From 871480933a1c28f8a9fed4c4d34d06c439a7a422 Mon Sep 17 00:00:00 2001 From: Srikant Patnaik Date: Sun, 11 Jan 2015 12:28:04 +0530 Subject: Moved, renamed, and deleted files The original directory structure was scattered and unorganized. Changes are basically to make it look like kernel structure. --- drivers/staging/Kconfig | 135 + drivers/staging/Makefile | 59 + drivers/staging/android/Kconfig | 92 + drivers/staging/android/Makefile | 15 + drivers/staging/android/alarm-dev.c | 384 + drivers/staging/android/android_alarm.h | 62 + drivers/staging/android/ashmem.c | 744 ++ drivers/staging/android/ashmem.h | 48 + drivers/staging/android/binder.c | 3666 +++++++ drivers/staging/android/binder.h | 330 + drivers/staging/android/binder_trace.h | 327 + drivers/staging/android/logger.c | 939 ++ drivers/staging/android/logger.h | 71 + drivers/staging/android/lowmemorykiller.c | 274 + drivers/staging/android/persistent_ram.c | 460 + drivers/staging/android/ram_console.c | 177 + drivers/staging/android/ram_console.h | 22 + drivers/staging/android/switch/Kconfig | 11 + drivers/staging/android/switch/Makefile | 4 + drivers/staging/android/switch/switch.h | 53 + drivers/staging/android/switch/switch_class.c | 174 + drivers/staging/android/switch/switch_gpio.c | 172 + drivers/staging/android/timed_gpio.c | 179 + drivers/staging/android/timed_gpio.h | 33 + drivers/staging/android/timed_output.c | 123 + drivers/staging/android/timed_output.h | 37 + drivers/staging/android/trace_persistent.c | 242 + drivers/staging/asus_oled/Kconfig | 6 + drivers/staging/asus_oled/Makefile | 1 + drivers/staging/asus_oled/README | 156 + drivers/staging/asus_oled/TODO | 10 + drivers/staging/asus_oled/asus_oled.c | 818 ++ drivers/staging/asus_oled/linux.txt | 33 + drivers/staging/asus_oled/linux_f.txt | 18 + drivers/staging/asus_oled/linux_fr.txt | 33 + drivers/staging/asus_oled/tux.txt | 33 + drivers/staging/asus_oled/tux_r.txt | 33 + drivers/staging/asus_oled/tux_r2.txt | 33 + drivers/staging/asus_oled/zig.txt | 33 + drivers/staging/bcm/Adapter.h | 648 ++ drivers/staging/bcm/Bcmchar.c | 2049 ++++ drivers/staging/bcm/Bcmnet.c | 239 + drivers/staging/bcm/CmHost.c | 1984 ++++ drivers/staging/bcm/CmHost.h | 163 + drivers/staging/bcm/DDRInit.c | 1289 +++ drivers/staging/bcm/DDRInit.h | 9 + drivers/staging/bcm/Debug.h | 259 + drivers/staging/bcm/HandleControlPacket.c | 235 + drivers/staging/bcm/HostMIBSInterface.h | 230 + drivers/staging/bcm/IPv6Protocol.c | 404 + drivers/staging/bcm/IPv6ProtocolHdr.h | 116 + drivers/staging/bcm/InterfaceAdapter.h | 97 + drivers/staging/bcm/InterfaceDld.c | 354 + drivers/staging/bcm/InterfaceIdleMode.c | 303 + drivers/staging/bcm/InterfaceIdleMode.h | 14 + drivers/staging/bcm/InterfaceInit.c | 695 ++ drivers/staging/bcm/InterfaceInit.h | 27 + drivers/staging/bcm/InterfaceIsr.c | 169 + drivers/staging/bcm/InterfaceIsr.h | 15 + drivers/staging/bcm/InterfaceMacros.h | 18 + drivers/staging/bcm/InterfaceMisc.c | 235 + drivers/staging/bcm/InterfaceMisc.h | 42 + drivers/staging/bcm/InterfaceRx.c | 264 + drivers/staging/bcm/InterfaceRx.h | 7 + drivers/staging/bcm/InterfaceTx.c | 197 + drivers/staging/bcm/InterfaceTx.h | 7 + drivers/staging/bcm/Ioctl.h | 360 + drivers/staging/bcm/Kconfig | 7 + drivers/staging/bcm/LeakyBucket.c | 353 + drivers/staging/bcm/Macros.h | 360 + drivers/staging/bcm/Makefile | 12 + drivers/staging/bcm/Misc.c | 1662 +++ drivers/staging/bcm/PHSDefines.h | 125 + drivers/staging/bcm/PHSModule.c | 1611 +++ drivers/staging/bcm/PHSModule.h | 50 + drivers/staging/bcm/Protocol.h | 151 + drivers/staging/bcm/Prototypes.h | 222 + drivers/staging/bcm/Qos.c | 855 ++ drivers/staging/bcm/Queue.h | 31 + drivers/staging/bcm/TODO | 22 + drivers/staging/bcm/Transmit.c | 260 + drivers/staging/bcm/Typedefs.h | 47 + drivers/staging/bcm/Version.h | 35 + drivers/staging/bcm/cntrl_SignalingInterface.h | 423 + drivers/staging/bcm/headers.h | 79 + drivers/staging/bcm/hostmibs.c | 141 + drivers/staging/bcm/led_control.c | 916 ++ drivers/staging/bcm/led_control.h | 102 + drivers/staging/bcm/nvm.c | 5222 +++++++++ drivers/staging/bcm/nvm.h | 409 + drivers/staging/bcm/sort.c | 52 + drivers/staging/bcm/target_params.h | 81 + drivers/staging/bcm/vendorspecificextn.c | 146 + drivers/staging/bcm/vendorspecificextn.h | 18 + drivers/staging/comedi/Kconfig | 1384 +++ drivers/staging/comedi/Makefile | 11 + drivers/staging/comedi/TODO | 12 + drivers/staging/comedi/comedi.h | 914 ++ drivers/staging/comedi/comedi_compat32.c | 463 + drivers/staging/comedi/comedi_compat32.h | 44 + drivers/staging/comedi/comedi_fops.c | 2706 +++++ drivers/staging/comedi/comedi_fops.h | 11 + drivers/staging/comedi/comedidev.h | 478 + drivers/staging/comedi/comedilib.h | 37 + drivers/staging/comedi/drivers.c | 909 ++ drivers/staging/comedi/drivers/8253.h | 433 + drivers/staging/comedi/drivers/8255.c | 467 + drivers/staging/comedi/drivers/8255.h | 39 + drivers/staging/comedi/drivers/Makefile | 140 + drivers/staging/comedi/drivers/acl7225b.c | 167 + .../comedi/drivers/addi-data/APCI1710_82x54.c | 1047 ++ .../comedi/drivers/addi-data/APCI1710_82x54.h | 73 + .../comedi/drivers/addi-data/APCI1710_Chrono.c | 2031 ++++ .../comedi/drivers/addi-data/APCI1710_Chrono.h | 74 + .../comedi/drivers/addi-data/APCI1710_Dig_io.c | 1025 ++ .../comedi/drivers/addi-data/APCI1710_Dig_io.h | 46 + .../comedi/drivers/addi-data/APCI1710_INCCPT.c | 5363 ++++++++++ .../comedi/drivers/addi-data/APCI1710_INCCPT.h | 271 + .../comedi/drivers/addi-data/APCI1710_Inp_cpt.c | 861 ++ .../comedi/drivers/addi-data/APCI1710_Inp_cpt.h | 47 + .../comedi/drivers/addi-data/APCI1710_Pwm.c | 3588 +++++++ .../comedi/drivers/addi-data/APCI1710_Pwm.h | 76 + .../comedi/drivers/addi-data/APCI1710_Ssi.c | 832 ++ .../comedi/drivers/addi-data/APCI1710_Ssi.h | 43 + .../comedi/drivers/addi-data/APCI1710_Tor.c | 2049 ++++ .../comedi/drivers/addi-data/APCI1710_Tor.h | 57 + .../comedi/drivers/addi-data/APCI1710_Ttl.c | 1038 ++ .../comedi/drivers/addi-data/APCI1710_Ttl.h | 44 + .../comedi/drivers/addi-data/addi_amcc_S5920.c | 195 + .../comedi/drivers/addi-data/addi_amcc_S5920.h | 27 + .../comedi/drivers/addi-data/addi_amcc_s5933.h | 475 + .../staging/comedi/drivers/addi-data/addi_common.c | 3108 ++++++ .../staging/comedi/drivers/addi-data/addi_common.h | 463 + .../staging/comedi/drivers/addi-data/addi_eeprom.c | 1162 ++ .../comedi/drivers/addi-data/amcc_s5933_58.h | 457 + .../comedi/drivers/addi-data/hwdrv_APCI1710.c | 1266 +++ .../comedi/drivers/addi-data/hwdrv_APCI1710.h | 71 + .../comedi/drivers/addi-data/hwdrv_apci035.c | 606 ++ .../comedi/drivers/addi-data/hwdrv_apci035.h | 109 + .../comedi/drivers/addi-data/hwdrv_apci1032.c | 287 + .../comedi/drivers/addi-data/hwdrv_apci1032.h | 64 + .../comedi/drivers/addi-data/hwdrv_apci1500.c | 3061 ++++++ .../comedi/drivers/addi-data/hwdrv_apci1500.h | 165 + .../comedi/drivers/addi-data/hwdrv_apci1516.c | 542 + .../comedi/drivers/addi-data/hwdrv_apci1516.h | 65 + .../comedi/drivers/addi-data/hwdrv_apci1564.c | 1128 ++ .../comedi/drivers/addi-data/hwdrv_apci1564.h | 121 + .../comedi/drivers/addi-data/hwdrv_apci16xx.c | 780 ++ .../comedi/drivers/addi-data/hwdrv_apci16xx.h | 94 + .../comedi/drivers/addi-data/hwdrv_apci2016.c | 460 + .../comedi/drivers/addi-data/hwdrv_apci2016.h | 72 + .../comedi/drivers/addi-data/hwdrv_apci2032.c | 579 + .../comedi/drivers/addi-data/hwdrv_apci2032.h | 83 + .../comedi/drivers/addi-data/hwdrv_apci2200.c | 549 + .../comedi/drivers/addi-data/hwdrv_apci2200.h | 61 + .../comedi/drivers/addi-data/hwdrv_apci3120.c | 2643 +++++ .../comedi/drivers/addi-data/hwdrv_apci3120.h | 249 + .../comedi/drivers/addi-data/hwdrv_apci3200.c | 3635 +++++++ .../comedi/drivers/addi-data/hwdrv_apci3200.h | 191 + .../comedi/drivers/addi-data/hwdrv_apci3501.c | 743 ++ .../comedi/drivers/addi-data/hwdrv_apci3501.h | 98 + .../comedi/drivers/addi-data/hwdrv_apci3xxx.c | 1664 +++ .../comedi/drivers/addi-data/hwdrv_apci3xxx.h | 62 + drivers/staging/comedi/drivers/addi_apci_035.c | 11 + drivers/staging/comedi/drivers/addi_apci_1032.c | 9 + drivers/staging/comedi/drivers/addi_apci_1500.c | 9 + drivers/staging/comedi/drivers/addi_apci_1516.c | 9 + drivers/staging/comedi/drivers/addi_apci_1564.c | 9 + drivers/staging/comedi/drivers/addi_apci_16xx.c | 9 + drivers/staging/comedi/drivers/addi_apci_1710.c | 5 + drivers/staging/comedi/drivers/addi_apci_2016.c | 9 + drivers/staging/comedi/drivers/addi_apci_2032.c | 9 + drivers/staging/comedi/drivers/addi_apci_2200.c | 9 + drivers/staging/comedi/drivers/addi_apci_3001.c | 9 + drivers/staging/comedi/drivers/addi_apci_3120.c | 9 + drivers/staging/comedi/drivers/addi_apci_3200.c | 5 + drivers/staging/comedi/drivers/addi_apci_3300.c | 5 + drivers/staging/comedi/drivers/addi_apci_3501.c | 9 + drivers/staging/comedi/drivers/addi_apci_3xxx.c | 9 + drivers/staging/comedi/drivers/addi_apci_all.c | 18 + drivers/staging/comedi/drivers/adl_pci6208.c | 447 + drivers/staging/comedi/drivers/adl_pci7230.c | 237 + drivers/staging/comedi/drivers/adl_pci7296.c | 217 + drivers/staging/comedi/drivers/adl_pci7432.c | 250 + drivers/staging/comedi/drivers/adl_pci8164.c | 429 + drivers/staging/comedi/drivers/adl_pci9111.c | 1475 +++ drivers/staging/comedi/drivers/adl_pci9118.c | 2471 +++++ drivers/staging/comedi/drivers/adq12b.c | 420 + drivers/staging/comedi/drivers/adv_pci1710.c | 1655 +++ drivers/staging/comedi/drivers/adv_pci1723.c | 538 + drivers/staging/comedi/drivers/adv_pci_dio.c | 1362 +++ drivers/staging/comedi/drivers/aio_aio12_8.c | 245 + drivers/staging/comedi/drivers/aio_iiro_16.c | 202 + drivers/staging/comedi/drivers/am9513.h | 79 + drivers/staging/comedi/drivers/amcc_s5933.h | 172 + drivers/staging/comedi/drivers/amplc_dio200.c | 1553 +++ drivers/staging/comedi/drivers/amplc_pc236.c | 717 ++ drivers/staging/comedi/drivers/amplc_pc263.c | 489 + drivers/staging/comedi/drivers/amplc_pci224.c | 1598 +++ drivers/staging/comedi/drivers/amplc_pci230.c | 3053 ++++++ drivers/staging/comedi/drivers/c6xdigio.c | 536 + drivers/staging/comedi/drivers/cb_das16_cs.c | 837 ++ drivers/staging/comedi/drivers/cb_pcidas.c | 1965 ++++ drivers/staging/comedi/drivers/cb_pcidas64.c | 4320 ++++++++ drivers/staging/comedi/drivers/cb_pcidda.c | 890 ++ drivers/staging/comedi/drivers/cb_pcidio.c | 333 + drivers/staging/comedi/drivers/cb_pcimdas.c | 528 + drivers/staging/comedi/drivers/cb_pcimdda.c | 517 + drivers/staging/comedi/drivers/comedi_bond.c | 504 + drivers/staging/comedi/drivers/comedi_fc.c | 119 + drivers/staging/comedi/drivers/comedi_fc.h | 76 + drivers/staging/comedi/drivers/comedi_parport.c | 413 + drivers/staging/comedi/drivers/comedi_pci.h | 60 + drivers/staging/comedi/drivers/comedi_test.c | 566 + drivers/staging/comedi/drivers/contec_pci_dio.c | 271 + drivers/staging/comedi/drivers/daqboard2000.c | 919 ++ drivers/staging/comedi/drivers/das08.c | 1139 ++ drivers/staging/comedi/drivers/das08.h | 79 + drivers/staging/comedi/drivers/das08_cs.c | 271 + drivers/staging/comedi/drivers/das16.c | 1791 ++++ drivers/staging/comedi/drivers/das16m1.c | 787 ++ drivers/staging/comedi/drivers/das1800.c | 1816 ++++ drivers/staging/comedi/drivers/das6402.c | 368 + drivers/staging/comedi/drivers/das800.c | 923 ++ drivers/staging/comedi/drivers/dmm32at.c | 1081 ++ drivers/staging/comedi/drivers/dt2801.c | 745 ++ drivers/staging/comedi/drivers/dt2811.c | 642 ++ drivers/staging/comedi/drivers/dt2814.c | 404 + drivers/staging/comedi/drivers/dt2815.c | 272 + drivers/staging/comedi/drivers/dt2817.c | 197 + drivers/staging/comedi/drivers/dt282x.c | 1520 +++ drivers/staging/comedi/drivers/dt3000.c | 1034 ++ drivers/staging/comedi/drivers/dt9812.c | 1169 ++ drivers/staging/comedi/drivers/dyna_pci10xx.c | 462 + drivers/staging/comedi/drivers/fl512.c | 222 + drivers/staging/comedi/drivers/gsc_hpdi.c | 1118 ++ drivers/staging/comedi/drivers/icp_multi.c | 1142 ++ drivers/staging/comedi/drivers/icp_multi.h | 298 + drivers/staging/comedi/drivers/ii_pci20kc.c | 684 ++ drivers/staging/comedi/drivers/jr3_pci.c | 1017 ++ drivers/staging/comedi/drivers/jr3_pci.h | 681 ++ drivers/staging/comedi/drivers/ke_counter.c | 297 + drivers/staging/comedi/drivers/me4000.c | 2467 +++++ drivers/staging/comedi/drivers/me4000.h | 446 + drivers/staging/comedi/drivers/me_daq.c | 894 ++ drivers/staging/comedi/drivers/mite.c | 830 ++ drivers/staging/comedi/drivers/mite.h | 480 + drivers/staging/comedi/drivers/mpc624.c | 424 + drivers/staging/comedi/drivers/mpc8260cpm.c | 186 + drivers/staging/comedi/drivers/multiq3.c | 355 + drivers/staging/comedi/drivers/ni_6527.c | 533 + drivers/staging/comedi/drivers/ni_65xx.c | 877 ++ drivers/staging/comedi/drivers/ni_660x.c | 1427 +++ drivers/staging/comedi/drivers/ni_670x.c | 390 + drivers/staging/comedi/drivers/ni_at_a2150.c | 925 ++ drivers/staging/comedi/drivers/ni_at_ao.c | 476 + drivers/staging/comedi/drivers/ni_atmio.c | 539 + drivers/staging/comedi/drivers/ni_atmio16d.c | 904 ++ drivers/staging/comedi/drivers/ni_daq_700.c | 605 ++ drivers/staging/comedi/drivers/ni_daq_dio24.c | 353 + drivers/staging/comedi/drivers/ni_labpc.c | 2196 ++++ drivers/staging/comedi/drivers/ni_labpc.h | 112 + drivers/staging/comedi/drivers/ni_labpc_cs.c | 320 + drivers/staging/comedi/drivers/ni_mio_common.c | 6005 +++++++++++ drivers/staging/comedi/drivers/ni_mio_cs.c | 459 + drivers/staging/comedi/drivers/ni_pcidio.c | 1399 +++ drivers/staging/comedi/drivers/ni_pcimio.c | 1879 ++++ drivers/staging/comedi/drivers/ni_stc.h | 1516 +++ drivers/staging/comedi/drivers/ni_tio.c | 1734 +++ drivers/staging/comedi/drivers/ni_tio.h | 174 + drivers/staging/comedi/drivers/ni_tio_internal.h | 784 ++ drivers/staging/comedi/drivers/ni_tiocmd.c | 533 + drivers/staging/comedi/drivers/pcl711.c | 645 ++ drivers/staging/comedi/drivers/pcl724.c | 238 + drivers/staging/comedi/drivers/pcl725.c | 127 + drivers/staging/comedi/drivers/pcl726.c | 398 + drivers/staging/comedi/drivers/pcl730.c | 183 + drivers/staging/comedi/drivers/pcl812.c | 1720 +++ drivers/staging/comedi/drivers/pcl816.c | 1360 +++ drivers/staging/comedi/drivers/pcl818.c | 2036 ++++ drivers/staging/comedi/drivers/pcm3724.c | 324 + drivers/staging/comedi/drivers/pcm3730.c | 171 + drivers/staging/comedi/drivers/pcm_common.c | 115 + drivers/staging/comedi/drivers/pcm_common.h | 8 + drivers/staging/comedi/drivers/pcmad.c | 193 + drivers/staging/comedi/drivers/pcmda12.c | 323 + drivers/staging/comedi/drivers/pcmmio.c | 1362 +++ drivers/staging/comedi/drivers/pcmuio.c | 1036 ++ drivers/staging/comedi/drivers/plx9052.h | 86 + drivers/staging/comedi/drivers/plx9080.h | 429 + drivers/staging/comedi/drivers/poc.c | 266 + drivers/staging/comedi/drivers/quatech_daqp_cs.c | 1123 ++ drivers/staging/comedi/drivers/rtd520.c | 2395 +++++ drivers/staging/comedi/drivers/rtd520.h | 412 + drivers/staging/comedi/drivers/rti800.c | 492 + drivers/staging/comedi/drivers/rti802.c | 167 + drivers/staging/comedi/drivers/s526.c | 1045 ++ drivers/staging/comedi/drivers/s626.c | 3369 ++++++ drivers/staging/comedi/drivers/s626.h | 727 ++ drivers/staging/comedi/drivers/serial2002.c | 919 ++ drivers/staging/comedi/drivers/skel.c | 677 ++ drivers/staging/comedi/drivers/ssv_dnp.c | 331 + drivers/staging/comedi/drivers/unioxx5.c | 558 + drivers/staging/comedi/drivers/usbdux.c | 2868 +++++ drivers/staging/comedi/drivers/usbduxfast.c | 1825 ++++ drivers/staging/comedi/drivers/usbduxsigma.c | 2885 +++++ drivers/staging/comedi/drivers/vmk80xx.c | 1561 +++ drivers/staging/comedi/internal.h | 12 + drivers/staging/comedi/kcomedilib/Makefile | 3 + .../staging/comedi/kcomedilib/kcomedilib_main.c | 195 + drivers/staging/comedi/proc.c | 99 + drivers/staging/comedi/range.c | 166 + drivers/staging/cptm1217/Kconfig | 12 + drivers/staging/cptm1217/Makefile | 2 + drivers/staging/cptm1217/TODO | 5 + drivers/staging/cptm1217/clearpad_tm1217.c | 676 ++ drivers/staging/cptm1217/cp_tm1217.h | 9 + drivers/staging/crystalhd/Kconfig | 6 + drivers/staging/crystalhd/Makefile | 6 + drivers/staging/crystalhd/TODO | 15 + drivers/staging/crystalhd/bc_dts_defs.h | 572 + drivers/staging/crystalhd/bc_dts_glob_lnx.h | 297 + drivers/staging/crystalhd/bcm_70012_regs.h | 757 ++ drivers/staging/crystalhd/crystalhd.h | 13 + drivers/staging/crystalhd/crystalhd_cmds.c | 1061 ++ drivers/staging/crystalhd/crystalhd_cmds.h | 87 + drivers/staging/crystalhd/crystalhd_fw_if.h | 363 + drivers/staging/crystalhd/crystalhd_hw.c | 2390 +++++ drivers/staging/crystalhd/crystalhd_hw.h | 390 + drivers/staging/crystalhd/crystalhd_lnx.c | 773 ++ drivers/staging/crystalhd/crystalhd_lnx.h | 94 + drivers/staging/crystalhd/crystalhd_misc.c | 1031 ++ drivers/staging/crystalhd/crystalhd_misc.h | 221 + drivers/staging/cxt1e1/Kconfig | 21 + drivers/staging/cxt1e1/Makefile | 19 + drivers/staging/cxt1e1/comet.c | 568 + drivers/staging/cxt1e1/comet.h | 344 + drivers/staging/cxt1e1/comet_tables.c | 537 + drivers/staging/cxt1e1/comet_tables.h | 61 + drivers/staging/cxt1e1/functions.c | 356 + drivers/staging/cxt1e1/hwprobe.c | 396 + drivers/staging/cxt1e1/libsbew.h | 549 + drivers/staging/cxt1e1/linux.c | 1188 +++ drivers/staging/cxt1e1/musycc.c | 1932 ++++ drivers/staging/cxt1e1/musycc.h | 429 + drivers/staging/cxt1e1/ossiRelease.c | 29 + drivers/staging/cxt1e1/pmc93x6_eeprom.c | 557 + drivers/staging/cxt1e1/pmc93x6_eeprom.h | 39 + drivers/staging/cxt1e1/pmcc4.h | 109 + drivers/staging/cxt1e1/pmcc4_cpld.h | 91 + drivers/staging/cxt1e1/pmcc4_defs.h | 68 + drivers/staging/cxt1e1/pmcc4_drv.c | 1632 +++ drivers/staging/cxt1e1/pmcc4_ioctls.h | 65 + drivers/staging/cxt1e1/pmcc4_private.h | 296 + drivers/staging/cxt1e1/pmcc4_sysdep.h | 62 + drivers/staging/cxt1e1/sbe_bid.h | 47 + drivers/staging/cxt1e1/sbe_promformat.h | 130 + drivers/staging/cxt1e1/sbecom_inline_linux.h | 214 + drivers/staging/cxt1e1/sbecrc.c | 137 + drivers/staging/cxt1e1/sbeid.c | 217 + drivers/staging/cxt1e1/sbeproc.c | 357 + drivers/staging/cxt1e1/sbeproc.h | 32 + drivers/staging/cxt1e1/sbew_ioc.h | 81 + drivers/staging/echo/Kconfig | 9 + drivers/staging/echo/Makefile | 1 + drivers/staging/echo/TODO | 5 + drivers/staging/echo/echo.c | 659 ++ drivers/staging/echo/echo.h | 175 + drivers/staging/echo/fir.h | 216 + drivers/staging/echo/oslec.h | 94 + drivers/staging/et131x/Kconfig | 10 + drivers/staging/et131x/Makefile | 5 + drivers/staging/et131x/README | 16 + drivers/staging/et131x/et131x.c | 5469 ++++++++++ drivers/staging/et131x/et131x.h | 1666 +++ drivers/staging/frontier/Kconfig | 6 + drivers/staging/frontier/Makefile | 2 + drivers/staging/frontier/README | 47 + drivers/staging/frontier/TODO | 9 + drivers/staging/frontier/alphatrack.c | 870 ++ drivers/staging/frontier/alphatrack.h | 76 + drivers/staging/frontier/tranzport.c | 974 ++ drivers/staging/ft1000/Kconfig | 22 + drivers/staging/ft1000/Makefile | 3 + drivers/staging/ft1000/TODO | 9 + drivers/staging/ft1000/ft1000-pcmcia/Makefile | 3 + drivers/staging/ft1000/ft1000-pcmcia/boot.h | 158 + drivers/staging/ft1000/ft1000-pcmcia/ft1000.h | 99 + drivers/staging/ft1000/ft1000-pcmcia/ft1000.img | Bin 0 -> 305770 bytes drivers/staging/ft1000/ft1000-pcmcia/ft1000_cs.c | 169 + drivers/staging/ft1000/ft1000-pcmcia/ft1000_dnld.c | 775 ++ drivers/staging/ft1000/ft1000-pcmcia/ft1000_hw.c | 2226 ++++ drivers/staging/ft1000/ft1000-pcmcia/ft1000_proc.c | 215 + drivers/staging/ft1000/ft1000-usb/Makefile | 3 + drivers/staging/ft1000/ft1000-usb/ft1000_debug.c | 782 ++ .../staging/ft1000/ft1000-usb/ft1000_download.c | 1235 +++ drivers/staging/ft1000/ft1000-usb/ft1000_hw.c | 1951 ++++ drivers/staging/ft1000/ft1000-usb/ft1000_ioctl.h | 107 + drivers/staging/ft1000/ft1000-usb/ft1000_proc.c | 251 + drivers/staging/ft1000/ft1000-usb/ft1000_usb.c | 266 + drivers/staging/ft1000/ft1000-usb/ft1000_usb.h | 190 + drivers/staging/ft1000/ft1000-usb/ft3000.img | Bin 0 -> 280414 bytes drivers/staging/ft1000/ft1000.h | 252 + drivers/staging/iio/Documentation/dac/max517 | 41 + drivers/staging/iio/Documentation/device.txt | 81 + drivers/staging/iio/Documentation/generic_buffer.c | 329 + .../staging/iio/Documentation/iio_event_monitor.c | 241 + drivers/staging/iio/Documentation/iio_utils.h | 639 ++ drivers/staging/iio/Documentation/inkernel.txt | 58 + drivers/staging/iio/Documentation/overview.txt | 57 + drivers/staging/iio/Documentation/ring.txt | 47 + drivers/staging/iio/Documentation/sysfs-bus-iio | 741 ++ .../iio/Documentation/sysfs-bus-iio-adc-ad7280a | 21 + .../staging/iio/Documentation/sysfs-bus-iio-dds | 93 + .../sysfs-bus-iio-impedance-analyzer-ad5933 | 30 + .../staging/iio/Documentation/sysfs-bus-iio-light | 85 + .../iio/Documentation/sysfs-bus-iio-light-tsl2583 | 20 + .../iio/Documentation/sysfs-bus-iio-trigger-sysfs | 11 + drivers/staging/iio/Documentation/trigger.txt | 38 + drivers/staging/iio/Kconfig | 107 + drivers/staging/iio/Makefile | 35 + drivers/staging/iio/TODO | 73 + drivers/staging/iio/accel/Kconfig | 105 + drivers/staging/iio/accel/Makefile | 35 + drivers/staging/iio/accel/adis16201.h | 141 + drivers/staging/iio/accel/adis16201_core.c | 556 + drivers/staging/iio/accel/adis16201_ring.c | 137 + drivers/staging/iio/accel/adis16201_trigger.c | 71 + drivers/staging/iio/accel/adis16203.h | 125 + drivers/staging/iio/accel/adis16203_core.c | 511 + drivers/staging/iio/accel/adis16203_ring.c | 140 + drivers/staging/iio/accel/adis16203_trigger.c | 73 + drivers/staging/iio/accel/adis16204.h | 133 + drivers/staging/iio/accel/adis16204_core.c | 585 + drivers/staging/iio/accel/adis16204_ring.c | 136 + drivers/staging/iio/accel/adis16204_trigger.c | 73 + drivers/staging/iio/accel/adis16209.h | 172 + drivers/staging/iio/accel/adis16209_core.c | 560 + drivers/staging/iio/accel/adis16209_ring.c | 137 + drivers/staging/iio/accel/adis16209_trigger.c | 81 + drivers/staging/iio/accel/adis16220.h | 140 + drivers/staging/iio/accel/adis16220_core.c | 716 ++ drivers/staging/iio/accel/adis16240.h | 196 + drivers/staging/iio/accel/adis16240_core.c | 613 ++ drivers/staging/iio/accel/adis16240_ring.c | 134 + drivers/staging/iio/accel/adis16240_trigger.c | 82 + drivers/staging/iio/accel/kxsd9.c | 289 + drivers/staging/iio/accel/lis3l02dq.h | 217 + drivers/staging/iio/accel/lis3l02dq_core.c | 810 ++ drivers/staging/iio/accel/lis3l02dq_ring.c | 432 + drivers/staging/iio/accel/sca3000.h | 269 + drivers/staging/iio/accel/sca3000_core.c | 1263 +++ drivers/staging/iio/accel/sca3000_ring.c | 364 + drivers/staging/iio/adc/Kconfig | 205 + drivers/staging/iio/adc/Makefile | 40 + drivers/staging/iio/adc/ad7192.c | 1161 ++ drivers/staging/iio/adc/ad7192.h | 47 + drivers/staging/iio/adc/ad7280a.c | 988 ++ drivers/staging/iio/adc/ad7280a.h | 38 + drivers/staging/iio/adc/ad7291.c | 714 ++ drivers/staging/iio/adc/ad7298.h | 71 + drivers/staging/iio/adc/ad7298_core.c | 285 + drivers/staging/iio/adc/ad7298_ring.c | 164 + drivers/staging/iio/adc/ad7476.h | 67 + drivers/staging/iio/adc/ad7476_core.c | 255 + drivers/staging/iio/adc/ad7476_ring.c | 132 + drivers/staging/iio/adc/ad7606.h | 104 + drivers/staging/iio/adc/ad7606_core.c | 615 ++ drivers/staging/iio/adc/ad7606_par.c | 180 + drivers/staging/iio/adc/ad7606_ring.c | 146 + drivers/staging/iio/adc/ad7606_spi.c | 116 + drivers/staging/iio/adc/ad7780.c | 290 + drivers/staging/iio/adc/ad7780.h | 30 + drivers/staging/iio/adc/ad7793.c | 1037 ++ drivers/staging/iio/adc/ad7793.h | 107 + drivers/staging/iio/adc/ad7816.c | 474 + drivers/staging/iio/adc/ad7887.h | 100 + drivers/staging/iio/adc/ad7887_core.c | 264 + drivers/staging/iio/adc/ad7887_ring.c | 161 + drivers/staging/iio/adc/ad799x.h | 141 + drivers/staging/iio/adc/ad799x_core.c | 936 ++ drivers/staging/iio/adc/ad799x_ring.c | 174 + drivers/staging/iio/adc/adt7310.c | 878 ++ drivers/staging/iio/adc/adt7410.c | 840 ++ drivers/staging/iio/adc/lpc32xx_adc.c | 237 + drivers/staging/iio/adc/max1363.h | 177 + drivers/staging/iio/adc/max1363_core.c | 1435 +++ drivers/staging/iio/adc/max1363_ring.c | 138 + drivers/staging/iio/addac/Kconfig | 28 + drivers/staging/iio/addac/Makefile | 7 + drivers/staging/iio/addac/adt7316-i2c.c | 143 + drivers/staging/iio/addac/adt7316-spi.c | 151 + drivers/staging/iio/addac/adt7316.c | 2236 ++++ drivers/staging/iio/addac/adt7316.h | 36 + drivers/staging/iio/buffer.h | 193 + drivers/staging/iio/cdc/Kconfig | 36 + drivers/staging/iio/cdc/Makefile | 7 + drivers/staging/iio/cdc/ad7150.c | 664 ++ drivers/staging/iio/cdc/ad7152.c | 547 + drivers/staging/iio/cdc/ad7746.c | 795 ++ drivers/staging/iio/cdc/ad7746.h | 29 + drivers/staging/iio/consumer.h | 96 + drivers/staging/iio/dac/Kconfig | 121 + drivers/staging/iio/dac/Makefile | 15 + drivers/staging/iio/dac/ad5064.c | 559 + drivers/staging/iio/dac/ad5360.c | 570 + drivers/staging/iio/dac/ad5380.c | 676 ++ drivers/staging/iio/dac/ad5421.c | 544 + drivers/staging/iio/dac/ad5421.h | 32 + drivers/staging/iio/dac/ad5446.c | 440 + drivers/staging/iio/dac/ad5446.h | 103 + drivers/staging/iio/dac/ad5504.c | 396 + drivers/staging/iio/dac/ad5504.h | 65 + drivers/staging/iio/dac/ad5624r.h | 79 + drivers/staging/iio/dac/ad5624r_spi.c | 353 + drivers/staging/iio/dac/ad5686.c | 454 + drivers/staging/iio/dac/ad5764.c | 382 + drivers/staging/iio/dac/ad5791.c | 420 + drivers/staging/iio/dac/ad5791.h | 112 + drivers/staging/iio/dac/dac.h | 6 + drivers/staging/iio/dac/max517.c | 293 + drivers/staging/iio/dac/max517.h | 19 + drivers/staging/iio/dds/Kconfig | 61 + drivers/staging/iio/dds/Makefile | 11 + drivers/staging/iio/dds/ad5930.c | 151 + drivers/staging/iio/dds/ad9832.c | 362 + drivers/staging/iio/dds/ad9832.h | 126 + drivers/staging/iio/dds/ad9834.c | 464 + drivers/staging/iio/dds/ad9834.h | 112 + drivers/staging/iio/dds/ad9850.c | 137 + drivers/staging/iio/dds/ad9852.c | 288 + drivers/staging/iio/dds/ad9910.c | 421 + drivers/staging/iio/dds/ad9951.c | 232 + drivers/staging/iio/dds/dds.h | 110 + drivers/staging/iio/driver.h | 34 + drivers/staging/iio/events.h | 105 + drivers/staging/iio/gyro/Kconfig | 49 + drivers/staging/iio/gyro/Makefile | 22 + drivers/staging/iio/gyro/adis16060_core.c | 259 + drivers/staging/iio/gyro/adis16080_core.c | 197 + drivers/staging/iio/gyro/adis16130_core.c | 176 + drivers/staging/iio/gyro/adis16260.h | 156 + drivers/staging/iio/gyro/adis16260_core.c | 723 ++ drivers/staging/iio/gyro/adis16260_platform_data.h | 19 + drivers/staging/iio/gyro/adis16260_ring.c | 138 + drivers/staging/iio/gyro/adis16260_trigger.c | 75 + drivers/staging/iio/gyro/adxrs450.h | 62 + drivers/staging/iio/gyro/adxrs450_core.c | 436 + drivers/staging/iio/iio.h | 471 + drivers/staging/iio/iio_core.h | 56 + drivers/staging/iio/iio_core_trigger.h | 46 + drivers/staging/iio/iio_dummy_evgen.c | 221 + drivers/staging/iio/iio_dummy_evgen.h | 2 + drivers/staging/iio/iio_hwmon.c | 232 + drivers/staging/iio/iio_simple_dummy.c | 550 + drivers/staging/iio/iio_simple_dummy.h | 108 + drivers/staging/iio/iio_simple_dummy_buffer.c | 205 + drivers/staging/iio/iio_simple_dummy_events.c | 191 + drivers/staging/iio/impedance-analyzer/Kconfig | 18 + drivers/staging/iio/impedance-analyzer/Makefile | 5 + drivers/staging/iio/impedance-analyzer/ad5933.c | 802 ++ drivers/staging/iio/impedance-analyzer/ad5933.h | 28 + drivers/staging/iio/imu/Kconfig | 17 + drivers/staging/iio/imu/Makefile | 7 + drivers/staging/iio/imu/adis16400.h | 230 + drivers/staging/iio/imu/adis16400_core.c | 1238 +++ drivers/staging/iio/imu/adis16400_ring.c | 210 + drivers/staging/iio/imu/adis16400_trigger.c | 74 + drivers/staging/iio/industrialio-buffer.c | 734 ++ drivers/staging/iio/industrialio-core.c | 927 ++ drivers/staging/iio/industrialio-event.c | 459 + drivers/staging/iio/industrialio-trigger.c | 509 + drivers/staging/iio/inkern.c | 293 + drivers/staging/iio/kfifo_buf.c | 151 + drivers/staging/iio/kfifo_buf.h | 8 + drivers/staging/iio/light/Kconfig | 34 + drivers/staging/iio/light/Makefile | 7 + drivers/staging/iio/light/isl29018.c | 615 ++ drivers/staging/iio/light/tsl2563.c | 895 ++ drivers/staging/iio/light/tsl2563.h | 9 + drivers/staging/iio/light/tsl2583.c | 956 ++ drivers/staging/iio/machine.h | 24 + drivers/staging/iio/magnetometer/Kconfig | 27 + drivers/staging/iio/magnetometer/Makefile | 6 + drivers/staging/iio/magnetometer/ak8975.c | 589 ++ drivers/staging/iio/magnetometer/hmc5843.c | 634 ++ drivers/staging/iio/meter/Kconfig | 63 + drivers/staging/iio/meter/Makefile | 15 + drivers/staging/iio/meter/ade7753.c | 585 + drivers/staging/iio/meter/ade7753.h | 74 + drivers/staging/iio/meter/ade7754.c | 608 ++ drivers/staging/iio/meter/ade7754.h | 92 + drivers/staging/iio/meter/ade7758.h | 187 + drivers/staging/iio/meter/ade7758_core.c | 861 ++ drivers/staging/iio/meter/ade7758_ring.c | 205 + drivers/staging/iio/meter/ade7758_trigger.c | 109 + drivers/staging/iio/meter/ade7759.c | 529 + drivers/staging/iio/meter/ade7759.h | 55 + drivers/staging/iio/meter/ade7854-i2c.c | 260 + drivers/staging/iio/meter/ade7854-spi.c | 361 + drivers/staging/iio/meter/ade7854.c | 601 ++ drivers/staging/iio/meter/ade7854.h | 176 + drivers/staging/iio/meter/meter.h | 396 + drivers/staging/iio/resolver/Kconfig | 30 + drivers/staging/iio/resolver/Makefile | 7 + drivers/staging/iio/resolver/ad2s1200.c | 178 + drivers/staging/iio/resolver/ad2s1210.c | 767 ++ drivers/staging/iio/resolver/ad2s1210.h | 17 + drivers/staging/iio/resolver/ad2s90.c | 127 + drivers/staging/iio/ring_hw.h | 22 + drivers/staging/iio/ring_sw.c | 367 + drivers/staging/iio/ring_sw.h | 30 + drivers/staging/iio/sysfs.h | 117 + drivers/staging/iio/trigger.h | 119 + drivers/staging/iio/trigger/Kconfig | 42 + drivers/staging/iio/trigger/Makefile | 8 + drivers/staging/iio/trigger/iio-trig-bfin-timer.c | 240 + drivers/staging/iio/trigger/iio-trig-gpio.c | 167 + .../staging/iio/trigger/iio-trig-periodic-rtc.c | 202 + drivers/staging/iio/trigger/iio-trig-sysfs.c | 212 + drivers/staging/iio/trigger_consumer.h | 52 + drivers/staging/iio/types.h | 53 + drivers/staging/keucr/Kconfig | 14 + drivers/staging/keucr/Makefile | 13 + drivers/staging/keucr/TODO | 12 + drivers/staging/keucr/common.h | 15 + drivers/staging/keucr/init.c | 373 + drivers/staging/keucr/init.h | 523 + drivers/staging/keucr/scsiglue.c | 483 + drivers/staging/keucr/scsiglue.h | 10 + drivers/staging/keucr/smcommon.h | 31 + drivers/staging/keucr/smil.h | 311 + drivers/staging/keucr/smilecc.c | 211 + drivers/staging/keucr/smilmain.c | 1850 ++++ drivers/staging/keucr/smilsub.c | 693 ++ drivers/staging/keucr/smscsi.c | 188 + drivers/staging/keucr/transport.c | 823 ++ drivers/staging/keucr/transport.h | 74 + drivers/staging/keucr/usb.c | 704 ++ drivers/staging/keucr/usb.h | 237 + drivers/staging/line6/Kconfig | 85 + drivers/staging/line6/Makefile | 16 + drivers/staging/line6/audio.c | 74 + drivers/staging/line6/audio.h | 21 + drivers/staging/line6/capture.c | 435 + drivers/staging/line6/capture.h | 35 + drivers/staging/line6/config.h | 48 + drivers/staging/line6/control.c | 995 ++ drivers/staging/line6/control.h | 195 + drivers/staging/line6/driver.c | 1362 +++ drivers/staging/line6/driver.h | 237 + drivers/staging/line6/dumprequest.c | 135 + drivers/staging/line6/dumprequest.h | 76 + drivers/staging/line6/midi.c | 446 + drivers/staging/line6/midi.h | 82 + drivers/staging/line6/midibuf.c | 264 + drivers/staging/line6/midibuf.h | 38 + drivers/staging/line6/pcm.c | 564 + drivers/staging/line6/pcm.h | 382 + drivers/staging/line6/playback.c | 586 + drivers/staging/line6/playback.h | 41 + drivers/staging/line6/pod.c | 1351 +++ drivers/staging/line6/pod.h | 205 + drivers/staging/line6/podhd.c | 154 + drivers/staging/line6/podhd.h | 30 + drivers/staging/line6/revision.h | 4 + drivers/staging/line6/toneport.c | 455 + drivers/staging/line6/toneport.h | 52 + drivers/staging/line6/usbdefs.h | 107 + drivers/staging/line6/variax.c | 719 ++ drivers/staging/line6/variax.h | 132 + drivers/staging/media/Kconfig | 37 + drivers/staging/media/Makefile | 7 + drivers/staging/media/as102/Kconfig | 8 + drivers/staging/media/as102/Makefile | 6 + drivers/staging/media/as102/as102_drv.c | 319 + drivers/staging/media/as102/as102_drv.h | 107 + drivers/staging/media/as102/as102_fe.c | 598 ++ drivers/staging/media/as102/as102_fw.c | 241 + drivers/staging/media/as102/as102_fw.h | 38 + drivers/staging/media/as102/as102_usb_drv.c | 493 + drivers/staging/media/as102/as102_usb_drv.h | 63 + drivers/staging/media/as102/as10x_cmd.c | 453 + drivers/staging/media/as102/as10x_cmd.h | 529 + drivers/staging/media/as102/as10x_cmd_cfg.c | 215 + drivers/staging/media/as102/as10x_cmd_stream.c | 223 + drivers/staging/media/as102/as10x_handle.h | 54 + drivers/staging/media/as102/as10x_types.h | 194 + drivers/staging/media/cxd2099/Kconfig | 12 + drivers/staging/media/cxd2099/Makefile | 5 + drivers/staging/media/cxd2099/TODO | 12 + drivers/staging/media/cxd2099/cxd2099.c | 716 ++ drivers/staging/media/cxd2099/cxd2099.h | 51 + drivers/staging/media/dt3155v4l/Kconfig | 28 + drivers/staging/media/dt3155v4l/Makefile | 1 + drivers/staging/media/dt3155v4l/dt3155v4l.c | 988 ++ drivers/staging/media/dt3155v4l/dt3155v4l.h | 212 + drivers/staging/media/easycap/Kconfig | 30 + drivers/staging/media/easycap/Makefile | 10 + drivers/staging/media/easycap/README | 141 + drivers/staging/media/easycap/easycap.h | 567 + drivers/staging/media/easycap/easycap_ioctl.c | 2442 +++++ drivers/staging/media/easycap/easycap_low.c | 968 ++ drivers/staging/media/easycap/easycap_main.c | 4077 +++++++ drivers/staging/media/easycap/easycap_settings.c | 696 ++ drivers/staging/media/easycap/easycap_sound.c | 750 ++ drivers/staging/media/easycap/easycap_testcard.c | 155 + drivers/staging/media/go7007/Kconfig | 109 + drivers/staging/media/go7007/Makefile | 30 + drivers/staging/media/go7007/README | 11 + drivers/staging/media/go7007/go7007-driver.c | 658 ++ drivers/staging/media/go7007/go7007-fw.c | 1636 +++ drivers/staging/media/go7007/go7007-i2c.c | 224 + drivers/staging/media/go7007/go7007-priv.h | 290 + drivers/staging/media/go7007/go7007-usb.c | 1282 +++ drivers/staging/media/go7007/go7007-v4l2.c | 1838 ++++ drivers/staging/media/go7007/go7007.h | 114 + drivers/staging/media/go7007/go7007.txt | 481 + drivers/staging/media/go7007/s2250-board.c | 702 ++ drivers/staging/media/go7007/s2250-loader.c | 191 + drivers/staging/media/go7007/s2250-loader.h | 24 + drivers/staging/media/go7007/saa7134-go7007.c | 532 + drivers/staging/media/go7007/snd-go7007.c | 305 + drivers/staging/media/go7007/wis-i2c.h | 47 + drivers/staging/media/go7007/wis-ov7640.c | 108 + drivers/staging/media/go7007/wis-saa7113.c | 336 + drivers/staging/media/go7007/wis-saa7115.c | 469 + drivers/staging/media/go7007/wis-sony-tuner.c | 720 ++ drivers/staging/media/go7007/wis-tw2804.c | 357 + drivers/staging/media/go7007/wis-tw9903.c | 341 + drivers/staging/media/go7007/wis-uda1342.c | 114 + drivers/staging/media/lirc/Kconfig | 78 + drivers/staging/media/lirc/Makefile | 14 + drivers/staging/media/lirc/TODO | 8 + drivers/staging/media/lirc/TODO.lirc_zilog | 36 + drivers/staging/media/lirc/lirc_bt829.c | 383 + drivers/staging/media/lirc/lirc_ene0100.h | 169 + drivers/staging/media/lirc/lirc_igorplugusb.c | 558 + drivers/staging/media/lirc/lirc_imon.c | 1028 ++ drivers/staging/media/lirc/lirc_parallel.c | 755 ++ drivers/staging/media/lirc/lirc_parallel.h | 26 + drivers/staging/media/lirc/lirc_sasem.c | 917 ++ drivers/staging/media/lirc/lirc_serial.c | 1309 +++ drivers/staging/media/lirc/lirc_sir.c | 1278 +++ drivers/staging/media/lirc/lirc_ttusbir.c | 375 + drivers/staging/media/lirc/lirc_zilog.c | 1676 +++ drivers/staging/media/solo6x10/Kconfig | 8 + drivers/staging/media/solo6x10/Makefile | 3 + drivers/staging/media/solo6x10/TODO | 24 + drivers/staging/media/solo6x10/core.c | 332 + drivers/staging/media/solo6x10/disp.c | 270 + drivers/staging/media/solo6x10/enc.c | 238 + drivers/staging/media/solo6x10/g723.c | 400 + drivers/staging/media/solo6x10/gpio.c | 102 + drivers/staging/media/solo6x10/i2c.c | 330 + drivers/staging/media/solo6x10/offsets.h | 74 + drivers/staging/media/solo6x10/osd-font.h | 154 + drivers/staging/media/solo6x10/p2m.c | 306 + drivers/staging/media/solo6x10/registers.h | 637 ++ drivers/staging/media/solo6x10/solo6x10-jpeg.h | 105 + drivers/staging/media/solo6x10/solo6x10.h | 336 + drivers/staging/media/solo6x10/tw28.c | 821 ++ drivers/staging/media/solo6x10/tw28.h | 63 + drivers/staging/media/solo6x10/v4l2-enc.c | 1825 ++++ drivers/staging/media/solo6x10/v4l2.c | 964 ++ drivers/staging/mei/Kconfig | 28 + drivers/staging/mei/Makefile | 11 + drivers/staging/mei/TODO | 10 + drivers/staging/mei/hw.h | 332 + drivers/staging/mei/init.c | 735 ++ drivers/staging/mei/interface.c | 428 + drivers/staging/mei/interface.h | 76 + drivers/staging/mei/interrupt.c | 1590 +++ drivers/staging/mei/iorw.c | 590 ++ drivers/staging/mei/main.c | 1237 +++ drivers/staging/mei/mei-amt-version.c | 481 + drivers/staging/mei/mei.h | 110 + drivers/staging/mei/mei.txt | 215 + drivers/staging/mei/mei_dev.h | 428 + drivers/staging/mei/wd.c | 379 + drivers/staging/nvec/Kconfig | 33 + drivers/staging/nvec/Makefile | 5 + drivers/staging/nvec/README | 14 + drivers/staging/nvec/TODO | 12 + drivers/staging/nvec/nvec-keytable.h | 307 + drivers/staging/nvec/nvec.c | 942 ++ drivers/staging/nvec/nvec.h | 203 + drivers/staging/nvec/nvec_kbd.c | 179 + drivers/staging/nvec/nvec_leds.c | 114 + drivers/staging/nvec/nvec_power.c | 426 + drivers/staging/nvec/nvec_ps2.c | 166 + drivers/staging/octeon/Kconfig | 13 + drivers/staging/octeon/Makefile | 23 + drivers/staging/octeon/ethernet-defines.h | 106 + drivers/staging/octeon/ethernet-mdio.c | 182 + drivers/staging/octeon/ethernet-mdio.h | 45 + drivers/staging/octeon/ethernet-mem.c | 177 + drivers/staging/octeon/ethernet-mem.h | 29 + drivers/staging/octeon/ethernet-rgmii.c | 430 + drivers/staging/octeon/ethernet-rx.c | 563 + drivers/staging/octeon/ethernet-rx.h | 52 + drivers/staging/octeon/ethernet-sgmii.c | 127 + drivers/staging/octeon/ethernet-spi.c | 324 + drivers/staging/octeon/ethernet-tx.c | 725 ++ drivers/staging/octeon/ethernet-tx.h | 34 + drivers/staging/octeon/ethernet-util.h | 72 + drivers/staging/octeon/ethernet-xaui.c | 125 + drivers/staging/octeon/ethernet.c | 844 ++ drivers/staging/octeon/octeon-ethernet.h | 98 + drivers/staging/olpc_dcon/Kconfig | 29 + drivers/staging/olpc_dcon/Makefile | 6 + drivers/staging/olpc_dcon/TODO | 16 + drivers/staging/olpc_dcon/olpc_dcon.c | 842 ++ drivers/staging/olpc_dcon/olpc_dcon.h | 102 + drivers/staging/olpc_dcon/olpc_dcon_xo_1.c | 202 + drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c | 189 + drivers/staging/omapdrm/Kconfig | 25 + drivers/staging/omapdrm/Makefile | 22 + drivers/staging/omapdrm/TODO | 38 + drivers/staging/omapdrm/omap_connector.c | 371 + drivers/staging/omapdrm/omap_crtc.c | 248 + drivers/staging/omapdrm/omap_debugfs.c | 137 + drivers/staging/omapdrm/omap_dmm_priv.h | 187 + drivers/staging/omapdrm/omap_dmm_tiler.c | 873 ++ drivers/staging/omapdrm/omap_dmm_tiler.h | 134 + drivers/staging/omapdrm/omap_drm.h | 123 + drivers/staging/omapdrm/omap_drv.c | 853 ++ drivers/staging/omapdrm/omap_drv.h | 190 + drivers/staging/omapdrm/omap_encoder.c | 171 + drivers/staging/omapdrm/omap_fb.c | 399 + drivers/staging/omapdrm/omap_fbdev.c | 405 + drivers/staging/omapdrm/omap_gem.c | 1328 +++ drivers/staging/omapdrm/omap_gem_helpers.c | 169 + drivers/staging/omapdrm/omap_plane.c | 487 + drivers/staging/omapdrm/omap_priv.h | 55 + drivers/staging/omapdrm/tcm-sita.c | 703 ++ drivers/staging/omapdrm/tcm-sita.h | 95 + drivers/staging/omapdrm/tcm.h | 326 + drivers/staging/ozwpan/Kbuild | 19 + drivers/staging/ozwpan/Kconfig | 9 + drivers/staging/ozwpan/README | 25 + drivers/staging/ozwpan/TODO | 14 + drivers/staging/ozwpan/ozappif.h | 46 + drivers/staging/ozwpan/ozcdev.c | 521 + drivers/staging/ozwpan/ozcdev.h | 18 + drivers/staging/ozwpan/ozconfig.h | 27 + drivers/staging/ozwpan/ozeltbuf.c | 339 + drivers/staging/ozwpan/ozeltbuf.h | 70 + drivers/staging/ozwpan/ozevent.c | 116 + drivers/staging/ozwpan/ozevent.h | 31 + drivers/staging/ozwpan/ozeventdef.h | 47 + drivers/staging/ozwpan/ozhcd.c | 2256 ++++ drivers/staging/ozwpan/ozhcd.h | 15 + drivers/staging/ozwpan/ozmain.c | 58 + drivers/staging/ozwpan/ozpd.c | 830 ++ drivers/staging/ozwpan/ozpd.h | 121 + drivers/staging/ozwpan/ozproto.c | 957 ++ drivers/staging/ozwpan/ozproto.h | 69 + drivers/staging/ozwpan/ozprotocol.h | 372 + drivers/staging/ozwpan/oztrace.c | 36 + drivers/staging/ozwpan/oztrace.h | 35 + drivers/staging/ozwpan/ozurbparanoia.c | 53 + drivers/staging/ozwpan/ozurbparanoia.h | 19 + drivers/staging/ozwpan/ozusbif.h | 43 + drivers/staging/ozwpan/ozusbsvc.c | 245 + drivers/staging/ozwpan/ozusbsvc.h | 32 + drivers/staging/ozwpan/ozusbsvc1.c | 437 + drivers/staging/panel/Kconfig | 278 + drivers/staging/panel/Makefile | 1 + drivers/staging/panel/TODO | 9 + drivers/staging/panel/lcd-panel-cgram.txt | 24 + drivers/staging/panel/panel.c | 2357 +++++ drivers/staging/phison/Kconfig | 5 + drivers/staging/phison/Makefile | 1 + drivers/staging/phison/phison.c | 106 + drivers/staging/quatech_usb2/Kconfig | 15 + drivers/staging/quatech_usb2/Makefile | 1 + drivers/staging/quatech_usb2/TODO | 8 + drivers/staging/quatech_usb2/quatech_usb2.c | 1976 ++++ drivers/staging/quickstart/Kconfig | 10 + drivers/staging/quickstart/Makefile | 1 + drivers/staging/quickstart/quickstart.c | 465 + drivers/staging/ramster/Kconfig | 13 + drivers/staging/ramster/Makefile | 1 + drivers/staging/ramster/TODO | 13 + drivers/staging/ramster/cluster/Makefile | 3 + drivers/staging/ramster/cluster/heartbeat.c | 464 + drivers/staging/ramster/cluster/heartbeat.h | 87 + drivers/staging/ramster/cluster/masklog.c | 155 + drivers/staging/ramster/cluster/masklog.h | 220 + drivers/staging/ramster/cluster/nodemanager.c | 992 ++ drivers/staging/ramster/cluster/nodemanager.h | 88 + .../staging/ramster/cluster/ramster_nodemanager.h | 39 + drivers/staging/ramster/cluster/tcp.c | 2256 ++++ drivers/staging/ramster/cluster/tcp.h | 159 + drivers/staging/ramster/cluster/tcp_internal.h | 248 + drivers/staging/ramster/r2net.c | 401 + drivers/staging/ramster/ramster.h | 118 + drivers/staging/ramster/tmem.c | 851 ++ drivers/staging/ramster/tmem.h | 244 + drivers/staging/ramster/xvmalloc.c | 509 + drivers/staging/ramster/xvmalloc.h | 30 + drivers/staging/ramster/xvmalloc_int.h | 95 + drivers/staging/ramster/zcache-main.c | 3320 ++++++ drivers/staging/ramster/zcache.h | 22 + drivers/staging/rtl8187se/Kconfig | 11 + drivers/staging/rtl8187se/Makefile | 38 + drivers/staging/rtl8187se/TODO | 13 + drivers/staging/rtl8187se/ieee80211/dot11d.c | 224 + drivers/staging/rtl8187se/ieee80211/dot11d.h | 100 + drivers/staging/rtl8187se/ieee80211/ieee80211.h | 1483 +++ .../staging/rtl8187se/ieee80211/ieee80211_crypt.c | 244 + .../staging/rtl8187se/ieee80211/ieee80211_crypt.h | 86 + .../rtl8187se/ieee80211/ieee80211_crypt_ccmp.c | 464 + .../rtl8187se/ieee80211/ieee80211_crypt_tkip.c | 751 ++ .../rtl8187se/ieee80211/ieee80211_crypt_wep.c | 293 + .../staging/rtl8187se/ieee80211/ieee80211_module.c | 206 + drivers/staging/rtl8187se/ieee80211/ieee80211_rx.c | 1544 +++ .../rtl8187se/ieee80211/ieee80211_softmac.c | 3008 ++++++ .../rtl8187se/ieee80211/ieee80211_softmac_wx.c | 567 + drivers/staging/rtl8187se/ieee80211/ieee80211_tx.c | 583 + drivers/staging/rtl8187se/ieee80211/ieee80211_wx.c | 747 ++ drivers/staging/rtl8187se/r8180.h | 718 ++ drivers/staging/rtl8187se/r8180_93cx6.h | 54 + drivers/staging/rtl8187se/r8180_core.c | 4172 ++++++++ drivers/staging/rtl8187se/r8180_dm.c | 1141 ++ drivers/staging/rtl8187se/r8180_dm.h | 23 + drivers/staging/rtl8187se/r8180_hw.h | 583 + drivers/staging/rtl8187se/r8180_rtl8225.h | 34 + drivers/staging/rtl8187se/r8180_rtl8225z2.c | 1055 ++ drivers/staging/rtl8187se/r8180_wx.c | 1411 +++ drivers/staging/rtl8187se/r8180_wx.h | 21 + drivers/staging/rtl8187se/r8185b_init.c | 1768 ++++ drivers/staging/rtl8192e/Kconfig | 43 + drivers/staging/rtl8192e/Makefile | 21 + drivers/staging/rtl8192e/TODO | 2 + drivers/staging/rtl8192e/dot11d.c | 218 + drivers/staging/rtl8192e/dot11d.h | 105 + drivers/staging/rtl8192e/license | 339 + drivers/staging/rtl8192e/rtl8192e/Kconfig | 9 + drivers/staging/rtl8192e/rtl8192e/Makefile | 21 + drivers/staging/rtl8192e/rtl8192e/r8190P_def.h | 410 + drivers/staging/rtl8192e/rtl8192e/r8190P_rtl8256.c | 306 + drivers/staging/rtl8192e/rtl8192e/r8190P_rtl8256.h | 31 + drivers/staging/rtl8192e/rtl8192e/r8192E_cmdpkt.c | 418 + drivers/staging/rtl8192e/rtl8192e/r8192E_cmdpkt.h | 159 + drivers/staging/rtl8192e/rtl8192e/r8192E_dev.c | 2395 +++++ drivers/staging/rtl8192e/rtl8192e/r8192E_dev.h | 62 + .../staging/rtl8192e/rtl8192e/r8192E_firmware.c | 348 + .../staging/rtl8192e/rtl8192e/r8192E_firmware.h | 73 + drivers/staging/rtl8192e/rtl8192e/r8192E_hw.h | 453 + drivers/staging/rtl8192e/rtl8192e/r8192E_hwimg.c | 3336 ++++++ drivers/staging/rtl8192e/rtl8192e/r8192E_hwimg.h | 51 + drivers/staging/rtl8192e/rtl8192e/r8192E_phy.c | 1636 +++ drivers/staging/rtl8192e/rtl8192e/r8192E_phy.h | 120 + drivers/staging/rtl8192e/rtl8192e/r8192E_phyreg.h | 852 ++ drivers/staging/rtl8192e/rtl8192e/r819xE_phyreg.h | 915 ++ drivers/staging/rtl8192e/rtl8192e/rtl_cam.c | 304 + drivers/staging/rtl8192e/rtl8192e/rtl_cam.h | 46 + drivers/staging/rtl8192e/rtl8192e/rtl_core.c | 3136 ++++++ drivers/staging/rtl8192e/rtl8192e/rtl_core.h | 1095 ++ drivers/staging/rtl8192e/rtl8192e/rtl_crypto.h | 382 + drivers/staging/rtl8192e/rtl8192e/rtl_debug.c | 1029 ++ drivers/staging/rtl8192e/rtl8192e/rtl_dm.c | 2995 ++++++ drivers/staging/rtl8192e/rtl8192e/rtl_dm.h | 324 + drivers/staging/rtl8192e/rtl8192e/rtl_eeprom.c | 139 + drivers/staging/rtl8192e/rtl8192e/rtl_eeprom.h | 29 + drivers/staging/rtl8192e/rtl8192e/rtl_ethtool.c | 53 + drivers/staging/rtl8192e/rtl8192e/rtl_pci.c | 97 + drivers/staging/rtl8192e/rtl8192e/rtl_pci.h | 103 + drivers/staging/rtl8192e/rtl8192e/rtl_pm.c | 134 + drivers/staging/rtl8192e/rtl8192e/rtl_pm.h | 31 + drivers/staging/rtl8192e/rtl8192e/rtl_ps.c | 310 + drivers/staging/rtl8192e/rtl8192e/rtl_ps.h | 47 + drivers/staging/rtl8192e/rtl8192e/rtl_wx.c | 1332 +++ drivers/staging/rtl8192e/rtl8192e/rtl_wx.h | 31 + drivers/staging/rtl8192e/rtl819x_BA.h | 77 + drivers/staging/rtl8192e/rtl819x_BAProc.c | 565 + drivers/staging/rtl8192e/rtl819x_HT.h | 475 + drivers/staging/rtl8192e/rtl819x_HTProc.c | 1076 ++ drivers/staging/rtl8192e/rtl819x_Qos.h | 444 + drivers/staging/rtl8192e/rtl819x_TS.h | 73 + drivers/staging/rtl8192e/rtl819x_TSProc.c | 549 + drivers/staging/rtl8192e/rtllib.h | 3053 ++++++ drivers/staging/rtl8192e/rtllib_crypt.c | 251 + drivers/staging/rtl8192e/rtllib_crypt.h | 35 + drivers/staging/rtl8192e/rtllib_crypt_ccmp.c | 461 + drivers/staging/rtl8192e/rtllib_crypt_tkip.c | 769 ++ drivers/staging/rtl8192e/rtllib_crypt_wep.c | 289 + drivers/staging/rtl8192e/rtllib_debug.h | 86 + drivers/staging/rtl8192e/rtllib_endianfree.h | 160 + drivers/staging/rtl8192e/rtllib_module.c | 281 + drivers/staging/rtl8192e/rtllib_rx.c | 2721 +++++ drivers/staging/rtl8192e/rtllib_softmac.c | 3754 +++++++ drivers/staging/rtl8192e/rtllib_softmac_wx.c | 662 ++ drivers/staging/rtl8192e/rtllib_tx.c | 970 ++ drivers/staging/rtl8192e/rtllib_wx.c | 879 ++ drivers/staging/rtl8192u/Kconfig | 9 + drivers/staging/rtl8192u/Makefile | 32 + drivers/staging/rtl8192u/authors | 1 + drivers/staging/rtl8192u/changes | 5 + drivers/staging/rtl8192u/copying | 340 + drivers/staging/rtl8192u/dot11d.h | 100 + drivers/staging/rtl8192u/ieee80211/EndianFree.h | 194 + drivers/staging/rtl8192u/ieee80211/Makefile | 28 + drivers/staging/rtl8192u/ieee80211/aes.c | 469 + drivers/staging/rtl8192u/ieee80211/arc4.c | 103 + drivers/staging/rtl8192u/ieee80211/autoload.c | 40 + drivers/staging/rtl8192u/ieee80211/cipher.c | 298 + drivers/staging/rtl8192u/ieee80211/compress.c | 64 + drivers/staging/rtl8192u/ieee80211/crypto_compat.h | 60 + drivers/staging/rtl8192u/ieee80211/digest.c | 108 + drivers/staging/rtl8192u/ieee80211/dot11d.c | 221 + drivers/staging/rtl8192u/ieee80211/dot11d.h | 99 + drivers/staging/rtl8192u/ieee80211/ieee80211.h | 2629 +++++ .../staging/rtl8192u/ieee80211/ieee80211_crypt.c | 241 + .../staging/rtl8192u/ieee80211/ieee80211_crypt.h | 86 + .../rtl8192u/ieee80211/ieee80211_crypt_ccmp.c | 475 + .../rtl8192u/ieee80211/ieee80211_crypt_tkip.c | 778 ++ .../rtl8192u/ieee80211/ieee80211_crypt_wep.c | 295 + .../staging/rtl8192u/ieee80211/ieee80211_module.c | 317 + drivers/staging/rtl8192u/ieee80211/ieee80211_rx.c | 2652 +++++ .../staging/rtl8192u/ieee80211/ieee80211_softmac.c | 3237 ++++++ .../rtl8192u/ieee80211/ieee80211_softmac_wx.c | 611 ++ drivers/staging/rtl8192u/ieee80211/ieee80211_tx.c | 911 ++ drivers/staging/rtl8192u/ieee80211/ieee80211_wx.c | 875 ++ drivers/staging/rtl8192u/ieee80211/internal.h | 82 + drivers/staging/rtl8192u/ieee80211/michael_mic.c | 194 + drivers/staging/rtl8192u/ieee80211/proc.c | 116 + drivers/staging/rtl8192u/ieee80211/rtl819x_BA.h | 69 + .../staging/rtl8192u/ieee80211/rtl819x_BAProc.c | 737 ++ drivers/staging/rtl8192u/ieee80211/rtl819x_HT.h | 481 + .../staging/rtl8192u/ieee80211/rtl819x_HTProc.c | 1462 +++ drivers/staging/rtl8192u/ieee80211/rtl819x_Qos.h | 582 + drivers/staging/rtl8192u/ieee80211/rtl819x_TS.h | 56 + .../staging/rtl8192u/ieee80211/rtl819x_TSProc.c | 628 ++ drivers/staging/rtl8192u/ieee80211/rtl_crypto.h | 399 + drivers/staging/rtl8192u/ieee80211/scatterwalk.c | 117 + drivers/staging/rtl8192u/ieee80211/scatterwalk.h | 51 + drivers/staging/rtl8192u/ieee80211_crypt.h | 86 + drivers/staging/rtl8192u/r8180_93cx6.c | 146 + drivers/staging/rtl8192u/r8180_93cx6.h | 40 + drivers/staging/rtl8192u/r8180_pm.c | 48 + drivers/staging/rtl8192u/r8180_pm.h | 28 + drivers/staging/rtl8192u/r8190_rtl8256.c | 312 + drivers/staging/rtl8192u/r8190_rtl8256.h | 27 + drivers/staging/rtl8192u/r8192U.h | 1256 +++ drivers/staging/rtl8192u/r8192U_core.c | 6043 +++++++++++ drivers/staging/rtl8192u/r8192U_dm.c | 3890 +++++++ drivers/staging/rtl8192u/r8192U_dm.h | 254 + drivers/staging/rtl8192u/r8192U_hw.h | 411 + drivers/staging/rtl8192u/r8192U_wx.c | 1222 +++ drivers/staging/rtl8192u/r8192U_wx.h | 23 + drivers/staging/rtl8192u/r819xU_HTGen.h | 13 + drivers/staging/rtl8192u/r819xU_HTType.h | 391 + drivers/staging/rtl8192u/r819xU_cmdpkt.c | 790 ++ drivers/staging/rtl8192u/r819xU_cmdpkt.h | 207 + drivers/staging/rtl8192u/r819xU_firmware.c | 405 + drivers/staging/rtl8192u/r819xU_firmware.h | 27 + drivers/staging/rtl8192u/r819xU_firmware_img.c | 547 + drivers/staging/rtl8192u/r819xU_firmware_img.h | 28 + drivers/staging/rtl8192u/r819xU_phy.c | 1773 ++++ drivers/staging/rtl8192u/r819xU_phy.h | 90 + drivers/staging/rtl8192u/r819xU_phyreg.h | 871 ++ drivers/staging/rtl8712/Kconfig | 19 + drivers/staging/rtl8712/Makefile | 34 + drivers/staging/rtl8712/TODO | 13 + drivers/staging/rtl8712/basic_types.h | 48 + drivers/staging/rtl8712/big_endian.h | 94 + drivers/staging/rtl8712/drv_types.h | 203 + drivers/staging/rtl8712/ethernet.h | 48 + drivers/staging/rtl8712/generic.h | 178 + drivers/staging/rtl8712/hal_init.c | 404 + drivers/staging/rtl8712/ieee80211.c | 421 + drivers/staging/rtl8712/ieee80211.h | 796 ++ drivers/staging/rtl8712/if_ether.h | 141 + drivers/staging/rtl8712/ip.h | 137 + drivers/staging/rtl8712/little_endian.h | 94 + drivers/staging/rtl8712/mlme_linux.c | 170 + drivers/staging/rtl8712/mlme_osdep.h | 43 + drivers/staging/rtl8712/mp_custom_oid.h | 299 + drivers/staging/rtl8712/os_intfs.c | 481 + drivers/staging/rtl8712/osdep_intf.h | 44 + drivers/staging/rtl8712/osdep_service.h | 215 + drivers/staging/rtl8712/recv_linux.c | 173 + drivers/staging/rtl8712/recv_osdep.h | 52 + drivers/staging/rtl8712/rtl8712_bitdef.h | 40 + drivers/staging/rtl8712/rtl8712_cmd.c | 483 + drivers/staging/rtl8712/rtl8712_cmd.h | 244 + drivers/staging/rtl8712/rtl8712_cmdctrl_bitdef.h | 108 + drivers/staging/rtl8712/rtl8712_cmdctrl_regdef.h | 34 + drivers/staging/rtl8712/rtl8712_debugctrl_bitdef.h | 55 + drivers/staging/rtl8712/rtl8712_debugctrl_regdef.h | 47 + .../staging/rtl8712/rtl8712_edcasetting_bitdef.h | 77 + .../staging/rtl8712/rtl8712_edcasetting_regdef.h | 37 + drivers/staging/rtl8712/rtl8712_efuse.c | 575 + drivers/staging/rtl8712/rtl8712_efuse.h | 43 + drivers/staging/rtl8712/rtl8712_event.h | 99 + drivers/staging/rtl8712/rtl8712_fifoctrl_bitdef.h | 145 + drivers/staging/rtl8712/rtl8712_fifoctrl_regdef.h | 76 + drivers/staging/rtl8712/rtl8712_gp_bitdef.h | 79 + drivers/staging/rtl8712/rtl8712_gp_regdef.h | 42 + drivers/staging/rtl8712/rtl8712_hal.h | 150 + drivers/staging/rtl8712/rtl8712_interrupt_bitdef.h | 58 + drivers/staging/rtl8712/rtl8712_io.c | 151 + drivers/staging/rtl8712/rtl8712_led.c | 1815 ++++ .../staging/rtl8712/rtl8712_macsetting_bitdef.h | 47 + .../staging/rtl8712/rtl8712_macsetting_regdef.h | 35 + drivers/staging/rtl8712/rtl8712_powersave_bitdef.h | 52 + drivers/staging/rtl8712/rtl8712_powersave_regdef.h | 39 + drivers/staging/rtl8712/rtl8712_ratectrl_bitdef.h | 49 + drivers/staging/rtl8712/rtl8712_ratectrl_regdef.h | 56 + drivers/staging/rtl8712/rtl8712_recv.c | 1131 ++ drivers/staging/rtl8712/rtl8712_recv.h | 157 + drivers/staging/rtl8712/rtl8712_regdef.h | 44 + drivers/staging/rtl8712/rtl8712_security_bitdef.h | 48 + drivers/staging/rtl8712/rtl8712_spec.h | 135 + drivers/staging/rtl8712/rtl8712_syscfg_bitdef.h | 170 + drivers/staging/rtl8712/rtl8712_syscfg_regdef.h | 56 + drivers/staging/rtl8712/rtl8712_timectrl_bitdef.h | 63 + drivers/staging/rtl8712/rtl8712_timectrl_regdef.h | 39 + drivers/staging/rtl8712/rtl8712_wmac_bitdef.h | 62 + drivers/staging/rtl8712/rtl8712_wmac_regdef.h | 49 + drivers/staging/rtl8712/rtl8712_xmit.c | 760 ++ drivers/staging/rtl8712/rtl8712_xmit.h | 123 + drivers/staging/rtl8712/rtl871x_byteorder.h | 32 + drivers/staging/rtl8712/rtl871x_cmd.c | 1067 ++ drivers/staging/rtl8712/rtl871x_cmd.h | 790 ++ drivers/staging/rtl8712/rtl871x_debug.h | 167 + drivers/staging/rtl8712/rtl871x_eeprom.c | 233 + drivers/staging/rtl8712/rtl871x_eeprom.h | 101 + drivers/staging/rtl8712/rtl871x_event.h | 120 + drivers/staging/rtl8712/rtl871x_ht.h | 44 + drivers/staging/rtl8712/rtl871x_io.c | 160 + drivers/staging/rtl8712/rtl871x_io.h | 257 + drivers/staging/rtl8712/rtl871x_ioctl.h | 97 + drivers/staging/rtl8712/rtl871x_ioctl_linux.c | 2399 +++++ drivers/staging/rtl8712/rtl871x_ioctl_rtl.c | 535 + drivers/staging/rtl8712/rtl871x_ioctl_rtl.h | 121 + drivers/staging/rtl8712/rtl871x_ioctl_set.c | 381 + drivers/staging/rtl8712/rtl871x_ioctl_set.h | 57 + drivers/staging/rtl8712/rtl871x_led.h | 124 + drivers/staging/rtl8712/rtl871x_mlme.c | 1818 ++++ drivers/staging/rtl8712/rtl871x_mlme.h | 232 + drivers/staging/rtl8712/rtl871x_mp.c | 748 ++ drivers/staging/rtl8712/rtl871x_mp.h | 342 + drivers/staging/rtl8712/rtl871x_mp_ioctl.c | 1473 +++ drivers/staging/rtl8712/rtl871x_mp_ioctl.h | 480 + drivers/staging/rtl8712/rtl871x_mp_phy_regdef.h | 1025 ++ drivers/staging/rtl8712/rtl871x_pwrctrl.c | 247 + drivers/staging/rtl8712/rtl871x_pwrctrl.h | 151 + drivers/staging/rtl8712/rtl871x_recv.c | 698 ++ drivers/staging/rtl8712/rtl871x_recv.h | 327 + drivers/staging/rtl8712/rtl871x_rf.h | 68 + drivers/staging/rtl8712/rtl871x_security.c | 1404 +++ drivers/staging/rtl8712/rtl871x_security.h | 222 + drivers/staging/rtl8712/rtl871x_sta_mgt.c | 296 + drivers/staging/rtl8712/rtl871x_wlan_sme.h | 47 + drivers/staging/rtl8712/rtl871x_xmit.c | 1063 ++ drivers/staging/rtl8712/rtl871x_xmit.h | 302 + drivers/staging/rtl8712/sta_info.h | 146 + drivers/staging/rtl8712/swab.h | 131 + drivers/staging/rtl8712/usb_halinit.c | 317 + drivers/staging/rtl8712/usb_intf.c | 671 ++ drivers/staging/rtl8712/usb_ops.c | 201 + drivers/staging/rtl8712/usb_ops.h | 50 + drivers/staging/rtl8712/usb_ops_linux.c | 530 + drivers/staging/rtl8712/usb_osintf.h | 50 + drivers/staging/rtl8712/usb_vendor_req.h | 58 + drivers/staging/rtl8712/wifi.h | 643 ++ drivers/staging/rtl8712/wlan_bssdef.h | 267 + drivers/staging/rtl8712/xmit_linux.c | 203 + drivers/staging/rtl8712/xmit_osdep.h | 64 + drivers/staging/rts5139/Kconfig | 16 + drivers/staging/rts5139/Makefile | 37 + drivers/staging/rts5139/TODO | 9 + drivers/staging/rts5139/debug.h | 46 + drivers/staging/rts5139/ms.c | 4191 ++++++++ drivers/staging/rts5139/ms.h | 263 + drivers/staging/rts5139/ms_mg.c | 642 ++ drivers/staging/rts5139/ms_mg.h | 41 + drivers/staging/rts5139/rts51x.c | 937 ++ drivers/staging/rts5139/rts51x.h | 204 + drivers/staging/rts5139/rts51x_card.c | 986 ++ drivers/staging/rts5139/rts51x_card.h | 881 ++ drivers/staging/rts5139/rts51x_chip.c | 1167 ++ drivers/staging/rts5139/rts51x_chip.h | 904 ++ drivers/staging/rts5139/rts51x_fop.c | 298 + drivers/staging/rts5139/rts51x_fop.h | 62 + drivers/staging/rts5139/rts51x_scsi.c | 2235 ++++ drivers/staging/rts5139/rts51x_scsi.h | 162 + drivers/staging/rts5139/rts51x_sys.h | 54 + drivers/staging/rts5139/rts51x_transport.c | 1000 ++ drivers/staging/rts5139/rts51x_transport.h | 79 + drivers/staging/rts5139/sd.c | 3400 ++++++ drivers/staging/rts5139/sd.h | 304 + drivers/staging/rts5139/sd_cprm.c | 1215 +++ drivers/staging/rts5139/sd_cprm.h | 54 + drivers/staging/rts5139/trace.h | 137 + drivers/staging/rts5139/xd.c | 2255 ++++ drivers/staging/rts5139/xd.h | 193 + drivers/staging/rts_pstor/Kconfig | 16 + drivers/staging/rts_pstor/Makefile | 16 + drivers/staging/rts_pstor/TODO | 9 + drivers/staging/rts_pstor/debug.h | 43 + drivers/staging/rts_pstor/general.c | 35 + drivers/staging/rts_pstor/general.h | 31 + drivers/staging/rts_pstor/ms.c | 4243 ++++++++ drivers/staging/rts_pstor/ms.h | 225 + drivers/staging/rts_pstor/rtsx.c | 1111 ++ drivers/staging/rts_pstor/rtsx.h | 186 + drivers/staging/rts_pstor/rtsx_card.c | 1257 +++ drivers/staging/rts_pstor/rtsx_card.h | 1093 ++ drivers/staging/rts_pstor/rtsx_chip.c | 2335 ++++ drivers/staging/rts_pstor/rtsx_chip.h | 989 ++ drivers/staging/rts_pstor/rtsx_scsi.c | 3204 ++++++ drivers/staging/rts_pstor/rtsx_scsi.h | 142 + drivers/staging/rts_pstor/rtsx_sys.h | 50 + drivers/staging/rts_pstor/rtsx_transport.c | 779 ++ drivers/staging/rts_pstor/rtsx_transport.h | 66 + drivers/staging/rts_pstor/sd.c | 4796 +++++++++ drivers/staging/rts_pstor/sd.h | 300 + drivers/staging/rts_pstor/spi.c | 812 ++ drivers/staging/rts_pstor/spi.h | 65 + drivers/staging/rts_pstor/trace.h | 117 + drivers/staging/rts_pstor/xd.c | 2052 ++++ drivers/staging/rts_pstor/xd.h | 188 + drivers/staging/sbe-2t3e3/2t3e3.h | 894 ++ drivers/staging/sbe-2t3e3/Kconfig | 13 + drivers/staging/sbe-2t3e3/Makefile | 4 + drivers/staging/sbe-2t3e3/TODO | 6 + drivers/staging/sbe-2t3e3/cpld.c | 366 + drivers/staging/sbe-2t3e3/ctrl.c | 362 + drivers/staging/sbe-2t3e3/ctrl.h | 131 + drivers/staging/sbe-2t3e3/dc.c | 477 + drivers/staging/sbe-2t3e3/exar7250.c | 217 + drivers/staging/sbe-2t3e3/exar7300.c | 182 + drivers/staging/sbe-2t3e3/intr.c | 635 ++ drivers/staging/sbe-2t3e3/io.c | 351 + drivers/staging/sbe-2t3e3/main.c | 171 + drivers/staging/sbe-2t3e3/maps.c | 104 + drivers/staging/sbe-2t3e3/module.c | 210 + drivers/staging/sbe-2t3e3/netdev.c | 142 + drivers/staging/sep/Kconfig | 11 + drivers/staging/sep/Makefile | 3 + drivers/staging/sep/TODO | 3 + drivers/staging/sep/sep_crypto.c | 4058 +++++++ drivers/staging/sep/sep_crypto.h | 359 + drivers/staging/sep/sep_dev.h | 161 + drivers/staging/sep/sep_driver_api.h | 402 + drivers/staging/sep/sep_driver_config.h | 298 + drivers/staging/sep/sep_driver_hw_defs.h | 56 + drivers/staging/sep/sep_main.c | 4518 ++++++++ drivers/staging/sep/sep_trace_events.h | 188 + drivers/staging/serial/68360serial.c | 2979 ++++++ drivers/staging/serial/Kconfig | 16 + drivers/staging/serial/Makefile | 1 + drivers/staging/serial/TODO | 6 + drivers/staging/serqt_usb2/Kconfig | 9 + drivers/staging/serqt_usb2/Makefile | 1 + drivers/staging/serqt_usb2/serqt_usb2.c | 1622 +++ drivers/staging/slicoss/Kconfig | 14 + drivers/staging/slicoss/Makefile | 1 + drivers/staging/slicoss/README | 48 + drivers/staging/slicoss/slic.h | 537 + drivers/staging/slicoss/slichw.h | 827 ++ drivers/staging/slicoss/slicoss.c | 4001 +++++++ drivers/staging/sm7xx/Kconfig | 8 + drivers/staging/sm7xx/Makefile | 3 + drivers/staging/sm7xx/TODO | 9 + drivers/staging/sm7xx/smtcfb.c | 1154 ++ drivers/staging/sm7xx/smtcfb.h | 788 ++ drivers/staging/speakup/DefaultKeyAssignments | 46 + drivers/staging/speakup/Kconfig | 195 + drivers/staging/speakup/Makefile | 30 + drivers/staging/speakup/TODO | 47 + drivers/staging/speakup/buffers.c | 106 + drivers/staging/speakup/devsynth.c | 95 + drivers/staging/speakup/fakekey.c | 99 + drivers/staging/speakup/i18n.c | 628 ++ drivers/staging/speakup/i18n.h | 228 + drivers/staging/speakup/keyhelp.c | 216 + drivers/staging/speakup/kobjects.c | 1024 ++ drivers/staging/speakup/main.c | 2366 +++++ drivers/staging/speakup/selection.c | 151 + drivers/staging/speakup/serialio.c | 214 + drivers/staging/speakup/serialio.h | 66 + drivers/staging/speakup/speakup.h | 129 + drivers/staging/speakup/speakup_acnt.h | 16 + drivers/staging/speakup/speakup_acntpc.c | 335 + drivers/staging/speakup/speakup_acntsa.c | 163 + drivers/staging/speakup/speakup_apollo.c | 227 + drivers/staging/speakup/speakup_audptr.c | 195 + drivers/staging/speakup/speakup_bns.c | 147 + drivers/staging/speakup/speakup_decext.c | 253 + drivers/staging/speakup/speakup_decpc.c | 504 + drivers/staging/speakup/speakup_dectlk.c | 322 + drivers/staging/speakup/speakup_dtlk.c | 402 + drivers/staging/speakup/speakup_dtlk.h | 54 + drivers/staging/speakup/speakup_dummy.c | 148 + drivers/staging/speakup/speakup_keypc.c | 335 + drivers/staging/speakup/speakup_ltlk.c | 194 + drivers/staging/speakup/speakup_soft.c | 372 + drivers/staging/speakup/speakup_spkout.c | 165 + drivers/staging/speakup/speakup_txprt.c | 147 + drivers/staging/speakup/speakupmap.h | 65 + drivers/staging/speakup/speakupmap.map | 93 + drivers/staging/speakup/spk_priv.h | 93 + drivers/staging/speakup/spk_priv_keyinfo.h | 110 + drivers/staging/speakup/spk_types.h | 193 + drivers/staging/speakup/spkguide.txt | 1575 +++ drivers/staging/speakup/synth.c | 463 + drivers/staging/speakup/thread.c | 58 + drivers/staging/speakup/varhandlers.c | 404 + drivers/staging/staging.c | 19 + drivers/staging/ste_rmi4/Kconfig | 9 + drivers/staging/ste_rmi4/Makefile | 5 + drivers/staging/ste_rmi4/TODO | 7 + .../staging/ste_rmi4/board-mop500-u8500uib-rmi4.c | 32 + drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c | 1174 ++ drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h | 47 + drivers/staging/telephony/Kconfig | 47 + drivers/staging/telephony/Makefile | 7 + drivers/staging/telephony/TODO | 10 + drivers/staging/telephony/ixj-ver.h | 4 + drivers/staging/telephony/ixj.c | 10571 +++++++++++++++++++ drivers/staging/telephony/ixj.h | 1322 +++ drivers/staging/telephony/ixj_pcmcia.c | 187 + drivers/staging/telephony/phonedev.c | 166 + .../staging/tidspbridge/Documentation/CONTRIBUTORS | 45 + drivers/staging/tidspbridge/Documentation/README | 70 + .../staging/tidspbridge/Documentation/error-codes | 157 + drivers/staging/tidspbridge/Kconfig | 68 + drivers/staging/tidspbridge/Makefile | 32 + drivers/staging/tidspbridge/TODO | 18 + drivers/staging/tidspbridge/core/_cmm.h | 45 + drivers/staging/tidspbridge/core/_deh.h | 35 + drivers/staging/tidspbridge/core/_msg_sm.h | 142 + drivers/staging/tidspbridge/core/_tiomap.h | 382 + drivers/staging/tidspbridge/core/_tiomap_pwr.h | 85 + drivers/staging/tidspbridge/core/chnl_sm.c | 906 ++ drivers/staging/tidspbridge/core/dsp-clock.c | 378 + drivers/staging/tidspbridge/core/io_sm.c | 2263 ++++ drivers/staging/tidspbridge/core/msg_sm.c | 565 + drivers/staging/tidspbridge/core/sync.c | 121 + drivers/staging/tidspbridge/core/tiomap3430.c | 1806 ++++ drivers/staging/tidspbridge/core/tiomap3430_pwr.c | 553 + drivers/staging/tidspbridge/core/tiomap_io.c | 438 + drivers/staging/tidspbridge/core/tiomap_io.h | 104 + drivers/staging/tidspbridge/core/ue_deh.c | 273 + drivers/staging/tidspbridge/core/wdt.c | 134 + drivers/staging/tidspbridge/dynload/cload.c | 1933 ++++ .../staging/tidspbridge/dynload/dload_internal.h | 344 + drivers/staging/tidspbridge/dynload/doff.h | 354 + drivers/staging/tidspbridge/dynload/getsection.c | 407 + drivers/staging/tidspbridge/dynload/header.h | 49 + drivers/staging/tidspbridge/dynload/module_list.h | 159 + drivers/staging/tidspbridge/dynload/params.h | 226 + drivers/staging/tidspbridge/dynload/reloc.c | 484 + drivers/staging/tidspbridge/dynload/reloc_table.h | 102 + .../tidspbridge/dynload/reloc_table_c6000.c | 257 + drivers/staging/tidspbridge/dynload/tramp.c | 1143 ++ .../tidspbridge/dynload/tramp_table_c6000.c | 164 + drivers/staging/tidspbridge/gen/gh.c | 177 + drivers/staging/tidspbridge/gen/uuidutil.c | 106 + drivers/staging/tidspbridge/hw/EasiGlobal.h | 41 + drivers/staging/tidspbridge/hw/MMUAccInt.h | 76 + drivers/staging/tidspbridge/hw/MMURegAcM.h | 225 + drivers/staging/tidspbridge/hw/hw_defs.h | 58 + drivers/staging/tidspbridge/hw/hw_mmu.c | 562 + drivers/staging/tidspbridge/hw/hw_mmu.h | 163 + .../tidspbridge/include/dspbridge/_chnl_sm.h | 177 + .../tidspbridge/include/dspbridge/brddefs.h | 37 + .../tidspbridge/include/dspbridge/cfgdefs.h | 61 + .../staging/tidspbridge/include/dspbridge/chnl.h | 80 + .../tidspbridge/include/dspbridge/chnldefs.h | 63 + .../tidspbridge/include/dspbridge/chnlpriv.h | 85 + .../staging/tidspbridge/include/dspbridge/clk.h | 101 + .../staging/tidspbridge/include/dspbridge/cmm.h | 337 + .../tidspbridge/include/dspbridge/cmmdefs.h | 104 + .../staging/tidspbridge/include/dspbridge/cod.h | 329 + .../staging/tidspbridge/include/dspbridge/dbdcd.h | 358 + .../tidspbridge/include/dspbridge/dbdcddef.h | 78 + .../staging/tidspbridge/include/dspbridge/dbdefs.h | 488 + .../staging/tidspbridge/include/dspbridge/dbll.h | 56 + .../tidspbridge/include/dspbridge/dblldefs.h | 431 + .../staging/tidspbridge/include/dspbridge/dev.h | 620 ++ .../tidspbridge/include/dspbridge/devdefs.h | 26 + .../staging/tidspbridge/include/dspbridge/disp.h | 186 + .../staging/tidspbridge/include/dspbridge/dmm.h | 71 + .../staging/tidspbridge/include/dspbridge/drv.h | 468 + .../tidspbridge/include/dspbridge/dspapi-ioctl.h | 467 + .../staging/tidspbridge/include/dspbridge/dspapi.h | 167 + .../tidspbridge/include/dspbridge/dspchnl.h | 72 + .../tidspbridge/include/dspbridge/dspdefs.h | 1048 ++ .../staging/tidspbridge/include/dspbridge/dspdeh.h | 43 + .../staging/tidspbridge/include/dspbridge/dspdrv.h | 60 + .../staging/tidspbridge/include/dspbridge/dspio.h | 41 + .../tidspbridge/include/dspbridge/dspioctl.h | 68 + .../staging/tidspbridge/include/dspbridge/dspmsg.h | 56 + .../tidspbridge/include/dspbridge/dynamic_loader.h | 490 + .../tidspbridge/include/dspbridge/getsection.h | 108 + drivers/staging/tidspbridge/include/dspbridge/gh.h | 32 + .../tidspbridge/include/dspbridge/host_os.h | 58 + drivers/staging/tidspbridge/include/dspbridge/io.h | 80 + .../staging/tidspbridge/include/dspbridge/io_sm.h | 160 + .../staging/tidspbridge/include/dspbridge/mbx_sh.h | 144 + .../tidspbridge/include/dspbridge/memdefs.h | 30 + .../staging/tidspbridge/include/dspbridge/mgr.h | 205 + .../tidspbridge/include/dspbridge/mgrpriv.h | 45 + .../staging/tidspbridge/include/dspbridge/msg.h | 59 + .../tidspbridge/include/dspbridge/msgdefs.h | 29 + .../staging/tidspbridge/include/dspbridge/nldr.h | 55 + .../tidspbridge/include/dspbridge/nldrdefs.h | 259 + .../staging/tidspbridge/include/dspbridge/node.h | 524 + .../tidspbridge/include/dspbridge/nodedefs.h | 28 + .../tidspbridge/include/dspbridge/nodepriv.h | 181 + .../staging/tidspbridge/include/dspbridge/ntfy.h | 217 + .../staging/tidspbridge/include/dspbridge/proc.h | 593 ++ .../tidspbridge/include/dspbridge/procpriv.h | 25 + .../staging/tidspbridge/include/dspbridge/pwr.h | 113 + .../include/dspbridge/resourcecleanup.h | 41 + .../staging/tidspbridge/include/dspbridge/rmm.h | 156 + .../staging/tidspbridge/include/dspbridge/rms_sh.h | 86 + .../tidspbridge/include/dspbridge/rmstypes.h | 24 + .../staging/tidspbridge/include/dspbridge/strm.h | 306 + .../tidspbridge/include/dspbridge/strmdefs.h | 44 + .../staging/tidspbridge/include/dspbridge/sync.h | 119 + .../tidspbridge/include/dspbridge/uuidutil.h | 62 + .../staging/tidspbridge/include/dspbridge/wdt.h | 79 + drivers/staging/tidspbridge/pmgr/chnl.c | 115 + drivers/staging/tidspbridge/pmgr/chnlobj.h | 46 + drivers/staging/tidspbridge/pmgr/cmm.c | 916 ++ drivers/staging/tidspbridge/pmgr/cod.c | 537 + drivers/staging/tidspbridge/pmgr/dbll.c | 1424 +++ drivers/staging/tidspbridge/pmgr/dev.c | 969 ++ drivers/staging/tidspbridge/pmgr/dmm.c | 487 + drivers/staging/tidspbridge/pmgr/dspapi.c | 1840 ++++ drivers/staging/tidspbridge/pmgr/io.c | 93 + drivers/staging/tidspbridge/pmgr/ioobj.h | 38 + drivers/staging/tidspbridge/pmgr/msg.c | 91 + drivers/staging/tidspbridge/pmgr/msgobj.h | 38 + drivers/staging/tidspbridge/rmgr/dbdcd.c | 1415 +++ drivers/staging/tidspbridge/rmgr/disp.c | 655 ++ drivers/staging/tidspbridge/rmgr/drv.c | 834 ++ drivers/staging/tidspbridge/rmgr/drv_interface.c | 641 ++ drivers/staging/tidspbridge/rmgr/dspdrv.c | 139 + drivers/staging/tidspbridge/rmgr/mgr.c | 352 + drivers/staging/tidspbridge/rmgr/nldr.c | 1859 ++++ drivers/staging/tidspbridge/rmgr/node.c | 3021 ++++++ drivers/staging/tidspbridge/rmgr/proc.c | 1844 ++++ drivers/staging/tidspbridge/rmgr/pwr.c | 176 + drivers/staging/tidspbridge/rmgr/rmm.c | 456 + drivers/staging/tidspbridge/rmgr/strm.c | 733 ++ drivers/staging/usbip/Kconfig | 45 + drivers/staging/usbip/Makefile | 10 + drivers/staging/usbip/README | 7 + drivers/staging/usbip/stub.h | 112 + drivers/staging/usbip/stub_dev.c | 545 + drivers/staging/usbip/stub_main.c | 300 + drivers/staging/usbip/stub_rx.c | 605 ++ drivers/staging/usbip/stub_tx.c | 399 + drivers/staging/usbip/usbip_common.c | 829 ++ drivers/staging/usbip/usbip_common.h | 330 + drivers/staging/usbip/usbip_event.c | 126 + drivers/staging/usbip/usbip_protocol.txt | 358 + drivers/staging/usbip/userspace/AUTHORS | 3 + drivers/staging/usbip/userspace/COPYING | 340 + drivers/staging/usbip/userspace/INSTALL | 237 + drivers/staging/usbip/userspace/Makefile.am | 6 + drivers/staging/usbip/userspace/README | 201 + drivers/staging/usbip/userspace/autogen.sh | 9 + drivers/staging/usbip/userspace/cleanup.sh | 12 + drivers/staging/usbip/userspace/configure.ac | 100 + drivers/staging/usbip/userspace/doc/usbip.8 | 71 + .../usbip/userspace/doc/usbip_bind_driver.8 | 42 + drivers/staging/usbip/userspace/doc/usbipd.8 | 62 + drivers/staging/usbip/userspace/libsrc/Makefile.am | 7 + drivers/staging/usbip/userspace/libsrc/names.c | 793 ++ drivers/staging/usbip/userspace/libsrc/names.h | 57 + .../staging/usbip/userspace/libsrc/usbip_common.c | 286 + .../staging/usbip/userspace/libsrc/usbip_common.h | 147 + .../usbip/userspace/libsrc/usbip_host_driver.c | 401 + .../usbip/userspace/libsrc/usbip_host_driver.h | 48 + .../staging/usbip/userspace/libsrc/vhci_driver.c | 519 + .../staging/usbip/userspace/libsrc/vhci_driver.h | 67 + drivers/staging/usbip/userspace/src/Makefile.am | 11 + drivers/staging/usbip/userspace/src/usbip.c | 190 + drivers/staging/usbip/userspace/src/usbip.h | 39 + drivers/staging/usbip/userspace/src/usbip_attach.c | 230 + drivers/staging/usbip/userspace/src/usbip_bind.c | 277 + drivers/staging/usbip/userspace/src/usbip_detach.c | 103 + drivers/staging/usbip/userspace/src/usbip_list.c | 303 + .../staging/usbip/userspace/src/usbip_network.c | 257 + .../staging/usbip/userspace/src/usbip_network.h | 184 + drivers/staging/usbip/userspace/src/usbip_unbind.c | 186 + drivers/staging/usbip/userspace/src/usbipd.c | 567 + drivers/staging/usbip/userspace/src/utils.c | 76 + drivers/staging/usbip/userspace/src/utils.h | 25 + drivers/staging/usbip/vhci.h | 136 + drivers/staging/usbip/vhci_hcd.c | 1223 +++ drivers/staging/usbip/vhci_rx.c | 268 + drivers/staging/usbip/vhci_sysfs.c | 244 + drivers/staging/usbip/vhci_tx.c | 226 + drivers/staging/vme/Kconfig | 19 + drivers/staging/vme/Makefile | 8 + drivers/staging/vme/TODO | 5 + drivers/staging/vme/boards/Kconfig | 9 + drivers/staging/vme/boards/Makefile | 5 + drivers/staging/vme/boards/vme_vmivme7805.c | 123 + drivers/staging/vme/boards/vme_vmivme7805.h | 37 + drivers/staging/vme/bridges/Kconfig | 15 + drivers/staging/vme/bridges/Makefile | 2 + drivers/staging/vme/bridges/vme_ca91cx42.c | 1959 ++++ drivers/staging/vme/bridges/vme_ca91cx42.h | 583 + drivers/staging/vme/bridges/vme_tsi148.c | 2660 +++++ drivers/staging/vme/bridges/vme_tsi148.h | 1409 +++ drivers/staging/vme/devices/Kconfig | 21 + drivers/staging/vme/devices/Makefile | 8 + drivers/staging/vme/devices/vme_pio2.h | 249 + drivers/staging/vme/devices/vme_pio2_cntr.c | 71 + drivers/staging/vme/devices/vme_pio2_core.c | 524 + drivers/staging/vme/devices/vme_pio2_gpio.c | 232 + drivers/staging/vme/devices/vme_user.c | 899 ++ drivers/staging/vme/devices/vme_user.h | 58 + drivers/staging/vme/vme.c | 1526 +++ drivers/staging/vme/vme.h | 174 + drivers/staging/vme/vme_api.txt | 396 + drivers/staging/vme/vme_bridge.h | 174 + drivers/staging/vt6655/80211hdr.h | 353 + drivers/staging/vt6655/80211mgr.c | 1028 ++ drivers/staging/vt6655/80211mgr.h | 827 ++ drivers/staging/vt6655/IEEE11h.c | 329 + drivers/staging/vt6655/IEEE11h.h | 52 + drivers/staging/vt6655/Kconfig | 8 + drivers/staging/vt6655/Makefile | 39 + drivers/staging/vt6655/TODO | 21 + drivers/staging/vt6655/aes_ccmp.c | 402 + drivers/staging/vt6655/aes_ccmp.h | 46 + drivers/staging/vt6655/baseband.c | 2975 ++++++ drivers/staging/vt6655/baseband.h | 177 + drivers/staging/vt6655/bssdb.c | 1737 +++ drivers/staging/vt6655/bssdb.h | 365 + drivers/staging/vt6655/card.c | 2225 ++++ drivers/staging/vt6655/card.h | 198 + drivers/staging/vt6655/channel.c | 835 ++ drivers/staging/vt6655/channel.h | 58 + drivers/staging/vt6655/country.h | 162 + drivers/staging/vt6655/datarate.c | 437 + drivers/staging/vt6655/datarate.h | 95 + drivers/staging/vt6655/desc.h | 684 ++ drivers/staging/vt6655/device.h | 928 ++ drivers/staging/vt6655/device_cfg.h | 100 + drivers/staging/vt6655/device_main.c | 3728 +++++++ drivers/staging/vt6655/dpc.c | 1492 +++ drivers/staging/vt6655/dpc.h | 55 + drivers/staging/vt6655/hostap.c | 867 ++ drivers/staging/vt6655/hostap.h | 70 + drivers/staging/vt6655/iocmd.h | 475 + drivers/staging/vt6655/ioctl.c | 661 ++ drivers/staging/vt6655/ioctl.h | 57 + drivers/staging/vt6655/iowpa.h | 159 + drivers/staging/vt6655/iwctl.c | 2266 ++++ drivers/staging/vt6655/iwctl.h | 224 + drivers/staging/vt6655/key.c | 826 ++ drivers/staging/vt6655/key.h | 184 + drivers/staging/vt6655/mac.c | 1750 +++ drivers/staging/vt6655/mac.h | 1154 ++ drivers/staging/vt6655/mib.c | 597 ++ drivers/staging/vt6655/mib.h | 377 + drivers/staging/vt6655/michael.c | 181 + drivers/staging/vt6655/michael.h | 58 + drivers/staging/vt6655/power.c | 412 + drivers/staging/vt6655/power.h | 84 + drivers/staging/vt6655/rc4.c | 87 + drivers/staging/vt6655/rc4.h | 47 + drivers/staging/vt6655/rf.c | 1273 +++ drivers/staging/vt6655/rf.h | 106 + drivers/staging/vt6655/rxtx.c | 3189 ++++++ drivers/staging/vt6655/rxtx.h | 87 + drivers/staging/vt6655/srom.c | 420 + drivers/staging/vt6655/srom.h | 157 + drivers/staging/vt6655/tcrc.c | 198 + drivers/staging/vt6655/tcrc.h | 53 + drivers/staging/vt6655/test | 9 + drivers/staging/vt6655/tether.c | 109 + drivers/staging/vt6655/tether.h | 203 + drivers/staging/vt6655/tkip.c | 272 + drivers/staging/vt6655/tkip.h | 60 + drivers/staging/vt6655/tmacro.h | 62 + drivers/staging/vt6655/ttype.h | 71 + drivers/staging/vt6655/upc.h | 166 + drivers/staging/vt6655/vntconfiguration.dat | 1 + drivers/staging/vt6655/vntwifi.c | 807 ++ drivers/staging/vt6655/vntwifi.h | 307 + drivers/staging/vt6655/wcmd.c | 1147 ++ drivers/staging/vt6655/wcmd.h | 145 + drivers/staging/vt6655/wctl.c | 250 + drivers/staging/vt6655/wctl.h | 109 + drivers/staging/vt6655/wmgr.c | 5004 +++++++++ drivers/staging/vt6655/wmgr.h | 502 + drivers/staging/vt6655/wpa.c | 316 + drivers/staging/vt6655/wpa.h | 84 + drivers/staging/vt6655/wpa2.c | 364 + drivers/staging/vt6655/wpa2.h | 78 + drivers/staging/vt6655/wpactl.c | 996 ++ drivers/staging/vt6655/wpactl.h | 72 + drivers/staging/vt6655/wroute.c | 199 + drivers/staging/vt6655/wroute.h | 47 + drivers/staging/vt6656/80211hdr.h | 336 + drivers/staging/vt6656/80211mgr.c | 983 ++ drivers/staging/vt6656/80211mgr.h | 848 ++ drivers/staging/vt6656/Kconfig | 9 + drivers/staging/vt6656/Makefile | 39 + drivers/staging/vt6656/TODO | 19 + drivers/staging/vt6656/aes_ccmp.c | 386 + drivers/staging/vt6656/aes_ccmp.h | 46 + drivers/staging/vt6656/baseband.c | 2061 ++++ drivers/staging/vt6656/baseband.h | 136 + drivers/staging/vt6656/bssdb.c | 1548 +++ drivers/staging/vt6656/bssdb.h | 307 + drivers/staging/vt6656/card.c | 1104 ++ drivers/staging/vt6656/card.h | 89 + drivers/staging/vt6656/channel.c | 524 + drivers/staging/vt6656/channel.h | 55 + drivers/staging/vt6656/control.c | 98 + drivers/staging/vt6656/control.h | 78 + drivers/staging/vt6656/country.h | 163 + drivers/staging/vt6656/datarate.c | 431 + drivers/staging/vt6656/datarate.h | 109 + drivers/staging/vt6656/desc.h | 432 + drivers/staging/vt6656/device.h | 907 ++ drivers/staging/vt6656/device_cfg.h | 101 + drivers/staging/vt6656/dpc.c | 1598 +++ drivers/staging/vt6656/dpc.h | 61 + drivers/staging/vt6656/firmware.c | 174 + drivers/staging/vt6656/firmware.h | 59 + drivers/staging/vt6656/hostap.c | 865 ++ drivers/staging/vt6656/hostap.h | 67 + drivers/staging/vt6656/int.c | 220 + drivers/staging/vt6656/int.h | 73 + drivers/staging/vt6656/iocmd.h | 444 + drivers/staging/vt6656/ioctl.c | 651 ++ drivers/staging/vt6656/ioctl.h | 54 + drivers/staging/vt6656/iowpa.h | 143 + drivers/staging/vt6656/iwctl.c | 1873 ++++ drivers/staging/vt6656/iwctl.h | 213 + drivers/staging/vt6656/key.c | 857 ++ drivers/staging/vt6656/key.h | 165 + drivers/staging/vt6656/mac.c | 478 + drivers/staging/vt6656/mac.h | 443 + drivers/staging/vt6656/main_usb.c | 2097 ++++ drivers/staging/vt6656/mib.c | 593 ++ drivers/staging/vt6656/mib.h | 410 + drivers/staging/vt6656/michael.c | 176 + drivers/staging/vt6656/michael.h | 56 + drivers/staging/vt6656/power.c | 345 + drivers/staging/vt6656/power.h | 61 + drivers/staging/vt6656/rc4.c | 87 + drivers/staging/vt6656/rc4.h | 48 + drivers/staging/vt6656/rf.c | 1151 ++ drivers/staging/vt6656/rf.h | 84 + drivers/staging/vt6656/rndis.h | 161 + drivers/staging/vt6656/rxtx.c | 3259 ++++++ drivers/staging/vt6656/rxtx.h | 694 ++ drivers/staging/vt6656/srom.h | 127 + drivers/staging/vt6656/tcrc.c | 199 + drivers/staging/vt6656/tcrc.h | 50 + drivers/staging/vt6656/tether.c | 108 + drivers/staging/vt6656/tether.h | 168 + drivers/staging/vt6656/tkip.c | 271 + drivers/staging/vt6656/tkip.h | 57 + drivers/staging/vt6656/tmacro.h | 60 + drivers/staging/vt6656/ttype.h | 76 + drivers/staging/vt6656/upc.h | 162 + drivers/staging/vt6656/usbpipe.c | 780 ++ drivers/staging/vt6656/usbpipe.h | 75 + drivers/staging/vt6656/vntconfiguration.dat | 6 + drivers/staging/vt6656/wcmd.c | 1304 +++ drivers/staging/vt6656/wcmd.h | 133 + drivers/staging/vt6656/wctl.c | 257 + drivers/staging/vt6656/wctl.h | 105 + drivers/staging/vt6656/wmgr.c | 4866 +++++++++ drivers/staging/vt6656/wmgr.h | 455 + drivers/staging/vt6656/wpa.c | 318 + drivers/staging/vt6656/wpa.h | 84 + drivers/staging/vt6656/wpa2.c | 367 + drivers/staging/vt6656/wpa2.h | 66 + drivers/staging/vt6656/wpactl.c | 919 ++ drivers/staging/vt6656/wpactl.h | 67 + drivers/staging/winbond/Kconfig | 11 + drivers/staging/winbond/Makefile | 15 + drivers/staging/winbond/TODO | 12 + drivers/staging/winbond/core.h | 65 + drivers/staging/winbond/localpara.h | 267 + drivers/staging/winbond/mac_structures.h | 71 + drivers/staging/winbond/mds.c | 612 ++ drivers/staging/winbond/mds_f.h | 19 + drivers/staging/winbond/mds_s.h | 130 + drivers/staging/winbond/mto.c | 172 + drivers/staging/winbond/mto.h | 138 + drivers/staging/winbond/phy_calibration.c | 1588 +++ drivers/staging/winbond/phy_calibration.h | 84 + drivers/staging/winbond/reg.c | 2372 +++++ drivers/staging/winbond/sme_api.h | 192 + drivers/staging/winbond/wb35reg.c | 727 ++ drivers/staging/winbond/wb35reg_f.h | 65 + drivers/staging/winbond/wb35reg_s.h | 240 + drivers/staging/winbond/wb35rx.c | 350 + drivers/staging/winbond/wb35rx_f.h | 15 + drivers/staging/winbond/wb35rx_s.h | 44 + drivers/staging/winbond/wb35tx.c | 296 + drivers/staging/winbond/wb35tx_f.h | 22 + drivers/staging/winbond/wb35tx_s.h | 47 + drivers/staging/winbond/wbhal.h | 513 + drivers/staging/winbond/wbusb.c | 868 ++ drivers/staging/wlags49_h2/Kconfig | 11 + drivers/staging/wlags49_h2/Makefile | 55 + drivers/staging/wlags49_h2/README.ubuntu | 180 + drivers/staging/wlags49_h2/README.wlags49 | 641 ++ drivers/staging/wlags49_h2/TODO | 33 + drivers/staging/wlags49_h2/WARNING.txt | 3 + drivers/staging/wlags49_h2/ap_h2.c | 3337 ++++++ drivers/staging/wlags49_h2/ap_h25.c | 4094 +++++++ drivers/staging/wlags49_h2/debug.h | 233 + drivers/staging/wlags49_h2/dhf.c | 381 + drivers/staging/wlags49_h2/dhf.h | 226 + drivers/staging/wlags49_h2/dhfcfg.h | 158 + drivers/staging/wlags49_h2/hcf.c | 4748 +++++++++ drivers/staging/wlags49_h2/hcf.h | 395 + drivers/staging/wlags49_h2/hcfcfg.h | 786 ++ drivers/staging/wlags49_h2/hcfdef.h | 752 ++ drivers/staging/wlags49_h2/man/wlags49.4 | 734 ++ drivers/staging/wlags49_h2/mdd.h | 1156 ++ drivers/staging/wlags49_h2/mmd.c | 251 + drivers/staging/wlags49_h2/mmd.h | 78 + drivers/staging/wlags49_h2/sta_h2.c | 4480 ++++++++ drivers/staging/wlags49_h2/sta_h25.c | 5255 +++++++++ drivers/staging/wlags49_h2/wl_cs.c | 500 + drivers/staging/wlags49_h2/wl_cs.h | 89 + drivers/staging/wlags49_h2/wl_enc.c | 232 + drivers/staging/wlags49_h2/wl_enc.h | 118 + drivers/staging/wlags49_h2/wl_if.h | 234 + drivers/staging/wlags49_h2/wl_internal.h | 1041 ++ drivers/staging/wlags49_h2/wl_main.c | 3871 +++++++ drivers/staging/wlags49_h2/wl_main.h | 138 + drivers/staging/wlags49_h2/wl_netdev.c | 2036 ++++ drivers/staging/wlags49_h2/wl_netdev.h | 154 + drivers/staging/wlags49_h2/wl_pci.c | 1575 +++ drivers/staging/wlags49_h2/wl_pci.h | 109 + drivers/staging/wlags49_h2/wl_priv.c | 2082 ++++ drivers/staging/wlags49_h2/wl_priv.h | 121 + drivers/staging/wlags49_h2/wl_profile.c | 1012 ++ drivers/staging/wlags49_h2/wl_profile.h | 87 + drivers/staging/wlags49_h2/wl_sysfs.c | 138 + drivers/staging/wlags49_h2/wl_sysfs.h | 7 + drivers/staging/wlags49_h2/wl_util.c | 1464 +++ drivers/staging/wlags49_h2/wl_util.h | 96 + drivers/staging/wlags49_h2/wl_version.h | 176 + drivers/staging/wlags49_h2/wl_wext.c | 3988 +++++++ drivers/staging/wlags49_h2/wl_wext.h | 88 + drivers/staging/wlags49_h25/Kconfig | 11 + drivers/staging/wlags49_h25/Makefile | 55 + drivers/staging/wlags49_h25/README.txt | 30 + drivers/staging/wlags49_h25/TODO | 33 + drivers/staging/wlags49_h25/ap_h25.c | 2 + drivers/staging/wlags49_h25/debug.h | 2 + drivers/staging/wlags49_h25/dhf.c | 2 + drivers/staging/wlags49_h25/dhf.h | 2 + drivers/staging/wlags49_h25/dhfcfg.h | 2 + drivers/staging/wlags49_h25/hcf.c | 2 + drivers/staging/wlags49_h25/hcf.h | 2 + drivers/staging/wlags49_h25/hcfcfg.h | 2 + drivers/staging/wlags49_h25/hcfdef.h | 2 + drivers/staging/wlags49_h25/mdd.h | 2 + drivers/staging/wlags49_h25/mmd.c | 2 + drivers/staging/wlags49_h25/mmd.h | 2 + drivers/staging/wlags49_h25/sta_h25.c | 2 + drivers/staging/wlags49_h25/wl_cs.c | 2 + drivers/staging/wlags49_h25/wl_cs.h | 2 + drivers/staging/wlags49_h25/wl_enc.c | 2 + drivers/staging/wlags49_h25/wl_enc.h | 2 + drivers/staging/wlags49_h25/wl_if.h | 2 + drivers/staging/wlags49_h25/wl_internal.h | 2 + drivers/staging/wlags49_h25/wl_main.c | 2 + drivers/staging/wlags49_h25/wl_main.h | 2 + drivers/staging/wlags49_h25/wl_netdev.c | 2 + drivers/staging/wlags49_h25/wl_netdev.h | 2 + drivers/staging/wlags49_h25/wl_priv.c | 2 + drivers/staging/wlags49_h25/wl_priv.h | 2 + drivers/staging/wlags49_h25/wl_profile.c | 2 + drivers/staging/wlags49_h25/wl_profile.h | 2 + drivers/staging/wlags49_h25/wl_sysfs.c | 2 + drivers/staging/wlags49_h25/wl_sysfs.h | 2 + drivers/staging/wlags49_h25/wl_util.c | 2 + drivers/staging/wlags49_h25/wl_util.h | 2 + drivers/staging/wlags49_h25/wl_version.h | 2 + drivers/staging/wlags49_h25/wl_wext.c | 2 + drivers/staging/wlags49_h25/wl_wext.h | 2 + drivers/staging/wlan-ng/Kconfig | 12 + drivers/staging/wlan-ng/Makefile | 7 + drivers/staging/wlan-ng/README | 8 + drivers/staging/wlan-ng/cfg80211.c | 763 ++ drivers/staging/wlan-ng/hfa384x.h | 1426 +++ drivers/staging/wlan-ng/hfa384x_usb.c | 4169 ++++++++ drivers/staging/wlan-ng/p80211conv.c | 664 ++ drivers/staging/wlan-ng/p80211conv.h | 163 + drivers/staging/wlan-ng/p80211hdr.h | 213 + drivers/staging/wlan-ng/p80211ioctl.h | 89 + drivers/staging/wlan-ng/p80211meta.h | 90 + drivers/staging/wlan-ng/p80211metadef.h | 261 + drivers/staging/wlan-ng/p80211metastruct.h | 271 + drivers/staging/wlan-ng/p80211mgmt.h | 520 + drivers/staging/wlan-ng/p80211msg.h | 59 + drivers/staging/wlan-ng/p80211netdev.c | 1123 ++ drivers/staging/wlan-ng/p80211netdev.h | 243 + drivers/staging/wlan-ng/p80211req.c | 265 + drivers/staging/wlan-ng/p80211req.h | 53 + drivers/staging/wlan-ng/p80211types.h | 375 + drivers/staging/wlan-ng/p80211wep.c | 307 + drivers/staging/wlan-ng/prism2fw.c | 1210 +++ drivers/staging/wlan-ng/prism2mgmt.c | 1318 +++ drivers/staging/wlan-ng/prism2mgmt.h | 110 + drivers/staging/wlan-ng/prism2mib.c | 832 ++ drivers/staging/wlan-ng/prism2sta.c | 2081 ++++ drivers/staging/wlan-ng/prism2usb.c | 361 + drivers/staging/xgifb/Kconfig | 11 + drivers/staging/xgifb/Makefile | 4 + drivers/staging/xgifb/TODO | 14 + drivers/staging/xgifb/XGI_main.h | 504 + drivers/staging/xgifb/XGI_main_26.c | 2398 +++++ drivers/staging/xgifb/XGIfb.h | 113 + drivers/staging/xgifb/vb_def.h | 267 + drivers/staging/xgifb/vb_init.c | 1835 ++++ drivers/staging/xgifb/vb_init.h | 6 + drivers/staging/xgifb/vb_setmode.c | 7538 +++++++++++++ drivers/staging/xgifb/vb_setmode.h | 27 + drivers/staging/xgifb/vb_struct.h | 334 + drivers/staging/xgifb/vb_table.h | 3215 ++++++ drivers/staging/xgifb/vb_util.c | 53 + drivers/staging/xgifb/vb_util.h | 9 + drivers/staging/xgifb/vgatypes.h | 88 + drivers/staging/zcache/Kconfig | 43 + drivers/staging/zcache/Makefile | 6 + drivers/staging/zcache/TODO | 69 + drivers/staging/zcache/ramster.h | 59 + drivers/staging/zcache/ramster/heartbeat.c | 462 + drivers/staging/zcache/ramster/heartbeat.h | 87 + drivers/staging/zcache/ramster/masklog.c | 155 + drivers/staging/zcache/ramster/masklog.h | 220 + drivers/staging/zcache/ramster/nodemanager.c | 995 ++ drivers/staging/zcache/ramster/nodemanager.h | 88 + drivers/staging/zcache/ramster/r2net.c | 414 + drivers/staging/zcache/ramster/ramster.c | 985 ++ drivers/staging/zcache/ramster/ramster.h | 161 + .../staging/zcache/ramster/ramster_nodemanager.h | 39 + drivers/staging/zcache/ramster/tcp.c | 2248 ++++ drivers/staging/zcache/ramster/tcp.h | 159 + drivers/staging/zcache/ramster/tcp_internal.h | 248 + drivers/staging/zcache/tmem.c | 894 ++ drivers/staging/zcache/tmem.h | 259 + drivers/staging/zcache/zbud.c | 1063 ++ drivers/staging/zcache/zbud.h | 33 + drivers/staging/zcache/zcache-main.c | 2019 ++++ drivers/staging/zcache/zcache.h | 53 + drivers/staging/zram/Kconfig | 25 + drivers/staging/zram/Makefile | 3 + drivers/staging/zram/zram.txt | 77 + drivers/staging/zram/zram_drv.c | 751 ++ drivers/staging/zram/zram_drv.h | 123 + drivers/staging/zram/zram_sysfs.c | 227 + drivers/staging/zsmalloc/Kconfig | 10 + drivers/staging/zsmalloc/Makefile | 3 + drivers/staging/zsmalloc/zsmalloc-main.c | 1067 ++ drivers/staging/zsmalloc/zsmalloc.h | 43 + drivers/staging/zsmalloc/zsmalloc_int.h | 155 + 1876 files changed, 890386 insertions(+) create mode 100644 drivers/staging/Kconfig create mode 100644 drivers/staging/Makefile create mode 100644 drivers/staging/android/Kconfig create mode 100644 drivers/staging/android/Makefile create mode 100644 drivers/staging/android/alarm-dev.c create mode 100644 drivers/staging/android/android_alarm.h create mode 100644 drivers/staging/android/ashmem.c create mode 100644 drivers/staging/android/ashmem.h create mode 100644 drivers/staging/android/binder.c create mode 100644 drivers/staging/android/binder.h create mode 100644 drivers/staging/android/binder_trace.h create mode 100644 drivers/staging/android/logger.c create mode 100644 drivers/staging/android/logger.h create mode 100644 drivers/staging/android/lowmemorykiller.c create mode 100644 drivers/staging/android/persistent_ram.c create mode 100644 drivers/staging/android/ram_console.c create mode 100644 drivers/staging/android/ram_console.h create mode 100644 drivers/staging/android/switch/Kconfig create mode 100644 drivers/staging/android/switch/Makefile create mode 100644 drivers/staging/android/switch/switch.h create mode 100644 drivers/staging/android/switch/switch_class.c create mode 100644 drivers/staging/android/switch/switch_gpio.c create mode 100644 drivers/staging/android/timed_gpio.c create mode 100644 drivers/staging/android/timed_gpio.h create mode 100644 drivers/staging/android/timed_output.c create mode 100644 drivers/staging/android/timed_output.h create mode 100644 drivers/staging/android/trace_persistent.c create mode 100644 drivers/staging/asus_oled/Kconfig create mode 100644 drivers/staging/asus_oled/Makefile create mode 100644 drivers/staging/asus_oled/README create mode 100644 drivers/staging/asus_oled/TODO create mode 100644 drivers/staging/asus_oled/asus_oled.c create mode 100644 drivers/staging/asus_oled/linux.txt create mode 100644 drivers/staging/asus_oled/linux_f.txt create mode 100644 drivers/staging/asus_oled/linux_fr.txt create mode 100644 drivers/staging/asus_oled/tux.txt create mode 100644 drivers/staging/asus_oled/tux_r.txt create mode 100644 drivers/staging/asus_oled/tux_r2.txt create mode 100644 drivers/staging/asus_oled/zig.txt create mode 100644 drivers/staging/bcm/Adapter.h create mode 100644 drivers/staging/bcm/Bcmchar.c create mode 100644 drivers/staging/bcm/Bcmnet.c create mode 100644 drivers/staging/bcm/CmHost.c create mode 100644 drivers/staging/bcm/CmHost.h create mode 100644 drivers/staging/bcm/DDRInit.c create mode 100644 drivers/staging/bcm/DDRInit.h create mode 100644 drivers/staging/bcm/Debug.h create mode 100644 drivers/staging/bcm/HandleControlPacket.c create mode 100644 drivers/staging/bcm/HostMIBSInterface.h create mode 100644 drivers/staging/bcm/IPv6Protocol.c create mode 100644 drivers/staging/bcm/IPv6ProtocolHdr.h create mode 100644 drivers/staging/bcm/InterfaceAdapter.h create mode 100644 drivers/staging/bcm/InterfaceDld.c create mode 100644 drivers/staging/bcm/InterfaceIdleMode.c create mode 100644 drivers/staging/bcm/InterfaceIdleMode.h create mode 100644 drivers/staging/bcm/InterfaceInit.c create mode 100644 drivers/staging/bcm/InterfaceInit.h create mode 100644 drivers/staging/bcm/InterfaceIsr.c create mode 100644 drivers/staging/bcm/InterfaceIsr.h create mode 100644 drivers/staging/bcm/InterfaceMacros.h create mode 100644 drivers/staging/bcm/InterfaceMisc.c create mode 100644 drivers/staging/bcm/InterfaceMisc.h create mode 100644 drivers/staging/bcm/InterfaceRx.c create mode 100644 drivers/staging/bcm/InterfaceRx.h create mode 100644 drivers/staging/bcm/InterfaceTx.c create mode 100644 drivers/staging/bcm/InterfaceTx.h create mode 100644 drivers/staging/bcm/Ioctl.h create mode 100644 drivers/staging/bcm/Kconfig create mode 100644 drivers/staging/bcm/LeakyBucket.c create mode 100644 drivers/staging/bcm/Macros.h create mode 100644 drivers/staging/bcm/Makefile create mode 100644 drivers/staging/bcm/Misc.c create mode 100644 drivers/staging/bcm/PHSDefines.h create mode 100644 drivers/staging/bcm/PHSModule.c create mode 100644 drivers/staging/bcm/PHSModule.h create mode 100644 drivers/staging/bcm/Protocol.h create mode 100644 drivers/staging/bcm/Prototypes.h create mode 100644 drivers/staging/bcm/Qos.c create mode 100644 drivers/staging/bcm/Queue.h create mode 100644 drivers/staging/bcm/TODO create mode 100644 drivers/staging/bcm/Transmit.c create mode 100644 drivers/staging/bcm/Typedefs.h create mode 100644 drivers/staging/bcm/Version.h create mode 100644 drivers/staging/bcm/cntrl_SignalingInterface.h create mode 100644 drivers/staging/bcm/headers.h create mode 100644 drivers/staging/bcm/hostmibs.c create mode 100644 drivers/staging/bcm/led_control.c create mode 100644 drivers/staging/bcm/led_control.h create mode 100644 drivers/staging/bcm/nvm.c create mode 100644 drivers/staging/bcm/nvm.h create mode 100644 drivers/staging/bcm/sort.c create mode 100644 drivers/staging/bcm/target_params.h create mode 100644 drivers/staging/bcm/vendorspecificextn.c create mode 100644 drivers/staging/bcm/vendorspecificextn.h create mode 100644 drivers/staging/comedi/Kconfig create mode 100644 drivers/staging/comedi/Makefile create mode 100644 drivers/staging/comedi/TODO create mode 100644 drivers/staging/comedi/comedi.h create mode 100644 drivers/staging/comedi/comedi_compat32.c create mode 100644 drivers/staging/comedi/comedi_compat32.h create mode 100644 drivers/staging/comedi/comedi_fops.c create mode 100644 drivers/staging/comedi/comedi_fops.h create mode 100644 drivers/staging/comedi/comedidev.h create mode 100644 drivers/staging/comedi/comedilib.h create mode 100644 drivers/staging/comedi/drivers.c create mode 100644 drivers/staging/comedi/drivers/8253.h create mode 100644 drivers/staging/comedi/drivers/8255.c create mode 100644 drivers/staging/comedi/drivers/8255.h create mode 100644 drivers/staging/comedi/drivers/Makefile create mode 100644 drivers/staging/comedi/drivers/acl7225b.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_82x54.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_82x54.h create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Chrono.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Chrono.h create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Dig_io.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Dig_io.h create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_INCCPT.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_INCCPT.h create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Inp_cpt.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Inp_cpt.h create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Pwm.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Pwm.h create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Ssi.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Ssi.h create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.h create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Ttl.c create mode 100644 drivers/staging/comedi/drivers/addi-data/APCI1710_Ttl.h create mode 100644 drivers/staging/comedi/drivers/addi-data/addi_amcc_S5920.c create mode 100644 drivers/staging/comedi/drivers/addi-data/addi_amcc_S5920.h create mode 100644 drivers/staging/comedi/drivers/addi-data/addi_amcc_s5933.h create mode 100644 drivers/staging/comedi/drivers/addi-data/addi_common.c create mode 100644 drivers/staging/comedi/drivers/addi-data/addi_common.h create mode 100644 drivers/staging/comedi/drivers/addi-data/addi_eeprom.c create mode 100644 drivers/staging/comedi/drivers/addi-data/amcc_s5933_58.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_APCI1710.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_APCI1710.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci1032.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci1032.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci16xx.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci16xx.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci2016.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci2016.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci2032.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci2032.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci2200.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci2200.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.h create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.c create mode 100644 drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.h create mode 100644 drivers/staging/comedi/drivers/addi_apci_035.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_1032.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_1500.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_1516.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_1564.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_16xx.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_1710.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_2016.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_2032.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_2200.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_3001.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_3120.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_3200.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_3300.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_3501.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_3xxx.c create mode 100644 drivers/staging/comedi/drivers/addi_apci_all.c create mode 100644 drivers/staging/comedi/drivers/adl_pci6208.c create mode 100644 drivers/staging/comedi/drivers/adl_pci7230.c create mode 100644 drivers/staging/comedi/drivers/adl_pci7296.c create mode 100644 drivers/staging/comedi/drivers/adl_pci7432.c create mode 100644 drivers/staging/comedi/drivers/adl_pci8164.c create mode 100644 drivers/staging/comedi/drivers/adl_pci9111.c create mode 100644 drivers/staging/comedi/drivers/adl_pci9118.c create mode 100644 drivers/staging/comedi/drivers/adq12b.c create mode 100644 drivers/staging/comedi/drivers/adv_pci1710.c create mode 100644 drivers/staging/comedi/drivers/adv_pci1723.c create mode 100644 drivers/staging/comedi/drivers/adv_pci_dio.c create mode 100644 drivers/staging/comedi/drivers/aio_aio12_8.c create mode 100644 drivers/staging/comedi/drivers/aio_iiro_16.c create mode 100644 drivers/staging/comedi/drivers/am9513.h create mode 100644 drivers/staging/comedi/drivers/amcc_s5933.h create mode 100644 drivers/staging/comedi/drivers/amplc_dio200.c create mode 100644 drivers/staging/comedi/drivers/amplc_pc236.c create mode 100644 drivers/staging/comedi/drivers/amplc_pc263.c create mode 100644 drivers/staging/comedi/drivers/amplc_pci224.c create mode 100644 drivers/staging/comedi/drivers/amplc_pci230.c create mode 100644 drivers/staging/comedi/drivers/c6xdigio.c create mode 100644 drivers/staging/comedi/drivers/cb_das16_cs.c create mode 100644 drivers/staging/comedi/drivers/cb_pcidas.c create mode 100644 drivers/staging/comedi/drivers/cb_pcidas64.c create mode 100644 drivers/staging/comedi/drivers/cb_pcidda.c create mode 100644 drivers/staging/comedi/drivers/cb_pcidio.c create mode 100644 drivers/staging/comedi/drivers/cb_pcimdas.c create mode 100644 drivers/staging/comedi/drivers/cb_pcimdda.c create mode 100644 drivers/staging/comedi/drivers/comedi_bond.c create mode 100644 drivers/staging/comedi/drivers/comedi_fc.c create mode 100644 drivers/staging/comedi/drivers/comedi_fc.h create mode 100644 drivers/staging/comedi/drivers/comedi_parport.c create mode 100644 drivers/staging/comedi/drivers/comedi_pci.h create mode 100644 drivers/staging/comedi/drivers/comedi_test.c create mode 100644 drivers/staging/comedi/drivers/contec_pci_dio.c create mode 100644 drivers/staging/comedi/drivers/daqboard2000.c create mode 100644 drivers/staging/comedi/drivers/das08.c create mode 100644 drivers/staging/comedi/drivers/das08.h create mode 100644 drivers/staging/comedi/drivers/das08_cs.c create mode 100644 drivers/staging/comedi/drivers/das16.c create mode 100644 drivers/staging/comedi/drivers/das16m1.c create mode 100644 drivers/staging/comedi/drivers/das1800.c create mode 100644 drivers/staging/comedi/drivers/das6402.c create mode 100644 drivers/staging/comedi/drivers/das800.c create mode 100644 drivers/staging/comedi/drivers/dmm32at.c create mode 100644 drivers/staging/comedi/drivers/dt2801.c create mode 100644 drivers/staging/comedi/drivers/dt2811.c create mode 100644 drivers/staging/comedi/drivers/dt2814.c create mode 100644 drivers/staging/comedi/drivers/dt2815.c create mode 100644 drivers/staging/comedi/drivers/dt2817.c create mode 100644 drivers/staging/comedi/drivers/dt282x.c create mode 100644 drivers/staging/comedi/drivers/dt3000.c create mode 100644 drivers/staging/comedi/drivers/dt9812.c create mode 100644 drivers/staging/comedi/drivers/dyna_pci10xx.c create mode 100644 drivers/staging/comedi/drivers/fl512.c create mode 100644 drivers/staging/comedi/drivers/gsc_hpdi.c create mode 100644 drivers/staging/comedi/drivers/icp_multi.c create mode 100644 drivers/staging/comedi/drivers/icp_multi.h create mode 100644 drivers/staging/comedi/drivers/ii_pci20kc.c create mode 100644 drivers/staging/comedi/drivers/jr3_pci.c create mode 100644 drivers/staging/comedi/drivers/jr3_pci.h create mode 100644 drivers/staging/comedi/drivers/ke_counter.c create mode 100644 drivers/staging/comedi/drivers/me4000.c create mode 100644 drivers/staging/comedi/drivers/me4000.h create mode 100644 drivers/staging/comedi/drivers/me_daq.c create mode 100644 drivers/staging/comedi/drivers/mite.c create mode 100644 drivers/staging/comedi/drivers/mite.h create mode 100644 drivers/staging/comedi/drivers/mpc624.c create mode 100644 drivers/staging/comedi/drivers/mpc8260cpm.c create mode 100644 drivers/staging/comedi/drivers/multiq3.c create mode 100644 drivers/staging/comedi/drivers/ni_6527.c create mode 100644 drivers/staging/comedi/drivers/ni_65xx.c create mode 100644 drivers/staging/comedi/drivers/ni_660x.c create mode 100644 drivers/staging/comedi/drivers/ni_670x.c create mode 100644 drivers/staging/comedi/drivers/ni_at_a2150.c create mode 100644 drivers/staging/comedi/drivers/ni_at_ao.c create mode 100644 drivers/staging/comedi/drivers/ni_atmio.c create mode 100644 drivers/staging/comedi/drivers/ni_atmio16d.c create mode 100644 drivers/staging/comedi/drivers/ni_daq_700.c create mode 100644 drivers/staging/comedi/drivers/ni_daq_dio24.c create mode 100644 drivers/staging/comedi/drivers/ni_labpc.c create mode 100644 drivers/staging/comedi/drivers/ni_labpc.h create mode 100644 drivers/staging/comedi/drivers/ni_labpc_cs.c create mode 100644 drivers/staging/comedi/drivers/ni_mio_common.c create mode 100644 drivers/staging/comedi/drivers/ni_mio_cs.c create mode 100644 drivers/staging/comedi/drivers/ni_pcidio.c create mode 100644 drivers/staging/comedi/drivers/ni_pcimio.c create mode 100644 drivers/staging/comedi/drivers/ni_stc.h create mode 100644 drivers/staging/comedi/drivers/ni_tio.c create mode 100644 drivers/staging/comedi/drivers/ni_tio.h create mode 100644 drivers/staging/comedi/drivers/ni_tio_internal.h create mode 100644 drivers/staging/comedi/drivers/ni_tiocmd.c create mode 100644 drivers/staging/comedi/drivers/pcl711.c create mode 100644 drivers/staging/comedi/drivers/pcl724.c create mode 100644 drivers/staging/comedi/drivers/pcl725.c create mode 100644 drivers/staging/comedi/drivers/pcl726.c create mode 100644 drivers/staging/comedi/drivers/pcl730.c create mode 100644 drivers/staging/comedi/drivers/pcl812.c create mode 100644 drivers/staging/comedi/drivers/pcl816.c create mode 100644 drivers/staging/comedi/drivers/pcl818.c create mode 100644 drivers/staging/comedi/drivers/pcm3724.c create mode 100644 drivers/staging/comedi/drivers/pcm3730.c create mode 100644 drivers/staging/comedi/drivers/pcm_common.c create mode 100644 drivers/staging/comedi/drivers/pcm_common.h create mode 100644 drivers/staging/comedi/drivers/pcmad.c create mode 100644 drivers/staging/comedi/drivers/pcmda12.c create mode 100644 drivers/staging/comedi/drivers/pcmmio.c create mode 100644 drivers/staging/comedi/drivers/pcmuio.c create mode 100644 drivers/staging/comedi/drivers/plx9052.h create mode 100644 drivers/staging/comedi/drivers/plx9080.h create mode 100644 drivers/staging/comedi/drivers/poc.c create mode 100644 drivers/staging/comedi/drivers/quatech_daqp_cs.c create mode 100644 drivers/staging/comedi/drivers/rtd520.c create mode 100644 drivers/staging/comedi/drivers/rtd520.h create mode 100644 drivers/staging/comedi/drivers/rti800.c create mode 100644 drivers/staging/comedi/drivers/rti802.c create mode 100644 drivers/staging/comedi/drivers/s526.c create mode 100644 drivers/staging/comedi/drivers/s626.c create mode 100644 drivers/staging/comedi/drivers/s626.h create mode 100644 drivers/staging/comedi/drivers/serial2002.c create mode 100644 drivers/staging/comedi/drivers/skel.c create mode 100644 drivers/staging/comedi/drivers/ssv_dnp.c create mode 100644 drivers/staging/comedi/drivers/unioxx5.c create mode 100644 drivers/staging/comedi/drivers/usbdux.c create mode 100644 drivers/staging/comedi/drivers/usbduxfast.c create mode 100644 drivers/staging/comedi/drivers/usbduxsigma.c create mode 100644 drivers/staging/comedi/drivers/vmk80xx.c create mode 100644 drivers/staging/comedi/internal.h create mode 100644 drivers/staging/comedi/kcomedilib/Makefile create mode 100644 drivers/staging/comedi/kcomedilib/kcomedilib_main.c create mode 100644 drivers/staging/comedi/proc.c create mode 100644 drivers/staging/comedi/range.c create mode 100644 drivers/staging/cptm1217/Kconfig create mode 100644 drivers/staging/cptm1217/Makefile create mode 100644 drivers/staging/cptm1217/TODO create mode 100644 drivers/staging/cptm1217/clearpad_tm1217.c create mode 100644 drivers/staging/cptm1217/cp_tm1217.h create mode 100644 drivers/staging/crystalhd/Kconfig create mode 100644 drivers/staging/crystalhd/Makefile create mode 100644 drivers/staging/crystalhd/TODO create mode 100644 drivers/staging/crystalhd/bc_dts_defs.h create mode 100644 drivers/staging/crystalhd/bc_dts_glob_lnx.h create mode 100644 drivers/staging/crystalhd/bcm_70012_regs.h create mode 100644 drivers/staging/crystalhd/crystalhd.h create mode 100644 drivers/staging/crystalhd/crystalhd_cmds.c create mode 100644 drivers/staging/crystalhd/crystalhd_cmds.h create mode 100644 drivers/staging/crystalhd/crystalhd_fw_if.h create mode 100644 drivers/staging/crystalhd/crystalhd_hw.c create mode 100644 drivers/staging/crystalhd/crystalhd_hw.h create mode 100644 drivers/staging/crystalhd/crystalhd_lnx.c create mode 100644 drivers/staging/crystalhd/crystalhd_lnx.h create mode 100644 drivers/staging/crystalhd/crystalhd_misc.c create mode 100644 drivers/staging/crystalhd/crystalhd_misc.h create mode 100644 drivers/staging/cxt1e1/Kconfig create mode 100644 drivers/staging/cxt1e1/Makefile create mode 100644 drivers/staging/cxt1e1/comet.c create mode 100644 drivers/staging/cxt1e1/comet.h create mode 100644 drivers/staging/cxt1e1/comet_tables.c create mode 100644 drivers/staging/cxt1e1/comet_tables.h create mode 100644 drivers/staging/cxt1e1/functions.c create mode 100644 drivers/staging/cxt1e1/hwprobe.c create mode 100644 drivers/staging/cxt1e1/libsbew.h create mode 100644 drivers/staging/cxt1e1/linux.c create mode 100644 drivers/staging/cxt1e1/musycc.c create mode 100644 drivers/staging/cxt1e1/musycc.h create mode 100644 drivers/staging/cxt1e1/ossiRelease.c create mode 100644 drivers/staging/cxt1e1/pmc93x6_eeprom.c create mode 100644 drivers/staging/cxt1e1/pmc93x6_eeprom.h create mode 100644 drivers/staging/cxt1e1/pmcc4.h create mode 100644 drivers/staging/cxt1e1/pmcc4_cpld.h create mode 100644 drivers/staging/cxt1e1/pmcc4_defs.h create mode 100644 drivers/staging/cxt1e1/pmcc4_drv.c create mode 100644 drivers/staging/cxt1e1/pmcc4_ioctls.h create mode 100644 drivers/staging/cxt1e1/pmcc4_private.h create mode 100644 drivers/staging/cxt1e1/pmcc4_sysdep.h create mode 100644 drivers/staging/cxt1e1/sbe_bid.h create mode 100644 drivers/staging/cxt1e1/sbe_promformat.h create mode 100644 drivers/staging/cxt1e1/sbecom_inline_linux.h create mode 100644 drivers/staging/cxt1e1/sbecrc.c create mode 100644 drivers/staging/cxt1e1/sbeid.c create mode 100644 drivers/staging/cxt1e1/sbeproc.c create mode 100644 drivers/staging/cxt1e1/sbeproc.h create mode 100644 drivers/staging/cxt1e1/sbew_ioc.h create mode 100644 drivers/staging/echo/Kconfig create mode 100644 drivers/staging/echo/Makefile create mode 100644 drivers/staging/echo/TODO create mode 100644 drivers/staging/echo/echo.c create mode 100644 drivers/staging/echo/echo.h create mode 100644 drivers/staging/echo/fir.h create mode 100644 drivers/staging/echo/oslec.h create mode 100644 drivers/staging/et131x/Kconfig create mode 100644 drivers/staging/et131x/Makefile create mode 100644 drivers/staging/et131x/README create mode 100644 drivers/staging/et131x/et131x.c create mode 100644 drivers/staging/et131x/et131x.h create mode 100644 drivers/staging/frontier/Kconfig create mode 100644 drivers/staging/frontier/Makefile create mode 100644 drivers/staging/frontier/README create mode 100644 drivers/staging/frontier/TODO create mode 100644 drivers/staging/frontier/alphatrack.c create mode 100644 drivers/staging/frontier/alphatrack.h create mode 100644 drivers/staging/frontier/tranzport.c create mode 100644 drivers/staging/ft1000/Kconfig create mode 100644 drivers/staging/ft1000/Makefile create mode 100644 drivers/staging/ft1000/TODO create mode 100644 drivers/staging/ft1000/ft1000-pcmcia/Makefile create mode 100644 drivers/staging/ft1000/ft1000-pcmcia/boot.h create mode 100644 drivers/staging/ft1000/ft1000-pcmcia/ft1000.h create mode 100644 drivers/staging/ft1000/ft1000-pcmcia/ft1000.img create mode 100644 drivers/staging/ft1000/ft1000-pcmcia/ft1000_cs.c create mode 100644 drivers/staging/ft1000/ft1000-pcmcia/ft1000_dnld.c create mode 100644 drivers/staging/ft1000/ft1000-pcmcia/ft1000_hw.c create mode 100644 drivers/staging/ft1000/ft1000-pcmcia/ft1000_proc.c create mode 100644 drivers/staging/ft1000/ft1000-usb/Makefile create mode 100644 drivers/staging/ft1000/ft1000-usb/ft1000_debug.c create mode 100644 drivers/staging/ft1000/ft1000-usb/ft1000_download.c create mode 100644 drivers/staging/ft1000/ft1000-usb/ft1000_hw.c create mode 100644 drivers/staging/ft1000/ft1000-usb/ft1000_ioctl.h create mode 100644 drivers/staging/ft1000/ft1000-usb/ft1000_proc.c create mode 100644 drivers/staging/ft1000/ft1000-usb/ft1000_usb.c create mode 100644 drivers/staging/ft1000/ft1000-usb/ft1000_usb.h create mode 100644 drivers/staging/ft1000/ft1000-usb/ft3000.img create mode 100644 drivers/staging/ft1000/ft1000.h create mode 100644 drivers/staging/iio/Documentation/dac/max517 create mode 100644 drivers/staging/iio/Documentation/device.txt create mode 100644 drivers/staging/iio/Documentation/generic_buffer.c create mode 100644 drivers/staging/iio/Documentation/iio_event_monitor.c create mode 100644 drivers/staging/iio/Documentation/iio_utils.h create mode 100644 drivers/staging/iio/Documentation/inkernel.txt create mode 100644 drivers/staging/iio/Documentation/overview.txt create mode 100644 drivers/staging/iio/Documentation/ring.txt create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-dds create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-impedance-analyzer-ad5933 create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light-tsl2583 create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-trigger-sysfs create mode 100644 drivers/staging/iio/Documentation/trigger.txt create mode 100644 drivers/staging/iio/Kconfig create mode 100644 drivers/staging/iio/Makefile create mode 100644 drivers/staging/iio/TODO create mode 100644 drivers/staging/iio/accel/Kconfig create mode 100644 drivers/staging/iio/accel/Makefile create mode 100644 drivers/staging/iio/accel/adis16201.h create mode 100644 drivers/staging/iio/accel/adis16201_core.c create mode 100644 drivers/staging/iio/accel/adis16201_ring.c create mode 100644 drivers/staging/iio/accel/adis16201_trigger.c create mode 100644 drivers/staging/iio/accel/adis16203.h create mode 100644 drivers/staging/iio/accel/adis16203_core.c create mode 100644 drivers/staging/iio/accel/adis16203_ring.c create mode 100644 drivers/staging/iio/accel/adis16203_trigger.c create mode 100644 drivers/staging/iio/accel/adis16204.h create mode 100644 drivers/staging/iio/accel/adis16204_core.c create mode 100644 drivers/staging/iio/accel/adis16204_ring.c create mode 100644 drivers/staging/iio/accel/adis16204_trigger.c create mode 100644 drivers/staging/iio/accel/adis16209.h create mode 100644 drivers/staging/iio/accel/adis16209_core.c create mode 100644 drivers/staging/iio/accel/adis16209_ring.c create mode 100644 drivers/staging/iio/accel/adis16209_trigger.c create mode 100644 drivers/staging/iio/accel/adis16220.h create mode 100644 drivers/staging/iio/accel/adis16220_core.c create mode 100644 drivers/staging/iio/accel/adis16240.h create mode 100644 drivers/staging/iio/accel/adis16240_core.c create mode 100644 drivers/staging/iio/accel/adis16240_ring.c create mode 100644 drivers/staging/iio/accel/adis16240_trigger.c create mode 100644 drivers/staging/iio/accel/kxsd9.c create mode 100644 drivers/staging/iio/accel/lis3l02dq.h create mode 100644 drivers/staging/iio/accel/lis3l02dq_core.c create mode 100644 drivers/staging/iio/accel/lis3l02dq_ring.c create mode 100644 drivers/staging/iio/accel/sca3000.h create mode 100644 drivers/staging/iio/accel/sca3000_core.c create mode 100644 drivers/staging/iio/accel/sca3000_ring.c create mode 100644 drivers/staging/iio/adc/Kconfig create mode 100644 drivers/staging/iio/adc/Makefile create mode 100644 drivers/staging/iio/adc/ad7192.c create mode 100644 drivers/staging/iio/adc/ad7192.h create mode 100644 drivers/staging/iio/adc/ad7280a.c create mode 100644 drivers/staging/iio/adc/ad7280a.h create mode 100644 drivers/staging/iio/adc/ad7291.c create mode 100644 drivers/staging/iio/adc/ad7298.h create mode 100644 drivers/staging/iio/adc/ad7298_core.c create mode 100644 drivers/staging/iio/adc/ad7298_ring.c create mode 100644 drivers/staging/iio/adc/ad7476.h create mode 100644 drivers/staging/iio/adc/ad7476_core.c create mode 100644 drivers/staging/iio/adc/ad7476_ring.c create mode 100644 drivers/staging/iio/adc/ad7606.h create mode 100644 drivers/staging/iio/adc/ad7606_core.c create mode 100644 drivers/staging/iio/adc/ad7606_par.c create mode 100644 drivers/staging/iio/adc/ad7606_ring.c create mode 100644 drivers/staging/iio/adc/ad7606_spi.c create mode 100644 drivers/staging/iio/adc/ad7780.c create mode 100644 drivers/staging/iio/adc/ad7780.h create mode 100644 drivers/staging/iio/adc/ad7793.c create mode 100644 drivers/staging/iio/adc/ad7793.h create mode 100644 drivers/staging/iio/adc/ad7816.c create mode 100644 drivers/staging/iio/adc/ad7887.h create mode 100644 drivers/staging/iio/adc/ad7887_core.c create mode 100644 drivers/staging/iio/adc/ad7887_ring.c create mode 100644 drivers/staging/iio/adc/ad799x.h create mode 100644 drivers/staging/iio/adc/ad799x_core.c create mode 100644 drivers/staging/iio/adc/ad799x_ring.c create mode 100644 drivers/staging/iio/adc/adt7310.c create mode 100644 drivers/staging/iio/adc/adt7410.c create mode 100644 drivers/staging/iio/adc/lpc32xx_adc.c create mode 100644 drivers/staging/iio/adc/max1363.h create mode 100644 drivers/staging/iio/adc/max1363_core.c create mode 100644 drivers/staging/iio/adc/max1363_ring.c create mode 100644 drivers/staging/iio/addac/Kconfig create mode 100644 drivers/staging/iio/addac/Makefile create mode 100644 drivers/staging/iio/addac/adt7316-i2c.c create mode 100644 drivers/staging/iio/addac/adt7316-spi.c create mode 100644 drivers/staging/iio/addac/adt7316.c create mode 100644 drivers/staging/iio/addac/adt7316.h create mode 100644 drivers/staging/iio/buffer.h create mode 100644 drivers/staging/iio/cdc/Kconfig create mode 100644 drivers/staging/iio/cdc/Makefile create mode 100644 drivers/staging/iio/cdc/ad7150.c create mode 100644 drivers/staging/iio/cdc/ad7152.c create mode 100644 drivers/staging/iio/cdc/ad7746.c create mode 100644 drivers/staging/iio/cdc/ad7746.h create mode 100644 drivers/staging/iio/consumer.h create mode 100644 drivers/staging/iio/dac/Kconfig create mode 100644 drivers/staging/iio/dac/Makefile create mode 100644 drivers/staging/iio/dac/ad5064.c create mode 100644 drivers/staging/iio/dac/ad5360.c create mode 100644 drivers/staging/iio/dac/ad5380.c create mode 100644 drivers/staging/iio/dac/ad5421.c create mode 100644 drivers/staging/iio/dac/ad5421.h create mode 100644 drivers/staging/iio/dac/ad5446.c create mode 100644 drivers/staging/iio/dac/ad5446.h create mode 100644 drivers/staging/iio/dac/ad5504.c create mode 100644 drivers/staging/iio/dac/ad5504.h create mode 100644 drivers/staging/iio/dac/ad5624r.h create mode 100644 drivers/staging/iio/dac/ad5624r_spi.c create mode 100644 drivers/staging/iio/dac/ad5686.c create mode 100644 drivers/staging/iio/dac/ad5764.c create mode 100644 drivers/staging/iio/dac/ad5791.c create mode 100644 drivers/staging/iio/dac/ad5791.h create mode 100644 drivers/staging/iio/dac/dac.h create mode 100644 drivers/staging/iio/dac/max517.c create mode 100644 drivers/staging/iio/dac/max517.h create mode 100644 drivers/staging/iio/dds/Kconfig create mode 100644 drivers/staging/iio/dds/Makefile create mode 100644 drivers/staging/iio/dds/ad5930.c create mode 100644 drivers/staging/iio/dds/ad9832.c create mode 100644 drivers/staging/iio/dds/ad9832.h create mode 100644 drivers/staging/iio/dds/ad9834.c create mode 100644 drivers/staging/iio/dds/ad9834.h create mode 100644 drivers/staging/iio/dds/ad9850.c create mode 100644 drivers/staging/iio/dds/ad9852.c create mode 100644 drivers/staging/iio/dds/ad9910.c create mode 100644 drivers/staging/iio/dds/ad9951.c create mode 100644 drivers/staging/iio/dds/dds.h create mode 100644 drivers/staging/iio/driver.h create mode 100644 drivers/staging/iio/events.h create mode 100644 drivers/staging/iio/gyro/Kconfig create mode 100644 drivers/staging/iio/gyro/Makefile create mode 100644 drivers/staging/iio/gyro/adis16060_core.c create mode 100644 drivers/staging/iio/gyro/adis16080_core.c create mode 100644 drivers/staging/iio/gyro/adis16130_core.c create mode 100644 drivers/staging/iio/gyro/adis16260.h create mode 100644 drivers/staging/iio/gyro/adis16260_core.c create mode 100644 drivers/staging/iio/gyro/adis16260_platform_data.h create mode 100644 drivers/staging/iio/gyro/adis16260_ring.c create mode 100644 drivers/staging/iio/gyro/adis16260_trigger.c create mode 100644 drivers/staging/iio/gyro/adxrs450.h create mode 100644 drivers/staging/iio/gyro/adxrs450_core.c create mode 100644 drivers/staging/iio/iio.h create mode 100644 drivers/staging/iio/iio_core.h create mode 100644 drivers/staging/iio/iio_core_trigger.h create mode 100644 drivers/staging/iio/iio_dummy_evgen.c create mode 100644 drivers/staging/iio/iio_dummy_evgen.h create mode 100644 drivers/staging/iio/iio_hwmon.c create mode 100644 drivers/staging/iio/iio_simple_dummy.c create mode 100644 drivers/staging/iio/iio_simple_dummy.h create mode 100644 drivers/staging/iio/iio_simple_dummy_buffer.c create mode 100644 drivers/staging/iio/iio_simple_dummy_events.c create mode 100644 drivers/staging/iio/impedance-analyzer/Kconfig create mode 100644 drivers/staging/iio/impedance-analyzer/Makefile create mode 100644 drivers/staging/iio/impedance-analyzer/ad5933.c create mode 100644 drivers/staging/iio/impedance-analyzer/ad5933.h create mode 100644 drivers/staging/iio/imu/Kconfig create mode 100644 drivers/staging/iio/imu/Makefile create mode 100644 drivers/staging/iio/imu/adis16400.h create mode 100644 drivers/staging/iio/imu/adis16400_core.c create mode 100644 drivers/staging/iio/imu/adis16400_ring.c create mode 100644 drivers/staging/iio/imu/adis16400_trigger.c create mode 100644 drivers/staging/iio/industrialio-buffer.c create mode 100644 drivers/staging/iio/industrialio-core.c create mode 100644 drivers/staging/iio/industrialio-event.c create mode 100644 drivers/staging/iio/industrialio-trigger.c create mode 100644 drivers/staging/iio/inkern.c create mode 100644 drivers/staging/iio/kfifo_buf.c create mode 100644 drivers/staging/iio/kfifo_buf.h create mode 100644 drivers/staging/iio/light/Kconfig create mode 100644 drivers/staging/iio/light/Makefile create mode 100644 drivers/staging/iio/light/isl29018.c create mode 100644 drivers/staging/iio/light/tsl2563.c create mode 100644 drivers/staging/iio/light/tsl2563.h create mode 100644 drivers/staging/iio/light/tsl2583.c create mode 100644 drivers/staging/iio/machine.h create mode 100644 drivers/staging/iio/magnetometer/Kconfig create mode 100644 drivers/staging/iio/magnetometer/Makefile create mode 100644 drivers/staging/iio/magnetometer/ak8975.c create mode 100644 drivers/staging/iio/magnetometer/hmc5843.c create mode 100644 drivers/staging/iio/meter/Kconfig create mode 100644 drivers/staging/iio/meter/Makefile create mode 100644 drivers/staging/iio/meter/ade7753.c create mode 100644 drivers/staging/iio/meter/ade7753.h create mode 100644 drivers/staging/iio/meter/ade7754.c create mode 100644 drivers/staging/iio/meter/ade7754.h create mode 100644 drivers/staging/iio/meter/ade7758.h create mode 100644 drivers/staging/iio/meter/ade7758_core.c create mode 100644 drivers/staging/iio/meter/ade7758_ring.c create mode 100644 drivers/staging/iio/meter/ade7758_trigger.c create mode 100644 drivers/staging/iio/meter/ade7759.c create mode 100644 drivers/staging/iio/meter/ade7759.h create mode 100644 drivers/staging/iio/meter/ade7854-i2c.c create mode 100644 drivers/staging/iio/meter/ade7854-spi.c create mode 100644 drivers/staging/iio/meter/ade7854.c create mode 100644 drivers/staging/iio/meter/ade7854.h create mode 100644 drivers/staging/iio/meter/meter.h create mode 100644 drivers/staging/iio/resolver/Kconfig create mode 100644 drivers/staging/iio/resolver/Makefile create mode 100644 drivers/staging/iio/resolver/ad2s1200.c create mode 100644 drivers/staging/iio/resolver/ad2s1210.c create mode 100644 drivers/staging/iio/resolver/ad2s1210.h create mode 100644 drivers/staging/iio/resolver/ad2s90.c create mode 100644 drivers/staging/iio/ring_hw.h create mode 100644 drivers/staging/iio/ring_sw.c create mode 100644 drivers/staging/iio/ring_sw.h create mode 100644 drivers/staging/iio/sysfs.h create mode 100644 drivers/staging/iio/trigger.h create mode 100644 drivers/staging/iio/trigger/Kconfig create mode 100644 drivers/staging/iio/trigger/Makefile create mode 100644 drivers/staging/iio/trigger/iio-trig-bfin-timer.c create mode 100644 drivers/staging/iio/trigger/iio-trig-gpio.c create mode 100644 drivers/staging/iio/trigger/iio-trig-periodic-rtc.c create mode 100644 drivers/staging/iio/trigger/iio-trig-sysfs.c create mode 100644 drivers/staging/iio/trigger_consumer.h create mode 100644 drivers/staging/iio/types.h create mode 100644 drivers/staging/keucr/Kconfig create mode 100644 drivers/staging/keucr/Makefile create mode 100644 drivers/staging/keucr/TODO create mode 100644 drivers/staging/keucr/common.h create mode 100644 drivers/staging/keucr/init.c create mode 100644 drivers/staging/keucr/init.h create mode 100644 drivers/staging/keucr/scsiglue.c create mode 100644 drivers/staging/keucr/scsiglue.h create mode 100644 drivers/staging/keucr/smcommon.h create mode 100644 drivers/staging/keucr/smil.h create mode 100644 drivers/staging/keucr/smilecc.c create mode 100644 drivers/staging/keucr/smilmain.c create mode 100644 drivers/staging/keucr/smilsub.c create mode 100644 drivers/staging/keucr/smscsi.c create mode 100644 drivers/staging/keucr/transport.c create mode 100644 drivers/staging/keucr/transport.h create mode 100644 drivers/staging/keucr/usb.c create mode 100644 drivers/staging/keucr/usb.h create mode 100644 drivers/staging/line6/Kconfig create mode 100644 drivers/staging/line6/Makefile create mode 100644 drivers/staging/line6/audio.c create mode 100644 drivers/staging/line6/audio.h create mode 100644 drivers/staging/line6/capture.c create mode 100644 drivers/staging/line6/capture.h create mode 100644 drivers/staging/line6/config.h create mode 100644 drivers/staging/line6/control.c create mode 100644 drivers/staging/line6/control.h create mode 100644 drivers/staging/line6/driver.c create mode 100644 drivers/staging/line6/driver.h create mode 100644 drivers/staging/line6/dumprequest.c create mode 100644 drivers/staging/line6/dumprequest.h create mode 100644 drivers/staging/line6/midi.c create mode 100644 drivers/staging/line6/midi.h create mode 100644 drivers/staging/line6/midibuf.c create mode 100644 drivers/staging/line6/midibuf.h create mode 100644 drivers/staging/line6/pcm.c create mode 100644 drivers/staging/line6/pcm.h create mode 100644 drivers/staging/line6/playback.c create mode 100644 drivers/staging/line6/playback.h create mode 100644 drivers/staging/line6/pod.c create mode 100644 drivers/staging/line6/pod.h create mode 100644 drivers/staging/line6/podhd.c create mode 100644 drivers/staging/line6/podhd.h create mode 100644 drivers/staging/line6/revision.h create mode 100644 drivers/staging/line6/toneport.c create mode 100644 drivers/staging/line6/toneport.h create mode 100644 drivers/staging/line6/usbdefs.h create mode 100644 drivers/staging/line6/variax.c create mode 100644 drivers/staging/line6/variax.h create mode 100644 drivers/staging/media/Kconfig create mode 100644 drivers/staging/media/Makefile create mode 100644 drivers/staging/media/as102/Kconfig create mode 100644 drivers/staging/media/as102/Makefile create mode 100644 drivers/staging/media/as102/as102_drv.c create mode 100644 drivers/staging/media/as102/as102_drv.h create mode 100644 drivers/staging/media/as102/as102_fe.c create mode 100644 drivers/staging/media/as102/as102_fw.c create mode 100644 drivers/staging/media/as102/as102_fw.h create mode 100644 drivers/staging/media/as102/as102_usb_drv.c create mode 100644 drivers/staging/media/as102/as102_usb_drv.h create mode 100644 drivers/staging/media/as102/as10x_cmd.c create mode 100644 drivers/staging/media/as102/as10x_cmd.h create mode 100644 drivers/staging/media/as102/as10x_cmd_cfg.c create mode 100644 drivers/staging/media/as102/as10x_cmd_stream.c create mode 100644 drivers/staging/media/as102/as10x_handle.h create mode 100644 drivers/staging/media/as102/as10x_types.h create mode 100644 drivers/staging/media/cxd2099/Kconfig create mode 100644 drivers/staging/media/cxd2099/Makefile create mode 100644 drivers/staging/media/cxd2099/TODO create mode 100644 drivers/staging/media/cxd2099/cxd2099.c create mode 100644 drivers/staging/media/cxd2099/cxd2099.h create mode 100644 drivers/staging/media/dt3155v4l/Kconfig create mode 100644 drivers/staging/media/dt3155v4l/Makefile create mode 100644 drivers/staging/media/dt3155v4l/dt3155v4l.c create mode 100644 drivers/staging/media/dt3155v4l/dt3155v4l.h create mode 100644 drivers/staging/media/easycap/Kconfig create mode 100644 drivers/staging/media/easycap/Makefile create mode 100644 drivers/staging/media/easycap/README create mode 100644 drivers/staging/media/easycap/easycap.h create mode 100644 drivers/staging/media/easycap/easycap_ioctl.c create mode 100644 drivers/staging/media/easycap/easycap_low.c create mode 100644 drivers/staging/media/easycap/easycap_main.c create mode 100644 drivers/staging/media/easycap/easycap_settings.c create mode 100644 drivers/staging/media/easycap/easycap_sound.c create mode 100644 drivers/staging/media/easycap/easycap_testcard.c create mode 100644 drivers/staging/media/go7007/Kconfig create mode 100644 drivers/staging/media/go7007/Makefile create mode 100644 drivers/staging/media/go7007/README create mode 100644 drivers/staging/media/go7007/go7007-driver.c create mode 100644 drivers/staging/media/go7007/go7007-fw.c create mode 100644 drivers/staging/media/go7007/go7007-i2c.c create mode 100644 drivers/staging/media/go7007/go7007-priv.h create mode 100644 drivers/staging/media/go7007/go7007-usb.c create mode 100644 drivers/staging/media/go7007/go7007-v4l2.c create mode 100644 drivers/staging/media/go7007/go7007.h create mode 100644 drivers/staging/media/go7007/go7007.txt create mode 100644 drivers/staging/media/go7007/s2250-board.c create mode 100644 drivers/staging/media/go7007/s2250-loader.c create mode 100644 drivers/staging/media/go7007/s2250-loader.h create mode 100644 drivers/staging/media/go7007/saa7134-go7007.c create mode 100644 drivers/staging/media/go7007/snd-go7007.c create mode 100644 drivers/staging/media/go7007/wis-i2c.h create mode 100644 drivers/staging/media/go7007/wis-ov7640.c create mode 100644 drivers/staging/media/go7007/wis-saa7113.c create mode 100644 drivers/staging/media/go7007/wis-saa7115.c create mode 100644 drivers/staging/media/go7007/wis-sony-tuner.c create mode 100644 drivers/staging/media/go7007/wis-tw2804.c create mode 100644 drivers/staging/media/go7007/wis-tw9903.c create mode 100644 drivers/staging/media/go7007/wis-uda1342.c create mode 100644 drivers/staging/media/lirc/Kconfig create mode 100644 drivers/staging/media/lirc/Makefile create mode 100644 drivers/staging/media/lirc/TODO create mode 100644 drivers/staging/media/lirc/TODO.lirc_zilog create mode 100644 drivers/staging/media/lirc/lirc_bt829.c create mode 100644 drivers/staging/media/lirc/lirc_ene0100.h create mode 100644 drivers/staging/media/lirc/lirc_igorplugusb.c create mode 100644 drivers/staging/media/lirc/lirc_imon.c create mode 100644 drivers/staging/media/lirc/lirc_parallel.c create mode 100644 drivers/staging/media/lirc/lirc_parallel.h create mode 100644 drivers/staging/media/lirc/lirc_sasem.c create mode 100644 drivers/staging/media/lirc/lirc_serial.c create mode 100644 drivers/staging/media/lirc/lirc_sir.c create mode 100644 drivers/staging/media/lirc/lirc_ttusbir.c create mode 100644 drivers/staging/media/lirc/lirc_zilog.c create mode 100644 drivers/staging/media/solo6x10/Kconfig create mode 100644 drivers/staging/media/solo6x10/Makefile create mode 100644 drivers/staging/media/solo6x10/TODO create mode 100644 drivers/staging/media/solo6x10/core.c create mode 100644 drivers/staging/media/solo6x10/disp.c create mode 100644 drivers/staging/media/solo6x10/enc.c create mode 100644 drivers/staging/media/solo6x10/g723.c create mode 100644 drivers/staging/media/solo6x10/gpio.c create mode 100644 drivers/staging/media/solo6x10/i2c.c create mode 100644 drivers/staging/media/solo6x10/offsets.h create mode 100644 drivers/staging/media/solo6x10/osd-font.h create mode 100644 drivers/staging/media/solo6x10/p2m.c create mode 100644 drivers/staging/media/solo6x10/registers.h create mode 100644 drivers/staging/media/solo6x10/solo6x10-jpeg.h create mode 100644 drivers/staging/media/solo6x10/solo6x10.h create mode 100644 drivers/staging/media/solo6x10/tw28.c create mode 100644 drivers/staging/media/solo6x10/tw28.h create mode 100644 drivers/staging/media/solo6x10/v4l2-enc.c create mode 100644 drivers/staging/media/solo6x10/v4l2.c create mode 100644 drivers/staging/mei/Kconfig create mode 100644 drivers/staging/mei/Makefile create mode 100644 drivers/staging/mei/TODO create mode 100644 drivers/staging/mei/hw.h create mode 100644 drivers/staging/mei/init.c create mode 100644 drivers/staging/mei/interface.c create mode 100644 drivers/staging/mei/interface.h create mode 100644 drivers/staging/mei/interrupt.c create mode 100644 drivers/staging/mei/iorw.c create mode 100644 drivers/staging/mei/main.c create mode 100644 drivers/staging/mei/mei-amt-version.c create mode 100644 drivers/staging/mei/mei.h create mode 100644 drivers/staging/mei/mei.txt create mode 100644 drivers/staging/mei/mei_dev.h create mode 100644 drivers/staging/mei/wd.c create mode 100644 drivers/staging/nvec/Kconfig create mode 100644 drivers/staging/nvec/Makefile create mode 100644 drivers/staging/nvec/README create mode 100644 drivers/staging/nvec/TODO create mode 100644 drivers/staging/nvec/nvec-keytable.h create mode 100644 drivers/staging/nvec/nvec.c create mode 100644 drivers/staging/nvec/nvec.h create mode 100644 drivers/staging/nvec/nvec_kbd.c create mode 100644 drivers/staging/nvec/nvec_leds.c create mode 100644 drivers/staging/nvec/nvec_power.c create mode 100644 drivers/staging/nvec/nvec_ps2.c create mode 100644 drivers/staging/octeon/Kconfig create mode 100644 drivers/staging/octeon/Makefile create mode 100644 drivers/staging/octeon/ethernet-defines.h create mode 100644 drivers/staging/octeon/ethernet-mdio.c create mode 100644 drivers/staging/octeon/ethernet-mdio.h create mode 100644 drivers/staging/octeon/ethernet-mem.c create mode 100644 drivers/staging/octeon/ethernet-mem.h create mode 100644 drivers/staging/octeon/ethernet-rgmii.c create mode 100644 drivers/staging/octeon/ethernet-rx.c create mode 100644 drivers/staging/octeon/ethernet-rx.h create mode 100644 drivers/staging/octeon/ethernet-sgmii.c create mode 100644 drivers/staging/octeon/ethernet-spi.c create mode 100644 drivers/staging/octeon/ethernet-tx.c create mode 100644 drivers/staging/octeon/ethernet-tx.h create mode 100644 drivers/staging/octeon/ethernet-util.h create mode 100644 drivers/staging/octeon/ethernet-xaui.c create mode 100644 drivers/staging/octeon/ethernet.c create mode 100644 drivers/staging/octeon/octeon-ethernet.h create mode 100644 drivers/staging/olpc_dcon/Kconfig create mode 100644 drivers/staging/olpc_dcon/Makefile create mode 100644 drivers/staging/olpc_dcon/TODO create mode 100644 drivers/staging/olpc_dcon/olpc_dcon.c create mode 100644 drivers/staging/olpc_dcon/olpc_dcon.h create mode 100644 drivers/staging/olpc_dcon/olpc_dcon_xo_1.c create mode 100644 drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c create mode 100644 drivers/staging/omapdrm/Kconfig create mode 100644 drivers/staging/omapdrm/Makefile create mode 100644 drivers/staging/omapdrm/TODO create mode 100644 drivers/staging/omapdrm/omap_connector.c create mode 100644 drivers/staging/omapdrm/omap_crtc.c create mode 100644 drivers/staging/omapdrm/omap_debugfs.c create mode 100644 drivers/staging/omapdrm/omap_dmm_priv.h create mode 100644 drivers/staging/omapdrm/omap_dmm_tiler.c create mode 100644 drivers/staging/omapdrm/omap_dmm_tiler.h create mode 100644 drivers/staging/omapdrm/omap_drm.h create mode 100644 drivers/staging/omapdrm/omap_drv.c create mode 100644 drivers/staging/omapdrm/omap_drv.h create mode 100644 drivers/staging/omapdrm/omap_encoder.c create mode 100644 drivers/staging/omapdrm/omap_fb.c create mode 100644 drivers/staging/omapdrm/omap_fbdev.c create mode 100644 drivers/staging/omapdrm/omap_gem.c create mode 100644 drivers/staging/omapdrm/omap_gem_helpers.c create mode 100644 drivers/staging/omapdrm/omap_plane.c create mode 100644 drivers/staging/omapdrm/omap_priv.h create mode 100644 drivers/staging/omapdrm/tcm-sita.c create mode 100644 drivers/staging/omapdrm/tcm-sita.h create mode 100644 drivers/staging/omapdrm/tcm.h create mode 100644 drivers/staging/ozwpan/Kbuild create mode 100644 drivers/staging/ozwpan/Kconfig create mode 100644 drivers/staging/ozwpan/README create mode 100644 drivers/staging/ozwpan/TODO create mode 100644 drivers/staging/ozwpan/ozappif.h create mode 100644 drivers/staging/ozwpan/ozcdev.c create mode 100644 drivers/staging/ozwpan/ozcdev.h create mode 100644 drivers/staging/ozwpan/ozconfig.h create mode 100644 drivers/staging/ozwpan/ozeltbuf.c create mode 100644 drivers/staging/ozwpan/ozeltbuf.h create mode 100644 drivers/staging/ozwpan/ozevent.c create mode 100644 drivers/staging/ozwpan/ozevent.h create mode 100644 drivers/staging/ozwpan/ozeventdef.h create mode 100644 drivers/staging/ozwpan/ozhcd.c create mode 100644 drivers/staging/ozwpan/ozhcd.h create mode 100644 drivers/staging/ozwpan/ozmain.c create mode 100644 drivers/staging/ozwpan/ozpd.c create mode 100644 drivers/staging/ozwpan/ozpd.h create mode 100644 drivers/staging/ozwpan/ozproto.c create mode 100644 drivers/staging/ozwpan/ozproto.h create mode 100644 drivers/staging/ozwpan/ozprotocol.h create mode 100644 drivers/staging/ozwpan/oztrace.c create mode 100644 drivers/staging/ozwpan/oztrace.h create mode 100644 drivers/staging/ozwpan/ozurbparanoia.c create mode 100644 drivers/staging/ozwpan/ozurbparanoia.h create mode 100644 drivers/staging/ozwpan/ozusbif.h create mode 100644 drivers/staging/ozwpan/ozusbsvc.c create mode 100644 drivers/staging/ozwpan/ozusbsvc.h create mode 100644 drivers/staging/ozwpan/ozusbsvc1.c create mode 100644 drivers/staging/panel/Kconfig create mode 100644 drivers/staging/panel/Makefile create mode 100644 drivers/staging/panel/TODO create mode 100644 drivers/staging/panel/lcd-panel-cgram.txt create mode 100644 drivers/staging/panel/panel.c create mode 100644 drivers/staging/phison/Kconfig create mode 100644 drivers/staging/phison/Makefile create mode 100644 drivers/staging/phison/phison.c create mode 100644 drivers/staging/quatech_usb2/Kconfig create mode 100644 drivers/staging/quatech_usb2/Makefile create mode 100644 drivers/staging/quatech_usb2/TODO create mode 100644 drivers/staging/quatech_usb2/quatech_usb2.c create mode 100644 drivers/staging/quickstart/Kconfig create mode 100644 drivers/staging/quickstart/Makefile create mode 100644 drivers/staging/quickstart/quickstart.c create mode 100644 drivers/staging/ramster/Kconfig create mode 100644 drivers/staging/ramster/Makefile create mode 100644 drivers/staging/ramster/TODO create mode 100644 drivers/staging/ramster/cluster/Makefile create mode 100644 drivers/staging/ramster/cluster/heartbeat.c create mode 100644 drivers/staging/ramster/cluster/heartbeat.h create mode 100644 drivers/staging/ramster/cluster/masklog.c create mode 100644 drivers/staging/ramster/cluster/masklog.h create mode 100644 drivers/staging/ramster/cluster/nodemanager.c create mode 100644 drivers/staging/ramster/cluster/nodemanager.h create mode 100644 drivers/staging/ramster/cluster/ramster_nodemanager.h create mode 100644 drivers/staging/ramster/cluster/tcp.c create mode 100644 drivers/staging/ramster/cluster/tcp.h create mode 100644 drivers/staging/ramster/cluster/tcp_internal.h create mode 100644 drivers/staging/ramster/r2net.c create mode 100644 drivers/staging/ramster/ramster.h create mode 100644 drivers/staging/ramster/tmem.c create mode 100644 drivers/staging/ramster/tmem.h create mode 100644 drivers/staging/ramster/xvmalloc.c create mode 100644 drivers/staging/ramster/xvmalloc.h create mode 100644 drivers/staging/ramster/xvmalloc_int.h create mode 100644 drivers/staging/ramster/zcache-main.c create mode 100644 drivers/staging/ramster/zcache.h create mode 100644 drivers/staging/rtl8187se/Kconfig create mode 100644 drivers/staging/rtl8187se/Makefile create mode 100644 drivers/staging/rtl8187se/TODO create mode 100644 drivers/staging/rtl8187se/ieee80211/dot11d.c create mode 100644 drivers/staging/rtl8187se/ieee80211/dot11d.h create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211.h create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_crypt.c create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_crypt.h create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_crypt_ccmp.c create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_crypt_tkip.c create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_crypt_wep.c create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_module.c create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_rx.c create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_softmac.c create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_softmac_wx.c create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_tx.c create mode 100644 drivers/staging/rtl8187se/ieee80211/ieee80211_wx.c create mode 100644 drivers/staging/rtl8187se/r8180.h create mode 100644 drivers/staging/rtl8187se/r8180_93cx6.h create mode 100644 drivers/staging/rtl8187se/r8180_core.c create mode 100644 drivers/staging/rtl8187se/r8180_dm.c create mode 100644 drivers/staging/rtl8187se/r8180_dm.h create mode 100644 drivers/staging/rtl8187se/r8180_hw.h create mode 100644 drivers/staging/rtl8187se/r8180_rtl8225.h create mode 100644 drivers/staging/rtl8187se/r8180_rtl8225z2.c create mode 100644 drivers/staging/rtl8187se/r8180_wx.c create mode 100644 drivers/staging/rtl8187se/r8180_wx.h create mode 100644 drivers/staging/rtl8187se/r8185b_init.c create mode 100644 drivers/staging/rtl8192e/Kconfig create mode 100644 drivers/staging/rtl8192e/Makefile create mode 100644 drivers/staging/rtl8192e/TODO create mode 100644 drivers/staging/rtl8192e/dot11d.c create mode 100644 drivers/staging/rtl8192e/dot11d.h create mode 100644 drivers/staging/rtl8192e/license create mode 100644 drivers/staging/rtl8192e/rtl8192e/Kconfig create mode 100644 drivers/staging/rtl8192e/rtl8192e/Makefile create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8190P_def.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8190P_rtl8256.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8190P_rtl8256.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_cmdpkt.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_cmdpkt.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_dev.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_dev.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_firmware.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_firmware.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_hw.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_hwimg.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_hwimg.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_phy.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_phy.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/r8192E_phyreg.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/r819xE_phyreg.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_cam.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_cam.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_core.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_core.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_crypto.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_debug.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_dm.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_dm.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_eeprom.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_eeprom.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_ethtool.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_pci.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_pci.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_pm.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_pm.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_ps.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_ps.h create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_wx.c create mode 100644 drivers/staging/rtl8192e/rtl8192e/rtl_wx.h create mode 100644 drivers/staging/rtl8192e/rtl819x_BA.h create mode 100644 drivers/staging/rtl8192e/rtl819x_BAProc.c create mode 100644 drivers/staging/rtl8192e/rtl819x_HT.h create mode 100644 drivers/staging/rtl8192e/rtl819x_HTProc.c create mode 100644 drivers/staging/rtl8192e/rtl819x_Qos.h create mode 100644 drivers/staging/rtl8192e/rtl819x_TS.h create mode 100644 drivers/staging/rtl8192e/rtl819x_TSProc.c create mode 100644 drivers/staging/rtl8192e/rtllib.h create mode 100644 drivers/staging/rtl8192e/rtllib_crypt.c create mode 100644 drivers/staging/rtl8192e/rtllib_crypt.h create mode 100644 drivers/staging/rtl8192e/rtllib_crypt_ccmp.c create mode 100644 drivers/staging/rtl8192e/rtllib_crypt_tkip.c create mode 100644 drivers/staging/rtl8192e/rtllib_crypt_wep.c create mode 100644 drivers/staging/rtl8192e/rtllib_debug.h create mode 100644 drivers/staging/rtl8192e/rtllib_endianfree.h create mode 100644 drivers/staging/rtl8192e/rtllib_module.c create mode 100644 drivers/staging/rtl8192e/rtllib_rx.c create mode 100644 drivers/staging/rtl8192e/rtllib_softmac.c create mode 100644 drivers/staging/rtl8192e/rtllib_softmac_wx.c create mode 100644 drivers/staging/rtl8192e/rtllib_tx.c create mode 100644 drivers/staging/rtl8192e/rtllib_wx.c create mode 100644 drivers/staging/rtl8192u/Kconfig create mode 100644 drivers/staging/rtl8192u/Makefile create mode 100644 drivers/staging/rtl8192u/authors create mode 100644 drivers/staging/rtl8192u/changes create mode 100644 drivers/staging/rtl8192u/copying create mode 100644 drivers/staging/rtl8192u/dot11d.h create mode 100644 drivers/staging/rtl8192u/ieee80211/EndianFree.h create mode 100644 drivers/staging/rtl8192u/ieee80211/Makefile create mode 100644 drivers/staging/rtl8192u/ieee80211/aes.c create mode 100644 drivers/staging/rtl8192u/ieee80211/arc4.c create mode 100644 drivers/staging/rtl8192u/ieee80211/autoload.c create mode 100644 drivers/staging/rtl8192u/ieee80211/cipher.c create mode 100644 drivers/staging/rtl8192u/ieee80211/compress.c create mode 100644 drivers/staging/rtl8192u/ieee80211/crypto_compat.h create mode 100644 drivers/staging/rtl8192u/ieee80211/digest.c create mode 100644 drivers/staging/rtl8192u/ieee80211/dot11d.c create mode 100644 drivers/staging/rtl8192u/ieee80211/dot11d.h create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211.h create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_crypt.c create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_crypt.h create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_crypt_ccmp.c create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_crypt_tkip.c create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_crypt_wep.c create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_module.c create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_rx.c create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_softmac.c create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_softmac_wx.c create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_tx.c create mode 100644 drivers/staging/rtl8192u/ieee80211/ieee80211_wx.c create mode 100644 drivers/staging/rtl8192u/ieee80211/internal.h create mode 100644 drivers/staging/rtl8192u/ieee80211/michael_mic.c create mode 100644 drivers/staging/rtl8192u/ieee80211/proc.c create mode 100644 drivers/staging/rtl8192u/ieee80211/rtl819x_BA.h create mode 100644 drivers/staging/rtl8192u/ieee80211/rtl819x_BAProc.c create mode 100644 drivers/staging/rtl8192u/ieee80211/rtl819x_HT.h create mode 100644 drivers/staging/rtl8192u/ieee80211/rtl819x_HTProc.c create mode 100644 drivers/staging/rtl8192u/ieee80211/rtl819x_Qos.h create mode 100644 drivers/staging/rtl8192u/ieee80211/rtl819x_TS.h create mode 100644 drivers/staging/rtl8192u/ieee80211/rtl819x_TSProc.c create mode 100644 drivers/staging/rtl8192u/ieee80211/rtl_crypto.h create mode 100644 drivers/staging/rtl8192u/ieee80211/scatterwalk.c create mode 100644 drivers/staging/rtl8192u/ieee80211/scatterwalk.h create mode 100644 drivers/staging/rtl8192u/ieee80211_crypt.h create mode 100644 drivers/staging/rtl8192u/r8180_93cx6.c create mode 100644 drivers/staging/rtl8192u/r8180_93cx6.h create mode 100644 drivers/staging/rtl8192u/r8180_pm.c create mode 100644 drivers/staging/rtl8192u/r8180_pm.h create mode 100644 drivers/staging/rtl8192u/r8190_rtl8256.c create mode 100644 drivers/staging/rtl8192u/r8190_rtl8256.h create mode 100644 drivers/staging/rtl8192u/r8192U.h create mode 100644 drivers/staging/rtl8192u/r8192U_core.c create mode 100644 drivers/staging/rtl8192u/r8192U_dm.c create mode 100644 drivers/staging/rtl8192u/r8192U_dm.h create mode 100644 drivers/staging/rtl8192u/r8192U_hw.h create mode 100644 drivers/staging/rtl8192u/r8192U_wx.c create mode 100644 drivers/staging/rtl8192u/r8192U_wx.h create mode 100644 drivers/staging/rtl8192u/r819xU_HTGen.h create mode 100644 drivers/staging/rtl8192u/r819xU_HTType.h create mode 100644 drivers/staging/rtl8192u/r819xU_cmdpkt.c create mode 100644 drivers/staging/rtl8192u/r819xU_cmdpkt.h create mode 100644 drivers/staging/rtl8192u/r819xU_firmware.c create mode 100644 drivers/staging/rtl8192u/r819xU_firmware.h create mode 100644 drivers/staging/rtl8192u/r819xU_firmware_img.c create mode 100644 drivers/staging/rtl8192u/r819xU_firmware_img.h create mode 100644 drivers/staging/rtl8192u/r819xU_phy.c create mode 100644 drivers/staging/rtl8192u/r819xU_phy.h create mode 100644 drivers/staging/rtl8192u/r819xU_phyreg.h create mode 100644 drivers/staging/rtl8712/Kconfig create mode 100644 drivers/staging/rtl8712/Makefile create mode 100644 drivers/staging/rtl8712/TODO create mode 100644 drivers/staging/rtl8712/basic_types.h create mode 100644 drivers/staging/rtl8712/big_endian.h create mode 100644 drivers/staging/rtl8712/drv_types.h create mode 100644 drivers/staging/rtl8712/ethernet.h create mode 100644 drivers/staging/rtl8712/generic.h create mode 100644 drivers/staging/rtl8712/hal_init.c create mode 100644 drivers/staging/rtl8712/ieee80211.c create mode 100644 drivers/staging/rtl8712/ieee80211.h create mode 100644 drivers/staging/rtl8712/if_ether.h create mode 100644 drivers/staging/rtl8712/ip.h create mode 100644 drivers/staging/rtl8712/little_endian.h create mode 100644 drivers/staging/rtl8712/mlme_linux.c create mode 100644 drivers/staging/rtl8712/mlme_osdep.h create mode 100644 drivers/staging/rtl8712/mp_custom_oid.h create mode 100644 drivers/staging/rtl8712/os_intfs.c create mode 100644 drivers/staging/rtl8712/osdep_intf.h create mode 100644 drivers/staging/rtl8712/osdep_service.h create mode 100644 drivers/staging/rtl8712/recv_linux.c create mode 100644 drivers/staging/rtl8712/recv_osdep.h create mode 100644 drivers/staging/rtl8712/rtl8712_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_cmd.c create mode 100644 drivers/staging/rtl8712/rtl8712_cmd.h create mode 100644 drivers/staging/rtl8712/rtl8712_cmdctrl_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_cmdctrl_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_debugctrl_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_debugctrl_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_edcasetting_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_edcasetting_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_efuse.c create mode 100644 drivers/staging/rtl8712/rtl8712_efuse.h create mode 100644 drivers/staging/rtl8712/rtl8712_event.h create mode 100644 drivers/staging/rtl8712/rtl8712_fifoctrl_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_fifoctrl_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_gp_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_gp_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_hal.h create mode 100644 drivers/staging/rtl8712/rtl8712_interrupt_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_io.c create mode 100644 drivers/staging/rtl8712/rtl8712_led.c create mode 100644 drivers/staging/rtl8712/rtl8712_macsetting_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_macsetting_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_powersave_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_powersave_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_ratectrl_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_ratectrl_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_recv.c create mode 100644 drivers/staging/rtl8712/rtl8712_recv.h create mode 100644 drivers/staging/rtl8712/rtl8712_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_security_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_spec.h create mode 100644 drivers/staging/rtl8712/rtl8712_syscfg_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_syscfg_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_timectrl_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_timectrl_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_wmac_bitdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_wmac_regdef.h create mode 100644 drivers/staging/rtl8712/rtl8712_xmit.c create mode 100644 drivers/staging/rtl8712/rtl8712_xmit.h create mode 100644 drivers/staging/rtl8712/rtl871x_byteorder.h create mode 100644 drivers/staging/rtl8712/rtl871x_cmd.c create mode 100644 drivers/staging/rtl8712/rtl871x_cmd.h create mode 100644 drivers/staging/rtl8712/rtl871x_debug.h create mode 100644 drivers/staging/rtl8712/rtl871x_eeprom.c create mode 100644 drivers/staging/rtl8712/rtl871x_eeprom.h create mode 100644 drivers/staging/rtl8712/rtl871x_event.h create mode 100644 drivers/staging/rtl8712/rtl871x_ht.h create mode 100644 drivers/staging/rtl8712/rtl871x_io.c create mode 100644 drivers/staging/rtl8712/rtl871x_io.h create mode 100644 drivers/staging/rtl8712/rtl871x_ioctl.h create mode 100644 drivers/staging/rtl8712/rtl871x_ioctl_linux.c create mode 100644 drivers/staging/rtl8712/rtl871x_ioctl_rtl.c create mode 100644 drivers/staging/rtl8712/rtl871x_ioctl_rtl.h create mode 100644 drivers/staging/rtl8712/rtl871x_ioctl_set.c create mode 100644 drivers/staging/rtl8712/rtl871x_ioctl_set.h create mode 100644 drivers/staging/rtl8712/rtl871x_led.h create mode 100644 drivers/staging/rtl8712/rtl871x_mlme.c create mode 100644 drivers/staging/rtl8712/rtl871x_mlme.h create mode 100644 drivers/staging/rtl8712/rtl871x_mp.c create mode 100644 drivers/staging/rtl8712/rtl871x_mp.h create mode 100644 drivers/staging/rtl8712/rtl871x_mp_ioctl.c create mode 100644 drivers/staging/rtl8712/rtl871x_mp_ioctl.h create mode 100644 drivers/staging/rtl8712/rtl871x_mp_phy_regdef.h create mode 100644 drivers/staging/rtl8712/rtl871x_pwrctrl.c create mode 100644 drivers/staging/rtl8712/rtl871x_pwrctrl.h create mode 100644 drivers/staging/rtl8712/rtl871x_recv.c create mode 100644 drivers/staging/rtl8712/rtl871x_recv.h create mode 100644 drivers/staging/rtl8712/rtl871x_rf.h create mode 100644 drivers/staging/rtl8712/rtl871x_security.c create mode 100644 drivers/staging/rtl8712/rtl871x_security.h create mode 100644 drivers/staging/rtl8712/rtl871x_sta_mgt.c create mode 100644 drivers/staging/rtl8712/rtl871x_wlan_sme.h create mode 100644 drivers/staging/rtl8712/rtl871x_xmit.c create mode 100644 drivers/staging/rtl8712/rtl871x_xmit.h create mode 100644 drivers/staging/rtl8712/sta_info.h create mode 100644 drivers/staging/rtl8712/swab.h create mode 100644 drivers/staging/rtl8712/usb_halinit.c create mode 100644 drivers/staging/rtl8712/usb_intf.c create mode 100644 drivers/staging/rtl8712/usb_ops.c create mode 100644 drivers/staging/rtl8712/usb_ops.h create mode 100644 drivers/staging/rtl8712/usb_ops_linux.c create mode 100644 drivers/staging/rtl8712/usb_osintf.h create mode 100644 drivers/staging/rtl8712/usb_vendor_req.h create mode 100644 drivers/staging/rtl8712/wifi.h create mode 100644 drivers/staging/rtl8712/wlan_bssdef.h create mode 100644 drivers/staging/rtl8712/xmit_linux.c create mode 100644 drivers/staging/rtl8712/xmit_osdep.h create mode 100644 drivers/staging/rts5139/Kconfig create mode 100644 drivers/staging/rts5139/Makefile create mode 100644 drivers/staging/rts5139/TODO create mode 100644 drivers/staging/rts5139/debug.h create mode 100644 drivers/staging/rts5139/ms.c create mode 100644 drivers/staging/rts5139/ms.h create mode 100644 drivers/staging/rts5139/ms_mg.c create mode 100644 drivers/staging/rts5139/ms_mg.h create mode 100644 drivers/staging/rts5139/rts51x.c create mode 100644 drivers/staging/rts5139/rts51x.h create mode 100644 drivers/staging/rts5139/rts51x_card.c create mode 100644 drivers/staging/rts5139/rts51x_card.h create mode 100644 drivers/staging/rts5139/rts51x_chip.c create mode 100644 drivers/staging/rts5139/rts51x_chip.h create mode 100644 drivers/staging/rts5139/rts51x_fop.c create mode 100644 drivers/staging/rts5139/rts51x_fop.h create mode 100644 drivers/staging/rts5139/rts51x_scsi.c create mode 100644 drivers/staging/rts5139/rts51x_scsi.h create mode 100644 drivers/staging/rts5139/rts51x_sys.h create mode 100644 drivers/staging/rts5139/rts51x_transport.c create mode 100644 drivers/staging/rts5139/rts51x_transport.h create mode 100644 drivers/staging/rts5139/sd.c create mode 100644 drivers/staging/rts5139/sd.h create mode 100644 drivers/staging/rts5139/sd_cprm.c create mode 100644 drivers/staging/rts5139/sd_cprm.h create mode 100644 drivers/staging/rts5139/trace.h create mode 100644 drivers/staging/rts5139/xd.c create mode 100644 drivers/staging/rts5139/xd.h create mode 100644 drivers/staging/rts_pstor/Kconfig create mode 100644 drivers/staging/rts_pstor/Makefile create mode 100644 drivers/staging/rts_pstor/TODO create mode 100644 drivers/staging/rts_pstor/debug.h create mode 100644 drivers/staging/rts_pstor/general.c create mode 100644 drivers/staging/rts_pstor/general.h create mode 100644 drivers/staging/rts_pstor/ms.c create mode 100644 drivers/staging/rts_pstor/ms.h create mode 100644 drivers/staging/rts_pstor/rtsx.c create mode 100644 drivers/staging/rts_pstor/rtsx.h create mode 100644 drivers/staging/rts_pstor/rtsx_card.c create mode 100644 drivers/staging/rts_pstor/rtsx_card.h create mode 100644 drivers/staging/rts_pstor/rtsx_chip.c create mode 100644 drivers/staging/rts_pstor/rtsx_chip.h create mode 100644 drivers/staging/rts_pstor/rtsx_scsi.c create mode 100644 drivers/staging/rts_pstor/rtsx_scsi.h create mode 100644 drivers/staging/rts_pstor/rtsx_sys.h create mode 100644 drivers/staging/rts_pstor/rtsx_transport.c create mode 100644 drivers/staging/rts_pstor/rtsx_transport.h create mode 100644 drivers/staging/rts_pstor/sd.c create mode 100644 drivers/staging/rts_pstor/sd.h create mode 100644 drivers/staging/rts_pstor/spi.c create mode 100644 drivers/staging/rts_pstor/spi.h create mode 100644 drivers/staging/rts_pstor/trace.h create mode 100644 drivers/staging/rts_pstor/xd.c create mode 100644 drivers/staging/rts_pstor/xd.h create mode 100644 drivers/staging/sbe-2t3e3/2t3e3.h create mode 100644 drivers/staging/sbe-2t3e3/Kconfig create mode 100644 drivers/staging/sbe-2t3e3/Makefile create mode 100644 drivers/staging/sbe-2t3e3/TODO create mode 100644 drivers/staging/sbe-2t3e3/cpld.c create mode 100644 drivers/staging/sbe-2t3e3/ctrl.c create mode 100644 drivers/staging/sbe-2t3e3/ctrl.h create mode 100644 drivers/staging/sbe-2t3e3/dc.c create mode 100644 drivers/staging/sbe-2t3e3/exar7250.c create mode 100644 drivers/staging/sbe-2t3e3/exar7300.c create mode 100644 drivers/staging/sbe-2t3e3/intr.c create mode 100644 drivers/staging/sbe-2t3e3/io.c create mode 100644 drivers/staging/sbe-2t3e3/main.c create mode 100644 drivers/staging/sbe-2t3e3/maps.c create mode 100644 drivers/staging/sbe-2t3e3/module.c create mode 100644 drivers/staging/sbe-2t3e3/netdev.c create mode 100644 drivers/staging/sep/Kconfig create mode 100644 drivers/staging/sep/Makefile create mode 100644 drivers/staging/sep/TODO create mode 100644 drivers/staging/sep/sep_crypto.c create mode 100644 drivers/staging/sep/sep_crypto.h create mode 100644 drivers/staging/sep/sep_dev.h create mode 100644 drivers/staging/sep/sep_driver_api.h create mode 100644 drivers/staging/sep/sep_driver_config.h create mode 100644 drivers/staging/sep/sep_driver_hw_defs.h create mode 100644 drivers/staging/sep/sep_main.c create mode 100644 drivers/staging/sep/sep_trace_events.h create mode 100644 drivers/staging/serial/68360serial.c create mode 100644 drivers/staging/serial/Kconfig create mode 100644 drivers/staging/serial/Makefile create mode 100644 drivers/staging/serial/TODO create mode 100644 drivers/staging/serqt_usb2/Kconfig create mode 100644 drivers/staging/serqt_usb2/Makefile create mode 100644 drivers/staging/serqt_usb2/serqt_usb2.c create mode 100644 drivers/staging/slicoss/Kconfig create mode 100644 drivers/staging/slicoss/Makefile create mode 100644 drivers/staging/slicoss/README create mode 100644 drivers/staging/slicoss/slic.h create mode 100644 drivers/staging/slicoss/slichw.h create mode 100644 drivers/staging/slicoss/slicoss.c create mode 100644 drivers/staging/sm7xx/Kconfig create mode 100644 drivers/staging/sm7xx/Makefile create mode 100644 drivers/staging/sm7xx/TODO create mode 100644 drivers/staging/sm7xx/smtcfb.c create mode 100644 drivers/staging/sm7xx/smtcfb.h create mode 100644 drivers/staging/speakup/DefaultKeyAssignments create mode 100644 drivers/staging/speakup/Kconfig create mode 100644 drivers/staging/speakup/Makefile create mode 100644 drivers/staging/speakup/TODO create mode 100644 drivers/staging/speakup/buffers.c create mode 100644 drivers/staging/speakup/devsynth.c create mode 100644 drivers/staging/speakup/fakekey.c create mode 100644 drivers/staging/speakup/i18n.c create mode 100644 drivers/staging/speakup/i18n.h create mode 100644 drivers/staging/speakup/keyhelp.c create mode 100644 drivers/staging/speakup/kobjects.c create mode 100644 drivers/staging/speakup/main.c create mode 100644 drivers/staging/speakup/selection.c create mode 100644 drivers/staging/speakup/serialio.c create mode 100644 drivers/staging/speakup/serialio.h create mode 100644 drivers/staging/speakup/speakup.h create mode 100644 drivers/staging/speakup/speakup_acnt.h create mode 100644 drivers/staging/speakup/speakup_acntpc.c create mode 100644 drivers/staging/speakup/speakup_acntsa.c create mode 100644 drivers/staging/speakup/speakup_apollo.c create mode 100644 drivers/staging/speakup/speakup_audptr.c create mode 100644 drivers/staging/speakup/speakup_bns.c create mode 100644 drivers/staging/speakup/speakup_decext.c create mode 100644 drivers/staging/speakup/speakup_decpc.c create mode 100644 drivers/staging/speakup/speakup_dectlk.c create mode 100644 drivers/staging/speakup/speakup_dtlk.c create mode 100644 drivers/staging/speakup/speakup_dtlk.h create mode 100644 drivers/staging/speakup/speakup_dummy.c create mode 100644 drivers/staging/speakup/speakup_keypc.c create mode 100644 drivers/staging/speakup/speakup_ltlk.c create mode 100644 drivers/staging/speakup/speakup_soft.c create mode 100644 drivers/staging/speakup/speakup_spkout.c create mode 100644 drivers/staging/speakup/speakup_txprt.c create mode 100644 drivers/staging/speakup/speakupmap.h create mode 100644 drivers/staging/speakup/speakupmap.map create mode 100644 drivers/staging/speakup/spk_priv.h create mode 100644 drivers/staging/speakup/spk_priv_keyinfo.h create mode 100644 drivers/staging/speakup/spk_types.h create mode 100644 drivers/staging/speakup/spkguide.txt create mode 100644 drivers/staging/speakup/synth.c create mode 100644 drivers/staging/speakup/thread.c create mode 100644 drivers/staging/speakup/varhandlers.c create mode 100644 drivers/staging/staging.c create mode 100644 drivers/staging/ste_rmi4/Kconfig create mode 100644 drivers/staging/ste_rmi4/Makefile create mode 100644 drivers/staging/ste_rmi4/TODO create mode 100644 drivers/staging/ste_rmi4/board-mop500-u8500uib-rmi4.c create mode 100644 drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c create mode 100644 drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h create mode 100644 drivers/staging/telephony/Kconfig create mode 100644 drivers/staging/telephony/Makefile create mode 100644 drivers/staging/telephony/TODO create mode 100644 drivers/staging/telephony/ixj-ver.h create mode 100644 drivers/staging/telephony/ixj.c create mode 100644 drivers/staging/telephony/ixj.h create mode 100644 drivers/staging/telephony/ixj_pcmcia.c create mode 100644 drivers/staging/telephony/phonedev.c create mode 100644 drivers/staging/tidspbridge/Documentation/CONTRIBUTORS create mode 100644 drivers/staging/tidspbridge/Documentation/README create mode 100644 drivers/staging/tidspbridge/Documentation/error-codes create mode 100644 drivers/staging/tidspbridge/Kconfig create mode 100644 drivers/staging/tidspbridge/Makefile create mode 100644 drivers/staging/tidspbridge/TODO create mode 100644 drivers/staging/tidspbridge/core/_cmm.h create mode 100644 drivers/staging/tidspbridge/core/_deh.h create mode 100644 drivers/staging/tidspbridge/core/_msg_sm.h create mode 100644 drivers/staging/tidspbridge/core/_tiomap.h create mode 100644 drivers/staging/tidspbridge/core/_tiomap_pwr.h create mode 100644 drivers/staging/tidspbridge/core/chnl_sm.c create mode 100644 drivers/staging/tidspbridge/core/dsp-clock.c create mode 100644 drivers/staging/tidspbridge/core/io_sm.c create mode 100644 drivers/staging/tidspbridge/core/msg_sm.c create mode 100644 drivers/staging/tidspbridge/core/sync.c create mode 100644 drivers/staging/tidspbridge/core/tiomap3430.c create mode 100644 drivers/staging/tidspbridge/core/tiomap3430_pwr.c create mode 100644 drivers/staging/tidspbridge/core/tiomap_io.c create mode 100644 drivers/staging/tidspbridge/core/tiomap_io.h create mode 100644 drivers/staging/tidspbridge/core/ue_deh.c create mode 100644 drivers/staging/tidspbridge/core/wdt.c create mode 100644 drivers/staging/tidspbridge/dynload/cload.c create mode 100644 drivers/staging/tidspbridge/dynload/dload_internal.h create mode 100644 drivers/staging/tidspbridge/dynload/doff.h create mode 100644 drivers/staging/tidspbridge/dynload/getsection.c create mode 100644 drivers/staging/tidspbridge/dynload/header.h create mode 100644 drivers/staging/tidspbridge/dynload/module_list.h create mode 100644 drivers/staging/tidspbridge/dynload/params.h create mode 100644 drivers/staging/tidspbridge/dynload/reloc.c create mode 100644 drivers/staging/tidspbridge/dynload/reloc_table.h create mode 100644 drivers/staging/tidspbridge/dynload/reloc_table_c6000.c create mode 100644 drivers/staging/tidspbridge/dynload/tramp.c create mode 100644 drivers/staging/tidspbridge/dynload/tramp_table_c6000.c create mode 100644 drivers/staging/tidspbridge/gen/gh.c create mode 100644 drivers/staging/tidspbridge/gen/uuidutil.c create mode 100644 drivers/staging/tidspbridge/hw/EasiGlobal.h create mode 100644 drivers/staging/tidspbridge/hw/MMUAccInt.h create mode 100644 drivers/staging/tidspbridge/hw/MMURegAcM.h create mode 100644 drivers/staging/tidspbridge/hw/hw_defs.h create mode 100644 drivers/staging/tidspbridge/hw/hw_mmu.c create mode 100644 drivers/staging/tidspbridge/hw/hw_mmu.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/_chnl_sm.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/brddefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/cfgdefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/chnl.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/chnldefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/chnlpriv.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/clk.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/cmm.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/cmmdefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/cod.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dbdcd.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dbdcddef.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dbdefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dbll.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dblldefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dev.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/devdefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/disp.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dmm.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/drv.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dspapi-ioctl.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dspapi.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dspchnl.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dspdefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dspdeh.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dspdrv.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dspio.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dspioctl.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dspmsg.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/dynamic_loader.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/getsection.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/gh.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/host_os.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/io.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/io_sm.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/mbx_sh.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/memdefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/mgr.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/mgrpriv.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/msg.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/msgdefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/nldr.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/nldrdefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/node.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/nodedefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/nodepriv.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/ntfy.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/proc.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/procpriv.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/pwr.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/resourcecleanup.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/rmm.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/rms_sh.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/rmstypes.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/strm.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/strmdefs.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/sync.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/uuidutil.h create mode 100644 drivers/staging/tidspbridge/include/dspbridge/wdt.h create mode 100644 drivers/staging/tidspbridge/pmgr/chnl.c create mode 100644 drivers/staging/tidspbridge/pmgr/chnlobj.h create mode 100644 drivers/staging/tidspbridge/pmgr/cmm.c create mode 100644 drivers/staging/tidspbridge/pmgr/cod.c create mode 100644 drivers/staging/tidspbridge/pmgr/dbll.c create mode 100644 drivers/staging/tidspbridge/pmgr/dev.c create mode 100644 drivers/staging/tidspbridge/pmgr/dmm.c create mode 100644 drivers/staging/tidspbridge/pmgr/dspapi.c create mode 100644 drivers/staging/tidspbridge/pmgr/io.c create mode 100644 drivers/staging/tidspbridge/pmgr/ioobj.h create mode 100644 drivers/staging/tidspbridge/pmgr/msg.c create mode 100644 drivers/staging/tidspbridge/pmgr/msgobj.h create mode 100644 drivers/staging/tidspbridge/rmgr/dbdcd.c create mode 100644 drivers/staging/tidspbridge/rmgr/disp.c create mode 100644 drivers/staging/tidspbridge/rmgr/drv.c create mode 100644 drivers/staging/tidspbridge/rmgr/drv_interface.c create mode 100644 drivers/staging/tidspbridge/rmgr/dspdrv.c create mode 100644 drivers/staging/tidspbridge/rmgr/mgr.c create mode 100644 drivers/staging/tidspbridge/rmgr/nldr.c create mode 100644 drivers/staging/tidspbridge/rmgr/node.c create mode 100644 drivers/staging/tidspbridge/rmgr/proc.c create mode 100644 drivers/staging/tidspbridge/rmgr/pwr.c create mode 100644 drivers/staging/tidspbridge/rmgr/rmm.c create mode 100644 drivers/staging/tidspbridge/rmgr/strm.c create mode 100644 drivers/staging/usbip/Kconfig create mode 100644 drivers/staging/usbip/Makefile create mode 100644 drivers/staging/usbip/README create mode 100644 drivers/staging/usbip/stub.h create mode 100644 drivers/staging/usbip/stub_dev.c create mode 100644 drivers/staging/usbip/stub_main.c create mode 100644 drivers/staging/usbip/stub_rx.c create mode 100644 drivers/staging/usbip/stub_tx.c create mode 100644 drivers/staging/usbip/usbip_common.c create mode 100644 drivers/staging/usbip/usbip_common.h create mode 100644 drivers/staging/usbip/usbip_event.c create mode 100644 drivers/staging/usbip/usbip_protocol.txt create mode 100644 drivers/staging/usbip/userspace/AUTHORS create mode 100644 drivers/staging/usbip/userspace/COPYING create mode 100644 drivers/staging/usbip/userspace/INSTALL create mode 100644 drivers/staging/usbip/userspace/Makefile.am create mode 100644 drivers/staging/usbip/userspace/README create mode 100755 drivers/staging/usbip/userspace/autogen.sh create mode 100755 drivers/staging/usbip/userspace/cleanup.sh create mode 100644 drivers/staging/usbip/userspace/configure.ac create mode 100644 drivers/staging/usbip/userspace/doc/usbip.8 create mode 100644 drivers/staging/usbip/userspace/doc/usbip_bind_driver.8 create mode 100644 drivers/staging/usbip/userspace/doc/usbipd.8 create mode 100644 drivers/staging/usbip/userspace/libsrc/Makefile.am create mode 100644 drivers/staging/usbip/userspace/libsrc/names.c create mode 100644 drivers/staging/usbip/userspace/libsrc/names.h create mode 100644 drivers/staging/usbip/userspace/libsrc/usbip_common.c create mode 100644 drivers/staging/usbip/userspace/libsrc/usbip_common.h create mode 100644 drivers/staging/usbip/userspace/libsrc/usbip_host_driver.c create mode 100644 drivers/staging/usbip/userspace/libsrc/usbip_host_driver.h create mode 100644 drivers/staging/usbip/userspace/libsrc/vhci_driver.c create mode 100644 drivers/staging/usbip/userspace/libsrc/vhci_driver.h create mode 100644 drivers/staging/usbip/userspace/src/Makefile.am create mode 100644 drivers/staging/usbip/userspace/src/usbip.c create mode 100644 drivers/staging/usbip/userspace/src/usbip.h create mode 100644 drivers/staging/usbip/userspace/src/usbip_attach.c create mode 100644 drivers/staging/usbip/userspace/src/usbip_bind.c create mode 100644 drivers/staging/usbip/userspace/src/usbip_detach.c create mode 100644 drivers/staging/usbip/userspace/src/usbip_list.c create mode 100644 drivers/staging/usbip/userspace/src/usbip_network.c create mode 100644 drivers/staging/usbip/userspace/src/usbip_network.h create mode 100644 drivers/staging/usbip/userspace/src/usbip_unbind.c create mode 100644 drivers/staging/usbip/userspace/src/usbipd.c create mode 100644 drivers/staging/usbip/userspace/src/utils.c create mode 100644 drivers/staging/usbip/userspace/src/utils.h create mode 100644 drivers/staging/usbip/vhci.h create mode 100644 drivers/staging/usbip/vhci_hcd.c create mode 100644 drivers/staging/usbip/vhci_rx.c create mode 100644 drivers/staging/usbip/vhci_sysfs.c create mode 100644 drivers/staging/usbip/vhci_tx.c create mode 100644 drivers/staging/vme/Kconfig create mode 100644 drivers/staging/vme/Makefile create mode 100644 drivers/staging/vme/TODO create mode 100644 drivers/staging/vme/boards/Kconfig create mode 100644 drivers/staging/vme/boards/Makefile create mode 100644 drivers/staging/vme/boards/vme_vmivme7805.c create mode 100644 drivers/staging/vme/boards/vme_vmivme7805.h create mode 100644 drivers/staging/vme/bridges/Kconfig create mode 100644 drivers/staging/vme/bridges/Makefile create mode 100644 drivers/staging/vme/bridges/vme_ca91cx42.c create mode 100644 drivers/staging/vme/bridges/vme_ca91cx42.h create mode 100644 drivers/staging/vme/bridges/vme_tsi148.c create mode 100644 drivers/staging/vme/bridges/vme_tsi148.h create mode 100644 drivers/staging/vme/devices/Kconfig create mode 100644 drivers/staging/vme/devices/Makefile create mode 100644 drivers/staging/vme/devices/vme_pio2.h create mode 100644 drivers/staging/vme/devices/vme_pio2_cntr.c create mode 100644 drivers/staging/vme/devices/vme_pio2_core.c create mode 100644 drivers/staging/vme/devices/vme_pio2_gpio.c create mode 100644 drivers/staging/vme/devices/vme_user.c create mode 100644 drivers/staging/vme/devices/vme_user.h create mode 100644 drivers/staging/vme/vme.c create mode 100644 drivers/staging/vme/vme.h create mode 100644 drivers/staging/vme/vme_api.txt create mode 100644 drivers/staging/vme/vme_bridge.h create mode 100644 drivers/staging/vt6655/80211hdr.h create mode 100644 drivers/staging/vt6655/80211mgr.c create mode 100644 drivers/staging/vt6655/80211mgr.h create mode 100644 drivers/staging/vt6655/IEEE11h.c create mode 100644 drivers/staging/vt6655/IEEE11h.h create mode 100644 drivers/staging/vt6655/Kconfig create mode 100644 drivers/staging/vt6655/Makefile create mode 100644 drivers/staging/vt6655/TODO create mode 100644 drivers/staging/vt6655/aes_ccmp.c create mode 100644 drivers/staging/vt6655/aes_ccmp.h create mode 100644 drivers/staging/vt6655/baseband.c create mode 100644 drivers/staging/vt6655/baseband.h create mode 100644 drivers/staging/vt6655/bssdb.c create mode 100644 drivers/staging/vt6655/bssdb.h create mode 100644 drivers/staging/vt6655/card.c create mode 100644 drivers/staging/vt6655/card.h create mode 100644 drivers/staging/vt6655/channel.c create mode 100644 drivers/staging/vt6655/channel.h create mode 100644 drivers/staging/vt6655/country.h create mode 100644 drivers/staging/vt6655/datarate.c create mode 100644 drivers/staging/vt6655/datarate.h create mode 100644 drivers/staging/vt6655/desc.h create mode 100644 drivers/staging/vt6655/device.h create mode 100644 drivers/staging/vt6655/device_cfg.h create mode 100644 drivers/staging/vt6655/device_main.c create mode 100644 drivers/staging/vt6655/dpc.c create mode 100644 drivers/staging/vt6655/dpc.h create mode 100644 drivers/staging/vt6655/hostap.c create mode 100644 drivers/staging/vt6655/hostap.h create mode 100644 drivers/staging/vt6655/iocmd.h create mode 100644 drivers/staging/vt6655/ioctl.c create mode 100644 drivers/staging/vt6655/ioctl.h create mode 100644 drivers/staging/vt6655/iowpa.h create mode 100644 drivers/staging/vt6655/iwctl.c create mode 100644 drivers/staging/vt6655/iwctl.h create mode 100644 drivers/staging/vt6655/key.c create mode 100644 drivers/staging/vt6655/key.h create mode 100644 drivers/staging/vt6655/mac.c create mode 100644 drivers/staging/vt6655/mac.h create mode 100644 drivers/staging/vt6655/mib.c create mode 100644 drivers/staging/vt6655/mib.h create mode 100644 drivers/staging/vt6655/michael.c create mode 100644 drivers/staging/vt6655/michael.h create mode 100644 drivers/staging/vt6655/power.c create mode 100644 drivers/staging/vt6655/power.h create mode 100644 drivers/staging/vt6655/rc4.c create mode 100644 drivers/staging/vt6655/rc4.h create mode 100644 drivers/staging/vt6655/rf.c create mode 100644 drivers/staging/vt6655/rf.h create mode 100644 drivers/staging/vt6655/rxtx.c create mode 100644 drivers/staging/vt6655/rxtx.h create mode 100644 drivers/staging/vt6655/srom.c create mode 100644 drivers/staging/vt6655/srom.h create mode 100644 drivers/staging/vt6655/tcrc.c create mode 100644 drivers/staging/vt6655/tcrc.h create mode 100644 drivers/staging/vt6655/test create mode 100644 drivers/staging/vt6655/tether.c create mode 100644 drivers/staging/vt6655/tether.h create mode 100644 drivers/staging/vt6655/tkip.c create mode 100644 drivers/staging/vt6655/tkip.h create mode 100644 drivers/staging/vt6655/tmacro.h create mode 100644 drivers/staging/vt6655/ttype.h create mode 100644 drivers/staging/vt6655/upc.h create mode 100644 drivers/staging/vt6655/vntconfiguration.dat create mode 100644 drivers/staging/vt6655/vntwifi.c create mode 100644 drivers/staging/vt6655/vntwifi.h create mode 100644 drivers/staging/vt6655/wcmd.c create mode 100644 drivers/staging/vt6655/wcmd.h create mode 100644 drivers/staging/vt6655/wctl.c create mode 100644 drivers/staging/vt6655/wctl.h create mode 100644 drivers/staging/vt6655/wmgr.c create mode 100644 drivers/staging/vt6655/wmgr.h create mode 100644 drivers/staging/vt6655/wpa.c create mode 100644 drivers/staging/vt6655/wpa.h create mode 100644 drivers/staging/vt6655/wpa2.c create mode 100644 drivers/staging/vt6655/wpa2.h create mode 100644 drivers/staging/vt6655/wpactl.c create mode 100644 drivers/staging/vt6655/wpactl.h create mode 100644 drivers/staging/vt6655/wroute.c create mode 100644 drivers/staging/vt6655/wroute.h create mode 100644 drivers/staging/vt6656/80211hdr.h create mode 100644 drivers/staging/vt6656/80211mgr.c create mode 100644 drivers/staging/vt6656/80211mgr.h create mode 100644 drivers/staging/vt6656/Kconfig create mode 100644 drivers/staging/vt6656/Makefile create mode 100644 drivers/staging/vt6656/TODO create mode 100644 drivers/staging/vt6656/aes_ccmp.c create mode 100644 drivers/staging/vt6656/aes_ccmp.h create mode 100644 drivers/staging/vt6656/baseband.c create mode 100644 drivers/staging/vt6656/baseband.h create mode 100644 drivers/staging/vt6656/bssdb.c create mode 100644 drivers/staging/vt6656/bssdb.h create mode 100644 drivers/staging/vt6656/card.c create mode 100644 drivers/staging/vt6656/card.h create mode 100644 drivers/staging/vt6656/channel.c create mode 100644 drivers/staging/vt6656/channel.h create mode 100644 drivers/staging/vt6656/control.c create mode 100644 drivers/staging/vt6656/control.h create mode 100644 drivers/staging/vt6656/country.h create mode 100644 drivers/staging/vt6656/datarate.c create mode 100644 drivers/staging/vt6656/datarate.h create mode 100644 drivers/staging/vt6656/desc.h create mode 100644 drivers/staging/vt6656/device.h create mode 100644 drivers/staging/vt6656/device_cfg.h create mode 100644 drivers/staging/vt6656/dpc.c create mode 100644 drivers/staging/vt6656/dpc.h create mode 100644 drivers/staging/vt6656/firmware.c create mode 100644 drivers/staging/vt6656/firmware.h create mode 100644 drivers/staging/vt6656/hostap.c create mode 100644 drivers/staging/vt6656/hostap.h create mode 100644 drivers/staging/vt6656/int.c create mode 100644 drivers/staging/vt6656/int.h create mode 100644 drivers/staging/vt6656/iocmd.h create mode 100644 drivers/staging/vt6656/ioctl.c create mode 100644 drivers/staging/vt6656/ioctl.h create mode 100644 drivers/staging/vt6656/iowpa.h create mode 100644 drivers/staging/vt6656/iwctl.c create mode 100644 drivers/staging/vt6656/iwctl.h create mode 100644 drivers/staging/vt6656/key.c create mode 100644 drivers/staging/vt6656/key.h create mode 100644 drivers/staging/vt6656/mac.c create mode 100644 drivers/staging/vt6656/mac.h create mode 100644 drivers/staging/vt6656/main_usb.c create mode 100644 drivers/staging/vt6656/mib.c create mode 100644 drivers/staging/vt6656/mib.h create mode 100644 drivers/staging/vt6656/michael.c create mode 100644 drivers/staging/vt6656/michael.h create mode 100644 drivers/staging/vt6656/power.c create mode 100644 drivers/staging/vt6656/power.h create mode 100644 drivers/staging/vt6656/rc4.c create mode 100644 drivers/staging/vt6656/rc4.h create mode 100644 drivers/staging/vt6656/rf.c create mode 100644 drivers/staging/vt6656/rf.h create mode 100644 drivers/staging/vt6656/rndis.h create mode 100644 drivers/staging/vt6656/rxtx.c create mode 100644 drivers/staging/vt6656/rxtx.h create mode 100644 drivers/staging/vt6656/srom.h create mode 100644 drivers/staging/vt6656/tcrc.c create mode 100644 drivers/staging/vt6656/tcrc.h create mode 100644 drivers/staging/vt6656/tether.c create mode 100644 drivers/staging/vt6656/tether.h create mode 100644 drivers/staging/vt6656/tkip.c create mode 100644 drivers/staging/vt6656/tkip.h create mode 100644 drivers/staging/vt6656/tmacro.h create mode 100644 drivers/staging/vt6656/ttype.h create mode 100644 drivers/staging/vt6656/upc.h create mode 100644 drivers/staging/vt6656/usbpipe.c create mode 100644 drivers/staging/vt6656/usbpipe.h create mode 100644 drivers/staging/vt6656/vntconfiguration.dat create mode 100644 drivers/staging/vt6656/wcmd.c create mode 100644 drivers/staging/vt6656/wcmd.h create mode 100644 drivers/staging/vt6656/wctl.c create mode 100644 drivers/staging/vt6656/wctl.h create mode 100644 drivers/staging/vt6656/wmgr.c create mode 100644 drivers/staging/vt6656/wmgr.h create mode 100644 drivers/staging/vt6656/wpa.c create mode 100644 drivers/staging/vt6656/wpa.h create mode 100644 drivers/staging/vt6656/wpa2.c create mode 100644 drivers/staging/vt6656/wpa2.h create mode 100644 drivers/staging/vt6656/wpactl.c create mode 100644 drivers/staging/vt6656/wpactl.h create mode 100644 drivers/staging/winbond/Kconfig create mode 100644 drivers/staging/winbond/Makefile create mode 100644 drivers/staging/winbond/TODO create mode 100644 drivers/staging/winbond/core.h create mode 100644 drivers/staging/winbond/localpara.h create mode 100644 drivers/staging/winbond/mac_structures.h create mode 100644 drivers/staging/winbond/mds.c create mode 100644 drivers/staging/winbond/mds_f.h create mode 100644 drivers/staging/winbond/mds_s.h create mode 100644 drivers/staging/winbond/mto.c create mode 100644 drivers/staging/winbond/mto.h create mode 100644 drivers/staging/winbond/phy_calibration.c create mode 100644 drivers/staging/winbond/phy_calibration.h create mode 100644 drivers/staging/winbond/reg.c create mode 100644 drivers/staging/winbond/sme_api.h create mode 100644 drivers/staging/winbond/wb35reg.c create mode 100644 drivers/staging/winbond/wb35reg_f.h create mode 100644 drivers/staging/winbond/wb35reg_s.h create mode 100644 drivers/staging/winbond/wb35rx.c create mode 100644 drivers/staging/winbond/wb35rx_f.h create mode 100644 drivers/staging/winbond/wb35rx_s.h create mode 100644 drivers/staging/winbond/wb35tx.c create mode 100644 drivers/staging/winbond/wb35tx_f.h create mode 100644 drivers/staging/winbond/wb35tx_s.h create mode 100644 drivers/staging/winbond/wbhal.h create mode 100644 drivers/staging/winbond/wbusb.c create mode 100644 drivers/staging/wlags49_h2/Kconfig create mode 100644 drivers/staging/wlags49_h2/Makefile create mode 100644 drivers/staging/wlags49_h2/README.ubuntu create mode 100644 drivers/staging/wlags49_h2/README.wlags49 create mode 100644 drivers/staging/wlags49_h2/TODO create mode 100644 drivers/staging/wlags49_h2/WARNING.txt create mode 100644 drivers/staging/wlags49_h2/ap_h2.c create mode 100644 drivers/staging/wlags49_h2/ap_h25.c create mode 100644 drivers/staging/wlags49_h2/debug.h create mode 100644 drivers/staging/wlags49_h2/dhf.c create mode 100644 drivers/staging/wlags49_h2/dhf.h create mode 100644 drivers/staging/wlags49_h2/dhfcfg.h create mode 100644 drivers/staging/wlags49_h2/hcf.c create mode 100644 drivers/staging/wlags49_h2/hcf.h create mode 100644 drivers/staging/wlags49_h2/hcfcfg.h create mode 100644 drivers/staging/wlags49_h2/hcfdef.h create mode 100644 drivers/staging/wlags49_h2/man/wlags49.4 create mode 100644 drivers/staging/wlags49_h2/mdd.h create mode 100644 drivers/staging/wlags49_h2/mmd.c create mode 100644 drivers/staging/wlags49_h2/mmd.h create mode 100644 drivers/staging/wlags49_h2/sta_h2.c create mode 100644 drivers/staging/wlags49_h2/sta_h25.c create mode 100644 drivers/staging/wlags49_h2/wl_cs.c create mode 100644 drivers/staging/wlags49_h2/wl_cs.h create mode 100644 drivers/staging/wlags49_h2/wl_enc.c create mode 100644 drivers/staging/wlags49_h2/wl_enc.h create mode 100644 drivers/staging/wlags49_h2/wl_if.h create mode 100644 drivers/staging/wlags49_h2/wl_internal.h create mode 100644 drivers/staging/wlags49_h2/wl_main.c create mode 100644 drivers/staging/wlags49_h2/wl_main.h create mode 100644 drivers/staging/wlags49_h2/wl_netdev.c create mode 100644 drivers/staging/wlags49_h2/wl_netdev.h create mode 100644 drivers/staging/wlags49_h2/wl_pci.c create mode 100644 drivers/staging/wlags49_h2/wl_pci.h create mode 100644 drivers/staging/wlags49_h2/wl_priv.c create mode 100644 drivers/staging/wlags49_h2/wl_priv.h create mode 100644 drivers/staging/wlags49_h2/wl_profile.c create mode 100644 drivers/staging/wlags49_h2/wl_profile.h create mode 100644 drivers/staging/wlags49_h2/wl_sysfs.c create mode 100644 drivers/staging/wlags49_h2/wl_sysfs.h create mode 100644 drivers/staging/wlags49_h2/wl_util.c create mode 100644 drivers/staging/wlags49_h2/wl_util.h create mode 100644 drivers/staging/wlags49_h2/wl_version.h create mode 100644 drivers/staging/wlags49_h2/wl_wext.c create mode 100644 drivers/staging/wlags49_h2/wl_wext.h create mode 100644 drivers/staging/wlags49_h25/Kconfig create mode 100644 drivers/staging/wlags49_h25/Makefile create mode 100644 drivers/staging/wlags49_h25/README.txt create mode 100644 drivers/staging/wlags49_h25/TODO create mode 100644 drivers/staging/wlags49_h25/ap_h25.c create mode 100644 drivers/staging/wlags49_h25/debug.h create mode 100644 drivers/staging/wlags49_h25/dhf.c create mode 100644 drivers/staging/wlags49_h25/dhf.h create mode 100644 drivers/staging/wlags49_h25/dhfcfg.h create mode 100644 drivers/staging/wlags49_h25/hcf.c create mode 100644 drivers/staging/wlags49_h25/hcf.h create mode 100644 drivers/staging/wlags49_h25/hcfcfg.h create mode 100644 drivers/staging/wlags49_h25/hcfdef.h create mode 100644 drivers/staging/wlags49_h25/mdd.h create mode 100644 drivers/staging/wlags49_h25/mmd.c create mode 100644 drivers/staging/wlags49_h25/mmd.h create mode 100644 drivers/staging/wlags49_h25/sta_h25.c create mode 100644 drivers/staging/wlags49_h25/wl_cs.c create mode 100644 drivers/staging/wlags49_h25/wl_cs.h create mode 100644 drivers/staging/wlags49_h25/wl_enc.c create mode 100644 drivers/staging/wlags49_h25/wl_enc.h create mode 100644 drivers/staging/wlags49_h25/wl_if.h create mode 100644 drivers/staging/wlags49_h25/wl_internal.h create mode 100644 drivers/staging/wlags49_h25/wl_main.c create mode 100644 drivers/staging/wlags49_h25/wl_main.h create mode 100644 drivers/staging/wlags49_h25/wl_netdev.c create mode 100644 drivers/staging/wlags49_h25/wl_netdev.h create mode 100644 drivers/staging/wlags49_h25/wl_priv.c create mode 100644 drivers/staging/wlags49_h25/wl_priv.h create mode 100644 drivers/staging/wlags49_h25/wl_profile.c create mode 100644 drivers/staging/wlags49_h25/wl_profile.h create mode 100644 drivers/staging/wlags49_h25/wl_sysfs.c create mode 100644 drivers/staging/wlags49_h25/wl_sysfs.h create mode 100644 drivers/staging/wlags49_h25/wl_util.c create mode 100644 drivers/staging/wlags49_h25/wl_util.h create mode 100644 drivers/staging/wlags49_h25/wl_version.h create mode 100644 drivers/staging/wlags49_h25/wl_wext.c create mode 100644 drivers/staging/wlags49_h25/wl_wext.h create mode 100644 drivers/staging/wlan-ng/Kconfig create mode 100644 drivers/staging/wlan-ng/Makefile create mode 100644 drivers/staging/wlan-ng/README create mode 100644 drivers/staging/wlan-ng/cfg80211.c create mode 100644 drivers/staging/wlan-ng/hfa384x.h create mode 100644 drivers/staging/wlan-ng/hfa384x_usb.c create mode 100644 drivers/staging/wlan-ng/p80211conv.c create mode 100644 drivers/staging/wlan-ng/p80211conv.h create mode 100644 drivers/staging/wlan-ng/p80211hdr.h create mode 100644 drivers/staging/wlan-ng/p80211ioctl.h create mode 100644 drivers/staging/wlan-ng/p80211meta.h create mode 100644 drivers/staging/wlan-ng/p80211metadef.h create mode 100644 drivers/staging/wlan-ng/p80211metastruct.h create mode 100644 drivers/staging/wlan-ng/p80211mgmt.h create mode 100644 drivers/staging/wlan-ng/p80211msg.h create mode 100644 drivers/staging/wlan-ng/p80211netdev.c create mode 100644 drivers/staging/wlan-ng/p80211netdev.h create mode 100644 drivers/staging/wlan-ng/p80211req.c create mode 100644 drivers/staging/wlan-ng/p80211req.h create mode 100644 drivers/staging/wlan-ng/p80211types.h create mode 100644 drivers/staging/wlan-ng/p80211wep.c create mode 100644 drivers/staging/wlan-ng/prism2fw.c create mode 100644 drivers/staging/wlan-ng/prism2mgmt.c create mode 100644 drivers/staging/wlan-ng/prism2mgmt.h create mode 100644 drivers/staging/wlan-ng/prism2mib.c create mode 100644 drivers/staging/wlan-ng/prism2sta.c create mode 100644 drivers/staging/wlan-ng/prism2usb.c create mode 100644 drivers/staging/xgifb/Kconfig create mode 100644 drivers/staging/xgifb/Makefile create mode 100644 drivers/staging/xgifb/TODO create mode 100644 drivers/staging/xgifb/XGI_main.h create mode 100644 drivers/staging/xgifb/XGI_main_26.c create mode 100644 drivers/staging/xgifb/XGIfb.h create mode 100644 drivers/staging/xgifb/vb_def.h create mode 100644 drivers/staging/xgifb/vb_init.c create mode 100644 drivers/staging/xgifb/vb_init.h create mode 100644 drivers/staging/xgifb/vb_setmode.c create mode 100644 drivers/staging/xgifb/vb_setmode.h create mode 100644 drivers/staging/xgifb/vb_struct.h create mode 100644 drivers/staging/xgifb/vb_table.h create mode 100644 drivers/staging/xgifb/vb_util.c create mode 100644 drivers/staging/xgifb/vb_util.h create mode 100644 drivers/staging/xgifb/vgatypes.h create mode 100644 drivers/staging/zcache/Kconfig create mode 100644 drivers/staging/zcache/Makefile create mode 100755 drivers/staging/zcache/TODO create mode 100755 drivers/staging/zcache/ramster.h create mode 100755 drivers/staging/zcache/ramster/heartbeat.c create mode 100755 drivers/staging/zcache/ramster/heartbeat.h create mode 100755 drivers/staging/zcache/ramster/masklog.c create mode 100755 drivers/staging/zcache/ramster/masklog.h create mode 100755 drivers/staging/zcache/ramster/nodemanager.c create mode 100755 drivers/staging/zcache/ramster/nodemanager.h create mode 100755 drivers/staging/zcache/ramster/r2net.c create mode 100755 drivers/staging/zcache/ramster/ramster.c create mode 100755 drivers/staging/zcache/ramster/ramster.h create mode 100755 drivers/staging/zcache/ramster/ramster_nodemanager.h create mode 100755 drivers/staging/zcache/ramster/tcp.c create mode 100755 drivers/staging/zcache/ramster/tcp.h create mode 100755 drivers/staging/zcache/ramster/tcp_internal.h create mode 100644 drivers/staging/zcache/tmem.c create mode 100644 drivers/staging/zcache/tmem.h create mode 100755 drivers/staging/zcache/zbud.c create mode 100755 drivers/staging/zcache/zbud.h create mode 100644 drivers/staging/zcache/zcache-main.c create mode 100755 drivers/staging/zcache/zcache.h create mode 100644 drivers/staging/zram/Kconfig create mode 100644 drivers/staging/zram/Makefile create mode 100644 drivers/staging/zram/zram.txt create mode 100644 drivers/staging/zram/zram_drv.c create mode 100644 drivers/staging/zram/zram_drv.h create mode 100644 drivers/staging/zram/zram_sysfs.c create mode 100644 drivers/staging/zsmalloc/Kconfig create mode 100644 drivers/staging/zsmalloc/Makefile create mode 100644 drivers/staging/zsmalloc/zsmalloc-main.c create mode 100644 drivers/staging/zsmalloc/zsmalloc.h create mode 100644 drivers/staging/zsmalloc/zsmalloc_int.h (limited to 'drivers/staging') diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig new file mode 100644 index 00000000..97d412d9 --- /dev/null +++ b/drivers/staging/Kconfig @@ -0,0 +1,135 @@ +menuconfig STAGING + bool "Staging drivers" + default n + ---help--- + This option allows you to select a number of drivers that are + not of the "normal" Linux kernel quality level. These drivers + are placed here in order to get a wider audience to make use of + them. Please note that these drivers are under heavy + development, may or may not work, and may contain userspace + interfaces that most likely will be changed in the near + future. + + Using any of these drivers will taint your kernel which might + affect support options from both the community, and various + commercial support organizations. + + If you wish to work on these drivers, to help improve them, or + to report problems you have with them, please see the + driver_name.README file in the drivers/staging/ directory to + see what needs to be worked on, and who to contact. + + If in doubt, say N here. + + +if STAGING + +source "drivers/staging/serial/Kconfig" + +source "drivers/staging/et131x/Kconfig" + +source "drivers/staging/slicoss/Kconfig" + +source "drivers/staging/usbip/Kconfig" + +source "drivers/staging/winbond/Kconfig" + +source "drivers/staging/wlan-ng/Kconfig" + +source "drivers/staging/echo/Kconfig" + +source "drivers/staging/comedi/Kconfig" + +source "drivers/staging/olpc_dcon/Kconfig" + +source "drivers/staging/asus_oled/Kconfig" + +source "drivers/staging/panel/Kconfig" + +source "drivers/staging/rtl8187se/Kconfig" + +source "drivers/staging/rtl8192u/Kconfig" + +source "drivers/staging/rtl8192e/Kconfig" + +source "drivers/staging/rtl8712/Kconfig" + +source "drivers/staging/rts_pstor/Kconfig" + +source "drivers/staging/rts5139/Kconfig" + +source "drivers/staging/frontier/Kconfig" + +source "drivers/staging/phison/Kconfig" + +source "drivers/staging/line6/Kconfig" + +source "drivers/staging/octeon/Kconfig" + +source "drivers/staging/serqt_usb2/Kconfig" + +source "drivers/staging/quatech_usb2/Kconfig" + +source "drivers/staging/vt6655/Kconfig" + +source "drivers/staging/vt6656/Kconfig" + +source "drivers/staging/vme/Kconfig" + +source "drivers/staging/sep/Kconfig" + +source "drivers/staging/iio/Kconfig" + +source "drivers/staging/zram/Kconfig" + +source "drivers/staging/zcache/Kconfig" + +source "drivers/staging/zsmalloc/Kconfig" + +source "drivers/staging/wlags49_h2/Kconfig" + +source "drivers/staging/wlags49_h25/Kconfig" + +source "drivers/staging/sm7xx/Kconfig" + +source "drivers/staging/crystalhd/Kconfig" + +source "drivers/staging/cxt1e1/Kconfig" + +source "drivers/staging/xgifb/Kconfig" + +source "drivers/staging/tidspbridge/Kconfig" + +source "drivers/staging/quickstart/Kconfig" + +source "drivers/staging/sbe-2t3e3/Kconfig" + +source "drivers/staging/keucr/Kconfig" + +source "drivers/staging/bcm/Kconfig" + +source "drivers/staging/ft1000/Kconfig" + +source "drivers/staging/speakup/Kconfig" + +source "drivers/staging/cptm1217/Kconfig" + +source "drivers/staging/ste_rmi4/Kconfig" + +source "drivers/staging/mei/Kconfig" + +source "drivers/staging/nvec/Kconfig" + +source "drivers/staging/media/Kconfig" + +source "drivers/staging/omapdrm/Kconfig" + +source "drivers/staging/android/Kconfig" + +source "drivers/staging/telephony/Kconfig" + +source "drivers/staging/ramster/Kconfig" + +source "drivers/staging/ozwpan/Kconfig" + +endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile new file mode 100644 index 00000000..ffe7d443 --- /dev/null +++ b/drivers/staging/Makefile @@ -0,0 +1,59 @@ +# Makefile for staging directory + +# fix for build system bug... +obj-$(CONFIG_STAGING) += staging.o + +obj-y += serial/ +obj-y += media/ +obj-$(CONFIG_ET131X) += et131x/ +obj-$(CONFIG_SLICOSS) += slicoss/ +obj-$(CONFIG_USBIP_CORE) += usbip/ +obj-$(CONFIG_W35UND) += winbond/ +obj-$(CONFIG_PRISM2_USB) += wlan-ng/ +obj-$(CONFIG_ECHO) += echo/ +obj-$(CONFIG_COMEDI) += comedi/ +obj-$(CONFIG_FB_OLPC_DCON) += olpc_dcon/ +obj-$(CONFIG_ASUS_OLED) += asus_oled/ +obj-$(CONFIG_PANEL) += panel/ +obj-$(CONFIG_R8187SE) += rtl8187se/ +obj-$(CONFIG_RTL8192U) += rtl8192u/ +obj-$(CONFIG_RTL8192E) += rtl8192e/ +obj-$(CONFIG_R8712U) += rtl8712/ +obj-$(CONFIG_RTS_PSTOR) += rts_pstor/ +obj-$(CONFIG_RTS5139) += rts5139/ +obj-$(CONFIG_TRANZPORT) += frontier/ +obj-$(CONFIG_IDE_PHISON) += phison/ +obj-$(CONFIG_LINE6_USB) += line6/ +obj-$(CONFIG_USB_SERIAL_QUATECH2) += serqt_usb2/ +obj-$(CONFIG_USB_SERIAL_QUATECH_USB2) += quatech_usb2/ +obj-$(CONFIG_OCTEON_ETHERNET) += octeon/ +obj-$(CONFIG_VT6655) += vt6655/ +obj-$(CONFIG_VT6656) += vt6656/ +obj-$(CONFIG_VME_BUS) += vme/ +obj-$(CONFIG_DX_SEP) += sep/ +obj-$(CONFIG_IIO) += iio/ +obj-$(CONFIG_ZRAM) += zram/ +obj-$(CONFIG_ZCACHE) += zcache/ +obj-$(CONFIG_ZSMALLOC) += zsmalloc/ +obj-$(CONFIG_WLAGS49_H2) += wlags49_h2/ +obj-$(CONFIG_WLAGS49_H25) += wlags49_h25/ +obj-$(CONFIG_FB_SM7XX) += sm7xx/ +obj-$(CONFIG_CRYSTALHD) += crystalhd/ +obj-$(CONFIG_CXT1E1) += cxt1e1/ +obj-$(CONFIG_FB_XGI) += xgifb/ +obj-$(CONFIG_TIDSPBRIDGE) += tidspbridge/ +obj-$(CONFIG_ACPI_QUICKSTART) += quickstart/ +obj-$(CONFIG_SBE_2T3E3) += sbe-2t3e3/ +obj-$(CONFIG_USB_ENESTORAGE) += keucr/ +obj-$(CONFIG_BCM_WIMAX) += bcm/ +obj-$(CONFIG_FT1000) += ft1000/ +obj-$(CONFIG_SPEAKUP) += speakup/ +obj-$(CONFIG_TOUCHSCREEN_CLEARPAD_TM1217) += cptm1217/ +obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4) += ste_rmi4/ +obj-$(CONFIG_INTEL_MEI) += mei/ +obj-$(CONFIG_MFD_NVEC) += nvec/ +obj-$(CONFIG_DRM_OMAP) += omapdrm/ +obj-$(CONFIG_ANDROID) += android/ +obj-$(CONFIG_PHONE) += telephony/ +obj-$(CONFIG_RAMSTER) += ramster/ +obj-$(CONFIG_USB_WPAN_HCD) += ozwpan/ diff --git a/drivers/staging/android/Kconfig b/drivers/staging/android/Kconfig new file mode 100644 index 00000000..43d17c27 --- /dev/null +++ b/drivers/staging/android/Kconfig @@ -0,0 +1,92 @@ +menu "Android" + +config ANDROID + bool "Android Drivers" + default N + ---help--- + Enable support for various drivers needed on the Android platform + +if ANDROID + +config ANDROID_BINDER_IPC + bool "Android Binder IPC Driver" + default n + +config ASHMEM + bool "Enable the Anonymous Shared Memory Subsystem" + default n + depends on SHMEM || TINY_SHMEM + help + The ashmem subsystem is a new shared memory allocator, similar to + POSIX SHM but with different behavior and sporting a simpler + file-based API. + +config ANDROID_LOGGER + tristate "Android log driver" + default n + +config ANDROID_PERSISTENT_RAM + bool + depends on HAVE_MEMBLOCK + select REED_SOLOMON + select REED_SOLOMON_ENC8 + select REED_SOLOMON_DEC8 + +config ANDROID_RAM_CONSOLE + bool "Android RAM buffer console" + depends on !S390 && !UML && HAVE_MEMBLOCK + select ANDROID_PERSISTENT_RAM + default n + +config PERSISTENT_TRACER + bool "Persistent function tracer" + depends on HAVE_FUNCTION_TRACER + select FUNCTION_TRACER + select ANDROID_PERSISTENT_RAM + help + persistent_trace traces function calls into a persistent ram + buffer that can be decoded and dumped after reboot through + /sys/kernel/debug/persistent_trace. It can be used to + determine what function was last called before a reset or + panic. + + If unsure, say N. + +config ANDROID_TIMED_OUTPUT + bool "Timed output class driver" + default y + +config ANDROID_TIMED_GPIO + tristate "Android timed gpio driver" + depends on GENERIC_GPIO && ANDROID_TIMED_OUTPUT + default n + +config ANDROID_LOW_MEMORY_KILLER + bool "Android Low Memory Killer" + default N + ---help--- + Register processes to be killed when memory is low + +config ANDROID_LOW_MEMORY_KILLER_AUTODETECT_OOM_ADJ_VALUES + bool "Android Low Memory Killer: detect oom_adj values" + depends on ANDROID_LOW_MEMORY_KILLER + default y + ---help--- + Detect oom_adj values written to + /sys/module/lowmemorykiller/parameters/adj and convert them + to oom_score_adj values. + +source "drivers/staging/android/switch/Kconfig" + +config ANDROID_INTF_ALARM_DEV + bool "Android alarm driver" + depends on RTC_CLASS + default n + help + Provides non-wakeup and rtc backed wakeup alarms based on rtc or + elapsed realtime, and a non-wakeup alarm on the monotonic clock. + Also exports the alarm interface to user-space. + +endif # if ANDROID + +endmenu diff --git a/drivers/staging/android/Makefile b/drivers/staging/android/Makefile new file mode 100644 index 00000000..8769e325 --- /dev/null +++ b/drivers/staging/android/Makefile @@ -0,0 +1,15 @@ +ccflags-y += -I$(src) # needed for trace events + +obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o +obj-$(CONFIG_ASHMEM) += ashmem.o +obj-$(CONFIG_ANDROID_LOGGER) += logger.o +obj-$(CONFIG_ANDROID_PERSISTENT_RAM) += persistent_ram.o +obj-$(CONFIG_ANDROID_RAM_CONSOLE) += ram_console.o +obj-$(CONFIG_ANDROID_TIMED_OUTPUT) += timed_output.o +obj-$(CONFIG_ANDROID_TIMED_GPIO) += timed_gpio.o +obj-$(CONFIG_ANDROID_LOW_MEMORY_KILLER) += lowmemorykiller.o +obj-$(CONFIG_ANDROID_SWITCH) += switch/ +obj-$(CONFIG_ANDROID_INTF_ALARM_DEV) += alarm-dev.o +obj-$(CONFIG_PERSISTENT_TRACER) += trace_persistent.o + +CFLAGS_REMOVE_trace_persistent.o = -pg diff --git a/drivers/staging/android/alarm-dev.c b/drivers/staging/android/alarm-dev.c new file mode 100644 index 00000000..6a9ff531 --- /dev/null +++ b/drivers/staging/android/alarm-dev.c @@ -0,0 +1,384 @@ +/* drivers/rtc/alarm-dev.c + * + * Copyright (C) 2007-2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "android_alarm.h" + +#define ANDROID_ALARM_PRINT_INFO (1U << 0) +#define ANDROID_ALARM_PRINT_IO (1U << 1) +#define ANDROID_ALARM_PRINT_INT (1U << 2) + +static int debug_mask = ANDROID_ALARM_PRINT_INFO; +module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define pr_alarm(debug_level_mask, args...) \ + do { \ + if (debug_mask & ANDROID_ALARM_PRINT_##debug_level_mask) { \ + pr_info(args); \ + } \ + } while (0) + +#define ANDROID_ALARM_WAKEUP_MASK ( \ + ANDROID_ALARM_RTC_WAKEUP_MASK | \ + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK) + +/* support old usespace code */ +#define ANDROID_ALARM_SET_OLD _IOW('a', 2, time_t) /* set alarm */ +#define ANDROID_ALARM_SET_AND_WAIT_OLD _IOW('a', 3, time_t) + +static int alarm_opened; +static DEFINE_SPINLOCK(alarm_slock); +static struct wake_lock alarm_wake_lock; +static DECLARE_WAIT_QUEUE_HEAD(alarm_wait_queue); +static uint32_t alarm_pending; +static uint32_t alarm_enabled; +static uint32_t wait_pending; + +struct devalarm { + union { + struct hrtimer hrt; + struct alarm alrm; + } u; + enum android_alarm_type type; +}; + +static struct devalarm alarms[ANDROID_ALARM_TYPE_COUNT]; + + +static int is_wakeup(enum android_alarm_type type) +{ + if (type == ANDROID_ALARM_RTC_WAKEUP || + type == ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP) + return 1; + return 0; +} + + +static void devalarm_start(struct devalarm *alrm, ktime_t exp) +{ + if (is_wakeup(alrm->type)) + alarm_start(&alrm->u.alrm, exp); + else + hrtimer_start(&alrm->u.hrt, exp, HRTIMER_MODE_ABS); +} + + +static int devalarm_try_to_cancel(struct devalarm *alrm) +{ + int ret; + if (is_wakeup(alrm->type)) + ret = alarm_try_to_cancel(&alrm->u.alrm); + else + ret = hrtimer_try_to_cancel(&alrm->u.hrt); + return ret; +} + +static void devalarm_cancel(struct devalarm *alrm) +{ + if (is_wakeup(alrm->type)) + alarm_cancel(&alrm->u.alrm); + else + hrtimer_cancel(&alrm->u.hrt); +} + + +static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rv = 0; + unsigned long flags; + struct timespec new_alarm_time; + struct timespec new_rtc_time; + struct timespec tmp_time; + struct rtc_time new_rtc_tm; + struct rtc_device *rtc_dev; + enum android_alarm_type alarm_type = ANDROID_ALARM_IOCTL_TO_TYPE(cmd); + uint32_t alarm_type_mask = 1U << alarm_type; + + if (alarm_type >= ANDROID_ALARM_TYPE_COUNT) + return -EINVAL; + + if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_GET_TIME(0)) { + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + return -EPERM; + if (file->private_data == NULL && + cmd != ANDROID_ALARM_SET_RTC) { + spin_lock_irqsave(&alarm_slock, flags); + if (alarm_opened) { + spin_unlock_irqrestore(&alarm_slock, flags); + return -EBUSY; + } + alarm_opened = 1; + file->private_data = (void *)1; + spin_unlock_irqrestore(&alarm_slock, flags); + } + } + + switch (ANDROID_ALARM_BASE_CMD(cmd)) { + case ANDROID_ALARM_CLEAR(0): + spin_lock_irqsave(&alarm_slock, flags); + pr_alarm(IO, "alarm %d clear\n", alarm_type); + devalarm_try_to_cancel(&alarms[alarm_type]); + if (alarm_pending) { + alarm_pending &= ~alarm_type_mask; + if (!alarm_pending && !wait_pending) + wake_unlock(&alarm_wake_lock); + } + alarm_enabled &= ~alarm_type_mask; + spin_unlock_irqrestore(&alarm_slock, flags); + break; + + case ANDROID_ALARM_SET_OLD: + case ANDROID_ALARM_SET_AND_WAIT_OLD: + if (get_user(new_alarm_time.tv_sec, (int __user *)arg)) { + rv = -EFAULT; + goto err1; + } + new_alarm_time.tv_nsec = 0; + goto from_old_alarm_set; + + case ANDROID_ALARM_SET_AND_WAIT(0): + case ANDROID_ALARM_SET(0): + if (copy_from_user(&new_alarm_time, (void __user *)arg, + sizeof(new_alarm_time))) { + rv = -EFAULT; + goto err1; + } +from_old_alarm_set: + spin_lock_irqsave(&alarm_slock, flags); + pr_alarm(IO, "alarm %d set %ld.%09ld\n", alarm_type, + new_alarm_time.tv_sec, new_alarm_time.tv_nsec); + alarm_enabled |= alarm_type_mask; + devalarm_start(&alarms[alarm_type], + timespec_to_ktime(new_alarm_time)); + spin_unlock_irqrestore(&alarm_slock, flags); + if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_SET_AND_WAIT(0) + && cmd != ANDROID_ALARM_SET_AND_WAIT_OLD) + break; + /* fall though */ + case ANDROID_ALARM_WAIT: + spin_lock_irqsave(&alarm_slock, flags); + pr_alarm(IO, "alarm wait\n"); + if (!alarm_pending && wait_pending) { + wake_unlock(&alarm_wake_lock); + wait_pending = 0; + } + spin_unlock_irqrestore(&alarm_slock, flags); + rv = wait_event_interruptible(alarm_wait_queue, alarm_pending); + if (rv) + goto err1; + spin_lock_irqsave(&alarm_slock, flags); + rv = alarm_pending; + wait_pending = 1; + alarm_pending = 0; + spin_unlock_irqrestore(&alarm_slock, flags); + break; + case ANDROID_ALARM_SET_RTC: + if (copy_from_user(&new_rtc_time, (void __user *)arg, + sizeof(new_rtc_time))) { + rv = -EFAULT; + goto err1; + } + rtc_time_to_tm(new_rtc_time.tv_sec, &new_rtc_tm); + rtc_dev = alarmtimer_get_rtcdev(); + rv = do_settimeofday(&new_rtc_time); + if (rv < 0) + goto err1; + if (rtc_dev) + rv = rtc_set_time(rtc_dev, &new_rtc_tm); + spin_lock_irqsave(&alarm_slock, flags); + alarm_pending |= ANDROID_ALARM_TIME_CHANGE_MASK; + wake_up(&alarm_wait_queue); + spin_unlock_irqrestore(&alarm_slock, flags); + if (rv < 0) + goto err1; + break; + case ANDROID_ALARM_GET_TIME(0): + switch (alarm_type) { + case ANDROID_ALARM_RTC_WAKEUP: + case ANDROID_ALARM_RTC: + getnstimeofday(&tmp_time); + break; + case ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP: + case ANDROID_ALARM_ELAPSED_REALTIME: + get_monotonic_boottime(&tmp_time); + break; + case ANDROID_ALARM_TYPE_COUNT: + case ANDROID_ALARM_SYSTEMTIME: + ktime_get_ts(&tmp_time); + break; + } + if (copy_to_user((void __user *)arg, &tmp_time, + sizeof(tmp_time))) { + rv = -EFAULT; + goto err1; + } + break; + + default: + rv = -EINVAL; + goto err1; + } +err1: + return rv; +} + +static int alarm_open(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +static int alarm_release(struct inode *inode, struct file *file) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&alarm_slock, flags); + if (file->private_data != 0) { + for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) { + uint32_t alarm_type_mask = 1U << i; + if (alarm_enabled & alarm_type_mask) { + pr_alarm(INFO, "alarm_release: clear alarm, " + "pending %d\n", + !!(alarm_pending & alarm_type_mask)); + alarm_enabled &= ~alarm_type_mask; + } + spin_unlock_irqrestore(&alarm_slock, flags); + devalarm_cancel(&alarms[i]); + spin_lock_irqsave(&alarm_slock, flags); + } + if (alarm_pending | wait_pending) { + if (alarm_pending) + pr_alarm(INFO, "alarm_release: clear " + "pending alarms %x\n", alarm_pending); + wake_unlock(&alarm_wake_lock); + wait_pending = 0; + alarm_pending = 0; + } + alarm_opened = 0; + } + spin_unlock_irqrestore(&alarm_slock, flags); + return 0; +} + +void devalarm_triggered2(void) +{ + unsigned long flags; + + spin_lock_irqsave(&alarm_slock, flags); + wake_lock_timeout(&alarm_wake_lock, 5 * HZ); + alarm_pending |= 0x10000; + wake_up(&alarm_wait_queue); + spin_unlock_irqrestore(&alarm_slock, flags); +} + +static void devalarm_triggered(struct devalarm *alarm) +{ + unsigned long flags; + uint32_t alarm_type_mask = 1U << alarm->type; + + pr_alarm(INT, "devalarm_triggered type %d\n", alarm->type); + spin_lock_irqsave(&alarm_slock, flags); + if (alarm_enabled & alarm_type_mask) { + wake_lock_timeout(&alarm_wake_lock, 5 * HZ); + alarm_enabled &= ~alarm_type_mask; + alarm_pending |= alarm_type_mask; + wake_up(&alarm_wait_queue); + } + spin_unlock_irqrestore(&alarm_slock, flags); +} + + +static enum hrtimer_restart devalarm_hrthandler(struct hrtimer *hrt) +{ + struct devalarm *devalrm = container_of(hrt, struct devalarm, u.hrt); + + devalarm_triggered(devalrm); + return HRTIMER_NORESTART; +} + +static enum alarmtimer_restart devalarm_alarmhandler(struct alarm *alrm, + ktime_t now) +{ + struct devalarm *devalrm = container_of(alrm, struct devalarm, u.alrm); + + devalarm_triggered(devalrm); + return ALARMTIMER_NORESTART; +} + + +static const struct file_operations alarm_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = alarm_ioctl, + .open = alarm_open, + .release = alarm_release, +}; + +static struct miscdevice alarm_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "alarm", + .fops = &alarm_fops, +}; + +static int __init alarm_dev_init(void) +{ + int err; + int i; + + err = misc_register(&alarm_device); + if (err) + return err; + + alarm_init(&alarms[ANDROID_ALARM_RTC_WAKEUP].u.alrm, + ALARM_REALTIME, devalarm_alarmhandler); + hrtimer_init(&alarms[ANDROID_ALARM_RTC].u.hrt, + CLOCK_REALTIME, HRTIMER_MODE_ABS); + alarm_init(&alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].u.alrm, + ALARM_BOOTTIME, devalarm_alarmhandler); + hrtimer_init(&alarms[ANDROID_ALARM_ELAPSED_REALTIME].u.hrt, + CLOCK_BOOTTIME, HRTIMER_MODE_ABS); + hrtimer_init(&alarms[ANDROID_ALARM_SYSTEMTIME].u.hrt, + CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + + for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) { + alarms[i].type = i; + if (!is_wakeup(i)) + alarms[i].u.hrt.function = devalarm_hrthandler; + } + + wake_lock_init(&alarm_wake_lock, WAKE_LOCK_SUSPEND, "alarm"); + + return 0; +} + +static void __exit alarm_dev_exit(void) +{ + misc_deregister(&alarm_device); + wake_lock_destroy(&alarm_wake_lock); +} + +module_init(alarm_dev_init); +module_exit(alarm_dev_exit); + diff --git a/drivers/staging/android/android_alarm.h b/drivers/staging/android/android_alarm.h new file mode 100644 index 00000000..d0cafd63 --- /dev/null +++ b/drivers/staging/android/android_alarm.h @@ -0,0 +1,62 @@ +/* include/linux/android_alarm.h + * + * Copyright (C) 2006-2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LINUX_ANDROID_ALARM_H +#define _LINUX_ANDROID_ALARM_H + +#include +#include + +enum android_alarm_type { + /* return code bit numbers or set alarm arg */ + ANDROID_ALARM_RTC_WAKEUP, + ANDROID_ALARM_RTC, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + ANDROID_ALARM_ELAPSED_REALTIME, + ANDROID_ALARM_SYSTEMTIME, + + ANDROID_ALARM_TYPE_COUNT, + + /* return code bit numbers */ + /* ANDROID_ALARM_TIME_CHANGE = 16 */ +}; + +enum android_alarm_return_flags { + ANDROID_ALARM_RTC_WAKEUP_MASK = 1U << ANDROID_ALARM_RTC_WAKEUP, + ANDROID_ALARM_RTC_MASK = 1U << ANDROID_ALARM_RTC, + ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK = + 1U << ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, + ANDROID_ALARM_ELAPSED_REALTIME_MASK = + 1U << ANDROID_ALARM_ELAPSED_REALTIME, + ANDROID_ALARM_SYSTEMTIME_MASK = 1U << ANDROID_ALARM_SYSTEMTIME, + ANDROID_ALARM_TIME_CHANGE_MASK = 1U << 16 +}; + +/* Disable alarm */ +#define ANDROID_ALARM_CLEAR(type) _IO('a', 0 | ((type) << 4)) + +/* Ack last alarm and wait for next */ +#define ANDROID_ALARM_WAIT _IO('a', 1) + +#define ALARM_IOW(c, type, size) _IOW('a', (c) | ((type) << 4), size) +/* Set alarm */ +#define ANDROID_ALARM_SET(type) ALARM_IOW(2, type, struct timespec) +#define ANDROID_ALARM_SET_AND_WAIT(type) ALARM_IOW(3, type, struct timespec) +#define ANDROID_ALARM_GET_TIME(type) ALARM_IOW(4, type, struct timespec) +#define ANDROID_ALARM_SET_RTC _IOW('a', 5, struct timespec) +#define ANDROID_ALARM_BASE_CMD(cmd) (cmd & ~(_IOC(0, 0, 0xf0, 0))) +#define ANDROID_ALARM_IOCTL_TO_TYPE(cmd) (_IOC_NR(cmd) >> 4) + +#endif diff --git a/drivers/staging/android/ashmem.c b/drivers/staging/android/ashmem.c new file mode 100644 index 00000000..bda20bf1 --- /dev/null +++ b/drivers/staging/android/ashmem.c @@ -0,0 +1,744 @@ +/* mm/ashmem.c +** +** Anonymous Shared Memory Subsystem, ashmem +** +** Copyright (C) 2008 Google, Inc. +** +** Robert Love +** +** This software is licensed under the terms of the GNU General Public +** License version 2, as published by the Free Software Foundation, and +** may be copied, distributed, and modified under those terms. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ashmem.h" + +#define ASHMEM_NAME_PREFIX "dev/ashmem/" +#define ASHMEM_NAME_PREFIX_LEN (sizeof(ASHMEM_NAME_PREFIX) - 1) +#define ASHMEM_FULL_NAME_LEN (ASHMEM_NAME_LEN + ASHMEM_NAME_PREFIX_LEN) + +/* + * ashmem_area - anonymous shared memory area + * Lifecycle: From our parent file's open() until its release() + * Locking: Protected by `ashmem_mutex' + * Big Note: Mappings do NOT pin this structure; it dies on close() + */ +struct ashmem_area { + char name[ASHMEM_FULL_NAME_LEN]; /* optional name in /proc/pid/maps */ + struct list_head unpinned_list; /* list of all ashmem areas */ + struct file *file; /* the shmem-based backing file */ + size_t size; /* size of the mapping, in bytes */ + unsigned long prot_mask; /* allowed prot bits, as vm_flags */ +}; + +/* + * ashmem_range - represents an interval of unpinned (evictable) pages + * Lifecycle: From unpin to pin + * Locking: Protected by `ashmem_mutex' + */ +struct ashmem_range { + struct list_head lru; /* entry in LRU list */ + struct list_head unpinned; /* entry in its area's unpinned list */ + struct ashmem_area *asma; /* associated area */ + size_t pgstart; /* starting page, inclusive */ + size_t pgend; /* ending page, inclusive */ + unsigned int purged; /* ASHMEM_NOT or ASHMEM_WAS_PURGED */ +}; + +/* LRU list of unpinned pages, protected by ashmem_mutex */ +static LIST_HEAD(ashmem_lru_list); + +/* Count of pages on our LRU list, protected by ashmem_mutex */ +static unsigned long lru_count; + +/* + * ashmem_mutex - protects the list of and each individual ashmem_area + * + * Lock Ordering: ashmex_mutex -> i_mutex -> i_alloc_sem + */ +static DEFINE_MUTEX(ashmem_mutex); + +static struct kmem_cache *ashmem_area_cachep __read_mostly; +static struct kmem_cache *ashmem_range_cachep __read_mostly; + +#define range_size(range) \ + ((range)->pgend - (range)->pgstart + 1) + +#define range_on_lru(range) \ + ((range)->purged == ASHMEM_NOT_PURGED) + +#define page_range_subsumes_range(range, start, end) \ + (((range)->pgstart >= (start)) && ((range)->pgend <= (end))) + +#define page_range_subsumed_by_range(range, start, end) \ + (((range)->pgstart <= (start)) && ((range)->pgend >= (end))) + +#define page_in_range(range, page) \ + (((range)->pgstart <= (page)) && ((range)->pgend >= (page))) + +#define page_range_in_range(range, start, end) \ + (page_in_range(range, start) || page_in_range(range, end) || \ + page_range_subsumes_range(range, start, end)) + +#define range_before_page(range, page) \ + ((range)->pgend < (page)) + +#define PROT_MASK (PROT_EXEC | PROT_READ | PROT_WRITE) + +static inline void lru_add(struct ashmem_range *range) +{ + list_add_tail(&range->lru, &ashmem_lru_list); + lru_count += range_size(range); +} + +static inline void lru_del(struct ashmem_range *range) +{ + list_del(&range->lru); + lru_count -= range_size(range); +} + +/* + * range_alloc - allocate and initialize a new ashmem_range structure + * + * 'asma' - associated ashmem_area + * 'prev_range' - the previous ashmem_range in the sorted asma->unpinned list + * 'purged' - initial purge value (ASMEM_NOT_PURGED or ASHMEM_WAS_PURGED) + * 'start' - starting page, inclusive + * 'end' - ending page, inclusive + * + * Caller must hold ashmem_mutex. + */ +static int range_alloc(struct ashmem_area *asma, + struct ashmem_range *prev_range, unsigned int purged, + size_t start, size_t end) +{ + struct ashmem_range *range; + + range = kmem_cache_zalloc(ashmem_range_cachep, GFP_KERNEL); + if (unlikely(!range)) + return -ENOMEM; + + range->asma = asma; + range->pgstart = start; + range->pgend = end; + range->purged = purged; + + list_add_tail(&range->unpinned, &prev_range->unpinned); + + if (range_on_lru(range)) + lru_add(range); + + return 0; +} + +static void range_del(struct ashmem_range *range) +{ + list_del(&range->unpinned); + if (range_on_lru(range)) + lru_del(range); + kmem_cache_free(ashmem_range_cachep, range); +} + +/* + * range_shrink - shrinks a range + * + * Caller must hold ashmem_mutex. + */ +static inline void range_shrink(struct ashmem_range *range, + size_t start, size_t end) +{ + size_t pre = range_size(range); + + range->pgstart = start; + range->pgend = end; + + if (range_on_lru(range)) + lru_count -= pre - range_size(range); +} + +static int ashmem_open(struct inode *inode, struct file *file) +{ + struct ashmem_area *asma; + int ret; + + ret = generic_file_open(inode, file); + if (unlikely(ret)) + return ret; + + asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL); + if (unlikely(!asma)) + return -ENOMEM; + + INIT_LIST_HEAD(&asma->unpinned_list); + memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN); + asma->prot_mask = PROT_MASK; + file->private_data = asma; + + return 0; +} + +static int ashmem_release(struct inode *ignored, struct file *file) +{ + struct ashmem_area *asma = file->private_data; + struct ashmem_range *range, *next; + + mutex_lock(&ashmem_mutex); + list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) + range_del(range); + mutex_unlock(&ashmem_mutex); + + if (asma->file) + fput(asma->file); + kmem_cache_free(ashmem_area_cachep, asma); + + return 0; +} + +static ssize_t ashmem_read(struct file *file, char __user *buf, + size_t len, loff_t *pos) +{ + struct ashmem_area *asma = file->private_data; + int ret = 0; + + mutex_lock(&ashmem_mutex); + + /* If size is not set, or set to 0, always return EOF. */ + if (asma->size == 0) + goto out; + + if (!asma->file) { + ret = -EBADF; + goto out; + } + + ret = asma->file->f_op->read(asma->file, buf, len, pos); + if (ret < 0) + goto out; + + /** Update backing file pos, since f_ops->read() doesn't */ + asma->file->f_pos = *pos; + +out: + mutex_unlock(&ashmem_mutex); + return ret; +} + +static loff_t ashmem_llseek(struct file *file, loff_t offset, int origin) +{ + struct ashmem_area *asma = file->private_data; + int ret; + + mutex_lock(&ashmem_mutex); + + if (asma->size == 0) { + ret = -EINVAL; + goto out; + } + + if (!asma->file) { + ret = -EBADF; + goto out; + } + + ret = asma->file->f_op->llseek(asma->file, offset, origin); + if (ret < 0) + goto out; + + /** Copy f_pos from backing file, since f_ops->llseek() sets it */ + file->f_pos = asma->file->f_pos; + +out: + mutex_unlock(&ashmem_mutex); + return ret; +} + +static inline unsigned long calc_vm_may_flags(unsigned long prot) +{ + return _calc_vm_trans(prot, PROT_READ, VM_MAYREAD) | + _calc_vm_trans(prot, PROT_WRITE, VM_MAYWRITE) | + _calc_vm_trans(prot, PROT_EXEC, VM_MAYEXEC); +} + +static int ashmem_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct ashmem_area *asma = file->private_data; + int ret = 0; + + mutex_lock(&ashmem_mutex); + + /* user needs to SET_SIZE before mapping */ + if (unlikely(!asma->size)) { + ret = -EINVAL; + goto out; + } + + /* requested protection bits must match our allowed protection mask */ + if (unlikely((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask)) & + calc_vm_prot_bits(PROT_MASK))) { + ret = -EPERM; + goto out; + } + vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask); + + if (!asma->file) { + char *name = ASHMEM_NAME_DEF; + struct file *vmfile; + + if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0') + name = asma->name; + + /* ... and allocate the backing shmem file */ + vmfile = shmem_file_setup(name, asma->size, vma->vm_flags); + if (unlikely(IS_ERR(vmfile))) { + ret = PTR_ERR(vmfile); + goto out; + } + asma->file = vmfile; + } + get_file(asma->file); + + if (vma->vm_flags & VM_SHARED) + shmem_set_file(vma, asma->file); + else { + if (vma->vm_file) + fput(vma->vm_file); + vma->vm_file = asma->file; + } + vma->vm_flags |= VM_CAN_NONLINEAR; + +out: + mutex_unlock(&ashmem_mutex); + return ret; +} + +/* + * ashmem_shrink - our cache shrinker, called from mm/vmscan.c :: shrink_slab + * + * 'nr_to_scan' is the number of objects (pages) to prune, or 0 to query how + * many objects (pages) we have in total. + * + * 'gfp_mask' is the mask of the allocation that got us into this mess. + * + * Return value is the number of objects (pages) remaining, or -1 if we cannot + * proceed without risk of deadlock (due to gfp_mask). + * + * We approximate LRU via least-recently-unpinned, jettisoning unpinned partial + * chunks of ashmem regions LRU-wise one-at-a-time until we hit 'nr_to_scan' + * pages freed. + */ +static int ashmem_shrink(struct shrinker *s, struct shrink_control *sc) +{ + struct ashmem_range *range, *next; + + /* We might recurse into filesystem code, so bail out if necessary */ + if (sc->nr_to_scan && !(sc->gfp_mask & __GFP_FS)) + return -1; + if (!sc->nr_to_scan) + return lru_count; + + mutex_lock(&ashmem_mutex); + list_for_each_entry_safe(range, next, &ashmem_lru_list, lru) { + struct inode *inode = range->asma->file->f_dentry->d_inode; + loff_t start = range->pgstart * PAGE_SIZE; + loff_t end = (range->pgend + 1) * PAGE_SIZE - 1; + + vmtruncate_range(inode, start, end); + range->purged = ASHMEM_WAS_PURGED; + lru_del(range); + + sc->nr_to_scan -= range_size(range); + if (sc->nr_to_scan <= 0) + break; + } + mutex_unlock(&ashmem_mutex); + + return lru_count; +} + +static struct shrinker ashmem_shrinker = { + .shrink = ashmem_shrink, + .seeks = DEFAULT_SEEKS * 4, +}; + +static int set_prot_mask(struct ashmem_area *asma, unsigned long prot) +{ + int ret = 0; + + mutex_lock(&ashmem_mutex); + + /* the user can only remove, not add, protection bits */ + if (unlikely((asma->prot_mask & prot) != prot)) { + ret = -EINVAL; + goto out; + } + + /* does the application expect PROT_READ to imply PROT_EXEC? */ + if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC)) + prot |= PROT_EXEC; + + asma->prot_mask = prot; + +out: + mutex_unlock(&ashmem_mutex); + return ret; +} + +static int set_name(struct ashmem_area *asma, void __user *name) +{ + int ret = 0; + + mutex_lock(&ashmem_mutex); + + /* cannot change an existing mapping's name */ + if (unlikely(asma->file)) { + ret = -EINVAL; + goto out; + } + + if (unlikely(copy_from_user(asma->name + ASHMEM_NAME_PREFIX_LEN, + name, ASHMEM_NAME_LEN))) + ret = -EFAULT; + asma->name[ASHMEM_FULL_NAME_LEN-1] = '\0'; + +out: + mutex_unlock(&ashmem_mutex); + + return ret; +} + +static int get_name(struct ashmem_area *asma, void __user *name) +{ + int ret = 0; + + mutex_lock(&ashmem_mutex); + if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0') { + size_t len; + + /* + * Copying only `len', instead of ASHMEM_NAME_LEN, bytes + * prevents us from revealing one user's stack to another. + */ + len = strlen(asma->name + ASHMEM_NAME_PREFIX_LEN) + 1; + if (unlikely(copy_to_user(name, + asma->name + ASHMEM_NAME_PREFIX_LEN, len))) + ret = -EFAULT; + } else { + if (unlikely(copy_to_user(name, ASHMEM_NAME_DEF, + sizeof(ASHMEM_NAME_DEF)))) + ret = -EFAULT; + } + mutex_unlock(&ashmem_mutex); + + return ret; +} + +/* + * ashmem_pin - pin the given ashmem region, returning whether it was + * previously purged (ASHMEM_WAS_PURGED) or not (ASHMEM_NOT_PURGED). + * + * Caller must hold ashmem_mutex. + */ +static int ashmem_pin(struct ashmem_area *asma, size_t pgstart, size_t pgend) +{ + struct ashmem_range *range, *next; + int ret = ASHMEM_NOT_PURGED; + + list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) { + /* moved past last applicable page; we can short circuit */ + if (range_before_page(range, pgstart)) + break; + + /* + * The user can ask us to pin pages that span multiple ranges, + * or to pin pages that aren't even unpinned, so this is messy. + * + * Four cases: + * 1. The requested range subsumes an existing range, so we + * just remove the entire matching range. + * 2. The requested range overlaps the start of an existing + * range, so we just update that range. + * 3. The requested range overlaps the end of an existing + * range, so we just update that range. + * 4. The requested range punches a hole in an existing range, + * so we have to update one side of the range and then + * create a new range for the other side. + */ + if (page_range_in_range(range, pgstart, pgend)) { + ret |= range->purged; + + /* Case #1: Easy. Just nuke the whole thing. */ + if (page_range_subsumes_range(range, pgstart, pgend)) { + range_del(range); + continue; + } + + /* Case #2: We overlap from the start, so adjust it */ + if (range->pgstart >= pgstart) { + range_shrink(range, pgend + 1, range->pgend); + continue; + } + + /* Case #3: We overlap from the rear, so adjust it */ + if (range->pgend <= pgend) { + range_shrink(range, range->pgstart, pgstart-1); + continue; + } + + /* + * Case #4: We eat a chunk out of the middle. A bit + * more complicated, we allocate a new range for the + * second half and adjust the first chunk's endpoint. + */ + range_alloc(asma, range, range->purged, + pgend + 1, range->pgend); + range_shrink(range, range->pgstart, pgstart - 1); + break; + } + } + + return ret; +} + +/* + * ashmem_unpin - unpin the given range of pages. Returns zero on success. + * + * Caller must hold ashmem_mutex. + */ +static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend) +{ + struct ashmem_range *range, *next; + unsigned int purged = ASHMEM_NOT_PURGED; + +restart: + list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) { + /* short circuit: this is our insertion point */ + if (range_before_page(range, pgstart)) + break; + + /* + * The user can ask us to unpin pages that are already entirely + * or partially pinned. We handle those two cases here. + */ + if (page_range_subsumed_by_range(range, pgstart, pgend)) + return 0; + if (page_range_in_range(range, pgstart, pgend)) { + pgstart = min_t(size_t, range->pgstart, pgstart), + pgend = max_t(size_t, range->pgend, pgend); + purged |= range->purged; + range_del(range); + goto restart; + } + } + + return range_alloc(asma, range, purged, pgstart, pgend); +} + +/* + * ashmem_get_pin_status - Returns ASHMEM_IS_UNPINNED if _any_ pages in the + * given interval are unpinned and ASHMEM_IS_PINNED otherwise. + * + * Caller must hold ashmem_mutex. + */ +static int ashmem_get_pin_status(struct ashmem_area *asma, size_t pgstart, + size_t pgend) +{ + struct ashmem_range *range; + int ret = ASHMEM_IS_PINNED; + + list_for_each_entry(range, &asma->unpinned_list, unpinned) { + if (range_before_page(range, pgstart)) + break; + if (page_range_in_range(range, pgstart, pgend)) { + ret = ASHMEM_IS_UNPINNED; + break; + } + } + + return ret; +} + +static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd, + void __user *p) +{ + struct ashmem_pin pin; + size_t pgstart, pgend; + int ret = -EINVAL; + + if (unlikely(!asma->file)) + return -EINVAL; + + if (unlikely(copy_from_user(&pin, p, sizeof(pin)))) + return -EFAULT; + + /* per custom, you can pass zero for len to mean "everything onward" */ + if (!pin.len) + pin.len = PAGE_ALIGN(asma->size) - pin.offset; + + if (unlikely((pin.offset | pin.len) & ~PAGE_MASK)) + return -EINVAL; + + if (unlikely(((__u32) -1) - pin.offset < pin.len)) + return -EINVAL; + + if (unlikely(PAGE_ALIGN(asma->size) < pin.offset + pin.len)) + return -EINVAL; + + pgstart = pin.offset / PAGE_SIZE; + pgend = pgstart + (pin.len / PAGE_SIZE) - 1; + + mutex_lock(&ashmem_mutex); + + switch (cmd) { + case ASHMEM_PIN: + ret = ashmem_pin(asma, pgstart, pgend); + break; + case ASHMEM_UNPIN: + ret = ashmem_unpin(asma, pgstart, pgend); + break; + case ASHMEM_GET_PIN_STATUS: + ret = ashmem_get_pin_status(asma, pgstart, pgend); + break; + } + + mutex_unlock(&ashmem_mutex); + + return ret; +} + +static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct ashmem_area *asma = file->private_data; + long ret = -ENOTTY; + + switch (cmd) { + case ASHMEM_SET_NAME: + ret = set_name(asma, (void __user *) arg); + break; + case ASHMEM_GET_NAME: + ret = get_name(asma, (void __user *) arg); + break; + case ASHMEM_SET_SIZE: + ret = -EINVAL; + if (!asma->file) { + ret = 0; + asma->size = (size_t) arg; + } + break; + case ASHMEM_GET_SIZE: + ret = asma->size; + break; + case ASHMEM_SET_PROT_MASK: + ret = set_prot_mask(asma, arg); + break; + case ASHMEM_GET_PROT_MASK: + ret = asma->prot_mask; + break; + case ASHMEM_PIN: + case ASHMEM_UNPIN: + case ASHMEM_GET_PIN_STATUS: + ret = ashmem_pin_unpin(asma, cmd, (void __user *) arg); + break; + case ASHMEM_PURGE_ALL_CACHES: + ret = -EPERM; + if (capable(CAP_SYS_ADMIN)) { + struct shrink_control sc = { + .gfp_mask = GFP_KERNEL, + .nr_to_scan = 0, + }; + ret = ashmem_shrink(&ashmem_shrinker, &sc); + sc.nr_to_scan = ret; + ashmem_shrink(&ashmem_shrinker, &sc); + } + break; + } + + return ret; +} + +static const struct file_operations ashmem_fops = { + .owner = THIS_MODULE, + .open = ashmem_open, + .release = ashmem_release, + .read = ashmem_read, + .llseek = ashmem_llseek, + .mmap = ashmem_mmap, + .unlocked_ioctl = ashmem_ioctl, + .compat_ioctl = ashmem_ioctl, +}; + +static struct miscdevice ashmem_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "ashmem", + .fops = &ashmem_fops, +}; + +static int __init ashmem_init(void) +{ + int ret; + + ashmem_area_cachep = kmem_cache_create("ashmem_area_cache", + sizeof(struct ashmem_area), + 0, 0, NULL); + if (unlikely(!ashmem_area_cachep)) { + printk(KERN_ERR "ashmem: failed to create slab cache\n"); + return -ENOMEM; + } + + ashmem_range_cachep = kmem_cache_create("ashmem_range_cache", + sizeof(struct ashmem_range), + 0, 0, NULL); + if (unlikely(!ashmem_range_cachep)) { + printk(KERN_ERR "ashmem: failed to create slab cache\n"); + return -ENOMEM; + } + + ret = misc_register(&ashmem_misc); + if (unlikely(ret)) { + printk(KERN_ERR "ashmem: failed to register misc device!\n"); + return ret; + } + + register_shrinker(&ashmem_shrinker); + + printk(KERN_INFO "ashmem: initialized\n"); + + return 0; +} + +static void __exit ashmem_exit(void) +{ + int ret; + + unregister_shrinker(&ashmem_shrinker); + + ret = misc_deregister(&ashmem_misc); + if (unlikely(ret)) + printk(KERN_ERR "ashmem: failed to unregister misc device!\n"); + + kmem_cache_destroy(ashmem_range_cachep); + kmem_cache_destroy(ashmem_area_cachep); + + printk(KERN_INFO "ashmem: unloaded\n"); +} + +module_init(ashmem_init); +module_exit(ashmem_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/android/ashmem.h b/drivers/staging/android/ashmem.h new file mode 100644 index 00000000..1976b10e --- /dev/null +++ b/drivers/staging/android/ashmem.h @@ -0,0 +1,48 @@ +/* + * include/linux/ashmem.h + * + * Copyright 2008 Google Inc. + * Author: Robert Love + * + * This file is dual licensed. It may be redistributed and/or modified + * under the terms of the Apache 2.0 License OR version 2 of the GNU + * General Public License. + */ + +#ifndef _LINUX_ASHMEM_H +#define _LINUX_ASHMEM_H + +#include +#include + +#define ASHMEM_NAME_LEN 256 + +#define ASHMEM_NAME_DEF "dev/ashmem" + +/* Return values from ASHMEM_PIN: Was the mapping purged while unpinned? */ +#define ASHMEM_NOT_PURGED 0 +#define ASHMEM_WAS_PURGED 1 + +/* Return values from ASHMEM_GET_PIN_STATUS: Is the mapping pinned? */ +#define ASHMEM_IS_UNPINNED 0 +#define ASHMEM_IS_PINNED 1 + +struct ashmem_pin { + __u32 offset; /* offset into region, in bytes, page-aligned */ + __u32 len; /* length forward from offset, in bytes, page-aligned */ +}; + +#define __ASHMEMIOC 0x77 + +#define ASHMEM_SET_NAME _IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN]) +#define ASHMEM_GET_NAME _IOR(__ASHMEMIOC, 2, char[ASHMEM_NAME_LEN]) +#define ASHMEM_SET_SIZE _IOW(__ASHMEMIOC, 3, size_t) +#define ASHMEM_GET_SIZE _IO(__ASHMEMIOC, 4) +#define ASHMEM_SET_PROT_MASK _IOW(__ASHMEMIOC, 5, unsigned long) +#define ASHMEM_GET_PROT_MASK _IO(__ASHMEMIOC, 6) +#define ASHMEM_PIN _IOW(__ASHMEMIOC, 7, struct ashmem_pin) +#define ASHMEM_UNPIN _IOW(__ASHMEMIOC, 8, struct ashmem_pin) +#define ASHMEM_GET_PIN_STATUS _IO(__ASHMEMIOC, 9) +#define ASHMEM_PURGE_ALL_CACHES _IO(__ASHMEMIOC, 10) + +#endif /* _LINUX_ASHMEM_H */ diff --git a/drivers/staging/android/binder.c b/drivers/staging/android/binder.c new file mode 100644 index 00000000..85ed6cfd --- /dev/null +++ b/drivers/staging/android/binder.c @@ -0,0 +1,3666 @@ +/* binder.c + * + * Android IPC Subsystem + * + * Copyright (C) 2007-2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "binder.h" +#include "binder_trace.h" + +static DEFINE_MUTEX(binder_main_lock); +static DEFINE_MUTEX(binder_deferred_lock); +static DEFINE_MUTEX(binder_mmap_lock); + +static HLIST_HEAD(binder_procs); +static HLIST_HEAD(binder_deferred_list); +static HLIST_HEAD(binder_dead_nodes); + +static struct dentry *binder_debugfs_dir_entry_root; +static struct dentry *binder_debugfs_dir_entry_proc; +static struct binder_node *binder_context_mgr_node; +static uid_t binder_context_mgr_uid = -1; +static int binder_last_id; +static struct workqueue_struct *binder_deferred_workqueue; + +#define BINDER_DEBUG_ENTRY(name) \ +static int binder_##name##_open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, binder_##name##_show, inode->i_private); \ +} \ +\ +static const struct file_operations binder_##name##_fops = { \ + .owner = THIS_MODULE, \ + .open = binder_##name##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ +} + +static int binder_proc_show(struct seq_file *m, void *unused); +BINDER_DEBUG_ENTRY(proc); + +/* This is only defined in include/asm-arm/sizes.h */ +#ifndef SZ_1K +#define SZ_1K 0x400 +#endif + +#ifndef SZ_4M +#define SZ_4M 0x400000 +#endif + +#define FORBIDDEN_MMAP_FLAGS (VM_WRITE) + +#define BINDER_SMALL_BUF_SIZE (PAGE_SIZE * 64) + +enum { + BINDER_DEBUG_USER_ERROR = 1U << 0, + BINDER_DEBUG_FAILED_TRANSACTION = 1U << 1, + BINDER_DEBUG_DEAD_TRANSACTION = 1U << 2, + BINDER_DEBUG_OPEN_CLOSE = 1U << 3, + BINDER_DEBUG_DEAD_BINDER = 1U << 4, + BINDER_DEBUG_DEATH_NOTIFICATION = 1U << 5, + BINDER_DEBUG_READ_WRITE = 1U << 6, + BINDER_DEBUG_USER_REFS = 1U << 7, + BINDER_DEBUG_THREADS = 1U << 8, + BINDER_DEBUG_TRANSACTION = 1U << 9, + BINDER_DEBUG_TRANSACTION_COMPLETE = 1U << 10, + BINDER_DEBUG_FREE_BUFFER = 1U << 11, + BINDER_DEBUG_INTERNAL_REFS = 1U << 12, + BINDER_DEBUG_BUFFER_ALLOC = 1U << 13, + BINDER_DEBUG_PRIORITY_CAP = 1U << 14, + BINDER_DEBUG_BUFFER_ALLOC_ASYNC = 1U << 15, +}; +static uint32_t binder_debug_mask = BINDER_DEBUG_USER_ERROR | + BINDER_DEBUG_FAILED_TRANSACTION | BINDER_DEBUG_DEAD_TRANSACTION; +module_param_named(debug_mask, binder_debug_mask, uint, S_IWUSR | S_IRUGO); + +static bool binder_debug_no_lock; +module_param_named(proc_no_lock, binder_debug_no_lock, bool, S_IWUSR | S_IRUGO); + +static DECLARE_WAIT_QUEUE_HEAD(binder_user_error_wait); +static int binder_stop_on_user_error; + +static int binder_set_stop_on_user_error(const char *val, + struct kernel_param *kp) +{ + int ret; + ret = param_set_int(val, kp); + if (binder_stop_on_user_error < 2) + wake_up(&binder_user_error_wait); + return ret; +} +module_param_call(stop_on_user_error, binder_set_stop_on_user_error, + param_get_int, &binder_stop_on_user_error, S_IWUSR | S_IRUGO); + +#define binder_debug(mask, x...) \ + do { \ + if (binder_debug_mask & mask) \ + printk(KERN_INFO x); \ + } while (0) + +#define binder_user_error(x...) \ + do { \ + if (binder_debug_mask & BINDER_DEBUG_USER_ERROR) \ + printk(KERN_INFO x); \ + if (binder_stop_on_user_error) \ + binder_stop_on_user_error = 2; \ + } while (0) + +enum binder_stat_types { + BINDER_STAT_PROC, + BINDER_STAT_THREAD, + BINDER_STAT_NODE, + BINDER_STAT_REF, + BINDER_STAT_DEATH, + BINDER_STAT_TRANSACTION, + BINDER_STAT_TRANSACTION_COMPLETE, + BINDER_STAT_COUNT +}; + +struct binder_stats { + int br[_IOC_NR(BR_FAILED_REPLY) + 1]; + int bc[_IOC_NR(BC_DEAD_BINDER_DONE) + 1]; + int obj_created[BINDER_STAT_COUNT]; + int obj_deleted[BINDER_STAT_COUNT]; +}; + +static struct binder_stats binder_stats; + +static inline void binder_stats_deleted(enum binder_stat_types type) +{ + binder_stats.obj_deleted[type]++; +} + +static inline void binder_stats_created(enum binder_stat_types type) +{ + binder_stats.obj_created[type]++; +} + +struct binder_transaction_log_entry { + int debug_id; + int call_type; + int from_proc; + int from_thread; + int target_handle; + int to_proc; + int to_thread; + int to_node; + int data_size; + int offsets_size; +}; +struct binder_transaction_log { + int next; + int full; + struct binder_transaction_log_entry entry[32]; +}; +static struct binder_transaction_log binder_transaction_log; +static struct binder_transaction_log binder_transaction_log_failed; + +static struct binder_transaction_log_entry *binder_transaction_log_add( + struct binder_transaction_log *log) +{ + struct binder_transaction_log_entry *e; + e = &log->entry[log->next]; + memset(e, 0, sizeof(*e)); + log->next++; + if (log->next == ARRAY_SIZE(log->entry)) { + log->next = 0; + log->full = 1; + } + return e; +} + +struct binder_work { + struct list_head entry; + enum { + BINDER_WORK_TRANSACTION = 1, + BINDER_WORK_TRANSACTION_COMPLETE, + BINDER_WORK_NODE, + BINDER_WORK_DEAD_BINDER, + BINDER_WORK_DEAD_BINDER_AND_CLEAR, + BINDER_WORK_CLEAR_DEATH_NOTIFICATION, + } type; +}; + +struct binder_node { + int debug_id; + struct binder_work work; + union { + struct rb_node rb_node; + struct hlist_node dead_node; + }; + struct binder_proc *proc; + struct hlist_head refs; + int internal_strong_refs; + int local_weak_refs; + int local_strong_refs; + void __user *ptr; + void __user *cookie; + unsigned has_strong_ref:1; + unsigned pending_strong_ref:1; + unsigned has_weak_ref:1; + unsigned pending_weak_ref:1; + unsigned has_async_transaction:1; + unsigned accept_fds:1; + unsigned min_priority:8; + struct list_head async_todo; +}; + +struct binder_ref_death { + struct binder_work work; + void __user *cookie; +}; + +struct binder_ref { + /* Lookups needed: */ + /* node + proc => ref (transaction) */ + /* desc + proc => ref (transaction, inc/dec ref) */ + /* node => refs + procs (proc exit) */ + int debug_id; + struct rb_node rb_node_desc; + struct rb_node rb_node_node; + struct hlist_node node_entry; + struct binder_proc *proc; + struct binder_node *node; + uint32_t desc; + int strong; + int weak; + struct binder_ref_death *death; +}; + +struct binder_buffer { + struct list_head entry; /* free and allocated entries by address */ + struct rb_node rb_node; /* free entry by size or allocated entry */ + /* by address */ + unsigned free:1; + unsigned allow_user_free:1; + unsigned async_transaction:1; + unsigned debug_id:29; + + struct binder_transaction *transaction; + + struct binder_node *target_node; + size_t data_size; + size_t offsets_size; + uint8_t data[0]; +}; + +enum binder_deferred_state { + BINDER_DEFERRED_PUT_FILES = 0x01, + BINDER_DEFERRED_FLUSH = 0x02, + BINDER_DEFERRED_RELEASE = 0x04, +}; + +struct binder_proc { + struct hlist_node proc_node; + struct rb_root threads; + struct rb_root nodes; + struct rb_root refs_by_desc; + struct rb_root refs_by_node; + int pid; + struct vm_area_struct *vma; + struct mm_struct *vma_vm_mm; + struct task_struct *tsk; + struct files_struct *files; + struct hlist_node deferred_work_node; + int deferred_work; + void *buffer; + ptrdiff_t user_buffer_offset; + + struct list_head buffers; + struct rb_root free_buffers; + struct rb_root allocated_buffers; + size_t free_async_space; + + struct page **pages; + size_t buffer_size; + uint32_t buffer_free; + struct list_head todo; + wait_queue_head_t wait; + struct binder_stats stats; + struct list_head delivered_death; + int max_threads; + int requested_threads; + int requested_threads_started; + int ready_threads; + long default_priority; + struct dentry *debugfs_entry; +}; + +enum { + BINDER_LOOPER_STATE_REGISTERED = 0x01, + BINDER_LOOPER_STATE_ENTERED = 0x02, + BINDER_LOOPER_STATE_EXITED = 0x04, + BINDER_LOOPER_STATE_INVALID = 0x08, + BINDER_LOOPER_STATE_WAITING = 0x10, + BINDER_LOOPER_STATE_NEED_RETURN = 0x20 +}; + +struct binder_thread { + struct binder_proc *proc; + struct rb_node rb_node; + int pid; + int looper; + struct binder_transaction *transaction_stack; + struct list_head todo; + uint32_t return_error; /* Write failed, return error code in read buf */ + uint32_t return_error2; /* Write failed, return error code in read */ + /* buffer. Used when sending a reply to a dead process that */ + /* we are also waiting on */ + wait_queue_head_t wait; + struct binder_stats stats; +}; + +struct binder_transaction { + int debug_id; + struct binder_work work; + struct binder_thread *from; + struct binder_transaction *from_parent; + struct binder_proc *to_proc; + struct binder_thread *to_thread; + struct binder_transaction *to_parent; + unsigned need_reply:1; + /* unsigned is_dead:1; */ /* not used at the moment */ + + struct binder_buffer *buffer; + unsigned int code; + unsigned int flags; + long priority; + long saved_priority; + uid_t sender_euid; +}; + +static void +binder_defer_work(struct binder_proc *proc, enum binder_deferred_state defer); + +/* + * copied from get_unused_fd_flags + */ +int task_get_unused_fd_flags(struct binder_proc *proc, int flags) +{ + struct files_struct *files = proc->files; + int fd, error; + struct fdtable *fdt; + unsigned long rlim_cur; + unsigned long irqs; + + if (files == NULL) + return -ESRCH; + + error = -EMFILE; + spin_lock(&files->file_lock); + +repeat: + fdt = files_fdtable(files); + fd = find_next_zero_bit(fdt->open_fds, fdt->max_fds, files->next_fd); + + /* + * N.B. For clone tasks sharing a files structure, this test + * will limit the total number of files that can be opened. + */ + rlim_cur = 0; + if (lock_task_sighand(proc->tsk, &irqs)) { + rlim_cur = proc->tsk->signal->rlim[RLIMIT_NOFILE].rlim_cur; + unlock_task_sighand(proc->tsk, &irqs); + } + if (fd >= rlim_cur) + goto out; + + /* Do we need to expand the fd array or fd set? */ + error = expand_files(files, fd); + if (error < 0) + goto out; + + if (error) { + /* + * If we needed to expand the fs array we + * might have blocked - try again. + */ + error = -EMFILE; + goto repeat; + } + + __set_open_fd(fd, fdt); + if (flags & O_CLOEXEC) + __set_close_on_exec(fd, fdt); + else + __clear_close_on_exec(fd, fdt); + files->next_fd = fd + 1; +#if 1 + /* Sanity check */ + if (fdt->fd[fd] != NULL) { + printk(KERN_WARNING "get_unused_fd: slot %d not NULL!\n", fd); + fdt->fd[fd] = NULL; + } +#endif + error = fd; + +out: + spin_unlock(&files->file_lock); + return error; +} + +/* + * copied from fd_install + */ +static void task_fd_install( + struct binder_proc *proc, unsigned int fd, struct file *file) +{ + struct files_struct *files = proc->files; + struct fdtable *fdt; + + if (files == NULL) + return; + + spin_lock(&files->file_lock); + fdt = files_fdtable(files); + BUG_ON(fdt->fd[fd] != NULL); + rcu_assign_pointer(fdt->fd[fd], file); + spin_unlock(&files->file_lock); +} + +/* + * copied from __put_unused_fd in open.c + */ +static void __put_unused_fd(struct files_struct *files, unsigned int fd) +{ + struct fdtable *fdt = files_fdtable(files); + __clear_open_fd(fd, fdt); + if (fd < files->next_fd) + files->next_fd = fd; +} + +/* + * copied from sys_close + */ +static long task_close_fd(struct binder_proc *proc, unsigned int fd) +{ + struct file *filp; + struct files_struct *files = proc->files; + struct fdtable *fdt; + int retval; + + if (files == NULL) + return -ESRCH; + + spin_lock(&files->file_lock); + fdt = files_fdtable(files); + if (fd >= fdt->max_fds) + goto out_unlock; + filp = fdt->fd[fd]; + if (!filp) + goto out_unlock; + rcu_assign_pointer(fdt->fd[fd], NULL); + __clear_close_on_exec(fd, fdt); + __put_unused_fd(files, fd); + spin_unlock(&files->file_lock); + retval = filp_close(filp, files); + + /* can't restart close syscall because file table entry was cleared */ + if (unlikely(retval == -ERESTARTSYS || + retval == -ERESTARTNOINTR || + retval == -ERESTARTNOHAND || + retval == -ERESTART_RESTARTBLOCK)) + retval = -EINTR; + + return retval; + +out_unlock: + spin_unlock(&files->file_lock); + return -EBADF; +} + +static inline void binder_lock(const char *tag) +{ + trace_binder_lock(tag); + mutex_lock(&binder_main_lock); + trace_binder_locked(tag); +} + +static inline void binder_unlock(const char *tag) +{ + trace_binder_unlock(tag); + mutex_unlock(&binder_main_lock); +} + +static void binder_set_nice(long nice) +{ + long min_nice; + if (can_nice(current, nice)) { + set_user_nice(current, nice); + return; + } + min_nice = 20 - current->signal->rlim[RLIMIT_NICE].rlim_cur; + binder_debug(BINDER_DEBUG_PRIORITY_CAP, + "binder: %d: nice value %ld not allowed use " + "%ld instead\n", current->pid, nice, min_nice); + set_user_nice(current, min_nice); + if (min_nice < 20) + return; + binder_user_error("binder: %d RLIMIT_NICE not set\n", current->pid); +} + +static size_t binder_buffer_size(struct binder_proc *proc, + struct binder_buffer *buffer) +{ + if (list_is_last(&buffer->entry, &proc->buffers)) + return proc->buffer + proc->buffer_size - (void *)buffer->data; + else + return (size_t)list_entry(buffer->entry.next, + struct binder_buffer, entry) - (size_t)buffer->data; +} + +static void binder_insert_free_buffer(struct binder_proc *proc, + struct binder_buffer *new_buffer) +{ + struct rb_node **p = &proc->free_buffers.rb_node; + struct rb_node *parent = NULL; + struct binder_buffer *buffer; + size_t buffer_size; + size_t new_buffer_size; + + BUG_ON(!new_buffer->free); + + new_buffer_size = binder_buffer_size(proc, new_buffer); + + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder: %d: add free buffer, size %zd, " + "at %p\n", proc->pid, new_buffer_size, new_buffer); + + while (*p) { + parent = *p; + buffer = rb_entry(parent, struct binder_buffer, rb_node); + BUG_ON(!buffer->free); + + buffer_size = binder_buffer_size(proc, buffer); + + if (new_buffer_size < buffer_size) + p = &parent->rb_left; + else + p = &parent->rb_right; + } + rb_link_node(&new_buffer->rb_node, parent, p); + rb_insert_color(&new_buffer->rb_node, &proc->free_buffers); +} + +static void binder_insert_allocated_buffer(struct binder_proc *proc, + struct binder_buffer *new_buffer) +{ + struct rb_node **p = &proc->allocated_buffers.rb_node; + struct rb_node *parent = NULL; + struct binder_buffer *buffer; + + BUG_ON(new_buffer->free); + + while (*p) { + parent = *p; + buffer = rb_entry(parent, struct binder_buffer, rb_node); + BUG_ON(buffer->free); + + if (new_buffer < buffer) + p = &parent->rb_left; + else if (new_buffer > buffer) + p = &parent->rb_right; + else + BUG(); + } + rb_link_node(&new_buffer->rb_node, parent, p); + rb_insert_color(&new_buffer->rb_node, &proc->allocated_buffers); +} + +static struct binder_buffer *binder_buffer_lookup(struct binder_proc *proc, + void __user *user_ptr) +{ + struct rb_node *n = proc->allocated_buffers.rb_node; + struct binder_buffer *buffer; + struct binder_buffer *kern_ptr; + + kern_ptr = user_ptr - proc->user_buffer_offset + - offsetof(struct binder_buffer, data); + + while (n) { + buffer = rb_entry(n, struct binder_buffer, rb_node); + BUG_ON(buffer->free); + + if (kern_ptr < buffer) + n = n->rb_left; + else if (kern_ptr > buffer) + n = n->rb_right; + else + return buffer; + } + return NULL; +} + +static int binder_update_page_range(struct binder_proc *proc, int allocate, + void *start, void *end, + struct vm_area_struct *vma) +{ + void *page_addr; + unsigned long user_page_addr; + struct vm_struct tmp_area; + struct page **page; + struct mm_struct *mm; + + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder: %d: %s pages %p-%p\n", proc->pid, + allocate ? "allocate" : "free", start, end); + + if (end <= start) + return 0; + + trace_binder_update_page_range(proc, allocate, start, end); + + if (vma) + mm = NULL; + else + mm = get_task_mm(proc->tsk); + + if (mm) { + down_write(&mm->mmap_sem); + vma = proc->vma; + if (vma && mm != proc->vma_vm_mm) { + pr_err("binder: %d: vma mm and task mm mismatch\n", + proc->pid); + vma = NULL; + } + } + + if (allocate == 0) + goto free_range; + + if (vma == NULL) { + printk(KERN_ERR "binder: %d: binder_alloc_buf failed to " + "map pages in userspace, no vma\n", proc->pid); + goto err_no_vma; + } + + for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) { + int ret; + struct page **page_array_ptr; + page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE]; + + BUG_ON(*page); + *page = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (*page == NULL) { + printk(KERN_ERR "binder: %d: binder_alloc_buf failed " + "for page at %p\n", proc->pid, page_addr); + goto err_alloc_page_failed; + } + tmp_area.addr = page_addr; + tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */; + page_array_ptr = page; + ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr); + if (ret) { + printk(KERN_ERR "binder: %d: binder_alloc_buf failed " + "to map page at %p in kernel\n", + proc->pid, page_addr); + goto err_map_kernel_failed; + } + user_page_addr = + (uintptr_t)page_addr + proc->user_buffer_offset; + ret = vm_insert_page(vma, user_page_addr, page[0]); + if (ret) { + printk(KERN_ERR "binder: %d: binder_alloc_buf failed " + "to map page at %lx in userspace\n", + proc->pid, user_page_addr); + goto err_vm_insert_page_failed; + } + /* vm_insert_page does not seem to increment the refcount */ + } + if (mm) { + up_write(&mm->mmap_sem); + mmput(mm); + } + return 0; + +free_range: + for (page_addr = end - PAGE_SIZE; page_addr >= start; + page_addr -= PAGE_SIZE) { + page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE]; + if (vma) + zap_page_range(vma, (uintptr_t)page_addr + + proc->user_buffer_offset, PAGE_SIZE, NULL); +err_vm_insert_page_failed: + unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE); +err_map_kernel_failed: + __free_page(*page); + *page = NULL; +err_alloc_page_failed: + ; + } +err_no_vma: + if (mm) { + up_write(&mm->mmap_sem); + mmput(mm); + } + return -ENOMEM; +} + +static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc, + size_t data_size, + size_t offsets_size, int is_async) +{ + struct rb_node *n = proc->free_buffers.rb_node; + struct binder_buffer *buffer; + size_t buffer_size; + struct rb_node *best_fit = NULL; + void *has_page_addr; + void *end_page_addr; + size_t size; + + if (proc->vma == NULL) { + printk(KERN_ERR "binder: %d: binder_alloc_buf, no vma\n", + proc->pid); + return NULL; + } + + size = ALIGN(data_size, sizeof(void *)) + + ALIGN(offsets_size, sizeof(void *)); + + if (size < data_size || size < offsets_size) { + binder_user_error("binder: %d: got transaction with invalid " + "size %zd-%zd\n", proc->pid, data_size, offsets_size); + return NULL; + } + + if (is_async && + proc->free_async_space < size + sizeof(struct binder_buffer)) { + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder: %d: binder_alloc_buf size %zd" + "failed, no async space left\n", proc->pid, size); + return NULL; + } + + while (n) { + buffer = rb_entry(n, struct binder_buffer, rb_node); + BUG_ON(!buffer->free); + buffer_size = binder_buffer_size(proc, buffer); + + if (size < buffer_size) { + best_fit = n; + n = n->rb_left; + } else if (size > buffer_size) + n = n->rb_right; + else { + best_fit = n; + break; + } + } + if (best_fit == NULL) { + printk(KERN_ERR "binder: %d: binder_alloc_buf size %zd failed, " + "no address space\n", proc->pid, size); + return NULL; + } + if (n == NULL) { + buffer = rb_entry(best_fit, struct binder_buffer, rb_node); + buffer_size = binder_buffer_size(proc, buffer); + } + + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder: %d: binder_alloc_buf size %zd got buff" + "er %p size %zd\n", proc->pid, size, buffer, buffer_size); + + has_page_addr = + (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK); + if (n == NULL) { + if (size + sizeof(struct binder_buffer) + 4 >= buffer_size) + buffer_size = size; /* no room for other buffers */ + else + buffer_size = size + sizeof(struct binder_buffer); + } + end_page_addr = + (void *)PAGE_ALIGN((uintptr_t)buffer->data + buffer_size); + if (end_page_addr > has_page_addr) + end_page_addr = has_page_addr; + if (binder_update_page_range(proc, 1, + (void *)PAGE_ALIGN((uintptr_t)buffer->data), end_page_addr, NULL)) + return NULL; + + rb_erase(best_fit, &proc->free_buffers); + buffer->free = 0; + binder_insert_allocated_buffer(proc, buffer); + if (buffer_size != size) { + struct binder_buffer *new_buffer = (void *)buffer->data + size; + list_add(&new_buffer->entry, &buffer->entry); + new_buffer->free = 1; + binder_insert_free_buffer(proc, new_buffer); + } + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder: %d: binder_alloc_buf size %zd got " + "%p\n", proc->pid, size, buffer); + buffer->data_size = data_size; + buffer->offsets_size = offsets_size; + buffer->async_transaction = is_async; + if (is_async) { + proc->free_async_space -= size + sizeof(struct binder_buffer); + binder_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC, + "binder: %d: binder_alloc_buf size %zd " + "async free %zd\n", proc->pid, size, + proc->free_async_space); + } + + return buffer; +} + +static void *buffer_start_page(struct binder_buffer *buffer) +{ + return (void *)((uintptr_t)buffer & PAGE_MASK); +} + +static void *buffer_end_page(struct binder_buffer *buffer) +{ + return (void *)(((uintptr_t)(buffer + 1) - 1) & PAGE_MASK); +} + +static void binder_delete_free_buffer(struct binder_proc *proc, + struct binder_buffer *buffer) +{ + struct binder_buffer *prev, *next = NULL; + int free_page_end = 1; + int free_page_start = 1; + + BUG_ON(proc->buffers.next == &buffer->entry); + prev = list_entry(buffer->entry.prev, struct binder_buffer, entry); + BUG_ON(!prev->free); + if (buffer_end_page(prev) == buffer_start_page(buffer)) { + free_page_start = 0; + if (buffer_end_page(prev) == buffer_end_page(buffer)) + free_page_end = 0; + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder: %d: merge free, buffer %p " + "share page with %p\n", proc->pid, buffer, prev); + } + + if (!list_is_last(&buffer->entry, &proc->buffers)) { + next = list_entry(buffer->entry.next, + struct binder_buffer, entry); + if (buffer_start_page(next) == buffer_end_page(buffer)) { + free_page_end = 0; + if (buffer_start_page(next) == + buffer_start_page(buffer)) + free_page_start = 0; + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder: %d: merge free, buffer" + " %p share page with %p\n", proc->pid, + buffer, prev); + } + } + list_del(&buffer->entry); + if (free_page_start || free_page_end) { + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder: %d: merge free, buffer %p do " + "not share page%s%s with with %p or %p\n", + proc->pid, buffer, free_page_start ? "" : " end", + free_page_end ? "" : " start", prev, next); + binder_update_page_range(proc, 0, free_page_start ? + buffer_start_page(buffer) : buffer_end_page(buffer), + (free_page_end ? buffer_end_page(buffer) : + buffer_start_page(buffer)) + PAGE_SIZE, NULL); + } +} + +static void binder_free_buf(struct binder_proc *proc, + struct binder_buffer *buffer) +{ + size_t size, buffer_size; + + buffer_size = binder_buffer_size(proc, buffer); + + size = ALIGN(buffer->data_size, sizeof(void *)) + + ALIGN(buffer->offsets_size, sizeof(void *)); + + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder: %d: binder_free_buf %p size %zd buffer" + "_size %zd\n", proc->pid, buffer, size, buffer_size); + + BUG_ON(buffer->free); + BUG_ON(size > buffer_size); + BUG_ON(buffer->transaction != NULL); + BUG_ON((void *)buffer < proc->buffer); + BUG_ON((void *)buffer > proc->buffer + proc->buffer_size); + + if (buffer->async_transaction) { + proc->free_async_space += size + sizeof(struct binder_buffer); + + binder_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC, + "binder: %d: binder_free_buf size %zd " + "async free %zd\n", proc->pid, size, + proc->free_async_space); + } + + binder_update_page_range(proc, 0, + (void *)PAGE_ALIGN((uintptr_t)buffer->data), + (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK), + NULL); + rb_erase(&buffer->rb_node, &proc->allocated_buffers); + buffer->free = 1; + if (!list_is_last(&buffer->entry, &proc->buffers)) { + struct binder_buffer *next = list_entry(buffer->entry.next, + struct binder_buffer, entry); + if (next->free) { + rb_erase(&next->rb_node, &proc->free_buffers); + binder_delete_free_buffer(proc, next); + } + } + if (proc->buffers.next != &buffer->entry) { + struct binder_buffer *prev = list_entry(buffer->entry.prev, + struct binder_buffer, entry); + if (prev->free) { + binder_delete_free_buffer(proc, buffer); + rb_erase(&prev->rb_node, &proc->free_buffers); + buffer = prev; + } + } + binder_insert_free_buffer(proc, buffer); +} + +static struct binder_node *binder_get_node(struct binder_proc *proc, + void __user *ptr) +{ + struct rb_node *n = proc->nodes.rb_node; + struct binder_node *node; + + while (n) { + node = rb_entry(n, struct binder_node, rb_node); + + if (ptr < node->ptr) + n = n->rb_left; + else if (ptr > node->ptr) + n = n->rb_right; + else + return node; + } + return NULL; +} + +static struct binder_node *binder_new_node(struct binder_proc *proc, + void __user *ptr, + void __user *cookie) +{ + struct rb_node **p = &proc->nodes.rb_node; + struct rb_node *parent = NULL; + struct binder_node *node; + + while (*p) { + parent = *p; + node = rb_entry(parent, struct binder_node, rb_node); + + if (ptr < node->ptr) + p = &(*p)->rb_left; + else if (ptr > node->ptr) + p = &(*p)->rb_right; + else + return NULL; + } + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (node == NULL) + return NULL; + binder_stats_created(BINDER_STAT_NODE); + rb_link_node(&node->rb_node, parent, p); + rb_insert_color(&node->rb_node, &proc->nodes); + node->debug_id = ++binder_last_id; + node->proc = proc; + node->ptr = ptr; + node->cookie = cookie; + node->work.type = BINDER_WORK_NODE; + INIT_LIST_HEAD(&node->work.entry); + INIT_LIST_HEAD(&node->async_todo); + binder_debug(BINDER_DEBUG_INTERNAL_REFS, + "binder: %d:%d node %d u%p c%p created\n", + proc->pid, current->pid, node->debug_id, + node->ptr, node->cookie); + return node; +} + +static int binder_inc_node(struct binder_node *node, int strong, int internal, + struct list_head *target_list) +{ + if (strong) { + if (internal) { + if (target_list == NULL && + node->internal_strong_refs == 0 && + !(node == binder_context_mgr_node && + node->has_strong_ref)) { + printk(KERN_ERR "binder: invalid inc strong " + "node for %d\n", node->debug_id); + return -EINVAL; + } + node->internal_strong_refs++; + } else + node->local_strong_refs++; + if (!node->has_strong_ref && target_list) { + list_del_init(&node->work.entry); + list_add_tail(&node->work.entry, target_list); + } + } else { + if (!internal) + node->local_weak_refs++; + if (!node->has_weak_ref && list_empty(&node->work.entry)) { + if (target_list == NULL) { + printk(KERN_ERR "binder: invalid inc weak node " + "for %d\n", node->debug_id); + return -EINVAL; + } + list_add_tail(&node->work.entry, target_list); + } + } + return 0; +} + +static int binder_dec_node(struct binder_node *node, int strong, int internal) +{ + if (strong) { + if (internal) + node->internal_strong_refs--; + else + node->local_strong_refs--; + if (node->local_strong_refs || node->internal_strong_refs) + return 0; + } else { + if (!internal) + node->local_weak_refs--; + if (node->local_weak_refs || !hlist_empty(&node->refs)) + return 0; + } + if (node->proc && (node->has_strong_ref || node->has_weak_ref)) { + if (list_empty(&node->work.entry)) { + list_add_tail(&node->work.entry, &node->proc->todo); + wake_up_interruptible(&node->proc->wait); + } + } else { + if (hlist_empty(&node->refs) && !node->local_strong_refs && + !node->local_weak_refs) { + list_del_init(&node->work.entry); + if (node->proc) { + rb_erase(&node->rb_node, &node->proc->nodes); + binder_debug(BINDER_DEBUG_INTERNAL_REFS, + "binder: refless node %d deleted\n", + node->debug_id); + } else { + hlist_del(&node->dead_node); + binder_debug(BINDER_DEBUG_INTERNAL_REFS, + "binder: dead node %d deleted\n", + node->debug_id); + } + kfree(node); + binder_stats_deleted(BINDER_STAT_NODE); + } + } + + return 0; +} + + +static struct binder_ref *binder_get_ref(struct binder_proc *proc, + uint32_t desc) +{ + struct rb_node *n = proc->refs_by_desc.rb_node; + struct binder_ref *ref; + + while (n) { + ref = rb_entry(n, struct binder_ref, rb_node_desc); + + if (desc < ref->desc) + n = n->rb_left; + else if (desc > ref->desc) + n = n->rb_right; + else + return ref; + } + return NULL; +} + +static struct binder_ref *binder_get_ref_for_node(struct binder_proc *proc, + struct binder_node *node) +{ + struct rb_node *n; + struct rb_node **p = &proc->refs_by_node.rb_node; + struct rb_node *parent = NULL; + struct binder_ref *ref, *new_ref; + + while (*p) { + parent = *p; + ref = rb_entry(parent, struct binder_ref, rb_node_node); + + if (node < ref->node) + p = &(*p)->rb_left; + else if (node > ref->node) + p = &(*p)->rb_right; + else + return ref; + } + new_ref = kzalloc(sizeof(*ref), GFP_KERNEL); + if (new_ref == NULL) + return NULL; + binder_stats_created(BINDER_STAT_REF); + new_ref->debug_id = ++binder_last_id; + new_ref->proc = proc; + new_ref->node = node; + rb_link_node(&new_ref->rb_node_node, parent, p); + rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node); + + new_ref->desc = (node == binder_context_mgr_node) ? 0 : 1; + for (n = rb_first(&proc->refs_by_desc); n != NULL; n = rb_next(n)) { + ref = rb_entry(n, struct binder_ref, rb_node_desc); + if (ref->desc > new_ref->desc) + break; + new_ref->desc = ref->desc + 1; + } + + p = &proc->refs_by_desc.rb_node; + while (*p) { + parent = *p; + ref = rb_entry(parent, struct binder_ref, rb_node_desc); + + if (new_ref->desc < ref->desc) + p = &(*p)->rb_left; + else if (new_ref->desc > ref->desc) + p = &(*p)->rb_right; + else + BUG(); + } + rb_link_node(&new_ref->rb_node_desc, parent, p); + rb_insert_color(&new_ref->rb_node_desc, &proc->refs_by_desc); + if (node) { + hlist_add_head(&new_ref->node_entry, &node->refs); + + binder_debug(BINDER_DEBUG_INTERNAL_REFS, + "binder: %d new ref %d desc %d for " + "node %d\n", proc->pid, new_ref->debug_id, + new_ref->desc, node->debug_id); + } else { + binder_debug(BINDER_DEBUG_INTERNAL_REFS, + "binder: %d new ref %d desc %d for " + "dead node\n", proc->pid, new_ref->debug_id, + new_ref->desc); + } + return new_ref; +} + +static void binder_delete_ref(struct binder_ref *ref) +{ + binder_debug(BINDER_DEBUG_INTERNAL_REFS, + "binder: %d delete ref %d desc %d for " + "node %d\n", ref->proc->pid, ref->debug_id, + ref->desc, ref->node->debug_id); + + rb_erase(&ref->rb_node_desc, &ref->proc->refs_by_desc); + rb_erase(&ref->rb_node_node, &ref->proc->refs_by_node); + if (ref->strong) + binder_dec_node(ref->node, 1, 1); + hlist_del(&ref->node_entry); + binder_dec_node(ref->node, 0, 1); + if (ref->death) { + binder_debug(BINDER_DEBUG_DEAD_BINDER, + "binder: %d delete ref %d desc %d " + "has death notification\n", ref->proc->pid, + ref->debug_id, ref->desc); + list_del(&ref->death->work.entry); + kfree(ref->death); + binder_stats_deleted(BINDER_STAT_DEATH); + } + kfree(ref); + binder_stats_deleted(BINDER_STAT_REF); +} + +static int binder_inc_ref(struct binder_ref *ref, int strong, + struct list_head *target_list) +{ + int ret; + if (strong) { + if (ref->strong == 0) { + ret = binder_inc_node(ref->node, 1, 1, target_list); + if (ret) + return ret; + } + ref->strong++; + } else { + if (ref->weak == 0) { + ret = binder_inc_node(ref->node, 0, 1, target_list); + if (ret) + return ret; + } + ref->weak++; + } + return 0; +} + + +static int binder_dec_ref(struct binder_ref *ref, int strong) +{ + if (strong) { + if (ref->strong == 0) { + binder_user_error("binder: %d invalid dec strong, " + "ref %d desc %d s %d w %d\n", + ref->proc->pid, ref->debug_id, + ref->desc, ref->strong, ref->weak); + return -EINVAL; + } + ref->strong--; + if (ref->strong == 0) { + int ret; + ret = binder_dec_node(ref->node, strong, 1); + if (ret) + return ret; + } + } else { + if (ref->weak == 0) { + binder_user_error("binder: %d invalid dec weak, " + "ref %d desc %d s %d w %d\n", + ref->proc->pid, ref->debug_id, + ref->desc, ref->strong, ref->weak); + return -EINVAL; + } + ref->weak--; + } + if (ref->strong == 0 && ref->weak == 0) + binder_delete_ref(ref); + return 0; +} + +static void binder_pop_transaction(struct binder_thread *target_thread, + struct binder_transaction *t) +{ + if (target_thread) { + BUG_ON(target_thread->transaction_stack != t); + BUG_ON(target_thread->transaction_stack->from != target_thread); + target_thread->transaction_stack = + target_thread->transaction_stack->from_parent; + t->from = NULL; + } + t->need_reply = 0; + if (t->buffer) + t->buffer->transaction = NULL; + kfree(t); + binder_stats_deleted(BINDER_STAT_TRANSACTION); +} + +static void binder_send_failed_reply(struct binder_transaction *t, + uint32_t error_code) +{ + struct binder_thread *target_thread; + BUG_ON(t->flags & TF_ONE_WAY); + while (1) { + target_thread = t->from; + if (target_thread) { + if (target_thread->return_error != BR_OK && + target_thread->return_error2 == BR_OK) { + target_thread->return_error2 = + target_thread->return_error; + target_thread->return_error = BR_OK; + } + if (target_thread->return_error == BR_OK) { + binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, + "binder: send failed reply for " + "transaction %d to %d:%d\n", + t->debug_id, target_thread->proc->pid, + target_thread->pid); + + binder_pop_transaction(target_thread, t); + target_thread->return_error = error_code; + wake_up_interruptible(&target_thread->wait); + } else { + printk(KERN_ERR "binder: reply failed, target " + "thread, %d:%d, has error code %d " + "already\n", target_thread->proc->pid, + target_thread->pid, + target_thread->return_error); + } + return; + } else { + struct binder_transaction *next = t->from_parent; + + binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, + "binder: send failed reply " + "for transaction %d, target dead\n", + t->debug_id); + + binder_pop_transaction(target_thread, t); + if (next == NULL) { + binder_debug(BINDER_DEBUG_DEAD_BINDER, + "binder: reply failed," + " no target thread at root\n"); + return; + } + t = next; + binder_debug(BINDER_DEBUG_DEAD_BINDER, + "binder: reply failed, no target " + "thread -- retry %d\n", t->debug_id); + } + } +} + +static void binder_transaction_buffer_release(struct binder_proc *proc, + struct binder_buffer *buffer, + size_t *failed_at) +{ + size_t *offp, *off_end; + int debug_id = buffer->debug_id; + + binder_debug(BINDER_DEBUG_TRANSACTION, + "binder: %d buffer release %d, size %zd-%zd, failed at %p\n", + proc->pid, buffer->debug_id, + buffer->data_size, buffer->offsets_size, failed_at); + + if (buffer->target_node) + binder_dec_node(buffer->target_node, 1, 0); + + offp = (size_t *)(buffer->data + ALIGN(buffer->data_size, sizeof(void *))); + if (failed_at) + off_end = failed_at; + else + off_end = (void *)offp + buffer->offsets_size; + for (; offp < off_end; offp++) { + struct flat_binder_object *fp; + if (*offp > buffer->data_size - sizeof(*fp) || + buffer->data_size < sizeof(*fp) || + !IS_ALIGNED(*offp, sizeof(void *))) { + printk(KERN_ERR "binder: transaction release %d bad" + "offset %zd, size %zd\n", debug_id, + *offp, buffer->data_size); + continue; + } + fp = (struct flat_binder_object *)(buffer->data + *offp); + switch (fp->type) { + case BINDER_TYPE_BINDER: + case BINDER_TYPE_WEAK_BINDER: { + struct binder_node *node = binder_get_node(proc, fp->binder); + if (node == NULL) { + printk(KERN_ERR "binder: transaction release %d" + " bad node %p\n", debug_id, fp->binder); + break; + } + binder_debug(BINDER_DEBUG_TRANSACTION, + " node %d u%p\n", + node->debug_id, node->ptr); + binder_dec_node(node, fp->type == BINDER_TYPE_BINDER, 0); + } break; + case BINDER_TYPE_HANDLE: + case BINDER_TYPE_WEAK_HANDLE: { + struct binder_ref *ref = binder_get_ref(proc, fp->handle); + if (ref == NULL) { + printk(KERN_ERR "binder: transaction release %d" + " bad handle %ld\n", debug_id, + fp->handle); + break; + } + binder_debug(BINDER_DEBUG_TRANSACTION, + " ref %d desc %d (node %d)\n", + ref->debug_id, ref->desc, ref->node->debug_id); + binder_dec_ref(ref, fp->type == BINDER_TYPE_HANDLE); + } break; + + case BINDER_TYPE_FD: + binder_debug(BINDER_DEBUG_TRANSACTION, + " fd %ld\n", fp->handle); + if (failed_at) + task_close_fd(proc, fp->handle); + break; + + default: + printk(KERN_ERR "binder: transaction release %d bad " + "object type %lx\n", debug_id, fp->type); + break; + } + } +} + +static void binder_transaction(struct binder_proc *proc, + struct binder_thread *thread, + struct binder_transaction_data *tr, int reply) +{ + struct binder_transaction *t; + struct binder_work *tcomplete; + size_t *offp, *off_end; + struct binder_proc *target_proc; + struct binder_thread *target_thread = NULL; + struct binder_node *target_node = NULL; + struct list_head *target_list; + wait_queue_head_t *target_wait; + struct binder_transaction *in_reply_to = NULL; + struct binder_transaction_log_entry *e; + uint32_t return_error; + + e = binder_transaction_log_add(&binder_transaction_log); + e->call_type = reply ? 2 : !!(tr->flags & TF_ONE_WAY); + e->from_proc = proc->pid; + e->from_thread = thread->pid; + e->target_handle = tr->target.handle; + e->data_size = tr->data_size; + e->offsets_size = tr->offsets_size; + + if (reply) { + in_reply_to = thread->transaction_stack; + if (in_reply_to == NULL) { + binder_user_error("binder: %d:%d got reply transaction " + "with no transaction stack\n", + proc->pid, thread->pid); + return_error = BR_FAILED_REPLY; + goto err_empty_call_stack; + } + binder_set_nice(in_reply_to->saved_priority); + if (in_reply_to->to_thread != thread) { + binder_user_error("binder: %d:%d got reply transaction " + "with bad transaction stack," + " transaction %d has target %d:%d\n", + proc->pid, thread->pid, in_reply_to->debug_id, + in_reply_to->to_proc ? + in_reply_to->to_proc->pid : 0, + in_reply_to->to_thread ? + in_reply_to->to_thread->pid : 0); + return_error = BR_FAILED_REPLY; + in_reply_to = NULL; + goto err_bad_call_stack; + } + thread->transaction_stack = in_reply_to->to_parent; + target_thread = in_reply_to->from; + if (target_thread == NULL) { + return_error = BR_DEAD_REPLY; + goto err_dead_binder; + } + if (target_thread->transaction_stack != in_reply_to) { + binder_user_error("binder: %d:%d got reply transaction " + "with bad target transaction stack %d, " + "expected %d\n", + proc->pid, thread->pid, + target_thread->transaction_stack ? + target_thread->transaction_stack->debug_id : 0, + in_reply_to->debug_id); + return_error = BR_FAILED_REPLY; + in_reply_to = NULL; + target_thread = NULL; + goto err_dead_binder; + } + target_proc = target_thread->proc; + } else { + if (tr->target.handle) { + struct binder_ref *ref; + ref = binder_get_ref(proc, tr->target.handle); + if (ref == NULL) { + binder_user_error("binder: %d:%d got " + "transaction to invalid handle\n", + proc->pid, thread->pid); + return_error = BR_FAILED_REPLY; + goto err_invalid_target_handle; + } + target_node = ref->node; + } else { + target_node = binder_context_mgr_node; + if (target_node == NULL) { + return_error = BR_DEAD_REPLY; + goto err_no_context_mgr_node; + } + } + e->to_node = target_node->debug_id; + target_proc = target_node->proc; + if (target_proc == NULL) { + return_error = BR_DEAD_REPLY; + goto err_dead_binder; + } + if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) { + struct binder_transaction *tmp; + tmp = thread->transaction_stack; + if (tmp->to_thread != thread) { + binder_user_error("binder: %d:%d got new " + "transaction with bad transaction stack" + ", transaction %d has target %d:%d\n", + proc->pid, thread->pid, tmp->debug_id, + tmp->to_proc ? tmp->to_proc->pid : 0, + tmp->to_thread ? + tmp->to_thread->pid : 0); + return_error = BR_FAILED_REPLY; + goto err_bad_call_stack; + } + while (tmp) { + if (tmp->from && tmp->from->proc == target_proc) + target_thread = tmp->from; + tmp = tmp->from_parent; + } + } + } + if (target_thread) { + e->to_thread = target_thread->pid; + target_list = &target_thread->todo; + target_wait = &target_thread->wait; + } else { + target_list = &target_proc->todo; + target_wait = &target_proc->wait; + } + e->to_proc = target_proc->pid; + + /* TODO: reuse incoming transaction for reply */ + t = kzalloc(sizeof(*t), GFP_KERNEL); + if (t == NULL) { + return_error = BR_FAILED_REPLY; + goto err_alloc_t_failed; + } + binder_stats_created(BINDER_STAT_TRANSACTION); + + tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); + if (tcomplete == NULL) { + return_error = BR_FAILED_REPLY; + goto err_alloc_tcomplete_failed; + } + binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE); + + t->debug_id = ++binder_last_id; + e->debug_id = t->debug_id; + + if (reply) + binder_debug(BINDER_DEBUG_TRANSACTION, + "binder: %d:%d BC_REPLY %d -> %d:%d, " + "data %p-%p size %zd-%zd\n", + proc->pid, thread->pid, t->debug_id, + target_proc->pid, target_thread->pid, + tr->data.ptr.buffer, tr->data.ptr.offsets, + tr->data_size, tr->offsets_size); + else + binder_debug(BINDER_DEBUG_TRANSACTION, + "binder: %d:%d BC_TRANSACTION %d -> " + "%d - node %d, data %p-%p size %zd-%zd\n", + proc->pid, thread->pid, t->debug_id, + target_proc->pid, target_node->debug_id, + tr->data.ptr.buffer, tr->data.ptr.offsets, + tr->data_size, tr->offsets_size); + + if (!reply && !(tr->flags & TF_ONE_WAY)) + t->from = thread; + else + t->from = NULL; + t->sender_euid = proc->tsk->cred->euid; + t->to_proc = target_proc; + t->to_thread = target_thread; + t->code = tr->code; + t->flags = tr->flags; + t->priority = task_nice(current); + + trace_binder_transaction(reply, t, target_node); + + t->buffer = binder_alloc_buf(target_proc, tr->data_size, + tr->offsets_size, !reply && (t->flags & TF_ONE_WAY)); + if (t->buffer == NULL) { + return_error = BR_FAILED_REPLY; + goto err_binder_alloc_buf_failed; + } + t->buffer->allow_user_free = 0; + t->buffer->debug_id = t->debug_id; + t->buffer->transaction = t; + t->buffer->target_node = target_node; + trace_binder_transaction_alloc_buf(t->buffer); + if (target_node) + binder_inc_node(target_node, 1, 0, NULL); + + offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *))); + + if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) { + binder_user_error("binder: %d:%d got transaction with invalid " + "data ptr\n", proc->pid, thread->pid); + return_error = BR_FAILED_REPLY; + goto err_copy_data_failed; + } + if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) { + binder_user_error("binder: %d:%d got transaction with invalid " + "offsets ptr\n", proc->pid, thread->pid); + return_error = BR_FAILED_REPLY; + goto err_copy_data_failed; + } + if (!IS_ALIGNED(tr->offsets_size, sizeof(size_t))) { + binder_user_error("binder: %d:%d got transaction with " + "invalid offsets size, %zd\n", + proc->pid, thread->pid, tr->offsets_size); + return_error = BR_FAILED_REPLY; + goto err_bad_offset; + } + off_end = (void *)offp + tr->offsets_size; + for (; offp < off_end; offp++) { + struct flat_binder_object *fp; + if (*offp > t->buffer->data_size - sizeof(*fp) || + t->buffer->data_size < sizeof(*fp) || + !IS_ALIGNED(*offp, sizeof(void *))) { + binder_user_error("binder: %d:%d got transaction with " + "invalid offset, %zd\n", + proc->pid, thread->pid, *offp); + return_error = BR_FAILED_REPLY; + goto err_bad_offset; + } + fp = (struct flat_binder_object *)(t->buffer->data + *offp); + switch (fp->type) { + case BINDER_TYPE_BINDER: + case BINDER_TYPE_WEAK_BINDER: { + struct binder_ref *ref; + struct binder_node *node = binder_get_node(proc, fp->binder); + if (node == NULL) { + node = binder_new_node(proc, fp->binder, fp->cookie); + if (node == NULL) { + return_error = BR_FAILED_REPLY; + goto err_binder_new_node_failed; + } + node->min_priority = fp->flags & FLAT_BINDER_FLAG_PRIORITY_MASK; + node->accept_fds = !!(fp->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS); + } + if (fp->cookie != node->cookie) { + binder_user_error("binder: %d:%d sending u%p " + "node %d, cookie mismatch %p != %p\n", + proc->pid, thread->pid, + fp->binder, node->debug_id, + fp->cookie, node->cookie); + goto err_binder_get_ref_for_node_failed; + } + ref = binder_get_ref_for_node(target_proc, node); + if (ref == NULL) { + return_error = BR_FAILED_REPLY; + goto err_binder_get_ref_for_node_failed; + } + if (fp->type == BINDER_TYPE_BINDER) + fp->type = BINDER_TYPE_HANDLE; + else + fp->type = BINDER_TYPE_WEAK_HANDLE; + fp->handle = ref->desc; + binder_inc_ref(ref, fp->type == BINDER_TYPE_HANDLE, + &thread->todo); + + trace_binder_transaction_node_to_ref(t, node, ref); + binder_debug(BINDER_DEBUG_TRANSACTION, + " node %d u%p -> ref %d desc %d\n", + node->debug_id, node->ptr, ref->debug_id, + ref->desc); + } break; + case BINDER_TYPE_HANDLE: + case BINDER_TYPE_WEAK_HANDLE: { + struct binder_ref *ref = binder_get_ref(proc, fp->handle); + if (ref == NULL) { + binder_user_error("binder: %d:%d got " + "transaction with invalid " + "handle, %ld\n", proc->pid, + thread->pid, fp->handle); + return_error = BR_FAILED_REPLY; + goto err_binder_get_ref_failed; + } + if (ref->node->proc == target_proc) { + if (fp->type == BINDER_TYPE_HANDLE) + fp->type = BINDER_TYPE_BINDER; + else + fp->type = BINDER_TYPE_WEAK_BINDER; + fp->binder = ref->node->ptr; + fp->cookie = ref->node->cookie; + binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL); + trace_binder_transaction_ref_to_node(t, ref); + binder_debug(BINDER_DEBUG_TRANSACTION, + " ref %d desc %d -> node %d u%p\n", + ref->debug_id, ref->desc, ref->node->debug_id, + ref->node->ptr); + } else { + struct binder_ref *new_ref; + new_ref = binder_get_ref_for_node(target_proc, ref->node); + if (new_ref == NULL) { + return_error = BR_FAILED_REPLY; + goto err_binder_get_ref_for_node_failed; + } + fp->handle = new_ref->desc; + binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL); + trace_binder_transaction_ref_to_ref(t, ref, + new_ref); + binder_debug(BINDER_DEBUG_TRANSACTION, + " ref %d desc %d -> ref %d desc %d (node %d)\n", + ref->debug_id, ref->desc, new_ref->debug_id, + new_ref->desc, ref->node->debug_id); + } + } break; + + case BINDER_TYPE_FD: { + int target_fd; + struct file *file; + + if (reply) { + if (!(in_reply_to->flags & TF_ACCEPT_FDS)) { + binder_user_error("binder: %d:%d got reply with fd, %ld, but target does not allow fds\n", + proc->pid, thread->pid, fp->handle); + return_error = BR_FAILED_REPLY; + goto err_fd_not_allowed; + } + } else if (!target_node->accept_fds) { + binder_user_error("binder: %d:%d got transaction with fd, %ld, but target does not allow fds\n", + proc->pid, thread->pid, fp->handle); + return_error = BR_FAILED_REPLY; + goto err_fd_not_allowed; + } + + file = fget(fp->handle); + if (file == NULL) { + binder_user_error("binder: %d:%d got transaction with invalid fd, %ld\n", + proc->pid, thread->pid, fp->handle); + return_error = BR_FAILED_REPLY; + goto err_fget_failed; + } + target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC); + if (target_fd < 0) { + fput(file); + return_error = BR_FAILED_REPLY; + goto err_get_unused_fd_failed; + } + task_fd_install(target_proc, target_fd, file); + trace_binder_transaction_fd(t, fp->handle, target_fd); + binder_debug(BINDER_DEBUG_TRANSACTION, + " fd %ld -> %d\n", fp->handle, target_fd); + /* TODO: fput? */ + fp->handle = target_fd; + } break; + + default: + binder_user_error("binder: %d:%d got transactio" + "n with invalid object type, %lx\n", + proc->pid, thread->pid, fp->type); + return_error = BR_FAILED_REPLY; + goto err_bad_object_type; + } + } + if (reply) { + BUG_ON(t->buffer->async_transaction != 0); + binder_pop_transaction(target_thread, in_reply_to); + } else if (!(t->flags & TF_ONE_WAY)) { + BUG_ON(t->buffer->async_transaction != 0); + t->need_reply = 1; + t->from_parent = thread->transaction_stack; + thread->transaction_stack = t; + } else { + BUG_ON(target_node == NULL); + BUG_ON(t->buffer->async_transaction != 1); + if (target_node->has_async_transaction) { + target_list = &target_node->async_todo; + target_wait = NULL; + } else + target_node->has_async_transaction = 1; + } + t->work.type = BINDER_WORK_TRANSACTION; + list_add_tail(&t->work.entry, target_list); + tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; + list_add_tail(&tcomplete->entry, &thread->todo); + if (target_wait) + wake_up_interruptible(target_wait); + return; + +err_get_unused_fd_failed: +err_fget_failed: +err_fd_not_allowed: +err_binder_get_ref_for_node_failed: +err_binder_get_ref_failed: +err_binder_new_node_failed: +err_bad_object_type: +err_bad_offset: +err_copy_data_failed: + trace_binder_transaction_failed_buffer_release(t->buffer); + binder_transaction_buffer_release(target_proc, t->buffer, offp); + t->buffer->transaction = NULL; + binder_free_buf(target_proc, t->buffer); +err_binder_alloc_buf_failed: + kfree(tcomplete); + binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE); +err_alloc_tcomplete_failed: + kfree(t); + binder_stats_deleted(BINDER_STAT_TRANSACTION); +err_alloc_t_failed: +err_bad_call_stack: +err_empty_call_stack: +err_dead_binder: +err_invalid_target_handle: +err_no_context_mgr_node: + binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, + "binder: %d:%d transaction failed %d, size %zd-%zd\n", + proc->pid, thread->pid, return_error, + tr->data_size, tr->offsets_size); + + { + struct binder_transaction_log_entry *fe; + fe = binder_transaction_log_add(&binder_transaction_log_failed); + *fe = *e; + } + + BUG_ON(thread->return_error != BR_OK); + if (in_reply_to) { + thread->return_error = BR_TRANSACTION_COMPLETE; + binder_send_failed_reply(in_reply_to, return_error); + } else + thread->return_error = return_error; +} + +int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread, + void __user *buffer, int size, signed long *consumed) +{ + uint32_t cmd; + void __user *ptr = buffer + *consumed; + void __user *end = buffer + size; + + while (ptr < end && thread->return_error == BR_OK) { + if (get_user(cmd, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + trace_binder_command(cmd); + if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) { + binder_stats.bc[_IOC_NR(cmd)]++; + proc->stats.bc[_IOC_NR(cmd)]++; + thread->stats.bc[_IOC_NR(cmd)]++; + } + switch (cmd) { + case BC_INCREFS: + case BC_ACQUIRE: + case BC_RELEASE: + case BC_DECREFS: { + uint32_t target; + struct binder_ref *ref; + const char *debug_string; + + if (get_user(target, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + if (target == 0 && binder_context_mgr_node && + (cmd == BC_INCREFS || cmd == BC_ACQUIRE)) { + ref = binder_get_ref_for_node(proc, + binder_context_mgr_node); + if (ref->desc != target) { + binder_user_error("binder: %d:" + "%d tried to acquire " + "reference to desc 0, " + "got %d instead\n", + proc->pid, thread->pid, + ref->desc); + } + } else + ref = binder_get_ref(proc, target); + if (ref == NULL) { + binder_user_error("binder: %d:%d refcou" + "nt change on invalid ref %d\n", + proc->pid, thread->pid, target); + break; + } + switch (cmd) { + case BC_INCREFS: + debug_string = "IncRefs"; + binder_inc_ref(ref, 0, NULL); + break; + case BC_ACQUIRE: + debug_string = "Acquire"; + binder_inc_ref(ref, 1, NULL); + break; + case BC_RELEASE: + debug_string = "Release"; + binder_dec_ref(ref, 1); + break; + case BC_DECREFS: + default: + debug_string = "DecRefs"; + binder_dec_ref(ref, 0); + break; + } + binder_debug(BINDER_DEBUG_USER_REFS, + "binder: %d:%d %s ref %d desc %d s %d w %d for node %d\n", + proc->pid, thread->pid, debug_string, ref->debug_id, + ref->desc, ref->strong, ref->weak, ref->node->debug_id); + break; + } + case BC_INCREFS_DONE: + case BC_ACQUIRE_DONE: { + void __user *node_ptr; + void *cookie; + struct binder_node *node; + + if (get_user(node_ptr, (void * __user *)ptr)) + return -EFAULT; + ptr += sizeof(void *); + if (get_user(cookie, (void * __user *)ptr)) + return -EFAULT; + ptr += sizeof(void *); + node = binder_get_node(proc, node_ptr); + if (node == NULL) { + binder_user_error("binder: %d:%d " + "%s u%p no match\n", + proc->pid, thread->pid, + cmd == BC_INCREFS_DONE ? + "BC_INCREFS_DONE" : + "BC_ACQUIRE_DONE", + node_ptr); + break; + } + if (cookie != node->cookie) { + binder_user_error("binder: %d:%d %s u%p node %d" + " cookie mismatch %p != %p\n", + proc->pid, thread->pid, + cmd == BC_INCREFS_DONE ? + "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE", + node_ptr, node->debug_id, + cookie, node->cookie); + break; + } + if (cmd == BC_ACQUIRE_DONE) { + if (node->pending_strong_ref == 0) { + binder_user_error("binder: %d:%d " + "BC_ACQUIRE_DONE node %d has " + "no pending acquire request\n", + proc->pid, thread->pid, + node->debug_id); + break; + } + node->pending_strong_ref = 0; + } else { + if (node->pending_weak_ref == 0) { + binder_user_error("binder: %d:%d " + "BC_INCREFS_DONE node %d has " + "no pending increfs request\n", + proc->pid, thread->pid, + node->debug_id); + break; + } + node->pending_weak_ref = 0; + } + binder_dec_node(node, cmd == BC_ACQUIRE_DONE, 0); + binder_debug(BINDER_DEBUG_USER_REFS, + "binder: %d:%d %s node %d ls %d lw %d\n", + proc->pid, thread->pid, + cmd == BC_INCREFS_DONE ? "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE", + node->debug_id, node->local_strong_refs, node->local_weak_refs); + break; + } + case BC_ATTEMPT_ACQUIRE: + printk(KERN_ERR "binder: BC_ATTEMPT_ACQUIRE not supported\n"); + return -EINVAL; + case BC_ACQUIRE_RESULT: + printk(KERN_ERR "binder: BC_ACQUIRE_RESULT not supported\n"); + return -EINVAL; + + case BC_FREE_BUFFER: { + void __user *data_ptr; + struct binder_buffer *buffer; + + if (get_user(data_ptr, (void * __user *)ptr)) + return -EFAULT; + ptr += sizeof(void *); + + buffer = binder_buffer_lookup(proc, data_ptr); + if (buffer == NULL) { + binder_user_error("binder: %d:%d " + "BC_FREE_BUFFER u%p no match\n", + proc->pid, thread->pid, data_ptr); + break; + } + if (!buffer->allow_user_free) { + binder_user_error("binder: %d:%d " + "BC_FREE_BUFFER u%p matched " + "unreturned buffer\n", + proc->pid, thread->pid, data_ptr); + break; + } + binder_debug(BINDER_DEBUG_FREE_BUFFER, + "binder: %d:%d BC_FREE_BUFFER u%p found buffer %d for %s transaction\n", + proc->pid, thread->pid, data_ptr, buffer->debug_id, + buffer->transaction ? "active" : "finished"); + + if (buffer->transaction) { + buffer->transaction->buffer = NULL; + buffer->transaction = NULL; + } + if (buffer->async_transaction && buffer->target_node) { + BUG_ON(!buffer->target_node->has_async_transaction); + if (list_empty(&buffer->target_node->async_todo)) + buffer->target_node->has_async_transaction = 0; + else + list_move_tail(buffer->target_node->async_todo.next, &thread->todo); + } + trace_binder_transaction_buffer_release(buffer); + binder_transaction_buffer_release(proc, buffer, NULL); + binder_free_buf(proc, buffer); + break; + } + + case BC_TRANSACTION: + case BC_REPLY: { + struct binder_transaction_data tr; + + if (copy_from_user(&tr, ptr, sizeof(tr))) + return -EFAULT; + ptr += sizeof(tr); + binder_transaction(proc, thread, &tr, cmd == BC_REPLY); + break; + } + + case BC_REGISTER_LOOPER: + binder_debug(BINDER_DEBUG_THREADS, + "binder: %d:%d BC_REGISTER_LOOPER\n", + proc->pid, thread->pid); + if (thread->looper & BINDER_LOOPER_STATE_ENTERED) { + thread->looper |= BINDER_LOOPER_STATE_INVALID; + binder_user_error("binder: %d:%d ERROR:" + " BC_REGISTER_LOOPER called " + "after BC_ENTER_LOOPER\n", + proc->pid, thread->pid); + } else if (proc->requested_threads == 0) { + thread->looper |= BINDER_LOOPER_STATE_INVALID; + binder_user_error("binder: %d:%d ERROR:" + " BC_REGISTER_LOOPER called " + "without request\n", + proc->pid, thread->pid); + } else { + proc->requested_threads--; + proc->requested_threads_started++; + } + thread->looper |= BINDER_LOOPER_STATE_REGISTERED; + break; + case BC_ENTER_LOOPER: + binder_debug(BINDER_DEBUG_THREADS, + "binder: %d:%d BC_ENTER_LOOPER\n", + proc->pid, thread->pid); + if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) { + thread->looper |= BINDER_LOOPER_STATE_INVALID; + binder_user_error("binder: %d:%d ERROR:" + " BC_ENTER_LOOPER called after " + "BC_REGISTER_LOOPER\n", + proc->pid, thread->pid); + } + thread->looper |= BINDER_LOOPER_STATE_ENTERED; + break; + case BC_EXIT_LOOPER: + binder_debug(BINDER_DEBUG_THREADS, + "binder: %d:%d BC_EXIT_LOOPER\n", + proc->pid, thread->pid); + thread->looper |= BINDER_LOOPER_STATE_EXITED; + break; + + case BC_REQUEST_DEATH_NOTIFICATION: + case BC_CLEAR_DEATH_NOTIFICATION: { + uint32_t target; + void __user *cookie; + struct binder_ref *ref; + struct binder_ref_death *death; + + if (get_user(target, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + if (get_user(cookie, (void __user * __user *)ptr)) + return -EFAULT; + ptr += sizeof(void *); + ref = binder_get_ref(proc, target); + if (ref == NULL) { + binder_user_error("binder: %d:%d %s " + "invalid ref %d\n", + proc->pid, thread->pid, + cmd == BC_REQUEST_DEATH_NOTIFICATION ? + "BC_REQUEST_DEATH_NOTIFICATION" : + "BC_CLEAR_DEATH_NOTIFICATION", + target); + break; + } + + binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION, + "binder: %d:%d %s %p ref %d desc %d s %d w %d for node %d\n", + proc->pid, thread->pid, + cmd == BC_REQUEST_DEATH_NOTIFICATION ? + "BC_REQUEST_DEATH_NOTIFICATION" : + "BC_CLEAR_DEATH_NOTIFICATION", + cookie, ref->debug_id, ref->desc, + ref->strong, ref->weak, ref->node->debug_id); + + if (cmd == BC_REQUEST_DEATH_NOTIFICATION) { + if (ref->death) { + binder_user_error("binder: %d:%" + "d BC_REQUEST_DEATH_NOTI" + "FICATION death notific" + "ation already set\n", + proc->pid, thread->pid); + break; + } + death = kzalloc(sizeof(*death), GFP_KERNEL); + if (death == NULL) { + thread->return_error = BR_ERROR; + binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, + "binder: %d:%d " + "BC_REQUEST_DEATH_NOTIFICATION failed\n", + proc->pid, thread->pid); + break; + } + binder_stats_created(BINDER_STAT_DEATH); + INIT_LIST_HEAD(&death->work.entry); + death->cookie = cookie; + ref->death = death; + if (ref->node->proc == NULL) { + ref->death->work.type = BINDER_WORK_DEAD_BINDER; + if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) { + list_add_tail(&ref->death->work.entry, &thread->todo); + } else { + list_add_tail(&ref->death->work.entry, &proc->todo); + wake_up_interruptible(&proc->wait); + } + } + } else { + if (ref->death == NULL) { + binder_user_error("binder: %d:%" + "d BC_CLEAR_DEATH_NOTIFI" + "CATION death notificat" + "ion not active\n", + proc->pid, thread->pid); + break; + } + death = ref->death; + if (death->cookie != cookie) { + binder_user_error("binder: %d:%" + "d BC_CLEAR_DEATH_NOTIFI" + "CATION death notificat" + "ion cookie mismatch " + "%p != %p\n", + proc->pid, thread->pid, + death->cookie, cookie); + break; + } + ref->death = NULL; + if (list_empty(&death->work.entry)) { + death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION; + if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) { + list_add_tail(&death->work.entry, &thread->todo); + } else { + list_add_tail(&death->work.entry, &proc->todo); + wake_up_interruptible(&proc->wait); + } + } else { + BUG_ON(death->work.type != BINDER_WORK_DEAD_BINDER); + death->work.type = BINDER_WORK_DEAD_BINDER_AND_CLEAR; + } + } + } break; + case BC_DEAD_BINDER_DONE: { + struct binder_work *w; + void __user *cookie; + struct binder_ref_death *death = NULL; + if (get_user(cookie, (void __user * __user *)ptr)) + return -EFAULT; + + ptr += sizeof(void *); + list_for_each_entry(w, &proc->delivered_death, entry) { + struct binder_ref_death *tmp_death = container_of(w, struct binder_ref_death, work); + if (tmp_death->cookie == cookie) { + death = tmp_death; + break; + } + } + binder_debug(BINDER_DEBUG_DEAD_BINDER, + "binder: %d:%d BC_DEAD_BINDER_DONE %p found %p\n", + proc->pid, thread->pid, cookie, death); + if (death == NULL) { + binder_user_error("binder: %d:%d BC_DEAD" + "_BINDER_DONE %p not found\n", + proc->pid, thread->pid, cookie); + break; + } + + list_del_init(&death->work.entry); + if (death->work.type == BINDER_WORK_DEAD_BINDER_AND_CLEAR) { + death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION; + if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) { + list_add_tail(&death->work.entry, &thread->todo); + } else { + list_add_tail(&death->work.entry, &proc->todo); + wake_up_interruptible(&proc->wait); + } + } + } break; + + default: + printk(KERN_ERR "binder: %d:%d unknown command %d\n", + proc->pid, thread->pid, cmd); + return -EINVAL; + } + *consumed = ptr - buffer; + } + return 0; +} + +void binder_stat_br(struct binder_proc *proc, struct binder_thread *thread, + uint32_t cmd) +{ + trace_binder_return(cmd); + if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.br)) { + binder_stats.br[_IOC_NR(cmd)]++; + proc->stats.br[_IOC_NR(cmd)]++; + thread->stats.br[_IOC_NR(cmd)]++; + } +} + +static int binder_has_proc_work(struct binder_proc *proc, + struct binder_thread *thread) +{ + return !list_empty(&proc->todo) || + (thread->looper & BINDER_LOOPER_STATE_NEED_RETURN); +} + +static int binder_has_thread_work(struct binder_thread *thread) +{ + return !list_empty(&thread->todo) || thread->return_error != BR_OK || + (thread->looper & BINDER_LOOPER_STATE_NEED_RETURN); +} + +static int binder_thread_read(struct binder_proc *proc, + struct binder_thread *thread, + void __user *buffer, int size, + signed long *consumed, int non_block) +{ + void __user *ptr = buffer + *consumed; + void __user *end = buffer + size; + + int ret = 0; + int wait_for_proc_work; + + if (*consumed == 0) { + if (put_user(BR_NOOP, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + } + +retry: + wait_for_proc_work = thread->transaction_stack == NULL && + list_empty(&thread->todo); + + if (thread->return_error != BR_OK && ptr < end) { + if (thread->return_error2 != BR_OK) { + if (put_user(thread->return_error2, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + binder_stat_br(proc, thread, thread->return_error2); + if (ptr == end) + goto done; + thread->return_error2 = BR_OK; + } + if (put_user(thread->return_error, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + binder_stat_br(proc, thread, thread->return_error); + thread->return_error = BR_OK; + goto done; + } + + + thread->looper |= BINDER_LOOPER_STATE_WAITING; + if (wait_for_proc_work) + proc->ready_threads++; + + binder_unlock(__func__); + + trace_binder_wait_for_work(wait_for_proc_work, + !!thread->transaction_stack, + !list_empty(&thread->todo)); + if (wait_for_proc_work) { + if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED | + BINDER_LOOPER_STATE_ENTERED))) { + binder_user_error("binder: %d:%d ERROR: Thread waiting " + "for process work before calling BC_REGISTER_" + "LOOPER or BC_ENTER_LOOPER (state %x)\n", + proc->pid, thread->pid, thread->looper); + wait_event_interruptible(binder_user_error_wait, + binder_stop_on_user_error < 2); + } + binder_set_nice(proc->default_priority); + if (non_block) { + if (!binder_has_proc_work(proc, thread)) + ret = -EAGAIN; + } else + ret = wait_event_interruptible_exclusive(proc->wait, binder_has_proc_work(proc, thread)); + } else { + if (non_block) { + if (!binder_has_thread_work(thread)) + ret = -EAGAIN; + } else + ret = wait_event_interruptible(thread->wait, binder_has_thread_work(thread)); + } + + binder_lock(__func__); + + if (wait_for_proc_work) + proc->ready_threads--; + thread->looper &= ~BINDER_LOOPER_STATE_WAITING; + + if (ret) + return ret; + + while (1) { + uint32_t cmd; + struct binder_transaction_data tr; + struct binder_work *w; + struct binder_transaction *t = NULL; + + if (!list_empty(&thread->todo)) + w = list_first_entry(&thread->todo, struct binder_work, entry); + else if (!list_empty(&proc->todo) && wait_for_proc_work) + w = list_first_entry(&proc->todo, struct binder_work, entry); + else { + if (ptr - buffer == 4 && !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN)) /* no data added */ + goto retry; + break; + } + + if (end - ptr < sizeof(tr) + 4) + break; + + switch (w->type) { + case BINDER_WORK_TRANSACTION: { + t = container_of(w, struct binder_transaction, work); + } break; + case BINDER_WORK_TRANSACTION_COMPLETE: { + cmd = BR_TRANSACTION_COMPLETE; + if (put_user(cmd, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + + binder_stat_br(proc, thread, cmd); + binder_debug(BINDER_DEBUG_TRANSACTION_COMPLETE, + "binder: %d:%d BR_TRANSACTION_COMPLETE\n", + proc->pid, thread->pid); + + list_del(&w->entry); + kfree(w); + binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE); + } break; + case BINDER_WORK_NODE: { + struct binder_node *node = container_of(w, struct binder_node, work); + uint32_t cmd = BR_NOOP; + const char *cmd_name; + int strong = node->internal_strong_refs || node->local_strong_refs; + int weak = !hlist_empty(&node->refs) || node->local_weak_refs || strong; + if (weak && !node->has_weak_ref) { + cmd = BR_INCREFS; + cmd_name = "BR_INCREFS"; + node->has_weak_ref = 1; + node->pending_weak_ref = 1; + node->local_weak_refs++; + } else if (strong && !node->has_strong_ref) { + cmd = BR_ACQUIRE; + cmd_name = "BR_ACQUIRE"; + node->has_strong_ref = 1; + node->pending_strong_ref = 1; + node->local_strong_refs++; + } else if (!strong && node->has_strong_ref) { + cmd = BR_RELEASE; + cmd_name = "BR_RELEASE"; + node->has_strong_ref = 0; + } else if (!weak && node->has_weak_ref) { + cmd = BR_DECREFS; + cmd_name = "BR_DECREFS"; + node->has_weak_ref = 0; + } + if (cmd != BR_NOOP) { + if (put_user(cmd, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + if (put_user(node->ptr, (void * __user *)ptr)) + return -EFAULT; + ptr += sizeof(void *); + if (put_user(node->cookie, (void * __user *)ptr)) + return -EFAULT; + ptr += sizeof(void *); + + binder_stat_br(proc, thread, cmd); + binder_debug(BINDER_DEBUG_USER_REFS, + "binder: %d:%d %s %d u%p c%p\n", + proc->pid, thread->pid, cmd_name, node->debug_id, node->ptr, node->cookie); + } else { + list_del_init(&w->entry); + if (!weak && !strong) { + binder_debug(BINDER_DEBUG_INTERNAL_REFS, + "binder: %d:%d node %d u%p c%p deleted\n", + proc->pid, thread->pid, node->debug_id, + node->ptr, node->cookie); + rb_erase(&node->rb_node, &proc->nodes); + kfree(node); + binder_stats_deleted(BINDER_STAT_NODE); + } else { + binder_debug(BINDER_DEBUG_INTERNAL_REFS, + "binder: %d:%d node %d u%p c%p state unchanged\n", + proc->pid, thread->pid, node->debug_id, node->ptr, + node->cookie); + } + } + } break; + case BINDER_WORK_DEAD_BINDER: + case BINDER_WORK_DEAD_BINDER_AND_CLEAR: + case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: { + struct binder_ref_death *death; + uint32_t cmd; + + death = container_of(w, struct binder_ref_death, work); + if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) + cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE; + else + cmd = BR_DEAD_BINDER; + if (put_user(cmd, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + if (put_user(death->cookie, (void * __user *)ptr)) + return -EFAULT; + ptr += sizeof(void *); + binder_stat_br(proc, thread, cmd); + binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION, + "binder: %d:%d %s %p\n", + proc->pid, thread->pid, + cmd == BR_DEAD_BINDER ? + "BR_DEAD_BINDER" : + "BR_CLEAR_DEATH_NOTIFICATION_DONE", + death->cookie); + + if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) { + list_del(&w->entry); + kfree(death); + binder_stats_deleted(BINDER_STAT_DEATH); + } else + list_move(&w->entry, &proc->delivered_death); + if (cmd == BR_DEAD_BINDER) + goto done; /* DEAD_BINDER notifications can cause transactions */ + } break; + } + + if (!t) + continue; + + BUG_ON(t->buffer == NULL); + if (t->buffer->target_node) { + struct binder_node *target_node = t->buffer->target_node; + tr.target.ptr = target_node->ptr; + tr.cookie = target_node->cookie; + t->saved_priority = task_nice(current); + if (t->priority < target_node->min_priority && + !(t->flags & TF_ONE_WAY)) + binder_set_nice(t->priority); + else if (!(t->flags & TF_ONE_WAY) || + t->saved_priority > target_node->min_priority) + binder_set_nice(target_node->min_priority); + cmd = BR_TRANSACTION; + } else { + tr.target.ptr = NULL; + tr.cookie = NULL; + cmd = BR_REPLY; + } + tr.code = t->code; + tr.flags = t->flags; + tr.sender_euid = t->sender_euid; + + if (t->from) { + struct task_struct *sender = t->from->proc->tsk; + tr.sender_pid = task_tgid_nr_ns(sender, + current->nsproxy->pid_ns); + } else { + tr.sender_pid = 0; + } + + tr.data_size = t->buffer->data_size; + tr.offsets_size = t->buffer->offsets_size; + tr.data.ptr.buffer = (void *)t->buffer->data + + proc->user_buffer_offset; + tr.data.ptr.offsets = tr.data.ptr.buffer + + ALIGN(t->buffer->data_size, + sizeof(void *)); + + if (put_user(cmd, (uint32_t __user *)ptr)) + return -EFAULT; + ptr += sizeof(uint32_t); + if (copy_to_user(ptr, &tr, sizeof(tr))) + return -EFAULT; + ptr += sizeof(tr); + + trace_binder_transaction_received(t); + binder_stat_br(proc, thread, cmd); + binder_debug(BINDER_DEBUG_TRANSACTION, + "binder: %d:%d %s %d %d:%d, cmd %d" + "size %zd-%zd ptr %p-%p\n", + proc->pid, thread->pid, + (cmd == BR_TRANSACTION) ? "BR_TRANSACTION" : + "BR_REPLY", + t->debug_id, t->from ? t->from->proc->pid : 0, + t->from ? t->from->pid : 0, cmd, + t->buffer->data_size, t->buffer->offsets_size, + tr.data.ptr.buffer, tr.data.ptr.offsets); + + list_del(&t->work.entry); + t->buffer->allow_user_free = 1; + if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) { + t->to_parent = thread->transaction_stack; + t->to_thread = thread; + thread->transaction_stack = t; + } else { + t->buffer->transaction = NULL; + kfree(t); + binder_stats_deleted(BINDER_STAT_TRANSACTION); + } + break; + } + +done: + + *consumed = ptr - buffer; + if (proc->requested_threads + proc->ready_threads == 0 && + proc->requested_threads_started < proc->max_threads && + (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | + BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */ + /*spawn a new thread if we leave this out */) { + proc->requested_threads++; + binder_debug(BINDER_DEBUG_THREADS, + "binder: %d:%d BR_SPAWN_LOOPER\n", + proc->pid, thread->pid); + if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer)) + return -EFAULT; + binder_stat_br(proc, thread, BR_SPAWN_LOOPER); + } + return 0; +} + +static void binder_release_work(struct list_head *list) +{ + struct binder_work *w; + while (!list_empty(list)) { + w = list_first_entry(list, struct binder_work, entry); + list_del_init(&w->entry); + switch (w->type) { + case BINDER_WORK_TRANSACTION: { + struct binder_transaction *t; + + t = container_of(w, struct binder_transaction, work); + if (t->buffer->target_node && !(t->flags & TF_ONE_WAY)) + binder_send_failed_reply(t, BR_DEAD_REPLY); + } break; + case BINDER_WORK_TRANSACTION_COMPLETE: { + kfree(w); + binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE); + } break; + default: + break; + } + } + +} + +static struct binder_thread *binder_get_thread(struct binder_proc *proc) +{ + struct binder_thread *thread = NULL; + struct rb_node *parent = NULL; + struct rb_node **p = &proc->threads.rb_node; + + while (*p) { + parent = *p; + thread = rb_entry(parent, struct binder_thread, rb_node); + + if (current->pid < thread->pid) + p = &(*p)->rb_left; + else if (current->pid > thread->pid) + p = &(*p)->rb_right; + else + break; + } + if (*p == NULL) { + thread = kzalloc(sizeof(*thread), GFP_KERNEL); + if (thread == NULL) + return NULL; + binder_stats_created(BINDER_STAT_THREAD); + thread->proc = proc; + thread->pid = current->pid; + init_waitqueue_head(&thread->wait); + INIT_LIST_HEAD(&thread->todo); + rb_link_node(&thread->rb_node, parent, p); + rb_insert_color(&thread->rb_node, &proc->threads); + thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN; + thread->return_error = BR_OK; + thread->return_error2 = BR_OK; + } + return thread; +} + +static int binder_free_thread(struct binder_proc *proc, + struct binder_thread *thread) +{ + struct binder_transaction *t; + struct binder_transaction *send_reply = NULL; + int active_transactions = 0; + + rb_erase(&thread->rb_node, &proc->threads); + t = thread->transaction_stack; + if (t && t->to_thread == thread) + send_reply = t; + while (t) { + active_transactions++; + binder_debug(BINDER_DEBUG_DEAD_TRANSACTION, + "binder: release %d:%d transaction %d " + "%s, still active\n", proc->pid, thread->pid, + t->debug_id, + (t->to_thread == thread) ? "in" : "out"); + + if (t->to_thread == thread) { + t->to_proc = NULL; + t->to_thread = NULL; + if (t->buffer) { + t->buffer->transaction = NULL; + t->buffer = NULL; + } + t = t->to_parent; + } else if (t->from == thread) { + t->from = NULL; + t = t->from_parent; + } else + BUG(); + } + if (send_reply) + binder_send_failed_reply(send_reply, BR_DEAD_REPLY); + binder_release_work(&thread->todo); + kfree(thread); + binder_stats_deleted(BINDER_STAT_THREAD); + return active_transactions; +} + +static unsigned int binder_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct binder_proc *proc = filp->private_data; + struct binder_thread *thread = NULL; + int wait_for_proc_work; + + binder_lock(__func__); + + thread = binder_get_thread(proc); + + wait_for_proc_work = thread->transaction_stack == NULL && + list_empty(&thread->todo) && thread->return_error == BR_OK; + + binder_unlock(__func__); + + if (wait_for_proc_work) { + if (binder_has_proc_work(proc, thread)) + return POLLIN; + poll_wait(filp, &proc->wait, wait); + if (binder_has_proc_work(proc, thread)) + return POLLIN; + } else { + if (binder_has_thread_work(thread)) + return POLLIN; + poll_wait(filp, &thread->wait, wait); + if (binder_has_thread_work(thread)) + return POLLIN; + } + return 0; +} + +static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int ret; + struct binder_proc *proc = filp->private_data; + struct binder_thread *thread; + unsigned int size = _IOC_SIZE(cmd); + void __user *ubuf = (void __user *)arg; + + /*printk(KERN_INFO "binder_ioctl: %d:%d %x %lx\n", proc->pid, current->pid, cmd, arg);*/ + + trace_binder_ioctl(cmd, arg); + + ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); + if (ret) + goto err_unlocked; + + binder_lock(__func__); + thread = binder_get_thread(proc); + if (thread == NULL) { + ret = -ENOMEM; + goto err; + } + + switch (cmd) { + case BINDER_WRITE_READ: { + struct binder_write_read bwr; + if (size != sizeof(struct binder_write_read)) { + ret = -EINVAL; + goto err; + } + if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { + ret = -EFAULT; + goto err; + } + binder_debug(BINDER_DEBUG_READ_WRITE, + "binder: %d:%d write %ld at %08lx, read %ld at %08lx\n", + proc->pid, thread->pid, bwr.write_size, bwr.write_buffer, + bwr.read_size, bwr.read_buffer); + + if (bwr.write_size > 0) { + ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed); + trace_binder_write_done(ret); + if (ret < 0) { + bwr.read_consumed = 0; + if (copy_to_user(ubuf, &bwr, sizeof(bwr))) + ret = -EFAULT; + goto err; + } + } + if (bwr.read_size > 0) { + ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK); + trace_binder_read_done(ret); + if (!list_empty(&proc->todo)) + wake_up_interruptible(&proc->wait); + if (ret < 0) { + if (copy_to_user(ubuf, &bwr, sizeof(bwr))) + ret = -EFAULT; + goto err; + } + } + binder_debug(BINDER_DEBUG_READ_WRITE, + "binder: %d:%d wrote %ld of %ld, read return %ld of %ld\n", + proc->pid, thread->pid, bwr.write_consumed, bwr.write_size, + bwr.read_consumed, bwr.read_size); + if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { + ret = -EFAULT; + goto err; + } + break; + } + case BINDER_SET_MAX_THREADS: + if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) { + ret = -EINVAL; + goto err; + } + break; + case BINDER_SET_CONTEXT_MGR: + if (binder_context_mgr_node != NULL) { + printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n"); + ret = -EBUSY; + goto err; + } + if (binder_context_mgr_uid != -1) { + if (binder_context_mgr_uid != current->cred->euid) { + printk(KERN_ERR "binder: BINDER_SET_" + "CONTEXT_MGR bad uid %d != %d\n", + current->cred->euid, + binder_context_mgr_uid); + ret = -EPERM; + goto err; + } + } else + binder_context_mgr_uid = current->cred->euid; + binder_context_mgr_node = binder_new_node(proc, NULL, NULL); + if (binder_context_mgr_node == NULL) { + ret = -ENOMEM; + goto err; + } + binder_context_mgr_node->local_weak_refs++; + binder_context_mgr_node->local_strong_refs++; + binder_context_mgr_node->has_strong_ref = 1; + binder_context_mgr_node->has_weak_ref = 1; + break; + case BINDER_THREAD_EXIT: + binder_debug(BINDER_DEBUG_THREADS, "binder: %d:%d exit\n", + proc->pid, thread->pid); + binder_free_thread(proc, thread); + thread = NULL; + break; + case BINDER_VERSION: + if (size != sizeof(struct binder_version)) { + ret = -EINVAL; + goto err; + } + if (put_user(BINDER_CURRENT_PROTOCOL_VERSION, &((struct binder_version *)ubuf)->protocol_version)) { + ret = -EINVAL; + goto err; + } + break; + default: + ret = -EINVAL; + goto err; + } + ret = 0; +err: + if (thread) + thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN; + binder_unlock(__func__); + wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); + if (ret && ret != -ERESTARTSYS) + printk(KERN_INFO "binder: %d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret); +err_unlocked: + trace_binder_ioctl_done(ret); + return ret; +} + +static void binder_vma_open(struct vm_area_struct *vma) +{ + struct binder_proc *proc = vma->vm_private_data; + binder_debug(BINDER_DEBUG_OPEN_CLOSE, + "binder: %d open vm area %lx-%lx (%ld K) vma %lx pagep %lx\n", + proc->pid, vma->vm_start, vma->vm_end, + (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags, + (unsigned long)pgprot_val(vma->vm_page_prot)); +} + +static void binder_vma_close(struct vm_area_struct *vma) +{ + struct binder_proc *proc = vma->vm_private_data; + binder_debug(BINDER_DEBUG_OPEN_CLOSE, + "binder: %d close vm area %lx-%lx (%ld K) vma %lx pagep %lx\n", + proc->pid, vma->vm_start, vma->vm_end, + (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags, + (unsigned long)pgprot_val(vma->vm_page_prot)); + proc->vma = NULL; + proc->vma_vm_mm = NULL; + binder_defer_work(proc, BINDER_DEFERRED_PUT_FILES); +} + +static struct vm_operations_struct binder_vm_ops = { + .open = binder_vma_open, + .close = binder_vma_close, +}; + +static int binder_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int ret; + struct vm_struct *area; + struct binder_proc *proc = filp->private_data; + const char *failure_string; + struct binder_buffer *buffer; + + if ((vma->vm_end - vma->vm_start) > SZ_4M) + vma->vm_end = vma->vm_start + SZ_4M; + + binder_debug(BINDER_DEBUG_OPEN_CLOSE, + "binder_mmap: %d %lx-%lx (%ld K) vma %lx pagep %lx\n", + proc->pid, vma->vm_start, vma->vm_end, + (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags, + (unsigned long)pgprot_val(vma->vm_page_prot)); + + if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) { + ret = -EPERM; + failure_string = "bad vm_flags"; + goto err_bad_arg; + } + vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE; + + mutex_lock(&binder_mmap_lock); + if (proc->buffer) { + ret = -EBUSY; + failure_string = "already mapped"; + goto err_already_mapped; + } + + area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP); + if (area == NULL) { + ret = -ENOMEM; + failure_string = "get_vm_area"; + goto err_get_vm_area_failed; + } + proc->buffer = area->addr; + proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer; + mutex_unlock(&binder_mmap_lock); + +#ifdef CONFIG_CPU_CACHE_VIPT + if (cache_is_vipt_aliasing()) { + while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) { + printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p bad alignment\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer); + vma->vm_start += PAGE_SIZE; + } + } +#endif + proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL); + if (proc->pages == NULL) { + ret = -ENOMEM; + failure_string = "alloc page array"; + goto err_alloc_pages_failed; + } + proc->buffer_size = vma->vm_end - vma->vm_start; + + vma->vm_ops = &binder_vm_ops; + vma->vm_private_data = proc; + + if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) { + ret = -ENOMEM; + failure_string = "alloc small buf"; + goto err_alloc_small_buf_failed; + } + buffer = proc->buffer; + INIT_LIST_HEAD(&proc->buffers); + list_add(&buffer->entry, &proc->buffers); + buffer->free = 1; + binder_insert_free_buffer(proc, buffer); + proc->free_async_space = proc->buffer_size / 2; + barrier(); + proc->files = get_files_struct(proc->tsk); + proc->vma = vma; + proc->vma_vm_mm = vma->vm_mm; + + /*printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p\n", + proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/ + return 0; + +err_alloc_small_buf_failed: + kfree(proc->pages); + proc->pages = NULL; +err_alloc_pages_failed: + mutex_lock(&binder_mmap_lock); + vfree(proc->buffer); + proc->buffer = NULL; +err_get_vm_area_failed: +err_already_mapped: + mutex_unlock(&binder_mmap_lock); +err_bad_arg: + printk(KERN_ERR "binder_mmap: %d %lx-%lx %s failed %d\n", + proc->pid, vma->vm_start, vma->vm_end, failure_string, ret); + return ret; +} + +static int binder_open(struct inode *nodp, struct file *filp) +{ + struct binder_proc *proc; + + binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_open: %d:%d\n", + current->group_leader->pid, current->pid); + + proc = kzalloc(sizeof(*proc), GFP_KERNEL); + if (proc == NULL) + return -ENOMEM; + get_task_struct(current); + proc->tsk = current; + INIT_LIST_HEAD(&proc->todo); + init_waitqueue_head(&proc->wait); + proc->default_priority = task_nice(current); + + binder_lock(__func__); + + binder_stats_created(BINDER_STAT_PROC); + hlist_add_head(&proc->proc_node, &binder_procs); + proc->pid = current->group_leader->pid; + INIT_LIST_HEAD(&proc->delivered_death); + filp->private_data = proc; + + binder_unlock(__func__); + + if (binder_debugfs_dir_entry_proc) { + char strbuf[11]; + snprintf(strbuf, sizeof(strbuf), "%u", proc->pid); + proc->debugfs_entry = debugfs_create_file(strbuf, S_IRUGO, + binder_debugfs_dir_entry_proc, proc, &binder_proc_fops); + } + + return 0; +} + +static int binder_flush(struct file *filp, fl_owner_t id) +{ + struct binder_proc *proc = filp->private_data; + + binder_defer_work(proc, BINDER_DEFERRED_FLUSH); + + return 0; +} + +static void binder_deferred_flush(struct binder_proc *proc) +{ + struct rb_node *n; + int wake_count = 0; + for (n = rb_first(&proc->threads); n != NULL; n = rb_next(n)) { + struct binder_thread *thread = rb_entry(n, struct binder_thread, rb_node); + thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN; + if (thread->looper & BINDER_LOOPER_STATE_WAITING) { + wake_up_interruptible(&thread->wait); + wake_count++; + } + } + wake_up_interruptible_all(&proc->wait); + + binder_debug(BINDER_DEBUG_OPEN_CLOSE, + "binder_flush: %d woke %d threads\n", proc->pid, + wake_count); +} + +static int binder_release(struct inode *nodp, struct file *filp) +{ + struct binder_proc *proc = filp->private_data; + debugfs_remove(proc->debugfs_entry); + binder_defer_work(proc, BINDER_DEFERRED_RELEASE); + + return 0; +} + +static void binder_deferred_release(struct binder_proc *proc) +{ + struct hlist_node *pos; + struct binder_transaction *t; + struct rb_node *n; + int threads, nodes, incoming_refs, outgoing_refs, buffers, active_transactions, page_count; + + BUG_ON(proc->vma); + BUG_ON(proc->files); + + hlist_del(&proc->proc_node); + if (binder_context_mgr_node && binder_context_mgr_node->proc == proc) { + binder_debug(BINDER_DEBUG_DEAD_BINDER, + "binder_release: %d context_mgr_node gone\n", + proc->pid); + binder_context_mgr_node = NULL; + } + + threads = 0; + active_transactions = 0; + while ((n = rb_first(&proc->threads))) { + struct binder_thread *thread = rb_entry(n, struct binder_thread, rb_node); + threads++; + active_transactions += binder_free_thread(proc, thread); + } + nodes = 0; + incoming_refs = 0; + while ((n = rb_first(&proc->nodes))) { + struct binder_node *node = rb_entry(n, struct binder_node, rb_node); + + nodes++; + rb_erase(&node->rb_node, &proc->nodes); + list_del_init(&node->work.entry); + if (hlist_empty(&node->refs)) { + kfree(node); + binder_stats_deleted(BINDER_STAT_NODE); + } else { + struct binder_ref *ref; + int death = 0; + + node->proc = NULL; + node->local_strong_refs = 0; + node->local_weak_refs = 0; + hlist_add_head(&node->dead_node, &binder_dead_nodes); + + hlist_for_each_entry(ref, pos, &node->refs, node_entry) { + incoming_refs++; + if (ref->death) { + death++; + if (list_empty(&ref->death->work.entry)) { + ref->death->work.type = BINDER_WORK_DEAD_BINDER; + list_add_tail(&ref->death->work.entry, &ref->proc->todo); + wake_up_interruptible(&ref->proc->wait); + } else + BUG(); + } + } + binder_debug(BINDER_DEBUG_DEAD_BINDER, + "binder: node %d now dead, " + "refs %d, death %d\n", node->debug_id, + incoming_refs, death); + } + } + outgoing_refs = 0; + while ((n = rb_first(&proc->refs_by_desc))) { + struct binder_ref *ref = rb_entry(n, struct binder_ref, + rb_node_desc); + outgoing_refs++; + binder_delete_ref(ref); + } + binder_release_work(&proc->todo); + buffers = 0; + + while ((n = rb_first(&proc->allocated_buffers))) { + struct binder_buffer *buffer = rb_entry(n, struct binder_buffer, + rb_node); + t = buffer->transaction; + if (t) { + t->buffer = NULL; + buffer->transaction = NULL; + printk(KERN_ERR "binder: release proc %d, " + "transaction %d, not freed\n", + proc->pid, t->debug_id); + /*BUG();*/ + } + binder_free_buf(proc, buffer); + buffers++; + } + + binder_stats_deleted(BINDER_STAT_PROC); + + page_count = 0; + if (proc->pages) { + int i; + for (i = 0; i < proc->buffer_size / PAGE_SIZE; i++) { + if (proc->pages[i]) { + void *page_addr = proc->buffer + i * PAGE_SIZE; + binder_debug(BINDER_DEBUG_BUFFER_ALLOC, + "binder_release: %d: " + "page %d at %p not freed\n", + proc->pid, i, + page_addr); + unmap_kernel_range((unsigned long)page_addr, + PAGE_SIZE); + __free_page(proc->pages[i]); + page_count++; + } + } + kfree(proc->pages); + vfree(proc->buffer); + } + + put_task_struct(proc->tsk); + + binder_debug(BINDER_DEBUG_OPEN_CLOSE, + "binder_release: %d threads %d, nodes %d (ref %d), " + "refs %d, active transactions %d, buffers %d, " + "pages %d\n", + proc->pid, threads, nodes, incoming_refs, outgoing_refs, + active_transactions, buffers, page_count); + + kfree(proc); +} + +static void binder_deferred_func(struct work_struct *work) +{ + struct binder_proc *proc; + struct files_struct *files; + + int defer; + do { + binder_lock(__func__); + mutex_lock(&binder_deferred_lock); + if (!hlist_empty(&binder_deferred_list)) { + proc = hlist_entry(binder_deferred_list.first, + struct binder_proc, deferred_work_node); + hlist_del_init(&proc->deferred_work_node); + defer = proc->deferred_work; + proc->deferred_work = 0; + } else { + proc = NULL; + defer = 0; + } + mutex_unlock(&binder_deferred_lock); + + files = NULL; + if (defer & BINDER_DEFERRED_PUT_FILES) { + files = proc->files; + if (files) + proc->files = NULL; + } + + if (defer & BINDER_DEFERRED_FLUSH) + binder_deferred_flush(proc); + + if (defer & BINDER_DEFERRED_RELEASE) + binder_deferred_release(proc); /* frees proc */ + + binder_unlock(__func__); + if (files) + put_files_struct(files); + } while (proc); +} +static DECLARE_WORK(binder_deferred_work, binder_deferred_func); + +static void +binder_defer_work(struct binder_proc *proc, enum binder_deferred_state defer) +{ + mutex_lock(&binder_deferred_lock); + proc->deferred_work |= defer; + if (hlist_unhashed(&proc->deferred_work_node)) { + hlist_add_head(&proc->deferred_work_node, + &binder_deferred_list); + queue_work(binder_deferred_workqueue, &binder_deferred_work); + } + mutex_unlock(&binder_deferred_lock); +} + +static void print_binder_transaction(struct seq_file *m, const char *prefix, + struct binder_transaction *t) +{ + seq_printf(m, + "%s %d: %p from %d:%d to %d:%d code %x flags %x pri %ld r%d", + prefix, t->debug_id, t, + t->from ? t->from->proc->pid : 0, + t->from ? t->from->pid : 0, + t->to_proc ? t->to_proc->pid : 0, + t->to_thread ? t->to_thread->pid : 0, + t->code, t->flags, t->priority, t->need_reply); + if (t->buffer == NULL) { + seq_puts(m, " buffer free\n"); + return; + } + if (t->buffer->target_node) + seq_printf(m, " node %d", + t->buffer->target_node->debug_id); + seq_printf(m, " size %zd:%zd data %p\n", + t->buffer->data_size, t->buffer->offsets_size, + t->buffer->data); +} + +static void print_binder_buffer(struct seq_file *m, const char *prefix, + struct binder_buffer *buffer) +{ + seq_printf(m, "%s %d: %p size %zd:%zd %s\n", + prefix, buffer->debug_id, buffer->data, + buffer->data_size, buffer->offsets_size, + buffer->transaction ? "active" : "delivered"); +} + +static void print_binder_work(struct seq_file *m, const char *prefix, + const char *transaction_prefix, + struct binder_work *w) +{ + struct binder_node *node; + struct binder_transaction *t; + + switch (w->type) { + case BINDER_WORK_TRANSACTION: + t = container_of(w, struct binder_transaction, work); + print_binder_transaction(m, transaction_prefix, t); + break; + case BINDER_WORK_TRANSACTION_COMPLETE: + seq_printf(m, "%stransaction complete\n", prefix); + break; + case BINDER_WORK_NODE: + node = container_of(w, struct binder_node, work); + seq_printf(m, "%snode work %d: u%p c%p\n", + prefix, node->debug_id, node->ptr, node->cookie); + break; + case BINDER_WORK_DEAD_BINDER: + seq_printf(m, "%shas dead binder\n", prefix); + break; + case BINDER_WORK_DEAD_BINDER_AND_CLEAR: + seq_printf(m, "%shas cleared dead binder\n", prefix); + break; + case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: + seq_printf(m, "%shas cleared death notification\n", prefix); + break; + default: + seq_printf(m, "%sunknown work: type %d\n", prefix, w->type); + break; + } +} + +static void print_binder_thread(struct seq_file *m, + struct binder_thread *thread, + int print_always) +{ + struct binder_transaction *t; + struct binder_work *w; + size_t start_pos = m->count; + size_t header_pos; + + seq_printf(m, " thread %d: l %02x\n", thread->pid, thread->looper); + header_pos = m->count; + t = thread->transaction_stack; + while (t) { + if (t->from == thread) { + print_binder_transaction(m, + " outgoing transaction", t); + t = t->from_parent; + } else if (t->to_thread == thread) { + print_binder_transaction(m, + " incoming transaction", t); + t = t->to_parent; + } else { + print_binder_transaction(m, " bad transaction", t); + t = NULL; + } + } + list_for_each_entry(w, &thread->todo, entry) { + print_binder_work(m, " ", " pending transaction", w); + } + if (!print_always && m->count == header_pos) + m->count = start_pos; +} + +static void print_binder_node(struct seq_file *m, struct binder_node *node) +{ + struct binder_ref *ref; + struct hlist_node *pos; + struct binder_work *w; + int count; + + count = 0; + hlist_for_each_entry(ref, pos, &node->refs, node_entry) + count++; + + seq_printf(m, " node %d: u%p c%p hs %d hw %d ls %d lw %d is %d iw %d", + node->debug_id, node->ptr, node->cookie, + node->has_strong_ref, node->has_weak_ref, + node->local_strong_refs, node->local_weak_refs, + node->internal_strong_refs, count); + if (count) { + seq_puts(m, " proc"); + hlist_for_each_entry(ref, pos, &node->refs, node_entry) + seq_printf(m, " %d", ref->proc->pid); + } + seq_puts(m, "\n"); + list_for_each_entry(w, &node->async_todo, entry) + print_binder_work(m, " ", + " pending async transaction", w); +} + +static void print_binder_ref(struct seq_file *m, struct binder_ref *ref) +{ + seq_printf(m, " ref %d: desc %d %snode %d s %d w %d d %p\n", + ref->debug_id, ref->desc, ref->node->proc ? "" : "dead ", + ref->node->debug_id, ref->strong, ref->weak, ref->death); +} + +static void print_binder_proc(struct seq_file *m, + struct binder_proc *proc, int print_all) +{ + struct binder_work *w; + struct rb_node *n; + size_t start_pos = m->count; + size_t header_pos; + + seq_printf(m, "proc %d\n", proc->pid); + header_pos = m->count; + + for (n = rb_first(&proc->threads); n != NULL; n = rb_next(n)) + print_binder_thread(m, rb_entry(n, struct binder_thread, + rb_node), print_all); + for (n = rb_first(&proc->nodes); n != NULL; n = rb_next(n)) { + struct binder_node *node = rb_entry(n, struct binder_node, + rb_node); + if (print_all || node->has_async_transaction) + print_binder_node(m, node); + } + if (print_all) { + for (n = rb_first(&proc->refs_by_desc); + n != NULL; + n = rb_next(n)) + print_binder_ref(m, rb_entry(n, struct binder_ref, + rb_node_desc)); + } + for (n = rb_first(&proc->allocated_buffers); n != NULL; n = rb_next(n)) + print_binder_buffer(m, " buffer", + rb_entry(n, struct binder_buffer, rb_node)); + list_for_each_entry(w, &proc->todo, entry) + print_binder_work(m, " ", " pending transaction", w); + list_for_each_entry(w, &proc->delivered_death, entry) { + seq_puts(m, " has delivered dead binder\n"); + break; + } + if (!print_all && m->count == header_pos) + m->count = start_pos; +} + +static const char *binder_return_strings[] = { + "BR_ERROR", + "BR_OK", + "BR_TRANSACTION", + "BR_REPLY", + "BR_ACQUIRE_RESULT", + "BR_DEAD_REPLY", + "BR_TRANSACTION_COMPLETE", + "BR_INCREFS", + "BR_ACQUIRE", + "BR_RELEASE", + "BR_DECREFS", + "BR_ATTEMPT_ACQUIRE", + "BR_NOOP", + "BR_SPAWN_LOOPER", + "BR_FINISHED", + "BR_DEAD_BINDER", + "BR_CLEAR_DEATH_NOTIFICATION_DONE", + "BR_FAILED_REPLY" +}; + +static const char *binder_command_strings[] = { + "BC_TRANSACTION", + "BC_REPLY", + "BC_ACQUIRE_RESULT", + "BC_FREE_BUFFER", + "BC_INCREFS", + "BC_ACQUIRE", + "BC_RELEASE", + "BC_DECREFS", + "BC_INCREFS_DONE", + "BC_ACQUIRE_DONE", + "BC_ATTEMPT_ACQUIRE", + "BC_REGISTER_LOOPER", + "BC_ENTER_LOOPER", + "BC_EXIT_LOOPER", + "BC_REQUEST_DEATH_NOTIFICATION", + "BC_CLEAR_DEATH_NOTIFICATION", + "BC_DEAD_BINDER_DONE" +}; + +static const char *binder_objstat_strings[] = { + "proc", + "thread", + "node", + "ref", + "death", + "transaction", + "transaction_complete" +}; + +static void print_binder_stats(struct seq_file *m, const char *prefix, + struct binder_stats *stats) +{ + int i; + + BUILD_BUG_ON(ARRAY_SIZE(stats->bc) != + ARRAY_SIZE(binder_command_strings)); + for (i = 0; i < ARRAY_SIZE(stats->bc); i++) { + if (stats->bc[i]) + seq_printf(m, "%s%s: %d\n", prefix, + binder_command_strings[i], stats->bc[i]); + } + + BUILD_BUG_ON(ARRAY_SIZE(stats->br) != + ARRAY_SIZE(binder_return_strings)); + for (i = 0; i < ARRAY_SIZE(stats->br); i++) { + if (stats->br[i]) + seq_printf(m, "%s%s: %d\n", prefix, + binder_return_strings[i], stats->br[i]); + } + + BUILD_BUG_ON(ARRAY_SIZE(stats->obj_created) != + ARRAY_SIZE(binder_objstat_strings)); + BUILD_BUG_ON(ARRAY_SIZE(stats->obj_created) != + ARRAY_SIZE(stats->obj_deleted)); + for (i = 0; i < ARRAY_SIZE(stats->obj_created); i++) { + if (stats->obj_created[i] || stats->obj_deleted[i]) + seq_printf(m, "%s%s: active %d total %d\n", prefix, + binder_objstat_strings[i], + stats->obj_created[i] - stats->obj_deleted[i], + stats->obj_created[i]); + } +} + +static void print_binder_proc_stats(struct seq_file *m, + struct binder_proc *proc) +{ + struct binder_work *w; + struct rb_node *n; + int count, strong, weak; + + seq_printf(m, "proc %d\n", proc->pid); + count = 0; + for (n = rb_first(&proc->threads); n != NULL; n = rb_next(n)) + count++; + seq_printf(m, " threads: %d\n", count); + seq_printf(m, " requested threads: %d+%d/%d\n" + " ready threads %d\n" + " free async space %zd\n", proc->requested_threads, + proc->requested_threads_started, proc->max_threads, + proc->ready_threads, proc->free_async_space); + count = 0; + for (n = rb_first(&proc->nodes); n != NULL; n = rb_next(n)) + count++; + seq_printf(m, " nodes: %d\n", count); + count = 0; + strong = 0; + weak = 0; + for (n = rb_first(&proc->refs_by_desc); n != NULL; n = rb_next(n)) { + struct binder_ref *ref = rb_entry(n, struct binder_ref, + rb_node_desc); + count++; + strong += ref->strong; + weak += ref->weak; + } + seq_printf(m, " refs: %d s %d w %d\n", count, strong, weak); + + count = 0; + for (n = rb_first(&proc->allocated_buffers); n != NULL; n = rb_next(n)) + count++; + seq_printf(m, " buffers: %d\n", count); + + count = 0; + list_for_each_entry(w, &proc->todo, entry) { + switch (w->type) { + case BINDER_WORK_TRANSACTION: + count++; + break; + default: + break; + } + } + seq_printf(m, " pending transactions: %d\n", count); + + print_binder_stats(m, " ", &proc->stats); +} + + +static int binder_state_show(struct seq_file *m, void *unused) +{ + struct binder_proc *proc; + struct hlist_node *pos; + struct binder_node *node; + int do_lock = !binder_debug_no_lock; + + if (do_lock) + binder_lock(__func__); + + seq_puts(m, "binder state:\n"); + + if (!hlist_empty(&binder_dead_nodes)) + seq_puts(m, "dead nodes:\n"); + hlist_for_each_entry(node, pos, &binder_dead_nodes, dead_node) + print_binder_node(m, node); + + hlist_for_each_entry(proc, pos, &binder_procs, proc_node) + print_binder_proc(m, proc, 1); + if (do_lock) + binder_unlock(__func__); + return 0; +} + +static int binder_stats_show(struct seq_file *m, void *unused) +{ + struct binder_proc *proc; + struct hlist_node *pos; + int do_lock = !binder_debug_no_lock; + + if (do_lock) + binder_lock(__func__); + + seq_puts(m, "binder stats:\n"); + + print_binder_stats(m, "", &binder_stats); + + hlist_for_each_entry(proc, pos, &binder_procs, proc_node) + print_binder_proc_stats(m, proc); + if (do_lock) + binder_unlock(__func__); + return 0; +} + +static int binder_transactions_show(struct seq_file *m, void *unused) +{ + struct binder_proc *proc; + struct hlist_node *pos; + int do_lock = !binder_debug_no_lock; + + if (do_lock) + binder_lock(__func__); + + seq_puts(m, "binder transactions:\n"); + hlist_for_each_entry(proc, pos, &binder_procs, proc_node) + print_binder_proc(m, proc, 0); + if (do_lock) + binder_unlock(__func__); + return 0; +} + +static int binder_proc_show(struct seq_file *m, void *unused) +{ + struct binder_proc *proc = m->private; + int do_lock = !binder_debug_no_lock; + + if (do_lock) + binder_lock(__func__); + seq_puts(m, "binder proc state:\n"); + print_binder_proc(m, proc, 1); + if (do_lock) + binder_unlock(__func__); + return 0; +} + +static void print_binder_transaction_log_entry(struct seq_file *m, + struct binder_transaction_log_entry *e) +{ + seq_printf(m, + "%d: %s from %d:%d to %d:%d node %d handle %d size %d:%d\n", + e->debug_id, (e->call_type == 2) ? "reply" : + ((e->call_type == 1) ? "async" : "call "), e->from_proc, + e->from_thread, e->to_proc, e->to_thread, e->to_node, + e->target_handle, e->data_size, e->offsets_size); +} + +static int binder_transaction_log_show(struct seq_file *m, void *unused) +{ + struct binder_transaction_log *log = m->private; + int i; + + if (log->full) { + for (i = log->next; i < ARRAY_SIZE(log->entry); i++) + print_binder_transaction_log_entry(m, &log->entry[i]); + } + for (i = 0; i < log->next; i++) + print_binder_transaction_log_entry(m, &log->entry[i]); + return 0; +} + +static const struct file_operations binder_fops = { + .owner = THIS_MODULE, + .poll = binder_poll, + .unlocked_ioctl = binder_ioctl, + .mmap = binder_mmap, + .open = binder_open, + .flush = binder_flush, + .release = binder_release, +}; + +static struct miscdevice binder_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "binder", + .fops = &binder_fops +}; + +BINDER_DEBUG_ENTRY(state); +BINDER_DEBUG_ENTRY(stats); +BINDER_DEBUG_ENTRY(transactions); +BINDER_DEBUG_ENTRY(transaction_log); + +static int __init binder_init(void) +{ + int ret; + + binder_deferred_workqueue = create_singlethread_workqueue("binder"); + if (!binder_deferred_workqueue) + return -ENOMEM; + + binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL); + if (binder_debugfs_dir_entry_root) + binder_debugfs_dir_entry_proc = debugfs_create_dir("proc", + binder_debugfs_dir_entry_root); + ret = misc_register(&binder_miscdev); + if (binder_debugfs_dir_entry_root) { + debugfs_create_file("state", + S_IRUGO, + binder_debugfs_dir_entry_root, + NULL, + &binder_state_fops); + debugfs_create_file("stats", + S_IRUGO, + binder_debugfs_dir_entry_root, + NULL, + &binder_stats_fops); + debugfs_create_file("transactions", + S_IRUGO, + binder_debugfs_dir_entry_root, + NULL, + &binder_transactions_fops); + debugfs_create_file("transaction_log", + S_IRUGO, + binder_debugfs_dir_entry_root, + &binder_transaction_log, + &binder_transaction_log_fops); + debugfs_create_file("failed_transaction_log", + S_IRUGO, + binder_debugfs_dir_entry_root, + &binder_transaction_log_failed, + &binder_transaction_log_fops); + } + return ret; +} + +device_initcall(binder_init); + +#define CREATE_TRACE_POINTS +#include "binder_trace.h" + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/android/binder.h b/drivers/staging/android/binder.h new file mode 100644 index 00000000..25ab6f27 --- /dev/null +++ b/drivers/staging/android/binder.h @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2008 Google, Inc. + * + * Based on, but no longer compatible with, the original + * OpenBinder.org binder driver interface, which is: + * + * Copyright (c) 2005 Palmsource, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LINUX_BINDER_H +#define _LINUX_BINDER_H + +#include + +#define B_PACK_CHARS(c1, c2, c3, c4) \ + ((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4)) +#define B_TYPE_LARGE 0x85 + +enum { + BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE), + BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE), + BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE), + BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE), + BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE), +}; + +enum { + FLAT_BINDER_FLAG_PRIORITY_MASK = 0xff, + FLAT_BINDER_FLAG_ACCEPTS_FDS = 0x100, +}; + +/* + * This is the flattened representation of a Binder object for transfer + * between processes. The 'offsets' supplied as part of a binder transaction + * contains offsets into the data where these structures occur. The Binder + * driver takes care of re-writing the structure type and data as it moves + * between processes. + */ +struct flat_binder_object { + /* 8 bytes for large_flat_header. */ + unsigned long type; + unsigned long flags; + + /* 8 bytes of data. */ + union { + void *binder; /* local object */ + signed long handle; /* remote object */ + }; + + /* extra data associated with local object */ + void *cookie; +}; + +/* + * On 64-bit platforms where user code may run in 32-bits the driver must + * translate the buffer (and local binder) addresses apropriately. + */ + +struct binder_write_read { + signed long write_size; /* bytes to write */ + signed long write_consumed; /* bytes consumed by driver */ + unsigned long write_buffer; + signed long read_size; /* bytes to read */ + signed long read_consumed; /* bytes consumed by driver */ + unsigned long read_buffer; +}; + +/* Use with BINDER_VERSION, driver fills in fields. */ +struct binder_version { + /* driver protocol version -- increment with incompatible change */ + signed long protocol_version; +}; + +/* This is the current protocol version. */ +#define BINDER_CURRENT_PROTOCOL_VERSION 7 + +#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read) +#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, int64_t) +#define BINDER_SET_MAX_THREADS _IOW('b', 5, size_t) +#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, int) +#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int) +#define BINDER_THREAD_EXIT _IOW('b', 8, int) +#define BINDER_VERSION _IOWR('b', 9, struct binder_version) + +/* + * NOTE: Two special error codes you should check for when calling + * in to the driver are: + * + * EINTR -- The operation has been interupted. This should be + * handled by retrying the ioctl() until a different error code + * is returned. + * + * ECONNREFUSED -- The driver is no longer accepting operations + * from your process. That is, the process is being destroyed. + * You should handle this by exiting from your process. Note + * that once this error code is returned, all further calls to + * the driver from any thread will return this same code. + */ + +enum transaction_flags { + TF_ONE_WAY = 0x01, /* this is a one-way call: async, no return */ + TF_ROOT_OBJECT = 0x04, /* contents are the component's root object */ + TF_STATUS_CODE = 0x08, /* contents are a 32-bit status code */ + TF_ACCEPT_FDS = 0x10, /* allow replies with file descriptors */ +}; + +struct binder_transaction_data { + /* The first two are only used for bcTRANSACTION and brTRANSACTION, + * identifying the target and contents of the transaction. + */ + union { + size_t handle; /* target descriptor of command transaction */ + void *ptr; /* target descriptor of return transaction */ + } target; + void *cookie; /* target object cookie */ + unsigned int code; /* transaction command */ + + /* General information about the transaction. */ + unsigned int flags; + pid_t sender_pid; + uid_t sender_euid; + size_t data_size; /* number of bytes of data */ + size_t offsets_size; /* number of bytes of offsets */ + + /* If this transaction is inline, the data immediately + * follows here; otherwise, it ends with a pointer to + * the data buffer. + */ + union { + struct { + /* transaction data */ + const void *buffer; + /* offsets from buffer to flat_binder_object structs */ + const void *offsets; + } ptr; + uint8_t buf[8]; + } data; +}; + +struct binder_ptr_cookie { + void *ptr; + void *cookie; +}; + +struct binder_pri_desc { + int priority; + int desc; +}; + +struct binder_pri_ptr_cookie { + int priority; + void *ptr; + void *cookie; +}; + +enum BinderDriverReturnProtocol { + BR_ERROR = _IOR('r', 0, int), + /* + * int: error code + */ + + BR_OK = _IO('r', 1), + /* No parameters! */ + + BR_TRANSACTION = _IOR('r', 2, struct binder_transaction_data), + BR_REPLY = _IOR('r', 3, struct binder_transaction_data), + /* + * binder_transaction_data: the received command. + */ + + BR_ACQUIRE_RESULT = _IOR('r', 4, int), + /* + * not currently supported + * int: 0 if the last bcATTEMPT_ACQUIRE was not successful. + * Else the remote object has acquired a primary reference. + */ + + BR_DEAD_REPLY = _IO('r', 5), + /* + * The target of the last transaction (either a bcTRANSACTION or + * a bcATTEMPT_ACQUIRE) is no longer with us. No parameters. + */ + + BR_TRANSACTION_COMPLETE = _IO('r', 6), + /* + * No parameters... always refers to the last transaction requested + * (including replies). Note that this will be sent even for + * asynchronous transactions. + */ + + BR_INCREFS = _IOR('r', 7, struct binder_ptr_cookie), + BR_ACQUIRE = _IOR('r', 8, struct binder_ptr_cookie), + BR_RELEASE = _IOR('r', 9, struct binder_ptr_cookie), + BR_DECREFS = _IOR('r', 10, struct binder_ptr_cookie), + /* + * void *: ptr to binder + * void *: cookie for binder + */ + + BR_ATTEMPT_ACQUIRE = _IOR('r', 11, struct binder_pri_ptr_cookie), + /* + * not currently supported + * int: priority + * void *: ptr to binder + * void *: cookie for binder + */ + + BR_NOOP = _IO('r', 12), + /* + * No parameters. Do nothing and examine the next command. It exists + * primarily so that we can replace it with a BR_SPAWN_LOOPER command. + */ + + BR_SPAWN_LOOPER = _IO('r', 13), + /* + * No parameters. The driver has determined that a process has no + * threads waiting to service incomming transactions. When a process + * receives this command, it must spawn a new service thread and + * register it via bcENTER_LOOPER. + */ + + BR_FINISHED = _IO('r', 14), + /* + * not currently supported + * stop threadpool thread + */ + + BR_DEAD_BINDER = _IOR('r', 15, void *), + /* + * void *: cookie + */ + BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, void *), + /* + * void *: cookie + */ + + BR_FAILED_REPLY = _IO('r', 17), + /* + * The the last transaction (either a bcTRANSACTION or + * a bcATTEMPT_ACQUIRE) failed (e.g. out of memory). No parameters. + */ +}; + +enum BinderDriverCommandProtocol { + BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data), + BC_REPLY = _IOW('c', 1, struct binder_transaction_data), + /* + * binder_transaction_data: the sent command. + */ + + BC_ACQUIRE_RESULT = _IOW('c', 2, int), + /* + * not currently supported + * int: 0 if the last BR_ATTEMPT_ACQUIRE was not successful. + * Else you have acquired a primary reference on the object. + */ + + BC_FREE_BUFFER = _IOW('c', 3, int), + /* + * void *: ptr to transaction data received on a read + */ + + BC_INCREFS = _IOW('c', 4, int), + BC_ACQUIRE = _IOW('c', 5, int), + BC_RELEASE = _IOW('c', 6, int), + BC_DECREFS = _IOW('c', 7, int), + /* + * int: descriptor + */ + + BC_INCREFS_DONE = _IOW('c', 8, struct binder_ptr_cookie), + BC_ACQUIRE_DONE = _IOW('c', 9, struct binder_ptr_cookie), + /* + * void *: ptr to binder + * void *: cookie for binder + */ + + BC_ATTEMPT_ACQUIRE = _IOW('c', 10, struct binder_pri_desc), + /* + * not currently supported + * int: priority + * int: descriptor + */ + + BC_REGISTER_LOOPER = _IO('c', 11), + /* + * No parameters. + * Register a spawned looper thread with the device. + */ + + BC_ENTER_LOOPER = _IO('c', 12), + BC_EXIT_LOOPER = _IO('c', 13), + /* + * No parameters. + * These two commands are sent as an application-level thread + * enters and exits the binder loop, respectively. They are + * used so the binder can have an accurate count of the number + * of looping threads it has available. + */ + + BC_REQUEST_DEATH_NOTIFICATION = _IOW('c', 14, struct binder_ptr_cookie), + /* + * void *: ptr to binder + * void *: cookie + */ + + BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15, struct binder_ptr_cookie), + /* + * void *: ptr to binder + * void *: cookie + */ + + BC_DEAD_BINDER_DONE = _IOW('c', 16, void *), + /* + * void *: cookie + */ +}; + +#endif /* _LINUX_BINDER_H */ + diff --git a/drivers/staging/android/binder_trace.h b/drivers/staging/android/binder_trace.h new file mode 100644 index 00000000..82a567c2 --- /dev/null +++ b/drivers/staging/android/binder_trace.h @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2012 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM binder + +#if !defined(_BINDER_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _BINDER_TRACE_H + +#include + +struct binder_buffer; +struct binder_node; +struct binder_proc; +struct binder_ref; +struct binder_thread; +struct binder_transaction; + +TRACE_EVENT(binder_ioctl, + TP_PROTO(unsigned int cmd, unsigned long arg), + TP_ARGS(cmd, arg), + + TP_STRUCT__entry( + __field(unsigned int, cmd) + __field(unsigned long, arg) + ), + TP_fast_assign( + __entry->cmd = cmd; + __entry->arg = arg; + ), + TP_printk("cmd=0x%x arg=0x%lx", __entry->cmd, __entry->arg) +); + +DECLARE_EVENT_CLASS(binder_lock_class, + TP_PROTO(const char *tag), + TP_ARGS(tag), + TP_STRUCT__entry( + __field(const char *, tag) + ), + TP_fast_assign( + __entry->tag = tag; + ), + TP_printk("tag=%s", __entry->tag) +); + +#define DEFINE_BINDER_LOCK_EVENT(name) \ +DEFINE_EVENT(binder_lock_class, name, \ + TP_PROTO(const char *func), \ + TP_ARGS(func)) + +DEFINE_BINDER_LOCK_EVENT(binder_lock); +DEFINE_BINDER_LOCK_EVENT(binder_locked); +DEFINE_BINDER_LOCK_EVENT(binder_unlock); + +DECLARE_EVENT_CLASS(binder_function_return_class, + TP_PROTO(int ret), + TP_ARGS(ret), + TP_STRUCT__entry( + __field(int, ret) + ), + TP_fast_assign( + __entry->ret = ret; + ), + TP_printk("ret=%d", __entry->ret) +); + +#define DEFINE_BINDER_FUNCTION_RETURN_EVENT(name) \ +DEFINE_EVENT(binder_function_return_class, name, \ + TP_PROTO(int ret), \ + TP_ARGS(ret)) + +DEFINE_BINDER_FUNCTION_RETURN_EVENT(binder_ioctl_done); +DEFINE_BINDER_FUNCTION_RETURN_EVENT(binder_write_done); +DEFINE_BINDER_FUNCTION_RETURN_EVENT(binder_read_done); + +TRACE_EVENT(binder_wait_for_work, + TP_PROTO(bool proc_work, bool transaction_stack, bool thread_todo), + TP_ARGS(proc_work, transaction_stack, thread_todo), + + TP_STRUCT__entry( + __field(bool, proc_work) + __field(bool, transaction_stack) + __field(bool, thread_todo) + ), + TP_fast_assign( + __entry->proc_work = proc_work; + __entry->transaction_stack = transaction_stack; + __entry->thread_todo = thread_todo; + ), + TP_printk("proc_work=%d transaction_stack=%d thread_todo=%d", + __entry->proc_work, __entry->transaction_stack, + __entry->thread_todo) +); + +TRACE_EVENT(binder_transaction, + TP_PROTO(bool reply, struct binder_transaction *t, + struct binder_node *target_node), + TP_ARGS(reply, t, target_node), + TP_STRUCT__entry( + __field(int, debug_id) + __field(int, target_node) + __field(int, to_proc) + __field(int, to_thread) + __field(int, reply) + __field(unsigned int, code) + __field(unsigned int, flags) + ), + TP_fast_assign( + __entry->debug_id = t->debug_id; + __entry->target_node = target_node ? target_node->debug_id : 0; + __entry->to_proc = t->to_proc->pid; + __entry->to_thread = t->to_thread ? t->to_thread->pid : 0; + __entry->reply = reply; + __entry->code = t->code; + __entry->flags = t->flags; + ), + TP_printk("transaction=%d dest_node=%d dest_proc=%d dest_thread=%d reply=%d flags=0x%x code=0x%x", + __entry->debug_id, __entry->target_node, + __entry->to_proc, __entry->to_thread, + __entry->reply, __entry->flags, __entry->code) +); + +TRACE_EVENT(binder_transaction_received, + TP_PROTO(struct binder_transaction *t), + TP_ARGS(t), + + TP_STRUCT__entry( + __field(int, debug_id) + ), + TP_fast_assign( + __entry->debug_id = t->debug_id; + ), + TP_printk("transaction=%d", __entry->debug_id) +); + +TRACE_EVENT(binder_transaction_node_to_ref, + TP_PROTO(struct binder_transaction *t, struct binder_node *node, + struct binder_ref *ref), + TP_ARGS(t, node, ref), + + TP_STRUCT__entry( + __field(int, debug_id) + __field(int, node_debug_id) + __field(void __user *, node_ptr) + __field(int, ref_debug_id) + __field(uint32_t, ref_desc) + ), + TP_fast_assign( + __entry->debug_id = t->debug_id; + __entry->node_debug_id = node->debug_id; + __entry->node_ptr = node->ptr; + __entry->ref_debug_id = ref->debug_id; + __entry->ref_desc = ref->desc; + ), + TP_printk("transaction=%d node=%d src_ptr=0x%p ==> dest_ref=%d dest_desc=%d", + __entry->debug_id, __entry->node_debug_id, __entry->node_ptr, + __entry->ref_debug_id, __entry->ref_desc) +); + +TRACE_EVENT(binder_transaction_ref_to_node, + TP_PROTO(struct binder_transaction *t, struct binder_ref *ref), + TP_ARGS(t, ref), + + TP_STRUCT__entry( + __field(int, debug_id) + __field(int, ref_debug_id) + __field(uint32_t, ref_desc) + __field(int, node_debug_id) + __field(void __user *, node_ptr) + ), + TP_fast_assign( + __entry->debug_id = t->debug_id; + __entry->ref_debug_id = ref->debug_id; + __entry->ref_desc = ref->desc; + __entry->node_debug_id = ref->node->debug_id; + __entry->node_ptr = ref->node->ptr; + ), + TP_printk("transaction=%d node=%d src_ref=%d src_desc=%d ==> dest_ptr=0x%p", + __entry->debug_id, __entry->node_debug_id, + __entry->ref_debug_id, __entry->ref_desc, __entry->node_ptr) +); + +TRACE_EVENT(binder_transaction_ref_to_ref, + TP_PROTO(struct binder_transaction *t, struct binder_ref *src_ref, + struct binder_ref *dest_ref), + TP_ARGS(t, src_ref, dest_ref), + + TP_STRUCT__entry( + __field(int, debug_id) + __field(int, node_debug_id) + __field(int, src_ref_debug_id) + __field(uint32_t, src_ref_desc) + __field(int, dest_ref_debug_id) + __field(uint32_t, dest_ref_desc) + ), + TP_fast_assign( + __entry->debug_id = t->debug_id; + __entry->node_debug_id = src_ref->node->debug_id; + __entry->src_ref_debug_id = src_ref->debug_id; + __entry->src_ref_desc = src_ref->desc; + __entry->dest_ref_debug_id = dest_ref->debug_id; + __entry->dest_ref_desc = dest_ref->desc; + ), + TP_printk("transaction=%d node=%d src_ref=%d src_desc=%d ==> dest_ref=%d dest_desc=%d", + __entry->debug_id, __entry->node_debug_id, + __entry->src_ref_debug_id, __entry->src_ref_desc, + __entry->dest_ref_debug_id, __entry->dest_ref_desc) +); + +TRACE_EVENT(binder_transaction_fd, + TP_PROTO(struct binder_transaction *t, int src_fd, int dest_fd), + TP_ARGS(t, src_fd, dest_fd), + + TP_STRUCT__entry( + __field(int, debug_id) + __field(int, src_fd) + __field(int, dest_fd) + ), + TP_fast_assign( + __entry->debug_id = t->debug_id; + __entry->src_fd = src_fd; + __entry->dest_fd = dest_fd; + ), + TP_printk("transaction=%d src_fd=%d ==> dest_fd=%d", + __entry->debug_id, __entry->src_fd, __entry->dest_fd) +); + +DECLARE_EVENT_CLASS(binder_buffer_class, + TP_PROTO(struct binder_buffer *buf), + TP_ARGS(buf), + TP_STRUCT__entry( + __field(int, debug_id) + __field(size_t, data_size) + __field(size_t, offsets_size) + ), + TP_fast_assign( + __entry->debug_id = buf->debug_id; + __entry->data_size = buf->data_size; + __entry->offsets_size = buf->offsets_size; + ), + TP_printk("transaction=%d data_size=%zd offsets_size=%zd", + __entry->debug_id, __entry->data_size, __entry->offsets_size) +); + +DEFINE_EVENT(binder_buffer_class, binder_transaction_alloc_buf, + TP_PROTO(struct binder_buffer *buffer), + TP_ARGS(buffer)); + +DEFINE_EVENT(binder_buffer_class, binder_transaction_buffer_release, + TP_PROTO(struct binder_buffer *buffer), + TP_ARGS(buffer)); + +DEFINE_EVENT(binder_buffer_class, binder_transaction_failed_buffer_release, + TP_PROTO(struct binder_buffer *buffer), + TP_ARGS(buffer)); + +TRACE_EVENT(binder_update_page_range, + TP_PROTO(struct binder_proc *proc, bool allocate, + void *start, void *end), + TP_ARGS(proc, allocate, start, end), + TP_STRUCT__entry( + __field(int, proc) + __field(bool, allocate) + __field(size_t, offset) + __field(size_t, size) + ), + TP_fast_assign( + __entry->proc = proc->pid; + __entry->allocate = allocate; + __entry->offset = start - proc->buffer; + __entry->size = end - start; + ), + TP_printk("proc=%d allocate=%d offset=%zu size=%zu", + __entry->proc, __entry->allocate, + __entry->offset, __entry->size) +); + +TRACE_EVENT(binder_command, + TP_PROTO(uint32_t cmd), + TP_ARGS(cmd), + TP_STRUCT__entry( + __field(uint32_t, cmd) + ), + TP_fast_assign( + __entry->cmd = cmd; + ), + TP_printk("cmd=0x%x %s", + __entry->cmd, + _IOC_NR(__entry->cmd) < ARRAY_SIZE(binder_command_strings) ? + binder_command_strings[_IOC_NR(__entry->cmd)] : + "unknown") +); + +TRACE_EVENT(binder_return, + TP_PROTO(uint32_t cmd), + TP_ARGS(cmd), + TP_STRUCT__entry( + __field(uint32_t, cmd) + ), + TP_fast_assign( + __entry->cmd = cmd; + ), + TP_printk("cmd=0x%x %s", + __entry->cmd, + _IOC_NR(__entry->cmd) < ARRAY_SIZE(binder_return_strings) ? + binder_return_strings[_IOC_NR(__entry->cmd)] : + "unknown") +); + +#endif /* _BINDER_TRACE_H */ + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE binder_trace +#include diff --git a/drivers/staging/android/logger.c b/drivers/staging/android/logger.c new file mode 100644 index 00000000..5ca86427 --- /dev/null +++ b/drivers/staging/android/logger.c @@ -0,0 +1,939 @@ +/* + * drivers/misc/logger.c + * + * A Logging Subsystem + * + * Copyright (C) 2007-2008 Google, Inc. + * + * Robert Love + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +//#include +#include +#include +#include +#include +#include +#include +#include +#include "logger.h" +#include +#include + +/* + * struct logger_log - represents a specific log, such as 'main' or 'radio' + * + * This structure lives from module insertion until module removal, so it does + * not need additional reference counting. The structure is protected by the + * mutex 'mutex'. + */ +struct logger_log { + unsigned char *buffer;/* the ring buffer itself */ + struct miscdevice misc; /* misc device representing the log */ + wait_queue_head_t wq; /* wait queue for readers */ + struct list_head readers; /* this log's readers */ + struct mutex mutex; /* mutex protecting buffer */ + size_t w_off; /* current write head offset */ + size_t head; /* new readers start here */ + size_t size; /* size of the log */ +}; + +/* + * struct logger_reader - a logging device open for reading + * + * This object lives from open to release, so we don't need additional + * reference counting. The structure is protected by log->mutex. + */ +struct logger_reader { + struct logger_log *log; /* associated log */ + struct list_head list; /* entry in logger_log's list */ + size_t r_off; /* current read head offset */ + bool r_all; /* reader can read all entries */ + int r_ver; /* reader ABI version */ +}; + +/* logger_offset - returns index 'n' into the log via (optimized) modulus */ +size_t logger_offset(struct logger_log *log, size_t n) +{ + return n & (log->size-1); +} + + +/* + * file_get_log - Given a file structure, return the associated log + * + * This isn't aesthetic. We have several goals: + * + * 1) Need to quickly obtain the associated log during an I/O operation + * 2) Readers need to maintain state (logger_reader) + * 3) Writers need to be very fast (open() should be a near no-op) + * + * In the reader case, we can trivially go file->logger_reader->logger_log. + * For a writer, we don't want to maintain a logger_reader, so we just go + * file->logger_log. Thus what file->private_data points at depends on whether + * or not the file was opened for reading. This function hides that dirtiness. + */ +extern struct k_clock posix_clocks[]; + +/** + * Redirect Android logcat to uart0 if logcat_set != 0 + */ +static int logcat_set; +/*getThreadMsec - get the runtime of current thread, add by jay*/ + uint32_t getThreadMsec(void) +{ + + struct timespec tm; + + posix_clocks[CLOCK_THREAD_CPUTIME_ID].clock_get(CLOCK_THREAD_CPUTIME_ID, &tm); + return tm.tv_sec * 1000LL + tm.tv_nsec / 1000000; +} + +static inline struct logger_log *file_get_log(struct file *file) +{ + if (file->f_mode & FMODE_READ) { + struct logger_reader *reader = file->private_data; + return reader->log; + } else + return file->private_data; +} + +/* + * get_entry_header - returns a pointer to the logger_entry header within + * 'log' starting at offset 'off'. A temporary logger_entry 'scratch' must + * be provided. Typically the return value will be a pointer within + * 'logger->buf'. However, a pointer to 'scratch' may be returned if + * the log entry spans the end and beginning of the circular buffer. + */ +static struct logger_entry *get_entry_header(struct logger_log *log, + size_t off, struct logger_entry *scratch) +{ + size_t len = min(sizeof(struct logger_entry), log->size - off); + if (len != sizeof(struct logger_entry)) { + memcpy(((void *) scratch), log->buffer + off, len); + memcpy(((void *) scratch) + len, log->buffer, + sizeof(struct logger_entry) - len); + return scratch; + } + + return (struct logger_entry *) (log->buffer + off); +} + +/* + * get_entry_msg_len - Grabs the length of the message of the entry + * starting from from 'off'. + * + * An entry length is 2 bytes (16 bits) in host endian order. + * In the log, the length does not include the size of the log entry structure. + * This function returns the size including the log entry structure. + * + * Caller needs to hold log->mutex. + */ +static __u32 get_entry_msg_len(struct logger_log *log, size_t off) +{ + struct logger_entry scratch; + struct logger_entry *entry; + + entry = get_entry_header(log, off, &scratch); + return entry->len; +} + +static size_t get_user_hdr_len(int ver) +{ + if (ver < 2) + return sizeof(struct user_logger_entry_compat); + else + return sizeof(struct logger_entry); +} + +static ssize_t copy_header_to_user(int ver, struct logger_entry *entry, + char __user *buf) +{ + void *hdr; + size_t hdr_len; + struct user_logger_entry_compat v1; + + if (ver < 2) { + v1.len = entry->len; + v1.__pad = 0; + v1.pid = entry->pid; + v1.tid = entry->tid; + v1.sec = entry->sec; + v1.nsec = entry->nsec; + hdr = &v1; + hdr_len = sizeof(struct user_logger_entry_compat); + } else { + hdr = entry; + hdr_len = sizeof(struct logger_entry); + } + + return copy_to_user(buf, hdr, hdr_len); +} + +/* + * do_read_log_to_user - reads exactly 'count' bytes from 'log' into the + * user-space buffer 'buf'. Returns 'count' on success. + * + * Caller must hold log->mutex. + */ +static ssize_t do_read_log_to_user(struct logger_log *log, + struct logger_reader *reader, + char __user *buf, + size_t count) +{ + struct logger_entry scratch; + struct logger_entry *entry; + size_t len; + size_t msg_start; + + /* + * First, copy the header to userspace, using the version of + * the header requested + */ + entry = get_entry_header(log, reader->r_off, &scratch); + if (copy_header_to_user(reader->r_ver, entry, buf)) + return -EFAULT; + + count -= get_user_hdr_len(reader->r_ver); + buf += get_user_hdr_len(reader->r_ver); + msg_start = logger_offset(log, + reader->r_off + sizeof(struct logger_entry)); + + /* + * We read from the msg in two disjoint operations. First, we read from + * the current msg head offset up to 'count' bytes or to the end of + * the log, whichever comes first. + */ + len = min(count, log->size - msg_start); + if (copy_to_user(buf, log->buffer + msg_start, len)) + return -EFAULT; + + /* + * Second, we read any remaining bytes, starting back at the head of + * the log. + */ + if (count != len) + if (copy_to_user(buf + len, log->buffer, count - len)) + return -EFAULT; + + reader->r_off = logger_offset(log, reader->r_off + + sizeof(struct logger_entry) + count); + + return count + get_user_hdr_len(reader->r_ver); +} + +/* + * get_next_entry_by_uid - Starting at 'off', returns an offset into + * 'log->buffer' which contains the first entry readable by 'euid' + */ +static size_t get_next_entry_by_uid(struct logger_log *log, + size_t off, uid_t euid) +{ + while (off != log->w_off) { + struct logger_entry *entry; + struct logger_entry scratch; + size_t next_len; + + entry = get_entry_header(log, off, &scratch); + + if (entry->euid == euid) + return off; + + next_len = sizeof(struct logger_entry) + entry->len; + off = logger_offset(log, off + next_len); + } + + return off; +} + +/* + * logger_read - our log's read() method + * + * Behavior: + * + * - O_NONBLOCK works + * - If there are no log entries to read, blocks until log is written to + * - Atomically reads exactly one log entry + * + * Will set errno to EINVAL if read + * buffer is insufficient to hold next entry. + */ +static ssize_t logger_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct logger_reader *reader = file->private_data; + struct logger_log *log = reader->log; + ssize_t ret; + DEFINE_WAIT(wait); + +start: + while (1) { + mutex_lock(&log->mutex); + + prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE); + + ret = (log->w_off == reader->r_off); + mutex_unlock(&log->mutex); + if (!ret) + break; + + if (file->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + break; + } + + if (signal_pending(current)) { + ret = -EINTR; + break; + } + + schedule(); + } + + finish_wait(&log->wq, &wait); + if (ret) + return ret; + + mutex_lock(&log->mutex); + + if (!reader->r_all) + reader->r_off = get_next_entry_by_uid(log, + reader->r_off, current_euid()); + + /* is there still something to read or did we race? */ + if (unlikely(log->w_off == reader->r_off)) { + mutex_unlock(&log->mutex); + goto start; + } + + /* get the size of the next entry */ + ret = get_user_hdr_len(reader->r_ver) + + get_entry_msg_len(log, reader->r_off); + if (count < ret) { + ret = -EINVAL; + goto out; + } + + /* get exactly one entry from the log */ + ret = do_read_log_to_user(log, reader, buf, ret); + +out: + mutex_unlock(&log->mutex); + + return ret; +} + +/* + * get_next_entry - return the offset of the first valid entry at least 'len' + * bytes after 'off'. + * + * Caller must hold log->mutex. + */ +static size_t get_next_entry(struct logger_log *log, size_t off, size_t len) +{ + size_t count = 0; + + do { + size_t nr = sizeof(struct logger_entry) + + get_entry_msg_len(log, off); + off = logger_offset(log, off + nr); + count += nr; + } while (count < len); + + return off; +} + +/* + * is_between - is a < c < b, accounting for wrapping of a, b, and c + * positions in the buffer + * + * That is, if ab, check for c outside (not between) a and b + * + * |------- a xxxxxxxx b --------| + * c^ + * + * |xxxxx b --------- a xxxxxxxxx| + * c^ + * or c^ + */ +static inline int is_between(size_t a, size_t b, size_t c) +{ + if (a < b) { + /* is c between a and b? */ + if (a < c && c <= b) + return 1; + } else { + /* is c outside of b through a? */ + if (c <= b || a < c) + return 1; + } + + return 0; +} + +/* + * fix_up_readers - walk the list of all readers and "fix up" any who were + * lapped by the writer; also do the same for the default "start head". + * We do this by "pulling forward" the readers and start head to the first + * entry after the new write head. + * + * The caller needs to hold log->mutex. + */ +static void fix_up_readers(struct logger_log *log, size_t len) +{ + size_t old = log->w_off; + size_t new = logger_offset(log, old + len); + struct logger_reader *reader; + + if (is_between(old, new, log->head)) + log->head = get_next_entry(log, log->head, len); + + list_for_each_entry(reader, &log->readers, list) + if (is_between(old, new, reader->r_off)) + reader->r_off = get_next_entry(log, reader->r_off, len); +} + +/* + * do_write_log - writes 'len' bytes from 'buf' to 'log' + * + * The caller needs to hold log->mutex. + */ +static void do_write_log(struct logger_log *log, const void *buf, size_t count) +{ + size_t len; + + len = min(count, log->size - log->w_off); + memcpy(log->buffer + log->w_off, buf, len); + + if (count != len) + memcpy(log->buffer, buf + len, count - len); + + log->w_off = logger_offset(log, log->w_off + count); + +} + +/* + * do_write_log_user - writes 'len' bytes from the user-space buffer 'buf' to + * the log 'log' + * + * The caller needs to hold log->mutex. + * + * Returns 'count' on success, negative error code on failure. + */ +static ssize_t do_write_log_from_user(struct logger_log *log, + const void __user *buf, size_t count, int seg) +{ + static int abnormal; + int i, s, str_len; + static struct logger_entry header; + static int label; + static char tag[20]; + /* struct timespec now;//modified by essenzhang */ + struct timeval now; + long threadtime; + size_t len; + if (logcat_set) { + switch (seg) { + case 2: + label = *((char *)buf); + abnormal = 0; + + switch (label) { + case 2: + label = 'V'; + break; /* VERBOSE */ + case 3: + label = 'D'; + break; /* DEBUG */ + case 4: + label = 'I'; + break; /* INFO */ + case 5: + label = 'W'; + break; /* WARN */ + case 6: + label = 'E'; + break; /* ERROR */ + case 7: + label = 'A'; + break; /* ASSERT */ + default: + abnormal = 1; + break; + } + /* now = current_kernel_time();//modified by essenzhang */ + do_gettimeofday(&now); + + threadtime = getThreadMsec(); /* add by jay */ + + header.pid = current->tgid; + header.tid = current->pid; + /* + //header.sec = now.tv_sec; + //header.nsec = now.tv_nsec;//modified by essenzhang + */ + header.sec = now.tv_sec; + header.nsec = now.tv_usec*1000; + + header.thread_msec = threadtime; /* add by jay */ + break; + case 1: + if (!abnormal) + memcpy(tag, buf, count); + tag[16] = 0; + break; + case 0: + if (!abnormal) { + abnormal = 1; + + for (i = 0, s = 0, str_len = 0 ; i < count ; i++) { + str_len++; + if (*((char *)buf+i) == 0) { + if (str_len > 1) { + if (logcat_set == 1) + printk(KERN_INFO "%c/%16s(%5d): %s\n", label, tag, header.pid, ((char *)buf+s)); + else if (logcat_set == 2) + printk(KERN_INFO "%06d.%06d %c/%16s(%5d): %s\n", header.sec%1000000, header.nsec/1000, label, tag, header.pid, ((char *)buf+s)); + } + str_len = 0; + } else if ((*((char *)buf+i) == 0xa) && (i != count-1)) { + *((char *)buf+i) = 0; + if (str_len > 1) { + if (logcat_set == 1) + printk(KERN_INFO "%c/%16s(%5d): %s\n", label, tag, header.pid, ((char *)buf+s)); + else if (logcat_set == 2) + printk(KERN_INFO "%06d.%06d %c/%16s(%5d): %s\n", header.sec%1000000, header.nsec/1000, label, tag, header.pid, ((char *)buf+s)); + } + s = i+1; + str_len = 0; + } + } + } + break; + } + return count; + } + + len = min(count, log->size - log->w_off); + if (len && copy_from_user(log->buffer + log->w_off, buf, len)) + return -EFAULT; + + if (count != len) + if (copy_from_user(log->buffer, buf + len, count - len)) + /* + * Note that by not updating w_off, this abandons the + * portion of the new entry that *was* successfully + * copied, just above. This is intentional to avoid + * message corruption from missing fragments. + */ + return -EFAULT; + + log->w_off = logger_offset(log, log->w_off + count); + + return count; +} + +/* + * logger_aio_write - our write method, implementing support for write(), + * writev(), and aio_write(). Writes are our fast path, and we try to optimize + * them above all else. + */ +ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov, + unsigned long nr_segs, loff_t ppos) +{ + struct logger_log *log = file_get_log(iocb->ki_filp); + size_t orig = log->w_off; + struct logger_entry header; + /* //struct timespec now;//modified by essenzhang */ + struct timeval now; + long threadtime; + ssize_t ret = 0; + + if (logcat_set) { + mutex_lock(&log->mutex); + while (nr_segs-- > 0) { + ssize_t nr; + + /* write out this segment's payload */ + nr = do_write_log_from_user(log, iov->iov_base, iov->iov_len, nr_segs); + + iov++; + ret += nr; + } + mutex_unlock(&log->mutex); + return ret; + } + /* //now = current_kernel_time();//modified by essenzhang */ + do_gettimeofday(&now); + + threadtime = getThreadMsec(); /* add by jay */ + + header.pid = current->tgid; + header.tid = current->pid; + /* + //header.sec = now.tv_sec; + //header.nsec = now.tv_nsec;//modified by essenzhang + */ + header.sec = now.tv_sec; + header.nsec = now.tv_usec*1000; + + header.euid = current_euid(); + header.thread_msec = threadtime; /*add by jay */ + header.len = min_t(size_t, iocb->ki_left, LOGGER_ENTRY_MAX_PAYLOAD); + header.hdr_size = sizeof(struct logger_entry); + + /* null writes succeed, return zero */ + if (unlikely(!header.len)) + return 0; + + mutex_lock(&log->mutex); + + /* + * Fix up any readers, pulling them forward to the first readable + * entry after (what will be) the new write offset. We do this now + * because if we partially fail, we can end up with clobbered log + * entries that encroach on readable buffer. + */ + fix_up_readers(log, sizeof(struct logger_entry) + header.len); + + do_write_log(log, &header, sizeof(struct logger_entry)); + + while (nr_segs-- > 0) { + size_t len; + ssize_t nr; + + /* figure out how much of this vector we can keep */ + len = min_t(size_t, iov->iov_len, header.len - ret); + + /* write out this segment's payload */ + nr = do_write_log_from_user(log, iov->iov_base, len, nr_segs); + if (unlikely(nr < 0)) { + log->w_off = orig; + mutex_unlock(&log->mutex); + return nr; + } + + iov++; + ret += nr; + } + + mutex_unlock(&log->mutex); + + /* wake up any blocked readers */ + wake_up_interruptible(&log->wq); + + return ret; +} + +static struct logger_log *get_log_from_minor(int); + +/* + * logger_open - the log's open() file operation + * + * Note how near a no-op this is in the write-only case. Keep it that way! + */ +static int logger_open(struct inode *inode, struct file *file) +{ + struct logger_log *log; + int ret; + + ret = nonseekable_open(inode, file); + if (ret) + return ret; + + log = get_log_from_minor(MINOR(inode->i_rdev)); + if (!log) + return -ENODEV; + + if (file->f_mode & FMODE_READ) { + struct logger_reader *reader; + + reader = kmalloc(sizeof(struct logger_reader), GFP_KERNEL); + if (!reader) + return -ENOMEM; + + reader->log = log; + reader->r_ver = 1; + reader->r_all = in_egroup_p(inode->i_gid) || + capable(CAP_SYSLOG); + + INIT_LIST_HEAD(&reader->list); + + mutex_lock(&log->mutex); + reader->r_off = log->head; + list_add_tail(&reader->list, &log->readers); + mutex_unlock(&log->mutex); + + file->private_data = reader; + } else + file->private_data = log; + + return 0; +} + +/* + * logger_release - the log's release file operation + * + * Note this is a total no-op in the write-only case. Keep it that way! + */ +static int logger_release(struct inode *ignored, struct file *file) +{ + if (file->f_mode & FMODE_READ) { + struct logger_reader *reader = file->private_data; + struct logger_log *log = reader->log; + + mutex_lock(&log->mutex); + list_del(&reader->list); + mutex_unlock(&log->mutex); + + kfree(reader); + } + + return 0; +} + +/* + * logger_poll - the log's poll file operation, for poll/select/epoll + * + * Note we always return POLLOUT, because you can always write() to the log. + * Note also that, strictly speaking, a return value of POLLIN does not + * guarantee that the log is readable without blocking, as there is a small + * chance that the writer can lap the reader in the interim between poll() + * returning and the read() request. + */ +static unsigned int logger_poll(struct file *file, poll_table *wait) +{ + struct logger_reader *reader; + struct logger_log *log; + unsigned int ret = POLLOUT | POLLWRNORM; + + if (!(file->f_mode & FMODE_READ)) + return ret; + + reader = file->private_data; + log = reader->log; + + poll_wait(file, &log->wq, wait); + + mutex_lock(&log->mutex); + if (!reader->r_all) + reader->r_off = get_next_entry_by_uid(log, + reader->r_off, current_euid()); + + if (log->w_off != reader->r_off) + ret |= POLLIN | POLLRDNORM; + mutex_unlock(&log->mutex); + + return ret; +} + +static long logger_set_version(struct logger_reader *reader, void __user *arg) +{ + int version; + if (copy_from_user(&version, arg, sizeof(int))) + return -EFAULT; + + if ((version < 1) || (version > 2)) + return -EINVAL; + + reader->r_ver = version; + return 0; +} + +static long logger_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct logger_log *log = file_get_log(file); + struct logger_reader *reader; + long ret = -EINVAL; + void __user *argp = (void __user *) arg; + + mutex_lock(&log->mutex); + + switch (cmd) { + case LOGGER_GET_LOG_BUF_SIZE: + ret = log->size; + break; + case LOGGER_GET_LOG_LEN: + if (!(file->f_mode & FMODE_READ)) { + ret = -EBADF; + break; + } + reader = file->private_data; + if (log->w_off >= reader->r_off) + ret = log->w_off - reader->r_off; + else + ret = (log->size - reader->r_off) + log->w_off; + break; + case LOGGER_GET_NEXT_ENTRY_LEN: + if (!(file->f_mode & FMODE_READ)) { + ret = -EBADF; + break; + } + reader = file->private_data; + + if (!reader->r_all) + reader->r_off = get_next_entry_by_uid(log, + reader->r_off, current_euid()); + + if (log->w_off != reader->r_off) + ret = get_user_hdr_len(reader->r_ver) + + get_entry_msg_len(log, reader->r_off); + else + ret = 0; + break; + case LOGGER_FLUSH_LOG: + if (!(file->f_mode & FMODE_WRITE)) { + ret = -EBADF; + break; + } + if (!(in_egroup_p(file->f_dentry->d_inode->i_gid) || + capable(CAP_SYSLOG))) { + ret = -EPERM; + break; + } + list_for_each_entry(reader, &log->readers, list) + reader->r_off = log->w_off; + log->head = log->w_off; + ret = 0; + break; + case LOGGER_GET_VERSION: + if (!(file->f_mode & FMODE_READ)) { + ret = -EBADF; + break; + } + reader = file->private_data; + ret = reader->r_ver; + break; + case LOGGER_SET_VERSION: + if (!(file->f_mode & FMODE_READ)) { + ret = -EBADF; + break; + } + reader = file->private_data; + ret = logger_set_version(reader, argp); + break; + } + + mutex_unlock(&log->mutex); + + return ret; +} + +static struct file_operations logger_fops = { + .owner = THIS_MODULE, + .read = logger_read, + .aio_write = logger_aio_write, + .poll = logger_poll, + .unlocked_ioctl = logger_ioctl, + .compat_ioctl = logger_ioctl, + .open = logger_open, + .release = logger_release, +}; + +/* + * Defines a log structure with name 'NAME' and a size of 'SIZE' bytes, which + * must be a power of two, and greater than + * (LOGGER_ENTRY_MAX_PAYLOAD + sizeof(struct logger_entry)). + */ +#define DEFINE_LOGGER_DEVICE(VAR, NAME, SIZE) \ +static unsigned char _buf_ ## VAR[SIZE]; \ +static struct logger_log VAR = { \ + .buffer = _buf_ ## VAR, \ + .misc = { \ + .minor = MISC_DYNAMIC_MINOR, \ + .name = NAME, \ + .fops = &logger_fops, \ + .parent = NULL, \ + }, \ + .wq = __WAIT_QUEUE_HEAD_INITIALIZER(VAR .wq), \ + .readers = LIST_HEAD_INIT(VAR .readers), \ + .mutex = __MUTEX_INITIALIZER(VAR .mutex), \ + .w_off = 0, \ + .head = 0, \ + .size = SIZE, \ +}; + +DEFINE_LOGGER_DEVICE(log_main, LOGGER_LOG_MAIN, 256*1024) +DEFINE_LOGGER_DEVICE(log_events, LOGGER_LOG_EVENTS, 256*1024) +DEFINE_LOGGER_DEVICE(log_radio, LOGGER_LOG_RADIO, 64*1024) +/* DEFINE_LOGGER_DEVICE(log_system, LOGGER_LOG_SYSTEM, 256*1024) */ + +static struct logger_log *get_log_from_minor(int minor) +{ + if (log_main.misc.minor == minor) + return &log_main; + if (log_events.misc.minor == minor) + return &log_events; + if (log_radio.misc.minor == minor) + return &log_radio; +/* if (log_system.misc.minor == minor) //by eason + return &log_system; */ + return NULL; +} + +static int __init init_log(struct logger_log *log) +{ + int ret; + + ret = misc_register(&log->misc); + if (unlikely(ret)) { + printk(KERN_ERR "logger: failed to register misc " + "device for log '%s'!\n", log->misc.name); + return ret; + } + + printk(KERN_INFO "logger: created %luK log '%s'\n", + (unsigned long) log->size >> 10, log->misc.name); + + return 0; +} + +static int __init logger_init(void) +{ + int ret; + + extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + char logcat_name[] = "wmt.logcat.param"; + char logcat_val[20] = "0"; + int varlen = 20, enable = 0xff, setting = 0xff; + + /** + * wmt.logcat.param = 1:0 ==> logcat (logcat_set = 1) + * wmt.logcat.param = 1:1 ==> logcat -v time (logcat_set = 2) + */ + if (wmt_getsyspara(logcat_name, logcat_val, &varlen) == 0) { + sscanf(logcat_val, "%x:%x", &enable, &setting); + if (enable & 1) + logcat_set = setting + 1; + } + printk(KERN_INFO "log enable:%X, setting:%X\n", enable, setting); + ret = init_log(&log_main); + if (unlikely(ret)) + goto out; + + ret = init_log(&log_events); + if (unlikely(ret)) + goto out; + + ret = init_log(&log_radio); + if (unlikely(ret)) + goto out; + +/* ret = init_log(&log_system); + if (unlikely(ret)) + goto out;*/ + +out: + return ret; +} +device_initcall(logger_init); diff --git a/drivers/staging/android/logger.h b/drivers/staging/android/logger.h new file mode 100644 index 00000000..e313c99f --- /dev/null +++ b/drivers/staging/android/logger.h @@ -0,0 +1,71 @@ +/* include/linux/logger.h + * + * Copyright (C) 2007-2008 Google, Inc. + * Author: Robert Love + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LINUX_LOGGER_H +#define _LINUX_LOGGER_H + +#include +#include + +/* + * The userspace structure for version 1 of the logger_entry ABI. + * This structure is returned to userspace unless the caller requests + * an upgrade to a newer ABI version. + */ +struct user_logger_entry_compat { + __u16 len; /* length of the payload */ + __u16 __pad; /* no matter what, we get 2 bytes of padding */ + __s32 pid; /* generating process's pid */ + __s32 tid; /* generating process's tid */ + __s32 sec; /* seconds since Epoch */ + __s32 nsec; /* nanoseconds */ + char msg[0]; /* the entry's payload */ +}; + +/* + * The structure for version 2 of the logger_entry ABI. + * This structure is returned to userspace if ioctl(LOGGER_SET_VERSION) + * is called with version >= 2 + */ +struct logger_entry { + __u16 len; /* length of the payload */ + __u16 hdr_size; /* sizeof(struct logger_entry_v2) */ + __s32 pid; /* generating process's pid */ + __s32 tid; /* generating process's tid */ + __s32 sec; /* seconds since Epoch */ + __s32 nsec; /* nanoseconds */ + __s32 thread_msec;/*thread time ,add by jay*/ + uid_t euid; /* effective UID of logger */ + char msg[0]; /* the entry's payload */ +}; + +#define LOGGER_LOG_RADIO "log_radio" /* radio-related messages */ +#define LOGGER_LOG_EVENTS "log_events" /* system/hardware events */ +//#define LOGGER_LOG_SYSTEM "log_system" /* system/framework messages */ +#define LOGGER_LOG_MAIN "log_main" /* everything else */ + +#define LOGGER_ENTRY_MAX_PAYLOAD 4076 + +#define __LOGGERIO 0xAE + +#define LOGGER_GET_LOG_BUF_SIZE _IO(__LOGGERIO, 1) /* size of log */ +#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */ +#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */ +#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */ +#define LOGGER_GET_VERSION _IO(__LOGGERIO, 5) /* abi version */ +#define LOGGER_SET_VERSION _IO(__LOGGERIO, 6) /* abi version */ + +#endif /* _LINUX_LOGGER_H */ diff --git a/drivers/staging/android/lowmemorykiller.c b/drivers/staging/android/lowmemorykiller.c new file mode 100644 index 00000000..12f0a13a --- /dev/null +++ b/drivers/staging/android/lowmemorykiller.c @@ -0,0 +1,274 @@ +/* drivers/misc/lowmemorykiller.c + * + * The lowmemorykiller driver lets user-space specify a set of memory thresholds + * where processes with a range of oom_score_adj values will get killed. Specify + * the minimum oom_score_adj values in + * /sys/module/lowmemorykiller/parameters/adj and the number of free pages in + * /sys/module/lowmemorykiller/parameters/minfree. Both files take a comma + * separated list of numbers in ascending order. + * + * For example, write "0,8" to /sys/module/lowmemorykiller/parameters/adj and + * "1024,4096" to /sys/module/lowmemorykiller/parameters/minfree to kill + * processes with a oom_score_adj value of 8 or higher when the free memory + * drops below 4096 pages and kill processes with a oom_score_adj value of 0 or + * higher when the free memory drops below 1024 pages. + * + * The driver considers memory used for caches to be free, but if a large + * percentage of the cached memory is locked this can be very inaccurate + * and processes may not get killed until the normal oom killer is triggered. + * + * Copyright (C) 2007-2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static uint32_t lowmem_debug_level = 1; +static int lowmem_adj[6] = { + 0, + 1, + 6, + 12, +}; +static int lowmem_adj_size = 4; +static int lowmem_minfree[6] = { + 3 * 512, /* 6MB */ + 2 * 1024, /* 8MB */ + 4 * 1024, /* 16MB */ + 16 * 1024, /* 64MB */ +}; +static int lowmem_minfree_size = 4; + +static unsigned long lowmem_deathpending_timeout; + +#define lowmem_print(level, x...) \ + do { \ + if (lowmem_debug_level >= (level)) \ + printk(x); \ + } while (0) + +static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc) +{ + struct task_struct *tsk; + struct task_struct *selected = NULL; + int rem = 0; + int tasksize; + int i; + int min_score_adj = OOM_SCORE_ADJ_MAX + 1; + int selected_tasksize = 0; + int selected_oom_score_adj; + int array_size = ARRAY_SIZE(lowmem_adj); + int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages; + int other_file = global_page_state(NR_FILE_PAGES) - + global_page_state(NR_SHMEM); + + if (lowmem_adj_size < array_size) + array_size = lowmem_adj_size; + if (lowmem_minfree_size < array_size) + array_size = lowmem_minfree_size; + for (i = 0; i < array_size; i++) { + if (other_free < lowmem_minfree[i] && + other_file < lowmem_minfree[i]) { + min_score_adj = lowmem_adj[i]; + break; + } + } + if (sc->nr_to_scan > 0) + lowmem_print(3, "lowmem_shrink %lu, %x, ofree %d %d, ma %d\n", + sc->nr_to_scan, sc->gfp_mask, other_free, + other_file, min_score_adj); + rem = global_page_state(NR_ACTIVE_ANON) + + global_page_state(NR_ACTIVE_FILE) + + global_page_state(NR_INACTIVE_ANON) + + global_page_state(NR_INACTIVE_FILE); + if (sc->nr_to_scan <= 0 || min_score_adj == OOM_SCORE_ADJ_MAX + 1) { + lowmem_print(5, "lowmem_shrink %lu, %x, return %d\n", + sc->nr_to_scan, sc->gfp_mask, rem); + return rem; + } + selected_oom_score_adj = min_score_adj; + + rcu_read_lock(); + for_each_process(tsk) { + struct task_struct *p; + int oom_score_adj; + + if (tsk->flags & PF_KTHREAD) + continue; + + p = find_lock_task_mm(tsk); + if (!p) + continue; + + if (test_tsk_thread_flag(p, TIF_MEMDIE) && + time_before_eq(jiffies, lowmem_deathpending_timeout)) { + task_unlock(p); + rcu_read_unlock(); + return 0; + } + oom_score_adj = p->signal->oom_score_adj; + if (oom_score_adj < min_score_adj) { + task_unlock(p); + continue; + } + tasksize = get_mm_rss(p->mm); + task_unlock(p); + if (tasksize <= 0) + continue; + if (selected) { + if (oom_score_adj < selected_oom_score_adj) + continue; + if (oom_score_adj == selected_oom_score_adj && + tasksize <= selected_tasksize) + continue; + } + selected = p; + selected_tasksize = tasksize; + selected_oom_score_adj = oom_score_adj; + lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n", + p->pid, p->comm, oom_score_adj, tasksize); + } + if (selected) { + lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n", + selected->pid, selected->comm, + selected_oom_score_adj, selected_tasksize); + lowmem_deathpending_timeout = jiffies + HZ; + send_sig(SIGKILL, selected, 0); + set_tsk_thread_flag(selected, TIF_MEMDIE); + rem -= selected_tasksize; + } + lowmem_print(4, "lowmem_shrink %lu, %x, return %d\n", + sc->nr_to_scan, sc->gfp_mask, rem); + rcu_read_unlock(); + return rem; +} + +static struct shrinker lowmem_shrinker = { + .shrink = lowmem_shrink, + .seeks = DEFAULT_SEEKS * 16 +}; + +static int __init lowmem_init(void) +{ + register_shrinker(&lowmem_shrinker); + return 0; +} + +static void __exit lowmem_exit(void) +{ + unregister_shrinker(&lowmem_shrinker); +} + +#ifdef CONFIG_ANDROID_LOW_MEMORY_KILLER_AUTODETECT_OOM_ADJ_VALUES +static int lowmem_oom_adj_to_oom_score_adj(int oom_adj) +{ + if (oom_adj == OOM_ADJUST_MAX) + return OOM_SCORE_ADJ_MAX; + else + return (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE; +} + +static void lowmem_autodetect_oom_adj_values(void) +{ + int i; + int oom_adj; + int oom_score_adj; + int array_size = ARRAY_SIZE(lowmem_adj); + + if (lowmem_adj_size < array_size) + array_size = lowmem_adj_size; + + if (array_size <= 0) + return; + + oom_adj = lowmem_adj[array_size - 1]; + if (oom_adj > OOM_ADJUST_MAX) + return; + + oom_score_adj = lowmem_oom_adj_to_oom_score_adj(oom_adj); + if (oom_score_adj <= OOM_ADJUST_MAX) + return; + + lowmem_print(1, "lowmem_shrink: convert oom_adj to oom_score_adj:\n"); + for (i = 0; i < array_size; i++) { + oom_adj = lowmem_adj[i]; + oom_score_adj = lowmem_oom_adj_to_oom_score_adj(oom_adj); + lowmem_adj[i] = oom_score_adj; + lowmem_print(1, "oom_adj %d => oom_score_adj %d\n", + oom_adj, oom_score_adj); + } +} + +static int lowmem_adj_array_set(const char *val, const struct kernel_param *kp) +{ + int ret; + + ret = param_array_ops.set(val, kp); + + /* HACK: Autodetect oom_adj values in lowmem_adj array */ + lowmem_autodetect_oom_adj_values(); + + return ret; +} + +static int lowmem_adj_array_get(char *buffer, const struct kernel_param *kp) +{ + return param_array_ops.get(buffer, kp); +} + +static void lowmem_adj_array_free(void *arg) +{ + param_array_ops.free(arg); +} + +static struct kernel_param_ops lowmem_adj_array_ops = { + .set = lowmem_adj_array_set, + .get = lowmem_adj_array_get, + .free = lowmem_adj_array_free, +}; + +static const struct kparam_array __param_arr_adj = { + .max = ARRAY_SIZE(lowmem_adj), + .num = &lowmem_adj_size, + .ops = ¶m_ops_int, + .elemsize = sizeof(lowmem_adj[0]), + .elem = lowmem_adj, +}; +#endif + +module_param_named(cost, lowmem_shrinker.seeks, int, S_IRUGO | S_IWUSR); +#ifdef CONFIG_ANDROID_LOW_MEMORY_KILLER_AUTODETECT_OOM_ADJ_VALUES +__module_param_call(MODULE_PARAM_PREFIX, adj, + &lowmem_adj_array_ops, + .arr = &__param_arr_adj, + S_IRUGO | S_IWUSR, -1); +__MODULE_PARM_TYPE(adj, "array of int"); +#else +module_param_array_named(adj, lowmem_adj, int, &lowmem_adj_size, + S_IRUGO | S_IWUSR); +#endif +module_param_array_named(minfree, lowmem_minfree, uint, &lowmem_minfree_size, + S_IRUGO | S_IWUSR); +module_param_named(debug_level, lowmem_debug_level, uint, S_IRUGO | S_IWUSR); + +module_init(lowmem_init); +module_exit(lowmem_exit); + +MODULE_LICENSE("GPL"); + diff --git a/drivers/staging/android/persistent_ram.c b/drivers/staging/android/persistent_ram.c new file mode 100644 index 00000000..e9e08fd0 --- /dev/null +++ b/drivers/staging/android/persistent_ram.c @@ -0,0 +1,460 @@ +/* + * Copyright (C) 2012 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct persistent_ram_buffer { + uint32_t sig; + atomic_t start; + atomic_t size; + uint8_t data[0]; +}; + +#define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */ + +static __devinitdata LIST_HEAD(persistent_ram_list); + +static inline size_t buffer_size(struct persistent_ram_zone *prz) +{ + return atomic_read(&prz->buffer->size); +} + +static inline size_t buffer_start(struct persistent_ram_zone *prz) +{ + return atomic_read(&prz->buffer->start); +} + +/* increase and wrap the start pointer, returning the old value */ +static inline size_t buffer_start_add(struct persistent_ram_zone *prz, size_t a) +{ + int old; + int new; + + do { + old = atomic_read(&prz->buffer->start); + new = old + a; + while (unlikely(new > prz->buffer_size)) + new -= prz->buffer_size; + } while (atomic_cmpxchg(&prz->buffer->start, old, new) != old); + + return old; +} + +/* increase the size counter until it hits the max size */ +static inline void buffer_size_add(struct persistent_ram_zone *prz, size_t a) +{ + size_t old; + size_t new; + + if (atomic_read(&prz->buffer->size) == prz->buffer_size) + return; + + do { + old = atomic_read(&prz->buffer->size); + new = old + a; + if (new > prz->buffer_size) + new = prz->buffer_size; + } while (atomic_cmpxchg(&prz->buffer->size, old, new) != old); +} + +static void notrace persistent_ram_encode_rs8(struct persistent_ram_zone *prz, + uint8_t *data, size_t len, uint8_t *ecc) +{ + int i; + uint16_t par[prz->ecc_size]; + + /* Initialize the parity buffer */ + memset(par, 0, sizeof(par)); + encode_rs8(prz->rs_decoder, data, len, par, 0); + for (i = 0; i < prz->ecc_size; i++) + ecc[i] = par[i]; +} + +static int persistent_ram_decode_rs8(struct persistent_ram_zone *prz, + void *data, size_t len, uint8_t *ecc) +{ + int i; + uint16_t par[prz->ecc_size]; + + for (i = 0; i < prz->ecc_size; i++) + par[i] = ecc[i]; + return decode_rs8(prz->rs_decoder, data, par, len, + NULL, 0, NULL, 0, NULL); +} + +static void notrace persistent_ram_update_ecc(struct persistent_ram_zone *prz, + unsigned int start, unsigned int count) +{ + struct persistent_ram_buffer *buffer = prz->buffer; + uint8_t *buffer_end = buffer->data + prz->buffer_size; + uint8_t *block; + uint8_t *par; + int ecc_block_size = prz->ecc_block_size; + int ecc_size = prz->ecc_size; + int size = prz->ecc_block_size; + + if (!prz->ecc) + return; + + block = buffer->data + (start & ~(ecc_block_size - 1)); + par = prz->par_buffer + (start / ecc_block_size) * prz->ecc_size; + + do { + if (block + ecc_block_size > buffer_end) + size = buffer_end - block; + persistent_ram_encode_rs8(prz, block, size, par); + block += ecc_block_size; + par += ecc_size; + } while (block < buffer->data + start + count); +} + +static void persistent_ram_update_header_ecc(struct persistent_ram_zone *prz) +{ + struct persistent_ram_buffer *buffer = prz->buffer; + + if (!prz->ecc) + return; + + persistent_ram_encode_rs8(prz, (uint8_t *)buffer, sizeof(*buffer), + prz->par_header); +} + +static void persistent_ram_ecc_old(struct persistent_ram_zone *prz) +{ + struct persistent_ram_buffer *buffer = prz->buffer; + uint8_t *block; + uint8_t *par; + + if (!prz->ecc) + return; + + block = buffer->data; + par = prz->par_buffer; + while (block < buffer->data + buffer_size(prz)) { + int numerr; + int size = prz->ecc_block_size; + if (block + size > buffer->data + prz->buffer_size) + size = buffer->data + prz->buffer_size - block; + numerr = persistent_ram_decode_rs8(prz, block, size, par); + if (numerr > 0) { + pr_devel("persistent_ram: error in block %p, %d\n", + block, numerr); + prz->corrected_bytes += numerr; + } else if (numerr < 0) { + pr_devel("persistent_ram: uncorrectable error in block %p\n", + block); + prz->bad_blocks++; + } + block += prz->ecc_block_size; + par += prz->ecc_size; + } +} + +static int persistent_ram_init_ecc(struct persistent_ram_zone *prz, + size_t buffer_size, struct persistent_ram *ram) +{ + int numerr; + struct persistent_ram_buffer *buffer = prz->buffer; + int ecc_blocks; + + if (!prz->ecc) + return 0; + + prz->ecc_block_size = ram->ecc_block_size ?: 128; + prz->ecc_size = ram->ecc_size ?: 16; + prz->ecc_symsize = ram->ecc_symsize ?: 8; + prz->ecc_poly = ram->ecc_poly ?: 0x11d; + + ecc_blocks = DIV_ROUND_UP(prz->buffer_size - prz->ecc_size, + prz->ecc_block_size + prz->ecc_size); + prz->buffer_size -= (ecc_blocks + 1) * prz->ecc_size; + + if (prz->buffer_size > buffer_size) { + pr_err("persistent_ram: invalid size %zu, non-ecc datasize %zu\n", + buffer_size, prz->buffer_size); + return -EINVAL; + } + + prz->par_buffer = buffer->data + prz->buffer_size; + prz->par_header = prz->par_buffer + ecc_blocks * prz->ecc_size; + + /* + * first consecutive root is 0 + * primitive element to generate roots = 1 + */ + prz->rs_decoder = init_rs(prz->ecc_symsize, prz->ecc_poly, 0, 1, + prz->ecc_size); + if (prz->rs_decoder == NULL) { + pr_info("persistent_ram: init_rs failed\n"); + return -EINVAL; + } + + prz->corrected_bytes = 0; + prz->bad_blocks = 0; + + numerr = persistent_ram_decode_rs8(prz, buffer, sizeof(*buffer), + prz->par_header); + if (numerr > 0) { + pr_info("persistent_ram: error in header, %d\n", numerr); + prz->corrected_bytes += numerr; + } else if (numerr < 0) { + pr_info("persistent_ram: uncorrectable error in header\n"); + prz->bad_blocks++; + } + + return 0; +} + +ssize_t persistent_ram_ecc_string(struct persistent_ram_zone *prz, + char *str, size_t len) +{ + ssize_t ret; + + if (prz->corrected_bytes || prz->bad_blocks) + ret = snprintf(str, len, "" + "\n%d Corrected bytes, %d unrecoverable blocks\n", + prz->corrected_bytes, prz->bad_blocks); + else + ret = snprintf(str, len, "\nNo errors detected\n"); + + return ret; +} + +static void notrace persistent_ram_update(struct persistent_ram_zone *prz, + const void *s, unsigned int start, unsigned int count) +{ + struct persistent_ram_buffer *buffer = prz->buffer; + memcpy(buffer->data + start, s, count); + persistent_ram_update_ecc(prz, start, count); +} + +static void __devinit +persistent_ram_save_old(struct persistent_ram_zone *prz) +{ + struct persistent_ram_buffer *buffer = prz->buffer; + size_t size = buffer_size(prz); + size_t start = buffer_start(prz); + char *dest; + + persistent_ram_ecc_old(prz); + + dest = kmalloc(size, GFP_KERNEL); + if (dest == NULL) { + pr_err("persistent_ram: failed to allocate buffer\n"); + return; + } + + prz->old_log = dest; + prz->old_log_size = size; + memcpy(prz->old_log, &buffer->data[start], size - start); + memcpy(prz->old_log + size - start, &buffer->data[0], start); +} + +int notrace persistent_ram_write(struct persistent_ram_zone *prz, + const void *s, unsigned int count) +{ + int rem; + int c = count; + size_t start; + + if (unlikely(c > prz->buffer_size)) { + s += c - prz->buffer_size; + c = prz->buffer_size; + } + + buffer_size_add(prz, c); + + start = buffer_start_add(prz, c); + + rem = prz->buffer_size - start; + if (unlikely(rem < c)) { + persistent_ram_update(prz, s, start, rem); + s += rem; + c -= rem; + start = 0; + } + persistent_ram_update(prz, s, start, c); + + persistent_ram_update_header_ecc(prz); + + return count; +} + +size_t persistent_ram_old_size(struct persistent_ram_zone *prz) +{ + return prz->old_log_size; +} + +void *persistent_ram_old(struct persistent_ram_zone *prz) +{ + return prz->old_log; +} + +void persistent_ram_free_old(struct persistent_ram_zone *prz) +{ + kfree(prz->old_log); + prz->old_log = NULL; + prz->old_log_size = 0; +} + +static int persistent_ram_buffer_map(phys_addr_t start, phys_addr_t size, + struct persistent_ram_zone *prz) +{ + struct page **pages; + phys_addr_t page_start; + unsigned int page_count; + pgprot_t prot; + unsigned int i; + + page_start = start - offset_in_page(start); + page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE); + + prot = pgprot_noncached(PAGE_KERNEL); + + pages = kmalloc(sizeof(struct page *) * page_count, GFP_KERNEL); + if (!pages) { + pr_err("%s: Failed to allocate array for %u pages\n", __func__, + page_count); + return -ENOMEM; + } + + for (i = 0; i < page_count; i++) { + phys_addr_t addr = page_start + i * PAGE_SIZE; + pages[i] = pfn_to_page(addr >> PAGE_SHIFT); + } + prz->vaddr = vmap(pages, page_count, VM_MAP, prot); + kfree(pages); + if (!prz->vaddr) { + pr_err("%s: Failed to map %u pages\n", __func__, page_count); + return -ENOMEM; + } + + prz->buffer = prz->vaddr + offset_in_page(start); + prz->buffer_size = size - sizeof(struct persistent_ram_buffer); + + return 0; +} + +static int __devinit persistent_ram_buffer_init(const char *name, + struct persistent_ram_zone *prz, struct persistent_ram **ramp) +{ + int i; + struct persistent_ram *ram; + struct persistent_ram_descriptor *desc; + phys_addr_t start; + + list_for_each_entry(ram, &persistent_ram_list, node) { + start = ram->start; + for (i = 0; i < ram->num_descs; i++) { + desc = &ram->descs[i]; + if (!strcmp(desc->name, name)) { + *ramp = ram; + return persistent_ram_buffer_map(start, + desc->size, prz); + } + start += desc->size; + } + } + + return -EINVAL; +} + +static __devinit +struct persistent_ram_zone *__persistent_ram_init(struct device *dev, bool ecc) +{ + struct persistent_ram *ram; + struct persistent_ram_zone *prz; + int ret = -ENOMEM; + + prz = kzalloc(sizeof(struct persistent_ram_zone), GFP_KERNEL); + if (!prz) { + pr_err("persistent_ram: failed to allocate persistent ram zone\n"); + goto err; + } + + INIT_LIST_HEAD(&prz->node); + + ret = persistent_ram_buffer_init(dev_name(dev), prz, &ram); + if (ret) { + pr_err("persistent_ram: failed to initialize buffer\n"); + goto err; + } + + prz->ecc = ecc; + ret = persistent_ram_init_ecc(prz, prz->buffer_size, ram); + if (ret) + goto err; + + if (prz->buffer->sig == PERSISTENT_RAM_SIG) { + if (buffer_size(prz) > prz->buffer_size || + buffer_start(prz) > buffer_size(prz)) + pr_info("persistent_ram: found existing invalid buffer," + " size %zu, start %zu\n", + buffer_size(prz), buffer_start(prz)); + else { + pr_info("persistent_ram: found existing buffer," + " size %zu, start %zu\n", + buffer_size(prz), buffer_start(prz)); + persistent_ram_save_old(prz); + } + } else { + pr_info("persistent_ram: no valid data in buffer" + " (sig = 0x%08x)\n", prz->buffer->sig); + } + + prz->buffer->sig = PERSISTENT_RAM_SIG; + atomic_set(&prz->buffer->start, 0); + atomic_set(&prz->buffer->size, 0); + + return prz; +err: + kfree(prz); + return ERR_PTR(ret); +} + +struct persistent_ram_zone * __devinit +persistent_ram_init_ringbuffer(struct device *dev, bool ecc) +{ + return __persistent_ram_init(dev, ecc); +} + +int __init persistent_ram_early_init(struct persistent_ram *ram) +{ + int ret; + + ret = memblock_reserve(ram->start, ram->size); + if (ret) { + pr_err("Failed to reserve persistent memory from %08lx-%08lx\n", + (long)ram->start, (long)(ram->start + ram->size - 1)); + return ret; + } + + list_add_tail(&ram->node, &persistent_ram_list); + + pr_info("Initialized persistent memory from %08lx-%08lx\n", + (long)ram->start, (long)(ram->start + ram->size - 1)); + + return 0; +} diff --git a/drivers/staging/android/ram_console.c b/drivers/staging/android/ram_console.c new file mode 100644 index 00000000..cf0f8fb4 --- /dev/null +++ b/drivers/staging/android/ram_console.c @@ -0,0 +1,177 @@ +/* drivers/android/ram_console.c + * + * Copyright (C) 2007-2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ram_console.h" + +static struct persistent_ram_zone *ram_console_zone; +static const char *bootinfo; +static size_t bootinfo_size; + +static void +ram_console_write(struct console *console, const char *s, unsigned int count) +{ + struct persistent_ram_zone *prz = console->data; + persistent_ram_write(prz, s, count); +} + +static struct console ram_console = { + .name = "ram", + .write = ram_console_write, + .flags = CON_PRINTBUFFER | CON_ENABLED | CON_ANYTIME, + .index = -1, +}; + +void ram_console_enable_console(int enabled) +{ + if (enabled) + ram_console.flags |= CON_ENABLED; + else + ram_console.flags &= ~CON_ENABLED; +} + +static int __devinit ram_console_probe(struct platform_device *pdev) +{ + struct ram_console_platform_data *pdata = pdev->dev.platform_data; + struct persistent_ram_zone *prz; + + prz = persistent_ram_init_ringbuffer(&pdev->dev, true); + if (IS_ERR(prz)) + return PTR_ERR(prz); + + + if (pdata) { + bootinfo = kstrdup(pdata->bootinfo, GFP_KERNEL); + if (bootinfo) + bootinfo_size = strlen(bootinfo); + } + + ram_console_zone = prz; + ram_console.data = prz; + + register_console(&ram_console); + + return 0; +} + +static struct platform_driver ram_console_driver = { + .driver = { + .name = "ram_console", + }, + .probe = ram_console_probe, +}; + +static int __init ram_console_module_init(void) +{ + return platform_driver_register(&ram_console_driver); +} + +#ifndef CONFIG_PRINTK +#define dmesg_restrict 0 +#endif + +static ssize_t ram_console_read_old(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + loff_t pos = *offset; + ssize_t count; + struct persistent_ram_zone *prz = ram_console_zone; + size_t old_log_size = persistent_ram_old_size(prz); + const char *old_log = persistent_ram_old(prz); + char *str; + int ret; + + /* Main last_kmsg log */ + if (pos < old_log_size) { + count = min(len, (size_t)(old_log_size - pos)); + if (copy_to_user(buf, old_log + pos, count)) + return -EFAULT; + goto out; + } + + /* ECC correction notice */ + pos -= old_log_size; + count = persistent_ram_ecc_string(prz, NULL, 0); + if (pos < count) { + str = kmalloc(count, GFP_KERNEL); + if (!str) + return -ENOMEM; + persistent_ram_ecc_string(prz, str, count + 1); + count = min(len, (size_t)(count - pos)); + ret = copy_to_user(buf, str + pos, count); + kfree(str); + if (ret) + return -EFAULT; + goto out; + } + + /* Boot info passed through pdata */ + pos -= count; + if (pos < bootinfo_size) { + count = min(len, (size_t)(bootinfo_size - pos)); + if (copy_to_user(buf, bootinfo + pos, count)) + return -EFAULT; + goto out; + } + + /* EOF */ + return 0; + +out: + *offset += count; + return count; +} + +static const struct file_operations ram_console_file_ops = { + .owner = THIS_MODULE, + .read = ram_console_read_old, +}; + +static int __init ram_console_late_init(void) +{ + struct proc_dir_entry *entry; + struct persistent_ram_zone *prz = ram_console_zone; + + if (!prz) + return 0; + + if (persistent_ram_old_size(prz) == 0) + return 0; + + entry = create_proc_entry("last_kmsg", S_IFREG | S_IRUGO, NULL); + if (!entry) { + printk(KERN_ERR "ram_console: failed to create proc entry\n"); + persistent_ram_free_old(prz); + return 0; + } + + entry->proc_fops = &ram_console_file_ops; + entry->size = persistent_ram_old_size(prz) + + persistent_ram_ecc_string(prz, NULL, 0) + + bootinfo_size; + + return 0; +} + +late_initcall(ram_console_late_init); +postcore_initcall(ram_console_module_init); diff --git a/drivers/staging/android/ram_console.h b/drivers/staging/android/ram_console.h new file mode 100644 index 00000000..9f1125c1 --- /dev/null +++ b/drivers/staging/android/ram_console.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _INCLUDE_LINUX_PLATFORM_DATA_RAM_CONSOLE_H_ +#define _INCLUDE_LINUX_PLATFORM_DATA_RAM_CONSOLE_H_ + +struct ram_console_platform_data { + const char *bootinfo; +}; + +#endif /* _INCLUDE_LINUX_PLATFORM_DATA_RAM_CONSOLE_H_ */ diff --git a/drivers/staging/android/switch/Kconfig b/drivers/staging/android/switch/Kconfig new file mode 100644 index 00000000..36846f62 --- /dev/null +++ b/drivers/staging/android/switch/Kconfig @@ -0,0 +1,11 @@ +menuconfig ANDROID_SWITCH + tristate "Android Switch class support" + help + Say Y here to enable Android switch class support. This allows + monitoring switches by userspace via sysfs and uevent. + +config ANDROID_SWITCH_GPIO + tristate "Android GPIO Switch support" + depends on GENERIC_GPIO && ANDROID_SWITCH + help + Say Y here to enable GPIO based switch support. diff --git a/drivers/staging/android/switch/Makefile b/drivers/staging/android/switch/Makefile new file mode 100644 index 00000000..d76bfdce --- /dev/null +++ b/drivers/staging/android/switch/Makefile @@ -0,0 +1,4 @@ +# Android Switch Class Driver +obj-$(CONFIG_ANDROID_SWITCH) += switch_class.o +obj-$(CONFIG_ANDROID_SWITCH_GPIO) += switch_gpio.o + diff --git a/drivers/staging/android/switch/switch.h b/drivers/staging/android/switch/switch.h new file mode 100644 index 00000000..4fcb3109 --- /dev/null +++ b/drivers/staging/android/switch/switch.h @@ -0,0 +1,53 @@ +/* + * Switch class driver + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#ifndef __LINUX_SWITCH_H__ +#define __LINUX_SWITCH_H__ + +struct switch_dev { + const char *name; + struct device *dev; + int index; + int state; + + ssize_t (*print_name)(struct switch_dev *sdev, char *buf); + ssize_t (*print_state)(struct switch_dev *sdev, char *buf); +}; + +struct gpio_switch_platform_data { + const char *name; + unsigned gpio; + + /* if NULL, switch_dev.name will be printed */ + const char *name_on; + const char *name_off; + /* if NULL, "0" or "1" will be printed */ + const char *state_on; + const char *state_off; +}; + +extern int switch_dev_register(struct switch_dev *sdev); +extern void switch_dev_unregister(struct switch_dev *sdev); + +static inline int switch_get_state(struct switch_dev *sdev) +{ + return sdev->state; +} + +extern void switch_set_state(struct switch_dev *sdev, int state); + +#endif /* __LINUX_SWITCH_H__ */ diff --git a/drivers/staging/android/switch/switch_class.c b/drivers/staging/android/switch/switch_class.c new file mode 100644 index 00000000..74680446 --- /dev/null +++ b/drivers/staging/android/switch/switch_class.c @@ -0,0 +1,174 @@ +/* + * switch_class.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include "switch.h" + +struct class *switch_class; +static atomic_t device_count; + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct switch_dev *sdev = (struct switch_dev *) + dev_get_drvdata(dev); + + if (sdev->print_state) { + int ret = sdev->print_state(sdev, buf); + if (ret >= 0) + return ret; + } + return sprintf(buf, "%d\n", sdev->state); +} + +static ssize_t name_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct switch_dev *sdev = (struct switch_dev *) + dev_get_drvdata(dev); + + if (sdev->print_name) { + int ret = sdev->print_name(sdev, buf); + if (ret >= 0) + return ret; + } + return sprintf(buf, "%s\n", sdev->name); +} + +static DEVICE_ATTR(state, S_IRUGO | S_IWUSR, state_show, NULL); +static DEVICE_ATTR(name, S_IRUGO | S_IWUSR, name_show, NULL); + +void switch_set_state(struct switch_dev *sdev, int state) +{ + char name_buf[120]; + char state_buf[120]; + char *prop_buf; + char *envp[3]; + int env_offset = 0; + int length; + + if (sdev->state != state) { + sdev->state = state; + + prop_buf = (char *)get_zeroed_page(GFP_KERNEL); + if (prop_buf) { + length = name_show(sdev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(name_buf, sizeof(name_buf), + "SWITCH_NAME=%s", prop_buf); + envp[env_offset++] = name_buf; + } + length = state_show(sdev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(state_buf, sizeof(state_buf), + "SWITCH_STATE=%s", prop_buf); + envp[env_offset++] = state_buf; + } + envp[env_offset] = NULL; + kobject_uevent_env(&sdev->dev->kobj, KOBJ_CHANGE, envp); + free_page((unsigned long)prop_buf); + } else { + printk(KERN_ERR "out of memory in switch_set_state\n"); + kobject_uevent(&sdev->dev->kobj, KOBJ_CHANGE); + } + } +} +EXPORT_SYMBOL_GPL(switch_set_state); + +static int create_switch_class(void) +{ + if (!switch_class) { + switch_class = class_create(THIS_MODULE, "switch"); + if (IS_ERR(switch_class)) + return PTR_ERR(switch_class); + atomic_set(&device_count, 0); + } + + return 0; +} + +int switch_dev_register(struct switch_dev *sdev) +{ + int ret; + + if (!switch_class) { + ret = create_switch_class(); + if (ret < 0) + return ret; + } + + sdev->index = atomic_inc_return(&device_count); + sdev->dev = device_create(switch_class, NULL, + MKDEV(0, sdev->index), NULL, sdev->name); + if (IS_ERR(sdev->dev)) + return PTR_ERR(sdev->dev); + + ret = device_create_file(sdev->dev, &dev_attr_state); + if (ret < 0) + goto err_create_file_1; + ret = device_create_file(sdev->dev, &dev_attr_name); + if (ret < 0) + goto err_create_file_2; + + dev_set_drvdata(sdev->dev, sdev); + sdev->state = 0; + return 0; + +err_create_file_2: + device_remove_file(sdev->dev, &dev_attr_state); +err_create_file_1: + device_destroy(switch_class, MKDEV(0, sdev->index)); + printk(KERN_ERR "switch: Failed to register driver %s\n", sdev->name); + + return ret; +} +EXPORT_SYMBOL_GPL(switch_dev_register); + +void switch_dev_unregister(struct switch_dev *sdev) +{ + device_remove_file(sdev->dev, &dev_attr_name); + device_remove_file(sdev->dev, &dev_attr_state); + device_destroy(switch_class, MKDEV(0, sdev->index)); + dev_set_drvdata(sdev->dev, NULL); +} +EXPORT_SYMBOL_GPL(switch_dev_unregister); + +static int __init switch_class_init(void) +{ + return create_switch_class(); +} + +static void __exit switch_class_exit(void) +{ + class_destroy(switch_class); +} + +module_init(switch_class_init); +module_exit(switch_class_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("Switch class driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/android/switch/switch_gpio.c b/drivers/staging/android/switch/switch_gpio.c new file mode 100644 index 00000000..38b2c2f6 --- /dev/null +++ b/drivers/staging/android/switch/switch_gpio.c @@ -0,0 +1,172 @@ +/* + * switch_gpio.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "switch.h" + +struct gpio_switch_data { + struct switch_dev sdev; + unsigned gpio; + const char *name_on; + const char *name_off; + const char *state_on; + const char *state_off; + int irq; + struct work_struct work; +}; + +static void gpio_switch_work(struct work_struct *work) +{ + int state; + struct gpio_switch_data *data = + container_of(work, struct gpio_switch_data, work); + + state = gpio_get_value(data->gpio); + switch_set_state(&data->sdev, state); +} + +static irqreturn_t gpio_irq_handler(int irq, void *dev_id) +{ + struct gpio_switch_data *switch_data = + (struct gpio_switch_data *)dev_id; + + schedule_work(&switch_data->work); + return IRQ_HANDLED; +} + +static ssize_t switch_gpio_print_state(struct switch_dev *sdev, char *buf) +{ + struct gpio_switch_data *switch_data = + container_of(sdev, struct gpio_switch_data, sdev); + const char *state; + if (switch_get_state(sdev)) + state = switch_data->state_on; + else + state = switch_data->state_off; + + if (state) + return sprintf(buf, "%s\n", state); + return -1; +} + +static int gpio_switch_probe(struct platform_device *pdev) +{ + struct gpio_switch_platform_data *pdata = pdev->dev.platform_data; + struct gpio_switch_data *switch_data; + int ret = 0; + + if (!pdata) + return -EBUSY; + + switch_data = kzalloc(sizeof(struct gpio_switch_data), GFP_KERNEL); + if (!switch_data) + return -ENOMEM; + + switch_data->sdev.name = pdata->name; + switch_data->gpio = pdata->gpio; + switch_data->name_on = pdata->name_on; + switch_data->name_off = pdata->name_off; + switch_data->state_on = pdata->state_on; + switch_data->state_off = pdata->state_off; + switch_data->sdev.print_state = switch_gpio_print_state; + + ret = switch_dev_register(&switch_data->sdev); + if (ret < 0) + goto err_switch_dev_register; + + ret = gpio_request(switch_data->gpio, pdev->name); + if (ret < 0) + goto err_request_gpio; + + ret = gpio_direction_input(switch_data->gpio); + if (ret < 0) + goto err_set_gpio_input; + + INIT_WORK(&switch_data->work, gpio_switch_work); + + switch_data->irq = gpio_to_irq(switch_data->gpio); + if (switch_data->irq < 0) { + ret = switch_data->irq; + goto err_detect_irq_num_failed; + } + + ret = request_irq(switch_data->irq, gpio_irq_handler, + IRQF_TRIGGER_LOW, pdev->name, switch_data); + if (ret < 0) + goto err_request_irq; + + /* Perform initial detection */ + gpio_switch_work(&switch_data->work); + + return 0; + +err_request_irq: +err_detect_irq_num_failed: +err_set_gpio_input: + gpio_free(switch_data->gpio); +err_request_gpio: + switch_dev_unregister(&switch_data->sdev); +err_switch_dev_register: + kfree(switch_data); + + return ret; +} + +static int __devexit gpio_switch_remove(struct platform_device *pdev) +{ + struct gpio_switch_data *switch_data = platform_get_drvdata(pdev); + + cancel_work_sync(&switch_data->work); + gpio_free(switch_data->gpio); + switch_dev_unregister(&switch_data->sdev); + kfree(switch_data); + + return 0; +} + +static struct platform_driver gpio_switch_driver = { + .probe = gpio_switch_probe, + .remove = __devexit_p(gpio_switch_remove), + .driver = { + .name = "switch-gpio", + .owner = THIS_MODULE, + }, +}; + +static int __init gpio_switch_init(void) +{ + return platform_driver_register(&gpio_switch_driver); +} + +static void __exit gpio_switch_exit(void) +{ + platform_driver_unregister(&gpio_switch_driver); +} + +module_init(gpio_switch_init); +module_exit(gpio_switch_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("GPIO Switch driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/android/timed_gpio.c b/drivers/staging/android/timed_gpio.c new file mode 100644 index 00000000..45c522cb --- /dev/null +++ b/drivers/staging/android/timed_gpio.c @@ -0,0 +1,179 @@ +/* drivers/misc/timed_gpio.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "timed_output.h" +#include "timed_gpio.h" + + +struct timed_gpio_data { + struct timed_output_dev dev; + struct hrtimer timer; + spinlock_t lock; + unsigned gpio; + int max_timeout; + u8 active_low; +}; + +static enum hrtimer_restart gpio_timer_func(struct hrtimer *timer) +{ + struct timed_gpio_data *data = + container_of(timer, struct timed_gpio_data, timer); + + gpio_direction_output(data->gpio, data->active_low ? 1 : 0); + return HRTIMER_NORESTART; +} + +static int gpio_get_time(struct timed_output_dev *dev) +{ + struct timed_gpio_data *data = + container_of(dev, struct timed_gpio_data, dev); + + if (hrtimer_active(&data->timer)) { + ktime_t r = hrtimer_get_remaining(&data->timer); + struct timeval t = ktime_to_timeval(r); + return t.tv_sec * 1000 + t.tv_usec / 1000; + } else + return 0; +} + +static void gpio_enable(struct timed_output_dev *dev, int value) +{ + struct timed_gpio_data *data = + container_of(dev, struct timed_gpio_data, dev); + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + + /* cancel previous timer and set GPIO according to value */ + hrtimer_cancel(&data->timer); + gpio_direction_output(data->gpio, data->active_low ? !value : !!value); + + if (value > 0) { + if (value > data->max_timeout) + value = data->max_timeout; + + hrtimer_start(&data->timer, + ktime_set(value / 1000, (value % 1000) * 1000000), + HRTIMER_MODE_REL); + } + + spin_unlock_irqrestore(&data->lock, flags); +} + +static int timed_gpio_probe(struct platform_device *pdev) +{ + struct timed_gpio_platform_data *pdata = pdev->dev.platform_data; + struct timed_gpio *cur_gpio; + struct timed_gpio_data *gpio_data, *gpio_dat; + int i, ret; + + if (!pdata) + return -EBUSY; + + gpio_data = kzalloc(sizeof(struct timed_gpio_data) * pdata->num_gpios, + GFP_KERNEL); + if (!gpio_data) + return -ENOMEM; + + for (i = 0; i < pdata->num_gpios; i++) { + cur_gpio = &pdata->gpios[i]; + gpio_dat = &gpio_data[i]; + + hrtimer_init(&gpio_dat->timer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + gpio_dat->timer.function = gpio_timer_func; + spin_lock_init(&gpio_dat->lock); + + gpio_dat->dev.name = cur_gpio->name; + gpio_dat->dev.get_time = gpio_get_time; + gpio_dat->dev.enable = gpio_enable; + ret = gpio_request(cur_gpio->gpio, cur_gpio->name); + if (ret < 0) + goto err_out; + ret = timed_output_dev_register(&gpio_dat->dev); + if (ret < 0) { + gpio_free(cur_gpio->gpio); + goto err_out; + } + + gpio_dat->gpio = cur_gpio->gpio; + gpio_dat->max_timeout = cur_gpio->max_timeout; + gpio_dat->active_low = cur_gpio->active_low; + gpio_direction_output(gpio_dat->gpio, gpio_dat->active_low); + } + + platform_set_drvdata(pdev, gpio_data); + + return 0; + +err_out: + while (--i >= 0) { + timed_output_dev_unregister(&gpio_data[i].dev); + gpio_free(gpio_data[i].gpio); + } + kfree(gpio_data); + + return ret; +} + +static int timed_gpio_remove(struct platform_device *pdev) +{ + struct timed_gpio_platform_data *pdata = pdev->dev.platform_data; + struct timed_gpio_data *gpio_data = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < pdata->num_gpios; i++) { + timed_output_dev_unregister(&gpio_data[i].dev); + gpio_free(gpio_data[i].gpio); + } + + kfree(gpio_data); + + return 0; +} + +static struct platform_driver timed_gpio_driver = { + .probe = timed_gpio_probe, + .remove = timed_gpio_remove, + .driver = { + .name = TIMED_GPIO_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init timed_gpio_init(void) +{ + return platform_driver_register(&timed_gpio_driver); +} + +static void __exit timed_gpio_exit(void) +{ + platform_driver_unregister(&timed_gpio_driver); +} + +module_init(timed_gpio_init); +module_exit(timed_gpio_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("timed gpio driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/android/timed_gpio.h b/drivers/staging/android/timed_gpio.h new file mode 100644 index 00000000..d29e169d --- /dev/null +++ b/drivers/staging/android/timed_gpio.h @@ -0,0 +1,33 @@ +/* include/linux/timed_gpio.h + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#ifndef _LINUX_TIMED_GPIO_H +#define _LINUX_TIMED_GPIO_H + +#define TIMED_GPIO_NAME "timed-gpio" + +struct timed_gpio { + const char *name; + unsigned gpio; + int max_timeout; + u8 active_low; +}; + +struct timed_gpio_platform_data { + int num_gpios; + struct timed_gpio *gpios; +}; + +#endif diff --git a/drivers/staging/android/timed_output.c b/drivers/staging/android/timed_output.c new file mode 100644 index 00000000..f3734223 --- /dev/null +++ b/drivers/staging/android/timed_output.c @@ -0,0 +1,123 @@ +/* drivers/misc/timed_output.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "timed_output.h" + +static struct class *timed_output_class; +static atomic_t device_count; + +static ssize_t enable_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct timed_output_dev *tdev = dev_get_drvdata(dev); + int remaining = tdev->get_time(tdev); + + return sprintf(buf, "%d\n", remaining); +} + +static ssize_t enable_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct timed_output_dev *tdev = dev_get_drvdata(dev); + int value; + + if (sscanf(buf, "%d", &value) != 1) + return -EINVAL; + + tdev->enable(tdev, value); + + return size; +} + +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store); + +static int create_timed_output_class(void) +{ + if (!timed_output_class) { + timed_output_class = class_create(THIS_MODULE, "timed_output"); + if (IS_ERR(timed_output_class)) + return PTR_ERR(timed_output_class); + atomic_set(&device_count, 0); + } + + return 0; +} + +int timed_output_dev_register(struct timed_output_dev *tdev) +{ + int ret; + + if (!tdev || !tdev->name || !tdev->enable || !tdev->get_time) + return -EINVAL; + + ret = create_timed_output_class(); + if (ret < 0) + return ret; + + tdev->index = atomic_inc_return(&device_count); + tdev->dev = device_create(timed_output_class, NULL, + MKDEV(0, tdev->index), NULL, tdev->name); + if (IS_ERR(tdev->dev)) + return PTR_ERR(tdev->dev); + + ret = device_create_file(tdev->dev, &dev_attr_enable); + if (ret < 0) + goto err_create_file; + + dev_set_drvdata(tdev->dev, tdev); + tdev->state = 0; + return 0; + +err_create_file: + device_destroy(timed_output_class, MKDEV(0, tdev->index)); + printk(KERN_ERR "timed_output: Failed to register driver %s\n", + tdev->name); + + return ret; +} +EXPORT_SYMBOL_GPL(timed_output_dev_register); + +void timed_output_dev_unregister(struct timed_output_dev *tdev) +{ + device_remove_file(tdev->dev, &dev_attr_enable); + device_destroy(timed_output_class, MKDEV(0, tdev->index)); + dev_set_drvdata(tdev->dev, NULL); +} +EXPORT_SYMBOL_GPL(timed_output_dev_unregister); + +static int __init timed_output_init(void) +{ + return create_timed_output_class(); +} + +static void __exit timed_output_exit(void) +{ + class_destroy(timed_output_class); +} + +module_init(timed_output_init); +module_exit(timed_output_exit); + +MODULE_AUTHOR("Mike Lockwood "); +MODULE_DESCRIPTION("timed output class driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/android/timed_output.h b/drivers/staging/android/timed_output.h new file mode 100644 index 00000000..ec907ab2 --- /dev/null +++ b/drivers/staging/android/timed_output.h @@ -0,0 +1,37 @@ +/* include/linux/timed_output.h + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#ifndef _LINUX_TIMED_OUTPUT_H +#define _LINUX_TIMED_OUTPUT_H + +struct timed_output_dev { + const char *name; + + /* enable the output and set the timer */ + void (*enable)(struct timed_output_dev *sdev, int timeout); + + /* returns the current number of milliseconds remaining on the timer */ + int (*get_time)(struct timed_output_dev *sdev); + + /* private data */ + struct device *dev; + int index; + int state; +}; + +extern int timed_output_dev_register(struct timed_output_dev *dev); +extern void timed_output_dev_unregister(struct timed_output_dev *dev); + +#endif diff --git a/drivers/staging/android/trace_persistent.c b/drivers/staging/android/trace_persistent.c new file mode 100644 index 00000000..7c3e9499 --- /dev/null +++ b/drivers/staging/android/trace_persistent.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2012 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../../kernel/trace/trace.h" + +struct persistent_trace_record { + unsigned long ip; + unsigned long parent_ip; +}; + +#define REC_SIZE sizeof(struct persistent_trace_record) + +static struct persistent_ram_zone *persistent_trace; + +static int persistent_trace_enabled; + +static struct trace_array *persistent_trace_array; + +static struct ftrace_ops trace_ops; + +static int persistent_tracer_init(struct trace_array *tr) +{ + persistent_trace_array = tr; + tr->cpu = get_cpu(); + put_cpu(); + + tracing_start_cmdline_record(); + + persistent_trace_enabled = 0; + smp_wmb(); + + register_ftrace_function(&trace_ops); + + smp_wmb(); + persistent_trace_enabled = 1; + + return 0; +} + +static void persistent_trace_reset(struct trace_array *tr) +{ + persistent_trace_enabled = 0; + smp_wmb(); + + unregister_ftrace_function(&trace_ops); + + tracing_stop_cmdline_record(); +} + +static void persistent_trace_start(struct trace_array *tr) +{ + tracing_reset_online_cpus(tr); +} + +static void persistent_trace_call(unsigned long ip, unsigned long parent_ip) +{ + struct trace_array *tr = persistent_trace_array; + struct trace_array_cpu *data; + long disabled; + struct persistent_trace_record rec; + unsigned long flags; + int cpu; + + smp_rmb(); + if (unlikely(!persistent_trace_enabled)) + return; + + if (unlikely(oops_in_progress)) + return; + + /* + * Need to use raw, since this must be called before the + * recursive protection is performed. + */ + local_irq_save(flags); + cpu = raw_smp_processor_id(); + data = tr->data[cpu]; + disabled = atomic_inc_return(&data->disabled); + + if (likely(disabled == 1)) { + rec.ip = ip; + rec.parent_ip = parent_ip; + rec.ip |= cpu; + persistent_ram_write(persistent_trace, &rec, sizeof(rec)); + } + + atomic_dec(&data->disabled); + local_irq_restore(flags); +} + +static struct ftrace_ops trace_ops __read_mostly = { + .func = persistent_trace_call, + .flags = FTRACE_OPS_FL_GLOBAL, +}; + +static struct tracer persistent_tracer __read_mostly = { + .name = "persistent", + .init = persistent_tracer_init, + .reset = persistent_trace_reset, + .start = persistent_trace_start, + .wait_pipe = poll_wait_pipe, +}; + +struct persistent_trace_seq_data { + const void *ptr; + size_t off; + size_t size; +}; + +void *persistent_trace_seq_start(struct seq_file *s, loff_t *pos) +{ + struct persistent_trace_seq_data *data; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return NULL; + + data->ptr = persistent_ram_old(persistent_trace); + data->size = persistent_ram_old_size(persistent_trace); + data->off = data->size % REC_SIZE; + + data->off += *pos * REC_SIZE; + + if (data->off + REC_SIZE > data->size) { + kfree(data); + return NULL; + } + + return data; + +} +void persistent_trace_seq_stop(struct seq_file *s, void *v) +{ + kfree(v); +} + +void *persistent_trace_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct persistent_trace_seq_data *data = v; + + data->off += REC_SIZE; + + if (data->off + REC_SIZE > data->size) + return NULL; + + (*pos)++; + + return data; +} + +int persistent_trace_seq_show(struct seq_file *s, void *v) +{ + struct persistent_trace_seq_data *data = v; + struct persistent_trace_record *rec; + + rec = (struct persistent_trace_record *)(data->ptr + data->off); + + seq_printf(s, "%ld %08lx %08lx %pf <- %pF\n", + rec->ip & 3, rec->ip, rec->parent_ip, + (void *)rec->ip, (void *)rec->parent_ip); + + return 0; +} + +static const struct seq_operations persistent_trace_seq_ops = { + .start = persistent_trace_seq_start, + .next = persistent_trace_seq_next, + .stop = persistent_trace_seq_stop, + .show = persistent_trace_seq_show, +}; + +static int persistent_trace_old_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &persistent_trace_seq_ops); +} + +static const struct file_operations persistent_trace_old_fops = { + .open = persistent_trace_old_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __devinit persistent_trace_probe(struct platform_device *pdev) +{ + struct dentry *d; + int ret; + + persistent_trace = persistent_ram_init_ringbuffer(&pdev->dev, false); + if (IS_ERR(persistent_trace)) { + pr_err("persistent_trace: failed to init ringbuffer: %ld\n", + PTR_ERR(persistent_trace)); + return PTR_ERR(persistent_trace); + } + + ret = register_tracer(&persistent_tracer); + if (ret) + pr_err("persistent_trace: failed to register tracer"); + + if (persistent_ram_old_size(persistent_trace) > 0) { + d = debugfs_create_file("persistent_trace", S_IRUGO, NULL, + NULL, &persistent_trace_old_fops); + if (IS_ERR_OR_NULL(d)) + pr_err("persistent_trace: failed to create old file\n"); + } + + return 0; +} + +static struct platform_driver persistent_trace_driver = { + .probe = persistent_trace_probe, + .driver = { + .name = "persistent_trace", + }, +}; + +static int __init persistent_trace_init(void) +{ + return platform_driver_register(&persistent_trace_driver); +} +core_initcall(persistent_trace_init); diff --git a/drivers/staging/asus_oled/Kconfig b/drivers/staging/asus_oled/Kconfig new file mode 100644 index 00000000..e56dbb25 --- /dev/null +++ b/drivers/staging/asus_oled/Kconfig @@ -0,0 +1,6 @@ +config ASUS_OLED + tristate "Asus OLED driver" + depends on USB + default N + ---help--- + Enable support for the OLED display present in some Asus laptops. diff --git a/drivers/staging/asus_oled/Makefile b/drivers/staging/asus_oled/Makefile new file mode 100644 index 00000000..e71f9aa9 --- /dev/null +++ b/drivers/staging/asus_oled/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ASUS_OLED) += asus_oled.o diff --git a/drivers/staging/asus_oled/README b/drivers/staging/asus_oled/README new file mode 100644 index 00000000..2d721232 --- /dev/null +++ b/drivers/staging/asus_oled/README @@ -0,0 +1,156 @@ + + Driver for Asus OLED display present in some Asus laptops. + + The code of this driver is based on 'asusoled' program taken from + . I just wanted to have a simple + kernel driver for controlling this device, but I didn't know how + to do that. Now I know ;) Also, that program can not be used + with usbhid loaded, which means no USB mouse/keyboard while + controlling OLED display :( + + It has been tested on Asus G1 and didn't cause any problems, + but I don't guarantee that it won't do anything wrong :) + + It can (and probably does) have errors. It is usable + in my case, and I hope others will find it useful too! + +******* + +Building the module + + To build the module you need kernel 2.6 include files and some C compiler. + + Just run: + make + make install (as a root) + + It will build (hopefully) the module and install it in + /lib/modules/'uname -r'/extra/asus_oled.ko. + + To load it just use: + modprobe asus_oled + + You can check if it has detected your OLED display by looking into dmesg output. + There should be something like this: + asus-oled 2-7:1.0: Attached Asus OLED device + + If it doesn't find your display, you can try removing usbhid module. + If you add asus_oled into the list of modules loaded during system boot + before usbhid, it will work even when usbhid is present. + + If it still doesn't detect your hardware, check lsusb output. + There should be similar line: + Bus 002 Device 005: ID 0b05:1726 ASUSTek Computer, Inc. + + If you don't see any lines with '0b05:1726' it means that you have different + type of hardware that is not detected (it may or may not work, but the driver + knows only '0b05:1726' device). + +******* + +Configuration + + There is only one option: start_off. + You can use it by: 'modprobe asus_oled start_off=1', or by adding this + line to /etc/modprobe.d/asus_oled.conf: + options asus_oled start_off=1 + + With this option provided, asus_oled driver will switch off the display + when it is detected and attached. It is nice feature to just switch off the 'ASUS' + logo. If you don't use the display, it is probably the good idea to switch it off, + to protect OLEDs from "wearing off". + +******* + +Usage + + This module can be controlled with two special files: + /sys/class/asus_oled/oled_N/enabled + /sys/class/asus_oled/oled_N/picture + + (N is the device number, the first, and probably the only, has number 1, + so it is /sys/class/asus_oled/oled_1/enabled + and /sys/class/asus_oled/oled_1/picture) + + 'enabled' files is for reading and writing, 'picture' is writeable only. + + You can write 0 or 1 to 'enabled' file, which will switch + on and off the display. Reading from this file will tell you the last + status set, either 0 or 1. By default it is 1, so if the device was set to 'off', + and the computer was rebooted without power-off, this file will contain wrong + value - because the device is off, but hasn't been disabled this time and is + assumed to be on... + + To 'picture' file you write pictures to be displayed by the OLED device. + The format of the file: + + 00001110010111000 + 00010101010101010 + .... + + First line is a configuration parameter. Meaning of fields in : + M - picture mode. It can be either 's' for static pictures, + 'r' for rolling pictures, and 'f' for flashing pictures. + W - width of the picture. May be between 1 and 1792 + H - height of the picture. May be between 1 and 32 + + For example means static picture, 128 pixels long and 32 pixels high. + + The physical size of the display is 128x32 pixels. Static and flashing pictures + can't be larger than that (actually they can, but only part of them will be displayed ;) ) + + If the picture is smaller than 128x32 it will be centered. Rolling pictures wider than + 128 pixels will be centered too, unless their width = n*128. Vertically they will be + centered just like static pictures, if their height is smaller than 32. + + Flashing pictures will be centered horizontally if their width < 128, but they were + centered vertically in a different way. If their height < 16, they will be centered + in the upper half of the display (rows 0-15). This is because only the first half + of flashing pictures is used for flashing. When the picture with heigh = 32 is + displayed in flashing mode, its upper 16 rows will be flashing in the upper half + of the display, and the lower half will be empty. After few seconds upper part will + stop flashing (but that part of the picture will remain there), and the lower + half of the display will start displayin the lower half of the picture + in rolling mode, unless it is empty, or the picture was small enough to fit in + upper part. It is not mine idea, this is just the way Asus' display work ;) + So if you need just flashing, use at most 128x16 picture. If you need flashing and + rolling, use whole size of the display. + + Lines following the first, configuration, line are picture data. Each '1' means + that the pixel is lit, and '0' means that it is not. You can also use '#' as ON, + and ' ' (space) as OFF. Empty lines and all other characters are ignored. + + It is possible to write everything in one line 01010101010101010..., + and W*H characters will be used. If there is not enough characters, nothing will be + displayed. However, the 'line mode' is easier to read (and write), and it also + lets to omit parts of data. Whenever End-Of-Line character is found, but + the line is not W characters long, it is assumed that all missing characters + are equal to the last character in the line. + + Following line represents '0', '1' and a lots of '0's, dependng on the width of the picture + provided in configuration data: + 010 + + So if you need empty line, it is sufficient to write line with only one '0' in it. + The same works with '1' (or ' ' and '#'). + + If there are too many data in the file, they will be ignored. If you are not sure + how many characters you are missing, you can add few lines with one zero in each of them. + + There are some example pictures in .txt format, that can be used as follows: + cat foo.txt > /sys/class/asus_oled/oled_1/picture + + If the display is switched off you also need to run: + echo 1 > /sys/class/asus_oled/oled_1/enabled + To switch it off, just use: + echo 0 > /sys/class/asus_oled/oled_1/enabled + + +******* + + For any additional info please have a look at http://lapsus.berlios.de/asus_oled.html + + + + Jakub Schmidtke (sjakub@gmail.com) + diff --git a/drivers/staging/asus_oled/TODO b/drivers/staging/asus_oled/TODO new file mode 100644 index 00000000..25141316 --- /dev/null +++ b/drivers/staging/asus_oled/TODO @@ -0,0 +1,10 @@ +TODO: + - checkpatch.pl cleanups + - sparse fixes + - audit the userspace interface + - sysfs vs. char? + - Documentation/ABI/ needs to be added + - put the sample .txt files and README file somewhere. + +Please send patches to Greg Kroah-Hartman and +Cc: Jakub Schmidtke diff --git a/drivers/staging/asus_oled/asus_oled.c b/drivers/staging/asus_oled/asus_oled.c new file mode 100644 index 00000000..83549d9c --- /dev/null +++ b/drivers/staging/asus_oled/asus_oled.c @@ -0,0 +1,818 @@ +/* + * Asus OLED USB driver + * + * Copyright (C) 2007,2008 Jakub Schmidtke (sjakub@gmail.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * + * + * This module is based on usbled and asus-laptop modules. + * + * + * Asus OLED support is based on asusoled program taken from + * . + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASUS_OLED_VERSION "0.04-dev" +#define ASUS_OLED_NAME "asus-oled" +#define ASUS_OLED_UNDERSCORE_NAME "asus_oled" + +#define ASUS_OLED_ERROR "Asus OLED Display Error: " + +#define ASUS_OLED_STATIC 's' +#define ASUS_OLED_ROLL 'r' +#define ASUS_OLED_FLASH 'f' + +#define ASUS_OLED_MAX_WIDTH 1792 +#define ASUS_OLED_DISP_HEIGHT 32 +#define ASUS_OLED_PACKET_BUF_SIZE 256 + +#define USB_VENDOR_ID_ASUS 0x0b05 +#define USB_DEVICE_ID_ASUS_LCM 0x1726 +#define USB_DEVICE_ID_ASUS_LCM2 0x175b + +MODULE_AUTHOR("Jakub Schmidtke, sjakub@gmail.com"); +MODULE_DESCRIPTION("Asus OLED Driver v" ASUS_OLED_VERSION); +MODULE_LICENSE("GPL"); + +static struct class *oled_class; +static int oled_num; + +static uint start_off; + +module_param(start_off, uint, 0644); + +MODULE_PARM_DESC(start_off, + "Set to 1 to switch off OLED display after it is attached"); + +enum oled_pack_mode { + PACK_MODE_G1, + PACK_MODE_G50, + PACK_MODE_LAST +}; + +struct oled_dev_desc_str { + uint16_t idVendor; + uint16_t idProduct; + /* width of display */ + uint16_t devWidth; + /* formula to be used while packing the picture */ + enum oled_pack_mode packMode; + const char *devDesc; +}; + +/* table of devices that work with this driver */ +static const struct usb_device_id id_table[] = { + /* Asus G1/G2 (and variants)*/ + { USB_DEVICE(USB_VENDOR_ID_ASUS, USB_DEVICE_ID_ASUS_LCM) }, + /* Asus G50V (and possibly others - G70? G71?)*/ + { USB_DEVICE(USB_VENDOR_ID_ASUS, USB_DEVICE_ID_ASUS_LCM2) }, + { }, +}; + +/* parameters of specific devices */ +static struct oled_dev_desc_str oled_dev_desc_table[] = { + { USB_VENDOR_ID_ASUS, USB_DEVICE_ID_ASUS_LCM, 128, PACK_MODE_G1, + "G1/G2" }, + { USB_VENDOR_ID_ASUS, USB_DEVICE_ID_ASUS_LCM2, 256, PACK_MODE_G50, + "G50" }, + { }, +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +struct asus_oled_header { + uint8_t magic1; + uint8_t magic2; + uint8_t flags; + uint8_t value3; + uint8_t buffer1; + uint8_t buffer2; + uint8_t value6; + uint8_t value7; + uint8_t value8; + uint8_t padding2[7]; +} __attribute((packed)); + +struct asus_oled_packet { + struct asus_oled_header header; + uint8_t bitmap[ASUS_OLED_PACKET_BUF_SIZE]; +} __attribute((packed)); + +struct asus_oled_dev { + struct usb_device *udev; + uint8_t pic_mode; + uint16_t dev_width; + enum oled_pack_mode pack_mode; + size_t height; + size_t width; + size_t x_shift; + size_t y_shift; + size_t buf_offs; + uint8_t last_val; + size_t buf_size; + char *buf; + uint8_t enabled; + struct device *dev; +}; + +static void setup_packet_header(struct asus_oled_packet *packet, char flags, + char value3, char buffer1, char buffer2, char value6, + char value7, char value8) +{ + memset(packet, 0, sizeof(struct asus_oled_header)); + packet->header.magic1 = 0x55; + packet->header.magic2 = 0xaa; + packet->header.flags = flags; + packet->header.value3 = value3; + packet->header.buffer1 = buffer1; + packet->header.buffer2 = buffer2; + packet->header.value6 = value6; + packet->header.value7 = value7; + packet->header.value8 = value8; +} + +static void enable_oled(struct asus_oled_dev *odev, uint8_t enabl) +{ + int retval; + int act_len; + struct asus_oled_packet *packet; + + packet = kzalloc(sizeof(struct asus_oled_packet), GFP_KERNEL); + + if (!packet) { + dev_err(&odev->udev->dev, "out of memory\n"); + return; + } + + setup_packet_header(packet, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00); + + if (enabl) + packet->bitmap[0] = 0xaf; + else + packet->bitmap[0] = 0xae; + + retval = usb_bulk_msg(odev->udev, + usb_sndbulkpipe(odev->udev, 2), + packet, + sizeof(struct asus_oled_header) + 1, + &act_len, + -1); + + if (retval) + dev_dbg(&odev->udev->dev, "retval = %d\n", retval); + + odev->enabled = enabl; + + kfree(packet); +} + +static ssize_t set_enabled(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct asus_oled_dev *odev = usb_get_intfdata(intf); + unsigned long value; + if (kstrtoul(buf, 10, &value)) + return -EINVAL; + + enable_oled(odev, value); + + return count; +} + +static ssize_t class_set_enabled(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct asus_oled_dev *odev = + (struct asus_oled_dev *) dev_get_drvdata(device); + unsigned long value; + + if (kstrtoul(buf, 10, &value)) + return -EINVAL; + + enable_oled(odev, value); + + return count; +} + +static ssize_t get_enabled(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct asus_oled_dev *odev = usb_get_intfdata(intf); + + return sprintf(buf, "%d\n", odev->enabled); +} + +static ssize_t class_get_enabled(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct asus_oled_dev *odev = + (struct asus_oled_dev *) dev_get_drvdata(device); + + return sprintf(buf, "%d\n", odev->enabled); +} + +static void send_packets(struct usb_device *udev, + struct asus_oled_packet *packet, + char *buf, uint8_t p_type, size_t p_num) +{ + size_t i; + int act_len; + + for (i = 0; i < p_num; i++) { + int retval; + + switch (p_type) { + case ASUS_OLED_ROLL: + setup_packet_header(packet, 0x40, 0x80, p_num, + i + 1, 0x00, 0x01, 0xff); + break; + case ASUS_OLED_STATIC: + setup_packet_header(packet, 0x10 + i, 0x80, 0x01, + 0x01, 0x00, 0x01, 0x00); + break; + case ASUS_OLED_FLASH: + setup_packet_header(packet, 0x10 + i, 0x80, 0x01, + 0x01, 0x00, 0x00, 0xff); + break; + } + + memcpy(packet->bitmap, buf + (ASUS_OLED_PACKET_BUF_SIZE*i), + ASUS_OLED_PACKET_BUF_SIZE); + + retval = usb_bulk_msg(udev, usb_sndctrlpipe(udev, 2), + packet, sizeof(struct asus_oled_packet), + &act_len, -1); + + if (retval) + dev_dbg(&udev->dev, "retval = %d\n", retval); + } +} + +static void send_packet(struct usb_device *udev, + struct asus_oled_packet *packet, + size_t offset, size_t len, char *buf, uint8_t b1, + uint8_t b2, uint8_t b3, uint8_t b4, uint8_t b5, + uint8_t b6) { + int retval; + int act_len; + + setup_packet_header(packet, b1, b2, b3, b4, b5, b6, 0x00); + memcpy(packet->bitmap, buf + offset, len); + + retval = usb_bulk_msg(udev, + usb_sndctrlpipe(udev, 2), + packet, + sizeof(struct asus_oled_packet), + &act_len, + -1); + + if (retval) + dev_dbg(&udev->dev, "retval = %d\n", retval); +} + + +static void send_packets_g50(struct usb_device *udev, + struct asus_oled_packet *packet, char *buf) +{ + send_packet(udev, packet, 0, 0x100, buf, + 0x10, 0x00, 0x02, 0x01, 0x00, 0x01); + send_packet(udev, packet, 0x100, 0x080, buf, + 0x10, 0x00, 0x02, 0x02, 0x80, 0x00); + + send_packet(udev, packet, 0x180, 0x100, buf, + 0x11, 0x00, 0x03, 0x01, 0x00, 0x01); + send_packet(udev, packet, 0x280, 0x100, buf, + 0x11, 0x00, 0x03, 0x02, 0x00, 0x01); + send_packet(udev, packet, 0x380, 0x080, buf, + 0x11, 0x00, 0x03, 0x03, 0x80, 0x00); +} + + +static void send_data(struct asus_oled_dev *odev) +{ + size_t packet_num = odev->buf_size / ASUS_OLED_PACKET_BUF_SIZE; + struct asus_oled_packet *packet; + + packet = kzalloc(sizeof(struct asus_oled_packet), GFP_KERNEL); + + if (!packet) { + dev_err(&odev->udev->dev, "out of memory\n"); + return; + } + + if (odev->pack_mode == PACK_MODE_G1) { + /* When sending roll-mode data the display updated only + first packet. I have no idea why, but when static picture + is sent just before rolling picture everything works fine. */ + if (odev->pic_mode == ASUS_OLED_ROLL) + send_packets(odev->udev, packet, odev->buf, + ASUS_OLED_STATIC, 2); + + /* Only ROLL mode can use more than 2 packets.*/ + if (odev->pic_mode != ASUS_OLED_ROLL && packet_num > 2) + packet_num = 2; + + send_packets(odev->udev, packet, odev->buf, + odev->pic_mode, packet_num); + } else if (odev->pack_mode == PACK_MODE_G50) { + send_packets_g50(odev->udev, packet, odev->buf); + } + + kfree(packet); +} + +static int append_values(struct asus_oled_dev *odev, uint8_t val, size_t count) +{ + odev->last_val = val; + + if (val == 0) { + odev->buf_offs += count; + return 0; + } + + while (count-- > 0) { + size_t x = odev->buf_offs % odev->width; + size_t y = odev->buf_offs / odev->width; + size_t i; + + x += odev->x_shift; + y += odev->y_shift; + + switch (odev->pack_mode) { + case PACK_MODE_G1: + /* i = (x/128)*640 + 127 - x + (y/8)*128; + This one for 128 is the same, but might be better + for different widths? */ + i = (x/odev->dev_width)*640 + + odev->dev_width - 1 - x + + (y/8)*odev->dev_width; + break; + + case PACK_MODE_G50: + i = (odev->dev_width - 1 - x)/8 + y*odev->dev_width/8; + break; + + default: + i = 0; + printk(ASUS_OLED_ERROR "Unknown OLED Pack Mode: %d!\n", + odev->pack_mode); + break; + } + + if (i >= odev->buf_size) { + printk(ASUS_OLED_ERROR "Buffer overflow! Report a bug:" + "offs: %d >= %d i: %d (x: %d y: %d)\n", + (int) odev->buf_offs, (int) odev->buf_size, + (int) i, (int) x, (int) y); + return -EIO; + } + + switch (odev->pack_mode) { + case PACK_MODE_G1: + odev->buf[i] &= ~(1<<(y%8)); + break; + + case PACK_MODE_G50: + odev->buf[i] &= ~(1<<(x%8)); + break; + + default: + /* cannot get here; stops gcc complaining*/ + ; + } + + odev->buf_offs++; + } + + return 0; +} + +static ssize_t odev_set_picture(struct asus_oled_dev *odev, + const char *buf, size_t count) +{ + size_t offs = 0, max_offs; + + if (count < 1) + return 0; + + if (tolower(buf[0]) == 'b') { + /* binary mode, set the entire memory*/ + + size_t i; + + odev->buf_size = (odev->dev_width * ASUS_OLED_DISP_HEIGHT) / 8; + + kfree(odev->buf); + odev->buf = kmalloc(odev->buf_size, GFP_KERNEL); + if (odev->buf == NULL) { + odev->buf_size = 0; + printk(ASUS_OLED_ERROR "Out of memory!\n"); + return -ENOMEM; + } + + memset(odev->buf, 0xff, odev->buf_size); + + for (i = 1; i < count && i <= 32 * 32; i++) { + odev->buf[i-1] = buf[i]; + odev->buf_offs = i-1; + } + + odev->width = odev->dev_width / 8; + odev->height = ASUS_OLED_DISP_HEIGHT; + odev->x_shift = 0; + odev->y_shift = 0; + odev->last_val = 0; + + send_data(odev); + + return count; + } + + if (buf[0] == '<') { + size_t i; + size_t w = 0, h = 0; + size_t w_mem, h_mem; + + if (count < 10 || buf[2] != ':') + goto error_header; + + + switch (tolower(buf[1])) { + case ASUS_OLED_STATIC: + case ASUS_OLED_ROLL: + case ASUS_OLED_FLASH: + odev->pic_mode = buf[1]; + break; + default: + printk(ASUS_OLED_ERROR "Wrong picture mode: '%c'.\n", + buf[1]); + return -EIO; + break; + } + + for (i = 3; i < count; ++i) { + if (buf[i] >= '0' && buf[i] <= '9') { + w = 10*w + (buf[i] - '0'); + + if (w > ASUS_OLED_MAX_WIDTH) + goto error_width; + } else if (tolower(buf[i]) == 'x') { + break; + } else { + goto error_width; + } + } + + for (++i; i < count; ++i) { + if (buf[i] >= '0' && buf[i] <= '9') { + h = 10*h + (buf[i] - '0'); + + if (h > ASUS_OLED_DISP_HEIGHT) + goto error_height; + } else if (tolower(buf[i]) == '>') { + break; + } else { + goto error_height; + } + } + + if (w < 1 || w > ASUS_OLED_MAX_WIDTH) + goto error_width; + + if (h < 1 || h > ASUS_OLED_DISP_HEIGHT) + goto error_height; + + if (i >= count || buf[i] != '>') + goto error_header; + + offs = i+1; + + if (w % (odev->dev_width) != 0) + w_mem = (w/(odev->dev_width) + 1)*(odev->dev_width); + else + w_mem = w; + + if (h < ASUS_OLED_DISP_HEIGHT) + h_mem = ASUS_OLED_DISP_HEIGHT; + else + h_mem = h; + + odev->buf_size = w_mem * h_mem / 8; + + kfree(odev->buf); + odev->buf = kmalloc(odev->buf_size, GFP_KERNEL); + + if (odev->buf == NULL) { + odev->buf_size = 0; + printk(ASUS_OLED_ERROR "Out of memory!\n"); + return -ENOMEM; + } + + memset(odev->buf, 0xff, odev->buf_size); + + odev->buf_offs = 0; + odev->width = w; + odev->height = h; + odev->x_shift = 0; + odev->y_shift = 0; + odev->last_val = 0; + + if (odev->pic_mode == ASUS_OLED_FLASH) { + if (h < ASUS_OLED_DISP_HEIGHT/2) + odev->y_shift = (ASUS_OLED_DISP_HEIGHT/2 - h)/2; + } else { + if (h < ASUS_OLED_DISP_HEIGHT) + odev->y_shift = (ASUS_OLED_DISP_HEIGHT - h)/2; + } + + if (w < (odev->dev_width)) + odev->x_shift = ((odev->dev_width) - w)/2; + } + + max_offs = odev->width * odev->height; + + while (offs < count && odev->buf_offs < max_offs) { + int ret = 0; + + if (buf[offs] == '1' || buf[offs] == '#') { + ret = append_values(odev, 1, 1); + if (ret < 0) + return ret; + } else if (buf[offs] == '0' || buf[offs] == ' ') { + ret = append_values(odev, 0, 1); + if (ret < 0) + return ret; + } else if (buf[offs] == '\n') { + /* New line detected. Lets assume, that all characters + till the end of the line were equal to the last + character in this line.*/ + if (odev->buf_offs % odev->width != 0) + ret = append_values(odev, odev->last_val, + odev->width - + (odev->buf_offs % + odev->width)); + if (ret < 0) + return ret; + } + + offs++; + } + + if (odev->buf_offs >= max_offs) + send_data(odev); + + return count; + +error_width: + printk(ASUS_OLED_ERROR "Wrong picture width specified.\n"); + return -EIO; + +error_height: + printk(ASUS_OLED_ERROR "Wrong picture height specified.\n"); + return -EIO; + +error_header: + printk(ASUS_OLED_ERROR "Wrong picture header.\n"); + return -EIO; +} + +static ssize_t set_picture(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + + return odev_set_picture(usb_get_intfdata(intf), buf, count); +} + +static ssize_t class_set_picture(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return odev_set_picture((struct asus_oled_dev *) + dev_get_drvdata(device), buf, count); +} + +#define ASUS_OLED_DEVICE_ATTR(_file) dev_attr_asus_oled_##_file + +static DEVICE_ATTR(asus_oled_enabled, S_IWUSR | S_IRUGO, + get_enabled, set_enabled); +static DEVICE_ATTR(asus_oled_picture, S_IWUSR , NULL, set_picture); + +static DEVICE_ATTR(enabled, S_IWUSR | S_IRUGO, + class_get_enabled, class_set_enabled); +static DEVICE_ATTR(picture, S_IWUSR, NULL, class_set_picture); + +static int asus_oled_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct asus_oled_dev *odev = NULL; + int retval = -ENOMEM; + uint16_t dev_width = 0; + enum oled_pack_mode pack_mode = PACK_MODE_LAST; + const struct oled_dev_desc_str *dev_desc = oled_dev_desc_table; + const char *desc = NULL; + + if (!id) { + /* Even possible? Just to make sure...*/ + dev_err(&interface->dev, "No usb_device_id provided!\n"); + return -ENODEV; + } + + for (; dev_desc->idVendor; dev_desc++) { + if (dev_desc->idVendor == id->idVendor + && dev_desc->idProduct == id->idProduct) { + dev_width = dev_desc->devWidth; + desc = dev_desc->devDesc; + pack_mode = dev_desc->packMode; + break; + } + } + + if (!desc || dev_width < 1 || pack_mode == PACK_MODE_LAST) { + dev_err(&interface->dev, + "Missing or incomplete device description!\n"); + return -ENODEV; + } + + odev = kzalloc(sizeof(struct asus_oled_dev), GFP_KERNEL); + + if (odev == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + return -ENOMEM; + } + + odev->udev = usb_get_dev(udev); + odev->pic_mode = ASUS_OLED_STATIC; + odev->dev_width = dev_width; + odev->pack_mode = pack_mode; + odev->height = 0; + odev->width = 0; + odev->x_shift = 0; + odev->y_shift = 0; + odev->buf_offs = 0; + odev->buf_size = 0; + odev->last_val = 0; + odev->buf = NULL; + odev->enabled = 1; + odev->dev = NULL; + + usb_set_intfdata(interface, odev); + + retval = device_create_file(&interface->dev, + &ASUS_OLED_DEVICE_ATTR(enabled)); + if (retval) + goto err_files; + + retval = device_create_file(&interface->dev, + &ASUS_OLED_DEVICE_ATTR(picture)); + if (retval) + goto err_files; + + odev->dev = device_create(oled_class, &interface->dev, MKDEV(0, 0), + NULL, "oled_%d", ++oled_num); + + if (IS_ERR(odev->dev)) { + retval = PTR_ERR(odev->dev); + goto err_files; + } + + dev_set_drvdata(odev->dev, odev); + + retval = device_create_file(odev->dev, &dev_attr_enabled); + if (retval) + goto err_class_enabled; + + retval = device_create_file(odev->dev, &dev_attr_picture); + if (retval) + goto err_class_picture; + + dev_info(&interface->dev, + "Attached Asus OLED device: %s [width %u, pack_mode %d]\n", + desc, odev->dev_width, odev->pack_mode); + + if (start_off) + enable_oled(odev, 0); + + return 0; + +err_class_picture: + device_remove_file(odev->dev, &dev_attr_picture); + +err_class_enabled: + device_remove_file(odev->dev, &dev_attr_enabled); + device_unregister(odev->dev); + +err_files: + device_remove_file(&interface->dev, &ASUS_OLED_DEVICE_ATTR(enabled)); + device_remove_file(&interface->dev, &ASUS_OLED_DEVICE_ATTR(picture)); + + usb_set_intfdata(interface, NULL); + usb_put_dev(odev->udev); + kfree(odev); + + return retval; +} + +static void asus_oled_disconnect(struct usb_interface *interface) +{ + struct asus_oled_dev *odev; + + odev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + device_remove_file(odev->dev, &dev_attr_picture); + device_remove_file(odev->dev, &dev_attr_enabled); + device_unregister(odev->dev); + + device_remove_file(&interface->dev, &ASUS_OLED_DEVICE_ATTR(picture)); + device_remove_file(&interface->dev, &ASUS_OLED_DEVICE_ATTR(enabled)); + + usb_put_dev(odev->udev); + + kfree(odev->buf); + + kfree(odev); + + dev_info(&interface->dev, "Disconnected Asus OLED device\n"); +} + +static struct usb_driver oled_driver = { + .name = ASUS_OLED_NAME, + .probe = asus_oled_probe, + .disconnect = asus_oled_disconnect, + .id_table = id_table, +}; + +static CLASS_ATTR_STRING(version, S_IRUGO, + ASUS_OLED_UNDERSCORE_NAME " " ASUS_OLED_VERSION); + +static int __init asus_oled_init(void) +{ + int retval = 0; + oled_class = class_create(THIS_MODULE, ASUS_OLED_UNDERSCORE_NAME); + + if (IS_ERR(oled_class)) { + err("Error creating " ASUS_OLED_UNDERSCORE_NAME " class"); + return PTR_ERR(oled_class); + } + + retval = class_create_file(oled_class, &class_attr_version.attr); + if (retval) { + err("Error creating class version file"); + goto error; + } + + retval = usb_register(&oled_driver); + + if (retval) { + err("usb_register failed. Error number %d", retval); + goto error; + } + + return retval; + +error: + class_destroy(oled_class); + return retval; +} + +static void __exit asus_oled_exit(void) +{ + usb_deregister(&oled_driver); + class_remove_file(oled_class, &class_attr_version.attr); + class_destroy(oled_class); +} + +module_init(asus_oled_init); +module_exit(asus_oled_exit); + diff --git a/drivers/staging/asus_oled/linux.txt b/drivers/staging/asus_oled/linux.txt new file mode 100644 index 00000000..dc758b0e --- /dev/null +++ b/drivers/staging/asus_oled/linux.txt @@ -0,0 +1,33 @@ + +0 +0 +00000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000 +01111111111000000000000000000000000000000000000000000000000000000000000000 +00011111100000000000000111000000000000000000000000000000000000000000000000 +00001111000000000000000111000000000000000000000000000000000000000000000000 +00001111000000000000000111000000000000000000000000000000000000000000000000 +00001111000000000000000000000000000000000000000000000000000000000000000000 +00001111000000000000000000000000000000000000000000000000000000000000000000 +00001111000000000000011100001111111111100000111110011111100011111101111000 +00001111000000000000111110000011111000111000111110000111100001111000110000 +00001111000000000001101110000011111000111000001111000111100000111100100000 +00001111000000000001001110000011110000111100001111000111100000111101100000 +00001111000000000100001110000011110000111100001111000111100000011111000000 +00001111000000000100011110000011110000111100001111000111100000001111000000 +00001111000000000100011110000011110000111100001111000111100000001111000000 +00001111000000000100011100100011110000111100001111000111100000001111100000 +00001111000000001100111100100011110000111100001111000111100000001111110000 +00001111000000001100111101100011110000111100001111000111100000011011110000 +00001111000000011100111101000011110000111100001111000111100000010001111000 +00011111000001111100111011000011110000111100001111001111100000110000111100 +11111111111111111100011110001111111011111110000111110111111011111011111110 +00000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000 +0 +0 +0 +0 diff --git a/drivers/staging/asus_oled/linux_f.txt b/drivers/staging/asus_oled/linux_f.txt new file mode 100644 index 00000000..b4bb85cc --- /dev/null +++ b/drivers/staging/asus_oled/linux_f.txt @@ -0,0 +1,18 @@ + +00000000000000000000000000000000001111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000001110000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000001110000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000011000111111111100001111001111100111110111000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000111100001111000110001111000111100011100010000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000001011100001111000111000111100111100001110110000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000011100001110000111000111100111100001111100000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000100011100001110000111000111100111100000111100000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000100011100001110000111000111100111100000111100000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000100111001001110000111000111100111100000111110000000000000000000000000000000000000 +00000000000000000000000000000000000011110000001100111011001110000111000111100111100000111110000000000000000000000000000000000000 +00000000000000000000000000000000000011110000001100111010001110000111000111100111100000100111000000000000000000000000000000000000 +00000000000000000000000000000000000011110000111100110110001110000111000111100111100001000011100000000000000000000000000000000000 +00000000000000000000000000000000001111111111111100111100111111011111100011110111110111101111110000000000000000000000000000000000 + diff --git a/drivers/staging/asus_oled/linux_fr.txt b/drivers/staging/asus_oled/linux_fr.txt new file mode 100644 index 00000000..f88e2b3b --- /dev/null +++ b/drivers/staging/asus_oled/linux_fr.txt @@ -0,0 +1,33 @@ + +00000000000000000000000000000000001111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000001110000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000001110000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000011000111111111100001111001111100111110111000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000111100001111000110001111000111100011100010000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000001011100001111000111000111100111100001110110000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000000011100001110000111000111100111100001111100000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000100011100001110000111000111100111100000111100000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000100011100001110000111000111100111100000111100000000000000000000000000000000000000 +00000000000000000000000000000000000011110000000100111001001110000111000111100111100000111110000000000000000000000000000000000000 +00000000000000000000000000000000000011110000001100111011001110000111000111100111100000111110000000000000000000000000000000000000 +00000000000000000000000000000000000011110000001100111010001110000111000111100111100000100111000000000000000000000000000000000000 +00000000000000000000000000000000000011110000111100110110001110000111000111100111100001000011100000000000000000000000000000000000 +00000000000000000000000000000000001111111111111100111100111111011111100011110111110111101111110000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000 +00000000000000000000000000001111000000000000000000000000000000000000000000000000000000000000001111000000000000000000000000000000 +00000000000000000000000000011111100000000000000000000000000000000000000000000000000000000000011111100000000000000000000000000000 +00000000000000000000000000011111100000000000000000000000000000000000000000000000000000000000011111100000000000000000000000000000 +00000000000000000000000000001111000000000000000000000000000000000000000000000000000000000000001111000000000000000000000000000000 +00000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 diff --git a/drivers/staging/asus_oled/tux.txt b/drivers/staging/asus_oled/tux.txt new file mode 100644 index 00000000..9d205285 --- /dev/null +++ b/drivers/staging/asus_oled/tux.txt @@ -0,0 +1,33 @@ + +00000000000001111111000000000000 +0000000000001 100000000000 +000000000001 10000000000 +000000000001 10000000000 +000000000001 10000000000 +000000000001 1 111 10000000000 +000000000001 1 1 1000000000 +000000000001 111 1000000000 +000000000001 111111 1000000000 +000000000001 111111 1000000000 +000000000001 1 1 100000000 +00000000001 11 100000000 +00000000001 11111111 10000000 +0000000001 11111111 1000000 +000000001 111111111 1000000 +000000001 1111111111 100000 +00000001 11111111111 100000 +00000001 111111111111 10000 +0000001 111111111111 10000 +0000001 111111111111 10000 +0000001 111111111111 10000 +0000001 111111111111 10000 +000000011 11111111111 10000 +000011 11 11111111111 100000 +0001 1111 111111111111111 1000 +001 1111111 11111111111111 1000 +001 1111111 1111111 111111 100 +001 11111111 111111 1111111 10 +001 11111111 11111 100 +001 1111111 111 11100 +000111 111 11111 11 100000 +000000111 111111111 1000000 diff --git a/drivers/staging/asus_oled/tux_r.txt b/drivers/staging/asus_oled/tux_r.txt new file mode 100644 index 00000000..fd81a3e8 --- /dev/null +++ b/drivers/staging/asus_oled/tux_r.txt @@ -0,0 +1,33 @@ + +00000000000001111111000000000000 +0000000000001 100000000000 +000000000001 10000000000 +000000000001 10000000000 +000000000001 10000000000 +000000000001 1 111 10000000000 +000000000001 1 1 1000000000 +000000000001 111 1000000000 +000000000001 111111 1000000000 +000000000001 111111 1000000000 +000000000001 1 1 100000000 +00000000001 11 100000000 +00000000001 11111111 10000000 +0000000001 11111111 1000000 +000000001 111111111 1000000 +000000001 1111111111 100000 +00000001 11111111111 100000 +00000001 111111111111 10000 +0000001 111111111111 10000 +0000001 111111111111 10000 +0000001 111111111111 10000 +0000001 111111111111 10000 +000000011 11111111111 10000 +000011 11 11111111111 100000 +0001 1111 111111111111111 1000 +001 1111111 11111111111111 1000 +001 1111111 1111111 111111 100 +001 11111111 111111 1111111 10 +001 11111111 11111 100 +001 1111111 111 11100 +000111 111 11111 11 100000 +000000111 111111111 1000000 diff --git a/drivers/staging/asus_oled/tux_r2.txt b/drivers/staging/asus_oled/tux_r2.txt new file mode 100644 index 00000000..e94d84ea --- /dev/null +++ b/drivers/staging/asus_oled/tux_r2.txt @@ -0,0 +1,33 @@ + +000000000000000000000000000000000000000000000000000000000000011111110000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000001 1000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001 1 111 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001 1 1 100000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001 111 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111100000000000000111000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001 111111 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000000000111000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001 111111 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000000000111000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000001 1 1 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000001 11 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000001 11111111 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000000011100001111111111100000111110011111100011111101111000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000001 11111111 100000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000000111110000011111000111000111110000111100001111000110000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000001 111111111 100000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000001101110000011111000111000001111000111100000111100100000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000001 1111111111 10000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000001001110000011110000111100001111000111100000111101100000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000001 11111111111 10000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000100001110000011110000111100001111000111100000011111000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000001 111111111111 10000000000000000000000000000000000000000000000000000000000000000000000000000000000011110000000001000111100000111100001111000011110001111000000011110000000 +0000000000000000000000000000000000000000000000000000001 111111111111 1000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000100011110000011110000111100001111000111100000001111000000 +0000000000000000000000000000000000000000000000000000001 111111111111 1000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000000100011100100011110000111100001111000111100000001111100000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000001 111111111111 1000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000001100111100100011110000111100001111000111100000001111110000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000001 111111111111 1000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000001100111101100011110000111100001111000111100000011011110000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000011 11111111111 1000000000000000000000000000000000000000000000000000000000000000000000000000000000001111000000011100111101000011110000111100001111000111100000010001111000000000000000000000000000000 +000000000000000000000000000000000000000000000000000011 11 11111111111 10000000000000000000000000000000000000000000000000000000000000000000000000000000000011111000001111100111011000011110000111100001111001111100000110000111100000000000000000000000000000 +0000000000000000000000000000000000000000000000000001 1111 111111111111111 100000000000000000000000000000000000000000000000000000000000000000000000000000011111111111111111100011110001111111011111110000111110111111011111011111110000000000000000000000000000 +000000000000000000000000000000000000000000000000001 1111111 11111111111111 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000001 1111111 1111111 111111 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000001 11111111 111111 1111111 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000001 11111111 11111 1000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000001 1111111 111 111000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000111 111 11111 11 10 +000000000000000000000000000000000000000000000000000000111 111111111 10 diff --git a/drivers/staging/asus_oled/zig.txt b/drivers/staging/asus_oled/zig.txt new file mode 100644 index 00000000..31573d8f --- /dev/null +++ b/drivers/staging/asus_oled/zig.txt @@ -0,0 +1,33 @@ + +10000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000001 +01000000000000000000000000000000000000000000000000000000000000100100000000000000000000000000000000000000000000000000000000000010 +00100000000000000000000000000000000000000000000000000000000001000010000000000000000000000000000000000000000000000000000000000100 +00010000000000000000000000000000000000000000000000000000000010000001000000000000000000000000000000000000000000000000000000001000 +00001000000000000000000000000000000000000000000000000000000100000000100000000000000000000000000000000000000000000000000000010000 +00000100000000000000000000000000000000000000000000000000001000000000010000000000000000000000000000000000000000000000000000100000 +00000010000000000000000000000000000000000000000000000000010000000000001000000000000000000000000000000000000000000000000001000000 +00000001000000000000000000000000000000000000000000000000100000000000000100000000000000000000000000000000000000000000000010000000 +00000000100000000000000000000000000000000000000000000001000000000000000010000000000000000000000000000000000000000000000100000000 +00000000010000000000000000000000000000000000000000000010000000000000000001000000000000000000000000000000000000000000001000000000 +00000000001000000000000000000000000000000000000000000100000000000000000000100000000000000000000000000000000000000000010000000000 +00000000000100000000000000000000000000000000000000001000000000000000000000010000000000000000000000000000000000000000100000000000 +00000000000010000000000000000000000000000000000000010000000000000000000000001000000000000000000000000000000000000001000000000000 +00000000000001000000000000000000000000000000000000100000000000000000000000000100000000000000000000000000000000000010000000000000 +00000000000000100000000000000000000000000000000001000000000000000000000000000010000000000000000000000000000000000100000000000000 +00000000000000010000000000000000000000000000000010000000000000000000000000000001000000000000000000000000000000001000000000000000 +00000000000000001000000000000000000000000000000100000000000000000000000000000000100000000000000000000000000000010000000000000000 +00000000000000000100000000000000000000000000001000000000000000000000000000000000010000000000000000000000000000100000000000000000 +00000000000000000010000000000000000000000000010000000000000000000000000000000000001000000000000000000000000001000000000000000000 +00000000000000000001000000000000000000000000100000000000000000000000000000000000000100000000000000000000000010000000000000000000 +00000000000000000000100000000000000000000001000000000000000000000000000000000000000010000000000000000000000100000000000000000000 +00000000000000000000010000000000000000000010000000000000000000000000000000000000000001000000000000000000001000000000000000000000 +00000000000000000000001000000000000000000100000000000000000000000000000000000000000000100000000000000000010000000000000000000000 +00000000000000000000000100000000000000001000000000000000000000000000000000000000000000010000000000000000100000000000000000000000 +00000000000000000000000010000000000000010000000000000000000000000000000000000000000000001000000000000001000000000000000000000000 +00000000000000000000000001000000000000100000000000000000000000000000000000000000000000000100000000000010000000000000000000000000 +00000000000000000000000000100000000001000000000000000000000000000000000000000000000000000010000000000100000000000000000000000000 +00000000000000000000000000010000000010000000000000000000000000000000000000000000000000000001000000001000000000000000000000000000 +00000000000000000000000000001000000100000000000000000000000000000000000000000000000000000000100000010000000000000000000000000000 +00000000000000000000000000000100001000000000000000000000000000000000000000000000000000000000010000100000000000000000000000000000 +00000000000000000000000000000010010000000000000000000000000000000000000000000000000000000000001001000000000000000000000000000000 +00000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000 diff --git a/drivers/staging/bcm/Adapter.h b/drivers/staging/bcm/Adapter.h new file mode 100644 index 00000000..20cca24f --- /dev/null +++ b/drivers/staging/bcm/Adapter.h @@ -0,0 +1,648 @@ +/*********************************** +* Adapter.h +************************************/ +#ifndef __ADAPTER_H__ +#define __ADAPTER_H__ + +#define MAX_FRAGMENTEDIP_CLASSIFICATION_ENTRIES 256 +#include "Debug.h" + +struct _LEADER +{ + USHORT Vcid; + USHORT PLength; + UCHAR Status; + UCHAR Unused[3]; +}__attribute__((packed)); +typedef struct _LEADER LEADER,*PLEADER; + +struct _PACKETTOSEND +{ + LEADER Leader; + UCHAR ucPayload; +}__attribute__((packed)); +typedef struct _PACKETTOSEND PACKETTOSEND, *PPACKETTOSEND; + + +struct _CONTROL_PACKET +{ + PVOID ControlBuff; + UINT ControlBuffLen; + struct _CONTROL_PACKET* next; +}__attribute__((packed)); +typedef struct _CONTROL_PACKET CONTROL_PACKET,*PCONTROL_PACKET; + + +struct link_request +{ + LEADER Leader; + UCHAR szData[4]; +}__attribute__((packed)); +typedef struct link_request LINK_REQUEST, *PLINK_REQUEST; + + +//classification extension is added +typedef struct _ADD_CONNECTION +{ + ULONG SrcIpAddressCount; + ULONG SrcIpAddress[MAX_CONNECTIONS]; + ULONG SrcIpMask[MAX_CONNECTIONS]; + + ULONG DestIpAddressCount; + ULONG DestIpAddress[MAX_CONNECTIONS]; + ULONG DestIpMask[MAX_CONNECTIONS]; + + USHORT SrcPortBegin; + USHORT SrcPortEnd; + + USHORT DestPortBegin; + USHORT DestPortEnd; + + UCHAR SrcTOS; + UCHAR SrcProtocol; +} ADD_CONNECTION,*PADD_CONNECTION; + + +typedef struct _CLASSIFICATION_RULE +{ + UCHAR ucIPSrcAddrLen; + UCHAR ucIPSrcAddr[32]; + UCHAR ucIPDestAddrLen; + UCHAR ucIPDestAddr[32]; + UCHAR ucSrcPortRangeLen; + UCHAR ucSrcPortRange[4]; + UCHAR ucDestPortRangeLen; + UCHAR ucDestPortRange[4]; + USHORT usVcid; +} CLASSIFICATION_RULE,*PCLASSIFICATION_RULE; + +typedef struct _CLASSIFICATION_ONLY +{ + USHORT usVcid; + ULONG DestIpAddress; + ULONG DestIpMask; + USHORT usPortLo; + USHORT usPortHi; + BOOLEAN bIpVersion; + UCHAR ucDestinationAddress[16]; +} CLASSIFICATION_ONLY, *PCLASSIFICATION_ONLY; + + +#define MAX_IP_RANGE_LENGTH 4 +#define MAX_PORT_RANGE 4 +#define MAX_PROTOCOL_LENGTH 32 +#define IPV6_ADDRESS_SIZEINBYTES 0x10 + +typedef union _U_IP_ADDRESS +{ + struct + { + ULONG ulIpv4Addr[MAX_IP_RANGE_LENGTH];//Source Ip Address Range + ULONG ulIpv4Mask[MAX_IP_RANGE_LENGTH];//Source Ip Mask Address Range + }; + struct + { + ULONG ulIpv6Addr[MAX_IP_RANGE_LENGTH * 4];//Source Ip Address Range + ULONG ulIpv6Mask[MAX_IP_RANGE_LENGTH * 4];//Source Ip Mask Address Range + + }; + struct + { + UCHAR ucIpv4Address[MAX_IP_RANGE_LENGTH * IP_LENGTH_OF_ADDRESS]; + UCHAR ucIpv4Mask[MAX_IP_RANGE_LENGTH * IP_LENGTH_OF_ADDRESS]; + }; + struct + { + UCHAR ucIpv6Address[MAX_IP_RANGE_LENGTH * IPV6_ADDRESS_SIZEINBYTES]; + UCHAR ucIpv6Mask[MAX_IP_RANGE_LENGTH * IPV6_ADDRESS_SIZEINBYTES]; + }; +}U_IP_ADDRESS; +struct _packet_info; + +typedef struct _S_HDR_SUPRESSION_CONTEXTINFO +{ + + UCHAR ucaHdrSupressionInBuf[MAX_PHS_LENGTHS]; //Intermediate buffer to accumulate pkt Header for PHS + UCHAR ucaHdrSupressionOutBuf[MAX_PHS_LENGTHS + PHSI_LEN]; //Intermediate buffer containing pkt Header after PHS + +}S_HDR_SUPRESSION_CONTEXTINFO; + + +typedef struct _S_CLASSIFIER_RULE +{ + ULONG ulSFID; + UCHAR ucReserved[2]; + B_UINT16 uiClassifierRuleIndex; + BOOLEAN bUsed; + USHORT usVCID_Value; + B_UINT8 u8ClassifierRulePriority; //This field detemines the Classifier Priority + U_IP_ADDRESS stSrcIpAddress; + UCHAR ucIPSourceAddressLength;//Ip Source Address Length + + U_IP_ADDRESS stDestIpAddress; + UCHAR ucIPDestinationAddressLength;//Ip Destination Address Length + UCHAR ucIPTypeOfServiceLength;//Type of service Length + UCHAR ucTosLow;//Tos Low + UCHAR ucTosHigh;//Tos High + UCHAR ucTosMask;//Tos Mask + + UCHAR ucProtocolLength;//protocol Length + UCHAR ucProtocol[MAX_PROTOCOL_LENGTH];//protocol Length + USHORT usSrcPortRangeLo[MAX_PORT_RANGE]; + USHORT usSrcPortRangeHi[MAX_PORT_RANGE]; + UCHAR ucSrcPortRangeLength; + + USHORT usDestPortRangeLo[MAX_PORT_RANGE]; + USHORT usDestPortRangeHi[MAX_PORT_RANGE]; + UCHAR ucDestPortRangeLength; + + BOOLEAN bProtocolValid; + BOOLEAN bTOSValid; + BOOLEAN bDestIpValid; + BOOLEAN bSrcIpValid; + + //For IPv6 Addressing + UCHAR ucDirection; + BOOLEAN bIpv6Protocol; + UINT32 u32PHSRuleID; + S_PHS_RULE sPhsRule; + UCHAR u8AssociatedPHSI; + + //Classification fields for ETH CS + UCHAR ucEthCSSrcMACLen; + UCHAR au8EThCSSrcMAC[MAC_ADDRESS_SIZE]; + UCHAR au8EThCSSrcMACMask[MAC_ADDRESS_SIZE]; + UCHAR ucEthCSDestMACLen; + UCHAR au8EThCSDestMAC[MAC_ADDRESS_SIZE]; + UCHAR au8EThCSDestMACMask[MAC_ADDRESS_SIZE]; + UCHAR ucEtherTypeLen; + UCHAR au8EthCSEtherType[NUM_ETHERTYPE_BYTES]; + UCHAR usUserPriority[2]; + USHORT usVLANID; + USHORT usValidityBitMap; +}S_CLASSIFIER_RULE; +//typedef struct _S_CLASSIFIER_RULE S_CLASSIFIER_RULE; + +typedef struct _S_FRAGMENTED_PACKET_INFO +{ + BOOLEAN bUsed; + ULONG ulSrcIpAddress; + USHORT usIpIdentification; + S_CLASSIFIER_RULE *pstMatchedClassifierEntry; + BOOLEAN bOutOfOrderFragment; +}S_FRAGMENTED_PACKET_INFO,*PS_FRAGMENTED_PACKET_INFO; + +struct _packet_info +{ + //classification extension Rule + ULONG ulSFID; + USHORT usVCID_Value; + UINT uiThreshold; + // This field determines the priority of the SF Queues + B_UINT8 u8TrafficPriority; + + BOOLEAN bValid; + BOOLEAN bActive; + BOOLEAN bActivateRequestSent; + + B_UINT8 u8QueueType;//BE or rtPS + + UINT uiMaxBucketSize;//maximum size of the bucket for the queue + UINT uiCurrentQueueDepthOnTarget; + UINT uiCurrentBytesOnHost; + UINT uiCurrentPacketsOnHost; + UINT uiDroppedCountBytes; + UINT uiDroppedCountPackets; + UINT uiSentBytes; + UINT uiSentPackets; + UINT uiCurrentDrainRate; + UINT uiThisPeriodSentBytes; + LARGE_INTEGER liDrainCalculated; + UINT uiCurrentTokenCount; + LARGE_INTEGER liLastUpdateTokenAt; + UINT uiMaxAllowedRate; + UINT NumOfPacketsSent; + UCHAR ucDirection; + USHORT usCID; + S_MIBS_EXTSERVICEFLOW_PARAMETERS stMibsExtServiceFlowTable; + UINT uiCurrentRxRate; + UINT uiThisPeriodRxBytes; + UINT uiTotalRxBytes; + UINT uiTotalTxBytes; + UINT uiPendedLast; + UCHAR ucIpVersion; + + union + { + struct + { + struct sk_buff* FirstTxQueue; + struct sk_buff* LastTxQueue; + }; + struct + { + struct sk_buff* ControlHead; + struct sk_buff* ControlTail; + }; + }; + BOOLEAN bProtocolValid; + BOOLEAN bTOSValid; + BOOLEAN bDestIpValid; + BOOLEAN bSrcIpValid; + + BOOLEAN bActiveSet; + BOOLEAN bAdmittedSet; + BOOLEAN bAuthorizedSet; + BOOLEAN bClassifierPriority; + UCHAR ucServiceClassName[MAX_CLASS_NAME_LENGTH]; + BOOLEAN bHeaderSuppressionEnabled; + spinlock_t SFQueueLock; + void *pstSFIndication; + struct timeval stLastUpdateTokenAt; + atomic_t uiPerSFTxResourceCount; + UINT uiMaxLatency; + UCHAR bIPCSSupport; + UCHAR bEthCSSupport; +}; +typedef struct _packet_info PacketInfo; + + +typedef struct _PER_TARANG_DATA +{ + struct _PER_TARANG_DATA * next; + struct _MINI_ADAPTER * Adapter; + struct sk_buff* RxAppControlHead; + struct sk_buff* RxAppControlTail; + volatile INT AppCtrlQueueLen; + BOOLEAN MacTracingEnabled; + BOOLEAN bApplicationToExit; + S_MIBS_DROPPED_APP_CNTRL_MESSAGES stDroppedAppCntrlMsgs; + ULONG RxCntrlMsgBitMask; +} PER_TARANG_DATA, *PPER_TARANG_DATA; + + +#ifdef REL_4_1 +typedef struct _TARGET_PARAMS +{ + B_UINT32 m_u32CfgVersion; + + // Scanning Related Params + B_UINT32 m_u32CenterFrequency; + B_UINT32 m_u32BandAScan; + B_UINT32 m_u32BandBScan; + B_UINT32 m_u32BandCScan; + + // QoS Params + B_UINT32 m_u32minGrantsize; // size of minimum grant is 0 or 6 + B_UINT32 m_u32PHSEnable; + + // HO Params + B_UINT32 m_u32HoEnable; + B_UINT32 m_u32HoReserved1; + B_UINT32 m_u32HoReserved2; + + // Power Control Params + B_UINT32 m_u32MimoEnable; + B_UINT32 m_u32SecurityEnable; + /* + * bit 1: 1 Idlemode enable; + * bit 2: 1 Sleepmode Enable + */ + B_UINT32 m_u32PowerSavingModesEnable; + /* PowerSaving Mode Options: + bit 0 = 1: CPE mode - to keep pcmcia if alive; + bit 1 = 1: CINR reporing in Idlemode Msg + bit 2 = 1: Default PSC Enable in sleepmode*/ + B_UINT32 m_u32PowerSavingModeOptions; + + B_UINT32 m_u32ArqEnable; + + // From Version #3, the HARQ section renamed as general + B_UINT32 m_u32HarqEnable; + // EEPROM Param Location + B_UINT32 m_u32EEPROMFlag; + /* BINARY TYPE - 4th MSByte: + * Interface Type - 3rd MSByte: + * Vendor Type - 2nd MSByte + */ + // Unused - LSByte + B_UINT32 m_u32Customize; + B_UINT32 m_u32ConfigBW; /* In Hz */ + B_UINT32 m_u32ShutDownTimer; + + + B_UINT32 m_u32RadioParameter; + B_UINT32 m_u32PhyParameter1; + B_UINT32 m_u32PhyParameter2; + B_UINT32 m_u32PhyParameter3; + + /* in eval mode only; + * lower 16bits = basic cid for testing; + * then bit 16 is test cqich, + * bit 17 test init rang; + * bit 18 test periodic rang + * bit 19 is test harq ack/nack + */ + B_UINT32 m_u32TestOptions; + + B_UINT32 m_u32MaxMACDataperDLFrame; + B_UINT32 m_u32MaxMACDataperULFrame; + + B_UINT32 m_u32Corr2MacFlags; + + //adding driver params. + B_UINT32 HostDrvrConfig1; + B_UINT32 HostDrvrConfig2; + B_UINT32 HostDrvrConfig3; + B_UINT32 HostDrvrConfig4; + B_UINT32 HostDrvrConfig5; + B_UINT32 HostDrvrConfig6; + B_UINT32 m_u32SegmentedPUSCenable; + + // BAMC enable - but 4.x does not support this feature + // This is added just to sync 4.x and 5.x CFGs + B_UINT32 m_u32BandAMCEnable; +} STARGETPARAMS, *PSTARGETPARAMS; +#endif + +typedef struct _STTARGETDSXBUFFER +{ + ULONG ulTargetDsxBuffer; + B_UINT16 tid; + BOOLEAN valid; +}STTARGETDSXBUFFER, *PSTTARGETDSXBUFFER; + +typedef INT (*FP_FLASH_WRITE)(struct _MINI_ADAPTER*,UINT,PVOID); + +typedef INT (*FP_FLASH_WRITE_STATUS)(struct _MINI_ADAPTER*,UINT,PVOID); + +/** +Driver adapter data structure +*/ +struct _MINI_ADAPTER +{ + struct _MINI_ADAPTER *next; + struct net_device *dev; + u32 msg_enable; + + CHAR *caDsxReqResp; + atomic_t ApplicationRunning; + volatile INT CtrlQueueLen; + atomic_t AppCtrlQueueLen; + BOOLEAN AppCtrlQueueOverFlow; + atomic_t CurrentApplicationCount; + atomic_t RegisteredApplicationCount; + BOOLEAN LinkUpStatus; + BOOLEAN TimerActive; + u32 StatisticsPointer; + struct sk_buff *RxControlHead; + struct sk_buff *RxControlTail; + + struct semaphore RxAppControlQueuelock; + struct semaphore fw_download_sema; + + PPER_TARANG_DATA pTarangs; + spinlock_t control_queue_lock; + wait_queue_head_t process_read_wait_queue; + + // the pointer to the first packet we have queued in send + // deserialized miniport support variables + atomic_t TotalPacketCount; + atomic_t TxPktAvail; + + // this to keep track of the Tx and Rx MailBox Registers. + atomic_t CurrNumFreeTxDesc; + // to keep track the no of byte received + USHORT PrevNumRecvDescs; + USHORT CurrNumRecvDescs; + UINT u32TotalDSD; + PacketInfo PackInfo[NO_OF_QUEUES]; + S_CLASSIFIER_RULE astClassifierTable[MAX_CLASSIFIERS]; + BOOLEAN TransferMode; + + /*************** qos ******************/ + BOOLEAN bETHCSEnabled; + + ULONG BEBucketSize; + ULONG rtPSBucketSize; + UCHAR LinkStatus; + BOOLEAN AutoLinkUp; + BOOLEAN AutoSyncup; + + int major; + int minor; + wait_queue_head_t tx_packet_wait_queue; + wait_queue_head_t process_rx_cntrlpkt; + atomic_t process_waiting; + BOOLEAN fw_download_done; + + char *txctlpacket[MAX_CNTRL_PKTS]; + atomic_t cntrlpktCnt ; + atomic_t index_app_read_cntrlpkt; + atomic_t index_wr_txcntrlpkt; + atomic_t index_rd_txcntrlpkt; + UINT index_datpkt; + struct semaphore rdmwrmsync; + + STTARGETDSXBUFFER astTargetDsxBuffer[MAX_TARGET_DSX_BUFFERS]; + ULONG ulFreeTargetBufferCnt; + ULONG ulCurrentTargetBuffer; + ULONG ulTotalTargetBuffersAvailable; + + unsigned long chip_id; + + wait_queue_head_t lowpower_mode_wait_queue; + + BOOLEAN bFlashBoot; + BOOLEAN bBinDownloaded; + BOOLEAN bCfgDownloaded; + BOOLEAN bSyncUpRequestSent; + USHORT usBestEffortQueueIndex; + + wait_queue_head_t ioctl_fw_dnld_wait_queue; + BOOLEAN waiting_to_fw_download_done; + pid_t fw_download_process_pid; + PSTARGETPARAMS pstargetparams; + BOOLEAN device_removed; + BOOLEAN DeviceAccess; + BOOLEAN bIsAutoCorrectEnabled; + BOOLEAN bDDRInitDone; + INT DDRSetting; + ULONG ulPowerSaveMode; + spinlock_t txtransmitlock; + B_UINT8 txtransmit_running; + /* Thread for control packet handling */ + struct task_struct *control_packet_handler; + /* thread for transmitting packets. */ + struct task_struct *transmit_packet_thread; + + /* LED Related Structures */ + LED_INFO_STRUCT LEDInfo; + + /* Driver State for LED Blinking */ + LedEventInfo_t DriverState; + /* Interface Specific */ + PVOID pvInterfaceAdapter; + int (*bcm_file_download)( PVOID, + struct file *, + unsigned int); + int (*bcm_file_readback_from_chip)( PVOID, + struct file *, + unsigned int); + INT (*interface_rdm)(PVOID, + UINT , + PVOID , + INT); + INT (*interface_wrm)(PVOID, + UINT , + PVOID , + INT); + int (*interface_transmit)(PVOID, PVOID , UINT); + BOOLEAN IdleMode; + BOOLEAN bDregRequestSentInIdleMode; + BOOLEAN bTriedToWakeUpFromlowPowerMode; + BOOLEAN bShutStatus; + BOOLEAN bWakeUpDevice; + unsigned int usIdleModePattern; + //BOOLEAN bTriedToWakeUpFromShutdown; + BOOLEAN bLinkDownRequested; + + int downloadDDR; + PHS_DEVICE_EXTENSION stBCMPhsContext; + S_HDR_SUPRESSION_CONTEXTINFO stPhsTxContextInfo; + uint8_t ucaPHSPktRestoreBuf[2048]; + uint8_t bPHSEnabled; + BOOLEAN AutoFirmDld; + BOOLEAN bMipsConfig; + BOOLEAN bDPLLConfig; + UINT32 aTxPktSizeHist[MIBS_MAX_HIST_ENTRIES]; + UINT32 aRxPktSizeHist[MIBS_MAX_HIST_ENTRIES]; + S_FRAGMENTED_PACKET_INFO astFragmentedPktClassifierTable[MAX_FRAGMENTEDIP_CLASSIFICATION_ENTRIES]; + atomic_t uiMBupdate; + UINT32 PmuMode; + NVM_TYPE eNVMType; + UINT uiSectorSize; + UINT uiSectorSizeInCFG; + BOOLEAN bSectorSizeOverride; + BOOLEAN bStatusWrite; + UINT uiNVMDSDSize; + UINT uiVendorExtnFlag; + //it will always represent chosen DSD at any point of time. + // Generally it is Active DSD but in case of NVM RD/WR it might be different. + UINT ulFlashCalStart; + ULONG ulFlashControlSectionStart; + ULONG ulFlashWriteSize; + ULONG ulFlashID; + FP_FLASH_WRITE fpFlashWrite; + FP_FLASH_WRITE_STATUS fpFlashWriteWithStatusCheck; + + + struct semaphore NVMRdmWrmLock; + + struct device *pstCreatedClassDevice; + +// BOOLEAN InterfaceUpStatus; + PFLASH2X_CS_INFO psFlash2xCSInfo; + PFLASH_CS_INFO psFlashCSInfo ; + PFLASH2X_VENDORSPECIFIC_INFO psFlash2xVendorInfo; + UINT uiFlashBaseAdd; //Flash start address + UINT uiActiveISOOffset; //Active ISO offset chosen before f/w download + FLASH2X_SECTION_VAL eActiveISO; //Active ISO section val + FLASH2X_SECTION_VAL eActiveDSD; //Active DSD val chosen before f/w download + UINT uiActiveDSDOffsetAtFwDld; //For accessing Active DSD chosen before f/w download + UINT uiFlashLayoutMajorVersion ; + UINT uiFlashLayoutMinorVersion; + BOOLEAN bAllDSDWriteAllow ; + BOOLEAN bSigCorrupted ; + //this should be set who so ever want to change the Headers. after Wrtie it should be reset immediately. + BOOLEAN bHeaderChangeAllowed ; + INT SelectedChip ; + BOOLEAN bEndPointHalted; + //while bFlashRawRead will be true, Driver ignore map lay out and consider flash as of without any map. + BOOLEAN bFlashRawRead; + BOOLEAN bPreparingForLowPowerMode ; + BOOLEAN bDoSuspend ; + UINT syscfgBefFwDld ; + BOOLEAN StopAllXaction ; + UINT32 liTimeSinceLastNetEntry; //Used to Support extended CAPI requirements from + struct semaphore LowPowerModeSync; + ULONG liDrainCalculated; + UINT gpioBitMap; + + S_BCM_DEBUG_STATE stDebugState; + +}; +typedef struct _MINI_ADAPTER MINI_ADAPTER, *PMINI_ADAPTER; + +#define GET_BCM_ADAPTER(net_dev) netdev_priv(net_dev) + +struct _ETH_HEADER_STRUC { + UCHAR au8DestinationAddress[6]; + UCHAR au8SourceAddress[6]; + USHORT u16Etype; +}__attribute__((packed)); +typedef struct _ETH_HEADER_STRUC ETH_HEADER_STRUC, *PETH_HEADER_STRUC; + + +typedef struct FirmwareInfo +{ + void __user * pvMappedFirmwareAddress; + ULONG u32FirmwareLength; + ULONG u32StartingAddress; +}__attribute__((packed)) FIRMWARE_INFO, *PFIRMWARE_INFO; + +// holds the value of net_device structure.. +extern struct net_device *gblpnetdev; +typedef struct _cntl_pkt{ + PMINI_ADAPTER Adapter; + PLEADER PLeader; +}cntl_pkt; +typedef LINK_REQUEST CONTROL_MESSAGE; + +typedef struct _DDR_SETTING +{ + UINT ulRegAddress; + UINT ulRegValue; +}DDR_SETTING, *PDDR_SETTING; +typedef DDR_SETTING DDR_SET_NODE, *PDDR_SET_NODE; +INT +InitAdapter(PMINI_ADAPTER psAdapter); + +// ===================================================================== +// Beceem vendor request codes for EP0 +// ===================================================================== + +#define BCM_REQUEST_READ 0x2 +#define BCM_REQUEST_WRITE 0x1 +#define EP2_MPS_REG 0x0F0110A0 +#define EP2_MPS 0x40 + +#define EP2_CFG_REG 0x0F0110A8 +#define EP2_CFG_INT 0x27 +#define EP2_CFG_BULK 0x25 + +#define EP4_MPS_REG 0x0F0110F0 +#define EP4_MPS 0x8C + +#define EP4_CFG_REG 0x0F0110F8 + +#define ISO_MPS_REG 0x0F0110C8 +#define ISO_MPS 0x00000000 + + +#define EP1 0 +#define EP2 1 +#define EP3 2 +#define EP4 3 +#define EP5 4 +#define EP6 5 + + +typedef enum eInterface_setting +{ + DEFAULT_SETTING_0 = 0, + ALTERNATE_SETTING_1 = 1, +}INTERFACE_SETTING; + +#endif //__ADAPTER_H__ + diff --git a/drivers/staging/bcm/Bcmchar.c b/drivers/staging/bcm/Bcmchar.c new file mode 100644 index 00000000..cf305921 --- /dev/null +++ b/drivers/staging/bcm/Bcmchar.c @@ -0,0 +1,2049 @@ +#include + +#include "headers.h" +/*************************************************************** +* Function - bcm_char_open() +* +* Description - This is the "open" entry point for the character +* driver. +* +* Parameters - inode: Pointer to the Inode structure of char device +* filp : File pointer of the char device +* +* Returns - Zero(Success) +****************************************************************/ + +static int bcm_char_open(struct inode *inode, struct file * filp) +{ + PMINI_ADAPTER Adapter = NULL; + PPER_TARANG_DATA pTarang = NULL; + + Adapter = GET_BCM_ADAPTER(gblpnetdev); + pTarang = kzalloc(sizeof(PER_TARANG_DATA), GFP_KERNEL); + if (!pTarang) + return -ENOMEM; + + pTarang->Adapter = Adapter; + pTarang->RxCntrlMsgBitMask = 0xFFFFFFFF & ~(1 << 0xB); + + down(&Adapter->RxAppControlQueuelock); + pTarang->next = Adapter->pTarangs; + Adapter->pTarangs = pTarang; + up(&Adapter->RxAppControlQueuelock); + + /* Store the Adapter structure */ + filp->private_data = pTarang; + + /* Start Queuing the control response Packets */ + atomic_inc(&Adapter->ApplicationRunning); + + nonseekable_open(inode, filp); + return 0; +} + +static int bcm_char_release(struct inode *inode, struct file *filp) +{ + PPER_TARANG_DATA pTarang, tmp, ptmp; + PMINI_ADAPTER Adapter = NULL; + struct sk_buff *pkt, *npkt; + + pTarang = (PPER_TARANG_DATA)filp->private_data; + + if (pTarang == NULL) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "ptarang is null\n"); + return 0; + } + + Adapter = pTarang->Adapter; + + down(&Adapter->RxAppControlQueuelock); + + tmp = Adapter->pTarangs; + for (ptmp = NULL; tmp; ptmp = tmp, tmp = tmp->next) { + if (tmp == pTarang) + break; + } + + if (tmp) { + if (!ptmp) + Adapter->pTarangs = tmp->next; + else + ptmp->next = tmp->next; + } else { + up(&Adapter->RxAppControlQueuelock); + return 0; + } + + pkt = pTarang->RxAppControlHead; + while (pkt) { + npkt = pkt->next; + kfree_skb(pkt); + pkt = npkt; + } + + up(&Adapter->RxAppControlQueuelock); + + /* Stop Queuing the control response Packets */ + atomic_dec(&Adapter->ApplicationRunning); + + kfree(pTarang); + + /* remove this filp from the asynchronously notified filp's */ + filp->private_data = NULL; + return 0; +} + +static ssize_t bcm_char_read(struct file *filp, char __user *buf, size_t size, + loff_t *f_pos) +{ + PPER_TARANG_DATA pTarang = filp->private_data; + PMINI_ADAPTER Adapter = pTarang->Adapter; + struct sk_buff *Packet = NULL; + ssize_t PktLen = 0; + int wait_ret_val = 0; + unsigned long ret = 0; + + wait_ret_val = wait_event_interruptible(Adapter->process_read_wait_queue, + (pTarang->RxAppControlHead || + Adapter->device_removed)); + if ((wait_ret_val == -ERESTARTSYS)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, + "Exiting as i've been asked to exit!!!\n"); + return wait_ret_val; + } + + if (Adapter->device_removed) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, + "Device Removed... Killing the Apps...\n"); + return -ENODEV; + } + + if (FALSE == Adapter->fw_download_done) + return -EACCES; + + down(&Adapter->RxAppControlQueuelock); + + if (pTarang->RxAppControlHead) { + Packet = pTarang->RxAppControlHead; + DEQUEUEPACKET(pTarang->RxAppControlHead, + pTarang->RxAppControlTail); + pTarang->AppCtrlQueueLen--; + } + + up(&Adapter->RxAppControlQueuelock); + + if (Packet) { + PktLen = Packet->len; + ret = copy_to_user(buf, Packet->data, + min_t(size_t, PktLen, size)); + if (ret) { + dev_kfree_skb(Packet); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "Returning from copy to user failure\n"); + return -EFAULT; + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, + "Read %zd Bytes From Adapter packet = %p by process %d!\n", + PktLen, Packet, current->pid); + dev_kfree_skb(Packet); + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "<\n"); + return PktLen; +} + +static long bcm_char_ioctl(struct file *filp, UINT cmd, ULONG arg) +{ + PPER_TARANG_DATA pTarang = filp->private_data; + void __user *argp = (void __user *)arg; + PMINI_ADAPTER Adapter = pTarang->Adapter; + INT Status = STATUS_FAILURE; + int timeout = 0; + IOCTL_BUFFER IoBuffer; + int bytes; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Parameters Passed to control IOCTL cmd=0x%X arg=0x%lX", cmd, arg); + + if (_IOC_TYPE(cmd) != BCM_IOCTL) + return -EFAULT; + if (_IOC_DIR(cmd) & _IOC_READ) + Status = !access_ok(VERIFY_WRITE, argp, _IOC_SIZE(cmd)); + else if (_IOC_DIR(cmd) & _IOC_WRITE) + Status = !access_ok(VERIFY_READ, argp, _IOC_SIZE(cmd)); + else if (_IOC_NONE == (_IOC_DIR(cmd) & _IOC_NONE)) + Status = STATUS_SUCCESS; + + if (Status) + return -EFAULT; + + if (Adapter->device_removed) + return -EFAULT; + + if (FALSE == Adapter->fw_download_done) { + switch (cmd) { + case IOCTL_MAC_ADDR_REQ: + case IOCTL_LINK_REQ: + case IOCTL_CM_REQUEST: + case IOCTL_SS_INFO_REQ: + case IOCTL_SEND_CONTROL_MESSAGE: + case IOCTL_IDLE_REQ: + case IOCTL_BCM_GPIO_SET_REQUEST: + case IOCTL_BCM_GPIO_STATUS_REQUEST: + return -EACCES; + default: + break; + } + } + + Status = vendorextnIoctl(Adapter, cmd, arg); + if (Status != CONTINUE_COMMON_PATH) + return Status; + + switch (cmd) { + /* Rdms for Swin Idle... */ + case IOCTL_BCM_REGISTER_READ_PRIVATE: { + RDM_BUFFER sRdmBuffer = {0}; + PCHAR temp_buff; + UINT Bufflen; + u16 temp_value; + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength > sizeof(sRdmBuffer)) + return -EINVAL; + + if (copy_from_user(&sRdmBuffer, IoBuffer.InputBuffer, IoBuffer.InputLength)) + return -EFAULT; + + if (IoBuffer.OutputLength > USHRT_MAX || + IoBuffer.OutputLength == 0) { + return -EINVAL; + } + + Bufflen = IoBuffer.OutputLength; + temp_value = 4 - (Bufflen % 4); + Bufflen += temp_value % 4; + + temp_buff = kmalloc(Bufflen, GFP_KERNEL); + if (!temp_buff) + return -ENOMEM; + + bytes = rdmalt(Adapter, (UINT)sRdmBuffer.Register, + (PUINT)temp_buff, Bufflen); + if (bytes > 0) { + Status = STATUS_SUCCESS; + if (copy_to_user(IoBuffer.OutputBuffer, temp_buff, bytes)) { + kfree(temp_buff); + return -EFAULT; + } + } else { + Status = bytes; + } + + kfree(temp_buff); + break; + } + + case IOCTL_BCM_REGISTER_WRITE_PRIVATE: { + WRM_BUFFER sWrmBuffer = {0}; + UINT uiTempVar = 0; + /* Copy Ioctl Buffer structure */ + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength > sizeof(sWrmBuffer)) + return -EINVAL; + + /* Get WrmBuffer structure */ + if (copy_from_user(&sWrmBuffer, IoBuffer.InputBuffer, IoBuffer.InputLength)) + return -EFAULT; + + uiTempVar = sWrmBuffer.Register & EEPROM_REJECT_MASK; + if (!((Adapter->pstargetparams->m_u32Customize) & VSG_MODE) && + ((uiTempVar == EEPROM_REJECT_REG_1) || + (uiTempVar == EEPROM_REJECT_REG_2) || + (uiTempVar == EEPROM_REJECT_REG_3) || + (uiTempVar == EEPROM_REJECT_REG_4))) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "EEPROM Access Denied, not in VSG Mode\n"); + return -EFAULT; + } + + Status = wrmalt(Adapter, (UINT)sWrmBuffer.Register, + (PUINT)sWrmBuffer.Data, sizeof(ULONG)); + + if (Status == STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "WRM Done\n"); + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "WRM Failed\n"); + Status = -EFAULT; + } + break; + } + + case IOCTL_BCM_REGISTER_READ: + case IOCTL_BCM_EEPROM_REGISTER_READ: { + RDM_BUFFER sRdmBuffer = {0}; + PCHAR temp_buff = NULL; + UINT uiTempVar = 0; + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Device in Idle Mode, Blocking Rdms\n"); + return -EACCES; + } + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength > sizeof(sRdmBuffer)) + return -EINVAL; + + if (copy_from_user(&sRdmBuffer, IoBuffer.InputBuffer, IoBuffer.InputLength)) + return -EFAULT; + + if (IoBuffer.OutputLength > USHRT_MAX || + IoBuffer.OutputLength == 0) { + return -EINVAL; + } + + temp_buff = kmalloc(IoBuffer.OutputLength, GFP_KERNEL); + if (!temp_buff) + return STATUS_FAILURE; + + if ((((ULONG)sRdmBuffer.Register & 0x0F000000) != 0x0F000000) || + ((ULONG)sRdmBuffer.Register & 0x3)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "RDM Done On invalid Address : %x Access Denied.\n", + (int)sRdmBuffer.Register); + + kfree(temp_buff); + return -EINVAL; + } + + uiTempVar = sRdmBuffer.Register & EEPROM_REJECT_MASK; + bytes = rdmaltWithLock(Adapter, (UINT)sRdmBuffer.Register, (PUINT)temp_buff, IoBuffer.OutputLength); + + if (bytes > 0) { + Status = STATUS_SUCCESS; + if (copy_to_user(IoBuffer.OutputBuffer, temp_buff, bytes)) { + kfree(temp_buff); + return -EFAULT; + } + } else { + Status = bytes; + } + + kfree(temp_buff); + break; + } + case IOCTL_BCM_REGISTER_WRITE: + case IOCTL_BCM_EEPROM_REGISTER_WRITE: { + WRM_BUFFER sWrmBuffer = {0}; + UINT uiTempVar = 0; + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Device in Idle Mode, Blocking Wrms\n"); + return -EACCES; + } + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength > sizeof(sWrmBuffer)) + return -EINVAL; + + /* Get WrmBuffer structure */ + if (copy_from_user(&sWrmBuffer, IoBuffer.InputBuffer, IoBuffer.InputLength)) + return -EFAULT; + + if ((((ULONG)sWrmBuffer.Register & 0x0F000000) != 0x0F000000) || + ((ULONG)sWrmBuffer.Register & 0x3)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "WRM Done On invalid Address : %x Access Denied.\n", (int)sWrmBuffer.Register); + return -EINVAL; + } + + uiTempVar = sWrmBuffer.Register & EEPROM_REJECT_MASK; + if (!((Adapter->pstargetparams->m_u32Customize) & VSG_MODE) && + ((uiTempVar == EEPROM_REJECT_REG_1) || + (uiTempVar == EEPROM_REJECT_REG_2) || + (uiTempVar == EEPROM_REJECT_REG_3) || + (uiTempVar == EEPROM_REJECT_REG_4)) && + (cmd == IOCTL_BCM_REGISTER_WRITE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "EEPROM Access Denied, not in VSG Mode\n"); + return -EFAULT; + } + + Status = wrmaltWithLock(Adapter, (UINT)sWrmBuffer.Register, + (PUINT)sWrmBuffer.Data, sWrmBuffer.Length); + + if (Status == STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, OSAL_DBG, DBG_LVL_ALL, "WRM Done\n"); + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "WRM Failed\n"); + Status = -EFAULT; + } + break; + } + case IOCTL_BCM_GPIO_SET_REQUEST: { + UCHAR ucResetValue[4]; + UINT value = 0; + UINT uiBit = 0; + UINT uiOperation = 0; + + GPIO_INFO gpio_info = {0}; + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "GPIO Can't be set/clear in Low power Mode"); + return -EACCES; + } + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength > sizeof(gpio_info)) + return -EINVAL; + + if (copy_from_user(&gpio_info, IoBuffer.InputBuffer, IoBuffer.InputLength)) + return -EFAULT; + + uiBit = gpio_info.uiGpioNumber; + uiOperation = gpio_info.uiGpioValue; + value = (1< is not correspond to LED !!!", value); + Status = -EINVAL; + break; + } + + /* Set - setting 1 */ + if (uiOperation) { + /* Set the gpio output register */ + Status = wrmaltWithLock(Adapter, BCM_GPIO_OUTPUT_SET_REG, (PUINT)(&value), sizeof(UINT)); + + if (Status == STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Set the GPIO bit\n"); + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Failed to set the %dth GPIO\n", uiBit); + break; + } + } else { + /* Set the gpio output register */ + Status = wrmaltWithLock(Adapter, BCM_GPIO_OUTPUT_CLR_REG, (PUINT)(&value), sizeof(UINT)); + + if (Status == STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Set the GPIO bit\n"); + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Failed to clear the %dth GPIO\n", uiBit); + break; + } + } + + bytes = rdmaltWithLock(Adapter, (UINT)GPIO_MODE_REGISTER, (PUINT)ucResetValue, sizeof(UINT)); + if (bytes < 0) { + Status = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, + "GPIO_MODE_REGISTER read failed"); + break; + } else { + Status = STATUS_SUCCESS; + } + + /* Set the gpio mode register to output */ + *(UINT *)ucResetValue |= (1<IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "GPIO Can't be set/clear in Low power Mode"); + Status = -EACCES; + break; + } + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength > sizeof(threadReq)) + return -EINVAL; + + if (copy_from_user(&threadReq, IoBuffer.InputBuffer, IoBuffer.InputLength)) + return -EFAULT; + + /* if LED thread is running(Actively or Inactively) set it state to make inactive */ + if (Adapter->LEDInfo.led_thread_running) { + if (threadReq.ThreadState == LED_THREAD_ACTIVATION_REQ) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Activating thread req"); + Adapter->DriverState = LED_THREAD_ACTIVE; + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "DeActivating Thread req....."); + Adapter->DriverState = LED_THREAD_INACTIVE; + } + + /* signal thread. */ + wake_up(&Adapter->LEDInfo.notify_led_event); + } + } + break; + + case IOCTL_BCM_GPIO_STATUS_REQUEST: { + ULONG uiBit = 0; + UCHAR ucRead[4]; + GPIO_INFO gpio_info = {0}; + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) + return -EACCES; + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength > sizeof(gpio_info)) + return -EINVAL; + + if (copy_from_user(&gpio_info, IoBuffer.InputBuffer, IoBuffer.InputLength)) + return -EFAULT; + + uiBit = gpio_info.uiGpioNumber; + + /* Set the gpio output register */ + bytes = rdmaltWithLock(Adapter, (UINT)GPIO_PIN_STATE_REGISTER, + (PUINT)ucRead, sizeof(UINT)); + + if (bytes < 0) { + Status = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "RDM Failed\n"); + return Status; + } else { + Status = STATUS_SUCCESS; + } + } + break; + + case IOCTL_BCM_GPIO_MULTI_REQUEST: { + UCHAR ucResetValue[4]; + GPIO_MULTI_INFO gpio_multi_info[MAX_IDX]; + PGPIO_MULTI_INFO pgpio_multi_info = (PGPIO_MULTI_INFO)gpio_multi_info; + + memset(pgpio_multi_info, 0, MAX_IDX * sizeof(GPIO_MULTI_INFO)); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) + return -EINVAL; + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength > sizeof(gpio_multi_info)) + return -EINVAL; + + if (copy_from_user(&gpio_multi_info, IoBuffer.InputBuffer, IoBuffer.InputLength)) + return -EFAULT; + + if (IsReqGpioIsLedInNVM(Adapter, pgpio_multi_info[WIMAX_IDX].uiGPIOMask) == FALSE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, + "Sorry, Requested GPIO<0x%X> is not correspond to NVM LED bit map<0x%X>!!!", + pgpio_multi_info[WIMAX_IDX].uiGPIOMask, Adapter->gpioBitMap); + Status = -EINVAL; + break; + } + + /* Set the gpio output register */ + if ((pgpio_multi_info[WIMAX_IDX].uiGPIOMask) & + (pgpio_multi_info[WIMAX_IDX].uiGPIOCommand)) { + /* Set 1's in GPIO OUTPUT REGISTER */ + *(UINT *)ucResetValue = pgpio_multi_info[WIMAX_IDX].uiGPIOMask & + pgpio_multi_info[WIMAX_IDX].uiGPIOCommand & + pgpio_multi_info[WIMAX_IDX].uiGPIOValue; + + if (*(UINT *) ucResetValue) + Status = wrmaltWithLock(Adapter, BCM_GPIO_OUTPUT_SET_REG, + (PUINT)ucResetValue, sizeof(ULONG)); + + if (Status != STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "WRM to BCM_GPIO_OUTPUT_SET_REG Failed."); + return Status; + } + + /* Clear to 0's in GPIO OUTPUT REGISTER */ + *(UINT *)ucResetValue = (pgpio_multi_info[WIMAX_IDX].uiGPIOMask & + pgpio_multi_info[WIMAX_IDX].uiGPIOCommand & + (~(pgpio_multi_info[WIMAX_IDX].uiGPIOValue))); + + if (*(UINT *) ucResetValue) + Status = wrmaltWithLock(Adapter, BCM_GPIO_OUTPUT_CLR_REG, (PUINT)ucResetValue, sizeof(ULONG)); + + if (Status != STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "WRM to BCM_GPIO_OUTPUT_CLR_REG Failed."); + return Status; + } + } + + if (pgpio_multi_info[WIMAX_IDX].uiGPIOMask) { + bytes = rdmaltWithLock(Adapter, (UINT)GPIO_PIN_STATE_REGISTER, (PUINT)ucResetValue, sizeof(UINT)); + + if (bytes < 0) { + Status = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "RDM to GPIO_PIN_STATE_REGISTER Failed."); + return Status; + } else { + Status = STATUS_SUCCESS; + } + + pgpio_multi_info[WIMAX_IDX].uiGPIOValue = (*(UINT *)ucResetValue & + pgpio_multi_info[WIMAX_IDX].uiGPIOMask); + } + + Status = copy_to_user(IoBuffer.OutputBuffer, &gpio_multi_info, IoBuffer.OutputLength); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "Failed while copying Content to IOBufer for user space err:%d", Status); + return -EFAULT; + } + } + break; + + case IOCTL_BCM_GPIO_MODE_REQUEST: { + UCHAR ucResetValue[4]; + GPIO_MULTI_MODE gpio_multi_mode[MAX_IDX]; + PGPIO_MULTI_MODE pgpio_multi_mode = (PGPIO_MULTI_MODE)gpio_multi_mode; + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) + return -EINVAL; + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength > sizeof(gpio_multi_mode)) + return -EINVAL; + + if (copy_from_user(&gpio_multi_mode, IoBuffer.InputBuffer, IoBuffer.InputLength)) + return -EFAULT; + + bytes = rdmaltWithLock(Adapter, (UINT)GPIO_MODE_REGISTER, (PUINT)ucResetValue, sizeof(UINT)); + + if (bytes < 0) { + Status = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Read of GPIO_MODE_REGISTER failed"); + return Status; + } else { + Status = STATUS_SUCCESS; + } + + /* Validating the request */ + if (IsReqGpioIsLedInNVM(Adapter, pgpio_multi_mode[WIMAX_IDX].uiGPIOMask) == FALSE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, + "Sorry, Requested GPIO<0x%X> is not correspond to NVM LED bit map<0x%X>!!!", + pgpio_multi_mode[WIMAX_IDX].uiGPIOMask, Adapter->gpioBitMap); + Status = -EINVAL; + break; + } + + if (pgpio_multi_mode[WIMAX_IDX].uiGPIOMask) { + /* write all OUT's (1's) */ + *(UINT *) ucResetValue |= (pgpio_multi_mode[WIMAX_IDX].uiGPIOMode & + pgpio_multi_mode[WIMAX_IDX].uiGPIOMask); + + /* write all IN's (0's) */ + *(UINT *) ucResetValue &= ~((~pgpio_multi_mode[WIMAX_IDX].uiGPIOMode) & + pgpio_multi_mode[WIMAX_IDX].uiGPIOMask); + + /* Currently implemented return the modes of all GPIO's + * else needs to bit AND with mask + */ + pgpio_multi_mode[WIMAX_IDX].uiGPIOMode = *(UINT *)ucResetValue; + + Status = wrmaltWithLock(Adapter, GPIO_MODE_REGISTER, (PUINT)ucResetValue, sizeof(ULONG)); + if (Status == STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, + "WRM to GPIO_MODE_REGISTER Done"); + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "WRM to GPIO_MODE_REGISTER Failed"); + Status = -EFAULT; + break; + } + } else { +/* if uiGPIOMask is 0 then return mode register configuration */ + pgpio_multi_mode[WIMAX_IDX].uiGPIOMode = *(UINT *)ucResetValue; + } + + Status = copy_to_user(IoBuffer.OutputBuffer, &gpio_multi_mode, IoBuffer.OutputLength); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "Failed while copying Content to IOBufer for user space err:%d", Status); + return -EFAULT; + } + } + break; + + case IOCTL_MAC_ADDR_REQ: + case IOCTL_LINK_REQ: + case IOCTL_CM_REQUEST: + case IOCTL_SS_INFO_REQ: + case IOCTL_SEND_CONTROL_MESSAGE: + case IOCTL_IDLE_REQ: { + PVOID pvBuffer = NULL; + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength < sizeof(struct link_request)) + return -EINVAL; + + if (IoBuffer.InputLength > MAX_CNTL_PKT_SIZE) + return -EINVAL; + + pvBuffer = memdup_user(IoBuffer.InputBuffer, + IoBuffer.InputLength); + if (IS_ERR(pvBuffer)) + return PTR_ERR(pvBuffer); + + down(&Adapter->LowPowerModeSync); + Status = wait_event_interruptible_timeout(Adapter->lowpower_mode_wait_queue, + !Adapter->bPreparingForLowPowerMode, + (1 * HZ)); + if (Status == -ERESTARTSYS) + goto cntrlEnd; + + if (Adapter->bPreparingForLowPowerMode) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, + "Preparing Idle Mode is still True - Hence Rejecting control message\n"); + Status = STATUS_FAILURE; + goto cntrlEnd; + } + Status = CopyBufferToControlPacket(Adapter, (PVOID)pvBuffer); + +cntrlEnd: + up(&Adapter->LowPowerModeSync); + kfree(pvBuffer); + break; + } + + case IOCTL_BCM_BUFFER_DOWNLOAD_START: { + if (down_trylock(&Adapter->NVMRdmWrmLock)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, + "IOCTL_BCM_CHIP_RESET not allowed as EEPROM Read/Write is in progress\n"); + return -EACCES; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "Starting the firmware download PID =0x%x!!!!\n", current->pid); + + if (down_trylock(&Adapter->fw_download_sema)) + return -EBUSY; + + Adapter->bBinDownloaded = FALSE; + Adapter->fw_download_process_pid = current->pid; + Adapter->bCfgDownloaded = FALSE; + Adapter->fw_download_done = FALSE; + netif_carrier_off(Adapter->dev); + netif_stop_queue(Adapter->dev); + Status = reset_card_proc(Adapter); + if (Status) { + pr_err(PFX "%s: reset_card_proc Failed!\n", Adapter->dev->name); + up(&Adapter->fw_download_sema); + up(&Adapter->NVMRdmWrmLock); + return Status; + } + mdelay(10); + + up(&Adapter->NVMRdmWrmLock); + return Status; + } + + case IOCTL_BCM_BUFFER_DOWNLOAD: { + FIRMWARE_INFO *psFwInfo = NULL; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Starting the firmware download PID =0x%x!!!!\n", current->pid); + + if (!down_trylock(&Adapter->fw_download_sema)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "Invalid way to download buffer. Use Start and then call this!!!\n"); + up(&Adapter->fw_download_sema); + Status = -EINVAL; + return Status; + } + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) { + up(&Adapter->fw_download_sema); + return -EFAULT; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "Length for FW DLD is : %lx\n", IoBuffer.InputLength); + + if (IoBuffer.InputLength > sizeof(FIRMWARE_INFO)) { + up(&Adapter->fw_download_sema); + return -EINVAL; + } + + psFwInfo = kmalloc(sizeof(*psFwInfo), GFP_KERNEL); + if (!psFwInfo) { + up(&Adapter->fw_download_sema); + return -ENOMEM; + } + + if (copy_from_user(psFwInfo, IoBuffer.InputBuffer, IoBuffer.InputLength)) { + up(&Adapter->fw_download_sema); + return -EFAULT; + } + + if (!psFwInfo->pvMappedFirmwareAddress || + (psFwInfo->u32FirmwareLength == 0)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Something else is wrong %lu\n", + psFwInfo->u32FirmwareLength); + up(&Adapter->fw_download_sema); + Status = -EINVAL; + return Status; + } + + Status = bcm_ioctl_fw_download(Adapter, psFwInfo); + + if (Status != STATUS_SUCCESS) { + if (psFwInfo->u32StartingAddress == CONFIG_BEGIN_ADDR) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "IOCTL: Configuration File Upload Failed\n"); + else + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "IOCTL: Firmware File Upload Failed\n"); + + /* up(&Adapter->fw_download_sema); */ + + if (Adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + Adapter->DriverState = DRIVER_INIT; + Adapter->LEDInfo.bLedInitDone = FALSE; + wake_up(&Adapter->LEDInfo.notify_led_event); + } + } + + if (Status != STATUS_SUCCESS) + up(&Adapter->fw_download_sema); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, OSAL_DBG, DBG_LVL_ALL, "IOCTL: Firmware File Uploaded\n"); + kfree(psFwInfo); + return Status; + } + + case IOCTL_BCM_BUFFER_DOWNLOAD_STOP: { + if (!down_trylock(&Adapter->fw_download_sema)) { + up(&Adapter->fw_download_sema); + return -EINVAL; + } + + if (down_trylock(&Adapter->NVMRdmWrmLock)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "FW download blocked as EEPROM Read/Write is in progress\n"); + up(&Adapter->fw_download_sema); + return -EACCES; + } + + Adapter->bBinDownloaded = TRUE; + Adapter->bCfgDownloaded = TRUE; + atomic_set(&Adapter->CurrNumFreeTxDesc, 0); + Adapter->CurrNumRecvDescs = 0; + Adapter->downloadDDR = 0; + + /* setting the Mips to Run */ + Status = run_card_proc(Adapter); + + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Firm Download Failed\n"); + up(&Adapter->fw_download_sema); + up(&Adapter->NVMRdmWrmLock); + return Status; + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, + DBG_LVL_ALL, "Firm Download Over...\n"); + } + + mdelay(10); + + /* Wait for MailBox Interrupt */ + if (StartInterruptUrb((PS_INTERFACE_ADAPTER)Adapter->pvInterfaceAdapter)) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Unable to send interrupt...\n"); + + timeout = 5*HZ; + Adapter->waiting_to_fw_download_done = FALSE; + wait_event_timeout(Adapter->ioctl_fw_dnld_wait_queue, + Adapter->waiting_to_fw_download_done, timeout); + Adapter->fw_download_process_pid = INVALID_PID; + Adapter->fw_download_done = TRUE; + atomic_set(&Adapter->CurrNumFreeTxDesc, 0); + Adapter->CurrNumRecvDescs = 0; + Adapter->PrevNumRecvDescs = 0; + atomic_set(&Adapter->cntrlpktCnt, 0); + Adapter->LinkUpStatus = 0; + Adapter->LinkStatus = 0; + + if (Adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + Adapter->DriverState = FW_DOWNLOAD_DONE; + wake_up(&Adapter->LEDInfo.notify_led_event); + } + + if (!timeout) + Status = -ENODEV; + + up(&Adapter->fw_download_sema); + up(&Adapter->NVMRdmWrmLock); + return Status; + } + + case IOCTL_BE_BUCKET_SIZE: + Status = 0; + if (get_user(Adapter->BEBucketSize, (unsigned long __user *)arg)) + Status = -EFAULT; + break; + + case IOCTL_RTPS_BUCKET_SIZE: + Status = 0; + if (get_user(Adapter->rtPSBucketSize, (unsigned long __user *)arg)) + Status = -EFAULT; + break; + + case IOCTL_CHIP_RESET: { + INT NVMAccess = down_trylock(&Adapter->NVMRdmWrmLock); + if (NVMAccess) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, " IOCTL_BCM_CHIP_RESET not allowed as EEPROM Read/Write is in progress\n"); + return -EACCES; + } + + down(&Adapter->RxAppControlQueuelock); + Status = reset_card_proc(Adapter); + flushAllAppQ(); + up(&Adapter->RxAppControlQueuelock); + up(&Adapter->NVMRdmWrmLock); + ResetCounters(Adapter); + break; + } + + case IOCTL_QOS_THRESHOLD: { + USHORT uiLoopIndex; + + Status = 0; + for (uiLoopIndex = 0; uiLoopIndex < NO_OF_QUEUES; uiLoopIndex++) { + if (get_user(Adapter->PackInfo[uiLoopIndex].uiThreshold, + (unsigned long __user *)arg)) { + Status = -EFAULT; + break; + } + } + break; + } + + case IOCTL_DUMP_PACKET_INFO: + DumpPackInfo(Adapter); + DumpPhsRules(&Adapter->stBCMPhsContext); + Status = STATUS_SUCCESS; + break; + + case IOCTL_GET_PACK_INFO: + if (copy_to_user(argp, &Adapter->PackInfo, sizeof(PacketInfo)*NO_OF_QUEUES)) + return -EFAULT; + Status = STATUS_SUCCESS; + break; + + case IOCTL_BCM_SWITCH_TRANSFER_MODE: { + UINT uiData = 0; + if (copy_from_user(&uiData, argp, sizeof(UINT))) + return -EFAULT; + + if (uiData) { + /* Allow All Packets */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_SWITCH_TRANSFER_MODE: ETH_PACKET_TUNNELING_MODE\n"); + Adapter->TransferMode = ETH_PACKET_TUNNELING_MODE; + } else { + /* Allow IP only Packets */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_SWITCH_TRANSFER_MODE: IP_PACKET_ONLY_MODE\n"); + Adapter->TransferMode = IP_PACKET_ONLY_MODE; + } + Status = STATUS_SUCCESS; + break; + } + + case IOCTL_BCM_GET_DRIVER_VERSION: { + ulong len; + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + len = min_t(ulong, IoBuffer.OutputLength, strlen(VER_FILEVERSION_STR) + 1); + + if (copy_to_user(IoBuffer.OutputBuffer, VER_FILEVERSION_STR, len)) + return -EFAULT; + Status = STATUS_SUCCESS; + break; + } + + case IOCTL_BCM_GET_CURRENT_STATUS: { + LINK_STATE link_state; + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "copy_from_user failed..\n"); + return -EFAULT; + } + + if (IoBuffer.OutputLength != sizeof(link_state)) { + Status = -EINVAL; + break; + } + + memset(&link_state, 0, sizeof(link_state)); + link_state.bIdleMode = Adapter->IdleMode; + link_state.bShutdownMode = Adapter->bShutStatus; + link_state.ucLinkStatus = Adapter->LinkStatus; + + if (copy_to_user(IoBuffer.OutputBuffer, &link_state, min_t(size_t, sizeof(link_state), IoBuffer.OutputLength))) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy_to_user Failed..\n"); + return -EFAULT; + } + Status = STATUS_SUCCESS; + break; + } + + case IOCTL_BCM_SET_MAC_TRACING: { + UINT tracing_flag; + + /* copy ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (copy_from_user(&tracing_flag, IoBuffer.InputBuffer, sizeof(UINT))) + return -EFAULT; + + if (tracing_flag) + Adapter->pTarangs->MacTracingEnabled = TRUE; + else + Adapter->pTarangs->MacTracingEnabled = FALSE; + break; + } + + case IOCTL_BCM_GET_DSX_INDICATION: { + ULONG ulSFId = 0; + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.OutputLength < sizeof(stLocalSFAddIndicationAlt)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "Mismatch req: %lx needed is =0x%zx!!!", + IoBuffer.OutputLength, sizeof(stLocalSFAddIndicationAlt)); + return -EINVAL; + } + + if (copy_from_user(&ulSFId, IoBuffer.InputBuffer, sizeof(ulSFId))) + return -EFAULT; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Get DSX Data SF ID is =%lx\n", ulSFId); + get_dsx_sf_data_to_application(Adapter, ulSFId, IoBuffer.OutputBuffer); + Status = STATUS_SUCCESS; + } + break; + + case IOCTL_BCM_GET_HOST_MIBS: { + PVOID temp_buff; + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.OutputLength != sizeof(S_MIBS_HOST_STATS_MIBS)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, + "Length Check failed %lu %zd\n", + IoBuffer.OutputLength, sizeof(S_MIBS_HOST_STATS_MIBS)); + return -EINVAL; + } + + /* FIXME: HOST_STATS are too big for kmalloc (122048)! */ + temp_buff = kzalloc(sizeof(S_MIBS_HOST_STATS_MIBS), GFP_KERNEL); + if (!temp_buff) + return STATUS_FAILURE; + + Status = ProcessGetHostMibs(Adapter, temp_buff); + GetDroppedAppCntrlPktMibs(temp_buff, pTarang); + + if (Status != STATUS_FAILURE) + if (copy_to_user(IoBuffer.OutputBuffer, temp_buff, sizeof(S_MIBS_HOST_STATS_MIBS))) { + kfree(temp_buff); + return -EFAULT; + } + + kfree(temp_buff); + break; + } + + case IOCTL_BCM_WAKE_UP_DEVICE_FROM_IDLE: + if ((FALSE == Adapter->bTriedToWakeUpFromlowPowerMode) && (TRUE == Adapter->IdleMode)) { + Adapter->usIdleModePattern = ABORT_IDLE_MODE; + Adapter->bWakeUpDevice = TRUE; + wake_up(&Adapter->process_rx_cntrlpkt); + } + + Status = STATUS_SUCCESS; + break; + + case IOCTL_BCM_BULK_WRM: { + PBULKWRM_BUFFER pBulkBuffer; + UINT uiTempVar = 0; + PCHAR pvBuffer = NULL; + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_PRINTK, 0, 0, "Device in Idle/Shutdown Mode, Blocking Wrms\n"); + Status = -EACCES; + break; + } + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.InputLength < sizeof(ULONG) * 2) + return -EINVAL; + + pvBuffer = memdup_user(IoBuffer.InputBuffer, + IoBuffer.InputLength); + if (IS_ERR(pvBuffer)) + return PTR_ERR(pvBuffer); + + pBulkBuffer = (PBULKWRM_BUFFER)pvBuffer; + + if (((ULONG)pBulkBuffer->Register & 0x0F000000) != 0x0F000000 || + ((ULONG)pBulkBuffer->Register & 0x3)) { + kfree(pvBuffer); + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_PRINTK, 0, 0, "WRM Done On invalid Address : %x Access Denied.\n", (int)pBulkBuffer->Register); + Status = -EINVAL; + break; + } + + uiTempVar = pBulkBuffer->Register & EEPROM_REJECT_MASK; + if (!((Adapter->pstargetparams->m_u32Customize)&VSG_MODE) && + ((uiTempVar == EEPROM_REJECT_REG_1) || + (uiTempVar == EEPROM_REJECT_REG_2) || + (uiTempVar == EEPROM_REJECT_REG_3) || + (uiTempVar == EEPROM_REJECT_REG_4)) && + (cmd == IOCTL_BCM_REGISTER_WRITE)) { + + kfree(pvBuffer); + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_PRINTK, 0, 0, "EEPROM Access Denied, not in VSG Mode\n"); + Status = -EFAULT; + break; + } + + if (pBulkBuffer->SwapEndian == FALSE) + Status = wrmWithLock(Adapter, (UINT)pBulkBuffer->Register, (PCHAR)pBulkBuffer->Values, IoBuffer.InputLength - 2*sizeof(ULONG)); + else + Status = wrmaltWithLock(Adapter, (UINT)pBulkBuffer->Register, (PUINT)pBulkBuffer->Values, IoBuffer.InputLength - 2*sizeof(ULONG)); + + if (Status != STATUS_SUCCESS) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "WRM Failed\n"); + + kfree(pvBuffer); + break; + } + + case IOCTL_BCM_GET_NVM_SIZE: + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (Adapter->eNVMType == NVM_EEPROM || Adapter->eNVMType == NVM_FLASH) { + if (copy_to_user(IoBuffer.OutputBuffer, &Adapter->uiNVMDSDSize, sizeof(UINT))) + return -EFAULT; + } + + Status = STATUS_SUCCESS; + break; + + case IOCTL_BCM_CAL_INIT: { + UINT uiSectorSize = 0 ; + if (Adapter->eNVMType == NVM_FLASH) { + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (copy_from_user(&uiSectorSize, IoBuffer.InputBuffer, sizeof(UINT))) + return -EFAULT; + + if ((uiSectorSize < MIN_SECTOR_SIZE) || (uiSectorSize > MAX_SECTOR_SIZE)) { + if (copy_to_user(IoBuffer.OutputBuffer, &Adapter->uiSectorSize, + sizeof(UINT))) + return -EFAULT; + } else { + if (IsFlash2x(Adapter)) { + if (copy_to_user(IoBuffer.OutputBuffer, &Adapter->uiSectorSize, sizeof(UINT))) + return -EFAULT; + } else { + if ((TRUE == Adapter->bShutStatus) || (TRUE == Adapter->IdleMode)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Device is in Idle/Shutdown Mode\n"); + return -EACCES; + } + + Adapter->uiSectorSize = uiSectorSize; + BcmUpdateSectorSize(Adapter, Adapter->uiSectorSize); + } + } + Status = STATUS_SUCCESS; + } else { + Status = STATUS_FAILURE; + } + } + break; + + case IOCTL_BCM_SET_DEBUG: +#ifdef DEBUG + { + USER_BCM_DBG_STATE sUserDebugState; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "In SET_DEBUG ioctl\n"); + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (copy_from_user(&sUserDebugState, IoBuffer.InputBuffer, sizeof(USER_BCM_DBG_STATE))) + return -EFAULT; + + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_PRINTK, 0, 0, "IOCTL_BCM_SET_DEBUG: OnOff=%d Type = 0x%x ", + sUserDebugState.OnOff, sUserDebugState.Type); + /* sUserDebugState.Subtype <<= 1; */ + sUserDebugState.Subtype = 1 << sUserDebugState.Subtype; + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_PRINTK, 0, 0, "actual Subtype=0x%x\n", sUserDebugState.Subtype); + + /* Update new 'DebugState' in the Adapter */ + Adapter->stDebugState.type |= sUserDebugState.Type; + /* Subtype: A bitmap of 32 bits for Subtype per Type. + * Valid indexes in 'subtype' array: 1,2,4,8 + * corresponding to valid Type values. Hence we can use the 'Type' field + * as the index value, ignoring the array entries 0,3,5,6,7 ! + */ + if (sUserDebugState.OnOff) + Adapter->stDebugState.subtype[sUserDebugState.Type] |= sUserDebugState.Subtype; + else + Adapter->stDebugState.subtype[sUserDebugState.Type] &= ~sUserDebugState.Subtype; + + BCM_SHOW_DEBUG_BITMAP(Adapter); + } +#endif + break; + + case IOCTL_BCM_NVM_READ: + case IOCTL_BCM_NVM_WRITE: { + NVM_READWRITE stNVMReadWrite; + PUCHAR pReadData = NULL; + ULONG ulDSDMagicNumInUsrBuff = 0; + struct timeval tv0, tv1; + memset(&tv0, 0, sizeof(struct timeval)); + memset(&tv1, 0, sizeof(struct timeval)); + if ((Adapter->eNVMType == NVM_FLASH) && (Adapter->uiFlashLayoutMajorVersion == 0)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "The Flash Control Section is Corrupted. Hence Rejection on NVM Read/Write\n"); + return -EFAULT; + } + + if (IsFlash2x(Adapter)) { + if ((Adapter->eActiveDSD != DSD0) && + (Adapter->eActiveDSD != DSD1) && + (Adapter->eActiveDSD != DSD2)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "No DSD is active..hence NVM Command is blocked"); + return STATUS_FAILURE; + } + } + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (copy_from_user(&stNVMReadWrite, + (IOCTL_BCM_NVM_READ == cmd) ? IoBuffer.OutputBuffer : IoBuffer.InputBuffer, + sizeof(NVM_READWRITE))) + return -EFAULT; + + /* + * Deny the access if the offset crosses the cal area limit. + */ + if (stNVMReadWrite.uiNumBytes > Adapter->uiNVMDSDSize) + return STATUS_FAILURE; + + if (stNVMReadWrite.uiOffset > Adapter->uiNVMDSDSize - stNVMReadWrite.uiNumBytes) { + /* BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Can't allow access beyond NVM Size: 0x%x 0x%x\n", stNVMReadWrite.uiOffset, stNVMReadWrite.uiNumBytes); */ + return STATUS_FAILURE; + } + + pReadData = memdup_user(stNVMReadWrite.pBuffer, + stNVMReadWrite.uiNumBytes); + if (IS_ERR(pReadData)) + return PTR_ERR(pReadData); + + do_gettimeofday(&tv0); + if (IOCTL_BCM_NVM_READ == cmd) { + down(&Adapter->NVMRdmWrmLock); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Device is in Idle/Shutdown Mode\n"); + up(&Adapter->NVMRdmWrmLock); + kfree(pReadData); + return -EACCES; + } + + Status = BeceemNVMRead(Adapter, (PUINT)pReadData, stNVMReadWrite.uiOffset, stNVMReadWrite.uiNumBytes); + up(&Adapter->NVMRdmWrmLock); + + if (Status != STATUS_SUCCESS) { + kfree(pReadData); + return Status; + } + + if (copy_to_user(stNVMReadWrite.pBuffer, pReadData, stNVMReadWrite.uiNumBytes)) { + kfree(pReadData); + return -EFAULT; + } + } else { + down(&Adapter->NVMRdmWrmLock); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Device is in Idle/Shutdown Mode\n"); + up(&Adapter->NVMRdmWrmLock); + kfree(pReadData); + return -EACCES; + } + + Adapter->bHeaderChangeAllowed = TRUE; + if (IsFlash2x(Adapter)) { + /* + * New Requirement:- + * DSD section updation will be allowed in two case:- + * 1. if DSD sig is present in DSD header means dongle is ok and updation is fruitfull + * 2. if point 1 failes then user buff should have DSD sig. this point ensures that if dongle is + * corrupted then user space program first modify the DSD header with valid DSD sig so + * that this as well as further write may be worthwhile. + * + * This restriction has been put assuming that if DSD sig is corrupted, DSD + * data won't be considered valid. + */ + + Status = BcmFlash2xCorruptSig(Adapter, Adapter->eActiveDSD); + if (Status != STATUS_SUCCESS) { + if (((stNVMReadWrite.uiOffset + stNVMReadWrite.uiNumBytes) != Adapter->uiNVMDSDSize) || + (stNVMReadWrite.uiNumBytes < SIGNATURE_SIZE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "DSD Sig is present neither in Flash nor User provided Input.."); + up(&Adapter->NVMRdmWrmLock); + kfree(pReadData); + return Status; + } + + ulDSDMagicNumInUsrBuff = ntohl(*(PUINT)(pReadData + stNVMReadWrite.uiNumBytes - SIGNATURE_SIZE)); + if (ulDSDMagicNumInUsrBuff != DSD_IMAGE_MAGIC_NUMBER) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "DSD Sig is present neither in Flash nor User provided Input.."); + up(&Adapter->NVMRdmWrmLock); + kfree(pReadData); + return Status; + } + } + } + + Status = BeceemNVMWrite(Adapter, (PUINT)pReadData, stNVMReadWrite.uiOffset, stNVMReadWrite.uiNumBytes, stNVMReadWrite.bVerify); + if (IsFlash2x(Adapter)) + BcmFlash2xWriteSig(Adapter, Adapter->eActiveDSD); + + Adapter->bHeaderChangeAllowed = FALSE; + + up(&Adapter->NVMRdmWrmLock); + + if (Status != STATUS_SUCCESS) { + kfree(pReadData); + return Status; + } + } + + do_gettimeofday(&tv1); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, " timetaken by Write/read :%ld msec\n", (tv1.tv_sec - tv0.tv_sec)*1000 + (tv1.tv_usec - tv0.tv_usec)/1000); + + kfree(pReadData); + return STATUS_SUCCESS; + } + + case IOCTL_BCM_FLASH2X_SECTION_READ: { + FLASH2X_READWRITE sFlash2xRead = {0}; + PUCHAR pReadBuff = NULL ; + UINT NOB = 0; + UINT BuffSize = 0; + UINT ReadBytes = 0; + UINT ReadOffset = 0; + void __user *OutPutBuff; + + if (IsFlash2x(Adapter) != TRUE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Flash Does not have 2.x map"); + return -EINVAL; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_FLASH2X_SECTION_READ Called"); + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + /* Reading FLASH 2.x READ structure */ + if (copy_from_user(&sFlash2xRead, IoBuffer.InputBuffer, sizeof(FLASH2X_READWRITE))) + return -EFAULT; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "\nsFlash2xRead.Section :%x", sFlash2xRead.Section); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "\nsFlash2xRead.offset :%x", sFlash2xRead.offset); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "\nsFlash2xRead.numOfBytes :%x", sFlash2xRead.numOfBytes); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "\nsFlash2xRead.bVerify :%x\n", sFlash2xRead.bVerify); + + /* This was internal to driver for raw read. now it has ben exposed to user space app. */ + if (validateFlash2xReadWrite(Adapter, &sFlash2xRead) == FALSE) + return STATUS_FAILURE; + + NOB = sFlash2xRead.numOfBytes; + if (NOB > Adapter->uiSectorSize) + BuffSize = Adapter->uiSectorSize; + else + BuffSize = NOB; + + ReadOffset = sFlash2xRead.offset ; + OutPutBuff = IoBuffer.OutputBuffer; + pReadBuff = (PCHAR)kzalloc(BuffSize , GFP_KERNEL); + + if (pReadBuff == NULL) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Memory allocation failed for Flash 2.x Read Structure"); + return -ENOMEM; + } + down(&Adapter->NVMRdmWrmLock); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Device is in Idle/Shutdown Mode\n"); + up(&Adapter->NVMRdmWrmLock); + kfree(pReadBuff); + return -EACCES; + } + + while (NOB) { + if (NOB > Adapter->uiSectorSize) + ReadBytes = Adapter->uiSectorSize; + else + ReadBytes = NOB; + + /* Reading the data from Flash 2.x */ + Status = BcmFlash2xBulkRead(Adapter, (PUINT)pReadBuff, sFlash2xRead.Section, ReadOffset, ReadBytes); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Flash 2x read err with Status :%d", Status); + break; + } + + BCM_DEBUG_PRINT_BUFFER(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, pReadBuff, ReadBytes); + + Status = copy_to_user(OutPutBuff, pReadBuff, ReadBytes); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Copy to use failed with status :%d", Status); + up(&Adapter->NVMRdmWrmLock); + kfree(pReadBuff); + return -EFAULT; + } + NOB = NOB - ReadBytes; + if (NOB) { + ReadOffset = ReadOffset + ReadBytes; + OutPutBuff = OutPutBuff + ReadBytes ; + } + } + + up(&Adapter->NVMRdmWrmLock); + kfree(pReadBuff); + } + break; + + case IOCTL_BCM_FLASH2X_SECTION_WRITE: { + FLASH2X_READWRITE sFlash2xWrite = {0}; + PUCHAR pWriteBuff; + void __user *InputAddr; + UINT NOB = 0; + UINT BuffSize = 0; + UINT WriteOffset = 0; + UINT WriteBytes = 0; + + if (IsFlash2x(Adapter) != TRUE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Flash Does not have 2.x map"); + return -EINVAL; + } + + /* First make this False so that we can enable the Sector Permission Check in BeceemFlashBulkWrite */ + Adapter->bAllDSDWriteAllow = FALSE; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_FLASH2X_SECTION_WRITE Called"); + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + /* Reading FLASH 2.x READ structure */ + if (copy_from_user(&sFlash2xWrite, IoBuffer.InputBuffer, sizeof(FLASH2X_READWRITE))) + return -EFAULT; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "\nsFlash2xRead.Section :%x", sFlash2xWrite.Section); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "\nsFlash2xRead.offset :%d", sFlash2xWrite.offset); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "\nsFlash2xRead.numOfBytes :%x", sFlash2xWrite.numOfBytes); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "\nsFlash2xRead.bVerify :%x\n", sFlash2xWrite.bVerify); + + if ((sFlash2xWrite.Section != VSA0) && (sFlash2xWrite.Section != VSA1) && (sFlash2xWrite.Section != VSA2)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Only VSA write is allowed"); + return -EINVAL; + } + + if (validateFlash2xReadWrite(Adapter, &sFlash2xWrite) == FALSE) + return STATUS_FAILURE; + + InputAddr = sFlash2xWrite.pDataBuff; + WriteOffset = sFlash2xWrite.offset; + NOB = sFlash2xWrite.numOfBytes; + + if (NOB > Adapter->uiSectorSize) + BuffSize = Adapter->uiSectorSize; + else + BuffSize = NOB ; + + pWriteBuff = kmalloc(BuffSize, GFP_KERNEL); + + if (pWriteBuff == NULL) + return -ENOMEM; + + /* extracting the remainder of the given offset. */ + WriteBytes = Adapter->uiSectorSize; + if (WriteOffset % Adapter->uiSectorSize) + WriteBytes = Adapter->uiSectorSize - (WriteOffset % Adapter->uiSectorSize); + + if (NOB < WriteBytes) + WriteBytes = NOB; + + down(&Adapter->NVMRdmWrmLock); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Device is in Idle/Shutdown Mode\n"); + up(&Adapter->NVMRdmWrmLock); + kfree(pWriteBuff); + return -EACCES; + } + + BcmFlash2xCorruptSig(Adapter, sFlash2xWrite.Section); + do { + Status = copy_from_user(pWriteBuff, InputAddr, WriteBytes); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy to user failed with status :%d", Status); + up(&Adapter->NVMRdmWrmLock); + kfree(pWriteBuff); + return -EFAULT; + } + BCM_DEBUG_PRINT_BUFFER(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, pWriteBuff, WriteBytes); + + /* Writing the data from Flash 2.x */ + Status = BcmFlash2xBulkWrite(Adapter, (PUINT)pWriteBuff, sFlash2xWrite.Section, WriteOffset, WriteBytes, sFlash2xWrite.bVerify); + + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Flash 2x read err with Status :%d", Status); + break; + } + + NOB = NOB - WriteBytes; + if (NOB) { + WriteOffset = WriteOffset + WriteBytes; + InputAddr = InputAddr + WriteBytes; + if (NOB > Adapter->uiSectorSize) + WriteBytes = Adapter->uiSectorSize; + else + WriteBytes = NOB; + } + } while (NOB > 0); + + BcmFlash2xWriteSig(Adapter, sFlash2xWrite.Section); + up(&Adapter->NVMRdmWrmLock); + kfree(pWriteBuff); + } + break; + + case IOCTL_BCM_GET_FLASH2X_SECTION_BITMAP: { + PFLASH2X_BITMAP psFlash2xBitMap; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_GET_FLASH2X_SECTION_BITMAP Called"); + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.OutputLength != sizeof(FLASH2X_BITMAP)) + return -EINVAL; + + psFlash2xBitMap = kzalloc(sizeof(FLASH2X_BITMAP), GFP_KERNEL); + if (psFlash2xBitMap == NULL) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Memory is not available"); + return -ENOMEM; + } + + /* Reading the Flash Sectio Bit map */ + down(&Adapter->NVMRdmWrmLock); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Device is in Idle/Shutdown Mode\n"); + up(&Adapter->NVMRdmWrmLock); + kfree(psFlash2xBitMap); + return -EACCES; + } + + BcmGetFlash2xSectionalBitMap(Adapter, psFlash2xBitMap); + up(&Adapter->NVMRdmWrmLock); + if (copy_to_user(IoBuffer.OutputBuffer, psFlash2xBitMap, sizeof(FLASH2X_BITMAP))) { + kfree(psFlash2xBitMap); + return -EFAULT; + } + + kfree(psFlash2xBitMap); + } + break; + + case IOCTL_BCM_SET_ACTIVE_SECTION: { + FLASH2X_SECTION_VAL eFlash2xSectionVal = 0; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_SET_ACTIVE_SECTION Called"); + + if (IsFlash2x(Adapter) != TRUE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Flash Does not have 2.x map"); + return -EINVAL; + } + + Status = copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER)); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy of IOCTL BUFFER failed"); + return -EFAULT; + } + + Status = copy_from_user(&eFlash2xSectionVal, IoBuffer.InputBuffer, sizeof(INT)); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy of flash section val failed"); + return -EFAULT; + } + + down(&Adapter->NVMRdmWrmLock); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Device is in Idle/Shutdown Mode\n"); + up(&Adapter->NVMRdmWrmLock); + return -EACCES; + } + + Status = BcmSetActiveSection(Adapter, eFlash2xSectionVal); + if (Status) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Failed to make it's priority Highest. Status %d", Status); + + up(&Adapter->NVMRdmWrmLock); + } + break; + + case IOCTL_BCM_IDENTIFY_ACTIVE_SECTION: { + /* Right Now we are taking care of only DSD */ + Adapter->bAllDSDWriteAllow = FALSE; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_IDENTIFY_ACTIVE_SECTION called"); + Status = STATUS_SUCCESS; + } + break; + + case IOCTL_BCM_COPY_SECTION: { + FLASH2X_COPY_SECTION sCopySectStrut = {0}; + Status = STATUS_SUCCESS; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_COPY_SECTION Called"); + + Adapter->bAllDSDWriteAllow = FALSE; + if (IsFlash2x(Adapter) != TRUE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Flash Does not have 2.x map"); + return -EINVAL; + } + + Status = copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER)); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy of IOCTL BUFFER failed Status :%d", Status); + return -EFAULT; + } + + Status = copy_from_user(&sCopySectStrut, IoBuffer.InputBuffer, sizeof(FLASH2X_COPY_SECTION)); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy of Copy_Section_Struct failed with Status :%d", Status); + return -EFAULT; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Source SEction :%x", sCopySectStrut.SrcSection); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Destination SEction :%x", sCopySectStrut.DstSection); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "offset :%x", sCopySectStrut.offset); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "NOB :%x", sCopySectStrut.numOfBytes); + + if (IsSectionExistInFlash(Adapter, sCopySectStrut.SrcSection) == FALSE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Source Section<%x> does not exixt in Flash ", sCopySectStrut.SrcSection); + return -EINVAL; + } + + if (IsSectionExistInFlash(Adapter, sCopySectStrut.DstSection) == FALSE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Destinatio Section<%x> does not exixt in Flash ", sCopySectStrut.DstSection); + return -EINVAL; + } + + if (sCopySectStrut.SrcSection == sCopySectStrut.DstSection) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Source and Destination section should be different"); + return -EINVAL; + } + + down(&Adapter->NVMRdmWrmLock); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Device is in Idle/Shutdown Mode\n"); + up(&Adapter->NVMRdmWrmLock); + return -EACCES; + } + + if (sCopySectStrut.SrcSection == ISO_IMAGE1 || sCopySectStrut.SrcSection == ISO_IMAGE2) { + if (IsNonCDLessDevice(Adapter)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Device is Non-CDLess hence won't have ISO !!"); + Status = -EINVAL; + } else if (sCopySectStrut.numOfBytes == 0) { + Status = BcmCopyISO(Adapter, sCopySectStrut); + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Partial Copy of ISO section is not Allowed.."); + Status = STATUS_FAILURE; + } + up(&Adapter->NVMRdmWrmLock); + return Status; + } + + Status = BcmCopySection(Adapter, sCopySectStrut.SrcSection, + sCopySectStrut.DstSection, sCopySectStrut.offset, sCopySectStrut.numOfBytes); + up(&Adapter->NVMRdmWrmLock); + } + break; + + case IOCTL_BCM_GET_FLASH_CS_INFO: { + Status = STATUS_SUCCESS; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, " IOCTL_BCM_GET_FLASH_CS_INFO Called"); + + Status = copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER)); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy of IOCTL BUFFER failed"); + return -EFAULT; + } + + if (Adapter->eNVMType != NVM_FLASH) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Connected device does not have flash"); + Status = -EINVAL; + break; + } + + if (IsFlash2x(Adapter) == TRUE) { + if (IoBuffer.OutputLength < sizeof(FLASH2X_CS_INFO)) + return -EINVAL; + + if (copy_to_user(IoBuffer.OutputBuffer, Adapter->psFlash2xCSInfo, sizeof(FLASH2X_CS_INFO))) + return -EFAULT; + } else { + if (IoBuffer.OutputLength < sizeof(FLASH_CS_INFO)) + return -EINVAL; + + if (copy_to_user(IoBuffer.OutputBuffer, Adapter->psFlashCSInfo, sizeof(FLASH_CS_INFO))) + return -EFAULT; + } + } + break; + + case IOCTL_BCM_SELECT_DSD: { + UINT SectOfset = 0; + FLASH2X_SECTION_VAL eFlash2xSectionVal; + eFlash2xSectionVal = NO_SECTION_VAL; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_SELECT_DSD Called"); + + if (IsFlash2x(Adapter) != TRUE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Flash Does not have 2.x map"); + return -EINVAL; + } + + Status = copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER)); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy of IOCTL BUFFER failed"); + return -EFAULT; + } + Status = copy_from_user(&eFlash2xSectionVal, IoBuffer.InputBuffer, sizeof(INT)); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy of flash section val failed"); + return -EFAULT; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Read Section :%d", eFlash2xSectionVal); + if ((eFlash2xSectionVal != DSD0) && + (eFlash2xSectionVal != DSD1) && + (eFlash2xSectionVal != DSD2)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Passed section<%x> is not DSD section", eFlash2xSectionVal); + return STATUS_FAILURE; + } + + SectOfset = BcmGetSectionValStartOffset(Adapter, eFlash2xSectionVal); + if (SectOfset == INVALID_OFFSET) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Provided Section val <%d> does not exixt in Flash 2.x", eFlash2xSectionVal); + return -EINVAL; + } + + Adapter->bAllDSDWriteAllow = TRUE; + Adapter->ulFlashCalStart = SectOfset; + Adapter->eActiveDSD = eFlash2xSectionVal; + } + Status = STATUS_SUCCESS; + break; + + case IOCTL_BCM_NVM_RAW_READ: { + NVM_READWRITE stNVMRead; + INT NOB ; + INT BuffSize ; + INT ReadOffset = 0; + UINT ReadBytes = 0 ; + PUCHAR pReadBuff; + void __user *OutPutBuff; + + if (Adapter->eNVMType != NVM_FLASH) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "NVM TYPE is not Flash"); + return -EINVAL; + } + + /* Copy Ioctl Buffer structure */ + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "copy_from_user 1 failed\n"); + return -EFAULT; + } + + if (copy_from_user(&stNVMRead, IoBuffer.OutputBuffer, sizeof(NVM_READWRITE))) + return -EFAULT; + + NOB = stNVMRead.uiNumBytes; + /* In Raw-Read max Buff size : 64MB */ + + if (NOB > DEFAULT_BUFF_SIZE) + BuffSize = DEFAULT_BUFF_SIZE; + else + BuffSize = NOB; + + ReadOffset = stNVMRead.uiOffset; + OutPutBuff = stNVMRead.pBuffer; + + pReadBuff = kzalloc(BuffSize , GFP_KERNEL); + if (pReadBuff == NULL) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Memory allocation failed for Flash 2.x Read Structure"); + Status = -ENOMEM; + break; + } + down(&Adapter->NVMRdmWrmLock); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Device is in Idle/Shutdown Mode\n"); + kfree(pReadBuff); + up(&Adapter->NVMRdmWrmLock); + return -EACCES; + } + + Adapter->bFlashRawRead = TRUE; + + while (NOB) { + if (NOB > DEFAULT_BUFF_SIZE) + ReadBytes = DEFAULT_BUFF_SIZE; + else + ReadBytes = NOB; + + /* Reading the data from Flash 2.x */ + Status = BeceemNVMRead(Adapter, (PUINT)pReadBuff, ReadOffset, ReadBytes); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Flash 2x read err with Status :%d", Status); + break; + } + + BCM_DEBUG_PRINT_BUFFER(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, pReadBuff, ReadBytes); + + Status = copy_to_user(OutPutBuff, pReadBuff, ReadBytes); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Copy to use failed with status :%d", Status); + up(&Adapter->NVMRdmWrmLock); + kfree(pReadBuff); + return -EFAULT; + } + NOB = NOB - ReadBytes; + if (NOB) { + ReadOffset = ReadOffset + ReadBytes; + OutPutBuff = OutPutBuff + ReadBytes; + } + } + Adapter->bFlashRawRead = FALSE; + up(&Adapter->NVMRdmWrmLock); + kfree(pReadBuff); + break; + } + + case IOCTL_BCM_CNTRLMSG_MASK: { + ULONG RxCntrlMsgBitMask = 0; + + /* Copy Ioctl Buffer structure */ + Status = copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER)); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "copy of Ioctl buffer is failed from user space"); + return -EFAULT; + } + + if (IoBuffer.InputLength != sizeof(unsigned long)) { + Status = -EINVAL; + break; + } + + Status = copy_from_user(&RxCntrlMsgBitMask, IoBuffer.InputBuffer, IoBuffer.InputLength); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "copy of control bit mask failed from user space"); + return -EFAULT; + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "\n Got user defined cntrl msg bit mask :%lx", RxCntrlMsgBitMask); + pTarang->RxCntrlMsgBitMask = RxCntrlMsgBitMask; + } + break; + + case IOCTL_BCM_GET_DEVICE_DRIVER_INFO: { + DEVICE_DRIVER_INFO DevInfo; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "Called IOCTL_BCM_GET_DEVICE_DRIVER_INFO\n"); + + DevInfo.MaxRDMBufferSize = BUFFER_4K; + DevInfo.u32DSDStartOffset = EEPROM_CALPARAM_START; + DevInfo.u32RxAlignmentCorrection = 0; + DevInfo.u32NVMType = Adapter->eNVMType; + DevInfo.u32InterfaceType = BCM_USB; + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.OutputLength < sizeof(DevInfo)) + return -EINVAL; + + if (copy_to_user(IoBuffer.OutputBuffer, &DevInfo, sizeof(DevInfo))) + return -EFAULT; + } + break; + + case IOCTL_BCM_TIME_SINCE_NET_ENTRY: { + ST_TIME_ELAPSED stTimeElapsedSinceNetEntry = {0}; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_BCM_TIME_SINCE_NET_ENTRY called"); + + if (copy_from_user(&IoBuffer, argp, sizeof(IOCTL_BUFFER))) + return -EFAULT; + + if (IoBuffer.OutputLength < sizeof(ST_TIME_ELAPSED)) + return -EINVAL; + + stTimeElapsedSinceNetEntry.ul64TimeElapsedSinceNetEntry = get_seconds() - Adapter->liTimeSinceLastNetEntry; + + if (copy_to_user(IoBuffer.OutputBuffer, &stTimeElapsedSinceNetEntry, sizeof(ST_TIME_ELAPSED))) + return -EFAULT; + } + break; + + case IOCTL_CLOSE_NOTIFICATION: + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, OSAL_DBG, DBG_LVL_ALL, "IOCTL_CLOSE_NOTIFICATION"); + break; + + default: + pr_info(DRV_NAME ": unknown ioctl cmd=%#x\n", cmd); + Status = STATUS_FAILURE; + break; + } + return Status; +} + + +static const struct file_operations bcm_fops = { + .owner = THIS_MODULE, + .open = bcm_char_open, + .release = bcm_char_release, + .read = bcm_char_read, + .unlocked_ioctl = bcm_char_ioctl, + .llseek = no_llseek, +}; + +int register_control_device_interface(PMINI_ADAPTER Adapter) +{ + + if (Adapter->major > 0) + return Adapter->major; + + Adapter->major = register_chrdev(0, DEV_NAME, &bcm_fops); + if (Adapter->major < 0) { + pr_err(DRV_NAME ": could not created character device\n"); + return Adapter->major; + } + + Adapter->pstCreatedClassDevice = device_create(bcm_class, NULL, + MKDEV(Adapter->major, 0), + Adapter, DEV_NAME); + + if (IS_ERR(Adapter->pstCreatedClassDevice)) { + pr_err(DRV_NAME ": class device create failed\n"); + unregister_chrdev(Adapter->major, DEV_NAME); + return PTR_ERR(Adapter->pstCreatedClassDevice); + } + + return 0; +} + +void unregister_control_device_interface(PMINI_ADAPTER Adapter) +{ + if (Adapter->major > 0) { + device_destroy(bcm_class, MKDEV(Adapter->major, 0)); + unregister_chrdev(Adapter->major, DEV_NAME); + } +} + diff --git a/drivers/staging/bcm/Bcmnet.c b/drivers/staging/bcm/Bcmnet.c new file mode 100644 index 00000000..133e146a --- /dev/null +++ b/drivers/staging/bcm/Bcmnet.c @@ -0,0 +1,239 @@ +#include "headers.h" + +struct net_device *gblpnetdev; + +static INT bcm_open(struct net_device *dev) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(dev); + + if (Adapter->fw_download_done == FALSE) { + pr_notice(PFX "%s: link up failed (download in progress)\n", + dev->name); + return -EBUSY; + } + + if (netif_msg_ifup(Adapter)) + pr_info(PFX "%s: enabling interface\n", dev->name); + + if (Adapter->LinkUpStatus) { + if (netif_msg_link(Adapter)) + pr_info(PFX "%s: link up\n", dev->name); + + netif_carrier_on(Adapter->dev); + netif_start_queue(Adapter->dev); + } + + return 0; +} + +static INT bcm_close(struct net_device *dev) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(dev); + + if (netif_msg_ifdown(Adapter)) + pr_info(PFX "%s: disabling interface\n", dev->name); + + netif_carrier_off(dev); + netif_stop_queue(dev); + + return 0; +} + +static u16 bcm_select_queue(struct net_device *dev, struct sk_buff *skb) +{ + return ClassifyPacket(netdev_priv(dev), skb); +} + +/******************************************************************* +* Function - bcm_transmit() +* +* Description - This is the main transmit function for our virtual +* interface(eth0). It handles the ARP packets. It +* clones this packet and then Queue it to a suitable +* Queue. Then calls the transmit_packet(). +* +* Parameter - skb - Pointer to the socket buffer structure +* dev - Pointer to the virtual net device structure +* +*********************************************************************/ + +static netdev_tx_t bcm_transmit(struct sk_buff *skb, struct net_device *dev) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(dev); + u16 qindex = skb_get_queue_mapping(skb); + + + if (Adapter->device_removed || !Adapter->LinkUpStatus) + goto drop; + + if (Adapter->TransferMode != IP_PACKET_ONLY_MODE) + goto drop; + + if (INVALID_QUEUE_INDEX == qindex) + goto drop; + + if (Adapter->PackInfo[qindex].uiCurrentPacketsOnHost >= + SF_MAX_ALLOWED_PACKETS_TO_BACKUP) + return NETDEV_TX_BUSY; + + /* Now Enqueue the packet */ + if (netif_msg_tx_queued(Adapter)) + pr_info(PFX "%s: enqueueing packet to queue %d\n", + dev->name, qindex); + + spin_lock(&Adapter->PackInfo[qindex].SFQueueLock); + Adapter->PackInfo[qindex].uiCurrentBytesOnHost += skb->len; + Adapter->PackInfo[qindex].uiCurrentPacketsOnHost++; + + *((B_UINT32 *) skb->cb + SKB_CB_LATENCY_OFFSET) = jiffies; + ENQUEUEPACKET(Adapter->PackInfo[qindex].FirstTxQueue, + Adapter->PackInfo[qindex].LastTxQueue, skb); + atomic_inc(&Adapter->TotalPacketCount); + spin_unlock(&Adapter->PackInfo[qindex].SFQueueLock); + + /* FIXME - this is racy and incorrect, replace with work queue */ + if (!atomic_read(&Adapter->TxPktAvail)) { + atomic_set(&Adapter->TxPktAvail, 1); + wake_up(&Adapter->tx_packet_wait_queue); + } + return NETDEV_TX_OK; + + drop: + dev_kfree_skb(skb); + return NETDEV_TX_OK; +} + + + +/** +@ingroup init_functions +Register other driver entry points with the kernel +*/ +static const struct net_device_ops bcmNetDevOps = { + .ndo_open = bcm_open, + .ndo_stop = bcm_close, + .ndo_start_xmit = bcm_transmit, + .ndo_change_mtu = eth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_select_queue = bcm_select_queue, +}; + +static struct device_type wimax_type = { + .name = "wimax", +}; + +static int bcm_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + cmd->supported = 0; + cmd->advertising = 0; + cmd->speed = SPEED_10000; + cmd->duplex = DUPLEX_FULL; + cmd->port = PORT_TP; + cmd->phy_address = 0; + cmd->transceiver = XCVR_INTERNAL; + cmd->autoneg = AUTONEG_DISABLE; + cmd->maxtxpkt = 0; + cmd->maxrxpkt = 0; + return 0; +} + +static void bcm_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(dev); + PS_INTERFACE_ADAPTER psIntfAdapter = Adapter->pvInterfaceAdapter; + struct usb_device *udev = interface_to_usbdev(psIntfAdapter->interface); + + strcpy(info->driver, DRV_NAME); + strcpy(info->version, DRV_VERSION); + snprintf(info->fw_version, sizeof(info->fw_version), "%u.%u", + Adapter->uiFlashLayoutMajorVersion, + Adapter->uiFlashLayoutMinorVersion); + + usb_make_path(udev, info->bus_info, sizeof(info->bus_info)); +} + +static u32 bcm_get_link(struct net_device *dev) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(dev); + + return Adapter->LinkUpStatus; +} + +static u32 bcm_get_msglevel(struct net_device *dev) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(dev); + + return Adapter->msg_enable; +} + +static void bcm_set_msglevel(struct net_device *dev, u32 level) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(dev); + + Adapter->msg_enable = level; +} + +static const struct ethtool_ops bcm_ethtool_ops = { + .get_settings = bcm_get_settings, + .get_drvinfo = bcm_get_drvinfo, + .get_link = bcm_get_link, + .get_msglevel = bcm_get_msglevel, + .set_msglevel = bcm_set_msglevel, +}; + +int register_networkdev(PMINI_ADAPTER Adapter) +{ + struct net_device *net = Adapter->dev; + PS_INTERFACE_ADAPTER IntfAdapter = Adapter->pvInterfaceAdapter; + struct usb_interface *udev = IntfAdapter->interface; + struct usb_device *xdev = IntfAdapter->udev; + + int result; + + net->netdev_ops = &bcmNetDevOps; + net->ethtool_ops = &bcm_ethtool_ops; + net->mtu = MTU_SIZE; /* 1400 Bytes */ + net->tx_queue_len = TX_QLEN; + net->flags |= IFF_NOARP; + + netif_carrier_off(net); + + SET_NETDEV_DEVTYPE(net, &wimax_type); + + /* Read the MAC Address from EEPROM */ + result = ReadMacAddressFromNVM(Adapter); + if (result != STATUS_SUCCESS) { + dev_err(&udev->dev, + PFX "Error in Reading the mac Address: %d", result); + return -EIO; + } + + result = register_netdev(net); + if (result) + return result; + + gblpnetdev = Adapter->dev; + + if (netif_msg_probe(Adapter)) + dev_info(&udev->dev, PFX "%s: register usb-%s-%s %pM\n", + net->name, xdev->bus->bus_name, xdev->devpath, + net->dev_addr); + + return 0; +} + +void unregister_networkdev(PMINI_ADAPTER Adapter) +{ + struct net_device *net = Adapter->dev; + PS_INTERFACE_ADAPTER IntfAdapter = Adapter->pvInterfaceAdapter; + struct usb_interface *udev = IntfAdapter->interface; + struct usb_device *xdev = IntfAdapter->udev; + + if (netif_msg_probe(Adapter)) + dev_info(&udev->dev, PFX "%s: unregister usb-%s%s\n", + net->name, xdev->bus->bus_name, xdev->devpath); + + unregister_netdev(Adapter->dev); +} diff --git a/drivers/staging/bcm/CmHost.c b/drivers/staging/bcm/CmHost.c new file mode 100644 index 00000000..7e38af5e --- /dev/null +++ b/drivers/staging/bcm/CmHost.c @@ -0,0 +1,1984 @@ +/************************************************************ + * CMHOST.C + * This file contains the routines for handling Connection + * Management. + ************************************************************/ + +/* #define CONN_MSG */ +#include "headers.h" + +enum E_CLASSIFIER_ACTION { + eInvalidClassifierAction, + eAddClassifier, + eReplaceClassifier, + eDeleteClassifier +}; + +static ULONG GetNextTargetBufferLocation(PMINI_ADAPTER Adapter, B_UINT16 tid); + +/************************************************************ + * Function - SearchSfid + * + * Description - This routinue would search QOS queues having + * specified SFID as input parameter. + * + * Parameters - Adapter: Pointer to the Adapter structure + * uiSfid : Given SFID for matching + * + * Returns - Queue index for this SFID(If matched) + * Else Invalid Queue Index(If Not matched) + ************************************************************/ +int SearchSfid(PMINI_ADAPTER Adapter, UINT uiSfid) +{ + int i; + + for (i = (NO_OF_QUEUES-1); i >= 0; i--) + if (Adapter->PackInfo[i].ulSFID == uiSfid) + return i; + + return NO_OF_QUEUES+1; +} + +/*************************************************************** + * Function -SearchFreeSfid + * + * Description - This routinue would search Free available SFID. + * + * Parameter - Adapter: Pointer to the Adapter structure + * + * Returns - Queue index for the free SFID + * Else returns Invalid Index. + ****************************************************************/ +static int SearchFreeSfid(PMINI_ADAPTER Adapter) +{ + int i; + + for (i = 0; i < (NO_OF_QUEUES-1); i++) + if (Adapter->PackInfo[i].ulSFID == 0) + return i; + + return NO_OF_QUEUES+1; +} + +/* + * Function: SearchClsid + * Description: This routinue would search Classifier having specified ClassifierID as input parameter + * Input parameters: PMINI_ADAPTER Adapter - Adapter Context + * unsigned int uiSfid - The SF in which the classifier is to searched + * B_UINT16 uiClassifierID - The classifier ID to be searched + * Return: int :Classifier table index of matching entry + */ +static int SearchClsid(PMINI_ADAPTER Adapter, ULONG ulSFID, B_UINT16 uiClassifierID) +{ + int i; + + for (i = 0; i < MAX_CLASSIFIERS; i++) { + if ((Adapter->astClassifierTable[i].bUsed) && + (Adapter->astClassifierTable[i].uiClassifierRuleIndex == uiClassifierID) && + (Adapter->astClassifierTable[i].ulSFID == ulSFID)) + return i; + } + + return MAX_CLASSIFIERS+1; +} + +/* + * @ingroup ctrl_pkt_functions + * This routinue would search Free available Classifier entry in classifier table. + * @return free Classifier Entry index in classifier table for specified SF + */ +static int SearchFreeClsid(PMINI_ADAPTER Adapter /**Adapter Context*/) +{ + int i; + + for (i = 0; i < MAX_CLASSIFIERS; i++) { + if (!Adapter->astClassifierTable[i].bUsed) + return i; + } + + return MAX_CLASSIFIERS+1; +} + +static VOID deleteSFBySfid(PMINI_ADAPTER Adapter, UINT uiSearchRuleIndex) +{ + /* deleting all the packet held in the SF */ + flush_queue(Adapter, uiSearchRuleIndex); + + /* Deleting the all classifiers for this SF */ + DeleteAllClassifiersForSF(Adapter, uiSearchRuleIndex); + + /* Resetting only MIBS related entries in the SF */ + memset((PVOID)&Adapter->PackInfo[uiSearchRuleIndex], 0, sizeof(S_MIBS_SERVICEFLOW_TABLE)); +} + +static inline VOID +CopyIpAddrToClassifier(S_CLASSIFIER_RULE *pstClassifierEntry, + B_UINT8 u8IpAddressLen, B_UINT8 *pu8IpAddressMaskSrc, + BOOLEAN bIpVersion6, E_IPADDR_CONTEXT eIpAddrContext) +{ + int i = 0; + UINT nSizeOfIPAddressInBytes = IP_LENGTH_OF_ADDRESS; + UCHAR *ptrClassifierIpAddress = NULL; + UCHAR *ptrClassifierIpMask = NULL; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + if (bIpVersion6) + nSizeOfIPAddressInBytes = IPV6_ADDRESS_SIZEINBYTES; + + /* Destination Ip Address */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Ip Address Range Length:0x%X ", u8IpAddressLen); + if ((bIpVersion6 ? (IPV6_ADDRESS_SIZEINBYTES * MAX_IP_RANGE_LENGTH * 2) : + (TOTAL_MASKED_ADDRESS_IN_BYTES)) >= u8IpAddressLen) { + /* + * checking both the mask and address togethor in Classification. + * So length will be : TotalLengthInBytes/nSizeOfIPAddressInBytes * 2 + * (nSizeOfIPAddressInBytes for address and nSizeOfIPAddressInBytes for mask) + */ + if (eIpAddrContext == eDestIpAddress) { + pstClassifierEntry->ucIPDestinationAddressLength = u8IpAddressLen/(nSizeOfIPAddressInBytes * 2); + if (bIpVersion6) { + ptrClassifierIpAddress = pstClassifierEntry->stDestIpAddress.ucIpv6Address; + ptrClassifierIpMask = pstClassifierEntry->stDestIpAddress.ucIpv6Mask; + } else { + ptrClassifierIpAddress = pstClassifierEntry->stDestIpAddress.ucIpv4Address; + ptrClassifierIpMask = pstClassifierEntry->stDestIpAddress.ucIpv4Mask; + } + } else if (eIpAddrContext == eSrcIpAddress) { + pstClassifierEntry->ucIPSourceAddressLength = u8IpAddressLen/(nSizeOfIPAddressInBytes * 2); + if (bIpVersion6) { + ptrClassifierIpAddress = pstClassifierEntry->stSrcIpAddress.ucIpv6Address; + ptrClassifierIpMask = pstClassifierEntry->stSrcIpAddress.ucIpv6Mask; + } else { + ptrClassifierIpAddress = pstClassifierEntry->stSrcIpAddress.ucIpv4Address; + ptrClassifierIpMask = pstClassifierEntry->stSrcIpAddress.ucIpv4Mask; + } + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Address Length:0x%X\n", pstClassifierEntry->ucIPDestinationAddressLength); + while ((u8IpAddressLen >= nSizeOfIPAddressInBytes) && (i < MAX_IP_RANGE_LENGTH)) { + memcpy(ptrClassifierIpAddress + + (i * nSizeOfIPAddressInBytes), + (pu8IpAddressMaskSrc+(i*nSizeOfIPAddressInBytes*2)), + nSizeOfIPAddressInBytes); + + if (!bIpVersion6) { + if (eIpAddrContext == eSrcIpAddress) { + pstClassifierEntry->stSrcIpAddress.ulIpv4Addr[i] = ntohl(pstClassifierEntry->stSrcIpAddress.ulIpv4Addr[i]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Src Ip Address:0x%luX ", + pstClassifierEntry->stSrcIpAddress.ulIpv4Addr[i]); + } else if (eIpAddrContext == eDestIpAddress) { + pstClassifierEntry->stDestIpAddress.ulIpv4Addr[i] = ntohl(pstClassifierEntry->stDestIpAddress.ulIpv4Addr[i]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Dest Ip Address:0x%luX ", + pstClassifierEntry->stDestIpAddress.ulIpv4Addr[i]); + } + } + u8IpAddressLen -= nSizeOfIPAddressInBytes; + if (u8IpAddressLen >= nSizeOfIPAddressInBytes) { + memcpy(ptrClassifierIpMask + + (i * nSizeOfIPAddressInBytes), + (pu8IpAddressMaskSrc+nSizeOfIPAddressInBytes + + (i*nSizeOfIPAddressInBytes*2)), + nSizeOfIPAddressInBytes); + + if (!bIpVersion6) { + if (eIpAddrContext == eSrcIpAddress) { + pstClassifierEntry->stSrcIpAddress.ulIpv4Mask[i] = + ntohl(pstClassifierEntry->stSrcIpAddress.ulIpv4Mask[i]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Src Ip Mask Address:0x%luX ", + pstClassifierEntry->stSrcIpAddress.ulIpv4Mask[i]); + } else if (eIpAddrContext == eDestIpAddress) { + pstClassifierEntry->stDestIpAddress.ulIpv4Mask[i] = + ntohl(pstClassifierEntry->stDestIpAddress.ulIpv4Mask[i]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Dest Ip Mask Address:0x%luX ", + pstClassifierEntry->stDestIpAddress.ulIpv4Mask[i]); + } + } + u8IpAddressLen -= nSizeOfIPAddressInBytes; + } + if (u8IpAddressLen == 0) + pstClassifierEntry->bDestIpValid = TRUE; + + i++; + } + if (bIpVersion6) { + /* Restore EndianNess of Struct */ + for (i = 0; i < MAX_IP_RANGE_LENGTH * 4; i++) { + if (eIpAddrContext == eSrcIpAddress) { + pstClassifierEntry->stSrcIpAddress.ulIpv6Addr[i] = ntohl(pstClassifierEntry->stSrcIpAddress.ulIpv6Addr[i]); + pstClassifierEntry->stSrcIpAddress.ulIpv6Mask[i] = ntohl(pstClassifierEntry->stSrcIpAddress.ulIpv6Mask[i]); + } else if (eIpAddrContext == eDestIpAddress) { + pstClassifierEntry->stDestIpAddress.ulIpv6Addr[i] = ntohl(pstClassifierEntry->stDestIpAddress.ulIpv6Addr[i]); + pstClassifierEntry->stDestIpAddress.ulIpv6Mask[i] = ntohl(pstClassifierEntry->stDestIpAddress.ulIpv6Mask[i]); + } + } + } + } +} + +void ClearTargetDSXBuffer(PMINI_ADAPTER Adapter, B_UINT16 TID, BOOLEAN bFreeAll) +{ + int i; + + for (i = 0; i < Adapter->ulTotalTargetBuffersAvailable; i++) { + if (Adapter->astTargetDsxBuffer[i].valid) + continue; + + if ((bFreeAll) || (Adapter->astTargetDsxBuffer[i].tid == TID)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "ClearTargetDSXBuffer: found tid %d buffer cleared %lx\n", + TID, Adapter->astTargetDsxBuffer[i].ulTargetDsxBuffer); + Adapter->astTargetDsxBuffer[i].valid = 1; + Adapter->astTargetDsxBuffer[i].tid = 0; + Adapter->ulFreeTargetBufferCnt++; + } + } +} + +/* + * @ingroup ctrl_pkt_functions + * copy classifier rule into the specified SF index + */ +static inline VOID CopyClassifierRuleToSF(PMINI_ADAPTER Adapter, stConvergenceSLTypes *psfCSType, UINT uiSearchRuleIndex, UINT nClassifierIndex) +{ + S_CLASSIFIER_RULE *pstClassifierEntry = NULL; + /* VOID *pvPhsContext = NULL; */ + int i; + /* UCHAR ucProtocolLength=0; */ + /* ULONG ulPhsStatus; */ + + if (Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value == 0 || + nClassifierIndex > (MAX_CLASSIFIERS-1)) + return; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Storing Classifier Rule Index : %X", + ntohs(psfCSType->cCPacketClassificationRule.u16PacketClassificationRuleIndex)); + + if (nClassifierIndex > MAX_CLASSIFIERS-1) + return; + + pstClassifierEntry = &Adapter->astClassifierTable[nClassifierIndex]; + if (pstClassifierEntry) { + /* Store if Ipv6 */ + pstClassifierEntry->bIpv6Protocol = (Adapter->PackInfo[uiSearchRuleIndex].ucIpVersion == IPV6) ? TRUE : FALSE; + + /* Destinaiton Port */ + pstClassifierEntry->ucDestPortRangeLength = psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRangeLength / 4; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Destination Port Range Length:0x%X ", pstClassifierEntry->ucDestPortRangeLength); + + if (psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRangeLength <= MAX_PORT_RANGE) { + for (i = 0; i < (pstClassifierEntry->ucDestPortRangeLength); i++) { + pstClassifierEntry->usDestPortRangeLo[i] = *((PUSHORT)(psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange+i)); + pstClassifierEntry->usDestPortRangeHi[i] = + *((PUSHORT)(psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange+2+i)); + pstClassifierEntry->usDestPortRangeLo[i] = ntohs(pstClassifierEntry->usDestPortRangeLo[i]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Destination Port Range Lo:0x%X ", + pstClassifierEntry->usDestPortRangeLo[i]); + pstClassifierEntry->usDestPortRangeHi[i] = ntohs(pstClassifierEntry->usDestPortRangeHi[i]); + } + } else { + pstClassifierEntry->ucDestPortRangeLength = 0; + } + + /* Source Port */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Source Port Range Length:0x%X ", + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRangeLength); + if (psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRangeLength <= MAX_PORT_RANGE) { + pstClassifierEntry->ucSrcPortRangeLength = psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRangeLength/4; + for (i = 0; i < (pstClassifierEntry->ucSrcPortRangeLength); i++) { + pstClassifierEntry->usSrcPortRangeLo[i] = + *((PUSHORT)(psfCSType->cCPacketClassificationRule. + u8ProtocolSourcePortRange+i)); + pstClassifierEntry->usSrcPortRangeHi[i] = + *((PUSHORT)(psfCSType->cCPacketClassificationRule. + u8ProtocolSourcePortRange+2+i)); + pstClassifierEntry->usSrcPortRangeLo[i] = + ntohs(pstClassifierEntry->usSrcPortRangeLo[i]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Source Port Range Lo:0x%X ", + pstClassifierEntry->usSrcPortRangeLo[i]); + pstClassifierEntry->usSrcPortRangeHi[i] = ntohs(pstClassifierEntry->usSrcPortRangeHi[i]); + } + } + /* Destination Ip Address and Mask */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Ip Destination Parameters : "); + CopyIpAddrToClassifier(pstClassifierEntry, + psfCSType->cCPacketClassificationRule.u8IPDestinationAddressLength, + psfCSType->cCPacketClassificationRule.u8IPDestinationAddress, + (Adapter->PackInfo[uiSearchRuleIndex].ucIpVersion == IPV6) ? + TRUE : FALSE, eDestIpAddress); + + /* Source Ip Address and Mask */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Ip Source Parameters : "); + + CopyIpAddrToClassifier(pstClassifierEntry, + psfCSType->cCPacketClassificationRule.u8IPMaskedSourceAddressLength, + psfCSType->cCPacketClassificationRule.u8IPMaskedSourceAddress, + (Adapter->PackInfo[uiSearchRuleIndex].ucIpVersion == IPV6) ? TRUE : FALSE, + eSrcIpAddress); + + /* TOS */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "TOS Length:0x%X ", psfCSType->cCPacketClassificationRule.u8IPTypeOfServiceLength); + if (psfCSType->cCPacketClassificationRule.u8IPTypeOfServiceLength == 3) { + pstClassifierEntry->ucIPTypeOfServiceLength = psfCSType->cCPacketClassificationRule.u8IPTypeOfServiceLength; + pstClassifierEntry->ucTosLow = psfCSType->cCPacketClassificationRule.u8IPTypeOfService[0]; + pstClassifierEntry->ucTosHigh = psfCSType->cCPacketClassificationRule.u8IPTypeOfService[1]; + pstClassifierEntry->ucTosMask = psfCSType->cCPacketClassificationRule.u8IPTypeOfService[2]; + pstClassifierEntry->bTOSValid = TRUE; + } + if (psfCSType->cCPacketClassificationRule.u8Protocol == 0) { + /* we didn't get protocol field filled in by the BS */ + pstClassifierEntry->ucProtocolLength = 0; + } else { + pstClassifierEntry->ucProtocolLength = 1; /* 1 valid protocol */ + } + + pstClassifierEntry->ucProtocol[0] = psfCSType->cCPacketClassificationRule.u8Protocol; + pstClassifierEntry->u8ClassifierRulePriority = psfCSType->cCPacketClassificationRule.u8ClassifierRulePriority; + + /* store the classifier rule ID and set this classifier entry as valid */ + pstClassifierEntry->ucDirection = Adapter->PackInfo[uiSearchRuleIndex].ucDirection; + pstClassifierEntry->uiClassifierRuleIndex = ntohs(psfCSType->cCPacketClassificationRule.u16PacketClassificationRuleIndex); + pstClassifierEntry->usVCID_Value = Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value; + pstClassifierEntry->ulSFID = Adapter->PackInfo[uiSearchRuleIndex].ulSFID; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Search Index %d Dir: %d, Index: %d, Vcid: %d\n", + uiSearchRuleIndex, pstClassifierEntry->ucDirection, + pstClassifierEntry->uiClassifierRuleIndex, + pstClassifierEntry->usVCID_Value); + + if (psfCSType->cCPacketClassificationRule.u8AssociatedPHSI) + pstClassifierEntry->u8AssociatedPHSI = psfCSType->cCPacketClassificationRule.u8AssociatedPHSI; + + /* Copy ETH CS Parameters */ + pstClassifierEntry->ucEthCSSrcMACLen = (psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddressLength); + memcpy(pstClassifierEntry->au8EThCSSrcMAC, psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress, MAC_ADDRESS_SIZE); + memcpy(pstClassifierEntry->au8EThCSSrcMACMask, psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress + MAC_ADDRESS_SIZE, MAC_ADDRESS_SIZE); + pstClassifierEntry->ucEthCSDestMACLen = (psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddressLength); + memcpy(pstClassifierEntry->au8EThCSDestMAC, psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress, MAC_ADDRESS_SIZE); + memcpy(pstClassifierEntry->au8EThCSDestMACMask, psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress + MAC_ADDRESS_SIZE, MAC_ADDRESS_SIZE); + pstClassifierEntry->ucEtherTypeLen = (psfCSType->cCPacketClassificationRule.u8EthertypeLength); + memcpy(pstClassifierEntry->au8EthCSEtherType, psfCSType->cCPacketClassificationRule.u8Ethertype, NUM_ETHERTYPE_BYTES); + memcpy(pstClassifierEntry->usUserPriority, &psfCSType->cCPacketClassificationRule.u16UserPriority, 2); + pstClassifierEntry->usVLANID = ntohs(psfCSType->cCPacketClassificationRule.u16VLANID); + pstClassifierEntry->usValidityBitMap = ntohs(psfCSType->cCPacketClassificationRule.u16ValidityBitMap); + + pstClassifierEntry->bUsed = TRUE; + } +} + +/* + * @ingroup ctrl_pkt_functions + */ +static inline VOID DeleteClassifierRuleFromSF(PMINI_ADAPTER Adapter, UINT uiSearchRuleIndex, UINT nClassifierIndex) +{ + S_CLASSIFIER_RULE *pstClassifierEntry = NULL; + B_UINT16 u16PacketClassificationRuleIndex; + USHORT usVCID; + /* VOID *pvPhsContext = NULL; */ + /*ULONG ulPhsStatus; */ + + usVCID = Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value; + + if (nClassifierIndex > MAX_CLASSIFIERS-1) + return; + + if (usVCID == 0) + return; + + u16PacketClassificationRuleIndex = Adapter->astClassifierTable[nClassifierIndex].uiClassifierRuleIndex; + pstClassifierEntry = &Adapter->astClassifierTable[nClassifierIndex]; + if (pstClassifierEntry) { + pstClassifierEntry->bUsed = FALSE; + pstClassifierEntry->uiClassifierRuleIndex = 0; + memset(pstClassifierEntry, 0, sizeof(S_CLASSIFIER_RULE)); + + /* Delete the PHS Rule for this classifier */ + PhsDeleteClassifierRule(&Adapter->stBCMPhsContext, usVCID, u16PacketClassificationRuleIndex); + } +} + +/* + * @ingroup ctrl_pkt_functions + */ +VOID DeleteAllClassifiersForSF(PMINI_ADAPTER Adapter, UINT uiSearchRuleIndex) +{ + S_CLASSIFIER_RULE *pstClassifierEntry = NULL; + int i; + /* B_UINT16 u16PacketClassificationRuleIndex; */ + USHORT ulVCID; + /* VOID *pvPhsContext = NULL; */ + /* ULONG ulPhsStatus; */ + + ulVCID = Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value; + + if (ulVCID == 0) + return; + + for (i = 0; i < MAX_CLASSIFIERS; i++) { + if (Adapter->astClassifierTable[i].usVCID_Value == ulVCID) { + pstClassifierEntry = &Adapter->astClassifierTable[i]; + + if (pstClassifierEntry->bUsed) + DeleteClassifierRuleFromSF(Adapter, uiSearchRuleIndex, i); + } + } + + /* Delete All Phs Rules Associated with this SF */ + PhsDeleteSFRules(&Adapter->stBCMPhsContext, ulVCID); +} + +/* + * This routinue copies the Connection Management + * related data into the Adapter structure. + * @ingroup ctrl_pkt_functions + */ +static VOID CopyToAdapter(register PMINI_ADAPTER Adapter, /* PackInfo[uiSearchRuleIndex].usVCID_Value; + UINT UGIValue = 0; + + Adapter->PackInfo[uiSearchRuleIndex].bValid = TRUE; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Search Rule Index = %d\n", uiSearchRuleIndex); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "%s: SFID= %x ", __func__, ntohl(psfLocalSet->u32SFID)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Updating Queue %d", uiSearchRuleIndex); + + ulSFID = ntohl(psfLocalSet->u32SFID); + /* Store IP Version used */ + /* Get The Version Of IP used (IPv6 or IPv4) from CSSpecification field of SF */ + + Adapter->PackInfo[uiSearchRuleIndex].bIPCSSupport = 0; + Adapter->PackInfo[uiSearchRuleIndex].bEthCSSupport = 0; + + /* Enable IP/ETh CS Support As Required */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "CopyToAdapter : u8CSSpecification : %X\n", psfLocalSet->u8CSSpecification); + switch (psfLocalSet->u8CSSpecification) { + case eCSPacketIPV4: + { + Adapter->PackInfo[uiSearchRuleIndex].bIPCSSupport = IPV4_CS; + break; + } + case eCSPacketIPV6: + { + Adapter->PackInfo[uiSearchRuleIndex].bIPCSSupport = IPV6_CS; + break; + } + case eCS802_3PacketEthernet: + case eCS802_1QPacketVLAN: + { + Adapter->PackInfo[uiSearchRuleIndex].bEthCSSupport = ETH_CS_802_3; + break; + } + case eCSPacketIPV4Over802_1QVLAN: + case eCSPacketIPV4Over802_3Ethernet: + { + Adapter->PackInfo[uiSearchRuleIndex].bIPCSSupport = IPV4_CS; + Adapter->PackInfo[uiSearchRuleIndex].bEthCSSupport = ETH_CS_802_3; + break; + } + case eCSPacketIPV6Over802_1QVLAN: + case eCSPacketIPV6Over802_3Ethernet: + { + Adapter->PackInfo[uiSearchRuleIndex].bIPCSSupport = IPV6_CS; + Adapter->PackInfo[uiSearchRuleIndex].bEthCSSupport = ETH_CS_802_3; + break; + } + default: + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Error in value of CS Classification.. setting default to IP CS\n"); + Adapter->PackInfo[uiSearchRuleIndex].bIPCSSupport = IPV4_CS; + break; + } + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "CopyToAdapter : Queue No : %X ETH CS Support : %X , IP CS Support : %X\n", + uiSearchRuleIndex, + Adapter->PackInfo[uiSearchRuleIndex].bEthCSSupport, + Adapter->PackInfo[uiSearchRuleIndex].bIPCSSupport); + + /* Store IP Version used */ + /* Get The Version Of IP used (IPv6 or IPv4) from CSSpecification field of SF */ + if (Adapter->PackInfo[uiSearchRuleIndex].bIPCSSupport == IPV6_CS) + Adapter->PackInfo[uiSearchRuleIndex].ucIpVersion = IPV6; + else + Adapter->PackInfo[uiSearchRuleIndex].ucIpVersion = IPV4; + + /* To ensure that the ETH CS code doesn't gets executed if the BS doesn't supports ETH CS */ + if (!Adapter->bETHCSEnabled) + Adapter->PackInfo[uiSearchRuleIndex].bEthCSSupport = 0; + + if (psfLocalSet->u8ServiceClassNameLength > 0 && psfLocalSet->u8ServiceClassNameLength < 32) + memcpy(Adapter->PackInfo[uiSearchRuleIndex].ucServiceClassName, psfLocalSet->u8ServiceClassName, psfLocalSet->u8ServiceClassNameLength); + + Adapter->PackInfo[uiSearchRuleIndex].u8QueueType = psfLocalSet->u8ServiceFlowSchedulingType; + + if (Adapter->PackInfo[uiSearchRuleIndex].u8QueueType == BE && Adapter->PackInfo[uiSearchRuleIndex].ucDirection) + Adapter->usBestEffortQueueIndex = uiSearchRuleIndex; + + Adapter->PackInfo[uiSearchRuleIndex].ulSFID = ntohl(psfLocalSet->u32SFID); + + Adapter->PackInfo[uiSearchRuleIndex].u8TrafficPriority = psfLocalSet->u8TrafficPriority; + + /* copy all the classifier in the Service Flow param structure */ + for (i = 0; i < psfLocalSet->u8TotalClassifiers; i++) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Classifier index =%d", i); + psfCSType = &psfLocalSet->cConvergenceSLTypes[i]; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Classifier index =%d", i); + + if (psfCSType->cCPacketClassificationRule.u8ClassifierRulePriority) + Adapter->PackInfo[uiSearchRuleIndex].bClassifierPriority = TRUE; + + if (psfCSType->cCPacketClassificationRule.u8ClassifierRulePriority) + Adapter->PackInfo[uiSearchRuleIndex].bClassifierPriority = TRUE; + + if (ucDsxType == DSA_ACK) { + eClassifierAction = eAddClassifier; + } else if (ucDsxType == DSC_ACK) { + switch (psfCSType->u8ClassfierDSCAction) { + case 0: /* DSC Add Classifier */ + { + eClassifierAction = eAddClassifier; + } + break; + case 1: /* DSC Replace Classifier */ + { + eClassifierAction = eReplaceClassifier; + } + break; + case 2: /* DSC Delete Classifier */ + { + eClassifierAction = eDeleteClassifier; + } + break; + default: + { + eClassifierAction = eInvalidClassifierAction; + } + } + } + + u16PacketClassificationRuleIndex = ntohs(psfCSType->cCPacketClassificationRule.u16PacketClassificationRuleIndex); + + switch (eClassifierAction) { + case eAddClassifier: + { + /* Get a Free Classifier Index From Classifier table for this SF to add the Classifier */ + /* Contained in this message */ + nClassifierIndex = SearchClsid(Adapter, ulSFID, u16PacketClassificationRuleIndex); + + if (nClassifierIndex > MAX_CLASSIFIERS) { + nClassifierIndex = SearchFreeClsid(Adapter); + if (nClassifierIndex > MAX_CLASSIFIERS) { + /* Failed To get a free Entry */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Error Failed To get a free Classifier Entry"); + break; + } + /* Copy the Classifier Rule for this service flow into our Classifier table maintained per SF. */ + CopyClassifierRuleToSF(Adapter, psfCSType, uiSearchRuleIndex, nClassifierIndex); + } else { + /* This Classifier Already Exists and it is invalid to Add Classifier with existing PCRI */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, + "CopyToAdapter: Error The Specified Classifier Already Exists and attempted To Add Classifier with Same PCRI : 0x%x\n", + u16PacketClassificationRuleIndex); + } + } + break; + case eReplaceClassifier: + { + /* Get the Classifier Index From Classifier table for this SF and replace existing Classifier */ + /* with the new classifier Contained in this message */ + nClassifierIndex = SearchClsid(Adapter, ulSFID, u16PacketClassificationRuleIndex); + if (nClassifierIndex > MAX_CLASSIFIERS) { + /* Failed To search the classifier */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Error Search for Classifier To be replaced failed"); + break; + } + /* Copy the Classifier Rule for this service flow into our Classifier table maintained per SF. */ + CopyClassifierRuleToSF(Adapter, psfCSType, uiSearchRuleIndex, nClassifierIndex); + } + break; + case eDeleteClassifier: + { + /* Get the Classifier Index From Classifier table for this SF and replace existing Classifier */ + /* with the new classifier Contained in this message */ + nClassifierIndex = SearchClsid(Adapter, ulSFID, u16PacketClassificationRuleIndex); + if (nClassifierIndex > MAX_CLASSIFIERS) { + /* Failed To search the classifier */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Error Search for Classifier To be deleted failed"); + break; + } + + /* Delete This classifier */ + DeleteClassifierRuleFromSF(Adapter, uiSearchRuleIndex, nClassifierIndex); + } + break; + default: + { + /* Invalid Action for classifier */ + break; + } + } + } + + /* Repeat parsing Classification Entries to process PHS Rules */ + for (i = 0; i < psfLocalSet->u8TotalClassifiers; i++) { + psfCSType = &psfLocalSet->cConvergenceSLTypes[i]; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "psfCSType->u8PhsDSCAction : 0x%x\n", psfCSType->u8PhsDSCAction); + + switch (psfCSType->u8PhsDSCAction) { + case eDeleteAllPHSRules: + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Deleting All PHS Rules For VCID: 0x%X\n", uVCID); + + /* Delete All the PHS rules for this Service flow */ + PhsDeleteSFRules(&Adapter->stBCMPhsContext, uVCID); + break; + } + case eDeletePHSRule: + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "PHS DSC Action = Delete PHS Rule\n"); + + if (psfCSType->cPhsRule.u8PHSI) + PhsDeletePHSRule(&Adapter->stBCMPhsContext, uVCID, psfCSType->cCPacketClassificationRule.u8AssociatedPHSI); + + break; + } + default: + { + if (ucDsxType == DSC_ACK) { + /* BCM_DEBUG_PRINT(CONN_MSG,("Invalid PHS DSC Action For DSC\n",psfCSType->cPhsRule.u8PHSI)); */ + break; /* FOr DSC ACK Case PHS DSC Action must be in valid set */ + } + } + /* Proceed To Add PHS rule for DSA_ACK case even if PHS DSC action is unspecified */ + /* No Break Here . Intentionally! */ + + case eAddPHSRule: + case eSetPHSRule: + { + if (psfCSType->cPhsRule.u8PHSI) { + /* Apply This PHS Rule to all classifiers whose Associated PHSI Match */ + unsigned int uiClassifierIndex = 0; + if (pstAddIndication->u8Direction == UPLINK_DIR) { + for (uiClassifierIndex = 0; uiClassifierIndex < MAX_CLASSIFIERS; uiClassifierIndex++) { + if ((Adapter->astClassifierTable[uiClassifierIndex].bUsed) && + (Adapter->astClassifierTable[uiClassifierIndex].ulSFID == Adapter->PackInfo[uiSearchRuleIndex].ulSFID) && + (Adapter->astClassifierTable[uiClassifierIndex].u8AssociatedPHSI == psfCSType->cPhsRule.u8PHSI)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, + "Adding PHS Rule For Classifier: 0x%x cPhsRule.u8PHSI: 0x%x\n", + Adapter->astClassifierTable[uiClassifierIndex].uiClassifierRuleIndex, + psfCSType->cPhsRule.u8PHSI); + /* Update The PHS Rule for this classifier as Associated PHSI id defined */ + + /* Copy the PHS Rule */ + sPhsRule.u8PHSI = psfCSType->cPhsRule.u8PHSI; + sPhsRule.u8PHSFLength = psfCSType->cPhsRule.u8PHSFLength; + sPhsRule.u8PHSMLength = psfCSType->cPhsRule.u8PHSMLength; + sPhsRule.u8PHSS = psfCSType->cPhsRule.u8PHSS; + sPhsRule.u8PHSV = psfCSType->cPhsRule.u8PHSV; + memcpy(sPhsRule.u8PHSF, psfCSType->cPhsRule.u8PHSF, MAX_PHS_LENGTHS); + memcpy(sPhsRule.u8PHSM, psfCSType->cPhsRule.u8PHSM, MAX_PHS_LENGTHS); + sPhsRule.u8RefCnt = 0; + sPhsRule.bUnclassifiedPHSRule = FALSE; + sPhsRule.PHSModifiedBytes = 0; + sPhsRule.PHSModifiedNumPackets = 0; + sPhsRule.PHSErrorNumPackets = 0; + + /* bPHSRuleAssociated = TRUE; */ + /* Store The PHS Rule for this classifier */ + + PhsUpdateClassifierRule( + &Adapter->stBCMPhsContext, + uVCID, + Adapter->astClassifierTable[uiClassifierIndex].uiClassifierRuleIndex, + &sPhsRule, + Adapter->astClassifierTable[uiClassifierIndex].u8AssociatedPHSI); + + /* Update PHS Rule For the Classifier */ + if (sPhsRule.u8PHSI) { + Adapter->astClassifierTable[uiClassifierIndex].u32PHSRuleID = sPhsRule.u8PHSI; + memcpy(&Adapter->astClassifierTable[uiClassifierIndex].sPhsRule, &sPhsRule, sizeof(S_PHS_RULE)); + } + } + } + } else { + /* Error PHS Rule specified in signaling could not be applied to any classifier */ + + /* Copy the PHS Rule */ + sPhsRule.u8PHSI = psfCSType->cPhsRule.u8PHSI; + sPhsRule.u8PHSFLength = psfCSType->cPhsRule.u8PHSFLength; + sPhsRule.u8PHSMLength = psfCSType->cPhsRule.u8PHSMLength; + sPhsRule.u8PHSS = psfCSType->cPhsRule.u8PHSS; + sPhsRule.u8PHSV = psfCSType->cPhsRule.u8PHSV; + memcpy(sPhsRule.u8PHSF, psfCSType->cPhsRule.u8PHSF, MAX_PHS_LENGTHS); + memcpy(sPhsRule.u8PHSM, psfCSType->cPhsRule.u8PHSM, MAX_PHS_LENGTHS); + sPhsRule.u8RefCnt = 0; + sPhsRule.bUnclassifiedPHSRule = TRUE; + sPhsRule.PHSModifiedBytes = 0; + sPhsRule.PHSModifiedNumPackets = 0; + sPhsRule.PHSErrorNumPackets = 0; + /* Store The PHS Rule for this classifier */ + + /* + * Passing the argument u8PHSI instead of clsid. Because for DL with no classifier rule, + * clsid will be zero hence we can't have multiple PHS rules for the same SF. + * To support multiple PHS rule, passing u8PHSI. + */ + PhsUpdateClassifierRule( + &Adapter->stBCMPhsContext, + uVCID, + sPhsRule.u8PHSI, + &sPhsRule, + sPhsRule.u8PHSI); + } + } + } + break; + } + } + + if (psfLocalSet->u32MaxSustainedTrafficRate == 0) { + /* No Rate Limit . Set Max Sustained Traffic Rate to Maximum */ + Adapter->PackInfo[uiSearchRuleIndex].uiMaxAllowedRate = WIMAX_MAX_ALLOWED_RATE; + } else if (ntohl(psfLocalSet->u32MaxSustainedTrafficRate) > WIMAX_MAX_ALLOWED_RATE) { + /* Too large Allowed Rate specified. Limiting to Wi Max Allowed rate */ + Adapter->PackInfo[uiSearchRuleIndex].uiMaxAllowedRate = WIMAX_MAX_ALLOWED_RATE; + } else { + Adapter->PackInfo[uiSearchRuleIndex].uiMaxAllowedRate = ntohl(psfLocalSet->u32MaxSustainedTrafficRate); + } + + Adapter->PackInfo[uiSearchRuleIndex].uiMaxLatency = ntohl(psfLocalSet->u32MaximumLatency); + if (Adapter->PackInfo[uiSearchRuleIndex].uiMaxLatency == 0) /* 0 should be treated as infinite */ + Adapter->PackInfo[uiSearchRuleIndex].uiMaxLatency = MAX_LATENCY_ALLOWED; + + if ((Adapter->PackInfo[uiSearchRuleIndex].u8QueueType == ERTPS || + Adapter->PackInfo[uiSearchRuleIndex].u8QueueType == UGS)) + UGIValue = ntohs(psfLocalSet->u16UnsolicitedGrantInterval); + + if (UGIValue == 0) + UGIValue = DEFAULT_UG_INTERVAL; + + /* + * For UGI based connections... + * DEFAULT_UGI_FACTOR*UGIInterval worth of data is the max token count at host... + * The extra amount of token is to ensure that a large amount of jitter won't have loss in throughput... + * In case of non-UGI based connection, 200 frames worth of data is the max token count at host... + */ + Adapter->PackInfo[uiSearchRuleIndex].uiMaxBucketSize = + (DEFAULT_UGI_FACTOR*Adapter->PackInfo[uiSearchRuleIndex].uiMaxAllowedRate*UGIValue)/1000; + + if (Adapter->PackInfo[uiSearchRuleIndex].uiMaxBucketSize < WIMAX_MAX_MTU*8) { + UINT UGIFactor = 0; + /* Special Handling to ensure the biggest size of packet can go out from host to FW as follows: + * 1. Any packet from Host to FW can go out in different packet size. + * 2. So in case the Bucket count is smaller than MTU, the packets of size (Size > TokenCount), will get dropped. + * 3. We can allow packets of MaxSize from Host->FW that can go out from FW in multiple SDUs by fragmentation at Wimax Layer + */ + UGIFactor = (Adapter->PackInfo[uiSearchRuleIndex].uiMaxLatency/UGIValue + 1); + + if (UGIFactor > DEFAULT_UGI_FACTOR) + Adapter->PackInfo[uiSearchRuleIndex].uiMaxBucketSize = + (UGIFactor*Adapter->PackInfo[uiSearchRuleIndex].uiMaxAllowedRate*UGIValue)/1000; + + if (Adapter->PackInfo[uiSearchRuleIndex].uiMaxBucketSize > WIMAX_MAX_MTU*8) + Adapter->PackInfo[uiSearchRuleIndex].uiMaxBucketSize = WIMAX_MAX_MTU*8; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "LAT: %d, UGI: %d\n", Adapter->PackInfo[uiSearchRuleIndex].uiMaxLatency, UGIValue); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "uiMaxAllowedRate: 0x%x, u32MaxSustainedTrafficRate: 0x%x ,uiMaxBucketSize: 0x%x", + Adapter->PackInfo[uiSearchRuleIndex].uiMaxAllowedRate, + ntohl(psfLocalSet->u32MaxSustainedTrafficRate), + Adapter->PackInfo[uiSearchRuleIndex].uiMaxBucketSize); + + /* copy the extended SF Parameters to Support MIBS */ + CopyMIBSExtendedSFParameters(Adapter, psfLocalSet, uiSearchRuleIndex); + + /* store header suppression enabled flag per SF */ + Adapter->PackInfo[uiSearchRuleIndex].bHeaderSuppressionEnabled = + !(psfLocalSet->u8RequesttransmissionPolicy & + MASK_DISABLE_HEADER_SUPPRESSION); + + kfree(Adapter->PackInfo[uiSearchRuleIndex].pstSFIndication); + Adapter->PackInfo[uiSearchRuleIndex].pstSFIndication = pstAddIndication; + + /* Re Sort the SF list in PackInfo according to Traffic Priority */ + SortPackInfo(Adapter); + + /* Re Sort the Classifier Rules table and re - arrange + * according to Classifier Rule Priority + */ + SortClassifiers(Adapter); + DumpPhsRules(&Adapter->stBCMPhsContext); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "%s <=====", __func__); +} + +/*********************************************************************** + * Function - DumpCmControlPacket + * + * Description - This routinue Dumps the Contents of the AddIndication + * Structure in the Connection Management Control Packet + * + * Parameter - pvBuffer: Pointer to the buffer containing the + * AddIndication data. + * + * Returns - None + *************************************************************************/ +static VOID DumpCmControlPacket(PVOID pvBuffer) +{ + int uiLoopIndex; + int nIndex; + stLocalSFAddIndicationAlt *pstAddIndication; + UINT nCurClassifierCnt; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + pstAddIndication = (stLocalSFAddIndicationAlt *)pvBuffer; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "======>"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8Type: 0x%X", pstAddIndication->u8Type); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8Direction: 0x%X", pstAddIndication->u8Direction); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16TID: 0x%X", ntohs(pstAddIndication->u16TID)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16CID: 0x%X", ntohs(pstAddIndication->u16CID)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16VCID: 0x%X", ntohs(pstAddIndication->u16VCID)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " AuthorizedSet--->"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32SFID: 0x%X", htonl(pstAddIndication->sfAuthorizedSet.u32SFID)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16CID: 0x%X", htons(pstAddIndication->sfAuthorizedSet.u16CID)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ServiceClassNameLength: 0x%X", + pstAddIndication->sfAuthorizedSet.u8ServiceClassNameLength); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ServiceClassName: 0x%X ,0x%X , 0x%X, 0x%X, 0x%X, 0x%X", + pstAddIndication->sfAuthorizedSet.u8ServiceClassName[0], + pstAddIndication->sfAuthorizedSet.u8ServiceClassName[1], + pstAddIndication->sfAuthorizedSet.u8ServiceClassName[2], + pstAddIndication->sfAuthorizedSet.u8ServiceClassName[3], + pstAddIndication->sfAuthorizedSet.u8ServiceClassName[4], + pstAddIndication->sfAuthorizedSet.u8ServiceClassName[5]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8MBSService: 0x%X", pstAddIndication->sfAuthorizedSet.u8MBSService); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8QosParamSet: 0x%X", pstAddIndication->sfAuthorizedSet.u8QosParamSet); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8TrafficPriority: 0x%X, %p", + pstAddIndication->sfAuthorizedSet.u8TrafficPriority, &pstAddIndication->sfAuthorizedSet.u8TrafficPriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MaxSustainedTrafficRate: 0x%X 0x%p", + pstAddIndication->sfAuthorizedSet.u32MaxSustainedTrafficRate, + &pstAddIndication->sfAuthorizedSet.u32MaxSustainedTrafficRate); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MaxTrafficBurst: 0x%X", pstAddIndication->sfAuthorizedSet.u32MaxTrafficBurst); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MinReservedTrafficRate : 0x%X", + pstAddIndication->sfAuthorizedSet.u32MinReservedTrafficRate); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificQoSParamLength: 0x%X", + pstAddIndication->sfAuthorizedSet.u8VendorSpecificQoSParamLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificQoSParam: 0x%X", + pstAddIndication->sfAuthorizedSet.u8VendorSpecificQoSParam[0]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ServiceFlowSchedulingType: 0x%X", + pstAddIndication->sfAuthorizedSet.u8ServiceFlowSchedulingType); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32ToleratedJitter: 0x%X", pstAddIndication->sfAuthorizedSet.u32ToleratedJitter); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MaximumLatency: 0x%X", pstAddIndication->sfAuthorizedSet.u32MaximumLatency); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8FixedLengthVSVariableLengthSDUIndicator: 0x%X", + pstAddIndication->sfAuthorizedSet.u8FixedLengthVSVariableLengthSDUIndicator); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8SDUSize: 0x%X", pstAddIndication->sfAuthorizedSet.u8SDUSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16TargetSAID: 0x%X", pstAddIndication->sfAuthorizedSet.u16TargetSAID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ARQEnable: 0x%X", pstAddIndication->sfAuthorizedSet.u8ARQEnable); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQWindowSize: 0x%X", pstAddIndication->sfAuthorizedSet.u16ARQWindowSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQRetryTxTimeOut: 0x%X", pstAddIndication->sfAuthorizedSet.u16ARQRetryTxTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQRetryRxTimeOut: 0x%X", pstAddIndication->sfAuthorizedSet.u16ARQRetryRxTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQBlockLifeTime: 0x%X", pstAddIndication->sfAuthorizedSet.u16ARQBlockLifeTime); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQSyncLossTimeOut: 0x%X", pstAddIndication->sfAuthorizedSet.u16ARQSyncLossTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ARQDeliverInOrder: 0x%X", pstAddIndication->sfAuthorizedSet.u8ARQDeliverInOrder); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQRxPurgeTimeOut: 0x%X", pstAddIndication->sfAuthorizedSet.u16ARQRxPurgeTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQBlockSize: 0x%X", pstAddIndication->sfAuthorizedSet.u16ARQBlockSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8CSSpecification: 0x%X", pstAddIndication->sfAuthorizedSet.u8CSSpecification); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8TypeOfDataDeliveryService: 0x%X", + pstAddIndication->sfAuthorizedSet.u8TypeOfDataDeliveryService); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16SDUInterArrivalTime: 0x%X", pstAddIndication->sfAuthorizedSet.u16SDUInterArrivalTime); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16TimeBase: 0x%X", pstAddIndication->sfAuthorizedSet.u16TimeBase); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8PagingPreference: 0x%X", pstAddIndication->sfAuthorizedSet.u8PagingPreference); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16UnsolicitedPollingInterval: 0x%X", + pstAddIndication->sfAuthorizedSet.u16UnsolicitedPollingInterval); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "sfAuthorizedSet.u8HARQChannelMapping %x %x %x ", + *(unsigned int *)pstAddIndication->sfAuthorizedSet.u8HARQChannelMapping, + *(unsigned int *)&pstAddIndication->sfAuthorizedSet.u8HARQChannelMapping[4], + *(USHORT *)&pstAddIndication->sfAuthorizedSet.u8HARQChannelMapping[8]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8TrafficIndicationPreference: 0x%X", + pstAddIndication->sfAuthorizedSet.u8TrafficIndicationPreference); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " Total Classifiers Received: 0x%X", pstAddIndication->sfAuthorizedSet.u8TotalClassifiers); + + nCurClassifierCnt = pstAddIndication->sfAuthorizedSet.u8TotalClassifiers; + if (nCurClassifierCnt > MAX_CLASSIFIERS_IN_SF) + nCurClassifierCnt = MAX_CLASSIFIERS_IN_SF; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "pstAddIndication->sfAuthorizedSet.bValid %d", pstAddIndication->sfAuthorizedSet.bValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "pstAddIndication->sfAuthorizedSet.u16MacOverhead %x", pstAddIndication->sfAuthorizedSet.u16MacOverhead); + if (!pstAddIndication->sfAuthorizedSet.bValid) + pstAddIndication->sfAuthorizedSet.bValid = 1; + for (nIndex = 0; nIndex < nCurClassifierCnt; nIndex++) { + stConvergenceSLTypes *psfCSType = NULL; + psfCSType = &pstAddIndication->sfAuthorizedSet.cConvergenceSLTypes[nIndex]; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "psfCSType = %p", psfCSType); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "CCPacketClassificationRuleSI====>"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ClassifierRulePriority: 0x%X ", + psfCSType->cCPacketClassificationRule.u8ClassifierRulePriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPTypeOfServiceLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8IPTypeOfServiceLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPTypeOfService[3]: 0x%X ,0x%X ,0x%X ", + psfCSType->cCPacketClassificationRule.u8IPTypeOfService[0], + psfCSType->cCPacketClassificationRule.u8IPTypeOfService[1], + psfCSType->cCPacketClassificationRule.u8IPTypeOfService[2]); + + for (uiLoopIndex = 0; uiLoopIndex < 1; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8Protocol: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8Protocol); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPMaskedSourceAddressLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8IPMaskedSourceAddressLength); + + for (uiLoopIndex = 0; uiLoopIndex < 32; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPMaskedSourceAddress[32]: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8IPMaskedSourceAddress[uiLoopIndex]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPDestinationAddressLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8IPDestinationAddressLength); + + for (uiLoopIndex = 0; uiLoopIndex < 32; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPDestinationAddress[32]: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8IPDestinationAddress[uiLoopIndex]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ProtocolSourcePortRangeLength:0x%X ", + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRangeLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ProtocolSourcePortRange[4]: 0x%02X ,0x%02X ,0x%02X ,0x%02X ", + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[0], + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[1], + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[2], + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[3]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ProtocolDestPortRangeLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRangeLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ProtocolDestPortRange[4]: 0x%02X ,0x%02X ,0x%02X ,0x%02X ", + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[0], + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[1], + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[2], + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[3]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthernetDestMacAddressLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddressLength); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthernetDestMacAddress[6]: 0x %02X %02X %02X %02X %02X %02X", + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[0], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[1], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[2], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[3], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[4], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[5]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthernetSourceMACAddressLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddressLength); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthernetSourceMACAddress[6]: 0x %02X %02X %02X %02X %02X %02X", + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[0], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[1], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[2], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[3], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[4], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[5]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthertypeLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8EthertypeLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8Ethertype[3]: 0x%02X ,0x%02X ,0x%02X ", + psfCSType->cCPacketClassificationRule.u8Ethertype[0], + psfCSType->cCPacketClassificationRule.u8Ethertype[1], + psfCSType->cCPacketClassificationRule.u8Ethertype[2]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16UserPriority: 0x%X ", psfCSType->cCPacketClassificationRule.u16UserPriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16VLANID: 0x%X ", psfCSType->cCPacketClassificationRule.u16VLANID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8AssociatedPHSI: 0x%02X ", psfCSType->cCPacketClassificationRule.u8AssociatedPHSI); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16PacketClassificationRuleIndex: 0x%X ", + psfCSType->cCPacketClassificationRule.u16PacketClassificationRuleIndex); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificClassifierParamLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8VendorSpecificClassifierParamLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificClassifierParam[1]: 0x%X ", + psfCSType->cCPacketClassificationRule.u8VendorSpecificClassifierParam[0]); +#ifdef VERSION_D5 + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPv6FlowLableLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8IPv6FlowLableLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPv6FlowLable[6]: 0x %02X %02X %02X %02X %02X %02X ", + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[0], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[1], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[2], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[3], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[4], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[5]); +#endif + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "bValid: 0x%02X", pstAddIndication->sfAuthorizedSet.bValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "AdmittedSet--->"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32SFID: 0x%X", pstAddIndication->sfAdmittedSet.u32SFID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16CID: 0x%X", pstAddIndication->sfAdmittedSet.u16CID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ServiceClassNameLength: 0x%X", + pstAddIndication->sfAdmittedSet.u8ServiceClassNameLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ServiceClassName: 0x %02X %02X %02X %02X %02X %02X", + pstAddIndication->sfAdmittedSet.u8ServiceClassName[0], + pstAddIndication->sfAdmittedSet.u8ServiceClassName[1], + pstAddIndication->sfAdmittedSet.u8ServiceClassName[2], + pstAddIndication->sfAdmittedSet.u8ServiceClassName[3], + pstAddIndication->sfAdmittedSet.u8ServiceClassName[4], + pstAddIndication->sfAdmittedSet.u8ServiceClassName[5]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8MBSService: 0x%02X", pstAddIndication->sfAdmittedSet.u8MBSService); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8QosParamSet: 0x%02X", pstAddIndication->sfAdmittedSet.u8QosParamSet); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8TrafficPriority: 0x%02X", pstAddIndication->sfAdmittedSet.u8TrafficPriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MaxTrafficBurst: 0x%X", pstAddIndication->sfAdmittedSet.u32MaxTrafficBurst); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MinReservedTrafficRate: 0x%X", + pstAddIndication->sfAdmittedSet.u32MinReservedTrafficRate); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificQoSParamLength: 0x%02X", + pstAddIndication->sfAdmittedSet.u8VendorSpecificQoSParamLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificQoSParam: 0x%02X", + pstAddIndication->sfAdmittedSet.u8VendorSpecificQoSParam[0]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ServiceFlowSchedulingType: 0x%02X", + pstAddIndication->sfAdmittedSet.u8ServiceFlowSchedulingType); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32ToleratedJitter: 0x%X", pstAddIndication->sfAdmittedSet.u32ToleratedJitter); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MaximumLatency: 0x%X", pstAddIndication->sfAdmittedSet.u32MaximumLatency); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8FixedLengthVSVariableLengthSDUIndicator: 0x%02X", + pstAddIndication->sfAdmittedSet.u8FixedLengthVSVariableLengthSDUIndicator); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8SDUSize: 0x%02X", pstAddIndication->sfAdmittedSet.u8SDUSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16TargetSAID: 0x%02X", pstAddIndication->sfAdmittedSet.u16TargetSAID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ARQEnable: 0x%02X", pstAddIndication->sfAdmittedSet.u8ARQEnable); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQWindowSize: 0x%X", pstAddIndication->sfAdmittedSet.u16ARQWindowSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQRetryTxTimeOut: 0x%X", pstAddIndication->sfAdmittedSet.u16ARQRetryTxTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQRetryRxTimeOut: 0x%X", pstAddIndication->sfAdmittedSet.u16ARQRetryRxTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQBlockLifeTime: 0x%X", pstAddIndication->sfAdmittedSet.u16ARQBlockLifeTime); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQSyncLossTimeOut: 0x%X", pstAddIndication->sfAdmittedSet.u16ARQSyncLossTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ARQDeliverInOrder: 0x%02X", pstAddIndication->sfAdmittedSet.u8ARQDeliverInOrder); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQRxPurgeTimeOut: 0x%X", pstAddIndication->sfAdmittedSet.u16ARQRxPurgeTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16ARQBlockSize: 0x%X", pstAddIndication->sfAdmittedSet.u16ARQBlockSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8CSSpecification: 0x%02X", pstAddIndication->sfAdmittedSet.u8CSSpecification); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8TypeOfDataDeliveryService: 0x%02X", + pstAddIndication->sfAdmittedSet.u8TypeOfDataDeliveryService); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16SDUInterArrivalTime: 0x%X", pstAddIndication->sfAdmittedSet.u16SDUInterArrivalTime); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16TimeBase: 0x%X", pstAddIndication->sfAdmittedSet.u16TimeBase); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8PagingPreference: 0x%X", pstAddIndication->sfAdmittedSet.u8PagingPreference); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8TrafficIndicationPreference: 0x%02X", + pstAddIndication->sfAdmittedSet.u8TrafficIndicationPreference); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " Total Classifiers Received: 0x%X", pstAddIndication->sfAdmittedSet.u8TotalClassifiers); + + nCurClassifierCnt = pstAddIndication->sfAdmittedSet.u8TotalClassifiers; + if (nCurClassifierCnt > MAX_CLASSIFIERS_IN_SF) + nCurClassifierCnt = MAX_CLASSIFIERS_IN_SF; + + for (nIndex = 0; nIndex < nCurClassifierCnt; nIndex++) { + stConvergenceSLTypes *psfCSType = NULL; + + psfCSType = &pstAddIndication->sfAdmittedSet.cConvergenceSLTypes[nIndex]; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " CCPacketClassificationRuleSI====>"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ClassifierRulePriority: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8ClassifierRulePriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPTypeOfServiceLength: 0x%02X", + psfCSType->cCPacketClassificationRule.u8IPTypeOfServiceLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPTypeOfService[3]: 0x%02X %02X %02X", + psfCSType->cCPacketClassificationRule.u8IPTypeOfService[0], + psfCSType->cCPacketClassificationRule.u8IPTypeOfService[1], + psfCSType->cCPacketClassificationRule.u8IPTypeOfService[2]); + for (uiLoopIndex = 0; uiLoopIndex < 1; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8Protocol: 0x%02X ", psfCSType->cCPacketClassificationRule.u8Protocol); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPMaskedSourceAddressLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8IPMaskedSourceAddressLength); + + for (uiLoopIndex = 0; uiLoopIndex < 32; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPMaskedSourceAddress[32]: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8IPMaskedSourceAddress[uiLoopIndex]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPDestinationAddressLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8IPDestinationAddressLength); + + for (uiLoopIndex = 0; uiLoopIndex < 32; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPDestinationAddress[32]: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8IPDestinationAddress[uiLoopIndex]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ProtocolSourcePortRangeLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRangeLength); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ProtocolSourcePortRange[4]: 0x %02X %02X %02X %02X ", + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[0], + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[1], + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[2], + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[3]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ProtocolDestPortRangeLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRangeLength); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ProtocolDestPortRange[4]: 0x %02X %02X %02X %02X ", + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[0], + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[1], + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[2], + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[3]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthernetDestMacAddressLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddressLength); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthernetDestMacAddress[6]: 0x %02X %02X %02X %02X %02X %02X", + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[0], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[1], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[2], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[3], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[4], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[5]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthernetSourceMACAddressLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddressLength); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthernetSourceMACAddress[6]: 0x %02X %02X %02X %02X %02X %02X", + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[0], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[1], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[2], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[3], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[4], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[5]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthertypeLength: 0x%02X ", psfCSType->cCPacketClassificationRule.u8EthertypeLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8Ethertype[3]: 0x%02X %02X %02X", + psfCSType->cCPacketClassificationRule.u8Ethertype[0], + psfCSType->cCPacketClassificationRule.u8Ethertype[1], + psfCSType->cCPacketClassificationRule.u8Ethertype[2]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16UserPriority: 0x%X ", psfCSType->cCPacketClassificationRule.u16UserPriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16VLANID: 0x%X ", psfCSType->cCPacketClassificationRule.u16VLANID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8AssociatedPHSI: 0x%02X ", psfCSType->cCPacketClassificationRule.u8AssociatedPHSI); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16PacketClassificationRuleIndex: 0x%X ", + psfCSType->cCPacketClassificationRule.u16PacketClassificationRuleIndex); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificClassifierParamLength: 0x%02X", + psfCSType->cCPacketClassificationRule.u8VendorSpecificClassifierParamLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificClassifierParam[1]: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8VendorSpecificClassifierParam[0]); +#ifdef VERSION_D5 + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPv6FlowLableLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8IPv6FlowLableLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPv6FlowLable[6]: 0x %02X %02X %02X %02X %02X %02X ", + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[0], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[1], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[2], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[3], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[4], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[5]); +#endif + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "bValid: 0x%X", pstAddIndication->sfAdmittedSet.bValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " ActiveSet--->"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32SFID: 0x%X", pstAddIndication->sfActiveSet.u32SFID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u16CID: 0x%X", pstAddIndication->sfActiveSet.u16CID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ServiceClassNameLength: 0x%X", pstAddIndication->sfActiveSet.u8ServiceClassNameLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ServiceClassName: 0x %02X %02X %02X %02X %02X %02X", + pstAddIndication->sfActiveSet.u8ServiceClassName[0], + pstAddIndication->sfActiveSet.u8ServiceClassName[1], + pstAddIndication->sfActiveSet.u8ServiceClassName[2], + pstAddIndication->sfActiveSet.u8ServiceClassName[3], + pstAddIndication->sfActiveSet.u8ServiceClassName[4], + pstAddIndication->sfActiveSet.u8ServiceClassName[5]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8MBSService: 0x%02X", pstAddIndication->sfActiveSet.u8MBSService); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8QosParamSet: 0x%02X", pstAddIndication->sfActiveSet.u8QosParamSet); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8TrafficPriority: 0x%02X", pstAddIndication->sfActiveSet.u8TrafficPriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MaxTrafficBurst: 0x%X", pstAddIndication->sfActiveSet.u32MaxTrafficBurst); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MinReservedTrafficRate: 0x%X", + pstAddIndication->sfActiveSet.u32MinReservedTrafficRate); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificQoSParamLength: 0x%02X", + pstAddIndication->sfActiveSet.u8VendorSpecificQoSParamLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8VendorSpecificQoSParam: 0x%02X", + pstAddIndication->sfActiveSet.u8VendorSpecificQoSParam[0]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8ServiceFlowSchedulingType: 0x%02X", + pstAddIndication->sfActiveSet.u8ServiceFlowSchedulingType); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32ToleratedJitter: 0x%X", pstAddIndication->sfActiveSet.u32ToleratedJitter); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u32MaximumLatency: 0x%X", pstAddIndication->sfActiveSet.u32MaximumLatency); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8FixedLengthVSVariableLengthSDUIndicator: 0x%02X", + pstAddIndication->sfActiveSet.u8FixedLengthVSVariableLengthSDUIndicator); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8SDUSize: 0x%X", pstAddIndication->sfActiveSet.u8SDUSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16TargetSAID: 0x%X", pstAddIndication->sfActiveSet.u16TargetSAID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8ARQEnable: 0x%X", pstAddIndication->sfActiveSet.u8ARQEnable); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16ARQWindowSize: 0x%X", pstAddIndication->sfActiveSet.u16ARQWindowSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16ARQRetryTxTimeOut: 0x%X", pstAddIndication->sfActiveSet.u16ARQRetryTxTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16ARQRetryRxTimeOut: 0x%X", pstAddIndication->sfActiveSet.u16ARQRetryRxTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16ARQBlockLifeTime: 0x%X", pstAddIndication->sfActiveSet.u16ARQBlockLifeTime); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16ARQSyncLossTimeOut: 0x%X", pstAddIndication->sfActiveSet.u16ARQSyncLossTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8ARQDeliverInOrder: 0x%X", pstAddIndication->sfActiveSet.u8ARQDeliverInOrder); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16ARQRxPurgeTimeOut: 0x%X", pstAddIndication->sfActiveSet.u16ARQRxPurgeTimeOut); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16ARQBlockSize: 0x%X", pstAddIndication->sfActiveSet.u16ARQBlockSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8CSSpecification: 0x%X", pstAddIndication->sfActiveSet.u8CSSpecification); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8TypeOfDataDeliveryService: 0x%X", + pstAddIndication->sfActiveSet.u8TypeOfDataDeliveryService); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16SDUInterArrivalTime: 0x%X", pstAddIndication->sfActiveSet.u16SDUInterArrivalTime); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16TimeBase: 0x%X", pstAddIndication->sfActiveSet.u16TimeBase); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8PagingPreference: 0x%X", pstAddIndication->sfActiveSet.u8PagingPreference); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8TrafficIndicationPreference: 0x%X", + pstAddIndication->sfActiveSet.u8TrafficIndicationPreference); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " Total Classifiers Received: 0x%X", pstAddIndication->sfActiveSet.u8TotalClassifiers); + + nCurClassifierCnt = pstAddIndication->sfActiveSet.u8TotalClassifiers; + if (nCurClassifierCnt > MAX_CLASSIFIERS_IN_SF) + nCurClassifierCnt = MAX_CLASSIFIERS_IN_SF; + + for (nIndex = 0; nIndex < nCurClassifierCnt; nIndex++) { + stConvergenceSLTypes *psfCSType = NULL; + + psfCSType = &pstAddIndication->sfActiveSet.cConvergenceSLTypes[nIndex]; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " CCPacketClassificationRuleSI====>"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8ClassifierRulePriority: 0x%X ", + psfCSType->cCPacketClassificationRule.u8ClassifierRulePriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8IPTypeOfServiceLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8IPTypeOfServiceLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8IPTypeOfService[3]: 0x%X ,0x%X ,0x%X ", + psfCSType->cCPacketClassificationRule.u8IPTypeOfService[0], + psfCSType->cCPacketClassificationRule.u8IPTypeOfService[1], + psfCSType->cCPacketClassificationRule.u8IPTypeOfService[2]); + + for (uiLoopIndex = 0; uiLoopIndex < 1; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8Protocol: 0x%X ", psfCSType->cCPacketClassificationRule.u8Protocol); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPMaskedSourceAddressLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8IPMaskedSourceAddressLength); + + for (uiLoopIndex = 0; uiLoopIndex < 32; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPMaskedSourceAddress[32]: 0x%X ", + psfCSType->cCPacketClassificationRule.u8IPMaskedSourceAddress[uiLoopIndex]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8IPDestinationAddressLength: 0x%02X ", + psfCSType->cCPacketClassificationRule.u8IPDestinationAddressLength); + + for (uiLoopIndex = 0; uiLoopIndex < 32; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8IPDestinationAddress[32]:0x%X ", + psfCSType->cCPacketClassificationRule.u8IPDestinationAddress[uiLoopIndex]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8ProtocolSourcePortRangeLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRangeLength); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8ProtocolSourcePortRange[4]: 0x%X ,0x%X ,0x%X ,0x%X ", + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[0], + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[1], + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[2], + psfCSType->cCPacketClassificationRule.u8ProtocolSourcePortRange[3]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8ProtocolDestPortRangeLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRangeLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8ProtocolDestPortRange[4]: 0x%X ,0x%X ,0x%X ,0x%X ", + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[0], + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[1], + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[2], + psfCSType->cCPacketClassificationRule.u8ProtocolDestPortRange[3]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8EthernetDestMacAddressLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddressLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8EthernetDestMacAddress[6]: 0x%X ,0x%X ,0x%X ,0x%X ,0x%X ,0x%X", + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[0], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[1], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[2], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[3], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[4], + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddress[5]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8EthernetSourceMACAddressLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8EthernetDestMacAddressLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, "u8EthernetSourceMACAddress[6]: 0x%X ,0x%X ,0x%X ,0x%X ,0x%X ,0x%X", + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[0], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[1], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[2], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[3], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[4], + psfCSType->cCPacketClassificationRule.u8EthernetSourceMACAddress[5]); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8EthertypeLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8EthertypeLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8Ethertype[3]: 0x%X ,0x%X ,0x%X ", + psfCSType->cCPacketClassificationRule.u8Ethertype[0], + psfCSType->cCPacketClassificationRule.u8Ethertype[1], + psfCSType->cCPacketClassificationRule.u8Ethertype[2]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16UserPriority: 0x%X ", + psfCSType->cCPacketClassificationRule.u16UserPriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16VLANID: 0x%X ", psfCSType->cCPacketClassificationRule.u16VLANID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8AssociatedPHSI: 0x%X ", psfCSType->cCPacketClassificationRule.u8AssociatedPHSI); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u16PacketClassificationRuleIndex:0x%X ", + psfCSType->cCPacketClassificationRule.u16PacketClassificationRuleIndex); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8VendorSpecificClassifierParamLength:0x%X ", + psfCSType->cCPacketClassificationRule.u8VendorSpecificClassifierParamLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8VendorSpecificClassifierParam[1]:0x%X ", + psfCSType->cCPacketClassificationRule.u8VendorSpecificClassifierParam[0]); +#ifdef VERSION_D5 + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8IPv6FlowLableLength: 0x%X ", + psfCSType->cCPacketClassificationRule.u8IPv6FlowLableLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " u8IPv6FlowLable[6]: 0x%X ,0x%X ,0x%X ,0x%X ,0x%X ,0x%X ", + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[0], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[1], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[2], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[3], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[4], + psfCSType->cCPacketClassificationRule.u8IPv6FlowLable[5]); +#endif + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_CONTROL, DBG_LVL_ALL, " bValid: 0x%X", pstAddIndication->sfActiveSet.bValid); +} + +static inline ULONG RestoreSFParam(PMINI_ADAPTER Adapter, ULONG ulAddrSFParamSet, PUCHAR pucDestBuffer) +{ + UINT nBytesToRead = sizeof(stServiceFlowParamSI); + + if (ulAddrSFParamSet == 0 || NULL == pucDestBuffer) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Got Param address as 0!!"); + return 0; + } + ulAddrSFParamSet = ntohl(ulAddrSFParamSet); + + /* Read out the SF Param Set At the indicated Location */ + if (rdm(Adapter, ulAddrSFParamSet, (PUCHAR)pucDestBuffer, nBytesToRead) < 0) + return STATUS_FAILURE; + + return 1; +} + +static ULONG StoreSFParam(PMINI_ADAPTER Adapter, PUCHAR pucSrcBuffer, ULONG ulAddrSFParamSet) +{ + UINT nBytesToWrite = sizeof(stServiceFlowParamSI); + int ret = 0; + + if (ulAddrSFParamSet == 0 || NULL == pucSrcBuffer) + return 0; + + ret = wrm(Adapter, ulAddrSFParamSet, (u8 *)pucSrcBuffer, nBytesToWrite); + if (ret < 0) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "%s:%d WRM failed", __func__, __LINE__); + return ret; + } + return 1; +} + +ULONG StoreCmControlResponseMessage(PMINI_ADAPTER Adapter, PVOID pvBuffer, UINT *puBufferLength) +{ + stLocalSFAddIndicationAlt *pstAddIndicationAlt = NULL; + stLocalSFAddIndication *pstAddIndication = NULL; + stLocalSFDeleteRequest *pstDeletionRequest; + UINT uiSearchRuleIndex; + ULONG ulSFID; + + pstAddIndicationAlt = (stLocalSFAddIndicationAlt *)(pvBuffer); + + /* + * In case of DSD Req By MS, we should immediately delete this SF so that + * we can stop the further classifying the pkt for this SF. + */ + if (pstAddIndicationAlt->u8Type == DSD_REQ) { + pstDeletionRequest = (stLocalSFDeleteRequest *)pvBuffer; + + ulSFID = ntohl(pstDeletionRequest->u32SFID); + uiSearchRuleIndex = SearchSfid(Adapter, ulSFID); + + if (uiSearchRuleIndex < NO_OF_QUEUES) { + deleteSFBySfid(Adapter, uiSearchRuleIndex); + Adapter->u32TotalDSD++; + } + return 1; + } + + if ((pstAddIndicationAlt->u8Type == DSD_RSP) || + (pstAddIndicationAlt->u8Type == DSD_ACK)) { + /* No Special handling send the message as it is */ + return 1; + } + /* For DSA_REQ, only up to "psfAuthorizedSet" parameter should be accessed by driver! */ + + pstAddIndication = kmalloc(sizeof(*pstAddIndication), GFP_KERNEL); + if (pstAddIndication == NULL) + return 0; + + /* AUTHORIZED SET */ + pstAddIndication->psfAuthorizedSet = (stServiceFlowParamSI *) + GetNextTargetBufferLocation(Adapter, pstAddIndicationAlt->u16TID); + if (!pstAddIndication->psfAuthorizedSet) { + kfree(pstAddIndication); + return 0; + } + + if (StoreSFParam(Adapter, (PUCHAR)&pstAddIndicationAlt->sfAuthorizedSet, + (ULONG)pstAddIndication->psfAuthorizedSet) != 1) { + kfree(pstAddIndication); + return 0; + } + + /* this can't possibly be right */ + pstAddIndication->psfAuthorizedSet = (stServiceFlowParamSI *)ntohl((ULONG)pstAddIndication->psfAuthorizedSet); + + if (pstAddIndicationAlt->u8Type == DSA_REQ) { + stLocalSFAddRequest AddRequest; + + AddRequest.u8Type = pstAddIndicationAlt->u8Type; + AddRequest.eConnectionDir = pstAddIndicationAlt->u8Direction; + AddRequest.u16TID = pstAddIndicationAlt->u16TID; + AddRequest.u16CID = pstAddIndicationAlt->u16CID; + AddRequest.u16VCID = pstAddIndicationAlt->u16VCID; + AddRequest.psfParameterSet = pstAddIndication->psfAuthorizedSet; + (*puBufferLength) = sizeof(stLocalSFAddRequest); + memcpy(pvBuffer, &AddRequest, sizeof(stLocalSFAddRequest)); + kfree(pstAddIndication); + return 1; + } + + /* Since it's not DSA_REQ, we can access all field in pstAddIndicationAlt */ + /* We need to extract the structure from the buffer and pack it differently */ + + pstAddIndication->u8Type = pstAddIndicationAlt->u8Type; + pstAddIndication->eConnectionDir = pstAddIndicationAlt->u8Direction; + pstAddIndication->u16TID = pstAddIndicationAlt->u16TID; + pstAddIndication->u16CID = pstAddIndicationAlt->u16CID; + pstAddIndication->u16VCID = pstAddIndicationAlt->u16VCID; + pstAddIndication->u8CC = pstAddIndicationAlt->u8CC; + + /* ADMITTED SET */ + pstAddIndication->psfAdmittedSet = (stServiceFlowParamSI *) + GetNextTargetBufferLocation(Adapter, pstAddIndicationAlt->u16TID); + if (!pstAddIndication->psfAdmittedSet) { + kfree(pstAddIndication); + return 0; + } + if (StoreSFParam(Adapter, (PUCHAR)&pstAddIndicationAlt->sfAdmittedSet, (ULONG)pstAddIndication->psfAdmittedSet) != 1) { + kfree(pstAddIndication); + return 0; + } + + pstAddIndication->psfAdmittedSet = (stServiceFlowParamSI *)ntohl((ULONG)pstAddIndication->psfAdmittedSet); + + /* ACTIVE SET */ + pstAddIndication->psfActiveSet = (stServiceFlowParamSI *) + GetNextTargetBufferLocation(Adapter, pstAddIndicationAlt->u16TID); + if (!pstAddIndication->psfActiveSet) { + kfree(pstAddIndication); + return 0; + } + if (StoreSFParam(Adapter, (PUCHAR)&pstAddIndicationAlt->sfActiveSet, (ULONG)pstAddIndication->psfActiveSet) != 1) { + kfree(pstAddIndication); + return 0; + } + + pstAddIndication->psfActiveSet = (stServiceFlowParamSI *)ntohl((ULONG)pstAddIndication->psfActiveSet); + + (*puBufferLength) = sizeof(stLocalSFAddIndication); + *(stLocalSFAddIndication *)pvBuffer = *pstAddIndication; + kfree(pstAddIndication); + return 1; +} + +static inline stLocalSFAddIndicationAlt +*RestoreCmControlResponseMessage(register PMINI_ADAPTER Adapter, register PVOID pvBuffer) +{ + ULONG ulStatus = 0; + stLocalSFAddIndication *pstAddIndication = NULL; + stLocalSFAddIndicationAlt *pstAddIndicationDest = NULL; + + pstAddIndication = (stLocalSFAddIndication *)(pvBuffer); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "=====>"); + if ((pstAddIndication->u8Type == DSD_REQ) || + (pstAddIndication->u8Type == DSD_RSP) || + (pstAddIndication->u8Type == DSD_ACK)) + return (stLocalSFAddIndicationAlt *)pvBuffer; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Inside RestoreCmControlResponseMessage "); + /* + * Need to Allocate memory to contain the SUPER Large structures + * Our driver can't create these structures on Stack :( + */ + pstAddIndicationDest = kmalloc(sizeof(stLocalSFAddIndicationAlt), GFP_KERNEL); + + if (pstAddIndicationDest) { + memset(pstAddIndicationDest, 0, sizeof(stLocalSFAddIndicationAlt)); + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Failed to allocate memory for SF Add Indication Structure "); + return NULL; + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "AddIndication-u8Type : 0x%X", pstAddIndication->u8Type); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "AddIndication-u8Direction : 0x%X", pstAddIndication->eConnectionDir); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "AddIndication-u8TID : 0x%X", ntohs(pstAddIndication->u16TID)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "AddIndication-u8CID : 0x%X", ntohs(pstAddIndication->u16CID)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "AddIndication-u16VCID : 0x%X", ntohs(pstAddIndication->u16VCID)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "AddIndication-autorized set loc : %p", pstAddIndication->psfAuthorizedSet); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "AddIndication-admitted set loc : %p", pstAddIndication->psfAdmittedSet); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "AddIndication-Active set loc : %p", pstAddIndication->psfActiveSet); + + pstAddIndicationDest->u8Type = pstAddIndication->u8Type; + pstAddIndicationDest->u8Direction = pstAddIndication->eConnectionDir; + pstAddIndicationDest->u16TID = pstAddIndication->u16TID; + pstAddIndicationDest->u16CID = pstAddIndication->u16CID; + pstAddIndicationDest->u16VCID = pstAddIndication->u16VCID; + pstAddIndicationDest->u8CC = pstAddIndication->u8CC; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Restoring Active Set "); + ulStatus = RestoreSFParam(Adapter, (ULONG)pstAddIndication->psfActiveSet, (PUCHAR)&pstAddIndicationDest->sfActiveSet); + if (ulStatus != 1) + goto failed_restore_sf_param; + + if (pstAddIndicationDest->sfActiveSet.u8TotalClassifiers > MAX_CLASSIFIERS_IN_SF) + pstAddIndicationDest->sfActiveSet.u8TotalClassifiers = MAX_CLASSIFIERS_IN_SF; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Restoring Admitted Set "); + ulStatus = RestoreSFParam(Adapter, (ULONG)pstAddIndication->psfAdmittedSet, (PUCHAR)&pstAddIndicationDest->sfAdmittedSet); + if (ulStatus != 1) + goto failed_restore_sf_param; + + if (pstAddIndicationDest->sfAdmittedSet.u8TotalClassifiers > MAX_CLASSIFIERS_IN_SF) + pstAddIndicationDest->sfAdmittedSet.u8TotalClassifiers = MAX_CLASSIFIERS_IN_SF; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Restoring Authorized Set "); + ulStatus = RestoreSFParam(Adapter, (ULONG)pstAddIndication->psfAuthorizedSet, (PUCHAR)&pstAddIndicationDest->sfAuthorizedSet); + if (ulStatus != 1) + goto failed_restore_sf_param; + + if (pstAddIndicationDest->sfAuthorizedSet.u8TotalClassifiers > MAX_CLASSIFIERS_IN_SF) + pstAddIndicationDest->sfAuthorizedSet.u8TotalClassifiers = MAX_CLASSIFIERS_IN_SF; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Dumping the whole raw packet"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "============================================================"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, " pstAddIndicationDest->sfActiveSet size %zx %p", sizeof(*pstAddIndicationDest), pstAddIndicationDest); + /* BCM_DEBUG_PRINT_BUFFER(Adapter,DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, (unsigned char *)pstAddIndicationDest, sizeof(*pstAddIndicationDest)); */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "============================================================"); + return pstAddIndicationDest; +failed_restore_sf_param: + kfree(pstAddIndicationDest); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "<====="); + return NULL; +} + +ULONG SetUpTargetDsxBuffers(PMINI_ADAPTER Adapter) +{ + ULONG ulTargetDsxBuffersBase = 0; + ULONG ulCntTargetBuffers; + ULONG i; + int Status; + + if (!Adapter) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Adapter was NULL!!!"); + return 0; + } + + if (Adapter->astTargetDsxBuffer[0].ulTargetDsxBuffer) + return 1; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Size of Each DSX Buffer(Also size of ServiceFlowParamSI): %zx ", sizeof(stServiceFlowParamSI)); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Reading DSX buffer From Target location %x ", DSX_MESSAGE_EXCHANGE_BUFFER); + + Status = rdmalt(Adapter, DSX_MESSAGE_EXCHANGE_BUFFER, (PUINT)&ulTargetDsxBuffersBase, sizeof(UINT)); + if (Status < 0) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "RDM failed!!"); + return 0; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Base Address Of DSX Target Buffer : 0x%lx", ulTargetDsxBuffersBase); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Tgt Buffer is Now %lx :", ulTargetDsxBuffersBase); + ulCntTargetBuffers = DSX_MESSAGE_EXCHANGE_BUFFER_SIZE / sizeof(stServiceFlowParamSI); + + Adapter->ulTotalTargetBuffersAvailable = + ulCntTargetBuffers > MAX_TARGET_DSX_BUFFERS ? + MAX_TARGET_DSX_BUFFERS : ulCntTargetBuffers; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, " Total Target DSX Buffer setup %lx ", Adapter->ulTotalTargetBuffersAvailable); + + for (i = 0; i < Adapter->ulTotalTargetBuffersAvailable; i++) { + Adapter->astTargetDsxBuffer[i].ulTargetDsxBuffer = ulTargetDsxBuffersBase; + Adapter->astTargetDsxBuffer[i].valid = 1; + Adapter->astTargetDsxBuffer[i].tid = 0; + ulTargetDsxBuffersBase += sizeof(stServiceFlowParamSI); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, " Target DSX Buffer %lx setup at 0x%lx", + i, Adapter->astTargetDsxBuffer[i].ulTargetDsxBuffer); + } + Adapter->ulCurrentTargetBuffer = 0; + Adapter->ulFreeTargetBufferCnt = Adapter->ulTotalTargetBuffersAvailable; + return 1; +} + +static ULONG GetNextTargetBufferLocation(PMINI_ADAPTER Adapter, B_UINT16 tid) +{ + ULONG ulTargetDSXBufferAddress; + ULONG ulTargetDsxBufferIndexToUse, ulMaxTry; + + if ((Adapter->ulTotalTargetBuffersAvailable == 0) || (Adapter->ulFreeTargetBufferCnt == 0)) { + ClearTargetDSXBuffer(Adapter, tid, FALSE); + return 0; + } + + ulTargetDsxBufferIndexToUse = Adapter->ulCurrentTargetBuffer; + ulMaxTry = Adapter->ulTotalTargetBuffersAvailable; + while ((ulMaxTry) && (Adapter->astTargetDsxBuffer[ulTargetDsxBufferIndexToUse].valid != 1)) { + ulTargetDsxBufferIndexToUse = (ulTargetDsxBufferIndexToUse+1) % Adapter->ulTotalTargetBuffersAvailable; + ulMaxTry--; + } + + if (ulMaxTry == 0) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "\n GetNextTargetBufferLocation : Error No Free Target DSX Buffers FreeCnt : %lx ", Adapter->ulFreeTargetBufferCnt); + ClearTargetDSXBuffer(Adapter, tid, FALSE); + return 0; + } + + ulTargetDSXBufferAddress = Adapter->astTargetDsxBuffer[ulTargetDsxBufferIndexToUse].ulTargetDsxBuffer; + Adapter->astTargetDsxBuffer[ulTargetDsxBufferIndexToUse].valid = 0; + Adapter->astTargetDsxBuffer[ulTargetDsxBufferIndexToUse].tid = tid; + Adapter->ulFreeTargetBufferCnt--; + ulTargetDsxBufferIndexToUse = (ulTargetDsxBufferIndexToUse+1)%Adapter->ulTotalTargetBuffersAvailable; + Adapter->ulCurrentTargetBuffer = ulTargetDsxBufferIndexToUse; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "GetNextTargetBufferLocation :Returning address %lx tid %d\n", ulTargetDSXBufferAddress, tid); + + return ulTargetDSXBufferAddress; +} + +int AllocAdapterDsxBuffer(PMINI_ADAPTER Adapter) +{ + /* + * Need to Allocate memory to contain the SUPER Large structures + * Our driver can't create these structures on Stack + */ + Adapter->caDsxReqResp = kmalloc(sizeof(stLocalSFAddIndicationAlt)+LEADER_SIZE, GFP_KERNEL); + if (!Adapter->caDsxReqResp) + return -ENOMEM; + + return 0; +} + +int FreeAdapterDsxBuffer(PMINI_ADAPTER Adapter) +{ + kfree(Adapter->caDsxReqResp); + return 0; +} + +/* + * @ingroup ctrl_pkt_functions + * This routinue would process the Control responses + * for the Connection Management. + * @return - Queue index for the free SFID else returns Invalid Index. + */ +BOOLEAN CmControlResponseMessage(PMINI_ADAPTER Adapter, /* u16TID, FALSE); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Error in restoring Service Flow param structure from DSx message"); + return FALSE; + } + + DumpCmControlPacket(pstAddIndication); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "====>"); + pLeader = (PLEADER)Adapter->caDsxReqResp; + + pLeader->Status = CM_CONTROL_NEWDSX_MULTICLASSIFIER_REQ; + pLeader->Vcid = 0; + + ClearTargetDSXBuffer(Adapter, pstAddIndication->u16TID, FALSE); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "### TID RECEIVED %d\n", pstAddIndication->u16TID); + switch (pstAddIndication->u8Type) { + case DSA_REQ: + { + pLeader->PLength = sizeof(stLocalSFAddIndicationAlt); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Sending DSA Response....\n"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SENDING DSA RESPONSE TO MAC %d", pLeader->PLength); + *((stLocalSFAddIndicationAlt *)&(Adapter->caDsxReqResp[LEADER_SIZE])) + = *pstAddIndication; + ((stLocalSFAddIndicationAlt *)&(Adapter->caDsxReqResp[LEADER_SIZE]))->u8Type = DSA_RSP; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, " VCID = %x", ntohs(pstAddIndication->u16VCID)); + CopyBufferToControlPacket(Adapter, (PVOID)Adapter->caDsxReqResp); + kfree(pstAddIndication); + } + break; + case DSA_RSP: + { + pLeader->PLength = sizeof(stLocalSFAddIndicationAlt); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SENDING DSA ACK TO MAC %d", + pLeader->PLength); + *((stLocalSFAddIndicationAlt *)&(Adapter->caDsxReqResp[LEADER_SIZE])) + = *pstAddIndication; + ((stLocalSFAddIndicationAlt *)&(Adapter->caDsxReqResp[LEADER_SIZE]))->u8Type = DSA_ACK; + + } /* no break here..we should go down. */ + case DSA_ACK: + { + UINT uiSearchRuleIndex = 0; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "VCID:0x%X", + ntohs(pstAddIndication->u16VCID)); + uiSearchRuleIndex = SearchFreeSfid(Adapter); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "uiSearchRuleIndex:0x%X ", + uiSearchRuleIndex); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Direction:0x%X ", + pstAddIndication->u8Direction); + if ((uiSearchRuleIndex < NO_OF_QUEUES)) { + Adapter->PackInfo[uiSearchRuleIndex].ucDirection = + pstAddIndication->u8Direction; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "bValid:0x%X ", + pstAddIndication->sfActiveSet.bValid); + if (pstAddIndication->sfActiveSet.bValid == TRUE) + Adapter->PackInfo[uiSearchRuleIndex].bActiveSet = TRUE; + + if (pstAddIndication->sfAuthorizedSet.bValid == TRUE) + Adapter->PackInfo[uiSearchRuleIndex].bAuthorizedSet = TRUE; + + if (pstAddIndication->sfAdmittedSet.bValid == TRUE) + Adapter->PackInfo[uiSearchRuleIndex].bAdmittedSet = TRUE; + + if (pstAddIndication->sfActiveSet.bValid == FALSE) { + Adapter->PackInfo[uiSearchRuleIndex].bActive = FALSE; + Adapter->PackInfo[uiSearchRuleIndex].bActivateRequestSent = FALSE; + if (pstAddIndication->sfAdmittedSet.bValid) + psfLocalSet = &pstAddIndication->sfAdmittedSet; + else if (pstAddIndication->sfAuthorizedSet.bValid) + psfLocalSet = &pstAddIndication->sfAuthorizedSet; + } else { + psfLocalSet = &pstAddIndication->sfActiveSet; + Adapter->PackInfo[uiSearchRuleIndex].bActive = TRUE; + } + + if (!psfLocalSet) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "No set is valid\n"); + Adapter->PackInfo[uiSearchRuleIndex].bActive = FALSE; + Adapter->PackInfo[uiSearchRuleIndex].bValid = FALSE; + Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value = 0; + kfree(pstAddIndication); + } else if (psfLocalSet->bValid && (pstAddIndication->u8CC == 0)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "DSA ACK"); + Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value = ntohs(pstAddIndication->u16VCID); + Adapter->PackInfo[uiSearchRuleIndex].usCID = ntohs(pstAddIndication->u16CID); + + if (UPLINK_DIR == pstAddIndication->u8Direction) + atomic_set(&Adapter->PackInfo[uiSearchRuleIndex].uiPerSFTxResourceCount, DEFAULT_PERSFCOUNT); + + CopyToAdapter(Adapter, psfLocalSet, uiSearchRuleIndex, DSA_ACK, pstAddIndication); + /* don't free pstAddIndication */ + + /* Inside CopyToAdapter, Sorting of all the SFs take place. + * Hence any access to the newly added SF through uiSearchRuleIndex is invalid. + * SHOULD BE STRICTLY AVOIDED. + */ + /* *(PULONG)(((PUCHAR)pvBuffer)+1)=psfLocalSet->u32SFID; */ + memcpy((((PUCHAR)pvBuffer)+1), &psfLocalSet->u32SFID, 4); + + if (pstAddIndication->sfActiveSet.bValid == TRUE) { + if (UPLINK_DIR == pstAddIndication->u8Direction) { + if (!Adapter->LinkUpStatus) { + netif_carrier_on(Adapter->dev); + netif_start_queue(Adapter->dev); + Adapter->LinkUpStatus = 1; + if (netif_msg_link(Adapter)) + pr_info(PFX "%s: link up\n", Adapter->dev->name); + atomic_set(&Adapter->TxPktAvail, 1); + wake_up(&Adapter->tx_packet_wait_queue); + Adapter->liTimeSinceLastNetEntry = get_seconds(); + } + } + } + } else { + Adapter->PackInfo[uiSearchRuleIndex].bActive = FALSE; + Adapter->PackInfo[uiSearchRuleIndex].bValid = FALSE; + Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value = 0; + kfree(pstAddIndication); + } + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "DSA ACK did not get valid SFID"); + kfree(pstAddIndication); + return FALSE; + } + } + break; + case DSC_REQ: + { + pLeader->PLength = sizeof(stLocalSFChangeIndicationAlt); + pstChangeIndication = (stLocalSFChangeIndicationAlt *)pstAddIndication; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SENDING DSC RESPONSE TO MAC %d", pLeader->PLength); + + *((stLocalSFChangeIndicationAlt *)&(Adapter->caDsxReqResp[LEADER_SIZE])) = *pstChangeIndication; + ((stLocalSFChangeIndicationAlt *)&(Adapter->caDsxReqResp[LEADER_SIZE]))->u8Type = DSC_RSP; + + CopyBufferToControlPacket(Adapter, (PVOID)Adapter->caDsxReqResp); + kfree(pstAddIndication); + } + break; + case DSC_RSP: + { + pLeader->PLength = sizeof(stLocalSFChangeIndicationAlt); + pstChangeIndication = (stLocalSFChangeIndicationAlt *)pstAddIndication; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SENDING DSC ACK TO MAC %d", pLeader->PLength); + *((stLocalSFChangeIndicationAlt *)&(Adapter->caDsxReqResp[LEADER_SIZE])) = *pstChangeIndication; + ((stLocalSFChangeIndicationAlt *)&(Adapter->caDsxReqResp[LEADER_SIZE]))->u8Type = DSC_ACK; + } + case DSC_ACK: + { + UINT uiSearchRuleIndex = 0; + + pstChangeIndication = (stLocalSFChangeIndicationAlt *)pstAddIndication; + uiSearchRuleIndex = SearchSfid(Adapter, ntohl(pstChangeIndication->sfActiveSet.u32SFID)); + if (uiSearchRuleIndex > NO_OF_QUEUES-1) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "SF doesn't exist for which DSC_ACK is received"); + + if ((uiSearchRuleIndex < NO_OF_QUEUES)) { + Adapter->PackInfo[uiSearchRuleIndex].ucDirection = pstChangeIndication->u8Direction; + if (pstChangeIndication->sfActiveSet.bValid == TRUE) + Adapter->PackInfo[uiSearchRuleIndex].bActiveSet = TRUE; + + if (pstChangeIndication->sfAuthorizedSet.bValid == TRUE) + Adapter->PackInfo[uiSearchRuleIndex].bAuthorizedSet = TRUE; + + if (pstChangeIndication->sfAdmittedSet.bValid == TRUE) + Adapter->PackInfo[uiSearchRuleIndex].bAdmittedSet = TRUE; + + if (pstChangeIndication->sfActiveSet.bValid == FALSE) { + Adapter->PackInfo[uiSearchRuleIndex].bActive = FALSE; + Adapter->PackInfo[uiSearchRuleIndex].bActivateRequestSent = FALSE; + + if (pstChangeIndication->sfAdmittedSet.bValid) + psfLocalSet = &pstChangeIndication->sfAdmittedSet; + else if (pstChangeIndication->sfAuthorizedSet.bValid) + psfLocalSet = &pstChangeIndication->sfAuthorizedSet; + } else { + psfLocalSet = &pstChangeIndication->sfActiveSet; + Adapter->PackInfo[uiSearchRuleIndex].bActive = TRUE; + } + + if (!psfLocalSet) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "No set is valid\n"); + Adapter->PackInfo[uiSearchRuleIndex].bActive = FALSE; + Adapter->PackInfo[uiSearchRuleIndex].bValid = FALSE; + Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value = 0; + kfree(pstAddIndication); + } else if (psfLocalSet->bValid && (pstChangeIndication->u8CC == 0)) { + Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value = ntohs(pstChangeIndication->u16VCID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "CC field is %d bvalid = %d\n", + pstChangeIndication->u8CC, psfLocalSet->bValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "VCID= %d\n", ntohs(pstChangeIndication->u16VCID)); + Adapter->PackInfo[uiSearchRuleIndex].usCID = ntohs(pstChangeIndication->u16CID); + CopyToAdapter(Adapter, psfLocalSet, uiSearchRuleIndex, DSC_ACK, pstAddIndication); + + *(PULONG)(((PUCHAR)pvBuffer)+1) = psfLocalSet->u32SFID; + } else if (pstChangeIndication->u8CC == 6) { + deleteSFBySfid(Adapter, uiSearchRuleIndex); + kfree(pstAddIndication); + } + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "DSC ACK did not get valid SFID"); + kfree(pstAddIndication); + return FALSE; + } + } + break; + case DSD_REQ: + { + UINT uiSearchRuleIndex; + ULONG ulSFID; + + pLeader->PLength = sizeof(stLocalSFDeleteIndication); + *((stLocalSFDeleteIndication *)&(Adapter->caDsxReqResp[LEADER_SIZE])) = *((stLocalSFDeleteIndication *)pstAddIndication); + + ulSFID = ntohl(((stLocalSFDeleteIndication *)pstAddIndication)->u32SFID); + uiSearchRuleIndex = SearchSfid(Adapter, ulSFID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "DSD - Removing connection %x", uiSearchRuleIndex); + + if (uiSearchRuleIndex < NO_OF_QUEUES) { + /* Delete All Classifiers Associated with this SFID */ + deleteSFBySfid(Adapter, uiSearchRuleIndex); + Adapter->u32TotalDSD++; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SENDING DSD RESPONSE TO MAC"); + ((stLocalSFDeleteIndication *)&(Adapter->caDsxReqResp[LEADER_SIZE]))->u8Type = DSD_RSP; + CopyBufferToControlPacket(Adapter, (PVOID)Adapter->caDsxReqResp); + } + case DSD_RSP: + { + /* Do nothing as SF has already got Deleted */ + } + break; + case DSD_ACK: + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "DSD ACK Rcd, let App handle it\n"); + break; + default: + kfree(pstAddIndication); + return FALSE; + } + return TRUE; +} + +int get_dsx_sf_data_to_application(PMINI_ADAPTER Adapter, UINT uiSFId, void __user *user_buffer) +{ + int status = 0; + struct _packet_info *psSfInfo = NULL; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "status =%d", status); + status = SearchSfid(Adapter, uiSFId); + if (status >= NO_OF_QUEUES) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SFID %d not present in queue !!!", uiSFId); + return -EINVAL; + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "status =%d", status); + psSfInfo = &Adapter->PackInfo[status]; + if (psSfInfo->pstSFIndication && copy_to_user(user_buffer, + psSfInfo->pstSFIndication, sizeof(stLocalSFAddIndicationAlt))) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "copy to user failed SFID %d, present in queue !!!", uiSFId); + status = -EFAULT; + return status; + } + return STATUS_SUCCESS; +} + +VOID OverrideServiceFlowParams(PMINI_ADAPTER Adapter, PUINT puiBuffer) +{ + B_UINT32 u32NumofSFsinMsg = ntohl(*(puiBuffer + 1)); + stIM_SFHostNotify *pHostInfo = NULL; + UINT uiSearchRuleIndex = 0; + ULONG ulSFID = 0; + + puiBuffer += 2; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "u32NumofSFsinMsg: 0x%x\n", u32NumofSFsinMsg); + + while (u32NumofSFsinMsg != 0 && u32NumofSFsinMsg < NO_OF_QUEUES) { + u32NumofSFsinMsg--; + pHostInfo = (stIM_SFHostNotify *)puiBuffer; + puiBuffer = (PUINT)(pHostInfo + 1); + + ulSFID = ntohl(pHostInfo->SFID); + uiSearchRuleIndex = SearchSfid(Adapter, ulSFID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "SFID: 0x%lx\n", ulSFID); + + if (uiSearchRuleIndex >= NO_OF_QUEUES || uiSearchRuleIndex == HiPriority) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "The SFID <%lx> doesn't exist in host entry or is Invalid\n", ulSFID); + continue; + } + + if (pHostInfo->RetainSF == FALSE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "Going to Delete SF"); + deleteSFBySfid(Adapter, uiSearchRuleIndex); + } else { + Adapter->PackInfo[uiSearchRuleIndex].usVCID_Value = ntohs(pHostInfo->VCID); + Adapter->PackInfo[uiSearchRuleIndex].usCID = ntohs(pHostInfo->newCID); + Adapter->PackInfo[uiSearchRuleIndex].bActive = FALSE; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, DBG_LVL_ALL, "pHostInfo->QoSParamSet: 0x%x\n", pHostInfo->QoSParamSet); + + if (pHostInfo->QoSParamSet & 0x1) + Adapter->PackInfo[uiSearchRuleIndex].bAuthorizedSet = TRUE; + if (pHostInfo->QoSParamSet & 0x2) + Adapter->PackInfo[uiSearchRuleIndex].bAdmittedSet = TRUE; + if (pHostInfo->QoSParamSet & 0x4) { + Adapter->PackInfo[uiSearchRuleIndex].bActiveSet = TRUE; + Adapter->PackInfo[uiSearchRuleIndex].bActive = TRUE; + } + } + } +} diff --git a/drivers/staging/bcm/CmHost.h b/drivers/staging/bcm/CmHost.h new file mode 100644 index 00000000..8f689769 --- /dev/null +++ b/drivers/staging/bcm/CmHost.h @@ -0,0 +1,163 @@ +/// ************************************************************************** +/// (c) Beceem Communications Inc. +/// All Rights Reserved +/// +/// \file : CmHost.h +/// \author : Rajeev Tirumala +/// \date : September 8 , 2006 +/// \brief : Definitions for Connection Management Requests structure +/// which we will use to setup our connection structures.Its high +/// time we had a header file for CmHost.cpp to isolate the way +/// f/w sends DSx messages and the way we interpret them in code. +/// Revision History +/// +/// Date Author Version Description +/// 08-Sep-06 Rajeev 0.1 Created +/// ************************************************************************** +#ifndef _CM_HOST_H +#define _CM_HOST_H + +#pragma once +#pragma pack (push,4) + +#define DSX_MESSAGE_EXCHANGE_BUFFER 0xBF60AC84 // This contains the pointer +#define DSX_MESSAGE_EXCHANGE_BUFFER_SIZE 72000 // 24 K Bytes + +/// \brief structure stLocalSFAddRequest +typedef struct stLocalSFAddRequestAlt{ + B_UINT8 u8Type; + B_UINT8 u8Direction; + + B_UINT16 u16TID; + /// \brief 16bitCID + B_UINT16 u16CID; + /// \brief 16bitVCID + B_UINT16 u16VCID; + + + /// \brief structure ParameterSet + stServiceFlowParamSI sfParameterSet; + + //USE_MEMORY_MANAGER(); +}stLocalSFAddRequestAlt; + +/// \brief structure stLocalSFAddIndication +typedef struct stLocalSFAddIndicationAlt{ + B_UINT8 u8Type; + B_UINT8 u8Direction; + B_UINT16 u16TID; + /// \brief 16bitCID + B_UINT16 u16CID; + /// \brief 16bitVCID + B_UINT16 u16VCID; + /// \brief structure AuthorizedSet + stServiceFlowParamSI sfAuthorizedSet; + /// \brief structure AdmittedSet + stServiceFlowParamSI sfAdmittedSet; + /// \brief structure ActiveSet + stServiceFlowParamSI sfActiveSet; + + B_UINT8 u8CC; /**< Confirmation Code*/ + B_UINT8 u8Padd; /**< 8-bit Padding */ + B_UINT16 u16Padd; /**< 16 bit Padding */ +// USE_MEMORY_MANAGER(); +}stLocalSFAddIndicationAlt; + +/// \brief structure stLocalSFAddConfirmation +typedef struct stLocalSFAddConfirmationAlt{ + B_UINT8 u8Type; + B_UINT8 u8Direction; + B_UINT16 u16TID; + /// \brief 16bitCID + B_UINT16 u16CID; + /// \brief 16bitVCID + B_UINT16 u16VCID; + /// \brief structure AuthorizedSet + stServiceFlowParamSI sfAuthorizedSet; + /// \brief structure AdmittedSet + stServiceFlowParamSI sfAdmittedSet; + /// \brief structure ActiveSet + stServiceFlowParamSI sfActiveSet; +}stLocalSFAddConfirmationAlt; + + +/// \brief structure stLocalSFChangeRequest +typedef struct stLocalSFChangeRequestAlt{ + B_UINT8 u8Type; + B_UINT8 u8Direction; + B_UINT16 u16TID; + /// \brief 16bitCID + B_UINT16 u16CID; + /// \brief 16bitVCID + B_UINT16 u16VCID; + /* + //Pointer location at which following Service Flow param Structure can be read + //from the target. We get only the address location and we need to read out the + //entire SF param structure at the given location on target + */ + /// \brief structure AuthorizedSet + stServiceFlowParamSI sfAuthorizedSet; + /// \brief structure AdmittedSet + stServiceFlowParamSI sfAdmittedSet; + /// \brief structure ParameterSet + stServiceFlowParamSI sfActiveSet; + + B_UINT8 u8CC; /**< Confirmation Code*/ + B_UINT8 u8Padd; /**< 8-bit Padding */ + B_UINT16 u16Padd; /**< 16 bit */ + +}stLocalSFChangeRequestAlt; + +/// \brief structure stLocalSFChangeConfirmation +typedef struct stLocalSFChangeConfirmationAlt{ + B_UINT8 u8Type; + B_UINT8 u8Direction; + B_UINT16 u16TID; + /// \brief 16bitCID + B_UINT16 u16CID; + /// \brief 16bitVCID + B_UINT16 u16VCID; + /// \brief structure AuthorizedSet + stServiceFlowParamSI sfAuthorizedSet; + /// \brief structure AdmittedSet + stServiceFlowParamSI sfAdmittedSet; + /// \brief structure ActiveSet + stServiceFlowParamSI sfActiveSet; + +}stLocalSFChangeConfirmationAlt; + +/// \brief structure stLocalSFChangeIndication +typedef struct stLocalSFChangeIndicationAlt{ + B_UINT8 u8Type; + B_UINT8 u8Direction; + B_UINT16 u16TID; + /// \brief 16bitCID + B_UINT16 u16CID; + /// \brief 16bitVCID + B_UINT16 u16VCID; + /// \brief structure AuthorizedSet + stServiceFlowParamSI sfAuthorizedSet; + /// \brief structure AdmittedSet + stServiceFlowParamSI sfAdmittedSet; + /// \brief structure ActiveSet + stServiceFlowParamSI sfActiveSet; + + B_UINT8 u8CC; /**< Confirmation Code*/ + B_UINT8 u8Padd; /**< 8-bit Padding */ + B_UINT16 u16Padd; /**< 16 bit */ + +}stLocalSFChangeIndicationAlt; + +ULONG StoreCmControlResponseMessage(PMINI_ADAPTER Adapter,PVOID pvBuffer,UINT *puBufferLength); + +INT AllocAdapterDsxBuffer(PMINI_ADAPTER Adapter); + +INT FreeAdapterDsxBuffer(PMINI_ADAPTER Adapter); +ULONG SetUpTargetDsxBuffers(PMINI_ADAPTER Adapter); + +BOOLEAN CmControlResponseMessage(PMINI_ADAPTER Adapter,PVOID pvBuffer); + + +#pragma pack (pop) + +#endif diff --git a/drivers/staging/bcm/DDRInit.c b/drivers/staging/bcm/DDRInit.c new file mode 100644 index 00000000..1c7db81a --- /dev/null +++ b/drivers/staging/bcm/DDRInit.c @@ -0,0 +1,1289 @@ +#include "headers.h" + + + +#define DDR_DUMP_INTERNAL_DEVICE_MEMORY 0xBFC02B00 +#define MIPS_CLOCK_REG 0x0f000820 + + //DDR INIT-133Mhz +#define T3_SKIP_CLOCK_PROGRAM_DUMP_133MHZ 12 //index for 0x0F007000 +static DDR_SET_NODE asT3_DDRSetting133MHz[]= {// # DPLL Clock Setting + {0x0F000800,0x00007212}, + {0x0f000820,0x07F13FFF}, + {0x0f000810,0x00000F95}, + {0x0f000860,0x00000000}, + {0x0f000880,0x000003DD}, + // Changed source for X-bar and MIPS clock to APLL + {0x0f000840,0x0FFF1B00}, + {0x0f000870,0x00000002}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0F00a084,0x1Cffffff}, + {0x0F00a080,0x1C000000}, + {0x0F00a04C,0x0000000C}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01010100}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020001},// POP - 0x00020001 Normal 0x01020001 + {0x0F007020,0x04030107}, //Normal - 0x04030107 POP - 0x05030107 + {0x0F007024,0x02000007}, + {0x0F007028,0x02020202}, + {0x0F00702c,0x0206060a},//ROB- 0x0205050a,//0x0206060a + {0x0F007030,0x05000000}, + {0x0F007034,0x00000003}, + {0x0F007038,0x110a0200},//ROB - 0x110a0200,//0x180a0200,// 0x1f0a0200 + {0x0F00703C,0x02101010},//ROB - 0x02101010,//0x02101018}, + {0x0F007040,0x45751200},//ROB - 0x45751200,//0x450f1200}, + {0x0F007044,0x110a0d00},//ROB - 0x110a0d00//0x111f0d00 + {0x0F007048,0x081b0306}, + {0x0F00704c,0x00000000}, + {0x0F007050,0x0000001c}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x0010246c}, + {0x0F007064,0x00000010}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00007000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + //# Enable BW improvement within memory controller + {0x0F007094,0x00000104}, + //# Enable 2 ports within X-bar + {0x0F00A000,0x00000016}, + //# Enable start bit within memory controller + {0x0F007018,0x01010000} + }; +//80Mhz +#define T3_SKIP_CLOCK_PROGRAM_DUMP_80MHZ 10 //index for 0x0F007000 +static DDR_SET_NODE asT3_DDRSetting80MHz[]= {// # DPLL Clock Setting + {0x0f000810,0x00000F95}, + {0x0f000820,0x07f1ffff}, + {0x0f000860,0x00000000}, + {0x0f000880,0x000003DD}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0F00a084,0x1Cffffff}, + {0x0F00a080,0x1C000000}, + {0x0F00a000,0x00000016}, + {0x0F00a04C,0x0000000C}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01000000}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020000}, + {0x0F007020,0x04020107}, + {0x0F007024,0x00000007}, + {0x0F007028,0x02020201}, + {0x0F00702c,0x0204040a}, + {0x0F007030,0x04000000}, + {0x0F007034,0x00000002}, + {0x0F007038,0x1F060200}, + {0x0F00703C,0x1C22221F}, + {0x0F007040,0x8A006600}, + {0x0F007044,0x221a0800}, + {0x0F007048,0x02690204}, + {0x0F00704c,0x00000000}, + {0x0F007050,0x0000001c}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x000A15D6}, + {0x0F007064,0x0000000A}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00004000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + {0x0F007094,0x00000104}, + //# Enable start bit within memory controller + {0x0F007018,0x01010000} + }; +//100Mhz +#define T3_SKIP_CLOCK_PROGRAM_DUMP_100MHZ 13 //index for 0x0F007000 +static DDR_SET_NODE asT3_DDRSetting100MHz[]= {// # DPLL Clock Setting + {0x0F000800,0x00007008}, + {0x0f000810,0x00000F95}, + {0x0f000820,0x07F13E3F}, + {0x0f000860,0x00000000}, + {0x0f000880,0x000003DD}, + // Changed source for X-bar and MIPS clock to APLL + //0x0f000840,0x0FFF1800, + {0x0f000840,0x0FFF1B00}, + {0x0f000870,0x00000002}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0F00a084,0x1Cffffff}, + {0x0F00a080,0x1C000000}, + {0x0F00a04C,0x0000000C}, + //# Enable 2 ports within X-bar + {0x0F00A000,0x00000016}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01010100}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020001}, // POP - 0x00020000 Normal 0x01020000 + {0x0F007020,0x04020107},//Normal - 0x04030107 POP - 0x05030107 + {0x0F007024,0x00000007}, + {0x0F007028,0x01020201}, + {0x0F00702c,0x0204040A}, + {0x0F007030,0x06000000}, + {0x0F007034,0x00000004}, + {0x0F007038,0x20080200}, + {0x0F00703C,0x02030320}, + {0x0F007040,0x6E7F1200}, + {0x0F007044,0x01190A00}, + {0x0F007048,0x06120305},//0x02690204 // 0x06120305 + {0x0F00704c,0x00000000}, + {0x0F007050,0x0000001C}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x00082ED6}, + {0x0F007064,0x0000000A}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00005000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + //# Enable BW improvement within memory controller + {0x0F007094,0x00000104}, + //# Enable start bit within memory controller + {0x0F007018,0x01010000} + }; + +//Net T3B DDR Settings +//DDR INIT-133Mhz +static DDR_SET_NODE asDPLL_266MHZ[] = { + {0x0F000800,0x00007212}, + {0x0f000820,0x07F13FFF}, + {0x0f000810,0x00000F95}, + {0x0f000860,0x00000000}, + {0x0f000880,0x000003DD}, + // Changed source for X-bar and MIPS clock to APLL + {0x0f000840,0x0FFF1B00}, + {0x0f000870,0x00000002} + }; + +#define T3B_SKIP_CLOCK_PROGRAM_DUMP_133MHZ 11 //index for 0x0F007000 +static DDR_SET_NODE asT3B_DDRSetting133MHz[] = {// # DPLL Clock Setting + {0x0f000810,0x00000F95}, + {0x0f000810,0x00000F95}, + {0x0f000810,0x00000F95}, + {0x0f000820,0x07F13652}, + {0x0f000840,0x0FFF0800}, + // Changed source for X-bar and MIPS clock to APLL + {0x0f000880,0x000003DD}, + {0x0f000860,0x00000000}, + // Changed source for X-bar and MIPS clock to APLL + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0F00a084,0x1Cffffff}, + {0x0F00a080,0x1C000000}, + //# Enable 2 ports within X-bar + {0x0F00A000,0x00000016}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01010100}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020001},// POP - 0x00020001 Normal 0x01020001 + {0x0F007020,0x04030107}, //Normal - 0x04030107 POP - 0x05030107 + {0x0F007024,0x02000007}, + {0x0F007028,0x02020202}, + {0x0F00702c,0x0206060a},//ROB- 0x0205050a,//0x0206060a + {0x0F007030,0x05000000}, + {0x0F007034,0x00000003}, + {0x0F007038,0x130a0200},//ROB - 0x110a0200,//0x180a0200,// 0x1f0a0200 + {0x0F00703C,0x02101012},//ROB - 0x02101010,//0x02101018}, + {0x0F007040,0x457D1200},//ROB - 0x45751200,//0x450f1200}, + {0x0F007044,0x11130d00},//ROB - 0x110a0d00//0x111f0d00 + {0x0F007048,0x040D0306}, + {0x0F00704c,0x00000000}, + {0x0F007050,0x0000001c}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x0010246c}, + {0x0F007064,0x00000012}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00007000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + //# Enable BW improvement within memory controller + {0x0F007094,0x00000104}, + //# Enable start bit within memory controller + {0x0F007018,0x01010000}, + }; + +#define T3B_SKIP_CLOCK_PROGRAM_DUMP_80MHZ 9 //index for 0x0F007000 +static DDR_SET_NODE asT3B_DDRSetting80MHz[] = {// # DPLL Clock Setting + {0x0f000810,0x00000F95}, + {0x0f000820,0x07F13FFF}, + {0x0f000840,0x0FFF1F00}, + {0x0f000880,0x000003DD}, + {0x0f000860,0x00000000}, + + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0F00a084,0x1Cffffff}, + {0x0F00a080,0x1C000000}, + {0x0F00a000,0x00000016}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01000000}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020000}, + {0x0F007020,0x04020107}, + {0x0F007024,0x00000007}, + {0x0F007028,0x02020201}, + {0x0F00702c,0x0204040a}, + {0x0F007030,0x04000000}, + {0x0F007034,0x02000002}, + {0x0F007038,0x1F060202}, + {0x0F00703C,0x1C22221F}, + {0x0F007040,0x8A006600}, + {0x0F007044,0x221a0800}, + {0x0F007048,0x02690204}, + {0x0F00704c,0x00000000}, + {0x0F007050,0x0100001c}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x000A15D6}, + {0x0F007064,0x0000000A}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00004000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + {0x0F007094,0x00000104}, + //# Enable start bit within memory controller + {0x0F007018,0x01010000} + }; + +//100Mhz +#define T3B_SKIP_CLOCK_PROGRAM_DUMP_100MHZ 9 //index for 0x0F007000 +static DDR_SET_NODE asT3B_DDRSetting100MHz[] = {// # DPLL Clock Setting + {0x0f000810,0x00000F95}, + {0x0f000820,0x07F1369B}, + {0x0f000840,0x0FFF0800}, + {0x0f000880,0x000003DD}, + {0x0f000860,0x00000000}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0F00a084,0x1Cffffff}, + {0x0F00a080,0x1C000000}, + //# Enable 2 ports within X-bar + {0x0F00A000,0x00000016}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01010100}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020000}, // POP - 0x00020000 Normal 0x01020000 + {0x0F007020,0x04020107},//Normal - 0x04030107 POP - 0x05030107 + {0x0F007024,0x00000007}, + {0x0F007028,0x01020201}, + {0x0F00702c,0x0204040A}, + {0x0F007030,0x06000000}, + {0x0F007034,0x02000004}, + {0x0F007038,0x20080200}, + {0x0F00703C,0x02030320}, + {0x0F007040,0x6E7F1200}, + {0x0F007044,0x01190A00}, + {0x0F007048,0x06120305},//0x02690204 // 0x06120305 + {0x0F00704c,0x00000000}, + {0x0F007050,0x0100001C}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x00082ED6}, + {0x0F007064,0x0000000A}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00005000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + //# Enable BW improvement within memory controller + {0x0F007094,0x00000104}, + //# Enable start bit within memory controller + {0x0F007018,0x01010000} + }; + + +#define T3LP_SKIP_CLOCK_PROGRAM_DUMP_133MHZ 9 //index for 0x0F007000 +static DDR_SET_NODE asT3LP_DDRSetting133MHz[]= {// # DPLL Clock Setting + {0x0f000820,0x03F1365B}, + {0x0f000810,0x00002F95}, + {0x0f000880,0x000003DD}, + // Changed source for X-bar and MIPS clock to APLL + {0x0f000840,0x0FFF0000}, + {0x0f000860,0x00000000}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0F00a084,0x1Cffffff}, + {0x0F00a080,0x1C000000}, + {0x0F00A000,0x00000016}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01010100}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020001},// POP - 0x00020001 Normal 0x01020001 + {0x0F007020,0x04030107}, //Normal - 0x04030107 POP - 0x05030107 + {0x0F007024,0x02000007}, + {0x0F007028,0x02020200}, + {0x0F00702c,0x0206060a},//ROB- 0x0205050a,//0x0206060a + {0x0F007030,0x05000000}, + {0x0F007034,0x00000003}, + {0x0F007038,0x200a0200},//ROB - 0x110a0200,//0x180a0200,// 0x1f0a0200 + {0x0F00703C,0x02101020},//ROB - 0x02101010,//0x02101018, + {0x0F007040,0x45711200},//ROB - 0x45751200,//0x450f1200, + {0x0F007044,0x110D0D00},//ROB - 0x110a0d00//0x111f0d00 + {0x0F007048,0x04080306}, + {0x0F00704c,0x00000000}, + {0x0F007050,0x0100001c}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x0010245F}, + {0x0F007064,0x00000010}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00007000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + {0x0F007088,0x01000001}, + {0x0F00708c,0x00000101}, + {0x0F007090,0x00000000}, + //# Enable BW improvement within memory controller + {0x0F007094,0x00040000}, + {0x0F007098,0x00000000}, + {0x0F0070c8,0x00000104}, + //# Enable 2 ports within X-bar + //# Enable start bit within memory controller + {0x0F007018,0x01010000} +}; + +#define T3LP_SKIP_CLOCK_PROGRAM_DUMP_100MHZ 11 //index for 0x0F007000 +static DDR_SET_NODE asT3LP_DDRSetting100MHz[]= {// # DPLL Clock Setting + {0x0f000810,0x00002F95}, + {0x0f000820,0x03F1369B}, + {0x0f000840,0x0fff0000}, + {0x0f000860,0x00000000}, + {0x0f000880,0x000003DD}, + // Changed source for X-bar and MIPS clock to APLL + {0x0f000840,0x0FFF0000}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0F00a084,0x1Cffffff}, + {0x0F00a080,0x1C000000}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01010100}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020000},// POP - 0x00020001 Normal 0x01020001 + {0x0F007020,0x04020107}, //Normal - 0x04030107 POP - 0x05030107 + {0x0F007024,0x00000007}, + {0x0F007028,0x01020200}, + {0x0F00702c,0x0204040a},//ROB- 0x0205050a,//0x0206060a + {0x0F007030,0x06000000}, + {0x0F007034,0x00000004}, + {0x0F007038,0x1F080200},//ROB - 0x110a0200,//0x180a0200,// 0x1f0a0200 + {0x0F00703C,0x0203031F},//ROB - 0x02101010,//0x02101018, + {0x0F007040,0x6e001200},//ROB - 0x45751200,//0x450f1200, + {0x0F007044,0x011a0a00},//ROB - 0x110a0d00//0x111f0d00 + {0x0F007048,0x03000305}, + {0x0F00704c,0x00000000}, + {0x0F007050,0x0100001c}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x00082ED6}, + {0x0F007064,0x0000000A}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00005000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + {0x0F007088,0x01000001}, + {0x0F00708c,0x00000101}, + {0x0F007090,0x00000000}, + {0x0F007094,0x00010000}, + {0x0F007098,0x00000000}, + {0x0F0070C8,0x00000104}, + //# Enable 2 ports within X-bar + {0x0F00A000,0x00000016}, + //# Enable start bit within memory controller + {0x0F007018,0x01010000} +}; + +#define T3LP_SKIP_CLOCK_PROGRAM_DUMP_80MHZ 9 //index for 0x0F007000 +static DDR_SET_NODE asT3LP_DDRSetting80MHz[]= {// # DPLL Clock Setting + {0x0f000820,0x07F13FFF}, + {0x0f000810,0x00002F95}, + {0x0f000860,0x00000000}, + {0x0f000880,0x000003DD}, + {0x0f000840,0x0FFF1F00}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0F00a084,0x1Cffffff}, + {0x0F00a080,0x1C000000}, + {0x0F00A000,0x00000016}, + {0x0f007000,0x00010001}, + {0x0f007004,0x01000000}, + {0x0f007008,0x01000001}, + {0x0f00700c,0x00000000}, + {0x0f007010,0x01000000}, + {0x0f007014,0x01000100}, + {0x0f007018,0x01000000}, + {0x0f00701c,0x01020000}, + {0x0f007020,0x04020107}, + {0x0f007024,0x00000007}, + {0x0f007028,0x02020200}, + {0x0f00702c,0x0204040a}, + {0x0f007030,0x04000000}, + {0x0f007034,0x00000002}, + {0x0f007038,0x1d060200}, + {0x0f00703c,0x1c22221d}, + {0x0f007040,0x8A116600}, + {0x0f007044,0x222d0800}, + {0x0f007048,0x02690204}, + {0x0f00704c,0x00000000}, + {0x0f007050,0x0100001c}, + {0x0f007054,0x00000000}, + {0x0f007058,0x00000000}, + {0x0f00705c,0x00000000}, + {0x0f007060,0x000A15D6}, + {0x0f007064,0x0000000A}, + {0x0f007068,0x00000000}, + {0x0f00706c,0x00000001}, + {0x0f007070,0x00004000}, + {0x0f007074,0x00000000}, + {0x0f007078,0x00000000}, + {0x0f00707c,0x00000000}, + {0x0f007080,0x00000000}, + {0x0f007084,0x00000000}, + {0x0f007088,0x01000001}, + {0x0f00708c,0x00000101}, + {0x0f007090,0x00000000}, + {0x0f007094,0x00010000}, + {0x0f007098,0x00000000}, + {0x0F0070C8,0x00000104}, + {0x0F007018,0x01010000} +}; + + + + +///T3 LP-B (UMA-B) + +#define T3LPB_SKIP_CLOCK_PROGRAM_DUMP_160MHZ 7 //index for 0x0F007000 +static DDR_SET_NODE asT3LPB_DDRSetting160MHz[]= {// # DPLL Clock Setting + + {0x0f000820,0x03F137DB}, + {0x0f000810,0x01842795}, + {0x0f000860,0x00000000}, + {0x0f000880,0x000003DD}, + {0x0f000840,0x0FFF0400}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0f003050,0x00000021},//this is flash/eeprom clock divisor which set the flash clock to 20 MHz + {0x0F00a084,0x1Cffffff},//Now dump from her in internal memory + {0x0F00a080,0x1C000000}, + {0x0F00A000,0x00000016}, + {0x0f007000,0x00010001}, + {0x0f007004,0x01000001}, + {0x0f007008,0x01000101}, + {0x0f00700c,0x00000000}, + {0x0f007010,0x01000100}, + {0x0f007014,0x01000100}, + {0x0f007018,0x01000000}, + {0x0f00701c,0x01020000}, + {0x0f007020,0x04030107}, + {0x0f007024,0x02000007}, + {0x0f007028,0x02020200}, + {0x0f00702c,0x0206060a}, + {0x0f007030,0x050d0d00}, + {0x0f007034,0x00000003}, + {0x0f007038,0x170a0200}, + {0x0f00703c,0x02101012}, + {0x0f007040,0x45161200}, + {0x0f007044,0x11250c00}, + {0x0f007048,0x04da0307}, + {0x0f00704c,0x00000000}, + {0x0f007050,0x0000001c}, + {0x0f007054,0x00000000}, + {0x0f007058,0x00000000}, + {0x0f00705c,0x00000000}, + {0x0f007060,0x00142bb6}, + {0x0f007064,0x20430014}, + {0x0f007068,0x00000000}, + {0x0f00706c,0x00000001}, + {0x0f007070,0x00009000}, + {0x0f007074,0x00000000}, + {0x0f007078,0x00000000}, + {0x0f00707c,0x00000000}, + {0x0f007080,0x00000000}, + {0x0f007084,0x00000000}, + {0x0f007088,0x01000001}, + {0x0f00708c,0x00000101}, + {0x0f007090,0x00000000}, + {0x0f007094,0x00040000}, + {0x0f007098,0x00000000}, + {0x0F0070C8,0x00000104}, + {0x0F007018,0x01010000} +}; + + +#define T3LPB_SKIP_CLOCK_PROGRAM_DUMP_133MHZ 7 //index for 0x0F007000 +static DDR_SET_NODE asT3LPB_DDRSetting133MHz[]= {// # DPLL Clock Setting + {0x0f000820,0x03F1365B}, + {0x0f000810,0x00002F95}, + {0x0f000880,0x000003DD}, + // Changed source for X-bar and MIPS clock to APLL + {0x0f000840,0x0FFF0000}, + {0x0f000860,0x00000000}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0f003050,0x00000021},//flash/eeprom clock divisor which set the flash clock to 20 MHz + {0x0F00a084,0x1Cffffff},//dump from here in internal memory + {0x0F00a080,0x1C000000}, + {0x0F00A000,0x00000016}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01010100}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020001},// POP - 0x00020001 Normal 0x01020001 + {0x0F007020,0x04030107}, //Normal - 0x04030107 POP - 0x05030107 + {0x0F007024,0x02000007}, + {0x0F007028,0x02020200}, + {0x0F00702c,0x0206060a},//ROB- 0x0205050a,//0x0206060a + {0x0F007030,0x05000000}, + {0x0F007034,0x00000003}, + {0x0F007038,0x190a0200},//ROB - 0x110a0200,//0x180a0200,// 0x1f0a0200 + {0x0F00703C,0x02101017},//ROB - 0x02101010,//0x02101018, + {0x0F007040,0x45171200},//ROB - 0x45751200,//0x450f1200, + {0x0F007044,0x11290D00},//ROB - 0x110a0d00//0x111f0d00 + {0x0F007048,0x04080306}, + {0x0F00704c,0x00000000}, + {0x0F007050,0x0100001c}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x0010245F}, + {0x0F007064,0x00000010}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00007000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + {0x0F007088,0x01000001}, + {0x0F00708c,0x00000101}, + {0x0F007090,0x00000000}, + //# Enable BW improvement within memory controller + {0x0F007094,0x00040000}, + {0x0F007098,0x00000000}, + {0x0F0070c8,0x00000104}, + //# Enable 2 ports within X-bar + //# Enable start bit within memory controller + {0x0F007018,0x01010000} +}; + +#define T3LPB_SKIP_CLOCK_PROGRAM_DUMP_100MHZ 8 //index for 0x0F007000 +static DDR_SET_NODE asT3LPB_DDRSetting100MHz[]= {// # DPLL Clock Setting + {0x0f000810,0x00002F95}, + {0x0f000820,0x03F1369B}, + {0x0f000840,0x0fff0000}, + {0x0f000860,0x00000000}, + {0x0f000880,0x000003DD}, + // Changed source for X-bar and MIPS clock to APLL + {0x0f000840,0x0FFF0000}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0f003050,0x00000021},//flash/eeprom clock divisor which set the flash clock to 20 MHz + {0x0F00a084,0x1Cffffff}, //dump from here in internal memory + {0x0F00a080,0x1C000000}, + //Memcontroller Default values + {0x0F007000,0x00010001}, + {0x0F007004,0x01010100}, + {0x0F007008,0x01000001}, + {0x0F00700c,0x00000000}, + {0x0F007010,0x01000000}, + {0x0F007014,0x01000100}, + {0x0F007018,0x01000000}, + {0x0F00701c,0x01020000},// POP - 0x00020001 Normal 0x01020001 + {0x0F007020,0x04020107}, //Normal - 0x04030107 POP - 0x05030107 + {0x0F007024,0x00000007}, + {0x0F007028,0x01020200}, + {0x0F00702c,0x0204040a},//ROB- 0x0205050a,//0x0206060a + {0x0F007030,0x06000000}, + {0x0F007034,0x00000004}, + {0x0F007038,0x1F080200},//ROB - 0x110a0200,//0x180a0200,// 0x1f0a0200 + {0x0F00703C,0x0203031F},//ROB - 0x02101010,//0x02101018, + {0x0F007040,0x6e001200},//ROB - 0x45751200,//0x450f1200, + {0x0F007044,0x011a0a00},//ROB - 0x110a0d00//0x111f0d00 + {0x0F007048,0x03000305}, + {0x0F00704c,0x00000000}, + {0x0F007050,0x0100001c}, + {0x0F007054,0x00000000}, + {0x0F007058,0x00000000}, + {0x0F00705c,0x00000000}, + {0x0F007060,0x00082ED6}, + {0x0F007064,0x0000000A}, + {0x0F007068,0x00000000}, + {0x0F00706c,0x00000001}, + {0x0F007070,0x00005000}, + {0x0F007074,0x00000000}, + {0x0F007078,0x00000000}, + {0x0F00707C,0x00000000}, + {0x0F007080,0x00000000}, + {0x0F007084,0x00000000}, + {0x0F007088,0x01000001}, + {0x0F00708c,0x00000101}, + {0x0F007090,0x00000000}, + {0x0F007094,0x00010000}, + {0x0F007098,0x00000000}, + {0x0F0070C8,0x00000104}, + //# Enable 2 ports within X-bar + {0x0F00A000,0x00000016}, + //# Enable start bit within memory controller + {0x0F007018,0x01010000} +}; + +#define T3LPB_SKIP_CLOCK_PROGRAM_DUMP_80MHZ 7 //index for 0x0F007000 +static DDR_SET_NODE asT3LPB_DDRSetting80MHz[]= {// # DPLL Clock Setting + {0x0f000820,0x07F13FFF}, + {0x0f000810,0x00002F95}, + {0x0f000860,0x00000000}, + {0x0f000880,0x000003DD}, + {0x0f000840,0x0FFF1F00}, + {0x0F00a044,0x1fffffff}, + {0x0F00a040,0x1f000000}, + {0x0f003050,0x00000021},//flash/eeprom clock divisor which set the flash clock to 20 MHz + {0x0F00a084,0x1Cffffff},// dump from here in internal memory + {0x0F00a080,0x1C000000}, + {0x0F00A000,0x00000016}, + {0x0f007000,0x00010001}, + {0x0f007004,0x01000000}, + {0x0f007008,0x01000001}, + {0x0f00700c,0x00000000}, + {0x0f007010,0x01000000}, + {0x0f007014,0x01000100}, + {0x0f007018,0x01000000}, + {0x0f00701c,0x01020000}, + {0x0f007020,0x04020107}, + {0x0f007024,0x00000007}, + {0x0f007028,0x02020200}, + {0x0f00702c,0x0204040a}, + {0x0f007030,0x04000000}, + {0x0f007034,0x00000002}, + {0x0f007038,0x1d060200}, + {0x0f00703c,0x1c22221d}, + {0x0f007040,0x8A116600}, + {0x0f007044,0x222d0800}, + {0x0f007048,0x02690204}, + {0x0f00704c,0x00000000}, + {0x0f007050,0x0100001c}, + {0x0f007054,0x00000000}, + {0x0f007058,0x00000000}, + {0x0f00705c,0x00000000}, + {0x0f007060,0x000A15D6}, + {0x0f007064,0x0000000A}, + {0x0f007068,0x00000000}, + {0x0f00706c,0x00000001}, + {0x0f007070,0x00004000}, + {0x0f007074,0x00000000}, + {0x0f007078,0x00000000}, + {0x0f00707c,0x00000000}, + {0x0f007080,0x00000000}, + {0x0f007084,0x00000000}, + {0x0f007088,0x01000001}, + {0x0f00708c,0x00000101}, + {0x0f007090,0x00000000}, + {0x0f007094,0x00010000}, + {0x0f007098,0x00000000}, + {0x0F0070C8,0x00000104}, + {0x0F007018,0x01010000} +}; + + +int ddr_init(MINI_ADAPTER *Adapter) +{ + PDDR_SETTING psDDRSetting=NULL; + ULONG RegCount=0; + UINT value = 0; + UINT uiResetValue = 0; + UINT uiClockSetting = 0; + int retval = STATUS_SUCCESS; + + switch (Adapter->chip_id) + { + case 0xbece3200: + switch (Adapter->DDRSetting) + { + case DDR_80_MHZ: + psDDRSetting=asT3LP_DDRSetting80MHz; + RegCount=(sizeof(asT3LP_DDRSetting80MHz)/ + sizeof(DDR_SETTING)); + break; + case DDR_100_MHZ: + psDDRSetting=asT3LP_DDRSetting100MHz; + RegCount=(sizeof(asT3LP_DDRSetting100MHz)/ + sizeof(DDR_SETTING)); + break; + case DDR_133_MHZ: + psDDRSetting=asT3LP_DDRSetting133MHz; + RegCount=(sizeof(asT3LP_DDRSetting133MHz)/ + sizeof(DDR_SETTING)); + if(Adapter->bMipsConfig == MIPS_200_MHZ) + { + uiClockSetting = 0x03F13652; + } + else + { + uiClockSetting = 0x03F1365B; + } + break; + default: + return -EINVAL; + } + + break; + case T3LPB: + case BCS220_2: + case BCS220_2BC: + case BCS250_BC: + case BCS220_3 : + /* Set bit 2 and bit 6 to 1 for BBIC 2mA drive + * (please check current value and additionally set these bits) + */ + if( (Adapter->chip_id != BCS220_2) && + (Adapter->chip_id != BCS220_2BC) && + (Adapter->chip_id != BCS220_3) ) + { + retval= rdmalt(Adapter,(UINT)0x0f000830, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + uiResetValue |= 0x44; + retval = wrmalt(Adapter,(UINT)0x0f000830, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, WRM, DBG_LVL_ALL, "%s:%d WRM failed\n", __FUNCTION__, __LINE__); + return retval; + } + } + switch(Adapter->DDRSetting) + { + + + + case DDR_80_MHZ: + psDDRSetting = asT3LPB_DDRSetting80MHz; + RegCount=(sizeof(asT3B_DDRSetting80MHz)/ + sizeof(DDR_SETTING)); + break; + case DDR_100_MHZ: + psDDRSetting=asT3LPB_DDRSetting100MHz; + RegCount=(sizeof(asT3B_DDRSetting100MHz)/ + sizeof(DDR_SETTING)); + break; + case DDR_133_MHZ: + psDDRSetting = asT3LPB_DDRSetting133MHz; + RegCount=(sizeof(asT3B_DDRSetting133MHz)/ + sizeof(DDR_SETTING)); + + if(Adapter->bMipsConfig == MIPS_200_MHZ) + { + uiClockSetting = 0x03F13652; + } + else + { + uiClockSetting = 0x03F1365B; + } + break; + + case DDR_160_MHZ: + psDDRSetting = asT3LPB_DDRSetting160MHz; + RegCount = sizeof(asT3LPB_DDRSetting160MHz)/sizeof(DDR_SETTING); + + if(Adapter->bMipsConfig == MIPS_200_MHZ) + { + uiClockSetting = 0x03F137D2; + } + else + { + uiClockSetting = 0x03F137DB; + } + } + break; + + case 0xbece0110: + case 0xbece0120: + case 0xbece0121: + case 0xbece0130: + case 0xbece0300: + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, "DDR Setting: %x\n", Adapter->DDRSetting); + switch (Adapter->DDRSetting) + { + case DDR_80_MHZ: + psDDRSetting = asT3_DDRSetting80MHz; + RegCount = (sizeof(asT3_DDRSetting80MHz)/ + sizeof(DDR_SETTING)); + break; + case DDR_100_MHZ: + psDDRSetting = asT3_DDRSetting100MHz; + RegCount = (sizeof(asT3_DDRSetting100MHz)/ + sizeof(DDR_SETTING)); + break; + case DDR_133_MHZ: + psDDRSetting = asT3_DDRSetting133MHz; + RegCount = (sizeof(asT3_DDRSetting133MHz)/ + sizeof(DDR_SETTING)); + break; + default: + return -EINVAL; + } + case 0xbece0310: + { + switch (Adapter->DDRSetting) + { + case DDR_80_MHZ: + psDDRSetting = asT3B_DDRSetting80MHz; + RegCount=(sizeof(asT3B_DDRSetting80MHz)/ + sizeof(DDR_SETTING)); + break; + case DDR_100_MHZ: + psDDRSetting=asT3B_DDRSetting100MHz; + RegCount=(sizeof(asT3B_DDRSetting100MHz)/ + sizeof(DDR_SETTING)); + break; + case DDR_133_MHZ: + + if(Adapter->bDPLLConfig == PLL_266_MHZ)//266Mhz PLL selected. + { + memcpy(asT3B_DDRSetting133MHz, asDPLL_266MHZ, + sizeof(asDPLL_266MHZ)); + psDDRSetting = asT3B_DDRSetting133MHz; + RegCount=(sizeof(asT3B_DDRSetting133MHz)/ + sizeof(DDR_SETTING)); + } + else + { + psDDRSetting = asT3B_DDRSetting133MHz; + RegCount=(sizeof(asT3B_DDRSetting133MHz)/ + sizeof(DDR_SETTING)); + if(Adapter->bMipsConfig == MIPS_200_MHZ) + { + uiClockSetting = 0x07F13652; + } + else + { + uiClockSetting = 0x07F1365B; + } + } + break; + default: + return -EINVAL; + } + break; + + } + default: + return -EINVAL; + } + + value=0; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, "Register Count is =%lu\n", RegCount); + while(RegCount && !retval) + { + if(uiClockSetting && psDDRSetting->ulRegAddress == MIPS_CLOCK_REG) + { + value = uiClockSetting; + } + else + { + value = psDDRSetting->ulRegValue; + } + retval = wrmalt(Adapter, psDDRSetting->ulRegAddress, &value, sizeof(value)); + if(STATUS_SUCCESS != retval) { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"%s:%d\n", __FUNCTION__, __LINE__); + break; + } + + RegCount--; + psDDRSetting++; + } + + if(Adapter->chip_id >= 0xbece3300 ) + { + + mdelay(3); + if( (Adapter->chip_id != BCS220_2)&& + (Adapter->chip_id != BCS220_2BC)&& + (Adapter->chip_id != BCS220_3)) + { + /* drive MDDR to half in case of UMA-B: */ + uiResetValue = 0x01010001; + retval = wrmalt(Adapter, (UINT)0x0F007018, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + uiResetValue = 0x00040020; + retval = wrmalt(Adapter, (UINT)0x0F007094, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + uiResetValue = 0x01020101; + retval = wrmalt(Adapter, (UINT)0x0F00701c, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + uiResetValue = 0x01010000; + retval = wrmalt(Adapter, (UINT)0x0F007018, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + } + mdelay(3); + + /* DC/DC standby change... + * This is to be done only for Hybrid PMU mode. + * with the current h/w there is no way to detect this. + * and since we dont have internal PMU lets do it under UMA-B chip id. + * we will change this when we will have internal PMU. + */ + if(Adapter->PmuMode == HYBRID_MODE_7C) + { + retval = rdmalt(Adapter,(UINT)0x0f000c00, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + retval = rdmalt(Adapter,(UINT)0x0f000c00, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + uiResetValue = 0x1322a8; + retval = wrmalt(Adapter, (UINT)0x0f000d1c, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + retval = rdmalt(Adapter,(UINT)0x0f000c00, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + retval = rdmalt(Adapter,(UINT)0x0f000c00, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + uiResetValue = 0x132296; + retval = wrmalt(Adapter, (UINT)0x0f000d14, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + } + else if(Adapter->PmuMode == HYBRID_MODE_6 ) + { + + retval = rdmalt(Adapter,(UINT)0x0f000c00, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + retval = rdmalt(Adapter,(UINT)0x0f000c00, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + uiResetValue = 0x6003229a; + retval = wrmalt(Adapter, (UINT)0x0f000d14, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + retval = rdmalt(Adapter,(UINT)0x0f000c00, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + retval = rdmalt(Adapter,(UINT)0x0f000c00, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + uiResetValue = 0x1322a8; + retval = wrmalt(Adapter, (UINT)0x0f000d1c, &uiResetValue, sizeof(uiResetValue)); + if(retval < 0) { + BCM_DEBUG_PRINT(Adapter,CMHOST, RDM, DBG_LVL_ALL, "%s:%d RDM failed\n", __FUNCTION__, __LINE__); + return retval; + } + } + + } + Adapter->bDDRInitDone = TRUE; + return retval; +} + +int download_ddr_settings(PMINI_ADAPTER Adapter) +{ + PDDR_SET_NODE psDDRSetting=NULL; + ULONG RegCount=0; + unsigned long ul_ddr_setting_load_addr = DDR_DUMP_INTERNAL_DEVICE_MEMORY; + UINT value = 0; + int retval = STATUS_SUCCESS; + BOOLEAN bOverrideSelfRefresh = FALSE; + + switch (Adapter->chip_id) + { + case 0xbece3200: + switch (Adapter->DDRSetting) + { + case DDR_80_MHZ: + psDDRSetting = asT3LP_DDRSetting80MHz; + RegCount = (sizeof(asT3LP_DDRSetting80MHz)/sizeof(DDR_SET_NODE)); + RegCount -= T3LP_SKIP_CLOCK_PROGRAM_DUMP_80MHZ ; + psDDRSetting += T3LP_SKIP_CLOCK_PROGRAM_DUMP_80MHZ; + break; + case DDR_100_MHZ: + psDDRSetting = asT3LP_DDRSetting100MHz; + RegCount = (sizeof(asT3LP_DDRSetting100MHz)/sizeof(DDR_SET_NODE)); + RegCount -= T3LP_SKIP_CLOCK_PROGRAM_DUMP_100MHZ ; + psDDRSetting += T3LP_SKIP_CLOCK_PROGRAM_DUMP_100MHZ; + break; + case DDR_133_MHZ: + bOverrideSelfRefresh = TRUE; + psDDRSetting = asT3LP_DDRSetting133MHz; + RegCount = (sizeof(asT3LP_DDRSetting133MHz)/sizeof(DDR_SET_NODE)); + RegCount -= T3LP_SKIP_CLOCK_PROGRAM_DUMP_133MHZ ; + psDDRSetting += T3LP_SKIP_CLOCK_PROGRAM_DUMP_133MHZ; + break; + default: + return -EINVAL; + } + break; + + case T3LPB: + case BCS220_2: + case BCS220_2BC: + case BCS250_BC: + case BCS220_3 : + switch (Adapter->DDRSetting) + { + case DDR_80_MHZ: + psDDRSetting = asT3LPB_DDRSetting80MHz; + RegCount=(sizeof(asT3LPB_DDRSetting80MHz)/sizeof(DDR_SET_NODE)); + RegCount -= T3LPB_SKIP_CLOCK_PROGRAM_DUMP_80MHZ ; + psDDRSetting += T3LPB_SKIP_CLOCK_PROGRAM_DUMP_80MHZ; + break; + case DDR_100_MHZ: + psDDRSetting = asT3LPB_DDRSetting100MHz; + RegCount = (sizeof(asT3LPB_DDRSetting100MHz)/sizeof(DDR_SET_NODE)); + RegCount -= T3LPB_SKIP_CLOCK_PROGRAM_DUMP_100MHZ ; + psDDRSetting += T3LPB_SKIP_CLOCK_PROGRAM_DUMP_100MHZ; + break; + case DDR_133_MHZ: + bOverrideSelfRefresh = TRUE; + psDDRSetting = asT3LPB_DDRSetting133MHz; + RegCount = (sizeof(asT3LPB_DDRSetting133MHz)/sizeof(DDR_SET_NODE)); + RegCount -= T3LPB_SKIP_CLOCK_PROGRAM_DUMP_133MHZ ; + psDDRSetting += T3LPB_SKIP_CLOCK_PROGRAM_DUMP_133MHZ; + break; + + case DDR_160_MHZ: + bOverrideSelfRefresh = TRUE; + psDDRSetting = asT3LPB_DDRSetting160MHz; + RegCount = sizeof(asT3LPB_DDRSetting160MHz)/sizeof(DDR_SET_NODE); + RegCount -= T3LPB_SKIP_CLOCK_PROGRAM_DUMP_160MHZ; + psDDRSetting += T3LPB_SKIP_CLOCK_PROGRAM_DUMP_160MHZ; + + break; + default: + return -EINVAL; + } + break; + case 0xbece0300: + switch (Adapter->DDRSetting) + { + case DDR_80_MHZ: + psDDRSetting = asT3_DDRSetting80MHz; + RegCount = (sizeof(asT3_DDRSetting80MHz)/sizeof(DDR_SET_NODE)); + RegCount-=T3_SKIP_CLOCK_PROGRAM_DUMP_80MHZ ; + psDDRSetting += T3_SKIP_CLOCK_PROGRAM_DUMP_80MHZ; + break; + case DDR_100_MHZ: + psDDRSetting = asT3_DDRSetting100MHz; + RegCount = (sizeof(asT3_DDRSetting100MHz)/sizeof(DDR_SET_NODE)); + RegCount-=T3_SKIP_CLOCK_PROGRAM_DUMP_100MHZ ; + psDDRSetting += T3_SKIP_CLOCK_PROGRAM_DUMP_100MHZ; + break; + case DDR_133_MHZ: + psDDRSetting = asT3_DDRSetting133MHz; + RegCount = (sizeof(asT3_DDRSetting133MHz)/sizeof(DDR_SET_NODE)); + RegCount-=T3_SKIP_CLOCK_PROGRAM_DUMP_133MHZ ; + psDDRSetting += T3_SKIP_CLOCK_PROGRAM_DUMP_133MHZ ; + break; + default: + return -EINVAL; + } + break; + case 0xbece0310: + { + switch (Adapter->DDRSetting) + { + case DDR_80_MHZ: + psDDRSetting = asT3B_DDRSetting80MHz; + RegCount = (sizeof(asT3B_DDRSetting80MHz)/sizeof(DDR_SET_NODE)); + RegCount -= T3B_SKIP_CLOCK_PROGRAM_DUMP_80MHZ ; + psDDRSetting += T3B_SKIP_CLOCK_PROGRAM_DUMP_80MHZ; + break; + case DDR_100_MHZ: + psDDRSetting = asT3B_DDRSetting100MHz; + RegCount = (sizeof(asT3B_DDRSetting100MHz)/sizeof(DDR_SET_NODE)); + RegCount -= T3B_SKIP_CLOCK_PROGRAM_DUMP_100MHZ ; + psDDRSetting += T3B_SKIP_CLOCK_PROGRAM_DUMP_100MHZ; + break; + case DDR_133_MHZ: + bOverrideSelfRefresh = TRUE; + psDDRSetting = asT3B_DDRSetting133MHz; + RegCount = (sizeof(asT3B_DDRSetting133MHz)/sizeof(DDR_SET_NODE)); + RegCount -= T3B_SKIP_CLOCK_PROGRAM_DUMP_133MHZ ; + psDDRSetting += T3B_SKIP_CLOCK_PROGRAM_DUMP_133MHZ; + break; + } + break; + } + default: + return -EINVAL; + } + //total number of Register that has to be dumped + value =RegCount ; + retval = wrmalt(Adapter, ul_ddr_setting_load_addr, &value, sizeof(value)); + if(retval) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"%s:%d\n", __FUNCTION__, __LINE__); + + return retval; + } + ul_ddr_setting_load_addr+=sizeof(ULONG); + /*signature */ + value =(0x1d1e0dd0); + retval = wrmalt(Adapter, ul_ddr_setting_load_addr, &value, sizeof(value)); + if(retval) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"%s:%d\n", __FUNCTION__, __LINE__); + return retval; + } + + ul_ddr_setting_load_addr+=sizeof(ULONG); + RegCount*=(sizeof(DDR_SETTING)/sizeof(ULONG)); + + while(RegCount && !retval) + { + value = psDDRSetting->ulRegAddress ; + retval = wrmalt( Adapter, ul_ddr_setting_load_addr, &value, sizeof(value)); + ul_ddr_setting_load_addr+=sizeof(ULONG); + if(!retval) + { + if(bOverrideSelfRefresh && (psDDRSetting->ulRegAddress == 0x0F007018)) + { + value = (psDDRSetting->ulRegValue |(1<<8)); + if(STATUS_SUCCESS != wrmalt(Adapter, ul_ddr_setting_load_addr, + &value, sizeof(value))){ + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"%s:%d\n", __FUNCTION__, __LINE__); + break; + } + } + else + { + value = psDDRSetting->ulRegValue; + + if(STATUS_SUCCESS != wrmalt(Adapter, ul_ddr_setting_load_addr , + &value, sizeof(value))){ + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"%s:%d\n", __FUNCTION__, __LINE__); + break; + } + } + } + ul_ddr_setting_load_addr+=sizeof(ULONG); + RegCount--; + psDDRSetting++; + } + return retval; +} + + diff --git a/drivers/staging/bcm/DDRInit.h b/drivers/staging/bcm/DDRInit.h new file mode 100644 index 00000000..550e260d --- /dev/null +++ b/drivers/staging/bcm/DDRInit.h @@ -0,0 +1,9 @@ +#ifndef _DDR_INIT_H_ +#define _DDR_INIT_H_ + + + +int ddr_init(PMINI_ADAPTER psAdapter); +int download_ddr_settings(PMINI_ADAPTER psAdapter); + +#endif diff --git a/drivers/staging/bcm/Debug.h b/drivers/staging/bcm/Debug.h new file mode 100644 index 00000000..420382d1 --- /dev/null +++ b/drivers/staging/bcm/Debug.h @@ -0,0 +1,259 @@ +/* + * Debug.h + * + * Dynamic (runtime) debug framework implementation. + * -kaiwan. + */ +#ifndef _DEBUG_H +#define _DEBUG_H +#include +#define NONE 0xFFFF + + +//-------------------------------------------------------------------------------- + +/* TYPE and SUBTYPE + * Define valid TYPE (or category or code-path, however you like to think of it) + * and SUBTYPE s. + * Type and SubType are treated as bitmasks. + */ +/*-----------------BEGIN TYPEs------------------------------------------*/ +#define DBG_TYPE_INITEXIT (1 << 0) // 1 +#define DBG_TYPE_TX (1 << 1) // 2 +#define DBG_TYPE_RX (1 << 2) // 4 +#define DBG_TYPE_OTHERS (1 << 3) // 8 +/*-----------------END TYPEs------------------------------------------*/ +#define NUMTYPES 4 // careful! + +/*-----------------BEGIN SUBTYPEs---------------------------------------*/ + +/*-SUBTYPEs for TX : TYPE is DBG_TYPE_TX -----// + Transmit.c ,Arp.c, LeakyBucket.c, And Qos.c + total 17 macros */ +// Transmit.c +#define TX 1 +#define MP_SEND (TX<<0) +#define NEXT_SEND (TX<<1) +#define TX_FIFO (TX<<2) +#define TX_CONTROL (TX<<3) + +// Arp.c +#define IP_ADDR (TX<<4) +#define ARP_REQ (TX<<5) +#define ARP_RESP (TX<<6) + +// dhcp.c +//#define DHCP TX +//#define DHCP_REQ (DHCP<<7) + +// Leakybucket.c +#define TOKEN_COUNTS (TX<<8) +#define CHECK_TOKENS (TX<<9) +#define TX_PACKETS (TX<<10) +#define TIMER (TX<<11) + +// Qos.c +#define QOS TX +#define QUEUE_INDEX (QOS<<12) +#define IPV4_DBG (QOS<<13) +#define IPV6_DBG (QOS<<14) +#define PRUNE_QUEUE (QOS<<15) +#define SEND_QUEUE (QOS<<16) + +//TX_Misc +#define TX_OSAL_DBG (TX<<17) + + +//--SUBTYPEs for ------INIT & EXIT--------------------- +/*------------ TYPE is DBG_TYPE_INITEXIT -----// +DriverEntry.c, bcmfwup.c, ChipDetectTask.c, HaltnReset.c, InterfaceDDR.c */ +#define MP 1 +#define DRV_ENTRY (MP<<0) +#define MP_INIT (MP<<1) +#define READ_REG (MP<<3) +#define DISPATCH (MP<<2) +#define CLAIM_ADAP (MP<<4) +#define REG_IO_PORT (MP<<5) +#define INIT_DISP (MP<<6) +#define RX_INIT (MP<<7) + + +//-SUBTYPEs for --RX---------------------------------- +//------------RX : TYPE is DBG_TYPE_RX -----// +// Receive.c +#define RX 1 +#define RX_DPC (RX<<0) +#define RX_CTRL (RX<<3) +#define RX_DATA (RX<<4) +#define MP_RETURN (RX<<1) +#define LINK_MSG (RX<<2) + + +//-SUBTYPEs for ----OTHER ROUTINES------------------ +//------------OTHERS : TYPE is DBG_TYPE_OTHER -----// +// HaltnReset,CheckForHang,PnP,Misc,CmHost +// total 12 macros +#define OTHERS 1 +// ??ISR.C + +#define ISR OTHERS +#define MP_DPC (ISR<<0) + +// HaltnReset.c +#define HALT OTHERS +#define MP_HALT (HALT<<1) +#define CHECK_HANG (HALT<<2) +#define MP_RESET (HALT<<3) +#define MP_SHUTDOWN (HALT<<4) + +// pnp.c +#define PNP OTHERS +#define MP_PNP (PNP<<5) + +// Misc.c +#define MISC OTHERS +#define DUMP_INFO (MISC<<6) +#define CLASSIFY (MISC<<7) +#define LINK_UP_MSG (MISC<<8) +#define CP_CTRL_PKT (MISC<<9) +#define DUMP_CONTROL (MISC<<10) +#define LED_DUMP_INFO (MISC<<11) + +// CmHost.c +#define CMHOST OTHERS + + +#define SERIAL (OTHERS<<12) +#define IDLE_MODE (OTHERS<<13) + +#define WRM (OTHERS<<14) +#define RDM (OTHERS<<15) + +// TODO - put PHS_SEND in Tx PHS_RECEIVE in Rx path ? +#define PHS_SEND (OTHERS<<16) +#define PHS_RECEIVE (OTHERS<<17) +#define PHS_MODULE (OTHERS<<18) + +#define INTF_INIT (OTHERS<<19) +#define INTF_ERR (OTHERS<<20) +#define INTF_WARN (OTHERS<<21) +#define INTF_NORM (OTHERS<<22) + +#define IRP_COMPLETION (OTHERS<<23) +#define SF_DESCRIPTOR_CNTS (OTHERS<<24) +#define PHS_DISPATCH (OTHERS << 25) +#define OSAL_DBG (OTHERS << 26) +#define NVM_RW (OTHERS << 27) + +#define HOST_MIBS (OTHERS << 28) +#define CONN_MSG (CMHOST << 29) +//#define OTHERS_MISC (OTHERS << 29) // ProcSupport.c +/*-----------------END SUBTYPEs------------------------------------------*/ + + +/* Debug level + * We have 8 debug levels, in (numerical) increasing order of verbosity. + * IMP: Currently implementing ONLY DBG_LVL_ALL , i.e. , all debug prints will + * appear (of course, iff global debug flag is ON and we match the Type and SubType). + * Finer granularity debug levels are currently not in use, although the feature exists. + * + * Another way to say this: + * All the debug prints currently have 'debug_level' set to DBG_LVL_ALL . + * You can compile-time change that to any of the below, if you wish to. However, as of now, there's + * no dynamic facility to have the userspace 'TestApp' set debug_level. Slated for future expansion. + */ +#define BCM_ALL 7 +#define BCM_LOW 6 +#define BCM_PRINT 5 +#define BCM_NORMAL 4 +#define BCM_MEDIUM 3 +#define BCM_SCREAM 2 +#define BCM_ERR 1 +/* Not meant for developer in debug prints. + * To be used to disable all prints by setting the DBG_LVL_CURR to this value */ +#define BCM_NONE 0 + +/* The current driver logging level. + * Everything at this level and (numerically) lower (meaning higher prio) + * is logged. +* Replace 'BCM_ALL' in the DBG_LVL_CURR macro with the logging level desired. + * For eg. to set the logging level to 'errors only' use: + * #define DBG_LVL_CURR (BCM_ERR) + */ + +#define DBG_LVL_CURR (BCM_ALL) +#define DBG_LVL_ALL BCM_ALL + +/*---Userspace mapping of Debug State. + * Delibrately matches that of the Windows driver.. + * The TestApp's ioctl passes this struct to us. + */ +typedef struct +{ + unsigned int Subtype, Type; + unsigned int OnOff; +// unsigned int debug_level; /* future expansion */ +} __attribute__((packed)) USER_BCM_DBG_STATE; + +//---Kernel-space mapping of Debug State +typedef struct _S_BCM_DEBUG_STATE { + UINT type; + /* A bitmap of 32 bits for Subtype per Type. + * Valid indexes in 'subtype' array are *only* 1,2,4 and 8, + * corresponding to valid Type values. Hence we use the 'Type' field + * as the index value, ignoring the array entries 0,3,5,6,7 ! + */ + UINT subtype[(NUMTYPES*2)+1]; + UINT debug_level; +} S_BCM_DEBUG_STATE; +/* Instantiated in the Adapter structure */ +/* We'll reuse the debug level parameter to include a bit (the MSB) to indicate whether or not + * we want the function's name printed. */ +#define DBG_NO_FUNC_PRINT 1 << 31 +#define DBG_LVL_BITMASK 0xFF + +//--- Only for direct printk's; "hidden" to API. +#define DBG_TYPE_PRINTK 3 + +#define BCM_DEBUG_PRINT(Adapter, Type, SubType, dbg_level, string, args...) \ + do { \ + if (DBG_TYPE_PRINTK == Type) \ + pr_info("%s:" string, __func__, ##args); \ + else if (Adapter && \ + (dbg_level & DBG_LVL_BITMASK) <= Adapter->stDebugState.debug_level && \ + (Type & Adapter->stDebugState.type) && \ + (SubType & Adapter->stDebugState.subtype[Type])) { \ + if (dbg_level & DBG_NO_FUNC_PRINT) \ + printk(KERN_DEBUG string, ##args); \ + else \ + printk(KERN_DEBUG "%s:" string, __func__, ##args); \ + } \ + } while (0) + +#define BCM_DEBUG_PRINT_BUFFER(Adapter, Type, SubType, dbg_level, buffer, bufferlen) do { \ + if (DBG_TYPE_PRINTK == Type || \ + (Adapter && \ + (dbg_level & DBG_LVL_BITMASK) <= Adapter->stDebugState.debug_level && \ + (Type & Adapter->stDebugState.type) && \ + (SubType & Adapter->stDebugState.subtype[Type]))) { \ + printk(KERN_DEBUG "%s:\n", __func__); \ + print_hex_dump(KERN_DEBUG, " ", DUMP_PREFIX_OFFSET, \ + 16, 1, buffer, bufferlen, false); \ + } \ +} while(0) + + +#define BCM_SHOW_DEBUG_BITMAP(Adapter) do { \ + int i; \ + for (i=0; i<(NUMTYPES*2)+1; i++) { \ + if ((i == 1) || (i == 2) || (i == 4) || (i == 8)) { \ + /* CAUTION! Forcefully turn on ALL debug paths and subpaths! \ + Adapter->stDebugState.subtype[i] = 0xffffffff; */ \ + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_PRINTK, 0, 0, "subtype[%d] = 0x%08x\n", \ + i, Adapter->stDebugState.subtype[i]); \ + } \ + } \ +} while (0) + +#endif + diff --git a/drivers/staging/bcm/HandleControlPacket.c b/drivers/staging/bcm/HandleControlPacket.c new file mode 100644 index 00000000..b058e30b --- /dev/null +++ b/drivers/staging/bcm/HandleControlPacket.c @@ -0,0 +1,235 @@ +/** + * @file HandleControlPacket.c + * This file contains the routines to deal with + * sending and receiving of control packets. + */ +#include "headers.h" + +/** + * When a control packet is received, analyze the + * "status" and call appropriate response function. + * Enqueue the control packet for Application. + * @return None + */ +static VOID handle_rx_control_packet(PMINI_ADAPTER Adapter, struct sk_buff *skb) +{ + PPER_TARANG_DATA pTarang = NULL; + BOOLEAN HighPriorityMessage = FALSE; + struct sk_buff *newPacket = NULL; + CHAR cntrl_msg_mask_bit = 0; + BOOLEAN drop_pkt_flag = TRUE; + USHORT usStatus = *(PUSHORT)(skb->data); + + if (netif_msg_pktdata(Adapter)) + print_hex_dump(KERN_DEBUG, PFX "rx control: ", DUMP_PREFIX_NONE, + 16, 1, skb->data, skb->len, 0); + + switch (usStatus) { + case CM_RESPONSES: /* 0xA0 */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CP_CTRL_PKT, + DBG_LVL_ALL, + "MAC Version Seems to be Non Multi-Classifier, rejected by Driver"); + HighPriorityMessage = TRUE; + break; + case CM_CONTROL_NEWDSX_MULTICLASSIFIER_RESP: + HighPriorityMessage = TRUE; + if (Adapter->LinkStatus == LINKUP_DONE) + CmControlResponseMessage(Adapter, + (skb->data + sizeof(USHORT))); + break; + case LINK_CONTROL_RESP: /* 0xA2 */ + case STATUS_RSP: /* 0xA1 */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CP_CTRL_PKT, + DBG_LVL_ALL, "LINK_CONTROL_RESP"); + HighPriorityMessage = TRUE; + LinkControlResponseMessage(Adapter, + (skb->data + sizeof(USHORT))); + break; + case STATS_POINTER_RESP: /* 0xA6 */ + HighPriorityMessage = TRUE; + StatisticsResponse(Adapter, (skb->data + sizeof(USHORT))); + break; + case IDLE_MODE_STATUS: /* 0xA3 */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CP_CTRL_PKT, + DBG_LVL_ALL, + "IDLE_MODE_STATUS Type Message Got from F/W"); + InterfaceIdleModeRespond(Adapter, (PUINT)(skb->data + + sizeof(USHORT))); + HighPriorityMessage = TRUE; + break; + + case AUTH_SS_HOST_MSG: + HighPriorityMessage = TRUE; + break; + + default: + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CP_CTRL_PKT, + DBG_LVL_ALL, "Got Default Response"); + /* Let the Application Deal with This Packet */ + break; + } + + /* Queue The Control Packet to The Application Queues */ + down(&Adapter->RxAppControlQueuelock); + + for (pTarang = Adapter->pTarangs; pTarang; pTarang = pTarang->next) { + if (Adapter->device_removed) + break; + + drop_pkt_flag = TRUE; + /* + * There are cntrl msg from A0 to AC. It has been mapped to 0 to + * C bit in the cntrl mask. + * Also, by default AD to BF has been masked to the rest of the + * bits... which wil be ON by default. + * if mask bit is enable to particular pkt status, send it out + * to app else stop it. + */ + cntrl_msg_mask_bit = (usStatus & 0x1F); + /* + * printk("\ninew msg mask bit which is disable in mask:%X", + * cntrl_msg_mask_bit); + */ + if (pTarang->RxCntrlMsgBitMask & (1 << cntrl_msg_mask_bit)) + drop_pkt_flag = FALSE; + + if ((drop_pkt_flag == TRUE) || + (pTarang->AppCtrlQueueLen > MAX_APP_QUEUE_LEN) + || ((pTarang->AppCtrlQueueLen > + MAX_APP_QUEUE_LEN / 2) && + (HighPriorityMessage == FALSE))) { + /* + * Assumption:- + * 1. every tarang manages it own dropped pkt + * statitistics + * 2. Total packet dropped per tarang will be equal to + * the sum of all types of dropped pkt by that + * tarang only. + */ + switch (*(PUSHORT)skb->data) { + case CM_RESPONSES: + pTarang->stDroppedAppCntrlMsgs.cm_responses++; + break; + case CM_CONTROL_NEWDSX_MULTICLASSIFIER_RESP: + pTarang->stDroppedAppCntrlMsgs.cm_control_newdsx_multiclassifier_resp++; + break; + case LINK_CONTROL_RESP: + pTarang->stDroppedAppCntrlMsgs.link_control_resp++; + break; + case STATUS_RSP: + pTarang->stDroppedAppCntrlMsgs.status_rsp++; + break; + case STATS_POINTER_RESP: + pTarang->stDroppedAppCntrlMsgs.stats_pointer_resp++; + break; + case IDLE_MODE_STATUS: + pTarang->stDroppedAppCntrlMsgs.idle_mode_status++; + break; + case AUTH_SS_HOST_MSG: + pTarang->stDroppedAppCntrlMsgs.auth_ss_host_msg++; + break; + default: + pTarang->stDroppedAppCntrlMsgs.low_priority_message++; + break; + } + + continue; + } + + newPacket = skb_clone(skb, GFP_KERNEL); + if (!newPacket) + break; + ENQUEUEPACKET(pTarang->RxAppControlHead, + pTarang->RxAppControlTail, newPacket); + pTarang->AppCtrlQueueLen++; + } + up(&Adapter->RxAppControlQueuelock); + wake_up(&Adapter->process_read_wait_queue); + dev_kfree_skb(skb); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CP_CTRL_PKT, DBG_LVL_ALL, + "After wake_up_interruptible"); +} + +/** + * @ingroup ctrl_pkt_functions + * Thread to handle control pkt reception + */ +int control_packet_handler(PMINI_ADAPTER Adapter /* pointer to adapter object*/) +{ + struct sk_buff *ctrl_packet = NULL; + unsigned long flags = 0; + /* struct timeval tv; */ + /* int *puiBuffer = NULL; */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CP_CTRL_PKT, DBG_LVL_ALL, + "Entering to make thread wait on control packet event!"); + while (1) { + wait_event_interruptible(Adapter->process_rx_cntrlpkt, + atomic_read(&Adapter->cntrlpktCnt) || + Adapter->bWakeUpDevice || + kthread_should_stop()); + + + if (kthread_should_stop()) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CP_CTRL_PKT, + DBG_LVL_ALL, "Exiting\n"); + return 0; + } + if (TRUE == Adapter->bWakeUpDevice) { + Adapter->bWakeUpDevice = FALSE; + if ((FALSE == Adapter->bTriedToWakeUpFromlowPowerMode) + && ((TRUE == Adapter->IdleMode) || + (TRUE == Adapter->bShutStatus))) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, + CP_CTRL_PKT, DBG_LVL_ALL, + "Calling InterfaceAbortIdlemode\n"); + /* + * Adapter->bTriedToWakeUpFromlowPowerMode + * = TRUE; + */ + InterfaceIdleModeWakeup(Adapter); + } + continue; + } + + while (atomic_read(&Adapter->cntrlpktCnt)) { + spin_lock_irqsave(&Adapter->control_queue_lock, flags); + ctrl_packet = Adapter->RxControlHead; + if (ctrl_packet) { + DEQUEUEPACKET(Adapter->RxControlHead, + Adapter->RxControlTail); + /* Adapter->RxControlHead=ctrl_packet->next; */ + } + + spin_unlock_irqrestore(&Adapter->control_queue_lock, + flags); + handle_rx_control_packet(Adapter, ctrl_packet); + atomic_dec(&Adapter->cntrlpktCnt); + } + + SetUpTargetDsxBuffers(Adapter); + } + return STATUS_SUCCESS; +} + +INT flushAllAppQ(void) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + PPER_TARANG_DATA pTarang = NULL; + struct sk_buff *PacketToDrop = NULL; + for (pTarang = Adapter->pTarangs; pTarang; pTarang = pTarang->next) { + while (pTarang->RxAppControlHead != NULL) { + PacketToDrop = pTarang->RxAppControlHead; + DEQUEUEPACKET(pTarang->RxAppControlHead, + pTarang->RxAppControlTail); + dev_kfree_skb(PacketToDrop); + } + pTarang->AppCtrlQueueLen = 0; + /* dropped contrl packet statistics also should be reset. */ + memset((PVOID)&pTarang->stDroppedAppCntrlMsgs, 0, + sizeof(S_MIBS_DROPPED_APP_CNTRL_MESSAGES)); + + } + return STATUS_SUCCESS; +} + + diff --git a/drivers/staging/bcm/HostMIBSInterface.h b/drivers/staging/bcm/HostMIBSInterface.h new file mode 100644 index 00000000..e34531b6 --- /dev/null +++ b/drivers/staging/bcm/HostMIBSInterface.h @@ -0,0 +1,230 @@ + + +#ifndef _HOST_MIBSINTERFACE_H +#define _HOST_MIBSINTERFACE_H + +/* + * Copyright (c) 2007 Beceem Communications Pvt. Ltd + * File Name: HostMIBSInterface.h + * Abstract: This file contains DS used by the Host to update the Host + * statistics used for the MIBS. + */ + +#define MIBS_MAX_CLASSIFIERS 100 +#define MIBS_MAX_PHSRULES 100 +#define MIBS_MAX_SERVICEFLOWS 17 +#define MIBS_MAX_IP_RANGE_LENGTH 4 +#define MIBS_MAX_PORT_RANGE 4 +#define MIBS_MAX_PROTOCOL_LENGTH 32 +#define MIBS_MAX_PHS_LENGTHS 255 +#define MIBS_IPV6_ADDRESS_SIZEINBYTES 0x10 +#define MIBS_IP_LENGTH_OF_ADDRESS 4 +#define MIBS_MAX_HIST_ENTRIES 12 +#define MIBS_PKTSIZEHIST_RANGE 128 + +typedef union _U_MIBS_IP_ADDRESS +{ + struct + { + //Source Ip Address Range + ULONG ulIpv4Addr[MIBS_MAX_IP_RANGE_LENGTH]; + //Source Ip Mask Address Range + ULONG ulIpv4Mask[MIBS_MAX_IP_RANGE_LENGTH]; + }; + struct + { + //Source Ip Address Range + ULONG ulIpv6Addr[MIBS_MAX_IP_RANGE_LENGTH * 4]; + //Source Ip Mask Address Range + ULONG ulIpv6Mask[MIBS_MAX_IP_RANGE_LENGTH * 4]; + + }; + struct + { + UCHAR ucIpv4Address[MIBS_MAX_IP_RANGE_LENGTH * + MIBS_IP_LENGTH_OF_ADDRESS]; + UCHAR ucIpv4Mask[MIBS_MAX_IP_RANGE_LENGTH * + MIBS_IP_LENGTH_OF_ADDRESS]; + }; + struct + { + UCHAR ucIpv6Address[MIBS_MAX_IP_RANGE_LENGTH * MIBS_IPV6_ADDRESS_SIZEINBYTES]; + UCHAR ucIpv6Mask[MIBS_MAX_IP_RANGE_LENGTH * MIBS_IPV6_ADDRESS_SIZEINBYTES]; + }; +}U_MIBS_IP_ADDRESS; + + +typedef struct _S_MIBS_HOST_INFO +{ + ULONG64 GoodTransmits; + ULONG64 GoodReceives; + // this to keep track of the Tx and Rx MailBox Registers. + ULONG NumDesUsed; + ULONG CurrNumFreeDesc; + ULONG PrevNumFreeDesc; + // to keep track the no of byte received + ULONG PrevNumRcevBytes; + ULONG CurrNumRcevBytes; + + /* QOS Related */ + ULONG BEBucketSize; + ULONG rtPSBucketSize; + ULONG LastTxQueueIndex; + BOOLEAN TxOutofDescriptors; + BOOLEAN TimerActive; + UINT32 u32TotalDSD; + UINT32 aTxPktSizeHist[MIBS_MAX_HIST_ENTRIES]; + UINT32 aRxPktSizeHist[MIBS_MAX_HIST_ENTRIES]; +}S_MIBS_HOST_INFO; + +typedef struct _S_MIBS_CLASSIFIER_RULE +{ + ULONG ulSFID; + UCHAR ucReserved[2]; + B_UINT16 uiClassifierRuleIndex; + BOOLEAN bUsed; + USHORT usVCID_Value; + // This field detemines the Classifier Priority + B_UINT8 u8ClassifierRulePriority; + U_MIBS_IP_ADDRESS stSrcIpAddress; + /*IP Source Address Length*/ + UCHAR ucIPSourceAddressLength; + + U_MIBS_IP_ADDRESS stDestIpAddress; + /* IP Destination Address Length */ + UCHAR ucIPDestinationAddressLength; + UCHAR ucIPTypeOfServiceLength;//Type of service Length + UCHAR ucTosLow;//Tos Low + UCHAR ucTosHigh;//Tos High + UCHAR ucTosMask;//Tos Mask + UCHAR ucProtocolLength;//protocol Length + UCHAR ucProtocol[MIBS_MAX_PROTOCOL_LENGTH];//protocol Length + USHORT usSrcPortRangeLo[MIBS_MAX_PORT_RANGE]; + USHORT usSrcPortRangeHi[MIBS_MAX_PORT_RANGE]; + UCHAR ucSrcPortRangeLength; + USHORT usDestPortRangeLo[MIBS_MAX_PORT_RANGE]; + USHORT usDestPortRangeHi[MIBS_MAX_PORT_RANGE]; + UCHAR ucDestPortRangeLength; + BOOLEAN bProtocolValid; + BOOLEAN bTOSValid; + BOOLEAN bDestIpValid; + BOOLEAN bSrcIpValid; + UCHAR ucDirection; + BOOLEAN bIpv6Protocol; + UINT32 u32PHSRuleID; +}S_MIBS_CLASSIFIER_RULE; + + +typedef struct _S_MIBS_PHS_RULE +{ + ULONG ulSFID; + /// brief 8bit PHSI Of The Service Flow + B_UINT8 u8PHSI; + /// brief PHSF Of The Service Flow + B_UINT8 u8PHSFLength; + B_UINT8 u8PHSF[MIBS_MAX_PHS_LENGTHS]; + /// brief PHSM Of The Service Flow + B_UINT8 u8PHSMLength; + B_UINT8 u8PHSM[MIBS_MAX_PHS_LENGTHS]; + /// brief 8bit PHSS Of The Service Flow + B_UINT8 u8PHSS; + /// brief 8bit PHSV Of The Service Flow + B_UINT8 u8PHSV; + // Reserved bytes are 5, so that it is similar to S_PHS_RULE structure. + B_UINT8 reserved[5]; + + LONG PHSModifiedBytes; + ULONG PHSModifiedNumPackets; + ULONG PHSErrorNumPackets; +}S_MIBS_PHS_RULE; + +typedef struct _S_MIBS_EXTSERVICEFLOW_PARAMETERS +{ + UINT32 wmanIfSfid; + UINT32 wmanIfCmnCpsSfState; + UINT32 wmanIfCmnCpsMaxSustainedRate; + UINT32 wmanIfCmnCpsMaxTrafficBurst; + UINT32 wmanIfCmnCpsMinReservedRate; + UINT32 wmanIfCmnCpsToleratedJitter; + UINT32 wmanIfCmnCpsMaxLatency; + UINT32 wmanIfCmnCpsFixedVsVariableSduInd; + UINT32 wmanIfCmnCpsSduSize; + UINT32 wmanIfCmnCpsSfSchedulingType; + UINT32 wmanIfCmnCpsArqEnable; + UINT32 wmanIfCmnCpsArqWindowSize; + UINT32 wmanIfCmnCpsArqBlockLifetime; + UINT32 wmanIfCmnCpsArqSyncLossTimeout; + UINT32 wmanIfCmnCpsArqDeliverInOrder; + UINT32 wmanIfCmnCpsArqRxPurgeTimeout; + UINT32 wmanIfCmnCpsArqBlockSize; + UINT32 wmanIfCmnCpsMinRsvdTolerableRate; + UINT32 wmanIfCmnCpsReqTxPolicy; + UINT32 wmanIfCmnSfCsSpecification; + UINT32 wmanIfCmnCpsTargetSaid; + +}S_MIBS_EXTSERVICEFLOW_PARAMETERS; + + +typedef struct _S_MIBS_SERVICEFLOW_TABLE +{ + //classification extension Rule + ULONG ulSFID; + USHORT usVCID_Value; + UINT uiThreshold; + // This field determines the priority of the SF Queues + B_UINT8 u8TrafficPriority; + + BOOLEAN bValid; + BOOLEAN bActive; + BOOLEAN bActivateRequestSent; + //BE or rtPS + B_UINT8 u8QueueType; + //maximum size of the bucket for the queue + UINT uiMaxBucketSize; + UINT uiCurrentQueueDepthOnTarget; + UINT uiCurrentBytesOnHost; + UINT uiCurrentPacketsOnHost; + UINT uiDroppedCountBytes; + UINT uiDroppedCountPackets; + UINT uiSentBytes; + UINT uiSentPackets; + UINT uiCurrentDrainRate; + UINT uiThisPeriodSentBytes; + LARGE_INTEGER liDrainCalculated; + UINT uiCurrentTokenCount; + LARGE_INTEGER liLastUpdateTokenAt; + UINT uiMaxAllowedRate; + UINT NumOfPacketsSent; + UCHAR ucDirection; + USHORT usCID; + S_MIBS_EXTSERVICEFLOW_PARAMETERS stMibsExtServiceFlowTable; + UINT uiCurrentRxRate; + UINT uiThisPeriodRxBytes; + UINT uiTotalRxBytes; + UINT uiTotalTxBytes; +}S_MIBS_SERVICEFLOW_TABLE; + +typedef struct _S_MIBS_DROPPED_APP_CNTRL_MESSAGES +{ + ULONG cm_responses; + ULONG cm_control_newdsx_multiclassifier_resp; + ULONG link_control_resp; + ULONG status_rsp; + ULONG stats_pointer_resp; + ULONG idle_mode_status; + ULONG auth_ss_host_msg; + ULONG low_priority_message; + +}S_MIBS_DROPPED_APP_CNTRL_MESSAGES; + +typedef struct _S_MIBS_HOST_STATS_MIBS +{ + S_MIBS_HOST_INFO stHostInfo; + S_MIBS_CLASSIFIER_RULE astClassifierTable[MIBS_MAX_CLASSIFIERS]; + S_MIBS_SERVICEFLOW_TABLE astSFtable[MIBS_MAX_SERVICEFLOWS]; + S_MIBS_PHS_RULE astPhsRulesTable[MIBS_MAX_PHSRULES]; + S_MIBS_DROPPED_APP_CNTRL_MESSAGES stDroppedAppCntrlMsgs; +}S_MIBS_HOST_STATS_MIBS; +#endif + + diff --git a/drivers/staging/bcm/IPv6Protocol.c b/drivers/staging/bcm/IPv6Protocol.c new file mode 100644 index 00000000..5b4fd372 --- /dev/null +++ b/drivers/staging/bcm/IPv6Protocol.c @@ -0,0 +1,404 @@ +#include "headers.h" + +static BOOLEAN MatchSrcIpv6Address(S_CLASSIFIER_RULE *pstClassifierRule,IPV6Header *pstIpv6Header); +static BOOLEAN MatchDestIpv6Address(S_CLASSIFIER_RULE *pstClassifierRule,IPV6Header *pstIpv6Header); +static VOID DumpIpv6Header(IPV6Header *pstIpv6Header); + +static UCHAR * GetNextIPV6ChainedHeader(UCHAR **ppucPayload,UCHAR *pucNextHeader,BOOLEAN *bParseDone,USHORT *pusPayloadLength) +{ + UCHAR *pucRetHeaderPtr = NULL; + UCHAR *pucPayloadPtr = NULL; + USHORT usNextHeaderOffset = 0 ; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + if((NULL == ppucPayload) || (*pusPayloadLength == 0) || (*bParseDone)) + { + *bParseDone = TRUE; + return NULL; + + } + + pucRetHeaderPtr = *ppucPayload; + pucPayloadPtr = *ppucPayload; + + if(!pucRetHeaderPtr || !pucPayloadPtr) + { + *bParseDone = TRUE; + return NULL; + } + + //Get the Nextt Header Type + *bParseDone = FALSE; + + + + switch(*pucNextHeader) + { + case IPV6HDR_TYPE_HOPBYHOP: + { + + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 HopByHop Header"); + usNextHeaderOffset+=sizeof(IPV6HopByHopOptionsHeader); + } + break; + + case IPV6HDR_TYPE_ROUTING: + { + IPV6RoutingHeader *pstIpv6RoutingHeader; + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 Routing Header"); + pstIpv6RoutingHeader = (IPV6RoutingHeader *)pucPayloadPtr; + usNextHeaderOffset += sizeof(IPV6RoutingHeader); + usNextHeaderOffset += pstIpv6RoutingHeader->ucNumAddresses * IPV6_ADDRESS_SIZEINBYTES; + + } + break; + case IPV6HDR_TYPE_FRAGMENTATION: + { + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 Fragmentation Header"); + usNextHeaderOffset+= sizeof(IPV6FragmentHeader); + + } + break; + case IPV6HDR_TYPE_DESTOPTS: + { + IPV6DestOptionsHeader *pstIpv6DestOptsHdr = (IPV6DestOptionsHeader *)pucPayloadPtr; + int nTotalOptions = pstIpv6DestOptsHdr->ucHdrExtLen; + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 DestOpts Header Header"); + usNextHeaderOffset+= sizeof(IPV6DestOptionsHeader); + usNextHeaderOffset+= nTotalOptions * IPV6_DESTOPTS_HDR_OPTIONSIZE ; + + } + break; + case IPV6HDR_TYPE_AUTHENTICATION: + { + IPV6AuthenticationHeader *pstIpv6AuthHdr = (IPV6AuthenticationHeader *)pucPayloadPtr; + int nHdrLen = pstIpv6AuthHdr->ucLength; + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 Authentication Header"); + usNextHeaderOffset+= nHdrLen * 4; + } + break; + case IPV6HDR_TYPE_ENCRYPTEDSECURITYPAYLOAD: + { + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 Encrypted Security Payload Header"); + *bParseDone = TRUE; + + } + break; + case IPV6_ICMP_HDR_TYPE: + { + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, " ICMP Header"); + *bParseDone = TRUE; + } + break; + case TCP_HEADER_TYPE: + { + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, " \nTCP Header"); + *bParseDone = TRUE; + } + break; + case UDP_HEADER_TYPE: + { + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, " \nUDP Header"); + *bParseDone = TRUE; + } + break; + default : + { + *bParseDone = TRUE; + + } + break; + + + } + + if(*bParseDone == FALSE) + { + if(*pusPayloadLength <= usNextHeaderOffset) + { + *bParseDone = TRUE; + } + else + { + *pucNextHeader = *pucPayloadPtr; + pucPayloadPtr+=usNextHeaderOffset; + (*pusPayloadLength)-=usNextHeaderOffset; + } + + } + + + + *ppucPayload = pucPayloadPtr; + return pucRetHeaderPtr; +} + + +static UCHAR GetIpv6ProtocolPorts(UCHAR *pucPayload,USHORT *pusSrcPort,USHORT *pusDestPort,USHORT usPayloadLength,UCHAR ucNextHeader) +{ + UCHAR *pIpv6HdrScanContext = pucPayload; + BOOLEAN bDone = FALSE; + UCHAR ucHeaderType =0; + UCHAR *pucNextHeader = NULL; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + if( !pucPayload || (usPayloadLength == 0)) + { + return 0; + } + + *pusSrcPort = *pusDestPort = 0; + ucHeaderType = ucNextHeader; + while(!bDone) + { + pucNextHeader = GetNextIPV6ChainedHeader(&pIpv6HdrScanContext,&ucHeaderType,&bDone,&usPayloadLength); + if(bDone) + { + if((ucHeaderType==TCP_HEADER_TYPE) || (ucHeaderType == UDP_HEADER_TYPE)) + { + *pusSrcPort=*((PUSHORT)(pucNextHeader)); + *pusDestPort=*((PUSHORT)(pucNextHeader+2)); + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, " \nProtocol Ports - Src Port :0x%x Dest Port : 0x%x",ntohs(*pusSrcPort),ntohs(*pusDestPort)); + } + break; + + } + } + return ucHeaderType; +} + + + +USHORT IpVersion6(PMINI_ADAPTER Adapter, /**< Pointer to the driver control structure */ + PVOID pcIpHeader, /**\n"); + + pstIpv6Header = (IPV6Header *)pcIpHeader; + + DumpIpv6Header(pstIpv6Header); + + //Try to get the next higher layer protocol and the Ports Nos if TCP or UDP + ucNextProtocolAboveIP = GetIpv6ProtocolPorts((UCHAR *)(pcIpHeader + sizeof(IPV6Header)), + &ushSrcPort, + &ushDestPort, + pstIpv6Header->usPayloadLength, + pstIpv6Header->ucNextHeader); + + do + { + if(0 == pstClassifierRule->ucDirection) + { + //cannot be processed for classification. + // it is a down link connection + break; + } + + if(!pstClassifierRule->bIpv6Protocol) + { + //We are looking for Ipv6 Classifiers . Lets ignore this classifier and try the next one. + break; + } + + bClassificationSucceed=MatchSrcIpv6Address(pstClassifierRule,pstIpv6Header); + if(!bClassificationSucceed) + break; + + bClassificationSucceed=MatchDestIpv6Address(pstClassifierRule,pstIpv6Header); + if(!bClassificationSucceed) + break; + + //Match the protocol type.For IPv6 the next protocol at end of Chain of IPv6 prot headers + bClassificationSucceed=MatchProtocol(pstClassifierRule,ucNextProtocolAboveIP); + if(!bClassificationSucceed) + break; + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 Protocol Matched"); + + if((ucNextProtocolAboveIP == TCP_HEADER_TYPE) || (ucNextProtocolAboveIP == UDP_HEADER_TYPE)) + { + //Match Src Port + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 Source Port:%x\n",ntohs(ushSrcPort)); + bClassificationSucceed=MatchSrcPort(pstClassifierRule,ntohs(ushSrcPort)); + if(!bClassificationSucceed) + break; + + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 Src Port Matched"); + + //Match Dest Port + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 Destination Port:%x\n",ntohs(ushDestPort)); + bClassificationSucceed=MatchDestPort(pstClassifierRule,ntohs(ushDestPort)); + if(!bClassificationSucceed) + break; + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\nIPv6 Dest Port Matched"); + } + }while(0); + + if(TRUE==bClassificationSucceed) + { + INT iMatchedSFQueueIndex = 0; + iMatchedSFQueueIndex = SearchSfid(Adapter,pstClassifierRule->ulSFID); + if(iMatchedSFQueueIndex >= NO_OF_QUEUES) + { + bClassificationSucceed = FALSE; + } + else + { + if(FALSE == Adapter->PackInfo[iMatchedSFQueueIndex].bActive) + { + bClassificationSucceed = FALSE; + } + } + } + + return bClassificationSucceed; +} + + +static BOOLEAN MatchSrcIpv6Address(S_CLASSIFIER_RULE *pstClassifierRule,IPV6Header *pstIpv6Header) +{ + UINT uiLoopIndex=0; + UINT uiIpv6AddIndex=0; + UINT uiIpv6AddrNoLongWords = 4; + ULONG aulSrcIP[4]; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + /* + //This is the no. of Src Addresses ie Range of IP Addresses contained + //in the classifier rule for which we need to match + */ + UINT uiCountIPSrcAddresses = (UINT)pstClassifierRule->ucIPSourceAddressLength; + + + if(0 == uiCountIPSrcAddresses) + return TRUE; + + + //First Convert the Ip Address in the packet to Host Endian order + for(uiIpv6AddIndex=0;uiIpv6AddIndexulSrcIpAddress[uiIpv6AddIndex]); + } + + for(uiLoopIndex=0;uiLoopIndexstSrcIpAddress.ulIpv6Mask[uiLoopIndex]); + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\n Src Ipv6 Address In Classifier Rule : \n"); + DumpIpv6Address(&pstClassifierRule->stSrcIpAddress.ulIpv6Addr[uiLoopIndex]); + + for(uiIpv6AddIndex=0;uiIpv6AddIndexstSrcIpAddress.ulIpv6Mask[uiLoopIndex+uiIpv6AddIndex] & aulSrcIP[uiIpv6AddIndex]) + != pstClassifierRule->stSrcIpAddress.ulIpv6Addr[uiLoopIndex+uiIpv6AddIndex]) + { + //Match failed for current Ipv6 Address.Try next Ipv6 Address + break; + } + + if(uiIpv6AddIndex == uiIpv6AddrNoLongWords-1) + { + //Match Found + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Ipv6 Src Ip Address Matched\n"); + return TRUE; + } + } + } + return FALSE; +} + +static BOOLEAN MatchDestIpv6Address(S_CLASSIFIER_RULE *pstClassifierRule,IPV6Header *pstIpv6Header) +{ + UINT uiLoopIndex=0; + UINT uiIpv6AddIndex=0; + UINT uiIpv6AddrNoLongWords = 4; + ULONG aulDestIP[4]; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + /* + //This is the no. of Destination Addresses ie Range of IP Addresses contained + //in the classifier rule for which we need to match + */ + UINT uiCountIPDestinationAddresses = (UINT)pstClassifierRule->ucIPDestinationAddressLength; + + + if(0 == uiCountIPDestinationAddresses) + return TRUE; + + + //First Convert the Ip Address in the packet to Host Endian order + for(uiIpv6AddIndex=0;uiIpv6AddIndexulDestIpAddress[uiIpv6AddIndex]); + } + + for(uiLoopIndex=0;uiLoopIndexstDestIpAddress.ulIpv6Mask[uiLoopIndex]); + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "\n Destination Ipv6 Address In Classifier Rule : \n"); + DumpIpv6Address(&pstClassifierRule->stDestIpAddress.ulIpv6Addr[uiLoopIndex]); + + for(uiIpv6AddIndex=0;uiIpv6AddIndexstDestIpAddress.ulIpv6Mask[uiLoopIndex+uiIpv6AddIndex] & aulDestIP[uiIpv6AddIndex]) + != pstClassifierRule->stDestIpAddress.ulIpv6Addr[uiLoopIndex+uiIpv6AddIndex]) + { + //Match failed for current Ipv6 Address.Try next Ipv6 Address + break; + } + + if(uiIpv6AddIndex == uiIpv6AddrNoLongWords-1) + { + //Match Found + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Ipv6 Destination Ip Address Matched\n"); + return TRUE; + } + } + } + return FALSE; + +} + +VOID DumpIpv6Address(ULONG *puIpv6Address) +{ + UINT uiIpv6AddrNoLongWords = 4; + UINT uiIpv6AddIndex=0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + for(uiIpv6AddIndex=0;uiIpv6AddIndexucVersionPrio & 0xf0; + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Version : %x \n",ucVersion); + ucPrio = pstIpv6Header->ucVersionPrio & 0x0f; + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Priority : %x \n",ucPrio); + //BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Flow Label : %x \n",(pstIpv6Header->ucVersionPrio &0xf0); + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Payload Length : %x \n",ntohs(pstIpv6Header->usPayloadLength)); + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Next Header : %x \n",pstIpv6Header->ucNextHeader); + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Hop Limit : %x \n",pstIpv6Header->ucHopLimit); + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Src Address :\n"); + DumpIpv6Address(pstIpv6Header->ulSrcIpAddress); + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "Dest Address :\n"); + DumpIpv6Address(pstIpv6Header->ulDestIpAddress); + BCM_DEBUG_PRINT( Adapter,DBG_TYPE_TX, IPV6_DBG, DBG_LVL_ALL, "----Ipv6 Header End---"); + + +} diff --git a/drivers/staging/bcm/IPv6ProtocolHdr.h b/drivers/staging/bcm/IPv6ProtocolHdr.h new file mode 100644 index 00000000..a0db5a1d --- /dev/null +++ b/drivers/staging/bcm/IPv6ProtocolHdr.h @@ -0,0 +1,116 @@ +#ifndef _IPV6_PROTOCOL_DEFINES_ +#define _IPV6_PROTOCOL_DEFINES_ + + +#define IPV6HDR_TYPE_HOPBYHOP 0x0 +#define IPV6HDR_TYPE_ROUTING 0x2B +#define IPV6HDR_TYPE_FRAGMENTATION 0x2C +#define IPV6HDR_TYPE_DESTOPTS 0x3c +#define IPV6HDR_TYPE_AUTHENTICATION 0x33 +#define IPV6HDR_TYPE_ENCRYPTEDSECURITYPAYLOAD 0x34 +#define MASK_IPV6_CS_SPEC 0x2 + + +#define TCP_HEADER_TYPE 0x6 +#define UDP_HEADER_TYPE 0x11 +#define IPV6_ICMP_HDR_TYPE 0x2 +#define IPV6_FLOWLABEL_BITOFFSET 9 + +#define IPV6_MAX_CHAINEDHDR_BUFFBYTES 0x64 +/* +// Size of Dest Options field of Destinations Options Header +// in bytes. +*/ +#define IPV6_DESTOPTS_HDR_OPTIONSIZE 0x8 + +//typedef unsigned char UCHAR; +//typedef unsigned short USHORT; +//typedef unsigned long int ULONG; + +typedef struct IPV6HeaderFormatTag +{ + UCHAR ucVersionPrio; + UCHAR aucFlowLabel[3]; + USHORT usPayloadLength; + UCHAR ucNextHeader; + UCHAR ucHopLimit; + ULONG ulSrcIpAddress[4]; + ULONG ulDestIpAddress[4]; +}IPV6Header; + +typedef struct IPV6RoutingHeaderFormatTag +{ + UCHAR ucNextHeader; + UCHAR ucRoutingType; + UCHAR ucNumAddresses; + UCHAR ucNextAddress; + ULONG ulReserved; + //UCHAR aucAddressList[0]; + +}IPV6RoutingHeader; + +typedef struct IPV6FragmentHeaderFormatTag +{ + UCHAR ucNextHeader; + UCHAR ucReserved; + USHORT usFragmentOffset; + ULONG ulIdentification; +}IPV6FragmentHeader; + +typedef struct IPV6DestOptionsHeaderFormatTag +{ + UCHAR ucNextHeader; + UCHAR ucHdrExtLen; + UCHAR ucDestOptions[6]; + //UCHAR udExtDestOptions[0]; +}IPV6DestOptionsHeader; + +typedef struct IPV6HopByHopOptionsHeaderFormatTag +{ + UCHAR ucNextHeader; + UCHAR ucMisc[3]; + ULONG ulJumboPayloadLen; +}IPV6HopByHopOptionsHeader; + +typedef struct IPV6AuthenticationHeaderFormatTag +{ + UCHAR ucNextHeader; + UCHAR ucLength; + USHORT usReserved; + ULONG ulSecurityParametersIndex; + //UCHAR ucAuthenticationData[0]; + +}IPV6AuthenticationHeader; + +typedef struct IPV6IcmpHeaderFormatTag +{ + UCHAR ucType; + UCHAR ucCode; + USHORT usChecksum; + //UCHAR ucIcmpMsg[0]; + +}IPV6IcmpHeader; + +typedef enum _E_IPADDR_CONTEXT +{ + eSrcIpAddress, + eDestIpAddress + +}E_IPADDR_CONTEXT; + + + +//Function Prototypes + +USHORT IpVersion6(PMINI_ADAPTER Adapter, /**< Pointer to the driver control structure */ + PVOID pcIpHeader, /**psAdapter; */ + char *buff = kmalloc(MAX_TRANSFER_CTRL_BYTE_USB, GFP_KERNEL); + + if (!buff) + return -ENOMEM; + + while (1) { + oldfs = get_fs(); + set_fs(get_ds()); + len = vfs_read(flp, (void __force __user *)buff, + MAX_TRANSFER_CTRL_BYTE_USB, &pos); + set_fs(oldfs); + if (len <= 0) { + if (len < 0) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, + DBG_TYPE_INITEXIT, MP_INIT, + DBG_LVL_ALL, "len < 0"); + errno = len; + } else { + errno = 0; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, + DBG_TYPE_INITEXIT, MP_INIT, + DBG_LVL_ALL, + "Got end of file!"); + } + break; + } + /* BCM_DEBUG_PRINT_BUFFER(Adapter,DBG_TYPE_INITEXIT, MP_INIT, + * DBG_LVL_ALL, buff, + * MAX_TRANSFER_CTRL_BYTE_USB); + */ + errno = InterfaceWRM(psIntfAdapter, on_chip_loc, buff, len); + if (errno) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, + DBG_TYPE_PRINTK, 0, 0, + "WRM Failed! status: %d", errno); + break; + } + on_chip_loc += MAX_TRANSFER_CTRL_BYTE_USB; + } + + kfree(buff); + return errno; +} + +int InterfaceFileReadbackFromChip(PVOID arg, struct file *flp, unsigned int on_chip_loc) +{ + char *buff, *buff_readback; + unsigned int reg = 0; + mm_segment_t oldfs = {0}; + int errno = 0, len = 0, is_config_file = 0; + loff_t pos = 0; + static int fw_down; + INT Status = STATUS_SUCCESS; + PS_INTERFACE_ADAPTER psIntfAdapter = (PS_INTERFACE_ADAPTER)arg; + int bytes; + + buff = kmalloc(MAX_TRANSFER_CTRL_BYTE_USB, GFP_DMA); + buff_readback = kmalloc(MAX_TRANSFER_CTRL_BYTE_USB , GFP_DMA); + if (!buff || !buff_readback) { + kfree(buff); + kfree(buff_readback); + + return -ENOMEM; + } + + is_config_file = (on_chip_loc == CONFIG_BEGIN_ADDR) ? 1 : 0; + + memset(buff_readback, 0, MAX_TRANSFER_CTRL_BYTE_USB); + memset(buff, 0, MAX_TRANSFER_CTRL_BYTE_USB); + while (1) { + oldfs = get_fs(); + set_fs(get_ds()); + len = vfs_read(flp, (void __force __user *)buff, MAX_TRANSFER_CTRL_BYTE_USB, &pos); + set_fs(oldfs); + fw_down++; + + if (len <= 0) { + if (len < 0) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "len < 0"); + errno = len; + } else { + errno = 0; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Got end of file!"); + } + break; + } + + bytes = InterfaceRDM(psIntfAdapter, on_chip_loc, buff_readback, len); + if (bytes < 0) { + Status = bytes; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "RDM of len %d Failed! %d", len, reg); + goto exit; + } + reg++; + if ((len-sizeof(unsigned int)) < 4) { + if (memcmp(buff_readback, buff, len)) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Firmware Download is not proper %d", fw_down); + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Length is: %d", len); + Status = -EIO; + goto exit; + } + } else { + len -= 4; + + while (len) { + if (*(unsigned int *)&buff_readback[len] != *(unsigned int *)&buff[len]) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Firmware Download is not proper %d", fw_down); + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Val from Binary %x, Val From Read Back %x ", *(unsigned int *)&buff[len], *(unsigned int*)&buff_readback[len]); + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "len =%x!!!", len); + Status = -EIO; + goto exit; + } + len -= 4; + } + } + on_chip_loc += MAX_TRANSFER_CTRL_BYTE_USB; + } /* End of while(1) */ + +exit: + kfree(buff); + kfree(buff_readback); + return Status; +} + +static int bcm_download_config_file(PMINI_ADAPTER Adapter, FIRMWARE_INFO *psFwInfo) +{ + int retval = STATUS_SUCCESS; + B_UINT32 value = 0; + + if (Adapter->pstargetparams == NULL) { + Adapter->pstargetparams = kmalloc(sizeof(STARGETPARAMS), GFP_KERNEL); + if (Adapter->pstargetparams == NULL) + return -ENOMEM; + } + + if (psFwInfo->u32FirmwareLength != sizeof(STARGETPARAMS)) + return -EIO; + + retval = copy_from_user(Adapter->pstargetparams, psFwInfo->pvMappedFirmwareAddress, psFwInfo->u32FirmwareLength); + if (retval) { + kfree(Adapter->pstargetparams); + Adapter->pstargetparams = NULL; + return -EFAULT; + } + + /* Parse the structure and then Download the Firmware */ + beceem_parse_target_struct(Adapter); + + /* Initializing the NVM. */ + BcmInitNVM(Adapter); + retval = InitLedSettings(Adapter); + + if (retval) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "INIT LED Failed\n"); + return retval; + } + + if (Adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + Adapter->LEDInfo.bLedInitDone = FALSE; + Adapter->DriverState = DRIVER_INIT; + wake_up(&Adapter->LEDInfo.notify_led_event); + } + + if (Adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + Adapter->DriverState = FW_DOWNLOAD; + wake_up(&Adapter->LEDInfo.notify_led_event); + } + + /* Initialize the DDR Controller */ + retval = ddr_init(Adapter); + if (retval) { + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "DDR Init Failed\n"); + return retval; + } + + value = 0; + wrmalt(Adapter, EEPROM_CAL_DATA_INTERNAL_LOC - 4, &value, sizeof(value)); + wrmalt(Adapter, EEPROM_CAL_DATA_INTERNAL_LOC - 8, &value, sizeof(value)); + + if (Adapter->eNVMType == NVM_FLASH) { + retval = PropagateCalParamsFromFlashToMemory(Adapter); + if (retval) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "propagaion of cal param failed with status :%d", retval); + return retval; + } + } + + retval = buffDnldVerify(Adapter, (PUCHAR)Adapter->pstargetparams, sizeof(STARGETPARAMS), CONFIG_BEGIN_ADDR); + + if (retval) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "configuration file not downloaded properly"); + else + Adapter->bCfgDownloaded = TRUE; + + return retval; +} + +static int bcm_compare_buff_contents(unsigned char *readbackbuff, unsigned char *buff, unsigned int len) +{ + int retval = STATUS_SUCCESS; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + if ((len-sizeof(unsigned int)) < 4) { + if (memcmp(readbackbuff , buff, len)) + retval = -EINVAL; + } else { + len -= 4; + + while (len) { + if (*(unsigned int *)&readbackbuff[len] != *(unsigned int *)&buff[len]) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Firmware Download is not proper"); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Val from Binary %x, Val From Read Back %x ", *(unsigned int *)&buff[len], *(unsigned int*)&readbackbuff[len]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "len =%x!!!", len); + retval = -EINVAL; + break; + } + len -= 4; + } + } + return retval; +} + +int bcm_ioctl_fw_download(PMINI_ADAPTER Adapter, FIRMWARE_INFO *psFwInfo) +{ + int retval = STATUS_SUCCESS; + PUCHAR buff = NULL; + + /* Config File is needed for the Driver to download the Config file and + * Firmware. Check for the Config file to be first to be sent from the + * Application + */ + atomic_set(&Adapter->uiMBupdate, FALSE); + if (!Adapter->bCfgDownloaded && psFwInfo->u32StartingAddress != CONFIG_BEGIN_ADDR) { + /* Can't Download Firmware. */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Download the config File first\n"); + return -EINVAL; + } + + /* If Config File, Finish the DDR Settings and then Download CFG File */ + if (psFwInfo->u32StartingAddress == CONFIG_BEGIN_ADDR) { + retval = bcm_download_config_file(Adapter, psFwInfo); + } else { + buff = kzalloc(psFwInfo->u32FirmwareLength, GFP_KERNEL); + if (buff == NULL) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Failed in allocation memory"); + return -ENOMEM; + } + + retval = copy_from_user(buff, psFwInfo->pvMappedFirmwareAddress, psFwInfo->u32FirmwareLength); + if (retval != STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "copying buffer from user space failed"); + retval = -EFAULT; + goto error; + } + + retval = buffDnldVerify(Adapter, + buff, + psFwInfo->u32FirmwareLength, + psFwInfo->u32StartingAddress); + + if (retval != STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "f/w download failed status :%d", retval); + goto error; + } + } + +error: + kfree(buff); + return retval; +} + +static INT buffDnld(PMINI_ADAPTER Adapter, PUCHAR mappedbuffer, UINT u32FirmwareLength, ULONG u32StartingAddress) +{ + unsigned int len = 0; + int retval = STATUS_SUCCESS; + len = u32FirmwareLength; + + while (u32FirmwareLength) { + len = MIN_VAL(u32FirmwareLength, MAX_TRANSFER_CTRL_BYTE_USB); + retval = wrm(Adapter, u32StartingAddress, mappedbuffer, len); + + if (retval) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "wrm failed with status :%d", retval); + break; + } + u32StartingAddress += len; + u32FirmwareLength -= len; + mappedbuffer += len; + } + return retval; +} + +static INT buffRdbkVerify(PMINI_ADAPTER Adapter, PUCHAR mappedbuffer, UINT u32FirmwareLength, ULONG u32StartingAddress) +{ + UINT len = u32FirmwareLength; + INT retval = STATUS_SUCCESS; + PUCHAR readbackbuff = kzalloc(MAX_TRANSFER_CTRL_BYTE_USB, GFP_KERNEL); + int bytes; + + if (NULL == readbackbuff) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "MEMORY ALLOCATION FAILED"); + return -ENOMEM; + } + + while (u32FirmwareLength && !retval) { + len = MIN_VAL(u32FirmwareLength, MAX_TRANSFER_CTRL_BYTE_USB); + bytes = rdm(Adapter, u32StartingAddress, readbackbuff, len); + + if (bytes < 0) { + retval = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "rdm failed with status %d", retval); + break; + } + + retval = bcm_compare_buff_contents(readbackbuff, mappedbuffer, len); + if (STATUS_SUCCESS != retval) + break; + + u32StartingAddress += len; + u32FirmwareLength -= len; + mappedbuffer += len; + + } /* end of while (u32FirmwareLength && !retval) */ + kfree(readbackbuff); + return retval; +} + +INT buffDnldVerify(PMINI_ADAPTER Adapter, unsigned char *mappedbuffer, unsigned int u32FirmwareLength, unsigned long u32StartingAddress) +{ + INT status = STATUS_SUCCESS; + + status = buffDnld(Adapter, mappedbuffer, u32FirmwareLength, u32StartingAddress); + if (status != STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Buffer download failed"); + goto error; + } + + status = buffRdbkVerify(Adapter, mappedbuffer, u32FirmwareLength, u32StartingAddress); + if (status != STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Buffer readback verifier failed"); + goto error; + } +error: + return status; +} diff --git a/drivers/staging/bcm/InterfaceIdleMode.c b/drivers/staging/bcm/InterfaceIdleMode.c new file mode 100644 index 00000000..faeb03e6 --- /dev/null +++ b/drivers/staging/bcm/InterfaceIdleMode.c @@ -0,0 +1,303 @@ +#include "headers.h" + +/* +Function: InterfaceIdleModeWakeup + +Description: This is the hardware specific Function for waking up HW device from Idle mode. + A software abort pattern is written to the device to wake it and necessary power state + transitions from host are performed here. + +Input parameters: IN PMINI_ADAPTER Adapter - Miniport Adapter Context + + +Return: BCM_STATUS_SUCCESS - If Wakeup of the HW Interface was successful. + Other - If an error occurred. +*/ + + +/* +Function: InterfaceIdleModeRespond + +Description: This is the hardware specific Function for responding to Idle mode request from target. + Necessary power state transitions from host for idle mode or other device specific + initializations are performed here. + +Input parameters: IN PMINI_ADAPTER Adapter - Miniport Adapter Context + + +Return: BCM_STATUS_SUCCESS - If Idle mode response related HW configuration was successful. + Other - If an error occurred. +*/ + +/* +"dmem bfc02f00 100" tells how many time device went in Idle mode. +this value will be at address bfc02fa4.just before value d0ea1dle. + +Set time value by writing at bfc02f98 7d0 + +checking the Ack timer expire on kannon by running command +d qcslog .. if it shows e means host has not send response to f/w with in 200 ms. Response should be +send to f/w with in 200 ms after the Idle/Shutdown req issued + +*/ + + +int InterfaceIdleModeRespond(PMINI_ADAPTER Adapter, unsigned int* puiBuffer) +{ + int status = STATUS_SUCCESS; + unsigned int uiRegRead = 0; + int bytes; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL,"SubType of Message :0x%X", ntohl(*puiBuffer)); + + if(ntohl(*puiBuffer) == GO_TO_IDLE_MODE_PAYLOAD) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL," Got GO_TO_IDLE_MODE_PAYLOAD(210) Msg Subtype"); + if(ntohl(*(puiBuffer+1)) == 0 ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL,"Got IDLE MODE WAKE UP Response From F/W"); + + status = wrmalt (Adapter,SW_ABORT_IDLEMODE_LOC, &uiRegRead, sizeof(uiRegRead)); + if(status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "wrm failed while clearing Idle Mode Reg"); + return status; + } + + if(Adapter->ulPowerSaveMode == DEVICE_POWERSAVE_MODE_AS_MANUAL_CLOCK_GATING) + { + uiRegRead = 0x00000000 ; + status = wrmalt (Adapter,DEBUG_INTERRUPT_GENERATOR_REGISTOR, &uiRegRead, sizeof(uiRegRead)); + if(status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "wrm failed while clearing Idle Mode Reg"); + return status; + } + } + //Below Register should not br read in case of Manual and Protocol Idle mode. + else if(Adapter->ulPowerSaveMode != DEVICE_POWERSAVE_MODE_AS_PROTOCOL_IDLE_MODE) + { + //clear on read Register + bytes = rdmalt(Adapter, DEVICE_INT_OUT_EP_REG0, &uiRegRead, sizeof(uiRegRead)); + if (bytes < 0) { + status = bytes; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "rdm failed while clearing H/W Abort Reg0"); + return status; + } + //clear on read Register + bytes = rdmalt(Adapter, DEVICE_INT_OUT_EP_REG1, &uiRegRead, sizeof(uiRegRead)); + if (bytes < 0) { + status = bytes; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "rdm failed while clearing H/W Abort Reg1"); + return status; + } + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL, "Device Up from Idle Mode"); + + // Set Idle Mode Flag to False and Clear IdleMode reg. + Adapter->IdleMode = FALSE; + Adapter->bTriedToWakeUpFromlowPowerMode = FALSE; + + wake_up(&Adapter->lowpower_mode_wait_queue); + + } + else + { + if(TRUE == Adapter->IdleMode) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL,"Device is already in Idle mode...."); + return status ; + } + + uiRegRead = 0; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL, "Got Req from F/W to go in IDLE mode \n"); + + if (Adapter->chip_id== BCS220_2 || + Adapter->chip_id == BCS220_2BC || + Adapter->chip_id== BCS250_BC || + Adapter->chip_id== BCS220_3) + { + + bytes = rdmalt(Adapter, HPM_CONFIG_MSW, &uiRegRead, sizeof(uiRegRead)); + if (bytes < 0) { + status = bytes; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL, "rdm failed while Reading HPM_CONFIG_LDO145 Reg 0\n"); + return status; + } + + + uiRegRead |= (1<<17); + + status = wrmalt (Adapter,HPM_CONFIG_MSW, &uiRegRead, sizeof(uiRegRead)); + if(status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "wrm failed while clearing Idle Mode Reg\n"); + return status; + } + + } + SendIdleModeResponse(Adapter); + } + } + else if(ntohl(*puiBuffer) == IDLE_MODE_SF_UPDATE_MSG) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL, "OverRiding Service Flow Params"); + OverrideServiceFlowParams(Adapter,puiBuffer); + } + return status; +} + +static int InterfaceAbortIdlemode(PMINI_ADAPTER Adapter, unsigned int Pattern) +{ + int status = STATUS_SUCCESS; + unsigned int value; + unsigned int chip_id ; + unsigned long timeout = 0 ,itr = 0; + + int lenwritten = 0; + unsigned char aucAbortPattern[8]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; + PS_INTERFACE_ADAPTER psInterfaceAdapter = Adapter->pvInterfaceAdapter; + + //Abort Bus suspend if its already suspended + if((TRUE == psInterfaceAdapter->bSuspended) && (TRUE == Adapter->bDoSuspend)) + { + status = usb_autopm_get_interface(psInterfaceAdapter->interface); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL,"Bus got wakeup..Aborting Idle mode... status:%d \n",status); + + } + + if((Adapter->ulPowerSaveMode == DEVICE_POWERSAVE_MODE_AS_MANUAL_CLOCK_GATING) + || + (Adapter->ulPowerSaveMode == DEVICE_POWERSAVE_MODE_AS_PROTOCOL_IDLE_MODE)) + { + //write the SW abort pattern. + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL, "Writing pattern<%d> to SW_ABORT_IDLEMODE_LOC\n", Pattern); + status = wrmalt(Adapter,SW_ABORT_IDLEMODE_LOC, &Pattern, sizeof(Pattern)); + if(status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL,"WRM to Register SW_ABORT_IDLEMODE_LOC failed.."); + return status; + } + } + + if(Adapter->ulPowerSaveMode == DEVICE_POWERSAVE_MODE_AS_MANUAL_CLOCK_GATING) + { + value = 0x80000000; + status = wrmalt(Adapter,DEBUG_INTERRUPT_GENERATOR_REGISTOR, &value, sizeof(value)); + if(status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL,"WRM to DEBUG_INTERRUPT_GENERATOR_REGISTOR Register failed"); + return status; + } + } + else if(Adapter->ulPowerSaveMode != DEVICE_POWERSAVE_MODE_AS_PROTOCOL_IDLE_MODE) + { + /* + * Get a Interrupt Out URB and send 8 Bytes Down + * To be Done in Thread Context. + * Not using Asynchronous Mechanism. + */ + status = usb_interrupt_msg (psInterfaceAdapter->udev, + usb_sndintpipe(psInterfaceAdapter->udev, + psInterfaceAdapter->sIntrOut.int_out_endpointAddr), + aucAbortPattern, + 8, + &lenwritten, + 5000); + if(status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL, "Sending Abort pattern down fails with status:%d..\n",status); + return status; + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL, "NOB Sent down :%d", lenwritten); + } + + //mdelay(25); + + timeout= jiffies + msecs_to_jiffies(50) ; + while( timeout > jiffies ) + { + itr++ ; + rdmalt(Adapter, CHIP_ID_REG, &chip_id, sizeof(UINT)); + if(0xbece3200==(chip_id&~(0xF0))) + { + chip_id = chip_id&~(0xF0); + } + if(chip_id == Adapter->chip_id) + break; + } + if(timeout < jiffies ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL,"Not able to read chip-id even after 25 msec"); + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL,"Number of completed iteration to read chip-id :%lu", itr); + } + + status = wrmalt(Adapter,SW_ABORT_IDLEMODE_LOC, &Pattern, sizeof(status)); + if(status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"WRM to Register SW_ABORT_IDLEMODE_LOC failed.."); + return status; + } + } + return status; +} +int InterfaceIdleModeWakeup(PMINI_ADAPTER Adapter) +{ + ULONG Status = 0; + if(Adapter->bTriedToWakeUpFromlowPowerMode) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL, "Wake up already attempted.. ignoring\n"); + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, IDLE_MODE, DBG_LVL_ALL,"Writing Low Power Mode Abort pattern to the Device\n"); + Adapter->bTriedToWakeUpFromlowPowerMode = TRUE; + InterfaceAbortIdlemode(Adapter, Adapter->usIdleModePattern); + + } + return Status; +} + +void InterfaceHandleShutdownModeWakeup(PMINI_ADAPTER Adapter) +{ + unsigned int uiRegVal = 0; + INT Status = 0; + int bytes; + + if(Adapter->ulPowerSaveMode == DEVICE_POWERSAVE_MODE_AS_MANUAL_CLOCK_GATING) + { + // clear idlemode interrupt. + uiRegVal = 0; + Status =wrmalt(Adapter,DEBUG_INTERRUPT_GENERATOR_REGISTOR, &uiRegVal, sizeof(uiRegVal)); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"WRM to DEBUG_INTERRUPT_GENERATOR_REGISTOR Failed with err :%d", Status); + return; + } + } + + else + { + + //clear Interrupt EP registers. + bytes = rdmalt(Adapter,DEVICE_INT_OUT_EP_REG0, &uiRegVal, sizeof(uiRegVal)); + if (bytes < 0) { + Status = bytes; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"RDM of DEVICE_INT_OUT_EP_REG0 failed with Err :%d", Status); + return; + } + + bytes = rdmalt(Adapter,DEVICE_INT_OUT_EP_REG1, &uiRegVal, sizeof(uiRegVal)); + if (bytes < 0) { + Status = bytes; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"RDM of DEVICE_INT_OUT_EP_REG1 failed with Err :%d", Status); + return; + } + } +} + diff --git a/drivers/staging/bcm/InterfaceIdleMode.h b/drivers/staging/bcm/InterfaceIdleMode.h new file mode 100644 index 00000000..859a2ffb --- /dev/null +++ b/drivers/staging/bcm/InterfaceIdleMode.h @@ -0,0 +1,14 @@ +#ifndef _INTERFACE_IDLEMODE_H +#define _INTERFACE_IDLEMODE_H + +INT InterfaceIdleModeWakeup(PMINI_ADAPTER Adapter); + +INT InterfaceIdleModeRespond(PMINI_ADAPTER Adapter, unsigned int *puiBuffer); + +VOID InterfaceWriteIdleModeWakePattern(PMINI_ADAPTER Adapter); + +INT InterfaceWakeUp(PMINI_ADAPTER Adapter); + +VOID InterfaceHandleShutdownModeWakeup(PMINI_ADAPTER Adapter); +#endif + diff --git a/drivers/staging/bcm/InterfaceInit.c b/drivers/staging/bcm/InterfaceInit.c new file mode 100644 index 00000000..8e3c586a --- /dev/null +++ b/drivers/staging/bcm/InterfaceInit.c @@ -0,0 +1,695 @@ +#include "headers.h" + +static struct usb_device_id InterfaceUsbtable[] = { + { USB_DEVICE(BCM_USB_VENDOR_ID_T3, BCM_USB_PRODUCT_ID_T3) }, + { USB_DEVICE(BCM_USB_VENDOR_ID_T3, BCM_USB_PRODUCT_ID_T3B) }, + { USB_DEVICE(BCM_USB_VENDOR_ID_T3, BCM_USB_PRODUCT_ID_T3L) }, + { USB_DEVICE(BCM_USB_VENDOR_ID_T3, BCM_USB_PRODUCT_ID_SM250) }, + { USB_DEVICE(BCM_USB_VENDOR_ID_ZTE, BCM_USB_PRODUCT_ID_226) }, + { USB_DEVICE(BCM_USB_VENDOR_ID_FOXCONN, BCM_USB_PRODUCT_ID_1901) }, + { USB_DEVICE(BCM_USB_VENDOR_ID_ZTE, BCM_USB_PRODUCT_ID_ZTE_TU25) }, + { } +}; +MODULE_DEVICE_TABLE(usb, InterfaceUsbtable); + +static int debug = -1; +module_param(debug, uint, 0600); +MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)"); + +static const u32 default_msg = + NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK + | NETIF_MSG_TIMER | NETIF_MSG_TX_ERR | NETIF_MSG_RX_ERR + | NETIF_MSG_IFUP | NETIF_MSG_IFDOWN; + +static int InterfaceAdapterInit(PS_INTERFACE_ADAPTER Adapter); + +static void InterfaceAdapterFree(PS_INTERFACE_ADAPTER psIntfAdapter) +{ + int i = 0; + + /* Wake up the wait_queue... */ + if (psIntfAdapter->psAdapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + psIntfAdapter->psAdapter->DriverState = DRIVER_HALT; + wake_up(&psIntfAdapter->psAdapter->LEDInfo.notify_led_event); + } + reset_card_proc(psIntfAdapter->psAdapter); + + /* + * worst case time taken by the RDM/WRM will be 5 sec. will check after every 100 ms + * to accertain the device is not being accessed. After this No RDM/WRM should be made. + */ + while (psIntfAdapter->psAdapter->DeviceAccess) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Device is being accessed.\n"); + msleep(100); + } + /* Free interrupt URB */ + /* psIntfAdapter->psAdapter->device_removed = TRUE; */ + usb_free_urb(psIntfAdapter->psInterruptUrb); + + /* Free transmit URBs */ + for (i = 0; i < MAXIMUM_USB_TCB; i++) { + if (psIntfAdapter->asUsbTcb[i].urb != NULL) { + usb_free_urb(psIntfAdapter->asUsbTcb[i].urb); + psIntfAdapter->asUsbTcb[i].urb = NULL; + } + } + /* Free receive URB and buffers */ + for (i = 0; i < MAXIMUM_USB_RCB; i++) { + if (psIntfAdapter->asUsbRcb[i].urb != NULL) { + kfree(psIntfAdapter->asUsbRcb[i].urb->transfer_buffer); + usb_free_urb(psIntfAdapter->asUsbRcb[i].urb); + psIntfAdapter->asUsbRcb[i].urb = NULL; + } + } + AdapterFree(psIntfAdapter->psAdapter); +} + +static void ConfigureEndPointTypesThroughEEPROM(PMINI_ADAPTER Adapter) +{ + unsigned long ulReg = 0; + int bytes; + + /* Program EP2 MAX_PKT_SIZE */ + ulReg = ntohl(EP2_MPS_REG); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x128, 4, TRUE); + ulReg = ntohl(EP2_MPS); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x12C, 4, TRUE); + + ulReg = ntohl(EP2_CFG_REG); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x132, 4, TRUE); + if (((PS_INTERFACE_ADAPTER)(Adapter->pvInterfaceAdapter))->bHighSpeedDevice == TRUE) { + ulReg = ntohl(EP2_CFG_INT); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x136, 4, TRUE); + } else { + /* USE BULK EP as TX in FS mode. */ + ulReg = ntohl(EP2_CFG_BULK); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x136, 4, TRUE); + } + + /* Program EP4 MAX_PKT_SIZE. */ + ulReg = ntohl(EP4_MPS_REG); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x13C, 4, TRUE); + ulReg = ntohl(EP4_MPS); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x140, 4, TRUE); + + /* Program TX EP as interrupt(Alternate Setting) */ + bytes = rdmalt(Adapter, 0x0F0110F8, (u32 *)&ulReg, sizeof(u32)); + if (bytes < 0) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "reading of Tx EP failed\n"); + return; + } + ulReg |= 0x6; + + ulReg = ntohl(ulReg); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x1CC, 4, TRUE); + + ulReg = ntohl(EP4_CFG_REG); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x1C8, 4, TRUE); + /* Program ISOCHRONOUS EP size to zero. */ + ulReg = ntohl(ISO_MPS_REG); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x1D2, 4, TRUE); + ulReg = ntohl(ISO_MPS); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x1D6, 4, TRUE); + + /* + * Update EEPROM Version. + * Read 4 bytes from 508 and modify 511 and 510. + */ + ReadBeceemEEPROM(Adapter, 0x1FC, (PUINT)&ulReg); + ulReg &= 0x0101FFFF; + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x1FC, 4, TRUE); + + /* Update length field if required. Also make the string NULL terminated. */ + + ReadBeceemEEPROM(Adapter, 0xA8, (PUINT)&ulReg); + if ((ulReg&0x00FF0000)>>16 > 0x30) { + ulReg = (ulReg&0xFF00FFFF)|(0x30<<16); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0xA8, 4, TRUE); + } + ReadBeceemEEPROM(Adapter, 0x148, (PUINT)&ulReg); + if ((ulReg&0x00FF0000)>>16 > 0x30) { + ulReg = (ulReg&0xFF00FFFF)|(0x30<<16); + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x148, 4, TRUE); + } + ulReg = 0; + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x122, 4, TRUE); + ulReg = 0; + BeceemEEPROMBulkWrite(Adapter, (PUCHAR)&ulReg, 0x1C2, 4, TRUE); +} + +static int usbbcm_device_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + int retval; + PMINI_ADAPTER psAdapter; + PS_INTERFACE_ADAPTER psIntfAdapter; + struct net_device *ndev; + + /* Reserve one extra queue for the bit-bucket */ + ndev = alloc_etherdev_mq(sizeof(MINI_ADAPTER), NO_OF_QUEUES+1); + if (ndev == NULL) { + dev_err(&udev->dev, DRV_NAME ": no memory for device\n"); + return -ENOMEM; + } + + SET_NETDEV_DEV(ndev, &intf->dev); + + psAdapter = netdev_priv(ndev); + psAdapter->dev = ndev; + psAdapter->msg_enable = netif_msg_init(debug, default_msg); + + /* Init default driver debug state */ + + psAdapter->stDebugState.debug_level = DBG_LVL_CURR; + psAdapter->stDebugState.type = DBG_TYPE_INITEXIT; + + /* + * Technically, one can start using BCM_DEBUG_PRINT after this point. + * However, realize that by default the Type/Subtype bitmaps are all zero now; + * so no prints will actually appear until the TestApp turns on debug paths via + * the ioctl(); so practically speaking, in early init, no logging happens. + * + * A solution (used below): we explicitly set the bitmaps to 1 for Type=DBG_TYPE_INITEXIT + * and ALL subtype's of the same. Now all bcm debug statements get logged, enabling debug + * during early init. + * Further, we turn this OFF once init_module() completes. + */ + + psAdapter->stDebugState.subtype[DBG_TYPE_INITEXIT] = 0xff; + BCM_SHOW_DEBUG_BITMAP(psAdapter); + + retval = InitAdapter(psAdapter); + if (retval) { + dev_err(&udev->dev, DRV_NAME ": InitAdapter Failed\n"); + AdapterFree(psAdapter); + return retval; + } + + /* Allocate interface adapter structure */ + psIntfAdapter = kzalloc(sizeof(S_INTERFACE_ADAPTER), GFP_KERNEL); + if (psIntfAdapter == NULL) { + dev_err(&udev->dev, DRV_NAME ": no memory for Interface adapter\n"); + AdapterFree(psAdapter); + return -ENOMEM; + } + + psAdapter->pvInterfaceAdapter = psIntfAdapter; + psIntfAdapter->psAdapter = psAdapter; + + /* Store usb interface in Interface Adapter */ + psIntfAdapter->interface = intf; + usb_set_intfdata(intf, psIntfAdapter); + + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "psIntfAdapter 0x%p\n", psIntfAdapter); + retval = InterfaceAdapterInit(psIntfAdapter); + if (retval) { + /* If the Firmware/Cfg File is not present + * then return success, let the application + * download the files. + */ + if (-ENOENT == retval) { + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "File Not Found. Use app to download.\n"); + return STATUS_SUCCESS; + } + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "InterfaceAdapterInit failed.\n"); + usb_set_intfdata(intf, NULL); + udev = interface_to_usbdev(intf); + usb_put_dev(udev); + InterfaceAdapterFree(psIntfAdapter); + return retval; + } + if (psAdapter->chip_id > T3) { + uint32_t uiNackZeroLengthInt = 4; + + retval = wrmalt(psAdapter, DISABLE_USB_ZERO_LEN_INT, &uiNackZeroLengthInt, sizeof(uiNackZeroLengthInt)); + if (retval) + return retval; + } + + /* Check whether the USB-Device Supports remote Wake-Up */ + if (USB_CONFIG_ATT_WAKEUP & udev->actconfig->desc.bmAttributes) { + /* If Suspend then only support dynamic suspend */ + if (psAdapter->bDoSuspend) { +#ifdef CONFIG_PM + pm_runtime_set_autosuspend_delay(&udev->dev, 0); + intf->needs_remote_wakeup = 1; + usb_enable_autosuspend(udev); + device_init_wakeup(&intf->dev, 1); + INIT_WORK(&psIntfAdapter->usbSuspendWork, putUsbSuspend); + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Enabling USB Auto-Suspend\n"); +#endif + } else { + intf->needs_remote_wakeup = 0; + usb_disable_autosuspend(udev); + } + } + + psAdapter->stDebugState.subtype[DBG_TYPE_INITEXIT] = 0x0; + return retval; +} + +static void usbbcm_disconnect(struct usb_interface *intf) +{ + PS_INTERFACE_ADAPTER psIntfAdapter = usb_get_intfdata(intf); + PMINI_ADAPTER psAdapter; + struct usb_device *udev = interface_to_usbdev(intf); + + if (psIntfAdapter == NULL) + return; + + psAdapter = psIntfAdapter->psAdapter; + netif_device_detach(psAdapter->dev); + + if (psAdapter->bDoSuspend) + intf->needs_remote_wakeup = 0; + + psAdapter->device_removed = TRUE ; + usb_set_intfdata(intf, NULL); + InterfaceAdapterFree(psIntfAdapter); + usb_put_dev(udev); +} + +static int AllocUsbCb(PS_INTERFACE_ADAPTER psIntfAdapter) +{ + int i = 0; + + for (i = 0; i < MAXIMUM_USB_TCB; i++) { + psIntfAdapter->asUsbTcb[i].urb = usb_alloc_urb(0, GFP_KERNEL); + + if (psIntfAdapter->asUsbTcb[i].urb == NULL) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_PRINTK, 0, 0, + "Can't allocate Tx urb for index %d\n", i); + return -ENOMEM; + } + } + + for (i = 0; i < MAXIMUM_USB_RCB; i++) { + psIntfAdapter->asUsbRcb[i].urb = usb_alloc_urb(0, GFP_KERNEL); + + if (psIntfAdapter->asUsbRcb[i].urb == NULL) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_PRINTK, 0, 0, + "Can't allocate Rx urb for index %d\n", i); + return -ENOMEM; + } + + psIntfAdapter->asUsbRcb[i].urb->transfer_buffer = kmalloc(MAX_DATA_BUFFER_SIZE, GFP_KERNEL); + + if (psIntfAdapter->asUsbRcb[i].urb->transfer_buffer == NULL) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_PRINTK, 0, 0, + "Can't allocate Rx buffer for index %d\n", i); + return -ENOMEM; + } + psIntfAdapter->asUsbRcb[i].urb->transfer_buffer_length = MAX_DATA_BUFFER_SIZE; + } + return 0; +} + +static int device_run(PS_INTERFACE_ADAPTER psIntfAdapter) +{ + int value = 0; + UINT status = STATUS_SUCCESS; + + status = InitCardAndDownloadFirmware(psIntfAdapter->psAdapter); + if (status != STATUS_SUCCESS) { + pr_err(DRV_NAME "InitCardAndDownloadFirmware failed.\n"); + return status; + } + if (TRUE == psIntfAdapter->psAdapter->fw_download_done) { + if (StartInterruptUrb(psIntfAdapter)) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Cannot send interrupt in URB\n"); + } + + /* + * now register the cntrl interface. + * after downloading the f/w waiting for 5 sec to get the mailbox interrupt. + */ + psIntfAdapter->psAdapter->waiting_to_fw_download_done = FALSE; + value = wait_event_timeout(psIntfAdapter->psAdapter->ioctl_fw_dnld_wait_queue, + psIntfAdapter->psAdapter->waiting_to_fw_download_done, 5*HZ); + + if (value == 0) + pr_err(DRV_NAME ": Timeout waiting for mailbox interrupt.\n"); + + if (register_control_device_interface(psIntfAdapter->psAdapter) < 0) { + pr_err(DRV_NAME ": Register Control Device failed.\n"); + return -EIO; + } + } + return 0; +} + + +static inline int bcm_usb_endpoint_num(const struct usb_endpoint_descriptor *epd) +{ + return epd->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; +} + +static inline int bcm_usb_endpoint_type(const struct usb_endpoint_descriptor *epd) +{ + return epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; +} + +static inline int bcm_usb_endpoint_dir_in(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN); +} + +static inline int bcm_usb_endpoint_dir_out(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); +} + +static inline int bcm_usb_endpoint_xfer_bulk(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_BULK); +} + +static inline int bcm_usb_endpoint_xfer_control(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_CONTROL); +} + +static inline int bcm_usb_endpoint_xfer_int(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT); +} + +static inline int bcm_usb_endpoint_xfer_isoc(const struct usb_endpoint_descriptor *epd) +{ + return ((epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_ISOC); +} + +static inline int bcm_usb_endpoint_is_bulk_in(const struct usb_endpoint_descriptor *epd) +{ + return bcm_usb_endpoint_xfer_bulk(epd) && bcm_usb_endpoint_dir_in(epd); +} + +static inline int bcm_usb_endpoint_is_bulk_out(const struct usb_endpoint_descriptor *epd) +{ + return bcm_usb_endpoint_xfer_bulk(epd) && bcm_usb_endpoint_dir_out(epd); +} + +static inline int bcm_usb_endpoint_is_int_in(const struct usb_endpoint_descriptor *epd) +{ + return bcm_usb_endpoint_xfer_int(epd) && bcm_usb_endpoint_dir_in(epd); +} + +static inline int bcm_usb_endpoint_is_int_out(const struct usb_endpoint_descriptor *epd) +{ + return bcm_usb_endpoint_xfer_int(epd) && bcm_usb_endpoint_dir_out(epd); +} + +static inline int bcm_usb_endpoint_is_isoc_in(const struct usb_endpoint_descriptor *epd) +{ + return bcm_usb_endpoint_xfer_isoc(epd) && bcm_usb_endpoint_dir_in(epd); +} + +static inline int bcm_usb_endpoint_is_isoc_out(const struct usb_endpoint_descriptor *epd) +{ + return bcm_usb_endpoint_xfer_isoc(epd) && bcm_usb_endpoint_dir_out(epd); +} + +static int InterfaceAdapterInit(PS_INTERFACE_ADAPTER psIntfAdapter) +{ + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + size_t buffer_size; + unsigned long value; + int retval = 0; + int usedIntOutForBulkTransfer = 0 ; + BOOLEAN bBcm16 = FALSE; + UINT uiData = 0; + int bytes; + + /* Store the usb dev into interface adapter */ + psIntfAdapter->udev = usb_get_dev(interface_to_usbdev(psIntfAdapter->interface)); + + psIntfAdapter->bHighSpeedDevice = (psIntfAdapter->udev->speed == USB_SPEED_HIGH); + psIntfAdapter->psAdapter->interface_rdm = BcmRDM; + psIntfAdapter->psAdapter->interface_wrm = BcmWRM; + + bytes = rdmalt(psIntfAdapter->psAdapter, CHIP_ID_REG, + (u32 *)&(psIntfAdapter->psAdapter->chip_id), sizeof(u32)); + if (bytes < 0) { + retval = bytes; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_PRINTK, 0, 0, "CHIP ID Read Failed\n"); + return retval; + } + + if (0xbece3200 == (psIntfAdapter->psAdapter->chip_id & ~(0xF0))) + psIntfAdapter->psAdapter->chip_id &= ~0xF0; + + dev_info(&psIntfAdapter->udev->dev, "RDM Chip ID 0x%lx\n", + psIntfAdapter->psAdapter->chip_id); + + iface_desc = psIntfAdapter->interface->cur_altsetting; + + if (psIntfAdapter->psAdapter->chip_id == T3B) { + /* T3B device will have EEPROM, check if EEPROM is proper and BCM16 can be done or not. */ + BeceemEEPROMBulkRead(psIntfAdapter->psAdapter, &uiData, 0x0, 4); + if (uiData == BECM) + bBcm16 = TRUE; + + dev_info(&psIntfAdapter->udev->dev, "number of alternate setting %d\n", + psIntfAdapter->interface->num_altsetting); + + if (bBcm16 == TRUE) { + /* selecting alternate setting one as a default setting for High Speed modem. */ + if (psIntfAdapter->bHighSpeedDevice) + retval = usb_set_interface(psIntfAdapter->udev, DEFAULT_SETTING_0, ALTERNATE_SETTING_1); + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "BCM16 is applicable on this dongle\n"); + if (retval || (psIntfAdapter->bHighSpeedDevice == FALSE)) { + usedIntOutForBulkTransfer = EP2 ; + endpoint = &iface_desc->endpoint[EP2].desc; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Interface altsetting failed or modem is configured to Full Speed, hence will work on default setting 0\n"); + /* + * If Modem is high speed device EP2 should be INT OUT End point + * If Mode is FS then EP2 should be bulk end point + */ + if (((psIntfAdapter->bHighSpeedDevice == TRUE) && (bcm_usb_endpoint_is_int_out(endpoint) == FALSE)) + || ((psIntfAdapter->bHighSpeedDevice == FALSE) && (bcm_usb_endpoint_is_bulk_out(endpoint) == FALSE))) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Configuring the EEPROM\n"); + /* change the EP2, EP4 to INT OUT end point */ + ConfigureEndPointTypesThroughEEPROM(psIntfAdapter->psAdapter); + + /* + * It resets the device and if any thing gets changed + * in USB descriptor it will show fail and re-enumerate + * the device + */ + retval = usb_reset_device(psIntfAdapter->udev); + if (retval) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "reset failed. Re-enumerating the device.\n"); + return retval ; + } + + } + if ((psIntfAdapter->bHighSpeedDevice == FALSE) && bcm_usb_endpoint_is_bulk_out(endpoint)) { + /* Once BULK is selected in FS mode. Revert it back to INT. Else USB_IF will fail. */ + UINT _uiData = ntohl(EP2_CFG_INT); + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Reverting Bulk to INT as it is in Full Speed mode.\n"); + BeceemEEPROMBulkWrite(psIntfAdapter->psAdapter, (PUCHAR)&_uiData, 0x136, 4, TRUE); + } + } else { + usedIntOutForBulkTransfer = EP4 ; + endpoint = &iface_desc->endpoint[EP4].desc; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Choosing AltSetting as a default setting.\n"); + if (bcm_usb_endpoint_is_int_out(endpoint) == FALSE) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Dongle does not have BCM16 Fix.\n"); + /* change the EP2, EP4 to INT OUT end point and use EP4 in altsetting */ + ConfigureEndPointTypesThroughEEPROM(psIntfAdapter->psAdapter); + + /* + * It resets the device and if any thing gets changed in + * USB descriptor it will show fail and re-enumerate the + * device + */ + retval = usb_reset_device(psIntfAdapter->udev); + if (retval) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "reset failed. Re-enumerating the device.\n"); + return retval; + } + + } + } + } + } + + iface_desc = psIntfAdapter->interface->cur_altsetting; + + for (value = 0; value < iface_desc->desc.bNumEndpoints; ++value) { + endpoint = &iface_desc->endpoint[value].desc; + + if (!psIntfAdapter->sBulkIn.bulk_in_endpointAddr && bcm_usb_endpoint_is_bulk_in(endpoint)) { + buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); + psIntfAdapter->sBulkIn.bulk_in_size = buffer_size; + psIntfAdapter->sBulkIn.bulk_in_endpointAddr = endpoint->bEndpointAddress; + psIntfAdapter->sBulkIn.bulk_in_pipe = + usb_rcvbulkpipe(psIntfAdapter->udev, + psIntfAdapter->sBulkIn.bulk_in_endpointAddr); + } + + if (!psIntfAdapter->sBulkOut.bulk_out_endpointAddr && bcm_usb_endpoint_is_bulk_out(endpoint)) { + psIntfAdapter->sBulkOut.bulk_out_endpointAddr = endpoint->bEndpointAddress; + psIntfAdapter->sBulkOut.bulk_out_pipe = + usb_sndbulkpipe(psIntfAdapter->udev, + psIntfAdapter->sBulkOut.bulk_out_endpointAddr); + } + + if (!psIntfAdapter->sIntrIn.int_in_endpointAddr && bcm_usb_endpoint_is_int_in(endpoint)) { + buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); + psIntfAdapter->sIntrIn.int_in_size = buffer_size; + psIntfAdapter->sIntrIn.int_in_endpointAddr = endpoint->bEndpointAddress; + psIntfAdapter->sIntrIn.int_in_interval = endpoint->bInterval; + psIntfAdapter->sIntrIn.int_in_buffer = + kmalloc(buffer_size, GFP_KERNEL); + if (!psIntfAdapter->sIntrIn.int_in_buffer) { + dev_err(&psIntfAdapter->udev->dev, + "could not allocate interrupt_in_buffer\n"); + return -EINVAL; + } + } + + if (!psIntfAdapter->sIntrOut.int_out_endpointAddr && bcm_usb_endpoint_is_int_out(endpoint)) { + if (!psIntfAdapter->sBulkOut.bulk_out_endpointAddr && + (psIntfAdapter->psAdapter->chip_id == T3B) && (value == usedIntOutForBulkTransfer)) { + /* use first intout end point as a bulk out end point */ + buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); + psIntfAdapter->sBulkOut.bulk_out_size = buffer_size; + psIntfAdapter->sBulkOut.bulk_out_endpointAddr = endpoint->bEndpointAddress; + psIntfAdapter->sBulkOut.bulk_out_pipe = usb_sndintpipe(psIntfAdapter->udev, + psIntfAdapter->sBulkOut.bulk_out_endpointAddr); + psIntfAdapter->sBulkOut.int_out_interval = endpoint->bInterval; + } else if (value == EP6) { + buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); + psIntfAdapter->sIntrOut.int_out_size = buffer_size; + psIntfAdapter->sIntrOut.int_out_endpointAddr = endpoint->bEndpointAddress; + psIntfAdapter->sIntrOut.int_out_interval = endpoint->bInterval; + psIntfAdapter->sIntrOut.int_out_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!psIntfAdapter->sIntrOut.int_out_buffer) { + dev_err(&psIntfAdapter->udev->dev, + "could not allocate interrupt_out_buffer\n"); + return -EINVAL; + } + } + } + } + + usb_set_intfdata(psIntfAdapter->interface, psIntfAdapter); + + psIntfAdapter->psAdapter->bcm_file_download = InterfaceFileDownload; + psIntfAdapter->psAdapter->bcm_file_readback_from_chip = + InterfaceFileReadbackFromChip; + psIntfAdapter->psAdapter->interface_transmit = InterfaceTransmitPacket; + + retval = CreateInterruptUrb(psIntfAdapter); + + if (retval) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_PRINTK, 0, 0, + "Cannot create interrupt urb\n"); + return retval; + } + + retval = AllocUsbCb(psIntfAdapter); + if (retval) + return retval; + + return device_run(psIntfAdapter); +} + +static int InterfaceSuspend(struct usb_interface *intf, pm_message_t message) +{ + PS_INTERFACE_ADAPTER psIntfAdapter = usb_get_intfdata(intf); + + psIntfAdapter->bSuspended = TRUE; + + if (TRUE == psIntfAdapter->bPreparingForBusSuspend) { + psIntfAdapter->bPreparingForBusSuspend = FALSE; + + if (psIntfAdapter->psAdapter->LinkStatus == LINKUP_DONE) { + psIntfAdapter->psAdapter->IdleMode = TRUE ; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Host Entered in PMU Idle Mode.\n"); + } else { + psIntfAdapter->psAdapter->bShutStatus = TRUE; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, + "Host Entered in PMU Shutdown Mode.\n"); + } + } + psIntfAdapter->psAdapter->bPreparingForLowPowerMode = FALSE; + + /* Signaling the control pkt path */ + wake_up(&psIntfAdapter->psAdapter->lowpower_mode_wait_queue); + + return 0; +} + +static int InterfaceResume(struct usb_interface *intf) +{ + PS_INTERFACE_ADAPTER psIntfAdapter = usb_get_intfdata(intf); + + mdelay(100); + psIntfAdapter->bSuspended = FALSE; + + StartInterruptUrb(psIntfAdapter); + InterfaceRx(psIntfAdapter); + return 0; +} + +static struct usb_driver usbbcm_driver = { + .name = "usbbcm", + .probe = usbbcm_device_probe, + .disconnect = usbbcm_disconnect, + .suspend = InterfaceSuspend, + .resume = InterfaceResume, + .id_table = InterfaceUsbtable, + .supports_autosuspend = 1, +}; + +struct class *bcm_class; + +static __init int bcm_init(void) +{ + printk(KERN_INFO "%s: %s, %s\n", DRV_NAME, DRV_DESCRIPTION, DRV_VERSION); + printk(KERN_INFO "%s\n", DRV_COPYRIGHT); + + bcm_class = class_create(THIS_MODULE, DRV_NAME); + if (IS_ERR(bcm_class)) { + printk(KERN_ERR DRV_NAME ": could not create class\n"); + return PTR_ERR(bcm_class); + } + + return usb_register(&usbbcm_driver); +} + +static __exit void bcm_exit(void) +{ + usb_deregister(&usbbcm_driver); + class_destroy(bcm_class); +} + +module_init(bcm_init); +module_exit(bcm_exit); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/bcm/InterfaceInit.h b/drivers/staging/bcm/InterfaceInit.h new file mode 100644 index 00000000..058315a6 --- /dev/null +++ b/drivers/staging/bcm/InterfaceInit.h @@ -0,0 +1,27 @@ +#ifndef _INTERFACE_INIT_H +#define _INTERFACE_INIT_H + +#define BCM_USB_VENDOR_ID_T3 0x198f +#define BCM_USB_VENDOR_ID_FOXCONN 0x0489 +#define BCM_USB_VENDOR_ID_ZTE 0x19d2 + +#define BCM_USB_PRODUCT_ID_T3 0x0300 +#define BCM_USB_PRODUCT_ID_T3B 0x0210 +#define BCM_USB_PRODUCT_ID_T3L 0x0220 +#define BCM_USB_PRODUCT_ID_SM250 0xbccd +#define BCM_USB_PRODUCT_ID_SYM 0x15E +#define BCM_USB_PRODUCT_ID_1901 0xe017 +#define BCM_USB_PRODUCT_ID_226 0x0132 +#define BCM_USB_PRODUCT_ID_ZTE_TU25 0x0007 + +#define BCM_USB_MINOR_BASE 192 + + +INT InterfaceInitialize(void); + +INT InterfaceExit(void); + +INT usbbcm_worker_thread(PS_INTERFACE_ADAPTER psIntfAdapter); + +#endif + diff --git a/drivers/staging/bcm/InterfaceIsr.c b/drivers/staging/bcm/InterfaceIsr.c new file mode 100644 index 00000000..67719d57 --- /dev/null +++ b/drivers/staging/bcm/InterfaceIsr.c @@ -0,0 +1,169 @@ +#include "headers.h" + + +static void read_int_callback(struct urb *urb/*, struct pt_regs *regs*/) +{ + int status = urb->status; + PS_INTERFACE_ADAPTER psIntfAdapter = (PS_INTERFACE_ADAPTER)urb->context; + PMINI_ADAPTER Adapter = psIntfAdapter->psAdapter ; + + if (netif_msg_intr(Adapter)) + pr_info(PFX "%s: interrupt status %d\n", + Adapter->dev->name, status); + + if(Adapter->device_removed == TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"Device has Got Removed."); + return ; + } + + if(((Adapter->bPreparingForLowPowerMode == TRUE) && (Adapter->bDoSuspend == TRUE)) || + psIntfAdapter->bSuspended || + psIntfAdapter->bPreparingForBusSuspend) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"Interrupt call back is called while suspending the device"); + return ; + } + + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL, "interrupt urb status %d", status); + switch (status) { + /* success */ + case STATUS_SUCCESS: + if ( urb->actual_length ) + { + + if(psIntfAdapter->ulInterruptData[1] & 0xFF) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL, "Got USIM interrupt"); + } + + if(psIntfAdapter->ulInterruptData[1] & 0xFF00) + { + atomic_set(&Adapter->CurrNumFreeTxDesc, + (psIntfAdapter->ulInterruptData[1] & 0xFF00) >> 8); + atomic_set (&Adapter->uiMBupdate, TRUE); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL, "TX mailbox contains %d", + atomic_read(&Adapter->CurrNumFreeTxDesc)); + } + if(psIntfAdapter->ulInterruptData[1] >> 16) + { + Adapter->CurrNumRecvDescs= + (psIntfAdapter->ulInterruptData[1] >> 16); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"RX mailbox contains %d", + Adapter->CurrNumRecvDescs); + InterfaceRx(psIntfAdapter); + } + if(Adapter->fw_download_done && + !Adapter->downloadDDR && + atomic_read(&Adapter->CurrNumFreeTxDesc)) + { + psIntfAdapter->psAdapter->downloadDDR +=1; + wake_up(&Adapter->tx_packet_wait_queue); + } + if(FALSE == Adapter->waiting_to_fw_download_done) + { + Adapter->waiting_to_fw_download_done = TRUE; + wake_up(&Adapter->ioctl_fw_dnld_wait_queue); + } + if(!atomic_read(&Adapter->TxPktAvail)) + { + atomic_set(&Adapter->TxPktAvail, 1); + wake_up(&Adapter->tx_packet_wait_queue); + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"Firing interrupt in URB"); + } + break; + case -ENOENT : + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"URB has got disconnected ...."); + return ; + } + case -EINPROGRESS: + { + //This situation may happened when URBunlink is used. for detail check usb_unlink_urb documentation. + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"Impossibe condition has occurred... something very bad is going on"); + break ; + //return; + } + case -EPIPE: + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"Interrupt IN endPoint has got halted/stalled...need to clear this"); + Adapter->bEndPointHalted = TRUE ; + wake_up(&Adapter->tx_packet_wait_queue); + urb->status = STATUS_SUCCESS ; + return; + } + /* software-driven interface shutdown */ + case -ECONNRESET: //URB got unlinked. + case -ESHUTDOWN: // hardware gone. this is the serious problem. + //Occurs only when something happens with the host controller device + case -ENODEV : //Device got removed + case -EINVAL : //Some thing very bad happened with the URB. No description is available. + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"interrupt urb error %d", status); + urb->status = STATUS_SUCCESS ; + break ; + //return; + default: + //This is required to check what is the defaults conditions when it occurs.. + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL,"GOT DEFAULT INTERRUPT URB STATUS :%d..Please Analyze it...", status); + break; + } + + StartInterruptUrb(psIntfAdapter); + + +} + +int CreateInterruptUrb(PS_INTERFACE_ADAPTER psIntfAdapter) +{ + psIntfAdapter->psInterruptUrb = usb_alloc_urb(0, GFP_KERNEL); + if (!psIntfAdapter->psInterruptUrb) + { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"Cannot allocate interrupt urb"); + return -ENOMEM; + } + psIntfAdapter->psInterruptUrb->transfer_buffer = + psIntfAdapter->ulInterruptData; + psIntfAdapter->psInterruptUrb->transfer_buffer_length = + sizeof(psIntfAdapter->ulInterruptData); + + psIntfAdapter->sIntrIn.int_in_pipe = usb_rcvintpipe(psIntfAdapter->udev, + psIntfAdapter->sIntrIn.int_in_endpointAddr); + + usb_fill_int_urb(psIntfAdapter->psInterruptUrb, psIntfAdapter->udev, + psIntfAdapter->sIntrIn.int_in_pipe, + psIntfAdapter->psInterruptUrb->transfer_buffer, + psIntfAdapter->psInterruptUrb->transfer_buffer_length, + read_int_callback, psIntfAdapter, + psIntfAdapter->sIntrIn.int_in_interval); + + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"Interrupt Interval: %d\n", + psIntfAdapter->sIntrIn.int_in_interval); + return 0; +} + + +INT StartInterruptUrb(PS_INTERFACE_ADAPTER psIntfAdapter) +{ + INT status = 0; + + if( FALSE == psIntfAdapter->psAdapter->device_removed && + FALSE == psIntfAdapter->psAdapter->bEndPointHalted && + FALSE == psIntfAdapter->bSuspended && + FALSE == psIntfAdapter->bPreparingForBusSuspend && + FALSE == psIntfAdapter->psAdapter->StopAllXaction) + { + status = usb_submit_urb(psIntfAdapter->psInterruptUrb, GFP_ATOMIC); + if (status) + { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL,"Cannot send int urb %d\n", status); + if(status == -EPIPE) + { + psIntfAdapter->psAdapter->bEndPointHalted = TRUE ; + wake_up(&psIntfAdapter->psAdapter->tx_packet_wait_queue); + } + } + } + return status; +} + diff --git a/drivers/staging/bcm/InterfaceIsr.h b/drivers/staging/bcm/InterfaceIsr.h new file mode 100644 index 00000000..6065a714 --- /dev/null +++ b/drivers/staging/bcm/InterfaceIsr.h @@ -0,0 +1,15 @@ +#ifndef _INTERFACE_ISR_H +#define _INTERFACE_ISR_H + +int CreateInterruptUrb(PS_INTERFACE_ADAPTER psIntfAdapter); + + +INT StartInterruptUrb(PS_INTERFACE_ADAPTER psIntfAdapter); + + +VOID InterfaceEnableInterrupt(PMINI_ADAPTER Adapter); + +VOID InterfaceDisableInterrupt(PMINI_ADAPTER Adapter); + +#endif + diff --git a/drivers/staging/bcm/InterfaceMacros.h b/drivers/staging/bcm/InterfaceMacros.h new file mode 100644 index 00000000..7001caff --- /dev/null +++ b/drivers/staging/bcm/InterfaceMacros.h @@ -0,0 +1,18 @@ +#ifndef _INTERFACE_MACROS_H +#define _INTERFACE_MACROS_H + +#define BCM_USB_MAX_READ_LENGTH 2048 + +#define MAXIMUM_USB_TCB 128 +#define MAXIMUM_USB_RCB 128 + +#define MAX_BUFFERS_PER_QUEUE 256 + +#define MAX_DATA_BUFFER_SIZE 2048 + +//Num of Asynchronous reads pending +#define NUM_RX_DESC 64 + +#define SYS_CFG 0x0F000C00 + +#endif diff --git a/drivers/staging/bcm/InterfaceMisc.c b/drivers/staging/bcm/InterfaceMisc.c new file mode 100644 index 00000000..2218faea --- /dev/null +++ b/drivers/staging/bcm/InterfaceMisc.c @@ -0,0 +1,235 @@ +#include "headers.h" + +INT InterfaceRDM(PS_INTERFACE_ADAPTER psIntfAdapter, + UINT addr, + PVOID buff, + INT len) +{ + int bytes; + USHORT usRetries = 0; + + if (psIntfAdapter == NULL) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_PRINTK, 0, 0, "Interface Adapter is NULL"); + return -EINVAL; + } + + if (psIntfAdapter->psAdapter->device_removed == TRUE) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_PRINTK, 0, 0, "Device got removed"); + return -ENODEV; + } + + if ((psIntfAdapter->psAdapter->StopAllXaction == TRUE) && (psIntfAdapter->psAdapter->chip_id >= T3LPB)) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, RDM, DBG_LVL_ALL, "Currently Xaction is not allowed on the bus"); + return -EACCES; + } + + if (psIntfAdapter->bSuspended == TRUE || psIntfAdapter->bPreparingForBusSuspend == TRUE) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, RDM, DBG_LVL_ALL, "Bus is in suspended states hence RDM not allowed.."); + return -EACCES; + } + psIntfAdapter->psAdapter->DeviceAccess = TRUE; + + do { + bytes = usb_control_msg(psIntfAdapter->udev, + usb_rcvctrlpipe(psIntfAdapter->udev, 0), + 0x02, + 0xC2, + (addr & 0xFFFF), + ((addr >> 16) & 0xFFFF), + buff, + len, + 5000); + + usRetries++; + if (-ENODEV == bytes) { + psIntfAdapter->psAdapter->device_removed = TRUE; + break; + } + + } while ((bytes < 0) && (usRetries < MAX_RDM_WRM_RETIRES)); + + if (bytes < 0) + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, RDM, DBG_LVL_ALL, "RDM failed status :%d, retires :%d", bytes, usRetries); + else + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, RDM, DBG_LVL_ALL, "RDM sent %d", bytes); + + psIntfAdapter->psAdapter->DeviceAccess = FALSE; + return bytes; +} + +INT InterfaceWRM(PS_INTERFACE_ADAPTER psIntfAdapter, + UINT addr, + PVOID buff, + INT len) +{ + int retval = 0; + USHORT usRetries = 0; + + if (psIntfAdapter == NULL) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_PRINTK, 0, 0, "Interface Adapter is NULL"); + return -EINVAL; + } + + if (psIntfAdapter->psAdapter->device_removed == TRUE) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_PRINTK, 0, 0, "Device got removed"); + return -ENODEV; + } + + if ((psIntfAdapter->psAdapter->StopAllXaction == TRUE) && (psIntfAdapter->psAdapter->chip_id >= T3LPB)) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, WRM, DBG_LVL_ALL, "Currently Xaction is not allowed on the bus..."); + return -EACCES; + } + + if (psIntfAdapter->bSuspended == TRUE || psIntfAdapter->bPreparingForBusSuspend == TRUE) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, WRM, DBG_LVL_ALL, "Bus is in suspended states hence RDM not allowed.."); + return -EACCES; + } + + psIntfAdapter->psAdapter->DeviceAccess = TRUE; + + do { + retval = usb_control_msg(psIntfAdapter->udev, + usb_sndctrlpipe(psIntfAdapter->udev, 0), + 0x01, + 0x42, + (addr & 0xFFFF), + ((addr >> 16) & 0xFFFF), + buff, + len, + 5000); + + usRetries++; + if (-ENODEV == retval) { + psIntfAdapter->psAdapter->device_removed = TRUE; + break; + } + + } while ((retval < 0) && (usRetries < MAX_RDM_WRM_RETIRES)); + + if (retval < 0) { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, WRM, DBG_LVL_ALL, "WRM failed status :%d, retires :%d", retval, usRetries); + psIntfAdapter->psAdapter->DeviceAccess = FALSE; + return retval; + } else { + psIntfAdapter->psAdapter->DeviceAccess = FALSE; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter, DBG_TYPE_OTHERS, WRM, DBG_LVL_ALL, "WRM sent %d", retval); + return STATUS_SUCCESS; + } +} + +INT BcmRDM(PVOID arg, + UINT addr, + PVOID buff, + INT len) +{ + return InterfaceRDM((PS_INTERFACE_ADAPTER)arg, addr, buff, len); +} + +INT BcmWRM(PVOID arg, + UINT addr, + PVOID buff, + INT len) +{ + return InterfaceWRM((PS_INTERFACE_ADAPTER)arg, addr, buff, len); +} + +INT Bcm_clear_halt_of_endpoints(PMINI_ADAPTER Adapter) +{ + PS_INTERFACE_ADAPTER psIntfAdapter = (PS_INTERFACE_ADAPTER)(Adapter->pvInterfaceAdapter); + INT status = STATUS_SUCCESS; + + /* + * usb_clear_halt - tells device to clear endpoint halt/stall condition + * @dev: device whose endpoint is halted + * @pipe: endpoint "pipe" being cleared + * @ Context: !in_interrupt () + * + * usb_clear_halt is the synchrnous call and returns 0 on success else returns with error code. + * This is used to clear halt conditions for bulk and interrupt endpoints only. + * Control and isochronous endpoints never halts. + * + * Any URBs queued for such an endpoint should normally be unlinked by the driver + * before clearing the halt condition. + * + */ + + /* Killing all the submitted urbs to different end points. */ + Bcm_kill_all_URBs(psIntfAdapter); + + /* clear the halted/stalled state for every end point */ + status = usb_clear_halt(psIntfAdapter->udev, psIntfAdapter->sIntrIn.int_in_pipe); + if (status != STATUS_SUCCESS) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL, "Unable to Clear Halt of Interrupt IN end point. :%d ", status); + + status = usb_clear_halt(psIntfAdapter->udev, psIntfAdapter->sBulkIn.bulk_in_pipe); + if (status != STATUS_SUCCESS) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL, "Unable to Clear Halt of Bulk IN end point. :%d ", status); + + status = usb_clear_halt(psIntfAdapter->udev, psIntfAdapter->sBulkOut.bulk_out_pipe); + if (status != STATUS_SUCCESS) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, INTF_INIT, DBG_LVL_ALL, "Unable to Clear Halt of Bulk OUT end point. :%d ", status); + + return status; +} + +VOID Bcm_kill_all_URBs(PS_INTERFACE_ADAPTER psIntfAdapter) +{ + struct urb *tempUrb = NULL; + UINT i; + + /* + * usb_kill_urb - cancel a transfer request and wait for it to finish + * @urb: pointer to URB describing a previously submitted request, + * returns nothing as it is void returned API. + * + * This routine cancels an in-progress request. It is guaranteed that + * upon return all completion handlers will have finished and the URB + * will be totally idle and available for reuse + * + * This routine may not be used in an interrupt context (such as a bottom + * half or a completion handler), or when holding a spinlock, or in other + * situations where the caller can't schedule(). + * + */ + + /* Cancel submitted Interrupt-URB's */ + if (psIntfAdapter->psInterruptUrb != NULL) { + if (psIntfAdapter->psInterruptUrb->status == -EINPROGRESS) + usb_kill_urb(psIntfAdapter->psInterruptUrb); + } + + /* Cancel All submitted TX URB's */ + for (i = 0; i < MAXIMUM_USB_TCB; i++) { + tempUrb = psIntfAdapter->asUsbTcb[i].urb; + if (tempUrb) { + if (tempUrb->status == -EINPROGRESS) + usb_kill_urb(tempUrb); + } + } + + for (i = 0; i < MAXIMUM_USB_RCB; i++) { + tempUrb = psIntfAdapter->asUsbRcb[i].urb; + if (tempUrb) { + if (tempUrb->status == -EINPROGRESS) + usb_kill_urb(tempUrb); + } + } + + atomic_set(&psIntfAdapter->uNumTcbUsed, 0); + atomic_set(&psIntfAdapter->uCurrTcb, 0); + + atomic_set(&psIntfAdapter->uNumRcbUsed, 0); + atomic_set(&psIntfAdapter->uCurrRcb, 0); +} + +VOID putUsbSuspend(struct work_struct *work) +{ + PS_INTERFACE_ADAPTER psIntfAdapter = NULL; + struct usb_interface *intf = NULL; + psIntfAdapter = container_of(work, S_INTERFACE_ADAPTER, usbSuspendWork); + intf = psIntfAdapter->interface; + + if (psIntfAdapter->bSuspended == FALSE) + usb_autopm_put_interface(intf); +} + diff --git a/drivers/staging/bcm/InterfaceMisc.h b/drivers/staging/bcm/InterfaceMisc.h new file mode 100644 index 00000000..6c9e39bf --- /dev/null +++ b/drivers/staging/bcm/InterfaceMisc.h @@ -0,0 +1,42 @@ +#ifndef __INTERFACE_MISC_H +#define __INTERFACE_MISC_H + +INT +InterfaceRDM(PS_INTERFACE_ADAPTER psIntfAdapter, + UINT addr, + PVOID buff, + INT len); + +INT +InterfaceWRM(PS_INTERFACE_ADAPTER psIntfAdapter, + UINT addr, + PVOID buff, + INT len); + + +int InterfaceFileDownload( PVOID psIntfAdapter, + struct file *flp, + unsigned int on_chip_loc); + +int InterfaceFileReadbackFromChip( PVOID psIntfAdapter, + struct file *flp, + unsigned int on_chip_loc); + + +int BcmRDM(PVOID arg, + UINT addr, + PVOID buff, + INT len); + +int BcmWRM(PVOID arg, + UINT addr, + PVOID buff, + INT len); + +INT Bcm_clear_halt_of_endpoints(PMINI_ADAPTER Adapter); + +VOID Bcm_kill_all_URBs(PS_INTERFACE_ADAPTER psIntfAdapter); + +#define DISABLE_USB_ZERO_LEN_INT 0x0F011878 + +#endif // __INTERFACE_MISC_H diff --git a/drivers/staging/bcm/InterfaceRx.c b/drivers/staging/bcm/InterfaceRx.c new file mode 100644 index 00000000..d495828a --- /dev/null +++ b/drivers/staging/bcm/InterfaceRx.c @@ -0,0 +1,264 @@ +#include "headers.h" + +static int SearchVcid(PMINI_ADAPTER Adapter,unsigned short usVcid) +{ + int iIndex=0; + + for(iIndex=(NO_OF_QUEUES-1);iIndex>=0;iIndex--) + if(Adapter->PackInfo[iIndex].usVCID_Value == usVcid) + return iIndex; + return NO_OF_QUEUES+1; + +} + + +static PUSB_RCB +GetBulkInRcb(PS_INTERFACE_ADAPTER psIntfAdapter) +{ + PUSB_RCB pRcb = NULL; + UINT index = 0; + + if((atomic_read(&psIntfAdapter->uNumRcbUsed) < MAXIMUM_USB_RCB) && + (psIntfAdapter->psAdapter->StopAllXaction == FALSE)) + { + index = atomic_read(&psIntfAdapter->uCurrRcb); + pRcb = &psIntfAdapter->asUsbRcb[index]; + pRcb->bUsed = TRUE; + pRcb->psIntfAdapter= psIntfAdapter; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "Got Rx desc %d used %d", + index, atomic_read(&psIntfAdapter->uNumRcbUsed)); + index = (index + 1) % MAXIMUM_USB_RCB; + atomic_set(&psIntfAdapter->uCurrRcb, index); + atomic_inc(&psIntfAdapter->uNumRcbUsed); + } + return pRcb; +} + +/*this is receive call back - when pkt available for receive (BULK IN- end point)*/ +static void read_bulk_callback(struct urb *urb) +{ + struct sk_buff *skb = NULL; + BOOLEAN bHeaderSupressionEnabled = FALSE; + int QueueIndex = NO_OF_QUEUES + 1; + UINT uiIndex=0; + int process_done = 1; + //int idleflag = 0 ; + PUSB_RCB pRcb = (PUSB_RCB)urb->context; + PS_INTERFACE_ADAPTER psIntfAdapter = pRcb->psIntfAdapter; + PMINI_ADAPTER Adapter = psIntfAdapter->psAdapter; + PLEADER pLeader = urb->transfer_buffer; + + if (unlikely(netif_msg_rx_status(Adapter))) + pr_info(PFX "%s: rx urb status %d length %d\n", + Adapter->dev->name, urb->status, urb->actual_length); + + if((Adapter->device_removed == TRUE) || + (TRUE == Adapter->bEndPointHalted) || + (0 == urb->actual_length) + ) + { + pRcb->bUsed = FALSE; + atomic_dec(&psIntfAdapter->uNumRcbUsed); + return; + } + + if(urb->status != STATUS_SUCCESS) + { + if(urb->status == -EPIPE) + { + Adapter->bEndPointHalted = TRUE ; + wake_up(&Adapter->tx_packet_wait_queue); + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL,"Rx URB has got cancelled. status :%d", urb->status); + } + pRcb->bUsed = FALSE; + atomic_dec(&psIntfAdapter->uNumRcbUsed); + urb->status = STATUS_SUCCESS ; + return ; + } + + if(Adapter->bDoSuspend && (Adapter->bPreparingForLowPowerMode)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL,"device is going in low power mode while PMU option selected..hence rx packet should not be process"); + return ; + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "Read back done len %d\n", pLeader->PLength); + if(!pLeader->PLength) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "Leader Length 0"); + atomic_dec(&psIntfAdapter->uNumRcbUsed); + return; + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "Leader Status:0x%hX, Length:0x%hX, VCID:0x%hX", pLeader->Status,pLeader->PLength,pLeader->Vcid); + if(MAX_CNTL_PKT_SIZE < pLeader->PLength) + { + if (netif_msg_rx_err(Adapter)) + pr_info(PFX "%s: corrupted leader length...%d\n", + Adapter->dev->name, pLeader->PLength); + ++Adapter->dev->stats.rx_dropped; + atomic_dec(&psIntfAdapter->uNumRcbUsed); + return; + } + + QueueIndex = SearchVcid( Adapter,pLeader->Vcid); + if(QueueIndex < NO_OF_QUEUES) + { + bHeaderSupressionEnabled = + Adapter->PackInfo[QueueIndex].bHeaderSuppressionEnabled; + bHeaderSupressionEnabled = + bHeaderSupressionEnabled & Adapter->bPHSEnabled; + } + + skb = dev_alloc_skb (pLeader->PLength + SKB_RESERVE_PHS_BYTES + SKB_RESERVE_ETHERNET_HEADER);//2 //2 for allignment + if(!skb) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "NO SKBUFF!!! Dropping the Packet"); + atomic_dec(&psIntfAdapter->uNumRcbUsed); + return; + } + /* If it is a control Packet, then call handle_bcm_packet ()*/ + if((ntohs(pLeader->Vcid) == VCID_CONTROL_PACKET) || + (!(pLeader->Status >= 0x20 && pLeader->Status <= 0x3F))) + { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_RX, RX_CTRL, DBG_LVL_ALL, "Received control pkt..."); + *(PUSHORT)skb->data = pLeader->Status; + memcpy(skb->data+sizeof(USHORT), urb->transfer_buffer + + (sizeof(LEADER)), pLeader->PLength); + skb->len = pLeader->PLength + sizeof(USHORT); + + spin_lock(&Adapter->control_queue_lock); + ENQUEUEPACKET(Adapter->RxControlHead,Adapter->RxControlTail,skb); + spin_unlock(&Adapter->control_queue_lock); + + atomic_inc(&Adapter->cntrlpktCnt); + wake_up(&Adapter->process_rx_cntrlpkt); + } + else + { + /* + * Data Packet, Format a proper Ethernet Header + * and give it to the stack + */ + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_RX, RX_DATA, DBG_LVL_ALL, "Received Data pkt..."); + skb_reserve(skb, 2 + SKB_RESERVE_PHS_BYTES); + memcpy(skb->data+ETH_HLEN, (PUCHAR)urb->transfer_buffer + sizeof(LEADER), pLeader->PLength); + skb->dev = Adapter->dev; + + /* currently skb->len has extra ETH_HLEN bytes in the beginning */ + skb_put (skb, pLeader->PLength + ETH_HLEN); + Adapter->PackInfo[QueueIndex].uiTotalRxBytes+=pLeader->PLength; + Adapter->PackInfo[QueueIndex].uiThisPeriodRxBytes+= pLeader->PLength; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_RX, RX_DATA, DBG_LVL_ALL, "Received Data pkt of len :0x%X", pLeader->PLength); + + if(netif_running(Adapter->dev)) + { + /* Moving ahead by ETH_HLEN to the data ptr as received from FW */ + skb_pull(skb, ETH_HLEN); + PHSReceive(Adapter, pLeader->Vcid, skb, &skb->len, + NULL,bHeaderSupressionEnabled); + + if(!Adapter->PackInfo[QueueIndex].bEthCSSupport) + { + skb_push(skb, ETH_HLEN); + + memcpy(skb->data, skb->dev->dev_addr, 6); + memcpy(skb->data+6, skb->dev->dev_addr, 6); + (*(skb->data+11))++; + *(skb->data+12) = 0x08; + *(skb->data+13) = 0x00; + pLeader->PLength+=ETH_HLEN; + } + + skb->protocol = eth_type_trans(skb, Adapter->dev); + process_done = netif_rx(skb); + } + else + { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_RX, RX_DATA, DBG_LVL_ALL, "i/f not up hance freeing SKB..."); + dev_kfree_skb(skb); + } + + ++Adapter->dev->stats.rx_packets; + Adapter->dev->stats.rx_bytes += pLeader->PLength; + + for(uiIndex = 0 ; uiIndex < MIBS_MAX_HIST_ENTRIES ; uiIndex++) + { + if((pLeader->PLength <= MIBS_PKTSIZEHIST_RANGE*(uiIndex+1)) + && (pLeader->PLength > MIBS_PKTSIZEHIST_RANGE*(uiIndex))) + Adapter->aRxPktSizeHist[uiIndex]++; + } + } + Adapter->PrevNumRecvDescs++; + pRcb->bUsed = FALSE; + atomic_dec(&psIntfAdapter->uNumRcbUsed); +} + +static int ReceiveRcb(PS_INTERFACE_ADAPTER psIntfAdapter, PUSB_RCB pRcb) +{ + struct urb *urb = pRcb->urb; + int retval = 0; + + usb_fill_bulk_urb(urb, psIntfAdapter->udev, usb_rcvbulkpipe( + psIntfAdapter->udev, psIntfAdapter->sBulkIn.bulk_in_endpointAddr), + urb->transfer_buffer, BCM_USB_MAX_READ_LENGTH, read_bulk_callback, + pRcb); + if(FALSE == psIntfAdapter->psAdapter->device_removed && + FALSE == psIntfAdapter->psAdapter->bEndPointHalted && + FALSE == psIntfAdapter->bSuspended && + FALSE == psIntfAdapter->bPreparingForBusSuspend) + { + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "failed submitting read urb, error %d", retval); + //if this return value is because of pipe halt. need to clear this. + if(retval == -EPIPE) + { + psIntfAdapter->psAdapter->bEndPointHalted = TRUE ; + wake_up(&psIntfAdapter->psAdapter->tx_packet_wait_queue); + } + + } + } + return retval; +} + +/* +Function: InterfaceRx + +Description: This is the hardware specific Function for Receiving + data packet/control packets from the device. + +Input parameters: IN PMINI_ADAPTER Adapter - Miniport Adapter Context + + + +Return: TRUE - If Rx was successful. + Other - If an error occurred. +*/ + +BOOLEAN InterfaceRx (PS_INTERFACE_ADAPTER psIntfAdapter) +{ + USHORT RxDescCount = NUM_RX_DESC - atomic_read(&psIntfAdapter->uNumRcbUsed); + PUSB_RCB pRcb = NULL; + +// RxDescCount = psIntfAdapter->psAdapter->CurrNumRecvDescs - +// psIntfAdapter->psAdapter->PrevNumRecvDescs; + while(RxDescCount) + { + pRcb = GetBulkInRcb(psIntfAdapter); + if(pRcb == NULL) + { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_PRINTK, 0, 0, "Unable to get Rcb pointer"); + return FALSE; + } + //atomic_inc(&psIntfAdapter->uNumRcbUsed); + ReceiveRcb(psIntfAdapter, pRcb); + RxDescCount--; + } + return TRUE; +} + diff --git a/drivers/staging/bcm/InterfaceRx.h b/drivers/staging/bcm/InterfaceRx.h new file mode 100644 index 00000000..96e81a1d --- /dev/null +++ b/drivers/staging/bcm/InterfaceRx.h @@ -0,0 +1,7 @@ +#ifndef _INTERFACE_RX_H +#define _INTERFACE_RX_H + +BOOLEAN InterfaceRx(PS_INTERFACE_ADAPTER Adapter); + +#endif + diff --git a/drivers/staging/bcm/InterfaceTx.c b/drivers/staging/bcm/InterfaceTx.c new file mode 100644 index 00000000..a842de9d --- /dev/null +++ b/drivers/staging/bcm/InterfaceTx.c @@ -0,0 +1,197 @@ +#include "headers.h" + +/*this is transmit call-back(BULK OUT)*/ +static void write_bulk_callback(struct urb *urb/*, struct pt_regs *regs*/) +{ + PUSB_TCB pTcb= (PUSB_TCB)urb->context; + PS_INTERFACE_ADAPTER psIntfAdapter = pTcb->psIntfAdapter; + CONTROL_MESSAGE *pControlMsg = (CONTROL_MESSAGE *)urb->transfer_buffer; + PMINI_ADAPTER psAdapter = psIntfAdapter->psAdapter ; + BOOLEAN bpowerDownMsg = FALSE ; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + if (unlikely(netif_msg_tx_done(Adapter))) + pr_info(PFX "%s: transmit status %d\n", Adapter->dev->name, urb->status); + + if(urb->status != STATUS_SUCCESS) + { + if(urb->status == -EPIPE) + { + psIntfAdapter->psAdapter->bEndPointHalted = TRUE ; + wake_up(&psIntfAdapter->psAdapter->tx_packet_wait_queue); + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL,"Tx URB has got cancelled. status :%d", urb->status); + } + } + + pTcb->bUsed = FALSE; + atomic_dec(&psIntfAdapter->uNumTcbUsed); + + + + if(TRUE == psAdapter->bPreparingForLowPowerMode) + { + + if(((pControlMsg->szData[0] == GO_TO_IDLE_MODE_PAYLOAD) && + (pControlMsg->szData[1] == TARGET_CAN_GO_TO_IDLE_MODE))) + + { + bpowerDownMsg = TRUE ; + //This covers the bus err while Idle Request msg sent down. + if(urb->status != STATUS_SUCCESS) + { + psAdapter->bPreparingForLowPowerMode = FALSE ; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL,"Idle Mode Request msg failed to reach to Modem"); + //Signalling the cntrl pkt path in Ioctl + wake_up(&psAdapter->lowpower_mode_wait_queue); + StartInterruptUrb(psIntfAdapter); + goto err_exit; + } + + if(psAdapter->bDoSuspend == FALSE) + { + psAdapter->IdleMode = TRUE; + //since going in Idle mode completed hence making this var false; + psAdapter->bPreparingForLowPowerMode = FALSE ; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL, "Host Entered in Idle Mode State..."); + //Signalling the cntrl pkt path in Ioctl + wake_up(&psAdapter->lowpower_mode_wait_queue); + } + + } + else if((pControlMsg->Leader.Status == LINK_UP_CONTROL_REQ) && + (pControlMsg->szData[0] == LINK_UP_ACK) && + (pControlMsg->szData[1] == LINK_SHUTDOWN_REQ_FROM_FIRMWARE) && + (pControlMsg->szData[2] == SHUTDOWN_ACK_FROM_DRIVER)) + { + //This covers the bus err while shutdown Request msg sent down. + if(urb->status != STATUS_SUCCESS) + { + psAdapter->bPreparingForLowPowerMode = FALSE ; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL,"Shutdown Request Msg failed to reach to Modem"); + //Signalling the cntrl pkt path in Ioctl + wake_up(&psAdapter->lowpower_mode_wait_queue); + StartInterruptUrb(psIntfAdapter); + goto err_exit; + } + + bpowerDownMsg = TRUE ; + if(psAdapter->bDoSuspend == FALSE) + { + psAdapter->bShutStatus = TRUE; + //since going in shutdown mode completed hence making this var false; + psAdapter->bPreparingForLowPowerMode = FALSE ; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL,"Host Entered in shutdown Mode State..."); + //Signalling the cntrl pkt path in Ioctl + wake_up(&psAdapter->lowpower_mode_wait_queue); + } + } + + if(psAdapter->bDoSuspend && bpowerDownMsg) + { + //issuing bus suspend request + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL,"Issuing the Bus suspend request to USB stack"); + psIntfAdapter->bPreparingForBusSuspend = TRUE; + schedule_work(&psIntfAdapter->usbSuspendWork); + + } + + } + +err_exit : + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); +} + + +static PUSB_TCB GetBulkOutTcb(PS_INTERFACE_ADAPTER psIntfAdapter) +{ + PUSB_TCB pTcb = NULL; + UINT index = 0; + + if((atomic_read(&psIntfAdapter->uNumTcbUsed) < MAXIMUM_USB_TCB) && + (psIntfAdapter->psAdapter->StopAllXaction ==FALSE)) + { + index = atomic_read(&psIntfAdapter->uCurrTcb); + pTcb = &psIntfAdapter->asUsbTcb[index]; + pTcb->bUsed = TRUE; + pTcb->psIntfAdapter= psIntfAdapter; + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL, "Got Tx desc %d used %d", + index, atomic_read(&psIntfAdapter->uNumTcbUsed)); + index = (index + 1) % MAXIMUM_USB_TCB; + atomic_set(&psIntfAdapter->uCurrTcb, index); + atomic_inc(&psIntfAdapter->uNumTcbUsed); + } + return pTcb; +} + +static int TransmitTcb(PS_INTERFACE_ADAPTER psIntfAdapter, PUSB_TCB pTcb, PVOID data, int len) +{ + + struct urb *urb = pTcb->urb; + int retval = 0; + + urb->transfer_buffer = usb_alloc_coherent(psIntfAdapter->udev, len, + GFP_ATOMIC, &urb->transfer_dma); + if (!urb->transfer_buffer) + { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_PRINTK, 0, 0, "Error allocating memory\n"); + return -ENOMEM; + } + memcpy(urb->transfer_buffer, data, len); + urb->transfer_buffer_length = len; + + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL, "Sending Bulk out packet\n"); + //For T3B,INT OUT end point will be used as bulk out end point + if((psIntfAdapter->psAdapter->chip_id == T3B) && (psIntfAdapter->bHighSpeedDevice == TRUE)) + { + usb_fill_int_urb(urb, psIntfAdapter->udev, + psIntfAdapter->sBulkOut.bulk_out_pipe, + urb->transfer_buffer, len, write_bulk_callback, pTcb, + psIntfAdapter->sBulkOut.int_out_interval); + } + else + { + usb_fill_bulk_urb(urb, psIntfAdapter->udev, + psIntfAdapter->sBulkOut.bulk_out_pipe, + urb->transfer_buffer, len, write_bulk_callback, pTcb); + } + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /* For DMA transfer */ + + if(FALSE == psIntfAdapter->psAdapter->device_removed && + FALSE == psIntfAdapter->psAdapter->bEndPointHalted && + FALSE == psIntfAdapter->bSuspended && + FALSE == psIntfAdapter->bPreparingForBusSuspend) + { + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL, "failed submitting write urb, error %d", retval); + if(retval == -EPIPE) + { + psIntfAdapter->psAdapter->bEndPointHalted = TRUE ; + wake_up(&psIntfAdapter->psAdapter->tx_packet_wait_queue); + } + } + } + return retval; +} + +int InterfaceTransmitPacket(PVOID arg, PVOID data, UINT len) +{ + PUSB_TCB pTcb= NULL; + + PS_INTERFACE_ADAPTER psIntfAdapter = (PS_INTERFACE_ADAPTER)arg; + pTcb= GetBulkOutTcb(psIntfAdapter); + if(pTcb == NULL) + { + BCM_DEBUG_PRINT(psIntfAdapter->psAdapter,DBG_TYPE_PRINTK, 0, 0, "No URB to transmit packet, dropping packet"); + return -EFAULT; + } + return TransmitTcb(psIntfAdapter, pTcb, data, len); +} + + diff --git a/drivers/staging/bcm/InterfaceTx.h b/drivers/staging/bcm/InterfaceTx.h new file mode 100644 index 00000000..27314757 --- /dev/null +++ b/drivers/staging/bcm/InterfaceTx.h @@ -0,0 +1,7 @@ +#ifndef _INTERFACE_TX_H +#define _INTERFACE_TX_H + +INT InterfaceTransmitPacket(PVOID arg, PVOID data, UINT len); + +#endif + diff --git a/drivers/staging/bcm/Ioctl.h b/drivers/staging/bcm/Ioctl.h new file mode 100644 index 00000000..f859cf1c --- /dev/null +++ b/drivers/staging/bcm/Ioctl.h @@ -0,0 +1,360 @@ +#ifndef _IOCTL_H_ +#define _IOCTL_H_ + +typedef struct rdmbuffer +{ + ULONG Register; + ULONG Length; +}__attribute__((packed)) RDM_BUFFER, *PRDM_BUFFER; + + +typedef struct wrmbuffer +{ + ULONG Register; + ULONG Length; + UCHAR Data[4]; +}__attribute__((packed)) WRM_BUFFER, *PWRM_BUFFER; + + +typedef struct ioctlbuffer +{ + void __user *InputBuffer; + ULONG InputLength; + void __user *OutputBuffer; + ULONG OutputLength; +}__attribute__((packed)) IOCTL_BUFFER, *PIOCTL_BUFFER; + +typedef struct stGPIOInfo +{ + UINT uiGpioNumber ; /* valid numbers 0-15 */ + UINT uiGpioValue; /* 1 set ; 0 not set */ +}__attribute__((packed))GPIO_INFO,*PGPIO_INFO; +typedef struct stUserThreadReq +{ + //0->Inactivate LED thread. + //1->Activate the LED thread + UINT ThreadState; +}__attribute__((packed))USER_THREAD_REQ,*PUSER_THREAD_REQ; +#define LED_THREAD_ACTIVATION_REQ 1 + + +////********** ioctl codes ***********//// + +#define BCM_IOCTL 'k' + +//1.Control code for CONTROL MESSAGES + +#define IOCTL_SEND_CONTROL_MESSAGE _IOW(BCM_IOCTL, 0x801,int) + +//2.Control code to write a particular value to a particular register +#define IOCTL_BCM_REGISTER_WRITE _IOW(BCM_IOCTL, 0x802, int) // + +//3. +#define IOCTL_BCM_REGISTER_READ _IOR(BCM_IOCTL, 0x803, int) // + +//4.Control code to write x number of bytes to common memory +//starting from address y +#define IOCTL_BCM_COMMON_MEMORY_WRITE _IOW(BCM_IOCTL, 0x804, int)// + +//5.Control code to write x number of bytes to common memory +//starting from address y +#define IOCTL_BCM_COMMON_MEMORY_READ _IOR(BCM_IOCTL, 0x805, int)// + +//6.Control code for CONTROL MESSAGES +#define IOCTL_GET_CONTROL_MESSAGE _IOR(BCM_IOCTL, 0x806, int)// + +//7.Control code for FIRMWARE DOWNLOAD +#define IOCTL_BCM_FIRMWARE_DOWNLOAD _IOW(BCM_IOCTL, 0x807, int)// + +#define IOCTL_BCM_SET_SEND_VCID _IOW(BCM_IOCTL, 0x808, int) + +//9.Control code for TRANSFER MODE SWITCHING +#define IOCTL_BCM_SWITCH_TRANSFER_MODE _IOW(BCM_IOCTL, 0x809, int) +//10.Control code for LINK UP +#define IOCTL_LINK_REQ _IOW(BCM_IOCTL, 0x80A, int) + +//11.Control code for RSSI Level Request +#define IOCTL_RSSI_LEVEL_REQ _IOW(BCM_IOCTL, 0x80B, int) +//12.Control code for IDLE MODE CONTROL +#define IOCTL_IDLE_REQ _IOW(BCM_IOCTL, 0x80C, int) +//13.Control code for SS/BS info +#define IOCTL_SS_INFO_REQ _IOW(BCM_IOCTL, 0x80D, int) + +#define IOCTL_GET_STATISTICS_POINTER _IOW(BCM_IOCTL, 0x80E, int) + +#define IOCTL_CM_REQUEST _IOW(BCM_IOCTL, 0x80F, int) + +#define IOCTL_INIT_PARAM_REQ _IOW(BCM_IOCTL, 0x810, int) + +#define IOCTL_MAC_ADDR_REQ _IOW(BCM_IOCTL, 0x811, int) + +#define IOCTL_MAC_ADDR_RESP _IOWR(BCM_IOCTL, 0x812, int) + +#define IOCTL_CLASSIFICATION_RULE _IOW(BCM_IOCTL, 0x813, char) + +#define IOCTL_CLOSE_NOTIFICATION _IO(BCM_IOCTL, 0x814) + +#define IOCTL_LINK_UP _IO(BCM_IOCTL, 0x815) + +#define IOCTL_LINK_DOWN _IO(BCM_IOCTL, 0x816, IOCTL_BUFFER) + +#define IOCTL_CHIP_RESET _IO(BCM_IOCTL, 0x816) + +#define IOCTL_CINR_LEVEL_REQ _IOW(BCM_IOCTL, 0x817, char) + +#define IOCTL_WTM_CONTROL_REQ _IOW(BCM_IOCTL, 0x817,char) + +#define IOCTL_BE_BUCKET_SIZE _IOW(BCM_IOCTL, 0x818, unsigned long) + +#define IOCTL_RTPS_BUCKET_SIZE _IOW(BCM_IOCTL, 0x819, unsigned long) + +#define IOCTL_QOS_THRESHOLD _IOW(BCM_IOCTL, 0x820, unsigned long) + +#define IOCTL_DUMP_PACKET_INFO _IO(BCM_IOCTL, 0x821) + +#define IOCTL_GET_PACK_INFO _IOR(BCM_IOCTL, 0x823, int) + +#define IOCTL_BCM_GET_DRIVER_VERSION _IOR(BCM_IOCTL, 0x829, int) + +#define IOCTL_BCM_GET_CURRENT_STATUS _IOW(BCM_IOCTL, 0x828, int) + +#define IOCTL_BCM_GPIO_SET_REQUEST _IOW(BCM_IOCTL, 0x82A, int) + +#define IOCTL_BCM_GPIO_STATUS_REQUEST _IOW(BCM_IOCTL, 0x82b, int) + +#define IOCTL_BCM_GET_DSX_INDICATION _IOR(BCM_IOCTL, 0x854, int) + +#define IOCTL_BCM_BUFFER_DOWNLOAD_START _IOW(BCM_IOCTL, 0x855, int) + +#define IOCTL_BCM_BUFFER_DOWNLOAD _IOW(BCM_IOCTL, 0x856, int) + +#define IOCTL_BCM_BUFFER_DOWNLOAD_STOP _IOW(BCM_IOCTL, 0x857, int) + +#define IOCTL_BCM_REGISTER_WRITE_PRIVATE _IOW(BCM_IOCTL, 0x826, char) + +#define IOCTL_BCM_REGISTER_READ_PRIVATE _IOW(BCM_IOCTL, 0x827, char) + +#define IOCTL_BCM_SET_DEBUG _IOW(BCM_IOCTL, 0x824, IOCTL_BUFFER) + +#define IOCTL_BCM_EEPROM_REGISTER_WRITE _IOW(BCM_IOCTL, 0x858, int) + +#define IOCTL_BCM_EEPROM_REGISTER_READ _IOR(BCM_IOCTL, 0x859, int) + +#define IOCTL_BCM_WAKE_UP_DEVICE_FROM_IDLE _IOR(BCM_IOCTL, 0x860, int) + +#define IOCTL_BCM_SET_MAC_TRACING _IOW(BCM_IOCTL, 0x82c, int) + +#define IOCTL_BCM_GET_HOST_MIBS _IOW(BCM_IOCTL, 0x853, int) + +#define IOCTL_BCM_NVM_READ _IOR(BCM_IOCTL, 0x861, int) + +#define IOCTL_BCM_NVM_WRITE _IOW(BCM_IOCTL, 0x862, int) + +#define IOCTL_BCM_GET_NVM_SIZE _IOR(BCM_IOCTL, 0x863, int) + +#define IOCTL_BCM_CAL_INIT _IOR(BCM_IOCTL, 0x864, int) + +#define IOCTL_BCM_BULK_WRM _IOW(BCM_IOCTL, 0x90B, int) + +#define IOCTL_BCM_FLASH2X_SECTION_READ _IOR(BCM_IOCTL, 0x865, int) + +#define IOCTL_BCM_FLASH2X_SECTION_WRITE _IOW(BCM_IOCTL, 0x866, int) + +#define IOCTL_BCM_GET_FLASH2X_SECTION_BITMAP _IOR(BCM_IOCTL,0x867, int) + +#define IOCTL_BCM_SET_ACTIVE_SECTION _IOW(BCM_IOCTL,0x868, int) + +#define IOCTL_BCM_IDENTIFY_ACTIVE_SECTION _IO(BCM_IOCTL,0x869) + +#define IOCTL_BCM_COPY_SECTION _IOW(BCM_IOCTL, 0x870,int) + +#define IOCTL_BCM_GET_FLASH_CS_INFO _IOR(BCM_IOCTL, 0x871, int) + +#define IOCTL_BCM_SELECT_DSD _IOW(BCM_IOCTL, 0x872, int) + +#define IOCTL_BCM_NVM_RAW_READ _IOR(BCM_IOCTL, 0x875, int) + +#define IOCTL_BCM_CNTRLMSG_MASK _IOW(BCM_IOCTL, 0x874, int) + +#define IOCTL_BCM_GET_DEVICE_DRIVER_INFO _IOR(BCM_IOCTL, 0x877, int) + +#define IOCTL_BCM_TIME_SINCE_NET_ENTRY _IOR(BCM_IOCTL, 0x876, int) + +#define BCM_LED_THREAD_STATE_CHANGE_REQ _IOW(BCM_IOCTL, 0x878, int) + +#define IOCTL_BCM_GPIO_MULTI_REQUEST _IOW(BCM_IOCTL, 0x82D, IOCTL_BUFFER) +#define IOCTL_BCM_GPIO_MODE_REQUEST _IOW(BCM_IOCTL, 0x82E, IOCTL_BUFFER) + + + +typedef enum _BCM_INTERFACE_TYPE +{ + BCM_MII, + BCM_CARDBUS, + BCM_USB, + BCM_SDIO, + BCM_PCMCIA +}BCM_INTERFACE_TYPE; + +typedef struct _DEVICE_DRIVER_INFO +{ + NVM_TYPE u32NVMType; + UINT MaxRDMBufferSize; + BCM_INTERFACE_TYPE u32InterfaceType; + UINT u32DSDStartOffset; + UINT u32RxAlignmentCorrection; + UINT u32Reserved[10]; +} DEVICE_DRIVER_INFO; + +typedef struct _NVM_READWRITE +{ + + void __user *pBuffer; +// Data to be written from|read to. Memory should be allocated by the caller. + + uint32_t uiOffset; +// offset at which data should be written to or read from. + + uint32_t uiNumBytes; +// No. of bytes to be written or read. + + bool bVerify; +// Applicable only for write. If set verification of written data will be done. + +} NVM_READWRITE,*PNVM_READWRITE; +typedef struct bulkwrmbuffer +{ + ULONG Register; + ULONG SwapEndian; + ULONG Values[1]; + +}BULKWRM_BUFFER,*PBULKWRM_BUFFER; + + +/***********Structure used for FlashMap2.x *******************************/ + +/* +* These are Sction present inside the Flash. +* There is sectional RD/WR for flash Map 2.x. +* hence these section will be used in read/write API. +*/ + +typedef enum _FLASH2X_SECTION_VAL +{ + NO_SECTION_VAL = 0, //no section is chosen when absolute offset is given for RD/WR + ISO_IMAGE1, + ISO_IMAGE2, + DSD0, + DSD1, + DSD2, + VSA0, + VSA1, + VSA2, + SCSI, + CONTROL_SECTION, + ISO_IMAGE1_PART2, + ISO_IMAGE1_PART3, + ISO_IMAGE2_PART2, + ISO_IMAGE2_PART3, + TOTAL_SECTIONS +}FLASH2X_SECTION_VAL; + +/* +* Structure used for READ/WRITE Flash Map2.x +*/ +typedef struct _FLASH2X_READWRITE +{ + + FLASH2X_SECTION_VAL Section; //which section has to be read/written + B_UINT32 offset; //Offset within Section. + B_UINT32 numOfBytes; //NOB from the offset + B_UINT32 bVerify; + void __user *pDataBuff; //Buffer for reading/writing + +}FLASH2X_READWRITE, *PFLASH2X_READWRITE; +/* +* This structure is used for coping one section to other. +* there are two ways to copy one section to other. +* it NOB =0, complete section will be copied on to other. +* if NOB !=0, only NOB will be copied from the given offset. +*/ + +typedef struct _FLASH2X_COPY_SECTION +{ + //Src Section from which Data has to be copied to DstSection + FLASH2X_SECTION_VAL SrcSection; + + //Destination Section from where Data has to be coppied. + FLASH2X_SECTION_VAL DstSection; + + //Offset within Section. if NOB =0 it will be ignored and data will be coped from offset 0. + B_UINT32 offset; + + //NOB from the offset. if NOB = 0 complete src section will be copied to Destination section. + B_UINT32 numOfBytes; +} FLASH2X_COPY_SECTION, *PFLASH2X_COPY_SECTION; + + +typedef enum _SECTION_TYPE +{ + ISO = 0, + VSA = 1, + DSD = 2 +} SECTION_TYPE, *PSECTION_TYPE; + +/* +* This section provide the complete bitmap of the Flash. +* using this map lib/APP will isssue read/write command. + Fields are defined as : + Bit [0] = section is present //1:present, 0: Not present +* Bit [1] = section is valid //1: valid, 0: not valid +* Bit [2] = Section is R/W //0: RW, 1: RO +* Bit [3] = Section is Active or not 1 means Active, 0->inactive +* Bit [7...3] = Reserved +*/ + +typedef struct _FLASH2X_BITMAP +{ + UCHAR ISO_IMAGE1; + UCHAR ISO_IMAGE2; + UCHAR DSD0; + UCHAR DSD1; + UCHAR DSD2; + UCHAR VSA0; + UCHAR VSA1; + UCHAR VSA2; + UCHAR SCSI; + UCHAR CONTROL_SECTION; + //Reserved for future use + UCHAR Reserved0; + UCHAR Reserved1; + UCHAR Reserved2; +}FLASH2X_BITMAP, *PFLASH2X_BITMAP; + +//for net entry time check +typedef struct _ST_TIME_ELAPSED_ +{ + ULONG64 ul64TimeElapsedSinceNetEntry; + UINT32 uiReserved[4]; //By chance if required for future proofing +}ST_TIME_ELAPSED,*PST_TIME_ELAPSED; + +enum { + WIMAX_IDX=0, /*To access WiMAX chip GPIO's for GPIO_MULTI_INFO or GPIO_MULTI_MODE*/ + HOST_IDX, /*To access Host chip GPIO's for GPIO_MULTI_INFO or GPIO_MULTI_MODE*/ + MAX_IDX +}; +typedef struct stGPIOMultiInfo +{ + UINT uiGPIOCommand; /* 1 for set and 0 for get*/ + UINT uiGPIOMask; /* set the correspondig bit to 1 to access GPIO*/ + UINT uiGPIOValue; /* 0 or 1; value to be set when command is 1.*/ +}__attribute__((packed))GPIO_MULTI_INFO , *PGPIO_MULTI_INFO; + +typedef struct stGPIOMultiMode +{ + UINT uiGPIOMode; /* 1 for OUT mode, 0 for IN mode*/ + UINT uiGPIOMask; /* GPIO mask to set mode*/ +}__attribute__((packed))GPIO_MULTI_MODE, *PGPIO_MULTI_MODE; + + +#endif diff --git a/drivers/staging/bcm/Kconfig b/drivers/staging/bcm/Kconfig new file mode 100644 index 00000000..96adb102 --- /dev/null +++ b/drivers/staging/bcm/Kconfig @@ -0,0 +1,7 @@ +config BCM_WIMAX + tristate "Beceem BCS200/BCS220-3 and BCSM250 wimax support" + depends on USB && NET && EXPERIMENTAL + default N + help + This is an experimental driver for the Beceem WIMAX chipset used + by Sprint 4G. diff --git a/drivers/staging/bcm/LeakyBucket.c b/drivers/staging/bcm/LeakyBucket.c new file mode 100644 index 00000000..a55d4228 --- /dev/null +++ b/drivers/staging/bcm/LeakyBucket.c @@ -0,0 +1,353 @@ +/********************************************************************** +* LEAKYBUCKET.C +* This file contains the routines related to Leaky Bucket Algorithm. +***********************************************************************/ +#include "headers.h" + +/********************************************************************* +* Function - UpdateTokenCount() +* +* Description - This function calculates the token count for each +* channel and updates the same in Adapter strucuture. +* +* Parameters - Adapter: Pointer to the Adapter structure. +* +* Returns - None +**********************************************************************/ + +static VOID UpdateTokenCount(register PMINI_ADAPTER Adapter) +{ + ULONG liCurrentTime; + INT i = 0; + struct timeval tv; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TOKEN_COUNTS, DBG_LVL_ALL, "=====>\n"); + if(NULL == Adapter) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TOKEN_COUNTS, DBG_LVL_ALL, "Adapter found NULL!\n"); + return; + } + + do_gettimeofday(&tv); + for(i = 0; i < NO_OF_QUEUES; i++) + { + if(TRUE == Adapter->PackInfo[i].bValid && + (1 == Adapter->PackInfo[i].ucDirection)) + { + liCurrentTime = ((tv.tv_sec- + Adapter->PackInfo[i].stLastUpdateTokenAt.tv_sec)*1000 + + (tv.tv_usec-Adapter->PackInfo[i].stLastUpdateTokenAt.tv_usec)/ + 1000); + if(0!=liCurrentTime) + { + Adapter->PackInfo[i].uiCurrentTokenCount += (ULONG) + ((Adapter->PackInfo[i].uiMaxAllowedRate) * + ((ULONG)((liCurrentTime)))/1000); + memcpy(&Adapter->PackInfo[i].stLastUpdateTokenAt, + &tv, sizeof(struct timeval)); + Adapter->PackInfo[i].liLastUpdateTokenAt = liCurrentTime; + if((Adapter->PackInfo[i].uiCurrentTokenCount) >= + Adapter->PackInfo[i].uiMaxBucketSize) + { + Adapter->PackInfo[i].uiCurrentTokenCount = + Adapter->PackInfo[i].uiMaxBucketSize; + } + } + } + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TOKEN_COUNTS, DBG_LVL_ALL, "<=====\n"); + return; + +} + + +/********************************************************************* +* Function - IsPacketAllowedForFlow() +* +* Description - This function checks whether the given packet from the +* specified queue can be allowed for transmission by +* checking the token count. +* +* Parameters - Adapter : Pointer to the Adpater structure. +* - iQIndex : The queue Identifier. +* - ulPacketLength: Number of bytes to be transmitted. +* +* Returns - The number of bytes allowed for transmission. +* +***********************************************************************/ +static ULONG GetSFTokenCount(PMINI_ADAPTER Adapter, PacketInfo *psSF) +{ + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TOKEN_COUNTS, DBG_LVL_ALL, "IsPacketAllowedForFlow ===>"); + /* Validate the parameters */ + if(NULL == Adapter || (psSF < Adapter->PackInfo && + (uintptr_t)psSF > (uintptr_t) &Adapter->PackInfo[HiPriority])) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TOKEN_COUNTS, DBG_LVL_ALL, "IPAFF: Got wrong Parameters:Adapter: %p, QIndex: %zd\n", Adapter, (psSF-Adapter->PackInfo)); + return 0; + } + + if(FALSE != psSF->bValid && psSF->ucDirection) + { + if(0 != psSF->uiCurrentTokenCount) + { + return psSF->uiCurrentTokenCount; + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TOKEN_COUNTS, DBG_LVL_ALL, "Not enough tokens in queue %zd Available %u\n", + psSF-Adapter->PackInfo, psSF->uiCurrentTokenCount); + psSF->uiPendedLast = 1; + } + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TOKEN_COUNTS, DBG_LVL_ALL, "IPAFF: Queue %zd not valid\n", psSF-Adapter->PackInfo); + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TOKEN_COUNTS, DBG_LVL_ALL, "IsPacketAllowedForFlow <==="); + return 0; +} + +/** +@ingroup tx_functions +This function despatches packet from the specified queue. +@return Zero(success) or Negative value(failure) +*/ +static INT SendPacketFromQueue(PMINI_ADAPTER Adapter,/**"); + if(!Adapter || !Packet || !psSF) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, SEND_QUEUE, DBG_LVL_ALL, "Got NULL Adapter or Packet"); + return -EINVAL; + } + + if(psSF->liDrainCalculated==0) + { + psSF->liDrainCalculated = jiffies; + } + ///send the packet to the fifo.. + PktLen = Packet->len; + Status = SetupNextSend(Adapter, Packet, psSF->usVCID_Value); + if(Status == 0) + { + for(uiIndex = 0 ; uiIndex < MIBS_MAX_HIST_ENTRIES ; uiIndex++) + { if((PktLen <= MIBS_PKTSIZEHIST_RANGE*(uiIndex+1)) && (PktLen > MIBS_PKTSIZEHIST_RANGE*(uiIndex))) + Adapter->aTxPktSizeHist[uiIndex]++; + } + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, SEND_QUEUE, DBG_LVL_ALL, "<====="); + return Status; +} + +/************************************************************************ +* Function - CheckAndSendPacketFromIndex() +* +* Description - This function dequeues the data/control packet from the +* specified queue for transmission. +* +* Parameters - Adapter : Pointer to the driver control structure. +* - iQIndex : The queue Identifier. +* +* Returns - None. +* +****************************************************************************/ +static VOID CheckAndSendPacketFromIndex(PMINI_ADAPTER Adapter, PacketInfo *psSF) +{ + struct sk_buff *QueuePacket=NULL; + char *pControlPacket = NULL; + INT Status=0; + int iPacketLen=0; + + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "%zd ====>", (psSF-Adapter->PackInfo)); + if((psSF != &Adapter->PackInfo[HiPriority]) && Adapter->LinkUpStatus && atomic_read(&psSF->uiPerSFTxResourceCount))//Get data packet + { + if(!psSF->ucDirection ) + return; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "UpdateTokenCount "); + if(Adapter->IdleMode || Adapter->bPreparingForLowPowerMode) + return; /* in idle mode */ + + // Check for Free Descriptors + if(atomic_read(&Adapter->CurrNumFreeTxDesc) <= MINIMUM_PENDING_DESCRIPTORS) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, " No Free Tx Descriptor(%d) is available for Data pkt..",atomic_read(&Adapter->CurrNumFreeTxDesc)); + return ; + } + + spin_lock_bh(&psSF->SFQueueLock); + QueuePacket=psSF->FirstTxQueue; + + if(QueuePacket) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "Dequeuing Data Packet"); + + if(psSF->bEthCSSupport) + iPacketLen = QueuePacket->len; + else + iPacketLen = QueuePacket->len-ETH_HLEN; + + iPacketLen<<=3; + if(iPacketLen <= GetSFTokenCount(Adapter, psSF)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "Allowed bytes %d", + (iPacketLen >> 3)); + + DEQUEUEPACKET(psSF->FirstTxQueue,psSF->LastTxQueue); + psSF->uiCurrentBytesOnHost -= (QueuePacket->len); + psSF->uiCurrentPacketsOnHost--; + atomic_dec(&Adapter->TotalPacketCount); + spin_unlock_bh(&psSF->SFQueueLock); + + Status = SendPacketFromQueue(Adapter, psSF, QueuePacket); + psSF->uiPendedLast = FALSE; + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "For Queue: %zd\n", psSF-Adapter->PackInfo); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "\nAvailable Tokens = %d required = %d\n", + psSF->uiCurrentTokenCount, iPacketLen); + //this part indicates that because of non-availability of the tokens + //pkt has not been send out hence setting the pending flag indicating the host to send it out + //first next iteration . + psSF->uiPendedLast = TRUE; + spin_unlock_bh(&psSF->SFQueueLock); + } + } + else + { + spin_unlock_bh(&psSF->SFQueueLock); + } + } + else + { + + if((atomic_read(&Adapter->CurrNumFreeTxDesc) > 0 ) && + (atomic_read(&Adapter->index_rd_txcntrlpkt) != + atomic_read(&Adapter->index_wr_txcntrlpkt)) + ) + { + pControlPacket = Adapter->txctlpacket + [(atomic_read(&Adapter->index_rd_txcntrlpkt)%MAX_CNTRL_PKTS)]; + if(pControlPacket) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "Sending Control packet"); + Status = SendControlPacket(Adapter, pControlPacket); + if(STATUS_SUCCESS==Status) + { + spin_lock_bh(&psSF->SFQueueLock); + psSF->NumOfPacketsSent++; + psSF->uiSentBytes+=((PLEADER)pControlPacket)->PLength; + psSF->uiSentPackets++; + atomic_dec(&Adapter->TotalPacketCount); + psSF->uiCurrentBytesOnHost -= ((PLEADER)pControlPacket)->PLength; + psSF->uiCurrentPacketsOnHost--; + atomic_inc(&Adapter->index_rd_txcntrlpkt); + spin_unlock_bh(&psSF->SFQueueLock); + } + else + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "SendControlPacket Failed\n"); + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, " Control Pkt is not available, Indexing is wrong...."); + } + } + } +} + + +/******************************************************************* +* Function - transmit_packets() +* +* Description - This function transmits the packets from different +* queues, if free descriptors are available on target. +* +* Parameters - Adapter: Pointer to the Adapter structure. +* +* Returns - None. +********************************************************************/ +VOID transmit_packets(PMINI_ADAPTER Adapter) +{ + UINT uiPrevTotalCount = 0; + int iIndex = 0; + + BOOLEAN exit_flag = TRUE ; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "=====>"); + + if(NULL == Adapter) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX,TX_PACKETS, DBG_LVL_ALL, "Got NULL Adapter"); + return; + } + if(Adapter->device_removed == TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "Device removed"); + return; + } + + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "\nUpdateTokenCount ====>\n"); + + UpdateTokenCount(Adapter); + + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "\nPruneQueueAllSF ====>\n"); + + PruneQueueAllSF(Adapter); + + uiPrevTotalCount = atomic_read(&Adapter->TotalPacketCount); + + for(iIndex=HiPriority;iIndex>=0;iIndex--) + { + if( !uiPrevTotalCount || (TRUE == Adapter->device_removed)) + break; + + if(Adapter->PackInfo[iIndex].bValid && + Adapter->PackInfo[iIndex].uiPendedLast && + Adapter->PackInfo[iIndex].uiCurrentBytesOnHost) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "Calling CheckAndSendPacketFromIndex.."); + CheckAndSendPacketFromIndex(Adapter, &Adapter->PackInfo[iIndex]); + uiPrevTotalCount--; + } + } + + while(uiPrevTotalCount > 0 && !Adapter->device_removed) + { + exit_flag = TRUE ; + //second iteration to parse non-pending queues + for(iIndex=HiPriority;iIndex>=0;iIndex--) + { + if( !uiPrevTotalCount || (TRUE == Adapter->device_removed)) + break; + + if(Adapter->PackInfo[iIndex].bValid && + Adapter->PackInfo[iIndex].uiCurrentBytesOnHost && + !Adapter->PackInfo[iIndex].uiPendedLast ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "Calling CheckAndSendPacketFromIndex.."); + CheckAndSendPacketFromIndex(Adapter, &Adapter->PackInfo[iIndex]); + uiPrevTotalCount--; + exit_flag = FALSE; + } + } + + if(Adapter->IdleMode || Adapter->bPreparingForLowPowerMode) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "In Idle Mode\n"); + break; + } + if(exit_flag == TRUE ) + break ; + }/* end of inner while loop */ + + update_per_cid_rx (Adapter); + Adapter->txtransmit_running = 0; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "<======"); +} diff --git a/drivers/staging/bcm/Macros.h b/drivers/staging/bcm/Macros.h new file mode 100644 index 00000000..46ed99c5 --- /dev/null +++ b/drivers/staging/bcm/Macros.h @@ -0,0 +1,360 @@ +/************************************* +* Macros.h +**************************************/ +#ifndef __MACROS_H__ +#define __MACROS_H__ + +#define TX_TIMER_PERIOD 10 /*10 msec*/ +#define MAX_CLASSIFIERS 100 +/* #define MAX_CLASSIFIERS_PER_SF 20 */ +#define MAX_TARGET_DSX_BUFFERS 24 + +#define MAX_CNTRL_PKTS 100 +#define MAX_DATA_PKTS 200 +#define MAX_ETH_SIZE 1536 +#define MAX_CNTL_PKT_SIZE 2048 + +#define MTU_SIZE 1400 +#define TX_QLEN 5 + +#define MAC_ADDR_REGISTER 0xbf60d000 + + +/* Quality of Service */ +#define NO_OF_QUEUES 17 +#define HiPriority (NO_OF_QUEUES-1) +#define LowPriority 0 +#define BE 2 +#define rtPS 4 +#define ERTPS 5 +#define UGS 6 + +#define BE_BUCKET_SIZE (1024*1024*100) /* 32kb */ +#define rtPS_BUCKET_SIZE (1024*1024*100) /* 8kb */ +#define MAX_ALLOWED_RATE (1024*1024*100) +#define TX_PACKET_THRESHOLD 10 +#define XSECONDS (1*HZ) +#define DSC_ACTIVATE_REQUEST 248 +#define QUEUE_DEPTH_OFFSET 0x1fc01000 +#define MAX_DEVICE_DESC_SIZE 2040 +#define MAX_CTRL_QUEUE_LEN 100 +#define MAX_APP_QUEUE_LEN 200 +#define MAX_LATENCY_ALLOWED 0xFFFFFFFF +#define DEFAULT_UG_INTERVAL 250 +#define DEFAULT_UGI_FACTOR 4 + +#define DEFAULT_PERSFCOUNT 60 +#define MAX_CONNECTIONS 10 +#define MAX_CLASS_NAME_LENGTH 32 + +#define ETH_LENGTH_OF_ADDRESS 6 +#define MAX_MULTICAST_ADDRESSES 32 +#define IP_LENGTH_OF_ADDRESS 4 + +#define IP_PACKET_ONLY_MODE 0 +#define ETH_PACKET_TUNNELING_MODE 1 + +/* Link Request */ +#define SET_MAC_ADDRESS_REQUEST 0 +#define SYNC_UP_REQUEST 1 +#define SYNCED_UP 2 +#define LINK_UP_REQUEST 3 +#define LINK_CONNECTED 4 +#define SYNC_UP_NOTIFICATION 2 +#define LINK_UP_NOTIFICATION 4 + + +#define LINK_NET_ENTRY 0x0002 +#define HMC_STATUS 0x0004 +#define LINK_UP_CONTROL_REQ 0x83 + +#define STATS_POINTER_REQ_STATUS 0x86 +#define NETWORK_ENTRY_REQ_PAYLOAD 198 +#define LINK_DOWN_REQ_PAYLOAD 226 +#define SYNC_UP_REQ_PAYLOAD 228 +#define STATISTICS_POINTER_REQ 237 +#define LINK_UP_REQ_PAYLOAD 245 +#define LINK_UP_ACK 246 + +#define STATS_MSG_SIZE 4 +#define INDEX_TO_DATA 4 + +#define GO_TO_IDLE_MODE_PAYLOAD 210 +#define COME_UP_FROM_IDLE_MODE_PAYLOAD 211 +#define IDLE_MODE_SF_UPDATE_MSG 187 + +#define SKB_RESERVE_ETHERNET_HEADER 16 +#define SKB_RESERVE_PHS_BYTES 32 + +#define IP_PACKET_ONLY_MODE 0 +#define ETH_PACKET_TUNNELING_MODE 1 + +#define ETH_CS_802_3 1 +#define ETH_CS_802_1Q_VLAN 3 +#define IPV4_CS 1 +#define IPV6_CS 2 +#define ETH_CS_MASK 0x3f + +/** \brief Validity bit maps for TLVs in packet classification rule */ + +#define PKT_CLASSIFICATION_USER_PRIORITY_VALID 0 +#define PKT_CLASSIFICATION_VLANID_VALID 1 + +#ifndef MIN +#define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) +#endif + + +/*Leader related terms */ +#define LEADER_STATUS 0x00 +#define LEADER_STATUS_TCP_ACK 0x1 +#define LEADER_SIZE sizeof(LEADER) +#define MAC_ADDR_REQ_SIZE sizeof(PACKETTOSEND) +#define SS_INFO_REQ_SIZE sizeof(PACKETTOSEND) +#define CM_REQUEST_SIZE (LEADER_SIZE + sizeof(stLocalSFChangeRequest)) +#define IDLE_REQ_SIZE sizeof(PACKETTOSEND) + + +#define MAX_TRANSFER_CTRL_BYTE_USB (2*1024) + +#define GET_MAILBOX1_REG_REQUEST 0x87 +#define GET_MAILBOX1_REG_RESPONSE 0x67 +#define VCID_CONTROL_PACKET 0x00 + +#define TRANSMIT_NETWORK_DATA 0x00 +#define RECEIVED_NETWORK_DATA 0x20 + +#define CM_RESPONSES 0xA0 +#define STATUS_RSP 0xA1 +#define LINK_CONTROL_RESP 0xA2 +#define IDLE_MODE_STATUS 0xA3 +#define STATS_POINTER_RESP 0xA6 +#define MGMT_MSG_INFO_SW_STATUS 0xA7 +#define AUTH_SS_HOST_MSG 0xA8 + +#define CM_DSA_ACK_PAYLOAD 247 +#define CM_DSC_ACK_PAYLOAD 248 +#define CM_DSD_ACK_PAYLOAD 249 +#define CM_DSDEACTVATE 250 +#define TOTAL_MASKED_ADDRESS_IN_BYTES 32 + +#define MAC_REQ 0 +#define LINK_RESP 1 +#define RSSI_INDICATION 2 + +#define SS_INFO 4 +#define STATISTICS_INFO 5 +#define CM_INDICATION 6 +#define PARAM_RESP 7 +#define BUFFER_1K 1024 +#define BUFFER_2K (BUFFER_1K*2) +#define BUFFER_4K (BUFFER_2K*2) +#define BUFFER_8K (BUFFER_4K*2) +#define BUFFER_16K (BUFFER_8K*2) +#define DOWNLINK_DIR 0 +#define UPLINK_DIR 1 + +#define BCM_SIGNATURE "BECEEM" + + +#define GPIO_OUTPUT_REGISTER 0x0F00003C +#define BCM_GPIO_OUTPUT_SET_REG 0x0F000040 +#define BCM_GPIO_OUTPUT_CLR_REG 0x0F000044 +#define GPIO_MODE_REGISTER 0x0F000034 +#define GPIO_PIN_STATE_REGISTER 0x0F000038 + + +typedef struct _LINK_STATE { + UCHAR ucLinkStatus; + UCHAR bIdleMode; + UCHAR bShutdownMode; +} LINK_STATE, *PLINK_STATE; + + +enum enLinkStatus { + WAIT_FOR_SYNC = 1, + PHY_SYNC_ACHIVED = 2, + LINKUP_IN_PROGRESS = 3, + LINKUP_DONE = 4, + DREG_RECEIVED = 5, + LINK_STATUS_RESET_RECEIVED = 6, + PERIODIC_WAKE_UP_NOTIFICATION_FRM_FW = 7, + LINK_SHUTDOWN_REQ_FROM_FIRMWARE = 8, + COMPLETE_WAKE_UP_NOTIFICATION_FRM_FW = 9 +}; + +typedef enum _E_PHS_DSC_ACTION { + eAddPHSRule = 0, + eSetPHSRule, + eDeletePHSRule, + eDeleteAllPHSRules +} E_PHS_DSC_ACTION; + + +#define CM_CONTROL_NEWDSX_MULTICLASSIFIER_REQ 0x89 /* Host to Mac */ +#define CM_CONTROL_NEWDSX_MULTICLASSIFIER_RESP 0xA9 /* Mac to Host */ +#define MASK_DISABLE_HEADER_SUPPRESSION 0x10 /* 0b000010000 */ +#define MINIMUM_PENDING_DESCRIPTORS 5 + +#define SHUTDOWN_HOSTINITIATED_REQUESTPAYLOAD 0xCC +#define SHUTDOWN_ACK_FROM_DRIVER 0x1 +#define SHUTDOWN_NACK_FROM_DRIVER 0x2 + +#define LINK_SYNC_UP_SUBTYPE 0x0001 +#define LINK_SYNC_DOWN_SUBTYPE 0x0001 + + + +#define CONT_MODE 1 +#define SINGLE_DESCRIPTOR 1 + + +#define DESCRIPTOR_LENGTH 0x30 +#define FIRMWARE_DESCS_ADDRESS 0x1F100000 + + +#define CLOCK_RESET_CNTRL_REG_1 0x0F00000C +#define CLOCK_RESET_CNTRL_REG_2 0x0F000840 + + + +#define TX_DESCRIPTOR_HEAD_REGISTER 0x0F010034 +#define RX_DESCRIPTOR_HEAD_REGISTER 0x0F010094 + +#define STATISTICS_BEGIN_ADDR 0xbf60f02c + +#define MAX_PENDING_CTRL_PACKET (MAX_CTRL_QUEUE_LEN-10) + +#define WIMAX_MAX_MTU (MTU_SIZE + ETH_HLEN) +#define AUTO_LINKUP_ENABLE 0x2 +#define AUTO_SYNC_DISABLE 0x1 +#define AUTO_FIRM_DOWNLOAD 0x1 +#define SETTLE_DOWN_TIME 50 + +#define HOST_BUS_SUSPEND_BIT 16 + +#define IDLE_MESSAGE 0x81 + +#define MIPS_CLOCK_133MHz 1 + +#define TARGET_CAN_GO_TO_IDLE_MODE 2 +#define TARGET_CAN_NOT_GO_TO_IDLE_MODE 3 +#define IDLE_MODE_PAYLOAD_LENGTH 8 + +#define IP_HEADER(Buffer) ((IPHeaderFormat *)(Buffer)) +#define IPV4 4 +#define IP_VERSION(byte) (((byte&0xF0)>>4)) + +#define SET_MAC_ADDRESS 193 +#define SET_MAC_ADDRESS_RESPONSE 236 + +#define IDLE_MODE_WAKEUP_PATTERN 0xd0ea1d1e +#define IDLE_MODE_WAKEUP_NOTIFIER_ADDRESS 0x1FC02FA8 +#define IDLE_MODE_MAX_RETRY_COUNT 1000 + +#ifdef REL_4_1 +#define CONFIG_BEGIN_ADDR 0xBF60B004 +#else +#define CONFIG_BEGIN_ADDR 0xBF60B000 +#endif + +#define FIRMWARE_BEGIN_ADDR 0xBFC00000 + +#define INVALID_QUEUE_INDEX NO_OF_QUEUES + +#define INVALID_PID ((pid_t)-1) +#define DDR_80_MHZ 0 +#define DDR_100_MHZ 1 +#define DDR_120_MHZ 2 /* Additional Frequency for T3LP */ +#define DDR_133_MHZ 3 +#define DDR_140_MHZ 4 /* Not Used (Reserved for future) */ +#define DDR_160_MHZ 5 /* Additional Frequency for T3LP */ +#define DDR_180_MHZ 6 /* Not Used (Reserved for future) */ +#define DDR_200_MHZ 7 /* Not Used (Reserved for future) */ + +#define MIPS_200_MHZ 0 +#define MIPS_160_MHZ 1 + +#define PLL_800_MHZ 0 +#define PLL_266_MHZ 1 + +#define DEVICE_POWERSAVE_MODE_AS_MANUAL_CLOCK_GATING 0 +#define DEVICE_POWERSAVE_MODE_AS_PMU_CLOCK_GATING 1 +#define DEVICE_POWERSAVE_MODE_AS_PMU_SHUTDOWN 2 +#define DEVICE_POWERSAVE_MODE_AS_RESERVED 3 +#define DEVICE_POWERSAVE_MODE_AS_PROTOCOL_IDLE_MODE 4 + + +#define EEPROM_REJECT_REG_1 0x0f003018 +#define EEPROM_REJECT_REG_2 0x0f00301c +#define EEPROM_REJECT_REG_3 0x0f003008 +#define EEPROM_REJECT_REG_4 0x0f003020 +#define EEPROM_REJECT_MASK 0x0fffffff +#define VSG_MODE 0x3 + +/* Idle Mode Related Registers */ +#define DEBUG_INTERRUPT_GENERATOR_REGISTOR 0x0F00007C +#define SW_ABORT_IDLEMODE_LOC 0x0FF01FFC + +#define SW_ABORT_IDLEMODE_PATTERN 0xd0ea1d1e +#define DEVICE_INT_OUT_EP_REG0 0x0F011870 +#define DEVICE_INT_OUT_EP_REG1 0x0F011874 + +#define BIN_FILE "/lib/firmware/macxvi200.bin" +#define CFG_FILE "/lib/firmware/macxvi.cfg" +#define SF_MAX_ALLOWED_PACKETS_TO_BACKUP 128 +#define MIN_VAL(x, y) ((x) < (y) ? (x) : (y)) +#define MAC_ADDRESS_SIZE 6 +#define EEPROM_COMMAND_Q_REG 0x0F003018 +#define EEPROM_READ_DATA_Q_REG 0x0F003020 +#define CHIP_ID_REG 0x0F000000 +#define GPIO_MODE_REG 0x0F000034 +#define GPIO_OUTPUT_REG 0x0F00003C +#define WIMAX_MAX_ALLOWED_RATE (1024*1024*50) + +#define T3 0xbece0300 +#define TARGET_SFID_TXDESC_MAP_LOC 0xBFFFF400 + +#define RWM_READ 0 +#define RWM_WRITE 1 + +#define T3LPB 0xbece3300 +#define BCS220_2 0xbece3311 +#define BCS220_2BC 0xBECE3310 +#define BCS250_BC 0xbece3301 +#define BCS220_3 0xbece3321 + + +#define HPM_CONFIG_LDO145 0x0F000D54 +#define HPM_CONFIG_MSW 0x0F000D58 + +#define T3B 0xbece0310 +typedef enum eNVM_TYPE { + NVM_AUTODETECT = 0, + NVM_EEPROM, + NVM_FLASH, + NVM_UNKNOWN +} NVM_TYPE; + +typedef enum ePMU_MODES { + HYBRID_MODE_7C = 0, + INTERNAL_MODE_6 = 1, + HYBRID_MODE_6 = 2 +} PMU_MODE; + +#define MAX_RDM_WRM_RETIRES 1 + +enum eAbortPattern { + ABORT_SHUTDOWN_MODE = 1, + ABORT_IDLE_REG = 1, + ABORT_IDLE_MODE = 2, + ABORT_IDLE_SYNCDOWN = 3 +}; + + +/* Offsets used by driver in skb cb variable */ +#define SKB_CB_CLASSIFICATION_OFFSET 0 +#define SKB_CB_LATENCY_OFFSET 1 +#define SKB_CB_TCPACK_OFFSET 2 + +#endif /* __MACROS_H__ */ diff --git a/drivers/staging/bcm/Makefile b/drivers/staging/bcm/Makefile new file mode 100644 index 00000000..652b7f87 --- /dev/null +++ b/drivers/staging/bcm/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for Beceem USB Wimax card +# + +obj-$(CONFIG_BCM_WIMAX) += bcm_wimax.o + +bcm_wimax-y := InterfaceDld.o InterfaceIdleMode.o InterfaceInit.o InterfaceRx.o \ + InterfaceIsr.o InterfaceMisc.o InterfaceTx.o \ + CmHost.o IPv6Protocol.o Qos.o Transmit.o\ + Bcmnet.o DDRInit.o HandleControlPacket.o\ + LeakyBucket.o Misc.o sort.o Bcmchar.o hostmibs.o PHSModule.o\ + led_control.o nvm.o vendorspecificextn.o diff --git a/drivers/staging/bcm/Misc.c b/drivers/staging/bcm/Misc.c new file mode 100644 index 00000000..c7725e14 --- /dev/null +++ b/drivers/staging/bcm/Misc.c @@ -0,0 +1,1662 @@ +#include "headers.h" + +static int BcmFileDownload(PMINI_ADAPTER Adapter, const char *path, unsigned int loc); +static VOID doPowerAutoCorrection(PMINI_ADAPTER psAdapter); +static void HandleShutDownModeRequest(PMINI_ADAPTER Adapter, PUCHAR pucBuffer); +static int bcm_parse_target_params(PMINI_ADAPTER Adapter); +static void beceem_protocol_reset(PMINI_ADAPTER Adapter); + +static VOID default_wimax_protocol_initialize(PMINI_ADAPTER Adapter) +{ + UINT uiLoopIndex; + + for (uiLoopIndex = 0; uiLoopIndex < NO_OF_QUEUES-1; uiLoopIndex++) { + Adapter->PackInfo[uiLoopIndex].uiThreshold = TX_PACKET_THRESHOLD; + Adapter->PackInfo[uiLoopIndex].uiMaxAllowedRate = MAX_ALLOWED_RATE; + Adapter->PackInfo[uiLoopIndex].uiMaxBucketSize = 20*1024*1024; + } + + Adapter->BEBucketSize = BE_BUCKET_SIZE; + Adapter->rtPSBucketSize = rtPS_BUCKET_SIZE; + Adapter->LinkStatus = SYNC_UP_REQUEST; + Adapter->TransferMode = IP_PACKET_ONLY_MODE; + Adapter->usBestEffortQueueIndex = -1; + return; +} + +INT InitAdapter(PMINI_ADAPTER psAdapter) +{ + int i = 0; + INT Status = STATUS_SUCCESS; + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Initialising Adapter = %p", psAdapter); + + if (psAdapter == NULL) { + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Adapter is NULL"); + return -EINVAL; + } + + sema_init(&psAdapter->NVMRdmWrmLock, 1); + sema_init(&psAdapter->rdmwrmsync, 1); + spin_lock_init(&psAdapter->control_queue_lock); + spin_lock_init(&psAdapter->txtransmitlock); + sema_init(&psAdapter->RxAppControlQueuelock, 1); + sema_init(&psAdapter->fw_download_sema, 1); + sema_init(&psAdapter->LowPowerModeSync, 1); + + for (i = 0; i < NO_OF_QUEUES; i++) + spin_lock_init(&psAdapter->PackInfo[i].SFQueueLock); + i = 0; + + init_waitqueue_head(&psAdapter->process_rx_cntrlpkt); + init_waitqueue_head(&psAdapter->tx_packet_wait_queue); + init_waitqueue_head(&psAdapter->process_read_wait_queue); + init_waitqueue_head(&psAdapter->ioctl_fw_dnld_wait_queue); + init_waitqueue_head(&psAdapter->lowpower_mode_wait_queue); + psAdapter->waiting_to_fw_download_done = TRUE; + psAdapter->fw_download_done = FALSE; + + default_wimax_protocol_initialize(psAdapter); + for (i = 0; i < MAX_CNTRL_PKTS; i++) { + psAdapter->txctlpacket[i] = kmalloc(MAX_CNTL_PKT_SIZE, GFP_KERNEL); + if (!psAdapter->txctlpacket[i]) { + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "No More Cntl pkts got, max got is %d", i); + return -ENOMEM; + } + } + + if (AllocAdapterDsxBuffer(psAdapter)) { + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Failed to allocate DSX buffers"); + return -EINVAL; + } + + /* Initialize PHS interface */ + if (phs_init(&psAdapter->stBCMPhsContext, psAdapter) != 0) { + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "%s:%s:%d:Error PHS Init Failed=====>\n", __FILE__, __func__, __LINE__); + return -ENOMEM; + } + + Status = BcmAllocFlashCSStructure(psAdapter); + if (Status) { + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Memory Allocation for Flash structure failed"); + return Status; + } + + Status = vendorextnInit(psAdapter); + + if (STATUS_SUCCESS != Status) { + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Vendor Init Failed"); + return Status; + } + + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Adapter initialised"); + + return STATUS_SUCCESS; +} + +VOID AdapterFree(PMINI_ADAPTER Adapter) +{ + int count; + beceem_protocol_reset(Adapter); + vendorextnExit(Adapter); + + if (Adapter->control_packet_handler && !IS_ERR(Adapter->control_packet_handler)) + kthread_stop(Adapter->control_packet_handler); + + if (Adapter->transmit_packet_thread && !IS_ERR(Adapter->transmit_packet_thread)) + kthread_stop(Adapter->transmit_packet_thread); + + wake_up(&Adapter->process_read_wait_queue); + + if (Adapter->LEDInfo.led_thread_running & (BCM_LED_THREAD_RUNNING_ACTIVELY | BCM_LED_THREAD_RUNNING_INACTIVELY)) + kthread_stop(Adapter->LEDInfo.led_cntrl_threadid); + + unregister_networkdev(Adapter); + + /* FIXME: use proper wait_event and refcounting */ + while (atomic_read(&Adapter->ApplicationRunning)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Waiting for Application to close.. %d\n", atomic_read(&Adapter->ApplicationRunning)); + msleep(100); + } + unregister_control_device_interface(Adapter); + kfree(Adapter->pstargetparams); + + for (count = 0; count < MAX_CNTRL_PKTS; count++) + kfree(Adapter->txctlpacket[count]); + + FreeAdapterDsxBuffer(Adapter); + kfree(Adapter->pvInterfaceAdapter); + + /* Free the PHS Interface */ + PhsCleanup(&Adapter->stBCMPhsContext); + + BcmDeAllocFlashCSStructure(Adapter); + + free_netdev(Adapter->dev); +} + +static int create_worker_threads(PMINI_ADAPTER psAdapter) +{ + /* Rx Control Packets Processing */ + psAdapter->control_packet_handler = kthread_run((int (*)(void *)) + control_packet_handler, psAdapter, "%s-rx", DRV_NAME); + if (IS_ERR(psAdapter->control_packet_handler)) { + pr_notice(DRV_NAME ": could not create control thread\n"); + return PTR_ERR(psAdapter->control_packet_handler); + } + + /* Tx Thread */ + psAdapter->transmit_packet_thread = kthread_run((int (*)(void *)) + tx_pkt_handler, psAdapter, "%s-tx", DRV_NAME); + if (IS_ERR(psAdapter->transmit_packet_thread)) { + pr_notice(DRV_NAME ": could not creat transmit thread\n"); + kthread_stop(psAdapter->control_packet_handler); + return PTR_ERR(psAdapter->transmit_packet_thread); + } + return 0; +} + +static struct file *open_firmware_file(PMINI_ADAPTER Adapter, const char *path) +{ + struct file *flp = NULL; + mm_segment_t oldfs; + oldfs = get_fs(); + set_fs(get_ds()); + flp = filp_open(path, O_RDONLY, S_IRWXU); + set_fs(oldfs); + if (IS_ERR(flp)) { + pr_err(DRV_NAME "Unable To Open File %s, err %ld", path, PTR_ERR(flp)); + flp = NULL; + } + + if (Adapter->device_removed) + flp = NULL; + + return flp; +} + +/* Arguments: + * Logical Adapter + * Path to image file + * Download Address on the chip + */ +static int BcmFileDownload(PMINI_ADAPTER Adapter, const char *path, unsigned int loc) +{ + int errorno = 0; + struct file *flp = NULL; + mm_segment_t oldfs; + struct timeval tv = {0}; + + flp = open_firmware_file(Adapter, path); + if (!flp) { + errorno = -ENOENT; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Unable to Open %s\n", path); + goto exit_download; + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Opened file is = %s and length =0x%lx to be downloaded at =0x%x", path, (unsigned long)flp->f_dentry->d_inode->i_size, loc); + do_gettimeofday(&tv); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "download start %lx", ((tv.tv_sec * 1000) + (tv.tv_usec / 1000))); + if (Adapter->bcm_file_download(Adapter->pvInterfaceAdapter, flp, loc)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Failed to download the firmware with error %x!!!", -EIO); + errorno = -EIO; + goto exit_download; + } + oldfs = get_fs(); + set_fs(get_ds()); + vfs_llseek(flp, 0, 0); + set_fs(oldfs); + if (Adapter->bcm_file_readback_from_chip(Adapter->pvInterfaceAdapter, flp, loc)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Failed to read back firmware!"); + errorno = -EIO; + goto exit_download; + } + +exit_download: + oldfs = get_fs(); + set_fs(get_ds()); + if (flp && !(IS_ERR(flp))) + filp_close(flp, current->files); + set_fs(oldfs); + + return errorno; +} + +/** + * @ingroup ctrl_pkt_functions + * This function copies the contents of given buffer + * to the control packet and queues it for transmission. + * @note Do not acquire the spinock, as it it already acquired. + * @return SUCCESS/FAILURE. + * Arguments: + * Logical Adapter + * Control Packet Buffer + */ +INT CopyBufferToControlPacket(PMINI_ADAPTER Adapter, PVOID ioBuffer) +{ + PLEADER pLeader = NULL; + INT Status = 0; + unsigned char *ctrl_buff = NULL; + UINT pktlen = 0; + PLINK_REQUEST pLinkReq = NULL; + PUCHAR pucAddIndication = NULL; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "======>"); + if (!ioBuffer) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Got Null Buffer\n"); + return -EINVAL; + } + + pLinkReq = (PLINK_REQUEST)ioBuffer; + pLeader = (PLEADER)ioBuffer; /* ioBuffer Contains sw_Status and Payload */ + + if (Adapter->bShutStatus == TRUE && + pLinkReq->szData[0] == LINK_DOWN_REQ_PAYLOAD && + pLinkReq->szData[1] == LINK_SYNC_UP_SUBTYPE) { + + /* Got sync down in SHUTDOWN..we could not process this. */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "SYNC DOWN Request in Shut Down Mode..\n"); + return STATUS_FAILURE; + } + + if ((pLeader->Status == LINK_UP_CONTROL_REQ) && + ((pLinkReq->szData[0] == LINK_UP_REQ_PAYLOAD && + (pLinkReq->szData[1] == LINK_SYNC_UP_SUBTYPE)) || /* Sync Up Command */ + pLinkReq->szData[0] == NETWORK_ENTRY_REQ_PAYLOAD)) /* Net Entry Command */ { + + if (Adapter->LinkStatus > PHY_SYNC_ACHIVED) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "LinkStatus is Greater than PHY_SYN_ACHIEVED"); + return STATUS_FAILURE; + } + + if (TRUE == Adapter->bShutStatus) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "SYNC UP IN SHUTDOWN..Device WakeUp\n"); + if (Adapter->bTriedToWakeUpFromlowPowerMode == FALSE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Waking up for the First Time..\n"); + Adapter->usIdleModePattern = ABORT_SHUTDOWN_MODE; /* change it to 1 for current support. */ + Adapter->bWakeUpDevice = TRUE; + wake_up(&Adapter->process_rx_cntrlpkt); + Status = wait_event_interruptible_timeout(Adapter->lowpower_mode_wait_queue, !Adapter->bShutStatus, (5 * HZ)); + + if (Status == -ERESTARTSYS) + return Status; + + if (Adapter->bShutStatus) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Shutdown Mode Wake up Failed - No Wake Up Received\n"); + return STATUS_FAILURE; + } + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Wakeup has been tried already...\n"); + } + } + } + + if (TRUE == Adapter->IdleMode) { + /* BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Device is in Idle mode ... hence\n"); */ + if (pLeader->Status == LINK_UP_CONTROL_REQ || pLeader->Status == 0x80 || + pLeader->Status == CM_CONTROL_NEWDSX_MULTICLASSIFIER_REQ) { + + if ((pLeader->Status == LINK_UP_CONTROL_REQ) && (pLinkReq->szData[0] == LINK_DOWN_REQ_PAYLOAD)) { + if ((pLinkReq->szData[1] == LINK_SYNC_DOWN_SUBTYPE)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Link Down Sent in Idle Mode\n"); + Adapter->usIdleModePattern = ABORT_IDLE_SYNCDOWN; /* LINK DOWN sent in Idle Mode */ + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "ABORT_IDLE_MODE pattern is being written\n"); + Adapter->usIdleModePattern = ABORT_IDLE_REG; + } + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "ABORT_IDLE_MODE pattern is being written\n"); + Adapter->usIdleModePattern = ABORT_IDLE_MODE; + } + + /*Setting bIdleMode_tx_from_host to TRUE to indicate LED control thread to represent + * the wake up from idlemode is from host + */ + /* Adapter->LEDInfo.bIdleMode_tx_from_host = TRUE; */ + Adapter->bWakeUpDevice = TRUE; + wake_up(&Adapter->process_rx_cntrlpkt); + + /* We should not send DREG message down while in idlemode. */ + if (LINK_DOWN_REQ_PAYLOAD == pLinkReq->szData[0]) + return STATUS_SUCCESS; + + Status = wait_event_interruptible_timeout(Adapter->lowpower_mode_wait_queue, !Adapter->IdleMode, (5 * HZ)); + + if (Status == -ERESTARTSYS) + return Status; + + if (Adapter->IdleMode) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Idle Mode Wake up Failed - No Wake Up Received\n"); + return STATUS_FAILURE; + } + } else { + return STATUS_SUCCESS; + } + } + + /* The Driver has to send control messages with a particular VCID */ + pLeader->Vcid = VCID_CONTROL_PACKET; /* VCID for control packet. */ + + /* Allocate skb for Control Packet */ + pktlen = pLeader->PLength; + ctrl_buff = (char *)Adapter->txctlpacket[atomic_read(&Adapter->index_wr_txcntrlpkt)%MAX_CNTRL_PKTS]; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Control packet to be taken =%d and address is =%pincoming address is =%p and packet len=%x", + atomic_read(&Adapter->index_wr_txcntrlpkt), ctrl_buff, ioBuffer, pktlen); + if (ctrl_buff) { + if (pLeader) { + if ((pLeader->Status == 0x80) || + (pLeader->Status == CM_CONTROL_NEWDSX_MULTICLASSIFIER_REQ)) { + /* + * Restructure the DSX message to handle Multiple classifier Support + * Write the Service Flow param Structures directly to the target + * and embed the pointers in the DSX messages sent to target. + */ + /* Lets store the current length of the control packet we are transmitting */ + pucAddIndication = (PUCHAR)ioBuffer + LEADER_SIZE; + pktlen = pLeader->PLength; + Status = StoreCmControlResponseMessage(Adapter, pucAddIndication, &pktlen); + if (Status != 1) { + ClearTargetDSXBuffer(Adapter, ((stLocalSFAddIndicationAlt *)pucAddIndication)->u16TID, FALSE); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, " Error Restoring The DSX Control Packet. Dsx Buffers on Target may not be Setup Properly "); + return STATUS_FAILURE; + } + /* + * update the leader to use the new length + * The length of the control packet is length of message being sent + Leader length + */ + pLeader->PLength = pktlen; + } + } + + if (pktlen + LEADER_SIZE > MAX_CNTL_PKT_SIZE) + return -EINVAL; + + memset(ctrl_buff, 0, pktlen+LEADER_SIZE); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Copying the Control Packet Buffer with length=%d\n", pLeader->PLength); + *(PLEADER)ctrl_buff = *pLeader; + memcpy(ctrl_buff + LEADER_SIZE, ((PUCHAR)ioBuffer + LEADER_SIZE), pLeader->PLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Enqueuing the Control Packet"); + + /* Update the statistics counters */ + spin_lock_bh(&Adapter->PackInfo[HiPriority].SFQueueLock); + Adapter->PackInfo[HiPriority].uiCurrentBytesOnHost += pLeader->PLength; + Adapter->PackInfo[HiPriority].uiCurrentPacketsOnHost++; + atomic_inc(&Adapter->TotalPacketCount); + spin_unlock_bh(&Adapter->PackInfo[HiPriority].SFQueueLock); + Adapter->PackInfo[HiPriority].bValid = TRUE; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "CurrBytesOnHost: %x bValid: %x", + Adapter->PackInfo[HiPriority].uiCurrentBytesOnHost, + Adapter->PackInfo[HiPriority].bValid); + Status = STATUS_SUCCESS; + /*Queue the packet for transmission */ + atomic_inc(&Adapter->index_wr_txcntrlpkt); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Calling transmit_packets"); + atomic_set(&Adapter->TxPktAvail, 1); + wake_up(&Adapter->tx_packet_wait_queue); + } else { + Status = -ENOMEM; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "mem allocation Failed"); + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "<===="); + return Status; +} + +#if 0 +/***************************************************************** +* Function - SendStatisticsPointerRequest() +* +* Description - This function builds and forwards the Statistics +* Pointer Request control Packet. +* +* Parameters - Adapter : Pointer to Adapter structure. +* - pstStatisticsPtrRequest : Pointer to link request. +* +* Returns - None. +*****************************************************************/ +static VOID SendStatisticsPointerRequest(PMINI_ADAPTER Adapter, PLINK_REQUEST pstStatisticsPtrRequest) +{ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "======>"); + pstStatisticsPtrRequest->Leader.Status = STATS_POINTER_REQ_STATUS; + pstStatisticsPtrRequest->Leader.PLength = sizeof(ULONG); /* minimum 4 bytes */ + pstStatisticsPtrRequest->szData[0] = STATISTICS_POINTER_REQ; + CopyBufferToControlPacket(Adapter, pstStatisticsPtrRequest); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "<====="); + return; +} +#endif + +/****************************************************************** +* Function - LinkMessage() +* +* Description - This function builds the Sync-up and Link-up request +* packet messages depending on the device Link status. +* +* Parameters - Adapter: Pointer to the Adapter structure. +* +* Returns - None. +*******************************************************************/ +VOID LinkMessage(PMINI_ADAPTER Adapter) +{ + PLINK_REQUEST pstLinkRequest = NULL; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LINK_UP_MSG, DBG_LVL_ALL, "=====>"); + if (Adapter->LinkStatus == SYNC_UP_REQUEST && Adapter->AutoSyncup) { + pstLinkRequest = kzalloc(sizeof(LINK_REQUEST), GFP_ATOMIC); + if (!pstLinkRequest) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LINK_UP_MSG, DBG_LVL_ALL, "Can not allocate memory for Link request!"); + return; + } + /* sync up request... */ + Adapter->LinkStatus = WAIT_FOR_SYNC; /* current link status */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LINK_UP_MSG, DBG_LVL_ALL, "Requesting For SyncUp..."); + pstLinkRequest->szData[0] = LINK_UP_REQ_PAYLOAD; + pstLinkRequest->szData[1] = LINK_SYNC_UP_SUBTYPE; + pstLinkRequest->Leader.Status = LINK_UP_CONTROL_REQ; + pstLinkRequest->Leader.PLength = sizeof(ULONG); + Adapter->bSyncUpRequestSent = TRUE; + + } else if (Adapter->LinkStatus == PHY_SYNC_ACHIVED && Adapter->AutoLinkUp) { + pstLinkRequest = kzalloc(sizeof(LINK_REQUEST), GFP_ATOMIC); + if (!pstLinkRequest) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LINK_UP_MSG, DBG_LVL_ALL, "Can not allocate memory for Link request!"); + return; + } + /* LINK_UP_REQUEST */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LINK_UP_MSG, DBG_LVL_ALL, "Requesting For LinkUp..."); + pstLinkRequest->szData[0] = LINK_UP_REQ_PAYLOAD; + pstLinkRequest->szData[1] = LINK_NET_ENTRY; + pstLinkRequest->Leader.Status = LINK_UP_CONTROL_REQ; + pstLinkRequest->Leader.PLength = sizeof(ULONG); + } + if (pstLinkRequest) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LINK_UP_MSG, DBG_LVL_ALL, "Calling CopyBufferToControlPacket"); + CopyBufferToControlPacket(Adapter, pstLinkRequest); + kfree(pstLinkRequest); + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LINK_UP_MSG, DBG_LVL_ALL, "LinkMessage <====="); + return; +} + +/********************************************************************** +* Function - StatisticsResponse() +* +* Description - This function handles the Statistics response packet. +* +* Parameters - Adapter : Pointer to the Adapter structure. +* - pvBuffer: Starting address of Statistic response data. +* +* Returns - None. +************************************************************************/ +VOID StatisticsResponse(PMINI_ADAPTER Adapter, PVOID pvBuffer) +{ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "%s====>", __func__); + Adapter->StatisticsPointer = ntohl(*(__be32 *)pvBuffer); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "Stats at %x", (UINT)Adapter->StatisticsPointer); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "%s <====", __func__); + return; +} + +/********************************************************************** +* Function - LinkControlResponseMessage() +* +* Description - This function handles the Link response packets. +* +* Parameters - Adapter : Pointer to the Adapter structure. +* - pucBuffer: Starting address of Link response data. +* +* Returns - None. +***********************************************************************/ +VOID LinkControlResponseMessage(PMINI_ADAPTER Adapter, PUCHAR pucBuffer) +{ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "=====>"); + + if (*pucBuffer == LINK_UP_ACK) { + switch (*(pucBuffer+1)) { + case PHY_SYNC_ACHIVED: /* SYNCed UP */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "PHY_SYNC_ACHIVED"); + + if (Adapter->LinkStatus == LINKUP_DONE) + beceem_protocol_reset(Adapter); + + Adapter->usBestEffortQueueIndex = INVALID_QUEUE_INDEX; + Adapter->LinkStatus = PHY_SYNC_ACHIVED; + + if (Adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + Adapter->DriverState = NO_NETWORK_ENTRY; + wake_up(&Adapter->LEDInfo.notify_led_event); + } + + LinkMessage(Adapter); + break; + + case LINKUP_DONE: + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "LINKUP_DONE"); + Adapter->LinkStatus = LINKUP_DONE; + Adapter->bPHSEnabled = *(pucBuffer+3); + Adapter->bETHCSEnabled = *(pucBuffer+4) & ETH_CS_MASK; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "PHS Support Status Received In LinkUp Ack : %x\n", Adapter->bPHSEnabled); + + if ((FALSE == Adapter->bShutStatus) && (FALSE == Adapter->IdleMode)) { + if (Adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + Adapter->DriverState = NORMAL_OPERATION; + wake_up(&Adapter->LEDInfo.notify_led_event); + } + } + LinkMessage(Adapter); + break; + + case WAIT_FOR_SYNC: + /* + * Driver to ignore the DREG_RECEIVED + * WiMAX Application should handle this Message + */ + /* Adapter->liTimeSinceLastNetEntry = 0; */ + Adapter->LinkUpStatus = 0; + Adapter->LinkStatus = 0; + Adapter->usBestEffortQueueIndex = INVALID_QUEUE_INDEX; + Adapter->bTriedToWakeUpFromlowPowerMode = FALSE; + Adapter->IdleMode = FALSE; + beceem_protocol_reset(Adapter); + + break; + case LINK_SHUTDOWN_REQ_FROM_FIRMWARE: + case COMPLETE_WAKE_UP_NOTIFICATION_FRM_FW: + { + HandleShutDownModeRequest(Adapter, pucBuffer); + } + break; + default: + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "default case:LinkResponse %x", *(pucBuffer + 1)); + break; + } + } else if (SET_MAC_ADDRESS_RESPONSE == *pucBuffer) { + PUCHAR puMacAddr = (pucBuffer + 1); + Adapter->LinkStatus = SYNC_UP_REQUEST; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "MAC address response, sending SYNC_UP"); + LinkMessage(Adapter); + memcpy(Adapter->dev->dev_addr, puMacAddr, MAC_ADDRESS_SIZE); + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "%s <=====", __func__); + return; +} + +void SendIdleModeResponse(PMINI_ADAPTER Adapter) +{ + INT status = 0, NVMAccess = 0, lowPwrAbortMsg = 0; + struct timeval tv; + CONTROL_MESSAGE stIdleResponse = {{0} }; + memset(&tv, 0, sizeof(tv)); + stIdleResponse.Leader.Status = IDLE_MESSAGE; + stIdleResponse.Leader.PLength = IDLE_MODE_PAYLOAD_LENGTH; + stIdleResponse.szData[0] = GO_TO_IDLE_MODE_PAYLOAD; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, " ============>"); + + /********************************* + *down_trylock - + * if [ semaphore is available ] + * acquire semaphone and return value 0 ; + * else + * return non-zero value ; + * + ***********************************/ + + NVMAccess = down_trylock(&Adapter->NVMRdmWrmLock); + lowPwrAbortMsg = down_trylock(&Adapter->LowPowerModeSync); + + + if ((NVMAccess || lowPwrAbortMsg || atomic_read(&Adapter->TotalPacketCount)) && + (Adapter->ulPowerSaveMode != DEVICE_POWERSAVE_MODE_AS_PROTOCOL_IDLE_MODE)) { + + if (!NVMAccess) + up(&Adapter->NVMRdmWrmLock); + + if (!lowPwrAbortMsg) + up(&Adapter->LowPowerModeSync); + + stIdleResponse.szData[1] = TARGET_CAN_NOT_GO_TO_IDLE_MODE; /* NACK- device access is going on. */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "HOST IS NACKING Idle mode To F/W!!!!!!!!"); + Adapter->bPreparingForLowPowerMode = FALSE; + } else { + stIdleResponse.szData[1] = TARGET_CAN_GO_TO_IDLE_MODE; /* 2; Idle ACK */ + Adapter->StatisticsPointer = 0; + + /* Wait for the LED to TURN OFF before sending ACK response */ + if (Adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + INT iRetVal = 0; + + /* Wake the LED Thread with IDLEMODE_ENTER State */ + Adapter->DriverState = LOWPOWER_MODE_ENTER; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "LED Thread is Running..Hence Setting LED Event as IDLEMODE_ENTER jiffies:%ld", jiffies); + wake_up(&Adapter->LEDInfo.notify_led_event); + + /* Wait for 1 SEC for LED to OFF */ + iRetVal = wait_event_timeout(Adapter->LEDInfo.idleModeSyncEvent, Adapter->LEDInfo.bIdle_led_off, msecs_to_jiffies(1000)); + + /* If Timed Out to Sync IDLE MODE Enter, do IDLE mode Exit and Send NACK to device */ + if (iRetVal <= 0) { + stIdleResponse.szData[1] = TARGET_CAN_NOT_GO_TO_IDLE_MODE; /* NACK- device access is going on. */ + Adapter->DriverState = NORMAL_OPERATION; + wake_up(&Adapter->LEDInfo.notify_led_event); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "NACKING Idle mode as time out happen from LED side!!!!!!!!"); + } + } + + if (stIdleResponse.szData[1] == TARGET_CAN_GO_TO_IDLE_MODE) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "ACKING IDLE MODE !!!!!!!!!"); + down(&Adapter->rdmwrmsync); + Adapter->bPreparingForLowPowerMode = TRUE; + up(&Adapter->rdmwrmsync); + /* Killing all URBS. */ + if (Adapter->bDoSuspend == TRUE) + Bcm_kill_all_URBs((PS_INTERFACE_ADAPTER)(Adapter->pvInterfaceAdapter)); + } else { + Adapter->bPreparingForLowPowerMode = FALSE; + } + + if (!NVMAccess) + up(&Adapter->NVMRdmWrmLock); + + if (!lowPwrAbortMsg) + up(&Adapter->LowPowerModeSync); + } + + status = CopyBufferToControlPacket(Adapter, &stIdleResponse); + if ((status != STATUS_SUCCESS)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "fail to send the Idle mode Request\n"); + Adapter->bPreparingForLowPowerMode = FALSE; + StartInterruptUrb((PS_INTERFACE_ADAPTER)(Adapter->pvInterfaceAdapter)); + } + do_gettimeofday(&tv); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_RX, RX_DPC, DBG_LVL_ALL, "IdleMode Msg submitter to Q :%ld ms", tv.tv_sec * 1000 + tv.tv_usec / 1000); +} + +/****************************************************************** +* Function - DumpPackInfo() +* +* Description - This function dumps the all Queue(PackInfo[]) details. +* +* Parameters - Adapter: Pointer to the Adapter structure. +* +* Returns - None. +*******************************************************************/ +VOID DumpPackInfo(PMINI_ADAPTER Adapter) +{ + UINT uiLoopIndex = 0; + UINT uiIndex = 0; + UINT uiClsfrIndex = 0; + S_CLASSIFIER_RULE *pstClassifierEntry = NULL; + + for (uiLoopIndex = 0; uiLoopIndex < NO_OF_QUEUES; uiLoopIndex++) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "*********** Showing Details Of Queue %d***** ******", uiLoopIndex); + if (FALSE == Adapter->PackInfo[uiLoopIndex].bValid) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "bValid is FALSE for %X index\n", uiLoopIndex); + continue; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, " Dumping SF Rule Entry For SFID %lX\n", Adapter->PackInfo[uiLoopIndex].ulSFID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, " ucDirection %X\n", Adapter->PackInfo[uiLoopIndex].ucDirection); + + if (Adapter->PackInfo[uiLoopIndex].ucIpVersion == IPV6) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "Ipv6 Service Flow\n"); + else + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "Ipv4 Service Flow\n"); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "SF Traffic Priority %X\n", Adapter->PackInfo[uiLoopIndex].u8TrafficPriority); + + for (uiClsfrIndex = 0; uiClsfrIndex < MAX_CLASSIFIERS; uiClsfrIndex++) { + pstClassifierEntry = &Adapter->astClassifierTable[uiClsfrIndex]; + if (!pstClassifierEntry->bUsed) + continue; + + if (pstClassifierEntry->ulSFID != Adapter->PackInfo[uiLoopIndex].ulSFID) + continue; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tDumping Classifier Rule Entry For Index: %X Classifier Rule ID : %X\n", uiClsfrIndex, pstClassifierEntry->uiClassifierRuleIndex); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tDumping Classifier Rule Entry For Index: %X usVCID_Value : %X\n", uiClsfrIndex, pstClassifierEntry->usVCID_Value); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tDumping Classifier Rule Entry For Index: %X bProtocolValid : %X\n", uiClsfrIndex, pstClassifierEntry->bProtocolValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tDumping Classifier Rule Entry For Index: %X bTOSValid : %X\n", uiClsfrIndex, pstClassifierEntry->bTOSValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tDumping Classifier Rule Entry For Index: %X bDestIpValid : %X\n", uiClsfrIndex, pstClassifierEntry->bDestIpValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tDumping Classifier Rule Entry For Index: %X bSrcIpValid : %X\n", uiClsfrIndex, pstClassifierEntry->bSrcIpValid); + + for (uiIndex = 0; uiIndex < MAX_PORT_RANGE; uiIndex++) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tusSrcPortRangeLo:%X\n", pstClassifierEntry->usSrcPortRangeLo[uiIndex]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tusSrcPortRangeHi:%X\n", pstClassifierEntry->usSrcPortRangeHi[uiIndex]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tusDestPortRangeLo:%X\n", pstClassifierEntry->usDestPortRangeLo[uiIndex]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tusDestPortRangeHi:%X\n", pstClassifierEntry->usDestPortRangeHi[uiIndex]); + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tucIPSourceAddressLength : 0x%x\n", pstClassifierEntry->ucIPSourceAddressLength); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tucIPDestinationAddressLength : 0x%x\n", pstClassifierEntry->ucIPDestinationAddressLength); + for (uiIndex = 0; uiIndex < pstClassifierEntry->ucIPSourceAddressLength; uiIndex++) { + if (Adapter->PackInfo[uiLoopIndex].ucIpVersion == IPV6) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tIpv6 ulSrcIpAddr :\n"); + DumpIpv6Address(pstClassifierEntry->stSrcIpAddress.ulIpv6Addr); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tIpv6 ulSrcIpMask :\n"); + DumpIpv6Address(pstClassifierEntry->stSrcIpAddress.ulIpv6Mask); + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tulSrcIpAddr:%lX\n", pstClassifierEntry->stSrcIpAddress.ulIpv4Addr[uiIndex]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tulSrcIpMask:%lX\n", pstClassifierEntry->stSrcIpAddress.ulIpv4Mask[uiIndex]); + } + } + + for (uiIndex = 0; uiIndex < pstClassifierEntry->ucIPDestinationAddressLength; uiIndex++) { + if (Adapter->PackInfo[uiLoopIndex].ucIpVersion == IPV6) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tIpv6 ulDestIpAddr :\n"); + DumpIpv6Address(pstClassifierEntry->stDestIpAddress.ulIpv6Addr); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tIpv6 ulDestIpMask :\n"); + DumpIpv6Address(pstClassifierEntry->stDestIpAddress.ulIpv6Mask); + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tulDestIpAddr:%lX\n", pstClassifierEntry->stDestIpAddress.ulIpv4Addr[uiIndex]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tulDestIpMask:%lX\n", pstClassifierEntry->stDestIpAddress.ulIpv4Mask[uiIndex]); + } + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tucProtocol:0x%X\n", pstClassifierEntry->ucProtocol[0]); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\tu8ClassifierRulePriority:%X\n", pstClassifierEntry->u8ClassifierRulePriority); + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "ulSFID:%lX\n", Adapter->PackInfo[uiLoopIndex].ulSFID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "usVCID_Value:%X\n", Adapter->PackInfo[uiLoopIndex].usVCID_Value); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "PhsEnabled: 0x%X\n", Adapter->PackInfo[uiLoopIndex].bHeaderSuppressionEnabled); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiThreshold:%X\n", Adapter->PackInfo[uiLoopIndex].uiThreshold); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "bValid:%X\n", Adapter->PackInfo[uiLoopIndex].bValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "bActive:%X\n", Adapter->PackInfo[uiLoopIndex].bActive); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "ActivateReqSent: %x", Adapter->PackInfo[uiLoopIndex].bActivateRequestSent); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "u8QueueType:%X\n", Adapter->PackInfo[uiLoopIndex].u8QueueType); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiMaxBucketSize:%X\n", Adapter->PackInfo[uiLoopIndex].uiMaxBucketSize); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiPerSFTxResourceCount:%X\n", atomic_read(&Adapter->PackInfo[uiLoopIndex].uiPerSFTxResourceCount)); + /* DumpDebug(DUMP_INFO,("bCSSupport:%X\n",Adapter->PackInfo[uiLoopIndex].bCSSupport)); */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "CurrQueueDepthOnTarget: %x\n", Adapter->PackInfo[uiLoopIndex].uiCurrentQueueDepthOnTarget); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiCurrentBytesOnHost:%X\n", Adapter->PackInfo[uiLoopIndex].uiCurrentBytesOnHost); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiCurrentPacketsOnHost:%X\n", Adapter->PackInfo[uiLoopIndex].uiCurrentPacketsOnHost); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiDroppedCountBytes:%X\n", Adapter->PackInfo[uiLoopIndex].uiDroppedCountBytes); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiDroppedCountPackets:%X\n", Adapter->PackInfo[uiLoopIndex].uiDroppedCountPackets); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiSentBytes:%X\n", Adapter->PackInfo[uiLoopIndex].uiSentBytes); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiSentPackets:%X\n", Adapter->PackInfo[uiLoopIndex].uiSentPackets); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiCurrentDrainRate:%X\n", Adapter->PackInfo[uiLoopIndex].uiCurrentDrainRate); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiThisPeriodSentBytes:%X\n", Adapter->PackInfo[uiLoopIndex].uiThisPeriodSentBytes); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "liDrainCalculated:%llX\n", Adapter->PackInfo[uiLoopIndex].liDrainCalculated); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiCurrentTokenCount:%X\n", Adapter->PackInfo[uiLoopIndex].uiCurrentTokenCount); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "liLastUpdateTokenAt:%llX\n", Adapter->PackInfo[uiLoopIndex].liLastUpdateTokenAt); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiMaxAllowedRate:%X\n", Adapter->PackInfo[uiLoopIndex].uiMaxAllowedRate); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiPendedLast:%X\n", Adapter->PackInfo[uiLoopIndex].uiPendedLast); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "NumOfPacketsSent:%X\n", Adapter->PackInfo[uiLoopIndex].NumOfPacketsSent); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "Direction: %x\n", Adapter->PackInfo[uiLoopIndex].ucDirection); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "CID: %x\n", Adapter->PackInfo[uiLoopIndex].usCID); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "ProtocolValid: %x\n", Adapter->PackInfo[uiLoopIndex].bProtocolValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "TOSValid: %x\n", Adapter->PackInfo[uiLoopIndex].bTOSValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "DestIpValid: %x\n", Adapter->PackInfo[uiLoopIndex].bDestIpValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "SrcIpValid: %x\n", Adapter->PackInfo[uiLoopIndex].bSrcIpValid); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "ActiveSet: %x\n", Adapter->PackInfo[uiLoopIndex].bActiveSet); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "AdmittedSet: %x\n", Adapter->PackInfo[uiLoopIndex].bAdmittedSet); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "AuthzSet: %x\n", Adapter->PackInfo[uiLoopIndex].bAuthorizedSet); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "ClassifyPrority: %x\n", Adapter->PackInfo[uiLoopIndex].bClassifierPriority); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiMaxLatency: %x\n", Adapter->PackInfo[uiLoopIndex].uiMaxLatency); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "ServiceClassName: %x %x %x %x\n", Adapter->PackInfo[uiLoopIndex].ucServiceClassName[0], Adapter->PackInfo[uiLoopIndex].ucServiceClassName[1], Adapter->PackInfo[uiLoopIndex].ucServiceClassName[2], Adapter->PackInfo[uiLoopIndex].ucServiceClassName[3]); +/* BCM_DEBUG_PRINT (Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "bHeaderSuppressionEnabled :%X\n", Adapter->PackInfo[uiLoopIndex].bHeaderSuppressionEnabled); + * BCM_DEBUG_PRINT (Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiTotalTxBytes:%X\n", Adapter->PackInfo[uiLoopIndex].uiTotalTxBytes); + * BCM_DEBUG_PRINT (Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "uiTotalRxBytes:%X\n", Adapter->PackInfo[uiLoopIndex].uiTotalRxBytes); + * DumpDebug(DUMP_INFO,(" uiRanOutOfResCount:%X\n",Adapter->PackInfo[uiLoopIndex].uiRanOutOfResCount)); + */ + } + + for (uiLoopIndex = 0; uiLoopIndex < MIBS_MAX_HIST_ENTRIES; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "Adapter->aRxPktSizeHist[%x] = %x\n", uiLoopIndex, Adapter->aRxPktSizeHist[uiLoopIndex]); + + for (uiLoopIndex = 0; uiLoopIndex < MIBS_MAX_HIST_ENTRIES; uiLoopIndex++) + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "Adapter->aTxPktSizeHist[%x] = %x\n", uiLoopIndex, Adapter->aTxPktSizeHist[uiLoopIndex]); + + return; +} + +int reset_card_proc(PMINI_ADAPTER ps_adapter) +{ + int retval = STATUS_SUCCESS; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + PS_INTERFACE_ADAPTER psIntfAdapter = NULL; + unsigned int value = 0, uiResetValue = 0; + int bytes; + + psIntfAdapter = ((PS_INTERFACE_ADAPTER)(ps_adapter->pvInterfaceAdapter)); + ps_adapter->bDDRInitDone = FALSE; + + if (ps_adapter->chip_id >= T3LPB) { + /* SYS_CFG register is write protected hence for modifying this reg value, it should be read twice before */ + rdmalt(ps_adapter, SYS_CFG, &value, sizeof(value)); + rdmalt(ps_adapter, SYS_CFG, &value, sizeof(value)); + + /* making bit[6...5] same as was before f/w download. this setting force the h/w to */ + /* re-populated the SP RAM area with the string descriptor. */ + value = value | (ps_adapter->syscfgBefFwDld & 0x00000060); + wrmalt(ps_adapter, SYS_CFG, &value, sizeof(value)); + } + + /* killing all submitted URBs. */ + psIntfAdapter->psAdapter->StopAllXaction = TRUE; + Bcm_kill_all_URBs(psIntfAdapter); + /* Reset the UMA-B Device */ + if (ps_adapter->chip_id >= T3LPB) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Reseting UMA-B\n"); + retval = usb_reset_device(psIntfAdapter->udev); + psIntfAdapter->psAdapter->StopAllXaction = FALSE; + + if (retval != STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Reset failed with ret value :%d", retval); + goto err_exit; + } + + if (ps_adapter->chip_id == BCS220_2 || + ps_adapter->chip_id == BCS220_2BC || + ps_adapter->chip_id == BCS250_BC || + ps_adapter->chip_id == BCS220_3) { + + bytes = rdmalt(ps_adapter, HPM_CONFIG_LDO145, &value, sizeof(value)); + if (bytes < 0) { + retval = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "read failed with status :%d", retval); + goto err_exit; + } + /* setting 0th bit */ + value |= (1<<0); + retval = wrmalt(ps_adapter, HPM_CONFIG_LDO145, &value, sizeof(value)); + if (retval < 0) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "write failed with status :%d", retval); + goto err_exit; + } + } + } else { + bytes = rdmalt(ps_adapter, 0x0f007018, &value, sizeof(value)); + if (bytes < 0) { + retval = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "read failed with status :%d", retval); + goto err_exit; + } + value &= (~(1<<16)); + retval = wrmalt(ps_adapter, 0x0f007018, &value, sizeof(value)); + if (retval < 0) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "write failed with status :%d", retval); + goto err_exit; + } + + /* Toggling the GPIO 8, 9 */ + value = 0; + retval = wrmalt(ps_adapter, GPIO_OUTPUT_REGISTER, &value, sizeof(value)); + if (retval < 0) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "write failed with status :%d", retval); + goto err_exit; + } + value = 0x300; + retval = wrmalt(ps_adapter, GPIO_MODE_REGISTER, &value, sizeof(value)); + if (retval < 0) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "write failed with status :%d", retval); + goto err_exit; + } + mdelay(50); + } + + /* ps_adapter->downloadDDR = false; */ + if (ps_adapter->bFlashBoot) { + /* In flash boot mode MIPS state register has reverse polarity. + * So just or with setting bit 30. + * Make the MIPS in Reset state. + */ + rdmalt(ps_adapter, CLOCK_RESET_CNTRL_REG_1, &uiResetValue, sizeof(uiResetValue)); + uiResetValue |= (1<<30); + wrmalt(ps_adapter, CLOCK_RESET_CNTRL_REG_1, &uiResetValue, sizeof(uiResetValue)); + } + + if (ps_adapter->chip_id >= T3LPB) { + uiResetValue = 0; + /* + * WA for SYSConfig Issue. + * Read SYSCFG Twice to make it writable. + */ + rdmalt(ps_adapter, SYS_CFG, &uiResetValue, sizeof(uiResetValue)); + if (uiResetValue & (1<<4)) { + uiResetValue = 0; + rdmalt(ps_adapter, SYS_CFG, &uiResetValue, sizeof(uiResetValue)); /* 2nd read to make it writable. */ + uiResetValue &= (~(1<<4)); + wrmalt(ps_adapter, SYS_CFG, &uiResetValue, sizeof(uiResetValue)); + } + } + uiResetValue = 0; + wrmalt(ps_adapter, 0x0f01186c, &uiResetValue, sizeof(uiResetValue)); + +err_exit: + psIntfAdapter->psAdapter->StopAllXaction = FALSE; + return retval; +} + +int run_card_proc(PMINI_ADAPTER ps_adapter) +{ + int status = STATUS_SUCCESS; + int bytes; + + unsigned int value = 0; + { + bytes = rdmalt(ps_adapter, CLOCK_RESET_CNTRL_REG_1, &value, sizeof(value)); + if (bytes < 0) { + status = bytes; + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "%s:%d\n", __func__, __LINE__); + return status; + } + + if (ps_adapter->bFlashBoot) + value &= (~(1<<30)); + else + value |= (1<<30); + + if (wrmalt(ps_adapter, CLOCK_RESET_CNTRL_REG_1, &value, sizeof(value)) < 0) { + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "%s:%d\n", __func__, __LINE__); + return STATUS_FAILURE; + } + } + return status; +} + +int InitCardAndDownloadFirmware(PMINI_ADAPTER ps_adapter) +{ + int status; + UINT value = 0; + /* + * Create the threads first and then download the + * Firm/DDR Settings.. + */ + status = create_worker_threads(ps_adapter); + if (status < 0) + return status; + + status = bcm_parse_target_params(ps_adapter); + if (status) + return status; + + if (ps_adapter->chip_id >= T3LPB) { + rdmalt(ps_adapter, SYS_CFG, &value, sizeof(value)); + ps_adapter->syscfgBefFwDld = value; + + if ((value & 0x60) == 0) + ps_adapter->bFlashBoot = TRUE; + } + + reset_card_proc(ps_adapter); + + /* Initializing the NVM. */ + BcmInitNVM(ps_adapter); + status = ddr_init(ps_adapter); + if (status) { + pr_err(DRV_NAME "ddr_init Failed\n"); + return status; + } + + /* Download cfg file */ + status = buffDnldVerify(ps_adapter, + (PUCHAR)ps_adapter->pstargetparams, + sizeof(STARGETPARAMS), + CONFIG_BEGIN_ADDR); + if (status) { + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Error downloading CFG file"); + goto OUT; + } + + if (register_networkdev(ps_adapter)) { + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Register Netdevice failed. Cleanup needs to be performed."); + return -EIO; + } + + if (FALSE == ps_adapter->AutoFirmDld) { + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "AutoFirmDld Disabled in CFG File..\n"); + /* If Auto f/w download is disable, register the control interface, */ + /* register the control interface after the mailbox. */ + if (register_control_device_interface(ps_adapter) < 0) { + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Register Control Device failed. Cleanup needs to be performed."); + return -EIO; + } + return STATUS_SUCCESS; + } + + /* + * Do the LED Settings here. It will be used by the Firmware Download + * Thread. + */ + + /* + * 1. If the LED Settings fails, do not stop and do the Firmware download. + * 2. This init would happened only if the cfg file is present, else + * call from the ioctl context. + */ + + status = InitLedSettings(ps_adapter); + if (status) { + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_PRINTK, 0, 0, "INIT LED FAILED\n"); + return status; + } + + if (ps_adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + ps_adapter->DriverState = DRIVER_INIT; + wake_up(&ps_adapter->LEDInfo.notify_led_event); + } + + if (ps_adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + ps_adapter->DriverState = FW_DOWNLOAD; + wake_up(&ps_adapter->LEDInfo.notify_led_event); + } + + value = 0; + wrmalt(ps_adapter, EEPROM_CAL_DATA_INTERNAL_LOC - 4, &value, sizeof(value)); + wrmalt(ps_adapter, EEPROM_CAL_DATA_INTERNAL_LOC - 8, &value, sizeof(value)); + + if (ps_adapter->eNVMType == NVM_FLASH) { + status = PropagateCalParamsFromFlashToMemory(ps_adapter); + if (status) { + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Propagation of Cal param failed .."); + goto OUT; + } + } + + /* Download Firmare */ + status = BcmFileDownload(ps_adapter, BIN_FILE, FIRMWARE_BEGIN_ADDR); + if (status != 0) { + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "No Firmware File is present...\n"); + goto OUT; + } + + status = run_card_proc(ps_adapter); + if (status) { + BCM_DEBUG_PRINT(ps_adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "run_card_proc Failed\n"); + goto OUT; + } + + ps_adapter->fw_download_done = TRUE; + mdelay(10); + +OUT: + if (ps_adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + ps_adapter->DriverState = FW_DOWNLOAD_DONE; + wake_up(&ps_adapter->LEDInfo.notify_led_event); + } + + return status; +} + +static int bcm_parse_target_params(PMINI_ADAPTER Adapter) +{ + struct file *flp = NULL; + mm_segment_t oldfs = {0}; + char *buff; + int len = 0; + loff_t pos = 0; + + buff = kmalloc(BUFFER_1K, GFP_KERNEL); + if (!buff) + return -ENOMEM; + + Adapter->pstargetparams = kmalloc(sizeof(STARGETPARAMS), GFP_KERNEL); + if (Adapter->pstargetparams == NULL) { + kfree(buff); + return -ENOMEM; + } + + flp = open_firmware_file(Adapter, CFG_FILE); + if (!flp) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "NOT ABLE TO OPEN THE %s FILE\n", CFG_FILE); + kfree(buff); + kfree(Adapter->pstargetparams); + Adapter->pstargetparams = NULL; + return -ENOENT; + } + oldfs = get_fs(); + set_fs(get_ds()); + len = vfs_read(flp, (void __user __force *)buff, BUFFER_1K, &pos); + set_fs(oldfs); + + if (len != sizeof(STARGETPARAMS)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Mismatch in Target Param Structure!\n"); + kfree(buff); + kfree(Adapter->pstargetparams); + Adapter->pstargetparams = NULL; + filp_close(flp, current->files); + return -ENOENT; + } + filp_close(flp, current->files); + + /* Check for autolink in config params */ + /* + * Values in Adapter->pstargetparams are in network byte order + */ + memcpy(Adapter->pstargetparams, buff, sizeof(STARGETPARAMS)); + kfree(buff); + beceem_parse_target_struct(Adapter); + return STATUS_SUCCESS; +} + +void beceem_parse_target_struct(PMINI_ADAPTER Adapter) +{ + UINT uiHostDrvrCfg6 = 0, uiEEPROMFlag = 0; + + if (ntohl(Adapter->pstargetparams->m_u32PhyParameter2) & AUTO_SYNC_DISABLE) { + pr_info(DRV_NAME ": AutoSyncup is Disabled\n"); + Adapter->AutoSyncup = FALSE; + } else { + pr_info(DRV_NAME ": AutoSyncup is Enabled\n"); + Adapter->AutoSyncup = TRUE; + } + + if (ntohl(Adapter->pstargetparams->HostDrvrConfig6) & AUTO_LINKUP_ENABLE) { + pr_info(DRV_NAME ": Enabling autolink up"); + Adapter->AutoLinkUp = TRUE; + } else { + pr_info(DRV_NAME ": Disabling autolink up"); + Adapter->AutoLinkUp = FALSE; + } + /* Setting the DDR Setting.. */ + Adapter->DDRSetting = (ntohl(Adapter->pstargetparams->HostDrvrConfig6) >> 8)&0x0F; + Adapter->ulPowerSaveMode = (ntohl(Adapter->pstargetparams->HostDrvrConfig6)>>12)&0x0F; + pr_info(DRV_NAME ": DDR Setting: %x\n", Adapter->DDRSetting); + pr_info(DRV_NAME ": Power Save Mode: %lx\n", Adapter->ulPowerSaveMode); + if (ntohl(Adapter->pstargetparams->HostDrvrConfig6) & AUTO_FIRM_DOWNLOAD) { + pr_info(DRV_NAME ": Enabling Auto Firmware Download\n"); + Adapter->AutoFirmDld = TRUE; + } else { + pr_info(DRV_NAME ": Disabling Auto Firmware Download\n"); + Adapter->AutoFirmDld = FALSE; + } + uiHostDrvrCfg6 = ntohl(Adapter->pstargetparams->HostDrvrConfig6); + Adapter->bMipsConfig = (uiHostDrvrCfg6>>20)&0x01; + pr_info(DRV_NAME ": MIPSConfig : 0x%X\n", Adapter->bMipsConfig); + /* used for backward compatibility. */ + Adapter->bDPLLConfig = (uiHostDrvrCfg6>>19)&0x01; + Adapter->PmuMode = (uiHostDrvrCfg6 >> 24) & 0x03; + pr_info(DRV_NAME ": PMU MODE: %x", Adapter->PmuMode); + + if ((uiHostDrvrCfg6 >> HOST_BUS_SUSPEND_BIT) & (0x01)) { + Adapter->bDoSuspend = TRUE; + pr_info(DRV_NAME ": Making DoSuspend TRUE as per configFile"); + } + + uiEEPROMFlag = ntohl(Adapter->pstargetparams->m_u32EEPROMFlag); + pr_info(DRV_NAME ": uiEEPROMFlag : 0x%X\n", uiEEPROMFlag); + Adapter->eNVMType = (NVM_TYPE)((uiEEPROMFlag>>4)&0x3); + Adapter->bStatusWrite = (uiEEPROMFlag>>6)&0x1; + Adapter->uiSectorSizeInCFG = 1024*(0xFFFF & ntohl(Adapter->pstargetparams->HostDrvrConfig4)); + Adapter->bSectorSizeOverride = (bool) ((ntohl(Adapter->pstargetparams->HostDrvrConfig4))>>16)&0x1; + + if (ntohl(Adapter->pstargetparams->m_u32PowerSavingModeOptions) & 0x01) + Adapter->ulPowerSaveMode = DEVICE_POWERSAVE_MODE_AS_PROTOCOL_IDLE_MODE; + + if (Adapter->ulPowerSaveMode != DEVICE_POWERSAVE_MODE_AS_PROTOCOL_IDLE_MODE) + doPowerAutoCorrection(Adapter); +} + +static VOID doPowerAutoCorrection(PMINI_ADAPTER psAdapter) +{ + UINT reporting_mode; + + reporting_mode = ntohl(psAdapter->pstargetparams->m_u32PowerSavingModeOptions) & 0x02; + psAdapter->bIsAutoCorrectEnabled = !((char)(psAdapter->ulPowerSaveMode >> 3) & 0x1); + + if (reporting_mode == TRUE) { + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "can't do suspen/resume as reporting mode is enable"); + psAdapter->bDoSuspend = FALSE; + } + + if (psAdapter->bIsAutoCorrectEnabled && (psAdapter->chip_id >= T3LPB)) { + /* If reporting mode is enable, switch PMU to PMC */ + { + psAdapter->ulPowerSaveMode = DEVICE_POWERSAVE_MODE_AS_PMU_CLOCK_GATING; + psAdapter->bDoSuspend = FALSE; + } + + /* clearing space bit[15..12] */ + psAdapter->pstargetparams->HostDrvrConfig6 &= ~(htonl((0xF << 12))); + /* placing the power save mode option */ + psAdapter->pstargetparams->HostDrvrConfig6 |= htonl((psAdapter->ulPowerSaveMode << 12)); + } else if (psAdapter->bIsAutoCorrectEnabled == FALSE) { + /* remove the autocorrect disable bit set before dumping. */ + psAdapter->ulPowerSaveMode &= ~(1 << 3); + psAdapter->pstargetparams->HostDrvrConfig6 &= ~(htonl(1 << 15)); + BCM_DEBUG_PRINT(psAdapter, DBG_TYPE_INITEXIT, MP_INIT, DBG_LVL_ALL, "Using Forced User Choice: %lx\n", psAdapter->ulPowerSaveMode); + } +} + +#if 0 +static unsigned char *ReadMacAddrEEPROM(PMINI_ADAPTER Adapter, ulong dwAddress) +{ + int status = 0, i = 0; + unsigned int temp = 0; + unsigned char *pucmacaddr = kmalloc(MAC_ADDRESS_SIZE, GFP_KERNEL); + int bytes; + + if (!pucmacaddr) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "No Buffers to Read the EEPROM Address\n"); + return NULL; + } + + dwAddress |= 0x5b000000; + status = wrmalt(Adapter, EEPROM_COMMAND_Q_REG, (PUINT)&dwAddress, sizeof(UINT)); + if (status != STATUS_SUCCESS) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "wrm Failed..\n"); + kfree(pucmacaddr); + pucmacaddr = NULL; + goto OUT; + } + + for (i = 0; i < MAC_ADDRESS_SIZE; i++) { + bytes = rdmalt(Adapter, EEPROM_READ_DATA_Q_REG, &temp, sizeof(temp)); + if (bytes < 0) { + status = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "rdm Failed..\n"); + kfree(pucmacaddr); + pucmacaddr = NULL; + goto OUT; + } + pucmacaddr[i] = temp & 0xff; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_INITEXIT, DRV_ENTRY, DBG_LVL_ALL, "%x\n", pucmacaddr[i]); + } +OUT: + return pucmacaddr; +} +#endif + +static void convertEndian(B_UINT8 rwFlag, PUINT puiBuffer, UINT uiByteCount) +{ + UINT uiIndex = 0; + + if (RWM_WRITE == rwFlag) { + for (uiIndex = 0; uiIndex < (uiByteCount/sizeof(UINT)); uiIndex++) + puiBuffer[uiIndex] = htonl(puiBuffer[uiIndex]); + } else { + for (uiIndex = 0; uiIndex < (uiByteCount/sizeof(UINT)); uiIndex++) + puiBuffer[uiIndex] = ntohl(puiBuffer[uiIndex]); + } +} + +#define CACHE_ADDRESS_MASK 0x80000000 +#define UNCACHE_ADDRESS_MASK 0xa0000000 + +int rdm(PMINI_ADAPTER Adapter, UINT uiAddress, PCHAR pucBuff, size_t sSize) +{ + return Adapter->interface_rdm(Adapter->pvInterfaceAdapter, + uiAddress, pucBuff, sSize); +} + +int wrm(PMINI_ADAPTER Adapter, UINT uiAddress, PCHAR pucBuff, size_t sSize) +{ + int iRetVal; + + iRetVal = Adapter->interface_wrm(Adapter->pvInterfaceAdapter, + uiAddress, pucBuff, sSize); + return iRetVal; +} + +int wrmalt(PMINI_ADAPTER Adapter, UINT uiAddress, PUINT pucBuff, size_t size) +{ + convertEndian(RWM_WRITE, pucBuff, size); + return wrm(Adapter, uiAddress, (PUCHAR)pucBuff, size); +} + +int rdmalt(PMINI_ADAPTER Adapter, UINT uiAddress, PUINT pucBuff, size_t size) +{ + INT uiRetVal = 0; + + uiRetVal = rdm(Adapter, uiAddress, (PUCHAR)pucBuff, size); + convertEndian(RWM_READ, (PUINT)pucBuff, size); + + return uiRetVal; +} + +int wrmWithLock(PMINI_ADAPTER Adapter, UINT uiAddress, PCHAR pucBuff, size_t sSize) +{ + INT status = STATUS_SUCCESS; + down(&Adapter->rdmwrmsync); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + status = -EACCES; + goto exit; + } + + status = wrm(Adapter, uiAddress, pucBuff, sSize); +exit: + up(&Adapter->rdmwrmsync); + return status; +} + +int wrmaltWithLock(PMINI_ADAPTER Adapter, UINT uiAddress, PUINT pucBuff, size_t size) +{ + int iRetVal = STATUS_SUCCESS; + + down(&Adapter->rdmwrmsync); + + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + iRetVal = -EACCES; + goto exit; + } + + iRetVal = wrmalt(Adapter, uiAddress, pucBuff, size); +exit: + up(&Adapter->rdmwrmsync); + return iRetVal; +} + +int rdmaltWithLock(PMINI_ADAPTER Adapter, UINT uiAddress, PUINT pucBuff, size_t size) +{ + INT uiRetVal = STATUS_SUCCESS; + + down(&Adapter->rdmwrmsync); + if ((Adapter->IdleMode == TRUE) || + (Adapter->bShutStatus == TRUE) || + (Adapter->bPreparingForLowPowerMode == TRUE)) { + + uiRetVal = -EACCES; + goto exit; + } + + uiRetVal = rdmalt(Adapter, uiAddress, pucBuff, size); +exit: + up(&Adapter->rdmwrmsync); + return uiRetVal; +} + +static VOID HandleShutDownModeWakeup(PMINI_ADAPTER Adapter) +{ + int clear_abort_pattern = 0, Status = 0; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "====>\n"); + /* target has woken up From Shut Down */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "Clearing Shut Down Software abort pattern\n"); + Status = wrmalt(Adapter, SW_ABORT_IDLEMODE_LOC, (PUINT)&clear_abort_pattern, sizeof(clear_abort_pattern)); + if (Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "WRM to SW_ABORT_IDLEMODE_LOC failed with err:%d", Status); + return; + } + + if (Adapter->ulPowerSaveMode != DEVICE_POWERSAVE_MODE_AS_PROTOCOL_IDLE_MODE) { + msleep(100); + InterfaceHandleShutdownModeWakeup(Adapter); + msleep(100); + } + + if (Adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + Adapter->DriverState = NO_NETWORK_ENTRY; + wake_up(&Adapter->LEDInfo.notify_led_event); + } + + Adapter->bTriedToWakeUpFromlowPowerMode = FALSE; + Adapter->bShutStatus = FALSE; + wake_up(&Adapter->lowpower_mode_wait_queue); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "<====\n"); +} + +static VOID SendShutModeResponse(PMINI_ADAPTER Adapter) +{ + CONTROL_MESSAGE stShutdownResponse; + UINT NVMAccess = 0, lowPwrAbortMsg = 0; + UINT Status = 0; + + memset(&stShutdownResponse, 0, sizeof(CONTROL_MESSAGE)); + stShutdownResponse.Leader.Status = LINK_UP_CONTROL_REQ; + stShutdownResponse.Leader.PLength = 8; /* 8 bytes; */ + stShutdownResponse.szData[0] = LINK_UP_ACK; + stShutdownResponse.szData[1] = LINK_SHUTDOWN_REQ_FROM_FIRMWARE; + + /********************************* + * down_trylock - + * if [ semaphore is available ] + * acquire semaphone and return value 0 ; + * else + * return non-zero value ; + * + ***********************************/ + + NVMAccess = down_trylock(&Adapter->NVMRdmWrmLock); + lowPwrAbortMsg = down_trylock(&Adapter->LowPowerModeSync); + + if (NVMAccess || lowPwrAbortMsg || atomic_read(&Adapter->TotalPacketCount)) { + if (!NVMAccess) + up(&Adapter->NVMRdmWrmLock); + + if (!lowPwrAbortMsg) + up(&Adapter->LowPowerModeSync); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "Device Access is going on NACK the Shut Down MODE\n"); + stShutdownResponse.szData[2] = SHUTDOWN_NACK_FROM_DRIVER; /* NACK- device access is going on. */ + Adapter->bPreparingForLowPowerMode = FALSE; + } else { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "Sending SHUTDOWN MODE ACK\n"); + stShutdownResponse.szData[2] = SHUTDOWN_ACK_FROM_DRIVER; /* ShutDown ACK */ + + /* Wait for the LED to TURN OFF before sending ACK response */ + if (Adapter->LEDInfo.led_thread_running & BCM_LED_THREAD_RUNNING_ACTIVELY) { + INT iRetVal = 0; + + /* Wake the LED Thread with LOWPOWER_MODE_ENTER State */ + Adapter->DriverState = LOWPOWER_MODE_ENTER; + wake_up(&Adapter->LEDInfo.notify_led_event); + + /* Wait for 1 SEC for LED to OFF */ + iRetVal = wait_event_timeout(Adapter->LEDInfo.idleModeSyncEvent, Adapter->LEDInfo.bIdle_led_off, msecs_to_jiffies(1000)); + + /* If Timed Out to Sync IDLE MODE Enter, do IDLE mode Exit and Send NACK to device */ + if (iRetVal <= 0) { + stShutdownResponse.szData[1] = SHUTDOWN_NACK_FROM_DRIVER; /* NACK- device access is going on. */ + Adapter->DriverState = NO_NETWORK_ENTRY; + wake_up(&Adapter->LEDInfo.notify_led_event); + } + } + + if (stShutdownResponse.szData[2] == SHUTDOWN_ACK_FROM_DRIVER) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "ACKING SHUTDOWN MODE !!!!!!!!!"); + down(&Adapter->rdmwrmsync); + Adapter->bPreparingForLowPowerMode = TRUE; + up(&Adapter->rdmwrmsync); + /* Killing all URBS. */ + if (Adapter->bDoSuspend == TRUE) + Bcm_kill_all_URBs((PS_INTERFACE_ADAPTER)(Adapter->pvInterfaceAdapter)); + } else { + Adapter->bPreparingForLowPowerMode = FALSE; + } + + if (!NVMAccess) + up(&Adapter->NVMRdmWrmLock); + + if (!lowPwrAbortMsg) + up(&Adapter->LowPowerModeSync); + } + + Status = CopyBufferToControlPacket(Adapter, &stShutdownResponse); + if ((Status != STATUS_SUCCESS)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "fail to send the Idle mode Request\n"); + Adapter->bPreparingForLowPowerMode = FALSE; + StartInterruptUrb((PS_INTERFACE_ADAPTER)(Adapter->pvInterfaceAdapter)); + } +} + +static void HandleShutDownModeRequest(PMINI_ADAPTER Adapter, PUCHAR pucBuffer) +{ + B_UINT32 uiResetValue = 0; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "====>\n"); + + if (*(pucBuffer+1) == COMPLETE_WAKE_UP_NOTIFICATION_FRM_FW) { + HandleShutDownModeWakeup(Adapter); + } else if (*(pucBuffer+1) == LINK_SHUTDOWN_REQ_FROM_FIRMWARE) { + /* Target wants to go to Shut Down Mode */ + /* InterfacePrepareForShutdown(Adapter); */ + if (Adapter->chip_id == BCS220_2 || + Adapter->chip_id == BCS220_2BC || + Adapter->chip_id == BCS250_BC || + Adapter->chip_id == BCS220_3) { + + rdmalt(Adapter, HPM_CONFIG_MSW, &uiResetValue, 4); + uiResetValue |= (1<<17); + wrmalt(Adapter, HPM_CONFIG_MSW, &uiResetValue, 4); + } + + SendShutModeResponse(Adapter); + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "ShutDownModeResponse:Notification received: Sending the response(Ack/Nack)\n"); + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, MP_SHUTDOWN, DBG_LVL_ALL, "<====\n"); + return; +} + +VOID ResetCounters(PMINI_ADAPTER Adapter) +{ + beceem_protocol_reset(Adapter); + Adapter->CurrNumRecvDescs = 0; + Adapter->PrevNumRecvDescs = 0; + Adapter->LinkUpStatus = 0; + Adapter->LinkStatus = 0; + atomic_set(&Adapter->cntrlpktCnt, 0); + atomic_set(&Adapter->TotalPacketCount, 0); + Adapter->fw_download_done = FALSE; + Adapter->LinkStatus = 0; + Adapter->AutoLinkUp = FALSE; + Adapter->IdleMode = FALSE; + Adapter->bShutStatus = FALSE; +} + +S_CLASSIFIER_RULE *GetFragIPClsEntry(PMINI_ADAPTER Adapter, USHORT usIpIdentification, ULONG SrcIP) +{ + UINT uiIndex = 0; + for (uiIndex = 0; uiIndex < MAX_FRAGMENTEDIP_CLASSIFICATION_ENTRIES; uiIndex++) { + if ((Adapter->astFragmentedPktClassifierTable[uiIndex].bUsed) && + (Adapter->astFragmentedPktClassifierTable[uiIndex].usIpIdentification == usIpIdentification) && + (Adapter->astFragmentedPktClassifierTable[uiIndex].ulSrcIpAddress == SrcIP) && + !Adapter->astFragmentedPktClassifierTable[uiIndex].bOutOfOrderFragment) + + return Adapter->astFragmentedPktClassifierTable[uiIndex].pstMatchedClassifierEntry; + } + return NULL; +} + +void AddFragIPClsEntry(PMINI_ADAPTER Adapter, PS_FRAGMENTED_PACKET_INFO psFragPktInfo) +{ + UINT uiIndex = 0; + for (uiIndex = 0; uiIndex < MAX_FRAGMENTEDIP_CLASSIFICATION_ENTRIES; uiIndex++) { + if (!Adapter->astFragmentedPktClassifierTable[uiIndex].bUsed) { + memcpy(&Adapter->astFragmentedPktClassifierTable[uiIndex], psFragPktInfo, sizeof(S_FRAGMENTED_PACKET_INFO)); + break; + } + } +} + +void DelFragIPClsEntry(PMINI_ADAPTER Adapter, USHORT usIpIdentification, ULONG SrcIp) +{ + UINT uiIndex = 0; + for (uiIndex = 0; uiIndex < MAX_FRAGMENTEDIP_CLASSIFICATION_ENTRIES; uiIndex++) { + if ((Adapter->astFragmentedPktClassifierTable[uiIndex].bUsed) && + (Adapter->astFragmentedPktClassifierTable[uiIndex].usIpIdentification == usIpIdentification) && + (Adapter->astFragmentedPktClassifierTable[uiIndex].ulSrcIpAddress == SrcIp)) + + memset(&Adapter->astFragmentedPktClassifierTable[uiIndex], 0, sizeof(S_FRAGMENTED_PACKET_INFO)); + } +} + +void update_per_cid_rx(PMINI_ADAPTER Adapter) +{ + UINT qindex = 0; + + if ((jiffies - Adapter->liDrainCalculated) < XSECONDS) + return; + + for (qindex = 0; qindex < HiPriority; qindex++) { + if (Adapter->PackInfo[qindex].ucDirection == 0) { + Adapter->PackInfo[qindex].uiCurrentRxRate = + (Adapter->PackInfo[qindex].uiCurrentRxRate + + Adapter->PackInfo[qindex].uiThisPeriodRxBytes) / 2; + + Adapter->PackInfo[qindex].uiThisPeriodRxBytes = 0; + } else { + Adapter->PackInfo[qindex].uiCurrentDrainRate = + (Adapter->PackInfo[qindex].uiCurrentDrainRate + + Adapter->PackInfo[qindex].uiThisPeriodSentBytes) / 2; + Adapter->PackInfo[qindex].uiThisPeriodSentBytes = 0; + } + } + Adapter->liDrainCalculated = jiffies; +} + +void update_per_sf_desc_cnts(PMINI_ADAPTER Adapter) +{ + INT iIndex = 0; + u32 uibuff[MAX_TARGET_DSX_BUFFERS]; + int bytes; + + if (!atomic_read(&Adapter->uiMBupdate)) + return; + + bytes = rdmaltWithLock(Adapter, TARGET_SFID_TXDESC_MAP_LOC, (PUINT)uibuff, sizeof(UINT) * MAX_TARGET_DSX_BUFFERS); + if (bytes < 0) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "rdm failed\n"); + return; + } + + for (iIndex = 0; iIndex < HiPriority; iIndex++) { + if (Adapter->PackInfo[iIndex].bValid && Adapter->PackInfo[iIndex].ucDirection) { + if (Adapter->PackInfo[iIndex].usVCID_Value < MAX_TARGET_DSX_BUFFERS) + atomic_set(&Adapter->PackInfo[iIndex].uiPerSFTxResourceCount, uibuff[Adapter->PackInfo[iIndex].usVCID_Value]); + else + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Invalid VCID : %x\n", Adapter->PackInfo[iIndex].usVCID_Value); + } + } + atomic_set(&Adapter->uiMBupdate, FALSE); +} + +void flush_queue(PMINI_ADAPTER Adapter, UINT iQIndex) +{ + struct sk_buff *PacketToDrop = NULL; + struct net_device_stats *netstats = &Adapter->dev->stats; + spin_lock_bh(&Adapter->PackInfo[iQIndex].SFQueueLock); + + while (Adapter->PackInfo[iQIndex].FirstTxQueue && atomic_read(&Adapter->TotalPacketCount)) { + PacketToDrop = Adapter->PackInfo[iQIndex].FirstTxQueue; + if (PacketToDrop && PacketToDrop->len) { + netstats->tx_dropped++; + DEQUEUEPACKET(Adapter->PackInfo[iQIndex].FirstTxQueue, Adapter->PackInfo[iQIndex].LastTxQueue); + Adapter->PackInfo[iQIndex].uiCurrentPacketsOnHost--; + Adapter->PackInfo[iQIndex].uiCurrentBytesOnHost -= PacketToDrop->len; + + /* Adding dropped statistics */ + Adapter->PackInfo[iQIndex].uiDroppedCountBytes += PacketToDrop->len; + Adapter->PackInfo[iQIndex].uiDroppedCountPackets++; + dev_kfree_skb(PacketToDrop); + atomic_dec(&Adapter->TotalPacketCount); + } + } + spin_unlock_bh(&Adapter->PackInfo[iQIndex].SFQueueLock); +} + +static void beceem_protocol_reset(PMINI_ADAPTER Adapter) +{ + int i; + if (netif_msg_link(Adapter)) + pr_notice(PFX "%s: protocol reset\n", Adapter->dev->name); + + netif_carrier_off(Adapter->dev); + netif_stop_queue(Adapter->dev); + + Adapter->IdleMode = FALSE; + Adapter->LinkUpStatus = FALSE; + ClearTargetDSXBuffer(Adapter, 0, TRUE); + /* Delete All Classifier Rules */ + + for (i = 0; i < HiPriority; i++) + DeleteAllClassifiersForSF(Adapter, i); + + flush_all_queues(Adapter); + + if (Adapter->TimerActive == TRUE) + Adapter->TimerActive = FALSE; + + memset(Adapter->astFragmentedPktClassifierTable, 0, sizeof(S_FRAGMENTED_PACKET_INFO) * MAX_FRAGMENTEDIP_CLASSIFICATION_ENTRIES); + + for (i = 0; i < HiPriority; i++) { + /* resetting only the first size (S_MIBS_SERVICEFLOW_TABLE) for the SF. */ + /* It is same between MIBs and SF. */ + memset(&Adapter->PackInfo[i].stMibsExtServiceFlowTable, 0, sizeof(S_MIBS_EXTSERVICEFLOW_PARAMETERS)); + } +} diff --git a/drivers/staging/bcm/PHSDefines.h b/drivers/staging/bcm/PHSDefines.h new file mode 100644 index 00000000..eed4cfc6 --- /dev/null +++ b/drivers/staging/bcm/PHSDefines.h @@ -0,0 +1,125 @@ +#ifndef BCM_PHS_DEFINES_H +#define BCM_PHS_DEFINES_H + +#define PHS_INVALID_TABLE_INDEX 0xffffffff + +/************************* MACROS **********************************************/ +#define PHS_MEM_TAG "_SHP" + + + +//PHS Defines +#define STATUS_PHS_COMPRESSED 0xa1 +#define STATUS_PHS_NOCOMPRESSION 0xa2 +#define APPLY_PHS 1 +#define MAX_NO_BIT 7 +#define ZERO_PHSI 0 +#define VERIFY 0 +#define SIZE_MULTIPLE_32 4 +#define UNCOMPRESSED_PACKET 0 +#define DYNAMIC 0 +#define SUPPRESS 0x80 +#define NO_CLASSIFIER_MATCH 0 +#define SEND_PACKET_UNCOMPRESSED 0 +#define PHSI_IS_ZERO 0 +#define PHSI_LEN 1 +#define ERROR_LEN 0 +#define PHS_BUFFER_SIZE 1532 + + +//#define MAX_PHS_LENGTHS 100 +#define MAX_PHSRULE_PER_SF 20 +#define MAX_SERVICEFLOWS 17 + +//PHS Error Defines +#define PHS_SUCCESS 0 +#define ERR_PHS_INVALID_DEVICE_EXETENSION 0x800 +#define ERR_PHS_INVALID_PHS_RULE 0x801 +#define ERR_PHS_RULE_ALREADY_EXISTS 0x802 +#define ERR_SF_MATCH_FAIL 0x803 +#define ERR_INVALID_CLASSIFIERTABLE_FOR_SF 0x804 +#define ERR_SFTABLE_FULL 0x805 +#define ERR_CLSASSIFIER_TABLE_FULL 0x806 +#define ERR_PHSRULE_MEMALLOC_FAIL 0x807 +#define ERR_CLSID_MATCH_FAIL 0x808 +#define ERR_PHSRULE_MATCH_FAIL 0x809 + +typedef struct _S_PHS_RULE +{ + /// brief 8bit PHSI Of The Service Flow + B_UINT8 u8PHSI; + /// brief PHSF Of The Service Flow + B_UINT8 u8PHSFLength; + B_UINT8 u8PHSF[MAX_PHS_LENGTHS]; + /// brief PHSM Of The Service Flow + B_UINT8 u8PHSMLength; + B_UINT8 u8PHSM[MAX_PHS_LENGTHS]; + /// brief 8bit PHSS Of The Service Flow + B_UINT8 u8PHSS; + /// brief 8bit PHSV Of The Service Flow + B_UINT8 u8PHSV; + //Reference Count for this PHS Rule + B_UINT8 u8RefCnt; + //Flag to Store Unclassified PHS rules only in DL + B_UINT8 bUnclassifiedPHSRule; + + B_UINT8 u8Reserved[3]; + + LONG PHSModifiedBytes; + ULONG PHSModifiedNumPackets; + ULONG PHSErrorNumPackets; +}S_PHS_RULE; + + +typedef enum _E_CLASSIFIER_ENTRY_CONTEXT +{ + eActiveClassifierRuleContext, + eOldClassifierRuleContext +}E_CLASSIFIER_ENTRY_CONTEXT; + +typedef struct _S_CLASSIFIER_ENTRY +{ + B_UINT8 bUsed; + B_UINT16 uiClassifierRuleId; + B_UINT8 u8PHSI; + S_PHS_RULE *pstPhsRule; + B_UINT8 bUnclassifiedPHSRule; + +}S_CLASSIFIER_ENTRY; + + +typedef struct _S_CLASSIFIER_TABLE +{ + B_UINT16 uiTotalClassifiers; + S_CLASSIFIER_ENTRY stActivePhsRulesList[MAX_PHSRULE_PER_SF]; + S_CLASSIFIER_ENTRY stOldPhsRulesList[MAX_PHSRULE_PER_SF]; + B_UINT16 uiOldestPhsRuleIndex; + +}S_CLASSIFIER_TABLE; + + +typedef struct _S_SERVICEFLOW_ENTRY +{ + B_UINT8 bUsed; + B_UINT16 uiVcid; + S_CLASSIFIER_TABLE *pstClassifierTable; +}S_SERVICEFLOW_ENTRY; + +typedef struct _S_SERVICEFLOW_TABLE +{ + B_UINT16 uiTotalServiceFlows; + S_SERVICEFLOW_ENTRY stSFList[MAX_SERVICEFLOWS]; + +}S_SERVICEFLOW_TABLE; + + +typedef struct _PHS_DEVICE_EXTENSION +{ + /* PHS Specific data*/ + S_SERVICEFLOW_TABLE *pstServiceFlowPhsRulesTable; + void *CompressedTxBuffer; + void *UnCompressedRxBuffer; +}PHS_DEVICE_EXTENSION,*PPHS_DEVICE_EXTENSION; + + +#endif diff --git a/drivers/staging/bcm/PHSModule.c b/drivers/staging/bcm/PHSModule.c new file mode 100644 index 00000000..4aa2b71a --- /dev/null +++ b/drivers/staging/bcm/PHSModule.c @@ -0,0 +1,1611 @@ +#include "headers.h" + +static UINT CreateSFToClassifierRuleMapping(B_UINT16 uiVcid,B_UINT16 uiClsId,S_SERVICEFLOW_TABLE *psServiceFlowTable,S_PHS_RULE *psPhsRule,B_UINT8 u8AssociatedPHSI); + +static UINT CreateClassiferToPHSRuleMapping(B_UINT16 uiVcid,B_UINT16 uiClsId,S_SERVICEFLOW_ENTRY *pstServiceFlowEntry,S_PHS_RULE *psPhsRule,B_UINT8 u8AssociatedPHSI); + +static UINT CreateClassifierPHSRule(B_UINT16 uiClsId,S_CLASSIFIER_TABLE *psaClassifiertable ,S_PHS_RULE *psPhsRule,E_CLASSIFIER_ENTRY_CONTEXT eClsContext,B_UINT8 u8AssociatedPHSI); + +static UINT UpdateClassifierPHSRule(B_UINT16 uiClsId,S_CLASSIFIER_ENTRY *pstClassifierEntry,S_CLASSIFIER_TABLE *psaClassifiertable ,S_PHS_RULE *psPhsRule,B_UINT8 u8AssociatedPHSI); + +static BOOLEAN ValidatePHSRuleComplete(S_PHS_RULE *psPhsRule); + +static BOOLEAN DerefPhsRule(B_UINT16 uiClsId,S_CLASSIFIER_TABLE *psaClassifiertable,S_PHS_RULE *pstPhsRule); + +static UINT GetClassifierEntry(S_CLASSIFIER_TABLE *pstClassifierTable,B_UINT32 uiClsid,E_CLASSIFIER_ENTRY_CONTEXT eClsContext, S_CLASSIFIER_ENTRY **ppstClassifierEntry); + +static UINT GetPhsRuleEntry(S_CLASSIFIER_TABLE *pstClassifierTable,B_UINT32 uiPHSI,E_CLASSIFIER_ENTRY_CONTEXT eClsContext,S_PHS_RULE **ppstPhsRule); + +static void free_phs_serviceflow_rules(S_SERVICEFLOW_TABLE *psServiceFlowRulesTable); + +static int phs_compress(S_PHS_RULE *phs_members,unsigned char *in_buf, + unsigned char *out_buf,unsigned int *header_size,UINT *new_header_size ); + + +static int verify_suppress_phsf(unsigned char *in_buffer,unsigned char *out_buffer, + unsigned char *phsf,unsigned char *phsm,unsigned int phss,unsigned int phsv,UINT *new_header_size ); + +static int phs_decompress(unsigned char *in_buf,unsigned char *out_buf,\ + S_PHS_RULE *phs_rules,UINT *header_size); + + +static ULONG PhsCompress(void* pvContext, + B_UINT16 uiVcid, + B_UINT16 uiClsId, + void *pvInputBuffer, + void *pvOutputBuffer, + UINT *pOldHeaderSize, + UINT *pNewHeaderSize ); + +static ULONG PhsDeCompress(void* pvContext, + B_UINT16 uiVcid, + void *pvInputBuffer, + void *pvOutputBuffer, + UINT *pInHeaderSize, + UINT *pOutHeaderSize); + + + +#define IN +#define OUT + +/* +Function: PHSTransmit + +Description: This routine handle PHS(Payload Header Suppression for Tx path. + It extracts a fragment of the NDIS_PACKET containing the header + to be suppressed.It then supresses the header by invoking PHS exported compress routine. + The header data after supression is copied back to the NDIS_PACKET. + + +Input parameters: IN PMINI_ADAPTER Adapter - Miniport Adapter Context + IN Packet - NDIS packet containing data to be transmitted + IN USHORT Vcid - vcid pertaining to connection on which the packet is being sent.Used to + identify PHS rule to be applied. + B_UINT16 uiClassifierRuleID - Classifier Rule ID + BOOLEAN bHeaderSuppressionEnabled - indicates if header suprression is enabled for SF. + +Return: STATUS_SUCCESS - If the send was successful. + Other - If an error occured. +*/ + +int PHSTransmit(PMINI_ADAPTER Adapter, + struct sk_buff **pPacket, + USHORT Vcid, + B_UINT16 uiClassifierRuleID, + BOOLEAN bHeaderSuppressionEnabled, + UINT *PacketLen, + UCHAR bEthCSSupport) +{ + + //PHS Sepcific + UINT unPHSPktHdrBytesCopied = 0; + UINT unPhsOldHdrSize = 0; + UINT unPHSNewPktHeaderLen = 0; + /* Pointer to PHS IN Hdr Buffer */ + PUCHAR pucPHSPktHdrInBuf = + Adapter->stPhsTxContextInfo.ucaHdrSupressionInBuf; + /* Pointer to PHS OUT Hdr Buffer */ + PUCHAR pucPHSPktHdrOutBuf = + Adapter->stPhsTxContextInfo.ucaHdrSupressionOutBuf; + UINT usPacketType; + UINT BytesToRemove=0; + BOOLEAN bPHSI = 0; + LONG ulPhsStatus = 0; + UINT numBytesCompressed = 0; + struct sk_buff *newPacket = NULL; + struct sk_buff *Packet = *pPacket; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL, "In PHSTransmit"); + + if(!bEthCSSupport) + BytesToRemove=ETH_HLEN; + /* + Accumulate the header upto the size we support supression + from NDIS packet + */ + + usPacketType=((struct ethhdr *)(Packet->data))->h_proto; + + + pucPHSPktHdrInBuf = Packet->data + BytesToRemove; + //considering data after ethernet header + if((*PacketLen - BytesToRemove) < MAX_PHS_LENGTHS) + { + + unPHSPktHdrBytesCopied = (*PacketLen - BytesToRemove); + } + else + { + unPHSPktHdrBytesCopied = MAX_PHS_LENGTHS; + } + + if( (unPHSPktHdrBytesCopied > 0 ) && + (unPHSPktHdrBytesCopied <= MAX_PHS_LENGTHS)) + { + + + // Step 2 Supress Header using PHS and fill into intermediate ucaPHSPktHdrOutBuf. + // Suppress only if IP Header and PHS Enabled For the Service Flow + if(((usPacketType == ETHERNET_FRAMETYPE_IPV4) || + (usPacketType == ETHERNET_FRAMETYPE_IPV6)) && + (bHeaderSuppressionEnabled)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nTrying to PHS Compress Using Classifier rule 0x%X",uiClassifierRuleID); + + + unPHSNewPktHeaderLen = unPHSPktHdrBytesCopied; + ulPhsStatus = PhsCompress(&Adapter->stBCMPhsContext, + Vcid, + uiClassifierRuleID, + pucPHSPktHdrInBuf, + pucPHSPktHdrOutBuf, + &unPhsOldHdrSize, + &unPHSNewPktHeaderLen); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nPHS Old header Size : %d New Header Size %d\n",unPhsOldHdrSize,unPHSNewPktHeaderLen); + + if(unPHSNewPktHeaderLen == unPhsOldHdrSize) + { + if( ulPhsStatus == STATUS_PHS_COMPRESSED) + bPHSI = *pucPHSPktHdrOutBuf; + ulPhsStatus = STATUS_PHS_NOCOMPRESSION; + } + + if( ulPhsStatus == STATUS_PHS_COMPRESSED) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"PHS Sending packet Compressed"); + + if(skb_cloned(Packet)) + { + newPacket = skb_copy(Packet, GFP_ATOMIC); + + if(newPacket == NULL) + return STATUS_FAILURE; + + dev_kfree_skb(Packet); + *pPacket = Packet = newPacket; + pucPHSPktHdrInBuf = Packet->data + BytesToRemove; + } + + numBytesCompressed = unPhsOldHdrSize - (unPHSNewPktHeaderLen+PHSI_LEN); + + memcpy(pucPHSPktHdrInBuf + numBytesCompressed, pucPHSPktHdrOutBuf, unPHSNewPktHeaderLen + PHSI_LEN); + memcpy(Packet->data + numBytesCompressed, Packet->data, BytesToRemove); + skb_pull(Packet, numBytesCompressed); + + return STATUS_SUCCESS; + } + + else + { + //if one byte headroom is not available, increase it through skb_cow + if(!(skb_headroom(Packet) > 0)) + { + if(skb_cow(Packet, 1)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "SKB Cow Failed\n"); + return STATUS_FAILURE; + } + } + skb_push(Packet, 1); + + // CAUTION: The MAC Header is getting corrupted here for IP CS - can be saved by copying 14 Bytes. not needed .... hence corrupting it. + *(Packet->data + BytesToRemove) = bPHSI; + return STATUS_SUCCESS; + } + } + else + { + if(!bHeaderSuppressionEnabled) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nHeader Suppression Disabled For SF: No PHS\n"); + } + + return STATUS_SUCCESS; + } + } + + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"PHSTransmit : Dumping data packet After PHS"); + return STATUS_SUCCESS; +} + +int PHSReceive(PMINI_ADAPTER Adapter, + USHORT usVcid, + struct sk_buff *packet, + UINT *punPacketLen, + UCHAR *pucEthernetHdr, + UINT bHeaderSuppressionEnabled) +{ + u32 nStandardPktHdrLen = 0; + u32 nTotalsupressedPktHdrBytes = 0; + int ulPhsStatus = 0; + PUCHAR pucInBuff = NULL ; + UINT TotalBytesAdded = 0; + if(!bHeaderSuppressionEnabled) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"\nPhs Disabled for incoming packet"); + return ulPhsStatus; + } + + pucInBuff = packet->data; + + //Restore PHS suppressed header + nStandardPktHdrLen = packet->len; + ulPhsStatus = PhsDeCompress(&Adapter->stBCMPhsContext, + usVcid, + pucInBuff, + Adapter->ucaPHSPktRestoreBuf, + &nTotalsupressedPktHdrBytes, + &nStandardPktHdrLen); + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"\nSupressed PktHdrLen : 0x%x Restored PktHdrLen : 0x%x", + nTotalsupressedPktHdrBytes,nStandardPktHdrLen); + + if(ulPhsStatus != STATUS_PHS_COMPRESSED) + { + skb_pull(packet, 1); + return STATUS_SUCCESS; + } + else + { + TotalBytesAdded = nStandardPktHdrLen - nTotalsupressedPktHdrBytes - PHSI_LEN; + if(TotalBytesAdded) + { + if(skb_headroom(packet) >= (SKB_RESERVE_ETHERNET_HEADER + TotalBytesAdded)) + skb_push(packet, TotalBytesAdded); + else + { + if(skb_cow(packet, skb_headroom(packet) + TotalBytesAdded)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "cow failed in receive\n"); + return STATUS_FAILURE; + } + + skb_push(packet, TotalBytesAdded); + } + } + + memcpy(packet->data, Adapter->ucaPHSPktRestoreBuf, nStandardPktHdrLen); + } + + return STATUS_SUCCESS; +} + +void DumpFullPacket(UCHAR *pBuf,UINT nPktLen) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL,"Dumping Data Packet"); + BCM_DEBUG_PRINT_BUFFER(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL,pBuf,nPktLen); +} + +//----------------------------------------------------------------------------- +// Procedure: phs_init +// +// Description: This routine is responsible for allocating memory for classifier and +// PHS rules. +// +// Arguments: +// pPhsdeviceExtension - ptr to Device extension containing PHS Classifier rules and PHS Rules , RX, TX buffer etc +// +// Returns: +// TRUE(1) -If allocation of memory was success full. +// FALSE -If allocation of memory fails. +//----------------------------------------------------------------------------- +int phs_init(PPHS_DEVICE_EXTENSION pPhsdeviceExtension,PMINI_ADAPTER Adapter) +{ + int i; + S_SERVICEFLOW_TABLE *pstServiceFlowTable; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "\nPHS:phs_init function "); + + if(pPhsdeviceExtension->pstServiceFlowPhsRulesTable) + return -EINVAL; + + pPhsdeviceExtension->pstServiceFlowPhsRulesTable = + kzalloc(sizeof(S_SERVICEFLOW_TABLE), GFP_KERNEL); + + if(!pPhsdeviceExtension->pstServiceFlowPhsRulesTable) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "\nAllocation ServiceFlowPhsRulesTable failed"); + return -ENOMEM; + } + + pstServiceFlowTable = pPhsdeviceExtension->pstServiceFlowPhsRulesTable; + for(i=0;istSFList[i]; + sServiceFlow.pstClassifierTable = kzalloc(sizeof(S_CLASSIFIER_TABLE), GFP_KERNEL); + if(!sServiceFlow.pstClassifierTable) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "\nAllocation failed"); + free_phs_serviceflow_rules(pPhsdeviceExtension-> + pstServiceFlowPhsRulesTable); + pPhsdeviceExtension->pstServiceFlowPhsRulesTable = NULL; + return -ENOMEM; + } + } + + pPhsdeviceExtension->CompressedTxBuffer = kmalloc(PHS_BUFFER_SIZE, GFP_KERNEL); + + if(pPhsdeviceExtension->CompressedTxBuffer == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "\nAllocation failed"); + free_phs_serviceflow_rules(pPhsdeviceExtension->pstServiceFlowPhsRulesTable); + pPhsdeviceExtension->pstServiceFlowPhsRulesTable = NULL; + return -ENOMEM; + } + + pPhsdeviceExtension->UnCompressedRxBuffer = kmalloc(PHS_BUFFER_SIZE, GFP_KERNEL); + if(pPhsdeviceExtension->UnCompressedRxBuffer == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "\nAllocation failed"); + kfree(pPhsdeviceExtension->CompressedTxBuffer); + free_phs_serviceflow_rules(pPhsdeviceExtension->pstServiceFlowPhsRulesTable); + pPhsdeviceExtension->pstServiceFlowPhsRulesTable = NULL; + return -ENOMEM; + } + + + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "\n phs_init Successfull"); + return STATUS_SUCCESS; +} + + +int PhsCleanup(IN PPHS_DEVICE_EXTENSION pPHSDeviceExt) +{ + if(pPHSDeviceExt->pstServiceFlowPhsRulesTable) + { + free_phs_serviceflow_rules(pPHSDeviceExt->pstServiceFlowPhsRulesTable); + pPHSDeviceExt->pstServiceFlowPhsRulesTable = NULL; + } + + kfree(pPHSDeviceExt->CompressedTxBuffer); + pPHSDeviceExt->CompressedTxBuffer = NULL; + + kfree(pPHSDeviceExt->UnCompressedRxBuffer); + pPHSDeviceExt->UnCompressedRxBuffer = NULL; + + return 0; +} + + + +//PHS functions +/*++ +PhsUpdateClassifierRule + +Routine Description: + Exported function to add or modify a PHS Rule. + +Arguments: + IN void* pvContext - PHS Driver Specific Context + IN B_UINT16 uiVcid - The Service Flow ID for which the PHS rule applies + IN B_UINT16 uiClsId - The Classifier ID within the Service Flow for which the PHS rule applies. + IN S_PHS_RULE *psPhsRule - The PHS Rule strcuture to be added to the PHS Rule table. + +Return Value: + + 0 if successful, + >0 Error. + +--*/ +ULONG PhsUpdateClassifierRule(IN void* pvContext, + IN B_UINT16 uiVcid , + IN B_UINT16 uiClsId , + IN S_PHS_RULE *psPhsRule, + IN B_UINT8 u8AssociatedPHSI) +{ + ULONG lStatus =0; + UINT nSFIndex =0 ; + S_SERVICEFLOW_ENTRY *pstServiceFlowEntry = NULL; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + + + PPHS_DEVICE_EXTENSION pDeviceExtension= (PPHS_DEVICE_EXTENSION)pvContext; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL,"PHS With Corr2 Changes \n"); + + if(pDeviceExtension == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL,"Invalid Device Extension\n"); + return ERR_PHS_INVALID_DEVICE_EXETENSION; + } + + + if(u8AssociatedPHSI == 0) + { + return ERR_PHS_INVALID_PHS_RULE; + } + + /* Retrieve the SFID Entry Index for requested Service Flow */ + + nSFIndex = GetServiceFlowEntry(pDeviceExtension->pstServiceFlowPhsRulesTable, + uiVcid,&pstServiceFlowEntry); + + if(nSFIndex == PHS_INVALID_TABLE_INDEX) + { + /* This is a new SF. Create a mapping entry for this */ + lStatus = CreateSFToClassifierRuleMapping(uiVcid, uiClsId, + pDeviceExtension->pstServiceFlowPhsRulesTable, psPhsRule, u8AssociatedPHSI); + return lStatus; + } + + /* SF already Exists Add PHS Rule to existing SF */ + lStatus = CreateClassiferToPHSRuleMapping(uiVcid, uiClsId, + pstServiceFlowEntry, psPhsRule, u8AssociatedPHSI); + + return lStatus; +} + +/*++ +PhsDeletePHSRule + +Routine Description: + Deletes the specified phs Rule within Vcid + +Arguments: + IN void* pvContext - PHS Driver Specific Context + IN B_UINT16 uiVcid - The Service Flow ID for which the PHS rule applies + IN B_UINT8 u8PHSI - the PHS Index identifying PHS rule to be deleted. + +Return Value: + + 0 if successful, + >0 Error. + +--*/ + +ULONG PhsDeletePHSRule(IN void* pvContext,IN B_UINT16 uiVcid,IN B_UINT8 u8PHSI) +{ + ULONG lStatus =0; + UINT nSFIndex =0, nClsidIndex =0 ; + S_SERVICEFLOW_ENTRY *pstServiceFlowEntry = NULL; + S_CLASSIFIER_TABLE *pstClassifierRulesTable = NULL; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + + PPHS_DEVICE_EXTENSION pDeviceExtension= (PPHS_DEVICE_EXTENSION)pvContext; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "======>\n"); + + if(pDeviceExtension) + { + + //Retrieve the SFID Entry Index for requested Service Flow + nSFIndex = GetServiceFlowEntry(pDeviceExtension + ->pstServiceFlowPhsRulesTable,uiVcid,&pstServiceFlowEntry); + + if(nSFIndex == PHS_INVALID_TABLE_INDEX) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "SFID Match Failed\n"); + return ERR_SF_MATCH_FAIL; + } + + pstClassifierRulesTable=pstServiceFlowEntry->pstClassifierTable; + if(pstClassifierRulesTable) + { + for(nClsidIndex=0;nClsidIndexstActivePhsRulesList[nClsidIndex].bUsed && pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex].pstPhsRule) + { + if(pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex].pstPhsRule->u8PHSI == u8PHSI) { + if(pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex].pstPhsRule->u8RefCnt) + pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex].pstPhsRule->u8RefCnt--; + if(0 == pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex].pstPhsRule->u8RefCnt) + kfree(pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex].pstPhsRule); + memset(&pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex], 0, + sizeof(S_CLASSIFIER_ENTRY)); + } + } + } + } + + } + return lStatus; +} + +/*++ +PhsDeleteClassifierRule + +Routine Description: + Exported function to Delete a PHS Rule for the SFID,CLSID Pair. + +Arguments: + IN void* pvContext - PHS Driver Specific Context + IN B_UINT16 uiVcid - The Service Flow ID for which the PHS rule applies + IN B_UINT16 uiClsId - The Classifier ID within the Service Flow for which the PHS rule applies. + +Return Value: + + 0 if successful, + >0 Error. + +--*/ +ULONG PhsDeleteClassifierRule(IN void* pvContext,IN B_UINT16 uiVcid ,IN B_UINT16 uiClsId) +{ + ULONG lStatus =0; + UINT nSFIndex =0, nClsidIndex =0 ; + S_SERVICEFLOW_ENTRY *pstServiceFlowEntry = NULL; + S_CLASSIFIER_ENTRY *pstClassifierEntry = NULL; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + PPHS_DEVICE_EXTENSION pDeviceExtension= (PPHS_DEVICE_EXTENSION)pvContext; + + if(pDeviceExtension) + { + //Retrieve the SFID Entry Index for requested Service Flow + nSFIndex = GetServiceFlowEntry(pDeviceExtension + ->pstServiceFlowPhsRulesTable, uiVcid, &pstServiceFlowEntry); + if(nSFIndex == PHS_INVALID_TABLE_INDEX) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL,"SFID Match Failed\n"); + return ERR_SF_MATCH_FAIL; + } + + nClsidIndex = GetClassifierEntry(pstServiceFlowEntry->pstClassifierTable, + uiClsId, eActiveClassifierRuleContext, &pstClassifierEntry); + if((nClsidIndex != PHS_INVALID_TABLE_INDEX) && (!pstClassifierEntry->bUnclassifiedPHSRule)) + { + if(pstClassifierEntry->pstPhsRule) + { + if(pstClassifierEntry->pstPhsRule->u8RefCnt) + pstClassifierEntry->pstPhsRule->u8RefCnt--; + if(0==pstClassifierEntry->pstPhsRule->u8RefCnt) + kfree(pstClassifierEntry->pstPhsRule); + + } + memset(pstClassifierEntry, 0, sizeof(S_CLASSIFIER_ENTRY)); + } + + nClsidIndex = GetClassifierEntry(pstServiceFlowEntry->pstClassifierTable, + uiClsId,eOldClassifierRuleContext,&pstClassifierEntry); + + if((nClsidIndex != PHS_INVALID_TABLE_INDEX) && (!pstClassifierEntry->bUnclassifiedPHSRule)) + { + kfree(pstClassifierEntry->pstPhsRule); + memset(pstClassifierEntry, 0, sizeof(S_CLASSIFIER_ENTRY)); + } + } + return lStatus; +} + +/*++ +PhsDeleteSFRules + +Routine Description: + Exported function to Delete a all PHS Rules for the SFID. + +Arguments: + IN void* pvContext - PHS Driver Specific Context + IN B_UINT16 uiVcid - The Service Flow ID for which the PHS rules need to be deleted + +Return Value: + + 0 if successful, + >0 Error. + +--*/ +ULONG PhsDeleteSFRules(IN void* pvContext,IN B_UINT16 uiVcid) +{ + + ULONG lStatus =0; + UINT nSFIndex =0, nClsidIndex =0 ; + S_SERVICEFLOW_ENTRY *pstServiceFlowEntry = NULL; + S_CLASSIFIER_TABLE *pstClassifierRulesTable = NULL; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + PPHS_DEVICE_EXTENSION pDeviceExtension= (PPHS_DEVICE_EXTENSION)pvContext; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL,"====> \n"); + + if(pDeviceExtension) + { + //Retrieve the SFID Entry Index for requested Service Flow + nSFIndex = GetServiceFlowEntry(pDeviceExtension->pstServiceFlowPhsRulesTable, + uiVcid,&pstServiceFlowEntry); + if(nSFIndex == PHS_INVALID_TABLE_INDEX) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "SFID Match Failed\n"); + return ERR_SF_MATCH_FAIL; + } + + pstClassifierRulesTable=pstServiceFlowEntry->pstClassifierTable; + if(pstClassifierRulesTable) + { + for(nClsidIndex=0;nClsidIndexstActivePhsRulesList[nClsidIndex].pstPhsRule) + { + if(pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex] + .pstPhsRule->u8RefCnt) + pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex] + .pstPhsRule->u8RefCnt--; + if(0==pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex] + .pstPhsRule->u8RefCnt) + kfree(pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex].pstPhsRule); + pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex] + .pstPhsRule = NULL; + } + memset(&pstClassifierRulesTable->stActivePhsRulesList[nClsidIndex], 0, sizeof(S_CLASSIFIER_ENTRY)); + if(pstClassifierRulesTable->stOldPhsRulesList[nClsidIndex].pstPhsRule) + { + if(pstClassifierRulesTable->stOldPhsRulesList[nClsidIndex] + .pstPhsRule->u8RefCnt) + pstClassifierRulesTable->stOldPhsRulesList[nClsidIndex] + .pstPhsRule->u8RefCnt--; + if(0 == pstClassifierRulesTable->stOldPhsRulesList[nClsidIndex] + .pstPhsRule->u8RefCnt) + kfree(pstClassifierRulesTable + ->stOldPhsRulesList[nClsidIndex].pstPhsRule); + pstClassifierRulesTable->stOldPhsRulesList[nClsidIndex] + .pstPhsRule = NULL; + } + memset(&pstClassifierRulesTable->stOldPhsRulesList[nClsidIndex], 0, sizeof(S_CLASSIFIER_ENTRY)); + } + } + pstServiceFlowEntry->bUsed = FALSE; + pstServiceFlowEntry->uiVcid = 0; + + } + + return lStatus; +} + + +/*++ +PhsCompress + +Routine Description: + Exported function to compress the data using PHS. + +Arguments: + IN void* pvContext - PHS Driver Specific Context. + IN B_UINT16 uiVcid - The Service Flow ID to which current packet header compression applies. + IN UINT uiClsId - The Classifier ID to which current packet header compression applies. + IN void *pvInputBuffer - The Input buffer containg packet header data + IN void *pvOutputBuffer - The output buffer returned by this function after PHS + IN UINT *pOldHeaderSize - The actual size of the header before PHS + IN UINT *pNewHeaderSize - The new size of the header after applying PHS + +Return Value: + + 0 if successful, + >0 Error. + +--*/ +ULONG PhsCompress(IN void* pvContext, + IN B_UINT16 uiVcid, + IN B_UINT16 uiClsId, + IN void *pvInputBuffer, + OUT void *pvOutputBuffer, + OUT UINT *pOldHeaderSize, + OUT UINT *pNewHeaderSize ) +{ + UINT nSFIndex =0, nClsidIndex =0 ; + S_SERVICEFLOW_ENTRY *pstServiceFlowEntry = NULL; + S_CLASSIFIER_ENTRY *pstClassifierEntry = NULL; + S_PHS_RULE *pstPhsRule = NULL; + ULONG lStatus =0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + + + PPHS_DEVICE_EXTENSION pDeviceExtension= (PPHS_DEVICE_EXTENSION)pvContext; + + + if(pDeviceExtension == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"Invalid Device Extension\n"); + lStatus = STATUS_PHS_NOCOMPRESSION ; + return lStatus; + + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"Suppressing header \n"); + + + //Retrieve the SFID Entry Index for requested Service Flow + nSFIndex = GetServiceFlowEntry(pDeviceExtension->pstServiceFlowPhsRulesTable, + uiVcid,&pstServiceFlowEntry); + if(nSFIndex == PHS_INVALID_TABLE_INDEX) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"SFID Match Failed\n"); + lStatus = STATUS_PHS_NOCOMPRESSION ; + return lStatus; + } + + nClsidIndex = GetClassifierEntry(pstServiceFlowEntry->pstClassifierTable, + uiClsId,eActiveClassifierRuleContext,&pstClassifierEntry); + + if(nClsidIndex == PHS_INVALID_TABLE_INDEX) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"No PHS Rule Defined For Classifier\n"); + lStatus = STATUS_PHS_NOCOMPRESSION ; + return lStatus; + } + + + //get rule from SF id,Cls ID pair and proceed + pstPhsRule = pstClassifierEntry->pstPhsRule; + + if(!ValidatePHSRuleComplete(pstPhsRule)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL,"PHS Rule Defined For Classifier But Not Complete\n"); + lStatus = STATUS_PHS_NOCOMPRESSION ; + return lStatus; + } + + //Compress Packet + lStatus = phs_compress(pstPhsRule,(PUCHAR)pvInputBuffer, + (PUCHAR)pvOutputBuffer, pOldHeaderSize,pNewHeaderSize); + + if(lStatus == STATUS_PHS_COMPRESSED) + { + pstPhsRule->PHSModifiedBytes += *pOldHeaderSize - *pNewHeaderSize - 1; + pstPhsRule->PHSModifiedNumPackets++; + } + else + pstPhsRule->PHSErrorNumPackets++; + + return lStatus; +} + +/*++ +PhsDeCompress + +Routine Description: + Exported function to restore the packet header in Rx path. + +Arguments: + IN void* pvContext - PHS Driver Specific Context. + IN B_UINT16 uiVcid - The Service Flow ID to which current packet header restoration applies. + IN void *pvInputBuffer - The Input buffer containg suppressed packet header data + OUT void *pvOutputBuffer - The output buffer returned by this function after restoration + OUT UINT *pHeaderSize - The packet header size after restoration is returned in this parameter. + +Return Value: + + 0 if successful, + >0 Error. + +--*/ +ULONG PhsDeCompress(IN void* pvContext, + IN B_UINT16 uiVcid, + IN void *pvInputBuffer, + OUT void *pvOutputBuffer, + OUT UINT *pInHeaderSize, + OUT UINT *pOutHeaderSize ) +{ + UINT nSFIndex =0, nPhsRuleIndex =0 ; + S_SERVICEFLOW_ENTRY *pstServiceFlowEntry = NULL; + S_PHS_RULE *pstPhsRule = NULL; + UINT phsi; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + PPHS_DEVICE_EXTENSION pDeviceExtension= + (PPHS_DEVICE_EXTENSION)pvContext; + + *pInHeaderSize = 0; + + if(pDeviceExtension == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"Invalid Device Extension\n"); + return ERR_PHS_INVALID_DEVICE_EXETENSION; + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"Restoring header\n"); + + phsi = *((unsigned char *)(pvInputBuffer)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"PHSI To Be Used For restore : %x\n",phsi); + if(phsi == UNCOMPRESSED_PACKET ) + { + return STATUS_PHS_NOCOMPRESSION; + } + + //Retrieve the SFID Entry Index for requested Service Flow + nSFIndex = GetServiceFlowEntry(pDeviceExtension->pstServiceFlowPhsRulesTable, + uiVcid,&pstServiceFlowEntry); + if(nSFIndex == PHS_INVALID_TABLE_INDEX) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"SFID Match Failed During Lookup\n"); + return ERR_SF_MATCH_FAIL; + } + + nPhsRuleIndex = GetPhsRuleEntry(pstServiceFlowEntry->pstClassifierTable,phsi, + eActiveClassifierRuleContext,&pstPhsRule); + if(nPhsRuleIndex == PHS_INVALID_TABLE_INDEX) + { + //Phs Rule does not exist in active rules table. Lets try in the old rules table. + nPhsRuleIndex = GetPhsRuleEntry(pstServiceFlowEntry->pstClassifierTable, + phsi,eOldClassifierRuleContext,&pstPhsRule); + if(nPhsRuleIndex == PHS_INVALID_TABLE_INDEX) + { + return ERR_PHSRULE_MATCH_FAIL; + } + + } + + *pInHeaderSize = phs_decompress((PUCHAR)pvInputBuffer, + (PUCHAR)pvOutputBuffer,pstPhsRule,pOutHeaderSize); + + pstPhsRule->PHSModifiedBytes += *pOutHeaderSize - *pInHeaderSize - 1; + + pstPhsRule->PHSModifiedNumPackets++; + return STATUS_PHS_COMPRESSED; +} + + +//----------------------------------------------------------------------------- +// Procedure: free_phs_serviceflow_rules +// +// Description: This routine is responsible for freeing memory allocated for PHS rules. +// +// Arguments: +// rules - ptr to S_SERVICEFLOW_TABLE structure. +// +// Returns: +// Does not return any value. +//----------------------------------------------------------------------------- + +static void free_phs_serviceflow_rules(S_SERVICEFLOW_TABLE *psServiceFlowRulesTable) +{ + int i,j; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "=======>\n"); + if(psServiceFlowRulesTable) + { + for(i=0;istSFList[i]; + S_CLASSIFIER_TABLE *pstClassifierRulesTable = + stServiceFlowEntry.pstClassifierTable; + + if(pstClassifierRulesTable) + { + for(j=0;jstActivePhsRulesList[j].pstPhsRule) + { + if(pstClassifierRulesTable->stActivePhsRulesList[j].pstPhsRule + ->u8RefCnt) + pstClassifierRulesTable->stActivePhsRulesList[j].pstPhsRule + ->u8RefCnt--; + if(0==pstClassifierRulesTable->stActivePhsRulesList[j].pstPhsRule + ->u8RefCnt) + kfree(pstClassifierRulesTable->stActivePhsRulesList[j].pstPhsRule); + pstClassifierRulesTable->stActivePhsRulesList[j].pstPhsRule = NULL; + } + if(pstClassifierRulesTable->stOldPhsRulesList[j].pstPhsRule) + { + if(pstClassifierRulesTable->stOldPhsRulesList[j].pstPhsRule + ->u8RefCnt) + pstClassifierRulesTable->stOldPhsRulesList[j].pstPhsRule + ->u8RefCnt--; + if(0==pstClassifierRulesTable->stOldPhsRulesList[j].pstPhsRule + ->u8RefCnt) + kfree(pstClassifierRulesTable->stOldPhsRulesList[j].pstPhsRule); + pstClassifierRulesTable->stOldPhsRulesList[j].pstPhsRule = NULL; + } + } + kfree(pstClassifierRulesTable); + stServiceFlowEntry.pstClassifierTable = pstClassifierRulesTable = NULL; + } + } + } + + kfree(psServiceFlowRulesTable); + psServiceFlowRulesTable = NULL; +} + + + +static BOOLEAN ValidatePHSRuleComplete(IN S_PHS_RULE *psPhsRule) +{ + if(psPhsRule) + { + if(!psPhsRule->u8PHSI) + { + // PHSI is not valid + return FALSE; + } + + if(!psPhsRule->u8PHSS) + { + //PHSS Is Undefined + return FALSE; + } + + //Check if PHSF is defines for the PHS Rule + if(!psPhsRule->u8PHSFLength) // If any part of PHSF is valid then Rule contains valid PHSF + { + return FALSE; + } + return TRUE; + } + else + { + return FALSE; + } +} + +UINT GetServiceFlowEntry(IN S_SERVICEFLOW_TABLE *psServiceFlowTable, + IN B_UINT16 uiVcid,S_SERVICEFLOW_ENTRY **ppstServiceFlowEntry) +{ + int i; + for(i=0;istSFList[i].bUsed) + { + if(psServiceFlowTable->stSFList[i].uiVcid == uiVcid) + { + *ppstServiceFlowEntry = &psServiceFlowTable->stSFList[i]; + return i; + } + } + } + + *ppstServiceFlowEntry = NULL; + return PHS_INVALID_TABLE_INDEX; +} + + +UINT GetClassifierEntry(IN S_CLASSIFIER_TABLE *pstClassifierTable, + IN B_UINT32 uiClsid,E_CLASSIFIER_ENTRY_CONTEXT eClsContext, + OUT S_CLASSIFIER_ENTRY **ppstClassifierEntry) +{ + int i; + S_CLASSIFIER_ENTRY *psClassifierRules = NULL; + for(i=0;istActivePhsRulesList[i]; + } + else + { + psClassifierRules = &pstClassifierTable->stOldPhsRulesList[i]; + } + + if(psClassifierRules->bUsed) + { + if(psClassifierRules->uiClassifierRuleId == uiClsid) + { + *ppstClassifierEntry = psClassifierRules; + return i; + } + } + + } + + *ppstClassifierEntry = NULL; + return PHS_INVALID_TABLE_INDEX; +} + +static UINT GetPhsRuleEntry(IN S_CLASSIFIER_TABLE *pstClassifierTable, + IN B_UINT32 uiPHSI,E_CLASSIFIER_ENTRY_CONTEXT eClsContext, + OUT S_PHS_RULE **ppstPhsRule) +{ + int i; + S_CLASSIFIER_ENTRY *pstClassifierRule = NULL; + for(i=0;istActivePhsRulesList[i]; + } + else + { + pstClassifierRule = &pstClassifierTable->stOldPhsRulesList[i]; + } + if(pstClassifierRule->bUsed) + { + if(pstClassifierRule->u8PHSI == uiPHSI) + { + *ppstPhsRule = pstClassifierRule->pstPhsRule; + return i; + } + } + + } + + *ppstPhsRule = NULL; + return PHS_INVALID_TABLE_INDEX; +} + +UINT CreateSFToClassifierRuleMapping(IN B_UINT16 uiVcid,IN B_UINT16 uiClsId, + IN S_SERVICEFLOW_TABLE *psServiceFlowTable,S_PHS_RULE *psPhsRule, + B_UINT8 u8AssociatedPHSI) +{ + + S_CLASSIFIER_TABLE *psaClassifiertable = NULL; + UINT uiStatus = 0; + int iSfIndex; + BOOLEAN bFreeEntryFound =FALSE; + //Check for a free entry in SFID table + for(iSfIndex=0;iSfIndex < MAX_SERVICEFLOWS;iSfIndex++) + { + if(!psServiceFlowTable->stSFList[iSfIndex].bUsed) + { + bFreeEntryFound = TRUE; + break; + } + } + + if(!bFreeEntryFound) + return ERR_SFTABLE_FULL; + + + psaClassifiertable = psServiceFlowTable->stSFList[iSfIndex].pstClassifierTable; + uiStatus = CreateClassifierPHSRule(uiClsId,psaClassifiertable,psPhsRule, + eActiveClassifierRuleContext,u8AssociatedPHSI); + if(uiStatus == PHS_SUCCESS) + { + //Add entry at free index to the SF + psServiceFlowTable->stSFList[iSfIndex].bUsed = TRUE; + psServiceFlowTable->stSFList[iSfIndex].uiVcid = uiVcid; + } + + return uiStatus; + +} + +UINT CreateClassiferToPHSRuleMapping(IN B_UINT16 uiVcid, + IN B_UINT16 uiClsId,IN S_SERVICEFLOW_ENTRY *pstServiceFlowEntry, + S_PHS_RULE *psPhsRule,B_UINT8 u8AssociatedPHSI) +{ + S_CLASSIFIER_ENTRY *pstClassifierEntry = NULL; + UINT uiStatus =PHS_SUCCESS; + UINT nClassifierIndex = 0; + S_CLASSIFIER_TABLE *psaClassifiertable = NULL; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + psaClassifiertable = pstServiceFlowEntry->pstClassifierTable; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "==>"); + + /* Check if the supplied Classifier already exists */ + nClassifierIndex =GetClassifierEntry( + pstServiceFlowEntry->pstClassifierTable,uiClsId, + eActiveClassifierRuleContext,&pstClassifierEntry); + if(nClassifierIndex == PHS_INVALID_TABLE_INDEX) + { + /* + The Classifier doesn't exist. So its a new classifier being added. + Add new entry to associate PHS Rule to the Classifier + */ + + uiStatus = CreateClassifierPHSRule(uiClsId,psaClassifiertable, + psPhsRule,eActiveClassifierRuleContext,u8AssociatedPHSI); + return uiStatus; + } + + /* + The Classifier exists.The PHS Rule for this classifier + is being modified + */ + if(pstClassifierEntry->u8PHSI == psPhsRule->u8PHSI) + { + if(pstClassifierEntry->pstPhsRule == NULL) + return ERR_PHS_INVALID_PHS_RULE; + + /* + This rule already exists if any fields are changed for this PHS + rule update them. + */ + /* If any part of PHSF is valid then we update PHSF */ + if(psPhsRule->u8PHSFLength) + { + //update PHSF + memcpy(pstClassifierEntry->pstPhsRule->u8PHSF, + psPhsRule->u8PHSF , MAX_PHS_LENGTHS); + } + if(psPhsRule->u8PHSFLength) + { + //update PHSFLen + pstClassifierEntry->pstPhsRule->u8PHSFLength = + psPhsRule->u8PHSFLength; + } + if(psPhsRule->u8PHSMLength) + { + //update PHSM + memcpy(pstClassifierEntry->pstPhsRule->u8PHSM, + psPhsRule->u8PHSM, MAX_PHS_LENGTHS); + } + if(psPhsRule->u8PHSMLength) + { + //update PHSM Len + pstClassifierEntry->pstPhsRule->u8PHSMLength = + psPhsRule->u8PHSMLength; + } + if(psPhsRule->u8PHSS) + { + //update PHSS + pstClassifierEntry->pstPhsRule->u8PHSS = psPhsRule->u8PHSS; + } + + //update PHSV + pstClassifierEntry->pstPhsRule->u8PHSV = psPhsRule->u8PHSV; + + } + else + { + /* + A new rule is being set for this classifier. + */ + uiStatus=UpdateClassifierPHSRule( uiClsId, pstClassifierEntry, + psaClassifiertable, psPhsRule, u8AssociatedPHSI); + } + + + + return uiStatus; +} + +static UINT CreateClassifierPHSRule(IN B_UINT16 uiClsId, + S_CLASSIFIER_TABLE *psaClassifiertable ,S_PHS_RULE *psPhsRule, + E_CLASSIFIER_ENTRY_CONTEXT eClsContext,B_UINT8 u8AssociatedPHSI) +{ + UINT iClassifierIndex = 0; + BOOLEAN bFreeEntryFound = FALSE; + S_CLASSIFIER_ENTRY *psClassifierRules = NULL; + UINT nStatus = PHS_SUCCESS; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL,"Inside CreateClassifierPHSRule"); + if(psaClassifiertable == NULL) + { + return ERR_INVALID_CLASSIFIERTABLE_FOR_SF; + } + + if(eClsContext == eOldClassifierRuleContext) + { + /* If An Old Entry for this classifier ID already exists in the + old rules table replace it. */ + + iClassifierIndex = + GetClassifierEntry(psaClassifiertable, uiClsId, + eClsContext,&psClassifierRules); + if(iClassifierIndex != PHS_INVALID_TABLE_INDEX) + { + /* + The Classifier already exists in the old rules table + Lets replace the old classifier with the new one. + */ + bFreeEntryFound = TRUE; + } + } + + if(!bFreeEntryFound) + { + /* + Continue to search for a free location to add the rule + */ + for(iClassifierIndex = 0; iClassifierIndex < + MAX_PHSRULE_PER_SF; iClassifierIndex++) + { + if(eClsContext == eActiveClassifierRuleContext) + { + psClassifierRules = + &psaClassifiertable->stActivePhsRulesList[iClassifierIndex]; + } + else + { + psClassifierRules = + &psaClassifiertable->stOldPhsRulesList[iClassifierIndex]; + } + + if(!psClassifierRules->bUsed) + { + bFreeEntryFound = TRUE; + break; + } + } + } + + if(!bFreeEntryFound) + { + if(eClsContext == eActiveClassifierRuleContext) + { + return ERR_CLSASSIFIER_TABLE_FULL; + } + else + { + //Lets replace the oldest rule if we are looking in old Rule table + if(psaClassifiertable->uiOldestPhsRuleIndex >= + MAX_PHSRULE_PER_SF) + { + psaClassifiertable->uiOldestPhsRuleIndex =0; + } + + iClassifierIndex = psaClassifiertable->uiOldestPhsRuleIndex; + psClassifierRules = + &psaClassifiertable->stOldPhsRulesList[iClassifierIndex]; + + (psaClassifiertable->uiOldestPhsRuleIndex)++; + } + } + + if(eClsContext == eOldClassifierRuleContext) + { + if(psClassifierRules->pstPhsRule == NULL) + { + psClassifierRules->pstPhsRule = kmalloc(sizeof(S_PHS_RULE),GFP_KERNEL); + + if(NULL == psClassifierRules->pstPhsRule) + return ERR_PHSRULE_MEMALLOC_FAIL; + } + + psClassifierRules->bUsed = TRUE; + psClassifierRules->uiClassifierRuleId = uiClsId; + psClassifierRules->u8PHSI = psPhsRule->u8PHSI; + psClassifierRules->bUnclassifiedPHSRule = psPhsRule->bUnclassifiedPHSRule; + + /* Update The PHS rule */ + memcpy(psClassifierRules->pstPhsRule, + psPhsRule, sizeof(S_PHS_RULE)); + } + else + { + nStatus = UpdateClassifierPHSRule(uiClsId,psClassifierRules, + psaClassifiertable,psPhsRule,u8AssociatedPHSI); + } + return nStatus; +} + + +static UINT UpdateClassifierPHSRule(IN B_UINT16 uiClsId, + IN S_CLASSIFIER_ENTRY *pstClassifierEntry, + S_CLASSIFIER_TABLE *psaClassifiertable ,S_PHS_RULE *psPhsRule, + B_UINT8 u8AssociatedPHSI) +{ + S_PHS_RULE *pstAddPhsRule = NULL; + UINT nPhsRuleIndex = 0; + BOOLEAN bPHSRuleOrphaned = FALSE; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + psPhsRule->u8RefCnt =0; + + /* Step 1 Deref Any Exisiting PHS Rule in this classifier Entry*/ + bPHSRuleOrphaned = DerefPhsRule( uiClsId, psaClassifiertable, + pstClassifierEntry->pstPhsRule); + + //Step 2 Search if there is a PHS Rule with u8AssociatedPHSI in Classifier table for this SF + nPhsRuleIndex =GetPhsRuleEntry(psaClassifiertable,u8AssociatedPHSI, + eActiveClassifierRuleContext, &pstAddPhsRule); + if(PHS_INVALID_TABLE_INDEX == nPhsRuleIndex) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "\nAdding New PHSRuleEntry For Classifier"); + + if(psPhsRule->u8PHSI == 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "\nError PHSI is Zero\n"); + return ERR_PHS_INVALID_PHS_RULE; + } + //Step 2.a PHS Rule Does Not Exist .Create New PHS Rule for uiClsId + if(FALSE == bPHSRuleOrphaned) + { + pstClassifierEntry->pstPhsRule = kmalloc(sizeof(S_PHS_RULE), GFP_KERNEL); + if(NULL == pstClassifierEntry->pstPhsRule) + { + return ERR_PHSRULE_MEMALLOC_FAIL; + } + } + memcpy(pstClassifierEntry->pstPhsRule, psPhsRule, sizeof(S_PHS_RULE)); + + } + else + { + //Step 2.b PHS Rule Exists Tie uiClsId with the existing PHS Rule + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_DISPATCH, DBG_LVL_ALL, "\nTying Classifier to Existing PHS Rule"); + if(bPHSRuleOrphaned) + { + kfree(pstClassifierEntry->pstPhsRule); + pstClassifierEntry->pstPhsRule = NULL; + } + pstClassifierEntry->pstPhsRule = pstAddPhsRule; + + } + pstClassifierEntry->bUsed = TRUE; + pstClassifierEntry->u8PHSI = pstClassifierEntry->pstPhsRule->u8PHSI; + pstClassifierEntry->uiClassifierRuleId = uiClsId; + pstClassifierEntry->pstPhsRule->u8RefCnt++; + pstClassifierEntry->bUnclassifiedPHSRule = pstClassifierEntry->pstPhsRule->bUnclassifiedPHSRule; + + return PHS_SUCCESS; + +} + +static BOOLEAN DerefPhsRule(IN B_UINT16 uiClsId,S_CLASSIFIER_TABLE *psaClassifiertable,S_PHS_RULE *pstPhsRule) +{ + if(pstPhsRule==NULL) + return FALSE; + if(pstPhsRule->u8RefCnt) + pstPhsRule->u8RefCnt--; + if(0==pstPhsRule->u8RefCnt) + { + /*if(pstPhsRule->u8PHSI) + //Store the currently active rule into the old rules list + CreateClassifierPHSRule(uiClsId,psaClassifiertable,pstPhsRule,eOldClassifierRuleContext,pstPhsRule->u8PHSI);*/ + return TRUE; + } + else + { + return FALSE; + } +} + +void DumpPhsRules(PPHS_DEVICE_EXTENSION pDeviceExtension) +{ + int i,j,k,l; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\n Dumping PHS Rules : \n"); + for(i=0;ipstServiceFlowPhsRulesTable->stSFList[i]; + if(stServFlowEntry.bUsed) + { + for(j=0;jstActivePhsRulesList[j]; + if(stClsEntry.bUsed) + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n Active PHS Rule : \n"); + } + else + { + stClsEntry = stServFlowEntry.pstClassifierTable->stOldPhsRulesList[j]; + if(stClsEntry.bUsed) + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n Old PHS Rule : \n"); + } + if(stClsEntry.bUsed) + { + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\n VCID : %#X",stServFlowEntry.uiVcid); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n ClassifierID : %#X",stClsEntry.uiClassifierRuleId); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n PHSRuleID : %#X",stClsEntry.u8PHSI); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n****************PHS Rule********************\n"); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n PHSI : %#X",stClsEntry.pstPhsRule->u8PHSI); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n PHSFLength : %#X ",stClsEntry.pstPhsRule->u8PHSFLength); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n PHSF : "); + for(k=0;ku8PHSFLength;k++) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "%#X ",stClsEntry.pstPhsRule->u8PHSF[k]); + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n PHSMLength : %#X",stClsEntry.pstPhsRule->u8PHSMLength); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n PHSM :"); + for(k=0;ku8PHSMLength;k++) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "%#X ",stClsEntry.pstPhsRule->u8PHSM[k]); + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n PHSS : %#X ",stClsEntry.pstPhsRule->u8PHSS); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, (DBG_LVL_ALL|DBG_NO_FUNC_PRINT), "\n PHSV : %#X",stClsEntry.pstPhsRule->u8PHSV); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "\n********************************************\n"); + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +// Procedure: phs_decompress +// +// Description: This routine restores the static fields within the packet. +// +// Arguments: +// in_buf - ptr to incoming packet buffer. +// out_buf - ptr to output buffer where the suppressed header is copied. +// decomp_phs_rules - ptr to PHS rule. +// header_size - ptr to field which holds the phss or phsf_length. +// +// Returns: +// size -The number of bytes of dynamic fields present with in the incoming packet +// header. +// 0 -If PHS rule is NULL.If PHSI is 0 indicateing packet as uncompressed. +//----------------------------------------------------------------------------- + +int phs_decompress(unsigned char *in_buf,unsigned char *out_buf, + S_PHS_RULE *decomp_phs_rules,UINT *header_size) +{ + int phss,size=0; + S_PHS_RULE *tmp_memb; + int bit,i=0; + unsigned char *phsf,*phsm; + int in_buf_len = *header_size-1; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + in_buf++; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"====>\n"); + *header_size = 0; + + if((decomp_phs_rules == NULL )) + return 0; + + + tmp_memb = decomp_phs_rules; + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"\nDECOMP:In phs_decompress PHSI 1 %d",phsi)); + //*header_size = tmp_memb->u8PHSFLength; + phss = tmp_memb->u8PHSS; + phsf = tmp_memb->u8PHSF; + phsm = tmp_memb->u8PHSM; + + if(phss > MAX_PHS_LENGTHS) + phss = MAX_PHS_LENGTHS; + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"\nDECOMP:In phs_decompress PHSI %d phss %d index %d",phsi,phss,index)); + while((phss > 0) && (size < in_buf_len)) + { + bit = ((*phsm << i)& SUPPRESS); + + if(bit == SUPPRESS) + { + *out_buf = *phsf; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"\nDECOMP:In phss %d phsf %d ouput %d", + phss,*phsf,*out_buf); + } + else + { + *out_buf = *in_buf; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_RECEIVE,DBG_LVL_ALL,"\nDECOMP:In phss %d input %d ouput %d", + phss,*in_buf,*out_buf); + in_buf++; + size++; + } + out_buf++; + phsf++; + phss--; + i++; + *header_size=*header_size + 1; + + if(i > MAX_NO_BIT) + { + i=0; + phsm++; + } + } + return size; +} + + + + +//----------------------------------------------------------------------------- +// Procedure: phs_compress +// +// Description: This routine suppresses the static fields within the packet.Before +// that it will verify the fields to be suppressed with the corresponding fields in the +// phsf. For verification it checks the phsv field of PHS rule. If set and verification +// succeeds it suppresses the field.If any one static field is found different none of +// the static fields are suppressed then the packet is sent as uncompressed packet with +// phsi=0. +// +// Arguments: +// phs_rule - ptr to PHS rule. +// in_buf - ptr to incoming packet buffer. +// out_buf - ptr to output buffer where the suppressed header is copied. +// header_size - ptr to field which holds the phss. +// +// Returns: +// size-The number of bytes copied into the output buffer i.e dynamic fields +// 0 -If PHS rule is NULL.If PHSV field is not set.If the verification fails. +//----------------------------------------------------------------------------- +static int phs_compress(S_PHS_RULE *phs_rule,unsigned char *in_buf + ,unsigned char *out_buf,UINT *header_size,UINT *new_header_size) +{ + unsigned char *old_addr = out_buf; + int supress = 0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + if(phs_rule == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nphs_compress(): phs_rule null!"); + *out_buf = ZERO_PHSI; + return STATUS_PHS_NOCOMPRESSION; + } + + + if(phs_rule->u8PHSS <= *new_header_size) + { + *header_size = phs_rule->u8PHSS; + } + else + { + *header_size = *new_header_size; + } + //To copy PHSI + out_buf++; + supress = verify_suppress_phsf(in_buf,out_buf,phs_rule->u8PHSF, + phs_rule->u8PHSM, phs_rule->u8PHSS, phs_rule->u8PHSV,new_header_size); + + if(supress == STATUS_PHS_COMPRESSED) + { + *old_addr = (unsigned char)phs_rule->u8PHSI; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nCOMP:In phs_compress phsi %d",phs_rule->u8PHSI); + } + else + { + *old_addr = ZERO_PHSI; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nCOMP:In phs_compress PHSV Verification failed"); + } + return supress; +} + + +//----------------------------------------------------------------------------- +// Procedure: verify_suppress_phsf +// +// Description: This routine verifies the fields of the packet and if all the +// static fields are equal it adds the phsi of that PHS rule.If any static +// field differs it woun't suppress any field. +// +// Arguments: +// rules_set - ptr to classifier_rules. +// in_buffer - ptr to incoming packet buffer. +// out_buffer - ptr to output buffer where the suppressed header is copied. +// phsf - ptr to phsf. +// phsm - ptr to phsm. +// phss - variable holding phss. +// +// Returns: +// size-The number of bytes copied into the output buffer i.e dynamic fields. +// 0 -Packet has failed the verification. +//----------------------------------------------------------------------------- + +static int verify_suppress_phsf(unsigned char *in_buffer,unsigned char *out_buffer, + unsigned char *phsf,unsigned char *phsm,unsigned int phss, + unsigned int phsv,UINT* new_header_size) +{ + unsigned int size=0; + int bit,i=0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nCOMP:In verify_phsf PHSM - 0x%X",*phsm); + + + if(phss>(*new_header_size)) + { + phss=*new_header_size; + } + while(phss > 0) + { + bit = ((*phsm << i)& SUPPRESS); + if(bit == SUPPRESS) + { + + if(*in_buffer != *phsf) + { + if(phsv == VERIFY) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nCOMP:In verify_phsf failed for field %d buf %d phsf %d",phss,*in_buffer,*phsf); + return STATUS_PHS_NOCOMPRESSION; + } + } + else + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nCOMP:In verify_phsf success for field %d buf %d phsf %d",phss,*in_buffer,*phsf); + } + else + { + *out_buffer = *in_buffer; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nCOMP:In copying_header input %d out %d",*in_buffer,*out_buffer); + out_buffer++; + size++; + } + in_buffer++; + phsf++; + phss--; + i++; + if(i > MAX_NO_BIT) + { + i=0; + phsm++; + } + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, PHS_SEND, DBG_LVL_ALL,"\nCOMP:In verify_phsf success"); + *new_header_size = size; + return STATUS_PHS_COMPRESSED; +} + + + + + + diff --git a/drivers/staging/bcm/PHSModule.h b/drivers/staging/bcm/PHSModule.h new file mode 100644 index 00000000..c629585d --- /dev/null +++ b/drivers/staging/bcm/PHSModule.h @@ -0,0 +1,50 @@ +#ifndef BCM_MINIPORT_PHSMODULE_H +#define BCM_MINIPORT_PHSMODULE_H + +int PHSTransmit(PMINI_ADAPTER Adapter, + struct sk_buff **pPacket, + USHORT Vcid, + B_UINT16 uiClassifierRuleID, + BOOLEAN bHeaderSuppressionEnabled, + PUINT PacketLen, + UCHAR bEthCSSupport); + +int PHSReceive(PMINI_ADAPTER Adapter, + USHORT usVcid, + struct sk_buff *packet, + UINT *punPacketLen, + UCHAR *pucEthernetHdr, + UINT + ); + + +void DumpDataPacketHeader(PUCHAR pPkt); + +void DumpFullPacket(UCHAR *pBuf,UINT nPktLen); + +void DumpPhsRules(PPHS_DEVICE_EXTENSION pDeviceExtension); + + +int phs_init(PPHS_DEVICE_EXTENSION pPhsdeviceExtension,PMINI_ADAPTER Adapter); + +int PhsCleanup(PPHS_DEVICE_EXTENSION pPHSDeviceExt); + +//Utility Functions +ULONG PhsUpdateClassifierRule(void* pvContext,B_UINT16 uiVcid,B_UINT16 uiClsId,S_PHS_RULE *psPhsRule,B_UINT8 u8AssociatedPHSI ); + +ULONG PhsDeletePHSRule(void* pvContext,B_UINT16 uiVcid,B_UINT8 u8PHSI); + +ULONG PhsDeleteClassifierRule(void* pvContext, B_UINT16 uiVcid ,B_UINT16 uiClsId); + +ULONG PhsDeleteSFRules(void* pvContext,B_UINT16 uiVcid) ; + + +BOOLEAN ValidatePHSRule(S_PHS_RULE *psPhsRule); + +UINT GetServiceFlowEntry(S_SERVICEFLOW_TABLE *psServiceFlowTable,B_UINT16 uiVcid,S_SERVICEFLOW_ENTRY **ppstServiceFlowEntry); + + +void DumpPhsRules(PPHS_DEVICE_EXTENSION pDeviceExtension); + + +#endif diff --git a/drivers/staging/bcm/Protocol.h b/drivers/staging/bcm/Protocol.h new file mode 100644 index 00000000..b8a4009b --- /dev/null +++ b/drivers/staging/bcm/Protocol.h @@ -0,0 +1,151 @@ +/************************************ +* Protocol.h +*************************************/ +#ifndef __PROTOCOL_H__ +#define __PROTOCOL_H__ + + +#define IPV4 4 +#define IPV6 6 + + +struct ArpHeader { + struct arphdr arp; + unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */ + unsigned char ar_sip[4]; /* sender IP address */ + unsigned char ar_tha[ETH_ALEN]; /* target hardware address */ + unsigned char ar_tip[4]; /* target IP address */ +}/*__attribute__((packed))*/; + + +struct TransportHeaderT +{ + union + { + struct udphdr uhdr; + struct tcphdr thdr; + }; +} __attribute__((packed)); +typedef struct TransportHeaderT xporthdr; + + +typedef enum _E_NWPKT_IPFRAME_TYPE +{ + eNonIPPacket, + eIPv4Packet, + eIPv6Packet +}E_NWPKT_IPFRAME_TYPE; + +typedef enum _E_NWPKT_ETHFRAME_TYPE +{ + eEthUnsupportedFrame, + eEth802LLCFrame, + eEth802LLCSNAPFrame, + eEth802QVLANFrame, + eEthOtherFrame +} E_NWPKT_ETHFRAME_TYPE; + +typedef struct _S_ETHCS_PKT_INFO +{ + E_NWPKT_IPFRAME_TYPE eNwpktIPFrameType; + E_NWPKT_ETHFRAME_TYPE eNwpktEthFrameType; + USHORT usEtherType; + UCHAR ucDSAP; +}S_ETHCS_PKT_INFO,*PS_ETHCS_PKT_INFO; + +typedef struct _ETH_CS_802_Q_FRAME +{ + ETH_HEADER_STRUC EThHdr; + USHORT UserPriority:3; + USHORT CFI:1; + USHORT VLANID:12; + USHORT EthType; +} __attribute__((packed)) ETH_CS_802_Q_FRAME; + +typedef struct _ETH_CS_802_LLC_FRAME +{ + ETH_HEADER_STRUC EThHdr; + unsigned char DSAP; + unsigned char SSAP; + unsigned char Control; +}__attribute__((packed)) ETH_CS_802_LLC_FRAME; + +typedef struct _ETH_CS_802_LLC_SNAP_FRAME +{ + ETH_HEADER_STRUC EThHdr; + unsigned char DSAP; + unsigned char SSAP; + unsigned char Control; + unsigned char OUI[3]; + unsigned short usEtherType; +} __attribute__((packed)) ETH_CS_802_LLC_SNAP_FRAME; + +typedef struct _ETH_CS_ETH2_FRAME +{ + ETH_HEADER_STRUC EThHdr; +} __attribute__((packed)) ETH_CS_ETH2_FRAME; + +#define ETHERNET_FRAMETYPE_IPV4 ntohs(0x0800) +#define ETHERNET_FRAMETYPE_IPV6 ntohs(0x86dd) +#define ETHERNET_FRAMETYPE_802QVLAN ntohs(0x8100) + +//Per SF CS Specification Encodings +typedef enum _E_SERVICEFLOW_CS_SPEC_ +{ + eCSSpecUnspecified =0, + eCSPacketIPV4, + eCSPacketIPV6, + eCS802_3PacketEthernet, + eCS802_1QPacketVLAN, + eCSPacketIPV4Over802_3Ethernet, + eCSPacketIPV6Over802_3Ethernet, + eCSPacketIPV4Over802_1QVLAN, + eCSPacketIPV6Over802_1QVLAN, + eCSPacketUnsupported +}E_SERVICEFLOW_CS_SPEC; + + +#define IP6_HEADER_LEN 40 + +#define IP_VERSION(byte) (((byte&0xF0)>>4)) + + + +#define MAC_ADDRESS_SIZE 6 +#define ETH_AND_IP_HEADER_LEN 14 + 20 +#define L4_SRC_PORT_LEN 2 +#define L4_DEST_PORT_LEN 2 + + + +#define CTRL_PKT_LEN 8 + ETH_AND_IP_HEADER_LEN + +#define ETH_ARP_FRAME 0x806 +#define ETH_IPV4_FRAME 0x800 +#define ETH_IPV6_FRAME 0x86DD +#define UDP 0x11 +#define TCP 0x06 + +#define ARP_OP_REQUEST 0x01 +#define ARP_OP_REPLY 0x02 +#define ARP_PKT_SIZE 60 + +// This is the format for the TCP packet header +typedef struct _TCP_HEADER +{ + USHORT usSrcPort; + USHORT usDestPort; + ULONG ulSeqNumber; + ULONG ulAckNumber; + UCHAR HeaderLength; + UCHAR ucFlags; + USHORT usWindowsSize; + USHORT usChkSum; + USHORT usUrgetPtr; +} TCP_HEADER,*PTCP_HEADER; +#define TCP_HEADER_LEN sizeof(TCP_HEADER) +#define TCP_ACK 0x10 //Bit 4 in tcpflags field. +#define GET_TCP_HEADER_LEN(byte) ((byte&0xF0)>>4) + + +#endif //__PROTOCOL_H__ diff --git a/drivers/staging/bcm/Prototypes.h b/drivers/staging/bcm/Prototypes.h new file mode 100644 index 00000000..b80b806c --- /dev/null +++ b/drivers/staging/bcm/Prototypes.h @@ -0,0 +1,222 @@ +#ifndef _PROTOTYPES_H_ +#define _PROTOTYPES_H_ + +VOID LinkControlResponseMessage(PMINI_ADAPTER Adapter, PUCHAR pucBuffer); + +VOID StatisticsResponse(PMINI_ADAPTER Adapter,PVOID pvBuffer); + +VOID IdleModeResponse(PMINI_ADAPTER Adapter,PUINT puiBuffer); + +int control_packet_handler (PMINI_ADAPTER Adapter); + +VOID DeleteAllClassifiersForSF(PMINI_ADAPTER Adapter,UINT uiSearchRuleIndex); + +VOID flush_all_queues(PMINI_ADAPTER Adapter); + +int register_control_device_interface(PMINI_ADAPTER ps_adapter); + +void unregister_control_device_interface(PMINI_ADAPTER Adapter); + +INT CopyBufferToControlPacket(PMINI_ADAPTER Adapter,/**ucIPSourceAddressLength) + return TRUE; + for(ucLoopIndex=0; ucLoopIndex < (pstClassifierRule->ucIPSourceAddressLength);ucLoopIndex++) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Src Ip Address Mask:0x%x PacketIp:0x%x and Classification:0x%x", (UINT)pstClassifierRule->stSrcIpAddress.ulIpv4Mask[ucLoopIndex], (UINT)ulSrcIP, (UINT)pstClassifierRule->stSrcIpAddress.ulIpv6Addr[ucLoopIndex]); + if((pstClassifierRule->stSrcIpAddress.ulIpv4Mask[ucLoopIndex] & ulSrcIP)== + (pstClassifierRule->stSrcIpAddress.ulIpv4Addr[ucLoopIndex] & pstClassifierRule->stSrcIpAddress.ulIpv4Mask[ucLoopIndex] )) + { + return TRUE; + } + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Src Ip Address Not Matched"); + return FALSE; +} + + +/******************************************************************* +* Function - MatchDestIpAddress() +* +* Description - Checks whether the Destination IP address from the packet +* matches with that of Queue. +* +* Parameters - pstClassifierRule: Pointer to the packet info structure. +* - ulDestIP : Destination IP address from the packet. +* +* Returns - TRUE(If address matches) else FAIL . +*********************************************************************/ +BOOLEAN MatchDestIpAddress(S_CLASSIFIER_RULE *pstClassifierRule,ULONG ulDestIP) +{ + UCHAR ucLoopIndex=0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + ulDestIP=ntohl(ulDestIP); + if(0 == pstClassifierRule->ucIPDestinationAddressLength) + return TRUE; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Destination Ip Address 0x%x 0x%x 0x%x ", (UINT)ulDestIP, (UINT)pstClassifierRule->stDestIpAddress.ulIpv4Mask[ucLoopIndex], (UINT)pstClassifierRule->stDestIpAddress.ulIpv4Addr[ucLoopIndex]); + + for(ucLoopIndex=0;ucLoopIndex<(pstClassifierRule->ucIPDestinationAddressLength);ucLoopIndex++) + { + if((pstClassifierRule->stDestIpAddress.ulIpv4Mask[ucLoopIndex] & ulDestIP)== + (pstClassifierRule->stDestIpAddress.ulIpv4Addr[ucLoopIndex] & pstClassifierRule->stDestIpAddress.ulIpv4Mask[ucLoopIndex])) + { + return TRUE; + } + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Destination Ip Address Not Matched"); + return FALSE; +} + + +/************************************************************************ +* Function - MatchTos() +* +* Description - Checks the TOS from the packet matches with that of queue. +* +* Parameters - pstClassifierRule : Pointer to the packet info structure. +* - ucTypeOfService: TOS from the packet. +* +* Returns - TRUE(If address matches) else FAIL. +**************************************************************************/ +BOOLEAN MatchTos(S_CLASSIFIER_RULE *pstClassifierRule,UCHAR ucTypeOfService) +{ + + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + if( 3 != pstClassifierRule->ucIPTypeOfServiceLength ) + return TRUE; + + if(((pstClassifierRule->ucTosMask & ucTypeOfService)<=pstClassifierRule->ucTosHigh) && ((pstClassifierRule->ucTosMask & ucTypeOfService)>=pstClassifierRule->ucTosLow)) + { + return TRUE; + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Type Of Service Not Matched"); + return FALSE; +} + + +/*************************************************************************** +* Function - MatchProtocol() +* +* Description - Checks the protocol from the packet matches with that of queue. +* +* Parameters - pstClassifierRule: Pointer to the packet info structure. +* - ucProtocol : Protocol from the packet. +* +* Returns - TRUE(If address matches) else FAIL. +****************************************************************************/ +BOOLEAN MatchProtocol(S_CLASSIFIER_RULE *pstClassifierRule,UCHAR ucProtocol) +{ + UCHAR ucLoopIndex=0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + if(0 == pstClassifierRule->ucProtocolLength) + return TRUE; + for(ucLoopIndex=0;ucLoopIndexucProtocolLength;ucLoopIndex++) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Protocol:0x%X Classification Protocol:0x%X",ucProtocol,pstClassifierRule->ucProtocol[ucLoopIndex]); + if(pstClassifierRule->ucProtocol[ucLoopIndex]==ucProtocol) + { + return TRUE; + } + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Protocol Not Matched"); + return FALSE; +} + + +/*********************************************************************** +* Function - MatchSrcPort() +* +* Description - Checks, Source port from the packet matches with that of queue. +* +* Parameters - pstClassifierRule: Pointer to the packet info structure. +* - ushSrcPort : Source port from the packet. +* +* Returns - TRUE(If address matches) else FAIL. +***************************************************************************/ +BOOLEAN MatchSrcPort(S_CLASSIFIER_RULE *pstClassifierRule,USHORT ushSrcPort) +{ + UCHAR ucLoopIndex=0; + + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + + if(0 == pstClassifierRule->ucSrcPortRangeLength) + return TRUE; + for(ucLoopIndex=0;ucLoopIndexucSrcPortRangeLength;ucLoopIndex++) + { + if(ushSrcPort <= pstClassifierRule->usSrcPortRangeHi[ucLoopIndex] && + ushSrcPort >= pstClassifierRule->usSrcPortRangeLo[ucLoopIndex]) + { + return TRUE; + } + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Src Port: %x Not Matched ",ushSrcPort); + return FALSE; +} + + +/*********************************************************************** +* Function - MatchDestPort() +* +* Description - Checks, Destination port from packet matches with that of queue. +* +* Parameters - pstClassifierRule: Pointer to the packet info structure. +* - ushDestPort : Destination port from the packet. +* +* Returns - TRUE(If address matches) else FAIL. +***************************************************************************/ +BOOLEAN MatchDestPort(S_CLASSIFIER_RULE *pstClassifierRule,USHORT ushDestPort) +{ + UCHAR ucLoopIndex=0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + if(0 == pstClassifierRule->ucDestPortRangeLength) + return TRUE; + + for(ucLoopIndex=0;ucLoopIndexucDestPortRangeLength;ucLoopIndex++) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Matching Port:0x%X 0x%X 0x%X",ushDestPort,pstClassifierRule->usDestPortRangeLo[ucLoopIndex],pstClassifierRule->usDestPortRangeHi[ucLoopIndex]); + + if(ushDestPort <= pstClassifierRule->usDestPortRangeHi[ucLoopIndex] && + ushDestPort >= pstClassifierRule->usDestPortRangeLo[ucLoopIndex]) + { + return TRUE; + } + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Dest Port: %x Not Matched",ushDestPort); + return FALSE; +} +/** +@ingroup tx_functions +Compares IPV4 Ip address and port number +@return Queue Index. +*/ +static USHORT IpVersion4(PMINI_ADAPTER Adapter, + struct iphdr *iphd, + S_CLASSIFIER_RULE *pstClassifierRule ) +{ + xporthdr *xprt_hdr=NULL; + BOOLEAN bClassificationSucceed=FALSE; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "========>"); + + xprt_hdr=(xporthdr *)((PUCHAR)iphd + sizeof(struct iphdr)); + + do { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Trying to see Direction = %d %d", + pstClassifierRule->ucDirection, + pstClassifierRule->usVCID_Value); + + //Checking classifier validity + if(!pstClassifierRule->bUsed || pstClassifierRule->ucDirection == DOWNLINK_DIR) + { + bClassificationSucceed = FALSE; + break; + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "is IPv6 check!"); + if(pstClassifierRule->bIpv6Protocol) + break; + + //**************Checking IP header parameter**************************// + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Trying to match Source IP Address"); + if(FALSE == (bClassificationSucceed = + MatchSrcIpAddress(pstClassifierRule, iphd->saddr))) + break; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Source IP Address Matched"); + + if(FALSE == (bClassificationSucceed = + MatchDestIpAddress(pstClassifierRule, iphd->daddr))) + break; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Destination IP Address Matched"); + + if(FALSE == (bClassificationSucceed = + MatchTos(pstClassifierRule, iphd->tos))) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "TOS Match failed\n"); + break; + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "TOS Matched"); + + if(FALSE == (bClassificationSucceed = + MatchProtocol(pstClassifierRule,iphd->protocol))) + break; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Protocol Matched"); + + //if protocol is not TCP or UDP then no need of comparing source port and destination port + if(iphd->protocol!=TCP && iphd->protocol!=UDP) + break; + //******************Checking Transport Layer Header field if present *****************// + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Source Port %04x", + (iphd->protocol==UDP)?xprt_hdr->uhdr.source:xprt_hdr->thdr.source); + + if(FALSE == (bClassificationSucceed = + MatchSrcPort(pstClassifierRule, + ntohs((iphd->protocol == UDP)? + xprt_hdr->uhdr.source:xprt_hdr->thdr.source)))) + break; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Src Port Matched"); + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Destination Port %04x", + (iphd->protocol==UDP)?xprt_hdr->uhdr.dest: + xprt_hdr->thdr.dest); + if(FALSE == (bClassificationSucceed = + MatchDestPort(pstClassifierRule, + ntohs((iphd->protocol == UDP)? + xprt_hdr->uhdr.dest:xprt_hdr->thdr.dest)))) + break; + } while(0); + + if(TRUE==bClassificationSucceed) + { + INT iMatchedSFQueueIndex = 0; + iMatchedSFQueueIndex = SearchSfid(Adapter,pstClassifierRule->ulSFID); + if(iMatchedSFQueueIndex >= NO_OF_QUEUES) + { + bClassificationSucceed = FALSE; + } + else + { + if(FALSE == Adapter->PackInfo[iMatchedSFQueueIndex].bActive) + { + bClassificationSucceed = FALSE; + } + } + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "IpVersion4 <=========="); + + return bClassificationSucceed; +} + +VOID PruneQueueAllSF(PMINI_ADAPTER Adapter) +{ + UINT iIndex = 0; + + for(iIndex = 0; iIndex < HiPriority; iIndex++) + { + if(!Adapter->PackInfo[iIndex].bValid) + continue; + + PruneQueue(Adapter, iIndex); + } +} + + +/** +@ingroup tx_functions +This function checks if the max queue size for a queue +is less than number of bytes in the queue. If so - +drops packets from the Head till the number of bytes is +less than or equal to max queue size for the queue. +*/ +static VOID PruneQueue(PMINI_ADAPTER Adapter, INT iIndex) +{ + struct sk_buff* PacketToDrop=NULL; + struct net_device_stats *netstats; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, PRUNE_QUEUE, DBG_LVL_ALL, "=====> Index %d",iIndex); + + if(iIndex == HiPriority) + return; + + if(!Adapter || (iIndex < 0) || (iIndex > HiPriority)) + return; + + /* To Store the netdevice statistic */ + netstats = &Adapter->dev->stats; + + spin_lock_bh(&Adapter->PackInfo[iIndex].SFQueueLock); + + while(1) +// while((UINT)Adapter->PackInfo[iIndex].uiCurrentPacketsOnHost > +// SF_MAX_ALLOWED_PACKETS_TO_BACKUP) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, PRUNE_QUEUE, DBG_LVL_ALL, "uiCurrentBytesOnHost:%x uiMaxBucketSize :%x", + Adapter->PackInfo[iIndex].uiCurrentBytesOnHost, + Adapter->PackInfo[iIndex].uiMaxBucketSize); + + PacketToDrop = Adapter->PackInfo[iIndex].FirstTxQueue; + + if(PacketToDrop == NULL) + break; + if((Adapter->PackInfo[iIndex].uiCurrentPacketsOnHost < SF_MAX_ALLOWED_PACKETS_TO_BACKUP) && + ((1000*(jiffies - *((B_UINT32 *)(PacketToDrop->cb)+SKB_CB_LATENCY_OFFSET))/HZ) <= Adapter->PackInfo[iIndex].uiMaxLatency)) + break; + + if(PacketToDrop) + { + if (netif_msg_tx_err(Adapter)) + pr_info(PFX "%s: tx queue %d overlimit\n", + Adapter->dev->name, iIndex); + + netstats->tx_dropped++; + + DEQUEUEPACKET(Adapter->PackInfo[iIndex].FirstTxQueue, + Adapter->PackInfo[iIndex].LastTxQueue); + /// update current bytes and packets count + Adapter->PackInfo[iIndex].uiCurrentBytesOnHost -= + PacketToDrop->len; + Adapter->PackInfo[iIndex].uiCurrentPacketsOnHost--; + /// update dropped bytes and packets counts + Adapter->PackInfo[iIndex].uiDroppedCountBytes += PacketToDrop->len; + Adapter->PackInfo[iIndex].uiDroppedCountPackets++; + dev_kfree_skb(PacketToDrop); + + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, PRUNE_QUEUE, DBG_LVL_ALL, "Dropped Bytes:%x Dropped Packets:%x", + Adapter->PackInfo[iIndex].uiDroppedCountBytes, + Adapter->PackInfo[iIndex].uiDroppedCountPackets); + + atomic_dec(&Adapter->TotalPacketCount); + } + + spin_unlock_bh(&Adapter->PackInfo[iIndex].SFQueueLock); + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, PRUNE_QUEUE, DBG_LVL_ALL, "TotalPacketCount:%x", + atomic_read(&Adapter->TotalPacketCount)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, PRUNE_QUEUE, DBG_LVL_ALL, "<====="); +} + +VOID flush_all_queues(PMINI_ADAPTER Adapter) +{ + INT iQIndex; + UINT uiTotalPacketLength; + struct sk_buff* PacketToDrop=NULL; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "=====>"); + +// down(&Adapter->data_packet_queue_lock); + for(iQIndex=LowPriority; iQIndexdev->stats; + + spin_lock_bh(&Adapter->PackInfo[iQIndex].SFQueueLock); + while(Adapter->PackInfo[iQIndex].FirstTxQueue) + { + PacketToDrop = Adapter->PackInfo[iQIndex].FirstTxQueue; + if(PacketToDrop) + { + uiTotalPacketLength = PacketToDrop->len; + netstats->tx_dropped++; + } + else + uiTotalPacketLength = 0; + + DEQUEUEPACKET(Adapter->PackInfo[iQIndex].FirstTxQueue, + Adapter->PackInfo[iQIndex].LastTxQueue); + + /* Free the skb */ + dev_kfree_skb(PacketToDrop); + + /// update current bytes and packets count + Adapter->PackInfo[iQIndex].uiCurrentBytesOnHost -= uiTotalPacketLength; + Adapter->PackInfo[iQIndex].uiCurrentPacketsOnHost--; + + /// update dropped bytes and packets counts + Adapter->PackInfo[iQIndex].uiDroppedCountBytes += uiTotalPacketLength; + Adapter->PackInfo[iQIndex].uiDroppedCountPackets++; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "Dropped Bytes:%x Dropped Packets:%x", + Adapter->PackInfo[iQIndex].uiDroppedCountBytes, + Adapter->PackInfo[iQIndex].uiDroppedCountPackets); + atomic_dec(&Adapter->TotalPacketCount); + } + spin_unlock_bh(&Adapter->PackInfo[iQIndex].SFQueueLock); + } +// up(&Adapter->data_packet_queue_lock); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, DUMP_INFO, DBG_LVL_ALL, "<====="); +} + +USHORT ClassifyPacket(PMINI_ADAPTER Adapter,struct sk_buff* skb) +{ + INT uiLoopIndex=0; + S_CLASSIFIER_RULE *pstClassifierRule = NULL; + S_ETHCS_PKT_INFO stEthCsPktInfo; + PVOID pvEThPayload = NULL; + struct iphdr *pIpHeader = NULL; + INT uiSfIndex=0; + USHORT usIndex=Adapter->usBestEffortQueueIndex; + BOOLEAN bFragmentedPkt=FALSE,bClassificationSucceed=FALSE; + USHORT usCurrFragment =0; + + PTCP_HEADER pTcpHeader; + UCHAR IpHeaderLength; + UCHAR TcpHeaderLength; + + pvEThPayload = skb->data; + *((UINT32*) (skb->cb) +SKB_CB_TCPACK_OFFSET ) = 0; + EThCSGetPktInfo(Adapter,pvEThPayload,&stEthCsPktInfo); + + switch(stEthCsPktInfo.eNwpktEthFrameType) + { + case eEth802LLCFrame: + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ClassifyPacket : 802LLCFrame\n"); + pIpHeader = pvEThPayload + sizeof(ETH_CS_802_LLC_FRAME); + break; + } + + case eEth802LLCSNAPFrame: + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ClassifyPacket : 802LLC SNAP Frame\n"); + pIpHeader = pvEThPayload + sizeof(ETH_CS_802_LLC_SNAP_FRAME); + break; + } + case eEth802QVLANFrame: + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ClassifyPacket : 802.1Q VLANFrame\n"); + pIpHeader = pvEThPayload + sizeof(ETH_CS_802_Q_FRAME); + break; + } + case eEthOtherFrame: + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ClassifyPacket : ETH Other Frame\n"); + pIpHeader = pvEThPayload + sizeof(ETH_CS_ETH2_FRAME); + break; + } + default: + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ClassifyPacket : Unrecognized ETH Frame\n"); + pIpHeader = pvEThPayload + sizeof(ETH_CS_ETH2_FRAME); + break; + } + } + + if(stEthCsPktInfo.eNwpktIPFrameType == eIPv4Packet) + { + usCurrFragment = (ntohs(pIpHeader->frag_off) & IP_OFFSET); + if((ntohs(pIpHeader->frag_off) & IP_MF) || usCurrFragment) + bFragmentedPkt = TRUE; + + if(bFragmentedPkt) + { + //Fragmented Packet. Get Frag Classifier Entry. + pstClassifierRule = GetFragIPClsEntry(Adapter,pIpHeader->id, pIpHeader->saddr); + if(pstClassifierRule) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL,"It is next Fragmented pkt"); + bClassificationSucceed=TRUE; + } + if(!(ntohs(pIpHeader->frag_off) & IP_MF)) + { + //Fragmented Last packet . Remove Frag Classifier Entry + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL,"This is the last fragmented Pkt"); + DelFragIPClsEntry(Adapter,pIpHeader->id, pIpHeader->saddr); + } + } + } + + for(uiLoopIndex = MAX_CLASSIFIERS - 1; uiLoopIndex >= 0; uiLoopIndex--) + { + if(bClassificationSucceed) + break; + //Iterate through all classifiers which are already in order of priority + //to classify the packet until match found + do + { + if(FALSE==Adapter->astClassifierTable[uiLoopIndex].bUsed) + { + bClassificationSucceed=FALSE; + break; + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Adapter->PackInfo[%d].bvalid=True\n",uiLoopIndex); + + if(0 == Adapter->astClassifierTable[uiLoopIndex].ucDirection) + { + bClassificationSucceed=FALSE;//cannot be processed for classification. + break; // it is a down link connection + } + + pstClassifierRule = &Adapter->astClassifierTable[uiLoopIndex]; + + uiSfIndex = SearchSfid(Adapter,pstClassifierRule->ulSFID); + if (uiSfIndex >= NO_OF_QUEUES) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Queue Not Valid. SearchSfid for this classifier Failed\n"); + break; + } + + if(Adapter->PackInfo[uiSfIndex].bEthCSSupport) + { + + if(eEthUnsupportedFrame==stEthCsPktInfo.eNwpktEthFrameType) + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, " ClassifyPacket : Packet Not a Valid Supported Ethernet Frame \n"); + bClassificationSucceed = FALSE; + break; + } + + + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Performing ETH CS Classification on Classifier Rule ID : %x Service Flow ID : %lx\n",pstClassifierRule->uiClassifierRuleIndex,Adapter->PackInfo[uiSfIndex].ulSFID); + bClassificationSucceed = EThCSClassifyPkt(Adapter,skb,&stEthCsPktInfo,pstClassifierRule, Adapter->PackInfo[uiSfIndex].bEthCSSupport); + + if(!bClassificationSucceed) + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ClassifyPacket : Ethernet CS Classification Failed\n"); + break; + } + } + + else // No ETH Supported on this SF + { + if(eEthOtherFrame != stEthCsPktInfo.eNwpktEthFrameType) + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, " ClassifyPacket : Packet Not a 802.3 Ethernet Frame... hence not allowed over non-ETH CS SF \n"); + bClassificationSucceed = FALSE; + break; + } + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Proceeding to IP CS Clasification"); + + if(Adapter->PackInfo[uiSfIndex].bIPCSSupport) + { + + if(stEthCsPktInfo.eNwpktIPFrameType == eNonIPPacket) + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, " ClassifyPacket : Packet is Not an IP Packet \n"); + bClassificationSucceed = FALSE; + break; + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "Dump IP Header : \n"); + DumpFullPacket((PUCHAR)pIpHeader,20); + + if(stEthCsPktInfo.eNwpktIPFrameType == eIPv4Packet) + bClassificationSucceed = IpVersion4(Adapter,pIpHeader,pstClassifierRule); + else if(stEthCsPktInfo.eNwpktIPFrameType == eIPv6Packet) + bClassificationSucceed = IpVersion6(Adapter,pIpHeader,pstClassifierRule); + } + + }while(0); + } + + if(bClassificationSucceed == TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "CF id : %d, SF ID is =%lu",pstClassifierRule->uiClassifierRuleIndex, pstClassifierRule->ulSFID); + + //Store The matched Classifier in SKB + *((UINT32*)(skb->cb)+SKB_CB_CLASSIFICATION_OFFSET) = pstClassifierRule->uiClassifierRuleIndex; + if((TCP == pIpHeader->protocol ) && !bFragmentedPkt && (ETH_AND_IP_HEADER_LEN + TCP_HEADER_LEN <= skb->len) ) + { + IpHeaderLength = pIpHeader->ihl; + pTcpHeader = (PTCP_HEADER)(((PUCHAR)pIpHeader)+(IpHeaderLength*4)); + TcpHeaderLength = GET_TCP_HEADER_LEN(pTcpHeader->HeaderLength); + + if((pTcpHeader->ucFlags & TCP_ACK) && + (ntohs(pIpHeader->tot_len) == (IpHeaderLength*4)+(TcpHeaderLength*4))) + { + *((UINT32*) (skb->cb) +SKB_CB_TCPACK_OFFSET ) = TCP_ACK; + } + } + + usIndex = SearchSfid(Adapter, pstClassifierRule->ulSFID); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "index is =%d", usIndex); + + //If this is the first fragment of a Fragmented pkt, add this CF. Only This CF should be used for all other fragment of this Pkt. + if(bFragmentedPkt && (usCurrFragment == 0)) + { + //First Fragment of Fragmented Packet. Create Frag CLS Entry + S_FRAGMENTED_PACKET_INFO stFragPktInfo; + stFragPktInfo.bUsed = TRUE; + stFragPktInfo.ulSrcIpAddress = pIpHeader->saddr; + stFragPktInfo.usIpIdentification = pIpHeader->id; + stFragPktInfo.pstMatchedClassifierEntry = pstClassifierRule; + stFragPktInfo.bOutOfOrderFragment = FALSE; + AddFragIPClsEntry(Adapter,&stFragPktInfo); + } + + + } + + if(bClassificationSucceed) + return usIndex; + else + return INVALID_QUEUE_INDEX; +} + +static BOOLEAN EthCSMatchSrcMACAddress(S_CLASSIFIER_RULE *pstClassifierRule,PUCHAR Mac) +{ + UINT i=0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + if(pstClassifierRule->ucEthCSSrcMACLen==0) + return TRUE; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "%s \n",__FUNCTION__); + for(i=0;iau8EThCSSrcMAC[i],pstClassifierRule->au8EThCSSrcMACMask[i]); + if((pstClassifierRule->au8EThCSSrcMAC[i] & pstClassifierRule->au8EThCSSrcMACMask[i])!= + (Mac[i] & pstClassifierRule->au8EThCSSrcMACMask[i])) + return FALSE; + } + return TRUE; +} + +static BOOLEAN EthCSMatchDestMACAddress(S_CLASSIFIER_RULE *pstClassifierRule,PUCHAR Mac) +{ + UINT i=0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + if(pstClassifierRule->ucEthCSDestMACLen==0) + return TRUE; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "%s \n",__FUNCTION__); + for(i=0;iau8EThCSDestMAC[i],pstClassifierRule->au8EThCSDestMACMask[i]); + if((pstClassifierRule->au8EThCSDestMAC[i] & pstClassifierRule->au8EThCSDestMACMask[i])!= + (Mac[i] & pstClassifierRule->au8EThCSDestMACMask[i])) + return FALSE; + } + return TRUE; +} + +static BOOLEAN EthCSMatchEThTypeSAP(S_CLASSIFIER_RULE *pstClassifierRule,struct sk_buff* skb,PS_ETHCS_PKT_INFO pstEthCsPktInfo) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + if((pstClassifierRule->ucEtherTypeLen==0)|| + (pstClassifierRule->au8EthCSEtherType[0] == 0)) + return TRUE; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "%s SrcEtherType:%x CLS EtherType[0]:%x\n",__FUNCTION__,pstEthCsPktInfo->usEtherType,pstClassifierRule->au8EthCSEtherType[0]); + if(pstClassifierRule->au8EthCSEtherType[0] == 1) + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "%s CLS EtherType[1]:%x EtherType[2]:%x\n",__FUNCTION__,pstClassifierRule->au8EthCSEtherType[1],pstClassifierRule->au8EthCSEtherType[2]); + + if(memcmp(&pstEthCsPktInfo->usEtherType,&pstClassifierRule->au8EthCSEtherType[1],2)==0) + return TRUE; + else + return FALSE; + } + + if(pstClassifierRule->au8EthCSEtherType[0] == 2) + { + if(eEth802LLCFrame != pstEthCsPktInfo->eNwpktEthFrameType) + return FALSE; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "%s EthCS DSAP:%x EtherType[2]:%x\n",__FUNCTION__,pstEthCsPktInfo->ucDSAP,pstClassifierRule->au8EthCSEtherType[2]); + if(pstEthCsPktInfo->ucDSAP == pstClassifierRule->au8EthCSEtherType[2]) + return TRUE; + else + return FALSE; + + } + + return FALSE; + +} + +static BOOLEAN EthCSMatchVLANRules(S_CLASSIFIER_RULE *pstClassifierRule,struct sk_buff* skb,PS_ETHCS_PKT_INFO pstEthCsPktInfo) +{ + BOOLEAN bClassificationSucceed = FALSE; + USHORT usVLANID; + B_UINT8 uPriority = 0; + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "%s CLS UserPrio:%x CLS VLANID:%x\n",__FUNCTION__,ntohs(*((USHORT *)pstClassifierRule->usUserPriority)),pstClassifierRule->usVLANID); + + /* In case FW didn't receive the TLV, the priority field should be ignored */ + if(pstClassifierRule->usValidityBitMap & (1<eNwpktEthFrameType!=eEth802QVLANFrame) + return FALSE; + + uPriority = (ntohs(*(USHORT *)(skb->data + sizeof(ETH_HEADER_STRUC))) & 0xF000) >> 13; + + if((uPriority >= pstClassifierRule->usUserPriority[0]) && (uPriority <= pstClassifierRule->usUserPriority[1])) + bClassificationSucceed = TRUE; + + if(!bClassificationSucceed) + return FALSE; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ETH CS 802.1 D User Priority Rule Matched\n"); + + bClassificationSucceed = FALSE; + + if(pstClassifierRule->usValidityBitMap & (1<eNwpktEthFrameType!=eEth802QVLANFrame) + return FALSE; + + usVLANID = ntohs(*(USHORT *)(skb->data + sizeof(ETH_HEADER_STRUC))) & 0xFFF; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "%s Pkt VLANID %x Priority: %d\n",__FUNCTION__,usVLANID, uPriority); + + if(usVLANID == ((pstClassifierRule->usVLANID & 0xFFF0) >> 4)) + bClassificationSucceed = TRUE; + + if(!bClassificationSucceed) + return FALSE; + } + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ETH CS 802.1 Q VLAN ID Rule Matched\n"); + + return TRUE; +} + + +static BOOLEAN EThCSClassifyPkt(PMINI_ADAPTER Adapter,struct sk_buff* skb, + PS_ETHCS_PKT_INFO pstEthCsPktInfo, + S_CLASSIFIER_RULE *pstClassifierRule, + B_UINT8 EthCSCupport) +{ + BOOLEAN bClassificationSucceed = FALSE; + bClassificationSucceed = EthCSMatchSrcMACAddress(pstClassifierRule,((ETH_HEADER_STRUC *)(skb->data))->au8SourceAddress); + if(!bClassificationSucceed) + return FALSE; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ETH CS SrcMAC Matched\n"); + + bClassificationSucceed = EthCSMatchDestMACAddress(pstClassifierRule,((ETH_HEADER_STRUC*)(skb->data))->au8DestinationAddress); + if(!bClassificationSucceed) + return FALSE; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ETH CS DestMAC Matched\n"); + + //classify on ETHType/802.2SAP TLV + bClassificationSucceed = EthCSMatchEThTypeSAP(pstClassifierRule,skb,pstEthCsPktInfo); + if(!bClassificationSucceed) + return FALSE; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ETH CS EthType/802.2SAP Matched\n"); + + //classify on 802.1VLAN Header Parameters + + bClassificationSucceed = EthCSMatchVLANRules(pstClassifierRule,skb,pstEthCsPktInfo); + if(!bClassificationSucceed) + return FALSE; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "ETH CS 802.1 VLAN Rules Matched\n"); + + return bClassificationSucceed; +} + +static void EThCSGetPktInfo(PMINI_ADAPTER Adapter,PVOID pvEthPayload, + PS_ETHCS_PKT_INFO pstEthCsPktInfo) +{ + USHORT u16Etype = ntohs(((ETH_HEADER_STRUC*)pvEthPayload)->u16Etype); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "EthCSGetPktInfo : Eth Hdr Type : %X\n",u16Etype); + if(u16Etype > 0x5dc) + { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "EthCSGetPktInfo : ETH2 Frame \n"); + //ETH2 Frame + if(u16Etype == ETHERNET_FRAMETYPE_802QVLAN) + { + //802.1Q VLAN Header + pstEthCsPktInfo->eNwpktEthFrameType = eEth802QVLANFrame; + u16Etype = ((ETH_CS_802_Q_FRAME*)pvEthPayload)->EthType; + //((ETH_CS_802_Q_FRAME*)pvEthPayload)->UserPriority + } + else + { + pstEthCsPktInfo->eNwpktEthFrameType = eEthOtherFrame; + u16Etype = ntohs(u16Etype); + } + + } + else + { + //802.2 LLC + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "802.2 LLC Frame \n"); + pstEthCsPktInfo->eNwpktEthFrameType = eEth802LLCFrame; + pstEthCsPktInfo->ucDSAP = ((ETH_CS_802_LLC_FRAME*)pvEthPayload)->DSAP; + if(pstEthCsPktInfo->ucDSAP == 0xAA && ((ETH_CS_802_LLC_FRAME*)pvEthPayload)->SSAP == 0xAA) + { + //SNAP Frame + pstEthCsPktInfo->eNwpktEthFrameType = eEth802LLCSNAPFrame; + u16Etype = ((ETH_CS_802_LLC_SNAP_FRAME*)pvEthPayload)->usEtherType; + } + } + if(u16Etype == ETHERNET_FRAMETYPE_IPV4) + pstEthCsPktInfo->eNwpktIPFrameType = eIPv4Packet; + else if(u16Etype == ETHERNET_FRAMETYPE_IPV6) + pstEthCsPktInfo->eNwpktIPFrameType = eIPv6Packet; + else + pstEthCsPktInfo->eNwpktIPFrameType = eNonIPPacket; + + pstEthCsPktInfo->usEtherType = ((ETH_HEADER_STRUC*)pvEthPayload)->u16Etype; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "EthCsPktInfo->eNwpktIPFrameType : %x\n",pstEthCsPktInfo->eNwpktIPFrameType); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "EthCsPktInfo->eNwpktEthFrameType : %x\n",pstEthCsPktInfo->eNwpktEthFrameType); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_TX, IPV4_DBG, DBG_LVL_ALL, "EthCsPktInfo->usEtherType : %x\n",pstEthCsPktInfo->usEtherType); +} + + + diff --git a/drivers/staging/bcm/Queue.h b/drivers/staging/bcm/Queue.h new file mode 100644 index 00000000..e1f1da2b --- /dev/null +++ b/drivers/staging/bcm/Queue.h @@ -0,0 +1,31 @@ +/************************************* +* Queue.h +**************************************/ +#ifndef __QUEUE_H__ +#define __QUEUE_H__ + + + +#define ENQUEUEPACKET(_Head, _Tail,_Packet) \ +do \ +{ \ + if (!_Head) { \ + _Head = _Packet; \ + } \ + else { \ + (_Tail)->next = _Packet; \ + } \ + (_Packet)->next = NULL; \ + _Tail = _Packet; \ +}while(0) +#define DEQUEUEPACKET(Head, Tail ) \ +do \ +{ if(Head) \ + { \ + if (!Head->next) { \ + Tail = NULL; \ + } \ + Head = Head->next; \ + } \ +}while(0) +#endif //__QUEUE_H__ diff --git a/drivers/staging/bcm/TODO b/drivers/staging/bcm/TODO new file mode 100644 index 00000000..cd3e9f2e --- /dev/null +++ b/drivers/staging/bcm/TODO @@ -0,0 +1,22 @@ +This driver is barely functional in its current state. + +BIG: + - existing API is (/dev/tarang) should be replaced + Is it possible to use same API as Intel Wimax stack and + have same user level components. + - Qos and queue model is non-standard and inflexible. + Use existing TC Qos? + +TODO: + - support more than one board - eliminate global variables + - remove developer debug BCM_DEBUG() macros + add a limited number of messages through netif_msg() + - fix non-standard kernel style + - checkpatch warnings + - use request firmware + - fix use of file I/O to load config with better API + - merge some files together? + - cleanup/eliminate debug messages + + + diff --git a/drivers/staging/bcm/Transmit.c b/drivers/staging/bcm/Transmit.c new file mode 100644 index 00000000..d5e4a740 --- /dev/null +++ b/drivers/staging/bcm/Transmit.c @@ -0,0 +1,260 @@ +/** +@file Transmit.c +@defgroup tx_functions Transmission +@section Queueing +@dot +digraph transmit1 { +node[shape=box] +edge[weight=5;color=red] + +bcm_transmit->GetPacketQueueIndex[label="IP Packet"] +GetPacketQueueIndex->IpVersion4[label="IPV4"] +GetPacketQueueIndex->IpVersion6[label="IPV6"] +} + +@enddot + +@section De-Queueing +@dot +digraph transmit2 { +node[shape=box] +edge[weight=5;color=red] +interrupt_service_thread->transmit_packets +tx_pkt_hdler->transmit_packets +transmit_packets->CheckAndSendPacketFromIndex +transmit_packets->UpdateTokenCount +CheckAndSendPacketFromIndex->PruneQueue +CheckAndSendPacketFromIndex->IsPacketAllowedForFlow +CheckAndSendPacketFromIndex->SendControlPacket[label="control pkt"] +SendControlPacket->bcm_cmd53 +CheckAndSendPacketFromIndex->SendPacketFromQueue[label="data pkt"] +SendPacketFromQueue->SetupNextSend->bcm_cmd53 +} +@enddot +*/ + +#include "headers.h" + + +/** +@ingroup ctrl_pkt_functions +This function dispatches control packet to the h/w interface +@return zero(success) or -ve value(failure) +*/ +INT SendControlPacket(PMINI_ADAPTER Adapter, char *pControlPacket) +{ + PLEADER PLeader = (PLEADER)pControlPacket; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Tx"); + if(!pControlPacket || !Adapter) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Got NULL Control Packet or Adapter"); + return STATUS_FAILURE; + } + if((atomic_read( &Adapter->CurrNumFreeTxDesc ) < + ((PLeader->PLength-1)/MAX_DEVICE_DESC_SIZE)+1)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "NO FREE DESCRIPTORS TO SEND CONTROL PACKET"); + return STATUS_FAILURE; + } + + /* Update the netdevice statistics */ + /* Dump Packet */ + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Leader Status: %x", PLeader->Status); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Leader VCID: %x",PLeader->Vcid); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "Leader Length: %x",PLeader->PLength); + if(Adapter->device_removed) + return 0; + + if (netif_msg_pktdata(Adapter)) + print_hex_dump(KERN_DEBUG, PFX "tx control: ", DUMP_PREFIX_NONE, + 16, 1, pControlPacket, PLeader->PLength + LEADER_SIZE, 0); + + Adapter->interface_transmit(Adapter->pvInterfaceAdapter, + pControlPacket, (PLeader->PLength + LEADER_SIZE)); + + atomic_dec(&Adapter->CurrNumFreeTxDesc); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_CONTROL, DBG_LVL_ALL, "<========="); + return STATUS_SUCCESS; +} + +/** +@ingroup tx_functions +This function despatches the IP packets with the given vcid +to the target via the host h/w interface. +@return zero(success) or -ve value(failure) +*/ +INT SetupNextSend(PMINI_ADAPTER Adapter, struct sk_buff *Packet, USHORT Vcid) +{ + int status=0; + BOOLEAN bHeaderSupressionEnabled = FALSE; + B_UINT16 uiClassifierRuleID; + u16 QueueIndex = skb_get_queue_mapping(Packet); + LEADER Leader={0}; + + if(Packet->len > MAX_DEVICE_DESC_SIZE) + { + status = STATUS_FAILURE; + goto errExit; + } + + /* Get the Classifier Rule ID */ + uiClassifierRuleID = *((UINT32*) (Packet->cb)+SKB_CB_CLASSIFICATION_OFFSET); + + bHeaderSupressionEnabled = Adapter->PackInfo[QueueIndex].bHeaderSuppressionEnabled + & Adapter->bPHSEnabled; + + if(Adapter->device_removed) + { + status = STATUS_FAILURE; + goto errExit; + } + + status = PHSTransmit(Adapter, &Packet, Vcid, uiClassifierRuleID, bHeaderSupressionEnabled, + (UINT *)&Packet->len, Adapter->PackInfo[QueueIndex].bEthCSSupport); + + if(status != STATUS_SUCCESS) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL, "PHS Transmit failed..\n"); + goto errExit; + } + + Leader.Vcid = Vcid; + + if(TCP_ACK == *((UINT32*) (Packet->cb) + SKB_CB_TCPACK_OFFSET )) + Leader.Status = LEADER_STATUS_TCP_ACK; + else + Leader.Status = LEADER_STATUS; + + if(Adapter->PackInfo[QueueIndex].bEthCSSupport) + { + Leader.PLength = Packet->len; + if(skb_headroom(Packet) < LEADER_SIZE) + { + if((status = skb_cow(Packet,LEADER_SIZE))) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, NEXT_SEND, DBG_LVL_ALL,"bcm_transmit : Failed To Increase headRoom\n"); + goto errExit; + } + } + skb_push(Packet, LEADER_SIZE); + memcpy(Packet->data, &Leader, LEADER_SIZE); + } + else + { + Leader.PLength = Packet->len - ETH_HLEN; + memcpy((LEADER*)skb_pull(Packet, (ETH_HLEN - LEADER_SIZE)), &Leader, LEADER_SIZE); + } + + status = Adapter->interface_transmit(Adapter->pvInterfaceAdapter, + Packet->data, (Leader.PLength + LEADER_SIZE)); + if(status) + { + ++Adapter->dev->stats.tx_errors; + if (netif_msg_tx_err(Adapter)) + pr_info(PFX "%s: transmit error %d\n", Adapter->dev->name, + status); + } + else + { + struct net_device_stats *netstats = &Adapter->dev->stats; + Adapter->PackInfo[QueueIndex].uiTotalTxBytes += Leader.PLength; + + netstats->tx_bytes += Leader.PLength; + ++netstats->tx_packets; + + Adapter->PackInfo[QueueIndex].uiCurrentTokenCount -= Leader.PLength << 3; + Adapter->PackInfo[QueueIndex].uiSentBytes += (Packet->len); + Adapter->PackInfo[QueueIndex].uiSentPackets++; + Adapter->PackInfo[QueueIndex].NumOfPacketsSent++; + + atomic_dec(&Adapter->PackInfo[QueueIndex].uiPerSFTxResourceCount); + Adapter->PackInfo[QueueIndex].uiThisPeriodSentBytes += Leader.PLength; + } + + atomic_dec(&Adapter->CurrNumFreeTxDesc); + +errExit: + + dev_kfree_skb(Packet); + return status; +} + +static int tx_pending(PMINI_ADAPTER Adapter) +{ + return (atomic_read(&Adapter->TxPktAvail) + && MINIMUM_PENDING_DESCRIPTORS < atomic_read(&Adapter->CurrNumFreeTxDesc)) + || Adapter->device_removed || (1 == Adapter->downloadDDR); +} + +/** +@ingroup tx_functions +Transmit thread +*/ +int tx_pkt_handler(PMINI_ADAPTER Adapter /**< pointer to adapter object*/ + ) +{ + int status = 0; + + while(! kthread_should_stop()) { + /* FIXME - the timeout looks like workaround for racey usage of TxPktAvail */ + if(Adapter->LinkUpStatus) + wait_event_timeout(Adapter->tx_packet_wait_queue, + tx_pending(Adapter), msecs_to_jiffies(10)); + else + wait_event_interruptible(Adapter->tx_packet_wait_queue, + tx_pending(Adapter)); + + if (Adapter->device_removed) + break; + + if(Adapter->downloadDDR == 1) + { + Adapter->downloadDDR +=1; + status = download_ddr_settings(Adapter); + if(status) + pr_err(PFX "DDR DOWNLOAD FAILED! %d\n", status); + continue; + } + + //Check end point for halt/stall. + if(Adapter->bEndPointHalted == TRUE) + { + Bcm_clear_halt_of_endpoints(Adapter); + Adapter->bEndPointHalted = FALSE; + StartInterruptUrb((PS_INTERFACE_ADAPTER)(Adapter->pvInterfaceAdapter)); + } + + if(Adapter->LinkUpStatus && !Adapter->IdleMode) + { + if(atomic_read(&Adapter->TotalPacketCount)) + { + update_per_sf_desc_cnts(Adapter); + } + } + + if( atomic_read(&Adapter->CurrNumFreeTxDesc) && + Adapter->LinkStatus == SYNC_UP_REQUEST && + !Adapter->bSyncUpRequestSent) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "Calling LinkMessage"); + LinkMessage(Adapter); + } + + if((Adapter->IdleMode || Adapter->bShutStatus) && atomic_read(&Adapter->TotalPacketCount)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "Device in Low Power mode...waking up"); + Adapter->usIdleModePattern = ABORT_IDLE_MODE; + Adapter->bWakeUpDevice = TRUE; + wake_up(&Adapter->process_rx_cntrlpkt); + } + + transmit_packets(Adapter); + + atomic_set(&Adapter->TxPktAvail, 0); + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_TX, TX_PACKETS, DBG_LVL_ALL, "Exiting the tx thread..\n"); + Adapter->transmit_packet_thread = NULL; + return 0; +} diff --git a/drivers/staging/bcm/Typedefs.h b/drivers/staging/bcm/Typedefs.h new file mode 100644 index 00000000..a985abf1 --- /dev/null +++ b/drivers/staging/bcm/Typedefs.h @@ -0,0 +1,47 @@ +/**************************** +* Typedefs.h +****************************/ +#ifndef __TYPEDEFS_H__ +#define __TYPEDEFS_H__ +#define STATUS_SUCCESS 0 +#define STATUS_FAILURE -1 + +#define FALSE 0 +#define TRUE 1 + +typedef char BOOLEAN; +typedef char CHAR; +typedef int INT; +typedef short SHORT; +typedef long LONG; +typedef void VOID; + +typedef unsigned char UCHAR; +typedef unsigned char B_UINT8; +typedef unsigned short USHORT; +typedef unsigned short B_UINT16; +typedef unsigned int UINT; +typedef unsigned int B_UINT32; +typedef unsigned long ULONG; +typedef unsigned long DWORD; + +typedef char* PCHAR; +typedef short* PSHORT; +typedef int* PINT; +typedef long* PLONG; +typedef void* PVOID; + +typedef unsigned char* PUCHAR; +typedef unsigned short* PUSHORT; +typedef unsigned int* PUINT; +typedef unsigned long* PULONG; +typedef unsigned long long ULONG64; +typedef unsigned long long LARGE_INTEGER; +typedef unsigned int UINT32; +#ifndef NULL +#define NULL 0 +#endif + + +#endif //__TYPEDEFS_H__ + diff --git a/drivers/staging/bcm/Version.h b/drivers/staging/bcm/Version.h new file mode 100644 index 00000000..a07b956b --- /dev/null +++ b/drivers/staging/bcm/Version.h @@ -0,0 +1,35 @@ + +/*Copyright (c) 2005 Beceem Communications Inc. + +Module Name: + + Version.h + +Abstract: + + +--*/ + +#ifndef VERSION_H +#define VERSION_H + + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_NETWORK + + +#define VER_FILEVERSION 5.2.45 +#define VER_FILEVERSION_STR "5.2.45" + +#undef VER_PRODUCTVERSION +#define VER_PRODUCTVERSION VER_FILEVERSION + +#undef VER_PRODUCTVERSION_STR +#define VER_PRODUCTVERSION_STR VER_FILEVERSION_STR + + + + +//#include "common.ver" + +#endif //VERSION_H diff --git a/drivers/staging/bcm/cntrl_SignalingInterface.h b/drivers/staging/bcm/cntrl_SignalingInterface.h new file mode 100644 index 00000000..ab131806 --- /dev/null +++ b/drivers/staging/bcm/cntrl_SignalingInterface.h @@ -0,0 +1,423 @@ +#ifndef CNTRL_SIGNALING_INTERFACE_ +#define CNTRL_SIGNALING_INTERFACE_ + + + + +#define DSA_REQ 11 +#define DSA_RSP 12 +#define DSA_ACK 13 +#define DSC_REQ 14 +#define DSC_RSP 15 +#define DSC_ACK 16 +#define DSD_REQ 17 +#define DSD_RSP 18 +#define DSD_ACK 19 +#define MAX_CLASSIFIERS_IN_SF 4 + + +#define MAX_STRING_LEN 20 +#define MAX_PHS_LENGTHS 255 +#define VENDOR_PHS_PARAM_LENGTH 10 +#define MAX_NUM_ACTIVE_BS 10 +#define AUTH_TOKEN_LENGTH 10 +#define NUM_HARQ_CHANNELS 16 //Changed from 10 to 16 to accommodate all HARQ channels +#define VENDOR_CLASSIFIER_PARAM_LENGTH 1 //Changed the size to 1 byte since we dnt use it +#define VENDOR_SPECIF_QOS_PARAM 1 +#define VENDOR_PHS_PARAM_LENGTH 10 +#define MBS_CONTENTS_ID_LENGTH 10 +#define GLOBAL_SF_CLASSNAME_LENGTH 6 + +#define TYPE_OF_SERVICE_LENGTH 3 +#define IP_MASKED_SRC_ADDRESS_LENGTH 32 +#define IP_MASKED_DEST_ADDRESS_LENGTH 32 +#define PROTOCOL_SRC_PORT_RANGE_LENGTH 4 +#define PROTOCOL_DEST_PORT_RANGE_LENGTH 4 +#define ETHERNET_DEST_MAC_ADDR_LENGTH 12 +#define ETHERNET_SRC_MAC_ADDR_LENGTH 12 +#define NUM_ETHERTYPE_BYTES 3 +#define NUM_IPV6_FLOWLABLE_BYTES 3 + + +//////////////////////////////////////////////////////////////////////////////// +////////////////////////structure Definitions/////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/// \brief class cCPacketClassificationRule +struct _stCPacketClassificationRuleSI{ + + /** 16bit UserPriority Of The Service Flow*/ + B_UINT16 u16UserPriority; + /** 16bit VLANID Of The Service Flow*/ + B_UINT16 u16VLANID; + /** 16bit Packet Classification RuleIndex Of The Service Flow*/ + B_UINT16 u16PacketClassificationRuleIndex; + /** 8bit Classifier Rule Priority Of The Service Flow*/ + B_UINT8 u8ClassifierRulePriority; + /** Length of IP TypeOfService field*/ + B_UINT8 u8IPTypeOfServiceLength; + /** 3bytes IP TypeOfService */ + B_UINT8 u8IPTypeOfService[TYPE_OF_SERVICE_LENGTH]; + /** Protocol used in classification of Service Flow*/ + B_UINT8 u8Protocol; + /** Length of IP Masked Source Address */ + B_UINT8 u8IPMaskedSourceAddressLength; + /** IP Masked Source Address used in classification for the Service Flow*/ + B_UINT8 u8IPMaskedSourceAddress[IP_MASKED_SRC_ADDRESS_LENGTH]; + /** Length of IP Destination Address */ + B_UINT8 u8IPDestinationAddressLength; + /** IP Destination Address used in classification for the Service Flow*/ + B_UINT8 u8IPDestinationAddress[IP_MASKED_DEST_ADDRESS_LENGTH]; + /** Length of Protocol Source Port Range */ + B_UINT8 u8ProtocolSourcePortRangeLength; + /** Protocol Source Port Range used in the Service Flow*/ + B_UINT8 u8ProtocolSourcePortRange[PROTOCOL_SRC_PORT_RANGE_LENGTH]; + /** Length of Protocol Dest Port Range */ + B_UINT8 u8ProtocolDestPortRangeLength; + /** Protocol Dest Port Range used in the Service Flow*/ + B_UINT8 u8ProtocolDestPortRange[PROTOCOL_DEST_PORT_RANGE_LENGTH]; + /** Length of Ethernet Destination MAC Address */ + B_UINT8 u8EthernetDestMacAddressLength; + /** Ethernet Destination MAC Address used in classification of the Service Flow*/ + B_UINT8 u8EthernetDestMacAddress[ETHERNET_DEST_MAC_ADDR_LENGTH]; + /** Length of Ethernet Source MAC Address */ + B_UINT8 u8EthernetSourceMACAddressLength; + /** Ethernet Source MAC Address used in classification of the Service Flow*/ + B_UINT8 u8EthernetSourceMACAddress[ETHERNET_SRC_MAC_ADDR_LENGTH]; + /** Length of Ethertype */ + B_UINT8 u8EthertypeLength; + /** 3bytes Ethertype Of The Service Flow*/ + B_UINT8 u8Ethertype[NUM_ETHERTYPE_BYTES]; + /** 8bit Associated PHSI Of The Service Flow*/ + B_UINT8 u8AssociatedPHSI; + /** Length of Vendor Specific Classifier Param length Of The Service Flow*/ + B_UINT8 u8VendorSpecificClassifierParamLength; + /** Vendor Specific Classifier Param Of The Service Flow*/ + B_UINT8 u8VendorSpecificClassifierParam[VENDOR_CLASSIFIER_PARAM_LENGTH]; + /** Length Of IPv6 Flow Lable of the Service Flow*/ + B_UINT8 u8IPv6FlowLableLength; + /** IPv6 Flow Lable Of The Service Flow*/ + B_UINT8 u8IPv6FlowLable[NUM_IPV6_FLOWLABLE_BYTES]; + /** Action associated with the classifier rule*/ + B_UINT8 u8ClassifierActionRule; + B_UINT16 u16ValidityBitMap; +}; +typedef struct _stCPacketClassificationRuleSI CCPacketClassificationRuleSI,stCPacketClassificationRuleSI, *pstCPacketClassificationRuleSI; + +/// \brief class CPhsRuleSI +typedef struct _stPhsRuleSI { + /** 8bit PHS Index Of The Service Flow*/ + B_UINT8 u8PHSI; + /** PHSF Length Of The Service Flow*/ + B_UINT8 u8PHSFLength; + /** String of bytes containing header information to be suppressed by the sending CS and reconstructed by the receiving CS*/ + B_UINT8 u8PHSF[MAX_PHS_LENGTHS]; + /** PHSM Length Of The Service Flow*/ + B_UINT8 u8PHSMLength; + /** PHS Mask for the SF*/ + B_UINT8 u8PHSM[MAX_PHS_LENGTHS]; + /** 8bit Total number of bytes to be suppressed for the Service Flow*/ + B_UINT8 u8PHSS; + /** 8bit Indicates whether or not Packet Header contents need to be verified prior to supression */ + B_UINT8 u8PHSV; + /** Vendor Specific PHS param Length Of The Service Flow*/ + B_UINT8 u8VendorSpecificPHSParamsLength; + /** Vendor Specific PHS param Of The Service Flow*/ + B_UINT8 u8VendorSpecificPHSParams[VENDOR_PHS_PARAM_LENGTH]; + + B_UINT8 u8Padding[2]; +}stPhsRuleSI,*pstPhsRuleSI; +typedef stPhsRuleSI CPhsRuleSI; + +/// \brief structure cConvergenceSLTypes +struct _stConvergenceSLTypes{ + /** 8bit Phs Classfier Action Of The Service Flow*/ + B_UINT8 u8ClassfierDSCAction; + /** 8bit Phs DSC Action Of The Service Flow*/ + B_UINT8 u8PhsDSCAction; + /** 16bit Padding */ + B_UINT8 u8Padding[2]; + /// \brief class cCPacketClassificationRule + stCPacketClassificationRuleSI cCPacketClassificationRule; + /// \brief class CPhsRuleSI + struct _stPhsRuleSI cPhsRule; +}; +typedef struct _stConvergenceSLTypes stConvergenceSLTypes,CConvergenceSLTypes, *pstConvergenceSLTypes; + + +/// \brief structure CServiceFlowParamSI +typedef struct _stServiceFlowParamSI{ + + /** 32bitSFID Of The Service Flow*/ + B_UINT32 u32SFID; + + /** 32bit Maximum Sustained Traffic Rate of the Service Flow*/ + B_UINT32 u32MaxSustainedTrafficRate; + + /** 32bit Maximum Traffic Burst allowed for the Service Flow*/ + B_UINT32 u32MaxTrafficBurst; + + /** 32bit Minimum Reserved Traffic Rate of the Service Flow*/ + B_UINT32 u32MinReservedTrafficRate; + + /** 32bit Tolerated Jitter of the Service Flow*/ + B_UINT32 u32ToleratedJitter; + + /** 32bit Maximum Latency of the Service Flow*/ + B_UINT32 u32MaximumLatency; + + /** 16bitCID Of The Service Flow*/ + B_UINT16 u16CID; + + /** 16bit SAID on which the service flow being set up shall be mapped*/ + B_UINT16 u16TargetSAID; + + /** 16bit ARQ window size negotiated*/ + B_UINT16 u16ARQWindowSize; + + /** 16bit Total Tx delay incl sending, receiving & processing delays */ + B_UINT16 u16ARQRetryTxTimeOut; + + /** 16bit Total Rx delay incl sending, receiving & processing delays */ + B_UINT16 u16ARQRetryRxTimeOut; + + /** 16bit ARQ block lifetime */ + B_UINT16 u16ARQBlockLifeTime; + + /** 16bit ARQ Sync loss timeout*/ + B_UINT16 u16ARQSyncLossTimeOut; + + /** 16bit ARQ Purge timeout */ + B_UINT16 u16ARQRxPurgeTimeOut; +//TODO::Remove this once we move to a new CORR2 driver + /// \brief Size of an ARQ block + B_UINT16 u16ARQBlockSize; + +//#endif + /** 16bit Nominal interval b/w consecutive SDU arrivals at MAC SAP*/ + B_UINT16 u16SDUInterArrivalTime; + + /** 16bit Specifies the time base for rate measurement */ + B_UINT16 u16TimeBase; + + /** 16bit Interval b/w Successive Grant oppurtunities*/ + B_UINT16 u16UnsolicitedGrantInterval; + + /** 16bit Interval b/w Successive Polling grant oppurtunities*/ + B_UINT16 u16UnsolicitedPollingInterval; + + /** internal var to get the overhead */ + B_UINT16 u16MacOverhead; + + /** MBS contents Identifier*/ + B_UINT16 u16MBSContentsID[MBS_CONTENTS_ID_LENGTH]; + + /** MBS contents Identifier length*/ + B_UINT8 u8MBSContentsIDLength; + + /** ServiceClassName Length Of The Service Flow*/ + B_UINT8 u8ServiceClassNameLength; + + /** 32bytes ServiceClassName Of The Service Flow*/ + B_UINT8 u8ServiceClassName[32]; + + /** 8bit Indicates whether or not MBS service is requested for this Serivce Flow*/ + B_UINT8 u8MBSService; + + /** 8bit QOS Parameter Set specifies proper application of QoS paramters to Provisioned, Admitted and Active sets*/ + B_UINT8 u8QosParamSet; + + /** 8bit Traffic Priority Of the Service Flow */ + B_UINT8 u8TrafficPriority; + + /** 8bit Uplink Grant Scheduling Type of The Service Flow */ + B_UINT8 u8ServiceFlowSchedulingType; + + /** 8bit Request transmission Policy of the Service Flow*/ + B_UINT8 u8RequesttransmissionPolicy; + + /** 8bit Specifies whether SDUs for this Service flow are of FixedLength or Variable length */ + B_UINT8 u8FixedLengthVSVariableLengthSDUIndicator; + + /** 8bit Length of the SDU for a fixed length SDU service flow*/ + B_UINT8 u8SDUSize; + + /** 8bit Indicates whether or not ARQ is requested for this connection*/ + B_UINT8 u8ARQEnable; + + /**< 8bit Indicates whether or not data has tobe delivered in order to higher layer*/ + B_UINT8 u8ARQDeliverInOrder; + + /** 8bit Receiver ARQ ACK processing time */ + B_UINT8 u8RxARQAckProcessingTime; + + /** 8bit Convergence Sublayer Specification Of The Service Flow*/ + B_UINT8 u8CSSpecification; + + /** 8 bit Type of data delivery service*/ + B_UINT8 u8TypeOfDataDeliveryService; + + /** 8bit Specifies whether a service flow may generate Paging */ + B_UINT8 u8PagingPreference; + + /** 8bit Indicates the MBS Zone through which the connection or virtual connection is valid */ + B_UINT8 u8MBSZoneIdentifierassignment; + + /** 8bit Specifies whether traffic on SF should generate MOB_TRF_IND to MS in sleep mode*/ + B_UINT8 u8TrafficIndicationPreference; + + /** 8bit Speciifes the length of predefined Global QoS parameter set encoding for this SF */ + B_UINT8 u8GlobalServicesClassNameLength; + + /** 6 byte Speciifes the predefined Global QoS parameter set encoding for this SF */ + B_UINT8 u8GlobalServicesClassName[GLOBAL_SF_CLASSNAME_LENGTH]; + + /** 8bit Indicates whether or not SN feedback is enabled for the conn */ + B_UINT8 u8SNFeedbackEnabled; + + /** Indicates the size of the Fragment Sequence Number for the connection */ + B_UINT8 u8FSNSize; + + /** 8bit Number of CIDs in active BS list */ + B_UINT8 u8CIDAllocation4activeBSsLength; + + /** CIDs of BS in the active list */ + B_UINT8 u8CIDAllocation4activeBSs[MAX_NUM_ACTIVE_BS]; + + /** Specifies if PDU extended subheader should be applied on every PDU on this conn*/ + B_UINT8 u8PDUSNExtendedSubheader4HarqReordering; + + /** 8bit Specifies whether the connection uses HARQ or not */ + B_UINT8 u8HARQServiceFlows; + + /** Specifies the length of Authorization token*/ + B_UINT8 u8AuthTokenLength; + + /** Specifies the Authorization token*/ + B_UINT8 u8AuthToken[AUTH_TOKEN_LENGTH]; + + /** specifes Number of HARQ channels used to carry data length*/ + B_UINT8 u8HarqChannelMappingLength; + + /** specifes HARQ channels used to carry data*/ + B_UINT8 u8HARQChannelMapping[NUM_HARQ_CHANNELS]; + + /** 8bit Length of Vendor Specific QoS Params */ + B_UINT8 u8VendorSpecificQoSParamLength; + + /** 1byte Vendor Specific QoS Param Of The Service Flow*/ + B_UINT8 u8VendorSpecificQoSParam[VENDOR_SPECIF_QOS_PARAM]; + + // indicates total classifiers in the SF + B_UINT8 u8TotalClassifiers; /**< Total number of valid classifiers*/ + B_UINT8 bValid; /**< Validity flag */ + B_UINT8 u8Padding; /**< Padding byte*/ + +/** +Structure for Convergence SubLayer Types with a maximum of 4 classifiers +*/ + stConvergenceSLTypes cConvergenceSLTypes[MAX_CLASSIFIERS_IN_SF]; + +} stServiceFlowParamSI, *pstServiceFlowParamSI; +typedef stServiceFlowParamSI CServiceFlowParamSI; + +/** +structure stLocalSFAddRequest +*/ +typedef struct _stLocalSFAddRequest{ + + B_UINT8 u8Type; /**< Type*/ + B_UINT8 eConnectionDir; /**< Connection direction*/ + /// \brief 16 bit TID + B_UINT16 u16TID; /**< 16bit TID*/ + /// \brief 16bitCID + B_UINT16 u16CID; /**< 16bit CID*/ + /// \brief 16bitVCID + B_UINT16 u16VCID; /**< 16bit VCID*/ + /// \brief structure ParameterSet + + stServiceFlowParamSI *psfParameterSet; /**< structure ParameterSet*/ + +}stLocalSFAddRequest, *pstLocalSFAddRequest; + + +/** +structure stLocalSFAddIndication +*/ +typedef struct _stLocalSFAddIndication{ + + B_UINT8 u8Type; /**< Type*/ + B_UINT8 eConnectionDir; /**< Connection Direction*/ + /// \brief 16 bit TID + B_UINT16 u16TID; /**< TID*/ + /// \brief 16bitCID + B_UINT16 u16CID; /**< 16bitCID*/ + /// \brief 16bitVCID + B_UINT16 u16VCID; /**< 16bitVCID*/ + + + /// \brief structure AuthorizedSet + /// \brief structure AuthorizedSet + stServiceFlowParamSI *psfAuthorizedSet; /**< AuthorizedSet of type stServiceFlowParamSI*/ + /// \brief structure AdmittedSet + stServiceFlowParamSI *psfAdmittedSet; /**< AdmittedSet of type stServiceFlowParamSI*/ + /// \brief structure ActiveSet + stServiceFlowParamSI *psfActiveSet; /**< sfActiveSet of type stServiceFlowParamSI*/ + B_UINT8 u8CC; /**< Confirmation Code*/ + B_UINT8 u8Padd; /**< 8-bit Padding */ + + B_UINT16 u16Padd; /**< 16 bit Padding */ + +}stLocalSFAddIndication; + + +typedef struct _stLocalSFAddIndication *pstLocalSFAddIndication; +/** +structure stLocalSFChangeRequest is same as structure stLocalSFAddIndication +*/ +typedef struct _stLocalSFAddIndication stLocalSFChangeRequest, *pstLocalSFChangeRequest; +/** +structure stLocalSFChangeIndication is same as structure stLocalSFAddIndication +*/ +typedef struct _stLocalSFAddIndication stLocalSFChangeIndication, *pstLocalSFChangeIndication; + +/** +structure stLocalSFDeleteRequest +*/ +typedef struct _stLocalSFDeleteRequest{ + B_UINT8 u8Type; /**< Type*/ + B_UINT8 u8Padding; /**< Padding byte*/ + B_UINT16 u16TID; /**< TID*/ + /// \brief 32bitSFID + B_UINT32 u32SFID; /**< SFID*/ +}stLocalSFDeleteRequest, *pstLocalSFDeleteRequest; + +/** +structure stLocalSFDeleteIndication +*/ +typedef struct stLocalSFDeleteIndication{ + B_UINT8 u8Type; /**< Type */ + B_UINT8 u8Padding; /**< Padding */ + B_UINT16 u16TID; /**< TID */ + /// \brief 16bitCID + B_UINT16 u16CID; /**< CID */ + /// \brief 16bitVCID + B_UINT16 u16VCID; /**< VCID */ + /// \brief 32bitSFID + B_UINT32 u32SFID; /**< SFID */ + /// \brief 8bit Confirmation code + B_UINT8 u8ConfirmationCode; /**< Confirmation code */ + B_UINT8 u8Padding1[3]; /**< 3 byte Padding */ +}stLocalSFDeleteIndication; + +typedef struct _stIM_SFHostNotify +{ + B_UINT32 SFID; //SFID of the service flow + B_UINT16 newCID; //the new/changed CID + B_UINT16 VCID; //Get new Vcid if the flow has been made active in CID update TLV, but was inactive earlier or the orig vcid + B_UINT8 RetainSF; //Indication to Host if the SF is to be retained or deleted; if TRUE-retain else delete + B_UINT8 QoSParamSet; //QoS paramset of the retained SF + B_UINT16 u16reserved; //For byte alignment + +} stIM_SFHostNotify; + +#endif diff --git a/drivers/staging/bcm/headers.h b/drivers/staging/bcm/headers.h new file mode 100644 index 00000000..da47db8c --- /dev/null +++ b/drivers/staging/bcm/headers.h @@ -0,0 +1,79 @@ + +/******************************************************************* +* Headers.h +*******************************************************************/ +#ifndef __HEADERS_H__ +#define __HEADERS_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 "Typedefs.h" +#include "Version.h" +#include "Macros.h" +#include "HostMIBSInterface.h" +#include "cntrl_SignalingInterface.h" +#include "PHSDefines.h" +#include "led_control.h" +#include "Ioctl.h" +#include "nvm.h" +#include "target_params.h" +#include "Adapter.h" +#include "CmHost.h" +#include "DDRInit.h" +#include "Debug.h" +#include "IPv6ProtocolHdr.h" +#include "PHSModule.h" +#include "Protocol.h" +#include "Prototypes.h" +#include "Queue.h" +#include "vendorspecificextn.h" + +#include "InterfaceMacros.h" +#include "InterfaceAdapter.h" +#include "InterfaceIsr.h" +#include "InterfaceMisc.h" +#include "InterfaceRx.h" +#include "InterfaceTx.h" +#include "InterfaceIdleMode.h" +#include "InterfaceInit.h" + +#define DRV_NAME "beceem" +#define DEV_NAME "tarang" +#define DRV_DESCRIPTION "Beceem Communications Inc. WiMAX driver" +#define DRV_COPYRIGHT "Copyright 2010. Beceem Communications Inc" +#define DRV_VERSION VER_FILEVERSION_STR +#define PFX DRV_NAME " " + +extern struct class *bcm_class; + +#endif diff --git a/drivers/staging/bcm/hostmibs.c b/drivers/staging/bcm/hostmibs.c new file mode 100644 index 00000000..101c4e31 --- /dev/null +++ b/drivers/staging/bcm/hostmibs.c @@ -0,0 +1,141 @@ +/* + * File Name: hostmibs.c + * + * Author: Beceem Communications Pvt. Ltd + * + * Abstract: This file contains the routines to copy the statistics used by + * the driver to the Host MIBS structure and giving the same to Application. + */ + +#include "headers.h" + +INT ProcessGetHostMibs(PMINI_ADAPTER Adapter, S_MIBS_HOST_STATS_MIBS *pstHostMibs) +{ + S_SERVICEFLOW_ENTRY *pstServiceFlowEntry = NULL; + S_PHS_RULE *pstPhsRule = NULL; + S_CLASSIFIER_TABLE *pstClassifierTable = NULL; + S_CLASSIFIER_ENTRY *pstClassifierRule = NULL; + PPHS_DEVICE_EXTENSION pDeviceExtension = (PPHS_DEVICE_EXTENSION) &Adapter->stBCMPhsContext; + + UINT nClassifierIndex = 0, nPhsTableIndex = 0, nSfIndex = 0, uiIndex = 0; + + if (pDeviceExtension == NULL) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, HOST_MIBS, DBG_LVL_ALL, "Invalid Device Extension\n"); + return STATUS_FAILURE; + } + + /* Copy the classifier Table */ + for (nClassifierIndex = 0; nClassifierIndex < MAX_CLASSIFIERS; nClassifierIndex++) { + if (Adapter->astClassifierTable[nClassifierIndex].bUsed == TRUE) + memcpy((PVOID) & pstHostMibs-> + astClassifierTable[nClassifierIndex], + (PVOID) & Adapter-> + astClassifierTable[nClassifierIndex], + sizeof(S_MIBS_CLASSIFIER_RULE)); + } + + /* Copy the SF Table */ + for (nSfIndex = 0; nSfIndex < NO_OF_QUEUES; nSfIndex++) { + if (Adapter->PackInfo[nSfIndex].bValid) { + memcpy((PVOID) & pstHostMibs->astSFtable[nSfIndex], + (PVOID) & Adapter->PackInfo[nSfIndex], + sizeof(S_MIBS_SERVICEFLOW_TABLE)); + } else { + /* If index in not valid, + * don't process this for the PHS table. + * Go For the next entry. + */ + continue; + } + + /* Retrieve the SFID Entry Index for requested Service Flow */ + if (PHS_INVALID_TABLE_INDEX == + GetServiceFlowEntry(pDeviceExtension-> + pstServiceFlowPhsRulesTable, + Adapter->PackInfo[nSfIndex]. + usVCID_Value, &pstServiceFlowEntry)) + + continue; + + pstClassifierTable = pstServiceFlowEntry->pstClassifierTable; + + for (uiIndex = 0; uiIndex < MAX_PHSRULE_PER_SF; uiIndex++) { + pstClassifierRule = &pstClassifierTable->stActivePhsRulesList[uiIndex]; + + if (pstClassifierRule->bUsed) { + pstPhsRule = pstClassifierRule->pstPhsRule; + + pstHostMibs->astPhsRulesTable[nPhsTableIndex]. + ulSFID = Adapter->PackInfo[nSfIndex].ulSFID; + + memcpy(&pstHostMibs-> + astPhsRulesTable[nPhsTableIndex].u8PHSI, + &pstPhsRule->u8PHSI, sizeof(S_PHS_RULE)); + nPhsTableIndex++; + + } + + } + + } + + /* Copy other Host Statistics parameters */ + pstHostMibs->stHostInfo.GoodTransmits = Adapter->dev->stats.tx_packets; + pstHostMibs->stHostInfo.GoodReceives = Adapter->dev->stats.rx_packets; + pstHostMibs->stHostInfo.CurrNumFreeDesc = atomic_read(&Adapter->CurrNumFreeTxDesc); + pstHostMibs->stHostInfo.BEBucketSize = Adapter->BEBucketSize; + pstHostMibs->stHostInfo.rtPSBucketSize = Adapter->rtPSBucketSize; + pstHostMibs->stHostInfo.TimerActive = Adapter->TimerActive; + pstHostMibs->stHostInfo.u32TotalDSD = Adapter->u32TotalDSD; + + memcpy(pstHostMibs->stHostInfo.aTxPktSizeHist, Adapter->aTxPktSizeHist, sizeof(UINT32) * MIBS_MAX_HIST_ENTRIES); + memcpy(pstHostMibs->stHostInfo.aRxPktSizeHist, Adapter->aRxPktSizeHist, sizeof(UINT32) * MIBS_MAX_HIST_ENTRIES); + + return STATUS_SUCCESS; +} + +VOID GetDroppedAppCntrlPktMibs(S_MIBS_HOST_STATS_MIBS *pstHostMibs, const PPER_TARANG_DATA pTarang) +{ + memcpy(&(pstHostMibs->stDroppedAppCntrlMsgs), + &(pTarang->stDroppedAppCntrlMsgs), + sizeof(S_MIBS_DROPPED_APP_CNTRL_MESSAGES)); +} + +VOID CopyMIBSExtendedSFParameters(PMINI_ADAPTER Adapter, CServiceFlowParamSI *psfLocalSet, UINT uiSearchRuleIndex) +{ + S_MIBS_EXTSERVICEFLOW_PARAMETERS *t = &Adapter->PackInfo[uiSearchRuleIndex].stMibsExtServiceFlowTable; + + t->wmanIfSfid = psfLocalSet->u32SFID; + t->wmanIfCmnCpsMaxSustainedRate = psfLocalSet->u32MaxSustainedTrafficRate; + t->wmanIfCmnCpsMaxTrafficBurst = psfLocalSet->u32MaxTrafficBurst; + t->wmanIfCmnCpsMinReservedRate = psfLocalSet->u32MinReservedTrafficRate; + t->wmanIfCmnCpsToleratedJitter = psfLocalSet->u32ToleratedJitter; + t->wmanIfCmnCpsMaxLatency = psfLocalSet->u32MaximumLatency; + t->wmanIfCmnCpsFixedVsVariableSduInd = psfLocalSet->u8FixedLengthVSVariableLengthSDUIndicator; + t->wmanIfCmnCpsFixedVsVariableSduInd = ntohl(t->wmanIfCmnCpsFixedVsVariableSduInd); + t->wmanIfCmnCpsSduSize = psfLocalSet->u8SDUSize; + t->wmanIfCmnCpsSduSize = ntohl(t->wmanIfCmnCpsSduSize); + t->wmanIfCmnCpsSfSchedulingType = psfLocalSet->u8ServiceFlowSchedulingType; + t->wmanIfCmnCpsSfSchedulingType = ntohl(t->wmanIfCmnCpsSfSchedulingType); + t->wmanIfCmnCpsArqEnable = psfLocalSet->u8ARQEnable; + t->wmanIfCmnCpsArqEnable = ntohl(t->wmanIfCmnCpsArqEnable); + t->wmanIfCmnCpsArqWindowSize = ntohs(psfLocalSet->u16ARQWindowSize); + t->wmanIfCmnCpsArqWindowSize = ntohl(t->wmanIfCmnCpsArqWindowSize); + t->wmanIfCmnCpsArqBlockLifetime = ntohs(psfLocalSet->u16ARQBlockLifeTime); + t->wmanIfCmnCpsArqBlockLifetime = ntohl(t->wmanIfCmnCpsArqBlockLifetime); + t->wmanIfCmnCpsArqSyncLossTimeout = ntohs(psfLocalSet->u16ARQSyncLossTimeOut); + t->wmanIfCmnCpsArqSyncLossTimeout = ntohl(t->wmanIfCmnCpsArqSyncLossTimeout); + t->wmanIfCmnCpsArqDeliverInOrder = psfLocalSet->u8ARQDeliverInOrder; + t->wmanIfCmnCpsArqDeliverInOrder = ntohl(t->wmanIfCmnCpsArqDeliverInOrder); + t->wmanIfCmnCpsArqRxPurgeTimeout = ntohs(psfLocalSet->u16ARQRxPurgeTimeOut); + t->wmanIfCmnCpsArqRxPurgeTimeout = ntohl(t->wmanIfCmnCpsArqRxPurgeTimeout); + t->wmanIfCmnCpsArqBlockSize = ntohs(psfLocalSet->u16ARQBlockSize); + t->wmanIfCmnCpsArqBlockSize = ntohl(t->wmanIfCmnCpsArqBlockSize); + t->wmanIfCmnCpsReqTxPolicy = psfLocalSet->u8RequesttransmissionPolicy; + t->wmanIfCmnCpsReqTxPolicy = ntohl(t->wmanIfCmnCpsReqTxPolicy); + t->wmanIfCmnSfCsSpecification = psfLocalSet->u8CSSpecification; + t->wmanIfCmnSfCsSpecification = ntohl(t->wmanIfCmnSfCsSpecification); + t->wmanIfCmnCpsTargetSaid = ntohs(psfLocalSet->u16TargetSAID); + t->wmanIfCmnCpsTargetSaid = ntohl(t->wmanIfCmnCpsTargetSaid); + +} diff --git a/drivers/staging/bcm/led_control.c b/drivers/staging/bcm/led_control.c new file mode 100644 index 00000000..c7f48862 --- /dev/null +++ b/drivers/staging/bcm/led_control.c @@ -0,0 +1,916 @@ +#include "headers.h" + +#define STATUS_IMAGE_CHECKSUM_MISMATCH -199 +#define EVENT_SIGNALED 1 + +static B_UINT16 CFG_CalculateChecksum(B_UINT8 *pu8Buffer, B_UINT32 u32Size) +{ + B_UINT16 u16CheckSum = 0; + while (u32Size--) { + u16CheckSum += (B_UINT8)~(*pu8Buffer); + pu8Buffer++; + } + return u16CheckSum; +} + +BOOLEAN IsReqGpioIsLedInNVM(PMINI_ADAPTER Adapter, UINT gpios) +{ + INT Status; + Status = (Adapter->gpioBitMap & gpios) ^ gpios; + if (Status) + return FALSE; + else + return TRUE; +} + +static INT LED_Blink(PMINI_ADAPTER Adapter, UINT GPIO_Num, UCHAR uiLedIndex, + ULONG timeout, INT num_of_time, LedEventInfo_t currdriverstate) +{ + int Status = STATUS_SUCCESS; + BOOLEAN bInfinite = FALSE; + + /* Check if num_of_time is -ve. If yes, blink led in infinite loop */ + if (num_of_time < 0) { + bInfinite = TRUE; + num_of_time = 1; + } + while (num_of_time) { + if (currdriverstate == Adapter->DriverState) + TURN_ON_LED(GPIO_Num, uiLedIndex); + + /* Wait for timeout after setting on the LED */ + Status = wait_event_interruptible_timeout( + Adapter->LEDInfo.notify_led_event, + currdriverstate != Adapter->DriverState || + kthread_should_stop(), + msecs_to_jiffies(timeout)); + + if (kthread_should_stop()) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, + "Led thread got signal to exit..hence exiting"); + Adapter->LEDInfo.led_thread_running = + BCM_LED_THREAD_DISABLED; + TURN_OFF_LED(GPIO_Num, uiLedIndex); + Status = EVENT_SIGNALED; + break; + } + if (Status) { + TURN_OFF_LED(GPIO_Num, uiLedIndex); + Status = EVENT_SIGNALED; + break; + } + + TURN_OFF_LED(GPIO_Num, uiLedIndex); + Status = wait_event_interruptible_timeout( + Adapter->LEDInfo.notify_led_event, + currdriverstate != Adapter->DriverState || + kthread_should_stop(), + msecs_to_jiffies(timeout)); + if (bInfinite == FALSE) + num_of_time--; + } + return Status; +} + +static INT ScaleRateofTransfer(ULONG rate) +{ + if (rate <= 3) + return rate; + else if ((rate > 3) && (rate <= 100)) + return 5; + else if ((rate > 100) && (rate <= 200)) + return 6; + else if ((rate > 200) && (rate <= 300)) + return 7; + else if ((rate > 300) && (rate <= 400)) + return 8; + else if ((rate > 400) && (rate <= 500)) + return 9; + else if ((rate > 500) && (rate <= 600)) + return 10; + else + return MAX_NUM_OF_BLINKS; +} + + + +static INT LED_Proportional_Blink(PMINI_ADAPTER Adapter, UCHAR GPIO_Num_tx, + UCHAR uiTxLedIndex, UCHAR GPIO_Num_rx, UCHAR uiRxLedIndex, + LedEventInfo_t currdriverstate) +{ + /* Initial values of TX and RX packets */ + ULONG64 Initial_num_of_packts_tx = 0, Initial_num_of_packts_rx = 0; + /* values of TX and RX packets after 1 sec */ + ULONG64 Final_num_of_packts_tx = 0, Final_num_of_packts_rx = 0; + /* Rate of transfer of Tx and Rx in 1 sec */ + ULONG64 rate_of_transfer_tx = 0, rate_of_transfer_rx = 0; + int Status = STATUS_SUCCESS; + INT num_of_time = 0, num_of_time_tx = 0, num_of_time_rx = 0; + UINT remDelay = 0; + BOOLEAN bBlinkBothLED = TRUE; + /* UINT GPIO_num = DISABLE_GPIO_NUM; */ + ulong timeout = 0; + + /* Read initial value of packets sent/received */ + Initial_num_of_packts_tx = Adapter->dev->stats.tx_packets; + Initial_num_of_packts_rx = Adapter->dev->stats.rx_packets; + + /* Scale the rate of transfer to no of blinks. */ + num_of_time_tx = ScaleRateofTransfer((ULONG)rate_of_transfer_tx); + num_of_time_rx = ScaleRateofTransfer((ULONG)rate_of_transfer_rx); + + while ((Adapter->device_removed == FALSE)) { + timeout = 50; + /* + * Blink Tx and Rx LED when both Tx and Rx is + * in normal bandwidth + */ + if (bBlinkBothLED) { + /* + * Assign minimum number of blinks of + * either Tx or Rx. + */ + if (num_of_time_tx > num_of_time_rx) + num_of_time = num_of_time_rx; + else + num_of_time = num_of_time_tx; + if (num_of_time > 0) { + /* Blink both Tx and Rx LEDs */ + if (LED_Blink(Adapter, 1 << GPIO_Num_tx, + uiTxLedIndex, timeout, + num_of_time, currdriverstate) + == EVENT_SIGNALED) + return EVENT_SIGNALED; + + if (LED_Blink(Adapter, 1 << GPIO_Num_rx, + uiRxLedIndex, timeout, + num_of_time, currdriverstate) + == EVENT_SIGNALED) + return EVENT_SIGNALED; + + } + + if (num_of_time == num_of_time_tx) { + /* Blink pending rate of Rx */ + if (LED_Blink(Adapter, (1 << GPIO_Num_rx), + uiRxLedIndex, timeout, + num_of_time_rx-num_of_time, + currdriverstate) + == EVENT_SIGNALED) + return EVENT_SIGNALED; + + num_of_time = num_of_time_rx; + } else { + /* Blink pending rate of Tx */ + if (LED_Blink(Adapter, 1 << GPIO_Num_tx, + uiTxLedIndex, timeout, + num_of_time_tx-num_of_time, + currdriverstate) + == EVENT_SIGNALED) + return EVENT_SIGNALED; + + num_of_time = num_of_time_tx; + } + } else { + if (num_of_time == num_of_time_tx) { + /* Blink pending rate of Rx */ + if (LED_Blink(Adapter, 1 << GPIO_Num_tx, + uiTxLedIndex, timeout, + num_of_time, currdriverstate) + == EVENT_SIGNALED) + return EVENT_SIGNALED; + } else { + /* Blink pending rate of Tx */ + if (LED_Blink(Adapter, 1 << GPIO_Num_rx, + uiRxLedIndex, timeout, + num_of_time, currdriverstate) + == EVENT_SIGNALED) + return EVENT_SIGNALED; + } + } + + /* + * If Tx/Rx rate is less than maximum blinks per second, + * wait till delay completes to 1 second + */ + remDelay = MAX_NUM_OF_BLINKS - num_of_time; + if (remDelay > 0) { + timeout = 100 * remDelay; + Status = wait_event_interruptible_timeout( + Adapter->LEDInfo.notify_led_event, + currdriverstate != Adapter->DriverState + || kthread_should_stop(), + msecs_to_jiffies(timeout)); + + if (kthread_should_stop()) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, + LED_DUMP_INFO, DBG_LVL_ALL, + "Led thread got signal to exit..hence exiting"); + Adapter->LEDInfo.led_thread_running = + BCM_LED_THREAD_DISABLED; + return EVENT_SIGNALED; + } + if (Status) + return EVENT_SIGNALED; + } + + /* Turn off both Tx and Rx LEDs before next second */ + TURN_OFF_LED(1 << GPIO_Num_tx, uiTxLedIndex); + TURN_OFF_LED(1 << GPIO_Num_rx, uiTxLedIndex); + + /* + * Read the Tx & Rx packets transmission after 1 second and + * calculate rate of transfer + */ + Final_num_of_packts_tx = Adapter->dev->stats.tx_packets; + Final_num_of_packts_rx = Adapter->dev->stats.rx_packets; + + rate_of_transfer_tx = Final_num_of_packts_tx - + Initial_num_of_packts_tx; + rate_of_transfer_rx = Final_num_of_packts_rx - + Initial_num_of_packts_rx; + + /* Read initial value of packets sent/received */ + Initial_num_of_packts_tx = Final_num_of_packts_tx; + Initial_num_of_packts_rx = Final_num_of_packts_rx; + + /* Scale the rate of transfer to no of blinks. */ + num_of_time_tx = + ScaleRateofTransfer((ULONG)rate_of_transfer_tx); + num_of_time_rx = + ScaleRateofTransfer((ULONG)rate_of_transfer_rx); + + } + return Status; +} + +/* + * ----------------------------------------------------------------------------- + * Procedure: ValidateDSDParamsChecksum + * + * Description: Reads DSD Params and validates checkusm. + * + * Arguments: + * Adapter - Pointer to Adapter structure. + * ulParamOffset - Start offset of the DSD parameter to be read and + * validated. + * usParamLen - Length of the DSD Parameter. + * + * Returns: + * + * ----------------------------------------------------------------------------- + */ +static INT ValidateDSDParamsChecksum(PMINI_ADAPTER Adapter, ULONG ulParamOffset, + USHORT usParamLen) +{ + INT Status = STATUS_SUCCESS; + PUCHAR puBuffer = NULL; + USHORT usChksmOrg = 0; + USHORT usChecksumCalculated = 0; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, DBG_LVL_ALL, + "LED Thread:ValidateDSDParamsChecksum: 0x%lx 0x%X", + ulParamOffset, usParamLen); + + puBuffer = kmalloc(usParamLen, GFP_KERNEL); + if (!puBuffer) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, + "LED Thread: ValidateDSDParamsChecksum Allocation failed"); + return -ENOMEM; + + } + + /* Read the DSD data from the parameter offset. */ + if (STATUS_SUCCESS != BeceemNVMRead(Adapter, (PUINT)puBuffer, + ulParamOffset, usParamLen)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, + "LED Thread: ValidateDSDParamsChecksum BeceemNVMRead failed"); + Status = STATUS_IMAGE_CHECKSUM_MISMATCH; + goto exit; + } + + /* Calculate the checksum of the data read from the DSD parameter. */ + usChecksumCalculated = CFG_CalculateChecksum(puBuffer, usParamLen); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, DBG_LVL_ALL, + "LED Thread: usCheckSumCalculated = 0x%x\n", + usChecksumCalculated); + + /* + * End of the DSD parameter will have a TWO bytes checksum stored in it. + * Read it and compare with the calculated Checksum. + */ + if (STATUS_SUCCESS != BeceemNVMRead(Adapter, (PUINT)&usChksmOrg, + ulParamOffset+usParamLen, 2)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, + "LED Thread: ValidateDSDParamsChecksum BeceemNVMRead failed"); + Status = STATUS_IMAGE_CHECKSUM_MISMATCH; + goto exit; + } + usChksmOrg = ntohs(usChksmOrg); + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, DBG_LVL_ALL, + "LED Thread: usChksmOrg = 0x%x", usChksmOrg); + + /* + * Compare the checksum calculated with the checksum read + * from DSD section + */ + if (usChecksumCalculated ^ usChksmOrg) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, + "LED Thread: ValidateDSDParamsChecksum: Checksums don't match"); + Status = STATUS_IMAGE_CHECKSUM_MISMATCH; + goto exit; + } + +exit: + kfree(puBuffer); + return Status; +} + + +/* + * ----------------------------------------------------------------------------- + * Procedure: ValidateHWParmStructure + * + * Description: Validates HW Parameters. + * + * Arguments: + * Adapter - Pointer to Adapter structure. + * ulHwParamOffset - Start offset of the HW parameter Section to be read + * and validated. + * + * Returns: + * + * ----------------------------------------------------------------------------- + */ +static INT ValidateHWParmStructure(PMINI_ADAPTER Adapter, ULONG ulHwParamOffset) +{ + + INT Status = STATUS_SUCCESS; + USHORT HwParamLen = 0; + /* + * Add DSD start offset to the hwParamOffset to get + * the actual address. + */ + ulHwParamOffset += DSD_START_OFFSET; + + /* Read the Length of HW_PARAM structure */ + BeceemNVMRead(Adapter, (PUINT)&HwParamLen, ulHwParamOffset, 2); + HwParamLen = ntohs(HwParamLen); + if (0 == HwParamLen || HwParamLen > Adapter->uiNVMDSDSize) + return STATUS_IMAGE_CHECKSUM_MISMATCH; + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, DBG_LVL_ALL, + "LED Thread:HwParamLen = 0x%x", HwParamLen); + Status = ValidateDSDParamsChecksum(Adapter, ulHwParamOffset, + HwParamLen); + return Status; +} /* ValidateHWParmStructure() */ + +static int ReadLEDInformationFromEEPROM(PMINI_ADAPTER Adapter, + UCHAR GPIO_Array[]) +{ + int Status = STATUS_SUCCESS; + + ULONG dwReadValue = 0; + USHORT usHwParamData = 0; + USHORT usEEPROMVersion = 0; + UCHAR ucIndex = 0; + UCHAR ucGPIOInfo[32] = {0}; + + BeceemNVMRead(Adapter, (PUINT)&usEEPROMVersion, + EEPROM_VERSION_OFFSET, 2); + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, DBG_LVL_ALL, + "usEEPROMVersion: Minor:0x%X Major:0x%x", + usEEPROMVersion&0xFF, ((usEEPROMVersion>>8)&0xFF)); + + + if (((usEEPROMVersion>>8)&0xFF) < EEPROM_MAP5_MAJORVERSION) { + BeceemNVMRead(Adapter, (PUINT)&usHwParamData, + EEPROM_HW_PARAM_POINTER_ADDRESS, 2); + usHwParamData = ntohs(usHwParamData); + dwReadValue = usHwParamData; + } else { + /* + * Validate Compatibility section and then read HW param + * if compatibility section is valid. + */ + Status = ValidateDSDParamsChecksum(Adapter, + DSD_START_OFFSET, + COMPATIBILITY_SECTION_LENGTH_MAP5); + + if (Status != STATUS_SUCCESS) + return Status; + + BeceemNVMRead(Adapter, (PUINT)&dwReadValue, + EEPROM_HW_PARAM_POINTER_ADDRRES_MAP5, 4); + dwReadValue = ntohl(dwReadValue); + } + + + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, DBG_LVL_ALL, + "LED Thread: Start address of HW_PARAM structure = 0x%lx", + dwReadValue); + + /* + * Validate if the address read out is within the DSD. + * Adapter->uiNVMDSDSize gives whole DSD size inclusive of Autoinit. + * lower limit should be above DSD_START_OFFSET and + * upper limit should be below (Adapter->uiNVMDSDSize-DSD_START_OFFSET) + */ + if (dwReadValue < DSD_START_OFFSET || + dwReadValue > (Adapter->uiNVMDSDSize-DSD_START_OFFSET)) + return STATUS_IMAGE_CHECKSUM_MISMATCH; + + Status = ValidateHWParmStructure(Adapter, dwReadValue); + if (Status) + return Status; + + /* + * Add DSD_START_OFFSET to the offset read from the EEPROM. + * This will give the actual start HW Parameters start address. + * To read GPIO section, add GPIO offset further. + */ + + dwReadValue += + DSD_START_OFFSET; /* = start address of hw param section. */ + dwReadValue += GPIO_SECTION_START_OFFSET; + /* = GPIO start offset within HW Param section. */ + + /* + * Read the GPIO values for 32 GPIOs from EEPROM and map the function + * number to GPIO pin number to GPIO_Array + */ + BeceemNVMRead(Adapter, (UINT *)ucGPIOInfo, dwReadValue, 32); + for (ucIndex = 0; ucIndex < 32; ucIndex++) { + + switch (ucGPIOInfo[ucIndex]) { + case RED_LED: + GPIO_Array[RED_LED] = ucIndex; + Adapter->gpioBitMap |= (1 << ucIndex); + break; + case BLUE_LED: + GPIO_Array[BLUE_LED] = ucIndex; + Adapter->gpioBitMap |= (1 << ucIndex); + break; + case YELLOW_LED: + GPIO_Array[YELLOW_LED] = ucIndex; + Adapter->gpioBitMap |= (1 << ucIndex); + break; + case GREEN_LED: + GPIO_Array[GREEN_LED] = ucIndex; + Adapter->gpioBitMap |= (1 << ucIndex); + break; + default: + break; + } + + } + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, DBG_LVL_ALL, + "GPIO's bit map correspond to LED :0x%X", Adapter->gpioBitMap); + return Status; +} + + +static int ReadConfigFileStructure(PMINI_ADAPTER Adapter, + BOOLEAN *bEnableThread) +{ + int Status = STATUS_SUCCESS; + /* Array to store GPIO numbers from EEPROM */ + UCHAR GPIO_Array[NUM_OF_LEDS+1]; + UINT uiIndex = 0; + UINT uiNum_of_LED_Type = 0; + PUCHAR puCFGData = NULL; + UCHAR bData = 0; + memset(GPIO_Array, DISABLE_GPIO_NUM, NUM_OF_LEDS+1); + + if (!Adapter->pstargetparams || IS_ERR(Adapter->pstargetparams)) { + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, "Target Params not Avail.\n"); + return -ENOENT; + } + + /* Populate GPIO_Array with GPIO numbers for LED functions */ + /* Read the GPIO numbers from EEPROM */ + Status = ReadLEDInformationFromEEPROM(Adapter, GPIO_Array); + if (Status == STATUS_IMAGE_CHECKSUM_MISMATCH) { + *bEnableThread = FALSE; + return STATUS_SUCCESS; + } else if (Status) { + *bEnableThread = FALSE; + return Status; + } + + /* + * CONFIG file read successfully. Deallocate the memory of + * uiFileNameBufferSize + */ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, DBG_LVL_ALL, + "LED Thread: Config file read successfully\n"); + puCFGData = (PUCHAR) &Adapter->pstargetparams->HostDrvrConfig1; + + /* + * Offset for HostDrvConfig1, HostDrvConfig2, HostDrvConfig3 which + * will have the information of LED type, LED on state for different + * driver state and LED blink state. + */ + + for (uiIndex = 0; uiIndex < NUM_OF_LEDS; uiIndex++) { + bData = *puCFGData; + + /* + * Check Bit 8 for polarity. If it is set, + * polarity is reverse polarity + */ + if (bData & 0x80) { + Adapter->LEDInfo.LEDState[uiIndex].BitPolarity = 0; + /* unset the bit 8 */ + bData = bData & 0x7f; + } + + Adapter->LEDInfo.LEDState[uiIndex].LED_Type = bData; + if (bData <= NUM_OF_LEDS) + Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num = + GPIO_Array[bData]; + else + Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num = + DISABLE_GPIO_NUM; + + puCFGData++; + bData = *puCFGData; + Adapter->LEDInfo.LEDState[uiIndex].LED_On_State = bData; + puCFGData++; + bData = *puCFGData; + Adapter->LEDInfo.LEDState[uiIndex].LED_Blink_State = bData; + puCFGData++; + } + + /* + * Check if all the LED settings are disabled. If it is disabled, + * dont launch the LED control thread. + */ + for (uiIndex = 0; uiIndex < NUM_OF_LEDS; uiIndex++) { + if ((Adapter->LEDInfo.LEDState[uiIndex].LED_Type == DISABLE_GPIO_NUM) || + (Adapter->LEDInfo.LEDState[uiIndex].LED_Type == 0x7f) || + (Adapter->LEDInfo.LEDState[uiIndex].LED_Type == 0)) + uiNum_of_LED_Type++; + } + if (uiNum_of_LED_Type >= NUM_OF_LEDS) + *bEnableThread = FALSE; + + return Status; +} + +/* + * ----------------------------------------------------------------------------- + * Procedure: LedGpioInit + * + * Description: Initializes LED GPIOs. Makes the LED GPIOs to OUTPUT mode + * and make the initial state to be OFF. + * + * Arguments: + * Adapter - Pointer to MINI_ADAPTER structure. + * + * Returns: VOID + * + * ----------------------------------------------------------------------------- + */ +static VOID LedGpioInit(PMINI_ADAPTER Adapter) +{ + UINT uiResetValue = 0; + UINT uiIndex = 0; + + /* Set all LED GPIO Mode to output mode */ + if (rdmalt(Adapter, GPIO_MODE_REGISTER, &uiResetValue, + sizeof(uiResetValue)) < 0) + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, "LED Thread: RDM Failed\n"); + for (uiIndex = 0; uiIndex < NUM_OF_LEDS; uiIndex++) { + if (Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num != + DISABLE_GPIO_NUM) + uiResetValue |= (1 << Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num); + TURN_OFF_LED(1 << Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num, + uiIndex); + } + if (wrmalt(Adapter, GPIO_MODE_REGISTER, &uiResetValue, + sizeof(uiResetValue)) < 0) + BCM_DEBUG_PRINT (Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, "LED Thread: WRM Failed\n"); + + Adapter->LEDInfo.bIdle_led_off = FALSE; +} + +static INT BcmGetGPIOPinInfo(PMINI_ADAPTER Adapter, UCHAR *GPIO_num_tx, + UCHAR *GPIO_num_rx, UCHAR *uiLedTxIndex, UCHAR *uiLedRxIndex, + LedEventInfo_t currdriverstate) +{ + UINT uiIndex = 0; + + *GPIO_num_tx = DISABLE_GPIO_NUM; + *GPIO_num_rx = DISABLE_GPIO_NUM; + + for (uiIndex = 0; uiIndex < NUM_OF_LEDS; uiIndex++) { + + if ((currdriverstate == NORMAL_OPERATION) || + (currdriverstate == IDLEMODE_EXIT) || + (currdriverstate == FW_DOWNLOAD)) { + if (Adapter->LEDInfo.LEDState[uiIndex].LED_Blink_State & + currdriverstate) { + if (Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num + != DISABLE_GPIO_NUM) { + if (*GPIO_num_tx == DISABLE_GPIO_NUM) { + *GPIO_num_tx = Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num; + *uiLedTxIndex = uiIndex; + } else { + *GPIO_num_rx = Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num; + *uiLedRxIndex = uiIndex; + } + } + } + } else { + if (Adapter->LEDInfo.LEDState[uiIndex].LED_On_State + & currdriverstate) { + if (Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num + != DISABLE_GPIO_NUM) { + *GPIO_num_tx = Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num; + *uiLedTxIndex = uiIndex; + } + } + } + } + return STATUS_SUCCESS; +} +static VOID LEDControlThread(PMINI_ADAPTER Adapter) +{ + UINT uiIndex = 0; + UCHAR GPIO_num = 0; + UCHAR uiLedIndex = 0; + UINT uiResetValue = 0; + LedEventInfo_t currdriverstate = 0; + ulong timeout = 0; + + INT Status = 0; + + UCHAR dummyGPIONum = 0; + UCHAR dummyIndex = 0; + + /* currdriverstate = Adapter->DriverState; */ + Adapter->LEDInfo.bIdleMode_tx_from_host = FALSE; + + /* + * Wait till event is triggered + * + * wait_event(Adapter->LEDInfo.notify_led_event, + * currdriverstate!= Adapter->DriverState); + */ + + GPIO_num = DISABLE_GPIO_NUM; + + while (TRUE) { + /* Wait till event is triggered */ + if ((GPIO_num == DISABLE_GPIO_NUM) + || + ((currdriverstate != FW_DOWNLOAD) && + (currdriverstate != NORMAL_OPERATION) && + (currdriverstate != LOWPOWER_MODE_ENTER)) + || + (currdriverstate == LED_THREAD_INACTIVE)) + Status = wait_event_interruptible( + Adapter->LEDInfo.notify_led_event, + currdriverstate != Adapter->DriverState + || kthread_should_stop()); + + if (kthread_should_stop() || Adapter->device_removed) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, + "Led thread got signal to exit..hence exiting"); + Adapter->LEDInfo.led_thread_running = + BCM_LED_THREAD_DISABLED; + TURN_OFF_LED(1 << GPIO_num, uiLedIndex); + return; /* STATUS_FAILURE; */ + } + + if (GPIO_num != DISABLE_GPIO_NUM) + TURN_OFF_LED(1 << GPIO_num, uiLedIndex); + + if (Adapter->LEDInfo.bLedInitDone == FALSE) { + LedGpioInit(Adapter); + Adapter->LEDInfo.bLedInitDone = TRUE; + } + + switch (Adapter->DriverState) { + case DRIVER_INIT: + currdriverstate = DRIVER_INIT; + /* Adapter->DriverState; */ + BcmGetGPIOPinInfo(Adapter, &GPIO_num, &dummyGPIONum, + &uiLedIndex, &dummyIndex, currdriverstate); + + if (GPIO_num != DISABLE_GPIO_NUM) + TURN_ON_LED(1 << GPIO_num, uiLedIndex); + + break; + case FW_DOWNLOAD: + /* + * BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, + * LED_DUMP_INFO, DBG_LVL_ALL, + * "LED Thread: FW_DN_DONE called\n"); + */ + currdriverstate = FW_DOWNLOAD; + BcmGetGPIOPinInfo(Adapter, &GPIO_num, &dummyGPIONum, + &uiLedIndex, &dummyIndex, currdriverstate); + + if (GPIO_num != DISABLE_GPIO_NUM) { + timeout = 50; + LED_Blink(Adapter, 1 << GPIO_num, uiLedIndex, + timeout, -1, currdriverstate); + } + break; + case FW_DOWNLOAD_DONE: + currdriverstate = FW_DOWNLOAD_DONE; + BcmGetGPIOPinInfo(Adapter, &GPIO_num, &dummyGPIONum, + &uiLedIndex, &dummyIndex, currdriverstate); + if (GPIO_num != DISABLE_GPIO_NUM) + TURN_ON_LED(1 << GPIO_num, uiLedIndex); + break; + + case SHUTDOWN_EXIT: + /* + * no break, continue to NO_NETWORK_ENTRY + * state as well. + */ + case NO_NETWORK_ENTRY: + currdriverstate = NO_NETWORK_ENTRY; + BcmGetGPIOPinInfo(Adapter, &GPIO_num, &dummyGPIONum, + &uiLedIndex, &dummyGPIONum, currdriverstate); + if (GPIO_num != DISABLE_GPIO_NUM) + TURN_ON_LED(1 << GPIO_num, uiLedIndex); + break; + case NORMAL_OPERATION: + { + UCHAR GPIO_num_tx = DISABLE_GPIO_NUM; + UCHAR GPIO_num_rx = DISABLE_GPIO_NUM; + UCHAR uiLEDTx = 0; + UCHAR uiLEDRx = 0; + currdriverstate = NORMAL_OPERATION; + Adapter->LEDInfo.bIdle_led_off = FALSE; + + BcmGetGPIOPinInfo(Adapter, &GPIO_num_tx, + &GPIO_num_rx, &uiLEDTx, &uiLEDRx, + currdriverstate); + if ((GPIO_num_tx == DISABLE_GPIO_NUM) && + (GPIO_num_rx == + DISABLE_GPIO_NUM)) { + GPIO_num = DISABLE_GPIO_NUM; + } else { + /* + * If single LED is selected, use same + * for both Tx and Rx + */ + if (GPIO_num_tx == DISABLE_GPIO_NUM) { + GPIO_num_tx = GPIO_num_rx; + uiLEDTx = uiLEDRx; + } else if (GPIO_num_rx == + DISABLE_GPIO_NUM) { + GPIO_num_rx = GPIO_num_tx; + uiLEDRx = uiLEDTx; + } + /* + * Blink the LED in proportionate + * to Tx and Rx transmissions. + */ + LED_Proportional_Blink(Adapter, + GPIO_num_tx, uiLEDTx, + GPIO_num_rx, uiLEDRx, + currdriverstate); + } + } + break; + case LOWPOWER_MODE_ENTER: + currdriverstate = LOWPOWER_MODE_ENTER; + if (DEVICE_POWERSAVE_MODE_AS_MANUAL_CLOCK_GATING == + Adapter->ulPowerSaveMode) { + /* Turn OFF all the LED */ + uiResetValue = 0; + for (uiIndex = 0; uiIndex < NUM_OF_LEDS; uiIndex++) { + if (Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num != DISABLE_GPIO_NUM) + TURN_OFF_LED((1 << Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num), uiIndex); + } + + } + /* Turn off LED And WAKE-UP for Sendinf IDLE mode ACK */ + Adapter->LEDInfo.bLedInitDone = FALSE; + Adapter->LEDInfo.bIdle_led_off = TRUE; + wake_up(&Adapter->LEDInfo.idleModeSyncEvent); + GPIO_num = DISABLE_GPIO_NUM; + break; + case IDLEMODE_CONTINUE: + currdriverstate = IDLEMODE_CONTINUE; + GPIO_num = DISABLE_GPIO_NUM; + break; + case IDLEMODE_EXIT: + break; + case DRIVER_HALT: + currdriverstate = DRIVER_HALT; + GPIO_num = DISABLE_GPIO_NUM; + for (uiIndex = 0; uiIndex < NUM_OF_LEDS; uiIndex++) { + if (Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num + != DISABLE_GPIO_NUM) + TURN_OFF_LED((1 << Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num), uiIndex); + } + /* Adapter->DriverState = DRIVER_INIT; */ + break; + case LED_THREAD_INACTIVE: + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, "InActivating LED thread..."); + currdriverstate = LED_THREAD_INACTIVE; + Adapter->LEDInfo.led_thread_running = + BCM_LED_THREAD_RUNNING_INACTIVELY; + Adapter->LEDInfo.bLedInitDone = FALSE; + /* disable ALL LED */ + for (uiIndex = 0; uiIndex < NUM_OF_LEDS; uiIndex++) { + if (Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num + != DISABLE_GPIO_NUM) + TURN_OFF_LED((1 << Adapter->LEDInfo.LEDState[uiIndex].GPIO_Num), uiIndex); + } + break; + case LED_THREAD_ACTIVE: + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, "Activating LED thread again..."); + if (Adapter->LinkUpStatus == FALSE) + Adapter->DriverState = NO_NETWORK_ENTRY; + else + Adapter->DriverState = NORMAL_OPERATION; + + Adapter->LEDInfo.led_thread_running = + BCM_LED_THREAD_RUNNING_ACTIVELY; + break; + /* return; */ + default: + break; + } + } + Adapter->LEDInfo.led_thread_running = BCM_LED_THREAD_DISABLED; +} + +int InitLedSettings(PMINI_ADAPTER Adapter) +{ + int Status = STATUS_SUCCESS; + BOOLEAN bEnableThread = TRUE; + UCHAR uiIndex = 0; + + /* + * Initially set BitPolarity to normal polarity. The bit 8 of LED type + * is used to change the polarity of the LED. + */ + + for (uiIndex = 0; uiIndex < NUM_OF_LEDS; uiIndex++) + Adapter->LEDInfo.LEDState[uiIndex].BitPolarity = 1; + + /* + * Read the LED settings of CONFIG file and map it + * to GPIO numbers in EEPROM + */ + Status = ReadConfigFileStructure(Adapter, &bEnableThread); + if (STATUS_SUCCESS != Status) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, + "LED Thread: FAILED in ReadConfigFileStructure\n"); + return Status; + } + + if (Adapter->LEDInfo.led_thread_running) { + if (bEnableThread) { + ; + } else { + Adapter->DriverState = DRIVER_HALT; + wake_up(&Adapter->LEDInfo.notify_led_event); + Adapter->LEDInfo.led_thread_running = + BCM_LED_THREAD_DISABLED; + } + + } else if (bEnableThread) { + /* Create secondary thread to handle the LEDs */ + init_waitqueue_head(&Adapter->LEDInfo.notify_led_event); + init_waitqueue_head(&Adapter->LEDInfo.idleModeSyncEvent); + Adapter->LEDInfo.led_thread_running = + BCM_LED_THREAD_RUNNING_ACTIVELY; + Adapter->LEDInfo.bIdle_led_off = FALSE; + Adapter->LEDInfo.led_cntrl_threadid = + kthread_run((int (*)(void *)) LEDControlThread, + Adapter, "led_control_thread"); + if (IS_ERR(Adapter->LEDInfo.led_cntrl_threadid)) { + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, LED_DUMP_INFO, + DBG_LVL_ALL, + "Not able to spawn Kernel Thread\n"); + Adapter->LEDInfo.led_thread_running = + BCM_LED_THREAD_DISABLED; + return PTR_ERR(Adapter->LEDInfo.led_cntrl_threadid); + } + } + return Status; +} diff --git a/drivers/staging/bcm/led_control.h b/drivers/staging/bcm/led_control.h new file mode 100644 index 00000000..ed8fbc09 --- /dev/null +++ b/drivers/staging/bcm/led_control.h @@ -0,0 +1,102 @@ +#ifndef _LED_CONTROL_H +#define _LED_CONTROL_H + +/*************************TYPE DEF**********************/ +#define NUM_OF_LEDS 4 + +#define DSD_START_OFFSET 0x0200 +#define EEPROM_VERSION_OFFSET 0x020E +#define EEPROM_HW_PARAM_POINTER_ADDRESS 0x0218 +#define EEPROM_HW_PARAM_POINTER_ADDRRES_MAP5 0x0220 +#define GPIO_SECTION_START_OFFSET 0x03 + +#define COMPATIBILITY_SECTION_LENGTH 42 +#define COMPATIBILITY_SECTION_LENGTH_MAP5 84 + + +#define EEPROM_MAP5_MAJORVERSION 5 +#define EEPROM_MAP5_MINORVERSION 0 + + +#define MAX_NUM_OF_BLINKS 10 +#define NUM_OF_GPIO_PINS 16 + +#define DISABLE_GPIO_NUM 0xFF +#define EVENT_SIGNALED 1 + +#define MAX_FILE_NAME_BUFFER_SIZE 100 + +#define TURN_ON_LED(GPIO, index) do { \ + UINT gpio_val = GPIO; \ + (Adapter->LEDInfo.LEDState[index].BitPolarity == 1) ? \ + wrmaltWithLock(Adapter, BCM_GPIO_OUTPUT_SET_REG, &gpio_val, sizeof(gpio_val)) : \ + wrmaltWithLock(Adapter, BCM_GPIO_OUTPUT_CLR_REG, &gpio_val, sizeof(gpio_val)); \ + } while (0); + +#define TURN_OFF_LED(GPIO, index) do { \ + UINT gpio_val = GPIO; \ + (Adapter->LEDInfo.LEDState[index].BitPolarity == 1) ? \ + wrmaltWithLock(Adapter, BCM_GPIO_OUTPUT_CLR_REG, &gpio_val, sizeof(gpio_val)) : \ + wrmaltWithLock(Adapter, BCM_GPIO_OUTPUT_SET_REG, &gpio_val, sizeof(gpio_val)); \ + } while (0); + +#define B_ULONG32 unsigned long + +/*******************************************************/ + + +typedef enum _LEDColors{ + RED_LED = 1, + BLUE_LED = 2, + YELLOW_LED = 3, + GREEN_LED = 4 +} LEDColors; /*Enumerated values of different LED types*/ + +typedef enum LedEvents { + SHUTDOWN_EXIT = 0x00, + DRIVER_INIT = 0x1, + FW_DOWNLOAD = 0x2, + FW_DOWNLOAD_DONE = 0x4, + NO_NETWORK_ENTRY = 0x8, + NORMAL_OPERATION = 0x10, + LOWPOWER_MODE_ENTER = 0x20, + IDLEMODE_CONTINUE = 0x40, + IDLEMODE_EXIT = 0x80, + LED_THREAD_INACTIVE = 0x100, /* Makes the LED thread Inactivce. It wil be equivallent to putting the thread on hold. */ + LED_THREAD_ACTIVE = 0x200, /* Makes the LED Thread Active back. */ + DRIVER_HALT = 0xff +} LedEventInfo_t; /* Enumerated values of different driver states */ + +/* + * Structure which stores the information of different LED types + * and corresponding LED state information of driver states + */ +typedef struct LedStateInfo_t { + UCHAR LED_Type; /* specify GPIO number - use 0xFF if not used */ + UCHAR LED_On_State; /* Bits set or reset for different states */ + UCHAR LED_Blink_State; /* Bits set or reset for blinking LEDs for different states */ + UCHAR GPIO_Num; + UCHAR BitPolarity; /* To represent whether H/W is normal polarity or reverse polarity */ +} LEDStateInfo, *pLEDStateInfo; + + +typedef struct _LED_INFO_STRUCT { + LEDStateInfo LEDState[NUM_OF_LEDS]; + BOOLEAN bIdleMode_tx_from_host; /* Variable to notify whether driver came out from idlemode due to Host or target*/ + BOOLEAN bIdle_led_off; + wait_queue_head_t notify_led_event; + wait_queue_head_t idleModeSyncEvent; + struct task_struct *led_cntrl_threadid; + int led_thread_running; + BOOLEAN bLedInitDone; + +} LED_INFO_STRUCT, *PLED_INFO_STRUCT; +/* LED Thread state. */ +#define BCM_LED_THREAD_DISABLED 0 /* LED Thread is not running. */ +#define BCM_LED_THREAD_RUNNING_ACTIVELY 1 /* LED thread is running. */ +#define BCM_LED_THREAD_RUNNING_INACTIVELY 2 /*LED thread has been put on hold*/ + + + +#endif + diff --git a/drivers/staging/bcm/nvm.c b/drivers/staging/bcm/nvm.c new file mode 100644 index 00000000..7d703cb3 --- /dev/null +++ b/drivers/staging/bcm/nvm.c @@ -0,0 +1,5222 @@ +#include "headers.h" + +#define DWORD unsigned int + +static INT BcmDoChipSelect(PMINI_ADAPTER Adapter, UINT offset); +static INT BcmGetActiveDSD(PMINI_ADAPTER Adapter); +static INT BcmGetActiveISO(PMINI_ADAPTER Adapter); +static UINT BcmGetEEPROMSize(PMINI_ADAPTER Adapter); +static INT BcmGetFlashCSInfo(PMINI_ADAPTER Adapter); +static UINT BcmGetFlashSectorSize(PMINI_ADAPTER Adapter, UINT FlashSectorSizeSig, UINT FlashSectorSize); + +static VOID BcmValidateNvmType(PMINI_ADAPTER Adapter); +static INT BcmGetNvmSize(PMINI_ADAPTER Adapter); +static UINT BcmGetFlashSize(PMINI_ADAPTER Adapter); +static NVM_TYPE BcmGetNvmType(PMINI_ADAPTER Adapter); + +static INT BcmGetSectionValEndOffset(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlash2xSectionVal); + +static B_UINT8 IsOffsetWritable(PMINI_ADAPTER Adapter, UINT uiOffset); +static INT IsSectionWritable(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL Section); +static INT IsSectionExistInVendorInfo(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL section); + +static INT ReadDSDPriority(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL dsd); +static INT ReadDSDSignature(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL dsd); +static INT ReadISOPriority(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL iso); +static INT ReadISOSignature(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL iso); + +static INT CorruptDSDSig(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlash2xSectionVal); +static INT CorruptISOSig(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlash2xSectionVal); +static INT SaveHeaderIfPresent(PMINI_ADAPTER Adapter, PUCHAR pBuff, UINT uiSectAlignAddr); +static INT WriteToFlashWithoutSectorErase(PMINI_ADAPTER Adapter, PUINT pBuff, + FLASH2X_SECTION_VAL eFlash2xSectionVal, + UINT uiOffset, UINT uiNumBytes); +static FLASH2X_SECTION_VAL getHighestPriDSD(PMINI_ADAPTER Adapter); +static FLASH2X_SECTION_VAL getHighestPriISO(PMINI_ADAPTER Adapter); + +static INT BeceemFlashBulkRead( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + UINT uiOffset, + UINT uiNumBytes); + +static INT BeceemFlashBulkWrite( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + UINT uiOffset, + UINT uiNumBytes, + BOOLEAN bVerify); + +static INT GetFlashBaseAddr(PMINI_ADAPTER Adapter); + +static INT ReadBeceemEEPROMBulk(PMINI_ADAPTER Adapter,UINT dwAddress, UINT *pdwData, UINT dwNumData); + +// Procedure: ReadEEPROMStatusRegister +// +// Description: Reads the standard EEPROM Status Register. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + +static UCHAR ReadEEPROMStatusRegister( PMINI_ADAPTER Adapter ) +{ + UCHAR uiData = 0; + DWORD dwRetries = MAX_EEPROM_RETRIES*RETRIES_PER_DELAY; + UINT uiStatus = 0; + UINT value = 0; + UINT value1 = 0; + + /* Read the EEPROM status register */ + value = EEPROM_READ_STATUS_REGISTER ; + wrmalt( Adapter, EEPROM_CMDQ_SPI_REG, &value, sizeof(value)); + + while ( dwRetries != 0 ) + { + value=0; + uiStatus = 0 ; + rdmalt(Adapter, EEPROM_SPI_Q_STATUS1_REG, &uiStatus, sizeof(uiStatus)); + if(Adapter->device_removed == TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Modem has got removed hence exiting...."); + break; + } + + /* Wait for Avail bit to be set. */ + if ( ( uiStatus & EEPROM_READ_DATA_AVAIL) != 0 ) + { + /* Clear the Avail/Full bits - which ever is set. */ + value = uiStatus & (EEPROM_READ_DATA_AVAIL | EEPROM_READ_DATA_FULL); + wrmalt( Adapter, EEPROM_SPI_Q_STATUS1_REG, &value, sizeof(value)); + + value =0; + rdmalt(Adapter, EEPROM_READ_DATAQ_REG, &value, sizeof(value)); + uiData = (UCHAR)value; + + break; + } + + dwRetries-- ; + if ( dwRetries == 0 ) + { + rdmalt(Adapter, EEPROM_SPI_Q_STATUS1_REG, &value, sizeof(value)); + rdmalt(Adapter, EEPROM_SPI_Q_STATUS_REG, &value1, sizeof(value1)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"0x3004 = %x 0x3008 = %x, retries = %d failed.\n",value,value1, MAX_EEPROM_RETRIES*RETRIES_PER_DELAY); + return uiData; + } + if( !(dwRetries%RETRIES_PER_DELAY) ) + msleep(1); + uiStatus = 0 ; + } + return uiData; +} /* ReadEEPROMStatusRegister */ + +//----------------------------------------------------------------------------- +// Procedure: ReadBeceemEEPROMBulk +// +// Description: This routine reads 16Byte data from EEPROM +// +// Arguments: +// Adapter - ptr to Adapter object instance +// dwAddress - EEPROM Offset to read the data from. +// pdwData - Pointer to double word where data needs to be stored in. // dwNumWords - Number of words. Valid values are 4 ONLY. +// +// Returns: +// OSAL_STATUS_CODE: +//----------------------------------------------------------------------------- + +INT ReadBeceemEEPROMBulk( PMINI_ADAPTER Adapter, + DWORD dwAddress, + DWORD *pdwData, + DWORD dwNumWords + ) +{ + DWORD dwIndex = 0; + DWORD dwRetries = MAX_EEPROM_RETRIES*RETRIES_PER_DELAY; + UINT uiStatus = 0; + UINT value= 0; + UINT value1 = 0; + UCHAR *pvalue; + + /* Flush the read and cmd queue. */ + value=( EEPROM_READ_QUEUE_FLUSH | EEPROM_CMD_QUEUE_FLUSH ); + wrmalt( Adapter, SPI_FLUSH_REG, &value, sizeof(value) ); + value=0; + wrmalt( Adapter, SPI_FLUSH_REG, &value, sizeof(value)); + + /* Clear the Avail/Full bits. */ + value=( EEPROM_READ_DATA_AVAIL | EEPROM_READ_DATA_FULL ); + wrmalt( Adapter, EEPROM_SPI_Q_STATUS1_REG,&value, sizeof(value)); + + value= dwAddress | ( (dwNumWords == 4) ? EEPROM_16_BYTE_PAGE_READ : EEPROM_4_BYTE_PAGE_READ ); + wrmalt( Adapter, EEPROM_CMDQ_SPI_REG, &value, sizeof(value)); + + while ( dwRetries != 0 ) + { + + uiStatus = 0; + rdmalt(Adapter, EEPROM_SPI_Q_STATUS1_REG, &uiStatus, sizeof(uiStatus)); + if(Adapter->device_removed == TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Modem has got Removed.hence exiting from loop..."); + return -ENODEV; + } + + /* If we are reading 16 bytes we want to be sure that the queue + * is full before we read. In the other cases we are ok if the + * queue has data available */ + if ( dwNumWords == 4 ) + { + if ( ( uiStatus & EEPROM_READ_DATA_FULL ) != 0 ) + { + /* Clear the Avail/Full bits - which ever is set. */ + value = ( uiStatus & (EEPROM_READ_DATA_AVAIL | EEPROM_READ_DATA_FULL) ) ; + wrmalt( Adapter, EEPROM_SPI_Q_STATUS1_REG,&value, sizeof(value)); + break; + } + } + else if ( dwNumWords == 1 ) + { + + if ( ( uiStatus & EEPROM_READ_DATA_AVAIL ) != 0 ) + { + /* We just got Avail and we have to read 32bits so we + * need this sleep for Cardbus kind of devices. */ + if (Adapter->chip_id == 0xBECE0210 ) + udelay(800); + + /* Clear the Avail/Full bits - which ever is set. */ + value=( uiStatus & (EEPROM_READ_DATA_AVAIL | EEPROM_READ_DATA_FULL) ); + wrmalt( Adapter, EEPROM_SPI_Q_STATUS1_REG,&value, sizeof(value)); + break; + } + } + + uiStatus = 0; + + dwRetries--; + if(dwRetries == 0) + { + value=0; + value1=0; + rdmalt(Adapter, EEPROM_SPI_Q_STATUS1_REG, &value, sizeof(value)); + rdmalt(Adapter, EEPROM_SPI_Q_STATUS_REG, &value1, sizeof(value1)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "dwNumWords %d 0x3004 = %x 0x3008 = %x retries = %d failed.\n", dwNumWords, value, value1, MAX_EEPROM_RETRIES*RETRIES_PER_DELAY); + return STATUS_FAILURE; + } + if( !(dwRetries%RETRIES_PER_DELAY) ) + msleep(1); + } + + for ( dwIndex = 0; dwIndex < dwNumWords ; dwIndex++ ) + { + /* We get only a byte at a time - from LSB to MSB. We shift it into an integer. */ + pvalue = (PUCHAR)(pdwData + dwIndex); + + value =0; + rdmalt(Adapter, EEPROM_READ_DATAQ_REG, &value, sizeof(value)); + + pvalue[0] = value; + + value = 0; + rdmalt(Adapter, EEPROM_READ_DATAQ_REG, &value, sizeof(value)); + + pvalue[1] = value; + + value =0; + rdmalt(Adapter, EEPROM_READ_DATAQ_REG, &value, sizeof(value)); + + pvalue[2] = value; + + value = 0; + rdmalt(Adapter, EEPROM_READ_DATAQ_REG, &value, sizeof(value)); + + pvalue[3] = value; + } + + return STATUS_SUCCESS; +} /* ReadBeceemEEPROMBulk() */ + +//----------------------------------------------------------------------------- +// Procedure: ReadBeceemEEPROM +// +// Description: This routine reads 4 data from EEPROM. It uses 1 or 2 page +// reads to do this operation. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// uiOffset - EEPROM Offset to read the data from. +// pBuffer - Pointer to word where data needs to be stored in. +// +// Returns: +// OSAL_STATUS_CODE: +//----------------------------------------------------------------------------- + +INT ReadBeceemEEPROM( PMINI_ADAPTER Adapter, + DWORD uiOffset, + DWORD *pBuffer + ) +{ + UINT uiData[8] = {0}; + UINT uiByteOffset = 0; + UINT uiTempOffset = 0; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL," ====> "); + + uiTempOffset = uiOffset - (uiOffset % MAX_RW_SIZE); + uiByteOffset = uiOffset - uiTempOffset; + + ReadBeceemEEPROMBulk(Adapter, uiTempOffset, (PUINT)&uiData[0], 4); + + /* A word can overlap at most over 2 pages. In that case we read the + * next page too. */ + if ( uiByteOffset > 12 ) + { + ReadBeceemEEPROMBulk(Adapter, uiTempOffset + MAX_RW_SIZE, (PUINT)&uiData[4], 4); + } + + memcpy( (PUCHAR) pBuffer, ( ((PUCHAR)&uiData[0]) + uiByteOffset ), 4); + + return STATUS_SUCCESS; +} /* ReadBeceemEEPROM() */ + + + +INT ReadMacAddressFromNVM(PMINI_ADAPTER Adapter) +{ + INT Status; + unsigned char puMacAddr[6]; + + Status = BeceemNVMRead(Adapter, + (PUINT)&puMacAddr[0], + INIT_PARAMS_1_MACADDRESS_ADDRESS, + MAC_ADDRESS_SIZE); + + if(Status == STATUS_SUCCESS) + memcpy(Adapter->dev->dev_addr, puMacAddr, MAC_ADDRESS_SIZE); + + return Status; +} + +//----------------------------------------------------------------------------- +// Procedure: BeceemEEPROMBulkRead +// +// Description: Reads the EEPROM and returns the Data. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// pBuffer - Buffer to store the data read from EEPROM +// uiOffset - Offset of EEPROM from where data should be read +// uiNumBytes - Number of bytes to be read from the EEPROM. +// +// Returns: +// OSAL_STATUS_SUCCESS - if EEPROM read is successful. +// - if failed. +//----------------------------------------------------------------------------- + +INT BeceemEEPROMBulkRead( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + UINT uiOffset, + UINT uiNumBytes) +{ + UINT uiData[4] = {0}; + //UINT uiAddress = 0; + UINT uiBytesRemaining = uiNumBytes; + UINT uiIndex = 0; + UINT uiTempOffset = 0; + UINT uiExtraBytes = 0; + UINT uiFailureRetries = 0; + PUCHAR pcBuff = (PUCHAR)pBuffer; + + + if(uiOffset%MAX_RW_SIZE&& uiBytesRemaining) + { + uiTempOffset = uiOffset - (uiOffset%MAX_RW_SIZE); + uiExtraBytes = uiOffset-uiTempOffset; + ReadBeceemEEPROMBulk(Adapter,uiTempOffset,(PUINT)&uiData[0],4); + if(uiBytesRemaining >= (MAX_RW_SIZE - uiExtraBytes)) + { + memcpy(pBuffer,(((PUCHAR)&uiData[0])+uiExtraBytes),MAX_RW_SIZE - uiExtraBytes); + + uiBytesRemaining -= (MAX_RW_SIZE - uiExtraBytes); + uiIndex += (MAX_RW_SIZE - uiExtraBytes); + uiOffset += (MAX_RW_SIZE - uiExtraBytes); + } + else + { + memcpy(pBuffer,(((PUCHAR)&uiData[0])+uiExtraBytes),uiBytesRemaining); + uiIndex += uiBytesRemaining; + uiOffset += uiBytesRemaining; + uiBytesRemaining = 0; + } + + + } + + + while(uiBytesRemaining && uiFailureRetries != 128) + { + if(Adapter->device_removed ) + { + return -1; + } + + if(uiBytesRemaining >= MAX_RW_SIZE) + { + /* For the requests more than or equal to 16 bytes, use bulk + * read function to make the access faster. + * We read 4 Dwords of data */ + if(0 == ReadBeceemEEPROMBulk(Adapter,uiOffset,&uiData[0],4)) + { + memcpy(pcBuff+uiIndex,&uiData[0],MAX_RW_SIZE); + uiOffset += MAX_RW_SIZE; + uiBytesRemaining -= MAX_RW_SIZE; + uiIndex += MAX_RW_SIZE; + } + else + { + uiFailureRetries++; + mdelay(3);//sleep for a while before retry... + } + } + else if(uiBytesRemaining >= 4) + { + if(0 == ReadBeceemEEPROM(Adapter,uiOffset,&uiData[0])) + { + memcpy(pcBuff+uiIndex,&uiData[0],4); + uiOffset += 4; + uiBytesRemaining -= 4; + uiIndex +=4; + } + else + { + uiFailureRetries++; + mdelay(3);//sleep for a while before retry... + } + } + else + { // Handle the reads less than 4 bytes... + PUCHAR pCharBuff = (PUCHAR)pBuffer; + pCharBuff += uiIndex; + if(0 == ReadBeceemEEPROM(Adapter,uiOffset,&uiData[0])) + { + memcpy(pCharBuff,&uiData[0],uiBytesRemaining);//copy only bytes requested. + uiBytesRemaining = 0; + } + else + { + uiFailureRetries++; + mdelay(3);//sleep for a while before retry... + } + } + + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Procedure: BeceemFlashBulkRead +// +// Description: Reads the FLASH and returns the Data. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// pBuffer - Buffer to store the data read from FLASH +// uiOffset - Offset of FLASH from where data should be read +// uiNumBytes - Number of bytes to be read from the FLASH. +// +// Returns: +// OSAL_STATUS_SUCCESS - if FLASH read is successful. +// - if failed. +//----------------------------------------------------------------------------- + +static INT BeceemFlashBulkRead( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + UINT uiOffset, + UINT uiNumBytes) +{ + UINT uiIndex = 0; + UINT uiBytesToRead = uiNumBytes; + INT Status = 0; + UINT uiPartOffset = 0; + int bytes; + + if(Adapter->device_removed ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Device Got Removed "); + return -ENODEV; + } + + //Adding flash Base address +// uiOffset = uiOffset + GetFlashBaseAddr(Adapter); +#if defined(BCM_SHM_INTERFACE) && !defined(FLASH_DIRECT_ACCESS) + Status = bcmflash_raw_read((uiOffset/FLASH_PART_SIZE),(uiOffset % FLASH_PART_SIZE),( unsigned char *)pBuffer,uiNumBytes); + return Status; +#endif + + Adapter->SelectedChip = RESET_CHIP_SELECT; + + if(uiOffset % MAX_RW_SIZE) + { + BcmDoChipSelect(Adapter,uiOffset); + uiPartOffset = (uiOffset & (FLASH_PART_SIZE - 1)) + GetFlashBaseAddr(Adapter); + + uiBytesToRead = MAX_RW_SIZE - (uiOffset%MAX_RW_SIZE); + uiBytesToRead = MIN(uiNumBytes,uiBytesToRead); + + bytes = rdm(Adapter, uiPartOffset, (PCHAR)pBuffer+uiIndex, uiBytesToRead); + if (bytes < 0) { + Status = bytes; + Adapter->SelectedChip = RESET_CHIP_SELECT; + return Status; + } + + uiIndex += uiBytesToRead; + uiOffset += uiBytesToRead; + uiNumBytes -= uiBytesToRead; + } + + while(uiNumBytes) + { + BcmDoChipSelect(Adapter,uiOffset); + uiPartOffset = (uiOffset & (FLASH_PART_SIZE - 1)) + GetFlashBaseAddr(Adapter); + + uiBytesToRead = MIN(uiNumBytes,MAX_RW_SIZE); + + bytes = rdm(Adapter, uiPartOffset, (PCHAR)pBuffer+uiIndex, uiBytesToRead); + if (bytes < 0) { + Status = bytes; + break; + } + + + uiIndex += uiBytesToRead; + uiOffset += uiBytesToRead; + uiNumBytes -= uiBytesToRead; + + } + Adapter->SelectedChip = RESET_CHIP_SELECT; + return Status; +} + +//----------------------------------------------------------------------------- +// Procedure: BcmGetFlashSize +// +// Description: Finds the size of FLASH. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// +// Returns: +// UINT - size of the FLASH Storage. +// +//----------------------------------------------------------------------------- + +static UINT BcmGetFlashSize(PMINI_ADAPTER Adapter) +{ + if(IsFlash2x(Adapter)) + return (Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + sizeof(DSD_HEADER)); + else + return 32*1024; + + +} + +//----------------------------------------------------------------------------- +// Procedure: BcmGetEEPROMSize +// +// Description: Finds the size of EEPROM. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// +// Returns: +// UINT - size of the EEPROM Storage. +// +//----------------------------------------------------------------------------- + +static UINT BcmGetEEPROMSize(PMINI_ADAPTER Adapter) +{ + UINT uiData = 0; + UINT uiIndex = 0; + +// +// if EEPROM is present and already Calibrated,it will have +// 'BECM' string at 0th offset. +// To find the EEPROM size read the possible boundaries of the +// EEPROM like 4K,8K etc..accessing the EEPROM beyond its size will +// result in wrap around. So when we get the End of the EEPROM we will +// get 'BECM' string which is indeed at offset 0. +// + BeceemEEPROMBulkRead(Adapter,&uiData,0x0,4); + if(uiData == BECM) + { + for(uiIndex = 2;uiIndex <=256; uiIndex*=2) + { + BeceemEEPROMBulkRead(Adapter,&uiData,uiIndex*1024,4); + if(uiData == BECM) + { + return uiIndex*1024; + } + } + } + else + { +// +// EEPROM may not be present or not programmed +// + + uiData = 0xBABEFACE; + if(0 == BeceemEEPROMBulkWrite(Adapter,(PUCHAR)&uiData,0,4,TRUE)) + { + uiData = 0; + for(uiIndex = 2;uiIndex <=256; uiIndex*=2) + { + BeceemEEPROMBulkRead(Adapter,&uiData,uiIndex*1024,4); + if(uiData == 0xBABEFACE) + { + return uiIndex*1024; + } + } + } + + } + return 0; +} + + +//----------------------------------------------------------------------------- +// Procedure: FlashSectorErase +// +// Description: Finds the sector size of the FLASH. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// addr - sector start address +// numOfSectors - number of sectors to be erased. +// +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + + +static INT FlashSectorErase(PMINI_ADAPTER Adapter, + UINT addr, + UINT numOfSectors) +{ + UINT iIndex = 0, iRetries = 0; + UINT uiStatus = 0; + UINT value; + int bytes; + + for(iIndex=0;iIndexuiSectorSize; + } + return 0; +} +//----------------------------------------------------------------------------- +// Procedure: flashByteWrite +// +// Description: Performs Byte by Byte write to flash +// +// Arguments: +// Adapter - ptr to Adapter object instance +// uiOffset - Offset of the flash where data needs to be written to. +// pData - Address of Data to be written. +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + +static INT flashByteWrite( + PMINI_ADAPTER Adapter, + UINT uiOffset, + PVOID pData) +{ + + UINT uiStatus = 0; + INT iRetries = MAX_FLASH_RETRIES * FLASH_PER_RETRIES_DELAY; //3 + + UINT value; + ULONG ulData = *(PUCHAR)pData; + int bytes; + +// +// need not write 0xFF because write requires an erase and erase will +// make whole sector 0xFF. +// + + if(0xFF == ulData) + { + return STATUS_SUCCESS; + } + +// DumpDebug(NVM_RW,("flashWrite ====>\n")); + value = (FLASH_CMD_WRITE_ENABLE << 24); + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG,&value, sizeof(value)) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Write enable in FLASH_SPI_CMDQ_REG register fails"); + return STATUS_FAILURE; + } + if(wrm(Adapter,FLASH_SPI_WRITEQ_REG, (PCHAR)&ulData, 4) < 0 ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"DATA Write on FLASH_SPI_WRITEQ_REG fails"); + return STATUS_FAILURE; + } + value = (0x02000000 | (uiOffset & 0xFFFFFF)); + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG,&value, sizeof(value)) < 0 ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Programming of FLASH_SPI_CMDQ_REG fails"); + return STATUS_FAILURE; + } + + //__udelay(950); + + do + { + value = (FLASH_CMD_STATUS_REG_READ << 24); + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value)) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Programing of FLASH_SPI_CMDQ_REG fails"); + return STATUS_FAILURE; + } + //__udelay(1); + bytes = rdmalt(Adapter, FLASH_SPI_READQ_REG, &uiStatus, sizeof(uiStatus)); + if (bytes < 0) { + uiStatus = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Reading status of FLASH_SPI_READQ_REG fails"); + return uiStatus; + } + iRetries--; + if( iRetries && ((iRetries % FLASH_PER_RETRIES_DELAY) == 0)) + msleep(1); + + }while((uiStatus & 0x1) && (iRetries >0) ); + + if(uiStatus & 0x1) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Flash Write fails even after checking status for 200 times."); + return STATUS_FAILURE ; + } + + return STATUS_SUCCESS; +} + + + +//----------------------------------------------------------------------------- +// Procedure: flashWrite +// +// Description: Performs write to flash +// +// Arguments: +// Adapter - ptr to Adapter object instance +// uiOffset - Offset of the flash where data needs to be written to. +// pData - Address of Data to be written. +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + +static INT flashWrite( + PMINI_ADAPTER Adapter, + UINT uiOffset, + PVOID pData) + +{ + //UINT uiStatus = 0; + //INT iRetries = 0; + //UINT uiReadBack = 0; + + UINT uiStatus = 0; + INT iRetries = MAX_FLASH_RETRIES * FLASH_PER_RETRIES_DELAY; //3 + + UINT value; + UINT uiErasePattern[4] = {0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF}; + int bytes; +// +// need not write 0xFFFFFFFF because write requires an erase and erase will +// make whole sector 0xFFFFFFFF. +// + if (!memcmp(pData, uiErasePattern, MAX_RW_SIZE)) + { + return 0; + } + + value = (FLASH_CMD_WRITE_ENABLE << 24); + + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG,&value, sizeof(value)) < 0 ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Write Enable of FLASH_SPI_CMDQ_REG fails"); + return STATUS_FAILURE; + } + if(wrm(Adapter, uiOffset, (PCHAR)pData, MAX_RW_SIZE) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Data write fails..."); + return STATUS_FAILURE; + } + + //__udelay(950); + do + { + value = (FLASH_CMD_STATUS_REG_READ << 24); + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value)) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Programing of FLASH_SPI_CMDQ_REG fails"); + return STATUS_FAILURE; + } + //__udelay(1); + bytes = rdmalt(Adapter, FLASH_SPI_READQ_REG, &uiStatus, sizeof(uiStatus)); + if (bytes < 0) { + uiStatus = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Reading status of FLASH_SPI_READQ_REG fails"); + return uiStatus; + } + + iRetries--; + //this will ensure that in there will be no changes in the current path. + //currently one rdm/wrm takes 125 us. + //Hence 125 *2 * FLASH_PER_RETRIES_DELAY > 3 ms(worst case delay) + //Hence current implementation cycle will intoduce no delay in current path + if(iRetries && ((iRetries % FLASH_PER_RETRIES_DELAY) == 0)) + msleep(1); + }while((uiStatus & 0x1) && (iRetries > 0)); + + if(uiStatus & 0x1) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Flash Write fails even after checking status for 200 times."); + return STATUS_FAILURE ; + } + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// Procedure: flashByteWriteStatus +// +// Description: Performs byte by byte write to flash with write done status check +// +// Arguments: +// Adapter - ptr to Adapter object instance +// uiOffset - Offset of the flash where data needs to be written to. +// pData - Address of the Data to be written. +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- +static INT flashByteWriteStatus( + PMINI_ADAPTER Adapter, + UINT uiOffset, + PVOID pData) +{ + UINT uiStatus = 0; + INT iRetries = MAX_FLASH_RETRIES * FLASH_PER_RETRIES_DELAY; //3 + ULONG ulData = *(PUCHAR)pData; + UINT value; + int bytes; + +// +// need not write 0xFFFFFFFF because write requires an erase and erase will +// make whole sector 0xFFFFFFFF. +// + + if(0xFF == ulData) + { + return STATUS_SUCCESS; + } + + // DumpDebug(NVM_RW,("flashWrite ====>\n")); + + value = (FLASH_CMD_WRITE_ENABLE << 24); + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG,&value, sizeof(value)) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Write enable in FLASH_SPI_CMDQ_REG register fails"); + return STATUS_SUCCESS; + } + if(wrm(Adapter,FLASH_SPI_WRITEQ_REG, (PCHAR)&ulData, 4) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"DATA Write on FLASH_SPI_WRITEQ_REG fails"); + return STATUS_FAILURE; + } + value = (0x02000000 | (uiOffset & 0xFFFFFF)); + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG,&value, sizeof(value)) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Programming of FLASH_SPI_CMDQ_REG fails"); + return STATUS_FAILURE; + } + + //msleep(1); + + do + { + value = (FLASH_CMD_STATUS_REG_READ << 24); + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value)) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Programing of FLASH_SPI_CMDQ_REG fails"); + return STATUS_FAILURE; + } + //__udelay(1); + bytes = rdmalt(Adapter, FLASH_SPI_READQ_REG, &uiStatus, sizeof(uiStatus)); + if (bytes < 0) { + uiStatus = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Reading status of FLASH_SPI_READQ_REG fails"); + return uiStatus; + } + + iRetries--; + if( iRetries && ((iRetries % FLASH_PER_RETRIES_DELAY) == 0)) + msleep(1); + }while((uiStatus & 0x1) && (iRetries > 0)); + + if(uiStatus & 0x1) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Flash Write fails even after checking status for 200 times."); + return STATUS_FAILURE ; + } + + return STATUS_SUCCESS; + +} +//----------------------------------------------------------------------------- +// Procedure: flashWriteStatus +// +// Description: Performs write to flash with write done status check +// +// Arguments: +// Adapter - ptr to Adapter object instance +// uiOffset - Offset of the flash where data needs to be written to. +// pData - Address of the Data to be written. +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + +static INT flashWriteStatus( + PMINI_ADAPTER Adapter, + UINT uiOffset, + PVOID pData) +{ + UINT uiStatus = 0; + INT iRetries = MAX_FLASH_RETRIES * FLASH_PER_RETRIES_DELAY; //3 + //UINT uiReadBack = 0; + UINT value; + UINT uiErasePattern[4] = {0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF}; + int bytes; + +// +// need not write 0xFFFFFFFF because write requires an erase and erase will +// make whole sector 0xFFFFFFFF. +// + if (!memcmp(pData,uiErasePattern,MAX_RW_SIZE)) + { + return 0; + } + + value = (FLASH_CMD_WRITE_ENABLE << 24); + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG,&value, sizeof(value)) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Write Enable of FLASH_SPI_CMDQ_REG fails"); + return STATUS_FAILURE; + } + if(wrm(Adapter, uiOffset, (PCHAR)pData, MAX_RW_SIZE) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Data write fails..."); + return STATUS_FAILURE; + } + // __udelay(1); + + do + { + value = (FLASH_CMD_STATUS_REG_READ << 24); + if(wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value)) < 0) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Programing of FLASH_SPI_CMDQ_REG fails"); + return STATUS_FAILURE; + } + //__udelay(1); + bytes = rdmalt(Adapter, FLASH_SPI_READQ_REG, &uiStatus, sizeof(uiStatus)); + if (bytes < 0) { + uiStatus = bytes; + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_PRINTK, 0, 0, "Reading status of FLASH_SPI_READQ_REG fails"); + return uiStatus; + } + iRetries--; + //this will ensure that in there will be no changes in the current path. + //currently one rdm/wrm takes 125 us. + //Hence 125 *2 * FLASH_PER_RETRIES_DELAY >3 ms(worst case delay) + //Hence current implementation cycle will intoduce no delay in current path + if(iRetries && ((iRetries % FLASH_PER_RETRIES_DELAY) == 0)) + msleep(1); + }while((uiStatus & 0x1) && (iRetries >0)); + + if(uiStatus & 0x1) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Flash Write fails even after checking status for 200 times."); + return STATUS_FAILURE ; + } + + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// Procedure: BcmRestoreBlockProtectStatus +// +// Description: Restores the original block protection status. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// ulWriteStatus -Original status +// Returns: +// +// +//----------------------------------------------------------------------------- + +static VOID BcmRestoreBlockProtectStatus(PMINI_ADAPTER Adapter,ULONG ulWriteStatus) +{ + UINT value; + value = (FLASH_CMD_WRITE_ENABLE<< 24); + wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value)); + + udelay(20); + value = (FLASH_CMD_STATUS_REG_WRITE<<24)|(ulWriteStatus << 16); + wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value)); + udelay(20); +} +//----------------------------------------------------------------------------- +// Procedure: BcmFlashUnProtectBlock +// +// Description: UnProtects appropriate blocks for writing. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// uiOffset - Offset of the flash where data needs to be written to. This should be Sector aligned. +// Returns: +// ULONG - Status value before UnProtect. +// +//----------------------------------------------------------------------------- +static ULONG BcmFlashUnProtectBlock(PMINI_ADAPTER Adapter,UINT uiOffset, UINT uiLength) +{ + ULONG ulStatus = 0; + ULONG ulWriteStatus = 0; + UINT value; + uiOffset = uiOffset&0x000FFFFF; + +// +// Implemented only for 1MB Flash parts. +// + if(FLASH_PART_SST25VF080B == Adapter->ulFlashID) + { + // + // Get Current BP status. + // + value = (FLASH_CMD_STATUS_REG_READ << 24); + wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value)); + udelay(10); + // + // Read status will be WWXXYYZZ. We have to take only WW. + // + rdmalt(Adapter, FLASH_SPI_READQ_REG, (PUINT)&ulStatus, sizeof(ulStatus)); + ulStatus >>= 24; + ulWriteStatus = ulStatus; + + // + // Bits [5-2] give current block level protection status. + // Bit5: BP3 - DONT CARE + // BP2-BP0: 0 - NO PROTECTION, 1 - UPPER 1/16, 2 - UPPER 1/8, 3 - UPPER 1/4 + // 4 - UPPER 1/2. 5 to 7 - ALL BLOCKS + // + + if(ulStatus) + { + if((uiOffset+uiLength) <= 0x80000) + { + // + // Offset comes in lower half of 1MB. Protect the upper half. + // Clear BP1 and BP0 and set BP2. + // + ulWriteStatus |= (0x4<<2); + ulWriteStatus &= ~(0x3<<2); + } + else if((uiOffset+uiLength) <= 0xC0000) + { + // + // Offset comes below Upper 1/4. Upper 1/4 can be protected. + // Clear BP2 and set BP1 and BP0. + // + ulWriteStatus |= (0x3<<2); + ulWriteStatus &= ~(0x1<<4); + } + else if((uiOffset+uiLength) <= 0xE0000) + { + // + // Offset comes below Upper 1/8. Upper 1/8 can be protected. + // Clear BP2 and BP0 and set BP1 + // + ulWriteStatus |= (0x1<<3); + ulWriteStatus &= ~(0x5<<2); + + } + else if((uiOffset+uiLength) <= 0xF0000) + { + // + // Offset comes below Upper 1/16. Only upper 1/16 can be protected. + // Set BP0 and Clear BP2,BP1. + // + ulWriteStatus |= (0x1<<2); + ulWriteStatus &= ~(0x3<<3); + } + else + { + // + // Unblock all. + // Clear BP2,BP1 and BP0. + // + ulWriteStatus &= ~(0x7<<2); + } + + value = (FLASH_CMD_WRITE_ENABLE<< 24); + wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value)); + udelay(20); + value = (FLASH_CMD_STATUS_REG_WRITE<<24)|(ulWriteStatus << 16); + wrmalt(Adapter, FLASH_SPI_CMDQ_REG, &value, sizeof(value)); + udelay(20); + + } + + } + return ulStatus; +} +//----------------------------------------------------------------------------- +// Procedure: BeceemFlashBulkWrite +// +// Description: Performs write to the flash +// +// Arguments: +// Adapter - ptr to Adapter object instance +// pBuffer - Data to be written. +// uiOffset - Offset of the flash where data needs to be written to. +// uiNumBytes - Number of bytes to be written. +// bVerify - read verify flag. +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + +static INT BeceemFlashBulkWrite( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + UINT uiOffset, + UINT uiNumBytes, + BOOLEAN bVerify) +{ + PCHAR pTempBuff = NULL; + PUCHAR pcBuffer = (PUCHAR)pBuffer; + UINT uiIndex = 0; + UINT uiOffsetFromSectStart = 0; + UINT uiSectAlignAddr = 0; + UINT uiCurrSectOffsetAddr = 0; + UINT uiSectBoundary = 0; + UINT uiNumSectTobeRead = 0; + UCHAR ucReadBk[16] = {0}; + ULONG ulStatus = 0; + INT Status = STATUS_SUCCESS; + UINT uiTemp = 0; + UINT index = 0; + UINT uiPartOffset = 0; + +#if defined(BCM_SHM_INTERFACE) && !defined(FLASH_DIRECT_ACCESS) + Status = bcmflash_raw_write((uiOffset/FLASH_PART_SIZE),(uiOffset % FLASH_PART_SIZE),( unsigned char *)pBuffer,uiNumBytes); + return Status; +#endif + + uiOffsetFromSectStart = uiOffset & ~(Adapter->uiSectorSize - 1); + + //Adding flash Base address +// uiOffset = uiOffset + GetFlashBaseAddr(Adapter); + + uiSectAlignAddr = uiOffset & ~(Adapter->uiSectorSize - 1); + uiCurrSectOffsetAddr = uiOffset & (Adapter->uiSectorSize - 1); + uiSectBoundary = uiSectAlignAddr + Adapter->uiSectorSize; + + pTempBuff = kmalloc(Adapter->uiSectorSize, GFP_KERNEL); + if(NULL == pTempBuff) + goto BeceemFlashBulkWrite_EXIT; +// +// check if the data to be written is overlapped across sectors +// + if(uiOffset+uiNumBytes < uiSectBoundary) + { + uiNumSectTobeRead = 1; + } + else + { + // Number of sectors = Last sector start address/First sector start address + uiNumSectTobeRead = (uiCurrSectOffsetAddr+uiNumBytes)/Adapter->uiSectorSize; + if((uiCurrSectOffsetAddr+uiNumBytes)%Adapter->uiSectorSize) + { + uiNumSectTobeRead++; + } + } + //Check whether Requested sector is writable or not in case of flash2x write. But if write call is + // for DSD calibration, allow it without checking of sector permission + + if(IsFlash2x(Adapter) && (Adapter->bAllDSDWriteAllow == FALSE)) + { + index = 0; + uiTemp = uiNumSectTobeRead ; + while(uiTemp) + { + if(IsOffsetWritable(Adapter, uiOffsetFromSectStart + index * Adapter->uiSectorSize ) == FALSE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Sector Starting at offset <0X%X> is not writable", + (uiOffsetFromSectStart + index * Adapter->uiSectorSize)); + Status = SECTOR_IS_NOT_WRITABLE; + goto BeceemFlashBulkWrite_EXIT; + } + uiTemp = uiTemp - 1; + index = index + 1 ; + } + } + Adapter->SelectedChip = RESET_CHIP_SELECT; + while(uiNumSectTobeRead) + { + //do_gettimeofday(&tv1); + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "\nTime In start of write :%ld ms\n",(tv1.tv_sec *1000 + tv1.tv_usec /1000)); + uiPartOffset = (uiSectAlignAddr & (FLASH_PART_SIZE - 1)) + GetFlashBaseAddr(Adapter); + + BcmDoChipSelect(Adapter,uiSectAlignAddr); + + if(0 != BeceemFlashBulkRead(Adapter, + (PUINT)pTempBuff, + uiOffsetFromSectStart, + Adapter->uiSectorSize)) + { + Status = -1; + goto BeceemFlashBulkWrite_EXIT; + } + + //do_gettimeofday(&tr); + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Total time taken by Read :%ld ms\n", (tr.tv_sec *1000 + tr.tv_usec/1000) - (tv1.tv_sec *1000 + tv1.tv_usec/1000)); + + ulStatus = BcmFlashUnProtectBlock(Adapter,uiSectAlignAddr,Adapter->uiSectorSize); + + + if(uiNumSectTobeRead > 1) + { + + memcpy(&pTempBuff[uiCurrSectOffsetAddr],pcBuffer,uiSectBoundary-(uiSectAlignAddr+uiCurrSectOffsetAddr)); + pcBuffer += ((uiSectBoundary-(uiSectAlignAddr+uiCurrSectOffsetAddr))); + uiNumBytes -= (uiSectBoundary-(uiSectAlignAddr+uiCurrSectOffsetAddr)); + } + else + { + memcpy(&pTempBuff[uiCurrSectOffsetAddr],pcBuffer,uiNumBytes); + } + + if(IsFlash2x(Adapter)) + { + SaveHeaderIfPresent(Adapter,(PUCHAR)pTempBuff,uiOffsetFromSectStart); + } + + FlashSectorErase(Adapter,uiPartOffset,1); + //do_gettimeofday(&te); + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Total time taken by Erase :%ld ms\n", (te.tv_sec *1000 + te.tv_usec/1000) - (tr.tv_sec *1000 + tr.tv_usec/1000)); + + for(uiIndex = 0; uiIndex < Adapter->uiSectorSize; uiIndex +=Adapter->ulFlashWriteSize) + { + if(Adapter->device_removed) + { + Status = -1; + goto BeceemFlashBulkWrite_EXIT; + } + if(STATUS_SUCCESS != (*Adapter->fpFlashWrite)(Adapter,uiPartOffset+uiIndex,(&pTempBuff[uiIndex]))) + { + Status = -1; + goto BeceemFlashBulkWrite_EXIT; + } + } + + //do_gettimeofday(&tw); + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Total time taken in Write to Flash :%ld ms\n", (tw.tv_sec *1000 + tw.tv_usec/1000) - (te.tv_sec *1000 + te.tv_usec/1000)); + for(uiIndex = 0;uiIndex < Adapter->uiSectorSize;uiIndex += MAX_RW_SIZE) + { + if(STATUS_SUCCESS == BeceemFlashBulkRead(Adapter,(PUINT)ucReadBk,uiOffsetFromSectStart+uiIndex,MAX_RW_SIZE)) + { + if(Adapter->ulFlashWriteSize == 1) + { + UINT uiReadIndex = 0; + for(uiReadIndex = 0; uiReadIndex < 16; uiReadIndex++) + { + if(ucReadBk[uiReadIndex] != pTempBuff[uiIndex+uiReadIndex]) + { + if(STATUS_SUCCESS != (*Adapter->fpFlashWriteWithStatusCheck)(Adapter,uiPartOffset+uiIndex+uiReadIndex,&pTempBuff[uiIndex+uiReadIndex])) + { + Status = STATUS_FAILURE; + goto BeceemFlashBulkWrite_EXIT; + } + } + } + } + else + { + if(memcmp(ucReadBk,&pTempBuff[uiIndex],MAX_RW_SIZE)) + { + if(STATUS_SUCCESS != (*Adapter->fpFlashWriteWithStatusCheck)(Adapter,uiPartOffset+uiIndex,&pTempBuff[uiIndex])) + { + Status = STATUS_FAILURE; + goto BeceemFlashBulkWrite_EXIT; + } + } + } + } + } + //do_gettimeofday(&twv); + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Total time taken in Write to Flash verification :%ld ms\n", (twv.tv_sec *1000 + twv.tv_usec/1000) - (tw.tv_sec *1000 + tw.tv_usec/1000)); + + + if(ulStatus) + { + BcmRestoreBlockProtectStatus(Adapter,ulStatus); + ulStatus = 0; + } + + uiCurrSectOffsetAddr = 0; + uiSectAlignAddr = uiSectBoundary; + uiSectBoundary += Adapter->uiSectorSize; + uiOffsetFromSectStart += Adapter->uiSectorSize; + uiNumSectTobeRead--; + } + //do_gettimeofday(&tv2); + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Time after Write :%ld ms\n",(tv2.tv_sec *1000 + tv2.tv_usec/1000)); + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Total time taken by in Write is :%ld ms\n", (tv2.tv_sec *1000 + tv2.tv_usec/1000) - (tv1.tv_sec *1000 + tv1.tv_usec/1000)); +// +// Cleanup. +// +BeceemFlashBulkWrite_EXIT: + if(ulStatus) + { + BcmRestoreBlockProtectStatus(Adapter,ulStatus); + } + + kfree(pTempBuff); + + Adapter->SelectedChip = RESET_CHIP_SELECT; + return Status; +} + + +//----------------------------------------------------------------------------- +// Procedure: BeceemFlashBulkWriteStatus +// +// Description: Writes to Flash. Checks the SPI status after each write. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// pBuffer - Data to be written. +// uiOffset - Offset of the flash where data needs to be written to. +// uiNumBytes - Number of bytes to be written. +// bVerify - read verify flag. +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + +static INT BeceemFlashBulkWriteStatus( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + UINT uiOffset, + UINT uiNumBytes, + BOOLEAN bVerify) +{ + PCHAR pTempBuff = NULL; + PUCHAR pcBuffer = (PUCHAR)pBuffer; + UINT uiIndex = 0; + UINT uiOffsetFromSectStart = 0; + UINT uiSectAlignAddr = 0; + UINT uiCurrSectOffsetAddr = 0; + UINT uiSectBoundary = 0; + UINT uiNumSectTobeRead = 0; + UCHAR ucReadBk[16] = {0}; + ULONG ulStatus = 0; + UINT Status = STATUS_SUCCESS; + UINT uiTemp = 0; + UINT index = 0; + UINT uiPartOffset = 0; + + uiOffsetFromSectStart = uiOffset & ~(Adapter->uiSectorSize - 1); + + //uiOffset += Adapter->ulFlashCalStart; + //Adding flash Base address +// uiOffset = uiOffset + GetFlashBaseAddr(Adapter); + + uiSectAlignAddr = uiOffset & ~(Adapter->uiSectorSize - 1); + uiCurrSectOffsetAddr = uiOffset & (Adapter->uiSectorSize - 1); + uiSectBoundary = uiSectAlignAddr + Adapter->uiSectorSize; + + pTempBuff = kmalloc(Adapter->uiSectorSize, GFP_KERNEL); + if(NULL == pTempBuff) + goto BeceemFlashBulkWriteStatus_EXIT; + +// +// check if the data to be written is overlapped across sectors +// + if(uiOffset+uiNumBytes < uiSectBoundary) + { + uiNumSectTobeRead = 1; + } + else + { +// Number of sectors = Last sector start address/First sector start address + uiNumSectTobeRead = (uiCurrSectOffsetAddr+uiNumBytes)/Adapter->uiSectorSize; + if((uiCurrSectOffsetAddr+uiNumBytes)%Adapter->uiSectorSize) + { + uiNumSectTobeRead++; + } + } + + if(IsFlash2x(Adapter) && (Adapter->bAllDSDWriteAllow == FALSE)) + { + index = 0; + uiTemp = uiNumSectTobeRead ; + while(uiTemp) + { + if(IsOffsetWritable(Adapter,uiOffsetFromSectStart + index * Adapter->uiSectorSize ) == FALSE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Sector Starting at offset <0X%x> is not writable", + (uiOffsetFromSectStart + index * Adapter->uiSectorSize)); + Status = SECTOR_IS_NOT_WRITABLE; + goto BeceemFlashBulkWriteStatus_EXIT; + } + uiTemp = uiTemp - 1; + index = index + 1 ; + } + } + + Adapter->SelectedChip = RESET_CHIP_SELECT; + while(uiNumSectTobeRead) + { + uiPartOffset = (uiSectAlignAddr & (FLASH_PART_SIZE - 1)) + GetFlashBaseAddr(Adapter); + + BcmDoChipSelect(Adapter,uiSectAlignAddr); + if(0 != BeceemFlashBulkRead(Adapter, + (PUINT)pTempBuff, + uiOffsetFromSectStart, + Adapter->uiSectorSize)) + { + Status = -1; + goto BeceemFlashBulkWriteStatus_EXIT; + } + + ulStatus = BcmFlashUnProtectBlock(Adapter,uiOffsetFromSectStart,Adapter->uiSectorSize); + + if(uiNumSectTobeRead > 1) + { + + memcpy(&pTempBuff[uiCurrSectOffsetAddr],pcBuffer,uiSectBoundary-(uiSectAlignAddr+uiCurrSectOffsetAddr)); + pcBuffer += ((uiSectBoundary-(uiSectAlignAddr+uiCurrSectOffsetAddr))); + uiNumBytes -= (uiSectBoundary-(uiSectAlignAddr+uiCurrSectOffsetAddr)); + } + else + { + memcpy(&pTempBuff[uiCurrSectOffsetAddr],pcBuffer,uiNumBytes); + } + + if(IsFlash2x(Adapter)) + { + SaveHeaderIfPresent(Adapter,(PUCHAR)pTempBuff,uiOffsetFromSectStart); + } + + FlashSectorErase(Adapter,uiPartOffset,1); + + for(uiIndex = 0; uiIndex < Adapter->uiSectorSize; uiIndex +=Adapter->ulFlashWriteSize) + + { + if(Adapter->device_removed) + { + Status = -1; + goto BeceemFlashBulkWriteStatus_EXIT; + } + + if(STATUS_SUCCESS != (*Adapter->fpFlashWriteWithStatusCheck)(Adapter,uiPartOffset+uiIndex,&pTempBuff[uiIndex])) + { + Status = -1; + goto BeceemFlashBulkWriteStatus_EXIT; + } + } + + if(bVerify) + { + for(uiIndex = 0;uiIndex < Adapter->uiSectorSize;uiIndex += MAX_RW_SIZE) + { + + if(STATUS_SUCCESS == BeceemFlashBulkRead(Adapter,(PUINT)ucReadBk,uiOffsetFromSectStart+uiIndex,MAX_RW_SIZE)) + { + if(memcmp(ucReadBk,&pTempBuff[uiIndex],MAX_RW_SIZE)) + { + Status = STATUS_FAILURE; + goto BeceemFlashBulkWriteStatus_EXIT; + } + + } + + } + } + + if(ulStatus) + { + BcmRestoreBlockProtectStatus(Adapter,ulStatus); + ulStatus = 0; + } + + uiCurrSectOffsetAddr = 0; + uiSectAlignAddr = uiSectBoundary; + uiSectBoundary += Adapter->uiSectorSize; + uiOffsetFromSectStart += Adapter->uiSectorSize; + uiNumSectTobeRead--; + } +// +// Cleanup. +// +BeceemFlashBulkWriteStatus_EXIT: + if(ulStatus) + { + BcmRestoreBlockProtectStatus(Adapter,ulStatus); + } + + kfree(pTempBuff); + Adapter->SelectedChip = RESET_CHIP_SELECT; + return Status; + +} + +//----------------------------------------------------------------------------- +// Procedure: PropagateCalParamsFromEEPROMToMemory +// +// Description: Dumps the calibration section of EEPROM to DDR. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + + +INT PropagateCalParamsFromEEPROMToMemory(PMINI_ADAPTER Adapter) +{ + PCHAR pBuff = kmalloc(BUFFER_4K, GFP_KERNEL); + UINT uiEepromSize = 0; + UINT uiIndex = 0; + UINT uiBytesToCopy = 0; + UINT uiCalStartAddr = EEPROM_CALPARAM_START; + UINT uiMemoryLoc = EEPROM_CAL_DATA_INTERNAL_LOC; + UINT value; + INT Status = 0; + if(pBuff == NULL) + { + return -1; + } + + if(0 != BeceemEEPROMBulkRead(Adapter,&uiEepromSize,EEPROM_SIZE_OFFSET,4)) + { + + kfree(pBuff); + return -1; + } + + uiEepromSize >>= 16; + if(uiEepromSize > 1024*1024) + { + kfree(pBuff); + return -1; + } + + + uiBytesToCopy = MIN(BUFFER_4K,uiEepromSize); + + while(uiBytesToCopy) + { + if(0 != BeceemEEPROMBulkRead(Adapter,(PUINT)pBuff,uiCalStartAddr,uiBytesToCopy)) + { + Status = -1; + break; + } + wrm(Adapter,uiMemoryLoc,(PCHAR)(((PULONG)pBuff)+uiIndex),uiBytesToCopy); + uiMemoryLoc += uiBytesToCopy; + uiEepromSize -= uiBytesToCopy; + uiCalStartAddr += uiBytesToCopy; + uiIndex += uiBytesToCopy/4; + uiBytesToCopy = MIN(BUFFER_4K,uiEepromSize); + + } + value = 0xbeadbead; + wrmalt(Adapter, EEPROM_CAL_DATA_INTERNAL_LOC-4,&value, sizeof(value)); + value = 0xbeadbead; + wrmalt(Adapter, EEPROM_CAL_DATA_INTERNAL_LOC-8,&value, sizeof(value)); + kfree(pBuff); + + return Status; + +} + +//----------------------------------------------------------------------------- +// Procedure: PropagateCalParamsFromFlashToMemory +// +// Description: Dumps the calibration section of EEPROM to DDR. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + +INT PropagateCalParamsFromFlashToMemory(PMINI_ADAPTER Adapter) +{ + PCHAR pBuff, pPtr; + UINT uiEepromSize = 0; + UINT uiBytesToCopy = 0; + //UINT uiIndex = 0; + UINT uiCalStartAddr = EEPROM_CALPARAM_START; + UINT uiMemoryLoc = EEPROM_CAL_DATA_INTERNAL_LOC; + UINT value; + INT Status = 0; +// +// Write the signature first. This will ensure firmware does not access EEPROM. +// + value = 0xbeadbead; + wrmalt(Adapter, EEPROM_CAL_DATA_INTERNAL_LOC - 4, &value, sizeof(value)); + value = 0xbeadbead; + wrmalt(Adapter, EEPROM_CAL_DATA_INTERNAL_LOC - 8, &value, sizeof(value)); + + if(0 != BeceemNVMRead(Adapter,&uiEepromSize,EEPROM_SIZE_OFFSET, 4)) + { + return -1; + } + uiEepromSize = ntohl(uiEepromSize); + uiEepromSize >>= 16; + +// +// subtract the auto init section size +// + uiEepromSize -= EEPROM_CALPARAM_START; + + if(uiEepromSize > 1024*1024) + { + return -1; + } + + pBuff = kmalloc(uiEepromSize, GFP_KERNEL); + if ( pBuff == NULL ) + return -1; + + if(0 != BeceemNVMRead(Adapter,(PUINT)pBuff,uiCalStartAddr, uiEepromSize)) + { + kfree(pBuff); + return -1; + } + + pPtr = pBuff; + + uiBytesToCopy = MIN(BUFFER_4K,uiEepromSize); + + while(uiBytesToCopy) + { + Status = wrm(Adapter,uiMemoryLoc,(PCHAR)pPtr,uiBytesToCopy); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"wrm failed with status :%d",Status); + break; + } + + pPtr += uiBytesToCopy; + uiEepromSize -= uiBytesToCopy; + uiMemoryLoc += uiBytesToCopy; + uiBytesToCopy = MIN(BUFFER_4K,uiEepromSize); + } + + kfree(pBuff); + return Status; + +} + +//----------------------------------------------------------------------------- +// Procedure: BeceemEEPROMReadBackandVerify +// +// Description: Read back the data written and verifies. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// pBuffer - Data to be written. +// uiOffset - Offset of the flash where data needs to be written to. +// uiNumBytes - Number of bytes to be written. +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + +static INT BeceemEEPROMReadBackandVerify( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + UINT uiOffset, + UINT uiNumBytes) +{ + UINT uiRdbk = 0; + UINT uiIndex = 0; + UINT uiData = 0; + UINT auiData[4] = {0}; + + while(uiNumBytes) + { + if(Adapter->device_removed ) + { + return -1; + } + + if(uiNumBytes >= MAX_RW_SIZE) + {// for the requests more than or equal to MAX_RW_SIZE bytes, use bulk read function to make the access faster. + BeceemEEPROMBulkRead(Adapter,&auiData[0],uiOffset,MAX_RW_SIZE); + + if(memcmp(&pBuffer[uiIndex],&auiData[0],MAX_RW_SIZE)) + { + // re-write + BeceemEEPROMBulkWrite(Adapter,(PUCHAR)(pBuffer+uiIndex),uiOffset,MAX_RW_SIZE,FALSE); + mdelay(3); + BeceemEEPROMBulkRead(Adapter,&auiData[0],uiOffset,MAX_RW_SIZE); + + if(memcmp(&pBuffer[uiIndex],&auiData[0],MAX_RW_SIZE)) + { + return -1; + } + } + uiOffset += MAX_RW_SIZE; + uiNumBytes -= MAX_RW_SIZE; + uiIndex += 4; + + } + else if(uiNumBytes >= 4) + { + BeceemEEPROMBulkRead(Adapter,&uiData,uiOffset,4); + if(uiData != pBuffer[uiIndex]) + { + //re-write + BeceemEEPROMBulkWrite(Adapter,(PUCHAR)(pBuffer+uiIndex),uiOffset,4,FALSE); + mdelay(3); + BeceemEEPROMBulkRead(Adapter,&uiData,uiOffset,4); + if(uiData != pBuffer[uiIndex]) + { + return -1; + } + } + uiOffset += 4; + uiNumBytes -= 4; + uiIndex++; + + } + else + { // Handle the reads less than 4 bytes... + uiData = 0; + memcpy(&uiData,((PUCHAR)pBuffer)+(uiIndex*sizeof(UINT)),uiNumBytes); + BeceemEEPROMBulkRead(Adapter,&uiRdbk,uiOffset,4); + + if(memcmp(&uiData, &uiRdbk, uiNumBytes)) + return -1; + + uiNumBytes = 0; + } + + } + + return 0; +} + +static VOID BcmSwapWord(UINT *ptr1) { + + UINT tempval = (UINT)*ptr1; + char *ptr2 = (char *)&tempval; + char *ptr = (char *)ptr1; + + ptr[0] = ptr2[3]; + ptr[1] = ptr2[2]; + ptr[2] = ptr2[1]; + ptr[3] = ptr2[0]; +} + +//----------------------------------------------------------------------------- +// Procedure: BeceemEEPROMWritePage +// +// Description: Performs page write (16bytes) to the EEPROM +// +// Arguments: +// Adapter - ptr to Adapter object instance +// uiData - Data to be written. +// uiOffset - Offset of the EEPROM where data needs to be written to. +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- +static INT BeceemEEPROMWritePage( PMINI_ADAPTER Adapter, UINT uiData[], UINT uiOffset ) +{ + UINT uiRetries = MAX_EEPROM_RETRIES*RETRIES_PER_DELAY; + UINT uiStatus = 0; + UCHAR uiEpromStatus = 0; + UINT value =0 ; + + /* Flush the Write/Read/Cmd queues. */ + value = ( EEPROM_WRITE_QUEUE_FLUSH | EEPROM_CMD_QUEUE_FLUSH | EEPROM_READ_QUEUE_FLUSH ); + wrmalt( Adapter, SPI_FLUSH_REG, &value, sizeof(value)); + value = 0 ; + wrmalt( Adapter, SPI_FLUSH_REG, &value, sizeof(value) ); + + /* Clear the Empty/Avail/Full bits. After this it has been confirmed + * that the bit was cleared by reading back the register. See NOTE below. + * We also clear the Read queues as we do a EEPROM status register read + * later. */ + value = ( EEPROM_WRITE_QUEUE_EMPTY | EEPROM_WRITE_QUEUE_AVAIL | EEPROM_WRITE_QUEUE_FULL | EEPROM_READ_DATA_AVAIL | EEPROM_READ_DATA_FULL ) ; + wrmalt( Adapter, EEPROM_SPI_Q_STATUS1_REG,&value, sizeof(value)); + + /* Enable write */ + value = EEPROM_WRITE_ENABLE ; + wrmalt( Adapter, EEPROM_CMDQ_SPI_REG,&value, sizeof(value) ); + + /* We can write back to back 8bits * 16 into the queue and as we have + * checked for the queue to be empty we can write in a burst. */ + + value = uiData[0]; + BcmSwapWord(&value); + wrm( Adapter, EEPROM_WRITE_DATAQ_REG, (PUCHAR)&value, 4); + + value = uiData[1]; + BcmSwapWord(&value); + wrm( Adapter, EEPROM_WRITE_DATAQ_REG, (PUCHAR)&value, 4); + + value = uiData[2]; + BcmSwapWord(&value); + wrm( Adapter, EEPROM_WRITE_DATAQ_REG, (PUCHAR)&value, 4); + + value = uiData[3]; + BcmSwapWord(&value); + wrm( Adapter, EEPROM_WRITE_DATAQ_REG, (PUCHAR)&value, 4); + + /* NOTE : After this write, on readback of EEPROM_SPI_Q_STATUS1_REG + * shows that we see 7 for the EEPROM data write. Which means that + * queue got full, also space is available as well as the queue is empty. + * This may happen in sequence. */ + value = EEPROM_16_BYTE_PAGE_WRITE | uiOffset ; + wrmalt( Adapter, EEPROM_CMDQ_SPI_REG, &value, sizeof(value) ); + + /* Ideally we should loop here without tries and eventually succeed. + * What we are checking if the previous write has completed, and this + * may take time. We should wait till the Empty bit is set. */ + uiStatus = 0; + rdmalt(Adapter, EEPROM_SPI_Q_STATUS1_REG, &uiStatus, sizeof(uiStatus)); + while ( ( uiStatus & EEPROM_WRITE_QUEUE_EMPTY ) == 0 ) + { + uiRetries--; + if ( uiRetries == 0 ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "0x0f003004 = %x, %d retries failed.\n", uiStatus, MAX_EEPROM_RETRIES *RETRIES_PER_DELAY); + return STATUS_FAILURE ; + } + + if( !(uiRetries%RETRIES_PER_DELAY) ) + msleep(1); + + uiStatus = 0; + rdmalt(Adapter, EEPROM_SPI_Q_STATUS1_REG, &uiStatus, sizeof(uiStatus)); + if(Adapter->device_removed == TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Modem got removed hence exiting from loop...."); + return -ENODEV; + } + + } + + if ( uiRetries != 0 ) + { + /* Clear the ones that are set - either, Empty/Full/Avail bits */ + value = ( uiStatus & ( EEPROM_WRITE_QUEUE_EMPTY | EEPROM_WRITE_QUEUE_AVAIL | EEPROM_WRITE_QUEUE_FULL ) ); + wrmalt( Adapter, EEPROM_SPI_Q_STATUS1_REG, &value, sizeof(value)); + } + + /* Here we should check if the EEPROM status register is correct before + * proceeding. Bit 0 in the EEPROM Status register should be 0 before + * we proceed further. A 1 at Bit 0 indicates that the EEPROM is busy + * with the previous write. Note also that issuing this read finally + * means the previous write to the EEPROM has completed. */ + uiRetries = MAX_EEPROM_RETRIES*RETRIES_PER_DELAY; + uiEpromStatus = 0; + while ( uiRetries != 0 ) + { + uiEpromStatus = ReadEEPROMStatusRegister( Adapter) ; + if(Adapter->device_removed == TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Modem has got removed hence exiting from loop..."); + return -ENODEV; + } + if ( ( EEPROM_STATUS_REG_WRITE_BUSY & uiEpromStatus ) == 0 ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "EEPROM status register = %x tries = %d\n", uiEpromStatus, (MAX_EEPROM_RETRIES * RETRIES_PER_DELAY- uiRetries) ); + return STATUS_SUCCESS ; + } + uiRetries--; + if ( uiRetries == 0 ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "0x0f003004 = %x, for EEPROM status read %d retries failed.\n", uiEpromStatus, MAX_EEPROM_RETRIES *RETRIES_PER_DELAY); + return STATUS_FAILURE ; + } + uiEpromStatus = 0; + if( !(uiRetries%RETRIES_PER_DELAY) ) + msleep(1); + } + + return STATUS_SUCCESS ; +} /* BeceemEEPROMWritePage */ + + +//----------------------------------------------------------------------------- +// Procedure: BeceemEEPROMBulkWrite +// +// Description: Performs write to the EEPROM +// +// Arguments: +// Adapter - ptr to Adapter object instance +// pBuffer - Data to be written. +// uiOffset - Offset of the EEPROM where data needs to be written to. +// uiNumBytes - Number of bytes to be written. +// bVerify - read verify flag. +// Returns: +// OSAL_STATUS_CODE +// +//----------------------------------------------------------------------------- + +INT BeceemEEPROMBulkWrite( + PMINI_ADAPTER Adapter, + PUCHAR pBuffer, + UINT uiOffset, + UINT uiNumBytes, + BOOLEAN bVerify) +{ + UINT uiBytesToCopy = uiNumBytes; + //UINT uiRdbk = 0; + UINT uiData[4] = {0}; + UINT uiIndex = 0; + UINT uiTempOffset = 0; + UINT uiExtraBytes = 0; + //PUINT puiBuffer = (PUINT)pBuffer; + //INT value; + + if(uiOffset%MAX_RW_SIZE && uiBytesToCopy) + { + uiTempOffset = uiOffset - (uiOffset%MAX_RW_SIZE); + uiExtraBytes = uiOffset-uiTempOffset; + + + BeceemEEPROMBulkRead(Adapter,&uiData[0],uiTempOffset,MAX_RW_SIZE); + + if(uiBytesToCopy >= (16 -uiExtraBytes)) + { + memcpy((((PUCHAR)&uiData[0])+uiExtraBytes),pBuffer,MAX_RW_SIZE- uiExtraBytes); + + if ( STATUS_FAILURE == BeceemEEPROMWritePage( Adapter, uiData, uiTempOffset ) ) + return STATUS_FAILURE; + + uiBytesToCopy -= (MAX_RW_SIZE - uiExtraBytes); + uiIndex += (MAX_RW_SIZE - uiExtraBytes); + uiOffset += (MAX_RW_SIZE - uiExtraBytes); + } + else + { + memcpy((((PUCHAR)&uiData[0])+uiExtraBytes),pBuffer,uiBytesToCopy); + + if ( STATUS_FAILURE == BeceemEEPROMWritePage( Adapter, uiData, uiTempOffset ) ) + return STATUS_FAILURE; + + uiIndex += uiBytesToCopy; + uiOffset += uiBytesToCopy; + uiBytesToCopy = 0; + } + + + } + + while(uiBytesToCopy) + { + if(Adapter->device_removed) + { + return -1; + } + + if(uiBytesToCopy >= MAX_RW_SIZE) + { + + if (STATUS_FAILURE == BeceemEEPROMWritePage( Adapter, (PUINT) &pBuffer[uiIndex], uiOffset ) ) + return STATUS_FAILURE; + + uiIndex += MAX_RW_SIZE; + uiOffset += MAX_RW_SIZE; + uiBytesToCopy -= MAX_RW_SIZE; + } + else + { + // + // To program non 16byte aligned data, read 16byte and then update. + // + BeceemEEPROMBulkRead(Adapter,&uiData[0],uiOffset,16); + memcpy(&uiData[0],pBuffer+uiIndex,uiBytesToCopy); + + + if ( STATUS_FAILURE == BeceemEEPROMWritePage( Adapter, uiData, uiOffset ) ) + return STATUS_FAILURE; + uiBytesToCopy = 0; + } + + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Procedure: BeceemNVMRead +// +// Description: Reads n number of bytes from NVM. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// pBuffer - Buffer to store the data read from NVM +// uiOffset - Offset of NVM from where data should be read +// uiNumBytes - Number of bytes to be read from the NVM. +// +// Returns: +// OSAL_STATUS_SUCCESS - if NVM read is successful. +// - if failed. +//----------------------------------------------------------------------------- + +INT BeceemNVMRead( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + UINT uiOffset, + UINT uiNumBytes) +{ + INT Status = 0; +#if !defined(BCM_SHM_INTERFACE) || defined(FLASH_DIRECT_ACCESS) + UINT uiTemp = 0, value; +#endif + + if(Adapter->eNVMType == NVM_FLASH) + { + if(Adapter->bFlashRawRead == FALSE) + { + if (IsSectionExistInVendorInfo(Adapter,Adapter->eActiveDSD)) + return vendorextnReadSection(Adapter,(PUCHAR)pBuffer,Adapter->eActiveDSD,uiOffset,uiNumBytes); + uiOffset = uiOffset+ Adapter->ulFlashCalStart ; + } +#if defined(BCM_SHM_INTERFACE) && !defined(FLASH_DIRECT_ACCESS) + Status = bcmflash_raw_read((uiOffset/FLASH_PART_SIZE),(uiOffset % FLASH_PART_SIZE),( unsigned char *)pBuffer,uiNumBytes); +#else + + rdmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); + value = 0; + wrmalt(Adapter, 0x0f000C80,&value, sizeof(value)); + Status = BeceemFlashBulkRead(Adapter, + pBuffer, + uiOffset, + uiNumBytes); + wrmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); +#endif + } + else if(Adapter->eNVMType == NVM_EEPROM) + { + Status = BeceemEEPROMBulkRead(Adapter, + pBuffer, + uiOffset, + uiNumBytes); + } + else + { + Status = -1; + } + return Status; +} + +//----------------------------------------------------------------------------- +// Procedure: BeceemNVMWrite +// +// Description: Writes n number of bytes to NVM. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// pBuffer - Buffer contains the data to be written. +// uiOffset - Offset of NVM where data to be written to. +// uiNumBytes - Number of bytes to be written.. +// +// Returns: +// OSAL_STATUS_SUCCESS - if NVM write is successful. +// - if failed. +//----------------------------------------------------------------------------- + +INT BeceemNVMWrite( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + UINT uiOffset, + UINT uiNumBytes, + BOOLEAN bVerify) +{ + INT Status = 0; + UINT uiTemp = 0; + UINT uiMemoryLoc = EEPROM_CAL_DATA_INTERNAL_LOC; + UINT uiIndex = 0; +#if !defined(BCM_SHM_INTERFACE) || defined(FLASH_DIRECT_ACCESS) + UINT value; +#endif + UINT uiFlashOffset = 0; + + if(Adapter->eNVMType == NVM_FLASH) + { + if (IsSectionExistInVendorInfo(Adapter,Adapter->eActiveDSD)) + Status = vendorextnWriteSection(Adapter,(PUCHAR)pBuffer,Adapter->eActiveDSD,uiOffset,uiNumBytes,bVerify); + else + { + uiFlashOffset = uiOffset + Adapter->ulFlashCalStart; + +#if defined(BCM_SHM_INTERFACE) && !defined(FLASH_DIRECT_ACCESS) + Status = bcmflash_raw_write((uiFlashOffset/FLASH_PART_SIZE), (uiFlashOffset % FLASH_PART_SIZE), (unsigned char *)pBuffer,uiNumBytes); +#else + rdmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); + value = 0; + wrmalt(Adapter, 0x0f000C80, &value, sizeof(value)); + + if(Adapter->bStatusWrite == TRUE) + { + Status = BeceemFlashBulkWriteStatus(Adapter, + pBuffer, + uiFlashOffset, + uiNumBytes , + bVerify); + } + else + { + + Status = BeceemFlashBulkWrite(Adapter, + pBuffer, + uiFlashOffset, + uiNumBytes, + bVerify); + } +#endif + } + + + if(uiOffset >= EEPROM_CALPARAM_START) + { + uiMemoryLoc += (uiOffset - EEPROM_CALPARAM_START); + while(uiNumBytes) + { + if(uiNumBytes > BUFFER_4K) + { + wrm(Adapter,(uiMemoryLoc+uiIndex),(PCHAR)(pBuffer+(uiIndex/4)),BUFFER_4K); + uiNumBytes -= BUFFER_4K; + uiIndex += BUFFER_4K; + } + else + { + wrm(Adapter,uiMemoryLoc+uiIndex,(PCHAR)(pBuffer+(uiIndex/4)),uiNumBytes); + uiNumBytes = 0; + break; + } + } + } + else + { + if((uiOffset+uiNumBytes) > EEPROM_CALPARAM_START) + { + ULONG ulBytesTobeSkipped = 0; + PUCHAR pcBuffer = (PUCHAR)pBuffer;// char pointer to take care of odd byte cases. + uiNumBytes -= (EEPROM_CALPARAM_START - uiOffset); + ulBytesTobeSkipped += (EEPROM_CALPARAM_START - uiOffset); + uiOffset += (EEPROM_CALPARAM_START - uiOffset); + while(uiNumBytes) + { + if(uiNumBytes > BUFFER_4K) + { + wrm(Adapter,uiMemoryLoc+uiIndex,(PCHAR )&pcBuffer[ulBytesTobeSkipped+uiIndex],BUFFER_4K); + uiNumBytes -= BUFFER_4K; + uiIndex += BUFFER_4K; + } + else + { + wrm(Adapter,uiMemoryLoc+uiIndex,(PCHAR)&pcBuffer[ulBytesTobeSkipped+uiIndex],uiNumBytes); + uiNumBytes = 0; + break; + } + } + + } + } + + // restore the values. + wrmalt(Adapter,0x0f000C80,&uiTemp, sizeof(uiTemp)); + } + else if(Adapter->eNVMType == NVM_EEPROM) + { + Status = BeceemEEPROMBulkWrite(Adapter, + (PUCHAR)pBuffer, + uiOffset, + uiNumBytes, + bVerify); + if(bVerify) + { + Status = BeceemEEPROMReadBackandVerify(Adapter,(PUINT)pBuffer,uiOffset,uiNumBytes); + } + } + else + { + Status = -1; + } + return Status; +} + +//----------------------------------------------------------------------------- +// Procedure: BcmUpdateSectorSize +// +// Description: Updates the sector size to FLASH. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// uiSectorSize - sector size +// +// Returns: +// OSAL_STATUS_SUCCESS - if NVM write is successful. +// - if failed. +//----------------------------------------------------------------------------- + +INT BcmUpdateSectorSize(PMINI_ADAPTER Adapter,UINT uiSectorSize) +{ + INT Status = -1; + FLASH_CS_INFO sFlashCsInfo = {0}; + UINT uiTemp = 0; + + UINT uiSectorSig = 0; + UINT uiCurrentSectorSize = 0; + + UINT value; + + + + rdmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); + value = 0; + wrmalt(Adapter, 0x0f000C80,&value, sizeof(value)); + +// +// Before updating the sector size in the reserved area, check if already present. +// + BeceemFlashBulkRead(Adapter,(PUINT)&sFlashCsInfo,Adapter->ulFlashControlSectionStart,sizeof(sFlashCsInfo)); + uiSectorSig = ntohl(sFlashCsInfo.FlashSectorSizeSig); + uiCurrentSectorSize = ntohl(sFlashCsInfo.FlashSectorSize); + + if(uiSectorSig == FLASH_SECTOR_SIZE_SIG) + { + + if((uiCurrentSectorSize <= MAX_SECTOR_SIZE) && (uiCurrentSectorSize >= MIN_SECTOR_SIZE)) + { + if(uiSectorSize == uiCurrentSectorSize) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Provided sector size is same as programmed in Flash"); + Status = STATUS_SUCCESS; + goto Restore ; + } + } + } + + if((uiSectorSize <= MAX_SECTOR_SIZE) && (uiSectorSize >= MIN_SECTOR_SIZE)) + { + + sFlashCsInfo.FlashSectorSize = htonl(uiSectorSize); + sFlashCsInfo.FlashSectorSizeSig = htonl(FLASH_SECTOR_SIZE_SIG); + + Status = BeceemFlashBulkWrite(Adapter, + (PUINT)&sFlashCsInfo, + Adapter->ulFlashControlSectionStart, + sizeof(sFlashCsInfo), + TRUE); + + + } + + Restore : + // restore the values. + wrmalt(Adapter, 0x0f000C80,&uiTemp, sizeof(uiTemp)); + + + return Status; + +} + +//----------------------------------------------------------------------------- +// Procedure: BcmGetFlashSectorSize +// +// Description: Finds the sector size of the FLASH. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// +// Returns: +// UINT - sector size. +// +//----------------------------------------------------------------------------- + +static UINT BcmGetFlashSectorSize(PMINI_ADAPTER Adapter, UINT FlashSectorSizeSig, UINT FlashSectorSize) +{ + UINT uiSectorSize = 0; + UINT uiSectorSig = 0; + + if(Adapter->bSectorSizeOverride && + (Adapter->uiSectorSizeInCFG <= MAX_SECTOR_SIZE && + Adapter->uiSectorSizeInCFG >= MIN_SECTOR_SIZE)) + { + Adapter->uiSectorSize = Adapter->uiSectorSizeInCFG; + } + else + { + + uiSectorSig = FlashSectorSizeSig; + + if(uiSectorSig == FLASH_SECTOR_SIZE_SIG) + { + uiSectorSize = FlashSectorSize; + // + // If the sector size stored in the FLASH makes sense then use it. + // + if(uiSectorSize <= MAX_SECTOR_SIZE && uiSectorSize >= MIN_SECTOR_SIZE) + { + Adapter->uiSectorSize = uiSectorSize; + } + //No valid size in FLASH, check if Config file has it. + else if(Adapter->uiSectorSizeInCFG <= MAX_SECTOR_SIZE && + Adapter->uiSectorSizeInCFG >= MIN_SECTOR_SIZE) + { + Adapter->uiSectorSize = Adapter->uiSectorSizeInCFG; + } + // Init to Default, if none of the above works. + else + { + Adapter->uiSectorSize = DEFAULT_SECTOR_SIZE; + } + + } + else + { + if(Adapter->uiSectorSizeInCFG <= MAX_SECTOR_SIZE && + Adapter->uiSectorSizeInCFG >= MIN_SECTOR_SIZE) + { + Adapter->uiSectorSize = Adapter->uiSectorSizeInCFG; + } + else + { + Adapter->uiSectorSize = DEFAULT_SECTOR_SIZE; + } + } + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Sector size :%x \n", Adapter->uiSectorSize); + return Adapter->uiSectorSize; +} + +//----------------------------------------------------------------------------- +// Procedure: BcmInitEEPROMQueues +// +// Description: Initialization of EEPROM queues. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// +// Returns: +// +//----------------------------------------------------------------------------- + +static INT BcmInitEEPROMQueues(PMINI_ADAPTER Adapter) +{ + UINT value = 0; + /* CHIP Bug : Clear the Avail bits on the Read queue. The default + * value on this register is supposed to be 0x00001102. + * But we get 0x00001122. */ + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Fixing reset value on 0x0f003004 register\n" ); + value = EEPROM_READ_DATA_AVAIL; + wrmalt( Adapter, EEPROM_SPI_Q_STATUS1_REG, &value, sizeof(value)); + + /* Flush the all the EEPROM queues. */ + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, " Flushing the queues\n"); + value =EEPROM_ALL_QUEUE_FLUSH ; + wrmalt( Adapter, SPI_FLUSH_REG, &value, sizeof(value)); + + value = 0; + wrmalt( Adapter, SPI_FLUSH_REG, &value, sizeof(value) ); + + /* Read the EEPROM Status Register. Just to see, no real purpose. */ + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "EEPROM Status register value = %x\n", ReadEEPROMStatusRegister(Adapter) ); + + return STATUS_SUCCESS; +} /* BcmInitEEPROMQueues() */ + +//----------------------------------------------------------------------------- +// Procedure: BcmInitNVM +// +// Description: Initialization of NVM, EEPROM size,FLASH size, sector size etc. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// +// Returns: +// +//----------------------------------------------------------------------------- + +INT BcmInitNVM(PMINI_ADAPTER ps_adapter) +{ + BcmValidateNvmType(ps_adapter); + BcmInitEEPROMQueues(ps_adapter); + + if(ps_adapter->eNVMType == NVM_AUTODETECT) + { + ps_adapter->eNVMType = BcmGetNvmType(ps_adapter); + if(ps_adapter->eNVMType == NVM_UNKNOWN) + { + BCM_DEBUG_PRINT(ps_adapter,DBG_TYPE_PRINTK, 0, 0, "NVM Type is unknown!!\n"); + } + } + else if(ps_adapter->eNVMType == NVM_FLASH) + { + BcmGetFlashCSInfo(ps_adapter); + } + + BcmGetNvmSize(ps_adapter); + + return STATUS_SUCCESS; +} +/***************************************************************************/ +/*BcmGetNvmSize : set the EEPROM or flash size in Adapter. +* +*Input Parameter: +* Adapter data structure +*Return Value : +* 0. means success; +*/ +/***************************************************************************/ + +static INT BcmGetNvmSize(PMINI_ADAPTER Adapter) +{ + if(Adapter->eNVMType == NVM_EEPROM) + { + Adapter->uiNVMDSDSize = BcmGetEEPROMSize(Adapter); + } + else if(Adapter->eNVMType == NVM_FLASH) + { + Adapter->uiNVMDSDSize = BcmGetFlashSize(Adapter); + } + return 0; +} + +//----------------------------------------------------------------------------- +// Procedure: BcmValidateNvm +// +// Description: Validates the NVM Type option selected against the device +// +// Arguments: +// Adapter - ptr to Adapter object instance +// +// Returns: +// +//----------------------------------------------------------------------------- +static VOID BcmValidateNvmType(PMINI_ADAPTER Adapter) +{ + + // + // if forcing the FLASH through CFG file, we should ensure device really has a FLASH. + // Accessing the FLASH address without the FLASH being present can cause hang/freeze etc. + // So if NVM_FLASH is selected for older chipsets, change it to AUTODETECT where EEPROM is 1st choice. + // + + if(Adapter->eNVMType == NVM_FLASH && + Adapter->chip_id < 0xBECE3300) + { + Adapter->eNVMType = NVM_AUTODETECT; + } +} +//----------------------------------------------------------------------------- +// Procedure: BcmReadFlashRDID +// +// Description: Reads ID from Serial Flash +// +// Arguments: +// Adapter - ptr to Adapter object instance +// +// Returns: +// Flash ID +//----------------------------------------------------------------------------- +static ULONG BcmReadFlashRDID(PMINI_ADAPTER Adapter) +{ + ULONG ulRDID = 0; + UINT value; +// +// Read ID Instruction. +// + value = (FLASH_CMD_READ_ID<<24); + wrmalt(Adapter, FLASH_SPI_CMDQ_REG,&value, sizeof(value)); + +//Delay + udelay(10); +// +// Read SPI READQ REG. The output will be WWXXYYZZ. +// The ID is 3Bytes long and is WWXXYY. ZZ needs to be Ignored. +// + rdmalt(Adapter, FLASH_SPI_READQ_REG, (PUINT)&ulRDID, sizeof(ulRDID)); + + return (ulRDID >>8); + + +} + +INT BcmAllocFlashCSStructure(PMINI_ADAPTER psAdapter) +{ + if(psAdapter == NULL) + { + BCM_DEBUG_PRINT(psAdapter,DBG_TYPE_PRINTK, 0, 0, "Adapter structure point is NULL"); + return -EINVAL; + } + psAdapter->psFlashCSInfo = (PFLASH_CS_INFO)kzalloc(sizeof(FLASH_CS_INFO), GFP_KERNEL); + if(psAdapter->psFlashCSInfo == NULL) + { + BCM_DEBUG_PRINT(psAdapter,DBG_TYPE_PRINTK, 0, 0,"Can't Allocate memory for Flash 1.x"); + return -ENOMEM; + } + + psAdapter->psFlash2xCSInfo = (PFLASH2X_CS_INFO)kzalloc(sizeof(FLASH2X_CS_INFO), GFP_KERNEL); + if(psAdapter->psFlash2xCSInfo == NULL) + { + BCM_DEBUG_PRINT(psAdapter,DBG_TYPE_PRINTK, 0, 0,"Can't Allocate memory for Flash 2.x"); + kfree(psAdapter->psFlashCSInfo); + return -ENOMEM; + } + + psAdapter->psFlash2xVendorInfo = (PFLASH2X_VENDORSPECIFIC_INFO)kzalloc(sizeof(FLASH2X_VENDORSPECIFIC_INFO), GFP_KERNEL); + if(psAdapter->psFlash2xVendorInfo == NULL) + { + BCM_DEBUG_PRINT(psAdapter,DBG_TYPE_PRINTK, 0, 0,"Can't Allocate Vendor Info Memory for Flash 2.x"); + kfree(psAdapter->psFlashCSInfo); + kfree(psAdapter->psFlash2xCSInfo); + return -ENOMEM; + } + + return STATUS_SUCCESS; +} + +INT BcmDeAllocFlashCSStructure(PMINI_ADAPTER psAdapter) +{ + if(psAdapter == NULL) + { + BCM_DEBUG_PRINT(psAdapter,DBG_TYPE_PRINTK, 0, 0," Adapter structure point is NULL"); + return -EINVAL; + } + kfree(psAdapter->psFlashCSInfo); + kfree(psAdapter->psFlash2xCSInfo); + kfree(psAdapter->psFlash2xVendorInfo); + return STATUS_SUCCESS ; +} + +static INT BcmDumpFlash2XCSStructure(PFLASH2X_CS_INFO psFlash2xCSInfo,PMINI_ADAPTER Adapter) +{ + UINT Index = 0; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "**********************FLASH2X CS Structure *******************"); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Signature is :%x", (psFlash2xCSInfo->MagicNumber)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Flash Major Version :%d", MAJOR_VERSION(psFlash2xCSInfo->FlashLayoutVersion)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Flash Minor Version :%d", MINOR_VERSION(psFlash2xCSInfo->FlashLayoutVersion)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, " ISOImageMajorVersion:0x%x", (psFlash2xCSInfo->ISOImageVersion)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "SCSIFirmwareMajorVersion :0x%x", (psFlash2xCSInfo->SCSIFirmwareVersion)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForPart1ISOImage :0x%x", (psFlash2xCSInfo->OffsetFromZeroForPart1ISOImage)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForScsiFirmware :0x%x", (psFlash2xCSInfo->OffsetFromZeroForScsiFirmware)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "SizeOfScsiFirmware :0x%x", (psFlash2xCSInfo->SizeOfScsiFirmware )); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForPart2ISOImage :0x%x", (psFlash2xCSInfo->OffsetFromZeroForPart2ISOImage)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForDSDStart :0x%x", (psFlash2xCSInfo->OffsetFromZeroForDSDStart)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForDSDEnd :0x%x", (psFlash2xCSInfo->OffsetFromZeroForDSDEnd)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForVSAStart :0x%x", (psFlash2xCSInfo->OffsetFromZeroForVSAStart)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForVSAEnd :0x%x", (psFlash2xCSInfo->OffsetFromZeroForVSAEnd)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForControlSectionStart :0x%x", (psFlash2xCSInfo->OffsetFromZeroForControlSectionStart)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForControlSectionData :0x%x", (psFlash2xCSInfo->OffsetFromZeroForControlSectionData)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "CDLessInactivityTimeout :0x%x", (psFlash2xCSInfo->CDLessInactivityTimeout)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "NewImageSignature :0x%x", (psFlash2xCSInfo->NewImageSignature)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "FlashSectorSizeSig :0x%x", (psFlash2xCSInfo->FlashSectorSizeSig)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "FlashSectorSize :0x%x", (psFlash2xCSInfo->FlashSectorSize)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "FlashWriteSupportSize :0x%x", (psFlash2xCSInfo->FlashWriteSupportSize)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "TotalFlashSize :0x%X", (psFlash2xCSInfo->TotalFlashSize)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "FlashBaseAddr :0x%x", (psFlash2xCSInfo->FlashBaseAddr)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "FlashPartMaxSize :0x%x", (psFlash2xCSInfo->FlashPartMaxSize)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "IsCDLessDeviceBootSig :0x%x", (psFlash2xCSInfo->IsCDLessDeviceBootSig)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "MassStorageTimeout :0x%x", (psFlash2xCSInfo->MassStorageTimeout)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage1Part1Start :0x%x", (psFlash2xCSInfo->OffsetISOImage1Part1Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage1Part1End :0x%x", (psFlash2xCSInfo->OffsetISOImage1Part1End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage1Part2Start :0x%x", (psFlash2xCSInfo->OffsetISOImage1Part2Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage1Part2End :0x%x", (psFlash2xCSInfo->OffsetISOImage1Part2End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage1Part3Start :0x%x", (psFlash2xCSInfo->OffsetISOImage1Part3Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage1Part3End :0x%x", (psFlash2xCSInfo->OffsetISOImage1Part3End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage2Part1Start :0x%x", (psFlash2xCSInfo->OffsetISOImage2Part1Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage2Part1End :0x%x", (psFlash2xCSInfo->OffsetISOImage2Part1End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage2Part2Start :0x%x", (psFlash2xCSInfo->OffsetISOImage2Part2Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage2Part2End :0x%x", (psFlash2xCSInfo->OffsetISOImage2Part2End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage2Part3Start :0x%x", (psFlash2xCSInfo->OffsetISOImage2Part3Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetISOImage2Part3End :0x%x", (psFlash2xCSInfo->OffsetISOImage2Part3End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromDSDStartForDSDHeader :0x%x", (psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForDSD1Start :0x%x", (psFlash2xCSInfo->OffsetFromZeroForDSD1Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForDSD1End :0x%x", (psFlash2xCSInfo->OffsetFromZeroForDSD1End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForDSD2Start :0x%x", (psFlash2xCSInfo->OffsetFromZeroForDSD2Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForDSD2End :0x%x", (psFlash2xCSInfo->OffsetFromZeroForDSD2End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForVSA1Start :0x%x", (psFlash2xCSInfo->OffsetFromZeroForVSA1Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForVSA1End :0x%x", (psFlash2xCSInfo->OffsetFromZeroForVSA1End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForVSA2Start :0x%x", (psFlash2xCSInfo->OffsetFromZeroForVSA2Start)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "OffsetFromZeroForVSA2End :0x%x", (psFlash2xCSInfo->OffsetFromZeroForVSA2End)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Sector Access Bit Map is Defined as :"); + for(Index =0; Index <(FLASH2X_TOTAL_SIZE/(DEFAULT_SECTOR_SIZE *16)); Index++) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "SectorAccessBitMap[%d] :0x%x", Index, + (psFlash2xCSInfo->SectorAccessBitMap[Index])); + } + + return STATUS_SUCCESS; +} + + +static INT ConvertEndianOf2XCSStructure(PFLASH2X_CS_INFO psFlash2xCSInfo) +{ + UINT Index = 0; + psFlash2xCSInfo->MagicNumber = ntohl(psFlash2xCSInfo->MagicNumber); + psFlash2xCSInfo->FlashLayoutVersion= ntohl(psFlash2xCSInfo->FlashLayoutVersion); + //psFlash2xCSInfo->FlashLayoutMinorVersion = ntohs(psFlash2xCSInfo->FlashLayoutMinorVersion); + psFlash2xCSInfo->ISOImageVersion = ntohl(psFlash2xCSInfo->ISOImageVersion); + psFlash2xCSInfo->SCSIFirmwareVersion =ntohl(psFlash2xCSInfo->SCSIFirmwareVersion); + psFlash2xCSInfo->OffsetFromZeroForPart1ISOImage = ntohl(psFlash2xCSInfo->OffsetFromZeroForPart1ISOImage); + psFlash2xCSInfo->OffsetFromZeroForScsiFirmware = ntohl(psFlash2xCSInfo->OffsetFromZeroForScsiFirmware); + psFlash2xCSInfo->SizeOfScsiFirmware = ntohl(psFlash2xCSInfo->SizeOfScsiFirmware ); + psFlash2xCSInfo->OffsetFromZeroForPart2ISOImage = ntohl(psFlash2xCSInfo->OffsetFromZeroForPart2ISOImage); + psFlash2xCSInfo->OffsetFromZeroForDSDStart = ntohl(psFlash2xCSInfo->OffsetFromZeroForDSDStart); + psFlash2xCSInfo->OffsetFromZeroForDSDEnd = ntohl(psFlash2xCSInfo->OffsetFromZeroForDSDEnd); + psFlash2xCSInfo->OffsetFromZeroForVSAStart = ntohl(psFlash2xCSInfo->OffsetFromZeroForVSAStart); + psFlash2xCSInfo->OffsetFromZeroForVSAEnd = ntohl(psFlash2xCSInfo->OffsetFromZeroForVSAEnd); + psFlash2xCSInfo->OffsetFromZeroForControlSectionStart = ntohl(psFlash2xCSInfo->OffsetFromZeroForControlSectionStart); + psFlash2xCSInfo->OffsetFromZeroForControlSectionData = ntohl(psFlash2xCSInfo->OffsetFromZeroForControlSectionData); + psFlash2xCSInfo->CDLessInactivityTimeout = ntohl(psFlash2xCSInfo->CDLessInactivityTimeout); + psFlash2xCSInfo->NewImageSignature = ntohl(psFlash2xCSInfo->NewImageSignature); + psFlash2xCSInfo->FlashSectorSizeSig = ntohl(psFlash2xCSInfo->FlashSectorSizeSig); + psFlash2xCSInfo->FlashSectorSize = ntohl(psFlash2xCSInfo->FlashSectorSize); + psFlash2xCSInfo->FlashWriteSupportSize = ntohl(psFlash2xCSInfo->FlashWriteSupportSize); + psFlash2xCSInfo->TotalFlashSize = ntohl(psFlash2xCSInfo->TotalFlashSize); + psFlash2xCSInfo->FlashBaseAddr = ntohl(psFlash2xCSInfo->FlashBaseAddr); + psFlash2xCSInfo->FlashPartMaxSize = ntohl(psFlash2xCSInfo->FlashPartMaxSize); + psFlash2xCSInfo->IsCDLessDeviceBootSig = ntohl(psFlash2xCSInfo->IsCDLessDeviceBootSig); + psFlash2xCSInfo->MassStorageTimeout = ntohl(psFlash2xCSInfo->MassStorageTimeout); + psFlash2xCSInfo->OffsetISOImage1Part1Start = ntohl(psFlash2xCSInfo->OffsetISOImage1Part1Start); + psFlash2xCSInfo->OffsetISOImage1Part1End = ntohl(psFlash2xCSInfo->OffsetISOImage1Part1End); + psFlash2xCSInfo->OffsetISOImage1Part2Start = ntohl(psFlash2xCSInfo->OffsetISOImage1Part2Start); + psFlash2xCSInfo->OffsetISOImage1Part2End = ntohl(psFlash2xCSInfo->OffsetISOImage1Part2End); + psFlash2xCSInfo->OffsetISOImage1Part3Start = ntohl(psFlash2xCSInfo->OffsetISOImage1Part3Start); + psFlash2xCSInfo->OffsetISOImage1Part3End = ntohl(psFlash2xCSInfo->OffsetISOImage1Part3End); + psFlash2xCSInfo->OffsetISOImage2Part1Start = ntohl(psFlash2xCSInfo->OffsetISOImage2Part1Start); + psFlash2xCSInfo->OffsetISOImage2Part1End = ntohl(psFlash2xCSInfo->OffsetISOImage2Part1End); + psFlash2xCSInfo->OffsetISOImage2Part2Start = ntohl(psFlash2xCSInfo->OffsetISOImage2Part2Start); + psFlash2xCSInfo->OffsetISOImage2Part2End = ntohl(psFlash2xCSInfo->OffsetISOImage2Part2End); + psFlash2xCSInfo->OffsetISOImage2Part3Start = ntohl(psFlash2xCSInfo->OffsetISOImage2Part3Start); + psFlash2xCSInfo->OffsetISOImage2Part3End = ntohl(psFlash2xCSInfo->OffsetISOImage2Part3End); + psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader = ntohl(psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader); + psFlash2xCSInfo->OffsetFromZeroForDSD1Start = ntohl(psFlash2xCSInfo->OffsetFromZeroForDSD1Start); + psFlash2xCSInfo->OffsetFromZeroForDSD1End = ntohl(psFlash2xCSInfo->OffsetFromZeroForDSD1End); + psFlash2xCSInfo->OffsetFromZeroForDSD2Start = ntohl(psFlash2xCSInfo->OffsetFromZeroForDSD2Start); + psFlash2xCSInfo->OffsetFromZeroForDSD2End = ntohl(psFlash2xCSInfo->OffsetFromZeroForDSD2End); + psFlash2xCSInfo->OffsetFromZeroForVSA1Start = ntohl(psFlash2xCSInfo->OffsetFromZeroForVSA1Start); + psFlash2xCSInfo->OffsetFromZeroForVSA1End = ntohl(psFlash2xCSInfo->OffsetFromZeroForVSA1End); + psFlash2xCSInfo->OffsetFromZeroForVSA2Start = ntohl(psFlash2xCSInfo->OffsetFromZeroForVSA2Start); + psFlash2xCSInfo->OffsetFromZeroForVSA2End = ntohl(psFlash2xCSInfo->OffsetFromZeroForVSA2End); + for(Index =0; Index <(FLASH2X_TOTAL_SIZE/(DEFAULT_SECTOR_SIZE *16)); Index++) + { + psFlash2xCSInfo->SectorAccessBitMap[Index] = ntohl(psFlash2xCSInfo->SectorAccessBitMap[Index]); + } + return STATUS_SUCCESS; +} + +static INT ConvertEndianOfCSStructure(PFLASH_CS_INFO psFlashCSInfo) +{ + //UINT Index = 0; + psFlashCSInfo->MagicNumber =ntohl(psFlashCSInfo->MagicNumber); + psFlashCSInfo->FlashLayoutVersion =ntohl(psFlashCSInfo->FlashLayoutVersion); + psFlashCSInfo->ISOImageVersion = ntohl(psFlashCSInfo->ISOImageVersion); + //won't convert according to old assumption + psFlashCSInfo->SCSIFirmwareVersion =(psFlashCSInfo->SCSIFirmwareVersion); + + psFlashCSInfo->OffsetFromZeroForPart1ISOImage = ntohl(psFlashCSInfo->OffsetFromZeroForPart1ISOImage); + psFlashCSInfo->OffsetFromZeroForScsiFirmware = ntohl(psFlashCSInfo->OffsetFromZeroForScsiFirmware); + psFlashCSInfo->SizeOfScsiFirmware = ntohl(psFlashCSInfo->SizeOfScsiFirmware ); + psFlashCSInfo->OffsetFromZeroForPart2ISOImage = ntohl(psFlashCSInfo->OffsetFromZeroForPart2ISOImage); + psFlashCSInfo->OffsetFromZeroForCalibrationStart = ntohl(psFlashCSInfo->OffsetFromZeroForCalibrationStart); + psFlashCSInfo->OffsetFromZeroForCalibrationEnd = ntohl(psFlashCSInfo->OffsetFromZeroForCalibrationEnd); + psFlashCSInfo->OffsetFromZeroForVSAStart = ntohl(psFlashCSInfo->OffsetFromZeroForVSAStart); + psFlashCSInfo->OffsetFromZeroForVSAEnd = ntohl(psFlashCSInfo->OffsetFromZeroForVSAEnd); + psFlashCSInfo->OffsetFromZeroForControlSectionStart = ntohl(psFlashCSInfo->OffsetFromZeroForControlSectionStart); + psFlashCSInfo->OffsetFromZeroForControlSectionData = ntohl(psFlashCSInfo->OffsetFromZeroForControlSectionData); + psFlashCSInfo->CDLessInactivityTimeout = ntohl(psFlashCSInfo->CDLessInactivityTimeout); + psFlashCSInfo->NewImageSignature = ntohl(psFlashCSInfo->NewImageSignature); + psFlashCSInfo->FlashSectorSizeSig = ntohl(psFlashCSInfo->FlashSectorSizeSig); + psFlashCSInfo->FlashSectorSize = ntohl(psFlashCSInfo->FlashSectorSize); + psFlashCSInfo->FlashWriteSupportSize = ntohl(psFlashCSInfo->FlashWriteSupportSize); + psFlashCSInfo->TotalFlashSize = ntohl(psFlashCSInfo->TotalFlashSize); + psFlashCSInfo->FlashBaseAddr = ntohl(psFlashCSInfo->FlashBaseAddr); + psFlashCSInfo->FlashPartMaxSize = ntohl(psFlashCSInfo->FlashPartMaxSize); + psFlashCSInfo->IsCDLessDeviceBootSig = ntohl(psFlashCSInfo->IsCDLessDeviceBootSig); + psFlashCSInfo->MassStorageTimeout = ntohl(psFlashCSInfo->MassStorageTimeout); + + return STATUS_SUCCESS; +} + +static INT IsSectionExistInVendorInfo(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL section) +{ + return ( Adapter->uiVendorExtnFlag && + (Adapter->psFlash2xVendorInfo->VendorSection[section].AccessFlags & FLASH2X_SECTION_PRESENT) && + (Adapter->psFlash2xVendorInfo->VendorSection[section].OffsetFromZeroForSectionStart != UNINIT_PTR_IN_CS) ); +} + +static VOID UpdateVendorInfo(PMINI_ADAPTER Adapter) +{ + B_UINT32 i = 0; + UINT uiSizeSection = 0; + + Adapter->uiVendorExtnFlag = FALSE; + + for(i = 0;i < TOTAL_SECTIONS;i++) + Adapter->psFlash2xVendorInfo->VendorSection[i].OffsetFromZeroForSectionStart = UNINIT_PTR_IN_CS; + + if(STATUS_SUCCESS != vendorextnGetSectionInfo(Adapter, Adapter->psFlash2xVendorInfo)) + return; + + i = 0; + while(i < TOTAL_SECTIONS) + { + if(!(Adapter->psFlash2xVendorInfo->VendorSection[i].AccessFlags & FLASH2X_SECTION_PRESENT)) + { + i++; + continue; + } + + Adapter->uiVendorExtnFlag = TRUE; + uiSizeSection = (Adapter->psFlash2xVendorInfo->VendorSection[i].OffsetFromZeroForSectionEnd - + Adapter->psFlash2xVendorInfo->VendorSection[i].OffsetFromZeroForSectionStart); + + switch(i) + { + case DSD0: + if(( uiSizeSection >= (Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + sizeof(DSD_HEADER))) && + (UNINIT_PTR_IN_CS != Adapter->psFlash2xVendorInfo->VendorSection[i].OffsetFromZeroForSectionStart)) + Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDEnd = VENDOR_PTR_IN_CS; + else + Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDEnd = UNINIT_PTR_IN_CS; + break; + + case DSD1: + if(( uiSizeSection >= (Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + sizeof(DSD_HEADER))) && + (UNINIT_PTR_IN_CS != Adapter->psFlash2xVendorInfo->VendorSection[i].OffsetFromZeroForSectionStart)) + Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1Start = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1End = VENDOR_PTR_IN_CS; + else + Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1Start = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1End = UNINIT_PTR_IN_CS; + break; + + case DSD2: + if(( uiSizeSection >= (Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + sizeof(DSD_HEADER))) && + (UNINIT_PTR_IN_CS != Adapter->psFlash2xVendorInfo->VendorSection[i].OffsetFromZeroForSectionStart)) + Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2Start = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2End = VENDOR_PTR_IN_CS; + else + Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2Start = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2End = UNINIT_PTR_IN_CS; + break; + case VSA0: + if(UNINIT_PTR_IN_CS != Adapter->psFlash2xVendorInfo->VendorSection[i].OffsetFromZeroForSectionStart) + Adapter->psFlash2xCSInfo->OffsetFromZeroForVSAStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForVSAEnd = VENDOR_PTR_IN_CS; + else + Adapter->psFlash2xCSInfo->OffsetFromZeroForVSAStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForVSAEnd = UNINIT_PTR_IN_CS; + break; + + case VSA1: + if(UNINIT_PTR_IN_CS != Adapter->psFlash2xVendorInfo->VendorSection[i].OffsetFromZeroForSectionStart) + Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA1Start = Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA1End = VENDOR_PTR_IN_CS; + else + Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA1Start = Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA1End = UNINIT_PTR_IN_CS; + break; + case VSA2: + if(UNINIT_PTR_IN_CS != Adapter->psFlash2xVendorInfo->VendorSection[i].OffsetFromZeroForSectionStart) + Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA2Start = Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA2End = VENDOR_PTR_IN_CS; + else + Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA2Start = Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA2End = UNINIT_PTR_IN_CS; + break; + + default: + break; + } + i++; + } + +} + +//----------------------------------------------------------------------------- +// Procedure: BcmGetFlashCSInfo +// +// Description: Reads control structure and gets Cal section addresses. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// +// Returns: +// +//----------------------------------------------------------------------------- + +static INT BcmGetFlashCSInfo(PMINI_ADAPTER Adapter) +{ + //FLASH_CS_INFO sFlashCsInfo = {0}; + +#if !defined(BCM_SHM_INTERFACE) || defined(FLASH_DIRECT_ACCESS) + UINT value; +#endif + UINT uiFlashLayoutMajorVersion; + Adapter->uiFlashLayoutMinorVersion = 0; + Adapter->uiFlashLayoutMajorVersion = 0; + Adapter->ulFlashControlSectionStart = FLASH_CS_INFO_START_ADDR; + + + Adapter->uiFlashBaseAdd = 0; + Adapter->ulFlashCalStart = 0; + memset(Adapter->psFlashCSInfo, 0 ,sizeof(FLASH_CS_INFO)); + memset(Adapter->psFlash2xCSInfo, 0 ,sizeof(FLASH2X_CS_INFO)); + + if(!Adapter->bDDRInitDone) + { + { + value = FLASH_CONTIGIOUS_START_ADDR_BEFORE_INIT; + wrmalt(Adapter, 0xAF00A080, &value, sizeof(value)); + } + } + + + // Reading first 8 Bytes to get the Flash Layout + // MagicNumber(4 bytes) +FlashLayoutMinorVersion(2 Bytes) +FlashLayoutMajorVersion(2 Bytes) + BeceemFlashBulkRead(Adapter,(PUINT)Adapter->psFlashCSInfo,Adapter->ulFlashControlSectionStart,8); + + Adapter->psFlashCSInfo->FlashLayoutVersion = ntohl(Adapter->psFlashCSInfo->FlashLayoutVersion); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Flash Layout Version :%X", (Adapter->psFlashCSInfo->FlashLayoutVersion)); + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Flash Layout Minor Version :%d\n", ntohs(sFlashCsInfo.FlashLayoutMinorVersion)); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Signature is :%x\n", ntohl(Adapter->psFlashCSInfo->MagicNumber)); + + if(FLASH_CONTROL_STRUCT_SIGNATURE == ntohl(Adapter->psFlashCSInfo->MagicNumber)) + { + uiFlashLayoutMajorVersion = MAJOR_VERSION((Adapter->psFlashCSInfo->FlashLayoutVersion)); + Adapter->uiFlashLayoutMinorVersion = MINOR_VERSION((Adapter->psFlashCSInfo->FlashLayoutVersion)); + } + else + { + Adapter->uiFlashLayoutMinorVersion = 0; + uiFlashLayoutMajorVersion = 0; + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"FLASH LAYOUT MAJOR VERSION :%X", uiFlashLayoutMajorVersion); + + if(uiFlashLayoutMajorVersion < FLASH_2X_MAJOR_NUMBER) + { + BeceemFlashBulkRead(Adapter,(PUINT)Adapter->psFlashCSInfo,Adapter->ulFlashControlSectionStart,sizeof(FLASH_CS_INFO)); + ConvertEndianOfCSStructure(Adapter->psFlashCSInfo); + Adapter->ulFlashCalStart = (Adapter->psFlashCSInfo->OffsetFromZeroForCalibrationStart); + + if(!((Adapter->uiFlashLayoutMajorVersion == 1) && (Adapter->uiFlashLayoutMinorVersion == 1))) + { + Adapter->ulFlashControlSectionStart = Adapter->psFlashCSInfo->OffsetFromZeroForControlSectionStart; + } + + if((FLASH_CONTROL_STRUCT_SIGNATURE == (Adapter->psFlashCSInfo->MagicNumber)) && + (SCSI_FIRMWARE_MINOR_VERSION <= MINOR_VERSION(Adapter->psFlashCSInfo->SCSIFirmwareVersion)) && + (FLASH_SECTOR_SIZE_SIG == (Adapter->psFlashCSInfo->FlashSectorSizeSig)) && + (BYTE_WRITE_SUPPORT == (Adapter->psFlashCSInfo->FlashWriteSupportSize))) + { + Adapter->ulFlashWriteSize = (Adapter->psFlashCSInfo->FlashWriteSupportSize); + Adapter->fpFlashWrite = flashByteWrite; + Adapter->fpFlashWriteWithStatusCheck = flashByteWriteStatus; + } + else + { + Adapter->ulFlashWriteSize = MAX_RW_SIZE; + Adapter->fpFlashWrite = flashWrite; + Adapter->fpFlashWriteWithStatusCheck = flashWriteStatus; + } + + BcmGetFlashSectorSize(Adapter, (Adapter->psFlashCSInfo->FlashSectorSizeSig), + (Adapter->psFlashCSInfo->FlashSectorSize)); + + + Adapter->uiFlashBaseAdd = Adapter->psFlashCSInfo->FlashBaseAddr & 0xFCFFFFFF; + + + } + else + { + if(BcmFlash2xBulkRead(Adapter,(PUINT)Adapter->psFlash2xCSInfo,NO_SECTION_VAL, + Adapter->ulFlashControlSectionStart,sizeof(FLASH2X_CS_INFO))) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Unable to read CS structure \n"); + return STATUS_FAILURE; + } + ConvertEndianOf2XCSStructure(Adapter->psFlash2xCSInfo); + BcmDumpFlash2XCSStructure(Adapter->psFlash2xCSInfo,Adapter); + if((FLASH_CONTROL_STRUCT_SIGNATURE == Adapter->psFlash2xCSInfo->MagicNumber) && + (SCSI_FIRMWARE_MINOR_VERSION <= MINOR_VERSION(Adapter->psFlash2xCSInfo->SCSIFirmwareVersion)) && + (FLASH_SECTOR_SIZE_SIG == Adapter->psFlash2xCSInfo->FlashSectorSizeSig) && + (BYTE_WRITE_SUPPORT == Adapter->psFlash2xCSInfo->FlashWriteSupportSize)) + { + Adapter->ulFlashWriteSize = Adapter->psFlash2xCSInfo->FlashWriteSupportSize; + Adapter->fpFlashWrite = flashByteWrite; + Adapter->fpFlashWriteWithStatusCheck = flashByteWriteStatus; + } + else + { + Adapter->ulFlashWriteSize = MAX_RW_SIZE; + Adapter->fpFlashWrite = flashWrite; + Adapter->fpFlashWriteWithStatusCheck = flashWriteStatus; + } + + BcmGetFlashSectorSize(Adapter, Adapter->psFlash2xCSInfo->FlashSectorSizeSig, + Adapter->psFlash2xCSInfo->FlashSectorSize); + + UpdateVendorInfo(Adapter); + + BcmGetActiveDSD(Adapter); + BcmGetActiveISO(Adapter); + Adapter->uiFlashBaseAdd = Adapter->psFlash2xCSInfo->FlashBaseAddr & 0xFCFFFFFF; + Adapter->ulFlashControlSectionStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForControlSectionStart; + + } + /* + Concerns: what if CS sector size does not match with this sector size ??? + what is the indication of AccessBitMap in CS in flash 2.x ???? + */ + Adapter->ulFlashID = BcmReadFlashRDID(Adapter); + + Adapter->uiFlashLayoutMajorVersion = uiFlashLayoutMajorVersion; + + + return STATUS_SUCCESS ; +} + + +//----------------------------------------------------------------------------- +// Procedure: BcmGetNvmType +// +// Description: Finds the type of NVM used. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// +// Returns: +// NVM_TYPE +// +//----------------------------------------------------------------------------- + +static NVM_TYPE BcmGetNvmType(PMINI_ADAPTER Adapter) +{ + UINT uiData = 0; + + BeceemEEPROMBulkRead(Adapter,&uiData,0x0,4); + if(uiData == BECM) + { + return NVM_EEPROM; + } + // + // Read control struct and get cal addresses before accessing the flash + // + BcmGetFlashCSInfo(Adapter); + + BeceemFlashBulkRead(Adapter,&uiData,0x0 + Adapter->ulFlashCalStart,4); + if(uiData == BECM) + { + return NVM_FLASH; + } +// +// even if there is no valid signature on EEPROM/FLASH find out if they really exist. +// if exist select it. +// + if(BcmGetEEPROMSize(Adapter)) + { + return NVM_EEPROM; + } + +//TBD for Flash. + + + return NVM_UNKNOWN; +} + +/** +* BcmGetSectionValStartOffset - this will calculate the section's starting offset if section val is given +* @Adapter : Drivers Private Data structure +* @eFlashSectionVal : Flash secion value defined in enum FLASH2X_SECTION_VAL +* +* Return value:- +* On success it return the start offset of the provided section val +* On Failure -returns STATUS_FAILURE +**/ + +INT BcmGetSectionValStartOffset(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlashSectionVal) +{ + /* + * Considering all the section for which end offset can be calculated or directly given + * in CS Structure. if matching case does not exist, return STATUS_FAILURE indicating section + * endoffset can't be calculated or given in CS Structure. + */ + + INT SectStartOffset = 0 ; + + SectStartOffset = INVALID_OFFSET ; + + if(IsSectionExistInVendorInfo(Adapter,eFlashSectionVal)) + { + return Adapter->psFlash2xVendorInfo->VendorSection[eFlashSectionVal].OffsetFromZeroForSectionStart; + } + + switch(eFlashSectionVal) + { + case ISO_IMAGE1 : + if((Adapter->psFlash2xCSInfo->OffsetISOImage1Part1Start != UNINIT_PTR_IN_CS) && + (IsNonCDLessDevice(Adapter) == FALSE)) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage1Part1Start); + break; + case ISO_IMAGE2 : + if((Adapter->psFlash2xCSInfo->OffsetISOImage2Part1Start != UNINIT_PTR_IN_CS) && + (IsNonCDLessDevice(Adapter) == FALSE)) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage2Part1Start); + break; + case DSD0 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDStart != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDStart); + break; + case DSD1 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1Start != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1Start); + break; + case DSD2 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2Start != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2Start); + break; + case VSA0 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForVSAStart != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForVSAStart); + break; + case VSA1 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA1Start != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA1Start); + break; + case VSA2 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA2Start != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA2Start); + break; + case SCSI : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForScsiFirmware != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForScsiFirmware); + break; + case CONTROL_SECTION : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForControlSectionStart != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForControlSectionStart); + break; + case ISO_IMAGE1_PART2 : + if(Adapter->psFlash2xCSInfo->OffsetISOImage1Part2Start != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage1Part2Start); + break; + case ISO_IMAGE1_PART3 : + if(Adapter->psFlash2xCSInfo->OffsetISOImage1Part3Start != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage1Part3Start); + break; + case ISO_IMAGE2_PART2 : + if(Adapter->psFlash2xCSInfo->OffsetISOImage2Part2Start != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage2Part2Start); + break; + case ISO_IMAGE2_PART3 : + if(Adapter->psFlash2xCSInfo->OffsetISOImage2Part3Start != UNINIT_PTR_IN_CS) + SectStartOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage2Part3Start); + break; + default : + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Section Does not exist in Flash 2.x"); + SectStartOffset = INVALID_OFFSET; + } + return SectStartOffset; +} + +/** +* BcmGetSectionValEndOffset - this will calculate the section's Ending offset if section val is given +* @Adapter : Drivers Private Data structure +* @eFlashSectionVal : Flash secion value defined in enum FLASH2X_SECTION_VAL +* +* Return value:- +* On success it return the end offset of the provided section val +* On Failure -returns STATUS_FAILURE +**/ + +INT BcmGetSectionValEndOffset(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlash2xSectionVal) +{ + INT SectEndOffset = 0 ; + SectEndOffset = INVALID_OFFSET; + + if(IsSectionExistInVendorInfo(Adapter,eFlash2xSectionVal)) + { + return Adapter->psFlash2xVendorInfo->VendorSection[eFlash2xSectionVal].OffsetFromZeroForSectionEnd; + } + + switch(eFlash2xSectionVal) + { + case ISO_IMAGE1 : + if((Adapter->psFlash2xCSInfo->OffsetISOImage1Part1End!= UNINIT_PTR_IN_CS) && + (IsNonCDLessDevice(Adapter) == FALSE)) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage1Part1End); + break; + case ISO_IMAGE2 : + if((Adapter->psFlash2xCSInfo->OffsetISOImage2Part1End!= UNINIT_PTR_IN_CS) && + (IsNonCDLessDevice(Adapter) == FALSE)) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage2Part1End); + break; + case DSD0 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDEnd != UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDEnd); + break; + case DSD1 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1End != UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1End); + break; + case DSD2 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2End != UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2End); + break; + case VSA0 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForVSAEnd != UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForVSAEnd); + break; + case VSA1 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA1End != UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA1End); + break; + case VSA2 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA2End != UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA2End); + break; + case SCSI : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForScsiFirmware != UNINIT_PTR_IN_CS) + SectEndOffset = ((Adapter->psFlash2xCSInfo->OffsetFromZeroForScsiFirmware) + + (Adapter->psFlash2xCSInfo->SizeOfScsiFirmware)); + break; + case CONTROL_SECTION : + //Not Clear So Putting failure. confirm and fix it. + SectEndOffset = STATUS_FAILURE; + case ISO_IMAGE1_PART2 : + if(Adapter->psFlash2xCSInfo->OffsetISOImage1Part2End!= UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage1Part2End); + break; + case ISO_IMAGE1_PART3 : + if(Adapter->psFlash2xCSInfo->OffsetISOImage1Part3End!= UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage1Part3End); + break; + case ISO_IMAGE2_PART2 : + if(Adapter->psFlash2xCSInfo->OffsetISOImage2Part2End != UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage2Part2End); + break; + case ISO_IMAGE2_PART3 : + if(Adapter->psFlash2xCSInfo->OffsetISOImage2Part3End!= UNINIT_PTR_IN_CS) + SectEndOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage2Part3End); + break; + + default : + SectEndOffset = INVALID_OFFSET; + } + return SectEndOffset ; +} + +/* +* BcmFlash2xBulkRead:- Read API for Flash Map 2.x . +* @Adapter :Driver Private Data Structure +* @pBuffer : Buffer where data has to be put after reading +* @eFlashSectionVal :Flash Section Val defined in FLASH2X_SECTION_VAL +* @uiOffsetWithinSectionVal :- Offset with in provided section +* @uiNumBytes : Number of Bytes for Read +* +* Return value:- +* return true on success and STATUS_FAILURE on fail. +*/ + +INT BcmFlash2xBulkRead( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + FLASH2X_SECTION_VAL eFlash2xSectionVal, + UINT uiOffsetWithinSectionVal, + UINT uiNumBytes) +{ + + INT Status = STATUS_SUCCESS; + INT SectionStartOffset = 0; + UINT uiAbsoluteOffset = 0 ; + UINT uiTemp =0, value =0 ; + if(Adapter == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Adapter structure is NULL"); + return -EINVAL; + } + if(Adapter->device_removed ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Device has been removed"); + return -ENODEV; + } + + //NO_SECTION_VAL means absolute offset is given. + if(eFlash2xSectionVal == NO_SECTION_VAL) + SectionStartOffset = 0; + else + SectionStartOffset = BcmGetSectionValStartOffset(Adapter,eFlash2xSectionVal); + + if(SectionStartOffset == STATUS_FAILURE ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"This Section<%d> does not exixt in Flash 2.x Map ",eFlash2xSectionVal); + return -EINVAL; + } + + if(IsSectionExistInVendorInfo(Adapter,eFlash2xSectionVal)) + return vendorextnReadSection(Adapter,(PUCHAR)pBuffer, eFlash2xSectionVal, uiOffsetWithinSectionVal, uiNumBytes); + + //calculating the absolute offset from FLASH; + uiAbsoluteOffset = uiOffsetWithinSectionVal + SectionStartOffset; + rdmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); + value = 0; + wrmalt(Adapter, 0x0f000C80,&value, sizeof(value)); + + Status= BeceemFlashBulkRead(Adapter, pBuffer,uiAbsoluteOffset,uiNumBytes) ; + + wrmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Flash Read Failed with Status :%d", Status); + return Status ; + } + + return Status; +} + +/* +* BcmFlash2xBulkWrite :-API for Writing on the Flash Map 2.x. +* @Adapter :Driver Private Data Structure +* @pBuffer : Buffer From where data has to taken for writing +* @eFlashSectionVal :Flash Section Val defined in FLASH2X_SECTION_VAL +* @uiOffsetWithinSectionVal :- Offset with in provided section +* @uiNumBytes : Number of Bytes for Write +* +* Return value:- +* return true on success and STATUS_FAILURE on fail. +* +*/ + +INT BcmFlash2xBulkWrite( + PMINI_ADAPTER Adapter, + PUINT pBuffer, + FLASH2X_SECTION_VAL eFlash2xSectVal, + UINT uiOffset, + UINT uiNumBytes, + UINT bVerify) +{ + + INT Status = STATUS_SUCCESS; + UINT FlashSectValStartOffset = 0; + UINT uiTemp = 0, value = 0; + if(Adapter == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Adapter structure is NULL"); + return -EINVAL; + } + if(Adapter->device_removed ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Device has been removed"); + return -ENODEV; + } + + //NO_SECTION_VAL means absolute offset is given. + if(eFlash2xSectVal == NO_SECTION_VAL) + FlashSectValStartOffset = 0; + else + FlashSectValStartOffset = BcmGetSectionValStartOffset(Adapter,eFlash2xSectVal); + + if(FlashSectValStartOffset == STATUS_FAILURE ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"This Section<%d> does not exixt in Flash Map 2.x",eFlash2xSectVal); + return -EINVAL; + } + + if(IsSectionExistInVendorInfo(Adapter,eFlash2xSectVal)) + return vendorextnWriteSection(Adapter, (PUCHAR)pBuffer, eFlash2xSectVal, uiOffset, uiNumBytes, bVerify); + + //calculating the absolute offset from FLASH; + uiOffset = uiOffset + FlashSectValStartOffset; + + rdmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); + value = 0; + wrmalt(Adapter, 0x0f000C80,&value, sizeof(value)); + + Status = BeceemFlashBulkWrite(Adapter, pBuffer,uiOffset,uiNumBytes,bVerify); + + wrmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Flash Write failed with Status :%d", Status); + return Status ; + } + + return Status; + +} + +/** +* BcmGetActiveDSD : Set the Active DSD in Adapter Structure which has to be dumped in DDR +* @Adapter :-Drivers private Data Structure +* +* Return Value:- +* Return STATUS_SUCESS if get success in setting the right DSD else negaive error code +* +**/ +static INT BcmGetActiveDSD(PMINI_ADAPTER Adapter) +{ + FLASH2X_SECTION_VAL uiHighestPriDSD = 0 ; + + uiHighestPriDSD = getHighestPriDSD(Adapter); + Adapter->eActiveDSD = uiHighestPriDSD; + + if(DSD0 == uiHighestPriDSD) + Adapter->ulFlashCalStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDStart; + if(DSD1 == uiHighestPriDSD) + Adapter->ulFlashCalStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1Start; + if(DSD2 == uiHighestPriDSD) + Adapter->ulFlashCalStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2Start; + if(Adapter->eActiveDSD) + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Active DSD :%d", Adapter->eActiveDSD); + if(Adapter->eActiveDSD == 0) + { + //if No DSD gets Active, Make Active the DSD with WR permission + if(IsSectionWritable(Adapter,DSD2)) + { + Adapter->eActiveDSD = DSD2; + Adapter->ulFlashCalStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2Start; + } + else if(IsSectionWritable(Adapter,DSD1)) + { + Adapter->eActiveDSD = DSD1; + Adapter->ulFlashCalStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1Start; + } + else if(IsSectionWritable(Adapter,DSD0)) + { + Adapter->eActiveDSD = DSD0; + Adapter->ulFlashCalStart = Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDStart; + } + } + + return STATUS_SUCCESS; +} + + +/** +* BcmGetActiveISO :- Set the Active ISO in Adapter Data Structue +* @Adapter : Driver private Data Structure +* +* Return Value:- +* Sucsess:- STATUS_SUCESS +* Failure- : negative erro code +* +**/ + +static INT BcmGetActiveISO(PMINI_ADAPTER Adapter) +{ + + INT HighestPriISO = 0 ; + HighestPriISO = getHighestPriISO(Adapter); + + Adapter->eActiveISO = HighestPriISO ; + if(Adapter->eActiveISO == ISO_IMAGE2) + Adapter->uiActiveISOOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage2Part1Start); + else if(Adapter->eActiveISO == ISO_IMAGE1) + Adapter->uiActiveISOOffset = (Adapter->psFlash2xCSInfo->OffsetISOImage1Part1Start); + + if(Adapter->eActiveISO) + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Active ISO :%x", Adapter->eActiveISO); + + return STATUS_SUCCESS; +} + +/** +* IsOffsetWritable :- it will tell the access permission of the sector having passed offset +* @Adapter : Drivers Private Data Structure +* @uiOffset : Offset provided in the Flash +* +* Return Value:- +* Success:-TRUE , offset is writable +* Failure:-FALSE, offset is RO +* +**/ +B_UINT8 IsOffsetWritable(PMINI_ADAPTER Adapter, UINT uiOffset) +{ + UINT uiSectorNum = 0; + UINT uiWordOfSectorPermission =0; + UINT uiBitofSectorePermission = 0; + B_UINT32 permissionBits = 0; + uiSectorNum = uiOffset/Adapter->uiSectorSize; + + //calculating the word having this Sector Access permission from SectorAccessBitMap Array + uiWordOfSectorPermission = Adapter->psFlash2xCSInfo->SectorAccessBitMap[uiSectorNum /16]; + + //calculating the bit index inside the word for this sector + uiBitofSectorePermission = 2*(15 - uiSectorNum %16); + + //Setting Access permission + permissionBits = uiWordOfSectorPermission & (0x3 << uiBitofSectorePermission) ; + permissionBits = (permissionBits >> uiBitofSectorePermission) & 0x3; + if(permissionBits == SECTOR_READWRITE_PERMISSION) + return TRUE; + else + return FALSE; +} + +static INT BcmDumpFlash2xSectionBitMap(PFLASH2X_BITMAP psFlash2xBitMap) +{ + PMINI_ADAPTER Adapter = GET_BCM_ADAPTER(gblpnetdev); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "***************Flash 2.x Section Bitmap***************"); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"ISO_IMAGE1 :0X%x", psFlash2xBitMap->ISO_IMAGE1); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"ISO_IMAGE2 :0X%x", psFlash2xBitMap->ISO_IMAGE2); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"DSD0 :0X%x", psFlash2xBitMap->DSD0); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"DSD1 :0X%x", psFlash2xBitMap->DSD1); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"DSD2 :0X%x", psFlash2xBitMap->DSD2); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"VSA0 :0X%x", psFlash2xBitMap->VSA0); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"VSA1 :0X%x", psFlash2xBitMap->VSA1); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"VSA2 :0X%x", psFlash2xBitMap->VSA2); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"SCSI :0X%x", psFlash2xBitMap->SCSI); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"CONTROL_SECTION :0X%x", psFlash2xBitMap->CONTROL_SECTION); + + return STATUS_SUCCESS; +} + +/** +* BcmGetFlash2xSectionalBitMap :- It will provide the bit map of all the section present in Flash +* 8bit has been assigned to every section. + bit[0] :Section present or not + bit[1] :section is valid or not + bit[2] : Secton is read only or has write permission too. + bit[3] : Active Section - + bit[7...4] = Reserved . + + @Adapter:-Driver private Data Structure +* +* Return value:- +* Success:- STATUS_SUCESS +* Failure:- negative error code +**/ + +INT BcmGetFlash2xSectionalBitMap(PMINI_ADAPTER Adapter, PFLASH2X_BITMAP psFlash2xBitMap) +{ + + + PFLASH2X_CS_INFO psFlash2xCSInfo = Adapter->psFlash2xCSInfo; + FLASH2X_SECTION_VAL uiHighestPriDSD = 0 ; + FLASH2X_SECTION_VAL uiHighestPriISO= 0 ; + BOOLEAN SetActiveDSDDone = FALSE ; + BOOLEAN SetActiveISODone = FALSE ; + + //For 1.x map all the section except DSD0 will be shown as not present + //This part will be used by calibration tool to detect the number of DSD present in Flash. + if(IsFlash2x(Adapter) == FALSE) + { + psFlash2xBitMap->ISO_IMAGE2 = 0; + psFlash2xBitMap->ISO_IMAGE1 = 0; + psFlash2xBitMap->DSD0 = FLASH2X_SECTION_VALID | FLASH2X_SECTION_ACT | FLASH2X_SECTION_PRESENT; //0xF; //0000(Reseved)1(Active)0(RW)1(valid)1(present) + psFlash2xBitMap->DSD1 = 0 ; + psFlash2xBitMap->DSD2 = 0 ; + psFlash2xBitMap->VSA0 = 0 ; + psFlash2xBitMap->VSA1 = 0 ; + psFlash2xBitMap->VSA2 = 0 ; + psFlash2xBitMap->CONTROL_SECTION = 0 ; + psFlash2xBitMap->SCSI= 0 ; + psFlash2xBitMap->Reserved0 = 0 ; + psFlash2xBitMap->Reserved1 = 0 ; + psFlash2xBitMap->Reserved2 = 0 ; + return STATUS_SUCCESS ; + + } + + uiHighestPriDSD = getHighestPriDSD(Adapter); + uiHighestPriISO = getHighestPriISO(Adapter); + + /// + // IS0 IMAGE 2 + /// + if((psFlash2xCSInfo->OffsetISOImage2Part1Start) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->ISO_IMAGE2= psFlash2xBitMap->ISO_IMAGE2 | FLASH2X_SECTION_PRESENT; + + + if(ReadISOSignature(Adapter,ISO_IMAGE2)== ISO_IMAGE_MAGIC_NUMBER) + psFlash2xBitMap->ISO_IMAGE2 |= FLASH2X_SECTION_VALID; + + + //Calculation for extrating the Access permission + if(IsSectionWritable(Adapter, ISO_IMAGE2) == FALSE) + psFlash2xBitMap->ISO_IMAGE2 |= FLASH2X_SECTION_RO; + + if(SetActiveISODone == FALSE && uiHighestPriISO == ISO_IMAGE2) + { + psFlash2xBitMap->ISO_IMAGE2 |= FLASH2X_SECTION_ACT ; + SetActiveISODone = TRUE; + } + + } + + /// + // IS0 IMAGE 1 + /// + if((psFlash2xCSInfo->OffsetISOImage1Part1Start) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->ISO_IMAGE1 = psFlash2xBitMap->ISO_IMAGE1 | FLASH2X_SECTION_PRESENT; + + if(ReadISOSignature(Adapter,ISO_IMAGE1) == ISO_IMAGE_MAGIC_NUMBER) + psFlash2xBitMap->ISO_IMAGE1 |= FLASH2X_SECTION_VALID; + + // Calculation for extrating the Access permission + if(IsSectionWritable(Adapter, ISO_IMAGE1) == FALSE) + psFlash2xBitMap->ISO_IMAGE1 |= FLASH2X_SECTION_RO; + + if(SetActiveISODone == FALSE && uiHighestPriISO == ISO_IMAGE1) + { + psFlash2xBitMap->ISO_IMAGE1 |= FLASH2X_SECTION_ACT ; + SetActiveISODone = TRUE; + } + } + + + + /// + // DSD2 + /// + if((psFlash2xCSInfo->OffsetFromZeroForDSD2Start) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->DSD2= psFlash2xBitMap->DSD2 | FLASH2X_SECTION_PRESENT; + + if(ReadDSDSignature(Adapter,DSD2)== DSD_IMAGE_MAGIC_NUMBER) + psFlash2xBitMap->DSD2 |= FLASH2X_SECTION_VALID; + + //Calculation for extrating the Access permission + if(IsSectionWritable(Adapter, DSD2) == FALSE) + { + psFlash2xBitMap->DSD2 |= FLASH2X_SECTION_RO; + + } + else + { + //Means section is writable + if((SetActiveDSDDone == FALSE) && (uiHighestPriDSD == DSD2)) + { + psFlash2xBitMap->DSD2 |= FLASH2X_SECTION_ACT ; + SetActiveDSDDone =TRUE ; + } + } + } + + /// + // DSD 1 + /// + if((psFlash2xCSInfo->OffsetFromZeroForDSD1Start) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->DSD1= psFlash2xBitMap->DSD1 | FLASH2X_SECTION_PRESENT; + + + if(ReadDSDSignature(Adapter,DSD1)== DSD_IMAGE_MAGIC_NUMBER) + psFlash2xBitMap->DSD1 |= FLASH2X_SECTION_VALID; + + //Calculation for extrating the Access permission + if(IsSectionWritable(Adapter, DSD1) == FALSE) + { + psFlash2xBitMap->DSD1 |= FLASH2X_SECTION_RO; + } + else + { + //Means section is writable + if((SetActiveDSDDone == FALSE) && (uiHighestPriDSD == DSD1)) + { + psFlash2xBitMap->DSD1 |= FLASH2X_SECTION_ACT ; + SetActiveDSDDone =TRUE ; + } + } + + } + + /// + //For DSD 0 + // + if((psFlash2xCSInfo->OffsetFromZeroForDSDStart) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->DSD0 = psFlash2xBitMap->DSD0 | FLASH2X_SECTION_PRESENT; + + if(ReadDSDSignature(Adapter,DSD0) == DSD_IMAGE_MAGIC_NUMBER) + psFlash2xBitMap->DSD0 |= FLASH2X_SECTION_VALID; + + //Setting Access permission + if(IsSectionWritable(Adapter, DSD0) == FALSE) + { + psFlash2xBitMap->DSD0 |= FLASH2X_SECTION_RO; + } + else + { + //Means section is writable + if((SetActiveDSDDone == FALSE) &&(uiHighestPriDSD == DSD0)) + { + psFlash2xBitMap->DSD0 |= FLASH2X_SECTION_ACT ; + SetActiveDSDDone =TRUE ; + } + } + } + + /// + // VSA 0 + /// + if((psFlash2xCSInfo->OffsetFromZeroForVSAStart) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->VSA0= psFlash2xBitMap->VSA0 | FLASH2X_SECTION_PRESENT; + + //Setting the Access Bit. Map is not defined hece setting it always valid + psFlash2xBitMap->VSA0 |= FLASH2X_SECTION_VALID; + + //Calculation for extrating the Access permission + if(IsSectionWritable(Adapter, VSA0) == FALSE) + psFlash2xBitMap->VSA0 |= FLASH2X_SECTION_RO; + + //By Default section is Active + psFlash2xBitMap->VSA0 |= FLASH2X_SECTION_ACT ; + + } + + + /// + // VSA 1 + /// + + if((psFlash2xCSInfo->OffsetFromZeroForVSA1Start) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->VSA1= psFlash2xBitMap->VSA1 | FLASH2X_SECTION_PRESENT; + + //Setting the Access Bit. Map is not defined hece setting it always valid + psFlash2xBitMap->VSA1|= FLASH2X_SECTION_VALID; + + //Checking For Access permission + if(IsSectionWritable(Adapter, VSA1) == FALSE) + psFlash2xBitMap->VSA1 |= FLASH2X_SECTION_RO; + + //By Default section is Active + psFlash2xBitMap->VSA1 |= FLASH2X_SECTION_ACT ; + + } + + + /// + // VSA 2 + /// + + if((psFlash2xCSInfo->OffsetFromZeroForVSA2Start) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->VSA2= psFlash2xBitMap->VSA2 | FLASH2X_SECTION_PRESENT; + + + //Setting the Access Bit. Map is not defined hece setting it always valid + psFlash2xBitMap->VSA2 |= FLASH2X_SECTION_VALID; + + //Checking For Access permission + if(IsSectionWritable(Adapter, VSA2) == FALSE) + psFlash2xBitMap->VSA2 |= FLASH2X_SECTION_RO; + + //By Default section is Active + psFlash2xBitMap->VSA2 |= FLASH2X_SECTION_ACT ; + } + + /// + // SCSI Section + /// + if((psFlash2xCSInfo->OffsetFromZeroForScsiFirmware) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->SCSI= psFlash2xBitMap->SCSI | FLASH2X_SECTION_PRESENT; + + + //Setting the Access Bit. Map is not defined hece setting it always valid + psFlash2xBitMap->SCSI|= FLASH2X_SECTION_VALID; + + //Checking For Access permission + if(IsSectionWritable(Adapter, SCSI) == FALSE) + psFlash2xBitMap->SCSI |= FLASH2X_SECTION_RO; + + //By Default section is Active + psFlash2xBitMap->SCSI |= FLASH2X_SECTION_ACT ; + + } + + + /// + // Control Section + /// + if((psFlash2xCSInfo->OffsetFromZeroForControlSectionStart) != UNINIT_PTR_IN_CS) + { + //Setting the 0th Bit representing the Section is present or not. + psFlash2xBitMap->CONTROL_SECTION = psFlash2xBitMap->CONTROL_SECTION | (FLASH2X_SECTION_PRESENT); + + + //Setting the Access Bit. Map is not defined hece setting it always valid + psFlash2xBitMap->CONTROL_SECTION |= FLASH2X_SECTION_VALID; + + //Checking For Access permission + if(IsSectionWritable(Adapter, CONTROL_SECTION) == FALSE) + psFlash2xBitMap->CONTROL_SECTION |= FLASH2X_SECTION_RO; + + //By Default section is Active + psFlash2xBitMap->CONTROL_SECTION |= FLASH2X_SECTION_ACT ; + + } + + /// + // For Reserved Sections + /// + psFlash2xBitMap->Reserved0 = 0; + psFlash2xBitMap->Reserved0 = 0; + psFlash2xBitMap->Reserved0 = 0; + + BcmDumpFlash2xSectionBitMap(psFlash2xBitMap); + + return STATUS_SUCCESS ; + +} +/** +BcmSetActiveSection :- Set Active section is used to make priority field highest over other + section of same type. + +@Adapater :- Bcm Driver Private Data Structure +@eFlash2xSectionVal :- Flash section val whose priority has to be made highest. + +Return Value:- Make the priorit highest else return erorr code + +**/ +INT BcmSetActiveSection(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlash2xSectVal) +{ + unsigned int SectImagePriority = 0; + INT Status =STATUS_SUCCESS; + + //DSD_HEADER sDSD = {0}; + //ISO_HEADER sISO = {0}; + INT HighestPriDSD = 0 ; + INT HighestPriISO = 0; + + + + Status = IsSectionWritable(Adapter,eFlash2xSectVal) ; + if(Status != TRUE ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Provided Section <%d> is not writable",eFlash2xSectVal); + return STATUS_FAILURE; + } + + Adapter->bHeaderChangeAllowed = TRUE ; + switch(eFlash2xSectVal) + { + case ISO_IMAGE1 : + case ISO_IMAGE2 : + if(ReadISOSignature(Adapter,eFlash2xSectVal)== ISO_IMAGE_MAGIC_NUMBER ) + { + HighestPriISO = getHighestPriISO(Adapter); + + if(HighestPriISO == eFlash2xSectVal ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Given ISO<%x> already has highest priority",eFlash2xSectVal ); + Status = STATUS_SUCCESS ; + break; + } + + SectImagePriority = ReadISOPriority(Adapter, HighestPriISO) + 1; + + if((SectImagePriority <= 0) && IsSectionWritable(Adapter,HighestPriISO)) + { + // This is a SPECIAL Case which will only happen if the current highest priority ISO has priority value = 0x7FFFFFFF. + // We will write 1 to the current Highest priority ISO And then shall increase the priority of the requested ISO + // by user + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "SectImagePriority wraparound happened, eFlash2xSectVal: 0x%x\n",eFlash2xSectVal); + SectImagePriority = htonl(0x1); + Status = BcmFlash2xBulkWrite(Adapter, + &SectImagePriority, + HighestPriISO, + 0 + FIELD_OFFSET_IN_HEADER(PISO_HEADER, ISOImagePriority), + SIGNATURE_SIZE, + TRUE); + + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Priority has not been written properly"); + Status = STATUS_FAILURE; + break ; + } + + HighestPriISO = getHighestPriISO(Adapter); + + if(HighestPriISO == eFlash2xSectVal ) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Given ISO<%x> already has highest priority",eFlash2xSectVal ); + Status = STATUS_SUCCESS ; + break; + } + + SectImagePriority = 2; + } + + + SectImagePriority = htonl(SectImagePriority); + + Status = BcmFlash2xBulkWrite(Adapter, + &SectImagePriority, + eFlash2xSectVal, + 0 + FIELD_OFFSET_IN_HEADER(PISO_HEADER, ISOImagePriority), + SIGNATURE_SIZE, + TRUE); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Priority has not been written properly"); + break ; + } + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Signature is currupted. Hence can't increase the priority"); + Status = STATUS_FAILURE ; + break; + } + break; + case DSD0 : + case DSD1 : + case DSD2 : + if(ReadDSDSignature(Adapter,eFlash2xSectVal)== DSD_IMAGE_MAGIC_NUMBER) + { + HighestPriDSD = getHighestPriDSD(Adapter); + + if((HighestPriDSD == eFlash2xSectVal)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Given DSD<%x> already has highest priority", eFlash2xSectVal); + Status = STATUS_SUCCESS ; + break; + } + + SectImagePriority = ReadDSDPriority(Adapter, HighestPriDSD) + 1 ; + if(SectImagePriority <= 0) + { + // This is a SPECIAL Case which will only happen if the current highest priority DSD has priority value = 0x7FFFFFFF. + // We will write 1 to the current Highest priority DSD And then shall increase the priority of the requested DSD + // by user + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, NVM_RW, DBG_LVL_ALL, "SectImagePriority wraparound happened, eFlash2xSectVal: 0x%x\n",eFlash2xSectVal); + SectImagePriority = htonl(0x1); + + Status = BcmFlash2xBulkWrite(Adapter, + &SectImagePriority, + HighestPriDSD, + Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + FIELD_OFFSET_IN_HEADER(PDSD_HEADER, DSDImagePriority), + SIGNATURE_SIZE, + TRUE); + + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Priority has not been written properly"); + break ; + } + + HighestPriDSD = getHighestPriDSD(Adapter); + + if((HighestPriDSD == eFlash2xSectVal)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Made the DSD: %x highest by reducing priority of other\n", eFlash2xSectVal); + Status = STATUS_SUCCESS ; + break; + } + + SectImagePriority = htonl(0x2); + Status = BcmFlash2xBulkWrite(Adapter, + &SectImagePriority, + HighestPriDSD, + Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + FIELD_OFFSET_IN_HEADER(PDSD_HEADER, DSDImagePriority), + SIGNATURE_SIZE, + TRUE); + + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Priority has not been written properly"); + break ; + } + + HighestPriDSD = getHighestPriDSD(Adapter); + + if((HighestPriDSD == eFlash2xSectVal)) + { + Status = STATUS_SUCCESS ; + break; + } + SectImagePriority = 3 ; + + } + SectImagePriority = htonl(SectImagePriority); + Status = BcmFlash2xBulkWrite(Adapter, + &SectImagePriority, + eFlash2xSectVal, + Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + FIELD_OFFSET_IN_HEADER(PDSD_HEADER, DSDImagePriority), + SIGNATURE_SIZE , + TRUE); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Priority has not been written properly"); + Status = STATUS_FAILURE ; + break ; + } + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Signature is currupted. Hence can't increase the priority"); + Status = STATUS_FAILURE ; + break; + } + break; + case VSA0 : + case VSA1 : + case VSA2 : + //Has to be decided + break ; + default : + Status = STATUS_FAILURE ; + break; + + } + + Adapter->bHeaderChangeAllowed = FALSE ; + return Status; + +} + +/** +BcmCopyISO - Used only for copying the ISO section +@Adapater :- Bcm Driver Private Data Structure +@sCopySectStrut :- Section copy structure + +Return value:- SUCCESS if copies successfully else negative error code + +**/ +INT BcmCopyISO(PMINI_ADAPTER Adapter, FLASH2X_COPY_SECTION sCopySectStrut) +{ + + PCHAR Buff = NULL; + FLASH2X_SECTION_VAL eISOReadPart = 0,eISOWritePart = 0; + UINT uiReadOffsetWithinPart = 0, uiWriteOffsetWithinPart = 0; + UINT uiTotalDataToCopy = 0; + BOOLEAN IsThisHeaderSector = FALSE ; + UINT sigOffset = 0; + UINT ISOLength = 0; + UINT Status = STATUS_SUCCESS; + UINT SigBuff[MAX_RW_SIZE]; + UINT i = 0; + + if(ReadISOSignature(Adapter,sCopySectStrut.SrcSection) != ISO_IMAGE_MAGIC_NUMBER) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "error as Source ISO Section does not have valid signature"); + return STATUS_FAILURE; + } + + Status = BcmFlash2xBulkRead(Adapter, + &ISOLength, + sCopySectStrut.SrcSection, + 0 + FIELD_OFFSET_IN_HEADER(PISO_HEADER,ISOImageSize), + 4); + + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Read failed while copying ISO\n"); + return Status; + } + + ISOLength = htonl(ISOLength); + + if(ISOLength % Adapter->uiSectorSize) + { + ISOLength = Adapter->uiSectorSize*(1 + ISOLength/Adapter->uiSectorSize); + } + + sigOffset = FIELD_OFFSET_IN_HEADER(PISO_HEADER, ISOImageMagicNumber); + + Buff = kzalloc(Adapter->uiSectorSize, GFP_KERNEL); + + if(Buff == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Memory allocation failed for section size"); + return -ENOMEM; + } + + if(sCopySectStrut.SrcSection ==ISO_IMAGE1 && sCopySectStrut.DstSection ==ISO_IMAGE2) + { + eISOReadPart = ISO_IMAGE1 ; + eISOWritePart = ISO_IMAGE2 ; + uiReadOffsetWithinPart = 0; + uiWriteOffsetWithinPart = 0 ; + + uiTotalDataToCopy =(Adapter->psFlash2xCSInfo->OffsetISOImage1Part1End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part1Start)+ + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part2End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part2Start)+ + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part3End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part3Start); + + if(uiTotalDataToCopy < ISOLength) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"error as Source ISO Section does not have valid signature"); + Status = STATUS_FAILURE; + goto out; + } + + uiTotalDataToCopy =(Adapter->psFlash2xCSInfo->OffsetISOImage2Part1End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part1Start)+ + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part2End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part2Start)+ + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part3End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part3Start); + + if(uiTotalDataToCopy < ISOLength) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"error as Dest ISO Section does not have enough section size"); + Status = STATUS_FAILURE; + goto out; + } + + uiTotalDataToCopy = ISOLength; + + CorruptISOSig(Adapter,ISO_IMAGE2); + + while(uiTotalDataToCopy) + { + if(uiTotalDataToCopy == Adapter->uiSectorSize) + { + //Setting for write of first sector. First sector is assumed to be written in last + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Writing the signature sector"); + eISOReadPart = ISO_IMAGE1 ; + uiReadOffsetWithinPart = 0; + eISOWritePart = ISO_IMAGE2; + uiWriteOffsetWithinPart = 0 ; + IsThisHeaderSector = TRUE ; + + } + else + { + uiReadOffsetWithinPart = uiReadOffsetWithinPart + Adapter->uiSectorSize ; + uiWriteOffsetWithinPart = uiWriteOffsetWithinPart + Adapter->uiSectorSize ; + + if((eISOReadPart == ISO_IMAGE1) && (uiReadOffsetWithinPart == (Adapter->psFlash2xCSInfo->OffsetISOImage1Part1End - Adapter->psFlash2xCSInfo->OffsetISOImage1Part1Start) )) + { + eISOReadPart = ISO_IMAGE1_PART2 ; + uiReadOffsetWithinPart = 0; + } + if((eISOReadPart == ISO_IMAGE1_PART2) && (uiReadOffsetWithinPart == (Adapter->psFlash2xCSInfo->OffsetISOImage1Part2End - Adapter->psFlash2xCSInfo->OffsetISOImage1Part2Start))) + { + eISOReadPart = ISO_IMAGE1_PART3 ; + uiReadOffsetWithinPart = 0; + } + if((eISOWritePart == ISO_IMAGE2) && (uiWriteOffsetWithinPart == (Adapter->psFlash2xCSInfo->OffsetISOImage2Part1End - Adapter->psFlash2xCSInfo->OffsetISOImage2Part1Start))) + { + eISOWritePart = ISO_IMAGE2_PART2 ; + uiWriteOffsetWithinPart = 0; + } + if((eISOWritePart == ISO_IMAGE2_PART2) && (uiWriteOffsetWithinPart == (Adapter->psFlash2xCSInfo->OffsetISOImage2Part2End - Adapter->psFlash2xCSInfo->OffsetISOImage2Part2Start))) + { + eISOWritePart = ISO_IMAGE2_PART3 ; + uiWriteOffsetWithinPart = 0; + } + } + + Status = BcmFlash2xBulkRead(Adapter, + (PUINT)Buff, + eISOReadPart, + uiReadOffsetWithinPart, + Adapter->uiSectorSize + ); + + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Read failed while copying ISO: Part: %x, OffsetWithinPart: %x\n", eISOReadPart, uiReadOffsetWithinPart); + break; + } + + if(IsThisHeaderSector == TRUE) + { + //If this is header sector write 0xFFFFFFFF at the sig time and in last write sig + memcpy(SigBuff, Buff + sigOffset, MAX_RW_SIZE); + + for(i = 0; i < MAX_RW_SIZE;i++) + *(Buff + sigOffset + i) = 0xFF; + } + Adapter->bHeaderChangeAllowed = TRUE ; + + Status = BcmFlash2xBulkWrite(Adapter, + (PUINT)Buff, + eISOWritePart, + uiWriteOffsetWithinPart, + Adapter->uiSectorSize, + TRUE); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Write failed while copying ISO: Part: %x, OffsetWithinPart: %x\n", eISOWritePart, uiWriteOffsetWithinPart); + break; + } + + Adapter->bHeaderChangeAllowed = FALSE; + + if(IsThisHeaderSector == TRUE) + { + WriteToFlashWithoutSectorErase(Adapter, + SigBuff, + eISOWritePart, + sigOffset, + MAX_RW_SIZE); + IsThisHeaderSector = FALSE ; + } + //subtracting the written Data + uiTotalDataToCopy = uiTotalDataToCopy - Adapter->uiSectorSize ; + } + + + } + + if(sCopySectStrut.SrcSection ==ISO_IMAGE2 && sCopySectStrut.DstSection ==ISO_IMAGE1) + { + eISOReadPart = ISO_IMAGE2 ; + eISOWritePart = ISO_IMAGE1 ; + uiReadOffsetWithinPart = 0; + uiWriteOffsetWithinPart = 0 ; + + uiTotalDataToCopy =(Adapter->psFlash2xCSInfo->OffsetISOImage2Part1End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part1Start)+ + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part2End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part2Start)+ + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part3End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage2Part3Start); + + if(uiTotalDataToCopy < ISOLength) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"error as Source ISO Section does not have valid signature"); + Status = STATUS_FAILURE; + goto out; + } + + uiTotalDataToCopy =(Adapter->psFlash2xCSInfo->OffsetISOImage1Part1End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part1Start)+ + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part2End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part2Start)+ + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part3End) - + (Adapter->psFlash2xCSInfo->OffsetISOImage1Part3Start); + + if(uiTotalDataToCopy < ISOLength) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"error as Dest ISO Section does not have enough section size"); + Status = STATUS_FAILURE; + goto out; + } + + uiTotalDataToCopy = ISOLength; + + CorruptISOSig(Adapter,ISO_IMAGE1); + + while(uiTotalDataToCopy) + { + if(uiTotalDataToCopy == Adapter->uiSectorSize) + { + //Setting for write of first sector. First sector is assumed to be written in last + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Writing the signature sector"); + eISOReadPart = ISO_IMAGE2 ; + uiReadOffsetWithinPart = 0; + eISOWritePart = ISO_IMAGE1; + uiWriteOffsetWithinPart = 0 ; + IsThisHeaderSector = TRUE; + + } + else + { + uiReadOffsetWithinPart = uiReadOffsetWithinPart + Adapter->uiSectorSize ; + uiWriteOffsetWithinPart = uiWriteOffsetWithinPart + Adapter->uiSectorSize ; + + if((eISOReadPart == ISO_IMAGE2) && (uiReadOffsetWithinPart == (Adapter->psFlash2xCSInfo->OffsetISOImage2Part1End - Adapter->psFlash2xCSInfo->OffsetISOImage2Part1Start) )) + { + eISOReadPart = ISO_IMAGE2_PART2 ; + uiReadOffsetWithinPart = 0; + } + if((eISOReadPart == ISO_IMAGE2_PART2) && (uiReadOffsetWithinPart == (Adapter->psFlash2xCSInfo->OffsetISOImage2Part2End - Adapter->psFlash2xCSInfo->OffsetISOImage2Part2Start))) + { + eISOReadPart = ISO_IMAGE2_PART3 ; + uiReadOffsetWithinPart = 0; + } + if((eISOWritePart == ISO_IMAGE1) && (uiWriteOffsetWithinPart == (Adapter->psFlash2xCSInfo->OffsetISOImage1Part1End - Adapter->psFlash2xCSInfo->OffsetISOImage1Part1Start))) + { + eISOWritePart = ISO_IMAGE1_PART2 ; + uiWriteOffsetWithinPart = 0; + } + if((eISOWritePart == ISO_IMAGE1_PART2) && (uiWriteOffsetWithinPart == (Adapter->psFlash2xCSInfo->OffsetISOImage1Part2End - Adapter->psFlash2xCSInfo->OffsetISOImage1Part2Start))) + { + eISOWritePart = ISO_IMAGE1_PART3 ; + uiWriteOffsetWithinPart = 0; + } + } + + Status = BcmFlash2xBulkRead(Adapter, + (PUINT)Buff, + eISOReadPart, + uiReadOffsetWithinPart, + Adapter->uiSectorSize + ); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Read failed while copying ISO: Part: %x, OffsetWithinPart: %x\n", eISOReadPart, uiReadOffsetWithinPart); + break; + } + + if(IsThisHeaderSector == TRUE) + { + //If this is header sector write 0xFFFFFFFF at the sig time and in last write sig + memcpy(SigBuff, Buff + sigOffset, MAX_RW_SIZE); + + for(i = 0; i < MAX_RW_SIZE;i++) + *(Buff + sigOffset + i) = 0xFF; + + } + Adapter->bHeaderChangeAllowed = TRUE ; + Status = BcmFlash2xBulkWrite(Adapter, + (PUINT)Buff, + eISOWritePart, + uiWriteOffsetWithinPart, + Adapter->uiSectorSize, + TRUE); + + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Write failed while copying ISO: Part: %x, OffsetWithinPart: %x\n", eISOWritePart, uiWriteOffsetWithinPart); + break; + } + + Adapter->bHeaderChangeAllowed = FALSE ; + + if(IsThisHeaderSector == TRUE) + { + WriteToFlashWithoutSectorErase(Adapter, + SigBuff, + eISOWritePart, + sigOffset, + MAX_RW_SIZE); + IsThisHeaderSector = FALSE ; + } + + //subtracting the written Data + uiTotalDataToCopy = uiTotalDataToCopy - Adapter->uiSectorSize ; + } + + + } + +out: + kfree(Buff); + + return Status; +} +/** +BcmFlash2xCorruptSig : this API is used to corrupt the written sig in Bcm Header present in flash section. + It will corrupt the sig, if Section is writable, by making first bytes as zero. +@Adapater :- Bcm Driver Private Data Structure +@eFlash2xSectionVal :- Flash section val which has header + +Return Value :- + Success :- If Section is present and writable, corrupt the sig and return STATUS_SUCCESS + Failure :-Return negative error code + + +**/ +INT BcmFlash2xCorruptSig(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlash2xSectionVal) +{ + + INT Status = STATUS_SUCCESS ; + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Section Value :%x \n", eFlash2xSectionVal); + + if((eFlash2xSectionVal == DSD0) || (eFlash2xSectionVal == DSD1) || (eFlash2xSectionVal == DSD2)) + { + Status = CorruptDSDSig(Adapter, eFlash2xSectionVal); + } + else if(eFlash2xSectionVal == ISO_IMAGE1 || eFlash2xSectionVal == ISO_IMAGE2) + { + Status = CorruptISOSig(Adapter, eFlash2xSectionVal); + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Given Section <%d>does not have Header",eFlash2xSectionVal); + return STATUS_SUCCESS; + } + return Status; +} +/** +BcmFlash2xWriteSig :-this API is used to Write the sig if requested Section has + header and Write Permission. +@Adapater :- Bcm Driver Private Data Structure +@eFlashSectionVal :- Flash section val which has header + +Return Value :- + Success :- If Section is present and writable write the sig and return STATUS_SUCCESS + Failure :-Return negative error code + +**/ +INT BcmFlash2xWriteSig(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlashSectionVal) +{ + + UINT uiSignature = 0 ; + UINT uiOffset = 0; + //DSD_HEADER dsdHeader = {0}; + + if(Adapter->bSigCorrupted == FALSE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Signature is not corrupted by driver, hence not restoring\n"); + return STATUS_SUCCESS; + } + if(Adapter->bAllDSDWriteAllow == FALSE) + { + if(IsSectionWritable(Adapter,eFlashSectionVal) == FALSE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Section is not Writable...Hence can't Write signature"); + return SECTOR_IS_NOT_WRITABLE; + } + } + if((eFlashSectionVal == DSD0) ||(eFlashSectionVal == DSD1) || (eFlashSectionVal == DSD2)) + { + uiSignature = htonl(DSD_IMAGE_MAGIC_NUMBER) ; + uiOffset = Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader ; + + uiOffset += FIELD_OFFSET_IN_HEADER(PDSD_HEADER,DSDImageMagicNumber); + + if((ReadDSDSignature(Adapter,eFlashSectionVal) & 0xFF000000) != CORRUPTED_PATTERN) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Corrupted Pattern is not there. Hence won't write sig"); + return STATUS_FAILURE; + } + + } + else if((eFlashSectionVal == ISO_IMAGE1) || (eFlashSectionVal == ISO_IMAGE2)) + { + uiSignature = htonl(ISO_IMAGE_MAGIC_NUMBER); + //uiOffset = 0; + uiOffset = FIELD_OFFSET_IN_HEADER(PISO_HEADER,ISOImageMagicNumber); + if((ReadISOSignature(Adapter,eFlashSectionVal) & 0xFF000000) != CORRUPTED_PATTERN) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Currupted Pattern is not there. Hence won't write sig"); + return STATUS_FAILURE; + } + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"GIVEN SECTION< %d > IS NOT VALID FOR SIG WRITE...", eFlashSectionVal); + return STATUS_FAILURE; + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Restoring the signature"); + + + Adapter->bHeaderChangeAllowed = TRUE; + Adapter->bSigCorrupted = FALSE; + BcmFlash2xBulkWrite(Adapter, &uiSignature,eFlashSectionVal,uiOffset,SIGNATURE_SIZE,TRUE); + Adapter->bHeaderChangeAllowed = FALSE; + + + + return STATUS_SUCCESS; +} +/** +validateFlash2xReadWrite :- This API is used to validate the user request for Read/Write. + if requested Bytes goes beyond the Requested section, it reports error. +@Adapater :- Bcm Driver Private Data Structure +@psFlash2xReadWrite :-Flash2x Read/write structure pointer + +Return values:-Return TRUE is request is valid else FALSE. + + +**/ +INT validateFlash2xReadWrite(PMINI_ADAPTER Adapter, PFLASH2X_READWRITE psFlash2xReadWrite) +{ + UINT uiNumOfBytes = 0 ; + UINT uiSectStartOffset = 0 ; + UINT uiSectEndOffset = 0; + uiNumOfBytes = psFlash2xReadWrite->numOfBytes; + + if(IsSectionExistInFlash(Adapter,psFlash2xReadWrite->Section) != TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Section<%x> does not exixt in Flash",psFlash2xReadWrite->Section); + return FALSE; + } + uiSectStartOffset = BcmGetSectionValStartOffset(Adapter,psFlash2xReadWrite->Section); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Start offset :%x ,section :%d\n",uiSectStartOffset,psFlash2xReadWrite->Section); + if((psFlash2xReadWrite->Section == ISO_IMAGE1) ||(psFlash2xReadWrite->Section == ISO_IMAGE2)) + { + if(psFlash2xReadWrite->Section == ISO_IMAGE1) + { + uiSectEndOffset = BcmGetSectionValEndOffset(Adapter,ISO_IMAGE1) - + BcmGetSectionValStartOffset(Adapter,ISO_IMAGE1)+ + BcmGetSectionValEndOffset(Adapter,ISO_IMAGE1_PART2) - + BcmGetSectionValStartOffset(Adapter,ISO_IMAGE1_PART2)+ + BcmGetSectionValEndOffset(Adapter,ISO_IMAGE1_PART3) - + BcmGetSectionValStartOffset(Adapter,ISO_IMAGE1_PART3); + } + else if(psFlash2xReadWrite->Section == ISO_IMAGE2) + { + uiSectEndOffset = BcmGetSectionValEndOffset(Adapter,ISO_IMAGE2) - + BcmGetSectionValStartOffset(Adapter,ISO_IMAGE2)+ + BcmGetSectionValEndOffset(Adapter,ISO_IMAGE2_PART2) - + BcmGetSectionValStartOffset(Adapter,ISO_IMAGE2_PART2)+ + BcmGetSectionValEndOffset(Adapter,ISO_IMAGE2_PART3) - + BcmGetSectionValStartOffset(Adapter,ISO_IMAGE2_PART3); + + } + + //since this uiSectEndoffset is the size of iso Image. hence for calculating the vitual endoffset + //it should be added in startoffset. so that check done in last of this function can be valued. + uiSectEndOffset = uiSectStartOffset + uiSectEndOffset ; + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Total size of the ISO Image :%x",uiSectEndOffset); + } + else + uiSectEndOffset = BcmGetSectionValEndOffset(Adapter,psFlash2xReadWrite->Section); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "End offset :%x \n",uiSectEndOffset); + + //Checking the boundary condition + if((uiSectStartOffset + psFlash2xReadWrite->offset + uiNumOfBytes) <= uiSectEndOffset) + return TRUE; + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Invalid Request...."); + return FALSE; + } + +} + +/** +IsFlash2x :- check for Flash 2.x +@Adapater :- Bcm Driver Private Data Structure + +Return value:- + return TRUE if flah2.x of hgher version else return false. +**/ + +INT IsFlash2x(PMINI_ADAPTER Adapter) +{ + if(Adapter->uiFlashLayoutMajorVersion >= FLASH_2X_MAJOR_NUMBER) + return TRUE ; + else + return FALSE; +} +/** +GetFlashBaseAddr :- Calculate the Flash Base address +@Adapater :- Bcm Driver Private Data Structure + +Return Value:- + Success :- Base Address of the Flash +**/ + +static INT GetFlashBaseAddr(PMINI_ADAPTER Adapter) +{ + + UINT uiBaseAddr = 0; + + if(Adapter->bDDRInitDone) + { + /* + For All Valid Flash Versions... except 1.1, take the value from FlashBaseAddr + In case of Raw Read... use the default value + */ + if(Adapter->uiFlashLayoutMajorVersion && (Adapter->bFlashRawRead == FALSE) && + !((Adapter->uiFlashLayoutMajorVersion == 1) && (Adapter->uiFlashLayoutMinorVersion == 1)) + ) + uiBaseAddr = Adapter->uiFlashBaseAdd ; + else + uiBaseAddr = FLASH_CONTIGIOUS_START_ADDR_AFTER_INIT; + } + else + { + /* + For All Valid Flash Versions... except 1.1, take the value from FlashBaseAddr + In case of Raw Read... use the default value + */ + if(Adapter->uiFlashLayoutMajorVersion && (Adapter->bFlashRawRead == FALSE) && + !((Adapter->uiFlashLayoutMajorVersion == 1) && (Adapter->uiFlashLayoutMinorVersion == 1)) + ) + uiBaseAddr = Adapter->uiFlashBaseAdd | FLASH_CONTIGIOUS_START_ADDR_BEFORE_INIT; + else + uiBaseAddr = FLASH_CONTIGIOUS_START_ADDR_BEFORE_INIT; + } + + return uiBaseAddr ; +} +/** +BcmCopySection :- This API is used to copy the One section in another. Both section should + be contiuous and of same size. Hence this Will not be applicabe to copy ISO. + +@Adapater :- Bcm Driver Private Data Structure +@SrcSection :- Source section From where data has to be copied +@DstSection :- Destination section to which data has to be copied +@offset :- Offset from/to where data has to be copied from one section to another. +@numOfBytes :- number of byes that has to be copyed from one section to another at given offset. + in case of numofBytes equal zero complete section will be copied. + +Return Values- + Success : Return STATUS_SUCCESS + Faillure :- return negative error code + +**/ + +INT BcmCopySection(PMINI_ADAPTER Adapter, + FLASH2X_SECTION_VAL SrcSection, + FLASH2X_SECTION_VAL DstSection, + UINT offset, + UINT numOfBytes) +{ + UINT BuffSize = 0 ; + UINT BytesToBeCopied = 0; + PUCHAR pBuff = NULL ; + INT Status = STATUS_SUCCESS ; + if(SrcSection == DstSection) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Source and Destination should be different ...try again"); + return -EINVAL; + } + if((SrcSection != DSD0) && (SrcSection != DSD1) && (SrcSection != DSD2)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Source should be DSD subsection"); + return -EINVAL; + } + if((DstSection != DSD0) && (DstSection != DSD1) && (DstSection != DSD2)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Destination should be DSD subsection"); + return -EINVAL; + } + + //if offset zero means have to copy complete secton + + if(numOfBytes == 0) + { + numOfBytes = BcmGetSectionValEndOffset(Adapter,SrcSection) + - BcmGetSectionValStartOffset(Adapter,SrcSection); + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL," Section Size :0x%x",numOfBytes); + } + + if((offset + numOfBytes) > BcmGetSectionValEndOffset(Adapter,SrcSection) + - BcmGetSectionValStartOffset(Adapter,SrcSection)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0," Input parameters going beyond the section offS: %x numB: %x of Source Section\n", + offset, numOfBytes); + return -EINVAL; + } + + if((offset + numOfBytes) > BcmGetSectionValEndOffset(Adapter,DstSection) + - BcmGetSectionValStartOffset(Adapter,DstSection)) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0," Input parameters going beyond the section offS: %x numB: %x of Destination Section\n", + offset, numOfBytes); + return -EINVAL; + } + + + if(numOfBytes > Adapter->uiSectorSize ) + BuffSize = Adapter->uiSectorSize; + else + BuffSize = numOfBytes ; + + pBuff = (PCHAR)kzalloc(BuffSize, GFP_KERNEL); + if(pBuff == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Memory allocation failed.. "); + return -ENOMEM; + } + + + BytesToBeCopied = Adapter->uiSectorSize ; + if(offset % Adapter->uiSectorSize) + BytesToBeCopied = Adapter->uiSectorSize - (offset % Adapter->uiSectorSize); + if(BytesToBeCopied > numOfBytes) + BytesToBeCopied = numOfBytes ; + + + + Adapter->bHeaderChangeAllowed = TRUE; + + do + { + Status = BcmFlash2xBulkRead(Adapter, (PUINT)pBuff, SrcSection , offset,BytesToBeCopied); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Read failed at offset :%d for NOB :%d", SrcSection,BytesToBeCopied); + break; + } + Status = BcmFlash2xBulkWrite(Adapter,(PUINT)pBuff,DstSection,offset,BytesToBeCopied,FALSE); + if(Status) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Write failed at offset :%d for NOB :%d", DstSection,BytesToBeCopied); + break; + } + offset = offset + BytesToBeCopied; + numOfBytes = numOfBytes - BytesToBeCopied ; + if(numOfBytes) + { + if(numOfBytes > Adapter->uiSectorSize ) + BytesToBeCopied = Adapter->uiSectorSize; + else + BytesToBeCopied = numOfBytes; + } + }while(numOfBytes > 0) ; + kfree(pBuff); + Adapter->bHeaderChangeAllowed = FALSE ; + return Status; +} + +/** +SaveHeaderIfPresent :- This API is use to Protect the Header in case of Header Sector write +@Adapater :- Bcm Driver Private Data Structure +@pBuff :- Data buffer that has to be written in sector having the header map. +@uiOffset :- Flash offset that has to be written. + +Return value :- + Success :- On success return STATUS_SUCCESS + Faillure :- Return negative error code + +**/ + +INT SaveHeaderIfPresent(PMINI_ADAPTER Adapter, PUCHAR pBuff, UINT uiOffset) +{ + UINT offsetToProtect = 0,HeaderSizeToProtect =0; + BOOLEAN bHasHeader = FALSE ; + PUCHAR pTempBuff =NULL; + UINT uiSectAlignAddr = 0; + UINT sig = 0; + + //making the offset sector aligned + uiSectAlignAddr = uiOffset & ~(Adapter->uiSectorSize - 1); + + + if((uiSectAlignAddr == BcmGetSectionValEndOffset(Adapter,DSD2)- Adapter->uiSectorSize)|| + (uiSectAlignAddr == BcmGetSectionValEndOffset(Adapter,DSD1)- Adapter->uiSectorSize)|| + (uiSectAlignAddr == BcmGetSectionValEndOffset(Adapter,DSD0)- Adapter->uiSectorSize)) + { + + //offset from the sector boundary having the header map + offsetToProtect = Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader % Adapter->uiSectorSize; + HeaderSizeToProtect = sizeof(DSD_HEADER); + bHasHeader = TRUE ; + } + + if(uiSectAlignAddr == BcmGetSectionValStartOffset(Adapter,ISO_IMAGE1) || + uiSectAlignAddr == BcmGetSectionValStartOffset(Adapter,ISO_IMAGE2)) + { + offsetToProtect = 0; + HeaderSizeToProtect = sizeof(ISO_HEADER); + bHasHeader = TRUE; + } + //If Header is present overwrite passed buffer with this + if(bHasHeader && (Adapter->bHeaderChangeAllowed == FALSE)) + { + pTempBuff = (PUCHAR)kzalloc(HeaderSizeToProtect, GFP_KERNEL); + if(pTempBuff == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Memory allocation failed "); + return -ENOMEM; + } + //Read header + BeceemFlashBulkRead(Adapter,(PUINT)pTempBuff,(uiSectAlignAddr + offsetToProtect),HeaderSizeToProtect); + BCM_DEBUG_PRINT_BUFFER(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,pTempBuff ,HeaderSizeToProtect); + //Replace Buffer content with Header + memcpy(pBuff +offsetToProtect,pTempBuff,HeaderSizeToProtect); + + kfree(pTempBuff); + } + if(bHasHeader && Adapter->bSigCorrupted) + { + sig = *((PUINT)(pBuff + offsetToProtect + FIELD_OFFSET_IN_HEADER(PDSD_HEADER,DSDImageMagicNumber))); + sig = ntohl(sig); + if((sig & 0xFF000000) != CORRUPTED_PATTERN) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Desired pattern is not at sig offset. Hence won't restore"); + Adapter->bSigCorrupted = FALSE; + return STATUS_SUCCESS; + } + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL," Corrupted sig is :%X", sig); + *((PUINT)(pBuff + offsetToProtect + FIELD_OFFSET_IN_HEADER(PDSD_HEADER,DSDImageMagicNumber)))= htonl(DSD_IMAGE_MAGIC_NUMBER); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Restoring the signature in Header Write only"); + Adapter->bSigCorrupted = FALSE; + } + + return STATUS_SUCCESS ; +} + +/** +BcmDoChipSelect : This will selcet the appropriate chip for writing. +@Adapater :- Bcm Driver Private Data Structure + +OutPut:- + Select the Appropriate chip and retrn status Success +**/ +static INT BcmDoChipSelect(PMINI_ADAPTER Adapter, UINT offset) +{ + UINT FlashConfig = 0; + INT ChipNum = 0; + UINT GPIOConfig = 0; + UINT PartNum = 0; + + ChipNum = offset / FLASH_PART_SIZE ; + + // + // Chip Select mapping to enable flash0. + // To select flash 0, we have to OR with (0<<12). + // ORing 0 will have no impact so not doing that part. + // In future if Chip select value changes from 0 to non zero, + // That needs be taken care with backward comaptibility. No worries for now. + // + + /* + SelectedChip Variable is the selection that the host is 100% Sure the same as what the register will hold. This can be ONLY ensured + if the Chip doesn't goes to low power mode while the flash operation is in progress (NVMRdmWrmLock is taken) + Before every new Flash Write operation, we reset the variable. This is to ensure that after any wake-up from + power down modes (Idle mode/shutdown mode), the values in the register will be different. + */ + + if(Adapter->SelectedChip == ChipNum) + return STATUS_SUCCESS; + + //BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL, "Selected Chip :%x", ChipNum); + Adapter->SelectedChip = ChipNum ; + + //bit[13..12] will select the appropriate chip + rdmalt(Adapter, FLASH_CONFIG_REG, &FlashConfig, 4); + rdmalt(Adapter, FLASH_GPIO_CONFIG_REG, &GPIOConfig, 4); + + { + switch(ChipNum) + { + case 0: + PartNum = 0; + break; + case 1: + PartNum = 3; + GPIOConfig |= (0x4 << CHIP_SELECT_BIT12); + break; + case 2: + PartNum = 1; + GPIOConfig |= (0x1 << CHIP_SELECT_BIT12); + break; + case 3: + PartNum = 2; + GPIOConfig |= (0x2 << CHIP_SELECT_BIT12); + break; + } + } + /* In case the bits already written in the FLASH_CONFIG_REG is same as what the user desired, + nothing to do... can return immediately. + ASSUMPTION: FLASH_GPIO_CONFIG_REG will be in sync with FLASH_CONFIG_REG. + Even if the chip goes to low power mode, it should wake with values in each register in sync with each other. + These values are not written by host other than during CHIP_SELECT. + */ + if(PartNum == ((FlashConfig >> CHIP_SELECT_BIT12) & 0x3)) + return STATUS_SUCCESS; + + //clearing the bit[13..12] + FlashConfig &= 0xFFFFCFFF; + FlashConfig = (FlashConfig | (PartNum<psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + FIELD_OFFSET_IN_HEADER(PDSD_HEADER,DSDImageMagicNumber), + SIGNATURE_SIZE); + + uiDSDsig = ntohl(uiDSDsig); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"DSD SIG :%x", uiDSDsig); + + return uiDSDsig ; +} +INT ReadDSDPriority(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL dsd) +{ + //UINT priOffsetInMap = 0 ; + unsigned int uiDSDPri = STATUS_FAILURE; + //DSD_HEADER dsdHeader = {0}; + //priOffsetInMap = (PUCHAR)&(dsdHeader.DSDImagePriority) -(PUCHAR)&dsdHeader; + if(IsSectionWritable(Adapter,dsd)) + { + if(ReadDSDSignature(Adapter,dsd)== DSD_IMAGE_MAGIC_NUMBER) + { + BcmFlash2xBulkRead(Adapter, + &uiDSDPri, + dsd, + Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader +FIELD_OFFSET_IN_HEADER(PDSD_HEADER, DSDImagePriority), + 4); + + uiDSDPri = ntohl(uiDSDPri); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"DSD<%x> Priority :%x", dsd, uiDSDPri); + + } + } + return uiDSDPri; +} +FLASH2X_SECTION_VAL getHighestPriDSD(PMINI_ADAPTER Adapter) +{ + INT DSDHighestPri = STATUS_FAILURE; + INT DsdPri= 0 ; + FLASH2X_SECTION_VAL HighestPriDSD = 0 ; + + if(IsSectionWritable(Adapter,DSD2)) + { + DSDHighestPri = ReadDSDPriority(Adapter,DSD2); + HighestPriDSD = DSD2 ; + } + if(IsSectionWritable(Adapter,DSD1)) + { + DsdPri = ReadDSDPriority(Adapter,DSD1); + if(DSDHighestPri < DsdPri) + { + DSDHighestPri = DsdPri ; + HighestPriDSD = DSD1; + } + } + if(IsSectionWritable(Adapter,DSD0)) + { + DsdPri = ReadDSDPriority(Adapter,DSD0); + if(DSDHighestPri < DsdPri) + { + DSDHighestPri = DsdPri ; + HighestPriDSD = DSD0; + } + } + if(HighestPriDSD) + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Highest DSD :%x , and its Pri :%x", HighestPriDSD, DSDHighestPri); + return HighestPriDSD ; +} + +INT ReadISOSignature(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL iso) +{ + UINT uiISOsig = 0; + //UINT sigoffsetInMap = 0; + //ISO_HEADER ISOHeader = {0}; + + + //sigoffsetInMap =(PUCHAR)&(ISOHeader.ISOImageMagicNumber) -(PUCHAR)&ISOHeader; + + if(iso != ISO_IMAGE1 && iso != ISO_IMAGE2) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"passed section value is not for ISOs"); + return STATUS_FAILURE; + } + BcmFlash2xBulkRead(Adapter, + &uiISOsig, + iso, + 0 + FIELD_OFFSET_IN_HEADER(PISO_HEADER,ISOImageMagicNumber), + SIGNATURE_SIZE); + + uiISOsig = ntohl(uiISOsig); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"ISO SIG :%x", uiISOsig); + + return uiISOsig ; +} +INT ReadISOPriority(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL iso) +{ + + unsigned int ISOPri = STATUS_FAILURE; + if(IsSectionWritable(Adapter,iso)) + { + if(ReadISOSignature(Adapter,iso)== ISO_IMAGE_MAGIC_NUMBER) + { + BcmFlash2xBulkRead(Adapter, + &ISOPri, + iso, + 0 + FIELD_OFFSET_IN_HEADER(PISO_HEADER, ISOImagePriority), + 4); + + ISOPri = ntohl(ISOPri); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"ISO<%x> Priority :%x", iso, ISOPri); + + } + } + return ISOPri; +} +FLASH2X_SECTION_VAL getHighestPriISO(PMINI_ADAPTER Adapter) +{ + INT ISOHighestPri = STATUS_FAILURE; + INT ISOPri= 0 ; + FLASH2X_SECTION_VAL HighestPriISO = NO_SECTION_VAL ; + + if(IsSectionWritable(Adapter,ISO_IMAGE2)) + { + ISOHighestPri = ReadISOPriority(Adapter,ISO_IMAGE2); + HighestPriISO = ISO_IMAGE2 ; + } + if(IsSectionWritable(Adapter,ISO_IMAGE1)) + { + ISOPri = ReadISOPriority(Adapter,ISO_IMAGE1); + if(ISOHighestPri < ISOPri) + { + ISOHighestPri = ISOPri ; + HighestPriISO = ISO_IMAGE1; + } + } + if(HighestPriISO) + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Highest ISO :%x and its Pri :%x",HighestPriISO,ISOHighestPri); + return HighestPriISO ; +} +INT WriteToFlashWithoutSectorErase(PMINI_ADAPTER Adapter, + PUINT pBuff, + FLASH2X_SECTION_VAL eFlash2xSectionVal, + UINT uiOffset, + UINT uiNumBytes + ) +{ +#if !defined(BCM_SHM_INTERFACE) || defined(FLASH_DIRECT_ACCESS) + UINT uiTemp = 0, value = 0 ; + UINT i = 0; + UINT uiPartOffset = 0; +#endif + UINT uiStartOffset = 0; + //Adding section start address + INT Status = STATUS_SUCCESS; + PUCHAR pcBuff = (PUCHAR)pBuff; + + if(uiNumBytes % Adapter->ulFlashWriteSize) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Writing without Sector Erase for non-FlashWriteSize number of bytes 0x%x\n", uiNumBytes); + return STATUS_FAILURE; + } + + uiStartOffset = BcmGetSectionValStartOffset(Adapter,eFlash2xSectionVal); + + if(IsSectionExistInVendorInfo(Adapter,eFlash2xSectionVal)) + { + return vendorextnWriteSectionWithoutErase(Adapter, pcBuff, eFlash2xSectionVal, uiOffset, uiNumBytes); + } + + uiOffset = uiOffset + uiStartOffset; + +#if defined(BCM_SHM_INTERFACE) && !defined(FLASH_DIRECT_ACCESS) + Status = bcmflash_raw_writenoerase((uiOffset/FLASH_PART_SIZE),(uiOffset % FLASH_PART_SIZE), pcBuff,uiNumBytes); +#else + rdmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); + value = 0; + wrmalt(Adapter, 0x0f000C80,&value, sizeof(value)); + + Adapter->SelectedChip = RESET_CHIP_SELECT; + BcmDoChipSelect(Adapter,uiOffset); + uiPartOffset = (uiOffset & (FLASH_PART_SIZE - 1)) + GetFlashBaseAddr(Adapter); + + for(i = 0 ; i< uiNumBytes; i += Adapter->ulFlashWriteSize) + { + if(Adapter->ulFlashWriteSize == BYTE_WRITE_SUPPORT) + Status = flashByteWrite(Adapter,uiPartOffset, pcBuff); + else + Status = flashWrite(Adapter,uiPartOffset, pcBuff); + + if(Status != STATUS_SUCCESS) + break; + + pcBuff = pcBuff + Adapter->ulFlashWriteSize; + uiPartOffset = uiPartOffset + Adapter->ulFlashWriteSize; + } + wrmalt(Adapter, 0x0f000C80, &uiTemp, sizeof(uiTemp)); + Adapter->SelectedChip = RESET_CHIP_SELECT; +#endif + + return Status; +} + +BOOLEAN IsSectionExistInFlash(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL section) +{ + + BOOLEAN SectionPresent = FALSE ; + + switch(section) + { + + case ISO_IMAGE1 : + if((Adapter->psFlash2xCSInfo->OffsetISOImage1Part1Start != UNINIT_PTR_IN_CS) && + (IsNonCDLessDevice(Adapter) == FALSE)) + SectionPresent = TRUE ; + break; + case ISO_IMAGE2 : + if((Adapter->psFlash2xCSInfo->OffsetISOImage2Part1Start != UNINIT_PTR_IN_CS) && + (IsNonCDLessDevice(Adapter) == FALSE)) + SectionPresent = TRUE ; + break; + case DSD0 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForDSDStart != UNINIT_PTR_IN_CS) + SectionPresent = TRUE ; + break; + case DSD1 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD1Start != UNINIT_PTR_IN_CS) + SectionPresent = TRUE ; + break; + case DSD2 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForDSD2Start != UNINIT_PTR_IN_CS) + SectionPresent = TRUE ; + break; + case VSA0 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForVSAStart != UNINIT_PTR_IN_CS) + SectionPresent = TRUE ; + break; + case VSA1 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA1Start != UNINIT_PTR_IN_CS) + SectionPresent = TRUE ; + break; + case VSA2 : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForVSA2Start != UNINIT_PTR_IN_CS) + SectionPresent = TRUE ; + break; + case SCSI : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForScsiFirmware != UNINIT_PTR_IN_CS) + SectionPresent = TRUE ; + break; + case CONTROL_SECTION : + if(Adapter->psFlash2xCSInfo->OffsetFromZeroForControlSectionStart != UNINIT_PTR_IN_CS) + SectionPresent = TRUE ; + break; + default : + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Section Does not exist in Flash 2.x"); + SectionPresent = FALSE; + } + return SectionPresent ; +} +INT IsSectionWritable(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL Section) +{ + INT offset = STATUS_FAILURE; + INT Status = FALSE; + if(IsSectionExistInFlash(Adapter,Section) == FALSE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Section <%d> does not exixt", Section); + return FALSE; + } + offset = BcmGetSectionValStartOffset(Adapter,Section); + if(offset == INVALID_OFFSET) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Section<%d> does not exixt", Section); + return FALSE; + } + + if(IsSectionExistInVendorInfo(Adapter,Section)) + { + return !(Adapter->psFlash2xVendorInfo->VendorSection[Section].AccessFlags & FLASH2X_SECTION_RO); + } + + Status = IsOffsetWritable(Adapter,offset); + return Status ; +} + +static INT CorruptDSDSig(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlash2xSectionVal) +{ + + PUCHAR pBuff = NULL; + UINT sig = 0; + UINT uiOffset = 0; + UINT BlockStatus = 0; + UINT uiSectAlignAddr = 0; + + Adapter->bSigCorrupted = FALSE; + + if(Adapter->bAllDSDWriteAllow == FALSE) + { + if(IsSectionWritable(Adapter,eFlash2xSectionVal) != TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Section is not Writable...Hence can't Corrupt signature"); + return SECTOR_IS_NOT_WRITABLE; + } + } + + pBuff = (PUCHAR)kzalloc(MAX_RW_SIZE, GFP_KERNEL); + if(pBuff == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0, "Can't allocate memorey"); + return -ENOMEM ; + } + + uiOffset = Adapter->psFlash2xCSInfo->OffsetFromDSDStartForDSDHeader + sizeof(DSD_HEADER); + uiOffset -= MAX_RW_SIZE ; + + BcmFlash2xBulkRead(Adapter, (PUINT)pBuff,eFlash2xSectionVal,uiOffset,MAX_RW_SIZE); + + + sig = *((PUINT)(pBuff +12)); + sig =ntohl(sig); + BCM_DEBUG_PRINT_BUFFER(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,pBuff,MAX_RW_SIZE); + //Now corrupting the sig by corrupting 4th last Byte. + *(pBuff + 12) = 0; + + if(sig == DSD_IMAGE_MAGIC_NUMBER) + { + Adapter->bSigCorrupted = TRUE; + if(Adapter->ulFlashWriteSize == BYTE_WRITE_SUPPORT) + { + uiSectAlignAddr = uiOffset & ~(Adapter->uiSectorSize -1); + BlockStatus = BcmFlashUnProtectBlock(Adapter,uiSectAlignAddr,Adapter->uiSectorSize); + + WriteToFlashWithoutSectorErase(Adapter,(PUINT)(pBuff + 12),eFlash2xSectionVal, + (uiOffset + 12),BYTE_WRITE_SUPPORT); + if(BlockStatus) + { + BcmRestoreBlockProtectStatus(Adapter,BlockStatus); + BlockStatus = 0; + } + } + else + { + WriteToFlashWithoutSectorErase(Adapter,(PUINT)pBuff,eFlash2xSectionVal, + uiOffset ,MAX_RW_SIZE); + } + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"BCM Signature is not present in header"); + kfree(pBuff); + return STATUS_FAILURE; + } + + kfree(pBuff); + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Corrupted the signature"); + return STATUS_SUCCESS ; +} + +static INT CorruptISOSig(PMINI_ADAPTER Adapter, FLASH2X_SECTION_VAL eFlash2xSectionVal) +{ + + PUCHAR pBuff = NULL; + UINT sig = 0; + UINT uiOffset = 0; + + Adapter->bSigCorrupted = FALSE; + + if(IsSectionWritable(Adapter,eFlash2xSectionVal) != TRUE) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Section is not Writable...Hence can't Corrupt signature"); + return SECTOR_IS_NOT_WRITABLE; + } + + pBuff = (PUCHAR)kzalloc(MAX_RW_SIZE, GFP_KERNEL); + if(pBuff == NULL) + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"Can't allocate memorey"); + return -ENOMEM ; + } + + uiOffset = 0; + + BcmFlash2xBulkRead(Adapter, (PUINT)pBuff,eFlash2xSectionVal,uiOffset, MAX_RW_SIZE); + + sig = *((PUINT)pBuff); + sig =ntohl(sig); + + //corrupt signature + *pBuff = 0; + + if(sig == ISO_IMAGE_MAGIC_NUMBER) + { + Adapter->bSigCorrupted = TRUE; + WriteToFlashWithoutSectorErase(Adapter,(PUINT)pBuff,eFlash2xSectionVal, + uiOffset ,Adapter->ulFlashWriteSize); + } + else + { + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_PRINTK, 0, 0,"BCM Signature is not present in header"); + kfree(pBuff); + return STATUS_FAILURE; + } + + BCM_DEBUG_PRINT(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,"Corrupted the signature"); + BCM_DEBUG_PRINT_BUFFER(Adapter,DBG_TYPE_OTHERS, NVM_RW, DBG_LVL_ALL,pBuff,MAX_RW_SIZE); + + kfree(pBuff); + return STATUS_SUCCESS ; +} + +BOOLEAN IsNonCDLessDevice(PMINI_ADAPTER Adapter) +{ + if(Adapter->psFlash2xCSInfo->IsCDLessDeviceBootSig == NON_CDLESS_DEVICE_BOOT_SIG) + return TRUE; + else + return FALSE ; +} + diff --git a/drivers/staging/bcm/nvm.h b/drivers/staging/bcm/nvm.h new file mode 100644 index 00000000..651b5a45 --- /dev/null +++ b/drivers/staging/bcm/nvm.h @@ -0,0 +1,409 @@ +/*************************************************************************************** +// +// Copyright (c) Beceem Communications Inc. +// +// Module Name: +// NVM.h +// +// Abstract: +// This file has the prototypes,preprocessors and definitions various NVM libraries. +// +// +// Revision History: +// Who When What +// -------- -------- ---------------------------------------------- +// Name Date Created/reviewed/modified +// +// Notes: +// +****************************************************************************************/ + + +#ifndef _NVM_H_ +#define _NVM_H_ + +typedef struct _FLASH_SECTOR_INFO +{ + UINT uiSectorSig; + UINT uiSectorSize; + +}FLASH_SECTOR_INFO,*PFLASH_SECTOR_INFO; + +typedef struct _FLASH_CS_INFO +{ + B_UINT32 MagicNumber; +// let the magic number be 0xBECE-F1A5 - F1A5 for "flas-h" + + B_UINT32 FlashLayoutVersion ; + + // ISO Image/Format/BuildTool versioning + B_UINT32 ISOImageVersion; + + // SCSI/Flash BootLoader versioning + B_UINT32 SCSIFirmwareVersion; + + + B_UINT32 OffsetFromZeroForPart1ISOImage; +// typically 0 + + B_UINT32 OffsetFromZeroForScsiFirmware; +//typically at 12MB + + B_UINT32 SizeOfScsiFirmware ; +//size of the firmware - depends on binary size + + B_UINT32 OffsetFromZeroForPart2ISOImage; +// typically at first Word Aligned offset 12MB + sizeOfScsiFirmware. + + B_UINT32 OffsetFromZeroForCalibrationStart; +// typically at 15MB + + B_UINT32 OffsetFromZeroForCalibrationEnd; + +// VSA0 offsets + B_UINT32 OffsetFromZeroForVSAStart; + B_UINT32 OffsetFromZeroForVSAEnd; + +// Control Section offsets + B_UINT32 OffsetFromZeroForControlSectionStart; + B_UINT32 OffsetFromZeroForControlSectionData; + +// NO Data Activity timeout to switch from MSC to NW Mode + B_UINT32 CDLessInactivityTimeout; + +// New ISO Image Signature + B_UINT32 NewImageSignature; + +// Signature to validate the sector size. + B_UINT32 FlashSectorSizeSig; + +// Sector Size + B_UINT32 FlashSectorSize; + +// Write Size Support + B_UINT32 FlashWriteSupportSize; + +// Total Flash Size + B_UINT32 TotalFlashSize; + +// Flash Base Address for offset specified + B_UINT32 FlashBaseAddr; + +// Flash Part Max Size + B_UINT32 FlashPartMaxSize; + +// Is CDLess or Flash Bootloader + B_UINT32 IsCDLessDeviceBootSig; + +// MSC Timeout after reset to switch from MSC to NW Mode + B_UINT32 MassStorageTimeout; + + +}FLASH_CS_INFO,*PFLASH_CS_INFO; + +#define FLASH2X_TOTAL_SIZE (64*1024*1024) +#define DEFAULT_SECTOR_SIZE (64*1024) + +typedef struct _FLASH_2X_CS_INFO +{ + + // magic number as 0xBECE-F1A5 - F1A5 for "flas-h" + B_UINT32 MagicNumber; + + B_UINT32 FlashLayoutVersion ; + + // ISO Image/Format/BuildTool versioning + B_UINT32 ISOImageVersion; + + // SCSI/Flash BootLoader versioning + B_UINT32 SCSIFirmwareVersion; + + // ISO Image1 Part1/SCSI Firmware/Flash Bootloader Start offset, size + B_UINT32 OffsetFromZeroForPart1ISOImage; + B_UINT32 OffsetFromZeroForScsiFirmware; + B_UINT32 SizeOfScsiFirmware ; + + // ISO Image1 Part2 start offset + B_UINT32 OffsetFromZeroForPart2ISOImage; + + + // DSD0 offset + B_UINT32 OffsetFromZeroForDSDStart; + B_UINT32 OffsetFromZeroForDSDEnd; + + // VSA0 offset + B_UINT32 OffsetFromZeroForVSAStart; + B_UINT32 OffsetFromZeroForVSAEnd; + + // Control Section offset + B_UINT32 OffsetFromZeroForControlSectionStart; + B_UINT32 OffsetFromZeroForControlSectionData; + + // NO Data Activity timeout to switch from MSC to NW Mode + B_UINT32 CDLessInactivityTimeout; + + // New ISO Image Signature + B_UINT32 NewImageSignature; + + B_UINT32 FlashSectorSizeSig; // Sector Size Signature + B_UINT32 FlashSectorSize; // Sector Size + B_UINT32 FlashWriteSupportSize; // Write Size Support + + B_UINT32 TotalFlashSize; // Total Flash Size + + // Flash Base Address for offset specified + B_UINT32 FlashBaseAddr; + B_UINT32 FlashPartMaxSize; // Flash Part Max Size + + // Is CDLess or Flash Bootloader + B_UINT32 IsCDLessDeviceBootSig; + + // MSC Timeout after reset to switch from MSC to NW Mode + B_UINT32 MassStorageTimeout; + + /* Flash Map 2.0 Field */ + B_UINT32 OffsetISOImage1Part1Start; // ISO Image1 Part1 offset + B_UINT32 OffsetISOImage1Part1End; + B_UINT32 OffsetISOImage1Part2Start; // ISO Image1 Part2 offset + B_UINT32 OffsetISOImage1Part2End; + B_UINT32 OffsetISOImage1Part3Start; // ISO Image1 Part3 offset + B_UINT32 OffsetISOImage1Part3End; + + B_UINT32 OffsetISOImage2Part1Start; // ISO Image2 Part1 offset + B_UINT32 OffsetISOImage2Part1End; + B_UINT32 OffsetISOImage2Part2Start; // ISO Image2 Part2 offset + B_UINT32 OffsetISOImage2Part2End; + B_UINT32 OffsetISOImage2Part3Start; // ISO Image2 Part3 offset + B_UINT32 OffsetISOImage2Part3End; + + + // DSD Header offset from start of DSD + B_UINT32 OffsetFromDSDStartForDSDHeader; + B_UINT32 OffsetFromZeroForDSD1Start; // DSD 1 offset + B_UINT32 OffsetFromZeroForDSD1End; + B_UINT32 OffsetFromZeroForDSD2Start; // DSD 2 offset + B_UINT32 OffsetFromZeroForDSD2End; + + B_UINT32 OffsetFromZeroForVSA1Start; // VSA 1 offset + B_UINT32 OffsetFromZeroForVSA1End; + B_UINT32 OffsetFromZeroForVSA2Start; // VSA 2 offset + B_UINT32 OffsetFromZeroForVSA2End; + + /* +* ACCESS_BITS_PER_SECTOR 2 +* ACCESS_RW 0 +* ACCESS_RO 1 +* ACCESS_RESVD 2 +* ACCESS_RESVD 3 +* */ + B_UINT32 SectorAccessBitMap[FLASH2X_TOTAL_SIZE/(DEFAULT_SECTOR_SIZE *16)]; + +// All expansions to the control data structure should add here + +}FLASH2X_CS_INFO,*PFLASH2X_CS_INFO; + +typedef struct _VENDOR_SECTION_INFO +{ + B_UINT32 OffsetFromZeroForSectionStart; + B_UINT32 OffsetFromZeroForSectionEnd; + B_UINT32 AccessFlags; + B_UINT32 Reserved[16]; + +} VENDOR_SECTION_INFO, *PVENDOR_SECTION_INFO; + +typedef struct _FLASH2X_VENDORSPECIFIC_INFO +{ + VENDOR_SECTION_INFO VendorSection[TOTAL_SECTIONS]; + B_UINT32 Reserved[16]; + +} FLASH2X_VENDORSPECIFIC_INFO, *PFLASH2X_VENDORSPECIFIC_INFO; + +typedef struct _DSD_HEADER +{ + B_UINT32 DSDImageSize; + B_UINT32 DSDImageCRC; + B_UINT32 DSDImagePriority; + //We should not consider right now. Reading reserve is worthless. + B_UINT32 Reserved[252]; // Resvd for DSD Header + B_UINT32 DSDImageMagicNumber; + +}DSD_HEADER, *PDSD_HEADER; + +typedef struct _ISO_HEADER +{ + B_UINT32 ISOImageMagicNumber; + B_UINT32 ISOImageSize; + B_UINT32 ISOImageCRC; + B_UINT32 ISOImagePriority; + //We should not consider right now. Reading reserve is worthless. + B_UINT32 Reserved[60]; //Resvd for ISO Header extension + +}ISO_HEADER, *PISO_HEADER; + +#define EEPROM_BEGIN_CIS (0) +#define EEPROM_BEGIN_NON_CIS (0x200) +#define EEPROM_END (0x2000) + +#define INIT_PARAMS_SIGNATURE (0x95a7a597) + +#define MAX_INIT_PARAMS_LENGTH (2048) + + +#define MAC_ADDRESS_OFFSET 0x200 + + +#define INIT_PARAMS_1_SIGNATURE_ADDRESS EEPROM_BEGIN_NON_CIS +#define INIT_PARAMS_1_DATA_ADDRESS (INIT_PARAMS_1_SIGNATURE_ADDRESS+16) +#define INIT_PARAMS_1_MACADDRESS_ADDRESS (MAC_ADDRESS_OFFSET) +#define INIT_PARAMS_1_LENGTH_ADDRESS (INIT_PARAMS_1_SIGNATURE_ADDRESS+4) + +#define INIT_PARAMS_2_SIGNATURE_ADDRESS (EEPROM_BEGIN_NON_CIS+2048+16) +#define INIT_PARAMS_2_DATA_ADDRESS (INIT_PARAMS_2_SIGNATURE_ADDRESS+16) +#define INIT_PARAMS_2_MACADDRESS_ADDRESS (INIT_PARAMS_2_SIGNATURE_ADDRESS+8) +#define INIT_PARAMS_2_LENGTH_ADDRESS (INIT_PARAMS_2_SIGNATURE_ADDRESS+4) + +#define EEPROM_SPI_DEV_CONFIG_REG 0x0F003000 +#define EEPROM_SPI_Q_STATUS1_REG 0x0F003004 +#define EEPROM_SPI_Q_STATUS1_MASK_REG 0x0F00300C + +#define EEPROM_SPI_Q_STATUS_REG 0x0F003008 +#define EEPROM_CMDQ_SPI_REG 0x0F003018 +#define EEPROM_WRITE_DATAQ_REG 0x0F00301C +#define EEPROM_READ_DATAQ_REG 0x0F003020 +#define SPI_FLUSH_REG 0x0F00304C + +#define EEPROM_WRITE_ENABLE 0x06000000 +#define EEPROM_READ_STATUS_REGISTER 0x05000000 +#define EEPROM_16_BYTE_PAGE_WRITE 0xFA000000 +#define EEPROM_WRITE_QUEUE_EMPTY 0x00001000 +#define EEPROM_WRITE_QUEUE_AVAIL 0x00002000 +#define EEPROM_WRITE_QUEUE_FULL 0x00004000 +#define EEPROM_16_BYTE_PAGE_READ 0xFB000000 +#define EEPROM_4_BYTE_PAGE_READ 0x3B000000 + +#define EEPROM_CMD_QUEUE_FLUSH 0x00000001 +#define EEPROM_WRITE_QUEUE_FLUSH 0x00000002 +#define EEPROM_READ_QUEUE_FLUSH 0x00000004 +#define EEPROM_ETH_QUEUE_FLUSH 0x00000008 +#define EEPROM_ALL_QUEUE_FLUSH 0x0000000f +#define EEPROM_READ_ENABLE 0x06000000 +#define EEPROM_16_BYTE_PAGE_WRITE 0xFA000000 +#define EEPROM_READ_DATA_FULL 0x00000010 +#define EEPROM_READ_DATA_AVAIL 0x00000020 +#define EEPROM_READ_QUEUE_EMPTY 0x00000002 +#define EEPROM_CMD_QUEUE_EMPTY 0x00000100 +#define EEPROM_CMD_QUEUE_AVAIL 0x00000200 +#define EEPROM_CMD_QUEUE_FULL 0x00000400 + +/* Most EEPROM status register bit 0 indicates if the EEPROM is busy + * with a write if set 1. See the details of the EEPROM Status Register + * in the EEPROM data sheet. */ +#define EEPROM_STATUS_REG_WRITE_BUSY 0x00000001 + +// We will have 1 mSec for every RETRIES_PER_DELAY count and have a max attempts of MAX_EEPROM_RETRIES +// This will give us 80 mSec minimum of delay = 80mSecs +#define MAX_EEPROM_RETRIES 80 +#define RETRIES_PER_DELAY 64 + + +#define MAX_RW_SIZE 0x10 +#define MAX_READ_SIZE 0x10 +#define MAX_SECTOR_SIZE (512*1024) +#define MIN_SECTOR_SIZE (1024) +#define FLASH_SECTOR_SIZE_OFFSET 0xEFFFC +#define FLASH_SECTOR_SIZE_SIG_OFFSET 0xEFFF8 +#define FLASH_SECTOR_SIZE_SIG 0xCAFEBABE +#define FLASH_CS_INFO_START_ADDR 0xFF0000 +#define FLASH_CONTROL_STRUCT_SIGNATURE 0xBECEF1A5 +#define SCSI_FIRMWARE_MAJOR_VERSION 0x1 +#define SCSI_FIRMWARE_MINOR_VERSION 0x5 +#define BYTE_WRITE_SUPPORT 0x1 + +#define FLASH_AUTO_INIT_BASE_ADDR 0xF00000 + + + + +#define FLASH_CONTIGIOUS_START_ADDR_AFTER_INIT 0x1C000000 +#define FLASH_CONTIGIOUS_START_ADDR_BEFORE_INIT 0x1F000000 + +#define FLASH_CONTIGIOUS_START_ADDR_BCS350 0x08000000 +#define FLASH_CONTIGIOUS_END_ADDR_BCS350 0x08FFFFFF + + + +#define FLASH_SIZE_ADDR 0xFFFFEC + +#define FLASH_SPI_CMDQ_REG 0xAF003040 +#define FLASH_SPI_WRITEQ_REG 0xAF003044 +#define FLASH_SPI_READQ_REG 0xAF003048 +#define FLASH_CONFIG_REG 0xAF003050 +#define FLASH_GPIO_CONFIG_REG 0xAF000030 + +#define FLASH_CMD_WRITE_ENABLE 0x06 +#define FLASH_CMD_READ_ENABLE 0x03 +#define FLASH_CMD_RESET_WRITE_ENABLE 0x04 +#define FLASH_CMD_STATUS_REG_READ 0x05 +#define FLASH_CMD_STATUS_REG_WRITE 0x01 +#define FLASH_CMD_READ_ID 0x9F + +#define PAD_SELECT_REGISTER 0xAF000410 + +#define FLASH_PART_SST25VF080B 0xBF258E + +#define EEPROM_CAL_DATA_INTERNAL_LOC 0xbFB00008 + +#define EEPROM_CALPARAM_START 0x200 +#define EEPROM_SIZE_OFFSET 524 + +//As Read/Write time vaires from 1.5 to 3.0 ms. +//so After Ignoring the rdm/wrm time(that is dependent on many factor like interface etc.), +//here time calculated meets the worst case delay, 3.0 ms +#define MAX_FLASH_RETRIES 4 +#define FLASH_PER_RETRIES_DELAY 16 + + +#define EEPROM_MAX_CAL_AREA_SIZE 0xF0000 + + + +#define BECM ntohl(0x4245434d) + +#define FLASH_2X_MAJOR_NUMBER 0x2 +#define DSD_IMAGE_MAGIC_NUMBER 0xBECE0D5D +#define ISO_IMAGE_MAGIC_NUMBER 0xBECE0150 +#define NON_CDLESS_DEVICE_BOOT_SIG 0xBECEB007 +#define MINOR_VERSION(x) ((x >>16) & 0xFFFF) +#define MAJOR_VERSION(x) (x & 0xFFFF) +#define CORRUPTED_PATTERN 0x0 +#define UNINIT_PTR_IN_CS 0xBBBBDDDD + +#define VENDOR_PTR_IN_CS 0xAAAACCCC + + +#define FLASH2X_SECTION_PRESENT 1<<0 +#define FLASH2X_SECTION_VALID 1<<1 +#define FLASH2X_SECTION_RO 1<<2 +#define FLASH2X_SECTION_ACT 1<<3 +#define SECTOR_IS_NOT_WRITABLE STATUS_FAILURE +#define INVALID_OFFSET STATUS_FAILURE +#define INVALID_SECTION STATUS_FAILURE +#define SECTOR_1K 1024 +#define SECTOR_64K (64 *SECTOR_1K) +#define SECTOR_128K (2 * SECTOR_64K) +#define SECTOR_256k (2 * SECTOR_128K) +#define SECTOR_512K (2 * SECTOR_256k) +#define FLASH_PART_SIZE (16 * 1024 * 1024) +#define RESET_CHIP_SELECT -1 +#define CHIP_SELECT_BIT12 12 + +#define SECTOR_READWRITE_PERMISSION 0 +#define SECTOR_READONLY 1 +#define SIGNATURE_SIZE 4 +#define DEFAULT_BUFF_SIZE 0x10000 + + +#define FIELD_OFFSET_IN_HEADER(HeaderPointer,Field) ((PUCHAR)&((HeaderPointer)(NULL))->Field - (PUCHAR)(NULL)) + +#endif + diff --git a/drivers/staging/bcm/sort.c b/drivers/staging/bcm/sort.c new file mode 100644 index 00000000..63c966a0 --- /dev/null +++ b/drivers/staging/bcm/sort.c @@ -0,0 +1,52 @@ +#include "headers.h" +#include + +/* + * File Name: sort.c + * + * Author: Beceem Communications Pvt. Ltd + * + * Abstract: This file contains the routines sorting the classification rules. + * + * Copyright (c) 2007 Beceem Communications Pvt. Ltd + */ + +static int compare_packet_info(void const *a, void const *b) +{ + PacketInfo const *pa = a; + PacketInfo const *pb = b; + + if (!pa->bValid || !pb->bValid) + return 0; + + return pa->u8TrafficPriority - pb->u8TrafficPriority; +} + +VOID SortPackInfo(PMINI_ADAPTER Adapter) +{ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, + DBG_LVL_ALL, "<======="); + + sort(Adapter->PackInfo, NO_OF_QUEUES, sizeof(PacketInfo), + compare_packet_info, NULL); +} + +static int compare_classifiers(void const *a, void const *b) +{ + S_CLASSIFIER_RULE const *pa = a; + S_CLASSIFIER_RULE const *pb = b; + + if (!pa->bUsed || !pb->bUsed) + return 0; + + return pa->u8ClassifierRulePriority - pb->u8ClassifierRulePriority; +} + +VOID SortClassifiers(PMINI_ADAPTER Adapter) +{ + BCM_DEBUG_PRINT(Adapter, DBG_TYPE_OTHERS, CONN_MSG, + DBG_LVL_ALL, "<======="); + + sort(Adapter->astClassifierTable, MAX_CLASSIFIERS, + sizeof(S_CLASSIFIER_RULE), compare_classifiers, NULL); +} diff --git a/drivers/staging/bcm/target_params.h b/drivers/staging/bcm/target_params.h new file mode 100644 index 00000000..14876388 --- /dev/null +++ b/drivers/staging/bcm/target_params.h @@ -0,0 +1,81 @@ +#ifndef TARGET_PARAMS_H +#define TARGET_PARAMS_H + +typedef struct _TARGET_PARAMS +{ + B_UINT32 m_u32CfgVersion; + + // Scanning Related Params + B_UINT32 m_u32CenterFrequency; + B_UINT32 m_u32BandAScan; + B_UINT32 m_u32BandBScan; + B_UINT32 m_u32BandCScan; + + + // QoS Params + B_UINT32 m_u32ErtpsOptions; + + B_UINT32 m_u32PHSEnable; + + + // HO Params + B_UINT32 m_u32HoEnable; + + B_UINT32 m_u32HoReserved1; + B_UINT32 m_u32HoReserved2; + // Power Control Params + + B_UINT32 m_u32MimoEnable; + + B_UINT32 m_u32SecurityEnable; + + B_UINT32 m_u32PowerSavingModesEnable; //bit 1: 1 Idlemode enable; bit2: 1 Sleepmode Enable + /* PowerSaving Mode Options: + bit 0 = 1: CPE mode - to keep pcmcia if alive; + bit 1 = 1: CINR reporing in Idlemode Msg + bit 2 = 1: Default PSC Enable in sleepmode*/ + B_UINT32 m_u32PowerSavingModeOptions; + + B_UINT32 m_u32ArqEnable; + + // From Version #3, the HARQ section renamed as general + B_UINT32 m_u32HarqEnable; + // EEPROM Param Location + B_UINT32 m_u32EEPROMFlag; + // BINARY TYPE - 4th MSByte: Interface Type - 3rd MSByte: Vendor Type - 2nd MSByte + // Unused - LSByte + B_UINT32 m_u32Customize; + B_UINT32 m_u32ConfigBW; /* In Hz */ + B_UINT32 m_u32ShutDownInitThresholdTimer; + + B_UINT32 m_u32RadioParameter; + B_UINT32 m_u32PhyParameter1; + B_UINT32 m_u32PhyParameter2; + B_UINT32 m_u32PhyParameter3; + + B_UINT32 m_u32TestOptions; // in eval mode only; lower 16bits = basic cid for testing; then bit 16 is test cqich,bit 17 test init rang; bit 18 test periodic rang and bit 19 is test harq ack/nack + + B_UINT32 m_u32MaxMACDataperDLFrame; + B_UINT32 m_u32MaxMACDataperULFrame; + + B_UINT32 m_u32Corr2MacFlags; + + //adding driver params. + B_UINT32 HostDrvrConfig1; + B_UINT32 HostDrvrConfig2; + B_UINT32 HostDrvrConfig3; + B_UINT32 HostDrvrConfig4; + B_UINT32 HostDrvrConfig5; + B_UINT32 HostDrvrConfig6; + B_UINT32 m_u32SegmentedPUSCenable; + + // removed SHUT down related 'unused' params from here to sync 4.x and 5.x CFG files.. + + //BAMC Related Parameters + //Bit 0-15 Band AMC signaling configuration: Bit 1 = 1 – Enable Band AMC signaling. + //bit 16-31 Band AMC Data configuration: Bit 16 = 1 – Band AMC 2x3 support. + B_UINT32 m_u32BandAMCEnable; + +} stTargetParams,TARGET_PARAMS,*PTARGET_PARAMS, STARGETPARAMS, *PSTARGETPARAMS; + +#endif diff --git a/drivers/staging/bcm/vendorspecificextn.c b/drivers/staging/bcm/vendorspecificextn.c new file mode 100644 index 00000000..4178cd16 --- /dev/null +++ b/drivers/staging/bcm/vendorspecificextn.c @@ -0,0 +1,146 @@ +#include "headers.h" +//----------------------------------------------------------------------------- +// Procedure: vendorextnGetSectionInfo +// +// Description: Finds the type of NVM used. +// +// Arguments: +// Adapter - ptr to Adapter object instance +// pNVMType - ptr to NVM type. +// Returns: +// STATUS_SUCCESS/STATUS_FAILURE +// +//----------------------------------------------------------------------------- +INT vendorextnGetSectionInfo(PVOID pContext,PFLASH2X_VENDORSPECIFIC_INFO pVendorInfo) +{ + return STATUS_FAILURE; +} + +//----------------------------------------------------------------------------- +// Procedure: vendorextnInit +// +// Description: Initializing the vendor extension NVM interface +// +// Arguments: +// Adapter - Pointer to MINI Adapter Structure. + +// Returns: +// STATUS_SUCCESS/STATUS_FAILURE +// +//----------------------------------------------------------------------------- +INT vendorextnInit(PMINI_ADAPTER Adapter) +{ + return STATUS_SUCCESS; +} + +//----------------------------------------------------------------------------- +// Procedure: vendorextnExit +// +// Description: Free the resource associated with vendor extension NVM interface +// +// Arguments: +// Adapter - Pointer to MINI Adapter Structure. + +// Returns: +// STATUS_SUCCESS/STATUS_FAILURE +// +//----------------------------------------------------------------------------- +INT vendorextnExit(PMINI_ADAPTER Adapter) +{ + return STATUS_SUCCESS; +} + +//------------------------------------------------------------------------ +// Procedure: vendorextnIoctl +// +// Description: execute the vendor extension specific ioctl +// +//Arguments: +// Adapter -Beceem private Adapter Structure +// cmd -vendor extension specific Ioctl commad +// arg -input parameter sent by vendor +// +// Returns: +// CONTINUE_COMMON_PATH in case it is not meant to be processed by vendor ioctls +// STATUS_SUCCESS/STATUS_FAILURE as per the IOCTL return value +// +//-------------------------------------------------------------------------- +INT vendorextnIoctl(PMINI_ADAPTER Adapter, UINT cmd, ULONG arg) +{ + return CONTINUE_COMMON_PATH; +} + + + +//------------------------------------------------------------------ +// Procedure: vendorextnReadSection +// +// Description: Reads from a section of NVM +// +// Arguments: +// pContext - ptr to Adapter object instance +// pBuffer - Read the data from Vendor Area to this buffer +// SectionVal - Value of type of Section +// Offset - Read from the Offset of the Vendor Section. +// numOfBytes - Read numOfBytes from the Vendor section to Buffer +// +// Returns: +// STATUS_SUCCESS/STATUS_FAILURE +// +//------------------------------------------------------------------ + +INT vendorextnReadSection(PVOID pContext, PUCHAR pBuffer, FLASH2X_SECTION_VAL SectionVal, + UINT offset, UINT numOfBytes) +{ + return STATUS_FAILURE; +} + + + +//------------------------------------------------------------------ +// Procedure: vendorextnWriteSection +// +// Description: Write to a Section of NVM +// +// Arguments: +// pContext - ptr to Adapter object instance +// pBuffer - Write the data provided in the buffer +// SectionVal - Value of type of Section +// Offset - Writes to the Offset of the Vendor Section. +// numOfBytes - Write num Bytes after reading from pBuffer. +// bVerify - the Buffer Written should be verified. +// +// Returns: +// STATUS_SUCCESS/STATUS_FAILURE +// +//------------------------------------------------------------------ +INT vendorextnWriteSection(PVOID pContext, PUCHAR pBuffer, FLASH2X_SECTION_VAL SectionVal, + UINT offset, UINT numOfBytes, BOOLEAN bVerify) +{ + return STATUS_FAILURE; +} + + + +//------------------------------------------------------------------ +// Procedure: vendorextnWriteSectionWithoutErase +// +// Description: Write to a Section of NVM without erasing the sector +// +// Arguments: +// pContext - ptr to Adapter object instance +// pBuffer - Write the data provided in the buffer +// SectionVal - Value of type of Section +// Offset - Writes to the Offset of the Vendor Section. +// numOfBytes - Write num Bytes after reading from pBuffer. +// +// Returns: +// STATUS_SUCCESS/STATUS_FAILURE +// +//------------------------------------------------------------------ +INT vendorextnWriteSectionWithoutErase(PVOID pContext, PUCHAR pBuffer, FLASH2X_SECTION_VAL SectionVal, + UINT offset, UINT numOfBytes) +{ + return STATUS_FAILURE; +} + diff --git a/drivers/staging/bcm/vendorspecificextn.h b/drivers/staging/bcm/vendorspecificextn.h new file mode 100644 index 00000000..7ff14951 --- /dev/null +++ b/drivers/staging/bcm/vendorspecificextn.h @@ -0,0 +1,18 @@ + +#ifndef __VENDOR_EXTN_NVM_H__ +#define __VENDOR_EXTN_NVM_H__ + +#define CONTINUE_COMMON_PATH 0xFFFF + +INT vendorextnGetSectionInfo(PVOID pContext,PFLASH2X_VENDORSPECIFIC_INFO pVendorInfo); +INT vendorextnExit(PMINI_ADAPTER Adapter); +INT vendorextnInit(PMINI_ADAPTER Adapter); +INT vendorextnIoctl(PMINI_ADAPTER Adapter, UINT cmd, ULONG arg); +INT vendorextnReadSection(PVOID pContext, PUCHAR pBuffer, FLASH2X_SECTION_VAL SectionVal, + UINT offset, UINT numOfBytes); +INT vendorextnWriteSection(PVOID pContext, PUCHAR pBuffer, FLASH2X_SECTION_VAL SectionVal, + UINT offset, UINT numOfBytes, BOOLEAN bVerify); +INT vendorextnWriteSectionWithoutErase(PVOID pContext, PUCHAR pBuffer, FLASH2X_SECTION_VAL SectionVal, + UINT offset, UINT numOfBytes); + +#endif /* */ diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig new file mode 100644 index 00000000..12c691d9 --- /dev/null +++ b/drivers/staging/comedi/Kconfig @@ -0,0 +1,1384 @@ +config COMEDI + tristate "Data acquisition support (comedi)" + default N + depends on m + depends on BROKEN || FRV || M32R || MN10300 || SUPERH || TILE || X86 + ---help--- + Enable support a wide range of data acquisition devices + for Linux. + +config COMEDI_DEBUG + bool "Comedi debugging" + depends on COMEDI != n + ---help--- + This is an option for use by developers; most people should + say N here. This enables comedi core and driver debugging. + +menuconfig COMEDI_MISC_DRIVERS + tristate "Comedi misc drivers" + depends on COMEDI + default N + ---help--- + Enable comedi misc drivers to be built + + Note that the answer to this question won't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about misc non-hardware comedi drivers. + +if COMEDI_MISC_DRIVERS + +config COMEDI_KCOMEDILIB + tristate "Comedi kcomedilib" + ---help--- + Build the kcomedilib + +config COMEDI_BOND + tristate "Device bonding support" + depends on COMEDI_KCOMEDILIB + default N + ---help--- + Enable support for a driver to 'bond' (merge) multiple subdevices + from multiple devices together as one. + + To compile this driver as a module, choose M here: the module will be + called comedi_bond. + +config COMEDI_TEST + tristate "Fake waveform generator support" + select COMEDI_FC + default N + ---help--- + Enable support for the fake waveform generator. + This driver is mainly for testing purposes, but can also be used to + generate sample waveforms on systems that don't have data acquisition + hardware. + + To compile this driver as a module, choose M here: the module will be + called comedi_test. + +config COMEDI_PARPORT + tristate "Parallel port support" + default N + ---help--- + Enable support for the standard parallel port. + A cheap and easy way to get a few more digital I/O lines. Steal + additional parallel ports from old computers or your neighbors' + computers. + + To compile this driver as a module, choose M here: the module will be + called comedi_parport. + +config COMEDI_SERIAL2002 + tristate "Driver for serial connected hardware" + default N + ---help--- + Enable support for serial connected hardware + + To compile this driver as a module, choose M here: the module will be + called serial2002. + +config COMEDI_SKEL + tristate "Comedi skeleton driver" + default N + ---help--- + Build the Skeleton driver, an example for driver writers + + To compile this driver as a module, choose M here: the module will be + called skel. + +endif # COMEDI_MISC_DRIVERS + +menuconfig COMEDI_ISA_DRIVERS + tristate "Comedi ISA and PC/104 drivers" + depends on COMEDI && ISA + default N + ---help--- + Enable comedi ISA and PC/104 drivers to be built + + Note that the answer to this question won't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about ISA and PC/104 comedi drivers. + +if COMEDI_ISA_DRIVERS && ISA + +config COMEDI_ACL7225B + tristate "ADlink NuDAQ ACL-7225b and compatibles support" + default N + ---help--- + Enable support for ADlink NuDAQ ACL-7225b and compatibles, + ADlink ACL-7225b (acl7225b), ICP P16R16DIO (p16r16dio) + + To compile this driver as a module, choose M here: the module will be + called acl7225b. + +config COMEDI_PCL711 + tristate "Advantech PCL-711/711b and ADlink ACL-8112 ISA card support" + default N + ---help--- + Enable support for Advantech PCL-711 and 711b, ADlink ACL-8112 + + To compile this driver as a module, choose M here: the module will be + called pcl711. + +config COMEDI_PCL724 + tristate "Advantech PCL-722/724/731 and ADlink ACL-7122/7124/PET-48DIO" + select COMEDI_8255 + default N + ---help--- + Enable support for Advantech PCL-724, PCL-722, PCL-731 and + ADlink ACL-7122, ACL-7124, PET-48DIO ISA cards + + To compile this driver as a module, choose M here: the module will be + called pcl724. + +config COMEDI_PCL725 + tristate "Advantech PCL-725 and compatible ISA card support" + default N + ---help--- + Enable support for Advantech PCL-725 and compatible ISA cards. + + To compile this driver as a module, choose M here: the module will be + called pcl725. + +config COMEDI_PCL726 + tristate "Advantech PCL-726 and compatible ISA card support" + default N + ---help--- + Enable support for Advantech PCL-726 and compatible ISA cards. + + To compile this driver as a module, choose M here: the module will be + called pcl726. + +config COMEDI_PCL730 + tristate "Advantech PCL-730 and ADlink ACL-7130 ISA card support" + default N + ---help--- + Enable support for Advantech PCL-730, ICP ISO-730 and ADlink + ACL-7130 ISA cards + + To compile this driver as a module, choose M here: the module will be + called pcl730. + +config COMEDI_PCL812 + tristate "Advantech PCL-812/813 and ADlink ACL-8112/8113/8113/8216" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for Advantech PCL-812/PG, PCL-813/B, ADLink + ACL-8112DG/HG/PG, ACL-8113, ACL-8216, ICP DAS A-821PGH/PGL/PGL-NDA, + A-822PGH/PGL, A-823PGH/PGL, A-826PG and ICP DAS ISO-813 ISA cards + + To compile this driver as a module, choose M here: the module will be + called pcl812. + +config COMEDI_PCL816 + tristate "Advantech PCL-814 and PCL-816 ISA card support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for Advantech PCL-814 and PCL-816 ISA cards + + To compile this driver as a module, choose M here: the module will be + called pcl816. + +config COMEDI_PCL818 + tristate "Advantech PCL-718 and PCL-818 ISA card support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for Advantech PCL-818 ISA cards + PCL-818L, PCL-818H, PCL-818HD, PCL-818HG, PCL-818 and PCL-718 + + To compile this driver as a module, choose M here: the module will be + called pcl818. + +config COMEDI_PCM3724 + tristate "Advantech PCM-3724 PC/104 card support" + select COMEDI_8255 + default N + ---help--- + Enable support for Advantech PCM-3724 PC/104 cards. + + To compile this driver as a module, choose M here: the module will be + called pcm3724. + +config COMEDI_PCM3730 + tristate "Advantech PCM-3730 and clone PC/104 board support" + default N + ---help--- + Enable support for Advantech PCM-3730 and clone PC/104 boards + + To compile this driver as a module, choose M here: the module will be + called pcm3730. + +config COMEDI_RTI800 + tristate "Analog Devices RTI-800/815 ISA card support" + default N + ---help--- + Enable support for Analog Devices RTI-800/815 ISA cards + + To compile this driver as a module, choose M here: the module will be + called rti800. + +config COMEDI_RTI802 + tristate "Analog Devices RTI-802 ISA card support" + default N + ---help--- + Enable support for Analog Devices RTI-802 ISA cards + + To compile this driver as a module, choose M here: the module will be + called rti802. + +config COMEDI_DAS16M1 + tristate "MeasurementComputing CIO-DAS16/M1DAS-16 ISA card support" + select COMEDI_8255 + select COMEDI_FC + default N + ---help--- + Enable support for Measurement Computing CIO-DAS16/M1 ISA cards. + + To compile this driver as a module, choose M here: the module will be + called das16m1. + +config COMEDI_DAS16 + tristate "DAS-16 compatible ISA and PC/104 card support" + select COMEDI_8255 + select COMEDI_FC + default N + ---help--- + Enable support for Keithley Metrabyte/ComputerBoards DAS16 + and compatible ISA and PC/104 cards: + Keithley Metrabyte DAS-16, DAS-16G, DAS-16F, DAS-1201, DAS-1202, + DAS-1401, DAS-1402, DAS-1601, DAS-1602 and + ComputerBoards/MeasurementComputing PC104-DAS16/JR/, + PC104-DAS16JR/16, CIO-DAS16JR/16, CIO-DAS16/JR, CIO-DAS1401/12, + CIO-DAS1402/12, CIO-DAS1402/16, CIO-DAS1601/12, CIO-DAS1602/12, + CIO-DAS1602/16, CIO-DAS16/330 + + To compile this driver as a module, choose M here: the module will be + called das16. + +config COMEDI_DAS800 + tristate "DAS800 and compatible ISA card support" + select COMEDI_FC + default N + ---help--- + Enable support for Keithley Metrabyte DAS800 and compatible ISA cards + Keithley Metrabyte DAS-800, DAS-801, DAS-802 + Measurement Computing CIO-DAS800, CIO-DAS801, CIO-DAS802 and + CIO-DAS802/16 + + To compile this driver as a module, choose M here: the module will be + called das800. + +config COMEDI_DAS1800 + tristate "DAS1800 and compatible ISA card support" + depends on VIRT_TO_BUS + select COMEDI_FC + default N + ---help--- + Enable support for DAS1800 and compatible ISA cards + Keithley Metrabyte DAS-1701ST, DAS-1701ST-DA, DAS-1701/AO, + DAS-1702ST, DAS-1702ST-DA, DAS-1702HR, DAS-1702HR-DA, DAS-1702/AO, + DAS-1801ST, DAS-1801ST-DA, DAS-1801HC, DAS-1801AO, DAS-1802ST, + DAS-1802ST-DA, DAS-1802HR, DAS-1802HR-DA, DAS-1802HC and + DAS-1802AO + + To compile this driver as a module, choose M here: the module will be + called das1800. + +config COMEDI_DAS6402 + tristate "DAS6402 and compatible ISA card support" + default N + ---help--- + Enable support for DAS6402 and compatible ISA cards + Computerboards, Keithley Metrabyte DAS6402 and compatibles + + To compile this driver as a module, choose M here: the module will be + called das6402. + +config COMEDI_DT2801 + tristate "Data Translation DT2801 ISA card support" + default N + ---help--- + Enable support for Data Translation DT2801 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2801. + +config COMEDI_DT2811 + tristate "Data Translation DT2811 ISA card support" + default N + ---help--- + Enable support for Data Translation DT2811 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2811. + +config COMEDI_DT2814 + tristate "Data Translation DT2814 ISA card support" + default N + ---help--- + Enable support for Data Translation DT2814 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2814. + +config COMEDI_DT2815 + tristate "Data Translation DT2815 ISA card support" + default N + ---help--- + Enable support for Data Translation DT2815 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2815. + +config COMEDI_DT2817 + tristate "Data Translation DT2817 ISA card support" + default N + ---help--- + Enable support for Data Translation DT2817 ISA cards + + To compile this driver as a module, choose M here: the module will be + called dt2817. + +config COMEDI_DT282X + tristate "Data Translation DT2821 series and DT-EZ ISA card support" + select COMEDI_FC + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for Data Translation DT2821 series including DT-EZ + DT2821, DT2821-F-16SE, DT2821-F-8DI, DT2821-G-16SE, DT2821-G-8DI, + DT2823 (dt2823), DT2824-PGH, DT2824-PGL, DT2825, DT2827, DT2828, + DT21-EZ, DT23-EZ, DT24-EZ and DT24-EZ-PGL + + To compile this driver as a module, choose M here: the module will be + called dt282x. + +config COMEDI_DMM32AT + tristate "Diamond Systems MM-32-AT PC/104 board support" + default N + ---help--- + Enable support for Diamond Systems MM-32-AT PC/104 boards + + To compile this driver as a module, choose M here: the module will be + called dmm32at. + +config COMEDI_FL512 + tristate "FL512 ISA card support" + default N + ---help--- + Enable support for FL512 ISA card + + To compile this driver as a module, choose M here: the module will be + called fl512. + +config COMEDI_AIO_AIO12_8 + tristate "I/O Products PC/104 AIO12-8 Analog I/O Board support" + select COMEDI_8255 + default N + ---help--- + Enable support for I/O Products PC/104 AIO12-8 Analog I/O Board + + To compile this driver as a module, choose M here: the module will be + called aio_aio12_8. + +config COMEDI_AIO_IIRO_16 + tristate "I/O Products PC/104 IIRO16 Board support" + default N + ---help--- + Enable support for I/O Products PC/104 IIRO16 Relay And Isolated + Input Board + + To compile this driver as a module, choose M here: the module will be + called aio_iiro_16. + +config COMEDI_C6XDIGIO + tristate "Mechatronic Systems Inc. C6x_DIGIO DSP daughter card support" + default N + ---help--- + Enable support for Mechatronic Systems Inc. C6x_DIGIO DSP daughter + card + + To compile this driver as a module, choose M here: the module will be + called c6xdigio. + +config COMEDI_MPC624 + tristate "Micro/sys MPC-624 PC/104 board support" + default N + ---help--- + Enable support for Micro/sys MPC-624 PC/104 board + + To compile this driver as a module, choose M here: the module will be + called mpc624. + +config COMEDI_ADQ12B + tristate "MicroAxial ADQ12-B data acquisition and control card support" + default N + ---help--- + Enable MicroAxial ADQ12-B daq and control card support. + + To compile this driver as a module, choose M here: the module will be + called adq12b. + +config COMEDI_NI_AT_A2150 + tristate "NI AT-A2150 ISA card support" + depends on COMEDI_NI_COMMON + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for National Instruments AT-A2150 cards + + To compile this driver as a module, choose M here: the module will be + called ni_at_a2150. + +config COMEDI_NI_AT_AO + tristate "NI AT-AO-6/10 EISA card support" + depends on COMEDI_NI_COMMON + default N + ---help--- + Enable support for National Instruments AT-AO-6/10 cards + + To compile this driver as a module, choose M here: the module will be + called ni_at_ao. + +config COMEDI_NI_ATMIO + tristate "NI AT-MIO E series ISA-PNP card support" + depends on ISAPNP && COMEDI_NI_TIO && COMEDI_NI_COMMON + select COMEDI_8255 + default N + ---help--- + Enable support for National Instruments AT-MIO E series cards + National Instruments AT-MIO-16E-1 (ni_atmio), + AT-MIO-16E-2, AT-MIO-16E-10, AT-MIO-16DE-10, AT-MIO-64E-3, + AT-MIO-16XE-50, AT-MIO-16XE-10, AT-AI-16XE-10 + + To compile this driver as a module, choose M here: the module will be + called ni_atmio. + +config COMEDI_NI_ATMIO16D + tristate "NI AT-MIO16/AT-MIO16D series ISA-PNP card support" + depends on ISAPNP && COMEDI_NI_COMMON + select COMEDI_8255 + default N + ---help--- + Enable support for National Instruments AT-MIO16/AT-MIO16D cards. + + To compile this driver as a module, choose M here: the module will be + called ni_atmio16d. + +config COMEDI_PCMAD + tristate "Winsystems PCM-A/D12 and PCM-A/D16 PC/104 board support" + default N + ---help--- + Enable support for Winsystems PCM-A/D12 and PCM-A/D16 PC/104 boards. + + To compile this driver as a module, choose M here: the module will be + called pcmad. + +config COMEDI_PCMDA12 + tristate "Winsystems PCM-D/A-12 8-channel AO PC/104 board support" + default N + ---help--- + Enable support for Winsystems PCM-D/A-12 8-channel AO PC/104 boards. + Note that the board is not ISA-PNP capable and thus needs the I/O + port comedi_config parameter. + + To compile this driver as a module, choose M here: the module will be + called pcmda12. + +config COMEDI_PCMMIO + tristate "Winsystems PCM-MIO PC/104 board support" + default N + ---help--- + Enable support for Winsystems PCM-MIO multifunction PC/104 boards. + + To compile this driver as a module, choose M here: the module will be + called pcmmio. + +config COMEDI_PCMUIO + tristate "Winsystems PCM-UIO48A and PCM-UIO96A PC/104 board support" + default N + ---help--- + Enable support for PCM-UIO48A and PCM-UIO96A PC/104 boards. + + To compile this driver as a module, choose M here: the module will be + called pcmuio. + +config COMEDI_MULTIQ3 + tristate "Quanser Consulting MultiQ-3 ISA card support" + default N + ---help--- + Enable support for Quanser Consulting MultiQ-3 ISA cards + + To compile this driver as a module, choose M here: the module will be + called multiq3. + +config COMEDI_POC + tristate "Generic driver for very simple devices" + default N + ---help--- + Enable generic support for very simple / POC (Piece of Crap) boards, + Keithley Metrabyte DAC-02 (dac02), Advantech PCL-733 (pcl733) and + PCL-734 (pcl734) + + To compile this driver as a module, choose M here: the module will be + called poc. + +endif # COMEDI_ISA_DRIVERS + +menuconfig COMEDI_PCI_DRIVERS + tristate "Comedi PCI drivers" + depends on COMEDI && PCI + default N + ---help--- + Enable comedi PCI drivers to be built + + Note that the answer to this question won't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about PCI comedi drivers. + +if COMEDI_PCI_DRIVERS && PCI + +config COMEDI_ADDI_APCI_035 + tristate "ADDI-DATA APCI_035 support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_035 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_035. + +config COMEDI_ADDI_APCI_1032 + tristate "ADDI-DATA APCI_1032 support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_1032 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_1032. + +config COMEDI_ADDI_APCI_1500 + tristate "ADDI-DATA APCI_1500 support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_1500 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_1500. + +config COMEDI_ADDI_APCI_1516 + tristate "ADDI-DATA APCI_1516 support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_1516 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_1516. + +config COMEDI_ADDI_APCI_1564 + tristate "ADDI-DATA APCI_1564 support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_1564 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_1564. + +config COMEDI_ADDI_APCI_16XX + tristate "ADDI-DATA APCI_16xx support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_16xx cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_16xx. + +config COMEDI_ADDI_APCI_2016 + tristate "ADDI-DATA APCI_2016 support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_2016 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_2016. + +config COMEDI_ADDI_APCI_2032 + tristate "ADDI-DATA APCI_2032 support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_2032 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_2032. + +config COMEDI_ADDI_APCI_2200 + tristate "ADDI-DATA APCI_2200 support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_2200 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_2200. + +config COMEDI_ADDI_APCI_3001 + tristate "ADDI-DATA APCI_3001 support" + depends on VIRT_TO_BUS + select COMEDI_FC + default N + ---help--- + Enable support for ADDI-DATA APCI_3001 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_3001. + +config COMEDI_ADDI_APCI_3120 + tristate "ADDI-DATA APCI_3520 support" + depends on VIRT_TO_BUS + select COMEDI_FC + default N + ---help--- + Enable support for ADDI-DATA APCI_3520 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_3120. + +config COMEDI_ADDI_APCI_3501 + tristate "ADDI-DATA APCI_3501 support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_3501 cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_3501. + +config COMEDI_ADDI_APCI_3XXX + tristate "ADDI-DATA APCI_3xxx support" + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADDI-DATA APCI_3xxx cards + + To compile this driver as a module, choose M here: the module will be + called addi_apci_3xxx. + +config COMEDI_ADL_PCI6208 + tristate "ADLink PCI-6208A support" + select COMEDI_8255 + default N + ---help--- + Enable support for ADLink PCI-6208A cards + + To compile this driver as a module, choose M here: the module will be + called adl_pci6208. + +config COMEDI_ADL_PCI7230 + tristate "ADLink PCI-7230 digital io board support" + default N + ---help--- + Enable support for ADlink PCI-7230 digital io board support + + To compile this driver as a module, choose M here: the module will be + called adl_pci7230. + +config COMEDI_ADL_PCI7296 + tristate "ADLink PCI-7296 96 ch. digital io board support" + default N + ---help--- + Enable support for ADlink PCI-7296 96 ch. digital io board support + + To compile this driver as a module, choose M here: the module will be + called adl_pci7296. + +config COMEDI_ADL_PCI7432 + tristate "ADLink PCI-7432 64 ch. isolated digital io board support" + default N + ---help--- + Enable support for ADlink PCI-7432 64 ch. isolated digital io board + + To compile this driver as a module, choose M here: the module will be + called adl_pci7432. + +config COMEDI_ADL_PCI8164 + tristate "ADLink PCI-8164 4 Axes Motion Control board support" + default N + ---help--- + Enable support for ADlink PCI-8164 4 Axes Motion Control board + + To compile this driver as a module, choose M here: the module will be + called adl_pci8164. + +config COMEDI_ADL_PCI9111 + tristate "ADLink PCI-9111HR support" + select COMEDI_FC + default N + ---help--- + Enable support for ADlink PCI9111 cards + + To compile this driver as a module, choose M here: the module will be + called adl_pci9111. + +config COMEDI_ADL_PCI9118 + tristate "ADLink PCI-9118DG, PCI-9118HG, PCI-9118HR support" + select COMEDI_FC + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for ADlink PCI-9118DG, PCI-9118HG, PCI-9118HR cards + + To compile this driver as a module, choose M here: the module will be + called adl_pci9118. + +config COMEDI_ADV_PCI1710 + tristate "Advantech PCI-171x, PCI-1720 and PCI-1731 support" + default N + ---help--- + Enable support for Advantech PCI-1710, PCI-1710HG, PCI-1711, + PCI-1713, PCI-1720 and PCI-1731 + + To compile this driver as a module, choose M here: the module will be + called adv_pci1710. + +config COMEDI_ADV_PCI1723 + tristate "Advantech PCI-1723 support" + default N + ---help--- + Enable support for Advantech PCI-1723 cards + + To compile this driver as a module, choose M here: the module will be + called adv_pci1723. + +config COMEDI_ADV_PCI_DIO + tristate "Advantech PCI DIO card support" + select COMEDI_8255 + default N + ---help--- + Enable support for Advantech PCI DIO cards + PCI-1730, PCI-1733, PCI-1734, PCI-1735U, PCI-1736UP, PCI-1739U, + PCI-1750, PCI-1751, PCI-1752, PCI-1753/E, PCI-1754, PCI-1756, + PCI-1760 and PCI-1762 + + To compile this driver as a module, choose M here: the module will be + called adv_pci_dio. + +config COMEDI_AMPLC_DIO200 + tristate "Amplicon PC272E and PCI272 DIO board support" + select COMEDI_8255 + default N + ---help--- + Enable support for Amplicon PC272E and PCI272 DIO boards + + To compile this driver as a module, choose M here: the module will be + called amplc_dio200. + +config COMEDI_AMPLC_PC236 + tristate "Amplicon PC36AT and PCI236 DIO board support" + select COMEDI_8255 + default N + ---help--- + Enable support for Amplicon PC36AT and PCI236 DIO boards + + To compile this driver as a module, choose M here: the module will be + called amplc_pc236. + +config COMEDI_AMPLC_PC263 + tristate "Amplicon PC263 and PCI263 relay board support" + default N + ---help--- + Enable support for Amplicon PC263 and PCI263 relay boards + + To compile this driver as a module, choose M here: the module will be + called amplc_pc263. + +config COMEDI_AMPLC_PCI224 + tristate "Amplicon PCI224 and PCI234 support" + select COMEDI_FC + default N + ---help--- + Enable support for Amplicon PCI224 and PCI234 AO boards + + To compile this driver as a module, choose M here: the module will be + called amplc_pci224. + +config COMEDI_AMPLC_PCI230 + tristate "Amplicon PCI230 and PCI260 support" + select COMEDI_8255 + default N + ---help--- + Enable support for Amplicon PCI230 and PCI260 Multifunction I/O + boards + + To compile this driver as a module, choose M here: the module will be + called amplc_pci230. + +config COMEDI_CONTEC_PCI_DIO + tristate "Contec PIO1616L digital I/O board support" + default N + ---help--- + Enable support for the Contec PIO1616L digital I/O board + + To compile this driver as a module, choose M here: the module will be + called contec_pci_dio. + +config COMEDI_DT3000 + tristate "Data Translation DT3000 series support" + default N + ---help--- + Enable support for Data Translation DT3000 series + DT3001, DT3001-PGL, DT3002, DT3003, DT3003-PGL, DT3004, DT3005 and + DT3004-200 + + To compile this driver as a module, choose M here: the module will be + called dt3000. + +config COMEDI_DYNA_PCI10XX + tristate "Dynalog PCI DAQ series support" + default N + ---help--- + Enable support for Dynalog PCI DAQ series + PCI-1050 + + To compile this driver as a module, choose M here: the module will be + called dyna_pci10xx. + +config COMEDI_UNIOXX5 + tristate "Fastwel UNIOxx-5 analog and digital io board support" + default N + ---help--- + Enable support for Fastwel UNIOxx-5 (analog and digital i/o) boards + + To compile this driver as a module, choose M here: the module will be + called unioxx5. + +config COMEDI_GSC_HPDI + tristate "General Standards PCI-HPDI32 / PMC-HPDI32 support" + select COMEDI_FC + default N + ---help--- + Enable support for General Standards Corporation high speed parallel + digital interface rs485 boards PCI-HPDI32 and PMC-HPDI32. + Only receive mode works, transmit not supported. + + To compile this driver as a module, choose M here: the module will be + called gsc_hpdi. + +config COMEDI_ICP_MULTI + tristate "Inova ICP_MULTI support" + default N + ---help--- + Enable support for Inova ICP_MULTI card + + To compile this driver as a module, choose M here: the module will be + called icp_multi. + +config COMEDI_II_PCI20KC + tristate "Intelligent Instruments PCI-20001C carrier support" + default N + ---help--- + Enable support for Intelligent Instruments PCI-20001C carrier + PCI-20001, PCI-20006 and PCI-20341 + + To compile this driver as a module, choose M here: the module will be + called ii_pci20kc. + +config COMEDI_DAQBOARD2000 + tristate "IOtech DAQboard/2000 support" + select COMEDI_8255 + default N + ---help--- + Enable support for the IOtech DAQboard/2000 + + To compile this driver as a module, choose M here: the module will be + called daqboard2000. + +config COMEDI_JR3_PCI + tristate "JR3/PCI force sensor board support" + default N + ---help--- + Enable support for JR3/PCI force sensor boards + + To compile this driver as a module, choose M here: the module will be + called jr3_pci. + +config COMEDI_KE_COUNTER + tristate "Kolter-Electronic PCI Counter 1 card support" + default N + ---help--- + Enable support for Kolter-Electronic PCI Counter 1 cards + + To compile this driver as a module, choose M here: the module will be + called ke_counter. + +config COMEDI_CB_PCIDAS64 + tristate "MeasurementComputing PCI-DAS 64xx, 60xx, and 4020 support" + select COMEDI_8255 + select COMEDI_FC + default N + ---help--- + Enable support for ComputerBoards/MeasurementComputing PCI-DAS 64xx, + 60xx, and 4020 series with the PLX 9080 PCI controller + + To compile this driver as a module, choose M here: the module will be + called cb_pcidas64. + +config COMEDI_CB_PCIDAS + tristate "MeasurementComputing PCI-DAS support" + select COMEDI_8255 + select COMEDI_FC + default N + ---help--- + Enable support for ComputerBoards/MeasurementComputing PCI-DAS with + AMCC S5933 PCIcontroller: PCI-DAS1602/16, PCI-DAS1602/16jr, + PCI-DAS1602/12, PCI-DAS1200, PCI-DAS1200jr, PCI-DAS1000, PCI-DAS1001 + and PCI_DAS1002. + + To compile this driver as a module, choose M here: the module will be + called cb_pcidas. + +config COMEDI_CB_PCIDDA + tristate "MeasurementComputing PCI-DDA series support" + select COMEDI_8255 + default N + ---help--- + Enable support for ComputerBoards/MeasurementComputing PCI-DDA + series: PCI-DDA08/12, PCI-DDA04/12, PCI-DDA02/12, PCI-DDA08/16, + PCI-DDA04/16 and PCI-DDA02/16 + + To compile this driver as a module, choose M here: the module will be + called cb_pcidda. + +config COMEDI_CB_PCIDIO + tristate "MeasurementComputing PCI-DIO series support" + select COMEDI_8255 + default N + ---help--- + Enable support for ComputerBoards/MeasurementComputing PCI-DIO series + PCI-DIO24, PCI-DIO24H and PCI-DIO48H + + To compile this driver as a module, choose M here: the module will be + called cb_pcidio. + +config COMEDI_CB_PCIMDAS + tristate "MeasurementComputing PCIM-DAS1602/16 support" + select COMEDI_8255 + default N + ---help--- + Enable support for ComputerBoards/MeasurementComputing PCI Migration + series PCIM-DAS1602/16 + + To compile this driver as a module, choose M here: the module will be + called cb_pcimdas. + +config COMEDI_CB_PCIMDDA + tristate "MeasurementComputing PCIM-DDA06-16 support" + select COMEDI_8255 + default N + ---help--- + Enable support for ComputerBoards/MeasurementComputing PCIM-DDA06-16 + + To compile this driver as a module, choose M here: the module will be + called cb_pcimdda. + +config COMEDI_ME4000 + tristate "Meilhaus ME-4000 support" + default N + ---help--- + Enable support for Meilhaus PCI data acquisition cards + ME-4650, ME-4670i, ME-4680, ME-4680i and ME-4680is + + To compile this driver as a module, choose M here: the module will be + called me4000. + +config COMEDI_ME_DAQ + tristate "Meilhaus ME-2000i, ME-2600i, ME-3000vm1 support" + default N + ---help--- + Enable support for Meilhaus PCI data acquisition cards + ME-2000i, ME-2600i and ME-3000vm1 + + To compile this driver as a module, choose M here: the module will be + called me_daq. + +config COMEDI_NI_6527 + tristate "NI 6527 support" + depends on COMEDI_MITE + default N + ---help--- + Enable support for the National Instruments 6527 PCI card + + To compile this driver as a module, choose M here: the module will be + called ni_6527. + +config COMEDI_NI_65XX + tristate "NI 65xx static dio PCI card support" + depends on COMEDI_MITE + default N + ---help--- + Enable support for National Instruments 65xx static dio boards. + Supported devices: National Instruments PCI-6509 (ni_65xx), + PXI-6509, PCI-6510, PCI-6511, PXI-6511, PCI-6512, PXI-6512, PCI-6513, + PXI-6513, PCI-6514, PXI-6514, PCI-6515, PXI-6515, PCI-6516, PCI-6517, + PCI-6518, PCI-6519, PCI-6520, PCI-6521, PXI-6521, PCI-6528, PXI-6528 + + To compile this driver as a module, choose M here: the module will be + called ni_65xx. + +config COMEDI_NI_660X + tristate "NI 660x counter/timer PCI card support" + depends on COMEDI_NI_TIO && COMEDI_NI_COMMON + default N + ---help--- + Enable support for National Instruments PCI-6601 (ni_660x), PCI-6602, + PXI-6602 and PXI-6608. + + To compile this driver as a module, choose M here: the module will be + called ni_660x. + +config COMEDI_NI_670X + tristate "NI 670x PCI card support" + depends on COMEDI_MITE + default N + ---help--- + Enable support for National Instruments PCI-6703 and PCI-6704 + + To compile this driver as a module, choose M here: the module will be + called ni_670x. + +config COMEDI_NI_PCIDIO + tristate "NI PCI-DIO32HS, PCI-DIO96, PCI-6533, PCI-6503 support" + depends on COMEDI_MITE + select COMEDI_8255 + default N + ---help--- + Enable support for National Instruments PCI-DIO-32HS, PXI-6533, + PCI-DIO-96, PCI-DIO-96B, PXI-6508, PCI-6503, PCI-6503B, PCI-6503X, + PXI-6503, PCI-6533 and PCI-6534 + The DIO-96 appears as four 8255 subdevices. See the 8255 + driver notes for details. + + To compile this driver as a module, choose M here: the module will be + called ni_pcidio. + +config COMEDI_NI_PCIMIO + tristate "NI PCI-MIO-E series and M series support" + depends on COMEDI_NI_TIO && COMEDI_NI_COMMON + select COMEDI_8255 + select COMEDI_FC + default N + ---help--- + Enable support for National Instruments PCI-MIO-E series and M series + (all boards): PCI-MIO-16XE-10, PXI-6030E, PCI-MIO-16E-1, + PCI-MIO-16E-4, PCI-6014, PCI-6040E, PXI-6040E, PCI-6030E, PCI-6031E, + PCI-6032E, PCI-6033E, PCI-6071E, PCI-6023E, PCI-6024E, PCI-6025E, + PXI-6025E, PCI-6034E, PCI-6035E, PCI-6052E, PCI-6110, PCI-6111, + PCI-6220, PCI-6221, PCI-6224, PXI-6224, PCI-6225, PXI-6225, PCI-6229, + PCI-6250, PCI-6251, PCIe-6251, PCI-6254, PCI-6259, PCIe-6259, + PCI-6280, PCI-6281, PXI-6281, PCI-6284, PCI-6289, PCI-6711, PXI-6711, + PCI-6713, PXI-6713, PXI-6071E, PCI-6070E, PXI-6070E, PXI-6052E, + PCI-6036E, PCI-6731, PCI-6733, PXI-6733, PCI-6143, PXI-6143 + + To compile this driver as a module, choose M here: the module will be + called ni_pcimio. + +config COMEDI_RTD520 + tristate "Real Time Devices PCI4520/DM7520 support" + select COMEDI_8255 + default N + ---help--- + Enable support for Real Time Devices PCI4520/DM7520 + + To compile this driver as a module, choose M here: the module will be + called rtd520. + +config COMEDI_S526 + tristate "Sensoray s526 support" + default N + ---help--- + Enable support for Sensoray s526 + + To compile this driver as a module, choose M here: the module will be + called s526. + +config COMEDI_S626 + tristate "Sensoray 626 support" + select COMEDI_FC + default N + ---help--- + Enable support for Sensoray 626 + + To compile this driver as a module, choose M here: the module will be + called s626. + +config COMEDI_SSV_DNP + tristate "SSV Embedded Systems DIL/Net-PC support" + default N + ---help--- + Enable support for SSV Embedded Systems DIL/Net-PC + + To compile this driver as a module, choose M here: the module will be + called ssv_dnp. + +endif # COMEDI_PCI_DRIVERS + +menuconfig COMEDI_PCMCIA_DRIVERS + tristate "Comedi PCMCIA drivers" + depends on COMEDI && (PCMCIA || PCCARD) + default N + ---help--- + Enable comedi PCMCIA and PCCARD drivers to be built + + Note that the answer to this question won't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about PCMCIA comedi drivers. + +if COMEDI_PCMCIA_DRIVERS && PCMCIA + +config COMEDI_CB_DAS16_CS + tristate "CB DAS16 series PCMCIA support" + default N + ---help--- + Enable support for the ComputerBoards/MeasurementComputing PCMCIA + cards DAS16/16, PCM-DAS16D/12 and PCM-DAS16s/16 + + To compile this driver as a module, choose M here: the module will be + called cb_das16_cs. + +config COMEDI_DAS08_CS + tristate "CB DAS08 PCMCIA support" + select COMEDI_DAS08 + default N + ---help--- + Enable support for the ComputerBoards/MeasurementComputing DAS-08 + PCMCIA card + + To compile this driver as a module, choose M here: the module will be + called das08_cs. + +config COMEDI_NI_DAQ_700_CS + tristate "NI DAQCard-700 PCMCIA support" + depends on COMEDI_NI_COMMON + default N + ---help--- + Enable support for the National Instruments PCMCIA DAQCard-700 DIO + + To compile this driver as a module, choose M here: the module will be + called ni_daq_700. + +config COMEDI_NI_DAQ_DIO24_CS + tristate "NI DAQ-Card DIO-24 PCMCIA support" + depends on COMEDI_NI_COMMON + select COMEDI_8255 + default N + ---help--- + Enable support for the National Instruments PCMCIA DAQ-Card DIO-24 + + To compile this driver as a module, choose M here: the module will be + called ni_daq_dio24. + +config COMEDI_NI_LABPC_CS + tristate "NI DAQCard-1200 PCMCIA support" + depends on COMEDI_NI_LABPC + default N + ---help--- + Enable support for the National Instruments PCMCIA DAQCard-1200 + + To compile this driver as a module, choose M here: the module will be + called ni_labpc_cs. + +config COMEDI_NI_MIO_CS + tristate "NI DAQCard E series PCMCIA support" + depends on COMEDI_NI_TIO && COMEDI_NI_COMMON + select COMEDI_8255 + select COMEDI_FC + default N + ---help--- + Enable support for the National Instruments PCMCIA DAQCard E series + DAQCard-ai-16xe-50, DAQCard-ai-16e-4, DAQCard-6062E, DAQCard-6024E + and DAQCard-6036E + + To compile this driver as a module, choose M here: the module will be + called ni_mio_cs. + +config COMEDI_QUATECH_DAQP_CS + tristate "Quatech DAQP PCMCIA data capture card support" + default N + ---help--- + Enable support for the Quatech DAQP PCMCIA data capture cards + DAQP-208 and DAQP-308 + + To compile this driver as a module, choose M here: the module will be + called quatech_daqp_cs. + +endif # COMEDI_PCMCIA_DRIVERS + +menuconfig COMEDI_USB_DRIVERS + tristate "Comedi USB drivers" + depends on COMEDI && USB + default N + ---help--- + Enable comedi USB drivers to be built + + Note that the answer to this question won't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about USB comedi drivers. + +if COMEDI_USB_DRIVERS && USB + +config COMEDI_DT9812 + tristate "DataTranslation DT9812 USB module support" + default N + ---help--- + Enable support for the Data Translation DT9812 USB module + + To compile this driver as a module, choose M here: the module will be + called dt9812. + +config COMEDI_USBDUX + tristate "ITL USB-DUX-D support" + default N + ---help--- + Enable support for the Incite Technology Ltd USB-DUX-D Board + + To compile this driver as a module, choose M here: the module will be + called usbdux. + +config COMEDI_USBDUXFAST + tristate "ITL USB-DUXfast support" + select COMEDI_FC + default N + ---help--- + Enable support for the Incite Technology Ltd USB-DUXfast Board + + To compile this driver as a module, choose M here: the module will be + called usbduxfast. + +config COMEDI_USBDUXSIGMA + tristate "ITL USB-DUXsigma support" + select COMEDI_FC + default N + ---help--- + Enable support for the Incite Technology Ltd USB-DUXsigma Board + + To compile this driver as a module, choose M here: the module will be + called usbduxsigma. + +config COMEDI_VMK80XX + tristate "Velleman VM110/VM140 USB Board support" + default N + ---help--- + Build the Velleman USB Board Low-Level Driver supporting the + K8055/K8061 aka VM110/VM140 devices + + To compile this driver as a module, choose M here: the module will be + called vmk80xx. + +endif # COMEDI_USB_DRIVERS + +menuconfig COMEDI_NI_COMMON + tristate "Comedi National Instruments card support" + depends on COMEDI + default N + ---help--- + Enable comedi support for National Instruments cards. + Modules in this section are used by many comedi NI drivers. + + Note that the answer to this question won't directly affect the + kernel: saying N will just cause the configurator to skip all + the questions about National Instruments cards. + +if COMEDI_NI_COMMON + +config COMEDI_MITE + tristate "NI Mite PCI interface chip support" + depends on PCI + default N + ---help--- + Enable support for National Instruments Mite PCI interface chip + + To compile this driver as a module, choose M here: the module will be + called mite. + +config COMEDI_NI_TIO + tristate "NI general purpose counter support" + depends on COMEDI_MITE + default N + ---help--- + Enable support for National Instruments general purpose counters. + This module is not used directly by end-users. Rather, it + is used by other drivers (for example ni_660x and ni_pcimio) + to provide support for NI's general purpose counters. + + To compile this driver as a modules, choose M here: two modules will + be build: ni_tio and ni_tiocmd. + +config COMEDI_NI_LABPC + tristate "NI Lab-PC and compatibles ISA and PCI support" + depends on COMEDI_MITE + select COMEDI_8255 + select COMEDI_FC + depends on VIRT_TO_BUS + default N + ---help--- + Enable support for National Instruments Lab-PC and compatibles + Lab-PC-1200, Lab-PC-1200AI, Lab-PC+ and PCI-1200. + Kernel-level ISA plug-and-play support for the lab-pc-1200 boards has + not yet been added to the driver. + + To compile this driver as a module, choose M here: the module will be + called ni_labpc. + +endif # COMEDI_NI_COMMON + +config COMEDI_8255 + tristate "Generic 8255 support" + depends on COMEDI + default N + ---help--- + Enable generic 8255 support. + + You should enable compilation this driver if you plan to use a board + that has an 8255 chip. For multifunction boards, the main driver will + configure the 8255 subdevice automatically. + + Note that most PCI 8255 boards do NOT work with this driver, and + need a separate driver as a wrapper. + + To compile this driver as a module, choose M here: the module will be + called 8255. + +config COMEDI_DAS08 + tristate "DAS-08 compatible support" + depends on COMEDI + select COMEDI_8255 + default N + ---help--- + Enable support for DAS08 and compatible ISA, PC/104 and PCI cards. + + Note that PCMCIA DAS08 cards are not directly supported by this + driver, and need a separate driver as a wrapper. + + To compile this driver as a module, choose M here: the module will be + called das08. + +config COMEDI_FC + tristate "Comedi shared functions for low-level driver support" + depends on COMEDI + default N + ---help--- + Enable support for shared functions for low-level drivers. + This module is not used directly by end-users. Rather, it + is used by many other comedi drivers. + + To compile this driver as a module, choose M here: the module will be + called comedi_fc. diff --git a/drivers/staging/comedi/Makefile b/drivers/staging/comedi/Makefile new file mode 100644 index 00000000..8dbd306f --- /dev/null +++ b/drivers/staging/comedi/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_COMEDI) += comedi.o + +obj-$(CONFIG_COMEDI) += kcomedilib/ +obj-$(CONFIG_COMEDI) += drivers/ + +comedi-y := \ + comedi_fops.o \ + proc.o \ + range.o \ + drivers.o \ + comedi_compat32.o \ diff --git a/drivers/staging/comedi/TODO b/drivers/staging/comedi/TODO new file mode 100644 index 00000000..b10f739b --- /dev/null +++ b/drivers/staging/comedi/TODO @@ -0,0 +1,12 @@ +TODO: + - checkpatch.pl cleanups + - Lindent + - remove all wrappers + - audit userspace interface + - reserve major number + - cleanup the individual comedi drivers as well + +Please send patches to Greg Kroah-Hartman and +copy: + Ian Abbott + Frank Mori Hess diff --git a/drivers/staging/comedi/comedi.h b/drivers/staging/comedi/comedi.h new file mode 100644 index 00000000..14ea35ac --- /dev/null +++ b/drivers/staging/comedi/comedi.h @@ -0,0 +1,914 @@ +/* + include/comedi.h (installed as /usr/include/comedi.h) + header file for comedi + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998-2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _COMEDI_H +#define _COMEDI_H + +#define COMEDI_MAJORVERSION 0 +#define COMEDI_MINORVERSION 7 +#define COMEDI_MICROVERSION 76 +#define VERSION "0.7.76" + +/* comedi's major device number */ +#define COMEDI_MAJOR 98 + +/* + maximum number of minor devices. This can be increased, although + kernel structures are currently statically allocated, thus you + don't want this to be much more than you actually use. + */ +#define COMEDI_NDEVICES 16 + +/* number of config options in the config structure */ +#define COMEDI_NDEVCONFOPTS 32 +/*length of nth chunk of firmware data*/ +#define COMEDI_DEVCONF_AUX_DATA3_LENGTH 25 +#define COMEDI_DEVCONF_AUX_DATA2_LENGTH 26 +#define COMEDI_DEVCONF_AUX_DATA1_LENGTH 27 +#define COMEDI_DEVCONF_AUX_DATA0_LENGTH 28 +/* most significant 32 bits of pointer address (if needed) */ +#define COMEDI_DEVCONF_AUX_DATA_HI 29 +/* least significant 32 bits of pointer address */ +#define COMEDI_DEVCONF_AUX_DATA_LO 30 +#define COMEDI_DEVCONF_AUX_DATA_LENGTH 31 /* total data length */ + +/* max length of device and driver names */ +#define COMEDI_NAMELEN 20 + +/* packs and unpacks a channel/range number */ + +#define CR_PACK(chan, rng, aref) \ + ((((aref)&0x3)<<24) | (((rng)&0xff)<<16) | (chan)) +#define CR_PACK_FLAGS(chan, range, aref, flags) \ + (CR_PACK(chan, range, aref) | ((flags) & CR_FLAGS_MASK)) + +#define CR_CHAN(a) ((a)&0xffff) +#define CR_RANGE(a) (((a)>>16)&0xff) +#define CR_AREF(a) (((a)>>24)&0x03) + +#define CR_FLAGS_MASK 0xfc000000 +#define CR_ALT_FILTER (1<<26) +#define CR_DITHER CR_ALT_FILTER +#define CR_DEGLITCH CR_ALT_FILTER +#define CR_ALT_SOURCE (1<<27) +#define CR_EDGE (1<<30) +#define CR_INVERT (1<<31) + +#define AREF_GROUND 0x00 /* analog ref = analog ground */ +#define AREF_COMMON 0x01 /* analog ref = analog common */ +#define AREF_DIFF 0x02 /* analog ref = differential */ +#define AREF_OTHER 0x03 /* analog ref = other (undefined) */ + +/* counters -- these are arbitrary values */ +#define GPCT_RESET 0x0001 +#define GPCT_SET_SOURCE 0x0002 +#define GPCT_SET_GATE 0x0004 +#define GPCT_SET_DIRECTION 0x0008 +#define GPCT_SET_OPERATION 0x0010 +#define GPCT_ARM 0x0020 +#define GPCT_DISARM 0x0040 +#define GPCT_GET_INT_CLK_FRQ 0x0080 + +#define GPCT_INT_CLOCK 0x0001 +#define GPCT_EXT_PIN 0x0002 +#define GPCT_NO_GATE 0x0004 +#define GPCT_UP 0x0008 +#define GPCT_DOWN 0x0010 +#define GPCT_HWUD 0x0020 +#define GPCT_SIMPLE_EVENT 0x0040 +#define GPCT_SINGLE_PERIOD 0x0080 +#define GPCT_SINGLE_PW 0x0100 +#define GPCT_CONT_PULSE_OUT 0x0200 +#define GPCT_SINGLE_PULSE_OUT 0x0400 + +/* instructions */ + +#define INSN_MASK_WRITE 0x8000000 +#define INSN_MASK_READ 0x4000000 +#define INSN_MASK_SPECIAL 0x2000000 + +#define INSN_READ (0 | INSN_MASK_READ) +#define INSN_WRITE (1 | INSN_MASK_WRITE) +#define INSN_BITS (2 | INSN_MASK_READ|INSN_MASK_WRITE) +#define INSN_CONFIG (3 | INSN_MASK_READ|INSN_MASK_WRITE) +#define INSN_GTOD (4 | INSN_MASK_READ|INSN_MASK_SPECIAL) +#define INSN_WAIT (5 | INSN_MASK_WRITE|INSN_MASK_SPECIAL) +#define INSN_INTTRIG (6 | INSN_MASK_WRITE|INSN_MASK_SPECIAL) + +/* trigger flags */ +/* These flags are used in comedi_trig structures */ + +#define TRIG_BOGUS 0x0001 /* do the motions */ +#define TRIG_DITHER 0x0002 /* enable dithering */ +#define TRIG_DEGLITCH 0x0004 /* enable deglitching */ + /*#define TRIG_RT 0x0008 *//* perform op in real time */ +#define TRIG_CONFIG 0x0010 /* perform configuration, not triggering */ +#define TRIG_WAKE_EOS 0x0020 /* wake up on end-of-scan events */ + /*#define TRIG_WRITE 0x0040*//* write to bidirectional devices */ + +/* command flags */ +/* These flags are used in comedi_cmd structures */ + +/* try to use a real-time interrupt while performing command */ +#define CMDF_PRIORITY 0x00000008 + +#define TRIG_RT CMDF_PRIORITY /* compatibility definition */ + +#define CMDF_WRITE 0x00000040 +#define TRIG_WRITE CMDF_WRITE /* compatibility definition */ + +#define CMDF_RAWDATA 0x00000080 + +#define COMEDI_EV_START 0x00040000 +#define COMEDI_EV_SCAN_BEGIN 0x00080000 +#define COMEDI_EV_CONVERT 0x00100000 +#define COMEDI_EV_SCAN_END 0x00200000 +#define COMEDI_EV_STOP 0x00400000 + +#define TRIG_ROUND_MASK 0x00030000 +#define TRIG_ROUND_NEAREST 0x00000000 +#define TRIG_ROUND_DOWN 0x00010000 +#define TRIG_ROUND_UP 0x00020000 +#define TRIG_ROUND_UP_NEXT 0x00030000 + +/* trigger sources */ + +#define TRIG_ANY 0xffffffff +#define TRIG_INVALID 0x00000000 + +#define TRIG_NONE 0x00000001 /* never trigger */ +#define TRIG_NOW 0x00000002 /* trigger now + N ns */ +#define TRIG_FOLLOW 0x00000004 /* trigger on next lower level trig */ +#define TRIG_TIME 0x00000008 /* trigger at time N ns */ +#define TRIG_TIMER 0x00000010 /* trigger at rate N ns */ +#define TRIG_COUNT 0x00000020 /* trigger when count reaches N */ +#define TRIG_EXT 0x00000040 /* trigger on external signal N */ +#define TRIG_INT 0x00000080 /* trigger on comedi-internal signal N */ +#define TRIG_OTHER 0x00000100 /* driver defined */ + +/* subdevice flags */ + +#define SDF_BUSY 0x0001 /* device is busy */ +#define SDF_BUSY_OWNER 0x0002 /* device is busy with your job */ +#define SDF_LOCKED 0x0004 /* subdevice is locked */ +#define SDF_LOCK_OWNER 0x0008 /* you own lock */ +#define SDF_MAXDATA 0x0010 /* maxdata depends on channel */ +#define SDF_FLAGS 0x0020 /* flags depend on channel */ +#define SDF_RANGETYPE 0x0040 /* range type depends on channel */ +#define SDF_MODE0 0x0080 /* can do mode 0 */ +#define SDF_MODE1 0x0100 /* can do mode 1 */ +#define SDF_MODE2 0x0200 /* can do mode 2 */ +#define SDF_MODE3 0x0400 /* can do mode 3 */ +#define SDF_MODE4 0x0800 /* can do mode 4 */ +#define SDF_CMD 0x1000 /* can do commands (deprecated) */ +#define SDF_SOFT_CALIBRATED 0x2000 /* subdevice uses software calibration */ +#define SDF_CMD_WRITE 0x4000 /* can do output commands */ +#define SDF_CMD_READ 0x8000 /* can do input commands */ + +/* subdevice can be read (e.g. analog input) */ +#define SDF_READABLE 0x00010000 +/* subdevice can be written (e.g. analog output) */ +#define SDF_WRITABLE 0x00020000 +#define SDF_WRITEABLE SDF_WRITABLE /* spelling error in API */ +/* subdevice does not have externally visible lines */ +#define SDF_INTERNAL 0x00040000 +#define SDF_GROUND 0x00100000 /* can do aref=ground */ +#define SDF_COMMON 0x00200000 /* can do aref=common */ +#define SDF_DIFF 0x00400000 /* can do aref=diff */ +#define SDF_OTHER 0x00800000 /* can do aref=other */ +#define SDF_DITHER 0x01000000 /* can do dithering */ +#define SDF_DEGLITCH 0x02000000 /* can do deglitching */ +#define SDF_MMAP 0x04000000 /* can do mmap() */ +#define SDF_RUNNING 0x08000000 /* subdevice is acquiring data */ +#define SDF_LSAMPL 0x10000000 /* subdevice uses 32-bit samples */ +#define SDF_PACKED 0x20000000 /* subdevice can do packed DIO */ +/* re recyle these flags for PWM */ +#define SDF_PWM_COUNTER SDF_MODE0 /* PWM can automatically switch off */ +#define SDF_PWM_HBRIDGE SDF_MODE1 /* PWM is signed (H-bridge) */ + +/* subdevice types */ + + enum comedi_subdevice_type { + COMEDI_SUBD_UNUSED, /* unused by driver */ + COMEDI_SUBD_AI, /* analog input */ + COMEDI_SUBD_AO, /* analog output */ + COMEDI_SUBD_DI, /* digital input */ + COMEDI_SUBD_DO, /* digital output */ + COMEDI_SUBD_DIO, /* digital input/output */ + COMEDI_SUBD_COUNTER, /* counter */ + COMEDI_SUBD_TIMER, /* timer */ + COMEDI_SUBD_MEMORY, /* memory, EEPROM, DPRAM */ + COMEDI_SUBD_CALIB, /* calibration DACs */ + COMEDI_SUBD_PROC, /* processor, DSP */ + COMEDI_SUBD_SERIAL, /* serial IO */ + COMEDI_SUBD_PWM /* PWM */ + }; + +/* configuration instructions */ + + enum configuration_ids { + INSN_CONFIG_DIO_INPUT = 0, + INSN_CONFIG_DIO_OUTPUT = 1, + INSN_CONFIG_DIO_OPENDRAIN = 2, + INSN_CONFIG_ANALOG_TRIG = 16, +/* INSN_CONFIG_WAVEFORM = 17, */ +/* INSN_CONFIG_TRIG = 18, */ +/* INSN_CONFIG_COUNTER = 19, */ + INSN_CONFIG_ALT_SOURCE = 20, + INSN_CONFIG_DIGITAL_TRIG = 21, + INSN_CONFIG_BLOCK_SIZE = 22, + INSN_CONFIG_TIMER_1 = 23, + INSN_CONFIG_FILTER = 24, + INSN_CONFIG_CHANGE_NOTIFY = 25, + + /*ALPHA*/ INSN_CONFIG_SERIAL_CLOCK = 26, + INSN_CONFIG_BIDIRECTIONAL_DATA = 27, + INSN_CONFIG_DIO_QUERY = 28, + INSN_CONFIG_PWM_OUTPUT = 29, + INSN_CONFIG_GET_PWM_OUTPUT = 30, + INSN_CONFIG_ARM = 31, + INSN_CONFIG_DISARM = 32, + INSN_CONFIG_GET_COUNTER_STATUS = 33, + INSN_CONFIG_RESET = 34, + /* Use CTR as single pulsegenerator */ + INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR = 1001, + /* Use CTR as pulsetraingenerator */ + INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR = 1002, + /* Use the counter as encoder */ + INSN_CONFIG_GPCT_QUADRATURE_ENCODER = 1003, + INSN_CONFIG_SET_GATE_SRC = 2001, /* Set gate source */ + INSN_CONFIG_GET_GATE_SRC = 2002, /* Get gate source */ + /* Set master clock source */ + INSN_CONFIG_SET_CLOCK_SRC = 2003, + INSN_CONFIG_GET_CLOCK_SRC = 2004, /* Get master clock source */ + INSN_CONFIG_SET_OTHER_SRC = 2005, /* Set other source */ + /* INSN_CONFIG_GET_OTHER_SRC = 2006,*//* Get other source */ + /* Get size in bytes of subdevice's on-board fifos used during + * streaming input/output */ + INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE = 2006, + INSN_CONFIG_SET_COUNTER_MODE = 4097, + /* INSN_CONFIG_8254_SET_MODE is deprecated */ + INSN_CONFIG_8254_SET_MODE = INSN_CONFIG_SET_COUNTER_MODE, + INSN_CONFIG_8254_READ_STATUS = 4098, + INSN_CONFIG_SET_ROUTING = 4099, + INSN_CONFIG_GET_ROUTING = 4109, +/* PWM */ + INSN_CONFIG_PWM_SET_PERIOD = 5000, /* sets frequency */ + INSN_CONFIG_PWM_GET_PERIOD = 5001, /* gets frequency */ + INSN_CONFIG_GET_PWM_STATUS = 5002, /* is it running? */ + /* sets H bridge: duty cycle and sign bit for a relay at the + * same time */ + INSN_CONFIG_PWM_SET_H_BRIDGE = 5003, + /* gets H bridge data: duty cycle and the sign bit */ + INSN_CONFIG_PWM_GET_H_BRIDGE = 5004 + }; + + enum comedi_io_direction { + COMEDI_INPUT = 0, + COMEDI_OUTPUT = 1, + COMEDI_OPENDRAIN = 2 + }; + + enum comedi_support_level { + COMEDI_UNKNOWN_SUPPORT = 0, + COMEDI_SUPPORTED, + COMEDI_UNSUPPORTED + }; + +/* ioctls */ + +#define CIO 'd' +#define COMEDI_DEVCONFIG _IOW(CIO, 0, struct comedi_devconfig) +#define COMEDI_DEVINFO _IOR(CIO, 1, struct comedi_devinfo) +#define COMEDI_SUBDINFO _IOR(CIO, 2, struct comedi_subdinfo) +#define COMEDI_CHANINFO _IOR(CIO, 3, struct comedi_chaninfo) +#define COMEDI_TRIG _IOWR(CIO, 4, comedi_trig) +#define COMEDI_LOCK _IO(CIO, 5) +#define COMEDI_UNLOCK _IO(CIO, 6) +#define COMEDI_CANCEL _IO(CIO, 7) +#define COMEDI_RANGEINFO _IOR(CIO, 8, struct comedi_rangeinfo) +#define COMEDI_CMD _IOR(CIO, 9, struct comedi_cmd) +#define COMEDI_CMDTEST _IOR(CIO, 10, struct comedi_cmd) +#define COMEDI_INSNLIST _IOR(CIO, 11, struct comedi_insnlist) +#define COMEDI_INSN _IOR(CIO, 12, struct comedi_insn) +#define COMEDI_BUFCONFIG _IOR(CIO, 13, struct comedi_bufconfig) +#define COMEDI_BUFINFO _IOWR(CIO, 14, struct comedi_bufinfo) +#define COMEDI_POLL _IO(CIO, 15) + +/* structures */ + + struct comedi_trig { + unsigned int subdev; /* subdevice */ + unsigned int mode; /* mode */ + unsigned int flags; + unsigned int n_chan; /* number of channels */ + unsigned int *chanlist; /* channel/range list */ + short *data; /* data list, size depends on subd flags */ + unsigned int n; /* number of scans */ + unsigned int trigsrc; + unsigned int trigvar; + unsigned int trigvar1; + unsigned int data_len; + unsigned int unused[3]; + }; + + struct comedi_insn { + unsigned int insn; + unsigned int n; + unsigned int __user *data; + unsigned int subdev; + unsigned int chanspec; + unsigned int unused[3]; + }; + + struct comedi_insnlist { + unsigned int n_insns; + struct comedi_insn __user *insns; + }; + + struct comedi_cmd { + unsigned int subdev; + unsigned int flags; + + unsigned int start_src; + unsigned int start_arg; + + unsigned int scan_begin_src; + unsigned int scan_begin_arg; + + unsigned int convert_src; + unsigned int convert_arg; + + unsigned int scan_end_src; + unsigned int scan_end_arg; + + unsigned int stop_src; + unsigned int stop_arg; + + unsigned int __user *chanlist; /* channel/range list */ + unsigned int chanlist_len; + + short __user *data; /* data list, size depends on subd flags */ + unsigned int data_len; + }; + + struct comedi_chaninfo { + unsigned int subdev; + unsigned int __user *maxdata_list; + unsigned int __user *flaglist; + unsigned int __user *rangelist; + unsigned int unused[4]; + }; + + struct comedi_rangeinfo { + unsigned int range_type; + void __user *range_ptr; + }; + + struct comedi_krange { + int min; /* fixed point, multiply by 1e-6 */ + int max; /* fixed point, multiply by 1e-6 */ + unsigned int flags; + }; + + struct comedi_subdinfo { + unsigned int type; + unsigned int n_chan; + unsigned int subd_flags; + unsigned int timer_type; + unsigned int len_chanlist; + unsigned int maxdata; + unsigned int flags; /* channel flags */ + unsigned int range_type; /* lookup in kernel */ + unsigned int settling_time_0; + /* see support_level enum for values */ + unsigned insn_bits_support; + unsigned int unused[8]; + }; + + struct comedi_devinfo { + unsigned int version_code; + unsigned int n_subdevs; + char driver_name[COMEDI_NAMELEN]; + char board_name[COMEDI_NAMELEN]; + int read_subdevice; + int write_subdevice; + int unused[30]; + }; + + struct comedi_devconfig { + char board_name[COMEDI_NAMELEN]; + int options[COMEDI_NDEVCONFOPTS]; + }; + + struct comedi_bufconfig { + unsigned int subdevice; + unsigned int flags; + + unsigned int maximum_size; + unsigned int size; + + unsigned int unused[4]; + }; + + struct comedi_bufinfo { + unsigned int subdevice; + unsigned int bytes_read; + + unsigned int buf_write_ptr; + unsigned int buf_read_ptr; + unsigned int buf_write_count; + unsigned int buf_read_count; + + unsigned int bytes_written; + + unsigned int unused[4]; + }; + +/* range stuff */ + +#define __RANGE(a, b) ((((a)&0xffff)<<16)|((b)&0xffff)) + +#define RANGE_OFFSET(a) (((a)>>16)&0xffff) +#define RANGE_LENGTH(b) ((b)&0xffff) + +#define RF_UNIT(flags) ((flags)&0xff) +#define RF_EXTERNAL (1<<8) + +#define UNIT_volt 0 +#define UNIT_mA 1 +#define UNIT_none 2 + +#define COMEDI_MIN_SPEED ((unsigned int)0xffffffff) + +/* callback stuff */ +/* only relevant to kernel modules. */ + +#define COMEDI_CB_EOS 1 /* end of scan */ +#define COMEDI_CB_EOA 2 /* end of acquisition */ +#define COMEDI_CB_BLOCK 4 /* data has arrived: + * wakes up read() / write() */ +#define COMEDI_CB_EOBUF 8 /* DEPRECATED: end of buffer */ +#define COMEDI_CB_ERROR 16 /* card error during acquisition */ +#define COMEDI_CB_OVERFLOW 32 /* buffer overflow/underflow */ + +/**********************************************************/ +/* everything after this line is ALPHA */ +/**********************************************************/ + +/* + 8254 specific configuration. + + It supports two config commands: + + 0 ID: INSN_CONFIG_SET_COUNTER_MODE + 1 8254 Mode + I8254_MODE0, I8254_MODE1, ..., I8254_MODE5 + OR'ed with: + I8254_BCD, I8254_BINARY + + 0 ID: INSN_CONFIG_8254_READ_STATUS + 1 <-- Status byte returned here. + B7 = Output + B6 = NULL Count + B5 - B0 Current mode. + +*/ + + enum i8254_mode { + I8254_MODE0 = (0 << 1), /* Interrupt on terminal count */ + I8254_MODE1 = (1 << 1), /* Hardware retriggerable one-shot */ + I8254_MODE2 = (2 << 1), /* Rate generator */ + I8254_MODE3 = (3 << 1), /* Square wave mode */ + I8254_MODE4 = (4 << 1), /* Software triggered strobe */ + I8254_MODE5 = (5 << 1), /* Hardware triggered strobe + * (retriggerable) */ + I8254_BCD = 1, /* use binary-coded decimal instead of binary + * (pretty useless) */ + I8254_BINARY = 0 + }; + + static inline unsigned NI_USUAL_PFI_SELECT(unsigned pfi_channel) + { + if (pfi_channel < 10) + return 0x1 + pfi_channel; + else + return 0xb + pfi_channel; + } + + static inline unsigned NI_USUAL_RTSI_SELECT(unsigned rtsi_channel) + { + if (rtsi_channel < 7) + return 0xb + rtsi_channel; + else + return 0x1b; + } + +/* mode bits for NI general-purpose counters, set with + * INSN_CONFIG_SET_COUNTER_MODE */ +#define NI_GPCT_COUNTING_MODE_SHIFT 16 +#define NI_GPCT_INDEX_PHASE_BITSHIFT 20 +#define NI_GPCT_COUNTING_DIRECTION_SHIFT 24 + enum ni_gpct_mode_bits { + NI_GPCT_GATE_ON_BOTH_EDGES_BIT = 0x4, + NI_GPCT_EDGE_GATE_MODE_MASK = 0x18, + NI_GPCT_EDGE_GATE_STARTS_STOPS_BITS = 0x0, + NI_GPCT_EDGE_GATE_STOPS_STARTS_BITS = 0x8, + NI_GPCT_EDGE_GATE_STARTS_BITS = 0x10, + NI_GPCT_EDGE_GATE_NO_STARTS_NO_STOPS_BITS = 0x18, + NI_GPCT_STOP_MODE_MASK = 0x60, + NI_GPCT_STOP_ON_GATE_BITS = 0x00, + NI_GPCT_STOP_ON_GATE_OR_TC_BITS = 0x20, + NI_GPCT_STOP_ON_GATE_OR_SECOND_TC_BITS = 0x40, + NI_GPCT_LOAD_B_SELECT_BIT = 0x80, + NI_GPCT_OUTPUT_MODE_MASK = 0x300, + NI_GPCT_OUTPUT_TC_PULSE_BITS = 0x100, + NI_GPCT_OUTPUT_TC_TOGGLE_BITS = 0x200, + NI_GPCT_OUTPUT_TC_OR_GATE_TOGGLE_BITS = 0x300, + NI_GPCT_HARDWARE_DISARM_MASK = 0xc00, + NI_GPCT_NO_HARDWARE_DISARM_BITS = 0x000, + NI_GPCT_DISARM_AT_TC_BITS = 0x400, + NI_GPCT_DISARM_AT_GATE_BITS = 0x800, + NI_GPCT_DISARM_AT_TC_OR_GATE_BITS = 0xc00, + NI_GPCT_LOADING_ON_TC_BIT = 0x1000, + NI_GPCT_LOADING_ON_GATE_BIT = 0x4000, + NI_GPCT_COUNTING_MODE_MASK = 0x7 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_NORMAL_BITS = + 0x0 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_QUADRATURE_X1_BITS = + 0x1 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_QUADRATURE_X2_BITS = + 0x2 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_QUADRATURE_X4_BITS = + 0x3 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_TWO_PULSE_BITS = + 0x4 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_SYNC_SOURCE_BITS = + 0x6 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_INDEX_PHASE_MASK = 0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_PHASE_LOW_A_LOW_B_BITS = + 0x0 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_PHASE_LOW_A_HIGH_B_BITS = + 0x1 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_PHASE_HIGH_A_LOW_B_BITS = + 0x2 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_PHASE_HIGH_A_HIGH_B_BITS = + 0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_ENABLE_BIT = 0x400000, + NI_GPCT_COUNTING_DIRECTION_MASK = + 0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_COUNTING_DIRECTION_DOWN_BITS = + 0x00 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_COUNTING_DIRECTION_UP_BITS = + 0x1 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_COUNTING_DIRECTION_HW_UP_DOWN_BITS = + 0x2 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_COUNTING_DIRECTION_HW_GATE_BITS = + 0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_RELOAD_SOURCE_MASK = 0xc000000, + NI_GPCT_RELOAD_SOURCE_FIXED_BITS = 0x0, + NI_GPCT_RELOAD_SOURCE_SWITCHING_BITS = 0x4000000, + NI_GPCT_RELOAD_SOURCE_GATE_SELECT_BITS = 0x8000000, + NI_GPCT_OR_GATE_BIT = 0x10000000, + NI_GPCT_INVERT_OUTPUT_BIT = 0x20000000 + }; + +/* Bits for setting a clock source with + * INSN_CONFIG_SET_CLOCK_SRC when using NI general-purpose counters. */ + enum ni_gpct_clock_source_bits { + NI_GPCT_CLOCK_SRC_SELECT_MASK = 0x3f, + NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS = 0x0, + NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS = 0x1, + NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS = 0x2, + NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS = 0x3, + NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS = 0x4, + NI_GPCT_NEXT_TC_CLOCK_SRC_BITS = 0x5, + /* NI 660x-specific */ + NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS = 0x6, + NI_GPCT_PXI10_CLOCK_SRC_BITS = 0x7, + NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS = 0x8, + NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS = 0x9, + NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK = 0x30000000, + NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS = 0x0, + /* divide source by 2 */ + NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS = 0x10000000, + /* divide source by 8 */ + NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS = 0x20000000, + NI_GPCT_INVERT_CLOCK_SRC_BIT = 0x80000000 + }; + static inline unsigned NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(unsigned n) + { + /* NI 660x-specific */ + return 0x10 + n; + } + static inline unsigned NI_GPCT_RTSI_CLOCK_SRC_BITS(unsigned n) + { + return 0x18 + n; + } + static inline unsigned NI_GPCT_PFI_CLOCK_SRC_BITS(unsigned n) + { + /* no pfi on NI 660x */ + return 0x20 + n; + } + +/* Possibilities for setting a gate source with +INSN_CONFIG_SET_GATE_SRC when using NI general-purpose counters. +May be bitwise-or'd with CR_EDGE or CR_INVERT. */ + enum ni_gpct_gate_select { + /* m-series gates */ + NI_GPCT_TIMESTAMP_MUX_GATE_SELECT = 0x0, + NI_GPCT_AI_START2_GATE_SELECT = 0x12, + NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT = 0x13, + NI_GPCT_NEXT_OUT_GATE_SELECT = 0x14, + NI_GPCT_AI_START1_GATE_SELECT = 0x1c, + NI_GPCT_NEXT_SOURCE_GATE_SELECT = 0x1d, + NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT = 0x1e, + NI_GPCT_LOGIC_LOW_GATE_SELECT = 0x1f, + /* more gates for 660x */ + NI_GPCT_SOURCE_PIN_i_GATE_SELECT = 0x100, + NI_GPCT_GATE_PIN_i_GATE_SELECT = 0x101, + /* more gates for 660x "second gate" */ + NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT = 0x201, + NI_GPCT_SELECTED_GATE_GATE_SELECT = 0x21e, + /* m-series "second gate" sources are unknown, + * we should add them here with an offset of 0x300 when + * known. */ + NI_GPCT_DISABLED_GATE_SELECT = 0x8000, + }; + static inline unsigned NI_GPCT_GATE_PIN_GATE_SELECT(unsigned n) + { + return 0x102 + n; + } + static inline unsigned NI_GPCT_RTSI_GATE_SELECT(unsigned n) + { + return NI_USUAL_RTSI_SELECT(n); + } + static inline unsigned NI_GPCT_PFI_GATE_SELECT(unsigned n) + { + return NI_USUAL_PFI_SELECT(n); + } + static inline unsigned NI_GPCT_UP_DOWN_PIN_GATE_SELECT(unsigned n) + { + return 0x202 + n; + } + +/* Possibilities for setting a source with +INSN_CONFIG_SET_OTHER_SRC when using NI general-purpose counters. */ + enum ni_gpct_other_index { + NI_GPCT_SOURCE_ENCODER_A, + NI_GPCT_SOURCE_ENCODER_B, + NI_GPCT_SOURCE_ENCODER_Z + }; + enum ni_gpct_other_select { + /* m-series gates */ + /* Still unknown, probably only need NI_GPCT_PFI_OTHER_SELECT */ + NI_GPCT_DISABLED_OTHER_SELECT = 0x8000, + }; + static inline unsigned NI_GPCT_PFI_OTHER_SELECT(unsigned n) + { + return NI_USUAL_PFI_SELECT(n); + } + +/* start sources for ni general-purpose counters for use with +INSN_CONFIG_ARM */ + enum ni_gpct_arm_source { + NI_GPCT_ARM_IMMEDIATE = 0x0, + NI_GPCT_ARM_PAIRED_IMMEDIATE = 0x1, /* Start both the counter + * and the adjacent paired + * counter simultaneously */ + /* NI doesn't document bits for selecting hardware arm triggers. + * If the NI_GPCT_ARM_UNKNOWN bit is set, we will pass the least + * significant bits (3 bits for 660x or 5 bits for m-series) + * through to the hardware. This will at least allow someone to + * figure out what the bits do later. */ + NI_GPCT_ARM_UNKNOWN = 0x1000, + }; + +/* digital filtering options for ni 660x for use with INSN_CONFIG_FILTER. */ + enum ni_gpct_filter_select { + NI_GPCT_FILTER_OFF = 0x0, + NI_GPCT_FILTER_TIMEBASE_3_SYNC = 0x1, + NI_GPCT_FILTER_100x_TIMEBASE_1 = 0x2, + NI_GPCT_FILTER_20x_TIMEBASE_1 = 0x3, + NI_GPCT_FILTER_10x_TIMEBASE_1 = 0x4, + NI_GPCT_FILTER_2x_TIMEBASE_1 = 0x5, + NI_GPCT_FILTER_2x_TIMEBASE_3 = 0x6 + }; + +/* PFI digital filtering options for ni m-series for use with + * INSN_CONFIG_FILTER. */ + enum ni_pfi_filter_select { + NI_PFI_FILTER_OFF = 0x0, + NI_PFI_FILTER_125ns = 0x1, + NI_PFI_FILTER_6425ns = 0x2, + NI_PFI_FILTER_2550us = 0x3 + }; + +/* master clock sources for ni mio boards and INSN_CONFIG_SET_CLOCK_SRC */ + enum ni_mio_clock_source { + NI_MIO_INTERNAL_CLOCK = 0, + NI_MIO_RTSI_CLOCK = 1, /* doesn't work for m-series, use + NI_MIO_PLL_RTSI_CLOCK() */ + /* the NI_MIO_PLL_* sources are m-series only */ + NI_MIO_PLL_PXI_STAR_TRIGGER_CLOCK = 2, + NI_MIO_PLL_PXI10_CLOCK = 3, + NI_MIO_PLL_RTSI0_CLOCK = 4 + }; + static inline unsigned NI_MIO_PLL_RTSI_CLOCK(unsigned rtsi_channel) + { + return NI_MIO_PLL_RTSI0_CLOCK + rtsi_channel; + } + +/* Signals which can be routed to an NI RTSI pin with INSN_CONFIG_SET_ROUTING. + The numbers assigned are not arbitrary, they correspond to the bits required + to program the board. */ + enum ni_rtsi_routing { + NI_RTSI_OUTPUT_ADR_START1 = 0, + NI_RTSI_OUTPUT_ADR_START2 = 1, + NI_RTSI_OUTPUT_SCLKG = 2, + NI_RTSI_OUTPUT_DACUPDN = 3, + NI_RTSI_OUTPUT_DA_START1 = 4, + NI_RTSI_OUTPUT_G_SRC0 = 5, + NI_RTSI_OUTPUT_G_GATE0 = 6, + NI_RTSI_OUTPUT_RGOUT0 = 7, + NI_RTSI_OUTPUT_RTSI_BRD_0 = 8, + NI_RTSI_OUTPUT_RTSI_OSC = 12 /* pre-m-series always have RTSI + * clock on line 7 */ + }; + static inline unsigned NI_RTSI_OUTPUT_RTSI_BRD(unsigned n) + { + return NI_RTSI_OUTPUT_RTSI_BRD_0 + n; + } + +/* Signals which can be routed to an NI PFI pin on an m-series board with + * INSN_CONFIG_SET_ROUTING. These numbers are also returned by + * INSN_CONFIG_GET_ROUTING on pre-m-series boards, even though their routing + * cannot be changed. The numbers assigned are not arbitrary, they correspond + * to the bits required to program the board. */ + enum ni_pfi_routing { + NI_PFI_OUTPUT_PFI_DEFAULT = 0, + NI_PFI_OUTPUT_AI_START1 = 1, + NI_PFI_OUTPUT_AI_START2 = 2, + NI_PFI_OUTPUT_AI_CONVERT = 3, + NI_PFI_OUTPUT_G_SRC1 = 4, + NI_PFI_OUTPUT_G_GATE1 = 5, + NI_PFI_OUTPUT_AO_UPDATE_N = 6, + NI_PFI_OUTPUT_AO_START1 = 7, + NI_PFI_OUTPUT_AI_START_PULSE = 8, + NI_PFI_OUTPUT_G_SRC0 = 9, + NI_PFI_OUTPUT_G_GATE0 = 10, + NI_PFI_OUTPUT_EXT_STROBE = 11, + NI_PFI_OUTPUT_AI_EXT_MUX_CLK = 12, + NI_PFI_OUTPUT_GOUT0 = 13, + NI_PFI_OUTPUT_GOUT1 = 14, + NI_PFI_OUTPUT_FREQ_OUT = 15, + NI_PFI_OUTPUT_PFI_DO = 16, + NI_PFI_OUTPUT_I_ATRIG = 17, + NI_PFI_OUTPUT_RTSI0 = 18, + NI_PFI_OUTPUT_PXI_STAR_TRIGGER_IN = 26, + NI_PFI_OUTPUT_SCXI_TRIG1 = 27, + NI_PFI_OUTPUT_DIO_CHANGE_DETECT_RTSI = 28, + NI_PFI_OUTPUT_CDI_SAMPLE = 29, + NI_PFI_OUTPUT_CDO_UPDATE = 30 + }; + static inline unsigned NI_PFI_OUTPUT_RTSI(unsigned rtsi_channel) + { + return NI_PFI_OUTPUT_RTSI0 + rtsi_channel; + } + +/* Signals which can be routed to output on a NI PFI pin on a 660x board + with INSN_CONFIG_SET_ROUTING. The numbers assigned are + not arbitrary, they correspond to the bits required + to program the board. Lines 0 to 7 can only be set to + NI_660X_PFI_OUTPUT_DIO. Lines 32 to 39 can only be set to + NI_660X_PFI_OUTPUT_COUNTER. */ + enum ni_660x_pfi_routing { + NI_660X_PFI_OUTPUT_COUNTER = 1, /* counter */ + NI_660X_PFI_OUTPUT_DIO = 2, /* static digital output */ + }; + +/* NI External Trigger lines. These values are not arbitrary, but are related + * to the bits required to program the board (offset by 1 for historical + * reasons). */ + static inline unsigned NI_EXT_PFI(unsigned pfi_channel) + { + return NI_USUAL_PFI_SELECT(pfi_channel) - 1; + } + static inline unsigned NI_EXT_RTSI(unsigned rtsi_channel) + { + return NI_USUAL_RTSI_SELECT(rtsi_channel) - 1; + } + +/* status bits for INSN_CONFIG_GET_COUNTER_STATUS */ + enum comedi_counter_status_flags { + COMEDI_COUNTER_ARMED = 0x1, + COMEDI_COUNTER_COUNTING = 0x2, + COMEDI_COUNTER_TERMINAL_COUNT = 0x4, + }; + +/* Clock sources for CDIO subdevice on NI m-series boards. Used as the + * scan_begin_arg for a comedi_command. These sources may also be bitwise-or'd + * with CR_INVERT to change polarity. */ + enum ni_m_series_cdio_scan_begin_src { + NI_CDIO_SCAN_BEGIN_SRC_GROUND = 0, + NI_CDIO_SCAN_BEGIN_SRC_AI_START = 18, + NI_CDIO_SCAN_BEGIN_SRC_AI_CONVERT = 19, + NI_CDIO_SCAN_BEGIN_SRC_PXI_STAR_TRIGGER = 20, + NI_CDIO_SCAN_BEGIN_SRC_G0_OUT = 28, + NI_CDIO_SCAN_BEGIN_SRC_G1_OUT = 29, + NI_CDIO_SCAN_BEGIN_SRC_ANALOG_TRIGGER = 30, + NI_CDIO_SCAN_BEGIN_SRC_AO_UPDATE = 31, + NI_CDIO_SCAN_BEGIN_SRC_FREQ_OUT = 32, + NI_CDIO_SCAN_BEGIN_SRC_DIO_CHANGE_DETECT_IRQ = 33 + }; + static inline unsigned NI_CDIO_SCAN_BEGIN_SRC_PFI(unsigned pfi_channel) + { + return NI_USUAL_PFI_SELECT(pfi_channel); + } + static inline unsigned + NI_CDIO_SCAN_BEGIN_SRC_RTSI(unsigned rtsi_channel) + { + return NI_USUAL_RTSI_SELECT(rtsi_channel); + } + +/* scan_begin_src for scan_begin_arg==TRIG_EXT with analog output command on NI + * boards. These scan begin sources can also be bitwise-or'd with CR_INVERT to + * change polarity. */ + static inline unsigned NI_AO_SCAN_BEGIN_SRC_PFI(unsigned pfi_channel) + { + return NI_USUAL_PFI_SELECT(pfi_channel); + } + static inline unsigned NI_AO_SCAN_BEGIN_SRC_RTSI(unsigned rtsi_channel) + { + return NI_USUAL_RTSI_SELECT(rtsi_channel); + } + +/* Bits for setting a clock source with + * INSN_CONFIG_SET_CLOCK_SRC when using NI frequency output subdevice. */ + enum ni_freq_out_clock_source_bits { + NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC, /* 10 MHz */ + NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC /* 100 KHz */ + }; + +/* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for + * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */ + enum amplc_dio_clock_source { + AMPLC_DIO_CLK_CLKN, /* per channel external clock + input/output pin (pin is only an + input when clock source set to this + value, otherwise it is an output) */ + AMPLC_DIO_CLK_10MHZ, /* 10 MHz internal clock */ + AMPLC_DIO_CLK_1MHZ, /* 1 MHz internal clock */ + AMPLC_DIO_CLK_100KHZ, /* 100 kHz internal clock */ + AMPLC_DIO_CLK_10KHZ, /* 10 kHz internal clock */ + AMPLC_DIO_CLK_1KHZ, /* 1 kHz internal clock */ + AMPLC_DIO_CLK_OUTNM1, /* output of preceding counter channel + (for channel 0, preceding counter + channel is channel 2 on preceding + counter subdevice, for first counter + subdevice, preceding counter + subdevice is the last counter + subdevice) */ + AMPLC_DIO_CLK_EXT /* per chip external input pin */ + }; + +/* Values for setting a gate source with INSN_CONFIG_SET_GATE_SRC for + * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */ + enum amplc_dio_gate_source { + AMPLC_DIO_GAT_VCC, /* internal high logic level */ + AMPLC_DIO_GAT_GND, /* internal low logic level */ + AMPLC_DIO_GAT_GATN, /* per channel external gate input */ + AMPLC_DIO_GAT_NOUTNM2, /* negated output of counter channel + minus 2 (for channels 0 or 1, + channel minus 2 is channel 1 or 2 on + the preceding counter subdevice, for + the first counter subdevice the + preceding counter subdevice is the + last counter subdevice) */ + AMPLC_DIO_GAT_RESERVED4, + AMPLC_DIO_GAT_RESERVED5, + AMPLC_DIO_GAT_RESERVED6, + AMPLC_DIO_GAT_RESERVED7 + }; + +#endif /* _COMEDI_H */ diff --git a/drivers/staging/comedi/comedi_compat32.c b/drivers/staging/comedi/comedi_compat32.c new file mode 100644 index 00000000..41a7a62b --- /dev/null +++ b/drivers/staging/comedi/comedi_compat32.c @@ -0,0 +1,463 @@ +/* + comedi/comedi_compat32.c + 32-bit ioctl compatibility for 64-bit comedi kernel module. + + Author: Ian Abbott, MEV Ltd. + Copyright (C) 2007 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2007 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#define __NO_VERSION__ +#include +#include "comedi.h" +#include "comedi_compat32.h" + +#ifdef CONFIG_COMPAT + +#define COMEDI32_CHANINFO _IOR(CIO, 3, struct comedi32_chaninfo_struct) +#define COMEDI32_RANGEINFO _IOR(CIO, 8, struct comedi32_rangeinfo_struct) +/* N.B. COMEDI32_CMD and COMEDI_CMD ought to use _IOWR, not _IOR. + * It's too late to change it now, but it only affects the command number. */ +#define COMEDI32_CMD _IOR(CIO, 9, struct comedi32_cmd_struct) +/* N.B. COMEDI32_CMDTEST and COMEDI_CMDTEST ought to use _IOWR, not _IOR. + * It's too late to change it now, but it only affects the command number. */ +#define COMEDI32_CMDTEST _IOR(CIO, 10, struct comedi32_cmd_struct) +#define COMEDI32_INSNLIST _IOR(CIO, 11, struct comedi32_insnlist_struct) +#define COMEDI32_INSN _IOR(CIO, 12, struct comedi32_insn_struct) + +struct comedi32_chaninfo_struct { + unsigned int subdev; + compat_uptr_t maxdata_list; /* 32-bit 'unsigned int *' */ + compat_uptr_t flaglist; /* 32-bit 'unsigned int *' */ + compat_uptr_t rangelist; /* 32-bit 'unsigned int *' */ + unsigned int unused[4]; +}; + +struct comedi32_rangeinfo_struct { + unsigned int range_type; + compat_uptr_t range_ptr; /* 32-bit 'void *' */ +}; + +struct comedi32_cmd_struct { + unsigned int subdev; + unsigned int flags; + unsigned int start_src; + unsigned int start_arg; + unsigned int scan_begin_src; + unsigned int scan_begin_arg; + unsigned int convert_src; + unsigned int convert_arg; + unsigned int scan_end_src; + unsigned int scan_end_arg; + unsigned int stop_src; + unsigned int stop_arg; + compat_uptr_t chanlist; /* 32-bit 'unsigned int *' */ + unsigned int chanlist_len; + compat_uptr_t data; /* 32-bit 'short *' */ + unsigned int data_len; +}; + +struct comedi32_insn_struct { + unsigned int insn; + unsigned int n; + compat_uptr_t data; /* 32-bit 'unsigned int *' */ + unsigned int subdev; + unsigned int chanspec; + unsigned int unused[3]; +}; + +struct comedi32_insnlist_struct { + unsigned int n_insns; + compat_uptr_t insns; /* 32-bit 'struct comedi_insn *' */ +}; + +/* Handle translated ioctl. */ +static int translated_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + if (!file->f_op) + return -ENOTTY; + + if (file->f_op->unlocked_ioctl) + return file->f_op->unlocked_ioctl(file, cmd, arg); + + return -ENOTTY; +} + +/* Handle 32-bit COMEDI_CHANINFO ioctl. */ +static int compat_chaninfo(struct file *file, unsigned long arg) +{ + struct comedi_chaninfo __user *chaninfo; + struct comedi32_chaninfo_struct __user *chaninfo32; + int err; + union { + unsigned int uint; + compat_uptr_t uptr; + } temp; + + chaninfo32 = compat_ptr(arg); + chaninfo = compat_alloc_user_space(sizeof(*chaninfo)); + + /* Copy chaninfo structure. Ignore unused members. */ + if (!access_ok(VERIFY_READ, chaninfo32, sizeof(*chaninfo32)) + || !access_ok(VERIFY_WRITE, chaninfo, sizeof(*chaninfo))) { + return -EFAULT; + } + err = 0; + err |= __get_user(temp.uint, &chaninfo32->subdev); + err |= __put_user(temp.uint, &chaninfo->subdev); + err |= __get_user(temp.uptr, &chaninfo32->maxdata_list); + err |= __put_user(compat_ptr(temp.uptr), &chaninfo->maxdata_list); + err |= __get_user(temp.uptr, &chaninfo32->flaglist); + err |= __put_user(compat_ptr(temp.uptr), &chaninfo->flaglist); + err |= __get_user(temp.uptr, &chaninfo32->rangelist); + err |= __put_user(compat_ptr(temp.uptr), &chaninfo->rangelist); + if (err) + return -EFAULT; + + return translated_ioctl(file, COMEDI_CHANINFO, (unsigned long)chaninfo); +} + +/* Handle 32-bit COMEDI_RANGEINFO ioctl. */ +static int compat_rangeinfo(struct file *file, unsigned long arg) +{ + struct comedi_rangeinfo __user *rangeinfo; + struct comedi32_rangeinfo_struct __user *rangeinfo32; + int err; + union { + unsigned int uint; + compat_uptr_t uptr; + } temp; + + rangeinfo32 = compat_ptr(arg); + rangeinfo = compat_alloc_user_space(sizeof(*rangeinfo)); + + /* Copy rangeinfo structure. */ + if (!access_ok(VERIFY_READ, rangeinfo32, sizeof(*rangeinfo32)) + || !access_ok(VERIFY_WRITE, rangeinfo, sizeof(*rangeinfo))) { + return -EFAULT; + } + err = 0; + err |= __get_user(temp.uint, &rangeinfo32->range_type); + err |= __put_user(temp.uint, &rangeinfo->range_type); + err |= __get_user(temp.uptr, &rangeinfo32->range_ptr); + err |= __put_user(compat_ptr(temp.uptr), &rangeinfo->range_ptr); + if (err) + return -EFAULT; + + return translated_ioctl(file, COMEDI_RANGEINFO, + (unsigned long)rangeinfo); +} + +/* Copy 32-bit cmd structure to native cmd structure. */ +static int get_compat_cmd(struct comedi_cmd __user *cmd, + struct comedi32_cmd_struct __user *cmd32) +{ + int err; + union { + unsigned int uint; + compat_uptr_t uptr; + } temp; + + /* Copy cmd structure. */ + if (!access_ok(VERIFY_READ, cmd32, sizeof(*cmd32)) + || !access_ok(VERIFY_WRITE, cmd, sizeof(*cmd))) { + return -EFAULT; + } + err = 0; + err |= __get_user(temp.uint, &cmd32->subdev); + err |= __put_user(temp.uint, &cmd->subdev); + err |= __get_user(temp.uint, &cmd32->flags); + err |= __put_user(temp.uint, &cmd->flags); + err |= __get_user(temp.uint, &cmd32->start_src); + err |= __put_user(temp.uint, &cmd->start_src); + err |= __get_user(temp.uint, &cmd32->start_arg); + err |= __put_user(temp.uint, &cmd->start_arg); + err |= __get_user(temp.uint, &cmd32->scan_begin_src); + err |= __put_user(temp.uint, &cmd->scan_begin_src); + err |= __get_user(temp.uint, &cmd32->scan_begin_arg); + err |= __put_user(temp.uint, &cmd->scan_begin_arg); + err |= __get_user(temp.uint, &cmd32->convert_src); + err |= __put_user(temp.uint, &cmd->convert_src); + err |= __get_user(temp.uint, &cmd32->convert_arg); + err |= __put_user(temp.uint, &cmd->convert_arg); + err |= __get_user(temp.uint, &cmd32->scan_end_src); + err |= __put_user(temp.uint, &cmd->scan_end_src); + err |= __get_user(temp.uint, &cmd32->scan_end_arg); + err |= __put_user(temp.uint, &cmd->scan_end_arg); + err |= __get_user(temp.uint, &cmd32->stop_src); + err |= __put_user(temp.uint, &cmd->stop_src); + err |= __get_user(temp.uint, &cmd32->stop_arg); + err |= __put_user(temp.uint, &cmd->stop_arg); + err |= __get_user(temp.uptr, &cmd32->chanlist); + err |= __put_user(compat_ptr(temp.uptr), &cmd->chanlist); + err |= __get_user(temp.uint, &cmd32->chanlist_len); + err |= __put_user(temp.uint, &cmd->chanlist_len); + err |= __get_user(temp.uptr, &cmd32->data); + err |= __put_user(compat_ptr(temp.uptr), &cmd->data); + err |= __get_user(temp.uint, &cmd32->data_len); + err |= __put_user(temp.uint, &cmd->data_len); + return err ? -EFAULT : 0; +} + +/* Copy native cmd structure to 32-bit cmd structure. */ +static int put_compat_cmd(struct comedi32_cmd_struct __user *cmd32, + struct comedi_cmd __user *cmd) +{ + int err; + unsigned int temp; + + /* Copy back most of cmd structure. */ + /* Assume the pointer values are already valid. */ + /* (Could use ptr_to_compat() to set them, but that wasn't implemented + * until kernel version 2.6.11.) */ + if (!access_ok(VERIFY_READ, cmd, sizeof(*cmd)) + || !access_ok(VERIFY_WRITE, cmd32, sizeof(*cmd32))) { + return -EFAULT; + } + err = 0; + err |= __get_user(temp, &cmd->subdev); + err |= __put_user(temp, &cmd32->subdev); + err |= __get_user(temp, &cmd->flags); + err |= __put_user(temp, &cmd32->flags); + err |= __get_user(temp, &cmd->start_src); + err |= __put_user(temp, &cmd32->start_src); + err |= __get_user(temp, &cmd->start_arg); + err |= __put_user(temp, &cmd32->start_arg); + err |= __get_user(temp, &cmd->scan_begin_src); + err |= __put_user(temp, &cmd32->scan_begin_src); + err |= __get_user(temp, &cmd->scan_begin_arg); + err |= __put_user(temp, &cmd32->scan_begin_arg); + err |= __get_user(temp, &cmd->convert_src); + err |= __put_user(temp, &cmd32->convert_src); + err |= __get_user(temp, &cmd->convert_arg); + err |= __put_user(temp, &cmd32->convert_arg); + err |= __get_user(temp, &cmd->scan_end_src); + err |= __put_user(temp, &cmd32->scan_end_src); + err |= __get_user(temp, &cmd->scan_end_arg); + err |= __put_user(temp, &cmd32->scan_end_arg); + err |= __get_user(temp, &cmd->stop_src); + err |= __put_user(temp, &cmd32->stop_src); + err |= __get_user(temp, &cmd->stop_arg); + err |= __put_user(temp, &cmd32->stop_arg); + /* Assume chanlist pointer is unchanged. */ + err |= __get_user(temp, &cmd->chanlist_len); + err |= __put_user(temp, &cmd32->chanlist_len); + /* Assume data pointer is unchanged. */ + err |= __get_user(temp, &cmd->data_len); + err |= __put_user(temp, &cmd32->data_len); + return err ? -EFAULT : 0; +} + +/* Handle 32-bit COMEDI_CMD ioctl. */ +static int compat_cmd(struct file *file, unsigned long arg) +{ + struct comedi_cmd __user *cmd; + struct comedi32_cmd_struct __user *cmd32; + int rc; + + cmd32 = compat_ptr(arg); + cmd = compat_alloc_user_space(sizeof(*cmd)); + + rc = get_compat_cmd(cmd, cmd32); + if (rc) + return rc; + + return translated_ioctl(file, COMEDI_CMD, (unsigned long)cmd); +} + +/* Handle 32-bit COMEDI_CMDTEST ioctl. */ +static int compat_cmdtest(struct file *file, unsigned long arg) +{ + struct comedi_cmd __user *cmd; + struct comedi32_cmd_struct __user *cmd32; + int rc, err; + + cmd32 = compat_ptr(arg); + cmd = compat_alloc_user_space(sizeof(*cmd)); + + rc = get_compat_cmd(cmd, cmd32); + if (rc) + return rc; + + rc = translated_ioctl(file, COMEDI_CMDTEST, (unsigned long)cmd); + if (rc < 0) + return rc; + + err = put_compat_cmd(cmd32, cmd); + if (err) + rc = err; + + return rc; +} + +/* Copy 32-bit insn structure to native insn structure. */ +static int get_compat_insn(struct comedi_insn __user *insn, + struct comedi32_insn_struct __user *insn32) +{ + int err; + union { + unsigned int uint; + compat_uptr_t uptr; + } temp; + + /* Copy insn structure. Ignore the unused members. */ + err = 0; + if (!access_ok(VERIFY_READ, insn32, sizeof(*insn32)) + || !access_ok(VERIFY_WRITE, insn, sizeof(*insn))) + return -EFAULT; + + err |= __get_user(temp.uint, &insn32->insn); + err |= __put_user(temp.uint, &insn->insn); + err |= __get_user(temp.uint, &insn32->n); + err |= __put_user(temp.uint, &insn->n); + err |= __get_user(temp.uptr, &insn32->data); + err |= __put_user(compat_ptr(temp.uptr), &insn->data); + err |= __get_user(temp.uint, &insn32->subdev); + err |= __put_user(temp.uint, &insn->subdev); + err |= __get_user(temp.uint, &insn32->chanspec); + err |= __put_user(temp.uint, &insn->chanspec); + return err ? -EFAULT : 0; +} + +/* Handle 32-bit COMEDI_INSNLIST ioctl. */ +static int compat_insnlist(struct file *file, unsigned long arg) +{ + struct combined_insnlist { + struct comedi_insnlist insnlist; + struct comedi_insn insn[1]; + } __user *s; + struct comedi32_insnlist_struct __user *insnlist32; + struct comedi32_insn_struct __user *insn32; + compat_uptr_t uptr; + unsigned int n_insns, n; + int err, rc; + + insnlist32 = compat_ptr(arg); + + /* Get 32-bit insnlist structure. */ + if (!access_ok(VERIFY_READ, insnlist32, sizeof(*insnlist32))) + return -EFAULT; + + err = 0; + err |= __get_user(n_insns, &insnlist32->n_insns); + err |= __get_user(uptr, &insnlist32->insns); + insn32 = compat_ptr(uptr); + if (err) + return -EFAULT; + + /* Allocate user memory to copy insnlist and insns into. */ + s = compat_alloc_user_space(offsetof(struct combined_insnlist, + insn[n_insns])); + + /* Set native insnlist structure. */ + if (!access_ok(VERIFY_WRITE, &s->insnlist, sizeof(s->insnlist))) + return -EFAULT; + + err |= __put_user(n_insns, &s->insnlist.n_insns); + err |= __put_user(&s->insn[0], &s->insnlist.insns); + if (err) + return -EFAULT; + + /* Copy insn structures. */ + for (n = 0; n < n_insns; n++) { + rc = get_compat_insn(&s->insn[n], &insn32[n]); + if (rc) + return rc; + } + + return translated_ioctl(file, COMEDI_INSNLIST, + (unsigned long)&s->insnlist); +} + +/* Handle 32-bit COMEDI_INSN ioctl. */ +static int compat_insn(struct file *file, unsigned long arg) +{ + struct comedi_insn __user *insn; + struct comedi32_insn_struct __user *insn32; + int rc; + + insn32 = compat_ptr(arg); + insn = compat_alloc_user_space(sizeof(*insn)); + + rc = get_compat_insn(insn, insn32); + if (rc) + return rc; + + return translated_ioctl(file, COMEDI_INSN, (unsigned long)insn); +} + +/* Process untranslated ioctl. */ +/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */ +static inline int raw_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int rc; + + switch (cmd) { + case COMEDI_DEVCONFIG: + case COMEDI_DEVINFO: + case COMEDI_SUBDINFO: + case COMEDI_BUFCONFIG: + case COMEDI_BUFINFO: + /* Just need to translate the pointer argument. */ + arg = (unsigned long)compat_ptr(arg); + rc = translated_ioctl(file, cmd, arg); + break; + case COMEDI_LOCK: + case COMEDI_UNLOCK: + case COMEDI_CANCEL: + case COMEDI_POLL: + /* No translation needed. */ + rc = translated_ioctl(file, cmd, arg); + break; + case COMEDI32_CHANINFO: + rc = compat_chaninfo(file, arg); + break; + case COMEDI32_RANGEINFO: + rc = compat_rangeinfo(file, arg); + break; + case COMEDI32_CMD: + rc = compat_cmd(file, arg); + break; + case COMEDI32_CMDTEST: + rc = compat_cmdtest(file, arg); + break; + case COMEDI32_INSNLIST: + rc = compat_insnlist(file, arg); + break; + case COMEDI32_INSN: + rc = compat_insn(file, arg); + break; + default: + rc = -ENOIOCTLCMD; + break; + } + return rc; +} + +/* compat_ioctl file operation. */ +/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */ +long comedi_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return raw_ioctl(file, cmd, arg); +} + +#endif /* CONFIG_COMPAT */ diff --git a/drivers/staging/comedi/comedi_compat32.h b/drivers/staging/comedi/comedi_compat32.h new file mode 100644 index 00000000..0340a894 --- /dev/null +++ b/drivers/staging/comedi/comedi_compat32.h @@ -0,0 +1,44 @@ +/* + comedi/comedi_compat32.h + 32-bit ioctl compatibility for 64-bit comedi kernel module. + + Author: Ian Abbott, MEV Ltd. + Copyright (C) 2007 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2007 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _COMEDI_COMPAT32_H +#define _COMEDI_COMPAT32_H + +#include +#include + +#ifdef CONFIG_COMPAT + +extern long comedi_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); + +#else /* CONFIG_COMPAT */ + +#define comedi_compat_ioctl 0 /* NULL */ + +#endif /* CONFIG_COMPAT */ + +#endif /* _COMEDI_COMPAT32_H */ diff --git a/drivers/staging/comedi/comedi_fops.c b/drivers/staging/comedi/comedi_fops.c new file mode 100644 index 00000000..a796964b --- /dev/null +++ b/drivers/staging/comedi/comedi_fops.c @@ -0,0 +1,2706 @@ +/* + comedi/comedi_fops.c + comedi kernel module + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#undef DEBUG + +#define __NO_VERSION__ +#include "comedi_fops.h" +#include "comedi_compat32.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "comedidev.h" +#include +#include + +#include +#include + +#include "internal.h" + +MODULE_AUTHOR("http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi core module"); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_COMEDI_DEBUG +int comedi_debug; +EXPORT_SYMBOL(comedi_debug); +module_param(comedi_debug, int, 0644); +#endif + +bool comedi_autoconfig = 1; +module_param(comedi_autoconfig, bool, 0444); + +static int comedi_num_legacy_minors; +module_param(comedi_num_legacy_minors, int, 0444); + +static DEFINE_SPINLOCK(comedi_file_info_table_lock); +static struct comedi_device_file_info +*comedi_file_info_table[COMEDI_NUM_MINORS]; + +static int do_devconfig_ioctl(struct comedi_device *dev, + struct comedi_devconfig __user *arg); +static int do_bufconfig_ioctl(struct comedi_device *dev, + struct comedi_bufconfig __user *arg); +static int do_devinfo_ioctl(struct comedi_device *dev, + struct comedi_devinfo __user *arg, + struct file *file); +static int do_subdinfo_ioctl(struct comedi_device *dev, + struct comedi_subdinfo __user *arg, void *file); +static int do_chaninfo_ioctl(struct comedi_device *dev, + struct comedi_chaninfo __user *arg); +static int do_bufinfo_ioctl(struct comedi_device *dev, + struct comedi_bufinfo __user *arg, void *file); +static int do_cmd_ioctl(struct comedi_device *dev, + struct comedi_cmd __user *arg, void *file); +static int do_lock_ioctl(struct comedi_device *dev, unsigned int arg, + void *file); +static int do_unlock_ioctl(struct comedi_device *dev, unsigned int arg, + void *file); +static int do_cancel_ioctl(struct comedi_device *dev, unsigned int arg, + void *file); +static int do_cmdtest_ioctl(struct comedi_device *dev, + struct comedi_cmd __user *arg, void *file); +static int do_insnlist_ioctl(struct comedi_device *dev, + struct comedi_insnlist __user *arg, void *file); +static int do_insn_ioctl(struct comedi_device *dev, + struct comedi_insn __user *arg, void *file); +static int do_poll_ioctl(struct comedi_device *dev, unsigned int subd, + void *file); + +static void do_become_nonbusy(struct comedi_device *dev, + struct comedi_subdevice *s); +static int do_cancel(struct comedi_device *dev, struct comedi_subdevice *s); + +static int comedi_fasync(int fd, struct file *file, int on); + +static int is_device_busy(struct comedi_device *dev); +static int resize_async_buffer(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_async *async, unsigned new_size); + +/* declarations for sysfs attribute files */ +static struct device_attribute dev_attr_max_read_buffer_kb; +static struct device_attribute dev_attr_read_buffer_kb; +static struct device_attribute dev_attr_max_write_buffer_kb; +static struct device_attribute dev_attr_write_buffer_kb; + +static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + struct comedi_device *dev; + int rc; + + if (dev_file_info == NULL || dev_file_info->device == NULL) + return -ENODEV; + dev = dev_file_info->device; + + mutex_lock(&dev->mutex); + + /* Device config is special, because it must work on + * an unconfigured device. */ + if (cmd == COMEDI_DEVCONFIG) { + rc = do_devconfig_ioctl(dev, + (struct comedi_devconfig __user *)arg); + goto done; + } + + if (!dev->attached) { + DPRINTK("no driver configured on /dev/comedi%i\n", dev->minor); + rc = -ENODEV; + goto done; + } + + switch (cmd) { + case COMEDI_BUFCONFIG: + rc = do_bufconfig_ioctl(dev, + (struct comedi_bufconfig __user *)arg); + break; + case COMEDI_DEVINFO: + rc = do_devinfo_ioctl(dev, (struct comedi_devinfo __user *)arg, + file); + break; + case COMEDI_SUBDINFO: + rc = do_subdinfo_ioctl(dev, + (struct comedi_subdinfo __user *)arg, + file); + break; + case COMEDI_CHANINFO: + rc = do_chaninfo_ioctl(dev, (void __user *)arg); + break; + case COMEDI_RANGEINFO: + rc = do_rangeinfo_ioctl(dev, (void __user *)arg); + break; + case COMEDI_BUFINFO: + rc = do_bufinfo_ioctl(dev, + (struct comedi_bufinfo __user *)arg, + file); + break; + case COMEDI_LOCK: + rc = do_lock_ioctl(dev, arg, file); + break; + case COMEDI_UNLOCK: + rc = do_unlock_ioctl(dev, arg, file); + break; + case COMEDI_CANCEL: + rc = do_cancel_ioctl(dev, arg, file); + break; + case COMEDI_CMD: + rc = do_cmd_ioctl(dev, (struct comedi_cmd __user *)arg, file); + break; + case COMEDI_CMDTEST: + rc = do_cmdtest_ioctl(dev, (struct comedi_cmd __user *)arg, + file); + break; + case COMEDI_INSNLIST: + rc = do_insnlist_ioctl(dev, + (struct comedi_insnlist __user *)arg, + file); + break; + case COMEDI_INSN: + rc = do_insn_ioctl(dev, (struct comedi_insn __user *)arg, + file); + break; + case COMEDI_POLL: + rc = do_poll_ioctl(dev, arg, file); + break; + default: + rc = -ENOTTY; + break; + } + +done: + mutex_unlock(&dev->mutex); + return rc; +} + +/* + COMEDI_DEVCONFIG + device config ioctl + + arg: + pointer to devconfig structure + + reads: + devconfig structure at arg + + writes: + none +*/ +static int do_devconfig_ioctl(struct comedi_device *dev, + struct comedi_devconfig __user *arg) +{ + struct comedi_devconfig it; + int ret; + unsigned char *aux_data = NULL; + int aux_len; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (arg == NULL) { + if (is_device_busy(dev)) + return -EBUSY; + if (dev->attached) { + struct module *driver_module = dev->driver->module; + comedi_device_detach(dev); + module_put(driver_module); + } + return 0; + } + + if (copy_from_user(&it, arg, sizeof(struct comedi_devconfig))) + return -EFAULT; + + it.board_name[COMEDI_NAMELEN - 1] = 0; + + if (comedi_aux_data(it.options, 0) && + it.options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { + int bit_shift; + aux_len = it.options[COMEDI_DEVCONF_AUX_DATA_LENGTH]; + if (aux_len < 0) + return -EFAULT; + + aux_data = vmalloc(aux_len); + if (!aux_data) + return -ENOMEM; + + if (copy_from_user(aux_data, + comedi_aux_data(it.options, 0), aux_len)) { + vfree(aux_data); + return -EFAULT; + } + it.options[COMEDI_DEVCONF_AUX_DATA_LO] = + (unsigned long)aux_data; + if (sizeof(void *) > sizeof(int)) { + bit_shift = sizeof(int) * 8; + it.options[COMEDI_DEVCONF_AUX_DATA_HI] = + ((unsigned long)aux_data) >> bit_shift; + } else + it.options[COMEDI_DEVCONF_AUX_DATA_HI] = 0; + } + + ret = comedi_device_attach(dev, &it); + if (ret == 0) { + if (!try_module_get(dev->driver->module)) { + comedi_device_detach(dev); + ret = -ENOSYS; + } + } + + if (aux_data) + vfree(aux_data); + + return ret; +} + +/* + COMEDI_BUFCONFIG + buffer configuration ioctl + + arg: + pointer to bufconfig structure + + reads: + bufconfig at arg + + writes: + modified bufconfig at arg + +*/ +static int do_bufconfig_ioctl(struct comedi_device *dev, + struct comedi_bufconfig __user *arg) +{ + struct comedi_bufconfig bc; + struct comedi_async *async; + struct comedi_subdevice *s; + int retval = 0; + + if (copy_from_user(&bc, arg, sizeof(struct comedi_bufconfig))) + return -EFAULT; + + if (bc.subdevice >= dev->n_subdevices || bc.subdevice < 0) + return -EINVAL; + + s = dev->subdevices + bc.subdevice; + async = s->async; + + if (!async) { + DPRINTK("subdevice does not have async capability\n"); + bc.size = 0; + bc.maximum_size = 0; + goto copyback; + } + + if (bc.maximum_size) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + async->max_bufsize = bc.maximum_size; + } + + if (bc.size) { + retval = resize_async_buffer(dev, s, async, bc.size); + if (retval < 0) + return retval; + } + + bc.size = async->prealloc_bufsz; + bc.maximum_size = async->max_bufsize; + +copyback: + if (copy_to_user(arg, &bc, sizeof(struct comedi_bufconfig))) + return -EFAULT; + + return 0; +} + +/* + COMEDI_DEVINFO + device info ioctl + + arg: + pointer to devinfo structure + + reads: + none + + writes: + devinfo structure + +*/ +static int do_devinfo_ioctl(struct comedi_device *dev, + struct comedi_devinfo __user *arg, + struct file *file) +{ + struct comedi_devinfo devinfo; + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + struct comedi_subdevice *read_subdev = + comedi_get_read_subdevice(dev_file_info); + struct comedi_subdevice *write_subdev = + comedi_get_write_subdevice(dev_file_info); + + memset(&devinfo, 0, sizeof(devinfo)); + + /* fill devinfo structure */ + devinfo.version_code = COMEDI_VERSION_CODE; + devinfo.n_subdevs = dev->n_subdevices; + strlcpy(devinfo.driver_name, dev->driver->driver_name, COMEDI_NAMELEN); + strlcpy(devinfo.board_name, dev->board_name, COMEDI_NAMELEN); + + if (read_subdev) + devinfo.read_subdevice = read_subdev - dev->subdevices; + else + devinfo.read_subdevice = -1; + + if (write_subdev) + devinfo.write_subdevice = write_subdev - dev->subdevices; + else + devinfo.write_subdevice = -1; + + if (copy_to_user(arg, &devinfo, sizeof(struct comedi_devinfo))) + return -EFAULT; + + return 0; +} + +/* + COMEDI_SUBDINFO + subdevice info ioctl + + arg: + pointer to array of subdevice info structures + + reads: + none + + writes: + array of subdevice info structures at arg + +*/ +static int do_subdinfo_ioctl(struct comedi_device *dev, + struct comedi_subdinfo __user *arg, void *file) +{ + int ret, i; + struct comedi_subdinfo *tmp, *us; + struct comedi_subdevice *s; + + tmp = + kcalloc(dev->n_subdevices, sizeof(struct comedi_subdinfo), + GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + /* fill subdinfo structs */ + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + us = tmp + i; + + us->type = s->type; + us->n_chan = s->n_chan; + us->subd_flags = s->subdev_flags; + if (comedi_get_subdevice_runflags(s) & SRF_RUNNING) + us->subd_flags |= SDF_RUNNING; +#define TIMER_nanosec 5 /* backwards compatibility */ + us->timer_type = TIMER_nanosec; + us->len_chanlist = s->len_chanlist; + us->maxdata = s->maxdata; + if (s->range_table) { + us->range_type = + (i << 24) | (0 << 16) | (s->range_table->length); + } else { + us->range_type = 0; /* XXX */ + } + us->flags = s->flags; + + if (s->busy) + us->subd_flags |= SDF_BUSY; + if (s->busy == file) + us->subd_flags |= SDF_BUSY_OWNER; + if (s->lock) + us->subd_flags |= SDF_LOCKED; + if (s->lock == file) + us->subd_flags |= SDF_LOCK_OWNER; + if (!s->maxdata && s->maxdata_list) + us->subd_flags |= SDF_MAXDATA; + if (s->flaglist) + us->subd_flags |= SDF_FLAGS; + if (s->range_table_list) + us->subd_flags |= SDF_RANGETYPE; + if (s->do_cmd) + us->subd_flags |= SDF_CMD; + + if (s->insn_bits != &insn_inval) + us->insn_bits_support = COMEDI_SUPPORTED; + else + us->insn_bits_support = COMEDI_UNSUPPORTED; + + us->settling_time_0 = s->settling_time_0; + } + + ret = copy_to_user(arg, tmp, + dev->n_subdevices * sizeof(struct comedi_subdinfo)); + + kfree(tmp); + + return ret ? -EFAULT : 0; +} + +/* + COMEDI_CHANINFO + subdevice info ioctl + + arg: + pointer to chaninfo structure + + reads: + chaninfo structure at arg + + writes: + arrays at elements of chaninfo structure + +*/ +static int do_chaninfo_ioctl(struct comedi_device *dev, + struct comedi_chaninfo __user *arg) +{ + struct comedi_subdevice *s; + struct comedi_chaninfo it; + + if (copy_from_user(&it, arg, sizeof(struct comedi_chaninfo))) + return -EFAULT; + + if (it.subdev >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + it.subdev; + + if (it.maxdata_list) { + if (s->maxdata || !s->maxdata_list) + return -EINVAL; + if (copy_to_user(it.maxdata_list, s->maxdata_list, + s->n_chan * sizeof(unsigned int))) + return -EFAULT; + } + + if (it.flaglist) { + if (!s->flaglist) + return -EINVAL; + if (copy_to_user(it.flaglist, s->flaglist, + s->n_chan * sizeof(unsigned int))) + return -EFAULT; + } + + if (it.rangelist) { + int i; + + if (!s->range_table_list) + return -EINVAL; + for (i = 0; i < s->n_chan; i++) { + int x; + + x = (dev->minor << 28) | (it.subdev << 24) | (i << 16) | + (s->range_table_list[i]->length); + if (put_user(x, it.rangelist + i)) + return -EFAULT; + } +#if 0 + if (copy_to_user(it.rangelist, s->range_type_list, + s->n_chan * sizeof(unsigned int))) + return -EFAULT; +#endif + } + + return 0; +} + + /* + COMEDI_BUFINFO + buffer information ioctl + + arg: + pointer to bufinfo structure + + reads: + bufinfo at arg + + writes: + modified bufinfo at arg + + */ +static int do_bufinfo_ioctl(struct comedi_device *dev, + struct comedi_bufinfo __user *arg, void *file) +{ + struct comedi_bufinfo bi; + struct comedi_subdevice *s; + struct comedi_async *async; + + if (copy_from_user(&bi, arg, sizeof(struct comedi_bufinfo))) + return -EFAULT; + + if (bi.subdevice >= dev->n_subdevices || bi.subdevice < 0) + return -EINVAL; + + s = dev->subdevices + bi.subdevice; + + if (s->lock && s->lock != file) + return -EACCES; + + async = s->async; + + if (!async) { + DPRINTK("subdevice does not have async capability\n"); + bi.buf_write_ptr = 0; + bi.buf_read_ptr = 0; + bi.buf_write_count = 0; + bi.buf_read_count = 0; + bi.bytes_read = 0; + bi.bytes_written = 0; + goto copyback; + } + if (!s->busy) { + bi.bytes_read = 0; + bi.bytes_written = 0; + goto copyback_position; + } + if (s->busy != file) + return -EACCES; + + if (bi.bytes_read && (s->subdev_flags & SDF_CMD_READ)) { + bi.bytes_read = comedi_buf_read_alloc(async, bi.bytes_read); + comedi_buf_read_free(async, bi.bytes_read); + + if (!(comedi_get_subdevice_runflags(s) & (SRF_ERROR | + SRF_RUNNING)) + && async->buf_write_count == async->buf_read_count) { + do_become_nonbusy(dev, s); + } + } + + if (bi.bytes_written && (s->subdev_flags & SDF_CMD_WRITE)) { + bi.bytes_written = + comedi_buf_write_alloc(async, bi.bytes_written); + comedi_buf_write_free(async, bi.bytes_written); + } + +copyback_position: + bi.buf_write_count = async->buf_write_count; + bi.buf_write_ptr = async->buf_write_ptr; + bi.buf_read_count = async->buf_read_count; + bi.buf_read_ptr = async->buf_read_ptr; + +copyback: + if (copy_to_user(arg, &bi, sizeof(struct comedi_bufinfo))) + return -EFAULT; + + return 0; +} + +static int parse_insn(struct comedi_device *dev, struct comedi_insn *insn, + unsigned int *data, void *file); +/* + * COMEDI_INSNLIST + * synchronous instructions + * + * arg: + * pointer to sync cmd structure + * + * reads: + * sync cmd struct at arg + * instruction list + * data (for writes) + * + * writes: + * data (for reads) + */ +/* arbitrary limits */ +#define MAX_SAMPLES 256 +static int do_insnlist_ioctl(struct comedi_device *dev, + struct comedi_insnlist __user *arg, void *file) +{ + struct comedi_insnlist insnlist; + struct comedi_insn *insns = NULL; + unsigned int *data = NULL; + int i = 0; + int ret = 0; + + if (copy_from_user(&insnlist, arg, sizeof(struct comedi_insnlist))) + return -EFAULT; + + data = kmalloc(sizeof(unsigned int) * MAX_SAMPLES, GFP_KERNEL); + if (!data) { + DPRINTK("kmalloc failed\n"); + ret = -ENOMEM; + goto error; + } + + insns = + kcalloc(insnlist.n_insns, sizeof(struct comedi_insn), GFP_KERNEL); + if (!insns) { + DPRINTK("kmalloc failed\n"); + ret = -ENOMEM; + goto error; + } + + if (copy_from_user(insns, insnlist.insns, + sizeof(struct comedi_insn) * insnlist.n_insns)) { + DPRINTK("copy_from_user failed\n"); + ret = -EFAULT; + goto error; + } + + for (i = 0; i < insnlist.n_insns; i++) { + if (insns[i].n > MAX_SAMPLES) { + DPRINTK("number of samples too large\n"); + ret = -EINVAL; + goto error; + } + if (insns[i].insn & INSN_MASK_WRITE) { + if (copy_from_user(data, insns[i].data, + insns[i].n * sizeof(unsigned int))) { + DPRINTK("copy_from_user failed\n"); + ret = -EFAULT; + goto error; + } + } + ret = parse_insn(dev, insns + i, data, file); + if (ret < 0) + goto error; + if (insns[i].insn & INSN_MASK_READ) { + if (copy_to_user(insns[i].data, data, + insns[i].n * sizeof(unsigned int))) { + DPRINTK("copy_to_user failed\n"); + ret = -EFAULT; + goto error; + } + } + if (need_resched()) + schedule(); + } + +error: + kfree(insns); + kfree(data); + + if (ret < 0) + return ret; + return i; +} + +static int check_insn_config_length(struct comedi_insn *insn, + unsigned int *data) +{ + if (insn->n < 1) + return -EINVAL; + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + case INSN_CONFIG_DIO_INPUT: + case INSN_CONFIG_DISARM: + case INSN_CONFIG_RESET: + if (insn->n == 1) + return 0; + break; + case INSN_CONFIG_ARM: + case INSN_CONFIG_DIO_QUERY: + case INSN_CONFIG_BLOCK_SIZE: + case INSN_CONFIG_FILTER: + case INSN_CONFIG_SERIAL_CLOCK: + case INSN_CONFIG_BIDIRECTIONAL_DATA: + case INSN_CONFIG_ALT_SOURCE: + case INSN_CONFIG_SET_COUNTER_MODE: + case INSN_CONFIG_8254_READ_STATUS: + case INSN_CONFIG_SET_ROUTING: + case INSN_CONFIG_GET_ROUTING: + case INSN_CONFIG_GET_PWM_STATUS: + case INSN_CONFIG_PWM_SET_PERIOD: + case INSN_CONFIG_PWM_GET_PERIOD: + if (insn->n == 2) + return 0; + break; + case INSN_CONFIG_SET_GATE_SRC: + case INSN_CONFIG_GET_GATE_SRC: + case INSN_CONFIG_SET_CLOCK_SRC: + case INSN_CONFIG_GET_CLOCK_SRC: + case INSN_CONFIG_SET_OTHER_SRC: + case INSN_CONFIG_GET_COUNTER_STATUS: + case INSN_CONFIG_PWM_SET_H_BRIDGE: + case INSN_CONFIG_PWM_GET_H_BRIDGE: + case INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE: + if (insn->n == 3) + return 0; + break; + case INSN_CONFIG_PWM_OUTPUT: + case INSN_CONFIG_ANALOG_TRIG: + if (insn->n == 5) + return 0; + break; + /* by default we allow the insn since we don't have checks for + * all possible cases yet */ + default: + printk(KERN_WARNING + "comedi: no check for data length of config insn id " + "%i is implemented.\n" + " Add a check to %s in %s.\n" + " Assuming n=%i is correct.\n", data[0], __func__, + __FILE__, insn->n); + return 0; + break; + } + return -EINVAL; +} + +static int parse_insn(struct comedi_device *dev, struct comedi_insn *insn, + unsigned int *data, void *file) +{ + struct comedi_subdevice *s; + int ret = 0; + int i; + + if (insn->insn & INSN_MASK_SPECIAL) { + /* a non-subdevice instruction */ + + switch (insn->insn) { + case INSN_GTOD: + { + struct timeval tv; + + if (insn->n != 2) { + ret = -EINVAL; + break; + } + + do_gettimeofday(&tv); + data[0] = tv.tv_sec; + data[1] = tv.tv_usec; + ret = 2; + + break; + } + case INSN_WAIT: + if (insn->n != 1 || data[0] >= 100000) { + ret = -EINVAL; + break; + } + udelay(data[0] / 1000); + ret = 1; + break; + case INSN_INTTRIG: + if (insn->n != 1) { + ret = -EINVAL; + break; + } + if (insn->subdev >= dev->n_subdevices) { + DPRINTK("%d not usable subdevice\n", + insn->subdev); + ret = -EINVAL; + break; + } + s = dev->subdevices + insn->subdev; + if (!s->async) { + DPRINTK("no async\n"); + ret = -EINVAL; + break; + } + if (!s->async->inttrig) { + DPRINTK("no inttrig\n"); + ret = -EAGAIN; + break; + } + ret = s->async->inttrig(dev, s, insn->data[0]); + if (ret >= 0) + ret = 1; + break; + default: + DPRINTK("invalid insn\n"); + ret = -EINVAL; + break; + } + } else { + /* a subdevice instruction */ + unsigned int maxdata; + + if (insn->subdev >= dev->n_subdevices) { + DPRINTK("subdevice %d out of range\n", insn->subdev); + ret = -EINVAL; + goto out; + } + s = dev->subdevices + insn->subdev; + + if (s->type == COMEDI_SUBD_UNUSED) { + DPRINTK("%d not usable subdevice\n", insn->subdev); + ret = -EIO; + goto out; + } + + /* are we locked? (ioctl lock) */ + if (s->lock && s->lock != file) { + DPRINTK("device locked\n"); + ret = -EACCES; + goto out; + } + + ret = comedi_check_chanlist(s, 1, &insn->chanspec); + if (ret < 0) { + ret = -EINVAL; + DPRINTK("bad chanspec\n"); + goto out; + } + + if (s->busy) { + ret = -EBUSY; + goto out; + } + /* This looks arbitrary. It is. */ + s->busy = &parse_insn; + switch (insn->insn) { + case INSN_READ: + ret = s->insn_read(dev, s, insn, data); + break; + case INSN_WRITE: + maxdata = s->maxdata_list + ? s->maxdata_list[CR_CHAN(insn->chanspec)] + : s->maxdata; + for (i = 0; i < insn->n; ++i) { + if (data[i] > maxdata) { + ret = -EINVAL; + DPRINTK("bad data value(s)\n"); + break; + } + } + if (ret == 0) + ret = s->insn_write(dev, s, insn, data); + break; + case INSN_BITS: + if (insn->n != 2) { + ret = -EINVAL; + } else { + /* Most drivers ignore the base channel in + * insn->chanspec. Fix this here if + * the subdevice has <= 32 channels. */ + unsigned int shift; + unsigned int orig_mask; + + orig_mask = data[0]; + if (s->n_chan <= 32) { + shift = CR_CHAN(insn->chanspec); + if (shift > 0) { + insn->chanspec = 0; + data[0] <<= shift; + data[1] <<= shift; + } + } else + shift = 0; + ret = s->insn_bits(dev, s, insn, data); + data[0] = orig_mask; + if (shift > 0) + data[1] >>= shift; + } + break; + case INSN_CONFIG: + ret = check_insn_config_length(insn, data); + if (ret) + break; + ret = s->insn_config(dev, s, insn, data); + break; + default: + ret = -EINVAL; + break; + } + + s->busy = NULL; + } + +out: + return ret; +} + +/* + * COMEDI_INSN + * synchronous instructions + * + * arg: + * pointer to insn + * + * reads: + * struct comedi_insn struct at arg + * data (for writes) + * + * writes: + * data (for reads) + */ +static int do_insn_ioctl(struct comedi_device *dev, + struct comedi_insn __user *arg, void *file) +{ + struct comedi_insn insn; + unsigned int *data = NULL; + int ret = 0; + + data = kmalloc(sizeof(unsigned int) * MAX_SAMPLES, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto error; + } + + if (copy_from_user(&insn, arg, sizeof(struct comedi_insn))) { + ret = -EFAULT; + goto error; + } + + /* This is where the behavior of insn and insnlist deviate. */ + if (insn.n > MAX_SAMPLES) + insn.n = MAX_SAMPLES; + if (insn.insn & INSN_MASK_WRITE) { + if (copy_from_user(data, + insn.data, + insn.n * sizeof(unsigned int))) { + ret = -EFAULT; + goto error; + } + } + ret = parse_insn(dev, &insn, data, file); + if (ret < 0) + goto error; + if (insn.insn & INSN_MASK_READ) { + if (copy_to_user(insn.data, + data, + insn.n * sizeof(unsigned int))) { + ret = -EFAULT; + goto error; + } + } + ret = insn.n; + +error: + kfree(data); + + return ret; +} + +static void comedi_set_subdevice_runflags(struct comedi_subdevice *s, + unsigned mask, unsigned bits) +{ + unsigned long flags; + + spin_lock_irqsave(&s->spin_lock, flags); + s->runflags &= ~mask; + s->runflags |= (bits & mask); + spin_unlock_irqrestore(&s->spin_lock, flags); +} + +static int do_cmd_ioctl(struct comedi_device *dev, + struct comedi_cmd __user *cmd, void *file) +{ + struct comedi_cmd user_cmd; + struct comedi_subdevice *s; + struct comedi_async *async; + int ret = 0; + unsigned int __user *chanlist_saver = NULL; + + if (copy_from_user(&user_cmd, cmd, sizeof(struct comedi_cmd))) { + DPRINTK("bad cmd address\n"); + return -EFAULT; + } + /* save user's chanlist pointer so it can be restored later */ + chanlist_saver = user_cmd.chanlist; + + if (user_cmd.subdev >= dev->n_subdevices) { + DPRINTK("%d no such subdevice\n", user_cmd.subdev); + return -ENODEV; + } + + s = dev->subdevices + user_cmd.subdev; + async = s->async; + + if (s->type == COMEDI_SUBD_UNUSED) { + DPRINTK("%d not valid subdevice\n", user_cmd.subdev); + return -EIO; + } + + if (!s->do_cmd || !s->do_cmdtest || !s->async) { + DPRINTK("subdevice %i does not support commands\n", + user_cmd.subdev); + return -EIO; + } + + /* are we locked? (ioctl lock) */ + if (s->lock && s->lock != file) { + DPRINTK("subdevice locked\n"); + return -EACCES; + } + + /* are we busy? */ + if (s->busy) { + DPRINTK("subdevice busy\n"); + return -EBUSY; + } + s->busy = file; + + /* make sure channel/gain list isn't too long */ + if (user_cmd.chanlist_len > s->len_chanlist) { + DPRINTK("channel/gain list too long %u > %d\n", + user_cmd.chanlist_len, s->len_chanlist); + ret = -EINVAL; + goto cleanup; + } + + /* make sure channel/gain list isn't too short */ + if (user_cmd.chanlist_len < 1) { + DPRINTK("channel/gain list too short %u < 1\n", + user_cmd.chanlist_len); + ret = -EINVAL; + goto cleanup; + } + + kfree(async->cmd.chanlist); + async->cmd = user_cmd; + async->cmd.data = NULL; + /* load channel/gain list */ + async->cmd.chanlist = + kmalloc(async->cmd.chanlist_len * sizeof(int), GFP_KERNEL); + if (!async->cmd.chanlist) { + DPRINTK("allocation failed\n"); + ret = -ENOMEM; + goto cleanup; + } + + if (copy_from_user(async->cmd.chanlist, user_cmd.chanlist, + async->cmd.chanlist_len * sizeof(int))) { + DPRINTK("fault reading chanlist\n"); + ret = -EFAULT; + goto cleanup; + } + + /* make sure each element in channel/gain list is valid */ + ret = comedi_check_chanlist(s, + async->cmd.chanlist_len, + async->cmd.chanlist); + if (ret < 0) { + DPRINTK("bad chanlist\n"); + goto cleanup; + } + + ret = s->do_cmdtest(dev, s, &async->cmd); + + if (async->cmd.flags & TRIG_BOGUS || ret) { + DPRINTK("test returned %d\n", ret); + user_cmd = async->cmd; + /* restore chanlist pointer before copying back */ + user_cmd.chanlist = chanlist_saver; + user_cmd.data = NULL; + if (copy_to_user(cmd, &user_cmd, sizeof(struct comedi_cmd))) { + DPRINTK("fault writing cmd\n"); + ret = -EFAULT; + goto cleanup; + } + ret = -EAGAIN; + goto cleanup; + } + + if (!async->prealloc_bufsz) { + ret = -ENOMEM; + DPRINTK("no buffer (?)\n"); + goto cleanup; + } + + comedi_reset_async_buf(async); + + async->cb_mask = + COMEDI_CB_EOA | COMEDI_CB_BLOCK | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW; + if (async->cmd.flags & TRIG_WAKE_EOS) + async->cb_mask |= COMEDI_CB_EOS; + + comedi_set_subdevice_runflags(s, ~0, SRF_USER | SRF_RUNNING); + + ret = s->do_cmd(dev, s); + if (ret == 0) + return 0; + +cleanup: + do_become_nonbusy(dev, s); + + return ret; +} + +/* + COMEDI_CMDTEST + command testing ioctl + + arg: + pointer to cmd structure + + reads: + cmd structure at arg + channel/range list + + writes: + modified cmd structure at arg + +*/ +static int do_cmdtest_ioctl(struct comedi_device *dev, + struct comedi_cmd __user *arg, void *file) +{ + struct comedi_cmd user_cmd; + struct comedi_subdevice *s; + int ret = 0; + unsigned int *chanlist = NULL; + unsigned int __user *chanlist_saver = NULL; + + if (copy_from_user(&user_cmd, arg, sizeof(struct comedi_cmd))) { + DPRINTK("bad cmd address\n"); + return -EFAULT; + } + /* save user's chanlist pointer so it can be restored later */ + chanlist_saver = user_cmd.chanlist; + + if (user_cmd.subdev >= dev->n_subdevices) { + DPRINTK("%d no such subdevice\n", user_cmd.subdev); + return -ENODEV; + } + + s = dev->subdevices + user_cmd.subdev; + if (s->type == COMEDI_SUBD_UNUSED) { + DPRINTK("%d not valid subdevice\n", user_cmd.subdev); + return -EIO; + } + + if (!s->do_cmd || !s->do_cmdtest) { + DPRINTK("subdevice %i does not support commands\n", + user_cmd.subdev); + return -EIO; + } + + /* make sure channel/gain list isn't too long */ + if (user_cmd.chanlist_len > s->len_chanlist) { + DPRINTK("channel/gain list too long %d > %d\n", + user_cmd.chanlist_len, s->len_chanlist); + ret = -EINVAL; + goto cleanup; + } + + /* load channel/gain list */ + if (user_cmd.chanlist) { + chanlist = + kmalloc(user_cmd.chanlist_len * sizeof(int), GFP_KERNEL); + if (!chanlist) { + DPRINTK("allocation failed\n"); + ret = -ENOMEM; + goto cleanup; + } + + if (copy_from_user(chanlist, user_cmd.chanlist, + user_cmd.chanlist_len * sizeof(int))) { + DPRINTK("fault reading chanlist\n"); + ret = -EFAULT; + goto cleanup; + } + + /* make sure each element in channel/gain list is valid */ + ret = comedi_check_chanlist(s, user_cmd.chanlist_len, chanlist); + if (ret < 0) { + DPRINTK("bad chanlist\n"); + goto cleanup; + } + + user_cmd.chanlist = chanlist; + } + + ret = s->do_cmdtest(dev, s, &user_cmd); + + /* restore chanlist pointer before copying back */ + user_cmd.chanlist = chanlist_saver; + + if (copy_to_user(arg, &user_cmd, sizeof(struct comedi_cmd))) { + DPRINTK("bad cmd address\n"); + ret = -EFAULT; + goto cleanup; + } +cleanup: + kfree(chanlist); + + return ret; +} + +/* + COMEDI_LOCK + lock subdevice + + arg: + subdevice number + + reads: + none + + writes: + none + +*/ + +static int do_lock_ioctl(struct comedi_device *dev, unsigned int arg, + void *file) +{ + int ret = 0; + unsigned long flags; + struct comedi_subdevice *s; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + arg; + + spin_lock_irqsave(&s->spin_lock, flags); + if (s->busy || s->lock) + ret = -EBUSY; + else + s->lock = file; + spin_unlock_irqrestore(&s->spin_lock, flags); + +#if 0 + if (ret < 0) + return ret; + + if (s->lock_f) + ret = s->lock_f(dev, s); +#endif + + return ret; +} + +/* + COMEDI_UNLOCK + unlock subdevice + + arg: + subdevice number + + reads: + none + + writes: + none + + This function isn't protected by the semaphore, since + we already own the lock. +*/ +static int do_unlock_ioctl(struct comedi_device *dev, unsigned int arg, + void *file) +{ + struct comedi_subdevice *s; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + arg; + + if (s->busy) + return -EBUSY; + + if (s->lock && s->lock != file) + return -EACCES; + + if (s->lock == file) { +#if 0 + if (s->unlock) + s->unlock(dev, s); +#endif + + s->lock = NULL; + } + + return 0; +} + +/* + COMEDI_CANCEL + cancel acquisition ioctl + + arg: + subdevice number + + reads: + nothing + + writes: + nothing + +*/ +static int do_cancel_ioctl(struct comedi_device *dev, unsigned int arg, + void *file) +{ + struct comedi_subdevice *s; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + arg; + if (s->async == NULL) + return -EINVAL; + + if (s->lock && s->lock != file) + return -EACCES; + + if (!s->busy) + return 0; + + if (s->busy != file) + return -EBUSY; + + return do_cancel(dev, s); +} + +/* + COMEDI_POLL ioctl + instructs driver to synchronize buffers + + arg: + subdevice number + + reads: + nothing + + writes: + nothing + +*/ +static int do_poll_ioctl(struct comedi_device *dev, unsigned int arg, + void *file) +{ + struct comedi_subdevice *s; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + arg; + + if (s->lock && s->lock != file) + return -EACCES; + + if (!s->busy) + return 0; + + if (s->busy != file) + return -EBUSY; + + if (s->poll) + return s->poll(dev, s); + + return -EINVAL; +} + +static int do_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int ret = 0; + + if ((comedi_get_subdevice_runflags(s) & SRF_RUNNING) && s->cancel) + ret = s->cancel(dev, s); + + do_become_nonbusy(dev, s); + + return ret; +} + + +static void comedi_vm_open(struct vm_area_struct *area) +{ + struct comedi_async *async; + struct comedi_device *dev; + + async = area->vm_private_data; + dev = async->subdevice->device; + + mutex_lock(&dev->mutex); + async->mmap_count++; + mutex_unlock(&dev->mutex); +} + +static void comedi_vm_close(struct vm_area_struct *area) +{ + struct comedi_async *async; + struct comedi_device *dev; + + async = area->vm_private_data; + dev = async->subdevice->device; + + mutex_lock(&dev->mutex); + async->mmap_count--; + mutex_unlock(&dev->mutex); +} + +static struct vm_operations_struct comedi_vm_ops = { + .open = comedi_vm_open, + .close = comedi_vm_close, +}; + +static int comedi_mmap(struct file *file, struct vm_area_struct *vma) +{ + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_async *async = NULL; + unsigned long start = vma->vm_start; + unsigned long size; + int n_pages; + int i; + int retval; + struct comedi_subdevice *s; + struct comedi_device_file_info *dev_file_info; + struct comedi_device *dev; + + dev_file_info = comedi_get_device_file_info(minor); + if (dev_file_info == NULL) + return -ENODEV; + dev = dev_file_info->device; + if (dev == NULL) + return -ENODEV; + + mutex_lock(&dev->mutex); + if (!dev->attached) { + DPRINTK("no driver configured on comedi%i\n", dev->minor); + retval = -ENODEV; + goto done; + } + if (vma->vm_flags & VM_WRITE) + s = comedi_get_write_subdevice(dev_file_info); + else + s = comedi_get_read_subdevice(dev_file_info); + + if (s == NULL) { + retval = -EINVAL; + goto done; + } + async = s->async; + if (async == NULL) { + retval = -EINVAL; + goto done; + } + + if (vma->vm_pgoff != 0) { + DPRINTK("comedi: mmap() offset must be 0.\n"); + retval = -EINVAL; + goto done; + } + + size = vma->vm_end - vma->vm_start; + if (size > async->prealloc_bufsz) { + retval = -EFAULT; + goto done; + } + if (size & (~PAGE_MASK)) { + retval = -EFAULT; + goto done; + } + + n_pages = size >> PAGE_SHIFT; + for (i = 0; i < n_pages; ++i) { + if (remap_pfn_range(vma, start, + page_to_pfn(virt_to_page + (async->buf_page_list + [i].virt_addr)), PAGE_SIZE, + PAGE_SHARED)) { + retval = -EAGAIN; + goto done; + } + start += PAGE_SIZE; + } + + vma->vm_ops = &comedi_vm_ops; + vma->vm_private_data = async; + + async->mmap_count++; + + retval = 0; +done: + mutex_unlock(&dev->mutex); + return retval; +} + +static unsigned int comedi_poll(struct file *file, poll_table * wait) +{ + unsigned int mask = 0; + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_subdevice *read_subdev; + struct comedi_subdevice *write_subdev; + struct comedi_device_file_info *dev_file_info; + struct comedi_device *dev; + dev_file_info = comedi_get_device_file_info(minor); + + if (dev_file_info == NULL) + return -ENODEV; + dev = dev_file_info->device; + if (dev == NULL) + return -ENODEV; + + mutex_lock(&dev->mutex); + if (!dev->attached) { + DPRINTK("no driver configured on comedi%i\n", dev->minor); + mutex_unlock(&dev->mutex); + return 0; + } + + mask = 0; + read_subdev = comedi_get_read_subdevice(dev_file_info); + if (read_subdev) { + poll_wait(file, &read_subdev->async->wait_head, wait); + if (!read_subdev->busy + || comedi_buf_read_n_available(read_subdev->async) > 0 + || !(comedi_get_subdevice_runflags(read_subdev) & + SRF_RUNNING)) { + mask |= POLLIN | POLLRDNORM; + } + } + write_subdev = comedi_get_write_subdevice(dev_file_info); + if (write_subdev) { + poll_wait(file, &write_subdev->async->wait_head, wait); + comedi_buf_write_alloc(write_subdev->async, + write_subdev->async->prealloc_bufsz); + if (!write_subdev->busy + || !(comedi_get_subdevice_runflags(write_subdev) & + SRF_RUNNING) + || comedi_buf_write_n_allocated(write_subdev->async) >= + bytes_per_sample(write_subdev->async->subdevice)) { + mask |= POLLOUT | POLLWRNORM; + } + } + + mutex_unlock(&dev->mutex); + return mask; +} + +static ssize_t comedi_write(struct file *file, const char __user *buf, + size_t nbytes, loff_t *offset) +{ + struct comedi_subdevice *s; + struct comedi_async *async; + int n, m, count = 0, retval = 0; + DECLARE_WAITQUEUE(wait, current); + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info; + struct comedi_device *dev; + dev_file_info = comedi_get_device_file_info(minor); + + if (dev_file_info == NULL) + return -ENODEV; + dev = dev_file_info->device; + if (dev == NULL) + return -ENODEV; + + if (!dev->attached) { + DPRINTK("no driver configured on comedi%i\n", dev->minor); + retval = -ENODEV; + goto done; + } + + s = comedi_get_write_subdevice(dev_file_info); + if (s == NULL) { + retval = -EIO; + goto done; + } + async = s->async; + + if (!nbytes) { + retval = 0; + goto done; + } + if (!s->busy) { + retval = 0; + goto done; + } + if (s->busy != file) { + retval = -EACCES; + goto done; + } + add_wait_queue(&async->wait_head, &wait); + while (nbytes > 0 && !retval) { + set_current_state(TASK_INTERRUPTIBLE); + + if (!(comedi_get_subdevice_runflags(s) & SRF_RUNNING)) { + if (count == 0) { + if (comedi_get_subdevice_runflags(s) & + SRF_ERROR) { + retval = -EPIPE; + } else { + retval = 0; + } + do_become_nonbusy(dev, s); + } + break; + } + + n = nbytes; + + m = n; + if (async->buf_write_ptr + m > async->prealloc_bufsz) + m = async->prealloc_bufsz - async->buf_write_ptr; + comedi_buf_write_alloc(async, async->prealloc_bufsz); + if (m > comedi_buf_write_n_allocated(async)) + m = comedi_buf_write_n_allocated(async); + if (m < n) + n = m; + + if (n == 0) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + if (!s->busy) + break; + if (s->busy != file) { + retval = -EACCES; + break; + } + continue; + } + + m = copy_from_user(async->prealloc_buf + async->buf_write_ptr, + buf, n); + if (m) { + n -= m; + retval = -EFAULT; + } + comedi_buf_write_free(async, n); + + count += n; + nbytes -= n; + + buf += n; + break; /* makes device work like a pipe */ + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&async->wait_head, &wait); + +done: + return count ? count : retval; +} + +static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes, + loff_t *offset) +{ + struct comedi_subdevice *s; + struct comedi_async *async; + int n, m, count = 0, retval = 0; + DECLARE_WAITQUEUE(wait, current); + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info; + struct comedi_device *dev; + dev_file_info = comedi_get_device_file_info(minor); + + if (dev_file_info == NULL) + return -ENODEV; + dev = dev_file_info->device; + if (dev == NULL) + return -ENODEV; + + if (!dev->attached) { + DPRINTK("no driver configured on comedi%i\n", dev->minor); + retval = -ENODEV; + goto done; + } + + s = comedi_get_read_subdevice(dev_file_info); + if (s == NULL) { + retval = -EIO; + goto done; + } + async = s->async; + if (!nbytes) { + retval = 0; + goto done; + } + if (!s->busy) { + retval = 0; + goto done; + } + if (s->busy != file) { + retval = -EACCES; + goto done; + } + + add_wait_queue(&async->wait_head, &wait); + while (nbytes > 0 && !retval) { + set_current_state(TASK_INTERRUPTIBLE); + + n = nbytes; + + m = comedi_buf_read_n_available(async); + /* printk("%d available\n",m); */ + if (async->buf_read_ptr + m > async->prealloc_bufsz) + m = async->prealloc_bufsz - async->buf_read_ptr; + /* printk("%d contiguous\n",m); */ + if (m < n) + n = m; + + if (n == 0) { + if (!(comedi_get_subdevice_runflags(s) & SRF_RUNNING)) { + do_become_nonbusy(dev, s); + if (comedi_get_subdevice_runflags(s) & + SRF_ERROR) { + retval = -EPIPE; + } else { + retval = 0; + } + break; + } + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + schedule(); + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + if (!s->busy) { + retval = 0; + break; + } + if (s->busy != file) { + retval = -EACCES; + break; + } + continue; + } + m = copy_to_user(buf, async->prealloc_buf + + async->buf_read_ptr, n); + if (m) { + n -= m; + retval = -EFAULT; + } + + comedi_buf_read_alloc(async, n); + comedi_buf_read_free(async, n); + + count += n; + nbytes -= n; + + buf += n; + break; /* makes device work like a pipe */ + } + if (!(comedi_get_subdevice_runflags(s) & (SRF_ERROR | SRF_RUNNING)) && + async->buf_read_count - async->buf_write_count == 0) { + do_become_nonbusy(dev, s); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&async->wait_head, &wait); + +done: + return count ? count : retval; +} + +/* + This function restores a subdevice to an idle state. + */ +void do_become_nonbusy(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + + comedi_set_subdevice_runflags(s, SRF_RUNNING, 0); + if (async) { + comedi_reset_async_buf(async); + async->inttrig = NULL; + } else { + printk(KERN_ERR + "BUG: (?) do_become_nonbusy called with async=0\n"); + } + + s->busy = NULL; +} + +static int comedi_open(struct inode *inode, struct file *file) +{ + const unsigned minor = iminor(inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + struct comedi_device *dev = + dev_file_info ? dev_file_info->device : NULL; + + if (dev == NULL) { + DPRINTK("invalid minor number\n"); + return -ENODEV; + } + + /* This is slightly hacky, but we want module autoloading + * to work for root. + * case: user opens device, attached -> ok + * case: user opens device, unattached, in_request_module=0 -> autoload + * case: user opens device, unattached, in_request_module=1 -> fail + * case: root opens device, attached -> ok + * case: root opens device, unattached, in_request_module=1 -> ok + * (typically called from modprobe) + * case: root opens device, unattached, in_request_module=0 -> autoload + * + * The last could be changed to "-> ok", which would deny root + * autoloading. + */ + mutex_lock(&dev->mutex); + if (dev->attached) + goto ok; + if (!capable(CAP_NET_ADMIN) && dev->in_request_module) { + DPRINTK("in request module\n"); + mutex_unlock(&dev->mutex); + return -ENODEV; + } + if (capable(CAP_NET_ADMIN) && dev->in_request_module) + goto ok; + + dev->in_request_module = 1; + +#ifdef CONFIG_KMOD + mutex_unlock(&dev->mutex); + request_module("char-major-%i-%i", COMEDI_MAJOR, dev->minor); + mutex_lock(&dev->mutex); +#endif + + dev->in_request_module = 0; + + if (!dev->attached && !capable(CAP_NET_ADMIN)) { + DPRINTK("not attached and not CAP_NET_ADMIN\n"); + mutex_unlock(&dev->mutex); + return -ENODEV; + } +ok: + __module_get(THIS_MODULE); + + if (dev->attached) { + if (!try_module_get(dev->driver->module)) { + module_put(THIS_MODULE); + mutex_unlock(&dev->mutex); + return -ENOSYS; + } + } + + if (dev->attached && dev->use_count == 0 && dev->open) { + int rc = dev->open(dev); + if (rc < 0) { + module_put(dev->driver->module); + module_put(THIS_MODULE); + mutex_unlock(&dev->mutex); + return rc; + } + } + + dev->use_count++; + + mutex_unlock(&dev->mutex); + + return 0; +} + +static int comedi_close(struct inode *inode, struct file *file) +{ + const unsigned minor = iminor(inode); + struct comedi_subdevice *s = NULL; + int i; + struct comedi_device_file_info *dev_file_info; + struct comedi_device *dev; + dev_file_info = comedi_get_device_file_info(minor); + + if (dev_file_info == NULL) + return -ENODEV; + dev = dev_file_info->device; + if (dev == NULL) + return -ENODEV; + + mutex_lock(&dev->mutex); + + if (dev->subdevices) { + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + + if (s->busy == file) + do_cancel(dev, s); + if (s->lock == file) + s->lock = NULL; + } + } + if (dev->attached && dev->use_count == 1 && dev->close) + dev->close(dev); + + module_put(THIS_MODULE); + if (dev->attached) + module_put(dev->driver->module); + + dev->use_count--; + + mutex_unlock(&dev->mutex); + + if (file->f_flags & FASYNC) + comedi_fasync(-1, file, 0); + + return 0; +} + +static int comedi_fasync(int fd, struct file *file, int on) +{ + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info; + struct comedi_device *dev; + dev_file_info = comedi_get_device_file_info(minor); + + if (dev_file_info == NULL) + return -ENODEV; + dev = dev_file_info->device; + if (dev == NULL) + return -ENODEV; + + return fasync_helper(fd, file, on, &dev->async_queue); +} + +const struct file_operations comedi_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = comedi_unlocked_ioctl, + .compat_ioctl = comedi_compat_ioctl, + .open = comedi_open, + .release = comedi_close, + .read = comedi_read, + .write = comedi_write, + .mmap = comedi_mmap, + .poll = comedi_poll, + .fasync = comedi_fasync, + .llseek = noop_llseek, +}; + +struct class *comedi_class; +static struct cdev comedi_cdev; + +static void comedi_cleanup_legacy_minors(void) +{ + unsigned i; + + for (i = 0; i < comedi_num_legacy_minors; i++) + comedi_free_board_minor(i); +} + +static int __init comedi_init(void) +{ + int i; + int retval; + + printk(KERN_INFO "comedi: version " COMEDI_RELEASE + " - http://www.comedi.org\n"); + + if (comedi_num_legacy_minors < 0 || + comedi_num_legacy_minors > COMEDI_NUM_BOARD_MINORS) { + printk(KERN_ERR "comedi: error: invalid value for module " + "parameter \"comedi_num_legacy_minors\". Valid values " + "are 0 through %i.\n", COMEDI_NUM_BOARD_MINORS); + return -EINVAL; + } + + /* + * comedi is unusable if both comedi_autoconfig and + * comedi_num_legacy_minors are zero, so we might as well adjust the + * defaults in that case + */ + if (comedi_autoconfig == 0 && comedi_num_legacy_minors == 0) + comedi_num_legacy_minors = 16; + + memset(comedi_file_info_table, 0, + sizeof(struct comedi_device_file_info *) * COMEDI_NUM_MINORS); + + retval = register_chrdev_region(MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS, "comedi"); + if (retval) + return -EIO; + cdev_init(&comedi_cdev, &comedi_fops); + comedi_cdev.owner = THIS_MODULE; + kobject_set_name(&comedi_cdev.kobj, "comedi"); + if (cdev_add(&comedi_cdev, MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS)) { + unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS); + return -EIO; + } + comedi_class = class_create(THIS_MODULE, "comedi"); + if (IS_ERR(comedi_class)) { + printk(KERN_ERR "comedi: failed to create class"); + cdev_del(&comedi_cdev); + unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS); + return PTR_ERR(comedi_class); + } + + /* XXX requires /proc interface */ + comedi_proc_init(); + + /* create devices files for legacy/manual use */ + for (i = 0; i < comedi_num_legacy_minors; i++) { + int minor; + minor = comedi_alloc_board_minor(NULL); + if (minor < 0) { + comedi_cleanup_legacy_minors(); + cdev_del(&comedi_cdev); + unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS); + return minor; + } + } + + return 0; +} + +static void __exit comedi_cleanup(void) +{ + int i; + + comedi_cleanup_legacy_minors(); + for (i = 0; i < COMEDI_NUM_MINORS; ++i) + BUG_ON(comedi_file_info_table[i]); + + class_destroy(comedi_class); + cdev_del(&comedi_cdev); + unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS); + + comedi_proc_cleanup(); +} + +module_init(comedi_init); +module_exit(comedi_cleanup); + +void comedi_error(const struct comedi_device *dev, const char *s) +{ + printk(KERN_ERR "comedi%d: %s: %s\n", dev->minor, + dev->driver->driver_name, s); +} +EXPORT_SYMBOL(comedi_error); + +void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + unsigned runflags = 0; + unsigned runflags_mask = 0; + + /* DPRINTK("comedi_event 0x%x\n",mask); */ + + if ((comedi_get_subdevice_runflags(s) & SRF_RUNNING) == 0) + return; + + if (s-> + async->events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW)) { + runflags_mask |= SRF_RUNNING; + } + /* remember if an error event has occurred, so an error + * can be returned the next time the user does a read() */ + if (s->async->events & (COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) { + runflags_mask |= SRF_ERROR; + runflags |= SRF_ERROR; + } + if (runflags_mask) { + /*sets SRF_ERROR and SRF_RUNNING together atomically */ + comedi_set_subdevice_runflags(s, runflags_mask, runflags); + } + + if (async->cb_mask & s->async->events) { + if (comedi_get_subdevice_runflags(s) & SRF_USER) { + wake_up_interruptible(&async->wait_head); + if (s->subdev_flags & SDF_CMD_READ) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + if (s->subdev_flags & SDF_CMD_WRITE) + kill_fasync(&dev->async_queue, SIGIO, POLL_OUT); + } else { + if (async->cb_func) + async->cb_func(s->async->events, async->cb_arg); + } + } + s->async->events = 0; +} +EXPORT_SYMBOL(comedi_event); + +unsigned comedi_get_subdevice_runflags(struct comedi_subdevice *s) +{ + unsigned long flags; + unsigned runflags; + + spin_lock_irqsave(&s->spin_lock, flags); + runflags = s->runflags; + spin_unlock_irqrestore(&s->spin_lock, flags); + return runflags; +} +EXPORT_SYMBOL(comedi_get_subdevice_runflags); + +static int is_device_busy(struct comedi_device *dev) +{ + struct comedi_subdevice *s; + int i; + + if (!dev->attached) + return 0; + + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + if (s->busy) + return 1; + if (s->async && s->async->mmap_count) + return 1; + } + + return 0; +} + +static void comedi_device_init(struct comedi_device *dev) +{ + memset(dev, 0, sizeof(struct comedi_device)); + spin_lock_init(&dev->spinlock); + mutex_init(&dev->mutex); + dev->minor = -1; +} + +static void comedi_device_cleanup(struct comedi_device *dev) +{ + if (dev == NULL) + return; + mutex_lock(&dev->mutex); + comedi_device_detach(dev); + mutex_unlock(&dev->mutex); + mutex_destroy(&dev->mutex); +} + +int comedi_alloc_board_minor(struct device *hardware_device) +{ + unsigned long flags; + struct comedi_device_file_info *info; + struct device *csdev; + unsigned i; + int retval; + + info = kzalloc(sizeof(struct comedi_device_file_info), GFP_KERNEL); + if (info == NULL) + return -ENOMEM; + info->device = kzalloc(sizeof(struct comedi_device), GFP_KERNEL); + if (info->device == NULL) { + kfree(info); + return -ENOMEM; + } + comedi_device_init(info->device); + spin_lock_irqsave(&comedi_file_info_table_lock, flags); + for (i = 0; i < COMEDI_NUM_BOARD_MINORS; ++i) { + if (comedi_file_info_table[i] == NULL) { + comedi_file_info_table[i] = info; + break; + } + } + spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + if (i == COMEDI_NUM_BOARD_MINORS) { + comedi_device_cleanup(info->device); + kfree(info->device); + kfree(info); + printk(KERN_ERR + "comedi: error: " + "ran out of minor numbers for board device files.\n"); + return -EBUSY; + } + info->device->minor = i; + csdev = device_create(comedi_class, hardware_device, + MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i", i); + if (!IS_ERR(csdev)) + info->device->class_dev = csdev; + dev_set_drvdata(csdev, info); + retval = device_create_file(csdev, &dev_attr_max_read_buffer_kb); + if (retval) { + printk(KERN_ERR + "comedi: " + "failed to create sysfs attribute file \"%s\".\n", + dev_attr_max_read_buffer_kb.attr.name); + comedi_free_board_minor(i); + return retval; + } + retval = device_create_file(csdev, &dev_attr_read_buffer_kb); + if (retval) { + printk(KERN_ERR + "comedi: " + "failed to create sysfs attribute file \"%s\".\n", + dev_attr_read_buffer_kb.attr.name); + comedi_free_board_minor(i); + return retval; + } + retval = device_create_file(csdev, &dev_attr_max_write_buffer_kb); + if (retval) { + printk(KERN_ERR + "comedi: " + "failed to create sysfs attribute file \"%s\".\n", + dev_attr_max_write_buffer_kb.attr.name); + comedi_free_board_minor(i); + return retval; + } + retval = device_create_file(csdev, &dev_attr_write_buffer_kb); + if (retval) { + printk(KERN_ERR + "comedi: " + "failed to create sysfs attribute file \"%s\".\n", + dev_attr_write_buffer_kb.attr.name); + comedi_free_board_minor(i); + return retval; + } + return i; +} + +void comedi_free_board_minor(unsigned minor) +{ + unsigned long flags; + struct comedi_device_file_info *info; + + BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS); + spin_lock_irqsave(&comedi_file_info_table_lock, flags); + info = comedi_file_info_table[minor]; + comedi_file_info_table[minor] = NULL; + spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + + if (info) { + struct comedi_device *dev = info->device; + if (dev) { + if (dev->class_dev) { + device_destroy(comedi_class, + MKDEV(COMEDI_MAJOR, dev->minor)); + } + comedi_device_cleanup(dev); + kfree(dev); + } + kfree(info); + } +} + +int comedi_alloc_subdevice_minor(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long flags; + struct comedi_device_file_info *info; + struct device *csdev; + unsigned i; + int retval; + + info = kmalloc(sizeof(struct comedi_device_file_info), GFP_KERNEL); + if (info == NULL) + return -ENOMEM; + info->device = dev; + info->read_subdevice = s; + info->write_subdevice = s; + spin_lock_irqsave(&comedi_file_info_table_lock, flags); + for (i = COMEDI_FIRST_SUBDEVICE_MINOR; i < COMEDI_NUM_MINORS; ++i) { + if (comedi_file_info_table[i] == NULL) { + comedi_file_info_table[i] = info; + break; + } + } + spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + if (i == COMEDI_NUM_MINORS) { + kfree(info); + printk(KERN_ERR + "comedi: error: " + "ran out of minor numbers for board device files.\n"); + return -EBUSY; + } + s->minor = i; + csdev = device_create(comedi_class, dev->class_dev, + MKDEV(COMEDI_MAJOR, i), NULL, "comedi%i_subd%i", + dev->minor, (int)(s - dev->subdevices)); + if (!IS_ERR(csdev)) + s->class_dev = csdev; + dev_set_drvdata(csdev, info); + retval = device_create_file(csdev, &dev_attr_max_read_buffer_kb); + if (retval) { + printk(KERN_ERR + "comedi: " + "failed to create sysfs attribute file \"%s\".\n", + dev_attr_max_read_buffer_kb.attr.name); + comedi_free_subdevice_minor(s); + return retval; + } + retval = device_create_file(csdev, &dev_attr_read_buffer_kb); + if (retval) { + printk(KERN_ERR + "comedi: " + "failed to create sysfs attribute file \"%s\".\n", + dev_attr_read_buffer_kb.attr.name); + comedi_free_subdevice_minor(s); + return retval; + } + retval = device_create_file(csdev, &dev_attr_max_write_buffer_kb); + if (retval) { + printk(KERN_ERR + "comedi: " + "failed to create sysfs attribute file \"%s\".\n", + dev_attr_max_write_buffer_kb.attr.name); + comedi_free_subdevice_minor(s); + return retval; + } + retval = device_create_file(csdev, &dev_attr_write_buffer_kb); + if (retval) { + printk(KERN_ERR + "comedi: " + "failed to create sysfs attribute file \"%s\".\n", + dev_attr_write_buffer_kb.attr.name); + comedi_free_subdevice_minor(s); + return retval; + } + return i; +} + +void comedi_free_subdevice_minor(struct comedi_subdevice *s) +{ + unsigned long flags; + struct comedi_device_file_info *info; + + if (s == NULL) + return; + if (s->minor < 0) + return; + + BUG_ON(s->minor >= COMEDI_NUM_MINORS); + BUG_ON(s->minor < COMEDI_FIRST_SUBDEVICE_MINOR); + + spin_lock_irqsave(&comedi_file_info_table_lock, flags); + info = comedi_file_info_table[s->minor]; + comedi_file_info_table[s->minor] = NULL; + spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + + if (s->class_dev) { + device_destroy(comedi_class, MKDEV(COMEDI_MAJOR, s->minor)); + s->class_dev = NULL; + } + kfree(info); +} + +struct comedi_device_file_info *comedi_get_device_file_info(unsigned minor) +{ + unsigned long flags; + struct comedi_device_file_info *info; + + BUG_ON(minor >= COMEDI_NUM_MINORS); + spin_lock_irqsave(&comedi_file_info_table_lock, flags); + info = comedi_file_info_table[minor]; + spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + return info; +} +EXPORT_SYMBOL_GPL(comedi_get_device_file_info); + +static int resize_async_buffer(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_async *async, unsigned new_size) +{ + int retval; + + if (new_size > async->max_bufsize) + return -EPERM; + + if (s->busy) { + DPRINTK("subdevice is busy, cannot resize buffer\n"); + return -EBUSY; + } + if (async->mmap_count) { + DPRINTK("subdevice is mmapped, cannot resize buffer\n"); + return -EBUSY; + } + + if (!async->prealloc_buf) + return -EINVAL; + + /* make sure buffer is an integral number of pages + * (we round up) */ + new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK; + + retval = comedi_buf_alloc(dev, s, new_size); + if (retval < 0) + return retval; + + if (s->buf_change) { + retval = s->buf_change(dev, s, new_size); + if (retval < 0) + return retval; + } + + DPRINTK("comedi%i subd %d buffer resized to %i bytes\n", + dev->minor, (int)(s - dev->subdevices), async->prealloc_bufsz); + return 0; +} + +/* sysfs attribute files */ + +static const unsigned bytes_per_kibi = 1024; + +static ssize_t show_max_read_buffer_kb(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t retval; + struct comedi_device_file_info *info = dev_get_drvdata(dev); + unsigned max_buffer_size_kb = 0; + struct comedi_subdevice *const read_subdevice = + comedi_get_read_subdevice(info); + + mutex_lock(&info->device->mutex); + if (read_subdevice && + (read_subdevice->subdev_flags & SDF_CMD_READ) && + read_subdevice->async) { + max_buffer_size_kb = read_subdevice->async->max_bufsize / + bytes_per_kibi; + } + retval = snprintf(buf, PAGE_SIZE, "%i\n", max_buffer_size_kb); + mutex_unlock(&info->device->mutex); + + return retval; +} + +static ssize_t store_max_read_buffer_kb(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct comedi_device_file_info *info = dev_get_drvdata(dev); + unsigned int new_max_size_kb; + unsigned int new_max_size; + int ret; + struct comedi_subdevice *const read_subdevice = + comedi_get_read_subdevice(info); + + ret = kstrtouint(buf, 10, &new_max_size_kb); + if (ret) + return ret; + if (new_max_size_kb > (UINT_MAX / bytes_per_kibi)) + return -EINVAL; + new_max_size = new_max_size_kb * bytes_per_kibi; + + mutex_lock(&info->device->mutex); + if (read_subdevice == NULL || + (read_subdevice->subdev_flags & SDF_CMD_READ) == 0 || + read_subdevice->async == NULL) { + mutex_unlock(&info->device->mutex); + return -EINVAL; + } + read_subdevice->async->max_bufsize = new_max_size; + mutex_unlock(&info->device->mutex); + + return count; +} + +static struct device_attribute dev_attr_max_read_buffer_kb = { + .attr = { + .name = "max_read_buffer_kb", + .mode = S_IRUGO | S_IWUSR}, + .show = &show_max_read_buffer_kb, + .store = &store_max_read_buffer_kb +}; + +static ssize_t show_read_buffer_kb(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t retval; + struct comedi_device_file_info *info = dev_get_drvdata(dev); + unsigned buffer_size_kb = 0; + struct comedi_subdevice *const read_subdevice = + comedi_get_read_subdevice(info); + + mutex_lock(&info->device->mutex); + if (read_subdevice && + (read_subdevice->subdev_flags & SDF_CMD_READ) && + read_subdevice->async) { + buffer_size_kb = read_subdevice->async->prealloc_bufsz / + bytes_per_kibi; + } + retval = snprintf(buf, PAGE_SIZE, "%i\n", buffer_size_kb); + mutex_unlock(&info->device->mutex); + + return retval; +} + +static ssize_t store_read_buffer_kb(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct comedi_device_file_info *info = dev_get_drvdata(dev); + unsigned int new_size_kb; + unsigned int new_size; + int retval; + int ret; + struct comedi_subdevice *const read_subdevice = + comedi_get_read_subdevice(info); + + ret = kstrtouint(buf, 10, &new_size_kb); + if (ret) + return ret; + if (new_size_kb > (UINT_MAX / bytes_per_kibi)) + return -EINVAL; + new_size = new_size_kb * bytes_per_kibi; + + mutex_lock(&info->device->mutex); + if (read_subdevice == NULL || + (read_subdevice->subdev_flags & SDF_CMD_READ) == 0 || + read_subdevice->async == NULL) { + mutex_unlock(&info->device->mutex); + return -EINVAL; + } + retval = resize_async_buffer(info->device, read_subdevice, + read_subdevice->async, new_size); + mutex_unlock(&info->device->mutex); + + if (retval < 0) + return retval; + return count; +} + +static struct device_attribute dev_attr_read_buffer_kb = { + .attr = { + .name = "read_buffer_kb", + .mode = S_IRUGO | S_IWUSR | S_IWGRP}, + .show = &show_read_buffer_kb, + .store = &store_read_buffer_kb +}; + +static ssize_t show_max_write_buffer_kb(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t retval; + struct comedi_device_file_info *info = dev_get_drvdata(dev); + unsigned max_buffer_size_kb = 0; + struct comedi_subdevice *const write_subdevice = + comedi_get_write_subdevice(info); + + mutex_lock(&info->device->mutex); + if (write_subdevice && + (write_subdevice->subdev_flags & SDF_CMD_WRITE) && + write_subdevice->async) { + max_buffer_size_kb = write_subdevice->async->max_bufsize / + bytes_per_kibi; + } + retval = snprintf(buf, PAGE_SIZE, "%i\n", max_buffer_size_kb); + mutex_unlock(&info->device->mutex); + + return retval; +} + +static ssize_t store_max_write_buffer_kb(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct comedi_device_file_info *info = dev_get_drvdata(dev); + unsigned int new_max_size_kb; + unsigned int new_max_size; + int ret; + struct comedi_subdevice *const write_subdevice = + comedi_get_write_subdevice(info); + + ret = kstrtouint(buf, 10, &new_max_size_kb); + if (ret) + return ret; + if (new_max_size_kb > (UINT_MAX / bytes_per_kibi)) + return -EINVAL; + new_max_size = new_max_size_kb * bytes_per_kibi; + + mutex_lock(&info->device->mutex); + if (write_subdevice == NULL || + (write_subdevice->subdev_flags & SDF_CMD_WRITE) == 0 || + write_subdevice->async == NULL) { + mutex_unlock(&info->device->mutex); + return -EINVAL; + } + write_subdevice->async->max_bufsize = new_max_size; + mutex_unlock(&info->device->mutex); + + return count; +} + +static struct device_attribute dev_attr_max_write_buffer_kb = { + .attr = { + .name = "max_write_buffer_kb", + .mode = S_IRUGO | S_IWUSR}, + .show = &show_max_write_buffer_kb, + .store = &store_max_write_buffer_kb +}; + +static ssize_t show_write_buffer_kb(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t retval; + struct comedi_device_file_info *info = dev_get_drvdata(dev); + unsigned buffer_size_kb = 0; + struct comedi_subdevice *const write_subdevice = + comedi_get_write_subdevice(info); + + mutex_lock(&info->device->mutex); + if (write_subdevice && + (write_subdevice->subdev_flags & SDF_CMD_WRITE) && + write_subdevice->async) { + buffer_size_kb = write_subdevice->async->prealloc_bufsz / + bytes_per_kibi; + } + retval = snprintf(buf, PAGE_SIZE, "%i\n", buffer_size_kb); + mutex_unlock(&info->device->mutex); + + return retval; +} + +static ssize_t store_write_buffer_kb(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct comedi_device_file_info *info = dev_get_drvdata(dev); + unsigned int new_size_kb; + unsigned int new_size; + int retval; + int ret; + struct comedi_subdevice *const write_subdevice = + comedi_get_write_subdevice(info); + + ret = kstrtouint(buf, 10, &new_size_kb); + if (ret) + return ret; + if (new_size_kb > (UINT_MAX / bytes_per_kibi)) + return -EINVAL; + new_size = ((uint64_t) new_size_kb) * bytes_per_kibi; + + mutex_lock(&info->device->mutex); + if (write_subdevice == NULL || + (write_subdevice->subdev_flags & SDF_CMD_WRITE) == 0 || + write_subdevice->async == NULL) { + mutex_unlock(&info->device->mutex); + return -EINVAL; + } + retval = resize_async_buffer(info->device, write_subdevice, + write_subdevice->async, new_size); + mutex_unlock(&info->device->mutex); + + if (retval < 0) + return retval; + return count; +} + +static struct device_attribute dev_attr_write_buffer_kb = { + .attr = { + .name = "write_buffer_kb", + .mode = S_IRUGO | S_IWUSR | S_IWGRP}, + .show = &show_write_buffer_kb, + .store = &store_write_buffer_kb +}; diff --git a/drivers/staging/comedi/comedi_fops.h b/drivers/staging/comedi/comedi_fops.h new file mode 100644 index 00000000..006cf14c --- /dev/null +++ b/drivers/staging/comedi/comedi_fops.h @@ -0,0 +1,11 @@ + +#ifndef _COMEDI_FOPS_H +#define _COMEDI_FOPS_H +#include + +extern struct class *comedi_class; +extern const struct file_operations comedi_fops; +extern bool comedi_autoconfig; +extern struct comedi_driver *comedi_drivers; + +#endif /* _COMEDI_FOPS_H */ diff --git a/drivers/staging/comedi/comedidev.h b/drivers/staging/comedi/comedidev.h new file mode 100644 index 00000000..7a0d4bcb --- /dev/null +++ b/drivers/staging/comedi/comedidev.h @@ -0,0 +1,478 @@ +/* + include/linux/comedidev.h + header file for kernel-only structures, variables, and constants + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _COMEDIDEV_H +#define _COMEDIDEV_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "comedi.h" + +#define DPRINTK(format, args...) do { \ + if (comedi_debug) \ + printk(KERN_DEBUG "comedi: " format , ## args); \ +} while (0) + +#define COMEDI_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c)) +#define COMEDI_VERSION_CODE COMEDI_VERSION(COMEDI_MAJORVERSION, \ + COMEDI_MINORVERSION, COMEDI_MICROVERSION) +#define COMEDI_RELEASE VERSION + +#define PCI_VENDOR_ID_ADLINK 0x144a +#define PCI_VENDOR_ID_ICP 0x104c +#define PCI_VENDOR_ID_CONTEC 0x1221 + +#define COMEDI_NUM_MINORS 0x100 +#define COMEDI_NUM_BOARD_MINORS 0x30 +#define COMEDI_FIRST_SUBDEVICE_MINOR COMEDI_NUM_BOARD_MINORS + +struct comedi_subdevice { + struct comedi_device *device; + int type; + int n_chan; + int subdev_flags; + int len_chanlist; /* maximum length of channel/gain list */ + + void *private; + + struct comedi_async *async; + + void *lock; + void *busy; + unsigned runflags; + spinlock_t spin_lock; + + int io_bits; + + unsigned int maxdata; /* if maxdata==0, use list */ + const unsigned int *maxdata_list; /* list is channel specific */ + + unsigned int flags; + const unsigned int *flaglist; + + unsigned int settling_time_0; + + const struct comedi_lrange *range_table; + const struct comedi_lrange *const *range_table_list; + + unsigned int *chanlist; /* driver-owned chanlist (not used) */ + + int (*insn_read) (struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*insn_write) (struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*insn_bits) (struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*insn_config) (struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + + int (*do_cmd) (struct comedi_device *, struct comedi_subdevice *); + int (*do_cmdtest) (struct comedi_device *, struct comedi_subdevice *, + struct comedi_cmd *); + int (*poll) (struct comedi_device *, struct comedi_subdevice *); + int (*cancel) (struct comedi_device *, struct comedi_subdevice *); + /* int (*do_lock)(struct comedi_device *, struct comedi_subdevice *); */ + /* int (*do_unlock)(struct comedi_device *, \ + struct comedi_subdevice *); */ + + /* called when the buffer changes */ + int (*buf_change) (struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size); + + void (*munge) (struct comedi_device *dev, struct comedi_subdevice *s, + void *data, unsigned int num_bytes, + unsigned int start_chan_index); + enum dma_data_direction async_dma_dir; + + unsigned int state; + + struct device *class_dev; + int minor; +}; + +struct comedi_buf_page { + void *virt_addr; + dma_addr_t dma_addr; +}; + +struct comedi_async { + struct comedi_subdevice *subdevice; + + void *prealloc_buf; /* pre-allocated buffer */ + unsigned int prealloc_bufsz; /* buffer size, in bytes */ + /* virtual and dma address of each page */ + struct comedi_buf_page *buf_page_list; + unsigned n_buf_pages; /* num elements in buf_page_list */ + + unsigned int max_bufsize; /* maximum buffer size, bytes */ + /* current number of mmaps of prealloc_buf */ + unsigned int mmap_count; + + /* byte count for writer (write completed) */ + unsigned int buf_write_count; + /* byte count for writer (allocated for writing) */ + unsigned int buf_write_alloc_count; + /* byte count for reader (read completed) */ + unsigned int buf_read_count; + /* byte count for reader (allocated for reading) */ + unsigned int buf_read_alloc_count; + + unsigned int buf_write_ptr; /* buffer marker for writer */ + unsigned int buf_read_ptr; /* buffer marker for reader */ + + unsigned int cur_chan; /* useless channel marker for interrupt */ + /* number of bytes that have been received for current scan */ + unsigned int scan_progress; + /* keeps track of where we are in chanlist as for munging */ + unsigned int munge_chan; + /* number of bytes that have been munged */ + unsigned int munge_count; + /* buffer marker for munging */ + unsigned int munge_ptr; + + unsigned int events; /* events that have occurred */ + + struct comedi_cmd cmd; + + wait_queue_head_t wait_head; + + /* callback stuff */ + unsigned int cb_mask; + int (*cb_func) (unsigned int flags, void *); + void *cb_arg; + + int (*inttrig) (struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int x); +}; + +struct comedi_driver { + struct comedi_driver *next; + + const char *driver_name; + struct module *module; + int (*attach) (struct comedi_device *, struct comedi_devconfig *); + int (*detach) (struct comedi_device *); + + /* number of elements in board_name and board_id arrays */ + unsigned int num_names; + const char *const *board_name; + /* offset in bytes from one board name pointer to the next */ + int offset; +}; + +struct comedi_device { + int use_count; + struct comedi_driver *driver; + void *private; + + struct device *class_dev; + int minor; + /* hw_dev is passed to dma_alloc_coherent when allocating async buffers + * for subdevices that have async_dma_dir set to something other than + * DMA_NONE */ + struct device *hw_dev; + + const char *board_name; + const void *board_ptr; + int attached; + spinlock_t spinlock; + struct mutex mutex; + int in_request_module; + + int n_subdevices; + struct comedi_subdevice *subdevices; + + /* dumb */ + unsigned long iobase; + unsigned int irq; + + struct comedi_subdevice *read_subdev; + struct comedi_subdevice *write_subdev; + + struct fasync_struct *async_queue; + + int (*open) (struct comedi_device *dev); + void (*close) (struct comedi_device *dev); +}; + +struct comedi_device_file_info { + struct comedi_device *device; + struct comedi_subdevice *read_subdevice; + struct comedi_subdevice *write_subdevice; +}; + +#ifdef CONFIG_COMEDI_DEBUG +extern int comedi_debug; +#else +static const int comedi_debug; +#endif + +/* + * function prototypes + */ + +void comedi_event(struct comedi_device *dev, struct comedi_subdevice *s); +void comedi_error(const struct comedi_device *dev, const char *s); + +/* we can expand the number of bits used to encode devices/subdevices into + the minor number soon, after more distros support > 8 bit minor numbers + (like after Debian Etch gets released) */ +enum comedi_minor_bits { + COMEDI_DEVICE_MINOR_MASK = 0xf, + COMEDI_SUBDEVICE_MINOR_MASK = 0xf0 +}; +static const unsigned COMEDI_SUBDEVICE_MINOR_SHIFT = 4; +static const unsigned COMEDI_SUBDEVICE_MINOR_OFFSET = 1; + +struct comedi_device_file_info *comedi_get_device_file_info(unsigned minor); + +static inline struct comedi_subdevice *comedi_get_read_subdevice( + const struct comedi_device_file_info *info) +{ + if (info->read_subdevice) + return info->read_subdevice; + if (info->device == NULL) + return NULL; + return info->device->read_subdev; +} + +static inline struct comedi_subdevice *comedi_get_write_subdevice( + const struct comedi_device_file_info *info) +{ + if (info->write_subdevice) + return info->write_subdevice; + if (info->device == NULL) + return NULL; + return info->device->write_subdev; +} + +void comedi_device_detach(struct comedi_device *dev); +int comedi_device_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +int comedi_driver_register(struct comedi_driver *); +int comedi_driver_unregister(struct comedi_driver *); + +void init_polling(void); +void cleanup_polling(void); +void start_polling(struct comedi_device *); +void stop_polling(struct comedi_device *); + +#ifdef CONFIG_PROC_FS +void comedi_proc_init(void); +void comedi_proc_cleanup(void); +#else +static inline void comedi_proc_init(void) +{ +} + +static inline void comedi_proc_cleanup(void) +{ +} +#endif + +/* subdevice runflags */ +enum subdevice_runflags { + SRF_USER = 0x00000001, + SRF_RT = 0x00000002, + /* indicates an COMEDI_CB_ERROR event has occurred since the last + * command was started */ + SRF_ERROR = 0x00000004, + SRF_RUNNING = 0x08000000 +}; + +int comedi_check_chanlist(struct comedi_subdevice *s, + int n, + unsigned int *chanlist); +unsigned comedi_get_subdevice_runflags(struct comedi_subdevice *s); + +/* range stuff */ + +#define RANGE(a, b) {(a)*1e6, (b)*1e6, 0} +#define RANGE_ext(a, b) {(a)*1e6, (b)*1e6, RF_EXTERNAL} +#define RANGE_mA(a, b) {(a)*1e6, (b)*1e6, UNIT_mA} +#define RANGE_unitless(a, b) {(a)*1e6, (b)*1e6, 0} +#define BIP_RANGE(a) {-(a)*1e6, (a)*1e6, 0} +#define UNI_RANGE(a) {0, (a)*1e6, 0} + +extern const struct comedi_lrange range_bipolar10; +extern const struct comedi_lrange range_bipolar5; +extern const struct comedi_lrange range_bipolar2_5; +extern const struct comedi_lrange range_unipolar10; +extern const struct comedi_lrange range_unipolar5; +extern const struct comedi_lrange range_unknown; + +#define range_digital range_unipolar5 + +#if __GNUC__ >= 3 +#define GCC_ZERO_LENGTH_ARRAY +#else +#define GCC_ZERO_LENGTH_ARRAY 0 +#endif + +struct comedi_lrange { + int length; + struct comedi_krange range[GCC_ZERO_LENGTH_ARRAY]; +}; + +/* some silly little inline functions */ + +static inline int alloc_subdevices(struct comedi_device *dev, + unsigned int num_subdevices) +{ + unsigned i; + + dev->n_subdevices = num_subdevices; + dev->subdevices = + kcalloc(num_subdevices, sizeof(struct comedi_subdevice), + GFP_KERNEL); + if (!dev->subdevices) + return -ENOMEM; + for (i = 0; i < num_subdevices; ++i) { + dev->subdevices[i].device = dev; + dev->subdevices[i].async_dma_dir = DMA_NONE; + spin_lock_init(&dev->subdevices[i].spin_lock); + dev->subdevices[i].minor = -1; + } + return 0; +} + +static inline int alloc_private(struct comedi_device *dev, int size) +{ + dev->private = kzalloc(size, GFP_KERNEL); + if (!dev->private) + return -ENOMEM; + return 0; +} + +static inline unsigned int bytes_per_sample(const struct comedi_subdevice *subd) +{ + if (subd->subdev_flags & SDF_LSAMPL) + return sizeof(unsigned int); + else + return sizeof(short); +} + +/* must be used in attach to set dev->hw_dev if you wish to dma directly +into comedi's buffer */ +static inline void comedi_set_hw_dev(struct comedi_device *dev, + struct device *hw_dev) +{ + if (dev->hw_dev) + put_device(dev->hw_dev); + + dev->hw_dev = hw_dev; + if (dev->hw_dev) { + dev->hw_dev = get_device(dev->hw_dev); + BUG_ON(dev->hw_dev == NULL); + } +} + +int comedi_buf_put(struct comedi_async *async, short x); +int comedi_buf_get(struct comedi_async *async, short *x); + +unsigned int comedi_buf_write_n_available(struct comedi_async *async); +unsigned int comedi_buf_write_alloc(struct comedi_async *async, + unsigned int nbytes); +unsigned int comedi_buf_write_alloc_strict(struct comedi_async *async, + unsigned int nbytes); +unsigned comedi_buf_write_free(struct comedi_async *async, unsigned int nbytes); +unsigned comedi_buf_read_alloc(struct comedi_async *async, unsigned nbytes); +unsigned comedi_buf_read_free(struct comedi_async *async, unsigned int nbytes); +unsigned int comedi_buf_read_n_available(struct comedi_async *async); +void comedi_buf_memcpy_to(struct comedi_async *async, unsigned int offset, + const void *source, unsigned int num_bytes); +void comedi_buf_memcpy_from(struct comedi_async *async, unsigned int offset, + void *destination, unsigned int num_bytes); +static inline unsigned comedi_buf_write_n_allocated(struct comedi_async *async) +{ + return async->buf_write_alloc_count - async->buf_write_count; +} + +static inline unsigned comedi_buf_read_n_allocated(struct comedi_async *async) +{ + return async->buf_read_alloc_count - async->buf_read_count; +} + +static inline void *comedi_aux_data(int options[], int n) +{ + unsigned long address; + unsigned long addressLow; + int bit_shift; + if (sizeof(int) >= sizeof(void *)) + address = options[COMEDI_DEVCONF_AUX_DATA_LO]; + else { + address = options[COMEDI_DEVCONF_AUX_DATA_HI]; + bit_shift = sizeof(int) * 8; + address <<= bit_shift; + addressLow = options[COMEDI_DEVCONF_AUX_DATA_LO]; + addressLow &= (1UL << bit_shift) - 1; + address |= addressLow; + } + if (n >= 1) + address += options[COMEDI_DEVCONF_AUX_DATA0_LENGTH]; + if (n >= 2) + address += options[COMEDI_DEVCONF_AUX_DATA1_LENGTH]; + if (n >= 3) + address += options[COMEDI_DEVCONF_AUX_DATA2_LENGTH]; + BUG_ON(n > 3); + return (void *)address; +} + +int comedi_alloc_subdevice_minor(struct comedi_device *dev, + struct comedi_subdevice *s); +void comedi_free_subdevice_minor(struct comedi_subdevice *s); +int comedi_pci_auto_config(struct pci_dev *pcidev, const char *board_name); +void comedi_pci_auto_unconfig(struct pci_dev *pcidev); +struct usb_device; /* forward declaration */ +int comedi_usb_auto_config(struct usb_device *usbdev, const char *board_name); +void comedi_usb_auto_unconfig(struct usb_device *usbdev); + +#ifdef CONFIG_COMEDI_PCI_DRIVERS +#define CONFIG_COMEDI_PCI +#endif +#ifdef CONFIG_COMEDI_PCI_DRIVERS_MODULE +#define CONFIG_COMEDI_PCI +#endif +#ifdef CONFIG_COMEDI_PCMCIA_DRIVERS +#define CONFIG_COMEDI_PCMCIA +#endif +#ifdef CONFIG_COMEDI_PCMCIA_DRIVERS_MODULE +#define CONFIG_COMEDI_PCMCIA +#endif + +#endif /* _COMEDIDEV_H */ diff --git a/drivers/staging/comedi/comedilib.h b/drivers/staging/comedi/comedilib.h new file mode 100644 index 00000000..ca92c43f --- /dev/null +++ b/drivers/staging/comedi/comedilib.h @@ -0,0 +1,37 @@ +/* + linux/include/comedilib.h + header file for kcomedilib + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998-2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _LINUX_COMEDILIB_H +#define _LINUX_COMEDILIB_H + +struct comedi_device *comedi_open(const char *path); +int comedi_close(struct comedi_device *dev); +int comedi_dio_config(struct comedi_device *dev, unsigned int subdev, + unsigned int chan, unsigned int io); +int comedi_dio_bitfield(struct comedi_device *dev, unsigned int subdev, + unsigned int mask, unsigned int *bits); +int comedi_find_subdevice_by_type(struct comedi_device *dev, int type, + unsigned int subd); +int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice); + +#endif diff --git a/drivers/staging/comedi/drivers.c b/drivers/staging/comedi/drivers.c new file mode 100644 index 00000000..bf185e28 --- /dev/null +++ b/drivers/staging/comedi/drivers.c @@ -0,0 +1,909 @@ +/* + module/drivers.c + functions for manipulating drivers + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#define _GNU_SOURCE + +#define __NO_VERSION__ +#include "comedi_fops.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for SuSE brokenness */ +#include +#include +#include +#include + +#include "comedidev.h" +#include "internal.h" + +static int postconfig(struct comedi_device *dev); +static int insn_rw_emulate_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static void *comedi_recognize(struct comedi_driver *driv, const char *name); +static void comedi_report_boards(struct comedi_driver *driv); +static int poll_invalid(struct comedi_device *dev, struct comedi_subdevice *s); + +struct comedi_driver *comedi_drivers; + +static void cleanup_device(struct comedi_device *dev) +{ + int i; + struct comedi_subdevice *s; + + if (dev->subdevices) { + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + comedi_free_subdevice_minor(s); + if (s->async) { + comedi_buf_alloc(dev, s, 0); + kfree(s->async); + } + } + kfree(dev->subdevices); + dev->subdevices = NULL; + dev->n_subdevices = 0; + } + kfree(dev->private); + dev->private = NULL; + dev->driver = NULL; + dev->board_name = NULL; + dev->board_ptr = NULL; + dev->iobase = 0; + dev->irq = 0; + dev->read_subdev = NULL; + dev->write_subdev = NULL; + dev->open = NULL; + dev->close = NULL; + comedi_set_hw_dev(dev, NULL); +} + +static void __comedi_device_detach(struct comedi_device *dev) +{ + dev->attached = 0; + if (dev->driver) + dev->driver->detach(dev); + else + printk(KERN_WARNING + "BUG: dev->driver=NULL in comedi_device_detach()\n"); + cleanup_device(dev); +} + +void comedi_device_detach(struct comedi_device *dev) +{ + if (!dev->attached) + return; + __comedi_device_detach(dev); +} + +int comedi_device_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_driver *driv; + int ret; + + if (dev->attached) + return -EBUSY; + + for (driv = comedi_drivers; driv; driv = driv->next) { + if (!try_module_get(driv->module)) { + printk(KERN_INFO "comedi: failed to increment module count, skipping\n"); + continue; + } + if (driv->num_names) { + dev->board_ptr = comedi_recognize(driv, it->board_name); + if (dev->board_ptr == NULL) { + module_put(driv->module); + continue; + } + } else { + if (strcmp(driv->driver_name, it->board_name)) { + module_put(driv->module); + continue; + } + } + /* initialize dev->driver here so + * comedi_error() can be called from attach */ + dev->driver = driv; + ret = driv->attach(dev, it); + if (ret < 0) { + module_put(dev->driver->module); + __comedi_device_detach(dev); + return ret; + } + goto attached; + } + + /* recognize has failed if we get here */ + /* report valid board names before returning error */ + for (driv = comedi_drivers; driv; driv = driv->next) { + if (!try_module_get(driv->module)) { + printk(KERN_INFO + "comedi: failed to increment module count\n"); + continue; + } + comedi_report_boards(driv); + module_put(driv->module); + } + return -EIO; + +attached: + /* do a little post-config cleanup */ + ret = postconfig(dev); + module_put(dev->driver->module); + if (ret < 0) { + __comedi_device_detach(dev); + return ret; + } + + if (!dev->board_name) { + printk(KERN_WARNING "BUG: dev->board_name=<%p>\n", + dev->board_name); + dev->board_name = "BUG"; + } + smp_wmb(); + dev->attached = 1; + + return 0; +} + +int comedi_driver_register(struct comedi_driver *driver) +{ + driver->next = comedi_drivers; + comedi_drivers = driver; + + return 0; +} +EXPORT_SYMBOL(comedi_driver_register); + +int comedi_driver_unregister(struct comedi_driver *driver) +{ + struct comedi_driver *prev; + int i; + + /* check for devices using this driver */ + for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) { + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(i); + struct comedi_device *dev; + + if (dev_file_info == NULL) + continue; + dev = dev_file_info->device; + + mutex_lock(&dev->mutex); + if (dev->attached && dev->driver == driver) { + if (dev->use_count) + printk(KERN_WARNING "BUG! detaching device with use_count=%d\n", + dev->use_count); + comedi_device_detach(dev); + } + mutex_unlock(&dev->mutex); + } + + if (comedi_drivers == driver) { + comedi_drivers = driver->next; + return 0; + } + + for (prev = comedi_drivers; prev->next; prev = prev->next) { + if (prev->next == driver) { + prev->next = driver->next; + return 0; + } + } + return -EINVAL; +} +EXPORT_SYMBOL(comedi_driver_unregister); + +static int postconfig(struct comedi_device *dev) +{ + int i; + struct comedi_subdevice *s; + struct comedi_async *async = NULL; + int ret; + + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + + if (s->type == COMEDI_SUBD_UNUSED) + continue; + + if (s->len_chanlist == 0) + s->len_chanlist = 1; + + if (s->do_cmd) { + BUG_ON((s->subdev_flags & (SDF_CMD_READ | + SDF_CMD_WRITE)) == 0); + BUG_ON(!s->do_cmdtest); + + async = + kzalloc(sizeof(struct comedi_async), GFP_KERNEL); + if (async == NULL) { + printk(KERN_INFO + "failed to allocate async struct\n"); + return -ENOMEM; + } + init_waitqueue_head(&async->wait_head); + async->subdevice = s; + s->async = async; + +#define DEFAULT_BUF_MAXSIZE (64*1024) +#define DEFAULT_BUF_SIZE (64*1024) + + async->max_bufsize = DEFAULT_BUF_MAXSIZE; + + async->prealloc_buf = NULL; + async->prealloc_bufsz = 0; + if (comedi_buf_alloc(dev, s, DEFAULT_BUF_SIZE) < 0) { + printk(KERN_INFO "Buffer allocation failed\n"); + return -ENOMEM; + } + if (s->buf_change) { + ret = s->buf_change(dev, s, DEFAULT_BUF_SIZE); + if (ret < 0) + return ret; + } + comedi_alloc_subdevice_minor(dev, s); + } + + if (!s->range_table && !s->range_table_list) + s->range_table = &range_unknown; + + if (!s->insn_read && s->insn_bits) + s->insn_read = insn_rw_emulate_bits; + if (!s->insn_write && s->insn_bits) + s->insn_write = insn_rw_emulate_bits; + + if (!s->insn_read) + s->insn_read = insn_inval; + if (!s->insn_write) + s->insn_write = insn_inval; + if (!s->insn_bits) + s->insn_bits = insn_inval; + if (!s->insn_config) + s->insn_config = insn_inval; + + if (!s->poll) + s->poll = poll_invalid; + } + + return 0; +} + +/* generic recognize function for drivers + * that register their supported board names */ +static void *comedi_recognize(struct comedi_driver *driv, const char *name) +{ + unsigned i; + const char *const *name_ptr = driv->board_name; + for (i = 0; i < driv->num_names; i++) { + if (strcmp(*name_ptr, name) == 0) + return (void *)name_ptr; + name_ptr = + (const char *const *)((const char *)name_ptr + + driv->offset); + } + + return NULL; +} + +static void comedi_report_boards(struct comedi_driver *driv) +{ + unsigned int i; + const char *const *name_ptr; + + printk(KERN_INFO "comedi: valid board names for %s driver are:\n", + driv->driver_name); + + name_ptr = driv->board_name; + for (i = 0; i < driv->num_names; i++) { + printk(KERN_INFO " %s\n", *name_ptr); + name_ptr = (const char **)((char *)name_ptr + driv->offset); + } + + if (driv->num_names == 0) + printk(KERN_INFO " %s\n", driv->driver_name); +} + +static int poll_invalid(struct comedi_device *dev, struct comedi_subdevice *s) +{ + return -EINVAL; +} + +int insn_inval(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + return -EINVAL; +} + +static int insn_rw_emulate_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct comedi_insn new_insn; + int ret; + static const unsigned channels_per_bitfield = 32; + + unsigned chan = CR_CHAN(insn->chanspec); + const unsigned base_bitfield_channel = + (chan < channels_per_bitfield) ? 0 : chan; + unsigned int new_data[2]; + memset(new_data, 0, sizeof(new_data)); + memset(&new_insn, 0, sizeof(new_insn)); + new_insn.insn = INSN_BITS; + new_insn.chanspec = base_bitfield_channel; + new_insn.n = 2; + new_insn.data = new_data; + new_insn.subdev = insn->subdev; + + if (insn->insn == INSN_WRITE) { + if (!(s->subdev_flags & SDF_WRITABLE)) + return -EINVAL; + new_data[0] = 1 << (chan - base_bitfield_channel); /* mask */ + new_data[1] = data[0] ? (1 << (chan - base_bitfield_channel)) + : 0; /* bits */ + } + + ret = s->insn_bits(dev, s, &new_insn, new_data); + if (ret < 0) + return ret; + + if (insn->insn == INSN_READ) + data[0] = (new_data[1] >> (chan - base_bitfield_channel)) & 1; + + return 1; +} + +static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr) +{ + unsigned long ret = 0UL; + pmd_t *pmd; + pte_t *ptep, pte; + pud_t *pud; + + if (!pgd_none(*pgd)) { + pud = pud_offset(pgd, adr); + pmd = pmd_offset(pud, adr); + if (!pmd_none(*pmd)) { + ptep = pte_offset_kernel(pmd, adr); + pte = *ptep; + if (pte_present(pte)) { + ret = (unsigned long) + page_address(pte_page(pte)); + ret |= (adr & (PAGE_SIZE - 1)); + } + } + } + return ret; +} + +static inline unsigned long kvirt_to_kva(unsigned long adr) +{ + unsigned long va, kva; + + va = adr; + kva = uvirt_to_kva(pgd_offset_k(va), va); + + return kva; +} + +int comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned long new_size) +{ + struct comedi_async *async = s->async; + + /* Round up new_size to multiple of PAGE_SIZE */ + new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK; + + /* if no change is required, do nothing */ + if (async->prealloc_buf && async->prealloc_bufsz == new_size) + return 0; + + /* deallocate old buffer */ + if (async->prealloc_buf) { + vunmap(async->prealloc_buf); + async->prealloc_buf = NULL; + async->prealloc_bufsz = 0; + } + if (async->buf_page_list) { + unsigned i; + for (i = 0; i < async->n_buf_pages; ++i) { + if (async->buf_page_list[i].virt_addr) { + clear_bit(PG_reserved, + &(virt_to_page(async->buf_page_list[i]. + virt_addr)->flags)); + if (s->async_dma_dir != DMA_NONE) { + dma_free_coherent(dev->hw_dev, + PAGE_SIZE, + async-> + buf_page_list + [i].virt_addr, + async-> + buf_page_list + [i].dma_addr); + } else { + free_page((unsigned long) + async->buf_page_list[i]. + virt_addr); + } + } + } + vfree(async->buf_page_list); + async->buf_page_list = NULL; + async->n_buf_pages = 0; + } + /* allocate new buffer */ + if (new_size) { + unsigned i = 0; + unsigned n_pages = new_size >> PAGE_SHIFT; + struct page **pages = NULL; + + async->buf_page_list = + vzalloc(sizeof(struct comedi_buf_page) * n_pages); + if (async->buf_page_list) + pages = vmalloc(sizeof(struct page *) * n_pages); + + if (pages) { + for (i = 0; i < n_pages; i++) { + if (s->async_dma_dir != DMA_NONE) { + async->buf_page_list[i].virt_addr = + dma_alloc_coherent(dev->hw_dev, + PAGE_SIZE, + &async-> + buf_page_list + [i].dma_addr, + GFP_KERNEL | + __GFP_COMP); + } else { + async->buf_page_list[i].virt_addr = + (void *) + get_zeroed_page(GFP_KERNEL); + } + if (async->buf_page_list[i].virt_addr == NULL) + break; + + set_bit(PG_reserved, + &(virt_to_page(async->buf_page_list[i]. + virt_addr)->flags)); + pages[i] = virt_to_page(async->buf_page_list[i]. + virt_addr); + } + } + if (i == n_pages) { + async->prealloc_buf = +#ifdef PAGE_KERNEL_NOCACHE + vmap(pages, n_pages, VM_MAP, PAGE_KERNEL_NOCACHE); +#else + vmap(pages, n_pages, VM_MAP, PAGE_KERNEL); +#endif + } + vfree(pages); + + if (async->prealloc_buf == NULL) { + /* Some allocation failed above. */ + if (async->buf_page_list) { + for (i = 0; i < n_pages; i++) { + if (async->buf_page_list[i].virt_addr == + NULL) { + break; + } + clear_bit(PG_reserved, + &(virt_to_page(async-> + buf_page_list[i]. + virt_addr)->flags)); + if (s->async_dma_dir != DMA_NONE) { + dma_free_coherent(dev->hw_dev, + PAGE_SIZE, + async-> + buf_page_list + [i].virt_addr, + async-> + buf_page_list + [i].dma_addr); + } else { + free_page((unsigned long) + async->buf_page_list + [i].virt_addr); + } + } + vfree(async->buf_page_list); + async->buf_page_list = NULL; + } + return -ENOMEM; + } + async->n_buf_pages = n_pages; + } + async->prealloc_bufsz = new_size; + + return 0; +} + +/* munging is applied to data by core as it passes between user + * and kernel space */ +static unsigned int comedi_buf_munge(struct comedi_async *async, + unsigned int num_bytes) +{ + struct comedi_subdevice *s = async->subdevice; + unsigned int count = 0; + const unsigned num_sample_bytes = bytes_per_sample(s); + + if (s->munge == NULL || (async->cmd.flags & CMDF_RAWDATA)) { + async->munge_count += num_bytes; + BUG_ON((int)(async->munge_count - async->buf_write_count) > 0); + return num_bytes; + } + /* don't munge partial samples */ + num_bytes -= num_bytes % num_sample_bytes; + while (count < num_bytes) { + int block_size; + + block_size = num_bytes - count; + if (block_size < 0) { + printk(KERN_WARNING + "%s: %s: bug! block_size is negative\n", + __FILE__, __func__); + break; + } + if ((int)(async->munge_ptr + block_size - + async->prealloc_bufsz) > 0) + block_size = async->prealloc_bufsz - async->munge_ptr; + + s->munge(s->device, s, async->prealloc_buf + async->munge_ptr, + block_size, async->munge_chan); + + smp_wmb(); /* barrier insures data is munged in buffer + * before munge_count is incremented */ + + async->munge_chan += block_size / num_sample_bytes; + async->munge_chan %= async->cmd.chanlist_len; + async->munge_count += block_size; + async->munge_ptr += block_size; + async->munge_ptr %= async->prealloc_bufsz; + count += block_size; + } + BUG_ON((int)(async->munge_count - async->buf_write_count) > 0); + return count; +} + +unsigned int comedi_buf_write_n_available(struct comedi_async *async) +{ + unsigned int free_end; + unsigned int nbytes; + + if (async == NULL) + return 0; + + free_end = async->buf_read_count + async->prealloc_bufsz; + nbytes = free_end - async->buf_write_alloc_count; + nbytes -= nbytes % bytes_per_sample(async->subdevice); + /* barrier insures the read of buf_read_count in this + query occurs before any following writes to the buffer which + might be based on the return value from this query. + */ + smp_mb(); + return nbytes; +} + +/* allocates chunk for the writer from free buffer space */ +unsigned int comedi_buf_write_alloc(struct comedi_async *async, + unsigned int nbytes) +{ + unsigned int free_end = async->buf_read_count + async->prealloc_bufsz; + + if ((int)(async->buf_write_alloc_count + nbytes - free_end) > 0) + nbytes = free_end - async->buf_write_alloc_count; + + async->buf_write_alloc_count += nbytes; + /* barrier insures the read of buf_read_count above occurs before + we write data to the write-alloc'ed buffer space */ + smp_mb(); + return nbytes; +} +EXPORT_SYMBOL(comedi_buf_write_alloc); + +/* allocates nothing unless it can completely fulfill the request */ +unsigned int comedi_buf_write_alloc_strict(struct comedi_async *async, + unsigned int nbytes) +{ + unsigned int free_end = async->buf_read_count + async->prealloc_bufsz; + + if ((int)(async->buf_write_alloc_count + nbytes - free_end) > 0) + nbytes = 0; + + async->buf_write_alloc_count += nbytes; + /* barrier insures the read of buf_read_count above occurs before + we write data to the write-alloc'ed buffer space */ + smp_mb(); + return nbytes; +} + +/* transfers a chunk from writer to filled buffer space */ +unsigned comedi_buf_write_free(struct comedi_async *async, unsigned int nbytes) +{ + if ((int)(async->buf_write_count + nbytes - + async->buf_write_alloc_count) > 0) { + printk(KERN_INFO "comedi: attempted to write-free more bytes than have been write-allocated.\n"); + nbytes = async->buf_write_alloc_count - async->buf_write_count; + } + async->buf_write_count += nbytes; + async->buf_write_ptr += nbytes; + comedi_buf_munge(async, async->buf_write_count - async->munge_count); + if (async->buf_write_ptr >= async->prealloc_bufsz) + async->buf_write_ptr %= async->prealloc_bufsz; + + return nbytes; +} +EXPORT_SYMBOL(comedi_buf_write_free); + +/* allocates a chunk for the reader from filled (and munged) buffer space */ +unsigned comedi_buf_read_alloc(struct comedi_async *async, unsigned nbytes) +{ + if ((int)(async->buf_read_alloc_count + nbytes - async->munge_count) > + 0) { + nbytes = async->munge_count - async->buf_read_alloc_count; + } + async->buf_read_alloc_count += nbytes; + /* barrier insures read of munge_count occurs before we actually read + data out of buffer */ + smp_rmb(); + return nbytes; +} +EXPORT_SYMBOL(comedi_buf_read_alloc); + +/* transfers control of a chunk from reader to free buffer space */ +unsigned comedi_buf_read_free(struct comedi_async *async, unsigned int nbytes) +{ + /* barrier insures data has been read out of + * buffer before read count is incremented */ + smp_mb(); + if ((int)(async->buf_read_count + nbytes - + async->buf_read_alloc_count) > 0) { + printk(KERN_INFO + "comedi: attempted to read-free more bytes than have been read-allocated.\n"); + nbytes = async->buf_read_alloc_count - async->buf_read_count; + } + async->buf_read_count += nbytes; + async->buf_read_ptr += nbytes; + async->buf_read_ptr %= async->prealloc_bufsz; + return nbytes; +} +EXPORT_SYMBOL(comedi_buf_read_free); + +void comedi_buf_memcpy_to(struct comedi_async *async, unsigned int offset, + const void *data, unsigned int num_bytes) +{ + unsigned int write_ptr = async->buf_write_ptr + offset; + + if (write_ptr >= async->prealloc_bufsz) + write_ptr %= async->prealloc_bufsz; + + while (num_bytes) { + unsigned int block_size; + + if (write_ptr + num_bytes > async->prealloc_bufsz) + block_size = async->prealloc_bufsz - write_ptr; + else + block_size = num_bytes; + + memcpy(async->prealloc_buf + write_ptr, data, block_size); + + data += block_size; + num_bytes -= block_size; + + write_ptr = 0; + } +} +EXPORT_SYMBOL(comedi_buf_memcpy_to); + +void comedi_buf_memcpy_from(struct comedi_async *async, unsigned int offset, + void *dest, unsigned int nbytes) +{ + void *src; + unsigned int read_ptr = async->buf_read_ptr + offset; + + if (read_ptr >= async->prealloc_bufsz) + read_ptr %= async->prealloc_bufsz; + + while (nbytes) { + unsigned int block_size; + + src = async->prealloc_buf + read_ptr; + + if (nbytes >= async->prealloc_bufsz - read_ptr) + block_size = async->prealloc_bufsz - read_ptr; + else + block_size = nbytes; + + memcpy(dest, src, block_size); + nbytes -= block_size; + dest += block_size; + read_ptr = 0; + } +} +EXPORT_SYMBOL(comedi_buf_memcpy_from); + +unsigned int comedi_buf_read_n_available(struct comedi_async *async) +{ + unsigned num_bytes; + + if (async == NULL) + return 0; + num_bytes = async->munge_count - async->buf_read_count; + /* barrier insures the read of munge_count in this + query occurs before any following reads of the buffer which + might be based on the return value from this query. + */ + smp_rmb(); + return num_bytes; +} +EXPORT_SYMBOL(comedi_buf_read_n_available); + +int comedi_buf_get(struct comedi_async *async, short *x) +{ + unsigned int n = comedi_buf_read_n_available(async); + + if (n < sizeof(short)) + return 0; + comedi_buf_read_alloc(async, sizeof(short)); + *x = *(short *)(async->prealloc_buf + async->buf_read_ptr); + comedi_buf_read_free(async, sizeof(short)); + return 1; +} +EXPORT_SYMBOL(comedi_buf_get); + +int comedi_buf_put(struct comedi_async *async, short x) +{ + unsigned int n = comedi_buf_write_alloc_strict(async, sizeof(short)); + + if (n < sizeof(short)) { + async->events |= COMEDI_CB_ERROR; + return 0; + } + *(short *)(async->prealloc_buf + async->buf_write_ptr) = x; + comedi_buf_write_free(async, sizeof(short)); + return 1; +} +EXPORT_SYMBOL(comedi_buf_put); + +void comedi_reset_async_buf(struct comedi_async *async) +{ + async->buf_write_alloc_count = 0; + async->buf_write_count = 0; + async->buf_read_alloc_count = 0; + async->buf_read_count = 0; + + async->buf_write_ptr = 0; + async->buf_read_ptr = 0; + + async->cur_chan = 0; + async->scan_progress = 0; + async->munge_chan = 0; + async->munge_count = 0; + async->munge_ptr = 0; + + async->events = 0; +} + +static int comedi_auto_config(struct device *hardware_device, + const char *board_name, const int *options, + unsigned num_options) +{ + struct comedi_devconfig it; + int minor; + struct comedi_device_file_info *dev_file_info; + int retval; + unsigned *private_data = NULL; + + if (!comedi_autoconfig) { + dev_set_drvdata(hardware_device, NULL); + return 0; + } + + minor = comedi_alloc_board_minor(hardware_device); + if (minor < 0) + return minor; + + private_data = kmalloc(sizeof(unsigned), GFP_KERNEL); + if (private_data == NULL) { + retval = -ENOMEM; + goto cleanup; + } + *private_data = minor; + dev_set_drvdata(hardware_device, private_data); + + dev_file_info = comedi_get_device_file_info(minor); + + memset(&it, 0, sizeof(it)); + strncpy(it.board_name, board_name, COMEDI_NAMELEN); + it.board_name[COMEDI_NAMELEN - 1] = '\0'; + BUG_ON(num_options > COMEDI_NDEVCONFOPTS); + memcpy(it.options, options, num_options * sizeof(int)); + + mutex_lock(&dev_file_info->device->mutex); + retval = comedi_device_attach(dev_file_info->device, &it); + mutex_unlock(&dev_file_info->device->mutex); + +cleanup: + if (retval < 0) { + kfree(private_data); + comedi_free_board_minor(minor); + } + return retval; +} + +static void comedi_auto_unconfig(struct device *hardware_device) +{ + unsigned *minor = (unsigned *)dev_get_drvdata(hardware_device); + if (minor == NULL) + return; + + BUG_ON(*minor >= COMEDI_NUM_BOARD_MINORS); + + comedi_free_board_minor(*minor); + dev_set_drvdata(hardware_device, NULL); + kfree(minor); +} + +int comedi_pci_auto_config(struct pci_dev *pcidev, const char *board_name) +{ + int options[2]; + + /* pci bus */ + options[0] = pcidev->bus->number; + /* pci slot */ + options[1] = PCI_SLOT(pcidev->devfn); + + return comedi_auto_config(&pcidev->dev, board_name, + options, ARRAY_SIZE(options)); +} +EXPORT_SYMBOL_GPL(comedi_pci_auto_config); + +void comedi_pci_auto_unconfig(struct pci_dev *pcidev) +{ + comedi_auto_unconfig(&pcidev->dev); +} +EXPORT_SYMBOL_GPL(comedi_pci_auto_unconfig); + +int comedi_usb_auto_config(struct usb_device *usbdev, const char *board_name) +{ + BUG_ON(usbdev == NULL); + return comedi_auto_config(&usbdev->dev, board_name, NULL, 0); +} +EXPORT_SYMBOL_GPL(comedi_usb_auto_config); + +void comedi_usb_auto_unconfig(struct usb_device *usbdev) +{ + BUG_ON(usbdev == NULL); + comedi_auto_unconfig(&usbdev->dev); +} +EXPORT_SYMBOL_GPL(comedi_usb_auto_unconfig); diff --git a/drivers/staging/comedi/drivers/8253.h b/drivers/staging/comedi/drivers/8253.h new file mode 100644 index 00000000..3eb45d46 --- /dev/null +++ b/drivers/staging/comedi/drivers/8253.h @@ -0,0 +1,433 @@ +/* + comedi/drivers/8253.h + Header file for 8253 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _8253_H +#define _8253_H + +#include "../comedi.h" + +#define i8253_cascade_ns_to_timer i8253_cascade_ns_to_timer_2div + +static inline void i8253_cascade_ns_to_timer_2div_old(int i8253_osc_base, + unsigned int *d1, + unsigned int *d2, + unsigned int *nanosec, + int round_mode) +{ + int divider; + int div1, div2; + int div1_glb, div2_glb, ns_glb; + int div1_lub, div2_lub, ns_lub; + int ns; + + divider = (*nanosec + i8253_osc_base / 2) / i8253_osc_base; + + /* find 2 integers 1<={x,y}<=65536 such that x*y is + close to divider */ + + div1_lub = div2_lub = 0; + div1_glb = div2_glb = 0; + + ns_glb = 0; + ns_lub = 0xffffffff; + + div2 = 0x10000; + for (div1 = divider / 65536 + 1; div1 < div2; div1++) { + div2 = divider / div1; + + ns = i8253_osc_base * div1 * div2; + if (ns <= *nanosec && ns > ns_glb) { + ns_glb = ns; + div1_glb = div1; + div2_glb = div2; + } + + div2++; + if (div2 <= 65536) { + ns = i8253_osc_base * div1 * div2; + if (ns > *nanosec && ns < ns_lub) { + ns_lub = ns; + div1_lub = div1; + div2_lub = div2; + } + } + } + + *nanosec = div1_lub * div2_lub * i8253_osc_base; + *d1 = div1_lub & 0xffff; + *d2 = div2_lub & 0xffff; + return; +} + +static inline void i8253_cascade_ns_to_timer_power(int i8253_osc_base, + unsigned int *d1, + unsigned int *d2, + unsigned int *nanosec, + int round_mode) +{ + int div1, div2; + int base; + + for (div1 = 2; div1 <= (1 << 16); div1 <<= 1) { + base = i8253_osc_base * div1; + round_mode &= TRIG_ROUND_MASK; + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + div2 = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + div2 = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + div2 = (*nanosec + base - 1) / base; + break; + } + if (div2 < 2) + div2 = 2; + if (div2 <= 65536) { + *nanosec = div2 * base; + *d1 = div1 & 0xffff; + *d2 = div2 & 0xffff; + return; + } + } + + /* shouldn't get here */ + div1 = 0x10000; + div2 = 0x10000; + *nanosec = div1 * div2 * i8253_osc_base; + *d1 = div1 & 0xffff; + *d2 = div2 & 0xffff; +} + +static inline void i8253_cascade_ns_to_timer_2div(int i8253_osc_base, + unsigned int *d1, + unsigned int *d2, + unsigned int *nanosec, + int round_mode) +{ + unsigned int divider; + unsigned int div1, div2; + unsigned int div1_glb, div2_glb, ns_glb; + unsigned int div1_lub, div2_lub, ns_lub; + unsigned int ns; + unsigned int start; + unsigned int ns_low, ns_high; + static const unsigned int max_count = 0x10000; + /* exit early if everything is already correct (this can save time + * since this function may be called repeatedly during command tests + * and execution) */ + div1 = *d1 ? *d1 : max_count; + div2 = *d2 ? *d2 : max_count; + divider = div1 * div2; + if (div1 * div2 * i8253_osc_base == *nanosec && + div1 > 1 && div1 <= max_count && div2 > 1 && div2 <= max_count && + /* check for overflow */ + divider > div1 && divider > div2 && + divider * i8253_osc_base > divider && + divider * i8253_osc_base > i8253_osc_base) { + return; + } + + divider = *nanosec / i8253_osc_base; + + div1_lub = div2_lub = 0; + div1_glb = div2_glb = 0; + + ns_glb = 0; + ns_lub = 0xffffffff; + + div2 = max_count; + start = divider / div2; + if (start < 2) + start = 2; + for (div1 = start; div1 <= divider / div1 + 1 && div1 <= max_count; + div1++) { + for (div2 = divider / div1; + div1 * div2 <= divider + div1 + 1 && div2 <= max_count; + div2++) { + ns = i8253_osc_base * div1 * div2; + if (ns <= *nanosec && ns > ns_glb) { + ns_glb = ns; + div1_glb = div1; + div2_glb = div2; + } + if (ns >= *nanosec && ns < ns_lub) { + ns_lub = ns; + div1_lub = div1; + div2_lub = div2; + } + } + } + + round_mode &= TRIG_ROUND_MASK; + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + ns_high = div1_lub * div2_lub * i8253_osc_base; + ns_low = div1_glb * div2_glb * i8253_osc_base; + if (ns_high - *nanosec < *nanosec - ns_low) { + div1 = div1_lub; + div2 = div2_lub; + } else { + div1 = div1_glb; + div2 = div2_glb; + } + break; + case TRIG_ROUND_UP: + div1 = div1_lub; + div2 = div2_lub; + break; + case TRIG_ROUND_DOWN: + div1 = div1_glb; + div2 = div2_glb; + break; + } + + *nanosec = div1 * div2 * i8253_osc_base; + /* masking is done since counter maps zero to 0x10000 */ + *d1 = div1 & 0xffff; + *d2 = div2 & 0xffff; + return; +} + +#ifndef CMDTEST +/* i8254_load programs 8254 counter chip. It should also work for the 8253. + * base_address is the lowest io address + * for the chip (the address of counter 0). + * counter_number is the counter you want to load (0,1 or 2) + * count is the number to load into the counter. + * + * You probably want to use mode 2. + * + * Use i8254_mm_load() if you board uses memory-mapped io, it is + * the same as i8254_load() except it uses writeb() instead of outb(). + * + * Neither i8254_load() or i8254_read() do their loading/reading + * atomically. The 16 bit read/writes are performed with two successive + * 8 bit read/writes. So if two parts of your driver do a load/read on + * the same counter, it may be necessary to protect these functions + * with a spinlock. + * + * FMH + */ + +#define i8254_control_reg 3 + +static inline int i8254_load(unsigned long base_address, unsigned int regshift, + unsigned int counter_number, unsigned int count, + unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (count > 0xffff) + return -1; + if (mode > 5) + return -1; + if ((mode == 2 || mode == 3) && count == 1) + return -1; + + byte = counter_number << 6; + byte |= 0x30; /* load low then high byte */ + byte |= (mode << 1); /* set counter mode */ + outb(byte, base_address + (i8254_control_reg << regshift)); + byte = count & 0xff; /* lsb of counter value */ + outb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; /* msb of counter value */ + outb(byte, base_address + (counter_number << regshift)); + + return 0; +} + +static inline int i8254_mm_load(void *base_address, unsigned int regshift, + unsigned int counter_number, unsigned int count, + unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (count > 0xffff) + return -1; + if (mode > 5) + return -1; + if ((mode == 2 || mode == 3) && count == 1) + return -1; + + byte = counter_number << 6; + byte |= 0x30; /* load low then high byte */ + byte |= (mode << 1); /* set counter mode */ + writeb(byte, base_address + (i8254_control_reg << regshift)); + byte = count & 0xff; /* lsb of counter value */ + writeb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; /* msb of counter value */ + writeb(byte, base_address + (counter_number << regshift)); + + return 0; +} + +/* Returns 16 bit counter value, should work for 8253 also.*/ +static inline int i8254_read(unsigned long base_address, unsigned int regshift, + unsigned int counter_number) +{ + unsigned int byte; + int ret; + + if (counter_number > 2) + return -1; + + /* latch counter */ + byte = counter_number << 6; + outb(byte, base_address + (i8254_control_reg << regshift)); + + /* read lsb */ + ret = inb(base_address + (counter_number << regshift)); + /* read msb */ + ret += inb(base_address + (counter_number << regshift)) << 8; + + return ret; +} + +static inline int i8254_mm_read(void *base_address, unsigned int regshift, + unsigned int counter_number) +{ + unsigned int byte; + int ret; + + if (counter_number > 2) + return -1; + + /* latch counter */ + byte = counter_number << 6; + writeb(byte, base_address + (i8254_control_reg << regshift)); + + /* read lsb */ + ret = readb(base_address + (counter_number << regshift)); + /* read msb */ + ret += readb(base_address + (counter_number << regshift)) << 8; + + return ret; +} + +/* Loads 16 bit initial counter value, should work for 8253 also. */ +static inline void i8254_write(unsigned long base_address, + unsigned int regshift, + unsigned int counter_number, unsigned int count) +{ + unsigned int byte; + + if (counter_number > 2) + return; + + byte = count & 0xff; /* lsb of counter value */ + outb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; /* msb of counter value */ + outb(byte, base_address + (counter_number << regshift)); +} + +static inline void i8254_mm_write(void *base_address, + unsigned int regshift, + unsigned int counter_number, + unsigned int count) +{ + unsigned int byte; + + if (counter_number > 2) + return; + + byte = count & 0xff; /* lsb of counter value */ + writeb(byte, base_address + (counter_number << regshift)); + byte = (count >> 8) & 0xff; /* msb of counter value */ + writeb(byte, base_address + (counter_number << regshift)); +} + +/* Set counter mode, should work for 8253 also. + * Note: the 'mode' value is different to that for i8254_load() and comes + * from the INSN_CONFIG_8254_SET_MODE command: + * I8254_MODE0, I8254_MODE1, ..., I8254_MODE5 + * OR'ed with: + * I8254_BCD, I8254_BINARY + */ +static inline int i8254_set_mode(unsigned long base_address, + unsigned int regshift, + unsigned int counter_number, unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (mode > (I8254_MODE5 | I8254_BINARY)) + return -1; + + byte = counter_number << 6; + byte |= 0x30; /* load low then high byte */ + byte |= mode; /* set counter mode and BCD|binary */ + outb(byte, base_address + (i8254_control_reg << regshift)); + + return 0; +} + +static inline int i8254_mm_set_mode(void *base_address, + unsigned int regshift, + unsigned int counter_number, + unsigned int mode) +{ + unsigned int byte; + + if (counter_number > 2) + return -1; + if (mode > (I8254_MODE5 | I8254_BINARY)) + return -1; + + byte = counter_number << 6; + byte |= 0x30; /* load low then high byte */ + byte |= mode; /* set counter mode and BCD|binary */ + writeb(byte, base_address + (i8254_control_reg << regshift)); + + return 0; +} + +static inline int i8254_status(unsigned long base_address, + unsigned int regshift, + unsigned int counter_number) +{ + outb(0xE0 | (2 << counter_number), + base_address + (i8254_control_reg << regshift)); + return inb(base_address + (counter_number << regshift)); +} + +static inline int i8254_mm_status(void *base_address, + unsigned int regshift, + unsigned int counter_number) +{ + writeb(0xE0 | (2 << counter_number), + base_address + (i8254_control_reg << regshift)); + return readb(base_address + (counter_number << regshift)); +} + +#endif + +#endif diff --git a/drivers/staging/comedi/drivers/8255.c b/drivers/staging/comedi/drivers/8255.c new file mode 100644 index 00000000..6c26ac88 --- /dev/null +++ b/drivers/staging/comedi/drivers/8255.c @@ -0,0 +1,467 @@ +/* + comedi/drivers/8255.c + Driver for 8255 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: 8255 +Description: generic 8255 support +Devices: [standard] 8255 (8255) +Author: ds +Status: works +Updated: Fri, 7 Jun 2002 12:56:45 -0700 + +The classic in digital I/O. The 8255 appears in Comedi as a single +digital I/O subdevice with 24 channels. The channel 0 corresponds +to the 8255's port A, bit 0; channel 23 corresponds to port C, bit +7. Direction configuration is done in blocks, with channels 0-7, +8-15, 16-19, and 20-23 making up the 4 blocks. The only 8255 mode +supported is mode 0. + +You should enable compilation this driver if you plan to use a board +that has an 8255 chip. For multifunction boards, the main driver will +configure the 8255 subdevice automatically. + +This driver also works independently with ISA and PCI cards that +directly map the 8255 registers to I/O ports, including cards with +multiple 8255 chips. To configure the driver for such a card, the +option list should be a list of the I/O port bases for each of the +8255 chips. For example, + + comedi_config /dev/comedi0 8255 0x200,0x204,0x208,0x20c + +Note that most PCI 8255 boards do NOT work with this driver, and +need a separate driver as a wrapper. For those that do work, the +I/O port base address can be found in the output of 'lspci -v'. + +*/ + +/* + This file contains an exported subdevice for driving an 8255. + + To use this subdevice as part of another driver, you need to + set up the subdevice in the attach function of the driver by + calling: + + subdev_8255_init(device, subdevice, callback_function, arg) + + device and subdevice are pointers to the device and subdevice + structures. callback_function will be called to provide the + low-level input/output to the device, i.e., actual register + access. callback_function will be called with the value of arg + as the last parameter. If the 8255 device is mapped as 4 + consecutive I/O ports, you can use NULL for callback_function + and the I/O port base for arg, and an internal function will + handle the register access. + + In addition, if the main driver handles interrupts, you can + enable commands on the subdevice by calling subdev_8255_init_irq() + instead. Then, when you get an interrupt that is likely to be + from the 8255, you should call subdev_8255_interrupt(), which + will copy the latched value to a Comedi buffer. + */ + +#include "../comedidev.h" + +#include +#include +#include "8255.h" + +#define _8255_SIZE 4 + +#define _8255_DATA 0 +#define _8255_CR 3 + +#define CR_C_LO_IO 0x01 +#define CR_B_IO 0x02 +#define CR_B_MODE 0x04 +#define CR_C_HI_IO 0x08 +#define CR_A_IO 0x10 +#define CR_A_MODE(a) ((a)<<5) +#define CR_CW 0x80 + +struct subdev_8255_struct { + unsigned long cb_arg; + int (*cb_func) (int, int, int, unsigned long); + int have_irq; +}; + +#define CALLBACK_ARG (((struct subdev_8255_struct *)s->private)->cb_arg) +#define CALLBACK_FUNC (((struct subdev_8255_struct *)s->private)->cb_func) +#define subdevpriv ((struct subdev_8255_struct *)s->private) + +static int dev_8255_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dev_8255_detach(struct comedi_device *dev); +static struct comedi_driver driver_8255 = { + .driver_name = "8255", + .module = THIS_MODULE, + .attach = dev_8255_attach, + .detach = dev_8255_detach, +}; + +static int __init driver_8255_init_module(void) +{ + return comedi_driver_register(&driver_8255); +} + +static void __exit driver_8255_cleanup_module(void) +{ + comedi_driver_unregister(&driver_8255); +} + +module_init(driver_8255_init_module); +module_exit(driver_8255_cleanup_module); + +static void do_config(struct comedi_device *dev, struct comedi_subdevice *s); + +void subdev_8255_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + short d; + + d = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG); + d |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8); + + comedi_buf_put(s->async, d); + s->async->events |= COMEDI_CB_EOS; + + comedi_event(dev, s); +} +EXPORT_SYMBOL(subdev_8255_interrupt); + +static int subdev_8255_cb(int dir, int port, int data, unsigned long arg) +{ + unsigned long iobase = arg; + + if (dir) { + outb(data, iobase + port); + return 0; + } else { + return inb(iobase + port); + } +} + +static int subdev_8255_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + + if (data[0] & 0xff) + CALLBACK_FUNC(1, _8255_DATA, s->state & 0xff, + CALLBACK_ARG); + if (data[0] & 0xff00) + CALLBACK_FUNC(1, _8255_DATA + 1, (s->state >> 8) & 0xff, + CALLBACK_ARG); + if (data[0] & 0xff0000) + CALLBACK_FUNC(1, _8255_DATA + 2, + (s->state >> 16) & 0xff, CALLBACK_ARG); + } + + data[1] = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG); + data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8); + data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 2, 0, CALLBACK_ARG) << 16); + + return 2; +} + +static int subdev_8255_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int mask; + unsigned int bits; + + mask = 1 << CR_CHAN(insn->chanspec); + if (mask & 0x0000ff) + bits = 0x0000ff; + else if (mask & 0x00ff00) + bits = 0x00ff00; + else if (mask & 0x0f0000) + bits = 0x0f0000; + else + bits = 0xf00000; + + switch (data[0]) { + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~bits; + break; + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= bits; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + } + + do_config(dev, s); + + return 1; +} + +static void do_config(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int config; + + config = CR_CW; + /* 1 in io_bits indicates output, 1 in config indicates input */ + if (!(s->io_bits & 0x0000ff)) + config |= CR_A_IO; + if (!(s->io_bits & 0x00ff00)) + config |= CR_B_IO; + if (!(s->io_bits & 0x0f0000)) + config |= CR_C_LO_IO; + if (!(s->io_bits & 0xf00000)) + config |= CR_C_HI_IO; + CALLBACK_FUNC(1, _8255_CR, config, CALLBACK_ARG); +} + +static int subdev_8255_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int tmp; + + /* step 1 */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_FOLLOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2 */ + + if (err) + return 2; + + /* step 3 */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + if (cmd->scan_end_arg != 1) { + cmd->scan_end_arg = 1; + err++; + } + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + + if (err) + return 3; + + /* step 4 */ + + if (err) + return 4; + + return 0; +} + +static int subdev_8255_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* FIXME */ + + return 0; +} + +static int subdev_8255_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* FIXME */ + + return 0; +} + +int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s, + int (*cb) (int, int, int, unsigned long), + unsigned long arg) +{ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->range_table = &range_digital; + s->maxdata = 1; + + s->private = kmalloc(sizeof(struct subdev_8255_struct), GFP_KERNEL); + if (!s->private) + return -ENOMEM; + + CALLBACK_ARG = arg; + if (cb == NULL) + CALLBACK_FUNC = subdev_8255_cb; + else + CALLBACK_FUNC = cb; + s->insn_bits = subdev_8255_insn; + s->insn_config = subdev_8255_insn_config; + + s->state = 0; + s->io_bits = 0; + do_config(dev, s); + + return 0; +} +EXPORT_SYMBOL(subdev_8255_init); + +int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s, + int (*cb) (int, int, int, unsigned long), + unsigned long arg) +{ + int ret; + + ret = subdev_8255_init(dev, s, cb, arg); + if (ret < 0) + return ret; + + s->do_cmdtest = subdev_8255_cmdtest; + s->do_cmd = subdev_8255_cmd; + s->cancel = subdev_8255_cancel; + + subdevpriv->have_irq = 1; + + return 0; +} +EXPORT_SYMBOL(subdev_8255_init_irq); + +void subdev_8255_cleanup(struct comedi_device *dev, struct comedi_subdevice *s) +{ + kfree(s->private); +} +EXPORT_SYMBOL(subdev_8255_cleanup); + +/* + + Start of the 8255 standalone device + + */ + +static int dev_8255_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int ret; + unsigned long iobase; + int i; + + dev->board_name = "8255"; + + for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) { + iobase = it->options[i]; + if (!iobase) + break; + } + if (i == 0) { + printk(KERN_WARNING + "comedi%d: 8255: no devices specified\n", dev->minor); + return -EINVAL; + } + + ret = alloc_subdevices(dev, i); + if (ret < 0) { + /* FIXME this printk call should give a proper message, the + * below line just maintains previous functionality */ + printk("comedi%d: 8255:", dev->minor); + return ret; + } + + printk(KERN_INFO "comedi%d: 8255:", dev->minor); + + for (i = 0; i < dev->n_subdevices; i++) { + iobase = it->options[i]; + + printk(" 0x%04lx", iobase); + if (!request_region(iobase, _8255_SIZE, "8255")) { + printk(" (I/O port conflict)"); + + dev->subdevices[i].type = COMEDI_SUBD_UNUSED; + } else { + subdev_8255_init(dev, dev->subdevices + i, NULL, + iobase); + } + } + + printk("\n"); + + return 0; +} + +static int dev_8255_detach(struct comedi_device *dev) +{ + int i; + unsigned long iobase; + struct comedi_subdevice *s; + + printk(KERN_INFO "comedi%d: 8255: remove\n", dev->minor); + + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + if (s->type != COMEDI_SUBD_UNUSED) { + iobase = CALLBACK_ARG; + release_region(iobase, _8255_SIZE); + } + subdev_8255_cleanup(dev, s); + } + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/8255.h b/drivers/staging/comedi/drivers/8255.h new file mode 100644 index 00000000..b6314c9b --- /dev/null +++ b/drivers/staging/comedi/drivers/8255.h @@ -0,0 +1,39 @@ +/* + module/8255.h + Header file for 8255 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _8255_H +#define _8255_H + +#include "../comedidev.h" + +int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s, + int (*cb) (int, int, int, unsigned long), + unsigned long arg); +int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s, + int (*cb) (int, int, int, unsigned long), + unsigned long arg); +void subdev_8255_cleanup(struct comedi_device *dev, struct comedi_subdevice *s); +void subdev_8255_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s); + +#endif diff --git a/drivers/staging/comedi/drivers/Makefile b/drivers/staging/comedi/drivers/Makefile new file mode 100644 index 00000000..170da609 --- /dev/null +++ b/drivers/staging/comedi/drivers/Makefile @@ -0,0 +1,140 @@ +# Makefile for individual comedi drivers +# + +# Comedi "helper" modules +obj-$(CONFIG_COMEDI) += pcm_common.o + +# Comedi misc drivers +obj-$(CONFIG_COMEDI_BOND) += comedi_bond.o +obj-$(CONFIG_COMEDI_TEST) += comedi_test.o +obj-$(CONFIG_COMEDI_PARPORT) += comedi_parport.o +obj-$(CONFIG_COMEDI_SERIAL2002) += serial2002.o +obj-$(CONFIG_COMEDI_SKEL) += skel.o + +# Comedi ISA drivers +obj-$(CONFIG_COMEDI_ACL7225B) += acl7225b.o +obj-$(CONFIG_COMEDI_PCL711) += pcl711.o +obj-$(CONFIG_COMEDI_PCL724) += pcl724.o +obj-$(CONFIG_COMEDI_PCL725) += pcl725.o +obj-$(CONFIG_COMEDI_PCL726) += pcl726.o +obj-$(CONFIG_COMEDI_PCL730) += pcl730.o +obj-$(CONFIG_COMEDI_PCL812) += pcl812.o +obj-$(CONFIG_COMEDI_PCL816) += pcl816.o +obj-$(CONFIG_COMEDI_PCL818) += pcl818.o +obj-$(CONFIG_COMEDI_PCM3724) += pcm3724.o +obj-$(CONFIG_COMEDI_PCM3730) += pcm3730.o +obj-$(CONFIG_COMEDI_RTI800) += rti800.o +obj-$(CONFIG_COMEDI_RTI802) += rti802.o +obj-$(CONFIG_COMEDI_DAS16M1) += das16m1.o +obj-$(CONFIG_COMEDI_DAS16) += das16.o +obj-$(CONFIG_COMEDI_DAS800) += das800.o +obj-$(CONFIG_COMEDI_DAS1800) += das1800.o +obj-$(CONFIG_COMEDI_DAS6402) += das6402.o +obj-$(CONFIG_COMEDI_DT2801) += dt2801.o +obj-$(CONFIG_COMEDI_DT2811) += dt2811.o +obj-$(CONFIG_COMEDI_DT2814) += dt2814.o +obj-$(CONFIG_COMEDI_DT2815) += dt2815.o +obj-$(CONFIG_COMEDI_DT2817) += dt2817.o +obj-$(CONFIG_COMEDI_DT282X) += dt282x.o +obj-$(CONFIG_COMEDI_DMM32AT) += dmm32at.o +obj-$(CONFIG_COMEDI_FL512) += fl512.o +obj-$(CONFIG_COMEDI_AIO_AIO12_8) += aio_aio12_8.o +obj-$(CONFIG_COMEDI_AIO_IIRO_16) += aio_iiro_16.o +obj-$(CONFIG_COMEDI_C6XDIGIO) += c6xdigio.o +obj-$(CONFIG_COMEDI_MPC624) += mpc624.o +obj-$(CONFIG_COMEDI_ADQ12B) += adq12b.o +obj-$(CONFIG_COMEDI_NI_AT_A2150) += ni_at_a2150.o +obj-$(CONFIG_COMEDI_NI_AT_AO) += ni_at_ao.o +obj-$(CONFIG_COMEDI_NI_ATMIO) += ni_atmio.o +obj-$(CONFIG_COMEDI_NI_ATMIO16D) += ni_atmio16d.o +obj-$(CONFIG_COMEDI_PCMAD) += pcmad.o +obj-$(CONFIG_COMEDI_PCMDA12) += pcmda12.o +obj-$(CONFIG_COMEDI_PCMMIO) += pcmmio.o +obj-$(CONFIG_COMEDI_PCMUIO) += pcmuio.o +obj-$(CONFIG_COMEDI_MULTIQ3) += multiq3.o +obj-$(CONFIG_COMEDI_POC) += poc.o + +# Comedi PCI drivers +obj-$(CONFIG_COMEDI_PCI_DRIVERS) += 8255.o +obj-$(CONFIG_COMEDI_ADDI_APCI_035) += addi_apci_035.o +obj-$(CONFIG_COMEDI_ADDI_APCI_1032) += addi_apci_1032.o +obj-$(CONFIG_COMEDI_ADDI_APCI_1500) += addi_apci_1500.o +obj-$(CONFIG_COMEDI_ADDI_APCI_1516) += addi_apci_1516.o +obj-$(CONFIG_COMEDI_ADDI_APCI_1564) += addi_apci_1564.o +obj-$(CONFIG_COMEDI_ADDI_APCI_16XX) += addi_apci_16xx.o +obj-$(CONFIG_COMEDI_ADDI_APCI_2016) += addi_apci_2016.o +obj-$(CONFIG_COMEDI_ADDI_APCI_2032) += addi_apci_2032.o +obj-$(CONFIG_COMEDI_ADDI_APCI_2200) += addi_apci_2200.o +obj-$(CONFIG_COMEDI_ADDI_APCI_3001) += addi_apci_3001.o +obj-$(CONFIG_COMEDI_ADDI_APCI_3120) += addi_apci_3120.o +obj-$(CONFIG_COMEDI_ADDI_APCI_3501) += addi_apci_3501.o +obj-$(CONFIG_COMEDI_ADDI_APCI_3XXX) += addi_apci_3xxx.o +obj-$(CONFIG_COMEDI_ADL_PCI6208) += adl_pci6208.o +obj-$(CONFIG_COMEDI_ADL_PCI7230) += adl_pci7230.o +obj-$(CONFIG_COMEDI_ADL_PCI7296) += adl_pci7296.o +obj-$(CONFIG_COMEDI_ADL_PCI7432) += adl_pci7432.o +obj-$(CONFIG_COMEDI_ADL_PCI8164) += adl_pci8164.o +obj-$(CONFIG_COMEDI_ADL_PCI9111) += adl_pci9111.o +obj-$(CONFIG_COMEDI_ADL_PCI9118) += adl_pci9118.o +obj-$(CONFIG_COMEDI_ADV_PCI1710) += adv_pci1710.o +obj-$(CONFIG_COMEDI_ADV_PCI1723) += adv_pci1723.o +obj-$(CONFIG_COMEDI_ADV_PCI_DIO) += adv_pci_dio.o +obj-$(CONFIG_COMEDI_AMPLC_DIO200) += amplc_dio200.o +obj-$(CONFIG_COMEDI_AMPLC_PC236) += amplc_pc236.o +obj-$(CONFIG_COMEDI_AMPLC_PC263) += amplc_pc263.o +obj-$(CONFIG_COMEDI_AMPLC_PCI224) += amplc_pci224.o +obj-$(CONFIG_COMEDI_AMPLC_PCI230) += amplc_pci230.o +obj-$(CONFIG_COMEDI_CONTEC_PCI_DIO) += contec_pci_dio.o +obj-$(CONFIG_COMEDI_DT3000) += dt3000.o +obj-$(CONFIG_COMEDI_DYNA_PCI10XX) += dyna_pci10xx.o +obj-$(CONFIG_COMEDI_UNIOXX5) += unioxx5.o +obj-$(CONFIG_COMEDI_GSC_HPDI) += gsc_hpdi.o +obj-$(CONFIG_COMEDI_ICP_MULTI) += icp_multi.o +obj-$(CONFIG_COMEDI_II_PCI20KC) += ii_pci20kc.o +obj-$(CONFIG_COMEDI_DAQBOARD2000) += daqboard2000.o +obj-$(CONFIG_COMEDI_JR3_PCI) += jr3_pci.o +obj-$(CONFIG_COMEDI_KE_COUNTER) += ke_counter.o +obj-$(CONFIG_COMEDI_CB_PCIDAS64) += cb_pcidas64.o +obj-$(CONFIG_COMEDI_CB_PCIDAS) += cb_pcidas.o +obj-$(CONFIG_COMEDI_CB_PCIDDA) += cb_pcidda.o +obj-$(CONFIG_COMEDI_CB_PCIDIO) += cb_pcidio.o +obj-$(CONFIG_COMEDI_CB_PCIMDAS) += cb_pcimdas.o +obj-$(CONFIG_COMEDI_CB_PCIMDDA) += cb_pcimdda.o +obj-$(CONFIG_COMEDI_ME4000) += me4000.o +obj-$(CONFIG_COMEDI_ME_DAQ) += me_daq.o +obj-$(CONFIG_COMEDI_NI_6527) += ni_6527.o +obj-$(CONFIG_COMEDI_NI_65XX) += ni_65xx.o +obj-$(CONFIG_COMEDI_NI_660X) += ni_660x.o +obj-$(CONFIG_COMEDI_NI_670X) += ni_670x.o +obj-$(CONFIG_COMEDI_NI_PCIDIO) += ni_pcidio.o +obj-$(CONFIG_COMEDI_NI_PCIMIO) += ni_pcimio.o +obj-$(CONFIG_COMEDI_RTD520) += rtd520.o +obj-$(CONFIG_COMEDI_S526) += s526.o +obj-$(CONFIG_COMEDI_S626) += s626.o +obj-$(CONFIG_COMEDI_SSV_DNP) += ssv_dnp.o + +# Comedi PCMCIA drivers +obj-$(CONFIG_COMEDI_CB_DAS16_CS) += cb_das16_cs.o +obj-$(CONFIG_COMEDI_DAS08_CS) += das08_cs.o +obj-$(CONFIG_COMEDI_NI_DAQ_700_CS) += ni_daq_700.o +obj-$(CONFIG_COMEDI_NI_DAQ_DIO24_CS) += ni_daq_dio24.o +obj-$(CONFIG_COMEDI_NI_LABPC_CS) += ni_labpc_cs.o +obj-$(CONFIG_COMEDI_NI_MIO_CS) += ni_mio_cs.o +obj-$(CONFIG_COMEDI_QUATECH_DAQP_CS) += quatech_daqp_cs.o + +# Comedi USB drivers +obj-$(CONFIG_COMEDI_DT9812) += dt9812.o +obj-$(CONFIG_COMEDI_USBDUX) += usbdux.o +obj-$(CONFIG_COMEDI_USBDUXFAST) += usbduxfast.o +obj-$(CONFIG_COMEDI_USBDUXSIGMA) += usbduxsigma.o +obj-$(CONFIG_COMEDI_VMK80XX) += vmk80xx.o + +# Comedi NI drivers +obj-$(CONFIG_COMEDI_MITE) += mite.o +obj-$(CONFIG_COMEDI_NI_TIO) += ni_tio.o +obj-$(CONFIG_COMEDI_NI_TIO) += ni_tiocmd.o +obj-$(CONFIG_COMEDI_NI_LABPC) += ni_labpc.o + +obj-$(CONFIG_COMEDI_8255) += 8255.o +obj-$(CONFIG_COMEDI_DAS08) += das08.o +obj-$(CONFIG_COMEDI_FC) += comedi_fc.o diff --git a/drivers/staging/comedi/drivers/acl7225b.c b/drivers/staging/comedi/drivers/acl7225b.c new file mode 100644 index 00000000..9def2250 --- /dev/null +++ b/drivers/staging/comedi/drivers/acl7225b.c @@ -0,0 +1,167 @@ +/* + * comedi/drivers/acl7225b.c + * Driver for Adlink NuDAQ ACL-7225b and clones + * José Luis Sánchez + */ +/* +Driver: acl7225b +Description: Adlink NuDAQ ACL-7225b & compatibles +Author: José Luis Sánchez (jsanchezv@teleline.es) +Status: testing +Devices: [Adlink] ACL-7225b (acl7225b), [ICP] P16R16DIO (p16r16dio) +*/ + +#include "../comedidev.h" + +#include + +#define ACL7225_SIZE 8 /* Requires 8 ioports, but only 4 are used */ +#define P16R16DIO_SIZE 4 +#define ACL7225_RIO_LO 0 /* Relays input/output low byte (R0-R7) */ +#define ACL7225_RIO_HI 1 /* Relays input/output high byte (R8-R15) */ +#define ACL7225_DI_LO 2 /* Digital input low byte (DI0-DI7) */ +#define ACL7225_DI_HI 3 /* Digital input high byte (DI8-DI15) */ + +static int acl7225b_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int acl7225b_detach(struct comedi_device *dev); + +struct boardtype { + const char *name; /* driver name */ + int io_range; /* len of I/O space */ +}; + +static const struct boardtype boardtypes[] = { + {"acl7225b", ACL7225_SIZE,}, + {"p16r16dio", P16R16DIO_SIZE,}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct boardtype)) +#define this_board ((const struct boardtype *)dev->board_ptr) + +static struct comedi_driver driver_acl7225b = { + .driver_name = "acl7225b", + .module = THIS_MODULE, + .attach = acl7225b_attach, + .detach = acl7225b_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct boardtype), +}; + +static int __init driver_acl7225b_init_module(void) +{ + return comedi_driver_register(&driver_acl7225b); +} + +static void __exit driver_acl7225b_cleanup_module(void) +{ + comedi_driver_unregister(&driver_acl7225b); +} + +module_init(driver_acl7225b_init_module); +module_exit(driver_acl7225b_cleanup_module); + +static int acl7225b_do_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + } + if (data[0] & 0x00ff) + outb(s->state & 0xff, dev->iobase + (unsigned long)s->private); + if (data[0] & 0xff00) + outb((s->state >> 8), + dev->iobase + (unsigned long)s->private + 1); + + data[1] = s->state; + + return 2; +} + +static int acl7225b_di_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + (unsigned long)s->private) | + (inb(dev->iobase + (unsigned long)s->private + 1) << 8); + + return 2; +} + +static int acl7225b_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int iobase, iorange; + + iobase = it->options[0]; + iorange = this_board->io_range; + printk(KERN_INFO "comedi%d: acl7225b: board=%s 0x%04x\n", dev->minor, + this_board->name, iobase); + if (!request_region(iobase, iorange, "acl7225b")) { + printk(KERN_ERR "comedi%d: request_region failed - I/O port conflict\n", + dev->minor); + return -EIO; + } + dev->board_name = this_board->name; + dev->iobase = iobase; + dev->irq = 0; + + if (alloc_subdevices(dev, 3) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* Relays outputs */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 1; + s->n_chan = 16; + s->insn_bits = acl7225b_do_insn; + s->range_table = &range_digital; + s->private = (void *)ACL7225_RIO_LO; + + s = dev->subdevices + 1; + /* Relays status */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->maxdata = 1; + s->n_chan = 16; + s->insn_bits = acl7225b_di_insn; + s->range_table = &range_digital; + s->private = (void *)ACL7225_RIO_LO; + + s = dev->subdevices + 2; + /* Isolated digital inputs */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->maxdata = 1; + s->n_chan = 16; + s->insn_bits = acl7225b_di_insn; + s->range_table = &range_digital; + s->private = (void *)ACL7225_DI_LO; + + return 0; +} + +static int acl7225b_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: acl7225b: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, this_board->io_range); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_82x54.c b/drivers/staging/comedi/drivers/addi-data/APCI1710_82x54.c new file mode 100644 index 00000000..b59f2d48 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_82x54.c @@ -0,0 +1,1047 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ +/* + | Description : APCI-1710 82X54 timer module | +*/ + +#include "APCI1710_82x54.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitTimer | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TimerNbr, | +| unsigned char_ b_TimerMode, | +| ULONG_ ul_ReloadValue, | +| unsigned char_ b_InputClockSelection, | +| unsigned char_ b_InputClockLevel, | +| unsigned char_ b_OutputLevel, | +| unsigned char_ b_HardwareGateLevel) +int i_InsnConfig_InitTimer(struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data) +| ++----------------------------------------------------------------------------+ +| Task : Configure the Timer (b_TimerNbr) operating mode | +| (b_TimerMode) from selected module (b_ModulNbr). | +| You must calling this function be for you call any | +| other function witch access of the timer. | +| | +| | +| Timer mode description table | +| | +|+--------+-----------------------------+--------------+--------------------+| +||Selected+ Mode description +u_ReloadValue | Hardware gate input|| +|| mode | | description | action || +|+--------+-----------------------------+--------------+--------------------+| +|| |Mode 0 is typically used | | || +|| |for event counting. After | | || +|| |the initialisation, OUT | | || +|| |is initially low, and | | || +|| 0 |will remain low until the |Start counting| Hardware gate || +|| |counter reaches zero. | value | || +|| |OUT then goes high and | | || +|| |remains high until a new | | || +|| |count is written. See | | || +|| |"i_APCI1710_WriteTimerValue" | | || +|| |function. | | || +|+--------+-----------------------------+--------------+--------------------+| +|| |Mode 1 is similar to mode 0 | | || +|| |except for the gate input | | || +|| 1 |action. The gate input is not|Start counting| Hardware trigger || +|| |used for enabled or disabled | value | || +|| |the timer. | | || +|| |The gate input is used for | | || +|| |triggered the timer. | | || +|+--------+-----------------------------+--------------+--------------------+| +|| |This mode functions like a | | || +|| |divide-by-ul_ReloadValue | | || +|| |counter. It is typically used| | || +|| |to generate a real time clock| | || +|| |interrupt. OUT will initially| | || +|| 2 |be high after the | Division | Hardware gate || +|| |initialisation. When the | factor | || +|| |initial count has decremented| | || +|| |to 1, OUT goes low for one | | || +|| |CLK pule. OUT then goes high | | || +|| |again, the counter reloads | | || +|| |the initial count | | || +|| |(ul_ReloadValue) and the | | || +|| |process is repeated. | | || +|| |This action can generated a | | || +|| |interrupt. See function | | || +|| |"i_APCI1710_SetBoardInt- | | || +|| |RoutineX" | | || +|| |and "i_APCI1710_EnableTimer" | | || +|+--------+-----------------------------+--------------+--------------------+| +|| |Mode 3 is typically used for | | || +|| |baud rate generation. This | | || +|| |mode is similar to mode 2 | | || +|| |except for the duty cycle of | | || +|| 3 |OUT. OUT will initially be | Division | Hardware gate || +|| |high after the initialisation| factor | || +|| |When half the initial count | | || +|| |(ul_ReloadValue) has expired,| | || +|| |OUT goes low for the | | || +|| |remainder of the count. The | | || +|| |mode is periodic; the | | || +|| |sequence above is repeated | | || +|| |indefinitely. | | || +|+--------+-----------------------------+--------------+--------------------+| +|| |OUT will be initially high | | || +|| |after the initialisation. | | || +|| |When the initial count | | || +|| 4 |expires OUT will go low for |Start counting| Hardware gate || +|| |one CLK pulse and then go | value | || +|| |high again. | | || +|| |The counting sequences is | | || +|| |triggered by writing a new | | || +|| |value. See | | || +|| |"i_APCI1710_WriteTimerValue" | | || +|| |function. If a new count is | | || +|| |written during counting, | | || +|| |it will be loaded on the | | || +|| |next CLK pulse | | || +|+--------+-----------------------------+--------------+--------------------+| +|| |Mode 5 is similar to mode 4 | | || +|| |except for the gate input | | || +|| |action. The gate input is not| | || +|| 5 |used for enabled or disabled |Start counting| Hardware trigger || +|| |the timer. The gate input is | value | || +|| |used for triggered the timer.| | || +|+--------+-----------------------------+--------------+--------------------+| +| | +| | +| | +| Input clock selection table | +| | +| +--------------------------------+------------------------------------+ | +| | b_InputClockSelection | Description | | +| | parameter | | | +| +--------------------------------+------------------------------------+ | +| | APCI1710_PCI_BUS_CLOCK | For the timer input clock, the PCI | | +| | | bus clock / 4 is used. This PCI bus| | +| | | clock can be 30MHz or 33MHz. For | | +| | | Timer 0 only this selection are | | +| | | available. | | +| +--------------------------------+------------------------------------+ | +| | APCI1710_ FRONT_CONNECTOR_INPUT| Of the front connector you have the| | +| | | possibility to inject a input clock| | +| | | for Timer 1 or Timer 2. The source | | +| | | from this clock can eat the output | | +| | | clock from Timer 0 or any other | | +| | | clock source. | | +| +--------------------------------+------------------------------------+ | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board | +| APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 3) | +| unsigned char_ b_TimerNbr : Timer number to | +| configure (0 to 2) | +| unsigned char_ b_TimerMode : Timer mode selection | +| (0 to 5) | +| 0: Interrupt on terminal| +| count | +| 1: Hardware | +| retriggerable one- | +| shot | +| 2: Rate generator | +| 3: Square wave mode | +| 4: Software triggered | +| strobe | +| 5: Hardware triggered | +| strobe | +| See timer mode | +| description table. | +| ULONG_ ul_ReloadValue : Start counting value | +| or division factor | +| See timer mode | +| description table. | +| unsigned char_ b_InputClockSelection : Selection from input | +| timer clock. | +| See input clock | +| selection table. | +| unsigned char_ b_InputClockLevel : Selection from input | +| clock level. | +| 0 : Low active | +| (Input inverted) | +| 1 : High active | +| unsigned char_ b_OutputLevel, : Selection from output | +| clock level. | +| 0 : Low active | +| 1 : High active | +| (Output inverted) | +| unsigned char_ b_HardwareGateLevel : Selection from | +| hardware gate level. | +| 0 : Low active | +| (Input inverted) | +| 1 : High active | +| If you will not used | +| the hardware gate set | +| this value to 0. +|b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_TimerNbr = (unsigned char) CR_CHAN(insn->chanspec); + b_TimerMode = (unsigned char) data[0]; + ul_ReloadValue = (unsigned int) data[1]; + b_InputClockSelection =(unsigned char) data[2]; + b_InputClockLevel =(unsigned char) data[3]; + b_OutputLevel =(unsigned char) data[4]; + b_HardwareGateLevel =(unsigned char) data[5]; ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: Timer selection wrong | +| -4: The module is not a TIMER module | +| -5: Timer mode selection is wrong | +| -6: Input timer clock selection is wrong | +| -7: Selection from input clock level is wrong | +| -8: Selection from output clock level is wrong | +| -9: Selection from hardware gate level is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnConfigInitTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + int i_ReturnValue = 0; + unsigned char b_ModulNbr; + unsigned char b_TimerNbr; + unsigned char b_TimerMode; + unsigned int ul_ReloadValue; + unsigned char b_InputClockSelection; + unsigned char b_InputClockLevel; + unsigned char b_OutputLevel; + unsigned char b_HardwareGateLevel; + + /* BEGIN JK 27.10.2003 : Add the possibility to use a 40 Mhz quartz */ + unsigned int dw_Test = 0; + /* END JK 27.10.2003 : Add the possibility to use a 40 Mhz quartz */ + + i_ReturnValue = insn->n; + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_TimerNbr = (unsigned char) CR_CHAN(insn->chanspec); + b_TimerMode = (unsigned char) data[0]; + ul_ReloadValue = (unsigned int) data[1]; + b_InputClockSelection = (unsigned char) data[2]; + b_InputClockLevel = (unsigned char) data[3]; + b_OutputLevel = (unsigned char) data[4]; + b_HardwareGateLevel = (unsigned char) data[5]; + + /* Test the module number */ + if (b_ModulNbr < 4) { + /* Test if 82X54 timer */ + if ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF0000UL) == APCI1710_82X54_TIMER) { + /* Test the timer number */ + + if (b_TimerNbr <= 2) { + /* Test the timer mode */ + if (b_TimerMode <= 5) { + /* BEGIN JK 27.10.2003 : Add the possibility to use a 40 Mhz quartz */ + /* Test te imput clock selection */ + /* + if (((b_TimerNbr == 0) && (b_InputClockSelection == 0)) || + ((b_TimerNbr != 0) && ((b_InputClockSelection == 0) || (b_InputClockSelection == 1)))) + */ + + if (((b_TimerNbr == 0) && + (b_InputClockSelection == APCI1710_PCI_BUS_CLOCK)) || + ((b_TimerNbr == 0) && + (b_InputClockSelection == APCI1710_10MHZ)) || + ((b_TimerNbr != 0) && + ((b_InputClockSelection == APCI1710_PCI_BUS_CLOCK) || + (b_InputClockSelection == APCI1710_FRONT_CONNECTOR_INPUT) || + (b_InputClockSelection == APCI1710_10MHZ)))) { + /* BEGIN JK 27.10.2003 : Add the possibility to use a 40 Mhz quartz */ + if (((b_InputClockSelection == APCI1710_10MHZ) && + ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0x0000FFFFUL) >= 0x3131)) || + (b_InputClockSelection != APCI1710_10MHZ)) { + /* END JK 27.10.2003 : Add the possibility to use a 40 Mhz quartz */ + /* Test the input clock level selection */ + + if ((b_InputClockLevel == 0) || + (b_InputClockLevel == 1)) { + /* Test the output clock level selection */ + if ((b_OutputLevel == 0) || (b_OutputLevel == 1)) { + /* Test the hardware gate level selection */ + if ((b_HardwareGateLevel == 0) || (b_HardwareGateLevel == 1)) { + /* BEGIN JK 27.10.03 : Add the possibility to use a 40 Mhz quartz */ + /* Test if version > 1.1 and clock selection = 10MHz */ + if ((b_InputClockSelection == APCI1710_10MHZ) && ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0x0000FFFFUL) > 0x3131)) { + /* Test if 40MHz quartz on board */ + dw_Test = inl(devpriv->s_BoardInfos.ui_Address + (16 + (b_TimerNbr * 4) + (64 * b_ModulNbr))); + + dw_Test = (dw_Test >> 16) & 1; + } else { + dw_Test = 1; + } + + /* Test if detection OK */ + if (dw_Test == 1) { + /* END JK 27.10.03 : Add the possibility to use a 40 Mhz quartz */ + /* Initialisation OK */ + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_82X54Init = 1; + + /* Save the input clock selection */ + devpriv-> s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_InputClockSelection = b_InputClockSelection; + + /* Save the input clock level */ + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_InputClockLevel = ~b_InputClockLevel & 1; + + /* Save the output level */ + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_OutputLevel = ~b_OutputLevel & 1; + + /* Save the gate level */ + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_HardwareGateLevel = b_HardwareGateLevel; + + /* Set the configuration word and disable the timer */ + /* BEGIN JK 27.10.03 : Add the possibility to use a 40 Mhz quartz */ + /* + devpriv->s_ModuleInfo [b_ModulNbr]. + s_82X54ModuleInfo. + s_82X54TimerInfo [b_TimerNbr]. + dw_ConfigurationWord = (unsigned int) (((b_HardwareGateLevel << 0) & 0x1) | + ((b_InputClockLevel << 1) & 0x2) | + (((~b_OutputLevel & 1) << 2) & 0x4) | + ((b_InputClockSelection << 4) & 0x10)); + */ + /* Test if 10MHz selected */ + if (b_InputClockSelection == APCI1710_10MHZ) { + b_InputClockSelection = 2; + } + + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord = (unsigned int)(((b_HardwareGateLevel << 0) & 0x1) | ((b_InputClockLevel << 1) & 0x2) | (((~b_OutputLevel & 1) << 2) & 0x4) | ((b_InputClockSelection << 4) & 0x30)); + /* END JK 27.10.03 : Add the possibility to use a 40 Mhz quartz */ + outl(devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord, devpriv->s_BoardInfos.ui_Address + 32 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + + /* Initialise the 82X54 Timer */ + outl((unsigned int) b_TimerMode, devpriv->s_BoardInfos.ui_Address + 16 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + + /* Write the reload value */ + outl(ul_ReloadValue, devpriv->s_BoardInfos.ui_Address + 0 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + /* BEGIN JK 27.10.03 : Add the possibility to use a 40 Mhz quartz */ + } /* if (dw_Test == 1) */ + else { + /* Input timer clock selection is wrong */ + i_ReturnValue = -6; + } /* if (dw_Test == 1) */ + /* END JK 27.10.03 : Add the possibility to use a 40 Mhz quartz */ + } /* if ((b_HardwareGateLevel == 0) || (b_HardwareGateLevel == 1)) */ + else { + /* Selection from hardware gate level is wrong */ + DPRINTK("Selection from hardware gate level is wrong\n"); + i_ReturnValue = -9; + } /* if ((b_HardwareGateLevel == 0) || (b_HardwareGateLevel == 1)) */ + } /* if ((b_OutputLevel == 0) || (b_OutputLevel == 1)) */ + else { + /* Selection from output clock level is wrong */ + DPRINTK("Selection from output clock level is wrong\n"); + i_ReturnValue = -8; + } /* if ((b_OutputLevel == 0) || (b_OutputLevel == 1)) */ + } /* if ((b_InputClockLevel == 0) || (b_InputClockLevel == 1)) */ + else { + /* Selection from input clock level is wrong */ + DPRINTK("Selection from input clock level is wrong\n"); + i_ReturnValue = -7; + } /* if ((b_InputClockLevel == 0) || (b_InputClockLevel == 1)) */ + } else { + /* Input timer clock selection is wrong */ + DPRINTK("Input timer clock selection is wrong\n"); + i_ReturnValue = -6; + } + } else { + /* Input timer clock selection is wrong */ + DPRINTK("Input timer clock selection is wrong\n"); + i_ReturnValue = -6; + } + } /* if ((b_TimerMode >= 0) && (b_TimerMode <= 5)) */ + else { + /* Timer mode selection is wrong */ + DPRINTK("Timer mode selection is wrong\n"); + i_ReturnValue = -5; + } /* if ((b_TimerMode >= 0) && (b_TimerMode <= 5)) */ + } /* if ((b_TimerNbr >= 0) && (b_TimerNbr <= 2)) */ + else { + /* Timer selection wrong */ + DPRINTK("Timer selection wrong\n"); + i_ReturnValue = -3; + } /* if ((b_TimerNbr >= 0) && (b_TimerNbr <= 2)) */ + } else { + /* The module is not a TIMER module */ + DPRINTK("The module is not a TIMER module\n"); + i_ReturnValue = -4; + } + } else { + /* Module number error */ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_EnableTimer | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TimerNbr, | +| unsigned char_ b_InterruptEnable) +int i_APCI1710_InsnWriteEnableDisableTimer(struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Enable OR Disable the Timer (b_TimerNbr) from selected module | +| (b_ModulNbr). You must calling the | +| "i_APCI1710_InitTimer" function be for you call this | +| function. If you enable the timer interrupt, the timer | +| generate a interrupt after the timer value reach | +| the zero. See function "i_APCI1710_SetBoardIntRoutineX"| ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board | +| APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number | +| (0 to 3) | +| unsigned char_ b_TimerNbr : Timer number to enable | +| (0 to 2) | +| unsigned char_ b_InterruptEnable : Enable or disable the | +| timer interrupt. | +| APCI1710_ENABLE : | +| Enable the timer interrupt | +| APCI1710_DISABLE : | +| Disable the timer interrupt| +i_ReturnValue=insn->n; + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_TimerNbr = (unsigned char) CR_CHAN(insn->chanspec); + b_ActionType = (unsigned char) data[0]; /* enable disable */ ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: Timer selection wrong | +| -4: The module is not a TIMER module | +| -5: Timer not initialised see function | +| "i_APCI1710_InitTimer" | +| -6: Interrupt parameter is wrong | +| -7: Interrupt function not initialised. | +| See function "i_APCI1710_SetBoardIntRoutineX" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnWriteEnableDisableTimer(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_DummyRead; + unsigned char b_ModulNbr; + unsigned char b_TimerNbr; + unsigned char b_ActionType; + unsigned char b_InterruptEnable; + + i_ReturnValue = insn->n; + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_TimerNbr = (unsigned char) CR_CHAN(insn->chanspec); + b_ActionType = (unsigned char) data[0]; /* enable disable */ + + /* Test the module number */ + if (b_ModulNbr < 4) { + /* Test if 82X54 timer */ + if ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF0000UL) == APCI1710_82X54_TIMER) { + /* Test the timer number */ + if (b_TimerNbr <= 2) { + /* Test if timer initialised */ + if (devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_82X54Init == 1) { + + switch (b_ActionType) { + case APCI1710_ENABLE: + b_InterruptEnable = (unsigned char) data[1]; + /* Test the interrupt selection */ + if ((b_InterruptEnable == APCI1710_ENABLE) || + (b_InterruptEnable == APCI1710_DISABLE)) { + if (b_InterruptEnable == APCI1710_ENABLE) { + + dw_DummyRead = inl(devpriv->s_BoardInfos.ui_Address + 12 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + + /* Enable the interrupt */ + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord = devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord | 0x8; + + outl(devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord, devpriv->s_BoardInfos.ui_Address + 32 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + devpriv->tsk_Current = current; /* Save the current process task structure */ + + } /* if (b_InterruptEnable == APCI1710_ENABLE) */ + else { + /* Disable the interrupt */ + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord = devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord & 0xF7; + + outl(devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord, devpriv->s_BoardInfos.ui_Address + 32 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + + /* Save the interrupt flag */ + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.b_InterruptMask = devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.b_InterruptMask & (0xFF - (1 << b_TimerNbr)); + } /* if (b_InterruptEnable == APCI1710_ENABLE) */ + + /* Test if error occur */ + if (i_ReturnValue >= 0) { + /* Save the interrupt flag */ + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.b_InterruptMask = devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.b_InterruptMask | ((1 & b_InterruptEnable) << b_TimerNbr); + + /* Enable the timer */ + outl(1, devpriv->s_BoardInfos.ui_Address + 44 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + } + } else { + /* Interrupt parameter is wrong */ + DPRINTK("\n"); + i_ReturnValue = -6; + } + break; + case APCI1710_DISABLE: + /* Test the interrupt flag */ + if (((devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.b_InterruptMask >> b_TimerNbr) & 1) == 1) { + /* Disable the interrupt */ + + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr]. dw_ConfigurationWord = devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord & 0xF7; + + outl(devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].dw_ConfigurationWord, devpriv->s_BoardInfos.ui_Address + 32 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + + /* Save the interrupt flag */ + devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.b_InterruptMask = devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.b_InterruptMask & (0xFF - (1 << b_TimerNbr)); + } + + /* Disable the timer */ + outl(0, devpriv->s_BoardInfos.ui_Address + 44 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + break; + } /* Switch end */ + } else { + /* Timer not initialised see function */ + DPRINTK ("Timer not initialised see function\n"); + i_ReturnValue = -5; + } + } else { + /* Timer selection wrong */ + DPRINTK("Timer selection wrong\n"); + i_ReturnValue = -3; + } /* if ((b_TimerNbr >= 0) && (b_TimerNbr <= 2)) */ + } else { + /* The module is not a TIMER module */ + DPRINTK("The module is not a TIMER module\n"); + i_ReturnValue = -4; + } + } else { + /* Module number error */ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadAllTimerValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| PULONG_ pul_TimerValueArray) +int i_APCI1710_InsnReadAllTimerValue(struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the all timer values from selected timer | +| module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board | +| APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_TimerValueArray : Timer value array. | +| Element 0 contain the timer 0 value. | +| Element 1 contain the timer 1 value. | +| Element 2 contain the timer 2 value. | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a TIMER module | +| -4: Timer 0 not initialised see function | +| "i_APCI1710_InitTimer" | +| -5: Timer 1 not initialised see function | +| "i_APCI1710_InitTimer" | +| -6: Timer 2 not initialised see function | +| "i_APCI1710_InitTimer" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnReadAllTimerValue(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned char b_ModulNbr, b_ReadType; + unsigned int *pul_TimerValueArray; + + b_ModulNbr = CR_AREF(insn->chanspec); + b_ReadType = CR_CHAN(insn->chanspec); + pul_TimerValueArray = (unsigned int *) data; + i_ReturnValue = insn->n; + + switch (b_ReadType) { + case APCI1710_TIMER_READINTERRUPT: + + data[0] = devpriv->s_InterruptParameters.s_FIFOInterruptParameters[devpriv->s_InterruptParameters.ui_Read].b_OldModuleMask; + data[1] = devpriv->s_InterruptParameters.s_FIFOInterruptParameters[devpriv->s_InterruptParameters.ui_Read].ul_OldInterruptMask; + data[2] = devpriv->s_InterruptParameters.s_FIFOInterruptParameters[devpriv->s_InterruptParameters.ui_Read].ul_OldCounterLatchValue; + + /* Increment the read FIFO */ + devpriv->s_InterruptParameters.ui_Read = (devpriv->s_InterruptParameters.ui_Read + 1) % APCI1710_SAVE_INTERRUPT; + + break; + + case APCI1710_TIMER_READALLTIMER: + /* Test the module number */ + if (b_ModulNbr < 4) { + /* Test if 82X54 timer */ + if ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF0000UL) == APCI1710_82X54_TIMER) { + /* Test if timer 0 iniutialised */ + if (devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[0].b_82X54Init == 1) { + /* Test if timer 1 iniutialised */ + if (devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[1].b_82X54Init == 1) { + /* Test if timer 2 iniutialised */ + if (devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[2].b_82X54Init == 1) { + /* Latch all counter */ + outl(0x17, devpriv->s_BoardInfos.ui_Address + 12 + (64 * b_ModulNbr)); + + /* Read the timer 0 value */ + pul_TimerValueArray[0] = inl(devpriv->s_BoardInfos.ui_Address + 0 + (64 * b_ModulNbr)); + + /* Read the timer 1 value */ + pul_TimerValueArray[1] = inl(devpriv->s_BoardInfos.ui_Address + 4 + (64 * b_ModulNbr)); + + /* Read the timer 2 value */ + pul_TimerValueArray[2] = inl(devpriv->s_BoardInfos.ui_Address + 8 + (64 * b_ModulNbr)); + } else { + /* Timer 2 not initialised see function */ + DPRINTK("Timer 2 not initialised see function\n"); + i_ReturnValue = -6; + } + } else { + /* Timer 1 not initialised see function */ + DPRINTK("Timer 1 not initialised see function\n"); + i_ReturnValue = -5; + } + } else { + /* Timer 0 not initialised see function */ + DPRINTK("Timer 0 not initialised see function\n"); + i_ReturnValue = -4; + } + } else { + /* The module is not a TIMER module */ + DPRINTK("The module is not a TIMER module\n"); + i_ReturnValue = -3; + } + } else { + /* Module number error */ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + } /* End of Switch */ + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name :INT i_APCI1710_InsnBitsTimer(struct comedi_device *dev, +struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read write functions for Timer | ++----------------------------------------------------------------------------+ +| Input Parameters : ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnBitsTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned char b_BitsType; + int i_ReturnValue = 0; + b_BitsType = data[0]; + + printk("\n82X54"); + + switch (b_BitsType) { + case APCI1710_TIMER_READVALUE: + i_ReturnValue = i_APCI1710_ReadTimerValue(dev, + (unsigned char)CR_AREF(insn->chanspec), + (unsigned char)CR_CHAN(insn->chanspec), + (unsigned int *) &data[0]); + break; + + case APCI1710_TIMER_GETOUTPUTLEVEL: + i_ReturnValue = i_APCI1710_GetTimerOutputLevel(dev, + (unsigned char)CR_AREF(insn->chanspec), + (unsigned char)CR_CHAN(insn->chanspec), + (unsigned char *) &data[0]); + break; + + case APCI1710_TIMER_GETPROGRESSSTATUS: + i_ReturnValue = i_APCI1710_GetTimerProgressStatus(dev, + (unsigned char)CR_AREF(insn->chanspec), + (unsigned char)CR_CHAN(insn->chanspec), + (unsigned char *)&data[0]); + break; + + case APCI1710_TIMER_WRITEVALUE: + i_ReturnValue = i_APCI1710_WriteTimerValue(dev, + (unsigned char)CR_AREF(insn->chanspec), + (unsigned char)CR_CHAN(insn->chanspec), + (unsigned int)data[1]); + + break; + + default: + printk("Bits Config Parameter Wrong\n"); + i_ReturnValue = -1; + } + + if (i_ReturnValue >= 0) + i_ReturnValue = insn->n; + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadTimerValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TimerNbr, | +| PULONG_ pul_TimerValue) | ++----------------------------------------------------------------------------+ +| Task : Return the timer value from selected digital timer | +| (b_TimerNbr) from selected timer module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board | +| APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number | +| (0 to 3) | +| unsigned char_ b_TimerNbr : Timer number to read | +| (0 to 2) | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_TimerValue : Timer value | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: Timer selection wrong | +| -4: The module is not a TIMER module | +| -5: Timer not initialised see function | +| "i_APCI1710_InitTimer" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_ReadTimerValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_TimerNbr, + unsigned int *pul_TimerValue) +{ + int i_ReturnValue = 0; + + /* Test the module number */ + if (b_ModulNbr < 4) { + /* Test if 82X54 timer */ + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_82X54_TIMER) { + /* Test the timer number */ + if (b_TimerNbr <= 2) { + /* Test if timer initialised */ + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_82X54ModuleInfo. + s_82X54TimerInfo[b_TimerNbr]. + b_82X54Init == 1) { + /* Latch the timer value */ + outl((2 << b_TimerNbr) | 0xD0, + devpriv->s_BoardInfos. + ui_Address + 12 + + (64 * b_ModulNbr)); + + /* Read the counter value */ + *pul_TimerValue = + inl(devpriv->s_BoardInfos. + ui_Address + (b_TimerNbr * 4) + + (64 * b_ModulNbr)); + } else { + /* Timer not initialised see function */ + DPRINTK("Timer not initialised see function\n"); + i_ReturnValue = -5; + } + } else { + /* Timer selection wrong */ + DPRINTK("Timer selection wrong\n"); + i_ReturnValue = -3; + } /* if ((b_TimerNbr >= 0) && (b_TimerNbr <= 2)) */ + } else { + /* The module is not a TIMER module */ + DPRINTK("The module is not a TIMER module\n"); + i_ReturnValue = -4; + } + } else { + /* Module number error */ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + + /* + +----------------------------------------------------------------------------+ + | Function Name : _INT_ i_APCI1710_GetTimerOutputLevel | + | (unsigned char_ b_BoardHandle, | + | unsigned char_ b_ModulNbr, | + | unsigned char_ b_TimerNbr, | + | unsigned char *_ pb_OutputLevel) | + +----------------------------------------------------------------------------+ + | Task : Return the output signal level (pb_OutputLevel) from | + | selected digital timer (b_TimerNbr) from selected timer| + | module (b_ModulNbr). | + +----------------------------------------------------------------------------+ + | Input Parameters : unsigned char_ b_BoardHandle : Handle of board | + | APCI-1710 | + | unsigned char_ b_ModulNbr : Selected module number | + | (0 to 3) | + | unsigned char_ b_TimerNbr : Timer number to test | + | (0 to 2) | + +----------------------------------------------------------------------------+ + | Output Parameters : unsigned char *_ pb_OutputLevel : Output signal level | + | 0 : The output is low | + | 1 : The output is high | + +----------------------------------------------------------------------------+ + | Return Value : 0: No error | + | -1: The handle parameter of the board is wrong | + | -2: Module selection wrong | + | -3: Timer selection wrong | + | -4: The module is not a TIMER module | + | -5: Timer not initialised see function | + | "i_APCI1710_InitTimer" | + +----------------------------------------------------------------------------+ + */ + +int i_APCI1710_GetTimerOutputLevel(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_TimerNbr, + unsigned char *pb_OutputLevel) +{ + int i_ReturnValue = 0; + unsigned int dw_TimerStatus; + + /* Test the module number */ + if (b_ModulNbr < 4) { + /* Test if 82X54 timer */ + if ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF0000UL) == APCI1710_82X54_TIMER) { + /* Test the timer number */ + if (b_TimerNbr <= 2) { + /* Test if timer initialised */ + if (devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_82X54Init == 1) { + /* Latch the timer value */ + outl((2 << b_TimerNbr) | 0xE0, devpriv->s_BoardInfos.ui_Address + 12 + (64 * b_ModulNbr)); + + /* Read the timer status */ + dw_TimerStatus = inl(devpriv->s_BoardInfos.ui_Address + 16 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + + *pb_OutputLevel = (unsigned char) (((dw_TimerStatus >> 7) & 1) ^ devpriv-> s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_OutputLevel); + } else { + /* Timer not initialised see function */ + DPRINTK("Timer not initialised see function\n"); + i_ReturnValue = -5; + } + } else { + /* Timer selection wrong */ + DPRINTK("Timer selection wrong\n"); + i_ReturnValue = -3; + } /* if ((b_TimerNbr >= 0) && (b_TimerNbr <= 2)) */ + } else { + /* The module is not a TIMER module */ + DPRINTK("The module is not a TIMER module\n"); + i_ReturnValue = -4; + } + } else { + /* Module number error */ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetTimerProgressStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TimerNbr, | +| unsigned char *_ pb_TimerStatus) | ++----------------------------------------------------------------------------+ +| Task : Return the progress status (pb_TimerStatus) from | +| selected digital timer (b_TimerNbr) from selected timer| +| module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board | +| APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number | +| (0 to 3) | +| unsigned char_ b_TimerNbr : Timer number to test | +| (0 to 2) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_TimerStatus : Output signal level | +| 0 : Timer not in progress | +| 1 : Timer in progress | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: Timer selection wrong | +| -4: The module is not a TIMER module | +| -5: Timer not initialised see function | +| "i_APCI1710_InitTimer" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_GetTimerProgressStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_TimerNbr, + unsigned char *pb_TimerStatus) +{ + int i_ReturnValue = 0; + unsigned int dw_TimerStatus; + + /* Test the module number */ + if (b_ModulNbr < 4) { + /* Test if 82X54 timer */ + + if ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF0000UL) == APCI1710_82X54_TIMER) { + /* Test the timer number */ + if (b_TimerNbr <= 2) { + /* Test if timer initialised */ + if (devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_82X54Init == 1) { + /* Latch the timer value */ + outl((2 << b_TimerNbr) | 0xE0, devpriv->s_BoardInfos.ui_Address + 12 + (64 * b_ModulNbr)); + + /* Read the timer status */ + dw_TimerStatus = inl(devpriv->s_BoardInfos.ui_Address + 16 + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + + *pb_TimerStatus = (unsigned char) ((dw_TimerStatus) >> 8) & 1; + printk("ProgressStatus : %d", *pb_TimerStatus); + } else { + /* Timer not initialised see function */ + i_ReturnValue = -5; + } + } else { + /* Timer selection wrong */ + i_ReturnValue = -3; + } /* if ((b_TimerNbr >= 0) && (b_TimerNbr <= 2)) */ + } else { + /* The module is not a TIMER module */ + + i_ReturnValue = -4; + } + } else { + /* Module number error */ + + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_WriteTimerValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TimerNbr, | +| ULONG_ ul_WriteValue) | ++----------------------------------------------------------------------------+ +| Task : Write the value (ul_WriteValue) into the selected timer| +| (b_TimerNbr) from selected timer module (b_ModulNbr). | +| The action in depend of the time mode selection. | +| See timer mode description table. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board | +| APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number | +| (0 to 3) | +| unsigned char_ b_TimerNbr : Timer number to write | +| (0 to 2) | +| ULONG_ ul_WriteValue : Value to write | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: Timer selection wrong | +| -4: The module is not a TIMER module | +| -5: Timer not initialised see function | +| "i_APCI1710_InitTimer" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_WriteTimerValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_TimerNbr, + unsigned int ul_WriteValue) +{ + int i_ReturnValue = 0; + + /* Test the module number */ + if (b_ModulNbr < 4) { + /* Test if 82X54 timer */ + if ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF0000UL) == APCI1710_82X54_TIMER) { + /* Test the timer number */ + if (b_TimerNbr <= 2) { + /* Test if timer initialised */ + if (devpriv->s_ModuleInfo[b_ModulNbr].s_82X54ModuleInfo.s_82X54TimerInfo[b_TimerNbr].b_82X54Init == 1) { + /* Write the value */ + outl(ul_WriteValue, devpriv->s_BoardInfos.ui_Address + (b_TimerNbr * 4) + (64 * b_ModulNbr)); + } else { + /* Timer not initialised see function */ + DPRINTK("Timer not initialised see function\n"); + i_ReturnValue = -5; + } + } else { + /* Timer selection wrong */ + DPRINTK("Timer selection wrong\n"); + i_ReturnValue = -3; + } /* if ((b_TimerNbr >= 0) && (b_TimerNbr <= 2)) */ + } else { + /* The module is not a TIMER module */ + DPRINTK("The module is not a TIMER module\n"); + i_ReturnValue = -4; + } + } else { + /* Module number error */ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_82x54.h b/drivers/staging/comedi/drivers/addi-data/APCI1710_82x54.h new file mode 100644 index 00000000..81346dbc --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_82x54.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define APCI1710_PCI_BUS_CLOCK 0 +#define APCI1710_FRONT_CONNECTOR_INPUT 1 +#define APCI1710_TIMER_READVALUE 0 +#define APCI1710_TIMER_GETOUTPUTLEVEL 1 +#define APCI1710_TIMER_GETPROGRESSSTATUS 2 +#define APCI1710_TIMER_WRITEVALUE 3 + +#define APCI1710_TIMER_READINTERRUPT 1 +#define APCI1710_TIMER_READALLTIMER 2 + +/* BEGIN JK 27.10.03 : Add the possibility to use a 40 Mhz quartz */ +#ifndef APCI1710_10MHZ +#define APCI1710_10MHZ 10 +#endif +/* END JK 27.10.03 : Add the possibility to use a 40 Mhz quartz */ + +/* + * 82X54 TIMER INISIALISATION FUNCTION + */ +int i_APCI1710_InsnConfigInitTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnWriteEnableDisableTimer(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * 82X54 READ FUNCTION + */ +int i_APCI1710_InsnReadAllTimerValue(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnBitsTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * 82X54 READ & WRITE FUNCTION + */ +int i_APCI1710_ReadTimerValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_TimerNbr, + unsigned int *pul_TimerValue); + +int i_APCI1710_GetTimerOutputLevel(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_TimerNbr, + unsigned char *pb_OutputLevel); + +int i_APCI1710_GetTimerProgressStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_TimerNbr, + unsigned char *pb_TimerStatus); + +/* + * 82X54 WRITE FUNCTION + */ +int i_APCI1710_WriteTimerValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_TimerNbr, + unsigned int ul_WriteValue); diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Chrono.c b/drivers/staging/comedi/drivers/addi-data/APCI1710_Chrono.c new file mode 100644 index 00000000..482a412a --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Chrono.c @@ -0,0 +1,2031 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : API APCI1710 | Compiler : gcc | + | Module name : CHRONO.C | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description : APCI-1710 chronometer module | + | | + | | + +-----------------------------------------------------------------------+ + | UPDATES | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | 29/06/98 | S. Weber | Digital input / output implementation | + |----------|-----------|------------------------------------------------| + | 08/05/00 | Guinot C | - 0400/0228 All Function in RING 0 | + | | | available | + +-----------------------------------------------------------------------+ + | | | | + | | | | + +-----------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ +#include "APCI1710_Chrono.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitChrono | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_ChronoMode, | +| unsigned char_ b_PCIInputClock, | +| unsigned char_ b_TimingUnit, | +| ULONG_ ul_TimingInterval, | +| PULONG_ pul_RealTimingInterval) + ++----------------------------------------------------------------------------+ +| Task : Configure the chronometer operating mode (b_ChronoMode)| +| from selected module (b_ModulNbr). | +| The ul_TimingInterval and ul_TimingUnit determine the | +| timing base for the measurement. | +| The pul_RealTimingInterval return the real timing | +| value. You must calling this function be for you call | +| any other function witch access of the chronometer. | +| | +| Witch this functionality from the APCI-1710 you have | +| the possibility to measure the timing witch two event. | +| | +| The mode 0 and 1 is appropriate for period measurement.| +| The mode 2 and 3 is appropriate for frequent | +| measurement. | +| The mode 4 to 7 is appropriate for measuring the timing| +| between two event. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr CR_AREF(insn->chanspec) : Module number to configure | +| (0 to 3) | +| unsigned char_ b_ChronoMode data[0] : Chronometer action mode | +| (0 to 7). | +| unsigned char_ b_PCIInputClock data[1] : Selection from PCI bus clock| +| - APCI1710_30MHZ : | +| The PC have a PCI bus | +| clock from 30 MHz | +| - APCI1710_33MHZ : | +| The PC have a PCI bus | +| clock from 33 MHz | +| - APCI1710_40MHZ | +| The APCI-1710 have a | +| integrated 40Mhz | +| quartz. | +| unsigned char_ b_TimingUnit data[2] : Base timing unity (0 to 4) | +| 0 : ns | +| 1 : µs | +| 2 : ms | +| 3 : s | +| 4 : mn | +| ULONG_ ul_TimingInterval : data[3] Base timing value. | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_RealTimingInterval : Real base timing | +| value. +| data[0] ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a Chronometer module | +| -4: Chronometer mode selection is wrong | +| -5: The selected PCI input clock is wrong | +| -6: Timing unity selection is wrong | +| -7: Base timing selection is wrong | +| -8: You can not used the 40MHz clock selection with | +| this board | +| -9: You can not used the 40MHz clock selection with | +| this CHRONOS version | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnConfigInitChrono(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int ul_TimerValue = 0; + unsigned int ul_TimingInterval = 0; + unsigned int ul_RealTimingInterval = 0; + double d_RealTimingInterval = 0; + unsigned int dw_ModeArray[8] = + { 0x01, 0x05, 0x00, 0x04, 0x02, 0x0E, 0x0A, 0x06 }; + unsigned char b_ModulNbr, b_ChronoMode, b_PCIInputClock, b_TimingUnit; + + b_ModulNbr = CR_AREF(insn->chanspec); + b_ChronoMode = (unsigned char) data[0]; + b_PCIInputClock = (unsigned char) data[1]; + b_TimingUnit = (unsigned char) data[2]; + ul_TimingInterval = (unsigned int) data[3]; + i_ReturnValue = insn->n; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if chronometer */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_CHRONOMETER) { + /*****************************/ + /* Test the chronometer mode */ + /*****************************/ + + if (b_ChronoMode <= 7) { + /**************************/ + /* Test the PCI bus clock */ + /**************************/ + + if ((b_PCIInputClock == APCI1710_30MHZ) || + (b_PCIInputClock == APCI1710_33MHZ) || + (b_PCIInputClock == APCI1710_40MHZ)) { + /*************************/ + /* Test the timing unity */ + /*************************/ + + if (b_TimingUnit <= 4) { + /**********************************/ + /* Test the base timing selection */ + /**********************************/ + + if (((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 0) && (ul_TimingInterval >= 66) && (ul_TimingInterval <= 0xFFFFFFFFUL)) || ((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 1) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 143165576UL)) || ((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 2) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 143165UL)) || ((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 3) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 143UL)) || ((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 4) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 2UL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 0) && (ul_TimingInterval >= 60) && (ul_TimingInterval <= 0xFFFFFFFFUL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 1) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 130150240UL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 2) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 130150UL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 3) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 130UL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 4) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 2UL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 0) && (ul_TimingInterval >= 50) && (ul_TimingInterval <= 0xFFFFFFFFUL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 1) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 107374182UL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 2) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 107374UL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 3) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 107UL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 4) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 1UL))) { + /**************************/ + /* Test the board version */ + /**************************/ + + if (((b_PCIInputClock == APCI1710_40MHZ) && (devpriv->s_BoardInfos.b_BoardVersion > 0)) || (b_PCIInputClock != APCI1710_40MHZ)) { + /************************/ + /* Test the TOR version */ + /************************/ + + if (((b_PCIInputClock == APCI1710_40MHZ) && ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF) >= 0x3131)) || (b_PCIInputClock != APCI1710_40MHZ)) { + fpu_begin + (); + + /****************************************/ + /* Calculate the timer 0 division fator */ + /****************************************/ + + switch (b_TimingUnit) { + /******/ + /* ns */ + /******/ + + case 0: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + (ul_TimingInterval + * + (0.001 * b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (0.001 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (0.001 * (double)b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + (0.001 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (0.001 * (double)b_PCIInputClock)) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 0.99392); + } + + break; + + /******/ + /* æs */ + /******/ + + case 1: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + (ul_TimingInterval + * + (1.0 * b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (1.0 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (1.0 * (double)b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + ( + (double) + 1.0 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (1.0 * (double)b_PCIInputClock)) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 0.99392); + } + + break; + + /******/ + /* ms */ + /******/ + + case 2: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + ul_TimingInterval + * + (1000 + * + b_PCIInputClock); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (1000.0 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (1000.0 * (double)b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + (1000.0 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (1000.0 * (double)b_PCIInputClock)) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 0.99392); + } + + break; + + /*****/ + /* s */ + /*****/ + + case 3: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + (ul_TimingInterval + * + (1000000.0 + * + b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (1000000.0 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (1000000.0 + * + (double) + b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + (1000000.0 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (1000000.0 * (double)b_PCIInputClock)) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 0.99392); + } + + break; + + /******/ + /* mn */ + /******/ + + case 4: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + ( + (ul_TimingInterval + * + 60) + * + (1000000.0 + * + b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)(ul_TimingInterval * 60.0) * (1000000.0 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (1000000.0 + * + (double) + b_PCIInputClock)) + / + 60; + d_RealTimingInterval + = + ( + (double) + ul_TimerValue + / + (0.001 * (double)b_PCIInputClock)) / 60.0; + + if ((double)(((double)ul_TimerValue / (1000000.0 * (double)b_PCIInputClock)) / 60.0) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 0.99392); + } + + break; + } + + fpu_end(); + + /****************************/ + /* Save the PCI input clock */ + /****************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + b_PCIInputClock + = + b_PCIInputClock; + + /*************************/ + /* Save the timing unity */ + /*************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + b_TimingUnit + = + b_TimingUnit; + + /************************/ + /* Save the base timing */ + /************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + d_TimingInterval + = + d_RealTimingInterval; + + /****************************/ + /* Set the chronometer mode */ + /****************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + dw_ConfigReg + = + dw_ModeArray + [b_ChronoMode]; + + /***********************/ + /* Test if 40 MHz used */ + /***********************/ + + if (b_PCIInputClock == APCI1710_40MHZ) { + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + dw_ConfigReg + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + dw_ConfigReg + | + 0x80; + } + + outl(devpriv->s_ModuleInfo[b_ModulNbr].s_ChronoModuleInfo.dw_ConfigReg, devpriv->s_BoardInfos.ui_Address + 16 + (64 * b_ModulNbr)); + + /***********************/ + /* Write timer 0 value */ + /***********************/ + + outl(ul_TimerValue, devpriv->s_BoardInfos.ui_Address + (64 * b_ModulNbr)); + + /*********************/ + /* Chronometer init. */ + /*********************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + b_ChronoInit + = + 1; + } else { + /***********************************************/ + /* TOR version error for 40MHz clock selection */ + /***********************************************/ + + DPRINTK("TOR version error for 40MHz clock selection\n"); + i_ReturnValue + = + -9; + } + } else { + /**************************************************************/ + /* You can not use the 40MHz clock selection with this board */ + /**************************************************************/ + + DPRINTK("You can not used the 40MHz clock selection with this board\n"); + i_ReturnValue = + -8; + } + } else { + /**********************************/ + /* Base timing selection is wrong */ + /**********************************/ + + DPRINTK("Base timing selection is wrong\n"); + i_ReturnValue = -7; + } + } /* if ((b_TimingUnit >= 0) && (b_TimingUnit <= 4)) */ + else { + /***********************************/ + /* Timing unity selection is wrong */ + /***********************************/ + + DPRINTK("Timing unity selection is wrong\n"); + i_ReturnValue = -6; + } /* if ((b_TimingUnit >= 0) && (b_TimingUnit <= 4)) */ + } /* if ((b_PCIInputClock == APCI1710_30MHZ) || (b_PCIInputClock == APCI1710_33MHZ)) */ + else { + /*****************************************/ + /* The selected PCI input clock is wrong */ + /*****************************************/ + + DPRINTK("The selected PCI input clock is wrong\n"); + i_ReturnValue = -5; + } /* if ((b_PCIInputClock == APCI1710_30MHZ) || (b_PCIInputClock == APCI1710_33MHZ)) */ + } /* if (b_ChronoMode >= 0 && b_ChronoMode <= 7) */ + else { + /***************************************/ + /* Chronometer mode selection is wrong */ + /***************************************/ + + DPRINTK("Chronometer mode selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_ChronoMode >= 0 && b_ChronoMode <= 7) */ + } else { + /******************************************/ + /* The module is not a Chronometer module */ + /******************************************/ + + DPRINTK("The module is not a Chronometer module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + data[0] = ul_RealTimingInterval; + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_EnableChrono | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_CycleMode, | +| unsigned char_ b_InterruptEnable) +int i_APCI1710_InsnWriteEnableDisableChrono(struct comedi_device *dev, +struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Enable the chronometer from selected module | +| (b_ModulNbr). You must calling the | +| "i_APCI1710_InitChrono" function be for you call this | +| function. | +| If you enable the chronometer interrupt, the | +| chronometer generate a interrupt after the stop signal.| +| See function "i_APCI1710_SetBoardIntRoutineX" and the | +| Interrupt mask description chapter from this manual. | +| The b_CycleMode parameter determine if you will | +| measured a single or more cycle. + +| Disable the chronometer from selected module | +| (b_ModulNbr). If you disable the chronometer after a | +| start signal occur and you restart the chronometer | +| witch the " i_APCI1710_EnableChrono" function, if no | +| stop signal occur this start signal is ignored. ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr CR_AREF(chanspec) : Selected module number (0 to 3) | + data[0] ENABle/Disable chrono +| unsigned char_ b_CycleMode : Selected the chronometer | +| data[1] acquisition mode | +| unsigned char_ b_InterruptEnable : Enable or disable the | +| data[2] chronometer interrupt. | +| APCI1710_ENABLE: | +| Enable the chronometer | +| interrupt | +| APCI1710_DISABLE: | +| Disable the chronometer | +| interrupt | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a Chronometer module | +| -4: Chronometer not initialised see function | +| "i_APCI1710_InitChrono" | +| -5: Chronometer acquisition mode cycle is wrong | +| -6: Interrupt parameter is wrong | +| -7: Interrupt function not initialised. | +| See function "i_APCI1710_SetBoardIntRoutineX" + -8: data[0] wrong input | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnWriteEnableDisableChrono(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned char b_ModulNbr, b_CycleMode, b_InterruptEnable, b_Action; + b_ModulNbr = CR_AREF(insn->chanspec); + b_Action = (unsigned char) data[0]; + b_CycleMode = (unsigned char) data[1]; + b_InterruptEnable = (unsigned char) data[2]; + i_ReturnValue = insn->n; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if chronometer */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_CHRONOMETER) { + /***********************************/ + /* Test if chronometer initialised */ + /***********************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo.b_ChronoInit == 1) { + + switch (b_Action) { + + case APCI1710_ENABLE: + + /*********************************/ + /* Test the cycle mode parameter */ + /*********************************/ + + if ((b_CycleMode == APCI1710_SINGLE) + || (b_CycleMode == + APCI1710_CONTINUOUS)) { + /***************************/ + /* Test the interrupt flag */ + /***************************/ + + if ((b_InterruptEnable == + APCI1710_ENABLE) + || (b_InterruptEnable == + APCI1710_DISABLE)) + { + + /***************************/ + /* Save the interrupt flag */ + /***************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + b_InterruptMask + = + b_InterruptEnable; + + /***********************/ + /* Save the cycle mode */ + /***********************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + b_CycleMode = + b_CycleMode; + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + dw_ConfigReg = + (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + dw_ConfigReg & + 0x8F) | ((1 & + b_InterruptEnable) + << 5) | ((1 & + b_CycleMode) + << 6) | 0x10; + + /*****************************/ + /* Test if interrupt enabled */ + /*****************************/ + + if (b_InterruptEnable == + APCI1710_ENABLE) + { + /****************************/ + /* Clear the interrupt flag */ + /****************************/ + + outl(devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + dw_ConfigReg, + devpriv-> + s_BoardInfos. + ui_Address + + 32 + + (64 * b_ModulNbr)); + devpriv->tsk_Current = current; /* Save the current process task structure */ + } + + /***********************************/ + /* Enable or disable the interrupt */ + /* Enable the chronometer */ + /***********************************/ + + outl(devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + dw_ConfigReg, + devpriv-> + s_BoardInfos. + ui_Address + + 16 + + (64 * b_ModulNbr)); + + /*************************/ + /* Clear status register */ + /*************************/ + + outl(0, devpriv-> + s_BoardInfos. + ui_Address + + 36 + + (64 * b_ModulNbr)); + + } /* if ((b_InterruptEnable == APCI1710_ENABLE) || (b_InterruptEnable == APCI1710_DISABLE)) */ + else { + /********************************/ + /* Interrupt parameter is wrong */ + /********************************/ + + DPRINTK("Interrupt parameter is wrong\n"); + i_ReturnValue = -6; + } /* if ((b_InterruptEnable == APCI1710_ENABLE) || (b_InterruptEnable == APCI1710_DISABLE)) */ + } /* if ((b_CycleMode == APCI1710_SINGLE) || (b_CycleMode == APCI1710_CONTINUOUS)) */ + else { + /***********************************************/ + /* Chronometer acquisition mode cycle is wrong */ + /***********************************************/ + + DPRINTK("Chronometer acquisition mode cycle is wrong\n"); + i_ReturnValue = -5; + } /* if ((b_CycleMode == APCI1710_SINGLE) || (b_CycleMode == APCI1710_CONTINUOUS)) */ + break; + + case APCI1710_DISABLE: + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo. + b_InterruptMask = 0; + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo. + dw_ConfigReg = + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo. + dw_ConfigReg & 0x2F; + + /***************************/ + /* Disable the interrupt */ + /* Disable the chronometer */ + /***************************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo.dw_ConfigReg, + devpriv->s_BoardInfos. + ui_Address + 16 + + (64 * b_ModulNbr)); + + /***************************/ + /* Test if continuous mode */ + /***************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo. + b_CycleMode == + APCI1710_CONTINUOUS) { + /*************************/ + /* Clear status register */ + /*************************/ + + outl(0, devpriv->s_BoardInfos. + ui_Address + 36 + + (64 * b_ModulNbr)); + } + break; + + default: + DPRINTK("Inputs wrong! Enable or Disable chrono\n"); + i_ReturnValue = -8; + } /* switch ENABLE/DISABLE */ + } else { + /*******************************/ + /* Chronometer not initialised */ + /*******************************/ + + DPRINTK("Chronometer not initialised\n"); + i_ReturnValue = -4; + } + } else { + /******************************************/ + /* The module is not a Chronometer module */ + /******************************************/ + + DPRINTK("The module is not a Chronometer module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name :INT i_APCI1710_InsnReadChrono(struct comedi_device *dev,struct comedi_subdevice *s, +struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read functions for Timer | ++----------------------------------------------------------------------------+ +| Input Parameters : ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnReadChrono(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned char b_ReadType; + int i_ReturnValue = insn->n; + + b_ReadType = CR_CHAN(insn->chanspec); + + switch (b_ReadType) { + case APCI1710_CHRONO_PROGRESS_STATUS: + i_ReturnValue = i_APCI1710_GetChronoProgressStatus(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char *) &data[0]); + break; + + case APCI1710_CHRONO_READVALUE: + i_ReturnValue = i_APCI1710_ReadChronoValue(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned int) insn->unused[0], + (unsigned char *) &data[0], (unsigned int *) &data[1]); + break; + + case APCI1710_CHRONO_CONVERTVALUE: + i_ReturnValue = i_APCI1710_ConvertChronoValue(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned int) insn->unused[0], + (unsigned int *) &data[0], + (unsigned char *) &data[1], + (unsigned char *) &data[2], + (unsigned int *) &data[3], + (unsigned int *) &data[4], (unsigned int *) &data[5]); + break; + + case APCI1710_CHRONO_READINTERRUPT: + printk("In Chrono Read Interrupt\n"); + + data[0] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].b_OldModuleMask; + data[1] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldInterruptMask; + data[2] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldCounterLatchValue; + + /**************************/ + /* Increment the read FIFO */ + /***************************/ + + devpriv-> + s_InterruptParameters. + ui_Read = (devpriv-> + s_InterruptParameters. + ui_Read + 1) % APCI1710_SAVE_INTERRUPT; + break; + + default: + printk("ReadType Parameter wrong\n"); + } + + if (i_ReturnValue >= 0) + i_ReturnValue = insn->n; + return i_ReturnValue; + +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetChronoProgressStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char *_ pb_ChronoStatus) | ++----------------------------------------------------------------------------+ +| Task : Return the chronometer status (pb_ChronoStatus) from | +| selected chronometer module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pb_ChronoStatus : Return the chronometer | +| status. | +| 0 : Measurement not started.| +| No start signal occur. | +| 1 : Measurement started. | +| A start signal occur. | +| 2 : Measurement stopped. | +| A stop signal occur. | +| The measurement is | +| terminate. | +| 3: A overflow occur. You | +| must change the base | +| timing witch the | +| function | +| "i_APCI1710_InitChrono" | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a Chronometer module | +| -4: Chronometer not initialised see function | +| "i_APCI1710_InitChrono" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_GetChronoProgressStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_ChronoStatus) +{ + int i_ReturnValue = 0; + unsigned int dw_Status; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if chronometer */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_CHRONOMETER) { + /***********************************/ + /* Test if chronometer initialised */ + /***********************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo.b_ChronoInit == 1) { + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 8 + (64 * b_ModulNbr)); + + /********************/ + /* Test if overflow */ + /********************/ + + if ((dw_Status & 8) == 8) { + /******************/ + /* Overflow occur */ + /******************/ + + *pb_ChronoStatus = 3; + } /* if ((dw_Status & 8) == 8) */ + else { + /*******************************/ + /* Test if measurement stopped */ + /*******************************/ + + if ((dw_Status & 2) == 2) { + /***********************/ + /* A stop signal occur */ + /***********************/ + + *pb_ChronoStatus = 2; + } /* if ((dw_Status & 2) == 2) */ + else { + /*******************************/ + /* Test if measurement started */ + /*******************************/ + + if ((dw_Status & 1) == 1) { + /************************/ + /* A start signal occur */ + /************************/ + + *pb_ChronoStatus = 1; + } /* if ((dw_Status & 1) == 1) */ + else { + /***************************/ + /* Measurement not started */ + /***************************/ + + *pb_ChronoStatus = 0; + } /* if ((dw_Status & 1) == 1) */ + } /* if ((dw_Status & 2) == 2) */ + } /* if ((dw_Status & 8) == 8) */ + } else { + /*******************************/ + /* Chronometer not initialised */ + /*******************************/ + DPRINTK("Chronometer not initialised\n"); + i_ReturnValue = -4; + } + } else { + /******************************************/ + /* The module is not a Chronometer module */ + /******************************************/ + DPRINTK("The module is not a Chronometer module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadChronoValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned int_ ui_TimeOut, | +| unsigned char *_ pb_ChronoStatus, | +| PULONG_ pul_ChronoValue) | ++----------------------------------------------------------------------------+ +| Task : Return the chronometer status (pb_ChronoStatus) and the| +| timing value (pul_ChronoValue) after a stop signal | +| occur from selected chronometer module (b_ModulNbr). | +| This function are only avaible if you have disabled | +| the interrupt functionality. See function | +| "i_APCI1710_EnableChrono" and the Interrupt mask | +| description chapter. | +| You can test the chronometer status witch the | +| "i_APCI1710_GetChronoProgressStatus" function. | +| | +| The returned value from pul_ChronoValue parameter is | +| not real measured timing. | +| You must used the "i_APCI1710_ConvertChronoValue" | +| function or make this operation for calculate the | +| timing: | +| | +| Timing = pul_ChronoValue * pul_RealTimingInterval. | +| | +| pul_RealTimingInterval is the returned parameter from | +| "i_APCI1710_InitChrono" function and the time unity is | +| the b_TimingUnit from "i_APCI1710_InitChrono" function| ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pb_ChronoStatus : Return the chronometer | +| status. | +| 0 : Measurement not started.| +| No start signal occur. | +| 1 : Measurement started. | +| A start signal occur. | +| 2 : Measurement stopped. | +| A stop signal occur. | +| The measurement is | +| terminate. | +| 3: A overflow occur. You | +| must change the base | +| timing witch the | +| function | +| "i_APCI1710_InitChrono" | +| unsigned int * pul_ChronoValue : Chronometer timing value. | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a Chronometer module | +| -4: Chronometer not initialised see function | +| "i_APCI1710_InitChrono" | +| -5: Timeout parameter is wrong (0 to 65535) | +| -6: Interrupt routine installed. You can not read | +| directly the chronometer measured timing. | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_ReadChronoValue(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned int ui_TimeOut, unsigned char *pb_ChronoStatus, unsigned int *pul_ChronoValue) +{ + int i_ReturnValue = 0; + unsigned int dw_Status; + unsigned int dw_TimeOut = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if chronometer */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_CHRONOMETER) { + /***********************************/ + /* Test if chronometer initialised */ + /***********************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo.b_ChronoInit == 1) { + /*****************************/ + /* Test the timout parameter */ + /*****************************/ + + if (ui_TimeOut <= 65535UL) { + + for (;;) { + /*******************/ + /* Read the status */ + /*******************/ + + dw_Status = + inl(devpriv-> + s_BoardInfos. + ui_Address + 8 + + (64 * b_ModulNbr)); + + /********************/ + /* Test if overflow */ + /********************/ + + if ((dw_Status & 8) == 8) { + /******************/ + /* Overflow occur */ + /******************/ + + *pb_ChronoStatus = 3; + + /***************************/ + /* Test if continuous mode */ + /***************************/ + + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + b_CycleMode == + APCI1710_CONTINUOUS) + { + /*************************/ + /* Clear status register */ + /*************************/ + + outl(0, devpriv->s_BoardInfos.ui_Address + 36 + (64 * b_ModulNbr)); + } + + break; + } /* if ((dw_Status & 8) == 8) */ + else { + /*******************************/ + /* Test if measurement stopped */ + /*******************************/ + + if ((dw_Status & 2) == + 2) { + /***********************/ + /* A stop signal occur */ + /***********************/ + + *pb_ChronoStatus + = 2; + + /***************************/ + /* Test if continnous mode */ + /***************************/ + + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_ChronoModuleInfo. + b_CycleMode + == + APCI1710_CONTINUOUS) + { + /*************************/ + /* Clear status register */ + /*************************/ + + outl(0, devpriv->s_BoardInfos.ui_Address + 36 + (64 * b_ModulNbr)); + } + break; + } /* if ((dw_Status & 2) == 2) */ + else { + /*******************************/ + /* Test if measurement started */ + /*******************************/ + + if ((dw_Status & 1) == 1) { + /************************/ + /* A start signal occur */ + /************************/ + + *pb_ChronoStatus + = + 1; + } /* if ((dw_Status & 1) == 1) */ + else { + /***************************/ + /* Measurement not started */ + /***************************/ + + *pb_ChronoStatus + = + 0; + } /* if ((dw_Status & 1) == 1) */ + } /* if ((dw_Status & 2) == 2) */ + } /* if ((dw_Status & 8) == 8) */ + + if (dw_TimeOut == ui_TimeOut) { + /*****************/ + /* Timeout occur */ + /*****************/ + + break; + } else { + /*************************/ + /* Increment the timeout */ + /*************************/ + + dw_TimeOut = + dw_TimeOut + 1; + mdelay(1000); + + } + } /* for (;;) */ + + /*****************************/ + /* Test if stop signal occur */ + /*****************************/ + + if (*pb_ChronoStatus == 2) { + /**********************************/ + /* Read the measured timing value */ + /**********************************/ + + *pul_ChronoValue = + inl(devpriv-> + s_BoardInfos. + ui_Address + 4 + + (64 * b_ModulNbr)); + + if (*pul_ChronoValue != 0) { + *pul_ChronoValue = + *pul_ChronoValue + - 1; + } + } else { + /*************************/ + /* Test if timeout occur */ + /*************************/ + + if ((*pb_ChronoStatus != 3) + && (dw_TimeOut == + ui_TimeOut) + && (ui_TimeOut != 0)) { + /*****************/ + /* Timeout occur */ + /*****************/ + + *pb_ChronoStatus = 4; + } + } + + } else { + /******************************/ + /* Timeout parameter is wrong */ + /******************************/ + DPRINTK("Timeout parameter is wrong\n"); + i_ReturnValue = -5; + } + } else { + /*******************************/ + /* Chronometer not initialised */ + /*******************************/ + DPRINTK("Chronometer not initialised\n"); + i_ReturnValue = -4; + } + } else { + /******************************************/ + /* The module is not a Chronometer module */ + /******************************************/ + DPRINTK("The module is not a Chronometer module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ConvertChronoValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| ULONG_ ul_ChronoValue, | +| PULONG_ pul_Hour, | +| unsigned char *_ pb_Minute, | +| unsigned char *_ pb_Second, | +| unsigned int *_ pui_MilliSecond, | +| unsigned int *_ pui_MicroSecond, | +| unsigned int *_ pui_NanoSecond) | ++----------------------------------------------------------------------------+ +| Task : Convert the chronometer measured timing | +| (ul_ChronoValue) in to h, mn, s, ms, µs, ns. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3)| +| ULONG_ ul_ChronoValue : Measured chronometer timing | +| value. | +| See"i_APCI1710_ReadChronoValue"| ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_Hour : Chronometer timing hour | +| unsigned char *_ pb_Minute : Chronometer timing minute | +| unsigned char *_ pb_Second : Chronometer timing second | +| unsigned int *_ pui_MilliSecond : Chronometer timing mini | +| second | +| unsigned int *_ pui_MicroSecond : Chronometer timing micro | +| second | +| unsigned int *_ pui_NanoSecond : Chronometer timing nano | +| second | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a Chronometer module | +| -4: Chronometer not initialised see function | +| "i_APCI1710_InitChrono" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_ConvertChronoValue(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned int ul_ChronoValue, + unsigned int *pul_Hour, + unsigned char *pb_Minute, + unsigned char *pb_Second, + unsigned int *pui_MilliSecond, unsigned int *pui_MicroSecond, unsigned int *pui_NanoSecond) +{ + int i_ReturnValue = 0; + double d_Hour; + double d_Minute; + double d_Second; + double d_MilliSecond; + double d_MicroSecond; + double d_NanoSecond; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if chronometer */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_CHRONOMETER) { + /***********************************/ + /* Test if chronometer initialised */ + /***********************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo.b_ChronoInit == 1) { + fpu_begin(); + + d_Hour = (double)ul_ChronoValue *(double) + devpriv->s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo.d_TimingInterval; + + switch (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo.b_TimingUnit) { + case 0: + d_Hour = d_Hour / (double)1000.0; + + case 1: + d_Hour = d_Hour / (double)1000.0; + + case 2: + d_Hour = d_Hour / (double)1000.0; + + case 3: + d_Hour = d_Hour / (double)60.0; + + case 4: + /**********************/ + /* Calculate the hour */ + /**********************/ + + d_Hour = d_Hour / (double)60.0; + *pul_Hour = (unsigned int) d_Hour; + + /************************/ + /* Calculate the minute */ + /************************/ + + d_Minute = d_Hour - *pul_Hour; + d_Minute = d_Minute * 60; + *pb_Minute = (unsigned char) d_Minute; + + /************************/ + /* Calculate the second */ + /************************/ + + d_Second = d_Minute - *pb_Minute; + d_Second = d_Second * 60; + *pb_Second = (unsigned char) d_Second; + + /*****************************/ + /* Calculate the mini second */ + /*****************************/ + + d_MilliSecond = d_Second - *pb_Second; + d_MilliSecond = d_MilliSecond * 1000; + *pui_MilliSecond = (unsigned int) d_MilliSecond; + + /******************************/ + /* Calculate the micro second */ + /******************************/ + + d_MicroSecond = + d_MilliSecond - + *pui_MilliSecond; + d_MicroSecond = d_MicroSecond * 1000; + *pui_MicroSecond = (unsigned int) d_MicroSecond; + + /******************************/ + /* Calculate the micro second */ + /******************************/ + + d_NanoSecond = + d_MicroSecond - + *pui_MicroSecond; + d_NanoSecond = d_NanoSecond * 1000; + *pui_NanoSecond = (unsigned int) d_NanoSecond; + break; + } + + fpu_end(); + } else { + /*******************************/ + /* Chronometer not initialised */ + /*******************************/ + DPRINTK("Chronometer not initialised\n"); + i_ReturnValue = -4; + } + } else { + /******************************************/ + /* The module is not a Chronometer module */ + /******************************************/ + DPRINTK("The module is not a Chronometer module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1710_InsnBitsChronoDigitalIO(struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Sets the output witch has been passed with the | +| parameter b_Channel. Setting an output means setting an| +| output high. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3)| +| unsigned char_ b_OutputChannel : Selection from digital output | +| CR_CHAN() channel (0 to 2) | +| 0 : Channel H | +| 1 : Channel A | +| 2 : Channel B | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a Chronometer module | +| -4: The selected digital output is wrong | +| -5: Chronometer not initialised see function | +| "i_APCI1710_InitChrono" | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_SetChronoChlOff | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_OutputChannel) | ++----------------------------------------------------------------------------+ +| Task : Resets the output witch has been passed with the | +| parameter b_Channel. Resetting an output means setting | +| an output low. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 + data[0] : Chl ON, Chl OFF , Chl Read , Port Read + +| unsigned char_ b_ModulNbr CR_AREF : Selected module number (0 to 3)| +| unsigned char_ b_OutputChannel CR_CHAN : Selection from digital output | +| channel (0 to 2) | +| 0 : Channel H | +| 1 : Channel A | +| 2 : Channel B | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a Chronometer module | +| -4: The selected digital output is wrong | +| -5: Chronometer not initialised see function | +| "i_APCI1710_InitChrono" | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadChronoChlValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_InputChannel, | +| unsigned char *_ pb_ChannelStatus) | ++----------------------------------------------------------------------------+ +| Task : Return the status from selected digital input | +| (b_InputChannel) from selected chronometer | +| module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3)| +| unsigned char_ b_InputChannel : Selection from digital input | +| channel (0 to 2) | +| CR_CHAN() 0 : Channel E | +| 1 : Channel F | +| 2 : Channel G | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_ChannelStatus : Digital input channel status.| +| data[0] 0 : Channel is not active | +| 1 : Channel is active | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a Chronometer module | +| -4: The selected digital input is wrong | +| -5: Chronometer not initialised see function | +| "i_APCI1710_InitChrono" | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadChronoPortValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char *_ pb_PortValue) | ++----------------------------------------------------------------------------+ +| Task : Return the status from digital inputs port from | +| selected (b_ModulNbr) chronometer module. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3)| ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_PortValue : Digital inputs port status. +| data[0] ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a Chronometer module | +| -4: Chronometer not initialised see function | +| "i_APCI1710_InitChrono" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnBitsChronoDigitalIO(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned char b_ModulNbr, b_OutputChannel, b_InputChannel, b_IOType; + unsigned int dw_Status; + unsigned char *pb_ChannelStatus; + unsigned char *pb_PortValue; + + b_ModulNbr = CR_AREF(insn->chanspec); + i_ReturnValue = insn->n; + b_IOType = (unsigned char) data[0]; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if chronometer */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_CHRONOMETER) { + /***********************************/ + /* Test if chronometer initialised */ + /***********************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_ChronoModuleInfo.b_ChronoInit == 1) { + /***********************************/ + /* Test the digital output channel */ + /***********************************/ + switch (b_IOType) { + + case APCI1710_CHRONO_SET_CHANNELOFF: + + b_OutputChannel = + (unsigned char) CR_CHAN(insn->chanspec); + if (b_OutputChannel <= 2) { + + outl(0, devpriv->s_BoardInfos. + ui_Address + 20 + + (b_OutputChannel * 4) + + (64 * b_ModulNbr)); + } /* if ((b_OutputChannel >= 0) && (b_OutputChannel <= 2)) */ + else { + /****************************************/ + /* The selected digital output is wrong */ + /****************************************/ + + DPRINTK("The selected digital output is wrong\n"); + i_ReturnValue = -4; + + } /* if ((b_OutputChannel >= 0) && (b_OutputChannel <= 2)) */ + + break; + + case APCI1710_CHRONO_SET_CHANNELON: + + b_OutputChannel = + (unsigned char) CR_CHAN(insn->chanspec); + if (b_OutputChannel <= 2) { + + outl(1, devpriv->s_BoardInfos. + ui_Address + 20 + + (b_OutputChannel * 4) + + (64 * b_ModulNbr)); + } /* if ((b_OutputChannel >= 0) && (b_OutputChannel <= 2)) */ + else { + /****************************************/ + /* The selected digital output is wrong */ + /****************************************/ + + DPRINTK("The selected digital output is wrong\n"); + i_ReturnValue = -4; + + } /* if ((b_OutputChannel >= 0) && (b_OutputChannel <= 2)) */ + + break; + + case APCI1710_CHRONO_READ_CHANNEL: + /**********************************/ + /* Test the digital input channel */ + /**********************************/ + pb_ChannelStatus = (unsigned char *) &data[0]; + b_InputChannel = + (unsigned char) CR_CHAN(insn->chanspec); + + if (b_InputChannel <= 2) { + + dw_Status = + inl(devpriv-> + s_BoardInfos. + ui_Address + 12 + + (64 * b_ModulNbr)); + + *pb_ChannelStatus = + (unsigned char) (((dw_Status >> + b_InputChannel) + & 1) ^ 1); + } /* if ((b_InputChannel >= 0) && (b_InputChannel <= 2)) */ + else { + /***************************************/ + /* The selected digital input is wrong */ + /***************************************/ + + DPRINTK("The selected digital input is wrong\n"); + i_ReturnValue = -4; + } /* if ((b_InputChannel >= 0) && (b_InputChannel <= 2)) */ + + break; + + case APCI1710_CHRONO_READ_PORT: + + pb_PortValue = (unsigned char *) &data[0]; + + dw_Status = + inl(devpriv->s_BoardInfos. + ui_Address + 12 + + (64 * b_ModulNbr)); + + *pb_PortValue = + (unsigned char) ((dw_Status & 0x7) ^ 7); + break; + } + } else { + /*******************************/ + /* Chronometer not initialised */ + /*******************************/ + + DPRINTK("Chronometer not initialised\n"); + i_ReturnValue = -5; + } + } else { + /******************************************/ + /* The module is not a Chronometer module */ + /******************************************/ + + DPRINTK("The module is not a Chronometer module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Chrono.h b/drivers/staging/comedi/drivers/addi-data/APCI1710_Chrono.h new file mode 100644 index 00000000..29bad1d1 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Chrono.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data-com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define APCI1710_30MHZ 30 +#define APCI1710_33MHZ 33 +#define APCI1710_40MHZ 40 + +#define APCI1710_SINGLE 0 +#define APCI1710_CONTINUOUS 1 + +#define APCI1710_CHRONO_PROGRESS_STATUS 0 +#define APCI1710_CHRONO_READVALUE 1 +#define APCI1710_CHRONO_CONVERTVALUE 2 +#define APCI1710_CHRONO_READINTERRUPT 3 + +#define APCI1710_CHRONO_SET_CHANNELON 0 +#define APCI1710_CHRONO_SET_CHANNELOFF 1 +#define APCI1710_CHRONO_READ_CHANNEL 2 +#define APCI1710_CHRONO_READ_PORT 3 + +/* + * CHRONOMETER INISIALISATION FUNCTION + */ +int i_APCI1710_InsnConfigInitChrono(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnWriteEnableDisableChrono(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +/* + * CHRONOMETER READ FUNCTION + */ +int i_APCI1710_InsnReadChrono(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_GetChronoProgressStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_ChronoStatus); + +int i_APCI1710_ReadChronoValue(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned int ui_TimeOut, unsigned char *pb_ChronoStatus, + unsigned int *pul_ChronoValue); + +int i_APCI1710_ConvertChronoValue(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned int ul_ChronoValue, + unsigned int *pul_Hour, + unsigned char *pb_Minute, + unsigned char *pb_Second, + unsigned int *pui_MilliSecond, unsigned int *pui_MicroSecond, + unsigned int *pui_NanoSecond); + +/* + * CHRONOMETER DIGITAL INPUT OUTPUT FUNCTION + */ +int i_APCI1710_InsnBitsChronoDigitalIO(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Dig_io.c b/drivers/staging/comedi/drivers/addi-data/APCI1710_Dig_io.c new file mode 100644 index 00000000..07108f9f --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Dig_io.c @@ -0,0 +1,1025 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : API APCI1710 | Compiler : gcc | + | Module name : DIG_IO.C | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description : APCI-1710 digital I/O module | + | | + | | + +-----------------------------------------------------------------------+ + | UPDATES | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | 16/06/98 | S. Weber | Digital input / output implementation | + |----------|-----------|------------------------------------------------| + | 08/05/00 | Guinot C | - 0400/0228 All Function in RING 0 | + | | | available | + +-----------------------------------------------------------------------+ + | | | | + | | | | + +-----------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ +#include "APCI1710_Dig_io.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1710_InsnConfigDigitalIO(struct comedi_device *dev, | +| struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data)| ++----------------------------------------------------------------------------+ +| Task : Configure the digital I/O operating mode from selected | +| module (b_ModulNbr). You must calling this function be| +| for you call any other function witch access of digital| +| I/O. | ++----------------------------------------------------------------------------+ +| Input Parameters : | +| unsigned char_ b_ModulNbr data[0]: Module number to | +| configure (0 to 3) | +| unsigned char_ b_ChannelAMode data[1] : Channel A mode selection | +| 0 : Channel used for digital | +| input | +| 1 : Channel used for digital | +| output | +| unsigned char_ b_ChannelBMode data[2] : Channel B mode selection | +| 0 : Channel used for digital | +| input | +| 1 : Channel used for digital | +| output | + data[0] memory on/off +Activates and deactivates the digital output memory. + After having | +| called up this function with memory on,the output you have previously| +| activated with the function are not reset ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a digital I/O module | +| -4: Bi-directional channel A configuration error | +| -5: Bi-directional channel B configuration error | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnConfigDigitalIO(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned char b_ModulNbr, b_ChannelAMode, b_ChannelBMode; + unsigned char b_MemoryOnOff, b_ConfigType; + int i_ReturnValue = 0; + unsigned int dw_WriteConfig = 0; + + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_ConfigType = (unsigned char) data[0]; /* Memory or Init */ + b_ChannelAMode = (unsigned char) data[1]; + b_ChannelBMode = (unsigned char) data[2]; + b_MemoryOnOff = (unsigned char) data[1]; /* if memory operation */ + i_ReturnValue = insn->n; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr >= 4) { + DPRINTK("Module Number invalid\n"); + i_ReturnValue = -2; + return i_ReturnValue; + } + switch (b_ConfigType) { + case APCI1710_DIGIO_MEMORYONOFF: + + if (b_MemoryOnOff) /* If Memory ON */ + { + /****************************/ + /* Set the output memory on */ + /****************************/ + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo.b_OutputMemoryEnabled = 1; + + /***************************/ + /* Clear the output memory */ + /***************************/ + devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo.dw_OutputMemory = 0; + } else /* If memory off */ + { + /*****************************/ + /* Set the output memory off */ + /*****************************/ + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo.b_OutputMemoryEnabled = 0; + } + break; + + case APCI1710_DIGIO_INIT: + + /*******************************/ + /* Test if digital I/O counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_DIGITAL_IO) { + + /***************************************************/ + /* Test the bi-directional channel A configuration */ + /***************************************************/ + + if ((b_ChannelAMode == 0) || (b_ChannelAMode == 1)) { + /***************************************************/ + /* Test the bi-directional channel B configuration */ + /***************************************************/ + + if ((b_ChannelBMode == 0) + || (b_ChannelBMode == 1)) { + devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo.b_DigitalInit = + 1; + + /********************************/ + /* Save channel A configuration */ + /********************************/ + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo. + b_ChannelAMode = b_ChannelAMode; + + /********************************/ + /* Save channel B configuration */ + /********************************/ + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo. + b_ChannelBMode = b_ChannelBMode; + + /*****************************************/ + /* Set the channel A and B configuration */ + /*****************************************/ + + dw_WriteConfig = + (unsigned int) (b_ChannelAMode | + (b_ChannelBMode * 2)); + + /***************************/ + /* Write the configuration */ + /***************************/ + + outl(dw_WriteConfig, + devpriv->s_BoardInfos. + ui_Address + 4 + + (64 * b_ModulNbr)); + + } else { + /************************************************/ + /* Bi-directional channel B configuration error */ + /************************************************/ + DPRINTK("Bi-directional channel B configuration error\n"); + i_ReturnValue = -5; + } + + } else { + /************************************************/ + /* Bi-directional channel A configuration error */ + /************************************************/ + DPRINTK("Bi-directional channel A configuration error\n"); + i_ReturnValue = -4; + + } + + } else { + /******************************************/ + /* The module is not a digital I/O module */ + /******************************************/ + DPRINTK("The module is not a digital I/O module\n"); + i_ReturnValue = -3; + } + } /* end of Switch */ + printk("Return Value %d\n", i_ReturnValue); + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| INPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ + +|INT i_APCI1710_InsnReadDigitalIOChlValue(struct comedi_device *dev,comedi_subdevice +*s, struct comedi_insn *insn,unsigned int *data) + ++----------------------------------------------------------------------------+ +| Task : Read the status from selected digital I/O digital input| +| (b_InputChannel) | ++----------------------------------------------------------------------------| + + +| +| unsigned char_ b_ModulNbr CR_AREF(chanspec) : Selected module number | +| (0 to 3) | +| unsigned char_ b_InputChannel CR_CHAN(chanspec) : Selection from digital | +| input ( 0 to 6) | +| 0 : Channel C | +| 1 : Channel D | +| 2 : Channel E | +| 3 : Channel F | +| 4 : Channel G | +| 5 : Channel A | +| 6 : Channel B + + + | ++----------------------------------------------------------------------------+ +| Output Parameters : data[0] : Digital input channel | +| status | +| 0 : Channle is not active| +| 1 : Channle is active | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a digital I/O module | +| -4: The selected digital I/O digital input is wrong | +| -5: Digital I/O not initialised | +| -6: The digital channel A is used for output | +| -7: The digital channel B is used for output | ++----------------------------------------------------------------------------+ +*/ + +/* _INT_ i_APCI1710_ReadDigitalIOChlValue (unsigned char_ b_BoardHandle, */ +/* +* unsigned char_ b_ModulNbr, unsigned char_ b_InputChannel, +* unsigned char *_ pb_ChannelStatus) +*/ +int i_APCI1710_InsnReadDigitalIOChlValue(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg; + unsigned char b_ModulNbr, b_InputChannel; + unsigned char *pb_ChannelStatus; + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_InputChannel = (unsigned char) CR_CHAN(insn->chanspec); + data[0] = 0; + pb_ChannelStatus = (unsigned char *) &data[0]; + i_ReturnValue = insn->n; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if digital I/O counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_DIGITAL_IO) { + /******************************************/ + /* Test the digital imnput channel number */ + /******************************************/ + + if (b_InputChannel <= 6) { + /**********************************************/ + /* Test if the digital I/O module initialised */ + /**********************************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo.b_DigitalInit == 1) { + /**********************************/ + /* Test if channel A or channel B */ + /**********************************/ + + if (b_InputChannel > 4) { + /*********************/ + /* Test if channel A */ + /*********************/ + + if (b_InputChannel == 5) { + /***************************/ + /* Test the channel A mode */ + /***************************/ + + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + b_ChannelAMode + != 0) { + /********************************************/ + /* The digital channel A is used for output */ + /********************************************/ + + i_ReturnValue = + -6; + } + } /* if (b_InputChannel == 5) */ + else { + /***************************/ + /* Test the channel B mode */ + /***************************/ + + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + b_ChannelBMode + != 0) { + /********************************************/ + /* The digital channel B is used for output */ + /********************************************/ + + i_ReturnValue = + -7; + } + } /* if (b_InputChannel == 5) */ + } /* if (b_InputChannel > 4) */ + + /***********************/ + /* Test if error occur */ + /***********************/ + + if (i_ReturnValue >= 0) { + /**************************/ + /* Read all digital input */ + /**************************/ + +/* +* INPDW (ps_APCI1710Variable-> s_Board [b_BoardHandle]. +* s_BoardInfos. ui_Address + (64 * b_ModulNbr), &dw_StatusReg); +*/ + + dw_StatusReg = + inl(devpriv-> + s_BoardInfos. + ui_Address + + (64 * b_ModulNbr)); + + *pb_ChannelStatus = + (unsigned char) ((dw_StatusReg ^ + 0x1C) >> + b_InputChannel) & 1; + + } /* if (i_ReturnValue == 0) */ + } else { + /*******************************/ + /* Digital I/O not initialised */ + /*******************************/ + DPRINTK("Digital I/O not initialised\n"); + i_ReturnValue = -5; + } + } else { + /********************************/ + /* Selected digital input error */ + /********************************/ + DPRINTK("Selected digital input error\n"); + i_ReturnValue = -4; + } + } else { + /******************************************/ + /* The module is not a digital I/O module */ + /******************************************/ + DPRINTK("The module is not a digital I/O module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| OUTPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1710_InsnWriteDigitalIOChlOnOff(comedi_device +|*dev,struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) + ++----------------------------------------------------------------------------+ +| Task : Sets or resets the output witch has been passed with the | +| parameter b_Channel. Setting an output means setting | +| an ouput high. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr (aref ) : Selected module number (0 to 3)| +| unsigned char_ b_OutputChannel (CR_CHAN) : Selection from digital output | +| channel (0 to 2) | +| 0 : Channel H | +| 1 : Channel A | +| 2 : Channel B | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a digital I/O module | +| -4: The selected digital output is wrong | +| -5: digital I/O not initialised see function | +| " i_APCI1710_InitDigitalIO" | +| -6: The digital channel A is used for input | +| -7: The digital channel B is used for input + -8: Digital Output Memory OFF. | +| Use previously the function | +| "i_APCI1710_SetDigitalIOMemoryOn". | ++----------------------------------------------------------------------------+ +*/ + +/* +* _INT_ i_APCI1710_SetDigitalIOChlOn (unsigned char_ b_BoardHandle, +* unsigned char_ b_ModulNbr, unsigned char_ b_OutputChannel) +*/ +int i_APCI1710_InsnWriteDigitalIOChlOnOff(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_WriteValue = 0; + unsigned char b_ModulNbr, b_OutputChannel; + i_ReturnValue = insn->n; + b_ModulNbr = CR_AREF(insn->chanspec); + b_OutputChannel = CR_CHAN(insn->chanspec); + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if digital I/O counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_DIGITAL_IO) { + /**********************************************/ + /* Test if the digital I/O module initialised */ + /**********************************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo.b_DigitalInit == 1) { + /******************************************/ + /* Test the digital output channel number */ + /******************************************/ + + switch (b_OutputChannel) { + /*************/ + /* Channel H */ + /*************/ + + case 0: + break; + + /*************/ + /* Channel A */ + /*************/ + + case 1: + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo. + b_ChannelAMode != 1) { + /*******************************************/ + /* The digital channel A is used for input */ + /*******************************************/ + + i_ReturnValue = -6; + } + break; + + /*************/ + /* Channel B */ + /*************/ + + case 2: + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo. + b_ChannelBMode != 1) { + /*******************************************/ + /* The digital channel B is used for input */ + /*******************************************/ + + i_ReturnValue = -7; + } + break; + + default: + /****************************************/ + /* The selected digital output is wrong */ + /****************************************/ + + i_ReturnValue = -4; + break; + } + + /***********************/ + /* Test if error occur */ + /***********************/ + + if (i_ReturnValue >= 0) { + + /*********************************/ + /* Test if set channel ON */ + /*********************************/ + if (data[0]) { + /*********************************/ + /* Test if output memory enabled */ + /*********************************/ + + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + b_OutputMemoryEnabled == + 1) { + dw_WriteValue = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + dw_OutputMemory + | (1 << + b_OutputChannel); + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + dw_OutputMemory + = dw_WriteValue; + } else { + dw_WriteValue = + 1 << + b_OutputChannel; + } + } /* set channel off */ + else { + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + b_OutputMemoryEnabled == + 1) { + dw_WriteValue = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + dw_OutputMemory + & (0xFFFFFFFFUL + - + (1 << b_OutputChannel)); + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + dw_OutputMemory + = dw_WriteValue; + } else { + /*****************************/ + /* Digital Output Memory OFF */ + /*****************************/ + /* +Use previously the function "i_APCI1710_SetDigitalIOMemoryOn" */ + i_ReturnValue = -8; + } + + } + /*******************/ + /* Write the value */ + /*******************/ + + /* OUTPDW (ps_APCI1710Variable-> + * s_Board [b_BoardHandle]. + * s_BoardInfos. ui_Address + (64 * b_ModulNbr), + * dw_WriteValue); + */ +*/ + outl(dw_WriteValue, + devpriv->s_BoardInfos. + ui_Address + (64 * b_ModulNbr)); + } + } else { + /*******************************/ + /* Digital I/O not initialised */ + /*******************************/ + + i_ReturnValue = -5; + } + } else { + /******************************************/ + /* The module is not a digital I/O module */ + /******************************************/ + + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ + +|INT i_APCI1710_InsnBitsDigitalIOPortOnOff(struct comedi_device *dev,comedi_subdevice + *s, struct comedi_insn *insn,unsigned int *data) ++----------------------------------------------------------------------------+ +| Task : write: + Sets or resets one or several outputs from port. | +| Setting an output means setting an output high. | +| If you have switched OFF the digital output memory | +| (OFF), all the other output are set to "0". + +| read: + Read the status from digital input port | +| from selected digital I/O module (b_ModulNbr) ++----------------------------------------------------------------------------+ +| Input Parameters : + unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr CR_AREF(aref) : Selected module number (0 to 3)| +| unsigned char_ b_PortValue CR_CHAN(chanspec) : Output Value ( 0 To 7 ) +| data[0] read or write port +| data[1] if write then indicate ON or OFF + +| if read : data[1] will return port status. ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : + +| INPUT : + + 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a digital I/O module | +| -4: Digital I/O not initialised + + OUTPUT: 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a digital I/O module | +| -4: Output value wrong | +| -5: digital I/O not initialised see function | +| " i_APCI1710_InitDigitalIO" | +| -6: The digital channel A is used for input | +| -7: The digital channel B is used for input + -8: Digital Output Memory OFF. | +| Use previously the function | +| "i_APCI1710_SetDigitalIOMemoryOn". | ++----------------------------------------------------------------------------+ +*/ + +/* + * _INT_ i_APCI1710_SetDigitalIOPortOn (unsigned char_ + * b_BoardHandle, unsigned char_ b_ModulNbr, unsigned char_ + * b_PortValue) +*/ +int i_APCI1710_InsnBitsDigitalIOPortOnOff(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_WriteValue = 0; + unsigned int dw_StatusReg; + unsigned char b_ModulNbr, b_PortValue; + unsigned char b_PortOperation, b_PortOnOFF; + + unsigned char *pb_PortValue; + + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_PortOperation = (unsigned char) data[0]; /* Input or output */ + b_PortOnOFF = (unsigned char) data[1]; /* if output then On or Off */ + b_PortValue = (unsigned char) data[2]; /* if out put then Value */ + i_ReturnValue = insn->n; + pb_PortValue = (unsigned char *) &data[0]; +/* if input then read value */ + + switch (b_PortOperation) { + case APCI1710_INPUT: + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if digital I/O counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_DIGITAL_IO) { + /**********************************************/ + /* Test if the digital I/O module initialised */ + /**********************************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo.b_DigitalInit == 1) { + /**************************/ + /* Read all digital input */ + /**************************/ + + /* INPDW (ps_APCI1710Variable-> + * s_Board [b_BoardHandle]. + * s_BoardInfos. + * ui_Address + (64 * b_ModulNbr), + * &dw_StatusReg); + */ + + dw_StatusReg = + inl(devpriv->s_BoardInfos. + ui_Address + (64 * b_ModulNbr)); + *pb_PortValue = + (unsigned char) (dw_StatusReg ^ 0x1C); + + } else { + /*******************************/ + /* Digital I/O not initialised */ + /*******************************/ + + i_ReturnValue = -4; + } + } else { + /******************************************/ + /* The module is not a digital I/O module */ + /******************************************/ + + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + i_ReturnValue = -2; + } + + break; + + case APCI1710_OUTPUT: + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if digital I/O counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_DIGITAL_IO) { + /**********************************************/ + /* Test if the digital I/O module initialised */ + /**********************************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_DigitalIOInfo.b_DigitalInit == 1) { + /***********************/ + /* Test the port value */ + /***********************/ + + if (b_PortValue <= 7) { + /***********************************/ + /* Test the digital output channel */ + /***********************************/ + + /**************************/ + /* Test if channel A used */ + /**************************/ + + if ((b_PortValue & 2) == 2) { + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + b_ChannelAMode + != 1) { + /*******************************************/ + /* The digital channel A is used for input */ + /*******************************************/ + + i_ReturnValue = + -6; + } + } /* if ((b_PortValue & 2) == 2) */ + + /**************************/ + /* Test if channel B used */ + /**************************/ + + if ((b_PortValue & 4) == 4) { + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + b_ChannelBMode + != 1) { + /*******************************************/ + /* The digital channel B is used for input */ + /*******************************************/ + + i_ReturnValue = + -7; + } + } /* if ((b_PortValue & 4) == 4) */ + + /***********************/ + /* Test if error occur */ + /***********************/ + + if (i_ReturnValue >= 0) { + + /* if(data[1]) { */ + + switch (b_PortOnOFF) { + /*********************************/ + /* Test if set Port ON */ + /*********************************/ + + case APCI1710_ON: + + /*********************************/ + /* Test if output memory enabled */ + /*********************************/ + + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + b_OutputMemoryEnabled + == 1) { + dw_WriteValue + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + dw_OutputMemory + | + b_PortValue; + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + dw_OutputMemory + = + dw_WriteValue; + } else { + dw_WriteValue + = + b_PortValue; + } + break; + + /* If Set PORT OFF */ + case APCI1710_OFF: + + /*********************************/ + /* Test if output memory enabled */ + /*********************************/ + + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + b_OutputMemoryEnabled + == 1) { + dw_WriteValue + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + dw_OutputMemory + & + (0xFFFFFFFFUL + - + b_PortValue); + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_DigitalIOInfo. + dw_OutputMemory + = + dw_WriteValue; + } else { + /*****************************/ + /* Digital Output Memory OFF */ + /*****************************/ + + i_ReturnValue + = + -8; + } + } /* switch */ + + /*******************/ + /* Write the value */ + /*******************/ + + /* OUTPDW (ps_APCI1710Variable-> + * s_Board [b_BoardHandle]. + * s_BoardInfos. + * ui_Address + (64 * b_ModulNbr), + * dw_WriteValue); */ + + outl(dw_WriteValue, + devpriv-> + s_BoardInfos. + ui_Address + + (64 * b_ModulNbr)); + } + } else { + /**********************/ + /* Output value wrong */ + /**********************/ + + i_ReturnValue = -4; + } + } else { + /*******************************/ + /* Digital I/O not initialised */ + /*******************************/ + + i_ReturnValue = -5; + } + } else { + /******************************************/ + /* The module is not a digital I/O module */ + /******************************************/ + + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + i_ReturnValue = -2; + } + break; + + default: + i_ReturnValue = -9; + DPRINTK("NO INPUT/OUTPUT specified\n"); + } /* switch INPUT / OUTPUT */ + return i_ReturnValue; +} diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Dig_io.h b/drivers/staging/comedi/drivers/addi-data/APCI1710_Dig_io.h new file mode 100644 index 00000000..cc3973d7 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Dig_io.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define APCI1710_ON 1 /* Digital Output ON or OFF */ +#define APCI1710_OFF 0 + +#define APCI1710_INPUT 0 /* Digital I/O */ +#define APCI1710_OUTPUT 1 + +#define APCI1710_DIGIO_MEMORYONOFF 0x10 +#define APCI1710_DIGIO_INIT 0x11 + +/* + * DIGITAL I/O INISIALISATION FUNCTION + */ +int i_APCI1710_InsnConfigDigitalIO(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * INPUT OUTPUT FUNCTIONS + */ +int i_APCI1710_InsnReadDigitalIOChlValue(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnWriteDigitalIOChlOnOff(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnBitsDigitalIOPortOnOff(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_INCCPT.c b/drivers/staging/comedi/drivers/addi-data/APCI1710_INCCPT.c new file mode 100644 index 00000000..14b13eae --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_INCCPT.c @@ -0,0 +1,5363 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : API APCI1710 | Compiler : gcc | + | Module name : INC_CPT.C | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description : APCI-1710 incremental counter module | + | | + | | + +-----------------------------------------------------------------------+ + | UPDATES | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + |----------|-----------|------------------------------------------------| + | 08/05/00 | Guinot C | - 0400/0228 All Function in RING 0 | + | | | available | + +-----------------------------------------------------------------------+ + | 29/06/01 | Guinot C. | - 1100/0231 -> 0701/0232 | + | | | See i_APCI1710_DisableFrequencyMeasurement | + +-----------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ + +#include "APCI1710_INCCPT.h" + +/* ++----------------------------------------------------------------------------+ +| int i_APCI1710_InsnConfigINCCPT(struct comedi_device *dev,struct comedi_subdevice *s, +struct comedi_insn *insn,unsigned int *data) + ++----------------------------------------------------------------------------+ +| Task : Configuration function for INC_CPT | ++----------------------------------------------------------------------------+ +| Input Parameters : | ++----------------------------------------------------------------------------+ +| Output Parameters : *data ++----------------------------------------------------------------------------+ +| Return Value : | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnConfigINCCPT(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_ConfigType; + int i_ReturnValue = 0; + ui_ConfigType = CR_CHAN(insn->chanspec); + + printk("\nINC_CPT"); + + devpriv->tsk_Current = current; /* Save the current process task structure */ + switch (ui_ConfigType) { + case APCI1710_INCCPT_INITCOUNTER: + i_ReturnValue = i_APCI1710_InitCounter(dev, + CR_AREF(insn->chanspec), + (unsigned char) data[0], + (unsigned char) data[1], + (unsigned char) data[2], (unsigned char) data[3], (unsigned char) data[4]); + break; + + case APCI1710_INCCPT_COUNTERAUTOTEST: + i_ReturnValue = i_APCI1710_CounterAutoTest(dev, + (unsigned char *) &data[0]); + break; + + case APCI1710_INCCPT_INITINDEX: + i_ReturnValue = i_APCI1710_InitIndex(dev, + CR_AREF(insn->chanspec), + (unsigned char) data[0], + (unsigned char) data[1], (unsigned char) data[2], (unsigned char) data[3]); + break; + + case APCI1710_INCCPT_INITREFERENCE: + i_ReturnValue = i_APCI1710_InitReference(dev, + CR_AREF(insn->chanspec), (unsigned char) data[0]); + break; + + case APCI1710_INCCPT_INITEXTERNALSTROBE: + i_ReturnValue = i_APCI1710_InitExternalStrobe(dev, + CR_AREF(insn->chanspec), + (unsigned char) data[0], (unsigned char) data[1]); + break; + + case APCI1710_INCCPT_INITCOMPARELOGIC: + i_ReturnValue = i_APCI1710_InitCompareLogic(dev, + CR_AREF(insn->chanspec), (unsigned int) data[0]); + break; + + case APCI1710_INCCPT_INITFREQUENCYMEASUREMENT: + i_ReturnValue = i_APCI1710_InitFrequencyMeasurement(dev, + CR_AREF(insn->chanspec), + (unsigned char) data[0], + (unsigned char) data[1], (unsigned int) data[2], (unsigned int *) &data[0]); + break; + + default: + printk("Insn Config : Config Parameter Wrong\n"); + + } + + if (i_ReturnValue >= 0) + i_ReturnValue = insn->n; + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitCounter | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_CounterRange, | +| unsigned char_ b_FirstCounterModus, | +| unsigned char_ b_FirstCounterOption, | +| unsigned char_ b_SecondCounterModus, | +| unsigned char_ b_SecondCounterOption) | ++----------------------------------------------------------------------------+ +| Task : Configure the counter operating mode from selected | +| module (b_ModulNbr). You must calling this function be | +| for you call any other function witch access of | +| counters. | +| | +| Counter range | +| ------------- | +| +------------------------------------+-----------------------------------+ | +| | Parameter Passed value | Description | | +| |------------------------------------+-----------------------------------| | +| |b_ModulNbr APCI1710_16BIT_COUNTER | The module is configured for | | +| | | two 16-bit counter. | | +| | | - b_FirstCounterModus and | | +| | | b_FirstCounterOption | | +| | | configure the first 16 bit | | +| | | counter. | | +| | | - b_SecondCounterModus and | | +| | | b_SecondCounterOption | | +| | | configure the second 16 bit | | +| | | counter. | | +| |------------------------------------+-----------------------------------| | +| |b_ModulNbr APCI1710_32BIT_COUNTER | The module is configured for one | | +| | | 32-bit counter. | | +| | | - b_FirstCounterModus and | | +| | | b_FirstCounterOption | | +| | | configure the 32 bit counter. | | +| | | - b_SecondCounterModus and | | +| | | b_SecondCounterOption | | +| | | are not used and have no | | +| | | importance. | | +| +------------------------------------+-----------------------------------+ | +| | +| Counter operating mode | +| ---------------------- | +| | +| +--------------------+-------------------------+-------------------------+ | +| | Parameter | Passed value | Description | | +| |--------------------+-------------------------+-------------------------| | +| |b_FirstCounterModus | APCI1710_QUADRUPLE_MODE | In the quadruple mode, | | +| | or | | the edge analysis | | +| |b_SecondCounterModus| | circuit generates a | | +| | | | counting pulse from | | +| | | | each edge of 2 signals | | +| | | | which are phase shifted | | +| | | | in relation to each | | +| | | | other. | | +| |--------------------+-------------------------+-------------------------| | +| |b_FirstCounterModus | APCI1710_DOUBLE_MODE | Functions in the same | | +| | or | | way as the quadruple | | +| |b_SecondCounterModus| | mode, except that only | | +| | | | two of the four edges | | +| | | | are analysed per | | +| | | | period | | +| |--------------------+-------------------------+-------------------------| | +| |b_FirstCounterModus | APCI1710_SIMPLE_MODE | Functions in the same | | +| | or | | way as the quadruple | | +| |b_SecondCounterModus| | mode, except that only | | +| | | | one of the four edges | | +| | | | is analysed per | | +| | | | period. | | +| |--------------------+-------------------------+-------------------------| | +| |b_FirstCounterModus | APCI1710_DIRECT_MODE | In the direct mode the | | +| | or | | both edge analysis | | +| |b_SecondCounterModus| | circuits are inactive. | | +| | | | The inputs A, B in the | | +| | | | 32-bit mode or A, B and | | +| | | | C, D in the 16-bit mode | | +| | | | represent, each, one | | +| | | | clock pulse gate circuit| | +| | | | There by frequency and | | +| | | | pulse duration | | +| | | | measurements can be | | +| | | | performed. | | +| +--------------------+-------------------------+-------------------------+ | +| | +| | +| IMPORTANT! | +| If you have configured the module for two 16-bit counter, a mixed | +| mode with a counter in quadruple/double/single mode | +| and the other counter in direct mode is not possible! | +| | +| | +| Counter operating option for quadruple/double/simple mode | +| --------------------------------------------------------- | +| | +| +----------------------+-------------------------+------------------------+| +| | Parameter | Passed value | Description || +| |----------------------+-------------------------+------------------------|| +| |b_FirstCounterOption | APCI1710_HYSTERESIS_ON | In both edge analysis || +| | or | | circuits is available || +| |b_SecondCounterOption | | one hysteresis circuit.|| +| | | | It suppresses each || +| | | | time the first counting|| +| | | | pulse after a change || +| | | | of rotation. || +| |----------------------+-------------------------+------------------------|| +| |b_FirstCounterOption | APCI1710_HYSTERESIS_OFF | The first counting || +| | or | | pulse is not suppress || +| |b_SecondCounterOption | | after a change of || +| | | | rotation. || +| +----------------------+-------------------------+------------------------+| +| | +| | +| IMPORTANT! | +| This option are only avaible if you have selected the direct mode. | +| | +| | +| Counter operating option for direct mode | +| ---------------------------------------- | +| | +| +----------------------+--------------------+----------------------------+ | +| | Parameter | Passed value | Description | | +| |----------------------+--------------------+----------------------------| | +| |b_FirstCounterOption | APCI1710_INCREMENT | The counter increment for | | +| | or | | each counting pulse | | +| |b_SecondCounterOption | | | | +| |----------------------+--------------------+----------------------------| | +| |b_FirstCounterOption | APCI1710_DECREMENT | The counter decrement for | | +| | or | | each counting pulse | | +| |b_SecondCounterOption | | | | +| +----------------------+--------------------+----------------------------+ | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 3) | +| unsigned char_ b_CounterRange : Selection form counter | +| range. | +| unsigned char_ b_FirstCounterModus : First counter operating | +| mode. | +| unsigned char_ b_FirstCounterOption : First counter option. | +| unsigned char_ b_SecondCounterModus : Second counter operating | +| mode. | +| unsigned char_ b_SecondCounterOption : Second counter option. | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module is not a counter module | +| -3: The selected counter range is wrong. | +| -4: The selected first counter operating mode is wrong. | +| -5: The selected first counter operating option is wrong| +| -6: The selected second counter operating mode is wrong.| +| -7: The selected second counter operating option is | +| wrong. | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InitCounter(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_CounterRange, + unsigned char b_FirstCounterModus, + unsigned char b_FirstCounterOption, + unsigned char b_SecondCounterModus, unsigned char b_SecondCounterOption) +{ + int i_ReturnValue = 0; + + /*******************************/ + /* Test if incremental counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER) { + /**************************/ + /* Test the counter range */ + /**************************/ + + if (b_CounterRange == APCI1710_16BIT_COUNTER + || b_CounterRange == APCI1710_32BIT_COUNTER) { + /********************************/ + /* Test the first counter modus */ + /********************************/ + + if (b_FirstCounterModus == APCI1710_QUADRUPLE_MODE || + b_FirstCounterModus == APCI1710_DOUBLE_MODE || + b_FirstCounterModus == APCI1710_SIMPLE_MODE || + b_FirstCounterModus == APCI1710_DIRECT_MODE) { + /*********************************/ + /* Test the first counter option */ + /*********************************/ + + if ((b_FirstCounterModus == APCI1710_DIRECT_MODE + && (b_FirstCounterOption == + APCI1710_INCREMENT + || b_FirstCounterOption + == APCI1710_DECREMENT)) + || (b_FirstCounterModus != + APCI1710_DIRECT_MODE + && (b_FirstCounterOption == + APCI1710_HYSTERESIS_ON + || b_FirstCounterOption + == + APCI1710_HYSTERESIS_OFF))) + { + /**************************/ + /* Test if 16-bit counter */ + /**************************/ + + if (b_CounterRange == + APCI1710_16BIT_COUNTER) { + /*********************************/ + /* Test the second counter modus */ + /*********************************/ + + if ((b_FirstCounterModus != + APCI1710_DIRECT_MODE + && + (b_SecondCounterModus + == + APCI1710_QUADRUPLE_MODE + || + b_SecondCounterModus + == + APCI1710_DOUBLE_MODE + || + b_SecondCounterModus + == + APCI1710_SIMPLE_MODE)) + || (b_FirstCounterModus + == + APCI1710_DIRECT_MODE + && + b_SecondCounterModus + == + APCI1710_DIRECT_MODE)) + { + /**********************************/ + /* Test the second counter option */ + /**********************************/ + + if ((b_SecondCounterModus == APCI1710_DIRECT_MODE && (b_SecondCounterOption == APCI1710_INCREMENT || b_SecondCounterOption == APCI1710_DECREMENT)) || (b_SecondCounterModus != APCI1710_DIRECT_MODE && (b_SecondCounterOption == APCI1710_HYSTERESIS_ON || b_SecondCounterOption == APCI1710_HYSTERESIS_OFF))) { + i_ReturnValue = + 0; + } else { + /*********************************************************/ + /* The selected second counter operating option is wrong */ + /*********************************************************/ + + DPRINTK("The selected second counter operating option is wrong\n"); + i_ReturnValue = + -7; + } + } else { + /*******************************************************/ + /* The selected second counter operating mode is wrong */ + /*******************************************************/ + + DPRINTK("The selected second counter operating mode is wrong\n"); + i_ReturnValue = -6; + } + } + } else { + /********************************************************/ + /* The selected first counter operating option is wrong */ + /********************************************************/ + + DPRINTK("The selected first counter operating option is wrong\n"); + i_ReturnValue = -5; + } + } else { + /******************************************************/ + /* The selected first counter operating mode is wrong */ + /******************************************************/ + DPRINTK("The selected first counter operating mode is wrong\n"); + i_ReturnValue = -4; + } + } else { + /***************************************/ + /* The selected counter range is wrong */ + /***************************************/ + + DPRINTK("The selected counter range is wrong\n"); + i_ReturnValue = -3; + } + + /*************************/ + /* Test if a error occur */ + /*************************/ + + if (i_ReturnValue == 0) { + /**************************/ + /* Test if 16-Bit counter */ + /**************************/ + + if (b_CounterRange == APCI1710_32BIT_COUNTER) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister1 = b_CounterRange | + b_FirstCounterModus | + b_FirstCounterOption; + } else { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister1 = b_CounterRange | + (b_FirstCounterModus & 0x5) | + (b_FirstCounterOption & 0x20) | + (b_SecondCounterModus & 0xA) | + (b_SecondCounterOption & 0x40); + + /***********************/ + /* Test if direct mode */ + /***********************/ + + if (b_FirstCounterModus == APCI1710_DIRECT_MODE) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister1 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister1 | + APCI1710_DIRECT_MODE; + } + } + + /***************************/ + /* Write the configuration */ + /***************************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv->s_BoardInfos. + ui_Address + 20 + (64 * b_ModulNbr)); + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_CounterInit = 1; + } + } else { + /**************************************/ + /* The module is not a counter module */ + /**************************************/ + + DPRINTK("The module is not a counter module\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_CounterAutoTest | +| (unsigned char_ b_BoardHandle, | +| unsigned char *_ pb_TestStatus) | ++----------------------------------------------------------------------------+ +| Task : A test mode is intended for testing the component and | +| the connected periphery. All the 8-bit counter chains | +| are operated internally as down counters. | +| Independently from the external signals, | +| all the four 8-bit counter chains are decremented in | +| parallel by each negative clock pulse edge of CLKX. | +| | +| Counter auto test conclusion | +| ---------------------------- | +| +-----------------+-----------------------------+ | +| | pb_TestStatus | Error description | | +| | mask | | | +| |-----------------+-----------------------------| | +| | 0000 | No error detected | | +| |-----------------|-----------------------------| | +| | 0001 | Error detected of counter 0 | | +| |-----------------|-----------------------------| | +| | 0010 | Error detected of counter 1 | | +| |-----------------|-----------------------------| | +| | 0100 | Error detected of counter 2 | | +| |-----------------|-----------------------------| | +| | 1000 | Error detected of counter 3 | | +| +-----------------+-----------------------------+ | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_TestStatus : Auto test conclusion. See table| ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_CounterAutoTest(struct comedi_device *dev, unsigned char *pb_TestStatus) +{ + unsigned char b_ModulCpt = 0; + int i_ReturnValue = 0; + unsigned int dw_LathchValue; + + *pb_TestStatus = 0; + + /********************************/ + /* Test if counter module found */ + /********************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[0] & 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER + || (devpriv->s_BoardInfos. + dw_MolduleConfiguration[1] & 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER + || (devpriv->s_BoardInfos. + dw_MolduleConfiguration[2] & 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER + || (devpriv->s_BoardInfos. + dw_MolduleConfiguration[3] & 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER) { + for (b_ModulCpt = 0; b_ModulCpt < 4; b_ModulCpt++) { + /*******************************/ + /* Test if incremental counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulCpt] & + 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER) { + /******************/ + /* Start the test */ + /******************/ + + outl(3, devpriv->s_BoardInfos. + ui_Address + 16 + (64 * b_ModulCpt)); + + /*********************/ + /* Tatch the counter */ + /*********************/ + + outl(1, devpriv->s_BoardInfos. + ui_Address + (64 * b_ModulCpt)); + + /************************/ + /* Read the latch value */ + /************************/ + + dw_LathchValue = inl(devpriv->s_BoardInfos. + ui_Address + 4 + (64 * b_ModulCpt)); + + if ((dw_LathchValue & 0xFF) != + ((dw_LathchValue >> 8) & 0xFF) + && (dw_LathchValue & 0xFF) != + ((dw_LathchValue >> 16) & 0xFF) + && (dw_LathchValue & 0xFF) != + ((dw_LathchValue >> 24) & 0xFF)) { + *pb_TestStatus = + *pb_TestStatus | (1 << + b_ModulCpt); + } + + /*****************/ + /* Stop the test */ + /*****************/ + + outl(0, devpriv->s_BoardInfos. + ui_Address + 16 + (64 * b_ModulCpt)); + } + } + } else { + /***************************/ + /* No counter module found */ + /***************************/ + + DPRINTK("No counter module found\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitIndex (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_ReferenceAction, | +| unsigned char_ b_IndexOperation, | +| unsigned char_ b_AutoMode, | +| unsigned char_ b_InterruptEnable) | ++----------------------------------------------------------------------------+ +| Task : Initialise the index corresponding to the selected | +| module (b_ModulNbr). If a INDEX flag occur, you have | +| the possibility to clear the 32-Bit counter or to latch| +| the current 32-Bit value in to the first latch | +| register. The b_IndexOperation parameter give the | +| possibility to choice the INDEX action. | +| If you have enabled the automatic mode, each INDEX | +| action is cleared automatically, else you must read | +| the index status ("i_APCI1710_ReadIndexStatus") | +| after each INDEX action. | +| | +| | +| Index action | +| ------------ | +| | +| +------------------------+------------------------------------+ | +| | b_IndexOperation | Operation | | +| |------------------------+------------------------------------| | +| |APCI1710_LATCH_COUNTER | After a index signal, the counter | | +| | | value (32-Bit) is latched in to | | +| | | the first latch register | | +| |------------------------|------------------------------------| | +| |APCI1710_CLEAR_COUNTER | After a index signal, the counter | | +| | | value is cleared (32-Bit) | | +| +------------------------+------------------------------------+ | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| unsigned char_ b_ReferenceAction : Determine if the reference | +| must set or no for the | +| acceptance from index | +| APCI1710_ENABLE : | +| Reference must be set for | +| accepted the index | +| APCI1710_DISABLE : | +| Reference have not | +| importance | +| unsigned char_ b_IndexOperation : Index operating mode. | +| See table. | +| unsigned char_ b_AutoMode : Enable or disable the | +| automatic index reset. | +| APCI1710_ENABLE : | +| Enable the automatic mode | +| APCI1710_DISABLE : | +| Disable the automatic mode | +| unsigned char_ b_InterruptEnable : Enable or disable the | +| interrupt. | +| APCI1710_ENABLE : | +| Enable the interrupt | +| APCI1710_DISABLE : | +| Disable the interrupt | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4 The reference action parameter is wrong | +| -5: The index operating mode parameter is wrong | +| -6: The auto mode parameter is wrong | +| -7: Interrupt parameter is wrong | +| -8: Interrupt function not initialised. | +| See function "i_APCI1710_SetBoardIntRoutineX" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InitIndex(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_ReferenceAction, + unsigned char b_IndexOperation, unsigned char b_AutoMode, unsigned char b_InterruptEnable) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /********************************/ + /* Test the reference parameter */ + /********************************/ + + if (b_ReferenceAction == APCI1710_ENABLE || + b_ReferenceAction == APCI1710_DISABLE) { + /****************************/ + /* Test the index parameter */ + /****************************/ + + if (b_IndexOperation == + APCI1710_HIGH_EDGE_LATCH_COUNTER + || b_IndexOperation == + APCI1710_LOW_EDGE_LATCH_COUNTER + || b_IndexOperation == + APCI1710_HIGH_EDGE_CLEAR_COUNTER + || b_IndexOperation == + APCI1710_LOW_EDGE_CLEAR_COUNTER + || b_IndexOperation == + APCI1710_HIGH_EDGE_LATCH_AND_CLEAR_COUNTER + || b_IndexOperation == + APCI1710_LOW_EDGE_LATCH_AND_CLEAR_COUNTER) + { + /********************************/ + /* Test the auto mode parameter */ + /********************************/ + + if (b_AutoMode == APCI1710_ENABLE || + b_AutoMode == APCI1710_DISABLE) + { + /***************************/ + /* Test the interrupt mode */ + /***************************/ + + if (b_InterruptEnable == + APCI1710_ENABLE + || b_InterruptEnable == + APCI1710_DISABLE) { + + /************************************/ + /* Makte the configuration commando */ + /************************************/ + + if (b_ReferenceAction == + APCI1710_ENABLE) + { + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + | + APCI1710_ENABLE_INDEX_ACTION; + } else { + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + & + APCI1710_DISABLE_INDEX_ACTION; + } + + /****************************************/ + /* Test if low level latch or/and clear */ + /****************************************/ + + if (b_IndexOperation == + APCI1710_LOW_EDGE_LATCH_COUNTER + || + b_IndexOperation + == + APCI1710_LOW_EDGE_CLEAR_COUNTER + || + b_IndexOperation + == + APCI1710_LOW_EDGE_LATCH_AND_CLEAR_COUNTER) + { + /*************************************/ + /* Set the index level to low (DQ26) */ + /*************************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + | + APCI1710_SET_LOW_INDEX_LEVEL; + } else { + /**************************************/ + /* Set the index level to high (DQ26) */ + /**************************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + & + APCI1710_SET_HIGH_INDEX_LEVEL; + } + + /***********************************/ + /* Test if latch and clear counter */ + /***********************************/ + + if (b_IndexOperation == + APCI1710_HIGH_EDGE_LATCH_AND_CLEAR_COUNTER + || + b_IndexOperation + == + APCI1710_LOW_EDGE_LATCH_AND_CLEAR_COUNTER) + { + /***************************************/ + /* Set the latch and clear flag (DQ27) */ + /***************************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + | + APCI1710_ENABLE_LATCH_AND_CLEAR; + } /* if (b_IndexOperation == APCI1710_HIGH_EDGE_LATCH_AND_CLEAR_COUNTER || b_IndexOperation == APCI1710_LOW_EDGE_LATCH_AND_CLEAR_COUNTER) */ + else { + /*****************************************/ + /* Clear the latch and clear flag (DQ27) */ + /*****************************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + & + APCI1710_DISABLE_LATCH_AND_CLEAR; + + /*************************/ + /* Test if latch counter */ + /*************************/ + + if (b_IndexOperation == APCI1710_HIGH_EDGE_LATCH_COUNTER || b_IndexOperation == APCI1710_LOW_EDGE_LATCH_COUNTER) { + /*********************************/ + /* Enable the latch from counter */ + /*********************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + | + APCI1710_INDEX_LATCH_COUNTER; + } else { + /*********************************/ + /* Enable the clear from counter */ + /*********************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + & + (~APCI1710_INDEX_LATCH_COUNTER); + } + } /* // if (b_IndexOperation == APCI1710_HIGH_EDGE_LATCH_AND_CLEAR_COUNTER || b_IndexOperation == APCI1710_LOW_EDGE_LATCH_AND_CLEAR_COUNTER) */ + + if (b_AutoMode == + APCI1710_DISABLE) + { + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + | + APCI1710_INDEX_AUTO_MODE; + } else { + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 + & + (~APCI1710_INDEX_AUTO_MODE); + } + + if (b_InterruptEnable == + APCI1710_ENABLE) + { + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 + | + APCI1710_ENABLE_INDEX_INT; + } else { + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 + & + APCI1710_DISABLE_INDEX_INT; + } + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag. + b_IndexInit = 1; + + } else { + /********************************/ + /* Interrupt parameter is wrong */ + /********************************/ + DPRINTK("Interrupt parameter is wrong\n"); + i_ReturnValue = -7; + } + } else { + /************************************/ + /* The auto mode parameter is wrong */ + /************************************/ + + DPRINTK("The auto mode parameter is wrong\n"); + i_ReturnValue = -6; + } + } else { + /***********************************************/ + /* The index operating mode parameter is wrong */ + /***********************************************/ + + DPRINTK("The index operating mode parameter is wrong\n"); + i_ReturnValue = -5; + } + } else { + /*******************************************/ + /* The reference action parameter is wrong */ + /*******************************************/ + + DPRINTK("The reference action parameter is wrong\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitReference | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_ReferenceLevel) | ++----------------------------------------------------------------------------+ +| Task : Initialise the reference corresponding to the selected | +| module (b_ModulNbr). | +| | +| Reference level | +| --------------- | +| +--------------------+-------------------------+ | +| | b_ReferenceLevel | Operation | | +| +--------------------+-------------------------+ | +| | APCI1710_LOW | Reference occur if "0" | | +| |--------------------|-------------------------| | +| | APCI1710_HIGH | Reference occur if "1" | | +| +--------------------+-------------------------+ | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| unsigned char_ b_ReferenceLevel : Reference level. | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The selected module number parameter is wrong | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Reference level parameter is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InitReference(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_ReferenceLevel) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /**************************************/ + /* Test the reference level parameter */ + /**************************************/ + + if (b_ReferenceLevel == 0 || b_ReferenceLevel == 1) { + if (b_ReferenceLevel == 1) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 | + APCI1710_REFERENCE_HIGH; + } else { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 & + APCI1710_REFERENCE_LOW; + } + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv->s_BoardInfos.ui_Address + 20 + + (64 * b_ModulNbr)); + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_ReferenceInit = 1; + } else { + /**************************************/ + /* Reference level parameter is wrong */ + /**************************************/ + + DPRINTK("Reference level parameter is wrong\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitExternalStrobe | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_ExternalStrobe, | +| unsigned char_ b_ExternalStrobeLevel) | ++----------------------------------------------------------------------------+ +| Task : Initialises the external strobe level corresponding to | +| the selected module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| unsigned char_ b_ExternalStrobe : External strobe selection | +| 0 : External strobe A | +| 1 : External strobe B | +| unsigned char_ b_ExternalStrobeLevel : External strobe level | +| APCI1710_LOW : | +| External latch occurs if "0" | +| APCI1710_HIGH : | +| External latch occurs if "1" | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The selected module number is wrong | +| -3: Counter not initialised. | +| See function "i_APCI1710_InitCounter" | +| -4: External strobe selection is wrong | +| -5: External strobe level parameter is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InitExternalStrobe(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_ExternalStrobe, unsigned char b_ExternalStrobeLevel) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /**************************************/ + /* Test the external strobe selection */ + /**************************************/ + + if (b_ExternalStrobe == 0 || b_ExternalStrobe == 1) { + /******************/ + /* Test the level */ + /******************/ + + if ((b_ExternalStrobeLevel == APCI1710_HIGH) || + ((b_ExternalStrobeLevel == APCI1710_LOW + && (devpriv-> + s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & + 0xFFFF) >= + 0x3135))) { + /*****************/ + /* Set the level */ + /*****************/ + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 = (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 & (0xFF - + (0x10 << b_ExternalStrobe))) | ((b_ExternalStrobeLevel ^ 1) << (4 + b_ExternalStrobe)); + } else { + /********************************************/ + /* External strobe level parameter is wrong */ + /********************************************/ + + DPRINTK("External strobe level parameter is wrong\n"); + i_ReturnValue = -5; + } + } /* if (b_ExternalStrobe == 0 || b_ExternalStrobe == 1) */ + else { + /**************************************/ + /* External strobe selection is wrong */ + /**************************************/ + + DPRINTK("External strobe selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_ExternalStrobe == 0 || b_ExternalStrobe == 1) */ + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + + /* + +----------------------------------------------------------------------------+ + | Function Name : _INT_ i_APCI1710_InitCompareLogic | + | (unsigned char_ b_BoardHandle, | + | unsigned char_ b_ModulNbr, | + | unsigned int_ ui_CompareValue) | + +----------------------------------------------------------------------------+ + | Task : Set the 32-Bit compare value. At that moment that the | + | incremental counter arrive to the compare value | + | (ui_CompareValue) a interrupt is generated. | + +----------------------------------------------------------------------------+ + | Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | + | unsigned char_ b_ModulNbr : Module number to configure | + | (0 to 3) | + | unsigned int_ ui_CompareValue : 32-Bit compare value | + +----------------------------------------------------------------------------+ + | Output Parameters : - + +----------------------------------------------------------------------------+ + | Return Value : 0: No error | + | -1: The handle parameter of the board is wrong | + | -2: No counter module found | + | -3: Counter not initialised see function | + | "i_APCI1710_InitCounter" | + +----------------------------------------------------------------------------+ + */ + +int i_APCI1710_InitCompareLogic(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned int ui_CompareValue) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + + outl(ui_CompareValue, devpriv->s_BoardInfos. + ui_Address + 28 + (64 * b_ModulNbr)); + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_CompareLogicInit = 1; + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitFrequencyMeasurement | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PCIInputClock, | +| unsigned char_ b_TimingUnity, | +| ULONG_ ul_TimingInterval, | +| PULONG_ pul_RealTimingInterval) | ++----------------------------------------------------------------------------+ +| Task : Sets the time for the frequency measurement. | +| Configures the selected TOR incremental counter of the | +| selected module (b_ModulNbr). The ul_TimingInterval and| +| ul_TimingUnity determine the time base for the | +| measurement. The pul_RealTimingInterval returns the | +| real time value. You must call up this function before | +| you call up any other function which gives access to | +| the frequency measurement. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Number of the module to be | +| configured (0 to 3) | +| unsigned char_ b_PCIInputClock : Selection of the PCI bus | +| clock | +| - APCI1710_30MHZ : | +| The PC has a PCI bus clock | +| of 30 MHz | +| - APCI1710_33MHZ : | +| The PC has a PCI bus clock | +| of 33 MHz | +| unsigned char_ b_TimingUnity : Base time unit (0 to 2) | +| 0 : ns | +| 1 : æs | +| 2 : ms | +| ULONG_ ul_TimingInterval: Base time value. | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_RealTimingInterval : Real base time value. | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The selected module number is wrong | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: The selected PCI input clock is wrong | +| -5: Timing unity selection is wrong | +| -6: Base timing selection is wrong | +| -7: 40MHz quartz not on board | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InitFrequencyMeasurement(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PCIInputClock, + unsigned char b_TimingUnity, + unsigned int ul_TimingInterval, unsigned int *pul_RealTimingInterval) +{ + int i_ReturnValue = 0; + unsigned int ul_TimerValue = 0; + double d_RealTimingInterval; + unsigned int dw_Status = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /**************************/ + /* Test the PCI bus clock */ + /**************************/ + + if ((b_PCIInputClock == APCI1710_30MHZ) || + (b_PCIInputClock == APCI1710_33MHZ) || + (b_PCIInputClock == APCI1710_40MHZ)) { + /************************/ + /* Test the timing unit */ + /************************/ + + if (b_TimingUnity <= 2) { + /**********************************/ + /* Test the base timing selection */ + /**********************************/ + + if (((b_PCIInputClock == APCI1710_30MHZ) + && (b_TimingUnity == 0) + && (ul_TimingInterval >= + 266) + && (ul_TimingInterval <= + 8738133UL)) + || ((b_PCIInputClock == + APCI1710_30MHZ) + && (b_TimingUnity == 1) + && (ul_TimingInterval >= + 1) + && (ul_TimingInterval <= + 8738UL)) + || ((b_PCIInputClock == + APCI1710_30MHZ) + && (b_TimingUnity == 2) + && (ul_TimingInterval >= + 1) + && (ul_TimingInterval <= + 8UL)) + || ((b_PCIInputClock == + APCI1710_33MHZ) + && (b_TimingUnity == 0) + && (ul_TimingInterval >= + 242) + && (ul_TimingInterval <= + 7943757UL)) + || ((b_PCIInputClock == + APCI1710_33MHZ) + && (b_TimingUnity == 1) + && (ul_TimingInterval >= + 1) + && (ul_TimingInterval <= + 7943UL)) + || ((b_PCIInputClock == + APCI1710_33MHZ) + && (b_TimingUnity == 2) + && (ul_TimingInterval >= + 1) + && (ul_TimingInterval <= + 7UL)) + || ((b_PCIInputClock == + APCI1710_40MHZ) + && (b_TimingUnity == 0) + && (ul_TimingInterval >= + 200) + && (ul_TimingInterval <= + 6553500UL)) + || ((b_PCIInputClock == + APCI1710_40MHZ) + && (b_TimingUnity == 1) + && (ul_TimingInterval >= + 1) + && (ul_TimingInterval <= + 6553UL)) + || ((b_PCIInputClock == + APCI1710_40MHZ) + && (b_TimingUnity == 2) + && (ul_TimingInterval >= + 1) + && (ul_TimingInterval <= + 6UL))) { + /**********************/ + /* Test if 40MHz used */ + /**********************/ + + if (b_PCIInputClock == + APCI1710_40MHZ) { + /******************************/ + /* Test if firmware >= Rev1.5 */ + /******************************/ + + if ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF) >= 0x3135) { + /*********************************/ + /* Test if 40MHz quartz on board */ + /*********************************/ + + /*INPDW (ps_APCI1710Variable-> + s_Board [b_BoardHandle]. + s_BoardInfos. + ui_Address + 36 + (64 * b_ModulNbr), &dw_Status); */ + dw_Status = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + 36 + + (64 * b_ModulNbr)); + + /******************************/ + /* Test the quartz flag (DQ0) */ + /******************************/ + + if ((dw_Status & 1) != 1) { + /*****************************/ + /* 40MHz quartz not on board */ + /*****************************/ + + DPRINTK("40MHz quartz not on board\n"); + i_ReturnValue + = + -7; + } + } else { + /*****************************/ + /* 40MHz quartz not on board */ + /*****************************/ + DPRINTK("40MHz quartz not on board\n"); + i_ReturnValue = + -7; + } + } /* if (b_PCIInputClock == APCI1710_40MHZ) */ + + /***************************/ + /* Test if not error occur */ + /***************************/ + + if (i_ReturnValue == 0) { + /****************************/ + /* Test the INC_CPT version */ + /****************************/ + + if ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF) >= 0x3131) { + + /**********************/ + /* Test if 40MHz used */ + /**********************/ + + if (b_PCIInputClock == APCI1710_40MHZ) { + /*********************************/ + /* Enable the 40MHz quarz (DQ30) */ + /*********************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + | + APCI1710_ENABLE_40MHZ_FREQUENCY; + } /* if (b_PCIInputClock == APCI1710_40MHZ) */ + else { + /**********************************/ + /* Disable the 40MHz quarz (DQ30) */ + /**********************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + & + APCI1710_DISABLE_40MHZ_FREQUENCY; + + } /* if (b_PCIInputClock == APCI1710_40MHZ) */ + + /********************************/ + /* Calculate the division fator */ + /********************************/ + + fpu_begin(); + switch (b_TimingUnity) { + /******/ + /* ns */ + /******/ + + case 0: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + (ul_TimingInterval + * + (0.00025 * b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (0.00025 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (0.00025 * (double)b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + (0.00025 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (0.00025 * (double)b_PCIInputClock)) >= (double)((double)*pul_RealTimingInterval + 0.5)) { + *pul_RealTimingInterval + = + *pul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + + break; + + /******/ + /* æs */ + /******/ + + case 1: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + (ul_TimingInterval + * + (0.25 * b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (0.25 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (0.25 * (double)b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + ( + (double) + 0.25 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (0.25 * (double)b_PCIInputClock)) >= (double)((double)*pul_RealTimingInterval + 0.5)) { + *pul_RealTimingInterval + = + *pul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + + break; + + /******/ + /* ms */ + /******/ + + case 2: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + ul_TimingInterval + * + (250.0 + * + b_PCIInputClock); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (250.0 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (250.0 * (double)b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + (250.0 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (250.0 * (double)b_PCIInputClock)) >= (double)((double)*pul_RealTimingInterval + 0.5)) { + *pul_RealTimingInterval + = + *pul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + + break; + } + + fpu_end(); + /*************************/ + /* Write the timer value */ + /*************************/ + + outl(ul_TimerValue, devpriv->s_BoardInfos.ui_Address + 32 + (64 * b_ModulNbr)); + + /*******************************/ + /* Set the initialisation flag */ + /*******************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag. + b_FrequencyMeasurementInit + = 1; + } else { + /***************************/ + /* Counter not initialised */ + /***************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = + -3; + } + } /* if (i_ReturnValue == 0) */ + } else { + /**********************************/ + /* Base timing selection is wrong */ + /**********************************/ + + DPRINTK("Base timing selection is wrong\n"); + i_ReturnValue = -6; + } + } else { + /***********************************/ + /* Timing unity selection is wrong */ + /***********************************/ + + DPRINTK("Timing unity selection is wrong\n"); + i_ReturnValue = -5; + } + } else { + /*****************************************/ + /* The selected PCI input clock is wrong */ + /*****************************************/ + + DPRINTK("The selected PCI input clock is wrong\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/*########################################################################### */ + + /* INSN BITS */ +/*########################################################################### */ + +/* ++----------------------------------------------------------------------------+ +| Function Name :INT i_APCI1710_InsnBitsINCCPT(struct comedi_device *dev,struct comedi_subdevice *s, +struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Set & Clear Functions for INC_CPT | ++----------------------------------------------------------------------------+ +| Input Parameters : ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnBitsINCCPT(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_BitsType; + int i_ReturnValue = 0; + ui_BitsType = CR_CHAN(insn->chanspec); + devpriv->tsk_Current = current; /* Save the current process task structure */ + + switch (ui_BitsType) { + case APCI1710_INCCPT_CLEARCOUNTERVALUE: + i_ReturnValue = i_APCI1710_ClearCounterValue(dev, + (unsigned char) CR_AREF(insn->chanspec)); + break; + + case APCI1710_INCCPT_CLEARALLCOUNTERVALUE: + i_ReturnValue = i_APCI1710_ClearAllCounterValue(dev); + break; + + case APCI1710_INCCPT_SETINPUTFILTER: + i_ReturnValue = i_APCI1710_SetInputFilter(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned char) data[0], (unsigned char) data[1]); + break; + + case APCI1710_INCCPT_LATCHCOUNTER: + i_ReturnValue = i_APCI1710_LatchCounter(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char) data[0]); + break; + + case APCI1710_INCCPT_SETINDEXANDREFERENCESOURCE: + i_ReturnValue = i_APCI1710_SetIndexAndReferenceSource(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char) data[0]); + break; + + case APCI1710_INCCPT_SETDIGITALCHLON: + i_ReturnValue = i_APCI1710_SetDigitalChlOn(dev, + (unsigned char) CR_AREF(insn->chanspec)); + break; + + case APCI1710_INCCPT_SETDIGITALCHLOFF: + i_ReturnValue = i_APCI1710_SetDigitalChlOff(dev, + (unsigned char) CR_AREF(insn->chanspec)); + break; + + default: + printk("Bits Config Parameter Wrong\n"); + } + + if (i_ReturnValue >= 0) + i_ReturnValue = insn->n; + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ClearCounterValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Task : Clear the counter value from selected module | +| (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The selected module number parameter is wrong | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_ClearCounterValue(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*********************/ + /* Clear the counter */ + /*********************/ + + outl(1, devpriv->s_BoardInfos. + ui_Address + 16 + (64 * b_ModulNbr)); + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ClearAllCounterValue | +| (unsigned char_ b_BoardHandle) | ++----------------------------------------------------------------------------+ +| Task : Clear all counter value. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_ClearAllCounterValue(struct comedi_device *dev) +{ + unsigned char b_ModulCpt = 0; + int i_ReturnValue = 0; + + /********************************/ + /* Test if counter module found */ + /********************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[0] & 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER + || (devpriv->s_BoardInfos. + dw_MolduleConfiguration[1] & 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER + || (devpriv->s_BoardInfos. + dw_MolduleConfiguration[2] & 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER + || (devpriv->s_BoardInfos. + dw_MolduleConfiguration[3] & 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER) { + for (b_ModulCpt = 0; b_ModulCpt < 4; b_ModulCpt++) { + /*******************************/ + /* Test if incremental counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulCpt] & + 0xFFFF0000UL) == + APCI1710_INCREMENTAL_COUNTER) { + /*********************/ + /* Clear the counter */ + /*********************/ + + outl(1, devpriv->s_BoardInfos. + ui_Address + 16 + (64 * b_ModulCpt)); + } + } + } else { + /***************************/ + /* No counter module found */ + /***************************/ + + DPRINTK("No counter module found\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_SetInputFilter | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_Module, | +| unsigned char_ b_PCIInputClock, | +| unsigned char_ b_Filter) | ++----------------------------------------------------------------------------+ +| Task : Disable or enable the software filter from selected | +| module (b_ModulNbr). b_Filter determine the filter time| ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Number of the module to be | +| configured (0 to 3) | +| unsigned char_ b_PCIInputClock : Selection of the PCI bus | +| clock | +| - APCI1710_30MHZ : | +| The PC has a PCI bus clock | +| of 30 MHz | +| - APCI1710_33MHZ : | +| The PC has a PCI bus clock | +| of 33 MHz | +| - APCI1710_40MHZ : | +| The APCI1710 has a 40MHz | +| quartz | +| unsigned char_ b_Filter : Filter selection | +| | +| 30 MHz | +| ------ | +| 0: Software filter not used | +| 1: Filter from 266ns (3.750000MHz) | +| 2: Filter from 400ns (2.500000MHz) | +| 3: Filter from 533ns (1.876170MHz) | +| 4: Filter from 666ns (1.501501MHz) | +| 5: Filter from 800ns (1.250000MHz) | +| 6: Filter from 933ns (1.071800MHz) | +| 7: Filter from 1066ns (0.938080MHz) | +| 8: Filter from 1200ns (0.833333MHz) | +| 9: Filter from 1333ns (0.750000MHz) | +| 10: Filter from 1466ns (0.682100MHz) | +| 11: Filter from 1600ns (0.625000MHz) | +| 12: Filter from 1733ns (0.577777MHz) | +| 13: Filter from 1866ns (0.535900MHz) | +| 14: Filter from 2000ns (0.500000MHz) | +| 15: Filter from 2133ns (0.468800MHz) | +| | +| 33 MHz | +| ------ | +| 0: Software filter not used | +| 1: Filter from 242ns (4.125000MHz) | +| 2: Filter from 363ns (2.754820MHz) | +| 3: Filter from 484ns (2.066115MHz) | +| 4: Filter from 605ns (1.652892MHz) | +| 5: Filter from 726ns (1.357741MHz) | +| 6: Filter from 847ns (1.180637MHz) | +| 7: Filter from 968ns (1.033055MHz) | +| 8: Filter from 1089ns (0.918273MHz) | +| 9: Filter from 1210ns (0.826446MHz) | +| 10: Filter from 1331ns (0.751314MHz) | +| 11: Filter from 1452ns (0.688705MHz) | +| 12: Filter from 1573ns (0.635727MHz) | +| 13: Filter from 1694ns (0.590318MHz) | +| 14: Filter from 1815ns (0.550964MHz) | +| 15: Filter from 1936ns (0.516528MHz) | +| | +| 40 MHz | +| ------ | +| 0: Software filter not used | +| 1: Filter from 200ns (5.000000MHz) | +| 2: Filter from 300ns (3.333333MHz) | +| 3: Filter from 400ns (2.500000MHz) | +| 4: Filter from 500ns (2.000000MHz) | +| 5: Filter from 600ns (1.666666MHz) | +| 6: Filter from 700ns (1.428500MHz) | +| 7: Filter from 800ns (1.250000MHz) | +| 8: Filter from 900ns (1.111111MHz) | +| 9: Filter from 1000ns (1.000000MHz) | +| 10: Filter from 1100ns (0.909090MHz) | +| 11: Filter from 1200ns (0.833333MHz) | +| 12: Filter from 1300ns (0.769200MHz) | +| 13: Filter from 1400ns (0.714200MHz) | +| 14: Filter from 1500ns (0.666666MHz) | +| 15: Filter from 1600ns (0.625000MHz) | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The selected module number is wrong | +| -3: The module is not a counter module | +| -4: The selected PCI input clock is wrong | +| -5: The selected filter value is wrong | +| -6: 40MHz quartz not on board | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_SetInputFilter(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_PCIInputClock, unsigned char b_Filter) +{ + int i_ReturnValue = 0; + unsigned int dw_Status = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if incremental counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_INCREMENTAL_COUNTER) { + /******************************/ + /* Test if firmware >= Rev1.5 */ + /******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF) >= 0x3135) { + /**************************/ + /* Test the PCI bus clock */ + /**************************/ + + if ((b_PCIInputClock == APCI1710_30MHZ) || + (b_PCIInputClock == APCI1710_33MHZ) || + (b_PCIInputClock == APCI1710_40MHZ)) { + /*************************/ + /* Test the filter value */ + /*************************/ + + if (b_Filter < 16) { + /**********************/ + /* Test if 40MHz used */ + /**********************/ + + if (b_PCIInputClock == + APCI1710_40MHZ) { + /*********************************/ + /* Test if 40MHz quartz on board */ + /*********************************/ + + dw_Status = + inl(devpriv-> + s_BoardInfos. + ui_Address + + 36 + + (64 * b_ModulNbr)); + + /******************************/ + /* Test the quartz flag (DQ0) */ + /******************************/ + + if ((dw_Status & 1) != + 1) { + /*****************************/ + /* 40MHz quartz not on board */ + /*****************************/ + + DPRINTK("40MHz quartz not on board\n"); + i_ReturnValue = + -6; + } + } /* if (b_PCIInputClock == APCI1710_40MHZ) */ + + /***************************/ + /* Test if error not occur */ + /***************************/ + + if (i_ReturnValue == 0) { + /**********************/ + /* Test if 40MHz used */ + /**********************/ + + if (b_PCIInputClock == + APCI1710_40MHZ) + { + /*********************************/ + /* Enable the 40MHz quarz (DQ31) */ + /*********************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + | + APCI1710_ENABLE_40MHZ_FILTER; + + } /* if (b_PCIInputClock == APCI1710_40MHZ) */ + else { + /**********************************/ + /* Disable the 40MHz quarz (DQ31) */ + /**********************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + & + APCI1710_DISABLE_40MHZ_FILTER; + + } /* if (b_PCIInputClock == APCI1710_40MHZ) */ + + /************************/ + /* Set the filter value */ + /************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 + = + (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 + & 0x1F) | + ((b_Filter & + 0x7) << + 5); + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + = + (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 + & 0xFE) | + ((b_Filter & + 0x8) >> + 3); + + /***************************/ + /* Write the configuration */ + /***************************/ + + outl(devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv-> + s_BoardInfos. + ui_Address + + 20 + + (64 * b_ModulNbr)); + } /* if (i_ReturnValue == 0) */ + } /* if (b_Filter < 16) */ + else { + /**************************************/ + /* The selected filter value is wrong */ + /**************************************/ + + DPRINTK("The selected filter value is wrong\n"); + i_ReturnValue = -5; + } /* if (b_Filter < 16) */ + } /* if ((b_PCIInputClock == APCI1710_30MHZ) || (b_PCIInputClock == APCI1710_33MHZ) || (b_PCIInputClock == APCI1710_40MHZ)) */ + else { + /*****************************************/ + /* The selected PCI input clock is wrong */ + /*****************************************/ + + DPRINTK("The selected PCI input clock is wrong\n"); + i_ReturnValue = 4; + } /* if ((b_PCIInputClock == APCI1710_30MHZ) || (b_PCIInputClock == APCI1710_33MHZ) || (b_PCIInputClock == APCI1710_40MHZ)) */ + } else { + /**************************************/ + /* The module is not a counter module */ + /**************************************/ + + DPRINTK("The module is not a counter module\n"); + i_ReturnValue = -3; + } + } else { + /**************************************/ + /* The module is not a counter module */ + /**************************************/ + + DPRINTK("The module is not a counter module\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_LatchCounter (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_LatchReg) | ++----------------------------------------------------------------------------+ +| Task : Latch the courant value from selected module | +| (b_ModulNbr) in to the selected latch register | +| (b_LatchReg). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| unsigned char_ b_LatchReg : Selected latch register | +| 0 : for the first latch register | +| 1 : for the second latch register | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: The selected latch register parameter is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_LatchCounter(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_LatchReg) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*************************************/ + /* Test the latch register parameter */ + /*************************************/ + + if (b_LatchReg < 2) { + /*********************/ + /* Tatch the counter */ + /*********************/ + + outl(1 << (b_LatchReg * 4), + devpriv->s_BoardInfos.ui_Address + + (64 * b_ModulNbr)); + } else { + /**************************************************/ + /* The selected latch register parameter is wrong */ + /**************************************************/ + + DPRINTK("The selected latch register parameter is wrong\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_SetIndexAndReferenceSource | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_SourceSelection) | ++----------------------------------------------------------------------------+ +| Task : Determine the hardware source for the index and the | +| reference logic. Per default the index logic is | +| connected to the difference input C and the reference | +| logic is connected to the 24V input E | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| unsigned char_ b_SourceSelection : APCI1710_SOURCE_0 : | +| The index logic is connected | +| to the difference input C and| +| the reference logic is | +| connected to the 24V input E.| +| This is the default | +| configuration. | +| APCI1710_SOURCE_1 : | +| The reference logic is | +| connected to the difference | +| input C and the index logic | +| is connected to the 24V | +| input E | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The selected module number is wrong | +| -3: The module is not a counter module. | +| -4: The source selection is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_SetIndexAndReferenceSource(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_SourceSelection) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if incremental counter */ + /*******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_INCREMENTAL_COUNTER) { + /******************************/ + /* Test if firmware >= Rev1.5 */ + /******************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF) >= 0x3135) { + /*****************************/ + /* Test the source selection */ + /*****************************/ + + if (b_SourceSelection == APCI1710_SOURCE_0 || + b_SourceSelection == APCI1710_SOURCE_1) + { + /******************************************/ + /* Test if invert the index and reference */ + /******************************************/ + + if (b_SourceSelection == + APCI1710_SOURCE_1) { + /********************************************/ + /* Invert index and reference source (DQ25) */ + /********************************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 | + APCI1710_INVERT_INDEX_RFERENCE; + } else { + /****************************************/ + /* Set the default configuration (DQ25) */ + /****************************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister4 & + APCI1710_DEFAULT_INDEX_RFERENCE; + } + } /* if (b_SourceSelection == APCI1710_SOURCE_0 ||b_SourceSelection == APCI1710_SOURCE_1) */ + else { + /*********************************/ + /* The source selection is wrong */ + /*********************************/ + + DPRINTK("The source selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_SourceSelection == APCI1710_SOURCE_0 ||b_SourceSelection == APCI1710_SOURCE_1) */ + } else { + /**************************************/ + /* The module is not a counter module */ + /**************************************/ + + DPRINTK("The module is not a counter module\n"); + i_ReturnValue = -3; + } + } else { + /**************************************/ + /* The module is not a counter module */ + /**************************************/ + + DPRINTK("The module is not a counter module\n"); + i_ReturnValue = -3; + } + } else { + /***************************************/ + /* The selected module number is wrong */ + /***************************************/ + + DPRINTK("The selected module number is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_SetDigitalChlOn | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Task : Sets the digital output H Setting an output means | +| setting an ouput high. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Number of the module to be | +| configured (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The selected module number is wrong | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_SetDigitalChlOn(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister.b_ModeRegister3 | 0x10; + + /*********************/ + /* Set the output On */ + /*********************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, devpriv->s_BoardInfos. + ui_Address + 20 + (64 * b_ModulNbr)); + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_SetDigitalChlOff | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Task : Resets the digital output H. Resetting an output means | +| setting an ouput low. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Number of the module to be | +| configured (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The selected module number is wrong | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_SetDigitalChlOff(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister.b_ModeRegister3 & 0xEF; + + /**********************/ + /* Set the output Off */ + /**********************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, devpriv->s_BoardInfos. + ui_Address + 20 + (64 * b_ModulNbr)); + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/*########################################################################### */ + + /* INSN WRITE */ +/*########################################################################### */ + +/* ++----------------------------------------------------------------------------+ +| Function Name :INT i_APCI1710_InsnWriteINCCPT(struct comedi_device *dev,struct comedi_subdevice *s, +struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Enable Disable functions for INC_CPT | ++----------------------------------------------------------------------------+ +| Input Parameters : ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : ++----------------------------------------------------------------------------+ +*/ +int i_APCI1710_InsnWriteINCCPT(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_WriteType; + int i_ReturnValue = 0; + + ui_WriteType = CR_CHAN(insn->chanspec); + devpriv->tsk_Current = current; /* Save the current process task structure */ + + switch (ui_WriteType) { + case APCI1710_INCCPT_ENABLELATCHINTERRUPT: + i_ReturnValue = i_APCI1710_EnableLatchInterrupt(dev, + (unsigned char) CR_AREF(insn->chanspec)); + break; + + case APCI1710_INCCPT_DISABLELATCHINTERRUPT: + i_ReturnValue = i_APCI1710_DisableLatchInterrupt(dev, + (unsigned char) CR_AREF(insn->chanspec)); + break; + + case APCI1710_INCCPT_WRITE16BITCOUNTERVALUE: + i_ReturnValue = i_APCI1710_Write16BitCounterValue(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned char) data[0], (unsigned int) data[1]); + break; + + case APCI1710_INCCPT_WRITE32BITCOUNTERVALUE: + i_ReturnValue = i_APCI1710_Write32BitCounterValue(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned int) data[0]); + + break; + + case APCI1710_INCCPT_ENABLEINDEX: + i_APCI1710_EnableIndex(dev, (unsigned char) CR_AREF(insn->chanspec)); + break; + + case APCI1710_INCCPT_DISABLEINDEX: + i_ReturnValue = i_APCI1710_DisableIndex(dev, + (unsigned char) CR_AREF(insn->chanspec)); + break; + + case APCI1710_INCCPT_ENABLECOMPARELOGIC: + i_ReturnValue = i_APCI1710_EnableCompareLogic(dev, + (unsigned char) CR_AREF(insn->chanspec)); + break; + + case APCI1710_INCCPT_DISABLECOMPARELOGIC: + i_ReturnValue = i_APCI1710_DisableCompareLogic(dev, + (unsigned char) CR_AREF(insn->chanspec)); + break; + + case APCI1710_INCCPT_ENABLEFREQUENCYMEASUREMENT: + i_ReturnValue = i_APCI1710_EnableFrequencyMeasurement(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char) data[0]); + break; + + case APCI1710_INCCPT_DISABLEFREQUENCYMEASUREMENT: + i_ReturnValue = i_APCI1710_DisableFrequencyMeasurement(dev, + (unsigned char) CR_AREF(insn->chanspec)); + break; + + default: + printk("Write Config Parameter Wrong\n"); + } + + if (i_ReturnValue >= 0) + i_ReturnValue = insn->n; + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_EnableLatchInterrupt | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Task : Enable the latch interrupt from selected module | +| (b_ModulNbr). Each software or hardware latch occur a | +| interrupt. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Interrupt routine not installed see function | +| "i_APCI1710_SetBoardIntRoutine" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_EnableLatchInterrupt(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + + /********************/ + /* Enable interrupt */ + /********************/ + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 | APCI1710_ENABLE_LATCH_INT; + + /***************************/ + /* Write the configuration */ + /***************************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, devpriv->s_BoardInfos. + ui_Address + 20 + (64 * b_ModulNbr)); + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_DisableLatchInterrupt | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Task : Disable the latch interrupt from selected module | +| (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Interrupt routine not installed see function | +| "i_APCI1710_SetBoardIntRoutine" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_DisableLatchInterrupt(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + + /***************************/ + /* Write the configuration */ + /***************************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4 & + ((APCI1710_DISABLE_LATCH_INT << 8) | 0xFF), + devpriv->s_BoardInfos.ui_Address + 20 + + (64 * b_ModulNbr)); + + mdelay(1000); + + /*********************/ + /* Disable interrupt */ + /*********************/ + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 & APCI1710_DISABLE_LATCH_INT; + + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_Write16BitCounterValue | +| (unsigned char_ b_BoardHandle | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_SelectedCounter, | +| unsigned int_ ui_WriteValue) | ++----------------------------------------------------------------------------+ +| Task : Write a 16-Bit value (ui_WriteValue) in to the selected| +| 16-Bit counter (b_SelectedCounter) from selected module| +| (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| unsigned char_ b_SelectedCounter : Selected 16-Bit counter | +| (0 or 1) | +| unsigned int_ ui_WriteValue : 16-Bit write value | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: The selected 16-Bit counter parameter is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_Write16BitCounterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_SelectedCounter, unsigned int ui_WriteValue) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /******************************/ + /* Test the counter selection */ + /******************************/ + + if (b_SelectedCounter < 2) { + /*******************/ + /* Write the value */ + /*******************/ + + outl((unsigned int) ((unsigned int) (ui_WriteValue) << (16 * + b_SelectedCounter)), + devpriv->s_BoardInfos.ui_Address + 8 + + (b_SelectedCounter * 4) + + (64 * b_ModulNbr)); + } else { + /**************************************************/ + /* The selected 16-Bit counter parameter is wrong */ + /**************************************************/ + + DPRINTK("The selected 16-Bit counter parameter is wrong\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_Write32BitCounterValue | +| (unsigned char_ b_BoardHandle | +| unsigned char_ b_ModulNbr, | +| ULONG_ ul_WriteValue) | ++----------------------------------------------------------------------------+ +| Task : Write a 32-Bit value (ui_WriteValue) in to the selected| +| module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| ULONG_ ul_WriteValue : 32-Bit write value | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_Write32BitCounterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned int ul_WriteValue) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*******************/ + /* Write the value */ + /*******************/ + + outl(ul_WriteValue, devpriv->s_BoardInfos. + ui_Address + 4 + (64 * b_ModulNbr)); + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_EnableIndex (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Task : Enable the INDEX actions | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Index not initialised see function | +| "i_APCI1710_InitIndex" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_EnableIndex(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + unsigned int ul_InterruptLatchReg; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*****************************/ + /* Test if index initialised */ + /*****************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_IndexInit) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 | APCI1710_ENABLE_INDEX; + + ul_InterruptLatchReg = + inl(devpriv->s_BoardInfos.ui_Address + + 24 + (64 * b_ModulNbr)); + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv->s_BoardInfos.ui_Address + 20 + + (64 * b_ModulNbr)); + } else { + /*************************************************************/ + /* Index not initialised see function "i_APCI1710_InitIndex" */ + /*************************************************************/ + + DPRINTK("Index not initialised \n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_DisableIndex (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Task : Disable the INDEX actions | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Index not initialised see function | +| "i_APCI1710_InitIndex" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_DisableIndex(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*****************************/ + /* Test if index initialised */ + /*****************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_IndexInit) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 & + APCI1710_DISABLE_INDEX; + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv->s_BoardInfos.ui_Address + 20 + + (64 * b_ModulNbr)); + } else { + /*************************************************************/ + /* Index not initialised see function "i_APCI1710_InitIndex" */ + /*************************************************************/ + + DPRINTK("Index not initialised \n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_EnableCompareLogic | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Task : Enable the 32-Bit compare logic. At that moment that | +| the incremental counter arrive to the compare value a | +| interrupt is generated. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : - ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Compare logic not initialised. | +| See function "i_APCI1710_InitCompareLogic" | +| -5: Interrupt function not initialised. | +| See function "i_APCI1710_SetBoardIntRoutineX" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_EnableCompareLogic(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*************************************/ + /* Test if compare logic initialised */ + /*************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_CompareLogicInit == 1) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 | + APCI1710_ENABLE_COMPARE_INT; + + /***************************/ + /* Write the configuration */ + /***************************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv->s_BoardInfos.ui_Address + 20 + + (64 * b_ModulNbr)); + } else { + /*********************************/ + /* Compare logic not initialised */ + /*********************************/ + + DPRINTK("Compare logic not initialised\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_DisableCompareLogic | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Task : Disable the 32-Bit compare logic. ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : - ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Compare logic not initialised. | +| See function "i_APCI1710_InitCompareLogic" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_DisableCompareLogic(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*************************************/ + /* Test if compare logic initialised */ + /*************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_CompareLogicInit == 1) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 & + APCI1710_DISABLE_COMPARE_INT; + + /***************************/ + /* Write the configuration */ + /***************************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv->s_BoardInfos.ui_Address + 20 + + (64 * b_ModulNbr)); + } else { + /*********************************/ + /* Compare logic not initialised */ + /*********************************/ + + DPRINTK("Compare logic not initialised\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + + /* + +----------------------------------------------------------------------------+ + | Function Name : _INT_ i_APCI1710_EnableFrequencyMeasurement | + | (unsigned char_ b_BoardHandle, | + | unsigned char_ b_ModulNbr, | + | unsigned char_ b_InterruptEnable) | + +----------------------------------------------------------------------------+ + | Task : Enables the frequency measurement function | + +----------------------------------------------------------------------------+ + | Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | + | unsigned char_ b_ModulNbr : Number of the module to be | + | configured (0 to 3) | + | unsigned char_ b_InterruptEnable: Enable or disable the | + | interrupt. | + | APCI1710_ENABLE: | + | Enable the interrupt | + | APCI1710_DISABLE: | + | Disable the interrupt | + +----------------------------------------------------------------------------+ + | Output Parameters : - | + +----------------------------------------------------------------------------+ + | Return Value : 0: No error | + | -1: The handle parameter of the board is wrong | + | -2: The selected module number is wrong | + | -3: Counter not initialised see function | + | "i_APCI1710_InitCounter" | + | -4: Frequency measurement logic not initialised. | + | See function "i_APCI1710_InitFrequencyMeasurement" | + | -5: Interrupt parameter is wrong | + | -6: Interrupt function not initialised. | + +----------------------------------------------------------------------------+ + */ + +int i_APCI1710_EnableFrequencyMeasurement(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_InterruptEnable) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /********************************************/ + /* Test if frequency measurement initialised */ + /********************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_FrequencyMeasurementInit == 1) { + /***************************/ + /* Test the interrupt mode */ + /***************************/ + + if ((b_InterruptEnable == APCI1710_DISABLE) || + (b_InterruptEnable == APCI1710_ENABLE)) + { + + /************************************/ + /* Enable the frequency measurement */ + /************************************/ + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 | + APCI1710_ENABLE_FREQUENCY; + + /*********************************************/ + /* Disable or enable the frequency interrupt */ + /*********************************************/ + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 = (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 & + APCI1710_DISABLE_FREQUENCY_INT) + | (b_InterruptEnable << 3); + + /***************************/ + /* Write the configuration */ + /***************************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv->s_BoardInfos. + ui_Address + 20 + + (64 * b_ModulNbr)); + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag. + b_FrequencyMeasurementEnable = + 1; + } else { + /********************************/ + /* Interrupt parameter is wrong */ + /********************************/ + + DPRINTK("Interrupt parameter is wrong\n"); + i_ReturnValue = -5; + } + } else { + /***********************************************/ + /* Frequency measurement logic not initialised */ + /***********************************************/ + + DPRINTK("Frequency measurement logic not initialised\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + + /* + +----------------------------------------------------------------------------+ + | Function Name : _INT_ i_APCI1710_DisableFrequencyMeasurement | + | (unsigned char_ b_BoardHandle, | + | unsigned char_ b_ModulNbr) | + +----------------------------------------------------------------------------+ + | Task : Disables the frequency measurement function | + +----------------------------------------------------------------------------+ + | Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | + | unsigned char_ b_ModulNbr : Number of the module to be | + | configured (0 to 3) | + +----------------------------------------------------------------------------+ + | Output Parameters : - | + +----------------------------------------------------------------------------+ + | Return Value : 0: No error | + | -1: The handle parameter of the board is wrong | + | -2: The selected module number is wrong | + | -3: Counter not initialised see function | + | "i_APCI1710_InitCounter" | + | -4: Frequency measurement logic not initialised. | + | See function "i_APCI1710_InitFrequencyMeasurement" | + +----------------------------------------------------------------------------+ + */ + +int i_APCI1710_DisableFrequencyMeasurement(struct comedi_device *dev, unsigned char b_ModulNbr) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /********************************************/ + /* Test if frequency measurement initialised */ + /********************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_FrequencyMeasurementInit == 1) { + /*************************************/ + /* Disable the frequency measurement */ + /*************************************/ + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 & + APCI1710_DISABLE_FREQUENCY + /* Begin CG 29/06/01 CG 1100/0231 -> 0701/0232 Frequence measure IRQ must be cleared */ + & APCI1710_DISABLE_FREQUENCY_INT; + /* End CG 29/06/01 CG 1100/0231 -> 0701/0232 Frequence measure IRQ must be cleared */ + + /***************************/ + /* Write the configuration */ + /***************************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv->s_BoardInfos.ui_Address + 20 + + (64 * b_ModulNbr)); + + /*************************************/ + /* Disable the frequency measurement */ + /*************************************/ + + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag. + b_FrequencyMeasurementEnable = 0; + } else { + /***********************************************/ + /* Frequency measurement logic not initialised */ + /***********************************************/ + + DPRINTK("Frequency measurement logic not initialised\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/*########################################################################### */ + + /* INSN READ */ + +/*########################################################################### */ + +/* ++----------------------------------------------------------------------------+ +| Function Name :INT i_APCI1710_InsnWriteINCCPT(struct comedi_device *dev,struct comedi_subdevice *s, +struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read and Get functions for INC_CPT | ++----------------------------------------------------------------------------+ +| Input Parameters : ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : ++----------------------------------------------------------------------------+ +*/ +int i_APCI1710_InsnReadINCCPT(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_ReadType; + int i_ReturnValue = 0; + + ui_ReadType = CR_CHAN(insn->chanspec); + + devpriv->tsk_Current = current; /* Save the current process task structure */ + switch (ui_ReadType) { + case APCI1710_INCCPT_READLATCHREGISTERSTATUS: + i_ReturnValue = i_APCI1710_ReadLatchRegisterStatus(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned char) CR_RANGE(insn->chanspec), (unsigned char *) &data[0]); + break; + + case APCI1710_INCCPT_READLATCHREGISTERVALUE: + i_ReturnValue = i_APCI1710_ReadLatchRegisterValue(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned char) CR_RANGE(insn->chanspec), (unsigned int *) &data[0]); + printk("Latch Register Value %d\n", data[0]); + break; + + case APCI1710_INCCPT_READ16BITCOUNTERVALUE: + i_ReturnValue = i_APCI1710_Read16BitCounterValue(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned char) CR_RANGE(insn->chanspec), (unsigned int *) &data[0]); + break; + + case APCI1710_INCCPT_READ32BITCOUNTERVALUE: + i_ReturnValue = i_APCI1710_Read32BitCounterValue(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned int *) &data[0]); + break; + + case APCI1710_INCCPT_GETINDEXSTATUS: + i_ReturnValue = i_APCI1710_GetIndexStatus(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char *) &data[0]); + break; + + case APCI1710_INCCPT_GETREFERENCESTATUS: + i_ReturnValue = i_APCI1710_GetReferenceStatus(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char *) &data[0]); + break; + + case APCI1710_INCCPT_GETUASSTATUS: + i_ReturnValue = i_APCI1710_GetUASStatus(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char *) &data[0]); + break; + + case APCI1710_INCCPT_GETCBSTATUS: + i_ReturnValue = i_APCI1710_GetCBStatus(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char *) &data[0]); + break; + + case APCI1710_INCCPT_GET16BITCBSTATUS: + i_ReturnValue = i_APCI1710_Get16BitCBStatus(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned char *) &data[0], (unsigned char *) &data[1]); + break; + + case APCI1710_INCCPT_GETUDSTATUS: + i_ReturnValue = i_APCI1710_GetUDStatus(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char *) &data[0]); + + break; + + case APCI1710_INCCPT_GETINTERRUPTUDLATCHEDSTATUS: + i_ReturnValue = i_APCI1710_GetInterruptUDLatchedStatus(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char *) &data[0]); + break; + + case APCI1710_INCCPT_READFREQUENCYMEASUREMENT: + i_ReturnValue = i_APCI1710_ReadFrequencyMeasurement(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned char *) &data[0], + (unsigned char *) &data[1], (unsigned int *) &data[2]); + break; + + case APCI1710_INCCPT_READINTERRUPT: + data[0] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].b_OldModuleMask; + data[1] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldInterruptMask; + data[2] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldCounterLatchValue; + + /**************************/ + /* Increment the read FIFO */ + /***************************/ + + devpriv-> + s_InterruptParameters. + ui_Read = (devpriv->s_InterruptParameters. + ui_Read + 1) % APCI1710_SAVE_INTERRUPT; + + break; + + default: + printk("ReadType Parameter wrong\n"); + } + + if (i_ReturnValue >= 0) + i_ReturnValue = insn->n; + return i_ReturnValue; + +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadLatchRegisterStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_LatchReg, | +| unsigned char *_ pb_LatchStatus) | ++----------------------------------------------------------------------------+ +| Task : Read the latch register status from selected module | +| (b_ModulNbr) and selected latch register (b_LatchReg). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| unsigned char_ b_LatchReg : Selected latch register | +| 0 : for the first latch register | +| 1 : for the second latch register | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_LatchStatus : Latch register status. | +| 0 : No latch occur | +| 1 : A software latch occur | +| 2 : A hardware latch occur | +| 3 : A software and hardware | +| latch occur | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: The selected latch register parameter is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_ReadLatchRegisterStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_LatchReg, unsigned char *pb_LatchStatus) +{ + int i_ReturnValue = 0; + unsigned int dw_LatchReg; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*************************************/ + /* Test the latch register parameter */ + /*************************************/ + + if (b_LatchReg < 2) { + dw_LatchReg = inl(devpriv->s_BoardInfos. + ui_Address + (64 * b_ModulNbr)); + + *pb_LatchStatus = + (unsigned char) ((dw_LatchReg >> (b_LatchReg * + 4)) & 0x3); + } else { + /**************************************************/ + /* The selected latch register parameter is wrong */ + /**************************************************/ + + DPRINTK("The selected latch register parameter is wrong\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadLatchRegisterValue | +| (unsigned char_ b_BoardHandle,| +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_LatchReg, | +| PULONG_ pul_LatchValue) | ++----------------------------------------------------------------------------+ +| Task : Read the latch register value from selected module | +| (b_ModulNbr) and selected latch register (b_LatchReg). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| unsigned char_ b_LatchReg : Selected latch register | +| 0 : for the first latch register | +| 1 : for the second latch register | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_LatchValue : Latch register value | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: The selected latch register parameter is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_ReadLatchRegisterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_LatchReg, unsigned int *pul_LatchValue) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*************************************/ + /* Test the latch register parameter */ + /*************************************/ + + if (b_LatchReg < 2) { + *pul_LatchValue = inl(devpriv->s_BoardInfos. + ui_Address + ((b_LatchReg + 1) * 4) + + (64 * b_ModulNbr)); + + } else { + /**************************************************/ + /* The selected latch register parameter is wrong */ + /**************************************************/ + + DPRINTK("The selected latch register parameter is wrong\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_Read16BitCounterValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_SelectedCounter, | +| unsigned int *_ pui_CounterValue) | ++----------------------------------------------------------------------------+ +| Task : Latch the selected 16-Bit counter (b_SelectedCounter) | +| from selected module (b_ModulNbr) in to the first | +| latch register and return the latched value. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| unsigned char_ b_SelectedCounter : Selected 16-Bit counter | +| (0 or 1) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned int *_ pui_CounterValue : 16-Bit counter value | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: The selected 16-Bit counter parameter is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_Read16BitCounterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_SelectedCounter, unsigned int *pui_CounterValue) +{ + int i_ReturnValue = 0; + unsigned int dw_LathchValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /******************************/ + /* Test the counter selection */ + /******************************/ + + if (b_SelectedCounter < 2) { + /*********************/ + /* Latch the counter */ + /*********************/ + + outl(1, devpriv->s_BoardInfos. + ui_Address + (64 * b_ModulNbr)); + + /************************/ + /* Read the latch value */ + /************************/ + + dw_LathchValue = inl(devpriv->s_BoardInfos. + ui_Address + 4 + (64 * b_ModulNbr)); + + *pui_CounterValue = + (unsigned int) ((dw_LathchValue >> (16 * + b_SelectedCounter)) & + 0xFFFFU); + } else { + /**************************************************/ + /* The selected 16-Bit counter parameter is wrong */ + /**************************************************/ + + DPRINTK("The selected 16-Bit counter parameter is wrong\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_Read32BitCounterValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| PULONG_ pul_CounterValue) | ++----------------------------------------------------------------------------+ +| Task : Latch the 32-Bit counter from selected module | +| (b_ModulNbr) in to the first latch register and return | +| the latched value. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_CounterValue : 32-Bit counter value | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_Read32BitCounterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned int *pul_CounterValue) +{ + int i_ReturnValue = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*********************/ + /* Tatch the counter */ + /*********************/ + + outl(1, devpriv->s_BoardInfos. + ui_Address + (64 * b_ModulNbr)); + + /************************/ + /* Read the latch value */ + /************************/ + + *pul_CounterValue = inl(devpriv->s_BoardInfos. + ui_Address + 4 + (64 * b_ModulNbr)); + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetIndexStatus (unsigned char_ b_BoardHandle,| +| unsigned char_ b_ModulNbr, | +| unsigned char *_ pb_IndexStatus)| ++----------------------------------------------------------------------------+ +| Task : Return the index status | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_IndexStatus : 0 : No INDEX occur | +| 1 : A INDEX occur | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Index not initialised see function | +| "i_APCI1710_InitIndex" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_GetIndexStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_IndexStatus) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*****************************/ + /* Test if index initialised */ + /*****************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_IndexInit) { + dw_StatusReg = inl(devpriv->s_BoardInfos. + ui_Address + 12 + (64 * b_ModulNbr)); + + *pb_IndexStatus = (unsigned char) (dw_StatusReg & 1); + } else { + /*************************************************************/ + /* Index not initialised see function "i_APCI1710_InitIndex" */ + /*************************************************************/ + + DPRINTK("Index not initialised\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetReferenceStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char *_ pb_ReferenceStatus) | ++----------------------------------------------------------------------------+ +| Task : Return the reference status | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_ReferenceStatus : 0 : No REFERENCE occur | +| 1 : A REFERENCE occur | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Reference not initialised see function | +| "i_APCI1710_InitReference" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_GetReferenceStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_ReferenceStatus) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*********************************/ + /* Test if reference initialised */ + /*********************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_ReferenceInit) { + dw_StatusReg = inl(devpriv->s_BoardInfos. + ui_Address + 24 + (64 * b_ModulNbr)); + + *pb_ReferenceStatus = + (unsigned char) (~dw_StatusReg & 1); + } else { + /*********************************************************************/ + /* Reference not initialised see function "i_APCI1710_InitReference" */ + /*********************************************************************/ + + DPRINTK("Reference not initialised\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetUASStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char *_ pb_UASStatus) | ++----------------------------------------------------------------------------+ +| Task : Return the error signal (UAS) status | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_UASStatus : 0 : UAS is low "0" | +| 1 : UAS is high "1" | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_GetUASStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_UASStatus) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + dw_StatusReg = inl(devpriv->s_BoardInfos. + ui_Address + 24 + (64 * b_ModulNbr)); + + *pb_UASStatus = (unsigned char) ((dw_StatusReg >> 1) & 1); + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetCBStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char *_ pb_CBStatus) | ++----------------------------------------------------------------------------+ +| Task : Return the counter overflow status | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_CBStatus : 0 : Counter no overflow | +| 1 : Counter overflow | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_GetCBStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_CBStatus) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + dw_StatusReg = inl(devpriv->s_BoardInfos. + ui_Address + 16 + (64 * b_ModulNbr)); + + *pb_CBStatus = (unsigned char) (dw_StatusReg & 1); + + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_Get16BitCBStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char *_ pb_CBStatusCounter0, | +| unsigned char *_ pb_CBStatusCounter1) | ++----------------------------------------------------------------------------+ +| Task : Returns the counter overflow (counter initialised to | +| 2*16-bit) status from selected incremental counter | +| module | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_CBStatusCounter0 : 0 : No overflow occur for | +| the first 16-bit | +| counter | +| 1 : Overflow occur for the| +| first 16-bit counter | +| unsigned char *_ pb_CBStatusCounter1 : 0 : No overflow occur for | +| the second 16-bit | +| counter | +| 1 : Overflow occur for the| +| second 16-bit counter | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Counter not initialised to 2*16-bit mode. | +| See function "i_APCI1710_InitCounter" | +| -5: Firmware revision error | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_Get16BitCBStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_CBStatusCounter0, unsigned char *pb_CBStatusCounter1) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*************************/ + /* Test if 2*16-Bit mode */ + /*************************/ + + if ((devpriv->s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister1 & 0x10) == 0x10) { + /*****************************/ + /* Test the Firmware version */ + /*****************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & 0xFFFF) >= + 0x3136) { + dw_StatusReg = + inl(devpriv->s_BoardInfos. + ui_Address + 16 + + (64 * b_ModulNbr)); + + *pb_CBStatusCounter1 = + (unsigned char) ((dw_StatusReg >> 0) & + 1); + *pb_CBStatusCounter0 = + (unsigned char) ((dw_StatusReg >> 1) & + 1); + } /* if ((ps_APCI1710Variable->s_Board [b_BoardHandle].s_BoardInfos.dw_MolduleConfiguration [b_ModulNbr] & 0xFFFF) >= 0x3136) */ + else { + /****************************/ + /* Firmware revision error */ + /****************************/ + + i_ReturnValue = -5; + } /* if ((ps_APCI1710Variable->s_Board [b_BoardHandle].s_BoardInfos.dw_MolduleConfiguration [b_ModulNbr] & 0xFFFF) >= 0x3136) */ + } /* if ((ps_APCI1710Variable->s_Board [b_BoardHandle].s_ModuleInfo [b_ModulNbr].s_SiemensCounterInfo.s_ModeRegister.s_ByteModeRegister.b_ModeRegister1 & 0x10) == 0x10) */ + else { + /********************************************/ + /* Counter not initialised to 2*16-bit mode */ + /* "i_APCI1710_InitCounter" */ + /********************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -4; + } /* if ((ps_APCI1710Variable->s_Board [b_BoardHandle].s_ModuleInfo [b_ModulNbr].s_SiemensCounterInfo.s_ModeRegister.s_ByteModeRegister.b_ModeRegister1 & 0x10) == 0x10) */ + } /* if (ps_APCI1710Variable->s_Board [b_BoardHandle].s_ModuleInfo [b_ModulNbr].s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) */ + else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } /* if (ps_APCI1710Variable->s_Board [b_BoardHandle].s_ModuleInfo [b_ModulNbr].s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) */ + } /* if (b_ModulNbr < 4) */ + else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } /* if (b_ModulNbr < 4) */ + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetUDStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char *_ pb_UDStatus) | ++----------------------------------------------------------------------------+ +| Task : Return the counter progress status | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_UDStatus : 0 : Counter progress in the | +| selected mode down | +| 1 : Counter progress in the | +| selected mode up | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_GetUDStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_UDStatus) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + dw_StatusReg = inl(devpriv->s_BoardInfos. + ui_Address + 24 + (64 * b_ModulNbr)); + + *pb_UDStatus = (unsigned char) ((dw_StatusReg >> 2) & 1); + + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetInterruptUDLatchedStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char *_ pb_UDStatus) | ++----------------------------------------------------------------------------+ +| Task : Return the counter progress latched status after a | +| index interrupt occur. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_UDStatus : 0 : Counter progress in the | +| selected mode down | +| 1 : Counter progress in the | +| selected mode up | +| 2 : No index interrupt occur | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: No counter module found | +| -3: Counter not initialised see function | +| "i_APCI1710_InitCounter" | +| -4: Interrupt function not initialised. | +| See function "i_APCI1710_SetBoardIntRoutineX" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_GetInterruptUDLatchedStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_UDStatus) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /*********************************/ + /* Test if index interrupt occur */ + /*********************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_IndexInterruptOccur == 1) { + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_IndexInterruptOccur = 0; + + dw_StatusReg = inl(devpriv->s_BoardInfos. + ui_Address + 12 + (64 * b_ModulNbr)); + + *pb_UDStatus = (unsigned char) ((dw_StatusReg >> 1) & 1); + } else { + /****************************/ + /* No index interrupt occur */ + /****************************/ + + *pb_UDStatus = 2; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + + /* + +----------------------------------------------------------------------------+ + | Function Name : _INT_ i_APCI1710_ReadFrequencyMeasurement | + | (unsigned char_ b_BoardHandle, | + | unsigned char_ b_ModulNbr, | + | unsigned char *_ pb_Status, | + | PULONG_ pul_ReadValue) | + +----------------------------------------------------------------------------+ + | Task : Returns the status (pb_Status) and the number of | + | increments in the set time. | + | See function " i_APCI1710_InitFrequencyMeasurement " | + +----------------------------------------------------------------------------+ + | Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | + | unsigned char_ b_ModulNbr : Number of the module to be | + | configured (0 to 3) | + +----------------------------------------------------------------------------+ + | Output Parameters : unsigned char *_ pb_Status : Returns the frequency | + | measurement status | + | 0 : Counting cycle not | + | started. | + | 1 : Counting cycle started. | + | 2 : Counting cycle stopped. | + | The measurement cycle is | + | completed. | + | unsigned char *_ pb_UDStatus : 0 : Counter progress in the | + | selected mode down | + | 1 : Counter progress in the | + | selected mode up | + | PULONG_ pul_ReadValue : Return the number of | + | increments in the defined | + | time base. | + +----------------------------------------------------------------------------+ + | Return Value : 0: No error | + | -1: The handle parameter of the board is wrong | + | -2: The selected module number is wrong | + | -3: Counter not initialised see function | + | "i_APCI1710_InitCounter" | + | -4: Frequency measurement logic not initialised. | + | See function "i_APCI1710_InitFrequencyMeasurement" | + +----------------------------------------------------------------------------+ + */ + +int i_APCI1710_ReadFrequencyMeasurement(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char *pb_Status, unsigned char *pb_UDStatus, unsigned int *pul_ReadValue) +{ + int i_ReturnValue = 0; + unsigned int ui_16BitValue; + unsigned int dw_StatusReg; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo.s_InitFlag.b_CounterInit == 1) { + /********************************************/ + /* Test if frequency measurement initialised */ + /********************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag.b_FrequencyMeasurementInit == 1) { + /******************/ + /* Test if enable */ + /******************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SiemensCounterInfo. + s_InitFlag. + b_FrequencyMeasurementEnable == 1) { + /*******************/ + /* Read the status */ + /*******************/ + + dw_StatusReg = + inl(devpriv->s_BoardInfos. + ui_Address + 32 + + (64 * b_ModulNbr)); + + /**************************/ + /* Test if frequency stop */ + /**************************/ + + if (dw_StatusReg & 1) { + *pb_Status = 2; + *pb_UDStatus = + (unsigned char) ((dw_StatusReg >> + 1) & 3); + + /******************/ + /* Read the value */ + /******************/ + + *pul_ReadValue = + inl(devpriv-> + s_BoardInfos. + ui_Address + 28 + + (64 * b_ModulNbr)); + + if (*pb_UDStatus == 0) { + /*************************/ + /* Test the counter mode */ + /*************************/ + + if ((devpriv->s_ModuleInfo[b_ModulNbr].s_SiemensCounterInfo.s_ModeRegister.s_ByteModeRegister.b_ModeRegister1 & APCI1710_16BIT_COUNTER) == APCI1710_16BIT_COUNTER) { + /****************************************/ + /* Test if 16-bit counter 1 pulse occur */ + /****************************************/ + + if ((*pul_ReadValue & 0xFFFFU) != 0) { + ui_16BitValue + = + (unsigned int) + * + pul_ReadValue + & + 0xFFFFU; + *pul_ReadValue + = + (*pul_ReadValue + & + 0xFFFF0000UL) + | + (0xFFFFU + - + ui_16BitValue); + } + + /****************************************/ + /* Test if 16-bit counter 2 pulse occur */ + /****************************************/ + + if ((*pul_ReadValue & 0xFFFF0000UL) != 0) { + ui_16BitValue + = + (unsigned int) + ( + (*pul_ReadValue + >> + 16) + & + 0xFFFFU); + *pul_ReadValue + = + (*pul_ReadValue + & + 0xFFFFUL) + | + ( + (0xFFFFU - ui_16BitValue) << 16); + } + } else { + if (*pul_ReadValue != 0) { + *pul_ReadValue + = + 0xFFFFFFFFUL + - + *pul_ReadValue; + } + } + } else { + if (*pb_UDStatus == 1) { + /****************************************/ + /* Test if 16-bit counter 2 pulse occur */ + /****************************************/ + + if ((*pul_ReadValue & 0xFFFF0000UL) != 0) { + ui_16BitValue + = + (unsigned int) + ( + (*pul_ReadValue + >> + 16) + & + 0xFFFFU); + *pul_ReadValue + = + (*pul_ReadValue + & + 0xFFFFUL) + | + ( + (0xFFFFU - ui_16BitValue) << 16); + } + } else { + if (*pb_UDStatus + == 2) { + /****************************************/ + /* Test if 16-bit counter 1 pulse occur */ + /****************************************/ + + if ((*pul_ReadValue & 0xFFFFU) != 0) { + ui_16BitValue + = + (unsigned int) + * + pul_ReadValue + & + 0xFFFFU; + *pul_ReadValue + = + (*pul_ReadValue + & + 0xFFFF0000UL) + | + (0xFFFFU + - + ui_16BitValue); + } + } + } + } + } else { + *pb_Status = 1; + *pb_UDStatus = 0; + } + } else { + *pb_Status = 0; + *pb_UDStatus = 0; + } + } else { + /***********************************************/ + /* Frequency measurement logic not initialised */ + /***********************************************/ + + DPRINTK("Frequency measurement logic not initialised\n"); + i_ReturnValue = -4; + } + } else { + /****************************************/ + /* Counter not initialised see function */ + /* "i_APCI1710_InitCounter" */ + /****************************************/ + + DPRINTK("Counter not initialised\n"); + i_ReturnValue = -3; + } + } else { + /*************************************************/ + /* The selected module number parameter is wrong */ + /*************************************************/ + + DPRINTK("The selected module number parameter is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_INCCPT.h b/drivers/staging/comedi/drivers/addi-data/APCI1710_INCCPT.h new file mode 100644 index 00000000..358298bf --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_INCCPT.h @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define APCI1710_16BIT_COUNTER 0x10 +#define APCI1710_32BIT_COUNTER 0x0 +#define APCI1710_QUADRUPLE_MODE 0x0 +#define APCI1710_DOUBLE_MODE 0x3 +#define APCI1710_SIMPLE_MODE 0xF +#define APCI1710_DIRECT_MODE 0x80 +#define APCI1710_HYSTERESIS_ON 0x60 +#define APCI1710_HYSTERESIS_OFF 0x0 +#define APCI1710_INCREMENT 0x60 +#define APCI1710_DECREMENT 0x0 +#define APCI1710_LATCH_COUNTER 0x1 +#define APCI1710_CLEAR_COUNTER 0x0 +#define APCI1710_LOW 0x0 +#define APCI1710_HIGH 0x1 + +/*********************/ +/* Version 0600-0229 */ +/*********************/ +#define APCI1710_HIGH_EDGE_CLEAR_COUNTER 0x0 +#define APCI1710_HIGH_EDGE_LATCH_COUNTER 0x1 +#define APCI1710_LOW_EDGE_CLEAR_COUNTER 0x2 +#define APCI1710_LOW_EDGE_LATCH_COUNTER 0x3 +#define APCI1710_HIGH_EDGE_LATCH_AND_CLEAR_COUNTER 0x4 +#define APCI1710_LOW_EDGE_LATCH_AND_CLEAR_COUNTER 0x5 +#define APCI1710_SOURCE_0 0x0 +#define APCI1710_SOURCE_1 0x1 + +#define APCI1710_30MHZ 30 +#define APCI1710_33MHZ 33 +#define APCI1710_40MHZ 40 + +#define APCI1710_ENABLE_LATCH_INT 0x80 +#define APCI1710_DISABLE_LATCH_INT (~APCI1710_ENABLE_LATCH_INT) + +#define APCI1710_INDEX_LATCH_COUNTER 0x10 +#define APCI1710_INDEX_AUTO_MODE 0x8 +#define APCI1710_ENABLE_INDEX 0x4 +#define APCI1710_DISABLE_INDEX (~APCI1710_ENABLE_INDEX) +#define APCI1710_ENABLE_LATCH_AND_CLEAR 0x8 +#define APCI1710_DISABLE_LATCH_AND_CLEAR (~APCI1710_ENABLE_LATCH_AND_CLEAR) +#define APCI1710_SET_LOW_INDEX_LEVEL 0x4 +#define APCI1710_SET_HIGH_INDEX_LEVEL (~APCI1710_SET_LOW_INDEX_LEVEL) +#define APCI1710_INVERT_INDEX_RFERENCE 0x2 +#define APCI1710_DEFAULT_INDEX_RFERENCE (~APCI1710_INVERT_INDEX_RFERENCE) + +#define APCI1710_ENABLE_INDEX_INT 0x1 +#define APCI1710_DISABLE_INDEX_INT (~APCI1710_ENABLE_INDEX_INT) + +#define APCI1710_ENABLE_FREQUENCY 0x4 +#define APCI1710_DISABLE_FREQUENCY (~APCI1710_ENABLE_FREQUENCY) + +#define APCI1710_ENABLE_FREQUENCY_INT 0x8 +#define APCI1710_DISABLE_FREQUENCY_INT (~APCI1710_ENABLE_FREQUENCY_INT) + +#define APCI1710_ENABLE_40MHZ_FREQUENCY 0x40 +#define APCI1710_DISABLE_40MHZ_FREQUENCY (~APCI1710_ENABLE_40MHZ_FREQUENCY) + +#define APCI1710_ENABLE_40MHZ_FILTER 0x80 +#define APCI1710_DISABLE_40MHZ_FILTER (~APCI1710_ENABLE_40MHZ_FILTER) + +#define APCI1710_ENABLE_COMPARE_INT 0x2 +#define APCI1710_DISABLE_COMPARE_INT (~APCI1710_ENABLE_COMPARE_INT) + +#define APCI1710_ENABLE_INDEX_ACTION 0x20 +#define APCI1710_DISABLE_INDEX_ACTION (~APCI1710_ENABLE_INDEX_ACTION) +#define APCI1710_REFERENCE_HIGH 0x40 +#define APCI1710_REFERENCE_LOW (~APCI1710_REFERENCE_HIGH) + +#define APCI1710_TOR_GATE_LOW 0x40 +#define APCI1710_TOR_GATE_HIGH (~APCI1710_TOR_GATE_LOW) + +/* INSN CONFIG */ +#define APCI1710_INCCPT_INITCOUNTER 100 +#define APCI1710_INCCPT_COUNTERAUTOTEST 101 +#define APCI1710_INCCPT_INITINDEX 102 +#define APCI1710_INCCPT_INITREFERENCE 103 +#define APCI1710_INCCPT_INITEXTERNALSTROBE 104 +#define APCI1710_INCCPT_INITCOMPARELOGIC 105 +#define APCI1710_INCCPT_INITFREQUENCYMEASUREMENT 106 + +/* INSN READ */ +#define APCI1710_INCCPT_READLATCHREGISTERSTATUS 200 +#define APCI1710_INCCPT_READLATCHREGISTERVALUE 201 +#define APCI1710_INCCPT_READ16BITCOUNTERVALUE 202 +#define APCI1710_INCCPT_READ32BITCOUNTERVALUE 203 +#define APCI1710_INCCPT_GETINDEXSTATUS 204 +#define APCI1710_INCCPT_GETREFERENCESTATUS 205 +#define APCI1710_INCCPT_GETUASSTATUS 206 +#define APCI1710_INCCPT_GETCBSTATUS 207 +#define APCI1710_INCCPT_GET16BITCBSTATUS 208 +#define APCI1710_INCCPT_GETUDSTATUS 209 +#define APCI1710_INCCPT_GETINTERRUPTUDLATCHEDSTATUS 210 +#define APCI1710_INCCPT_READFREQUENCYMEASUREMENT 211 +#define APCI1710_INCCPT_READINTERRUPT 212 + +/* INSN BITS */ +#define APCI1710_INCCPT_CLEARCOUNTERVALUE 300 +#define APCI1710_INCCPT_CLEARALLCOUNTERVALUE 301 +#define APCI1710_INCCPT_SETINPUTFILTER 302 +#define APCI1710_INCCPT_LATCHCOUNTER 303 +#define APCI1710_INCCPT_SETINDEXANDREFERENCESOURCE 304 +#define APCI1710_INCCPT_SETDIGITALCHLON 305 +#define APCI1710_INCCPT_SETDIGITALCHLOFF 306 + +/* INSN WRITE */ +#define APCI1710_INCCPT_ENABLELATCHINTERRUPT 400 +#define APCI1710_INCCPT_DISABLELATCHINTERRUPT 401 +#define APCI1710_INCCPT_WRITE16BITCOUNTERVALUE 402 +#define APCI1710_INCCPT_WRITE32BITCOUNTERVALUE 403 +#define APCI1710_INCCPT_ENABLEINDEX 404 +#define APCI1710_INCCPT_DISABLEINDEX 405 +#define APCI1710_INCCPT_ENABLECOMPARELOGIC 406 +#define APCI1710_INCCPT_DISABLECOMPARELOGIC 407 +#define APCI1710_INCCPT_ENABLEFREQUENCYMEASUREMENT 408 +#define APCI1710_INCCPT_DISABLEFREQUENCYMEASUREMENT 409 + +/************ Main Functions *************/ +int i_APCI1710_InsnConfigINCCPT(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int * data); + +int i_APCI1710_InsnBitsINCCPT(struct comedi_device *dev, struct comedi_subdevice * s, + struct comedi_insn *insn, unsigned int * data); + +int i_APCI1710_InsnWriteINCCPT(struct comedi_device *dev, struct comedi_subdevice * s, + struct comedi_insn *insn, unsigned int * data); + +int i_APCI1710_InsnReadINCCPT(struct comedi_device *dev, struct comedi_subdevice * s, + struct comedi_insn *insn, unsigned int * data); + +/*********** Supplementary Functions********/ + +/* INSN CONFIG */ +int i_APCI1710_InitCounter(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_CounterRange, + unsigned char b_FirstCounterModus, + unsigned char b_FirstCounterOption, + unsigned char b_SecondCounterModus, + unsigned char b_SecondCounterOption); + +int i_APCI1710_CounterAutoTest(struct comedi_device *dev, unsigned char * pb_TestStatus); + +int i_APCI1710_InitIndex(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_ReferenceAction, + unsigned char b_IndexOperation, unsigned char b_AutoMode, + unsigned char b_InterruptEnable); + +int i_APCI1710_InitReference(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_ReferenceLevel); + +int i_APCI1710_InitExternalStrobe(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_ExternalStrobe, + unsigned char b_ExternalStrobeLevel); + +int i_APCI1710_InitCompareLogic(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned int ui_CompareValue); + +int i_APCI1710_InitFrequencyMeasurement(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PCIInputClock, + unsigned char b_TimingUnity, + unsigned int ul_TimingInterval, + unsigned int *pul_RealTimingInterval); + +/* INSN BITS */ +int i_APCI1710_ClearCounterValue(struct comedi_device *dev, unsigned char b_ModulNbr); + +int i_APCI1710_ClearAllCounterValue(struct comedi_device *dev); + +int i_APCI1710_SetInputFilter(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_PCIInputClock, + unsigned char b_Filter); + +int i_APCI1710_LatchCounter(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_LatchReg); + +int i_APCI1710_SetIndexAndReferenceSource(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_SourceSelection); + +int i_APCI1710_SetDigitalChlOn(struct comedi_device *dev, unsigned char b_ModulNbr); + +int i_APCI1710_SetDigitalChlOff(struct comedi_device *dev, unsigned char b_ModulNbr); + +/* INSN WRITE */ +int i_APCI1710_EnableLatchInterrupt(struct comedi_device *dev, unsigned char b_ModulNbr); + +int i_APCI1710_DisableLatchInterrupt(struct comedi_device *dev, unsigned char b_ModulNbr); + +int i_APCI1710_Write16BitCounterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_SelectedCounter, + unsigned int ui_WriteValue); + +int i_APCI1710_Write32BitCounterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned int ul_WriteValue); + +int i_APCI1710_EnableIndex(struct comedi_device *dev, unsigned char b_ModulNbr); + +int i_APCI1710_DisableIndex(struct comedi_device *dev, unsigned char b_ModulNbr); + +int i_APCI1710_EnableCompareLogic(struct comedi_device *dev, unsigned char b_ModulNbr); + +int i_APCI1710_DisableCompareLogic(struct comedi_device *dev, unsigned char b_ModulNbr); + +int i_APCI1710_EnableFrequencyMeasurement(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_InterruptEnable); + +int i_APCI1710_DisableFrequencyMeasurement(struct comedi_device *dev, + unsigned char b_ModulNbr); + +/* INSN READ */ +int i_APCI1710_ReadLatchRegisterStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_LatchReg, + unsigned char *pb_LatchStatus); + +int i_APCI1710_ReadLatchRegisterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_LatchReg, + unsigned int *pul_LatchValue); + +int i_APCI1710_Read16BitCounterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char b_SelectedCounter, + unsigned int *pui_CounterValue); + +int i_APCI1710_Read32BitCounterValue(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned int *pul_CounterValue); + +int i_APCI1710_GetIndexStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_IndexStatus); + +int i_APCI1710_GetReferenceStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_ReferenceStatus); + +int i_APCI1710_GetUASStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_UASStatus); + +int i_APCI1710_GetCBStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_CBStatus); + +int i_APCI1710_Get16BitCBStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_CBStatusCounter0, + unsigned char *pb_CBStatusCounter1); + +int i_APCI1710_GetUDStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_UDStatus); + +int i_APCI1710_GetInterruptUDLatchedStatus(struct comedi_device *dev, + unsigned char b_ModulNbr, unsigned char *pb_UDStatus); + +int i_APCI1710_ReadFrequencyMeasurement(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char *pb_Status, unsigned char *pb_UDStatus, + unsigned int *pul_ReadValue); diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Inp_cpt.c b/drivers/staging/comedi/drivers/addi-data/APCI1710_Inp_cpt.c new file mode 100644 index 00000000..3f9cfa20 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Inp_cpt.c @@ -0,0 +1,861 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : API APCI1710 | Compiler : gcc | + | Module name : Inp_CPT.C | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description : APCI-1710 pulse encoder module | + | | + | | + +-----------------------------------------------------------------------+ + | UPDATES | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + |----------|-----------|------------------------------------------------| + | 08/05/00 | Guinot C | - 0400/0228 All Function in RING 0 | + | | | available | + +-----------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ + +#include "APCI1710_Inp_cpt.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitPulseEncoder | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PulseEncoderNbr, | +| unsigned char_ b_InputLevelSelection, | +| unsigned char_ b_TriggerOutputAction, | +| ULONG_ ul_StartValue) | ++----------------------------------------------------------------------------+ +| Task : Configure the pulse encoder operating mode selected via| +| b_ModulNbr and b_PulseEncoderNbr. The pulse encoder | +| after each pulse decrement the counter value from 1. | +| | +| You must calling this function be for you call any | +| other function witch access of pulse encoders. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 3) | +| unsigned char_ b_PulseEncoderNbr : Pulse encoder selection | +| (0 to 3) | +| unsigned char_ b_InputLevelSelection : Input level selection | +| (0 or 1) | +| 0 : Set pulse encoder| +| count the the low| +| level pulse. | +| 1 : Set pulse encoder| +| count the the | +| high level pulse.| +| unsigned char_ b_TriggerOutputAction : Digital TRIGGER output | +| action | +| 0 : No action | +| 1 : Set the trigger | +| output to "1" | +| (high) after the | +| passage from 1 to| +| 0 from pulse | +| encoder. | +| 2 : Set the trigger | +| output to "0" | +| (low) after the | +| passage from 1 to| +| 0 from pulse | +| encoder | +| ULONG_ ul_StartValue : Pulse encoder start value| +| (1 to 4294967295) + b_ModulNbr =(unsigned char) CR_AREF(insn->chanspec); + b_PulseEncoderNbr =(unsigned char) data[0]; + b_InputLevelSelection =(unsigned char) data[1]; + b_TriggerOutputAction =(unsigned char) data[2]; + ul_StartValue =(unsigned int) data[3]; + | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module is not a pulse encoder module | +| -3: Pulse encoder selection is wrong | +| -4: Input level selection is wrong | +| -5: Digital TRIGGER output action selection is wrong | +| -6: Pulse encoder start value is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnConfigInitPulseEncoder(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_IntRegister; + + unsigned char b_ModulNbr; + unsigned char b_PulseEncoderNbr; + unsigned char b_InputLevelSelection; + unsigned char b_TriggerOutputAction; + unsigned int ul_StartValue; + + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_PulseEncoderNbr = (unsigned char) data[0]; + b_InputLevelSelection = (unsigned char) data[1]; + b_TriggerOutputAction = (unsigned char) data[2]; + ul_StartValue = (unsigned int) data[3]; + + i_ReturnValue = insn->n; + + /***********************************/ + /* Test the selected module number */ + /***********************************/ + + if (b_ModulNbr <= 3) { + /*************************/ + /* Test if pulse encoder */ + /*************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + APCI1710_PULSE_ENCODER) == + APCI1710_PULSE_ENCODER) { + /******************************************/ + /* Test the selected pulse encoder number */ + /******************************************/ + + if (b_PulseEncoderNbr <= 3) { + /************************/ + /* Test the input level */ + /************************/ + + if ((b_InputLevelSelection == 0) + || (b_InputLevelSelection == 1)) { + /*******************************************/ + /* Test the ouput TRIGGER action selection */ + /*******************************************/ + + if ((b_TriggerOutputAction <= 2) + || (b_PulseEncoderNbr > 0)) { + if (ul_StartValue > 1) { + + dw_IntRegister = + inl(devpriv-> + s_BoardInfos. + ui_Address + + 20 + + (64 * b_ModulNbr)); + + /***********************/ + /* Set the start value */ + /***********************/ + + outl(ul_StartValue, + devpriv-> + s_BoardInfos. + ui_Address + + (b_PulseEncoderNbr + * 4) + + (64 * b_ModulNbr)); + + /***********************/ + /* Set the input level */ + /***********************/ + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister = + (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister & + (0xFFFFFFFFUL - + (1UL << (8 + b_PulseEncoderNbr)))) | ((1UL & (~b_InputLevelSelection)) << (8 + b_PulseEncoderNbr)); + + /*******************************/ + /* Test if output trigger used */ + /*******************************/ + + if ((b_TriggerOutputAction > 0) && (b_PulseEncoderNbr > 1)) { + /****************************/ + /* Enable the output action */ + /****************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + | (1UL + << (4 + b_PulseEncoderNbr)); + + /*********************************/ + /* Set the output TRIGGER action */ + /*********************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + = + (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + & + (0xFFFFFFFFUL + - + (1UL << (12 + b_PulseEncoderNbr)))) | ((1UL & (b_TriggerOutputAction - 1)) << (12 + b_PulseEncoderNbr)); + } else { + /*****************************/ + /* Disable the output action */ + /*****************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + & + (0xFFFFFFFFUL + - + (1UL << (4 + b_PulseEncoderNbr))); + } + + /*************************/ + /* Set the configuration */ + /*************************/ + + outl(devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister, + devpriv-> + s_BoardInfos. + ui_Address + + 20 + + (64 * b_ModulNbr)); + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + s_PulseEncoderInfo + [b_PulseEncoderNbr]. + b_PulseEncoderInit + = 1; + } else { + /**************************************/ + /* Pulse encoder start value is wrong */ + /**************************************/ + + DPRINTK("Pulse encoder start value is wrong\n"); + i_ReturnValue = -6; + } + } else { + /****************************************************/ + /* Digital TRIGGER output action selection is wrong */ + /****************************************************/ + + DPRINTK("Digital TRIGGER output action selection is wrong\n"); + i_ReturnValue = -5; + } + } else { + /**********************************/ + /* Input level selection is wrong */ + /**********************************/ + + DPRINTK("Input level selection is wrong\n"); + i_ReturnValue = -4; + } + } else { + /************************************/ + /* Pulse encoder selection is wrong */ + /************************************/ + + DPRINTK("Pulse encoder selection is wrong\n"); + i_ReturnValue = -3; + } + } else { + /********************************************/ + /* The module is not a pulse encoder module */ + /********************************************/ + + DPRINTK("The module is not a pulse encoder module\n"); + i_ReturnValue = -2; + } + } else { + /********************************************/ + /* The module is not a pulse encoder module */ + /********************************************/ + + DPRINTK("The module is not a pulse encoder module\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_EnablePulseEncoder | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PulseEncoderNbr, | +| unsigned char_ b_CycleSelection, | +| unsigned char_ b_InterruptHandling) | ++----------------------------------------------------------------------------+ +| Task : Enableor disable the selected pulse encoder (b_PulseEncoderNbr) | +| from selected module (b_ModulNbr). Each input pulse | +| decrement the pulse encoder counter value from 1. | +| If you enabled the interrupt (b_InterruptHandling), a | +| interrupt is generated when the pulse encoder has run | +| down. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 3) | +| unsigned char_ b_PulseEncoderNbr : Pulse encoder selection | +| (0 to 3) | +| unsigned char_ b_CycleSelection : APCI1710_CONTINUOUS: | +| Each time the | +| counting value is set| +| on "0", the pulse | +| encoder load the | +| start value after | +| the next pulse. | +| APCI1710_SINGLE: | +| If the counter is set| +| on "0", the pulse | +| encoder is stopped. | +| unsigned char_ b_InterruptHandling : Interrupts can be | +| generated, when the pulse| +| encoder has run down. | +| With this parameter the | +| user decides if | +| interrupts are used or | +| not. | +| APCI1710_ENABLE: | +| Interrupts are enabled | +| APCI1710_DISABLE: | +| Interrupts are disabled + + b_ModulNbr =(unsigned char) CR_AREF(insn->chanspec); + b_Action =(unsigned char) data[0]; + b_PulseEncoderNbr =(unsigned char) data[1]; + b_CycleSelection =(unsigned char) data[2]; + b_InterruptHandling =(unsigned char) data[3];| ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection is wrong | +| -3: Pulse encoder selection is wrong | +| -4: Pulse encoder not initialised. | +| See function "i_APCI1710_InitPulseEncoder" | +| -5: Cycle selection mode is wrong | +| -6: Interrupt handling mode is wrong | +| -7: Interrupt routine not installed. | +| See function "i_APCI1710_SetBoardIntRoutineX" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnWriteEnableDisablePulseEncoder(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned char b_ModulNbr; + unsigned char b_PulseEncoderNbr; + unsigned char b_CycleSelection; + unsigned char b_InterruptHandling; + unsigned char b_Action; + + i_ReturnValue = insn->n; + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_Action = (unsigned char) data[0]; + b_PulseEncoderNbr = (unsigned char) data[1]; + b_CycleSelection = (unsigned char) data[2]; + b_InterruptHandling = (unsigned char) data[3]; + + /***********************************/ + /* Test the selected module number */ + /***********************************/ + + if (b_ModulNbr <= 3) { + /******************************************/ + /* Test the selected pulse encoder number */ + /******************************************/ + + if (b_PulseEncoderNbr <= 3) { + /*************************************/ + /* Test if pulse encoder initialised */ + /*************************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + s_PulseEncoderInfo[b_PulseEncoderNbr]. + b_PulseEncoderInit == 1) { + switch (b_Action) { + + case APCI1710_ENABLE: + /****************************/ + /* Test the cycle selection */ + /****************************/ + + if (b_CycleSelection == + APCI1710_CONTINUOUS + || b_CycleSelection == + APCI1710_SINGLE) { + /*******************************/ + /* Test the interrupt handling */ + /*******************************/ + + if (b_InterruptHandling == + APCI1710_ENABLE + || b_InterruptHandling + == APCI1710_DISABLE) { + /******************************/ + /* Test if interrupt not used */ + /******************************/ + + if (b_InterruptHandling + == + APCI1710_DISABLE) + { + /*************************/ + /* Disable the interrupt */ + /*************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + & + (0xFFFFFFFFUL + - + (1UL << b_PulseEncoderNbr)); + } else { + + /************************/ + /* Enable the interrupt */ + /************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister + | (1UL + << + b_PulseEncoderNbr); + devpriv->tsk_Current = current; /* Save the current process task structure */ + + } + + if (i_ReturnValue >= 0) { + /***********************************/ + /* Enable or disable the interrupt */ + /***********************************/ + + outl(devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_SetRegister, + devpriv-> + s_BoardInfos. + ui_Address + + 20 + + (64 * b_ModulNbr)); + + /****************************/ + /* Enable the pulse encoder */ + /****************************/ + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_ControlRegister + = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_ControlRegister + | (1UL + << + b_PulseEncoderNbr); + + /**********************/ + /* Set the cycle mode */ + /**********************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_ControlRegister + = + (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_ControlRegister + & + (0xFFFFFFFFUL + - + (1 << (b_PulseEncoderNbr + 4)))) | ((b_CycleSelection & 1UL) << (4 + b_PulseEncoderNbr)); + + /****************************/ + /* Enable the pulse encoder */ + /****************************/ + + outl(devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_ControlRegister, + devpriv-> + s_BoardInfos. + ui_Address + + 16 + + (64 * b_ModulNbr)); + } + } else { + /************************************/ + /* Interrupt handling mode is wrong */ + /************************************/ + + DPRINTK("Interrupt handling mode is wrong\n"); + i_ReturnValue = -6; + } + } else { + /*********************************/ + /* Cycle selection mode is wrong */ + /*********************************/ + + DPRINTK("Cycle selection mode is wrong\n"); + i_ReturnValue = -5; + } + break; + + case APCI1710_DISABLE: + devpriv->s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_ControlRegister = + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_ControlRegister & + (0xFFFFFFFFUL - + (1UL << b_PulseEncoderNbr)); + + /*****************************/ + /* Disable the pulse encoder */ + /*****************************/ + + outl(devpriv->s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_ControlRegister, + devpriv->s_BoardInfos. + ui_Address + 16 + + (64 * b_ModulNbr)); + + break; + } /* switch End */ + + } else { + /*********************************/ + /* Pulse encoder not initialised */ + /*********************************/ + + DPRINTK("Pulse encoder not initialised\n"); + i_ReturnValue = -4; + } + } else { + /************************************/ + /* Pulse encoder selection is wrong */ + /************************************/ + + DPRINTK("Pulse encoder selection is wrong\n"); + i_ReturnValue = -3; + } + } else { + /*****************************/ + /* Module selection is wrong */ + /*****************************/ + + DPRINTK("Module selection is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadPulseEncoderStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PulseEncoderNbr, | +| unsigned char *_ pb_Status) | ++----------------------------------------------------------------------------+ +| Task APCI1710_PULSEENCODER_READ : Reads the pulse encoder status + and valuefrom selected pulse | +| encoder (b_PulseEncoderNbr) from selected module | +| (b_ModulNbr). | ++----------------------------------------------------------------------------+ + unsigned char b_Type; data[0] + APCI1710_PULSEENCODER_WRITE + Writes a 32-bit value (ul_WriteValue) into the selected| +| pulse encoder (b_PulseEncoderNbr) from selected module | +| (b_ModulNbr). This operation set the new start pulse | +| encoder value. + APCI1710_PULSEENCODER_READ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| CRAREF() unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 3) | +| data[1] unsigned char_ b_PulseEncoderNbr : Pulse encoder selection | +| (0 to 3) + APCI1710_PULSEENCODER_WRITE + data[2] ULONG_ ul_WriteValue : 32-bit value to be | +| written | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_Status : Pulse encoder status. | +| 0 : No overflow occur| +| 1 : Overflow occur + PULONG_ pul_ReadValue : Pulse encoder value | | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection is wrong | +| -3: Pulse encoder selection is wrong | +| -4: Pulse encoder not initialised. | +| See function "i_APCI1710_InitPulseEncoder" | ++----------------------------------------------------------------------------+ +*/ + +/*_INT_ i_APCI1710_ReadPulseEncoderStatus (unsigned char_ b_BoardHandle, + unsigned char_ b_ModulNbr, + unsigned char_ b_PulseEncoderNbr, + + unsigned char *_ pb_Status) + */ +int i_APCI1710_InsnBitsReadWritePulseEncoder(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusRegister; + unsigned char b_ModulNbr; + unsigned char b_PulseEncoderNbr; + unsigned char *pb_Status; + unsigned char b_Type; + unsigned int *pul_ReadValue; + unsigned int ul_WriteValue; + + i_ReturnValue = insn->n; + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_Type = (unsigned char) data[0]; + b_PulseEncoderNbr = (unsigned char) data[1]; + pb_Status = (unsigned char *) &data[0]; + pul_ReadValue = (unsigned int *) &data[1]; + + /***********************************/ + /* Test the selected module number */ + /***********************************/ + + if (b_ModulNbr <= 3) { + /******************************************/ + /* Test the selected pulse encoder number */ + /******************************************/ + + if (b_PulseEncoderNbr <= 3) { + /*************************************/ + /* Test if pulse encoder initialised */ + /*************************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + s_PulseEncoderInfo[b_PulseEncoderNbr]. + b_PulseEncoderInit == 1) { + + switch (b_Type) { + case APCI1710_PULSEENCODER_READ: + /****************************/ + /* Read the status register */ + /****************************/ + + dw_StatusRegister = + inl(devpriv->s_BoardInfos. + ui_Address + 16 + + (64 * b_ModulNbr)); + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_StatusRegister = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_StatusRegister | + dw_StatusRegister; + + *pb_Status = + (unsigned char) (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_StatusRegister >> (1 + + b_PulseEncoderNbr)) & 1; + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_StatusRegister = + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_PulseEncoderModuleInfo. + dw_StatusRegister & + (0xFFFFFFFFUL - (1 << (1 + + b_PulseEncoderNbr))); + + /******************/ + /* Read the value */ + /******************/ + + *pul_ReadValue = + inl(devpriv->s_BoardInfos. + ui_Address + + (4 * b_PulseEncoderNbr) + + (64 * b_ModulNbr)); + break; + + case APCI1710_PULSEENCODER_WRITE: + ul_WriteValue = (unsigned int) data[2]; + /*******************/ + /* Write the value */ + /*******************/ + + outl(ul_WriteValue, + devpriv->s_BoardInfos. + ui_Address + + (4 * b_PulseEncoderNbr) + + (64 * b_ModulNbr)); + + } /* end of switch */ + } else { + /*********************************/ + /* Pulse encoder not initialised */ + /*********************************/ + + DPRINTK("Pulse encoder not initialised\n"); + i_ReturnValue = -4; + } + } else { + /************************************/ + /* Pulse encoder selection is wrong */ + /************************************/ + + DPRINTK("Pulse encoder selection is wrong\n"); + i_ReturnValue = -3; + } + } else { + /*****************************/ + /* Module selection is wrong */ + /*****************************/ + + DPRINTK("Module selection is wrong\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +int i_APCI1710_InsnReadInterruptPulseEncoder(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + + data[0] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].b_OldModuleMask; + data[1] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldInterruptMask; + data[2] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldCounterLatchValue; + + /***************************/ + /* Increment the read FIFO */ + /***************************/ + + devpriv->s_InterruptParameters. + ui_Read = (devpriv-> + s_InterruptParameters.ui_Read + 1) % APCI1710_SAVE_INTERRUPT; + + return insn->n; + +} diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Inp_cpt.h b/drivers/staging/comedi/drivers/addi-data/APCI1710_Inp_cpt.h new file mode 100644 index 00000000..31fbb0be --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Inp_cpt.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define APCI1710_SINGLE 0 +#define APCI1710_CONTINUOUS 1 + +#define APCI1710_PULSEENCODER_READ 0 +#define APCI1710_PULSEENCODER_WRITE 1 + +int i_APCI1710_InsnConfigInitPulseEncoder(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnWriteEnableDisablePulseEncoder(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +/* + * READ PULSE ENCODER FUNCTIONS + */ +int i_APCI1710_InsnReadInterruptPulseEncoder(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +/* + * WRITE PULSE ENCODER FUNCTIONS + */ +int i_APCI1710_InsnBitsReadWritePulseEncoder(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Pwm.c b/drivers/staging/comedi/drivers/addi-data/APCI1710_Pwm.c new file mode 100644 index 00000000..8883e666 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Pwm.c @@ -0,0 +1,3588 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : API APCI1710 | Compiler : gcc | + | Module name : PWM.C | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description : APCI-1710 Wulse wide modulation module | + | | + | | + +-----------------------------------------------------------------------+ + | UPDATES | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +-----------------------------------------------------------------------+ + | 08/05/00 | Guinot C | - 0400/0228 All Function in RING 0 | + | | | available | + +-----------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ + +#include "APCI1710_Pwm.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name :INT i_APCI1710_InsnConfigPWM(struct comedi_device *dev, +struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Pwm Init and Get Pwm Initialisation | ++----------------------------------------------------------------------------+ +| Input Parameters : ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnConfigPWM(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned char b_ConfigType; + int i_ReturnValue = 0; + b_ConfigType = CR_CHAN(insn->chanspec); + + switch (b_ConfigType) { + case APCI1710_PWM_INIT: + i_ReturnValue = i_APCI1710_InitPWM(dev, (unsigned char) CR_AREF(insn->chanspec), /* b_ModulNbr */ + (unsigned char) data[0], /* b_PWM */ + (unsigned char) data[1], /* b_ClockSelection */ + (unsigned char) data[2], /* b_TimingUnit */ + (unsigned int) data[3], /* ul_LowTiming */ + (unsigned int) data[4], /* ul_HighTiming */ + (unsigned int *) &data[0], /* pul_RealLowTiming */ + (unsigned int *) &data[1] /* pul_RealHighTiming */ + ); + break; + + case APCI1710_PWM_GETINITDATA: + i_ReturnValue = i_APCI1710_GetPWMInitialisation(dev, (unsigned char) CR_AREF(insn->chanspec), /* b_ModulNbr */ + (unsigned char) data[0], /* b_PWM */ + (unsigned char *) &data[0], /* pb_TimingUnit */ + (unsigned int *) &data[1], /* pul_LowTiming */ + (unsigned int *) &data[2], /* pul_HighTiming */ + (unsigned char *) &data[3], /* pb_StartLevel */ + (unsigned char *) &data[4], /* pb_StopMode */ + (unsigned char *) &data[5], /* pb_StopLevel */ + (unsigned char *) &data[6], /* pb_ExternGate */ + (unsigned char *) &data[7], /* pb_InterruptEnable */ + (unsigned char *) &data[8] /* pb_Enable */ + ); + break; + + default: + printk(" Config Parameter Wrong\n"); + } + + if (i_ReturnValue >= 0) + i_ReturnValue = insn->n; + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitPWM | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PWM, | +| unsigned char_ b_ClockSelection, | +| unsigned char_ b_TimingUnit, | +| ULONG_ ul_LowTiming, | +| ULONG_ ul_HighTiming, | +| PULONG_ pul_RealLowTiming, | +| PULONG_ pul_RealHighTiming) | ++----------------------------------------------------------------------------+ +| Task : Configure the selected PWM (b_PWM) from selected module| +| (b_ModulNbr). The ul_LowTiming, ul_HighTiming and | +| ul_TimingUnit determine the low/high timing base for | +| the period. pul_RealLowTiming, pul_RealHighTiming | +| return the real timing value. | +| You must calling this function be for you call any | +| other function witch access of the PWM. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure| +| (0 to 3) | +| unsigned char_ b_PWM : Selected PWM (0 or 1). | +| unsigned char_ b_ClockSelection : Selection from PCI bus | +| clock | +| - APCI1710_30MHZ : | +| The PC have a 30 MHz | +| PCI bus clock | +| - APCI1710_33MHZ : | +| The PC have a 33 MHz | +| PCI bus clock | +| - APCI1710_40MHZ | +| The APCI-1710 have a | +| integrated 40Mhz | +| quartz. | +| unsigned char_ b_TimingUnit : Base timing Unit (0 to 4) | +| 0 : ns | +| 1 : æs | +| 2 : ms | +| 3 : s | +| 4 : mn | +| ULONG_ ul_LowTiming : Low base timing value. | +| ULONG_ ul_HighTiming : High base timing value. | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_RealLowTiming : Real low base timing | +| value. | +| PULONG_ pul_RealHighTiming : Real high base timing | +| value. | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a PWM module | +| -4: PWM selection is wrong | +| -5: The selected input clock is wrong | +| -6: Timing Unit selection is wrong | +| -7: Low base timing selection is wrong | +| -8: High base timing selection is wrong | +| -9: You can not used the 40MHz clock selection with | +| this board | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InitPWM(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PWM, + unsigned char b_ClockSelection, + unsigned char b_TimingUnit, + unsigned int ul_LowTiming, + unsigned int ul_HighTiming, + unsigned int *pul_RealLowTiming, unsigned int *pul_RealHighTiming) +{ + int i_ReturnValue = 0; + unsigned int ul_LowTimerValue = 0; + unsigned int ul_HighTimerValue = 0; + unsigned int dw_Command; + double d_RealLowTiming = 0; + double d_RealHighTiming = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***************/ + /* Test if PWM */ + /***************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_PWM) { + /**************************/ + /* Test the PWM selection */ + /**************************/ + + if (b_PWM <= 1) { + /******************/ + /* Test the clock */ + /******************/ + + if ((b_ClockSelection == APCI1710_30MHZ) || + (b_ClockSelection == APCI1710_33MHZ) || + (b_ClockSelection == APCI1710_40MHZ)) { + /************************/ + /* Test the timing unit */ + /************************/ + + if (b_TimingUnit <= 4) { + /*********************************/ + /* Test the low timing selection */ + /*********************************/ + + if (((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 0) + && (ul_LowTiming + >= 266) + && (ul_LowTiming + <= + 0xFFFFFFFFUL)) + || ((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 1) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 571230650UL)) + || ((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 2) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 571230UL)) + || ((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 3) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 571UL)) + || ((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 4) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= 9UL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 0) + && (ul_LowTiming + >= 242) + && (ul_LowTiming + <= + 0xFFFFFFFFUL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 1) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 519691043UL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 2) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 519691UL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 3) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 520UL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 4) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= 8UL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 0) + && (ul_LowTiming + >= 200) + && (ul_LowTiming + <= + 0xFFFFFFFFUL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 1) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 429496729UL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 2) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 429496UL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 3) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 429UL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 4) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 7UL))) { + /**********************************/ + /* Test the High timing selection */ + /**********************************/ + + if (((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 0) && (ul_HighTiming >= 266) && (ul_HighTiming <= 0xFFFFFFFFUL)) || ((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 1) && (ul_HighTiming >= 1) && (ul_HighTiming <= 571230650UL)) || ((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 2) && (ul_HighTiming >= 1) && (ul_HighTiming <= 571230UL)) || ((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 3) && (ul_HighTiming >= 1) && (ul_HighTiming <= 571UL)) || ((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 4) && (ul_HighTiming >= 1) && (ul_HighTiming <= 9UL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 0) && (ul_HighTiming >= 242) && (ul_HighTiming <= 0xFFFFFFFFUL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 1) && (ul_HighTiming >= 1) && (ul_HighTiming <= 519691043UL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 2) && (ul_HighTiming >= 1) && (ul_HighTiming <= 519691UL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 3) && (ul_HighTiming >= 1) && (ul_HighTiming <= 520UL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 4) && (ul_HighTiming >= 1) && (ul_HighTiming <= 8UL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 0) && (ul_HighTiming >= 200) && (ul_HighTiming <= 0xFFFFFFFFUL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 1) && (ul_HighTiming >= 1) && (ul_HighTiming <= 429496729UL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 2) && (ul_HighTiming >= 1) && (ul_HighTiming <= 429496UL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 3) && (ul_HighTiming >= 1) && (ul_HighTiming <= 429UL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 4) && (ul_HighTiming >= 1) && (ul_HighTiming <= 7UL))) { + /**************************/ + /* Test the board version */ + /**************************/ + + if (((b_ClockSelection == APCI1710_40MHZ) && (devpriv->s_BoardInfos.b_BoardVersion > 0)) || (b_ClockSelection != APCI1710_40MHZ)) { + + /************************************/ + /* Calculate the low division fator */ + /************************************/ + + fpu_begin + (); + + switch (b_TimingUnit) { + /******/ + /* ns */ + /******/ + + case 0: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + (unsigned int) + (ul_LowTiming + * + (0.00025 * b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_LowTiming * (0.00025 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (0.00025 * (double)b_ClockSelection)); + d_RealLowTiming + = + (double) + ul_LowTimerValue + / + (0.00025 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_LowTimerValue / (0.00025 * (double)b_ClockSelection)) >= (double)((double)*pul_RealLowTiming + 0.5)) { + *pul_RealLowTiming + = + *pul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* æs */ + /******/ + + case 1: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + (unsigned int) + (ul_LowTiming + * + (0.25 * b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_LowTiming * (0.25 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (0.25 * (double)b_ClockSelection)); + d_RealLowTiming + = + (double) + ul_LowTimerValue + / + ( + (double) + 0.25 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_LowTimerValue / (0.25 * (double)b_ClockSelection)) >= (double)((double)*pul_RealLowTiming + 0.5)) { + *pul_RealLowTiming + = + *pul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* ms */ + /******/ + + case 2: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + ul_LowTiming + * + (250.0 + * + b_ClockSelection); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_LowTiming * (250.0 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (250.0 * (double)b_ClockSelection)); + d_RealLowTiming + = + (double) + ul_LowTimerValue + / + (250.0 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_LowTimerValue / (250.0 * (double)b_ClockSelection)) >= (double)((double)*pul_RealLowTiming + 0.5)) { + *pul_RealLowTiming + = + *pul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + + /*****/ + /* s */ + /*****/ + + case 3: + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + (unsigned int) + (ul_LowTiming + * + (250000.0 + * + b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_LowTiming * (250000.0 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)); + d_RealLowTiming + = + (double) + ul_LowTimerValue + / + (250000.0 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_LowTimerValue / (250000.0 * (double)b_ClockSelection)) >= (double)((double)*pul_RealLowTiming + 0.5)) { + *pul_RealLowTiming + = + *pul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* mn */ + /******/ + + case 4: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + (unsigned int) + ( + (ul_LowTiming + * + 60) + * + (250000.0 + * + b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)(ul_LowTiming * 60.0) * (250000.0 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)) + / + 60; + d_RealLowTiming + = + ( + (double) + ul_LowTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)) + / + 60.0; + + if ((double)(((double)ul_LowTimerValue / (250000.0 * (double)b_ClockSelection)) / 60.0) >= (double)((double)*pul_RealLowTiming + 0.5)) { + *pul_RealLowTiming + = + *pul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + } + + /*************************************/ + /* Calculate the high division fator */ + /*************************************/ + + switch (b_TimingUnit) { + /******/ + /* ns */ + /******/ + + case 0: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + (unsigned int) + (ul_HighTiming + * + (0.00025 * b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_HighTiming * (0.00025 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (0.00025 * (double)b_ClockSelection)); + d_RealHighTiming + = + (double) + ul_HighTimerValue + / + (0.00025 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_HighTimerValue / (0.00025 * (double)b_ClockSelection)) >= (double)((double)*pul_RealHighTiming + 0.5)) { + *pul_RealHighTiming + = + *pul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* æs */ + /******/ + + case 1: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + (unsigned int) + (ul_HighTiming + * + (0.25 * b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_HighTiming * (0.25 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (0.25 * (double)b_ClockSelection)); + d_RealHighTiming + = + (double) + ul_HighTimerValue + / + ( + (double) + 0.25 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_HighTimerValue / (0.25 * (double)b_ClockSelection)) >= (double)((double)*pul_RealHighTiming + 0.5)) { + *pul_RealHighTiming + = + *pul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* ms */ + /******/ + + case 2: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + ul_HighTiming + * + (250.0 + * + b_ClockSelection); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_HighTiming * (250.0 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (250.0 * (double)b_ClockSelection)); + d_RealHighTiming + = + (double) + ul_HighTimerValue + / + (250.0 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_HighTimerValue / (250.0 * (double)b_ClockSelection)) >= (double)((double)*pul_RealHighTiming + 0.5)) { + *pul_RealHighTiming + = + *pul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + + /*****/ + /* s */ + /*****/ + + case 3: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + (unsigned int) + (ul_HighTiming + * + (250000.0 + * + b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_HighTiming * (250000.0 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)); + d_RealHighTiming + = + (double) + ul_HighTimerValue + / + (250000.0 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_HighTimerValue / (250000.0 * (double)b_ClockSelection)) >= (double)((double)*pul_RealHighTiming + 0.5)) { + *pul_RealHighTiming + = + *pul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* mn */ + /******/ + + case 4: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + (unsigned int) + ( + (ul_HighTiming + * + 60) + * + (250000.0 + * + b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)(ul_HighTiming * 60.0) * (250000.0 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + *pul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)) + / + 60; + d_RealHighTiming + = + ( + (double) + ul_HighTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)) + / + 60.0; + + if ((double)(((double)ul_HighTimerValue / (250000.0 * (double)b_ClockSelection)) / 60.0) >= (double)((double)*pul_RealHighTiming + 0.5)) { + *pul_RealHighTiming + = + *pul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + } + + fpu_end(); + /****************************/ + /* Save the clock selection */ + /****************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + b_ClockSelection + = + b_ClockSelection; + + /************************/ + /* Save the timing unit */ + /************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + b_TimingUnit + = + b_TimingUnit; + + /****************************/ + /* Save the low base timing */ + /****************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + d_LowTiming + = + d_RealLowTiming; + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + ul_RealLowTiming + = + *pul_RealLowTiming; + + /****************************/ + /* Save the high base timing */ + /****************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + d_HighTiming + = + d_RealHighTiming; + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + ul_RealHighTiming + = + *pul_RealHighTiming; + + /************************/ + /* Write the low timing */ + /************************/ + + outl(ul_LowTimerValue, devpriv->s_BoardInfos.ui_Address + 0 + (20 * b_PWM) + (64 * b_ModulNbr)); + + /*************************/ + /* Write the high timing */ + /*************************/ + + outl(ul_HighTimerValue, devpriv->s_BoardInfos.ui_Address + 4 + (20 * b_PWM) + (64 * b_ModulNbr)); + + /***************************/ + /* Set the clock selection */ + /***************************/ + + dw_Command + = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + 8 + + + (20 * b_PWM) + (64 * b_ModulNbr)); + + dw_Command + = + dw_Command + & + 0x7F; + + if (b_ClockSelection == APCI1710_40MHZ) { + dw_Command + = + dw_Command + | + 0x80; + } + + /***************************/ + /* Set the clock selection */ + /***************************/ + + outl(dw_Command, devpriv->s_BoardInfos.ui_Address + 8 + (20 * b_PWM) + (64 * b_ModulNbr)); + + /*************/ + /* PWM init. */ + /*************/ + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + b_PWMInit + = + 1; + } else { + /***************************************************/ + /* You can not used the 40MHz clock selection with */ + /* this board */ + /***************************************************/ + DPRINTK("You can not used the 40MHz clock selection with this board\n"); + i_ReturnValue + = + -9; + } + } else { + /***************************************/ + /* High base timing selection is wrong */ + /***************************************/ + DPRINTK("High base timing selection is wrong\n"); + i_ReturnValue = + -8; + } + } else { + /**************************************/ + /* Low base timing selection is wrong */ + /**************************************/ + DPRINTK("Low base timing selection is wrong\n"); + i_ReturnValue = -7; + } + } /* if ((b_TimingUnit >= 0) && (b_TimingUnit <= 4)) */ + else { + /**********************************/ + /* Timing unit selection is wrong */ + /**********************************/ + DPRINTK("Timing unit selection is wrong\n"); + i_ReturnValue = -6; + } /* if ((b_TimingUnit >= 0) && (b_TimingUnit <= 4)) */ + } /* if ((b_ClockSelection == APCI1710_30MHZ) || (b_ClockSelection == APCI1710_33MHZ) || (b_ClockSelection == APCI1710_40MHZ)) */ + else { + /*******************************/ + /* The selected clock is wrong */ + /*******************************/ + DPRINTK("The selected clock is wrong\n"); + i_ReturnValue = -5; + } /* if ((b_ClockSelection == APCI1710_30MHZ) || (b_ClockSelection == APCI1710_33MHZ) || (b_ClockSelection == APCI1710_40MHZ)) */ + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + else { + /******************************/ + /* Tor PWM selection is wrong */ + /******************************/ + DPRINTK("Tor PWM selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + } else { + /**********************************/ + /* The module is not a PWM module */ + /**********************************/ + DPRINTK("The module is not a PWM module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetPWMInitialisation | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PWM, | +| unsigned char *_ pb_TimingUnit, | +| PULONG_ pul_LowTiming, | +| PULONG_ pul_HighTiming, | +| unsigned char *_ pb_StartLevel, | +| unsigned char *_ pb_StopMode, | +| unsigned char *_ pb_StopLevel, | +| unsigned char *_ pb_ExternGate, | +| unsigned char *_ pb_InterruptEnable, | +| unsigned char *_ pb_Enable) | ++----------------------------------------------------------------------------+ +| Task : Return the PWM (b_PWM) initialisation from selected | +| module (b_ModulNbr). You must calling the | +| "i_APCI1710_InitPWM" function be for you call this | +| function. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3) | +| unsigned char_ b_PWM : Selected PWM (0 or 1) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_TimingUnit : Base timing Unit (0 to 4) | +| 0 : ns | +| 1 : æs | +| 2 : ms | +| 3 : s | +| 4 : mn | +| PULONG_ pul_LowTiming : Low base timing value. | +| PULONG_ pul_HighTiming : High base timing value. | +| unsigned char *_ pb_StartLevel : Start period level | +| selection | +| 0 : The period start | +| with a low level | +| 1 : The period start | +| with a high level| +| unsigned char *_ pb_StopMode : Stop mode selection | +| 0 : The PWM is stopped | +| directly after the | +| "i_APCI1710_DisablePWM"| +| function and break the| +| last period | +| 1 : After the | +| "i_APCI1710_DisablePWM"| +| function the PWM is | +| stopped at the end | +| from last period cycle| +| unsigned char *_ pb_StopLevel : Stop PWM level selection | +| 0 : The output signal | +| keep the level after| +| the | +| "i_APCI1710_DisablePWM"| +| function | +| 1 : The output signal is| +| set to low after the| +| "i_APCI1710_DisablePWM"| +| function | +| 2 : The output signal is| +| set to high after | +| the | +| "i_APCI1710_DisablePWM"| +| function | +| unsigned char *_ pb_ExternGate : Extern gate action | +| selection | +| 0 : Extern gate signal | +| not used. | +| 1 : Extern gate signal | +| used. | +| unsigned char *_ pb_InterruptEnable : Enable or disable the PWM | +| interrupt. | +| - APCI1710_ENABLE : | +| Enable the PWM interrupt| +| A interrupt occur after | +| each period | +| - APCI1710_DISABLE : | +| Disable the PWM | +| interrupt | +| unsigned char *_ pb_Enable : Indicate if the PWM is | +| enabled or no | +| 0 : PWM not enabled | +| 1 : PWM enabled | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a PWM module | +| -4: PWM selection is wrong | +| -5: PWM not initialised see function | +| "i_APCI1710_InitPWM" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_GetPWMInitialisation(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PWM, + unsigned char *pb_TimingUnit, + unsigned int *pul_LowTiming, + unsigned int *pul_HighTiming, + unsigned char *pb_StartLevel, + unsigned char *pb_StopMode, + unsigned char *pb_StopLevel, + unsigned char *pb_ExternGate, unsigned char *pb_InterruptEnable, unsigned char *pb_Enable) +{ + int i_ReturnValue = 0; + unsigned int dw_Status; + unsigned int dw_Command; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***************/ + /* Test if PWM */ + /***************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_PWM) { + /**************************/ + /* Test the PWM selection */ + /**************************/ + + if (b_PWM <= 1) { + /***************************/ + /* Test if PWM initialised */ + /***************************/ + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 12 + (20 * b_PWM) + + (64 * b_ModulNbr)); + + if (dw_Status & 0x10) { + /***********************/ + /* Read the low timing */ + /***********************/ + + *pul_LowTiming = + inl(devpriv->s_BoardInfos. + ui_Address + 0 + (20 * b_PWM) + + (64 * b_ModulNbr)); + + /************************/ + /* Read the high timing */ + /************************/ + + *pul_HighTiming = + inl(devpriv->s_BoardInfos. + ui_Address + 4 + (20 * b_PWM) + + (64 * b_ModulNbr)); + + /********************/ + /* Read the command */ + /********************/ + + dw_Command = inl(devpriv->s_BoardInfos. + ui_Address + 8 + (20 * b_PWM) + + (64 * b_ModulNbr)); + + *pb_StartLevel = + (unsigned char) ((dw_Command >> 5) & 1); + *pb_StopMode = + (unsigned char) ((dw_Command >> 0) & 1); + *pb_StopLevel = + (unsigned char) ((dw_Command >> 1) & 1); + *pb_ExternGate = + (unsigned char) ((dw_Command >> 4) & 1); + *pb_InterruptEnable = + (unsigned char) ((dw_Command >> 3) & 1); + + if (*pb_StopLevel) { + *pb_StopLevel = + *pb_StopLevel + + (unsigned char) ((dw_Command >> + 2) & 1); + } + + /********************/ + /* Read the command */ + /********************/ + + dw_Command = inl(devpriv->s_BoardInfos. + ui_Address + 8 + (20 * b_PWM) + + (64 * b_ModulNbr)); + + *pb_Enable = + (unsigned char) ((dw_Command >> 0) & 1); + + *pb_TimingUnit = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo[b_PWM].b_TimingUnit; + } /* if (dw_Status & 0x10) */ + else { + /***********************/ + /* PWM not initialised */ + /***********************/ + DPRINTK("PWM not initialised\n"); + i_ReturnValue = -5; + } /* if (dw_Status & 0x10) */ + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + else { + /******************************/ + /* Tor PWM selection is wrong */ + /******************************/ + DPRINTK("Tor PWM selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + } else { + /**********************************/ + /* The module is not a PWM module */ + /**********************************/ + DPRINTK("The module is not a PWM module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name :INT i_APCI1710_InsnWritePWM(struct comedi_device *dev, +struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Pwm Enable Disable and Set New Timing | ++----------------------------------------------------------------------------+ +| Input Parameters : ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnWritePWM(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned char b_WriteType; + int i_ReturnValue = 0; + b_WriteType = CR_CHAN(insn->chanspec); + + switch (b_WriteType) { + case APCI1710_PWM_ENABLE: + i_ReturnValue = i_APCI1710_EnablePWM(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned char) data[0], + (unsigned char) data[1], + (unsigned char) data[2], + (unsigned char) data[3], (unsigned char) data[4], (unsigned char) data[5]); + break; + + case APCI1710_PWM_DISABLE: + i_ReturnValue = i_APCI1710_DisablePWM(dev, + (unsigned char) CR_AREF(insn->chanspec), (unsigned char) data[0]); + break; + + case APCI1710_PWM_NEWTIMING: + i_ReturnValue = i_APCI1710_SetNewPWMTiming(dev, + (unsigned char) CR_AREF(insn->chanspec), + (unsigned char) data[0], + (unsigned char) data[1], (unsigned int) data[2], (unsigned int) data[3]); + break; + + default: + printk("Write Config Parameter Wrong\n"); + } + + if (i_ReturnValue >= 0) + i_ReturnValue = insn->n; + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_EnablePWM | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PWM, | +| unsigned char_ b_StartLevel, | +| unsigned char_ b_StopMode, | +| unsigned char_ b_StopLevel, | +| unsigned char_ b_ExternGate, | +| unsigned char_ b_InterruptEnable) | ++----------------------------------------------------------------------------+ +| Task : Enable the selected PWM (b_PWM) from selected module | +| (b_ModulNbr). You must calling the "i_APCI1710_InitPWM"| +| function be for you call this function. | +| If you enable the PWM interrupt, the PWM generate a | +| interrupt after each period. | +| See function "i_APCI1710_SetBoardIntRoutineX" and the | +| Interrupt mask description chapter. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number | +| (0 to 3) | +| unsigned char_ b_PWM : Selected PWM (0 or 1) | +| unsigned char_ b_StartLevel : Start period level selection | +| 0 : The period start with a | +| low level | +| 1 : The period start with a | +| high level | +| unsigned char_ b_StopMode : Stop mode selection | +| 0 : The PWM is stopped | +| directly after the | +| "i_APCI1710_DisablePWM" | +| function and break the | +| last period | +| 1 : After the | +| "i_APCI1710_DisablePWM" | +| function the PWM is | +| stopped at the end from| +| last period cycle. | +| unsigned char_ b_StopLevel : Stop PWM level selection | +| 0 : The output signal keep | +| the level after the | +| "i_APCI1710_DisablePWM" | +| function | +| 1 : The output signal is set| +| to low after the | +| "i_APCI1710_DisablePWM" | +| function | +| 2 : The output signal is set| +| to high after the | +| "i_APCI1710_DisablePWM" | +| function | +| unsigned char_ b_ExternGate : Extern gate action selection | +| 0 : Extern gate signal not | +| used. | +| 1 : Extern gate signal used.| +| unsigned char_ b_InterruptEnable : Enable or disable the PWM | +| interrupt. | +| - APCI1710_ENABLE : | +| Enable the PWM interrupt | +| A interrupt occur after | +| each period | +| - APCI1710_DISABLE : | +| Disable the PWM interrupt | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a PWM module | +| -4: PWM selection is wrong | +| -5: PWM not initialised see function | +| "i_APCI1710_InitPWM" | +| -6: PWM start level selection is wrong | +| -7: PWM stop mode selection is wrong | +| -8: PWM stop level selection is wrong | +| -9: Extern gate signal selection is wrong | +| -10: Interrupt parameter is wrong | +| -11: Interrupt function not initialised. | +| See function "i_APCI1710_SetBoardIntRoutineX" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_EnablePWM(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PWM, + unsigned char b_StartLevel, + unsigned char b_StopMode, + unsigned char b_StopLevel, unsigned char b_ExternGate, unsigned char b_InterruptEnable) +{ + int i_ReturnValue = 0; + unsigned int dw_Status; + unsigned int dw_Command; + + devpriv->tsk_Current = current; /* Save the current process task structure */ + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***************/ + /* Test if PWM */ + /***************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_PWM) { + /**************************/ + /* Test the PWM selection */ + /**************************/ + + if (b_PWM <= 1) { + /***************************/ + /* Test if PWM initialised */ + /***************************/ + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 12 + (20 * b_PWM) + + (64 * b_ModulNbr)); + + if (dw_Status & 0x10) { + /**********************************/ + /* Test the start level selection */ + /**********************************/ + + if (b_StartLevel <= 1) { + /**********************/ + /* Test the stop mode */ + /**********************/ + + if (b_StopMode <= 1) { + /***********************/ + /* Test the stop level */ + /***********************/ + + if (b_StopLevel <= 2) { + /*****************************/ + /* Test the extern gate mode */ + /*****************************/ + + if (b_ExternGate + <= 1) { + /*****************************/ + /* Test the interrupt action */ + /*****************************/ + + if (b_InterruptEnable == APCI1710_ENABLE || b_InterruptEnable == APCI1710_DISABLE) { + /******************************************/ + /* Test if interrupt function initialised */ + /******************************************/ + + /********************/ + /* Read the command */ + /********************/ + + dw_Command + = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + 8 + + + (20 * b_PWM) + (64 * b_ModulNbr)); + + dw_Command + = + dw_Command + & + 0x80; + + /********************/ + /* Make the command */ + /********************/ + + dw_Command + = + dw_Command + | + b_StopMode + | + (b_InterruptEnable + << + 3) + | + (b_ExternGate + << + 4) + | + (b_StartLevel + << + 5); + + if (b_StopLevel & 3) { + dw_Command + = + dw_Command + | + 2; + + if (b_StopLevel & 2) { + dw_Command + = + dw_Command + | + 4; + } + } + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + b_InterruptEnable + = + b_InterruptEnable; + + /*******************/ + /* Set the command */ + /*******************/ + + outl(dw_Command, devpriv->s_BoardInfos.ui_Address + 8 + (20 * b_PWM) + (64 * b_ModulNbr)); + + /******************/ + /* Enable the PWM */ + /******************/ + outl(1, devpriv->s_BoardInfos.ui_Address + 12 + (20 * b_PWM) + (64 * b_ModulNbr)); + } /* if (b_InterruptEnable == APCI1710_ENABLE || b_InterruptEnable == APCI1710_DISABLE) */ + else { + /********************************/ + /* Interrupt parameter is wrong */ + /********************************/ + DPRINTK("Interrupt parameter is wrong\n"); + i_ReturnValue + = + -10; + } /* if (b_InterruptEnable == APCI1710_ENABLE || b_InterruptEnable == APCI1710_DISABLE) */ + } /* if (b_ExternGate >= 0 && b_ExternGate <= 1) */ + else { + /*****************************************/ + /* Extern gate signal selection is wrong */ + /*****************************************/ + DPRINTK("Extern gate signal selection is wrong\n"); + i_ReturnValue + = + -9; + } /* if (b_ExternGate >= 0 && b_ExternGate <= 1) */ + } /* if (b_StopLevel >= 0 && b_StopLevel <= 2) */ + else { + /*************************************/ + /* PWM stop level selection is wrong */ + /*************************************/ + DPRINTK("PWM stop level selection is wrong\n"); + i_ReturnValue = + -8; + } /* if (b_StopLevel >= 0 && b_StopLevel <= 2) */ + } /* if (b_StopMode >= 0 && b_StopMode <= 1) */ + else { + /************************************/ + /* PWM stop mode selection is wrong */ + /************************************/ + DPRINTK("PWM stop mode selection is wrong\n"); + i_ReturnValue = -7; + } /* if (b_StopMode >= 0 && b_StopMode <= 1) */ + } /* if (b_StartLevel >= 0 && b_StartLevel <= 1) */ + else { + /**************************************/ + /* PWM start level selection is wrong */ + /**************************************/ + DPRINTK("PWM start level selection is wrong\n"); + i_ReturnValue = -6; + } /* if (b_StartLevel >= 0 && b_StartLevel <= 1) */ + } /* if (dw_Status & 0x10) */ + else { + /***********************/ + /* PWM not initialised */ + /***********************/ + DPRINTK("PWM not initialised\n"); + i_ReturnValue = -5; + } /* if (dw_Status & 0x10) */ + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + else { + /******************************/ + /* Tor PWM selection is wrong */ + /******************************/ + DPRINTK("Tor PWM selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + } else { + /**********************************/ + /* The module is not a PWM module */ + /**********************************/ + DPRINTK("The module is not a PWM module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_DisablePWM (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PWM) | ++----------------------------------------------------------------------------+ +| Task : Disable the selected PWM (b_PWM) from selected module | +| (b_ModulNbr). The output signal level depend of the | +| initialisation by the "i_APCI1710_EnablePWM". | +| See the b_StartLevel, b_StopMode and b_StopLevel | +| parameters from this function. | ++----------------------------------------------------------------------------+ +| Input Parameters :BYTE_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3) | +| unsigned char_ b_PWM : Selected PWM (0 or 1) | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a PWM module | +| -4: PWM selection is wrong | +| -5: PWM not initialised see function | +| "i_APCI1710_InitPWM" | +| -6: PWM not enabled see function | +| "i_APCI1710_EnablePWM" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_DisablePWM(struct comedi_device *dev, unsigned char b_ModulNbr, unsigned char b_PWM) +{ + int i_ReturnValue = 0; + unsigned int dw_Status; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***************/ + /* Test if PWM */ + /***************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_PWM) { + /**************************/ + /* Test the PWM selection */ + /**************************/ + + if (b_PWM <= 1) { + /***************************/ + /* Test if PWM initialised */ + /***************************/ + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 12 + (20 * b_PWM) + + (64 * b_ModulNbr)); + + if (dw_Status & 0x10) { + /***********************/ + /* Test if PWM enabled */ + /***********************/ + + if (dw_Status & 0x1) { + /*******************/ + /* Disable the PWM */ + /*******************/ + outl(0, devpriv->s_BoardInfos. + ui_Address + 12 + + (20 * b_PWM) + + (64 * b_ModulNbr)); + } /* if (dw_Status & 0x1) */ + else { + /*******************/ + /* PWM not enabled */ + /*******************/ + DPRINTK("PWM not enabled\n"); + i_ReturnValue = -6; + } /* if (dw_Status & 0x1) */ + } /* if (dw_Status & 0x10) */ + else { + /***********************/ + /* PWM not initialised */ + /***********************/ + DPRINTK(" PWM not initialised\n"); + i_ReturnValue = -5; + } /* if (dw_Status & 0x10) */ + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + else { + /******************************/ + /* Tor PWM selection is wrong */ + /******************************/ + DPRINTK("Tor PWM selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + } else { + /**********************************/ + /* The module is not a PWM module */ + /**********************************/ + DPRINTK("The module is not a PWM module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_SetNewPWMTiming | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PWM, | +| unsigned char_ b_ClockSelection, | +| unsigned char_ b_TimingUnit, | +| ULONG_ ul_LowTiming, | +| ULONG_ ul_HighTiming) | ++----------------------------------------------------------------------------+ +| Task : Set a new timing. The ul_LowTiming, ul_HighTiming and | +| ul_TimingUnit determine the low/high timing base for | +| the period. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Module number to configure| +| (0 to 3) | +| unsigned char_ b_PWM : Selected PWM (0 or 1). | +| unsigned char_ b_TimingUnit : Base timing Unit (0 to 4) | +| 0 : ns | +| 1 : æs | +| 2 : ms | +| 3 : s | +| 4 : mn | +| ULONG_ ul_LowTiming : Low base timing value. | +| ULONG_ ul_HighTiming : High base timing value. | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a PWM module | +| -4: PWM selection is wrong | +| -5: PWM not initialised | +| -6: Timing Unit selection is wrong | +| -7: Low base timing selection is wrong | +| -8: High base timing selection is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_SetNewPWMTiming(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PWM, unsigned char b_TimingUnit, unsigned int ul_LowTiming, unsigned int ul_HighTiming) +{ + unsigned char b_ClockSelection; + int i_ReturnValue = 0; + unsigned int ul_LowTimerValue = 0; + unsigned int ul_HighTimerValue = 0; + unsigned int ul_RealLowTiming = 0; + unsigned int ul_RealHighTiming = 0; + unsigned int dw_Status; + unsigned int dw_Command; + double d_RealLowTiming = 0; + double d_RealHighTiming = 0; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***************/ + /* Test if PWM */ + /***************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_PWM) { + /**************************/ + /* Test the PWM selection */ + /**************************/ + + if (b_PWM <= 1) { + /***************************/ + /* Test if PWM initialised */ + /***************************/ + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 12 + (20 * b_PWM) + + (64 * b_ModulNbr)); + + if (dw_Status & 0x10) { + b_ClockSelection = devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_PWMModuleInfo. + b_ClockSelection; + + /************************/ + /* Test the timing unit */ + /************************/ + + if (b_TimingUnit <= 4) { + /*********************************/ + /* Test the low timing selection */ + /*********************************/ + + if (((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 0) + && (ul_LowTiming + >= 266) + && (ul_LowTiming + <= + 0xFFFFFFFFUL)) + || ((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 1) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 571230650UL)) + || ((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 2) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 571230UL)) + || ((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 3) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 571UL)) + || ((b_ClockSelection == + APCI1710_30MHZ) + && (b_TimingUnit + == 4) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= 9UL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 0) + && (ul_LowTiming + >= 242) + && (ul_LowTiming + <= + 0xFFFFFFFFUL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 1) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 519691043UL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 2) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 519691UL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 3) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 520UL)) + || ((b_ClockSelection == + APCI1710_33MHZ) + && (b_TimingUnit + == 4) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= 8UL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 0) + && (ul_LowTiming + >= 200) + && (ul_LowTiming + <= + 0xFFFFFFFFUL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 1) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 429496729UL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 2) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 429496UL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 3) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 429UL)) + || ((b_ClockSelection == + APCI1710_40MHZ) + && (b_TimingUnit + == 4) + && (ul_LowTiming + >= 1) + && (ul_LowTiming + <= + 7UL))) { + /**********************************/ + /* Test the High timing selection */ + /**********************************/ + + if (((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 0) && (ul_HighTiming >= 266) && (ul_HighTiming <= 0xFFFFFFFFUL)) || ((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 1) && (ul_HighTiming >= 1) && (ul_HighTiming <= 571230650UL)) || ((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 2) && (ul_HighTiming >= 1) && (ul_HighTiming <= 571230UL)) || ((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 3) && (ul_HighTiming >= 1) && (ul_HighTiming <= 571UL)) || ((b_ClockSelection == APCI1710_30MHZ) && (b_TimingUnit == 4) && (ul_HighTiming >= 1) && (ul_HighTiming <= 9UL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 0) && (ul_HighTiming >= 242) && (ul_HighTiming <= 0xFFFFFFFFUL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 1) && (ul_HighTiming >= 1) && (ul_HighTiming <= 519691043UL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 2) && (ul_HighTiming >= 1) && (ul_HighTiming <= 519691UL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 3) && (ul_HighTiming >= 1) && (ul_HighTiming <= 520UL)) || ((b_ClockSelection == APCI1710_33MHZ) && (b_TimingUnit == 4) && (ul_HighTiming >= 1) && (ul_HighTiming <= 8UL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 0) && (ul_HighTiming >= 200) && (ul_HighTiming <= 0xFFFFFFFFUL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 1) && (ul_HighTiming >= 1) && (ul_HighTiming <= 429496729UL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 2) && (ul_HighTiming >= 1) && (ul_HighTiming <= 429496UL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 3) && (ul_HighTiming >= 1) && (ul_HighTiming <= 429UL)) || ((b_ClockSelection == APCI1710_40MHZ) && (b_TimingUnit == 4) && (ul_HighTiming >= 1) && (ul_HighTiming <= 7UL))) { + /************************************/ + /* Calculate the low division fator */ + /************************************/ + + fpu_begin(); + switch (b_TimingUnit) { + /******/ + /* ns */ + /******/ + + case 0: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + (unsigned int) + (ul_LowTiming + * + (0.00025 * b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_LowTiming * (0.00025 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (0.00025 * (double)b_ClockSelection)); + d_RealLowTiming + = + (double) + ul_LowTimerValue + / + (0.00025 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_LowTimerValue / (0.00025 * (double)b_ClockSelection)) >= (double)((double)ul_RealLowTiming + 0.5)) { + ul_RealLowTiming + = + ul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* æs */ + /******/ + + case 1: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + (unsigned int) + (ul_LowTiming + * + (0.25 * b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_LowTiming * (0.25 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (0.25 * (double)b_ClockSelection)); + d_RealLowTiming + = + (double) + ul_LowTimerValue + / + ( + (double) + 0.25 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_LowTimerValue / (0.25 * (double)b_ClockSelection)) >= (double)((double)ul_RealLowTiming + 0.5)) { + ul_RealLowTiming + = + ul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* ms */ + /******/ + + case 2: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + ul_LowTiming + * + (250.0 + * + b_ClockSelection); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_LowTiming * (250.0 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (250.0 * (double)b_ClockSelection)); + d_RealLowTiming + = + (double) + ul_LowTimerValue + / + (250.0 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_LowTimerValue / (250.0 * (double)b_ClockSelection)) >= (double)((double)ul_RealLowTiming + 0.5)) { + ul_RealLowTiming + = + ul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + + /*****/ + /* s */ + /*****/ + + case 3: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + (unsigned int) + (ul_LowTiming + * + (250000.0 + * + b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_LowTiming * (250000.0 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)); + d_RealLowTiming + = + (double) + ul_LowTimerValue + / + (250000.0 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_LowTimerValue / (250000.0 * (double)b_ClockSelection)) >= (double)((double)ul_RealLowTiming + 0.5)) { + ul_RealLowTiming + = + ul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* mn */ + /******/ + + case 4: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_LowTimerValue + = + (unsigned int) + ( + (ul_LowTiming + * + 60) + * + (250000.0 + * + b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)(ul_LowTiming * 60.0) * (250000.0 * (double)b_ClockSelection)) >= ((double)((double)ul_LowTimerValue + 0.5))) { + ul_LowTimerValue + = + ul_LowTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealLowTiming + = + (unsigned int) + (ul_LowTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)) + / + 60; + d_RealLowTiming + = + ( + (double) + ul_LowTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)) + / + 60.0; + + if ((double)(((double)ul_LowTimerValue / (250000.0 * (double)b_ClockSelection)) / 60.0) >= (double)((double)ul_RealLowTiming + 0.5)) { + ul_RealLowTiming + = + ul_RealLowTiming + + + 1; + } + + ul_LowTiming + = + ul_LowTiming + - + 1; + ul_LowTimerValue + = + ul_LowTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_LowTimerValue + = + (unsigned int) + ( + (double) + (ul_LowTimerValue) + * + 1.007752288); + } + + break; + } + + /*************************************/ + /* Calculate the high division fator */ + /*************************************/ + + switch (b_TimingUnit) { + /******/ + /* ns */ + /******/ + + case 0: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + (unsigned int) + (ul_HighTiming + * + (0.00025 * b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_HighTiming * (0.00025 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (0.00025 * (double)b_ClockSelection)); + d_RealHighTiming + = + (double) + ul_HighTimerValue + / + (0.00025 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_HighTimerValue / (0.00025 * (double)b_ClockSelection)) >= (double)((double)ul_RealHighTiming + 0.5)) { + ul_RealHighTiming + = + ul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* æs */ + /******/ + + case 1: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + (unsigned int) + (ul_HighTiming + * + (0.25 * b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_HighTiming * (0.25 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (0.25 * (double)b_ClockSelection)); + d_RealHighTiming + = + (double) + ul_HighTimerValue + / + ( + (double) + 0.25 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_HighTimerValue / (0.25 * (double)b_ClockSelection)) >= (double)((double)ul_RealHighTiming + 0.5)) { + ul_RealHighTiming + = + ul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* ms */ + /******/ + + case 2: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + ul_HighTiming + * + (250.0 + * + b_ClockSelection); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_HighTiming * (250.0 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (250.0 * (double)b_ClockSelection)); + d_RealHighTiming + = + (double) + ul_HighTimerValue + / + (250.0 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_HighTimerValue / (250.0 * (double)b_ClockSelection)) >= (double)((double)ul_RealHighTiming + 0.5)) { + ul_RealHighTiming + = + ul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + + /*****/ + /* s */ + /*****/ + + case 3: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + (unsigned int) + (ul_HighTiming + * + (250000.0 + * + b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_HighTiming * (250000.0 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)); + d_RealHighTiming + = + (double) + ul_HighTimerValue + / + (250000.0 + * + (double) + b_ClockSelection); + + if ((double)((double)ul_HighTimerValue / (250000.0 * (double)b_ClockSelection)) >= (double)((double)ul_RealHighTiming + 0.5)) { + ul_RealHighTiming + = + ul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* mn */ + /******/ + + case 4: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_HighTimerValue + = + (unsigned int) + ( + (ul_HighTiming + * + 60) + * + (250000.0 + * + b_ClockSelection)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)(ul_HighTiming * 60.0) * (250000.0 * (double)b_ClockSelection)) >= ((double)((double)ul_HighTimerValue + 0.5))) { + ul_HighTimerValue + = + ul_HighTimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealHighTiming + = + (unsigned int) + (ul_HighTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)) + / + 60; + d_RealHighTiming + = + ( + (double) + ul_HighTimerValue + / + (250000.0 + * + (double) + b_ClockSelection)) + / + 60.0; + + if ((double)(((double)ul_HighTimerValue / (250000.0 * (double)b_ClockSelection)) / 60.0) >= (double)((double)ul_RealHighTiming + 0.5)) { + ul_RealHighTiming + = + ul_RealHighTiming + + + 1; + } + + ul_HighTiming + = + ul_HighTiming + - + 1; + ul_HighTimerValue + = + ul_HighTimerValue + - + 2; + + if (b_ClockSelection != APCI1710_40MHZ) { + ul_HighTimerValue + = + (unsigned int) + ( + (double) + (ul_HighTimerValue) + * + 1.007752288); + } + + break; + } + + fpu_end(); + + /************************/ + /* Save the timing unit */ + /************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + b_TimingUnit + = + b_TimingUnit; + + /****************************/ + /* Save the low base timing */ + /****************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + d_LowTiming + = + d_RealLowTiming; + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + ul_RealLowTiming + = + ul_RealLowTiming; + + /****************************/ + /* Save the high base timing */ + /****************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + d_HighTiming + = + d_RealHighTiming; + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_PWMModuleInfo. + s_PWMInfo + [b_PWM]. + ul_RealHighTiming + = + ul_RealHighTiming; + + /************************/ + /* Write the low timing */ + /************************/ + + outl(ul_LowTimerValue, devpriv->s_BoardInfos.ui_Address + 0 + (20 * b_PWM) + (64 * b_ModulNbr)); + + /*************************/ + /* Write the high timing */ + /*************************/ + + outl(ul_HighTimerValue, devpriv->s_BoardInfos.ui_Address + 4 + (20 * b_PWM) + (64 * b_ModulNbr)); + + /***************************/ + /* Set the clock selection */ + /***************************/ + + dw_Command = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + 8 + + (20 * b_PWM) + (64 * b_ModulNbr)); + + dw_Command = + dw_Command + & 0x7F; + + if (b_ClockSelection == APCI1710_40MHZ) { + dw_Command + = + dw_Command + | + 0x80; + } + + /***************************/ + /* Set the clock selection */ + /***************************/ + + outl(dw_Command, + devpriv-> + s_BoardInfos. + ui_Address + + 8 + + (20 * b_PWM) + (64 * b_ModulNbr)); + } else { + /***************************************/ + /* High base timing selection is wrong */ + /***************************************/ + DPRINTK("High base timing selection is wrong\n"); + i_ReturnValue = + -8; + } + } else { + /**************************************/ + /* Low base timing selection is wrong */ + /**************************************/ + DPRINTK("Low base timing selection is wrong\n"); + i_ReturnValue = -7; + } + } /* if ((b_TimingUnit >= 0) && (b_TimingUnit <= 4)) */ + else { + /**********************************/ + /* Timing unit selection is wrong */ + /**********************************/ + DPRINTK("Timing unit selection is wrong\n"); + i_ReturnValue = -6; + } /* if ((b_TimingUnit >= 0) && (b_TimingUnit <= 4)) */ + } /* if (dw_Status & 0x10) */ + else { + /***********************/ + /* PWM not initialised */ + /***********************/ + DPRINTK("PWM not initialised\n"); + i_ReturnValue = -5; + } /* if (dw_Status & 0x10) */ + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + else { + /******************************/ + /* Tor PWM selection is wrong */ + /******************************/ + DPRINTK("Tor PWM selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + } else { + /**********************************/ + /* The module is not a PWM module */ + /**********************************/ + DPRINTK("The module is not a PWM module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetPWMStatus | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PWM, | +| unsigned char *_ pb_PWMOutputStatus, | +| unsigned char *_ pb_ExternGateStatus) | ++----------------------------------------------------------------------------+ +| Task : Return the status from selected PWM (b_PWM) from | +| selected module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_PWM : Selected PWM (0 or 1) | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3) + b_ModulNbr =(unsigned char) CR_AREF(insn->chanspec); + b_PWM =(unsigned char) data[0]; + + | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_PWMOutputStatus : Return the PWM output | +| level status. | +| 0 : The PWM output level| +| is low. | +| 1 : The PWM output level| +| is high. | +| unsigned char *_ pb_ExternGateStatus : Return the extern gate | +| level status. | +| 0 : The extern gate is | +| low. | +| 1 : The extern gate is | +| high. + pb_PWMOutputStatus =(unsigned char *) data[0]; + pb_ExternGateStatus =(unsigned char *) data[1]; | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a PWM module | +| -4: PWM selection is wrong | +| -5: PWM not initialised see function | +| "i_APCI1710_InitPWM" | +| -6: PWM not enabled see function "i_APCI1710_EnablePWM"| ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnReadGetPWMStatus(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_Status; + + unsigned char b_ModulNbr; + unsigned char b_PWM; + unsigned char *pb_PWMOutputStatus; + unsigned char *pb_ExternGateStatus; + + i_ReturnValue = insn->n; + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_PWM = (unsigned char) CR_CHAN(insn->chanspec); + pb_PWMOutputStatus = (unsigned char *) &data[0]; + pb_ExternGateStatus = (unsigned char *) &data[1]; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***************/ + /* Test if PWM */ + /***************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_PWM) { + /**************************/ + /* Test the PWM selection */ + /**************************/ + + if (b_PWM <= 1) { + /***************************/ + /* Test if PWM initialised */ + /***************************/ + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 12 + (20 * b_PWM) + + (64 * b_ModulNbr)); + + if (dw_Status & 0x10) { + /***********************/ + /* Test if PWM enabled */ + /***********************/ + + if (dw_Status & 0x1) { + *pb_PWMOutputStatus = + (unsigned char) ((dw_Status >> 7) + & 1); + *pb_ExternGateStatus = + (unsigned char) ((dw_Status >> 6) + & 1); + } /* if (dw_Status & 0x1) */ + else { + /*******************/ + /* PWM not enabled */ + /*******************/ + + DPRINTK("PWM not enabled \n"); + i_ReturnValue = -6; + } /* if (dw_Status & 0x1) */ + } /* if (dw_Status & 0x10) */ + else { + /***********************/ + /* PWM not initialised */ + /***********************/ + + DPRINTK("PWM not initialised\n"); + i_ReturnValue = -5; + } /* if (dw_Status & 0x10) */ + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + else { + /******************************/ + /* Tor PWM selection is wrong */ + /******************************/ + + DPRINTK("Tor PWM selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_PWM >= 0 && b_PWM <= 1) */ + } else { + /**********************************/ + /* The module is not a PWM module */ + /**********************************/ + + DPRINTK("The module is not a PWM module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +int i_APCI1710_InsnBitsReadPWMInterrupt(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + data[0] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].b_OldModuleMask; + data[1] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldInterruptMask; + data[2] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldCounterLatchValue; + + /**************************/ + /* Increment the read FIFO */ + /***************************/ + + devpriv-> + s_InterruptParameters. + ui_Read = (devpriv-> + s_InterruptParameters.ui_Read + 1) % APCI1710_SAVE_INTERRUPT; + + return insn->n; + +} diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Pwm.h b/drivers/staging/comedi/drivers/addi-data/APCI1710_Pwm.h new file mode 100644 index 00000000..d8ad0b9c --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Pwm.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define APCI1710_30MHZ 30 +#define APCI1710_33MHZ 33 +#define APCI1710_40MHZ 40 + +#define APCI1710_PWM_INIT 0 +#define APCI1710_PWM_GETINITDATA 1 + +#define APCI1710_PWM_DISABLE 0 +#define APCI1710_PWM_ENABLE 1 +#define APCI1710_PWM_NEWTIMING 2 + +int i_APCI1710_InsnConfigPWM(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InitPWM(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PWM, + unsigned char b_ClockSelection, + unsigned char b_TimingUnit, + unsigned int ul_LowTiming, + unsigned int ul_HighTiming, + unsigned int *pul_RealLowTiming, unsigned int *pul_RealHighTiming); + +int i_APCI1710_GetPWMInitialisation(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PWM, + unsigned char *pb_TimingUnit, + unsigned int *pul_LowTiming, + unsigned int *pul_HighTiming, + unsigned char *pb_StartLevel, + unsigned char *pb_StopMode, + unsigned char *pb_StopLevel, + unsigned char *pb_ExternGate, + unsigned char *pb_InterruptEnable, unsigned char *pb_Enable); + +int i_APCI1710_InsnWritePWM(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_EnablePWM(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PWM, + unsigned char b_StartLevel, + unsigned char b_StopMode, + unsigned char b_StopLevel, unsigned char b_ExternGate, + unsigned char b_InterruptEnable); + +int i_APCI1710_SetNewPWMTiming(struct comedi_device *dev, + unsigned char b_ModulNbr, + unsigned char b_PWM, unsigned char b_TimingUnit, + unsigned int ul_LowTiming, unsigned int ul_HighTiming); + +int i_APCI1710_DisablePWM(struct comedi_device *dev, unsigned char b_ModulNbr, unsigned char b_PWM); + +int i_APCI1710_InsnReadGetPWMStatus(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnBitsReadPWMInterrupt(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Ssi.c b/drivers/staging/comedi/drivers/addi-data/APCI1710_Ssi.c new file mode 100644 index 00000000..c13b0027 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Ssi.c @@ -0,0 +1,832 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : API APCI1710 | Compiler : gcc | + | Module name : SSI.C | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description : APCI-1710 SSI counter module | + +-----------------------------------------------------------------------+ + | several changes done by S. Weber in 1998 and C. Guinot in 2000 | + +-----------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ + +#include "APCI1710_Ssi.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitSSI | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_SSIProfile, | +| unsigned char_ b_PositionTurnLength, | +| unsigned char_ b_TurnCptLength, | +| unsigned char_ b_PCIInputClock, | +| ULONG_ ul_SSIOutputClock, | +| unsigned char_ b_SSICountingMode) | ++----------------------------------------------------------------------------+ +| Task : Configure the SSI operating mode from selected module | +| (b_ModulNbr). You must calling this function be for you| +| call any other function witch access of SSI. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 3) | +| unsigned char_ b_SSIProfile : Selection from SSI | +| profile length (2 to 32).| +| unsigned char_ b_PositionTurnLength : Selection from SSI | +| position data length | +| (1 to 31). | +| unsigned char_ b_TurnCptLength : Selection from SSI turn | +| counter data length | +| (1 to 31). | +| unsigned char b_PCIInputClock : Selection from PCI bus | +| clock | +| - APCI1710_30MHZ : | +| The PC have a PCI bus | +| clock from 30 MHz | +| - APCI1710_33MHZ : | +| The PC have a PCI bus | +| clock from 33 MHz | +| ULONG_ ul_SSIOutputClock : Selection from SSI output| +| clock. | +| From 229 to 5 000 000 Hz| +| for 30 MHz selection. | +| From 252 to 5 000 000 Hz| +| for 33 MHz selection. | +| unsigned char b_SSICountingMode : SSI counting mode | +| selection | +| - APCI1710_BINARY_MODE : | +| Binary counting mode. | +| - APCI1710_GRAY_MODE : | +| Gray counting mode. + + b_ModulNbr = CR_AREF(insn->chanspec); + b_SSIProfile = (unsigned char) data[0]; + b_PositionTurnLength= (unsigned char) data[1]; + b_TurnCptLength = (unsigned char) data[2]; + b_PCIInputClock = (unsigned char) data[3]; + ul_SSIOutputClock = (unsigned int) data[4]; + b_SSICountingMode = (unsigned char) data[5]; | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a SSI module | +| -4: The selected SSI profile length is wrong | +| -5: The selected SSI position data length is wrong | +| -6: The selected SSI turn counter data length is wrong | +| -7: The selected PCI input clock is wrong | +| -8: The selected SSI output clock is wrong | +| -9: The selected SSI counting mode parameter is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnConfigInitSSI(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int ui_TimerValue; + unsigned char b_ModulNbr, b_SSIProfile, b_PositionTurnLength, b_TurnCptLength, + b_PCIInputClock, b_SSICountingMode; + unsigned int ul_SSIOutputClock; + + b_ModulNbr = CR_AREF(insn->chanspec); + b_SSIProfile = (unsigned char) data[0]; + b_PositionTurnLength = (unsigned char) data[1]; + b_TurnCptLength = (unsigned char) data[2]; + b_PCIInputClock = (unsigned char) data[3]; + ul_SSIOutputClock = (unsigned int) data[4]; + b_SSICountingMode = (unsigned char) data[5]; + + i_ReturnValue = insn->n; + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if SSI counter */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_SSI_COUNTER) { + /*******************************/ + /* Test the SSI profile length */ + /*******************************/ + + /* CG 22/03/00 b_SSIProfile >= 2 anstatt b_SSIProfile > 2 */ + if (b_SSIProfile >= 2 && b_SSIProfile < 33) { + /*************************************/ + /* Test the SSI position data length */ + /*************************************/ + + if (b_PositionTurnLength > 0 + && b_PositionTurnLength < 32) { + /*****************************************/ + /* Test the SSI turn counter data length */ + /*****************************************/ + + if (b_TurnCptLength > 0 + && b_TurnCptLength < 32) { + /***************************/ + /* Test the profile length */ + /***************************/ + + if ((b_TurnCptLength + + b_PositionTurnLength) + <= b_SSIProfile) { + /****************************/ + /* Test the PCI input clock */ + /****************************/ + + if (b_PCIInputClock == + APCI1710_30MHZ + || + b_PCIInputClock + == + APCI1710_33MHZ) + { + /*************************/ + /* Test the output clock */ + /*************************/ + + if ((b_PCIInputClock == APCI1710_30MHZ && (ul_SSIOutputClock > 228 && ul_SSIOutputClock <= 5000000UL)) || (b_PCIInputClock == APCI1710_33MHZ && (ul_SSIOutputClock > 251 && ul_SSIOutputClock <= 5000000UL))) { + if (b_SSICountingMode == APCI1710_BINARY_MODE || b_SSICountingMode == APCI1710_GRAY_MODE) { + /**********************/ + /* Save configuration */ + /**********************/ + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_SSIProfile + = + b_SSIProfile; + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_PositionTurnLength + = + b_PositionTurnLength; + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_TurnCptLength + = + b_TurnCptLength; + + /*********************************/ + /* Initialise the profile length */ + /*********************************/ + + if (b_SSICountingMode == APCI1710_BINARY_MODE) { + + outl(b_SSIProfile + 1, devpriv->s_BoardInfos.ui_Address + 4 + (64 * b_ModulNbr)); + } else { + + outl(b_SSIProfile, devpriv->s_BoardInfos.ui_Address + 4 + (64 * b_ModulNbr)); + } + + /******************************/ + /* Calculate the output clock */ + /******************************/ + + ui_TimerValue + = + (unsigned int) + ( + ((unsigned int) (b_PCIInputClock) * 500000UL) / ul_SSIOutputClock); + + /************************/ + /* Initialise the timer */ + /************************/ + + outl(ui_TimerValue, devpriv->s_BoardInfos.ui_Address + (64 * b_ModulNbr)); + + /********************************/ + /* Initialise the counting mode */ + /********************************/ + + outl(7 * b_SSICountingMode, devpriv->s_BoardInfos.ui_Address + 12 + (64 * b_ModulNbr)); + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_SSIInit + = + 1; + } else { + /*****************************************************/ + /* The selected SSI counting mode parameter is wrong */ + /*****************************************************/ + + DPRINTK("The selected SSI counting mode parameter is wrong\n"); + i_ReturnValue + = + -9; + } + } else { + /******************************************/ + /* The selected SSI output clock is wrong */ + /******************************************/ + + DPRINTK("The selected SSI output clock is wrong\n"); + i_ReturnValue + = + -8; + } + } else { + /*****************************************/ + /* The selected PCI input clock is wrong */ + /*****************************************/ + + DPRINTK("The selected PCI input clock is wrong\n"); + i_ReturnValue = + -7; + } + } else { + /********************************************/ + /* The selected SSI profile length is wrong */ + /********************************************/ + + DPRINTK("The selected SSI profile length is wrong\n"); + i_ReturnValue = -4; + } + } else { + /******************************************************/ + /* The selected SSI turn counter data length is wrong */ + /******************************************************/ + + DPRINTK("The selected SSI turn counter data length is wrong\n"); + i_ReturnValue = -6; + } + } else { + /**************************************************/ + /* The selected SSI position data length is wrong */ + /**************************************************/ + + DPRINTK("The selected SSI position data length is wrong\n"); + i_ReturnValue = -5; + } + } else { + /********************************************/ + /* The selected SSI profile length is wrong */ + /********************************************/ + + DPRINTK("The selected SSI profile length is wrong\n"); + i_ReturnValue = -4; + } + } else { + /**********************************/ + /* The module is not a SSI module */ + /**********************************/ + + DPRINTK("The module is not a SSI module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_Read1SSIValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_SelectedSSI, | +| PULONG_ pul_Position, | +| PULONG_ pul_TurnCpt) + int i_APCI1710_ReadSSIValue(struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : + + + Read the selected SSI counter (b_SelectedSSI) from | +| selected module (b_ModulNbr). + or Read all SSI counter (b_SelectedSSI) from | +| selected module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 3) | +| unsigned char_ b_SelectedSSI : Selection from SSI | +| counter (0 to 2) + + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_SelectedSSI = (unsigned char) CR_CHAN(insn->chanspec); (in case of single ssi) + b_ReadType = (unsigned char) CR_RANGE(insn->chanspec); +| ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_Position : SSI position in the turn | +| PULONG_ pul_TurnCpt : Number of turns + +pul_Position = (unsigned int *) &data[0]; + pul_TurnCpt = (unsigned int *) &data[1]; | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a SSI module | +| -4: SSI not initialised see function | +| "i_APCI1710_InitSSI" | +| -5: The selected SSI is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnReadSSIValue(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned char b_Cpt; + unsigned char b_Length; + unsigned char b_Schift; + unsigned char b_SSICpt; + unsigned int dw_And; + unsigned int dw_And1; + unsigned int dw_And2; + unsigned int dw_StatusReg; + unsigned int dw_CounterValue; + unsigned char b_ModulNbr; + unsigned char b_SelectedSSI; + unsigned char b_ReadType; + unsigned int *pul_Position; + unsigned int *pul_TurnCpt; + unsigned int *pul_Position1; + unsigned int *pul_TurnCpt1; + + i_ReturnValue = insn->n; + pul_Position1 = (unsigned int *) &data[0]; +/* For Read1 */ + pul_TurnCpt1 = (unsigned int *) &data[1]; +/* For Read all */ + pul_Position = (unsigned int *) &data[0]; /* 0-2 */ + pul_TurnCpt = (unsigned int *) &data[3]; /* 3-5 */ + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_SelectedSSI = (unsigned char) CR_CHAN(insn->chanspec); + b_ReadType = (unsigned char) CR_RANGE(insn->chanspec); + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if SSI counter */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_SSI_COUNTER) { + /***************************/ + /* Test if SSI initialised */ + /***************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_SSICounterInfo.b_SSIInit == 1) { + + switch (b_ReadType) { + + case APCI1710_SSI_READ1VALUE: + /****************************************/ + /* Test the selected SSI counter number */ + /****************************************/ + + if (b_SelectedSSI < 3) { + /************************/ + /* Start the conversion */ + /************************/ + + outl(0, devpriv->s_BoardInfos. + ui_Address + 8 + + (64 * b_ModulNbr)); + + do { + /*******************/ + /* Read the status */ + /*******************/ + + dw_StatusReg = + inl(devpriv-> + s_BoardInfos. + ui_Address + + (64 * b_ModulNbr)); + } while ((dw_StatusReg & 0x1) + != 0); + + /******************************/ + /* Read the SSI counter value */ + /******************************/ + + dw_CounterValue = + inl(devpriv-> + s_BoardInfos. + ui_Address + 4 + + (b_SelectedSSI * 4) + + (64 * b_ModulNbr)); + + b_Length = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_SSIProfile / 2; + + if ((b_Length * 2) != + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_SSIProfile) { + b_Length++; + } + + b_Schift = + b_Length - + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_PositionTurnLength; + + *pul_Position1 = + dw_CounterValue >> + b_Schift; + + dw_And = 1; + + for (b_Cpt = 0; + b_Cpt < + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_PositionTurnLength; + b_Cpt++) { + dw_And = dw_And * 2; + } + + *pul_Position1 = + *pul_Position1 & + ((dw_And) - 1); + + *pul_TurnCpt1 = + dw_CounterValue >> + b_Length; + + dw_And = 1; + + for (b_Cpt = 0; + b_Cpt < + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_TurnCptLength; + b_Cpt++) { + dw_And = dw_And * 2; + } + + *pul_TurnCpt1 = + *pul_TurnCpt1 & + ((dw_And) - 1); + } else { + /*****************************/ + /* The selected SSI is wrong */ + /*****************************/ + + DPRINTK("The selected SSI is wrong\n"); + i_ReturnValue = -5; + } + break; + + case APCI1710_SSI_READALLVALUE: + dw_And1 = 1; + + for (b_Cpt = 0; + b_Cpt < + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SSICounterInfo. + b_PositionTurnLength; b_Cpt++) { + dw_And1 = dw_And1 * 2; + } + + dw_And2 = 1; + + for (b_Cpt = 0; + b_Cpt < + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_SSICounterInfo. + b_TurnCptLength; b_Cpt++) { + dw_And2 = dw_And2 * 2; + } + + /************************/ + /* Start the conversion */ + /************************/ + + outl(0, devpriv->s_BoardInfos. + ui_Address + 8 + + (64 * b_ModulNbr)); + + do { + /*******************/ + /* Read the status */ + /*******************/ + + dw_StatusReg = + inl(devpriv-> + s_BoardInfos. + ui_Address + + (64 * b_ModulNbr)); + } while ((dw_StatusReg & 0x1) != 0); + + for (b_SSICpt = 0; b_SSICpt < 3; + b_SSICpt++) { + /******************************/ + /* Read the SSI counter value */ + /******************************/ + + dw_CounterValue = + inl(devpriv-> + s_BoardInfos. + ui_Address + 4 + + (b_SSICpt * 4) + + (64 * b_ModulNbr)); + + b_Length = + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_SSIProfile / 2; + + if ((b_Length * 2) != + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_SSIProfile) { + b_Length++; + } + + b_Schift = + b_Length - + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_SSICounterInfo. + b_PositionTurnLength; + + pul_Position[b_SSICpt] = + dw_CounterValue >> + b_Schift; + pul_Position[b_SSICpt] = + pul_Position[b_SSICpt] & + ((dw_And1) - 1); + + pul_TurnCpt[b_SSICpt] = + dw_CounterValue >> + b_Length; + pul_TurnCpt[b_SSICpt] = + pul_TurnCpt[b_SSICpt] & + ((dw_And2) - 1); + } + break; + + default: + printk("Read Type Inputs Wrong\n"); + + } /* switch ending */ + + } else { + /***********************/ + /* SSI not initialised */ + /***********************/ + + DPRINTK("SSI not initialised\n"); + i_ReturnValue = -4; + } + } else { + /**********************************/ + /* The module is not a SSI module */ + /**********************************/ + + DPRINTK("The module is not a SSI module\n"); + i_ReturnValue = -3; + + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadSSI1DigitalInput | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_InputChannel, | +| unsigned char *_ pb_ChannelStatus) | ++----------------------------------------------------------------------------+ +| Task : + (0) Set the digital output from selected SSI moule | +| (b_ModuleNbr) ON + (1) Set the digital output from selected SSI moule | +| (b_ModuleNbr) OFF + (2)Read the status from selected SSI digital input | +| (b_InputChannel) + (3)Read the status from all SSI digital inputs from | +| selected SSI module (b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| unsigned char_ b_ModulNbr CR_AREF : Module number to | +| configure (0 to 3) | +| unsigned char_ b_InputChannel CR_CHAN : Selection from digital | +| data[0] which IOTYPE input ( 0 to 2) | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_ChannelStatus : Digital input channel | +| data[0] status | +| 0 : Channle is not active| +| 1 : Channle is active | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a SSI module | +| -4: The selected SSI digital input is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnBitsSSIDigitalIO(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg; + unsigned char b_ModulNbr; + unsigned char b_InputChannel; + unsigned char *pb_ChannelStatus; + unsigned char *pb_InputStatus; + unsigned char b_IOType; + i_ReturnValue = insn->n; + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_IOType = (unsigned char) data[0]; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if SSI counter */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_SSI_COUNTER) { + switch (b_IOType) { + case APCI1710_SSI_SET_CHANNELON: + /*****************************/ + /* Set the digital output ON */ + /*****************************/ + + outl(1, devpriv->s_BoardInfos.ui_Address + 16 + + (64 * b_ModulNbr)); + break; + + case APCI1710_SSI_SET_CHANNELOFF: + /******************************/ + /* Set the digital output OFF */ + /******************************/ + + outl(0, devpriv->s_BoardInfos.ui_Address + 16 + + (64 * b_ModulNbr)); + break; + + case APCI1710_SSI_READ_1CHANNEL: + /******************************************/ + /* Test the digital imnput channel number */ + /******************************************/ + + b_InputChannel = (unsigned char) CR_CHAN(insn->chanspec); + pb_ChannelStatus = (unsigned char *) &data[0]; + + if (b_InputChannel <= 2) { + /**************************/ + /* Read all digital input */ + /**************************/ + + dw_StatusReg = + inl(devpriv->s_BoardInfos. + ui_Address + (64 * b_ModulNbr)); + *pb_ChannelStatus = + (unsigned char) (((~dw_StatusReg) >> (4 + + b_InputChannel)) + & 1); + } else { + /********************************/ + /* Selected digital input error */ + /********************************/ + + DPRINTK("Selected digital input error\n"); + i_ReturnValue = -4; + } + break; + + case APCI1710_SSI_READ_ALLCHANNEL: + /**************************/ + /* Read all digital input */ + /**************************/ + pb_InputStatus = (unsigned char *) &data[0]; + + dw_StatusReg = + inl(devpriv->s_BoardInfos.ui_Address + + (64 * b_ModulNbr)); + *pb_InputStatus = + (unsigned char) (((~dw_StatusReg) >> 4) & 7); + break; + + default: + printk("IO type wrong\n"); + + } /* switch end */ + } else { + /**********************************/ + /* The module is not a SSI module */ + /**********************************/ + + DPRINTK("The module is not a SSI module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Ssi.h b/drivers/staging/comedi/drivers/addi-data/APCI1710_Ssi.h new file mode 100644 index 00000000..ef4d8878 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Ssi.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define APCI1710_30MHZ 30 +#define APCI1710_33MHZ 33 +#define APCI1710_40MHZ 40 + +#define APCI1710_BINARY_MODE 0x1 +#define APCI1710_GRAY_MODE 0x0 + +#define APCI1710_SSI_READ1VALUE 1 +#define APCI1710_SSI_READALLVALUE 2 + +#define APCI1710_SSI_SET_CHANNELON 0 +#define APCI1710_SSI_SET_CHANNELOFF 1 +#define APCI1710_SSI_READ_1CHANNEL 2 +#define APCI1710_SSI_READ_ALLCHANNEL 3 + +/* + * SSI INISIALISATION FUNCTION + */ +int i_APCI1710_InsnConfigInitSSI(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnReadSSIValue(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnBitsSSIDigitalIO(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.c b/drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.c new file mode 100644 index 00000000..0e6affd9 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.c @@ -0,0 +1,2049 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : API APCI1710 | Compiler : gcc | + | Module name : TOR.C | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description : APCI-1710 tor counter module | + | | + | | + +-----------------------------------------------------------------------+ + | UPDATES | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | 27/01/99 | S. Weber | 40 MHz implementation | + +-----------------------------------------------------------------------+ + | 28/04/00 | S. Weber | Simple,double and quadruple mode implementation| + | | | Extern clock implementation | + +-----------------------------------------------------------------------+ + | 08/05/00 | Guinot C | - 0400/0228 All Function in RING 0 | + | | | available | + +-----------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ + +#include "APCI1710_Tor.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitTorCounter | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TorCounter, | +| unsigned char_ b_PCIInputClock, | +| unsigned char_ b_TimingUnit, | +| ULONG_ ul_TimingInterval, | +| PULONG_ pul_RealTimingInterval) | ++----------------------------------------------------------------------------+ +| Task : Configure the selected tor counter (b_TorCounter) | +| from selected module (b_ModulNbr). | +| The ul_TimingInterval and ul_TimingUnit determine the | +| timing base for the measurement. | +| The pul_RealTimingInterval return the real timing | +| value. You must calling this function be for you call | +| any other function witch access of the tor counter. | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : | +| + CR_AREF unsigned char_ b_ModulNbr : Module number to configure | +| (0 to 3) | +| data[0] unsigned char_ b_TorCounter : Tor counter selection | +| (0 or 1). | +| data[1] unsigned char_ b_PCIInputClock : Selection from PCI bus clock| +| - APCI1710_30MHZ : | +| The PC have a PCI bus | +| clock from 30 MHz | +| - APCI1710_33MHZ : | +| The PC have a PCI bus | +| clock from 33 MHz | +| - APCI1710_40MHZ | +| The APCI-1710 have a | +| integrated 40Mhz | +| quartz. | +| - APCI1710_GATE_INPUT | +| Used the gate input for | +| the base clock. If you | +| have selected this option,| +| than it is not possibl to | +| used the gate input for | +| enabled the acquisition | +| data[2] unsigned char_ b_TimingUnit : Base timing unit (0 to 4) | +| 0 : ns | +| 1 : µs | +| 2 : ms | +| 3 : s | +| 4 : mn | +| data[3] ULONG_ ul_TimingInterval : Base timing value. | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_RealTimingInterval : Real base timing | +| data[0] value. | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a tor counter module | +| -4: Tor counter selection is wrong | +| -5: The selected PCI input clock is wrong | +| -6: Timing unit selection is wrong | +| -7: Base timing selection is wrong | +| -8: You can not used the 40MHz clock selection wich | +| this board | +| -9: You can not used the 40MHz clock selection wich | +| this TOR version | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnConfigInitTorCounter(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int ul_TimerValue = 0; + unsigned int dw_Command; + double d_RealTimingInterval = 0; + unsigned char b_ModulNbr; + unsigned char b_TorCounter; + unsigned char b_PCIInputClock; + unsigned char b_TimingUnit; + unsigned int ul_TimingInterval; + unsigned int ul_RealTimingInterval = 0; + + i_ReturnValue = insn->n; + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + + b_TorCounter = (unsigned char) data[0]; + b_PCIInputClock = (unsigned char) data[1]; + b_TimingUnit = (unsigned char) data[2]; + ul_TimingInterval = (unsigned int) data[3]; + printk("INPUT clock %d\n", b_PCIInputClock); + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if tor counter */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_TOR_COUNTER) { + /**********************************/ + /* Test the tor counter selection */ + /**********************************/ + + if (b_TorCounter <= 1) { + /**************************/ + /* Test the PCI bus clock */ + /**************************/ + + if ((b_PCIInputClock == APCI1710_30MHZ) || + (b_PCIInputClock == APCI1710_33MHZ) || + (b_PCIInputClock == APCI1710_40MHZ) || + (b_PCIInputClock == + APCI1710_GATE_INPUT)) { + /************************/ + /* Test the timing unit */ + /************************/ + + if ((b_TimingUnit <= 4) + || (b_PCIInputClock == + APCI1710_GATE_INPUT)) { + /**********************************/ + /* Test the base timing selection */ + /**********************************/ + + if (((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 0) && (ul_TimingInterval >= 133) && (ul_TimingInterval <= 0xFFFFFFFFUL)) || ((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 1) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 571230650UL)) || ((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 2) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 571230UL)) || ((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 3) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 571UL)) || ((b_PCIInputClock == APCI1710_30MHZ) && (b_TimingUnit == 4) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 9UL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 0) && (ul_TimingInterval >= 121) && (ul_TimingInterval <= 0xFFFFFFFFUL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 1) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 519691043UL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 2) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 519691UL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 3) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 520UL)) || ((b_PCIInputClock == APCI1710_33MHZ) && (b_TimingUnit == 4) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 8UL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 0) && (ul_TimingInterval >= 100) && (ul_TimingInterval <= 0xFFFFFFFFUL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 1) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 429496729UL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 2) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 429496UL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 3) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 429UL)) || ((b_PCIInputClock == APCI1710_40MHZ) && (b_TimingUnit == 4) && (ul_TimingInterval >= 1) && (ul_TimingInterval <= 7UL)) || ((b_PCIInputClock == APCI1710_GATE_INPUT) && (ul_TimingInterval >= 2))) { + /**************************/ + /* Test the board version */ + /**************************/ + + if (((b_PCIInputClock == APCI1710_40MHZ) && (devpriv->s_BoardInfos.b_BoardVersion > 0)) || (b_PCIInputClock != APCI1710_40MHZ)) { + /************************/ + /* Test the TOR version */ + /************************/ + + if (((b_PCIInputClock == APCI1710_40MHZ) && ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF) >= 0x3131)) || ((b_PCIInputClock == APCI1710_GATE_INPUT) && ((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF) >= 0x3132)) || (b_PCIInputClock == APCI1710_30MHZ) || (b_PCIInputClock == APCI1710_33MHZ)) { + /*********************************/ + /* Test if not extern clock used */ + /*********************************/ + + if (b_PCIInputClock != APCI1710_GATE_INPUT) { + fpu_begin + (); + /****************************************/ + /* Calculate the timer 0 division fator */ + /****************************************/ + + switch (b_TimingUnit) { + /******/ + /* ns */ + /******/ + + case 0: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + (ul_TimingInterval + * + (0.00025 * b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (0.00025 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (0.00025 * (double)b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + (0.00025 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (0.00025 * (double)b_PCIInputClock)) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* æs */ + /******/ + + case 1: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + (ul_TimingInterval + * + (0.25 * b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (0.25 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (0.25 * (double)b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + ( + (double) + 0.25 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (0.25 * (double)b_PCIInputClock)) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* ms */ + /******/ + + case 2: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + ul_TimingInterval + * + (250.0 + * + b_PCIInputClock); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (250.0 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (250.0 * (double)b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + (250.0 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (250.0 * (double)b_PCIInputClock)) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 1.007752288); + } + + break; + + /*****/ + /* s */ + /*****/ + + case 3: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + (ul_TimingInterval + * + (250000.0 + * + b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)ul_TimingInterval * (250000.0 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (250000.0 + * + (double) + b_PCIInputClock)); + d_RealTimingInterval + = + (double) + ul_TimerValue + / + (250000.0 + * + (double) + b_PCIInputClock); + + if ((double)((double)ul_TimerValue / (250000.0 * (double)b_PCIInputClock)) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 1.007752288); + } + + break; + + /******/ + /* mn */ + /******/ + + case 4: + + /******************/ + /* Timer 0 factor */ + /******************/ + + ul_TimerValue + = + (unsigned int) + ( + (ul_TimingInterval + * + 60) + * + (250000.0 + * + b_PCIInputClock)); + + /*******************/ + /* Round the value */ + /*******************/ + + if ((double)((double)(ul_TimingInterval * 60.0) * (250000.0 * (double)b_PCIInputClock)) >= ((double)((double)ul_TimerValue + 0.5))) { + ul_TimerValue + = + ul_TimerValue + + + 1; + } + + /*****************************/ + /* Calculate the real timing */ + /*****************************/ + + ul_RealTimingInterval + = + (unsigned int) + (ul_TimerValue + / + (250000.0 + * + (double) + b_PCIInputClock)) + / + 60; + d_RealTimingInterval + = + ( + (double) + ul_TimerValue + / + (250000.0 + * + (double) + b_PCIInputClock)) + / + 60.0; + + if ((double)(((double)ul_TimerValue / (250000.0 * (double)b_PCIInputClock)) / 60.0) >= (double)((double)ul_RealTimingInterval + 0.5)) { + ul_RealTimingInterval + = + ul_RealTimingInterval + + + 1; + } + + ul_TimingInterval + = + ul_TimingInterval + - + 1; + ul_TimerValue + = + ul_TimerValue + - + 2; + + if (b_PCIInputClock != APCI1710_40MHZ) { + ul_TimerValue + = + (unsigned int) + ( + (double) + (ul_TimerValue) + * + 1.007752288); + } + + break; + } + + fpu_end(); + } /* if (b_PCIInputClock != APCI1710_GATE_INPUT) */ + else { + /*************************************************************/ + /* 2 Clock used for the overflow and the reload from counter */ + /*************************************************************/ + + ul_TimerValue + = + ul_TimingInterval + - + 2; + } /* if (b_PCIInputClock != APCI1710_GATE_INPUT) */ + + /****************************/ + /* Save the PCI input clock */ + /****************************/ + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TorCounterModuleInfo. + b_PCIInputClock + = + b_PCIInputClock; + + /************************/ + /* Save the timing unit */ + /************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TorCounterModuleInfo. + s_TorCounterInfo + [b_TorCounter]. + b_TimingUnit + = + b_TimingUnit; + + /************************/ + /* Save the base timing */ + /************************/ + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TorCounterModuleInfo. + s_TorCounterInfo + [b_TorCounter]. + d_TimingInterval + = + d_RealTimingInterval; + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TorCounterModuleInfo. + s_TorCounterInfo + [b_TorCounter]. + ul_RealTimingInterval + = + ul_RealTimingInterval; + + /*******************/ + /* Get the command */ + /*******************/ + + dw_Command + = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + 4 + + + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + dw_Command + = + (dw_Command + >> + 4) + & + 0xF; + + /******************/ + /* Test if 40 MHz */ + /******************/ + + if (b_PCIInputClock == APCI1710_40MHZ) { + /****************************/ + /* Set the 40 MHz selection */ + /****************************/ + + dw_Command + = + dw_Command + | + 0x10; + } + + /*****************************/ + /* Test if extern clock used */ + /*****************************/ + + if (b_PCIInputClock == APCI1710_GATE_INPUT) { + /****************************/ + /* Set the 40 MHz selection */ + /****************************/ + + dw_Command + = + dw_Command + | + 0x20; + } + + /*************************/ + /* Write the new command */ + /*************************/ + + outl(dw_Command, devpriv->s_BoardInfos.ui_Address + 4 + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + /*******************/ + /* Disable the tor */ + /*******************/ + + outl(0, devpriv->s_BoardInfos.ui_Address + 8 + (16 * b_TorCounter) + (64 * b_ModulNbr)); + /*************************/ + /* Set the timer 1 value */ + /*************************/ + + outl(ul_TimerValue, devpriv->s_BoardInfos.ui_Address + 0 + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + /*********************/ + /* Tor counter init. */ + /*********************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TorCounterModuleInfo. + s_TorCounterInfo + [b_TorCounter]. + b_TorCounterInit + = + 1; + } else { + /***********************************************/ + /* TOR version error for 40MHz clock selection */ + /***********************************************/ + + DPRINTK("TOR version error for 40MHz clock selection\n"); + i_ReturnValue + = + -9; + } + } else { + /**************************************************************/ + /* You can not used the 40MHz clock selection wich this board */ + /**************************************************************/ + + DPRINTK("You can not used the 40MHz clock selection wich this board\n"); + i_ReturnValue = + -8; + } + } else { + /**********************************/ + /* Base timing selection is wrong */ + /**********************************/ + + DPRINTK("Base timing selection is wrong\n"); + i_ReturnValue = -7; + } + } /* if ((b_TimingUnit >= 0) && (b_TimingUnit <= 4)) */ + else { + /**********************************/ + /* Timing unit selection is wrong */ + /**********************************/ + + DPRINTK("Timing unit selection is wrong\n"); + i_ReturnValue = -6; + } /* if ((b_TimingUnit >= 0) && (b_TimingUnit <= 4)) */ + } /* if ((b_PCIInputClock == APCI1710_30MHZ) || (b_PCIInputClock == APCI1710_33MHZ)) */ + else { + /*****************************************/ + /* The selected PCI input clock is wrong */ + /*****************************************/ + + DPRINTK("The selected PCI input clock is wrong\n"); + i_ReturnValue = -5; + } /* if ((b_PCIInputClock == APCI1710_30MHZ) || (b_PCIInputClock == APCI1710_33MHZ)) */ + } /* if (b_TorCounterMode >= 0 && b_TorCounterMode <= 7) */ + else { + /**********************************/ + /* Tor Counter selection is wrong */ + /**********************************/ + + DPRINTK("Tor Counter selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_TorCounterMode >= 0 && b_TorCounterMode <= 7) */ + } else { + /******************************************/ + /* The module is not a tor counter module */ + /******************************************/ + + DPRINTK("The module is not a tor counter module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + data[0] = (unsigned int) ul_RealTimingInterval; + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_EnableTorCounter | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TorCounter, | +| unsigned char_ b_InputMode, | +| unsigned char_ b_ExternGate, | +| unsigned char_ b_CycleMode, | +| unsigned char_ b_InterruptEnable) | ++----------------------------------------------------------------------------+ +| Task : Enable the tor counter (b_TorCounter) from selected | +| module (b_ModulNbr). You must calling the | +| "i_APCI1710_InitTorCounter" function be for you call | +| this function. | +| If you enable the tor counter interrupt, the | +| tor counter generate a interrupt after the timing cycle| +| See function "i_APCI1710_SetBoardIntRoutineX" and the | +| Interrupt mask description chapter from this manual. | +| The b_CycleMode parameter determine if you will | +| measured a single or more cycle. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3) | +| unsigned char_ b_TorCounter : Tor counter selection (0 or 1). | +| unsigned char_ b_InputMode : Input signal level selection | +| 0 : Tor count each low level | +| 1 : Tor count each high level| +| unsigned char_ b_ExternGate : Extern gate action selection | +| 0 : Extern gate signal not | +| used | +| 1 : Extern gate signal used. | +| If you selected the | +| single mode, each high | +| level signal start the | +| counter. | +| If you selected the | +| continuous mode, the | +| first high level signal | +| start the tor counter | +| | +| APCI1710_TOR_QUADRUPLE _MODE : | +| In the quadruple mode, the edge| +| analysis circuit generates a | +| counting pulse from each edge | +| of 2 signals which are phase | +| shifted in relation to each | +| other. | +| The gate input is used for the | +| signal B | +| | +| APCI1710_TOR_DOUBLE_MODE: | +| Functions in the same way as | +| the quadruple mode, except that| +| only two of the four edges are | +| analysed per period. | +| The gate input is used for the | +| signal B | +| | +| APCI1710_TOR_SIMPLE_MODE: | +| Functions in the same way as | +| the quadruple mode, except that| +| only one of the four edges is | +| analysed per period. | +| The gate input is used for the | +| signal B | +| | +| unsigned char_ b_CycleMode : Selected the tor counter | +| acquisition mode | +| unsigned char_ b_InterruptEnable : Enable or disable the | +| tor counter interrupt. | +| APCI1710_ENABLE: | +| Enable the tor counter | +| interrupt | +| APCI1710_DISABLE: | +| Disable the tor counter | +| interrupt | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a tor counter module | +| -4: Tor counter selection is wrong | +| -5: Tor counter not initialised see function | +| "i_APCI1710_InitTorCounter" | +| -6: Tor input signal selection is wrong | +| -7: Extern gate signal mode is wrong | +| -8: Tor counter acquisition mode cycle is wrong | +| -9: Interrupt parameter is wrong | +| -10:Interrupt function not initialised. | +| See function "i_APCI1710_SetBoardIntRoutineX" | ++----------------------------------------------------------------------------+ +*/ +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_DisableTorCounter | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TorCounter) | ++----------------------------------------------------------------------------+ +| Task : Disable the tor counter (b_TorCounter) from selected | +| module (b_ModulNbr). If you disable the tor counter | +| after a start cycle occur and you restart the tor | +| counter witch the " i_APCI1710_EnableTorCounter" | +| function, the status register is cleared | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3) | +| unsigned char_ b_TorCounter : Tor counter selection (0 or 1). | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a tor counter module | +| -4: Tor counter selection is wrong | +| -5: Tor counter not initialised see function | +| "i_APCI1710_InitTorCounter" | +| -6: Tor counter not enabled see function | +| "i_APCI1710_EnableTorCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnWriteEnableDisableTorCounter(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_Status; + unsigned int dw_DummyRead; + unsigned int dw_ConfigReg; + unsigned char b_ModulNbr, b_Action; + unsigned char b_TorCounter; + unsigned char b_InputMode; + unsigned char b_ExternGate; + unsigned char b_CycleMode; + unsigned char b_InterruptEnable; + + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_Action = (unsigned char) data[0]; /* enable or disable */ + b_TorCounter = (unsigned char) data[1]; + b_InputMode = (unsigned char) data[2]; + b_ExternGate = (unsigned char) data[3]; + b_CycleMode = (unsigned char) data[4]; + b_InterruptEnable = (unsigned char) data[5]; + i_ReturnValue = insn->n; + devpriv->tsk_Current = current; /* Save the current process task structure */ + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if tor counter */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_TOR_COUNTER) { + /**********************************/ + /* Test the tor counter selection */ + /**********************************/ + + if (b_TorCounter <= 1) { + switch (b_Action) /* Enable or Disable */ + { + case APCI1710_ENABLE: + /***********************************/ + /* Test if tor counter initialised */ + /***********************************/ + + dw_Status = + inl(devpriv->s_BoardInfos. + ui_Address + 8 + + (16 * b_TorCounter) + + (64 * b_ModulNbr)); + + if (dw_Status & 0x10) { + /******************************/ + /* Test the input signal mode */ + /******************************/ + + if (b_InputMode == 0 || + b_InputMode == 1 || + b_InputMode == + APCI1710_TOR_SIMPLE_MODE + || b_InputMode == + APCI1710_TOR_DOUBLE_MODE + || b_InputMode == + APCI1710_TOR_QUADRUPLE_MODE) + { + /************************************/ + /* Test the extern gate signal mode */ + /************************************/ + + if (b_ExternGate == 0 + || b_ExternGate + == 1 + || b_InputMode > + 1) { + /*********************************/ + /* Test the cycle mode parameter */ + /*********************************/ + + if ((b_CycleMode == APCI1710_SINGLE) || (b_CycleMode == APCI1710_CONTINUOUS)) { + /***************************/ + /* Test the interrupt flag */ + /***************************/ + + if ((b_InterruptEnable == APCI1710_ENABLE) || (b_InterruptEnable == APCI1710_DISABLE)) { + + /***************************/ + /* Save the interrupt mode */ + /***************************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TorCounterModuleInfo. + s_TorCounterInfo + [b_TorCounter]. + b_InterruptEnable + = + b_InterruptEnable; + + /*******************/ + /* Get the command */ + /*******************/ + + dw_ConfigReg + = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + 4 + + + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + dw_ConfigReg + = + (dw_ConfigReg + >> + 4) + & + 0x30; + + /********************************/ + /* Test if not direct mode used */ + /********************************/ + + if (b_InputMode > 1) { + /*******************************/ + /* Extern gate can not be used */ + /*******************************/ + + b_ExternGate + = + 0; + + /*******************************************/ + /* Enable the extern gate for the Signal B */ + /*******************************************/ + + dw_ConfigReg + = + dw_ConfigReg + | + 0x40; + + /***********************/ + /* Test if simple mode */ + /***********************/ + + if (b_InputMode == APCI1710_TOR_SIMPLE_MODE) { + /**************************/ + /* Enable the sinple mode */ + /**************************/ + + dw_ConfigReg + = + dw_ConfigReg + | + 0x780; + + } /* if (b_InputMode == APCI1710_TOR_SIMPLE_MODE) */ + + /***********************/ + /* Test if double mode */ + /***********************/ + + if (b_InputMode == APCI1710_TOR_DOUBLE_MODE) { + /**************************/ + /* Enable the double mode */ + /**************************/ + + dw_ConfigReg + = + dw_ConfigReg + | + 0x180; + + } /* if (b_InputMode == APCI1710_TOR_DOUBLE_MODE) */ + + b_InputMode + = + 0; + } /* if (b_InputMode > 1) */ + + /*******************/ + /* Set the command */ + /*******************/ + + dw_ConfigReg + = + dw_ConfigReg + | + b_CycleMode + | + (b_InterruptEnable + * + 2) + | + (b_InputMode + * + 4) + | + (b_ExternGate + * + 8); + + /*****************************/ + /* Clear the status register */ + /*****************************/ + + dw_DummyRead + = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + 0 + + + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + /***************************************/ + /* Clear the interrupt status register */ + /***************************************/ + + dw_DummyRead + = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + 12 + + + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + /********************/ + /* Set the commando */ + /********************/ + + outl(dw_ConfigReg, devpriv->s_BoardInfos.ui_Address + 4 + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + /****************/ + /* Set the gate */ + /****************/ + + outl(1, devpriv->s_BoardInfos.ui_Address + 8 + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + } /* if ((b_InterruptEnable == APCI1710_ENABLE) || (b_InterruptEnable == APCI1710_DISABLE)) */ + else { + /********************************/ + /* Interrupt parameter is wrong */ + /********************************/ + + DPRINTK("Interrupt parameter is wrong\n"); + i_ReturnValue + = + -9; + } /* if ((b_InterruptEnable == APCI1710_ENABLE) || (b_InterruptEnable == APCI1710_DISABLE)) */ + } /* if ((b_CycleMode == APCI1710_SINGLE) || (b_CycleMode == APCI1710_CONTINUOUS)) */ + else { + /***********************************************/ + /* Tor counter acquisition mode cycle is wrong */ + /***********************************************/ + + DPRINTK("Tor counter acquisition mode cycle is wrong\n"); + i_ReturnValue + = + -8; + } /* if ((b_CycleMode == APCI1710_SINGLE) || (b_CycleMode == APCI1710_CONTINUOUS)) */ + } /* if (b_ExternGate >= 0 && b_ExternGate <= 1) */ + else { + /***********************************/ + /* Extern gate input mode is wrong */ + /***********************************/ + + DPRINTK("Extern gate input mode is wrong\n"); + i_ReturnValue = + -7; + } /* if (b_ExternGate >= 0 && b_ExternGate <= 1) */ + } /* if (b_InputMode >= 0 && b_InputMode <= 1) */ + else { + /***************************************/ + /* Tor input signal selection is wrong */ + /***************************************/ + + DPRINTK("Tor input signal selection is wrong\n"); + i_ReturnValue = -6; + } + } else { + /*******************************/ + /* Tor counter not initialised */ + /*******************************/ + + DPRINTK("Tor counter not initialised\n"); + i_ReturnValue = -5; + } + break; + + case APCI1710_DISABLE: + /***********************************/ + /* Test if tor counter initialised */ + /***********************************/ + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 8 + + (16 * b_TorCounter) + + (64 * b_ModulNbr)); + + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (dw_Status & 0x10) { + /***************************/ + /* Test if counter enabled */ + /***************************/ + + if (dw_Status & 0x1) { + /****************************/ + /* Clear the interrupt mode */ + /****************************/ + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TorCounterModuleInfo. + s_TorCounterInfo + [b_TorCounter]. + b_InterruptEnable + = + APCI1710_DISABLE; + + /******************/ + /* Clear the gate */ + /******************/ + + outl(0, devpriv-> + s_BoardInfos. + ui_Address + 8 + + (16 * b_TorCounter) + (64 * b_ModulNbr)); + } /* if (dw_Status & 0x1) */ + else { + /***************************/ + /* Tor counter not enabled */ + /***************************/ + + DPRINTK("Tor counter not enabled \n"); + i_ReturnValue = -6; + } /* if (dw_Status & 0x1) */ + } /* if (dw_Status & 0x10) */ + else { + /*******************************/ + /* Tor counter not initialised */ + /*******************************/ + + DPRINTK("Tor counter not initialised\n"); + i_ReturnValue = -5; + } /* // if (dw_Status & 0x10) */ + + } /* switch */ + } /* if (b_TorCounter <= 1) */ + else { + /**********************************/ + /* Tor counter selection is wrong */ + /**********************************/ + + DPRINTK("Tor counter selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_TorCounter <= 1) */ + } else { + /******************************************/ + /* The module is not a tor counter module */ + /******************************************/ + + DPRINTK("The module is not a tor counter module \n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error \n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_GetTorCounterInitialisation | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TorCounter, | +| unsigned char *_ pb_TimingUnit, | +| PULONG_ pul_TimingInterval, | +| unsigned char *_ pb_InputMode, | +| unsigned char *_ pb_ExternGate, | +| unsigned char *_ pb_CycleMode, | +| unsigned char *_ pb_Enable, | +| unsigned char *_ pb_InterruptEnable)| ++----------------------------------------------------------------------------+ +| Task : Enable the tor counter (b_TorCounter) from selected | +| module (b_ModulNbr). You must calling the | +| "i_APCI1710_InitTorCounter" function be for you call | +| this function. | +| If you enable the tor counter interrupt, the | +| tor counter generate a interrupt after the timing cycle| +| See function "i_APCI1710_SetBoardIntRoutineX" and the | +| Interrupt mask description chapter from this manual. | +| The b_CycleMode parameter determine if you will | +| measured a single or more cycle. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3) | +| unsigned char_ b_TorCounter : Tor counter selection (0 or 1) + + b_ModulNbr = CR_AREF(insn->chanspec); + b_TorCounter = CR_CHAN(insn->chanspec); +. | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_TimingUnit : Base timing unit (0 to 4) | +| 0 : ns | +| 1 : µs | +| 2 : ms | +| 3 : s | +| 4 : mn | +| PULONG_ pul_TimingInterval : Base timing value. | +| unsigned char *_ pb_InputMode : Input signal level | +| selection | +| 0 : Tor count each low level | +| 1 : Tor count each high level| +| unsigned char *_ pb_ExternGate : Extern gate action | +| selection | +| 0 : Extern gate signal not | +| used | +| 1 : Extern gate signal used| +| unsigned char *_ pb_CycleMode : Tor counter acquisition | +| mode | +| unsigned char *_ pb_Enable : Indicate if the tor counter| +| is enabled or no | +| 0 : Tor counter disabled | +| 1 : Tor counter enabled | +| unsigned char *_ pb_InterruptEnable : Enable or disable the | +| tor counter interrupt. | +| APCI1710_ENABLE: | +| Enable the tor counter | +| interrupt | +| APCI1710_DISABLE: | +| Disable the tor counter | +| interrupt + pb_TimingUnit = (unsigned char *) &data[0]; + pul_TimingInterval = (unsigned int *) &data[1]; + pb_InputMode = (unsigned char *) &data[2]; + pb_ExternGate = (unsigned char *) &data[3]; + pb_CycleMode = (unsigned char *) &data[4]; + pb_Enable = (unsigned char *) &data[5]; + pb_InterruptEnable = (unsigned char *) &data[6]; + | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a tor counter module | +| -4: Tor counter selection is wrong | +| -5: Tor counter not initialised see function | +| "i_APCI1710_InitTorCounter" | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnReadGetTorCounterInitialisation(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_Status; + unsigned char b_ModulNbr; + unsigned char b_TorCounter; + unsigned char *pb_TimingUnit; + unsigned int *pul_TimingInterval; + unsigned char *pb_InputMode; + unsigned char *pb_ExternGate; + unsigned char *pb_CycleMode; + unsigned char *pb_Enable; + unsigned char *pb_InterruptEnable; + + i_ReturnValue = insn->n; + b_ModulNbr = CR_AREF(insn->chanspec); + b_TorCounter = CR_CHAN(insn->chanspec); + + pb_TimingUnit = (unsigned char *) &data[0]; + pul_TimingInterval = (unsigned int *) &data[1]; + pb_InputMode = (unsigned char *) &data[2]; + pb_ExternGate = (unsigned char *) &data[3]; + pb_CycleMode = (unsigned char *) &data[4]; + pb_Enable = (unsigned char *) &data[5]; + pb_InterruptEnable = (unsigned char *) &data[6]; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if tor counter */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_TOR_COUNTER) { + /**********************************/ + /* Test the tor counter selection */ + /**********************************/ + + if (b_TorCounter <= 1) { + + /***********************************/ + /* Test if tor counter initialised */ + /***********************************/ + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 8 + (16 * b_TorCounter) + + (64 * b_ModulNbr)); + + if (dw_Status & 0x10) { + *pb_Enable = dw_Status & 1; + + /********************/ + /* Get the commando */ + /********************/ + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 4 + + (16 * b_TorCounter) + + (64 * b_ModulNbr)); + + *pb_CycleMode = + (unsigned char) ((dw_Status >> 4) & 1); + *pb_InterruptEnable = + (unsigned char) ((dw_Status >> 5) & 1); + + /******************************************************/ + /* Test if extern gate used for clock or for signal B */ + /******************************************************/ + + if (dw_Status & 0x600) { + /*****************************************/ + /* Test if extern gate used for signal B */ + /*****************************************/ + + if (dw_Status & 0x400) { + /***********************/ + /* Test if simple mode */ + /***********************/ + + if ((dw_Status & 0x7800) + == 0x7800) { + *pb_InputMode = + APCI1710_TOR_SIMPLE_MODE; + } + + /***********************/ + /* Test if double mode */ + /***********************/ + + if ((dw_Status & 0x7800) + == 0x1800) { + *pb_InputMode = + APCI1710_TOR_DOUBLE_MODE; + } + + /**************************/ + /* Test if quadruple mode */ + /**************************/ + + if ((dw_Status & 0x7800) + == 0x0000) { + *pb_InputMode = + APCI1710_TOR_QUADRUPLE_MODE; + } + } /* if (dw_Status & 0x400) */ + else { + *pb_InputMode = 1; + } /* // if (dw_Status & 0x400) */ + + /************************/ + /* Extern gate not used */ + /************************/ + + *pb_ExternGate = 0; + } /* if (dw_Status & 0x600) */ + else { + *pb_InputMode = + (unsigned char) ((dw_Status >> 6) + & 1); + *pb_ExternGate = + (unsigned char) ((dw_Status >> 7) + & 1); + } /* if (dw_Status & 0x600) */ + + *pb_TimingUnit = + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_TorCounterModuleInfo. + s_TorCounterInfo[b_TorCounter]. + b_TimingUnit; + + *pul_TimingInterval = + devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_TorCounterModuleInfo. + s_TorCounterInfo[b_TorCounter]. + ul_RealTimingInterval; + } else { + /*******************************/ + /* Tor counter not initialised */ + /*******************************/ + + DPRINTK("Tor counter not initialised\n"); + i_ReturnValue = -5; + } + + } /* if (b_TorCounter <= 1) */ + else { + /**********************************/ + /* Tor counter selection is wrong */ + /**********************************/ + + DPRINTK("Tor counter selection is wrong \n"); + i_ReturnValue = -4; + } /* if (b_TorCounter <= 1) */ + } else { + /******************************************/ + /* The module is not a tor counter module */ + /******************************************/ + + DPRINTK("The module is not a tor counter module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadTorCounterValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_TorCounter, | +| unsigned int_ ui_TimeOut, | +| unsigned char *_ pb_TorCounterStatus, | +| PULONG_ pul_TorCounterValue) | ++----------------------------------------------------------------------------+ +| Task case APCI1710_TOR_GETPROGRESSSTATUS: Return the tor counter +(b_TorCounter) status (pb_TorCounterStatus) from selected tor counter | +| module (b_ModulNbr). + + case APCI1710_TOR_GETCOUNTERVALUE : + Return the tor counter (b_TorCounter) status | +| (pb_TorCounterStatus) and the timing value | +| (pul_TorCounterValue) after a conting cycle stop | +| from selected tor counter module (b_ModulNbr). | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3) | +| unsigned char_ b_TorCounter : Tor counter selection (0 or 1). + b_ModulNbr = CR_AREF(insn->chanspec); + b_ReadType = (unsigned char) data[0]; + b_TorCounter = (unsigned char) data[1]; + ui_TimeOut = (unsigned int) data[2]; | ++----------------------------------------------------------------------------+ +| Output Parameters : unsigned char *_ pb_TorCounterStatus : Return the tor counter | +| status. | +| 0 : Conting cycle not started| +| Software gate not set. | +| 1 : Conting cycle started. | +| Software gate set. | +| 2 : Conting cycle stopped. | +| The conting cycle is | +| terminate. | +| 3 : A overflow occur. You | +| must change the base | +| timing witch the | +| function | +| "i_APCI1710_InitTorCounter"| +| 4 : Timeeout occur | +| unsigned int * pul_TorCounterValue : Tor counter value. + pb_TorCounterStatus=(unsigned char *) &data[0]; + pul_TorCounterValue=(unsigned int *) &data[1]; | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: Module selection wrong | +| -3: The module is not a tor counter module | +| -4: Tor counter selection is wrong | +| -5: Tor counter not initialised see function | +| "i_APCI1710_InitTorCounter" | +| -6: Tor counter not enabled see function | +| "i_APCI1710_EnableTorCounter" | +| -7: Timeout parameter is wrong (0 to 65535) | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnBitsGetTorCounterProgressStatusAndValue(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_Status; + unsigned int dw_TimeOut = 0; + + unsigned char b_ModulNbr; + unsigned char b_TorCounter; + unsigned char b_ReadType; + unsigned int ui_TimeOut; + unsigned char *pb_TorCounterStatus; + unsigned int *pul_TorCounterValue; + + i_ReturnValue = insn->n; + b_ModulNbr = CR_AREF(insn->chanspec); + b_ReadType = (unsigned char) data[0]; + b_TorCounter = (unsigned char) data[1]; + ui_TimeOut = (unsigned int) data[2]; + pb_TorCounterStatus = (unsigned char *) &data[0]; + pul_TorCounterValue = (unsigned int *) &data[1]; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ReadType == APCI1710_TOR_READINTERRUPT) { + + data[0] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].b_OldModuleMask; + data[1] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldInterruptMask; + data[2] = devpriv->s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters.ui_Read].ul_OldCounterLatchValue; + + /**************************/ + /* Increment the read FIFO */ + /***************************/ + + devpriv-> + s_InterruptParameters. + ui_Read = (devpriv-> + s_InterruptParameters. + ui_Read + 1) % APCI1710_SAVE_INTERRUPT; + + return insn->n; + } + + if (b_ModulNbr < 4) { + /***********************/ + /* Test if tor counter */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_TOR_COUNTER) { + /**********************************/ + /* Test the tor counter selection */ + /**********************************/ + + if (b_TorCounter <= 1) { + /***********************************/ + /* Test if tor counter initialised */ + /***********************************/ + + dw_Status = inl(devpriv->s_BoardInfos. + ui_Address + 8 + (16 * b_TorCounter) + + (64 * b_ModulNbr)); + + /*******************************/ + /* Test if counter initialised */ + /*******************************/ + + if (dw_Status & 0x10) { + /***************************/ + /* Test if counter enabled */ + /***************************/ + + if (dw_Status & 0x1) { + + switch (b_ReadType) { + + case APCI1710_TOR_GETPROGRESSSTATUS: + /*******************/ + /* Read the status */ + /*******************/ + + dw_Status = + inl(devpriv-> + s_BoardInfos. + ui_Address + 4 + + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + dw_Status = + dw_Status & 0xF; + + /*****************/ + /* Test if start */ + /*****************/ + + if (dw_Status & 1) { + if (dw_Status & + 2) { + if (dw_Status & 4) { + /************************/ + /* Tor counter overflow */ + /************************/ + + *pb_TorCounterStatus + = + 3; + } else { + /***********************/ + /* Tor counter started */ + /***********************/ + + *pb_TorCounterStatus + = + 2; + } + } else { + /***********************/ + /* Tor counter started */ + /***********************/ + + *pb_TorCounterStatus + = + 1; + } + } else { + /***************************/ + /* Tor counter not started */ + /***************************/ + + *pb_TorCounterStatus + = 0; + } + break; + + case APCI1710_TOR_GETCOUNTERVALUE: + + /*****************************/ + /* Test the timout parameter */ + /*****************************/ + + if ((ui_TimeOut >= 0) + && (ui_TimeOut + <= + 65535UL)) + { + for (;;) { + /*******************/ + /* Read the status */ + /*******************/ + + dw_Status + = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + 4 + + + (16 * b_TorCounter) + (64 * b_ModulNbr)); + /********************/ + /* Test if overflow */ + /********************/ + + if ((dw_Status & 4) == 4) { + /******************/ + /* Overflow occur */ + /******************/ + + *pb_TorCounterStatus + = + 3; + + /******************/ + /* Read the value */ + /******************/ + + *pul_TorCounterValue + = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + 0 + + + (16 * b_TorCounter) + (64 * b_ModulNbr)); + break; + } /* if ((dw_Status & 4) == 4) */ + else { + /*******************************/ + /* Test if measurement stopped */ + /*******************************/ + + if ((dw_Status & 2) == 2) { + /***********************/ + /* A stop signal occur */ + /***********************/ + + *pb_TorCounterStatus + = + 2; + + /******************/ + /* Read the value */ + /******************/ + + *pul_TorCounterValue + = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + 0 + + + (16 * b_TorCounter) + (64 * b_ModulNbr)); + + break; + } /* if ((dw_Status & 2) == 2) */ + else { + /*******************************/ + /* Test if measurement started */ + /*******************************/ + + if ((dw_Status & 1) == 1) { + /************************/ + /* A start signal occur */ + /************************/ + + *pb_TorCounterStatus + = + 1; + } /* if ((dw_Status & 1) == 1) */ + else { + /***************************/ + /* Measurement not started */ + /***************************/ + + *pb_TorCounterStatus + = + 0; + } /* if ((dw_Status & 1) == 1) */ + } /* if ((dw_Status & 2) == 2) */ + } /* if ((dw_Status & 8) == 8) */ + + if (dw_TimeOut == ui_TimeOut) { + /*****************/ + /* Timeout occur */ + /*****************/ + + break; + } else { + /*************************/ + /* Increment the timeout */ + /*************************/ + + dw_TimeOut + = + dw_TimeOut + + + 1; + + mdelay(1000); + } + } /* for (;;) */ + + /*************************/ + /* Test if timeout occur */ + /*************************/ + + if ((*pb_TorCounterStatus != 3) && (dw_TimeOut == ui_TimeOut) && (ui_TimeOut != 0)) { + /*****************/ + /* Timeout occur */ + /*****************/ + + *pb_TorCounterStatus + = + 4; + } + } else { + /******************************/ + /* Timeout parameter is wrong */ + /******************************/ + + DPRINTK("Timeout parameter is wrong\n"); + i_ReturnValue = + -7; + } + break; + + default: + printk("Inputs wrong\n"); + } /* switch end */ + } /* if (dw_Status & 0x1) */ + else { + /***************************/ + /* Tor counter not enabled */ + /***************************/ + + DPRINTK("Tor counter not enabled\n"); + i_ReturnValue = -6; + } /* if (dw_Status & 0x1) */ + } else { + /*******************************/ + /* Tor counter not initialised */ + /*******************************/ + + DPRINTK("Tor counter not initialised\n"); + i_ReturnValue = -5; + } + } /* if (b_TorCounter <= 1) */ + else { + /**********************************/ + /* Tor counter selection is wrong */ + /**********************************/ + + DPRINTK("Tor counter selection is wrong\n"); + i_ReturnValue = -4; + } /* if (b_TorCounter <= 1) */ + } else { + /******************************************/ + /* The module is not a tor counter module */ + /******************************************/ + + DPRINTK("The module is not a tor counter module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.h b/drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.h new file mode 100644 index 00000000..537d4755 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Tor.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define APCI1710_30MHZ 30 +#define APCI1710_33MHZ 33 +#define APCI1710_40MHZ 40 + +#define APCI1710_GATE_INPUT 10 + +#define APCI1710_TOR_SIMPLE_MODE 2 +#define APCI1710_TOR_DOUBLE_MODE 3 +#define APCI1710_TOR_QUADRUPLE_MODE 4 + +#define APCI1710_SINGLE 0 +#define APCI1710_CONTINUOUS 1 + +#define APCI1710_TOR_GETPROGRESSSTATUS 0 +#define APCI1710_TOR_GETCOUNTERVALUE 1 +#define APCI1710_TOR_READINTERRUPT 2 + +/* + * TOR_COUNTER INISIALISATION FUNCTION + */ +int i_APCI1710_InsnConfigInitTorCounter(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1710_InsnWriteEnableDisableTorCounter(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +int i_APCI1710_InsnReadGetTorCounterInitialisation(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +/* + * TOR_COUNTER READ FUNCTION + */ +int i_APCI1710_InsnBitsGetTorCounterProgressStatusAndValue(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Ttl.c b/drivers/staging/comedi/drivers/addi-data/APCI1710_Ttl.c new file mode 100644 index 00000000..9e177f4a --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Ttl.c @@ -0,0 +1,1038 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : API APCI1710 | Compiler : gcc | + | Module name : TTL.C | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description : APCI-1710 TTL I/O module | + | | + | | + +-----------------------------------------------------------------------+ + | UPDATES | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | 13/05/98 | S. Weber | TTL digital input / output implementation | + |----------|-----------|------------------------------------------------| + | 08/05/00 | Guinot C | - 0400/0228 All Function in RING 0 | + | | | available | + +-----------------------------------------------------------------------+ + | | | | + | | | | + +-----------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ + +#include "APCI1710_Ttl.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_InitTTLIODirection | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_PortAMode, | +| unsigned char_ b_PortBMode, | +| unsigned char_ b_PortCMode, | +| unsigned char_ b_PortDMode) | ++----------------------------------------------------------------------------+ +| Task APCI1710_TTL_INIT (using defaults) : Configure the TTL I/O operating mode from selected | +| module (b_ModulNbr). You must calling this function be| +| for you call any other function witch access of TTL. | + APCI1710_TTL_INITDIRECTION(user inputs for direction) + ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 3) + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_InitType = (unsigned char) data[0]; + b_PortAMode = (unsigned char) data[1]; + b_PortBMode = (unsigned char) data[2]; + b_PortCMode = (unsigned char) data[3]; + b_PortDMode = (unsigned char) data[4];| ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a TTL module | +| -4: Function not available for this version | +| -5: Port A mode selection is wrong | +| -6: Port B mode selection is wrong | +| -7: Port C mode selection is wrong | +| -8: Port D mode selection is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnConfigInitTTLIO(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned char b_ModulNbr; + unsigned char b_InitType; + unsigned char b_PortAMode; + unsigned char b_PortBMode; + unsigned char b_PortCMode; + unsigned char b_PortDMode; + + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + b_InitType = (unsigned char) data[0]; + i_ReturnValue = insn->n; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /**************************/ + /* Test if TTL I/O module */ + /**************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_TTL_IO) { + switch (b_InitType) { + case APCI1710_TTL_INIT: + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo.b_TTLInit = 1; + + /***************************/ + /* Set TTL port A to input */ + /***************************/ + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo.b_PortConfiguration[0] = 0; + + /***************************/ + /* Set TTL port B to input */ + /***************************/ + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo.b_PortConfiguration[1] = 0; + + /***************************/ + /* Set TTL port C to input */ + /***************************/ + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo.b_PortConfiguration[2] = 0; + + /****************************/ + /* Set TTL port D to output */ + /****************************/ + + devpriv->s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo.b_PortConfiguration[3] = 1; + + /*************************/ + /* Set the configuration */ + /*************************/ + + outl(0x8, + devpriv->s_BoardInfos.ui_Address + 20 + + (64 * b_ModulNbr)); + break; + + case APCI1710_TTL_INITDIRECTION: + + b_PortAMode = (unsigned char) data[1]; + b_PortBMode = (unsigned char) data[2]; + b_PortCMode = (unsigned char) data[3]; + b_PortDMode = (unsigned char) data[4]; + + /********************/ + /* Test the version */ + /********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & 0xFFFF) >= + 0x3230) { + /************************/ + /* Test the port A mode */ + /************************/ + + if ((b_PortAMode == 0) + || (b_PortAMode == 1)) { + /************************/ + /* Test the port B mode */ + /************************/ + + if ((b_PortBMode == 0) + || (b_PortBMode == 1)) { + /************************/ + /* Test the port C mode */ + /************************/ + + if ((b_PortCMode == 0) + || (b_PortCMode + == 1)) { + /************************/ + /* Test the port D mode */ + /************************/ + + if ((b_PortDMode == 0) || (b_PortDMode == 1)) { + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo. + b_TTLInit + = + 1; + + /***********************/ + /* Set TTL port A mode */ + /***********************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration + [0] + = + b_PortAMode; + + /***********************/ + /* Set TTL port B mode */ + /***********************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration + [1] + = + b_PortBMode; + + /***********************/ + /* Set TTL port C mode */ + /***********************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration + [2] + = + b_PortCMode; + + /***********************/ + /* Set TTL port D mode */ + /***********************/ + + devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration + [3] + = + b_PortDMode; + + /*************************/ + /* Set the configuration */ + /*************************/ + + outl((b_PortAMode << 0) | (b_PortBMode << 1) | (b_PortCMode << 2) | (b_PortDMode << 3), devpriv->s_BoardInfos.ui_Address + 20 + (64 * b_ModulNbr)); + } else { + /**********************************/ + /* Port D mode selection is wrong */ + /**********************************/ + + DPRINTK("Port D mode selection is wrong\n"); + i_ReturnValue + = + -8; + } + } else { + /**********************************/ + /* Port C mode selection is wrong */ + /**********************************/ + + DPRINTK("Port C mode selection is wrong\n"); + i_ReturnValue = + -7; + } + } else { + /**********************************/ + /* Port B mode selection is wrong */ + /**********************************/ + + DPRINTK("Port B mode selection is wrong\n"); + i_ReturnValue = -6; + } + } else { + /**********************************/ + /* Port A mode selection is wrong */ + /**********************************/ + + DPRINTK("Port A mode selection is wrong\n"); + i_ReturnValue = -5; + } + } else { + /*******************************************/ + /* Function not available for this version */ + /*******************************************/ + + DPRINTK("Function not available for this version\n"); + i_ReturnValue = -4; + } + break; + + DPRINTK("\n"); + default: + printk("Bad Config Type\n"); + } /* switch end */ + } else { + /**********************************/ + /* The module is not a TTL module */ + /**********************************/ + + DPRINTK("The module is not a TTL module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| INPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_ReadTTLIOChannelValue | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_SelectedPort, | +| unsigned char_ b_InputChannel, | +| unsigned char *_ pb_ChannelStatus) | ++----------------------------------------------------------------------------+ +| Task : Read the status from selected TTL digital input | +| (b_InputChannel) ++----------------------------------------------------------------------------+ +| Task : Read the status from digital input port | +| (b_SelectedPort) from selected TTL module (b_ModulNbr) | ++----------------------------------------------------------------------------+ + ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 7) | +| unsigned char_ b_SelectedPort, : Selection from TTL I/O | +| port (0 to 2) | +| 0 : Port A selection | +| 1 : Port B selection | +| 2 : Port C selection | +| 3 : Port D selection | +| unsigned char_ b_InputChannel : Selection from digital | +| input ( 0 to 2) +APCI1710_TTL_READCHANNEL + b_ModulNbr = CR_AREF(insn->chanspec); + b_SelectedPort= CR_RANGE(insn->chanspec); + b_InputChannel= CR_CHAN(insn->chanspec); + b_ReadType = (unsigned char) data[0]; + + APCI1710_TTL_READPORT| + b_ModulNbr = CR_AREF(insn->chanspec); + b_SelectedPort= CR_RANGE(insn->chanspec); + b_ReadType = (unsigned char) data[0]; + ++----------------------------------------------------------------------------+ +| Output Parameters : data[0] + + unsigned char *_ pb_ChannelStatus : Digital input channel | +| status | +| 0 : Channle is not active| +| 1 : Channle is active | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a TTL module | +| -4: The selected TTL input port is wrong | +| -5: The selected TTL digital input is wrong | +| -6: TTL I/O not initialised | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnBitsReadTTLIO(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg; + unsigned char b_ModulNbr; + unsigned char b_SelectedPort; + unsigned char b_InputChannel; + unsigned char b_ReadType; + unsigned char *pb_ChannelStatus; + unsigned char *pb_PortValue; + + i_ReturnValue = insn->n; + b_ReadType = (unsigned char) data[0]; + b_ModulNbr = CR_AREF(insn->chanspec); + b_SelectedPort = CR_RANGE(insn->chanspec); + b_InputChannel = CR_CHAN(insn->chanspec); + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /**************************/ + /* Test if TTL I/O module */ + /**************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_TTL_IO) { + switch (b_ReadType) { + + case APCI1710_TTL_READCHANNEL: + pb_ChannelStatus = (unsigned char *) &data[0]; + /********************************/ + /* Test the TTL I/O port number */ + /********************************/ + + if (((b_SelectedPort <= 2) + && ((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & + 0xFFFF) == + 0x3130)) + || ((b_SelectedPort <= 3) + && ((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & + 0xFFFF) >= + 0x3230))) { + /******************************************/ + /* Test the digital imnput channel number */ + /******************************************/ + + if (((b_InputChannel <= 7) + && (b_SelectedPort < 3)) + || ((b_InputChannel <= 1) + && (b_SelectedPort == + 3))) { + /******************************************/ + /* Test if the TTL I/O module initialised */ + /******************************************/ + + if (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo.b_TTLInit == + 1) { + /***********************************/ + /* Test if TTL port used for input */ + /***********************************/ + + if (((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF) == 0x3130) || (((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF) >= 0x3230) && (devpriv->s_ModuleInfo[b_ModulNbr].s_TTLIOInfo.b_PortConfiguration[b_SelectedPort] == 0))) { + /**************************/ + /* Read all digital input */ + /**************************/ + + dw_StatusReg = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + (64 * b_ModulNbr)); + + *pb_ChannelStatus + = + (unsigned char) ( + (dw_StatusReg + >> + (8 * b_SelectedPort)) >> b_InputChannel) & 1; + } else { + /*******************************/ + /* Selected TTL I/O port error */ + /*******************************/ + + DPRINTK("Selected TTL I/O port error\n"); + i_ReturnValue = + -4; + } + } else { + /***************************/ + /* TTL I/O not initialised */ + /***************************/ + + DPRINTK("TTL I/O not initialised\n"); + i_ReturnValue = -6; + } + } else { + /********************************/ + /* Selected digital input error */ + /********************************/ + + DPRINTK("Selected digital input error\n"); + i_ReturnValue = -5; + } + } else { + /*******************************/ + /* Selected TTL I/O port error */ + /*******************************/ + + DPRINTK("Selected TTL I/O port error\n"); + i_ReturnValue = -4; + } + break; + + case APCI1710_TTL_READPORT: + pb_PortValue = (unsigned char *) &data[0]; + /********************************/ + /* Test the TTL I/O port number */ + /********************************/ + + if (((b_SelectedPort <= 2) + && ((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & + 0xFFFF) == + 0x3130)) + || ((b_SelectedPort <= 3) + && ((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & + 0xFFFF) >= + 0x3230))) { + /******************************************/ + /* Test if the TTL I/O module initialised */ + /******************************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo.b_TTLInit == 1) { + /***********************************/ + /* Test if TTL port used for input */ + /***********************************/ + + if (((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] + & + 0xFFFF) + == 0x3130) + || (((devpriv->s_BoardInfos.dw_MolduleConfiguration[b_ModulNbr] & 0xFFFF) >= 0x3230) && (devpriv->s_ModuleInfo[b_ModulNbr].s_TTLIOInfo.b_PortConfiguration[b_SelectedPort] == 0))) { + /**************************/ + /* Read all digital input */ + /**************************/ + + dw_StatusReg = + inl(devpriv-> + s_BoardInfos. + ui_Address + + (64 * b_ModulNbr)); + + *pb_PortValue = + (unsigned char) ( + (dw_StatusReg >> + (8 * b_SelectedPort)) & 0xFF); + } else { + /*******************************/ + /* Selected TTL I/O port error */ + /*******************************/ + + DPRINTK("Selected TTL I/O port error\n"); + i_ReturnValue = -4; + } + } else { + /***************************/ + /* TTL I/O not initialised */ + /***************************/ + + DPRINTK("TTL I/O not initialised\n"); + i_ReturnValue = -5; + } + } else { + /*******************************/ + /* Selected TTL I/O port error */ + /*******************************/ + + DPRINTK("Selected TTL I/O port error\n"); + i_ReturnValue = -4; + } + break; + + default: + printk("Bad ReadType\n"); + + } /* End Switch */ + } else { + /**********************************/ + /* The module is not a TTL module */ + /**********************************/ + + DPRINTK("The module is not a TTL module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1710_InsnReadTTLIOAllPortValue(comedi_device +*dev,struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read the status from all digital input ports | +| (port A, port B and port C) from selected TTL | +| module (b_ModulNbr) | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710| +| unsigned char_ b_ModulNbr : Module number to | +| configure (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : PULONG_ pul_PortValue : Digital TTL inputs port | +| status | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a TTL module | +| -4: TTL I/O not initialised | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnReadTTLIOAllPortValue(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg; + unsigned char b_ModulNbr; + unsigned int *pul_PortValue; + + b_ModulNbr = (unsigned char) CR_AREF(insn->chanspec); + i_ReturnValue = insn->n; + pul_PortValue = (unsigned int *) &data[0]; + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /**************************/ + /* Test if TTL I/O module */ + /**************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_TTL_IO) { + /******************************************/ + /* Test if the TTL I/O module initialised */ + /******************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo.b_TTLInit == 1) { + /**************************/ + /* Read all digital input */ + /**************************/ + + dw_StatusReg = inl(devpriv->s_BoardInfos. + ui_Address + (64 * b_ModulNbr)); + + /**********************/ + /* Test if TTL Rev1.0 */ + /**********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & 0xFFFF) == + 0x3130) { + *pul_PortValue = + dw_StatusReg & 0xFFFFFFUL; + } else { + /**************************************/ + /* Test if port A not used for output */ + /**************************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration[0] == 1) { + *pul_PortValue = + dw_StatusReg & + 0x3FFFF00UL; + } + + /**************************************/ + /* Test if port B not used for output */ + /**************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration[1] == 1) { + *pul_PortValue = + dw_StatusReg & + 0x3FF00FFUL; + } + + /**************************************/ + /* Test if port C not used for output */ + /**************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration[2] == 1) { + *pul_PortValue = + dw_StatusReg & + 0x300FFFFUL; + } + + /**************************************/ + /* Test if port D not used for output */ + /**************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration[3] == 1) { + *pul_PortValue = + dw_StatusReg & + 0xFFFFFFUL; + } + } + } else { + /***************************/ + /* TTL I/O not initialised */ + /***************************/ + DPRINTK("TTL I/O not initialised\n"); + i_ReturnValue = -5; + } + } else { + /**********************************/ + /* The module is not a TTL module */ + /**********************************/ + DPRINTK("The module is not a TTL module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| OUTPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : _INT_ i_APCI1710_SetTTLIOChlOn | +| (unsigned char_ b_BoardHandle, | +| unsigned char_ b_ModulNbr, | +| unsigned char_ b_OutputChannel) +int i_APCI1710_InsnWriteSetTTLIOChlOnOff(struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Sets or resets the output witch has been passed with the | +| parameter b_Channel. Setting an output means setting | +| an ouput high. | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char_ b_BoardHandle : Handle of board APCI-1710 | +| unsigned char_ b_ModulNbr : Selected module number (0 to 3)| +| unsigned char_ b_OutputChannel : Selection from digital output | +| channel (0 or 1) | +| 0 : PD0 | +| 1 : PD1 | +| 2 to 9 : PA | +| 10 to 17: PB | +| 18 to 25: PC | + + b_ModulNbr = CR_AREF(insn->chanspec); + b_OutputChannel= CR_CHAN(insn->chanspec); + ui_State = data[0]; /* ON or OFF */ ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -1: The handle parameter of the board is wrong | +| -2: The module parameter is wrong | +| -3: The module is not a TTL I/O module | +| -4: The selected digital output is wrong | +| -5: TTL I/O not initialised see function | +| " i_APCI1710_InitTTLIO" ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1710_InsnWriteSetTTLIOChlOnOff(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = 0; + unsigned int dw_StatusReg = 0; + unsigned char b_ModulNbr; + unsigned char b_OutputChannel; + unsigned int ui_State; + + i_ReturnValue = insn->n; + b_ModulNbr = CR_AREF(insn->chanspec); + b_OutputChannel = CR_CHAN(insn->chanspec); + ui_State = data[0]; /* ON or OFF */ + + /**************************/ + /* Test the module number */ + /**************************/ + + if (b_ModulNbr < 4) { + /**************************/ + /* Test if TTL I/O module */ + /**************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModulNbr] & + 0xFFFF0000UL) == APCI1710_TTL_IO) { + /******************************************/ + /* Test if the TTL I/O module initialised */ + /******************************************/ + + if (devpriv->s_ModuleInfo[b_ModulNbr]. + s_TTLIOInfo.b_TTLInit == 1) { + /***********************************/ + /* Test the TTL I/O channel number */ + /***********************************/ + + if (((b_OutputChannel <= 1) + && ((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & + 0xFFFF) == + 0x3130)) + || ((b_OutputChannel <= 25) + && ((devpriv->s_BoardInfos. + dw_MolduleConfiguration + [b_ModulNbr] & + 0xFFFF) >= + 0x3230))) { + /****************************************************/ + /* Test if the selected channel is a output channel */ + /****************************************************/ + + if (((b_OutputChannel <= 1) + && (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration + [3] == 1)) + || ((b_OutputChannel >= 2) + && (b_OutputChannel <= + 9) + && (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration + [0] == 1)) + || ((b_OutputChannel >= 10) + && (b_OutputChannel <= + 17) + && (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration + [1] == 1)) + || ((b_OutputChannel >= 18) + && (b_OutputChannel <= + 25) + && (devpriv-> + s_ModuleInfo + [b_ModulNbr]. + s_TTLIOInfo. + b_PortConfiguration + [2] == 1))) { + /************************/ + /* Test if PD0 selected */ + /************************/ + + if (b_OutputChannel == 0) { + + outl(ui_State, + devpriv-> + s_BoardInfos. + ui_Address + + (64 * b_ModulNbr)); + } else { + /************************/ + /* Test if PD1 selected */ + /************************/ + + if (b_OutputChannel == + 1) { + + outl(ui_State, + devpriv-> + s_BoardInfos. + ui_Address + + 4 + + (64 * b_ModulNbr)); + } else { + b_OutputChannel + = + b_OutputChannel + - 2; + + /********************/ + /* Read all channel */ + /********************/ + + dw_StatusReg = + inl + (devpriv-> + s_BoardInfos. + ui_Address + + + (64 * b_ModulNbr)); + if (ui_State) /* ON */ + { + dw_StatusReg + = + (dw_StatusReg + >> + ((b_OutputChannel / 8) * 8)) & 0xFF; + dw_StatusReg + = + dw_StatusReg + | + (1 + << + (b_OutputChannel + % + 8)); + } else /* Off */ + { + dw_StatusReg + = + (dw_StatusReg + >> + ((b_OutputChannel / 8) * 8)) & 0xFF; + dw_StatusReg + = + dw_StatusReg + & + (0xFF + - + (1 << (b_OutputChannel % 8))); + + } + + /****************************/ + /* Set the new output value */ + /****************************/ + + outl(dw_StatusReg, devpriv->s_BoardInfos.ui_Address + 8 + ((b_OutputChannel / 8) * 4) + (64 * b_ModulNbr)); + } + } + } else { + /************************************/ + /* The selected TTL output is wrong */ + /************************************/ + + DPRINTK(" The selected TTL output is wrong\n"); + i_ReturnValue = -4; + } + } else { + /************************************/ + /* The selected TTL output is wrong */ + /************************************/ + + DPRINTK("The selected TTL output is wrong\n"); + i_ReturnValue = -4; + } + } else { + /***************************/ + /* TTL I/O not initialised */ + /***************************/ + + DPRINTK("TTL I/O not initialised\n"); + i_ReturnValue = -5; + } + } else { + /**************************************/ + /* The module is not a TTL I/O module */ + /**************************************/ + + DPRINTK("The module is not a TTL I/O module\n"); + i_ReturnValue = -3; + } + } else { + /***********************/ + /* Module number error */ + /***********************/ + + DPRINTK("Module number error\n"); + i_ReturnValue = -2; + } + + return i_ReturnValue; +} diff --git a/drivers/staging/comedi/drivers/addi-data/APCI1710_Ttl.h b/drivers/staging/comedi/drivers/addi-data/APCI1710_Ttl.h new file mode 100644 index 00000000..adcab824 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/APCI1710_Ttl.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define APCI1710_TTL_INIT 0 +#define APCI1710_TTL_INITDIRECTION 1 + +#define APCI1710_TTL_READCHANNEL 0 +#define APCI1710_TTL_READPORT 1 + +/* + * TTL INISIALISATION FUNCTION + */ +int i_APCI1710_InsnConfigInitTTLIO(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * TTL INPUT FUNCTION + */ +int i_APCI1710_InsnBitsReadTTLIO(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1710_InsnReadTTLIOAllPortValue(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * TTL OUTPUT FUNCTIONS + */ +int i_APCI1710_InsnWriteSetTTLIOChlOnOff(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); diff --git a/drivers/staging/comedi/drivers/addi-data/addi_amcc_S5920.c b/drivers/staging/comedi/drivers/addi-data/addi_amcc_S5920.c new file mode 100644 index 00000000..b9730951 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/addi_amcc_S5920.c @@ -0,0 +1,195 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : ADDI HEADER READ WRITER | Compiler : Visual C++ | + | Module name : S5920.cpp | Version : 6.0 | + +-------------------------------+---------------------------------------+ + | Author : E. LIBS Date : 02/05/2002 | + +-----------------------------------------------------------------------+ + | Description : DLL with the S5920 PCI Controller functions | + +-----------------------------------------------------------------------+ + | UPDATE'S | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | 28/08/02 | LIBS Eric | Add return codes each time a function of the | + | | | Addi Library is called | + +-----------------------------------------------------------------------+ + | 31/07/03 | KRAUTH J. | Changes for the MSX-Box | + +-----------------------------------------------------------------------+ +*/ + +#include "addi_amcc_S5920.h" + +/*+----------------------------------------------------------------------------+*/ +/*| Function Name : int i_AddiHeaderRW_ReadEeprom |*/ +/*| (int i_NbOfWordsToRead, |*/ +/*| unsigned int dw_PCIBoardEepromAddress, |*/ +/*| unsigned short w_EepromStartAddress, |*/ +/*| unsigned short * pw_DataRead) |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Task : Read word from the 5920 eeprom. |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Input Parameters : int i_NbOfWordsToRead : Nbr. of word to read |*/ +/*| unsigned int dw_PCIBoardEepromAddress : Address of the eeprom |*/ +/*| unsigned short w_EepromStartAddress : Eeprom start address |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Output Parameters : unsigned short * pw_DataRead : Read data |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Return Value : - |*/ +/*+----------------------------------------------------------------------------+*/ + +int i_AddiHeaderRW_ReadEeprom(int i_NbOfWordsToRead, + unsigned int dw_PCIBoardEepromAddress, + unsigned short w_EepromStartAddress, unsigned short *pw_DataRead) +{ + unsigned int dw_eeprom_busy = 0; + int i_Counter = 0; + int i_WordCounter; + int i; + unsigned char pb_ReadByte[1]; + unsigned char b_ReadLowByte = 0; + unsigned char b_ReadHighByte = 0; + unsigned char b_SelectedAddressLow = 0; + unsigned char b_SelectedAddressHigh = 0; + unsigned short w_ReadWord = 0; + + for (i_WordCounter = 0; i_WordCounter < i_NbOfWordsToRead; + i_WordCounter++) { + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + for (i_Counter = 0; i_Counter < 2; i_Counter++) { + b_SelectedAddressLow = (w_EepromStartAddress + i_Counter) % 256; /* Read the low 8 bit part */ + b_SelectedAddressHigh = (w_EepromStartAddress + i_Counter) / 256; /* Read the high 8 bit part */ + + /* Select the load low address mode */ + outb(NVCMD_LOAD_LOW, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 3); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Load the low address */ + outb(b_SelectedAddressLow, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 2); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Select the load high address mode */ + outb(NVCMD_LOAD_HIGH, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 3); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Load the high address */ + outb(b_SelectedAddressHigh, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 2); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Select the READ mode */ + outb(NVCMD_BEGIN_READ, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 3); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Read data into the EEPROM */ + *pb_ReadByte = + inb(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR + 2); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Select the upper address part */ + if (i_Counter == 0) + b_ReadLowByte = pb_ReadByte[0]; + else + b_ReadHighByte = pb_ReadByte[0]; + + /* Sleep */ + msleep(1); + + } + w_ReadWord = + (b_ReadLowByte | (((unsigned short)b_ReadHighByte) * + 256)); + + pw_DataRead[i_WordCounter] = w_ReadWord; + + w_EepromStartAddress += 2; /* to read the next word */ + + } /* for (...) i_NbOfWordsToRead */ + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/addi_amcc_S5920.h b/drivers/staging/comedi/drivers/addi-data/addi_amcc_S5920.h new file mode 100644 index 00000000..9afdb135 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/addi_amcc_S5920.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define AMCC_OP_REG_MCSR 0x3c +#define EEPROM_BUSY 0x80000000 +#define NVCMD_LOAD_LOW (0x4 << 5) /* nvRam load low command */ +#define NVCMD_LOAD_HIGH (0x5 << 5) /* nvRam load high command */ +#define NVCMD_BEGIN_READ (0x7 << 5) /* nvRam begin read command */ +#define NVCMD_BEGIN_WRITE (0x6 << 5) /* EEPROM begin write command */ + +int i_AddiHeaderRW_ReadEeprom(int i_NbOfWordsToRead, + unsigned int dw_PCIBoardEepromAddress, + unsigned short w_EepromStartAddress, unsigned short *pw_DataRead); diff --git a/drivers/staging/comedi/drivers/addi-data/addi_amcc_s5933.h b/drivers/staging/comedi/drivers/addi-data/addi_amcc_s5933.h new file mode 100644 index 00000000..35a3ea19 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/addi_amcc_s5933.h @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* Header file for AMCC s 5933 */ + +#ifndef _AMCC_S5933_H_ +#define _AMCC_S5933_H_ + +#include "../../comedidev.h" + +#include "../comedi_pci.h" + +#ifdef PCI_SUPPORT_VER1 +#error No support for 2.1.55 and older +#endif + +/* written on base0 */ +#define FIFO_ADVANCE_ON_BYTE_2 0x20000000 + +/* added for step 6 dma written on base2 */ +#define AMWEN_ENABLE 0x02 + +#define A2P_FIFO_WRITE_ENABLE 0x01 + +/* for transfer count enable bit */ +#define AGCSTS_TC_ENABLE 0x10000000 + +/* + * ADDON RELATED ADDITIONS + */ +/* Constant */ +#define APCI3120_ENABLE_TRANSFER_ADD_ON_LOW 0x00 +#define APCI3120_ENABLE_TRANSFER_ADD_ON_HIGH 0x1200 +#define APCI3120_A2P_FIFO_MANAGEMENT 0x04000400L +#define APCI3120_AMWEN_ENABLE 0x02 +#define APCI3120_A2P_FIFO_WRITE_ENABLE 0x01 +#define APCI3120_FIFO_ADVANCE_ON_BYTE_2 0x20000000L +#define APCI3120_ENABLE_WRITE_TC_INT 0x00004000L +#define APCI3120_CLEAR_WRITE_TC_INT 0x00040000L +#define APCI3120_DISABLE_AMWEN_AND_A2P_FIFO_WRITE 0x0 +#define APCI3120_DISABLE_BUS_MASTER_ADD_ON 0x0 +#define APCI3120_DISABLE_BUS_MASTER_PCI 0x0 + +/* ADD_ON ::: this needed since apci supports 16 bit interface to add on */ +#define APCI3120_ADD_ON_AGCSTS_LOW 0x3C +#define APCI3120_ADD_ON_AGCSTS_HIGH (APCI3120_ADD_ON_AGCSTS_LOW + 2) +#define APCI3120_ADD_ON_MWAR_LOW 0x24 +#define APCI3120_ADD_ON_MWAR_HIGH (APCI3120_ADD_ON_MWAR_LOW + 2) +#define APCI3120_ADD_ON_MWTC_LOW 0x058 +#define APCI3120_ADD_ON_MWTC_HIGH (APCI3120_ADD_ON_MWTC_LOW + 2) + +/* AMCC */ +#define APCI3120_AMCC_OP_MCSR 0x3C +#define APCI3120_AMCC_OP_REG_INTCSR 0x38 + +/* + * AMCC Operation Register Offsets - PCI + */ +#define AMCC_OP_REG_OMB1 0x00 +#define AMCC_OP_REG_OMB2 0x04 +#define AMCC_OP_REG_OMB3 0x08 +#define AMCC_OP_REG_OMB4 0x0c +#define AMCC_OP_REG_IMB1 0x10 +#define AMCC_OP_REG_IMB2 0x14 +#define AMCC_OP_REG_IMB3 0x18 +#define AMCC_OP_REG_IMB4 0x1c +#define AMCC_OP_REG_FIFO 0x20 +#define AMCC_OP_REG_MWAR 0x24 +#define AMCC_OP_REG_MWTC 0x28 +#define AMCC_OP_REG_MRAR 0x2c +#define AMCC_OP_REG_MRTC 0x30 +#define AMCC_OP_REG_MBEF 0x34 +#define AMCC_OP_REG_INTCSR 0x38 +/* int source */ +#define AMCC_OP_REG_INTCSR_SRC (AMCC_OP_REG_INTCSR + 2) +/* FIFO ctrl */ +#define AMCC_OP_REG_INTCSR_FEC (AMCC_OP_REG_INTCSR + 3) +#define AMCC_OP_REG_MCSR 0x3c +/* Data in byte 2 */ +#define AMCC_OP_REG_MCSR_NVDATA (AMCC_OP_REG_MCSR + 2) +/* Command in byte 3 */ +#define AMCC_OP_REG_MCSR_NVCMD (AMCC_OP_REG_MCSR + 3) + +#define AMCC_FIFO_DEPTH_DWORD 8 +#define AMCC_FIFO_DEPTH_BYTES (8 * sizeof(u32)) + +/* + * AMCC Operation Registers Size - PCI + */ +#define AMCC_OP_REG_SIZE 64 /* in bytes */ + +/* + * AMCC Operation Register Offsets - Add-on + */ +#define AMCC_OP_REG_AIMB1 0x00 +#define AMCC_OP_REG_AIMB2 0x04 +#define AMCC_OP_REG_AIMB3 0x08 +#define AMCC_OP_REG_AIMB4 0x0c +#define AMCC_OP_REG_AOMB1 0x10 +#define AMCC_OP_REG_AOMB2 0x14 +#define AMCC_OP_REG_AOMB3 0x18 +#define AMCC_OP_REG_AOMB4 0x1c +#define AMCC_OP_REG_AFIFO 0x20 +#define AMCC_OP_REG_AMWAR 0x24 +#define AMCC_OP_REG_APTA 0x28 +#define AMCC_OP_REG_APTD 0x2c +#define AMCC_OP_REG_AMRAR 0x30 +#define AMCC_OP_REG_AMBEF 0x34 +#define AMCC_OP_REG_AINT 0x38 +#define AMCC_OP_REG_AGCSTS 0x3c +#define AMCC_OP_REG_AMWTC 0x58 +#define AMCC_OP_REG_AMRTC 0x5c + +/* + * AMCC - Add-on General Control/Status Register + */ +#define AGCSTS_CONTROL_MASK 0xfffff000 +#define AGCSTS_NV_ACC_MASK 0xe0000000 +#define AGCSTS_RESET_MASK 0x0e000000 +#define AGCSTS_NV_DA_MASK 0x00ff0000 +#define AGCSTS_BIST_MASK 0x0000f000 +#define AGCSTS_STATUS_MASK 0x000000ff +#define AGCSTS_TCZERO_MASK 0x000000c0 +#define AGCSTS_FIFO_ST_MASK 0x0000003f + +#define AGCSTS_RESET_MBFLAGS 0x08000000 +#define AGCSTS_RESET_P2A_FIFO 0x04000000 +#define AGCSTS_RESET_A2P_FIFO 0x02000000 +#define AGCSTS_RESET_FIFOS (AGCSTS_RESET_A2P_FIFO | AGCSTS_RESET_P2A_FIFO) + +#define AGCSTS_A2P_TCOUNT 0x00000080 +#define AGCSTS_P2A_TCOUNT 0x00000040 + +#define AGCSTS_FS_P2A_EMPTY 0x00000020 +#define AGCSTS_FS_P2A_HALF 0x00000010 +#define AGCSTS_FS_P2A_FULL 0x00000008 + +#define AGCSTS_FS_A2P_EMPTY 0x00000004 +#define AGCSTS_FS_A2P_HALF 0x00000002 +#define AGCSTS_FS_A2P_FULL 0x00000001 + +/* + * AMCC - Add-on Interrupt Control/Status Register + */ +#define AINT_INT_MASK 0x00ff0000 +#define AINT_SEL_MASK 0x0000ffff +#define AINT_IS_ENSEL_MASK 0x00001f1f + +#define AINT_INT_ASSERTED 0x00800000 +#define AINT_BM_ERROR 0x00200000 +#define AINT_BIST_INT 0x00100000 + +#define AINT_RT_COMPLETE 0x00080000 +#define AINT_WT_COMPLETE 0x00040000 + +#define AINT_OUT_MB_INT 0x00020000 +#define AINT_IN_MB_INT 0x00010000 + +#define AINT_READ_COMPL 0x00008000 +#define AINT_WRITE_COMPL 0x00004000 + +#define AINT_OMB_ENABLE 0x00001000 +#define AINT_OMB_SELECT 0x00000c00 +#define AINT_OMB_BYTE 0x00000300 + +#define AINT_IMB_ENABLE 0x00000010 +#define AINT_IMB_SELECT 0x0000000c +#define AINT_IMB_BYTE 0x00000003 + +/* Enable Bus Mastering */ +#define EN_A2P_TRANSFERS 0x00000400 +/* FIFO Flag Reset */ +#define RESET_A2P_FLAGS 0x04000000L +/* FIFO Relative Priority */ +#define A2P_HI_PRIORITY 0x00000100L +/* Identify Interrupt Sources */ +#define ANY_S593X_INT 0x00800000L +#define READ_TC_INT 0x00080000L +#define WRITE_TC_INT 0x00040000L +#define IN_MB_INT 0x00020000L +#define MASTER_ABORT_INT 0x00100000L +#define TARGET_ABORT_INT 0x00200000L +#define BUS_MASTER_INT 0x00200000L + +/****************************************************************************/ + +struct pcilst_struct { + struct pcilst_struct *next; + int used; + struct pci_dev *pcidev; + unsigned short vendor; + unsigned short device; + unsigned char pci_bus; + unsigned char pci_slot; + unsigned char pci_func; + resource_size_t io_addr[5]; + unsigned int irq; +}; + +/* ptr to root list of all amcc devices */ +static struct pcilst_struct *amcc_devices; + +static const int i_ADDIDATADeviceID[] = { 0x15B8, 0x10E8 }; + +/****************************************************************************/ + +void v_pci_card_list_init(unsigned short pci_vendor, char display); +void v_pci_card_list_cleanup(unsigned short pci_vendor); +struct pcilst_struct *ptr_find_free_pci_card_by_device(unsigned short vendor_id, + unsigned short + device_id); +int i_find_free_pci_card_by_position(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot, + struct pcilst_struct **card); +struct pcilst_struct *ptr_select_and_alloc_pci_card(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot, + int i_Master); + +int pci_card_alloc(struct pcilst_struct *amcc, int master); +int i_pci_card_free(struct pcilst_struct *amcc); +void v_pci_card_list_display(void); +int i_pci_card_data(struct pcilst_struct *amcc, + unsigned char *pci_bus, unsigned char *pci_slot, + unsigned char *pci_func, resource_size_t * io_addr, + unsigned int *irq); + +/****************************************************************************/ + +/* build list of amcc cards in this system */ +void v_pci_card_list_init(unsigned short pci_vendor, char display) +{ + struct pci_dev *pcidev = NULL; + struct pcilst_struct *amcc, *last; + int i; + int i_Count = 0; + amcc_devices = NULL; + last = NULL; + + for_each_pci_dev(pcidev) { + for (i_Count = 0; i_Count < 2; i_Count++) { + pci_vendor = i_ADDIDATADeviceID[i_Count]; + if (pcidev->vendor == pci_vendor) { + amcc = kzalloc(sizeof(*amcc), GFP_KERNEL); + if (amcc == NULL) + continue; + + amcc->pcidev = pcidev; + if (last) + last->next = amcc; + else + amcc_devices = amcc; + last = amcc; + + amcc->vendor = pcidev->vendor; + amcc->device = pcidev->device; + amcc->pci_bus = pcidev->bus->number; + amcc->pci_slot = PCI_SLOT(pcidev->devfn); + amcc->pci_func = PCI_FUNC(pcidev->devfn); + /* Note: resources may be invalid if PCI device + * not enabled, but they are corrected in + * pci_card_alloc. */ + for (i = 0; i < 5; i++) + amcc->io_addr[i] = + pci_resource_start(pcidev, i); + amcc->irq = pcidev->irq; + + } + } + } + + if (display) + v_pci_card_list_display(); +} + +/****************************************************************************/ +/* free up list of amcc cards in this system */ +void v_pci_card_list_cleanup(unsigned short pci_vendor) +{ + struct pcilst_struct *amcc, *next; + + for (amcc = amcc_devices; amcc; amcc = next) { + next = amcc->next; + kfree(amcc); + } + + amcc_devices = NULL; +} + +/****************************************************************************/ +/* find first unused card with this device_id */ +struct pcilst_struct *ptr_find_free_pci_card_by_device(unsigned short vendor_id, + unsigned short device_id) +{ + struct pcilst_struct *amcc, *next; + + for (amcc = amcc_devices; amcc; amcc = next) { + next = amcc->next; + if ((!amcc->used) && (amcc->device == device_id) + && (amcc->vendor == vendor_id)) + return amcc; + + } + + return NULL; +} + +/****************************************************************************/ +/* find card on requested position */ +int i_find_free_pci_card_by_position(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot, + struct pcilst_struct **card) +{ + struct pcilst_struct *amcc, *next; + + *card = NULL; + for (amcc = amcc_devices; amcc; amcc = next) { + next = amcc->next; + if ((amcc->vendor == vendor_id) && (amcc->device == device_id) + && (amcc->pci_bus == pci_bus) + && (amcc->pci_slot == pci_slot)) { + if (!(amcc->used)) { + *card = amcc; + return 0; /* ok, card is found */ + } else { + printk(" - \nCard on requested position is used b:s %d:%d!\n", + pci_bus, pci_slot); + return 2; /* card exist but is used */ + } + } + } + + /* no card found */ + return 1; +} + +/****************************************************************************/ +/* mark card as used */ +int pci_card_alloc(struct pcilst_struct *amcc, int master) +{ + int i; + + if (!amcc) + return -1; + + if (amcc->used) + return 1; + if (comedi_pci_enable(amcc->pcidev, "addi_amcc_s5933")) + return -1; + /* Resources will be accurate now. */ + for (i = 0; i < 5; i++) + amcc->io_addr[i] = pci_resource_start(amcc->pcidev, i); + if (master) + pci_set_master(amcc->pcidev); + amcc->used = 1; + + return 0; +} + +/****************************************************************************/ +/* mark card as free */ +int i_pci_card_free(struct pcilst_struct *amcc) +{ + if (!amcc) + return -1; + + if (!amcc->used) + return 1; + amcc->used = 0; + comedi_pci_disable(amcc->pcidev); + return 0; +} + +/****************************************************************************/ +/* display list of found cards */ +void v_pci_card_list_display(void) +{ + struct pcilst_struct *amcc, *next; + + printk(KERN_DEBUG "List of pci cards\n"); + printk(KERN_DEBUG "bus:slot:func vendor device io_amcc io_daq irq used\n"); + + for (amcc = amcc_devices; amcc; amcc = next) { + next = amcc->next; + printk + ("%2d %2d %2d 0x%4x 0x%4x 0x%8llx 0x%8llx %2u %2d\n", + amcc->pci_bus, amcc->pci_slot, amcc->pci_func, + amcc->vendor, amcc->device, + (unsigned long long)amcc->io_addr[0], + (unsigned long long)amcc->io_addr[2], amcc->irq, + amcc->used); + + } +} + +/****************************************************************************/ +/* return all card information for driver */ +int i_pci_card_data(struct pcilst_struct *amcc, + unsigned char *pci_bus, unsigned char *pci_slot, + unsigned char *pci_func, resource_size_t * io_addr, + unsigned int *irq) +{ + int i; + + if (!amcc) + return -1; + *pci_bus = amcc->pci_bus; + *pci_slot = amcc->pci_slot; + *pci_func = amcc->pci_func; + for (i = 0; i < 5; i++) + io_addr[i] = amcc->io_addr[i]; + *irq = amcc->irq; + return 0; +} + +/****************************************************************************/ +/* select and alloc card */ +struct pcilst_struct *ptr_select_and_alloc_pci_card(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot, + int i_Master) +{ + struct pcilst_struct *card; + + if ((pci_bus < 1) & (pci_slot < 1)) { + /* use autodetection */ + card = ptr_find_free_pci_card_by_device(vendor_id, device_id); + if (card == NULL) { + printk(" - Unused card not found in system!\n"); + return NULL; + } + } else { + switch (i_find_free_pci_card_by_position(vendor_id, device_id, + pci_bus, pci_slot, + &card)) { + case 1: + printk(" - Card not found on requested position b:s %d:%d!\n", + pci_bus, pci_slot); + return NULL; + case 2: + printk(" - Card on requested position is used b:s %d:%d!\n", + pci_bus, pci_slot); + return NULL; + } + } + + if (pci_card_alloc(card, i_Master) != 0) { + printk(" - Can't allocate card!\n"); + return NULL; + + } + + return card; +} +#endif diff --git a/drivers/staging/comedi/drivers/addi-data/addi_common.c b/drivers/staging/comedi/drivers/addi-data/addi_common.c new file mode 100644 index 00000000..ca5bd9b8 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/addi_common.c @@ -0,0 +1,3108 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstrasse 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : ADDI DATA | Compiler : GCC | + | Modulname : addi_common.c | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Author : | Date : | + +-----------------------------------------------------------------------+ + | Description : ADDI COMMON Main Module | + +-----------------------------------------------------------------------+ + | CONFIG OPTIONS | + | option[0] - PCI bus number - if bus number and slot number are 0, | + | then driver search for first unused card | + | option[1] - PCI slot number | + | | + | option[2] = 0 - DMA ENABLE | + | = 1 - DMA DISABLE | + +----------+-----------+------------------------------------------------+ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../comedidev.h" +#if defined(CONFIG_APCI_1710) || defined(CONFIG_APCI_3200) || defined(CONFIG_APCI_3300) +#include +#endif +#include "../comedi_fc.h" + +#include "addi_common.h" +#include "addi_amcc_s5933.h" + +#ifndef ADDIDATA_DRIVER_NAME +#define ADDIDATA_DRIVER_NAME "addi_common" +#endif + +/* Update-0.7.57->0.7.68MODULE_AUTHOR("ADDI-DATA GmbH "); */ +/* Update-0.7.57->0.7.68MODULE_DESCRIPTION("Comedi ADDI-DATA module"); */ +/* Update-0.7.57->0.7.68MODULE_LICENSE("GPL"); */ + +#define devpriv ((struct addi_private *)dev->private) +#define this_board ((const struct addi_board *)dev->board_ptr) + +#if defined(CONFIG_APCI_1710) || defined(CONFIG_APCI_3200) || defined(CONFIG_APCI_3300) +/* BYTE b_SaveFPUReg [94]; */ + +void fpu_begin(void) +{ + /* asm ("fstenv b_SaveFPUReg"); */ + kernel_fpu_begin(); +} + +void fpu_end(void) +{ + /* asm ("frstor b_SaveFPUReg"); */ + kernel_fpu_end(); +} +#endif + +#include "addi_eeprom.c" +#if (defined (CONFIG_APCI_3120) || defined (CONFIG_APCI_3001)) +#include "hwdrv_apci3120.c" +#endif +#ifdef CONFIG_APCI_1032 +#include "hwdrv_apci1032.c" +#endif +#ifdef CONFIG_APCI_1516 +#include "hwdrv_apci1516.c" +#endif +#ifdef CONFIG_APCI_2016 +#include "hwdrv_apci2016.c" +#endif +#ifdef CONFIG_APCI_2032 +#include "hwdrv_apci2032.c" +#endif +#ifdef CONFIG_APCI_2200 +#include "hwdrv_apci2200.c" +#endif +#ifdef CONFIG_APCI_1564 +#include "hwdrv_apci1564.c" +#endif +#ifdef CONFIG_APCI_1500 +#include "hwdrv_apci1500.c" +#endif +#ifdef CONFIG_APCI_3501 +#include "hwdrv_apci3501.c" +#endif +#ifdef CONFIG_APCI_035 +#include "hwdrv_apci035.c" +#endif +#if (defined (CONFIG_APCI_3200) || defined (CONFIG_APCI_3300)) +#include "hwdrv_apci3200.c" +#endif +#ifdef CONFIG_APCI_1710 +#include "hwdrv_APCI1710.c" +#endif +#ifdef CONFIG_APCI_16XX +#include "hwdrv_apci16xx.c" +#endif +#ifdef CONFIG_APCI_3XXX +#include "hwdrv_apci3xxx.c" +#endif + +#ifndef COMEDI_SUBD_TTLIO +#define COMEDI_SUBD_TTLIO 11 /* Digital Input Output But TTL */ +#endif + +static DEFINE_PCI_DEVICE_TABLE(addi_apci_tbl) = { +#ifdef CONFIG_APCI_3120 + {PCI_DEVICE(APCI3120_BOARD_VENDOR_ID, 0x818D)}, +#endif +#ifdef CONFIG_APCI_1032 + {PCI_DEVICE(APCI1032_BOARD_VENDOR_ID, 0x1003)}, +#endif +#ifdef CONFIG_APCI_1516 + {PCI_DEVICE(APCI1516_BOARD_VENDOR_ID, 0x1001)}, +#endif +#ifdef CONFIG_APCI_2016 + {PCI_DEVICE(APCI2016_BOARD_VENDOR_ID, 0x1002)}, +#endif +#ifdef CONFIG_APCI_2032 + {PCI_DEVICE(APCI2032_BOARD_VENDOR_ID, 0x1004)}, +#endif +#ifdef CONFIG_APCI_2200 + {PCI_DEVICE(APCI2200_BOARD_VENDOR_ID, 0x1005)}, +#endif +#ifdef CONFIG_APCI_1564 + {PCI_DEVICE(APCI1564_BOARD_VENDOR_ID, 0x1006)}, +#endif +#ifdef CONFIG_APCI_1500 + {PCI_DEVICE(APCI1500_BOARD_VENDOR_ID, 0x80fc)}, +#endif +#ifdef CONFIG_APCI_3001 + {PCI_DEVICE(APCI3120_BOARD_VENDOR_ID, 0x828D)}, +#endif +#ifdef CONFIG_APCI_3501 + {PCI_DEVICE(APCI3501_BOARD_VENDOR_ID, 0x3001)}, +#endif +#ifdef CONFIG_APCI_035 + {PCI_DEVICE(APCI035_BOARD_VENDOR_ID, 0x0300)}, +#endif +#ifdef CONFIG_APCI_3200 + {PCI_DEVICE(APCI3200_BOARD_VENDOR_ID, 0x3000)}, +#endif +#ifdef CONFIG_APCI_3300 + {PCI_DEVICE(APCI3200_BOARD_VENDOR_ID, 0x3007)}, +#endif +#ifdef CONFIG_APCI_1710 + {PCI_DEVICE(APCI1710_BOARD_VENDOR_ID, APCI1710_BOARD_DEVICE_ID)}, +#endif +#ifdef CONFIG_APCI_16XX + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1009)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x100A)}, +#endif +#ifdef CONFIG_APCI_3XXX + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3010)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x300F)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x300E)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3013)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3014)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3015)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3016)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3017)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3018)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3019)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x301A)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x301B)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x301C)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x301D)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x301E)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x301F)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3020)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3021)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3022)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3023)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x300B)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3002)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3003)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3004)}, + {PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3024)}, +#endif + {0} +}; + +MODULE_DEVICE_TABLE(pci, addi_apci_tbl); + +static const struct addi_board boardtypes[] = { +#ifdef CONFIG_APCI_3120 + {"apci3120", + APCI3120_BOARD_VENDOR_ID, + 0x818D, + AMCC_OP_REG_SIZE, + APCI3120_ADDRESS_RANGE, + 8, + 0, + ADDIDATA_NO_EEPROM, + NULL, + 16, + 8, + 16, + 8, + 0xffff, + 0x3fff, + &range_apci3120_ai, + &range_apci3120_ao, + 4, + 4, + 0x0f, + 0, + NULL, + 1, + 1, + 1, + 10000, + 100000, + v_APCI3120_Interrupt, + i_APCI3120_Reset, + i_APCI3120_InsnConfigAnalogInput, + i_APCI3120_InsnReadAnalogInput, + NULL, + NULL, + i_APCI3120_CommandTestAnalogInput, + i_APCI3120_CommandAnalogInput, + i_APCI3120_StopCyclicAcquisition, + NULL, + i_APCI3120_InsnWriteAnalogOutput, + NULL, + NULL, + i_APCI3120_InsnReadDigitalInput, + NULL, + i_APCI3120_InsnBitsDigitalInput, + i_APCI3120_InsnConfigDigitalOutput, + i_APCI3120_InsnWriteDigitalOutput, + i_APCI3120_InsnBitsDigitalOutput, + NULL, + i_APCI3120_InsnConfigTimer, + i_APCI3120_InsnWriteTimer, + i_APCI3120_InsnReadTimer, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_1032 + {"apci1032", + APCI1032_BOARD_VENDOR_ID, + 0x1003, + 4, + APCI1032_ADDRESS_RANGE, + 0, + 0, + ADDIDATA_EEPROM, + ADDIDATA_93C76, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 32, + 0, + 0, + 0, + NULL, + 0, + 0, + 0, + 0, + 0, + v_APCI1032_Interrupt, + i_APCI1032_Reset, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI1032_ConfigDigitalInput, + i_APCI1032_Read1DigitalInput, + NULL, + i_APCI1032_ReadMoreDigitalInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_1516 + {"apci1516", + APCI1516_BOARD_VENDOR_ID, + 0x1001, + 128, + APCI1516_ADDRESS_RANGE, + 32, + 0, + ADDIDATA_EEPROM, + ADDIDATA_S5920, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 8, + 8, + 0, + 0, + NULL, + 0, + 1, + 0, + 0, + 0, + NULL, + i_APCI1516_Reset, + NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI1516_Read1DigitalInput, + NULL, + i_APCI1516_ReadMoreDigitalInput, + i_APCI1516_ConfigDigitalOutput, + i_APCI1516_WriteDigitalOutput, + i_APCI1516_ReadDigitalOutput, + NULL, + i_APCI1516_ConfigWatchdog, + i_APCI1516_StartStopWriteWatchdog, + i_APCI1516_ReadWatchdog, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_2016 + {"apci2016", + APCI2016_BOARD_VENDOR_ID, + 0x1002, + 128, + APCI2016_ADDRESS_RANGE, + 32, + 0, + ADDIDATA_EEPROM, + ADDIDATA_S5920, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 0, + 16, + 0, + 0, + NULL, + 0, + 1, + 0, + 0, + 0, + NULL, + i_APCI2016_Reset, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI2016_ConfigDigitalOutput, + i_APCI2016_WriteDigitalOutput, + i_APCI2016_BitsDigitalOutput, + NULL, + i_APCI2016_ConfigWatchdog, + i_APCI2016_StartStopWriteWatchdog, + i_APCI2016_ReadWatchdog, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_2032 + {"apci2032", + APCI2032_BOARD_VENDOR_ID, + 0x1004, + 4, + APCI2032_ADDRESS_RANGE, + 0, + 0, + ADDIDATA_EEPROM, + ADDIDATA_93C76, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 0, + 32, + 0xffffffff, + 0, + NULL, + 0, + 1, + 0, + 0, + 0, + v_APCI2032_Interrupt, + i_APCI2032_Reset, + NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI2032_ConfigDigitalOutput, + i_APCI2032_WriteDigitalOutput, + i_APCI2032_ReadDigitalOutput, + i_APCI2032_ReadInterruptStatus, + i_APCI2032_ConfigWatchdog, + i_APCI2032_StartStopWriteWatchdog, + i_APCI2032_ReadWatchdog, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_2200 + {"apci2200", + APCI2200_BOARD_VENDOR_ID, + 0x1005, + 4, + APCI2200_ADDRESS_RANGE, + 0, + 0, + ADDIDATA_EEPROM, + ADDIDATA_93C76, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 8, + 16, + 0, + 0, + NULL, + 0, + 1, + 0, + 0, + 0, + NULL, + i_APCI2200_Reset, + NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI2200_Read1DigitalInput, + NULL, + i_APCI2200_ReadMoreDigitalInput, + i_APCI2200_ConfigDigitalOutput, + i_APCI2200_WriteDigitalOutput, + i_APCI2200_ReadDigitalOutput, + NULL, + i_APCI2200_ConfigWatchdog, + i_APCI2200_StartStopWriteWatchdog, + i_APCI2200_ReadWatchdog, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_1564 + {"apci1564", + APCI1564_BOARD_VENDOR_ID, + 0x1006, + 128, + APCI1564_ADDRESS_RANGE, + 0, + 0, + ADDIDATA_EEPROM, + ADDIDATA_93C76, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 32, + 32, + 0xffffffff, + 0, + NULL, + 0, + 1, + 0, + 0, + 0, + v_APCI1564_Interrupt, + i_APCI1564_Reset, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI1564_ConfigDigitalInput, + i_APCI1564_Read1DigitalInput, + NULL, + i_APCI1564_ReadMoreDigitalInput, + i_APCI1564_ConfigDigitalOutput, + i_APCI1564_WriteDigitalOutput, + i_APCI1564_ReadDigitalOutput, + i_APCI1564_ReadInterruptStatus, + i_APCI1564_ConfigTimerCounterWatchdog, + i_APCI1564_StartStopWriteTimerCounterWatchdog, + i_APCI1564_ReadTimerCounterWatchdog, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_1500 + {"apci1500", + APCI1500_BOARD_VENDOR_ID, + 0x80fc, + 128, + APCI1500_ADDRESS_RANGE, + 4, + 0, + ADDIDATA_NO_EEPROM, + NULL, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 16, + 16, + 0xffff, + 0, + NULL, + 0, + 1, + 0, + 0, + 0, + v_APCI1500_Interrupt, + i_APCI1500_Reset, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI1500_ConfigDigitalInputEvent, + i_APCI1500_Initialisation, + i_APCI1500_StartStopInputEvent, + i_APCI1500_ReadMoreDigitalInput, + i_APCI1500_ConfigDigitalOutputErrorInterrupt, + i_APCI1500_WriteDigitalOutput, + i_APCI1500_ConfigureInterrupt, + NULL, + i_APCI1500_ConfigCounterTimerWatchdog, + i_APCI1500_StartStopTriggerTimerCounterWatchdog, + i_APCI1500_ReadInterruptMask, + i_APCI1500_ReadCounterTimerWatchdog, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_3001 + {"apci3001", + APCI3120_BOARD_VENDOR_ID, + 0x828D, + AMCC_OP_REG_SIZE, + APCI3120_ADDRESS_RANGE, + 8, + 0, + ADDIDATA_NO_EEPROM, + NULL, + 16, + 8, + 16, + 0, + 0xfff, + 0, + &range_apci3120_ai, + NULL, + 4, + 4, + 0x0f, + 0, + NULL, + 1, + 1, + 1, + 10000, + 100000, + v_APCI3120_Interrupt, + i_APCI3120_Reset, + i_APCI3120_InsnConfigAnalogInput, + i_APCI3120_InsnReadAnalogInput, + NULL, + NULL, + i_APCI3120_CommandTestAnalogInput, + i_APCI3120_CommandAnalogInput, + i_APCI3120_StopCyclicAcquisition, + NULL, + NULL, + NULL, + NULL, + i_APCI3120_InsnReadDigitalInput, + NULL, + i_APCI3120_InsnBitsDigitalInput, + i_APCI3120_InsnConfigDigitalOutput, + i_APCI3120_InsnWriteDigitalOutput, + i_APCI3120_InsnBitsDigitalOutput, + NULL, + i_APCI3120_InsnConfigTimer, + i_APCI3120_InsnWriteTimer, + i_APCI3120_InsnReadTimer, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_3501 + {"apci3501", + APCI3501_BOARD_VENDOR_ID, + 0x3001, + 64, + APCI3501_ADDRESS_RANGE, + 0, + 0, + ADDIDATA_EEPROM, + ADDIDATA_S5933, + 0, + 0, + 0, + 8, + 0, + 16383, + NULL, + &range_apci3501_ao, + 2, + 2, + 0x3, + 0, + NULL, + 0, + 1, + 0, + 0, + 0, + v_APCI3501_Interrupt, + i_APCI3501_Reset, + NULL, NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3501_ConfigAnalogOutput, + i_APCI3501_WriteAnalogOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3501_ReadDigitalInput, + i_APCI3501_ConfigDigitalOutput, + i_APCI3501_WriteDigitalOutput, + i_APCI3501_ReadDigitalOutput, + NULL, + i_APCI3501_ConfigTimerCounterWatchdog, + i_APCI3501_StartStopWriteTimerCounterWatchdog, + i_APCI3501_ReadTimerCounterWatchdog, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_035 + {"apci035", + APCI035_BOARD_VENDOR_ID, + 0x0300, + 127, + APCI035_ADDRESS_RANGE, + 0, + 0, + 1, + ADDIDATA_S5920, + 16, + 8, + 16, + 0, + 0xff, + 0, + &range_apci035_ai, + NULL, + 0, + 0, + 0, + 0, + NULL, + 0, + 1, + 0, + 10000, + 100000, + v_APCI035_Interrupt, + i_APCI035_Reset, + i_APCI035_ConfigAnalogInput, + i_APCI035_ReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI035_ConfigTimerWatchdog, + i_APCI035_StartStopWriteTimerWatchdog, + i_APCI035_ReadTimerWatchdog, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_3200 + {"apci3200", + APCI3200_BOARD_VENDOR_ID, + 0x3000, + 128, + 256, + 4, + 4, + ADDIDATA_EEPROM, + ADDIDATA_S5920, + 16, + 8, + 16, + 0, + 0x3ffff, + 0, + &range_apci3200_ai, + NULL, + 4, + 4, + 0, + 0, + NULL, + 0, + 0, + 0, + 10000, + 100000, + v_APCI3200_Interrupt, + i_APCI3200_Reset, + i_APCI3200_ConfigAnalogInput, + i_APCI3200_ReadAnalogInput, + i_APCI3200_InsnWriteReleaseAnalogInput, + i_APCI3200_InsnBits_AnalogInput_Test, + i_APCI3200_CommandTestAnalogInput, + i_APCI3200_CommandAnalogInput, + i_APCI3200_StopCyclicAcquisition, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3200_ReadDigitalInput, + i_APCI3200_ConfigDigitalOutput, + i_APCI3200_WriteDigitalOutput, + i_APCI3200_ReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_3300 + /* Begin JK .20.10.2004 = APCI-3300 integration */ + {"apci3300", + APCI3200_BOARD_VENDOR_ID, + 0x3007, + 128, + 256, + 4, + 4, + ADDIDATA_EEPROM, + ADDIDATA_S5920, + 0, + 8, + 8, + 0, + 0x3ffff, + 0, + &range_apci3300_ai, + NULL, + 4, + 4, + 0, + 0, + NULL, + 0, + 0, + 0, + 10000, + 100000, + v_APCI3200_Interrupt, + i_APCI3200_Reset, + i_APCI3200_ConfigAnalogInput, + i_APCI3200_ReadAnalogInput, + i_APCI3200_InsnWriteReleaseAnalogInput, + i_APCI3200_InsnBits_AnalogInput_Test, + i_APCI3200_CommandTestAnalogInput, + i_APCI3200_CommandAnalogInput, + i_APCI3200_StopCyclicAcquisition, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3200_ReadDigitalInput, + i_APCI3200_ConfigDigitalOutput, + i_APCI3200_WriteDigitalOutput, + i_APCI3200_ReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_1710 + {"apci1710", APCI1710_BOARD_VENDOR_ID, APCI1710_BOARD_DEVICE_ID, + 128, + 8, + 256, + 0, + ADDIDATA_NO_EEPROM, + NULL, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 0, + 0, + 0, + 0, + NULL, + 0, + 0, + 0, + 0, + 0, + v_APCI1710_Interrupt, + i_APCI1710_Reset, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, +#endif +#ifdef CONFIG_APCI_16XX + {"apci1648", + PCI_VENDOR_ID_ADDIDATA, + 0x1009, + 128, + 0, + 0, + 0, + ADDIDATA_NO_EEPROM, + NULL, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 0, + 0, + 0, + 48, + &range_apci16xx_ttl, + 0, + 0, + 0, + 0, + 0, + NULL, + i_APCI16XX_Reset, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI16XX_InsnConfigInitTTLIO, + i_APCI16XX_InsnBitsReadTTLIO, + i_APCI16XX_InsnReadTTLIOAllPortValue, + i_APCI16XX_InsnBitsWriteTTLIO}, + + {"apci1696", + PCI_VENDOR_ID_ADDIDATA, + 0x100A, + 128, + 0, + 0, + 0, + ADDIDATA_NO_EEPROM, + NULL, + 0, + 0, + 0, + 0, + 0, + 0, + NULL, + NULL, + 0, + 0, + 0, + 96, + &range_apci16xx_ttl, + 0, + 0, + 0, + 0, + 0, + NULL, + i_APCI16XX_Reset, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI16XX_InsnConfigInitTTLIO, + i_APCI16XX_InsnBitsReadTTLIO, + i_APCI16XX_InsnReadTTLIOAllPortValue, + i_APCI16XX_InsnBitsWriteTTLIO}, +#endif +#ifdef CONFIG_APCI_3XXX + {"apci3000-16", + PCI_VENDOR_ID_ADDIDATA, + 0x3010, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 16, + 8, + 16, + 0, + 4095, + 0, + &range_apci3XXX_ai, + NULL, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3000-8", + PCI_VENDOR_ID_ADDIDATA, + 0x300F, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 8, + 4, + 8, + 0, + 4095, + 0, + &range_apci3XXX_ai, + NULL, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3000-4", + PCI_VENDOR_ID_ADDIDATA, + 0x300E, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 4, + 2, + 4, + 0, + 4095, + 0, + &range_apci3XXX_ai, + NULL, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3006-16", + PCI_VENDOR_ID_ADDIDATA, + 0x3013, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 16, + 8, + 16, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3006-8", + PCI_VENDOR_ID_ADDIDATA, + 0x3014, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 8, + 4, + 8, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3006-4", + PCI_VENDOR_ID_ADDIDATA, + 0x3015, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 4, + 2, + 4, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3010-16", + PCI_VENDOR_ID_ADDIDATA, + 0x3016, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 16, + 8, + 16, + 0, + 4095, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3010-8", + PCI_VENDOR_ID_ADDIDATA, + 0x3017, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 8, + 4, + 8, + 0, + 4095, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3010-4", + PCI_VENDOR_ID_ADDIDATA, + 0x3018, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 4, + 2, + 4, + 0, + 4095, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3016-16", + PCI_VENDOR_ID_ADDIDATA, + 0x3019, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 16, + 8, + 16, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3016-8", + PCI_VENDOR_ID_ADDIDATA, + 0x301A, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 8, + 4, + 8, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3016-4", + PCI_VENDOR_ID_ADDIDATA, + 0x301B, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 4, + 2, + 4, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3100-16-4", + PCI_VENDOR_ID_ADDIDATA, + 0x301C, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 16, + 8, + 16, + 4, + 4095, + 4095, + &range_apci3XXX_ai, + &range_apci3XXX_ao, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnWriteAnalogOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3100-8-4", + PCI_VENDOR_ID_ADDIDATA, + 0x301D, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 8, + 4, + 8, + 4, + 4095, + 4095, + &range_apci3XXX_ai, + &range_apci3XXX_ao, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnWriteAnalogOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3106-16-4", + PCI_VENDOR_ID_ADDIDATA, + 0x301E, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 16, + 8, + 16, + 4, + 65535, + 4095, + &range_apci3XXX_ai, + &range_apci3XXX_ao, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnWriteAnalogOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3106-8-4", + PCI_VENDOR_ID_ADDIDATA, + 0x301F, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 8, + 4, + 8, + 4, + 65535, + 4095, + &range_apci3XXX_ai, + &range_apci3XXX_ao, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 10000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnWriteAnalogOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3110-16-4", + PCI_VENDOR_ID_ADDIDATA, + 0x3020, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 16, + 8, + 16, + 4, + 4095, + 4095, + &range_apci3XXX_ai, + &range_apci3XXX_ao, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnWriteAnalogOutput, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3110-8-4", + PCI_VENDOR_ID_ADDIDATA, + 0x3021, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 8, + 4, + 8, + 4, + 4095, + 4095, + &range_apci3XXX_ai, + &range_apci3XXX_ao, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnWriteAnalogOutput, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3116-16-4", + PCI_VENDOR_ID_ADDIDATA, + 0x3022, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 16, + 8, + 16, + 4, + 65535, + 4095, + &range_apci3XXX_ai, + &range_apci3XXX_ao, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnWriteAnalogOutput, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3116-8-4", + PCI_VENDOR_ID_ADDIDATA, + 0x3023, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 8, + 4, + 8, + 4, + 65535, + 4095, + &range_apci3XXX_ai, + &range_apci3XXX_ao, + 4, + 4, + 1, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnWriteAnalogOutput, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, + + {"apci3003", + PCI_VENDOR_ID_ADDIDATA, + 0x300B, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 0, + 4, + 4, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 0, + NULL, + 0, + 0, + 7, + 2500, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, + + {"apci3002-16", + PCI_VENDOR_ID_ADDIDATA, + 0x3002, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 0, + 16, + 16, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 0, + NULL, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, + + {"apci3002-8", + PCI_VENDOR_ID_ADDIDATA, + 0x3003, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 0, + 8, + 8, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 0, + NULL, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, + + {"apci3002-4", + PCI_VENDOR_ID_ADDIDATA, + 0x3004, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 0, + 4, + 4, + 0, + 65535, + 0, + &range_apci3XXX_ai, + NULL, + 4, + 4, + 1, + 0, + NULL, + 0, + 0, + 6, + 5000, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + i_APCI3XXX_InsnConfigAnalogInput, + i_APCI3XXX_InsnReadAnalogInput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnReadDigitalInput, + NULL, + i_APCI3XXX_InsnBitsDigitalInput, + NULL, + i_APCI3XXX_InsnWriteDigitalOutput, + i_APCI3XXX_InsnBitsDigitalOutput, + i_APCI3XXX_InsnReadDigitalOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL}, + + {"apci3500", + PCI_VENDOR_ID_ADDIDATA, + 0x3024, + 256, + 256, + 256, + 256, + ADDIDATA_NO_EEPROM, + ADDIDATA_9054, + 0, + 0, + 0, + 4, + 0, + 4095, + NULL, + &range_apci3XXX_ao, + 0, + 0, + 0, + 24, + &range_apci3XXX_ttl, + 0, + 0, + 0, + 0, + 0, + v_APCI3XXX_Interrupt, + i_APCI3XXX_Reset, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnWriteAnalogOutput, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + i_APCI3XXX_InsnConfigInitTTLIO, + i_APCI3XXX_InsnBitsTTLIO, + i_APCI3XXX_InsnReadTTLIO, + i_APCI3XXX_InsnWriteTTLIO}, +#endif +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct addi_board)) + +static struct comedi_driver driver_addi = { + .driver_name = ADDIDATA_DRIVER_NAME, + .module = THIS_MODULE, + .attach = i_ADDI_Attach, + .detach = i_ADDI_Detach, + .num_names = n_boardtypes, + .board_name = &boardtypes[0].pc_DriverName, + .offset = sizeof(struct addi_board), +}; + +static int __devinit driver_addi_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_addi.driver_name); +} + +static void __devexit driver_addi_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_addi_pci_driver = { + .id_table = addi_apci_tbl, + .probe = &driver_addi_pci_probe, + .remove = __devexit_p(&driver_addi_pci_remove) +}; + +static int __init driver_addi_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_addi); + if (retval < 0) + return retval; + + driver_addi_pci_driver.name = (char *)driver_addi.driver_name; + return pci_register_driver(&driver_addi_pci_driver); +} + +static void __exit driver_addi_cleanup_module(void) +{ + pci_unregister_driver(&driver_addi_pci_driver); + comedi_driver_unregister(&driver_addi); +} + +module_init(driver_addi_init_module); +module_exit(driver_addi_cleanup_module); + +/* ++----------------------------------------------------------------------------+ +| Function name :static int i_ADDI_Attach(struct comedi_device *dev, | +| struct comedi_devconfig *it) | +| | ++----------------------------------------------------------------------------+ +| Task :Detects the card. | +| Configure the driver for a particular board. | +| This function does all the initializations and memory | +| allocation of data structures for the driver. | ++----------------------------------------------------------------------------+ +| Input Parameters :struct comedi_device *dev | +| struct comedi_devconfig *it | +| | ++----------------------------------------------------------------------------+ +| Return Value : 0 | +| | ++----------------------------------------------------------------------------+ +*/ + +static int i_ADDI_Attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret, pages, i, n_subdevices; + unsigned int dw_Dummy; + resource_size_t io_addr[5]; + unsigned int irq; + resource_size_t iobase_a, iobase_main, iobase_addon, iobase_reserved; + struct pcilst_struct *card = NULL; + unsigned char pci_bus, pci_slot, pci_func; + int i_Dma = 0; + + ret = alloc_private(dev, sizeof(struct addi_private)); + if (ret < 0) + return -ENOMEM; + + if (!pci_list_builded) { + v_pci_card_list_init(this_board->i_VendorId, 1); /* 1 for displaying the list.. */ + pci_list_builded = 1; + } + /* printk("comedi%d: "ADDIDATA_DRIVER_NAME": board=%s",dev->minor,this_board->pc_DriverName); */ + + if ((this_board->i_Dma) && (it->options[2] == 0)) { + i_Dma = 1; + } + + card = ptr_select_and_alloc_pci_card(this_board->i_VendorId, + this_board->i_DeviceId, + it->options[0], + it->options[1], i_Dma); + + if (card == NULL) + return -EIO; + + devpriv->allocated = 1; + + if ((i_pci_card_data(card, &pci_bus, &pci_slot, &pci_func, &io_addr[0], + &irq)) < 0) { + i_pci_card_free(card); + printk(" - Can't get AMCC data!\n"); + return -EIO; + } + + iobase_a = io_addr[0]; + iobase_main = io_addr[1]; + iobase_addon = io_addr[2]; + iobase_reserved = io_addr[3]; + printk("\nBus %d: Slot %d: Funct%d\nBase0: 0x%8llx\nBase1: 0x%8llx\nBase2: 0x%8llx\nBase3: 0x%8llx\n", pci_bus, pci_slot, pci_func, (unsigned long long)io_addr[0], (unsigned long long)io_addr[1], (unsigned long long)io_addr[2], (unsigned long long)io_addr[3]); + + if ((this_board->pc_EepromChip == NULL) + || (strcmp(this_board->pc_EepromChip, ADDIDATA_9054) != 0)) { + /************************************/ + /* Test if more that 1 address used */ + /************************************/ + + if (this_board->i_IorangeBase1 != 0) { + dev->iobase = (unsigned long)iobase_main; /* DAQ base address... */ + } else { + dev->iobase = (unsigned long)iobase_a; /* DAQ base address... */ + } + + dev->board_name = this_board->pc_DriverName; + devpriv->amcc = card; + devpriv->iobase = (int) dev->iobase; + devpriv->i_IobaseAmcc = (int) iobase_a; /* AMCC base address... */ + devpriv->i_IobaseAddon = (int) iobase_addon; /* ADD ON base address.... */ + devpriv->i_IobaseReserved = (int) iobase_reserved; + } else { + dev->board_name = this_board->pc_DriverName; + dev->iobase = (unsigned long)io_addr[2]; + devpriv->amcc = card; + devpriv->iobase = (int) io_addr[2]; + devpriv->i_IobaseReserved = (int) io_addr[3]; + printk("\nioremap begin"); + devpriv->dw_AiBase = ioremap(io_addr[3], + this_board->i_IorangeBase3); + printk("\nioremap end"); + } + + /* Initialize parameters that can be overridden in EEPROM */ + devpriv->s_EeParameters.i_NbrAiChannel = this_board->i_NbrAiChannel; + devpriv->s_EeParameters.i_NbrAoChannel = this_board->i_NbrAoChannel; + devpriv->s_EeParameters.i_AiMaxdata = this_board->i_AiMaxdata; + devpriv->s_EeParameters.i_AoMaxdata = this_board->i_AoMaxdata; + devpriv->s_EeParameters.i_NbrDiChannel = this_board->i_NbrDiChannel; + devpriv->s_EeParameters.i_NbrDoChannel = this_board->i_NbrDoChannel; + devpriv->s_EeParameters.i_DoMaxdata = this_board->i_DoMaxdata; + devpriv->s_EeParameters.i_Dma = this_board->i_Dma; + devpriv->s_EeParameters.i_Timer = this_board->i_Timer; + devpriv->s_EeParameters.ui_MinAcquisitiontimeNs = + this_board->ui_MinAcquisitiontimeNs; + devpriv->s_EeParameters.ui_MinDelaytimeNs = + this_board->ui_MinDelaytimeNs; + + /* ## */ + + if (irq > 0) { + if (request_irq(irq, v_ADDI_Interrupt, IRQF_SHARED, + this_board->pc_DriverName, dev) < 0) { + printk(", unable to allocate IRQ %u, DISABLING IT", + irq); + irq = 0; /* Can't use IRQ */ + } else { + printk("\nirq=%u", irq); + } + } else { + printk(", IRQ disabled"); + } + + printk("\nOption %d %d %d\n", it->options[0], it->options[1], + it->options[2]); + dev->irq = irq; + + /* Read eepeom and fill addi_board Structure */ + + if (this_board->i_PCIEeprom) { + printk("\nPCI Eeprom used"); + if (!(strcmp(this_board->pc_EepromChip, "S5920"))) { + /* Set 3 wait stait */ + if (!(strcmp(this_board->pc_DriverName, "apci035"))) { + outl(0x80808082, devpriv->i_IobaseAmcc + 0x60); + } else { + outl(0x83838383, devpriv->i_IobaseAmcc + 0x60); + } + /* Enable the interrupt for the controller */ + dw_Dummy = inl(devpriv->i_IobaseAmcc + 0x38); + outl(dw_Dummy | 0x2000, devpriv->i_IobaseAmcc + 0x38); + printk("\nEnable the interrupt for the controller"); + } + printk("\nRead Eeprom"); + i_EepromReadMainHeader(io_addr[0], this_board->pc_EepromChip, + dev); + } else { + printk("\nPCI Eeprom unused"); + } + + if (it->options[2] > 0) { + devpriv->us_UseDma = ADDI_DISABLE; + } else { + devpriv->us_UseDma = ADDI_ENABLE; + } + + if (devpriv->s_EeParameters.i_Dma) { + printk("\nDMA used"); + if (devpriv->us_UseDma == ADDI_ENABLE) { + /* alloc DMA buffers */ + devpriv->b_DmaDoubleBuffer = 0; + for (i = 0; i < 2; i++) { + for (pages = 4; pages >= 0; pages--) { + devpriv->ul_DmaBufferVirtual[i] = + (void *) __get_free_pages(GFP_KERNEL, pages); + + if (devpriv->ul_DmaBufferVirtual[i]) + break; + } + if (devpriv->ul_DmaBufferVirtual[i]) { + devpriv->ui_DmaBufferPages[i] = pages; + devpriv->ui_DmaBufferSize[i] = + PAGE_SIZE * pages; + devpriv->ui_DmaBufferSamples[i] = + devpriv-> + ui_DmaBufferSize[i] >> 1; + devpriv->ul_DmaBufferHw[i] = + virt_to_bus((void *)devpriv-> + ul_DmaBufferVirtual[i]); + } + } + if (!devpriv->ul_DmaBufferVirtual[0]) { + printk + (", Can't allocate DMA buffer, DMA disabled!"); + devpriv->us_UseDma = ADDI_DISABLE; + } + + if (devpriv->ul_DmaBufferVirtual[1]) { + devpriv->b_DmaDoubleBuffer = 1; + } + } + + if ((devpriv->us_UseDma == ADDI_ENABLE)) { + printk("\nDMA ENABLED\n"); + } else { + printk("\nDMA DISABLED\n"); + } + } + + if (!strcmp(this_board->pc_DriverName, "apci1710")) { +#ifdef CONFIG_APCI_1710 + i_ADDI_AttachPCI1710(dev); + + /* save base address */ + devpriv->s_BoardInfos.ui_Address = io_addr[2]; +#endif + } else { + /* Update-0.7.57->0.7.68dev->n_subdevices = 7; */ + n_subdevices = 7; + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) + return ret; + + /* Allocate and Initialise AI Subdevice Structures */ + s = dev->subdevices + 0; + if ((devpriv->s_EeParameters.i_NbrAiChannel) + || (this_board->i_NbrAiChannelDiff)) { + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_COMMON | SDF_GROUND + | SDF_DIFF; + if (devpriv->s_EeParameters.i_NbrAiChannel) { + s->n_chan = + devpriv->s_EeParameters.i_NbrAiChannel; + devpriv->b_SingelDiff = 0; + } else { + s->n_chan = this_board->i_NbrAiChannelDiff; + devpriv->b_SingelDiff = 1; + } + s->maxdata = devpriv->s_EeParameters.i_AiMaxdata; + s->len_chanlist = this_board->i_AiChannelList; + s->range_table = this_board->pr_AiRangelist; + + /* Set the initialisation flag */ + devpriv->b_AiInitialisation = 1; + + s->insn_config = + this_board->i_hwdrv_InsnConfigAnalogInput; + s->insn_read = this_board->i_hwdrv_InsnReadAnalogInput; + s->insn_write = + this_board->i_hwdrv_InsnWriteAnalogInput; + s->insn_bits = this_board->i_hwdrv_InsnBitsAnalogInput; + s->do_cmdtest = + this_board->i_hwdrv_CommandTestAnalogInput; + s->do_cmd = this_board->i_hwdrv_CommandAnalogInput; + s->cancel = this_board->i_hwdrv_CancelAnalogInput; + + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Allocate and Initialise AO Subdevice Structures */ + s = dev->subdevices + 1; + if (devpriv->s_EeParameters.i_NbrAoChannel) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = devpriv->s_EeParameters.i_NbrAoChannel; + s->maxdata = devpriv->s_EeParameters.i_AoMaxdata; + s->len_chanlist = + devpriv->s_EeParameters.i_NbrAoChannel; + s->range_table = this_board->pr_AoRangelist; + s->insn_config = + this_board->i_hwdrv_InsnConfigAnalogOutput; + s->insn_write = + this_board->i_hwdrv_InsnWriteAnalogOutput; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + /* Allocate and Initialise DI Subdevice Structures */ + s = dev->subdevices + 2; + if (devpriv->s_EeParameters.i_NbrDiChannel) { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = devpriv->s_EeParameters.i_NbrDiChannel; + s->maxdata = 1; + s->len_chanlist = + devpriv->s_EeParameters.i_NbrDiChannel; + s->range_table = &range_digital; + s->io_bits = 0; /* all bits input */ + s->insn_config = + this_board->i_hwdrv_InsnConfigDigitalInput; + s->insn_read = this_board->i_hwdrv_InsnReadDigitalInput; + s->insn_write = + this_board->i_hwdrv_InsnWriteDigitalInput; + s->insn_bits = this_board->i_hwdrv_InsnBitsDigitalInput; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + /* Allocate and Initialise DO Subdevice Structures */ + s = dev->subdevices + 3; + if (devpriv->s_EeParameters.i_NbrDoChannel) { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = + SDF_READABLE | SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = devpriv->s_EeParameters.i_NbrDoChannel; + s->maxdata = devpriv->s_EeParameters.i_DoMaxdata; + s->len_chanlist = + devpriv->s_EeParameters.i_NbrDoChannel; + s->range_table = &range_digital; + s->io_bits = 0xf; /* all bits output */ + + s->insn_config = this_board->i_hwdrv_InsnConfigDigitalOutput; /* for digital output memory.. */ + s->insn_write = + this_board->i_hwdrv_InsnWriteDigitalOutput; + s->insn_bits = + this_board->i_hwdrv_InsnBitsDigitalOutput; + s->insn_read = + this_board->i_hwdrv_InsnReadDigitalOutput; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Allocate and Initialise Timer Subdevice Structures */ + s = dev->subdevices + 4; + if (devpriv->s_EeParameters.i_Timer) { + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 1; + s->maxdata = 0; + s->len_chanlist = 1; + s->range_table = &range_digital; + + s->insn_write = this_board->i_hwdrv_InsnWriteTimer; + s->insn_read = this_board->i_hwdrv_InsnReadTimer; + s->insn_config = this_board->i_hwdrv_InsnConfigTimer; + s->insn_bits = this_board->i_hwdrv_InsnBitsTimer; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Allocate and Initialise TTL */ + s = dev->subdevices + 5; + if (this_board->i_NbrTTLChannel) { + s->type = COMEDI_SUBD_TTLIO; + s->subdev_flags = + SDF_WRITEABLE | SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->i_NbrTTLChannel; + s->maxdata = 1; + s->io_bits = 0; /* all bits input */ + s->len_chanlist = this_board->i_NbrTTLChannel; + s->range_table = &range_digital; + s->insn_config = this_board->i_hwdr_ConfigInitTTLIO; + s->insn_bits = this_board->i_hwdr_ReadTTLIOBits; + s->insn_read = this_board->i_hwdr_ReadTTLIOAllPortValue; + s->insn_write = this_board->i_hwdr_WriteTTLIOChlOnOff; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* EEPROM */ + s = dev->subdevices + 6; + if (this_board->i_PCIEeprom) { + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE | SDF_INTERNAL; + s->n_chan = 256; + s->maxdata = 0xffff; + s->insn_read = i_ADDIDATA_InsnReadEeprom; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + } + + printk("\ni_ADDI_Attach end\n"); + i_ADDI_Reset(dev); + devpriv->b_ValidDriver = 1; + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function name : static int i_ADDI_Detach(struct comedi_device *dev) | +| | +| | ++----------------------------------------------------------------------------+ +| Task : Deallocates resources of the addi_common driver | +| Free the DMA buffers, unregister irq. | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| | +| | ++----------------------------------------------------------------------------+ +| Return Value : 0 | +| | ++----------------------------------------------------------------------------+ +*/ + +static int i_ADDI_Detach(struct comedi_device *dev) +{ + + if (dev->private) { + if (devpriv->b_ValidDriver) { + i_ADDI_Reset(dev); + } + + if (dev->irq) { + free_irq(dev->irq, dev); + } + + if ((this_board->pc_EepromChip == NULL) + || (strcmp(this_board->pc_EepromChip, + ADDIDATA_9054) != 0)) { + if (devpriv->allocated) { + i_pci_card_free(devpriv->amcc); + } + + if (devpriv->ul_DmaBufferVirtual[0]) { + free_pages((unsigned long)devpriv-> + ul_DmaBufferVirtual[0], + devpriv->ui_DmaBufferPages[0]); + } + + if (devpriv->ul_DmaBufferVirtual[1]) { + free_pages((unsigned long)devpriv-> + ul_DmaBufferVirtual[1], + devpriv->ui_DmaBufferPages[1]); + } + } else { + iounmap(devpriv->dw_AiBase); + + if (devpriv->allocated) { + i_pci_card_free(devpriv->amcc); + } + } + + if (pci_list_builded) { + /* v_pci_card_list_cleanup(PCI_VENDOR_ID_AMCC); */ + v_pci_card_list_cleanup(this_board->i_VendorId); + pci_list_builded = 0; + } + } + + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function name : static int i_ADDI_Reset(struct comedi_device *dev) | +| | ++----------------------------------------------------------------------------+ +| Task : Disables all interrupts, Resets digital output to low, | +| Set all analog output to low | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| | +| | ++----------------------------------------------------------------------------+ +| Return Value : 0 | +| | ++----------------------------------------------------------------------------+ +*/ + +static int i_ADDI_Reset(struct comedi_device *dev) +{ + + this_board->i_hwdrv_Reset(dev); + return 0; +} + +/* Interrupt function */ +/* ++----------------------------------------------------------------------------+ +| Function name : | +|static void v_ADDI_Interrupt(int irq, void *d) | +| | ++----------------------------------------------------------------------------+ +| Task : Registerd interrupt routine | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : int irq | +| | +| | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +static irqreturn_t v_ADDI_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + this_board->v_hwdrv_Interrupt(irq, d); + return IRQ_RETVAL(1); +} + +/* EEPROM Read Function */ +/* ++----------------------------------------------------------------------------+ +| Function name : | +|INT i_ADDIDATA_InsnReadEeprom(struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data) +| | ++----------------------------------------------------------------------------+ +| Task : Read 256 words from EEPROM | +| | ++----------------------------------------------------------------------------+ +| Input Parameters :(struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data) | +| | +| | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +static int i_ADDIDATA_InsnReadEeprom(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned short w_Data; + unsigned short w_Address; + w_Address = CR_CHAN(insn->chanspec); /* address to be read as 0,1,2,3...255 */ + + w_Data = w_EepromReadWord(devpriv->i_IobaseAmcc, + this_board->pc_EepromChip, 0x100 + (2 * w_Address)); + data[0] = w_Data; + /* multiplied by 2 bcozinput will be like 0,1,2...255 */ + return insn->n; + +} diff --git a/drivers/staging/comedi/drivers/addi-data/addi_common.h b/drivers/staging/comedi/drivers/addi-data/addi_common.h new file mode 100644 index 00000000..c6980b7d --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/addi_common.h @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../comedidev.h" +#include "addi_amcc_s5933.h" + +#define ERROR -1 +#define SUCCESS 1 + +#define LOBYTE(W) (unsigned char)((W) & 0xFF) +#define HIBYTE(W) (unsigned char)(((W) >> 8) & 0xFF) +#define MAKEWORD(H, L) (unsigned short)((L) | ((H) << 8)) +#define LOWORD(W) (unsigned short)((W) & 0xFFFF) +#define HIWORD(W) (unsigned short)(((W) >> 16) & 0xFFFF) +#define MAKEDWORD(H, L) (unsigned int)((L) | ((H) << 16)) + +#define ADDI_ENABLE 1 +#define ADDI_DISABLE 0 +#define APCI1710_SAVE_INTERRUPT 1 + +#define ADDIDATA_EEPROM 1 +#define ADDIDATA_NO_EEPROM 0 +#define ADDIDATA_93C76 "93C76" +#define ADDIDATA_S5920 "S5920" +#define ADDIDATA_S5933 "S5933" +#define ADDIDATA_9054 "9054" + +/* ADDIDATA Enable Disable */ +#define ADDIDATA_ENABLE 1 +#define ADDIDATA_DISABLE 0 + +/* Structures */ + +/* structure for the boardtype */ +struct addi_board { + const char *pc_DriverName; /* driver name */ + int i_VendorId; /* PCI vendor a device ID of card */ + int i_DeviceId; + int i_IorangeBase0; + int i_IorangeBase1; + int i_IorangeBase2; /* base 2 range */ + int i_IorangeBase3; /* base 3 range */ + int i_PCIEeprom; /* eeprom present or not */ + char *pc_EepromChip; /* type of chip */ + int i_NbrAiChannel; /* num of A/D chans */ + int i_NbrAiChannelDiff; /* num of A/D chans in diff mode */ + int i_AiChannelList; /* len of chanlist */ + int i_NbrAoChannel; /* num of D/A chans */ + int i_AiMaxdata; /* resolution of A/D */ + int i_AoMaxdata; /* resolution of D/A */ + const struct comedi_lrange *pr_AiRangelist; /* rangelist for A/D */ + const struct comedi_lrange *pr_AoRangelist; /* rangelist for D/A */ + + int i_NbrDiChannel; /* Number of DI channels */ + int i_NbrDoChannel; /* Number of DO channels */ + int i_DoMaxdata; /* data to set all channels high */ + + int i_NbrTTLChannel; /* Number of TTL channels */ + const struct comedi_lrange *pr_TTLRangelist; /* rangelist for TTL */ + + int i_Dma; /* dma present or not */ + int i_Timer; /* timer subdevice present or not */ + unsigned char b_AvailableConvertUnit; + unsigned int ui_MinAcquisitiontimeNs; /* Minimum Acquisition in Nano secs */ + unsigned int ui_MinDelaytimeNs; /* Minimum Delay in Nano secs */ + + /* interrupt and reset */ + void (*v_hwdrv_Interrupt)(int irq, void *d); + int (*i_hwdrv_Reset)(struct comedi_device *dev); + + /* Subdevice functions */ + + /* ANALOG INPUT */ + int (*i_hwdrv_InsnConfigAnalogInput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnReadAnalogInput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnWriteAnalogInput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnBitsAnalogInput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_CommandTestAnalogInput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); + int (*i_hwdrv_CommandAnalogInput)(struct comedi_device *dev, + struct comedi_subdevice *s); + int (*i_hwdrv_CancelAnalogInput)(struct comedi_device *dev, + struct comedi_subdevice *s); + + /* Analog Output */ + int (*i_hwdrv_InsnConfigAnalogOutput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnWriteAnalogOutput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnBitsAnalogOutput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + + /* Digital Input */ + int (*i_hwdrv_InsnConfigDigitalInput) (struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnReadDigitalInput) (struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnWriteDigitalInput) (struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnBitsDigitalInput) (struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + + /* Digital Output */ + int (*i_hwdrv_InsnConfigDigitalOutput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnWriteDigitalOutput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnBitsDigitalOutput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnReadDigitalOutput)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + + /* TIMER */ + int (*i_hwdrv_InsnConfigTimer)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + int (*i_hwdrv_InsnWriteTimer)(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdrv_InsnReadTimer)(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + int (*i_hwdrv_InsnBitsTimer)(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + + /* TTL IO */ + int (*i_hwdr_ConfigInitTTLIO)(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdr_ReadTTLIOBits)(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + int (*i_hwdr_ReadTTLIOAllPortValue)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + int (*i_hwdr_WriteTTLIOChlOnOff)(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +}; + +/* MODULE INFO STRUCTURE */ + +union str_ModuleInfo { + /* Incremental counter infos */ + struct { + union { + struct { + unsigned char b_ModeRegister1; + unsigned char b_ModeRegister2; + unsigned char b_ModeRegister3; + unsigned char b_ModeRegister4; + } s_ByteModeRegister; + unsigned int dw_ModeRegister1_2_3_4; + } s_ModeRegister; + + struct { + unsigned int b_IndexInit:1; + unsigned int b_CounterInit:1; + unsigned int b_ReferenceInit:1; + unsigned int b_IndexInterruptOccur:1; + unsigned int b_CompareLogicInit:1; + unsigned int b_FrequencyMeasurementInit:1; + unsigned int b_FrequencyMeasurementEnable:1; + } s_InitFlag; + + } s_SiemensCounterInfo; + + /* SSI infos */ + struct { + unsigned char b_SSIProfile; + unsigned char b_PositionTurnLength; + unsigned char b_TurnCptLength; + unsigned char b_SSIInit; + } s_SSICounterInfo; + + /* TTL I/O infos */ + struct { + unsigned char b_TTLInit; + unsigned char b_PortConfiguration[4]; + } s_TTLIOInfo; + + /* Digital I/O infos */ + struct { + unsigned char b_DigitalInit; + unsigned char b_ChannelAMode; + unsigned char b_ChannelBMode; + unsigned char b_OutputMemoryEnabled; + unsigned int dw_OutputMemory; + } s_DigitalIOInfo; + + /*********************/ + /* 82X54 timer infos */ + /*********************/ + + struct { + struct { + unsigned char b_82X54Init; + unsigned char b_InputClockSelection; + unsigned char b_InputClockLevel; + unsigned char b_OutputLevel; + unsigned char b_HardwareGateLevel; + unsigned int dw_ConfigurationWord; + } s_82X54TimerInfo[3]; + unsigned char b_InterruptMask; + } s_82X54ModuleInfo; + + /*********************/ + /* Chronometer infos */ + /*********************/ + + struct { + unsigned char b_ChronoInit; + unsigned char b_InterruptMask; + unsigned char b_PCIInputClock; + unsigned char b_TimingUnit; + unsigned char b_CycleMode; + double d_TimingInterval; + unsigned int dw_ConfigReg; + } s_ChronoModuleInfo; + + /***********************/ + /* Pulse encoder infos */ + /***********************/ + + struct { + struct { + unsigned char b_PulseEncoderInit; + } s_PulseEncoderInfo[4]; + unsigned int dw_SetRegister; + unsigned int dw_ControlRegister; + unsigned int dw_StatusRegister; + } s_PulseEncoderModuleInfo; + + /* Tor conter infos */ + struct { + struct { + unsigned char b_TorCounterInit; + unsigned char b_TimingUnit; + unsigned char b_InterruptEnable; + double d_TimingInterval; + unsigned int ul_RealTimingInterval; + } s_TorCounterInfo[2]; + unsigned char b_PCIInputClock; + } s_TorCounterModuleInfo; + + /* PWM infos */ + struct { + struct { + unsigned char b_PWMInit; + unsigned char b_TimingUnit; + unsigned char b_InterruptEnable; + double d_LowTiming; + double d_HighTiming; + unsigned int ul_RealLowTiming; + unsigned int ul_RealHighTiming; + } s_PWMInfo[2]; + unsigned char b_ClockSelection; + } s_PWMModuleInfo; + + /* ETM infos */ + struct { + struct { + unsigned char b_ETMEnable; + unsigned char b_ETMInterrupt; + } s_ETMInfo[2]; + unsigned char b_ETMInit; + unsigned char b_TimingUnit; + unsigned char b_ClockSelection; + double d_TimingInterval; + unsigned int ul_Timing; + } s_ETMModuleInfo; + + /* CDA infos */ + struct { + unsigned char b_CDAEnable; + unsigned char b_CDAInterrupt; + unsigned char b_CDAInit; + unsigned char b_FctSelection; + unsigned char b_CDAReadFIFOOverflow; + } s_CDAModuleInfo; + +}; + +/* Private structure for the addi_apci3120 driver */ +struct addi_private { + + int iobase; + int i_IobaseAmcc; /* base+size for AMCC chip */ + int i_IobaseAddon; /* addon base address */ + int i_IobaseReserved; + void __iomem *dw_AiBase; + struct pcilst_struct *amcc; /* ptr too AMCC data */ + unsigned char allocated; /* we have blocked card */ + unsigned char b_ValidDriver; /* driver is ok */ + unsigned char b_AiContinuous; /* we do unlimited AI */ + unsigned char b_AiInitialisation; + unsigned int ui_AiActualScan; /* how many scans we finished */ + unsigned int ui_AiBufferPtr; /* data buffer ptr in samples */ + unsigned int ui_AiNbrofChannels; /* how many channels is measured */ + unsigned int ui_AiScanLength; /* Length of actual scanlist */ + unsigned int ui_AiActualScanPosition; /* position in actual scan */ + unsigned int *pui_AiChannelList; /* actual chanlist */ + unsigned int ui_AiChannelList[32]; /* actual chanlist */ + unsigned char b_AiChannelConfiguration[32]; /* actual chanlist */ + unsigned int ui_AiReadData[32]; + unsigned int dw_AiInitialised; + unsigned int ui_AiTimer0; /* Timer Constant for Timer0 */ + unsigned int ui_AiTimer1; /* Timer constant for Timer1 */ + unsigned int ui_AiFlags; + unsigned int ui_AiDataLength; + short *AiData; /* Pointer to sample data */ + unsigned int ui_AiNbrofScans; /* number of scans to do */ + unsigned short us_UseDma; /* To use Dma or not */ + unsigned char b_DmaDoubleBuffer; /* we can use double buffering */ + unsigned int ui_DmaActualBuffer; /* which buffer is used now */ + /* UPDATE-0.7.57->0.7.68 */ + /* unsigned int ul_DmaBufferVirtual[2]; pointers to begin of DMA buffer */ + short *ul_DmaBufferVirtual[2]; /* pointers to begin of DMA buffer */ + unsigned int ul_DmaBufferHw[2]; /* hw address of DMA buff */ + unsigned int ui_DmaBufferSize[2]; /* size of dma buffer in bytes */ + unsigned int ui_DmaBufferUsesize[2]; /* which size we may now used for transfer */ + unsigned int ui_DmaBufferSamples[2]; /* size in samples */ + unsigned int ui_DmaBufferPages[2]; /* number of pages in buffer */ + unsigned char b_DigitalOutputRegister; /* Digital Output Register */ + unsigned char b_OutputMemoryStatus; + unsigned char b_AnalogInputChannelNbr; /* Analog input channel Nbr */ + unsigned char b_AnalogOutputChannelNbr; /* Analog input Output Nbr */ + unsigned char b_TimerSelectMode; /* Contain data written at iobase + 0C */ + unsigned char b_ModeSelectRegister; /* Contain data written at iobase + 0E */ + unsigned short us_OutputRegister; /* Contain data written at iobase + 0 */ + unsigned char b_InterruptState; + unsigned char b_TimerInit; /* Specify if InitTimerWatchdog was load */ + unsigned char b_TimerStarted; /* Specify if timer 2 is running or not */ + unsigned char b_Timer2Mode; /* Specify the timer 2 mode */ + unsigned char b_Timer2Interrupt; /* Timer2 interrupt enable or disable */ + unsigned char b_AiCyclicAcquisition; /* indicate cyclic acquisition */ + unsigned char b_InterruptMode; /* eoc eos or dma */ + unsigned char b_EocEosInterrupt; /* Enable disable eoc eos interrupt */ + unsigned int ui_EocEosConversionTime; + unsigned char b_EocEosConversionTimeBase; + unsigned char b_SingelDiff; + unsigned char b_ExttrigEnable; /* To enable or disable external trigger */ + + /* Pointer to the current process */ + struct task_struct *tsk_Current; + + /* Hardware board infos for 1710 */ + struct { + unsigned int ui_Address; /* Board address */ + unsigned int ui_FlashAddress; + unsigned char b_InterruptNbr; /* Board interrupt number */ + unsigned char b_SlotNumber; /* PCI slot number */ + unsigned char b_BoardVersion; + unsigned int dw_MolduleConfiguration[4]; /* Module config */ + } s_BoardInfos; + + /* Interrupt infos */ + struct { + unsigned int ul_InterruptOccur; /* 0 : No interrupt occur */ + /* > 0 : Interrupt occur */ + unsigned int ui_Read; /* Read FIFO */ + unsigned int ui_Write; /* Write FIFO */ + struct { + unsigned char b_OldModuleMask; + unsigned int ul_OldInterruptMask; /* Interrupt mask */ + unsigned int ul_OldCounterLatchValue; /* Interrupt counter value */ + } s_FIFOInterruptParameters[APCI1710_SAVE_INTERRUPT]; + } s_InterruptParameters; + + union str_ModuleInfo s_ModuleInfo[4]; + unsigned int ul_TTLPortConfiguration[10]; + + /* Parameters read from EEPROM overriding static board info */ + struct { + int i_NbrAiChannel; /* num of A/D chans */ + int i_NbrAoChannel; /* num of D/A chans */ + int i_AiMaxdata; /* resolution of A/D */ + int i_AoMaxdata; /* resolution of D/A */ + int i_NbrDiChannel; /* Number of DI channels */ + int i_NbrDoChannel; /* Number of DO channels */ + int i_DoMaxdata; /* data to set all channels high */ + int i_Dma; /* dma present or not */ + int i_Timer; /* timer subdevice present or not */ + unsigned int ui_MinAcquisitiontimeNs; + /* Minimum Acquisition in Nano secs */ + unsigned int ui_MinDelaytimeNs; + /* Minimum Delay in Nano secs */ + } s_EeParameters; +}; + +static unsigned short pci_list_builded; /* set to 1 when list of card is known */ + +/* Function declarations */ +static int i_ADDI_Attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int i_ADDI_Detach(struct comedi_device *dev); +static int i_ADDI_Reset(struct comedi_device *dev); + +static irqreturn_t v_ADDI_Interrupt(int irq, void *d); +static int i_ADDIDATA_InsnReadEeprom(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); diff --git a/drivers/staging/comedi/drivers/addi-data/addi_eeprom.c b/drivers/staging/comedi/drivers/addi-data/addi_eeprom.c new file mode 100644 index 00000000..3a9339b9 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/addi_eeprom.c @@ -0,0 +1,1162 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstrasse 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : ADDI DATA | Compiler : GCC | + | Modulname : addi_eeprom.c | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description : ADDI EEPROM Module | + +-----------------------------------------------------------------------+ + | UPDATE'S | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +#define NVCMD_BEGIN_READ (0x7 << 5) /* nvRam begin read command */ +#define NVCMD_LOAD_LOW (0x4 << 5) /* nvRam load low command */ +#define NVCMD_LOAD_HIGH (0x5 << 5) /* nvRam load high command */ +#define EE76_CMD_LEN 13 /* bits in instructions */ +#define EE_READ 0x0180 /* 01 1000 0000 read instruction */ + +#define EEPROM_DIGITALINPUT 0 +#define EEPROM_DIGITALOUTPUT 1 +#define EEPROM_ANALOGINPUT 2 +#define EEPROM_ANALOGOUTPUT 3 +#define EEPROM_TIMER 4 +#define EEPROM_WATCHDOG 5 +#define EEPROM_TIMER_WATCHDOG_COUNTER 10 + +struct str_Functionality { + unsigned char b_Type; + unsigned short w_Address; +}; + +struct str_MainHeader { + unsigned short w_HeaderSize; + unsigned char b_Nfunctions; + struct str_Functionality s_Functions[7]; +}; + +struct str_DigitalInputHeader { + unsigned short w_Nchannel; + unsigned char b_Interruptible; + unsigned short w_NinterruptLogic; +}; + +struct str_DigitalOutputHeader { + + unsigned short w_Nchannel; +}; + + +/* used for timer as well as watchdog */ + +struct str_TimerDetails { + + unsigned short w_HeaderSize; + unsigned char b_Resolution; + unsigned char b_Mode; /* in case of Watchdog it is functionality */ + unsigned short w_MinTiming; + unsigned char b_TimeBase; +}; + +struct str_TimerMainHeader { + + + unsigned short w_Ntimer; + struct str_TimerDetails s_TimerDetails[4]; /* supports 4 timers */ +}; + + +struct str_AnalogOutputHeader { + unsigned short w_Nchannel; + unsigned char b_Resolution; +}; + +struct str_AnalogInputHeader { + unsigned short w_Nchannel; + unsigned short w_MinConvertTiming; + unsigned short w_MinDelayTiming; + unsigned char b_HasDma; + unsigned char b_Resolution; +}; + + + /*****************************************/ + /* Read Header Functions */ + /*****************************************/ + +int i_EepromReadMainHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, struct comedi_device *dev); + +int i_EepromReadDigitalInputHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_DigitalInputHeader *s_Header); + +int i_EepromReadDigitalOutputHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_DigitalOutputHeader *s_Header); + +int i_EepromReadTimerHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_TimerMainHeader *s_Header); + +int i_EepromReadAnlogOutputHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_AnalogOutputHeader *s_Header); + +int i_EepromReadAnlogInputHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_AnalogInputHeader *s_Header); + + /******************************************/ + /* Eeprom Specific Functions */ + /******************************************/ +unsigned short w_EepromReadWord(unsigned short w_PCIBoardEepromAddress, char *pc_PCIChipInformation, + unsigned short w_EepromStartAddress); +void v_EepromWaitBusy(unsigned short w_PCIBoardEepromAddress); +void v_EepromClock76(unsigned int dw_Address, unsigned int dw_RegisterValue); +void v_EepromWaitBusy(unsigned short w_PCIBoardEepromAddress); +void v_EepromSendCommand76(unsigned int dw_Address, unsigned int dw_EepromCommand, + unsigned char b_DataLengthInBits); +void v_EepromCs76Read(unsigned int dw_Address, unsigned short w_offset, unsigned short *pw_Value); + +/* ++----------------------------------------------------------------------------+ +| Function Name : unsigned short w_EepromReadWord | +| (unsigned short w_PCIBoardEepromAddress, | +| char * pc_PCIChipInformation, | +| unsigned short w_EepromStartAddress) | ++----------------------------------------------------------------------------+ +| Task : Read from eepromn a word | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned short w_PCIBoardEepromAddress : PCI eeprom address | +| | +| char *pc_PCIChipInformation : PCI Chip Type. | +| | +| unsigned short w_EepromStartAddress : Selected eeprom address | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : Read word value from eeprom | ++----------------------------------------------------------------------------+ +*/ + +unsigned short w_EepromReadWord(unsigned short w_PCIBoardEepromAddress, char *pc_PCIChipInformation, + unsigned short w_EepromStartAddress) +{ + + unsigned char b_Counter = 0; + + unsigned char b_ReadByte = 0; + + unsigned char b_ReadLowByte = 0; + + unsigned char b_ReadHighByte = 0; + + unsigned char b_SelectedAddressLow = 0; + + unsigned char b_SelectedAddressHigh = 0; + + unsigned short w_ReadWord = 0; + + /**************************/ + + /* Test the PCI chip type */ + + /**************************/ + + if ((!strcmp(pc_PCIChipInformation, "S5920")) || + (!strcmp(pc_PCIChipInformation, "S5933"))) + { + + for (b_Counter = 0; b_Counter < 2; b_Counter++) + { + + b_SelectedAddressLow = (w_EepromStartAddress + b_Counter) % 256; /* Read the low 8 bit part */ + + b_SelectedAddressHigh = (w_EepromStartAddress + b_Counter) / 256; /* Read the high 8 bit part */ + + /************************************/ + + /* Select the load low address mode */ + + /************************************/ + + outb(NVCMD_LOAD_LOW, w_PCIBoardEepromAddress + 0x3F); + + /****************/ + + /* Wait on busy */ + + /****************/ + + v_EepromWaitBusy(w_PCIBoardEepromAddress); + + /************************/ + + /* Load the low address */ + + /************************/ + + outb(b_SelectedAddressLow, + w_PCIBoardEepromAddress + 0x3E); + + /****************/ + + /* Wait on busy */ + + /****************/ + + v_EepromWaitBusy(w_PCIBoardEepromAddress); + + /*************************************/ + + /* Select the load high address mode */ + + /*************************************/ + + outb(NVCMD_LOAD_HIGH, w_PCIBoardEepromAddress + 0x3F); + + /****************/ + + /* Wait on busy */ + + /****************/ + + v_EepromWaitBusy(w_PCIBoardEepromAddress); + + /*************************/ + + /* Load the high address */ + + /*************************/ + + outb(b_SelectedAddressHigh, + w_PCIBoardEepromAddress + 0x3E); + + /****************/ + + /* Wait on busy */ + + /****************/ + + v_EepromWaitBusy(w_PCIBoardEepromAddress); + + /************************/ + + /* Select the READ mode */ + + /************************/ + + outb(NVCMD_BEGIN_READ, w_PCIBoardEepromAddress + 0x3F); + + /****************/ + + /* Wait on busy */ + + /****************/ + + v_EepromWaitBusy(w_PCIBoardEepromAddress); + + /*****************************/ + + /* Read data into the EEPROM */ + + /*****************************/ + + b_ReadByte = inb(w_PCIBoardEepromAddress + 0x3E); + + /****************/ + + /* Wait on busy */ + + /****************/ + + v_EepromWaitBusy(w_PCIBoardEepromAddress); + + /*********************************/ + + /* Select the upper address part */ + + /*********************************/ + + if (b_Counter == 0) + { + + b_ReadLowByte = b_ReadByte; + + } /* if(b_Counter==0) */ + + else + { + + b_ReadHighByte = b_ReadByte; + + } /* if(b_Counter==0) */ + + } /* for (b_Counter=0; b_Counter<2; b_Counter++) */ + + w_ReadWord = (b_ReadLowByte | (((unsigned short) b_ReadHighByte) * 256)); + + } /* end of if ((!strcmp(pc_PCIChipInformation, "S5920")) || (!strcmp(pc_PCIChipInformation, "S5933"))) */ + + if (!strcmp(pc_PCIChipInformation, "93C76")) + { + + /*************************************/ + + /* Read 16 bit from the EEPROM 93C76 */ + + /*************************************/ + + v_EepromCs76Read(w_PCIBoardEepromAddress, w_EepromStartAddress, + &w_ReadWord); + + } + + return w_ReadWord; + +} + +/* + ++----------------------------------------------------------------------------+ + +| Function Name : void v_EepromWaitBusy | + +| (unsigned short w_PCIBoardEepromAddress) | + ++----------------------------------------------------------------------------+ + +| Task : Wait the busy flag from PCI controller | + ++----------------------------------------------------------------------------+ + +| Input Parameters : unsigned short w_PCIBoardEepromAddress : PCI eeprom base address | + ++----------------------------------------------------------------------------+ + +| Output Parameters : - | + ++----------------------------------------------------------------------------+ + +| Return Value : - | + ++----------------------------------------------------------------------------+ + +*/ + +void v_EepromWaitBusy(unsigned short w_PCIBoardEepromAddress) +{ + + unsigned char b_EepromBusy = 0; + + do + { + + /*************/ + + /* IMPORTANT */ + + /*************/ + + /************************************************************************/ + + /* An error has been written in the AMCC 5933 book at the page B-13 */ + + /* Ex: if you read a byte and look for the busy statusEEPROM=0x80 and */ + + /* the operator register is AMCC_OP_REG_MCSR+3 */ + + /* unsigned short read EEPROM=0x8000 andAMCC_OP_REG_MCSR+2 */ + + /* unsigned int read EEPROM=0x80000000 and AMCC_OP_REG_MCSR */ + + /************************************************************************/ + + b_EepromBusy = inb(w_PCIBoardEepromAddress + 0x3F); + b_EepromBusy = b_EepromBusy & 0x80; + + } while (b_EepromBusy == 0x80); + +} + +/* + ++---------------------------------------------------------------------------------+ + +| Function Name : void v_EepromClock76(unsigned int dw_Address, | + +| unsigned int dw_RegisterValue) | + ++---------------------------------------------------------------------------------+ + +| Task : This function sends the clocking sequence to the EEPROM. | + ++---------------------------------------------------------------------------------+ + +| Input Parameters : unsigned int dw_Address : PCI eeprom base address | + +| unsigned int dw_RegisterValue : PCI eeprom register value to write.| + ++---------------------------------------------------------------------------------+ + +| Output Parameters : - | + ++---------------------------------------------------------------------------------+ + +| Return Value : - | + ++---------------------------------------------------------------------------------+ + +*/ + +void v_EepromClock76(unsigned int dw_Address, unsigned int dw_RegisterValue) +{ + + /************************/ + + /* Set EEPROM clock Low */ + + /************************/ + + outl(dw_RegisterValue & 0x6, dw_Address); + + /***************/ + + /* Wait 0.1 ms */ + + /***************/ + + udelay(100); + + /*************************/ + + /* Set EEPROM clock High */ + + /*************************/ + + outl(dw_RegisterValue | 0x1, dw_Address); + + /***************/ + + /* Wait 0.1 ms */ + + /***************/ + + udelay(100); + +} + +/* + ++---------------------------------------------------------------------------------+ + +| Function Name : void v_EepromSendCommand76(unsigned int dw_Address, | + +| unsigned int dw_EepromCommand, | + +| unsigned char b_DataLengthInBits) | + ++---------------------------------------------------------------------------------+ + +| Task : This function sends a Command to the EEPROM 93C76. | + ++---------------------------------------------------------------------------------+ + +| Input Parameters : unsigned int dw_Address : PCI eeprom base address | + +| unsigned int dw_EepromCommand : PCI eeprom command to write. | + +| unsigned char b_DataLengthInBits : PCI eeprom command data length. | + ++---------------------------------------------------------------------------------+ + +| Output Parameters : - | + ++---------------------------------------------------------------------------------+ + +| Return Value : - | + ++---------------------------------------------------------------------------------+ + +*/ + +void v_EepromSendCommand76(unsigned int dw_Address, unsigned int dw_EepromCommand, + unsigned char b_DataLengthInBits) +{ + + char c_BitPos = 0; + + unsigned int dw_RegisterValue = 0; + + /*****************************/ + + /* Enable EEPROM Chip Select */ + + /*****************************/ + + dw_RegisterValue = 0x2; + + /********************************************************************/ + + /* Toggle EEPROM's Chip select to get it out of Shift Register Mode */ + + /********************************************************************/ + + outl(dw_RegisterValue, dw_Address); + + /***************/ + + /* Wait 0.1 ms */ + + /***************/ + + udelay(100); + + /*******************************************/ + + /* Send EEPROM command - one bit at a time */ + + /*******************************************/ + + for (c_BitPos = (b_DataLengthInBits - 1); c_BitPos >= 0; c_BitPos--) + { + + /**********************************/ + + /* Check if current bit is 0 or 1 */ + + /**********************************/ + + if (dw_EepromCommand & (1 << c_BitPos)) + { + + /***********/ + + /* Write 1 */ + + /***********/ + + dw_RegisterValue = dw_RegisterValue | 0x4; + + } + + else + { + + /***********/ + + /* Write 0 */ + + /***********/ + + dw_RegisterValue = dw_RegisterValue & 0x3; + + } + + /*********************/ + + /* Write the command */ + + /*********************/ + + outl(dw_RegisterValue, dw_Address); + + /***************/ + + /* Wait 0.1 ms */ + + /***************/ + + udelay(100); + + /****************************/ + + /* Trigger the EEPROM clock */ + + /****************************/ + + v_EepromClock76(dw_Address, dw_RegisterValue); + + } + +} + +/* + ++---------------------------------------------------------------------------------+ + +| Function Name : void v_EepromCs76Read(unsigned int dw_Address, | + +| unsigned short w_offset, | + +| unsigned short * pw_Value) | + ++---------------------------------------------------------------------------------+ + +| Task : This function read a value from the EEPROM 93C76. | + ++---------------------------------------------------------------------------------+ + +| Input Parameters : unsigned int dw_Address : PCI eeprom base address | + +| unsigned short w_offset : Offset of the address to read | + +| unsigned short * pw_Value : PCI eeprom 16 bit read value. | + ++---------------------------------------------------------------------------------+ + +| Output Parameters : - | + ++---------------------------------------------------------------------------------+ + +| Return Value : - | + ++---------------------------------------------------------------------------------+ + +*/ + +void v_EepromCs76Read(unsigned int dw_Address, unsigned short w_offset, unsigned short *pw_Value) +{ + + char c_BitPos = 0; + + unsigned int dw_RegisterValue = 0; + + unsigned int dw_RegisterValueRead = 0; + + /*************************************************/ + + /* Send EEPROM read command and offset to EEPROM */ + + /*************************************************/ + + v_EepromSendCommand76(dw_Address, (EE_READ << 4) | (w_offset / 2), + EE76_CMD_LEN); + + /*******************************/ + + /* Get the last register value */ + + /*******************************/ + + dw_RegisterValue = (((w_offset / 2) & 0x1) << 2) | 0x2; + + /*****************************/ + + /* Set the 16-bit value of 0 */ + + /*****************************/ + + *pw_Value = 0; + + /************************/ + + /* Get the 16-bit value */ + + /************************/ + + for (c_BitPos = 0; c_BitPos < 16; c_BitPos++) + { + + /****************************/ + + /* Trigger the EEPROM clock */ + + /****************************/ + + v_EepromClock76(dw_Address, dw_RegisterValue); + + /**********************/ + + /* Get the result bit */ + + /**********************/ + + dw_RegisterValueRead = inl(dw_Address); + + /***************/ + + /* Wait 0.1 ms */ + + /***************/ + + udelay(100); + + /***************************************/ + + /* Get bit value and shift into result */ + + /***************************************/ + + if (dw_RegisterValueRead & 0x8) + { + + /**********/ + + /* Read 1 */ + + /**********/ + + *pw_Value = (*pw_Value << 1) | 0x1; + + } + + else + { + + /**********/ + + /* Read 0 */ + + /**********/ + + *pw_Value = (*pw_Value << 1); + + } + + } + + /*************************/ + + /* Clear all EEPROM bits */ + + /*************************/ + + dw_RegisterValue = 0x0; + + /********************************************************************/ + + /* Toggle EEPROM's Chip select to get it out of Shift Register Mode */ + + /********************************************************************/ + + outl(dw_RegisterValue, dw_Address); + + /***************/ + + /* Wait 0.1 ms */ + + /***************/ + + udelay(100); + +} + + /******************************************/ + /* EEPROM HEADER READ FUNCTIONS */ + /******************************************/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_EepromReadMainHeader(unsigned short w_PCIBoardEepromAddress, | +| char * pc_PCIChipInformation,struct comedi_device *dev) | ++----------------------------------------------------------------------------+ +| Task : Read from eeprom Main Header | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned short w_PCIBoardEepromAddress : PCI eeprom address | +| | +| char *pc_PCIChipInformation : PCI Chip Type. | +| | +| struct comedi_device *dev : comedi device structure | +| pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0 | ++----------------------------------------------------------------------------+ +*/ + +int i_EepromReadMainHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, struct comedi_device *dev) +{ + unsigned short w_Temp, i, w_Count = 0; + unsigned int ui_Temp; + struct str_MainHeader s_MainHeader; + struct str_DigitalInputHeader s_DigitalInputHeader; + struct str_DigitalOutputHeader s_DigitalOutputHeader; + /* struct str_TimerMainHeader s_TimerMainHeader,s_WatchdogMainHeader; */ + struct str_AnalogOutputHeader s_AnalogOutputHeader; + struct str_AnalogInputHeader s_AnalogInputHeader; + + /* Read size */ + s_MainHeader.w_HeaderSize = + w_EepromReadWord(w_PCIBoardEepromAddress, pc_PCIChipInformation, + 0x100 + 8); + + /* Read nbr of functionality */ + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, 0x100 + 10); + s_MainHeader.b_Nfunctions = (unsigned char) w_Temp & 0x00FF; + + /* Read functionality details */ + for (i = 0; i < s_MainHeader.b_Nfunctions; i++) { + /* Read Type */ + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, 0x100 + 12 + w_Count); + s_MainHeader.s_Functions[i].b_Type = (unsigned char) w_Temp & 0x3F; + w_Count = w_Count + 2; + /* Read Address */ + s_MainHeader.s_Functions[i].w_Address = + w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, 0x100 + 12 + w_Count); + w_Count = w_Count + 2; + } + + /* Display main header info */ + for (i = 0; i < s_MainHeader.b_Nfunctions; i++) { + + switch (s_MainHeader.s_Functions[i].b_Type) { + case EEPROM_DIGITALINPUT: + i_EepromReadDigitalInputHeader(w_PCIBoardEepromAddress, + pc_PCIChipInformation, + s_MainHeader.s_Functions[i].w_Address, + &s_DigitalInputHeader); + devpriv->s_EeParameters.i_NbrDiChannel = + s_DigitalInputHeader.w_Nchannel; + break; + + case EEPROM_DIGITALOUTPUT: + i_EepromReadDigitalOutputHeader(w_PCIBoardEepromAddress, + pc_PCIChipInformation, + s_MainHeader.s_Functions[i].w_Address, + &s_DigitalOutputHeader); + devpriv->s_EeParameters.i_NbrDoChannel = + s_DigitalOutputHeader.w_Nchannel; + ui_Temp = 0xffffffff; + devpriv->s_EeParameters.i_DoMaxdata = + ui_Temp >> (32 - + devpriv->s_EeParameters.i_NbrDoChannel); + break; + + case EEPROM_ANALOGINPUT: + i_EepromReadAnlogInputHeader(w_PCIBoardEepromAddress, + pc_PCIChipInformation, + s_MainHeader.s_Functions[i].w_Address, + &s_AnalogInputHeader); + if (!(strcmp(this_board->pc_DriverName, "apci3200"))) + devpriv->s_EeParameters.i_NbrAiChannel = + s_AnalogInputHeader.w_Nchannel * 4; + else + devpriv->s_EeParameters.i_NbrAiChannel = + s_AnalogInputHeader.w_Nchannel; + devpriv->s_EeParameters.i_Dma = + s_AnalogInputHeader.b_HasDma; + devpriv->s_EeParameters.ui_MinAcquisitiontimeNs = + (unsigned int) s_AnalogInputHeader.w_MinConvertTiming * + 1000; + devpriv->s_EeParameters.ui_MinDelaytimeNs = + (unsigned int) s_AnalogInputHeader.w_MinDelayTiming * + 1000; + ui_Temp = 0xffff; + devpriv->s_EeParameters.i_AiMaxdata = + ui_Temp >> (16 - + s_AnalogInputHeader.b_Resolution); + break; + + case EEPROM_ANALOGOUTPUT: + i_EepromReadAnlogOutputHeader(w_PCIBoardEepromAddress, + pc_PCIChipInformation, + s_MainHeader.s_Functions[i].w_Address, + &s_AnalogOutputHeader); + devpriv->s_EeParameters.i_NbrAoChannel = + s_AnalogOutputHeader.w_Nchannel; + ui_Temp = 0xffff; + devpriv->s_EeParameters.i_AoMaxdata = + ui_Temp >> (16 - + s_AnalogOutputHeader.b_Resolution); + break; + + case EEPROM_TIMER: + /* Timer subdevice present */ + devpriv->s_EeParameters.i_Timer = 1; + break; + + case EEPROM_WATCHDOG: + /* Timer subdevice present */ + devpriv->s_EeParameters.i_Timer = 1; + break; + + case EEPROM_TIMER_WATCHDOG_COUNTER: + /* Timer subdevice present */ + devpriv->s_EeParameters.i_Timer = 1; + break; + } + } + + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_EepromReadDigitalInputHeader(unsigned short | +| w_PCIBoardEepromAddress,char *pc_PCIChipInformation, | +| unsigned short w_Address,struct str_DigitalInputHeader *s_Header) | +| | ++----------------------------------------------------------------------------+ +| Task : Read Digital Input Header | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned short w_PCIBoardEepromAddress : PCI eeprom address | +| | +| char *pc_PCIChipInformation : PCI Chip Type. | +| | +| struct str_DigitalInputHeader *s_Header: Digita Input Header | +| Pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0 | ++----------------------------------------------------------------------------+ +*/ +int i_EepromReadDigitalInputHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_DigitalInputHeader *s_Header) +{ + unsigned short w_Temp; + + /* read nbr of channels */ + s_Header->w_Nchannel = + w_EepromReadWord(w_PCIBoardEepromAddress, pc_PCIChipInformation, + 0x100 + w_Address + 6); + + /* interruptible or not */ + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, 0x100 + w_Address + 8); + s_Header->b_Interruptible = (unsigned char) (w_Temp >> 7) & 0x01; + +/* How many interruptible logic */ + s_Header->w_NinterruptLogic = + w_EepromReadWord(w_PCIBoardEepromAddress, pc_PCIChipInformation, + 0x100 + w_Address + 10); + + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_EepromReadDigitalOutputHeader(unsigned short | +| w_PCIBoardEepromAddress,char *pc_PCIChipInformation, | +| unsigned short w_Address,struct str_DigitalOutputHeader *s_Header) | +| | ++----------------------------------------------------------------------------+ +| Task : Read Digital Output Header | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned short w_PCIBoardEepromAddress : PCI eeprom address | +| | +| char *pc_PCIChipInformation : PCI Chip Type. | +| | +| struct str_DigitalOutputHeader *s_Header: Digital Output Header| +| Pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0 | ++----------------------------------------------------------------------------+ +*/ +int i_EepromReadDigitalOutputHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_DigitalOutputHeader *s_Header) +{ +/* Read Nbr channels */ + s_Header->w_Nchannel = + w_EepromReadWord(w_PCIBoardEepromAddress, pc_PCIChipInformation, + 0x100 + w_Address + 6); + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_EepromReadTimerHeader(unsigned short w_PCIBoardEepromAddress, | +| char *pc_PCIChipInformation,WORD w_Address, | +| struct str_TimerMainHeader *s_Header) | ++----------------------------------------------------------------------------+ +| Task : Read Timer or Watchdog Header | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned short w_PCIBoardEepromAddress : PCI eeprom address | +| | +| char *pc_PCIChipInformation : PCI Chip Type. | +| | +| struct str_TimerMainHeader *s_Header: Timer Header | +| Pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0 | ++----------------------------------------------------------------------------+ +*/ +int i_EepromReadTimerHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_TimerMainHeader *s_Header) +{ + + unsigned short i, w_Size = 0, w_Temp; + +/* Read No of Timer */ + s_Header->w_Ntimer = + w_EepromReadWord(w_PCIBoardEepromAddress, pc_PCIChipInformation, + 0x100 + w_Address + 6); +/* Read header size */ + + for (i = 0; i < s_Header->w_Ntimer; i++) { + s_Header->s_TimerDetails[i].w_HeaderSize = + w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, + 0x100 + w_Address + 8 + w_Size + 0); + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, + 0x100 + w_Address + 8 + w_Size + 2); + + /* Read Resolution */ + s_Header->s_TimerDetails[i].b_Resolution = + (unsigned char) (w_Temp >> 10) & 0x3F; + + /* Read Mode */ + s_Header->s_TimerDetails[i].b_Mode = + (unsigned char) (w_Temp >> 4) & 0x3F; + + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, + 0x100 + w_Address + 8 + w_Size + 4); + + /* Read MinTiming */ + s_Header->s_TimerDetails[i].w_MinTiming = (w_Temp >> 6) & 0x3FF; + + /* Read Timebase */ + s_Header->s_TimerDetails[i].b_TimeBase = (unsigned char) (w_Temp) & 0x3F; + w_Size += s_Header->s_TimerDetails[i].w_HeaderSize; + } + + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_EepromReadAnlogOutputHeader(unsigned short | +| w_PCIBoardEepromAddress,char *pc_PCIChipInformation, | +| unsigned short w_Address,str_AnalogOutputHeader *s_Header) | ++----------------------------------------------------------------------------+ +| Task : Read Nalog Output Header | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned short w_PCIBoardEepromAddress : PCI eeprom address | +| | +| char *pc_PCIChipInformation : PCI Chip Type. | +| | +| str_AnalogOutputHeader *s_Header:Anlog Output Header | +| Pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0 | ++----------------------------------------------------------------------------+ +*/ + +int i_EepromReadAnlogOutputHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_AnalogOutputHeader *s_Header) +{ + unsigned short w_Temp; + /* No of channels for 1st hard component */ + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, 0x100 + w_Address + 10); + s_Header->w_Nchannel = (w_Temp >> 4) & 0x03FF; + /* Resolution for 1st hard component */ + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, 0x100 + w_Address + 16); + s_Header->b_Resolution = (unsigned char) (w_Temp >> 8) & 0xFF; + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_EepromReadAnlogInputHeader(unsigned short | +| w_PCIBoardEepromAddress,char *pc_PCIChipInformation, | +| unsigned short w_Address,struct str_AnalogInputHeader *s_Header) | ++----------------------------------------------------------------------------+ +| Task : Read Nalog Output Header | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned short w_PCIBoardEepromAddress : PCI eeprom address | +| | +| char *pc_PCIChipInformation : PCI Chip Type. | +| | +| struct str_AnalogInputHeader *s_Header:Anlog Input Header | +| Pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0 | ++----------------------------------------------------------------------------+ +*/ + +/* Reads only for ONE hardware component */ +int i_EepromReadAnlogInputHeader(unsigned short w_PCIBoardEepromAddress, + char *pc_PCIChipInformation, unsigned short w_Address, + struct str_AnalogInputHeader *s_Header) +{ + unsigned short w_Temp, w_Offset; + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, 0x100 + w_Address + 10); + s_Header->w_Nchannel = (w_Temp >> 4) & 0x03FF; + s_Header->w_MinConvertTiming = + w_EepromReadWord(w_PCIBoardEepromAddress, pc_PCIChipInformation, + 0x100 + w_Address + 16); + s_Header->w_MinDelayTiming = + w_EepromReadWord(w_PCIBoardEepromAddress, pc_PCIChipInformation, + 0x100 + w_Address + 30); + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, 0x100 + w_Address + 20); + s_Header->b_HasDma = (w_Temp >> 13) & 0x01; /* whether dma present or not */ + + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, pc_PCIChipInformation, 0x100 + w_Address + 72); /* reading Y */ + w_Temp = w_Temp & 0x00FF; + if (w_Temp) /* Y>0 */ + { + w_Offset = 74 + (2 * w_Temp) + (10 * (1 + (w_Temp / 16))); /* offset of first analog input single header */ + w_Offset = w_Offset + 2; /* resolution */ + } else /* Y=0 */ + { + w_Offset = 74; + w_Offset = w_Offset + 2; /* resolution */ + } + +/* read Resolution */ + w_Temp = w_EepromReadWord(w_PCIBoardEepromAddress, + pc_PCIChipInformation, 0x100 + w_Address + w_Offset); + s_Header->b_Resolution = w_Temp & 0x001F; /* last 5 bits */ + + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/amcc_s5933_58.h b/drivers/staging/comedi/drivers/addi-data/amcc_s5933_58.h new file mode 100644 index 00000000..349e93c2 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/amcc_s5933_58.h @@ -0,0 +1,457 @@ +/* + Modified by umesh on 16th may 2001 + Modified by sarath on 22nd may 2001 +*/ + +/* + comedi/drivers/amcc_s5933_v_58.h + + Stuff for AMCC S5933 PCI Controller + + Author: Michal Dobes + + Inspirated from general-purpose AMCC S5933 PCI Matchmaker driver + made by Andrea Cisternino + and as result of espionage from MITE code made by David A. Schleef. + Thanks to AMCC for their on-line documentation and bus master DMA + example. +*/ + +#ifndef _AMCC_S5933_H_ +#define _AMCC_S5933_H_ + +#include +#include "../../comedidev.h" + +#ifdef PCI_SUPPORT_VER1 +#error Sorry, no support for 2.1.55 and older! :-(((( +#endif + +/***********Added by sarath for compatibility with APCI3120 + +*************************/ + +#define FIFO_ADVANCE_ON_BYTE_2 0x20000000 /* written on base0 */ + +#define AMWEN_ENABLE 0x02 /* added for step 6 dma written on base2 */ +#define A2P_FIFO_WRITE_ENABLE 0x01 + +#define AGCSTS_TC_ENABLE 0x10000000 /* Added for transfer count enable bit */ + +/* ADDON RELATED ADDITIONS */ +/* Constant */ +#define APCI3120_ENABLE_TRANSFER_ADD_ON_LOW 0x00 +#define APCI3120_ENABLE_TRANSFER_ADD_ON_HIGH 0x1200 +#define APCI3120_A2P_FIFO_MANAGEMENT 0x04000400L +#define APCI3120_AMWEN_ENABLE 0x02 +#define APCI3120_A2P_FIFO_WRITE_ENABLE 0x01 +#define APCI3120_FIFO_ADVANCE_ON_BYTE_2 0x20000000L +#define APCI3120_ENABLE_WRITE_TC_INT 0x00004000L +#define APCI3120_CLEAR_WRITE_TC_INT 0x00040000L +#define APCI3120_DISABLE_AMWEN_AND_A2P_FIFO_WRITE 0x0 +#define APCI3120_DISABLE_BUS_MASTER_ADD_ON 0x0 +#define APCI3120_DISABLE_BUS_MASTER_PCI 0x0 + + /* ADD_ON ::: this needed since apci supports 16 bit interface to add on */ +#define APCI3120_ADD_ON_AGCSTS_LOW 0x3C +#define APCI3120_ADD_ON_AGCSTS_HIGH APCI3120_ADD_ON_AGCSTS_LOW + 2 +#define APCI3120_ADD_ON_MWAR_LOW 0x24 +#define APCI3120_ADD_ON_MWAR_HIGH APCI3120_ADD_ON_MWAR_LOW + 2 +#define APCI3120_ADD_ON_MWTC_LOW 0x058 +#define APCI3120_ADD_ON_MWTC_HIGH APCI3120_ADD_ON_MWTC_LOW + 2 + +/* AMCC */ +#define APCI3120_AMCC_OP_MCSR 0x3C +#define APCI3120_AMCC_OP_REG_INTCSR 0x38 + +/*******from here all upward definitions are added by sarath */ + +/****************************************************************************/ +/* AMCC Operation Register Offsets - PCI */ +/****************************************************************************/ + +#define AMCC_OP_REG_OMB1 0x00 +#define AMCC_OP_REG_OMB2 0x04 +#define AMCC_OP_REG_OMB3 0x08 +#define AMCC_OP_REG_OMB4 0x0c +#define AMCC_OP_REG_IMB1 0x10 +#define AMCC_OP_REG_IMB2 0x14 +#define AMCC_OP_REG_IMB3 0x18 +#define AMCC_OP_REG_IMB4 0x1c +#define AMCC_OP_REG_FIFO 0x20 +#define AMCC_OP_REG_MWAR 0x24 +#define AMCC_OP_REG_MWTC 0x28 +#define AMCC_OP_REG_MRAR 0x2c +#define AMCC_OP_REG_MRTC 0x30 +#define AMCC_OP_REG_MBEF 0x34 +#define AMCC_OP_REG_INTCSR 0x38 +#define AMCC_OP_REG_INTCSR_SRC (AMCC_OP_REG_INTCSR + 2) /* int source */ +#define AMCC_OP_REG_INTCSR_FEC (AMCC_OP_REG_INTCSR + 3) /* FIFO ctrl */ +#define AMCC_OP_REG_MCSR 0x3c +#define AMCC_OP_REG_MCSR_NVDATA (AMCC_OP_REG_MCSR + 2) /* Data in byte 2 */ +#define AMCC_OP_REG_MCSR_NVCMD (AMCC_OP_REG_MCSR + 3) /* Command in byte 3 */ + +#define AMCC_FIFO_DEPTH_DWORD 8 +#define AMCC_FIFO_DEPTH_BYTES (8 * sizeof (u32)) + +/****************************************************************************/ +/* AMCC Operation Registers Size - PCI */ +/****************************************************************************/ + +#define AMCC_OP_REG_SIZE 64 /* in bytes */ + +/****************************************************************************/ +/* AMCC Operation Register Offsets - Add-on */ +/****************************************************************************/ + +#define AMCC_OP_REG_AIMB1 0x00 +#define AMCC_OP_REG_AIMB2 0x04 +#define AMCC_OP_REG_AIMB3 0x08 +#define AMCC_OP_REG_AIMB4 0x0c +#define AMCC_OP_REG_AOMB1 0x10 +#define AMCC_OP_REG_AOMB2 0x14 +#define AMCC_OP_REG_AOMB3 0x18 +#define AMCC_OP_REG_AOMB4 0x1c +#define AMCC_OP_REG_AFIFO 0x20 +#define AMCC_OP_REG_AMWAR 0x24 +#define AMCC_OP_REG_APTA 0x28 +#define AMCC_OP_REG_APTD 0x2c +#define AMCC_OP_REG_AMRAR 0x30 +#define AMCC_OP_REG_AMBEF 0x34 +#define AMCC_OP_REG_AINT 0x38 +#define AMCC_OP_REG_AGCSTS 0x3c +#define AMCC_OP_REG_AMWTC 0x58 +#define AMCC_OP_REG_AMRTC 0x5c + +/****************************************************************************/ +/* AMCC - Add-on General Control/Status Register */ +/****************************************************************************/ + +#define AGCSTS_CONTROL_MASK 0xfffff000 +#define AGCSTS_NV_ACC_MASK 0xe0000000 +#define AGCSTS_RESET_MASK 0x0e000000 +#define AGCSTS_NV_DA_MASK 0x00ff0000 +#define AGCSTS_BIST_MASK 0x0000f000 +#define AGCSTS_STATUS_MASK 0x000000ff +#define AGCSTS_TCZERO_MASK 0x000000c0 +#define AGCSTS_FIFO_ST_MASK 0x0000003f + +#define AGCSTS_RESET_MBFLAGS 0x08000000 +#define AGCSTS_RESET_P2A_FIFO 0x04000000 +#define AGCSTS_RESET_A2P_FIFO 0x02000000 +#define AGCSTS_RESET_FIFOS (AGCSTS_RESET_A2P_FIFO | AGCSTS_RESET_P2A_FIFO) + +#define AGCSTS_A2P_TCOUNT 0x00000080 +#define AGCSTS_P2A_TCOUNT 0x00000040 + +#define AGCSTS_FS_P2A_EMPTY 0x00000020 +#define AGCSTS_FS_P2A_HALF 0x00000010 +#define AGCSTS_FS_P2A_FULL 0x00000008 + +#define AGCSTS_FS_A2P_EMPTY 0x00000004 +#define AGCSTS_FS_A2P_HALF 0x00000002 +#define AGCSTS_FS_A2P_FULL 0x00000001 + +/****************************************************************************/ +/* AMCC - Add-on Interrupt Control/Status Register */ +/****************************************************************************/ + +#define AINT_INT_MASK 0x00ff0000 +#define AINT_SEL_MASK 0x0000ffff +#define AINT_IS_ENSEL_MASK 0x00001f1f + +#define AINT_INT_ASSERTED 0x00800000 +#define AINT_BM_ERROR 0x00200000 +#define AINT_BIST_INT 0x00100000 + +#define AINT_RT_COMPLETE 0x00080000 +#define AINT_WT_COMPLETE 0x00040000 + +#define AINT_OUT_MB_INT 0x00020000 +#define AINT_IN_MB_INT 0x00010000 + +#define AINT_READ_COMPL 0x00008000 +#define AINT_WRITE_COMPL 0x00004000 + +#define AINT_OMB_ENABLE 0x00001000 +#define AINT_OMB_SELECT 0x00000c00 +#define AINT_OMB_BYTE 0x00000300 + +#define AINT_IMB_ENABLE 0x00000010 +#define AINT_IMB_SELECT 0x0000000c +#define AINT_IMB_BYTE 0x00000003 + +/* Enable Bus Mastering */ +#define EN_A2P_TRANSFERS 0x00000400 +/* FIFO Flag Reset */ +#define RESET_A2P_FLAGS 0x04000000L +/* FIFO Relative Priority */ +#define A2P_HI_PRIORITY 0x00000100L +/* Identify Interrupt Sources */ +#define ANY_S593X_INT 0x00800000L +#define READ_TC_INT 0x00080000L +#define WRITE_TC_INT 0x00040000L +#define IN_MB_INT 0x00020000L +#define MASTER_ABORT_INT 0x00100000L +#define TARGET_ABORT_INT 0x00200000L +#define BUS_MASTER_INT 0x00200000L + +/****************************************************************************/ + +struct pcilst_struct { + struct pcilst_struct *next; + int used; + struct pci_dev *pcidev; + unsigned short vendor; + unsigned short device; + unsigned int master; + unsigned char pci_bus; + unsigned char pci_slot; + unsigned char pci_func; + unsigned int io_addr[5]; + unsigned int irq; +}; + +struct pcilst_struct *amcc_devices; /* ptr to root list of all amcc devices */ + +/****************************************************************************/ + +void v_pci_card_list_init(unsigned short pci_vendor, char display); +void v_pci_card_list_cleanup(unsigned short pci_vendor); +struct pcilst_struct *ptr_find_free_pci_card_by_device(unsigned short vendor_id, + unsigned short + device_id); +int i_find_free_pci_card_by_position(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot, + struct pcilst_struct **card); +struct pcilst_struct *ptr_select_and_alloc_pci_card(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot); + +int i_pci_card_alloc(struct pcilst_struct *amcc); +int i_pci_card_free(struct pcilst_struct *amcc); +void v_pci_card_list_display(void); +int i_pci_card_data(struct pcilst_struct *amcc, + unsigned char *pci_bus, unsigned char *pci_slot, + unsigned char *pci_func, unsigned short *io_addr, + unsigned short *irq, unsigned short *master); + +/****************************************************************************/ + +/* build list of amcc cards in this system */ +void v_pci_card_list_init(unsigned short pci_vendor, char display) +{ + struct pci_dev *pcidev; + struct pcilst_struct *amcc, *last; + int i; + + amcc_devices = NULL; + last = NULL; + + pci_for_each_dev(pcidev) { + if (pcidev->vendor == pci_vendor) { + amcc = kzalloc(sizeof(*amcc), GFP_KERNEL); + if (amcc == NULL) + continue; + + amcc->pcidev = pcidev; + if (last) { + last->next = amcc; + } else { + amcc_devices = amcc; + } + last = amcc; + + amcc->vendor = pcidev->vendor; + amcc->device = pcidev->device; +#if 0 + amcc->master = pcidev->master; /* how get this information under 2.4 kernels? */ +#endif + amcc->pci_bus = pcidev->bus->number; + amcc->pci_slot = PCI_SLOT(pcidev->devfn); + amcc->pci_func = PCI_FUNC(pcidev->devfn); + for (i = 0; i < 5; i++) + amcc->io_addr[i] = + pcidev->resource[i].start & ~3UL; + amcc->irq = pcidev->irq; + } + } + + if (display) + v_pci_card_list_display(); +} + +/****************************************************************************/ +/* free up list of amcc cards in this system */ +void v_pci_card_list_cleanup(unsigned short pci_vendor) +{ + struct pcilst_struct *amcc, *next; + + for (amcc = amcc_devices; amcc; amcc = next) { + next = amcc->next; + kfree(amcc); + } + + amcc_devices = NULL; +} + +/****************************************************************************/ +/* find first unused card with this device_id */ +struct pcilst_struct *ptr_find_free_pci_card_by_device(unsigned short vendor_id, + unsigned short device_id) +{ + struct pcilst_struct *amcc, *next; + + for (amcc = amcc_devices; amcc; amcc = next) { + next = amcc->next; + if ((!amcc->used) && (amcc->device == device_id) + && (amcc->vendor == vendor_id)) + return amcc; + + } + + return NULL; +} + +/****************************************************************************/ +/* find card on requested position */ +int i_find_free_pci_card_by_position(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot, + struct pcilst_struct **card) +{ + struct pcilst_struct *amcc, *next; + + *card = NULL; + for (amcc = amcc_devices; amcc; amcc = next) { + next = amcc->next; + if ((amcc->vendor == vendor_id) && (amcc->device == device_id) + && (amcc->pci_bus == pci_bus) + && (amcc->pci_slot == pci_slot)) { + if (!(amcc->used)) { + *card = amcc; + return 0; /* ok, card is found */ + } else { + printk + (" - \nCard on requested position is used b:s %d:%d!\n", + pci_bus, pci_slot); + return 2; /* card exist but is used */ + } + } + } + + return 1; /* no card found */ +} + +/****************************************************************************/ +/* mark card as used */ +int i_pci_card_alloc(struct pcilst_struct *amcc) +{ + if (!amcc) + return -1; + + if (amcc->used) + return 1; + amcc->used = 1; + return 0; +} + +/****************************************************************************/ +/* mark card as free */ +int i_pci_card_free(struct pcilst_struct *amcc) +{ + if (!amcc) + return -1; + + if (!amcc->used) + return 1; + amcc->used = 0; + return 0; +} + +/****************************************************************************/ +/* display list of found cards */ +void v_pci_card_list_display(void) +{ + struct pcilst_struct *amcc, *next; + + printk("List of pci cards\n"); + printk("bus:slot:func vendor device master io_amcc io_daq irq used\n"); + + for (amcc = amcc_devices; amcc; amcc = next) { + next = amcc->next; + printk + ("%2d %2d %2d 0x%4x 0x%4x %3s 0x%4x 0x%4x %2d %2d\n", + amcc->pci_bus, amcc->pci_slot, amcc->pci_func, + amcc->vendor, amcc->device, amcc->master ? "yes" : "no", + amcc->io_addr[0], amcc->io_addr[2], amcc->irq, amcc->used); + + } +} + +/****************************************************************************/ +/* return all card information for driver */ +int i_pci_card_data(struct pcilst_struct *amcc, + unsigned char *pci_bus, unsigned char *pci_slot, + unsigned char *pci_func, unsigned short *io_addr, + unsigned short *irq, unsigned short *master) +{ + int i; + + if (!amcc) + return -1; + *pci_bus = amcc->pci_bus; + *pci_slot = amcc->pci_slot; + *pci_func = amcc->pci_func; + for (i = 0; i < 5; i++) + io_addr[i] = amcc->io_addr[i]; + *irq = amcc->irq; + *master = amcc->master; + return 0; +} + +/****************************************************************************/ +/* select and alloc card */ +struct pcilst_struct *ptr_select_and_alloc_pci_card(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot) +{ + struct pcilst_struct *card; + + if ((pci_bus < 1) & (pci_slot < 1)) { /* use autodetection */ + card = ptr_find_free_pci_card_by_device(vendor_id, device_id); + if (card == NULL) { + printk(" - Unused card not found in system!\n"); + return NULL; + } + } else { + switch (i_find_free_pci_card_by_position(vendor_id, device_id, + pci_bus, pci_slot, + &card)) { + case 1: + printk + (" - Card not found on requested position b:s %d:%d!\n", + pci_bus, pci_slot); + return NULL; + case 2: + printk + (" - Card on requested position is used b:s %d:%d!\n", + pci_bus, pci_slot); + return NULL; + } + } + + if (i_pci_card_alloc(card) != 0) { + printk(" - Can't allocate card!\n"); + return NULL; + } + + return card; +} + +#endif diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_APCI1710.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_APCI1710.c new file mode 100644 index 00000000..a76ed255 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_APCI1710.c @@ -0,0 +1,1266 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-1710 | Compiler : GCC | + | Module name : hwdrv_apci1710.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-1710 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ +#include "hwdrv_APCI1710.h" +#include "APCI1710_Inp_cpt.c" + +#include "APCI1710_Ssi.c" +#include "APCI1710_Tor.c" +#include "APCI1710_Ttl.c" +#include "APCI1710_Dig_io.c" +#include "APCI1710_82x54.c" +#include "APCI1710_Chrono.c" +#include "APCI1710_Pwm.c" +#include "APCI1710_INCCPT.c" + +void i_ADDI_AttachPCI1710(struct comedi_device *dev) +{ + struct comedi_subdevice *s; + int ret = 0; + int n_subdevices = 9; + + /* Update-0.7.57->0.7.68dev->n_subdevices = 9; */ + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) + return; + + /* Allocate and Initialise Timer Subdevice Structures */ + s = dev->subdevices + 0; + + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 3; + s->maxdata = 0; + s->len_chanlist = 3; + s->range_table = &range_digital; + s->insn_write = i_APCI1710_InsnWriteEnableDisableTimer; + s->insn_read = i_APCI1710_InsnReadAllTimerValue; + s->insn_config = i_APCI1710_InsnConfigInitTimer; + s->insn_bits = i_APCI1710_InsnBitsTimer; + + /* Allocate and Initialise DIO Subdevice Structures */ + s = dev->subdevices + 1; + + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = + SDF_WRITEABLE | SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 7; + s->maxdata = 1; + s->len_chanlist = 7; + s->range_table = &range_digital; + s->insn_config = i_APCI1710_InsnConfigDigitalIO; + s->insn_read = i_APCI1710_InsnReadDigitalIOChlValue; + s->insn_bits = i_APCI1710_InsnBitsDigitalIOPortOnOff; + s->insn_write = i_APCI1710_InsnWriteDigitalIOChlOnOff; + + /* Allocate and Initialise Chrono Subdevice Structures */ + s = dev->subdevices + 2; + + s->type = COMEDI_SUBD_CHRONO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 0; + s->len_chanlist = 4; + s->range_table = &range_digital; + s->insn_write = i_APCI1710_InsnWriteEnableDisableChrono; + s->insn_read = i_APCI1710_InsnReadChrono; + s->insn_config = i_APCI1710_InsnConfigInitChrono; + s->insn_bits = i_APCI1710_InsnBitsChronoDigitalIO; + + /* Allocate and Initialise PWM Subdevice Structures */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = + SDF_WRITEABLE | SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 3; + s->maxdata = 1; + s->len_chanlist = 3; + s->range_table = &range_digital; + s->io_bits = 0; /* all bits input */ + s->insn_config = i_APCI1710_InsnConfigPWM; + s->insn_read = i_APCI1710_InsnReadGetPWMStatus; + s->insn_write = i_APCI1710_InsnWritePWM; + s->insn_bits = i_APCI1710_InsnBitsReadPWMInterrupt; + + /* Allocate and Initialise TTLIO Subdevice Structures */ + s = dev->subdevices + 4; + s->type = COMEDI_SUBD_TTLIO; + s->subdev_flags = + SDF_WRITEABLE | SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 8; + s->maxdata = 1; + s->len_chanlist = 8; + s->range_table = &range_apci1710_ttl; /* to pass arguments in range */ + s->insn_config = i_APCI1710_InsnConfigInitTTLIO; + s->insn_bits = i_APCI1710_InsnBitsReadTTLIO; + s->insn_write = i_APCI1710_InsnWriteSetTTLIOChlOnOff; + s->insn_read = i_APCI1710_InsnReadTTLIOAllPortValue; + + /* Allocate and Initialise TOR Subdevice Structures */ + s = dev->subdevices + 5; + s->type = COMEDI_SUBD_TOR; + s->subdev_flags = + SDF_WRITEABLE | SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 8; + s->maxdata = 1; + s->len_chanlist = 8; + s->range_table = &range_digital; + s->io_bits = 0; /* all bits input */ + s->insn_config = i_APCI1710_InsnConfigInitTorCounter; + s->insn_read = i_APCI1710_InsnReadGetTorCounterInitialisation; + s->insn_write = i_APCI1710_InsnWriteEnableDisableTorCounter; + s->insn_bits = i_APCI1710_InsnBitsGetTorCounterProgressStatusAndValue; + + /* Allocate and Initialise SSI Subdevice Structures */ + s = dev->subdevices + 6; + s->type = COMEDI_SUBD_SSI; + s->subdev_flags = + SDF_WRITEABLE | SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 1; + s->len_chanlist = 4; + s->range_table = &range_apci1710_ssi; + s->insn_config = i_APCI1710_InsnConfigInitSSI; + s->insn_read = i_APCI1710_InsnReadSSIValue; + s->insn_bits = i_APCI1710_InsnBitsSSIDigitalIO; + + /* Allocate and Initialise PULSEENCODER Subdevice Structures */ + s = dev->subdevices + 7; + s->type = COMEDI_SUBD_PULSEENCODER; + s->subdev_flags = + SDF_WRITEABLE | SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 1; + s->len_chanlist = 4; + s->range_table = &range_digital; + s->insn_config = i_APCI1710_InsnConfigInitPulseEncoder; + s->insn_write = i_APCI1710_InsnWriteEnableDisablePulseEncoder; + s->insn_bits = i_APCI1710_InsnBitsReadWritePulseEncoder; + s->insn_read = i_APCI1710_InsnReadInterruptPulseEncoder; + + /* Allocate and Initialise INCREMENTALCOUNTER Subdevice Structures */ + s = dev->subdevices + 8; + s->type = COMEDI_SUBD_INCREMENTALCOUNTER; + s->subdev_flags = + SDF_WRITEABLE | SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 500; + s->maxdata = 1; + s->len_chanlist = 500; + s->range_table = &range_apci1710_inccpt; + s->insn_config = i_APCI1710_InsnConfigINCCPT; + s->insn_write = i_APCI1710_InsnWriteINCCPT; + s->insn_read = i_APCI1710_InsnReadINCCPT; + s->insn_bits = i_APCI1710_InsnBitsINCCPT; +} + +int i_APCI1710_Reset(struct comedi_device *dev); +void v_APCI1710_Interrupt(int irq, void *d); +/* for 1710 */ + +int i_APCI1710_Reset(struct comedi_device *dev) +{ + int ret; + unsigned int dw_Dummy; + + /*********************************/ + /* Read all module configuration */ + /*********************************/ + ret = inl(devpriv->s_BoardInfos.ui_Address + 60); + devpriv->s_BoardInfos.dw_MolduleConfiguration[0] = ret; + + ret = inl(devpriv->s_BoardInfos.ui_Address + 124); + devpriv->s_BoardInfos.dw_MolduleConfiguration[1] = ret; + + ret = inl(devpriv->s_BoardInfos.ui_Address + 188); + devpriv->s_BoardInfos.dw_MolduleConfiguration[2] = ret; + + ret = inl(devpriv->s_BoardInfos.ui_Address + 252); + devpriv->s_BoardInfos.dw_MolduleConfiguration[3] = ret; + + /* outl(0x80808082,devpriv->s_BoardInfos.ui_Address+0x60); */ + outl(0x83838383, devpriv->s_BoardInfos.ui_Address + 0x60); + + devpriv->s_BoardInfos.b_BoardVersion = 1; + + /* Enable the interrupt for the controller */ + dw_Dummy = inl(devpriv->s_BoardInfos.ui_Address + 0x38); + outl(dw_Dummy | 0x2000, devpriv->s_BoardInfos.ui_Address + 0x38); + + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function's Name : __void__ v_APCI1710_InterruptFunction | +| (unsigned char b_Interrupt, __CPPARGS) | ++----------------------------------------------------------------------------+ +| Task : APCI-1710 interrupt function | ++----------------------------------------------------------------------------+ +| Input Parameters : unsigned char b_Interrupt : Interrupt number | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0 : OK | +| -1 : Error | ++----------------------------------------------------------------------------+ +*/ + +void v_APCI1710_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned char b_ModuleCpt = 0; + unsigned char b_InterruptFlag = 0; + unsigned char b_PWMCpt = 0; + unsigned char b_TorCounterCpt = 0; + unsigned char b_PulseIncoderCpt = 0; + unsigned int ui_16BitValue; + unsigned int ul_InterruptLatchReg = 0; + unsigned int ul_LatchRegisterValue = 0; + unsigned int ul_82X54InterruptStatus; + unsigned int ul_StatusRegister; + + union str_ModuleInfo *ps_ModuleInfo; + + printk("APCI1710 Interrupt\n"); + for (b_ModuleCpt = 0; b_ModuleCpt < 4; b_ModuleCpt++, ps_ModuleInfo++) { + + /**************************/ + /* 1199/0225 to 0100/0226 */ + /**************************/ + ps_ModuleInfo = &devpriv->s_ModuleInfo[b_ModuleCpt]; + + /***********************/ + /* Test if 82X54 timer */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModuleCpt] & + 0xFFFF0000UL) == APCI1710_82X54_TIMER) { + + /* printk("TIMER Interrupt Occurred\n"); */ + ul_82X54InterruptStatus = inl(devpriv->s_BoardInfos. + ui_Address + 12 + (64 * b_ModuleCpt)); + + /***************************/ + /* Test if interrupt occur */ + /***************************/ + + if ((ul_82X54InterruptStatus & ps_ModuleInfo-> + s_82X54ModuleInfo. + b_InterruptMask) != 0) { + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters. + ui_Write]. + ul_OldInterruptMask = + (ul_82X54InterruptStatus & + ps_ModuleInfo->s_82X54ModuleInfo. + b_InterruptMask) << 4; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters. + ui_Write]. + b_OldModuleMask = 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters. + ui_Write].ul_OldCounterLatchValue = 0; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + 1) % APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + } /* if ((ul_82X54InterruptStatus & 0x7) != 0) */ + } /* 82X54 timer */ + + /***************************/ + /* Test if increm. counter */ + /***************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModuleCpt] & + 0xFFFF0000UL) == APCI1710_INCREMENTAL_COUNTER) { + + ul_InterruptLatchReg = inl(devpriv->s_BoardInfos. + ui_Address + (64 * b_ModuleCpt)); + + /*********************/ + /* Test if interrupt */ + /*********************/ + + if ((ul_InterruptLatchReg & 0x22) && (ps_ModuleInfo-> + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 & 0x80)) { + /************************************/ + /* Test if strobe latch I interrupt */ + /************************************/ + + if (ul_InterruptLatchReg & 2) { + ul_LatchRegisterValue = + inl(devpriv->s_BoardInfos. + ui_Address + 4 + + (64 * b_ModuleCpt)); + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].ul_OldInterruptMask = + 1UL; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].b_OldModuleMask = + 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write]. + ul_OldCounterLatchValue = + ul_LatchRegisterValue; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* 0899/0224 to 1199/0225 */ + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + + 1) % APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, + 0); + + } + + /*************************************/ + /* Test if strobe latch II interrupt */ + /*************************************/ + + if (ul_InterruptLatchReg & 0x20) { + + ul_LatchRegisterValue = + inl(devpriv->s_BoardInfos. + ui_Address + 8 + + (64 * b_ModuleCpt)); + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].ul_OldInterruptMask = + 2UL; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].b_OldModuleMask = + 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write]. + ul_OldCounterLatchValue = + ul_LatchRegisterValue; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* 0899/0224 to 1199/0225 */ + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + + 1) % APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, + 0); + + } + } + + ul_InterruptLatchReg = inl(devpriv->s_BoardInfos. + ui_Address + 24 + (64 * b_ModuleCpt)); + + /***************************/ + /* Test if index interrupt */ + /***************************/ + + if (ul_InterruptLatchReg & 0x8) { + ps_ModuleInfo-> + s_SiemensCounterInfo. + s_InitFlag.b_IndexInterruptOccur = 1; + + if (ps_ModuleInfo-> + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister2 & + APCI1710_INDEX_AUTO_MODE) { + + outl(ps_ModuleInfo-> + s_SiemensCounterInfo. + s_ModeRegister. + dw_ModeRegister1_2_3_4, + devpriv->s_BoardInfos. + ui_Address + 20 + + (64 * b_ModuleCpt)); + } + + /*****************************/ + /* Test if interrupt enabled */ + /*****************************/ + + if ((ps_ModuleInfo-> + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 & + APCI1710_ENABLE_INDEX_INT) == + APCI1710_ENABLE_INDEX_INT) { + devpriv->s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].ul_OldInterruptMask = + 4UL; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].b_OldModuleMask = + 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write]. + ul_OldCounterLatchValue = + ul_LatchRegisterValue; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* 0899/0224 to 1199/0225 */ + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + + 1) % APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, + 0); + + } + } + + /*****************************/ + /* Test if compare interrupt */ + /*****************************/ + + if (ul_InterruptLatchReg & 0x10) { + /*****************************/ + /* Test if interrupt enabled */ + /*****************************/ + + if ((ps_ModuleInfo-> + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister3 & + APCI1710_ENABLE_COMPARE_INT) == + APCI1710_ENABLE_COMPARE_INT) { + devpriv->s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].ul_OldInterruptMask = + 8UL; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].b_OldModuleMask = + 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write]. + ul_OldCounterLatchValue = + ul_LatchRegisterValue; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* 0899/0224 to 1199/0225 */ + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + + 1) % APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, + 0); + + } + } + + /*******************************************/ + /* Test if frequency measurement interrupt */ + /*******************************************/ + + if (ul_InterruptLatchReg & 0x20) { + /*******************/ + /* Read the status */ + /*******************/ + + ul_StatusRegister = inl(devpriv->s_BoardInfos. + ui_Address + 32 + (64 * b_ModuleCpt)); + + /******************/ + /* Read the value */ + /******************/ + + ul_LatchRegisterValue = + inl(devpriv->s_BoardInfos.ui_Address + + 28 + (64 * b_ModuleCpt)); + + switch ((ul_StatusRegister >> 1) & 3) { + case 0: + /*************************/ + /* Test the counter mode */ + /*************************/ + + if ((devpriv->s_ModuleInfo[b_ModuleCpt]. + s_SiemensCounterInfo. + s_ModeRegister. + s_ByteModeRegister. + b_ModeRegister1 & + APCI1710_16BIT_COUNTER) + == APCI1710_16BIT_COUNTER) { + /****************************************/ + /* Test if 16-bit counter 1 pulse occur */ + /****************************************/ + + if ((ul_LatchRegisterValue & + 0xFFFFU) != 0) { + ui_16BitValue = + (unsigned int) + ul_LatchRegisterValue + & 0xFFFFU; + ul_LatchRegisterValue = + (ul_LatchRegisterValue + & 0xFFFF0000UL) + | (0xFFFFU - + ui_16BitValue); + } + + /****************************************/ + /* Test if 16-bit counter 2 pulse occur */ + /****************************************/ + + if ((ul_LatchRegisterValue & + 0xFFFF0000UL) != + 0) { + ui_16BitValue = + (unsigned int) ( + (ul_LatchRegisterValue + >> 16) & + 0xFFFFU); + ul_LatchRegisterValue = + (ul_LatchRegisterValue + & 0xFFFFUL) | + ((0xFFFFU - + ui_16BitValue) + << 16); + } + } else { + if (ul_LatchRegisterValue != 0) { + ul_LatchRegisterValue = + 0xFFFFFFFFUL - + ul_LatchRegisterValue; + } + } + break; + + case 1: + /****************************************/ + /* Test if 16-bit counter 2 pulse occur */ + /****************************************/ + + if ((ul_LatchRegisterValue & + 0xFFFF0000UL) != 0) { + ui_16BitValue = + (unsigned int) ( + (ul_LatchRegisterValue + >> 16) & + 0xFFFFU); + ul_LatchRegisterValue = + (ul_LatchRegisterValue & + 0xFFFFUL) | ((0xFFFFU - + ui_16BitValue) + << 16); + } + break; + + case 2: + /****************************************/ + /* Test if 16-bit counter 1 pulse occur */ + /****************************************/ + + if ((ul_LatchRegisterValue & 0xFFFFU) != + 0) { + ui_16BitValue = + (unsigned int) + ul_LatchRegisterValue & + 0xFFFFU; + ul_LatchRegisterValue = + (ul_LatchRegisterValue & + 0xFFFF0000UL) | (0xFFFFU + - ui_16BitValue); + } + break; + } + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters. + ui_Write]. + ul_OldInterruptMask = 0x10000UL; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters. + ui_Write]. + b_OldModuleMask = 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters[devpriv-> + s_InterruptParameters. + ui_Write]. + ul_OldCounterLatchValue = + ul_LatchRegisterValue; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* 0899/0224 to 1199/0225 */ + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + 1) % APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + } + } /* Incremental counter */ + + /***************/ + /* Test if CDA */ + /***************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModuleCpt] & + 0xFFFF0000UL) == APCI1710_CDA) { + /******************************************/ + /* Test if CDA enable and functionality 0 */ + /******************************************/ + + if ((devpriv->s_ModuleInfo[b_ModuleCpt]. + s_CDAModuleInfo. + b_CDAEnable == APCI1710_ENABLE) + && (devpriv->s_ModuleInfo[b_ModuleCpt]. + s_CDAModuleInfo.b_FctSelection == 0)) { + /****************************/ + /* Get the interrupt status */ + /****************************/ + + ul_StatusRegister = inl(devpriv->s_BoardInfos. + ui_Address + 16 + (64 * b_ModuleCpt)); + /***************************/ + /* Test if interrupt occur */ + /***************************/ + + if (ul_StatusRegister & 1) { + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].ul_OldInterruptMask = + 0x80000UL; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].b_OldModuleMask = + 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write]. + ul_OldCounterLatchValue = 0; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + + 1) % APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, + 0); + + } /* if (ul_StatusRegister & 1) */ + + } + } /* CDA */ + + /***********************/ + /* Test if PWM counter */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModuleCpt] & + 0xFFFF0000UL) == APCI1710_PWM) { + for (b_PWMCpt = 0; b_PWMCpt < 2; b_PWMCpt++) { + /*************************************/ + /* Test if PWM interrupt initialised */ + /*************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModuleCpt]. + s_PWMModuleInfo. + s_PWMInfo[b_PWMCpt]. + b_InterruptEnable == APCI1710_ENABLE) { + /*****************************/ + /* Read the interrupt status */ + /*****************************/ + + ul_StatusRegister = + inl(devpriv->s_BoardInfos. + ui_Address + 16 + + (20 * b_PWMCpt) + + (64 * b_ModuleCpt)); + + /***************************/ + /* Test if interrupt occur */ + /***************************/ + + if (ul_StatusRegister & 0x1) { + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv-> + s_InterruptParameters. + ui_Write]. + ul_OldInterruptMask = + 0x4000UL << b_PWMCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv-> + s_InterruptParameters. + ui_Write]. + b_OldModuleMask = + 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + + 1) % + APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, + devpriv->tsk_Current, + 0); + + } /* if (ul_StatusRegister & 0x1) */ + } /* if (APCI1710_ENABLE) */ + } /* for (b_PWMCpt == 0; b_PWMCpt < 0; b_PWMCpt ++) */ + } /* PWM counter */ + + /***********************/ + /* Test if tor counter */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModuleCpt] & + 0xFFFF0000UL) == APCI1710_TOR_COUNTER) { + for (b_TorCounterCpt = 0; b_TorCounterCpt < 2; + b_TorCounterCpt++) { + /*************************************/ + /* Test if tor interrupt initialised */ + /*************************************/ + + if (devpriv-> + s_ModuleInfo[b_ModuleCpt]. + s_TorCounterModuleInfo. + s_TorCounterInfo[b_TorCounterCpt]. + b_InterruptEnable == APCI1710_ENABLE) { + /*****************************/ + /* Read the interrupt status */ + /*****************************/ + + ul_StatusRegister = + inl(devpriv->s_BoardInfos. + ui_Address + 12 + + (16 * b_TorCounterCpt) + + (64 * b_ModuleCpt)); + + /***************************/ + /* Test if interrupt occur */ + /***************************/ + + if (ul_StatusRegister & 0x1) { + /******************************/ + /* Read the tor counter value */ + /******************************/ + + ul_LatchRegisterValue = + inl(devpriv-> + s_BoardInfos. + ui_Address + 0 + + (16 * b_TorCounterCpt) + + (64 * b_ModuleCpt)); + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv-> + s_InterruptParameters. + ui_Write]. + ul_OldInterruptMask = + 0x1000UL << + b_TorCounterCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv-> + s_InterruptParameters. + ui_Write]. + b_OldModuleMask = + 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv-> + s_InterruptParameters. + ui_Write]. + ul_OldCounterLatchValue + = ul_LatchRegisterValue; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + + 1) % + APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, + devpriv->tsk_Current, + 0); + } /* if (ul_StatusRegister & 0x1) */ + } /* if (APCI1710_ENABLE) */ + } /* for (b_TorCounterCpt == 0; b_TorCounterCpt < 0; b_TorCounterCpt ++) */ + } /* Tor counter */ + + /***********************/ + /* Test if chronometer */ + /***********************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModuleCpt] & + 0xFFFF0000UL) == APCI1710_CHRONOMETER) { + + /* printk("APCI1710 Chrono Interrupt\n"); */ + /*****************************/ + /* Read the interrupt status */ + /*****************************/ + + ul_InterruptLatchReg = inl(devpriv->s_BoardInfos. + ui_Address + 12 + (64 * b_ModuleCpt)); + + /***************************/ + /* Test if interrupt occur */ + /***************************/ + + if ((ul_InterruptLatchReg & 0x8) == 0x8) { + /****************************/ + /* Clear the interrupt flag */ + /****************************/ + + outl(0, devpriv->s_BoardInfos. + ui_Address + 32 + (64 * b_ModuleCpt)); + + /***************************/ + /* Test if continuous mode */ + /***************************/ + + if (ps_ModuleInfo-> + s_ChronoModuleInfo. + b_CycleMode == APCI1710_ENABLE) { + /********************/ + /* Clear the status */ + /********************/ + + outl(0, devpriv->s_BoardInfos. + ui_Address + 36 + + (64 * b_ModuleCpt)); + } + + /*************************/ + /* Read the timing value */ + /*************************/ + + ul_LatchRegisterValue = + inl(devpriv->s_BoardInfos.ui_Address + + 4 + (64 * b_ModuleCpt)); + + /*****************************/ + /* Test if interrupt enabled */ + /*****************************/ + + if (ps_ModuleInfo-> + s_ChronoModuleInfo.b_InterruptMask) { + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].ul_OldInterruptMask = + 0x80; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write].b_OldModuleMask = + 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv->s_InterruptParameters. + ui_Write]. + ul_OldCounterLatchValue = + ul_LatchRegisterValue; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + + 1) % APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, + 0); + + } + } + } /* Chronometer */ + + /*************************/ + /* Test if pulse encoder */ + /*************************/ + + if ((devpriv->s_BoardInfos. + dw_MolduleConfiguration[b_ModuleCpt] & + 0xFFFF0000UL) == APCI1710_PULSE_ENCODER) { + /****************************/ + /* Read the status register */ + /****************************/ + + ul_StatusRegister = inl(devpriv->s_BoardInfos. + ui_Address + 20 + (64 * b_ModuleCpt)); + + if (ul_StatusRegister & 0xF) { + for (b_PulseIncoderCpt = 0; + b_PulseIncoderCpt < 4; + b_PulseIncoderCpt++) { + /*************************************/ + /* Test if pulse encoder initialised */ + /*************************************/ + + if ((ps_ModuleInfo-> + s_PulseEncoderModuleInfo. + s_PulseEncoderInfo + [b_PulseIncoderCpt]. + b_PulseEncoderInit == 1) + && (((ps_ModuleInfo->s_PulseEncoderModuleInfo.dw_SetRegister >> b_PulseIncoderCpt) & 1) == 1) && (((ul_StatusRegister >> (b_PulseIncoderCpt)) & 1) == 1)) { + devpriv->s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv-> + s_InterruptParameters. + ui_Write]. + ul_OldInterruptMask = + 0x100UL << + b_PulseIncoderCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv-> + s_InterruptParameters. + ui_Write]. + b_OldModuleMask = + 1 << b_ModuleCpt; + + devpriv-> + s_InterruptParameters. + s_FIFOInterruptParameters + [devpriv-> + s_InterruptParameters. + ui_Write]. + ul_OldCounterLatchValue + = ul_LatchRegisterValue; + + devpriv-> + s_InterruptParameters. + ul_InterruptOccur++; + + /****************************/ + /* 0899/0224 to 1199/0225 */ + /****************************/ + /* Increment the write FIFO */ + /****************************/ + + devpriv-> + s_InterruptParameters. + ui_Write = (devpriv-> + s_InterruptParameters. + ui_Write + + 1) % + APCI1710_SAVE_INTERRUPT; + + b_InterruptFlag = 1; + + /**********************/ + /* Call user function */ + /**********************/ + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, + devpriv->tsk_Current, + 0); + + } + } + } + } /* pulse encoder */ + + } + return; + +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_APCI1710.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_APCI1710.h new file mode 100644 index 00000000..89c99eb5 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_APCI1710.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#define COMEDI_SUBD_TTLIO 11 /* Digital Input Output But TTL */ +#define COMEDI_SUBD_PWM 12 /* Pulse width Measurement */ +#define COMEDI_SUBD_SSI 13 /* Synchronous serial interface */ +#define COMEDI_SUBD_TOR 14 /* Tor counter */ +#define COMEDI_SUBD_CHRONO 15 /* Chrono meter */ +#define COMEDI_SUBD_PULSEENCODER 16 /* Pulse Encoder INP CPT */ +#define COMEDI_SUBD_INCREMENTALCOUNTER 17 /* Incremental Counter */ + +#define APCI1710_BOARD_NAME "apci1710" +#define APCI1710_BOARD_VENDOR_ID 0x10E8 +#define APCI1710_BOARD_DEVICE_ID 0x818F +#define APCI1710_ADDRESS_RANGE 256 +#define APCI1710_CONFIG_ADDRESS_RANGE 8 +#define APCI1710_INCREMENTAL_COUNTER 0x53430000UL +#define APCI1710_SSI_COUNTER 0x53490000UL +#define APCI1710_TTL_IO 0x544C0000UL +#define APCI1710_DIGITAL_IO 0x44490000UL +#define APCI1710_82X54_TIMER 0x49430000UL +#define APCI1710_CHRONOMETER 0x43480000UL +#define APCI1710_PULSE_ENCODER 0x495A0000UL +#define APCI1710_TOR_COUNTER 0x544F0000UL +#define APCI1710_PWM 0x50570000UL +#define APCI1710_ETM 0x45540000UL +#define APCI1710_CDA 0x43440000UL +#define APCI1710_DISABLE 0 +#define APCI1710_ENABLE 1 +#define APCI1710_SYNCHRONOUS_MODE 1 +#define APCI1710_ASYNCHRONOUS_MODE 0 + +/* MODULE INFO STRUCTURE */ + +static const struct comedi_lrange range_apci1710_ttl = { 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1) + } +}; + +static const struct comedi_lrange range_apci1710_ssi = { 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1) + } +}; + +static const struct comedi_lrange range_apci1710_inccpt = { 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1) + } +}; diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.c new file mode 100644 index 00000000..5997b2f5 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.c @@ -0,0 +1,606 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-035 | Compiler : GCC | + | Module name : hwdrv_apci035.c | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-035 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ +#include "hwdrv_apci035.h" +static int i_WatchdogNbr = 0; +static int i_Temp = 0; +static int i_Flag = 1; +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI035_ConfigTimerWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Timer , Counter or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 0 Configure As Timer | +| 1 Configure As Watchdog | +| data[1] : Watchdog number +| data[2] : Time base Unit | +| data[3] : Reload Value | +| data[4] : External Trigger | +| 1:Enable +| 0:Disable +| data[5] :External Trigger Level +| 00 Trigger Disabled +| 01 Trigger Enabled (Low level) +| 10 Trigger Enabled (High Level) +| 11 Trigger Enabled (High/Low level) +| data[6] : External Gate | +| 1:Enable +| 0:Disable +| data[7] : External Gate level +| 00 Gate Disabled +| 01 Gate Enabled (Low level) +| 10 Gate Enabled (High Level) +| data[8] :Warning Relay +| 1: ENABLE +| 0: DISABLE +| data[9] :Warning Delay available +| data[10] :Warning Relay Time unit +| data[11] :Warning Relay Time Reload value +| data[12] :Reset Relay +| 1 : ENABLE +| 0 : DISABLE +| data[13] :Interrupt +| 1 : ENABLE +| 0 : DISABLE +| +| ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI035_ConfigTimerWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Status = 0; + unsigned int ui_Command = 0; + unsigned int ui_Mode = 0; + i_Temp = 0; + devpriv->tsk_Current = current; + devpriv->b_TimerSelectMode = data[0]; + i_WatchdogNbr = data[1]; + if (data[0] == 0) { + ui_Mode = 2; + } else { + ui_Mode = 0; + } +/* ui_Command = inl(devpriv->iobase+((i_WatchdogNbr-1)*32)+12); */ + ui_Command = 0; +/* ui_Command = ui_Command & 0xFFFFF9FEUL; */ + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + ui_Command = 0; + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); +/************************/ +/* Set the reload value */ +/************************/ + outl(data[3], devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 4); +/*********************/ +/* Set the time unit */ +/*********************/ + outl(data[2], devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 8); + if (data[0] == ADDIDATA_TIMER) { + + /******************************/ + /* Set the mode : */ + /* - Disable the hardware */ + /* - Disable the counter mode */ + /* - Disable the warning */ + /* - Disable the reset */ + /* - Enable the timer mode */ + /* - Set the timer mode */ + /******************************/ + + ui_Command = + (ui_Command & 0xFFF719E2UL) | ui_Mode << 13UL | 0x10UL; + + } /* if (data[0] == ADDIDATA_TIMER) */ + else { + if (data[0] == ADDIDATA_WATCHDOG) { + + /******************************/ + /* Set the mode : */ + /* - Disable the hardware */ + /* - Disable the counter mode */ + /* - Disable the warning */ + /* - Disable the reset */ + /* - Disable the timer mode */ + /******************************/ + + ui_Command = ui_Command & 0xFFF819E2UL; + + } else { + printk("\n The parameter for Timer/watchdog selection is in error\n"); + return -EINVAL; + } + } + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + ui_Command = 0; + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); +/********************************/ +/* Disable the hardware trigger */ +/********************************/ + ui_Command = ui_Command & 0xFFFFF89FUL; + if (data[4] == ADDIDATA_ENABLE) { + /**********************************/ + /* Set the hardware trigger level */ + /**********************************/ + ui_Command = ui_Command | (data[5] << 5); + } + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + ui_Command = 0; + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); +/*****************************/ +/* Disable the hardware gate */ +/*****************************/ + ui_Command = ui_Command & 0xFFFFF87FUL; + if (data[6] == ADDIDATA_ENABLE) { +/*******************************/ +/* Set the hardware gate level */ +/*******************************/ + ui_Command = ui_Command | (data[7] << 7); + } + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + ui_Command = 0; + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); +/*******************************/ +/* Disable the hardware output */ +/*******************************/ + ui_Command = ui_Command & 0xFFFFF9FBUL; +/*********************************/ +/* Set the hardware output level */ +/*********************************/ + ui_Command = ui_Command | (data[8] << 2); + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + if (data[9] == ADDIDATA_ENABLE) { + /************************/ + /* Set the reload value */ + /************************/ + outl(data[11], + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 24); + /**********************/ + /* Set the time unite */ + /**********************/ + outl(data[10], + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 28); + } + + ui_Command = 0; + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + /*******************************/ + /* Disable the hardware output */ + /*******************************/ + ui_Command = ui_Command & 0xFFFFF9F7UL; + /*********************************/ + /* Set the hardware output level */ + /*********************************/ + ui_Command = ui_Command | (data[12] << 3); + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + /*************************************/ + /** Enable the watchdog interrupt **/ + /*************************************/ + ui_Command = 0; + ui_Command = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); +/*******************************/ +/* Set the interrupt selection */ +/*******************************/ + ui_Status = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 16); + + ui_Command = (ui_Command & 0xFFFFF9FDUL) | (data[13] << 1); + outl(ui_Command, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI035_StartStopWriteTimerWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Start / Stop The Selected Timer , or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 0 - Stop Selected Timer/Watchdog | +| 1 - Start Selected Timer/Watchdog | +| 2 - Trigger Selected Timer/Watchdog | +| 3 - Stop All Timer/Watchdog | +| 4 - Start All Timer/Watchdog | +| 5 - Trigger All Timer/Watchdog | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI035_StartStopWriteTimerWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Command = 0; + int i_Count = 0; + if (data[0] == 1) { + ui_Command = + inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + /**********************/ + /* Start the hardware */ + /**********************/ + ui_Command = (ui_Command & 0xFFFFF9FFUL) | 0x1UL; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + } /* if (data[0]==1) */ + if (data[0] == 2) { + ui_Command = + inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + /***************************/ + /* Set the trigger command */ + /***************************/ + ui_Command = (ui_Command & 0xFFFFF9FFUL) | 0x200UL; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + } + + if (data[0] == 0) /* Stop The Watchdog */ + { + /* Stop The Watchdog */ + ui_Command = 0; +/* +* ui_Command = inl(devpriv->iobase+((i_WatchdogNbr-1)*32)+12); +* ui_Command = ui_Command & 0xFFFFF9FEUL; +*/ + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 12); + } /* if (data[1]==0) */ + if (data[0] == 3) /* stop all Watchdogs */ + { + ui_Command = 0; + for (i_Count = 1; i_Count <= 4; i_Count++) { + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) { + ui_Command = 0x2UL; + } else { + ui_Command = 0x10UL; + } + i_WatchdogNbr = i_Count; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + + 0); + } + + } + if (data[0] == 4) /* start all Watchdogs */ + { + ui_Command = 0; + for (i_Count = 1; i_Count <= 4; i_Count++) { + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) { + ui_Command = 0x1UL; + } else { + ui_Command = 0x8UL; + } + i_WatchdogNbr = i_Count; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + + 0); + } + } + if (data[0] == 5) /* trigger all Watchdogs */ + { + ui_Command = 0; + for (i_Count = 1; i_Count <= 4; i_Count++) { + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) { + ui_Command = 0x4UL; + } else { + ui_Command = 0x20UL; + } + + i_WatchdogNbr = i_Count; + outl(ui_Command, + devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + + 0); + } + i_Temp = 1; + } + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI035_ReadTimerWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read The Selected Timer , Counter or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : data[0] : software trigger status +| data[1] : hardware trigger status +| data[2] : Software clear status +| data[3] : Overflow status +| data[4] : Timer actual value +| + ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI035_ReadTimerWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Status = 0; /* Status register */ + i_WatchdogNbr = insn->unused[0]; + + /******************/ + /* Get the status */ + /******************/ + + ui_Status = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 16); + + /***********************************/ + /* Get the software trigger status */ + /***********************************/ + + data[0] = ((ui_Status >> 1) & 1); + /***********************************/ + /* Get the hardware trigger status */ + /***********************************/ + data[1] = ((ui_Status >> 2) & 1); + /*********************************/ + /* Get the software clear status */ + /*********************************/ + data[2] = ((ui_Status >> 3) & 1); + /***************************/ + /* Get the overflow status */ + /***************************/ + data[3] = ((ui_Status >> 0) & 1); + if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) { + data[4] = inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 0); + + } /* if (devpriv->b_TimerSelectMode==ADDIDATA_TIMER) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI035_ConfigAnalogInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Analog Input Subdevice | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s : Subdevice Pointer | +| struct comedi_insn *insn : Insn Structure Pointer | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| data[0] : Warning delay value +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI035_ConfigAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + devpriv->tsk_Current = current; + outl(0x200 | 0, devpriv->iobase + 128 + 0x4); + outl(0, devpriv->iobase + 128 + 0); +/********************************/ +/* Initialise the warning value */ +/********************************/ + outl(0x300 | 0, devpriv->iobase + 128 + 0x4); + outl((data[0] << 8), devpriv->iobase + 128 + 0); + outl(0x200000UL, devpriv->iobase + 128 + 12); + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI035_ReadAnalogInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read value of the selected channel | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | +| data[0] : Digital Value Of Input | +| | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI035_ReadAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_CommandRegister = 0; +/******************/ +/* Set the start */ +/******************/ + ui_CommandRegister = 0x80000; + /******************************/ + /* Write the command register */ + /******************************/ + outl(ui_CommandRegister, devpriv->iobase + 128 + 8); + +/***************************************/ +/* Read the digital value of the input */ +/***************************************/ + data[0] = inl(devpriv->iobase + 128 + 28); + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI035_Reset(struct comedi_device *dev) | +| | ++----------------------------------------------------------------------------+ +| Task :Resets the registers of the card | ++----------------------------------------------------------------------------+ +| Input Parameters : | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI035_Reset(struct comedi_device *dev) +{ + int i_Count = 0; + for (i_Count = 1; i_Count <= 4; i_Count++) { + i_WatchdogNbr = i_Count; + outl(0x0, devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 0); /* stop all timers */ + } + outl(0x0, devpriv->iobase + 128 + 12); /* Disable the warning delay */ + + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : static void v_APCI035_Interrupt | +| (int irq , void *d) | ++----------------------------------------------------------------------------+ +| Task : Interrupt processing Routine | ++----------------------------------------------------------------------------+ +| Input Parameters : int irq : irq number | +| void *d : void pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static void v_APCI035_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned int ui_StatusRegister1 = 0; + unsigned int ui_StatusRegister2 = 0; + unsigned int ui_ReadCommand = 0; + unsigned int ui_ChannelNumber = 0; + unsigned int ui_DigitalTemperature = 0; + if (i_Temp == 1) { + i_WatchdogNbr = i_Flag; + i_Flag = i_Flag + 1; + } + /**************************************/ + /* Read the interrupt status register of temperature Warning */ + /**************************************/ + ui_StatusRegister1 = inl(devpriv->iobase + 128 + 16); + /**************************************/ + /* Read the interrupt status register for Watchdog/timer */ + /**************************************/ + + ui_StatusRegister2 = + inl(devpriv->iobase + ((i_WatchdogNbr - 1) * 32) + 20); + + if ((((ui_StatusRegister1) & 0x8) == 0x8)) /* Test if warning relay interrupt */ + { + /**********************************/ + /* Disable the temperature warning */ + /**********************************/ + ui_ReadCommand = inl(devpriv->iobase + 128 + 12); + ui_ReadCommand = ui_ReadCommand & 0xFFDF0000UL; + outl(ui_ReadCommand, devpriv->iobase + 128 + 12); + /***************************/ + /* Read the channel number */ + /***************************/ + ui_ChannelNumber = inl(devpriv->iobase + 128 + 60); + /**************************************/ + /* Read the digital temperature value */ + /**************************************/ + ui_DigitalTemperature = inl(devpriv->iobase + 128 + 60); + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } /* if (((ui_StatusRegister1 & 0x8) == 0x8)) */ + + else { + if ((ui_StatusRegister2 & 0x1) == 0x1) { + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } + } /* else if (((ui_StatusRegister1 & 0x8) == 0x8)) */ + + return; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.h new file mode 100644 index 00000000..3c700c7b --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci035.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* Card Specific information */ +#define APCI035_BOARD_VENDOR_ID 0x15B8 +#define APCI035_ADDRESS_RANGE 255 + +/* ANALOG INPUT RANGE */ +static struct comedi_lrange range_apci035_ai = { 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1) + } +}; + +/* Timer / Watchdog Related Defines */ +#define APCI035_TCW_SYNC_ENABLEDISABLE 0 +#define APCI035_TCW_RELOAD_VALUE 4 +#define APCI035_TCW_TIMEBASE 8 +#define APCI035_TCW_PROG 12 +#define APCI035_TCW_TRIG_STATUS 16 +#define APCI035_TCW_IRQ 20 +#define APCI035_TCW_WARN_TIMEVAL 24 +#define APCI035_TCW_WARN_TIMEBASE 28 + +#define ADDIDATA_TIMER 0 +/* #define ADDIDATA_WATCHDOG 1 */ + +#define APCI035_TW1 0 +#define APCI035_TW2 32 +#define APCI035_TW3 64 +#define APCI035_TW4 96 + +#define APCI035_AI_OFFSET 0 +#define APCI035_TEMP 128 +#define APCI035_ALR_SEQ 4 +#define APCI035_START_STOP_INDEX 8 +#define APCI035_ALR_START_STOP 12 +#define APCI035_ALR_IRQ 16 +#define APCI035_EOS 20 +#define APCI035_CHAN_NO 24 +#define APCI035_CHAN_VAL 28 +#define APCI035_CONV_TIME_TIME_BASE 36 +#define APCI035_RELOAD_CONV_TIME_VAL 32 +#define APCI035_DELAY_TIME_TIME_BASE 44 +#define APCI035_RELOAD_DELAY_TIME_VAL 40 +#define ENABLE_EXT_TRIG 1 +#define ENABLE_EXT_GATE 2 +#define ENABLE_EXT_TRIG_GATE 3 + +#define ANALOG_INPUT 0 +#define TEMPERATURE 1 +#define RESISTANCE 2 + +#define ADDIDATA_GREATER_THAN_TEST 0 +#define ADDIDATA_LESS_THAN_TEST 1 + +#define APCI035_MAXVOLT 2.5 + +#define ADDIDATA_UNIPOLAR 1 +#define ADDIDATA_BIPOLAR 2 + +/* ADDIDATA Enable Disable */ +#define ADDIDATA_ENABLE 1 +#define ADDIDATA_DISABLE 0 + +/* Hardware Layer functions for Apci035 */ + +/* TIMER */ +/* timer value is passed as u seconds */ +int i_APCI035_ConfigTimerWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI035_StartStopWriteTimerWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI035_ReadTimerWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* Temperature Related Defines (Analog Input Subdevice) */ + +int i_APCI035_ConfigAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI035_ReadAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* Interrupt */ +static void v_APCI035_Interrupt(int irq, void *d); + +/* Reset functions */ +int i_APCI035_Reset(struct comedi_device *dev); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1032.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1032.c new file mode 100644 index 00000000..bab7b61a --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1032.c @@ -0,0 +1,287 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-1032 | Compiler : GCC | + | Module name : hwdrv_apci1032.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-1032 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ +#include "hwdrv_apci1032.h" +#include + +static unsigned int ui_InterruptStatus; + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1032_ConfigDigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures the digital input Subdevice | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 1 Enable Digital Input Interrupt | +| 0 Disable Digital Input Interrupt | +| data[1] : 0 ADDIDATA Interrupt OR LOGIC | +| : 1 ADDIDATA Interrupt AND LOGIC | +| data[2] : Interrupt mask for the mode 1 | +| data[3] : Interrupt mask for the mode 2 | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1032_ConfigDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_TmpValue; + + unsigned int ul_Command1 = 0; + unsigned int ul_Command2 = 0; + devpriv->tsk_Current = current; + + /*******************************/ + /* Set the digital input logic */ + /*******************************/ + if (data[0] == ADDIDATA_ENABLE) { + ul_Command1 = ul_Command1 | data[2]; + ul_Command2 = ul_Command2 | data[3]; + outl(ul_Command1, + devpriv->iobase + APCI1032_DIGITAL_IP_INTERRUPT_MODE1); + outl(ul_Command2, + devpriv->iobase + APCI1032_DIGITAL_IP_INTERRUPT_MODE2); + if (data[1] == ADDIDATA_OR) { + outl(0x4, devpriv->iobase + APCI1032_DIGITAL_IP_IRQ); + ui_TmpValue = + inl(devpriv->iobase + APCI1032_DIGITAL_IP_IRQ); + } /* if (data[1] == ADDIDATA_OR) */ + else + outl(0x6, devpriv->iobase + APCI1032_DIGITAL_IP_IRQ); + /* else if(data[1] == ADDIDATA_OR) */ + } /* if( data[0] == ADDIDATA_ENABLE) */ + else { + ul_Command1 = ul_Command1 & 0xFFFF0000; + ul_Command2 = ul_Command2 & 0xFFFF0000; + outl(ul_Command1, + devpriv->iobase + APCI1032_DIGITAL_IP_INTERRUPT_MODE1); + outl(ul_Command2, + devpriv->iobase + APCI1032_DIGITAL_IP_INTERRUPT_MODE2); + outl(0x0, devpriv->iobase + APCI1032_DIGITAL_IP_IRQ); + } /* else if ( data[0] == ADDIDATA_ENABLE) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1032_Read1DigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the digital input | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_Channel : Channel number to read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1032_Read1DigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_TmpValue = 0; + unsigned int ui_Channel; + ui_Channel = CR_CHAN(insn->chanspec); + if (ui_Channel <= 31) { + ui_TmpValue = (unsigned int) inl(devpriv->iobase + APCI1032_DIGITAL_IP); +/* +* since only 1 channel reqd to bring it to last bit it is rotated 8 +* +(chan - 1) times then ANDed with 1 for last bit. +*/ + *data = (ui_TmpValue >> ui_Channel) & 0x1; + } /* if(ui_Channel >= 0 && ui_Channel <=31) */ + else { + /* comedi_error(dev," \n chan spec wrong\n"); */ + return -EINVAL; /* "sorry channel spec wrong " */ + } /* else if(ui_Channel >= 0 && ui_Channel <=31) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1032_ReadMoreDigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the Requested digital inputs | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To be Read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1032_ReadMoreDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_PortValue = data[0]; + unsigned int ui_Mask = 0; + unsigned int ui_NoOfChannels; + + ui_NoOfChannels = CR_CHAN(insn->chanspec); + if (data[1] == 0) { + *data = (unsigned int) inl(devpriv->iobase + APCI1032_DIGITAL_IP); + switch (ui_NoOfChannels) { + case 2: + ui_Mask = 3; + *data = (*data >> (2 * ui_PortValue)) & ui_Mask; + break; + case 4: + ui_Mask = 15; + *data = (*data >> (4 * ui_PortValue)) & ui_Mask; + break; + case 8: + ui_Mask = 255; + *data = (*data >> (8 * ui_PortValue)) & ui_Mask; + break; + case 16: + ui_Mask = 65535; + *data = (*data >> (16 * ui_PortValue)) & ui_Mask; + break; + case 31: + break; + default: + /* comedi_error(dev," \nchan spec wrong\n"); */ + return -EINVAL; /* "sorry channel spec wrong " */ + break; + } /* switch(ui_NoOfChannels) */ + } /* if(data[1]==0) */ + else { + if (data[1] == 1) + *data = ui_InterruptStatus; + /* if(data[1]==1) */ + } /* else if(data[1]==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : static void v_APCI1032_Interrupt | +| (int irq , void *d) | ++----------------------------------------------------------------------------+ +| Task : Interrupt handler for the interruptible digital inputs | ++----------------------------------------------------------------------------+ +| Input Parameters : int irq : irq number | +| void *d : void pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static void v_APCI1032_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + + unsigned int ui_Temp; + /* disable the interrupt */ + ui_Temp = inl(devpriv->iobase + APCI1032_DIGITAL_IP_IRQ); + outl(ui_Temp & APCI1032_DIGITAL_IP_INTERRUPT_DISABLE, + devpriv->iobase + APCI1032_DIGITAL_IP_IRQ); + ui_InterruptStatus = + inl(devpriv->iobase + APCI1032_DIGITAL_IP_INTERRUPT_STATUS); + ui_InterruptStatus = ui_InterruptStatus & 0X0000FFFF; + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + outl(ui_Temp, devpriv->iobase + APCI1032_DIGITAL_IP_IRQ); /* enable the interrupt */ + return; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1032_Reset(struct comedi_device *dev) | | ++----------------------------------------------------------------------------+ +| Task :resets all the registers | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1032_Reset(struct comedi_device *dev) +{ + outl(0x0, devpriv->iobase + APCI1032_DIGITAL_IP_IRQ); /* disable the interrupts */ + inl(devpriv->iobase + APCI1032_DIGITAL_IP_INTERRUPT_STATUS); /* Reset the interrupt status register */ + outl(0x0, devpriv->iobase + APCI1032_DIGITAL_IP_INTERRUPT_MODE1); /* Disable the and/or interrupt */ + outl(0x0, devpriv->iobase + APCI1032_DIGITAL_IP_INTERRUPT_MODE2); + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1032.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1032.h new file mode 100644 index 00000000..7114acb4 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1032.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/********* Definitions for APCI-1032 card *****/ + +#define APCI1032_BOARD_VENDOR_ID 0x15B8 +#define APCI1032_ADDRESS_RANGE 20 +/* DIGITAL INPUT DEFINE */ + +#define APCI1032_DIGITAL_IP 0 +#define APCI1032_DIGITAL_IP_INTERRUPT_MODE1 4 +#define APCI1032_DIGITAL_IP_INTERRUPT_MODE2 8 +#define APCI1032_DIGITAL_IP_IRQ 16 + +/* Digital Input IRQ Function Selection */ +#define ADDIDATA_OR 0 +#define ADDIDATA_AND 1 + +/* Digital Input Interrupt Status */ +#define APCI1032_DIGITAL_IP_INTERRUPT_STATUS 12 + +/* Digital Input Interrupt Enable Disable. */ +#define APCI1032_DIGITAL_IP_INTERRUPT_ENABLE 0x4 +#define APCI1032_DIGITAL_IP_INTERRUPT_DISABLE 0xFFFFFFFB + +/* ADDIDATA Enable Disable */ + +#define ADDIDATA_ENABLE 1 +#define ADDIDATA_DISABLE 0 + +/* Hardware Layer functions for Apci1032 */ + +/* +* DI for di read +*/ + +int i_APCI1032_ConfigDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1032_Read1DigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI1032_ReadMoreDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* Interrupt functions..... */ + +static void v_APCI1032_Interrupt(int irq, void *d); +/* Reset */ +int i_APCI1032_Reset(struct comedi_device *dev); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c new file mode 100644 index 00000000..62f421a0 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.c @@ -0,0 +1,3061 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-1500 | Compiler : GCC | + | Module name : hwdrv_apci1500.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-1500 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ +#include "hwdrv_apci1500.h" + +static int i_TimerCounter1Init = 0; +static int i_TimerCounter2Init = 0; +static int i_WatchdogCounter3Init = 0; +static int i_Event1Status = 0, i_Event2Status = 0; +static int i_TimerCounterWatchdogInterrupt = 0; +static int i_Logic = 0, i_CounterLogic = 0; +static int i_InterruptMask = 0; +static int i_InputChannel = 0; +static int i_TimerCounter1Enabled = 0, i_TimerCounter2Enabled = 0, + i_WatchdogCounter3Enabled = 0; + +/* + +----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_ConfigDigitalInputEvent | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : An event can be generated for each port. | +| The first event is related to the first 8 channels | +| (port 1) and the second to the following 6 channels | +| (port 2). An interrupt is generated when one or both | +| events have occurred | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] :Number of the input port on | +| which the event will take place | +| (1 or 2) +| data[1] : The event logic for port 1 has | +| three possibilities | +| :0 APCI1500_AND :This logic | +| links | +| the inputs | +| with an AND | +| logic. | +| 1 APCI1500_OR :This logic | +| links | +| the inputs | +| with a | +| OR logic. | +| 2 APCI1500_OR_PRIORITY | +| :This logic | +| links | +| the inputs | +| with a | +| priority | +| OR logic. | +| Input 1 | +| has the | +| highest | +| priority | +| level and | +| input 8 | +| the smallest| +| For the second port the user has| +| 1 possibility: | +| APCI1500_OR :This logic | +| links | +| the inputs | +| with a | +| polarity | +| OR logic | +| data[2] : These 8-character word for port1| +| and 6-character word for port 2 | +| give the mask of the event. | +| Each place gives the state | +| of the input channels and can | +| have one of these six characters| +| | +| 0 : This input must be on 0 | +| 1 : This input must be on 1 | +| 2 : This input reacts to | +| a falling edge | +| 3 : This input reacts to a | +| rising edge | +| 4 : This input reacts to both edges | +| +| 5 : This input is not | +| used for event | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_ConfigDigitalInputEvent(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_PatternPolarity = 0, i_PatternTransition = 0, i_PatternMask = 0; + int i_MaxChannel = 0, i_Count = 0, i_EventMask = 0; + int i_PatternTransitionCount = 0, i_RegValue; + int i; + + /*************************************************/ + /* Selects the master interrupt control register */ + /*************************************************/ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /**********************************************/ + /* Disables the main interrupt on the board */ + /**********************************************/ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + if (data[0] == 1) { + i_MaxChannel = 8; + } /* if (data[0] == 1) */ + else { + if (data[0] == 2) { + i_MaxChannel = 6; + } /* if(data[0]==2) */ + else { + printk("\nThe specified port event does not exist\n"); + return -EINVAL; + } /* else if(data[0]==2) */ + } /* else if (data[0] == 1) */ + switch (data[1]) { + case 0: + data[1] = APCI1500_AND; + break; + case 1: + data[1] = APCI1500_OR; + break; + case 2: + data[1] = APCI1500_OR_PRIORITY; + break; + default: + printk("\nThe specified interrupt logic does not exist\n"); + return -EINVAL; + } /* switch(data[1]); */ + + i_Logic = data[1]; + for (i_Count = i_MaxChannel, i = 0; i_Count > 0; i_Count--, i++) { + i_EventMask = data[2 + i]; + switch (i_EventMask) { + case 0: + i_PatternMask = + i_PatternMask | (1 << (i_MaxChannel - i_Count)); + break; + case 1: + i_PatternMask = + i_PatternMask | (1 << (i_MaxChannel - i_Count)); + i_PatternPolarity = + i_PatternPolarity | (1 << (i_MaxChannel - + i_Count)); + break; + case 2: + i_PatternMask = + i_PatternMask | (1 << (i_MaxChannel - i_Count)); + i_PatternTransition = + i_PatternTransition | (1 << (i_MaxChannel - + i_Count)); + break; + case 3: + i_PatternMask = + i_PatternMask | (1 << (i_MaxChannel - i_Count)); + i_PatternPolarity = + i_PatternPolarity | (1 << (i_MaxChannel - + i_Count)); + i_PatternTransition = + i_PatternTransition | (1 << (i_MaxChannel - + i_Count)); + break; + case 4: + i_PatternTransition = + i_PatternTransition | (1 << (i_MaxChannel - + i_Count)); + break; + case 5: + break; + default: + printk("\nThe option indicated in the event mask does not exist\n"); + return -EINVAL; + } /* switch(i_EventMask) */ + } /* for (i_Count = i_MaxChannel; i_Count >0;i_Count --) */ + + if (data[0] == 1) { + /****************************/ + /* Test the interrupt logic */ + /****************************/ + + if (data[1] == APCI1500_AND || + data[1] == APCI1500_OR || + data[1] == APCI1500_OR_PRIORITY) { + /**************************************/ + /* Tests if a transition was declared */ + /* for a OR PRIORITY logic */ + /**************************************/ + + if (data[1] == APCI1500_OR_PRIORITY + && i_PatternTransition != 0) { + /********************************************/ + /* Transition error on an OR PRIORITY logic */ + /********************************************/ + printk("\nTransition error on an OR PRIORITY logic\n"); + return -EINVAL; + } /* if (data[1]== APCI1500_OR_PRIORITY && i_PatternTransition != 0) */ + + /*************************************/ + /* Tests if more than one transition */ + /* was declared for an AND logic */ + /*************************************/ + + if (data[1] == APCI1500_AND) { + for (i_Count = 0; i_Count < 8; i_Count++) { + i_PatternTransitionCount = + i_PatternTransitionCount + + ((i_PatternTransition >> + i_Count) & 0x1); + + } /* for (i_Count = 0; i_Count < 8; i_Count++) */ + + if (i_PatternTransitionCount > 1) { + /****************************************/ + /* Transition error on an AND logic */ + /****************************************/ + printk("\n Transition error on an AND logic\n"); + return -EINVAL; + } /* if (i_PatternTransitionCount > 1) */ + } /* if (data[1]== APCI1500_AND) */ + + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /******************/ + /* Disable Port A */ + /******************/ + outb(0xF0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /**********************************************/ + /* Selects the polarity register of port 1 */ + /**********************************************/ + outb(APCI1500_RW_PORT_A_PATTERN_POLARITY, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternPolarity, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /*********************************************/ + /* Selects the pattern mask register of */ + /* port 1 */ + /*********************************************/ + outb(APCI1500_RW_PORT_A_PATTERN_MASK, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternMask, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /********************************************/ + /* Selects the pattern transition register */ + /* of port 1 */ + /********************************************/ + outb(APCI1500_RW_PORT_A_PATTERN_TRANSITION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternTransition, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /******************************************/ + /* Selects the mode specification mask */ + /* register of port 1 */ + /******************************************/ + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /******************************************/ + /* Selects the mode specification mask */ + /* register of port 1 */ + /******************************************/ + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /**********************/ + /* Port A new mode */ + /**********************/ + + i_RegValue = (i_RegValue & 0xF9) | data[1] | 0x9; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + i_Event1Status = 1; + + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /*****************/ + /* Enable Port A */ + /*****************/ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + } /* if(data[1]==APCI1500_AND||data[1]==APCI1500_OR||data[1]==APCI1500_OR_PRIORITY) */ + else { + printk("\nThe choice for interrupt logic does not exist\n"); + return -EINVAL; + } /* else }// if(data[1]==APCI1500_AND||data[1]==APCI1500_OR||data[1]==APCI1500_OR_PRIORITY) */ + } /* if (data[0]== 1) */ + + /************************************/ + /* Test if event setting for port 2 */ + /************************************/ + + if (data[0] == 2) { + /************************/ + /* Test the event logic */ + /************************/ + + if (data[1] == APCI1500_OR) { + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /******************/ + /* Disable Port B */ + /******************/ + outb(0x74, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /****************************************/ + /* Selects the mode specification mask */ + /* register of port B */ + /****************************************/ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /******************************************/ + /* Selects the mode specification mask */ + /* register of port B */ + /******************************************/ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = i_RegValue & 0xF9; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /**********************************/ + /* Selects error channels 1 and 2 */ + /**********************************/ + + i_PatternMask = (i_PatternMask | 0xC0); + i_PatternPolarity = (i_PatternPolarity | 0xC0); + i_PatternTransition = (i_PatternTransition | 0xC0); + + /**********************************************/ + /* Selects the polarity register of port 2 */ + /**********************************************/ + outb(APCI1500_RW_PORT_B_PATTERN_POLARITY, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternPolarity, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /**********************************************/ + /* Selects the pattern transition register */ + /* of port 2 */ + /**********************************************/ + outb(APCI1500_RW_PORT_B_PATTERN_TRANSITION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternTransition, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /**********************************************/ + /* Selects the pattern Mask register */ + /* of port 2 */ + /**********************************************/ + + outb(APCI1500_RW_PORT_B_PATTERN_MASK, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_PatternMask, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /******************************************/ + /* Selects the mode specification mask */ + /* register of port 2 */ + /******************************************/ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /******************************************/ + /* Selects the mode specification mask */ + /* register of port 2 */ + /******************************************/ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = (i_RegValue & 0xF9) | 4; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + i_Event2Status = 1; + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /*****************/ + /* Enable Port B */ + /*****************/ + + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if (data[1] == APCI1500_OR) */ + else { + printk("\nThe choice for interrupt logic does not exist\n"); + return -EINVAL; + } /* elseif (data[1] == APCI1500_OR) */ + } /* if(data[0]==2) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_StartStopInputEvent | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Allows or disallows a port event | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_Channel : Channel number to read | +| unsigned int *data : Data Pointer to read status | +| data[0] :0 Start input event +| 1 Stop input event +| data[1] :No of port (1 or 2) ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_StartStopInputEvent(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_Event1InterruptStatus = 0, i_Event2InterruptStatus = + 0, i_RegValue; + switch (data[0]) { + case START: + /*************************/ + /* Tests the port number */ + /*************************/ + + if (data[1] == 1 || data[1] == 2) { + /***************************/ + /* Test if port 1 selected */ + /***************************/ + + if (data[1] == 1) { + /*****************************/ + /* Test if event initialised */ + /*****************************/ + if (i_Event1Status == 1) { + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************/ + /* Disable Port A */ + /******************/ + outb(0xF0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***************************************************/ + /* Selects the command and status register of */ + /* port 1 */ + /***************************************************/ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*************************************/ + /* Allows the pattern interrupt */ + /*************************************/ + outb(0xC0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*****************/ + /* Enable Port A */ + /*****************/ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_Event1InterruptStatus = 1; + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master interrupt control register */ + /*************************************************/ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /**********************************************/ + /* Authorizes the main interrupt on the board */ + /**********************************************/ + outb(0xD0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + } /* if(i_Event1Status==1) */ + else { + printk("\nEvent 1 not initialised\n"); + return -EINVAL; + } /* else if(i_Event1Status==1) */ + } /* if (data[1]==1) */ + if (data[1] == 2) { + + if (i_Event2Status == 1) { + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************/ + /* Disable Port B */ + /******************/ + outb(0x74, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***************************************************/ + /* Selects the command and status register of */ + /* port 2 */ + /***************************************************/ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*************************************/ + /* Allows the pattern interrupt */ + /*************************************/ + outb(0xC0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*****************/ + /* Enable Port B */ + /*****************/ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the master interrupt control register */ + /*************************************************/ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /**********************************************/ + /* Authorizes the main interrupt on the board */ + /**********************************************/ + outb(0xD0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_Event2InterruptStatus = 1; + } /* if(i_Event2Status==1) */ + else { + printk("\nEvent 2 not initialised\n"); + return -EINVAL; + } /* else if(i_Event2Status==1) */ + } /* if(data[1]==2) */ + } /* if (data[1] == 1 || data[0] == 2) */ + else { + printk("\nThe port parameter is in error\n"); + return -EINVAL; + } /* else if (data[1] == 1 || data[0] == 2) */ + + break; + + case STOP: + /*************************/ + /* Tests the port number */ + /*************************/ + + if (data[1] == 1 || data[1] == 2) { + /***************************/ + /* Test if port 1 selected */ + /***************************/ + + if (data[1] == 1) { + /*****************************/ + /* Test if event initialised */ + /*****************************/ + if (i_Event1Status == 1) { + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************/ + /* Disable Port A */ + /******************/ + outb(0xF0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***************************************************/ + /* Selects the command and status register of */ + /* port 1 */ + /***************************************************/ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*************************************/ + /* Inhibits the pattern interrupt */ + /*************************************/ + outb(0xE0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*****************/ + /* Enable Port A */ + /*****************/ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_Event1InterruptStatus = 0; + } /* if(i_Event1Status==1) */ + else { + printk("\nEvent 1 not initialised\n"); + return -EINVAL; + } /* else if(i_Event1Status==1) */ + } /* if (data[1]==1) */ + if (data[1] == 2) { + /*****************************/ + /* Test if event initialised */ + /*****************************/ + if (i_Event2Status == 1) { + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************/ + /* Disable Port B */ + /******************/ + outb(0x74, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***************************************************/ + /* Selects the command and status register of */ + /* port 2 */ + /***************************************************/ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*************************************/ + /* Inhibits the pattern interrupt */ + /*************************************/ + outb(0xE0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /*****************************************************************/ + /* Selects the APCI1500_RW_MASTER_CONFIGURATION_CONTROL register */ + /*****************************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*****************/ + /* Enable Port B */ + /*****************/ + outb(0xF4, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_Event2InterruptStatus = 0; + } /* if(i_Event2Status==1) */ + else { + printk("\nEvent 2 not initialised\n"); + return -EINVAL; + } /* else if(i_Event2Status==1) */ + } /* if(data[1]==2) */ + + } /* if (data[1] == 1 || data[1] == 2) */ + else { + printk("\nThe port parameter is in error\n"); + return -EINVAL; + } /* else if (data[1] == 1 || data[1] == 2) */ + break; + default: + printk("\nThe option of START/STOP logic does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_Initialisation | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the digital input | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_Channel : Channel number to read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_Initialisation(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_DummyRead = 0; + /******************/ + /* Software reset */ + /******************/ + i_DummyRead = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_DummyRead = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(1, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the master configuration control register */ + /*****************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0xF4, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the mode specification register of port A */ + /*****************************************************/ + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x10, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data path polarity register of port A */ + outb(APCI1500_RW_PORT_A_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* High level of port A means 1 */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data direction register of port A */ + outb(APCI1500_RW_PORT_A_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of port A: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the handshake specification register of port A */ + outb(APCI1500_RW_PORT_A_HANDSHAKE_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the register */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the mode specification register of port B */ + /*****************************************************/ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x10, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data path polarity register of port B */ + outb(APCI1500_RW_PORT_B_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* A high level of port B means 1 */ + outb(0x7F, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data direction register of port B */ + outb(APCI1500_RW_PORT_B_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of port B: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the handshake specification register of port B */ + outb(APCI1500_RW_PORT_B_HANDSHAKE_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the register */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the data path polarity register of port C */ + /*****************************************************/ + outb(APCI1500_RW_PORT_C_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* High level of port C means 1 */ + outb(0x9, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data direction register of port C */ + outb(APCI1500_RW_PORT_C_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs except channel 1 */ + outb(0x0E, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the special IO register of port C */ + outb(APCI1500_RW_PORT_C_SPECIAL_IO_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes it */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************************************************/ + /* Selects the command and status register of timer 1 */ + /******************************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of timer 1 */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************************************************/ + /* Selects the command and status register of timer 2 */ + /******************************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates Timer 2 interrupt management: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************************************************/ + /* Selects the command and status register of timer 3 */ + /******************************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of Timer 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates interrupt management of timer 3: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*************************************************/ + /* Selects the master interrupt control register */ + /*************************************************/ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes all interrupts */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_ReadMoreDigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the Requested digital inputs | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To be Read | +| unsigned int *data : Data Pointer +| data[0] : 0 Read a single channel +| 1 read a port value +| data[1] : port value ++----------------------------------------------------------------------------+ +| Output Parameters : -- data[0] :The read status value ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_ReadMoreDigitalInput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int ui_PortValue = data[1]; + unsigned int ui_Mask = 0; + unsigned int ui_Channel; + unsigned int ui_TmpValue = 0; + ui_Channel = CR_CHAN(insn->chanspec); + + switch (data[0]) { + case 0: + if (ui_Channel <= 15) { + ui_TmpValue = + (unsigned int) inw(devpriv->i_IobaseAddon + + APCI1500_DIGITAL_IP); + *data = (ui_TmpValue >> ui_Channel) & 0x1; + } /* if(ui_Channel >= 0 && ui_Channel <=15) */ + else { + printk("\nThe channel specification are in error\n"); + return -EINVAL; /* "sorry channel spec wrong " */ + } /* else if(ui_Channel >= 0 && ui_Channel <=15) */ + break; + case 1: + + *data = (unsigned int) inw(devpriv->i_IobaseAddon + + APCI1500_DIGITAL_IP); + switch (ui_Channel) { + case 2: + ui_Mask = 3; + *data = (*data >> (2 * ui_PortValue)) & ui_Mask; + break; + case 4: + ui_Mask = 15; + *data = (*data >> (4 * ui_PortValue)) & ui_Mask; + break; + case 8: + ui_Mask = 255; + *data = (*data >> (8 * ui_PortValue)) & ui_Mask; + break; + case 15: + break; + + default: + printk("\nSpecified channel cannot be read \n"); + return -EINVAL; /* "sorry channel spec wrong " */ + break; + } /* switch(ui_Channel) */ + break; + default: + printk("\nThe specified functionality does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_ConfigDigitalOutputErrorInterrupt +| (struct comedi_device *dev,struct comedi_subdevice *s struct comedi_insn +| *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : Configures the digital output memory and the digital +| output error interrupt | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| data[0] :1:Memory on | +| 0:Memory off | +| data[1] :1 Enable the voltage error interrupt +| :0 Disable the voltage error interrupt | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_ConfigDigitalOutputErrorInterrupt(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + devpriv->b_OutputMemoryStatus = data[0]; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_WriteDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Writes port value To the selected port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To Write | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_WriteDigitalOutput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + static unsigned int ui_Temp = 0; + unsigned int ui_Temp1; + + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + + if (!devpriv->b_OutputMemoryStatus) { + ui_Temp = 0; + + } /* if(!devpriv->b_OutputMemoryStatus ) */ + if (data[3] == 0) { + if (data[1] == 0) { + data[0] = (data[0] << ui_NoOfChannel) | ui_Temp; + outw(data[0], + devpriv->i_IobaseAddon + APCI1500_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = + (data[0] << (2 * + data[2])) | ui_Temp; + break; + + case 4: + data[0] = + (data[0] << (4 * + data[2])) | ui_Temp; + break; + + case 8: + data[0] = + (data[0] << (8 * + data[2])) | ui_Temp; + break; + + case 15: + data[0] = data[0] | ui_Temp; + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outw(data[0], + devpriv->i_IobaseAddon + + APCI1500_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==0) */ + else { + if (data[3] == 1) { + if (data[1] == 0) { + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + (data[0] << ui_NoOfChannel) ^ + 0xffffffff; + data[0] = data[0] & ui_Temp; + outw(data[0], + devpriv->i_IobaseAddon + + APCI1500_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = ~data[0] & 0x3; + ui_Temp1 = 3; + ui_Temp1 = + ui_Temp1 << 2 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (2 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 4: + data[0] = ~data[0] & 0xf; + ui_Temp1 = 15; + ui_Temp1 = + ui_Temp1 << 4 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (4 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 8: + data[0] = ~data[0] & 0xff; + ui_Temp1 = 255; + ui_Temp1 = + ui_Temp1 << 8 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (8 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 15: + break; + + default: + comedi_error(dev, + " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outw(data[0], + devpriv->i_IobaseAddon + + APCI1500_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==1); */ + else { + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* if else data[3]==1) */ + } /* if else data[3]==0) */ + ui_Temp = data[0]; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_ConfigCounterTimerWatchdog(comedi_device +| *dev,struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data)| +| | ++----------------------------------------------------------------------------+ +| Task : Configures The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status data[0] : 2 APCI1500_1_8_KHZ +| 1 APCI1500_3_6_KHZ | +| 0 APCI1500_115_KHZ +| data[1] : 0 Counter1/Timer1 +| 1 Counter2/Timer2 +| 2 Counter3/Watchdog +| data[2] : 0 Counter +| 1 Timer/Watchdog +| data[3] : This parameter has | +| two meanings. | +| - If the counter/timer | +| is used as a counter | +| the limit value of | +| the counter is given | +| | +| - If the counter/timer | +| is used as a timer, | +| the divider factor | +| for the output is | +| given. +| data[4] : 0 APCI1500_CONTINUOUS +| 1 APCI1500_SINGLE +| data[5] : 0 Software Trigger +| 1 Hardware Trigger +| +| data[6] :0 Software gate +| 1 Hardware gate +| data[7] :0 Interrupt Disable +| 1 Interrupt Enable ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_ConfigCounterTimerWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_TimerCounterMode, i_MasterConfiguration; + + devpriv->tsk_Current = current; + +/* Selection of the input clock */ + if (data[0] == 0 || data[0] == 1 || data[0] == 2) { + outw(data[0], devpriv->i_IobaseAddon + APCI1500_CLK_SELECT); + } /* if(data[0]==0||data[0]==1||data[0]==2) */ + else { + if (data[0] != 3) { + printk("\nThe option for input clock selection does not exist\n"); + return -EINVAL; + } /* if(data[0]!=3) */ + } /* elseif(data[0]==0||data[0]==1||data[0]==2) */ + /* Select the counter/timer */ + switch (data[1]) { + case COUNTER1: + /* selecting counter or timer */ + switch (data[2]) { + case 0: + data[2] = APCI1500_COUNTER; + break; + case 1: + data[2] = APCI1500_TIMER; + break; + default: + printk("\nThis choice is not a timer nor a counter\n"); + return -EINVAL; + } /* switch(data[2]) */ + + /* Selecting single or continuous mode */ + switch (data[4]) { + case 0: + data[4] = APCI1500_CONTINUOUS; + break; + case 1: + data[4] = APCI1500_SINGLE; + break; + default: + printk("\nThis option for single/continuous mode does not exist\n"); + return -EINVAL; + } /* switch(data[4]) */ + + i_TimerCounterMode = data[2] | data[4] | 7; + /*************************/ + /* Test the reload value */ + /*************************/ + + if ((data[3] >= 0) && (data[3] <= 65535)) { + if (data[7] == APCI1500_ENABLE + || data[7] == APCI1500_DISABLE) { + + /************************************************/ + /* Selects the mode register of timer/counter 1 */ + /************************************************/ + outb(APCI1500_RW_CPT_TMR1_MODE_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***********************/ + /* Writes the new mode */ + /***********************/ + outb(i_TimerCounterMode, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /****************************************************/ + /* Selects the constant register of timer/counter 1 */ + /****************************************************/ + + outb(APCI1500_RW_CPT_TMR1_TIME_CST_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /*************************/ + /* Writes the low value */ + /*************************/ + + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /****************************************************/ + /* Selects the constant register of timer/counter 1 */ + /****************************************************/ + + outb(APCI1500_RW_CPT_TMR1_TIME_CST_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /**************************/ + /* Writes the high value */ + /**************************/ + + data[3] = data[3] >> 8; + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /*********************************************/ + /* Selects the master configuration register */ + /*********************************************/ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /**********************/ + /* Reads the register */ + /**********************/ + + i_MasterConfiguration = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /********************************************************/ + /* Enables timer/counter 1 and triggers timer/counter 1 */ + /********************************************************/ + + i_MasterConfiguration = + i_MasterConfiguration | 0x40; + + /*********************************************/ + /* Selects the master configuration register */ + /*********************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /********************************/ + /* Writes the new configuration */ + /********************************/ + outb(i_MasterConfiguration, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /****************************************/ + /* Selects the commands register of */ + /* timer/counter 1 */ + /****************************************/ + + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /***************************/ + /* Disable timer/counter 1 */ + /***************************/ + + outb(0x0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /****************************************/ + /* Selects the commands register of */ + /* timer/counter 1 */ + /****************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /***************************/ + /* Trigger timer/counter 1 */ + /***************************/ + outb(0x2, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + else { + printk("\nError in selection of interrupt enable or disable\n"); + return -EINVAL; + } /* elseif(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + } /* if ((data[3]>= 0) && (data[3] <= 65535)) */ + else { + printk("\nError in selection of reload value\n"); + return -EINVAL; + } /* else if ((data[3]>= 0) && (data[3] <= 65535)) */ + i_TimerCounterWatchdogInterrupt = data[7]; + i_TimerCounter1Init = 1; + break; + + case COUNTER2: /* selecting counter or timer */ + switch (data[2]) { + case 0: + data[2] = APCI1500_COUNTER; + break; + case 1: + data[2] = APCI1500_TIMER; + break; + default: + printk("\nThis choice is not a timer nor a counter\n"); + return -EINVAL; + } /* switch(data[2]) */ + + /* Selecting single or continuous mode */ + switch (data[4]) { + case 0: + data[4] = APCI1500_CONTINUOUS; + break; + case 1: + data[4] = APCI1500_SINGLE; + break; + default: + printk("\nThis option for single/continuous mode does not exist\n"); + return -EINVAL; + } /* switch(data[4]) */ + + /* Selecting software or hardware trigger */ + switch (data[5]) { + case 0: + data[5] = APCI1500_SOFTWARE_TRIGGER; + break; + case 1: + data[5] = APCI1500_HARDWARE_TRIGGER; + break; + default: + printk("\nThis choice for software or hardware trigger does not exist\n"); + return -EINVAL; + } /* switch(data[5]) */ + + /* Selecting software or hardware gate */ + switch (data[6]) { + case 0: + data[6] = APCI1500_SOFTWARE_GATE; + break; + case 1: + data[6] = APCI1500_HARDWARE_GATE; + break; + default: + printk("\nThis choice for software or hardware gate does not exist\n"); + return -EINVAL; + } /* switch(data[6]) */ + + i_TimerCounterMode = data[2] | data[4] | data[5] | data[6] | 7; + + /*************************/ + /* Test the reload value */ + /*************************/ + + if ((data[3] >= 0) && (data[3] <= 65535)) { + if (data[7] == APCI1500_ENABLE + || data[7] == APCI1500_DISABLE) { + + /************************************************/ + /* Selects the mode register of timer/counter 2 */ + /************************************************/ + outb(APCI1500_RW_CPT_TMR2_MODE_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***********************/ + /* Writes the new mode */ + /***********************/ + outb(i_TimerCounterMode, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /****************************************************/ + /* Selects the constant register of timer/counter 2 */ + /****************************************************/ + + outb(APCI1500_RW_CPT_TMR2_TIME_CST_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /*************************/ + /* Writes the low value */ + /*************************/ + + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /****************************************************/ + /* Selects the constant register of timer/counter 2 */ + /****************************************************/ + + outb(APCI1500_RW_CPT_TMR2_TIME_CST_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /**************************/ + /* Writes the high value */ + /**************************/ + + data[3] = data[3] >> 8; + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /*********************************************/ + /* Selects the master configuration register */ + /*********************************************/ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /**********************/ + /* Reads the register */ + /**********************/ + + i_MasterConfiguration = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /********************************************************/ + /* Enables timer/counter 2 and triggers timer/counter 2 */ + /********************************************************/ + + i_MasterConfiguration = + i_MasterConfiguration | 0x20; + + /*********************************************/ + /* Selects the master configuration register */ + /*********************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /********************************/ + /* Writes the new configuration */ + /********************************/ + outb(i_MasterConfiguration, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /****************************************/ + /* Selects the commands register of */ + /* timer/counter 2 */ + /****************************************/ + + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /***************************/ + /* Disable timer/counter 2 */ + /***************************/ + + outb(0x0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /****************************************/ + /* Selects the commands register of */ + /* timer/counter 2 */ + /****************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /***************************/ + /* Trigger timer/counter 1 */ + /***************************/ + outb(0x2, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + else { + printk("\nError in selection of interrupt enable or disable\n"); + return -EINVAL; + } /* elseif(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + } /* if ((data[3]>= 0) && (data[3] <= 65535)) */ + else { + printk("\nError in selection of reload value\n"); + return -EINVAL; + } /* else if ((data[3]>= 0) && (data[3] <= 65535)) */ + i_TimerCounterWatchdogInterrupt = data[7]; + i_TimerCounter2Init = 1; + break; + + case COUNTER3: /* selecting counter or watchdog */ + switch (data[2]) { + case 0: + data[2] = APCI1500_COUNTER; + break; + case 1: + data[2] = APCI1500_WATCHDOG; + break; + default: + printk("\nThis choice is not a watchdog nor a counter\n"); + return -EINVAL; + } /* switch(data[2]) */ + + /* Selecting single or continuous mode */ + switch (data[4]) { + case 0: + data[4] = APCI1500_CONTINUOUS; + break; + case 1: + data[4] = APCI1500_SINGLE; + break; + default: + printk("\nThis option for single/continuous mode does not exist\n"); + return -EINVAL; + } /* switch(data[4]) */ + + /* Selecting software or hardware gate */ + switch (data[6]) { + case 0: + data[6] = APCI1500_SOFTWARE_GATE; + break; + case 1: + data[6] = APCI1500_HARDWARE_GATE; + break; + default: + printk("\nThis choice for software or hardware gate does not exist\n"); + return -EINVAL; + } /* switch(data[6]) */ + + /*****************************/ + /* Test if used for watchdog */ + /*****************************/ + + if (data[2] == APCI1500_WATCHDOG) { + /*****************************/ + /* - Enables the output line */ + /* - Enables retrigger */ + /* - Pulses output */ + /*****************************/ + i_TimerCounterMode = data[2] | data[4] | 0x54; + } /* if (data[2] == APCI1500_WATCHDOG) */ + else { + i_TimerCounterMode = data[2] | data[4] | data[6] | 7; + } /* elseif (data[2] == APCI1500_WATCHDOG) */ + /*************************/ + /* Test the reload value */ + /*************************/ + + if ((data[3] >= 0) && (data[3] <= 65535)) { + if (data[7] == APCI1500_ENABLE + || data[7] == APCI1500_DISABLE) { + + /************************************************/ + /* Selects the mode register of watchdog/counter 3 */ + /************************************************/ + outb(APCI1500_RW_CPT_TMR3_MODE_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***********************/ + /* Writes the new mode */ + /***********************/ + outb(i_TimerCounterMode, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /****************************************************/ + /* Selects the constant register of watchdog/counter 3 */ + /****************************************************/ + + outb(APCI1500_RW_CPT_TMR3_TIME_CST_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /*************************/ + /* Writes the low value */ + /*************************/ + + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /****************************************************/ + /* Selects the constant register of watchdog/counter 3 */ + /****************************************************/ + + outb(APCI1500_RW_CPT_TMR3_TIME_CST_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /**************************/ + /* Writes the high value */ + /**************************/ + + data[3] = data[3] >> 8; + outb(data[3], + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /*********************************************/ + /* Selects the master configuration register */ + /*********************************************/ + + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /**********************/ + /* Reads the register */ + /**********************/ + + i_MasterConfiguration = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /********************************************************/ + /* Enables watchdog/counter 3 and triggers watchdog/counter 3 */ + /********************************************************/ + + i_MasterConfiguration = + i_MasterConfiguration | 0x10; + + /*********************************************/ + /* Selects the master configuration register */ + /*********************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /********************************/ + /* Writes the new configuration */ + /********************************/ + outb(i_MasterConfiguration, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /********************/ + /* Test if COUNTER */ + /********************/ + if (data[2] == APCI1500_COUNTER) { + + /*************************************/ + /* Selects the command register of */ + /* watchdog/counter 3 */ + /*************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /*************************************************/ + /* Disable the watchdog/counter 3 and starts it */ + /*************************************************/ + outb(0x0, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /*************************************/ + /* Selects the command register of */ + /* watchdog/counter 3 */ + /*************************************/ + + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /*************************************************/ + /* Trigger the watchdog/counter 3 and starts it */ + /*************************************************/ + outb(0x2, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + } /* elseif(data[2]==APCI1500_COUNTER) */ + + } /* if(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + else { + printk("\nError in selection of interrupt enable or disable\n"); + return -EINVAL; + } /* elseif(data[7]== APCI1500_ENABLE ||data[7]== APCI1500_DISABLE) */ + } /* if ((data[3]>= 0) && (data[3] <= 65535)) */ + else { + printk("\nError in selection of reload value\n"); + return -EINVAL; + } /* else if ((data[3]>= 0) && (data[3] <= 65535)) */ + i_TimerCounterWatchdogInterrupt = data[7]; + i_WatchdogCounter3Init = 1; + break; + + default: + printk("\nThe specified counter\timer option does not exist\n"); + } /* switch(data[1]) */ + i_CounterLogic = data[2]; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_StartStopTriggerTimerCounterWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, +| struct comedi_insn *insn,unsigned int *data); | ++----------------------------------------------------------------------------+ +| Task : Start / Stop or trigger the timer counter or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | +| data[0] : 0 Counter1/Timer1 +| 1 Counter2/Timer2 +| 2 Counter3/Watchdog +| data[1] : 0 start +| 1 stop +| 2 Trigger +| data[2] : 0 Counter +| 1 Timer/Watchdog ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_StartStopTriggerTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_CommandAndStatusValue; + + switch (data[0]) { + case COUNTER1: + switch (data[1]) { + case START: + if (i_TimerCounter1Init == 1) { + if (i_TimerCounterWatchdogInterrupt == 1) { + i_CommandAndStatusValue = 0xC4; /* Enable the interrupt */ + } /* if(i_TimerCounterWatchdogInterrupt==1) */ + else { + i_CommandAndStatusValue = 0xE4; /* disable the interrupt */ + } /* elseif(i_TimerCounterWatchdogInterrupt==1) */ + /**************************/ + /* Starts timer/counter 1 */ + /**************************/ + i_TimerCounter1Enabled = 1; + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter1Init==1) */ + else { + printk("\nCounter/Timer1 not configured\n"); + return -EINVAL; + } + break; + + case STOP: + + /**************************/ + /* Stop timer/counter 1 */ + /**************************/ + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x00, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_TimerCounter1Enabled = 0; + break; + + case TRIGGER: + if (i_TimerCounter1Init == 1) { + if (i_TimerCounter1Enabled == 1) { + /************************/ + /* Set Trigger and gate */ + /************************/ + + i_CommandAndStatusValue = 0x6; + } /* if( i_TimerCounter1Enabled==1) */ + else { + /***************/ + /* Set Trigger */ + /***************/ + + i_CommandAndStatusValue = 0x2; + } /* elseif(i_TimerCounter1Enabled==1) */ + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter1Init==1) */ + else { + printk("\nCounter/Timer1 not configured\n"); + return -EINVAL; + } + break; + + default: + printk("\nThe specified option for start/stop/trigger does not exist\n"); + return -EINVAL; + } /* switch(data[1]) */ + break; + + case COUNTER2: + switch (data[1]) { + case START: + if (i_TimerCounter2Init == 1) { + if (i_TimerCounterWatchdogInterrupt == 1) { + i_CommandAndStatusValue = 0xC4; /* Enable the interrupt */ + } /* if(i_TimerCounterWatchdogInterrupt==1) */ + else { + i_CommandAndStatusValue = 0xE4; /* disable the interrupt */ + } /* elseif(i_TimerCounterWatchdogInterrupt==1) */ + /**************************/ + /* Starts timer/counter 2 */ + /**************************/ + i_TimerCounter2Enabled = 1; + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter2Init==1) */ + else { + printk("\nCounter/Timer2 not configured\n"); + return -EINVAL; + } + break; + + case STOP: + + /**************************/ + /* Stop timer/counter 2 */ + /**************************/ + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x00, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_TimerCounter2Enabled = 0; + break; + case TRIGGER: + if (i_TimerCounter2Init == 1) { + if (i_TimerCounter2Enabled == 1) { + /************************/ + /* Set Trigger and gate */ + /************************/ + + i_CommandAndStatusValue = 0x6; + } /* if( i_TimerCounter2Enabled==1) */ + else { + /***************/ + /* Set Trigger */ + /***************/ + + i_CommandAndStatusValue = 0x2; + } /* elseif(i_TimerCounter2Enabled==1) */ + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter2Init==1) */ + else { + printk("\nCounter/Timer2 not configured\n"); + return -EINVAL; + } + break; + default: + printk("\nThe specified option for start/stop/trigger does not exist\n"); + return -EINVAL; + } /* switch(data[1]) */ + break; + case COUNTER3: + switch (data[1]) { + case START: + if (i_WatchdogCounter3Init == 1) { + + if (i_TimerCounterWatchdogInterrupt == 1) { + i_CommandAndStatusValue = 0xC4; /* Enable the interrupt */ + } /* if(i_TimerCounterWatchdogInterrupt==1) */ + else { + i_CommandAndStatusValue = 0xE4; /* disable the interrupt */ + } /* elseif(i_TimerCounterWatchdogInterrupt==1) */ + /**************************/ + /* Starts Watchdog/counter 3 */ + /**************************/ + i_WatchdogCounter3Enabled = 1; + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + } /* if( i_WatchdogCounter3init==1) */ + else { + printk("\nWatchdog/Counter3 not configured\n"); + return -EINVAL; + } + break; + + case STOP: + + /**************************/ + /* Stop Watchdog/counter 3 */ + /**************************/ + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x00, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_WatchdogCounter3Enabled = 0; + break; + + case TRIGGER: + switch (data[2]) { + case 0: /* triggering counter 3 */ + if (i_WatchdogCounter3Init == 1) { + if (i_WatchdogCounter3Enabled == 1) { + /************************/ + /* Set Trigger and gate */ + /************************/ + + i_CommandAndStatusValue = 0x6; + } /* if( i_WatchdogCounter3Enabled==1) */ + else { + /***************/ + /* Set Trigger */ + /***************/ + + i_CommandAndStatusValue = 0x2; + } /* elseif(i_WatchdogCounter3Enabled==1) */ + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_WatchdogCounter3Init==1) */ + else { + printk("\nCounter3 not configured\n"); + return -EINVAL; + } + break; + case 1: + /* triggering Watchdog 3 */ + if (i_WatchdogCounter3Init == 1) { + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x6, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_WatchdogCounter3Init==1) */ + else { + printk("\nWatchdog 3 not configured\n"); + return -EINVAL; + } + break; + default: + printk("\nWrong choice of watchdog/counter3\n"); + return -EINVAL; + } /* switch(data[2]) */ + break; + default: + printk("\nThe specified option for start/stop/trigger does not exist\n"); + return -EINVAL; + } /* switch(data[1]) */ + break; + default: + printk("\nThe specified choice for counter/watchdog/timer does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_ReadCounterTimerWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, +| unsigned int *data); | ++----------------------------------------------------------------------------+ +| Task : Read The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | +| data[0] : 0 Counter1/Timer1 +| 1 Counter2/Timer2 +| 2 Counter3/Watchdog +| ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_ReadCounterTimerWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_CommandAndStatusValue; + switch (data[0]) { + case COUNTER1: + /* Read counter/timer1 */ + if (i_TimerCounter1Init == 1) { + if (i_TimerCounter1Enabled == 1) { + /************************/ + /* Set RCC and gate */ + /************************/ + + i_CommandAndStatusValue = 0xC; + } /* if( i_TimerCounter1Init==1) */ + else { + /***************/ + /* Set RCC */ + /***************/ + + i_CommandAndStatusValue = 0x8; + } /* elseif(i_TimerCounter1Init==1) */ + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /***************************************/ + /* Selects the counter register (high) */ + /***************************************/ + outb(APCI1500_R_CPT_TMR1_VALUE_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = data[0] << 8; + data[0] = data[0] & 0xff00; + outb(APCI1500_R_CPT_TMR1_VALUE_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + data[0] | inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter1Init==1) */ + else { + printk("\nTimer/Counter1 not configured\n"); + return -EINVAL; + } /* elseif( i_TimerCounter1Init==1) */ + break; + case COUNTER2: + /* Read counter/timer2 */ + if (i_TimerCounter2Init == 1) { + if (i_TimerCounter2Enabled == 1) { + /************************/ + /* Set RCC and gate */ + /************************/ + + i_CommandAndStatusValue = 0xC; + } /* if( i_TimerCounter2Init==1) */ + else { + /***************/ + /* Set RCC */ + /***************/ + + i_CommandAndStatusValue = 0x8; + } /* elseif(i_TimerCounter2Init==1) */ + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /***************************************/ + /* Selects the counter register (high) */ + /***************************************/ + outb(APCI1500_R_CPT_TMR2_VALUE_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = data[0] << 8; + data[0] = data[0] & 0xff00; + outb(APCI1500_R_CPT_TMR2_VALUE_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + data[0] | inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_TimerCounter2Init==1) */ + else { + printk("\nTimer/Counter2 not configured\n"); + return -EINVAL; + } /* elseif( i_TimerCounter2Init==1) */ + break; + case COUNTER3: + /* Read counter/watchdog2 */ + if (i_WatchdogCounter3Init == 1) { + if (i_WatchdogCounter3Enabled == 1) { + /************************/ + /* Set RCC and gate */ + /************************/ + + i_CommandAndStatusValue = 0xC; + } /* if( i_TimerCounter2Init==1) */ + else { + /***************/ + /* Set RCC */ + /***************/ + + i_CommandAndStatusValue = 0x8; + } /* elseif(i_WatchdogCounter3Init==1) */ + + /********************************************/ + /* Selects the commands and status register */ + /********************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_CommandAndStatusValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /***************************************/ + /* Selects the counter register (high) */ + /***************************************/ + outb(APCI1500_R_CPT_TMR3_VALUE_HIGH, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = data[0] << 8; + data[0] = data[0] & 0xff00; + outb(APCI1500_R_CPT_TMR3_VALUE_LOW, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + data[0] = + data[0] | inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + } /* if( i_WatchdogCounter3Init==1) */ + else { + printk("\nWatchdogCounter3 not configured\n"); + return -EINVAL; + } /* elseif( i_WatchdogCounter3Init==1) */ + break; + default: + printk("\nThe choice of timer/counter/watchdog does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_ReadInterruptMask | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, +| unsigned int *data); | ++----------------------------------------------------------------------------+ +| Task : Read the interrupt mask | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | + + ++----------------------------------------------------------------------------+ +| Output Parameters : -- data[0]:The interrupt mask value data[1]:Channel no ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_ReadInterruptMask(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[0] = i_InterruptMask; + data[1] = i_InputChannel; + i_InterruptMask = 0; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_ConfigureInterrupt | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, +| unsigned int *data); | ++----------------------------------------------------------------------------+ +| Task : Configures the interrupt registers | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer | +| + ++----------------------------------------------------------------------------+ +| Output Parameters : -- ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_ConfigureInterrupt(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int ui_Status; + int i_RegValue; + int i_Constant; + devpriv->tsk_Current = current; + outl(0x0, devpriv->i_IobaseAmcc + 0x38); + if (data[0] == 1) { + i_Constant = 0xC0; + } /* if(data[0]==1) */ + else { + if (data[0] == 0) { + i_Constant = 0x00; + } /* if{data[0]==0) */ + else { + printk("\nThe parameter passed to driver is in error for enabling the voltage interrupt\n"); + return -EINVAL; + } /* else if(data[0]==0) */ + } /* elseif(data[0]==1) */ + + /*****************************************************/ + /* Selects the mode specification register of port B */ + /*****************************************************/ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*********************************************/ + /* Writes the new configuration (APCI1500_OR) */ + /*********************************************/ + i_RegValue = (i_RegValue & 0xF9) | APCI1500_OR; + + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*****************************************************/ + /* Selects the command and status register of port B */ + /*****************************************************/ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*****************************************/ + /* Authorises the interrupt on the board */ + /*****************************************/ + outb(0xC0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /***************************************************/ + /* Selects the pattern polarity register of port B */ + /***************************************************/ + outb(APCI1500_RW_PORT_B_PATTERN_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_Constant, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*****************************************************/ + /* Selects the pattern transition register of port B */ + /*****************************************************/ + outb(APCI1500_RW_PORT_B_PATTERN_TRANSITION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_Constant, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************************/ + /* Selects the pattern mask register of port B */ + /***********************************************/ + outb(APCI1500_RW_PORT_B_PATTERN_MASK, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(i_Constant, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the command and status register of port A */ + /*****************************************************/ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of port A */ + /***********************************/ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*****************************************************/ + /* Selects the command and status register of port B */ + /*****************************************************/ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of port B */ + /***********************************/ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the command and status register of timer 1 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of timer 1 */ + /***********************************/ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the command and status register of timer 2 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of timer 2 */ + /***********************************/ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the command and status register of timer 3 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of timer 3 */ + /***********************************/ + + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*************************************************/ + /* Selects the master interrupt control register */ + /*************************************************/ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /**********************************************/ + /* Authorizes the main interrupt on the board */ + /**********************************************/ + outb(0xD0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /***************************/ + /* Enables the PCI interrupt */ + /*****************************/ + outl(0x3000, devpriv->i_IobaseAmcc + 0x38); + ui_Status = inl(devpriv->i_IobaseAmcc + 0x10); + ui_Status = inl(devpriv->i_IobaseAmcc + 0x38); + outl(0x23000, devpriv->i_IobaseAmcc + 0x38); + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : static void v_APCI1500_Interrupt | +| (int irq , void *d) | ++----------------------------------------------------------------------------+ +| Task : Interrupt handler | ++----------------------------------------------------------------------------+ +| Input Parameters : int irq : irq number | +| void *d : void pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static void v_APCI1500_Interrupt(int irq, void *d) +{ + + struct comedi_device *dev = d; + unsigned int ui_InterruptStatus = 0; + int i_RegValue = 0; + i_InterruptMask = 0; + + /***********************************/ + /* Read the board interrupt status */ + /***********************************/ + ui_InterruptStatus = inl(devpriv->i_IobaseAmcc + 0x38); + + /***************************************/ + /* Test if board generated a interrupt */ + /***************************************/ + if ((ui_InterruptStatus & 0x800000) == 0x800000) { + /************************/ + /* Disable all Interrupt */ + /************************/ + /*************************************************/ + /* Selects the master interrupt control register */ + /*************************************************/ + /* outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL,devpriv->iobase+APCI1500_Z8536_CONTROL_REGISTER); */ + /**********************************************/ + /* Disables the main interrupt on the board */ + /**********************************************/ + /* outb(0x00,devpriv->iobase+APCI1500_Z8536_CONTROL_REGISTER); */ + + /*****************************************************/ + /* Selects the command and status register of port A */ + /*****************************************************/ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /*****************************************************/ + /* Selects the command and status register of port A */ + /*****************************************************/ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of port A */ + /***********************************/ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_InterruptMask = i_InterruptMask | 1; + if (i_Logic == APCI1500_OR_PRIORITY) { + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + /***************************************************/ + /* Selects the interrupt vector register of port A */ + /***************************************************/ + outb(APCI1500_RW_PORT_A_INTERRUPT_CONTROL, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + + i_InputChannel = 1 + (i_RegValue >> 1); + + } /* if(i_Logic==APCI1500_OR_PRIORITY) */ + else { + i_InputChannel = 0; + } /* elseif(i_Logic==APCI1500_OR_PRIORITY) */ + } /* if ((i_RegValue & 0x60) == 0x60) */ + + /*****************************************************/ + /* Selects the command and status register of port B */ + /*****************************************************/ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /*****************************************************/ + /* Selects the command and status register of port B */ + /*****************************************************/ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of port B */ + /***********************************/ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + printk("\n\n\n"); + /****************/ + /* Reads port B */ + /****************/ + i_RegValue = + inb((unsigned int) devpriv->iobase + + APCI1500_Z8536_PORT_B); + + i_RegValue = i_RegValue & 0xC0; + /**************************************/ + /* Tests if this is an external error */ + /**************************************/ + + if (i_RegValue) { + /* Disable the interrupt */ + /*****************************************************/ + /* Selects the command and status register of port B */ + /*****************************************************/ + outl(0x0, devpriv->i_IobaseAmcc + 0x38); + + if (i_RegValue & 0x80) { + i_InterruptMask = + i_InterruptMask | 0x40; + } /* if (i_RegValue & 0x80) */ + + if (i_RegValue & 0x40) { + i_InterruptMask = + i_InterruptMask | 0x80; + } /* if (i_RegValue & 0x40) */ + } /* if (i_RegValue) */ + else { + i_InterruptMask = i_InterruptMask | 2; + } /* if (i_RegValue) */ + } /* if ((i_RegValue & 0x60) == 0x60) */ + + /*****************************************************/ + /* Selects the command and status register of timer 1 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /*****************************************************/ + /* Selects the command and status register of timer 1 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of timer 1 */ + /***********************************/ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_InterruptMask = i_InterruptMask | 4; + } /* if ((i_RegValue & 0x60) == 0x60) */ + /*****************************************************/ + /* Selects the command and status register of timer 2 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /*****************************************************/ + /* Selects the command and status register of timer 2 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of timer 2 */ + /***********************************/ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + i_InterruptMask = i_InterruptMask | 8; + } /* if ((i_RegValue & 0x60) == 0x60) */ + + /*****************************************************/ + /* Selects the command and status register of timer 3 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_RegValue = + inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + if ((i_RegValue & 0x60) == 0x60) { + /*****************************************************/ + /* Selects the command and status register of timer 3 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + /***********************************/ + /* Deletes the interrupt of timer 3 */ + /***********************************/ + i_RegValue = (i_RegValue & 0x0F) | 0x20; + outb(i_RegValue, + devpriv->iobase + + APCI1500_Z8536_CONTROL_REGISTER); + if (i_CounterLogic == APCI1500_COUNTER) { + i_InterruptMask = i_InterruptMask | 0x10; + } /* if(i_CounterLogic==APCI1500_COUNTER) */ + else { + i_InterruptMask = i_InterruptMask | 0x20; + } + } /* if ((i_RegValue & 0x60) == 0x60) */ + + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + /***********************/ + /* Enable all Interrupts */ + /***********************/ + + /*************************************************/ + /* Selects the master interrupt control register */ + /*************************************************/ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /**********************************************/ + /* Authorizes the main interrupt on the board */ + /**********************************************/ + outb(0xD0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + } /* if ((ui_InterruptStatus & 0x800000) == 0x800000) */ + else { + printk("\nInterrupt from unknown source\n"); + + } /* else if ((ui_InterruptStatus & 0x800000) == 0x800000) */ + return; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1500_Reset(struct comedi_device *dev) | | ++----------------------------------------------------------------------------+ +| Task :resets all the registers | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI1500_Reset(struct comedi_device *dev) +{ + int i_DummyRead = 0; + i_TimerCounter1Init = 0; + i_TimerCounter2Init = 0; + i_WatchdogCounter3Init = 0; + i_Event1Status = 0; + i_Event2Status = 0; + i_TimerCounterWatchdogInterrupt = 0; + i_Logic = 0; + i_CounterLogic = 0; + i_InterruptMask = 0; + i_InputChannel = 0; + i_TimerCounter1Enabled = 0; + i_TimerCounter2Enabled = 0; + i_WatchdogCounter3Enabled = 0; + + /******************/ + /* Software reset */ + /******************/ + i_DummyRead = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + i_DummyRead = inb(devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(1, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the master configuration control register */ + /*****************************************************/ + outb(APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0xF4, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the mode specification register of port A */ + /*****************************************************/ + outb(APCI1500_RW_PORT_A_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x10, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data path polarity register of port A */ + outb(APCI1500_RW_PORT_A_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* High level of port A means 1 */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /* Selects the data direction register of port A */ + outb(APCI1500_RW_PORT_A_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port A */ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of port A: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the handshake specification register of port A */ + outb(APCI1500_RW_PORT_A_HANDSHAKE_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the register */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the mode specification register of port B */ + /*****************************************************/ + outb(APCI1500_RW_PORT_B_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + outb(0x10, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data path polarity register of port B */ + outb(APCI1500_RW_PORT_B_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* A high level of port B means 1 */ + outb(0x7F, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data direction register of port B */ + outb(APCI1500_RW_PORT_B_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs */ + outb(0xFF, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of port B */ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of port B: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the handshake specification register of port B */ + outb(APCI1500_RW_PORT_B_HANDSHAKE_SPECIFICATION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes the register */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + + /*****************************************************/ + /* Selects the data path polarity register of port C */ + /*****************************************************/ + outb(APCI1500_RW_PORT_C_DATA_PCITCH_POLARITY, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* High level of port C means 1 */ + outb(0x9, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the data direction register of port C */ + outb(APCI1500_RW_PORT_C_DATA_DIRECTION, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* All bits used as inputs except channel 1 */ + outb(0x0E, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the special IO register of port C */ + outb(APCI1500_RW_PORT_C_SPECIAL_IO_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes it */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************************************************/ + /* Selects the command and status register of timer 1 */ + /******************************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 1 */ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates the interrupt management of timer 1 */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************************************************/ + /* Selects the command and status register of timer 2 */ + /******************************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of timer 2 */ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates Timer 2 interrupt management: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /******************************************************/ + /* Selects the command and status register of timer 3 */ + /******************************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes IP and IUS */ + outb(0x20, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Selects the command and status register of Timer 3 */ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deactivates interrupt management of timer 3: */ + outb(0xE0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*************************************************/ + /* Selects the master interrupt control register */ + /*************************************************/ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* Deletes all interrupts */ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /* reset all the digital outputs */ + outw(0x0, devpriv->i_IobaseAddon + APCI1500_DIGITAL_OP); +/*******************************/ +/* Disable the board interrupt */ +/*******************************/ + /*************************************************/ + /* Selects the master interrupt control register */ + /*************************************************/ + outb(APCI1500_RW_MASTER_INTERRUPT_CONTROL, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/****************************/ +/* Deactivates all interrupts */ +/******************************/ + outb(0, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + /*****************************************************/ + /* Selects the command and status register of port A */ + /*****************************************************/ + outb(APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/****************************/ +/* Deactivates all interrupts */ +/******************************/ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/*****************************************************/ + /* Selects the command and status register of port B */ + /*****************************************************/ + outb(APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/****************************/ +/* Deactivates all interrupts */ +/******************************/ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/*****************************************************/ + /* Selects the command and status register of timer 1 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR1_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/****************************/ +/* Deactivates all interrupts */ +/******************************/ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/*****************************************************/ + /* Selects the command and status register of timer 2 */ + /*****************************************************/ + outb(APCI1500_RW_CPT_TMR2_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/****************************/ +/* Deactivates all interrupts */ +/******************************/ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/*****************************************************/ +/* Selects the command and status register of timer 3*/ +/*****************************************************/ + outb(APCI1500_RW_CPT_TMR3_CMD_STATUS, + devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); +/****************************/ +/* Deactivates all interrupts */ +/******************************/ + outb(0x00, devpriv->iobase + APCI1500_Z8536_CONTROL_REGISTER); + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.h new file mode 100644 index 00000000..647f9ebf --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1500.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/********* Definitions for APCI-1500 card *****/ + +/* Card Specific information */ +#define APCI1500_BOARD_VENDOR_ID 0x10e8 +#define APCI1500_ADDRESS_RANGE 4 + +/* DIGITAL INPUT-OUTPUT DEFINE */ + +#define APCI1500_DIGITAL_OP 2 +#define APCI1500_DIGITAL_IP 0 +#define APCI1500_AND 2 +#define APCI1500_OR 4 +#define APCI1500_OR_PRIORITY 6 +#define APCI1500_CLK_SELECT 0 +#define COUNTER1 0 +#define COUNTER2 1 +#define COUNTER3 2 +#define APCI1500_COUNTER 0x20 +#define APCI1500_TIMER 0 +#define APCI1500_WATCHDOG 0 +#define APCI1500_SINGLE 0 +#define APCI1500_CONTINUOUS 0x80 +#define APCI1500_DISABLE 0 +#define APCI1500_ENABLE 1 +#define APCI1500_SOFTWARE_TRIGGER 0x4 +#define APCI1500_HARDWARE_TRIGGER 0x10 +#define APCI1500_SOFTWARE_GATE 0 +#define APCI1500_HARDWARE_GATE 0x8 +#define START 0 +#define STOP 1 +#define TRIGGER 2 + +/* + * Zillog I/O enumeration + */ +enum { + APCI1500_Z8536_PORT_C, + APCI1500_Z8536_PORT_B, + APCI1500_Z8536_PORT_A, + APCI1500_Z8536_CONTROL_REGISTER +}; + +/* + * Z8536 CIO Internal Address + */ +enum { + APCI1500_RW_MASTER_INTERRUPT_CONTROL, + APCI1500_RW_MASTER_CONFIGURATION_CONTROL, + APCI1500_RW_PORT_A_INTERRUPT_CONTROL, + APCI1500_RW_PORT_B_INTERRUPT_CONTROL, + APCI1500_RW_TIMER_COUNTER_INTERRUPT_VECTOR, + APCI1500_RW_PORT_C_DATA_PCITCH_POLARITY, + APCI1500_RW_PORT_C_DATA_DIRECTION, + APCI1500_RW_PORT_C_SPECIAL_IO_CONTROL, + + APCI1500_RW_PORT_A_COMMAND_AND_STATUS, + APCI1500_RW_PORT_B_COMMAND_AND_STATUS, + APCI1500_RW_CPT_TMR1_CMD_STATUS, + APCI1500_RW_CPT_TMR2_CMD_STATUS, + APCI1500_RW_CPT_TMR3_CMD_STATUS, + APCI1500_RW_PORT_A_DATA, + APCI1500_RW_PORT_B_DATA, + APCI1500_RW_PORT_C_DATA, + + APCI1500_R_CPT_TMR1_VALUE_HIGH, + APCI1500_R_CPT_TMR1_VALUE_LOW, + APCI1500_R_CPT_TMR2_VALUE_HIGH, + APCI1500_R_CPT_TMR2_VALUE_LOW, + APCI1500_R_CPT_TMR3_VALUE_HIGH, + APCI1500_R_CPT_TMR3_VALUE_LOW, + APCI1500_RW_CPT_TMR1_TIME_CST_HIGH, + APCI1500_RW_CPT_TMR1_TIME_CST_LOW, + APCI1500_RW_CPT_TMR2_TIME_CST_HIGH, + APCI1500_RW_CPT_TMR2_TIME_CST_LOW, + APCI1500_RW_CPT_TMR3_TIME_CST_HIGH, + APCI1500_RW_CPT_TMR3_TIME_CST_LOW, + APCI1500_RW_CPT_TMR1_MODE_SPECIFICATION, + APCI1500_RW_CPT_TMR2_MODE_SPECIFICATION, + APCI1500_RW_CPT_TMR3_MODE_SPECIFICATION, + APCI1500_R_CURRENT_VECTOR, + + APCI1500_RW_PORT_A_SPECIFICATION, + APCI1500_RW_PORT_A_HANDSHAKE_SPECIFICATION, + APCI1500_RW_PORT_A_DATA_PCITCH_POLARITY, + APCI1500_RW_PORT_A_DATA_DIRECTION, + APCI1500_RW_PORT_A_SPECIAL_IO_CONTROL, + APCI1500_RW_PORT_A_PATTERN_POLARITY, + APCI1500_RW_PORT_A_PATTERN_TRANSITION, + APCI1500_RW_PORT_A_PATTERN_MASK, + + APCI1500_RW_PORT_B_SPECIFICATION, + APCI1500_RW_PORT_B_HANDSHAKE_SPECIFICATION, + APCI1500_RW_PORT_B_DATA_PCITCH_POLARITY, + APCI1500_RW_PORT_B_DATA_DIRECTION, + APCI1500_RW_PORT_B_SPECIAL_IO_CONTROL, + APCI1500_RW_PORT_B_PATTERN_POLARITY, + APCI1500_RW_PORT_B_PATTERN_TRANSITION, + APCI1500_RW_PORT_B_PATTERN_MASK +}; + + /*----------DIGITAL INPUT----------------*/ +static int i_APCI1500_Initialisation(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int i_APCI1500_ConfigDigitalInputEvent(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int i_APCI1500_StartStopInputEvent(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int i_APCI1500_ReadMoreDigitalInput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/*---------- DIGITAL OUTPUT------------*/ +static int i_APCI1500_ConfigDigitalOutputErrorInterrupt(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int i_APCI1500_WriteDigitalOutput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/*----------TIMER----------------*/ +static int i_APCI1500_ConfigCounterTimerWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int i_APCI1500_StartStopTriggerTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int i_APCI1500_ReadCounterTimerWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int i_APCI1500_ReadInterruptMask(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/*----------INTERRUPT HANDLER------*/ +static void v_APCI1500_Interrupt(int irq, void *d); +static int i_APCI1500_ConfigureInterrupt(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +/*----------RESET---------------*/ +static int i_APCI1500_Reset(struct comedi_device *dev); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.c new file mode 100644 index 00000000..8a584a01 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.c @@ -0,0 +1,542 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-1516 | Compiler : GCC | + | Module name : hwdrv_apci1516.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-1516 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ +#include "hwdrv_apci1516.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1516_Read1DigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the digital input | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1516_Read1DigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_TmpValue = 0; + unsigned int ui_Channel; + ui_Channel = CR_CHAN(insn->chanspec); + if (ui_Channel <= 7) { + ui_TmpValue = (unsigned int) inw(devpriv->iobase + APCI1516_DIGITAL_IP); + /* since only 1 channel reqd to bring it to last bit it is rotated */ + /* 8 +(chan - 1) times then ANDed with 1 for last bit. */ + *data = (ui_TmpValue >> ui_Channel) & 0x1; + } /* if(ui_Channel >= 0 && ui_Channel <=7) */ + else { + /* comedi_error(dev," \n chan spec wrong\n"); */ + return -EINVAL; /* "sorry channel spec wrong " */ + } /* else if(ui_Channel >= 0 && ui_Channel <=7) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1516_ReadMoreDigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the Requested digital inputs | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1516_ReadMoreDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + unsigned int ui_PortValue = data[0]; + unsigned int ui_Mask = 0; + unsigned int ui_NoOfChannels; + + ui_NoOfChannels = CR_CHAN(insn->chanspec); + + *data = (unsigned int) inw(devpriv->iobase + APCI1516_DIGITAL_IP); + switch (ui_NoOfChannels) { + case 2: + ui_Mask = 3; + *data = (*data >> (2 * ui_PortValue)) & ui_Mask; + break; + case 4: + ui_Mask = 15; + *data = (*data >> (4 * ui_PortValue)) & ui_Mask; + break; + case 7: + break; + + default: + printk("\nWrong parameters\n"); + return -EINVAL; /* "sorry channel spec wrong " */ + break; + } /* switch(ui_NoOfChannels) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1516_ConfigDigitalOutput (struct comedi_device *dev, +| struct comedi_subdevice *s struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : Configures The Digital Output Subdevice. | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| data[0] :1:Memory on | +| 0:Memory off | +| | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1516_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + devpriv->b_OutputMemoryStatus = data[0]; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1516_WriteDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Writes port value To the selected port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1516_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp, ui_Temp1; + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + + printk("EL311003 : @=%x\n", devpriv->iobase + APCI1516_DIGITAL_OP); + + if (devpriv->b_OutputMemoryStatus) { + ui_Temp = inw(devpriv->iobase + APCI1516_DIGITAL_OP); + + } /* if(devpriv->b_OutputMemoryStatus ) */ + else { + ui_Temp = 0; + } /* if(devpriv->b_OutputMemoryStatus ) */ + if (data[3] == 0) { + if (data[1] == 0) { + data[0] = (data[0] << ui_NoOfChannel) | ui_Temp; + outw(data[0], devpriv->iobase + APCI1516_DIGITAL_OP); + + printk("EL311003 : d=%d @=%x\n", data[0], + devpriv->iobase + APCI1516_DIGITAL_OP); + + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = + (data[0] << (2 * + data[2])) | ui_Temp; + break; + + case 4: + data[0] = + (data[0] << (4 * + data[2])) | ui_Temp; + break; + + case 7: + data[0] = data[0] | ui_Temp; + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outw(data[0], + devpriv->iobase + APCI1516_DIGITAL_OP); + + printk("EL311003 : d=%d @=%x\n", data[0], + devpriv->iobase + APCI1516_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==0) */ + else { + if (data[3] == 1) { + if (data[1] == 0) { + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = (data[0] << ui_NoOfChannel) ^ 0xff; + data[0] = data[0] & ui_Temp; + outw(data[0], + devpriv->iobase + APCI1516_DIGITAL_OP); + + printk("EL311003 : d=%d @=%x\n", data[0], + devpriv->iobase + APCI1516_DIGITAL_OP); + + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = ~data[0] & 0x3; + ui_Temp1 = 3; + ui_Temp1 = + ui_Temp1 << 2 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (2 * + data + [2])) ^ + 0xff) & ui_Temp; + break; + + case 4: + data[0] = ~data[0] & 0xf; + ui_Temp1 = 15; + ui_Temp1 = + ui_Temp1 << 4 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (4 * + data + [2])) ^ + 0xff) & ui_Temp; + break; + + case 7: + break; + + default: + comedi_error(dev, + " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outw(data[0], + devpriv->iobase + + APCI1516_DIGITAL_OP); + + printk("EL311003 : d=%d @=%x\n", + data[0], + devpriv->iobase + + APCI1516_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==1); */ + else { + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* if else data[3]==1) */ + } /* if else data[3]==0) */ + return (insn->n); +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1516_ReadDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read value of the selected channel or port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1516_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + unsigned int ui_Temp; + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + ui_Temp = data[0]; + *data = inw(devpriv->iobase + APCI1516_DIGITAL_OP_RW); + if (ui_Temp == 0) { + *data = (*data >> ui_NoOfChannel) & 0x1; + } /* if(ui_Temp==0) */ + else { + if (ui_Temp == 1) { + switch (ui_NoOfChannel) { + + case 2: + *data = (*data >> (2 * data[1])) & 3; + break; + + case 4: + *data = (*data >> (4 * data[1])) & 15; + break; + + case 7: + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + } /* if(ui_Temp==1) */ + else { + printk("\nSpecified channel not supported \n"); + } /* elseif(ui_Temp==1) */ + } /* elseif(ui_Temp==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1516_ConfigWatchdog(struct comedi_device *dev, +| struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : Configures The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1516_ConfigWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0] == 0) { + /* Disable the watchdog */ + outw(0x0, + devpriv->i_IobaseAddon + + APCI1516_WATCHDOG_ENABLEDISABLE); + /* Loading the Reload value */ + outw(data[1], + devpriv->i_IobaseAddon + + APCI1516_WATCHDOG_RELOAD_VALUE); + data[1] = data[1] >> 16; + outw(data[1], + devpriv->i_IobaseAddon + + APCI1516_WATCHDOG_RELOAD_VALUE + 2); + } /* if(data[0]==0) */ + else { + printk("\nThe input parameters are wrong\n"); + return -EINVAL; + } /* elseif(data[0]==0) */ + + return insn->n; +} + + /* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI1516_StartStopWriteWatchdog | + | (struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data); | + +----------------------------------------------------------------------------+ + | Task : Start / Stop The Watchdog | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | struct comedi_subdevice *s, :pointer to subdevice structure + struct comedi_insn *insn :pointer to insn structure | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ + */ + +int i_APCI1516_StartStopWriteWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + switch (data[0]) { + case 0: /* stop the watchdog */ + outw(0x0, devpriv->i_IobaseAddon + APCI1516_WATCHDOG_ENABLEDISABLE); /* disable the watchdog */ + break; + case 1: /* start the watchdog */ + outw(0x0001, + devpriv->i_IobaseAddon + + APCI1516_WATCHDOG_ENABLEDISABLE); + break; + case 2: /* Software trigger */ + outw(0x0201, + devpriv->i_IobaseAddon + + APCI1516_WATCHDOG_ENABLEDISABLE); + break; + default: + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1516_ReadWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, + unsigned int *data); | ++----------------------------------------------------------------------------+ +| Task : Read The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure + struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1516_ReadWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = inw(devpriv->i_IobaseAddon + APCI1516_WATCHDOG_STATUS) & 0x1; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1516_Reset(struct comedi_device *dev) | | ++----------------------------------------------------------------------------+ +| Task :resets all the registers | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1516_Reset(struct comedi_device *dev) +{ + outw(0x0, devpriv->iobase + APCI1516_DIGITAL_OP); /* RESETS THE DIGITAL OUTPUTS */ + outw(0x0, devpriv->i_IobaseAddon + APCI1516_WATCHDOG_ENABLEDISABLE); + outw(0x0, devpriv->i_IobaseAddon + APCI1516_WATCHDOG_RELOAD_VALUE); + outw(0x0, devpriv->i_IobaseAddon + APCI1516_WATCHDOG_RELOAD_VALUE + 2); + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.h new file mode 100644 index 00000000..44728293 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1516.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/********* Definitions for APCI-1516 card *****/ + +/* Card Specific information */ +#define APCI1516_BOARD_VENDOR_ID 0x15B8 +#define APCI1516_ADDRESS_RANGE 8 + +/* DIGITAL INPUT-OUTPUT DEFINE */ + +#define APCI1516_DIGITAL_OP 4 +#define APCI1516_DIGITAL_OP_RW 4 +#define APCI1516_DIGITAL_IP 0 + +/* TIMER COUNTER WATCHDOG DEFINES */ + +#define ADDIDATA_WATCHDOG 2 +#define APCI1516_DIGITAL_OP_WATCHDOG 0 +#define APCI1516_WATCHDOG_ENABLEDISABLE 12 +#define APCI1516_WATCHDOG_RELOAD_VALUE 4 +#define APCI1516_WATCHDOG_STATUS 16 + +/* Hardware Layer functions for Apci1516 */ + +/* Digital Input */ +int i_APCI1516_ReadMoreDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1516_Read1DigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* Digital Output */ +int i_APCI1516_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1516_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1516_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* +* TIMER timer value is passed as u seconds +*/ +int i_APCI1516_ConfigWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1516_StartStopWriteWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1516_ReadWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* reset */ +int i_APCI1516_Reset(struct comedi_device *dev); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c new file mode 100644 index 00000000..5b92e45c --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c @@ -0,0 +1,1128 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-1564 | Compiler : GCC | + | Module name : hwdrv_apci1564.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-1564 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ + +#include +#include "hwdrv_apci1564.h" + +/* Global variables */ +static unsigned int ui_InterruptStatus_1564 = 0; +static unsigned int ui_InterruptData, ui_Type; + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_ConfigDigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures the digital input Subdevice | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 1 Enable Digital Input Interrupt | +| 0 Disable Digital Input Interrupt | +| data[1] : 0 ADDIDATA Interrupt OR LOGIC | +| : 1 ADDIDATA Interrupt AND LOGIC | +| data[2] : Interrupt mask for the mode 1 | +| data[3] : Interrupt mask for the mode 2 | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1564_ConfigDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + devpriv->tsk_Current = current; + /*******************************/ + /* Set the digital input logic */ + /*******************************/ + if (data[0] == ADDIDATA_ENABLE) { + data[2] = data[2] << 4; + data[3] = data[3] << 4; + outl(data[2], + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_INTERRUPT_MODE1); + outl(data[3], + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_INTERRUPT_MODE2); + if (data[1] == ADDIDATA_OR) { + outl(0x4, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_IRQ); + } /* if (data[1] == ADDIDATA_OR) */ + else { + outl(0x6, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_IRQ); + } /* else if (data[1] == ADDIDATA_OR) */ + } /* if (data[0] == ADDIDATA_ENABLE) */ + else { + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_INTERRUPT_MODE1); + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_INTERRUPT_MODE2); + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_IRQ); + } /* else if (data[0] == ADDIDATA_ENABLE) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_Read1DigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the digital input | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_Channel : Channel number to read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1564_Read1DigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_TmpValue = 0; + unsigned int ui_Channel; + + ui_Channel = CR_CHAN(insn->chanspec); + if (ui_Channel <= 31) { + ui_TmpValue = + (unsigned int) inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP); +/* +* since only 1 channel reqd to bring it to last bit it is rotated 8 +* +(chan - 1) times then ANDed with 1 for last bit. +*/ + *data = (ui_TmpValue >> ui_Channel) & 0x1; + } /* if (ui_Channel >= 0 && ui_Channel <=31) */ + else { + comedi_error(dev, "Not a valid channel number !!! \n"); + return -EINVAL; /* "sorry channel spec wrong " */ + } /* else if (ui_Channel >= 0 && ui_Channel <=31) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_ReadMoreDigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the Requested digital inputs | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To be Read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1564_ReadMoreDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_PortValue = data[0]; + unsigned int ui_Mask = 0; + unsigned int ui_NoOfChannels; + + ui_NoOfChannels = CR_CHAN(insn->chanspec); + if (data[1] == 0) { + *data = (unsigned int) inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP); + switch (ui_NoOfChannels) { + case 2: + ui_Mask = 3; + *data = (*data >> (2 * ui_PortValue)) & ui_Mask; + break; + case 4: + ui_Mask = 15; + *data = (*data >> (4 * ui_PortValue)) & ui_Mask; + break; + case 8: + ui_Mask = 255; + *data = (*data >> (8 * ui_PortValue)) & ui_Mask; + break; + case 16: + ui_Mask = 65535; + *data = (*data >> (16 * ui_PortValue)) & ui_Mask; + break; + case 31: + break; + default: + comedi_error(dev, "Not a valid Channel number !!!\n"); + return -EINVAL; /* "sorry channel spec wrong " */ + break; + } /* switch (ui_NoOfChannels) */ + } /* if (data[1]==0) */ + else { + if (data[1] == 1) { + *data = ui_InterruptStatus_1564; + } /* if (data[1]==1) */ + } /* else if (data[1]==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_ConfigDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Digital Output Subdevice. | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[1] : 1 Enable VCC Interrupt | +| 0 Disable VCC Interrupt | +| data[2] : 1 Enable CC Interrupt | +| 0 Disable CC Interrupt | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1564_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ul_Command = 0; + + if ((data[0] != 0) && (data[0] != 1)) { + comedi_error(dev, + "Not a valid Data !!! ,Data should be 1 or 0\n"); + return -EINVAL; + } /* if ((data[0]!=0) && (data[0]!=1)) */ + if (data[0]) { + devpriv->b_OutputMemoryStatus = ADDIDATA_ENABLE; + } /* if (data[0]) */ + else { + devpriv->b_OutputMemoryStatus = ADDIDATA_DISABLE; + } /* else if (data[0]) */ + if (data[1] == ADDIDATA_ENABLE) { + ul_Command = ul_Command | 0x1; + } /* if (data[1] == ADDIDATA_ENABLE) */ + else { + ul_Command = ul_Command & 0xFFFFFFFE; + } /* else if (data[1] == ADDIDATA_ENABLE) */ + if (data[2] == ADDIDATA_ENABLE) { + ul_Command = ul_Command | 0x2; + } /* if (data[2] == ADDIDATA_ENABLE) */ + else { + ul_Command = ul_Command & 0xFFFFFFFD; + } /* else if (data[2] == ADDIDATA_ENABLE) */ + outl(ul_Command, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_INTERRUPT); + ui_InterruptData = + inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_INTERRUPT); + devpriv->tsk_Current = current; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_WriteDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Writes port value To the selected port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To Write | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1564_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp, ui_Temp1; + unsigned int ui_NoOfChannel; + + ui_NoOfChannel = CR_CHAN(insn->chanspec); + if (devpriv->b_OutputMemoryStatus) { + ui_Temp = + inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_RW); + } /* if (devpriv->b_OutputMemoryStatus ) */ + else { + ui_Temp = 0; + } /* else if (devpriv->b_OutputMemoryStatus ) */ + if (data[3] == 0) { + if (data[1] == 0) { + data[0] = (data[0] << ui_NoOfChannel) | ui_Temp; + outl(data[0], + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_RW); + } /* if (data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + case 2: + data[0] = + (data[0] << (2 * + data[2])) | ui_Temp; + break; + case 4: + data[0] = + (data[0] << (4 * + data[2])) | ui_Temp; + break; + case 8: + data[0] = + (data[0] << (8 * + data[2])) | ui_Temp; + break; + case 16: + data[0] = + (data[0] << (16 * + data[2])) | ui_Temp; + break; + case 31: + data[0] = data[0] | ui_Temp; + break; + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + } /* switch (ui_NoOfChannels) */ + outl(data[0], + devpriv->i_IobaseAmcc + + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_RW); + } /* if (data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if (data[1]==1) */ + } /* else if (data[1]==0) */ + } /* if(data[3]==0) */ + else { + if (data[3] == 1) { + if (data[1] == 0) { + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + (data[0] << ui_NoOfChannel) ^ + 0xffffffff; + data[0] = data[0] & ui_Temp; + outl(data[0], + devpriv->i_IobaseAmcc + + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_RW); + } /* if (data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + case 2: + data[0] = ~data[0] & 0x3; + ui_Temp1 = 3; + ui_Temp1 = + ui_Temp1 << 2 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (2 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + case 4: + data[0] = ~data[0] & 0xf; + ui_Temp1 = 15; + ui_Temp1 = + ui_Temp1 << 4 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (4 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + case 8: + data[0] = ~data[0] & 0xff; + ui_Temp1 = 255; + ui_Temp1 = + ui_Temp1 << 8 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (8 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + case 16: + data[0] = ~data[0] & 0xffff; + ui_Temp1 = 65535; + ui_Temp1 = + ui_Temp1 << 16 * + data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (16 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + case 31: + break; + default: + comedi_error(dev, + " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + } /* switch(ui_NoOfChannels) */ + outl(data[0], + devpriv->i_IobaseAmcc + + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_RW); + } /* if (data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if (data[1]==1) */ + } /* else if (data[1]==0) */ + } /* if (data[3]==1); */ + else { + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* else if (data[3]==1) */ + } /* else if (data[3]==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_ReadDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read value of the selected channel or port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1564_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp; + unsigned int ui_NoOfChannel; + + ui_NoOfChannel = CR_CHAN(insn->chanspec); + ui_Temp = data[0]; + *data = inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_RW); + if (ui_Temp == 0) { + *data = (*data >> ui_NoOfChannel) & 0x1; + } /* if (ui_Temp==0) */ + else { + if (ui_Temp == 1) { + switch (ui_NoOfChannel) { + case 2: + *data = (*data >> (2 * data[1])) & 3; + break; + + case 4: + *data = (*data >> (4 * data[1])) & 15; + break; + + case 8: + *data = (*data >> (8 * data[1])) & 255; + break; + + case 16: + *data = (*data >> (16 * data[1])) & 65535; + break; + + case 31: + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + break; + } /* switch(ui_NoOfChannels) */ + } /* if (ui_Temp==1) */ + else { + printk("\nSpecified channel not supported \n"); + } /* else if (ui_Temp==1) */ + } /* else if (ui_Temp==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_ConfigTimerCounterWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Timer , Counter or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 0 Configure As Timer | +| 1 Configure As Counter | +| 2 Configure As Watchdog | +| data[1] : 1 Enable Interrupt | +| 0 Disable Interrupt | +| data[2] : Time Unit | +| data[3] : Reload Value | +| data[4] : Timer Mode | +| data[5] : Timer Counter Watchdog Number| + data[6] : Counter Direction ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1564_ConfigTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ul_Command1 = 0; + devpriv->tsk_Current = current; + if (data[0] == ADDIDATA_WATCHDOG) { + devpriv->b_TimerSelectMode = ADDIDATA_WATCHDOG; + + /* Disable the watchdog */ + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP_WATCHDOG + + APCI1564_TCW_PROG); + /* Loading the Reload value */ + outl(data[3], + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP_WATCHDOG + + APCI1564_TCW_RELOAD_VALUE); + } /* if (data[0]==ADDIDATA_WATCHDOG) */ + else if (data[0] == ADDIDATA_TIMER) { + /* First Stop The Timer */ + ul_Command1 = + inl(devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_PROG); + ul_Command1 = ul_Command1 & 0xFFFFF9FEUL; + outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER + APCI1564_TCW_PROG); /* Stop The Timer */ + + devpriv->b_TimerSelectMode = ADDIDATA_TIMER; + if (data[1] == 1) { + outl(0x02, devpriv->i_IobaseAmcc + APCI1564_TIMER + APCI1564_TCW_PROG); /* Enable TIMER int & DISABLE ALL THE OTHER int SOURCES */ + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_IRQ); + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_IRQ); + outl(0x0, + devpriv->i_IobaseAmcc + + APCI1564_DIGITAL_OP_WATCHDOG + + APCI1564_TCW_IRQ); + outl(0x0, + devpriv->iobase + APCI1564_COUNTER1 + + APCI1564_TCW_IRQ); + outl(0x0, + devpriv->iobase + APCI1564_COUNTER2 + + APCI1564_TCW_IRQ); + outl(0x0, + devpriv->iobase + APCI1564_COUNTER3 + + APCI1564_TCW_IRQ); + outl(0x0, + devpriv->iobase + APCI1564_COUNTER4 + + APCI1564_TCW_IRQ); + } /* if (data[1]==1) */ + else { + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER + APCI1564_TCW_PROG); /* disable Timer interrupt */ + } /* else if (data[1]==1) */ + + /* Loading Timebase */ + + outl(data[2], + devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_TIMEBASE); + + /* Loading the Reload value */ + outl(data[3], + devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_RELOAD_VALUE); + + ul_Command1 = + inl(devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_PROG); + ul_Command1 = + (ul_Command1 & 0xFFF719E2UL) | 2UL << 13UL | 0x10UL; + outl(ul_Command1, devpriv->i_IobaseAmcc + APCI1564_TIMER + APCI1564_TCW_PROG); /* mode 2 */ + } /* else if (data[0]==ADDIDATA_TIMER) */ + else if (data[0] == ADDIDATA_COUNTER) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + devpriv->b_ModeSelectRegister = data[5]; + + /* First Stop The Counter */ + ul_Command1 = + inl(devpriv->iobase + ((data[5] - 1) * 0x20) + + APCI1564_TCW_PROG); + ul_Command1 = ul_Command1 & 0xFFFFF9FEUL; + outl(ul_Command1, devpriv->iobase + ((data[5] - 1) * 0x20) + APCI1564_TCW_PROG); /* Stop The Timer */ + + /************************/ + /* Set the reload value */ + /************************/ + outl(data[3], + devpriv->iobase + ((data[5] - 1) * 0x20) + + APCI1564_TCW_RELOAD_VALUE); + + /******************************/ + /* Set the mode : */ + /* - Disable the hardware */ + /* - Disable the counter mode */ + /* - Disable the warning */ + /* - Disable the reset */ + /* - Disable the timer mode */ + /* - Enable the counter mode */ + /******************************/ + ul_Command1 = + (ul_Command1 & 0xFFFC19E2UL) | 0x80000UL | + (unsigned int) ((unsigned int) data[4] << 16UL); + outl(ul_Command1, + devpriv->iobase + ((data[5] - 1) * 0x20) + + APCI1564_TCW_PROG); + + /* Enable or Disable Interrupt */ + ul_Command1 = (ul_Command1 & 0xFFFFF9FD) | (data[1] << 1); + outl(ul_Command1, + devpriv->iobase + ((data[5] - 1) * 0x20) + + APCI1564_TCW_PROG); + + /*****************************/ + /* Set the Up/Down selection */ + /*****************************/ + ul_Command1 = (ul_Command1 & 0xFFFBF9FFUL) | (data[6] << 18); + outl(ul_Command1, + devpriv->iobase + ((data[5] - 1) * 0x20) + + APCI1564_TCW_PROG); + } /* else if (data[0]==ADDIDATA_COUNTER) */ + else { + printk(" Invalid subdevice."); + } /* else if (data[0]==ADDIDATA_WATCHDOG) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_StartStopWriteTimerCounterWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Start / Stop The Selected Timer , Counter or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 0 Timer | +| 1 Counter | +| 2 Watchdog | | data[1] : 1 Start | +| 0 Stop | +| 2 Trigger | +| Clear (Only Counter) | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1564_StartStopWriteTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ul_Command1 = 0; + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) { + switch (data[1]) { + case 0: /* stop the watchdog */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP_WATCHDOG + APCI1564_TCW_PROG); /* disable the watchdog */ + break; + case 1: /* start the watchdog */ + outl(0x0001, + devpriv->i_IobaseAmcc + + APCI1564_DIGITAL_OP_WATCHDOG + + APCI1564_TCW_PROG); + break; + case 2: /* Software trigger */ + outl(0x0201, + devpriv->i_IobaseAmcc + + APCI1564_DIGITAL_OP_WATCHDOG + + APCI1564_TCW_PROG); + break; + default: + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* switch (data[1]) */ + } /* if (devpriv->b_TimerSelectMode==ADDIDATA_WATCHDOG) */ + if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) { + if (data[1] == 1) { + ul_Command1 = + inl(devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_PROG); + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL; + + /* Enable the Timer */ + outl(ul_Command1, + devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_PROG); + } /* if (data[1]==1) */ + else if (data[1] == 0) { + /* Stop The Timer */ + + ul_Command1 = + inl(devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_PROG); + ul_Command1 = ul_Command1 & 0xFFFFF9FEUL; + outl(ul_Command1, + devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_PROG); + } /* else if(data[1]==0) */ + } /* if (devpriv->b_TimerSelectMode==ADDIDATA_TIMER) */ + if (devpriv->b_TimerSelectMode == ADDIDATA_COUNTER) { + ul_Command1 = + inl(devpriv->iobase + ((devpriv->b_ModeSelectRegister - + 1) * 0x20) + APCI1564_TCW_PROG); + if (data[1] == 1) { + /* Start the Counter subdevice */ + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL; + } /* if (data[1] == 1) */ + else if (data[1] == 0) { + /* Stops the Counter subdevice */ + ul_Command1 = 0; + + } /* else if (data[1] == 0) */ + else if (data[1] == 2) { + /* Clears the Counter subdevice */ + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x400; + } /* else if (data[1] == 3) */ + outl(ul_Command1, + devpriv->iobase + ((devpriv->b_ModeSelectRegister - + 1) * 0x20) + APCI1564_TCW_PROG); + } /* if (devpriv->b_TimerSelectMode==ADDIDATA_COUNTER) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_ReadTimerCounterWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read The Selected Timer , Counter or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | + ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI1564_ReadTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ul_Command1 = 0; + + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) { + /* Stores the status of the Watchdog */ + data[0] = + inl(devpriv->i_IobaseAmcc + + APCI1564_DIGITAL_OP_WATCHDOG + + APCI1564_TCW_TRIG_STATUS) & 0x1; + data[1] = + inl(devpriv->i_IobaseAmcc + + APCI1564_DIGITAL_OP_WATCHDOG); + } /* if (devpriv->b_TimerSelectMode==ADDIDATA_WATCHDOG) */ + else if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) { + /* Stores the status of the Timer */ + data[0] = + inl(devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_TRIG_STATUS) & 0x1; + + /* Stores the Actual value of the Timer */ + data[1] = inl(devpriv->i_IobaseAmcc + APCI1564_TIMER); + } /* else if (devpriv->b_TimerSelectMode==ADDIDATA_TIMER) */ + else if (devpriv->b_TimerSelectMode == ADDIDATA_COUNTER) { + /* Read the Counter Actual Value. */ + data[0] = + inl(devpriv->iobase + ((devpriv->b_ModeSelectRegister - + 1) * 0x20) + + APCI1564_TCW_SYNC_ENABLEDISABLE); + ul_Command1 = + inl(devpriv->iobase + ((devpriv->b_ModeSelectRegister - + 1) * 0x20) + APCI1564_TCW_TRIG_STATUS); + + /***********************************/ + /* Get the software trigger status */ + /***********************************/ + data[1] = (unsigned char) ((ul_Command1 >> 1) & 1); + + /***********************************/ + /* Get the hardware trigger status */ + /***********************************/ + data[2] = (unsigned char) ((ul_Command1 >> 2) & 1); + + /*********************************/ + /* Get the software clear status */ + /*********************************/ + data[3] = (unsigned char) ((ul_Command1 >> 3) & 1); + + /***************************/ + /* Get the overflow status */ + /***************************/ + data[4] = (unsigned char) ((ul_Command1 >> 0) & 1); + } /* else if (devpriv->b_TimerSelectMode==ADDIDATA_COUNTER) */ + else if ((devpriv->b_TimerSelectMode != ADDIDATA_TIMER) + && (devpriv->b_TimerSelectMode != ADDIDATA_WATCHDOG) + && (devpriv->b_TimerSelectMode != ADDIDATA_COUNTER)) { + printk("\n Invalid Subdevice !!!\n"); + } /* else if ((devpriv->b_TimerSelectMode!=ADDIDATA_TIMER) && (devpriv->b_TimerSelectMode!=ADDIDATA_WATCHDOG)&& (devpriv->b_TimerSelectMode!=ADDIDATA_COUNTER)) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_ReadInterruptStatus | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task :Reads the interrupt status register | ++----------------------------------------------------------------------------+ +| Input Parameters : | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1564_ReadInterruptStatus(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + *data = ui_Type; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : static void v_APCI1564_Interrupt | +| (int irq , void *d) | ++----------------------------------------------------------------------------+ +| Task : Interrupt handler for the interruptible digital inputs | ++----------------------------------------------------------------------------+ +| Input Parameters : int irq : irq number | +| void *d : void pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +static void v_APCI1564_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned int ui_DO, ui_DI; + unsigned int ui_Timer; + unsigned int ui_C1, ui_C2, ui_C3, ui_C4; + unsigned int ul_Command2 = 0; + ui_DI = inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_IRQ) & 0x01; + ui_DO = inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_IRQ) & 0x01; + ui_Timer = + inl(devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_IRQ) & 0x01; + ui_C1 = inl(devpriv->iobase + APCI1564_COUNTER1 + + APCI1564_TCW_IRQ) & 0x1; + ui_C2 = inl(devpriv->iobase + APCI1564_COUNTER2 + + APCI1564_TCW_IRQ) & 0x1; + ui_C3 = inl(devpriv->iobase + APCI1564_COUNTER3 + + APCI1564_TCW_IRQ) & 0x1; + ui_C4 = inl(devpriv->iobase + APCI1564_COUNTER4 + + APCI1564_TCW_IRQ) & 0x1; + if (ui_DI == 0 && ui_DO == 0 && ui_Timer == 0 && ui_C1 == 0 + && ui_C2 == 0 && ui_C3 == 0 && ui_C4 == 0) { + printk("\nInterrupt from unknown source\n"); + } /* if(ui_DI==0 && ui_DO==0 && ui_Timer==0 && ui_C1==0 && ui_C2==0 && ui_C3==0 && ui_C4==0) */ + + if (ui_DI == 1) { + ui_DI = inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_IRQ); + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_IRQ); + ui_InterruptStatus_1564 = + inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + + APCI1564_DIGITAL_IP_INTERRUPT_STATUS); + ui_InterruptStatus_1564 = ui_InterruptStatus_1564 & 0X000FFFF0; + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + outl(ui_DI, devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP + APCI1564_DIGITAL_IP_IRQ); /* enable the interrupt */ + return; + } + + if (ui_DO == 1) { + /* Check for Digital Output interrupt Type - 1: Vcc interrupt 2: CC interrupt. */ + ui_Type = + inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_INTERRUPT_STATUS) & 0x3; + /* Disable the Interrupt */ + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP + + APCI1564_DIGITAL_OP_INTERRUPT); + + /* Sends signal to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + } /* if (ui_DO) */ + + if (ui_Timer == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_TIMER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Timer Interrupt */ + ul_Command2 = + inl(devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_PROG); + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_PROG); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Timer Interrupt */ + + outl(ul_Command2, + devpriv->i_IobaseAmcc + APCI1564_TIMER + + APCI1564_TCW_PROG); + } + }/* if (ui_Timer == 1) */ + + + if (ui_C1 == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Counter Interrupt */ + ul_Command2 = + inl(devpriv->iobase + APCI1564_COUNTER1 + + APCI1564_TCW_PROG); + outl(0x0, + devpriv->iobase + APCI1564_COUNTER1 + + APCI1564_TCW_PROG); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Counter Interrupt */ + outl(ul_Command2, + devpriv->iobase + APCI1564_COUNTER1 + + APCI1564_TCW_PROG); + } + } /* if (ui_C1 == 1) */ + + if (ui_C2 == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Counter Interrupt */ + ul_Command2 = + inl(devpriv->iobase + APCI1564_COUNTER2 + + APCI1564_TCW_PROG); + outl(0x0, + devpriv->iobase + APCI1564_COUNTER2 + + APCI1564_TCW_PROG); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Counter Interrupt */ + outl(ul_Command2, + devpriv->iobase + APCI1564_COUNTER2 + + APCI1564_TCW_PROG); + } + } /* if ((ui_C2 == 1) */ + + if (ui_C3 == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Counter Interrupt */ + ul_Command2 = + inl(devpriv->iobase + APCI1564_COUNTER3 + + APCI1564_TCW_PROG); + outl(0x0, + devpriv->iobase + APCI1564_COUNTER3 + + APCI1564_TCW_PROG); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Counter Interrupt */ + outl(ul_Command2, + devpriv->iobase + APCI1564_COUNTER3 + + APCI1564_TCW_PROG); + } + } /* if ((ui_C3 == 1) */ + + if (ui_C4 == 1) { + devpriv->b_TimerSelectMode = ADDIDATA_COUNTER; + if (devpriv->b_TimerSelectMode) { + + /* Disable Counter Interrupt */ + ul_Command2 = + inl(devpriv->iobase + APCI1564_COUNTER4 + + APCI1564_TCW_PROG); + outl(0x0, + devpriv->iobase + APCI1564_COUNTER4 + + APCI1564_TCW_PROG); + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + + /* Enable Counter Interrupt */ + outl(ul_Command2, + devpriv->iobase + APCI1564_COUNTER4 + + APCI1564_TCW_PROG); + } + } /* if (ui_C4 == 1) */ + return; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI1564_Reset(struct comedi_device *dev) | | ++----------------------------------------------------------------------------+ +| Task :resets all the registers | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI1564_Reset(struct comedi_device *dev) +{ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP_IRQ); /* disable the interrupts */ + inl(devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP_INTERRUPT_STATUS); /* Reset the interrupt status register */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP_INTERRUPT_MODE1); /* Disable the and/or interrupt */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DIGITAL_IP_INTERRUPT_MODE2); + devpriv->b_DigitalOutputRegister = 0; + ui_Type = 0; + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP); /* Resets the output channels */ + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP_INTERRUPT); /* Disables the interrupt. */ + outl(0x0, + devpriv->i_IobaseAmcc + APCI1564_DIGITAL_OP_WATCHDOG + + APCI1564_TCW_RELOAD_VALUE); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER); + outl(0x0, devpriv->i_IobaseAmcc + APCI1564_TIMER + APCI1564_TCW_PROG); + + outl(0x0, devpriv->iobase + APCI1564_COUNTER1 + APCI1564_TCW_PROG); + outl(0x0, devpriv->iobase + APCI1564_COUNTER2 + APCI1564_TCW_PROG); + outl(0x0, devpriv->iobase + APCI1564_COUNTER3 + APCI1564_TCW_PROG); + outl(0x0, devpriv->iobase + APCI1564_COUNTER4 + APCI1564_TCW_PROG); + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.h new file mode 100644 index 00000000..c91594d5 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/********* Definitions for APCI-1564 card *****/ + +#define APCI1564_BOARD_VENDOR_ID 0x15B8 +#define APCI1564_ADDRESS_RANGE 128 + +/* DIGITAL INPUT-OUTPUT DEFINE */ +/* Input defines */ +#define APCI1564_DIGITAL_IP 0x04 +#define APCI1564_DIGITAL_IP_INTERRUPT_MODE1 4 +#define APCI1564_DIGITAL_IP_INTERRUPT_MODE2 8 +#define APCI1564_DIGITAL_IP_IRQ 16 + +/* Output defines */ +#define APCI1564_DIGITAL_OP 0x18 +#define APCI1564_DIGITAL_OP_RW 0 +#define APCI1564_DIGITAL_OP_INTERRUPT 4 +#define APCI1564_DIGITAL_OP_IRQ 12 + +/* Digital Input IRQ Function Selection */ +#define ADDIDATA_OR 0 +#define ADDIDATA_AND 1 + +/* Digital Input Interrupt Status */ +#define APCI1564_DIGITAL_IP_INTERRUPT_STATUS 12 + +/* Digital Output Interrupt Status */ +#define APCI1564_DIGITAL_OP_INTERRUPT_STATUS 8 + +/* Digital Input Interrupt Enable Disable. */ +#define APCI1564_DIGITAL_IP_INTERRUPT_ENABLE 0x4 +#define APCI1564_DIGITAL_IP_INTERRUPT_DISABLE 0xFFFFFFFB + +/* Digital Output Interrupt Enable Disable. */ +#define APCI1564_DIGITAL_OP_VCC_INTERRUPT_ENABLE 0x1 +#define APCI1564_DIGITAL_OP_VCC_INTERRUPT_DISABLE 0xFFFFFFFE +#define APCI1564_DIGITAL_OP_CC_INTERRUPT_ENABLE 0x2 +#define APCI1564_DIGITAL_OP_CC_INTERRUPT_DISABLE 0xFFFFFFFD + +/* ADDIDATA Enable Disable */ + +#define ADDIDATA_ENABLE 1 +#define ADDIDATA_DISABLE 0 + +/* TIMER COUNTER WATCHDOG DEFINES */ + +#define ADDIDATA_TIMER 0 +#define ADDIDATA_COUNTER 1 +#define ADDIDATA_WATCHDOG 2 +#define APCI1564_DIGITAL_OP_WATCHDOG 0x28 +#define APCI1564_TIMER 0x48 +#define APCI1564_COUNTER1 0x0 +#define APCI1564_COUNTER2 0x20 +#define APCI1564_COUNTER3 0x40 +#define APCI1564_COUNTER4 0x60 +#define APCI1564_TCW_SYNC_ENABLEDISABLE 0 +#define APCI1564_TCW_RELOAD_VALUE 4 +#define APCI1564_TCW_TIMEBASE 8 +#define APCI1564_TCW_PROG 12 +#define APCI1564_TCW_TRIG_STATUS 16 +#define APCI1564_TCW_IRQ 20 +#define APCI1564_TCW_WARN_TIMEVAL 24 +#define APCI1564_TCW_WARN_TIMEBASE 28 + +/* Hardware Layer functions for Apci1564 */ + +/* +* DI for di read +*/ +int i_APCI1564_ConfigDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1564_Read1DigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1564_ReadMoreDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* DO */ +int i_APCI1564_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1564_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1564_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1564_ReadInterruptStatus(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* +* TIMER timer value is passed as u seconds +*/ +int i_APCI1564_ConfigTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI1564_StartStopWriteTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +int i_APCI1564_ReadTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* intERRUPT */ +static void v_APCI1564_Interrupt(int irq, void *d); + +/* RESET */ +int i_APCI1564_Reset(struct comedi_device *dev); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci16xx.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci16xx.c new file mode 100644 index 00000000..00a088f8 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci16xx.c @@ -0,0 +1,780 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : API APCI1648 | Compiler : gcc | + | Module name : TTL.C | Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: S. Weber | Date : 25/05/2005 | + +-----------------------------------------------------------------------+ + | Description : APCI-16XX TTL I/O module | + | | + | | + +-----------------------------------------------------------------------+ + | UPDATES | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + |25.05.2005| S.Weber | Creation | + | | | | + +-----------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ + +#include "hwdrv_apci16xx.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI16XX_InsnConfigInitTTLIO | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task APCI16XX_TTL_INIT (using defaults) : | +| Configure the TTL I/O operating mode from all ports | +| You must calling this function be | +| for you call any other function witch access of TTL. | +| APCI16XX_TTL_INITDIRECTION(user inputs for direction) | ++----------------------------------------------------------------------------+ +| Input Parameters : b_InitType = (unsigned char) data[0]; | +| b_Port0Mode = (unsigned char) data[1]; | +| b_Port1Mode = (unsigned char) data[2]; | +| b_Port2Mode = (unsigned char) data[3]; | +| b_Port3Mode = (unsigned char) data[4]; | +| ........ | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value :>0: No error | +| -1: Port 0 mode selection is wrong | +| -2: Port 1 mode selection is wrong | +| -3: Port 2 mode selection is wrong | +| -4: Port 3 mode selection is wrong | +| -X: Port X-1 mode selection is wrong | +| .... | +| -100 : Config command error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI16XX_InsnConfigInitTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_Command = 0; + unsigned char b_Cpt = 0; + unsigned char b_NumberOfPort = + (unsigned char) (this_board->i_NbrTTLChannel / 8); + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + /*******************/ + /* Get the command */ + /* **************** */ + + b_Command = (unsigned char) data[0]; + + /********************/ + /* Test the command */ + /********************/ + + if ((b_Command == APCI16XX_TTL_INIT) || + (b_Command == APCI16XX_TTL_INITDIRECTION) || + (b_Command == APCI16XX_TTL_OUTPUTMEMORY)) { + /***************************************/ + /* Test the initialisation buffer size */ + /***************************************/ + + if ((b_Command == APCI16XX_TTL_INITDIRECTION) + && ((unsigned char) (insn->n - 1) != b_NumberOfPort)) { + /*******************/ + /* Data size error */ + /*******************/ + + printk("\nBuffer size error"); + i_ReturnValue = -101; + } + + if ((b_Command == APCI16XX_TTL_OUTPUTMEMORY) + && ((unsigned char) (insn->n) != 2)) { + /*******************/ + /* Data size error */ + /*******************/ + + printk("\nBuffer size error"); + i_ReturnValue = -101; + } + } else { + /************************/ + /* Config command error */ + /************************/ + + printk("\nCommand selection error"); + i_ReturnValue = -100; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("\nBuffer size error"); + i_ReturnValue = -101; + } + + /**************************************************************************/ + /* Test if no error occur and APCI16XX_TTL_INITDIRECTION command selected */ + /**************************************************************************/ + + if ((i_ReturnValue >= 0) && (b_Command == APCI16XX_TTL_INITDIRECTION)) { + memset(devpriv->ul_TTLPortConfiguration, 0, + sizeof(devpriv->ul_TTLPortConfiguration)); + + /*************************************/ + /* Test the port direction selection */ + /*************************************/ + + for (b_Cpt = 1; + (b_Cpt <= b_NumberOfPort) && (i_ReturnValue >= 0); + b_Cpt++) { + /**********************/ + /* Test the direction */ + /**********************/ + + if ((data[b_Cpt] != 0) && (data[b_Cpt] != 0xFF)) { + /************************/ + /* Port direction error */ + /************************/ + + printk("\nPort %d direction selection error", + (int) b_Cpt); + i_ReturnValue = -(int) b_Cpt; + } + + /**************************/ + /* Save the configuration */ + /**************************/ + + devpriv->ul_TTLPortConfiguration[(b_Cpt - 1) / 4] = + devpriv->ul_TTLPortConfiguration[(b_Cpt - + 1) / 4] | (data[b_Cpt] << (8 * ((b_Cpt - + 1) % 4))); + } + } + + /**************************/ + /* Test if no error occur */ + /**************************/ + + if (i_ReturnValue >= 0) { + /***********************************/ + /* Test if TTL port initilaisation */ + /***********************************/ + + if ((b_Command == APCI16XX_TTL_INIT) + || (b_Command == APCI16XX_TTL_INITDIRECTION)) { + /******************************/ + /* Set all port configuration */ + /******************************/ + + for (b_Cpt = 0; b_Cpt <= b_NumberOfPort; b_Cpt++) { + if ((b_Cpt % 4) == 0) { + /*************************/ + /* Set the configuration */ + /*************************/ + + outl(devpriv-> + ul_TTLPortConfiguration[b_Cpt / + 4], + devpriv->iobase + 32 + b_Cpt); + } + } + } + } + + /************************************************/ + /* Test if output memory initialisation command */ + /************************************************/ + + if (b_Command == APCI16XX_TTL_OUTPUTMEMORY) { + if (data[1]) { + devpriv->b_OutputMemoryStatus = ADDIDATA_ENABLE; + } else { + devpriv->b_OutputMemoryStatus = ADDIDATA_DISABLE; + } + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| INPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI16XX_InsnBitsReadTTLIO | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read the status from selected TTL digital input | +| (b_InputChannel) | ++----------------------------------------------------------------------------+ +| Task : Read the status from digital input port | +| (b_SelectedPort) | ++----------------------------------------------------------------------------+ +| Input Parameters : | +| APCI16XX_TTL_READCHANNEL | +| b_SelectedPort= CR_RANGE(insn->chanspec); | +| b_InputChannel= CR_CHAN(insn->chanspec); | +| b_ReadType = (unsigned char) data[0]; | +| | +| APCI16XX_TTL_READPORT | +| b_SelectedPort= CR_RANGE(insn->chanspec); | +| b_ReadType = (unsigned char) data[0]; | ++----------------------------------------------------------------------------+ +| Output Parameters : data[0] 0 : Channle is not active | +| 1 : Channle is active | ++----------------------------------------------------------------------------+ +| Return Value : >0 : No error | +| -100 : Config command error | +| -101 : Data size error | +| -102 : The selected TTL input port is wrong | +| -103 : The selected TTL digital input is wrong | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI16XX_InsnBitsReadTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_Command = 0; + unsigned char b_NumberOfPort = + (unsigned char) (this_board->i_NbrTTLChannel / 8); + unsigned char b_SelectedPort = CR_RANGE(insn->chanspec); + unsigned char b_InputChannel = CR_CHAN(insn->chanspec); + unsigned char *pb_Status; + unsigned int dw_Status; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + /*******************/ + /* Get the command */ + /* **************** */ + + b_Command = (unsigned char) data[0]; + + /********************/ + /* Test the command */ + /********************/ + + if ((b_Command == APCI16XX_TTL_READCHANNEL) + || (b_Command == APCI16XX_TTL_READPORT)) { + /**************************/ + /* Test the selected port */ + /**************************/ + + if (b_SelectedPort < b_NumberOfPort) { + /**********************/ + /* Test if input port */ + /**********************/ + + if (((devpriv->ul_TTLPortConfiguration + [b_SelectedPort / + 4] >> (8 * + (b_SelectedPort + % + 4))) & + 0xFF) == 0) { + /***************************/ + /* Test the channel number */ + /***************************/ + + if ((b_Command == + APCI16XX_TTL_READCHANNEL) + && (b_InputChannel > 7)) { + /*******************************************/ + /* The selected TTL digital input is wrong */ + /*******************************************/ + + printk("\nChannel selection error"); + i_ReturnValue = -103; + } + } else { + /****************************************/ + /* The selected TTL input port is wrong */ + /****************************************/ + + printk("\nPort selection error"); + i_ReturnValue = -102; + } + } else { + /****************************************/ + /* The selected TTL input port is wrong */ + /****************************************/ + + printk("\nPort selection error"); + i_ReturnValue = -102; + } + } else { + /************************/ + /* Config command error */ + /************************/ + + printk("\nCommand selection error"); + i_ReturnValue = -100; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("\nBuffer size error"); + i_ReturnValue = -101; + } + + /**************************/ + /* Test if no error occur */ + /**************************/ + + if (i_ReturnValue >= 0) { + pb_Status = (unsigned char *) &data[0]; + + /*******************************/ + /* Get the digital inpu status */ + /*******************************/ + + dw_Status = + inl(devpriv->iobase + 8 + ((b_SelectedPort / 4) * 4)); + dw_Status = (dw_Status >> (8 * (b_SelectedPort % 4))) & 0xFF; + + /***********************/ + /* Save the port value */ + /***********************/ + + *pb_Status = (unsigned char) dw_Status; + + /***************************************/ + /* Test if read channel status command */ + /***************************************/ + + if (b_Command == APCI16XX_TTL_READCHANNEL) { + *pb_Status = (*pb_Status >> b_InputChannel) & 1; + } + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI16XX_InsnReadTTLIOAllPortValue | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read the status from all digital input ports | ++----------------------------------------------------------------------------+ +| Input Parameters : - | ++----------------------------------------------------------------------------+ +| Output Parameters : data[0] : Port 0 to 3 data | +| data[1] : Port 4 to 7 data | +| .... | ++----------------------------------------------------------------------------+ +| Return Value : 0: No error | +| -100 : Read command error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI16XX_InsnReadTTLIOAllPortValue(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + unsigned char b_Command = (unsigned char) CR_AREF(insn->chanspec); + int i_ReturnValue = insn->n; + unsigned char b_Cpt = 0; + unsigned char b_NumberOfPort = 0; + unsigned int *pls_ReadData = data; + + /********************/ + /* Test the command */ + /********************/ + + if ((b_Command == APCI16XX_TTL_READ_ALL_INPUTS) + || (b_Command == APCI16XX_TTL_READ_ALL_OUTPUTS)) { + /**********************************/ + /* Get the number of 32-Bit ports */ + /**********************************/ + + b_NumberOfPort = + (unsigned char) (this_board->i_NbrTTLChannel / 32); + if ((b_NumberOfPort * 32) < + this_board->i_NbrTTLChannel) { + b_NumberOfPort = b_NumberOfPort + 1; + } + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= b_NumberOfPort) { + if (b_Command == APCI16XX_TTL_READ_ALL_INPUTS) { + /**************************/ + /* Read all digital input */ + /**************************/ + + for (b_Cpt = 0; b_Cpt < b_NumberOfPort; b_Cpt++) { + /************************/ + /* Read the 32-Bit port */ + /************************/ + + pls_ReadData[b_Cpt] = + inl(devpriv->iobase + 8 + + (b_Cpt * 4)); + + /**************************************/ + /* Mask all channels used als outputs */ + /**************************************/ + + pls_ReadData[b_Cpt] = + pls_ReadData[b_Cpt] & + (~devpriv-> + ul_TTLPortConfiguration[b_Cpt]); + } + } else { + /****************************/ + /* Read all digital outputs */ + /****************************/ + + for (b_Cpt = 0; b_Cpt < b_NumberOfPort; b_Cpt++) { + /************************/ + /* Read the 32-Bit port */ + /************************/ + + pls_ReadData[b_Cpt] = + inl(devpriv->iobase + 20 + + (b_Cpt * 4)); + + /**************************************/ + /* Mask all channels used als outputs */ + /**************************************/ + + pls_ReadData[b_Cpt] = + pls_ReadData[b_Cpt] & devpriv-> + ul_TTLPortConfiguration[b_Cpt]; + } + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("\nBuffer size error"); + i_ReturnValue = -101; + } + } else { + /*****************/ + /* Command error */ + /*****************/ + + printk("\nCommand selection error"); + i_ReturnValue = -100; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| OUTPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI16XX_InsnBitsWriteTTLIO | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Set the state from selected TTL digital output | +| (b_OutputChannel) | ++----------------------------------------------------------------------------+ +| Task : Set the state from digital output port | +| (b_SelectedPort) | ++----------------------------------------------------------------------------+ +| Input Parameters : | +| APCI16XX_TTL_WRITECHANNEL_ON | APCI16XX_TTL_WRITECHANNEL_OFF | +| b_SelectedPort = CR_RANGE(insn->chanspec); | +| b_OutputChannel= CR_CHAN(insn->chanspec); | +| b_Command = (unsigned char) data[0]; | +| | +| APCI16XX_TTL_WRITEPORT_ON | APCI16XX_TTL_WRITEPORT_OFF | +| b_SelectedPort = CR_RANGE(insn->chanspec); | +| b_Command = (unsigned char) data[0]; | ++----------------------------------------------------------------------------+ +| Output Parameters : data[0] : TTL output port 0 to 3 data | +| data[1] : TTL output port 4 to 7 data | +| .... | ++----------------------------------------------------------------------------+ +| Return Value : >0 : No error | +| -100 : Command error | +| -101 : Data size error | +| -102 : The selected TTL output port is wrong | +| -103 : The selected TTL digital output is wrong | +| -104 : Output memory disabled | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI16XX_InsnBitsWriteTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_Command = 0; + unsigned char b_NumberOfPort = + (unsigned char) (this_board->i_NbrTTLChannel / 8); + unsigned char b_SelectedPort = CR_RANGE(insn->chanspec); + unsigned char b_OutputChannel = CR_CHAN(insn->chanspec); + unsigned int dw_Status = 0; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + /*******************/ + /* Get the command */ + /* **************** */ + + b_Command = (unsigned char) data[0]; + + /********************/ + /* Test the command */ + /********************/ + + if ((b_Command == APCI16XX_TTL_WRITECHANNEL_ON) || + (b_Command == APCI16XX_TTL_WRITEPORT_ON) || + (b_Command == APCI16XX_TTL_WRITECHANNEL_OFF) || + (b_Command == APCI16XX_TTL_WRITEPORT_OFF)) { + /**************************/ + /* Test the selected port */ + /**************************/ + + if (b_SelectedPort < b_NumberOfPort) { + /***********************/ + /* Test if output port */ + /***********************/ + + if (((devpriv->ul_TTLPortConfiguration + [b_SelectedPort / + 4] >> (8 * + (b_SelectedPort + % + 4))) & + 0xFF) == 0xFF) { + /***************************/ + /* Test the channel number */ + /***************************/ + + if (((b_Command == APCI16XX_TTL_WRITECHANNEL_ON) || (b_Command == APCI16XX_TTL_WRITECHANNEL_OFF)) && (b_OutputChannel > 7)) { + /********************************************/ + /* The selected TTL digital output is wrong */ + /********************************************/ + + printk("\nChannel selection error"); + i_ReturnValue = -103; + } + + if (((b_Command == APCI16XX_TTL_WRITECHANNEL_OFF) || (b_Command == APCI16XX_TTL_WRITEPORT_OFF)) && (devpriv->b_OutputMemoryStatus == ADDIDATA_DISABLE)) { + /********************************************/ + /* The selected TTL digital output is wrong */ + /********************************************/ + + printk("\nOutput memory disabled"); + i_ReturnValue = -104; + } + + /************************/ + /* Test the buffer size */ + /************************/ + + if (((b_Command == APCI16XX_TTL_WRITEPORT_ON) || (b_Command == APCI16XX_TTL_WRITEPORT_OFF)) && (insn->n < 2)) { + /*******************/ + /* Data size error */ + /*******************/ + + printk("\nBuffer size error"); + i_ReturnValue = -101; + } + } else { + /*****************************************/ + /* The selected TTL output port is wrong */ + /*****************************************/ + + printk("\nPort selection error %lX", + (unsigned long)devpriv-> + ul_TTLPortConfiguration[0]); + i_ReturnValue = -102; + } + } else { + /****************************************/ + /* The selected TTL output port is wrong */ + /****************************************/ + + printk("\nPort selection error %d %d", + b_SelectedPort, b_NumberOfPort); + i_ReturnValue = -102; + } + } else { + /************************/ + /* Config command error */ + /************************/ + + printk("\nCommand selection error"); + i_ReturnValue = -100; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("\nBuffer size error"); + i_ReturnValue = -101; + } + + /**************************/ + /* Test if no error occur */ + /**************************/ + + if (i_ReturnValue >= 0) { + /********************************/ + /* Get the digital output state */ + /********************************/ + + dw_Status = + inl(devpriv->iobase + 20 + ((b_SelectedPort / 4) * 4)); + + /**********************************/ + /* Test if output memory not used */ + /**********************************/ + + if (devpriv->b_OutputMemoryStatus == ADDIDATA_DISABLE) { + /*********************************/ + /* Clear the selected port value */ + /*********************************/ + + dw_Status = + dw_Status & (0xFFFFFFFFUL - + (0xFFUL << (8 * (b_SelectedPort % 4)))); + } + + /******************************/ + /* Test if setting channel ON */ + /******************************/ + + if (b_Command == APCI16XX_TTL_WRITECHANNEL_ON) { + dw_Status = + dw_Status | (1UL << ((8 * (b_SelectedPort % + 4)) + b_OutputChannel)); + } + + /***************************/ + /* Test if setting port ON */ + /***************************/ + + if (b_Command == APCI16XX_TTL_WRITEPORT_ON) { + dw_Status = + dw_Status | ((data[1] & 0xFF) << (8 * + (b_SelectedPort % 4))); + } + + /*******************************/ + /* Test if setting channel OFF */ + /*******************************/ + + if (b_Command == APCI16XX_TTL_WRITECHANNEL_OFF) { + dw_Status = + dw_Status & (0xFFFFFFFFUL - + (1UL << ((8 * (b_SelectedPort % 4)) + + b_OutputChannel))); + } + + /****************************/ + /* Test if setting port OFF */ + /****************************/ + + if (b_Command == APCI16XX_TTL_WRITEPORT_OFF) { + dw_Status = + dw_Status & (0xFFFFFFFFUL - + ((data[1] & 0xFF) << (8 * (b_SelectedPort % + 4)))); + } + + outl(dw_Status, + devpriv->iobase + 20 + ((b_SelectedPort / 4) * 4)); + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2200_Reset(struct comedi_device *dev) | +----------------------------------------------------------------------------+ +| Task :resets all the registers | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : - | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI16XX_Reset(struct comedi_device *dev) +{ + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci16xx.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci16xx.h new file mode 100644 index 00000000..5bf91e13 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci16xx.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data-com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#ifndef COMEDI_SUBD_TTLIO +#define COMEDI_SUBD_TTLIO 11 /* Digital Input Output But TTL */ +#endif + +#ifndef ADDIDATA_ENABLE +#define ADDIDATA_ENABLE 1 +#define ADDIDATA_DISABLE 0 +#endif + +#define APCI16XX_TTL_INIT 0 +#define APCI16XX_TTL_INITDIRECTION 1 +#define APCI16XX_TTL_OUTPUTMEMORY 2 + +#define APCI16XX_TTL_READCHANNEL 0 +#define APCI16XX_TTL_READPORT 1 + +#define APCI16XX_TTL_WRITECHANNEL_ON 0 +#define APCI16XX_TTL_WRITECHANNEL_OFF 1 +#define APCI16XX_TTL_WRITEPORT_ON 2 +#define APCI16XX_TTL_WRITEPORT_OFF 3 + +#define APCI16XX_TTL_READ_ALL_INPUTS 0 +#define APCI16XX_TTL_READ_ALL_OUTPUTS 1 + +#ifdef __KERNEL__ + +static const struct comedi_lrange range_apci16xx_ttl = { 12, + {BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1)} +}; + +/* ++----------------------------------------------------------------------------+ +| TTL INISIALISATION FUNCTION | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI16XX_InsnConfigInitTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); + +/* ++----------------------------------------------------------------------------+ +| TTL INPUT FUNCTION | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI16XX_InsnBitsReadTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); + +int i_APCI16XX_InsnReadTTLIOAllPortValue(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* ++----------------------------------------------------------------------------+ +| TTL OUTPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI16XX_InsnBitsWriteTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); + +int i_APCI16XX_Reset(struct comedi_device *dev); +#endif diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2016.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2016.c new file mode 100644 index 00000000..49dcbe24 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2016.c @@ -0,0 +1,460 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-2016 | Compiler : GCC | + | Module name : hwdrv_apci2016.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-2016 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ +#include "hwdrv_apci2016.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2016_ConfigDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Digital Output Subdevice. | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 1 Digital Memory On | +| 0 Digital Memory Off | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI2016_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if ((data[0] != 0) && (data[0] != 1)) { + comedi_error(dev, + "Not a valid Data !!! ,Data should be 1 or 0\n"); + return -EINVAL; + } /* if ((data[0]!=0) && (data[0]!=1)) */ + if (data[0]) { + devpriv->b_OutputMemoryStatus = ADDIDATA_ENABLE; + } /* if (data[0] */ + else { + devpriv->b_OutputMemoryStatus = ADDIDATA_DISABLE; + } /* else if (data[0] */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2016_WriteDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Writes port value To the selected port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To Write | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI2016_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_NoOfChannel; + unsigned int ui_Temp, ui_Temp1; + ui_NoOfChannel = CR_CHAN(insn->chanspec); + if (ui_NoOfChannel > 15) { + comedi_error(dev, + "Invalid Channel Numbers !!!, Channel Numbers must be between 0 and 15\n"); + return -EINVAL; + } /* if ((ui_NoOfChannel<0) || (ui_NoOfChannel>15)) */ + if (devpriv->b_OutputMemoryStatus) { + ui_Temp = inw(devpriv->iobase + APCI2016_DIGITAL_OP); + } /* if (devpriv->b_OutputMemoryStatus ) */ + else { + ui_Temp = 0; + } /* else if (devpriv->b_OutputMemoryStatus ) */ + if ((data[1] != 0) && (data[1] != 1)) { + comedi_error(dev, + "Invalid Data[1] value !!!, Data[1] should be 0 or 1\n"); + return -EINVAL; + } /* if ((data[1]!=0) && (data[1]!=1)) */ + + if (data[3] == 0) { + if (data[1] == 0) { + data[0] = (data[0] << ui_NoOfChannel) | ui_Temp; + outw(data[0], devpriv->iobase + APCI2016_DIGITAL_OP); + } /* if (data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + case 2: + data[0] = + (data[0] << (2 * + data[2])) | ui_Temp; + break; + case 4: + data[0] = + (data[0] << (4 * + data[2])) | ui_Temp; + break; + case 8: + data[0] = + (data[0] << (8 * + data[2])) | ui_Temp; + break; + case 15: + data[0] = data[0] | ui_Temp; + break; + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + } /* switch(ui_NoOfChannels) */ + outw(data[0], + devpriv->iobase + APCI2016_DIGITAL_OP); + } /* if (data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if (data[1]==1) */ + } /* else if (data[1]==0) */ + } /* if (data[3]==0) */ + else { + if (data[3] == 1) { + if (data[1] == 0) { + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = (data[0] << ui_NoOfChannel) ^ 0xffff; + data[0] = data[0] & ui_Temp; + outw(data[0], + devpriv->iobase + APCI2016_DIGITAL_OP); + } /* if (data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + case 2: + data[0] = ~data[0] & 0x3; + ui_Temp1 = 3; + ui_Temp1 = + ui_Temp1 << 2 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (2 * + data + [2])) ^ + 0xffff) & ui_Temp; + break; + case 4: + data[0] = ~data[0] & 0xf; + ui_Temp1 = 15; + ui_Temp1 = + ui_Temp1 << 4 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (4 * + data + [2])) ^ + 0xffff) & ui_Temp; + break; + case 8: + data[0] = ~data[0] & 0xff; + ui_Temp1 = 255; + ui_Temp1 = + ui_Temp1 << 8 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (8 * + data + [2])) ^ + 0xffff) & ui_Temp; + break; + case 15: + break; + default: + comedi_error(dev, + " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + } /* switch(ui_NoOfChannels) */ + outw(data[0], + devpriv->iobase + + APCI2016_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==1); */ + else { + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* if else data[3]==1) */ + } /* if else data[3]==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2016_BitsDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read value of the selected channel or port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI2016_BitsDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp; + unsigned int ui_NoOfChannel; + ui_NoOfChannel = CR_CHAN(insn->chanspec); + if (ui_NoOfChannel > 15) { + comedi_error(dev, + "Invalid Channel Numbers !!!, Channel Numbers must be between 0 and 15\n"); + return -EINVAL; + } /* if ((ui_NoOfChannel<0) || (ui_NoOfChannel>15)) */ + if ((data[0] != 0) && (data[0] != 1)) { + comedi_error(dev, + "Invalid Data[0] value !!!, Data[0] should be 0 or 1\n"); + return -EINVAL; + } /* if ((data[0]!=0) && (data[0]!=1)) */ + ui_Temp = data[0]; + *data = inw(devpriv->iobase + APCI2016_DIGITAL_OP_RW); + if (ui_Temp == 0) { + *data = (*data >> ui_NoOfChannel) & 0x1; + } /* if (ui_Temp==0) */ + else { + if (ui_Temp == 1) { + switch (ui_NoOfChannel) { + case 2: + *data = (*data >> (2 * data[1])) & 3; + break; + + case 4: + *data = (*data >> (4 * data[1])) & 15; + break; + + case 8: + *data = (*data >> (8 * data[1])) & 255; + break; + + case 15: + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + } /* switch(ui_NoOfChannel) */ + } /* if (ui_Temp==1) */ + else { + printk("\nSpecified channel not supported \n"); + } /* else if (ui_Temp==1) */ + } /* if (ui_Temp==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2016_ConfigWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure | +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI2016_ConfigWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + if (data[0] == 0) { + /* Disable the watchdog */ + outw(0x0, + devpriv->i_IobaseAddon + + APCI2016_WATCHDOG_ENABLEDISABLE); + /* Loading the Reload value */ + outw(data[1], + devpriv->i_IobaseAddon + + APCI2016_WATCHDOG_RELOAD_VALUE); + data[1] = data[1] >> 16; + outw(data[1], + devpriv->i_IobaseAddon + + APCI2016_WATCHDOG_RELOAD_VALUE + 2); + } else { + printk("\nThe input parameters are wrong\n"); + } + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2016_StartStopWriteWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Start / Stop The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure | +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI2016_StartStopWriteWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + switch (data[0]) { + case 0: /* stop the watchdog */ + outw(0x0, devpriv->i_IobaseAddon + APCI2016_WATCHDOG_ENABLEDISABLE); /* disable the watchdog */ + break; + case 1: /* start the watchdog */ + outw(0x0001, + devpriv->i_IobaseAddon + + APCI2016_WATCHDOG_ENABLEDISABLE); + break; + case 2: /* Software trigger */ + outw(0x0201, + devpriv->i_IobaseAddon + + APCI2016_WATCHDOG_ENABLEDISABLE); + break; + default: + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2016_ReadWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure | +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2016_ReadWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + udelay(5); + data[0] = inw(devpriv->i_IobaseAddon + APCI2016_WATCHDOG_STATUS) & 0x1; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2016_Reset(struct comedi_device *dev) | | ++----------------------------------------------------------------------------+ +| Task :resets all the registers | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2016_Reset(struct comedi_device *dev) +{ + outw(0x0, devpriv->iobase + APCI2016_DIGITAL_OP); /* Resets the digital output channels */ + outw(0x0, devpriv->i_IobaseAddon + APCI2016_WATCHDOG_ENABLEDISABLE); + outw(0x0, devpriv->i_IobaseAddon + APCI2016_WATCHDOG_RELOAD_VALUE); + outw(0x0, devpriv->i_IobaseAddon + APCI2016_WATCHDOG_RELOAD_VALUE + 2); + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2016.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2016.h new file mode 100644 index 00000000..c42612af --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2016.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ +/********* Definitions for APCI-2016 card *****/ + +#define APCI2016_BOARD_VENDOR_ID 0x15B8 +#define APCI2016_ADDRESS_RANGE 8 + +/* DIGITAL INPUT-OUTPUT DEFINE */ + +#define APCI2016_DIGITAL_OP 0x04 +#define APCI2016_DIGITAL_OP_RW 4 + +/* ADDIDATA Enable Disable */ + +#define ADDIDATA_ENABLE 1 +#define ADDIDATA_DISABLE 0 + +/* TIMER COUNTER WATCHDOG DEFINES */ + +#define ADDIDATA_WATCHDOG 2 +#define APCI2016_DIGITAL_OP_WATCHDOG 0 +#define APCI2016_WATCHDOG_ENABLEDISABLE 12 +#define APCI2016_WATCHDOG_RELOAD_VALUE 4 +#define APCI2016_WATCHDOG_STATUS 16 + +/* Hardware Layer functions for Apci2016 */ + +/* DO */ +int i_APCI2016_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI2016_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI2016_BitsDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* +* TIMER +* timer value is passed as u seconds +*/ + +int i_APCI2016_ConfigWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI2016_StartStopWriteWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +int i_APCI2016_ReadWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* Interrupt functions..... */ + +/* void v_APCI2016_Interrupt(int irq, void *d); */ + +/* void v_APCI2016_Interrupt(int irq, void *d); */ +/* RESET */ +int i_APCI2016_Reset(struct comedi_device *dev); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2032.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2032.c new file mode 100644 index 00000000..002297df --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2032.c @@ -0,0 +1,579 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-2032 | Compiler : GCC | + | Module name : hwdrv_apci2032.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-2032 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ + +#include "hwdrv_apci2032.h" +static unsigned int ui_InterruptData, ui_Type; +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2032_ConfigDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Digital Output Subdevice. | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[1] : 1 Enable VCC Interrupt | +| 0 Disable VCC Interrupt | +| data[2] : 1 Enable CC Interrupt | +| 0 Disable CC Interrupt | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI2032_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ul_Command = 0; + devpriv->tsk_Current = current; + + if ((data[0] != 0) && (data[0] != 1)) { + comedi_error(dev, + "Not a valid Data !!! ,Data should be 1 or 0\n"); + return -EINVAL; + } /* if ( (data[0]!=0) && (data[0]!=1) ) */ + if (data[0]) { + devpriv->b_OutputMemoryStatus = ADDIDATA_ENABLE; + } /* if (data[0]) */ + else { + devpriv->b_OutputMemoryStatus = ADDIDATA_DISABLE; + } /* else if (data[0]) */ + + if (data[1] == ADDIDATA_ENABLE) { + ul_Command = ul_Command | 0x1; + } /* if (data[1] == ADDIDATA_ENABLE) */ + else { + ul_Command = ul_Command & 0xFFFFFFFE; + } /* elseif (data[1] == ADDIDATA_ENABLE) */ + if (data[2] == ADDIDATA_ENABLE) { + ul_Command = ul_Command | 0x2; + } /* if (data[2] == ADDIDATA_ENABLE) */ + else { + ul_Command = ul_Command & 0xFFFFFFFD; + } /* elseif (data[2] == ADDIDATA_ENABLE) */ + outl(ul_Command, devpriv->iobase + APCI2032_DIGITAL_OP_INTERRUPT); + ui_InterruptData = inl(devpriv->iobase + APCI2032_DIGITAL_OP_INTERRUPT); + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2032_WriteDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Writes port value To the selected port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To Write | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2032_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp, ui_Temp1; + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + if (devpriv->b_OutputMemoryStatus) { + ui_Temp = inl(devpriv->iobase + APCI2032_DIGITAL_OP); + + } /* if(devpriv->b_OutputMemoryStatus ) */ + else { + ui_Temp = 0; + } /* if(devpriv->b_OutputMemoryStatus ) */ + if (data[3] == 0) { + if (data[1] == 0) { + data[0] = (data[0] << ui_NoOfChannel) | ui_Temp; + outl(data[0], devpriv->iobase + APCI2032_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = + (data[0] << (2 * + data[2])) | ui_Temp; + break; + + case 4: + data[0] = + (data[0] << (4 * + data[2])) | ui_Temp; + break; + + case 8: + data[0] = + (data[0] << (8 * + data[2])) | ui_Temp; + break; + + case 16: + data[0] = + (data[0] << (16 * + data[2])) | ui_Temp; + break; + case 31: + data[0] = data[0] | ui_Temp; + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outl(data[0], + devpriv->iobase + APCI2032_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==0) */ + else { + if (data[3] == 1) { + if (data[1] == 0) { + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + (data[0] << ui_NoOfChannel) ^ + 0xffffffff; + data[0] = data[0] & ui_Temp; + outl(data[0], + devpriv->iobase + APCI2032_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = ~data[0] & 0x3; + ui_Temp1 = 3; + ui_Temp1 = + ui_Temp1 << 2 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (2 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 4: + data[0] = ~data[0] & 0xf; + ui_Temp1 = 15; + ui_Temp1 = + ui_Temp1 << 4 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (4 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 8: + data[0] = ~data[0] & 0xff; + ui_Temp1 = 255; + ui_Temp1 = + ui_Temp1 << 8 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (8 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 16: + data[0] = ~data[0] & 0xffff; + ui_Temp1 = 65535; + ui_Temp1 = + ui_Temp1 << 16 * + data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (16 * + data + [2])) ^ + 0xffffffff) & ui_Temp; + break; + + case 31: + break; + default: + comedi_error(dev, + " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outl(data[0], + devpriv->iobase + + APCI2032_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==1); */ + else { + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* if else data[3]==1) */ + } /* if else data[3]==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2032_ReadDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read value of the selected channel or port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2032_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp; + unsigned int ui_NoOfChannel; + ui_NoOfChannel = CR_CHAN(insn->chanspec); + ui_Temp = data[0]; + *data = inl(devpriv->iobase + APCI2032_DIGITAL_OP_RW); + if (ui_Temp == 0) { + *data = (*data >> ui_NoOfChannel) & 0x1; + } /* if (ui_Temp==0) */ + else { + if (ui_Temp == 1) { + switch (ui_NoOfChannel) { + + case 2: + *data = (*data >> (2 * data[1])) & 3; + break; + + case 4: + *data = (*data >> (4 * data[1])) & 15; + break; + + case 8: + *data = (*data >> (8 * data[1])) & 255; + break; + + case 16: + *data = (*data >> (16 * data[1])) & 65535; + break; + + case 31: + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + } /* if (ui_Temp==1) */ + else { + printk("\nSpecified channel not supported \n"); + } /* elseif (ui_Temp==1) */ + } + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2032_ConfigWatchdog(comedi_device +| *dev,struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data)| +| | ++----------------------------------------------------------------------------+ +| Task : Configures The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI2032_ConfigWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0] == 0) { + /* Disable the watchdog */ + outl(0x0, + devpriv->iobase + APCI2032_DIGITAL_OP_WATCHDOG + + APCI2032_TCW_PROG); + /* Loading the Reload value */ + outl(data[1], + devpriv->iobase + APCI2032_DIGITAL_OP_WATCHDOG + + APCI2032_TCW_RELOAD_VALUE); + } else { + printk("\nThe input parameters are wrong\n"); + return -EINVAL; + } + + return insn->n; +} + + /* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI2032_StartStopWriteWatchdog | + | (struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data); | + +----------------------------------------------------------------------------+ + | Task : Start / Stop The Watchdog | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | struct comedi_subdevice *s, :pointer to subdevice structure + struct comedi_insn *insn :pointer to insn structure | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ + */ + +int i_APCI2032_StartStopWriteWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + switch (data[0]) { + case 0: /* stop the watchdog */ + outl(0x0, devpriv->iobase + APCI2032_DIGITAL_OP_WATCHDOG + APCI2032_TCW_PROG); /* disable the watchdog */ + break; + case 1: /* start the watchdog */ + outl(0x0001, + devpriv->iobase + APCI2032_DIGITAL_OP_WATCHDOG + + APCI2032_TCW_PROG); + break; + case 2: /* Software trigger */ + outl(0x0201, + devpriv->iobase + APCI2032_DIGITAL_OP_WATCHDOG + + APCI2032_TCW_PROG); + break; + default: + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2032_ReadWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, +| unsigned int *data); | ++----------------------------------------------------------------------------+ +| Task : Read The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2032_ReadWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + data[0] = + inl(devpriv->iobase + APCI2032_DIGITAL_OP_WATCHDOG + + APCI2032_TCW_TRIG_STATUS) & 0x1; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : void v_APCI2032_Interrupt | +| (int irq , void *d) | ++----------------------------------------------------------------------------+ +| Task : Writes port value To the selected port | ++----------------------------------------------------------------------------+ +| Input Parameters : int irq : irq number | +| void *d : void pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +void v_APCI2032_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned int ui_DO; + + ui_DO = inl(devpriv->iobase + APCI2032_DIGITAL_OP_IRQ) & 0x1; /* Check if VCC OR CC interrupt has occurred. */ + + if (ui_DO == 0) { + printk("\nInterrupt from unKnown source\n"); + } /* if(ui_DO==0) */ + if (ui_DO) { + /* Check for Digital Output interrupt Type - 1: Vcc interrupt 2: CC interrupt. */ + ui_Type = + inl(devpriv->iobase + + APCI2032_DIGITAL_OP_INTERRUPT_STATUS) & 0x3; + outl(0x0, + devpriv->iobase + APCI2032_DIGITAL_OP + + APCI2032_DIGITAL_OP_INTERRUPT); + if (ui_Type == 1) { + /* Sends signal to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + } /* if (ui_Type==1) */ + else { + if (ui_Type == 2) { + /* Sends signal to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + } /* if (ui_Type==2) */ + } /* else if (ui_Type==1) */ + } /* if(ui_DO) */ + + return; + +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2032_ReadInterruptStatus | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task :Reads the interrupt status register | ++----------------------------------------------------------------------------+ +| Input Parameters : | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2032_ReadInterruptStatus(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + *data = ui_Type; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2032_Reset(struct comedi_device *dev) | +| | ++----------------------------------------------------------------------------+ +| Task :Resets the registers of the card | ++----------------------------------------------------------------------------+ +| Input Parameters : | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2032_Reset(struct comedi_device *dev) +{ + devpriv->b_DigitalOutputRegister = 0; + ui_Type = 0; + outl(0x0, devpriv->iobase + APCI2032_DIGITAL_OP); /* Resets the output channels */ + outl(0x0, devpriv->iobase + APCI2032_DIGITAL_OP_INTERRUPT); /* Disables the interrupt. */ + outl(0x0, devpriv->iobase + APCI2032_DIGITAL_OP_WATCHDOG + APCI2032_TCW_PROG); /* disable the watchdog */ + outl(0x0, devpriv->iobase + APCI2032_DIGITAL_OP_WATCHDOG + APCI2032_TCW_RELOAD_VALUE); /* reload=0 */ + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2032.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2032.h new file mode 100644 index 00000000..ab145e7c --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2032.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/********* Definitions for APCI-2032 card *****/ + +/* Card Specific information */ +#define APCI2032_BOARD_VENDOR_ID 0x15B8 +#define APCI2032_ADDRESS_RANGE 63 + +/* DIGITAL INPUT-OUTPUT DEFINE */ + +#define APCI2032_DIGITAL_OP 0 +#define APCI2032_DIGITAL_OP_RW 0 +#define APCI2032_DIGITAL_OP_INTERRUPT 4 +#define APCI2032_DIGITAL_OP_IRQ 12 + +/* Digital Output Interrupt Status */ +#define APCI2032_DIGITAL_OP_INTERRUPT_STATUS 8 + +/* Digital Output Interrupt Enable Disable. */ +#define APCI2032_DIGITAL_OP_VCC_INTERRUPT_ENABLE 0x1 +#define APCI2032_DIGITAL_OP_VCC_INTERRUPT_DISABLE 0xFFFFFFFE +#define APCI2032_DIGITAL_OP_CC_INTERRUPT_ENABLE 0x2 +#define APCI2032_DIGITAL_OP_CC_INTERRUPT_DISABLE 0xFFFFFFFD + +/* ADDIDATA Enable Disable */ + +#define ADDIDATA_ENABLE 1 +#define ADDIDATA_DISABLE 0 + +/* TIMER COUNTER WATCHDOG DEFINES */ + +#define ADDIDATA_WATCHDOG 2 +#define APCI2032_DIGITAL_OP_WATCHDOG 16 +#define APCI2032_TCW_RELOAD_VALUE 4 +#define APCI2032_TCW_TIMEBASE 8 +#define APCI2032_TCW_PROG 12 +#define APCI2032_TCW_TRIG_STATUS 16 +#define APCI2032_TCW_IRQ 20 + +/* Hardware Layer functions for Apci2032 */ + +/* DO */ +int i_APCI2032_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2032_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2032_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2032_ReadInterruptStatus(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* TIMER + * timer value is passed as u seconds +*/ + +int i_APCI2032_ConfigWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2032_StartStopWriteWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2032_ReadWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* Interrupt functions..... */ + +void v_APCI2032_Interrupt(int irq, void *d); + +/* Reset functions */ +int i_APCI2032_Reset(struct comedi_device *dev); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2200.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2200.c new file mode 100644 index 00000000..3d378b5e --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2200.c @@ -0,0 +1,549 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-2200 | Compiler : GCC | + | Module name : hwdrv_apci2200.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-2200 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ +#include "hwdrv_apci2200.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2200_Read1DigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the digital input | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI2200_Read1DigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_TmpValue = 0; + unsigned int ui_Channel; + ui_Channel = CR_CHAN(insn->chanspec); + if (ui_Channel <= 7) { + ui_TmpValue = (unsigned int) inw(devpriv->iobase + APCI2200_DIGITAL_IP); + *data = (ui_TmpValue >> ui_Channel) & 0x1; + } /* if(ui_Channel >= 0 && ui_Channel <=7) */ + else { + printk("\nThe specified channel does not exist\n"); + return -EINVAL; /* "sorry channel spec wrong " */ + } /* else if(ui_Channel >= 0 && ui_Channel <=7) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2200_ReadMoreDigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Return the status of the Requested digital inputs | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2200_ReadMoreDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + unsigned int ui_PortValue = data[0]; + unsigned int ui_Mask = 0; + unsigned int ui_NoOfChannels; + + ui_NoOfChannels = CR_CHAN(insn->chanspec); + + *data = (unsigned int) inw(devpriv->iobase + APCI2200_DIGITAL_IP); + switch (ui_NoOfChannels) { + case 2: + ui_Mask = 3; + *data = (*data >> (2 * ui_PortValue)) & ui_Mask; + break; + case 4: + ui_Mask = 15; + *data = (*data >> (4 * ui_PortValue)) & ui_Mask; + break; + case 7: + break; + + default: + printk("\nWrong parameters\n"); + return -EINVAL; /* "sorry channel spec wrong " */ + break; + } /* switch(ui_NoOfChannels) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2200_ConfigDigitalOutput (struct comedi_device *dev, +| struct comedi_subdevice *s struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : Configures The Digital Output Subdevice. | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| data[0] :1:Memory on | +| 0:Memory off | +| | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI2200_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + devpriv->b_OutputMemoryStatus = data[0]; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2200_WriteDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Writes port value To the selected port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2200_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp, ui_Temp1; + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + if (devpriv->b_OutputMemoryStatus) { + ui_Temp = inw(devpriv->iobase + APCI2200_DIGITAL_OP); + + } /* if(devpriv->b_OutputMemoryStatus ) */ + else { + ui_Temp = 0; + } /* if(devpriv->b_OutputMemoryStatus ) */ + if (data[3] == 0) { + if (data[1] == 0) { + data[0] = (data[0] << ui_NoOfChannel) | ui_Temp; + outw(data[0], devpriv->iobase + APCI2200_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = + (data[0] << (2 * + data[2])) | ui_Temp; + break; + + case 4: + data[0] = + (data[0] << (4 * + data[2])) | ui_Temp; + break; + + case 8: + data[0] = + (data[0] << (8 * + data[2])) | ui_Temp; + break; + case 15: + data[0] = data[0] | ui_Temp; + break; + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outw(data[0], + devpriv->iobase + APCI2200_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==0) */ + else { + if (data[3] == 1) { + if (data[1] == 0) { + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = (data[0] << ui_NoOfChannel) ^ 0xffff; + data[0] = data[0] & ui_Temp; + outw(data[0], + devpriv->iobase + APCI2200_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = ~data[0] & 0x3; + ui_Temp1 = 3; + ui_Temp1 = + ui_Temp1 << 2 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (2 * + data + [2])) ^ + 0xffff) & ui_Temp; + break; + + case 4: + data[0] = ~data[0] & 0xf; + ui_Temp1 = 15; + ui_Temp1 = + ui_Temp1 << 4 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (4 * + data + [2])) ^ + 0xffff) & ui_Temp; + break; + + case 8: + data[0] = ~data[0] & 0xff; + ui_Temp1 = 255; + ui_Temp1 = + ui_Temp1 << 8 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (8 * + data + [2])) ^ + 0xffff) & ui_Temp; + break; + case 15: + break; + + default: + comedi_error(dev, + " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + + outw(data[0], + devpriv->iobase + + APCI2200_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==1); */ + else { + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* if else data[3]==1) */ + } /* if else data[3]==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2200_ReadDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read value of the selected channel or port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2200_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + unsigned int ui_Temp; + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + ui_Temp = data[0]; + *data = inw(devpriv->iobase + APCI2200_DIGITAL_OP); + if (ui_Temp == 0) { + *data = (*data >> ui_NoOfChannel) & 0x1; + } /* if(ui_Temp==0) */ + else { + if (ui_Temp == 1) { + switch (ui_NoOfChannel) { + + case 2: + *data = (*data >> (2 * data[1])) & 3; + break; + + case 4: + *data = (*data >> (4 * data[1])) & 15; + break; + + case 8: + *data = (*data >> (8 * data[1])) & 255; + break; + + case 15: + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + } /* if(ui_Temp==1) */ + else { + printk("\nSpecified channel not supported \n"); + } /* elseif(ui_Temp==1) */ + } /* elseif(ui_Temp==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2200_ConfigWatchdog(struct comedi_device *dev, +| struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : Configures The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2200_ConfigWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0] == 0) { + /* Disable the watchdog */ + outw(0x0, + devpriv->iobase + APCI2200_WATCHDOG + + APCI2200_WATCHDOG_ENABLEDISABLE); + /* Loading the Reload value */ + outw(data[1], + devpriv->iobase + APCI2200_WATCHDOG + + APCI2200_WATCHDOG_RELOAD_VALUE); + data[1] = data[1] >> 16; + outw(data[1], + devpriv->iobase + APCI2200_WATCHDOG + + APCI2200_WATCHDOG_RELOAD_VALUE + 2); + } /* if(data[0]==0) */ + else { + printk("\nThe input parameters are wrong\n"); + return -EINVAL; + } /* elseif(data[0]==0) */ + + return insn->n; +} + + /* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI2200_StartStopWriteWatchdog | + | (struct comedi_device *dev,struct comedi_subdevice *s, + struct comedi_insn *insn,unsigned int *data); | + +----------------------------------------------------------------------------+ + | Task : Start / Stop The Watchdog | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | struct comedi_subdevice *s, :pointer to subdevice structure + struct comedi_insn *insn :pointer to insn structure | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ + */ + +int i_APCI2200_StartStopWriteWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + switch (data[0]) { + case 0: /* stop the watchdog */ + outw(0x0, devpriv->iobase + APCI2200_WATCHDOG + APCI2200_WATCHDOG_ENABLEDISABLE); /* disable the watchdog */ + break; + case 1: /* start the watchdog */ + outw(0x0001, + devpriv->iobase + APCI2200_WATCHDOG + + APCI2200_WATCHDOG_ENABLEDISABLE); + break; + case 2: /* Software trigger */ + outw(0x0201, + devpriv->iobase + APCI2200_WATCHDOG + + APCI2200_WATCHDOG_ENABLEDISABLE); + break; + default: + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* switch(data[0]) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2200_ReadWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn, +| unsigned int *data); | ++----------------------------------------------------------------------------+ +| Task : Read The Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s, :pointer to subdevice structure +| struct comedi_insn *insn :pointer to insn structure | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2200_ReadWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = + inw(devpriv->iobase + APCI2200_WATCHDOG + + APCI2200_WATCHDOG_STATUS) & 0x1; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI2200_Reset(struct comedi_device *dev) | | ++----------------------------------------------------------------------------+ +| Task :resets all the registers | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI2200_Reset(struct comedi_device *dev) +{ + outw(0x0, devpriv->iobase + APCI2200_DIGITAL_OP); /* RESETS THE DIGITAL OUTPUTS */ + outw(0x0, + devpriv->iobase + APCI2200_WATCHDOG + + APCI2200_WATCHDOG_ENABLEDISABLE); + outw(0x0, + devpriv->iobase + APCI2200_WATCHDOG + + APCI2200_WATCHDOG_RELOAD_VALUE); + outw(0x0, + devpriv->iobase + APCI2200_WATCHDOG + + APCI2200_WATCHDOG_RELOAD_VALUE + 2); + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2200.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2200.h new file mode 100644 index 00000000..83f42af8 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci2200.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/********* Definitions for APCI-2200 card *****/ + +/* Card Specific information */ +#define APCI2200_BOARD_VENDOR_ID 0x15b8 +#define APCI2200_ADDRESS_RANGE 64 + +/* DIGITAL INPUT-OUTPUT DEFINE */ + +#define APCI2200_DIGITAL_OP 4 +#define APCI2200_DIGITAL_IP 0 + +/* TIMER COUNTER WATCHDOG DEFINES */ + +#define APCI2200_WATCHDOG 0x08 +#define APCI2200_WATCHDOG_ENABLEDISABLE 12 +#define APCI2200_WATCHDOG_RELOAD_VALUE 4 +#define APCI2200_WATCHDOG_STATUS 16 + +/* Hardware Layer functions for Apci2200 */ + +/* Digital Input */ +int i_APCI2200_ReadMoreDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2200_Read1DigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* Digital Output */ +int i_APCI2200_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2200_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2200_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* TIMER */ +int i_APCI2200_ConfigWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2200_StartStopWriteWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI2200_ReadWatchdog(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* reset */ +int i_APCI2200_Reset(struct comedi_device *dev); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.c new file mode 100644 index 00000000..e886ced4 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.c @@ -0,0 +1,2643 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstrasse 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : APCI-3120 | Compiler : GCC | + | Module name : hwdrv_apci3120.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-----------------------------------------------------------------------+ + | Description :APCI3120 Module. Hardware abstraction Layer for APCI3120| + +-----------------------------------------------------------------------+ + | UPDATE'S | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +#include "hwdrv_apci3120.h" +static unsigned int ui_Temp; + +/* FUNCTION DEFINITIONS */ + +/* ++----------------------------------------------------------------------------+ +| ANALOG INPUT SUBDEVICE | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnConfigAnalogInput(struct comedi_device *dev,| +| struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : Calls card specific function | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_InsnConfigAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int i; + + if ((data[0] != APCI3120_EOC_MODE) && (data[0] != APCI3120_EOS_MODE)) + return -1; + + /* Check for Conversion time to be added ?? */ + devpriv->ui_EocEosConversionTime = data[2]; + + if (data[0] == APCI3120_EOS_MODE) { + + /* Test the number of the channel */ + for (i = 0; i < data[3]; i++) { + + if (CR_CHAN(data[4 + i]) >= + devpriv->s_EeParameters.i_NbrAiChannel) { + printk("bad channel list\n"); + return -2; + } + } + + devpriv->b_InterruptMode = APCI3120_EOS_MODE; + + if (data[1]) + devpriv->b_EocEosInterrupt = APCI3120_ENABLE; + else + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + /* Copy channel list and Range List to devpriv */ + + devpriv->ui_AiNbrofChannels = data[3]; + for (i = 0; i < devpriv->ui_AiNbrofChannels; i++) + devpriv->ui_AiChannelList[i] = data[4 + i]; + + } else { /* EOC */ + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + if (data[1]) + devpriv->b_EocEosInterrupt = APCI3120_ENABLE; + else + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + } + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnReadAnalogInput(struct comedi_device *dev, | +| struct comedi_subdevice *s,struct comedi_insn *insn, unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : card specific function | +| Reads analog input in synchronous mode | +| EOC and EOS is selected as per configured | +| if no conversion time is set uses default conversion | +| time 10 microsec. | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_InsnReadAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned short us_ConvertTiming, us_TmpValue, i; + unsigned char b_Tmp; + + /* fix conversion time to 10 us */ + if (!devpriv->ui_EocEosConversionTime) { + printk("No timer0 Value using 10 us\n"); + us_ConvertTiming = 10; + } else + us_ConvertTiming = (unsigned short) (devpriv->ui_EocEosConversionTime / 1000); /* nano to useconds */ + + /* this_board->i_hwdrv_InsnReadAnalogInput(dev,us_ConvertTiming,insn->n,&insn->chanspec,data,insn->unused[0]); */ + + /* Clear software registers */ + devpriv->b_TimerSelectMode = 0; + devpriv->b_ModeSelectRegister = 0; + devpriv->us_OutputRegister = 0; +/* devpriv->b_DigitalOutputRegister=0; */ + + if (insn->unused[0] == 222) { /* second insn read */ + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ui_AiReadData[i]; + } else { + devpriv->tsk_Current = current; /* Save the current process task structure */ +/* + * Testing if board have the new Quartz and calculate the time value + * to set in the timer + */ + + us_TmpValue = + (unsigned short) inw(devpriv->iobase + APCI3120_RD_STATUS); + + /* EL250804: Testing if board APCI3120 have the new Quartz or if it is an APCI3001 */ + if ((us_TmpValue & 0x00B0) == 0x00B0 + || !strcmp(this_board->pc_DriverName, "apci3001")) { + us_ConvertTiming = (us_ConvertTiming * 2) - 2; + } else { + us_ConvertTiming = + ((us_ConvertTiming * 12926) / 10000) - 1; + } + + us_TmpValue = (unsigned short) devpriv->b_InterruptMode; + + switch (us_TmpValue) { + + case APCI3120_EOC_MODE: + +/* + * Testing the interrupt flag and set the EOC bit Clears the FIFO + */ + inw(devpriv->iobase + APCI3120_RESET_FIFO); + + /* Initialize the sequence array */ + + /* if (!i_APCI3120_SetupChannelList(dev,s,1,chanlist,0)) return -EINVAL; */ + + if (!i_APCI3120_SetupChannelList(dev, s, 1, + &insn->chanspec, 0)) + return -EINVAL; + + /* Initialize Timer 0 mode 4 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xFC) | + APCI3120_TIMER_0_MODE_4; + outb(devpriv->b_TimerSelectMode, + devpriv->iobase + APCI3120_TIMER_CRT1); + + /* Reset the scan bit and Disables the EOS, DMA, EOC interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_SCAN; + + if (devpriv->b_EocEosInterrupt == APCI3120_ENABLE) { + + /* Disables the EOS,DMA and enables the EOC interrupt */ + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_EOS_INT) | + APCI3120_ENABLE_EOC_INT; + inw(devpriv->iobase); + + } else { + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_ALL_INTERRUPT_WITHOUT_TIMER; + } + + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + /* Sets gate 0 */ + devpriv->us_OutputRegister = + (devpriv-> + us_OutputRegister & APCI3120_CLEAR_PA_PR) | + APCI3120_ENABLE_TIMER0; + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + + /* Select Timer 0 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_0_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + /* Set the conversion time */ + outw(us_ConvertTiming, + devpriv->iobase + APCI3120_TIMER_VALUE); + + us_TmpValue = + (unsigned short) inw(dev->iobase + APCI3120_RD_STATUS); + + if (devpriv->b_EocEosInterrupt == APCI3120_DISABLE) { + + do { + /* Waiting for the end of conversion */ + us_TmpValue = + inw(devpriv->iobase + + APCI3120_RD_STATUS); + } while ((us_TmpValue & APCI3120_EOC) == + APCI3120_EOC); + + /* Read the result in FIFO and put it in insn data pointer */ + us_TmpValue = inw(devpriv->iobase + 0); + *data = us_TmpValue; + + inw(devpriv->iobase + APCI3120_RESET_FIFO); + } + + break; + + case APCI3120_EOS_MODE: + + inw(devpriv->iobase); + /* Clears the FIFO */ + inw(devpriv->iobase + APCI3120_RESET_FIFO); + /* clear PA PR and disable timer 0 */ + + devpriv->us_OutputRegister = + (devpriv-> + us_OutputRegister & APCI3120_CLEAR_PA_PR) | + APCI3120_DISABLE_TIMER0; + + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + + if (!i_APCI3120_SetupChannelList(dev, s, + devpriv->ui_AiNbrofChannels, + devpriv->ui_AiChannelList, 0)) + return -EINVAL; + + /* Initialize Timer 0 mode 2 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xFC) | + APCI3120_TIMER_0_MODE_2; + outb(devpriv->b_TimerSelectMode, + devpriv->iobase + APCI3120_TIMER_CRT1); + + /* Select Timer 0 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_0_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + /* Set the conversion time */ + outw(us_ConvertTiming, + devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Set the scan bit */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | APCI3120_ENABLE_SCAN; + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + /* If Interrupt function is loaded */ + if (devpriv->b_EocEosInterrupt == APCI3120_ENABLE) { + /* Disables the EOC,DMA and enables the EOS interrupt */ + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_EOC_INT) | + APCI3120_ENABLE_EOS_INT; + inw(devpriv->iobase); + + } else + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_ALL_INTERRUPT_WITHOUT_TIMER; + + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + inw(devpriv->iobase + APCI3120_RD_STATUS); + + /* Sets gate 0 */ + + devpriv->us_OutputRegister = + devpriv-> + us_OutputRegister | APCI3120_ENABLE_TIMER0; + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + + /* Start conversion */ + outw(0, devpriv->iobase + APCI3120_START_CONVERSION); + + /* Waiting of end of conversion if interrupt is not installed */ + if (devpriv->b_EocEosInterrupt == APCI3120_DISABLE) { + /* Waiting the end of conversion */ + do { + us_TmpValue = + inw(devpriv->iobase + + APCI3120_RD_STATUS); + } while ((us_TmpValue & APCI3120_EOS) != + APCI3120_EOS); + + for (i = 0; i < devpriv->ui_AiNbrofChannels; + i++) { + /* Read the result in FIFO and write them in shared memory */ + us_TmpValue = inw(devpriv->iobase); + data[i] = (unsigned int) us_TmpValue; + } + + devpriv->b_InterruptMode = APCI3120_EOC_MODE; /* Restore defaults. */ + } + break; + + default: + printk("inputs wrong\n"); + + } + devpriv->ui_EocEosConversionTime = 0; /* re initializing the variable; */ + } + + return insn->n; + +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_StopCyclicAcquisition(struct comedi_device *dev,| +| struct comedi_subdevice *s)| +| | ++----------------------------------------------------------------------------+ +| Task : Stops Cyclic acquisition | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| | ++----------------------------------------------------------------------------+ +| Return Value :0 | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_StopCyclicAcquisition(struct comedi_device *dev, struct comedi_subdevice *s) +{ + /* Disable A2P Fifo write and AMWEN signal */ + outw(0, devpriv->i_IobaseAddon + 4); + + /* Disable Bus Master ADD ON */ + outw(APCI3120_ADD_ON_AGCSTS_LOW, devpriv->i_IobaseAddon + 0); + outw(0, devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_AGCSTS_HIGH, devpriv->i_IobaseAddon + 0); + outw(0, devpriv->i_IobaseAddon + 2); + + /* Disable BUS Master PCI */ + outl(0, devpriv->i_IobaseAmcc + AMCC_OP_REG_MCSR); + + /* outl(inl(devpriv->i_IobaseAmcc+AMCC_OP_REG_INTCSR)&(~AINT_WRITE_COMPL), + * devpriv->i_IobaseAmcc+AMCC_OP_REG_INTCSR); stop amcc irqs */ + + /* outl(inl(devpriv->i_IobaseAmcc+AMCC_OP_REG_MCSR)&(~EN_A2P_TRANSFERS), + * devpriv->i_IobaseAmcc+AMCC_OP_REG_MCSR); stop DMA */ + + /* Disable ext trigger */ + i_APCI3120_ExttrigDisable(dev); + + devpriv->us_OutputRegister = 0; + /* stop counters */ + outw(devpriv-> + us_OutputRegister & APCI3120_DISABLE_TIMER0 & + APCI3120_DISABLE_TIMER1, dev->iobase + APCI3120_WR_ADDRESS); + + outw(APCI3120_DISABLE_ALL_TIMER, dev->iobase + APCI3120_WR_ADDRESS); + + /* DISABLE_ALL_INTERRUPT */ + outb(APCI3120_DISABLE_ALL_INTERRUPT, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + /* Flush FIFO */ + inb(dev->iobase + APCI3120_RESET_FIFO); + inw(dev->iobase + APCI3120_RD_STATUS); + devpriv->ui_AiActualScan = 0; + devpriv->ui_AiActualScanPosition = 0; + s->async->cur_chan = 0; + devpriv->ui_AiBufferPtr = 0; + devpriv->b_AiContinuous = 0; + devpriv->ui_DmaActualBuffer = 0; + + devpriv->b_AiCyclicAcquisition = APCI3120_DISABLE; + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + i_APCI3120_Reset(dev); + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_CommandTestAnalogInput(struct comedi_device *dev| +| ,struct comedi_subdevice *s,struct comedi_cmd *cmd) | +| | ++----------------------------------------------------------------------------+ +| Task : Test validity for a command for cyclic anlog input | +| acquisition | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_cmd *cmd | ++----------------------------------------------------------------------------+ +| Return Value :0 | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_CommandTestAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; /* divisor1,divisor2; */ + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) + err++; + + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_FOLLOW) + err++; + + if (cmd->convert_src != TRIG_TIMER) + err++; + + if (cmd->scan_end_src != TRIG_COUNT) { + cmd->scan_end_src = TRIG_COUNT; + err++; + } + + if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { /* Test Delay timing */ + if (cmd->scan_begin_arg < + devpriv->s_EeParameters.ui_MinDelaytimeNs) { + cmd->scan_begin_arg = + devpriv->s_EeParameters.ui_MinDelaytimeNs; + err++; + } + } + + if (cmd->convert_src == TRIG_TIMER) { /* Test Acquisition timing */ + if (cmd->scan_begin_src == TRIG_TIMER) { + if ((cmd->convert_arg) + && (cmd->convert_arg < + devpriv->s_EeParameters. + ui_MinAcquisitiontimeNs)) { + cmd->convert_arg = devpriv->s_EeParameters. + ui_MinAcquisitiontimeNs; + err++; + } + } else { + if (cmd->convert_arg < + devpriv->s_EeParameters.ui_MinAcquisitiontimeNs + ) { + cmd->convert_arg = devpriv->s_EeParameters. + ui_MinAcquisitiontimeNs; + err++; + + } + } + } + + if (!cmd->chanlist_len) { + cmd->chanlist_len = 1; + err++; + } + if (cmd->chanlist_len > this_board->i_AiChannelList) { + cmd->chanlist_len = this_board->i_AiChannelList; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if (err) + return 4; + + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function name : int i_APCI3120_CommandAnalogInput(struct comedi_device *dev, | +| struct comedi_subdevice *s) | +| | ++----------------------------------------------------------------------------+ +| Task : Does asynchronous acquisition | +| Determines the mode 1 or 2. | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_CommandAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + /* loading private structure with cmd structure inputs */ + devpriv->ui_AiFlags = cmd->flags; + devpriv->ui_AiNbrofChannels = cmd->chanlist_len; + devpriv->ui_AiScanLength = cmd->scan_end_arg; + devpriv->pui_AiChannelList = cmd->chanlist; + + /* UPDATE-0.7.57->0.7.68devpriv->AiData=s->async->data; */ + devpriv->AiData = s->async->prealloc_buf; + /* UPDATE-0.7.57->0.7.68devpriv->ui_AiDataLength=s->async->data_len; */ + devpriv->ui_AiDataLength = s->async->prealloc_bufsz; + + if (cmd->stop_src == TRIG_COUNT) + devpriv->ui_AiNbrofScans = cmd->stop_arg; + else + devpriv->ui_AiNbrofScans = 0; + + devpriv->ui_AiTimer0 = 0; /* variables changed to timer0,timer1 */ + devpriv->ui_AiTimer1 = 0; + if ((devpriv->ui_AiNbrofScans == 0) || (devpriv->ui_AiNbrofScans == -1)) + devpriv->b_AiContinuous = 1; /* user want neverending analog acquisition */ + /* stopped using cancel */ + + if (cmd->start_src == TRIG_EXT) + devpriv->b_ExttrigEnable = APCI3120_ENABLE; + else + devpriv->b_ExttrigEnable = APCI3120_DISABLE; + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* mode 1 or 3 */ + if (cmd->convert_src == TRIG_TIMER) { + /* mode 1 */ + + devpriv->ui_AiTimer0 = cmd->convert_arg; /* timer constant in nano seconds */ + /* return this_board->i_hwdrv_CommandAnalogInput(1,dev,s); */ + return i_APCI3120_CyclicAnalogInput(1, dev, s); + } + + } + if ((cmd->scan_begin_src == TRIG_TIMER) + && (cmd->convert_src == TRIG_TIMER)) { + /* mode 2 */ + devpriv->ui_AiTimer1 = cmd->scan_begin_arg; + devpriv->ui_AiTimer0 = cmd->convert_arg; /* variable changed timer2 to timer0 */ + /* return this_board->i_hwdrv_CommandAnalogInput(2,dev,s); */ + return i_APCI3120_CyclicAnalogInput(2, dev, s); + } + return -1; +} + +/* ++----------------------------------------------------------------------------+ +| Function name : int i_APCI3120_CyclicAnalogInput(int mode, | +| struct comedi_device * dev,struct comedi_subdevice * s) | ++----------------------------------------------------------------------------+ +| Task : This is used for analog input cyclic acquisition | +| Performs the command operations. | +| If DMA is configured does DMA initialization | +| otherwise does the acquisition with EOS interrupt. | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : | +| | +| | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_CyclicAnalogInput(int mode, struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned char b_Tmp; + unsigned int ui_Tmp, ui_DelayTiming = 0, ui_TimerValue1 = 0, dmalen0 = + 0, dmalen1 = 0, ui_TimerValue2 = + 0, ui_TimerValue0, ui_ConvertTiming; + unsigned short us_TmpValue; + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* devpriv->b_AiCyclicAcquisition=APCI3120_ENABLE; */ + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + /*******************/ + /* Resets the FIFO */ + /*******************/ + inb(dev->iobase + APCI3120_RESET_FIFO); + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* inw(dev->iobase+APCI3120_RD_STATUS); */ + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + /***************************/ + /* Acquisition initialized */ + /***************************/ + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + devpriv->b_AiCyclicAcquisition = APCI3120_ENABLE; + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + /* clear software registers */ + devpriv->b_TimerSelectMode = 0; + devpriv->us_OutputRegister = 0; + devpriv->b_ModeSelectRegister = 0; + /* devpriv->b_DigitalOutputRegister=0; */ + + /* COMMENT JK 07.05.04: Followings calls are in i_APCI3120_StartAnalogInputAcquisition */ + + /****************************/ + /* Clear Timer Write TC int */ + /****************************/ + outl(APCI3120_CLEAR_WRITE_TC_INT, + devpriv->i_IobaseAmcc + APCI3120_AMCC_OP_REG_INTCSR); + + /************************************/ + /* Clears the timer status register */ + /************************************/ + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* inw(dev->iobase+APCI3120_TIMER_STATUS_REGISTER); */ + /* inb(dev->iobase + APCI3120_TIMER_STATUS_REGISTER); */ + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + /**************************/ + /* Disables All Timer */ + /* Sets PR and PA to 0 */ + /**************************/ + devpriv->us_OutputRegister = devpriv->us_OutputRegister & + APCI3120_DISABLE_TIMER0 & + APCI3120_DISABLE_TIMER1 & APCI3120_CLEAR_PA_PR; + + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + + /*******************/ + /* Resets the FIFO */ + /*******************/ + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + inb(devpriv->iobase + APCI3120_RESET_FIFO); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + devpriv->ui_AiActualScan = 0; + devpriv->ui_AiActualScanPosition = 0; + s->async->cur_chan = 0; + devpriv->ui_AiBufferPtr = 0; + devpriv->ui_DmaActualBuffer = 0; + + /* value for timer2 minus -2 has to be done .....dunno y?? */ + ui_TimerValue2 = devpriv->ui_AiNbrofScans - 2; + ui_ConvertTiming = devpriv->ui_AiTimer0; + + if (mode == 2) + ui_DelayTiming = devpriv->ui_AiTimer1; + + /**********************************/ + /* Initializes the sequence array */ + /**********************************/ + if (!i_APCI3120_SetupChannelList(dev, s, devpriv->ui_AiNbrofChannels, + devpriv->pui_AiChannelList, 0)) + return -EINVAL; + + us_TmpValue = (unsigned short) inw(dev->iobase + APCI3120_RD_STATUS); +/*** EL241003 : add this section in comment because floats must not be used + if((us_TmpValue & 0x00B0)==0x00B0) + { + f_ConvertValue=(((float)ui_ConvertTiming * 0.002) - 2); + ui_TimerValue0=(unsigned int)f_ConvertValue; + if (mode==2) + { + f_DelayValue = (((float)ui_DelayTiming * 0.00002) - 2); + ui_TimerValue1 = (unsigned int) f_DelayValue; + } + } + else + { + f_ConvertValue=(((float)ui_ConvertTiming * 0.0012926) - 1); + ui_TimerValue0=(unsigned int)f_ConvertValue; + if (mode == 2) + { + f_DelayValue = (((float)ui_DelayTiming * 0.000012926) - 1); + ui_TimerValue1 = (unsigned int) f_DelayValue; + } + } +***********************************************************************************************/ +/*** EL241003 Begin : add this section to replace floats calculation by integer calculations **/ + /* EL250804: Testing if board APCI3120 have the new Quartz or if it is an APCI3001 */ + if ((us_TmpValue & 0x00B0) == 0x00B0 + || !strcmp(this_board->pc_DriverName, "apci3001")) { + ui_TimerValue0 = ui_ConvertTiming * 2 - 2000; + ui_TimerValue0 = ui_TimerValue0 / 1000; + + if (mode == 2) { + ui_DelayTiming = ui_DelayTiming / 1000; + ui_TimerValue1 = ui_DelayTiming * 2 - 200; + ui_TimerValue1 = ui_TimerValue1 / 100; + } + } else { + ui_ConvertTiming = ui_ConvertTiming / 1000; + ui_TimerValue0 = ui_ConvertTiming * 12926 - 10000; + ui_TimerValue0 = ui_TimerValue0 / 10000; + + if (mode == 2) { + ui_DelayTiming = ui_DelayTiming / 1000; + ui_TimerValue1 = ui_DelayTiming * 12926 - 1; + ui_TimerValue1 = ui_TimerValue1 / 1000000; + } + } +/*** EL241003 End ******************************************************************************/ + + if (devpriv->b_ExttrigEnable == APCI3120_ENABLE) + i_APCI3120_ExttrigEnable(dev); /* activate EXT trigger */ + switch (mode) { + case 1: + /* init timer0 in mode 2 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xFC) | APCI3120_TIMER_0_MODE_2; + outb(devpriv->b_TimerSelectMode, + dev->iobase + APCI3120_TIMER_CRT1); + + /* Select Timer 0 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_0_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + /* Set the conversion time */ + outw(((unsigned short) ui_TimerValue0), + dev->iobase + APCI3120_TIMER_VALUE); + break; + + case 2: + /* init timer1 in mode 2 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xF3) | APCI3120_TIMER_1_MODE_2; + outb(devpriv->b_TimerSelectMode, + dev->iobase + APCI3120_TIMER_CRT1); + + /* Select Timer 1 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_1_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + /* Set the conversion time */ + outw(((unsigned short) ui_TimerValue1), + dev->iobase + APCI3120_TIMER_VALUE); + + /* init timer0 in mode 2 */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0xFC) | APCI3120_TIMER_0_MODE_2; + outb(devpriv->b_TimerSelectMode, + dev->iobase + APCI3120_TIMER_CRT1); + + /* Select Timer 0 */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_0_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + + /* Set the conversion time */ + outw(((unsigned short) ui_TimerValue0), + dev->iobase + APCI3120_TIMER_VALUE); + break; + + } + /* ##########common for all modes################# */ + + /***********************/ + /* Clears the SCAN bit */ + /***********************/ + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* devpriv->b_ModeSelectRegister=devpriv->b_ModeSelectRegister | APCI3120_DISABLE_SCAN; */ + + devpriv->b_ModeSelectRegister = devpriv->b_ModeSelectRegister & + APCI3120_DISABLE_SCAN; + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + /* If DMA is disabled */ + if (devpriv->us_UseDma == APCI3120_DISABLE) { + /* disable EOC and enable EOS */ + devpriv->b_InterruptMode = APCI3120_EOS_MODE; + devpriv->b_EocEosInterrupt = APCI3120_ENABLE; + + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOC_INT) | + APCI3120_ENABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + if (!devpriv->b_AiContinuous) { +/* + * configure Timer2 For counting EOS Reset gate 2 of Timer 2 to + * disable it (Set Bit D14 to 0) + */ + devpriv->us_OutputRegister = + devpriv-> + us_OutputRegister & APCI3120_DISABLE_TIMER2; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + + /* DISABLE TIMER intERRUPT */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_TIMER_INT & 0xEF; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + /* (1) Init timer 2 in mode 0 and write timer value */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0x0F) | + APCI3120_TIMER_2_MODE_0; + outb(devpriv->b_TimerSelectMode, + dev->iobase + APCI3120_TIMER_CRT1); + + /* Writing LOW unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + outw(LOWORD(ui_TimerValue2), + dev->iobase + APCI3120_TIMER_VALUE); + + /* Writing HIGH unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, dev->iobase + APCI3120_TIMER_CRT0); + outw(HIWORD(ui_TimerValue2), + dev->iobase + APCI3120_TIMER_VALUE); + + /* (2) Reset FC_TIMER BIT Clearing timer status register */ + inb(dev->iobase + APCI3120_TIMER_STATUS_REGISTER); + /* enable timer counter and disable watch dog */ + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister | + APCI3120_ENABLE_TIMER_COUNTER) & + APCI3120_DISABLE_WATCHDOG; + /* select EOS clock input for timer 2 */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | + APCI3120_TIMER2_SELECT_EOS; + /* Enable timer2 interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | + APCI3120_ENABLE_TIMER_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + devpriv->b_Timer2Mode = APCI3120_COUNTER; + devpriv->b_Timer2Interrupt = APCI3120_ENABLE; + } + } else { + /* If DMA Enabled */ + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* inw(dev->iobase+0); reset EOC bit */ + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + devpriv->b_InterruptMode = APCI3120_DMA_MODE; + + /************************************/ + /* Disables the EOC, EOS interrupt */ + /************************************/ + devpriv->b_ModeSelectRegister = devpriv->b_ModeSelectRegister & + APCI3120_DISABLE_EOC_INT & APCI3120_DISABLE_EOS_INT; + + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + dmalen0 = devpriv->ui_DmaBufferSize[0]; + dmalen1 = devpriv->ui_DmaBufferSize[1]; + + if (!devpriv->b_AiContinuous) { + + if (dmalen0 > (devpriv->ui_AiNbrofScans * devpriv->ui_AiScanLength * 2)) { /* must we fill full first buffer? */ + dmalen0 = + devpriv->ui_AiNbrofScans * + devpriv->ui_AiScanLength * 2; + } else if (dmalen1 > (devpriv->ui_AiNbrofScans * devpriv->ui_AiScanLength * 2 - dmalen0)) /* and must we fill full second buffer when first is once filled? */ + dmalen1 = + devpriv->ui_AiNbrofScans * + devpriv->ui_AiScanLength * 2 - dmalen0; + } + + if (devpriv->ui_AiFlags & TRIG_WAKE_EOS) { + /* don't we want wake up every scan? */ + if (dmalen0 > (devpriv->ui_AiScanLength * 2)) { + dmalen0 = devpriv->ui_AiScanLength * 2; + if (devpriv->ui_AiScanLength & 1) + dmalen0 += 2; + } + if (dmalen1 > (devpriv->ui_AiScanLength * 2)) { + dmalen1 = devpriv->ui_AiScanLength * 2; + if (devpriv->ui_AiScanLength & 1) + dmalen1 -= 2; + if (dmalen1 < 4) + dmalen1 = 4; + } + } else { /* isn't output buff smaller that our DMA buff? */ + if (dmalen0 > (devpriv->ui_AiDataLength)) + dmalen0 = devpriv->ui_AiDataLength; + if (dmalen1 > (devpriv->ui_AiDataLength)) + dmalen1 = devpriv->ui_AiDataLength; + } + devpriv->ui_DmaBufferUsesize[0] = dmalen0; + devpriv->ui_DmaBufferUsesize[1] = dmalen1; + + /* Initialize DMA */ + +/* + * Set Transfer count enable bit and A2P_fifo reset bit in AGCSTS + * register 1 + */ + ui_Tmp = AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO; + outl(ui_Tmp, devpriv->i_IobaseAmcc + AMCC_OP_REG_AGCSTS); + + /* changed since 16 bit interface for add on */ + /*********************/ + /* ENABLE BUS MASTER */ + /*********************/ + outw(APCI3120_ADD_ON_AGCSTS_LOW, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_LOW, + devpriv->i_IobaseAddon + 2); + + outw(APCI3120_ADD_ON_AGCSTS_HIGH, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_HIGH, + devpriv->i_IobaseAddon + 2); + +/* + * TO VERIFIED BEGIN JK 07.05.04: Comparison between WIN32 and Linux + * driver + */ + outw(0x1000, devpriv->i_IobaseAddon + 2); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + /* 2 No change */ + /* A2P FIFO MANAGEMENT */ + /* A2P fifo reset & transfer control enable */ + + /***********************/ + /* A2P FIFO MANAGEMENT */ + /***********************/ + outl(APCI3120_A2P_FIFO_MANAGEMENT, devpriv->i_IobaseAmcc + + APCI3120_AMCC_OP_MCSR); + +/* + * 3 + * beginning address of dma buf The 32 bit address of dma buffer + * is converted into two 16 bit addresses Can done by using _attach + * and put into into an array array used may be for differnet pages + */ + + /* DMA Start Address Low */ + outw(APCI3120_ADD_ON_MWAR_LOW, devpriv->i_IobaseAddon + 0); + outw((devpriv->ul_DmaBufferHw[0] & 0xFFFF), + devpriv->i_IobaseAddon + 2); + + /*************************/ + /* DMA Start Address High */ + /*************************/ + outw(APCI3120_ADD_ON_MWAR_HIGH, devpriv->i_IobaseAddon + 0); + outw((devpriv->ul_DmaBufferHw[0] / 65536), + devpriv->i_IobaseAddon + 2); + +/* + * 4 + * amount of bytes to be transferred set transfer count used ADDON + * MWTC register commented testing + * outl(devpriv->ui_DmaBufferUsesize[0], + * devpriv->i_IobaseAddon+AMCC_OP_REG_AMWTC); + */ + + /**************************/ + /* Nbr of acquisition LOW */ + /**************************/ + outw(APCI3120_ADD_ON_MWTC_LOW, devpriv->i_IobaseAddon + 0); + outw((devpriv->ui_DmaBufferUsesize[0] & 0xFFFF), + devpriv->i_IobaseAddon + 2); + + /***************************/ + /* Nbr of acquisition HIGH */ + /***************************/ + outw(APCI3120_ADD_ON_MWTC_HIGH, devpriv->i_IobaseAddon + 0); + outw((devpriv->ui_DmaBufferUsesize[0] / 65536), + devpriv->i_IobaseAddon + 2); + +/* + * 5 + * To configure A2P FIFO testing outl( + * FIFO_ADVANCE_ON_BYTE_2,devpriv->i_IobaseAmcc+AMCC_OP_REG_INTCSR); + */ + + /******************/ + /* A2P FIFO RESET */ + /******************/ +/* + * TO VERIFY BEGIN JK 07.05.04: Comparison between WIN32 and Linux + * driver + */ + outl(0x04000000UL, devpriv->i_IobaseAmcc + AMCC_OP_REG_MCSR); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + +/* + * 6 + * ENABLE A2P FIFO WRITE AND ENABLE AMWEN AMWEN_ENABLE | + * A2P_FIFO_WRITE_ENABLE (0x01|0x02)=0x03 + */ + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /* outw(3,devpriv->i_IobaseAddon + 4); */ + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + +/* + * 7 + * initialise end of dma interrupt AINT_WRITE_COMPL = + * ENABLE_WRITE_TC_INT(ADDI) + */ + /***************************************************/ + /* A2P FIFO CONFIGURATE, END OF DMA intERRUPT INIT */ + /***************************************************/ + outl((APCI3120_FIFO_ADVANCE_ON_BYTE_2 | + APCI3120_ENABLE_WRITE_TC_INT), + devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); + + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + /******************************************/ + /* ENABLE A2P FIFO WRITE AND ENABLE AMWEN */ + /******************************************/ + outw(3, devpriv->i_IobaseAddon + 4); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + + /******************/ + /* A2P FIFO RESET */ + /******************/ + /* BEGIN JK 07.05.04: Comparison between WIN32 and Linux driver */ + outl(0x04000000UL, + devpriv->i_IobaseAmcc + APCI3120_AMCC_OP_MCSR); + /* END JK 07.05.04: Comparison between WIN32 and Linux driver */ + } + + if ((devpriv->us_UseDma == APCI3120_DISABLE) + && !devpriv->b_AiContinuous) { + /* set gate 2 to start conversion */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister | APCI3120_ENABLE_TIMER2; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + } + + switch (mode) { + case 1: + /* set gate 0 to start conversion */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister | APCI3120_ENABLE_TIMER0; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + break; + case 2: + /* set gate 0 and gate 1 */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister | APCI3120_ENABLE_TIMER1; + devpriv->us_OutputRegister = + devpriv->us_OutputRegister | APCI3120_ENABLE_TIMER0; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + break; + + } + + return 0; + +} + +/* ++----------------------------------------------------------------------------+ +| intERNAL FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function name : int i_APCI3120_Reset(struct comedi_device *dev) | +| | +| | ++----------------------------------------------------------------------------+ +| Task : Hardware reset function | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| | +| | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_Reset(struct comedi_device *dev) +{ + unsigned int i; + unsigned short us_TmpValue; + + devpriv->b_AiCyclicAcquisition = APCI3120_DISABLE; + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + devpriv->ui_EocEosConversionTime = 0; /* set eoc eos conv time to 0 */ + devpriv->b_OutputMemoryStatus = 0; + + /* variables used in timer subdevice */ + devpriv->b_Timer2Mode = 0; + devpriv->b_Timer2Interrupt = 0; + devpriv->b_ExttrigEnable = 0; /* Disable ext trigger */ + + /* Disable all interrupts, watchdog for the anolog output */ + devpriv->b_ModeSelectRegister = 0; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + /* Disables all counters, ext trigger and clears PA, PR */ + devpriv->us_OutputRegister = 0; + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + +/* + * Code to set the all anolog o/p channel to 0v 8191 is decimal + * value for zero(0 v)volt in bipolar mode(default) + */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_1, dev->iobase + APCI3120_ANALOG_OUTPUT_1); /* channel 1 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_2, dev->iobase + APCI3120_ANALOG_OUTPUT_1); /* channel 2 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_3, dev->iobase + APCI3120_ANALOG_OUTPUT_1); /* channel 3 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_4, dev->iobase + APCI3120_ANALOG_OUTPUT_1); /* channel 4 */ + + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_5, dev->iobase + APCI3120_ANALOG_OUTPUT_2); /* channel 5 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_6, dev->iobase + APCI3120_ANALOG_OUTPUT_2); /* channel 6 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_7, dev->iobase + APCI3120_ANALOG_OUTPUT_2); /* channel 7 */ + outw(8191 | APCI3120_ANALOG_OP_CHANNEL_8, dev->iobase + APCI3120_ANALOG_OUTPUT_2); /* channel 8 */ + + /* Reset digital output to L0W */ + +/* ES05 outb(0x0,dev->iobase+APCI3120_DIGITAL_OUTPUT); */ + udelay(10); + + inw(dev->iobase + 0); /* make a dummy read */ + inb(dev->iobase + APCI3120_RESET_FIFO); /* flush FIFO */ + inw(dev->iobase + APCI3120_RD_STATUS); /* flush A/D status register */ + + /* code to reset the RAM sequence */ + for (i = 0; i < 16; i++) { + us_TmpValue = i << 8; /* select the location */ + outw(us_TmpValue, dev->iobase + APCI3120_SEQ_RAM_ADDRESS); + } + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function name : int i_APCI3120_SetupChannelList(struct comedi_device * dev, | +| struct comedi_subdevice * s, int n_chan,unsigned int *chanlist| +| ,char check) | +| | ++----------------------------------------------------------------------------+ +| Task :This function will first check channel list is ok or not| +|and then initialize the sequence RAM with the polarity, Gain,Channel number | +|If the last argument of function "check"is 1 then it only checks the channel| +|list is ok or not. | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device * dev | +| struct comedi_subdevice * s | +| int n_chan | + unsigned int *chanlist + char check ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_SetupChannelList(struct comedi_device *dev, struct comedi_subdevice *s, + int n_chan, unsigned int *chanlist, char check) +{ + unsigned int i; /* , differencial=0, bipolar=0; */ + unsigned int gain; + unsigned short us_TmpValue; + + /* correct channel and range number check itself comedi/range.c */ + if (n_chan < 1) { + if (!check) + comedi_error(dev, "range/channel list is empty!"); + return 0; + } + /* All is ok, so we can setup channel/range list */ + if (check) + return 1; + + /* Code to set the PA and PR...Here it set PA to 0.. */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister & APCI3120_CLEAR_PA_PR; + devpriv->us_OutputRegister = ((n_chan - 1) & 0xf) << 8; + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + + for (i = 0; i < n_chan; i++) { + /* store range list to card */ + us_TmpValue = CR_CHAN(chanlist[i]); /* get channel number; */ + + if (CR_RANGE(chanlist[i]) < APCI3120_BIPOLAR_RANGES) + us_TmpValue &= ((~APCI3120_UNIPOLAR) & 0xff); /* set bipolar */ + else + us_TmpValue |= APCI3120_UNIPOLAR; /* enable unipolar...... */ + + gain = CR_RANGE(chanlist[i]); /* get gain number */ + us_TmpValue |= ((gain & 0x03) << 4); /* <<4 for G0 and G1 bit in RAM */ + us_TmpValue |= i << 8; /* To select the RAM LOCATION.... */ + outw(us_TmpValue, dev->iobase + APCI3120_SEQ_RAM_ADDRESS); + + printk("\n Gain = %i", + (((unsigned char)CR_RANGE(chanlist[i]) & 0x03) << 2)); + printk("\n Channel = %i", CR_CHAN(chanlist[i])); + printk("\n Polarity = %i", us_TmpValue & APCI3120_UNIPOLAR); + } + return 1; /* we can serve this with scan logic */ +} + +/* ++----------------------------------------------------------------------------+ +| Function name : int i_APCI3120_ExttrigEnable(struct comedi_device * dev) | +| | +| | ++----------------------------------------------------------------------------+ +| Task : Enable the external trigger | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device * dev | +| | +| | ++----------------------------------------------------------------------------+ +| Return Value : 0 | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_ExttrigEnable(struct comedi_device *dev) +{ + + devpriv->us_OutputRegister |= APCI3120_ENABLE_EXT_TRIGGER; + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function name : int i_APCI3120_ExttrigDisable(struct comedi_device * dev) | +| | ++----------------------------------------------------------------------------+ +| Task : Disables the external trigger | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device * dev | +| | +| | ++----------------------------------------------------------------------------+ +| Return Value : 0 | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_ExttrigDisable(struct comedi_device *dev) +{ + devpriv->us_OutputRegister &= ~APCI3120_ENABLE_EXT_TRIGGER; + outw(devpriv->us_OutputRegister, dev->iobase + APCI3120_WR_ADDRESS); + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| intERRUPT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function name : void v_APCI3120_Interrupt(int irq, void *d) | +| | +| | ++----------------------------------------------------------------------------+ +| Task :Interrupt handler for APCI3120 | +| When interrupt occurs this gets called. | +| First it finds which interrupt has been generated and | +| handles corresponding interrupt | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : int irq | +| void *d | +| | ++----------------------------------------------------------------------------+ +| Return Value : void | +| | ++----------------------------------------------------------------------------+ +*/ + +void v_APCI3120_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned short int_daq; + + unsigned int int_amcc, ui_Check, i; + unsigned short us_TmpValue; + unsigned char b_DummyRead; + + struct comedi_subdevice *s = dev->subdevices + 0; + ui_Check = 1; + + int_daq = inw(dev->iobase + APCI3120_RD_STATUS) & 0xf000; /* get IRQ reasons */ + int_amcc = inl(devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); /* get AMCC int register */ + + if ((!int_daq) && (!(int_amcc & ANY_S593X_INT))) { + comedi_error(dev, "IRQ from unknown source"); + return; + } + + outl(int_amcc | 0x00ff0000, devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); /* shutdown IRQ reasons in AMCC */ + + int_daq = (int_daq >> 12) & 0xF; + + if (devpriv->b_ExttrigEnable == APCI3120_ENABLE) { + /* Disable ext trigger */ + i_APCI3120_ExttrigDisable(dev); + devpriv->b_ExttrigEnable = APCI3120_DISABLE; + } + /* clear the timer 2 interrupt */ + inb(devpriv->i_IobaseAmcc + APCI3120_TIMER_STATUS_REGISTER); + + if (int_amcc & MASTER_ABORT_INT) + comedi_error(dev, "AMCC IRQ - MASTER DMA ABORT!"); + if (int_amcc & TARGET_ABORT_INT) + comedi_error(dev, "AMCC IRQ - TARGET DMA ABORT!"); + + /* Ckeck if EOC interrupt */ + if (((int_daq & 0x8) == 0) + && (devpriv->b_InterruptMode == APCI3120_EOC_MODE)) { + if (devpriv->b_EocEosInterrupt == APCI3120_ENABLE) { + + /* Read the AI Value */ + + devpriv->ui_AiReadData[0] = + (unsigned int) inw(devpriv->iobase + 0); + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } else { + /* Disable EOC Interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOC_INT; + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + } + } + + /* Check If EOS interrupt */ + if ((int_daq & 0x2) && (devpriv->b_InterruptMode == APCI3120_EOS_MODE)) { + + if (devpriv->b_EocEosInterrupt == APCI3120_ENABLE) { /* enable this in without DMA ??? */ + + if (devpriv->b_AiCyclicAcquisition == APCI3120_ENABLE) { + ui_Check = 0; + i_APCI3120_InterruptHandleEos(dev); + devpriv->ui_AiActualScan++; + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | + APCI3120_ENABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + + APCI3120_WRITE_MODE_SELECT); + } else { + ui_Check = 0; + for (i = 0; i < devpriv->ui_AiNbrofChannels; + i++) { + us_TmpValue = inw(devpriv->iobase + 0); + devpriv->ui_AiReadData[i] = + (unsigned int) us_TmpValue; + } + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + + } + + } else { + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + devpriv->b_EocEosInterrupt = APCI3120_DISABLE; /* Default settings */ + devpriv->b_InterruptMode = APCI3120_EOC_MODE; + } + + } + /* Timer2 interrupt */ + if (int_daq & 0x1) { + + switch (devpriv->b_Timer2Mode) { + case APCI3120_COUNTER: + + devpriv->b_AiCyclicAcquisition = APCI3120_DISABLE; + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + /* stop timer 2 */ + devpriv->us_OutputRegister = + devpriv-> + us_OutputRegister & APCI3120_DISABLE_ALL_TIMER; + outw(devpriv->us_OutputRegister, + dev->iobase + APCI3120_WR_ADDRESS); + + /* stop timer 0 and timer 1 */ + i_APCI3120_StopCyclicAcquisition(dev, s); + devpriv->b_AiCyclicAcquisition = APCI3120_DISABLE; + + /* UPDATE-0.7.57->0.7.68comedi_done(dev,s); */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + + break; + + case APCI3120_TIMER: + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + break; + + case APCI3120_WATCHDOG: + + /* Send a signal to from kernel to user space */ + send_sig(SIGIO, devpriv->tsk_Current, 0); + break; + + default: + + /* disable Timer Interrupt */ + + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_TIMER_INT; + + outb(devpriv->b_ModeSelectRegister, + dev->iobase + APCI3120_WRITE_MODE_SELECT); + + } + + b_DummyRead = inb(dev->iobase + APCI3120_TIMER_STATUS_REGISTER); + + } + + if ((int_daq & 0x4) && (devpriv->b_InterruptMode == APCI3120_DMA_MODE)) { + if (devpriv->b_AiCyclicAcquisition == APCI3120_ENABLE) { + + /****************************/ + /* Clear Timer Write TC int */ + /****************************/ + + outl(APCI3120_CLEAR_WRITE_TC_INT, + devpriv->i_IobaseAmcc + + APCI3120_AMCC_OP_REG_INTCSR); + + /************************************/ + /* Clears the timer status register */ + /************************************/ + inw(dev->iobase + APCI3120_TIMER_STATUS_REGISTER); + v_APCI3120_InterruptDma(irq, d); /* do some data transfer */ + } else { + /* Stops the Timer */ + outw(devpriv-> + us_OutputRegister & APCI3120_DISABLE_TIMER0 & + APCI3120_DISABLE_TIMER1, + dev->iobase + APCI3120_WR_ADDRESS); + } + + } + + return; +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InterruptHandleEos(struct comedi_device *dev) | +| | +| | ++----------------------------------------------------------------------------+ +| Task : This function handles EOS interrupt. | +| This function copies the acquired data(from FIFO) | +| to Comedi buffer. | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| | +| | ++----------------------------------------------------------------------------+ +| Return Value : 0 | +| | ++----------------------------------------------------------------------------+ +*/ + + +int i_APCI3120_InterruptHandleEos(struct comedi_device *dev) +{ + int n_chan, i; + struct comedi_subdevice *s = dev->subdevices + 0; + int err = 1; + + n_chan = devpriv->ui_AiNbrofChannels; + + s->async->events = 0; + + for (i = 0; i < n_chan; i++) + err &= comedi_buf_put(s->async, inw(dev->iobase + 0)); + + s->async->events |= COMEDI_CB_EOS; + + if (err == 0) + s->async->events |= COMEDI_CB_OVERFLOW; + + comedi_event(dev, s); + + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function name : void v_APCI3120_InterruptDma(int irq, void *d) | +| | ++----------------------------------------------------------------------------+ +| Task : This is a handler for the DMA interrupt | +| This function copies the data to Comedi Buffer. | +| For continuous DMA it reinitializes the DMA operation. | +| For single mode DMA it stop the acquisition. | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : int irq, void *d | +| | ++----------------------------------------------------------------------------+ +| Return Value : void | +| | ++----------------------------------------------------------------------------+ +*/ + +void v_APCI3120_InterruptDma(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + unsigned int next_dma_buf, samplesinbuf; + unsigned long low_word, high_word, var; + + unsigned int ui_Tmp; + samplesinbuf = + devpriv->ui_DmaBufferUsesize[devpriv->ui_DmaActualBuffer] - + inl(devpriv->i_IobaseAmcc + AMCC_OP_REG_MWTC); + + if (samplesinbuf < + devpriv->ui_DmaBufferUsesize[devpriv->ui_DmaActualBuffer]) { + comedi_error(dev, "Interrupted DMA transfer!"); + } + if (samplesinbuf & 1) { + comedi_error(dev, "Odd count of bytes in DMA ring!"); + i_APCI3120_StopCyclicAcquisition(dev, s); + devpriv->b_AiCyclicAcquisition = APCI3120_DISABLE; + + return; + } + samplesinbuf = samplesinbuf >> 1; /* number of received samples */ + if (devpriv->b_DmaDoubleBuffer) { + /* switch DMA buffers if is used double buffering */ + next_dma_buf = 1 - devpriv->ui_DmaActualBuffer; + + ui_Tmp = AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO; + outl(ui_Tmp, devpriv->i_IobaseAddon + AMCC_OP_REG_AGCSTS); + + /* changed since 16 bit interface for add on */ + outw(APCI3120_ADD_ON_AGCSTS_LOW, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_LOW, + devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_AGCSTS_HIGH, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_HIGH, devpriv->i_IobaseAddon + 2); /* 0x1000 is out putted in windows driver */ + + var = devpriv->ul_DmaBufferHw[next_dma_buf]; + low_word = var & 0xffff; + var = devpriv->ul_DmaBufferHw[next_dma_buf]; + high_word = var / 65536; + + /* DMA Start Address Low */ + outw(APCI3120_ADD_ON_MWAR_LOW, devpriv->i_IobaseAddon + 0); + outw(low_word, devpriv->i_IobaseAddon + 2); + + /* DMA Start Address High */ + outw(APCI3120_ADD_ON_MWAR_HIGH, devpriv->i_IobaseAddon + 0); + outw(high_word, devpriv->i_IobaseAddon + 2); + + var = devpriv->ui_DmaBufferUsesize[next_dma_buf]; + low_word = var & 0xffff; + var = devpriv->ui_DmaBufferUsesize[next_dma_buf]; + high_word = var / 65536; + + /* Nbr of acquisition LOW */ + outw(APCI3120_ADD_ON_MWTC_LOW, devpriv->i_IobaseAddon + 0); + outw(low_word, devpriv->i_IobaseAddon + 2); + + /* Nbr of acquisition HIGH */ + outw(APCI3120_ADD_ON_MWTC_HIGH, devpriv->i_IobaseAddon + 0); + outw(high_word, devpriv->i_IobaseAddon + 2); + +/* + * To configure A2P FIFO + * ENABLE A2P FIFO WRITE AND ENABLE AMWEN + * AMWEN_ENABLE | A2P_FIFO_WRITE_ENABLE (0x01|0x02)=0x03 + */ + outw(3, devpriv->i_IobaseAddon + 4); + /* initialise end of dma interrupt AINT_WRITE_COMPL = ENABLE_WRITE_TC_INT(ADDI) */ + outl((APCI3120_FIFO_ADVANCE_ON_BYTE_2 | + APCI3120_ENABLE_WRITE_TC_INT), + devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); + + } + if (samplesinbuf) { + v_APCI3120_InterruptDmaMoveBlock16bit(dev, s, + devpriv->ul_DmaBufferVirtual[devpriv-> + ui_DmaActualBuffer], samplesinbuf); + + if (!(devpriv->ui_AiFlags & TRIG_WAKE_EOS)) { + s->async->events |= COMEDI_CB_EOS; + comedi_event(dev, s); + } + } + if (!devpriv->b_AiContinuous) + if (devpriv->ui_AiActualScan >= devpriv->ui_AiNbrofScans) { + /* all data sampled */ + i_APCI3120_StopCyclicAcquisition(dev, s); + devpriv->b_AiCyclicAcquisition = APCI3120_DISABLE; + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return; + } + + if (devpriv->b_DmaDoubleBuffer) { /* switch dma buffers */ + devpriv->ui_DmaActualBuffer = 1 - devpriv->ui_DmaActualBuffer; + } else { +/* + * restart DMA if is not used double buffering + * ADDED REINITIALISE THE DMA + */ + ui_Tmp = AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO; + outl(ui_Tmp, devpriv->i_IobaseAddon + AMCC_OP_REG_AGCSTS); + + /* changed since 16 bit interface for add on */ + outw(APCI3120_ADD_ON_AGCSTS_LOW, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_LOW, + devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_AGCSTS_HIGH, devpriv->i_IobaseAddon + 0); + outw(APCI3120_ENABLE_TRANSFER_ADD_ON_HIGH, devpriv->i_IobaseAddon + 2); /* */ +/* + * A2P FIFO MANAGEMENT + * A2P fifo reset & transfer control enable + */ + outl(APCI3120_A2P_FIFO_MANAGEMENT, + devpriv->i_IobaseAmcc + AMCC_OP_REG_MCSR); + + var = devpriv->ul_DmaBufferHw[0]; + low_word = var & 0xffff; + var = devpriv->ul_DmaBufferHw[0]; + high_word = var / 65536; + outw(APCI3120_ADD_ON_MWAR_LOW, devpriv->i_IobaseAddon + 0); + outw(low_word, devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_MWAR_HIGH, devpriv->i_IobaseAddon + 0); + outw(high_word, devpriv->i_IobaseAddon + 2); + + var = devpriv->ui_DmaBufferUsesize[0]; + low_word = var & 0xffff; /* changed */ + var = devpriv->ui_DmaBufferUsesize[0]; + high_word = var / 65536; + outw(APCI3120_ADD_ON_MWTC_LOW, devpriv->i_IobaseAddon + 0); + outw(low_word, devpriv->i_IobaseAddon + 2); + outw(APCI3120_ADD_ON_MWTC_HIGH, devpriv->i_IobaseAddon + 0); + outw(high_word, devpriv->i_IobaseAddon + 2); + +/* + * To configure A2P FIFO + * ENABLE A2P FIFO WRITE AND ENABLE AMWEN + * AMWEN_ENABLE | A2P_FIFO_WRITE_ENABLE (0x01|0x02)=0x03 + */ + outw(3, devpriv->i_IobaseAddon + 4); + /* initialise end of dma interrupt AINT_WRITE_COMPL = ENABLE_WRITE_TC_INT(ADDI) */ + outl((APCI3120_FIFO_ADVANCE_ON_BYTE_2 | + APCI3120_ENABLE_WRITE_TC_INT), + devpriv->i_IobaseAmcc + AMCC_OP_REG_INTCSR); + } +} + +/* ++----------------------------------------------------------------------------+ +| Function name :void v_APCI3120_InterruptDmaMoveBlock16bit(comedi_device| +|*dev,struct comedi_subdevice *s,short *dma,short *data,int n) | +| | ++----------------------------------------------------------------------------+ +| Task : This function copies the data from DMA buffer to the | +| Comedi buffer | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| short *dma | +| short *data,int n | ++----------------------------------------------------------------------------+ +| Return Value : void | +| | ++----------------------------------------------------------------------------+ +*/ + +void v_APCI3120_InterruptDmaMoveBlock16bit(struct comedi_device *dev, + struct comedi_subdevice *s, short *dma_buffer, unsigned int num_samples) +{ + devpriv->ui_AiActualScan += + (s->async->cur_chan + num_samples) / devpriv->ui_AiScanLength; + s->async->cur_chan += num_samples; + s->async->cur_chan %= devpriv->ui_AiScanLength; + + cfc_write_array_to_buffer(s, dma_buffer, num_samples * sizeof(short)); +} + +/* ++----------------------------------------------------------------------------+ +| TIMER SUBDEVICE | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnConfigTimer(struct comedi_device *dev, | +| struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task :Configure Timer 2 | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | +| | +| data[0]= TIMER configure as timer | +| = WATCHDOG configure as watchdog | +| data[1] = Timer constant | +| data[2] = Timer2 interrupt (1)enable or(0) disable | +| | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_InsnConfigTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + unsigned int ui_Timervalue2; + unsigned short us_TmpValue; + unsigned char b_Tmp; + + if (!data[1]) + comedi_error(dev, "config:No timer constant !"); + + devpriv->b_Timer2Interrupt = (unsigned char) data[2]; /* save info whether to enable or disable interrupt */ + + ui_Timervalue2 = data[1] / 1000; /* convert nano seconds to u seconds */ + + /* this_board->i_hwdrv_InsnConfigTimer(dev, ui_Timervalue2,(unsigned char)data[0]); */ + us_TmpValue = (unsigned short) inw(devpriv->iobase + APCI3120_RD_STATUS); + +/* + * EL250804: Testing if board APCI3120 have the new Quartz or if it + * is an APCI3001 and calculate the time value to set in the timer + */ + if ((us_TmpValue & 0x00B0) == 0x00B0 + || !strcmp(this_board->pc_DriverName, "apci3001")) { + /* Calculate the time value to set in the timer */ + ui_Timervalue2 = ui_Timervalue2 / 50; + } else { + /* Calculate the time value to set in the timer */ + ui_Timervalue2 = ui_Timervalue2 / 70; + } + + /* Reset gate 2 of Timer 2 to disable it (Set Bit D14 to 0) */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister & APCI3120_DISABLE_TIMER2; + outw(devpriv->us_OutputRegister, devpriv->iobase + APCI3120_WR_ADDRESS); + + /* Disable TIMER Interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_TIMER_INT & 0xEF; + + /* Disable Eoc and Eos Interrupts */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_EOC_INT & + APCI3120_DISABLE_EOS_INT; + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + if (data[0] == APCI3120_TIMER) { /* initialize timer */ + /* devpriv->b_ModeSelectRegister=devpriv->b_ModeSelectRegister | + * APCI3120_ENABLE_TIMER_INT; */ + + /* outb(devpriv->b_ModeSelectRegister,devpriv->iobase+APCI3120_WRITE_MODE_SELECT); */ + + /* Set the Timer 2 in mode 2(Timer) */ + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0x0F) | APCI3120_TIMER_2_MODE_2; + outb(devpriv->b_TimerSelectMode, + devpriv->iobase + APCI3120_TIMER_CRT1); + +/* + * Configure the timer 2 for writing the LOW unsigned short of timer + * is Delay value You must make a b_tmp variable with + * DigitalOutPutRegister because at Address_1+APCI3120_TIMER_CRT0 + * you can set the digital output and configure the timer 2,and if + * you don't make this, digital output are erase (Set to 0) + */ + + /* Writing LOW unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + outw(LOWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Writing HIGH unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + outw(HIWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + /* timer2 in Timer mode enabled */ + devpriv->b_Timer2Mode = APCI3120_TIMER; + + } else { /* Initialize Watch dog */ + + /* Set the Timer 2 in mode 5(Watchdog) */ + + devpriv->b_TimerSelectMode = + (devpriv-> + b_TimerSelectMode & 0x0F) | APCI3120_TIMER_2_MODE_5; + outb(devpriv->b_TimerSelectMode, + devpriv->iobase + APCI3120_TIMER_CRT1); + +/* + * Configure the timer 2 for writing the LOW unsigned short of timer + * is Delay value You must make a b_tmp variable with + * DigitalOutPutRegister because at Address_1+APCI3120_TIMER_CRT0 + * you can set the digital output and configure the timer 2,and if + * you don't make this, digital output are erase (Set to 0) + */ + + /* Writing LOW unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + outw(LOWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Writing HIGH unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + outw(HIWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + /* watchdog enabled */ + devpriv->b_Timer2Mode = APCI3120_WATCHDOG; + + } + + return insn->n; + +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnWriteTimer(struct comedi_device *dev, | +| struct comedi_subdevice *s, struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : To start and stop the timer | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | +| | +| data[0] = 1 (start) | +| data[0] = 0 (stop ) | +| data[0] = 2 (write new value) | +| data[1]= new value | +| | +| devpriv->b_Timer2Mode = 0 DISABLE | +| 1 Timer | +| 2 Watch dog | +| | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_InsnWriteTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + unsigned int ui_Timervalue2 = 0; + unsigned short us_TmpValue; + unsigned char b_Tmp; + + if ((devpriv->b_Timer2Mode != APCI3120_WATCHDOG) + && (devpriv->b_Timer2Mode != APCI3120_TIMER)) { + comedi_error(dev, "\nwrite:timer2 not configured "); + return -EINVAL; + } + + if (data[0] == 2) { /* write new value */ + if (devpriv->b_Timer2Mode != APCI3120_TIMER) { + comedi_error(dev, + "write :timer2 not configured in TIMER MODE"); + return -EINVAL; + } + + if (data[1]) + ui_Timervalue2 = data[1]; + else + ui_Timervalue2 = 0; + } + + /* this_board->i_hwdrv_InsnWriteTimer(dev,data[0],ui_Timervalue2); */ + + switch (data[0]) { + case APCI3120_START: + + /* Reset FC_TIMER BIT */ + inb(devpriv->iobase + APCI3120_TIMER_STATUS_REGISTER); + if (devpriv->b_Timer2Mode == APCI3120_TIMER) { /* start timer */ + /* Enable Timer */ + devpriv->b_ModeSelectRegister = + devpriv->b_ModeSelectRegister & 0x0B; + } else { /* start watch dog */ + /* Enable WatchDog */ + devpriv->b_ModeSelectRegister = + (devpriv-> + b_ModeSelectRegister & 0x0B) | + APCI3120_ENABLE_WATCHDOG; + } + + /* enable disable interrupt */ + if ((devpriv->b_Timer2Interrupt) == APCI3120_ENABLE) { + + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister | + APCI3120_ENABLE_TIMER_INT; + /* save the task structure to pass info to user */ + devpriv->tsk_Current = current; + } else { + + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_TIMER_INT; + } + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + if (devpriv->b_Timer2Mode == APCI3120_TIMER) { /* start timer */ + /* For Timer mode is Gate2 must be activated **timer started */ + devpriv->us_OutputRegister = + devpriv-> + us_OutputRegister | APCI3120_ENABLE_TIMER2; + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + } + + break; + + case APCI3120_STOP: + if (devpriv->b_Timer2Mode == APCI3120_TIMER) { + /* Disable timer */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_TIMER_COUNTER; + } else { + /* Disable WatchDog */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & + APCI3120_DISABLE_WATCHDOG; + } + /* Disable timer interrupt */ + devpriv->b_ModeSelectRegister = + devpriv-> + b_ModeSelectRegister & APCI3120_DISABLE_TIMER_INT; + + /* Write above states to register */ + outb(devpriv->b_ModeSelectRegister, + devpriv->iobase + APCI3120_WRITE_MODE_SELECT); + + /* Reset Gate 2 */ + devpriv->us_OutputRegister = + devpriv->us_OutputRegister & APCI3120_DISABLE_TIMER_INT; + outw(devpriv->us_OutputRegister, + devpriv->iobase + APCI3120_WR_ADDRESS); + + /* Reset FC_TIMER BIT */ + inb(devpriv->iobase + APCI3120_TIMER_STATUS_REGISTER); + + /* Disable timer */ + /* devpriv->b_Timer2Mode=APCI3120_DISABLE; */ + + break; + + case 2: /* write new value to Timer */ + if (devpriv->b_Timer2Mode != APCI3120_TIMER) { + comedi_error(dev, + "write :timer2 not configured in TIMER MODE"); + return -EINVAL; + } + /* ui_Timervalue2=data[1]; // passed as argument */ + us_TmpValue = + (unsigned short) inw(devpriv->iobase + APCI3120_RD_STATUS); + +/* + * EL250804: Testing if board APCI3120 have the new Quartz or if it + * is an APCI3001 and calculate the time value to set in the timer + */ + if ((us_TmpValue & 0x00B0) == 0x00B0 + || !strcmp(this_board->pc_DriverName, "apci3001")) { + /* Calculate the time value to set in the timer */ + ui_Timervalue2 = ui_Timervalue2 / 50; + } else { + /* Calculate the time value to set in the timer */ + ui_Timervalue2 = ui_Timervalue2 / 70; + } + /* Writing LOW unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + outw(LOWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Writing HIGH unsigned short */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + outw(HIWORD(ui_Timervalue2), + devpriv->iobase + APCI3120_TIMER_VALUE); + + break; + default: + return -EINVAL; /* Not a valid input */ + } + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function name : int i_APCI3120_InsnReadTimer(struct comedi_device *dev, | +| struct comedi_subdevice *s,struct comedi_insn *insn, unsigned int *data) | +| | +| | ++----------------------------------------------------------------------------+ +| Task : read the Timer value | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | +| | ++----------------------------------------------------------------------------+ +| Return Value : | +| for Timer: data[0]= Timer constant | +| | +| for watchdog: data[0]=0 (still running) | +| data[0]=1 (run down) | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI3120_InsnReadTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned char b_Tmp; + unsigned short us_TmpValue, us_TmpValue_2, us_StatusValue; + + if ((devpriv->b_Timer2Mode != APCI3120_WATCHDOG) + && (devpriv->b_Timer2Mode != APCI3120_TIMER)) { + comedi_error(dev, "\nread:timer2 not configured "); + } + + /* this_board->i_hwdrv_InsnReadTimer(dev,data); */ + if (devpriv->b_Timer2Mode == APCI3120_TIMER) { + + /* Read the LOW unsigned short of Timer 2 register */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_LOW_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + us_TmpValue = inw(devpriv->iobase + APCI3120_TIMER_VALUE); + + /* Read the HIGH unsigned short of Timer 2 register */ + b_Tmp = ((devpriv-> + b_DigitalOutputRegister) & 0xF0) | + APCI3120_SELECT_TIMER_2_HIGH_WORD; + outb(b_Tmp, devpriv->iobase + APCI3120_TIMER_CRT0); + + us_TmpValue_2 = inw(devpriv->iobase + APCI3120_TIMER_VALUE); + + /* combining both words */ + data[0] = (unsigned int) ((us_TmpValue) | ((us_TmpValue_2) << 16)); + + } else { /* Read watch dog status */ + + us_StatusValue = inw(devpriv->iobase + APCI3120_RD_STATUS); + us_StatusValue = + ((us_StatusValue & APCI3120_FC_TIMER) >> 12) & 1; + if (us_StatusValue == 1) { + /* RESET FC_TIMER BIT */ + inb(devpriv->iobase + APCI3120_TIMER_STATUS_REGISTER); + } + data[0] = us_StatusValue; /* when data[0] = 1 then the watch dog has rundown */ + } + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| DIGITAL INPUT SUBDEVICE | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnReadDigitalInput(struct comedi_device *dev, | +| struct comedi_subdevice *s, struct comedi_insn *insn,unsigned int *data) | +| | +| | ++----------------------------------------------------------------------------+ +| Task : Reads the value of the specified Digital input channel| +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_InsnReadDigitalInput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int ui_Chan, ui_TmpValue; + + ui_Chan = CR_CHAN(insn->chanspec); /* channel specified */ + + /* this_board->i_hwdrv_InsnReadDigitalInput(dev,ui_Chan,data); */ + if (ui_Chan <= 3) { + ui_TmpValue = (unsigned int) inw(devpriv->iobase + APCI3120_RD_STATUS); + +/* + * since only 1 channel reqd to bring it to last bit it is rotated 8 + * +(chan - 1) times then ANDed with 1 for last bit. + */ + *data = (ui_TmpValue >> (ui_Chan + 8)) & 1; + /* return 0; */ + } else { + /* comedi_error(dev," chan spec wrong"); */ + return -EINVAL; /* "sorry channel spec wrong " */ + } + return insn->n; + +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnBitsDigitalInput(struct comedi_device *dev, | +|struct comedi_subdevice *s, struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : Reads the value of the Digital input Port i.e.4channels| +| value is returned in data[0] | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI3120_InsnBitsDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_TmpValue; + ui_TmpValue = (unsigned int) inw(devpriv->iobase + APCI3120_RD_STATUS); + /***** state of 4 channels in the 11, 10, 9, 8 bits of status reg + rotated right 8 times to bring them to last four bits + ANDed with oxf for value. + *****/ + + *data = (ui_TmpValue >> 8) & 0xf; + /* this_board->i_hwdrv_InsnBitsDigitalInput(dev,data); */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| DIGITAL OUTPUT SUBDEVICE | ++----------------------------------------------------------------------------+ +*/ +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnConfigDigitalOutput(struct comedi_device | +| *dev,struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task :Configure the output memory ON or OFF | +| | ++----------------------------------------------------------------------------+ +| Input Parameters :struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_InsnConfigDigitalOutput(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + + if ((data[0] != 0) && (data[0] != 1)) { + comedi_error(dev, + "Not a valid Data !!! ,Data should be 1 or 0\n"); + return -EINVAL; + } + if (data[0]) { + devpriv->b_OutputMemoryStatus = APCI3120_ENABLE; + + } else { + devpriv->b_OutputMemoryStatus = APCI3120_DISABLE; + devpriv->b_DigitalOutputRegister = 0; + } + if (!devpriv->b_OutputMemoryStatus) + ui_Temp = 0; + /* if(!devpriv->b_OutputMemoryStatus ) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnBitsDigitalOutput(struct comedi_device *dev, | +| struct comedi_subdevice *s, struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : write diatal output port | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | +| data[0] Value to be written +| data[1] :1 Set digital o/p ON +| data[1] 2 Set digital o/p OFF with memory ON ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_InsnBitsDigitalOutput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if ((data[0] > devpriv->s_EeParameters.i_DoMaxdata) || (data[0] < 0)) { + + comedi_error(dev, "Data is not valid !!! \n"); + return -EINVAL; + } + + switch (data[1]) { + case 1: + data[0] = (data[0] << 4) | devpriv->b_DigitalOutputRegister; + break; + + case 2: + data[0] = data[0]; + break; + default: + printk("\nThe parameter passed is in error \n"); + return -EINVAL; + } /* switch(data[1]) */ + outb(data[0], devpriv->iobase + APCI3120_DIGITAL_OUTPUT); + + devpriv->b_DigitalOutputRegister = data[0] & 0xF0; + + return insn->n; + +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnWriteDigitalOutput(struct comedi_device *dev,| +|struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : Write digiatl output | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | + data[0] Value to be written + data[1] :1 Set digital o/p ON + data[1] 2 Set digital o/p OFF with memory ON ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_InsnWriteDigitalOutput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + + unsigned int ui_Temp1; + + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + + if ((data[0] != 0) && (data[0] != 1)) { + comedi_error(dev, + "Not a valid Data !!! ,Data should be 1 or 0\n"); + return -EINVAL; + } + if (ui_NoOfChannel > devpriv->s_EeParameters.i_NbrDoChannel - 1) { + comedi_error(dev, + "This board doesn't have specified channel !!! \n"); + return -EINVAL; + } + + switch (data[1]) { + case 1: + data[0] = (data[0] << ui_NoOfChannel); +/* ES05 data[0]=(data[0]<<4)|ui_Temp; */ + data[0] = (data[0] << 4) | devpriv->b_DigitalOutputRegister; + break; + + case 2: + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp1 = ui_Temp1 << 4; +/* ES05 ui_Temp=ui_Temp|ui_Temp1; */ + devpriv->b_DigitalOutputRegister = + devpriv->b_DigitalOutputRegister | ui_Temp1; + + data[0] = (data[0] << ui_NoOfChannel) ^ 0xf; + data[0] = data[0] << 4; +/* ES05 data[0]=data[0]& ui_Temp; */ + data[0] = data[0] & devpriv->b_DigitalOutputRegister; + break; + default: + printk("\nThe parameter passed is in error \n"); + return -EINVAL; + } /* switch(data[1]) */ + outb(data[0], devpriv->iobase + APCI3120_DIGITAL_OUTPUT); + +/* ES05 ui_Temp=data[0] & 0xf0; */ + devpriv->b_DigitalOutputRegister = data[0] & 0xf0; + return insn->n; + +} + +/* ++----------------------------------------------------------------------------+ +| ANALOG OUTPUT SUBDEVICE | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3120_InsnWriteAnalogOutput(struct comedi_device *dev,| +|struct comedi_subdevice *s, struct comedi_insn *insn,unsigned int *data) | +| | ++----------------------------------------------------------------------------+ +| Task : Write analog output | +| | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | +| struct comedi_subdevice *s | +| struct comedi_insn *insn | +| unsigned int *data | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3120_InsnWriteAnalogOutput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int ui_Range, ui_Channel; + unsigned short us_TmpValue; + + ui_Range = CR_RANGE(insn->chanspec); + ui_Channel = CR_CHAN(insn->chanspec); + + /* this_board->i_hwdrv_InsnWriteAnalogOutput(dev, ui_Range, ui_Channel,data[0]); */ + if (ui_Range) { /* if 1 then unipolar */ + + if (data[0] != 0) + data[0] = + ((((ui_Channel & 0x03) << 14) & 0xC000) | (1 << + 13) | (data[0] + 8191)); + else + data[0] = + ((((ui_Channel & 0x03) << 14) & 0xC000) | (1 << + 13) | 8192); + + } else { /* if 0 then bipolar */ + data[0] = + ((((ui_Channel & 0x03) << 14) & 0xC000) | (0 << 13) | + data[0]); + + } + +/* + * out put n values at the given channel. printk("\nwaiting for + * DA_READY BIT"); + */ + do { /* Waiting of DA_READY BIT */ + us_TmpValue = + ((unsigned short) inw(devpriv->iobase + + APCI3120_RD_STATUS)) & 0x0001; + } while (us_TmpValue != 0x0001); + + if (ui_Channel <= 3) +/* + * for channel 0-3 out at the register 1 (wrDac1-8) data[i] + * typecasted to ushort since word write is to be done + */ + outw((unsigned short) data[0], + devpriv->iobase + APCI3120_ANALOG_OUTPUT_1); + else +/* + * for channel 4-7 out at the register 2 (wrDac5-8) data[i] + * typecasted to ushort since word write is to be done + */ + outw((unsigned short) data[0], + devpriv->iobase + APCI3120_ANALOG_OUTPUT_2); + + return insn->n; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.h new file mode 100644 index 00000000..50eb0a0a --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3120.h @@ -0,0 +1,249 @@ + +/* hwdrv_apci3120.h */ + +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* comedi related defines */ + +/* ANALOG INPUT RANGE */ +static const struct comedi_lrange range_apci3120_ai = { 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1) + } +}; + +/* ANALOG OUTPUT RANGE */ +static const struct comedi_lrange range_apci3120_ao = { 2, { + BIP_RANGE(10), + UNI_RANGE(10) + } +}; + +#define APCI3120_BIPOLAR_RANGES 4 /* used for test on mixture of BIP/UNI ranges */ + +#define APCI3120_BOARD_VENDOR_ID 0x10E8 +#define APCI3120_ADDRESS_RANGE 16 + +#define APCI3120_DISABLE 0 +#define APCI3120_ENABLE 1 + +#define APCI3120_START 1 +#define APCI3120_STOP 0 + +#define APCI3120_EOC_MODE 1 +#define APCI3120_EOS_MODE 2 +#define APCI3120_DMA_MODE 3 + +/* DIGITAL INPUT-OUTPUT DEFINE */ + +#define APCI3120_DIGITAL_OUTPUT 0x0D +#define APCI3120_RD_STATUS 0x02 +#define APCI3120_RD_FIFO 0x00 + +/* digital output insn_write ON /OFF selection */ +#define APCI3120_SET4DIGITALOUTPUTON 1 +#define APCI3120_SET4DIGITALOUTPUTOFF 0 + +/* analog output SELECT BIT */ +#define APCI3120_ANALOG_OP_CHANNEL_1 0x0000 +#define APCI3120_ANALOG_OP_CHANNEL_2 0x4000 +#define APCI3120_ANALOG_OP_CHANNEL_3 0x8000 +#define APCI3120_ANALOG_OP_CHANNEL_4 0xC000 +#define APCI3120_ANALOG_OP_CHANNEL_5 0x0000 +#define APCI3120_ANALOG_OP_CHANNEL_6 0x4000 +#define APCI3120_ANALOG_OP_CHANNEL_7 0x8000 +#define APCI3120_ANALOG_OP_CHANNEL_8 0xC000 + +/* Enable external trigger bit in nWrAddress */ +#define APCI3120_ENABLE_EXT_TRIGGER 0x8000 + +/* ANALOG OUTPUT AND INPUT DEFINE */ +#define APCI3120_UNIPOLAR 0x80 /* $$ RAM sequence polarity BIT */ +#define APCI3120_BIPOLAR 0x00 /* $$ RAM sequence polarity BIT */ +#define APCI3120_ANALOG_OUTPUT_1 0x08 /* (ADDRESS ) */ +#define APCI3120_ANALOG_OUTPUT_2 0x0A /* (ADDRESS ) */ +#define APCI3120_1_GAIN 0x00 /* $$ RAM sequence Gain Bits for gain 1 */ +#define APCI3120_2_GAIN 0x10 /* $$ RAM sequence Gain Bits for gain 2 */ +#define APCI3120_5_GAIN 0x20 /* $$ RAM sequence Gain Bits for gain 5 */ +#define APCI3120_10_GAIN 0x30 /* $$ RAM sequence Gain Bits for gain 10 */ +#define APCI3120_SEQ_RAM_ADDRESS 0x06 /* $$ EARLIER NAMED APCI3120_FIFO_ADDRESS */ +#define APCI3120_RESET_FIFO 0x0C /* (ADDRESS) */ +#define APCI3120_TIMER_0_MODE_2 0x01 /* $$ Bits for timer mode */ +#define APCI3120_TIMER_0_MODE_4 0x2 +#define APCI3120_SELECT_TIMER_0_WORD 0x00 +#define APCI3120_ENABLE_TIMER0 0x1000 /* $$Gatebit 0 in nWrAddress */ +#define APCI3120_CLEAR_PR 0xF0FF +#define APCI3120_CLEAR_PA 0xFFF0 +#define APCI3120_CLEAR_PA_PR (APCI3120_CLEAR_PR & APCI3120_CLEAR_PA) + +/* nWrMode_Select */ +#define APCI3120_ENABLE_SCAN 0x8 /* $$ bit in nWrMode_Select */ +#define APCI3120_DISABLE_SCAN (~APCI3120_ENABLE_SCAN) +#define APCI3120_ENABLE_EOS_INT 0x2 /* $$ bit in nWrMode_Select */ + +#define APCI3120_DISABLE_EOS_INT (~APCI3120_ENABLE_EOS_INT) +#define APCI3120_ENABLE_EOC_INT 0x1 +#define APCI3120_DISABLE_EOC_INT (~APCI3120_ENABLE_EOC_INT) +#define APCI3120_DISABLE_ALL_INTERRUPT_WITHOUT_TIMER (APCI3120_DISABLE_EOS_INT & APCI3120_DISABLE_EOC_INT) +#define APCI3120_DISABLE_ALL_INTERRUPT (APCI3120_DISABLE_TIMER_INT & APCI3120_DISABLE_EOS_INT & APCI3120_DISABLE_EOC_INT) + +/* status register bits */ +#define APCI3120_EOC 0x8000 +#define APCI3120_EOS 0x2000 + +/* software trigger dummy register */ +#define APCI3120_START_CONVERSION 0x02 /* (ADDRESS) */ + +/* TIMER DEFINE */ +#define APCI3120_QUARTZ_A 70 +#define APCI3120_QUARTZ_B 50 +#define APCI3120_TIMER 1 +#define APCI3120_WATCHDOG 2 +#define APCI3120_TIMER_DISABLE 0 +#define APCI3120_TIMER_ENABLE 1 +#define APCI3120_ENABLE_TIMER2 0x4000 /* $$ gatebit 2 in nWrAddress */ +#define APCI3120_DISABLE_TIMER2 (~APCI3120_ENABLE_TIMER2) +#define APCI3120_ENABLE_TIMER_INT 0x04 /* $$ ENAIRQ_FC_Bit in nWrModeSelect */ +#define APCI3120_DISABLE_TIMER_INT (~APCI3120_ENABLE_TIMER_INT) +#define APCI3120_WRITE_MODE_SELECT 0x0E /* (ADDRESS) */ +#define APCI3120_SELECT_TIMER_0_WORD 0x00 +#define APCI3120_SELECT_TIMER_1_WORD 0x01 +#define APCI3120_TIMER_1_MODE_2 0x4 + +/* $$ BIT FOR MODE IN nCsTimerCtr1 */ +#define APCI3120_TIMER_2_MODE_0 0x0 +#define APCI3120_TIMER_2_MODE_2 0x10 +#define APCI3120_TIMER_2_MODE_5 0x30 + +/* $$ BIT FOR MODE IN nCsTimerCtr0 */ +#define APCI3120_SELECT_TIMER_2_LOW_WORD 0x02 +#define APCI3120_SELECT_TIMER_2_HIGH_WORD 0x03 + +#define APCI3120_TIMER_CRT0 0x0D /* (ADDRESS for cCsTimerCtr0) */ +#define APCI3120_TIMER_CRT1 0x0C /* (ADDRESS for cCsTimerCtr1) */ + +#define APCI3120_TIMER_VALUE 0x04 /* ADDRESS for nCsTimerWert */ +#define APCI3120_TIMER_STATUS_REGISTER 0x0D /* ADDRESS for delete timer 2 interrupt */ +#define APCI3120_RD_STATUS 0x02 /* ADDRESS */ +#define APCI3120_WR_ADDRESS 0x00 /* ADDRESS */ +#define APCI3120_ENABLE_WATCHDOG 0x20 /* $$BIT in nWrMode_Select */ +#define APCI3120_DISABLE_WATCHDOG (~APCI3120_ENABLE_WATCHDOG) +#define APCI3120_ENABLE_TIMER_COUNTER 0x10 /* $$BIT in nWrMode_Select */ +#define APCI3120_DISABLE_TIMER_COUNTER (~APCI3120_ENABLE_TIMER_COUNTER) +#define APCI3120_FC_TIMER 0x1000 /* bit in status register */ +#define APCI3120_ENABLE_TIMER0 0x1000 +#define APCI3120_ENABLE_TIMER1 0x2000 +#define APCI3120_ENABLE_TIMER2 0x4000 +#define APCI3120_DISABLE_TIMER0 (~APCI3120_ENABLE_TIMER0) +#define APCI3120_DISABLE_TIMER1 (~APCI3120_ENABLE_TIMER1) +#define APCI3120_DISABLE_TIMER2 (~APCI3120_ENABLE_TIMER2) + +#define APCI3120_TIMER2_SELECT_EOS 0xC0 /* ADDED on 20-6 */ +#define APCI3120_COUNTER 3 /* on 20-6 */ +#define APCI3120_DISABLE_ALL_TIMER (APCI3120_DISABLE_TIMER0 & APCI3120_DISABLE_TIMER1 & APCI3120_DISABLE_TIMER2) /* on 20-6 */ + +#define MAX_ANALOGINPUT_CHANNELS 32 + +struct str_AnalogReadInformation { + + unsigned char b_Type; /* EOC or EOS */ + unsigned char b_InterruptFlag; /* Interrupt use or not */ + unsigned int ui_ConvertTiming; /* Selection of the conversion time */ + unsigned char b_NbrOfChannel; /* Number of channel to read */ + unsigned int ui_ChannelList[MAX_ANALOGINPUT_CHANNELS]; /* Number of the channel to be read */ + unsigned int ui_RangeList[MAX_ANALOGINPUT_CHANNELS]; /* Gain of each channel */ + +}; + + +/* Function Declaration For APCI-3120 */ + +/* Internal functions */ +int i_APCI3120_SetupChannelList(struct comedi_device *dev, struct comedi_subdevice *s, + int n_chan, unsigned int *chanlist, char check); +int i_APCI3120_ExttrigEnable(struct comedi_device *dev); +int i_APCI3120_ExttrigDisable(struct comedi_device *dev); +int i_APCI3120_StopCyclicAcquisition(struct comedi_device *dev, struct comedi_subdevice *s); +int i_APCI3120_Reset(struct comedi_device *dev); +int i_APCI3120_CyclicAnalogInput(int mode, struct comedi_device *dev, + struct comedi_subdevice *s); +/* Interrupt functions */ +void v_APCI3120_Interrupt(int irq, void *d); +/* UPDATE-0.7.57->0.7.68 void v_APCI3120_InterruptDmaMoveBlock16bit(struct comedi_device *dev,struct comedi_subdevice *s,short *dma,short *data,int n); */ +void v_APCI3120_InterruptDmaMoveBlock16bit(struct comedi_device *dev, + struct comedi_subdevice *s, + short *dma_buffer, + unsigned int num_samples); +int i_APCI3120_InterruptHandleEos(struct comedi_device *dev); +void v_APCI3120_InterruptDma(int irq, void *d); + +/* TIMER */ + +int i_APCI3120_InsnConfigTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3120_InsnWriteTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3120_InsnReadTimer(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* +* DI for di read +*/ + +int i_APCI3120_InsnBitsDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3120_InsnReadDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* DO */ +/* int i_APCI3120_WriteDigitalOutput(struct comedi_device *dev, + * unsigned char data); + */ +int i_APCI3120_InsnConfigDigitalOutput(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +int i_APCI3120_InsnBitsDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3120_InsnWriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* AO */ +/* int i_APCI3120_Write1AnalogValue(struct comedi_device *dev,UINT ui_Range, + * UINT ui_Channel,UINT data ); + */ + +int i_APCI3120_InsnWriteAnalogOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* AI HArdware layer */ + +int i_APCI3120_InsnConfigAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3120_InsnReadAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3120_CommandTestAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); +int i_APCI3120_CommandAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s); +/* int i_APCI3120_CancelAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s); */ +int i_APCI3120_StopCyclicAcquisition(struct comedi_device *dev, struct comedi_subdevice *s); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c new file mode 100644 index 00000000..f9545b06 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.c @@ -0,0 +1,3635 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-3200 | Compiler : GCC | + | Module name : hwdrv_apci3200.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-3200 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | 02.07.04 | J. Krauth | Modification from the driver in order to | + | | | correct some errors when using several boards. | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ + | 26.10.04 | J. Krauth | - Update for COMEDI 0.7.68 | + | | | - Read eeprom value | + | | | - Append APCI-3300 | + +----------+-----------+------------------------------------------------+ +*/ + +/* + +----------------------------------------------------------------------------+ + | Included files | + +----------------------------------------------------------------------------+ +*/ +#include "hwdrv_apci3200.h" +/* Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ +#include "addi_amcc_S5920.h" +/* #define PRINT_INFO */ + +/* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + +/* BEGIN JK 06.07.04: Management of sevrals boards */ +/* + int i_CJCAvailable=1; + int i_CJCPolarity=0; + int i_CJCGain=2;/* changed from 0 to 2 */ + int i_InterruptFlag=0; + int i_ADDIDATAPolarity; + int i_ADDIDATAGain; + int i_AutoCalibration=0; /* : auto calibration */ + int i_ADDIDATAConversionTime; + int i_ADDIDATAConversionTimeUnit; + int i_ADDIDATAType; + int i_ChannelNo; + int i_ChannelCount=0; + int i_ScanType; + int i_FirstChannel; + int i_LastChannel; + int i_Sum=0; + int i_Offset; + unsigned int ui_Channel_num=0; + static int i_Count=0; + int i_Initialised=0; + unsigned int ui_InterruptChannelValue[96]; /* Buffer */ +*/ +struct str_BoardInfos s_BoardInfos[100]; /* 100 will be the max number of boards to be used */ +/* END JK 06.07.04: Management of sevrals boards */ + +/* Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + +/*+----------------------------------------------------------------------------+*/ +/*| Function Name : int i_AddiHeaderRW_ReadEeprom |*/ +/*| (int i_NbOfWordsToRead, |*/ +/*| unsigned int dw_PCIBoardEepromAddress, |*/ +/*| unsigned short w_EepromStartAddress, |*/ +/*| unsigned short * pw_DataRead) |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Task : Read word from the 5920 eeprom. |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Input Parameters : int i_NbOfWordsToRead : Nbr. of word to read |*/ +/*| unsigned int dw_PCIBoardEepromAddress : Address of the eeprom |*/ +/*| unsigned short w_EepromStartAddress : Eeprom start address |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Output Parameters : unsigned short * pw_DataRead : Read data |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Return Value : - |*/ +/*+----------------------------------------------------------------------------+*/ + +int i_AddiHeaderRW_ReadEeprom(int i_NbOfWordsToRead, + unsigned int dw_PCIBoardEepromAddress, + unsigned short w_EepromStartAddress, unsigned short *pw_DataRead) +{ + unsigned int dw_eeprom_busy = 0; + int i_Counter = 0; + int i_WordCounter; + int i; + unsigned char pb_ReadByte[1]; + unsigned char b_ReadLowByte = 0; + unsigned char b_ReadHighByte = 0; + unsigned char b_SelectedAddressLow = 0; + unsigned char b_SelectedAddressHigh = 0; + unsigned short w_ReadWord = 0; + + for (i_WordCounter = 0; i_WordCounter < i_NbOfWordsToRead; + i_WordCounter++) { + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + for (i_Counter = 0; i_Counter < 2; i_Counter++) { + b_SelectedAddressLow = (w_EepromStartAddress + i_Counter) % 256; /* Read the low 8 bit part */ + b_SelectedAddressHigh = (w_EepromStartAddress + i_Counter) / 256; /* Read the high 8 bit part */ + + /* Select the load low address mode */ + outb(NVCMD_LOAD_LOW, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 3); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Load the low address */ + outb(b_SelectedAddressLow, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 2); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Select the load high address mode */ + outb(NVCMD_LOAD_HIGH, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 3); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Load the high address */ + outb(b_SelectedAddressHigh, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 2); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Select the READ mode */ + outb(NVCMD_BEGIN_READ, + dw_PCIBoardEepromAddress + AMCC_OP_REG_MCSR + + 3); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Read data into the EEPROM */ + *pb_ReadByte = + inb(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR + 2); + + /* Wait on busy */ + do { + dw_eeprom_busy = + inl(dw_PCIBoardEepromAddress + + AMCC_OP_REG_MCSR); + dw_eeprom_busy = dw_eeprom_busy & EEPROM_BUSY; + } while (dw_eeprom_busy == EEPROM_BUSY); + + /* Select the upper address part */ + if (i_Counter == 0) + b_ReadLowByte = pb_ReadByte[0]; + else + b_ReadHighByte = pb_ReadByte[0]; + + + /* Sleep */ + msleep(1); + + } + w_ReadWord = + (b_ReadLowByte | (((unsigned short)b_ReadHighByte) * + 256)); + + pw_DataRead[i_WordCounter] = w_ReadWord; + + w_EepromStartAddress += 2; /* to read the next word */ + + } /* for (...) i_NbOfWordsToRead */ + return 0; +} + +/*+----------------------------------------------------------------------------+*/ +/*| Function Name : void v_GetAPCI3200EepromCalibrationValue (void) |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Task : Read calibration value from the APCI-3200 eeprom. |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Input Parameters : - |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Output Parameters : - |*/ +/*+----------------------------------------------------------------------------+*/ +/*| Return Value : - |*/ +/*+----------------------------------------------------------------------------+*/ + +void v_GetAPCI3200EepromCalibrationValue(unsigned int dw_PCIBoardEepromAddress, + struct str_BoardInfos *BoardInformations) +{ + unsigned short w_AnalogInputMainHeaderAddress; + unsigned short w_AnalogInputComponentAddress; + unsigned short w_NumberOfModuls = 0; + unsigned short w_CurrentSources[2]; + unsigned short w_ModulCounter = 0; + unsigned short w_FirstHeaderSize = 0; + unsigned short w_NumberOfInputs = 0; + unsigned short w_CJCFlag = 0; + unsigned short w_NumberOfGainValue = 0; + unsigned short w_SingleHeaderAddress = 0; + unsigned short w_SingleHeaderSize = 0; + unsigned short w_Input = 0; + unsigned short w_GainFactorAddress = 0; + unsigned short w_GainFactorValue[2]; + unsigned short w_GainIndex = 0; + unsigned short w_GainValue = 0; + + /*****************************************/ + /** Get the Analog input header address **/ + /*****************************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, 0x116, /* w_EepromStartAddress: Analog input header address */ + &w_AnalogInputMainHeaderAddress); + + /*******************************************/ + /** Compute the real analog input address **/ + /*******************************************/ + w_AnalogInputMainHeaderAddress = w_AnalogInputMainHeaderAddress + 0x100; + + /******************************/ + /** Get the number of moduls **/ + /******************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputMainHeaderAddress + 0x02, /* w_EepromStartAddress: Number of conponment */ + &w_NumberOfModuls); + + for (w_ModulCounter = 0; w_ModulCounter < w_NumberOfModuls; + w_ModulCounter++) { + /***********************************/ + /** Compute the component address **/ + /***********************************/ + w_AnalogInputComponentAddress = + w_AnalogInputMainHeaderAddress + + (w_FirstHeaderSize * w_ModulCounter) + 0x04; + + /****************************/ + /** Read first header size **/ + /****************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress, /* Address of the first header */ + &w_FirstHeaderSize); + + w_FirstHeaderSize = w_FirstHeaderSize >> 4; + + /***************************/ + /** Read number of inputs **/ + /***************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 0x06, /* Number of inputs for the first modul */ + &w_NumberOfInputs); + + w_NumberOfInputs = w_NumberOfInputs >> 4; + + /***********************/ + /** Read the CJC flag **/ + /***********************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 0x08, /* CJC flag */ + &w_CJCFlag); + + w_CJCFlag = (w_CJCFlag >> 3) & 0x1; /* Get only the CJC flag */ + + /*******************************/ + /** Read number of gain value **/ + /*******************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 0x44, /* Number of gain value */ + &w_NumberOfGainValue); + + w_NumberOfGainValue = w_NumberOfGainValue & 0xFF; + + /***********************************/ + /** Compute single header address **/ + /***********************************/ + w_SingleHeaderAddress = + w_AnalogInputComponentAddress + 0x46 + + (((w_NumberOfGainValue / 16) + 1) * 2) + + (6 * w_NumberOfGainValue) + + (4 * (((w_NumberOfGainValue / 16) + 1) * 2)); + + /********************************************/ + /** Read current sources value for input 1 **/ + /********************************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_SingleHeaderAddress, /* w_EepromStartAddress: Single header address */ + &w_SingleHeaderSize); + + w_SingleHeaderSize = w_SingleHeaderSize >> 4; + + /*************************************/ + /** Read gain factor for the module **/ + /*************************************/ + w_GainFactorAddress = w_AnalogInputComponentAddress; + + for (w_GainIndex = 0; w_GainIndex < w_NumberOfGainValue; + w_GainIndex++) { + /************************************/ + /** Read gain value for the module **/ + /************************************/ + i_AddiHeaderRW_ReadEeprom(1, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 70 + (2 * (1 + (w_NumberOfGainValue / 16))) + (0x02 * w_GainIndex), /* Gain value */ + &w_GainValue); + + BoardInformations->s_Module[w_ModulCounter]. + w_GainValue[w_GainIndex] = w_GainValue; + +# ifdef PRINT_INFO + printk("\n Gain value = %d", + BoardInformations->s_Module[w_ModulCounter]. + w_GainValue[w_GainIndex]); +# endif + + /*************************************/ + /** Read gain factor for the module **/ + /*************************************/ + i_AddiHeaderRW_ReadEeprom(2, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, w_AnalogInputComponentAddress + 70 + ((2 * w_NumberOfGainValue) + (2 * (1 + (w_NumberOfGainValue / 16)))) + (0x04 * w_GainIndex), /* Gain factor */ + w_GainFactorValue); + + BoardInformations->s_Module[w_ModulCounter]. + ul_GainFactor[w_GainIndex] = + (w_GainFactorValue[1] << 16) + + w_GainFactorValue[0]; + +# ifdef PRINT_INFO + printk("\n w_GainFactorValue [%d] = %lu", w_GainIndex, + BoardInformations->s_Module[w_ModulCounter]. + ul_GainFactor[w_GainIndex]); +# endif + } + + /***************************************************************/ + /** Read current source value for each channels of the module **/ + /***************************************************************/ + for (w_Input = 0; w_Input < w_NumberOfInputs; w_Input++) { + /********************************************/ + /** Read current sources value for input 1 **/ + /********************************************/ + i_AddiHeaderRW_ReadEeprom(2, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, + (w_Input * w_SingleHeaderSize) + + w_SingleHeaderAddress + 0x0C, w_CurrentSources); + + /************************************/ + /** Save the current sources value **/ + /************************************/ + BoardInformations->s_Module[w_ModulCounter]. + ul_CurrentSource[w_Input] = + (w_CurrentSources[0] + + ((w_CurrentSources[1] & 0xFFF) << 16)); + +# ifdef PRINT_INFO + printk("\n Current sources [%d] = %lu", w_Input, + BoardInformations->s_Module[w_ModulCounter]. + ul_CurrentSource[w_Input]); +# endif + } + + /***************************************/ + /** Read the CJC current source value **/ + /***************************************/ + i_AddiHeaderRW_ReadEeprom(2, /* i_NbOfWordsToRead */ + dw_PCIBoardEepromAddress, + (w_Input * w_SingleHeaderSize) + w_SingleHeaderAddress + + 0x0C, w_CurrentSources); + + /************************************/ + /** Save the current sources value **/ + /************************************/ + BoardInformations->s_Module[w_ModulCounter]. + ul_CurrentSourceCJC = + (w_CurrentSources[0] + + ((w_CurrentSources[1] & 0xFFF) << 16)); + +# ifdef PRINT_INFO + printk("\n Current sources CJC = %lu", + BoardInformations->s_Module[w_ModulCounter]. + ul_CurrentSourceCJC); +# endif + } +} + +int i_APCI3200_GetChannelCalibrationValue(struct comedi_device *dev, + unsigned int ui_Channel_num, unsigned int *CJCCurrentSource, + unsigned int *ChannelCurrentSource, unsigned int *ChannelGainFactor) +{ + int i_DiffChannel = 0; + int i_Module = 0; + +#ifdef PRINT_INFO + printk("\n Channel = %u", ui_Channel_num); +#endif + + /* Test if single or differential mode */ + if (s_BoardInfos[dev->minor].i_ConnectionType == 1) { + /* if diff */ + + if (ui_Channel_num <= 1) + i_DiffChannel = ui_Channel_num, i_Module = 0; + else if ((ui_Channel_num >= 2) && (ui_Channel_num <= 3)) + i_DiffChannel = ui_Channel_num - 2, i_Module = 1; + else if ((ui_Channel_num >= 4) && (ui_Channel_num <= 5)) + i_DiffChannel = ui_Channel_num - 4, i_Module = 2; + else if ((ui_Channel_num >= 6) && (ui_Channel_num <= 7)) + i_DiffChannel = ui_Channel_num - 6, i_Module = 3; + + } else { + /* if single */ + if ((ui_Channel_num == 0) || (ui_Channel_num == 1)) + i_DiffChannel = 0, i_Module = 0; + else if ((ui_Channel_num == 2) || (ui_Channel_num == 3)) + i_DiffChannel = 1, i_Module = 0; + else if ((ui_Channel_num == 4) || (ui_Channel_num == 5)) + i_DiffChannel = 0, i_Module = 1; + else if ((ui_Channel_num == 6) || (ui_Channel_num == 7)) + i_DiffChannel = 1, i_Module = 1; + else if ((ui_Channel_num == 8) || (ui_Channel_num == 9)) + i_DiffChannel = 0, i_Module = 2; + else if ((ui_Channel_num == 10) || (ui_Channel_num == 11)) + i_DiffChannel = 1, i_Module = 2; + else if ((ui_Channel_num == 12) || (ui_Channel_num == 13)) + i_DiffChannel = 0, i_Module = 3; + else if ((ui_Channel_num == 14) || (ui_Channel_num == 15)) + i_DiffChannel = 1, i_Module = 3; + } + + /* Test if thermocouple or RTD mode */ + *CJCCurrentSource = + s_BoardInfos[dev->minor].s_Module[i_Module].ul_CurrentSourceCJC; +#ifdef PRINT_INFO + printk("\n CJCCurrentSource = %lu", *CJCCurrentSource); +#endif + + *ChannelCurrentSource = + s_BoardInfos[dev->minor].s_Module[i_Module]. + ul_CurrentSource[i_DiffChannel]; +#ifdef PRINT_INFO + printk("\n ChannelCurrentSource = %lu", *ChannelCurrentSource); +#endif + /* } */ + /* } */ + + /* Channle gain factor */ + *ChannelGainFactor = + s_BoardInfos[dev->minor].s_Module[i_Module]. + ul_GainFactor[s_BoardInfos[dev->minor].i_ADDIDATAGain]; +#ifdef PRINT_INFO + printk("\n ChannelGainFactor = %lu", *ChannelGainFactor); +#endif + /* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + return 0; +} + +/* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ReadDigitalInput | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Read value of the selected channel or port | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | unsigned int ui_NoOfChannels : No Of Channels To read for Port + Channel Numberfor single channel + | unsigned int data[0] : 0: Read single channel + 1: Read port value + data[1] Port number + +----------------------------------------------------------------------------+ + | Output Parameters : -- data[0] :Read status value + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ + +int i_APCI3200_ReadDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp = 0; + unsigned int ui_NoOfChannel = 0; + ui_NoOfChannel = CR_CHAN(insn->chanspec); + ui_Temp = data[0]; + *data = inl(devpriv->i_IobaseReserved); + + if (ui_Temp == 0) { + *data = (*data >> ui_NoOfChannel) & 0x1; + } /* if (ui_Temp==0) */ + else { + if (ui_Temp == 1) { + if (data[1] < 0 || data[1] > 1) { + printk("\nThe port number is in error\n"); + return -EINVAL; + } /* if(data[1] < 0 || data[1] >1) */ + switch (ui_NoOfChannel) { + + case 2: + *data = (*data >> (2 * data[1])) & 0x3; + break; + case 3: + *data = (*data & 15); + break; + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + + } /* switch(ui_NoOfChannels) */ + } /* if (ui_Temp==1) */ + else { + printk("\nSpecified channel not supported \n"); + } /* elseif (ui_Temp==1) */ + } + return insn->n; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ConfigDigitalOutput | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Configures The Digital Output Subdevice. | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | data[0] :1 Memory enable + 0 Memory Disable + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + if ((data[0] != 0) && (data[0] != 1)) { + comedi_error(dev, + "Not a valid Data !!! ,Data should be 1 or 0\n"); + return -EINVAL; + } /* if ( (data[0]!=0) && (data[0]!=1) ) */ + if (data[0]) { + devpriv->b_OutputMemoryStatus = ADDIDATA_ENABLE; + } /* if (data[0]) */ + else { + devpriv->b_OutputMemoryStatus = ADDIDATA_DISABLE; + } /* else if (data[0]) */ + return insn->n; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_WriteDigitalOutput | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : writes To the digital Output Subdevice | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | struct comedi_subdevice *s : Subdevice Pointer | + | struct comedi_insn *insn : Insn Structure Pointer | + | unsigned int *data : Data Pointer contains | + | configuration parameters as below | + | data[0] :Value to output + data[1] : 0 o/p single channel + 1 o/p port + data[2] : port no + data[3] :0 set the digital o/p on + 1 set the digital o/p off + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp = 0, ui_Temp1 = 0; + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + if (devpriv->b_OutputMemoryStatus) { + ui_Temp = inl(devpriv->i_IobaseAddon); + + } /* if(devpriv->b_OutputMemoryStatus ) */ + else { + ui_Temp = 0; + } /* if(devpriv->b_OutputMemoryStatus ) */ + if (data[3] == 0) { + if (data[1] == 0) { + data[0] = (data[0] << ui_NoOfChannel) | ui_Temp; + outl(data[0], devpriv->i_IobaseAddon); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = + (data[0] << (2 * + data[2])) | ui_Temp; + break; + case 3: + data[0] = (data[0] | ui_Temp); + break; + } /* switch(ui_NoOfChannels) */ + + outl(data[0], devpriv->i_IobaseAddon); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==0) */ + else { + if (data[3] == 1) { + if (data[1] == 0) { + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = (data[0] << ui_NoOfChannel) ^ 0xf; + data[0] = data[0] & ui_Temp; + outl(data[0], devpriv->i_IobaseAddon); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + switch (ui_NoOfChannel) { + + case 2: + data[0] = ~data[0] & 0x3; + ui_Temp1 = 3; + ui_Temp1 = + ui_Temp1 << 2 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (2 * + data + [2])) ^ + 0xf) & ui_Temp; + + break; + case 3: + break; + + default: + comedi_error(dev, + " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + } /* switch(ui_NoOfChannels) */ + + outl(data[0], devpriv->i_IobaseAddon); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==1); */ + else { + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* if else data[3]==1) */ + } /* if else data[3]==0) */ + return insn->n; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ReadDigitalOutput | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Read value of the selected channel or port | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | unsigned int ui_NoOfChannels : No Of Channels To read | + | unsigned int *data : Data Pointer to read status | + data[0] :0 read single channel + 1 read port value + data[1] port no + + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp; + unsigned int ui_NoOfChannel; + ui_NoOfChannel = CR_CHAN(insn->chanspec); + ui_Temp = data[0]; + *data = inl(devpriv->i_IobaseAddon); + if (ui_Temp == 0) { + *data = (*data >> ui_NoOfChannel) & 0x1; + } /* if (ui_Temp==0) */ + else { + if (ui_Temp == 1) { + if (data[1] < 0 || data[1] > 1) { + printk("\nThe port selection is in error\n"); + return -EINVAL; + } /* if(data[1] <0 ||data[1] >1) */ + switch (ui_NoOfChannel) { + case 2: + *data = (*data >> (2 * data[1])) & 3; + break; + + case 3: + break; + + default: + comedi_error(dev, " chan spec wrong"); + return -EINVAL; /* "sorry channel spec wrong " */ + break; + } /* switch(ui_NoOfChannels) */ + } /* if (ui_Temp==1) */ + else { + printk("\nSpecified channel not supported \n"); + } /* else if (ui_Temp==1) */ + } /* else if (ui_Temp==0) */ + return insn->n; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ConfigAnalogInput | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Configures The Analog Input Subdevice | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | struct comedi_subdevice *s : Subdevice Pointer | + | struct comedi_insn *insn : Insn Structure Pointer | + | unsigned int *data : Data Pointer contains | + | configuration parameters as below | + | | + | data[0] + | 0:Normal AI | + | 1:RTD | + | 2:THERMOCOUPLE | + | data[1] : Gain To Use | + | | + | data[2] : Polarity + | 0:Bipolar | + | 1:Unipolar | + | | + | data[3] : Offset Range + | | + | data[4] : Coupling + | 0:DC Coupling | + | 1:AC Coupling | + | | + | data[5] :Differential/Single + | 0:Single | + | 1:Differential | + | | + | data[6] :TimerReloadValue + | | + | data[7] :ConvertingTimeUnit + | | + | data[8] :0 Analog voltage measurement + 1 Resistance measurement + 2 Temperature measurement + | data[9] :Interrupt + | 0:Disable + | 1:Enable + data[10] :Type of Thermocouple + | data[11] : 0: single channel + Module Number + | + | data[12] + | 0:Single Read + | 1:Read more channel + 2:Single scan + | 3:Continuous Scan + data[13] :Number of channels to read + | data[14] :RTD connection type + :0:RTD not used + 1:RTD 2 wire connection + 2:RTD 3 wire connection + 3:RTD 4 wire connection + | | + | | + | | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_ConfigAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + unsigned int ul_Config = 0, ul_Temp = 0; + unsigned int ui_ChannelNo = 0; + unsigned int ui_Dummy = 0; + int i_err = 0; + + /* Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + +#ifdef PRINT_INFO + int i = 0, i2 = 0; +#endif + /* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* Initialize the structure */ + if (s_BoardInfos[dev->minor].b_StructInitialized != 1) { + s_BoardInfos[dev->minor].i_CJCAvailable = 1; + s_BoardInfos[dev->minor].i_CJCPolarity = 0; + s_BoardInfos[dev->minor].i_CJCGain = 2; /* changed from 0 to 2 */ + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + s_BoardInfos[dev->minor].i_AutoCalibration = 0; /* : auto calibration */ + s_BoardInfos[dev->minor].i_ChannelCount = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + s_BoardInfos[dev->minor].ui_Channel_num = 0; + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Initialised = 0; + s_BoardInfos[dev->minor].b_StructInitialized = 1; + + /* Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + s_BoardInfos[dev->minor].i_ConnectionType = 0; + /* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /* Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + memset(s_BoardInfos[dev->minor].s_Module, 0, + sizeof(s_BoardInfos[dev->minor].s_Module[MAX_MODULE])); + + v_GetAPCI3200EepromCalibrationValue(devpriv->i_IobaseAmcc, + &s_BoardInfos[dev->minor]); + +#ifdef PRINT_INFO + for (i = 0; i < MAX_MODULE; i++) { + printk("\n s_Module[%i].ul_CurrentSourceCJC = %lu", i, + s_BoardInfos[dev->minor].s_Module[i]. + ul_CurrentSourceCJC); + + for (i2 = 0; i2 < 5; i2++) { + printk("\n s_Module[%i].ul_CurrentSource [%i] = %lu", i, i2, s_BoardInfos[dev->minor].s_Module[i].ul_CurrentSource[i2]); + } + + for (i2 = 0; i2 < 8; i2++) { + printk("\n s_Module[%i].ul_GainFactor [%i] = %lu", i, i2, s_BoardInfos[dev->minor].s_Module[i].ul_GainFactor[i2]); + } + + for (i2 = 0; i2 < 8; i2++) { + printk("\n s_Module[%i].w_GainValue [%i] = %u", + i, i2, + s_BoardInfos[dev->minor].s_Module[i]. + w_GainValue[i2]); + } + } +#endif + /* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } + + if (data[0] != 0 && data[0] != 1 && data[0] != 2) { + printk("\nThe selection of acquisition type is in error\n"); + i_err++; + } /* if(data[0]!=0 && data[0]!=1 && data[0]!=2) */ + if (data[0] == 1) { + if (data[14] != 0 && data[14] != 1 && data[14] != 2 + && data[14] != 4) { + printk("\n Error in selection of RTD connection type\n"); + i_err++; + } /* if(data[14]!=0 && data[14]!=1 && data[14]!=2 && data[14]!=4) */ + } /* if(data[0]==1 ) */ + if (data[1] < 0 || data[1] > 7) { + printk("\nThe selection of gain is in error\n"); + i_err++; + } /* if(data[1]<0 || data[1]>7) */ + if (data[2] != 0 && data[2] != 1) { + printk("\nThe selection of polarity is in error\n"); + i_err++; + } /* if(data[2]!=0 && data[2]!=1) */ + if (data[3] != 0) { + printk("\nThe selection of offset range is in error\n"); + i_err++; + } /* if(data[3]!=0) */ + if (data[4] != 0 && data[4] != 1) { + printk("\nThe selection of coupling is in error\n"); + i_err++; + } /* if(data[4]!=0 && data[4]!=1) */ + if (data[5] != 0 && data[5] != 1) { + printk("\nThe selection of single/differential mode is in error\n"); + i_err++; + } /* if(data[5]!=0 && data[5]!=1) */ + if (data[8] != 0 && data[8] != 1 && data[2] != 2) { + printk("\nError in selection of functionality\n"); + } /* if(data[8]!=0 && data[8]!=1 && data[2]!=2) */ + if (data[12] == 0 || data[12] == 1) { + if (data[6] != 20 && data[6] != 40 && data[6] != 80 + && data[6] != 160) { + printk("\nThe selection of conversion time reload value is in error\n"); + i_err++; + } /* if (data[6]!=20 && data[6]!=40 && data[6]!=80 && data[6]!=160 ) */ + if (data[7] != 2) { + printk("\nThe selection of conversion time unit is in error\n"); + i_err++; + } /* if(data[7]!=2) */ + } + if (data[9] != 0 && data[9] != 1) { + printk("\nThe selection of interrupt enable is in error\n"); + i_err++; + } /* if(data[9]!=0 && data[9]!=1) */ + if (data[11] < 0 || data[11] > 4) { + printk("\nThe selection of module is in error\n"); + i_err++; + } /* if(data[11] <0 || data[11]>1) */ + if (data[12] < 0 || data[12] > 3) { + printk("\nThe selection of singlechannel/scan selection is in error\n"); + i_err++; + } /* if(data[12] < 0 || data[12]> 3) */ + if (data[13] < 0 || data[13] > 16) { + printk("\nThe selection of number of channels is in error\n"); + i_err++; + } /* if(data[13] <0 ||data[13] >15) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* + i_ChannelCount=data[13]; + i_ScanType=data[12]; + i_ADDIDATAPolarity = data[2]; + i_ADDIDATAGain=data[1]; + i_ADDIDATAConversionTime=data[6]; + i_ADDIDATAConversionTimeUnit=data[7]; + i_ADDIDATAType=data[0]; + */ + + /* Save acquisition configuration for the actual board */ + s_BoardInfos[dev->minor].i_ChannelCount = data[13]; + s_BoardInfos[dev->minor].i_ScanType = data[12]; + s_BoardInfos[dev->minor].i_ADDIDATAPolarity = data[2]; + s_BoardInfos[dev->minor].i_ADDIDATAGain = data[1]; + s_BoardInfos[dev->minor].i_ADDIDATAConversionTime = data[6]; + s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit = data[7]; + s_BoardInfos[dev->minor].i_ADDIDATAType = data[0]; + /* Begin JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + s_BoardInfos[dev->minor].i_ConnectionType = data[5]; + /* End JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* END JK 06.07.04: Management of sevrals boards */ + + /* Begin JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + memset(s_BoardInfos[dev->minor].ui_ScanValueArray, 0, (7 + 12) * sizeof(unsigned int)); /* 7 is the maximal number of channels */ + /* End JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /* BEGIN JK 02.07.04 : This while can't be do, it block the process when using severals boards */ + /* while(i_InterruptFlag==1) */ + while (s_BoardInfos[dev->minor].i_InterruptFlag == 1) { +#ifndef MSXBOX + udelay(1); +#else + /* In the case where the driver is compiled for the MSX-Box */ + /* we used a printk to have a little delay because udelay */ + /* seems to be broken under the MSX-Box. */ + /* This solution hat to be studied. */ + printk(""); +#endif + } + /* END JK 02.07.04 : This while can't be do, it block the process when using severals boards */ + + ui_ChannelNo = CR_CHAN(insn->chanspec); /* get the channel */ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=ui_ChannelNo; */ + /* ui_Channel_num =ui_ChannelNo; */ + + s_BoardInfos[dev->minor].i_ChannelNo = ui_ChannelNo; + s_BoardInfos[dev->minor].ui_Channel_num = ui_ChannelNo; + + /* END JK 06.07.04: Management of sevrals boards */ + + if (data[5] == 0) { + if (ui_ChannelNo < 0 || ui_ChannelNo > 15) { + printk("\nThe Selection of the channel is in error\n"); + i_err++; + } /* if(ui_ChannelNo<0 || ui_ChannelNo>15) */ + } /* if(data[5]==0) */ + else { + if (data[14] == 2) { + if (ui_ChannelNo < 0 || ui_ChannelNo > 3) { + printk("\nThe Selection of the channel is in error\n"); + i_err++; + } /* if(ui_ChannelNo<0 || ui_ChannelNo>3) */ + } /* if(data[14]==2) */ + else { + if (ui_ChannelNo < 0 || ui_ChannelNo > 7) { + printk("\nThe Selection of the channel is in error\n"); + i_err++; + } /* if(ui_ChannelNo<0 || ui_ChannelNo>7) */ + } /* elseif(data[14]==2) */ + } /* elseif(data[5]==0) */ + if (data[12] == 0 || data[12] == 1) { + switch (data[5]) { + case 0: + if (ui_ChannelNo >= 0 && ui_ChannelNo <= 3) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=0 && ui_ChannelNo <=3) */ + if (ui_ChannelNo >= 4 && ui_ChannelNo <= 7) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=64; */ + s_BoardInfos[dev->minor].i_Offset = 64; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=4 && ui_ChannelNo <=7) */ + if (ui_ChannelNo >= 8 && ui_ChannelNo <= 11) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=128; */ + s_BoardInfos[dev->minor].i_Offset = 128; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=8 && ui_ChannelNo <=11) */ + if (ui_ChannelNo >= 12 && ui_ChannelNo <= 15) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=192; */ + s_BoardInfos[dev->minor].i_Offset = 192; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=12 && ui_ChannelNo <=15) */ + break; + case 1: + if (data[14] == 2) { + if (ui_ChannelNo == 0) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo ==0 ) */ + if (ui_ChannelNo == 1) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 64; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo ==1) */ + if (ui_ChannelNo == 2) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=128; */ + s_BoardInfos[dev->minor].i_Offset = 128; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo ==2 ) */ + if (ui_ChannelNo == 3) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=192; */ + s_BoardInfos[dev->minor].i_Offset = 192; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo ==3) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=0; */ + s_BoardInfos[dev->minor].i_ChannelNo = 0; + /* END JK 06.07.04: Management of sevrals boards */ + ui_ChannelNo = 0; + break; + } /* if(data[14]==2) */ + if (ui_ChannelNo >= 0 && ui_ChannelNo <= 1) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(ui_ChannelNo >=0 && ui_ChannelNo <=1) */ + if (ui_ChannelNo >= 2 && ui_ChannelNo <= 3) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=i_ChannelNo-2; */ + /* i_Offset=64; */ + s_BoardInfos[dev->minor].i_ChannelNo = + s_BoardInfos[dev->minor].i_ChannelNo - + 2; + s_BoardInfos[dev->minor].i_Offset = 64; + /* END JK 06.07.04: Management of sevrals boards */ + ui_ChannelNo = ui_ChannelNo - 2; + } /* if(ui_ChannelNo >=2 && ui_ChannelNo <=3) */ + if (ui_ChannelNo >= 4 && ui_ChannelNo <= 5) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=i_ChannelNo-4; */ + /* i_Offset=128; */ + s_BoardInfos[dev->minor].i_ChannelNo = + s_BoardInfos[dev->minor].i_ChannelNo - + 4; + s_BoardInfos[dev->minor].i_Offset = 128; + /* END JK 06.07.04: Management of sevrals boards */ + ui_ChannelNo = ui_ChannelNo - 4; + } /* if(ui_ChannelNo >=4 && ui_ChannelNo <=5) */ + if (ui_ChannelNo >= 6 && ui_ChannelNo <= 7) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_ChannelNo=i_ChannelNo-6; */ + /* i_Offset=192; */ + s_BoardInfos[dev->minor].i_ChannelNo = + s_BoardInfos[dev->minor].i_ChannelNo - + 6; + s_BoardInfos[dev->minor].i_Offset = 192; + /* END JK 06.07.04: Management of sevrals boards */ + ui_ChannelNo = ui_ChannelNo - 6; + } /* if(ui_ChannelNo >=6 && ui_ChannelNo <=7) */ + break; + + default: + printk("\n This selection of polarity does not exist\n"); + i_err++; + } /* switch(data[2]) */ + } /* if(data[12]==0 || data[12]==1) */ + else { + switch (data[11]) { + case 1: + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=0; */ + s_BoardInfos[dev->minor].i_Offset = 0; + /* END JK 06.07.04: Management of sevrals boards */ + break; + case 2: + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=64; */ + s_BoardInfos[dev->minor].i_Offset = 64; + /* END JK 06.07.04: Management of sevrals boards */ + break; + case 3: + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=128; */ + s_BoardInfos[dev->minor].i_Offset = 128; + /* END JK 06.07.04: Management of sevrals boards */ + break; + case 4: + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Offset=192; */ + s_BoardInfos[dev->minor].i_Offset = 192; + /* END JK 06.07.04: Management of sevrals boards */ + break; + default: + printk("\nError in module selection\n"); + i_err++; + } /* switch(data[11]) */ + } /* elseif(data[12]==0 || data[12]==1) */ + if (i_err) { + i_APCI3200_Reset(dev); + return -EINVAL; + } + /* if(i_ScanType!=1) */ + if (s_BoardInfos[dev->minor].i_ScanType != 1) { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Count=0; */ + /* i_Sum=0; */ + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(i_ScanType!=1) */ + + ul_Config = + data[1] | (data[2] << 6) | (data[5] << 7) | (data[3] << 8) | + (data[4] << 9); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* END JK 06.07.04: Management of sevrals boards */ + /*********************************/ + /* Write the channel to configure */ + /*********************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* outl(0 | ui_ChannelNo , devpriv->iobase+i_Offset + 0x4); */ + outl(0 | ui_ChannelNo, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x4); + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* END JK 06.07.04: Management of sevrals boards */ + /**************************/ + /* Reset the configuration */ + /**************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* outl(0 , devpriv->iobase+i_Offset + 0x0); */ + outl(0, devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x0); + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* END JK 06.07.04: Management of sevrals boards */ + + /***************************/ + /* Write the configuration */ + /***************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* outl(ul_Config , devpriv->iobase+i_Offset + 0x0); */ + outl(ul_Config, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x0); + /* END JK 06.07.04: Management of sevrals boards */ + + /***************************/ + /*Reset the calibration bit */ + /***************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ul_Temp = inl(devpriv->iobase+i_Offset + 12); */ + ul_Temp = inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* outl((ul_Temp & 0xFFF9FFFF) , devpriv->iobase+.i_Offset + 12); */ + outl((ul_Temp & 0xFFF9FFFF), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /* END JK 06.07.04: Management of sevrals boards */ + + if (data[9] == 1) { + devpriv->tsk_Current = current; + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_InterruptFlag=1; */ + s_BoardInfos[dev->minor].i_InterruptFlag = 1; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(data[9]==1) */ + else { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* else if(data[9]==1) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Initialised=1; */ + s_BoardInfos[dev->minor].i_Initialised = 1; + /* END JK 06.07.04: Management of sevrals boards */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_ScanType==1) */ + if (s_BoardInfos[dev->minor].i_ScanType == 1) + /* END JK 06.07.04: Management of sevrals boards */ + { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* i_Sum=i_Sum+1; */ + s_BoardInfos[dev->minor].i_Sum = + s_BoardInfos[dev->minor].i_Sum + 1; + /* END JK 06.07.04: Management of sevrals boards */ + + insn->unused[0] = 0; + i_APCI3200_ReadAnalogInput(dev, s, insn, &ui_Dummy); + } + + return insn->n; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ReadAnalogInput | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Read value of the selected channel | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | unsigned int ui_NoOfChannels : No Of Channels To read | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + | data[0] : Digital Value Of Input | + | data[1] : Calibration Offset Value | + | data[2] : Calibration Gain Value + | data[3] : CJC value + | data[4] : CJC offset value + | data[5] : CJC gain value + | Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values + | data[6] : CJC current source from eeprom + | data[7] : Channel current source from eeprom + | data[8] : Channle gain factor from eeprom + | End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_ReadAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_DummyValue = 0; + int i_ConvertCJCCalibration; + int i = 0; + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_Initialised==0) */ + if (s_BoardInfos[dev->minor].i_Initialised == 0) + /* END JK 06.07.04: Management of sevrals boards */ + { + i_APCI3200_Reset(dev); + return -EINVAL; + } /* if(i_Initialised==0); */ + +#ifdef PRINT_INFO + printk("\n insn->unused[0] = %i", insn->unused[0]); +#endif + + switch (insn->unused[0]) { + case 0: + + i_APCI3200_Read1AnalogInputChannel(dev, s, insn, + &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count+0]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev->minor]. + i_Count + 0] = ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + + /* Begin JK 25.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + i_APCI3200_GetChannelCalibrationValue(dev, + s_BoardInfos[dev->minor].ui_Channel_num, + &s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev->minor]. + i_Count + 6], + &s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev->minor]. + i_Count + 7], + &s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev->minor]. + i_Count + 8]); + +#ifdef PRINT_INFO + printk("\n s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count+6] = %lu", s_BoardInfos[dev->minor].ui_InterruptChannelValue[s_BoardInfos[dev->minor].i_Count + 6]); + + printk("\n s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count+7] = %lu", s_BoardInfos[dev->minor].ui_InterruptChannelValue[s_BoardInfos[dev->minor].i_Count + 7]); + + printk("\n s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count+8] = %lu", s_BoardInfos[dev->minor].ui_InterruptChannelValue[s_BoardInfos[dev->minor].i_Count + 8]); +#endif + + /* End JK 25.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE) && (i_CJCAvailable==1)) */ + if ((s_BoardInfos[dev->minor].i_ADDIDATAType == 2) + && (s_BoardInfos[dev->minor].i_InterruptFlag == FALSE) + && (s_BoardInfos[dev->minor].i_CJCAvailable == 1)) + /* END JK 06.07.04: Management of sevrals boards */ + { + i_APCI3200_ReadCJCValue(dev, &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count + 3]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev-> + minor].i_Count + 3] = ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE)) */ + else { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count + 3]=0; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev-> + minor].i_Count + 3] = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* elseif((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE) && (i_CJCAvailable==1)) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if (( i_AutoCalibration == FALSE) && (i_InterruptFlag == FALSE)) */ + if ((s_BoardInfos[dev->minor].i_AutoCalibration == FALSE) + && (s_BoardInfos[dev->minor].i_InterruptFlag == FALSE)) + /* END JK 06.07.04: Management of sevrals boards */ + { + i_APCI3200_ReadCalibrationOffsetValue(dev, + &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count + 1]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev-> + minor].i_Count + 1] = ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + i_APCI3200_ReadCalibrationGainValue(dev, + &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count + 2]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos[dev-> + minor].i_Count + 2] = ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if (( i_AutoCalibration == FALSE) && (i_InterruptFlag == FALSE)) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE)&& (i_CJCAvailable==1)) */ + if ((s_BoardInfos[dev->minor].i_ADDIDATAType == 2) + && (s_BoardInfos[dev->minor].i_InterruptFlag == FALSE) + && (s_BoardInfos[dev->minor].i_CJCAvailable == 1)) + /* END JK 06.07.04: Management of sevrals boards */ + { + /**********************************************************/ + /*Test if the Calibration channel must be read for the CJC */ + /**********************************************************/ + /**********************************/ + /*Test if the polarity is the same */ + /**********************************/ + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_CJCPolarity!=i_ADDIDATAPolarity) */ + if (s_BoardInfos[dev->minor].i_CJCPolarity != + s_BoardInfos[dev->minor].i_ADDIDATAPolarity) + /* END JK 06.07.04: Management of sevrals boards */ + { + i_ConvertCJCCalibration = 1; + } /* if(i_CJCPolarity!=i_ADDIDATAPolarity) */ + else { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_CJCGain==i_ADDIDATAGain) */ + if (s_BoardInfos[dev->minor].i_CJCGain == + s_BoardInfos[dev->minor].i_ADDIDATAGain) + /* END JK 06.07.04: Management of sevrals boards */ + { + i_ConvertCJCCalibration = 0; + } /* if(i_CJCGain==i_ADDIDATAGain) */ + else { + i_ConvertCJCCalibration = 1; + } /* elseif(i_CJCGain==i_ADDIDATAGain) */ + } /* elseif(i_CJCPolarity!=i_ADDIDATAPolarity) */ + if (i_ConvertCJCCalibration == 1) { + i_APCI3200_ReadCJCCalOffset(dev, + &ui_DummyValue); + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count+4]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos + [dev->minor].i_Count + 4] = + ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + + i_APCI3200_ReadCJCCalGain(dev, &ui_DummyValue); + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count+5]=ui_DummyValue; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos + [dev->minor].i_Count + 5] = + ui_DummyValue; + /* END JK 06.07.04: Management of sevrals boards */ + } /* if(i_ConvertCJCCalibration==1) */ + else { + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_InterruptChannelValue[i_Count+4]=0; */ + /* ui_InterruptChannelValue[i_Count+5]=0; */ + + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos + [dev->minor].i_Count + 4] = 0; + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[s_BoardInfos + [dev->minor].i_Count + 5] = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } /* elseif(i_ConvertCJCCalibration==1) */ + } /* if((i_ADDIDATAType==2) && (i_InterruptFlag == FALSE)) */ + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* if(i_ScanType!=1) */ + if (s_BoardInfos[dev->minor].i_ScanType != 1) { + /* i_Count=0; */ + s_BoardInfos[dev->minor].i_Count = 0; + } /* if(i_ScanType!=1) */ + else { + /* i_Count=i_Count +6; */ + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* s_BoardInfos [dev->minor].i_Count=s_BoardInfos [dev->minor].i_Count +6; */ + s_BoardInfos[dev->minor].i_Count = + s_BoardInfos[dev->minor].i_Count + 9; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } /* else if(i_ScanType!=1) */ + + /* if((i_ScanType==1) &&(i_InterruptFlag==1)) */ + if ((s_BoardInfos[dev->minor].i_ScanType == 1) + && (s_BoardInfos[dev->minor].i_InterruptFlag == 1)) { + /* i_Count=i_Count-6; */ + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* s_BoardInfos [dev->minor].i_Count=s_BoardInfos [dev->minor].i_Count-6; */ + s_BoardInfos[dev->minor].i_Count = + s_BoardInfos[dev->minor].i_Count - 9; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } + /* if(i_ScanType==0) */ + if (s_BoardInfos[dev->minor].i_ScanType == 0) { + /* + data[0]= ui_InterruptChannelValue[0]; + data[1]= ui_InterruptChannelValue[1]; + data[2]= ui_InterruptChannelValue[2]; + data[3]= ui_InterruptChannelValue[3]; + data[4]= ui_InterruptChannelValue[4]; + data[5]= ui_InterruptChannelValue[5]; + */ +#ifdef PRINT_INFO + printk("\n data[0]= s_BoardInfos [dev->minor].ui_InterruptChannelValue[0];"); +#endif + data[0] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[0]; + data[1] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[1]; + data[2] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[2]; + data[3] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[3]; + data[4] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[4]; + data[5] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[5]; + + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* printk("\n 0 - i_APCI3200_GetChannelCalibrationValue data [6] = %lu, data [7] = %lu, data [8] = %lu", data [6], data [7], data [8]); */ + i_APCI3200_GetChannelCalibrationValue(dev, + s_BoardInfos[dev->minor].ui_Channel_num, + &data[6], &data[7], &data[8]); + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } + break; + case 1: + + for (i = 0; i < insn->n; i++) { + /* data[i]=ui_InterruptChannelValue[i]; */ + data[i] = + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue[i]; + } + + /* i_Count=0; */ + /* i_Sum=0; */ + /* if(i_ScanType==1) */ + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + if (s_BoardInfos[dev->minor].i_ScanType == 1) { + /* i_Initialised=0; */ + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor].i_Initialised = 0; + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + /* END JK 06.07.04: Management of sevrals boards */ + } + break; + default: + printk("\nThe parameters passed are in error\n"); + i_APCI3200_Reset(dev); + return -EINVAL; + } /* switch(insn->unused[0]) */ + + return insn->n; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_Read1AnalogInputChannel | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Read value of the selected channel | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | unsigned int ui_NoOfChannel : Channel No to read | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + | data[0] : Digital Value read | + | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_Read1AnalogInputChannel(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_EOC = 0; + unsigned int ui_ChannelNo = 0; + unsigned int ui_CommandRegister = 0; + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* ui_ChannelNo=i_ChannelNo; */ + ui_ChannelNo = s_BoardInfos[dev->minor].i_ChannelNo; + + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /*********************************/ + /* Write the channel to configure */ + /*********************************/ + /* Begin JK 20.10.2004: Bad channel value is used when using differential mode */ + /* outl(0 | ui_Channel_num , devpriv->iobase+i_Offset + 0x4); */ + /* outl(0 | s_BoardInfos [dev->minor].ui_Channel_num , devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 0x4); */ + outl(0 | s_BoardInfos[dev->minor].i_ChannelNo, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x4); + /* End JK 20.10.2004: Bad channel value is used when using differential mode */ + + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + + /**************************************************************************/ + /* Set the start end stop index to the selected channel and set the start */ + /**************************************************************************/ + + ui_CommandRegister = ui_ChannelNo | (ui_ChannelNo << 8) | 0x80000; + + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + /************************/ + /* Enable the interrupt */ + /************************/ + ui_CommandRegister = ui_CommandRegister | 0x00100000; + } /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + + /******************************/ + /* Write the command register */ + /******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(ui_CommandRegister, devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*****************************/ + /*Test if interrupt is enable */ + /*****************************/ + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + do { + /*************************/ + /*Read the EOC Status bit */ + /*************************/ + + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + + } while (ui_EOC != 1); + + /***************************************/ + /* Read the digital value of the input */ + /***************************************/ + + /* data[0] = inl (devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + /* END JK 06.07.04: Management of sevrals boards */ + + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ReadCalibrationOffsetValue | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Read calibration offset value of the selected channel| + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + | data[0] : Calibration offset Value | + | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_ReadCalibrationOffsetValue(struct comedi_device *dev, unsigned int *data) +{ + unsigned int ui_Temp = 0, ui_EOC = 0; + unsigned int ui_CommandRegister = 0; + + /* BEGIN JK 06.07.04: Management of sevrals boards */ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /*********************************/ + /* Write the channel to configure */ + /*********************************/ + /* Begin JK 20.10.2004: This seems not necessary ! */ + /* outl(0 | ui_Channel_num , devpriv->iobase+i_Offset + 0x4); */ + /* outl(0 | s_BoardInfos [dev->minor].ui_Channel_num , devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 0x4); */ + /* End JK 20.10.2004: This seems not necessary ! */ + + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + /*****************************/ + /*Read the calibration offset */ + /*****************************/ + /* ui_Temp = inl(devpriv->iobase+i_Offset + 12); */ + ui_Temp = inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + + /*********************************/ + /*Configure the Offset Conversion */ + /*********************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((ui_Temp | 0x00020000), devpriv->iobase+i_Offset + 12); */ + outl((ui_Temp | 0x00020000), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /*******************************/ + /*Initialise ui_CommandRegister */ + /*******************************/ + + ui_CommandRegister = 0; + + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + + /**********************/ + /*Enable the interrupt */ + /**********************/ + + ui_CommandRegister = ui_CommandRegister | 0x00100000; + + } /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + + /**********************/ + /*Start the conversion */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00080000; + + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister, devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*****************************/ + /*Test if interrupt is enable */ + /*****************************/ + + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + + do { + /*******************/ + /*Read the EOC flag */ + /*******************/ + + /* ui_EOC = inl (devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + + } while (ui_EOC != 1); + + /**************************************************/ + /*Read the digital value of the calibration Offset */ + /**************************************************/ + + /* data[0] = inl(devpriv->iobase+i_Offset+ 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ReadCalibrationGainValue | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Read calibration gain value of the selected channel | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + | data[0] : Calibration gain Value Of Input | + | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_ReadCalibrationGainValue(struct comedi_device *dev, unsigned int *data) +{ + unsigned int ui_EOC = 0; + int ui_CommandRegister = 0; + + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /*********************************/ + /* Write the channel to configure */ + /*********************************/ + /* Begin JK 20.10.2004: This seems not necessary ! */ + /* outl(0 | ui_Channel_num , devpriv->iobase+i_Offset + 0x4); */ + /* outl(0 | s_BoardInfos [dev->minor].ui_Channel_num , devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 0x4); */ + /* End JK 20.10.2004: This seems not necessary ! */ + + /***************************/ + /*Read the calibration gain */ + /***************************/ + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + /*******************************/ + /*Configure the Gain Conversion */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00040000 , devpriv->iobase+i_Offset + 12); */ + outl(0x00040000, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + + /*******************************/ + /*Initialise ui_CommandRegister */ + /*******************************/ + + ui_CommandRegister = 0; + + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + + /**********************/ + /*Enable the interrupt */ + /**********************/ + + ui_CommandRegister = ui_CommandRegister | 0x00100000; + + } /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + + /**********************/ + /*Start the conversion */ + /**********************/ + + ui_CommandRegister = ui_CommandRegister | 0x00080000; + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister , devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*****************************/ + /*Test if interrupt is enable */ + /*****************************/ + + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + + do { + + /*******************/ + /*Read the EOC flag */ + /*******************/ + + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + + } while (ui_EOC != 1); + + /************************************************/ + /*Read the digital value of the calibration Gain */ + /************************************************/ + + /* data[0] = inl(devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ReadCJCValue | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Read CJC value of the selected channel | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + | data[0] : CJC Value | + | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ + +int i_APCI3200_ReadCJCValue(struct comedi_device *dev, unsigned int *data) +{ + unsigned int ui_EOC = 0; + int ui_CommandRegister = 0; + + /******************************/ + /*Set the converting time unit */ + /******************************/ + + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + + /******************************/ + /*Configure the CJC Conversion */ + /******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl( 0x00000400 , devpriv->iobase+i_Offset + 4); */ + outl(0x00000400, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /*******************************/ + /*Initialise dw_CommandRegister */ + /*******************************/ + ui_CommandRegister = 0; + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + /**********************/ + /*Enable the interrupt */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00100000; + } + + /**********************/ + /*Start the conversion */ + /**********************/ + + ui_CommandRegister = ui_CommandRegister | 0x00080000; + + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister , devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*****************************/ + /*Test if interrupt is enable */ + /*****************************/ + + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + do { + + /*******************/ + /*Read the EOC flag */ + /*******************/ + + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + + } while (ui_EOC != 1); + + /***********************************/ + /*Read the digital value of the CJC */ + /***********************************/ + + /* data[0] = inl(devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ReadCJCCalOffset | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Read CJC calibration offset value of the selected channel + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + | data[0] : CJC calibration offset Value + | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_ReadCJCCalOffset(struct comedi_device *dev, unsigned int *data) +{ + unsigned int ui_EOC = 0; + int ui_CommandRegister = 0; + /*******************************************/ + /*Read calibration offset value for the CJC */ + /*******************************************/ + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + /******************************/ + /*Configure the CJC Conversion */ + /******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00000400 , devpriv->iobase+i_Offset + 4); */ + outl(0x00000400, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /*********************************/ + /*Configure the Offset Conversion */ + /*********************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00020000, devpriv->iobase+i_Offset + 12); */ + outl(0x00020000, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /*******************************/ + /*Initialise ui_CommandRegister */ + /*******************************/ + ui_CommandRegister = 0; + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + /**********************/ + /*Enable the interrupt */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00100000; + + } + + /**********************/ + /*Start the conversion */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00080000; + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister,devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + do { + /*******************/ + /*Read the EOC flag */ + /*******************/ + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + } while (ui_EOC != 1); + + /**************************************************/ + /*Read the digital value of the calibration Offset */ + /**************************************************/ + /* data[0] = inl(devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_ReadCJCGainValue | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Read CJC calibration gain value + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | unsigned int ui_NoOfChannels : No Of Channels To read | + | unsigned int *data : Data Pointer to read status | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + | data[0] : CJC calibration gain value + | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_ReadCJCCalGain(struct comedi_device *dev, unsigned int *data) +{ + unsigned int ui_EOC = 0; + int ui_CommandRegister = 0; + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTimeUnit , devpriv->iobase+i_Offset + 36); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTimeUnit, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /**************************/ + /* Set the convert timing */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(i_ADDIDATAConversionTime , devpriv->iobase+i_Offset + 32); */ + outl(s_BoardInfos[dev->minor].i_ADDIDATAConversionTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + /******************************/ + /*Configure the CJC Conversion */ + /******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00000400,devpriv->iobase+i_Offset + 4); */ + outl(0x00000400, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /*******************************/ + /*Configure the Gain Conversion */ + /*******************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(0x00040000,devpriv->iobase+i_Offset + 12); */ + outl(0x00040000, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + + /*******************************/ + /*Initialise dw_CommandRegister */ + /*******************************/ + ui_CommandRegister = 0; + /*********************************/ + /*Test if the interrupt is enable */ + /*********************************/ + /* if (i_InterruptFlag == ADDIDATA_ENABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_ENABLE) { + /**********************/ + /*Enable the interrupt */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00100000; + } + /**********************/ + /*Start the conversion */ + /**********************/ + ui_CommandRegister = ui_CommandRegister | 0x00080000; + /***************************/ + /*Write the command regiter */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_CommandRegister ,devpriv->iobase+i_Offset + 8); */ + outl(ui_CommandRegister, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == ADDIDATA_DISABLE) { + do { + /*******************/ + /*Read the EOC flag */ + /*******************/ + /* ui_EOC = inl(devpriv->iobase+i_Offset + 20) & 1; */ + ui_EOC = inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 20) & 1; + } while (ui_EOC != 1); + /************************************************/ + /*Read the digital value of the calibration Gain */ + /************************************************/ + /* data[0] = inl (devpriv->iobase+i_Offset + 28); */ + data[0] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + } /* if (i_InterruptFlag == ADDIDATA_DISABLE) */ + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_InsnBits_AnalogInput_Test | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Tests the Selected Anlog Input Channel | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | struct comedi_subdevice *s : Subdevice Pointer | + | struct comedi_insn *insn : Insn Structure Pointer | + | unsigned int *data : Data Pointer contains | + | configuration parameters as below | + | + | + | data[0] : 0 TestAnalogInputShortCircuit + | 1 TestAnalogInputConnection | + + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + | data[0] : Digital value obtained | + | data[1] : calibration offset | + | data[2] : calibration gain | + | | + | | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ + +int i_APCI3200_InsnBits_AnalogInput_Test(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Configuration = 0; + int i_Temp; /* ,i_TimeUnit; */ + /* if(i_Initialised==0) */ + + if (s_BoardInfos[dev->minor].i_Initialised == 0) { + i_APCI3200_Reset(dev); + return -EINVAL; + } /* if(i_Initialised==0); */ + if (data[0] != 0 && data[0] != 1) { + printk("\nError in selection of functionality\n"); + i_APCI3200_Reset(dev); + return -EINVAL; + } /* if(data[0]!=0 && data[0]!=1) */ + + if (data[0] == 1) /* Perform Short Circuit TEST */ + { + /**************************/ + /*Set the short-cicuit bit */ + /**************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor]. + i_Offset + 12) >> 19) & 1) != + 1) ; + /* outl((0x00001000 |i_ChannelNo) , devpriv->iobase+i_Offset + 4); */ + outl((0x00001000 | s_BoardInfos[dev->minor].i_ChannelNo), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 4); + /*************************/ + /*Set the time unit to ns */ + /*************************/ + /* i_TimeUnit= i_ADDIDATAConversionTimeUnit; + i_ADDIDATAConversionTimeUnit= 1; */ + /* i_Temp= i_InterruptFlag ; */ + i_Temp = s_BoardInfos[dev->minor].i_InterruptFlag; + /* i_InterruptFlag = ADDIDATA_DISABLE; */ + s_BoardInfos[dev->minor].i_InterruptFlag = ADDIDATA_DISABLE; + i_APCI3200_Read1AnalogInputChannel(dev, s, insn, data); + /* if(i_AutoCalibration == FALSE) */ + if (s_BoardInfos[dev->minor].i_AutoCalibration == FALSE) { + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor]. + i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl((0x00001000 |i_ChannelNo) , devpriv->iobase+i_Offset + 4); */ + outl((0x00001000 | s_BoardInfos[dev->minor]. + i_ChannelNo), + devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 4); + data++; + i_APCI3200_ReadCalibrationOffsetValue(dev, data); + data++; + i_APCI3200_ReadCalibrationGainValue(dev, data); + } + } else { + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor]. + i_Offset + 12) >> 19) & 1) != + 1) ; + /* outl((0x00000800|i_ChannelNo) , devpriv->iobase+i_Offset + 4); */ + outl((0x00000800 | s_BoardInfos[dev->minor].i_ChannelNo), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 4); + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 0); */ + ui_Configuration = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 0); + /*************************/ + /*Set the time unit to ns */ + /*************************/ + /* i_TimeUnit= i_ADDIDATAConversionTimeUnit; + i_ADDIDATAConversionTimeUnit= 1; */ + /* i_Temp= i_InterruptFlag ; */ + i_Temp = s_BoardInfos[dev->minor].i_InterruptFlag; + /* i_InterruptFlag = ADDIDATA_DISABLE; */ + s_BoardInfos[dev->minor].i_InterruptFlag = ADDIDATA_DISABLE; + i_APCI3200_Read1AnalogInputChannel(dev, s, insn, data); + /* if(i_AutoCalibration == FALSE) */ + if (s_BoardInfos[dev->minor].i_AutoCalibration == FALSE) { + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor]. + i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((0x00000800|i_ChannelNo) , devpriv->iobase+i_Offset + 4); */ + outl((0x00000800 | s_BoardInfos[dev->minor]. + i_ChannelNo), + devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 4); + data++; + i_APCI3200_ReadCalibrationOffsetValue(dev, data); + data++; + i_APCI3200_ReadCalibrationGainValue(dev, data); + } + } + /* i_InterruptFlag=i_Temp ; */ + s_BoardInfos[dev->minor].i_InterruptFlag = i_Temp; + /* printk("\ni_InterruptFlag=%d\n",i_InterruptFlag); */ + return insn->n; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_InsnWriteReleaseAnalogInput | + | (struct comedi_device *dev,struct comedi_subdevice *s, | + | struct comedi_insn *insn,unsigned int *data) | + +----------------------------------------------------------------------------+ + | Task : Resets the channels | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev : Driver handle | + | struct comedi_subdevice *s : Subdevice Pointer | + | struct comedi_insn *insn : Insn Structure Pointer | + | unsigned int *data : Data Pointer + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ + +int i_APCI3200_InsnWriteReleaseAnalogInput(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + i_APCI3200_Reset(dev); + return insn->n; +} + +/* + +----------------------------------------------------------------------------+ + | Function name :int i_APCI3200_CommandTestAnalogInput(struct comedi_device *dev| + | ,struct comedi_subdevice *s,struct comedi_cmd *cmd) | + | | + +----------------------------------------------------------------------------+ + | Task : Test validity for a command for cyclic anlog input | + | acquisition | + | | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev | + | struct comedi_subdevice *s | + | struct comedi_cmd *cmd | + | | + | + | | + | | + | | + +----------------------------------------------------------------------------+ + | Return Value :0 | + | | + +----------------------------------------------------------------------------+ +*/ + +int i_APCI3200_CommandTestAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + + int err = 0; + int tmp; /* divisor1,divisor2; */ + unsigned int ui_ConvertTime = 0; + unsigned int ui_ConvertTimeBase = 0; + unsigned int ui_DelayTime = 0; + unsigned int ui_DelayTimeBase = 0; + int i_Triggermode = 0; + int i_TriggerEdge = 0; + int i_NbrOfChannel = 0; + int i_Cpt = 0; + double d_ConversionTimeForAllChannels = 0.0; + double d_SCANTimeNewUnit = 0.0; + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + /* if(i_InterruptFlag==0) */ + if (s_BoardInfos[dev->minor].i_InterruptFlag == 0) { + err++; + /* printk("\nThe interrupt should be enabled\n"); */ + } + if (err) { + i_APCI3200_Reset(dev); + return 1; + } + + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) { + err++; + } + if (cmd->start_src == TRIG_EXT) { + i_TriggerEdge = cmd->start_arg & 0xFFFF; + i_Triggermode = cmd->start_arg >> 16; + if (i_TriggerEdge < 1 || i_TriggerEdge > 3) { + err++; + printk("\nThe trigger edge selection is in error\n"); + } + if (i_Triggermode != 2) { + err++; + printk("\nThe trigger mode selection is in error\n"); + } + } + + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_FOLLOW) + err++; + + if (cmd->convert_src != TRIG_TIMER) + err++; + + if (cmd->scan_end_src != TRIG_COUNT) { + cmd->scan_end_src = TRIG_COUNT; + err++; + } + + if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) + err++; + + if (err) { + i_APCI3200_Reset(dev); + return 2; + } + /* i_FirstChannel=cmd->chanlist[0]; */ + s_BoardInfos[dev->minor].i_FirstChannel = cmd->chanlist[0]; + /* i_LastChannel=cmd->chanlist[1]; */ + s_BoardInfos[dev->minor].i_LastChannel = cmd->chanlist[1]; + + if (cmd->convert_src == TRIG_TIMER) { + ui_ConvertTime = cmd->convert_arg & 0xFFFF; + ui_ConvertTimeBase = cmd->convert_arg >> 16; + if (ui_ConvertTime != 20 && ui_ConvertTime != 40 + && ui_ConvertTime != 80 && ui_ConvertTime != 160) + { + printk("\nThe selection of conversion time reload value is in error\n"); + err++; + } /* if (ui_ConvertTime!=20 && ui_ConvertTime!=40 && ui_ConvertTime!=80 && ui_ConvertTime!=160 ) */ + if (ui_ConvertTimeBase != 2) { + printk("\nThe selection of conversion time unit is in error\n"); + err++; + } /* if(ui_ConvertTimeBase!=2) */ + } else { + ui_ConvertTime = 0; + ui_ConvertTimeBase = 0; + } + if (cmd->scan_begin_src == TRIG_FOLLOW) { + ui_DelayTime = 0; + ui_DelayTimeBase = 0; + } /* if(cmd->scan_begin_src==TRIG_FOLLOW) */ + else { + ui_DelayTime = cmd->scan_begin_arg & 0xFFFF; + ui_DelayTimeBase = cmd->scan_begin_arg >> 16; + if (ui_DelayTimeBase != 2 && ui_DelayTimeBase != 3) { + err++; + printk("\nThe Delay time base selection is in error\n"); + } + if (ui_DelayTime < 1 || ui_DelayTime > 1023) { + err++; + printk("\nThe Delay time value is in error\n"); + } + if (err) { + i_APCI3200_Reset(dev); + return 3; + } + fpu_begin(); + d_SCANTimeNewUnit = (double)ui_DelayTime; + /* i_NbrOfChannel= i_LastChannel-i_FirstChannel + 4; */ + i_NbrOfChannel = + s_BoardInfos[dev->minor].i_LastChannel - + s_BoardInfos[dev->minor].i_FirstChannel + 4; + /**********************************************************/ + /*calculate the total conversion time for all the channels */ + /**********************************************************/ + d_ConversionTimeForAllChannels = + (double)((double)ui_ConvertTime / + (double)i_NbrOfChannel); + + /*******************************/ + /*Convert the frequence in time */ + /*******************************/ + d_ConversionTimeForAllChannels = + (double)1.0 / d_ConversionTimeForAllChannels; + ui_ConvertTimeBase = 3; + /***********************************/ + /*Test if the time unit is the same */ + /***********************************/ + + if (ui_DelayTimeBase <= ui_ConvertTimeBase) { + + for (i_Cpt = 0; + i_Cpt < (ui_ConvertTimeBase - ui_DelayTimeBase); + i_Cpt++) { + + d_ConversionTimeForAllChannels = + d_ConversionTimeForAllChannels * 1000; + d_ConversionTimeForAllChannels = + d_ConversionTimeForAllChannels + 1; + } + } else { + for (i_Cpt = 0; + i_Cpt < (ui_DelayTimeBase - ui_ConvertTimeBase); + i_Cpt++) { + d_SCANTimeNewUnit = d_SCANTimeNewUnit * 1000; + + } + } + + if (d_ConversionTimeForAllChannels >= d_SCANTimeNewUnit) { + + printk("\nSCAN Delay value cannot be used\n"); + /*********************************/ + /*SCAN Delay value cannot be used */ + /*********************************/ + err++; + } + fpu_end(); + } /* else if(cmd->scan_begin_src==TRIG_FOLLOW) */ + + if (err) { + i_APCI3200_Reset(dev); + return 4; + } + + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function name :int i_APCI3200_StopCyclicAcquisition(struct comedi_device *dev,| + | struct comedi_subdevice *s)| + | | + +----------------------------------------------------------------------------+ + | Task : Stop the acquisition | + | | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev | + | struct comedi_subdevice *s | + | | + +----------------------------------------------------------------------------+ + | Return Value :0 | + | | + +----------------------------------------------------------------------------+ +*/ + +int i_APCI3200_StopCyclicAcquisition(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned int ui_Configuration = 0; + /* i_InterruptFlag=0; */ + /* i_Initialised=0; */ + /* i_Count=0; */ + /* i_Sum=0; */ + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + s_BoardInfos[dev->minor].i_Initialised = 0; + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + + /*******************/ + /*Read the register */ + /*******************/ + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 8); */ + ui_Configuration = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + /*****************************/ + /*Reset the START and IRQ bit */ + /*****************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((ui_Configuration & 0xFFE7FFFF),devpriv->iobase+i_Offset + 8); */ + outl((ui_Configuration & 0xFFE7FFFF), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function name : int i_APCI3200_CommandAnalogInput(struct comedi_device *dev, | + | struct comedi_subdevice *s) | + | | + +----------------------------------------------------------------------------+ + | Task : Does asynchronous acquisition | + | Determines the mode 1 or 2. | + | | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev | + | struct comedi_subdevice *s | + | | + | | + +----------------------------------------------------------------------------+ + | Return Value : | + | | + +----------------------------------------------------------------------------+ +*/ + +int i_APCI3200_CommandAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int ui_Configuration = 0; + /* INT i_CurrentSource = 0; */ + unsigned int ui_Trigger = 0; + unsigned int ui_TriggerEdge = 0; + unsigned int ui_Triggermode = 0; + unsigned int ui_ScanMode = 0; + unsigned int ui_ConvertTime = 0; + unsigned int ui_ConvertTimeBase = 0; + unsigned int ui_DelayTime = 0; + unsigned int ui_DelayTimeBase = 0; + unsigned int ui_DelayMode = 0; + /* i_FirstChannel=cmd->chanlist[0]; */ + /* i_LastChannel=cmd->chanlist[1]; */ + s_BoardInfos[dev->minor].i_FirstChannel = cmd->chanlist[0]; + s_BoardInfos[dev->minor].i_LastChannel = cmd->chanlist[1]; + if (cmd->start_src == TRIG_EXT) { + ui_Trigger = 1; + ui_TriggerEdge = cmd->start_arg & 0xFFFF; + ui_Triggermode = cmd->start_arg >> 16; + } /* if(cmd->start_src==TRIG_EXT) */ + else { + ui_Trigger = 0; + } /* elseif(cmd->start_src==TRIG_EXT) */ + + if (cmd->stop_src == TRIG_COUNT) { + ui_ScanMode = 0; + } /* if (cmd->stop_src==TRIG_COUNT) */ + else { + ui_ScanMode = 2; + } /* else if (cmd->stop_src==TRIG_COUNT) */ + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + ui_DelayTime = 0; + ui_DelayTimeBase = 0; + ui_DelayMode = 0; + } /* if(cmd->scan_begin_src==TRIG_FOLLOW) */ + else { + ui_DelayTime = cmd->scan_begin_arg & 0xFFFF; + ui_DelayTimeBase = cmd->scan_begin_arg >> 16; + ui_DelayMode = 1; + } /* else if(cmd->scan_begin_src==TRIG_FOLLOW) */ + /* printk("\nui_DelayTime=%u\n",ui_DelayTime); */ + /* printk("\nui_DelayTimeBase=%u\n",ui_DelayTimeBase); */ + if (cmd->convert_src == TRIG_TIMER) { + ui_ConvertTime = cmd->convert_arg & 0xFFFF; + ui_ConvertTimeBase = cmd->convert_arg >> 16; + } else { + ui_ConvertTime = 0; + ui_ConvertTimeBase = 0; + } + + /* if(i_ADDIDATAType ==1 || ((i_ADDIDATAType==2))) */ + /* { */ + /**************************************************/ + /*Read the old configuration of the current source */ + /**************************************************/ + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 12); */ + ui_Configuration = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /***********************************************/ + /*Write the configuration of the current source */ + /***********************************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((ui_Configuration & 0xFFC00000 ), devpriv->iobase+i_Offset +12); */ + outl((ui_Configuration & 0xFFC00000), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 12); + /* } */ + ui_Configuration = 0; + /* printk("\nfirstchannel=%u\n",i_FirstChannel); */ + /* printk("\nlastchannel=%u\n",i_LastChannel); */ + /* printk("\nui_Trigger=%u\n",ui_Trigger); */ + /* printk("\nui_TriggerEdge=%u\n",ui_TriggerEdge); */ + /* printk("\nui_Triggermode=%u\n",ui_Triggermode); */ + /* printk("\nui_DelayMode=%u\n",ui_DelayMode); */ + /* printk("\nui_ScanMode=%u\n",ui_ScanMode); */ + + /* ui_Configuration = i_FirstChannel |(i_LastChannel << 8)| 0x00100000 | */ + ui_Configuration = + s_BoardInfos[dev->minor].i_FirstChannel | (s_BoardInfos[dev-> + minor]. + i_LastChannel << 8) | 0x00100000 | (ui_Trigger << 24) | + (ui_TriggerEdge << 25) | (ui_Triggermode << 27) | (ui_DelayMode + << 18) | (ui_ScanMode << 16); + + /*************************/ + /*Write the Configuration */ + /*************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl( ui_Configuration, devpriv->iobase+i_Offset + 0x8); */ + outl(ui_Configuration, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 0x8); + /***********************/ + /*Write the Delay Value */ + /***********************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_DelayTime,devpriv->iobase+i_Offset + 40); */ + outl(ui_DelayTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 40); + /***************************/ + /*Write the Delay time base */ + /***************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_DelayTimeBase,devpriv->iobase+i_Offset + 44); */ + outl(ui_DelayTimeBase, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 44); + /*********************************/ + /*Write the conversion time value */ + /*********************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_ConvertTime,devpriv->iobase+i_Offset + 32); */ + outl(ui_ConvertTime, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 32); + + /********************************/ + /*Write the conversion time base */ + /********************************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl(ui_ConvertTimeBase,devpriv->iobase+i_Offset + 36); */ + outl(ui_ConvertTimeBase, + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 36); + /*******************/ + /*Read the register */ + /*******************/ + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 4); */ + ui_Configuration = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /******************/ + /*Set the SCAN bit */ + /******************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + + /* outl(((ui_Configuration & 0x1E0FF) | 0x00002000),devpriv->iobase+i_Offset + 4); */ + outl(((ui_Configuration & 0x1E0FF) | 0x00002000), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 4); + /*******************/ + /*Read the register */ + /*******************/ + ui_Configuration = 0; + /* ui_Configuration = inl(devpriv->iobase+i_Offset + 8); */ + ui_Configuration = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + + /*******************/ + /*Set the START bit */ + /*******************/ + /* while (((inl(devpriv->iobase+i_Offset+12)>>19) & 1) != 1); */ + while (((inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + + 12) >> 19) & 1) != 1) ; + /* outl((ui_Configuration | 0x00080000),devpriv->iobase+i_Offset + 8); */ + outl((ui_Configuration | 0x00080000), + devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 8); + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : int i_APCI3200_Reset(struct comedi_device *dev) | + | | + +----------------------------------------------------------------------------+ + | Task :Resets the registers of the card | + +----------------------------------------------------------------------------+ + | Input Parameters : | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + +----------------------------------------------------------------------------+ + | Return Value : | + | | + +----------------------------------------------------------------------------+ +*/ + +int i_APCI3200_Reset(struct comedi_device *dev) +{ + int i_Temp; + unsigned int dw_Dummy; + /* i_InterruptFlag=0; */ + /* i_Initialised==0; */ + /* i_Count=0; */ + /* i_Sum=0; */ + + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + s_BoardInfos[dev->minor].i_Initialised = 0; + s_BoardInfos[dev->minor].i_Count = 0; + s_BoardInfos[dev->minor].i_Sum = 0; + s_BoardInfos[dev->minor].b_StructInitialized = 0; + + outl(0x83838383, devpriv->i_IobaseAmcc + 0x60); + + /* Enable the interrupt for the controller */ + dw_Dummy = inl(devpriv->i_IobaseAmcc + 0x38); + outl(dw_Dummy | 0x2000, devpriv->i_IobaseAmcc + 0x38); + outl(0, devpriv->i_IobaseAddon); /* Resets the output */ + /***************/ + /*Empty the buffer */ + /**************/ + for (i_Temp = 0; i_Temp <= 95; i_Temp++) { + /* ui_InterruptChannelValue[i_Temp]=0; */ + s_BoardInfos[dev->minor].ui_InterruptChannelValue[i_Temp] = 0; + } /* for(i_Temp=0;i_Temp<=95;i_Temp++) */ + /*****************************/ + /*Reset the START and IRQ bit */ + /*****************************/ + for (i_Temp = 0; i_Temp <= 192;) { + while (((inl(devpriv->iobase + i_Temp + 12) >> 19) & 1) != 1) ; + outl(0, devpriv->iobase + i_Temp + 8); + i_Temp = i_Temp + 64; + } /* for(i_Temp=0;i_Temp<=192;i_Temp+64) */ + return 0; +} + +/* + +----------------------------------------------------------------------------+ + | Function Name : static void v_APCI3200_Interrupt | + | (int irq , void *d) | + +----------------------------------------------------------------------------+ + | Task : Interrupt processing Routine | + +----------------------------------------------------------------------------+ + | Input Parameters : int irq : irq number | + | void *d : void pointer | + +----------------------------------------------------------------------------+ + | Output Parameters : -- | + +----------------------------------------------------------------------------+ + | Return Value : TRUE : No error occur | + | : FALSE : Error occur. Return the error | + | | + +----------------------------------------------------------------------------+ +*/ +void v_APCI3200_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned int ui_StatusRegister = 0; + unsigned int ui_ChannelNumber = 0; + int i_CalibrationFlag = 0; + int i_CJCFlag = 0; + unsigned int ui_DummyValue = 0; + unsigned int ui_DigitalTemperature = 0; + unsigned int ui_DigitalInput = 0; + int i_ConvertCJCCalibration; + + /* BEGIN JK TEST */ + int i_ReturnValue = 0; + /* END JK TEST */ + + /* printk ("\n i_ScanType = %i i_ADDIDATAType = %i", s_BoardInfos [dev->minor].i_ScanType, s_BoardInfos [dev->minor].i_ADDIDATAType); */ + + /* switch(i_ScanType) */ + switch (s_BoardInfos[dev->minor].i_ScanType) { + case 0: + case 1: + /* switch(i_ADDIDATAType) */ + switch (s_BoardInfos[dev->minor].i_ADDIDATAType) { + case 0: + case 1: + + /************************************/ + /*Read the interrupt status register */ + /************************************/ + /* ui_StatusRegister = inl(devpriv->iobase+i_Offset + 16); */ + ui_StatusRegister = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 16); + if ((ui_StatusRegister & 0x2) == 0x2) { + /* i_CalibrationFlag = ((inl(devpriv->iobase+i_Offset + 12) & 0x00060000) >> 17); */ + i_CalibrationFlag = + ((inl(devpriv->iobase + + s_BoardInfos[dev-> + minor]. + i_Offset + + 12) & 0x00060000) >> + 17); + /*************************/ + /*Read the channel number */ + /*************************/ + /* ui_ChannelNumber = inl(devpriv->iobase+i_Offset + 24); */ + + /*************************************/ + /*Read the digital analog input value */ + /*************************************/ + /* ui_DigitalInput = inl(devpriv->iobase+i_Offset + 28); */ + ui_DigitalInput = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + + /***********************************************/ + /* Test if the value read is the channel value */ + /***********************************************/ + if (i_CalibrationFlag == 0) { + /* ui_InterruptChannelValue[i_Count + 0] = ui_DigitalInput; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 0] = ui_DigitalInput; + + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* + printk("\n 1 - i_APCI3200_GetChannelCalibrationValue (dev, s_BoardInfos %i", ui_ChannelNumber); + i_APCI3200_GetChannelCalibrationValue (dev, s_BoardInfos [dev->minor].ui_Channel_num, + &s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count + 6], + &s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count + 7], + &s_BoardInfos [dev->minor].ui_InterruptChannelValue[s_BoardInfos [dev->minor].i_Count + 8]); + */ + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /******************************************************/ + /*Start the conversion of the calibration offset value */ + /******************************************************/ + i_APCI3200_ReadCalibrationOffsetValue + (dev, &ui_DummyValue); + } /* if (i_CalibrationFlag == 0) */ + /**********************************************************/ + /* Test if the value read is the calibration offset value */ + /**********************************************************/ + + if (i_CalibrationFlag == 1) { + + /******************/ + /* Save the value */ + /******************/ + + /* ui_InterruptChannelValue[i_Count + 1] = ui_DigitalInput; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 1] = ui_DigitalInput; + + /******************************************************/ + /* Start the conversion of the calibration gain value */ + /******************************************************/ + i_APCI3200_ReadCalibrationGainValue(dev, + &ui_DummyValue); + } /* if (i_CalibrationFlag == 1) */ + /******************************************************/ + /*Test if the value read is the calibration gain value */ + /******************************************************/ + + if (i_CalibrationFlag == 2) { + + /****************/ + /*Save the value */ + /****************/ + /* ui_InterruptChannelValue[i_Count + 2] = ui_DigitalInput; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 2] = ui_DigitalInput; + /* if(i_ScanType==1) */ + if (s_BoardInfos[dev->minor]. + i_ScanType == 1) { + + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor]. + i_InterruptFlag = 0; + /* i_Count=i_Count + 6; */ + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* s_BoardInfos [dev->minor].i_Count=s_BoardInfos [dev->minor].i_Count + 6; */ + s_BoardInfos[dev->minor]. + i_Count = + s_BoardInfos[dev-> + minor].i_Count + 9; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } /* if(i_ScanType==1) */ + else { + /* i_Count=0; */ + s_BoardInfos[dev->minor]. + i_Count = 0; + } /* elseif(i_ScanType==1) */ + /* if(i_ScanType!=1) */ + if (s_BoardInfos[dev->minor]. + i_ScanType != 1) { + i_ReturnValue = send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } /* if(i_ScanType!=1) */ + else { + /* if(i_ChannelCount==i_Sum) */ + if (s_BoardInfos[dev->minor]. + i_ChannelCount == + s_BoardInfos[dev-> + minor].i_Sum) { + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } + } /* if(i_ScanType!=1) */ + } /* if (i_CalibrationFlag == 2) */ + } /* if ((ui_StatusRegister & 0x2) == 0x2) */ + + break; + + case 2: + /************************************/ + /*Read the interrupt status register */ + /************************************/ + + /* ui_StatusRegister = inl(devpriv->iobase+i_Offset + 16); */ + ui_StatusRegister = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 16); + /*************************/ + /*Test if interrupt occur */ + /*************************/ + + if ((ui_StatusRegister & 0x2) == 0x2) { + + /* i_CJCFlag = ((inl(devpriv->iobase+i_Offset + 4) & 0x00000400) >> 10); */ + i_CJCFlag = + ((inl(devpriv->iobase + + s_BoardInfos[dev-> + minor]. + i_Offset + + 4) & 0x00000400) >> 10); + + /* i_CalibrationFlag = ((inl(devpriv->iobase+i_Offset + 12) & 0x00060000) >> 17); */ + i_CalibrationFlag = + ((inl(devpriv->iobase + + s_BoardInfos[dev-> + minor]. + i_Offset + + 12) & 0x00060000) >> + 17); + + /*************************/ + /*Read the channel number */ + /*************************/ + + /* ui_ChannelNumber = inl(devpriv->iobase+i_Offset + 24); */ + ui_ChannelNumber = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 24); + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + s_BoardInfos[dev->minor].ui_Channel_num = + ui_ChannelNumber; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /************************************/ + /*Read the digital temperature value */ + /************************************/ + /* ui_DigitalTemperature = inl(devpriv->iobase+i_Offset + 28); */ + ui_DigitalTemperature = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + + /*********************************************/ + /*Test if the value read is the channel value */ + /*********************************************/ + + if ((i_CalibrationFlag == 0) + && (i_CJCFlag == 0)) { + /* ui_InterruptChannelValue[i_Count + 0]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 0] = + ui_DigitalTemperature; + + /*********************************/ + /*Start the conversion of the CJC */ + /*********************************/ + i_APCI3200_ReadCJCValue(dev, + &ui_DummyValue); + + } /* if ((i_CalibrationFlag == 0) && (i_CJCFlag == 0)) */ + + /*****************************************/ + /*Test if the value read is the CJC value */ + /*****************************************/ + + if ((i_CJCFlag == 1) + && (i_CalibrationFlag == 0)) { + /* ui_InterruptChannelValue[i_Count + 3]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 3] = + ui_DigitalTemperature; + + /******************************************************/ + /*Start the conversion of the calibration offset value */ + /******************************************************/ + i_APCI3200_ReadCalibrationOffsetValue + (dev, &ui_DummyValue); + } /* if ((i_CJCFlag == 1) && (i_CalibrationFlag == 0)) */ + + /********************************************************/ + /*Test if the value read is the calibration offset value */ + /********************************************************/ + + if ((i_CalibrationFlag == 1) + && (i_CJCFlag == 0)) { + /* ui_InterruptChannelValue[i_Count + 1]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 1] = + ui_DigitalTemperature; + + /****************************************************/ + /*Start the conversion of the calibration gain value */ + /****************************************************/ + i_APCI3200_ReadCalibrationGainValue(dev, + &ui_DummyValue); + + } /* if ((i_CalibrationFlag == 1) && (i_CJCFlag == 0)) */ + + /******************************************************/ + /*Test if the value read is the calibration gain value */ + /******************************************************/ + + if ((i_CalibrationFlag == 2) + && (i_CJCFlag == 0)) { + /* ui_InterruptChannelValue[i_Count + 2]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 2] = + ui_DigitalTemperature; + + /**********************************************************/ + /*Test if the Calibration channel must be read for the CJC */ + /**********************************************************/ + + /*Test if the polarity is the same */ + /**********************************/ + /* if(i_CJCPolarity!=i_ADDIDATAPolarity) */ + if (s_BoardInfos[dev->minor]. + i_CJCPolarity != + s_BoardInfos[dev->minor]. + i_ADDIDATAPolarity) { + i_ConvertCJCCalibration = 1; + } /* if(i_CJCPolarity!=i_ADDIDATAPolarity) */ + else { + /* if(i_CJCGain==i_ADDIDATAGain) */ + if (s_BoardInfos[dev->minor]. + i_CJCGain == + s_BoardInfos[dev-> + minor]. + i_ADDIDATAGain) { + i_ConvertCJCCalibration + = 0; + } /* if(i_CJCGain==i_ADDIDATAGain) */ + else { + i_ConvertCJCCalibration + = 1; + } /* elseif(i_CJCGain==i_ADDIDATAGain) */ + } /* elseif(i_CJCPolarity!=i_ADDIDATAPolarity) */ + if (i_ConvertCJCCalibration == 1) { + /****************************************************************/ + /*Start the conversion of the calibration gain value for the CJC */ + /****************************************************************/ + i_APCI3200_ReadCJCCalOffset(dev, + &ui_DummyValue); + + } /* if(i_ConvertCJCCalibration==1) */ + else { + /* ui_InterruptChannelValue[i_Count + 4]=0; */ + /* ui_InterruptChannelValue[i_Count + 5]=0; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev-> + minor].i_Count + + 4] = 0; + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev-> + minor].i_Count + + 5] = 0; + } /* elseif(i_ConvertCJCCalibration==1) */ + } /* else if ((i_CalibrationFlag == 2) && (i_CJCFlag == 0)) */ + + /********************************************************************/ + /*Test if the value read is the calibration offset value for the CJC */ + /********************************************************************/ + + if ((i_CalibrationFlag == 1) + && (i_CJCFlag == 1)) { + /* ui_InterruptChannelValue[i_Count + 4]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 4] = + ui_DigitalTemperature; + + /****************************************************************/ + /*Start the conversion of the calibration gain value for the CJC */ + /****************************************************************/ + i_APCI3200_ReadCJCCalGain(dev, + &ui_DummyValue); + + } /* if ((i_CalibrationFlag == 1) && (i_CJCFlag == 1)) */ + + /******************************************************************/ + /*Test if the value read is the calibration gain value for the CJC */ + /******************************************************************/ + + if ((i_CalibrationFlag == 2) + && (i_CJCFlag == 1)) { + /* ui_InterruptChannelValue[i_Count + 5]=ui_DigitalTemperature; */ + s_BoardInfos[dev->minor]. + ui_InterruptChannelValue + [s_BoardInfos[dev->minor]. + i_Count + 5] = + ui_DigitalTemperature; + + /* if(i_ScanType==1) */ + if (s_BoardInfos[dev->minor]. + i_ScanType == 1) { + + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor]. + i_InterruptFlag = 0; + /* i_Count=i_Count + 6; */ + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + /* s_BoardInfos [dev->minor].i_Count=s_BoardInfos [dev->minor].i_Count + 6; */ + s_BoardInfos[dev->minor]. + i_Count = + s_BoardInfos[dev-> + minor].i_Count + 9; + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + } /* if(i_ScanType==1) */ + else { + /* i_Count=0; */ + s_BoardInfos[dev->minor]. + i_Count = 0; + } /* elseif(i_ScanType==1) */ + + /* if(i_ScanType!=1) */ + if (s_BoardInfos[dev->minor]. + i_ScanType != 1) { + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + } /* if(i_ScanType!=1) */ + else { + /* if(i_ChannelCount==i_Sum) */ + if (s_BoardInfos[dev->minor]. + i_ChannelCount == + s_BoardInfos[dev-> + minor].i_Sum) { + send_sig(SIGIO, devpriv->tsk_Current, 0); /* send signal to the sample */ + + } /* if(i_ChannelCount==i_Sum) */ + } /* else if(i_ScanType!=1) */ + } /* if ((i_CalibrationFlag == 2) && (i_CJCFlag == 1)) */ + + } /* else if ((ui_StatusRegister & 0x2) == 0x2) */ + break; + } /* switch(i_ADDIDATAType) */ + break; + case 2: + case 3: + i_APCI3200_InterruptHandleEos(dev); + break; + } /* switch(i_ScanType) */ + return; +} + +/* + +----------------------------------------------------------------------------+ + | Function name :int i_APCI3200_InterruptHandleEos(struct comedi_device *dev) | + | | + | | + +----------------------------------------------------------------------------+ + | Task : . | + | This function copies the acquired data(from FIFO) | + | to Comedi buffer. | + | | + +----------------------------------------------------------------------------+ + | Input Parameters : struct comedi_device *dev | + | | + | | + +----------------------------------------------------------------------------+ + | Return Value : 0 | + | | + +----------------------------------------------------------------------------+ +*/ +int i_APCI3200_InterruptHandleEos(struct comedi_device *dev) +{ + unsigned int ui_StatusRegister = 0; + struct comedi_subdevice *s = dev->subdevices + 0; + + /* BEGIN JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* comedi_async *async = s->async; */ + /* UINT *data; */ + /* data=async->data+async->buf_int_ptr;//new samples added from here onwards */ + int n = 0, i = 0; + /* END JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /************************************/ + /*Read the interrupt status register */ + /************************************/ + /* ui_StatusRegister = inl(devpriv->iobase+i_Offset + 16); */ + ui_StatusRegister = + inl(devpriv->iobase + s_BoardInfos[dev->minor].i_Offset + 16); + + /*************************/ + /*Test if interrupt occur */ + /*************************/ + + if ((ui_StatusRegister & 0x2) == 0x2) { + /*************************/ + /*Read the channel number */ + /*************************/ + /* ui_ChannelNumber = inl(devpriv->iobase+i_Offset + 24); */ + /* BEGIN JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* This value is not used */ + /* ui_ChannelNumber = inl(devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 24); */ + s->async->events = 0; + /* END JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /*************************************/ + /*Read the digital Analog Input value */ + /*************************************/ + + /* data[i_Count] = inl(devpriv->iobase+i_Offset + 28); */ + /* Begin JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* data[s_BoardInfos [dev->minor].i_Count] = inl(devpriv->iobase+s_BoardInfos [dev->minor].i_Offset + 28); */ + s_BoardInfos[dev->minor].ui_ScanValueArray[s_BoardInfos[dev-> + minor].i_Count] = + inl(devpriv->iobase + + s_BoardInfos[dev->minor].i_Offset + 28); + /* End JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /* if((i_Count == (i_LastChannel-i_FirstChannel+3))) */ + if ((s_BoardInfos[dev->minor].i_Count == + (s_BoardInfos[dev->minor].i_LastChannel - + s_BoardInfos[dev->minor]. + i_FirstChannel + 3))) { + + /* Begin JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + s_BoardInfos[dev->minor].i_Count++; + + for (i = s_BoardInfos[dev->minor].i_FirstChannel; + i <= s_BoardInfos[dev->minor].i_LastChannel; + i++) { + i_APCI3200_GetChannelCalibrationValue(dev, i, + &s_BoardInfos[dev->minor]. + ui_ScanValueArray[s_BoardInfos[dev-> + minor].i_Count + ((i - + s_BoardInfos + [dev->minor]. + i_FirstChannel) + * 3)], + &s_BoardInfos[dev->minor]. + ui_ScanValueArray[s_BoardInfos[dev-> + minor].i_Count + ((i - + s_BoardInfos + [dev->minor]. + i_FirstChannel) + * 3) + 1], + &s_BoardInfos[dev->minor]. + ui_ScanValueArray[s_BoardInfos[dev-> + minor].i_Count + ((i - + s_BoardInfos + [dev->minor]. + i_FirstChannel) + * 3) + 2]); + } + + /* End JK 22.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + + /* i_Count=-1; */ + + s_BoardInfos[dev->minor].i_Count = -1; + + /* async->buf_int_count+=(i_LastChannel-i_FirstChannel+4)*sizeof(unsigned int); */ + /* Begin JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* async->buf_int_count+=(s_BoardInfos [dev->minor].i_LastChannel-s_BoardInfos [dev->minor].i_FirstChannel+4)*sizeof(unsigned int); */ + /* End JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* async->buf_int_ptr+=(i_LastChannel-i_FirstChannel+4)*sizeof(unsigned int); */ + /* Begin JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* async->buf_int_ptr+=(s_BoardInfos [dev->minor].i_LastChannel-s_BoardInfos [dev->minor].i_FirstChannel+4)*sizeof(unsigned int); */ + /* comedi_eos(dev,s); */ + + /* Set the event type (Comedi Buffer End Of Scan) */ + s->async->events |= COMEDI_CB_EOS; + + /* Test if enougth memory is available and allocate it for 7 values */ + /* n = comedi_buf_write_alloc(s->async, 7*sizeof(unsigned int)); */ + n = comedi_buf_write_alloc(s->async, + (7 + 12) * sizeof(unsigned int)); + + /* If not enough memory available, event is set to Comedi Buffer Error */ + if (n > ((7 + 12) * sizeof(unsigned int))) { + printk("\ncomedi_buf_write_alloc n = %i", n); + s->async->events |= COMEDI_CB_ERROR; + } + /* Write all 7 scan values in the comedi buffer */ + comedi_buf_memcpy_to(s->async, 0, + (unsigned int *) s_BoardInfos[dev->minor]. + ui_ScanValueArray, (7 + 12) * sizeof(unsigned int)); + + /* Update comedi buffer pinters indexes */ + comedi_buf_write_free(s->async, + (7 + 12) * sizeof(unsigned int)); + + /* Send events */ + comedi_event(dev, s); + /* End JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /* BEGIN JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + /* */ + /* if (s->async->buf_int_ptr>=s->async->data_len) // for buffer rool over */ + /* { */ + /* /* buffer rollover */ */ + /* s->async->buf_int_ptr=0; */ + /* comedi_eobuf(dev,s); */ + /* } */ + /* End JK 18.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + } + /* i_Count++; */ + s_BoardInfos[dev->minor].i_Count++; + } + /* i_InterruptFlag=0; */ + s_BoardInfos[dev->minor].i_InterruptFlag = 0; + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.h new file mode 100644 index 00000000..812a9c46 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3200.h @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* Card Specific information */ +#define APCI3200_BOARD_VENDOR_ID 0x15B8 +/* #define APCI3200_ADDRESS_RANGE 264 */ + +int MODULE_NO; +struct { + int i_Gain; + int i_Polarity; + int i_OffsetRange; + int i_Coupling; + int i_SingleDiff; + int i_AutoCalibration; + unsigned int ui_ReloadValue; + unsigned int ui_TimeUnitReloadVal; + int i_Interrupt; + int i_ModuleSelection; +} Config_Parameters_Module1, Config_Parameters_Module2, + Config_Parameters_Module3, Config_Parameters_Module4; + +/* ANALOG INPUT RANGE */ +static const struct comedi_lrange range_apci3200_ai = { 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1) + } +}; + +static const struct comedi_lrange range_apci3300_ai = { 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1) + } +}; + +/* Analog Input related Defines */ +#define APCI3200_AI_OFFSET_GAIN 0 +#define APCI3200_AI_SC_TEST 4 +#define APCI3200_AI_IRQ 8 +#define APCI3200_AI_AUTOCAL 12 +#define APCI3200_RELOAD_CONV_TIME_VAL 32 +#define APCI3200_CONV_TIME_TIME_BASE 36 +#define APCI3200_RELOAD_DELAY_TIME_VAL 40 +#define APCI3200_DELAY_TIME_TIME_BASE 44 +#define APCI3200_AI_MODULE1 0 +#define APCI3200_AI_MODULE2 64 +#define APCI3200_AI_MODULE3 128 +#define APCI3200_AI_MODULE4 192 +#define TRUE 1 +#define FALSE 0 +#define APCI3200_AI_EOSIRQ 16 +#define APCI3200_AI_EOS 20 +#define APCI3200_AI_CHAN_ID 24 +#define APCI3200_AI_CHAN_VAL 28 +#define ANALOG_INPUT 0 +#define TEMPERATURE 1 +#define RESISTANCE 2 + +#define ENABLE_EXT_TRIG 1 +#define ENABLE_EXT_GATE 2 +#define ENABLE_EXT_TRIG_GATE 3 + +#define APCI3200_MAXVOLT 2.5 +#define ADDIDATA_GREATER_THAN_TEST 0 +#define ADDIDATA_LESS_THAN_TEST 1 + +#define ADDIDATA_UNIPOLAR 1 +#define ADDIDATA_BIPOLAR 2 + +/* BEGIN JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ +#define MAX_MODULE 4 +/* END JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + +struct str_ADDIDATA_RTDStruct { + unsigned int ul_NumberOfValue; + unsigned int *pul_ResistanceValue; + unsigned int *pul_TemperatureValue; +}; + +/* BEGIN JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ +struct str_Module { + + /* Begin JK 05/08/2003 change for Linux */ + unsigned long ul_CurrentSourceCJC; + unsigned long ul_CurrentSource[5]; + /* End JK 05/08/2003 change for Linux */ + + /* Begin CG 15/02/02 Rev 1.0 -> Rev 1.1 : Add Header Type 1 */ + unsigned long ul_GainFactor[8]; /* Gain Factor */ + unsigned int w_GainValue[10]; + /* End CG 15/02/02 Rev 1.0 -> Rev 1.1 : Add Header Type 1 */ +}; + +/* END JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + +/* BEGIN JK 06.07.04: Management of sevrals boards */ +struct str_BoardInfos { + + int i_CJCAvailable; + int i_CJCPolarity; + int i_CJCGain; + int i_InterruptFlag; + int i_ADDIDATAPolarity; + int i_ADDIDATAGain; + int i_AutoCalibration; + int i_ADDIDATAConversionTime; + int i_ADDIDATAConversionTimeUnit; + int i_ADDIDATAType; + int i_ChannelNo; + int i_ChannelCount; + int i_ScanType; + int i_FirstChannel; + int i_LastChannel; + int i_Sum; + int i_Offset; + unsigned int ui_Channel_num; + int i_Count; + int i_Initialised; + /* UINT ui_InterruptChannelValue[96]; //Buffer */ + unsigned int ui_InterruptChannelValue[144]; /* Buffer */ + unsigned char b_StructInitialized; + /* Begin JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + unsigned int ui_ScanValueArray[7 + 12]; /* 7 is the maximal number of channels */ + /* End JK 19.10.2004: APCI-3200 Driver update 0.7.57 -> 0.7.68 */ + + /* Begin JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ + int i_ConnectionType; + int i_NbrOfModule; + struct str_Module s_Module[MAX_MODULE]; + /* End JK 21.10.2004: APCI-3200 / APCI-3300 Reading of EEPROM values */ +}; + +/* END JK 06.07.04: Management of sevrals boards */ + +/* Hardware Layer functions for Apci3200 */ + +/* AI */ + +int i_APCI3200_ConfigAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3200_ReadAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3200_InsnWriteReleaseAnalogInput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3200_InsnBits_AnalogInput_Test(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3200_StopCyclicAcquisition(struct comedi_device *dev, struct comedi_subdevice *s); +int i_APCI3200_InterruptHandleEos(struct comedi_device *dev); +int i_APCI3200_CommandTestAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); +int i_APCI3200_CommandAnalogInput(struct comedi_device *dev, struct comedi_subdevice *s); +int i_APCI3200_ReadDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +/* Interrupt */ +void v_APCI3200_Interrupt(int irq, void *d); +int i_APCI3200_InterruptHandleEos(struct comedi_device *dev); +/* Reset functions */ +int i_APCI3200_Reset(struct comedi_device *dev); + +int i_APCI3200_ReadCJCCalOffset(struct comedi_device *dev, unsigned int *data); +int i_APCI3200_ReadCJCValue(struct comedi_device *dev, unsigned int *data); +int i_APCI3200_ReadCalibrationGainValue(struct comedi_device *dev, unsigned int *data); +int i_APCI3200_ReadCalibrationOffsetValue(struct comedi_device *dev, unsigned int *data); +int i_APCI3200_Read1AnalogInputChannel(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +int i_APCI3200_ReadCJCCalGain(struct comedi_device *dev, unsigned int *data); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c new file mode 100644 index 00000000..acaceb01 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.c @@ -0,0 +1,743 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/*. + + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstraße 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-------------------------------+---------------------------------------+ + | Project : APCI-3501 | Compiler : GCC | + | Module name : hwdrv_apci3501.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: Eric Stolz | Date : 02/12/2002 | + +-------------------------------+---------------------------------------+ + | Description : Hardware Layer Access For APCI-3501 | + +-----------------------------------------------------------------------+ + | UPDATES | + +----------+-----------+------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Included files | ++----------------------------------------------------------------------------+ +*/ +#include "hwdrv_apci3501.h" + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_ReadDigitalInput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read value of the selected channel or port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3501_ReadDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp; + unsigned int ui_NoOfChannel; + ui_NoOfChannel = CR_CHAN(insn->chanspec); + ui_Temp = data[0]; + *data = inl(devpriv->iobase + APCI3501_DIGITAL_IP); + if (ui_Temp == 0) { + *data = (*data >> ui_NoOfChannel) & 0x1; + } /* if (ui_Temp==0) */ + else { + if (ui_Temp == 1) { + + *data = *data & 0x3; + } /* if (ui_Temp==1) */ + else { + printk("\nSpecified channel not supported \n"); + } /* elseif (ui_Temp==1) */ + } /* elseif (ui_Temp==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_ConfigDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Digital Output Subdevice. | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[1] : 1 Enable VCC Interrupt | +| 0 Disable VCC Interrupt | +| data[2] : 1 Enable CC Interrupt | +| 0 Disable CC Interrupt | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI3501_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + if ((data[0] != 0) && (data[0] != 1)) { + comedi_error(dev, + "Not a valid Data !!! ,Data should be 1 or 0\n"); + return -EINVAL; + } /* if ( (data[0]!=0) && (data[0]!=1) ) */ + if (data[0]) { + devpriv->b_OutputMemoryStatus = ADDIDATA_ENABLE; + } /* if (data[0]) */ + else { + devpriv->b_OutputMemoryStatus = ADDIDATA_DISABLE; + } /* else if (data[0]) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_WriteDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : writes To the digital Output Subdevice | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s : Subdevice Pointer | +| struct comedi_insn *insn : Insn Structure Pointer | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI3501_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp, ui_Temp1; + unsigned int ui_NoOfChannel = CR_CHAN(insn->chanspec); /* get the channel */ + if (devpriv->b_OutputMemoryStatus) { + ui_Temp = inl(devpriv->iobase + APCI3501_DIGITAL_OP); + } /* if(devpriv->b_OutputMemoryStatus ) */ + else { + ui_Temp = 0; + } /* if(devpriv->b_OutputMemoryStatus ) */ + if (data[3] == 0) { + if (data[1] == 0) { + data[0] = (data[0] << ui_NoOfChannel) | ui_Temp; + outl(data[0], devpriv->iobase + APCI3501_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + data[0] = (data[0] << (2 * data[2])) | ui_Temp; + outl(data[0], + devpriv->iobase + APCI3501_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==0) */ + else { + if (data[3] == 1) { + if (data[1] == 0) { + data[0] = ~data[0] & 0x1; + ui_Temp1 = 1; + ui_Temp1 = ui_Temp1 << ui_NoOfChannel; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + (data[0] << ui_NoOfChannel) ^ + 0xffffffff; + data[0] = data[0] & ui_Temp; + outl(data[0], + devpriv->iobase + APCI3501_DIGITAL_OP); + } /* if(data[1]==0) */ + else { + if (data[1] == 1) { + data[0] = ~data[0] & 0x3; + ui_Temp1 = 3; + ui_Temp1 = ui_Temp1 << 2 * data[2]; + ui_Temp = ui_Temp | ui_Temp1; + data[0] = + ((data[0] << (2 * + data[2])) ^ + 0xffffffff) & ui_Temp; + outl(data[0], + devpriv->iobase + + APCI3501_DIGITAL_OP); + } /* if(data[1]==1) */ + else { + printk("\nSpecified channel not supported\n"); + } /* else if(data[1]==1) */ + } /* elseif(data[1]==0) */ + } /* if(data[3]==1); */ + else { + printk("\nSpecified functionality does not exist\n"); + return -EINVAL; + } /* if else data[3]==1) */ + } /* if else data[3]==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_ReadDigitalOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read value of the selected channel or port | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int ui_NoOfChannels : No Of Channels To read | +| unsigned int *data : Data Pointer to read status | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI3501_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ui_Temp; + unsigned int ui_NoOfChannel; + + ui_NoOfChannel = CR_CHAN(insn->chanspec); + ui_Temp = data[0]; + *data = inl(devpriv->iobase + APCI3501_DIGITAL_OP); + if (ui_Temp == 0) { + *data = (*data >> ui_NoOfChannel) & 0x1; + } /* if (ui_Temp==0) */ + else { + if (ui_Temp == 1) { + *data = *data & 0x3; + + } /* if (ui_Temp==1) */ + else { + printk("\nSpecified channel not supported \n"); + } /* else if (ui_Temp==1) */ + } /* else if (ui_Temp==0) */ + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_ConfigAnalogOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Analog Output Subdevice | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s : Subdevice Pointer | +| struct comedi_insn *insn : Insn Structure Pointer | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : Voltage Mode | +| 0:Mode 0 | +| 1:Mode 1 | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI3501_ConfigAnalogOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + outl(data[0], + devpriv->iobase + APCI3501_ANALOG_OUTPUT + + APCI3501_AO_VOLT_MODE); + + if (data[0]) { + devpriv->b_InterruptMode = MODE1; + } else { + devpriv->b_InterruptMode = MODE0; + } + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_WriteAnalogOutput | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Writes To the Selected Anlog Output Channel | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| struct comedi_subdevice *s : Subdevice Pointer | +| struct comedi_insn *insn : Insn Structure Pointer | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI3501_WriteAnalogOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ul_Command1 = 0, ul_Channel_no, ul_Polarity, ul_DAC_Ready = 0; + + ul_Channel_no = CR_CHAN(insn->chanspec); + + if (devpriv->b_InterruptMode == MODE1) { + ul_Polarity = 0x80000000; + if ((*data < 0) || (*data > 16384)) { + printk("\nIn WriteAnalogOutput :: Not Valid Data\n"); + } + + } /* end if(devpriv->b_InterruptMode==MODE1) */ + else { + ul_Polarity = 0; + if ((*data < 0) || (*data > 8192)) { + printk("\nIn WriteAnalogOutput :: Not Valid Data\n"); + } + + } /* end else */ + + if ((ul_Channel_no < 0) || (ul_Channel_no > 7)) { + printk("\nIn WriteAnalogOutput :: Not Valid Channel\n"); + } /* end if((ul_Channel_no<0)||(ul_Channel_no>7)) */ + + ul_DAC_Ready = inl(devpriv->iobase + APCI3501_ANALOG_OUTPUT); + + while (ul_DAC_Ready == 0) { + ul_DAC_Ready = inl(devpriv->iobase + APCI3501_ANALOG_OUTPUT); + ul_DAC_Ready = (ul_DAC_Ready >> 8) & 1; + } + + if (ul_DAC_Ready) { +/* Output the Value on the output channels. */ + ul_Command1 = + (unsigned int) ((unsigned int) (ul_Channel_no & 0xFF) | + (unsigned int) ((*data << 0x8) & 0x7FFFFF00L) | + (unsigned int) (ul_Polarity)); + outl(ul_Command1, + devpriv->iobase + APCI3501_ANALOG_OUTPUT + + APCI3501_AO_PROG); + } + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_ConfigTimerCounterWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Configures The Timer , Counter or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 0 Configure As Timer | +| 1 Configure As Counter | +| 2 Configure As Watchdog | +| data[1] : 1 Enable Interrupt | +| 0 Disable Interrupt | +| data[2] : Time Unit | +| data[3] : Reload Value | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +int i_APCI3501_ConfigTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ul_Command1 = 0; + devpriv->tsk_Current = current; + if (data[0] == ADDIDATA_WATCHDOG) { + + devpriv->b_TimerSelectMode = ADDIDATA_WATCHDOG; + /* Disable the watchdog */ + outl(0x0, devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG); /* disable Wa */ + + if (data[1] == 1) { + /* Enable TIMER int & DISABLE ALL THE OTHER int SOURCES */ + outl(0x02, + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + } else { + outl(0x0, devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG); /* disable Timer interrupt */ + } + + /* Loading the Timebase value */ + outl(data[2], + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_TIMEBASE); + + /* Loading the Reload value */ + outl(data[3], + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_RELOAD_VALUE); + /* Set the mode */ + ul_Command1 = inl(devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG) | 0xFFF819E0UL; /* e2->e0 */ + outl(ul_Command1, + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + } /* end if(data[0]==ADDIDATA_WATCHDOG) */ + + else if (data[0] == ADDIDATA_TIMER) { + /* First Stop The Timer */ + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + ul_Command1 = ul_Command1 & 0xFFFFF9FEUL; + outl(ul_Command1, devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG); /* Stop The Timer */ + devpriv->b_TimerSelectMode = ADDIDATA_TIMER; + if (data[1] == 1) { + /* Enable TIMER int & DISABLE ALL THE OTHER int SOURCES */ + outl(0x02, + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + } else { + outl(0x0, devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG); /* disable Timer interrupt */ + } + + /* Loading Timebase */ + outl(data[2], + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_TIMEBASE); + + /* Loading the Reload value */ + outl(data[3], + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_RELOAD_VALUE); + + /* printk ("\nTimer Address :: %x\n", (devpriv->iobase+APCI3501_WATCHDOG)); */ + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + ul_Command1 = + (ul_Command1 & 0xFFF719E2UL) | 2UL << 13UL | 0x10UL; + outl(ul_Command1, devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG); /* mode 2 */ + + } /* end if(data[0]==ADDIDATA_TIMER) */ + + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_StartStopWriteTimerCounterWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Start / Stop The Selected Timer , Counter or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 0 Timer | +| 1 Counter | +| 2 Watchdog | | data[1] : 1 Start | +| 0 Stop | 2 Trigger | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3501_StartStopWriteTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + unsigned int ul_Command1 = 0; + int i_Temp; + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) { + + if (data[1] == 1) { + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL; + /* Enable the Watchdog */ + outl(ul_Command1, + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + } + + else if (data[1] == 0) /* Stop The Watchdog */ + { + /* Stop The Watchdog */ + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + ul_Command1 = ul_Command1 & 0xFFFFF9FEUL; + outl(0x0, + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + } else if (data[1] == 2) { + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x200UL; + outl(ul_Command1, + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + } /* if(data[1]==2) */ + } /* end if (devpriv->b_TimerSelectMode==ADDIDATA_WATCHDOG) */ + + if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) { + if (data[1] == 1) { + + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x1UL; + /* Enable the Timer */ + outl(ul_Command1, + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + } else if (data[1] == 0) { + /* Stop The Timer */ + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + ul_Command1 = ul_Command1 & 0xFFFFF9FEUL; + outl(ul_Command1, + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + } + + else if (data[1] == 2) { + /* Trigger the Timer */ + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + ul_Command1 = (ul_Command1 & 0xFFFFF9FFUL) | 0x200UL; + outl(ul_Command1, + devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_PROG); + } + + } /* end if (devpriv->b_TimerSelectMode==ADDIDATA_TIMER) */ + i_Temp = inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_TRIG_STATUS) & 0x1; + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_ReadTimerCounterWatchdog | +| (struct comedi_device *dev,struct comedi_subdevice *s, | +| struct comedi_insn *insn,unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read The Selected Timer , Counter or Watchdog | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev : Driver handle | +| unsigned int *data : Data Pointer contains | +| configuration parameters as below | +| | +| data[0] : 0 Timer | +| 1 Counter | +| 2 Watchdog | | data[1] : Timer Counter Watchdog Number | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3501_ReadTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) +{ + + if (devpriv->b_TimerSelectMode == ADDIDATA_WATCHDOG) { + data[0] = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_TRIG_STATUS) & 0x1; + data[1] = inl(devpriv->iobase + APCI3501_WATCHDOG); + } /* end if (devpriv->b_TimerSelectMode==ADDIDATA_WATCHDOG) */ + + else if (devpriv->b_TimerSelectMode == ADDIDATA_TIMER) { + data[0] = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_TRIG_STATUS) & 0x1; + data[1] = inl(devpriv->iobase + APCI3501_WATCHDOG); + } /* end if (devpriv->b_TimerSelectMode==ADDIDATA_TIMER) */ + + else if ((devpriv->b_TimerSelectMode != ADDIDATA_TIMER) + && (devpriv->b_TimerSelectMode != ADDIDATA_WATCHDOG)) { + printk("\nIn ReadTimerCounterWatchdog :: Invalid Subdevice \n"); + } + return insn->n; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3501_Reset(struct comedi_device *dev) | +| | ++----------------------------------------------------------------------------+ +| Task :Resets the registers of the card | ++----------------------------------------------------------------------------+ +| Input Parameters : | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : | +| | ++----------------------------------------------------------------------------+ +*/ + +int i_APCI3501_Reset(struct comedi_device *dev) +{ + int i_Count = 0, i_temp = 0; + unsigned int ul_Command1 = 0, ul_Polarity, ul_DAC_Ready = 0; + outl(0x0, devpriv->iobase + APCI3501_DIGITAL_OP); + outl(1, devpriv->iobase + APCI3501_ANALOG_OUTPUT + + APCI3501_AO_VOLT_MODE); + + ul_Polarity = 0x80000000; + + for (i_Count = 0; i_Count <= 7; i_Count++) { + ul_DAC_Ready = inl(devpriv->iobase + APCI3501_ANALOG_OUTPUT); + + while (ul_DAC_Ready == 0) { + ul_DAC_Ready = + inl(devpriv->iobase + APCI3501_ANALOG_OUTPUT); + ul_DAC_Ready = (ul_DAC_Ready >> 8) & 1; + } + + if (ul_DAC_Ready) { + /* Output the Value on the output channels. */ + ul_Command1 = + (unsigned int) ((unsigned int) (i_Count & 0xFF) | + (unsigned int) ((i_temp << 0x8) & 0x7FFFFF00L) | + (unsigned int) (ul_Polarity)); + outl(ul_Command1, + devpriv->iobase + APCI3501_ANALOG_OUTPUT + + APCI3501_AO_PROG); + } + } + + return 0; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : static void v_APCI3501_Interrupt | +| (int irq , void *d) | ++----------------------------------------------------------------------------+ +| Task : Interrupt processing Routine | ++----------------------------------------------------------------------------+ +| Input Parameters : int irq : irq number | +| void *d : void pointer | ++----------------------------------------------------------------------------+ +| Output Parameters : -- | ++----------------------------------------------------------------------------+ +| Return Value : TRUE : No error occur | +| : FALSE : Error occur. Return the error | +| | ++----------------------------------------------------------------------------+ +*/ +void v_APCI3501_Interrupt(int irq, void *d) +{ + int i_temp; + struct comedi_device *dev = d; + unsigned int ui_Timer_AOWatchdog; + unsigned long ul_Command1; + /* Disable Interrupt */ + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG); + + ul_Command1 = (ul_Command1 & 0xFFFFF9FDul); + outl(ul_Command1, + devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG); + + ui_Timer_AOWatchdog = + inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_IRQ) & 0x1; + + if ((!ui_Timer_AOWatchdog)) { + comedi_error(dev, "IRQ from unknown source"); + return; + } + +/* +* Enable Interrupt Send a signal to from kernel to user space +*/ + send_sig(SIGIO, devpriv->tsk_Current, 0); + ul_Command1 = + inl(devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG); + ul_Command1 = ((ul_Command1 & 0xFFFFF9FDul) | 1 << 1); + outl(ul_Command1, + devpriv->iobase + APCI3501_WATCHDOG + APCI3501_TCW_PROG); + i_temp = inl(devpriv->iobase + APCI3501_WATCHDOG + + APCI3501_TCW_TRIG_STATUS) & 0x1; + return; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.h new file mode 100644 index 00000000..63df635a --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3501.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* Card Specific information */ +#define APCI3501_BOARD_VENDOR_ID 0x15B8 +#define APCI3501_ADDRESS_RANGE 255 + +#define APCI3501_DIGITAL_IP 0x50 +#define APCI3501_DIGITAL_OP 0x40 +#define APCI3501_ANALOG_OUTPUT 0x00 + +/* Analog Output related Defines */ +#define APCI3501_AO_VOLT_MODE 0 +#define APCI3501_AO_PROG 4 +#define APCI3501_AO_TRIG_SCS 8 +#define UNIPOLAR 0 +#define BIPOLAR 1 +#define MODE0 0 +#define MODE1 1 +/* ANALOG OUTPUT RANGE */ +static struct comedi_lrange range_apci3501_ao = { 2, { + BIP_RANGE(10), + UNI_RANGE(10) + } +}; + +/* Watchdog Related Defines */ + +#define APCI3501_WATCHDOG 0x20 +#define APCI3501_TCW_SYNC_ENABLEDISABLE 0 +#define APCI3501_TCW_RELOAD_VALUE 4 +#define APCI3501_TCW_TIMEBASE 8 +#define APCI3501_TCW_PROG 12 +#define APCI3501_TCW_TRIG_STATUS 16 +#define APCI3501_TCW_IRQ 20 +#define APCI3501_TCW_WARN_TIMEVAL 24 +#define APCI3501_TCW_WARN_TIMEBASE 28 +#define ADDIDATA_TIMER 0 +#define ADDIDATA_WATCHDOG 2 + +/* Hardware Layer functions for Apci3501 */ + +/* AO */ +int i_APCI3501_ConfigAnalogOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3501_WriteAnalogOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* +* DI for di read INT i_APCI3501_ReadDigitalInput(struct +* comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn +* *insn,unsigned int *data); +*/ + +int i_APCI3501_ReadDigitalInput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* DO */ +int i_APCI3501_ConfigDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3501_WriteDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3501_ReadDigitalOutput(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* TIMER + * timer value is passed as u seconds + */ + +int i_APCI3501_ConfigTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int i_APCI3501_StartStopWriteTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +int i_APCI3501_ReadTimerCounterWatchdog(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +/* Interrupt */ +void v_APCI3501_Interrupt(int irq, void *d); + +/* Reset functions */ +int i_APCI3501_Reset(struct comedi_device *dev); diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.c b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.c new file mode 100644 index 00000000..fff99df5 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.c @@ -0,0 +1,1664 @@ +/** +@verbatim + +Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + + ADDI-DATA GmbH + Dieselstrasse 3 + D-77833 Ottersweier + Tel: +19(0)7223/9493-0 + Fax: +49(0)7223/9493-92 + http://www.addi-data.com + info@addi-data.com + +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You should also find the complete GPL in the COPYING file accompanying this source code. + +@endverbatim +*/ +/* + +-----------------------------------------------------------------------+ + | (C) ADDI-DATA GmbH Dieselstrasse 3 D-77833 Ottersweier | + +-----------------------------------------------------------------------+ + | Tel : +49 (0) 7223/9493-0 | email : info@addi-data.com | + | Fax : +49 (0) 7223/9493-92 | Internet : http://www.addi-data.com | + +-----------------------------------------------------------------------+ + | Project : APCI-3XXX | Compiler : GCC | + | Module name : hwdrv_apci3xxx.c| Version : 2.96 | + +-------------------------------+---------------------------------------+ + | Project manager: S. Weber | Date : 15/09/2005 | + +-----------------------------------------------------------------------+ + | Description :APCI3XXX Module. Hardware abstraction Layer for APCI3XXX| + +-----------------------------------------------------------------------+ + | UPDATE'S | + +-----------------------------------------------------------------------+ + | Date | Author | Description of updates | + +----------+-----------+------------------------------------------------+ + | | | | + | | | | + +----------+-----------+------------------------------------------------+ +*/ + +#include "hwdrv_apci3xxx.h" + +/* ++----------------------------------------------------------------------------+ +| ANALOG INPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_TestConversionStarted | +| (struct comedi_device *dev) | ++----------------------------------------------------------------------------+ +| Task Test if any conversion started | ++----------------------------------------------------------------------------+ +| Input Parameters : - | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0 : Conversion not started | +| 1 : Conversion started | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_TestConversionStarted(struct comedi_device *dev) +{ + if ((readl(devpriv->dw_AiBase + 8) & 0x80000UL) == 0x80000UL) + return 1; + else + return 0; + +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_AnalogInputConfigOperatingMode | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task Converting mode and convert time selection | ++----------------------------------------------------------------------------+ +| Input Parameters : b_SingleDiff = (unsigned char) data[1]; | +| b_TimeBase = (unsigned char) data[2]; (0: ns, 1:micros 2:ms)| +| dw_ReloadValue = (unsigned int) data[3]; | +| ........ | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value :>0 : No error | +| -1 : Single/Diff selection error | +| -2 : Convert time base unity selection error | +| -3 : Convert time value selection error | +| -10: Any conversion started | +| .... | +| -100 : Config command error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_AnalogInputConfigOperatingMode(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_TimeBase = 0; + unsigned char b_SingleDiff = 0; + unsigned int dw_ReloadValue = 0; + unsigned int dw_TestReloadValue = 0; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n == 4) { + /****************************/ + /* Get the Singel/Diff flag */ + /****************************/ + + b_SingleDiff = (unsigned char) data[1]; + + /****************************/ + /* Get the time base unitiy */ + /****************************/ + + b_TimeBase = (unsigned char) data[2]; + + /*************************************/ + /* Get the convert time reload value */ + /*************************************/ + + dw_ReloadValue = (unsigned int) data[3]; + + /**********************/ + /* Test the time base */ + /**********************/ + + if ((this_board->b_AvailableConvertUnit & (1 << b_TimeBase)) != + 0) { + /*******************************/ + /* Test the convert time value */ + /*******************************/ + + if (dw_ReloadValue <= 65535) { + dw_TestReloadValue = dw_ReloadValue; + + if (b_TimeBase == 1) { + dw_TestReloadValue = + dw_TestReloadValue * 1000UL; + } + if (b_TimeBase == 2) { + dw_TestReloadValue = + dw_TestReloadValue * 1000000UL; + } + + /*******************************/ + /* Test the convert time value */ + /*******************************/ + + if (dw_TestReloadValue >= + devpriv->s_EeParameters. + ui_MinAcquisitiontimeNs) { + if ((b_SingleDiff == APCI3XXX_SINGLE) + || (b_SingleDiff == + APCI3XXX_DIFF)) { + if (((b_SingleDiff == APCI3XXX_SINGLE) + && (devpriv->s_EeParameters.i_NbrAiChannel == 0)) + || ((b_SingleDiff == APCI3XXX_DIFF) + && (this_board->i_NbrAiChannelDiff == 0)) + ) { + /*******************************/ + /* Single/Diff selection error */ + /*******************************/ + + printk("Single/Diff selection error\n"); + i_ReturnValue = -1; + } else { + /**********************************/ + /* Test if conversion not started */ + /**********************************/ + + if (i_APCI3XXX_TestConversionStarted(dev) == 0) { + devpriv-> + ui_EocEosConversionTime + = + (unsigned int) + dw_ReloadValue; + devpriv-> + b_EocEosConversionTimeBase + = + b_TimeBase; + devpriv-> + b_SingelDiff + = + b_SingleDiff; + devpriv-> + b_AiInitialisation + = 1; + + /*******************************/ + /* Set the convert timing unit */ + /*******************************/ + + writel((unsigned int)b_TimeBase, + devpriv->dw_AiBase + 36); + + /**************************/ + /* Set the convert timing */ + /*************************/ + + writel(dw_ReloadValue, devpriv->dw_AiBase + 32); + } else { + /**************************/ + /* Any conversion started */ + /**************************/ + + printk("Any conversion started\n"); + i_ReturnValue = + -10; + } + } + } else { + /*******************************/ + /* Single/Diff selection error */ + /*******************************/ + + printk("Single/Diff selection error\n"); + i_ReturnValue = -1; + } + } else { + /************************/ + /* Time selection error */ + /************************/ + + printk("Convert time value selection error\n"); + i_ReturnValue = -3; + } + } else { + /************************/ + /* Time selection error */ + /************************/ + + printk("Convert time value selection error\n"); + i_ReturnValue = -3; + } + } else { + /*****************************/ + /* Time base selection error */ + /*****************************/ + + printk("Convert time base unity selection error\n"); + i_ReturnValue = -2; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_InsnConfigAnalogInput | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task Converting mode and convert time selection | ++----------------------------------------------------------------------------+ +| Input Parameters : b_ConvertMode = (unsigned char) data[0]; | +| b_TimeBase = (unsigned char) data[1]; (0: ns, 1:micros 2:ms)| +| dw_ReloadValue = (unsigned int) data[2]; | +| ........ | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value :>0: No error | +| .... | +| -100 : Config command error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_InsnConfigAnalogInput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + switch ((unsigned char) data[0]) { + case APCI3XXX_CONFIGURATION: + i_ReturnValue = + i_APCI3XXX_AnalogInputConfigOperatingMode(dev, + s, insn, data); + break; + + default: + i_ReturnValue = -100; + printk("Config command error %d\n", data[0]); + break; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_InsnReadAnalogInput | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task Read 1 analog input | ++----------------------------------------------------------------------------+ +| Input Parameters : b_Range = CR_RANGE(insn->chanspec); | +| b_Channel = CR_CHAN(insn->chanspec); | +| dw_NbrOfAcquisition = insn->n; | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value :>0: No error | +| -3 : Channel selection error | +| -4 : Configuration selelection error | +| -10: Any conversion started | +| .... | +| -100 : Config command error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_InsnReadAnalogInput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_Configuration = (unsigned char) CR_RANGE(insn->chanspec); + unsigned char b_Channel = (unsigned char) CR_CHAN(insn->chanspec); + unsigned int dw_Temp = 0; + unsigned int dw_Configuration = 0; + unsigned int dw_AcquisitionCpt = 0; + unsigned char b_Interrupt = 0; + + /*************************************/ + /* Test if operating mode configured */ + /*************************************/ + + if (devpriv->b_AiInitialisation) { + /***************************/ + /* Test the channel number */ + /***************************/ + + if (((b_Channel < devpriv->s_EeParameters.i_NbrAiChannel) + && (devpriv->b_SingelDiff == APCI3XXX_SINGLE)) + || ((b_Channel < this_board->i_NbrAiChannelDiff) + && (devpriv->b_SingelDiff == APCI3XXX_DIFF))) { + /**********************************/ + /* Test the channel configuration */ + /**********************************/ + + if (b_Configuration > 7) { + /***************************/ + /* Channel not initialised */ + /***************************/ + + i_ReturnValue = -4; + printk("Channel %d range %d selection error\n", + b_Channel, b_Configuration); + } + } else { + /***************************/ + /* Channel selection error */ + /***************************/ + + i_ReturnValue = -3; + printk("Channel %d selection error\n", b_Channel); + } + + /**************************/ + /* Test if no error occur */ + /**************************/ + + if (i_ReturnValue >= 0) { + /************************/ + /* Test the buffer size */ + /************************/ + + if ((b_Interrupt != 0) || ((b_Interrupt == 0) + && (insn->n >= 1))) { + /**********************************/ + /* Test if conversion not started */ + /**********************************/ + + if (i_APCI3XXX_TestConversionStarted(dev) == 0) { + /******************/ + /* Clear the FIFO */ + /******************/ + + writel(0x10000UL, devpriv->dw_AiBase + 12); + + /*******************************/ + /* Get and save the delay mode */ + /*******************************/ + + dw_Temp = readl(devpriv->dw_AiBase + 4); + dw_Temp = dw_Temp & 0xFFFFFEF0UL; + + /***********************************/ + /* Channel configuration selection */ + /***********************************/ + + writel(dw_Temp, devpriv->dw_AiBase + 4); + + /**************************/ + /* Make the configuration */ + /**************************/ + + dw_Configuration = + (b_Configuration & 3) | + ((unsigned int) (b_Configuration >> 2) + << 6) | ((unsigned int) devpriv-> + b_SingelDiff << 7); + + /***************************/ + /* Write the configuration */ + /***************************/ + + writel(dw_Configuration, + devpriv->dw_AiBase + 0); + + /*********************/ + /* Channel selection */ + /*********************/ + + writel(dw_Temp | 0x100UL, + devpriv->dw_AiBase + 4); + writel((unsigned int) b_Channel, + devpriv->dw_AiBase + 0); + + /***********************/ + /* Restaure delay mode */ + /***********************/ + + writel(dw_Temp, devpriv->dw_AiBase + 4); + + /***********************************/ + /* Set the number of sequence to 1 */ + /***********************************/ + + writel(1, devpriv->dw_AiBase + 48); + + /***************************/ + /* Save the interrupt flag */ + /***************************/ + + devpriv->b_EocEosInterrupt = + b_Interrupt; + + /*******************************/ + /* Save the number of channels */ + /*******************************/ + + devpriv->ui_AiNbrofChannels = 1; + + /******************************/ + /* Test if interrupt not used */ + /******************************/ + + if (b_Interrupt == 0) { + for (dw_AcquisitionCpt = 0; + dw_AcquisitionCpt < + insn->n; + dw_AcquisitionCpt++) { + /************************/ + /* Start the conversion */ + /************************/ + + writel(0x80000UL, devpriv->dw_AiBase + 8); + + /****************/ + /* Wait the EOS */ + /****************/ + + do { + dw_Temp = readl(devpriv->dw_AiBase + 20); + dw_Temp = dw_Temp & 1; + } while (dw_Temp != 1); + + /*************************/ + /* Read the analog value */ + /*************************/ + + data[dw_AcquisitionCpt] = (unsigned int)readl(devpriv->dw_AiBase + 28); + } + } else { + /************************/ + /* Start the conversion */ + /************************/ + + writel(0x180000UL, devpriv->dw_AiBase + 8); + } + } else { + /**************************/ + /* Any conversion started */ + /**************************/ + + printk("Any conversion started\n"); + i_ReturnValue = -10; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + } + } else { + /***************************/ + /* Channel selection error */ + /***************************/ + + printk("Operating mode not configured\n"); + i_ReturnValue = -1; + } + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function name : void v_APCI3XXX_Interrupt (int irq, | +| void *d) | ++----------------------------------------------------------------------------+ +| Task :Interrupt handler for APCI3XXX | +| When interrupt occurs this gets called. | +| First it finds which interrupt has been generated and | +| handles corresponding interrupt | ++----------------------------------------------------------------------------+ +| Input Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : - | ++----------------------------------------------------------------------------+ +*/ + +static void v_APCI3XXX_Interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned char b_CopyCpt = 0; + unsigned int dw_Status = 0; + + /***************************/ + /* Test if interrupt occur */ + /***************************/ + + dw_Status = readl(devpriv->dw_AiBase + 16); + if ( (dw_Status & 0x2UL) == 0x2UL) { + /***********************/ + /* Reset the interrupt */ + /***********************/ + + writel(dw_Status, devpriv->dw_AiBase + 16); + + /*****************************/ + /* Test if interrupt enabled */ + /*****************************/ + + if (devpriv->b_EocEosInterrupt == 1) { + /********************************/ + /* Read all analog inputs value */ + /********************************/ + + for (b_CopyCpt = 0; + b_CopyCpt < devpriv->ui_AiNbrofChannels; + b_CopyCpt++) { + devpriv->ui_AiReadData[b_CopyCpt] = + (unsigned int)readl(devpriv->dw_AiBase + 28); + } + + /**************************/ + /* Set the interrupt flag */ + /**************************/ + + devpriv->b_EocEosInterrupt = 2; + + /**********************************************/ + /* Send a signal to from kernel to user space */ + /**********************************************/ + + send_sig(SIGIO, devpriv->tsk_Current, 0); + } + } +} + +/* ++----------------------------------------------------------------------------+ +| ANALOG OUTPUT SUBDEVICE | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_InsnWriteAnalogOutput | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task Read 1 analog input | ++----------------------------------------------------------------------------+ +| Input Parameters : b_Range = CR_RANGE(insn->chanspec); | +| b_Channel = CR_CHAN(insn->chanspec); | +| data[0] = analog value; | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value :>0: No error | +| -3 : Channel selection error | +| -4 : Configuration selelection error | +| .... | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_InsnWriteAnalogOutput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned char b_Range = (unsigned char) CR_RANGE(insn->chanspec); + unsigned char b_Channel = (unsigned char) CR_CHAN(insn->chanspec); + unsigned int dw_Status = 0; + int i_ReturnValue = insn->n; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + /***************************/ + /* Test the channel number */ + /***************************/ + + if (b_Channel < devpriv->s_EeParameters.i_NbrAoChannel) { + /**********************************/ + /* Test the channel configuration */ + /**********************************/ + + if (b_Range < 2) { + /***************************/ + /* Set the range selection */ + /***************************/ + + writel(b_Range, devpriv->dw_AiBase + 96); + + /**************************************************/ + /* Write the analog value to the selected channel */ + /**************************************************/ + + writel((data[0] << 8) | b_Channel, + devpriv->dw_AiBase + 100); + + /****************************/ + /* Wait the end of transfer */ + /****************************/ + + do { + dw_Status = readl(devpriv->dw_AiBase + 96); + } while ((dw_Status & 0x100) != 0x100); + } else { + /***************************/ + /* Channel not initialised */ + /***************************/ + + i_ReturnValue = -4; + printk("Channel %d range %d selection error\n", + b_Channel, b_Range); + } + } else { + /***************************/ + /* Channel selection error */ + /***************************/ + + i_ReturnValue = -3; + printk("Channel %d selection error\n", b_Channel); + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| TTL FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_InsnConfigInitTTLIO | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task You must calling this function be | +| for you call any other function witch access of TTL. | +| APCI3XXX_TTL_INIT_DIRECTION_PORT2(user inputs for direction)| ++----------------------------------------------------------------------------+ +| Input Parameters : b_InitType = (unsigned char) data[0]; | +| b_Port2Mode = (unsigned char) data[1]; | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value :>0: No error | +| -1: Port 2 mode selection is wrong | +| .... | +| -100 : Config command error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_InsnConfigInitTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_Command = 0; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + /*******************/ + /* Get the command */ + /* **************** */ + + b_Command = (unsigned char) data[0]; + + /********************/ + /* Test the command */ + /********************/ + + if (b_Command == APCI3XXX_TTL_INIT_DIRECTION_PORT2) { + /***************************************/ + /* Test the initialisation buffer size */ + /***************************************/ + + if ((b_Command == APCI3XXX_TTL_INIT_DIRECTION_PORT2) + && (insn->n != 2)) { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + } else { + /************************/ + /* Config command error */ + /************************/ + + printk("Command selection error\n"); + i_ReturnValue = -100; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + /*********************************************************************************/ + /* Test if no error occur and APCI3XXX_TTL_INIT_DIRECTION_PORT2 command selected */ + /*********************************************************************************/ + + if ((i_ReturnValue >= 0) + && (b_Command == APCI3XXX_TTL_INIT_DIRECTION_PORT2)) { + /**********************/ + /* Test the direction */ + /**********************/ + + if ((data[1] == 0) || (data[1] == 0xFF)) { + /**************************/ + /* Save the configuration */ + /**************************/ + + devpriv->ul_TTLPortConfiguration[0] = + devpriv->ul_TTLPortConfiguration[0] | data[1]; + } else { + /************************/ + /* Port direction error */ + /************************/ + + printk("Port 2 direction selection error\n"); + i_ReturnValue = -1; + } + } + + /**************************/ + /* Test if no error occur */ + /**************************/ + + if (i_ReturnValue >= 0) { + /***********************************/ + /* Test if TTL port initilaisation */ + /***********************************/ + + if (b_Command == APCI3XXX_TTL_INIT_DIRECTION_PORT2) { + /*************************/ + /* Set the configuration */ + /*************************/ + + outl(data[1], devpriv->iobase + 224); + } + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| TTL INPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_InsnBitsTTLIO | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Write the selected output mask and read the status from| +| all TTL channles | ++----------------------------------------------------------------------------+ +| Input Parameters : dw_ChannelMask = data [0]; | +| dw_BitMask = data [1]; | ++----------------------------------------------------------------------------+ +| Output Parameters : data[1] : All TTL channles states | ++----------------------------------------------------------------------------+ +| Return Value : >0 : No error | +| -4 : Channel mask error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_InsnBitsTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_ChannelCpt = 0; + unsigned int dw_ChannelMask = 0; + unsigned int dw_BitMask = 0; + unsigned int dw_Status = 0; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 2) { + /*******************************/ + /* Get the channe and bit mask */ + /*******************************/ + + dw_ChannelMask = data[0]; + dw_BitMask = data[1]; + + /*************************/ + /* Test the channel mask */ + /*************************/ + + if (((dw_ChannelMask & 0XFF00FF00) == 0) && + (((devpriv->ul_TTLPortConfiguration[0] & 0xFF) == 0xFF) + || (((devpriv->ul_TTLPortConfiguration[0] & + 0xFF) == 0) + && ((dw_ChannelMask & 0XFF0000) == + 0)))) { + /*********************************/ + /* Test if set/reset any channel */ + /*********************************/ + + if (dw_ChannelMask) { + /****************************************/ + /* Test if set/rest any port 0 channels */ + /****************************************/ + + if (dw_ChannelMask & 0xFF) { + /*******************************************/ + /* Read port 0 (first digital output port) */ + /*******************************************/ + + dw_Status = inl(devpriv->iobase + 80); + + for (b_ChannelCpt = 0; b_ChannelCpt < 8; + b_ChannelCpt++) { + if ((dw_ChannelMask >> + b_ChannelCpt) & + 1) { + dw_Status = + (dw_Status & + (0xFF - (1 << b_ChannelCpt))) | (dw_BitMask & (1 << b_ChannelCpt)); + } + } + + outl(dw_Status, devpriv->iobase + 80); + } + + /****************************************/ + /* Test if set/rest any port 2 channels */ + /****************************************/ + + if (dw_ChannelMask & 0xFF0000) { + dw_BitMask = dw_BitMask >> 16; + dw_ChannelMask = dw_ChannelMask >> 16; + + /********************************************/ + /* Read port 2 (second digital output port) */ + /********************************************/ + + dw_Status = inl(devpriv->iobase + 112); + + for (b_ChannelCpt = 0; b_ChannelCpt < 8; + b_ChannelCpt++) { + if ((dw_ChannelMask >> + b_ChannelCpt) & + 1) { + dw_Status = + (dw_Status & + (0xFF - (1 << b_ChannelCpt))) | (dw_BitMask & (1 << b_ChannelCpt)); + } + } + + outl(dw_Status, devpriv->iobase + 112); + } + } + + /*******************************************/ + /* Read port 0 (first digital output port) */ + /*******************************************/ + + data[1] = inl(devpriv->iobase + 80); + + /******************************************/ + /* Read port 1 (first digital input port) */ + /******************************************/ + + data[1] = data[1] | (inl(devpriv->iobase + 64) << 8); + + /************************/ + /* Test if port 2 input */ + /************************/ + + if ((devpriv->ul_TTLPortConfiguration[0] & 0xFF) == 0) { + data[1] = + data[1] | (inl(devpriv->iobase + + 96) << 16); + } else { + data[1] = + data[1] | (inl(devpriv->iobase + + 112) << 16); + } + } else { + /************************/ + /* Config command error */ + /************************/ + + printk("Channel mask error\n"); + i_ReturnValue = -4; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_InsnReadTTLIO | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read the status from selected channel | ++----------------------------------------------------------------------------+ +| Input Parameters : b_Channel = CR_CHAN(insn->chanspec) | ++----------------------------------------------------------------------------+ +| Output Parameters : data[0] : Selected TTL channel state | ++----------------------------------------------------------------------------+ +| Return Value : 0 : No error | +| -3 : Channel selection error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_InsnReadTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned char b_Channel = (unsigned char) CR_CHAN(insn->chanspec); + int i_ReturnValue = insn->n; + unsigned int *pls_ReadData = data; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + /***********************/ + /* Test if read port 0 */ + /***********************/ + + if (b_Channel < 8) { + /*******************************************/ + /* Read port 0 (first digital output port) */ + /*******************************************/ + + pls_ReadData[0] = inl(devpriv->iobase + 80); + pls_ReadData[0] = (pls_ReadData[0] >> b_Channel) & 1; + } else { + /***********************/ + /* Test if read port 1 */ + /***********************/ + + if ((b_Channel > 7) && (b_Channel < 16)) { + /******************************************/ + /* Read port 1 (first digital input port) */ + /******************************************/ + + pls_ReadData[0] = inl(devpriv->iobase + 64); + pls_ReadData[0] = + (pls_ReadData[0] >> (b_Channel - + 8)) & 1; + } else { + /***********************/ + /* Test if read port 2 */ + /***********************/ + + if ((b_Channel > 15) && (b_Channel < 24)) { + /************************/ + /* Test if port 2 input */ + /************************/ + + if ((devpriv->ul_TTLPortConfiguration[0] + & 0xFF) == 0) { + pls_ReadData[0] = + inl(devpriv->iobase + + 96); + pls_ReadData[0] = + (pls_ReadData[0] >> + (b_Channel - 16)) & 1; + } else { + pls_ReadData[0] = + inl(devpriv->iobase + + 112); + pls_ReadData[0] = + (pls_ReadData[0] >> + (b_Channel - 16)) & 1; + } + } else { + /***************************/ + /* Channel selection error */ + /***************************/ + + i_ReturnValue = -3; + printk("Channel %d selection error\n", + b_Channel); + } + } + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| TTL OUTPUT FUNCTIONS | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_InsnWriteTTLIO | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Set the state from TTL output channel | ++----------------------------------------------------------------------------+ +| Input Parameters : b_Channel = CR_CHAN(insn->chanspec) | +| b_State = data [0] | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : 0 : No error | +| -3 : Channel selection error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_InsnWriteTTLIO(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_Channel = (unsigned char) CR_CHAN(insn->chanspec); + unsigned char b_State = 0; + unsigned int dw_Status = 0; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + b_State = (unsigned char) data[0]; + + /***********************/ + /* Test if read port 0 */ + /***********************/ + + if (b_Channel < 8) { + /*****************************************************************************/ + /* Read port 0 (first digital output port) and set/reset the selected channel */ + /*****************************************************************************/ + + dw_Status = inl(devpriv->iobase + 80); + dw_Status = + (dw_Status & (0xFF - + (1 << b_Channel))) | ((b_State & 1) << + b_Channel); + outl(dw_Status, devpriv->iobase + 80); + } else { + /***********************/ + /* Test if read port 2 */ + /***********************/ + + if ((b_Channel > 15) && (b_Channel < 24)) { + /*************************/ + /* Test if port 2 output */ + /*************************/ + + if ((devpriv->ul_TTLPortConfiguration[0] & 0xFF) + == 0xFF) { + /*****************************************************************************/ + /* Read port 2 (first digital output port) and set/reset the selected channel */ + /*****************************************************************************/ + + dw_Status = inl(devpriv->iobase + 112); + dw_Status = + (dw_Status & (0xFF - + (1 << (b_Channel - + 16)))) | + ((b_State & 1) << (b_Channel - + 16)); + outl(dw_Status, devpriv->iobase + 112); + } else { + /***************************/ + /* Channel selection error */ + /***************************/ + + i_ReturnValue = -3; + printk("Channel %d selection error\n", + b_Channel); + } + } else { + /***************************/ + /* Channel selection error */ + /***************************/ + + i_ReturnValue = -3; + printk("Channel %d selection error\n", + b_Channel); + } + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| DIGITAL INPUT SUBDEVICE | ++----------------------------------------------------------------------------+ +*/ + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3XXX_InsnReadDigitalInput | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Reads the value of the specified Digital input channel | ++----------------------------------------------------------------------------+ +| Input Parameters : b_Channel = CR_CHAN(insn->chanspec) (0 to 3) | ++----------------------------------------------------------------------------+ +| Output Parameters : data[0] : Channel value | ++----------------------------------------------------------------------------+ +| Return Value : 0 : No error | +| -3 : Channel selection error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ + +static int i_APCI3XXX_InsnReadDigitalInput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_Channel = (unsigned char) CR_CHAN(insn->chanspec); + unsigned int dw_Temp = 0; + + /***************************/ + /* Test the channel number */ + /***************************/ + + if (b_Channel <= devpriv->s_EeParameters.i_NbrDiChannel) { + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + dw_Temp = inl(devpriv->iobase + 32); + *data = (dw_Temp >> b_Channel) & 1; + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + } else { + /***************************/ + /* Channel selection error */ + /***************************/ + + printk("Channel selection error\n"); + i_ReturnValue = -3; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3XXX_InsnBitsDigitalInput | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Reads the value of the Digital input Port i.e.4channels| ++----------------------------------------------------------------------------+ +| Input Parameters : - | ++----------------------------------------------------------------------------+ +| Output Parameters : data[0] : Port value | ++----------------------------------------------------------------------------+ +| Return Value :>0: No error | +| .... | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_InsnBitsDigitalInput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned int dw_Temp = 0; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + dw_Temp = inl(devpriv->iobase + 32); + *data = dw_Temp & 0xf; + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| DIGITAL OUTPUT SUBDEVICE | ++----------------------------------------------------------------------------+ + +*/ + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3XXX_InsnBitsDigitalOutput | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Write the selected output mask and read the status from| +| all digital output channles | ++----------------------------------------------------------------------------+ +| Input Parameters : dw_ChannelMask = data [0]; | +| dw_BitMask = data [1]; | ++----------------------------------------------------------------------------+ +| Output Parameters : data[1] : All digital output channles states | ++----------------------------------------------------------------------------+ +| Return Value : >0 : No error | +| -4 : Channel mask error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ +static int i_APCI3XXX_InsnBitsDigitalOutput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_ChannelCpt = 0; + unsigned int dw_ChannelMask = 0; + unsigned int dw_BitMask = 0; + unsigned int dw_Status = 0; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 2) { + /*******************************/ + /* Get the channe and bit mask */ + /*******************************/ + + dw_ChannelMask = data[0]; + dw_BitMask = data[1]; + + /*************************/ + /* Test the channel mask */ + /*************************/ + + if ((dw_ChannelMask & 0XFFFFFFF0) == 0) { + /*********************************/ + /* Test if set/reset any channel */ + /*********************************/ + + if (dw_ChannelMask & 0xF) { + /********************************/ + /* Read the digital output port */ + /********************************/ + + dw_Status = inl(devpriv->iobase + 48); + + for (b_ChannelCpt = 0; b_ChannelCpt < 4; + b_ChannelCpt++) { + if ((dw_ChannelMask >> b_ChannelCpt) & + 1) { + dw_Status = + (dw_Status & (0xF - + (1 << b_ChannelCpt))) | (dw_BitMask & (1 << b_ChannelCpt)); + } + } + + outl(dw_Status, devpriv->iobase + 48); + } + + /********************************/ + /* Read the digital output port */ + /********************************/ + + data[1] = inl(devpriv->iobase + 48); + } else { + /************************/ + /* Config command error */ + /************************/ + + printk("Channel mask error\n"); + i_ReturnValue = -4; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3XXX_InsnWriteDigitalOutput | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Set the state from digital output channel | ++----------------------------------------------------------------------------+ +| Input Parameters : b_Channel = CR_CHAN(insn->chanspec) | +| b_State = data [0] | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : >0 : No error | +| -3 : Channel selection error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ + +static int i_APCI3XXX_InsnWriteDigitalOutput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_Channel = CR_CHAN(insn->chanspec); + unsigned char b_State = 0; + unsigned int dw_Status = 0; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + /***************************/ + /* Test the channel number */ + /***************************/ + + if (b_Channel < devpriv->s_EeParameters.i_NbrDoChannel) { + /*******************/ + /* Get the command */ + /*******************/ + + b_State = (unsigned char) data[0]; + + /********************************/ + /* Read the digital output port */ + /********************************/ + + dw_Status = inl(devpriv->iobase + 48); + + dw_Status = + (dw_Status & (0xF - + (1 << b_Channel))) | ((b_State & 1) << + b_Channel); + outl(dw_Status, devpriv->iobase + 48); + } else { + /***************************/ + /* Channel selection error */ + /***************************/ + + printk("Channel selection error\n"); + i_ReturnValue = -3; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function name :int i_APCI3XXX_InsnReadDigitalOutput | +| (struct comedi_device *dev, | +| struct comedi_subdevice *s, | +| struct comedi_insn *insn, | +| unsigned int *data) | ++----------------------------------------------------------------------------+ +| Task : Read the state from digital output channel | ++----------------------------------------------------------------------------+ +| Input Parameters : b_Channel = CR_CHAN(insn->chanspec) | ++----------------------------------------------------------------------------+ +| Output Parameters : b_State = data [0] | ++----------------------------------------------------------------------------+ +| Return Value : >0 : No error | +| -3 : Channel selection error | +| -101 : Data size error | ++----------------------------------------------------------------------------+ +*/ + +static int i_APCI3XXX_InsnReadDigitalOutput(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i_ReturnValue = insn->n; + unsigned char b_Channel = CR_CHAN(insn->chanspec); + unsigned int dw_Status = 0; + + /************************/ + /* Test the buffer size */ + /************************/ + + if (insn->n >= 1) { + /***************************/ + /* Test the channel number */ + /***************************/ + + if (b_Channel < devpriv->s_EeParameters.i_NbrDoChannel) { + /********************************/ + /* Read the digital output port */ + /********************************/ + + dw_Status = inl(devpriv->iobase + 48); + + dw_Status = (dw_Status >> b_Channel) & 1; + *data = dw_Status; + } else { + /***************************/ + /* Channel selection error */ + /***************************/ + + printk("Channel selection error\n"); + i_ReturnValue = -3; + } + } else { + /*******************/ + /* Data size error */ + /*******************/ + + printk("Buffer size error\n"); + i_ReturnValue = -101; + } + + return i_ReturnValue; +} + +/* ++----------------------------------------------------------------------------+ +| Function Name : int i_APCI3XXX_Reset(struct comedi_device *dev) | +----------------------------------------------------------------------------+ +| Task :resets all the registers | ++----------------------------------------------------------------------------+ +| Input Parameters : struct comedi_device *dev | ++----------------------------------------------------------------------------+ +| Output Parameters : - | ++----------------------------------------------------------------------------+ +| Return Value : - | ++----------------------------------------------------------------------------+ +*/ + +static int i_APCI3XXX_Reset(struct comedi_device *dev) +{ + unsigned char b_Cpt = 0; + + /*************************/ + /* Disable the interrupt */ + /*************************/ + + disable_irq(dev->irq); + + /****************************/ + /* Reset the interrupt flag */ + /****************************/ + + devpriv->b_EocEosInterrupt = 0; + + /***************************/ + /* Clear the start command */ + /***************************/ + + writel(0, devpriv->dw_AiBase + 8); + + /*****************************/ + /* Reset the interrupt flags */ + /*****************************/ + + writel(readl(devpriv->dw_AiBase + 16), devpriv->dw_AiBase + 16); + + /*****************/ + /* clear the EOS */ + /*****************/ + + readl(devpriv->dw_AiBase + 20); + + /******************/ + /* Clear the FIFO */ + /******************/ + + for (b_Cpt = 0; b_Cpt < 16; b_Cpt++) { + readl(devpriv->dw_AiBase + 28); + } + + /************************/ + /* Enable the interrupt */ + /************************/ + + enable_irq(dev->irq); + + return 0; +} diff --git a/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.h b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.h new file mode 100644 index 00000000..cce9e12e --- /dev/null +++ b/drivers/staging/comedi/drivers/addi-data/hwdrv_apci3xxx.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. + * + * ADDI-DATA GmbH + * Dieselstrasse 3 + * D-77833 Ottersweier + * Tel: +19(0)7223/9493-0 + * Fax: +49(0)7223/9493-92 + * http://www.addi-data.com + * info@addi-data.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#ifndef COMEDI_SUBD_TTLIO +#define COMEDI_SUBD_TTLIO 11 /* Digital Input Output But TTL */ +#endif + +#ifndef ADDIDATA_ENABLE +#define ADDIDATA_ENABLE 1 +#define ADDIDATA_DISABLE 0 +#endif + +#define APCI3XXX_SINGLE 0 +#define APCI3XXX_DIFF 1 +#define APCI3XXX_CONFIGURATION 0 + +#define APCI3XXX_TTL_INIT_DIRECTION_PORT2 0 + +#ifdef __KERNEL__ + +static const struct comedi_lrange range_apci3XXX_ai = { 8, {BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1)} +}; + +static const struct comedi_lrange range_apci3XXX_ttl = { 12, {BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1), + BIP_RANGE(1)} +}; + +static const struct comedi_lrange range_apci3XXX_ao = { 2, {BIP_RANGE(10), + UNI_RANGE(10)} +}; +#endif diff --git a/drivers/staging/comedi/drivers/addi_apci_035.c b/drivers/staging/comedi/drivers/addi_apci_035.c new file mode 100644 index 00000000..4c00df4b --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_035.c @@ -0,0 +1,11 @@ +#define CONFIG_APCI_035 1 + +#define ADDIDATA_WATCHDOG 2 /* Or shold it be something else */ + +#define ADDIDATA_DRIVER_NAME "addi_apci_035" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_1032.c b/drivers/staging/comedi/drivers/addi_apci_1032.c new file mode 100644 index 00000000..7831ce33 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_1032.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_1032 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_1032" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_1500.c b/drivers/staging/comedi/drivers/addi_apci_1500.c new file mode 100644 index 00000000..bfd84f66 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_1500.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_1500 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_1500" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_1516.c b/drivers/staging/comedi/drivers/addi_apci_1516.c new file mode 100644 index 00000000..a12e2f42 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_1516.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_1516 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_1516" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_1564.c b/drivers/staging/comedi/drivers/addi_apci_1564.c new file mode 100644 index 00000000..1b9d598f --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_1564.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_1564 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_1564" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_16xx.c b/drivers/staging/comedi/drivers/addi_apci_16xx.c new file mode 100644 index 00000000..d54218d5 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_16xx.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_16XX 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_16xx" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_1710.c b/drivers/staging/comedi/drivers/addi_apci_1710.c new file mode 100644 index 00000000..df6ba8cc --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_1710.c @@ -0,0 +1,5 @@ +#define CONFIG_APCI_1710 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_1710" + +#include "addi-data/addi_common.c" diff --git a/drivers/staging/comedi/drivers/addi_apci_2016.c b/drivers/staging/comedi/drivers/addi_apci_2016.c new file mode 100644 index 00000000..fa50c7bb --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_2016.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_2016 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_2016" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_2032.c b/drivers/staging/comedi/drivers/addi_apci_2032.c new file mode 100644 index 00000000..073a8a56 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_2032.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_2032 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_2032" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_2200.c b/drivers/staging/comedi/drivers/addi_apci_2200.c new file mode 100644 index 00000000..adfbb5d4 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_2200.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_2200 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_2200" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_3001.c b/drivers/staging/comedi/drivers/addi_apci_3001.c new file mode 100644 index 00000000..00ac7629 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3001.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_3001 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_3001" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_3120.c b/drivers/staging/comedi/drivers/addi_apci_3120.c new file mode 100644 index 00000000..c3551584 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3120.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_3120 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_3120" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_3200.c b/drivers/staging/comedi/drivers/addi_apci_3200.c new file mode 100644 index 00000000..15931399 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3200.c @@ -0,0 +1,5 @@ +#define CONFIG_APCI_3200 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_3200" + +#include "addi-data/addi_common.c" diff --git a/drivers/staging/comedi/drivers/addi_apci_3300.c b/drivers/staging/comedi/drivers/addi_apci_3300.c new file mode 100644 index 00000000..733c69ab --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3300.c @@ -0,0 +1,5 @@ +#define CONFIG_APCI_3300 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_3300" + +#include "addi-data/addi_common.c" diff --git a/drivers/staging/comedi/drivers/addi_apci_3501.c b/drivers/staging/comedi/drivers/addi_apci_3501.c new file mode 100644 index 00000000..dd2c1d3b --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3501.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_3501 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_3501" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_3xxx.c b/drivers/staging/comedi/drivers/addi_apci_3xxx.c new file mode 100644 index 00000000..03161c88 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_3xxx.c @@ -0,0 +1,9 @@ +#define CONFIG_APCI_3XXX 1 + +#define ADDIDATA_DRIVER_NAME "addi_apci_3xxx" + +#include "addi-data/addi_common.c" + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/addi_apci_all.c b/drivers/staging/comedi/drivers/addi_apci_all.c new file mode 100644 index 00000000..aeb1b268 --- /dev/null +++ b/drivers/staging/comedi/drivers/addi_apci_all.c @@ -0,0 +1,18 @@ +#define CONFIG_APCI_035 1 +#define CONFIG_APCI_1032 1 +#define CONFIG_APCI_1500 1 +#define CONFIG_APCI_1516 1 +#define CONFIG_APCI_1564 1 +#define CONFIG_APCI_16XX 1 +#define CONFIG_APCI_1710 1 +#define CONFIG_APCI_2016 1 +#define CONFIG_APCI_2032 1 +#define CONFIG_APCI_2200 1 +#define CONFIG_APCI_3001 1 +#define CONFIG_APCI_3120 1 +#define CONFIG_APCI_3200 1 +#define CONFIG_APCI_3300 1 +#define CONFIG_APCI_3501 1 +#define CONFIG_APCI_3XXX 1 + +#include "addi-data/addi_common.c" diff --git a/drivers/staging/comedi/drivers/adl_pci6208.c b/drivers/staging/comedi/drivers/adl_pci6208.c new file mode 100644 index 00000000..4fc9e852 --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci6208.c @@ -0,0 +1,447 @@ +/* + comedi/drivers/adl_pci6208.c + + Hardware driver for ADLink 6208 series cards: + card | voltage output | current output + -------------+-------------------+--------------- + PCI-6208V | 8 channels | - + PCI-6216V | 16 channels | - + PCI-6208A | 8 channels | 8 channels + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* +Driver: adl_pci6208 +Description: ADLink PCI-6208A +Devices: [ADLink] PCI-6208A (adl_pci6208) +Author: nsyeow +Updated: Fri, 30 Jan 2004 14:44:27 +0800 +Status: untested + +Configuration Options: + none + +References: + - ni_660x.c + - adl_pci9111.c copied the entire pci setup section + - adl_pci9118.c +*/ +/* + * These headers should be followed by a blank line, and any comments + * you wish to say about the driver. The comment area is the place + * to put any known bugs, limitations, unsupported features, supported + * command triggers, whether or not commands are supported on particular + * subdevices, etc. + * + * Somewhere in the comment should be information about configuration + * options that are used with comedi_config. + */ +#include "../comedidev.h" +#include "comedi_pci.h" + +#define PCI6208_DRIVER_NAME "adl_pci6208" + +/* Board descriptions */ +struct pci6208_board { + const char *name; + unsigned short dev_id; /* `lspci` will show you this */ + int ao_chans; + /* int ao_bits; */ +}; + +static const struct pci6208_board pci6208_boards[] = { + /*{ + .name = "pci6208v", + .dev_id = 0x6208, // not sure + .ao_chans = 8 + // , .ao_bits = 16 + }, + { + .name = "pci6216v", + .dev_id = 0x6208, // not sure + .ao_chans = 16 + // , .ao_bits = 16 + }, */ + { + .name = "pci6208a", + .dev_id = 0x6208, + .ao_chans = 8 + /* , .ao_bits = 16 */ + } +}; + +/* This is used by modprobe to translate PCI IDs to drivers. Should + * only be used for PCI and ISA-PnP devices */ +static DEFINE_PCI_DEVICE_TABLE(pci6208_pci_table) = { + /* { PCI_VENDOR_ID_ADLINK, 0x6208, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, */ + /* { PCI_VENDOR_ID_ADLINK, 0x6208, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, */ + { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x6208) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, pci6208_pci_table); + +/* Will be initialized in pci6208_find device(). */ +#define thisboard ((const struct pci6208_board *)dev->board_ptr) + +struct pci6208_private { + int data; + struct pci_dev *pci_dev; /* for a PCI device */ + unsigned int ao_readback[2]; /* Used for AO readback */ +}; + +#define devpriv ((struct pci6208_private *)dev->private) + +static int pci6208_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pci6208_detach(struct comedi_device *dev); + +static struct comedi_driver driver_pci6208 = { + .driver_name = PCI6208_DRIVER_NAME, + .module = THIS_MODULE, + .attach = pci6208_attach, + .detach = pci6208_detach, +}; + +static int __devinit driver_pci6208_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_pci6208.driver_name); +} + +static void __devexit driver_pci6208_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_pci6208_pci_driver = { + .id_table = pci6208_pci_table, + .probe = &driver_pci6208_pci_probe, + .remove = __devexit_p(&driver_pci6208_pci_remove) +}; + +static int __init driver_pci6208_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_pci6208); + if (retval < 0) + return retval; + + driver_pci6208_pci_driver.name = (char *)driver_pci6208.driver_name; + return pci_register_driver(&driver_pci6208_pci_driver); +} + +static void __exit driver_pci6208_cleanup_module(void) +{ + pci_unregister_driver(&driver_pci6208_pci_driver); + comedi_driver_unregister(&driver_pci6208); +} + +module_init(driver_pci6208_init_module); +module_exit(driver_pci6208_cleanup_module); + +static int pci6208_find_device(struct comedi_device *dev, int bus, int slot); +static int +pci6208_pci_setup(struct pci_dev *pci_dev, unsigned long *io_base_ptr, + int dev_minor); + +/*read/write functions*/ +static int pci6208_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int pci6208_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +/* static int pci6208_dio_insn_bits (struct comedi_device *dev, + * struct comedi_subdevice *s, */ +/* struct comedi_insn *insn,unsigned int *data); */ +/* static int pci6208_dio_insn_config(struct comedi_device *dev, + * struct comedi_subdevice *s, */ +/* struct comedi_insn *insn,unsigned int *data); */ + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pci6208_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int retval; + unsigned long io_base; + + printk(KERN_INFO "comedi%d: pci6208: ", dev->minor); + + retval = alloc_private(dev, sizeof(struct pci6208_private)); + if (retval < 0) + return retval; + + retval = pci6208_find_device(dev, it->options[0], it->options[1]); + if (retval < 0) + return retval; + + retval = pci6208_pci_setup(devpriv->pci_dev, &io_base, dev->minor); + if (retval < 0) + return retval; + + dev->iobase = io_base; + dev->board_name = thisboard->name; + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 2) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; /* anything else to add here?? */ + s->n_chan = thisboard->ao_chans; + s->maxdata = 0xffff; /* 16-bit DAC */ + s->range_table = &range_bipolar10; /* this needs to be checked. */ + s->insn_write = pci6208_ao_winsn; + s->insn_read = pci6208_ao_rinsn; + + /* s=dev->subdevices+1; */ + /* digital i/o subdevice */ + /* s->type=COMEDI_SUBD_DIO; */ + /* s->subdev_flags=SDF_READABLE|SDF_WRITABLE; */ + /* s->n_chan=16; */ + /* s->maxdata=1; */ + /* s->range_table=&range_digital; */ + /* s->insn_bits = pci6208_dio_insn_bits; */ + /* s->insn_config = pci6208_dio_insn_config; */ + + printk(KERN_INFO "attached\n"); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pci6208_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pci6208: remove\n", dev->minor); + + if (devpriv && devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + + return 0; +} + +static int pci6208_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i = 0, Data_Read; + unsigned short chan = CR_CHAN(insn->chanspec); + unsigned long invert = 1 << (16 - 1); + unsigned long out_value; + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; i++) { + out_value = data[i] ^ invert; + /* a typical programming sequence */ + do { + Data_Read = (inw(dev->iobase) & 1); + } while (Data_Read); + outw(out_value, dev->iobase + (0x02 * chan)); + devpriv->ao_readback[chan] = out_value; + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int pci6208_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +/* static int pci6208_dio_insn_bits(struct comedi_device *dev, + * struct comedi_subdevice *s, */ +/* struct comedi_insn *insn,unsigned int *data) */ +/* { */ +/* if(insn->n!=2)return -EINVAL; */ + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ +/* if(data[0]){ */ +/* s->state &= ~data[0]; */ +/* s->state |= data[0]&data[1]; */ + /* Write out the new digital output lines */ + /* outw(s->state,dev->iobase + SKEL_DIO); */ +/* } */ + + /* on return, data[1] contains the value of the digital + * input and output lines. */ + /* data[1]=inw(dev->iobase + SKEL_DIO); */ + /* or we could just return the software copy of the output values if + * it was a purely digital output subdevice */ + /* data[1]=s->state; */ + +/* return 2; */ +/* } */ + +/* static int pci6208_dio_insn_config(struct comedi_device *dev, + * struct comedi_subdevice *s, */ +/* struct comedi_insn *insn,unsigned int *data) */ +/* { */ +/* int chan=CR_CHAN(insn->chanspec); */ + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + +/* if(data[0]==COMEDI_OUTPUT){ */ +/* s->io_bits |= 1<io_bits &= ~(1<io_bits,dev->iobase + SKEL_DIO_CONFIG); */ + +/* return 1; */ +/* } */ + +static int pci6208_find_device(struct comedi_device *dev, int bus, int slot) +{ + struct pci_dev *pci_dev = NULL; + int i; + + for_each_pci_dev(pci_dev) { + if (pci_dev->vendor == PCI_VENDOR_ID_ADLINK) { + for (i = 0; i < ARRAY_SIZE(pci6208_boards); i++) { + if (pci6208_boards[i].dev_id == + pci_dev->device) { + /* + * was a particular bus/slot requested? + */ + if ((bus != 0) || (slot != 0)) { + /* + * are we on the + * wrong bus/slot? + */ + if (pci_dev->bus->number + != bus || + PCI_SLOT(pci_dev->devfn) + != slot) { + continue; + } + } + dev->board_ptr = pci6208_boards + i; + goto found; + } + } + } + } + + printk(KERN_ERR "comedi%d: no supported board found! " + "(req. bus/slot : %d/%d)\n", + dev->minor, bus, slot); + return -EIO; + +found: + printk("comedi%d: found %s (b:s:f=%d:%d:%d) , irq=%d\n", + dev->minor, + pci6208_boards[i].name, + pci_dev->bus->number, + PCI_SLOT(pci_dev->devfn), + PCI_FUNC(pci_dev->devfn), pci_dev->irq); + + /* TODO: Warn about non-tested boards. */ + /* switch(board->device_id) */ + /* { */ + /* }; */ + + devpriv->pci_dev = pci_dev; + + return 0; +} + +static int +pci6208_pci_setup(struct pci_dev *pci_dev, unsigned long *io_base_ptr, + int dev_minor) +{ + unsigned long io_base, io_range, lcr_io_base, lcr_io_range; + + /* Enable PCI device and request regions */ + if (comedi_pci_enable(pci_dev, PCI6208_DRIVER_NAME) < 0) { + printk(KERN_ERR "comedi%d: Failed to enable PCI device " + "and request regions\n", + dev_minor); + return -EIO; + } + /* Read local configuration register + * base address [PCI_BASE_ADDRESS #1]. + */ + lcr_io_base = pci_resource_start(pci_dev, 1); + lcr_io_range = pci_resource_len(pci_dev, 1); + + printk(KERN_INFO "comedi%d: local config registers at address" + " 0x%4lx [0x%4lx]\n", + dev_minor, lcr_io_base, lcr_io_range); + + /* Read PCI6208 register base address [PCI_BASE_ADDRESS #2]. */ + io_base = pci_resource_start(pci_dev, 2); + io_range = pci_resource_end(pci_dev, 2) - io_base + 1; + + printk("comedi%d: 6208 registers at address 0x%4lx [0x%4lx]\n", + dev_minor, io_base, io_range); + + *io_base_ptr = io_base; + /* devpriv->io_range = io_range; */ + /* devpriv->is_valid=0; */ + /* devpriv->lcr_io_base=lcr_io_base; */ + /* devpriv->lcr_io_range=lcr_io_range; */ + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adl_pci7230.c b/drivers/staging/comedi/drivers/adl_pci7230.c new file mode 100644 index 00000000..20d57055 --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci7230.c @@ -0,0 +1,237 @@ +/* + comedi/drivers/adl_pci7230.c + + Hardware comedi driver fot PCI7230 Adlink card + Copyright (C) 2010 David Fernandez + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: adl_pci7230 +Description: Driver for the Adlink PCI-7230 32 ch. isolated digital io board +Devices: [ADLink] PCI-7230 (adl_pci7230) +Author: David Fernandez +Status: experimental +Updated: Mon, 14 Apr 2008 15:08:14 +0100 + +Configuration Options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. +*/ + +#include "../comedidev.h" +#include +#include "comedi_pci.h" + +#define PCI7230_DI 0x00 +#define PCI7230_DO 0x00 + +#define PCI_DEVICE_ID_PCI7230 0x7230 + +static DEFINE_PCI_DEVICE_TABLE(adl_pci7230_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI_DEVICE_ID_PCI7230) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, adl_pci7230_pci_table); + +struct adl_pci7230_private { + int data; + struct pci_dev *pci_dev; +}; + +#define devpriv ((struct adl_pci7230_private *)dev->private) + +static int adl_pci7230_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int adl_pci7230_detach(struct comedi_device *dev); +static struct comedi_driver driver_adl_pci7230 = { + .driver_name = "adl_pci7230", + .module = THIS_MODULE, + .attach = adl_pci7230_attach, + .detach = adl_pci7230_detach, +}; + +/* Digital IO */ + +static int adl_pci7230_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci7230_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci7230_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pci_dev *pcidev = NULL; + struct comedi_subdevice *s; + int bus, slot; + + printk(KERN_INFO "comedi%d: adl_pci7230\n", dev->minor); + + dev->board_name = "pci7230"; + bus = it->options[0]; + slot = it->options[1]; + + if (alloc_private(dev, sizeof(struct adl_pci7230_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 2) < 0) + return -ENOMEM; + + for_each_pci_dev(pcidev) { + if (pcidev->vendor == PCI_VENDOR_ID_ADLINK && + pcidev->device == PCI_DEVICE_ID_PCI7230) { + if (bus || slot) { + /* requested particular bus/slot */ + if (pcidev->bus->number != bus || + PCI_SLOT(pcidev->devfn) != slot) { + continue; + } + } + devpriv->pci_dev = pcidev; + break; + } + } + if (pcidev == NULL) { + printk(KERN_ERR "comedi%d: no supported board found! (req. bus/slot : %d/%d)\n", + dev->minor, bus, slot); + return -EIO; + } + if (comedi_pci_enable(pcidev, "adl_pci7230") < 0) { + printk(KERN_ERR "comedi%d: Failed to enable PCI device and request regions\n", + dev->minor); + return -EIO; + } + dev->iobase = pci_resource_start(pcidev, 2); + printk(KERN_DEBUG "comedi: base addr %4lx\n", dev->iobase); + + s = dev->subdevices + 0; + /* Isolated do */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adl_pci7230_do_insn_bits; + + s = dev->subdevices + 1; + /* Isolated di */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adl_pci7230_di_insn_bits; + + printk(KERN_DEBUG "comedi: attached\n"); + + return 1; +} + +static int adl_pci7230_detach(struct comedi_device *dev) +{ + printk(KERN_DEBUG "comedi%d: pci7230: remove\n", dev->minor); + + if (devpriv && devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + + return 0; +} + +static int adl_pci7230_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + + outl((s->state << 16) & 0xffffffff, dev->iobase + PCI7230_DO); + } + + return 2; +} + +static int adl_pci7230_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inl(dev->iobase + PCI7230_DI) & 0xffffffff; + + return 2; +} + +static int __devinit driver_adl_pci7230_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_adl_pci7230.driver_name); +} + +static void __devexit driver_adl_pci7230_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_adl_pci7230_pci_driver = { + .id_table = adl_pci7230_pci_table, + .probe = &driver_adl_pci7230_pci_probe, + .remove = __devexit_p(&driver_adl_pci7230_pci_remove) +}; + +static int __init driver_adl_pci7230_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_adl_pci7230); + if (retval < 0) + return retval; + + driver_adl_pci7230_pci_driver.name = + (char *)driver_adl_pci7230.driver_name; + return pci_register_driver(&driver_adl_pci7230_pci_driver); +} + +static void __exit driver_adl_pci7230_cleanup_module(void) +{ + pci_unregister_driver(&driver_adl_pci7230_pci_driver); + comedi_driver_unregister(&driver_adl_pci7230); +} + +module_init(driver_adl_pci7230_init_module); +module_exit(driver_adl_pci7230_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adl_pci7296.c b/drivers/staging/comedi/drivers/adl_pci7296.c new file mode 100644 index 00000000..9a232053 --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci7296.c @@ -0,0 +1,217 @@ +/* + comedi/drivers/adl_pci7296.c + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: adl_pci7296 +Description: Driver for the Adlink PCI-7296 96 ch. digital io board +Devices: [ADLink] PCI-7296 (adl_pci7296) +Author: Jon Grierson +Updated: Mon, 14 Apr 2008 15:05:56 +0100 +Status: testing + +Configuration Options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. +*/ + +#include "../comedidev.h" +#include + +#include "comedi_pci.h" +#include "8255.h" +/* #include "8253.h" */ + +#define PORT1A 0 +#define PORT2A 4 +#define PORT3A 8 +#define PORT4A 12 + +#define PCI_DEVICE_ID_PCI7296 0x7296 + +static DEFINE_PCI_DEVICE_TABLE(adl_pci7296_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI_DEVICE_ID_PCI7296) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, adl_pci7296_pci_table); + +struct adl_pci7296_private { + int data; + struct pci_dev *pci_dev; +}; + +#define devpriv ((struct adl_pci7296_private *)dev->private) + +static int adl_pci7296_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int adl_pci7296_detach(struct comedi_device *dev); +static struct comedi_driver driver_adl_pci7296 = { + .driver_name = "adl_pci7296", + .module = THIS_MODULE, + .attach = adl_pci7296_attach, + .detach = adl_pci7296_detach, +}; + +static int adl_pci7296_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pci_dev *pcidev = NULL; + struct comedi_subdevice *s; + int bus, slot; + int ret; + + printk(KERN_INFO "comedi%d: attach adl_pci7432\n", dev->minor); + + dev->board_name = "pci7432"; + bus = it->options[0]; + slot = it->options[1]; + + if (alloc_private(dev, sizeof(struct adl_pci7296_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + for_each_pci_dev(pcidev) { + if (pcidev->vendor == PCI_VENDOR_ID_ADLINK && + pcidev->device == PCI_DEVICE_ID_PCI7296) { + if (bus || slot) { + /* requested particular bus/slot */ + if (pcidev->bus->number != bus + || PCI_SLOT(pcidev->devfn) != slot) { + continue; + } + } + devpriv->pci_dev = pcidev; + if (comedi_pci_enable(pcidev, "adl_pci7296") < 0) { + printk(KERN_ERR "comedi%d: Failed to enable PCI device and request regions\n", + dev->minor); + return -EIO; + } + + dev->iobase = pci_resource_start(pcidev, 2); + printk(KERN_INFO "comedi: base addr %4lx\n", + dev->iobase); + + /* four 8255 digital io subdevices */ + s = dev->subdevices + 0; + subdev_8255_init(dev, s, NULL, + (unsigned long)(dev->iobase)); + + s = dev->subdevices + 1; + ret = subdev_8255_init(dev, s, NULL, + (unsigned long)(dev->iobase + + PORT2A)); + if (ret < 0) + return ret; + + s = dev->subdevices + 2; + ret = subdev_8255_init(dev, s, NULL, + (unsigned long)(dev->iobase + + PORT3A)); + if (ret < 0) + return ret; + + s = dev->subdevices + 3; + ret = subdev_8255_init(dev, s, NULL, + (unsigned long)(dev->iobase + + PORT4A)); + if (ret < 0) + return ret; + + printk(KERN_DEBUG "comedi%d: adl_pci7432 attached\n", + dev->minor); + + return 1; + } + } + + printk(KERN_ERR "comedi%d: no supported board found! (req. bus/slot : %d/%d)\n", + dev->minor, bus, slot); + return -EIO; +} + +static int adl_pci7296_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pci7432: remove\n", dev->minor); + + if (devpriv && devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + /* detach four 8255 digital io subdevices */ + if (dev->subdevices) { + subdev_8255_cleanup(dev, dev->subdevices + 0); + subdev_8255_cleanup(dev, dev->subdevices + 1); + subdev_8255_cleanup(dev, dev->subdevices + 2); + subdev_8255_cleanup(dev, dev->subdevices + 3); + + } + + return 0; +} + +static int __devinit driver_adl_pci7296_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_adl_pci7296.driver_name); +} + +static void __devexit driver_adl_pci7296_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_adl_pci7296_pci_driver = { + .id_table = adl_pci7296_pci_table, + .probe = &driver_adl_pci7296_pci_probe, + .remove = __devexit_p(&driver_adl_pci7296_pci_remove) +}; + +static int __init driver_adl_pci7296_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_adl_pci7296); + if (retval < 0) + return retval; + + driver_adl_pci7296_pci_driver.name = + (char *)driver_adl_pci7296.driver_name; + return pci_register_driver(&driver_adl_pci7296_pci_driver); +} + +static void __exit driver_adl_pci7296_cleanup_module(void) +{ + pci_unregister_driver(&driver_adl_pci7296_pci_driver); + comedi_driver_unregister(&driver_adl_pci7296); +} + +module_init(driver_adl_pci7296_init_module); +module_exit(driver_adl_pci7296_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adl_pci7432.c b/drivers/staging/comedi/drivers/adl_pci7432.c new file mode 100644 index 00000000..86ee2197 --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci7432.c @@ -0,0 +1,250 @@ +/* + comedi/drivers/adl_pci7432.c + + Hardware comedi driver fot PCI7432 Adlink card + Copyright (C) 2004 Michel Lachine + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: adl_pci7432 +Description: Driver for the Adlink PCI-7432 64 ch. isolated digital io board +Devices: [ADLink] PCI-7432 (adl_pci7432) +Author: Michel Lachaine +Status: experimental +Updated: Mon, 14 Apr 2008 15:08:14 +0100 + +Configuration Options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. +*/ + +#include "../comedidev.h" +#include +#include "comedi_pci.h" + +#define PCI7432_DI 0x00 +#define PCI7432_DO 0x00 + +#define PCI_DEVICE_ID_PCI7432 0x7432 + +static DEFINE_PCI_DEVICE_TABLE(adl_pci7432_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI_DEVICE_ID_PCI7432) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, adl_pci7432_pci_table); + +struct adl_pci7432_private { + int data; + struct pci_dev *pci_dev; +}; + +#define devpriv ((struct adl_pci7432_private *)dev->private) + +static int adl_pci7432_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int adl_pci7432_detach(struct comedi_device *dev); +static struct comedi_driver driver_adl_pci7432 = { + .driver_name = "adl_pci7432", + .module = THIS_MODULE, + .attach = adl_pci7432_attach, + .detach = adl_pci7432_detach, +}; + +/* Digital IO */ + +static int adl_pci7432_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci7432_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +/* */ + +static int adl_pci7432_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pci_dev *pcidev = NULL; + struct comedi_subdevice *s; + int bus, slot; + + printk(KERN_INFO "comedi%d: attach adl_pci7432\n", dev->minor); + + dev->board_name = "pci7432"; + bus = it->options[0]; + slot = it->options[1]; + + if (alloc_private(dev, sizeof(struct adl_pci7432_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 2) < 0) + return -ENOMEM; + + for_each_pci_dev(pcidev) { + if (pcidev->vendor == PCI_VENDOR_ID_ADLINK && + pcidev->device == PCI_DEVICE_ID_PCI7432) { + if (bus || slot) { + /* requested particular bus/slot */ + if (pcidev->bus->number != bus + || PCI_SLOT(pcidev->devfn) != slot) { + continue; + } + } + devpriv->pci_dev = pcidev; + if (comedi_pci_enable(pcidev, "adl_pci7432") < 0) { + printk(KERN_ERR "comedi%d: Failed to enable PCI device and request regions\n", + dev->minor); + return -EIO; + } + dev->iobase = pci_resource_start(pcidev, 2); + printk(KERN_INFO "comedi: base addr %4lx\n", + dev->iobase); + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = + SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 32; + s->maxdata = 1; + s->len_chanlist = 32; + s->io_bits = 0x00000000; + s->range_table = &range_digital; + s->insn_bits = adl_pci7432_di_insn_bits; + + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = + SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 32; + s->maxdata = 1; + s->len_chanlist = 32; + s->io_bits = 0xffffffff; + s->range_table = &range_digital; + s->insn_bits = adl_pci7432_do_insn_bits; + + printk(KERN_DEBUG "comedi%d: adl_pci7432 attached\n", + dev->minor); + return 1; + } + } + + printk(KERN_ERR "comedi%d: no supported board found! (req. bus/slot : %d/%d)\n", + dev->minor, bus, slot); + return -EIO; +} + +static int adl_pci7432_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pci7432: remove\n", dev->minor); + + if (devpriv && devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + + return 0; +} + +static int adl_pci7432_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + printk(KERN_DEBUG "comedi: pci7432_do_insn_bits called\n"); + printk(KERN_DEBUG "comedi: data0: %8x data1: %8x\n", data[0], data[1]); + + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + + printk(KERN_DEBUG "comedi: out: %8x on iobase %4lx\n", s->state, + dev->iobase + PCI7432_DO); + outl(s->state & 0xffffffff, dev->iobase + PCI7432_DO); + } + return 2; +} + +static int adl_pci7432_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + printk(KERN_DEBUG "comedi: pci7432_di_insn_bits called\n"); + printk(KERN_DEBUG "comedi: data0: %8x data1: %8x\n", data[0], data[1]); + + if (insn->n != 2) + return -EINVAL; + + data[1] = inl(dev->iobase + PCI7432_DI) & 0xffffffff; + printk(KERN_DEBUG "comedi: data1 %8x\n", data[1]); + + return 2; +} + +static int __devinit driver_adl_pci7432_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_adl_pci7432.driver_name); +} + +static void __devexit driver_adl_pci7432_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_adl_pci7432_pci_driver = { + .id_table = adl_pci7432_pci_table, + .probe = &driver_adl_pci7432_pci_probe, + .remove = __devexit_p(&driver_adl_pci7432_pci_remove) +}; + +static int __init driver_adl_pci7432_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_adl_pci7432); + if (retval < 0) + return retval; + + driver_adl_pci7432_pci_driver.name = + (char *)driver_adl_pci7432.driver_name; + return pci_register_driver(&driver_adl_pci7432_pci_driver); +} + +static void __exit driver_adl_pci7432_cleanup_module(void) +{ + pci_unregister_driver(&driver_adl_pci7432_pci_driver); + comedi_driver_unregister(&driver_adl_pci7432); +} + +module_init(driver_adl_pci7432_init_module); +module_exit(driver_adl_pci7432_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adl_pci8164.c b/drivers/staging/comedi/drivers/adl_pci8164.c new file mode 100644 index 00000000..3b83d65b --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci8164.c @@ -0,0 +1,429 @@ +/* + comedi/drivers/adl_pci8164.c + + Hardware comedi driver fot PCI-8164 Adlink card + Copyright (C) 2004 Michel Lachine + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: adl_pci8164 +Description: Driver for the Adlink PCI-8164 4 Axes Motion Control board +Devices: [ADLink] PCI-8164 (adl_pci8164) +Author: Michel Lachaine +Status: experimental +Updated: Mon, 14 Apr 2008 15:10:32 +0100 + +Configuration Options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. +*/ + +#include "../comedidev.h" +#include +#include +#include "comedi_fc.h" +#include "comedi_pci.h" +#include "8253.h" + +#define PCI8164_AXIS_X 0x00 +#define PCI8164_AXIS_Y 0x08 +#define PCI8164_AXIS_Z 0x10 +#define PCI8164_AXIS_U 0x18 + +#define PCI8164_MSTS 0x00 +#define PCI8164_SSTS 0x02 +#define PCI8164_BUF0 0x04 +#define PCI8164_BUF1 0x06 + +#define PCI8164_CMD 0x00 +#define PCI8164_OTP 0x02 + +#define PCI_DEVICE_ID_PCI8164 0x8164 + +static DEFINE_PCI_DEVICE_TABLE(adl_pci8164_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI_DEVICE_ID_PCI8164) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, adl_pci8164_pci_table); + +struct adl_pci8164_private { + int data; + struct pci_dev *pci_dev; +}; + +#define devpriv ((struct adl_pci8164_private *)dev->private) + +static int adl_pci8164_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int adl_pci8164_detach(struct comedi_device *dev); +static struct comedi_driver driver_adl_pci8164 = { + .driver_name = "adl_pci8164", + .module = THIS_MODULE, + .attach = adl_pci8164_attach, + .detach = adl_pci8164_detach, +}; + +static int adl_pci8164_insn_read_msts(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci8164_insn_read_ssts(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci8164_insn_read_buf0(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci8164_insn_read_buf1(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci8164_insn_write_cmd(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci8164_insn_write_otp(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci8164_insn_write_buf0(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci8164_insn_write_buf1(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int adl_pci8164_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pci_dev *pcidev = NULL; + struct comedi_subdevice *s; + int bus, slot; + + printk(KERN_INFO "comedi: attempt to attach...\n"); + printk(KERN_INFO "comedi%d: adl_pci8164\n", dev->minor); + + dev->board_name = "pci8164"; + bus = it->options[0]; + slot = it->options[1]; + + if (alloc_private(dev, sizeof(struct adl_pci8164_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + for_each_pci_dev(pcidev) { + if (pcidev->vendor == PCI_VENDOR_ID_ADLINK && + pcidev->device == PCI_DEVICE_ID_PCI8164) { + if (bus || slot) { + /* requested particular bus/slot */ + if (pcidev->bus->number != bus + || PCI_SLOT(pcidev->devfn) != slot) + continue; + } + devpriv->pci_dev = pcidev; + if (comedi_pci_enable(pcidev, "adl_pci8164") < 0) { + printk(KERN_ERR "comedi%d: Failed to enable " + "PCI device and request regions\n", dev->minor); + return -EIO; + } + dev->iobase = pci_resource_start(pcidev, 2); + printk(KERN_DEBUG "comedi: base addr %4lx\n", + dev->iobase); + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_PROC; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->len_chanlist = 4; + /* s->range_table = &range_axis; */ + s->insn_read = adl_pci8164_insn_read_msts; + s->insn_write = adl_pci8164_insn_write_cmd; + + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_PROC; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->len_chanlist = 4; + /* s->range_table = &range_axis; */ + s->insn_read = adl_pci8164_insn_read_ssts; + s->insn_write = adl_pci8164_insn_write_otp; + + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_PROC; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->len_chanlist = 4; + /* s->range_table = &range_axis; */ + s->insn_read = adl_pci8164_insn_read_buf0; + s->insn_write = adl_pci8164_insn_write_buf0; + + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_PROC; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->len_chanlist = 4; + /* s->range_table = &range_axis; */ + s->insn_read = adl_pci8164_insn_read_buf1; + s->insn_write = adl_pci8164_insn_write_buf1; + + printk(KERN_INFO "comedi: attached\n"); + + return 1; + } + } + + printk(KERN_ERR "comedi%d: no supported board found!" + "(req. bus/slot : %d/%d)\n", dev->minor, bus, slot); + return -EIO; +} + +static int adl_pci8164_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pci8164: remove\n", dev->minor); + + if (devpriv && devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + + return 0; +} + +/* + all the read commands are the same except for the addition a constant + * const to the data for inw() + */ +static void adl_pci8164_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data, + char *action, unsigned short offset) +{ + int axis, axis_reg; + char *axisname; + + axis = CR_CHAN(insn->chanspec); + + switch (axis) { + case 0: + axis_reg = PCI8164_AXIS_X; + axisname = "X"; + break; + case 1: + axis_reg = PCI8164_AXIS_Y; + axisname = "Y"; + break; + case 2: + axis_reg = PCI8164_AXIS_Z; + axisname = "Z"; + break; + case 3: + axis_reg = PCI8164_AXIS_U; + axisname = "U"; + break; + default: + axis_reg = PCI8164_AXIS_X; + axisname = "X"; + } + + data[0] = inw(dev->iobase + axis_reg + offset); + printk(KERN_DEBUG "comedi: pci8164 %s read -> " + "%04X:%04X on axis %s\n", + action, data[0], data[1], axisname); +} + +static int adl_pci8164_insn_read_msts(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + adl_pci8164_insn_read(dev, s, insn, data, "MSTS", PCI8164_MSTS); + return 2; +} + +static int adl_pci8164_insn_read_ssts(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + adl_pci8164_insn_read(dev, s, insn, data, "SSTS", PCI8164_SSTS); + return 2; +} + +static int adl_pci8164_insn_read_buf0(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + adl_pci8164_insn_read(dev, s, insn, data, "BUF0", PCI8164_BUF0); + return 2; +} + +static int adl_pci8164_insn_read_buf1(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + adl_pci8164_insn_read(dev, s, insn, data, "BUF1", PCI8164_BUF1); + return 2; +} + +/* + all the write commands are the same except for the addition a constant + * const to the data for outw() + */ +static void adl_pci8164_insn_out(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data, + char *action, unsigned short offset) +{ + unsigned int axis, axis_reg; + + char *axisname; + + axis = CR_CHAN(insn->chanspec); + + switch (axis) { + case 0: + axis_reg = PCI8164_AXIS_X; + axisname = "X"; + break; + case 1: + axis_reg = PCI8164_AXIS_Y; + axisname = "Y"; + break; + case 2: + axis_reg = PCI8164_AXIS_Z; + axisname = "Z"; + break; + case 3: + axis_reg = PCI8164_AXIS_U; + axisname = "U"; + break; + default: + axis_reg = PCI8164_AXIS_X; + axisname = "X"; + } + + outw(data[0], dev->iobase + axis_reg + offset); + + printk(KERN_DEBUG "comedi: pci8164 %s write -> " + "%04X:%04X on axis %s\n", + action, data[0], data[1], axisname); + +} + +static int adl_pci8164_insn_write_cmd(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + adl_pci8164_insn_out(dev, s, insn, data, "CMD", PCI8164_CMD); + return 2; +} + +static int adl_pci8164_insn_write_otp(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + adl_pci8164_insn_out(dev, s, insn, data, "OTP", PCI8164_OTP); + return 2; +} + +static int adl_pci8164_insn_write_buf0(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + adl_pci8164_insn_out(dev, s, insn, data, "BUF0", PCI8164_BUF0); + return 2; +} + +static int adl_pci8164_insn_write_buf1(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + adl_pci8164_insn_out(dev, s, insn, data, "BUF1", PCI8164_BUF1); + return 2; +} + +static int __devinit driver_adl_pci8164_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_adl_pci8164.driver_name); +} + +static void __devexit driver_adl_pci8164_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_adl_pci8164_pci_driver = { + .id_table = adl_pci8164_pci_table, + .probe = &driver_adl_pci8164_pci_probe, + .remove = __devexit_p(&driver_adl_pci8164_pci_remove) +}; + +static int __init driver_adl_pci8164_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_adl_pci8164); + if (retval < 0) + return retval; + + driver_adl_pci8164_pci_driver.name = + (char *)driver_adl_pci8164.driver_name; + return pci_register_driver(&driver_adl_pci8164_pci_driver); +} + +static void __exit driver_adl_pci8164_cleanup_module(void) +{ + pci_unregister_driver(&driver_adl_pci8164_pci_driver); + comedi_driver_unregister(&driver_adl_pci8164); +} + +module_init(driver_adl_pci8164_init_module); +module_exit(driver_adl_pci8164_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adl_pci9111.c b/drivers/staging/comedi/drivers/adl_pci9111.c new file mode 100644 index 00000000..2a9bd88a --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci9111.c @@ -0,0 +1,1475 @@ +/* + +comedi/drivers/adl_pci9111.c + +Hardware driver for PCI9111 ADLink cards: + +PCI-9111HR + +Copyright (C) 2002-2005 Emmanuel Pacaud + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* +Driver: adl_pci9111 +Description: Adlink PCI-9111HR +Author: Emmanuel Pacaud +Devices: [ADLink] PCI-9111HR (adl_pci9111) +Status: experimental + +Supports: + + - ai_insn read + - ao_insn read/write + - di_insn read + - do_insn read/write + - ai_do_cmd mode with the following sources: + + - start_src TRIG_NOW + - scan_begin_src TRIG_FOLLOW TRIG_TIMER TRIG_EXT + - convert_src TRIG_TIMER TRIG_EXT + - scan_end_src TRIG_COUNT + - stop_src TRIG_COUNT TRIG_NONE + +The scanned channels must be consecutive and start from 0. They must +all have the same range and aref. + +Configuration options: + + [0] - PCI bus number (optional) + [1] - PCI slot number (optional) + +If bus/slot is not specified, the first available PCI +device will be used. + +*/ + +/* +CHANGELOG: + +2005/02/17 Extend AI streaming capabilities. Now, scan_begin_arg can be +a multiple of chanlist_len*convert_arg. +2002/02/19 Fixed the two's complement conversion in pci9111_(hr_)ai_get_data. +2002/02/18 Added external trigger support for analog input. + +TODO: + + - Really test implemented functionality. + - Add support for the PCI-9111DG with a probe routine to identify + the card type (perhaps with the help of the channel number readback + of the A/D Data register). + - Add external multiplexer support. + +*/ + +#include "../comedidev.h" + +#include +#include + +#include "8253.h" +#include "comedi_pci.h" +#include "comedi_fc.h" + +#define PCI9111_DRIVER_NAME "adl_pci9111" +#define PCI9111_HR_DEVICE_ID 0x9111 + +/* TODO: Add other pci9111 board id */ + +#define PCI9111_IO_RANGE 0x0100 + +#define PCI9111_FIFO_HALF_SIZE 512 + +#define PCI9111_AI_CHANNEL_NBR 16 + +#define PCI9111_AI_RESOLUTION 12 +#define PCI9111_AI_RESOLUTION_MASK 0x0FFF +#define PCI9111_AI_RESOLUTION_2_CMP_BIT 0x0800 + +#define PCI9111_HR_AI_RESOLUTION 16 +#define PCI9111_HR_AI_RESOLUTION_MASK 0xFFFF +#define PCI9111_HR_AI_RESOLUTION_2_CMP_BIT 0x8000 + +#define PCI9111_AI_ACQUISITION_PERIOD_MIN_NS 10000 +#define PCI9111_AO_CHANNEL_NBR 1 +#define PCI9111_AO_RESOLUTION 12 +#define PCI9111_AO_RESOLUTION_MASK 0x0FFF +#define PCI9111_DI_CHANNEL_NBR 16 +#define PCI9111_DO_CHANNEL_NBR 16 +#define PCI9111_DO_MASK 0xFFFF + +#define PCI9111_RANGE_SETTING_DELAY 10 +#define PCI9111_AI_INSTANT_READ_UDELAY_US 2 +#define PCI9111_AI_INSTANT_READ_TIMEOUT 100 + +#define PCI9111_8254_CLOCK_PERIOD_NS 500 + +#define PCI9111_8254_COUNTER_0 0x00 +#define PCI9111_8254_COUNTER_1 0x40 +#define PCI9111_8254_COUNTER_2 0x80 +#define PCI9111_8254_COUNTER_LATCH 0x00 +#define PCI9111_8254_READ_LOAD_LSB_ONLY 0x10 +#define PCI9111_8254_READ_LOAD_MSB_ONLY 0x20 +#define PCI9111_8254_READ_LOAD_LSB_MSB 0x30 +#define PCI9111_8254_MODE_0 0x00 +#define PCI9111_8254_MODE_1 0x02 +#define PCI9111_8254_MODE_2 0x04 +#define PCI9111_8254_MODE_3 0x06 +#define PCI9111_8254_MODE_4 0x08 +#define PCI9111_8254_MODE_5 0x0A +#define PCI9111_8254_BINARY_COUNTER 0x00 +#define PCI9111_8254_BCD_COUNTER 0x01 + +/* IO address map */ + +#define PCI9111_REGISTER_AD_FIFO_VALUE 0x00 /* AD Data stored + in FIFO */ +#define PCI9111_REGISTER_DA_OUTPUT 0x00 +#define PCI9111_REGISTER_DIGITAL_IO 0x02 +#define PCI9111_REGISTER_EXTENDED_IO_PORTS 0x04 +#define PCI9111_REGISTER_AD_CHANNEL_CONTROL 0x06 /* Channel + selection */ +#define PCI9111_REGISTER_AD_CHANNEL_READBACK 0x06 +#define PCI9111_REGISTER_INPUT_SIGNAL_RANGE 0x08 +#define PCI9111_REGISTER_RANGE_STATUS_READBACK 0x08 +#define PCI9111_REGISTER_TRIGGER_MODE_CONTROL 0x0A +#define PCI9111_REGISTER_AD_MODE_INTERRUPT_READBACK 0x0A +#define PCI9111_REGISTER_SOFTWARE_TRIGGER 0x0E +#define PCI9111_REGISTER_INTERRUPT_CONTROL 0x0C +#define PCI9111_REGISTER_8254_COUNTER_0 0x40 +#define PCI9111_REGISTER_8254_COUNTER_1 0x42 +#define PCI9111_REGISTER_8254_COUNTER_2 0X44 +#define PCI9111_REGISTER_8254_CONTROL 0x46 +#define PCI9111_REGISTER_INTERRUPT_CLEAR 0x48 + +#define PCI9111_TRIGGER_MASK 0x0F +#define PCI9111_PTRG_OFF (0 << 3) +#define PCI9111_PTRG_ON (1 << 3) +#define PCI9111_EITS_EXTERNAL (1 << 2) +#define PCI9111_EITS_INTERNAL (0 << 2) +#define PCI9111_TPST_SOFTWARE_TRIGGER (0 << 1) +#define PCI9111_TPST_TIMER_PACER (1 << 1) +#define PCI9111_ASCAN_ON (1 << 0) +#define PCI9111_ASCAN_OFF (0 << 0) + +#define PCI9111_ISC0_SET_IRQ_ON_ENDING_OF_AD_CONVERSION (0 << 0) +#define PCI9111_ISC0_SET_IRQ_ON_FIFO_HALF_FULL (1 << 0) +#define PCI9111_ISC1_SET_IRQ_ON_TIMER_TICK (0 << 1) +#define PCI9111_ISC1_SET_IRQ_ON_EXT_TRG (1 << 1) +#define PCI9111_FFEN_SET_FIFO_ENABLE (0 << 2) +#define PCI9111_FFEN_SET_FIFO_DISABLE (1 << 2) + +#define PCI9111_CHANNEL_MASK 0x0F + +#define PCI9111_RANGE_MASK 0x07 +#define PCI9111_FIFO_EMPTY_MASK 0x10 +#define PCI9111_FIFO_HALF_FULL_MASK 0x20 +#define PCI9111_FIFO_FULL_MASK 0x40 +#define PCI9111_AD_BUSY_MASK 0x80 + +#define PCI9111_IO_BASE (dev->iobase) + +/* + * Define inlined function + */ + +#define pci9111_trigger_and_autoscan_get() \ + (inb(PCI9111_IO_BASE+PCI9111_REGISTER_AD_MODE_INTERRUPT_READBACK)&0x0F) + +#define pci9111_trigger_and_autoscan_set(flags) \ + outb(flags, PCI9111_IO_BASE+PCI9111_REGISTER_TRIGGER_MODE_CONTROL) + +#define pci9111_interrupt_and_fifo_get() \ + ((inb(PCI9111_IO_BASE+PCI9111_REGISTER_AD_MODE_INTERRUPT_READBACK) \ + >> 4) & 0x03) + +#define pci9111_interrupt_and_fifo_set(flags) \ + outb(flags, PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CONTROL) + +#define pci9111_interrupt_clear() \ + outb(0, PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CLEAR) + +#define pci9111_software_trigger() \ + outb(0, PCI9111_IO_BASE+PCI9111_REGISTER_SOFTWARE_TRIGGER) + +#define pci9111_fifo_reset() do { \ + outb(PCI9111_FFEN_SET_FIFO_ENABLE, \ + PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CONTROL); \ + outb(PCI9111_FFEN_SET_FIFO_DISABLE, \ + PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CONTROL); \ + outb(PCI9111_FFEN_SET_FIFO_ENABLE, \ + PCI9111_IO_BASE+PCI9111_REGISTER_INTERRUPT_CONTROL); \ + } while (0) + +#define pci9111_is_fifo_full() \ + ((inb(PCI9111_IO_BASE+PCI9111_REGISTER_RANGE_STATUS_READBACK)& \ + PCI9111_FIFO_FULL_MASK) == 0) + +#define pci9111_is_fifo_half_full() \ + ((inb(PCI9111_IO_BASE+PCI9111_REGISTER_RANGE_STATUS_READBACK)& \ + PCI9111_FIFO_HALF_FULL_MASK) == 0) + +#define pci9111_is_fifo_empty() \ + ((inb(PCI9111_IO_BASE+PCI9111_REGISTER_RANGE_STATUS_READBACK)& \ + PCI9111_FIFO_EMPTY_MASK) == 0) + +#define pci9111_ai_channel_set(channel) \ + outb((channel)&PCI9111_CHANNEL_MASK, \ + PCI9111_IO_BASE+PCI9111_REGISTER_AD_CHANNEL_CONTROL) + +#define pci9111_ai_channel_get() \ + (inb(PCI9111_IO_BASE+PCI9111_REGISTER_AD_CHANNEL_READBACK) \ + &PCI9111_CHANNEL_MASK) + +#define pci9111_ai_range_set(range) \ + outb((range)&PCI9111_RANGE_MASK, \ + PCI9111_IO_BASE+PCI9111_REGISTER_INPUT_SIGNAL_RANGE) + +#define pci9111_ai_range_get() \ + (inb(PCI9111_IO_BASE+PCI9111_REGISTER_RANGE_STATUS_READBACK) \ + &PCI9111_RANGE_MASK) + +#define pci9111_ai_get_data() \ + (((inw(PCI9111_IO_BASE+PCI9111_REGISTER_AD_FIFO_VALUE)>>4) \ + &PCI9111_AI_RESOLUTION_MASK) \ + ^ PCI9111_AI_RESOLUTION_2_CMP_BIT) + +#define pci9111_hr_ai_get_data() \ + ((inw(PCI9111_IO_BASE+PCI9111_REGISTER_AD_FIFO_VALUE) \ + &PCI9111_HR_AI_RESOLUTION_MASK) \ + ^ PCI9111_HR_AI_RESOLUTION_2_CMP_BIT) + +#define pci9111_ao_set_data(data) \ + outw(data&PCI9111_AO_RESOLUTION_MASK, \ + PCI9111_IO_BASE+PCI9111_REGISTER_DA_OUTPUT) + +#define pci9111_di_get_bits() \ + inw(PCI9111_IO_BASE+PCI9111_REGISTER_DIGITAL_IO) + +#define pci9111_do_set_bits(bits) \ + outw(bits, PCI9111_IO_BASE+PCI9111_REGISTER_DIGITAL_IO) + +#define pci9111_8254_control_set(flags) \ + outb(flags, PCI9111_IO_BASE+PCI9111_REGISTER_8254_CONTROL) + +#define pci9111_8254_counter_0_set(data) \ + do { \ + outb(data & 0xFF, \ + PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_0); \ + outb((data >> 8) & 0xFF, \ + PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_0); \ + } while (0) + +#define pci9111_8254_counter_1_set(data) \ + do { \ + outb(data & 0xFF, \ + PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_1); \ + outb((data >> 8) & 0xFF, \ + PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_1); \ + } while (0) + +#define pci9111_8254_counter_2_set(data) \ + do { \ + outb(data & 0xFF, \ + PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_2); \ + outb((data >> 8) & 0xFF, \ + PCI9111_IO_BASE+PCI9111_REGISTER_8254_COUNTER_2); \ + } while (0) + +/* Function prototypes */ + +static int pci9111_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pci9111_detach(struct comedi_device *dev); +static void pci9111_ai_munge(struct comedi_device *dev, + struct comedi_subdevice *s, void *data, + unsigned int num_bytes, + unsigned int start_chan_index); + +static const struct comedi_lrange pci9111_hr_ai_range = { + 5, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625) + } +}; + +static DEFINE_PCI_DEVICE_TABLE(pci9111_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI9111_HR_DEVICE_ID) }, + /* { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI9111_HG_DEVICE_ID) }, */ + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, pci9111_pci_table); + +/* */ +/* Board specification structure */ +/* */ + +struct pci9111_board { + const char *name; /* driver name */ + int device_id; + int ai_channel_nbr; /* num of A/D chans */ + int ao_channel_nbr; /* num of D/A chans */ + int ai_resolution; /* resolution of A/D */ + int ai_resolution_mask; + int ao_resolution; /* resolution of D/A */ + int ao_resolution_mask; + const struct comedi_lrange *ai_range_list; /* rangelist for A/D */ + const struct comedi_lrange *ao_range_list; /* rangelist for D/A */ + unsigned int ai_acquisition_period_min_ns; +}; + +static const struct pci9111_board pci9111_boards[] = { + { + .name = "pci9111_hr", + .device_id = PCI9111_HR_DEVICE_ID, + .ai_channel_nbr = PCI9111_AI_CHANNEL_NBR, + .ao_channel_nbr = PCI9111_AO_CHANNEL_NBR, + .ai_resolution = PCI9111_HR_AI_RESOLUTION, + .ai_resolution_mask = PCI9111_HR_AI_RESOLUTION_MASK, + .ao_resolution = PCI9111_AO_RESOLUTION, + .ao_resolution_mask = PCI9111_AO_RESOLUTION_MASK, + .ai_range_list = &pci9111_hr_ai_range, + .ao_range_list = &range_bipolar10, + .ai_acquisition_period_min_ns = PCI9111_AI_ACQUISITION_PERIOD_MIN_NS} +}; + +#define pci9111_board_nbr \ + (sizeof(pci9111_boards)/sizeof(struct pci9111_board)) + +static struct comedi_driver pci9111_driver = { + .driver_name = PCI9111_DRIVER_NAME, + .module = THIS_MODULE, + .attach = pci9111_attach, + .detach = pci9111_detach, +}; + +static int __devinit pci9111_driver_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, pci9111_driver.driver_name); +} + +static void __devexit pci9111_driver_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver pci9111_driver_pci_driver = { + .id_table = pci9111_pci_table, + .probe = &pci9111_driver_pci_probe, + .remove = __devexit_p(&pci9111_driver_pci_remove) +}; + +static int __init pci9111_driver_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&pci9111_driver); + if (retval < 0) + return retval; + + pci9111_driver_pci_driver.name = (char *)pci9111_driver.driver_name; + return pci_register_driver(&pci9111_driver_pci_driver); +} + +static void __exit pci9111_driver_cleanup_module(void) +{ + pci_unregister_driver(&pci9111_driver_pci_driver); + comedi_driver_unregister(&pci9111_driver); +} + +module_init(pci9111_driver_init_module); +module_exit(pci9111_driver_cleanup_module); + +/* Private data structure */ + +struct pci9111_private_data { + struct pci_dev *pci_device; + unsigned long io_range; /* PCI6503 io range */ + + unsigned long lcr_io_base; /* Local configuration register base + * address */ + unsigned long lcr_io_range; + + int stop_counter; + int stop_is_none; + + unsigned int scan_delay; + unsigned int chanlist_len; + unsigned int chunk_counter; + unsigned int chunk_num_samples; + + int ao_readback; /* Last written analog output data */ + + unsigned int timer_divisor_1; /* Divisor values for the 8254 timer + * pacer */ + unsigned int timer_divisor_2; + + int is_valid; /* Is device valid */ + + short ai_bounce_buffer[2 * PCI9111_FIFO_HALF_SIZE]; +}; + +#define dev_private ((struct pci9111_private_data *)dev->private) + +/* ------------------------------------------------------------------ */ +/* PLX9050 SECTION */ +/* ------------------------------------------------------------------ */ + +#define PLX9050_REGISTER_INTERRUPT_CONTROL 0x4c + +#define PLX9050_LINTI1_ENABLE (1 << 0) +#define PLX9050_LINTI1_ACTIVE_HIGH (1 << 1) +#define PLX9050_LINTI1_STATUS (1 << 2) +#define PLX9050_LINTI2_ENABLE (1 << 3) +#define PLX9050_LINTI2_ACTIVE_HIGH (1 << 4) +#define PLX9050_LINTI2_STATUS (1 << 5) +#define PLX9050_PCI_INTERRUPT_ENABLE (1 << 6) +#define PLX9050_SOFTWARE_INTERRUPT (1 << 7) + +static void plx9050_interrupt_control(unsigned long io_base, + bool LINTi1_enable, + bool LINTi1_active_high, + bool LINTi2_enable, + bool LINTi2_active_high, + bool interrupt_enable) +{ + int flags = 0; + + if (LINTi1_enable) + flags |= PLX9050_LINTI1_ENABLE; + if (LINTi1_active_high) + flags |= PLX9050_LINTI1_ACTIVE_HIGH; + if (LINTi2_enable) + flags |= PLX9050_LINTI2_ENABLE; + if (LINTi2_active_high) + flags |= PLX9050_LINTI2_ACTIVE_HIGH; + + if (interrupt_enable) + flags |= PLX9050_PCI_INTERRUPT_ENABLE; + + outb(flags, io_base + PLX9050_REGISTER_INTERRUPT_CONTROL); +} + +/* ------------------------------------------------------------------ */ +/* MISCELLANEOUS SECTION */ +/* ------------------------------------------------------------------ */ + +/* 8254 timer */ + +static void pci9111_timer_set(struct comedi_device *dev) +{ + pci9111_8254_control_set(PCI9111_8254_COUNTER_0 | + PCI9111_8254_READ_LOAD_LSB_MSB | + PCI9111_8254_MODE_0 | + PCI9111_8254_BINARY_COUNTER); + + pci9111_8254_control_set(PCI9111_8254_COUNTER_1 | + PCI9111_8254_READ_LOAD_LSB_MSB | + PCI9111_8254_MODE_2 | + PCI9111_8254_BINARY_COUNTER); + + pci9111_8254_control_set(PCI9111_8254_COUNTER_2 | + PCI9111_8254_READ_LOAD_LSB_MSB | + PCI9111_8254_MODE_2 | + PCI9111_8254_BINARY_COUNTER); + + udelay(1); + + pci9111_8254_counter_2_set(dev_private->timer_divisor_2); + pci9111_8254_counter_1_set(dev_private->timer_divisor_1); +} + +enum pci9111_trigger_sources { + software, + timer_pacer, + external +}; + +static void pci9111_trigger_source_set(struct comedi_device *dev, + enum pci9111_trigger_sources source) +{ + int flags; + + flags = pci9111_trigger_and_autoscan_get() & 0x09; + + switch (source) { + case software: + flags |= PCI9111_EITS_INTERNAL | PCI9111_TPST_SOFTWARE_TRIGGER; + break; + + case timer_pacer: + flags |= PCI9111_EITS_INTERNAL | PCI9111_TPST_TIMER_PACER; + break; + + case external: + flags |= PCI9111_EITS_EXTERNAL; + break; + } + + pci9111_trigger_and_autoscan_set(flags); +} + +static void pci9111_pretrigger_set(struct comedi_device *dev, bool pretrigger) +{ + int flags; + + flags = pci9111_trigger_and_autoscan_get() & 0x07; + + if (pretrigger) + flags |= PCI9111_PTRG_ON; + + pci9111_trigger_and_autoscan_set(flags); +} + +static void pci9111_autoscan_set(struct comedi_device *dev, bool autoscan) +{ + int flags; + + flags = pci9111_trigger_and_autoscan_get() & 0x0e; + + if (autoscan) + flags |= PCI9111_ASCAN_ON; + + pci9111_trigger_and_autoscan_set(flags); +} + +enum pci9111_ISC0_sources { + irq_on_eoc, + irq_on_fifo_half_full +}; + +enum pci9111_ISC1_sources { + irq_on_timer_tick, + irq_on_external_trigger +}; + +static void pci9111_interrupt_source_set(struct comedi_device *dev, + enum pci9111_ISC0_sources irq_0_source, + enum pci9111_ISC1_sources irq_1_source) +{ + int flags; + + flags = pci9111_interrupt_and_fifo_get() & 0x04; + + if (irq_0_source == irq_on_fifo_half_full) + flags |= PCI9111_ISC0_SET_IRQ_ON_FIFO_HALF_FULL; + + if (irq_1_source == irq_on_external_trigger) + flags |= PCI9111_ISC1_SET_IRQ_ON_EXT_TRG; + + pci9111_interrupt_and_fifo_set(flags); +} + +/* ------------------------------------------------------------------ */ +/* HARDWARE TRIGGERED ANALOG INPUT SECTION */ +/* ------------------------------------------------------------------ */ + +/* Cancel analog input autoscan */ + +#undef AI_DO_CMD_DEBUG + +static int pci9111_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* Disable interrupts */ + + plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, + true, false); + + pci9111_trigger_source_set(dev, software); + + pci9111_autoscan_set(dev, false); + + pci9111_fifo_reset(); + +#ifdef AI_DO_CMD_DEBUG + printk(PCI9111_DRIVER_NAME ": ai_cancel\n"); +#endif + + return 0; +} + +/* Test analog input command */ + +#define pci9111_check_trigger_src(src, flags) do { \ + tmp = src; \ + src &= flags; \ + if (!src || tmp != src) \ + error++; \ + } while (false); + +static int +pci9111_ai_do_cmd_test(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int tmp; + int error = 0; + int range, reference; + int i; + struct pci9111_board *board = (struct pci9111_board *)dev->board_ptr; + + /* Step 1 : check if trigger are trivialy valid */ + + pci9111_check_trigger_src(cmd->start_src, TRIG_NOW); + pci9111_check_trigger_src(cmd->scan_begin_src, + TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT); + pci9111_check_trigger_src(cmd->convert_src, TRIG_TIMER | TRIG_EXT); + pci9111_check_trigger_src(cmd->scan_end_src, TRIG_COUNT); + pci9111_check_trigger_src(cmd->stop_src, TRIG_COUNT | TRIG_NONE); + + if (error) + return 1; + + /* step 2 : make sure trigger sources are unique and mutually + * compatible */ + + if (cmd->start_src != TRIG_NOW) + error++; + + if ((cmd->scan_begin_src != TRIG_TIMER) && + (cmd->scan_begin_src != TRIG_FOLLOW) && + (cmd->scan_begin_src != TRIG_EXT)) + error++; + + if ((cmd->convert_src != TRIG_TIMER) && (cmd->convert_src != TRIG_EXT)) + error++; + if ((cmd->convert_src == TRIG_TIMER) && + !((cmd->scan_begin_src == TRIG_TIMER) || + (cmd->scan_begin_src == TRIG_FOLLOW))) + error++; + if ((cmd->convert_src == TRIG_EXT) && + !((cmd->scan_begin_src == TRIG_EXT) || + (cmd->scan_begin_src == TRIG_FOLLOW))) + error++; + + + if (cmd->scan_end_src != TRIG_COUNT) + error++; + if ((cmd->stop_src != TRIG_COUNT) && (cmd->stop_src != TRIG_NONE)) + error++; + + if (error) + return 2; + + /* Step 3 : make sure arguments are trivialy compatible */ + + if (cmd->chanlist_len < 1) { + cmd->chanlist_len = 1; + error++; + } + + if (cmd->chanlist_len > board->ai_channel_nbr) { + cmd->chanlist_len = board->ai_channel_nbr; + error++; + } + + if ((cmd->start_src == TRIG_NOW) && (cmd->start_arg != 0)) { + cmd->start_arg = 0; + error++; + } + + if ((cmd->convert_src == TRIG_TIMER) && + (cmd->convert_arg < board->ai_acquisition_period_min_ns)) { + cmd->convert_arg = board->ai_acquisition_period_min_ns; + error++; + } + if ((cmd->convert_src == TRIG_EXT) && (cmd->convert_arg != 0)) { + cmd->convert_arg = 0; + error++; + } + + if ((cmd->scan_begin_src == TRIG_TIMER) && + (cmd->scan_begin_arg < board->ai_acquisition_period_min_ns)) { + cmd->scan_begin_arg = board->ai_acquisition_period_min_ns; + error++; + } + if ((cmd->scan_begin_src == TRIG_FOLLOW) + && (cmd->scan_begin_arg != 0)) { + cmd->scan_begin_arg = 0; + error++; + } + if ((cmd->scan_begin_src == TRIG_EXT) && (cmd->scan_begin_arg != 0)) { + cmd->scan_begin_arg = 0; + error++; + } + + if ((cmd->scan_end_src == TRIG_COUNT) && + (cmd->scan_end_arg != cmd->chanlist_len)) { + cmd->scan_end_arg = cmd->chanlist_len; + error++; + } + + if ((cmd->stop_src == TRIG_COUNT) && (cmd->stop_arg < 1)) { + cmd->stop_arg = 1; + error++; + } + if ((cmd->stop_src == TRIG_NONE) && (cmd->stop_arg != 0)) { + cmd->stop_arg = 0; + error++; + } + + if (error) + return 3; + + /* Step 4 : fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + i8253_cascade_ns_to_timer_2div(PCI9111_8254_CLOCK_PERIOD_NS, + &(dev_private->timer_divisor_1), + &(dev_private->timer_divisor_2), + &(cmd->convert_arg), + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + error++; + } + /* There's only one timer on this card, so the scan_begin timer must */ + /* be a multiple of chanlist_len*convert_arg */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + + unsigned int scan_begin_min; + unsigned int scan_begin_arg; + unsigned int scan_factor; + + scan_begin_min = cmd->chanlist_len * cmd->convert_arg; + + if (cmd->scan_begin_arg != scan_begin_min) { + if (scan_begin_min < cmd->scan_begin_arg) { + scan_factor = + cmd->scan_begin_arg / scan_begin_min; + scan_begin_arg = scan_factor * scan_begin_min; + if (cmd->scan_begin_arg != scan_begin_arg) { + cmd->scan_begin_arg = scan_begin_arg; + error++; + } + } else { + cmd->scan_begin_arg = scan_begin_min; + error++; + } + } + } + + if (error) + return 4; + + /* Step 5 : check channel list */ + + if (cmd->chanlist) { + + range = CR_RANGE(cmd->chanlist[0]); + reference = CR_AREF(cmd->chanlist[0]); + + if (cmd->chanlist_len > 1) { + for (i = 0; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != i) { + comedi_error(dev, + "entries in chanlist must be consecutive " + "channels,counting upwards from 0\n"); + error++; + } + if (CR_RANGE(cmd->chanlist[i]) != range) { + comedi_error(dev, + "entries in chanlist must all have the same gain\n"); + error++; + } + if (CR_AREF(cmd->chanlist[i]) != reference) { + comedi_error(dev, + "entries in chanlist must all have the same reference\n"); + error++; + } + } + } else { + if ((CR_CHAN(cmd->chanlist[0]) > + (board->ai_channel_nbr - 1)) + || (CR_CHAN(cmd->chanlist[0]) < 0)) { + comedi_error(dev, + "channel number is out of limits\n"); + error++; + } + } + } + + if (error) + return 5; + + return 0; + +} + +/* Analog input command */ + +static int pci9111_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *subdevice) +{ + struct comedi_cmd *async_cmd = &subdevice->async->cmd; + + if (!dev->irq) { + comedi_error(dev, + "no irq assigned for PCI9111, cannot do hardware conversion"); + return -1; + } + /* Set channel scan limit */ + /* PCI9111 allows only scanning from channel 0 to channel n */ + /* TODO: handle the case of an external multiplexer */ + + if (async_cmd->chanlist_len > 1) { + pci9111_ai_channel_set((async_cmd->chanlist_len) - 1); + pci9111_autoscan_set(dev, true); + } else { + pci9111_ai_channel_set(CR_CHAN(async_cmd->chanlist[0])); + pci9111_autoscan_set(dev, false); + } + + /* Set gain */ + /* This is the same gain on every channel */ + + pci9111_ai_range_set(CR_RANGE(async_cmd->chanlist[0])); + + /* Set counter */ + + switch (async_cmd->stop_src) { + case TRIG_COUNT: + dev_private->stop_counter = + async_cmd->stop_arg * async_cmd->chanlist_len; + dev_private->stop_is_none = 0; + break; + + case TRIG_NONE: + dev_private->stop_counter = 0; + dev_private->stop_is_none = 1; + break; + + default: + comedi_error(dev, "Invalid stop trigger"); + return -1; + } + + /* Set timer pacer */ + + dev_private->scan_delay = 0; + switch (async_cmd->convert_src) { + case TRIG_TIMER: + i8253_cascade_ns_to_timer_2div(PCI9111_8254_CLOCK_PERIOD_NS, + &(dev_private->timer_divisor_1), + &(dev_private->timer_divisor_2), + &(async_cmd->convert_arg), + async_cmd-> + flags & TRIG_ROUND_MASK); +#ifdef AI_DO_CMD_DEBUG + printk(PCI9111_DRIVER_NAME ": divisors = %d, %d\n", + dev_private->timer_divisor_1, + dev_private->timer_divisor_2); +#endif + + pci9111_trigger_source_set(dev, software); + pci9111_timer_set(dev); + pci9111_fifo_reset(); + pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, + irq_on_timer_tick); + pci9111_trigger_source_set(dev, timer_pacer); + plx9050_interrupt_control(dev_private->lcr_io_base, true, true, + false, true, true); + + if (async_cmd->scan_begin_src == TRIG_TIMER) { + dev_private->scan_delay = + (async_cmd->scan_begin_arg / + (async_cmd->convert_arg * + async_cmd->chanlist_len)) - 1; + } + + break; + + case TRIG_EXT: + + pci9111_trigger_source_set(dev, external); + pci9111_fifo_reset(); + pci9111_interrupt_source_set(dev, irq_on_fifo_half_full, + irq_on_timer_tick); + plx9050_interrupt_control(dev_private->lcr_io_base, true, true, + false, true, true); + + break; + + default: + comedi_error(dev, "Invalid convert trigger"); + return -1; + } + + dev_private->stop_counter *= (1 + dev_private->scan_delay); + dev_private->chanlist_len = async_cmd->chanlist_len; + dev_private->chunk_counter = 0; + dev_private->chunk_num_samples = + dev_private->chanlist_len * (1 + dev_private->scan_delay); + +#ifdef AI_DO_CMD_DEBUG + printk(PCI9111_DRIVER_NAME ": start interruptions!\n"); + printk(PCI9111_DRIVER_NAME ": trigger source = %2x\n", + pci9111_trigger_and_autoscan_get()); + printk(PCI9111_DRIVER_NAME ": irq source = %2x\n", + pci9111_interrupt_and_fifo_get()); + printk(PCI9111_DRIVER_NAME ": ai_do_cmd\n"); + printk(PCI9111_DRIVER_NAME ": stop counter = %d\n", + dev_private->stop_counter); + printk(PCI9111_DRIVER_NAME ": scan delay = %d\n", + dev_private->scan_delay); + printk(PCI9111_DRIVER_NAME ": chanlist_len = %d\n", + dev_private->chanlist_len); + printk(PCI9111_DRIVER_NAME ": chunk num samples = %d\n", + dev_private->chunk_num_samples); +#endif + + return 0; +} + +static void pci9111_ai_munge(struct comedi_device *dev, + struct comedi_subdevice *s, void *data, + unsigned int num_bytes, + unsigned int start_chan_index) +{ + unsigned int i, num_samples = num_bytes / sizeof(short); + short *array = data; + int resolution = + ((struct pci9111_board *)dev->board_ptr)->ai_resolution; + + for (i = 0; i < num_samples; i++) { + if (resolution == PCI9111_HR_AI_RESOLUTION) + array[i] = + (array[i] & PCI9111_HR_AI_RESOLUTION_MASK) ^ + PCI9111_HR_AI_RESOLUTION_2_CMP_BIT; + else + array[i] = + ((array[i] >> 4) & PCI9111_AI_RESOLUTION_MASK) ^ + PCI9111_AI_RESOLUTION_2_CMP_BIT; + } +} + +/* ------------------------------------------------------------------ */ +/* INTERRUPT SECTION */ +/* ------------------------------------------------------------------ */ + +#undef INTERRUPT_DEBUG + +static irqreturn_t pci9111_interrupt(int irq, void *p_device) +{ + struct comedi_device *dev = p_device; + struct comedi_subdevice *subdevice = dev->read_subdev; + struct comedi_async *async; + unsigned long irq_flags; + unsigned char intcsr; + + if (!dev->attached) { + /* Ignore interrupt before device fully attached. */ + /* Might not even have allocated subdevices yet! */ + return IRQ_NONE; + } + + async = subdevice->async; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + + /* Check if we are source of interrupt */ + intcsr = inb(dev_private->lcr_io_base + + PLX9050_REGISTER_INTERRUPT_CONTROL); + if (!(((intcsr & PLX9050_PCI_INTERRUPT_ENABLE) != 0) + && (((intcsr & (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) + == (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) + || ((intcsr & (PLX9050_LINTI2_ENABLE | PLX9050_LINTI2_STATUS)) + == (PLX9050_LINTI2_ENABLE | PLX9050_LINTI2_STATUS))))) { + /* Not the source of the interrupt. */ + /* (N.B. not using PLX9050_SOFTWARE_INTERRUPT) */ + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + return IRQ_NONE; + } + + if ((intcsr & (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) == + (PLX9050_LINTI1_ENABLE | PLX9050_LINTI1_STATUS)) { + /* Interrupt comes from fifo_half-full signal */ + + if (pci9111_is_fifo_full()) { + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + comedi_error(dev, PCI9111_DRIVER_NAME " fifo overflow"); + pci9111_interrupt_clear(); + pci9111_ai_cancel(dev, subdevice); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + comedi_event(dev, subdevice); + + return IRQ_HANDLED; + } + + if (pci9111_is_fifo_half_full()) { + unsigned int num_samples; + unsigned int bytes_written = 0; + +#ifdef INTERRUPT_DEBUG + printk(PCI9111_DRIVER_NAME ": fifo is half full\n"); +#endif + + num_samples = + PCI9111_FIFO_HALF_SIZE > + dev_private->stop_counter + && !dev_private-> + stop_is_none ? dev_private->stop_counter : + PCI9111_FIFO_HALF_SIZE; + insw(PCI9111_IO_BASE + PCI9111_REGISTER_AD_FIFO_VALUE, + dev_private->ai_bounce_buffer, num_samples); + + if (dev_private->scan_delay < 1) { + bytes_written = + cfc_write_array_to_buffer(subdevice, + dev_private-> + ai_bounce_buffer, + num_samples * + sizeof(short)); + } else { + int position = 0; + int to_read; + + while (position < num_samples) { + if (dev_private->chunk_counter < + dev_private->chanlist_len) { + to_read = + dev_private->chanlist_len - + dev_private->chunk_counter; + + if (to_read > + num_samples - position) + to_read = + num_samples - + position; + + bytes_written += + cfc_write_array_to_buffer + (subdevice, + dev_private->ai_bounce_buffer + + position, + to_read * sizeof(short)); + } else { + to_read = + dev_private->chunk_num_samples + - + dev_private->chunk_counter; + if (to_read > + num_samples - position) + to_read = + num_samples - + position; + + bytes_written += + sizeof(short) * to_read; + } + + position += to_read; + dev_private->chunk_counter += to_read; + + if (dev_private->chunk_counter >= + dev_private->chunk_num_samples) + dev_private->chunk_counter = 0; + } + } + + dev_private->stop_counter -= + bytes_written / sizeof(short); + } + } + + if ((dev_private->stop_counter == 0) && (!dev_private->stop_is_none)) { + async->events |= COMEDI_CB_EOA; + pci9111_ai_cancel(dev, subdevice); + } + + /* Very important, otherwise another interrupt request will be inserted + * and will cause driver hangs on processing interrupt event. */ + + pci9111_interrupt_clear(); + + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + comedi_event(dev, subdevice); + + return IRQ_HANDLED; +} + +/* ------------------------------------------------------------------ */ +/* INSTANT ANALOG INPUT OUTPUT SECTION */ +/* ------------------------------------------------------------------ */ + +/* analog instant input */ + +#undef AI_INSN_DEBUG + +static int pci9111_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *subdevice, + struct comedi_insn *insn, unsigned int *data) +{ + int resolution = + ((struct pci9111_board *)dev->board_ptr)->ai_resolution; + + int timeout, i; + +#ifdef AI_INSN_DEBUG + printk(PCI9111_DRIVER_NAME ": ai_insn set c/r/n = %2x/%2x/%2x\n", + CR_CHAN((&insn->chanspec)[0]), + CR_RANGE((&insn->chanspec)[0]), insn->n); +#endif + + pci9111_ai_channel_set(CR_CHAN((&insn->chanspec)[0])); + + if ((pci9111_ai_range_get()) != CR_RANGE((&insn->chanspec)[0])) + pci9111_ai_range_set(CR_RANGE((&insn->chanspec)[0])); + + pci9111_fifo_reset(); + + for (i = 0; i < insn->n; i++) { + pci9111_software_trigger(); + + timeout = PCI9111_AI_INSTANT_READ_TIMEOUT; + + while (timeout--) { + if (!pci9111_is_fifo_empty()) + goto conversion_done; + } + + comedi_error(dev, "A/D read timeout"); + data[i] = 0; + pci9111_fifo_reset(); + return -ETIME; + +conversion_done: + + if (resolution == PCI9111_HR_AI_RESOLUTION) + data[i] = pci9111_hr_ai_get_data(); + else + data[i] = pci9111_ai_get_data(); + } + +#ifdef AI_INSN_DEBUG + printk(PCI9111_DRIVER_NAME ": ai_insn get c/r/t = %2x/%2x/%2x\n", + pci9111_ai_channel_get(), + pci9111_ai_range_get(), pci9111_trigger_and_autoscan_get()); +#endif + + return i; +} + +/* Analog instant output */ + +static int +pci9111_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i; + + for (i = 0; i < insn->n; i++) { + pci9111_ao_set_data(data[i]); + dev_private->ao_readback = data[i]; + } + + return i; +} + +/* Analog output readback */ + +static int pci9111_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + + for (i = 0; i < insn->n; i++) + data[i] = dev_private->ao_readback & PCI9111_AO_RESOLUTION_MASK; + + return i; +} + +/* ------------------------------------------------------------------ */ +/* DIGITAL INPUT OUTPUT SECTION */ +/* ------------------------------------------------------------------ */ + +/* Digital inputs */ + +static int pci9111_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *subdevice, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int bits; + + bits = pci9111_di_get_bits(); + data[1] = bits; + + return 2; +} + +/* Digital outputs */ + +static int pci9111_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *subdevice, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int bits; + + /* Only set bits that have been masked */ + /* data[0] = mask */ + /* data[1] = bit state */ + + data[0] &= PCI9111_DO_MASK; + + bits = subdevice->state; + bits &= ~data[0]; + bits |= data[0] & data[1]; + subdevice->state = bits; + + pci9111_do_set_bits(bits); + + data[1] = bits; + + return 2; +} + +/* ------------------------------------------------------------------ */ +/* INITIALISATION SECTION */ +/* ------------------------------------------------------------------ */ + +/* Reset device */ + +static int pci9111_reset(struct comedi_device *dev) +{ + /* Set trigger source to software */ + + plx9050_interrupt_control(dev_private->lcr_io_base, true, true, true, + true, false); + + pci9111_trigger_source_set(dev, software); + pci9111_pretrigger_set(dev, false); + pci9111_autoscan_set(dev, false); + + /* Reset 8254 chip */ + + dev_private->timer_divisor_1 = 0; + dev_private->timer_divisor_2 = 0; + + pci9111_timer_set(dev); + + return 0; +} + +/* Attach */ +/* - Register PCI device */ +/* - Declare device driver capability */ + +static int pci9111_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *subdevice; + unsigned long io_base, io_range, lcr_io_base, lcr_io_range; + struct pci_dev *pci_device = NULL; + int error, i; + const struct pci9111_board *board; + + if (alloc_private(dev, sizeof(struct pci9111_private_data)) < 0) + return -ENOMEM; + /* Probe the device to determine what device in the series it is. */ + + printk(KERN_ERR "comedi%d: " PCI9111_DRIVER_NAME " driver\n", + dev->minor); + + for_each_pci_dev(pci_device) { + if (pci_device->vendor == PCI_VENDOR_ID_ADLINK) { + for (i = 0; i < pci9111_board_nbr; i++) { + if (pci9111_boards[i].device_id == + pci_device->device) { + /* was a particular bus/slot + * requested? */ + if ((it->options[0] != 0) + || (it->options[1] != 0)) { + /* are we on the wrong + * bus/slot? */ + if (pci_device->bus->number != + it->options[0] + || + PCI_SLOT(pci_device->devfn) + != it->options[1]) { + continue; + } + } + + dev->board_ptr = pci9111_boards + i; + board = + (struct pci9111_board *) + dev->board_ptr; + dev_private->pci_device = pci_device; + goto found; + } + } + } + } + + printk(KERN_ERR + "comedi%d: no supported board found! (req. bus/slot : %d/%d)\n", + dev->minor, it->options[0], it->options[1]); + return -EIO; + +found: + + printk(KERN_ERR "comedi%d: found %s (b:s:f=%d:%d:%d) , irq=%d\n", + dev->minor, + pci9111_boards[i].name, + pci_device->bus->number, + PCI_SLOT(pci_device->devfn), + PCI_FUNC(pci_device->devfn), pci_device->irq); + + /* TODO: Warn about non-tested boards. */ + + /* Read local configuration register base address + * [PCI_BASE_ADDRESS #1]. */ + + lcr_io_base = pci_resource_start(pci_device, 1); + lcr_io_range = pci_resource_len(pci_device, 1); + + printk + ("comedi%d: local configuration registers at address 0x%4lx [0x%4lx]\n", + dev->minor, lcr_io_base, lcr_io_range); + + /* Enable PCI device and request regions */ + if (comedi_pci_enable(pci_device, PCI9111_DRIVER_NAME) < 0) { + printk + ("comedi%d: Failed to enable PCI device and request regions\n", + dev->minor); + return -EIO; + } + /* Read PCI6308 register base address [PCI_BASE_ADDRESS #2]. */ + + io_base = pci_resource_start(pci_device, 2); + io_range = pci_resource_len(pci_device, 2); + + printk(KERN_ERR "comedi%d: 6503 registers at address 0x%4lx [0x%4lx]\n", + dev->minor, io_base, io_range); + + dev->iobase = io_base; + dev->board_name = board->name; + dev_private->io_range = io_range; + dev_private->is_valid = 0; + dev_private->lcr_io_base = lcr_io_base; + dev_private->lcr_io_range = lcr_io_range; + + pci9111_reset(dev); + + /* Irq setup */ + + dev->irq = 0; + if (pci_device->irq > 0) { + if (request_irq(pci_device->irq, pci9111_interrupt, + IRQF_SHARED, PCI9111_DRIVER_NAME, dev) != 0) { + printk(KERN_ERR + "comedi%d: unable to allocate irq %u\n", + dev->minor, pci_device->irq); + return -EINVAL; + } + } + dev->irq = pci_device->irq; + + /* TODO: Add external multiplexer setup (according to option[2]). */ + + error = alloc_subdevices(dev, 4); + if (error < 0) + return error; + + subdevice = dev->subdevices + 0; + dev->read_subdev = subdevice; + + subdevice->type = COMEDI_SUBD_AI; + subdevice->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_CMD_READ; + + /* TODO: Add external multiplexer data */ + /* if (devpriv->usemux) { subdevice->n_chan = devpriv->usemux; } */ + /* else { subdevice->n_chan = this_board->n_aichan; } */ + + subdevice->n_chan = board->ai_channel_nbr; + subdevice->maxdata = board->ai_resolution_mask; + subdevice->len_chanlist = board->ai_channel_nbr; + subdevice->range_table = board->ai_range_list; + subdevice->cancel = pci9111_ai_cancel; + subdevice->insn_read = pci9111_ai_insn_read; + subdevice->do_cmdtest = pci9111_ai_do_cmd_test; + subdevice->do_cmd = pci9111_ai_do_cmd; + subdevice->munge = pci9111_ai_munge; + + subdevice = dev->subdevices + 1; + subdevice->type = COMEDI_SUBD_AO; + subdevice->subdev_flags = SDF_WRITABLE | SDF_COMMON; + subdevice->n_chan = board->ao_channel_nbr; + subdevice->maxdata = board->ao_resolution_mask; + subdevice->len_chanlist = board->ao_channel_nbr; + subdevice->range_table = board->ao_range_list; + subdevice->insn_write = pci9111_ao_insn_write; + subdevice->insn_read = pci9111_ao_insn_read; + + subdevice = dev->subdevices + 2; + subdevice->type = COMEDI_SUBD_DI; + subdevice->subdev_flags = SDF_READABLE; + subdevice->n_chan = PCI9111_DI_CHANNEL_NBR; + subdevice->maxdata = 1; + subdevice->range_table = &range_digital; + subdevice->insn_bits = pci9111_di_insn_bits; + + subdevice = dev->subdevices + 3; + subdevice->type = COMEDI_SUBD_DO; + subdevice->subdev_flags = SDF_READABLE | SDF_WRITABLE; + subdevice->n_chan = PCI9111_DO_CHANNEL_NBR; + subdevice->maxdata = 1; + subdevice->range_table = &range_digital; + subdevice->insn_bits = pci9111_do_insn_bits; + + dev_private->is_valid = 1; + + return 0; +} + +/* Detach */ + +static int pci9111_detach(struct comedi_device *dev) +{ + /* Reset device */ + + if (dev->private != NULL) { + if (dev_private->is_valid) + pci9111_reset(dev); + + } + /* Release previously allocated irq */ + + if (dev->irq != 0) + free_irq(dev->irq, dev); + + if (dev_private != NULL && dev_private->pci_device != NULL) { + if (dev->iobase) + comedi_pci_disable(dev_private->pci_device); + pci_dev_put(dev_private->pci_device); + } + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adl_pci9118.c b/drivers/staging/comedi/drivers/adl_pci9118.c new file mode 100644 index 00000000..f17654e4 --- /dev/null +++ b/drivers/staging/comedi/drivers/adl_pci9118.c @@ -0,0 +1,2471 @@ +/* + * comedi/drivers/adl_pci9118.c + * + * hardware driver for ADLink cards: + * card: PCI-9118DG, PCI-9118HG, PCI-9118HR + * driver: pci9118dg, pci9118hg, pci9118hr + * + * Author: Michal Dobes + * +*/ +/* +Driver: adl_pci9118 +Description: Adlink PCI-9118DG, PCI-9118HG, PCI-9118HR +Author: Michal Dobes +Devices: [ADLink] PCI-9118DG (pci9118dg), PCI-9118HG (pci9118hg), + PCI-9118HR (pci9118hr) +Status: works + +This driver supports AI, AO, DI and DO subdevices. +AI subdevice supports cmd and insn interface, +other subdevices support only insn interface. +For AI: +- If cmd->scan_begin_src=TRIG_EXT then trigger input is TGIN (pin 46). +- If cmd->convert_src=TRIG_EXT then trigger input is EXTTRG (pin 44). +- If cmd->start_src/stop_src=TRIG_EXT then trigger input is TGIN (pin 46). +- It is not necessary to have cmd.scan_end_arg=cmd.chanlist_len but + cmd.scan_end_arg modulo cmd.chanlist_len must by 0. +- If return value of cmdtest is 5 then you've bad channel list + (it isn't possible mixture S.E. and DIFF inputs or bipolar and unipolar + ranges). + +There are some hardware limitations: +a) You cann't use mixture of unipolar/bipoar ranges or differencial/single + ended inputs. +b) DMA transfers must have the length aligned to two samples (32 bit), + so there is some problems if cmd->chanlist_len is odd. This driver tries + bypass this with adding one sample to the end of the every scan and discard + it on output but this cann't be used if cmd->scan_begin_src=TRIG_FOLLOW + and is used flag TRIG_WAKE_EOS, then driver switch to interrupt driven mode + with interrupt after every sample. +c) If isn't used DMA then you can use only mode where + cmd->scan_begin_src=TRIG_FOLLOW. + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, then first available PCI + card will be used. + [2] - 0= standard 8 DIFF/16 SE channels configuration + n = external multiplexer connected, 1 <= n <= 256 + [3] - 0=autoselect DMA or EOC interrupts operation + 1 = disable DMA mode + 3 = disable DMA and INT, only insn interface will work + [4] - sample&hold signal - card can generate signal for external S&H board + 0 = use SSHO(pin 45) signal is generated in onboard hardware S&H logic + 0 != use ADCHN7(pin 23) signal is generated from driver, number say how + long delay is requested in ns and sign polarity of the hold + (in this case external multiplexor can serve only 128 channels) + [5] - 0=stop measure on all hardware errors + 2 | = ignore ADOR - A/D Overrun status + 8|=ignore Bover - A/D Burst Mode Overrun status + 256|=ignore nFull - A/D FIFO Full status + +*/ +#include "../comedidev.h" + +#include +#include +#include +#include + +#include "amcc_s5933.h" +#include "8253.h" +#include "comedi_pci.h" +#include "comedi_fc.h" + +#define PCI_VENDOR_ID_AMCC 0x10e8 + +/* paranoid checks are broken */ +#undef PCI9118_PARANOIDCHECK /* + * if defined, then is used code which control + * correct channel number on every 12 bit sample + */ + +#undef PCI9118_EXTDEBUG /* + * if defined then driver prints + * a lot of messages + */ + +#undef DPRINTK +#ifdef PCI9118_EXTDEBUG +#define DPRINTK(fmt, args...) printk(fmt, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +#define IORANGE_9118 64 /* I hope */ +#define PCI9118_CHANLEN 255 /* + * len of chanlist, some source say 256, + * but reality looks like 255 :-( + */ + +#define PCI9118_CNT0 0x00 /* R/W: 8254 counter 0 */ +#define PCI9118_CNT1 0x04 /* R/W: 8254 counter 0 */ +#define PCI9118_CNT2 0x08 /* R/W: 8254 counter 0 */ +#define PCI9118_CNTCTRL 0x0c /* W: 8254 counter control */ +#define PCI9118_AD_DATA 0x10 /* R: A/D data */ +#define PCI9118_DA1 0x10 /* W: D/A registers */ +#define PCI9118_DA2 0x14 +#define PCI9118_ADSTAT 0x18 /* R: A/D status register */ +#define PCI9118_ADCNTRL 0x18 /* W: A/D control register */ +#define PCI9118_DI 0x1c /* R: digi input register */ +#define PCI9118_DO 0x1c /* W: digi output register */ +#define PCI9118_SOFTTRG 0x20 /* W: soft trigger for A/D */ +#define PCI9118_GAIN 0x24 /* W: A/D gain/channel register */ +#define PCI9118_BURST 0x28 /* W: A/D burst number register */ +#define PCI9118_SCANMOD 0x2c /* W: A/D auto scan mode */ +#define PCI9118_ADFUNC 0x30 /* W: A/D function register */ +#define PCI9118_DELFIFO 0x34 /* W: A/D data FIFO reset */ +#define PCI9118_INTSRC 0x38 /* R: interrupt reason register */ +#define PCI9118_INTCTRL 0x38 /* W: interrupt control register */ + +/* bits from A/D control register (PCI9118_ADCNTRL) */ +#define AdControl_UniP 0x80 /* 1=bipolar, 0=unipolar */ +#define AdControl_Diff 0x40 /* 1=differential, 0= single end inputs */ +#define AdControl_SoftG 0x20 /* 1=8254 counter works, 0=counter stops */ +#define AdControl_ExtG 0x10 /* + * 1=8254 countrol controlled by TGIN(pin 46), + * 0=controlled by SoftG + */ +#define AdControl_ExtM 0x08 /* + * 1=external hardware trigger (pin 44), + * 0=internal trigger + */ +#define AdControl_TmrTr 0x04 /* + * 1=8254 is iternal trigger source, + * 0=software trigger is source + * (register PCI9118_SOFTTRG) + */ +#define AdControl_Int 0x02 /* 1=enable INT, 0=disable */ +#define AdControl_Dma 0x01 /* 1=enable DMA, 0=disable */ + +/* bits from A/D function register (PCI9118_ADFUNC) */ +#define AdFunction_PDTrg 0x80 /* + * 1=positive, + * 0=negative digital trigger + * (only positive is correct) + */ +#define AdFunction_PETrg 0x40 /* + * 1=positive, + * 0=negative external trigger + * (only positive is correct) + */ +#define AdFunction_BSSH 0x20 /* 1=with sample&hold, 0=without */ +#define AdFunction_BM 0x10 /* 1=burst mode, 0=normal mode */ +#define AdFunction_BS 0x08 /* + * 1=burst mode start, + * 0=burst mode stop + */ +#define AdFunction_PM 0x04 /* + * 1=post trigger mode, + * 0=not post trigger + */ +#define AdFunction_AM 0x02 /* + * 1=about trigger mode, + * 0=not about trigger + */ +#define AdFunction_Start 0x01 /* 1=trigger start, 0=trigger stop */ + +/* bits from A/D status register (PCI9118_ADSTAT) */ +#define AdStatus_nFull 0x100 /* 0=FIFO full (fatal), 1=not full */ +#define AdStatus_nHfull 0x080 /* 0=FIFO half full, 1=FIFO not half full */ +#define AdStatus_nEpty 0x040 /* 0=FIFO empty, 1=FIFO not empty */ +#define AdStatus_Acmp 0x020 /* */ +#define AdStatus_DTH 0x010 /* 1=external digital trigger */ +#define AdStatus_Bover 0x008 /* 1=burst mode overrun (fatal) */ +#define AdStatus_ADOS 0x004 /* 1=A/D over speed (warning) */ +#define AdStatus_ADOR 0x002 /* 1=A/D overrun (fatal) */ +#define AdStatus_ADrdy 0x001 /* 1=A/D already ready, 0=not ready */ + +/* bits for interrupt reason and control (PCI9118_INTSRC, PCI9118_INTCTRL) */ +/* 1=interrupt occur, enable source, 0=interrupt not occur, disable source */ +#define Int_Timer 0x08 /* timer interrupt */ +#define Int_About 0x04 /* about trigger complete */ +#define Int_Hfull 0x02 /* A/D FIFO hlaf full */ +#define Int_DTrg 0x01 /* external digital trigger */ + +#define START_AI_EXT 0x01 /* start measure on external trigger */ +#define STOP_AI_EXT 0x02 /* stop measure on external trigger */ +#define START_AI_INT 0x04 /* start measure on internal trigger */ +#define STOP_AI_INT 0x08 /* stop measure on internal trigger */ + +#define EXTTRG_AI 0 /* ext trg is used by AI */ + +static const struct comedi_lrange range_pci9118dg_hr = { 8, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange range_pci9118hg = { 8, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01) + } +}; + +#define PCI9118_BIPOLAR_RANGES 4 /* + * used for test on mixture + * of BIP/UNI ranges + */ + +static int pci9118_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pci9118_detach(struct comedi_device *dev); + +struct boardtype { + const char *name; /* board name */ + int vendor_id; /* PCI vendor a device ID of card */ + int device_id; + int iorange_amcc; /* iorange for own S5933 region */ + int iorange_9118; /* pass thru card region size */ + int n_aichan; /* num of A/D chans */ + int n_aichand; /* num of A/D chans in diff mode */ + int mux_aichan; /* + * num of A/D chans with + * external multiplexor + */ + int n_aichanlist; /* len of chanlist */ + int n_aochan; /* num of D/A chans */ + int ai_maxdata; /* resolution of A/D */ + int ao_maxdata; /* resolution of D/A */ + const struct comedi_lrange *rangelist_ai; /* rangelist for A/D */ + const struct comedi_lrange *rangelist_ao; /* rangelist for D/A */ + unsigned int ai_ns_min; /* max sample speed of card v ns */ + unsigned int ai_pacer_min; /* + * minimal pacer value + * (c1*c2 or c1 in burst) + */ + int half_fifo_size; /* size of FIFO/2 */ + +}; + +static DEFINE_PCI_DEVICE_TABLE(pci9118_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_AMCC, 0x80d9) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, pci9118_pci_table); + +static const struct boardtype boardtypes[] = { + {"pci9118dg", PCI_VENDOR_ID_AMCC, 0x80d9, + AMCC_OP_REG_SIZE, IORANGE_9118, + 16, 8, 256, PCI9118_CHANLEN, 2, 0x0fff, 0x0fff, + &range_pci9118dg_hr, &range_bipolar10, + 3000, 12, 512}, + {"pci9118hg", PCI_VENDOR_ID_AMCC, 0x80d9, + AMCC_OP_REG_SIZE, IORANGE_9118, + 16, 8, 256, PCI9118_CHANLEN, 2, 0x0fff, 0x0fff, + &range_pci9118hg, &range_bipolar10, + 3000, 12, 512}, + {"pci9118hr", PCI_VENDOR_ID_AMCC, 0x80d9, + AMCC_OP_REG_SIZE, IORANGE_9118, + 16, 8, 256, PCI9118_CHANLEN, 2, 0xffff, 0x0fff, + &range_pci9118dg_hr, &range_bipolar10, + 10000, 40, 512}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct boardtype)) + +static struct comedi_driver driver_pci9118 = { + .driver_name = "adl_pci9118", + .module = THIS_MODULE, + .attach = pci9118_attach, + .detach = pci9118_detach, + .num_names = n_boardtypes, + .board_name = &boardtypes[0].name, + .offset = sizeof(struct boardtype), +}; + +static int __devinit driver_pci9118_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_pci9118.driver_name); +} + +static void __devexit driver_pci9118_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_pci9118_pci_driver = { + .id_table = pci9118_pci_table, + .probe = &driver_pci9118_pci_probe, + .remove = __devexit_p(&driver_pci9118_pci_remove) +}; + +static int __init driver_pci9118_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_pci9118); + if (retval < 0) + return retval; + + driver_pci9118_pci_driver.name = (char *)driver_pci9118.driver_name; + return pci_register_driver(&driver_pci9118_pci_driver); +} + +static void __exit driver_pci9118_cleanup_module(void) +{ + pci_unregister_driver(&driver_pci9118_pci_driver); + comedi_driver_unregister(&driver_pci9118); +} + +module_init(driver_pci9118_init_module); +module_exit(driver_pci9118_cleanup_module); + +struct pci9118_private { + unsigned long iobase_a; /* base+size for AMCC chip */ + unsigned int master; /* master capable */ + struct pci_dev *pcidev; /* ptr to actual pcidev */ + unsigned int usemux; /* we want to use external multiplexor! */ +#ifdef PCI9118_PARANOIDCHECK + unsigned short chanlist[PCI9118_CHANLEN + 1]; /* + * list of + * scanned channel + */ + unsigned char chanlistlen; /* number of scanlist */ +#endif + unsigned char AdControlReg; /* A/D control register */ + unsigned char IntControlReg; /* Interrupt control register */ + unsigned char AdFunctionReg; /* A/D function register */ + char valid; /* driver is ok */ + char ai_neverending; /* we do unlimited AI */ + unsigned int i8254_osc_base; /* frequence of onboard oscilator */ + unsigned int ai_do; /* what do AI? 0=nothing, 1 to 4 mode */ + unsigned int ai_act_scan; /* how many scans we finished */ + unsigned int ai_buf_ptr; /* data buffer ptr in samples */ + unsigned int ai_n_chan; /* how many channels is measured */ + unsigned int ai_n_scanlen; /* len of actual scanlist */ + unsigned int ai_n_realscanlen; /* + * what we must transfer for one + * outgoing scan include front/back adds + */ + unsigned int ai_act_dmapos; /* position in actual real stream */ + unsigned int ai_add_front; /* + * how many channels we must add + * before scan to satisfy S&H? + */ + unsigned int ai_add_back; /* + * how many channels we must add + * before scan to satisfy DMA? + */ + unsigned int *ai_chanlist; /* actual chanlist */ + unsigned int ai_timer1; + unsigned int ai_timer2; + unsigned int ai_flags; + char ai12_startstop; /* + * measure can start/stop + * on external trigger + */ + unsigned int ai_divisor1, ai_divisor2; /* + * divisors for start of measure + * on external start + */ + unsigned int ai_data_len; + short *ai_data; + short ao_data[2]; /* data output buffer */ + unsigned int ai_scans; /* number of scans to do */ + char dma_doublebuf; /* we can use double buffring */ + unsigned int dma_actbuf; /* which buffer is used now */ + short *dmabuf_virt[2]; /* + * pointers to begin of + * DMA buffer + */ + unsigned long dmabuf_hw[2]; /* hw address of DMA buff */ + unsigned int dmabuf_size[2]; /* + * size of dma buffer in bytes + */ + unsigned int dmabuf_use_size[2]; /* + * which size we may now use + * for transfer + */ + unsigned int dmabuf_used_size[2]; /* which size was truly used */ + unsigned int dmabuf_panic_size[2]; + unsigned int dmabuf_samples[2]; /* size in samples */ + int dmabuf_pages[2]; /* number of pages in buffer */ + unsigned char cnt0_users; /* + * bit field of 8254 CNT0 users + * (0-unused, 1-AO, 2-DI, 3-DO) + */ + unsigned char exttrg_users; /* + * bit field of external trigger + * users(0-AI, 1-AO, 2-DI, 3-DO) + */ + unsigned int cnt0_divisor; /* actual CNT0 divisor */ + void (*int_ai_func) (struct comedi_device *, struct comedi_subdevice *, + unsigned short, + unsigned int, + unsigned short); /* + * ptr to actual interrupt + * AI function + */ + unsigned char ai16bits; /* =1 16 bit card */ + unsigned char usedma; /* =1 use DMA transfer and not INT */ + unsigned char useeoshandle; /* + * =1 change WAKE_EOS DMA transfer + * to fit on every second + */ + unsigned char usessh; /* =1 turn on S&H support */ + int softsshdelay; /* + * >0 use software S&H, + * numer is requested delay in ns + */ + unsigned char softsshsample; /* + * polarity of S&H signal + * in sample state + */ + unsigned char softsshhold; /* + * polarity of S&H signal + * in hold state + */ + unsigned int ai_maskerr; /* which warning was printed */ + unsigned int ai_maskharderr; /* on which error bits stops */ + unsigned int ai_inttrig_start; /* TRIG_INT for start */ +}; + +#define devpriv ((struct pci9118_private *)dev->private) +#define this_board ((struct boardtype *)dev->board_ptr) + +/* +============================================================================== +*/ + +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, int n_chan, + unsigned int *chanlist, int frontadd, + int backadd); +static int setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, int n_chan, + unsigned int *chanlist, int rot, int frontadd, + int backadd, int usedma, char eoshandle); +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2); +static int pci9118_reset(struct comedi_device *dev); +static int pci9118_exttrg_add(struct comedi_device *dev, unsigned char source); +static int pci9118_exttrg_del(struct comedi_device *dev, unsigned char source); +static int pci9118_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void pci9118_calc_divisors(char mode, struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *tim1, unsigned int *tim2, + unsigned int flags, int chans, + unsigned int *div1, unsigned int *div2, + char usessh, unsigned int chnsshfront); + +/* +============================================================================== +*/ +static int pci9118_insn_read_ai(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + int n, timeout; + + devpriv->AdControlReg = AdControl_Int & 0xff; + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + /* + * positive triggers, no S&H, + * no burst, burst stop, + * no post trigger, + * no about trigger, + * trigger stop + */ + + if (!setup_channel_list(dev, s, 1, &insn->chanspec, 0, 0, 0, 0, 0)) + return -EINVAL; + + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + + for (n = 0; n < insn->n; n++) { + outw(0, dev->iobase + PCI9118_SOFTTRG); /* start conversion */ + udelay(2); + timeout = 100; + while (timeout--) { + if (inl(dev->iobase + PCI9118_ADSTAT) & AdStatus_ADrdy) + goto conv_finish; + udelay(1); + } + + comedi_error(dev, "A/D insn timeout"); + data[n] = 0; + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + return -ETIME; + +conv_finish: + if (devpriv->ai16bits) { + data[n] = + (inl(dev->iobase + + PCI9118_AD_DATA) & 0xffff) ^ 0x8000; + } else { + data[n] = + (inw(dev->iobase + PCI9118_AD_DATA) >> 4) & 0xfff; + } + } + + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + return n; + +} + +/* +============================================================================== +*/ +static int pci9118_insn_write_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, chanreg, ch; + + ch = CR_CHAN(insn->chanspec); + if (ch) + chanreg = PCI9118_DA2; + else + chanreg = PCI9118_DA1; + + + for (n = 0; n < insn->n; n++) { + outl(data[n], dev->iobase + chanreg); + devpriv->ao_data[ch] = data[n]; + } + + return n; +} + +/* +============================================================================== +*/ +static int pci9118_insn_read_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, chan; + + chan = CR_CHAN(insn->chanspec); + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_data[chan]; + + return n; +} + +/* +============================================================================== +*/ +static int pci9118_insn_bits_di(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[1] = inl(dev->iobase + PCI9118_DI) & 0xf; + + return 2; +} + +/* +============================================================================== +*/ +static int pci9118_insn_bits_do(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + outl(s->state & 0x0f, dev->iobase + PCI9118_DO); + } + data[1] = s->state; + + return 2; +} + +/* +============================================================================== +*/ +static void interrupt_pci9118_ai_mode4_switch(struct comedi_device *dev) +{ + devpriv->AdFunctionReg = + AdFunction_PDTrg | AdFunction_PETrg | AdFunction_AM; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + outl(0x30, dev->iobase + PCI9118_CNTCTRL); + outl((devpriv->dmabuf_hw[1 - devpriv->dma_actbuf] >> 1) & 0xff, + dev->iobase + PCI9118_CNT0); + outl((devpriv->dmabuf_hw[1 - devpriv->dma_actbuf] >> 9) & 0xff, + dev->iobase + PCI9118_CNT0); + devpriv->AdFunctionReg |= AdFunction_Start; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); +} + +static unsigned int defragment_dma_buffer(struct comedi_device *dev, + struct comedi_subdevice *s, + short *dma_buffer, + unsigned int num_samples) +{ + unsigned int i = 0, j = 0; + unsigned int start_pos = devpriv->ai_add_front, + stop_pos = devpriv->ai_add_front + devpriv->ai_n_chan; + unsigned int raw_scanlen = devpriv->ai_add_front + devpriv->ai_n_chan + + devpriv->ai_add_back; + + for (i = 0; i < num_samples; i++) { + if (devpriv->ai_act_dmapos >= start_pos && + devpriv->ai_act_dmapos < stop_pos) { + dma_buffer[j++] = dma_buffer[i]; + } + devpriv->ai_act_dmapos++; + devpriv->ai_act_dmapos %= raw_scanlen; + } + + return j; +} + +/* +============================================================================== +*/ +static int move_block_from_dma(struct comedi_device *dev, + struct comedi_subdevice *s, + short *dma_buffer, + unsigned int num_samples) +{ + unsigned int num_bytes; + + num_samples = defragment_dma_buffer(dev, s, dma_buffer, num_samples); + devpriv->ai_act_scan += + (s->async->cur_chan + num_samples) / devpriv->ai_n_scanlen; + s->async->cur_chan += num_samples; + s->async->cur_chan %= devpriv->ai_n_scanlen; + num_bytes = + cfc_write_array_to_buffer(s, dma_buffer, + num_samples * sizeof(short)); + if (num_bytes < num_samples * sizeof(short)) + return -1; + return 0; +} + +/* +============================================================================== +*/ +static char pci9118_decode_error_status(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned char m) +{ + if (m & 0x100) { + comedi_error(dev, "A/D FIFO Full status (Fatal Error!)"); + devpriv->ai_maskerr &= ~0x100L; + } + if (m & 0x008) { + comedi_error(dev, + "A/D Burst Mode Overrun Status (Fatal Error!)"); + devpriv->ai_maskerr &= ~0x008L; + } + if (m & 0x004) { + comedi_error(dev, "A/D Over Speed Status (Warning!)"); + devpriv->ai_maskerr &= ~0x004L; + } + if (m & 0x002) { + comedi_error(dev, "A/D Overrun Status (Fatal Error!)"); + devpriv->ai_maskerr &= ~0x002L; + } + if (m & devpriv->ai_maskharderr) { + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + pci9118_ai_cancel(dev, s); + comedi_event(dev, s); + return 1; + } + + return 0; +} + +static void pci9118_ai_munge(struct comedi_device *dev, + struct comedi_subdevice *s, void *data, + unsigned int num_bytes, + unsigned int start_chan_index) +{ + unsigned int i, num_samples = num_bytes / sizeof(short); + short *array = data; + + for (i = 0; i < num_samples; i++) { + if (devpriv->usedma) + array[i] = be16_to_cpu(array[i]); + if (devpriv->ai16bits) + array[i] ^= 0x8000; + else + array[i] = (array[i] >> 4) & 0x0fff; + + } +} + +/* +============================================================================== +*/ +static void interrupt_pci9118_ai_onesample(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short int_adstat, + unsigned int int_amcc, + unsigned short int_daq) +{ + register short sampl; + + s->async->events = 0; + + if (int_adstat & devpriv->ai_maskerr) + if (pci9118_decode_error_status(dev, s, int_adstat)) + return; + + sampl = inw(dev->iobase + PCI9118_AD_DATA); + +#ifdef PCI9118_PARANOIDCHECK + if (devpriv->ai16bits == 0) { + if ((sampl & 0x000f) != devpriv->chanlist[s->async->cur_chan]) { + /* data dropout! */ + printk + ("comedi: A/D SAMPL - data dropout: " + "received channel %d, expected %d!\n", + sampl & 0x000f, + devpriv->chanlist[s->async->cur_chan]); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + pci9118_ai_cancel(dev, s); + comedi_event(dev, s); + return; + } + } +#endif + cfc_write_to_buffer(s, sampl); + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_scanlen) { + /* one scan done */ + s->async->cur_chan %= devpriv->ai_n_scanlen; + devpriv->ai_act_scan++; + if (!(devpriv->ai_neverending)) + if (devpriv->ai_act_scan >= devpriv->ai_scans) { + /* all data sampled */ + pci9118_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + } + } + + if (s->async->events) + comedi_event(dev, s); +} + +/* +============================================================================== +*/ +static void interrupt_pci9118_ai_dma(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned short int_adstat, + unsigned int int_amcc, + unsigned short int_daq) +{ + unsigned int next_dma_buf, samplesinbuf, sampls, m; + + if (int_amcc & MASTER_ABORT_INT) { + comedi_error(dev, "AMCC IRQ - MASTER DMA ABORT!"); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + pci9118_ai_cancel(dev, s); + comedi_event(dev, s); + return; + } + + if (int_amcc & TARGET_ABORT_INT) { + comedi_error(dev, "AMCC IRQ - TARGET DMA ABORT!"); + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + pci9118_ai_cancel(dev, s); + comedi_event(dev, s); + return; + } + if (int_adstat & devpriv->ai_maskerr) + /* if (int_adstat & 0x106) */ + if (pci9118_decode_error_status(dev, s, int_adstat)) + return; + + samplesinbuf = devpriv->dmabuf_use_size[devpriv->dma_actbuf] >> 1; + /* number of received real samples */ +/* DPRINTK("dma_actbuf=%d\n",devpriv->dma_actbuf); */ + + if (devpriv->dma_doublebuf) { /* + * switch DMA buffers if is used + * double buffering + */ + next_dma_buf = 1 - devpriv->dma_actbuf; + outl(devpriv->dmabuf_hw[next_dma_buf], + devpriv->iobase_a + AMCC_OP_REG_MWAR); + outl(devpriv->dmabuf_use_size[next_dma_buf], + devpriv->iobase_a + AMCC_OP_REG_MWTC); + devpriv->dmabuf_used_size[next_dma_buf] = + devpriv->dmabuf_use_size[next_dma_buf]; + if (devpriv->ai_do == 4) + interrupt_pci9118_ai_mode4_switch(dev); + } + + if (samplesinbuf) { + m = devpriv->ai_data_len >> 1; /* + * how many samples is to + * end of buffer + */ +/* + * DPRINTK("samps=%d m=%d %d %d\n", + * samplesinbuf,m,s->async->buf_int_count,s->async->buf_int_ptr); + */ + sampls = m; + move_block_from_dma(dev, s, + devpriv->dmabuf_virt[devpriv->dma_actbuf], + samplesinbuf); + m = m - sampls; /* m= how many samples was transferred */ + } +/* DPRINTK("YYY\n"); */ + + if (!devpriv->ai_neverending) + if (devpriv->ai_act_scan >= devpriv->ai_scans) { + /* all data sampled */ + pci9118_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + } + + if (devpriv->dma_doublebuf) { /* switch dma buffers */ + devpriv->dma_actbuf = 1 - devpriv->dma_actbuf; + } else { /* restart DMA if is not used double buffering */ + outl(devpriv->dmabuf_hw[0], + devpriv->iobase_a + AMCC_OP_REG_MWAR); + outl(devpriv->dmabuf_use_size[0], + devpriv->iobase_a + AMCC_OP_REG_MWTC); + if (devpriv->ai_do == 4) + interrupt_pci9118_ai_mode4_switch(dev); + } + + comedi_event(dev, s); +} + +/* +============================================================================== +*/ +static irqreturn_t interrupt_pci9118(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned int int_daq = 0, int_amcc, int_adstat; + + if (!dev->attached) + return IRQ_NONE; /* not fully initialized */ + + int_daq = inl(dev->iobase + PCI9118_INTSRC) & 0xf; + /* get IRQ reasons from card */ + int_amcc = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* get INT register from AMCC chip */ + +/* + * DPRINTK("INT daq=0x%01x amcc=0x%08x MWAR=0x%08x + * MWTC=0x%08x ADSTAT=0x%02x ai_do=%d\n", + * int_daq, int_amcc, inl(devpriv->iobase_a+AMCC_OP_REG_MWAR), + * inl(devpriv->iobase_a+AMCC_OP_REG_MWTC), + * inw(dev->iobase+PCI9118_ADSTAT)&0x1ff,devpriv->ai_do); + */ + + if ((!int_daq) && (!(int_amcc & ANY_S593X_INT))) + return IRQ_NONE; /* interrupt from other source */ + + outl(int_amcc | 0x00ff0000, devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* shutdown IRQ reasons in AMCC */ + + int_adstat = inw(dev->iobase + PCI9118_ADSTAT) & 0x1ff; + /* get STATUS register */ + + if (devpriv->ai_do) { + if (devpriv->ai12_startstop) + if ((int_adstat & AdStatus_DTH) && + (int_daq & Int_DTrg)) { + /* start stop of measure */ + if (devpriv->ai12_startstop & START_AI_EXT) { + devpriv->ai12_startstop &= + ~START_AI_EXT; + if (!(devpriv->ai12_startstop & + STOP_AI_EXT)) + pci9118_exttrg_del + (dev, EXTTRG_AI); + /* deactivate EXT trigger */ + start_pacer(dev, devpriv->ai_do, + devpriv->ai_divisor1, + devpriv->ai_divisor2); + /* start pacer */ + outl(devpriv->AdControlReg, + dev->iobase + PCI9118_ADCNTRL); + } else { + if (devpriv->ai12_startstop & + STOP_AI_EXT) { + devpriv->ai12_startstop &= + ~STOP_AI_EXT; + pci9118_exttrg_del + (dev, EXTTRG_AI); + /* deactivate EXT trigger */ + devpriv->ai_neverending = 0; + /* + * well, on next interrupt from + * DMA/EOC measure will stop + */ + } + } + } + + (devpriv->int_ai_func) (dev, dev->subdevices + 0, int_adstat, + int_amcc, int_daq); + + } + return IRQ_HANDLED; +} + +/* +============================================================================== +*/ +static int pci9118_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum) +{ + if (trignum != devpriv->ai_inttrig_start) + return -EINVAL; + + devpriv->ai12_startstop &= ~START_AI_INT; + s->async->inttrig = NULL; + + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + if (devpriv->ai_do != 3) { + start_pacer(dev, devpriv->ai_do, devpriv->ai_divisor1, + devpriv->ai_divisor2); + devpriv->AdControlReg |= AdControl_SoftG; + } + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + + return 1; +} + +/* +============================================================================== +*/ +static int pci9118_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + unsigned int divisor1 = 0, divisor2 = 0; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + if (devpriv->master) + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT | TRIG_FOLLOW; + else + cmd->scan_begin_src &= TRIG_FOLLOW; + + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + if (devpriv->master) + cmd->convert_src &= TRIG_TIMER | TRIG_EXT | TRIG_NOW; + else + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE | TRIG_EXT; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: + * make sure trigger sources are + * unique and mutually compatible + */ + + if (cmd->start_src != TRIG_NOW && + cmd->start_src != TRIG_INT && cmd->start_src != TRIG_EXT) { + cmd->start_src = TRIG_NOW; + err++; + } + + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_INT && + cmd->scan_begin_src != TRIG_FOLLOW) { + cmd->scan_begin_src = TRIG_FOLLOW; + err++; + } + + if (cmd->convert_src != TRIG_TIMER && + cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW) { + cmd->convert_src = TRIG_TIMER; + err++; + } + + if (cmd->scan_end_src != TRIG_COUNT) { + cmd->scan_end_src = TRIG_COUNT; + err++; + } + + if (cmd->stop_src != TRIG_NONE && + cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_INT && cmd->stop_src != TRIG_EXT) { + cmd->stop_src = TRIG_COUNT; + err++; + } + + if (cmd->start_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT) { + cmd->start_src = TRIG_NOW; + err++; + } + + if (cmd->start_src == TRIG_INT && cmd->scan_begin_src == TRIG_INT) { + cmd->start_src = TRIG_NOW; + err++; + } + + if ((cmd->scan_begin_src & (TRIG_TIMER | TRIG_EXT)) && + (!(cmd->convert_src & (TRIG_TIMER | TRIG_NOW)))) { + cmd->convert_src = TRIG_TIMER; + err++; + } + + if ((cmd->scan_begin_src == TRIG_FOLLOW) && + (!(cmd->convert_src & (TRIG_TIMER | TRIG_EXT)))) { + cmd->convert_src = TRIG_TIMER; + err++; + } + + if (cmd->stop_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT) { + cmd->stop_src = TRIG_COUNT; + err++; + } + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_src & (TRIG_NOW | TRIG_EXT)) + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src & (TRIG_FOLLOW | TRIG_EXT)) + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + + if ((cmd->scan_begin_src == TRIG_TIMER) && + (cmd->convert_src == TRIG_TIMER) && (cmd->scan_end_arg == 1)) { + cmd->scan_begin_src = TRIG_FOLLOW; + cmd->convert_arg = cmd->scan_begin_arg; + cmd->scan_begin_arg = 0; + } + + if (cmd->scan_begin_src == TRIG_TIMER) + if (cmd->scan_begin_arg < this_board->ai_ns_min) { + cmd->scan_begin_arg = this_board->ai_ns_min; + err++; + } + + if (cmd->scan_begin_src == TRIG_EXT) + if (cmd->scan_begin_arg) { + cmd->scan_begin_arg = 0; + err++; + if (cmd->scan_end_arg > 65535) { + cmd->scan_end_arg = 65535; + err++; + } + } + + if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) + if (cmd->convert_arg < this_board->ai_ns_min) { + cmd->convert_arg = this_board->ai_ns_min; + err++; + } + + if (cmd->convert_src == TRIG_EXT) + if (cmd->convert_arg) { + cmd->convert_arg = 0; + err++; + } + + if (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (!cmd->chanlist_len) { + cmd->chanlist_len = 1; + err++; + } + + if (cmd->chanlist_len > this_board->n_aichanlist) { + cmd->chanlist_len = this_board->n_aichanlist; + err++; + } + + if (cmd->scan_end_arg < cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if ((cmd->scan_end_arg % cmd->chanlist_len)) { + cmd->scan_end_arg = + cmd->chanlist_len * (cmd->scan_end_arg / cmd->chanlist_len); + err++; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; +/* printk("S1 timer1=%u timer2=%u\n",cmd->scan_begin_arg,cmd->convert_arg); */ + i8253_cascade_ns_to_timer(devpriv->i8254_osc_base, &divisor1, + &divisor2, &cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); +/* printk("S2 timer1=%u timer2=%u\n",cmd->scan_begin_arg,cmd->convert_arg); */ + if (cmd->scan_begin_arg < this_board->ai_ns_min) + cmd->scan_begin_arg = this_board->ai_ns_min; + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) { + tmp = cmd->convert_arg; + i8253_cascade_ns_to_timer(devpriv->i8254_osc_base, &divisor1, + &divisor2, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); +/* printk("s1 timer1=%u timer2=%u\n",cmd->scan_begin_arg,cmd->convert_arg); */ + if (cmd->convert_arg < this_board->ai_ns_min) + cmd->convert_arg = this_board->ai_ns_min; + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER + && cmd->convert_src == TRIG_NOW) { + if (cmd->convert_arg == 0) { + if (cmd->scan_begin_arg < + this_board->ai_ns_min * + (cmd->scan_end_arg + 2)) { + cmd->scan_begin_arg = + this_board->ai_ns_min * + (cmd->scan_end_arg + 2); +/* printk("s2 timer1=%u timer2=%u\n",cmd->scan_begin_arg,cmd->convert_arg); */ + err++; + } + } else { + if (cmd->scan_begin_arg < + cmd->convert_arg * cmd->chanlist_len) { + cmd->scan_begin_arg = + cmd->convert_arg * + cmd->chanlist_len; +/* printk("s3 timer1=%u timer2=%u\n",cmd->scan_begin_arg,cmd->convert_arg); */ + err++; + } + } + } + } + + if (err) + return 4; + + if (cmd->chanlist) + if (!check_channel_list(dev, s, cmd->chanlist_len, + cmd->chanlist, 0, 0)) + return 5; /* incorrect channels list */ + + return 0; +} + +/* +============================================================================== +*/ +static int Compute_and_setup_dma(struct comedi_device *dev) +{ + unsigned int dmalen0, dmalen1, i; + + DPRINTK("adl_pci9118 EDBG: BGN: Compute_and_setup_dma()\n"); + dmalen0 = devpriv->dmabuf_size[0]; + dmalen1 = devpriv->dmabuf_size[1]; + DPRINTK("1 dmalen0=%d dmalen1=%d ai_data_len=%d\n", dmalen0, dmalen1, + devpriv->ai_data_len); + /* isn't output buff smaller that our DMA buff? */ + if (dmalen0 > (devpriv->ai_data_len)) { + dmalen0 = devpriv->ai_data_len & ~3L; /* + * align to 32bit down + */ + } + if (dmalen1 > (devpriv->ai_data_len)) { + dmalen1 = devpriv->ai_data_len & ~3L; /* + * align to 32bit down + */ + } + DPRINTK("2 dmalen0=%d dmalen1=%d\n", dmalen0, dmalen1); + + /* we want wake up every scan? */ + if (devpriv->ai_flags & TRIG_WAKE_EOS) { + if (dmalen0 < (devpriv->ai_n_realscanlen << 1)) { + /* uff, too short DMA buffer, disable EOS support! */ + devpriv->ai_flags &= (~TRIG_WAKE_EOS); + printk + ("comedi%d: WAR: DMA0 buf too short, can't " + "support TRIG_WAKE_EOS (%d<%d)\n", + dev->minor, dmalen0, + devpriv->ai_n_realscanlen << 1); + } else { + /* short first DMA buffer to one scan */ + dmalen0 = devpriv->ai_n_realscanlen << 1; + DPRINTK + ("21 dmalen0=%d ai_n_realscanlen=%d " + "useeoshandle=%d\n", + dmalen0, devpriv->ai_n_realscanlen, + devpriv->useeoshandle); + if (devpriv->useeoshandle) + dmalen0 += 2; + if (dmalen0 < 4) { + printk + ("comedi%d: ERR: DMA0 buf len bug? " + "(%d<4)\n", + dev->minor, dmalen0); + dmalen0 = 4; + } + } + } + if (devpriv->ai_flags & TRIG_WAKE_EOS) { + if (dmalen1 < (devpriv->ai_n_realscanlen << 1)) { + /* uff, too short DMA buffer, disable EOS support! */ + devpriv->ai_flags &= (~TRIG_WAKE_EOS); + printk + ("comedi%d: WAR: DMA1 buf too short, " + "can't support TRIG_WAKE_EOS (%d<%d)\n", + dev->minor, dmalen1, + devpriv->ai_n_realscanlen << 1); + } else { + /* short second DMA buffer to one scan */ + dmalen1 = devpriv->ai_n_realscanlen << 1; + DPRINTK + ("22 dmalen1=%d ai_n_realscanlen=%d " + "useeoshandle=%d\n", + dmalen1, devpriv->ai_n_realscanlen, + devpriv->useeoshandle); + if (devpriv->useeoshandle) + dmalen1 -= 2; + if (dmalen1 < 4) { + printk + ("comedi%d: ERR: DMA1 buf len bug? " + "(%d<4)\n", + dev->minor, dmalen1); + dmalen1 = 4; + } + } + } + + DPRINTK("3 dmalen0=%d dmalen1=%d\n", dmalen0, dmalen1); + /* transfer without TRIG_WAKE_EOS */ + if (!(devpriv->ai_flags & TRIG_WAKE_EOS)) { + /* if it's possible then align DMA buffers to length of scan */ + i = dmalen0; + dmalen0 = + (dmalen0 / (devpriv->ai_n_realscanlen << 1)) * + (devpriv->ai_n_realscanlen << 1); + dmalen0 &= ~3L; + if (!dmalen0) + dmalen0 = i; /* uff. very long scan? */ + i = dmalen1; + dmalen1 = + (dmalen1 / (devpriv->ai_n_realscanlen << 1)) * + (devpriv->ai_n_realscanlen << 1); + dmalen1 &= ~3L; + if (!dmalen1) + dmalen1 = i; /* uff. very long scan? */ + /* + * if measure isn't neverending then test, if it fits whole + * into one or two DMA buffers + */ + if (!devpriv->ai_neverending) { + /* fits whole measure into one DMA buffer? */ + if (dmalen0 > + ((devpriv->ai_n_realscanlen << 1) * + devpriv->ai_scans)) { + DPRINTK + ("3.0 ai_n_realscanlen=%d ai_scans=%d\n", + devpriv->ai_n_realscanlen, + devpriv->ai_scans); + dmalen0 = + (devpriv->ai_n_realscanlen << 1) * + devpriv->ai_scans; + DPRINTK("3.1 dmalen0=%d dmalen1=%d\n", dmalen0, + dmalen1); + dmalen0 &= ~3L; + } else { /* + * fits whole measure into + * two DMA buffer? + */ + if (dmalen1 > + ((devpriv->ai_n_realscanlen << 1) * + devpriv->ai_scans - dmalen0)) + dmalen1 = + (devpriv->ai_n_realscanlen << 1) * + devpriv->ai_scans - dmalen0; + DPRINTK("3.2 dmalen0=%d dmalen1=%d\n", dmalen0, + dmalen1); + dmalen1 &= ~3L; + } + } + } + + DPRINTK("4 dmalen0=%d dmalen1=%d\n", dmalen0, dmalen1); + + /* these DMA buffer size will be used */ + devpriv->dma_actbuf = 0; + devpriv->dmabuf_use_size[0] = dmalen0; + devpriv->dmabuf_use_size[1] = dmalen1; + + DPRINTK("5 dmalen0=%d dmalen1=%d\n", dmalen0, dmalen1); +#if 0 + if (devpriv->ai_n_scanlen < this_board->half_fifo_size) { + devpriv->dmabuf_panic_size[0] = + (this_board->half_fifo_size / devpriv->ai_n_scanlen + + 1) * devpriv->ai_n_scanlen * sizeof(short); + devpriv->dmabuf_panic_size[1] = + (this_board->half_fifo_size / devpriv->ai_n_scanlen + + 1) * devpriv->ai_n_scanlen * sizeof(short); + } else { + devpriv->dmabuf_panic_size[0] = + (devpriv->ai_n_scanlen << 1) % devpriv->dmabuf_size[0]; + devpriv->dmabuf_panic_size[1] = + (devpriv->ai_n_scanlen << 1) % devpriv->dmabuf_size[1]; + } +#endif + + outl(inl(devpriv->iobase_a + AMCC_OP_REG_MCSR) & (~EN_A2P_TRANSFERS), + devpriv->iobase_a + AMCC_OP_REG_MCSR); /* stop DMA */ + outl(devpriv->dmabuf_hw[0], devpriv->iobase_a + AMCC_OP_REG_MWAR); + outl(devpriv->dmabuf_use_size[0], devpriv->iobase_a + AMCC_OP_REG_MWTC); + /* init DMA transfer */ + outl(0x00000000 | AINT_WRITE_COMPL, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); +/* outl(0x02000000|AINT_WRITE_COMPL, devpriv->iobase_a+AMCC_OP_REG_INTCSR); */ + + outl(inl(devpriv->iobase_a + + AMCC_OP_REG_MCSR) | RESET_A2P_FLAGS | A2P_HI_PRIORITY | + EN_A2P_TRANSFERS, devpriv->iobase_a + AMCC_OP_REG_MCSR); + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | EN_A2P_TRANSFERS, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* allow bus mastering */ + + DPRINTK("adl_pci9118 EDBG: END: Compute_and_setup_dma()\n"); + return 0; +} + +/* +============================================================================== +*/ +static int pci9118_ai_docmd_sampl(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + DPRINTK("adl_pci9118 EDBG: BGN: pci9118_ai_docmd_sampl(%d,) [%d]\n", + dev->minor, devpriv->ai_do); + switch (devpriv->ai_do) { + case 1: + devpriv->AdControlReg |= AdControl_TmrTr; + break; + case 2: + comedi_error(dev, "pci9118_ai_docmd_sampl() mode 2 bug!\n"); + return -EIO; + case 3: + devpriv->AdControlReg |= AdControl_ExtM; + break; + case 4: + comedi_error(dev, "pci9118_ai_docmd_sampl() mode 4 bug!\n"); + return -EIO; + default: + comedi_error(dev, + "pci9118_ai_docmd_sampl() mode number bug!\n"); + return -EIO; + } + + devpriv->int_ai_func = interrupt_pci9118_ai_onesample; + /* transfer function */ + + if (devpriv->ai12_startstop) + pci9118_exttrg_add(dev, EXTTRG_AI); + /* activate EXT trigger */ + + if ((devpriv->ai_do == 1) || (devpriv->ai_do == 2)) + devpriv->IntControlReg |= Int_Timer; + + devpriv->AdControlReg |= AdControl_Int; + + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | 0x1f00, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* allow INT in AMCC */ + + if (!(devpriv->ai12_startstop & (START_AI_EXT | START_AI_INT))) { + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + if (devpriv->ai_do != 3) { + start_pacer(dev, devpriv->ai_do, devpriv->ai_divisor1, + devpriv->ai_divisor2); + devpriv->AdControlReg |= AdControl_SoftG; + } + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + } + + DPRINTK("adl_pci9118 EDBG: END: pci9118_ai_docmd_sampl()\n"); + return 0; +} + +/* +============================================================================== +*/ +static int pci9118_ai_docmd_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + DPRINTK("adl_pci9118 EDBG: BGN: pci9118_ai_docmd_dma(%d,) [%d,%d]\n", + dev->minor, devpriv->ai_do, devpriv->usedma); + Compute_and_setup_dma(dev); + + switch (devpriv->ai_do) { + case 1: + devpriv->AdControlReg |= + ((AdControl_TmrTr | AdControl_Dma) & 0xff); + break; + case 2: + devpriv->AdControlReg |= + ((AdControl_TmrTr | AdControl_Dma) & 0xff); + devpriv->AdFunctionReg = + AdFunction_PDTrg | AdFunction_PETrg | AdFunction_BM | + AdFunction_BS; + if (devpriv->usessh && (!devpriv->softsshdelay)) + devpriv->AdFunctionReg |= AdFunction_BSSH; + outl(devpriv->ai_n_realscanlen, dev->iobase + PCI9118_BURST); + break; + case 3: + devpriv->AdControlReg |= + ((AdControl_ExtM | AdControl_Dma) & 0xff); + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + break; + case 4: + devpriv->AdControlReg |= + ((AdControl_TmrTr | AdControl_Dma) & 0xff); + devpriv->AdFunctionReg = + AdFunction_PDTrg | AdFunction_PETrg | AdFunction_AM; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + outl(0x30, dev->iobase + PCI9118_CNTCTRL); + outl((devpriv->dmabuf_hw[0] >> 1) & 0xff, + dev->iobase + PCI9118_CNT0); + outl((devpriv->dmabuf_hw[0] >> 9) & 0xff, + dev->iobase + PCI9118_CNT0); + devpriv->AdFunctionReg |= AdFunction_Start; + break; + default: + comedi_error(dev, "pci9118_ai_docmd_dma() mode number bug!\n"); + return -EIO; + } + + if (devpriv->ai12_startstop) { + pci9118_exttrg_add(dev, EXTTRG_AI); + /* activate EXT trigger */ + } + + devpriv->int_ai_func = interrupt_pci9118_ai_dma; + /* transfer function */ + + outl(0x02000000 | AINT_WRITE_COMPL, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + + if (!(devpriv->ai12_startstop & (START_AI_EXT | START_AI_INT))) { + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + if (devpriv->ai_do != 3) { + start_pacer(dev, devpriv->ai_do, devpriv->ai_divisor1, + devpriv->ai_divisor2); + devpriv->AdControlReg |= AdControl_SoftG; + } + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + } + + DPRINTK("adl_pci9118 EDBG: BGN: pci9118_ai_docmd_dma()\n"); + return 0; +} + +/* +============================================================================== +*/ +static int pci9118_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int addchans = 0; + int ret = 0; + + DPRINTK("adl_pci9118 EDBG: BGN: pci9118_ai_cmd(%d,)\n", dev->minor); + devpriv->ai12_startstop = 0; + devpriv->ai_flags = cmd->flags; + devpriv->ai_n_chan = cmd->chanlist_len; + devpriv->ai_n_scanlen = cmd->scan_end_arg; + devpriv->ai_chanlist = cmd->chanlist; + devpriv->ai_data = s->async->prealloc_buf; + devpriv->ai_data_len = s->async->prealloc_bufsz; + devpriv->ai_timer1 = 0; + devpriv->ai_timer2 = 0; + devpriv->ai_add_front = 0; + devpriv->ai_add_back = 0; + devpriv->ai_maskerr = 0x10e; + + /* prepare for start/stop conditions */ + if (cmd->start_src == TRIG_EXT) + devpriv->ai12_startstop |= START_AI_EXT; + if (cmd->stop_src == TRIG_EXT) { + devpriv->ai_neverending = 1; + devpriv->ai12_startstop |= STOP_AI_EXT; + } + if (cmd->start_src == TRIG_INT) { + devpriv->ai12_startstop |= START_AI_INT; + devpriv->ai_inttrig_start = cmd->start_arg; + s->async->inttrig = pci9118_ai_inttrig; + } +#if 0 + if (cmd->stop_src == TRIG_INT) { + devpriv->ai_neverending = 1; + devpriv->ai12_startstop |= STOP_AI_INT; + } +#endif + if (cmd->stop_src == TRIG_NONE) + devpriv->ai_neverending = 1; + if (cmd->stop_src == TRIG_COUNT) { + devpriv->ai_scans = cmd->stop_arg; + devpriv->ai_neverending = 0; + } else { + devpriv->ai_scans = 0; + } + + /* use sample&hold signal? */ + if (cmd->convert_src == TRIG_NOW) + devpriv->usessh = 1; + /* yes */ + else + devpriv->usessh = 0; + /* no */ + + DPRINTK("1 neverending=%d scans=%u usessh=%d ai_startstop=0x%2x\n", + devpriv->ai_neverending, devpriv->ai_scans, devpriv->usessh, + devpriv->ai12_startstop); + + /* + * use additional sample at end of every scan + * to satisty DMA 32 bit transfer? + */ + devpriv->ai_add_front = 0; + devpriv->ai_add_back = 0; + devpriv->useeoshandle = 0; + if (devpriv->master) { + devpriv->usedma = 1; + if ((cmd->flags & TRIG_WAKE_EOS) && + (devpriv->ai_n_scanlen == 1)) { + if (cmd->convert_src == TRIG_NOW) + devpriv->ai_add_back = 1; + if (cmd->convert_src == TRIG_TIMER) { + devpriv->usedma = 0; + /* + * use INT transfer if scanlist + * have only one channel + */ + } + } + if ((cmd->flags & TRIG_WAKE_EOS) && + (devpriv->ai_n_scanlen & 1) && + (devpriv->ai_n_scanlen > 1)) { + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* + * vpriv->useeoshandle=1; // change DMA transfer + * block to fit EOS on every second call + */ + devpriv->usedma = 0; + /* + * XXX maybe can be corrected to use 16 bit DMA + */ + } else { /* + * well, we must insert one sample + * to end of EOS to meet 32 bit transfer + */ + devpriv->ai_add_back = 1; + } + } + } else { /* interrupt transfer don't need any correction */ + devpriv->usedma = 0; + } + + /* + * we need software S&H signal? + * It adds two samples before every scan as minimum + */ + if (devpriv->usessh && devpriv->softsshdelay) { + devpriv->ai_add_front = 2; + if ((devpriv->usedma == 1) && (devpriv->ai_add_back == 1)) { + /* move it to front */ + devpriv->ai_add_front++; + devpriv->ai_add_back = 0; + } + if (cmd->convert_arg < this_board->ai_ns_min) + cmd->convert_arg = this_board->ai_ns_min; + addchans = devpriv->softsshdelay / cmd->convert_arg; + if (devpriv->softsshdelay % cmd->convert_arg) + addchans++; + if (addchans > (devpriv->ai_add_front - 1)) { + /* uff, still short */ + devpriv->ai_add_front = addchans + 1; + if (devpriv->usedma == 1) + if ((devpriv->ai_add_front + + devpriv->ai_n_chan + + devpriv->ai_add_back) & 1) + devpriv->ai_add_front++; + /* round up to 32 bit */ + } + } + /* well, we now know what must be all added */ + devpriv->ai_n_realscanlen = /* + * what we must take from card in real + * to have ai_n_scanlen on output? + */ + (devpriv->ai_add_front + devpriv->ai_n_chan + + devpriv->ai_add_back) * (devpriv->ai_n_scanlen / + devpriv->ai_n_chan); + + DPRINTK("2 usedma=%d realscan=%d af=%u n_chan=%d ab=%d n_scanlen=%d\n", + devpriv->usedma, + devpriv->ai_n_realscanlen, devpriv->ai_add_front, + devpriv->ai_n_chan, devpriv->ai_add_back, + devpriv->ai_n_scanlen); + + /* check and setup channel list */ + if (!check_channel_list(dev, s, devpriv->ai_n_chan, + devpriv->ai_chanlist, devpriv->ai_add_front, + devpriv->ai_add_back)) + return -EINVAL; + if (!setup_channel_list(dev, s, devpriv->ai_n_chan, + devpriv->ai_chanlist, 0, devpriv->ai_add_front, + devpriv->ai_add_back, devpriv->usedma, + devpriv->useeoshandle)) + return -EINVAL; + + /* compute timers settings */ + /* + * simplest way, fr=4Mhz/(tim1*tim2), + * channel manipulation without timers effect + */ + if (((cmd->scan_begin_src == TRIG_FOLLOW) || + (cmd->scan_begin_src == TRIG_EXT) || + (cmd->scan_begin_src == TRIG_INT)) && + (cmd->convert_src == TRIG_TIMER)) { + /* both timer is used for one time */ + if (cmd->scan_begin_src == TRIG_EXT) + devpriv->ai_do = 4; + else + devpriv->ai_do = 1; + pci9118_calc_divisors(devpriv->ai_do, dev, s, + &cmd->scan_begin_arg, &cmd->convert_arg, + devpriv->ai_flags, + devpriv->ai_n_realscanlen, + &devpriv->ai_divisor1, + &devpriv->ai_divisor2, devpriv->usessh, + devpriv->ai_add_front); + devpriv->ai_timer2 = cmd->convert_arg; + } + + if ((cmd->scan_begin_src == TRIG_TIMER) && + ((cmd->convert_src == TRIG_TIMER) || + (cmd->convert_src == TRIG_NOW))) { + /* double timed action */ + if (!devpriv->usedma) { + comedi_error(dev, + "cmd->scan_begin_src=TRIG_TIMER works " + "only with bus mastering!"); + return -EIO; + } + + devpriv->ai_do = 2; + pci9118_calc_divisors(devpriv->ai_do, dev, s, + &cmd->scan_begin_arg, &cmd->convert_arg, + devpriv->ai_flags, + devpriv->ai_n_realscanlen, + &devpriv->ai_divisor1, + &devpriv->ai_divisor2, devpriv->usessh, + devpriv->ai_add_front); + devpriv->ai_timer1 = cmd->scan_begin_arg; + devpriv->ai_timer2 = cmd->convert_arg; + } + + if ((cmd->scan_begin_src == TRIG_FOLLOW) + && (cmd->convert_src == TRIG_EXT)) { + devpriv->ai_do = 3; + } + + start_pacer(dev, -1, 0, 0); /* stop pacer */ + + devpriv->AdControlReg = 0; /* + * bipolar, S.E., use 8254, stop 8354, + * internal trigger, soft trigger, + * disable DMA + */ + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + /* + * positive triggers, no S&H, no burst, + * burst stop, no post trigger, + * no about trigger, trigger stop + */ + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + udelay(1); + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + inl(dev->iobase + PCI9118_ADSTAT); /* + * flush A/D and INT + * status register + */ + inl(dev->iobase + PCI9118_INTSRC); + + devpriv->ai_act_scan = 0; + devpriv->ai_act_dmapos = 0; + s->async->cur_chan = 0; + devpriv->ai_buf_ptr = 0; + + if (devpriv->usedma) + ret = pci9118_ai_docmd_dma(dev, s); + else + ret = pci9118_ai_docmd_sampl(dev, s); + + DPRINTK("adl_pci9118 EDBG: END: pci9118_ai_cmd()\n"); + return ret; +} + +/* +============================================================================== +*/ +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, int n_chan, + unsigned int *chanlist, int frontadd, int backadd) +{ + unsigned int i, differencial = 0, bipolar = 0; + + /* correct channel and range number check itself comedi/range.c */ + if (n_chan < 1) { + comedi_error(dev, "range/channel list is empty!"); + return 0; + } + if ((frontadd + n_chan + backadd) > s->len_chanlist) { + printk + ("comedi%d: range/channel list is too long for " + "actual configuration (%d>%d)!", + dev->minor, n_chan, s->len_chanlist - frontadd - backadd); + return 0; + } + + if (CR_AREF(chanlist[0]) == AREF_DIFF) + differencial = 1; /* all input must be diff */ + if (CR_RANGE(chanlist[0]) < PCI9118_BIPOLAR_RANGES) + bipolar = 1; /* all input must be bipolar */ + if (n_chan > 1) + for (i = 1; i < n_chan; i++) { /* check S.E/diff */ + if ((CR_AREF(chanlist[i]) == AREF_DIFF) != + (differencial)) { + comedi_error(dev, + "Differencial and single ended " + "inputs can't be mixtured!"); + return 0; + } + if ((CR_RANGE(chanlist[i]) < PCI9118_BIPOLAR_RANGES) != + (bipolar)) { + comedi_error(dev, + "Bipolar and unipolar ranges " + "can't be mixtured!"); + return 0; + } + if ((!devpriv->usemux) & (differencial) & + (CR_CHAN(chanlist[i]) >= this_board->n_aichand)) { + comedi_error(dev, + "If AREF_DIFF is used then is " + "available only first 8 channels!"); + return 0; + } + } + + return 1; +} + +/* +============================================================================== +*/ +static int setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, int n_chan, + unsigned int *chanlist, int rot, int frontadd, + int backadd, int usedma, char useeos) +{ + unsigned int i, differencial = 0, bipolar = 0; + unsigned int scanquad, gain, ssh = 0x00; + + DPRINTK + ("adl_pci9118 EDBG: BGN: setup_channel_list" + "(%d,.,%d,.,%d,%d,%d,%d)\n", + dev->minor, n_chan, rot, frontadd, backadd, usedma); + + if (usedma == 1) { + rot = 8; + usedma = 0; + } + + if (CR_AREF(chanlist[0]) == AREF_DIFF) + differencial = 1; /* all input must be diff */ + if (CR_RANGE(chanlist[0]) < PCI9118_BIPOLAR_RANGES) + bipolar = 1; /* all input must be bipolar */ + + /* All is ok, so we can setup channel/range list */ + + if (!bipolar) { + devpriv->AdControlReg |= AdControl_UniP; + /* set unibipolar */ + } else { + devpriv->AdControlReg &= ((~AdControl_UniP) & 0xff); + /* enable bipolar */ + } + + if (differencial) { + devpriv->AdControlReg |= AdControl_Diff; + /* enable diff inputs */ + } else { + devpriv->AdControlReg &= ((~AdControl_Diff) & 0xff); + /* set single ended inputs */ + } + + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + /* setup mode */ + + outl(2, dev->iobase + PCI9118_SCANMOD); + /* gods know why this sequence! */ + outl(0, dev->iobase + PCI9118_SCANMOD); + outl(1, dev->iobase + PCI9118_SCANMOD); + +#ifdef PCI9118_PARANOIDCHECK + devpriv->chanlistlen = n_chan; + for (i = 0; i < (PCI9118_CHANLEN + 1); i++) + devpriv->chanlist[i] = 0x55aa; +#endif + + if (frontadd) { /* insert channels for S&H */ + ssh = devpriv->softsshsample; + DPRINTK("FA: %04x: ", ssh); + for (i = 0; i < frontadd; i++) { + /* store range list to card */ + scanquad = CR_CHAN(chanlist[0]); + /* get channel number; */ + gain = CR_RANGE(chanlist[0]); + /* get gain number */ + scanquad |= ((gain & 0x03) << 8); + outl(scanquad | ssh, dev->iobase + PCI9118_GAIN); + DPRINTK("%02x ", scanquad | ssh); + ssh = devpriv->softsshhold; + } + DPRINTK("\n "); + } + + DPRINTK("SL: ", ssh); + for (i = 0; i < n_chan; i++) { /* store range list to card */ + scanquad = CR_CHAN(chanlist[i]); /* get channel number */ +#ifdef PCI9118_PARANOIDCHECK + devpriv->chanlist[i ^ usedma] = (scanquad & 0xf) << rot; +#endif + gain = CR_RANGE(chanlist[i]); /* get gain number */ + scanquad |= ((gain & 0x03) << 8); + outl(scanquad | ssh, dev->iobase + PCI9118_GAIN); + DPRINTK("%02x ", scanquad | ssh); + } + DPRINTK("\n "); + + if (backadd) { /* insert channels for fit onto 32bit DMA */ + DPRINTK("BA: %04x: ", ssh); + for (i = 0; i < backadd; i++) { /* store range list to card */ + scanquad = CR_CHAN(chanlist[0]); + /* get channel number */ + gain = CR_RANGE(chanlist[0]); /* get gain number */ + scanquad |= ((gain & 0x03) << 8); + outl(scanquad | ssh, dev->iobase + PCI9118_GAIN); + DPRINTK("%02x ", scanquad | ssh); + } + DPRINTK("\n "); + } +#ifdef PCI9118_PARANOIDCHECK + devpriv->chanlist[n_chan ^ usedma] = devpriv->chanlist[0 ^ usedma]; + /* for 32bit operations */ + if (useeos) { + for (i = 1; i < n_chan; i++) { /* store range list to card */ + devpriv->chanlist[(n_chan + i) ^ usedma] = + (CR_CHAN(chanlist[i]) & 0xf) << rot; + } + devpriv->chanlist[(2 * n_chan) ^ usedma] = + devpriv->chanlist[0 ^ usedma]; + /* for 32bit operations */ + useeos = 2; + } else { + useeos = 1; + } +#ifdef PCI9118_EXTDEBUG + DPRINTK("CHL: "); + for (i = 0; i <= (useeos * n_chan); i++) + DPRINTK("%04x ", devpriv->chanlist[i]); + + DPRINTK("\n "); +#endif +#endif + outl(0, dev->iobase + PCI9118_SCANMOD); /* close scan queue */ + /* udelay(100); important delay, or first sample will be crippled */ + + DPRINTK("adl_pci9118 EDBG: END: setup_channel_list()\n"); + return 1; /* we can serve this with scan logic */ +} + +/* +============================================================================== + calculate 8254 divisors if they are used for dual timing +*/ +static void pci9118_calc_divisors(char mode, struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *tim1, unsigned int *tim2, + unsigned int flags, int chans, + unsigned int *div1, unsigned int *div2, + char usessh, unsigned int chnsshfront) +{ + DPRINTK + ("adl_pci9118 EDBG: BGN: pci9118_calc_divisors" + "(%d,%d,.,%u,%u,%u,%d,.,.,,%u,%u)\n", + mode, dev->minor, *tim1, *tim2, flags, chans, usessh, chnsshfront); + switch (mode) { + case 1: + case 4: + if (*tim2 < this_board->ai_ns_min) + *tim2 = this_board->ai_ns_min; + i8253_cascade_ns_to_timer(devpriv->i8254_osc_base, div1, div2, + tim2, flags & TRIG_ROUND_NEAREST); + DPRINTK("OSC base=%u div1=%u div2=%u timer1=%u\n", + devpriv->i8254_osc_base, *div1, *div2, *tim1); + break; + case 2: + if (*tim2 < this_board->ai_ns_min) + *tim2 = this_board->ai_ns_min; + DPRINTK("1 div1=%u div2=%u timer1=%u timer2=%u\n", *div1, *div2, + *tim1, *tim2); + *div1 = *tim2 / devpriv->i8254_osc_base; + /* convert timer (burst) */ + DPRINTK("2 div1=%u div2=%u timer1=%u timer2=%u\n", *div1, *div2, + *tim1, *tim2); + if (*div1 < this_board->ai_pacer_min) + *div1 = this_board->ai_pacer_min; + DPRINTK("3 div1=%u div2=%u timer1=%u timer2=%u\n", *div1, *div2, + *tim1, *tim2); + *div2 = *tim1 / devpriv->i8254_osc_base; /* scan timer */ + DPRINTK("4 div1=%u div2=%u timer1=%u timer2=%u\n", *div1, *div2, + *tim1, *tim2); + *div2 = *div2 / *div1; /* major timer is c1*c2 */ + DPRINTK("5 div1=%u div2=%u timer1=%u timer2=%u\n", *div1, *div2, + *tim1, *tim2); + if (*div2 < chans) + *div2 = chans; + DPRINTK("6 div1=%u div2=%u timer1=%u timer2=%u\n", *div1, *div2, + *tim1, *tim2); + + *tim2 = *div1 * devpriv->i8254_osc_base; + /* real convert timer */ + + if (usessh & (chnsshfront == 0)) /* use BSSH signal */ + if (*div2 < (chans + 2)) + *div2 = chans + 2; + + DPRINTK("7 div1=%u div2=%u timer1=%u timer2=%u\n", *div1, *div2, + *tim1, *tim2); + *tim1 = *div1 * *div2 * devpriv->i8254_osc_base; + DPRINTK("OSC base=%u div1=%u div2=%u timer1=%u timer2=%u\n", + devpriv->i8254_osc_base, *div1, *div2, *tim1, *tim2); + break; + } + DPRINTK("adl_pci9118 EDBG: END: pci9118_calc_divisors(%u,%u)\n", + *div1, *div2); +} + +/* +============================================================================== +*/ +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2) +{ + outl(0x74, dev->iobase + PCI9118_CNTCTRL); + outl(0xb4, dev->iobase + PCI9118_CNTCTRL); +/* outl(0x30, dev->iobase + PCI9118_CNTCTRL); */ + udelay(1); + + if ((mode == 1) || (mode == 2) || (mode == 4)) { + outl(divisor2 & 0xff, dev->iobase + PCI9118_CNT2); + outl((divisor2 >> 8) & 0xff, dev->iobase + PCI9118_CNT2); + outl(divisor1 & 0xff, dev->iobase + PCI9118_CNT1); + outl((divisor1 >> 8) & 0xff, dev->iobase + PCI9118_CNT1); + } +} + +/* +============================================================================== +*/ +static int pci9118_exttrg_add(struct comedi_device *dev, unsigned char source) +{ + if (source > 3) + return -1; /* incorrect source */ + devpriv->exttrg_users |= (1 << source); + devpriv->IntControlReg |= Int_DTrg; + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | 0x1f00, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* allow INT in AMCC */ + return 0; +} + +/* +============================================================================== +*/ +static int pci9118_exttrg_del(struct comedi_device *dev, unsigned char source) +{ + if (source > 3) + return -1; /* incorrect source */ + devpriv->exttrg_users &= ~(1 << source); + if (!devpriv->exttrg_users) { /* shutdown ext trg intterrupts */ + devpriv->IntControlReg &= ~Int_DTrg; + if (!devpriv->IntControlReg) /* all IRQ disabled */ + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) & + (~0x00001f00), + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* disable int in AMCC */ + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + } + return 0; +} + +/* +============================================================================== +*/ +static int pci9118_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + if (devpriv->usedma) + outl(inl(devpriv->iobase_a + AMCC_OP_REG_MCSR) & + (~EN_A2P_TRANSFERS), + devpriv->iobase_a + AMCC_OP_REG_MCSR); /* stop DMA */ + pci9118_exttrg_del(dev, EXTTRG_AI); + start_pacer(dev, 0, 0, 0); /* stop 8254 counters */ + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + /* + * positive triggers, no S&H, no burst, + * burst stop, no post trigger, + * no about trigger, trigger stop + */ + devpriv->AdControlReg = 0x00; + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + /* + * bipolar, S.E., use 8254, stop 8354, + * internal trigger, soft trigger, + * disable INT and DMA + */ + outl(0, dev->iobase + PCI9118_BURST); + outl(1, dev->iobase + PCI9118_SCANMOD); + outl(2, dev->iobase + PCI9118_SCANMOD); /* reset scan queue */ + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + + devpriv->ai_do = 0; + devpriv->usedma = 0; + + devpriv->ai_act_scan = 0; + devpriv->ai_act_dmapos = 0; + s->async->cur_chan = 0; + s->async->inttrig = NULL; + devpriv->ai_buf_ptr = 0; + devpriv->ai_neverending = 0; + devpriv->dma_actbuf = 0; + + if (!devpriv->IntControlReg) + outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | 0x1f00, + devpriv->iobase_a + AMCC_OP_REG_INTCSR); + /* allow INT in AMCC */ + + return 0; +} + +/* +============================================================================== +*/ +static int pci9118_reset(struct comedi_device *dev) +{ + devpriv->IntControlReg = 0; + devpriv->exttrg_users = 0; + inl(dev->iobase + PCI9118_INTCTRL); + outl(devpriv->IntControlReg, dev->iobase + PCI9118_INTCTRL); + /* disable interrupts source */ + outl(0x30, dev->iobase + PCI9118_CNTCTRL); +/* outl(0xb4, dev->iobase + PCI9118_CNTCTRL); */ + start_pacer(dev, 0, 0, 0); /* stop 8254 counters */ + devpriv->AdControlReg = 0; + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + /* + * bipolar, S.E., use 8254, + * stop 8354, internal trigger, + * soft trigger, + * disable INT and DMA + */ + outl(0, dev->iobase + PCI9118_BURST); + outl(1, dev->iobase + PCI9118_SCANMOD); + outl(2, dev->iobase + PCI9118_SCANMOD); /* reset scan queue */ + devpriv->AdFunctionReg = AdFunction_PDTrg | AdFunction_PETrg; + outl(devpriv->AdFunctionReg, dev->iobase + PCI9118_ADFUNC); + /* + * positive triggers, no S&H, + * no burst, burst stop, + * no post trigger, + * no about trigger, + * trigger stop + */ + + devpriv->ao_data[0] = 2047; + devpriv->ao_data[1] = 2047; + outl(devpriv->ao_data[0], dev->iobase + PCI9118_DA1); + /* reset A/D outs to 0V */ + outl(devpriv->ao_data[1], dev->iobase + PCI9118_DA2); + outl(0, dev->iobase + PCI9118_DO); /* reset digi outs to L */ + udelay(10); + inl(dev->iobase + PCI9118_AD_DATA); + outl(0, dev->iobase + PCI9118_DELFIFO); /* flush FIFO */ + outl(0, dev->iobase + PCI9118_INTSRC); /* remove INT requests */ + inl(dev->iobase + PCI9118_ADSTAT); /* flush A/D status register */ + inl(dev->iobase + PCI9118_INTSRC); /* flush INT requests */ + devpriv->AdControlReg = 0; + outl(devpriv->AdControlReg, dev->iobase + PCI9118_ADCNTRL); + /* + * bipolar, S.E., use 8254, + * stop 8354, internal trigger, + * soft trigger, + * disable INT and DMA + */ + + devpriv->cnt0_users = 0; + devpriv->exttrg_users = 0; + + return 0; +} + +/* +============================================================================== +*/ +static int pci9118_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret, pages, i; + unsigned short master; + unsigned int irq; + unsigned long iobase_a, iobase_9; + struct pci_dev *pcidev; + int opt_bus, opt_slot; + const char *errstr; + unsigned char pci_bus, pci_slot, pci_func; + u16 u16w; + + printk("comedi%d: adl_pci9118: board=%s", dev->minor, this_board->name); + + opt_bus = it->options[0]; + opt_slot = it->options[1]; + if (it->options[3] & 1) + master = 0; /* user don't want use bus master */ + else + master = 1; + + ret = alloc_private(dev, sizeof(struct pci9118_private)); + if (ret < 0) { + printk(" - Allocation failed!\n"); + return -ENOMEM; + } + + /* Look for matching PCI device */ + errstr = "not found!"; + pcidev = NULL; + while (NULL != (pcidev = pci_get_device(PCI_VENDOR_ID_AMCC, + this_board->device_id, + pcidev))) { + /* Found matching vendor/device. */ + if (opt_bus || opt_slot) { + /* Check bus/slot. */ + if (opt_bus != pcidev->bus->number + || opt_slot != PCI_SLOT(pcidev->devfn)) + continue; /* no match */ + } + /* + * Look for device that isn't in use. + * Enable PCI device and request regions. + */ + if (comedi_pci_enable(pcidev, "adl_pci9118")) { + errstr = + "failed to enable PCI device and request regions!"; + continue; + } + break; + } + + if (!pcidev) { + if (opt_bus || opt_slot) { + printk(KERN_ERR " - Card at b:s %d:%d %s\n", + opt_bus, opt_slot, errstr); + } else { + printk(KERN_ERR " - Card %s\n", errstr); + } + return -EIO; + } + + if (master) + pci_set_master(pcidev); + + + pci_bus = pcidev->bus->number; + pci_slot = PCI_SLOT(pcidev->devfn); + pci_func = PCI_FUNC(pcidev->devfn); + irq = pcidev->irq; + iobase_a = pci_resource_start(pcidev, 0); + iobase_9 = pci_resource_start(pcidev, 2); + + printk(KERN_ERR ", b:s:f=%d:%d:%d, io=0x%4lx, 0x%4lx", pci_bus, + pci_slot, pci_func, iobase_9, iobase_a); + + dev->iobase = iobase_9; + dev->board_name = this_board->name; + + devpriv->pcidev = pcidev; + devpriv->iobase_a = iobase_a; + + pci9118_reset(dev); + + if (it->options[3] & 2) + irq = 0; /* user don't want use IRQ */ + if (irq > 0) { + if (request_irq(irq, interrupt_pci9118, IRQF_SHARED, + "ADLink PCI-9118", dev)) { + printk(", unable to allocate IRQ %d, DISABLING IT", + irq); + irq = 0; /* Can't use IRQ */ + } else { + printk(", irq=%u", irq); + } + } else { + printk(", IRQ disabled"); + } + + dev->irq = irq; + + if (master) { /* alloc DMA buffers */ + devpriv->dma_doublebuf = 0; + for (i = 0; i < 2; i++) { + for (pages = 4; pages >= 0; pages--) { + devpriv->dmabuf_virt[i] = + (short *)__get_free_pages(GFP_KERNEL, + pages); + if (devpriv->dmabuf_virt[i]) + break; + } + if (devpriv->dmabuf_virt[i]) { + devpriv->dmabuf_pages[i] = pages; + devpriv->dmabuf_size[i] = PAGE_SIZE * pages; + devpriv->dmabuf_samples[i] = + devpriv->dmabuf_size[i] >> 1; + devpriv->dmabuf_hw[i] = + virt_to_bus((void *) + devpriv->dmabuf_virt[i]); + } + } + if (!devpriv->dmabuf_virt[0]) { + printk(", Can't allocate DMA buffer, DMA disabled!"); + master = 0; + } + + if (devpriv->dmabuf_virt[1]) + devpriv->dma_doublebuf = 1; + + } + + devpriv->master = master; + if (devpriv->master) + printk(", bus master"); + else + printk(", no bus master"); + + devpriv->usemux = 0; + if (it->options[2] > 0) { + devpriv->usemux = it->options[2]; + if (devpriv->usemux > 256) + devpriv->usemux = 256; /* max 256 channels! */ + if (it->options[4] > 0) + if (devpriv->usemux > 128) { + devpriv->usemux = 128; + /* max 128 channels with softare S&H! */ + } + printk(", ext. mux %d channels", devpriv->usemux); + } + + devpriv->softsshdelay = it->options[4]; + if (devpriv->softsshdelay < 0) { + /* select sample&hold signal polarity */ + devpriv->softsshdelay = -devpriv->softsshdelay; + devpriv->softsshsample = 0x80; + devpriv->softsshhold = 0x00; + } else { + devpriv->softsshsample = 0x00; + devpriv->softsshhold = 0x80; + } + + printk(".\n"); + + pci_read_config_word(devpriv->pcidev, PCI_COMMAND, &u16w); + pci_write_config_word(devpriv->pcidev, PCI_COMMAND, u16w | 64); + /* Enable parity check for parity error */ + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; + if (devpriv->usemux) + s->n_chan = devpriv->usemux; + else + s->n_chan = this_board->n_aichan; + + s->maxdata = this_board->ai_maxdata; + s->len_chanlist = this_board->n_aichanlist; + s->range_table = this_board->rangelist_ai; + s->cancel = pci9118_ai_cancel; + s->insn_read = pci9118_insn_read_ai; + if (dev->irq) { + s->subdev_flags |= SDF_CMD_READ; + s->do_cmdtest = pci9118_ai_cmdtest; + s->do_cmd = pci9118_ai_cmd; + s->munge = pci9118_ai_munge; + } + + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->n_aochan; + s->range_table = this_board->rangelist_ao; + s->insn_write = pci9118_insn_write_ao; + s->insn_read = pci9118_insn_read_ao; + + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 1; + s->len_chanlist = 4; + s->range_table = &range_digital; + s->io_bits = 0; /* all bits input */ + s->insn_bits = pci9118_insn_bits_di; + + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 4; + s->maxdata = 1; + s->len_chanlist = 4; + s->range_table = &range_digital; + s->io_bits = 0xf; /* all bits output */ + s->insn_bits = pci9118_insn_bits_do; + + devpriv->valid = 1; + devpriv->i8254_osc_base = 250; /* 250ns=4MHz */ + devpriv->ai_maskharderr = 0x10a; + /* default measure crash condition */ + if (it->options[5]) /* disable some requested */ + devpriv->ai_maskharderr &= ~it->options[5]; + + switch (this_board->ai_maxdata) { + case 0xffff: + devpriv->ai16bits = 1; + break; + default: + devpriv->ai16bits = 0; + break; + } + return 0; +} + +/* +============================================================================== +*/ +static int pci9118_detach(struct comedi_device *dev) +{ + if (dev->private) { + if (devpriv->valid) + pci9118_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv->pcidev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pcidev); + + pci_dev_put(devpriv->pcidev); + } + if (devpriv->dmabuf_virt[0]) + free_pages((unsigned long)devpriv->dmabuf_virt[0], + devpriv->dmabuf_pages[0]); + if (devpriv->dmabuf_virt[1]) + free_pages((unsigned long)devpriv->dmabuf_virt[1], + devpriv->dmabuf_pages[1]); + } + + return 0; +} + +/* +============================================================================== +*/ + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adq12b.c b/drivers/staging/comedi/drivers/adq12b.c new file mode 100644 index 00000000..5361f318 --- /dev/null +++ b/drivers/staging/comedi/drivers/adq12b.c @@ -0,0 +1,420 @@ +/* + comedi/drivers/adq12b.c + driver for MicroAxial ADQ12-B data acquisition and control card + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: adq12b +Description: driver for MicroAxial ADQ12-B data acquisition and control card +Devices: [MicroAxial] ADQ12-B (adq12b) +Author: jeremy theler +Updated: Thu, 21 Feb 2008 02:56:27 -0300 +Status: works + +Driver for the acquisition card ADQ12-B (without any add-on). + + - Analog input is subdevice 0 (16 channels single-ended or 8 differential) + - Digital input is subdevice 1 (5 channels) + - Digital output is subdevice 1 (8 channels) + - The PACER is not supported in this version + +If you do not specify any options, they will default to + + # comedi_config /dev/comedi0 adq12b 0x300,0,0 + + option 1: I/O base address. The following table is provided as a help + of the hardware jumpers. + + address jumper JADR + 0x300 1 (factory default) + 0x320 2 + 0x340 3 + 0x360 4 + 0x380 5 + 0x3A0 6 + + option 2: unipolar/bipolar ADC selection: 0 -> bipolar, 1 -> unipolar + + selection comedi_config option JUB + bipolar 0 2-3 (factory default) + unipolar 1 1-2 + + option 3: single-ended/differential AI selection: 0 -> SE, 1 -> differential + + selection comedi_config option JCHA JCHB + single-ended 0 1-2 1-2 (factory default) + differential 1 2-3 2-3 + + written by jeremy theler + + instituto balseiro + commission nacional de energia atomica + universidad nacional de cuyo + argentina + + 21-feb-2008 + + changed supported devices string (missused the [] and ()) + + 13-oct-2007 + + first try + + +*/ + +#include "../comedidev.h" + +/* address scheme (page 2.17 of the manual) */ +#define ADQ12B_SIZE 16 + +#define ADQ12B_CTREG 0x00 +#define ADQ12B_STINR 0x00 +#define ADQ12B_OUTBR 0x04 +#define ADQ12B_ADLOW 0x08 +#define ADQ12B_ADHIG 0x09 +#define ADQ12B_CONT0 0x0c +#define ADQ12B_CONT1 0x0d +#define ADQ12B_CONT2 0x0e +#define ADQ12B_COWORD 0x0f + +/* mask of the bit at STINR to check end of conversion */ +#define ADQ12B_EOC 0x20 + +#define TIMEOUT 20 + +/* available ranges through the PGA gains */ +static const struct comedi_lrange range_adq12b_ai_bipolar = { 4, { + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + BIP_RANGE(0.5) + } +}; + +static const struct comedi_lrange range_adq12b_ai_unipolar = { 4, { + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE + (0.5) + } +}; + +struct adq12b_board { + const char *name; + int ai_se_chans; + int ai_diff_chans; + int ai_bits; + int di_chans; + int do_chans; +}; + +static const struct adq12b_board adq12b_boards[] = { + { + .name = "adq12b", + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 12, + .di_chans = 5, + .do_chans = 8} +/* potentially, more adq-based deviced will be added */ +/*, + .name = "adq12b", + .ai_chans = 16, // this is just for reference, hardcoded again later + .ai_bits = 12, + .di_chans = 8, + .do_chans = 5 + }*/ +}; + +#define thisboard ((const struct adq12b_board *)dev->board_ptr) + +struct adq12b_private { + int unipolar; /* option 2 of comedi_config (1 is iobase) */ + int differential; /* option 3 of comedi_config */ + int last_channel; + int last_range; + unsigned int digital_state; +}; + +#define devpriv ((struct adq12b_private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int adq12b_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int adq12b_detach(struct comedi_device *dev); + +static struct comedi_driver driver_adq12b = { + .driver_name = "adq12b", + .module = THIS_MODULE, + .attach = adq12b_attach, + .detach = adq12b_detach, + .board_name = &adq12b_boards[0].name, + .offset = sizeof(struct adq12b_board), + .num_names = ARRAY_SIZE(adq12b_boards), +}; + +static int adq12b_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int adq12b_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int adq12b_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int adq12b_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + int unipolar, differential; + + iobase = it->options[0]; + unipolar = it->options[1]; + differential = it->options[2]; + + printk(KERN_INFO "comedi%d: adq12b called with options base=0x%03lx, " + "%s and %s\n", dev->minor, iobase, + (unipolar == 1) ? "unipolar" : "bipolar", + (differential == 1) ? "differential" : "single-ended"); + + /* if no address was specified, try the default 0x300 */ + if (iobase == 0) { + printk(KERN_WARNING "comedi%d: adq12b warning: I/O base " + "address not specified. Trying the default 0x300.\n", + dev->minor); + iobase = 0x300; + } + + printk("comedi%d: adq12b: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, ADQ12B_SIZE, "adq12b")) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = thisboard->name; + +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct adq12b_private)) < 0) + return -ENOMEM; + +/* fill in devpriv structure */ + devpriv->unipolar = unipolar; + devpriv->differential = differential; + devpriv->digital_state = 0; +/* initialize channel and range to -1 so we make sure we always write + at least once to the CTREG in the instruction */ + devpriv->last_channel = -1; + devpriv->last_range = -1; + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 3) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + if (differential) { + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = thisboard->ai_diff_chans; + } else { + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = thisboard->ai_se_chans; + } + + if (unipolar) + s->range_table = &range_adq12b_ai_unipolar; + else + s->range_table = &range_adq12b_ai_bipolar; + + s->maxdata = (1 << thisboard->ai_bits) - 1; + + s->len_chanlist = 4; /* This is the maximum chanlist length that + the board can handle */ + s->insn_read = adq12b_ai_rinsn; + + s = dev->subdevices + 1; + /* digital input subdevice */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = thisboard->di_chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adq12b_di_insn_bits; + + s = dev->subdevices + 2; + /* digital output subdevice */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = thisboard->do_chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = adq12b_do_insn_bits; + + printk(KERN_INFO "attached\n"); + + return 0; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int adq12b_detach(struct comedi_device *dev) +{ + if (dev->iobase) + release_region(dev->iobase, ADQ12B_SIZE); + + kfree(devpriv); + + printk(KERN_INFO "comedi%d: adq12b: removed\n", dev->minor); + + return 0; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ + +static int adq12b_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n, i; + int range, channel; + unsigned char hi, lo, status; + + /* change channel and range only if it is different from the previous */ + range = CR_RANGE(insn->chanspec); + channel = CR_CHAN(insn->chanspec); + if (channel != devpriv->last_channel || range != devpriv->last_range) { + outb((range << 4) | channel, dev->iobase + ADQ12B_CTREG); + udelay(50); /* wait for the mux to settle */ + } + + /* trigger conversion */ + status = inb(dev->iobase + ADQ12B_ADLOW); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + + /* wait for end of conversion */ + i = 0; + do { + /* udelay(1); */ + status = inb(dev->iobase + ADQ12B_STINR); + status = status & ADQ12B_EOC; + } while (status == 0 && ++i < TIMEOUT); + /* } while (++i < 10); */ + + /* read data */ + hi = inb(dev->iobase + ADQ12B_ADHIG); + lo = inb(dev->iobase + ADQ12B_ADLOW); + + /* printk("debug: chan=%d range=%d status=%d hi=%d lo=%d\n", + channel, range, status, hi, lo); */ + data[n] = (hi << 8) | lo; + + } + + /* return the number of samples read/written */ + return n; +} + +static int adq12b_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + /* only bits 0-4 have information about digital inputs */ + data[1] = (inb(dev->iobase + ADQ12B_STINR) & (0x1f)); + + return 2; +} + +static int adq12b_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int channel; + + for (channel = 0; channel < 8; channel++) + if (((data[0] >> channel) & 0x01) != 0) + outb((((data[1] >> channel) & 0x01) << 3) | channel, + dev->iobase + ADQ12B_OUTBR); + + /* store information to retrieve when asked for reading */ + if (data[0]) { + devpriv->digital_state &= ~data[0]; + devpriv->digital_state |= (data[0] & data[1]); + } + + data[1] = devpriv->digital_state; + + return 2; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __init driver_adq12b_init_module(void) +{ + return comedi_driver_register(&driver_adq12b); +} + +static void __exit driver_adq12b_cleanup_module(void) +{ + comedi_driver_unregister(&driver_adq12b); +} + +module_init(driver_adq12b_init_module); +module_exit(driver_adq12b_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adv_pci1710.c b/drivers/staging/comedi/drivers/adv_pci1710.c new file mode 100644 index 00000000..8318c82a --- /dev/null +++ b/drivers/staging/comedi/drivers/adv_pci1710.c @@ -0,0 +1,1655 @@ +/* + * comedi/drivers/adv_pci1710.c + * + * Author: Michal Dobes + * + * Thanks to ZhenGang Shang + * for testing and informations. + * + * hardware driver for Advantech cards: + * card: PCI-1710, PCI-1710HG, PCI-1711, PCI-1713, PCI-1720, PCI-1731 + * driver: pci1710, pci1710hg, pci1711, pci1713, pci1720, pci1731 + * + * Options: + * [0] - PCI bus number - if bus number and slot number are 0, + * then driver search for first unused card + * [1] - PCI slot number + * +*/ +/* +Driver: adv_pci1710 +Description: Advantech PCI-1710, PCI-1710HG, PCI-1711, PCI-1713, + Advantech PCI-1720, PCI-1731 +Author: Michal Dobes +Devices: [Advantech] PCI-1710 (adv_pci1710), PCI-1710HG (pci1710hg), + PCI-1711 (adv_pci1710), PCI-1713, PCI-1720, + PCI-1731 +Status: works + +This driver supports AI, AO, DI and DO subdevices. +AI subdevice supports cmd and insn interface, +other subdevices support only insn interface. + +The PCI-1710 and PCI-1710HG have the same PCI device ID, so the +driver cannot distinguish between them, as would be normal for a +PCI driver. + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first available PCI + device will be used. +*/ + +#include + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#include "8253.h" +#include "amcc_s5933.h" + +#define PCI171x_PARANOIDCHECK /* if defined, then is used code which control + * correct channel number on every 12 bit + * sample */ + +#undef PCI171X_EXTDEBUG + +#define DRV_NAME "adv_pci1710" + +#undef DPRINTK +#ifdef PCI171X_EXTDEBUG +#define DPRINTK(fmt, args...) printk(fmt, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +#define PCI_VENDOR_ID_ADVANTECH 0x13fe + +/* hardware types of the cards */ +#define TYPE_PCI171X 0 +#define TYPE_PCI1713 2 +#define TYPE_PCI1720 3 + +#define IORANGE_171x 32 +#define IORANGE_1720 16 + +#define PCI171x_AD_DATA 0 /* R: A/D data */ +#define PCI171x_SOFTTRG 0 /* W: soft trigger for A/D */ +#define PCI171x_RANGE 2 /* W: A/D gain/range register */ +#define PCI171x_MUX 4 /* W: A/D multiplexor control */ +#define PCI171x_STATUS 6 /* R: status register */ +#define PCI171x_CONTROL 6 /* W: control register */ +#define PCI171x_CLRINT 8 /* W: clear interrupts request */ +#define PCI171x_CLRFIFO 9 /* W: clear FIFO */ +#define PCI171x_DA1 10 /* W: D/A register */ +#define PCI171x_DA2 12 /* W: D/A register */ +#define PCI171x_DAREF 14 /* W: D/A reference control */ +#define PCI171x_DI 16 /* R: digi inputs */ +#define PCI171x_DO 16 /* R: digi inputs */ +#define PCI171x_CNT0 24 /* R/W: 8254 counter 0 */ +#define PCI171x_CNT1 26 /* R/W: 8254 counter 1 */ +#define PCI171x_CNT2 28 /* R/W: 8254 counter 2 */ +#define PCI171x_CNTCTRL 30 /* W: 8254 counter control */ + +/* upper bits from status register (PCI171x_STATUS) (lower is same with control + * reg) */ +#define Status_FE 0x0100 /* 1=FIFO is empty */ +#define Status_FH 0x0200 /* 1=FIFO is half full */ +#define Status_FF 0x0400 /* 1=FIFO is full, fatal error */ +#define Status_IRQ 0x0800 /* 1=IRQ occurred */ +/* bits from control register (PCI171x_CONTROL) */ +#define Control_CNT0 0x0040 /* 1=CNT0 have external source, + * 0=have internal 100kHz source */ +#define Control_ONEFH 0x0020 /* 1=IRQ on FIFO is half full, 0=every sample */ +#define Control_IRQEN 0x0010 /* 1=enable IRQ */ +#define Control_GATE 0x0008 /* 1=enable external trigger GATE (8254?) */ +#define Control_EXT 0x0004 /* 1=external trigger source */ +#define Control_PACER 0x0002 /* 1=enable internal 8254 trigger source */ +#define Control_SW 0x0001 /* 1=enable software trigger source */ +/* bits from counter control register (PCI171x_CNTCTRL) */ +#define Counter_BCD 0x0001 /* 0 = binary counter, 1 = BCD counter */ +#define Counter_M0 0x0002 /* M0-M2 select modes 0-5 */ +#define Counter_M1 0x0004 /* 000 = mode 0, 010 = mode 2 ... */ +#define Counter_M2 0x0008 +#define Counter_RW0 0x0010 /* RW0/RW1 select read/write mode */ +#define Counter_RW1 0x0020 +#define Counter_SC0 0x0040 /* Select Counter. Only 00 or 11 may */ +#define Counter_SC1 0x0080 /* be used, 00 for CNT0, + * 11 for read-back command */ + +#define PCI1720_DA0 0 /* W: D/A register 0 */ +#define PCI1720_DA1 2 /* W: D/A register 1 */ +#define PCI1720_DA2 4 /* W: D/A register 2 */ +#define PCI1720_DA3 6 /* W: D/A register 3 */ +#define PCI1720_RANGE 8 /* R/W: D/A range register */ +#define PCI1720_SYNCOUT 9 /* W: D/A synchronized output register */ +#define PCI1720_SYNCONT 15 /* R/W: D/A synchronized control */ + +/* D/A synchronized control (PCI1720_SYNCONT) */ +#define Syncont_SC0 1 /* set synchronous output mode */ + +static const struct comedi_lrange range_pci1710_3 = { 9, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + BIP_RANGE(10), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const char range_codes_pci1710_3[] = { 0x00, 0x01, 0x02, 0x03, 0x04, + 0x10, 0x11, 0x12, 0x13 }; + +static const struct comedi_lrange range_pci1710hg = { 12, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01) + } +}; + +static const char range_codes_pci1710hg[] = { 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x10, 0x11, + 0x12, 0x13 }; + +static const struct comedi_lrange range_pci17x1 = { 5, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625) + } +}; + +static const char range_codes_pci17x1[] = { 0x00, 0x01, 0x02, 0x03, 0x04 }; + +static const struct comedi_lrange range_pci1720 = { 4, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(10) + } +}; + +static const struct comedi_lrange range_pci171x_da = { 2, { + UNI_RANGE(5), + UNI_RANGE(10), + } +}; + +static int pci1710_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pci1710_detach(struct comedi_device *dev); + +struct boardtype { + const char *name; /* board name */ + int device_id; + int iorange; /* I/O range len */ + char have_irq; /* 1=card support IRQ */ + char cardtype; /* 0=1710& co. 2=1713, ... */ + int n_aichan; /* num of A/D chans */ + int n_aichand; /* num of A/D chans in diff mode */ + int n_aochan; /* num of D/A chans */ + int n_dichan; /* num of DI chans */ + int n_dochan; /* num of DO chans */ + int n_counter; /* num of counters */ + int ai_maxdata; /* resolution of A/D */ + int ao_maxdata; /* resolution of D/A */ + const struct comedi_lrange *rangelist_ai; /* rangelist for A/D */ + const char *rangecode_ai; /* range codes for programming */ + const struct comedi_lrange *rangelist_ao; /* rangelist for D/A */ + unsigned int ai_ns_min; /* max sample speed of card v ns */ + unsigned int fifo_half_size; /* size of FIFO/2 */ +}; + +static DEFINE_PCI_DEVICE_TABLE(pci1710_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1710) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1711) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1713) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1720) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1731) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, pci1710_pci_table); + +static const struct boardtype boardtypes[] = { + {"pci1710", 0x1710, + IORANGE_171x, 1, TYPE_PCI171X, + 16, 8, 2, 16, 16, 1, 0x0fff, 0x0fff, + &range_pci1710_3, range_codes_pci1710_3, + &range_pci171x_da, + 10000, 2048}, + {"pci1710hg", 0x1710, + IORANGE_171x, 1, TYPE_PCI171X, + 16, 8, 2, 16, 16, 1, 0x0fff, 0x0fff, + &range_pci1710hg, range_codes_pci1710hg, + &range_pci171x_da, + 10000, 2048}, + {"pci1711", 0x1711, + IORANGE_171x, 1, TYPE_PCI171X, + 16, 0, 2, 16, 16, 1, 0x0fff, 0x0fff, + &range_pci17x1, range_codes_pci17x1, &range_pci171x_da, + 10000, 512}, + {"pci1713", 0x1713, + IORANGE_171x, 1, TYPE_PCI1713, + 32, 16, 0, 0, 0, 0, 0x0fff, 0x0000, + &range_pci1710_3, range_codes_pci1710_3, NULL, + 10000, 2048}, + {"pci1720", 0x1720, + IORANGE_1720, 0, TYPE_PCI1720, + 0, 0, 4, 0, 0, 0, 0x0000, 0x0fff, + NULL, NULL, &range_pci1720, + 0, 0}, + {"pci1731", 0x1731, + IORANGE_171x, 1, TYPE_PCI171X, + 16, 0, 0, 16, 16, 0, 0x0fff, 0x0000, + &range_pci17x1, range_codes_pci17x1, NULL, + 10000, 512}, + /* dummy entry corresponding to driver name */ + {.name = DRV_NAME}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct boardtype)) + +static struct comedi_driver driver_pci1710 = { + .driver_name = DRV_NAME, + .module = THIS_MODULE, + .attach = pci1710_attach, + .detach = pci1710_detach, + .num_names = n_boardtypes, + .board_name = &boardtypes[0].name, + .offset = sizeof(struct boardtype), +}; + +struct pci1710_private { + struct pci_dev *pcidev; /* ptr to PCI device */ + char valid; /* card is usable */ + char neverending_ai; /* we do unlimited AI */ + unsigned int CntrlReg; /* Control register */ + unsigned int i8254_osc_base; /* frequence of onboard oscilator */ + unsigned int ai_do; /* what do AI? 0=nothing, 1 to 4 mode */ + unsigned int ai_act_scan; /* how many scans we finished */ + unsigned int ai_act_chan; /* actual position in actual scan */ + unsigned int ai_buf_ptr; /* data buffer ptr in samples */ + unsigned char ai_eos; /* 1=EOS wake up */ + unsigned char ai_et; + unsigned int ai_et_CntrlReg; + unsigned int ai_et_MuxVal; + unsigned int ai_et_div1, ai_et_div2; + unsigned int act_chanlist[32]; /* list of scaned channel */ + unsigned char act_chanlist_len; /* len of scanlist */ + unsigned char act_chanlist_pos; /* actual position in MUX list */ + unsigned char da_ranges; /* copy of D/A outpit range register */ + unsigned int ai_scans; /* len of scanlist */ + unsigned int ai_n_chan; /* how many channels is measured */ + unsigned int *ai_chanlist; /* actaul chanlist */ + unsigned int ai_flags; /* flaglist */ + unsigned int ai_data_len; /* len of data buffer */ + short *ai_data; /* data buffer */ + unsigned int ai_timer1; /* timers */ + unsigned int ai_timer2; + short ao_data[4]; /* data output buffer */ + unsigned int cnt0_write_wait; /* after a write, wait for update of the + * internal state */ +}; + +#define devpriv ((struct pci1710_private *)dev->private) +#define this_board ((const struct boardtype *)dev->board_ptr) + +/* +============================================================================== +*/ + +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan); +static void setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan, + unsigned int seglen); +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2); +static int pci1710_reset(struct comedi_device *dev); +static int pci171x_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); + +/* used for gain list programming */ +static const unsigned int muxonechan[] = { + 0x0000, 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, + 0x0808, 0x0909, 0x0a0a, 0x0b0b, 0x0c0c, 0x0d0d, 0x0e0e, 0x0f0f, + 0x1010, 0x1111, 0x1212, 0x1313, 0x1414, 0x1515, 0x1616, 0x1717, + 0x1818, 0x1919, 0x1a1a, 0x1b1b, 0x1c1c, 0x1d1d, 0x1e1e, 0x1f1f +}; + +/* +============================================================================== +*/ +static int pci171x_insn_read_ai(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, timeout; +#ifdef PCI171x_PARANOIDCHECK + unsigned int idata; +#endif + + DPRINTK("adv_pci1710 EDBG: BGN: pci171x_insn_read_ai(...)\n"); + devpriv->CntrlReg &= Control_CNT0; + devpriv->CntrlReg |= Control_SW; /* set software trigger */ + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + + setup_channel_list(dev, s, &insn->chanspec, 1, 1); + + DPRINTK("adv_pci1710 A ST=%4x IO=%x\n", + inw(dev->iobase + PCI171x_STATUS), + dev->iobase + PCI171x_STATUS); + for (n = 0; n < insn->n; n++) { + outw(0, dev->iobase + PCI171x_SOFTTRG); /* start conversion */ + DPRINTK("adv_pci1710 B n=%d ST=%4x\n", n, + inw(dev->iobase + PCI171x_STATUS)); + /* udelay(1); */ + DPRINTK("adv_pci1710 C n=%d ST=%4x\n", n, + inw(dev->iobase + PCI171x_STATUS)); + timeout = 100; + while (timeout--) { + if (!(inw(dev->iobase + PCI171x_STATUS) & Status_FE)) + goto conv_finish; + if (!(timeout % 10)) + DPRINTK("adv_pci1710 D n=%d tm=%d ST=%4x\n", n, + timeout, + inw(dev->iobase + PCI171x_STATUS)); + } + comedi_error(dev, "A/D insn timeout"); + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + data[n] = 0; + DPRINTK + ("adv_pci1710 EDBG: END: pci171x_insn_read_ai(...) n=%d\n", + n); + return -ETIME; + +conv_finish: +#ifdef PCI171x_PARANOIDCHECK + idata = inw(dev->iobase + PCI171x_AD_DATA); + if (this_board->cardtype != TYPE_PCI1713) + if ((idata & 0xf000) != devpriv->act_chanlist[0]) { + comedi_error(dev, "A/D insn data droput!"); + return -ETIME; + } + data[n] = idata & 0x0fff; +#else + data[n] = inw(dev->iobase + PCI171x_AD_DATA) & 0x0fff; +#endif + + } + + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + + DPRINTK("adv_pci1710 EDBG: END: pci171x_insn_read_ai(...) n=%d\n", n); + return n; +} + +/* +============================================================================== +*/ +static int pci171x_insn_write_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, chan, range, ofs; + + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + if (chan) { + devpriv->da_ranges &= 0xfb; + devpriv->da_ranges |= (range << 2); + outw(devpriv->da_ranges, dev->iobase + PCI171x_DAREF); + ofs = PCI171x_DA2; + } else { + devpriv->da_ranges &= 0xfe; + devpriv->da_ranges |= range; + outw(devpriv->da_ranges, dev->iobase + PCI171x_DAREF); + ofs = PCI171x_DA1; + } + + for (n = 0; n < insn->n; n++) + outw(data[n], dev->iobase + ofs); + + devpriv->ao_data[chan] = data[n]; + + return n; + +} + +/* +============================================================================== +*/ +static int pci171x_insn_read_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, chan; + + chan = CR_CHAN(insn->chanspec); + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_data[chan]; + + return n; +} + +/* +============================================================================== +*/ +static int pci171x_insn_bits_di(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[1] = inw(dev->iobase + PCI171x_DI); + + return 2; +} + +/* +============================================================================== +*/ +static int pci171x_insn_bits_do(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + outw(s->state, dev->iobase + PCI171x_DO); + } + data[1] = s->state; + + return 2; +} + +/* +============================================================================== +*/ +static int pci171x_insn_counter_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int msb, lsb, ccntrl; + int i; + + ccntrl = 0xD2; /* count only */ + for (i = 0; i < insn->n; i++) { + outw(ccntrl, dev->iobase + PCI171x_CNTCTRL); + + lsb = inw(dev->iobase + PCI171x_CNT0) & 0xFF; + msb = inw(dev->iobase + PCI171x_CNT0) & 0xFF; + + data[0] = lsb | (msb << 8); + } + + return insn->n; +} + +/* +============================================================================== +*/ +static int pci171x_insn_counter_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + uint msb, lsb, ccntrl, status; + + lsb = data[0] & 0x00FF; + msb = (data[0] & 0xFF00) >> 8; + + /* write lsb, then msb */ + outw(lsb, dev->iobase + PCI171x_CNT0); + outw(msb, dev->iobase + PCI171x_CNT0); + + if (devpriv->cnt0_write_wait) { + /* wait for the new count to be loaded */ + ccntrl = 0xE2; + do { + outw(ccntrl, dev->iobase + PCI171x_CNTCTRL); + status = inw(dev->iobase + PCI171x_CNT0) & 0xFF; + } while (status & 0x40); + } + + return insn->n; +} + +/* +============================================================================== +*/ +static int pci171x_insn_counter_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ +#ifdef unused + /* This doesn't work like a normal Comedi counter config */ + uint ccntrl = 0; + + devpriv->cnt0_write_wait = data[0] & 0x20; + + /* internal or external clock? */ + if (!(data[0] & 0x10)) { /* internal */ + devpriv->CntrlReg &= ~Control_CNT0; + } else { + devpriv->CntrlReg |= Control_CNT0; + } + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + + if (data[0] & 0x01) + ccntrl |= Counter_M0; + if (data[0] & 0x02) + ccntrl |= Counter_M1; + if (data[0] & 0x04) + ccntrl |= Counter_M2; + if (data[0] & 0x08) + ccntrl |= Counter_BCD; + ccntrl |= Counter_RW0; /* set read/write mode */ + ccntrl |= Counter_RW1; + outw(ccntrl, dev->iobase + PCI171x_CNTCTRL); +#endif + + return 1; +} + +/* +============================================================================== +*/ +static int pci1720_insn_write_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, rangereg, chan; + + chan = CR_CHAN(insn->chanspec); + rangereg = devpriv->da_ranges & (~(0x03 << (chan << 1))); + rangereg |= (CR_RANGE(insn->chanspec) << (chan << 1)); + if (rangereg != devpriv->da_ranges) { + outb(rangereg, dev->iobase + PCI1720_RANGE); + devpriv->da_ranges = rangereg; + } + + for (n = 0; n < insn->n; n++) { + outw(data[n], dev->iobase + PCI1720_DA0 + (chan << 1)); + outb(0, dev->iobase + PCI1720_SYNCOUT); /* update outputs */ + } + + devpriv->ao_data[chan] = data[n]; + + return n; +} + +/* +============================================================================== +*/ +static void interrupt_pci1710_every_sample(void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + int m; +#ifdef PCI171x_PARANOIDCHECK + short sampl; +#endif + + DPRINTK("adv_pci1710 EDBG: BGN: interrupt_pci1710_every_sample(...)\n"); + m = inw(dev->iobase + PCI171x_STATUS); + if (m & Status_FE) { + printk("comedi%d: A/D FIFO empty (%4x)\n", dev->minor, m); + pci171x_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return; + } + if (m & Status_FF) { + printk + ("comedi%d: A/D FIFO Full status (Fatal Error!) (%4x)\n", + dev->minor, m); + pci171x_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return; + } + + outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */ + + DPRINTK("FOR "); + for (; !(inw(dev->iobase + PCI171x_STATUS) & Status_FE);) { +#ifdef PCI171x_PARANOIDCHECK + sampl = inw(dev->iobase + PCI171x_AD_DATA); + DPRINTK("%04x:", sampl); + if (this_board->cardtype != TYPE_PCI1713) + if ((sampl & 0xf000) != + devpriv->act_chanlist[s->async->cur_chan]) { + printk + ("comedi: A/D data dropout: received data from channel %d, expected %d!\n", + (sampl & 0xf000) >> 12, + (devpriv-> + act_chanlist[s-> + async->cur_chan] & 0xf000) >> + 12); + pci171x_ai_cancel(dev, s); + s->async->events |= + COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return; + } + DPRINTK("%8d %2d %8d~", s->async->buf_int_ptr, + s->async->cur_chan, s->async->buf_int_count); + comedi_buf_put(s->async, sampl & 0x0fff); +#else + comedi_buf_put(s->async, + inw(dev->iobase + PCI171x_AD_DATA) & 0x0fff); +#endif + ++s->async->cur_chan; + + if (s->async->cur_chan >= devpriv->ai_n_chan) + s->async->cur_chan = 0; + + + if (s->async->cur_chan == 0) { /* one scan done */ + devpriv->ai_act_scan++; + DPRINTK + ("adv_pci1710 EDBG: EOS1 bic %d bip %d buc %d bup %d\n", + s->async->buf_int_count, s->async->buf_int_ptr, + s->async->buf_user_count, s->async->buf_user_ptr); + DPRINTK("adv_pci1710 EDBG: EOS2\n"); + if ((!devpriv->neverending_ai) && (devpriv->ai_act_scan >= devpriv->ai_scans)) { /* all data sampled */ + pci171x_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return; + } + } + } + + outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */ + DPRINTK("adv_pci1710 EDBG: END: interrupt_pci1710_every_sample(...)\n"); + + comedi_event(dev, s); +} + +/* +============================================================================== +*/ +static int move_block_from_fifo(struct comedi_device *dev, + struct comedi_subdevice *s, int n, int turn) +{ + int i, j; +#ifdef PCI171x_PARANOIDCHECK + int sampl; +#endif + DPRINTK("adv_pci1710 EDBG: BGN: move_block_from_fifo(...,%d,%d)\n", n, + turn); + j = s->async->cur_chan; + for (i = 0; i < n; i++) { +#ifdef PCI171x_PARANOIDCHECK + sampl = inw(dev->iobase + PCI171x_AD_DATA); + if (this_board->cardtype != TYPE_PCI1713) + if ((sampl & 0xf000) != devpriv->act_chanlist[j]) { + printk + ("comedi%d: A/D FIFO data dropout: received data from channel %d, expected %d! (%d/%d/%d/%d/%d/%4x)\n", + dev->minor, (sampl & 0xf000) >> 12, + (devpriv->act_chanlist[j] & 0xf000) >> 12, + i, j, devpriv->ai_act_scan, n, turn, + sampl); + pci171x_ai_cancel(dev, s); + s->async->events |= + COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return 1; + } + comedi_buf_put(s->async, sampl & 0x0fff); +#else + comedi_buf_put(s->async, + inw(dev->iobase + PCI171x_AD_DATA) & 0x0fff); +#endif + j++; + if (j >= devpriv->ai_n_chan) { + j = 0; + devpriv->ai_act_scan++; + } + } + s->async->cur_chan = j; + DPRINTK("adv_pci1710 EDBG: END: move_block_from_fifo(...)\n"); + return 0; +} + +/* +============================================================================== +*/ +static void interrupt_pci1710_half_fifo(void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + int m, samplesinbuf; + + DPRINTK("adv_pci1710 EDBG: BGN: interrupt_pci1710_half_fifo(...)\n"); + m = inw(dev->iobase + PCI171x_STATUS); + if (!(m & Status_FH)) { + printk("comedi%d: A/D FIFO not half full! (%4x)\n", + dev->minor, m); + pci171x_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return; + } + if (m & Status_FF) { + printk + ("comedi%d: A/D FIFO Full status (Fatal Error!) (%4x)\n", + dev->minor, m); + pci171x_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return; + } + + samplesinbuf = this_board->fifo_half_size; + if (samplesinbuf * sizeof(short) >= devpriv->ai_data_len) { + m = devpriv->ai_data_len / sizeof(short); + if (move_block_from_fifo(dev, s, m, 0)) + return; + samplesinbuf -= m; + } + + if (samplesinbuf) { + if (move_block_from_fifo(dev, s, samplesinbuf, 1)) + return; + } + + if (!devpriv->neverending_ai) + if (devpriv->ai_act_scan >= devpriv->ai_scans) { /* all data + sampled */ + pci171x_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return; + } + outb(0, dev->iobase + PCI171x_CLRINT); /* clear our INT request */ + DPRINTK("adv_pci1710 EDBG: END: interrupt_pci1710_half_fifo(...)\n"); + + comedi_event(dev, s); +} + +/* +============================================================================== +*/ +static irqreturn_t interrupt_service_pci1710(int irq, void *d) +{ + struct comedi_device *dev = d; + + DPRINTK("adv_pci1710 EDBG: BGN: interrupt_service_pci1710(%d,...)\n", + irq); + if (!dev->attached) /* is device attached? */ + return IRQ_NONE; /* no, exit */ + + if (!(inw(dev->iobase + PCI171x_STATUS) & Status_IRQ)) /* is this interrupt from our board? */ + return IRQ_NONE; /* no, exit */ + + DPRINTK("adv_pci1710 EDBG: interrupt_service_pci1710() ST: %4x\n", + inw(dev->iobase + PCI171x_STATUS)); + + if (devpriv->ai_et) { /* Switch from initial TRIG_EXT to TRIG_xxx. */ + devpriv->ai_et = 0; + devpriv->CntrlReg &= Control_CNT0; + devpriv->CntrlReg |= Control_SW; /* set software trigger */ + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + devpriv->CntrlReg = devpriv->ai_et_CntrlReg; + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + outw(devpriv->ai_et_MuxVal, dev->iobase + PCI171x_MUX); + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + /* start pacer */ + start_pacer(dev, 1, devpriv->ai_et_div1, devpriv->ai_et_div2); + return IRQ_HANDLED; + } + if (devpriv->ai_eos) { /* We use FIFO half full INT or not? */ + interrupt_pci1710_every_sample(d); + } else { + interrupt_pci1710_half_fifo(d); + } + DPRINTK("adv_pci1710 EDBG: END: interrupt_service_pci1710(...)\n"); + return IRQ_HANDLED; +} + +/* +============================================================================== +*/ +static int pci171x_ai_docmd_and_mode(int mode, struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int divisor1 = 0, divisor2 = 0; + unsigned int seglen; + + DPRINTK("adv_pci1710 EDBG: BGN: pci171x_ai_docmd_and_mode(%d,...)\n", + mode); + start_pacer(dev, -1, 0, 0); /* stop pacer */ + + seglen = check_channel_list(dev, s, devpriv->ai_chanlist, + devpriv->ai_n_chan); + if (seglen < 1) + return -EINVAL; + setup_channel_list(dev, s, devpriv->ai_chanlist, + devpriv->ai_n_chan, seglen); + + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + + devpriv->ai_do = mode; + + devpriv->ai_act_scan = 0; + s->async->cur_chan = 0; + devpriv->ai_buf_ptr = 0; + devpriv->neverending_ai = 0; + + devpriv->CntrlReg &= Control_CNT0; + if ((devpriv->ai_flags & TRIG_WAKE_EOS)) { /* don't we want wake up every scan? devpriv->ai_eos=1; */ + devpriv->ai_eos = 1; + } else { + devpriv->CntrlReg |= Control_ONEFH; + devpriv->ai_eos = 0; + } + + if ((devpriv->ai_scans == 0) || (devpriv->ai_scans == -1)) + devpriv->neverending_ai = 1; + /* well, user want neverending */ + else + devpriv->neverending_ai = 0; + + switch (mode) { + case 1: + case 2: + if (devpriv->ai_timer1 < this_board->ai_ns_min) + devpriv->ai_timer1 = this_board->ai_ns_min; + devpriv->CntrlReg |= Control_PACER | Control_IRQEN; + if (mode == 2) { + devpriv->ai_et_CntrlReg = devpriv->CntrlReg; + devpriv->CntrlReg &= + ~(Control_PACER | Control_ONEFH | Control_GATE); + devpriv->CntrlReg |= Control_EXT; + devpriv->ai_et = 1; + } else { + devpriv->ai_et = 0; + } + i8253_cascade_ns_to_timer(devpriv->i8254_osc_base, &divisor1, + &divisor2, &devpriv->ai_timer1, + devpriv->ai_flags & TRIG_ROUND_MASK); + DPRINTK + ("adv_pci1710 EDBG: OSC base=%u div1=%u div2=%u timer=%u\n", + devpriv->i8254_osc_base, divisor1, divisor2, + devpriv->ai_timer1); + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + if (mode != 2) { + /* start pacer */ + start_pacer(dev, mode, divisor1, divisor2); + } else { + devpriv->ai_et_div1 = divisor1; + devpriv->ai_et_div2 = divisor2; + } + break; + case 3: + devpriv->CntrlReg |= Control_EXT | Control_IRQEN; + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); + break; + } + + DPRINTK("adv_pci1710 EDBG: END: pci171x_ai_docmd_and_mode(...)\n"); + return 0; +} + +#ifdef PCI171X_EXTDEBUG +/* +============================================================================== +*/ +static void pci171x_cmdtest_out(int e, struct comedi_cmd *cmd) +{ + printk("adv_pci1710 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e, + cmd->start_src, cmd->scan_begin_src, cmd->convert_src); + printk("adv_pci1710 e=%d startarg=%d scanarg=%d convarg=%d\n", e, + cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg); + printk("adv_pci1710 e=%d stopsrc=%x scanend=%x\n", e, cmd->stop_src, + cmd->scan_end_src); + printk("adv_pci1710 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n", + e, cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len); +} +#endif + +/* +============================================================================== +*/ +static int pci171x_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + unsigned int divisor1 = 0, divisor2 = 0; + + DPRINTK("adv_pci1710 EDBG: BGN: pci171x_ai_cmdtest(...)\n"); +#ifdef PCI171X_EXTDEBUG + pci171x_cmdtest_out(-1, cmd); +#endif + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) { +#ifdef PCI171X_EXTDEBUG + pci171x_cmdtest_out(1, cmd); +#endif + DPRINTK + ("adv_pci1710 EDBG: BGN: pci171x_ai_cmdtest(...) err=%d ret=1\n", + err); + return 1; + } + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) { + cmd->start_src = TRIG_NOW; + err++; + } + + if (cmd->scan_begin_src != TRIG_FOLLOW) { + cmd->scan_begin_src = TRIG_FOLLOW; + err++; + } + + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + + if (cmd->scan_end_src != TRIG_COUNT) { + cmd->scan_end_src = TRIG_COUNT; + err++; + } + + if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) + err++; + + if (err) { +#ifdef PCI171X_EXTDEBUG + pci171x_cmdtest_out(2, cmd); +#endif + DPRINTK + ("adv_pci1710 EDBG: BGN: pci171x_ai_cmdtest(...) err=%d ret=2\n", + err); + return 2; + } + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < this_board->ai_ns_min) { + cmd->convert_arg = this_board->ai_ns_min; + err++; + } + } else { /* TRIG_FOLLOW */ + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) { +#ifdef PCI171X_EXTDEBUG + pci171x_cmdtest_out(3, cmd); +#endif + DPRINTK + ("adv_pci1710 EDBG: BGN: pci171x_ai_cmdtest(...) err=%d ret=3\n", + err); + return 3; + } + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + i8253_cascade_ns_to_timer(devpriv->i8254_osc_base, &divisor1, + &divisor2, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (cmd->convert_arg < this_board->ai_ns_min) + cmd->convert_arg = this_board->ai_ns_min; + if (tmp != cmd->convert_arg) + err++; + } + + if (err) { + DPRINTK + ("adv_pci1710 EDBG: BGN: pci171x_ai_cmdtest(...) err=%d ret=4\n", + err); + return 4; + } + + /* step 5: complain about special chanlist considerations */ + + if (cmd->chanlist) { + if (!check_channel_list(dev, s, cmd->chanlist, + cmd->chanlist_len)) + return 5; /* incorrect channels list */ + } + + DPRINTK("adv_pci1710 EDBG: BGN: pci171x_ai_cmdtest(...) ret=0\n"); + return 0; +} + +/* +============================================================================== +*/ +static int pci171x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + DPRINTK("adv_pci1710 EDBG: BGN: pci171x_ai_cmd(...)\n"); + devpriv->ai_n_chan = cmd->chanlist_len; + devpriv->ai_chanlist = cmd->chanlist; + devpriv->ai_flags = cmd->flags; + devpriv->ai_data_len = s->async->prealloc_bufsz; + devpriv->ai_data = s->async->prealloc_buf; + devpriv->ai_timer1 = 0; + devpriv->ai_timer2 = 0; + + if (cmd->stop_src == TRIG_COUNT) + devpriv->ai_scans = cmd->stop_arg; + else + devpriv->ai_scans = 0; + + + if (cmd->scan_begin_src == TRIG_FOLLOW) { /* mode 1, 2, 3 */ + if (cmd->convert_src == TRIG_TIMER) { /* mode 1 and 2 */ + devpriv->ai_timer1 = cmd->convert_arg; + return pci171x_ai_docmd_and_mode(cmd->start_src == + TRIG_EXT ? 2 : 1, dev, + s); + } + if (cmd->convert_src == TRIG_EXT) { /* mode 3 */ + return pci171x_ai_docmd_and_mode(3, dev, s); + } + } + + return -1; +} + +/* +============================================================================== + Check if channel list from user is builded correctly + If it's ok, then program scan/gain logic. + This works for all cards. +*/ +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan) +{ + unsigned int chansegment[32]; + unsigned int i, nowmustbechan, seglen, segpos; + + DPRINTK("adv_pci1710 EDBG: check_channel_list(...,%d)\n", n_chan); + /* correct channel and range number check itself comedi/range.c */ + if (n_chan < 1) { + comedi_error(dev, "range/channel list is empty!"); + return 0; + } + + if (n_chan > 1) { + chansegment[0] = chanlist[0]; /* first channel is every time ok */ + for (i = 1, seglen = 1; i < n_chan; i++, seglen++) { /* build part of chanlist */ + /* printk("%d. %d %d\n",i,CR_CHAN(chanlist[i]),CR_RANGE(chanlist[i])); */ + if (chanlist[0] == chanlist[i]) + break; /* we detect loop, this must by finish */ + if (CR_CHAN(chanlist[i]) & 1) /* odd channel cann't by differencial */ + if (CR_AREF(chanlist[i]) == AREF_DIFF) { + comedi_error(dev, + "Odd channel can't be differential input!\n"); + return 0; + } + nowmustbechan = + (CR_CHAN(chansegment[i - 1]) + 1) % s->n_chan; + if (CR_AREF(chansegment[i - 1]) == AREF_DIFF) + nowmustbechan = (nowmustbechan + 1) % s->n_chan; + if (nowmustbechan != CR_CHAN(chanlist[i])) { /* channel list isn't continuous :-( */ + printk + ("channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n", + i, CR_CHAN(chanlist[i]), nowmustbechan, + CR_CHAN(chanlist[0])); + return 0; + } + chansegment[i] = chanlist[i]; /* well, this is next correct channel in list */ + } + + for (i = 0, segpos = 0; i < n_chan; i++) { /* check whole chanlist */ + /* printk("%d %d=%d %d\n",CR_CHAN(chansegment[i%seglen]),CR_RANGE(chansegment[i%seglen]),CR_CHAN(chanlist[i]),CR_RANGE(chanlist[i])); */ + if (chanlist[i] != chansegment[i % seglen]) { + printk + ("bad channel, reference or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", + i, CR_CHAN(chansegment[i]), + CR_RANGE(chansegment[i]), + CR_AREF(chansegment[i]), + CR_CHAN(chanlist[i % seglen]), + CR_RANGE(chanlist[i % seglen]), + CR_AREF(chansegment[i % seglen])); + return 0; /* chan/gain list is strange */ + } + } + } else { + seglen = 1; + } + return seglen; +} + +static void setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan, + unsigned int seglen) +{ + unsigned int i, range, chanprog; + + DPRINTK("adv_pci1710 EDBG: setup_channel_list(...,%d,%d)\n", n_chan, + seglen); + devpriv->act_chanlist_len = seglen; + devpriv->act_chanlist_pos = 0; + + DPRINTK("SegLen: %d\n", seglen); + for (i = 0; i < seglen; i++) { /* store range list to card */ + chanprog = muxonechan[CR_CHAN(chanlist[i])]; + outw(chanprog, dev->iobase + PCI171x_MUX); /* select channel */ + range = this_board->rangecode_ai[CR_RANGE(chanlist[i])]; + if (CR_AREF(chanlist[i]) == AREF_DIFF) + range |= 0x0020; + outw(range, dev->iobase + PCI171x_RANGE); /* select gain */ +#ifdef PCI171x_PARANOIDCHECK + devpriv->act_chanlist[i] = + (CR_CHAN(chanlist[i]) << 12) & 0xf000; +#endif + DPRINTK("GS: %2d. [%4x]=%4x %4x\n", i, chanprog, range, + devpriv->act_chanlist[i]); + } +#ifdef PCI171x_PARANOIDCHECK + for ( ; i < n_chan; i++) { /* store remainder of channel list */ + devpriv->act_chanlist[i] = + (CR_CHAN(chanlist[i]) << 12) & 0xf000; + } +#endif + + devpriv->ai_et_MuxVal = + CR_CHAN(chanlist[0]) | (CR_CHAN(chanlist[seglen - 1]) << 8); + outw(devpriv->ai_et_MuxVal, dev->iobase + PCI171x_MUX); /* select channel interval to scan */ + DPRINTK("MUX: %4x L%4x.H%4x\n", + CR_CHAN(chanlist[0]) | (CR_CHAN(chanlist[seglen - 1]) << 8), + CR_CHAN(chanlist[0]), CR_CHAN(chanlist[seglen - 1])); +} + +/* +============================================================================== +*/ +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2) +{ + DPRINTK("adv_pci1710 EDBG: BGN: start_pacer(%d,%u,%u)\n", mode, + divisor1, divisor2); + outw(0xb4, dev->iobase + PCI171x_CNTCTRL); + outw(0x74, dev->iobase + PCI171x_CNTCTRL); + + if (mode == 1) { + outw(divisor2 & 0xff, dev->iobase + PCI171x_CNT2); + outw((divisor2 >> 8) & 0xff, dev->iobase + PCI171x_CNT2); + outw(divisor1 & 0xff, dev->iobase + PCI171x_CNT1); + outw((divisor1 >> 8) & 0xff, dev->iobase + PCI171x_CNT1); + } + DPRINTK("adv_pci1710 EDBG: END: start_pacer(...)\n"); +} + +/* +============================================================================== +*/ +static int pci171x_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + DPRINTK("adv_pci1710 EDBG: BGN: pci171x_ai_cancel(...)\n"); + + switch (this_board->cardtype) { + default: + devpriv->CntrlReg &= Control_CNT0; + devpriv->CntrlReg |= Control_SW; + + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); /* reset any operations */ + start_pacer(dev, -1, 0, 0); + outb(0, dev->iobase + PCI171x_CLRFIFO); + outb(0, dev->iobase + PCI171x_CLRINT); + break; + } + + devpriv->ai_do = 0; + devpriv->ai_act_scan = 0; + s->async->cur_chan = 0; + devpriv->ai_buf_ptr = 0; + devpriv->neverending_ai = 0; + + DPRINTK("adv_pci1710 EDBG: END: pci171x_ai_cancel(...)\n"); + return 0; +} + +/* +============================================================================== +*/ +static int pci171x_reset(struct comedi_device *dev) +{ + DPRINTK("adv_pci1710 EDBG: BGN: pci171x_reset(...)\n"); + outw(0x30, dev->iobase + PCI171x_CNTCTRL); + devpriv->CntrlReg = Control_SW | Control_CNT0; /* Software trigger, CNT0=external */ + outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL); /* reset any operations */ + outb(0, dev->iobase + PCI171x_CLRFIFO); /* clear FIFO */ + outb(0, dev->iobase + PCI171x_CLRINT); /* clear INT request */ + start_pacer(dev, -1, 0, 0); /* stop 8254 */ + devpriv->da_ranges = 0; + if (this_board->n_aochan) { + outb(devpriv->da_ranges, dev->iobase + PCI171x_DAREF); /* set DACs to 0..5V */ + outw(0, dev->iobase + PCI171x_DA1); /* set DA outputs to 0V */ + devpriv->ao_data[0] = 0x0000; + if (this_board->n_aochan > 1) { + outw(0, dev->iobase + PCI171x_DA2); + devpriv->ao_data[1] = 0x0000; + } + } + outw(0, dev->iobase + PCI171x_DO); /* digital outputs to 0 */ + outb(0, dev->iobase + PCI171x_CLRFIFO); /* clear FIFO */ + outb(0, dev->iobase + PCI171x_CLRINT); /* clear INT request */ + + DPRINTK("adv_pci1710 EDBG: END: pci171x_reset(...)\n"); + return 0; +} + +/* +============================================================================== +*/ +static int pci1720_reset(struct comedi_device *dev) +{ + DPRINTK("adv_pci1710 EDBG: BGN: pci1720_reset(...)\n"); + outb(Syncont_SC0, dev->iobase + PCI1720_SYNCONT); /* set synchronous output mode */ + devpriv->da_ranges = 0xAA; + outb(devpriv->da_ranges, dev->iobase + PCI1720_RANGE); /* set all ranges to +/-5V */ + outw(0x0800, dev->iobase + PCI1720_DA0); /* set outputs to 0V */ + outw(0x0800, dev->iobase + PCI1720_DA1); + outw(0x0800, dev->iobase + PCI1720_DA2); + outw(0x0800, dev->iobase + PCI1720_DA3); + outb(0, dev->iobase + PCI1720_SYNCOUT); /* update outputs */ + devpriv->ao_data[0] = 0x0800; + devpriv->ao_data[1] = 0x0800; + devpriv->ao_data[2] = 0x0800; + devpriv->ao_data[3] = 0x0800; + DPRINTK("adv_pci1710 EDBG: END: pci1720_reset(...)\n"); + return 0; +} + +/* +============================================================================== +*/ +static int pci1710_reset(struct comedi_device *dev) +{ + DPRINTK("adv_pci1710 EDBG: BGN: pci1710_reset(...)\n"); + switch (this_board->cardtype) { + case TYPE_PCI1720: + return pci1720_reset(dev); + default: + return pci171x_reset(dev); + } + DPRINTK("adv_pci1710 EDBG: END: pci1710_reset(...)\n"); +} + +/* +============================================================================== +*/ +static int pci1710_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret, subdev, n_subdevices; + unsigned int irq; + unsigned long iobase; + struct pci_dev *pcidev; + int opt_bus, opt_slot; + const char *errstr; + unsigned char pci_bus, pci_slot, pci_func; + int i; + int board_index; + + dev_info(dev->hw_dev, "comedi%d: adv_pci1710:\n", dev->minor); + + opt_bus = it->options[0]; + opt_slot = it->options[1]; + + ret = alloc_private(dev, sizeof(struct pci1710_private)); + if (ret < 0) + return -ENOMEM; + + /* Look for matching PCI device */ + errstr = "not found!"; + pcidev = NULL; + board_index = this_board - boardtypes; + while (NULL != (pcidev = pci_get_device(PCI_VENDOR_ID_ADVANTECH, + PCI_ANY_ID, pcidev))) { + if (strcmp(this_board->name, DRV_NAME) == 0) { + for (i = 0; i < n_boardtypes; ++i) { + if (pcidev->device == boardtypes[i].device_id) { + board_index = i; + break; + } + } + if (i == n_boardtypes) + continue; + } else { + if (pcidev->device != boardtypes[board_index].device_id) + continue; + } + + /* Found matching vendor/device. */ + if (opt_bus || opt_slot) { + /* Check bus/slot. */ + if (opt_bus != pcidev->bus->number + || opt_slot != PCI_SLOT(pcidev->devfn)) + continue; /* no match */ + } + /* + * Look for device that isn't in use. + * Enable PCI device and request regions. + */ + if (comedi_pci_enable(pcidev, DRV_NAME)) { + errstr = + "failed to enable PCI device and request regions!"; + continue; + } + /* fixup board_ptr in case we were using the dummy entry with the driver name */ + dev->board_ptr = &boardtypes[board_index]; + break; + } + + if (!pcidev) { + if (opt_bus || opt_slot) { + dev_err(dev->hw_dev, "- Card at b:s %d:%d %s\n", + opt_bus, opt_slot, errstr); + } else { + dev_err(dev->hw_dev, "- Card %s\n", errstr); + } + return -EIO; + } + + pci_bus = pcidev->bus->number; + pci_slot = PCI_SLOT(pcidev->devfn); + pci_func = PCI_FUNC(pcidev->devfn); + irq = pcidev->irq; + iobase = pci_resource_start(pcidev, 2); + + dev_dbg(dev->hw_dev, "b:s:f=%d:%d:%d, io=0x%4lx\n", pci_bus, pci_slot, + pci_func, iobase); + + dev->iobase = iobase; + + dev->board_name = this_board->name; + devpriv->pcidev = pcidev; + + n_subdevices = 0; + if (this_board->n_aichan) + n_subdevices++; + if (this_board->n_aochan) + n_subdevices++; + if (this_board->n_dichan) + n_subdevices++; + if (this_board->n_dochan) + n_subdevices++; + if (this_board->n_counter) + n_subdevices++; + + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) + return ret; + + pci1710_reset(dev); + + if (this_board->have_irq) { + if (irq) { + if (request_irq(irq, interrupt_service_pci1710, + IRQF_SHARED, "Advantech PCI-1710", + dev)) { + dev_dbg(dev->hw_dev, "unable to allocate IRQ %d, DISABLING IT", + irq); + irq = 0; /* Can't use IRQ */ + } else { + dev_dbg(dev->hw_dev, "irq=%u", irq); + } + } else { + dev_dbg(dev->hw_dev, "IRQ disabled"); + } + } else { + irq = 0; + } + + dev->irq = irq; + subdev = 0; + + if (this_board->n_aichan) { + s = dev->subdevices + subdev; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND; + if (this_board->n_aichand) + s->subdev_flags |= SDF_DIFF; + s->n_chan = this_board->n_aichan; + s->maxdata = this_board->ai_maxdata; + s->len_chanlist = this_board->n_aichan; + s->range_table = this_board->rangelist_ai; + s->cancel = pci171x_ai_cancel; + s->insn_read = pci171x_insn_read_ai; + if (irq) { + s->subdev_flags |= SDF_CMD_READ; + s->do_cmdtest = pci171x_ai_cmdtest; + s->do_cmd = pci171x_ai_cmd; + } + devpriv->i8254_osc_base = 100; /* 100ns=10MHz */ + subdev++; + } + + if (this_board->n_aochan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->n_aochan; + s->range_table = this_board->rangelist_ao; + switch (this_board->cardtype) { + case TYPE_PCI1720: + s->insn_write = pci1720_insn_write_ao; + break; + default: + s->insn_write = pci171x_insn_write_ao; + break; + } + s->insn_read = pci171x_insn_read_ao; + subdev++; + } + + if (this_board->n_dichan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_dichan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dichan; + s->range_table = &range_digital; + s->io_bits = 0; /* all bits input */ + s->insn_bits = pci171x_insn_bits_di; + subdev++; + } + + if (this_board->n_dochan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_dochan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dochan; + s->range_table = &range_digital; + /* all bits output */ + s->io_bits = (1 << this_board->n_dochan) - 1; + s->state = 0; + s->insn_bits = pci171x_insn_bits_do; + subdev++; + } + + if (this_board->n_counter) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = this_board->n_counter; + s->len_chanlist = this_board->n_counter; + s->maxdata = 0xffff; + s->range_table = &range_unknown; + s->insn_read = pci171x_insn_counter_read; + s->insn_write = pci171x_insn_counter_write; + s->insn_config = pci171x_insn_counter_config; + subdev++; + } + + devpriv->valid = 1; + + return 0; +} + +/* +============================================================================== +*/ +static int pci1710_detach(struct comedi_device *dev) +{ + + if (dev->private) { + if (devpriv->valid) + pci1710_reset(dev); + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv->pcidev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pcidev); + + pci_dev_put(devpriv->pcidev); + } + } + + return 0; +} + +/* +============================================================================== +*/ +static int __devinit driver_pci1710_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_pci1710.driver_name); +} + +static void __devexit driver_pci1710_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_pci1710_pci_driver = { + .id_table = pci1710_pci_table, + .probe = &driver_pci1710_pci_probe, + .remove = __devexit_p(&driver_pci1710_pci_remove) +}; + +static int __init driver_pci1710_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_pci1710); + if (retval < 0) + return retval; + + driver_pci1710_pci_driver.name = (char *)driver_pci1710.driver_name; + return pci_register_driver(&driver_pci1710_pci_driver); +} + +static void __exit driver_pci1710_cleanup_module(void) +{ + pci_unregister_driver(&driver_pci1710_pci_driver); + comedi_driver_unregister(&driver_pci1710); +} + +module_init(driver_pci1710_init_module); +module_exit(driver_pci1710_cleanup_module); +/* +============================================================================== +*/ + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adv_pci1723.c b/drivers/staging/comedi/drivers/adv_pci1723.c new file mode 100644 index 00000000..29455a8e --- /dev/null +++ b/drivers/staging/comedi/drivers/adv_pci1723.c @@ -0,0 +1,538 @@ +/******************************************************************************* + comedi/drivers/pci1723.c + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*******************************************************************************/ +/* +Driver: adv_pci1723 +Description: Advantech PCI-1723 +Author: yonggang , Ian Abbott +Devices: [Advantech] PCI-1723 (adv_pci1723) +Updated: Mon, 14 Apr 2008 15:12:56 +0100 +Status: works + +Configuration Options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + + If bus/slot is not specified, the first supported + PCI device found will be used. + +Subdevice 0 is 8-channel AO, 16-bit, range +/- 10 V. + +Subdevice 1 is 16-channel DIO. The channels are configurable as input or +output in 2 groups (0 to 7, 8 to 15). Configuring any channel implicitly +configures all channels in the same group. + +TODO: + +1. Add the two milliamp ranges to the AO subdevice (0 to 20 mA, 4 to 20 mA). +2. Read the initial ranges and values of the AO subdevice at start-up instead + of reinitializing them. +3. Implement calibration. +*/ + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#define PCI_VENDOR_ID_ADVANTECH 0x13fe /* Advantech PCI vendor ID */ + +/* hardware types of the cards */ +#define TYPE_PCI1723 0 + +#define IORANGE_1723 0x2A + +/* all the registers for the pci1723 board */ +#define PCI1723_DA(N) ((N)<<1) /* W: D/A register N (0 to 7) */ + +#define PCI1723_SYN_SET 0x12 /* synchronized set register */ +#define PCI1723_ALL_CHNNELE_SYN_STROBE 0x12 + /* synchronized status register */ + +#define PCI1723_RANGE_CALIBRATION_MODE 0x14 + /* range and calibration mode */ +#define PCI1723_RANGE_CALIBRATION_STATUS 0x14 + /* range and calibration status */ + +#define PCI1723_CONTROL_CMD_CALIBRATION_FUN 0x16 + /* + * SADC control command for + * calibration function + */ +#define PCI1723_STATUS_CMD_CALIBRATION_FUN 0x16 + /* + * SADC control status for + * calibration function + */ + +#define PCI1723_CALIBRATION_PARA_STROBE 0x18 + /* Calibration parameter strobe */ + +#define PCI1723_DIGITAL_IO_PORT_SET 0x1A /* Digital I/O port setting */ +#define PCI1723_DIGITAL_IO_PORT_MODE 0x1A /* Digital I/O port mode */ + +#define PCI1723_WRITE_DIGITAL_OUTPUT_CMD 0x1C + /* Write digital output command */ +#define PCI1723_READ_DIGITAL_INPUT_DATA 0x1C /* Read digital input data */ + +#define PCI1723_WRITE_CAL_CMD 0x1E /* Write calibration command */ +#define PCI1723_READ_CAL_STATUS 0x1E /* Read calibration status */ + +#define PCI1723_SYN_STROBE 0x20 /* Synchronized strobe */ + +#define PCI1723_RESET_ALL_CHN_STROBE 0x22 + /* Reset all D/A channels strobe */ + +#define PCI1723_RESET_CAL_CONTROL_STROBE 0x24 + /* + * Reset the calibration + * controller strobe + */ + +#define PCI1723_CHANGE_CHA_OUTPUT_TYPE_STROBE 0x26 + /* + * Change D/A channels output + * type strobe + */ + +#define PCI1723_SELECT_CALIBRATION 0x28 /* Select the calibration Ref_V */ + +/* static unsigned short pci_list_builded=0; =1 list of card is know */ + +static const struct comedi_lrange range_pci1723 = { 1, { + BIP_RANGE(10) + } +}; + +/* + * Board descriptions for pci1723 boards. + */ +struct pci1723_board { + const char *name; + int vendor_id; /* PCI vendor a device ID of card */ + int device_id; + int iorange; + char cardtype; + int n_aochan; /* num of D/A chans */ + int n_diochan; /* num of DIO chans */ + int ao_maxdata; /* resolution of D/A */ + const struct comedi_lrange *rangelist_ao; /* rangelist for D/A */ +}; + +static const struct pci1723_board boardtypes[] = { + { + .name = "pci1723", + .vendor_id = PCI_VENDOR_ID_ADVANTECH, + .device_id = 0x1723, + .iorange = IORANGE_1723, + .cardtype = TYPE_PCI1723, + .n_aochan = 8, + .n_diochan = 16, + .ao_maxdata = 0xffff, + .rangelist_ao = &range_pci1723, + }, +}; + +/* + * This is used by modprobe to translate PCI IDs to drivers. + * Should only be used for PCI and ISA-PnP devices + */ +static DEFINE_PCI_DEVICE_TABLE(pci1723_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1723) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, pci1723_pci_table); + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pci1723_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pci1723_detach(struct comedi_device *dev); + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pci1723_board)) + +static struct comedi_driver driver_pci1723 = { + .driver_name = "adv_pci1723", + .module = THIS_MODULE, + .attach = pci1723_attach, + .detach = pci1723_detach, +}; + +/* This structure is for data unique to this hardware driver. */ +struct pci1723_private { + int valid; /* card is usable; */ + + struct pci_dev *pcidev; + unsigned char da_range[8]; /* D/A output range for each channel */ + + short ao_data[8]; /* data output buffer */ +}; + +/* The following macro to make it easy to access the private structure. */ +#define devpriv ((struct pci1723_private *)dev->private) + +#define this_board boardtypes + +/* + * The pci1723 card reset; + */ +static int pci1723_reset(struct comedi_device *dev) +{ + int i; + DPRINTK("adv_pci1723 EDBG: BGN: pci1723_reset(...)\n"); + + outw(0x01, dev->iobase + PCI1723_SYN_SET); + /* set synchronous output mode */ + + for (i = 0; i < 8; i++) { + /* set all outputs to 0V */ + devpriv->ao_data[i] = 0x8000; + outw(devpriv->ao_data[i], dev->iobase + PCI1723_DA(i)); + /* set all ranges to +/- 10V */ + devpriv->da_range[i] = 0; + outw(((devpriv->da_range[i] << 4) | i), + PCI1723_RANGE_CALIBRATION_MODE); + } + + outw(0, dev->iobase + PCI1723_CHANGE_CHA_OUTPUT_TYPE_STROBE); + /* update ranges */ + outw(0, dev->iobase + PCI1723_SYN_STROBE); /* update outputs */ + + /* set asynchronous output mode */ + outw(0, dev->iobase + PCI1723_SYN_SET); + + DPRINTK("adv_pci1723 EDBG: END: pci1723_reset(...)\n"); + return 0; +} + +static int pci1723_insn_read_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, chan; + + chan = CR_CHAN(insn->chanspec); + DPRINTK(" adv_PCI1723 DEBUG: pci1723_insn_read_ao() -----\n"); + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_data[chan]; + + return n; +} + +/* + analog data output; +*/ +static int pci1723_ao_write_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, chan; + chan = CR_CHAN(insn->chanspec); + + DPRINTK("PCI1723: the pci1723_ao_write_winsn() ------\n"); + + for (n = 0; n < insn->n; n++) { + + devpriv->ao_data[chan] = data[n]; + outw(data[n], dev->iobase + PCI1723_DA(chan)); + } + + return n; +} + +/* + digital i/o config/query +*/ +static int pci1723_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int mask; + unsigned int bits; + unsigned short dio_mode; + + mask = 1 << CR_CHAN(insn->chanspec); + if (mask & 0x00FF) + bits = 0x00FF; + else + bits = 0xFF00; + + switch (data[0]) { + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~bits; + break; + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= bits; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + default: + return -EINVAL; + } + + /* update hardware DIO mode */ + dio_mode = 0x0000; /* low byte output, high byte output */ + if ((s->io_bits & 0x00FF) == 0) + dio_mode |= 0x0001; /* low byte input */ + if ((s->io_bits & 0xFF00) == 0) + dio_mode |= 0x0002; /* high byte input */ + outw(dio_mode, dev->iobase + PCI1723_DIGITAL_IO_PORT_SET); + return 1; +} + +/* + digital i/o bits read/write +*/ +static int pci1723_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + outw(s->state, dev->iobase + PCI1723_WRITE_DIGITAL_OUTPUT_CMD); + } + data[1] = inw(dev->iobase + PCI1723_READ_DIGITAL_INPUT_DATA); + return 2; +} + +/* + * Attach is called by the Comedi core to configure the driver + * for a pci1723 board. + */ +static int pci1723_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret, subdev, n_subdevices; + struct pci_dev *pcidev; + unsigned int iobase; + unsigned char pci_bus, pci_slot, pci_func; + int opt_bus, opt_slot; + const char *errstr; + + printk(KERN_ERR "comedi%d: adv_pci1723: board=%s", + dev->minor, this_board->name); + + opt_bus = it->options[0]; + opt_slot = it->options[1]; + + ret = alloc_private(dev, sizeof(struct pci1723_private)); + if (ret < 0) { + printk(" - Allocation failed!\n"); + return -ENOMEM; + } + + /* Look for matching PCI device */ + errstr = "not found!"; + pcidev = NULL; + while (NULL != (pcidev = + pci_get_device(PCI_VENDOR_ID_ADVANTECH, + this_board->device_id, pcidev))) { + /* Found matching vendor/device. */ + if (opt_bus || opt_slot) { + /* Check bus/slot. */ + if (opt_bus != pcidev->bus->number + || opt_slot != PCI_SLOT(pcidev->devfn)) + continue; /* no match */ + } + /* + * Look for device that isn't in use. + * Enable PCI device and request regions. + */ + if (comedi_pci_enable(pcidev, "adv_pci1723")) { + errstr = + "failed to enable PCI device and request regions!"; + continue; + } + break; + } + + if (!pcidev) { + if (opt_bus || opt_slot) { + printk(KERN_ERR " - Card at b:s %d:%d %s\n", + opt_bus, opt_slot, errstr); + } else { + printk(KERN_ERR " - Card %s\n", errstr); + } + return -EIO; + } + + pci_bus = pcidev->bus->number; + pci_slot = PCI_SLOT(pcidev->devfn); + pci_func = PCI_FUNC(pcidev->devfn); + iobase = pci_resource_start(pcidev, 2); + + printk(KERN_ERR ", b:s:f=%d:%d:%d, io=0x%4x", + pci_bus, pci_slot, pci_func, iobase); + + dev->iobase = iobase; + + dev->board_name = this_board->name; + devpriv->pcidev = pcidev; + + n_subdevices = 0; + + if (this_board->n_aochan) + n_subdevices++; + if (this_board->n_diochan) + n_subdevices++; + + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) { + printk(" - Allocation failed!\n"); + return ret; + } + + pci1723_reset(dev); + subdev = 0; + if (this_board->n_aochan) { + s = dev->subdevices + subdev; + dev->write_subdev = s; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->n_aochan; + s->range_table = this_board->rangelist_ao; + + s->insn_write = pci1723_ao_write_winsn; + s->insn_read = pci1723_insn_read_ao; + + /* read DIO config */ + switch (inw(dev->iobase + PCI1723_DIGITAL_IO_PORT_MODE) + & 0x03) { + case 0x00: /* low byte output, high byte output */ + s->io_bits = 0xFFFF; + break; + case 0x01: /* low byte input, high byte output */ + s->io_bits = 0xFF00; + break; + case 0x02: /* low byte output, high byte input */ + s->io_bits = 0x00FF; + break; + case 0x03: /* low byte input, high byte input */ + s->io_bits = 0x0000; + break; + } + /* read DIO port state */ + s->state = inw(dev->iobase + PCI1723_READ_DIGITAL_INPUT_DATA); + + subdev++; + } + + if (this_board->n_diochan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = + SDF_READABLE | SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_diochan; + s->maxdata = 1; + s->len_chanlist = this_board->n_diochan; + s->range_table = &range_digital; + s->insn_config = pci1723_dio_insn_config; + s->insn_bits = pci1723_dio_insn_bits; + subdev++; + } + + devpriv->valid = 1; + + pci1723_reset(dev); + + return 0; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pci1723_detach(struct comedi_device *dev) +{ + printk(KERN_ERR "comedi%d: pci1723: remove\n", dev->minor); + + if (dev->private) { + if (devpriv->valid) + pci1723_reset(dev); + + if (devpriv->pcidev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pcidev); + pci_dev_put(devpriv->pcidev); + } + } + + return 0; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __devinit driver_pci1723_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_pci1723.driver_name); +} + +static void __devexit driver_pci1723_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_pci1723_pci_driver = { + .id_table = pci1723_pci_table, + .probe = &driver_pci1723_pci_probe, + .remove = __devexit_p(&driver_pci1723_pci_remove) +}; + +static int __init driver_pci1723_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_pci1723); + if (retval < 0) + return retval; + + driver_pci1723_pci_driver.name = (char *)driver_pci1723.driver_name; + return pci_register_driver(&driver_pci1723_pci_driver); +} + +static void __exit driver_pci1723_cleanup_module(void) +{ + pci_unregister_driver(&driver_pci1723_pci_driver); + comedi_driver_unregister(&driver_pci1723); +} + +module_init(driver_pci1723_init_module); +module_exit(driver_pci1723_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/adv_pci_dio.c b/drivers/staging/comedi/drivers/adv_pci_dio.c new file mode 100644 index 00000000..7af068f4 --- /dev/null +++ b/drivers/staging/comedi/drivers/adv_pci_dio.c @@ -0,0 +1,1362 @@ +/* + * comedi/drivers/adv_pci_dio.c + * + * Author: Michal Dobes + * + * Hardware driver for Advantech PCI DIO cards. +*/ +/* +Driver: adv_pci_dio +Description: Advantech PCI-1730, PCI-1733, PCI-1734, PCI-1735U, + PCI-1736UP, PCI-1739U, PCI-1750, PCI-1751, PCI-1752, + PCI-1753/E, PCI-1754, PCI-1756, PCI-1760, PCI-1762 +Author: Michal Dobes +Devices: [Advantech] PCI-1730 (adv_pci_dio), PCI-1733, + PCI-1734, PCI-1735U, PCI-1736UP, PCI-1739U, PCI-1750, + PCI-1751, PCI-1752, PCI-1753, + PCI-1753+PCI-1753E, PCI-1754, PCI-1756, + PCI-1760, PCI-1762 +Status: untested +Updated: Mon, 09 Jan 2012 12:40:46 +0000 + +This driver supports now only insn interface for DI/DO/DIO. + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first available PCI + device will be used. + +*/ + +#include "../comedidev.h" + +#include + +#include "comedi_pci.h" +#include "8255.h" +#include "8253.h" + +#undef PCI_DIO_EXTDEBUG /* if defined, enable extensive debug logging */ + +#undef DPRINTK +#ifdef PCI_DIO_EXTDEBUG +#define DPRINTK(fmt, args...) printk(fmt, ## args) +#else +#define DPRINTK(fmt, args...) +#endif + +#define PCI_VENDOR_ID_ADVANTECH 0x13fe + +/* hardware types of the cards */ +enum hw_cards_id { + TYPE_PCI1730, TYPE_PCI1733, TYPE_PCI1734, TYPE_PCI1735, TYPE_PCI1736, + TYPE_PCI1739, + TYPE_PCI1750, + TYPE_PCI1751, + TYPE_PCI1752, + TYPE_PCI1753, TYPE_PCI1753E, + TYPE_PCI1754, TYPE_PCI1756, + TYPE_PCI1760, + TYPE_PCI1762 +}; + +/* which I/O instructions to use */ +enum hw_io_access { + IO_8b, IO_16b +}; + +#define MAX_DI_SUBDEVS 2 /* max number of DI subdevices per card */ +#define MAX_DO_SUBDEVS 2 /* max number of DO subdevices per card */ +#define MAX_DIO_SUBDEVG 2 /* max number of DIO subdevices group per + * card */ +#define MAX_8254_SUBDEVS 1 /* max number of 8254 counter subdevs per + * card */ + /* (could be more than one 8254 per + * subdevice) */ + +#define SIZE_8254 4 /* 8254 IO space length */ +#define SIZE_8255 4 /* 8255 IO space length */ + +#define PCIDIO_MAINREG 2 /* main I/O region for all Advantech cards? */ + +/* Register offset definitions */ +/* Advantech PCI-1730/3/4 */ +#define PCI1730_IDI 0 /* R: Isolated digital input 0-15 */ +#define PCI1730_IDO 0 /* W: Isolated digital output 0-15 */ +#define PCI1730_DI 2 /* R: Digital input 0-15 */ +#define PCI1730_DO 2 /* W: Digital output 0-15 */ +#define PCI1733_IDI 0 /* R: Isolated digital input 0-31 */ +#define PCI1730_3_INT_EN 0x08 /* R/W: enable/disable interrupts */ +#define PCI1730_3_INT_RF 0x0c /* R/W: set falling/raising edge for + * interrupts */ +#define PCI1730_3_INT_CLR 0x10 /* R/W: clear interrupts */ +#define PCI1734_IDO 0 /* W: Isolated digital output 0-31 */ +#define PCI173x_BOARDID 4 /* R: Board I/D switch for 1730/3/4 */ + +/* Advantech PCI-1735U */ +#define PCI1735_DI 0 /* R: Digital input 0-31 */ +#define PCI1735_DO 0 /* W: Digital output 0-31 */ +#define PCI1735_C8254 4 /* R/W: 8254 counter */ +#define PCI1735_BOARDID 8 /* R: Board I/D switch for 1735U */ + +/* Advantech PCI-1736UP */ +#define PCI1736_IDI 0 /* R: Isolated digital input 0-15 */ +#define PCI1736_IDO 0 /* W: Isolated digital output 0-15 */ +#define PCI1736_3_INT_EN 0x08 /* R/W: enable/disable interrupts */ +#define PCI1736_3_INT_RF 0x0c /* R/W: set falling/raising edge for + * interrupts */ +#define PCI1736_3_INT_CLR 0x10 /* R/W: clear interrupts */ +#define PCI1736_BOARDID 4 /* R: Board I/D switch for 1736UP */ +#define PCI1736_MAINREG 0 /* Normal register (2) doesn't work */ + +/* Advantech PCI-1739U */ +#define PCI1739_DIO 0 /* R/W: begin of 8255 registers block */ +#define PCI1739_ICR 32 /* W: Interrupt control register */ +#define PCI1739_ISR 32 /* R: Interrupt status register */ +#define PCI1739_BOARDID 8 /* R: Board I/D switch for 1739U */ + +/* Advantech PCI-1750 */ +#define PCI1750_IDI 0 /* R: Isolated digital input 0-15 */ +#define PCI1750_IDO 0 /* W: Isolated digital output 0-15 */ +#define PCI1750_ICR 32 /* W: Interrupt control register */ +#define PCI1750_ISR 32 /* R: Interrupt status register */ + +/* Advantech PCI-1751/3/3E */ +#define PCI1751_DIO 0 /* R/W: begin of 8255 registers block */ +#define PCI1751_CNT 24 /* R/W: begin of 8254 registers block */ +#define PCI1751_ICR 32 /* W: Interrupt control register */ +#define PCI1751_ISR 32 /* R: Interrupt status register */ +#define PCI1753_DIO 0 /* R/W: begin of 8255 registers block */ +#define PCI1753_ICR0 16 /* R/W: Interrupt control register group 0 */ +#define PCI1753_ICR1 17 /* R/W: Interrupt control register group 1 */ +#define PCI1753_ICR2 18 /* R/W: Interrupt control register group 2 */ +#define PCI1753_ICR3 19 /* R/W: Interrupt control register group 3 */ +#define PCI1753E_DIO 32 /* R/W: begin of 8255 registers block */ +#define PCI1753E_ICR0 48 /* R/W: Interrupt control register group 0 */ +#define PCI1753E_ICR1 49 /* R/W: Interrupt control register group 1 */ +#define PCI1753E_ICR2 50 /* R/W: Interrupt control register group 2 */ +#define PCI1753E_ICR3 51 /* R/W: Interrupt control register group 3 */ + +/* Advantech PCI-1752/4/6 */ +#define PCI1752_IDO 0 /* R/W: Digital output 0-31 */ +#define PCI1752_IDO2 4 /* R/W: Digital output 32-63 */ +#define PCI1754_IDI 0 /* R: Digital input 0-31 */ +#define PCI1754_IDI2 4 /* R: Digital input 32-64 */ +#define PCI1756_IDI 0 /* R: Digital input 0-31 */ +#define PCI1756_IDO 4 /* R/W: Digital output 0-31 */ +#define PCI1754_6_ICR0 0x08 /* R/W: Interrupt control register group 0 */ +#define PCI1754_6_ICR1 0x0a /* R/W: Interrupt control register group 1 */ +#define PCI1754_ICR2 0x0c /* R/W: Interrupt control register group 2 */ +#define PCI1754_ICR3 0x0e /* R/W: Interrupt control register group 3 */ +#define PCI1752_6_CFC 0x12 /* R/W: set/read channel freeze function */ +#define PCI175x_BOARDID 0x10 /* R: Board I/D switch for 1752/4/6 */ + +/* Advantech PCI-1762 registers */ +#define PCI1762_RO 0 /* R/W: Relays status/output */ +#define PCI1762_IDI 2 /* R: Isolated input status */ +#define PCI1762_BOARDID 4 /* R: Board I/D switch */ +#define PCI1762_ICR 6 /* W: Interrupt control register */ +#define PCI1762_ISR 6 /* R: Interrupt status register */ + +/* Advantech PCI-1760 registers */ +#define OMB0 0x0c /* W: Mailbox outgoing registers */ +#define OMB1 0x0d +#define OMB2 0x0e +#define OMB3 0x0f +#define IMB0 0x1c /* R: Mailbox incoming registers */ +#define IMB1 0x1d +#define IMB2 0x1e +#define IMB3 0x1f +#define INTCSR0 0x38 /* R/W: Interrupt control registers */ +#define INTCSR1 0x39 +#define INTCSR2 0x3a +#define INTCSR3 0x3b + +/* PCI-1760 mailbox commands */ +#define CMD_ClearIMB2 0x00 /* Clear IMB2 status and return actual + * DI status in IMB3 */ +#define CMD_SetRelaysOutput 0x01 /* Set relay output from OMB0 */ +#define CMD_GetRelaysStatus 0x02 /* Get relay status to IMB0 */ +#define CMD_ReadCurrentStatus 0x07 /* Read the current status of the + * register in OMB0, result in IMB0 */ +#define CMD_ReadFirmwareVersion 0x0e /* Read the firmware ver., result in + * IMB1.IMB0 */ +#define CMD_ReadHardwareVersion 0x0f /* Read the hardware ver., result in + * IMB1.IMB0 */ +#define CMD_EnableIDIFilters 0x20 /* Enable IDI filters based on bits in + * OMB0 */ +#define CMD_EnableIDIPatternMatch 0x21 /* Enable IDI pattern match based on + * bits in OMB0 */ +#define CMD_SetIDIPatternMatch 0x22 /* Enable IDI pattern match based on + * bits in OMB0 */ +#define CMD_EnableIDICounters 0x28 /* Enable IDI counters based on bits in + * OMB0 */ +#define CMD_ResetIDICounters 0x29 /* Reset IDI counters based on bits in + * OMB0 to its reset values */ +#define CMD_OverflowIDICounters 0x2a /* Enable IDI counters overflow + * interrupts based on bits in OMB0 */ +#define CMD_MatchIntIDICounters 0x2b /* Enable IDI counters match value + * interrupts based on bits in OMB0 */ +#define CMD_EdgeIDICounters 0x2c /* Set IDI up counters count edge (bit=0 + * - rising, =1 - falling) */ +#define CMD_GetIDICntCurValue 0x2f /* Read IDI{OMB0} up counter current + * value */ +#define CMD_SetIDI0CntResetValue 0x40 /* Set IDI0 Counter Reset Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI1CntResetValue 0x41 /* Set IDI1 Counter Reset Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI2CntResetValue 0x42 /* Set IDI2 Counter Reset Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI3CntResetValue 0x43 /* Set IDI3 Counter Reset Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI4CntResetValue 0x44 /* Set IDI4 Counter Reset Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI5CntResetValue 0x45 /* Set IDI5 Counter Reset Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI6CntResetValue 0x46 /* Set IDI6 Counter Reset Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI7CntResetValue 0x47 /* Set IDI7 Counter Reset Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI0CntMatchValue 0x48 /* Set IDI0 Counter Match Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI1CntMatchValue 0x49 /* Set IDI1 Counter Match Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI2CntMatchValue 0x4a /* Set IDI2 Counter Match Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI3CntMatchValue 0x4b /* Set IDI3 Counter Match Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI4CntMatchValue 0x4c /* Set IDI4 Counter Match Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI5CntMatchValue 0x4d /* Set IDI5 Counter Match Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI6CntMatchValue 0x4e /* Set IDI6 Counter Match Value + * 256*OMB1+OMB0 */ +#define CMD_SetIDI7CntMatchValue 0x4f /* Set IDI7 Counter Match Value + * 256*OMB1+OMB0 */ + +#define OMBCMD_RETRY 0x03 /* 3 times try request before error */ + +static int pci_dio_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pci_dio_detach(struct comedi_device *dev); + +struct diosubd_data { + int chans; /* num of chans */ + int addr; /* PCI address ofset */ + int regs; /* number of registers to read or 8255 + subdevices or 8254 chips */ + unsigned int specflags; /* addon subdevice flags */ +}; + +struct dio_boardtype { + const char *name; /* board name */ + int vendor_id; /* vendor/device PCI ID */ + int device_id; + int main_pci_region; /* main I/O PCI region */ + enum hw_cards_id cardtype; + struct diosubd_data sdi[MAX_DI_SUBDEVS]; /* DI chans */ + struct diosubd_data sdo[MAX_DO_SUBDEVS]; /* DO chans */ + struct diosubd_data sdio[MAX_DIO_SUBDEVG]; /* DIO 8255 chans */ + struct diosubd_data boardid; /* card supports board ID switch */ + struct diosubd_data s8254[MAX_8254_SUBDEVS]; /* 8254 subdevices */ + enum hw_io_access io_access; +}; + +static DEFINE_PCI_DEVICE_TABLE(pci_dio_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1730) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1733) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1734) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1735) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1736) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1739) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1750) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1751) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1752) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1753) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1754) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1756) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1760) }, + { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1762) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, pci_dio_pci_table); + +static const struct dio_boardtype boardtypes[] = { + {"pci1730", PCI_VENDOR_ID_ADVANTECH, 0x1730, PCIDIO_MAINREG, + TYPE_PCI1730, + { {16, PCI1730_DI, 2, 0}, {16, PCI1730_IDI, 2, 0} }, + { {16, PCI1730_DO, 2, 0}, {16, PCI1730_IDO, 2, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {4, PCI173x_BOARDID, 1, SDF_INTERNAL}, + { {0, 0, 0, 0} }, + IO_8b}, + {"pci1733", PCI_VENDOR_ID_ADVANTECH, 0x1733, PCIDIO_MAINREG, + TYPE_PCI1733, + { {0, 0, 0, 0}, {32, PCI1733_IDI, 4, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {4, PCI173x_BOARDID, 1, SDF_INTERNAL}, + { {0, 0, 0, 0} }, + IO_8b}, + {"pci1734", PCI_VENDOR_ID_ADVANTECH, 0x1734, PCIDIO_MAINREG, + TYPE_PCI1734, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {0, 0, 0, 0}, {32, PCI1734_IDO, 4, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {4, PCI173x_BOARDID, 1, SDF_INTERNAL}, + { {0, 0, 0, 0} }, + IO_8b}, + {"pci1735", PCI_VENDOR_ID_ADVANTECH, 0x1735, PCIDIO_MAINREG, + TYPE_PCI1735, + { {32, PCI1735_DI, 4, 0}, {0, 0, 0, 0} }, + { {32, PCI1735_DO, 4, 0}, {0, 0, 0, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { 4, PCI1735_BOARDID, 1, SDF_INTERNAL}, + { {3, PCI1735_C8254, 1, 0} }, + IO_8b}, + {"pci1736", PCI_VENDOR_ID_ADVANTECH, 0x1736, PCI1736_MAINREG, + TYPE_PCI1736, + { {0, 0, 0, 0}, {16, PCI1736_IDI, 2, 0} }, + { {0, 0, 0, 0}, {16, PCI1736_IDO, 2, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {4, PCI1736_BOARDID, 1, SDF_INTERNAL}, + { {0, 0, 0, 0} }, + IO_8b}, + {"pci1739", PCI_VENDOR_ID_ADVANTECH, 0x1739, PCIDIO_MAINREG, + TYPE_PCI1739, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {48, PCI1739_DIO, 2, 0}, {0, 0, 0, 0} }, + {0, 0, 0, 0}, + { {0, 0, 0, 0} }, + IO_8b}, + {"pci1750", PCI_VENDOR_ID_ADVANTECH, 0x1750, PCIDIO_MAINREG, + TYPE_PCI1750, + { {0, 0, 0, 0}, {16, PCI1750_IDI, 2, 0} }, + { {0, 0, 0, 0}, {16, PCI1750_IDO, 2, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {0, 0, 0, 0}, + { {0, 0, 0, 0} }, + IO_8b}, + {"pci1751", PCI_VENDOR_ID_ADVANTECH, 0x1751, PCIDIO_MAINREG, + TYPE_PCI1751, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {48, PCI1751_DIO, 2, 0}, {0, 0, 0, 0} }, + {0, 0, 0, 0}, + { {3, PCI1751_CNT, 1, 0} }, + IO_8b}, + {"pci1752", PCI_VENDOR_ID_ADVANTECH, 0x1752, PCIDIO_MAINREG, + TYPE_PCI1752, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {32, PCI1752_IDO, 2, 0}, {32, PCI1752_IDO2, 2, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {4, PCI175x_BOARDID, 1, SDF_INTERNAL}, + { {0, 0, 0, 0} }, + IO_16b}, + {"pci1753", PCI_VENDOR_ID_ADVANTECH, 0x1753, PCIDIO_MAINREG, + TYPE_PCI1753, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {96, PCI1753_DIO, 4, 0}, {0, 0, 0, 0} }, + {0, 0, 0, 0}, + { {0, 0, 0, 0} }, + IO_8b}, + {"pci1753e", PCI_VENDOR_ID_ADVANTECH, 0x1753, PCIDIO_MAINREG, + TYPE_PCI1753E, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {96, PCI1753_DIO, 4, 0}, {96, PCI1753E_DIO, 4, 0} }, + {0, 0, 0, 0}, + { {0, 0, 0, 0} }, + IO_8b}, + {"pci1754", PCI_VENDOR_ID_ADVANTECH, 0x1754, PCIDIO_MAINREG, + TYPE_PCI1754, + { {32, PCI1754_IDI, 2, 0}, {32, PCI1754_IDI2, 2, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {4, PCI175x_BOARDID, 1, SDF_INTERNAL}, + { {0, 0, 0, 0} }, + IO_16b}, + {"pci1756", PCI_VENDOR_ID_ADVANTECH, 0x1756, PCIDIO_MAINREG, + TYPE_PCI1756, + { {0, 0, 0, 0}, {32, PCI1756_IDI, 2, 0} }, + { {0, 0, 0, 0}, {32, PCI1756_IDO, 2, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {4, PCI175x_BOARDID, 1, SDF_INTERNAL}, + { {0, 0, 0, 0} }, + IO_16b}, + {"pci1760", PCI_VENDOR_ID_ADVANTECH, 0x1760, 0, + TYPE_PCI1760, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, /* This card have own setup work */ + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {0, 0, 0, 0}, + { {0, 0, 0, 0} }, + IO_8b}, + {"pci1762", PCI_VENDOR_ID_ADVANTECH, 0x1762, PCIDIO_MAINREG, + TYPE_PCI1762, + { {0, 0, 0, 0}, {16, PCI1762_IDI, 1, 0} }, + { {0, 0, 0, 0}, {16, PCI1762_RO, 1, 0} }, + { {0, 0, 0, 0}, {0, 0, 0, 0} }, + {4, PCI1762_BOARDID, 1, SDF_INTERNAL}, + { {0, 0, 0, 0} }, + IO_16b} +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct dio_boardtype)) + +static struct comedi_driver driver_pci_dio = { + .driver_name = "adv_pci_dio", + .module = THIS_MODULE, + .attach = pci_dio_attach, + .detach = pci_dio_detach +}; + +struct pci_dio_private { + struct pci_dio_private *prev; /* previous private struct */ + struct pci_dio_private *next; /* next private struct */ + struct pci_dev *pcidev; /* pointer to board's pci_dev */ + char valid; /* card is usable */ + char GlobalIrqEnabled; /* 1= any IRQ source is enabled */ + /* PCI-1760 specific data */ + unsigned char IDICntEnable; /* counter's counting enable status */ + unsigned char IDICntOverEnable; /* counter's overflow interrupts enable + * status */ + unsigned char IDICntMatchEnable; /* counter's match interrupts + * enable status */ + unsigned char IDICntEdge; /* counter's count edge value + * (bit=0 - rising, =1 - falling) */ + unsigned short CntResValue[8]; /* counters' reset value */ + unsigned short CntMatchValue[8]; /* counters' match interrupt value */ + unsigned char IDIFiltersEn; /* IDI's digital filters enable status */ + unsigned char IDIPatMatchEn; /* IDI's pattern match enable status */ + unsigned char IDIPatMatchValue; /* IDI's pattern match value */ + unsigned short IDIFiltrLow[8]; /* IDI's filter value low signal */ + unsigned short IDIFiltrHigh[8]; /* IDI's filter value high signal */ +}; + +static struct pci_dio_private *pci_priv; /* list of allocated cards */ + +#define devpriv ((struct pci_dio_private *)dev->private) +#define this_board ((const struct dio_boardtype *)dev->board_ptr) + +/* +============================================================================== +*/ +static int pci_dio_insn_bits_di_b(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct diosubd_data *d = (const struct diosubd_data *)s->private; + int i; + + data[1] = 0; + for (i = 0; i < d->regs; i++) + data[1] |= inb(dev->iobase + d->addr + i) << (8 * i); + + + return 2; +} + +/* +============================================================================== +*/ +static int pci_dio_insn_bits_di_w(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct diosubd_data *d = (const struct diosubd_data *)s->private; + int i; + + data[1] = 0; + for (i = 0; i < d->regs; i++) + data[1] |= inw(dev->iobase + d->addr + 2 * i) << (16 * i); + + return 2; +} + +/* +============================================================================== +*/ +static int pci_dio_insn_bits_do_b(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct diosubd_data *d = (const struct diosubd_data *)s->private; + int i; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + for (i = 0; i < d->regs; i++) + outb((s->state >> (8 * i)) & 0xff, + dev->iobase + d->addr + i); + } + data[1] = s->state; + + return 2; +} + +/* +============================================================================== +*/ +static int pci_dio_insn_bits_do_w(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct diosubd_data *d = (const struct diosubd_data *)s->private; + int i; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + for (i = 0; i < d->regs; i++) + outw((s->state >> (16 * i)) & 0xffff, + dev->iobase + d->addr + 2 * i); + } + data[1] = s->state; + + return 2; +} + +/* +============================================================================== +*/ +static int pci_8254_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct diosubd_data *d = (const struct diosubd_data *)s->private; + unsigned int chan, chip, chipchan; + unsigned long flags; + + chan = CR_CHAN(insn->chanspec); /* channel on subdevice */ + chip = chan / 3; /* chip on subdevice */ + chipchan = chan - (3 * chip); /* channel on chip on subdevice */ + spin_lock_irqsave(&s->spin_lock, flags); + data[0] = i8254_read(dev->iobase + d->addr + (SIZE_8254 * chip), + 0, chipchan); + spin_unlock_irqrestore(&s->spin_lock, flags); + return 1; +} + +/* +============================================================================== +*/ +static int pci_8254_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct diosubd_data *d = (const struct diosubd_data *)s->private; + unsigned int chan, chip, chipchan; + unsigned long flags; + + chan = CR_CHAN(insn->chanspec); /* channel on subdevice */ + chip = chan / 3; /* chip on subdevice */ + chipchan = chan - (3 * chip); /* channel on chip on subdevice */ + spin_lock_irqsave(&s->spin_lock, flags); + i8254_write(dev->iobase + d->addr + (SIZE_8254 * chip), + 0, chipchan, data[0]); + spin_unlock_irqrestore(&s->spin_lock, flags); + return 1; +} + +/* +============================================================================== +*/ +static int pci_8254_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const struct diosubd_data *d = (const struct diosubd_data *)s->private; + unsigned int chan, chip, chipchan; + unsigned long iobase; + int ret = 0; + unsigned long flags; + + chan = CR_CHAN(insn->chanspec); /* channel on subdevice */ + chip = chan / 3; /* chip on subdevice */ + chipchan = chan - (3 * chip); /* channel on chip on subdevice */ + iobase = dev->iobase + d->addr + (SIZE_8254 * chip); + spin_lock_irqsave(&s->spin_lock, flags); + switch (data[0]) { + case INSN_CONFIG_SET_COUNTER_MODE: + ret = i8254_set_mode(iobase, 0, chipchan, data[1]); + if (ret < 0) + ret = -EINVAL; + break; + case INSN_CONFIG_8254_READ_STATUS: + data[1] = i8254_status(iobase, 0, chipchan); + break; + default: + ret = -EINVAL; + break; + } + spin_unlock_irqrestore(&s->spin_lock, flags); + return ret < 0 ? ret : insn->n; +} + +/* +============================================================================== +*/ +static int pci1760_unchecked_mbxrequest(struct comedi_device *dev, + unsigned char *omb, unsigned char *imb, + int repeats) +{ + int cnt, tout, ok = 0; + + for (cnt = 0; cnt < repeats; cnt++) { + outb(omb[0], dev->iobase + OMB0); + outb(omb[1], dev->iobase + OMB1); + outb(omb[2], dev->iobase + OMB2); + outb(omb[3], dev->iobase + OMB3); + for (tout = 0; tout < 251; tout++) { + imb[2] = inb(dev->iobase + IMB2); + if (imb[2] == omb[2]) { + imb[0] = inb(dev->iobase + IMB0); + imb[1] = inb(dev->iobase + IMB1); + imb[3] = inb(dev->iobase + IMB3); + ok = 1; + break; + } + udelay(1); + } + if (ok) + return 0; + } + + comedi_error(dev, "PCI-1760 mailbox request timeout!"); + return -ETIME; +} + +static int pci1760_clear_imb2(struct comedi_device *dev) +{ + unsigned char omb[4] = { 0x0, 0x0, CMD_ClearIMB2, 0x0 }; + unsigned char imb[4]; + /* check if imb2 is already clear */ + if (inb(dev->iobase + IMB2) == CMD_ClearIMB2) + return 0; + return pci1760_unchecked_mbxrequest(dev, omb, imb, OMBCMD_RETRY); +} + +static int pci1760_mbxrequest(struct comedi_device *dev, + unsigned char *omb, unsigned char *imb) +{ + if (omb[2] == CMD_ClearIMB2) { + comedi_error(dev, + "bug! this function should not be used for CMD_ClearIMB2 command"); + return -EINVAL; + } + if (inb(dev->iobase + IMB2) == omb[2]) { + int retval; + retval = pci1760_clear_imb2(dev); + if (retval < 0) + return retval; + } + return pci1760_unchecked_mbxrequest(dev, omb, imb, OMBCMD_RETRY); +} + +/* +============================================================================== +*/ +static int pci1760_insn_bits_di(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[1] = inb(dev->iobase + IMB3); + + return 2; +} + +/* +============================================================================== +*/ +static int pci1760_insn_bits_do(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int ret; + unsigned char omb[4] = { + 0x00, + 0x00, + CMD_SetRelaysOutput, + 0x00 + }; + unsigned char imb[4]; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + omb[0] = s->state; + ret = pci1760_mbxrequest(dev, omb, imb); + if (!ret) + return ret; + } + data[1] = s->state; + + return 2; +} + +/* +============================================================================== +*/ +static int pci1760_insn_cnt_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int ret, n; + unsigned char omb[4] = { + CR_CHAN(insn->chanspec) & 0x07, + 0x00, + CMD_GetIDICntCurValue, + 0x00 + }; + unsigned char imb[4]; + + for (n = 0; n < insn->n; n++) { + ret = pci1760_mbxrequest(dev, omb, imb); + if (!ret) + return ret; + data[n] = (imb[1] << 8) + imb[0]; + } + + return n; +} + +/* +============================================================================== +*/ +static int pci1760_insn_cnt_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int ret; + unsigned char chan = CR_CHAN(insn->chanspec) & 0x07; + unsigned char bitmask = 1 << chan; + unsigned char omb[4] = { + data[0] & 0xff, + (data[0] >> 8) & 0xff, + CMD_SetIDI0CntResetValue + chan, + 0x00 + }; + unsigned char imb[4]; + + /* Set reset value if different */ + if (devpriv->CntResValue[chan] != (data[0] & 0xffff)) { + ret = pci1760_mbxrequest(dev, omb, imb); + if (!ret) + return ret; + devpriv->CntResValue[chan] = data[0] & 0xffff; + } + + omb[0] = bitmask; /* reset counter to it reset value */ + omb[2] = CMD_ResetIDICounters; + ret = pci1760_mbxrequest(dev, omb, imb); + if (!ret) + return ret; + + /* start counter if it don't run */ + if (!(bitmask & devpriv->IDICntEnable)) { + omb[0] = bitmask; + omb[2] = CMD_EnableIDICounters; + ret = pci1760_mbxrequest(dev, omb, imb); + if (!ret) + return ret; + devpriv->IDICntEnable |= bitmask; + } + return 1; +} + +/* +============================================================================== +*/ +static int pci1760_reset(struct comedi_device *dev) +{ + int i; + unsigned char omb[4] = { 0x00, 0x00, 0x00, 0x00 }; + unsigned char imb[4]; + + outb(0, dev->iobase + INTCSR0); /* disable IRQ */ + outb(0, dev->iobase + INTCSR1); + outb(0, dev->iobase + INTCSR2); + outb(0, dev->iobase + INTCSR3); + devpriv->GlobalIrqEnabled = 0; + + omb[0] = 0x00; + omb[2] = CMD_SetRelaysOutput; /* reset relay outputs */ + pci1760_mbxrequest(dev, omb, imb); + + omb[0] = 0x00; + omb[2] = CMD_EnableIDICounters; /* disable IDI up counters */ + pci1760_mbxrequest(dev, omb, imb); + devpriv->IDICntEnable = 0; + + omb[0] = 0x00; + omb[2] = CMD_OverflowIDICounters; /* disable counters overflow + * interrupts */ + pci1760_mbxrequest(dev, omb, imb); + devpriv->IDICntOverEnable = 0; + + omb[0] = 0x00; + omb[2] = CMD_MatchIntIDICounters; /* disable counters match value + * interrupts */ + pci1760_mbxrequest(dev, omb, imb); + devpriv->IDICntMatchEnable = 0; + + omb[0] = 0x00; + omb[1] = 0x80; + for (i = 0; i < 8; i++) { /* set IDI up counters match value */ + omb[2] = CMD_SetIDI0CntMatchValue + i; + pci1760_mbxrequest(dev, omb, imb); + devpriv->CntMatchValue[i] = 0x8000; + } + + omb[0] = 0x00; + omb[1] = 0x00; + for (i = 0; i < 8; i++) { /* set IDI up counters reset value */ + omb[2] = CMD_SetIDI0CntResetValue + i; + pci1760_mbxrequest(dev, omb, imb); + devpriv->CntResValue[i] = 0x0000; + } + + omb[0] = 0xff; + omb[2] = CMD_ResetIDICounters; /* reset IDI up counters to reset + * values */ + pci1760_mbxrequest(dev, omb, imb); + + omb[0] = 0x00; + omb[2] = CMD_EdgeIDICounters; /* set IDI up counters count edge */ + pci1760_mbxrequest(dev, omb, imb); + devpriv->IDICntEdge = 0x00; + + omb[0] = 0x00; + omb[2] = CMD_EnableIDIFilters; /* disable all digital in filters */ + pci1760_mbxrequest(dev, omb, imb); + devpriv->IDIFiltersEn = 0x00; + + omb[0] = 0x00; + omb[2] = CMD_EnableIDIPatternMatch; /* disable pattern matching */ + pci1760_mbxrequest(dev, omb, imb); + devpriv->IDIPatMatchEn = 0x00; + + omb[0] = 0x00; + omb[2] = CMD_SetIDIPatternMatch; /* set pattern match value */ + pci1760_mbxrequest(dev, omb, imb); + devpriv->IDIPatMatchValue = 0x00; + + return 0; +} + +/* +============================================================================== +*/ +static int pci_dio_reset(struct comedi_device *dev) +{ + DPRINTK("adv_pci_dio EDBG: BGN: pci171x_reset(...)\n"); + + switch (this_board->cardtype) { + case TYPE_PCI1730: + outb(0, dev->iobase + PCI1730_DO); /* clear outputs */ + outb(0, dev->iobase + PCI1730_DO + 1); + outb(0, dev->iobase + PCI1730_IDO); + outb(0, dev->iobase + PCI1730_IDO + 1); + /* NO break there! */ + case TYPE_PCI1733: + /* disable interrupts */ + outb(0, dev->iobase + PCI1730_3_INT_EN); + /* clear interrupts */ + outb(0x0f, dev->iobase + PCI1730_3_INT_CLR); + /* set rising edge trigger */ + outb(0, dev->iobase + PCI1730_3_INT_RF); + break; + case TYPE_PCI1734: + outb(0, dev->iobase + PCI1734_IDO); /* clear outputs */ + outb(0, dev->iobase + PCI1734_IDO + 1); + outb(0, dev->iobase + PCI1734_IDO + 2); + outb(0, dev->iobase + PCI1734_IDO + 3); + break; + case TYPE_PCI1735: + outb(0, dev->iobase + PCI1735_DO); /* clear outputs */ + outb(0, dev->iobase + PCI1735_DO + 1); + outb(0, dev->iobase + PCI1735_DO + 2); + outb(0, dev->iobase + PCI1735_DO + 3); + i8254_set_mode(dev->iobase + PCI1735_C8254, 0, 0, I8254_MODE0); + i8254_set_mode(dev->iobase + PCI1735_C8254, 0, 1, I8254_MODE0); + i8254_set_mode(dev->iobase + PCI1735_C8254, 0, 2, I8254_MODE0); + break; + + case TYPE_PCI1736: + outb(0, dev->iobase + PCI1736_IDO); + outb(0, dev->iobase + PCI1736_IDO + 1); + /* disable interrupts */ + outb(0, dev->iobase + PCI1736_3_INT_EN); + /* clear interrupts */ + outb(0x0f, dev->iobase + PCI1736_3_INT_CLR); + /* set rising edge trigger */ + outb(0, dev->iobase + PCI1736_3_INT_RF); + break; + + case TYPE_PCI1739: + /* disable & clear interrupts */ + outb(0x88, dev->iobase + PCI1739_ICR); + break; + + case TYPE_PCI1750: + case TYPE_PCI1751: + /* disable & clear interrupts */ + outb(0x88, dev->iobase + PCI1750_ICR); + break; + case TYPE_PCI1752: + outw(0, dev->iobase + PCI1752_6_CFC); /* disable channel freeze + * function */ + outw(0, dev->iobase + PCI1752_IDO); /* clear outputs */ + outw(0, dev->iobase + PCI1752_IDO + 2); + outw(0, dev->iobase + PCI1752_IDO2); + outw(0, dev->iobase + PCI1752_IDO2 + 2); + break; + case TYPE_PCI1753E: + outb(0x88, dev->iobase + PCI1753E_ICR0); /* disable & clear + * interrupts */ + outb(0x80, dev->iobase + PCI1753E_ICR1); + outb(0x80, dev->iobase + PCI1753E_ICR2); + outb(0x80, dev->iobase + PCI1753E_ICR3); + /* NO break there! */ + case TYPE_PCI1753: + outb(0x88, dev->iobase + PCI1753_ICR0); /* disable & clear + * interrupts */ + outb(0x80, dev->iobase + PCI1753_ICR1); + outb(0x80, dev->iobase + PCI1753_ICR2); + outb(0x80, dev->iobase + PCI1753_ICR3); + break; + case TYPE_PCI1754: + outw(0x08, dev->iobase + PCI1754_6_ICR0); /* disable and clear + * interrupts */ + outw(0x08, dev->iobase + PCI1754_6_ICR1); + outw(0x08, dev->iobase + PCI1754_ICR2); + outw(0x08, dev->iobase + PCI1754_ICR3); + break; + case TYPE_PCI1756: + outw(0, dev->iobase + PCI1752_6_CFC); /* disable channel freeze + * function */ + outw(0x08, dev->iobase + PCI1754_6_ICR0); /* disable and clear + * interrupts */ + outw(0x08, dev->iobase + PCI1754_6_ICR1); + outw(0, dev->iobase + PCI1756_IDO); /* clear outputs */ + outw(0, dev->iobase + PCI1756_IDO + 2); + break; + case TYPE_PCI1760: + pci1760_reset(dev); + break; + case TYPE_PCI1762: + outw(0x0101, dev->iobase + PCI1762_ICR); /* disable & clear + * interrupts */ + break; + } + + DPRINTK("adv_pci_dio EDBG: END: pci171x_reset(...)\n"); + + return 0; +} + +/* +============================================================================== +*/ +static int pci1760_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int subdev = 0; + + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 8; + s->maxdata = 1; + s->len_chanlist = 8; + s->range_table = &range_digital; + s->insn_bits = pci1760_insn_bits_di; + subdev++; + + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = 8; + s->maxdata = 1; + s->len_chanlist = 8; + s->range_table = &range_digital; + s->state = 0; + s->insn_bits = pci1760_insn_bits_do; + subdev++; + + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_WRITABLE | SDF_LSAMPL; + s->n_chan = 2; + s->maxdata = 0xffffffff; + s->len_chanlist = 2; +/* s->insn_config=pci1760_insn_pwm_cfg; */ + subdev++; + + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 0xffff; + s->len_chanlist = 8; + s->insn_read = pci1760_insn_cnt_read; + s->insn_write = pci1760_insn_cnt_write; +/* s->insn_config=pci1760_insn_cnt_cfg; */ + subdev++; + + return 0; +} + +/* +============================================================================== +*/ +static int pci_dio_add_di(struct comedi_device *dev, struct comedi_subdevice *s, + const struct diosubd_data *d, int subdev) +{ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON | d->specflags; + if (d->chans > 16) + s->subdev_flags |= SDF_LSAMPL; + s->n_chan = d->chans; + s->maxdata = 1; + s->len_chanlist = d->chans; + s->range_table = &range_digital; + switch (this_board->io_access) { + case IO_8b: + s->insn_bits = pci_dio_insn_bits_di_b; + break; + case IO_16b: + s->insn_bits = pci_dio_insn_bits_di_w; + break; + } + s->private = (void *)d; + + return 0; +} + +/* +============================================================================== +*/ +static int pci_dio_add_do(struct comedi_device *dev, struct comedi_subdevice *s, + const struct diosubd_data *d, int subdev) +{ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + if (d->chans > 16) + s->subdev_flags |= SDF_LSAMPL; + s->n_chan = d->chans; + s->maxdata = 1; + s->len_chanlist = d->chans; + s->range_table = &range_digital; + s->state = 0; + switch (this_board->io_access) { + case IO_8b: + s->insn_bits = pci_dio_insn_bits_do_b; + break; + case IO_16b: + s->insn_bits = pci_dio_insn_bits_do_w; + break; + } + s->private = (void *)d; + + return 0; +} + +/* +============================================================================== +*/ +static int pci_dio_add_8254(struct comedi_device *dev, + struct comedi_subdevice *s, + const struct diosubd_data *d, int subdev) +{ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = d->chans; + s->maxdata = 65535; + s->len_chanlist = d->chans; + s->insn_read = pci_8254_insn_read; + s->insn_write = pci_8254_insn_write; + s->insn_config = pci_8254_insn_config; + s->private = (void *)d; + + return 0; +} + +/* +============================================================================== +*/ +static int CheckAndAllocCard(struct comedi_device *dev, + struct comedi_devconfig *it, + struct pci_dev *pcidev) +{ + struct pci_dio_private *pr, *prev; + + for (pr = pci_priv, prev = NULL; pr != NULL; prev = pr, pr = pr->next) { + if (pr->pcidev == pcidev) + return 0; /* this card is used, look for another */ + + } + + if (prev) { + devpriv->prev = prev; + prev->next = devpriv; + } else { + pci_priv = devpriv; + } + + devpriv->pcidev = pcidev; + + return 1; +} + +/* +============================================================================== +*/ +static int pci_dio_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret, subdev, n_subdevices, i, j; + unsigned long iobase; + struct pci_dev *pcidev = NULL; + + + ret = alloc_private(dev, sizeof(struct pci_dio_private)); + if (ret < 0) + return -ENOMEM; + + for_each_pci_dev(pcidev) { + /* loop through cards supported by this driver */ + for (i = 0; i < n_boardtypes; ++i) { + if (boardtypes[i].vendor_id != pcidev->vendor) + continue; + if (boardtypes[i].device_id != pcidev->device) + continue; + /* was a particular bus/slot requested? */ + if (it->options[0] || it->options[1]) { + /* are we on the wrong bus/slot? */ + if (pcidev->bus->number != it->options[0] || + PCI_SLOT(pcidev->devfn) != it->options[1]) { + continue; + } + } + ret = CheckAndAllocCard(dev, it, pcidev); + if (ret != 1) + continue; + dev->board_ptr = boardtypes + i; + break; + } + if (dev->board_ptr) + break; + } + + if (!dev->board_ptr) { + dev_err(dev->hw_dev, "Error: Requested type of the card was not found!\n"); + return -EIO; + } + + if (comedi_pci_enable(pcidev, driver_pci_dio.driver_name)) { + dev_err(dev->hw_dev, "Error: Can't enable PCI device and request regions!\n"); + return -EIO; + } + iobase = pci_resource_start(pcidev, this_board->main_pci_region); + dev_dbg(dev->hw_dev, "b:s:f=%d:%d:%d, io=0x%4lx\n", + pcidev->bus->number, PCI_SLOT(pcidev->devfn), + PCI_FUNC(pcidev->devfn), iobase); + + dev->iobase = iobase; + dev->board_name = this_board->name; + + if (this_board->cardtype == TYPE_PCI1760) { + n_subdevices = 4; /* 8 IDI, 8 IDO, 2 PWM, 8 CNT */ + } else { + n_subdevices = 0; + for (i = 0; i < MAX_DI_SUBDEVS; i++) + if (this_board->sdi[i].chans) + n_subdevices++; + for (i = 0; i < MAX_DO_SUBDEVS; i++) + if (this_board->sdo[i].chans) + n_subdevices++; + for (i = 0; i < MAX_DIO_SUBDEVG; i++) + n_subdevices += this_board->sdio[i].regs; + if (this_board->boardid.chans) + n_subdevices++; + for (i = 0; i < MAX_8254_SUBDEVS; i++) + if (this_board->s8254[i].chans) + n_subdevices++; + } + + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) + return ret; + + subdev = 0; + for (i = 0; i < MAX_DI_SUBDEVS; i++) + if (this_board->sdi[i].chans) { + s = dev->subdevices + subdev; + pci_dio_add_di(dev, s, &this_board->sdi[i], subdev); + subdev++; + } + + for (i = 0; i < MAX_DO_SUBDEVS; i++) + if (this_board->sdo[i].chans) { + s = dev->subdevices + subdev; + pci_dio_add_do(dev, s, &this_board->sdo[i], subdev); + subdev++; + } + + for (i = 0; i < MAX_DIO_SUBDEVG; i++) + for (j = 0; j < this_board->sdio[i].regs; j++) { + s = dev->subdevices + subdev; + subdev_8255_init(dev, s, NULL, + dev->iobase + + this_board->sdio[i].addr + + SIZE_8255 * j); + subdev++; + } + + if (this_board->boardid.chans) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DI; + pci_dio_add_di(dev, s, &this_board->boardid, subdev); + subdev++; + } + + for (i = 0; i < MAX_8254_SUBDEVS; i++) + if (this_board->s8254[i].chans) { + s = dev->subdevices + subdev; + pci_dio_add_8254(dev, s, &this_board->s8254[i], subdev); + subdev++; + } + + if (this_board->cardtype == TYPE_PCI1760) + pci1760_attach(dev, it); + + devpriv->valid = 1; + + pci_dio_reset(dev); + + return 0; +} + +/* +============================================================================== +*/ +static int pci_dio_detach(struct comedi_device *dev) +{ + int i, j; + struct comedi_subdevice *s; + int subdev; + + if (dev->private) { + if (devpriv->valid) + pci_dio_reset(dev); + + + /* This shows the silliness of using this kind of + * scheme for numbering subdevices. Don't do it. --ds */ + subdev = 0; + for (i = 0; i < MAX_DI_SUBDEVS; i++) { + if (this_board->sdi[i].chans) + subdev++; + + } + for (i = 0; i < MAX_DO_SUBDEVS; i++) { + if (this_board->sdo[i].chans) + subdev++; + + } + for (i = 0; i < MAX_DIO_SUBDEVG; i++) { + for (j = 0; j < this_board->sdio[i].regs; j++) { + s = dev->subdevices + subdev; + subdev_8255_cleanup(dev, s); + subdev++; + } + } + + if (this_board->boardid.chans) + subdev++; + + for (i = 0; i < MAX_8254_SUBDEVS; i++) + if (this_board->s8254[i].chans) + subdev++; + + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + s->private = NULL; + } + + if (devpriv->pcidev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pcidev); + + pci_dev_put(devpriv->pcidev); + } + + if (devpriv->prev) + devpriv->prev->next = devpriv->next; + else + pci_priv = devpriv->next; + + if (devpriv->next) + devpriv->next->prev = devpriv->prev; + + } + + return 0; +} + +/* +============================================================================== +*/ +static int __devinit driver_pci_dio_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_pci_dio.driver_name); +} + +static void __devexit driver_pci_dio_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_pci_dio_pci_driver = { + .id_table = pci_dio_pci_table, + .probe = &driver_pci_dio_pci_probe, + .remove = __devexit_p(&driver_pci_dio_pci_remove) +}; + +static int __init driver_pci_dio_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_pci_dio); + if (retval < 0) + return retval; + + driver_pci_dio_pci_driver.name = (char *)driver_pci_dio.driver_name; + return pci_register_driver(&driver_pci_dio_pci_driver); +} + +static void __exit driver_pci_dio_cleanup_module(void) +{ + pci_unregister_driver(&driver_pci_dio_pci_driver); + comedi_driver_unregister(&driver_pci_dio); +} + +module_init(driver_pci_dio_init_module); +module_exit(driver_pci_dio_cleanup_module); +/* +============================================================================== +*/ + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/aio_aio12_8.c b/drivers/staging/comedi/drivers/aio_aio12_8.c new file mode 100644 index 00000000..b0f98e5e --- /dev/null +++ b/drivers/staging/comedi/drivers/aio_aio12_8.c @@ -0,0 +1,245 @@ +/* + + comedi/drivers/aio_aio12_8.c + + Driver for Access I/O Products PC-104 AIO12-8 Analog I/O Board + Copyright (C) 2006 C&C Technologies, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + +Driver: aio_aio12_8 +Description: Access I/O Products PC-104 AIO12-8 Analog I/O Board +Author: Pablo Mejia +Devices: + [Access I/O] PC-104 AIO12-8 +Status: experimental + +Configuration Options: + [0] - I/O port base address + +Notes: + + Only synchronous operations are supported. + +*/ + +#include "../comedidev.h" +#include +#include "8255.h" + +#define AIO12_8_STATUS 0x00 +#define AIO12_8_INTERRUPT 0x01 +#define AIO12_8_ADC 0x02 +#define AIO12_8_DAC_0 0x04 +#define AIO12_8_DAC_1 0x06 +#define AIO12_8_DAC_2 0x08 +#define AIO12_8_DAC_3 0x0A +#define AIO12_8_COUNTER_0 0x0C +#define AIO12_8_COUNTER_1 0x0D +#define AIO12_8_COUNTER_2 0x0E +#define AIO12_8_COUNTER_CONTROL 0x0F +#define AIO12_8_DIO_0 0x10 +#define AIO12_8_DIO_1 0x11 +#define AIO12_8_DIO_2 0x12 +#define AIO12_8_DIO_STATUS 0x13 +#define AIO12_8_DIO_CONTROL 0x14 +#define AIO12_8_ADC_TRIGGER_CONTROL 0x15 +#define AIO12_8_TRIGGER 0x16 +#define AIO12_8_POWER 0x17 + +#define STATUS_ADC_EOC 0x80 + +#define ADC_MODE_NORMAL 0x00 +#define ADC_MODE_INTERNAL_CLOCK 0x40 +#define ADC_MODE_STANDBY 0x80 +#define ADC_MODE_POWERDOWN 0xC0 + +#define DAC_ENABLE 0x18 + +struct aio12_8_boardtype { + const char *name; +}; + +static const struct aio12_8_boardtype board_types[] = { + { + .name = "aio_aio12_8"}, +}; + +#define thisboard ((const struct aio12_8_boardtype *) dev->board_ptr) + +struct aio12_8_private { + unsigned int ao_readback[4]; +}; + +#define devpriv ((struct aio12_8_private *) dev->private) + +static int aio_aio12_8_ai_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + unsigned char control = + ADC_MODE_NORMAL | + (CR_RANGE(insn->chanspec) << 3) | CR_CHAN(insn->chanspec); + + /* read status to clear EOC latch */ + inb(dev->iobase + AIO12_8_STATUS); + + for (n = 0; n < insn->n; n++) { + int timeout = 5; + + /* Setup and start conversion */ + outb(control, dev->iobase + AIO12_8_ADC); + + /* Wait for conversion to complete */ + while (timeout && + !(inb(dev->iobase + AIO12_8_STATUS) & STATUS_ADC_EOC)) { + timeout--; + printk(KERN_ERR "timeout %d\n", timeout); + udelay(1); + } + if (timeout == 0) { + comedi_error(dev, "ADC timeout"); + return -EIO; + } + + data[n] = inw(dev->iobase + AIO12_8_ADC) & 0x0FFF; + } + return n; +} + +static int aio_aio12_8_ao_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int val = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + + for (i = 0; i < insn->n; i++) + data[i] = val; + return insn->n; +} + +static int aio_aio12_8_ao_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + unsigned long port = dev->iobase + AIO12_8_DAC_0 + (2 * chan); + + /* enable DACs */ + outb(0x01, dev->iobase + DAC_ENABLE); + + for (i = 0; i < insn->n; i++) { + outb(data[i] & 0xFF, port); /* LSB */ + outb((data[i] >> 8) & 0x0F, port + 1); /* MSB */ + devpriv->ao_readback[chan] = data[i]; + } + return insn->n; +} + +static const struct comedi_lrange range_aio_aio12_8 = { + 4, + { + UNI_RANGE(5), + BIP_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(10), + } +}; + +static int aio_aio12_8_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int iobase; + struct comedi_subdevice *s; + + iobase = it->options[0]; + if (!request_region(iobase, 24, "aio_aio12_8")) { + printk(KERN_ERR "I/O port conflict"); + return -EIO; + } + + dev->board_name = thisboard->name; + + dev->iobase = iobase; + + if (alloc_private(dev, sizeof(struct aio12_8_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 3) < 0) + return -ENOMEM; + + s = &dev->subdevices[0]; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = 8; + s->maxdata = (1 << 12) - 1; + s->range_table = &range_aio_aio12_8; + s->insn_read = aio_aio12_8_ai_read; + + s = &dev->subdevices[1]; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = 4; + s->maxdata = (1 << 12) - 1; + s->range_table = &range_aio_aio12_8; + s->insn_read = aio_aio12_8_ao_read; + s->insn_write = aio_aio12_8_ao_write; + + s = &dev->subdevices[2]; + subdev_8255_init(dev, s, NULL, dev->iobase + AIO12_8_DIO_0); + + return 0; +} + +static int aio_aio12_8_detach(struct comedi_device *dev) +{ + subdev_8255_cleanup(dev, &dev->subdevices[2]); + if (dev->iobase) + release_region(dev->iobase, 24); + return 0; +} + +static struct comedi_driver driver_aio_aio12_8 = { + .driver_name = "aio_aio12_8", + .module = THIS_MODULE, + .attach = aio_aio12_8_attach, + .detach = aio_aio12_8_detach, + .board_name = &board_types[0].name, + .num_names = 1, + .offset = sizeof(struct aio12_8_boardtype), +}; + +static int __init driver_aio_aio12_8_init_module(void) +{ + return comedi_driver_register(&driver_aio_aio12_8); +} + +static void __exit driver_aio_aio12_8_cleanup_module(void) +{ + comedi_driver_unregister(&driver_aio_aio12_8); +} + +module_init(driver_aio_aio12_8_init_module); +module_exit(driver_aio_aio12_8_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/aio_iiro_16.c b/drivers/staging/comedi/drivers/aio_iiro_16.c new file mode 100644 index 00000000..160b0a0f --- /dev/null +++ b/drivers/staging/comedi/drivers/aio_iiro_16.c @@ -0,0 +1,202 @@ +/* + + comedi/drivers/aio_iiro_16.c + + Driver for Access I/O Products PC-104 AIO-IIRO-16 Digital I/O board + Copyright (C) 2006 C&C Technologies, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + +Driver: aio_iiro_16 +Description: Access I/O Products PC-104 IIRO16 Relay And Isolated Input Board +Author: Zachary Ware +Devices: + [Access I/O] PC-104 AIO12-8 +Status: experimental + +Configuration Options: + [0] - I/O port base address + +*/ + +#include "../comedidev.h" +#include + +#define AIO_IIRO_16_SIZE 0x08 +#define AIO_IIRO_16_RELAY_0_7 0x00 +#define AIO_IIRO_16_INPUT_0_7 0x01 +#define AIO_IIRO_16_IRQ 0x02 +#define AIO_IIRO_16_RELAY_8_15 0x04 +#define AIO_IIRO_16_INPUT_8_15 0x05 + +struct aio_iiro_16_board { + const char *name; + int do_; + int di; +}; + +static const struct aio_iiro_16_board aio_iiro_16_boards[] = { + { + .name = "aio_iiro_16", + .di = 16, + .do_ = 16}, +}; + +#define thisboard ((const struct aio_iiro_16_board *) dev->board_ptr) + +struct aio_iiro_16_private { + int data; + struct pci_dev *pci_dev; + unsigned int ao_readback[2]; +}; + +#define devpriv ((struct aio_iiro_16_private *) dev->private) + +static int aio_iiro_16_attach(struct comedi_device *dev, + struct comedi_devconfig *it); + +static int aio_iiro_16_detach(struct comedi_device *dev); + +static struct comedi_driver driver_aio_iiro_16 = { + .driver_name = "aio_iiro_16", + .module = THIS_MODULE, + .attach = aio_iiro_16_attach, + .detach = aio_iiro_16_detach, + .board_name = &aio_iiro_16_boards[0].name, + .offset = sizeof(struct aio_iiro_16_board), + .num_names = ARRAY_SIZE(aio_iiro_16_boards), +}; + +static int aio_iiro_16_dio_insn_bits_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int aio_iiro_16_dio_insn_bits_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int aio_iiro_16_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int iobase; + struct comedi_subdevice *s; + + printk(KERN_INFO "comedi%d: aio_iiro_16: ", dev->minor); + + dev->board_name = thisboard->name; + + iobase = it->options[0]; + + if (!request_region(iobase, AIO_IIRO_16_SIZE, dev->board_name)) { + printk("I/O port conflict"); + return -EIO; + } + + dev->iobase = iobase; + + if (alloc_private(dev, sizeof(struct aio_iiro_16_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 2) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = aio_iiro_16_dio_insn_bits_write; + + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = aio_iiro_16_dio_insn_bits_read; + + printk("attached\n"); + + return 1; +} + +static int aio_iiro_16_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: aio_iiro_16: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, AIO_IIRO_16_SIZE); + + return 0; +} + +static int aio_iiro_16_dio_insn_bits_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + outb(s->state & 0xff, dev->iobase + AIO_IIRO_16_RELAY_0_7); + outb((s->state >> 8) & 0xff, + dev->iobase + AIO_IIRO_16_RELAY_8_15); + } + + data[1] = s->state; + + return 2; +} + +static int aio_iiro_16_dio_insn_bits_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = 0; + data[1] |= inb(dev->iobase + AIO_IIRO_16_INPUT_0_7); + data[1] |= inb(dev->iobase + AIO_IIRO_16_INPUT_8_15) << 8; + + return 2; +} + +static int __init driver_aio_iiro_16_init_module(void) +{ + return comedi_driver_register(&driver_aio_iiro_16); +} + +static void __exit driver_aio_iiro_16_cleanup_module(void) +{ + comedi_driver_unregister(&driver_aio_iiro_16); +} + +module_init(driver_aio_iiro_16_init_module); +module_exit(driver_aio_iiro_16_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/am9513.h b/drivers/staging/comedi/drivers/am9513.h new file mode 100644 index 00000000..0bb839e5 --- /dev/null +++ b/drivers/staging/comedi/drivers/am9513.h @@ -0,0 +1,79 @@ +/* + module/am9513.h + value added preprocessor definitions for Am9513 timer chip + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _AM9513_H_ +#define _AM9513_H_ + +#if 0 + +/* + * Before including this file, the following need to be defined: + */ +#define Am9513_8BITBUS xxx +/* or */ +#define Am9513_16BITBUS xxx + +#define Am9513_output_control(a) xxx +#define Am9513_input_status() xxx +#define Am9513_output_data(a) xxx +#define Am9513_input_data() xxx + +#endif + +/* + * + */ + +#ifdef Am9513_8BITBUS + +#define Am9513_write_register(reg, val) \ + do { \ + Am9513_output_control(reg); \ + Am9513_output_data(val>>8); \ + Am9513_output_data(val&0xff); \ + } while (0) + +#define Am9513_read_register(reg, val) \ + do { \ + Am9513_output_control(reg); \ + val = Am9513_input_data()<<8; \ + val |= Am9513_input_data(); \ + } while (0) + +#else /* Am9513_16BITBUS */ + +#define Am9513_write_register(reg, val) \ + do { \ + Am9513_output_control(reg); \ + Am9513_output_data(val); \ + } while (0) + +#define Am9513_read_register(reg, val) \ + do { \ + Am9513_output_control(reg); \ + val = Am9513_input_data(); \ + } while (0) + +#endif + +#endif diff --git a/drivers/staging/comedi/drivers/amcc_s5933.h b/drivers/staging/comedi/drivers/amcc_s5933.h new file mode 100644 index 00000000..b810d5f3 --- /dev/null +++ b/drivers/staging/comedi/drivers/amcc_s5933.h @@ -0,0 +1,172 @@ +/* + comedi/drivers/amcc_s5933.h + + Stuff for AMCC S5933 PCI Controller + + Author: Michal Dobes + + Inspirated from general-purpose AMCC S5933 PCI Matchmaker driver + made by Andrea Cisternino + and as result of espionage from MITE code made by David A. Schleef. + Thanks to AMCC for their on-line documentation and bus master DMA + example. +*/ + +#ifndef _AMCC_S5933_H_ +#define _AMCC_S5933_H_ + +/****************************************************************************/ +/* AMCC Operation Register Offsets - PCI */ +/****************************************************************************/ + +#define AMCC_OP_REG_OMB1 0x00 +#define AMCC_OP_REG_OMB2 0x04 +#define AMCC_OP_REG_OMB3 0x08 +#define AMCC_OP_REG_OMB4 0x0c +#define AMCC_OP_REG_IMB1 0x10 +#define AMCC_OP_REG_IMB2 0x14 +#define AMCC_OP_REG_IMB3 0x18 +#define AMCC_OP_REG_IMB4 0x1c +#define AMCC_OP_REG_FIFO 0x20 +#define AMCC_OP_REG_MWAR 0x24 +#define AMCC_OP_REG_MWTC 0x28 +#define AMCC_OP_REG_MRAR 0x2c +#define AMCC_OP_REG_MRTC 0x30 +#define AMCC_OP_REG_MBEF 0x34 +#define AMCC_OP_REG_INTCSR 0x38 +#define AMCC_OP_REG_INTCSR_SRC (AMCC_OP_REG_INTCSR + 2) /* INT source */ +#define AMCC_OP_REG_INTCSR_FEC (AMCC_OP_REG_INTCSR + 3) /* FIFO ctrl */ +#define AMCC_OP_REG_MCSR 0x3c +#define AMCC_OP_REG_MCSR_NVDATA (AMCC_OP_REG_MCSR + 2) /* Data in byte 2 */ +#define AMCC_OP_REG_MCSR_NVCMD (AMCC_OP_REG_MCSR + 3) /* Command in byte 3 */ + +#define AMCC_FIFO_DEPTH_DWORD 8 +#define AMCC_FIFO_DEPTH_BYTES (8 * sizeof (u32)) + +/****************************************************************************/ +/* AMCC - PCI Interrupt Control/Status Register */ +/****************************************************************************/ +#define INTCSR_OUTBOX_BYTE(x) ((x) & 0x3) +#define INTCSR_OUTBOX_SELECT(x) (((x) & 0x3) << 2) +#define INTCSR_OUTBOX_EMPTY_INT 0x10 /* enable outbox empty interrupt */ +#define INTCSR_INBOX_BYTE(x) (((x) & 0x3) << 8) +#define INTCSR_INBOX_SELECT(x) (((x) & 0x3) << 10) +#define INTCSR_INBOX_FULL_INT 0x1000 /* enable inbox full interrupt */ +#define INTCSR_INBOX_INTR_STATUS 0x20000 /* read, or write clear inbox full interrupt */ +#define INTCSR_INTR_ASSERTED 0x800000 /* read only, interrupt asserted */ + +/****************************************************************************/ +/* AMCC - PCI non-volatile ram command register (byte 3 of master control/status register) */ +/****************************************************************************/ +#define MCSR_NV_LOAD_LOW_ADDR 0x0 +#define MCSR_NV_LOAD_HIGH_ADDR 0x20 +#define MCSR_NV_WRITE 0x40 +#define MCSR_NV_READ 0x60 +#define MCSR_NV_MASK 0x60 +#define MCSR_NV_ENABLE 0x80 +#define MCSR_NV_BUSY MCSR_NV_ENABLE + +/****************************************************************************/ +/* AMCC Operation Registers Size - PCI */ +/****************************************************************************/ + +#define AMCC_OP_REG_SIZE 64 /* in bytes */ + +/****************************************************************************/ +/* AMCC Operation Register Offsets - Add-on */ +/****************************************************************************/ + +#define AMCC_OP_REG_AIMB1 0x00 +#define AMCC_OP_REG_AIMB2 0x04 +#define AMCC_OP_REG_AIMB3 0x08 +#define AMCC_OP_REG_AIMB4 0x0c +#define AMCC_OP_REG_AOMB1 0x10 +#define AMCC_OP_REG_AOMB2 0x14 +#define AMCC_OP_REG_AOMB3 0x18 +#define AMCC_OP_REG_AOMB4 0x1c +#define AMCC_OP_REG_AFIFO 0x20 +#define AMCC_OP_REG_AMWAR 0x24 +#define AMCC_OP_REG_APTA 0x28 +#define AMCC_OP_REG_APTD 0x2c +#define AMCC_OP_REG_AMRAR 0x30 +#define AMCC_OP_REG_AMBEF 0x34 +#define AMCC_OP_REG_AINT 0x38 +#define AMCC_OP_REG_AGCSTS 0x3c +#define AMCC_OP_REG_AMWTC 0x58 +#define AMCC_OP_REG_AMRTC 0x5c + +/****************************************************************************/ +/* AMCC - Add-on General Control/Status Register */ +/****************************************************************************/ + +#define AGCSTS_CONTROL_MASK 0xfffff000 +#define AGCSTS_NV_ACC_MASK 0xe0000000 +#define AGCSTS_RESET_MASK 0x0e000000 +#define AGCSTS_NV_DA_MASK 0x00ff0000 +#define AGCSTS_BIST_MASK 0x0000f000 +#define AGCSTS_STATUS_MASK 0x000000ff +#define AGCSTS_TCZERO_MASK 0x000000c0 +#define AGCSTS_FIFO_ST_MASK 0x0000003f + +#define AGCSTS_RESET_MBFLAGS 0x08000000 +#define AGCSTS_RESET_P2A_FIFO 0x04000000 +#define AGCSTS_RESET_A2P_FIFO 0x02000000 +#define AGCSTS_RESET_FIFOS (AGCSTS_RESET_A2P_FIFO | AGCSTS_RESET_P2A_FIFO) + +#define AGCSTS_A2P_TCOUNT 0x00000080 +#define AGCSTS_P2A_TCOUNT 0x00000040 + +#define AGCSTS_FS_P2A_EMPTY 0x00000020 +#define AGCSTS_FS_P2A_HALF 0x00000010 +#define AGCSTS_FS_P2A_FULL 0x00000008 + +#define AGCSTS_FS_A2P_EMPTY 0x00000004 +#define AGCSTS_FS_A2P_HALF 0x00000002 +#define AGCSTS_FS_A2P_FULL 0x00000001 + +/****************************************************************************/ +/* AMCC - Add-on Interrupt Control/Status Register */ +/****************************************************************************/ + +#define AINT_INT_MASK 0x00ff0000 +#define AINT_SEL_MASK 0x0000ffff +#define AINT_IS_ENSEL_MASK 0x00001f1f + +#define AINT_INT_ASSERTED 0x00800000 +#define AINT_BM_ERROR 0x00200000 +#define AINT_BIST_INT 0x00100000 + +#define AINT_RT_COMPLETE 0x00080000 +#define AINT_WT_COMPLETE 0x00040000 + +#define AINT_OUT_MB_INT 0x00020000 +#define AINT_IN_MB_INT 0x00010000 + +#define AINT_READ_COMPL 0x00008000 +#define AINT_WRITE_COMPL 0x00004000 + +#define AINT_OMB_ENABLE 0x00001000 +#define AINT_OMB_SELECT 0x00000c00 +#define AINT_OMB_BYTE 0x00000300 + +#define AINT_IMB_ENABLE 0x00000010 +#define AINT_IMB_SELECT 0x0000000c +#define AINT_IMB_BYTE 0x00000003 + +/* these are bits from various different registers, needs cleanup XXX */ +/* Enable Bus Mastering */ +#define EN_A2P_TRANSFERS 0x00000400 +/* FIFO Flag Reset */ +#define RESET_A2P_FLAGS 0x04000000L +/* FIFO Relative Priority */ +#define A2P_HI_PRIORITY 0x00000100L +/* Identify Interrupt Sources */ +#define ANY_S593X_INT 0x00800000L +#define READ_TC_INT 0x00080000L +#define WRITE_TC_INT 0x00040000L +#define IN_MB_INT 0x00020000L +#define MASTER_ABORT_INT 0x00100000L +#define TARGET_ABORT_INT 0x00200000L +#define BUS_MASTER_INT 0x00200000L + +#endif diff --git a/drivers/staging/comedi/drivers/amplc_dio200.c b/drivers/staging/comedi/drivers/amplc_dio200.c new file mode 100644 index 00000000..566cc441 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_dio200.c @@ -0,0 +1,1553 @@ +/* + comedi/drivers/amplc_dio200.c + Driver for Amplicon PC272E and PCI272 DIO boards. + (Support for other boards in Amplicon 200 series may be added at + a later date, e.g. PCI215.) + + Copyright (C) 2005 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998,2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: amplc_dio200 +Description: Amplicon 200 Series Digital I/O +Author: Ian Abbott +Devices: [Amplicon] PC212E (pc212e), PC214E (pc214e), PC215E (pc215e), + PCI215 (pci215 or amplc_dio200), PC218E (pc218e), PC272E (pc272e), + PCI272 (pci272 or amplc_dio200) +Updated: Wed, 22 Oct 2008 13:36:02 +0100 +Status: works + +Configuration options - PC212E, PC214E, PC215E, PC218E, PC272E: + [0] - I/O port base address + [1] - IRQ (optional, but commands won't work without it) + +Configuration options - PCI215, PCI272: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first available PCI device will + be used. + +Passing a zero for an option is the same as leaving it unspecified. + +SUBDEVICES + + PC218E PC212E PC215E/PCI215 + ------------- ------------- ------------- + Subdevices 7 6 5 + 0 CTR-X1 PPI-X PPI-X + 1 CTR-X2 CTR-Y1 PPI-Y + 2 CTR-Y1 CTR-Y2 CTR-Z1 + 3 CTR-Y2 CTR-Z1 CTR-Z2 + 4 CTR-Z1 CTR-Z2 INTERRUPT + 5 CTR-Z2 INTERRUPT + 6 INTERRUPT + + PC214E PC272E/PCI272 + ------------- ------------- + Subdevices 4 4 + 0 PPI-X PPI-X + 1 PPI-Y PPI-Y + 2 CTR-Z1* PPI-Z + 3 INTERRUPT* INTERRUPT + +Each PPI is a 8255 chip providing 24 DIO channels. The DIO channels +are configurable as inputs or outputs in four groups: + + Port A - channels 0 to 7 + Port B - channels 8 to 15 + Port CL - channels 16 to 19 + Port CH - channels 20 to 23 + +Only mode 0 of the 8255 chips is supported. + +Each CTR is a 8254 chip providing 3 16-bit counter channels. Each +channel is configured individually with INSN_CONFIG instructions. The +specific type of configuration instruction is specified in data[0]. +Some configuration instructions expect an additional parameter in +data[1]; others return a value in data[1]. The following configuration +instructions are supported: + + INSN_CONFIG_SET_COUNTER_MODE. Sets the counter channel's mode and + BCD/binary setting specified in data[1]. + + INSN_CONFIG_8254_READ_STATUS. Reads the status register value for the + counter channel into data[1]. + + INSN_CONFIG_SET_CLOCK_SRC. Sets the counter channel's clock source as + specified in data[1] (this is a hardware-specific value). Not + supported on PC214E. For the other boards, valid clock sources are + 0 to 7 as follows: + + 0. CLK n, the counter channel's dedicated CLK input from the SK1 + connector. (N.B. for other values, the counter channel's CLKn + pin on the SK1 connector is an output!) + 1. Internal 10 MHz clock. + 2. Internal 1 MHz clock. + 3. Internal 100 kHz clock. + 4. Internal 10 kHz clock. + 5. Internal 1 kHz clock. + 6. OUT n-1, the output of counter channel n-1 (see note 1 below). + 7. Ext Clock, the counter chip's dedicated Ext Clock input from + the SK1 connector. This pin is shared by all three counter + channels on the chip. + + INSN_CONFIG_GET_CLOCK_SRC. Returns the counter channel's current + clock source in data[1]. For internal clock sources, data[2] is set + to the period in ns. + + INSN_CONFIG_SET_GATE_SRC. Sets the counter channel's gate source as + specified in data[2] (this is a hardware-specific value). Not + supported on PC214E. For the other boards, valid gate sources are 0 + to 7 as follows: + + 0. VCC (internal +5V d.c.), i.e. gate permanently enabled. + 1. GND (internal 0V d.c.), i.e. gate permanently disabled. + 2. GAT n, the counter channel's dedicated GAT input from the SK1 + connector. (N.B. for other values, the counter channel's GATn + pin on the SK1 connector is an output!) + 3. /OUT n-2, the inverted output of counter channel n-2 (see note + 2 below). + 4. Reserved. + 5. Reserved. + 6. Reserved. + 7. Reserved. + + INSN_CONFIG_GET_GATE_SRC. Returns the counter channel's current gate + source in data[2]. + +Clock and gate interconnection notes: + + 1. Clock source OUT n-1 is the output of the preceding channel on the + same counter subdevice if n > 0, or the output of channel 2 on the + preceding counter subdevice (see note 3) if n = 0. + + 2. Gate source /OUT n-2 is the inverted output of channel 0 on the + same counter subdevice if n = 2, or the inverted output of channel n+1 + on the preceding counter subdevice (see note 3) if n < 2. + + 3. The counter subdevices are connected in a ring, so the highest + counter subdevice precedes the lowest. + +The 'INTERRUPT' subdevice pretends to be a digital input subdevice. The +digital inputs come from the interrupt status register. The number of +channels matches the number of interrupt sources. The PC214E does not +have an interrupt status register; see notes on 'INTERRUPT SOURCES' +below. + +INTERRUPT SOURCES + + PC218E PC212E PC215E/PCI215 + ------------- ------------- ------------- + Sources 6 6 6 + 0 CTR-X1-OUT PPI-X-C0 PPI-X-C0 + 1 CTR-X2-OUT PPI-X-C3 PPI-X-C3 + 2 CTR-Y1-OUT CTR-Y1-OUT PPI-Y-C0 + 3 CTR-Y2-OUT CTR-Y2-OUT PPI-Y-C3 + 4 CTR-Z1-OUT CTR-Z1-OUT CTR-Z1-OUT + 5 CTR-Z2-OUT CTR-Z2-OUT CTR-Z2-OUT + + PC214E PC272E/PCI272 + ------------- ------------- + Sources 1 6 + 0 JUMPER-J5 PPI-X-C0 + 1 PPI-X-C3 + 2 PPI-Y-C0 + 3 PPI-Y-C3 + 4 PPI-Z-C0 + 5 PPI-Z-C3 + +When an interrupt source is enabled in the interrupt source enable +register, a rising edge on the source signal latches the corresponding +bit to 1 in the interrupt status register. + +When the interrupt status register value as a whole (actually, just the +6 least significant bits) goes from zero to non-zero, the board will +generate an interrupt. For level-triggered hardware interrupts (PCI +card), the interrupt will remain asserted until the interrupt status +register is cleared to zero. For edge-triggered hardware interrupts +(ISA card), no further interrupts will occur until the interrupt status +register is cleared to zero. To clear a bit to zero in the interrupt +status register, the corresponding interrupt source must be disabled +in the interrupt source enable register (there is no separate interrupt +clear register). + +The PC214E does not have an interrupt source enable register or an +interrupt status register; its 'INTERRUPT' subdevice has a single +channel and its interrupt source is selected by the position of jumper +J5. + +COMMANDS + +The driver supports a read streaming acquisition command on the +'INTERRUPT' subdevice. The channel list selects the interrupt sources +to be enabled. All channels will be sampled together (convert_src == +TRIG_NOW). The scan begins a short time after the hardware interrupt +occurs, subject to interrupt latencies (scan_begin_src == TRIG_EXT, +scan_begin_arg == 0). The value read from the interrupt status register +is packed into a short value, one bit per requested channel, in the +order they appear in the channel list. +*/ + +#include +#include + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#include "8255.h" +#include "8253.h" + +#define DIO200_DRIVER_NAME "amplc_dio200" + +/* PCI IDs */ +#define PCI_VENDOR_ID_AMPLICON 0x14dc +#define PCI_DEVICE_ID_AMPLICON_PCI272 0x000a +#define PCI_DEVICE_ID_AMPLICON_PCI215 0x000b +#define PCI_DEVICE_ID_INVALID 0xffff + +/* 200 series registers */ +#define DIO200_IO_SIZE 0x20 +#define DIO200_XCLK_SCE 0x18 /* Group X clock selection register */ +#define DIO200_YCLK_SCE 0x19 /* Group Y clock selection register */ +#define DIO200_ZCLK_SCE 0x1a /* Group Z clock selection register */ +#define DIO200_XGAT_SCE 0x1b /* Group X gate selection register */ +#define DIO200_YGAT_SCE 0x1c /* Group Y gate selection register */ +#define DIO200_ZGAT_SCE 0x1d /* Group Z gate selection register */ +#define DIO200_INT_SCE 0x1e /* Interrupt enable/status register */ + +/* + * Macros for constructing value for DIO_200_?CLK_SCE and + * DIO_200_?GAT_SCE registers: + * + * 'which' is: 0 for CTR-X1, CTR-Y1, CTR-Z1; 1 for CTR-X2, CTR-Y2 or CTR-Z2. + * 'chan' is the channel: 0, 1 or 2. + * 'source' is the signal source: 0 to 7. + */ +#define CLK_SCE(which, chan, source) (((which) << 5) | ((chan) << 3) | (source)) +#define GAT_SCE(which, chan, source) (((which) << 5) | ((chan) << 3) | (source)) + +/* + * Periods of the internal clock sources in nanoseconds. + */ +static const unsigned clock_period[8] = { + 0, /* dedicated clock input/output pin */ + 100, /* 10 MHz */ + 1000, /* 1 MHz */ + 10000, /* 100 kHz */ + 100000, /* 10 kHz */ + 1000000, /* 1 kHz */ + 0, /* OUT N-1 */ + 0 /* group clock input pin */ +}; + +/* + * Board descriptions. + */ + +enum dio200_bustype { isa_bustype, pci_bustype }; + +enum dio200_model { + pc212e_model, + pc214e_model, + pc215e_model, pci215_model, + pc218e_model, + pc272e_model, pci272_model, + anypci_model +}; + +enum dio200_layout { + pc212_layout, + pc214_layout, + pc215_layout, + pc218_layout, + pc272_layout +}; + +struct dio200_board { + const char *name; + unsigned short devid; + enum dio200_bustype bustype; + enum dio200_model model; + enum dio200_layout layout; +}; + +static const struct dio200_board dio200_boards[] = { + { + .name = "pc212e", + .bustype = isa_bustype, + .model = pc212e_model, + .layout = pc212_layout, + }, + { + .name = "pc214e", + .bustype = isa_bustype, + .model = pc214e_model, + .layout = pc214_layout, + }, + { + .name = "pc215e", + .bustype = isa_bustype, + .model = pc215e_model, + .layout = pc215_layout, + }, +#ifdef CONFIG_COMEDI_PCI + { + .name = "pci215", + .devid = PCI_DEVICE_ID_AMPLICON_PCI215, + .bustype = pci_bustype, + .model = pci215_model, + .layout = pc215_layout, + }, +#endif + { + .name = "pc218e", + .bustype = isa_bustype, + .model = pc218e_model, + .layout = pc218_layout, + }, + { + .name = "pc272e", + .bustype = isa_bustype, + .model = pc272e_model, + .layout = pc272_layout, + }, +#ifdef CONFIG_COMEDI_PCI + { + .name = "pci272", + .devid = PCI_DEVICE_ID_AMPLICON_PCI272, + .bustype = pci_bustype, + .model = pci272_model, + .layout = pc272_layout, + }, +#endif +#ifdef CONFIG_COMEDI_PCI + { + .name = DIO200_DRIVER_NAME, + .devid = PCI_DEVICE_ID_INVALID, + .bustype = pci_bustype, + .model = anypci_model, /* wildcard */ + }, +#endif +}; + +/* + * Layout descriptions - some ISA and PCI board descriptions share the same + * layout. + */ + +enum dio200_sdtype { sd_none, sd_intr, sd_8255, sd_8254 }; + +#define DIO200_MAX_SUBDEVS 7 +#define DIO200_MAX_ISNS 6 + +struct dio200_layout_struct { + unsigned short n_subdevs; /* number of subdevices */ + unsigned char sdtype[DIO200_MAX_SUBDEVS]; /* enum dio200_sdtype */ + unsigned char sdinfo[DIO200_MAX_SUBDEVS]; /* depends on sdtype */ + char has_int_sce; /* has interrupt enable/status register */ + char has_clk_gat_sce; /* has clock/gate selection registers */ +}; + +static const struct dio200_layout_struct dio200_layouts[] = { + [pc212_layout] = { + .n_subdevs = 6, + .sdtype = {sd_8255, sd_8254, sd_8254, sd_8254, + sd_8254, + sd_intr}, + .sdinfo = {0x00, 0x08, 0x0C, 0x10, 0x14, + 0x3F}, + .has_int_sce = 1, + .has_clk_gat_sce = 1, + }, + [pc214_layout] = { + .n_subdevs = 4, + .sdtype = {sd_8255, sd_8255, sd_8254, + sd_intr}, + .sdinfo = {0x00, 0x08, 0x10, 0x01}, + .has_int_sce = 0, + .has_clk_gat_sce = 0, + }, + [pc215_layout] = { + .n_subdevs = 5, + .sdtype = {sd_8255, sd_8255, sd_8254, + sd_8254, + sd_intr}, + .sdinfo = {0x00, 0x08, 0x10, 0x14, 0x3F}, + .has_int_sce = 1, + .has_clk_gat_sce = 1, + }, + [pc218_layout] = { + .n_subdevs = 7, + .sdtype = {sd_8254, sd_8254, sd_8255, sd_8254, + sd_8254, + sd_intr}, + .sdinfo = {0x00, 0x04, 0x08, 0x0C, 0x10, + 0x14, + 0x3F}, + .has_int_sce = 1, + .has_clk_gat_sce = 1, + }, + [pc272_layout] = { + .n_subdevs = 4, + .sdtype = {sd_8255, sd_8255, sd_8255, + sd_intr}, + .sdinfo = {0x00, 0x08, 0x10, 0x3F}, + .has_int_sce = 1, + .has_clk_gat_sce = 0, + }, +}; + +/* + * PCI driver table. + */ + +#ifdef CONFIG_COMEDI_PCI +static DEFINE_PCI_DEVICE_TABLE(dio200_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI215) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI272) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, dio200_pci_table); +#endif /* CONFIG_COMEDI_PCI */ + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct dio200_board *)dev->board_ptr) +#define thislayout (&dio200_layouts[((struct dio200_board *) \ + dev->board_ptr)->layout]) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. + */ +struct dio200_private { +#ifdef CONFIG_COMEDI_PCI + struct pci_dev *pci_dev; /* PCI device */ +#endif + int intr_sd; +}; + +#define devpriv ((struct dio200_private *)dev->private) + +struct dio200_subdev_8254 { + unsigned long iobase; /* Counter base address */ + unsigned long clk_sce_iobase; /* CLK_SCE base address */ + unsigned long gat_sce_iobase; /* GAT_SCE base address */ + int which; /* Bit 5 of CLK_SCE or GAT_SCE */ + int has_clk_gat_sce; + unsigned clock_src[3]; /* Current clock sources */ + unsigned gate_src[3]; /* Current gate sources */ + spinlock_t spinlock; +}; + +struct dio200_subdev_intr { + unsigned long iobase; + spinlock_t spinlock; + int active; + int has_int_sce; + unsigned int valid_isns; + unsigned int enabled_isns; + unsigned int stopcount; + int continuous; +}; + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int dio200_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dio200_detach(struct comedi_device *dev); +static struct comedi_driver driver_amplc_dio200 = { + .driver_name = DIO200_DRIVER_NAME, + .module = THIS_MODULE, + .attach = dio200_attach, + .detach = dio200_detach, + .board_name = &dio200_boards[0].name, + .offset = sizeof(struct dio200_board), + .num_names = ARRAY_SIZE(dio200_boards), +}; + +#ifdef CONFIG_COMEDI_PCI +static int __devinit driver_amplc_dio200_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_amplc_dio200.driver_name); +} + +static void __devexit driver_amplc_dio200_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_amplc_dio200_pci_driver = { + .id_table = dio200_pci_table, + .probe = &driver_amplc_dio200_pci_probe, + .remove = __devexit_p(&driver_amplc_dio200_pci_remove) +}; + +static int __init driver_amplc_dio200_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_amplc_dio200); + if (retval < 0) + return retval; + + driver_amplc_dio200_pci_driver.name = + (char *)driver_amplc_dio200.driver_name; + return pci_register_driver(&driver_amplc_dio200_pci_driver); +} + +static void __exit driver_amplc_dio200_cleanup_module(void) +{ + pci_unregister_driver(&driver_amplc_dio200_pci_driver); + comedi_driver_unregister(&driver_amplc_dio200); +} + +module_init(driver_amplc_dio200_init_module); +module_exit(driver_amplc_dio200_cleanup_module); +#else +static int __init driver_amplc_dio200_init_module(void) +{ + return comedi_driver_register(&driver_amplc_dio200); +} + +static void __exit driver_amplc_dio200_cleanup_module(void) +{ + comedi_driver_unregister(&driver_amplc_dio200); +} + +module_init(driver_amplc_dio200_init_module); +module_exit(driver_amplc_dio200_cleanup_module); +#endif + +/* + * This function looks for a PCI device matching the requested board name, + * bus and slot. + */ +#ifdef CONFIG_COMEDI_PCI +static int +dio200_find_pci(struct comedi_device *dev, int bus, int slot, + struct pci_dev **pci_dev_p) +{ + struct pci_dev *pci_dev = NULL; + + *pci_dev_p = NULL; + + /* Look for matching PCI device. */ + for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL); + pci_dev != NULL; + pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, + PCI_ANY_ID, pci_dev)) { + /* If bus/slot specified, check them. */ + if (bus || slot) { + if (bus != pci_dev->bus->number + || slot != PCI_SLOT(pci_dev->devfn)) + continue; + } + if (thisboard->model == anypci_model) { + /* Match any supported model. */ + int i; + + for (i = 0; i < ARRAY_SIZE(dio200_boards); i++) { + if (dio200_boards[i].bustype != pci_bustype) + continue; + if (pci_dev->device == dio200_boards[i].devid) { + /* Change board_ptr to matched board. */ + dev->board_ptr = &dio200_boards[i]; + break; + } + } + if (i == ARRAY_SIZE(dio200_boards)) + continue; + } else { + /* Match specific model name. */ + if (pci_dev->device != thisboard->devid) + continue; + } + + /* Found a match. */ + *pci_dev_p = pci_dev; + return 0; + } + /* No match found. */ + if (bus || slot) { + printk(KERN_ERR + "comedi%d: error! no %s found at pci %02x:%02x!\n", + dev->minor, thisboard->name, bus, slot); + } else { + printk(KERN_ERR "comedi%d: error! no %s found!\n", + dev->minor, thisboard->name); + } + return -EIO; +} +#endif + +/* + * This function checks and requests an I/O region, reporting an error + * if there is a conflict. + */ +static int +dio200_request_region(unsigned minor, unsigned long from, unsigned long extent) +{ + if (!from || !request_region(from, extent, DIO200_DRIVER_NAME)) { + printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n", + minor, from, extent); + return -EIO; + } + return 0; +} + +/* + * 'insn_bits' function for an 'INTERRUPT' subdevice. + */ +static int +dio200_subdev_intr_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dio200_subdev_intr *subpriv = s->private; + + if (subpriv->has_int_sce) { + /* Just read the interrupt status register. */ + data[1] = inb(subpriv->iobase) & subpriv->valid_isns; + } else { + /* No interrupt status register. */ + data[0] = 0; + } + + return 2; +} + +/* + * Called to stop acquisition for an 'INTERRUPT' subdevice. + */ +static void dio200_stop_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dio200_subdev_intr *subpriv = s->private; + + subpriv->active = 0; + subpriv->enabled_isns = 0; + if (subpriv->has_int_sce) + outb(0, subpriv->iobase); +} + +/* + * Called to start acquisition for an 'INTERRUPT' subdevice. + */ +static int dio200_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int n; + unsigned isn_bits; + struct dio200_subdev_intr *subpriv = s->private; + struct comedi_cmd *cmd = &s->async->cmd; + int retval = 0; + + if (!subpriv->continuous && subpriv->stopcount == 0) { + /* An empty acquisition! */ + s->async->events |= COMEDI_CB_EOA; + subpriv->active = 0; + retval = 1; + } else { + /* Determine interrupt sources to enable. */ + isn_bits = 0; + if (cmd->chanlist) { + for (n = 0; n < cmd->chanlist_len; n++) + isn_bits |= (1U << CR_CHAN(cmd->chanlist[n])); + } + isn_bits &= subpriv->valid_isns; + /* Enable interrupt sources. */ + subpriv->enabled_isns = isn_bits; + if (subpriv->has_int_sce) + outb(isn_bits, subpriv->iobase); + } + + return retval; +} + +/* + * Internal trigger function to start acquisition for an 'INTERRUPT' subdevice. + */ +static int +dio200_inttrig_start_intr(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum) +{ + struct dio200_subdev_intr *subpriv; + unsigned long flags; + int event = 0; + + if (trignum != 0) + return -EINVAL; + + subpriv = s->private; + + spin_lock_irqsave(&subpriv->spinlock, flags); + s->async->inttrig = NULL; + if (subpriv->active) + event = dio200_start_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + if (event) + comedi_event(dev, s); + + return 1; +} + +/* + * This is called from the interrupt service routine to handle a read + * scan on an 'INTERRUPT' subdevice. + */ +static int dio200_handle_read_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dio200_subdev_intr *subpriv = s->private; + unsigned triggered; + unsigned intstat; + unsigned cur_enabled; + unsigned int oldevents; + unsigned long flags; + + triggered = 0; + + spin_lock_irqsave(&subpriv->spinlock, flags); + oldevents = s->async->events; + if (subpriv->has_int_sce) { + /* + * Collect interrupt sources that have triggered and disable + * them temporarily. Loop around until no extra interrupt + * sources have triggered, at which point, the valid part of + * the interrupt status register will read zero, clearing the + * cause of the interrupt. + * + * Mask off interrupt sources already seen to avoid infinite + * loop in case of misconfiguration. + */ + cur_enabled = subpriv->enabled_isns; + while ((intstat = (inb(subpriv->iobase) & subpriv->valid_isns + & ~triggered)) != 0) { + triggered |= intstat; + cur_enabled &= ~triggered; + outb(cur_enabled, subpriv->iobase); + } + } else { + /* + * No interrupt status register. Assume the single interrupt + * source has triggered. + */ + triggered = subpriv->enabled_isns; + } + + if (triggered) { + /* + * Some interrupt sources have triggered and have been + * temporarily disabled to clear the cause of the interrupt. + * + * Reenable them NOW to minimize the time they are disabled. + */ + cur_enabled = subpriv->enabled_isns; + if (subpriv->has_int_sce) + outb(cur_enabled, subpriv->iobase); + + if (subpriv->active) { + /* + * The command is still active. + * + * Ignore interrupt sources that the command isn't + * interested in (just in case there's a race + * condition). + */ + if (triggered & subpriv->enabled_isns) { + /* Collect scan data. */ + short val; + unsigned int n, ch, len; + + val = 0; + len = s->async->cmd.chanlist_len; + for (n = 0; n < len; n++) { + ch = CR_CHAN(s->async->cmd.chanlist[n]); + if (triggered & (1U << ch)) + val |= (1U << n); + } + /* Write the scan to the buffer. */ + if (comedi_buf_put(s->async, val)) { + s->async->events |= (COMEDI_CB_BLOCK | + COMEDI_CB_EOS); + } else { + /* Error! Stop acquisition. */ + dio200_stop_intr(dev, s); + s->async->events |= COMEDI_CB_ERROR + | COMEDI_CB_OVERFLOW; + comedi_error(dev, "buffer overflow"); + } + + /* Check for end of acquisition. */ + if (!subpriv->continuous) { + /* stop_src == TRIG_COUNT */ + if (subpriv->stopcount > 0) { + subpriv->stopcount--; + if (subpriv->stopcount == 0) { + s->async->events |= + COMEDI_CB_EOA; + dio200_stop_intr(dev, + s); + } + } + } + } + } + } + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + if (oldevents != s->async->events) + comedi_event(dev, s); + + return (triggered != 0); +} + +/* + * 'cancel' function for an 'INTERRUPT' subdevice. + */ +static int dio200_subdev_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dio200_subdev_intr *subpriv = s->private; + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + if (subpriv->active) + dio200_stop_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 0; +} + +/* + * 'do_cmdtest' function for an 'INTERRUPT' subdevice. + */ +static int +dio200_subdev_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= (TRIG_NOW | TRIG_INT); + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= (TRIG_COUNT | TRIG_NONE); + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually + compatible */ + + /* these tests are true if more than one _src bit is set */ + if ((cmd->start_src & (cmd->start_src - 1)) != 0) + err++; + if ((cmd->scan_begin_src & (cmd->scan_begin_src - 1)) != 0) + err++; + if ((cmd->convert_src & (cmd->convert_src - 1)) != 0) + err++; + if ((cmd->scan_end_src & (cmd->scan_end_src - 1)) != 0) + err++; + if ((cmd->stop_src & (cmd->stop_src - 1)) != 0) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + /* cmd->start_src == TRIG_NOW || cmd->start_src == TRIG_INT */ + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + /* cmd->scan_begin_src == TRIG_EXT */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + + /* cmd->convert_src == TRIG_NOW */ + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + + /* cmd->scan_end_src == TRIG_COUNT */ + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + /* any count allowed */ + break; + case TRIG_NONE: + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + break; + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + /* if (err) return 4; */ + + return 0; +} + +/* + * 'do_cmd' function for an 'INTERRUPT' subdevice. + */ +static int dio200_subdev_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + struct dio200_subdev_intr *subpriv = s->private; + unsigned long flags; + int event = 0; + + spin_lock_irqsave(&subpriv->spinlock, flags); + subpriv->active = 1; + + /* Set up end of acquisition. */ + switch (cmd->stop_src) { + case TRIG_COUNT: + subpriv->continuous = 0; + subpriv->stopcount = cmd->stop_arg; + break; + default: + /* TRIG_NONE */ + subpriv->continuous = 1; + subpriv->stopcount = 0; + break; + } + + /* Set up start of acquisition. */ + switch (cmd->start_src) { + case TRIG_INT: + s->async->inttrig = dio200_inttrig_start_intr; + break; + default: + /* TRIG_NOW */ + event = dio200_start_intr(dev, s); + break; + } + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + if (event) + comedi_event(dev, s); + + return 0; +} + +/* + * This function initializes an 'INTERRUPT' subdevice. + */ +static int +dio200_subdev_intr_init(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned long iobase, unsigned valid_isns, + int has_int_sce) +{ + struct dio200_subdev_intr *subpriv; + + subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL); + if (!subpriv) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return -ENOMEM; + } + subpriv->iobase = iobase; + subpriv->has_int_sce = has_int_sce; + subpriv->valid_isns = valid_isns; + spin_lock_init(&subpriv->spinlock); + + if (has_int_sce) + outb(0, subpriv->iobase); /* Disable interrupt sources. */ + + s->private = subpriv; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + if (has_int_sce) { + s->n_chan = DIO200_MAX_ISNS; + s->len_chanlist = DIO200_MAX_ISNS; + } else { + /* No interrupt source register. Support single channel. */ + s->n_chan = 1; + s->len_chanlist = 1; + } + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = dio200_subdev_intr_insn_bits; + s->do_cmdtest = dio200_subdev_intr_cmdtest; + s->do_cmd = dio200_subdev_intr_cmd; + s->cancel = dio200_subdev_intr_cancel; + + return 0; +} + +/* + * This function cleans up an 'INTERRUPT' subdevice. + */ +static void +dio200_subdev_intr_cleanup(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dio200_subdev_intr *subpriv = s->private; + kfree(subpriv); +} + +/* + * Interrupt service routine. + */ +static irqreturn_t dio200_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + int handled; + + if (!dev->attached) + return IRQ_NONE; + + if (devpriv->intr_sd >= 0) { + handled = dio200_handle_read_intr(dev, + dev->subdevices + + devpriv->intr_sd); + } else { + handled = 0; + } + + return IRQ_RETVAL(handled); +} + +/* + * Handle 'insn_read' for an '8254' counter subdevice. + */ +static int +dio200_subdev_8254_read(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dio200_subdev_8254 *subpriv = s->private; + int chan = CR_CHAN(insn->chanspec); + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + data[0] = i8254_read(subpriv->iobase, 0, chan); + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 1; +} + +/* + * Handle 'insn_write' for an '8254' counter subdevice. + */ +static int +dio200_subdev_8254_write(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dio200_subdev_8254 *subpriv = s->private; + int chan = CR_CHAN(insn->chanspec); + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + i8254_write(subpriv->iobase, 0, chan, data[0]); + spin_unlock_irqrestore(&subpriv->spinlock, flags); + + return 1; +} + +/* + * Set gate source for an '8254' counter subdevice channel. + */ +static int +dio200_set_gate_src(struct dio200_subdev_8254 *subpriv, + unsigned int counter_number, unsigned int gate_src) +{ + unsigned char byte; + + if (!subpriv->has_clk_gat_sce) + return -1; + if (counter_number > 2) + return -1; + if (gate_src > 7) + return -1; + + subpriv->gate_src[counter_number] = gate_src; + byte = GAT_SCE(subpriv->which, counter_number, gate_src); + outb(byte, subpriv->gat_sce_iobase); + + return 0; +} + +/* + * Get gate source for an '8254' counter subdevice channel. + */ +static int +dio200_get_gate_src(struct dio200_subdev_8254 *subpriv, + unsigned int counter_number) +{ + if (!subpriv->has_clk_gat_sce) + return -1; + if (counter_number > 2) + return -1; + + return subpriv->gate_src[counter_number]; +} + +/* + * Set clock source for an '8254' counter subdevice channel. + */ +static int +dio200_set_clock_src(struct dio200_subdev_8254 *subpriv, + unsigned int counter_number, unsigned int clock_src) +{ + unsigned char byte; + + if (!subpriv->has_clk_gat_sce) + return -1; + if (counter_number > 2) + return -1; + if (clock_src > 7) + return -1; + + subpriv->clock_src[counter_number] = clock_src; + byte = CLK_SCE(subpriv->which, counter_number, clock_src); + outb(byte, subpriv->clk_sce_iobase); + + return 0; +} + +/* + * Get clock source for an '8254' counter subdevice channel. + */ +static int +dio200_get_clock_src(struct dio200_subdev_8254 *subpriv, + unsigned int counter_number, unsigned int *period_ns) +{ + unsigned clock_src; + + if (!subpriv->has_clk_gat_sce) + return -1; + if (counter_number > 2) + return -1; + + clock_src = subpriv->clock_src[counter_number]; + *period_ns = clock_period[clock_src]; + return clock_src; +} + +/* + * Handle 'insn_config' for an '8254' counter subdevice. + */ +static int +dio200_subdev_8254_config(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct dio200_subdev_8254 *subpriv = s->private; + int ret = 0; + int chan = CR_CHAN(insn->chanspec); + unsigned long flags; + + spin_lock_irqsave(&subpriv->spinlock, flags); + switch (data[0]) { + case INSN_CONFIG_SET_COUNTER_MODE: + ret = i8254_set_mode(subpriv->iobase, 0, chan, data[1]); + if (ret < 0) + ret = -EINVAL; + break; + case INSN_CONFIG_8254_READ_STATUS: + data[1] = i8254_status(subpriv->iobase, 0, chan); + break; + case INSN_CONFIG_SET_GATE_SRC: + ret = dio200_set_gate_src(subpriv, chan, data[2]); + if (ret < 0) + ret = -EINVAL; + break; + case INSN_CONFIG_GET_GATE_SRC: + ret = dio200_get_gate_src(subpriv, chan); + if (ret < 0) { + ret = -EINVAL; + break; + } + data[2] = ret; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + ret = dio200_set_clock_src(subpriv, chan, data[1]); + if (ret < 0) + ret = -EINVAL; + break; + case INSN_CONFIG_GET_CLOCK_SRC: + ret = dio200_get_clock_src(subpriv, chan, &data[2]); + if (ret < 0) { + ret = -EINVAL; + break; + } + data[1] = ret; + break; + default: + ret = -EINVAL; + break; + } + spin_unlock_irqrestore(&subpriv->spinlock, flags); + return ret < 0 ? ret : insn->n; +} + +/* + * This function initializes an '8254' counter subdevice. + * + * Note: iobase is the base address of the board, not the subdevice; + * offset is the offset to the 8254 chip. + */ +static int +dio200_subdev_8254_init(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned long iobase, unsigned offset, + int has_clk_gat_sce) +{ + struct dio200_subdev_8254 *subpriv; + unsigned int chan; + + subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL); + if (!subpriv) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return -ENOMEM; + } + + s->private = subpriv; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 3; + s->maxdata = 0xFFFF; + s->insn_read = dio200_subdev_8254_read; + s->insn_write = dio200_subdev_8254_write; + s->insn_config = dio200_subdev_8254_config; + + spin_lock_init(&subpriv->spinlock); + subpriv->iobase = offset + iobase; + subpriv->has_clk_gat_sce = has_clk_gat_sce; + if (has_clk_gat_sce) { + /* Derive CLK_SCE and GAT_SCE register offsets from + * 8254 offset. */ + subpriv->clk_sce_iobase = + DIO200_XCLK_SCE + (offset >> 3) + iobase; + subpriv->gat_sce_iobase = + DIO200_XGAT_SCE + (offset >> 3) + iobase; + subpriv->which = (offset >> 2) & 1; + } + + /* Initialize channels. */ + for (chan = 0; chan < 3; chan++) { + i8254_set_mode(subpriv->iobase, 0, chan, + I8254_MODE0 | I8254_BINARY); + if (subpriv->has_clk_gat_sce) { + /* Gate source 0 is VCC (logic 1). */ + dio200_set_gate_src(subpriv, chan, 0); + /* Clock source 0 is the dedicated clock input. */ + dio200_set_clock_src(subpriv, chan, 0); + } + } + + return 0; +} + +/* + * This function cleans up an '8254' counter subdevice. + */ +static void +dio200_subdev_8254_cleanup(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct dio200_subdev_intr *subpriv = s->private; + kfree(subpriv); +} + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int dio200_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase = 0; + unsigned int irq = 0; +#ifdef CONFIG_COMEDI_PCI + struct pci_dev *pci_dev = NULL; + int bus = 0, slot = 0; +#endif + const struct dio200_layout_struct *layout; + int share_irq = 0; + int sdx; + unsigned n; + int ret; + + printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor, + DIO200_DRIVER_NAME); + + ret = alloc_private(dev, sizeof(struct dio200_private)); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return ret; + } + + /* Process options. */ + switch (thisboard->bustype) { + case isa_bustype: + iobase = it->options[0]; + irq = it->options[1]; + share_irq = 0; + break; +#ifdef CONFIG_COMEDI_PCI + case pci_bustype: + bus = it->options[0]; + slot = it->options[1]; + share_irq = 1; + + ret = dio200_find_pci(dev, bus, slot, &pci_dev); + if (ret < 0) + return ret; + devpriv->pci_dev = pci_dev; + break; +#endif + default: + printk(KERN_ERR + "comedi%d: %s: BUG! cannot determine board type!\n", + dev->minor, DIO200_DRIVER_NAME); + return -EINVAL; + break; + } + + devpriv->intr_sd = -1; + + /* Enable device and reserve I/O spaces. */ +#ifdef CONFIG_COMEDI_PCI + if (pci_dev) { + ret = comedi_pci_enable(pci_dev, DIO200_DRIVER_NAME); + if (ret < 0) { + printk(KERN_ERR + "comedi%d: error! cannot enable PCI device and request regions!\n", + dev->minor); + return ret; + } + iobase = pci_resource_start(pci_dev, 2); + irq = pci_dev->irq; + } else +#endif + { + ret = dio200_request_region(dev->minor, iobase, DIO200_IO_SIZE); + if (ret < 0) + return ret; + } + dev->iobase = iobase; + + layout = thislayout; + + ret = alloc_subdevices(dev, layout->n_subdevs); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return ret; + } + + for (n = 0; n < dev->n_subdevices; n++) { + s = &dev->subdevices[n]; + switch (layout->sdtype[n]) { + case sd_8254: + /* counter subdevice (8254) */ + ret = dio200_subdev_8254_init(dev, s, iobase, + layout->sdinfo[n], + layout->has_clk_gat_sce); + if (ret < 0) + return ret; + + break; + case sd_8255: + /* digital i/o subdevice (8255) */ + ret = subdev_8255_init(dev, s, NULL, + iobase + layout->sdinfo[n]); + if (ret < 0) + return ret; + + break; + case sd_intr: + /* 'INTERRUPT' subdevice */ + if (irq) { + ret = dio200_subdev_intr_init(dev, s, + iobase + + DIO200_INT_SCE, + layout->sdinfo[n], + layout-> + has_int_sce); + if (ret < 0) + return ret; + + devpriv->intr_sd = n; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + break; + default: + s->type = COMEDI_SUBD_UNUSED; + break; + } + } + + sdx = devpriv->intr_sd; + if (sdx >= 0 && sdx < dev->n_subdevices) + dev->read_subdev = &dev->subdevices[sdx]; + + dev->board_name = thisboard->name; + + if (irq) { + unsigned long flags = share_irq ? IRQF_SHARED : 0; + + if (request_irq(irq, dio200_interrupt, flags, + DIO200_DRIVER_NAME, dev) >= 0) { + dev->irq = irq; + } else { + printk(KERN_WARNING + "comedi%d: warning! irq %u unavailable!\n", + dev->minor, irq); + } + } + + printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name); + if (thisboard->bustype == isa_bustype) { + printk("(base %#lx) ", iobase); + } else { +#ifdef CONFIG_COMEDI_PCI + printk("(pci %s) ", pci_name(pci_dev)); +#endif + } + if (irq) + printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE")); + else + printk("(no irq) "); + + printk("attached\n"); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int dio200_detach(struct comedi_device *dev) +{ + const struct dio200_layout_struct *layout; + unsigned n; + + printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor, + DIO200_DRIVER_NAME); + + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->subdevices) { + layout = thislayout; + for (n = 0; n < dev->n_subdevices; n++) { + struct comedi_subdevice *s = &dev->subdevices[n]; + switch (layout->sdtype[n]) { + case sd_8254: + dio200_subdev_8254_cleanup(dev, s); + break; + case sd_8255: + subdev_8255_cleanup(dev, s); + break; + case sd_intr: + dio200_subdev_intr_cleanup(dev, s); + break; + default: + break; + } + } + } + if (devpriv) { +#ifdef CONFIG_COMEDI_PCI + if (devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } else +#endif + { + if (dev->iobase) + release_region(dev->iobase, DIO200_IO_SIZE); + } + } + if (dev->board_name) + printk(KERN_INFO "comedi%d: %s removed\n", + dev->minor, dev->board_name); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/amplc_pc236.c b/drivers/staging/comedi/drivers/amplc_pc236.c new file mode 100644 index 00000000..7972cadd --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pc236.c @@ -0,0 +1,717 @@ +/* + comedi/drivers/amplc_pc236.c + Driver for Amplicon PC36AT and PCI236 DIO boards. + + Copyright (C) 2002 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: amplc_pc236 +Description: Amplicon PC36AT, PCI236 +Author: Ian Abbott +Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236 or amplc_pc236) +Updated: Wed, 01 Apr 2009 15:41:25 +0100 +Status: works + +Configuration options - PC36AT: + [0] - I/O port base address + [1] - IRQ (optional) + +Configuration options - PCI236: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first available PCI device will be + used. + +The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing +as subdevice 0. + +Subdevice 1 pretends to be a digital input device, but it always returns +0 when read. However, if you run a command with scan_begin_src=TRIG_EXT, +a rising edge on port C bit 3 acts as an external trigger, which can be +used to wake up tasks. This is like the comedi_parport device, but the +only way to physically disable the interrupt on the PC36AT is to remove +the IRQ jumper. If no interrupt is connected, then subdevice 1 is +unused. +*/ + +#include + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#include "8255.h" +#include "plx9052.h" + +#define PC236_DRIVER_NAME "amplc_pc236" + +/* PCI236 PCI configuration register information */ +#define PCI_VENDOR_ID_AMPLICON 0x14dc +#define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009 +#define PCI_DEVICE_ID_INVALID 0xffff + +/* PC36AT / PCI236 registers */ + +#define PC236_IO_SIZE 4 +#define PC236_LCR_IO_SIZE 128 + +/* + * INTCSR values for PCI236. + */ +/* Disable interrupt, also clear any interrupt there */ +#define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1ENAB_DISABLED \ + | PLX9052_INTCSR_LI1POL_HIGH \ + | PLX9052_INTCSR_LI2POL_HIGH \ + | PLX9052_INTCSR_PCIENAB_DISABLED \ + | PLX9052_INTCSR_LI1SEL_EDGE \ + | PLX9052_INTCSR_LI1CLRINT_ASSERTED) +/* Enable interrupt, also clear any interrupt there. */ +#define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB_ENABLED \ + | PLX9052_INTCSR_LI1POL_HIGH \ + | PLX9052_INTCSR_LI2POL_HIGH \ + | PLX9052_INTCSR_PCIENAB_ENABLED \ + | PLX9052_INTCSR_LI1SEL_EDGE \ + | PLX9052_INTCSR_LI1CLRINT_ASSERTED) + +/* + * Board descriptions for Amplicon PC36AT and PCI236. + */ + +enum pc236_bustype { isa_bustype, pci_bustype }; +enum pc236_model { pc36at_model, pci236_model, anypci_model }; + +struct pc236_board { + const char *name; + const char *fancy_name; + unsigned short devid; + enum pc236_bustype bustype; + enum pc236_model model; +}; +static const struct pc236_board pc236_boards[] = { + { + .name = "pc36at", + .fancy_name = "PC36AT", + .bustype = isa_bustype, + .model = pc36at_model, + }, +#ifdef CONFIG_COMEDI_PCI + { + .name = "pci236", + .fancy_name = "PCI236", + .devid = PCI_DEVICE_ID_AMPLICON_PCI236, + .bustype = pci_bustype, + .model = pci236_model, + }, +#endif +#ifdef CONFIG_COMEDI_PCI + { + .name = PC236_DRIVER_NAME, + .fancy_name = PC236_DRIVER_NAME, + .devid = PCI_DEVICE_ID_INVALID, + .bustype = pci_bustype, + .model = anypci_model, /* wildcard */ + }, +#endif +}; + +#ifdef CONFIG_COMEDI_PCI +static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, pc236_pci_table); +#endif /* CONFIG_COMEDI_PCI */ + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct pc236_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. + */ +struct pc236_private { +#ifdef CONFIG_COMEDI_PCI + /* PCI device */ + struct pci_dev *pci_dev; + unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */ +#endif + int enable_irq; +}; + +#define devpriv ((struct pc236_private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int pc236_detach(struct comedi_device *dev); +static struct comedi_driver driver_amplc_pc236 = { + .driver_name = PC236_DRIVER_NAME, + .module = THIS_MODULE, + .attach = pc236_attach, + .detach = pc236_detach, + .board_name = &pc236_boards[0].name, + .offset = sizeof(struct pc236_board), + .num_names = ARRAY_SIZE(pc236_boards), +}; + +#ifdef CONFIG_COMEDI_PCI +static int __devinit driver_amplc_pc236_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_amplc_pc236.driver_name); +} + +static void __devexit driver_amplc_pc236_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_amplc_pc236_pci_driver = { + .id_table = pc236_pci_table, + .probe = &driver_amplc_pc236_pci_probe, + .remove = __devexit_p(&driver_amplc_pc236_pci_remove) +}; + +static int __init driver_amplc_pc236_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_amplc_pc236); + if (retval < 0) + return retval; + + driver_amplc_pc236_pci_driver.name = + (char *)driver_amplc_pc236.driver_name; + return pci_register_driver(&driver_amplc_pc236_pci_driver); +} + +static void __exit driver_amplc_pc236_cleanup_module(void) +{ + pci_unregister_driver(&driver_amplc_pc236_pci_driver); + comedi_driver_unregister(&driver_amplc_pc236); +} + +module_init(driver_amplc_pc236_init_module); +module_exit(driver_amplc_pc236_cleanup_module); +#else +static int __init driver_amplc_pc236_init_module(void) +{ + return comedi_driver_register(&driver_amplc_pc236); +} + +static void __exit driver_amplc_pc236_cleanup_module(void) +{ + comedi_driver_unregister(&driver_amplc_pc236); +} + +module_init(driver_amplc_pc236_init_module); +module_exit(driver_amplc_pc236_cleanup_module); +#endif + +static int pc236_request_region(unsigned minor, unsigned long from, + unsigned long extent); +static void pc236_intr_disable(struct comedi_device *dev); +static void pc236_intr_enable(struct comedi_device *dev); +static int pc236_intr_check(struct comedi_device *dev); +static int pc236_intr_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int pc236_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int pc236_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); +static int pc236_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static irqreturn_t pc236_interrupt(int irq, void *d); + +/* + * This function looks for a PCI device matching the requested board name, + * bus and slot. + */ +#ifdef CONFIG_COMEDI_PCI +static int +pc236_find_pci(struct comedi_device *dev, int bus, int slot, + struct pci_dev **pci_dev_p) +{ + struct pci_dev *pci_dev = NULL; + + *pci_dev_p = NULL; + + /* Look for matching PCI device. */ + for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL); + pci_dev != NULL; + pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, + PCI_ANY_ID, pci_dev)) { + /* If bus/slot specified, check them. */ + if (bus || slot) { + if (bus != pci_dev->bus->number + || slot != PCI_SLOT(pci_dev->devfn)) + continue; + } + if (thisboard->model == anypci_model) { + /* Match any supported model. */ + int i; + + for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) { + if (pc236_boards[i].bustype != pci_bustype) + continue; + if (pci_dev->device == pc236_boards[i].devid) { + /* Change board_ptr to matched board. */ + dev->board_ptr = &pc236_boards[i]; + break; + } + } + if (i == ARRAY_SIZE(pc236_boards)) + continue; + } else { + /* Match specific model name. */ + if (pci_dev->device != thisboard->devid) + continue; + } + + /* Found a match. */ + *pci_dev_p = pci_dev; + return 0; + } + /* No match found. */ + if (bus || slot) { + printk(KERN_ERR + "comedi%d: error! no %s found at pci %02x:%02x!\n", + dev->minor, thisboard->name, bus, slot); + } else { + printk(KERN_ERR "comedi%d: error! no %s found!\n", + dev->minor, thisboard->name); + } + return -EIO; +} +#endif + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase = 0; + unsigned int irq = 0; +#ifdef CONFIG_COMEDI_PCI + struct pci_dev *pci_dev = NULL; + int bus = 0, slot = 0; +#endif + int share_irq = 0; + int ret; + + printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor, + PC236_DRIVER_NAME); +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + ret = alloc_private(dev, sizeof(struct pc236_private)); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return ret; + } + /* Process options. */ + switch (thisboard->bustype) { + case isa_bustype: + iobase = it->options[0]; + irq = it->options[1]; + share_irq = 0; + break; +#ifdef CONFIG_COMEDI_PCI + case pci_bustype: + bus = it->options[0]; + slot = it->options[1]; + share_irq = 1; + + ret = pc236_find_pci(dev, bus, slot, &pci_dev); + if (ret < 0) + return ret; + devpriv->pci_dev = pci_dev; + break; +#endif /* CONFIG_COMEDI_PCI */ + default: + printk(KERN_ERR + "comedi%d: %s: BUG! cannot determine board type!\n", + dev->minor, PC236_DRIVER_NAME); + return -EINVAL; + break; + } + +/* + * Initialize dev->board_name. + */ + dev->board_name = thisboard->name; + + /* Enable device and reserve I/O spaces. */ +#ifdef CONFIG_COMEDI_PCI + if (pci_dev) { + + ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME); + if (ret < 0) { + printk(KERN_ERR + "comedi%d: error! cannot enable PCI device and request regions!\n", + dev->minor); + return ret; + } + devpriv->lcr_iobase = pci_resource_start(pci_dev, 1); + iobase = pci_resource_start(pci_dev, 2); + irq = pci_dev->irq; + } else +#endif + { + ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE); + if (ret < 0) + return ret; + } + dev->iobase = iobase; + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + ret = alloc_subdevices(dev, 2); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return ret; + } + + s = dev->subdevices + 0; + /* digital i/o subdevice (8255) */ + ret = subdev_8255_init(dev, s, NULL, iobase); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return ret; + } + s = dev->subdevices + 1; + dev->read_subdev = s; + s->type = COMEDI_SUBD_UNUSED; + pc236_intr_disable(dev); + if (irq) { + unsigned long flags = share_irq ? IRQF_SHARED : 0; + + if (request_irq(irq, pc236_interrupt, flags, + PC236_DRIVER_NAME, dev) >= 0) { + dev->irq = irq; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pc236_intr_insn; + s->do_cmdtest = pc236_intr_cmdtest; + s->do_cmd = pc236_intr_cmd; + s->cancel = pc236_intr_cancel; + } + } + printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name); + if (thisboard->bustype == isa_bustype) { + printk("(base %#lx) ", iobase); + } else { +#ifdef CONFIG_COMEDI_PCI + printk("(pci %s) ", pci_name(pci_dev)); +#endif + } + if (irq) + printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE")); + else + printk("(no irq) "); + + printk("attached\n"); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pc236_detach(struct comedi_device *dev) +{ + printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor, + PC236_DRIVER_NAME); + if (devpriv) + pc236_intr_disable(dev); + + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 0); + if (devpriv) { +#ifdef CONFIG_COMEDI_PCI + if (devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } else +#endif + { + if (dev->iobase) + release_region(dev->iobase, PC236_IO_SIZE); + } + } + if (dev->board_name) { + printk(KERN_INFO "comedi%d: %s removed\n", + dev->minor, dev->board_name); + } + return 0; +} + +/* + * This function checks and requests an I/O region, reporting an error + * if there is a conflict. + */ +static int pc236_request_region(unsigned minor, unsigned long from, + unsigned long extent) +{ + if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) { + printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n", + minor, from, extent); + return -EIO; + } + return 0; +} + +/* + * This function is called to mark the interrupt as disabled (no command + * configured on subdevice 1) and to physically disable the interrupt + * (not possible on the PC36AT, except by removing the IRQ jumper!). + */ +static void pc236_intr_disable(struct comedi_device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->enable_irq = 0; +#ifdef CONFIG_COMEDI_PCI + if (devpriv->lcr_iobase) + outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR); +#endif + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +/* + * This function is called to mark the interrupt as enabled (a command + * configured on subdevice 1) and to physically enable the interrupt + * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!). + */ +static void pc236_intr_enable(struct comedi_device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->enable_irq = 1; +#ifdef CONFIG_COMEDI_PCI + if (devpriv->lcr_iobase) + outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR); +#endif + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +/* + * This function is called when an interrupt occurs to check whether + * the interrupt has been marked as enabled and was generated by the + * board. If so, the function prepares the hardware for the next + * interrupt. + * Returns 0 if the interrupt should be ignored. + */ +static int pc236_intr_check(struct comedi_device *dev) +{ + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + if (devpriv->enable_irq) { + retval = 1; +#ifdef CONFIG_COMEDI_PCI + if (devpriv->lcr_iobase) { + if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR) + & PLX9052_INTCSR_LI1STAT_MASK) + == PLX9052_INTCSR_LI1STAT_INACTIVE) { + retval = 0; + } else { + /* Clear interrupt and keep it enabled. */ + outl(PCI236_INTR_ENABLE, + devpriv->lcr_iobase + PLX9052_INTCSR); + } + } +#endif + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + return retval; +} + +/* + * Input from subdevice 1. + * Copied from the comedi_parport driver. + */ +static int pc236_intr_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + data[1] = 0; + return 2; +} + +/* + * Subdevice 1 command test. + * Copied from the comedi_parport driver. + */ +static int pc236_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1 */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_FOLLOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: ignored */ + + if (err) + return 2; + + /* step 3: */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + if (cmd->scan_end_arg != 1) { + cmd->scan_end_arg = 1; + err++; + } + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + + if (err) + return 3; + + /* step 4: ignored */ + + if (err) + return 4; + + return 0; +} + +/* + * Subdevice 1 command. + */ +static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + pc236_intr_enable(dev); + + return 0; +} + +/* + * Subdevice 1 cancel command. + */ +static int pc236_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + pc236_intr_disable(dev); + + return 0; +} + +/* + * Interrupt service routine. + * Based on the comedi_parport driver. + */ +static irqreturn_t pc236_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 1; + int handled; + + handled = pc236_intr_check(dev); + if (dev->attached && handled) { + comedi_buf_put(s->async, 0); + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + comedi_event(dev, s); + } + return IRQ_RETVAL(handled); +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/amplc_pc263.c b/drivers/staging/comedi/drivers/amplc_pc263.c new file mode 100644 index 00000000..191ac0d2 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pc263.c @@ -0,0 +1,489 @@ +/* + comedi/drivers/amplc_pc263.c + Driver for Amplicon PC263 and PCI263 relay boards. + + Copyright (C) 2002 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: amplc_pc263 +Description: Amplicon PC263, PCI263 +Author: Ian Abbott +Devices: [Amplicon] PC263 (pc263), PCI263 (pci263 or amplc_pc263) +Updated: Wed, 22 Oct 2008 14:10:53 +0100 +Status: works + +Configuration options - PC263: + [0] - I/O port base address + +Configuration options - PCI263: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first available PCI device will be + used. + +Each board appears as one subdevice, with 16 digital outputs, each +connected to a reed-relay. Relay contacts are closed when output is 1. +The state of the outputs can be read. +*/ + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#define PC263_DRIVER_NAME "amplc_pc263" + +/* PCI263 PCI configuration register information */ +#define PCI_VENDOR_ID_AMPLICON 0x14dc +#define PCI_DEVICE_ID_AMPLICON_PCI263 0x000c +#define PCI_DEVICE_ID_INVALID 0xffff + +/* PC263 / PCI263 registers */ +#define PC263_IO_SIZE 2 + +/* + * Board descriptions for Amplicon PC263 / PCI263. + */ + +enum pc263_bustype { isa_bustype, pci_bustype }; +enum pc263_model { pc263_model, pci263_model, anypci_model }; + +struct pc263_board { + const char *name; + const char *fancy_name; + unsigned short devid; + enum pc263_bustype bustype; + enum pc263_model model; +}; +static const struct pc263_board pc263_boards[] = { + { + .name = "pc263", + .fancy_name = "PC263", + .bustype = isa_bustype, + .model = pc263_model, + }, +#ifdef CONFIG_COMEDI_PCI + { + .name = "pci263", + .fancy_name = "PCI263", + .devid = PCI_DEVICE_ID_AMPLICON_PCI263, + .bustype = pci_bustype, + .model = pci263_model, + }, +#endif +#ifdef CONFIG_COMEDI_PCI + { + .name = PC263_DRIVER_NAME, + .fancy_name = PC263_DRIVER_NAME, + .devid = PCI_DEVICE_ID_INVALID, + .bustype = pci_bustype, + .model = anypci_model, /* wildcard */ + }, +#endif +}; + +#ifdef CONFIG_COMEDI_PCI +static DEFINE_PCI_DEVICE_TABLE(pc263_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI263) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, pc263_pci_table); +#endif /* CONFIG_COMEDI_PCI */ + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct pc263_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. +*/ +#ifdef CONFIG_COMEDI_PCI +struct pc263_private { + /* PCI device. */ + struct pci_dev *pci_dev; +}; + +#define devpriv ((struct pc263_private *)dev->private) +#endif /* CONFIG_COMEDI_PCI */ + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pc263_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int pc263_detach(struct comedi_device *dev); +static struct comedi_driver driver_amplc_pc263 = { + .driver_name = PC263_DRIVER_NAME, + .module = THIS_MODULE, + .attach = pc263_attach, + .detach = pc263_detach, + .board_name = &pc263_boards[0].name, + .offset = sizeof(struct pc263_board), + .num_names = ARRAY_SIZE(pc263_boards), +}; + +static int pc263_request_region(unsigned minor, unsigned long from, + unsigned long extent); +static int pc263_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int pc263_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * This function looks for a PCI device matching the requested board name, + * bus and slot. + */ +#ifdef CONFIG_COMEDI_PCI +static int +pc263_find_pci(struct comedi_device *dev, int bus, int slot, + struct pci_dev **pci_dev_p) +{ + struct pci_dev *pci_dev = NULL; + + *pci_dev_p = NULL; + + /* Look for matching PCI device. */ + for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL); + pci_dev != NULL; + pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, + PCI_ANY_ID, pci_dev)) { + /* If bus/slot specified, check them. */ + if (bus || slot) { + if (bus != pci_dev->bus->number + || slot != PCI_SLOT(pci_dev->devfn)) + continue; + } + if (thisboard->model == anypci_model) { + /* Match any supported model. */ + int i; + + for (i = 0; i < ARRAY_SIZE(pc263_boards); i++) { + if (pc263_boards[i].bustype != pci_bustype) + continue; + if (pci_dev->device == pc263_boards[i].devid) { + /* Change board_ptr to matched board. */ + dev->board_ptr = &pc263_boards[i]; + break; + } + } + if (i == ARRAY_SIZE(pc263_boards)) + continue; + } else { + /* Match specific model name. */ + if (pci_dev->device != thisboard->devid) + continue; + } + + /* Found a match. */ + *pci_dev_p = pci_dev; + return 0; + } + /* No match found. */ + if (bus || slot) { + printk(KERN_ERR + "comedi%d: error! no %s found at pci %02x:%02x!\n", + dev->minor, thisboard->name, bus, slot); + } else { + printk(KERN_ERR "comedi%d: error! no %s found!\n", + dev->minor, thisboard->name); + } + return -EIO; +} +#endif + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pc263_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase = 0; +#ifdef CONFIG_COMEDI_PCI + struct pci_dev *pci_dev = NULL; + int bus = 0, slot = 0; +#endif + int ret; + + printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor, + PC263_DRIVER_NAME); +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ +#ifdef CONFIG_COMEDI_PCI + ret = alloc_private(dev, sizeof(struct pc263_private)); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return ret; + } +#endif + /* Process options. */ + switch (thisboard->bustype) { + case isa_bustype: + iobase = it->options[0]; + break; +#ifdef CONFIG_COMEDI_PCI + case pci_bustype: + bus = it->options[0]; + slot = it->options[1]; + + ret = pc263_find_pci(dev, bus, slot, &pci_dev); + if (ret < 0) + return ret; + devpriv->pci_dev = pci_dev; + break; +#endif /* CONFIG_COMEDI_PCI */ + default: + printk(KERN_ERR + "comedi%d: %s: BUG! cannot determine board type!\n", + dev->minor, PC263_DRIVER_NAME); + return -EINVAL; + break; + } + +/* + * Initialize dev->board_name. + */ + dev->board_name = thisboard->name; + + /* Enable device and reserve I/O spaces. */ +#ifdef CONFIG_COMEDI_PCI + if (pci_dev) { + ret = comedi_pci_enable(pci_dev, PC263_DRIVER_NAME); + if (ret < 0) { + printk(KERN_ERR + "comedi%d: error! cannot enable PCI device and " + "request regions!\n", + dev->minor); + return ret; + } + iobase = pci_resource_start(pci_dev, 2); + } else +#endif + { + ret = pc263_request_region(dev->minor, iobase, PC263_IO_SIZE); + if (ret < 0) + return ret; + } + dev->iobase = iobase; + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + ret = alloc_subdevices(dev, 1); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return ret; + } + + s = dev->subdevices + 0; + /* digital i/o subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = pc263_dio_insn_bits; + s->insn_config = pc263_dio_insn_config; + /* all outputs */ + s->io_bits = 0xffff; + /* read initial relay state */ + s->state = inb(dev->iobase); + s->state = s->state | (inb(dev->iobase) << 8); + + printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name); + if (thisboard->bustype == isa_bustype) { + printk("(base %#lx) ", iobase); + } else { +#ifdef CONFIG_COMEDI_PCI + printk("(pci %s) ", pci_name(pci_dev)); +#endif + } + + printk("attached\n"); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pc263_detach(struct comedi_device *dev) +{ + printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor, + PC263_DRIVER_NAME); + +#ifdef CONFIG_COMEDI_PCI + if (devpriv) { +#endif +#ifdef CONFIG_COMEDI_PCI + if (devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } else +#endif + { + if (dev->iobase) + release_region(dev->iobase, PC263_IO_SIZE); + } + } + if (dev->board_name) { + printk(KERN_INFO "comedi%d: %s removed\n", + dev->minor, dev->board_name); + } + return 0; +} + +/* + * This function checks and requests an I/O region, reporting an error + * if there is a conflict. + */ +static int pc263_request_region(unsigned minor, unsigned long from, + unsigned long extent) +{ + if (!from || !request_region(from, extent, PC263_DRIVER_NAME)) { + printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n", + minor, from, extent); + return -EIO; + } + return 0; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +static int pc263_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + /* Write out the new digital output lines */ + outb(s->state & 0xFF, dev->iobase); + outb(s->state >> 8, dev->iobase + 1); + } + + /* on return, data[1] contains the value of the digital + * input and output lines. */ + /* or we could just return the software copy of the output values if + * it was a purely digital output subdevice */ + data[1] = s->state; + + return 2; +} + +static int pc263_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 1) + return -EINVAL; + return 1; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +#ifdef CONFIG_COMEDI_PCI +static int __devinit driver_amplc_pc263_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_amplc_pc263.driver_name); +} + +static void __devexit driver_amplc_pc263_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_amplc_pc263_pci_driver = { + .id_table = pc263_pci_table, + .probe = &driver_amplc_pc263_pci_probe, + .remove = __devexit_p(&driver_amplc_pc263_pci_remove) +}; + +static int __init driver_amplc_pc263_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_amplc_pc263); + if (retval < 0) + return retval; + + driver_amplc_pc263_pci_driver.name = + (char *)driver_amplc_pc263.driver_name; + return pci_register_driver(&driver_amplc_pc263_pci_driver); +} + +static void __exit driver_amplc_pc263_cleanup_module(void) +{ + pci_unregister_driver(&driver_amplc_pc263_pci_driver); + comedi_driver_unregister(&driver_amplc_pc263); +} + +module_init(driver_amplc_pc263_init_module); +module_exit(driver_amplc_pc263_cleanup_module); +#else +static int __init driver_amplc_pc263_init_module(void) +{ + return comedi_driver_register(&driver_amplc_pc263); +} + +static void __exit driver_amplc_pc263_cleanup_module(void) +{ + comedi_driver_unregister(&driver_amplc_pc263); +} + +module_init(driver_amplc_pc263_init_module); +module_exit(driver_amplc_pc263_cleanup_module); +#endif + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/amplc_pci224.c b/drivers/staging/comedi/drivers/amplc_pci224.c new file mode 100644 index 00000000..b278917c --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pci224.c @@ -0,0 +1,1598 @@ +/* + comedi/drivers/amplc_pci224.c + Driver for Amplicon PCI224 and PCI234 AO boards. + + Copyright (C) 2005 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998,2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: amplc_pci224 +Description: Amplicon PCI224, PCI234 +Author: Ian Abbott +Devices: [Amplicon] PCI224 (amplc_pci224 or pci224), + PCI234 (amplc_pci224 or pci234) +Updated: Wed, 22 Oct 2008 12:25:08 +0100 +Status: works, but see caveats + +Supports: + + - ao_insn read/write + - ao_do_cmd mode with the following sources: + + - start_src TRIG_INT TRIG_EXT + - scan_begin_src TRIG_TIMER TRIG_EXT + - convert_src TRIG_NOW + - scan_end_src TRIG_COUNT + - stop_src TRIG_COUNT TRIG_EXT TRIG_NONE + + The channel list must contain at least one channel with no repeated + channels. The scan end count must equal the number of channels in + the channel list. + + There is only one external trigger source so only one of start_src, + scan_begin_src or stop_src may use TRIG_EXT. + +Configuration options - PCI224: + [0] - PCI bus of device (optional). + [1] - PCI slot of device (optional). + If bus/slot is not specified, the first available PCI device + will be used. + [2] - Select available ranges according to jumper LK1. All channels + are set to the same range: + 0=Jumper position 1-2 (factory default), 4 software-selectable + internal voltage references, giving 4 bipolar and 4 unipolar + ranges: + [-10V,+10V], [-5V,+5V], [-2.5V,+2.5V], [-1.25V,+1.25V], + [0,+10V], [0,+5V], [0,+2.5V], [0,1.25V]. + 1=Jumper position 2-3, 1 external voltage reference, giving + 1 bipolar and 1 unipolar range: + [-Vext,+Vext], [0,+Vext]. + +Configuration options - PCI234: + [0] - PCI bus of device (optional). + [1] - PCI slot of device (optional). + If bus/slot is not specified, the first available PCI device + will be used. + [2] - Select internal or external voltage reference according to + jumper LK1. This affects all channels: + 0=Jumper position 1-2 (factory default), Vref=5V internal. + 1=Jumper position 2-3, Vref=Vext external. + [3] - Select channel 0 range according to jumper LK2: + 0=Jumper position 2-3 (factory default), range [-2*Vref,+2*Vref] + (10V bipolar when options[2]=0). + 1=Jumper position 1-2, range [-Vref,+Vref] + (5V bipolar when options[2]=0). + [4] - Select channel 1 range according to jumper LK3: cf. options[3]. + [5] - Select channel 2 range according to jumper LK4: cf. options[3]. + [6] - Select channel 3 range according to jumper LK5: cf. options[3]. + +Passing a zero for an option is the same as leaving it unspecified. + +Caveats: + + 1) All channels on the PCI224 share the same range. Any change to the + range as a result of insn_write or a streaming command will affect + the output voltages of all channels, including those not specified + by the instruction or command. + + 2) For the analog output command, the first scan may be triggered + falsely at the start of acquisition. This occurs when the DAC scan + trigger source is switched from 'none' to 'timer' (scan_begin_src = + TRIG_TIMER) or 'external' (scan_begin_src == TRIG_EXT) at the start + of acquisition and the trigger source is at logic level 1 at the + time of the switch. This is very likely for TRIG_TIMER. For + TRIG_EXT, it depends on the state of the external line and whether + the CR_INVERT flag has been set. The remaining scans are triggered + correctly. +*/ + +#include +#include + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#include "comedi_fc.h" +#include "8253.h" + +#define DRIVER_NAME "amplc_pci224" + +/* + * PCI IDs. + */ +#define PCI_VENDOR_ID_AMPLICON 0x14dc +#define PCI_DEVICE_ID_AMPLICON_PCI224 0x0007 +#define PCI_DEVICE_ID_AMPLICON_PCI234 0x0008 +#define PCI_DEVICE_ID_INVALID 0xffff + +/* + * PCI224/234 i/o space 1 (PCIBAR2) registers. + */ +#define PCI224_IO1_SIZE 0x20 /* Size of i/o space 1 (8-bit registers) */ +#define PCI224_Z2_CT0 0x14 /* 82C54 counter/timer 0 */ +#define PCI224_Z2_CT1 0x15 /* 82C54 counter/timer 1 */ +#define PCI224_Z2_CT2 0x16 /* 82C54 counter/timer 2 */ +#define PCI224_Z2_CTC 0x17 /* 82C54 counter/timer control word */ +#define PCI224_ZCLK_SCE 0x1A /* Group Z Clock Configuration Register */ +#define PCI224_ZGAT_SCE 0x1D /* Group Z Gate Configuration Register */ +#define PCI224_INT_SCE 0x1E /* ISR Interrupt source mask register */ + /* /Interrupt status */ + +/* + * PCI224/234 i/o space 2 (PCIBAR3) 16-bit registers. + */ +#define PCI224_IO2_SIZE 0x10 /* Size of i/o space 2 (16-bit registers). */ +#define PCI224_DACDATA 0x00 /* (w-o) DAC FIFO data. */ +#define PCI224_SOFTTRIG 0x00 /* (r-o) DAC software scan trigger. */ +#define PCI224_DACCON 0x02 /* (r/w) DAC status/configuration. */ +#define PCI224_FIFOSIZ 0x04 /* (w-o) FIFO size for wraparound mode. */ +#define PCI224_DACCEN 0x06 /* (w-o) DAC channel enable register. */ + +/* + * DACCON values. + */ +/* (r/w) Scan trigger. */ +#define PCI224_DACCON_TRIG_MASK (7 << 0) +#define PCI224_DACCON_TRIG_NONE (0 << 0) /* none */ +#define PCI224_DACCON_TRIG_SW (1 << 0) /* software trig */ +#define PCI224_DACCON_TRIG_EXTP (2 << 0) /* ext +ve edge */ +#define PCI224_DACCON_TRIG_EXTN (3 << 0) /* ext -ve edge */ +#define PCI224_DACCON_TRIG_Z2CT0 (4 << 0) /* Z2 CT0 out */ +#define PCI224_DACCON_TRIG_Z2CT1 (5 << 0) /* Z2 CT1 out */ +#define PCI224_DACCON_TRIG_Z2CT2 (6 << 0) /* Z2 CT2 out */ +/* (r/w) Polarity (PCI224 only, PCI234 always bipolar!). */ +#define PCI224_DACCON_POLAR_MASK (1 << 3) +#define PCI224_DACCON_POLAR_UNI (0 << 3) /* range [0,Vref] */ +#define PCI224_DACCON_POLAR_BI (1 << 3) /* range [-Vref,Vref] */ +/* (r/w) Internal Vref (PCI224 only, when LK1 in position 1-2). */ +#define PCI224_DACCON_VREF_MASK (3 << 4) +#define PCI224_DACCON_VREF_1_25 (0 << 4) /* Vref = 1.25V */ +#define PCI224_DACCON_VREF_2_5 (1 << 4) /* Vref = 2.5V */ +#define PCI224_DACCON_VREF_5 (2 << 4) /* Vref = 5V */ +#define PCI224_DACCON_VREF_10 (3 << 4) /* Vref = 10V */ +/* (r/w) Wraparound mode enable (to play back stored waveform). */ +#define PCI224_DACCON_FIFOWRAP (1 << 7) +/* (r/w) FIFO enable. It MUST be set! */ +#define PCI224_DACCON_FIFOENAB (1 << 8) +/* (r/w) FIFO interrupt trigger level (most values are not very useful). */ +#define PCI224_DACCON_FIFOINTR_MASK (7 << 9) +#define PCI224_DACCON_FIFOINTR_EMPTY (0 << 9) /* when empty */ +#define PCI224_DACCON_FIFOINTR_NEMPTY (1 << 9) /* when not empty */ +#define PCI224_DACCON_FIFOINTR_NHALF (2 << 9) /* when not half full */ +#define PCI224_DACCON_FIFOINTR_HALF (3 << 9) /* when half full */ +#define PCI224_DACCON_FIFOINTR_NFULL (4 << 9) /* when not full */ +#define PCI224_DACCON_FIFOINTR_FULL (5 << 9) /* when full */ +/* (r-o) FIFO fill level. */ +#define PCI224_DACCON_FIFOFL_MASK (7 << 12) +#define PCI224_DACCON_FIFOFL_EMPTY (1 << 12) /* 0 */ +#define PCI224_DACCON_FIFOFL_ONETOHALF (0 << 12) /* [1,2048] */ +#define PCI224_DACCON_FIFOFL_HALFTOFULL (4 << 12) /* [2049,4095] */ +#define PCI224_DACCON_FIFOFL_FULL (6 << 12) /* 4096 */ +/* (r-o) DAC busy flag. */ +#define PCI224_DACCON_BUSY (1 << 15) +/* (w-o) FIFO reset. */ +#define PCI224_DACCON_FIFORESET (1 << 12) +/* (w-o) Global reset (not sure what it does). */ +#define PCI224_DACCON_GLOBALRESET (1 << 13) + +/* + * DAC FIFO size. + */ +#define PCI224_FIFO_SIZE 4096 + +/* + * DAC FIFO guaranteed minimum room available, depending on reported fill level. + * The maximum room available depends on the reported fill level and how much + * has been written! + */ +#define PCI224_FIFO_ROOM_EMPTY PCI224_FIFO_SIZE +#define PCI224_FIFO_ROOM_ONETOHALF (PCI224_FIFO_SIZE / 2) +#define PCI224_FIFO_ROOM_HALFTOFULL 1 +#define PCI224_FIFO_ROOM_FULL 0 + +/* + * Counter/timer clock input configuration sources. + */ +#define CLK_CLK 0 /* reserved (channel-specific clock) */ +#define CLK_10MHZ 1 /* internal 10 MHz clock */ +#define CLK_1MHZ 2 /* internal 1 MHz clock */ +#define CLK_100KHZ 3 /* internal 100 kHz clock */ +#define CLK_10KHZ 4 /* internal 10 kHz clock */ +#define CLK_1KHZ 5 /* internal 1 kHz clock */ +#define CLK_OUTNM1 6 /* output of channel-1 modulo total */ +#define CLK_EXT 7 /* external clock */ +/* Macro to construct clock input configuration register value. */ +#define CLK_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7)) +/* Timebases in ns. */ +#define TIMEBASE_10MHZ 100 +#define TIMEBASE_1MHZ 1000 +#define TIMEBASE_100KHZ 10000 +#define TIMEBASE_10KHZ 100000 +#define TIMEBASE_1KHZ 1000000 + +/* + * Counter/timer gate input configuration sources. + */ +#define GAT_VCC 0 /* VCC (i.e. enabled) */ +#define GAT_GND 1 /* GND (i.e. disabled) */ +#define GAT_EXT 2 /* reserved (external gate input) */ +#define GAT_NOUTNM2 3 /* inverted output of channel-2 modulo total */ +/* Macro to construct gate input configuration register value. */ +#define GAT_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7)) + +/* + * Summary of CLK_OUTNM1 and GAT_NOUTNM2 connections for PCI224 and PCI234: + * + * Channel's Channel's + * clock input gate input + * Channel CLK_OUTNM1 GAT_NOUTNM2 + * ------- ---------- ----------- + * Z2-CT0 Z2-CT2-OUT /Z2-CT1-OUT + * Z2-CT1 Z2-CT0-OUT /Z2-CT2-OUT + * Z2-CT2 Z2-CT1-OUT /Z2-CT0-OUT + */ + +/* + * Interrupt enable/status bits + */ +#define PCI224_INTR_EXT 0x01 /* rising edge on external input */ +#define PCI224_INTR_DAC 0x04 /* DAC (FIFO) interrupt */ +#define PCI224_INTR_Z2CT1 0x20 /* rising edge on Z2-CT1 output */ + +#define PCI224_INTR_EDGE_BITS (PCI224_INTR_EXT | PCI224_INTR_Z2CT1) +#define PCI224_INTR_LEVEL_BITS PCI224_INTR_DACFIFO + +/* + * Handy macros. + */ + +/* Combine old and new bits. */ +#define COMBINE(old, new, mask) (((old) & ~(mask)) | ((new) & (mask))) + +/* A generic null function pointer value. */ +#define NULLFUNC 0 + +/* Current CPU. XXX should this be hard_smp_processor_id()? */ +#define THISCPU smp_processor_id() + +/* State bits for use with atomic bit operations. */ +#define AO_CMD_STARTED 0 + +/* + * Range tables. + */ + +/* The software selectable internal ranges for PCI224 (option[2] == 0). */ +static const struct comedi_lrange range_pci224_internal = { + 8, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; + +static const unsigned short hwrange_pci224_internal[8] = { + PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_10, + PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_5, + PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_2_5, + PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_1_25, + PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_10, + PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_5, + PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_2_5, + PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_1_25, +}; + +/* The software selectable external ranges for PCI224 (option[2] == 1). */ +static const struct comedi_lrange range_pci224_external = { + 2, + { + RANGE_ext(-1, 1), /* bipolar [-Vref,+Vref] */ + RANGE_ext(0, 1), /* unipolar [0,+Vref] */ + } +}; + +static const unsigned short hwrange_pci224_external[2] = { + PCI224_DACCON_POLAR_BI, + PCI224_DACCON_POLAR_UNI, +}; + +/* The hardware selectable Vref*2 external range for PCI234 + * (option[2] == 1, option[3+n] == 0). */ +static const struct comedi_lrange range_pci234_ext2 = { + 1, + { + RANGE_ext(-2, 2), + } +}; + +/* The hardware selectable Vref external range for PCI234 + * (option[2] == 1, option[3+n] == 1). */ +static const struct comedi_lrange range_pci234_ext = { + 1, + { + RANGE_ext(-1, 1), + } +}; + +/* This serves for all the PCI234 ranges. */ +static const unsigned short hwrange_pci234[1] = { + PCI224_DACCON_POLAR_BI, /* bipolar - hardware ignores it! */ +}; + +/* + * Board descriptions. + */ + +enum pci224_model { any_model, pci224_model, pci234_model }; + +struct pci224_board { + const char *name; + unsigned short devid; + enum pci224_model model; + unsigned int ao_chans; + unsigned int ao_bits; +}; + +static const struct pci224_board pci224_boards[] = { + { + .name = "pci224", + .devid = PCI_DEVICE_ID_AMPLICON_PCI224, + .model = pci224_model, + .ao_chans = 16, + .ao_bits = 12, + }, + { + .name = "pci234", + .devid = PCI_DEVICE_ID_AMPLICON_PCI234, + .model = pci234_model, + .ao_chans = 4, + .ao_bits = 16, + }, + { + .name = DRIVER_NAME, + .devid = PCI_DEVICE_ID_INVALID, + .model = any_model, /* wildcard */ + }, +}; + +/* + * PCI driver table. + */ + +static DEFINE_PCI_DEVICE_TABLE(pci224_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI224) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI234) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, pci224_pci_table); + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((struct pci224_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. */ +struct pci224_private { + struct pci_dev *pci_dev; /* PCI device */ + const unsigned short *hwrange; + unsigned long iobase1; + unsigned long state; + spinlock_t ao_spinlock; + unsigned int *ao_readback; + short *ao_scan_vals; + unsigned char *ao_scan_order; + int intr_cpuid; + short intr_running; + unsigned short daccon; + unsigned int cached_div1; + unsigned int cached_div2; + unsigned int ao_stop_count; + short ao_stop_continuous; + unsigned short ao_enab; /* max 16 channels so 'short' will do */ + unsigned char intsce; +}; + +#define devpriv ((struct pci224_private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pci224_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pci224_detach(struct comedi_device *dev); +static struct comedi_driver driver_amplc_pci224 = { + .driver_name = DRIVER_NAME, + .module = THIS_MODULE, + .attach = pci224_attach, + .detach = pci224_detach, + .board_name = &pci224_boards[0].name, + .offset = sizeof(struct pci224_board), + .num_names = ARRAY_SIZE(pci224_boards), +}; + +static int __devinit driver_amplc_pci224_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_amplc_pci224.driver_name); +} + +static void __devexit driver_amplc_pci224_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_amplc_pci224_pci_driver = { + .id_table = pci224_pci_table, + .probe = &driver_amplc_pci224_pci_probe, + .remove = __devexit_p(&driver_amplc_pci224_pci_remove) +}; + +static int __init driver_amplc_pci224_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_amplc_pci224); + if (retval < 0) + return retval; + + driver_amplc_pci224_pci_driver.name = + (char *)driver_amplc_pci224.driver_name; + return pci_register_driver(&driver_amplc_pci224_pci_driver); +} + +static void __exit driver_amplc_pci224_cleanup_module(void) +{ + pci_unregister_driver(&driver_amplc_pci224_pci_driver); + comedi_driver_unregister(&driver_amplc_pci224); +} + +module_init(driver_amplc_pci224_init_module); +module_exit(driver_amplc_pci224_cleanup_module); + +/* + * Called from the 'insn_write' function to perform a single write. + */ +static void +pci224_ao_set_data(struct comedi_device *dev, int chan, int range, + unsigned int data) +{ + unsigned short mangled; + + /* Store unmangled data for readback. */ + devpriv->ao_readback[chan] = data; + /* Enable the channel. */ + outw(1 << chan, dev->iobase + PCI224_DACCEN); + /* Set range and reset FIFO. */ + devpriv->daccon = COMBINE(devpriv->daccon, devpriv->hwrange[range], + (PCI224_DACCON_POLAR_MASK | + PCI224_DACCON_VREF_MASK)); + outw(devpriv->daccon | PCI224_DACCON_FIFORESET, + dev->iobase + PCI224_DACCON); + /* + * Mangle the data. The hardware expects: + * - bipolar: 16-bit 2's complement + * - unipolar: 16-bit unsigned + */ + mangled = (unsigned short)data << (16 - thisboard->ao_bits); + if ((devpriv->daccon & PCI224_DACCON_POLAR_MASK) == + PCI224_DACCON_POLAR_BI) { + mangled ^= 0x8000; + } + /* Write mangled data to the FIFO. */ + outw(mangled, dev->iobase + PCI224_DACDATA); + /* Trigger the conversion. */ + inw(dev->iobase + PCI224_SOFTTRIG); +} + +/* + * 'insn_write' function for AO subdevice. + */ +static int +pci224_ao_insn_write(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan, range; + + /* Unpack channel and range. */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; i++) + pci224_ao_set_data(dev, chan, range, data[i]); + + return i; +} + +/* + * 'insn_read' function for AO subdevice. + * + * N.B. The value read will not be valid if the DAC channel has + * never been written successfully since the device was attached + * or since the channel has been used by an AO streaming write + * command. + */ +static int +pci224_ao_insn_read(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + + return i; +} + +/* + * Just a wrapper for the inline function 'i8253_cascade_ns_to_timer'. + */ +static void +pci224_cascade_ns_to_timer(int osc_base, unsigned int *d1, unsigned int *d2, + unsigned int *nanosec, int round_mode) +{ + i8253_cascade_ns_to_timer(osc_base, d1, d2, nanosec, round_mode); +} + +/* + * Kills a command running on the AO subdevice. + */ +static void pci224_ao_stop(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long flags; + + if (!test_and_clear_bit(AO_CMD_STARTED, &devpriv->state)) + return; + + + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + /* Kill the interrupts. */ + devpriv->intsce = 0; + outb(0, devpriv->iobase1 + PCI224_INT_SCE); + /* + * Interrupt routine may or may not be running. We may or may not + * have been called from the interrupt routine (directly or + * indirectly via a comedi_events() callback routine). It's highly + * unlikely that we've been called from some other interrupt routine + * but who knows what strange things coders get up to! + * + * If the interrupt routine is currently running, wait for it to + * finish, unless we appear to have been called via the interrupt + * routine. + */ + while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) { + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + } + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + /* Reconfigure DAC for insn_write usage. */ + outw(0, dev->iobase + PCI224_DACCEN); /* Disable channels. */ + devpriv->daccon = COMBINE(devpriv->daccon, + PCI224_DACCON_TRIG_SW | + PCI224_DACCON_FIFOINTR_EMPTY, + PCI224_DACCON_TRIG_MASK | + PCI224_DACCON_FIFOINTR_MASK); + outw(devpriv->daccon | PCI224_DACCON_FIFORESET, + dev->iobase + PCI224_DACCON); +} + +/* + * Handles start of acquisition for the AO subdevice. + */ +static void pci224_ao_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + + set_bit(AO_CMD_STARTED, &devpriv->state); + if (!devpriv->ao_stop_continuous && devpriv->ao_stop_count == 0) { + /* An empty acquisition! */ + pci224_ao_stop(dev, s); + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + } else { + /* Enable interrupts. */ + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + if (cmd->stop_src == TRIG_EXT) + devpriv->intsce = PCI224_INTR_EXT | PCI224_INTR_DAC; + else + devpriv->intsce = PCI224_INTR_DAC; + + outb(devpriv->intsce, devpriv->iobase1 + PCI224_INT_SCE); + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + } +} + +/* + * Handles interrupts from the DAC FIFO. + */ +static void pci224_ao_handle_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int num_scans; + unsigned int room; + unsigned short dacstat; + unsigned int i, n; + unsigned int bytes_per_scan; + + if (cmd->chanlist_len) { + bytes_per_scan = cmd->chanlist_len * sizeof(short); + } else { + /* Shouldn't get here! */ + bytes_per_scan = sizeof(short); + } + /* Determine number of scans available in buffer. */ + num_scans = comedi_buf_read_n_available(s->async) / bytes_per_scan; + if (!devpriv->ao_stop_continuous) { + /* Fixed number of scans. */ + if (num_scans > devpriv->ao_stop_count) + num_scans = devpriv->ao_stop_count; + + } + + /* Determine how much room is in the FIFO (in samples). */ + dacstat = inw(dev->iobase + PCI224_DACCON); + switch (dacstat & PCI224_DACCON_FIFOFL_MASK) { + case PCI224_DACCON_FIFOFL_EMPTY: + room = PCI224_FIFO_ROOM_EMPTY; + if (!devpriv->ao_stop_continuous && devpriv->ao_stop_count == 0) { + /* FIFO empty at end of counted acquisition. */ + pci224_ao_stop(dev, s); + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return; + } + break; + case PCI224_DACCON_FIFOFL_ONETOHALF: + room = PCI224_FIFO_ROOM_ONETOHALF; + break; + case PCI224_DACCON_FIFOFL_HALFTOFULL: + room = PCI224_FIFO_ROOM_HALFTOFULL; + break; + default: + room = PCI224_FIFO_ROOM_FULL; + break; + } + if (room >= PCI224_FIFO_ROOM_ONETOHALF) { + /* FIFO is less than half-full. */ + if (num_scans == 0) { + /* Nothing left to put in the FIFO. */ + pci224_ao_stop(dev, s); + s->async->events |= COMEDI_CB_OVERFLOW; + printk(KERN_ERR "comedi%d: " + "AO buffer underrun\n", dev->minor); + } + } + /* Determine how many new scans can be put in the FIFO. */ + if (cmd->chanlist_len) + room /= cmd->chanlist_len; + + /* Determine how many scans to process. */ + if (num_scans > room) + num_scans = room; + + /* Process scans. */ + for (n = 0; n < num_scans; n++) { + cfc_read_array_from_buffer(s, &devpriv->ao_scan_vals[0], + bytes_per_scan); + for (i = 0; i < cmd->chanlist_len; i++) { + outw(devpriv->ao_scan_vals[devpriv->ao_scan_order[i]], + dev->iobase + PCI224_DACDATA); + } + } + if (!devpriv->ao_stop_continuous) { + devpriv->ao_stop_count -= num_scans; + if (devpriv->ao_stop_count == 0) { + /* + * Change FIFO interrupt trigger level to wait + * until FIFO is empty. + */ + devpriv->daccon = COMBINE(devpriv->daccon, + PCI224_DACCON_FIFOINTR_EMPTY, + PCI224_DACCON_FIFOINTR_MASK); + outw(devpriv->daccon, dev->iobase + PCI224_DACCON); + } + } + if ((devpriv->daccon & PCI224_DACCON_TRIG_MASK) == + PCI224_DACCON_TRIG_NONE) { + unsigned short trig; + + /* + * This is the initial DAC FIFO interrupt at the + * start of the acquisition. The DAC's scan trigger + * has been set to 'none' up until now. + * + * Now that data has been written to the FIFO, the + * DAC's scan trigger source can be set to the + * correct value. + * + * BUG: The first scan will be triggered immediately + * if the scan trigger source is at logic level 1. + */ + if (cmd->scan_begin_src == TRIG_TIMER) { + trig = PCI224_DACCON_TRIG_Z2CT0; + } else { + /* cmd->scan_begin_src == TRIG_EXT */ + if (cmd->scan_begin_arg & CR_INVERT) + trig = PCI224_DACCON_TRIG_EXTN; + else + trig = PCI224_DACCON_TRIG_EXTP; + + } + devpriv->daccon = COMBINE(devpriv->daccon, trig, + PCI224_DACCON_TRIG_MASK); + outw(devpriv->daccon, dev->iobase + PCI224_DACCON); + } + if (s->async->events) + comedi_event(dev, s); + +} + +/* + * Internal trigger function to start acquisition on AO subdevice. + */ +static int +pci224_ao_inttrig_start(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum) +{ + if (trignum != 0) + return -EINVAL; + + s->async->inttrig = NULLFUNC; + pci224_ao_start(dev, s); + + return 1; +} + +#define MAX_SCAN_PERIOD 0xFFFFFFFFU +#define MIN_SCAN_PERIOD 2500 +#define CONVERT_PERIOD 625 + +/* + * 'do_cmdtest' function for AO subdevice. + */ +static int +pci224_ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int tmp; + + /* Step 1: make sure trigger sources are trivially valid. */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_INT | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT | TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_EXT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* Step 2: make sure trigger sources are unique and mutually + * compatible. */ + + /* these tests are true if more than one _src bit is set */ + if ((cmd->start_src & (cmd->start_src - 1)) != 0) + err++; + if ((cmd->scan_begin_src & (cmd->scan_begin_src - 1)) != 0) + err++; + if ((cmd->convert_src & (cmd->convert_src - 1)) != 0) + err++; + if ((cmd->scan_end_src & (cmd->scan_end_src - 1)) != 0) + err++; + if ((cmd->stop_src & (cmd->stop_src - 1)) != 0) + err++; + + /* There's only one external trigger signal (which makes these + * tests easier). Only one thing can use it. */ + tmp = 0; + if (cmd->start_src & TRIG_EXT) + tmp++; + if (cmd->scan_begin_src & TRIG_EXT) + tmp++; + if (cmd->stop_src & TRIG_EXT) + tmp++; + if (tmp > 1) + err++; + + if (err) + return 2; + + /* Step 3: make sure arguments are trivially compatible. */ + + switch (cmd->start_src) { + case TRIG_INT: + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + break; + case TRIG_EXT: + /* Force to external trigger 0. */ + if ((cmd->start_arg & ~CR_FLAGS_MASK) != 0) { + cmd->start_arg = COMBINE(cmd->start_arg, 0, + ~CR_FLAGS_MASK); + err++; + } + /* The only flag allowed is CR_EDGE, which is ignored. */ + if ((cmd->start_arg & CR_FLAGS_MASK & ~CR_EDGE) != 0) { + cmd->start_arg = COMBINE(cmd->start_arg, 0, + CR_FLAGS_MASK & ~CR_EDGE); + err++; + } + break; + } + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + if (cmd->scan_begin_arg > MAX_SCAN_PERIOD) { + cmd->scan_begin_arg = MAX_SCAN_PERIOD; + err++; + } + tmp = cmd->chanlist_len * CONVERT_PERIOD; + if (tmp < MIN_SCAN_PERIOD) + tmp = MIN_SCAN_PERIOD; + + if (cmd->scan_begin_arg < tmp) { + cmd->scan_begin_arg = tmp; + err++; + } + break; + case TRIG_EXT: + /* Force to external trigger 0. */ + if ((cmd->scan_begin_arg & ~CR_FLAGS_MASK) != 0) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + ~CR_FLAGS_MASK); + err++; + } + /* Only allow flags CR_EDGE and CR_INVERT. Ignore CR_EDGE. */ + if ((cmd->scan_begin_arg & CR_FLAGS_MASK & + ~(CR_EDGE | CR_INVERT)) != 0) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + CR_FLAGS_MASK & ~(CR_EDGE + | + CR_INVERT)); + err++; + } + break; + } + + /* cmd->convert_src == TRIG_NOW */ + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + + /* cmd->scan_end_arg == TRIG_COUNT */ + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + /* Any count allowed. */ + break; + case TRIG_EXT: + /* Force to external trigger 0. */ + if ((cmd->stop_arg & ~CR_FLAGS_MASK) != 0) { + cmd->stop_arg = COMBINE(cmd->stop_arg, 0, + ~CR_FLAGS_MASK); + err++; + } + /* The only flag allowed is CR_EDGE, which is ignored. */ + if ((cmd->stop_arg & CR_FLAGS_MASK & ~CR_EDGE) != 0) { + cmd->stop_arg = COMBINE(cmd->stop_arg, 0, + CR_FLAGS_MASK & ~CR_EDGE); + } + break; + case TRIG_NONE: + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + break; + } + + if (err) + return 3; + + /* Step 4: fix up any arguments. */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + unsigned int div1, div2, round; + int round_mode = cmd->flags & TRIG_ROUND_MASK; + + tmp = cmd->scan_begin_arg; + /* Check whether to use a single timer. */ + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + round = TIMEBASE_10MHZ / 2; + break; + case TRIG_ROUND_DOWN: + round = 0; + break; + case TRIG_ROUND_UP: + round = TIMEBASE_10MHZ - 1; + break; + } + /* Be careful to avoid overflow! */ + div2 = cmd->scan_begin_arg / TIMEBASE_10MHZ; + div2 += (round + cmd->scan_begin_arg % TIMEBASE_10MHZ) / + TIMEBASE_10MHZ; + if (div2 <= 0x10000) { + /* A single timer will suffice. */ + if (div2 < 2) + div2 = 2; + cmd->scan_begin_arg = div2 * TIMEBASE_10MHZ; + if (cmd->scan_begin_arg < div2 || + cmd->scan_begin_arg < TIMEBASE_10MHZ) { + /* Overflow! */ + cmd->scan_begin_arg = MAX_SCAN_PERIOD; + } + } else { + /* Use two timers. */ + div1 = devpriv->cached_div1; + div2 = devpriv->cached_div2; + pci224_cascade_ns_to_timer(TIMEBASE_10MHZ, &div1, &div2, + &cmd->scan_begin_arg, + round_mode); + devpriv->cached_div1 = div1; + devpriv->cached_div2 = div2; + } + if (tmp != cmd->scan_begin_arg) + err++; + + } + + if (err) + return 4; + + /* Step 5: check channel list. */ + + if (cmd->chanlist && (cmd->chanlist_len > 0)) { + unsigned int range; + enum { range_err = 1, dupchan_err = 2, }; + unsigned errors; + unsigned int n; + unsigned int ch; + + /* + * Check all channels have the same range index. Don't care + * about analogue reference, as we can't configure it. + * + * Check the list has no duplicate channels. + */ + range = CR_RANGE(cmd->chanlist[0]); + errors = 0; + tmp = 0; + for (n = 0; n < cmd->chanlist_len; n++) { + ch = CR_CHAN(cmd->chanlist[n]); + if (tmp & (1U << ch)) + errors |= dupchan_err; + + tmp |= (1U << ch); + if (CR_RANGE(cmd->chanlist[n]) != range) + errors |= range_err; + + } + if (errors) { + if (errors & dupchan_err) { + DPRINTK("comedi%d: " DRIVER_NAME + ": ao_cmdtest: " + "entries in chanlist must contain no " + "duplicate channels\n", dev->minor); + } + if (errors & range_err) { + DPRINTK("comedi%d: " DRIVER_NAME + ": ao_cmdtest: " + "entries in chanlist must all have " + "the same range index\n", dev->minor); + } + err++; + } + } + + if (err) + return 5; + + return 0; +} + +/* + * 'do_cmd' function for AO subdevice. + */ +static int pci224_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int range; + unsigned int i, j; + unsigned int ch; + unsigned int rank; + unsigned long flags; + + /* Cannot handle null/empty chanlist. */ + if (cmd->chanlist == NULL || cmd->chanlist_len == 0) + return -EINVAL; + + + /* Determine which channels are enabled and their load order. */ + devpriv->ao_enab = 0; + + for (i = 0; i < cmd->chanlist_len; i++) { + ch = CR_CHAN(cmd->chanlist[i]); + devpriv->ao_enab |= 1U << ch; + rank = 0; + for (j = 0; j < cmd->chanlist_len; j++) { + if (CR_CHAN(cmd->chanlist[j]) < ch) + rank++; + + } + devpriv->ao_scan_order[rank] = i; + } + + /* Set enabled channels. */ + outw(devpriv->ao_enab, dev->iobase + PCI224_DACCEN); + + /* Determine range and polarity. All channels the same. */ + range = CR_RANGE(cmd->chanlist[0]); + + /* + * Set DAC range and polarity. + * Set DAC scan trigger source to 'none'. + * Set DAC FIFO interrupt trigger level to 'not half full'. + * Reset DAC FIFO. + * + * N.B. DAC FIFO interrupts are currently disabled. + */ + devpriv->daccon = COMBINE(devpriv->daccon, + (devpriv-> + hwrange[range] | PCI224_DACCON_TRIG_NONE | + PCI224_DACCON_FIFOINTR_NHALF), + (PCI224_DACCON_POLAR_MASK | + PCI224_DACCON_VREF_MASK | + PCI224_DACCON_TRIG_MASK | + PCI224_DACCON_FIFOINTR_MASK)); + outw(devpriv->daccon | PCI224_DACCON_FIFORESET, + dev->iobase + PCI224_DACCON); + + if (cmd->scan_begin_src == TRIG_TIMER) { + unsigned int div1, div2, round; + unsigned int ns = cmd->scan_begin_arg; + int round_mode = cmd->flags & TRIG_ROUND_MASK; + + /* Check whether to use a single timer. */ + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + round = TIMEBASE_10MHZ / 2; + break; + case TRIG_ROUND_DOWN: + round = 0; + break; + case TRIG_ROUND_UP: + round = TIMEBASE_10MHZ - 1; + break; + } + /* Be careful to avoid overflow! */ + div2 = cmd->scan_begin_arg / TIMEBASE_10MHZ; + div2 += (round + cmd->scan_begin_arg % TIMEBASE_10MHZ) / + TIMEBASE_10MHZ; + if (div2 <= 0x10000) { + /* A single timer will suffice. */ + if (div2 < 2) + div2 = 2; + div2 &= 0xffff; + div1 = 1; /* Flag that single timer to be used. */ + } else { + /* Use two timers. */ + div1 = devpriv->cached_div1; + div2 = devpriv->cached_div2; + pci224_cascade_ns_to_timer(TIMEBASE_10MHZ, &div1, &div2, + &ns, round_mode); + } + + /* + * The output of timer Z2-0 will be used as the scan trigger + * source. + */ + /* Make sure Z2-0 is gated on. */ + outb(GAT_CONFIG(0, GAT_VCC), + devpriv->iobase1 + PCI224_ZGAT_SCE); + if (div1 == 1) { + /* Not cascading. Z2-0 needs 10 MHz clock. */ + outb(CLK_CONFIG(0, CLK_10MHZ), + devpriv->iobase1 + PCI224_ZCLK_SCE); + } else { + /* Cascading with Z2-2. */ + /* Make sure Z2-2 is gated on. */ + outb(GAT_CONFIG(2, GAT_VCC), + devpriv->iobase1 + PCI224_ZGAT_SCE); + /* Z2-2 needs 10 MHz clock. */ + outb(CLK_CONFIG(2, CLK_10MHZ), + devpriv->iobase1 + PCI224_ZCLK_SCE); + /* Load Z2-2 mode (2) and counter (div1). */ + i8254_load(devpriv->iobase1 + PCI224_Z2_CT0, 0, + 2, div1, 2); + /* Z2-0 is clocked from Z2-2's output. */ + outb(CLK_CONFIG(0, CLK_OUTNM1), + devpriv->iobase1 + PCI224_ZCLK_SCE); + } + /* Load Z2-0 mode (2) and counter (div2). */ + i8254_load(devpriv->iobase1 + PCI224_Z2_CT0, 0, 0, div2, 2); + } + + /* + * Sort out end of acquisition. + */ + switch (cmd->stop_src) { + case TRIG_COUNT: + /* Fixed number of scans. */ + devpriv->ao_stop_continuous = 0; + devpriv->ao_stop_count = cmd->stop_arg; + break; + default: + /* Continuous scans. */ + devpriv->ao_stop_continuous = 1; + devpriv->ao_stop_count = 0; + break; + } + + /* + * Sort out start of acquisition. + */ + switch (cmd->start_src) { + case TRIG_INT: + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + s->async->inttrig = &pci224_ao_inttrig_start; + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + break; + case TRIG_EXT: + /* Enable external interrupt trigger to start acquisition. */ + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + devpriv->intsce |= PCI224_INTR_EXT; + outb(devpriv->intsce, devpriv->iobase1 + PCI224_INT_SCE); + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + break; + } + + return 0; +} + +/* + * 'cancel' function for AO subdevice. + */ +static int pci224_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + pci224_ao_stop(dev, s); + return 0; +} + +/* + * 'munge' data for AO command. + */ +static void +pci224_ao_munge(struct comedi_device *dev, struct comedi_subdevice *s, + void *data, unsigned int num_bytes, unsigned int chan_index) +{ + struct comedi_async *async = s->async; + short *array = data; + unsigned int length = num_bytes / sizeof(*array); + unsigned int offset; + unsigned int shift; + unsigned int i; + + /* The hardware expects 16-bit numbers. */ + shift = 16 - thisboard->ao_bits; + /* Channels will be all bipolar or all unipolar. */ + if ((devpriv->hwrange[CR_RANGE(async->cmd.chanlist[0])] & + PCI224_DACCON_POLAR_MASK) == PCI224_DACCON_POLAR_UNI) { + /* Unipolar */ + offset = 0; + } else { + /* Bipolar */ + offset = 32768; + } + /* Munge the data. */ + for (i = 0; i < length; i++) + array[i] = (array[i] << shift) - offset; + +} + +/* + * Interrupt handler. + */ +static irqreturn_t pci224_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = &dev->subdevices[0]; + struct comedi_cmd *cmd; + unsigned char intstat, valid_intstat; + unsigned char curenab; + int retval = 0; + unsigned long flags; + + intstat = inb(devpriv->iobase1 + PCI224_INT_SCE) & 0x3F; + if (intstat) { + retval = 1; + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + valid_intstat = devpriv->intsce & intstat; + /* Temporarily disable interrupt sources. */ + curenab = devpriv->intsce & ~intstat; + outb(curenab, devpriv->iobase1 + PCI224_INT_SCE); + devpriv->intr_running = 1; + devpriv->intr_cpuid = THISCPU; + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + if (valid_intstat != 0) { + cmd = &s->async->cmd; + if (valid_intstat & PCI224_INTR_EXT) { + devpriv->intsce &= ~PCI224_INTR_EXT; + if (cmd->start_src == TRIG_EXT) + pci224_ao_start(dev, s); + else if (cmd->stop_src == TRIG_EXT) + pci224_ao_stop(dev, s); + + } + if (valid_intstat & PCI224_INTR_DAC) + pci224_ao_handle_fifo(dev, s); + + } + /* Reenable interrupt sources. */ + spin_lock_irqsave(&devpriv->ao_spinlock, flags); + if (curenab != devpriv->intsce) { + outb(devpriv->intsce, + devpriv->iobase1 + PCI224_INT_SCE); + } + devpriv->intr_running = 0; + spin_unlock_irqrestore(&devpriv->ao_spinlock, flags); + } + return IRQ_RETVAL(retval); +} + +/* + * This function looks for a PCI device matching the requested board name, + * bus and slot. + */ +static int +pci224_find_pci(struct comedi_device *dev, int bus, int slot, + struct pci_dev **pci_dev_p) +{ + struct pci_dev *pci_dev = NULL; + + *pci_dev_p = NULL; + + /* Look for matching PCI device. */ + for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL); + pci_dev != NULL; + pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, + pci_dev)) { + /* If bus/slot specified, check them. */ + if (bus || slot) { + if (bus != pci_dev->bus->number + || slot != PCI_SLOT(pci_dev->devfn)) + continue; + } + if (thisboard->model == any_model) { + /* Match any supported model. */ + int i; + + for (i = 0; i < ARRAY_SIZE(pci224_boards); i++) { + if (pci_dev->device == pci224_boards[i].devid) { + /* Change board_ptr to matched board. */ + dev->board_ptr = &pci224_boards[i]; + break; + } + } + if (i == ARRAY_SIZE(pci224_boards)) + continue; + } else { + /* Match specific model name. */ + if (thisboard->devid != pci_dev->device) + continue; + } + + /* Found a match. */ + *pci_dev_p = pci_dev; + return 0; + } + /* No match found. */ + if (bus || slot) { + printk(KERN_ERR "comedi%d: error! " + "no %s found at pci %02x:%02x!\n", + dev->minor, thisboard->name, bus, slot); + } else { + printk(KERN_ERR "comedi%d: error! no %s found!\n", + dev->minor, thisboard->name); + } + return -EIO; +} + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pci224_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + struct pci_dev *pci_dev; + unsigned int irq; + int bus = 0, slot = 0; + unsigned n; + int ret; + + printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor, DRIVER_NAME); + + bus = it->options[0]; + slot = it->options[1]; + ret = alloc_private(dev, sizeof(struct pci224_private)); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return ret; + } + + ret = pci224_find_pci(dev, bus, slot, &pci_dev); + if (ret < 0) + return ret; + + devpriv->pci_dev = pci_dev; + ret = comedi_pci_enable(pci_dev, DRIVER_NAME); + if (ret < 0) { + printk(KERN_ERR + "comedi%d: error! cannot enable PCI device " + "and request regions!\n", dev->minor); + return ret; + } + spin_lock_init(&devpriv->ao_spinlock); + + devpriv->iobase1 = pci_resource_start(pci_dev, 2); + dev->iobase = pci_resource_start(pci_dev, 3); + irq = pci_dev->irq; + + /* Allocate readback buffer for AO channels. */ + devpriv->ao_readback = kmalloc(sizeof(devpriv->ao_readback[0]) * + thisboard->ao_chans, GFP_KERNEL); + if (!devpriv->ao_readback) + return -ENOMEM; + + + /* Allocate buffer to hold values for AO channel scan. */ + devpriv->ao_scan_vals = kmalloc(sizeof(devpriv->ao_scan_vals[0]) * + thisboard->ao_chans, GFP_KERNEL); + if (!devpriv->ao_scan_vals) + return -ENOMEM; + + + /* Allocate buffer to hold AO channel scan order. */ + devpriv->ao_scan_order = kmalloc(sizeof(devpriv->ao_scan_order[0]) * + thisboard->ao_chans, GFP_KERNEL); + if (!devpriv->ao_scan_order) + return -ENOMEM; + + + /* Disable interrupt sources. */ + devpriv->intsce = 0; + outb(0, devpriv->iobase1 + PCI224_INT_SCE); + + /* Initialize the DAC hardware. */ + outw(PCI224_DACCON_GLOBALRESET, dev->iobase + PCI224_DACCON); + outw(0, dev->iobase + PCI224_DACCEN); + outw(0, dev->iobase + PCI224_FIFOSIZ); + devpriv->daccon = (PCI224_DACCON_TRIG_SW | PCI224_DACCON_POLAR_BI | + PCI224_DACCON_FIFOENAB | + PCI224_DACCON_FIFOINTR_EMPTY); + outw(devpriv->daccon | PCI224_DACCON_FIFORESET, + dev->iobase + PCI224_DACCON); + + /* Allocate subdevices. There is only one! */ + ret = alloc_subdevices(dev, 1); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! out of memory!\n", + dev->minor); + return ret; + } + + s = dev->subdevices + 0; + /* Analog output subdevice. */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; + s->n_chan = thisboard->ao_chans; + s->maxdata = (1 << thisboard->ao_bits) - 1; + s->insn_write = &pci224_ao_insn_write; + s->insn_read = &pci224_ao_insn_read; + s->len_chanlist = s->n_chan; + + dev->write_subdev = s; + s->do_cmd = &pci224_ao_cmd; + s->do_cmdtest = &pci224_ao_cmdtest; + s->cancel = &pci224_ao_cancel; + s->munge = &pci224_ao_munge; + + /* Sort out channel range options. */ + if (thisboard->model == pci234_model) { + /* PCI234 range options. */ + const struct comedi_lrange **range_table_list; + + s->range_table_list = range_table_list = + kmalloc(sizeof(struct comedi_lrange *) * s->n_chan, + GFP_KERNEL); + if (!s->range_table_list) + return -ENOMEM; + + for (n = 2; n < 3 + s->n_chan; n++) { + if (it->options[n] < 0 || it->options[n] > 1) { + printk(KERN_WARNING "comedi%d: %s: warning! " + "bad options[%u]=%d\n", + dev->minor, DRIVER_NAME, n, + it->options[n]); + } + } + for (n = 0; n < s->n_chan; n++) { + if (n < COMEDI_NDEVCONFOPTS - 3 && + it->options[3 + n] == 1) { + if (it->options[2] == 1) + range_table_list[n] = &range_pci234_ext; + else + range_table_list[n] = &range_bipolar5; + + } else { + if (it->options[2] == 1) { + range_table_list[n] = + &range_pci234_ext2; + } else { + range_table_list[n] = &range_bipolar10; + } + } + } + devpriv->hwrange = hwrange_pci234; + } else { + /* PCI224 range options. */ + if (it->options[2] == 1) { + s->range_table = &range_pci224_external; + devpriv->hwrange = hwrange_pci224_external; + } else { + if (it->options[2] != 0) { + printk(KERN_WARNING "comedi%d: %s: warning! " + "bad options[2]=%d\n", + dev->minor, DRIVER_NAME, it->options[2]); + } + s->range_table = &range_pci224_internal; + devpriv->hwrange = hwrange_pci224_internal; + } + } + + dev->board_name = thisboard->name; + + if (irq) { + ret = request_irq(irq, pci224_interrupt, IRQF_SHARED, + DRIVER_NAME, dev); + if (ret < 0) { + printk(KERN_ERR "comedi%d: error! " + "unable to allocate irq %u\n", dev->minor, irq); + return ret; + } else { + dev->irq = irq; + } + } + + printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name); + printk("(pci %s) ", pci_name(pci_dev)); + if (irq) + printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE")); + else + printk("(no irq) "); + + + printk("attached\n"); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pci224_detach(struct comedi_device *dev) +{ + printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor, DRIVER_NAME); + + if (dev->irq) + free_irq(dev->irq, dev); + + if (dev->subdevices) { + struct comedi_subdevice *s; + + s = dev->subdevices + 0; + /* AO subdevice */ + kfree(s->range_table_list); + } + if (devpriv) { + kfree(devpriv->ao_readback); + kfree(devpriv->ao_scan_vals); + kfree(devpriv->ao_scan_order); + if (devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + + pci_dev_put(devpriv->pci_dev); + } + } + if (dev->board_name) { + printk(KERN_INFO "comedi%d: %s removed\n", + dev->minor, dev->board_name); + } + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/amplc_pci230.c b/drivers/staging/comedi/drivers/amplc_pci230.c new file mode 100644 index 00000000..53897955 --- /dev/null +++ b/drivers/staging/comedi/drivers/amplc_pci230.c @@ -0,0 +1,3053 @@ + /* + comedi/drivers/amplc_pci230.c + Driver for Amplicon PCI230 and PCI260 Multifunction I/O boards. + + Copyright (C) 2001 Allan Willcox + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/* +Driver: amplc_pci230 +Description: Amplicon PCI230, PCI260 Multifunction I/O boards +Author: Allan Willcox , + Steve D Sharples , + Ian Abbott +Updated: Wed, 22 Oct 2008 12:34:49 +0100 +Devices: [Amplicon] PCI230 (pci230 or amplc_pci230), + PCI230+ (pci230+ or amplc_pci230), + PCI260 (pci260 or amplc_pci230), PCI260+ (pci260+ or amplc_pci230) +Status: works + +Configuration options: + [0] - PCI bus of device (optional). + [1] - PCI slot of device (optional). + If bus/slot is not specified, the first available PCI device + will be used. + +Configuring a "amplc_pci230" will match any supported card and it will +choose the best match, picking the "+" models if possible. Configuring +a "pci230" will match a PCI230 or PCI230+ card and it will be treated as +a PCI230. Configuring a "pci260" will match a PCI260 or PCI260+ card +and it will be treated as a PCI260. Configuring a "pci230+" will match +a PCI230+ card. Configuring a "pci260+" will match a PCI260+ card. + +Subdevices: + + PCI230(+) PCI260(+) + --------- --------- + Subdevices 3 1 + 0 AI AI + 1 AO + 2 DIO + +AI Subdevice: + + The AI subdevice has 16 single-ended channels or 8 differential + channels. + + The PCI230 and PCI260 cards have 12-bit resolution. The PCI230+ and + PCI260+ cards have 16-bit resolution. + + For differential mode, use inputs 2N and 2N+1 for channel N (e.g. use + inputs 14 and 15 for channel 7). If the card is physically a PCI230 + or PCI260 then it actually uses a "pseudo-differential" mode where the + inputs are sampled a few microseconds apart. The PCI230+ and PCI260+ + use true differential sampling. Another difference is that if the + card is physically a PCI230 or PCI260, the inverting input is 2N, + whereas for a PCI230+ or PCI260+ the inverting input is 2N+1. So if a + PCI230 is physically replaced by a PCI230+ (or a PCI260 with a + PCI260+) and differential mode is used, the differential inputs need + to be physically swapped on the connector. + + The following input ranges are supported: + + 0 => [-10, +10] V + 1 => [-5, +5] V + 2 => [-2.5, +2.5] V + 3 => [-1.25, +1.25] V + 4 => [0, 10] V + 5 => [0, 5] V + 6 => [0, 2.5] V + +AI Commands: + + +=========+==============+===========+============+==========+ + |start_src|scan_begin_src|convert_src|scan_end_src| stop_src | + +=========+==============+===========+============+==========+ + |TRIG_NOW | TRIG_FOLLOW |TRIG_TIMER | TRIG_COUNT |TRIG_NONE | + |TRIG_INT | |TRIG_EXT(3)| |TRIG_COUNT| + | | |TRIG_INT | | | + | |--------------|-----------| | | + | | TRIG_TIMER(1)|TRIG_TIMER | | | + | | TRIG_EXT(2) | | | | + | | TRIG_INT | | | | + +---------+--------------+-----------+------------+----------+ + + Note 1: If AI command and AO command are used simultaneously, only + one may have scan_begin_src == TRIG_TIMER. + + Note 2: For PCI230 and PCI230+, scan_begin_src == TRIG_EXT uses + DIO channel 16 (pin 49) which will need to be configured as + a digital input. For PCI260+, the EXTTRIG/EXTCONVCLK input + (pin 17) is used instead. For PCI230, scan_begin_src == + TRIG_EXT is not supported. The trigger is a rising edge + on the input. + + Note 3: For convert_src == TRIG_EXT, the EXTTRIG/EXTCONVCLK input + (pin 25 on PCI230(+), pin 17 on PCI260(+)) is used. The + convert_arg value is interpreted as follows: + + convert_arg == (CR_EDGE | 0) => rising edge + convert_arg == (CR_EDGE | CR_INVERT | 0) => falling edge + convert_arg == 0 => falling edge (backwards compatibility) + convert_arg == 1 => rising edge (backwards compatibility) + + All entries in the channel list must use the same analogue reference. + If the analogue reference is not AREF_DIFF (not differential) each + pair of channel numbers (0 and 1, 2 and 3, etc.) must use the same + input range. The input ranges used in the sequence must be all + bipolar (ranges 0 to 3) or all unipolar (ranges 4 to 6). The channel + sequence must consist of 1 or more identical subsequences. Within the + subsequence, channels must be in ascending order with no repeated + channels. For example, the following sequences are valid: 0 1 2 3 + (single valid subsequence), 0 2 3 5 0 2 3 5 (repeated valid + subsequence), 1 1 1 1 (repeated valid subsequence). The following + sequences are invalid: 0 3 2 1 (invalid subsequence), 0 2 3 5 0 2 3 + (incompletely repeated subsequence). Some versions of the PCI230+ and + PCI260+ have a bug that requires a subsequence longer than one entry + long to include channel 0. + +AO Subdevice: + + The AO subdevice has 2 channels with 12-bit resolution. + + The following output ranges are supported: + + 0 => [0, 10] V + 1 => [-10, +10] V + +AO Commands: + + +=========+==============+===========+============+==========+ + |start_src|scan_begin_src|convert_src|scan_end_src| stop_src | + +=========+==============+===========+============+==========+ + |TRIG_INT | TRIG_TIMER(1)| TRIG_NOW | TRIG_COUNT |TRIG_NONE | + | | TRIG_EXT(2) | | |TRIG_COUNT| + | | TRIG_INT | | | | + +---------+--------------+-----------+------------+----------+ + + Note 1: If AI command and AO command are used simultaneously, only + one may have scan_begin_src == TRIG_TIMER. + + Note 2: scan_begin_src == TRIG_EXT is only supported if the card is + configured as a PCI230+ and is only supported on later + versions of the card. As a card configured as a PCI230+ is + not guaranteed to support external triggering, please consider + this support to be a bonus. It uses the EXTTRIG/ EXTCONVCLK + input (PCI230+ pin 25). Triggering will be on the rising edge + unless the CR_INVERT flag is set in scan_begin_arg. + + The channels in the channel sequence must be in ascending order with + no repeats. All entries in the channel sequence must use the same + output range. + +DIO Subdevice: + + The DIO subdevice is a 8255 chip providing 24 DIO channels. The DIO + channels are configurable as inputs or outputs in four groups: + + Port A - channels 0 to 7 + Port B - channels 8 to 15 + Port CL - channels 16 to 19 + Port CH - channels 20 to 23 + + Only mode 0 of the 8255 chip is supported. + + Bit 0 of port C (DIO channel 16) is also used as an external scan + trigger input for AI commands on PCI230 and PCI230+, so would need to + be configured as an input to use it for that purpose. +*/ +/* +Extra triggered scan functionality, interrupt bug-fix added by Steve Sharples. +Support for PCI230+/260+, more triggered scan functionality, and workarounds +for (or detection of) various hardware problems added by Ian Abbott. +*/ + +#include "../comedidev.h" + +#include +#include + +#include "comedi_pci.h" +#include "8253.h" +#include "8255.h" + +/* PCI230 PCI configuration register information */ +#define PCI_VENDOR_ID_AMPLICON 0x14dc +#define PCI_DEVICE_ID_PCI230 0x0000 +#define PCI_DEVICE_ID_PCI260 0x0006 +#define PCI_DEVICE_ID_INVALID 0xffff + +#define PCI230_IO1_SIZE 32 /* Size of I/O space 1 */ +#define PCI230_IO2_SIZE 16 /* Size of I/O space 2 */ + +/* PCI230 i/o space 1 registers. */ +#define PCI230_PPI_X_BASE 0x00 /* User PPI (82C55) base */ +#define PCI230_PPI_X_A 0x00 /* User PPI (82C55) port A */ +#define PCI230_PPI_X_B 0x01 /* User PPI (82C55) port B */ +#define PCI230_PPI_X_C 0x02 /* User PPI (82C55) port C */ +#define PCI230_PPI_X_CMD 0x03 /* User PPI (82C55) control word */ +#define PCI230_Z2_CT_BASE 0x14 /* 82C54 counter/timer base */ +#define PCI230_Z2_CT0 0x14 /* 82C54 counter/timer 0 */ +#define PCI230_Z2_CT1 0x15 /* 82C54 counter/timer 1 */ +#define PCI230_Z2_CT2 0x16 /* 82C54 counter/timer 2 */ +#define PCI230_Z2_CTC 0x17 /* 82C54 counter/timer control word */ +#define PCI230_ZCLK_SCE 0x1A /* Group Z Clock Configuration */ +#define PCI230_ZGAT_SCE 0x1D /* Group Z Gate Configuration */ +#define PCI230_INT_SCE 0x1E /* Interrupt source mask (w) */ +#define PCI230_INT_STAT 0x1E /* Interrupt status (r) */ + +/* PCI230 i/o space 2 registers. */ +#define PCI230_DACCON 0x00 /* DAC control */ +#define PCI230_DACOUT1 0x02 /* DAC channel 0 (w) */ +#define PCI230_DACOUT2 0x04 /* DAC channel 1 (w) (not FIFO mode) */ +#define PCI230_ADCDATA 0x08 /* ADC data (r) */ +#define PCI230_ADCSWTRIG 0x08 /* ADC software trigger (w) */ +#define PCI230_ADCCON 0x0A /* ADC control */ +#define PCI230_ADCEN 0x0C /* ADC channel enable bits */ +#define PCI230_ADCG 0x0E /* ADC gain control bits */ +/* PCI230+ i/o space 2 additional registers. */ +#define PCI230P_ADCTRIG 0x10 /* ADC start acquisition trigger */ +#define PCI230P_ADCTH 0x12 /* ADC analog trigger threshold */ +#define PCI230P_ADCFFTH 0x14 /* ADC FIFO interrupt threshold */ +#define PCI230P_ADCFFLEV 0x16 /* ADC FIFO level (r) */ +#define PCI230P_ADCPTSC 0x18 /* ADC pre-trigger sample count (r) */ +#define PCI230P_ADCHYST 0x1A /* ADC analog trigger hysteresys */ +#define PCI230P_EXTFUNC 0x1C /* Extended functions */ +#define PCI230P_HWVER 0x1E /* Hardware version (r) */ +/* PCI230+ hardware version 2 onwards. */ +#define PCI230P2_DACDATA 0x02 /* DAC data (FIFO mode) (w) */ +#define PCI230P2_DACSWTRIG 0x02 /* DAC soft trigger (FIFO mode) (r) */ +#define PCI230P2_DACEN 0x06 /* DAC channel enable (FIFO mode) */ + +/* Convertor related constants. */ +#define PCI230_DAC_SETTLE 5 /* Analogue output settling time in µs */ + /* (DAC itself is 1µs nominally). */ +#define PCI230_ADC_SETTLE 1 /* Analogue input settling time in µs */ + /* (ADC itself is 1.6µs nominally but we poll + * anyway). */ +#define PCI230_MUX_SETTLE 10 /* ADC MUX settling time in µS */ + /* - 10µs for se, 20µs de. */ + +/* DACCON read-write values. */ +#define PCI230_DAC_OR_UNI (0<<0) /* Output range unipolar */ +#define PCI230_DAC_OR_BIP (1<<0) /* Output range bipolar */ +#define PCI230_DAC_OR_MASK (1<<0) +/* The following applies only if DAC FIFO support is enabled in the EXTFUNC + * register (and only for PCI230+ hardware version 2 onwards). */ +#define PCI230P2_DAC_FIFO_EN (1<<8) /* FIFO enable */ +/* The following apply only if the DAC FIFO is enabled (and only for PCI230+ + * hardware version 2 onwards). */ +#define PCI230P2_DAC_TRIG_NONE (0<<2) /* No trigger */ +#define PCI230P2_DAC_TRIG_SW (1<<2) /* Software trigger trigger */ +#define PCI230P2_DAC_TRIG_EXTP (2<<2) /* EXTTRIG +ve edge trigger */ +#define PCI230P2_DAC_TRIG_EXTN (3<<2) /* EXTTRIG -ve edge trigger */ +#define PCI230P2_DAC_TRIG_Z2CT0 (4<<2) /* CT0-OUT +ve edge trigger */ +#define PCI230P2_DAC_TRIG_Z2CT1 (5<<2) /* CT1-OUT +ve edge trigger */ +#define PCI230P2_DAC_TRIG_Z2CT2 (6<<2) /* CT2-OUT +ve edge trigger */ +#define PCI230P2_DAC_TRIG_MASK (7<<2) +#define PCI230P2_DAC_FIFO_WRAP (1<<7) /* FIFO wraparound mode */ +#define PCI230P2_DAC_INT_FIFO_EMPTY (0<<9) /* FIFO interrupt empty */ +#define PCI230P2_DAC_INT_FIFO_NEMPTY (1<<9) +#define PCI230P2_DAC_INT_FIFO_NHALF (2<<9) /* FIFO intr not half full */ +#define PCI230P2_DAC_INT_FIFO_HALF (3<<9) +#define PCI230P2_DAC_INT_FIFO_NFULL (4<<9) /* FIFO interrupt not full */ +#define PCI230P2_DAC_INT_FIFO_FULL (5<<9) +#define PCI230P2_DAC_INT_FIFO_MASK (7<<9) + +/* DACCON read-only values. */ +#define PCI230_DAC_BUSY (1<<1) /* DAC busy. */ +/* The following apply only if the DAC FIFO is enabled (and only for PCI230+ + * hardware version 2 onwards). */ +#define PCI230P2_DAC_FIFO_UNDERRUN_LATCHED (1<<5) /* Underrun error */ +#define PCI230P2_DAC_FIFO_EMPTY (1<<13) /* FIFO empty */ +#define PCI230P2_DAC_FIFO_FULL (1<<14) /* FIFO full */ +#define PCI230P2_DAC_FIFO_HALF (1<<15) /* FIFO half full */ + +/* DACCON write-only, transient values. */ +/* The following apply only if the DAC FIFO is enabled (and only for PCI230+ + * hardware version 2 onwards). */ +#define PCI230P2_DAC_FIFO_UNDERRUN_CLEAR (1<<5) /* Clear underrun */ +#define PCI230P2_DAC_FIFO_RESET (1<<12) /* FIFO reset */ + +/* PCI230+ hardware version 2 DAC FIFO levels. */ +#define PCI230P2_DAC_FIFOLEVEL_HALF 512 +#define PCI230P2_DAC_FIFOLEVEL_FULL 1024 +/* Free space in DAC FIFO. */ +#define PCI230P2_DAC_FIFOROOM_EMPTY PCI230P2_DAC_FIFOLEVEL_FULL +#define PCI230P2_DAC_FIFOROOM_ONETOHALF \ + (PCI230P2_DAC_FIFOLEVEL_FULL - PCI230P2_DAC_FIFOLEVEL_HALF) +#define PCI230P2_DAC_FIFOROOM_HALFTOFULL 1 +#define PCI230P2_DAC_FIFOROOM_FULL 0 + +/* ADCCON read/write values. */ +#define PCI230_ADC_TRIG_NONE (0<<0) /* No trigger */ +#define PCI230_ADC_TRIG_SW (1<<0) /* Software trigger trigger */ +#define PCI230_ADC_TRIG_EXTP (2<<0) /* EXTTRIG +ve edge trigger */ +#define PCI230_ADC_TRIG_EXTN (3<<0) /* EXTTRIG -ve edge trigger */ +#define PCI230_ADC_TRIG_Z2CT0 (4<<0) /* CT0-OUT +ve edge trigger */ +#define PCI230_ADC_TRIG_Z2CT1 (5<<0) /* CT1-OUT +ve edge trigger */ +#define PCI230_ADC_TRIG_Z2CT2 (6<<0) /* CT2-OUT +ve edge trigger */ +#define PCI230_ADC_TRIG_MASK (7<<0) +#define PCI230_ADC_IR_UNI (0<<3) /* Input range unipolar */ +#define PCI230_ADC_IR_BIP (1<<3) /* Input range bipolar */ +#define PCI230_ADC_IR_MASK (1<<3) +#define PCI230_ADC_IM_SE (0<<4) /* Input mode single ended */ +#define PCI230_ADC_IM_DIF (1<<4) /* Input mode differential */ +#define PCI230_ADC_IM_MASK (1<<4) +#define PCI230_ADC_FIFO_EN (1<<8) /* FIFO enable */ +#define PCI230_ADC_INT_FIFO_EMPTY (0<<9) +#define PCI230_ADC_INT_FIFO_NEMPTY (1<<9) /* FIFO interrupt not empty */ +#define PCI230_ADC_INT_FIFO_NHALF (2<<9) +#define PCI230_ADC_INT_FIFO_HALF (3<<9) /* FIFO interrupt half full */ +#define PCI230_ADC_INT_FIFO_NFULL (4<<9) +#define PCI230_ADC_INT_FIFO_FULL (5<<9) /* FIFO interrupt full */ +#define PCI230P_ADC_INT_FIFO_THRESH (7<<9) /* FIFO interrupt threshold */ +#define PCI230_ADC_INT_FIFO_MASK (7<<9) + +/* ADCCON write-only, transient values. */ +#define PCI230_ADC_FIFO_RESET (1<<12) /* FIFO reset */ +#define PCI230_ADC_GLOB_RESET (1<<13) /* Global reset */ + +/* ADCCON read-only values. */ +#define PCI230_ADC_BUSY (1<<15) /* ADC busy */ +#define PCI230_ADC_FIFO_EMPTY (1<<12) /* FIFO empty */ +#define PCI230_ADC_FIFO_FULL (1<<13) /* FIFO full */ +#define PCI230_ADC_FIFO_HALF (1<<14) /* FIFO half full */ +#define PCI230_ADC_FIFO_FULL_LATCHED (1<<5) /* Indicates overrun occurred */ + +/* PCI230 ADC FIFO levels. */ +#define PCI230_ADC_FIFOLEVEL_HALFFULL 2049 /* Value for FIFO half full */ +#define PCI230_ADC_FIFOLEVEL_FULL 4096 /* FIFO size */ + +/* Value to write to ADCSWTRIG to trigger ADC conversion in software trigger + * mode. Can be anything. */ +#define PCI230_ADC_CONV 0xffff + +/* PCI230+ EXTFUNC values. */ +#define PCI230P_EXTFUNC_GAT_EXTTRIG (1<<0) + /* Route EXTTRIG pin to external gate inputs. */ +/* PCI230+ hardware version 2 values. */ +#define PCI230P2_EXTFUNC_DACFIFO (1<<1) + /* Allow DAC FIFO to be enabled. */ + +/* + * Counter/timer clock input configuration sources. + */ +#define CLK_CLK 0 /* reserved (channel-specific clock) */ +#define CLK_10MHZ 1 /* internal 10 MHz clock */ +#define CLK_1MHZ 2 /* internal 1 MHz clock */ +#define CLK_100KHZ 3 /* internal 100 kHz clock */ +#define CLK_10KHZ 4 /* internal 10 kHz clock */ +#define CLK_1KHZ 5 /* internal 1 kHz clock */ +#define CLK_OUTNM1 6 /* output of channel-1 modulo total */ +#define CLK_EXT 7 /* external clock */ +/* Macro to construct clock input configuration register value. */ +#define CLK_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7)) +/* Timebases in ns. */ +#define TIMEBASE_10MHZ 100 +#define TIMEBASE_1MHZ 1000 +#define TIMEBASE_100KHZ 10000 +#define TIMEBASE_10KHZ 100000 +#define TIMEBASE_1KHZ 1000000 + +/* + * Counter/timer gate input configuration sources. + */ +#define GAT_VCC 0 /* VCC (i.e. enabled) */ +#define GAT_GND 1 /* GND (i.e. disabled) */ +#define GAT_EXT 2 /* external gate input (PPCn on PCI230) */ +#define GAT_NOUTNM2 3 /* inverted output of channel-2 modulo total */ +/* Macro to construct gate input configuration register value. */ +#define GAT_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7)) + +/* + * Summary of CLK_OUTNM1 and GAT_NOUTNM2 connections for PCI230 and PCI260: + * + * Channel's Channel's + * clock input gate input + * Channel CLK_OUTNM1 GAT_NOUTNM2 + * ------- ---------- ----------- + * Z2-CT0 Z2-CT2-OUT /Z2-CT1-OUT + * Z2-CT1 Z2-CT0-OUT /Z2-CT2-OUT + * Z2-CT2 Z2-CT1-OUT /Z2-CT0-OUT + */ + +/* Interrupt enables/status register values. */ +#define PCI230_INT_DISABLE 0 +#define PCI230_INT_PPI_C0 (1<<0) +#define PCI230_INT_PPI_C3 (1<<1) +#define PCI230_INT_ADC (1<<2) +#define PCI230_INT_ZCLK_CT1 (1<<5) +/* For PCI230+ hardware version 2 when DAC FIFO enabled. */ +#define PCI230P2_INT_DAC (1<<4) + +#define PCI230_TEST_BIT(val, n) ((val>>n)&1) + /* Assumes bits numbered with zero offset, ie. 0-15 */ + +/* (Potentially) shared resources and their owners */ +enum { + RES_Z2CT0, /* Z2-CT0 */ + RES_Z2CT1, /* Z2-CT1 */ + RES_Z2CT2, /* Z2-CT2 */ + NUM_RESOURCES /* Number of (potentially) shared resources. */ +}; + +enum { + OWNER_NONE, /* Not owned */ + OWNER_AICMD, /* Owned by AI command */ + OWNER_AOCMD /* Owned by AO command */ +}; + +/* + * Handy macros. + */ + +/* Combine old and new bits. */ +#define COMBINE(old, new, mask) (((old) & ~(mask)) | ((new) & (mask))) + +/* A generic null function pointer value. */ +#define NULLFUNC 0 + +/* Current CPU. XXX should this be hard_smp_processor_id()? */ +#define THISCPU smp_processor_id() + +/* State flags for atomic bit operations */ +#define AI_CMD_STARTED 0 +#define AO_CMD_STARTED 1 + +/* + * Board descriptions for the two boards supported. + */ + +struct pci230_board { + const char *name; + unsigned short id; + int ai_chans; + int ai_bits; + int ao_chans; + int ao_bits; + int have_dio; + unsigned int min_hwver; /* Minimum hardware version supported. */ +}; +static const struct pci230_board pci230_boards[] = { + { + .name = "pci230+", + .id = PCI_DEVICE_ID_PCI230, + .ai_chans = 16, + .ai_bits = 16, + .ao_chans = 2, + .ao_bits = 12, + .have_dio = 1, + .min_hwver = 1, + }, + { + .name = "pci260+", + .id = PCI_DEVICE_ID_PCI260, + .ai_chans = 16, + .ai_bits = 16, + .ao_chans = 0, + .ao_bits = 0, + .have_dio = 0, + .min_hwver = 1, + }, + { + .name = "pci230", + .id = PCI_DEVICE_ID_PCI230, + .ai_chans = 16, + .ai_bits = 12, + .ao_chans = 2, + .ao_bits = 12, + .have_dio = 1, + }, + { + .name = "pci260", + .id = PCI_DEVICE_ID_PCI260, + .ai_chans = 16, + .ai_bits = 12, + .ao_chans = 0, + .ao_bits = 0, + .have_dio = 0, + }, + { + .name = "amplc_pci230", /* Wildcard matches any above */ + .id = PCI_DEVICE_ID_INVALID, + }, +}; + +static DEFINE_PCI_DEVICE_TABLE(pci230_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI230) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI260) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, pci230_pci_table); +/* + * Useful for shorthand access to the particular board structure + */ +#define n_pci230_boards ARRAY_SIZE(pci230_boards) +#define thisboard ((const struct pci230_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. */ +struct pci230_private { + struct pci_dev *pci_dev; + spinlock_t isr_spinlock; /* Interrupt spin lock */ + spinlock_t res_spinlock; /* Shared resources spin lock */ + spinlock_t ai_stop_spinlock; /* Spin lock for stopping AI command */ + spinlock_t ao_stop_spinlock; /* Spin lock for stopping AO command */ + unsigned long state; /* State flags */ + unsigned long iobase1; /* PCI230's I/O space 1 */ + unsigned int ao_readback[2]; /* Used for AO readback */ + unsigned int ai_scan_count; /* Number of analogue input scans + * remaining. */ + unsigned int ai_scan_pos; /* Current position within analogue + * input scan */ + unsigned int ao_scan_count; /* Number of analogue output scans + * remaining. */ + int intr_cpuid; /* ID of CPU running interrupt routine. */ + unsigned short hwver; /* Hardware version (for '+' models). */ + unsigned short adccon; /* ADCCON register value. */ + unsigned short daccon; /* DACCON register value. */ + unsigned short adcfifothresh; /* ADC FIFO programmable interrupt + * level threshold (PCI230+/260+). */ + unsigned short adcg; /* ADCG register value. */ + unsigned char int_en; /* Interrupt enables bits. */ + unsigned char ai_continuous; /* Flag set when cmd->stop_src == + * TRIG_NONE - user chooses to stop + * continuous conversion by + * cancelation. */ + unsigned char ao_continuous; /* Flag set when cmd->stop_src == + * TRIG_NONE - user chooses to stop + * continuous conversion by + * cancelation. */ + unsigned char ai_bipolar; /* Set if bipolar input range so we + * know to mangle it. */ + unsigned char ao_bipolar; /* Set if bipolar output range so we + * know to mangle it. */ + unsigned char ier; /* Copy of interrupt enables/status register. */ + unsigned char intr_running; /* Flag set in interrupt routine. */ + unsigned char res_owner[NUM_RESOURCES]; /* Shared resource owners. */ +}; + +#define devpriv ((struct pci230_private *)dev->private) + +/* PCI230 clock source periods in ns */ +static const unsigned int pci230_timebase[8] = { + [CLK_10MHZ] = TIMEBASE_10MHZ, + [CLK_1MHZ] = TIMEBASE_1MHZ, + [CLK_100KHZ] = TIMEBASE_100KHZ, + [CLK_10KHZ] = TIMEBASE_10KHZ, + [CLK_1KHZ] = TIMEBASE_1KHZ, +}; + +/* PCI230 analogue input range table */ +static const struct comedi_lrange pci230_ai_range = { 7, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5) + } +}; + +/* PCI230 analogue gain bits for each input range. */ +static const unsigned char pci230_ai_gain[7] = { 0, 1, 2, 3, 1, 2, 3 }; + +/* PCI230 adccon bipolar flag for each analogue input range. */ +static const unsigned char pci230_ai_bipolar[7] = { 1, 1, 1, 1, 0, 0, 0 }; + +/* PCI230 analogue output range table */ +static const struct comedi_lrange pci230_ao_range = { 2, { + UNI_RANGE(10), + BIP_RANGE(10) + } +}; + +/* PCI230 daccon bipolar flag for each analogue output range. */ +static const unsigned char pci230_ao_bipolar[2] = { 0, 1 }; + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pci230_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pci230_detach(struct comedi_device *dev); +static struct comedi_driver driver_amplc_pci230 = { + .driver_name = "amplc_pci230", + .module = THIS_MODULE, + .attach = pci230_attach, + .detach = pci230_detach, + .board_name = &pci230_boards[0].name, + .offset = sizeof(pci230_boards[0]), + .num_names = ARRAY_SIZE(pci230_boards), +}; + +static int __devinit driver_amplc_pci230_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_amplc_pci230.driver_name); +} + +static void __devexit driver_amplc_pci230_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_amplc_pci230_pci_driver = { + .id_table = pci230_pci_table, + .probe = &driver_amplc_pci230_pci_probe, + .remove = __devexit_p(&driver_amplc_pci230_pci_remove) +}; + +static int __init driver_amplc_pci230_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_amplc_pci230); + if (retval < 0) + return retval; + + driver_amplc_pci230_pci_driver.name = + (char *)driver_amplc_pci230.driver_name; + return pci_register_driver(&driver_amplc_pci230_pci_driver); +} + +static void __exit driver_amplc_pci230_cleanup_module(void) +{ + pci_unregister_driver(&driver_amplc_pci230_pci_driver); + comedi_driver_unregister(&driver_amplc_pci230); +} + +module_init(driver_amplc_pci230_init_module); +module_exit(driver_amplc_pci230_cleanup_module); + +static int pci230_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int pci230_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int pci230_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static void pci230_ct_setup_ns_mode(struct comedi_device *dev, unsigned int ct, + unsigned int mode, uint64_t ns, + unsigned int round); +static void pci230_ns_to_single_timer(unsigned int *ns, unsigned int round); +static void pci230_cancel_ct(struct comedi_device *dev, unsigned int ct); +static irqreturn_t pci230_interrupt(int irq, void *d); +static int pci230_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int pci230_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int pci230_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void pci230_ao_stop(struct comedi_device *dev, + struct comedi_subdevice *s); +static void pci230_handle_ao_nofifo(struct comedi_device *dev, + struct comedi_subdevice *s); +static int pci230_handle_ao_fifo(struct comedi_device *dev, + struct comedi_subdevice *s); +static int pci230_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int pci230_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int pci230_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void pci230_ai_stop(struct comedi_device *dev, + struct comedi_subdevice *s); +static void pci230_handle_ai(struct comedi_device *dev, + struct comedi_subdevice *s); + +static short pci230_ai_read(struct comedi_device *dev) +{ + /* Read sample. */ + short data = (short)inw(dev->iobase + PCI230_ADCDATA); + + /* PCI230 is 12 bit - stored in upper bits of 16 bit register (lower + * four bits reserved for expansion). */ + /* PCI230+ is 16 bit AI. */ + data = data >> (16 - thisboard->ai_bits); + + /* If a bipolar range was specified, mangle it (twos + * complement->straight binary). */ + if (devpriv->ai_bipolar) + data ^= 1 << (thisboard->ai_bits - 1); + + return data; +} + +static inline unsigned short pci230_ao_mangle_datum(struct comedi_device *dev, + short datum) +{ + /* If a bipolar range was specified, mangle it (straight binary->twos + * complement). */ + if (devpriv->ao_bipolar) + datum ^= 1 << (thisboard->ao_bits - 1); + + + /* PCI230 is 12 bit - stored in upper bits of 16 bit register (lower + * four bits reserved for expansion). */ + /* PCI230+ is also 12 bit AO. */ + datum <<= (16 - thisboard->ao_bits); + return (unsigned short)datum; +} + +static inline void pci230_ao_write_nofifo(struct comedi_device *dev, + short datum, unsigned int chan) +{ + /* Store unmangled datum to be read back later. */ + devpriv->ao_readback[chan] = datum; + + /* Write mangled datum to appropriate DACOUT register. */ + outw(pci230_ao_mangle_datum(dev, datum), dev->iobase + (((chan) == 0) + ? PCI230_DACOUT1 + : + PCI230_DACOUT2)); +} + +static inline void pci230_ao_write_fifo(struct comedi_device *dev, short datum, + unsigned int chan) +{ + /* Store unmangled datum to be read back later. */ + devpriv->ao_readback[chan] = datum; + + /* Write mangled datum to appropriate DACDATA register. */ + outw(pci230_ao_mangle_datum(dev, datum), + dev->iobase + PCI230P2_DACDATA); +} + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pci230_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase1, iobase2; + /* PCI230's I/O spaces 1 and 2 respectively. */ + struct pci_dev *pci_dev = NULL; + int i = 0, irq_hdl, rc; + + printk("comedi%d: amplc_pci230: attach %s %d,%d\n", dev->minor, + thisboard->name, it->options[0], it->options[1]); + + /* Allocate the private structure area using alloc_private(). + * Macro defined in comedidev.h - memsets struct fields to 0. */ + if ((alloc_private(dev, sizeof(struct pci230_private))) < 0) + return -ENOMEM; + + spin_lock_init(&devpriv->isr_spinlock); + spin_lock_init(&devpriv->res_spinlock); + spin_lock_init(&devpriv->ai_stop_spinlock); + spin_lock_init(&devpriv->ao_stop_spinlock); + /* Find card */ + for_each_pci_dev(pci_dev) { + if (it->options[0] || it->options[1]) { + /* Match against bus/slot options. */ + if (it->options[0] != pci_dev->bus->number || + it->options[1] != PCI_SLOT(pci_dev->devfn)) + continue; + } + if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON) + continue; + if (thisboard->id == PCI_DEVICE_ID_INVALID) { + /* The name was specified as "amplc_pci230" which is + * used to match any supported device. Replace the + * current dev->board_ptr with one that matches the + * PCI device ID. */ + for (i = 0; i < n_pci230_boards; i++) { + if (pci_dev->device == pci230_boards[i].id) { + if (pci230_boards[i].min_hwver > 0) { + /* Check for a '+' model. + * First check length of + * registers. */ + if (pci_resource_len(pci_dev, 3) + < 32) { + /* Not a '+' model. */ + continue; + } + /* TODO: temporarily enable the + * PCI device and read the + * hardware version register. + * For now assume it's okay. */ + } + /* Change board_ptr to matched board */ + dev->board_ptr = &pci230_boards[i]; + break; + } + } + if (i < n_pci230_boards) + break; + } else { + /* The name was specified as a specific device name. + * The current dev->board_ptr is correct. Check + * whether it matches the PCI device ID. */ + if (thisboard->id == pci_dev->device) { + /* Check minimum hardware version. */ + if (thisboard->min_hwver > 0) { + /* Looking for a '+' model. First + * check length of registers. */ + if (pci_resource_len(pci_dev, 3) < 32) { + /* Not a '+' model. */ + continue; + } + /* TODO: temporarily enable the PCI + * device and read the hardware version + * register. For now, assume it's + * okay. */ + break; + } else { + break; + } + } + } + } + if (!pci_dev) { + printk("comedi%d: No %s card found\n", dev->minor, + thisboard->name); + return -EIO; + } + devpriv->pci_dev = pci_dev; + + /* + * Initialize dev->board_name. + */ + dev->board_name = thisboard->name; + + /* Enable PCI device and reserve I/O spaces. */ + if (comedi_pci_enable(pci_dev, "amplc_pci230") < 0) { + printk("comedi%d: failed to enable PCI device " + "and request regions\n", dev->minor); + return -EIO; + } + + /* Read base addresses of the PCI230's two I/O regions from PCI + * configuration register. */ + iobase1 = pci_resource_start(pci_dev, 2); + iobase2 = pci_resource_start(pci_dev, 3); + + printk("comedi%d: %s I/O region 1 0x%04lx I/O region 2 0x%04lx\n", + dev->minor, dev->board_name, iobase1, iobase2); + + devpriv->iobase1 = iobase1; + dev->iobase = iobase2; + + /* Read bits of DACCON register - only the output range. */ + devpriv->daccon = inw(dev->iobase + PCI230_DACCON) & PCI230_DAC_OR_MASK; + + /* Read hardware version register and set extended function register + * if they exist. */ + if (pci_resource_len(pci_dev, 3) >= 32) { + unsigned short extfunc = 0; + + devpriv->hwver = inw(dev->iobase + PCI230P_HWVER); + if (devpriv->hwver < thisboard->min_hwver) { + printk("comedi%d: %s - bad hardware version " + "- got %u, need %u\n", dev->minor, + dev->board_name, devpriv->hwver, + thisboard->min_hwver); + return -EIO; + } + if (devpriv->hwver > 0) { + if (!thisboard->have_dio) { + /* No DIO ports. Route counters' external gates + * to the EXTTRIG signal (PCI260+ pin 17). + * (Otherwise, they would be routed to DIO + * inputs PC0, PC1 and PC2 which don't exist + * on PCI260[+].) */ + extfunc |= PCI230P_EXTFUNC_GAT_EXTTRIG; + } + if ((thisboard->ao_chans > 0) + && (devpriv->hwver >= 2)) { + /* Enable DAC FIFO functionality. */ + extfunc |= PCI230P2_EXTFUNC_DACFIFO; + } + } + outw(extfunc, dev->iobase + PCI230P_EXTFUNC); + if ((extfunc & PCI230P2_EXTFUNC_DACFIFO) != 0) { + /* Temporarily enable DAC FIFO, reset it and disable + * FIFO wraparound. */ + outw(devpriv->daccon | PCI230P2_DAC_FIFO_EN + | PCI230P2_DAC_FIFO_RESET, + dev->iobase + PCI230_DACCON); + /* Clear DAC FIFO channel enable register. */ + outw(0, dev->iobase + PCI230P2_DACEN); + /* Disable DAC FIFO. */ + outw(devpriv->daccon, dev->iobase + PCI230_DACCON); + } + } + + /* Disable board's interrupts. */ + outb(0, devpriv->iobase1 + PCI230_INT_SCE); + + /* Set ADC to a reasonable state. */ + devpriv->adcg = 0; + devpriv->adccon = PCI230_ADC_TRIG_NONE | PCI230_ADC_IM_SE + | PCI230_ADC_IR_BIP; + outw(1 << 0, dev->iobase + PCI230_ADCEN); + outw(devpriv->adcg, dev->iobase + PCI230_ADCG); + outw(devpriv->adccon | PCI230_ADC_FIFO_RESET, + dev->iobase + PCI230_ADCCON); + + /* Register the interrupt handler. */ + irq_hdl = request_irq(devpriv->pci_dev->irq, pci230_interrupt, + IRQF_SHARED, "amplc_pci230", dev); + if (irq_hdl < 0) { + printk("comedi%d: unable to register irq, " + "commands will not be available %d\n", dev->minor, + devpriv->pci_dev->irq); + } else { + dev->irq = devpriv->pci_dev->irq; + printk("comedi%d: registered irq %u\n", dev->minor, + devpriv->pci_dev->irq); + } + + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 3) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_GROUND; + s->n_chan = thisboard->ai_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &pci230_ai_range; + s->insn_read = &pci230_ai_rinsn; + s->len_chanlist = 256; /* but there are restrictions. */ + /* Only register commands if the interrupt handler is installed. */ + if (irq_hdl == 0) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->do_cmd = &pci230_ai_cmd; + s->do_cmdtest = &pci230_ai_cmdtest; + s->cancel = pci230_ai_cancel; + } + + s = dev->subdevices + 1; + /* analog output subdevice */ + if (thisboard->ao_chans > 0) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = thisboard->ao_chans; + s->maxdata = (1 << thisboard->ao_bits) - 1; + s->range_table = &pci230_ao_range; + s->insn_write = &pci230_ao_winsn; + s->insn_read = &pci230_ao_rinsn; + s->len_chanlist = thisboard->ao_chans; + /* Only register commands if the interrupt handler is + * installed. */ + if (irq_hdl == 0) { + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + s->do_cmd = &pci230_ao_cmd; + s->do_cmdtest = &pci230_ao_cmdtest; + s->cancel = pci230_ao_cancel; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 2; + /* digital i/o subdevice */ + if (thisboard->have_dio) { + rc = subdev_8255_init(dev, s, NULL, + (devpriv->iobase1 + PCI230_PPI_X_BASE)); + if (rc < 0) + return rc; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + printk("comedi%d: attached\n", dev->minor); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pci230_detach(struct comedi_device *dev) +{ + printk("comedi%d: amplc_pci230: remove\n", dev->minor); + + if (dev->subdevices && thisboard->have_dio) + /* Clean up dio subdevice. */ + subdev_8255_cleanup(dev, dev->subdevices + 2); + + if (dev->irq) + free_irq(dev->irq, dev); + + if (devpriv) { + if (devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + + pci_dev_put(devpriv->pci_dev); + } + } + + return 0; +} + +static int get_resources(struct comedi_device *dev, unsigned int res_mask, + unsigned char owner) +{ + int ok; + unsigned int i; + unsigned int b; + unsigned int claimed; + unsigned long irqflags; + + ok = 1; + claimed = 0; + spin_lock_irqsave(&devpriv->res_spinlock, irqflags); + for (b = 1, i = 0; (i < NUM_RESOURCES) + && (res_mask != 0); b <<= 1, i++) { + if ((res_mask & b) != 0) { + res_mask &= ~b; + if (devpriv->res_owner[i] == OWNER_NONE) { + devpriv->res_owner[i] = owner; + claimed |= b; + } else if (devpriv->res_owner[i] != owner) { + for (b = 1, i = 0; claimed != 0; b <<= 1, i++) { + if ((claimed & b) != 0) { + devpriv->res_owner[i] + = OWNER_NONE; + claimed &= ~b; + } + } + ok = 0; + break; + } + } + } + spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags); + return ok; +} + +static inline int get_one_resource(struct comedi_device *dev, + unsigned int resource, unsigned char owner) +{ + return get_resources(dev, (1U << resource), owner); +} + +static void put_resources(struct comedi_device *dev, unsigned int res_mask, + unsigned char owner) +{ + unsigned int i; + unsigned int b; + unsigned long irqflags; + + spin_lock_irqsave(&devpriv->res_spinlock, irqflags); + for (b = 1, i = 0; (i < NUM_RESOURCES) + && (res_mask != 0); b <<= 1, i++) { + if ((res_mask & b) != 0) { + res_mask &= ~b; + if (devpriv->res_owner[i] == owner) + devpriv->res_owner[i] = OWNER_NONE; + + } + } + spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags); +} + +static inline void put_one_resource(struct comedi_device *dev, + unsigned int resource, unsigned char owner) +{ + put_resources(dev, (1U << resource), owner); +} + +static inline void put_all_resources(struct comedi_device *dev, + unsigned char owner) +{ + put_resources(dev, (1U << NUM_RESOURCES) - 1, owner); +} + +/* + * COMEDI_SUBD_AI instruction; + */ +static int pci230_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int n, i; + unsigned int chan, range, aref; + unsigned int gainshift; + unsigned int status; + unsigned short adccon, adcen; + + /* Unpack channel and range. */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + aref = CR_AREF(insn->chanspec); + if (aref == AREF_DIFF) { + /* Differential. */ + if (chan >= s->n_chan / 2) { + DPRINTK("comedi%d: amplc_pci230: ai_rinsn: " + "differential channel number out of range " + "0 to %u\n", dev->minor, (s->n_chan / 2) - 1); + return -EINVAL; + } + } + + /* Use Z2-CT2 as a conversion trigger instead of the built-in + * software trigger, as otherwise triggering of differential channels + * doesn't work properly for some versions of PCI230/260. Also set + * FIFO mode because the ADC busy bit only works for software triggers. + */ + adccon = PCI230_ADC_TRIG_Z2CT2 | PCI230_ADC_FIFO_EN; + /* Set Z2-CT2 output low to avoid any false triggers. */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, I8254_MODE0); + devpriv->ai_bipolar = pci230_ai_bipolar[range]; + if (aref == AREF_DIFF) { + /* Differential. */ + gainshift = chan * 2; + if (devpriv->hwver == 0) { + /* Original PCI230/260 expects both inputs of the + * differential channel to be enabled. */ + adcen = 3 << gainshift; + } else { + /* PCI230+/260+ expects only one input of the + * differential channel to be enabled. */ + adcen = 1 << gainshift; + } + adccon |= PCI230_ADC_IM_DIF; + } else { + /* Single ended. */ + adcen = 1 << chan; + gainshift = chan & ~1; + adccon |= PCI230_ADC_IM_SE; + } + devpriv->adcg = (devpriv->adcg & ~(3 << gainshift)) + | (pci230_ai_gain[range] << gainshift); + if (devpriv->ai_bipolar) + adccon |= PCI230_ADC_IR_BIP; + else + adccon |= PCI230_ADC_IR_UNI; + + + /* Enable only this channel in the scan list - otherwise by default + * we'll get one sample from each channel. */ + outw(adcen, dev->iobase + PCI230_ADCEN); + + /* Set gain for channel. */ + outw(devpriv->adcg, dev->iobase + PCI230_ADCG); + + /* Specify uni/bip, se/diff, conversion source, and reset FIFO. */ + devpriv->adccon = adccon; + outw(adccon | PCI230_ADC_FIFO_RESET, dev->iobase + PCI230_ADCCON); + + /* Convert n samples */ + for (n = 0; n < insn->n; n++) { + /* Trigger conversion by toggling Z2-CT2 output (finish with + * output high). */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, + I8254_MODE0); + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, + I8254_MODE1); + +#define TIMEOUT 100 + /* wait for conversion to end */ + for (i = 0; i < TIMEOUT; i++) { + status = inw(dev->iobase + PCI230_ADCCON); + if (!(status & PCI230_ADC_FIFO_EMPTY)) + break; + udelay(1); + } + if (i == TIMEOUT) { + /* printk() should be used instead of printk() + * whenever the code can be called from real-time. */ + printk("timeout\n"); + return -ETIMEDOUT; + } + + /* read data */ + data[n] = pci230_ai_read(dev); + } + + /* return the number of samples read/written */ + return n; +} + +/* + * COMEDI_SUBD_AO instructions; + */ +static int pci230_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i; + int chan, range; + + /* Unpack channel and range. */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* Set range - see analogue output range table; 0 => unipolar 10V, + * 1 => bipolar +/-10V range scale */ + devpriv->ao_bipolar = pci230_ao_bipolar[range]; + outw(range, dev->iobase + PCI230_DACCON); + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; i++) { + /* Write value to DAC and store it. */ + pci230_ao_write_nofifo(dev, data[i], chan); + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int pci230_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int pci230_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int tmp; + + /* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. */ + + /* Step 1: make sure trigger sources are trivially valid. + * "invalid source" returned by comedilib to user mode process + * if this fails. */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + if ((thisboard->min_hwver > 0) && (devpriv->hwver >= 2)) { + /* + * For PCI230+ hardware version 2 onwards, allow external + * trigger from EXTTRIG/EXTCONVCLK input (PCI230+ pin 25). + * + * FIXME: The permitted scan_begin_src values shouldn't depend + * on devpriv->hwver (the detected card's actual hardware + * version). They should only depend on thisboard->min_hwver + * (the static capabilities of the configured card). To fix + * it, a new card model, e.g. "pci230+2" would have to be + * defined with min_hwver set to 2. It doesn't seem worth it + * for this alone. At the moment, please consider + * scan_begin_src==TRIG_EXT support to be a bonus rather than a + * guarantee! + */ + cmd->scan_begin_src &= TRIG_TIMER | TRIG_INT | TRIG_EXT; + } else { + cmd->scan_begin_src &= TRIG_TIMER | TRIG_INT; + } + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* Step 2: make sure trigger sources are unique and mutually compatible + * "source conflict" returned by comedilib to user mode process + * if this fails. */ + + /* these tests are true if more than one _src bit is set */ + if ((cmd->start_src & (cmd->start_src - 1)) != 0) + err++; + if ((cmd->scan_begin_src & (cmd->scan_begin_src - 1)) != 0) + err++; + if ((cmd->convert_src & (cmd->convert_src - 1)) != 0) + err++; + if ((cmd->scan_end_src & (cmd->scan_end_src - 1)) != 0) + err++; + if ((cmd->stop_src & (cmd->stop_src - 1)) != 0) + err++; + + if (err) + return 2; + + /* Step 3: make sure arguments are trivially compatible. + * "invalid argument" returned by comedilib to user mode process + * if this fails. */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } +#define MAX_SPEED_AO 8000 /* 8000 ns => 125 kHz */ +#define MIN_SPEED_AO 4294967295u /* 4294967295ns = 4.29s */ + /*- Comedi limit due to unsigned int cmd. Driver limit + * = 2^16 (16bit * counter) * 1000000ns (1kHz onboard + * clock) = 65.536s */ + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + if (cmd->scan_begin_arg < MAX_SPEED_AO) { + cmd->scan_begin_arg = MAX_SPEED_AO; + err++; + } + if (cmd->scan_begin_arg > MIN_SPEED_AO) { + cmd->scan_begin_arg = MIN_SPEED_AO; + err++; + } + break; + case TRIG_EXT: + /* External trigger - for PCI230+ hardware version 2 onwards. */ + /* Trigger number must be 0. */ + if ((cmd->scan_begin_arg & ~CR_FLAGS_MASK) != 0) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + ~CR_FLAGS_MASK); + err++; + } + /* The only flags allowed are CR_EDGE and CR_INVERT. The + * CR_EDGE flag is ignored. */ + if ((cmd->scan_begin_arg + & (CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT))) != 0) { + cmd->scan_begin_arg = + COMBINE(cmd->scan_begin_arg, 0, + CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT)); + err++; + } + break; + default: + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + break; + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_NONE) { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* Step 4: fix up any arguments. + * "argument conflict" returned by comedilib to user mode process + * if this fails. */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + pci230_ns_to_single_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists. */ + + if (cmd->chanlist && cmd->chanlist_len > 0) { + enum { + seq_err = (1 << 0), + range_err = (1 << 1) + }; + unsigned int errors; + unsigned int n; + unsigned int chan, prev_chan; + unsigned int range, first_range; + + prev_chan = CR_CHAN(cmd->chanlist[0]); + first_range = CR_RANGE(cmd->chanlist[0]); + errors = 0; + for (n = 1; n < cmd->chanlist_len; n++) { + chan = CR_CHAN(cmd->chanlist[n]); + range = CR_RANGE(cmd->chanlist[n]); + /* Channel numbers must strictly increase. */ + if (chan < prev_chan) + errors |= seq_err; + + /* Ranges must be the same. */ + if (range != first_range) + errors |= range_err; + + prev_chan = chan; + } + if (errors != 0) { + err++; + if ((errors & seq_err) != 0) { + DPRINTK("comedi%d: amplc_pci230: ao_cmdtest: " + "channel numbers must increase\n", + dev->minor); + } + if ((errors & range_err) != 0) { + DPRINTK("comedi%d: amplc_pci230: ao_cmdtest: " + "channels must have the same range\n", + dev->minor); + } + } + } + + if (err) + return 5; + + return 0; +} + +static int pci230_ao_inttrig_scan_begin(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + unsigned long irqflags; + + if (trig_num != 0) + return -EINVAL; + + spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags); + if (test_bit(AO_CMD_STARTED, &devpriv->state)) { + /* Perform scan. */ + if (devpriv->hwver < 2) { + /* Not using DAC FIFO. */ + spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, + irqflags); + pci230_handle_ao_nofifo(dev, s); + comedi_event(dev, s); + } else { + /* Using DAC FIFO. */ + /* Read DACSWTRIG register to trigger conversion. */ + inw(dev->iobase + PCI230P2_DACSWTRIG); + spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, + irqflags); + } + /* Delay. Should driver be responsible for this? */ + /* XXX TODO: See if DAC busy bit can be used. */ + udelay(8); + } + + return 1; +} + +static void pci230_ao_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned long irqflags; + + set_bit(AO_CMD_STARTED, &devpriv->state); + if (!devpriv->ao_continuous && (devpriv->ao_scan_count == 0)) { + /* An empty acquisition! */ + async->events |= COMEDI_CB_EOA; + pci230_ao_stop(dev, s); + comedi_event(dev, s); + } else { + if (devpriv->hwver >= 2) { + /* Using DAC FIFO. */ + unsigned short scantrig; + int run; + + /* Preload FIFO data. */ + run = pci230_handle_ao_fifo(dev, s); + comedi_event(dev, s); + if (!run) { + /* Stopped. */ + return; + } + /* Set scan trigger source. */ + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + scantrig = PCI230P2_DAC_TRIG_Z2CT1; + break; + case TRIG_EXT: + /* Trigger on EXTTRIG/EXTCONVCLK pin. */ + if ((cmd->scan_begin_arg & CR_INVERT) == 0) { + /* +ve edge */ + scantrig = PCI230P2_DAC_TRIG_EXTP; + } else { + /* -ve edge */ + scantrig = PCI230P2_DAC_TRIG_EXTN; + } + break; + case TRIG_INT: + scantrig = PCI230P2_DAC_TRIG_SW; + break; + default: + /* Shouldn't get here. */ + scantrig = PCI230P2_DAC_TRIG_NONE; + break; + } + devpriv->daccon = (devpriv->daccon + & ~PCI230P2_DAC_TRIG_MASK) | + scantrig; + outw(devpriv->daccon, dev->iobase + PCI230_DACCON); + + } + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + if (devpriv->hwver < 2) { + /* Not using DAC FIFO. */ + /* Enable CT1 timer interrupt. */ + spin_lock_irqsave(&devpriv->isr_spinlock, + irqflags); + devpriv->int_en |= PCI230_INT_ZCLK_CT1; + devpriv->ier |= PCI230_INT_ZCLK_CT1; + outb(devpriv->ier, + devpriv->iobase1 + PCI230_INT_SCE); + spin_unlock_irqrestore(&devpriv->isr_spinlock, + irqflags); + } + /* Set CT1 gate high to start counting. */ + outb(GAT_CONFIG(1, GAT_VCC), + devpriv->iobase1 + PCI230_ZGAT_SCE); + break; + case TRIG_INT: + async->inttrig = pci230_ao_inttrig_scan_begin; + break; + } + if (devpriv->hwver >= 2) { + /* Using DAC FIFO. Enable DAC FIFO interrupt. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + devpriv->int_en |= PCI230P2_INT_DAC; + devpriv->ier |= PCI230P2_INT_DAC; + outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); + spin_unlock_irqrestore(&devpriv->isr_spinlock, + irqflags); + } + } +} + +static int pci230_ao_inttrig_start(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + if (trig_num != 0) + return -EINVAL; + + s->async->inttrig = NULLFUNC; + pci230_ao_start(dev, s); + + return 1; +} + +static int pci230_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned short daccon; + unsigned int range; + + /* Get the command. */ + struct comedi_cmd *cmd = &s->async->cmd; + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Claim Z2-CT1. */ + if (!get_one_resource(dev, RES_Z2CT1, OWNER_AOCMD)) + return -EBUSY; + + } + + /* Get number of scans required. */ + if (cmd->stop_src == TRIG_COUNT) { + devpriv->ao_scan_count = cmd->stop_arg; + devpriv->ao_continuous = 0; + } else { + /* TRIG_NONE, user calls cancel. */ + devpriv->ao_scan_count = 0; + devpriv->ao_continuous = 1; + } + + /* Set range - see analogue output range table; 0 => unipolar 10V, + * 1 => bipolar +/-10V range scale */ + range = CR_RANGE(cmd->chanlist[0]); + devpriv->ao_bipolar = pci230_ao_bipolar[range]; + daccon = devpriv->ao_bipolar ? PCI230_DAC_OR_BIP : PCI230_DAC_OR_UNI; + /* Use DAC FIFO for hardware version 2 onwards. */ + if (devpriv->hwver >= 2) { + unsigned short dacen; + unsigned int i; + + dacen = 0; + for (i = 0; i < cmd->chanlist_len; i++) + dacen |= 1 << CR_CHAN(cmd->chanlist[i]); + + /* Set channel scan list. */ + outw(dacen, dev->iobase + PCI230P2_DACEN); + /* + * Enable DAC FIFO. + * Set DAC scan source to 'none'. + * Set DAC FIFO interrupt trigger level to 'not half full'. + * Reset DAC FIFO and clear underrun. + * + * N.B. DAC FIFO interrupts are currently disabled. + */ + daccon |= PCI230P2_DAC_FIFO_EN | PCI230P2_DAC_FIFO_RESET + | PCI230P2_DAC_FIFO_UNDERRUN_CLEAR + | PCI230P2_DAC_TRIG_NONE | PCI230P2_DAC_INT_FIFO_NHALF; + } + + /* Set DACCON. */ + outw(daccon, dev->iobase + PCI230_DACCON); + /* Preserve most of DACCON apart from write-only, transient bits. */ + devpriv->daccon = daccon + & ~(PCI230P2_DAC_FIFO_RESET | PCI230P2_DAC_FIFO_UNDERRUN_CLEAR); + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Set the counter timer 1 to the specified scan frequency. */ + /* cmd->scan_begin_arg is sampling period in ns */ + /* gate it off for now. */ + outb(GAT_CONFIG(1, GAT_GND), + devpriv->iobase1 + PCI230_ZGAT_SCE); + pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3, + cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + } + + /* N.B. cmd->start_src == TRIG_INT */ + s->async->inttrig = pci230_ao_inttrig_start; + + return 0; +} + +static int pci230_ai_check_scan_period(struct comedi_cmd *cmd) +{ + unsigned int min_scan_period, chanlist_len; + int err = 0; + + chanlist_len = cmd->chanlist_len; + if (cmd->chanlist_len == 0) + chanlist_len = 1; + + min_scan_period = chanlist_len * cmd->convert_arg; + if ((min_scan_period < chanlist_len) + || (min_scan_period < cmd->convert_arg)) { + /* Arithmetic overflow. */ + min_scan_period = UINT_MAX; + err++; + } + if (cmd->scan_begin_arg < min_scan_period) { + cmd->scan_begin_arg = min_scan_period; + err++; + } + + return !err; +} + +static int pci230_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int tmp; + + /* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4,5 or 0, depending on which tests + * the command passes. */ + + /* Step 1: make sure trigger sources are trivially valid. + * "invalid source" returned by comedilib to user mode process + * if this fails. */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + /* Unfortunately, we cannot trigger a scan off an external source + * on the PCI260 board, since it uses the PPIC0 (DIO) input, which + * isn't present on the PCI260. For PCI260+ we can use the + * EXTTRIG/EXTCONVCLK input on pin 17 instead. */ + if ((thisboard->have_dio) || (thisboard->min_hwver > 0)) { + cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_INT + | TRIG_EXT; + } else { + cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_INT; + } + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_INT | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* Step 2: make sure trigger sources are unique and mutually compatible + * "source conflict" returned by comedilib to user mode process + * if this fails. */ + + /* these tests are true if more than one _src bit is set */ + if ((cmd->start_src & (cmd->start_src - 1)) != 0) + err++; + if ((cmd->scan_begin_src & (cmd->scan_begin_src - 1)) != 0) + err++; + if ((cmd->convert_src & (cmd->convert_src - 1)) != 0) + err++; + if ((cmd->scan_end_src & (cmd->scan_end_src - 1)) != 0) + err++; + if ((cmd->stop_src & (cmd->stop_src - 1)) != 0) + err++; + + /* If scan_begin_src is not TRIG_FOLLOW, then a monostable will be + * set up to generate a fixed number of timed conversion pulses. */ + if ((cmd->scan_begin_src != TRIG_FOLLOW) + && (cmd->convert_src != TRIG_TIMER)) + err++; + + if (err) + return 2; + + /* Step 3: make sure arguments are trivially compatible. + * "invalid argument" returned by comedilib to user mode process + * if this fails. */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } +#define MAX_SPEED_AI_SE 3200 /* PCI230 SE: 3200 ns => 312.5 kHz */ +#define MAX_SPEED_AI_DIFF 8000 /* PCI230 DIFF: 8000 ns => 125 kHz */ +#define MAX_SPEED_AI_PLUS 4000 /* PCI230+: 4000 ns => 250 kHz */ +#define MIN_SPEED_AI 4294967295u /* 4294967295ns = 4.29s */ + /*- Comedi limit due to unsigned int cmd. Driver limit + * = 2^16 (16bit * counter) * 1000000ns (1kHz onboard + * clock) = 65.536s */ + + if (cmd->convert_src == TRIG_TIMER) { + unsigned int max_speed_ai; + + if (devpriv->hwver == 0) { + /* PCI230 or PCI260. Max speed depends whether + * single-ended or pseudo-differential. */ + if (cmd->chanlist && (cmd->chanlist_len > 0)) { + /* Peek analogue reference of first channel. */ + if (CR_AREF(cmd->chanlist[0]) == AREF_DIFF) + max_speed_ai = MAX_SPEED_AI_DIFF; + else + max_speed_ai = MAX_SPEED_AI_SE; + + } else { + /* No channel list. Assume single-ended. */ + max_speed_ai = MAX_SPEED_AI_SE; + } + } else { + /* PCI230+ or PCI260+. */ + max_speed_ai = MAX_SPEED_AI_PLUS; + } + + if (cmd->convert_arg < max_speed_ai) { + cmd->convert_arg = max_speed_ai; + err++; + } + if (cmd->convert_arg > MIN_SPEED_AI) { + cmd->convert_arg = MIN_SPEED_AI; + err++; + } + } else if (cmd->convert_src == TRIG_EXT) { + /* + * external trigger + * + * convert_arg == (CR_EDGE | 0) + * => trigger on +ve edge. + * convert_arg == (CR_EDGE | CR_INVERT | 0) + * => trigger on -ve edge. + */ + if ((cmd->convert_arg & CR_FLAGS_MASK) != 0) { + /* Trigger number must be 0. */ + if ((cmd->convert_arg & ~CR_FLAGS_MASK) != 0) { + cmd->convert_arg = COMBINE(cmd->convert_arg, 0, + ~CR_FLAGS_MASK); + err++; + } + /* The only flags allowed are CR_INVERT and CR_EDGE. + * CR_EDGE is required. */ + if ((cmd->convert_arg & (CR_FLAGS_MASK & ~CR_INVERT)) + != CR_EDGE) { + /* Set CR_EDGE, preserve CR_INVERT. */ + cmd->convert_arg = + COMBINE(cmd->start_arg, (CR_EDGE | 0), + CR_FLAGS_MASK & ~CR_INVERT); + err++; + } + } else { + /* Backwards compatibility with previous versions. */ + /* convert_arg == 0 => trigger on -ve edge. */ + /* convert_arg == 1 => trigger on +ve edge. */ + if (cmd->convert_arg > 1) { + /* Default to trigger on +ve edge. */ + cmd->convert_arg = 1; + err++; + } + } + } else { + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_NONE) { + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (cmd->scan_begin_src == TRIG_EXT) { + /* external "trigger" to begin each scan + * scan_begin_arg==0 => use PPC0 input -> gate of CT0 -> gate + * of CT2 (sample convert trigger is CT2) */ + if ((cmd->scan_begin_arg & ~CR_FLAGS_MASK) != 0) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + ~CR_FLAGS_MASK); + err++; + } + /* The only flag allowed is CR_EDGE, which is ignored. */ + if ((cmd->scan_begin_arg & CR_FLAGS_MASK & ~CR_EDGE) != 0) { + cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, + CR_FLAGS_MASK & ~CR_EDGE); + err++; + } + } else if (cmd->scan_begin_src == TRIG_TIMER) { + /* N.B. cmd->convert_arg is also TRIG_TIMER */ + if (!pci230_ai_check_scan_period(cmd)) + err++; + + } else { + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* Step 4: fix up any arguments. + * "argument conflict" returned by comedilib to user mode process + * if this fails. */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + pci230_ns_to_single_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* N.B. cmd->convert_arg is also TRIG_TIMER */ + tmp = cmd->scan_begin_arg; + pci230_ns_to_single_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (!pci230_ai_check_scan_period(cmd)) { + /* Was below minimum required. Round up. */ + pci230_ns_to_single_timer(&cmd->scan_begin_arg, + TRIG_ROUND_UP); + pci230_ai_check_scan_period(cmd); + } + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + /* Step 5: check channel list if it exists. */ + + if (cmd->chanlist && cmd->chanlist_len > 0) { + enum { + seq_err = 1 << 0, + rangepair_err = 1 << 1, + polarity_err = 1 << 2, + aref_err = 1 << 3, + diffchan_err = 1 << 4, + buggy_chan0_err = 1 << 5 + }; + unsigned int errors; + unsigned int chan, prev_chan; + unsigned int range, prev_range; + unsigned int polarity, prev_polarity; + unsigned int aref, prev_aref; + unsigned int subseq_len; + unsigned int n; + + subseq_len = 0; + errors = 0; + prev_chan = prev_aref = prev_range = prev_polarity = 0; + for (n = 0; n < cmd->chanlist_len; n++) { + chan = CR_CHAN(cmd->chanlist[n]); + range = CR_RANGE(cmd->chanlist[n]); + aref = CR_AREF(cmd->chanlist[n]); + polarity = pci230_ai_bipolar[range]; + /* Only the first half of the channels are available if + * differential. (These are remapped in software. In + * hardware, only the even channels are available.) */ + if ((aref == AREF_DIFF) + && (chan >= (s->n_chan / 2))) { + errors |= diffchan_err; + } + if (n > 0) { + /* Channel numbers must strictly increase or + * subsequence must repeat exactly. */ + if ((chan <= prev_chan) + && (subseq_len == 0)) { + subseq_len = n; + } + if ((subseq_len > 0) + && (cmd->chanlist[n] != + cmd->chanlist[n % subseq_len])) { + errors |= seq_err; + } + /* Channels must have same AREF. */ + if (aref != prev_aref) + errors |= aref_err; + + /* Channel ranges must have same polarity. */ + if (polarity != prev_polarity) + errors |= polarity_err; + + /* Single-ended channel pairs must have same + * range. */ + if ((aref != AREF_DIFF) + && (((chan ^ prev_chan) & ~1) == 0) + && (range != prev_range)) { + errors |= rangepair_err; + } + } + prev_chan = chan; + prev_range = range; + prev_aref = aref; + prev_polarity = polarity; + } + if (subseq_len == 0) { + /* Subsequence is whole sequence. */ + subseq_len = n; + } + /* If channel list is a repeating subsequence, need a whole + * number of repeats. */ + if ((n % subseq_len) != 0) + errors |= seq_err; + + if ((devpriv->hwver > 0) && (devpriv->hwver < 4)) { + /* + * Buggy PCI230+ or PCI260+ requires channel 0 to be + * (first) in the sequence if the sequence contains + * more than one channel. Hardware versions 1 and 2 + * have the bug. There is no hardware version 3. + * + * Actually, there are two firmwares that report + * themselves as hardware version 1 (the boards + * have different ADC chips with slightly different + * timing requirements, which was supposed to be + * invisible to software). The first one doesn't + * seem to have the bug, but the second one + * does, and we can't tell them apart! + */ + if ((subseq_len > 1) + && (CR_CHAN(cmd->chanlist[0]) != 0)) { + errors |= buggy_chan0_err; + } + } + if (errors != 0) { + err++; + if ((errors & seq_err) != 0) { + DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " + "channel numbers must increase or " + "sequence must repeat exactly\n", + dev->minor); + } + if ((errors & rangepair_err) != 0) { + DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " + "single-ended channel pairs must " + "have the same range\n", dev->minor); + } + if ((errors & polarity_err) != 0) { + DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " + "channel sequence ranges must be all " + "bipolar or all unipolar\n", + dev->minor); + } + if ((errors & aref_err) != 0) { + DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " + "channel sequence analogue references " + "must be all the same (single-ended " + "or differential)\n", dev->minor); + } + if ((errors & diffchan_err) != 0) { + DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " + "differential channel number out of " + "range 0 to %u\n", dev->minor, + (s->n_chan / 2) - 1); + } + if ((errors & buggy_chan0_err) != 0) { + /* Use printk instead of DPRINTK here. */ + printk("comedi: comedi%d: amplc_pci230: " + "ai_cmdtest: Buggy PCI230+/260+ " + "h/w version %u requires first channel " + "of multi-channel sequence to be 0 " + "(corrected in h/w version 4)\n", + dev->minor, devpriv->hwver); + } + } + } + + if (err) + return 5; + + return 0; +} + +static void pci230_ai_update_fifo_trigger_level(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int scanlen = cmd->scan_end_arg; + unsigned int wake; + unsigned short triglev; + unsigned short adccon; + + if ((cmd->flags & TRIG_WAKE_EOS) != 0) { + /* Wake at end of scan. */ + wake = scanlen - devpriv->ai_scan_pos; + } else { + if (devpriv->ai_continuous + || (devpriv->ai_scan_count >= PCI230_ADC_FIFOLEVEL_HALFFULL) + || (scanlen >= PCI230_ADC_FIFOLEVEL_HALFFULL)) { + wake = PCI230_ADC_FIFOLEVEL_HALFFULL; + } else { + wake = (devpriv->ai_scan_count * scanlen) + - devpriv->ai_scan_pos; + } + } + if (wake >= PCI230_ADC_FIFOLEVEL_HALFFULL) { + triglev = PCI230_ADC_INT_FIFO_HALF; + } else { + if ((wake > 1) && (devpriv->hwver > 0)) { + /* PCI230+/260+ programmable FIFO interrupt level. */ + if (devpriv->adcfifothresh != wake) { + devpriv->adcfifothresh = wake; + outw(wake, dev->iobase + PCI230P_ADCFFTH); + } + triglev = PCI230P_ADC_INT_FIFO_THRESH; + } else { + triglev = PCI230_ADC_INT_FIFO_NEMPTY; + } + } + adccon = (devpriv->adccon & ~PCI230_ADC_INT_FIFO_MASK) | triglev; + if (adccon != devpriv->adccon) { + devpriv->adccon = adccon; + outw(adccon, dev->iobase + PCI230_ADCCON); + } +} + +static int pci230_ai_inttrig_convert(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + unsigned long irqflags; + + if (trig_num != 0) + return -EINVAL; + + spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); + if (test_bit(AI_CMD_STARTED, &devpriv->state)) { + unsigned int delayus; + + /* Trigger conversion by toggling Z2-CT2 output. Finish + * with output high. */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, + I8254_MODE0); + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, + I8254_MODE1); + /* Delay. Should driver be responsible for this? An + * alternative would be to wait until conversion is complete, + * but we can't tell when it's complete because the ADC busy + * bit has a different meaning when FIFO enabled (and when + * FIFO not enabled, it only works for software triggers). */ + if (((devpriv->adccon & PCI230_ADC_IM_MASK) + == PCI230_ADC_IM_DIF) + && (devpriv->hwver == 0)) { + /* PCI230/260 in differential mode */ + delayus = 8; + } else { + /* single-ended or PCI230+/260+ */ + delayus = 4; + } + spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); + udelay(delayus); + } else { + spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); + } + + return 1; +} + +static int pci230_ai_inttrig_scan_begin(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + unsigned long irqflags; + unsigned char zgat; + + if (trig_num != 0) + return -EINVAL; + + spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); + if (test_bit(AI_CMD_STARTED, &devpriv->state)) { + /* Trigger scan by waggling CT0 gate source. */ + zgat = GAT_CONFIG(0, GAT_GND); + outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); + zgat = GAT_CONFIG(0, GAT_VCC); + outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); + } + spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); + + return 1; +} + +static void pci230_ai_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long irqflags; + unsigned short conv; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + + set_bit(AI_CMD_STARTED, &devpriv->state); + if (!devpriv->ai_continuous && (devpriv->ai_scan_count == 0)) { + /* An empty acquisition! */ + async->events |= COMEDI_CB_EOA; + pci230_ai_stop(dev, s); + comedi_event(dev, s); + } else { + /* Enable ADC FIFO trigger level interrupt. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + devpriv->int_en |= PCI230_INT_ADC; + devpriv->ier |= PCI230_INT_ADC; + outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + + /* Update conversion trigger source which is currently set + * to CT2 output, which is currently stuck high. */ + switch (cmd->convert_src) { + default: + conv = PCI230_ADC_TRIG_NONE; + break; + case TRIG_TIMER: + /* Using CT2 output. */ + conv = PCI230_ADC_TRIG_Z2CT2; + break; + case TRIG_EXT: + if ((cmd->convert_arg & CR_EDGE) != 0) { + if ((cmd->convert_arg & CR_INVERT) == 0) { + /* Trigger on +ve edge. */ + conv = PCI230_ADC_TRIG_EXTP; + } else { + /* Trigger on -ve edge. */ + conv = PCI230_ADC_TRIG_EXTN; + } + } else { + /* Backwards compatibility. */ + if (cmd->convert_arg != 0) { + /* Trigger on +ve edge. */ + conv = PCI230_ADC_TRIG_EXTP; + } else { + /* Trigger on -ve edge. */ + conv = PCI230_ADC_TRIG_EXTN; + } + } + break; + case TRIG_INT: + /* Use CT2 output for software trigger due to problems + * in differential mode on PCI230/260. */ + conv = PCI230_ADC_TRIG_Z2CT2; + break; + } + devpriv->adccon = (devpriv->adccon & ~PCI230_ADC_TRIG_MASK) + | conv; + outw(devpriv->adccon, dev->iobase + PCI230_ADCCON); + if (cmd->convert_src == TRIG_INT) + async->inttrig = pci230_ai_inttrig_convert; + + /* Update FIFO interrupt trigger level, which is currently + * set to "full". */ + pci230_ai_update_fifo_trigger_level(dev, s); + if (cmd->convert_src == TRIG_TIMER) { + /* Update timer gates. */ + unsigned char zgat; + + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* Conversion timer CT2 needs to be gated by + * inverted output of monostable CT2. */ + zgat = GAT_CONFIG(2, GAT_NOUTNM2); + } else { + /* Conversion timer CT2 needs to be gated on + * continuously. */ + zgat = GAT_CONFIG(2, GAT_VCC); + } + outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* Set monostable CT0 trigger source. */ + switch (cmd->scan_begin_src) { + default: + zgat = GAT_CONFIG(0, GAT_VCC); + break; + case TRIG_EXT: + /* + * For CT0 on PCI230, the external + * trigger (gate) signal comes from + * PPC0, which is channel 16 of the DIO + * subdevice. The application needs to + * configure this as an input in order + * to use it as an external scan + * trigger. + */ + zgat = GAT_CONFIG(0, GAT_EXT); + break; + case TRIG_TIMER: + /* + * Monostable CT0 triggered by rising + * edge on inverted output of CT1 + * (falling edge on CT1). + */ + zgat = GAT_CONFIG(0, GAT_NOUTNM2); + break; + case TRIG_INT: + /* + * Monostable CT0 is triggered by + * inttrig function waggling the CT0 + * gate source. + */ + zgat = GAT_CONFIG(0, GAT_VCC); + break; + } + outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + /* Scan period timer CT1 needs to be + * gated on to start counting. */ + zgat = GAT_CONFIG(1, GAT_VCC); + outb(zgat, devpriv->iobase1 + + PCI230_ZGAT_SCE); + break; + case TRIG_INT: + async->inttrig = + pci230_ai_inttrig_scan_begin; + break; + } + } + } else if (cmd->convert_src != TRIG_INT) { + /* No longer need Z2-CT2. */ + put_one_resource(dev, RES_Z2CT2, OWNER_AICMD); + } + } +} + +static int pci230_ai_inttrig_start(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + if (trig_num != 0) + return -EINVAL; + + s->async->inttrig = NULLFUNC; + pci230_ai_start(dev, s); + + return 1; +} + +static int pci230_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned int i, chan, range, diff; + unsigned int res_mask; + unsigned short adccon, adcen; + unsigned char zgat; + + /* Get the command. */ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + + /* + * Determine which shared resources are needed. + */ + res_mask = 0; + /* Need Z2-CT2 to supply a conversion trigger source at a high + * logic level, even if not doing timed conversions. */ + res_mask |= (1U << RES_Z2CT2); + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* Using Z2-CT0 monostable to gate Z2-CT2 conversion timer */ + res_mask |= (1U << RES_Z2CT0); + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Using Z2-CT1 for scan frequency */ + res_mask |= (1U << RES_Z2CT1); + } + } + /* Claim resources. */ + if (!get_resources(dev, res_mask, OWNER_AICMD)) + return -EBUSY; + + + /* Get number of scans required. */ + if (cmd->stop_src == TRIG_COUNT) { + devpriv->ai_scan_count = cmd->stop_arg; + devpriv->ai_continuous = 0; + } else { + /* TRIG_NONE, user calls cancel. */ + devpriv->ai_scan_count = 0; + devpriv->ai_continuous = 1; + } + devpriv->ai_scan_pos = 0; /* Position within scan. */ + + /* Steps; + * - Set channel scan list. + * - Set channel gains. + * - Enable and reset FIFO, specify uni/bip, se/diff, and set + * start conversion source to point to something at a high logic + * level (we use the output of counter/timer 2 for this purpose. + * - PAUSE to allow things to settle down. + * - Reset the FIFO again because it needs resetting twice and there + * may have been a false conversion trigger on some versions of + * PCI230/260 due to the start conversion source being set to a + * high logic level. + * - Enable ADC FIFO level interrupt. + * - Set actual conversion trigger source and FIFO interrupt trigger + * level. + * - If convert_src is TRIG_TIMER, set up the timers. + */ + + adccon = PCI230_ADC_FIFO_EN; + adcen = 0; + + if (CR_AREF(cmd->chanlist[0]) == AREF_DIFF) { + /* Differential - all channels must be differential. */ + diff = 1; + adccon |= PCI230_ADC_IM_DIF; + } else { + /* Single ended - all channels must be single-ended. */ + diff = 0; + adccon |= PCI230_ADC_IM_SE; + } + + range = CR_RANGE(cmd->chanlist[0]); + devpriv->ai_bipolar = pci230_ai_bipolar[range]; + if (devpriv->ai_bipolar) + adccon |= PCI230_ADC_IR_BIP; + else + adccon |= PCI230_ADC_IR_UNI; + + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int gainshift; + + chan = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + if (diff) { + gainshift = 2 * chan; + if (devpriv->hwver == 0) { + /* Original PCI230/260 expects both inputs of + * the differential channel to be enabled. */ + adcen |= 3 << gainshift; + } else { + /* PCI230+/260+ expects only one input of the + * differential channel to be enabled. */ + adcen |= 1 << gainshift; + } + } else { + gainshift = (chan & ~1); + adcen |= 1 << chan; + } + devpriv->adcg = (devpriv->adcg & ~(3 << gainshift)) + | (pci230_ai_gain[range] << gainshift); + } + + /* Set channel scan list. */ + outw(adcen, dev->iobase + PCI230_ADCEN); + + /* Set channel gains. */ + outw(devpriv->adcg, dev->iobase + PCI230_ADCG); + + /* Set counter/timer 2 output high for use as the initial start + * conversion source. */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, I8254_MODE1); + + /* Temporarily use CT2 output as conversion trigger source and + * temporarily set FIFO interrupt trigger level to 'full'. */ + adccon |= PCI230_ADC_INT_FIFO_FULL | PCI230_ADC_TRIG_Z2CT2; + + /* Enable and reset FIFO, specify FIFO trigger level full, specify + * uni/bip, se/diff, and temporarily set the start conversion source + * to CT2 output. Note that CT2 output is currently high, and this + * will produce a false conversion trigger on some versions of the + * PCI230/260, but that will be dealt with later. */ + devpriv->adccon = adccon; + outw(adccon | PCI230_ADC_FIFO_RESET, dev->iobase + PCI230_ADCCON); + + /* Delay */ + /* Failure to include this will result in the first few channels'-worth + * of data being corrupt, normally manifesting itself by large negative + * voltages. It seems the board needs time to settle between the first + * FIFO reset (above) and the second FIFO reset (below). Setting the + * channel gains and scan list _before_ the first FIFO reset also + * helps, though only slightly. */ + udelay(25); + + /* Reset FIFO again. */ + outw(adccon | PCI230_ADC_FIFO_RESET, dev->iobase + PCI230_ADCCON); + + if (cmd->convert_src == TRIG_TIMER) { + /* Set up CT2 as conversion timer, but gate it off for now. + * Note, counter/timer output 2 can be monitored on the + * connector: PCI230 pin 21, PCI260 pin 18. */ + zgat = GAT_CONFIG(2, GAT_GND); + outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); + /* Set counter/timer 2 to the specified conversion period. */ + pci230_ct_setup_ns_mode(dev, 2, I8254_MODE3, cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* + * Set up monostable on CT0 output for scan timing. A + * rising edge on the trigger (gate) input of CT0 will + * trigger the monostable, causing its output to go low + * for the configured period. The period depends on + * the conversion period and the number of conversions + * in the scan. + * + * Set the trigger high before setting up the + * monostable to stop it triggering. The trigger + * source will be changed later. + */ + zgat = GAT_CONFIG(0, GAT_VCC); + outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); + pci230_ct_setup_ns_mode(dev, 0, I8254_MODE1, + ((uint64_t) cmd->convert_arg + * cmd->scan_end_arg), + TRIG_ROUND_UP); + if (cmd->scan_begin_src == TRIG_TIMER) { + /* + * Monostable on CT0 will be triggered by + * output of CT1 at configured scan frequency. + * + * Set up CT1 but gate it off for now. + */ + zgat = GAT_CONFIG(1, GAT_GND); + outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); + pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3, + cmd->scan_begin_arg, + cmd-> + flags & + TRIG_ROUND_MASK); + } + } + } + + if (cmd->start_src == TRIG_INT) { + s->async->inttrig = pci230_ai_inttrig_start; + } else { + /* TRIG_NOW */ + pci230_ai_start(dev, s); + } + + return 0; +} + +static unsigned int divide_ns(uint64_t ns, unsigned int timebase, + unsigned int round_mode) +{ + uint64_t div; + unsigned int rem; + + div = ns; + rem = do_div(div, timebase); + round_mode &= TRIG_ROUND_MASK; + switch (round_mode) { + default: + case TRIG_ROUND_NEAREST: + div += (rem + (timebase / 2)) / timebase; + break; + case TRIG_ROUND_DOWN: + break; + case TRIG_ROUND_UP: + div += (rem + timebase - 1) / timebase; + break; + } + return div > UINT_MAX ? UINT_MAX : (unsigned int)div; +} + +/* Given desired period in ns, returns the required internal clock source + * and gets the initial count. */ +static unsigned int pci230_choose_clk_count(uint64_t ns, unsigned int *count, + unsigned int round_mode) +{ + unsigned int clk_src, cnt; + + for (clk_src = CLK_10MHZ;; clk_src++) { + cnt = divide_ns(ns, pci230_timebase[clk_src], round_mode); + if ((cnt <= 65536) || (clk_src == CLK_1KHZ)) + break; + + } + *count = cnt; + return clk_src; +} + +static void pci230_ns_to_single_timer(unsigned int *ns, unsigned int round) +{ + unsigned int count; + unsigned int clk_src; + + clk_src = pci230_choose_clk_count(*ns, &count, round); + *ns = count * pci230_timebase[clk_src]; + return; +} + +static void pci230_ct_setup_ns_mode(struct comedi_device *dev, unsigned int ct, + unsigned int mode, uint64_t ns, + unsigned int round) +{ + unsigned int clk_src; + unsigned int count; + + /* Set mode. */ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct, mode); + /* Determine clock source and count. */ + clk_src = pci230_choose_clk_count(ns, &count, round); + /* Program clock source. */ + outb(CLK_CONFIG(ct, clk_src), devpriv->iobase1 + PCI230_ZCLK_SCE); + /* Set initial count. */ + if (count >= 65536) + count = 0; + + i8254_write(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct, count); +} + +static void pci230_cancel_ct(struct comedi_device *dev, unsigned int ct) +{ + i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct, + I8254_MODE1); + /* Counter ct, 8254 mode 1, initial count not written. */ +} + +/* Interrupt handler */ +static irqreturn_t pci230_interrupt(int irq, void *d) +{ + unsigned char status_int, valid_status_int; + struct comedi_device *dev = (struct comedi_device *)d; + struct comedi_subdevice *s; + unsigned long irqflags; + + /* Read interrupt status/enable register. */ + status_int = inb(devpriv->iobase1 + PCI230_INT_STAT); + + if (status_int == PCI230_INT_DISABLE) + return IRQ_NONE; + + + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + valid_status_int = devpriv->int_en & status_int; + /* Disable triggered interrupts. + * (Only those interrupts that need re-enabling, are, later in the + * handler). */ + devpriv->ier = devpriv->int_en & ~status_int; + outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); + devpriv->intr_running = 1; + devpriv->intr_cpuid = THISCPU; + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + + /* + * Check the source of interrupt and handle it. + * The PCI230 can cope with concurrent ADC, DAC, PPI C0 and C3 + * interrupts. However, at present (Comedi-0.7.60) does not allow + * concurrent execution of commands, instructions or a mixture of the + * two. + */ + + if ((valid_status_int & PCI230_INT_ZCLK_CT1) != 0) { + s = dev->write_subdev; + pci230_handle_ao_nofifo(dev, s); + comedi_event(dev, s); + } + + if ((valid_status_int & PCI230P2_INT_DAC) != 0) { + s = dev->write_subdev; + pci230_handle_ao_fifo(dev, s); + comedi_event(dev, s); + } + + if ((valid_status_int & PCI230_INT_ADC) != 0) { + s = dev->read_subdev; + pci230_handle_ai(dev, s); + comedi_event(dev, s); + } + + /* Reenable interrupts. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + if (devpriv->ier != devpriv->int_en) { + devpriv->ier = devpriv->int_en; + outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); + } + devpriv->intr_running = 0; + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + + return IRQ_HANDLED; +} + +static void pci230_handle_ao_nofifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + short data; + int i, ret; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + + if (!devpriv->ao_continuous && (devpriv->ao_scan_count == 0)) + return; + + + for (i = 0; i < cmd->chanlist_len; i++) { + /* Read sample from Comedi's circular buffer. */ + ret = comedi_buf_get(s->async, &data); + if (ret == 0) { + s->async->events |= COMEDI_CB_OVERFLOW; + pci230_ao_stop(dev, s); + comedi_error(dev, "AO buffer underrun"); + return; + } + /* Write value to DAC. */ + pci230_ao_write_nofifo(dev, data, CR_CHAN(cmd->chanlist[i])); + } + + async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + if (!devpriv->ao_continuous) { + devpriv->ao_scan_count--; + if (devpriv->ao_scan_count == 0) { + /* End of acquisition. */ + async->events |= COMEDI_CB_EOA; + pci230_ao_stop(dev, s); + } + } +} + +/* Loads DAC FIFO (if using it) from buffer. */ +/* Returns 0 if AO finished due to completion or error, 1 if still going. */ +static int pci230_handle_ao_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int num_scans; + unsigned int room; + unsigned short dacstat; + unsigned int i, n; + unsigned int bytes_per_scan; + unsigned int events = 0; + int running; + + /* Get DAC FIFO status. */ + dacstat = inw(dev->iobase + PCI230_DACCON); + + /* Determine number of scans available in buffer. */ + bytes_per_scan = cmd->chanlist_len * sizeof(short); + num_scans = comedi_buf_read_n_available(async) / bytes_per_scan; + if (!devpriv->ao_continuous) { + /* Fixed number of scans. */ + if (num_scans > devpriv->ao_scan_count) + num_scans = devpriv->ao_scan_count; + + if (devpriv->ao_scan_count == 0) { + /* End of acquisition. */ + events |= COMEDI_CB_EOA; + } + } + if (events == 0) { + /* Check for FIFO underrun. */ + if ((dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) != 0) { + comedi_error(dev, "AO FIFO underrun"); + events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + } + /* Check for buffer underrun if FIFO less than half full + * (otherwise there will be loads of "DAC FIFO not half full" + * interrupts). */ + if ((num_scans == 0) + && ((dacstat & PCI230P2_DAC_FIFO_HALF) == 0)) { + comedi_error(dev, "AO buffer underrun"); + events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + } + } + if (events == 0) { + /* Determine how much room is in the FIFO (in samples). */ + if ((dacstat & PCI230P2_DAC_FIFO_FULL) != 0) + room = PCI230P2_DAC_FIFOROOM_FULL; + else if ((dacstat & PCI230P2_DAC_FIFO_HALF) != 0) + room = PCI230P2_DAC_FIFOROOM_HALFTOFULL; + else if ((dacstat & PCI230P2_DAC_FIFO_EMPTY) != 0) + room = PCI230P2_DAC_FIFOROOM_EMPTY; + else + room = PCI230P2_DAC_FIFOROOM_ONETOHALF; + + /* Convert room to number of scans that can be added. */ + room /= cmd->chanlist_len; + /* Determine number of scans to process. */ + if (num_scans > room) + num_scans = room; + + /* Process scans. */ + for (n = 0; n < num_scans; n++) { + for (i = 0; i < cmd->chanlist_len; i++) { + short datum; + + comedi_buf_get(async, &datum); + pci230_ao_write_fifo(dev, datum, + CR_CHAN(cmd->chanlist[i])); + } + } + events |= COMEDI_CB_EOS | COMEDI_CB_BLOCK; + if (!devpriv->ao_continuous) { + devpriv->ao_scan_count -= num_scans; + if (devpriv->ao_scan_count == 0) { + /* All data for the command has been written + * to FIFO. Set FIFO interrupt trigger level + * to 'empty'. */ + devpriv->daccon = (devpriv->daccon + & + ~PCI230P2_DAC_INT_FIFO_MASK) + | PCI230P2_DAC_INT_FIFO_EMPTY; + outw(devpriv->daccon, + dev->iobase + PCI230_DACCON); + } + } + /* Check if FIFO underrun occurred while writing to FIFO. */ + dacstat = inw(dev->iobase + PCI230_DACCON); + if ((dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) != 0) { + comedi_error(dev, "AO FIFO underrun"); + events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + } + } + if ((events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) + != 0) { + /* Stopping AO due to completion or error. */ + pci230_ao_stop(dev, s); + running = 0; + } else { + running = 1; + } + async->events |= events; + return running; +} + +static void pci230_handle_ai(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int events = 0; + unsigned int status_fifo; + unsigned int i; + unsigned int todo; + unsigned int fifoamount; + struct comedi_async *async = s->async; + unsigned int scanlen = async->cmd.scan_end_arg; + + /* Determine number of samples to read. */ + if (devpriv->ai_continuous) { + todo = PCI230_ADC_FIFOLEVEL_HALFFULL; + } else if (devpriv->ai_scan_count == 0) { + todo = 0; + } else if ((devpriv->ai_scan_count > PCI230_ADC_FIFOLEVEL_HALFFULL) + || (scanlen > PCI230_ADC_FIFOLEVEL_HALFFULL)) { + todo = PCI230_ADC_FIFOLEVEL_HALFFULL; + } else { + todo = (devpriv->ai_scan_count * scanlen) + - devpriv->ai_scan_pos; + if (todo > PCI230_ADC_FIFOLEVEL_HALFFULL) + todo = PCI230_ADC_FIFOLEVEL_HALFFULL; + + } + + if (todo == 0) + return; + + + fifoamount = 0; + for (i = 0; i < todo; i++) { + if (fifoamount == 0) { + /* Read FIFO state. */ + status_fifo = inw(dev->iobase + PCI230_ADCCON); + + if ((status_fifo & PCI230_ADC_FIFO_FULL_LATCHED) != 0) { + /* Report error otherwise FIFO overruns will go + * unnoticed by the caller. */ + comedi_error(dev, "AI FIFO overrun"); + events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; + break; + } else if ((status_fifo & PCI230_ADC_FIFO_EMPTY) != 0) { + /* FIFO empty. */ + break; + } else if ((status_fifo & PCI230_ADC_FIFO_HALF) != 0) { + /* FIFO half full. */ + fifoamount = PCI230_ADC_FIFOLEVEL_HALFFULL; + } else { + /* FIFO not empty. */ + if (devpriv->hwver > 0) { + /* Read PCI230+/260+ ADC FIFO level. */ + fifoamount = inw(dev->iobase + + PCI230P_ADCFFLEV); + if (fifoamount == 0) { + /* Shouldn't happen. */ + break; + } + } else { + fifoamount = 1; + } + } + } + + /* Read sample and store in Comedi's circular buffer. */ + if (comedi_buf_put(async, pci230_ai_read(dev)) == 0) { + events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW; + comedi_error(dev, "AI buffer overflow"); + break; + } + fifoamount--; + devpriv->ai_scan_pos++; + if (devpriv->ai_scan_pos == scanlen) { + /* End of scan. */ + devpriv->ai_scan_pos = 0; + devpriv->ai_scan_count--; + async->events |= COMEDI_CB_EOS; + } + } + + if (!devpriv->ai_continuous && (devpriv->ai_scan_count == 0)) { + /* End of acquisition. */ + events |= COMEDI_CB_EOA; + } else { + /* More samples required, tell Comedi to block. */ + events |= COMEDI_CB_BLOCK; + } + async->events |= events; + + if ((async->events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW)) != 0) { + /* disable hardware conversions */ + pci230_ai_stop(dev, s); + } else { + /* update FIFO interrupt trigger level */ + pci230_ai_update_fifo_trigger_level(dev, s); + } +} + +static void pci230_ao_stop(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long irqflags; + unsigned char intsrc; + int started; + struct comedi_cmd *cmd; + + spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags); + started = test_and_clear_bit(AO_CMD_STARTED, &devpriv->state); + spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags); + if (!started) + return; + + + cmd = &s->async->cmd; + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Stop scan rate generator. */ + pci230_cancel_ct(dev, 1); + } + + /* Determine interrupt source. */ + if (devpriv->hwver < 2) { + /* Not using DAC FIFO. Using CT1 interrupt. */ + intsrc = PCI230_INT_ZCLK_CT1; + } else { + /* Using DAC FIFO interrupt. */ + intsrc = PCI230P2_INT_DAC; + } + /* Disable interrupt and wait for interrupt routine to finish running + * unless we are called from the interrupt routine. */ + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + devpriv->int_en &= ~intsrc; + while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) { + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + } + if (devpriv->ier != devpriv->int_en) { + devpriv->ier = devpriv->int_en; + outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); + } + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + + if (devpriv->hwver >= 2) { + /* Using DAC FIFO. Reset FIFO, clear underrun error, + * disable FIFO. */ + devpriv->daccon &= PCI230_DAC_OR_MASK; + outw(devpriv->daccon | PCI230P2_DAC_FIFO_RESET + | PCI230P2_DAC_FIFO_UNDERRUN_CLEAR, + dev->iobase + PCI230_DACCON); + } + + /* Release resources. */ + put_all_resources(dev, OWNER_AOCMD); +} + +static int pci230_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + pci230_ao_stop(dev, s); + return 0; +} + +static void pci230_ai_stop(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long irqflags; + struct comedi_cmd *cmd; + int started; + + spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); + started = test_and_clear_bit(AI_CMD_STARTED, &devpriv->state); + spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); + if (!started) + return; + + + cmd = &s->async->cmd; + if (cmd->convert_src == TRIG_TIMER) { + /* Stop conversion rate generator. */ + pci230_cancel_ct(dev, 2); + } + if (cmd->scan_begin_src != TRIG_FOLLOW) { + /* Stop scan period monostable. */ + pci230_cancel_ct(dev, 0); + } + + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + /* Disable ADC interrupt and wait for interrupt routine to finish + * running unless we are called from the interrupt routine. */ + devpriv->int_en &= ~PCI230_INT_ADC; + while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) { + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); + } + if (devpriv->ier != devpriv->int_en) { + devpriv->ier = devpriv->int_en; + outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); + } + spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); + + /* Reset FIFO, disable FIFO and set start conversion source to none. + * Keep se/diff and bip/uni settings */ + devpriv->adccon = (devpriv->adccon & (PCI230_ADC_IR_MASK + | PCI230_ADC_IM_MASK)) | + PCI230_ADC_TRIG_NONE; + outw(devpriv->adccon | PCI230_ADC_FIFO_RESET, + dev->iobase + PCI230_ADCCON); + + /* Release resources. */ + put_all_resources(dev, OWNER_AICMD); +} + +static int pci230_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + pci230_ai_stop(dev, s); + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/c6xdigio.c b/drivers/staging/comedi/drivers/c6xdigio.c new file mode 100644 index 00000000..11cdaf2a --- /dev/null +++ b/drivers/staging/comedi/drivers/c6xdigio.c @@ -0,0 +1,536 @@ +/* + comedi/drivers/c6xdigio.c + + Hardware driver for Mechatronic Systems Inc. C6x_DIGIO DSP daughter card. + (http://robot0.ge.uiuc.edu/~spong/mecha/) + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999 Dan Block + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: c6xdigio +Description: Mechatronic Systems Inc. C6x_DIGIO DSP daughter card +Author: Dan Block +Status: unknown +Devices: [Mechatronic Systems Inc.] C6x_DIGIO DSP daughter card (c6xdigio) +Updated: Sun Nov 20 20:18:34 EST 2005 + +This driver will not work with a 2.4 kernel. +http://robot0.ge.uiuc.edu/~spong/mecha/ + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../comedidev.h" + +static u8 ReadByteFromHwPort(unsigned long addr) +{ + u8 result = inb(addr); + return result; +} + +static void WriteByteToHwPort(unsigned long addr, u8 val) +{ + outb_p(val, addr); +} + +#define C6XDIGIO_SIZE 3 + +/* + * port offsets + */ +#define C6XDIGIO_PARALLEL_DATA 0 +#define C6XDIGIO_PARALLEL_STATUS 1 +#define C6XDIGIO_PARALLEL_CONTROL 2 +struct pwmbitstype { + unsigned sb0:2; + unsigned sb1:2; + unsigned sb2:2; + unsigned sb3:2; + unsigned sb4:2; +}; +union pwmcmdtype { + unsigned cmd; /* assuming here that int is 32bit */ + struct pwmbitstype bits; +}; +struct encbitstype { + unsigned sb0:3; + unsigned sb1:3; + unsigned sb2:3; + unsigned sb3:3; + unsigned sb4:3; + unsigned sb5:3; + unsigned sb6:3; + unsigned sb7:3; +}; +union encvaluetype { + unsigned value; + struct encbitstype bits; +}; + +#define C6XDIGIO_TIME_OUT 20 + +static int c6xdigio_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int c6xdigio_detach(struct comedi_device *dev); +struct comedi_driver driver_c6xdigio = { + .driver_name = "c6xdigio", + .module = THIS_MODULE, + .attach = c6xdigio_attach, + .detach = c6xdigio_detach, +}; + +static void C6X_pwmInit(unsigned long baseAddr) +{ + int timeout = 0; + +/* printk("Inside C6X_pwmInit\n"); */ + + WriteByteToHwPort(baseAddr, 0x70); + while (((ReadByteFromHwPort(baseAddr + 1) & 0x80) == 0) + && (timeout < C6XDIGIO_TIME_OUT)) { + timeout++; + } + + WriteByteToHwPort(baseAddr, 0x74); + timeout = 0; + while (((ReadByteFromHwPort(baseAddr + 1) & 0x80) == 0x80) + && (timeout < C6XDIGIO_TIME_OUT)) { + timeout++; + } + + WriteByteToHwPort(baseAddr, 0x70); + timeout = 0; + while (((ReadByteFromHwPort(baseAddr + 1) & 0x80) == 0x0) + && (timeout < C6XDIGIO_TIME_OUT)) { + timeout++; + } + + WriteByteToHwPort(baseAddr, 0x0); + timeout = 0; + while (((ReadByteFromHwPort(baseAddr + 1) & 0x80) == 0x80) + && (timeout < C6XDIGIO_TIME_OUT)) { + timeout++; + } + +} + +static void C6X_pwmOutput(unsigned long baseAddr, unsigned channel, int value) +{ + unsigned ppcmd; + union pwmcmdtype pwm; + int timeout = 0; + unsigned tmp; + + /* printk("Inside C6X_pwmOutput\n"); */ + + pwm.cmd = value; + if (pwm.cmd > 498) + pwm.cmd = 498; + if (pwm.cmd < 2) + pwm.cmd = 2; + + if (channel == 0) { + ppcmd = 0x28; + } else { /* if channel == 1 */ + ppcmd = 0x30; + } /* endif */ + + WriteByteToHwPort(baseAddr, ppcmd + pwm.bits.sb0); + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + + WriteByteToHwPort(baseAddr, ppcmd + pwm.bits.sb1 + 0x4); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x80) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + + WriteByteToHwPort(baseAddr, ppcmd + pwm.bits.sb2); + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + + WriteByteToHwPort(baseAddr, ppcmd + pwm.bits.sb3 + 0x4); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x80) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + + WriteByteToHwPort(baseAddr, ppcmd + pwm.bits.sb4); + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + + WriteByteToHwPort(baseAddr, 0x0); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x80) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + +} + +static int C6X_encInput(unsigned long baseAddr, unsigned channel) +{ + unsigned ppcmd; + union encvaluetype enc; + int timeout = 0; + int tmp; + + /* printk("Inside C6X_encInput\n"); */ + + enc.value = 0; + if (channel == 0) + ppcmd = 0x48; + else + ppcmd = 0x50; + + WriteByteToHwPort(baseAddr, ppcmd); + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + + enc.bits.sb0 = ((ReadByteFromHwPort(baseAddr + 1) >> 3) & 0x7); + WriteByteToHwPort(baseAddr, ppcmd + 0x4); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x80) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + enc.bits.sb1 = ((ReadByteFromHwPort(baseAddr + 1) >> 3) & 0x7); + WriteByteToHwPort(baseAddr, ppcmd); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + enc.bits.sb2 = ((ReadByteFromHwPort(baseAddr + 1) >> 3) & 0x7); + WriteByteToHwPort(baseAddr, ppcmd + 0x4); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x80) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + enc.bits.sb3 = ((ReadByteFromHwPort(baseAddr + 1) >> 3) & 0x7); + WriteByteToHwPort(baseAddr, ppcmd); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + enc.bits.sb4 = ((ReadByteFromHwPort(baseAddr + 1) >> 3) & 0x7); + WriteByteToHwPort(baseAddr, ppcmd + 0x4); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x80) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + enc.bits.sb5 = ((ReadByteFromHwPort(baseAddr + 1) >> 3) & 0x7); + WriteByteToHwPort(baseAddr, ppcmd); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x0) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + enc.bits.sb6 = ((ReadByteFromHwPort(baseAddr + 1) >> 3) & 0x7); + WriteByteToHwPort(baseAddr, ppcmd + 0x4); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x80) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + enc.bits.sb7 = ((ReadByteFromHwPort(baseAddr + 1) >> 3) & 0x7); + WriteByteToHwPort(baseAddr, ppcmd); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x0) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + + WriteByteToHwPort(baseAddr, 0x0); + timeout = 0; + tmp = ReadByteFromHwPort(baseAddr + 1); + while (((tmp & 0x80) == 0x80) && (timeout < C6XDIGIO_TIME_OUT)) { + tmp = ReadByteFromHwPort(baseAddr + 1); + timeout++; + } + + return enc.value ^ 0x800000; +} + +static void C6X_encResetAll(unsigned long baseAddr) +{ + unsigned timeout = 0; + +/* printk("Inside C6X_encResetAll\n"); */ + + WriteByteToHwPort(baseAddr, 0x68); + while (((ReadByteFromHwPort(baseAddr + 1) & 0x80) == 0) + && (timeout < C6XDIGIO_TIME_OUT)) { + timeout++; + } + WriteByteToHwPort(baseAddr, 0x6C); + timeout = 0; + while (((ReadByteFromHwPort(baseAddr + 1) & 0x80) == 0x80) + && (timeout < C6XDIGIO_TIME_OUT)) { + timeout++; + } + WriteByteToHwPort(baseAddr, 0x68); + timeout = 0; + while (((ReadByteFromHwPort(baseAddr + 1) & 0x80) == 0x0) + && (timeout < C6XDIGIO_TIME_OUT)) { + timeout++; + } + WriteByteToHwPort(baseAddr, 0x0); + timeout = 0; + while (((ReadByteFromHwPort(baseAddr + 1) & 0x80) == 0x80) + && (timeout < C6XDIGIO_TIME_OUT)) { + timeout++; + } +} + +static int c6xdigio_pwmo_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + printk(KERN_DEBUG "c6xdigio_pwmo_insn_read %x\n", insn->n); + return insn->n; +} + +static int c6xdigio_pwmo_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + /* printk("c6xdigio_pwmo_insn_write %x\n", insn->n); */ + for (i = 0; i < insn->n; i++) { + C6X_pwmOutput(dev->iobase, chan, data[i]); + /* devpriv->ao_readback[chan] = data[i]; */ + } + return i; +} + +/* static int c6xdigio_ei_init_insn_read(struct comedi_device *dev, */ +/* struct comedi_subdevice *s, */ +/* struct comedi_insn *insn, */ +/* unsigned int *data) */ +/* { */ +/* printk("c6xdigio_ei_init_insn_read %x\n", insn->n); */ +/* return insn->n; */ +/* } */ + +/* static int c6xdigio_ei_init_insn_write(struct comedi_device *dev, */ +/* struct comedi_subdevice *s, */ +/* struct comedi_insn *insn, */ +/* unsigned int *data) */ +/* { */ +/* int i; */ +/* int chan = CR_CHAN(insn->chanspec); */ + /* *//* C6X_encResetAll( dev->iobase ); */ + /* *//* return insn->n; */ +/* } */ + +static int c6xdigio_ei_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + /* printk("c6xdigio_ei__insn_read %x\n", insn->n); */ + int n; + int chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) + data[n] = (C6X_encInput(dev->iobase, chan) & 0xffffff); + + return n; +} + +static void board_init(struct comedi_device *dev) +{ + + /* printk("Inside board_init\n"); */ + + C6X_pwmInit(dev->iobase); + C6X_encResetAll(dev->iobase); + +} + +/* static void board_halt(struct comedi_device *dev) { */ +/* C6X_pwmInit(dev->iobase); */ +/* } */ + +/* + options[0] - I/O port + options[1] - irq + options[2] - number of encoder chips installed + */ + +static const struct pnp_device_id c6xdigio_pnp_tbl[] = { + /* Standard LPT Printer Port */ + {.id = "PNP0400", .driver_data = 0}, + /* ECP Printer Port */ + {.id = "PNP0401", .driver_data = 0}, + {} +}; + +static struct pnp_driver c6xdigio_pnp_driver = { + .name = "c6xdigio", + .id_table = c6xdigio_pnp_tbl, +}; + +static int c6xdigio_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int result = 0; + unsigned long iobase; + unsigned int irq; + struct comedi_subdevice *s; + + iobase = it->options[0]; + printk(KERN_DEBUG "comedi%d: c6xdigio: 0x%04lx\n", dev->minor, iobase); + if (!request_region(iobase, C6XDIGIO_SIZE, "c6xdigio")) { + printk(KERN_ERR "comedi%d: I/O port conflict\n", dev->minor); + return -EIO; + } + dev->iobase = iobase; + dev->board_name = "c6xdigio"; + + result = alloc_subdevices(dev, 2); /* 3 with encoder_init write */ + if (result < 0) + return result; + + /* Make sure that PnP ports get activated */ + pnp_register_driver(&c6xdigio_pnp_driver); + + irq = it->options[1]; + if (irq > 0) + printk(KERN_DEBUG "comedi%d: irq = %u ignored\n", + dev->minor, irq); + else if (irq == 0) + printk(KERN_DEBUG "comedi%d: no irq\n", dev->minor); + + s = dev->subdevices + 0; + /* pwm output subdevice */ + s->type = COMEDI_SUBD_AO; /* Not sure what to put here */ + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 2; + /* s->trig[0] = c6xdigio_pwmo; */ + s->insn_read = c6xdigio_pwmo_insn_read; + s->insn_write = c6xdigio_pwmo_insn_write; + s->maxdata = 500; + s->range_table = &range_bipolar10; /* A suitable lie */ + + s = dev->subdevices + 1; + /* encoder (counter) subdevice */ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_LSAMPL; + s->n_chan = 2; + /* s->trig[0] = c6xdigio_ei; */ + s->insn_read = c6xdigio_ei_insn_read; + s->maxdata = 0xffffff; + s->range_table = &range_unknown; + + /* s = dev->subdevices + 2; */ + /* pwm output subdevice */ + /* s->type = COMEDI_SUBD_COUNTER; // Not sure what to put here */ + /* s->subdev_flags = SDF_WRITEABLE; */ + /* s->n_chan = 1; */ + /* s->trig[0] = c6xdigio_ei_init; */ + /* s->insn_read = c6xdigio_ei_init_insn_read; */ + /* s->insn_write = c6xdigio_ei_init_insn_write; */ + /* s->maxdata = 0xFFFF; // Really just a don't care */ + /* s->range_table = &range_unknown; // Not sure what to put here */ + + /* I will call this init anyway but more than likely the DSP board */ + /* will not be connected when device driver is loaded. */ + board_init(dev); + + return 0; +} + +static int c6xdigio_detach(struct comedi_device *dev) +{ + /* board_halt(dev); may not need this */ + + printk(KERN_DEBUG "comedi%d: c6xdigio: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, C6XDIGIO_SIZE); + + /* Not using IRQ so I am not sure if I need this */ + if (dev->irq) + free_irq(dev->irq, dev); + + pnp_unregister_driver(&c6xdigio_pnp_driver); + + return 0; +} + +static int __init driver_c6xdigio_init_module(void) +{ + return comedi_driver_register(&driver_c6xdigio); +} + +static void __exit driver_c6xdigio_cleanup_module(void) +{ + comedi_driver_unregister(&driver_c6xdigio); +} + +module_init(driver_c6xdigio_init_module); +module_exit(driver_c6xdigio_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/cb_das16_cs.c b/drivers/staging/comedi/drivers/cb_das16_cs.c new file mode 100644 index 00000000..49404f49 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_das16_cs.c @@ -0,0 +1,837 @@ +/* + comedi/drivers/das16cs.c + Driver for Computer Boards PC-CARD DAS16/16. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000, 2001, 2002 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: cb_das16_cs +Description: Computer Boards PC-CARD DAS16/16 +Devices: [ComputerBoards] PC-CARD DAS16/16 (cb_das16_cs), PC-CARD DAS16/16-AO +Author: ds +Updated: Mon, 04 Nov 2002 20:04:21 -0800 +Status: experimental + + +*/ + +#include +#include +#include "../comedidev.h" +#include +#include + +#include +#include + +#include "8253.h" + +#define DAS16CS_SIZE 18 + +#define DAS16CS_ADC_DATA 0 +#define DAS16CS_DIO_MUX 2 +#define DAS16CS_MISC1 4 +#define DAS16CS_MISC2 6 +#define DAS16CS_CTR0 8 +#define DAS16CS_CTR1 10 +#define DAS16CS_CTR2 12 +#define DAS16CS_CTR_CONTROL 14 +#define DAS16CS_DIO 16 + +struct das16cs_board { + const char *name; + int device_id; + int n_ao_chans; +}; +static const struct das16cs_board das16cs_boards[] = { + { + .device_id = 0x0000, /* unknown */ + .name = "PC-CARD DAS16/16", + .n_ao_chans = 0, + }, + { + .device_id = 0x0039, + .name = "PC-CARD DAS16/16-AO", + .n_ao_chans = 2, + }, + { + .device_id = 0x4009, + .name = "PCM-DAS16s/16", + .n_ao_chans = 0, + }, +}; + +#define n_boards ARRAY_SIZE(das16cs_boards) +#define thisboard ((const struct das16cs_board *)dev->board_ptr) + +struct das16cs_private { + struct pcmcia_device *link; + + unsigned int ao_readback[2]; + unsigned short status1; + unsigned short status2; +}; +#define devpriv ((struct das16cs_private *)dev->private) + +static int das16cs_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int das16cs_detach(struct comedi_device *dev); +static struct comedi_driver driver_das16cs = { + .driver_name = "cb_das16_cs", + .module = THIS_MODULE, + .attach = das16cs_attach, + .detach = das16cs_detach, +}; + +static struct pcmcia_device *cur_dev; + +static const struct comedi_lrange das16cs_ai_range = { 4, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-2.5, 2.5), + RANGE(-1.25, 1.25), + } +}; + +static irqreturn_t das16cs_interrupt(int irq, void *d); +static int das16cs_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das16cs_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); +static int das16cs_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int das16cs_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das16cs_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das16cs_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das16cs_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int das16cs_timer_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int das16cs_timer_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static const struct das16cs_board *das16cs_probe(struct comedi_device *dev, + struct pcmcia_device *link) +{ + int i; + + for (i = 0; i < n_boards; i++) { + if (das16cs_boards[i].device_id == link->card_id) + return das16cs_boards + i; + } + + dev_dbg(dev->hw_dev, "unknown board!\n"); + + return NULL; +} + +static int das16cs_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pcmcia_device *link; + struct comedi_subdevice *s; + int ret; + int i; + + dev_dbg(dev->hw_dev, "comedi%d: cb_das16_cs: attached\n", dev->minor); + + link = cur_dev; /* XXX hack */ + if (!link) + return -EIO; + + dev->iobase = link->resource[0]->start; + dev_dbg(dev->hw_dev, "I/O base=0x%04lx\n", dev->iobase); + + dev_dbg(dev->hw_dev, "fingerprint:\n"); + for (i = 0; i < 48; i += 2) + dev_dbg(dev->hw_dev, "%04x\n", inw(dev->iobase + i)); + + + ret = request_irq(link->irq, das16cs_interrupt, + IRQF_SHARED, "cb_das16_cs", dev); + if (ret < 0) + return ret; + + dev->irq = link->irq; + + dev_dbg(dev->hw_dev, "irq=%u\n", dev->irq); + + dev->board_ptr = das16cs_probe(dev, link); + if (!dev->board_ptr) + return -EIO; + + dev->board_name = thisboard->name; + + if (alloc_private(dev, sizeof(struct das16cs_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + dev->read_subdev = s; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ; + s->n_chan = 16; + s->maxdata = 0xffff; + s->range_table = &das16cs_ai_range; + s->len_chanlist = 16; + s->insn_read = das16cs_ai_rinsn; + s->do_cmd = das16cs_ai_cmd; + s->do_cmdtest = das16cs_ai_cmdtest; + + s = dev->subdevices + 1; + /* analog output subdevice */ + if (thisboard->n_ao_chans) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = thisboard->n_ao_chans; + s->maxdata = 0xffff; + s->range_table = &range_bipolar10; + s->insn_write = &das16cs_ao_winsn; + s->insn_read = &das16cs_ao_rinsn; + } + + s = dev->subdevices + 2; + /* digital i/o subdevice */ + if (1) { + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das16cs_dio_insn_bits; + s->insn_config = das16cs_dio_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 3; + /* timer subdevice */ + if (0) { + s->type = COMEDI_SUBD_TIMER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 1; + s->maxdata = 0xff; + s->range_table = &range_unknown; + s->insn_read = das16cs_timer_insn_read; + s->insn_config = das16cs_timer_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + + return 1; +} + +static int das16cs_detach(struct comedi_device *dev) +{ + dev_dbg(dev->hw_dev, "comedi%d: das16cs: remove\n", dev->minor); + + if (dev->irq) + free_irq(dev->irq, dev); + + + return 0; +} + +static irqreturn_t das16cs_interrupt(int irq, void *d) +{ + /* struct comedi_device *dev = d; */ + return IRQ_HANDLED; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ +static int das16cs_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int to; + int aref; + int range; + int chan; + static int range_bits[] = { 0x800, 0x000, 0x100, 0x200 }; + + chan = CR_CHAN(insn->chanspec); + aref = CR_AREF(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + outw(chan, dev->iobase + 2); + + devpriv->status1 &= ~0xf320; + devpriv->status1 |= (aref == AREF_DIFF) ? 0 : 0x0020; + outw(devpriv->status1, dev->iobase + 4); + + devpriv->status2 &= ~0xff00; + devpriv->status2 |= range_bits[range]; + outw(devpriv->status2, dev->iobase + 6); + + for (i = 0; i < insn->n; i++) { + outw(0, dev->iobase); + +#define TIMEOUT 1000 + for (to = 0; to < TIMEOUT; to++) { + if (inw(dev->iobase + 4) & 0x0080) + break; + } + if (to == TIMEOUT) { + dev_dbg(dev->hw_dev, "cb_das16_cs: ai timeout\n"); + return -ETIME; + } + data[i] = (unsigned short)inw(dev->iobase + 0); + } + + return i; +} + +static int das16cs_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + return -EINVAL; +} + +static int das16cs_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. */ + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and + * mutually compatible */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } +#define MAX_SPEED 10000 /* in nanoseconds */ +#define MIN_SPEED 1000000000 /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < MAX_SPEED) { + cmd->scan_begin_arg = MAX_SPEED; + err++; + } + if (cmd->scan_begin_arg > MIN_SPEED) { + cmd->scan_begin_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + if (cmd->scan_begin_arg > 9) { + cmd->scan_begin_arg = 9; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < MAX_SPEED) { + cmd->convert_arg = MAX_SPEED; + err++; + } + if (cmd->convert_arg > MIN_SPEED) { + cmd->convert_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* see above */ + if (cmd->convert_arg > 9) { + cmd->convert_arg = 9; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg > 0x00ffffff) { + cmd->stop_arg = 0x00ffffff; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + unsigned int div1 = 0, div2 = 0; + + tmp = cmd->scan_begin_arg; + i8253_cascade_ns_to_timer(100, &div1, &div2, + &cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + unsigned int div1 = 0, div2 = 0; + + tmp = cmd->convert_arg; + i8253_cascade_ns_to_timer(100, &div1, &div2, + &cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if (err) + return 4; + + return 0; +} + +static int das16cs_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + unsigned short status1; + unsigned short d; + int bit; + + for (i = 0; i < insn->n; i++) { + devpriv->ao_readback[chan] = data[i]; + d = data[i]; + + outw(devpriv->status1, dev->iobase + 4); + udelay(1); + + status1 = devpriv->status1 & ~0xf; + if (chan) + status1 |= 0x0001; + else + status1 |= 0x0008; + +/* printk("0x%04x\n",status1);*/ + outw(status1, dev->iobase + 4); + udelay(1); + + for (bit = 15; bit >= 0; bit--) { + int b = (d >> bit) & 0x1; + b <<= 1; +/* printk("0x%04x\n",status1 | b | 0x0000);*/ + outw(status1 | b | 0x0000, dev->iobase + 4); + udelay(1); +/* printk("0x%04x\n",status1 | b | 0x0004);*/ + outw(status1 | b | 0x0004, dev->iobase + 4); + udelay(1); + } +/* make high both DAC0CS and DAC1CS to load + new data and update analog output*/ + outw(status1 | 0x9, dev->iobase + 4); + } + + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int das16cs_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +static int das16cs_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + + outw(s->state, dev->iobase + 16); + } + + /* on return, data[1] contains the value of the digital + * input and output lines. */ + data[1] = inw(dev->iobase + 16); + + return 2; +} + +static int das16cs_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + int bits; + + if (chan < 4) + bits = 0x0f; + else + bits = 0xf0; + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= bits; + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= bits; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + break; + } + + devpriv->status2 &= ~0x00c0; + devpriv->status2 |= (s->io_bits & 0xf0) ? 0x0080 : 0; + devpriv->status2 |= (s->io_bits & 0x0f) ? 0x0040 : 0; + + outw(devpriv->status2, dev->iobase + 6); + + return insn->n; +} + +static int das16cs_timer_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + return -EINVAL; +} + +static int das16cs_timer_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + return -EINVAL; +} + +/* PCMCIA stuff */ + +/*====================================================================== + + The following pcmcia code for the pcm-das08 is adapted from the + dummy_cs.c driver of the Linux PCMCIA Card Services package. + + The initial developer of the original code is David A. Hinds + . Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + +======================================================================*/ + +#if defined(CONFIG_PCMCIA) || defined(CONFIG_PCMCIA_MODULE) + +static void das16cs_pcmcia_config(struct pcmcia_device *link); +static void das16cs_pcmcia_release(struct pcmcia_device *link); +static int das16cs_pcmcia_suspend(struct pcmcia_device *p_dev); +static int das16cs_pcmcia_resume(struct pcmcia_device *p_dev); + +/* + The attach() and detach() entry points are used to create and destroy + "instances" of the driver, where each instance represents everything + needed to manage one actual PCMCIA card. +*/ + +static int das16cs_pcmcia_attach(struct pcmcia_device *); +static void das16cs_pcmcia_detach(struct pcmcia_device *); + +/* + You'll also need to prototype all the functions that will actually + be used to talk to your device. See 'memory_cs' for a good example + of a fully self-sufficient driver; the other drivers rely more or + less on other parts of the kernel. +*/ + +struct local_info_t { + struct pcmcia_device *link; + int stop; + struct bus_operations *bus; +}; + +/*====================================================================== + + das16cs_pcmcia_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + + The dev_link structure is initialized, but we don't actually + configure the card at this point -- we wait until we receive a + card insertion event. + +======================================================================*/ + +static int das16cs_pcmcia_attach(struct pcmcia_device *link) +{ + struct local_info_t *local; + + dev_dbg(&link->dev, "das16cs_pcmcia_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kzalloc(sizeof(struct local_info_t), GFP_KERNEL); + if (!local) + return -ENOMEM; + local->link = link; + link->priv = local; + + cur_dev = link; + + das16cs_pcmcia_config(link); + + return 0; +} /* das16cs_pcmcia_attach */ + +static void das16cs_pcmcia_detach(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "das16cs_pcmcia_detach\n"); + + ((struct local_info_t *)link->priv)->stop = 1; + das16cs_pcmcia_release(link); + /* This points to the parent struct local_info_t struct */ + kfree(link->priv); +} /* das16cs_pcmcia_detach */ + + +static int das16cs_pcmcia_config_loop(struct pcmcia_device *p_dev, + void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(p_dev); +} + +static void das16cs_pcmcia_config(struct pcmcia_device *link) +{ + int ret; + + dev_dbg(&link->dev, "das16cs_pcmcia_config\n"); + + /* Do we need to allocate an interrupt? */ + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + ret = pcmcia_loop_config(link, das16cs_pcmcia_config_loop, NULL); + if (ret) { + dev_warn(&link->dev, "no configuration found\n"); + goto failed; + } + + if (!link->irq) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + return; + +failed: + das16cs_pcmcia_release(link); +} /* das16cs_pcmcia_config */ + +static void das16cs_pcmcia_release(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "das16cs_pcmcia_release\n"); + pcmcia_disable_device(link); +} /* das16cs_pcmcia_release */ + +static int das16cs_pcmcia_suspend(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + /* Mark the device as stopped, to block IO until later */ + local->stop = 1; + + return 0; +} /* das16cs_pcmcia_suspend */ + +static int das16cs_pcmcia_resume(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + local->stop = 0; + return 0; +} /* das16cs_pcmcia_resume */ + +/*====================================================================*/ + +static const struct pcmcia_device_id das16cs_id_table[] = { + PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x0039), + PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x4009), + PCMCIA_DEVICE_NULL +}; + +MODULE_DEVICE_TABLE(pcmcia, das16cs_id_table); +MODULE_AUTHOR("David A. Schleef "); +MODULE_DESCRIPTION("Comedi driver for Computer Boards PC-CARD DAS16/16"); +MODULE_LICENSE("GPL"); + +struct pcmcia_driver das16cs_driver = { + .probe = das16cs_pcmcia_attach, + .remove = das16cs_pcmcia_detach, + .suspend = das16cs_pcmcia_suspend, + .resume = das16cs_pcmcia_resume, + .id_table = das16cs_id_table, + .owner = THIS_MODULE, + .name = "cb_das16_cs", +}; + +static int __init init_das16cs_pcmcia_cs(void) +{ + pcmcia_register_driver(&das16cs_driver); + return 0; +} + +static void __exit exit_das16cs_pcmcia_cs(void) +{ + pr_debug("das16cs_pcmcia_cs: unloading\n"); + pcmcia_unregister_driver(&das16cs_driver); +} + +int __init init_module(void) +{ + int ret; + + ret = init_das16cs_pcmcia_cs(); + if (ret < 0) + return ret; + + return comedi_driver_register(&driver_das16cs); +} + +void __exit cleanup_module(void) +{ + exit_das16cs_pcmcia_cs(); + comedi_driver_unregister(&driver_das16cs); +} + +#else +static int __init driver_das16cs_init_module(void) +{ + return comedi_driver_register(&driver_das16cs); +} + +static void __exit driver_das16cs_cleanup_module(void) +{ + comedi_driver_unregister(&driver_das16cs); +} + +module_init(driver_das16cs_init_module); +module_exit(driver_das16cs_cleanup_module); +#endif /* CONFIG_PCMCIA */ diff --git a/drivers/staging/comedi/drivers/cb_pcidas.c b/drivers/staging/comedi/drivers/cb_pcidas.c new file mode 100644 index 00000000..7e4ffcfd --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcidas.c @@ -0,0 +1,1965 @@ +/* + comedi/drivers/cb_pcidas.c + + Developed by Ivan Martinez and Frank Mori Hess, with valuable help from + David Schleef and the rest of the Comedi developers comunity. + + Copyright (C) 2001-2003 Ivan Martinez + Copyright (C) 2001,2002 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-8 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ +*/ +/* +Driver: cb_pcidas +Description: MeasurementComputing PCI-DAS series with the AMCC S5933 PCI controller +Author: Ivan Martinez , + Frank Mori Hess +Updated: 2003-3-11 +Devices: [Measurement Computing] PCI-DAS1602/16 (cb_pcidas), + PCI-DAS1602/16jr, PCI-DAS1602/12, PCI-DAS1200, PCI-DAS1200jr, + PCI-DAS1000, PCI-DAS1001, PCI_DAS1002 + +Status: + There are many reports of the driver being used with most of the + supported cards. Despite no detailed log is maintained, it can + be said that the driver is quite tested and stable. + + The boards may be autocalibrated using the comedi_calibrate + utility. + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. + +For commands, the scanned channels must be consecutive +(i.e. 4-5-6-7, 2-3-4,...), and must all have the same +range and aref. + +AI Triggering: + For start_src == TRIG_EXT, the A/D EXTERNAL TRIGGER IN (pin 45) is used. + For 1602 series, the start_arg is interpreted as follows: + start_arg == 0 => gated triger (level high) + start_arg == CR_INVERT => gated triger (level low) + start_arg == CR_EDGE => Rising edge + start_arg == CR_EDGE | CR_INVERT => Falling edge + For the other boards the trigger will be done on rising edge +*/ +/* + +TODO: + +analog triggering on 1602 series +*/ + +#include "../comedidev.h" +#include +#include + +#include "8253.h" +#include "8255.h" +#include "amcc_s5933.h" +#include "comedi_pci.h" +#include "comedi_fc.h" + +#undef CB_PCIDAS_DEBUG /* disable debugging code */ +/* #define CB_PCIDAS_DEBUG enable debugging code */ + +/* PCI vendor number of ComputerBoards/MeasurementComputing */ +#define PCI_VENDOR_ID_CB 0x1307 +#define TIMER_BASE 100 /* 10MHz master clock */ +#define AI_BUFFER_SIZE 1024 /* maximum fifo size of any supported board */ +#define AO_BUFFER_SIZE 1024 /* maximum fifo size of any supported board */ +#define NUM_CHANNELS_8800 8 +#define NUM_CHANNELS_7376 1 +#define NUM_CHANNELS_8402 2 +#define NUM_CHANNELS_DAC08 1 + +/* PCI-DAS base addresses */ + +/* indices of base address regions */ +#define S5933_BADRINDEX 0 +#define CONT_STAT_BADRINDEX 1 +#define ADC_FIFO_BADRINDEX 2 +#define PACER_BADRINDEX 3 +#define AO_BADRINDEX 4 +/* sizes of io regions */ +#define CONT_STAT_SIZE 10 +#define ADC_FIFO_SIZE 4 +#define PACER_SIZE 12 +#define AO_SIZE 4 + +/* Control/Status registers */ +#define INT_ADCFIFO 0 /* INTERRUPT / ADC FIFO register */ +#define INT_EOS 0x1 /* interrupt end of scan */ +#define INT_FHF 0x2 /* interrupt fifo half full */ +#define INT_FNE 0x3 /* interrupt fifo not empty */ +#define INT_MASK 0x3 /* mask of interrupt select bits */ +#define INTE 0x4 /* interrupt enable */ +#define DAHFIE 0x8 /* dac half full interrupt enable */ +#define EOAIE 0x10 /* end of acquisition interrupt enable */ +#define DAHFI 0x20 /* dac half full read status / write interrupt clear */ +#define EOAI 0x40 /* read end of acq. interrupt status / write clear */ +#define INT 0x80 /* read interrupt status / write clear */ +#define EOBI 0x200 /* read end of burst interrupt status */ +#define ADHFI 0x400 /* read half-full interrupt status */ +#define ADNEI 0x800 /* read fifo not empty interrupt latch status */ +#define ADNE 0x1000 /* read, fifo not empty (realtime, not latched) status */ +#define DAEMIE 0x1000 /* write, dac empty interrupt enable */ +#define LADFUL 0x2000 /* read fifo overflow / write clear */ +#define DAEMI 0x4000 /* dac fifo empty interrupt status / write clear */ + +#define ADCMUX_CONT 2 /* ADC CHANNEL MUX AND CONTROL register */ +#define BEGIN_SCAN(x) ((x) & 0xf) +#define END_SCAN(x) (((x) & 0xf) << 4) +#define GAIN_BITS(x) (((x) & 0x3) << 8) +#define UNIP 0x800 /* Analog front-end unipolar for range */ +#define SE 0x400 /* Inputs in single-ended mode */ +#define PACER_MASK 0x3000 /* pacer source bits */ +#define PACER_INT 0x1000 /* internal pacer */ +#define PACER_EXT_FALL 0x2000 /* external falling edge */ +#define PACER_EXT_RISE 0x3000 /* external rising edge */ +#define EOC 0x4000 /* adc not busy */ + +#define TRIG_CONTSTAT 4 /* TRIGGER CONTROL/STATUS register */ +#define SW_TRIGGER 0x1 /* software start trigger */ +#define EXT_TRIGGER 0x2 /* external start trigger */ +#define ANALOG_TRIGGER 0x3 /* external analog trigger */ +#define TRIGGER_MASK 0x3 /* mask of bits that determine start trigger */ +#define TGPOL 0x04 /* invert the edge/level of the external trigger (1602 only) */ +#define TGSEL 0x08 /* if set edge triggered, otherwise level trigerred (1602 only) */ +#define TGEN 0x10 /* enable external start trigger */ +#define BURSTE 0x20 /* burst mode enable */ +#define XTRCL 0x80 /* clear external trigger */ + +#define CALIBRATION_REG 6 /* CALIBRATION register */ +#define SELECT_8800_BIT 0x100 /* select 8800 caldac */ +#define SELECT_TRIMPOT_BIT 0x200 /* select ad7376 trim pot */ +#define SELECT_DAC08_BIT 0x400 /* select dac08 caldac */ +#define CAL_SRC_BITS(x) (((x) & 0x7) << 11) +#define CAL_EN_BIT 0x4000 /* read calibration source instead of analog input channel 0 */ +#define SERIAL_DATA_IN_BIT 0x8000 /* serial data stream going to 8800 and 7376 */ + +#define DAC_CSR 0x8 /* dac control and status register */ +enum dac_csr_bits { + DACEN = 0x2, /* dac enable */ + DAC_MODE_UPDATE_BOTH = 0x80, /* update both dacs when dac0 is written */ +}; +static inline unsigned int DAC_RANGE(unsigned int channel, unsigned int range) +{ + return (range & 0x3) << (8 + 2 * (channel & 0x1)); +} + +static inline unsigned int DAC_RANGE_MASK(unsigned int channel) +{ + return 0x3 << (8 + 2 * (channel & 0x1)); +}; + +/* bits for 1602 series only */ +enum dac_csr_bits_1602 { + DAC_EMPTY = 0x1, /* dac fifo empty, read, write clear */ + DAC_START = 0x4, /* start/arm dac fifo operations */ + DAC_PACER_MASK = 0x18, /* bits that set dac pacer source */ + DAC_PACER_INT = 0x8, /* dac internal pacing */ + DAC_PACER_EXT_FALL = 0x10, /* dac external pacing, falling edge */ + DAC_PACER_EXT_RISE = 0x18, /* dac external pacing, rising edge */ +}; +static inline unsigned int DAC_CHAN_EN(unsigned int channel) +{ + return 1 << (5 + (channel & 0x1)); /* enable channel 0 or 1 */ +}; + +/* analog input fifo */ +#define ADCDATA 0 /* ADC DATA register */ +#define ADCFIFOCLR 2 /* ADC FIFO CLEAR */ + +/* pacer, counter, dio registers */ +#define ADC8254 0 +#define DIO_8255 4 +#define DAC8254 8 + +/* analog output registers for 100x, 1200 series */ +static inline unsigned int DAC_DATA_REG(unsigned int channel) +{ + return 2 * (channel & 0x1); +} + +/* analog output registers for 1602 series*/ +#define DACDATA 0 /* DAC DATA register */ +#define DACFIFOCLR 2 /* DAC FIFO CLEAR */ + +/* bit in hexadecimal representation of range index that indicates unipolar input range */ +#define IS_UNIPOLAR 0x4 +/* analog input ranges for most boards */ +static const struct comedi_lrange cb_pcidas_ranges = { + 8, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +/* pci-das1001 input ranges */ +static const struct comedi_lrange cb_pcidas_alt_ranges = { + 8, + { + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01) + } +}; + +/* analog output ranges */ +static const struct comedi_lrange cb_pcidas_ao_ranges = { + 4, + { + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(10), + } +}; + +enum trimpot_model { + AD7376, + AD8402, +}; + +struct cb_pcidas_board { + const char *name; + unsigned short device_id; + int ai_se_chans; /* Inputs in single-ended mode */ + int ai_diff_chans; /* Inputs in differential mode */ + int ai_bits; /* analog input resolution */ + int ai_speed; /* fastest conversion period in ns */ + int ao_nchan; /* number of analog out channels */ + int has_ao_fifo; /* analog output has fifo */ + int ao_scan_speed; /* analog output speed for 1602 series (for a scan, not conversion) */ + int fifo_size; /* number of samples fifo can hold */ + const struct comedi_lrange *ranges; + enum trimpot_model trimpot; + unsigned has_dac08:1; + unsigned has_ai_trig_gated:1; /* Tells if the AI trigger can be gated */ + unsigned has_ai_trig_invert:1; /* Tells if the AI trigger can be inverted */ +}; + +static const struct cb_pcidas_board cb_pcidas_boards[] = { + { + .name = "pci-das1602/16", + .device_id = 0x1, + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 2, + .has_ao_fifo = 1, + .ao_scan_speed = 10000, + .fifo_size = 512, + .ranges = &cb_pcidas_ranges, + .trimpot = AD8402, + .has_dac08 = 1, + .has_ai_trig_gated = 1, + .has_ai_trig_invert = 1, + }, + { + .name = "pci-das1200", + .device_id = 0xF, + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 12, + .ai_speed = 3200, + .ao_nchan = 2, + .has_ao_fifo = 0, + .fifo_size = 1024, + .ranges = &cb_pcidas_ranges, + .trimpot = AD7376, + .has_dac08 = 0, + .has_ai_trig_gated = 0, + .has_ai_trig_invert = 0, + }, + { + .name = "pci-das1602/12", + .device_id = 0x10, + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 12, + .ai_speed = 3200, + .ao_nchan = 2, + .has_ao_fifo = 1, + .ao_scan_speed = 4000, + .fifo_size = 1024, + .ranges = &cb_pcidas_ranges, + .trimpot = AD7376, + .has_dac08 = 0, + .has_ai_trig_gated = 1, + .has_ai_trig_invert = 1, + }, + { + .name = "pci-das1200/jr", + .device_id = 0x19, + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 12, + .ai_speed = 3200, + .ao_nchan = 0, + .has_ao_fifo = 0, + .fifo_size = 1024, + .ranges = &cb_pcidas_ranges, + .trimpot = AD7376, + .has_dac08 = 0, + .has_ai_trig_gated = 0, + .has_ai_trig_invert = 0, + }, + { + .name = "pci-das1602/16/jr", + .device_id = 0x1C, + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 0, + .has_ao_fifo = 0, + .fifo_size = 512, + .ranges = &cb_pcidas_ranges, + .trimpot = AD8402, + .has_dac08 = 1, + .has_ai_trig_gated = 1, + .has_ai_trig_invert = 1, + }, + { + .name = "pci-das1000", + .device_id = 0x4C, + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 12, + .ai_speed = 4000, + .ao_nchan = 0, + .has_ao_fifo = 0, + .fifo_size = 1024, + .ranges = &cb_pcidas_ranges, + .trimpot = AD7376, + .has_dac08 = 0, + .has_ai_trig_gated = 0, + .has_ai_trig_invert = 0, + }, + { + .name = "pci-das1001", + .device_id = 0x1a, + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 12, + .ai_speed = 6800, + .ao_nchan = 2, + .has_ao_fifo = 0, + .fifo_size = 1024, + .ranges = &cb_pcidas_alt_ranges, + .trimpot = AD7376, + .has_dac08 = 0, + .has_ai_trig_gated = 0, + .has_ai_trig_invert = 0, + }, + { + .name = "pci-das1002", + .device_id = 0x1b, + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 12, + .ai_speed = 6800, + .ao_nchan = 2, + .has_ao_fifo = 0, + .fifo_size = 1024, + .ranges = &cb_pcidas_ranges, + .trimpot = AD7376, + .has_dac08 = 0, + .has_ai_trig_gated = 0, + .has_ai_trig_invert = 0, + }, +}; + +static DEFINE_PCI_DEVICE_TABLE(cb_pcidas_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0001) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x000f) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0010) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0019) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x001c) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x004c) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x001a) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x001b) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, cb_pcidas_pci_table); + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct cb_pcidas_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. */ +struct cb_pcidas_private { + /* would be useful for a PCI device */ + struct pci_dev *pci_dev; + /* base addresses */ + unsigned long s5933_config; + unsigned long control_status; + unsigned long adc_fifo; + unsigned long pacer_counter_dio; + unsigned long ao_registers; + /* divisors of master clock for analog input pacing */ + unsigned int divisor1; + unsigned int divisor2; + volatile unsigned int count; /* number of analog input samples remaining */ + volatile unsigned int adc_fifo_bits; /* bits to write to interrupt/adcfifo register */ + volatile unsigned int s5933_intcsr_bits; /* bits to write to amcc s5933 interrupt control/status register */ + volatile unsigned int ao_control_bits; /* bits to write to ao control and status register */ + short ai_buffer[AI_BUFFER_SIZE]; + short ao_buffer[AO_BUFFER_SIZE]; + /* divisors of master clock for analog output pacing */ + unsigned int ao_divisor1; + unsigned int ao_divisor2; + volatile unsigned int ao_count; /* number of analog output samples remaining */ + int ao_value[2]; /* remember what the analog outputs are set to, to allow readback */ + unsigned int caldac_value[NUM_CHANNELS_8800]; /* for readback of caldac */ + unsigned int trimpot_value[NUM_CHANNELS_8402]; /* for readback of trimpot */ + unsigned int dac08_value; + unsigned int calibration_source; +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct cb_pcidas_private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int cb_pcidas_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int cb_pcidas_detach(struct comedi_device *dev); +static struct comedi_driver driver_cb_pcidas = { + .driver_name = "cb_pcidas", + .module = THIS_MODULE, + .attach = cb_pcidas_attach, + .detach = cb_pcidas_detach, +}; + +static int cb_pcidas_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int cb_pcidas_ao_nofifo_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int cb_pcidas_ao_fifo_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int cb_pcidas_ao_readback_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int cb_pcidas_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); +static int cb_pcidas_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int cb_pcidas_ao_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); +static int cb_pcidas_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *subdev, + unsigned int trig_num); +static int cb_pcidas_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static irqreturn_t cb_pcidas_interrupt(int irq, void *d); +static void handle_ao_interrupt(struct comedi_device *dev, unsigned int status); +static int cb_pcidas_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static int cb_pcidas_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void cb_pcidas_load_counters(struct comedi_device *dev, unsigned int *ns, + int round_flags); +static int eeprom_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int caldac_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int caldac_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int trimpot_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int cb_pcidas_trimpot_write(struct comedi_device *dev, + unsigned int channel, unsigned int value); +static int trimpot_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dac08_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int dac08_write(struct comedi_device *dev, unsigned int value); +static int dac08_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int caldac_8800_write(struct comedi_device *dev, unsigned int address, + uint8_t value); +static int trimpot_7376_write(struct comedi_device *dev, uint8_t value); +static int trimpot_8402_write(struct comedi_device *dev, unsigned int channel, + uint8_t value); +static int nvram_read(struct comedi_device *dev, unsigned int address, + uint8_t *data); + +static inline unsigned int cal_enable_bits(struct comedi_device *dev) +{ + return CAL_EN_BIT | CAL_SRC_BITS(devpriv->calibration_source); +} + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. + */ +static int cb_pcidas_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + struct pci_dev *pcidev = NULL; + int index; + int i; + +/* + * Allocate the private structure area. + */ + if (alloc_private(dev, sizeof(struct cb_pcidas_private)) < 0) + return -ENOMEM; + +/* + * Probe the device to determine what device in the series it is. + */ + + for_each_pci_dev(pcidev) { + /* is it not a computer boards card? */ + if (pcidev->vendor != PCI_VENDOR_ID_CB) + continue; + /* loop through cards supported by this driver */ + for (index = 0; index < ARRAY_SIZE(cb_pcidas_boards); index++) { + if (cb_pcidas_boards[index].device_id != pcidev->device) + continue; + /* was a particular bus/slot requested? */ + if (it->options[0] || it->options[1]) { + /* are we on the wrong bus/slot? */ + if (pcidev->bus->number != it->options[0] || + PCI_SLOT(pcidev->devfn) != it->options[1]) { + continue; + } + } + devpriv->pci_dev = pcidev; + dev->board_ptr = cb_pcidas_boards + index; + goto found; + } + } + + dev_err(dev->hw_dev, "No supported ComputerBoards/MeasurementComputing card found on requested position\n"); + return -EIO; + +found: + + dev_dbg(dev->hw_dev, "Found %s on bus %i, slot %i\n", + cb_pcidas_boards[index].name, pcidev->bus->number, + PCI_SLOT(pcidev->devfn)); + + /* + * Enable PCI device and reserve I/O ports. + */ + if (comedi_pci_enable(pcidev, "cb_pcidas")) { + dev_err(dev->hw_dev, "Failed to enable PCI device and request regions\n"); + return -EIO; + } + /* + * Initialize devpriv->control_status and devpriv->adc_fifo to point to + * their base address. + */ + devpriv->s5933_config = + pci_resource_start(devpriv->pci_dev, S5933_BADRINDEX); + devpriv->control_status = + pci_resource_start(devpriv->pci_dev, CONT_STAT_BADRINDEX); + devpriv->adc_fifo = + pci_resource_start(devpriv->pci_dev, ADC_FIFO_BADRINDEX); + devpriv->pacer_counter_dio = + pci_resource_start(devpriv->pci_dev, PACER_BADRINDEX); + if (thisboard->ao_nchan) { + devpriv->ao_registers = + pci_resource_start(devpriv->pci_dev, AO_BADRINDEX); + } + /* disable and clear interrupts on amcc s5933 */ + outl(INTCSR_INBOX_INTR_STATUS, + devpriv->s5933_config + AMCC_OP_REG_INTCSR); + + /* get irq */ + if (request_irq(devpriv->pci_dev->irq, cb_pcidas_interrupt, + IRQF_SHARED, "cb_pcidas", dev)) { + dev_dbg(dev->hw_dev, "unable to allocate irq %d\n", + devpriv->pci_dev->irq); + return -EINVAL; + } + dev->irq = devpriv->pci_dev->irq; + + /* Initialize dev->board_name */ + dev->board_name = thisboard->name; + +/* + * Allocate the subdevice structures. + */ + if (alloc_subdevices(dev, 7) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog input subdevice */ + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ; + /* WARNING: Number of inputs in differential mode is ignored */ + s->n_chan = thisboard->ai_se_chans; + s->len_chanlist = thisboard->ai_se_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = thisboard->ranges; + s->insn_read = cb_pcidas_ai_rinsn; + s->insn_config = ai_config_insn; + s->do_cmd = cb_pcidas_ai_cmd; + s->do_cmdtest = cb_pcidas_ai_cmdtest; + s->cancel = cb_pcidas_cancel; + + /* analog output subdevice */ + s = dev->subdevices + 1; + if (thisboard->ao_nchan) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND; + s->n_chan = thisboard->ao_nchan; + /* analog out resolution is the same as analog input resolution, so use ai_bits */ + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &cb_pcidas_ao_ranges; + s->insn_read = cb_pcidas_ao_readback_insn; + if (thisboard->has_ao_fifo) { + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + s->insn_write = cb_pcidas_ao_fifo_winsn; + s->do_cmdtest = cb_pcidas_ao_cmdtest; + s->do_cmd = cb_pcidas_ao_cmd; + s->cancel = cb_pcidas_ao_cancel; + } else { + s->insn_write = cb_pcidas_ao_nofifo_winsn; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* 8255 */ + s = dev->subdevices + 2; + subdev_8255_init(dev, s, NULL, devpriv->pacer_counter_dio + DIO_8255); + + /* serial EEPROM, */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE | SDF_INTERNAL; + s->n_chan = 256; + s->maxdata = 0xff; + s->insn_read = eeprom_read_insn; + + /* 8800 caldac */ + s = dev->subdevices + 4; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = NUM_CHANNELS_8800; + s->maxdata = 0xff; + s->insn_read = caldac_read_insn; + s->insn_write = caldac_write_insn; + for (i = 0; i < s->n_chan; i++) + caldac_8800_write(dev, i, s->maxdata / 2); + + /* trim potentiometer */ + s = dev->subdevices + 5; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + if (thisboard->trimpot == AD7376) { + s->n_chan = NUM_CHANNELS_7376; + s->maxdata = 0x7f; + } else { + s->n_chan = NUM_CHANNELS_8402; + s->maxdata = 0xff; + } + s->insn_read = trimpot_read_insn; + s->insn_write = trimpot_write_insn; + for (i = 0; i < s->n_chan; i++) + cb_pcidas_trimpot_write(dev, i, s->maxdata / 2); + + /* dac08 caldac */ + s = dev->subdevices + 6; + if (thisboard->has_dac08) { + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = NUM_CHANNELS_DAC08; + s->insn_read = dac08_read_insn; + s->insn_write = dac08_write_insn; + s->maxdata = 0xff; + dac08_write(dev, s->maxdata / 2); + } else + s->type = COMEDI_SUBD_UNUSED; + + /* make sure mailbox 4 is empty */ + inl(devpriv->s5933_config + AMCC_OP_REG_IMB4); + /* Set bits to enable incoming mailbox interrupts on amcc s5933. */ + devpriv->s5933_intcsr_bits = + INTCSR_INBOX_BYTE(3) | INTCSR_INBOX_SELECT(3) | + INTCSR_INBOX_FULL_INT; + /* clear and enable interrupt on amcc s5933 */ + outl(devpriv->s5933_intcsr_bits | INTCSR_INBOX_INTR_STATUS, + devpriv->s5933_config + AMCC_OP_REG_INTCSR); + + return 1; +} + +/* + * cb_pcidas_detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int cb_pcidas_detach(struct comedi_device *dev) +{ + + if (devpriv) { + if (devpriv->s5933_config) { + /* disable and clear interrupts on amcc s5933 */ + outl(INTCSR_INBOX_INTR_STATUS, + devpriv->s5933_config + AMCC_OP_REG_INTCSR); +#ifdef CB_PCIDAS_DEBUG + dev_dbg(dev->hw_dev, "detaching, incsr is 0x%x\n", + inl(devpriv->s5933_config + AMCC_OP_REG_INTCSR)); +#endif + } + } + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 2); + if (devpriv && devpriv->pci_dev) { + if (devpriv->s5933_config) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + + return 0; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ +static int cb_pcidas_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, i; + unsigned int bits; + static const int timeout = 10000; + int channel; + /* enable calibration input if appropriate */ + if (insn->chanspec & CR_ALT_SOURCE) { + outw(cal_enable_bits(dev), + devpriv->control_status + CALIBRATION_REG); + channel = 0; + } else { + outw(0, devpriv->control_status + CALIBRATION_REG); + channel = CR_CHAN(insn->chanspec); + } + /* set mux limits and gain */ + bits = BEGIN_SCAN(channel) | + END_SCAN(channel) | GAIN_BITS(CR_RANGE(insn->chanspec)); + /* set unipolar/bipolar */ + if (CR_RANGE(insn->chanspec) & IS_UNIPOLAR) + bits |= UNIP; + /* set singleended/differential */ + if (CR_AREF(insn->chanspec) != AREF_DIFF) + bits |= SE; + outw(bits, devpriv->control_status + ADCMUX_CONT); + + /* clear fifo */ + outw(0, devpriv->adc_fifo + ADCFIFOCLR); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outw(0, devpriv->adc_fifo + ADCDATA); + + /* wait for conversion to end */ + /* return -ETIMEDOUT if there is a timeout */ + for (i = 0; i < timeout; i++) { + if (inw(devpriv->control_status + ADCMUX_CONT) & EOC) + break; + } + if (i == timeout) + return -ETIMEDOUT; + + /* read data */ + data[n] = inw(devpriv->adc_fifo + ADCDATA); + } + + /* return the number of samples read/written */ + return n; +} + +static int ai_config_calibration_source(struct comedi_device *dev, + unsigned int *data) +{ + static const int num_calibration_sources = 8; + unsigned int source = data[1]; + + if (source >= num_calibration_sources) { + dev_err(dev->hw_dev, "invalid calibration source: %i\n", + source); + return -EINVAL; + } + + devpriv->calibration_source = source; + + return 2; +} + +static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int id = data[0]; + + switch (id) { + case INSN_CONFIG_ALT_SOURCE: + return ai_config_calibration_source(dev, data); + break; + default: + return -EINVAL; + break; + } + return -EINVAL; +} + +/* analog output insn for pcidas-1000 and 1200 series */ +static int cb_pcidas_ao_nofifo_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int channel; + unsigned long flags; + + /* set channel and range */ + channel = CR_CHAN(insn->chanspec); + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->ao_control_bits &= + ~DAC_MODE_UPDATE_BOTH & ~DAC_RANGE_MASK(channel); + devpriv->ao_control_bits |= + DACEN | DAC_RANGE(channel, CR_RANGE(insn->chanspec)); + outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* remember value for readback */ + devpriv->ao_value[channel] = data[0]; + /* send data */ + outw(data[0], devpriv->ao_registers + DAC_DATA_REG(channel)); + + return 1; +} + +/* analog output insn for pcidas-1602 series */ +static int cb_pcidas_ao_fifo_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int channel; + unsigned long flags; + + /* clear dac fifo */ + outw(0, devpriv->ao_registers + DACFIFOCLR); + + /* set channel and range */ + channel = CR_CHAN(insn->chanspec); + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->ao_control_bits &= + ~DAC_CHAN_EN(0) & ~DAC_CHAN_EN(1) & ~DAC_RANGE_MASK(channel) & + ~DAC_PACER_MASK; + devpriv->ao_control_bits |= + DACEN | DAC_RANGE(channel, + CR_RANGE(insn-> + chanspec)) | DAC_CHAN_EN(channel) | + DAC_START; + outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* remember value for readback */ + devpriv->ao_value[channel] = data[0]; + /* send data */ + outw(data[0], devpriv->ao_registers + DACDATA); + + return 1; +} + +/* analog output readback insn */ +/* XXX loses track of analog output value back after an analog ouput command is executed */ +static int cb_pcidas_ao_readback_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int eeprom_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + uint8_t nvram_data; + int retval; + + retval = nvram_read(dev, CR_CHAN(insn->chanspec), &nvram_data); + if (retval < 0) + return retval; + + data[0] = nvram_data; + + return 1; +} + +static int caldac_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const unsigned int channel = CR_CHAN(insn->chanspec); + + return caldac_8800_write(dev, channel, data[0]); +} + +static int caldac_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = devpriv->caldac_value[CR_CHAN(insn->chanspec)]; + + return 1; +} + +/* 1602/16 pregain offset */ +static int dac08_write(struct comedi_device *dev, unsigned int value) +{ + if (devpriv->dac08_value == value) + return 1; + + devpriv->dac08_value = value; + + outw(cal_enable_bits(dev) | (value & 0xff), + devpriv->control_status + CALIBRATION_REG); + udelay(1); + outw(cal_enable_bits(dev) | SELECT_DAC08_BIT | (value & 0xff), + devpriv->control_status + CALIBRATION_REG); + udelay(1); + outw(cal_enable_bits(dev) | (value & 0xff), + devpriv->control_status + CALIBRATION_REG); + udelay(1); + + return 1; +} + +static int dac08_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + return dac08_write(dev, data[0]); +} + +static int dac08_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + data[0] = devpriv->dac08_value; + + return 1; +} + +static int cb_pcidas_trimpot_write(struct comedi_device *dev, + unsigned int channel, unsigned int value) +{ + if (devpriv->trimpot_value[channel] == value) + return 1; + + devpriv->trimpot_value[channel] = value; + switch (thisboard->trimpot) { + case AD7376: + trimpot_7376_write(dev, value); + break; + case AD8402: + trimpot_8402_write(dev, channel, value); + break; + default: + comedi_error(dev, "driver bug?"); + return -1; + break; + } + + return 1; +} + +static int trimpot_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int channel = CR_CHAN(insn->chanspec); + + return cb_pcidas_trimpot_write(dev, channel, data[0]); +} + +static int trimpot_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int channel = CR_CHAN(insn->chanspec); + + data[0] = devpriv->trimpot_value[channel]; + + return 1; +} + +static int cb_pcidas_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + int i, gain, start_chan; + + /* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. */ + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_NOW | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) + err++; + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && + cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + /* make sure trigger sources are compatible with each other */ + if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) + err++; + if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW) + err++; + if (cmd->start_src == TRIG_EXT && + (cmd->convert_src == TRIG_EXT || cmd->scan_begin_src == TRIG_EXT)) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + switch (cmd->start_src) { + case TRIG_EXT: + /* External trigger, only CR_EDGE and CR_INVERT flags allowed */ + if ((cmd->start_arg + & (CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT))) != 0) { + cmd->start_arg &= + ~(CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT)); + err++; + } + if (!thisboard->has_ai_trig_invert && + (cmd->start_arg & CR_INVERT)) { + cmd->start_arg &= (CR_FLAGS_MASK & ~CR_INVERT); + err++; + } + break; + default: + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + break; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < + thisboard->ai_speed * cmd->chanlist_len) { + cmd->scan_begin_arg = + thisboard->ai_speed * cmd->chanlist_len; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < thisboard->ai_speed) { + cmd->convert_arg = thisboard->ai_speed; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_NONE) { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + i8253_cascade_ns_to_timer_2div(TIMER_BASE, + &(devpriv->divisor1), + &(devpriv->divisor2), + &(cmd->scan_begin_arg), + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + i8253_cascade_ns_to_timer_2div(TIMER_BASE, + &(devpriv->divisor1), + &(devpriv->divisor2), + &(cmd->convert_arg), + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + } + + if (err) + return 4; + + /* check channel/gain list against card's limitations */ + if (cmd->chanlist) { + gain = CR_RANGE(cmd->chanlist[0]); + start_chan = CR_CHAN(cmd->chanlist[0]); + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != + (start_chan + i) % s->n_chan) { + comedi_error(dev, + "entries in chanlist must be consecutive channels, counting upwards\n"); + err++; + } + if (CR_RANGE(cmd->chanlist[i]) != gain) { + comedi_error(dev, + "entries in chanlist must all have the same gain\n"); + err++; + } + } + } + + if (err) + return 5; + + return 0; +} + +static int cb_pcidas_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int bits; + unsigned long flags; + + /* make sure CAL_EN_BIT is disabled */ + outw(0, devpriv->control_status + CALIBRATION_REG); + /* initialize before settings pacer source and count values */ + outw(0, devpriv->control_status + TRIG_CONTSTAT); + /* clear fifo */ + outw(0, devpriv->adc_fifo + ADCFIFOCLR); + + /* set mux limits, gain and pacer source */ + bits = BEGIN_SCAN(CR_CHAN(cmd->chanlist[0])) | + END_SCAN(CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1])) | + GAIN_BITS(CR_RANGE(cmd->chanlist[0])); + /* set unipolar/bipolar */ + if (CR_RANGE(cmd->chanlist[0]) & IS_UNIPOLAR) + bits |= UNIP; + /* set singleended/differential */ + if (CR_AREF(cmd->chanlist[0]) != AREF_DIFF) + bits |= SE; + /* set pacer source */ + if (cmd->convert_src == TRIG_EXT || cmd->scan_begin_src == TRIG_EXT) + bits |= PACER_EXT_RISE; + else + bits |= PACER_INT; + outw(bits, devpriv->control_status + ADCMUX_CONT); + +#ifdef CB_PCIDAS_DEBUG + dev_dbg(dev->hw_dev, "comedi: sent 0x%x to adcmux control\n", bits); +#endif + + /* load counters */ + if (cmd->convert_src == TRIG_TIMER) + cb_pcidas_load_counters(dev, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + else if (cmd->scan_begin_src == TRIG_TIMER) + cb_pcidas_load_counters(dev, &cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + + /* set number of conversions */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->count = cmd->chanlist_len * cmd->stop_arg; + /* enable interrupts */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->adc_fifo_bits |= INTE; + devpriv->adc_fifo_bits &= ~INT_MASK; + if (cmd->flags & TRIG_WAKE_EOS) { + if (cmd->convert_src == TRIG_NOW && cmd->chanlist_len > 1) + devpriv->adc_fifo_bits |= INT_EOS; /* interrupt end of burst */ + else + devpriv->adc_fifo_bits |= INT_FNE; /* interrupt fifo not empty */ + } else { + devpriv->adc_fifo_bits |= INT_FHF; /* interrupt fifo half full */ + } +#ifdef CB_PCIDAS_DEBUG + dev_dbg(dev->hw_dev, "comedi: adc_fifo_bits are 0x%x\n", + devpriv->adc_fifo_bits); +#endif + /* enable (and clear) interrupts */ + outw(devpriv->adc_fifo_bits | EOAI | INT | LADFUL, + devpriv->control_status + INT_ADCFIFO); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* set start trigger and burst mode */ + bits = 0; + if (cmd->start_src == TRIG_NOW) + bits |= SW_TRIGGER; + else if (cmd->start_src == TRIG_EXT) { + bits |= EXT_TRIGGER | TGEN | XTRCL; + if (thisboard->has_ai_trig_invert + && (cmd->start_arg & CR_INVERT)) + bits |= TGPOL; + if (thisboard->has_ai_trig_gated && (cmd->start_arg & CR_EDGE)) + bits |= TGSEL; + } else { + comedi_error(dev, "bug!"); + return -1; + } + if (cmd->convert_src == TRIG_NOW && cmd->chanlist_len > 1) + bits |= BURSTE; + outw(bits, devpriv->control_status + TRIG_CONTSTAT); +#ifdef CB_PCIDAS_DEBUG + dev_dbg(dev->hw_dev, "comedi: sent 0x%x to trig control\n", bits); +#endif + + return 0; +} + +static int cb_pcidas_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. */ + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < thisboard->ao_scan_speed) { + cmd->scan_begin_arg = thisboard->ao_scan_speed; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_NONE) { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + i8253_cascade_ns_to_timer_2div(TIMER_BASE, + &(devpriv->ao_divisor1), + &(devpriv->ao_divisor2), + &(cmd->scan_begin_arg), + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + /* check channel/gain list against card's limitations */ + if (cmd->chanlist && cmd->chanlist_len > 1) { + if (CR_CHAN(cmd->chanlist[0]) != 0 || + CR_CHAN(cmd->chanlist[1]) != 1) { + comedi_error(dev, + "channels must be ordered channel 0, channel 1 in chanlist\n"); + err++; + } + } + + if (err) + return 5; + + return 0; +} + +static int cb_pcidas_ao_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int i; + unsigned long flags; + + /* set channel limits, gain */ + spin_lock_irqsave(&dev->spinlock, flags); + for (i = 0; i < cmd->chanlist_len; i++) { + /* enable channel */ + devpriv->ao_control_bits |= + DAC_CHAN_EN(CR_CHAN(cmd->chanlist[i])); + /* set range */ + devpriv->ao_control_bits |= DAC_RANGE(CR_CHAN(cmd->chanlist[i]), + CR_RANGE(cmd-> + chanlist[i])); + } + + /* disable analog out before settings pacer source and count values */ + outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* clear fifo */ + outw(0, devpriv->ao_registers + DACFIFOCLR); + + /* load counters */ + if (cmd->scan_begin_src == TRIG_TIMER) { + i8253_cascade_ns_to_timer_2div(TIMER_BASE, + &(devpriv->ao_divisor1), + &(devpriv->ao_divisor2), + &(cmd->scan_begin_arg), + cmd->flags); + + /* Write the values of ctr1 and ctr2 into counters 1 and 2 */ + i8254_load(devpriv->pacer_counter_dio + DAC8254, 0, 1, + devpriv->ao_divisor1, 2); + i8254_load(devpriv->pacer_counter_dio + DAC8254, 0, 2, + devpriv->ao_divisor2, 2); + } + /* set number of conversions */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->ao_count = cmd->chanlist_len * cmd->stop_arg; + /* set pacer source */ + spin_lock_irqsave(&dev->spinlock, flags); + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + devpriv->ao_control_bits |= DAC_PACER_INT; + break; + case TRIG_EXT: + devpriv->ao_control_bits |= DAC_PACER_EXT_RISE; + break; + default: + spin_unlock_irqrestore(&dev->spinlock, flags); + comedi_error(dev, "error setting dac pacer source"); + return -1; + break; + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + async->inttrig = cb_pcidas_ao_inttrig; + + return 0; +} + +static int cb_pcidas_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trig_num) +{ + unsigned int num_bytes, num_points = thisboard->fifo_size; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + + if (trig_num != 0) + return -EINVAL; + + /* load up fifo */ + if (cmd->stop_src == TRIG_COUNT && devpriv->ao_count < num_points) + num_points = devpriv->ao_count; + + num_bytes = cfc_read_array_from_buffer(s, devpriv->ao_buffer, + num_points * sizeof(short)); + num_points = num_bytes / sizeof(short); + + if (cmd->stop_src == TRIG_COUNT) + devpriv->ao_count -= num_points; + /* write data to board's fifo */ + outsw(devpriv->ao_registers + DACDATA, devpriv->ao_buffer, num_bytes); + + /* enable dac half-full and empty interrupts */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->adc_fifo_bits |= DAEMIE | DAHFIE; +#ifdef CB_PCIDAS_DEBUG + dev_dbg(dev->hw_dev, "comedi: adc_fifo_bits are 0x%x\n", + devpriv->adc_fifo_bits); +#endif + /* enable and clear interrupts */ + outw(devpriv->adc_fifo_bits | DAEMI | DAHFI, + devpriv->control_status + INT_ADCFIFO); + + /* start dac */ + devpriv->ao_control_bits |= DAC_START | DACEN | DAC_EMPTY; + outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR); +#ifdef CB_PCIDAS_DEBUG + dev_dbg(dev->hw_dev, "comedi: sent 0x%x to dac control\n", + devpriv->ao_control_bits); +#endif + spin_unlock_irqrestore(&dev->spinlock, flags); + + async->inttrig = NULL; + + return 0; +} + +static irqreturn_t cb_pcidas_interrupt(int irq, void *d) +{ + struct comedi_device *dev = (struct comedi_device *)d; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + int status, s5933_status; + int half_fifo = thisboard->fifo_size / 2; + unsigned int num_samples, i; + static const int timeout = 10000; + unsigned long flags; + + if (dev->attached == 0) + return IRQ_NONE; + + async = s->async; + async->events = 0; + + s5933_status = inl(devpriv->s5933_config + AMCC_OP_REG_INTCSR); +#ifdef CB_PCIDAS_DEBUG + dev_dbg(dev->hw_dev, "intcsr 0x%x\n", s5933_status); + dev_dbg(dev->hw_dev, "mbef 0x%x\n", + inl(devpriv->s5933_config + AMCC_OP_REG_MBEF)); +#endif + + if ((INTCSR_INTR_ASSERTED & s5933_status) == 0) + return IRQ_NONE; + + /* make sure mailbox 4 is empty */ + inl_p(devpriv->s5933_config + AMCC_OP_REG_IMB4); + /* clear interrupt on amcc s5933 */ + outl(devpriv->s5933_intcsr_bits | INTCSR_INBOX_INTR_STATUS, + devpriv->s5933_config + AMCC_OP_REG_INTCSR); + + status = inw(devpriv->control_status + INT_ADCFIFO); +#ifdef CB_PCIDAS_DEBUG + if ((status & (INT | EOAI | LADFUL | DAHFI | DAEMI)) == 0) + comedi_error(dev, "spurious interrupt"); +#endif + + /* check for analog output interrupt */ + if (status & (DAHFI | DAEMI)) + handle_ao_interrupt(dev, status); + /* check for analog input interrupts */ + /* if fifo half-full */ + if (status & ADHFI) { + /* read data */ + num_samples = half_fifo; + if (async->cmd.stop_src == TRIG_COUNT && + num_samples > devpriv->count) { + num_samples = devpriv->count; + } + insw(devpriv->adc_fifo + ADCDATA, devpriv->ai_buffer, + num_samples); + cfc_write_array_to_buffer(s, devpriv->ai_buffer, + num_samples * sizeof(short)); + devpriv->count -= num_samples; + if (async->cmd.stop_src == TRIG_COUNT && devpriv->count == 0) { + async->events |= COMEDI_CB_EOA; + cb_pcidas_cancel(dev, s); + } + /* clear half-full interrupt latch */ + spin_lock_irqsave(&dev->spinlock, flags); + outw(devpriv->adc_fifo_bits | INT, + devpriv->control_status + INT_ADCFIFO); + spin_unlock_irqrestore(&dev->spinlock, flags); + /* else if fifo not empty */ + } else if (status & (ADNEI | EOBI)) { + for (i = 0; i < timeout; i++) { + /* break if fifo is empty */ + if ((ADNE & inw(devpriv->control_status + + INT_ADCFIFO)) == 0) + break; + cfc_write_to_buffer(s, inw(devpriv->adc_fifo)); + if (async->cmd.stop_src == TRIG_COUNT && --devpriv->count == 0) { /* end of acquisition */ + cb_pcidas_cancel(dev, s); + async->events |= COMEDI_CB_EOA; + break; + } + } + /* clear not-empty interrupt latch */ + spin_lock_irqsave(&dev->spinlock, flags); + outw(devpriv->adc_fifo_bits | INT, + devpriv->control_status + INT_ADCFIFO); + spin_unlock_irqrestore(&dev->spinlock, flags); + } else if (status & EOAI) { + comedi_error(dev, + "bug! encountered end of acquisition interrupt?"); + /* clear EOA interrupt latch */ + spin_lock_irqsave(&dev->spinlock, flags); + outw(devpriv->adc_fifo_bits | EOAI, + devpriv->control_status + INT_ADCFIFO); + spin_unlock_irqrestore(&dev->spinlock, flags); + } + /* check for fifo overflow */ + if (status & LADFUL) { + comedi_error(dev, "fifo overflow"); + /* clear overflow interrupt latch */ + spin_lock_irqsave(&dev->spinlock, flags); + outw(devpriv->adc_fifo_bits | LADFUL, + devpriv->control_status + INT_ADCFIFO); + spin_unlock_irqrestore(&dev->spinlock, flags); + cb_pcidas_cancel(dev, s); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + } + + comedi_event(dev, s); + + return IRQ_HANDLED; +} + +static void handle_ao_interrupt(struct comedi_device *dev, unsigned int status) +{ + struct comedi_subdevice *s = dev->write_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int half_fifo = thisboard->fifo_size / 2; + unsigned int num_points; + unsigned long flags; + + async->events = 0; + + if (status & DAEMI) { + /* clear dac empty interrupt latch */ + spin_lock_irqsave(&dev->spinlock, flags); + outw(devpriv->adc_fifo_bits | DAEMI, + devpriv->control_status + INT_ADCFIFO); + spin_unlock_irqrestore(&dev->spinlock, flags); + if (inw(devpriv->ao_registers + DAC_CSR) & DAC_EMPTY) { + if (cmd->stop_src == TRIG_NONE || + (cmd->stop_src == TRIG_COUNT + && devpriv->ao_count)) { + comedi_error(dev, "dac fifo underflow"); + cb_pcidas_ao_cancel(dev, s); + async->events |= COMEDI_CB_ERROR; + } + async->events |= COMEDI_CB_EOA; + } + } else if (status & DAHFI) { + unsigned int num_bytes; + + /* figure out how many points we are writing to fifo */ + num_points = half_fifo; + if (cmd->stop_src == TRIG_COUNT && + devpriv->ao_count < num_points) + num_points = devpriv->ao_count; + num_bytes = + cfc_read_array_from_buffer(s, devpriv->ao_buffer, + num_points * sizeof(short)); + num_points = num_bytes / sizeof(short); + + if (async->cmd.stop_src == TRIG_COUNT) + devpriv->ao_count -= num_points; + /* write data to board's fifo */ + outsw(devpriv->ao_registers + DACDATA, devpriv->ao_buffer, + num_points); + /* clear half-full interrupt latch */ + spin_lock_irqsave(&dev->spinlock, flags); + outw(devpriv->adc_fifo_bits | DAHFI, + devpriv->control_status + INT_ADCFIFO); + spin_unlock_irqrestore(&dev->spinlock, flags); + } + + comedi_event(dev, s); +} + +/* cancel analog input command */ +static int cb_pcidas_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + /* disable interrupts */ + devpriv->adc_fifo_bits &= ~INTE & ~EOAIE; + outw(devpriv->adc_fifo_bits, devpriv->control_status + INT_ADCFIFO); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* disable start trigger source and burst mode */ + outw(0, devpriv->control_status + TRIG_CONTSTAT); + /* software pacer source */ + outw(0, devpriv->control_status + ADCMUX_CONT); + + return 0; +} + +/* cancel analog output command */ +static int cb_pcidas_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + /* disable interrupts */ + devpriv->adc_fifo_bits &= ~DAHFIE & ~DAEMIE; + outw(devpriv->adc_fifo_bits, devpriv->control_status + INT_ADCFIFO); + + /* disable output */ + devpriv->ao_control_bits &= ~DACEN & ~DAC_PACER_MASK; + outw(devpriv->ao_control_bits, devpriv->control_status + DAC_CSR); + spin_unlock_irqrestore(&dev->spinlock, flags); + + return 0; +} + +static void cb_pcidas_load_counters(struct comedi_device *dev, unsigned int *ns, + int rounding_flags) +{ + i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1), + &(devpriv->divisor2), ns, + rounding_flags & TRIG_ROUND_MASK); + + /* Write the values of ctr1 and ctr2 into counters 1 and 2 */ + i8254_load(devpriv->pacer_counter_dio + ADC8254, 0, 1, + devpriv->divisor1, 2); + i8254_load(devpriv->pacer_counter_dio + ADC8254, 0, 2, + devpriv->divisor2, 2); +} + +static void write_calibration_bitstream(struct comedi_device *dev, + unsigned int register_bits, + unsigned int bitstream, + unsigned int bitstream_length) +{ + static const int write_delay = 1; + unsigned int bit; + + for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) { + if (bitstream & bit) + register_bits |= SERIAL_DATA_IN_BIT; + else + register_bits &= ~SERIAL_DATA_IN_BIT; + udelay(write_delay); + outw(register_bits, devpriv->control_status + CALIBRATION_REG); + } +} + +static int caldac_8800_write(struct comedi_device *dev, unsigned int address, + uint8_t value) +{ + static const int num_caldac_channels = 8; + static const int bitstream_length = 11; + unsigned int bitstream = ((address & 0x7) << 8) | value; + static const int caldac_8800_udelay = 1; + + if (address >= num_caldac_channels) { + comedi_error(dev, "illegal caldac channel"); + return -1; + } + + if (value == devpriv->caldac_value[address]) + return 1; + + devpriv->caldac_value[address] = value; + + write_calibration_bitstream(dev, cal_enable_bits(dev), bitstream, + bitstream_length); + + udelay(caldac_8800_udelay); + outw(cal_enable_bits(dev) | SELECT_8800_BIT, + devpriv->control_status + CALIBRATION_REG); + udelay(caldac_8800_udelay); + outw(cal_enable_bits(dev), devpriv->control_status + CALIBRATION_REG); + + return 1; +} + +static int trimpot_7376_write(struct comedi_device *dev, uint8_t value) +{ + static const int bitstream_length = 7; + unsigned int bitstream = value & 0x7f; + unsigned int register_bits; + static const int ad7376_udelay = 1; + + register_bits = cal_enable_bits(dev) | SELECT_TRIMPOT_BIT; + udelay(ad7376_udelay); + outw(register_bits, devpriv->control_status + CALIBRATION_REG); + + write_calibration_bitstream(dev, register_bits, bitstream, + bitstream_length); + + udelay(ad7376_udelay); + outw(cal_enable_bits(dev), devpriv->control_status + CALIBRATION_REG); + + return 0; +} + +/* For 1602/16 only + * ch 0 : adc gain + * ch 1 : adc postgain offset */ +static int trimpot_8402_write(struct comedi_device *dev, unsigned int channel, + uint8_t value) +{ + static const int bitstream_length = 10; + unsigned int bitstream = ((channel & 0x3) << 8) | (value & 0xff); + unsigned int register_bits; + static const int ad8402_udelay = 1; + + register_bits = cal_enable_bits(dev) | SELECT_TRIMPOT_BIT; + udelay(ad8402_udelay); + outw(register_bits, devpriv->control_status + CALIBRATION_REG); + + write_calibration_bitstream(dev, register_bits, bitstream, + bitstream_length); + + udelay(ad8402_udelay); + outw(cal_enable_bits(dev), devpriv->control_status + CALIBRATION_REG); + + return 0; +} + +static int wait_for_nvram_ready(unsigned long s5933_base_addr) +{ + static const int timeout = 1000; + unsigned int i; + + for (i = 0; i < timeout; i++) { + if ((inb(s5933_base_addr + + AMCC_OP_REG_MCSR_NVCMD) & MCSR_NV_BUSY) + == 0) + return 0; + udelay(1); + } + return -1; +} + +static int nvram_read(struct comedi_device *dev, unsigned int address, + uint8_t *data) +{ + unsigned long iobase = devpriv->s5933_config; + + if (wait_for_nvram_ready(iobase) < 0) + return -ETIMEDOUT; + + outb(MCSR_NV_ENABLE | MCSR_NV_LOAD_LOW_ADDR, + iobase + AMCC_OP_REG_MCSR_NVCMD); + outb(address & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA); + outb(MCSR_NV_ENABLE | MCSR_NV_LOAD_HIGH_ADDR, + iobase + AMCC_OP_REG_MCSR_NVCMD); + outb((address >> 8) & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA); + outb(MCSR_NV_ENABLE | MCSR_NV_READ, iobase + AMCC_OP_REG_MCSR_NVCMD); + + if (wait_for_nvram_ready(iobase) < 0) + return -ETIMEDOUT; + + *data = inb(iobase + AMCC_OP_REG_MCSR_NVDATA); + + return 0; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __devinit driver_cb_pcidas_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_cb_pcidas.driver_name); +} + +static void __devexit driver_cb_pcidas_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_cb_pcidas_pci_driver = { + .id_table = cb_pcidas_pci_table, + .probe = &driver_cb_pcidas_pci_probe, + .remove = __devexit_p(&driver_cb_pcidas_pci_remove) +}; + +static int __init driver_cb_pcidas_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_cb_pcidas); + if (retval < 0) + return retval; + + driver_cb_pcidas_pci_driver.name = (char *)driver_cb_pcidas.driver_name; + return pci_register_driver(&driver_cb_pcidas_pci_driver); +} + +static void __exit driver_cb_pcidas_cleanup_module(void) +{ + pci_unregister_driver(&driver_cb_pcidas_pci_driver); + comedi_driver_unregister(&driver_cb_pcidas); +} + +module_init(driver_cb_pcidas_init_module); +module_exit(driver_cb_pcidas_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/cb_pcidas64.c b/drivers/staging/comedi/drivers/cb_pcidas64.c new file mode 100644 index 00000000..915157d4 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcidas64.c @@ -0,0 +1,4320 @@ +/* + comedi/drivers/cb_pcidas64.c + This is a driver for the ComputerBoards/MeasurementComputing PCI-DAS + 64xx, 60xx, and 4020 cards. + + Author: Frank Mori Hess + Copyright (C) 2001, 2002 Frank Mori Hess + + Thanks also go to the following people: + + Steve Rosenbluth, for providing the source code for + his pci-das6402 driver, and source code for working QNX pci-6402 + drivers by Greg Laird and Mariusz Bogacz. None of the code was + used directly here, but it was useful as an additional source of + documentation on how to program the boards. + + John Sims, for much testing and feedback on pcidas-4020 support. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-8 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************/ + +/* + +Driver: cb_pcidas64 +Description: MeasurementComputing PCI-DAS64xx, 60XX, and 4020 series with the PLX 9080 PCI controller +Author: Frank Mori Hess +Status: works +Updated: 2002-10-09 +Devices: [Measurement Computing] PCI-DAS6402/16 (cb_pcidas64), + PCI-DAS6402/12, PCI-DAS64/M1/16, PCI-DAS64/M2/16, + PCI-DAS64/M3/16, PCI-DAS6402/16/JR, PCI-DAS64/M1/16/JR, + PCI-DAS64/M2/16/JR, PCI-DAS64/M3/16/JR, PCI-DAS64/M1/14, + PCI-DAS64/M2/14, PCI-DAS64/M3/14, PCI-DAS6013, PCI-DAS6014, + PCI-DAS6023, PCI-DAS6025, PCI-DAS6030, + PCI-DAS6031, PCI-DAS6032, PCI-DAS6033, PCI-DAS6034, + PCI-DAS6035, PCI-DAS6036, PCI-DAS6040, PCI-DAS6052, + PCI-DAS6070, PCI-DAS6071, PCI-DAS4020/12 + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + +These boards may be autocalibrated with the comedi_calibrate utility. + +To select the bnc trigger input on the 4020 (instead of the dio input), +specify a nonzero channel in the chanspec. If you wish to use an external +master clock on the 4020, you may do so by setting the scan_begin_src +to TRIG_OTHER, and using an INSN_CONFIG_TIMER_1 configuration insn +to configure the divisor to use for the external clock. + +Some devices are not identified because the PCI device IDs are not yet +known. If you have such a board, please file a bug report at +https://bugs.comedi.org. + +*/ + +/* + +TODO: + make it return error if user attempts an ai command that uses the + external queue, and an ao command simultaneously + user counter subdevice + there are a number of boards this driver will support when they are + fully released, but does not yet since the pci device id numbers + are not yet available. + support prescaled 100khz clock for slow pacing (not available on 6000 series?) + make ao fifo size adjustable like ai fifo +*/ + +#include "../comedidev.h" +#include +#include + +#include "comedi_pci.h" +#include "8253.h" +#include "8255.h" +#include "plx9080.h" +#include "comedi_fc.h" + +#undef PCIDAS64_DEBUG /* disable debugging code */ +/* #define PCIDAS64_DEBUG enable debugging code */ + +#ifdef PCIDAS64_DEBUG +#define DEBUG_PRINT(format, args...) printk(format , ## args) +#else +#define DEBUG_PRINT(format, args...) +#endif + +#define TIMER_BASE 25 /* 40MHz master clock */ +#define PRESCALED_TIMER_BASE 10000 /* 100kHz 'prescaled' clock for slow acquisition, maybe I'll support this someday */ +#define DMA_BUFFER_SIZE 0x1000 + +#define PCI_VENDOR_ID_COMPUTERBOARDS 0x1307 + +/* maximum value that can be loaded into board's 24-bit counters*/ +static const int max_counter_value = 0xffffff; + +/* PCI-DAS64xxx base addresses */ + +/* indices of base address regions */ +enum base_address_regions { + PLX9080_BADDRINDEX = 0, + MAIN_BADDRINDEX = 2, + DIO_COUNTER_BADDRINDEX = 3, +}; + +/* priv(dev)->main_iobase registers */ +enum write_only_registers { + INTR_ENABLE_REG = 0x0, /* interrupt enable register */ + HW_CONFIG_REG = 0x2, /* hardware config register */ + DAQ_SYNC_REG = 0xc, + DAQ_ATRIG_LOW_4020_REG = 0xc, + ADC_CONTROL0_REG = 0x10, /* adc control register 0 */ + ADC_CONTROL1_REG = 0x12, /* adc control register 1 */ + CALIBRATION_REG = 0x14, + ADC_SAMPLE_INTERVAL_LOWER_REG = 0x16, /* lower 16 bits of adc sample interval counter */ + ADC_SAMPLE_INTERVAL_UPPER_REG = 0x18, /* upper 8 bits of adc sample interval counter */ + ADC_DELAY_INTERVAL_LOWER_REG = 0x1a, /* lower 16 bits of delay interval counter */ + ADC_DELAY_INTERVAL_UPPER_REG = 0x1c, /* upper 8 bits of delay interval counter */ + ADC_COUNT_LOWER_REG = 0x1e, /* lower 16 bits of hardware conversion/scan counter */ + ADC_COUNT_UPPER_REG = 0x20, /* upper 8 bits of hardware conversion/scan counter */ + ADC_START_REG = 0x22, /* software trigger to start acquisition */ + ADC_CONVERT_REG = 0x24, /* initiates single conversion */ + ADC_QUEUE_CLEAR_REG = 0x26, /* clears adc queue */ + ADC_QUEUE_LOAD_REG = 0x28, /* loads adc queue */ + ADC_BUFFER_CLEAR_REG = 0x2a, + ADC_QUEUE_HIGH_REG = 0x2c, /* high channel for internal queue, use adc_chan_bits() inline above */ + DAC_CONTROL0_REG = 0x50, /* dac control register 0 */ + DAC_CONTROL1_REG = 0x52, /* dac control register 0 */ + DAC_SAMPLE_INTERVAL_LOWER_REG = 0x54, /* lower 16 bits of dac sample interval counter */ + DAC_SAMPLE_INTERVAL_UPPER_REG = 0x56, /* upper 8 bits of dac sample interval counter */ + DAC_SELECT_REG = 0x60, + DAC_START_REG = 0x64, + DAC_BUFFER_CLEAR_REG = 0x66, /* clear dac buffer */ +}; +static inline unsigned int dac_convert_reg(unsigned int channel) +{ + return 0x70 + (2 * (channel & 0x1)); +} + +static inline unsigned int dac_lsb_4020_reg(unsigned int channel) +{ + return 0x70 + (4 * (channel & 0x1)); +} + +static inline unsigned int dac_msb_4020_reg(unsigned int channel) +{ + return 0x72 + (4 * (channel & 0x1)); +} + +enum read_only_registers { + HW_STATUS_REG = 0x0, /* hardware status register, reading this apparently clears pending interrupts as well */ + PIPE1_READ_REG = 0x4, + ADC_READ_PNTR_REG = 0x8, + LOWER_XFER_REG = 0x10, + ADC_WRITE_PNTR_REG = 0xc, + PREPOST_REG = 0x14, +}; + +enum read_write_registers { + I8255_4020_REG = 0x48, /* 8255 offset, for 4020 only */ + ADC_QUEUE_FIFO_REG = 0x100, /* external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG */ + ADC_FIFO_REG = 0x200, /* adc data fifo */ + DAC_FIFO_REG = 0x300, /* dac data fifo, has weird interactions with external channel queue */ +}; + +/* priv(dev)->dio_counter_iobase registers */ +enum dio_counter_registers { + DIO_8255_OFFSET = 0x0, + DO_REG = 0x20, + DI_REG = 0x28, + DIO_DIRECTION_60XX_REG = 0x40, + DIO_DATA_60XX_REG = 0x48, +}; + +/* bit definitions for write-only registers */ + +enum intr_enable_contents { + ADC_INTR_SRC_MASK = 0x3, /* bits that set adc interrupt source */ + ADC_INTR_QFULL_BITS = 0x0, /* interrupt fifo quater full */ + ADC_INTR_EOC_BITS = 0x1, /* interrupt end of conversion */ + ADC_INTR_EOSCAN_BITS = 0x2, /* interrupt end of scan */ + ADC_INTR_EOSEQ_BITS = 0x3, /* interrupt end of sequence (probably wont use this it's pretty fancy) */ + EN_ADC_INTR_SRC_BIT = 0x4, /* enable adc interrupt source */ + EN_ADC_DONE_INTR_BIT = 0x8, /* enable adc acquisition done interrupt */ + DAC_INTR_SRC_MASK = 0x30, + DAC_INTR_QEMPTY_BITS = 0x0, + DAC_INTR_HIGH_CHAN_BITS = 0x10, + EN_DAC_INTR_SRC_BIT = 0x40, /* enable dac interrupt source */ + EN_DAC_DONE_INTR_BIT = 0x80, + EN_ADC_ACTIVE_INTR_BIT = 0x200, /* enable adc active interrupt */ + EN_ADC_STOP_INTR_BIT = 0x400, /* enable adc stop trigger interrupt */ + EN_DAC_ACTIVE_INTR_BIT = 0x800, /* enable dac active interrupt */ + EN_DAC_UNDERRUN_BIT = 0x4000, /* enable dac underrun status bit */ + EN_ADC_OVERRUN_BIT = 0x8000, /* enable adc overrun status bit */ +}; + +enum hw_config_contents { + MASTER_CLOCK_4020_MASK = 0x3, /* bits that specify master clock source for 4020 */ + INTERNAL_CLOCK_4020_BITS = 0x1, /* use 40 MHz internal master clock for 4020 */ + BNC_CLOCK_4020_BITS = 0x2, /* use BNC input for master clock */ + EXT_CLOCK_4020_BITS = 0x3, /* use dio input for master clock */ + EXT_QUEUE_BIT = 0x200, /* use external channel/gain queue (more versatile than internal queue) */ + SLOW_DAC_BIT = 0x400, /* use 225 nanosec strobe when loading dac instead of 50 nanosec */ + HW_CONFIG_DUMMY_BITS = 0x2000, /* bit with unknown function yet given as default value in pci-das64 manual */ + DMA_CH_SELECT_BIT = 0x8000, /* bit selects channels 1/0 for analog input/output, otherwise 0/1 */ + FIFO_SIZE_REG = 0x4, /* allows adjustment of fifo sizes */ + DAC_FIFO_SIZE_MASK = 0xff00, /* bits that set dac fifo size */ + DAC_FIFO_BITS = 0xf800, /* 8k sample ao fifo */ +}; +#define DAC_FIFO_SIZE 0x2000 + +enum daq_atrig_low_4020_contents { + EXT_AGATE_BNC_BIT = 0x8000, /* use trig/ext clk bnc input for analog gate signal */ + EXT_STOP_TRIG_BNC_BIT = 0x4000, /* use trig/ext clk bnc input for external stop trigger signal */ + EXT_START_TRIG_BNC_BIT = 0x2000, /* use trig/ext clk bnc input for external start trigger signal */ +}; +static inline uint16_t analog_trig_low_threshold_bits(uint16_t threshold) +{ + return threshold & 0xfff; +} + +enum adc_control0_contents { + ADC_GATE_SRC_MASK = 0x3, /* bits that select gate */ + ADC_SOFT_GATE_BITS = 0x1, /* software gate */ + ADC_EXT_GATE_BITS = 0x2, /* external digital gate */ + ADC_ANALOG_GATE_BITS = 0x3, /* analog level gate */ + ADC_GATE_LEVEL_BIT = 0x4, /* level-sensitive gate (for digital) */ + ADC_GATE_POLARITY_BIT = 0x8, /* gate active low */ + ADC_START_TRIG_SOFT_BITS = 0x10, + ADC_START_TRIG_EXT_BITS = 0x20, + ADC_START_TRIG_ANALOG_BITS = 0x30, + ADC_START_TRIG_MASK = 0x30, + ADC_START_TRIG_FALLING_BIT = 0x40, /* trig 1 uses falling edge */ + ADC_EXT_CONV_FALLING_BIT = 0x800, /* external pacing uses falling edge */ + ADC_SAMPLE_COUNTER_EN_BIT = 0x1000, /* enable hardware scan counter */ + ADC_DMA_DISABLE_BIT = 0x4000, /* disables dma */ + ADC_ENABLE_BIT = 0x8000, /* master adc enable */ +}; + +enum adc_control1_contents { + ADC_QUEUE_CONFIG_BIT = 0x1, /* should be set for boards with > 16 channels */ + CONVERT_POLARITY_BIT = 0x10, + EOC_POLARITY_BIT = 0x20, + ADC_SW_GATE_BIT = 0x40, /* software gate of adc */ + ADC_DITHER_BIT = 0x200, /* turn on extra noise for dithering */ + RETRIGGER_BIT = 0x800, + ADC_LO_CHANNEL_4020_MASK = 0x300, + ADC_HI_CHANNEL_4020_MASK = 0xc00, + TWO_CHANNEL_4020_BITS = 0x1000, /* two channel mode for 4020 */ + FOUR_CHANNEL_4020_BITS = 0x2000, /* four channel mode for 4020 */ + CHANNEL_MODE_4020_MASK = 0x3000, + ADC_MODE_MASK = 0xf000, +}; +static inline uint16_t adc_lo_chan_4020_bits(unsigned int channel) +{ + return (channel & 0x3) << 8; +}; + +static inline uint16_t adc_hi_chan_4020_bits(unsigned int channel) +{ + return (channel & 0x3) << 10; +}; + +static inline uint16_t adc_mode_bits(unsigned int mode) +{ + return (mode & 0xf) << 12; +}; + +enum calibration_contents { + SELECT_8800_BIT = 0x1, + SELECT_8402_64XX_BIT = 0x2, + SELECT_1590_60XX_BIT = 0x2, + CAL_EN_64XX_BIT = 0x40, /* calibration enable for 64xx series */ + SERIAL_DATA_IN_BIT = 0x80, + SERIAL_CLOCK_BIT = 0x100, + CAL_EN_60XX_BIT = 0x200, /* calibration enable for 60xx series */ + CAL_GAIN_BIT = 0x800, +}; +/* calibration sources for 6025 are: + * 0 : ground + * 1 : 10V + * 2 : 5V + * 3 : 0.5V + * 4 : 0.05V + * 5 : ground + * 6 : dac channel 0 + * 7 : dac channel 1 + */ +static inline uint16_t adc_src_bits(unsigned int source) +{ + return (source & 0xf) << 3; +}; + +static inline uint16_t adc_convert_chan_4020_bits(unsigned int channel) +{ + return (channel & 0x3) << 8; +}; + +enum adc_queue_load_contents { + UNIP_BIT = 0x800, /* unipolar/bipolar bit */ + ADC_SE_DIFF_BIT = 0x1000, /* single-ended/ differential bit */ + ADC_COMMON_BIT = 0x2000, /* non-referenced single-ended (common-mode input) */ + QUEUE_EOSEQ_BIT = 0x4000, /* queue end of sequence */ + QUEUE_EOSCAN_BIT = 0x8000, /* queue end of scan */ +}; +static inline uint16_t adc_chan_bits(unsigned int channel) +{ + return channel & 0x3f; +}; + +enum dac_control0_contents { + DAC_ENABLE_BIT = 0x8000, /* dac controller enable bit */ + DAC_CYCLIC_STOP_BIT = 0x4000, + DAC_WAVEFORM_MODE_BIT = 0x100, + DAC_EXT_UPDATE_FALLING_BIT = 0x80, + DAC_EXT_UPDATE_ENABLE_BIT = 0x40, + WAVEFORM_TRIG_MASK = 0x30, + WAVEFORM_TRIG_DISABLED_BITS = 0x0, + WAVEFORM_TRIG_SOFT_BITS = 0x10, + WAVEFORM_TRIG_EXT_BITS = 0x20, + WAVEFORM_TRIG_ADC1_BITS = 0x30, + WAVEFORM_TRIG_FALLING_BIT = 0x8, + WAVEFORM_GATE_LEVEL_BIT = 0x4, + WAVEFORM_GATE_ENABLE_BIT = 0x2, + WAVEFORM_GATE_SELECT_BIT = 0x1, +}; + +enum dac_control1_contents { + DAC_WRITE_POLARITY_BIT = 0x800, /* board-dependent setting */ + DAC1_EXT_REF_BIT = 0x200, + DAC0_EXT_REF_BIT = 0x100, + DAC_OUTPUT_ENABLE_BIT = 0x80, /* dac output enable bit */ + DAC_UPDATE_POLARITY_BIT = 0x40, /* board-dependent setting */ + DAC_SW_GATE_BIT = 0x20, + DAC1_UNIPOLAR_BIT = 0x8, + DAC0_UNIPOLAR_BIT = 0x2, +}; + +/* bit definitions for read-only registers */ +enum hw_status_contents { + DAC_UNDERRUN_BIT = 0x1, + ADC_OVERRUN_BIT = 0x2, + DAC_ACTIVE_BIT = 0x4, + ADC_ACTIVE_BIT = 0x8, + DAC_INTR_PENDING_BIT = 0x10, + ADC_INTR_PENDING_BIT = 0x20, + DAC_DONE_BIT = 0x40, + ADC_DONE_BIT = 0x80, + EXT_INTR_PENDING_BIT = 0x100, + ADC_STOP_BIT = 0x200, +}; +static inline uint16_t pipe_full_bits(uint16_t hw_status_bits) +{ + return (hw_status_bits >> 10) & 0x3; +}; + +static inline unsigned int dma_chain_flag_bits(uint16_t prepost_bits) +{ + return (prepost_bits >> 6) & 0x3; +} + +static inline unsigned int adc_upper_read_ptr_code(uint16_t prepost_bits) +{ + return (prepost_bits >> 12) & 0x3; +} + +static inline unsigned int adc_upper_write_ptr_code(uint16_t prepost_bits) +{ + return (prepost_bits >> 14) & 0x3; +} + +/* I2C addresses for 4020 */ +enum i2c_addresses { + RANGE_CAL_I2C_ADDR = 0x20, + CALDAC0_I2C_ADDR = 0xc, + CALDAC1_I2C_ADDR = 0xd, +}; + +enum range_cal_i2c_contents { + ADC_SRC_4020_MASK = 0x70, /* bits that set what source the adc converter measures */ + BNC_TRIG_THRESHOLD_0V_BIT = 0x80, /* make bnc trig/ext clock threshold 0V instead of 2.5V */ +}; +static inline uint8_t adc_src_4020_bits(unsigned int source) +{ + return (source << 4) & ADC_SRC_4020_MASK; +}; + +static inline uint8_t attenuate_bit(unsigned int channel) +{ + /* attenuate channel (+-5V input range) */ + return 1 << (channel & 0x3); +}; + +/* analog input ranges for 64xx boards */ +static const struct comedi_lrange ai_ranges_64xx = { + 8, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +/* analog input ranges for 60xx boards */ +static const struct comedi_lrange ai_ranges_60xx = { + 4, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + } +}; + +/* analog input ranges for 6030, etc boards */ +static const struct comedi_lrange ai_ranges_6030 = { + 14, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.2), + BIP_RANGE(0.1), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1), + } +}; + +/* analog input ranges for 6052, etc boards */ +static const struct comedi_lrange ai_ranges_6052 = { + 15, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.25), + BIP_RANGE(0.1), + BIP_RANGE(0.05), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1), + } +}; + +/* analog input ranges for 4020 board */ +static const struct comedi_lrange ai_ranges_4020 = { + 2, + { + BIP_RANGE(5), + BIP_RANGE(1), + } +}; + +/* analog output ranges */ +static const struct comedi_lrange ao_ranges_64xx = { + 4, + { + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(10), + } +}; + +static const int ao_range_code_64xx[] = { + 0x0, + 0x1, + 0x2, + 0x3, +}; + +static const struct comedi_lrange ao_ranges_60xx = { + 1, + { + BIP_RANGE(10), + } +}; + +static const int ao_range_code_60xx[] = { + 0x0, +}; + +static const struct comedi_lrange ao_ranges_6030 = { + 2, + { + BIP_RANGE(10), + UNI_RANGE(10), + } +}; + +static const int ao_range_code_6030[] = { + 0x0, + 0x2, +}; + +static const struct comedi_lrange ao_ranges_4020 = { + 2, + { + BIP_RANGE(5), + BIP_RANGE(10), + } +}; + +static const int ao_range_code_4020[] = { + 0x1, + 0x0, +}; + +enum register_layout { + LAYOUT_60XX, + LAYOUT_64XX, + LAYOUT_4020, +}; + +struct hw_fifo_info { + unsigned int num_segments; + unsigned int max_segment_length; + unsigned int sample_packing_ratio; + uint16_t fifo_size_reg_mask; +}; + +struct pcidas64_board { + const char *name; + int device_id; /* pci device id */ + int ai_se_chans; /* number of ai inputs in single-ended mode */ + int ai_bits; /* analog input resolution */ + int ai_speed; /* fastest conversion period in ns */ + const struct comedi_lrange *ai_range_table; + int ao_nchan; /* number of analog out channels */ + int ao_bits; /* analog output resolution */ + int ao_scan_speed; /* analog output speed (for a scan, not conversion) */ + const struct comedi_lrange *ao_range_table; + const int *ao_range_code; + const struct hw_fifo_info *const ai_fifo; + enum register_layout layout; /* different board families have slightly different registers */ + unsigned has_8255:1; +}; + +static const struct hw_fifo_info ai_fifo_4020 = { + .num_segments = 2, + .max_segment_length = 0x8000, + .sample_packing_ratio = 2, + .fifo_size_reg_mask = 0x7f, +}; + +static const struct hw_fifo_info ai_fifo_64xx = { + .num_segments = 4, + .max_segment_length = 0x800, + .sample_packing_ratio = 1, + .fifo_size_reg_mask = 0x3f, +}; + +static const struct hw_fifo_info ai_fifo_60xx = { + .num_segments = 4, + .max_segment_length = 0x800, + .sample_packing_ratio = 1, + .fifo_size_reg_mask = 0x7f, +}; + +/* maximum number of dma transfers we will chain together into a ring + * (and the maximum number of dma buffers we maintain) */ +#define MAX_AI_DMA_RING_COUNT (0x80000 / DMA_BUFFER_SIZE) +#define MIN_AI_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE) +#define AO_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE) +static inline unsigned int ai_dma_ring_count(struct pcidas64_board *board) +{ + if (board->layout == LAYOUT_4020) + return MAX_AI_DMA_RING_COUNT; + else + return MIN_AI_DMA_RING_COUNT; +} + +static const int bytes_in_sample = 2; + +static const struct pcidas64_board pcidas64_boards[] = { + { + .name = "pci-das6402/16", + .device_id = 0x1d, + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das6402/12", /* XXX check */ + .device_id = 0x1e, + .ai_se_chans = 64, + .ai_bits = 12, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das64/m1/16", + .device_id = 0x35, + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 1000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das64/m2/16", + .device_id = 0x36, + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 500, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das64/m3/16", + .device_id = 0x37, + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 333, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ao_range_table = &ao_ranges_64xx, + .ao_range_code = ao_range_code_64xx, + .ai_fifo = &ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das6013", + .device_id = 0x78, + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 0, + .ao_bits = 16, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ao_range_table = &ao_ranges_60xx, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6014", + .device_id = 0x79, + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ao_range_table = &ao_ranges_60xx, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6023", + .device_id = 0x5d, + .ai_se_chans = 16, + .ai_bits = 12, + .ai_speed = 5000, + .ao_nchan = 0, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ao_range_table = &ao_ranges_60xx, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 1, + }, + { + .name = "pci-das6025", + .device_id = 0x5e, + .ai_se_chans = 16, + .ai_bits = 12, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ao_range_table = &ao_ranges_60xx, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 1, + }, + { + .name = "pci-das6030", + .device_id = 0x5f, + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 10000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6030, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6031", + .device_id = 0x60, + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 10000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 10000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6030, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6032", + .device_id = 0x61, + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 10000, + .ao_nchan = 0, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6033", + .device_id = 0x62, + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 10000, + .ao_nchan = 0, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6034", + .device_id = 0x63, + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 0, + .ao_scan_speed = 0, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6035", + .device_id = 0x64, + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ao_range_table = &ao_ranges_60xx, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6036", + .device_id = 0x6f, + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 100000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_60xx, + .ao_range_table = &ao_ranges_60xx, + .ao_range_code = ao_range_code_60xx, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6040", + .device_id = 0x65, + .ai_se_chans = 16, + .ai_bits = 12, + .ai_speed = 2000, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 1000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6052, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6052", + .device_id = 0x66, + .ai_se_chans = 16, + .ai_bits = 16, + .ai_speed = 3333, + .ao_nchan = 2, + .ao_bits = 16, + .ao_scan_speed = 3333, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6052, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6070", + .device_id = 0x67, + .ai_se_chans = 16, + .ai_bits = 12, + .ai_speed = 800, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 1000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6052, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das6071", + .device_id = 0x68, + .ai_se_chans = 64, + .ai_bits = 12, + .ai_speed = 800, + .ao_nchan = 2, + .ao_bits = 12, + .ao_scan_speed = 1000, + .layout = LAYOUT_60XX, + .ai_range_table = &ai_ranges_6052, + .ao_range_table = &ao_ranges_6030, + .ao_range_code = ao_range_code_6030, + .ai_fifo = &ai_fifo_60xx, + .has_8255 = 0, + }, + { + .name = "pci-das4020/12", + .device_id = 0x52, + .ai_se_chans = 4, + .ai_bits = 12, + .ai_speed = 50, + .ao_bits = 12, + .ao_nchan = 2, + .ao_scan_speed = 0, /* no hardware pacing on ao */ + .layout = LAYOUT_4020, + .ai_range_table = &ai_ranges_4020, + .ao_range_table = &ao_ranges_4020, + .ao_range_code = ao_range_code_4020, + .ai_fifo = &ai_fifo_4020, + .has_8255 = 1, + }, +#if 0 + { + .name = "pci-das6402/16/jr", + .device_id = 0 /* XXX, */ + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 5000, + .ao_nchan = 0, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das64/m1/16/jr", + .device_id = 0 /* XXX, */ + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 1000, + .ao_nchan = 0, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das64/m2/16/jr", + .device_id = 0 /* XXX, */ + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 500, + .ao_nchan = 0, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das64/m3/16/jr", + .device_id = 0 /* XXX, */ + .ai_se_chans = 64, + .ai_bits = 16, + .ai_speed = 333, + .ao_nchan = 0, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das64/m1/14", + .device_id = 0, /* XXX */ + .ai_se_chans = 64, + .ai_bits = 14, + .ai_speed = 1000, + .ao_nchan = 2, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das64/m2/14", + .device_id = 0, /* XXX */ + .ai_se_chans = 64, + .ai_bits = 14, + .ai_speed = 500, + .ao_nchan = 2, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, + { + .name = "pci-das64/m3/14", + .device_id = 0, /* XXX */ + .ai_se_chans = 64, + .ai_bits = 14, + .ai_speed = 333, + .ao_nchan = 2, + .ao_scan_speed = 10000, + .layout = LAYOUT_64XX, + .ai_range_table = &ai_ranges_64xx, + .ai_fifo = ai_fifo_64xx, + .has_8255 = 1, + }, +#endif +}; + +static DEFINE_PCI_DEVICE_TABLE(pcidas64_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x001d) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x001e) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0035) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0036) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0037) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0052) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x005d) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x005e) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x005f) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0061) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0062) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0063) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0064) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0066) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0067) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0068) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x006f) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0078) }, + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0079) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, pcidas64_pci_table); + +static inline struct pcidas64_board *board(const struct comedi_device *dev) +{ + return (struct pcidas64_board *)dev->board_ptr; +} + +static inline unsigned short se_diff_bit_6xxx(struct comedi_device *dev, + int use_differential) +{ + if ((board(dev)->layout == LAYOUT_64XX && !use_differential) || + (board(dev)->layout == LAYOUT_60XX && use_differential)) + return ADC_SE_DIFF_BIT; + else + return 0; +}; + +struct ext_clock_info { + unsigned int divisor; /* master clock divisor to use for scans with external master clock */ + unsigned int chanspec; /* chanspec for master clock input when used as scan begin src */ +}; + +/* this structure is for data unique to this hardware driver. */ +struct pcidas64_private { + + struct pci_dev *hw_dev; /* pointer to board's pci_dev struct */ + /* base addresses (physical) */ + resource_size_t plx9080_phys_iobase; + resource_size_t main_phys_iobase; + resource_size_t dio_counter_phys_iobase; + /* base addresses (ioremapped) */ + void __iomem *plx9080_iobase; + void __iomem *main_iobase; + void __iomem *dio_counter_iobase; + /* local address (used by dma controller) */ + uint32_t local0_iobase; + uint32_t local1_iobase; + volatile unsigned int ai_count; /* number of analog input samples remaining */ + uint16_t *ai_buffer[MAX_AI_DMA_RING_COUNT]; /* dma buffers for analog input */ + dma_addr_t ai_buffer_bus_addr[MAX_AI_DMA_RING_COUNT]; /* physical addresses of ai dma buffers */ + struct plx_dma_desc *ai_dma_desc; /* array of ai dma descriptors read by plx9080, allocated to get proper alignment */ + dma_addr_t ai_dma_desc_bus_addr; /* physical address of ai dma descriptor array */ + volatile unsigned int ai_dma_index; /* index of the ai dma descriptor/buffer that is currently being used */ + uint16_t *ao_buffer[AO_DMA_RING_COUNT]; /* dma buffers for analog output */ + dma_addr_t ao_buffer_bus_addr[AO_DMA_RING_COUNT]; /* physical addresses of ao dma buffers */ + struct plx_dma_desc *ao_dma_desc; + dma_addr_t ao_dma_desc_bus_addr; + volatile unsigned int ao_dma_index; /* keeps track of buffer where the next ao sample should go */ + volatile unsigned long ao_count; /* number of analog output samples remaining */ + volatile unsigned int ao_value[2]; /* remember what the analog outputs are set to, to allow readback */ + unsigned int hw_revision; /* stc chip hardware revision number */ + volatile unsigned int intr_enable_bits; /* last bits sent to INTR_ENABLE_REG register */ + volatile uint16_t adc_control1_bits; /* last bits sent to ADC_CONTROL1_REG register */ + volatile uint16_t fifo_size_bits; /* last bits sent to FIFO_SIZE_REG register */ + volatile uint16_t hw_config_bits; /* last bits sent to HW_CONFIG_REG register */ + volatile uint16_t dac_control1_bits; + volatile uint32_t plx_control_bits; /* last bits written to plx9080 control register */ + volatile uint32_t plx_intcsr_bits; /* last bits written to plx interrupt control and status register */ + volatile int calibration_source; /* index of calibration source readable through ai ch0 */ + volatile uint8_t i2c_cal_range_bits; /* bits written to i2c calibration/range register */ + volatile unsigned int ext_trig_falling; /* configure digital triggers to trigger on falling edge */ + /* states of various devices stored to enable read-back */ + unsigned int ad8402_state[2]; + unsigned int caldac_state[8]; + volatile short ai_cmd_running; + unsigned int ai_fifo_segment_length; + struct ext_clock_info ext_clock; + short ao_bounce_buffer[DAC_FIFO_SIZE]; +}; + +/* inline function that makes it easier to + * access the private structure. + */ +static inline struct pcidas64_private *priv(struct comedi_device *dev) +{ + return dev->private; +} + +/* + * The comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int detach(struct comedi_device *dev); +static struct comedi_driver driver_cb_pcidas = { + .driver_name = "cb_pcidas64", + .module = THIS_MODULE, + .attach = attach, + .detach = detach, +}; + +static int ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ao_readback_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *subdev, unsigned int trig_num); +static int ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static irqreturn_t handle_interrupt(int irq, void *d); +static int ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s); +static int ao_cancel(struct comedi_device *dev, struct comedi_subdevice *s); +static int dio_callback(int dir, int port, int data, unsigned long arg); +static int dio_callback_4020(int dir, int port, int data, unsigned long arg); +static int di_rbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int do_wbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dio_60xx_config_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dio_60xx_wbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int calib_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int calib_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ad8402_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static void ad8402_write(struct comedi_device *dev, unsigned int channel, + unsigned int value); +static int ad8402_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int eeprom_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static void check_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd); +static unsigned int get_divisor(unsigned int ns, unsigned int flags); +static void i2c_write(struct comedi_device *dev, unsigned int address, + const uint8_t * data, unsigned int length); +static void caldac_write(struct comedi_device *dev, unsigned int channel, + unsigned int value); +static int caldac_8800_write(struct comedi_device *dev, unsigned int address, + uint8_t value); +/* static int dac_1590_write(struct comedi_device *dev, unsigned int dac_a, unsigned int dac_b); */ +static int caldac_i2c_write(struct comedi_device *dev, + unsigned int caldac_channel, unsigned int value); +static void abort_dma(struct comedi_device *dev, unsigned int channel); +static void disable_plx_interrupts(struct comedi_device *dev); +static int set_ai_fifo_size(struct comedi_device *dev, + unsigned int num_samples); +static unsigned int ai_fifo_size(struct comedi_device *dev); +static int set_ai_fifo_segment_length(struct comedi_device *dev, + unsigned int num_entries); +static void disable_ai_pacing(struct comedi_device *dev); +static void disable_ai_interrupts(struct comedi_device *dev); +static void enable_ai_interrupts(struct comedi_device *dev, + const struct comedi_cmd *cmd); +static unsigned int get_ao_divisor(unsigned int ns, unsigned int flags); +static void load_ao_dma(struct comedi_device *dev, + const struct comedi_cmd *cmd); + +static int __devinit driver_cb_pcidas_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_cb_pcidas.driver_name); +} + +static void __devexit driver_cb_pcidas_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_cb_pcidas_pci_driver = { + .id_table = pcidas64_pci_table, + .probe = &driver_cb_pcidas_pci_probe, + .remove = __devexit_p(&driver_cb_pcidas_pci_remove) +}; + +static int __init driver_cb_pcidas_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_cb_pcidas); + if (retval < 0) + return retval; + + driver_cb_pcidas_pci_driver.name = (char *)driver_cb_pcidas.driver_name; + return pci_register_driver(&driver_cb_pcidas_pci_driver); +} + +static void __exit driver_cb_pcidas_cleanup_module(void) +{ + pci_unregister_driver(&driver_cb_pcidas_pci_driver); + comedi_driver_unregister(&driver_cb_pcidas); +} + +module_init(driver_cb_pcidas_init_module); +module_exit(driver_cb_pcidas_cleanup_module); + +static unsigned int ai_range_bits_6xxx(const struct comedi_device *dev, + unsigned int range_index) +{ + const struct comedi_krange *range = + &board(dev)->ai_range_table->range[range_index]; + unsigned int bits = 0; + + switch (range->max) { + case 10000000: + bits = 0x000; + break; + case 5000000: + bits = 0x100; + break; + case 2000000: + case 2500000: + bits = 0x200; + break; + case 1000000: + case 1250000: + bits = 0x300; + break; + case 500000: + bits = 0x400; + break; + case 200000: + case 250000: + bits = 0x500; + break; + case 100000: + bits = 0x600; + break; + case 50000: + bits = 0x700; + break; + default: + comedi_error(dev, "bug! in ai_range_bits_6xxx"); + break; + } + if (range->min == 0) + bits += 0x900; + return bits; +} + +static unsigned int hw_revision(const struct comedi_device *dev, + uint16_t hw_status_bits) +{ + if (board(dev)->layout == LAYOUT_4020) + return (hw_status_bits >> 13) & 0x7; + + return (hw_status_bits >> 12) & 0xf; +} + +static void set_dac_range_bits(struct comedi_device *dev, + volatile uint16_t * bits, unsigned int channel, + unsigned int range) +{ + unsigned int code = board(dev)->ao_range_code[range]; + + if (channel > 1) + comedi_error(dev, "bug! bad channel?"); + if (code & ~0x3) + comedi_error(dev, "bug! bad range code?"); + + *bits &= ~(0x3 << (2 * channel)); + *bits |= code << (2 * channel); +}; + +static inline int ao_cmd_is_supported(const struct pcidas64_board *board) +{ + return board->ao_nchan && board->layout != LAYOUT_4020; +} + +/* initialize plx9080 chip */ +static void init_plx9080(struct comedi_device *dev) +{ + uint32_t bits; + void __iomem *plx_iobase = priv(dev)->plx9080_iobase; + + priv(dev)->plx_control_bits = + readl(priv(dev)->plx9080_iobase + PLX_CONTROL_REG); + + /* plx9080 dump */ + DEBUG_PRINT(" plx interrupt status 0x%x\n", + readl(plx_iobase + PLX_INTRCS_REG)); + DEBUG_PRINT(" plx id bits 0x%x\n", readl(plx_iobase + PLX_ID_REG)); + DEBUG_PRINT(" plx control reg 0x%x\n", priv(dev)->plx_control_bits); + DEBUG_PRINT(" plx mode/arbitration reg 0x%x\n", + readl(plx_iobase + PLX_MARB_REG)); + DEBUG_PRINT(" plx region0 reg 0x%x\n", + readl(plx_iobase + PLX_REGION0_REG)); + DEBUG_PRINT(" plx region1 reg 0x%x\n", + readl(plx_iobase + PLX_REGION1_REG)); + + DEBUG_PRINT(" plx revision 0x%x\n", + readl(plx_iobase + PLX_REVISION_REG)); + DEBUG_PRINT(" plx dma channel 0 mode 0x%x\n", + readl(plx_iobase + PLX_DMA0_MODE_REG)); + DEBUG_PRINT(" plx dma channel 1 mode 0x%x\n", + readl(plx_iobase + PLX_DMA1_MODE_REG)); + DEBUG_PRINT(" plx dma channel 0 pci address 0x%x\n", + readl(plx_iobase + PLX_DMA0_PCI_ADDRESS_REG)); + DEBUG_PRINT(" plx dma channel 0 local address 0x%x\n", + readl(plx_iobase + PLX_DMA0_LOCAL_ADDRESS_REG)); + DEBUG_PRINT(" plx dma channel 0 transfer size 0x%x\n", + readl(plx_iobase + PLX_DMA0_TRANSFER_SIZE_REG)); + DEBUG_PRINT(" plx dma channel 0 descriptor 0x%x\n", + readl(plx_iobase + PLX_DMA0_DESCRIPTOR_REG)); + DEBUG_PRINT(" plx dma channel 0 command status 0x%x\n", + readb(plx_iobase + PLX_DMA0_CS_REG)); + DEBUG_PRINT(" plx dma channel 0 threshold 0x%x\n", + readl(plx_iobase + PLX_DMA0_THRESHOLD_REG)); + DEBUG_PRINT(" plx bigend 0x%x\n", readl(plx_iobase + PLX_BIGEND_REG)); + +#ifdef __BIG_ENDIAN + bits = BIGEND_DMA0 | BIGEND_DMA1; +#else + bits = 0; +#endif + writel(bits, priv(dev)->plx9080_iobase + PLX_BIGEND_REG); + + disable_plx_interrupts(dev); + + abort_dma(dev, 0); + abort_dma(dev, 1); + + /* configure dma0 mode */ + bits = 0; + /* enable ready input, not sure if this is necessary */ + bits |= PLX_DMA_EN_READYIN_BIT; + /* enable bterm, not sure if this is necessary */ + bits |= PLX_EN_BTERM_BIT; + /* enable dma chaining */ + bits |= PLX_EN_CHAIN_BIT; + /* enable interrupt on dma done (probably don't need this, since chain never finishes) */ + bits |= PLX_EN_DMA_DONE_INTR_BIT; + /* don't increment local address during transfers (we are transferring from a fixed fifo register) */ + bits |= PLX_LOCAL_ADDR_CONST_BIT; + /* route dma interrupt to pci bus */ + bits |= PLX_DMA_INTR_PCI_BIT; + /* enable demand mode */ + bits |= PLX_DEMAND_MODE_BIT; + /* enable local burst mode */ + bits |= PLX_DMA_LOCAL_BURST_EN_BIT; + /* 4020 uses 32 bit dma */ + if (board(dev)->layout == LAYOUT_4020) { + bits |= PLX_LOCAL_BUS_32_WIDE_BITS; + } else { /* localspace0 bus is 16 bits wide */ + bits |= PLX_LOCAL_BUS_16_WIDE_BITS; + } + writel(bits, plx_iobase + PLX_DMA1_MODE_REG); + if (ao_cmd_is_supported(board(dev))) + writel(bits, plx_iobase + PLX_DMA0_MODE_REG); + + /* enable interrupts on plx 9080 */ + priv(dev)->plx_intcsr_bits |= + ICS_AERR | ICS_PERR | ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_LIE | + ICS_DMA0_E | ICS_DMA1_E; + writel(priv(dev)->plx_intcsr_bits, + priv(dev)->plx9080_iobase + PLX_INTRCS_REG); +} + +/* Allocate and initialize the subdevice structures. + */ +static int setup_subdevices(struct comedi_device *dev) +{ + struct comedi_subdevice *s; + void __iomem *dio_8255_iobase; + int i; + + if (alloc_subdevices(dev, 10) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog input subdevice */ + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DITHER | SDF_CMD_READ; + if (board(dev)->layout == LAYOUT_60XX) + s->subdev_flags |= SDF_COMMON | SDF_DIFF; + else if (board(dev)->layout == LAYOUT_64XX) + s->subdev_flags |= SDF_DIFF; + /* XXX Number of inputs in differential mode is ignored */ + s->n_chan = board(dev)->ai_se_chans; + s->len_chanlist = 0x2000; + s->maxdata = (1 << board(dev)->ai_bits) - 1; + s->range_table = board(dev)->ai_range_table; + s->insn_read = ai_rinsn; + s->insn_config = ai_config_insn; + s->do_cmd = ai_cmd; + s->do_cmdtest = ai_cmdtest; + s->cancel = ai_cancel; + if (board(dev)->layout == LAYOUT_4020) { + uint8_t data; + /* set adc to read from inputs (not internal calibration sources) */ + priv(dev)->i2c_cal_range_bits = adc_src_4020_bits(4); + /* set channels to +-5 volt input ranges */ + for (i = 0; i < s->n_chan; i++) + priv(dev)->i2c_cal_range_bits |= attenuate_bit(i); + data = priv(dev)->i2c_cal_range_bits; + i2c_write(dev, RANGE_CAL_I2C_ADDR, &data, sizeof(data)); + } + + /* analog output subdevice */ + s = dev->subdevices + 1; + if (board(dev)->ao_nchan) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = + SDF_READABLE | SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; + s->n_chan = board(dev)->ao_nchan; + s->maxdata = (1 << board(dev)->ao_bits) - 1; + s->range_table = board(dev)->ao_range_table; + s->insn_read = ao_readback_insn; + s->insn_write = ao_winsn; + if (ao_cmd_is_supported(board(dev))) { + dev->write_subdev = s; + s->do_cmdtest = ao_cmdtest; + s->do_cmd = ao_cmd; + s->len_chanlist = board(dev)->ao_nchan; + s->cancel = ao_cancel; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* digital input */ + s = dev->subdevices + 2; + if (board(dev)->layout == LAYOUT_64XX) { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = di_rbits; + } else + s->type = COMEDI_SUBD_UNUSED; + + /* digital output */ + if (board(dev)->layout == LAYOUT_64XX) { + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = do_wbits; + } else + s->type = COMEDI_SUBD_UNUSED; + + /* 8255 */ + s = dev->subdevices + 4; + if (board(dev)->has_8255) { + if (board(dev)->layout == LAYOUT_4020) { + dio_8255_iobase = + priv(dev)->main_iobase + I8255_4020_REG; + subdev_8255_init(dev, s, dio_callback_4020, + (unsigned long)dio_8255_iobase); + } else { + dio_8255_iobase = + priv(dev)->dio_counter_iobase + DIO_8255_OFFSET; + subdev_8255_init(dev, s, dio_callback, + (unsigned long)dio_8255_iobase); + } + } else + s->type = COMEDI_SUBD_UNUSED; + + /* 8 channel dio for 60xx */ + s = dev->subdevices + 5; + if (board(dev)->layout == LAYOUT_60XX) { + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = dio_60xx_config_insn; + s->insn_bits = dio_60xx_wbits; + } else + s->type = COMEDI_SUBD_UNUSED; + + /* caldac */ + s = dev->subdevices + 6; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 8; + if (board(dev)->layout == LAYOUT_4020) + s->maxdata = 0xfff; + else + s->maxdata = 0xff; + s->insn_read = calib_read_insn; + s->insn_write = calib_write_insn; + for (i = 0; i < s->n_chan; i++) + caldac_write(dev, i, s->maxdata / 2); + + /* 2 channel ad8402 potentiometer */ + s = dev->subdevices + 7; + if (board(dev)->layout == LAYOUT_64XX) { + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 2; + s->insn_read = ad8402_read_insn; + s->insn_write = ad8402_write_insn; + s->maxdata = 0xff; + for (i = 0; i < s->n_chan; i++) + ad8402_write(dev, i, s->maxdata / 2); + } else + s->type = COMEDI_SUBD_UNUSED; + + /* serial EEPROM, if present */ + s = dev->subdevices + 8; + if (readl(priv(dev)->plx9080_iobase + PLX_CONTROL_REG) & CTL_EECHK) { + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE | SDF_INTERNAL; + s->n_chan = 128; + s->maxdata = 0xffff; + s->insn_read = eeprom_read_insn; + } else + s->type = COMEDI_SUBD_UNUSED; + + /* user counter subd XXX */ + s = dev->subdevices + 9; + s->type = COMEDI_SUBD_UNUSED; + + return 0; +} + +static void disable_plx_interrupts(struct comedi_device *dev) +{ + priv(dev)->plx_intcsr_bits = 0; + writel(priv(dev)->plx_intcsr_bits, + priv(dev)->plx9080_iobase + PLX_INTRCS_REG); +} + +static void init_stc_registers(struct comedi_device *dev) +{ + uint16_t bits; + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + + /* bit should be set for 6025, although docs say boards with <= 16 chans should be cleared XXX */ + if (1) + priv(dev)->adc_control1_bits |= ADC_QUEUE_CONFIG_BIT; + writew(priv(dev)->adc_control1_bits, + priv(dev)->main_iobase + ADC_CONTROL1_REG); + + /* 6402/16 manual says this register must be initialized to 0xff? */ + writew(0xff, priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG); + + bits = SLOW_DAC_BIT | DMA_CH_SELECT_BIT; + if (board(dev)->layout == LAYOUT_4020) + bits |= INTERNAL_CLOCK_4020_BITS; + priv(dev)->hw_config_bits |= bits; + writew(priv(dev)->hw_config_bits, + priv(dev)->main_iobase + HW_CONFIG_REG); + + writew(0, priv(dev)->main_iobase + DAQ_SYNC_REG); + writew(0, priv(dev)->main_iobase + CALIBRATION_REG); + + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* set fifos to maximum size */ + priv(dev)->fifo_size_bits |= DAC_FIFO_BITS; + set_ai_fifo_segment_length(dev, + board(dev)->ai_fifo->max_segment_length); + + priv(dev)->dac_control1_bits = DAC_OUTPUT_ENABLE_BIT; + priv(dev)->intr_enable_bits = /* EN_DAC_INTR_SRC_BIT | DAC_INTR_QEMPTY_BITS | */ + EN_DAC_DONE_INTR_BIT | EN_DAC_UNDERRUN_BIT; + writew(priv(dev)->intr_enable_bits, + priv(dev)->main_iobase + INTR_ENABLE_REG); + + disable_ai_pacing(dev); +}; + +static int alloc_and_init_dma_members(struct comedi_device *dev) +{ + int i; + + /* alocate pci dma buffers */ + for (i = 0; i < ai_dma_ring_count(board(dev)); i++) { + priv(dev)->ai_buffer[i] = + pci_alloc_consistent(priv(dev)->hw_dev, DMA_BUFFER_SIZE, + &priv(dev)->ai_buffer_bus_addr[i]); + if (priv(dev)->ai_buffer[i] == NULL) + return -ENOMEM; + + } + for (i = 0; i < AO_DMA_RING_COUNT; i++) { + if (ao_cmd_is_supported(board(dev))) { + priv(dev)->ao_buffer[i] = + pci_alloc_consistent(priv(dev)->hw_dev, + DMA_BUFFER_SIZE, + &priv(dev)-> + ao_buffer_bus_addr[i]); + if (priv(dev)->ao_buffer[i] == NULL) + return -ENOMEM; + + } + } + /* allocate dma descriptors */ + priv(dev)->ai_dma_desc = + pci_alloc_consistent(priv(dev)->hw_dev, + sizeof(struct plx_dma_desc) * + ai_dma_ring_count(board(dev)), + &priv(dev)->ai_dma_desc_bus_addr); + if (priv(dev)->ai_dma_desc == NULL) + return -ENOMEM; + + DEBUG_PRINT("ai dma descriptors start at bus addr 0x%x\n", + priv(dev)->ai_dma_desc_bus_addr); + if (ao_cmd_is_supported(board(dev))) { + priv(dev)->ao_dma_desc = + pci_alloc_consistent(priv(dev)->hw_dev, + sizeof(struct plx_dma_desc) * + AO_DMA_RING_COUNT, + &priv(dev)->ao_dma_desc_bus_addr); + if (priv(dev)->ao_dma_desc == NULL) + return -ENOMEM; + + DEBUG_PRINT("ao dma descriptors start at bus addr 0x%x\n", + priv(dev)->ao_dma_desc_bus_addr); + } + /* initialize dma descriptors */ + for (i = 0; i < ai_dma_ring_count(board(dev)); i++) { + priv(dev)->ai_dma_desc[i].pci_start_addr = + cpu_to_le32(priv(dev)->ai_buffer_bus_addr[i]); + if (board(dev)->layout == LAYOUT_4020) + priv(dev)->ai_dma_desc[i].local_start_addr = + cpu_to_le32(priv(dev)->local1_iobase + + ADC_FIFO_REG); + else + priv(dev)->ai_dma_desc[i].local_start_addr = + cpu_to_le32(priv(dev)->local0_iobase + + ADC_FIFO_REG); + priv(dev)->ai_dma_desc[i].transfer_size = cpu_to_le32(0); + priv(dev)->ai_dma_desc[i].next = + cpu_to_le32((priv(dev)->ai_dma_desc_bus_addr + ((i + + 1) % + ai_dma_ring_count + (board + (dev))) * + sizeof(priv(dev)->ai_dma_desc[0])) | + PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT | + PLX_XFER_LOCAL_TO_PCI); + } + if (ao_cmd_is_supported(board(dev))) { + for (i = 0; i < AO_DMA_RING_COUNT; i++) { + priv(dev)->ao_dma_desc[i].pci_start_addr = + cpu_to_le32(priv(dev)->ao_buffer_bus_addr[i]); + priv(dev)->ao_dma_desc[i].local_start_addr = + cpu_to_le32(priv(dev)->local0_iobase + + DAC_FIFO_REG); + priv(dev)->ao_dma_desc[i].transfer_size = + cpu_to_le32(0); + priv(dev)->ao_dma_desc[i].next = + cpu_to_le32((priv(dev)->ao_dma_desc_bus_addr + + ((i + 1) % (AO_DMA_RING_COUNT)) * + sizeof(priv(dev)->ao_dma_desc[0])) | + PLX_DESC_IN_PCI_BIT | + PLX_INTR_TERM_COUNT); + } + } + return 0; +} + +static inline void warn_external_queue(struct comedi_device *dev) +{ + comedi_error(dev, + "AO command and AI external channel queue cannot be used simultaneously."); + comedi_error(dev, + "Use internal AI channel queue (channels must be consecutive and use same range/aref)"); +} + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. + */ +static int attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pci_dev *pcidev = NULL; + int index; + uint32_t local_range, local_decode; + int retval; + +/* + * Allocate the private structure area. + */ + if (alloc_private(dev, sizeof(struct pcidas64_private)) < 0) + return -ENOMEM; + +/* + * Probe the device to determine what device in the series it is. + */ + + for_each_pci_dev(pcidev) { + /* is it not a computer boards card? */ + if (pcidev->vendor != PCI_VENDOR_ID_COMPUTERBOARDS) + continue; + /* loop through cards supported by this driver */ + for (index = 0; index < ARRAY_SIZE(pcidas64_boards); index++) { + if (pcidas64_boards[index].device_id != pcidev->device) + continue; + /* was a particular bus/slot requested? */ + if (it->options[0] || it->options[1]) { + /* are we on the wrong bus/slot? */ + if (pcidev->bus->number != it->options[0] || + PCI_SLOT(pcidev->devfn) != it->options[1]) { + continue; + } + } + priv(dev)->hw_dev = pcidev; + dev->board_ptr = pcidas64_boards + index; + break; + } + if (dev->board_ptr) + break; + } + + if (dev->board_ptr == NULL) { + printk + ("No supported ComputerBoards/MeasurementComputing card found\n"); + return -EIO; + } + + dev_dbg(dev->hw_dev, "Found %s on bus %i, slot %i\n", board(dev)->name, + pcidev->bus->number, PCI_SLOT(pcidev->devfn)); + + if (comedi_pci_enable(pcidev, driver_cb_pcidas.driver_name)) { + dev_warn(dev->hw_dev, "failed to enable PCI device and request regions\n"); + return -EIO; + } + pci_set_master(pcidev); + + /* Initialize dev->board_name */ + dev->board_name = board(dev)->name; + + priv(dev)->plx9080_phys_iobase = + pci_resource_start(pcidev, PLX9080_BADDRINDEX); + priv(dev)->main_phys_iobase = + pci_resource_start(pcidev, MAIN_BADDRINDEX); + priv(dev)->dio_counter_phys_iobase = + pci_resource_start(pcidev, DIO_COUNTER_BADDRINDEX); + + /* remap, won't work with 2.0 kernels but who cares */ + priv(dev)->plx9080_iobase = ioremap(priv(dev)->plx9080_phys_iobase, + pci_resource_len(pcidev, + PLX9080_BADDRINDEX)); + priv(dev)->main_iobase = + ioremap(priv(dev)->main_phys_iobase, + pci_resource_len(pcidev, MAIN_BADDRINDEX)); + priv(dev)->dio_counter_iobase = + ioremap(priv(dev)->dio_counter_phys_iobase, + pci_resource_len(pcidev, DIO_COUNTER_BADDRINDEX)); + + if (!priv(dev)->plx9080_iobase || !priv(dev)->main_iobase + || !priv(dev)->dio_counter_iobase) { + dev_warn(dev->hw_dev, "failed to remap io memory\n"); + return -ENOMEM; + } + + DEBUG_PRINT(" plx9080 remapped to 0x%p\n", priv(dev)->plx9080_iobase); + DEBUG_PRINT(" main remapped to 0x%p\n", priv(dev)->main_iobase); + DEBUG_PRINT(" diocounter remapped to 0x%p\n", + priv(dev)->dio_counter_iobase); + + /* figure out what local addresses are */ + local_range = + readl(priv(dev)->plx9080_iobase + PLX_LAS0RNG_REG) & LRNG_MEM_MASK; + local_decode = + readl(priv(dev)->plx9080_iobase + + PLX_LAS0MAP_REG) & local_range & LMAP_MEM_MASK; + priv(dev)->local0_iobase = + ((uint32_t) priv(dev)->main_phys_iobase & ~local_range) | + local_decode; + local_range = + readl(priv(dev)->plx9080_iobase + PLX_LAS1RNG_REG) & LRNG_MEM_MASK; + local_decode = + readl(priv(dev)->plx9080_iobase + + PLX_LAS1MAP_REG) & local_range & LMAP_MEM_MASK; + priv(dev)->local1_iobase = + ((uint32_t) priv(dev)->dio_counter_phys_iobase & ~local_range) | + local_decode; + + DEBUG_PRINT(" local 0 io addr 0x%x\n", priv(dev)->local0_iobase); + DEBUG_PRINT(" local 1 io addr 0x%x\n", priv(dev)->local1_iobase); + + retval = alloc_and_init_dma_members(dev); + if (retval < 0) + return retval; + + priv(dev)->hw_revision = + hw_revision(dev, readw(priv(dev)->main_iobase + HW_STATUS_REG)); + dev_dbg(dev->hw_dev, "stc hardware revision %i\n", + priv(dev)->hw_revision); + init_plx9080(dev); + init_stc_registers(dev); + /* get irq */ + if (request_irq(pcidev->irq, handle_interrupt, IRQF_SHARED, + "cb_pcidas64", dev)) { + dev_dbg(dev->hw_dev, "unable to allocate irq %u\n", + pcidev->irq); + return -EINVAL; + } + dev->irq = pcidev->irq; + dev_dbg(dev->hw_dev, "irq %u\n", dev->irq); + + retval = setup_subdevices(dev); + if (retval < 0) + return retval; + + + return 0; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int detach(struct comedi_device *dev) +{ + unsigned int i; + + if (dev->irq) + free_irq(dev->irq, dev); + if (priv(dev)) { + if (priv(dev)->hw_dev) { + if (priv(dev)->plx9080_iobase) { + disable_plx_interrupts(dev); + iounmap(priv(dev)->plx9080_iobase); + } + if (priv(dev)->main_iobase) + iounmap(priv(dev)->main_iobase); + if (priv(dev)->dio_counter_iobase) + iounmap(priv(dev)->dio_counter_iobase); + /* free pci dma buffers */ + for (i = 0; i < ai_dma_ring_count(board(dev)); i++) { + if (priv(dev)->ai_buffer[i]) + pci_free_consistent(priv(dev)->hw_dev, + DMA_BUFFER_SIZE, + priv(dev)-> + ai_buffer[i], + priv + (dev)->ai_buffer_bus_addr + [i]); + } + for (i = 0; i < AO_DMA_RING_COUNT; i++) { + if (priv(dev)->ao_buffer[i]) + pci_free_consistent(priv(dev)->hw_dev, + DMA_BUFFER_SIZE, + priv(dev)-> + ao_buffer[i], + priv + (dev)->ao_buffer_bus_addr + [i]); + } + /* free dma descriptors */ + if (priv(dev)->ai_dma_desc) + pci_free_consistent(priv(dev)->hw_dev, + sizeof(struct plx_dma_desc) + * + ai_dma_ring_count(board + (dev)), + priv(dev)->ai_dma_desc, + priv(dev)-> + ai_dma_desc_bus_addr); + if (priv(dev)->ao_dma_desc) + pci_free_consistent(priv(dev)->hw_dev, + sizeof(struct plx_dma_desc) + * AO_DMA_RING_COUNT, + priv(dev)->ao_dma_desc, + priv(dev)-> + ao_dma_desc_bus_addr); + if (priv(dev)->main_phys_iobase) + comedi_pci_disable(priv(dev)->hw_dev); + + pci_dev_put(priv(dev)->hw_dev); + } + } + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 4); + + return 0; +} + +static int ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int bits = 0, n, i; + unsigned int channel, range, aref; + unsigned long flags; + static const int timeout = 100; + + DEBUG_PRINT("chanspec 0x%x\n", insn->chanspec); + channel = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + aref = CR_AREF(insn->chanspec); + + /* disable card's analog input interrupt sources and pacing */ + /* 4020 generates dac done interrupts even though they are disabled */ + disable_ai_pacing(dev); + + spin_lock_irqsave(&dev->spinlock, flags); + if (insn->chanspec & CR_ALT_FILTER) + priv(dev)->adc_control1_bits |= ADC_DITHER_BIT; + else + priv(dev)->adc_control1_bits &= ~ADC_DITHER_BIT; + writew(priv(dev)->adc_control1_bits, + priv(dev)->main_iobase + ADC_CONTROL1_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + if (board(dev)->layout != LAYOUT_4020) { + /* use internal queue */ + priv(dev)->hw_config_bits &= ~EXT_QUEUE_BIT; + writew(priv(dev)->hw_config_bits, + priv(dev)->main_iobase + HW_CONFIG_REG); + + /* ALT_SOURCE is internal calibration reference */ + if (insn->chanspec & CR_ALT_SOURCE) { + unsigned int cal_en_bit; + + DEBUG_PRINT("reading calibration source\n"); + if (board(dev)->layout == LAYOUT_60XX) + cal_en_bit = CAL_EN_60XX_BIT; + else + cal_en_bit = CAL_EN_64XX_BIT; + /* select internal reference source to connect to channel 0 */ + writew(cal_en_bit | + adc_src_bits(priv(dev)->calibration_source), + priv(dev)->main_iobase + CALIBRATION_REG); + } else { + /* make sure internal calibration source is turned off */ + writew(0, priv(dev)->main_iobase + CALIBRATION_REG); + } + /* load internal queue */ + bits = 0; + /* set gain */ + bits |= ai_range_bits_6xxx(dev, CR_RANGE(insn->chanspec)); + /* set single-ended / differential */ + bits |= se_diff_bit_6xxx(dev, aref == AREF_DIFF); + if (aref == AREF_COMMON) + bits |= ADC_COMMON_BIT; + bits |= adc_chan_bits(channel); + /* set stop channel */ + writew(adc_chan_bits(channel), + priv(dev)->main_iobase + ADC_QUEUE_HIGH_REG); + /* set start channel, and rest of settings */ + writew(bits, priv(dev)->main_iobase + ADC_QUEUE_LOAD_REG); + } else { + uint8_t old_cal_range_bits = priv(dev)->i2c_cal_range_bits; + + priv(dev)->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK; + if (insn->chanspec & CR_ALT_SOURCE) { + DEBUG_PRINT("reading calibration source\n"); + priv(dev)->i2c_cal_range_bits |= + adc_src_4020_bits(priv(dev)->calibration_source); + } else { /* select BNC inputs */ + priv(dev)->i2c_cal_range_bits |= adc_src_4020_bits(4); + } + /* select range */ + if (range == 0) + priv(dev)->i2c_cal_range_bits |= attenuate_bit(channel); + else + priv(dev)->i2c_cal_range_bits &= + ~attenuate_bit(channel); + /* update calibration/range i2c register only if necessary, as it is very slow */ + if (old_cal_range_bits != priv(dev)->i2c_cal_range_bits) { + uint8_t i2c_data = priv(dev)->i2c_cal_range_bits; + i2c_write(dev, RANGE_CAL_I2C_ADDR, &i2c_data, + sizeof(i2c_data)); + } + + /* 4020 manual asks that sample interval register to be set before writing to convert register. + * Using somewhat arbitrary setting of 4 master clock ticks = 0.1 usec */ + writew(0, + priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG); + writew(2, + priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG); + } + + for (n = 0; n < insn->n; n++) { + + /* clear adc buffer (inside loop for 4020 sake) */ + writew(0, priv(dev)->main_iobase + ADC_BUFFER_CLEAR_REG); + + /* trigger conversion, bits sent only matter for 4020 */ + writew(adc_convert_chan_4020_bits(CR_CHAN(insn->chanspec)), + priv(dev)->main_iobase + ADC_CONVERT_REG); + + /* wait for data */ + for (i = 0; i < timeout; i++) { + bits = readw(priv(dev)->main_iobase + HW_STATUS_REG); + DEBUG_PRINT(" pipe bits 0x%x\n", pipe_full_bits(bits)); + if (board(dev)->layout == LAYOUT_4020) { + if (readw(priv(dev)->main_iobase + + ADC_WRITE_PNTR_REG)) + break; + } else { + if (pipe_full_bits(bits)) + break; + } + udelay(1); + } + DEBUG_PRINT(" looped %i times waiting for data\n", i); + if (i == timeout) { + comedi_error(dev, " analog input read insn timed out"); + printk(" status 0x%x\n", bits); + return -ETIME; + } + if (board(dev)->layout == LAYOUT_4020) + data[n] = + readl(priv(dev)->dio_counter_iobase + + ADC_FIFO_REG) & 0xffff; + else + data[n] = + readw(priv(dev)->main_iobase + PIPE1_READ_REG); + } + + return n; +} + +static int ai_config_calibration_source(struct comedi_device *dev, + unsigned int *data) +{ + unsigned int source = data[1]; + int num_calibration_sources; + + if (board(dev)->layout == LAYOUT_60XX) + num_calibration_sources = 16; + else + num_calibration_sources = 8; + if (source >= num_calibration_sources) { + dev_dbg(dev->hw_dev, "invalid calibration source: %i\n", + source); + return -EINVAL; + } + + DEBUG_PRINT("setting calibration source to %i\n", source); + priv(dev)->calibration_source = source; + + return 2; +} + +static int ai_config_block_size(struct comedi_device *dev, unsigned int *data) +{ + int fifo_size; + const struct hw_fifo_info *const fifo = board(dev)->ai_fifo; + unsigned int block_size, requested_block_size; + int retval; + + requested_block_size = data[1]; + + if (requested_block_size) { + fifo_size = + requested_block_size * fifo->num_segments / bytes_in_sample; + + retval = set_ai_fifo_size(dev, fifo_size); + if (retval < 0) + return retval; + + } + + block_size = ai_fifo_size(dev) / fifo->num_segments * bytes_in_sample; + + data[1] = block_size; + + return 2; +} + +static int ai_config_master_clock_4020(struct comedi_device *dev, + unsigned int *data) +{ + unsigned int divisor = data[4]; + int retval = 0; + + if (divisor < 2) { + divisor = 2; + retval = -EAGAIN; + } + + switch (data[1]) { + case COMEDI_EV_SCAN_BEGIN: + priv(dev)->ext_clock.divisor = divisor; + priv(dev)->ext_clock.chanspec = data[2]; + break; + default: + return -EINVAL; + break; + } + + data[4] = divisor; + + return retval ? retval : 5; +} + +/* XXX could add support for 60xx series */ +static int ai_config_master_clock(struct comedi_device *dev, unsigned int *data) +{ + + switch (board(dev)->layout) { + case LAYOUT_4020: + return ai_config_master_clock_4020(dev, data); + break; + default: + return -EINVAL; + break; + } + + return -EINVAL; +} + +static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int id = data[0]; + + switch (id) { + case INSN_CONFIG_ALT_SOURCE: + return ai_config_calibration_source(dev, data); + break; + case INSN_CONFIG_BLOCK_SIZE: + return ai_config_block_size(dev, data); + break; + case INSN_CONFIG_TIMER_1: + return ai_config_master_clock(dev, data); + break; + default: + return -EINVAL; + break; + } + return -EINVAL; +} + +static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + unsigned int tmp_arg, tmp_arg2; + int i; + int aref; + unsigned int triggers; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + triggers = TRIG_TIMER; + if (board(dev)->layout == LAYOUT_4020) + triggers |= TRIG_OTHER; + else + triggers |= TRIG_FOLLOW; + cmd->scan_begin_src &= triggers; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + triggers = TRIG_TIMER; + if (board(dev)->layout == LAYOUT_4020) + triggers |= TRIG_NOW; + else + triggers |= TRIG_EXT; + cmd->convert_src &= triggers; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_EXT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + /* uniqueness check */ + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) + err++; + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_OTHER && + cmd->scan_begin_src != TRIG_FOLLOW) + err++; + if (cmd->convert_src != TRIG_TIMER && + cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW) + err++; + if (cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT) + err++; + + /* compatibility check */ + if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->convert_src == TRIG_TIMER) { + if (board(dev)->layout == LAYOUT_4020) { + if (cmd->convert_arg) { + cmd->convert_arg = 0; + err++; + } + } else { + if (cmd->convert_arg < board(dev)->ai_speed) { + cmd->convert_arg = board(dev)->ai_speed; + err++; + } + if (cmd->scan_begin_src == TRIG_TIMER) { + /* if scans are timed faster than conversion rate allows */ + if (cmd->convert_arg * cmd->chanlist_len > + cmd->scan_begin_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * + cmd->chanlist_len; + err++; + } + } + } + } + + if (!cmd->chanlist_len) { + cmd->chanlist_len = 1; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + switch (cmd->stop_src) { + case TRIG_EXT: + break; + case TRIG_COUNT: + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + break; + case TRIG_NONE: + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + break; + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp_arg = cmd->convert_arg; + tmp_arg2 = cmd->scan_begin_arg; + check_adc_timing(dev, cmd); + if (tmp_arg != cmd->convert_arg) + err++; + if (tmp_arg2 != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + /* make sure user is doesn't change analog reference mid chanlist */ + if (cmd->chanlist) { + aref = CR_AREF(cmd->chanlist[0]); + for (i = 1; i < cmd->chanlist_len; i++) { + if (aref != CR_AREF(cmd->chanlist[i])) { + comedi_error(dev, + "all elements in chanlist must use the same analog reference"); + err++; + break; + } + } + /* check 4020 chanlist */ + if (board(dev)->layout == LAYOUT_4020) { + unsigned int first_channel = CR_CHAN(cmd->chanlist[0]); + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != + first_channel + i) { + comedi_error(dev, + "chanlist must use consecutive channels"); + err++; + break; + } + } + if (cmd->chanlist_len == 3) { + comedi_error(dev, + "chanlist cannot be 3 channels long, use 1, 2, or 4 channels"); + err++; + } + } + } + + if (err) + return 5; + + return 0; +} + +static int use_hw_sample_counter(struct comedi_cmd *cmd) +{ +/* disable for now until I work out a race */ + return 0; + + if (cmd->stop_src == TRIG_COUNT && cmd->stop_arg <= max_counter_value) + return 1; + else + return 0; +} + +static void setup_sample_counters(struct comedi_device *dev, + struct comedi_cmd *cmd) +{ + if (cmd->stop_src == TRIG_COUNT) { + /* set software count */ + priv(dev)->ai_count = cmd->stop_arg * cmd->chanlist_len; + } + /* load hardware conversion counter */ + if (use_hw_sample_counter(cmd)) { + writew(cmd->stop_arg & 0xffff, + priv(dev)->main_iobase + ADC_COUNT_LOWER_REG); + writew((cmd->stop_arg >> 16) & 0xff, + priv(dev)->main_iobase + ADC_COUNT_UPPER_REG); + } else { + writew(1, priv(dev)->main_iobase + ADC_COUNT_LOWER_REG); + } +} + +static inline unsigned int dma_transfer_size(struct comedi_device *dev) +{ + unsigned int num_samples; + + num_samples = + priv(dev)->ai_fifo_segment_length * + board(dev)->ai_fifo->sample_packing_ratio; + if (num_samples > DMA_BUFFER_SIZE / sizeof(uint16_t)) + num_samples = DMA_BUFFER_SIZE / sizeof(uint16_t); + + return num_samples; +} + +static void disable_ai_pacing(struct comedi_device *dev) +{ + unsigned long flags; + + disable_ai_interrupts(dev); + + spin_lock_irqsave(&dev->spinlock, flags); + priv(dev)->adc_control1_bits &= ~ADC_SW_GATE_BIT; + writew(priv(dev)->adc_control1_bits, + priv(dev)->main_iobase + ADC_CONTROL1_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* disable pacing, triggering, etc */ + writew(ADC_DMA_DISABLE_BIT | ADC_SOFT_GATE_BITS | ADC_GATE_LEVEL_BIT, + priv(dev)->main_iobase + ADC_CONTROL0_REG); +} + +static void disable_ai_interrupts(struct comedi_device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + priv(dev)->intr_enable_bits &= + ~EN_ADC_INTR_SRC_BIT & ~EN_ADC_DONE_INTR_BIT & + ~EN_ADC_ACTIVE_INTR_BIT & ~EN_ADC_STOP_INTR_BIT & + ~EN_ADC_OVERRUN_BIT & ~ADC_INTR_SRC_MASK; + writew(priv(dev)->intr_enable_bits, + priv(dev)->main_iobase + INTR_ENABLE_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + DEBUG_PRINT("intr enable bits 0x%x\n", priv(dev)->intr_enable_bits); +} + +static void enable_ai_interrupts(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + uint32_t bits; + unsigned long flags; + + bits = EN_ADC_OVERRUN_BIT | EN_ADC_DONE_INTR_BIT | + EN_ADC_ACTIVE_INTR_BIT | EN_ADC_STOP_INTR_BIT; + /* Use pio transfer and interrupt on end of conversion if TRIG_WAKE_EOS flag is set. */ + if (cmd->flags & TRIG_WAKE_EOS) { + /* 4020 doesn't support pio transfers except for fifo dregs */ + if (board(dev)->layout != LAYOUT_4020) + bits |= ADC_INTR_EOSCAN_BITS | EN_ADC_INTR_SRC_BIT; + } + spin_lock_irqsave(&dev->spinlock, flags); + priv(dev)->intr_enable_bits |= bits; + writew(priv(dev)->intr_enable_bits, + priv(dev)->main_iobase + INTR_ENABLE_REG); + DEBUG_PRINT("intr enable bits 0x%x\n", priv(dev)->intr_enable_bits); + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static uint32_t ai_convert_counter_6xxx(const struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + /* supposed to load counter with desired divisor minus 3 */ + return cmd->convert_arg / TIMER_BASE - 3; +} + +static uint32_t ai_scan_counter_6xxx(struct comedi_device *dev, + struct comedi_cmd *cmd) +{ + uint32_t count; + /* figure out how long we need to delay at end of scan */ + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + count = (cmd->scan_begin_arg - + (cmd->convert_arg * (cmd->chanlist_len - 1))) + / TIMER_BASE; + break; + case TRIG_FOLLOW: + count = cmd->convert_arg / TIMER_BASE; + break; + default: + return 0; + break; + } + return count - 3; +} + +static uint32_t ai_convert_counter_4020(struct comedi_device *dev, + struct comedi_cmd *cmd) +{ + unsigned int divisor; + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + divisor = cmd->scan_begin_arg / TIMER_BASE; + break; + case TRIG_OTHER: + divisor = priv(dev)->ext_clock.divisor; + break; + default: /* should never happen */ + comedi_error(dev, "bug! failed to set ai pacing!"); + divisor = 1000; + break; + } + + /* supposed to load counter with desired divisor minus 2 for 4020 */ + return divisor - 2; +} + +static void select_master_clock_4020(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + /* select internal/external master clock */ + priv(dev)->hw_config_bits &= ~MASTER_CLOCK_4020_MASK; + if (cmd->scan_begin_src == TRIG_OTHER) { + int chanspec = priv(dev)->ext_clock.chanspec; + + if (CR_CHAN(chanspec)) + priv(dev)->hw_config_bits |= BNC_CLOCK_4020_BITS; + else + priv(dev)->hw_config_bits |= EXT_CLOCK_4020_BITS; + } else { + priv(dev)->hw_config_bits |= INTERNAL_CLOCK_4020_BITS; + } + writew(priv(dev)->hw_config_bits, + priv(dev)->main_iobase + HW_CONFIG_REG); +} + +static void select_master_clock(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + switch (board(dev)->layout) { + case LAYOUT_4020: + select_master_clock_4020(dev, cmd); + break; + default: + break; + } +} + +static inline void dma_start_sync(struct comedi_device *dev, + unsigned int channel) +{ + unsigned long flags; + + /* spinlock for plx dma control/status reg */ + spin_lock_irqsave(&dev->spinlock, flags); + if (channel) + writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT | + PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG); + else + writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT | + PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static void set_ai_pacing(struct comedi_device *dev, struct comedi_cmd *cmd) +{ + uint32_t convert_counter = 0, scan_counter = 0; + + check_adc_timing(dev, cmd); + + select_master_clock(dev, cmd); + + if (board(dev)->layout == LAYOUT_4020) { + convert_counter = ai_convert_counter_4020(dev, cmd); + } else { + convert_counter = ai_convert_counter_6xxx(dev, cmd); + scan_counter = ai_scan_counter_6xxx(dev, cmd); + } + + /* load lower 16 bits of convert interval */ + writew(convert_counter & 0xffff, + priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG); + DEBUG_PRINT("convert counter 0x%x\n", convert_counter); + /* load upper 8 bits of convert interval */ + writew((convert_counter >> 16) & 0xff, + priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG); + /* load lower 16 bits of scan delay */ + writew(scan_counter & 0xffff, + priv(dev)->main_iobase + ADC_DELAY_INTERVAL_LOWER_REG); + /* load upper 8 bits of scan delay */ + writew((scan_counter >> 16) & 0xff, + priv(dev)->main_iobase + ADC_DELAY_INTERVAL_UPPER_REG); + DEBUG_PRINT("scan counter 0x%x\n", scan_counter); +} + +static int use_internal_queue_6xxx(const struct comedi_cmd *cmd) +{ + int i; + for (i = 0; i + 1 < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i + 1]) != + CR_CHAN(cmd->chanlist[i]) + 1) + return 0; + if (CR_RANGE(cmd->chanlist[i + 1]) != + CR_RANGE(cmd->chanlist[i])) + return 0; + if (CR_AREF(cmd->chanlist[i + 1]) != CR_AREF(cmd->chanlist[i])) + return 0; + } + return 1; +} + +static int setup_channel_queue(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + unsigned short bits; + int i; + + if (board(dev)->layout != LAYOUT_4020) { + if (use_internal_queue_6xxx(cmd)) { + priv(dev)->hw_config_bits &= ~EXT_QUEUE_BIT; + writew(priv(dev)->hw_config_bits, + priv(dev)->main_iobase + HW_CONFIG_REG); + bits = 0; + /* set channel */ + bits |= adc_chan_bits(CR_CHAN(cmd->chanlist[0])); + /* set gain */ + bits |= ai_range_bits_6xxx(dev, + CR_RANGE(cmd->chanlist[0])); + /* set single-ended / differential */ + bits |= se_diff_bit_6xxx(dev, + CR_AREF(cmd->chanlist[0]) == + AREF_DIFF); + if (CR_AREF(cmd->chanlist[0]) == AREF_COMMON) + bits |= ADC_COMMON_BIT; + /* set stop channel */ + writew(adc_chan_bits + (CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1])), + priv(dev)->main_iobase + ADC_QUEUE_HIGH_REG); + /* set start channel, and rest of settings */ + writew(bits, + priv(dev)->main_iobase + ADC_QUEUE_LOAD_REG); + } else { + /* use external queue */ + if (dev->write_subdev && dev->write_subdev->busy) { + warn_external_queue(dev); + return -EBUSY; + } + priv(dev)->hw_config_bits |= EXT_QUEUE_BIT; + writew(priv(dev)->hw_config_bits, + priv(dev)->main_iobase + HW_CONFIG_REG); + /* clear DAC buffer to prevent weird interactions */ + writew(0, + priv(dev)->main_iobase + DAC_BUFFER_CLEAR_REG); + /* clear queue pointer */ + writew(0, priv(dev)->main_iobase + ADC_QUEUE_CLEAR_REG); + /* load external queue */ + for (i = 0; i < cmd->chanlist_len; i++) { + bits = 0; + /* set channel */ + bits |= + adc_chan_bits(CR_CHAN(cmd->chanlist[i])); + /* set gain */ + bits |= ai_range_bits_6xxx(dev, + CR_RANGE(cmd-> + chanlist + [i])); + /* set single-ended / differential */ + bits |= se_diff_bit_6xxx(dev, + CR_AREF(cmd-> + chanlist[i]) == + AREF_DIFF); + if (CR_AREF(cmd->chanlist[i]) == AREF_COMMON) + bits |= ADC_COMMON_BIT; + /* mark end of queue */ + if (i == cmd->chanlist_len - 1) + bits |= QUEUE_EOSCAN_BIT | + QUEUE_EOSEQ_BIT; + writew(bits, + priv(dev)->main_iobase + + ADC_QUEUE_FIFO_REG); + DEBUG_PRINT + ("wrote 0x%x to external channel queue\n", + bits); + } + /* doing a queue clear is not specified in board docs, + * but required for reliable operation */ + writew(0, priv(dev)->main_iobase + ADC_QUEUE_CLEAR_REG); + /* prime queue holding register */ + writew(0, priv(dev)->main_iobase + ADC_QUEUE_LOAD_REG); + } + } else { + unsigned short old_cal_range_bits = + priv(dev)->i2c_cal_range_bits; + + priv(dev)->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK; + /* select BNC inputs */ + priv(dev)->i2c_cal_range_bits |= adc_src_4020_bits(4); + /* select ranges */ + for (i = 0; i < cmd->chanlist_len; i++) { + unsigned int channel = CR_CHAN(cmd->chanlist[i]); + unsigned int range = CR_RANGE(cmd->chanlist[i]); + + if (range == 0) + priv(dev)->i2c_cal_range_bits |= + attenuate_bit(channel); + else + priv(dev)->i2c_cal_range_bits &= + ~attenuate_bit(channel); + } + /* update calibration/range i2c register only if necessary, as it is very slow */ + if (old_cal_range_bits != priv(dev)->i2c_cal_range_bits) { + uint8_t i2c_data = priv(dev)->i2c_cal_range_bits; + i2c_write(dev, RANGE_CAL_I2C_ADDR, &i2c_data, + sizeof(i2c_data)); + } + } + return 0; +} + +static inline void load_first_dma_descriptor(struct comedi_device *dev, + unsigned int dma_channel, + unsigned int descriptor_bits) +{ + /* The transfer size, pci address, and local address registers + * are supposedly unused during chained dma, + * but I have found that left over values from last operation + * occasionally cause problems with transfer of first dma + * block. Initializing them to zero seems to fix the problem. */ + if (dma_channel) { + writel(0, + priv(dev)->plx9080_iobase + PLX_DMA1_TRANSFER_SIZE_REG); + writel(0, priv(dev)->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG); + writel(0, + priv(dev)->plx9080_iobase + PLX_DMA1_LOCAL_ADDRESS_REG); + writel(descriptor_bits, + priv(dev)->plx9080_iobase + PLX_DMA1_DESCRIPTOR_REG); + } else { + writel(0, + priv(dev)->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG); + writel(0, priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG); + writel(0, + priv(dev)->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG); + writel(descriptor_bits, + priv(dev)->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG); + } +} + +static int ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + uint32_t bits; + unsigned int i; + unsigned long flags; + int retval; + + disable_ai_pacing(dev); + abort_dma(dev, 1); + + retval = setup_channel_queue(dev, cmd); + if (retval < 0) + return retval; + + /* make sure internal calibration source is turned off */ + writew(0, priv(dev)->main_iobase + CALIBRATION_REG); + + set_ai_pacing(dev, cmd); + + setup_sample_counters(dev, cmd); + + enable_ai_interrupts(dev, cmd); + + spin_lock_irqsave(&dev->spinlock, flags); + /* set mode, allow conversions through software gate */ + priv(dev)->adc_control1_bits |= ADC_SW_GATE_BIT; + priv(dev)->adc_control1_bits &= ~ADC_DITHER_BIT; + if (board(dev)->layout != LAYOUT_4020) { + priv(dev)->adc_control1_bits &= ~ADC_MODE_MASK; + if (cmd->convert_src == TRIG_EXT) + priv(dev)->adc_control1_bits |= adc_mode_bits(13); /* good old mode 13 */ + else + priv(dev)->adc_control1_bits |= adc_mode_bits(8); /* mode 8. What else could you need? */ + } else { + priv(dev)->adc_control1_bits &= ~CHANNEL_MODE_4020_MASK; + if (cmd->chanlist_len == 4) + priv(dev)->adc_control1_bits |= FOUR_CHANNEL_4020_BITS; + else if (cmd->chanlist_len == 2) + priv(dev)->adc_control1_bits |= TWO_CHANNEL_4020_BITS; + priv(dev)->adc_control1_bits &= ~ADC_LO_CHANNEL_4020_MASK; + priv(dev)->adc_control1_bits |= + adc_lo_chan_4020_bits(CR_CHAN(cmd->chanlist[0])); + priv(dev)->adc_control1_bits &= ~ADC_HI_CHANNEL_4020_MASK; + priv(dev)->adc_control1_bits |= + adc_hi_chan_4020_bits(CR_CHAN + (cmd-> + chanlist[cmd->chanlist_len - 1])); + } + writew(priv(dev)->adc_control1_bits, + priv(dev)->main_iobase + ADC_CONTROL1_REG); + DEBUG_PRINT("control1 bits 0x%x\n", priv(dev)->adc_control1_bits); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* clear adc buffer */ + writew(0, priv(dev)->main_iobase + ADC_BUFFER_CLEAR_REG); + + if ((cmd->flags & TRIG_WAKE_EOS) == 0 || + board(dev)->layout == LAYOUT_4020) { + priv(dev)->ai_dma_index = 0; + + /* set dma transfer size */ + for (i = 0; i < ai_dma_ring_count(board(dev)); i++) + priv(dev)->ai_dma_desc[i].transfer_size = + cpu_to_le32(dma_transfer_size(dev) * + sizeof(uint16_t)); + + /* give location of first dma descriptor */ + load_first_dma_descriptor(dev, 1, + priv(dev)->ai_dma_desc_bus_addr | + PLX_DESC_IN_PCI_BIT | + PLX_INTR_TERM_COUNT | + PLX_XFER_LOCAL_TO_PCI); + + dma_start_sync(dev, 1); + } + + if (board(dev)->layout == LAYOUT_4020) { + /* set source for external triggers */ + bits = 0; + if (cmd->start_src == TRIG_EXT && CR_CHAN(cmd->start_arg)) + bits |= EXT_START_TRIG_BNC_BIT; + if (cmd->stop_src == TRIG_EXT && CR_CHAN(cmd->stop_arg)) + bits |= EXT_STOP_TRIG_BNC_BIT; + writew(bits, priv(dev)->main_iobase + DAQ_ATRIG_LOW_4020_REG); + } + + spin_lock_irqsave(&dev->spinlock, flags); + + /* enable pacing, triggering, etc */ + bits = ADC_ENABLE_BIT | ADC_SOFT_GATE_BITS | ADC_GATE_LEVEL_BIT; + if (cmd->flags & TRIG_WAKE_EOS) + bits |= ADC_DMA_DISABLE_BIT; + /* set start trigger */ + if (cmd->start_src == TRIG_EXT) { + bits |= ADC_START_TRIG_EXT_BITS; + if (cmd->start_arg & CR_INVERT) + bits |= ADC_START_TRIG_FALLING_BIT; + } else if (cmd->start_src == TRIG_NOW) + bits |= ADC_START_TRIG_SOFT_BITS; + if (use_hw_sample_counter(cmd)) + bits |= ADC_SAMPLE_COUNTER_EN_BIT; + writew(bits, priv(dev)->main_iobase + ADC_CONTROL0_REG); + DEBUG_PRINT("control0 bits 0x%x\n", bits); + + priv(dev)->ai_cmd_running = 1; + + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* start acquisition */ + if (cmd->start_src == TRIG_NOW) { + writew(0, priv(dev)->main_iobase + ADC_START_REG); + DEBUG_PRINT("soft trig\n"); + } + + return 0; +} + +/* read num_samples from 16 bit wide ai fifo */ +static void pio_drain_ai_fifo_16(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int i; + uint16_t prepost_bits; + int read_segment, read_index, write_segment, write_index; + int num_samples; + + do { + /* get least significant 15 bits */ + read_index = + readw(priv(dev)->main_iobase + ADC_READ_PNTR_REG) & 0x7fff; + write_index = + readw(priv(dev)->main_iobase + ADC_WRITE_PNTR_REG) & 0x7fff; + /* Get most significant bits (grey code). Different boards use different code + * so use a scheme that doesn't depend on encoding. This read must + * occur after reading least significant 15 bits to avoid race + * with fifo switching to next segment. */ + prepost_bits = readw(priv(dev)->main_iobase + PREPOST_REG); + + /* if read and write pointers are not on the same fifo segment, read to the + * end of the read segment */ + read_segment = adc_upper_read_ptr_code(prepost_bits); + write_segment = adc_upper_write_ptr_code(prepost_bits); + + DEBUG_PRINT(" rd seg %i, wrt seg %i, rd idx %i, wrt idx %i\n", + read_segment, write_segment, read_index, + write_index); + + if (read_segment != write_segment) + num_samples = + priv(dev)->ai_fifo_segment_length - read_index; + else + num_samples = write_index - read_index; + + if (cmd->stop_src == TRIG_COUNT) { + if (priv(dev)->ai_count == 0) + break; + if (num_samples > priv(dev)->ai_count) + num_samples = priv(dev)->ai_count; + + priv(dev)->ai_count -= num_samples; + } + + if (num_samples < 0) { + dev_err(dev->hw_dev, "cb_pcidas64: bug! num_samples < 0\n"); + break; + } + + DEBUG_PRINT(" read %i samples from fifo\n", num_samples); + + for (i = 0; i < num_samples; i++) { + cfc_write_to_buffer(s, + readw(priv(dev)->main_iobase + + ADC_FIFO_REG)); + } + + } while (read_segment != write_segment); +} + +/* Read from 32 bit wide ai fifo of 4020 - deal with insane grey coding of pointers. + * The pci-4020 hardware only supports + * dma transfers (it only supports the use of pio for draining the last remaining + * points from the fifo when a data acquisition operation has completed). + */ +static void pio_drain_ai_fifo_32(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int i; + unsigned int max_transfer = 100000; + uint32_t fifo_data; + int write_code = + readw(priv(dev)->main_iobase + ADC_WRITE_PNTR_REG) & 0x7fff; + int read_code = + readw(priv(dev)->main_iobase + ADC_READ_PNTR_REG) & 0x7fff; + + if (cmd->stop_src == TRIG_COUNT) { + if (max_transfer > priv(dev)->ai_count) + max_transfer = priv(dev)->ai_count; + + } + for (i = 0; read_code != write_code && i < max_transfer;) { + fifo_data = readl(priv(dev)->dio_counter_iobase + ADC_FIFO_REG); + cfc_write_to_buffer(s, fifo_data & 0xffff); + i++; + if (i < max_transfer) { + cfc_write_to_buffer(s, (fifo_data >> 16) & 0xffff); + i++; + } + read_code = + readw(priv(dev)->main_iobase + ADC_READ_PNTR_REG) & 0x7fff; + } + priv(dev)->ai_count -= i; +} + +/* empty fifo */ +static void pio_drain_ai_fifo(struct comedi_device *dev) +{ + if (board(dev)->layout == LAYOUT_4020) + pio_drain_ai_fifo_32(dev); + else + pio_drain_ai_fifo_16(dev); +} + +static void drain_dma_buffers(struct comedi_device *dev, unsigned int channel) +{ + struct comedi_async *async = dev->read_subdev->async; + uint32_t next_transfer_addr; + int j; + int num_samples = 0; + void __iomem *pci_addr_reg; + + if (channel) + pci_addr_reg = + priv(dev)->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG; + else + pci_addr_reg = + priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG; + + /* loop until we have read all the full buffers */ + for (j = 0, next_transfer_addr = readl(pci_addr_reg); + (next_transfer_addr < + priv(dev)->ai_buffer_bus_addr[priv(dev)->ai_dma_index] + || next_transfer_addr >= + priv(dev)->ai_buffer_bus_addr[priv(dev)->ai_dma_index] + + DMA_BUFFER_SIZE) && j < ai_dma_ring_count(board(dev)); j++) { + /* transfer data from dma buffer to comedi buffer */ + num_samples = dma_transfer_size(dev); + if (async->cmd.stop_src == TRIG_COUNT) { + if (num_samples > priv(dev)->ai_count) + num_samples = priv(dev)->ai_count; + priv(dev)->ai_count -= num_samples; + } + cfc_write_array_to_buffer(dev->read_subdev, + priv(dev)->ai_buffer[priv(dev)-> + ai_dma_index], + num_samples * sizeof(uint16_t)); + priv(dev)->ai_dma_index = + (priv(dev)->ai_dma_index + + 1) % ai_dma_ring_count(board(dev)); + + DEBUG_PRINT("next buffer addr 0x%lx\n", + (unsigned long)priv(dev)-> + ai_buffer_bus_addr[priv(dev)->ai_dma_index]); + DEBUG_PRINT("pci addr reg 0x%x\n", next_transfer_addr); + } + /* XXX check for dma ring buffer overrun (use end-of-chain bit to mark last + * unused buffer) */ +} + +static void handle_ai_interrupt(struct comedi_device *dev, + unsigned short status, + unsigned int plx_status) +{ + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + uint8_t dma1_status; + unsigned long flags; + + /* check for fifo overrun */ + if (status & ADC_OVERRUN_BIT) { + comedi_error(dev, "fifo overrun"); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + } + /* spin lock makes sure no one else changes plx dma control reg */ + spin_lock_irqsave(&dev->spinlock, flags); + dma1_status = readb(priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG); + if (plx_status & ICS_DMA1_A) { /* dma chan 1 interrupt */ + writeb((dma1_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG); + DEBUG_PRINT("dma1 status 0x%x\n", dma1_status); + + if (dma1_status & PLX_DMA_EN_BIT) + drain_dma_buffers(dev, 1); + + DEBUG_PRINT(" cleared dma ch1 interrupt\n"); + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + if (status & ADC_DONE_BIT) + DEBUG_PRINT("adc done interrupt\n"); + + /* drain fifo with pio */ + if ((status & ADC_DONE_BIT) || + ((cmd->flags & TRIG_WAKE_EOS) && + (status & ADC_INTR_PENDING_BIT) && + (board(dev)->layout != LAYOUT_4020))) { + DEBUG_PRINT("pio fifo drain\n"); + spin_lock_irqsave(&dev->spinlock, flags); + if (priv(dev)->ai_cmd_running) { + spin_unlock_irqrestore(&dev->spinlock, flags); + pio_drain_ai_fifo(dev); + } else + spin_unlock_irqrestore(&dev->spinlock, flags); + } + /* if we are have all the data, then quit */ + if ((cmd->stop_src == TRIG_COUNT && (int)priv(dev)->ai_count <= 0) || + (cmd->stop_src == TRIG_EXT && (status & ADC_STOP_BIT))) { + async->events |= COMEDI_CB_EOA; + } + + cfc_handle_events(dev, s); +} + +static inline unsigned int prev_ao_dma_index(struct comedi_device *dev) +{ + unsigned int buffer_index; + + if (priv(dev)->ao_dma_index == 0) + buffer_index = AO_DMA_RING_COUNT - 1; + else + buffer_index = priv(dev)->ao_dma_index - 1; + return buffer_index; +} + +static int last_ao_dma_load_completed(struct comedi_device *dev) +{ + unsigned int buffer_index; + unsigned int transfer_address; + unsigned short dma_status; + + buffer_index = prev_ao_dma_index(dev); + dma_status = readb(priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + if ((dma_status & PLX_DMA_DONE_BIT) == 0) + return 0; + + transfer_address = + readl(priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG); + if (transfer_address != priv(dev)->ao_buffer_bus_addr[buffer_index]) + return 0; + + return 1; +} + +static int ao_stopped_by_error(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + if (cmd->stop_src == TRIG_NONE) + return 1; + if (cmd->stop_src == TRIG_COUNT) { + if (priv(dev)->ao_count) + return 1; + if (last_ao_dma_load_completed(dev) == 0) + return 1; + } + return 0; +} + +static inline int ao_dma_needs_restart(struct comedi_device *dev, + unsigned short dma_status) +{ + if ((dma_status & PLX_DMA_DONE_BIT) == 0 || + (dma_status & PLX_DMA_EN_BIT) == 0) + return 0; + if (last_ao_dma_load_completed(dev)) + return 0; + + return 1; +} + +static void restart_ao_dma(struct comedi_device *dev) +{ + unsigned int dma_desc_bits; + + dma_desc_bits = + readl(priv(dev)->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG); + dma_desc_bits &= ~PLX_END_OF_CHAIN_BIT; + DEBUG_PRINT("restarting ao dma, descriptor reg 0x%x\n", dma_desc_bits); + load_first_dma_descriptor(dev, 0, dma_desc_bits); + + dma_start_sync(dev, 0); +} + +static void handle_ao_interrupt(struct comedi_device *dev, + unsigned short status, unsigned int plx_status) +{ + struct comedi_subdevice *s = dev->write_subdev; + struct comedi_async *async; + struct comedi_cmd *cmd; + uint8_t dma0_status; + unsigned long flags; + + /* board might not support ao, in which case write_subdev is NULL */ + if (s == NULL) + return; + async = s->async; + cmd = &async->cmd; + + /* spin lock makes sure no one else changes plx dma control reg */ + spin_lock_irqsave(&dev->spinlock, flags); + dma0_status = readb(priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + if (plx_status & ICS_DMA0_A) { /* dma chan 0 interrupt */ + if ((dma0_status & PLX_DMA_EN_BIT) + && !(dma0_status & PLX_DMA_DONE_BIT)) + writeb(PLX_DMA_EN_BIT | PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + else + writeb(PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + DEBUG_PRINT("dma0 status 0x%x\n", dma0_status); + if (dma0_status & PLX_DMA_EN_BIT) { + load_ao_dma(dev, cmd); + /* try to recover from dma end-of-chain event */ + if (ao_dma_needs_restart(dev, dma0_status)) + restart_ao_dma(dev); + } + DEBUG_PRINT(" cleared dma ch0 interrupt\n"); + } else + spin_unlock_irqrestore(&dev->spinlock, flags); + + if ((status & DAC_DONE_BIT)) { + async->events |= COMEDI_CB_EOA; + if (ao_stopped_by_error(dev, cmd)) + async->events |= COMEDI_CB_ERROR; + DEBUG_PRINT("plx dma0 desc reg 0x%x\n", + readl(priv(dev)->plx9080_iobase + + PLX_DMA0_DESCRIPTOR_REG)); + DEBUG_PRINT("plx dma0 address reg 0x%x\n", + readl(priv(dev)->plx9080_iobase + + PLX_DMA0_PCI_ADDRESS_REG)); + } + cfc_handle_events(dev, s); +} + +static irqreturn_t handle_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned short status; + uint32_t plx_status; + uint32_t plx_bits; + + plx_status = readl(priv(dev)->plx9080_iobase + PLX_INTRCS_REG); + status = readw(priv(dev)->main_iobase + HW_STATUS_REG); + + DEBUG_PRINT("cb_pcidas64: hw status 0x%x ", status); + DEBUG_PRINT("plx status 0x%x\n", plx_status); + + /* an interrupt before all the postconfig stuff gets done could + * cause a NULL dereference if we continue through the + * interrupt handler */ + if (dev->attached == 0) { + DEBUG_PRINT("cb_pcidas64: premature interrupt, ignoring", + status); + return IRQ_HANDLED; + } + handle_ai_interrupt(dev, status, plx_status); + handle_ao_interrupt(dev, status, plx_status); + + /* clear possible plx9080 interrupt sources */ + if (plx_status & ICS_LDIA) { /* clear local doorbell interrupt */ + plx_bits = readl(priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG); + writel(plx_bits, priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG); + DEBUG_PRINT(" cleared local doorbell bits 0x%x\n", plx_bits); + } + + DEBUG_PRINT("exiting handler\n"); + + return IRQ_HANDLED; +} + +static void abort_dma(struct comedi_device *dev, unsigned int channel) +{ + unsigned long flags; + + /* spinlock for plx dma control/status reg */ + spin_lock_irqsave(&dev->spinlock, flags); + + plx9080_abort_dma(priv(dev)->plx9080_iobase, channel); + + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static int ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + if (priv(dev)->ai_cmd_running == 0) { + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + priv(dev)->ai_cmd_running = 0; + spin_unlock_irqrestore(&dev->spinlock, flags); + + disable_ai_pacing(dev); + + abort_dma(dev, 1); + + DEBUG_PRINT("ai canceled\n"); + return 0; +} + +static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + int range = CR_RANGE(insn->chanspec); + + /* do some initializing */ + writew(0, priv(dev)->main_iobase + DAC_CONTROL0_REG); + + /* set range */ + set_dac_range_bits(dev, &priv(dev)->dac_control1_bits, chan, range); + writew(priv(dev)->dac_control1_bits, + priv(dev)->main_iobase + DAC_CONTROL1_REG); + + /* write to channel */ + if (board(dev)->layout == LAYOUT_4020) { + writew(data[0] & 0xff, + priv(dev)->main_iobase + dac_lsb_4020_reg(chan)); + writew((data[0] >> 8) & 0xf, + priv(dev)->main_iobase + dac_msb_4020_reg(chan)); + } else { + writew(data[0], priv(dev)->main_iobase + dac_convert_reg(chan)); + } + + /* remember output value */ + priv(dev)->ao_value[chan] = data[0]; + + return 1; +} + +static int ao_readback_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = priv(dev)->ao_value[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static void set_dac_control0_reg(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + unsigned int bits = DAC_ENABLE_BIT | WAVEFORM_GATE_LEVEL_BIT | + WAVEFORM_GATE_ENABLE_BIT | WAVEFORM_GATE_SELECT_BIT; + + if (cmd->start_src == TRIG_EXT) { + bits |= WAVEFORM_TRIG_EXT_BITS; + if (cmd->start_arg & CR_INVERT) + bits |= WAVEFORM_TRIG_FALLING_BIT; + } else { + bits |= WAVEFORM_TRIG_SOFT_BITS; + } + if (cmd->scan_begin_src == TRIG_EXT) { + bits |= DAC_EXT_UPDATE_ENABLE_BIT; + if (cmd->scan_begin_arg & CR_INVERT) + bits |= DAC_EXT_UPDATE_FALLING_BIT; + } + writew(bits, priv(dev)->main_iobase + DAC_CONTROL0_REG); +} + +static void set_dac_control1_reg(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + int i; + + for (i = 0; i < cmd->chanlist_len; i++) { + int channel, range; + + channel = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + set_dac_range_bits(dev, &priv(dev)->dac_control1_bits, channel, + range); + } + priv(dev)->dac_control1_bits |= DAC_SW_GATE_BIT; + writew(priv(dev)->dac_control1_bits, + priv(dev)->main_iobase + DAC_CONTROL1_REG); +} + +static void set_dac_select_reg(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + uint16_t bits; + unsigned int first_channel, last_channel; + + first_channel = CR_CHAN(cmd->chanlist[0]); + last_channel = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); + if (last_channel < first_channel) + comedi_error(dev, "bug! last ao channel < first ao channel"); + + bits = (first_channel & 0x7) | (last_channel & 0x7) << 3; + + writew(bits, priv(dev)->main_iobase + DAC_SELECT_REG); +} + +static void set_dac_interval_regs(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + unsigned int divisor; + + if (cmd->scan_begin_src != TRIG_TIMER) + return; + + divisor = get_ao_divisor(cmd->scan_begin_arg, cmd->flags); + if (divisor > max_counter_value) { + comedi_error(dev, "bug! ao divisor too big"); + divisor = max_counter_value; + } + writew(divisor & 0xffff, + priv(dev)->main_iobase + DAC_SAMPLE_INTERVAL_LOWER_REG); + writew((divisor >> 16) & 0xff, + priv(dev)->main_iobase + DAC_SAMPLE_INTERVAL_UPPER_REG); +} + +static unsigned int load_ao_dma_buffer(struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + unsigned int num_bytes, buffer_index, prev_buffer_index; + unsigned int next_bits; + + buffer_index = priv(dev)->ao_dma_index; + prev_buffer_index = prev_ao_dma_index(dev); + + DEBUG_PRINT("attempting to load ao buffer %i (0x%x)\n", buffer_index, + priv(dev)->ao_buffer_bus_addr[buffer_index]); + + num_bytes = comedi_buf_read_n_available(dev->write_subdev->async); + if (num_bytes > DMA_BUFFER_SIZE) + num_bytes = DMA_BUFFER_SIZE; + if (cmd->stop_src == TRIG_COUNT && num_bytes > priv(dev)->ao_count) + num_bytes = priv(dev)->ao_count; + num_bytes -= num_bytes % bytes_in_sample; + + if (num_bytes == 0) + return 0; + + DEBUG_PRINT("loading %i bytes\n", num_bytes); + + num_bytes = cfc_read_array_from_buffer(dev->write_subdev, + priv(dev)-> + ao_buffer[buffer_index], + num_bytes); + priv(dev)->ao_dma_desc[buffer_index].transfer_size = + cpu_to_le32(num_bytes); + /* set end of chain bit so we catch underruns */ + next_bits = le32_to_cpu(priv(dev)->ao_dma_desc[buffer_index].next); + next_bits |= PLX_END_OF_CHAIN_BIT; + priv(dev)->ao_dma_desc[buffer_index].next = cpu_to_le32(next_bits); + /* clear end of chain bit on previous buffer now that we have set it + * for the last buffer */ + next_bits = le32_to_cpu(priv(dev)->ao_dma_desc[prev_buffer_index].next); + next_bits &= ~PLX_END_OF_CHAIN_BIT; + priv(dev)->ao_dma_desc[prev_buffer_index].next = cpu_to_le32(next_bits); + + priv(dev)->ao_dma_index = (buffer_index + 1) % AO_DMA_RING_COUNT; + priv(dev)->ao_count -= num_bytes; + + return num_bytes; +} + +static void load_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd) +{ + unsigned int num_bytes; + unsigned int next_transfer_addr; + void __iomem *pci_addr_reg = + priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG; + unsigned int buffer_index; + + do { + buffer_index = priv(dev)->ao_dma_index; + /* don't overwrite data that hasn't been transferred yet */ + next_transfer_addr = readl(pci_addr_reg); + if (next_transfer_addr >= + priv(dev)->ao_buffer_bus_addr[buffer_index] + && next_transfer_addr < + priv(dev)->ao_buffer_bus_addr[buffer_index] + + DMA_BUFFER_SIZE) + return; + num_bytes = load_ao_dma_buffer(dev, cmd); + } while (num_bytes >= DMA_BUFFER_SIZE); +} + +static int prep_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd) +{ + unsigned int num_bytes; + int i; + + /* clear queue pointer too, since external queue has + * weird interactions with ao fifo */ + writew(0, priv(dev)->main_iobase + ADC_QUEUE_CLEAR_REG); + writew(0, priv(dev)->main_iobase + DAC_BUFFER_CLEAR_REG); + + num_bytes = (DAC_FIFO_SIZE / 2) * bytes_in_sample; + if (cmd->stop_src == TRIG_COUNT && + num_bytes / bytes_in_sample > priv(dev)->ao_count) + num_bytes = priv(dev)->ao_count * bytes_in_sample; + num_bytes = cfc_read_array_from_buffer(dev->write_subdev, + priv(dev)->ao_bounce_buffer, + num_bytes); + for (i = 0; i < num_bytes / bytes_in_sample; i++) { + writew(priv(dev)->ao_bounce_buffer[i], + priv(dev)->main_iobase + DAC_FIFO_REG); + } + priv(dev)->ao_count -= num_bytes / bytes_in_sample; + if (cmd->stop_src == TRIG_COUNT && priv(dev)->ao_count == 0) + return 0; + num_bytes = load_ao_dma_buffer(dev, cmd); + if (num_bytes == 0) + return -1; + if (num_bytes >= DMA_BUFFER_SIZE) ; + load_ao_dma(dev, cmd); + + dma_start_sync(dev, 0); + + return 0; +} + +static inline int external_ai_queue_in_use(struct comedi_device *dev) +{ + if (dev->read_subdev->busy) + return 0; + if (board(dev)->layout == LAYOUT_4020) + return 0; + else if (use_internal_queue_6xxx(&dev->read_subdev->async->cmd)) + return 0; + return 1; +} + +static int ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + if (external_ai_queue_in_use(dev)) { + warn_external_queue(dev); + return -EBUSY; + } + /* disable analog output system during setup */ + writew(0x0, priv(dev)->main_iobase + DAC_CONTROL0_REG); + + priv(dev)->ao_dma_index = 0; + priv(dev)->ao_count = cmd->stop_arg * cmd->chanlist_len; + + set_dac_select_reg(dev, cmd); + set_dac_interval_regs(dev, cmd); + load_first_dma_descriptor(dev, 0, priv(dev)->ao_dma_desc_bus_addr | + PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT); + + set_dac_control1_reg(dev, cmd); + s->async->inttrig = ao_inttrig; + + return 0; +} + +static int ao_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trig_num) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int retval; + + if (trig_num != 0) + return -EINVAL; + + retval = prep_ao_dma(dev, cmd); + if (retval < 0) + return -EPIPE; + + set_dac_control0_reg(dev, cmd); + + if (cmd->start_src == TRIG_INT) + writew(0, priv(dev)->main_iobase + DAC_START_REG); + + s->async->inttrig = NULL; + + return 0; +} + +static int ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + unsigned int tmp_arg; + int i; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_INT | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + /* uniqueness check */ + if (cmd->start_src != TRIG_INT && cmd->start_src != TRIG_EXT) + err++; + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) + err++; + + /* compatibility check */ + if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < board(dev)->ao_scan_speed) { + cmd->scan_begin_arg = board(dev)->ao_scan_speed; + err++; + } + if (get_ao_divisor(cmd->scan_begin_arg, + cmd->flags) > max_counter_value) { + cmd->scan_begin_arg = + (max_counter_value + 2) * TIMER_BASE; + err++; + } + } + + if (!cmd->chanlist_len) { + cmd->chanlist_len = 1; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp_arg = cmd->scan_begin_arg; + cmd->scan_begin_arg = + get_divisor(cmd->scan_begin_arg, cmd->flags) * TIMER_BASE; + if (tmp_arg != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + if (cmd->chanlist) { + unsigned int first_channel = CR_CHAN(cmd->chanlist[0]); + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != first_channel + i) { + comedi_error(dev, + "chanlist must use consecutive channels"); + err++; + break; + } + } + } + + if (err) + return 5; + + return 0; +} + +static int ao_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + writew(0x0, priv(dev)->main_iobase + DAC_CONTROL0_REG); + abort_dma(dev, 0); + return 0; +} + +static int dio_callback(int dir, int port, int data, unsigned long arg) +{ + void __iomem *iobase = (void __iomem *)arg; + if (dir) { + writeb(data, iobase + port); + DEBUG_PRINT("wrote 0x%x to port %i\n", data, port); + return 0; + } else { + return readb(iobase + port); + } +} + +static int dio_callback_4020(int dir, int port, int data, unsigned long arg) +{ + void __iomem *iobase = (void __iomem *)arg; + if (dir) { + writew(data, iobase + 2 * port); + return 0; + } else { + return readw(iobase + 2 * port); + } +} + +static int di_rbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int bits; + + bits = readb(priv(dev)->dio_counter_iobase + DI_REG); + bits &= 0xf; + data[1] = bits; + data[0] = 0; + + return 2; +} + +static int do_wbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] &= 0xf; + /* zero bits we are going to change */ + s->state &= ~data[0]; + /* set new bits */ + s->state |= data[0] & data[1]; + + writeb(s->state, priv(dev)->dio_counter_iobase + DO_REG); + + data[1] = s->state; + + return 2; +} + +static int dio_60xx_config_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int mask; + + mask = 1 << CR_CHAN(insn->chanspec); + + switch (data[0]) { + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~mask; + break; + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= mask; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT; + return 2; + default: + return -EINVAL; + } + + writeb(s->io_bits, + priv(dev)->dio_counter_iobase + DIO_DIRECTION_60XX_REG); + + return 1; +} + +static int dio_60xx_wbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + writeb(s->state, + priv(dev)->dio_counter_iobase + DIO_DATA_60XX_REG); + } + + data[1] = readb(priv(dev)->dio_counter_iobase + DIO_DATA_60XX_REG); + + return 2; +} + +static void caldac_write(struct comedi_device *dev, unsigned int channel, + unsigned int value) +{ + priv(dev)->caldac_state[channel] = value; + + switch (board(dev)->layout) { + case LAYOUT_60XX: + case LAYOUT_64XX: + caldac_8800_write(dev, channel, value); + break; + case LAYOUT_4020: + caldac_i2c_write(dev, channel, value); + break; + default: + break; + } +} + +static int calib_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int channel = CR_CHAN(insn->chanspec); + + /* return immediately if setting hasn't changed, since + * programming these things is slow */ + if (priv(dev)->caldac_state[channel] == data[0]) + return 1; + + caldac_write(dev, channel, data[0]); + + return 1; +} + +static int calib_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int channel = CR_CHAN(insn->chanspec); + + data[0] = priv(dev)->caldac_state[channel]; + + return 1; +} + +static void ad8402_write(struct comedi_device *dev, unsigned int channel, + unsigned int value) +{ + static const int bitstream_length = 10; + unsigned int bit, register_bits; + unsigned int bitstream = ((channel & 0x3) << 8) | (value & 0xff); + static const int ad8402_udelay = 1; + + priv(dev)->ad8402_state[channel] = value; + + register_bits = SELECT_8402_64XX_BIT; + udelay(ad8402_udelay); + writew(register_bits, priv(dev)->main_iobase + CALIBRATION_REG); + + for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) { + if (bitstream & bit) + register_bits |= SERIAL_DATA_IN_BIT; + else + register_bits &= ~SERIAL_DATA_IN_BIT; + udelay(ad8402_udelay); + writew(register_bits, priv(dev)->main_iobase + CALIBRATION_REG); + udelay(ad8402_udelay); + writew(register_bits | SERIAL_CLOCK_BIT, + priv(dev)->main_iobase + CALIBRATION_REG); + } + + udelay(ad8402_udelay); + writew(0, priv(dev)->main_iobase + CALIBRATION_REG); +} + +/* for pci-das6402/16, channel 0 is analog input gain and channel 1 is offset */ +static int ad8402_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int channel = CR_CHAN(insn->chanspec); + + /* return immediately if setting hasn't changed, since + * programming these things is slow */ + if (priv(dev)->ad8402_state[channel] == data[0]) + return 1; + + priv(dev)->ad8402_state[channel] = data[0]; + + ad8402_write(dev, channel, data[0]); + + return 1; +} + +static int ad8402_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int channel = CR_CHAN(insn->chanspec); + + data[0] = priv(dev)->ad8402_state[channel]; + + return 1; +} + +static uint16_t read_eeprom(struct comedi_device *dev, uint8_t address) +{ + static const int bitstream_length = 11; + static const int read_command = 0x6; + unsigned int bitstream = (read_command << 8) | address; + unsigned int bit; + void __iomem * const plx_control_addr = + priv(dev)->plx9080_iobase + PLX_CONTROL_REG; + uint16_t value; + static const int value_length = 16; + static const int eeprom_udelay = 1; + + udelay(eeprom_udelay); + priv(dev)->plx_control_bits &= ~CTL_EE_CLK & ~CTL_EE_CS; + /* make sure we don't send anything to the i2c bus on 4020 */ + priv(dev)->plx_control_bits |= CTL_USERO; + writel(priv(dev)->plx_control_bits, plx_control_addr); + /* activate serial eeprom */ + udelay(eeprom_udelay); + priv(dev)->plx_control_bits |= CTL_EE_CS; + writel(priv(dev)->plx_control_bits, plx_control_addr); + + /* write read command and desired memory address */ + for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) { + /* set bit to be written */ + udelay(eeprom_udelay); + if (bitstream & bit) + priv(dev)->plx_control_bits |= CTL_EE_W; + else + priv(dev)->plx_control_bits &= ~CTL_EE_W; + writel(priv(dev)->plx_control_bits, plx_control_addr); + /* clock in bit */ + udelay(eeprom_udelay); + priv(dev)->plx_control_bits |= CTL_EE_CLK; + writel(priv(dev)->plx_control_bits, plx_control_addr); + udelay(eeprom_udelay); + priv(dev)->plx_control_bits &= ~CTL_EE_CLK; + writel(priv(dev)->plx_control_bits, plx_control_addr); + } + /* read back value from eeprom memory location */ + value = 0; + for (bit = 1 << (value_length - 1); bit; bit >>= 1) { + /* clock out bit */ + udelay(eeprom_udelay); + priv(dev)->plx_control_bits |= CTL_EE_CLK; + writel(priv(dev)->plx_control_bits, plx_control_addr); + udelay(eeprom_udelay); + priv(dev)->plx_control_bits &= ~CTL_EE_CLK; + writel(priv(dev)->plx_control_bits, plx_control_addr); + udelay(eeprom_udelay); + if (readl(plx_control_addr) & CTL_EE_R) + value |= bit; + } + + /* deactivate eeprom serial input */ + udelay(eeprom_udelay); + priv(dev)->plx_control_bits &= ~CTL_EE_CS; + writel(priv(dev)->plx_control_bits, plx_control_addr); + + return value; +} + +static int eeprom_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = read_eeprom(dev, CR_CHAN(insn->chanspec)); + + return 1; +} + +/* utility function that rounds desired timing to an achievable time, and + * sets cmd members appropriately. + * adc paces conversions from master clock by dividing by (x + 3) where x is 24 bit number + */ +static void check_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd) +{ + unsigned int convert_divisor = 0, scan_divisor; + static const int min_convert_divisor = 3; + static const int max_convert_divisor = + max_counter_value + min_convert_divisor; + static const int min_scan_divisor_4020 = 2; + unsigned long long max_scan_divisor, min_scan_divisor; + + if (cmd->convert_src == TRIG_TIMER) { + if (board(dev)->layout == LAYOUT_4020) { + cmd->convert_arg = 0; + } else { + convert_divisor = + get_divisor(cmd->convert_arg, cmd->flags); + if (convert_divisor > max_convert_divisor) + convert_divisor = max_convert_divisor; + if (convert_divisor < min_convert_divisor) + convert_divisor = min_convert_divisor; + cmd->convert_arg = convert_divisor * TIMER_BASE; + } + } else if (cmd->convert_src == TRIG_NOW) + cmd->convert_arg = 0; + + if (cmd->scan_begin_src == TRIG_TIMER) { + scan_divisor = get_divisor(cmd->scan_begin_arg, cmd->flags); + if (cmd->convert_src == TRIG_TIMER) { + /* XXX check for integer overflows */ + min_scan_divisor = convert_divisor * cmd->chanlist_len; + max_scan_divisor = + (convert_divisor * cmd->chanlist_len - 1) + + max_counter_value; + } else { + min_scan_divisor = min_scan_divisor_4020; + max_scan_divisor = max_counter_value + min_scan_divisor; + } + if (scan_divisor > max_scan_divisor) + scan_divisor = max_scan_divisor; + if (scan_divisor < min_scan_divisor) + scan_divisor = min_scan_divisor; + cmd->scan_begin_arg = scan_divisor * TIMER_BASE; + } + + return; +} + +/* Gets nearest achievable timing given master clock speed, does not + * take into account possible minimum/maximum divisor values. Used + * by other timing checking functions. */ +static unsigned int get_divisor(unsigned int ns, unsigned int flags) +{ + unsigned int divisor; + + switch (flags & TRIG_ROUND_MASK) { + case TRIG_ROUND_UP: + divisor = (ns + TIMER_BASE - 1) / TIMER_BASE; + break; + case TRIG_ROUND_DOWN: + divisor = ns / TIMER_BASE; + break; + case TRIG_ROUND_NEAREST: + default: + divisor = (ns + TIMER_BASE / 2) / TIMER_BASE; + break; + } + return divisor; +} + +static unsigned int get_ao_divisor(unsigned int ns, unsigned int flags) +{ + return get_divisor(ns, flags) - 2; +} + +/* adjusts the size of hardware fifo (which determines block size for dma xfers) */ +static int set_ai_fifo_size(struct comedi_device *dev, unsigned int num_samples) +{ + unsigned int num_fifo_entries; + int retval; + const struct hw_fifo_info *const fifo = board(dev)->ai_fifo; + + num_fifo_entries = num_samples / fifo->sample_packing_ratio; + + retval = set_ai_fifo_segment_length(dev, + num_fifo_entries / + fifo->num_segments); + if (retval < 0) + return retval; + + num_samples = retval * fifo->num_segments * fifo->sample_packing_ratio; + + DEBUG_PRINT("set hardware fifo size to %i\n", num_samples); + + return num_samples; +} + +/* query length of fifo */ +static unsigned int ai_fifo_size(struct comedi_device *dev) +{ + return priv(dev)->ai_fifo_segment_length * + board(dev)->ai_fifo->num_segments * + board(dev)->ai_fifo->sample_packing_ratio; +} + +static int set_ai_fifo_segment_length(struct comedi_device *dev, + unsigned int num_entries) +{ + static const int increment_size = 0x100; + const struct hw_fifo_info *const fifo = board(dev)->ai_fifo; + unsigned int num_increments; + uint16_t bits; + + if (num_entries < increment_size) + num_entries = increment_size; + if (num_entries > fifo->max_segment_length) + num_entries = fifo->max_segment_length; + + /* 1 == 256 entries, 2 == 512 entries, etc */ + num_increments = (num_entries + increment_size / 2) / increment_size; + + bits = (~(num_increments - 1)) & fifo->fifo_size_reg_mask; + priv(dev)->fifo_size_bits &= ~fifo->fifo_size_reg_mask; + priv(dev)->fifo_size_bits |= bits; + writew(priv(dev)->fifo_size_bits, + priv(dev)->main_iobase + FIFO_SIZE_REG); + + priv(dev)->ai_fifo_segment_length = num_increments * increment_size; + + DEBUG_PRINT("set hardware fifo segment length to %i\n", + priv(dev)->ai_fifo_segment_length); + + return priv(dev)->ai_fifo_segment_length; +} + +/* pci-6025 8800 caldac: + * address 0 == dac channel 0 offset + * address 1 == dac channel 0 gain + * address 2 == dac channel 1 offset + * address 3 == dac channel 1 gain + * address 4 == fine adc offset + * address 5 == coarse adc offset + * address 6 == coarse adc gain + * address 7 == fine adc gain + */ +/* pci-6402/16 uses all 8 channels for dac: + * address 0 == dac channel 0 fine gain + * address 1 == dac channel 0 coarse gain + * address 2 == dac channel 0 coarse offset + * address 3 == dac channel 1 coarse offset + * address 4 == dac channel 1 fine gain + * address 5 == dac channel 1 coarse gain + * address 6 == dac channel 0 fine offset + * address 7 == dac channel 1 fine offset +*/ + +static int caldac_8800_write(struct comedi_device *dev, unsigned int address, + uint8_t value) +{ + static const int num_caldac_channels = 8; + static const int bitstream_length = 11; + unsigned int bitstream = ((address & 0x7) << 8) | value; + unsigned int bit, register_bits; + static const int caldac_8800_udelay = 1; + + if (address >= num_caldac_channels) { + comedi_error(dev, "illegal caldac channel"); + return -1; + } + for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) { + register_bits = 0; + if (bitstream & bit) + register_bits |= SERIAL_DATA_IN_BIT; + udelay(caldac_8800_udelay); + writew(register_bits, priv(dev)->main_iobase + CALIBRATION_REG); + register_bits |= SERIAL_CLOCK_BIT; + udelay(caldac_8800_udelay); + writew(register_bits, priv(dev)->main_iobase + CALIBRATION_REG); + } + udelay(caldac_8800_udelay); + writew(SELECT_8800_BIT, priv(dev)->main_iobase + CALIBRATION_REG); + udelay(caldac_8800_udelay); + writew(0, priv(dev)->main_iobase + CALIBRATION_REG); + udelay(caldac_8800_udelay); + return 0; +} + +/* 4020 caldacs */ +static int caldac_i2c_write(struct comedi_device *dev, + unsigned int caldac_channel, unsigned int value) +{ + uint8_t serial_bytes[3]; + uint8_t i2c_addr; + enum pointer_bits { + /* manual has gain and offset bits switched */ + OFFSET_0_2 = 0x1, + GAIN_0_2 = 0x2, + OFFSET_1_3 = 0x4, + GAIN_1_3 = 0x8, + }; + enum data_bits { + NOT_CLEAR_REGISTERS = 0x20, + }; + + switch (caldac_channel) { + case 0: /* chan 0 offset */ + i2c_addr = CALDAC0_I2C_ADDR; + serial_bytes[0] = OFFSET_0_2; + break; + case 1: /* chan 1 offset */ + i2c_addr = CALDAC0_I2C_ADDR; + serial_bytes[0] = OFFSET_1_3; + break; + case 2: /* chan 2 offset */ + i2c_addr = CALDAC1_I2C_ADDR; + serial_bytes[0] = OFFSET_0_2; + break; + case 3: /* chan 3 offset */ + i2c_addr = CALDAC1_I2C_ADDR; + serial_bytes[0] = OFFSET_1_3; + break; + case 4: /* chan 0 gain */ + i2c_addr = CALDAC0_I2C_ADDR; + serial_bytes[0] = GAIN_0_2; + break; + case 5: /* chan 1 gain */ + i2c_addr = CALDAC0_I2C_ADDR; + serial_bytes[0] = GAIN_1_3; + break; + case 6: /* chan 2 gain */ + i2c_addr = CALDAC1_I2C_ADDR; + serial_bytes[0] = GAIN_0_2; + break; + case 7: /* chan 3 gain */ + i2c_addr = CALDAC1_I2C_ADDR; + serial_bytes[0] = GAIN_1_3; + break; + default: + comedi_error(dev, "invalid caldac channel\n"); + return -1; + break; + } + serial_bytes[1] = NOT_CLEAR_REGISTERS | ((value >> 8) & 0xf); + serial_bytes[2] = value & 0xff; + i2c_write(dev, i2c_addr, serial_bytes, 3); + return 0; +} + +/* Their i2c requires a huge delay on setting clock or data high for some reason */ +static const int i2c_high_udelay = 1000; +static const int i2c_low_udelay = 10; + +/* set i2c data line high or low */ +static void i2c_set_sda(struct comedi_device *dev, int state) +{ + static const int data_bit = CTL_EE_W; + void __iomem *plx_control_addr = priv(dev)->plx9080_iobase + + PLX_CONTROL_REG; + + if (state) { + /* set data line high */ + priv(dev)->plx_control_bits &= ~data_bit; + writel(priv(dev)->plx_control_bits, plx_control_addr); + udelay(i2c_high_udelay); + } else { /* set data line low */ + + priv(dev)->plx_control_bits |= data_bit; + writel(priv(dev)->plx_control_bits, plx_control_addr); + udelay(i2c_low_udelay); + } +} + +/* set i2c clock line high or low */ +static void i2c_set_scl(struct comedi_device *dev, int state) +{ + static const int clock_bit = CTL_USERO; + void __iomem *plx_control_addr = priv(dev)->plx9080_iobase + + PLX_CONTROL_REG; + + if (state) { + /* set clock line high */ + priv(dev)->plx_control_bits &= ~clock_bit; + writel(priv(dev)->plx_control_bits, plx_control_addr); + udelay(i2c_high_udelay); + } else { /* set clock line low */ + + priv(dev)->plx_control_bits |= clock_bit; + writel(priv(dev)->plx_control_bits, plx_control_addr); + udelay(i2c_low_udelay); + } +} + +static void i2c_write_byte(struct comedi_device *dev, uint8_t byte) +{ + uint8_t bit; + unsigned int num_bits = 8; + + DEBUG_PRINT("writing to i2c byte 0x%x\n", byte); + + for (bit = 1 << (num_bits - 1); bit; bit >>= 1) { + i2c_set_scl(dev, 0); + if ((byte & bit)) + i2c_set_sda(dev, 1); + else + i2c_set_sda(dev, 0); + i2c_set_scl(dev, 1); + } +} + +/* we can't really read the lines, so fake it */ +static int i2c_read_ack(struct comedi_device *dev) +{ + i2c_set_scl(dev, 0); + i2c_set_sda(dev, 1); + i2c_set_scl(dev, 1); + + return 0; /* return fake acknowledge bit */ +} + +/* send start bit */ +static void i2c_start(struct comedi_device *dev) +{ + i2c_set_scl(dev, 1); + i2c_set_sda(dev, 1); + i2c_set_sda(dev, 0); +} + +/* send stop bit */ +static void i2c_stop(struct comedi_device *dev) +{ + i2c_set_scl(dev, 0); + i2c_set_sda(dev, 0); + i2c_set_scl(dev, 1); + i2c_set_sda(dev, 1); +} + +static void i2c_write(struct comedi_device *dev, unsigned int address, + const uint8_t * data, unsigned int length) +{ + unsigned int i; + uint8_t bitstream; + static const int read_bit = 0x1; + +/* XXX need mutex to prevent simultaneous attempts to access eeprom and i2c bus */ + + /* make sure we dont send anything to eeprom */ + priv(dev)->plx_control_bits &= ~CTL_EE_CS; + + i2c_stop(dev); + i2c_start(dev); + + /* send address and write bit */ + bitstream = (address << 1) & ~read_bit; + i2c_write_byte(dev, bitstream); + + /* get acknowledge */ + if (i2c_read_ack(dev) != 0) { + comedi_error(dev, "i2c write failed: no acknowledge"); + i2c_stop(dev); + return; + } + /* write data bytes */ + for (i = 0; i < length; i++) { + i2c_write_byte(dev, data[i]); + if (i2c_read_ack(dev) != 0) { + comedi_error(dev, "i2c write failed: no acknowledge"); + i2c_stop(dev); + return; + } + } + i2c_stop(dev); +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/cb_pcidda.c b/drivers/staging/comedi/drivers/cb_pcidda.c new file mode 100644 index 00000000..abba220a --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcidda.c @@ -0,0 +1,890 @@ +/* + comedi/drivers/cb_pcidda.c + This intends to be a driver for the ComputerBoards / MeasurementComputing + PCI-DDA series. + + Copyright (C) 2001 Ivan Martinez + Copyright (C) 2001 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-8 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: cb_pcidda +Description: MeasurementComputing PCI-DDA series +Author: Ivan Martinez , Frank Mori Hess +Status: Supports 08/16, 04/16, 02/16, 08/12, 04/12, and 02/12 +Devices: [Measurement Computing] PCI-DDA08/12 (cb_pcidda), PCI-DDA04/12, + PCI-DDA02/12, PCI-DDA08/16, PCI-DDA04/16, PCI-DDA02/16 + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first available PCI + device will be used. + +Only simple analog output writing is supported. + +So far it has only been tested with: + - PCI-DDA08/12 +Please report success/failure with other different cards to +. +*/ + +#include "../comedidev.h" + +#include "comedi_pci.h" +#include "8255.h" + +#define PCI_VENDOR_ID_CB 0x1307 /* PCI vendor number of ComputerBoards */ +#define EEPROM_SIZE 128 /* number of entries in eeprom */ +#define MAX_AO_CHANNELS 8 /* maximum number of ao channels for supported boards */ + +/* PCI-DDA base addresses */ +#define DIGITALIO_BADRINDEX 2 + /* DIGITAL I/O is pci_dev->resource[2] */ +#define DIGITALIO_SIZE 8 + /* DIGITAL I/O uses 8 I/O port addresses */ +#define DAC_BADRINDEX 3 + /* DAC is pci_dev->resource[3] */ + +/* Digital I/O registers */ +#define PORT1A 0 /* PORT 1A DATA */ + +#define PORT1B 1 /* PORT 1B DATA */ + +#define PORT1C 2 /* PORT 1C DATA */ + +#define CONTROL1 3 /* CONTROL REGISTER 1 */ + +#define PORT2A 4 /* PORT 2A DATA */ + +#define PORT2B 5 /* PORT 2B DATA */ + +#define PORT2C 6 /* PORT 2C DATA */ + +#define CONTROL2 7 /* CONTROL REGISTER 2 */ + +/* DAC registers */ +#define DACONTROL 0 /* D/A CONTROL REGISTER */ +#define SU 0000001 /* Simultaneous update enabled */ +#define NOSU 0000000 /* Simultaneous update disabled */ +#define ENABLEDAC 0000002 /* Enable specified DAC */ +#define DISABLEDAC 0000000 /* Disable specified DAC */ +#define RANGE2V5 0000000 /* 2.5V */ +#define RANGE5V 0000200 /* 5V */ +#define RANGE10V 0000300 /* 10V */ +#define UNIP 0000400 /* Unipolar outputs */ +#define BIP 0000000 /* Bipolar outputs */ + +#define DACALIBRATION1 4 /* D/A CALIBRATION REGISTER 1 */ +/* write bits */ +#define SERIAL_IN_BIT 0x1 /* serial data input for eeprom, caldacs, reference dac */ +#define CAL_CHANNEL_MASK (0x7 << 1) +#define CAL_CHANNEL_BITS(channel) (((channel) << 1) & CAL_CHANNEL_MASK) +/* read bits */ +#define CAL_COUNTER_MASK 0x1f +#define CAL_COUNTER_OVERFLOW_BIT 0x20 /* calibration counter overflow status bit */ +#define AO_BELOW_REF_BIT 0x40 /* analog output is less than reference dac voltage */ +#define SERIAL_OUT_BIT 0x80 /* serial data out, for reading from eeprom */ + +#define DACALIBRATION2 6 /* D/A CALIBRATION REGISTER 2 */ +#define SELECT_EEPROM_BIT 0x1 /* send serial data in to eeprom */ +#define DESELECT_REF_DAC_BIT 0x2 /* don't send serial data to MAX542 reference dac */ +#define DESELECT_CALDAC_BIT(n) (0x4 << (n)) /* don't send serial data to caldac n */ +#define DUMMY_BIT 0x40 /* manual says to set this bit with no explanation */ + +#define DADATA 8 /* FIRST D/A DATA REGISTER (0) */ + +static const struct comedi_lrange cb_pcidda_ranges = { + 6, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + } +}; + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +struct cb_pcidda_board { + const char *name; + char status; /* Driver status: */ + + /* + * 0 - tested + * 1 - manual read, not tested + * 2 - manual not read + */ + + unsigned short device_id; + int ao_chans; + int ao_bits; + const struct comedi_lrange *ranges; +}; + +static const struct cb_pcidda_board cb_pcidda_boards[] = { + { + .name = "pci-dda02/12", + .status = 1, + .device_id = 0x20, + .ao_chans = 2, + .ao_bits = 12, + .ranges = &cb_pcidda_ranges, + }, + { + .name = "pci-dda04/12", + .status = 1, + .device_id = 0x21, + .ao_chans = 4, + .ao_bits = 12, + .ranges = &cb_pcidda_ranges, + }, + { + .name = "pci-dda08/12", + .status = 0, + .device_id = 0x22, + .ao_chans = 8, + .ao_bits = 12, + .ranges = &cb_pcidda_ranges, + }, + { + .name = "pci-dda02/16", + .status = 2, + .device_id = 0x23, + .ao_chans = 2, + .ao_bits = 16, + .ranges = &cb_pcidda_ranges, + }, + { + .name = "pci-dda04/16", + .status = 2, + .device_id = 0x24, + .ao_chans = 4, + .ao_bits = 16, + .ranges = &cb_pcidda_ranges, + }, + { + .name = "pci-dda08/16", + .status = 0, + .device_id = 0x25, + .ao_chans = 8, + .ao_bits = 16, + .ranges = &cb_pcidda_ranges, + }, +}; + +static DEFINE_PCI_DEVICE_TABLE(cb_pcidda_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0020) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0021) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0022) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0023) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0024) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0025) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, cb_pcidda_pci_table); + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct cb_pcidda_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. */ +struct cb_pcidda_private { + int data; + + /* would be useful for a PCI device */ + struct pci_dev *pci_dev; + + unsigned long digitalio; + unsigned long dac; + + /* unsigned long control_status; */ + /* unsigned long adc_fifo; */ + + unsigned int dac_cal1_bits; /* bits last written to da calibration register 1 */ + unsigned int ao_range[MAX_AO_CHANNELS]; /* current range settings for output channels */ + u16 eeprom_data[EEPROM_SIZE]; /* software copy of board's eeprom */ +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct cb_pcidda_private *)dev->private) + +static int cb_pcidda_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int cb_pcidda_detach(struct comedi_device *dev); +/* static int cb_pcidda_ai_rinsn(struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data); */ +static int cb_pcidda_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* static int cb_pcidda_ai_cmd(struct comedi_device *dev, struct *comedi_subdevice *s);*/ +/* static int cb_pcidda_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_cmd *cmd); */ +/* static int cb_pcidda_ns_to_timer(unsigned int *ns,int *round); */ + +static unsigned int cb_pcidda_serial_in(struct comedi_device *dev); +static void cb_pcidda_serial_out(struct comedi_device *dev, unsigned int value, + unsigned int num_bits); +static unsigned int cb_pcidda_read_eeprom(struct comedi_device *dev, + unsigned int address); +static void cb_pcidda_calibrate(struct comedi_device *dev, unsigned int channel, + unsigned int range); + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static struct comedi_driver driver_cb_pcidda = { + .driver_name = "cb_pcidda", + .module = THIS_MODULE, + .attach = cb_pcidda_attach, + .detach = cb_pcidda_detach, +}; + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. + */ +static int cb_pcidda_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + struct pci_dev *pcidev = NULL; + int index; + + +/* + * Allocate the private structure area. + */ + if (alloc_private(dev, sizeof(struct cb_pcidda_private)) < 0) + return -ENOMEM; + +/* + * Probe the device to determine what device in the series it is. + */ + + for_each_pci_dev(pcidev) { + if (pcidev->vendor == PCI_VENDOR_ID_CB) { + if (it->options[0] || it->options[1]) { + if (pcidev->bus->number != it->options[0] || + PCI_SLOT(pcidev->devfn) != it->options[1]) { + continue; + } + } + for (index = 0; index < ARRAY_SIZE(cb_pcidda_boards); index++) { + if (cb_pcidda_boards[index].device_id == + pcidev->device) { + goto found; + } + } + } + } + if (!pcidev) { + dev_err(dev->hw_dev, "Not a ComputerBoards/MeasurementComputing card on requested position\n"); + return -EIO; + } +found: + devpriv->pci_dev = pcidev; + dev->board_ptr = cb_pcidda_boards + index; + /* "thisboard" macro can be used from here. */ + dev_dbg(dev->hw_dev, "Found %s at requested position\n", + thisboard->name); + + /* + * Enable PCI device and request regions. + */ + if (comedi_pci_enable(pcidev, thisboard->name)) { + dev_err(dev->hw_dev, "cb_pcidda: failed to enable PCI device and request regions\n"); + return -EIO; + } + +/* + * Allocate the I/O ports. + */ + devpriv->digitalio = + pci_resource_start(devpriv->pci_dev, DIGITALIO_BADRINDEX); + devpriv->dac = pci_resource_start(devpriv->pci_dev, DAC_BADRINDEX); + +/* + * Warn about the status of the driver. + */ + if (thisboard->status == 2) + printk + ("WARNING: DRIVER FOR THIS BOARD NOT CHECKED WITH MANUAL. " + "WORKS ASSUMING FULL COMPATIBILITY WITH PCI-DDA08/12. " + "PLEASE REPORT USAGE TO .\n"); + +/* + * Initialize dev->board_name. + */ + dev->board_name = thisboard->name; + +/* + * Allocate the subdevice structures. + */ + if (alloc_subdevices(dev, 3) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = thisboard->ao_chans; + s->maxdata = (1 << thisboard->ao_bits) - 1; + s->range_table = thisboard->ranges; + s->insn_write = cb_pcidda_ao_winsn; + + /* s->subdev_flags |= SDF_CMD_READ; */ + /* s->do_cmd = cb_pcidda_ai_cmd; */ + /* s->do_cmdtest = cb_pcidda_ai_cmdtest; */ + + /* two 8255 digital io subdevices */ + s = dev->subdevices + 1; + subdev_8255_init(dev, s, NULL, devpriv->digitalio); + s = dev->subdevices + 2; + subdev_8255_init(dev, s, NULL, devpriv->digitalio + PORT2A); + + dev_dbg(dev->hw_dev, "eeprom:\n"); + for (index = 0; index < EEPROM_SIZE; index++) { + devpriv->eeprom_data[index] = cb_pcidda_read_eeprom(dev, index); + dev_dbg(dev->hw_dev, "%i:0x%x\n", index, devpriv->eeprom_data[index]); + } + + /* set calibrations dacs */ + for (index = 0; index < thisboard->ao_chans; index++) + cb_pcidda_calibrate(dev, index, devpriv->ao_range[index]); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int cb_pcidda_detach(struct comedi_device *dev) +{ +/* + * Deallocate the I/O ports. + */ + if (devpriv) { + if (devpriv->pci_dev) { + if (devpriv->dac) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + } + /* cleanup 8255 */ + if (dev->subdevices) { + subdev_8255_cleanup(dev, dev->subdevices + 1); + subdev_8255_cleanup(dev, dev->subdevices + 2); + } + + return 0; +} + +/* + * I will program this later... ;-) + */ +#if 0 +static int cb_pcidda_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + printk("cb_pcidda_ai_cmd\n"); + printk("subdev: %d\n", cmd->subdev); + printk("flags: %d\n", cmd->flags); + printk("start_src: %d\n", cmd->start_src); + printk("start_arg: %d\n", cmd->start_arg); + printk("scan_begin_src: %d\n", cmd->scan_begin_src); + printk("convert_src: %d\n", cmd->convert_src); + printk("convert_arg: %d\n", cmd->convert_arg); + printk("scan_end_src: %d\n", cmd->scan_end_src); + printk("scan_end_arg: %d\n", cmd->scan_end_arg); + printk("stop_src: %d\n", cmd->stop_src); + printk("stop_arg: %d\n", cmd->stop_arg); + printk("chanlist_len: %d\n", cmd->chanlist_len); +} +#endif + +#if 0 +static int cb_pcidda_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. */ + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER + && cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_TIMER && cmd->stop_src != TRIG_EXT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } +#define MAX_SPEED 10000 /* in nanoseconds */ +#define MIN_SPEED 1000000000 /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < MAX_SPEED) { + cmd->scan_begin_arg = MAX_SPEED; + err++; + } + if (cmd->scan_begin_arg > MIN_SPEED) { + cmd->scan_begin_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + if (cmd->scan_begin_arg > 9) { + cmd->scan_begin_arg = 9; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < MAX_SPEED) { + cmd->convert_arg = MAX_SPEED; + err++; + } + if (cmd->convert_arg > MIN_SPEED) { + cmd->convert_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* see above */ + if (cmd->convert_arg > 9) { + cmd->convert_arg = 9; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg > 0x00ffffff) { + cmd->stop_arg = 0x00ffffff; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + cb_pcidda_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + cb_pcidda_ns_to_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if (err) + return 4; + + return 0; +} +#endif + +/* This function doesn't require a particular form, this is just + * what happens to be used in some of the drivers. It should + * convert ns nanoseconds to a counter value suitable for programming + * the device. Also, it should adjust ns so that it cooresponds to + * the actual time that the device will use. */ +#if 0 +static int cb_pcidda_ns_to_timer(unsigned int *ns, int round) +{ + /* trivial timer */ + return *ns; +} +#endif + +static int cb_pcidda_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int command; + unsigned int channel, range; + + channel = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* adjust calibration dacs if range has changed */ + if (range != devpriv->ao_range[channel]) + cb_pcidda_calibrate(dev, channel, range); + + /* output channel configuration */ + command = NOSU | ENABLEDAC; + + /* output channel range */ + switch (range) { + case 0: + command |= BIP | RANGE10V; + break; + case 1: + command |= BIP | RANGE5V; + break; + case 2: + command |= BIP | RANGE2V5; + break; + case 3: + command |= UNIP | RANGE10V; + break; + case 4: + command |= UNIP | RANGE5V; + break; + case 5: + command |= UNIP | RANGE2V5; + break; + } + + /* output channel specification */ + command |= channel << 2; + outw(command, devpriv->dac + DACONTROL); + + /* write data */ + outw(data[0], devpriv->dac + DADATA + channel * 2); + + /* return the number of samples read/written */ + return 1; +} + +/* lowlevel read from eeprom */ +static unsigned int cb_pcidda_serial_in(struct comedi_device *dev) +{ + unsigned int value = 0; + int i; + const int value_width = 16; /* number of bits wide values are */ + + for (i = 1; i <= value_width; i++) { + /* read bits most significant bit first */ + if (inw_p(devpriv->dac + DACALIBRATION1) & SERIAL_OUT_BIT) + value |= 1 << (value_width - i); + } + + return value; +} + +/* lowlevel write to eeprom/dac */ +static void cb_pcidda_serial_out(struct comedi_device *dev, unsigned int value, + unsigned int num_bits) +{ + int i; + + for (i = 1; i <= num_bits; i++) { + /* send bits most significant bit first */ + if (value & (1 << (num_bits - i))) + devpriv->dac_cal1_bits |= SERIAL_IN_BIT; + else + devpriv->dac_cal1_bits &= ~SERIAL_IN_BIT; + outw_p(devpriv->dac_cal1_bits, devpriv->dac + DACALIBRATION1); + } +} + +/* reads a 16 bit value from board's eeprom */ +static unsigned int cb_pcidda_read_eeprom(struct comedi_device *dev, + unsigned int address) +{ + unsigned int i; + unsigned int cal2_bits; + unsigned int value; + const int max_num_caldacs = 4; /* one caldac for every two dac channels */ + const int read_instruction = 0x6; /* bits to send to tell eeprom we want to read */ + const int instruction_length = 3; + const int address_length = 8; + + /* send serial output stream to eeprom */ + cal2_bits = SELECT_EEPROM_BIT | DESELECT_REF_DAC_BIT | DUMMY_BIT; + /* deactivate caldacs (one caldac for every two channels) */ + for (i = 0; i < max_num_caldacs; i++) + cal2_bits |= DESELECT_CALDAC_BIT(i); + outw_p(cal2_bits, devpriv->dac + DACALIBRATION2); + + /* tell eeprom we want to read */ + cb_pcidda_serial_out(dev, read_instruction, instruction_length); + /* send address we want to read from */ + cb_pcidda_serial_out(dev, address, address_length); + + value = cb_pcidda_serial_in(dev); + + /* deactivate eeprom */ + cal2_bits &= ~SELECT_EEPROM_BIT; + outw_p(cal2_bits, devpriv->dac + DACALIBRATION2); + + return value; +} + +/* writes to 8 bit calibration dacs */ +static void cb_pcidda_write_caldac(struct comedi_device *dev, + unsigned int caldac, unsigned int channel, + unsigned int value) +{ + unsigned int cal2_bits; + unsigned int i; + const int num_channel_bits = 3; /* caldacs use 3 bit channel specification */ + const int num_caldac_bits = 8; /* 8 bit calibration dacs */ + const int max_num_caldacs = 4; /* one caldac for every two dac channels */ + + /* write 3 bit channel */ + cb_pcidda_serial_out(dev, channel, num_channel_bits); + /* write 8 bit caldac value */ + cb_pcidda_serial_out(dev, value, num_caldac_bits); + +/* +* latch stream into appropriate caldac deselect reference dac +*/ + cal2_bits = DESELECT_REF_DAC_BIT | DUMMY_BIT; + /* deactivate caldacs (one caldac for every two channels) */ + for (i = 0; i < max_num_caldacs; i++) + cal2_bits |= DESELECT_CALDAC_BIT(i); + /* activate the caldac we want */ + cal2_bits &= ~DESELECT_CALDAC_BIT(caldac); + outw_p(cal2_bits, devpriv->dac + DACALIBRATION2); + /* deactivate caldac */ + cal2_bits |= DESELECT_CALDAC_BIT(caldac); + outw_p(cal2_bits, devpriv->dac + DACALIBRATION2); +} + +/* returns caldac that calibrates given analog out channel */ +static unsigned int caldac_number(unsigned int channel) +{ + return channel / 2; +} + +/* returns caldac channel that provides fine gain for given ao channel */ +static unsigned int fine_gain_channel(unsigned int ao_channel) +{ + return 4 * (ao_channel % 2); +} + +/* returns caldac channel that provides coarse gain for given ao channel */ +static unsigned int coarse_gain_channel(unsigned int ao_channel) +{ + return 1 + 4 * (ao_channel % 2); +} + +/* returns caldac channel that provides coarse offset for given ao channel */ +static unsigned int coarse_offset_channel(unsigned int ao_channel) +{ + return 2 + 4 * (ao_channel % 2); +} + +/* returns caldac channel that provides fine offset for given ao channel */ +static unsigned int fine_offset_channel(unsigned int ao_channel) +{ + return 3 + 4 * (ao_channel % 2); +} + +/* returns eeprom address that provides offset for given ao channel and range */ +static unsigned int offset_eeprom_address(unsigned int ao_channel, + unsigned int range) +{ + return 0x7 + 2 * range + 12 * ao_channel; +} + +/* returns eeprom address that provides gain calibration for given ao channel and range */ +static unsigned int gain_eeprom_address(unsigned int ao_channel, + unsigned int range) +{ + return 0x8 + 2 * range + 12 * ao_channel; +} + +/* returns upper byte of eeprom entry, which gives the coarse adjustment values */ +static unsigned int eeprom_coarse_byte(unsigned int word) +{ + return (word >> 8) & 0xff; +} + +/* returns lower byte of eeprom entry, which gives the fine adjustment values */ +static unsigned int eeprom_fine_byte(unsigned int word) +{ + return word & 0xff; +} + +/* set caldacs to eeprom values for given channel and range */ +static void cb_pcidda_calibrate(struct comedi_device *dev, unsigned int channel, + unsigned int range) +{ + unsigned int coarse_offset, fine_offset, coarse_gain, fine_gain; + + /* remember range so we can tell when we need to readjust calibration */ + devpriv->ao_range[channel] = range; + + /* get values from eeprom data */ + coarse_offset = + eeprom_coarse_byte(devpriv->eeprom_data + [offset_eeprom_address(channel, range)]); + fine_offset = + eeprom_fine_byte(devpriv->eeprom_data + [offset_eeprom_address(channel, range)]); + coarse_gain = + eeprom_coarse_byte(devpriv->eeprom_data + [gain_eeprom_address(channel, range)]); + fine_gain = + eeprom_fine_byte(devpriv->eeprom_data + [gain_eeprom_address(channel, range)]); + + /* set caldacs */ + cb_pcidda_write_caldac(dev, caldac_number(channel), + coarse_offset_channel(channel), coarse_offset); + cb_pcidda_write_caldac(dev, caldac_number(channel), + fine_offset_channel(channel), fine_offset); + cb_pcidda_write_caldac(dev, caldac_number(channel), + coarse_gain_channel(channel), coarse_gain); + cb_pcidda_write_caldac(dev, caldac_number(channel), + fine_gain_channel(channel), fine_gain); +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __devinit driver_cb_pcidda_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_cb_pcidda.driver_name); +} + +static void __devexit driver_cb_pcidda_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_cb_pcidda_pci_driver = { + .id_table = cb_pcidda_pci_table, + .probe = &driver_cb_pcidda_pci_probe, + .remove = __devexit_p(&driver_cb_pcidda_pci_remove) +}; + +static int __init driver_cb_pcidda_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_cb_pcidda); + if (retval < 0) + return retval; + + driver_cb_pcidda_pci_driver.name = (char *)driver_cb_pcidda.driver_name; + return pci_register_driver(&driver_cb_pcidda_pci_driver); +} + +static void __exit driver_cb_pcidda_cleanup_module(void) +{ + pci_unregister_driver(&driver_cb_pcidda_pci_driver); + comedi_driver_unregister(&driver_cb_pcidda); +} + +module_init(driver_cb_pcidda_init_module); +module_exit(driver_cb_pcidda_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/cb_pcidio.c b/drivers/staging/comedi/drivers/cb_pcidio.c new file mode 100644 index 00000000..8f321523 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcidio.c @@ -0,0 +1,333 @@ +/* + comedi/drivers/cb_pcidio.c + A Comedi driver for PCI-DIO24H & PCI-DIO48H of ComputerBoards (currently MeasurementComputing) + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: cb_pcidio +Description: ComputerBoards' DIO boards with PCI interface +Devices: [Measurement Computing] PCI-DIO24 (cb_pcidio), PCI-DIO24H, PCI-DIO48H +Author: Yoshiya Matsuzaka +Updated: Mon, 29 Oct 2007 15:40:47 +0000 +Status: experimental + +This driver has been modified from skel.c of comedi-0.7.70. + +Configuration Options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first available PCI device will + be used. + +Passing a zero for an option is the same as leaving it unspecified. +*/ + +/*------------------------------ HEADER FILES ---------------------------------*/ +#include "../comedidev.h" +#include "comedi_pci.h" +#include "8255.h" + +/*-------------------------- MACROS and DATATYPES -----------------------------*/ +#define PCI_VENDOR_ID_CB 0x1307 + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +struct pcidio_board { + const char *name; /* name of the board */ + int dev_id; + int n_8255; /* number of 8255 chips on board */ + + /* indices of base address regions */ + int pcicontroler_badrindex; + int dioregs_badrindex; +}; + +static const struct pcidio_board pcidio_boards[] = { + { + .name = "pci-dio24", + .dev_id = 0x0028, + .n_8255 = 1, + .pcicontroler_badrindex = 1, + .dioregs_badrindex = 2, + }, + { + .name = "pci-dio24h", + .dev_id = 0x0014, + .n_8255 = 1, + .pcicontroler_badrindex = 1, + .dioregs_badrindex = 2, + }, + { + .name = "pci-dio48h", + .dev_id = 0x000b, + .n_8255 = 2, + .pcicontroler_badrindex = 0, + .dioregs_badrindex = 1, + }, +}; + +/* This is used by modprobe to translate PCI IDs to drivers. Should + * only be used for PCI and ISA-PnP devices */ +/* Please add your PCI vendor ID to comedidev.h, and it will be forwarded + * upstream. */ +static DEFINE_PCI_DEVICE_TABLE(pcidio_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0028) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0014) }, + { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x000b) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, pcidio_pci_table); + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct pcidio_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. */ +struct pcidio_private { + int data; /* currently unused */ + + /* would be useful for a PCI device */ + struct pci_dev *pci_dev; + + /* used for DO readback, currently unused */ + unsigned int do_readback[4]; /* up to 4 unsigned int suffice to hold 96 bits for PCI-DIO96 */ + + unsigned long dio_reg_base; /* address of port A of the first 8255 chip on board */ +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct pcidio_private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pcidio_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcidio_detach(struct comedi_device *dev); +static struct comedi_driver driver_cb_pcidio = { + .driver_name = "cb_pcidio", + .module = THIS_MODULE, + .attach = pcidio_attach, + .detach = pcidio_detach, + +/* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in pcidio_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + +/* The following fields should NOT be initialized if you are dealing + * with PCI devices + * + * .board_name = pcidio_boards, + * .offset = sizeof(struct pcidio_board), + * .num_names = sizeof(pcidio_boards) / sizeof(structpcidio_board), + */ + +}; + +/*------------------------------- FUNCTIONS -----------------------------------*/ + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pcidio_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pci_dev *pcidev = NULL; + int index; + int i; + +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct pcidio_private)) < 0) + return -ENOMEM; +/* + * If you can probe the device to determine what device in a series + * it is, this is the place to do it. Otherwise, dev->board_ptr + * should already be initialized. + */ +/* + * Probe the device to determine what device in the series it is. + */ + + for_each_pci_dev(pcidev) { + /* is it not a computer boards card? */ + if (pcidev->vendor != PCI_VENDOR_ID_CB) + continue; + /* loop through cards supported by this driver */ + for (index = 0; index < ARRAY_SIZE(pcidio_boards); index++) { + if (pcidio_boards[index].dev_id != pcidev->device) + continue; + + /* was a particular bus/slot requested? */ + if (it->options[0] || it->options[1]) { + /* are we on the wrong bus/slot? */ + if (pcidev->bus->number != it->options[0] || + PCI_SLOT(pcidev->devfn) != it->options[1]) { + continue; + } + } + dev->board_ptr = pcidio_boards + index; + goto found; + } + } + + dev_err(dev->hw_dev, "No supported ComputerBoards/MeasurementComputing card found on requested position\n"); + return -EIO; + +found: + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = thisboard->name; + + devpriv->pci_dev = pcidev; + dev_dbg(dev->hw_dev, "Found %s on bus %i, slot %i\n", thisboard->name, + devpriv->pci_dev->bus->number, + PCI_SLOT(devpriv->pci_dev->devfn)); + if (comedi_pci_enable(pcidev, thisboard->name)) + return -EIO; + + devpriv->dio_reg_base + = + pci_resource_start(devpriv->pci_dev, + pcidio_boards[index].dioregs_badrindex); + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, thisboard->n_8255) < 0) + return -ENOMEM; + + for (i = 0; i < thisboard->n_8255; i++) { + subdev_8255_init(dev, dev->subdevices + i, + NULL, devpriv->dio_reg_base + i * 4); + dev_dbg(dev->hw_dev, "subdev %d: base = 0x%lx\n", i, + devpriv->dio_reg_base + i * 4); + } + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pcidio_detach(struct comedi_device *dev) +{ + if (devpriv) { + if (devpriv->pci_dev) { + if (devpriv->dio_reg_base) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + } + if (dev->subdevices) { + int i; + for (i = 0; i < thisboard->n_8255; i++) + subdev_8255_cleanup(dev, dev->subdevices + i); + } + return 0; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __devinit driver_cb_pcidio_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_cb_pcidio.driver_name); +} + +static void __devexit driver_cb_pcidio_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_cb_pcidio_pci_driver = { + .id_table = pcidio_pci_table, + .probe = &driver_cb_pcidio_pci_probe, + .remove = __devexit_p(&driver_cb_pcidio_pci_remove) +}; + +static int __init driver_cb_pcidio_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_cb_pcidio); + if (retval < 0) + return retval; + + driver_cb_pcidio_pci_driver.name = (char *)driver_cb_pcidio.driver_name; + return pci_register_driver(&driver_cb_pcidio_pci_driver); +} + +static void __exit driver_cb_pcidio_cleanup_module(void) +{ + pci_unregister_driver(&driver_cb_pcidio_pci_driver); + comedi_driver_unregister(&driver_cb_pcidio); +} + +module_init(driver_cb_pcidio_init_module); +module_exit(driver_cb_pcidio_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/cb_pcimdas.c b/drivers/staging/comedi/drivers/cb_pcimdas.c new file mode 100644 index 00000000..8ba69426 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcimdas.c @@ -0,0 +1,528 @@ +/* + comedi/drivers/cb_pcimdas.c + Comedi driver for Computer Boards PCIM-DAS1602/16 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: cb_pcimdas +Description: Measurement Computing PCI Migration series boards +Devices: [ComputerBoards] PCIM-DAS1602/16 (cb_pcimdas) +Author: Richard Bytheway +Updated: Wed, 13 Nov 2002 12:34:56 +0000 +Status: experimental + +Written to support the PCIM-DAS1602/16 on a 2.4 series kernel. + +Configuration Options: + [0] - PCI bus number + [1] - PCI slot number + +Developed from cb_pcidas and skel by Richard Bytheway (mocelet@sucs.org). +Only supports DIO, AO and simple AI in it's present form. +No interrupts, multi channel or FIFO AI, although the card looks like it could support this. +See http://www.mccdaq.com/PDFs/Manuals/pcim-das1602-16.pdf for more details. +*/ + +#include "../comedidev.h" + +#include +#include + +#include "comedi_pci.h" +#include "plx9052.h" +#include "8255.h" + +/* #define CBPCIMDAS_DEBUG */ +#undef CBPCIMDAS_DEBUG + +#define PCI_VENDOR_ID_COMPUTERBOARDS 0x1307 + +/* Registers for the PCIM-DAS1602/16 */ + +/* sizes of io regions (bytes) */ +#define BADR0_SIZE 2 /* ?? */ +#define BADR1_SIZE 4 +#define BADR2_SIZE 6 +#define BADR3_SIZE 16 +#define BADR4_SIZE 4 + +/* DAC Offsets */ +#define ADC_TRIG 0 +#define DAC0_OFFSET 2 +#define DAC1_OFFSET 4 + +/* AI and Counter Constants */ +#define MUX_LIMITS 0 +#define MAIN_CONN_DIO 1 +#define ADC_STAT 2 +#define ADC_CONV_STAT 3 +#define ADC_INT 4 +#define ADC_PACER 5 +#define BURST_MODE 6 +#define PROG_GAIN 7 +#define CLK8254_1_DATA 8 +#define CLK8254_2_DATA 9 +#define CLK8254_3_DATA 10 +#define CLK8254_CONTROL 11 +#define USER_COUNTER 12 +#define RESID_COUNT_H 13 +#define RESID_COUNT_L 14 + +/* Board description */ +struct cb_pcimdas_board { + const char *name; + unsigned short device_id; + int ai_se_chans; /* Inputs in single-ended mode */ + int ai_diff_chans; /* Inputs in differential mode */ + int ai_bits; /* analog input resolution */ + int ai_speed; /* fastest conversion period in ns */ + int ao_nchan; /* number of analog out channels */ + int ao_bits; /* analogue output resolution */ + int has_ao_fifo; /* analog output has fifo */ + int ao_scan_speed; /* analog output speed for 1602 series (for a scan, not conversion) */ + int fifo_size; /* number of samples fifo can hold */ + int dio_bits; /* number of dio bits */ + int has_dio; /* has DIO */ + const struct comedi_lrange *ranges; +}; + +static const struct cb_pcimdas_board cb_pcimdas_boards[] = { + { + .name = "PCIM-DAS1602/16", + .device_id = 0x56, + .ai_se_chans = 16, + .ai_diff_chans = 8, + .ai_bits = 16, + .ai_speed = 10000, /* ?? */ + .ao_nchan = 2, + .ao_bits = 12, + .has_ao_fifo = 0, /* ?? */ + .ao_scan_speed = 10000, + /* ?? */ + .fifo_size = 1024, + .dio_bits = 24, + .has_dio = 1, +/* .ranges = &cb_pcimdas_ranges, */ + }, +}; + +/* This is used by modprobe to translate PCI IDs to drivers. Should + * only be used for PCI and ISA-PnP devices */ +static DEFINE_PCI_DEVICE_TABLE(cb_pcimdas_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, 0x0056) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, cb_pcimdas_pci_table); + +#define N_BOARDS 1 /* Max number of boards supported */ + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct cb_pcimdas_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. */ +struct cb_pcimdas_private { + int data; + + /* would be useful for a PCI device */ + struct pci_dev *pci_dev; + + /* base addresses */ + unsigned long BADR0; + unsigned long BADR1; + unsigned long BADR2; + unsigned long BADR3; + unsigned long BADR4; + + /* Used for AO readback */ + unsigned int ao_readback[2]; + + /* Used for DIO */ + unsigned short int port_a; /* copy of BADR4+0 */ + unsigned short int port_b; /* copy of BADR4+1 */ + unsigned short int port_c; /* copy of BADR4+2 */ + unsigned short int dio_mode; /* copy of BADR4+3 */ + +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct cb_pcimdas_private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int cb_pcimdas_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int cb_pcimdas_detach(struct comedi_device *dev); +static struct comedi_driver driver_cb_pcimdas = { + .driver_name = "cb_pcimdas", + .module = THIS_MODULE, + .attach = cb_pcimdas_attach, + .detach = cb_pcimdas_detach, +}; + +static int cb_pcimdas_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int cb_pcimdas_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int cb_pcimdas_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int cb_pcimdas_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + struct pci_dev *pcidev = NULL; + int index; + /* int i; */ + +/* + * Allocate the private structure area. + */ + if (alloc_private(dev, sizeof(struct cb_pcimdas_private)) < 0) + return -ENOMEM; + +/* + * Probe the device to determine what device in the series it is. + */ + + for_each_pci_dev(pcidev) { + /* is it not a computer boards card? */ + if (pcidev->vendor != PCI_VENDOR_ID_COMPUTERBOARDS) + continue; + /* loop through cards supported by this driver */ + for (index = 0; index < N_BOARDS; index++) { + if (cb_pcimdas_boards[index].device_id != + pcidev->device) + continue; + /* was a particular bus/slot requested? */ + if (it->options[0] || it->options[1]) { + /* are we on the wrong bus/slot? */ + if (pcidev->bus->number != it->options[0] || + PCI_SLOT(pcidev->devfn) != it->options[1]) { + continue; + } + } + devpriv->pci_dev = pcidev; + dev->board_ptr = cb_pcimdas_boards + index; + goto found; + } + } + + dev_err(dev->hw_dev, "No supported ComputerBoards/MeasurementComputing card found on requested position\n"); + return -EIO; + +found: + + dev_dbg(dev->hw_dev, "Found %s on bus %i, slot %i\n", + cb_pcimdas_boards[index].name, pcidev->bus->number, + PCI_SLOT(pcidev->devfn)); + + /* Warn about non-tested features */ + switch (thisboard->device_id) { + case 0x56: + break; + default: + dev_dbg(dev->hw_dev, "THIS CARD IS UNSUPPORTED.\n" + "PLEASE REPORT USAGE TO \n"); + } + + if (comedi_pci_enable(pcidev, "cb_pcimdas")) { + dev_err(dev->hw_dev, "Failed to enable PCI device and request regions\n"); + return -EIO; + } + + devpriv->BADR0 = pci_resource_start(devpriv->pci_dev, 0); + devpriv->BADR1 = pci_resource_start(devpriv->pci_dev, 1); + devpriv->BADR2 = pci_resource_start(devpriv->pci_dev, 2); + devpriv->BADR3 = pci_resource_start(devpriv->pci_dev, 3); + devpriv->BADR4 = pci_resource_start(devpriv->pci_dev, 4); + + dev_dbg(dev->hw_dev, "devpriv->BADR0 = 0x%lx\n", devpriv->BADR0); + dev_dbg(dev->hw_dev, "devpriv->BADR1 = 0x%lx\n", devpriv->BADR1); + dev_dbg(dev->hw_dev, "devpriv->BADR2 = 0x%lx\n", devpriv->BADR2); + dev_dbg(dev->hw_dev, "devpriv->BADR3 = 0x%lx\n", devpriv->BADR3); + dev_dbg(dev->hw_dev, "devpriv->BADR4 = 0x%lx\n", devpriv->BADR4); + +/* Dont support IRQ yet */ +/* get irq */ +/* if(request_irq(devpriv->pci_dev->irq, cb_pcimdas_interrupt, IRQF_SHARED, "cb_pcimdas", dev )) */ +/* { */ +/* printk(" unable to allocate irq %u\n", devpriv->pci_dev->irq); */ +/* return -EINVAL; */ +/* } */ +/* dev->irq = devpriv->pci_dev->irq; */ + + /* Initialize dev->board_name */ + dev->board_name = thisboard->name; + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 3) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* dev->read_subdev=s; */ + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = thisboard->ai_se_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &range_unknown; + s->len_chanlist = 1; /* This is the maximum chanlist length that */ + /* the board can handle */ + s->insn_read = cb_pcimdas_ai_rinsn; + + s = dev->subdevices + 1; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = thisboard->ao_nchan; + s->maxdata = 1 << thisboard->ao_bits; + s->range_table = &range_unknown; /* ranges are hardware settable, but not software readable. */ + s->insn_write = &cb_pcimdas_ao_winsn; + s->insn_read = &cb_pcimdas_ao_rinsn; + + s = dev->subdevices + 2; + /* digital i/o subdevice */ + if (thisboard->has_dio) + subdev_8255_init(dev, s, NULL, devpriv->BADR4); + else + s->type = COMEDI_SUBD_UNUSED; + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int cb_pcimdas_detach(struct comedi_device *dev) +{ + if (devpriv) { + dev_dbg(dev->hw_dev, "devpriv->BADR0 = 0x%lx\n", + devpriv->BADR0); + dev_dbg(dev->hw_dev, "devpriv->BADR1 = 0x%lx\n", + devpriv->BADR1); + dev_dbg(dev->hw_dev, "devpriv->BADR2 = 0x%lx\n", + devpriv->BADR2); + dev_dbg(dev->hw_dev, "devpriv->BADR3 = 0x%lx\n", + devpriv->BADR3); + dev_dbg(dev->hw_dev, "devpriv->BADR4 = 0x%lx\n", + devpriv->BADR4); + } + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (devpriv->pci_dev) { + if (devpriv->BADR0) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + } + + return 0; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ +static int cb_pcimdas_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, i; + unsigned int d; + unsigned int busy; + int chan = CR_CHAN(insn->chanspec); + unsigned short chanlims; + int maxchans; + + /* only support sw initiated reads from a single channel */ + + /* check channel number */ + if ((inb(devpriv->BADR3 + 2) & 0x20) == 0) /* differential mode */ + maxchans = thisboard->ai_diff_chans; + else + maxchans = thisboard->ai_se_chans; + + if (chan > (maxchans - 1)) + return -ETIMEDOUT; /* *** Wrong error code. Fixme. */ + + /* configure for sw initiated read */ + d = inb(devpriv->BADR3 + 5); + if ((d & 0x03) > 0) { /* only reset if needed. */ + d = d & 0xfd; + outb(d, devpriv->BADR3 + 5); + } + outb(0x01, devpriv->BADR3 + 6); /* set bursting off, conversions on */ + outb(0x00, devpriv->BADR3 + 7); /* set range to 10V. UP/BP is controlled by a switch on the board */ + + /* write channel limits to multiplexer, set Low (bits 0-3) and High (bits 4-7) channels to chan. */ + chanlims = chan | (chan << 4); + outb(chanlims, devpriv->BADR3 + 0); + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outw(0, devpriv->BADR2 + 0); + +#define TIMEOUT 1000 /* typically takes 5 loops on a lightly loaded Pentium 100MHz, */ + /* this is likely to be 100 loops on a 2GHz machine, so set 1000 as the limit. */ + + /* wait for conversion to end */ + for (i = 0; i < TIMEOUT; i++) { + busy = inb(devpriv->BADR3 + 2) & 0x80; + if (!busy) + break; + } + if (i == TIMEOUT) { + printk("timeout\n"); + return -ETIMEDOUT; + } + /* read data */ + d = inw(devpriv->BADR2 + 0); + + /* mangle the data as necessary */ + /* d ^= 1<<(thisboard->ai_bits-1); // 16 bit data from ADC, so no mangle needed. */ + + data[n] = d; + } + + /* return the number of samples read/written */ + return n; +} + +static int cb_pcimdas_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; i++) { + switch (chan) { + case 0: + outw(data[i] & 0x0FFF, devpriv->BADR2 + DAC0_OFFSET); + break; + case 1: + outw(data[i] & 0x0FFF, devpriv->BADR2 + DAC1_OFFSET); + break; + default: + return -1; + } + devpriv->ao_readback[chan] = data[i]; + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int cb_pcimdas_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __devinit driver_cb_pcimdas_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_cb_pcimdas.driver_name); +} + +static void __devexit driver_cb_pcimdas_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_cb_pcimdas_pci_driver = { + .id_table = cb_pcimdas_pci_table, + .probe = &driver_cb_pcimdas_pci_probe, + .remove = __devexit_p(&driver_cb_pcimdas_pci_remove) +}; + +static int __init driver_cb_pcimdas_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_cb_pcimdas); + if (retval < 0) + return retval; + + driver_cb_pcimdas_pci_driver.name = + (char *)driver_cb_pcimdas.driver_name; + return pci_register_driver(&driver_cb_pcimdas_pci_driver); +} + +static void __exit driver_cb_pcimdas_cleanup_module(void) +{ + pci_unregister_driver(&driver_cb_pcimdas_pci_driver); + comedi_driver_unregister(&driver_cb_pcimdas); +} + +module_init(driver_cb_pcimdas_init_module); +module_exit(driver_cb_pcimdas_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/cb_pcimdda.c b/drivers/staging/comedi/drivers/cb_pcimdda.c new file mode 100644 index 00000000..40bddfa2 --- /dev/null +++ b/drivers/staging/comedi/drivers/cb_pcimdda.c @@ -0,0 +1,517 @@ +/* + comedi/drivers/cb_pcimdda.c + Computer Boards PCIM-DDA06-16 Comedi driver + Author: Calin Culianu + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: cb_pcimdda +Description: Measurement Computing PCIM-DDA06-16 +Devices: [Measurement Computing] PCIM-DDA06-16 (cb_pcimdda) +Author: Calin Culianu +Updated: Mon, 14 Apr 2008 15:15:51 +0100 +Status: works + +All features of the PCIM-DDA06-16 board are supported. This board +has 6 16-bit AO channels, and the usual 8255 DIO setup. (24 channels, +configurable in banks of 8 and 4, etc.). This board does not support commands. + +The board has a peculiar way of specifying AO gain/range settings -- You have +1 jumper bank on the card, which either makes all 6 AO channels either +5 Volt unipolar, 5V bipolar, 10 Volt unipolar or 10V bipolar. + +Since there is absolutely _no_ way to tell in software how this jumper is set +(well, at least according to the rather thin spec. from Measurement Computing + that comes with the board), the driver assumes the jumper is at its factory +default setting of +/-5V. + +Also of note is the fact that this board features another jumper, whose +state is also completely invisible to software. It toggles two possible AO +output modes on the board: + + - Update Mode: Writing to an AO channel instantaneously updates the actual + signal output by the DAC on the board (this is the factory default). + - Simultaneous XFER Mode: Writing to an AO channel has no effect until + you read from any one of the AO channels. This is useful for loading + all 6 AO values, and then reading from any one of the AO channels on the + device to instantly update all 6 AO values in unison. Useful for some + control apps, I would assume? If your jumper is in this setting, then you + need to issue your comedi_data_write()s to load all the values you want, + then issue one comedi_data_read() on any channel on the AO subdevice + to initiate the simultaneous XFER. + +Configuration Options: + [0] PCI bus (optional) + [1] PCI slot (optional) + [2] analog output range jumper setting + 0 == +/- 5 V + 1 == +/- 10 V +*/ + +/* + This is a driver for the Computer Boards PCIM-DDA06-16 Analog Output + card. This board has a unique register layout and as such probably + deserves its own driver file. + + It is theoretically possible to integrate this board into the cb_pcidda + file, but since that isn't my code, I didn't want to significantly + modify that file to support this board (I thought it impolite to do so). + + At any rate, if you feel ambitious, please feel free to take + the code out of this file and combine it with a more unified driver + file. + + I would like to thank Timothy Curry + for lending me a board so that I could write this driver. + + -Calin Culianu + */ + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#include "8255.h" + +/* device ids of the cards we support -- currently only 1 card supported */ +#define PCI_VENDOR_ID_COMPUTERBOARDS 0x1307 +#define PCI_ID_PCIM_DDA06_16 0x0053 + +/* + * This is straight from skel.c -- I did this in case this source file + * will someday support more than 1 board... + */ +struct board_struct { + const char *name; + unsigned short device_id; + int ao_chans; + int ao_bits; + int dio_chans; + int dio_method; + /* how many bytes into the BADR are the DIO ports */ + int dio_offset; + int regs_badrindex; /* IO Region for the control, analog output, + and DIO registers */ + int reg_sz; /* number of bytes of registers in io region */ +}; + +enum DIO_METHODS { + DIO_NONE = 0, + DIO_8255, + DIO_INTERNAL /* unimplemented */ +}; + +static const struct board_struct boards[] = { + { + .name = "cb_pcimdda06-16", + .device_id = PCI_ID_PCIM_DDA06_16, + .ao_chans = 6, + .ao_bits = 16, + .dio_chans = 24, + .dio_method = DIO_8255, + .dio_offset = 12, + .regs_badrindex = 3, + .reg_sz = 16, + } +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct board_struct *)dev->board_ptr) + +#define REG_SZ (thisboard->reg_sz) +#define REGS_BADRINDEX (thisboard->regs_badrindex) + +/* This is used by modprobe to translate PCI IDs to drivers. Should + * only be used for PCI and ISA-PnP devices */ +/* Please add your PCI vendor ID to comedidev.h, and it will be forwarded + * upstream. */ +static DEFINE_PCI_DEVICE_TABLE(pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, PCI_ID_PCIM_DDA06_16) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, pci_table); + +/* + * this structure is for data unique to this hardware driver. If + * several hardware drivers keep similar information in this structure, + * feel free to suggest moving the variable to the struct comedi_device + * struct. + */ +struct board_private_struct { + unsigned long registers; /* set by probe */ + unsigned long dio_registers; + char attached_to_8255; /* boolean */ + char attached_successfully; /* boolean */ + /* would be useful for a PCI device */ + struct pci_dev *pci_dev; + +#define MAX_AO_READBACK_CHANNELS 6 + /* Used for AO readback */ + unsigned int ao_readback[MAX_AO_READBACK_CHANNELS]; + +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct board_private_struct *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int detach(struct comedi_device *dev); +static struct comedi_driver cb_pcimdda_driver = { + .driver_name = "cb_pcimdda", + .module = THIS_MODULE, + .attach = attach, + .detach = detach, +}; + +MODULE_AUTHOR("Calin A. Culianu "); +MODULE_DESCRIPTION("Comedi low-level driver for the Computerboards PCIM-DDA " + "series. Currently only supports PCIM-DDA06-16 (which " + "also happens to be the only board in this series. :) ) "); +MODULE_LICENSE("GPL"); +static int __devinit cb_pcimdda_driver_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, cb_pcimdda_driver.driver_name); +} + +static void __devexit cb_pcimdda_driver_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver cb_pcimdda_driver_pci_driver = { + .id_table = pci_table, + .probe = &cb_pcimdda_driver_pci_probe, + .remove = __devexit_p(&cb_pcimdda_driver_pci_remove) +}; + +static int __init cb_pcimdda_driver_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&cb_pcimdda_driver); + if (retval < 0) + return retval; + + cb_pcimdda_driver_pci_driver.name = + (char *)cb_pcimdda_driver.driver_name; + return pci_register_driver(&cb_pcimdda_driver_pci_driver); +} + +static void __exit cb_pcimdda_driver_cleanup_module(void) +{ + pci_unregister_driver(&cb_pcimdda_driver_pci_driver); + comedi_driver_unregister(&cb_pcimdda_driver); +} + +module_init(cb_pcimdda_driver_init_module); +module_exit(cb_pcimdda_driver_cleanup_module); + +static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/*--------------------------------------------------------------------------- + HELPER FUNCTION DECLARATIONS +-----------------------------------------------------------------------------*/ + +/* returns a maxdata value for a given n_bits */ +static inline unsigned int figure_out_maxdata(int bits) +{ + return ((unsigned int)1 << bits) - 1; +} + +/* + * Probes for a supported device. + * + * Prerequisite: private be allocated already inside dev + * + * If the device is found, it returns 0 and has the following side effects: + * + * o assigns a struct pci_dev * to dev->private->pci_dev + * o assigns a struct board * to dev->board_ptr + * o sets dev->private->registers + * o sets dev->private->dio_registers + * + * Otherwise, returns a -errno on error + */ +static int probe(struct comedi_device *dev, const struct comedi_devconfig *it); + +/*--------------------------------------------------------------------------- + FUNCTION DEFINITIONS +-----------------------------------------------------------------------------*/ + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int err; + +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + * if this function fails (returns negative) then the private area is + * kfree'd by comedi + */ + if (alloc_private(dev, sizeof(struct board_private_struct)) < 0) + return -ENOMEM; + +/* + * If you can probe the device to determine what device in a series + * it is, this is the place to do it. Otherwise, dev->board_ptr + * should already be initialized. + */ + err = probe(dev, it); + if (err) + return err; + +/* Output some info */ + printk("comedi%d: %s: ", dev->minor, thisboard->name); + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = thisboard->name; + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 2) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = thisboard->ao_chans; + s->maxdata = figure_out_maxdata(thisboard->ao_bits); + /* this is hard-coded here */ + if (it->options[2]) + s->range_table = &range_bipolar10; + else + s->range_table = &range_bipolar5; + s->insn_write = &ao_winsn; + s->insn_read = &ao_rinsn; + + s = dev->subdevices + 1; + /* digital i/o subdevice */ + if (thisboard->dio_chans) { + switch (thisboard->dio_method) { + case DIO_8255: + /* + * this is a straight 8255, so register us with + * the 8255 driver + */ + subdev_8255_init(dev, s, NULL, devpriv->dio_registers); + devpriv->attached_to_8255 = 1; + break; + case DIO_INTERNAL: + default: + printk("DIO_INTERNAL not implemented yet!\n"); + return -ENXIO; + break; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + devpriv->attached_successfully = 1; + + printk("attached\n"); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int detach(struct comedi_device *dev) +{ + if (devpriv) { + + if (dev->subdevices && devpriv->attached_to_8255) { + /* de-register us from the 8255 driver */ + subdev_8255_cleanup(dev, dev->subdevices + 2); + devpriv->attached_to_8255 = 0; + } + + if (devpriv->pci_dev) { + if (devpriv->registers) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + + if (devpriv->attached_successfully && thisboard) + printk("comedi%d: %s: detached\n", dev->minor, + thisboard->name); + + } + + return 0; +} + +static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + unsigned long offset = devpriv->registers + chan * 2; + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; i++) { + /* first, load the low byte */ + outb((char)(data[i] & 0x00ff), offset); + /* next, write the high byte -- only after this is written is + the channel voltage updated in the DAC, unless + we're in simultaneous xfer mode (jumper on card) + then a rinsn is necessary to actually update the DAC -- + see ao_rinsn() below... */ + outb((char)(data[i] >> 8 & 0x00ff), offset + 1); + + /* for testing only.. the actual rinsn SHOULD do an inw! + (see the stuff about simultaneous XFER mode on this board) */ + devpriv->ao_readback[chan] = data[i]; + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + + Usually this means copying a value stored in devpriv->ao_readback. + However, since this board has this jumper setting called "Simultaneous + Xfer mode" (off by default), we will support it. Simultaneaous xfer + mode is accomplished by loading ALL the values you want for AO in all the + channels, then READing off one of the AO registers to initiate the + instantaneous simultaneous update of all DAC outputs, which makes + all AO channels update simultaneously. This is useful for some control + applications, I would imagine. +*/ +static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + inw(devpriv->registers + chan * 2); + /* + * should I set data[i] to the result of the actual read + * on the register or the cached unsigned int in + * devpriv->ao_readback[]? + */ + data[i] = devpriv->ao_readback[chan]; + } + + return i; +} + +/*--------------------------------------------------------------------------- + HELPER FUNCTION DEFINITIONS +-----------------------------------------------------------------------------*/ + +/* + * Probes for a supported device. + * + * Prerequisite: private be allocated already inside dev + * + * If the device is found, it returns 0 and has the following side effects: + * + * o assigns a struct pci_dev * to dev->private->pci_dev + * o assigns a struct board * to dev->board_ptr + * o sets dev->private->registers + * o sets dev->private->dio_registers + * + * Otherwise, returns a -errno on error + */ +static int probe(struct comedi_device *dev, const struct comedi_devconfig *it) +{ + struct pci_dev *pcidev = NULL; + int index; + unsigned long registers; + + for_each_pci_dev(pcidev) { + /* is it not a computer boards card? */ + if (pcidev->vendor != PCI_VENDOR_ID_COMPUTERBOARDS) + continue; + /* loop through cards supported by this driver */ + for (index = 0; index < ARRAY_SIZE(boards); index++) { + if (boards[index].device_id != pcidev->device) + continue; + /* was a particular bus/slot requested? */ + if (it->options[0] || it->options[1]) { + /* are we on the wrong bus/slot? */ + if (pcidev->bus->number != it->options[0] || + PCI_SLOT(pcidev->devfn) != it->options[1]) { + continue; + } + } + /* found ! */ + + devpriv->pci_dev = pcidev; + dev->board_ptr = boards + index; + if (comedi_pci_enable(pcidev, thisboard->name)) { + printk + ("cb_pcimdda: Failed to enable PCI device and request regions\n"); + return -EIO; + } + registers = + pci_resource_start(devpriv->pci_dev, + REGS_BADRINDEX); + devpriv->registers = registers; + devpriv->dio_registers + = devpriv->registers + thisboard->dio_offset; + return 0; + } + } + + printk("cb_pcimdda: No supported ComputerBoards/MeasurementComputing " + "card found at the requested position\n"); + return -ENODEV; +} diff --git a/drivers/staging/comedi/drivers/comedi_bond.c b/drivers/staging/comedi/drivers/comedi_bond.c new file mode 100644 index 00000000..d8aefb23 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_bond.c @@ -0,0 +1,504 @@ +/* + comedi/drivers/comedi_bond.c + A Comedi driver to 'bond' or merge multiple drivers and devices as one. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + Copyright (C) 2005 Calin A. Culianu + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: comedi_bond +Description: A driver to 'bond' (merge) multiple subdevices from multiple + devices together as one. +Devices: +Author: ds +Updated: Mon, 10 Oct 00:18:25 -0500 +Status: works + +This driver allows you to 'bond' (merge) multiple comedi subdevices +(coming from possibly difference boards and/or drivers) together. For +example, if you had a board with 2 different DIO subdevices, and +another with 1 DIO subdevice, you could 'bond' them with this driver +so that they look like one big fat DIO subdevice. This makes writing +applications slightly easier as you don't have to worry about managing +different subdevices in the application -- you just worry about +indexing one linear array of channel id's. + +Right now only DIO subdevices are supported as that's the personal itch +I am scratching with this driver. If you want to add support for AI and AO +subdevs, go right on ahead and do so! + +Commands aren't supported -- although it would be cool if they were. + +Configuration Options: + List of comedi-minors to bond. All subdevices of the same type + within each minor will be concatenated together in the order given here. +*/ + +#include +#include +#include "../comedi.h" +#include "../comedilib.h" +#include "../comedidev.h" + +/* The maxiumum number of channels per subdevice. */ +#define MAX_CHANS 256 + +#define MODULE_NAME "comedi_bond" +MODULE_LICENSE("GPL"); +#ifndef STR +# define STR1(x) #x +# define STR(x) STR1(x) +#endif + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "If true, print extra cryptic debugging output useful" + "only to developers."); + +#define LOG_MSG(x...) printk(KERN_INFO MODULE_NAME": "x) +#define DEBUG(x...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG MODULE_NAME": DEBUG: "x); \ + } while (0) +#define WARNING(x...) printk(KERN_WARNING MODULE_NAME ": WARNING: "x) +#define ERROR(x...) printk(KERN_ERR MODULE_NAME ": INTERNAL ERROR: "x) +MODULE_AUTHOR("Calin A. Culianu"); +MODULE_DESCRIPTION(MODULE_NAME "A driver for COMEDI to bond multiple COMEDI " + "devices together as one. In the words of John Lennon: " + "'And the world will live as one...'"); + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +struct BondingBoard { + const char *name; +}; + +static const struct BondingBoard bondingBoards[] = { + { + .name = MODULE_NAME, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct BondingBoard *)dev->board_ptr) + +struct BondedDevice { + struct comedi_device *dev; + unsigned minor; + unsigned subdev; + unsigned subdev_type; + unsigned nchans; + unsigned chanid_offset; /* The offset into our unified linear + channel-id's of chanid 0 on this + subdevice. */ +}; + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. */ +struct Private { +# define MAX_BOARD_NAME 256 + char name[MAX_BOARD_NAME]; + struct BondedDevice **devs; + unsigned ndevs; + struct BondedDevice *chanIdDevMap[MAX_CHANS]; + unsigned nchans; +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct Private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int bonding_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int bonding_detach(struct comedi_device *dev); +/** Build Private array of all devices.. */ +static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it); +static void doDevUnconfig(struct comedi_device *dev); +/* Ugly implementation of realloc that always copies memory around -- I'm lazy, + * what can I say? I like to do wasteful memcopies.. :) */ +static void *Realloc(const void *ptr, size_t len, size_t old_len); + +static struct comedi_driver driver_bonding = { + .driver_name = MODULE_NAME, + .module = THIS_MODULE, + .attach = bonding_attach, + .detach = bonding_detach, + /* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in skel_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + .board_name = &bondingBoards[0].name, + .offset = sizeof(struct BondingBoard), + .num_names = ARRAY_SIZE(bondingBoards), +}; + +static int bonding_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int bonding_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int bonding_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + + LOG_MSG("comedi%d\n", dev->minor); + + /* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct Private)) < 0) + return -ENOMEM; + + /* + * Setup our bonding from config params.. sets up our Private struct.. + */ + if (!doDevConfig(dev, it)) + return -EINVAL; + + /* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = devpriv->name; + + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = devpriv->nchans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = bonding_dio_insn_bits; + s->insn_config = bonding_dio_insn_config; + + LOG_MSG("attached with %u DIO channels coming from %u different " + "subdevices all bonded together. " + "John Lennon would be proud!\n", + devpriv->nchans, devpriv->ndevs); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int bonding_detach(struct comedi_device *dev) +{ + LOG_MSG("comedi%d: remove\n", dev->minor); + doDevUnconfig(dev); + return 0; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +static int bonding_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ +#define LSAMPL_BITS (sizeof(unsigned int)*8) + unsigned nchans = LSAMPL_BITS, num_done = 0, i; + if (insn->n != 2) + return -EINVAL; + + if (devpriv->nchans < nchans) + nchans = devpriv->nchans; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + for (i = 0; num_done < nchans && i < devpriv->ndevs; ++i) { + struct BondedDevice *bdev = devpriv->devs[i]; + /* Grab the channel mask and data of only the bits corresponding + to this subdevice.. need to shift them to zero position of + course. */ + /* Bits corresponding to this subdev. */ + unsigned int subdevMask = ((1 << bdev->nchans) - 1); + unsigned int writeMask, dataBits; + + /* Argh, we have >= LSAMPL_BITS chans.. take all bits */ + if (bdev->nchans >= LSAMPL_BITS) + subdevMask = (unsigned int)(-1); + + writeMask = (data[0] >> num_done) & subdevMask; + dataBits = (data[1] >> num_done) & subdevMask; + + /* Read/Write the new digital lines */ + if (comedi_dio_bitfield(bdev->dev, bdev->subdev, writeMask, + &dataBits) != 2) + return -EINVAL; + + /* Make room for the new bits in data[1], the return value */ + data[1] &= ~(subdevMask << num_done); + /* Put the bits in the return value */ + data[1] |= (dataBits & subdevMask) << num_done; + /* Save the new bits to the saved state.. */ + s->state = data[1]; + + num_done += bdev->nchans; + } + + return insn->n; +} + +static int bonding_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec), ret, io_bits = s->io_bits; + unsigned int io; + struct BondedDevice *bdev; + + if (chan < 0 || chan >= devpriv->nchans) + return -EINVAL; + bdev = devpriv->chanIdDevMap[chan]; + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + io = COMEDI_OUTPUT; /* is this really necessary? */ + io_bits |= 1 << chan; + break; + case INSN_CONFIG_DIO_INPUT: + io = COMEDI_INPUT; /* is this really necessary? */ + io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + break; + } + /* 'real' channel id for this subdev.. */ + chan -= bdev->chanid_offset; + ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, io); + if (ret != 1) + return -EINVAL; + /* Finally, save the new io_bits values since we didn't get + an error above. */ + s->io_bits = io_bits; + return insn->n; +} + +static void *Realloc(const void *oldmem, size_t newlen, size_t oldlen) +{ + void *newmem = kmalloc(newlen, GFP_KERNEL); + + if (newmem && oldmem) + memcpy(newmem, oldmem, min(oldlen, newlen)); + kfree(oldmem); + return newmem; +} + +static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int i; + struct comedi_device *devs_opened[COMEDI_NUM_BOARD_MINORS]; + + memset(devs_opened, 0, sizeof(devs_opened)); + devpriv->name[0] = 0; + /* Loop through all comedi devices specified on the command-line, + building our device list */ + for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) { + char file[] = "/dev/comediXXXXXX"; + int minor = it->options[i]; + struct comedi_device *d; + int sdev = -1, nchans, tmp; + struct BondedDevice *bdev = NULL; + + if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) { + ERROR("Minor %d is invalid!\n", minor); + return 0; + } + if (minor == dev->minor) { + ERROR("Cannot bond this driver to itself!\n"); + return 0; + } + if (devs_opened[minor]) { + ERROR("Minor %d specified more than once!\n", minor); + return 0; + } + + snprintf(file, sizeof(file), "/dev/comedi%u", minor); + file[sizeof(file) - 1] = 0; + + d = devs_opened[minor] = comedi_open(file); + + if (!d) { + ERROR("Minor %u could not be opened\n", minor); + return 0; + } + + /* Do DIO, as that's all we support now.. */ + while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO, + sdev + 1)) > -1) { + nchans = comedi_get_n_channels(d, sdev); + if (nchans <= 0) { + ERROR("comedi_get_n_channels() returned %d " + "on minor %u subdev %d!\n", + nchans, minor, sdev); + return 0; + } + bdev = kmalloc(sizeof(*bdev), GFP_KERNEL); + if (!bdev) { + ERROR("Out of memory.\n"); + return 0; + } + bdev->dev = d; + bdev->minor = minor; + bdev->subdev = sdev; + bdev->subdev_type = COMEDI_SUBD_DIO; + bdev->nchans = nchans; + bdev->chanid_offset = devpriv->nchans; + + /* map channel id's to BondedDevice * pointer.. */ + while (nchans--) + devpriv->chanIdDevMap[devpriv->nchans++] = bdev; + + /* Now put bdev pointer at end of devpriv->devs array + * list.. */ + + /* ergh.. ugly.. we need to realloc :( */ + tmp = devpriv->ndevs * sizeof(bdev); + devpriv->devs = + Realloc(devpriv->devs, + ++devpriv->ndevs * sizeof(bdev), tmp); + if (!devpriv->devs) { + ERROR("Could not allocate memory. " + "Out of memory?"); + return 0; + } + + devpriv->devs[devpriv->ndevs - 1] = bdev; + { + /** Append dev:subdev to devpriv->name */ + char buf[20]; + int left = + MAX_BOARD_NAME - strlen(devpriv->name) - 1; + snprintf(buf, sizeof(buf), "%d:%d ", dev->minor, + bdev->subdev); + buf[sizeof(buf) - 1] = 0; + strncat(devpriv->name, buf, left); + } + + } + } + + if (!devpriv->nchans) { + ERROR("No channels found!\n"); + return 0; + } + + return 1; +} + +static void doDevUnconfig(struct comedi_device *dev) +{ + unsigned long devs_closed = 0; + + if (devpriv) { + while (devpriv->ndevs-- && devpriv->devs) { + struct BondedDevice *bdev; + + bdev = devpriv->devs[devpriv->ndevs]; + if (!bdev) + continue; + if (!(devs_closed & (0x1 << bdev->minor))) { + comedi_close(bdev->dev); + devs_closed |= (0x1 << bdev->minor); + } + kfree(bdev); + } + kfree(devpriv->devs); + devpriv->devs = NULL; + kfree(devpriv); + dev->private = NULL; + } +} + +static int __init init(void) +{ + return comedi_driver_register(&driver_bonding); +} + +static void __exit cleanup(void) +{ + comedi_driver_unregister(&driver_bonding); +} + +module_init(init); +module_exit(cleanup); diff --git a/drivers/staging/comedi/drivers/comedi_fc.c b/drivers/staging/comedi/drivers/comedi_fc.c new file mode 100644 index 00000000..63be619d --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_fc.c @@ -0,0 +1,119 @@ +/* + comedi/drivers/comedi_fc.c + + This is a place for code driver writers wish to share between + two or more drivers. fc is short + for frank-common. + + Author: Frank Mori Hess + Copyright (C) 2002 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************/ + +#include "../comedidev.h" + +#include "comedi_fc.h" + +static void increment_scan_progress(struct comedi_subdevice *subd, + unsigned int num_bytes) +{ + struct comedi_async *async = subd->async; + unsigned int scan_length = cfc_bytes_per_scan(subd); + + async->scan_progress += num_bytes; + if (async->scan_progress >= scan_length) { + async->scan_progress %= scan_length; + async->events |= COMEDI_CB_EOS; + } +} + +/* Writes an array of data points to comedi's buffer */ +unsigned int cfc_write_array_to_buffer(struct comedi_subdevice *subd, + void *data, unsigned int num_bytes) +{ + struct comedi_async *async = subd->async; + unsigned int retval; + + if (num_bytes == 0) + return 0; + + retval = comedi_buf_write_alloc(async, num_bytes); + if (retval != num_bytes) { + printk(KERN_WARNING "comedi: buffer overrun\n"); + async->events |= COMEDI_CB_OVERFLOW; + return 0; + } + + comedi_buf_memcpy_to(async, 0, data, num_bytes); + comedi_buf_write_free(async, num_bytes); + increment_scan_progress(subd, num_bytes); + async->events |= COMEDI_CB_BLOCK; + + return num_bytes; +} +EXPORT_SYMBOL(cfc_write_array_to_buffer); + +unsigned int cfc_read_array_from_buffer(struct comedi_subdevice *subd, + void *data, unsigned int num_bytes) +{ + struct comedi_async *async = subd->async; + + if (num_bytes == 0) + return 0; + + num_bytes = comedi_buf_read_alloc(async, num_bytes); + comedi_buf_memcpy_from(async, 0, data, num_bytes); + comedi_buf_read_free(async, num_bytes); + increment_scan_progress(subd, num_bytes); + async->events |= COMEDI_CB_BLOCK; + + return num_bytes; +} +EXPORT_SYMBOL(cfc_read_array_from_buffer); + +unsigned int cfc_handle_events(struct comedi_device *dev, + struct comedi_subdevice *subd) +{ + unsigned int events = subd->async->events; + + if (events == 0) + return events; + + if (events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) + subd->cancel(dev, subd); + + comedi_event(dev, subd); + + return events; +} +EXPORT_SYMBOL(cfc_handle_events); + +MODULE_AUTHOR("Frank Mori Hess "); +MODULE_DESCRIPTION("Shared functions for Comedi low-level drivers"); +MODULE_LICENSE("GPL"); + +static int __init comedi_fc_init_module(void) +{ + return 0; +} + +static void __exit comedi_fc_cleanup_module(void) +{ +} + +module_init(comedi_fc_init_module); +module_exit(comedi_fc_cleanup_module); diff --git a/drivers/staging/comedi/drivers/comedi_fc.h b/drivers/staging/comedi/drivers/comedi_fc.h new file mode 100644 index 00000000..4b2cfd32 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_fc.h @@ -0,0 +1,76 @@ +/* + comedi_fc.h + + This is a place for code driver writers wish to share between + two or more drivers. These functions are meant to be used only + by drivers, they are NOT part of the kcomedilib API! + + Author: Frank Mori Hess + Copyright (C) 2002 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************/ + +#ifndef _COMEDI_FC_H +#define _COMEDI_FC_H + +#include "../comedidev.h" + +/* Writes an array of data points to comedi's buffer */ +extern unsigned int cfc_write_array_to_buffer(struct comedi_subdevice *subd, + void *data, + unsigned int num_bytes); + +static inline unsigned int cfc_write_to_buffer(struct comedi_subdevice *subd, + short data) +{ + return cfc_write_array_to_buffer(subd, &data, sizeof(data)); +}; + +static inline unsigned int cfc_write_long_to_buffer(struct comedi_subdevice + *subd, unsigned int data) +{ + return cfc_write_array_to_buffer(subd, &data, sizeof(data)); +}; + +extern unsigned int cfc_read_array_from_buffer(struct comedi_subdevice *subd, + void *data, + unsigned int num_bytes); + +extern unsigned int cfc_handle_events(struct comedi_device *dev, + struct comedi_subdevice *subd); + +static inline unsigned int cfc_bytes_per_scan(struct comedi_subdevice *subd) +{ + int num_samples; + int bits_per_sample; + + switch (subd->type) { + case COMEDI_SUBD_DI: + case COMEDI_SUBD_DO: + case COMEDI_SUBD_DIO: + bits_per_sample = 8 * bytes_per_sample(subd); + num_samples = (subd->async->cmd.chanlist_len + + bits_per_sample - 1) / bits_per_sample; + break; + default: + num_samples = subd->async->cmd.chanlist_len; + break; + } + return num_samples * bytes_per_sample(subd); +} + +#endif /* _COMEDI_FC_H */ diff --git a/drivers/staging/comedi/drivers/comedi_parport.c b/drivers/staging/comedi/drivers/comedi_parport.c new file mode 100644 index 00000000..21d834dd --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_parport.c @@ -0,0 +1,413 @@ +/* + comedi/drivers/comedi_parport.c + hardware driver for standard parallel port + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998,2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: comedi_parport +Description: Standard PC parallel port +Author: ds +Status: works in immediate mode +Devices: [standard] parallel port (comedi_parport) +Updated: Tue, 30 Apr 2002 21:11:45 -0700 + +A cheap and easy way to get a few more digital I/O lines. Steal +additional parallel ports from old computers or your neighbors' +computers. + +Option list: + 0: I/O port base for the parallel port. + 1: IRQ + +Parallel Port Lines: + +pin subdev chan aka +--- ------ ---- --- +1 2 0 strobe +2 0 0 data 0 +3 0 1 data 1 +4 0 2 data 2 +5 0 3 data 3 +6 0 4 data 4 +7 0 5 data 5 +8 0 6 data 6 +9 0 7 data 7 +10 1 3 acknowledge +11 1 4 busy +12 1 2 output +13 1 1 printer selected +14 2 1 auto LF +15 1 0 error +16 2 2 init +17 2 3 select printer +18-25 ground + +Notes: + +Subdevices 0 is digital I/O, subdevice 1 is digital input, and +subdevice 2 is digital output. Unlike other Comedi devices, +subdevice 0 defaults to output. + +Pins 13 and 14 are inverted once by Comedi and once by the +hardware, thus cancelling the effect. + +Pin 1 is a strobe, thus acts like one. There's no way in software +to change this, at least on a standard parallel port. + +Subdevice 3 pretends to be a digital input subdevice, but it always +returns 0 when read. However, if you run a command with +scan_begin_src=TRIG_EXT, it uses pin 10 as a external triggering +pin, which can be used to wake up tasks. +*/ +/* + see http://www.beyondlogic.org/ for information. + or http://www.linux-magazin.de/ausgabe/1999/10/IO/io.html + */ + +#include "../comedidev.h" +#include +#include + +#define PARPORT_SIZE 3 + +#define PARPORT_A 0 +#define PARPORT_B 1 +#define PARPORT_C 2 + +static int parport_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int parport_detach(struct comedi_device *dev); +static struct comedi_driver driver_parport = { + .driver_name = "comedi_parport", + .module = THIS_MODULE, + .attach = parport_attach, + .detach = parport_detach, +}; + +static int __init driver_parport_init_module(void) +{ + return comedi_driver_register(&driver_parport); +} + +static void __exit driver_parport_cleanup_module(void) +{ + comedi_driver_unregister(&driver_parport); +} + +module_init(driver_parport_init_module); +module_exit(driver_parport_cleanup_module); + +struct parport_private { + unsigned int a_data; + unsigned int c_data; + int enable_irq; +}; +#define devpriv ((struct parport_private *)(dev->private)) + +static int parport_insn_a(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0]) { + devpriv->a_data &= ~data[0]; + devpriv->a_data |= (data[0] & data[1]); + + outb(devpriv->a_data, dev->iobase + PARPORT_A); + } + + data[1] = inb(dev->iobase + PARPORT_A); + + return 2; +} + +static int parport_insn_config_a(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0]) { + s->io_bits = 0xff; + devpriv->c_data &= ~(1 << 5); + } else { + s->io_bits = 0; + devpriv->c_data |= (1 << 5); + } + outb(devpriv->c_data, dev->iobase + PARPORT_C); + + return 1; +} + +static int parport_insn_b(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0]) { + /* should writes be ignored? */ + /* anyone??? */ + } + + data[1] = (inb(dev->iobase + PARPORT_B) >> 3); + + return 2; +} + +static int parport_insn_c(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] &= 0x0f; + if (data[0]) { + devpriv->c_data &= ~data[0]; + devpriv->c_data |= (data[0] & data[1]); + + outb(devpriv->c_data, dev->iobase + PARPORT_C); + } + + data[1] = devpriv->c_data & 0xf; + + return 2; +} + +static int parport_intr_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n < 1) + return -EINVAL; + + data[1] = 0; + return 2; +} + +static int parport_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1 */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_FOLLOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: ignored */ + + if (err) + return 2; + + /* step 3: */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + if (cmd->scan_end_arg != 1) { + cmd->scan_end_arg = 1; + err++; + } + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + + if (err) + return 3; + + /* step 4: ignored */ + + if (err) + return 4; + + return 0; +} + +static int parport_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + devpriv->c_data |= 0x10; + outb(devpriv->c_data, dev->iobase + PARPORT_C); + + devpriv->enable_irq = 1; + + return 0; +} + +static int parport_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + printk(KERN_DEBUG "parport_intr_cancel()\n"); + + devpriv->c_data &= ~0x10; + outb(devpriv->c_data, dev->iobase + PARPORT_C); + + devpriv->enable_irq = 0; + + return 0; +} + +static irqreturn_t parport_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 3; + + if (!devpriv->enable_irq) { + printk(KERN_ERR "comedi_parport: bogus irq, ignored\n"); + return IRQ_NONE; + } + + comedi_buf_put(s->async, 0); + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + + comedi_event(dev, s); + return IRQ_HANDLED; +} + +static int parport_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int ret; + unsigned int irq; + unsigned long iobase; + struct comedi_subdevice *s; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: parport: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, PARPORT_SIZE, "parport (comedi)")) { + printk(KERN_ERR "I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + irq = it->options[1]; + if (irq) { + printk(KERN_INFO " irq=%u", irq); + ret = request_irq(irq, parport_interrupt, 0, "comedi_parport", + dev); + if (ret < 0) { + printk(KERN_ERR " irq not available\n"); + return -EINVAL; + } + dev->irq = irq; + } + dev->board_name = "parport"; + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + ret = alloc_private(dev, sizeof(struct parport_private)); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_insn_a; + s->insn_config = parport_insn_config_a; + + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 5; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_insn_b; + + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_insn_c; + + s = dev->subdevices + 3; + if (irq) { + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_intr_insn; + s->do_cmdtest = parport_intr_cmdtest; + s->do_cmd = parport_intr_cmd; + s->cancel = parport_intr_cancel; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + devpriv->a_data = 0; + outb(devpriv->a_data, dev->iobase + PARPORT_A); + devpriv->c_data = 0; + outb(devpriv->c_data, dev->iobase + PARPORT_C); + + printk(KERN_INFO "\n"); + return 1; +} + +static int parport_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: parport: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, PARPORT_SIZE); + + if (dev->irq) + free_irq(dev->irq, dev); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/comedi_pci.h b/drivers/staging/comedi/drivers/comedi_pci.h new file mode 100644 index 00000000..c14a036a --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_pci.h @@ -0,0 +1,60 @@ +/* + comedi/drivers/comedi_pci.h + Various PCI functions for drivers. + + Copyright (C) 2007 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _COMEDI_PCI_H_ +#define _COMEDI_PCI_H_ + +#include + +/* + * Enable the PCI device and request the regions. + */ +static inline int comedi_pci_enable(struct pci_dev *pdev, const char *res_name) +{ + int rc; + + rc = pci_enable_device(pdev); + if (rc < 0) + return rc; + + rc = pci_request_regions(pdev, res_name); + if (rc < 0) + pci_disable_device(pdev); + + return rc; +} + +/* + * Release the regions and disable the PCI device. + * + * This must be matched with a previous successful call to comedi_pci_enable(). + */ +static inline void comedi_pci_disable(struct pci_dev *pdev) +{ + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +#endif diff --git a/drivers/staging/comedi/drivers/comedi_test.c b/drivers/staging/comedi/drivers/comedi_test.c new file mode 100644 index 00000000..a804742b --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_test.c @@ -0,0 +1,566 @@ +/* + comedi/drivers/comedi_test.c + + Generates fake waveform signals that can be read through + the command interface. It does _not_ read from any board; + it just generates deterministic waveforms. + Useful for various testing purposes. + + Copyright (C) 2002 Joachim Wuttke + Copyright (C) 2002 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************/ +/* +Driver: comedi_test +Description: generates fake waveforms +Author: Joachim Wuttke , Frank Mori Hess + , ds +Devices: +Status: works +Updated: Sat, 16 Mar 2002 17:34:48 -0800 + +This driver is mainly for testing purposes, but can also be used to +generate sample waveforms on systems that don't have data acquisition +hardware. + +Configuration options: + [0] - Amplitude in microvolts for fake waveforms (default 1 volt) + [1] - Period in microseconds for fake waveforms (default 0.1 sec) + +Generates a sawtooth wave on channel 0, square wave on channel 1, additional +waveforms could be added to other channels (currently they return flatline +zero volts). + +*/ + +#include "../comedidev.h" + +#include + +#include "comedi_fc.h" +#include + +/* Board descriptions */ +struct waveform_board { + const char *name; + int ai_chans; + int ai_bits; + int have_dio; +}; + +#define N_CHANS 8 + +static const struct waveform_board waveform_boards[] = { + { + .name = "comedi_test", + .ai_chans = N_CHANS, + .ai_bits = 16, + .have_dio = 0, + }, +}; + +#define thisboard ((const struct waveform_board *)dev->board_ptr) + +/* Data unique to this driver */ +struct waveform_private { + struct timer_list timer; + struct timeval last; /* time at which last timer interrupt occurred */ + unsigned int uvolt_amplitude; /* waveform amplitude in microvolts */ + unsigned long usec_period; /* waveform period in microseconds */ + unsigned long usec_current; /* current time (modulo waveform period) */ + unsigned long usec_remainder; /* usec since last scan; */ + unsigned long ai_count; /* number of conversions remaining */ + unsigned int scan_period; /* scan period in usec */ + unsigned int convert_period; /* conversion period in usec */ + unsigned timer_running:1; + unsigned int ao_loopbacks[N_CHANS]; +}; +#define devpriv ((struct waveform_private *)dev->private) + +static int waveform_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int waveform_detach(struct comedi_device *dev); +static struct comedi_driver driver_waveform = { + .driver_name = "comedi_test", + .module = THIS_MODULE, + .attach = waveform_attach, + .detach = waveform_detach, + .board_name = &waveform_boards[0].name, + .offset = sizeof(struct waveform_board), + .num_names = ARRAY_SIZE(waveform_boards), +}; + +static int __init driver_waveform_init_module(void) +{ + return comedi_driver_register(&driver_waveform); +} + +static void __exit driver_waveform_cleanup_module(void) +{ + comedi_driver_unregister(&driver_waveform); +} + +module_init(driver_waveform_init_module); +module_exit(driver_waveform_cleanup_module); + +static int waveform_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int waveform_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); +static int waveform_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static int waveform_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int waveform_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static short fake_sawtooth(struct comedi_device *dev, unsigned int range, + unsigned long current_time); +static short fake_squarewave(struct comedi_device *dev, unsigned int range, + unsigned long current_time); +static short fake_flatline(struct comedi_device *dev, unsigned int range, + unsigned long current_time); +static short fake_waveform(struct comedi_device *dev, unsigned int channel, + unsigned int range, unsigned long current_time); + +/* 1000 nanosec in a microsec */ +static const int nano_per_micro = 1000; + +/* fake analog input ranges */ +static const struct comedi_lrange waveform_ai_ranges = { + 2, + { + BIP_RANGE(10), + BIP_RANGE(5), + } +}; + +/* + This is the background routine used to generate arbitrary data. + It should run in the background; therefore it is scheduled by + a timer mechanism. +*/ +static void waveform_ai_interrupt(unsigned long arg) +{ + struct comedi_device *dev = (struct comedi_device *)arg; + struct comedi_async *async = dev->read_subdev->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int i, j; + /* all times in microsec */ + unsigned long elapsed_time; + unsigned int num_scans; + struct timeval now; + + do_gettimeofday(&now); + + elapsed_time = + 1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec - + devpriv->last.tv_usec; + devpriv->last = now; + num_scans = + (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period; + devpriv->usec_remainder = + (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period; + async->events = 0; + + for (i = 0; i < num_scans; i++) { + for (j = 0; j < cmd->chanlist_len; j++) { + cfc_write_to_buffer(dev->read_subdev, + fake_waveform(dev, + CR_CHAN(cmd-> + chanlist[j]), + CR_RANGE(cmd-> + chanlist[j]), + devpriv-> + usec_current + + i * + devpriv->scan_period + + j * + devpriv-> + convert_period)); + } + devpriv->ai_count++; + if (cmd->stop_src == TRIG_COUNT + && devpriv->ai_count >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + break; + } + } + + devpriv->usec_current += elapsed_time; + devpriv->usec_current %= devpriv->usec_period; + + if ((async->events & COMEDI_CB_EOA) == 0 && devpriv->timer_running) + mod_timer(&devpriv->timer, jiffies + 1); + else + del_timer(&devpriv->timer); + + comedi_event(dev, dev->read_subdev); +} + +static int waveform_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int amplitude = it->options[0]; + int period = it->options[1]; + int i; + + dev->board_name = thisboard->name; + + if (alloc_private(dev, sizeof(struct waveform_private)) < 0) + return -ENOMEM; + + /* set default amplitude and period */ + if (amplitude <= 0) + amplitude = 1000000; /* 1 volt */ + if (period <= 0) + period = 100000; /* 0.1 sec */ + + devpriv->uvolt_amplitude = amplitude; + devpriv->usec_period = period; + + dev->n_subdevices = 2; + if (alloc_subdevices(dev, dev->n_subdevices) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + dev->read_subdev = s; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + s->n_chan = thisboard->ai_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &waveform_ai_ranges; + s->len_chanlist = s->n_chan * 2; + s->insn_read = waveform_ai_insn_read; + s->do_cmd = waveform_ai_cmd; + s->do_cmdtest = waveform_ai_cmdtest; + s->cancel = waveform_ai_cancel; + + s = dev->subdevices + 1; + dev->write_subdev = s; + /* analog output subdevice (loopback) */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND; + s->n_chan = thisboard->ai_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &waveform_ai_ranges; + s->len_chanlist = s->n_chan * 2; + s->insn_write = waveform_ao_insn_write; + s->do_cmd = NULL; + s->do_cmdtest = NULL; + s->cancel = NULL; + + /* Our default loopback value is just a 0V flatline */ + for (i = 0; i < s->n_chan; i++) + devpriv->ao_loopbacks[i] = s->maxdata / 2; + + init_timer(&(devpriv->timer)); + devpriv->timer.function = waveform_ai_interrupt; + devpriv->timer.data = (unsigned long)dev; + + printk(KERN_INFO "comedi%d: comedi_test: " + "%i microvolt, %li microsecond waveform attached\n", dev->minor, + devpriv->uvolt_amplitude, devpriv->usec_period); + return 1; +} + +static int waveform_detach(struct comedi_device *dev) +{ + printk("comedi%d: comedi_test: remove\n", dev->minor); + + if (dev->private) + waveform_ai_cancel(dev, dev->read_subdev); + + return 0; +} + +static int waveform_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW | TRIG_TIMER; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique and mutually compatible + */ + + if (cmd->convert_src != TRIG_NOW && cmd->convert_src != TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->convert_src == TRIG_NOW) { + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + } + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < nano_per_micro) { + cmd->scan_begin_arg = nano_per_micro; + err++; + } + if (cmd->convert_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->chanlist_len) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->chanlist_len; + err++; + } + } + /* + * XXX these checks are generic and should go in core if not there + * already + */ + if (!cmd->chanlist_len) { + cmd->chanlist_len = 1; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + /* round to nearest microsec */ + cmd->scan_begin_arg = + nano_per_micro * ((tmp + + (nano_per_micro / 2)) / nano_per_micro); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + /* round to nearest microsec */ + cmd->convert_arg = + nano_per_micro * ((tmp + + (nano_per_micro / 2)) / nano_per_micro); + if (tmp != cmd->convert_arg) + err++; + } + + if (err) + return 4; + + return 0; +} + +static int waveform_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + if (cmd->flags & TRIG_RT) { + comedi_error(dev, + "commands at RT priority not supported in this driver"); + return -1; + } + + devpriv->timer_running = 1; + devpriv->ai_count = 0; + devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro; + + if (cmd->convert_src == TRIG_NOW) + devpriv->convert_period = 0; + else if (cmd->convert_src == TRIG_TIMER) + devpriv->convert_period = cmd->convert_arg / nano_per_micro; + else { + comedi_error(dev, "bug setting conversion period"); + return -1; + } + + do_gettimeofday(&devpriv->last); + devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period; + devpriv->usec_remainder = 0; + + devpriv->timer.expires = jiffies + 1; + add_timer(&devpriv->timer); + return 0; +} + +static int waveform_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + devpriv->timer_running = 0; + del_timer(&devpriv->timer); + return 0; +} + +static short fake_sawtooth(struct comedi_device *dev, unsigned int range_index, + unsigned long current_time) +{ + struct comedi_subdevice *s = dev->read_subdev; + unsigned int offset = s->maxdata / 2; + u64 value; + const struct comedi_krange *krange = + &s->range_table->range[range_index]; + u64 binary_amplitude; + + binary_amplitude = s->maxdata; + binary_amplitude *= devpriv->uvolt_amplitude; + do_div(binary_amplitude, krange->max - krange->min); + + current_time %= devpriv->usec_period; + value = current_time; + value *= binary_amplitude * 2; + do_div(value, devpriv->usec_period); + value -= binary_amplitude; /* get rid of sawtooth's dc offset */ + + return offset + value; +} + +static short fake_squarewave(struct comedi_device *dev, + unsigned int range_index, + unsigned long current_time) +{ + struct comedi_subdevice *s = dev->read_subdev; + unsigned int offset = s->maxdata / 2; + u64 value; + const struct comedi_krange *krange = + &s->range_table->range[range_index]; + current_time %= devpriv->usec_period; + + value = s->maxdata; + value *= devpriv->uvolt_amplitude; + do_div(value, krange->max - krange->min); + + if (current_time < devpriv->usec_period / 2) + value *= -1; + + return offset + value; +} + +static short fake_flatline(struct comedi_device *dev, unsigned int range_index, + unsigned long current_time) +{ + return dev->read_subdev->maxdata / 2; +} + +/* generates a different waveform depending on what channel is read */ +static short fake_waveform(struct comedi_device *dev, unsigned int channel, + unsigned int range, unsigned long current_time) +{ + enum { + SAWTOOTH_CHAN, + SQUARE_CHAN, + }; + switch (channel) { + case SAWTOOTH_CHAN: + return fake_sawtooth(dev, range, current_time); + break; + case SQUARE_CHAN: + return fake_squarewave(dev, range, current_time); + break; + default: + break; + } + + return fake_flatline(dev, range, current_time); +} + +static int waveform_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_loopbacks[chan]; + + return insn->n; +} + +static int waveform_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + devpriv->ao_loopbacks[chan] = data[i]; + + return insn->n; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/contec_pci_dio.c b/drivers/staging/comedi/drivers/contec_pci_dio.c new file mode 100644 index 00000000..e3659bd6 --- /dev/null +++ b/drivers/staging/comedi/drivers/contec_pci_dio.c @@ -0,0 +1,271 @@ +/* + comedi/drivers/contec_pci_dio.c + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: contec_pci_dio +Description: Contec PIO1616L digital I/O board +Devices: [Contec] PIO1616L (contec_pci_dio) +Author: Stefano Rivoir +Updated: Wed, 27 Jun 2007 13:00:06 +0100 +Status: works + +Configuration Options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. +*/ + +#include "../comedidev.h" + +#include "comedi_pci.h" + +enum contec_model { + PIO1616L = 0, +}; + +struct contec_board { + const char *name; + int model; + int in_ports; + int out_ports; + int in_offs; + int out_offs; + int out_boffs; +}; +static const struct contec_board contec_boards[] = { + {"PIO1616L", PIO1616L, 16, 16, 0, 2, 10}, +}; + +#define PCI_DEVICE_ID_PIO1616L 0x8172 +static DEFINE_PCI_DEVICE_TABLE(contec_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_CONTEC, PCI_DEVICE_ID_PIO1616L), + .driver_data = PIO1616L }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, contec_pci_table); + +#define thisboard ((const struct contec_board *)dev->board_ptr) + +struct contec_private { + int data; + + struct pci_dev *pci_dev; + +}; + +#define devpriv ((struct contec_private *)dev->private) + +static int contec_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int contec_detach(struct comedi_device *dev); +static struct comedi_driver driver_contec = { + .driver_name = "contec_pci_dio", + .module = THIS_MODULE, + .attach = contec_attach, + .detach = contec_detach, +}; + +/* Classic digital IO */ +static int contec_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int contec_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +#if 0 +static int contec_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); + +static int contec_ns_to_timer(unsigned int *ns, int round); +#endif + +static int contec_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pci_dev *pcidev = NULL; + struct comedi_subdevice *s; + + printk("comedi%d: contec: ", dev->minor); + + dev->board_name = thisboard->name; + + if (alloc_private(dev, sizeof(struct contec_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 2) < 0) + return -ENOMEM; + + for_each_pci_dev(pcidev) { + if (pcidev->vendor == PCI_VENDOR_ID_CONTEC && + pcidev->device == PCI_DEVICE_ID_PIO1616L) { + if (it->options[0] || it->options[1]) { + /* Check bus and slot. */ + if (it->options[0] != pcidev->bus->number || + it->options[1] != PCI_SLOT(pcidev->devfn)) { + continue; + } + } + devpriv->pci_dev = pcidev; + if (comedi_pci_enable(pcidev, "contec_pci_dio")) { + printk + ("error enabling PCI device and request regions!\n"); + return -EIO; + } + dev->iobase = pci_resource_start(pcidev, 0); + printk(" base addr %lx ", dev->iobase); + + dev->board_ptr = contec_boards + 0; + + s = dev->subdevices + 0; + + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = contec_di_insn_bits; + + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = contec_do_insn_bits; + + printk("attached\n"); + + return 1; + } + } + + printk("card not present!\n"); + + return -EIO; +} + +static int contec_detach(struct comedi_device *dev) +{ + printk("comedi%d: contec: remove\n", dev->minor); + + if (devpriv && devpriv->pci_dev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + + return 0; +} + +#if 0 +static int contec_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + printk("contec_cmdtest called\n"); + return 0; +} + +static int contec_ns_to_timer(unsigned int *ns, int round) +{ + return *ns; +} +#endif + +static int contec_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + dev_dbg(dev->hw_dev, "contec_do_insn_bits called\n"); + dev_dbg(dev->hw_dev, "data: %d %d\n", data[0], data[1]); + + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + dev_dbg(dev->hw_dev, "out: %d on %lx\n", s->state, + dev->iobase + thisboard->out_offs); + outw(s->state, dev->iobase + thisboard->out_offs); + } + return 2; +} + +static int contec_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + dev_dbg(dev->hw_dev, "contec_di_insn_bits called\n"); + dev_dbg(dev->hw_dev, "data: %d %d\n", data[0], data[1]); + + if (insn->n != 2) + return -EINVAL; + + data[1] = inw(dev->iobase + thisboard->in_offs); + + return 2; +} + +static int __devinit driver_contec_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_contec.driver_name); +} + +static void __devexit driver_contec_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_contec_pci_driver = { + .id_table = contec_pci_table, + .probe = &driver_contec_pci_probe, + .remove = __devexit_p(&driver_contec_pci_remove) +}; + +static int __init driver_contec_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_contec); + if (retval < 0) + return retval; + + driver_contec_pci_driver.name = (char *)driver_contec.driver_name; + return pci_register_driver(&driver_contec_pci_driver); +} + +static void __exit driver_contec_cleanup_module(void) +{ + pci_unregister_driver(&driver_contec_pci_driver); + comedi_driver_unregister(&driver_contec); +} + +module_init(driver_contec_init_module); +module_exit(driver_contec_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/daqboard2000.c b/drivers/staging/comedi/drivers/daqboard2000.c new file mode 100644 index 00000000..e61c6a8f --- /dev/null +++ b/drivers/staging/comedi/drivers/daqboard2000.c @@ -0,0 +1,919 @@ +/* + comedi/drivers/daqboard2000.c + hardware driver for IOtech DAQboard/2000 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999 Anders Blomdell + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: daqboard2000 +Description: IOTech DAQBoard/2000 +Author: Anders Blomdell +Status: works +Updated: Mon, 14 Apr 2008 15:28:52 +0100 +Devices: [IOTech] DAQBoard/2000 (daqboard2000) + +Much of the functionality of this driver was determined from reading +the source code for the Windows driver. + +The FPGA on the board requires initialization code, which can +be loaded by comedi_config using the -i +option. The initialization code is available from http://www.comedi.org +in the comedi_nonfree_firmware tarball. + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. +*/ +/* + This card was obviously never intended to leave the Windows world, + since it lacked all kind of hardware documentation (except for cable + pinouts, plug and pray has something to catch up with yet). + + With some help from our swedish distributor, we got the Windows sourcecode + for the card, and here are the findings so far. + + 1. A good document that describes the PCI interface chip is 9080db-106.pdf + available from http://www.plxtech.com/products/io/pci9080 + + 2. The initialization done so far is: + a. program the FPGA (windows code sans a lot of error messages) + b. + + 3. Analog out seems to work OK with DAC's disabled, if DAC's are enabled, + you have to output values to all enabled DAC's until result appears, I + guess that it has something to do with pacer clocks, but the source + gives me no clues. I'll keep it simple so far. + + 4. Analog in. + Each channel in the scanlist seems to be controlled by four + control words: + + Word0: + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! | | | ! | | | ! | | | ! | | | ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Word1: + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! | | | ! | | | ! | | | ! | | | ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | | | | | | + +------+------+ | | | | +-- Digital input (??) + | | | | +---- 10 us settling time + | | | +------ Suspend acquisition (last to scan) + | | +-------- Simultaneous sample and hold + | +---------- Signed data format + +------------------------- Correction offset low + + Word2: + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! | | | ! | | | ! | | | ! | | | ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | | | | | | | | | + +-----+ +--+--+ +++ +++ +--+--+ + | | | | +----- Expansion channel + | | | +----------- Expansion gain + | | +--------------- Channel (low) + | +--------------------- Correction offset high + +----------------------------- Correction gain low + Word3: + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ! | | | ! | | | ! | | | ! | | | ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | | | | | | | | + +------+------+ | | +-+-+ | | +-- Low bank enable + | | | | | +---- High bank enable + | | | | +------ Hi/low select + | | | +---------- Gain (1,?,2,4,8,16,32,64) + | | +-------------- differential/single ended + | +---------------- Unipolar + +------------------------- Correction gain high + + 999. The card seems to have an incredible amount of capabilities, but + trying to reverse engineer them from the Windows source is beyond my + patience. + + */ + +#include "../comedidev.h" + +#include +#include + +#include "comedi_pci.h" +#include "8255.h" + +#define DAQBOARD2000_SUBSYSTEM_IDS2 0x00021616 /* Daqboard/2000 - 2 Dacs */ +#define DAQBOARD2000_SUBSYSTEM_IDS4 0x00041616 /* Daqboard/2000 - 4 Dacs */ + +#define DAQBOARD2000_DAQ_SIZE 0x1002 +#define DAQBOARD2000_PLX_SIZE 0x100 + +/* Initialization bits for the Serial EEPROM Control Register */ +#define DAQBOARD2000_SECRProgPinHi 0x8001767e +#define DAQBOARD2000_SECRProgPinLo 0x8000767e +#define DAQBOARD2000_SECRLocalBusHi 0xc000767e +#define DAQBOARD2000_SECRLocalBusLo 0x8000767e +#define DAQBOARD2000_SECRReloadHi 0xa000767e +#define DAQBOARD2000_SECRReloadLo 0x8000767e + +/* SECR status bits */ +#define DAQBOARD2000_EEPROM_PRESENT 0x10000000 + +/* CPLD status bits */ +#define DAQBOARD2000_CPLD_INIT 0x0002 +#define DAQBOARD2000_CPLD_DONE 0x0004 + +/* Available ranges */ +static const struct comedi_lrange range_daqboard2000_ai = { 13, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-2.5, + 2.5), + RANGE(-1.25, + 1.25), + RANGE(-0.625, + 0.625), + RANGE(-0.3125, + 0.3125), + RANGE(-0.156, + 0.156), + RANGE(0, 10), + RANGE(0, 5), + RANGE(0, 2.5), + RANGE(0, 1.25), + RANGE(0, + 0.625), + RANGE(0, + 0.3125) + } +}; + +static const struct comedi_lrange range_daqboard2000_ao = { 1, { + RANGE(-10, 10) + } +}; + +struct daqboard2000_hw { + volatile u16 acqControl; /* 0x00 */ + volatile u16 acqScanListFIFO; /* 0x02 */ + volatile u32 acqPacerClockDivLow; /* 0x04 */ + + volatile u16 acqScanCounter; /* 0x08 */ + volatile u16 acqPacerClockDivHigh; /* 0x0a */ + volatile u16 acqTriggerCount; /* 0x0c */ + volatile u16 fill2; /* 0x0e */ + volatile u16 acqResultsFIFO; /* 0x10 */ + volatile u16 fill3; /* 0x12 */ + volatile u16 acqResultsShadow; /* 0x14 */ + volatile u16 fill4; /* 0x16 */ + volatile u16 acqAdcResult; /* 0x18 */ + volatile u16 fill5; /* 0x1a */ + volatile u16 dacScanCounter; /* 0x1c */ + volatile u16 fill6; /* 0x1e */ + + volatile u16 dacControl; /* 0x20 */ + volatile u16 fill7; /* 0x22 */ + volatile s16 dacFIFO; /* 0x24 */ + volatile u16 fill8[2]; /* 0x26 */ + volatile u16 dacPacerClockDiv; /* 0x2a */ + volatile u16 refDacs; /* 0x2c */ + volatile u16 fill9; /* 0x2e */ + + volatile u16 dioControl; /* 0x30 */ + volatile s16 dioP3hsioData; /* 0x32 */ + volatile u16 dioP3Control; /* 0x34 */ + volatile u16 calEepromControl; /* 0x36 */ + volatile s16 dacSetting[4]; /* 0x38 */ + volatile s16 dioP2ExpansionIO8Bit[32]; /* 0x40 */ + + volatile u16 ctrTmrControl; /* 0x80 */ + volatile u16 fill10[3]; /* 0x82 */ + volatile s16 ctrInput[4]; /* 0x88 */ + volatile u16 fill11[8]; /* 0x90 */ + volatile u16 timerDivisor[2]; /* 0xa0 */ + volatile u16 fill12[6]; /* 0xa4 */ + + volatile u16 dmaControl; /* 0xb0 */ + volatile u16 trigControl; /* 0xb2 */ + volatile u16 fill13[2]; /* 0xb4 */ + volatile u16 calEeprom; /* 0xb8 */ + volatile u16 acqDigitalMark; /* 0xba */ + volatile u16 trigDacs; /* 0xbc */ + volatile u16 fill14; /* 0xbe */ + volatile s16 dioP2ExpansionIO16Bit[32]; /* 0xc0 */ +}; + +/* Scan Sequencer programming */ +#define DAQBOARD2000_SeqStartScanList 0x0011 +#define DAQBOARD2000_SeqStopScanList 0x0010 + +/* Prepare for acquisition */ +#define DAQBOARD2000_AcqResetScanListFifo 0x0004 +#define DAQBOARD2000_AcqResetResultsFifo 0x0002 +#define DAQBOARD2000_AcqResetConfigPipe 0x0001 + +/* Acqusition status bits */ +#define DAQBOARD2000_AcqResultsFIFOMore1Sample 0x0001 +#define DAQBOARD2000_AcqResultsFIFOHasValidData 0x0002 +#define DAQBOARD2000_AcqResultsFIFOOverrun 0x0004 +#define DAQBOARD2000_AcqLogicScanning 0x0008 +#define DAQBOARD2000_AcqConfigPipeFull 0x0010 +#define DAQBOARD2000_AcqScanListFIFOEmpty 0x0020 +#define DAQBOARD2000_AcqAdcNotReady 0x0040 +#define DAQBOARD2000_ArbitrationFailure 0x0080 +#define DAQBOARD2000_AcqPacerOverrun 0x0100 +#define DAQBOARD2000_DacPacerOverrun 0x0200 +#define DAQBOARD2000_AcqHardwareError 0x01c0 + +/* Scan Sequencer programming */ +#define DAQBOARD2000_SeqStartScanList 0x0011 +#define DAQBOARD2000_SeqStopScanList 0x0010 + +/* Pacer Clock Control */ +#define DAQBOARD2000_AdcPacerInternal 0x0030 +#define DAQBOARD2000_AdcPacerExternal 0x0032 +#define DAQBOARD2000_AdcPacerEnable 0x0031 +#define DAQBOARD2000_AdcPacerEnableDacPacer 0x0034 +#define DAQBOARD2000_AdcPacerDisable 0x0030 +#define DAQBOARD2000_AdcPacerNormalMode 0x0060 +#define DAQBOARD2000_AdcPacerCompatibilityMode 0x0061 +#define DAQBOARD2000_AdcPacerInternalOutEnable 0x0008 +#define DAQBOARD2000_AdcPacerExternalRising 0x0100 + +/* DAC status */ +#define DAQBOARD2000_DacFull 0x0001 +#define DAQBOARD2000_RefBusy 0x0002 +#define DAQBOARD2000_TrgBusy 0x0004 +#define DAQBOARD2000_CalBusy 0x0008 +#define DAQBOARD2000_Dac0Busy 0x0010 +#define DAQBOARD2000_Dac1Busy 0x0020 +#define DAQBOARD2000_Dac2Busy 0x0040 +#define DAQBOARD2000_Dac3Busy 0x0080 + +/* DAC control */ +#define DAQBOARD2000_Dac0Enable 0x0021 +#define DAQBOARD2000_Dac1Enable 0x0031 +#define DAQBOARD2000_Dac2Enable 0x0041 +#define DAQBOARD2000_Dac3Enable 0x0051 +#define DAQBOARD2000_DacEnableBit 0x0001 +#define DAQBOARD2000_Dac0Disable 0x0020 +#define DAQBOARD2000_Dac1Disable 0x0030 +#define DAQBOARD2000_Dac2Disable 0x0040 +#define DAQBOARD2000_Dac3Disable 0x0050 +#define DAQBOARD2000_DacResetFifo 0x0004 +#define DAQBOARD2000_DacPatternDisable 0x0060 +#define DAQBOARD2000_DacPatternEnable 0x0061 +#define DAQBOARD2000_DacSelectSignedData 0x0002 +#define DAQBOARD2000_DacSelectUnsignedData 0x0000 + +/* Trigger Control */ +#define DAQBOARD2000_TrigAnalog 0x0000 +#define DAQBOARD2000_TrigTTL 0x0010 +#define DAQBOARD2000_TrigTransHiLo 0x0004 +#define DAQBOARD2000_TrigTransLoHi 0x0000 +#define DAQBOARD2000_TrigAbove 0x0000 +#define DAQBOARD2000_TrigBelow 0x0004 +#define DAQBOARD2000_TrigLevelSense 0x0002 +#define DAQBOARD2000_TrigEdgeSense 0x0000 +#define DAQBOARD2000_TrigEnable 0x0001 +#define DAQBOARD2000_TrigDisable 0x0000 + +/* Reference Dac Selection */ +#define DAQBOARD2000_PosRefDacSelect 0x0100 +#define DAQBOARD2000_NegRefDacSelect 0x0000 + +static int daqboard2000_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int daqboard2000_detach(struct comedi_device *dev); + +static struct comedi_driver driver_daqboard2000 = { + .driver_name = "daqboard2000", + .module = THIS_MODULE, + .attach = daqboard2000_attach, + .detach = daqboard2000_detach, +}; + +struct daq200_boardtype { + const char *name; + int id; +}; +static const struct daq200_boardtype boardtypes[] = { + {"ids2", DAQBOARD2000_SUBSYSTEM_IDS2}, + {"ids4", DAQBOARD2000_SUBSYSTEM_IDS4}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct daq200_boardtype)) +#define this_board ((const struct daq200_boardtype *)dev->board_ptr) + +static DEFINE_PCI_DEVICE_TABLE(daqboard2000_pci_table) = { + { PCI_DEVICE(0x1616, 0x0409) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, daqboard2000_pci_table); + +struct daqboard2000_private { + enum { + card_daqboard_2000 + } card; + struct pci_dev *pci_dev; + void *daq; + void *plx; + int got_regions; + unsigned int ao_readback[2]; +}; + +#define devpriv ((struct daqboard2000_private *)dev->private) + +static void writeAcqScanListEntry(struct comedi_device *dev, u16 entry) +{ + struct daqboard2000_hw *fpga = devpriv->daq; + +/* udelay(4); */ + fpga->acqScanListFIFO = entry & 0x00ff; +/* udelay(4); */ + fpga->acqScanListFIFO = (entry >> 8) & 0x00ff; +} + +static void setup_sampling(struct comedi_device *dev, int chan, int gain) +{ + u16 word0, word1, word2, word3; + + /* Channel 0-7 diff, channel 8-23 single ended */ + word0 = 0; + word1 = 0x0004; /* Last scan */ + word2 = (chan << 6) & 0x00c0; + switch (chan / 4) { + case 0: + word3 = 0x0001; + break; + case 1: + word3 = 0x0002; + break; + case 2: + word3 = 0x0005; + break; + case 3: + word3 = 0x0006; + break; + case 4: + word3 = 0x0041; + break; + case 5: + word3 = 0x0042; + break; + default: + word3 = 0; + break; + } +/* + dev->eeprom.correctionDACSE[i][j][k].offset = 0x800; + dev->eeprom.correctionDACSE[i][j][k].gain = 0xc00; +*/ + /* These should be read from EEPROM */ + word2 |= 0x0800; + word3 |= 0xc000; +/* printk("%d %4.4x %4.4x %4.4x %4.4x\n", chan, word0, word1, word2, word3);*/ + writeAcqScanListEntry(dev, word0); + writeAcqScanListEntry(dev, word1); + writeAcqScanListEntry(dev, word2); + writeAcqScanListEntry(dev, word3); +} + +static int daqboard2000_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i; + struct daqboard2000_hw *fpga = devpriv->daq; + int gain, chan, timeout; + + fpga->acqControl = + DAQBOARD2000_AcqResetScanListFifo | + DAQBOARD2000_AcqResetResultsFifo | DAQBOARD2000_AcqResetConfigPipe; + + /* If pacer clock is not set to some high value (> 10 us), we + risk multiple samples to be put into the result FIFO. */ + fpga->acqPacerClockDivLow = 1000000; /* 1 second, should be long enough */ + fpga->acqPacerClockDivHigh = 0; + + gain = CR_RANGE(insn->chanspec); + chan = CR_CHAN(insn->chanspec); + + /* This doesn't look efficient. I decided to take the conservative + * approach when I did the insn conversion. Perhaps it would be + * better to have broken it completely, then someone would have been + * forced to fix it. --ds */ + for (i = 0; i < insn->n; i++) { + setup_sampling(dev, chan, gain); + /* Enable reading from the scanlist FIFO */ + fpga->acqControl = DAQBOARD2000_SeqStartScanList; + for (timeout = 0; timeout < 20; timeout++) { + if (fpga->acqControl & DAQBOARD2000_AcqConfigPipeFull) + break; + /* udelay(2); */ + } + fpga->acqControl = DAQBOARD2000_AdcPacerEnable; + for (timeout = 0; timeout < 20; timeout++) { + if (fpga->acqControl & DAQBOARD2000_AcqLogicScanning) + break; + /* udelay(2); */ + } + for (timeout = 0; timeout < 20; timeout++) { + if (fpga->acqControl & + DAQBOARD2000_AcqResultsFIFOHasValidData) { + break; + } + /* udelay(2); */ + } + data[i] = fpga->acqResultsFIFO; + fpga->acqControl = DAQBOARD2000_AdcPacerDisable; + fpga->acqControl = DAQBOARD2000_SeqStopScanList; + } + + return i; +} + +static int daqboard2000_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int daqboard2000_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + struct daqboard2000_hw *fpga = devpriv->daq; + int timeout; + + for (i = 0; i < insn->n; i++) { + /* + * OK, since it works OK without enabling the DAC's, let's keep + * it as simple as possible... + */ + /* fpga->dacControl = (chan + 2) * 0x0010 | 0x0001; udelay(1000); */ + fpga->dacSetting[chan] = data[i]; + for (timeout = 0; timeout < 20; timeout++) { + if ((fpga->dacControl & ((chan + 1) * 0x0010)) == 0) + break; + /* udelay(2); */ + } + devpriv->ao_readback[chan] = data[i]; + /* + * Since we never enabled the DAC's, we don't need to disable it... + * fpga->dacControl = (chan + 2) * 0x0010 | 0x0000; udelay(1000); + */ + } + + return i; +} + +static void daqboard2000_resetLocalBus(struct comedi_device *dev) +{ + dev_dbg(dev->hw_dev, "daqboard2000_resetLocalBus\n"); + writel(DAQBOARD2000_SECRLocalBusHi, devpriv->plx + 0x6c); + udelay(10000); + writel(DAQBOARD2000_SECRLocalBusLo, devpriv->plx + 0x6c); + udelay(10000); +} + +static void daqboard2000_reloadPLX(struct comedi_device *dev) +{ + dev_dbg(dev->hw_dev, "daqboard2000_reloadPLX\n"); + writel(DAQBOARD2000_SECRReloadLo, devpriv->plx + 0x6c); + udelay(10000); + writel(DAQBOARD2000_SECRReloadHi, devpriv->plx + 0x6c); + udelay(10000); + writel(DAQBOARD2000_SECRReloadLo, devpriv->plx + 0x6c); + udelay(10000); +} + +static void daqboard2000_pulseProgPin(struct comedi_device *dev) +{ + dev_dbg(dev->hw_dev, "daqboard2000_pulseProgPin 1\n"); + writel(DAQBOARD2000_SECRProgPinHi, devpriv->plx + 0x6c); + udelay(10000); + writel(DAQBOARD2000_SECRProgPinLo, devpriv->plx + 0x6c); + udelay(10000); /* Not in the original code, but I like symmetry... */ +} + +static int daqboard2000_pollCPLD(struct comedi_device *dev, int mask) +{ + int result = 0; + int i; + int cpld; + + /* timeout after 50 tries -> 5ms */ + for (i = 0; i < 50; i++) { + cpld = readw(devpriv->daq + 0x1000); + if ((cpld & mask) == mask) { + result = 1; + break; + } + udelay(100); + } + udelay(5); + return result; +} + +static int daqboard2000_writeCPLD(struct comedi_device *dev, int data) +{ + int result = 0; + + udelay(10); + writew(data, devpriv->daq + 0x1000); + if ((readw(devpriv->daq + 0x1000) & DAQBOARD2000_CPLD_INIT) == + DAQBOARD2000_CPLD_INIT) { + result = 1; + } + return result; +} + +static int initialize_daqboard2000(struct comedi_device *dev, + unsigned char *cpld_array, int len) +{ + int result = -EIO; + /* Read the serial EEPROM control register */ + int secr; + int retry; + int i; + + /* Check to make sure the serial eeprom is present on the board */ + secr = readl(devpriv->plx + 0x6c); + if (!(secr & DAQBOARD2000_EEPROM_PRESENT)) { +#ifdef DEBUG_EEPROM + dev_dbg(dev->hw_dev, "no serial eeprom\n"); +#endif + return -EIO; + } + + for (retry = 0; retry < 3; retry++) { +#ifdef DEBUG_EEPROM + dev_dbg(dev->hw_dev, "Programming EEPROM try %x\n", retry); +#endif + + daqboard2000_resetLocalBus(dev); + daqboard2000_reloadPLX(dev); + daqboard2000_pulseProgPin(dev); + if (daqboard2000_pollCPLD(dev, DAQBOARD2000_CPLD_INIT)) { + for (i = 0; i < len; i++) { + if (cpld_array[i] == 0xff + && cpld_array[i + 1] == 0x20) { +#ifdef DEBUG_EEPROM + dev_dbg(dev->hw_dev, "Preamble found at %d\n", + i); +#endif + break; + } + } + for (; i < len; i += 2) { + int data = + (cpld_array[i] << 8) + cpld_array[i + 1]; + if (!daqboard2000_writeCPLD(dev, data)) + break; + } + if (i >= len) { +#ifdef DEBUG_EEPROM + dev_dbg(dev->hw_dev, "Programmed\n"); +#endif + daqboard2000_resetLocalBus(dev); + daqboard2000_reloadPLX(dev); + result = 0; + break; + } + } + } + return result; +} + +static void daqboard2000_adcStopDmaTransfer(struct comedi_device *dev) +{ +/* printk("Implement: daqboard2000_adcStopDmaTransfer\n");*/ +} + +static void daqboard2000_adcDisarm(struct comedi_device *dev) +{ + struct daqboard2000_hw *fpga = devpriv->daq; + + /* Disable hardware triggers */ + udelay(2); + fpga->trigControl = DAQBOARD2000_TrigAnalog | DAQBOARD2000_TrigDisable; + udelay(2); + fpga->trigControl = DAQBOARD2000_TrigTTL | DAQBOARD2000_TrigDisable; + + /* Stop the scan list FIFO from loading the configuration pipe */ + udelay(2); + fpga->acqControl = DAQBOARD2000_SeqStopScanList; + + /* Stop the pacer clock */ + udelay(2); + fpga->acqControl = DAQBOARD2000_AdcPacerDisable; + + /* Stop the input dma (abort channel 1) */ + daqboard2000_adcStopDmaTransfer(dev); +} + +static void daqboard2000_activateReferenceDacs(struct comedi_device *dev) +{ + struct daqboard2000_hw *fpga = devpriv->daq; + int timeout; + + /* Set the + reference dac value in the FPGA */ + fpga->refDacs = 0x80 | DAQBOARD2000_PosRefDacSelect; + for (timeout = 0; timeout < 20; timeout++) { + if ((fpga->dacControl & DAQBOARD2000_RefBusy) == 0) + break; + udelay(2); + } +/* printk("DAQBOARD2000_PosRefDacSelect %d\n", timeout);*/ + + /* Set the - reference dac value in the FPGA */ + fpga->refDacs = 0x80 | DAQBOARD2000_NegRefDacSelect; + for (timeout = 0; timeout < 20; timeout++) { + if ((fpga->dacControl & DAQBOARD2000_RefBusy) == 0) + break; + udelay(2); + } +/* printk("DAQBOARD2000_NegRefDacSelect %d\n", timeout);*/ +} + +static void daqboard2000_initializeCtrs(struct comedi_device *dev) +{ +/* printk("Implement: daqboard2000_initializeCtrs\n");*/ +} + +static void daqboard2000_initializeTmrs(struct comedi_device *dev) +{ +/* printk("Implement: daqboard2000_initializeTmrs\n");*/ +} + +static void daqboard2000_dacDisarm(struct comedi_device *dev) +{ +/* printk("Implement: daqboard2000_dacDisarm\n");*/ +} + +static void daqboard2000_initializeAdc(struct comedi_device *dev) +{ + daqboard2000_adcDisarm(dev); + daqboard2000_activateReferenceDacs(dev); + daqboard2000_initializeCtrs(dev); + daqboard2000_initializeTmrs(dev); +} + +static void daqboard2000_initializeDac(struct comedi_device *dev) +{ + daqboard2000_dacDisarm(dev); +} + +/* +The test command, REMOVE!!: + +rmmod daqboard2000 ; rmmod comedi; make install ; modprobe daqboard2000; /usr/sbin/comedi_config /dev/comedi0 daqboard/2000 ; tail -40 /var/log/messages +*/ + +static int daqboard2000_8255_cb(int dir, int port, int data, + unsigned long ioaddr) +{ + int result = 0; + if (dir) { + writew(data, ((void *)ioaddr) + port * 2); + result = 0; + } else { + result = readw(((void *)ioaddr) + port * 2); + } +/* + printk("daqboard2000_8255_cb %x %d %d %2.2x -> %2.2x\n", + arg, dir, port, data, result); +*/ + return result; +} + +static int daqboard2000_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int result = 0; + struct comedi_subdevice *s; + struct pci_dev *card = NULL; + void *aux_data; + unsigned int aux_len; + int bus, slot; + + bus = it->options[0]; + slot = it->options[1]; + + result = alloc_private(dev, sizeof(struct daqboard2000_private)); + if (result < 0) + return -ENOMEM; + + for (card = pci_get_device(0x1616, 0x0409, NULL); + card != NULL; card = pci_get_device(0x1616, 0x0409, card)) { + if (bus || slot) { + /* requested particular bus/slot */ + if (card->bus->number != bus || + PCI_SLOT(card->devfn) != slot) { + continue; + } + } + break; /* found one */ + } + if (!card) { + if (bus || slot) + dev_err(dev->hw_dev, "no daqboard2000 found at bus/slot: %d/%d\n", + bus, slot); + else + dev_err(dev->hw_dev, "no daqboard2000 found\n"); + return -EIO; + } else { + u32 id; + int i; + devpriv->pci_dev = card; + id = ((u32) card-> + subsystem_device << 16) | card->subsystem_vendor; + for (i = 0; i < n_boardtypes; i++) { + if (boardtypes[i].id == id) { + dev_dbg(dev->hw_dev, "%s\n", + boardtypes[i].name); + dev->board_ptr = boardtypes + i; + } + } + if (!dev->board_ptr) { + printk + (" unknown subsystem id %08x (pretend it is an ids2)", + id); + dev->board_ptr = boardtypes; + } + } + + result = comedi_pci_enable(card, "daqboard2000"); + if (result < 0) { + dev_err(dev->hw_dev, "failed to enable PCI device and request regions\n"); + return -EIO; + } + devpriv->got_regions = 1; + devpriv->plx = + ioremap(pci_resource_start(card, 0), DAQBOARD2000_PLX_SIZE); + devpriv->daq = + ioremap(pci_resource_start(card, 2), DAQBOARD2000_DAQ_SIZE); + if (!devpriv->plx || !devpriv->daq) + return -ENOMEM; + + result = alloc_subdevices(dev, 3); + if (result < 0) + goto out; + + readl(devpriv->plx + 0x6c); + + /* + u8 interrupt; + Windows code does restore interrupts, but since we don't use them... + pci_read_config_byte(card, PCI_INTERRUPT_LINE, &interrupt); + printk("Interrupt before is: %x\n", interrupt); + */ + + aux_data = comedi_aux_data(it->options, 0); + aux_len = it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]; + + if (aux_data && aux_len) { + result = initialize_daqboard2000(dev, aux_data, aux_len); + } else { + dev_dbg(dev->hw_dev, "no FPGA initialization code, aborting\n"); + result = -EIO; + } + if (result < 0) + goto out; + daqboard2000_initializeAdc(dev); + daqboard2000_initializeDac(dev); + /* + Windows code does restore interrupts, but since we don't use them... + pci_read_config_byte(card, PCI_INTERRUPT_LINE, &interrupt); + printk("Interrupt after is: %x\n", interrupt); + */ + + dev->iobase = (unsigned long)devpriv->daq; + + dev->board_name = this_board->name; + + s = dev->subdevices + 0; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 24; + s->maxdata = 0xffff; + s->insn_read = daqboard2000_ai_insn_read; + s->range_table = &range_daqboard2000_ai; + + s = dev->subdevices + 1; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = 0xffff; + s->insn_read = daqboard2000_ao_insn_read; + s->insn_write = daqboard2000_ao_insn_write; + s->range_table = &range_daqboard2000_ao; + + s = dev->subdevices + 2; + result = subdev_8255_init(dev, s, daqboard2000_8255_cb, + (unsigned long)(dev->iobase + 0x40)); + +out: + return result; +} + +static int daqboard2000_detach(struct comedi_device *dev) +{ + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 2); + + if (dev->irq) + free_irq(dev->irq, dev); + + if (devpriv) { + if (devpriv->daq) + iounmap(devpriv->daq); + if (devpriv->plx) + iounmap(devpriv->plx); + if (devpriv->pci_dev) { + if (devpriv->got_regions) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + } + return 0; +} + +static int __devinit driver_daqboard2000_pci_probe(struct pci_dev *dev, + const struct pci_device_id + *ent) +{ + return comedi_pci_auto_config(dev, driver_daqboard2000.driver_name); +} + +static void __devexit driver_daqboard2000_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_daqboard2000_pci_driver = { + .id_table = daqboard2000_pci_table, + .probe = &driver_daqboard2000_pci_probe, + .remove = __devexit_p(&driver_daqboard2000_pci_remove) +}; + +static int __init driver_daqboard2000_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_daqboard2000); + if (retval < 0) + return retval; + + driver_daqboard2000_pci_driver.name = + (char *)driver_daqboard2000.driver_name; + return pci_register_driver(&driver_daqboard2000_pci_driver); +} + +static void __exit driver_daqboard2000_cleanup_module(void) +{ + pci_unregister_driver(&driver_daqboard2000_pci_driver); + comedi_driver_unregister(&driver_daqboard2000); +} + +module_init(driver_daqboard2000_init_module); +module_exit(driver_daqboard2000_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/das08.c b/drivers/staging/comedi/drivers/das08.c new file mode 100644 index 00000000..c2dd0ed3 --- /dev/null +++ b/drivers/staging/comedi/drivers/das08.c @@ -0,0 +1,1139 @@ +/* + * comedi/drivers/das08.c + * DAS08 driver + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef + * Copyright (C) 2001,2002,2003 Frank Mori Hess + * Copyright (C) 2004 Salvador E. Tropea + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + ***************************************************************** + */ + +/* + * Driver: das08 + * Description: DAS-08 compatible boards + * Author: Warren Jasper, ds, Frank Hess + * Devices: [Keithley Metrabyte] DAS08 (isa-das08), + * [ComputerBoards] DAS08 (isa-das08), DAS08-PGM (das08-pgm), + * DAS08-PGH (das08-pgh), DAS08-PGL (das08-pgl), DAS08-AOH (das08-aoh), + * DAS08-AOL (das08-aol), DAS08-AOM (das08-aom), DAS08/JR-AO (das08/jr-ao), + * DAS08/JR-16-AO (das08jr-16-ao), PCI-DAS08 (das08), + * PC104-DAS08 (pc104-das08), DAS08/JR/16 (das08jr/16) + * Status: works + * + * This is a rewrite of the das08 and das08jr drivers. + * + * Options (for ISA cards): + * [0] - base io address + * + * Options (for pci-das08): + * [0] - bus (optional) + * [1] = slot (optional) + * + * The das08 driver doesn't support asynchronous commands, since + * the cheap das08 hardware doesn't really support them. The + * comedi_rt_timer driver can be used to emulate commands for this + * driver. + */ + +#include "../comedidev.h" + +#include + +#include "comedi_pci.h" +#include "8255.h" +#include "das08.h" + +#define DRV_NAME "das08" + +#define PCI_VENDOR_ID_COMPUTERBOARDS 0x1307 +#define PCI_DEVICE_ID_PCIDAS08 0x29 +#define PCIDAS08_SIZE 0x54 + +/* pci configuration registers */ +#define INTCSR 0x4c +#define INTR1_ENABLE 0x1 +#define INTR1_HIGH_POLARITY 0x2 +#define PCI_INTR_ENABLE 0x40 +#define INTR1_EDGE_TRIG 0x100 /* requires high polarity */ +#define CNTRL 0x50 +#define CNTRL_DIR 0x2 +#define CNTRL_INTR 0x4 + +/* + cio-das08.pdf + + "isa-das08" + + 0 a/d bits 0-3 start 8 bit + 1 a/d bits 4-11 start 12 bit + 2 eoc, ip1-3, irq, mux op1-4, inte, mux + 3 unused unused + 4567 8254 + 89ab 8255 + + requires hard-wiring for async ai + +*/ + +#define DAS08_LSB 0 +#define DAS08_MSB 1 +#define DAS08_TRIG_12BIT 1 +#define DAS08_STATUS 2 +#define DAS08_EOC (1<<7) +#define DAS08_IRQ (1<<3) +#define DAS08_IP(x) (((x)>>4)&0x7) +#define DAS08_CONTROL 2 +#define DAS08_MUX_MASK 0x7 +#define DAS08_MUX(x) ((x) & DAS08_MUX_MASK) +#define DAS08_INTE (1<<3) +#define DAS08_DO_MASK 0xf0 +#define DAS08_OP(x) (((x) << 4) & DAS08_DO_MASK) + +/* + cio-das08jr.pdf + + "das08/jr-ao" + + 0 a/d bits 0-3 unused + 1 a/d bits 4-11 start 12 bit + 2 eoc, mux mux + 3 di do + 4 unused ao0_lsb + 5 unused ao0_msb + 6 unused ao1_lsb + 7 unused ao1_msb + +*/ + +#define DAS08JR_DIO 3 +#define DAS08JR_AO_LSB(x) ((x) ? 6 : 4) +#define DAS08JR_AO_MSB(x) ((x) ? 7 : 5) + +/* + cio-das08_aox.pdf + + "das08-aoh" + "das08-aol" + "das08-aom" + + 0 a/d bits 0-3 start 8 bit + 1 a/d bits 4-11 start 12 bit + 2 eoc, ip1-3, irq, mux op1-4, inte, mux + 3 mux, gain status gain control + 4567 8254 + 8 unused ao0_lsb + 9 unused ao0_msb + a unused ao1_lsb + b unused ao1_msb + 89ab + cdef 8255 +*/ + +#define DAS08AO_GAIN_CONTROL 3 +#define DAS08AO_GAIN_STATUS 3 + +#define DAS08AO_AO_LSB(x) ((x) ? 0xa : 8) +#define DAS08AO_AO_MSB(x) ((x) ? 0xb : 9) +#define DAS08AO_AO_UPDATE 8 + +/* gainlist same as _pgx_ below */ + +static int das08_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das08_di_rbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das08_do_wbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das08jr_di_rbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das08jr_do_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das08jr_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das08ao_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static void i8254_set_mode_low(unsigned int base, int channel, + unsigned int mode); + +static const struct comedi_lrange range_das08_pgl = { 9, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange range_das08_pgh = { 12, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.1), + BIP_RANGE(0.05), + BIP_RANGE(0.01), + BIP_RANGE(0.005), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01), + } +}; + +static const struct comedi_lrange range_das08_pgm = { 9, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.01), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01) + } +}; /* + cio-das08jr.pdf + + "das08/jr-ao" + + 0 a/d bits 0-3 unused + 1 a/d bits 4-11 start 12 bit + 2 eoc, mux mux + 3 di do + 4 unused ao0_lsb + 5 unused ao0_msb + 6 unused ao1_lsb + 7 unused ao1_msb + + */ + +static const struct comedi_lrange *const das08_ai_lranges[] = { + &range_unknown, + &range_bipolar5, + &range_das08_pgh, + &range_das08_pgl, + &range_das08_pgm, +}; + +static const int das08_pgh_gainlist[] = { + 8, 0, 10, 2, 12, 4, 14, 6, 1, 3, 5, 7 +}; +static const int das08_pgl_gainlist[] = { 8, 0, 2, 4, 6, 1, 3, 5, 7 }; +static const int das08_pgm_gainlist[] = { 8, 0, 10, 12, 14, 9, 11, 13, 15 }; + +static const int *const das08_gainlists[] = { + NULL, + NULL, + das08_pgh_gainlist, + das08_pgl_gainlist, + das08_pgm_gainlist, +}; + +static const struct das08_board_struct das08_boards[] = { + { + .name = "isa-das08", /* cio-das08.pdf */ + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_pg_none, + .ai_encoding = das08_encode12, + .ao = NULL, + .ao_nbits = 12, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 4, + .i8255_offset = 8, + .i8254_offset = 4, + .iosize = 16, /* unchecked */ + }, + { + .name = "das08-pgm", /* cio-das08pgx.pdf */ + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_pgm, + .ai_encoding = das08_encode12, + .ao = NULL, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 4, + .i8255_offset = 0, + .i8254_offset = 0x04, + .iosize = 16, /* unchecked */ + }, + { + .name = "das08-pgh", /* cio-das08pgx.pdf */ + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_pgh, + .ai_encoding = das08_encode12, + .ao = NULL, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 4, + .i8255_offset = 0, + .i8254_offset = 0x04, + .iosize = 16, /* unchecked */ + }, + { + .name = "das08-pgl", /* cio-das08pgx.pdf */ + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_pgl, + .ai_encoding = das08_encode12, + .ao = NULL, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 4, + .i8255_offset = 0, + .i8254_offset = 0x04, + .iosize = 16, /* unchecked */ + }, + { + .name = "das08-aoh", /* cio-das08_aox.pdf */ + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_pgh, + .ai_encoding = das08_encode12, + .ao = das08ao_ao_winsn, /* 8 */ + .ao_nbits = 12, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 4, + .i8255_offset = 0x0c, + .i8254_offset = 0x04, + .iosize = 16, /* unchecked */ + }, + { + .name = "das08-aol", /* cio-das08_aox.pdf */ + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_pgl, + .ai_encoding = das08_encode12, + .ao = das08ao_ao_winsn, /* 8 */ + .ao_nbits = 12, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 4, + .i8255_offset = 0x0c, + .i8254_offset = 0x04, + .iosize = 16, /* unchecked */ + }, + { + .name = "das08-aom", /* cio-das08_aox.pdf */ + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_pgm, + .ai_encoding = das08_encode12, + .ao = das08ao_ao_winsn, /* 8 */ + .ao_nbits = 12, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 4, + .i8255_offset = 0x0c, + .i8254_offset = 0x04, + .iosize = 16, /* unchecked */ + }, + { + .name = "das08/jr-ao", /* cio-das08-jr-ao.pdf */ + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_pg_none, + .ai_encoding = das08_encode12, + .ao = das08jr_ao_winsn, + .ao_nbits = 12, + .di = das08jr_di_rbits, + .do_ = das08jr_do_wbits, + .do_nchan = 8, + .i8255_offset = 0, + .i8254_offset = 0, + .iosize = 16, /* unchecked */ + }, + { + .name = "das08jr-16-ao", /* cio-das08jr-16-ao.pdf */ + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 16, + .ai_pg = das08_pg_none, + .ai_encoding = das08_encode12, + .ao = das08jr_ao_winsn, + .ao_nbits = 16, + .di = das08jr_di_rbits, + .do_ = das08jr_do_wbits, + .do_nchan = 8, + .i8255_offset = 0, + .i8254_offset = 0x04, + .iosize = 16, /* unchecked */ + }, +#ifdef CONFIG_COMEDI_PCI + { + .name = "das08", /* pci-das08 */ + .id = PCI_DEVICE_ID_PCIDAS08, + .bustype = pci, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_bipolar5, + .ai_encoding = das08_encode12, + .ao = NULL, + .ao_nbits = 0, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 4, + .i8255_offset = 0, + .i8254_offset = 4, + .iosize = 8, + }, +#endif + { + .name = "pc104-das08", + .bustype = pc104, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_pg_none, + .ai_encoding = das08_encode12, + .ao = NULL, + .ao_nbits = 0, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 4, + .i8255_offset = 0, + .i8254_offset = 4, + .iosize = 16, /* unchecked */ + }, +#if 0 + { + .name = "das08/f", + }, + { + .name = "das08jr", + }, +#endif + { + .name = "das08jr/16", + .bustype = isa, + .ai = das08_ai_rinsn, + .ai_nbits = 16, + .ai_pg = das08_pg_none, + .ai_encoding = das08_encode16, + .ao = NULL, + .ao_nbits = 0, + .di = das08jr_di_rbits, + .do_ = das08jr_do_wbits, + .do_nchan = 8, + .i8255_offset = 0, + .i8254_offset = 0, + .iosize = 16, /* unchecked */ + }, +#if 0 + { + .name = "das48-pga", /* cio-das48-pga.pdf */ + }, + { + .name = "das08-pga-g2", /* a KM board */ + }, +#endif +}; + +#ifdef CONFIG_COMEDI_PCMCIA +struct das08_board_struct das08_cs_boards[NUM_DAS08_CS_BOARDS] = { + { + .name = "pcm-das08", + .id = 0x0, /* XXX */ + .bustype = pcmcia, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_bipolar5, + .ai_encoding = das08_pcm_encode12, + .ao = NULL, + .ao_nbits = 0, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 3, + .i8255_offset = 0, + .i8254_offset = 0, + .iosize = 16, + }, + /* duplicate so driver name can be used also */ + { + .name = "das08_cs", + .id = 0x0, /* XXX */ + .bustype = pcmcia, + .ai = das08_ai_rinsn, + .ai_nbits = 12, + .ai_pg = das08_bipolar5, + .ai_encoding = das08_pcm_encode12, + .ao = NULL, + .ao_nbits = 0, + .di = das08_di_rbits, + .do_ = das08_do_wbits, + .do_nchan = 3, + .i8255_offset = 0, + .i8254_offset = 0, + .iosize = 16, + }, +}; +#endif + +#ifdef CONFIG_COMEDI_PCI +static DEFINE_PCI_DEVICE_TABLE(das08_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_COMPUTERBOARDS, PCI_DEVICE_ID_PCIDAS08) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, das08_pci_table); +#endif + +#define devpriv ((struct das08_private_struct *)dev->private) +#define thisboard ((const struct das08_board_struct *)dev->board_ptr) + +#define TIMEOUT 100000 + +static int das08_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, n; + int chan; + int range; + int lsb, msb; + + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* clear crap */ + inb(dev->iobase + DAS08_LSB); + inb(dev->iobase + DAS08_MSB); + + /* set multiplexer */ + /* lock to prevent race with digital output */ + spin_lock(&dev->spinlock); + devpriv->do_mux_bits &= ~DAS08_MUX_MASK; + devpriv->do_mux_bits |= DAS08_MUX(chan); + outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL); + spin_unlock(&dev->spinlock); + + if (s->range_table->length > 1) { + /* set gain/range */ + range = CR_RANGE(insn->chanspec); + outb(devpriv->pg_gainlist[range], + dev->iobase + DAS08AO_GAIN_CONTROL); + } + + for (n = 0; n < insn->n; n++) { + /* clear over-range bits for 16-bit boards */ + if (thisboard->ai_nbits == 16) + if (inb(dev->iobase + DAS08_MSB) & 0x80) + printk(KERN_INFO "das08: over-range\n"); + + /* trigger conversion */ + outb_p(0, dev->iobase + DAS08_TRIG_12BIT); + + for (i = 0; i < TIMEOUT; i++) { + if (!(inb(dev->iobase + DAS08_STATUS) & DAS08_EOC)) + break; + } + if (i == TIMEOUT) { + printk(KERN_ERR "das08: timeout\n"); + return -ETIME; + } + msb = inb(dev->iobase + DAS08_MSB); + lsb = inb(dev->iobase + DAS08_LSB); + if (thisboard->ai_encoding == das08_encode12) { + data[n] = (lsb >> 4) | (msb << 4); + } else if (thisboard->ai_encoding == das08_pcm_encode12) { + data[n] = (msb << 8) + lsb; + } else if (thisboard->ai_encoding == das08_encode16) { + /* FPOS 16-bit boards are sign-magnitude */ + if (msb & 0x80) + data[n] = (1 << 15) | lsb | ((msb & 0x7f) << 8); + else + data[n] = (1 << 15) - (lsb | (msb & 0x7f) << 8); + } else { + comedi_error(dev, "bug! unknown ai encoding"); + return -1; + } + } + + return n; +} + +static int das08_di_rbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = 0; + data[1] = DAS08_IP(inb(dev->iobase + DAS08_STATUS)); + + return 2; +} + +static int das08_do_wbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int wbits; + + /* get current settings of digital output lines */ + wbits = (devpriv->do_mux_bits >> 4) & 0xf; + /* null bits we are going to set */ + wbits &= ~data[0]; + /* set new bit values */ + wbits |= data[0] & data[1]; + /* remember digital output bits */ + /* prevent race with setting of analog input mux */ + spin_lock(&dev->spinlock); + devpriv->do_mux_bits &= ~DAS08_DO_MASK; + devpriv->do_mux_bits |= DAS08_OP(wbits); + outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL); + spin_unlock(&dev->spinlock); + + data[1] = wbits; + + return 2; +} + +static int das08jr_di_rbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = 0; + data[1] = inb(dev->iobase + DAS08JR_DIO); + + return 2; +} + +static int das08jr_do_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + /* null bits we are going to set */ + devpriv->do_bits &= ~data[0]; + /* set new bit values */ + devpriv->do_bits |= data[0] & data[1]; + outb(devpriv->do_bits, dev->iobase + DAS08JR_DIO); + + data[1] = devpriv->do_bits; + + return 2; +} + +static int das08jr_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int lsb, msb; + int chan; + + lsb = data[0] & 0xff; + msb = (data[0] >> 8) & 0xf; + + chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) { +#if 0 + outb(lsb, dev->iobase + devpriv->ao_offset_lsb[chan]); + outb(msb, dev->iobase + devpriv->ao_offset_msb[chan]); +#else + outb(lsb, dev->iobase + DAS08JR_AO_LSB(chan)); + outb(msb, dev->iobase + DAS08JR_AO_MSB(chan)); +#endif + + /* load DACs */ + inb(dev->iobase + DAS08JR_DIO); + } + + return n; +} + +/* + * + * The -aox boards have the DACs at a different offset and use + * a different method to force an update. + * + */ +static int das08ao_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int lsb, msb; + int chan; + + lsb = data[0] & 0xff; + msb = (data[0] >> 8) & 0xf; + + chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) { +#if 0 + outb(lsb, dev->iobase + devpriv->ao_offset_lsb[chan]); + outb(msb, dev->iobase + devpriv->ao_offset_msb[chan]); +#else + outb(lsb, dev->iobase + DAS08AO_AO_LSB(chan)); + outb(msb, dev->iobase + DAS08AO_AO_MSB(chan)); +#endif + + /* load DACs */ + inb(dev->iobase + DAS08AO_AO_UPDATE); + } + + return n; +} + +static unsigned int i8254_read_channel_low(unsigned int base, int chan) +{ + unsigned int msb, lsb; + + /* The following instructions must be in order. + We must avoid other process reading the counter's value in the + middle. + The spin_lock isn't needed since ioctl calls grab the big kernel + lock automatically */ + /*spin_lock(sp); */ + outb(chan << 6, base + I8254_CTRL); + base += chan; + lsb = inb(base); + msb = inb(base); + /*spin_unlock(sp); */ + + return lsb | (msb << 8); +} + +static void i8254_write_channel_low(unsigned int base, int chan, + unsigned int value) +{ + unsigned int msb, lsb; + + lsb = value & 0xFF; + msb = value >> 8; + + /* write lsb, then msb */ + base += chan; + /* See comments in i8254_read_channel_low */ + /*spin_lock(sp); */ + outb(lsb, base); + outb(msb, base); + /*spin_unlock(sp); */ +} + +static unsigned int i8254_read_channel(struct i8254_struct *st, int channel) +{ + int chan = st->logic2phys[channel]; + + return i8254_read_channel_low(st->iobase, chan); +} + +static void i8254_write_channel(struct i8254_struct *st, int channel, + unsigned int value) +{ + int chan = st->logic2phys[channel]; + + i8254_write_channel_low(st->iobase, chan, value); +} + +static void i8254_initialize(struct i8254_struct *st) +{ + int i; + for (i = 0; i < 3; ++i) + i8254_set_mode_low(st->iobase, i, st->mode[i]); +} + +static void i8254_set_mode_low(unsigned int base, int channel, + unsigned int mode) +{ + outb((channel << 6) | 0x30 | (mode & 0x0F), base + I8254_CTRL); +} + +static void i8254_set_mode(struct i8254_struct *st, int channel, + unsigned int mode) +{ + int chan = st->logic2phys[channel]; + + st->mode[chan] = mode; + return i8254_set_mode_low(st->iobase, chan, mode); +} + +static unsigned int i8254_read_status_low(unsigned int base, int channel) +{ + outb(0xE0 | (2 << channel), base + I8254_CTRL); + return inb(base + channel); +} + +static unsigned int i8254_read_status(struct i8254_struct *st, int channel) +{ + int chan = st->logic2phys[channel]; + + return i8254_read_status_low(st->iobase, chan); +} + +static int das08_counter_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = insn->chanspec; + + /* printk("Reading counter channel %d ",chan); */ + data[0] = i8254_read_channel(&devpriv->i8254, chan); + /* printk("=> 0x%08X\n",data[0]); */ + + return 1; +} + +static int das08_counter_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = insn->chanspec; + + /* printk("Writing counter channel %d with 0x%04X\n",chan,data[0]); */ + i8254_write_channel(&devpriv->i8254, chan, data[0]); + + return 1; +} + +static int das08_counter_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = insn->chanspec; + + if (insn->n != 2) + return -EINVAL; + + switch (data[0]) { + case INSN_CONFIG_SET_COUNTER_MODE: + i8254_set_mode(&devpriv->i8254, chan, data[1]); + break; + case INSN_CONFIG_8254_READ_STATUS: + data[1] = i8254_read_status(&devpriv->i8254, chan); + break; + default: + return -EINVAL; + break; + } + return 2; +} + +static int das08_attach(struct comedi_device *dev, struct comedi_devconfig *it); + +static struct comedi_driver driver_das08 = { + .driver_name = DRV_NAME, + .module = THIS_MODULE, + .attach = das08_attach, + .detach = das08_common_detach, + .board_name = &das08_boards[0].name, + .num_names = sizeof(das08_boards) / sizeof(struct das08_board_struct), + .offset = sizeof(struct das08_board_struct), +}; + +int das08_common_attach(struct comedi_device *dev, unsigned long iobase) +{ + struct comedi_subdevice *s; + int ret; + + /* allocate ioports for non-pcmcia, non-pci boards */ + if ((thisboard->bustype != pcmcia) && (thisboard->bustype != pci)) { + printk(KERN_INFO " iobase 0x%lx\n", iobase); + if (!request_region(iobase, thisboard->iosize, DRV_NAME)) { + printk(KERN_ERR " I/O port conflict\n"); + return -EIO; + } + } + dev->iobase = iobase; + + dev->board_name = thisboard->name; + + ret = alloc_subdevices(dev, 6); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + /* ai */ + if (thisboard->ai) { + s->type = COMEDI_SUBD_AI; + /* XXX some boards actually have differential + * inputs instead of single ended. + * The driver does nothing with arefs though, + * so it's no big deal. + */ + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 8; + s->maxdata = (1 << thisboard->ai_nbits) - 1; + s->range_table = das08_ai_lranges[thisboard->ai_pg]; + s->insn_read = thisboard->ai; + devpriv->pg_gainlist = das08_gainlists[thisboard->ai_pg]; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 1; + /* ao */ + if (thisboard->ao) { + s->type = COMEDI_SUBD_AO; +/* XXX lacks read-back insn */ + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << thisboard->ao_nbits) - 1; + s->range_table = &range_bipolar5; + s->insn_write = thisboard->ao; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 2; + /* di */ + if (thisboard->di) { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = (thisboard->di == das08_di_rbits) ? 3 : 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = thisboard->di; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 3; + /* do */ + if (thisboard->do_) { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = thisboard->do_nchan; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = thisboard->do_; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 4; + /* 8255 */ + if (thisboard->i8255_offset != 0) { + subdev_8255_init(dev, s, NULL, (unsigned long)(dev->iobase + + thisboard-> + i8255_offset)); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 5; + /* 8254 */ + if (thisboard->i8254_offset != 0) { + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 3; + s->maxdata = 0xFFFF; + s->insn_read = das08_counter_read; + s->insn_write = das08_counter_write; + s->insn_config = das08_counter_config; + /* Set-up the 8254 structure */ + devpriv->i8254.channels = 3; + devpriv->i8254.logic2phys[0] = 0; + devpriv->i8254.logic2phys[1] = 1; + devpriv->i8254.logic2phys[2] = 2; + devpriv->i8254.iobase = iobase + thisboard->i8254_offset; + devpriv->i8254.mode[0] = + devpriv->i8254.mode[1] = + devpriv->i8254.mode[2] = I8254_MODE0 | I8254_BINARY; + i8254_initialize(&devpriv->i8254); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} +EXPORT_SYMBOL_GPL(das08_common_attach); + +static int das08_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + unsigned long iobase; +#ifdef CONFIG_COMEDI_PCI + unsigned long pci_iobase = 0; + struct pci_dev *pdev = NULL; +#endif + + ret = alloc_private(dev, sizeof(struct das08_private_struct)); + if (ret < 0) + return ret; + + printk(KERN_INFO "comedi%d: das08: ", dev->minor); + /* deal with a pci board */ + if (thisboard->bustype == pci) { +#ifdef CONFIG_COMEDI_PCI + if (it->options[0] || it->options[1]) { + printk("bus %i slot %i ", + it->options[0], it->options[1]); + } + printk("\n"); + /* find card */ + for_each_pci_dev(pdev) { + if (pdev->vendor == PCI_VENDOR_ID_COMPUTERBOARDS + && pdev->device == PCI_DEVICE_ID_PCIDAS08) { + if (it->options[0] || it->options[1]) { + if (pdev->bus->number == it->options[0] + && PCI_SLOT(pdev->devfn) == + it->options[1]) { + break; + } + } else { + break; + } + } + } + if (!pdev) { + printk(KERN_ERR "No pci das08 cards found\n"); + return -EIO; + } + devpriv->pdev = pdev; + /* enable PCI device and reserve I/O spaces */ + if (comedi_pci_enable(pdev, DRV_NAME)) { + printk(KERN_ERR " Error enabling PCI device and " + "requesting regions\n"); + return -EIO; + } + /* read base addresses */ + pci_iobase = pci_resource_start(pdev, 1); + iobase = pci_resource_start(pdev, 2); + printk(KERN_INFO "pcibase 0x%lx iobase 0x%lx\n", + pci_iobase, iobase); + devpriv->pci_iobase = pci_iobase; +#if 0 +/* We could enable to pci-das08's interrupt here to make it possible + * to do timed input in this driver, but there is little point since + * conversions would have to be started by the interrupt handler + * so you might as well use comedi_rt_timer to emulate commands + */ + /* set source of interrupt trigger to counter2 output */ + outb(CNTRL_INTR | CNTRL_DIR, pci_iobase + CNTRL); + /* Enable local interrupt 1 and pci interrupt */ + outw(INTR1_ENABLE | PCI_INTR_ENABLE, pci_iobase + INTCSR); +#endif +#else /* CONFIG_COMEDI_PCI */ + printk(KERN_ERR "this driver has not been built with PCI support.\n"); + return -EINVAL; +#endif /* CONFIG_COMEDI_PCI */ + } else { + iobase = it->options[0]; + } + printk(KERN_INFO "\n"); + + return das08_common_attach(dev, iobase); +} + + +int das08_common_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: das08: remove\n", dev->minor); + + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 4); + + /* deallocate ioports for non-pcmcia, non-pci boards */ + if ((thisboard->bustype != pcmcia) && (thisboard->bustype != pci)) { + if (dev->iobase) + release_region(dev->iobase, thisboard->iosize); + } +#ifdef CONFIG_COMEDI_PCI + if (devpriv) { + if (devpriv->pdev) { + if (devpriv->pci_iobase) + comedi_pci_disable(devpriv->pdev); + + pci_dev_put(devpriv->pdev); + } + } +#endif + + return 0; +} +EXPORT_SYMBOL_GPL(das08_common_detach); + +#ifdef CONFIG_COMEDI_PCI +static int __devinit driver_das08_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_das08.driver_name); +} + +static void __devexit driver_das08_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_das08_pci_driver = { + .id_table = das08_pci_table, + .probe = &driver_das08_pci_probe, + .remove = __devexit_p(&driver_das08_pci_remove) +}; + +static int __init driver_das08_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_das08); + if (retval < 0) + return retval; + + driver_das08_pci_driver.name = (char *)driver_das08.driver_name; + return pci_register_driver(&driver_das08_pci_driver); +} + +static void __exit driver_das08_cleanup_module(void) +{ + pci_unregister_driver(&driver_das08_pci_driver); + comedi_driver_unregister(&driver_das08); +} + +module_init(driver_das08_init_module); +module_exit(driver_das08_cleanup_module); +#else +static int __init driver_das08_init_module(void) +{ + return comedi_driver_register(&driver_das08); +} + +static void __exit driver_das08_cleanup_module(void) +{ + comedi_driver_unregister(&driver_das08); +} + +module_init(driver_das08_init_module); +module_exit(driver_das08_cleanup_module); +#endif + +#ifdef CONFIG_COMEDI_PCMCIA +EXPORT_SYMBOL_GPL(das08_cs_boards); +#endif + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/das08.h b/drivers/staging/comedi/drivers/das08.h new file mode 100644 index 00000000..2a30d764 --- /dev/null +++ b/drivers/staging/comedi/drivers/das08.h @@ -0,0 +1,79 @@ +/* + das08.h + + Header for das08.c and das08_cs.c + + Copyright (C) 2003 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _DAS08_H +#define _DAS08_H + +enum das08_bustype { isa, pci, pcmcia, pc104 }; +/* different ways ai data is encoded in first two registers */ +enum das08_ai_encoding { das08_encode12, das08_encode16, das08_pcm_encode12 }; +enum das08_lrange { das08_pg_none, das08_bipolar5, das08_pgh, das08_pgl, + das08_pgm +}; + +struct das08_board_struct { + const char *name; + unsigned int id; /* id for pci/pcmcia boards */ + enum das08_bustype bustype; + void *ai; + unsigned int ai_nbits; + enum das08_lrange ai_pg; + enum das08_ai_encoding ai_encoding; + void *ao; + unsigned int ao_nbits; + void *di; + void *do_; + unsigned int do_nchan; + unsigned int i8255_offset; + unsigned int i8254_offset; + unsigned int iosize; /* number of ioports used */ +}; + +struct i8254_struct { + int channels; /* available channels. Some could be used internally. */ + int logic2phys[3]; /* to know which physical channel is. */ + int mode[3]; /* the index is the real counter. */ + unsigned int iobase; +}; + +#define I8254_CNT0 0 +#define I8254_CNT1 1 +#define I8254_CNT2 2 +#define I8254_CTRL 3 + +struct das08_private_struct { + unsigned int do_mux_bits; /* bits for do/mux register on boards without separate do register */ + unsigned int do_bits; /* bits for do register on boards with register dedicated to digital out only */ + const unsigned int *pg_gainlist; + struct pci_dev *pdev; /* struct for pci-das08 */ + unsigned int pci_iobase; /* additional base address for pci-das08 */ + struct i8254_struct i8254; +}; + +#define NUM_DAS08_CS_BOARDS 2 +extern struct das08_board_struct das08_cs_boards[NUM_DAS08_CS_BOARDS]; + +int das08_common_attach(struct comedi_device *dev, unsigned long iobase); +int das08_common_detach(struct comedi_device *dev); + +#endif /* _DAS08_H */ diff --git a/drivers/staging/comedi/drivers/das08_cs.c b/drivers/staging/comedi/drivers/das08_cs.c new file mode 100644 index 00000000..4ad398aa --- /dev/null +++ b/drivers/staging/comedi/drivers/das08_cs.c @@ -0,0 +1,271 @@ +/* + comedi/drivers/das08_cs.c + DAS08 driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + Copyright (C) 2001,2002,2003 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +***************************************************************** + +*/ +/* +Driver: das08_cs +Description: DAS-08 PCMCIA boards +Author: Warren Jasper, ds, Frank Hess +Devices: [ComputerBoards] PCM-DAS08 (pcm-das08) +Status: works + +This is the PCMCIA-specific support split off from the +das08 driver. + +Options (for pcm-das08): + NONE + +Command support does not exist, but could be added for this board. +*/ + +#include "../comedidev.h" + +#include +#include +#include + +#include "das08.h" + +/* pcmcia includes */ +#include +#include + +static struct pcmcia_device *cur_dev; + +#define thisboard ((const struct das08_board_struct *)dev->board_ptr) + +static int das08_cs_attach(struct comedi_device *dev, + struct comedi_devconfig *it); + +static struct comedi_driver driver_das08_cs = { + .driver_name = "das08_cs", + .module = THIS_MODULE, + .attach = das08_cs_attach, + .detach = das08_common_detach, + .board_name = &das08_cs_boards[0].name, + .num_names = ARRAY_SIZE(das08_cs_boards), + .offset = sizeof(struct das08_board_struct), +}; + +static int das08_cs_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int ret; + unsigned long iobase; + struct pcmcia_device *link = cur_dev; /* XXX hack */ + + ret = alloc_private(dev, sizeof(struct das08_private_struct)); + if (ret < 0) + return ret; + + dev_info(dev->hw_dev, "comedi%d: das08_cs:\n", dev->minor); + /* deal with a pci board */ + + if (thisboard->bustype == pcmcia) { + if (link == NULL) { + dev_err(dev->hw_dev, "no pcmcia cards found\n"); + return -EIO; + } + iobase = link->resource[0]->start; + } else { + dev_err(dev->hw_dev, "bug! board does not have PCMCIA bustype\n"); + return -EINVAL; + } + + return das08_common_attach(dev, iobase); +} + +/*====================================================================== + + The following pcmcia code for the pcm-das08 is adapted from the + dummy_cs.c driver of the Linux PCMCIA Card Services package. + + The initial developer of the original code is David A. Hinds + . Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + +======================================================================*/ + +static void das08_pcmcia_config(struct pcmcia_device *link); +static void das08_pcmcia_release(struct pcmcia_device *link); +static int das08_pcmcia_suspend(struct pcmcia_device *p_dev); +static int das08_pcmcia_resume(struct pcmcia_device *p_dev); + +static int das08_pcmcia_attach(struct pcmcia_device *); +static void das08_pcmcia_detach(struct pcmcia_device *); + +struct local_info_t { + struct pcmcia_device *link; + int stop; + struct bus_operations *bus; +}; + +static int das08_pcmcia_attach(struct pcmcia_device *link) +{ + struct local_info_t *local; + + dev_dbg(&link->dev, "das08_pcmcia_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kzalloc(sizeof(struct local_info_t), GFP_KERNEL); + if (!local) + return -ENOMEM; + local->link = link; + link->priv = local; + + cur_dev = link; + + das08_pcmcia_config(link); + + return 0; +} /* das08_pcmcia_attach */ + +static void das08_pcmcia_detach(struct pcmcia_device *link) +{ + + dev_dbg(&link->dev, "das08_pcmcia_detach\n"); + + ((struct local_info_t *)link->priv)->stop = 1; + das08_pcmcia_release(link); + + /* This points to the parent struct local_info_t struct */ + kfree(link->priv); + +} /* das08_pcmcia_detach */ + + +static int das08_pcmcia_config_loop(struct pcmcia_device *p_dev, + void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(p_dev); +} + +static void das08_pcmcia_config(struct pcmcia_device *link) +{ + int ret; + + dev_dbg(&link->dev, "das08_pcmcia_config\n"); + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + ret = pcmcia_loop_config(link, das08_pcmcia_config_loop, NULL); + if (ret) { + dev_warn(&link->dev, "no configuration found\n"); + goto failed; + } + + if (!link->irq) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + return; + +failed: + das08_pcmcia_release(link); + +} /* das08_pcmcia_config */ + +static void das08_pcmcia_release(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "das08_pcmcia_release\n"); + pcmcia_disable_device(link); +} /* das08_pcmcia_release */ + +static int das08_pcmcia_suspend(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + /* Mark the device as stopped, to block IO until later */ + local->stop = 1; + + return 0; +} /* das08_pcmcia_suspend */ + +static int das08_pcmcia_resume(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + local->stop = 0; + return 0; +} /* das08_pcmcia_resume */ + +/*====================================================================*/ + +static const struct pcmcia_device_id das08_cs_id_table[] = { + PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x4001), + PCMCIA_DEVICE_NULL +}; + +MODULE_DEVICE_TABLE(pcmcia, das08_cs_id_table); +MODULE_AUTHOR("David A. Schleef , " + "Frank Mori Hess "); +MODULE_DESCRIPTION("Comedi driver for ComputerBoards DAS-08 PCMCIA boards"); +MODULE_LICENSE("GPL"); + +struct pcmcia_driver das08_cs_driver = { + .probe = das08_pcmcia_attach, + .remove = das08_pcmcia_detach, + .suspend = das08_pcmcia_suspend, + .resume = das08_pcmcia_resume, + .id_table = das08_cs_id_table, + .owner = THIS_MODULE, + .name = "pcm-das08", +}; + +static int __init init_das08_pcmcia_cs(void) +{ + pcmcia_register_driver(&das08_cs_driver); + return 0; +} + +static void __exit exit_das08_pcmcia_cs(void) +{ + pr_debug("das08_pcmcia_cs: unloading\n"); + pcmcia_unregister_driver(&das08_cs_driver); +} + +static int __init das08_cs_init_module(void) +{ + int ret; + + ret = init_das08_pcmcia_cs(); + if (ret < 0) + return ret; + + return comedi_driver_register(&driver_das08_cs); +} + +static void __exit das08_cs_exit_module(void) +{ + exit_das08_pcmcia_cs(); + comedi_driver_unregister(&driver_das08_cs); +} + +module_init(das08_cs_init_module); +module_exit(das08_cs_exit_module); diff --git a/drivers/staging/comedi/drivers/das16.c b/drivers/staging/comedi/drivers/das16.c new file mode 100644 index 00000000..e7905bac --- /dev/null +++ b/drivers/staging/comedi/drivers/das16.c @@ -0,0 +1,1791 @@ +/* + comedi/drivers/das16.c + DAS16 driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + Copyright (C) 2000 Chris R. Baugher + Copyright (C) 2001,2002 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ +*/ +/* +Driver: das16 +Description: DAS16 compatible boards +Author: Sam Moore, Warren Jasper, ds, Chris Baugher, Frank Hess, Roman Fietze +Devices: [Keithley Metrabyte] DAS-16 (das-16), DAS-16G (das-16g), + DAS-16F (das-16f), DAS-1201 (das-1201), DAS-1202 (das-1202), + DAS-1401 (das-1401), DAS-1402 (das-1402), DAS-1601 (das-1601), + DAS-1602 (das-1602), + [ComputerBoards] PC104-DAS16/JR (pc104-das16jr), + PC104-DAS16JR/16 (pc104-das16jr/16), + CIO-DAS16JR/16 (cio-das16jr/16), + CIO-DAS16/JR (cio-das16/jr), CIO-DAS1401/12 (cio-das1401/12), + CIO-DAS1402/12 (cio-das1402/12), CIO-DAS1402/16 (cio-das1402/16), + CIO-DAS1601/12 (cio-das1601/12), CIO-DAS1602/12 (cio-das1602/12), + CIO-DAS1602/16 (cio-das1602/16), CIO-DAS16/330 (cio-das16/330) +Status: works +Updated: 2003-10-12 + +A rewrite of the das16 and das1600 drivers. +Options: + [0] - base io address + [1] - irq (does nothing, irq is not used anymore) + [2] - dma (optional, required for comedi_command support) + [3] - master clock speed in MHz (optional, 1 or 10, ignored if + board can probe clock, defaults to 1) + [4] - analog input range lowest voltage in microvolts (optional, + only useful if your board does not have software + programmable gain) + [5] - analog input range highest voltage in microvolts (optional, + only useful if board does not have software programmable + gain) + [6] - analog output range lowest voltage in microvolts (optional) + [7] - analog output range highest voltage in microvolts (optional) + [8] - use timer mode for DMA. Timer mode is needed e.g. for + buggy DMA controllers in NS CS5530A (Geode Companion), and for + 'jr' cards that lack a hardware fifo. This option is no + longer needed, since timer mode is _always_ used. + +Passing a zero for an option is the same as leaving it unspecified. + +*/ +/* + +Testing and debugging help provided by Daniel Koch. + +Keithley Manuals: + 2309.PDF (das16) + 4919.PDF (das1400, 1600) + 4922.PDF (das-1400) + 4923.PDF (das1200, 1400, 1600) + +Computer boards manuals also available from their website +www.measurementcomputing.com + +*/ + +#include +#include +#include +#include +#include "../comedidev.h" + +#include "8253.h" +#include "8255.h" +#include "comedi_fc.h" + +#undef DEBUG +/* #define DEBUG */ + +#ifdef DEBUG +#define DEBUG_PRINT(format, args...) \ + printk(KERN_DEBUG "das16: " format, ## args) +#else +#define DEBUG_PRINT(format, args...) +#endif + +#define DAS16_SIZE 20 /* number of ioports */ +#define DAS16_DMA_SIZE 0xff00 /* size in bytes of allocated dma buffer */ + +/* + cio-das16.pdf + + "das16" + "das16/f" + + 0 a/d bits 0-3 start 12 bit + 1 a/d bits 4-11 unused + 2 mux read mux set + 3 di 4 bit do 4 bit + 4 unused ao0_lsb + 5 unused ao0_msb + 6 unused ao1_lsb + 7 unused ao1_msb + 8 status eoc uni/bip interrupt reset + 9 dma, int, trig ctrl set dma, int + a pacer control unused + b reserved reserved + cdef 8254 + 0123 8255 + +*/ + +/* + cio-das16jr.pdf + + "das16jr" + + 0 a/d bits 0-3 start 12 bit + 1 a/d bits 4-11 unused + 2 mux read mux set + 3 di 4 bit do 4 bit + 4567 unused unused + 8 status eoc uni/bip interrupt reset + 9 dma, int, trig ctrl set dma, int + a pacer control unused + b gain status gain control + cdef 8254 + +*/ + +/* + cio-das16jr_16.pdf + + "das16jr_16" + + 0 a/d bits 0-7 start 16 bit + 1 a/d bits 8-15 unused + 2 mux read mux set + 3 di 4 bit do 4 bit + 4567 unused unused + 8 status eoc uni/bip interrupt reset + 9 dma, int, trig ctrl set dma, int + a pacer control unused + b gain status gain control + cdef 8254 + +*/ +/* + cio-das160x-1x.pdf + + "das1601/12" + "das1602/12" + "das1602/16" + + 0 a/d bits 0-3 start 12 bit + 1 a/d bits 4-11 unused + 2 mux read mux set + 3 di 4 bit do 4 bit + 4 unused ao0_lsb + 5 unused ao0_msb + 6 unused ao1_lsb + 7 unused ao1_msb + 8 status eoc uni/bip interrupt reset + 9 dma, int, trig ctrl set dma, int + a pacer control unused + b gain status gain control + cdef 8254 + 400 8255 + 404 unused conversion enable + 405 unused burst enable + 406 unused das1600 enable + 407 status + +*/ + +/* size in bytes of a sample from board */ +static const int sample_size = 2; + +#define DAS16_TRIG 0 +#define DAS16_AI_LSB 0 +#define DAS16_AI_MSB 1 +#define DAS16_MUX 2 +#define DAS16_DIO 3 +#define DAS16_AO_LSB(x) ((x) ? 6 : 4) +#define DAS16_AO_MSB(x) ((x) ? 7 : 5) +#define DAS16_STATUS 8 +#define BUSY (1<<7) +#define UNIPOLAR (1<<6) +#define DAS16_MUXBIT (1<<5) +#define DAS16_INT (1<<4) +#define DAS16_CONTROL 9 +#define DAS16_INTE (1<<7) +#define DAS16_IRQ(x) (((x) & 0x7) << 4) +#define DMA_ENABLE (1<<2) +#define PACING_MASK 0x3 +#define INT_PACER 0x03 +#define EXT_PACER 0x02 +#define DAS16_SOFT 0x00 +#define DAS16_PACER 0x0A +#define DAS16_CTR0 (1<<1) +#define DAS16_TRIG0 (1<<0) +#define BURST_LEN_BITS(x) (((x) & 0xf) << 4) +#define DAS16_GAIN 0x0B +#define DAS16_CNTR0_DATA 0x0C +#define DAS16_CNTR1_DATA 0x0D +#define DAS16_CNTR2_DATA 0x0E +#define DAS16_CNTR_CONTROL 0x0F +#define DAS16_TERM_CNT 0x00 +#define DAS16_ONE_SHOT 0x02 +#define DAS16_RATE_GEN 0x04 +#define DAS16_CNTR_LSB_MSB 0x30 +#define DAS16_CNTR0 0x00 +#define DAS16_CNTR1 0x40 +#define DAS16_CNTR2 0x80 + +#define DAS1600_CONV 0x404 +#define DAS1600_CONV_DISABLE 0x40 +#define DAS1600_BURST 0x405 +#define DAS1600_BURST_VAL 0x40 +#define DAS1600_ENABLE 0x406 +#define DAS1600_ENABLE_VAL 0x40 +#define DAS1600_STATUS_B 0x407 +#define DAS1600_BME 0x40 +#define DAS1600_ME 0x20 +#define DAS1600_CD 0x10 +#define DAS1600_WS 0x02 +#define DAS1600_CLK_10MHZ 0x01 + +static const struct comedi_lrange range_das1x01_bip = { 4, { + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01), + } +}; + +static const struct comedi_lrange range_das1x01_unip = { 4, { + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01), + } +}; + +static const struct comedi_lrange range_das1x02_bip = { 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + } +}; + +static const struct comedi_lrange range_das1x02_unip = { 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; + +static const struct comedi_lrange range_das16jr = { 9, { + /* also used by 16/330 */ + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; + +static const struct comedi_lrange range_das16jr_16 = { 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; + +static const int das16jr_gainlist[] = { 8, 0, 1, 2, 3, 4, 5, 6, 7 }; +static const int das16jr_16_gainlist[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; +static const int das1600_gainlist[] = { 0, 1, 2, 3 }; + +enum { + das16_pg_none = 0, + das16_pg_16jr, + das16_pg_16jr_16, + das16_pg_1601, + das16_pg_1602, +}; +static const int *const das16_gainlists[] = { + NULL, + das16jr_gainlist, + das16jr_16_gainlist, + das1600_gainlist, + das1600_gainlist, +}; + +static const struct comedi_lrange *const das16_ai_uni_lranges[] = { + &range_unknown, + &range_das16jr, + &range_das16jr_16, + &range_das1x01_unip, + &range_das1x02_unip, +}; + +static const struct comedi_lrange *const das16_ai_bip_lranges[] = { + &range_unknown, + &range_das16jr, + &range_das16jr_16, + &range_das1x01_bip, + &range_das1x02_bip, +}; + +struct munge_info { + uint8_t byte; + unsigned have_byte:1; +}; + +static int das16_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das16_do_wbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das16_di_rbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das16_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int das16_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int das16_cmd_exec(struct comedi_device *dev, + struct comedi_subdevice *s); +static int das16_cancel(struct comedi_device *dev, struct comedi_subdevice *s); +static void das16_ai_munge(struct comedi_device *dev, + struct comedi_subdevice *s, void *array, + unsigned int num_bytes, + unsigned int start_chan_index); + +static void das16_reset(struct comedi_device *dev); +static irqreturn_t das16_dma_interrupt(int irq, void *d); +static void das16_timer_interrupt(unsigned long arg); +static void das16_interrupt(struct comedi_device *dev); + +static unsigned int das16_set_pacer(struct comedi_device *dev, unsigned int ns, + int flags); +static int das1600_mode_detect(struct comedi_device *dev); +static unsigned int das16_suggest_transfer_size(struct comedi_device *dev, + struct comedi_cmd cmd); + +static void reg_dump(struct comedi_device *dev); + +struct das16_board { + const char *name; + void *ai; + unsigned int ai_nbits; + unsigned int ai_speed; /* max conversion speed in nanosec */ + unsigned int ai_pg; + void *ao; + unsigned int ao_nbits; + void *di; + void *do_; + + unsigned int i8255_offset; + unsigned int i8254_offset; + + unsigned int size; + unsigned int id; +}; + +static const struct das16_board das16_boards[] = { + { + .name = "das-16", + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 15000, + .ai_pg = das16_pg_none, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x10, + .i8254_offset = 0x0c, + .size = 0x14, + .id = 0x00, + }, + { + .name = "das-16g", + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 15000, + .ai_pg = das16_pg_none, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x10, + .i8254_offset = 0x0c, + .size = 0x14, + .id = 0x00, + }, + { + .name = "das-16f", + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 8500, + .ai_pg = das16_pg_none, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x10, + .i8254_offset = 0x0c, + .size = 0x14, + .id = 0x00, + }, + { + .name = "cio-das16", /* cio-das16.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 20000, + .ai_pg = das16_pg_none, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x10, + .i8254_offset = 0x0c, + .size = 0x14, + .id = 0x80, + }, + { + .name = "cio-das16/f", /* das16.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 10000, + .ai_pg = das16_pg_none, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x10, + .i8254_offset = 0x0c, + .size = 0x14, + .id = 0x80, + }, + { + .name = "cio-das16/jr", /* cio-das16jr.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 7692, + .ai_pg = das16_pg_16jr, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0, + .i8254_offset = 0x0c, + .size = 0x10, + .id = 0x00, + }, + { + .name = "pc104-das16jr", /* pc104-das16jr_xx.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 3300, + .ai_pg = das16_pg_16jr, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0, + .i8254_offset = 0x0c, + .size = 0x10, + .id = 0x00, + }, + { + .name = "cio-das16jr/16", /* cio-das16jr_16.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 16, + .ai_speed = 10000, + .ai_pg = das16_pg_16jr_16, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0, + .i8254_offset = 0x0c, + .size = 0x10, + .id = 0x00, + }, + { + .name = "pc104-das16jr/16", /* pc104-das16jr_xx.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 16, + .ai_speed = 10000, + .ai_pg = das16_pg_16jr_16, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0, + .i8254_offset = 0x0c, + .size = 0x10, + .id = 0x00, + }, + { + .name = "das-1201", /* 4924.pdf (keithley user's manual) */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 20000, + .ai_pg = das16_pg_none, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x400, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0x20, + }, + { + .name = "das-1202", /* 4924.pdf (keithley user's manual) */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 10000, + .ai_pg = das16_pg_none, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x400, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0x20, + }, + { + /* 4919.pdf and 4922.pdf (keithley user's manual) */ + .name = "das-1401", + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 10000, + .ai_pg = das16_pg_1601, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x0, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0 /* 4919.pdf says id bits are 0xe0, 4922.pdf says 0xc0 */ + }, + { + /* 4919.pdf and 4922.pdf (keithley user's manual) */ + .name = "das-1402", + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x0, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0 /* 4919.pdf says id bits are 0xe0, 4922.pdf says 0xc0 */ + }, + { + .name = "das-1601", /* 4919.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 10000, + .ai_pg = das16_pg_1601, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x400, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0}, + { + .name = "das-1602", /* 4919.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x400, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0}, + { + .name = "cio-das1401/12", /* cio-das1400_series.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 6250, + .ai_pg = das16_pg_1601, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0}, + { + .name = "cio-das1402/12", /* cio-das1400_series.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 6250, + .ai_pg = das16_pg_1602, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0}, + { + .name = "cio-das1402/16", /* cio-das1400_series.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 16, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0}, + { + .name = "cio-das1601/12", /* cio-das160x-1x.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 6250, + .ai_pg = das16_pg_1601, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x400, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0}, + { + .name = "cio-das1602/12", /* cio-das160x-1x.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x400, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0}, + { + .name = "cio-das1602/16", /* cio-das160x-1x.pdf */ + .ai = das16_ai_rinsn, + .ai_nbits = 16, + .ai_speed = 10000, + .ai_pg = das16_pg_1602, + .ao = das16_ao_winsn, + .ao_nbits = 12, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0x400, + .i8254_offset = 0x0c, + .size = 0x408, + .id = 0xc0}, + { + .name = "cio-das16/330", /* ? */ + .ai = das16_ai_rinsn, + .ai_nbits = 12, + .ai_speed = 3030, + .ai_pg = das16_pg_16jr, + .ao = NULL, + .di = das16_di_rbits, + .do_ = das16_do_wbits, + .i8255_offset = 0, + .i8254_offset = 0x0c, + .size = 0x14, + .id = 0xf0}, +#if 0 + { + .name = "das16/330i", /* ? */ + }, + { + .name = "das16/jr/ctr5", /* ? */ + }, + { + /* cio-das16_m1_16.pdf, this board is a bit quirky, no dma */ + .name = "cio-das16/m1/16", + }, +#endif +}; + +static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int das16_detach(struct comedi_device *dev); +static struct comedi_driver driver_das16 = { + .driver_name = "das16", + .module = THIS_MODULE, + .attach = das16_attach, + .detach = das16_detach, + .board_name = &das16_boards[0].name, + .num_names = ARRAY_SIZE(das16_boards), + .offset = sizeof(das16_boards[0]), +}; + +#define DAS16_TIMEOUT 1000 + +/* Period for timer interrupt in jiffies. It's a function + * to deal with possibility of dynamic HZ patches */ +static inline int timer_period(void) +{ + return HZ / 20; +} + +struct das16_private_struct { + unsigned int ai_unipolar; /* unipolar flag */ + unsigned int ai_singleended; /* single ended flag */ + unsigned int clockbase; /* master clock speed in ns */ + volatile unsigned int control_state; /* dma, interrupt and trigger control bits */ + volatile unsigned long adc_byte_count; /* number of bytes remaining */ + /* divisor dividing master clock to get conversion frequency */ + unsigned int divisor1; + /* divisor dividing master clock to get conversion frequency */ + unsigned int divisor2; + unsigned int dma_chan; /* dma channel */ + uint16_t *dma_buffer[2]; + dma_addr_t dma_buffer_addr[2]; + unsigned int current_buffer; + volatile unsigned int dma_transfer_size; /* target number of bytes to transfer per dma shot */ + /** + * user-defined analog input and output ranges + * defined from config options + */ + struct comedi_lrange *user_ai_range_table; + struct comedi_lrange *user_ao_range_table; + + struct timer_list timer; /* for timed interrupt */ + volatile short timer_running; + volatile short timer_mode; /* true if using timer mode */ +}; +#define devpriv ((struct das16_private_struct *)(dev->private)) +#define thisboard ((struct das16_board *)(dev->board_ptr)) + +static int das16_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0, tmp; + int gain, start_chan, i; + int mask; + + /* make sure triggers are valid */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + mask = TRIG_FOLLOW; + /* if board supports burst mode */ + if (thisboard->size > 0x400) + mask |= TRIG_TIMER | TRIG_EXT; + cmd->scan_begin_src &= mask; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + mask = TRIG_TIMER | TRIG_EXT; + /* if board supports burst mode */ + if (thisboard->size > 0x400) + mask |= TRIG_NOW; + cmd->convert_src &= mask; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /** + * step 2: make sure trigger sources are unique and + * mutually compatible + */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_FOLLOW) + err++; + if (cmd->convert_src != TRIG_TIMER && + cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW) + err++; + if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) + err++; + + /* make sure scan_begin_src and convert_src dont conflict */ + if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) + err++; + if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + /* check against maximum frequency */ + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < + thisboard->ai_speed * cmd->chanlist_len) { + cmd->scan_begin_arg = + thisboard->ai_speed * cmd->chanlist_len; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < thisboard->ai_speed) { + cmd->convert_arg = thisboard->ai_speed; + err++; + } + } + + if (cmd->stop_src == TRIG_NONE) { + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + if (err) + return 3; + + /* step 4: fix up arguments */ + if (cmd->scan_begin_src == TRIG_TIMER) { + unsigned int tmp = cmd->scan_begin_arg; + /* set divisors, correct timing arguments */ + i8253_cascade_ns_to_timer_2div(devpriv->clockbase, + &(devpriv->divisor1), + &(devpriv->divisor2), + &(cmd->scan_begin_arg), + cmd->flags & TRIG_ROUND_MASK); + err += (tmp != cmd->scan_begin_arg); + } + if (cmd->convert_src == TRIG_TIMER) { + unsigned int tmp = cmd->convert_arg; + /* set divisors, correct timing arguments */ + i8253_cascade_ns_to_timer_2div(devpriv->clockbase, + &(devpriv->divisor1), + &(devpriv->divisor2), + &(cmd->convert_arg), + cmd->flags & TRIG_ROUND_MASK); + err += (tmp != cmd->convert_arg); + } + if (err) + return 4; + + /* check channel/gain list against card's limitations */ + if (cmd->chanlist) { + gain = CR_RANGE(cmd->chanlist[0]); + start_chan = CR_CHAN(cmd->chanlist[0]); + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != + (start_chan + i) % s->n_chan) { + comedi_error(dev, + "entries in chanlist must be " + "consecutive channels, " + "counting upwards\n"); + err++; + } + if (CR_RANGE(cmd->chanlist[i]) != gain) { + comedi_error(dev, + "entries in chanlist must all " + "have the same gain\n"); + err++; + } + } + } + if (err) + return 5; + + return 0; +} + +static int das16_cmd_exec(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int byte; + unsigned long flags; + int range; + + if (devpriv->dma_chan == 0 || (dev->irq == 0 + && devpriv->timer_mode == 0)) { + comedi_error(dev, + "irq (or use of 'timer mode') dma required to " + "execute comedi_cmd"); + return -1; + } + if (cmd->flags & TRIG_RT) { + comedi_error(dev, "isa dma transfers cannot be performed with " + "TRIG_RT, aborting"); + return -1; + } + + devpriv->adc_byte_count = + cmd->stop_arg * cmd->chanlist_len * sizeof(uint16_t); + + /* disable conversions for das1600 mode */ + if (thisboard->size > 0x400) + outb(DAS1600_CONV_DISABLE, dev->iobase + DAS1600_CONV); + + /* set scan limits */ + byte = CR_CHAN(cmd->chanlist[0]); + byte |= CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]) << 4; + outb(byte, dev->iobase + DAS16_MUX); + + /* set gain (this is also burst rate register but according to + * computer boards manual, burst rate does nothing, even on + * keithley cards) */ + if (thisboard->ai_pg != das16_pg_none) { + range = CR_RANGE(cmd->chanlist[0]); + outb((das16_gainlists[thisboard->ai_pg])[range], + dev->iobase + DAS16_GAIN); + } + + /* set counter mode and counts */ + cmd->convert_arg = + das16_set_pacer(dev, cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + DEBUG_PRINT("pacer period: %d ns\n", cmd->convert_arg); + + /* enable counters */ + byte = 0; + /* Enable burst mode if appropriate. */ + if (thisboard->size > 0x400) { + if (cmd->convert_src == TRIG_NOW) { + outb(DAS1600_BURST_VAL, dev->iobase + DAS1600_BURST); + /* set burst length */ + byte |= BURST_LEN_BITS(cmd->chanlist_len - 1); + } else { + outb(0, dev->iobase + DAS1600_BURST); + } + } + outb(byte, dev->iobase + DAS16_PACER); + + /* set up dma transfer */ + flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma_chan); + devpriv->current_buffer = 0; + set_dma_addr(devpriv->dma_chan, + devpriv->dma_buffer_addr[devpriv->current_buffer]); + /* set appropriate size of transfer */ + devpriv->dma_transfer_size = das16_suggest_transfer_size(dev, *cmd); + set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_chan); + release_dma_lock(flags); + + /* set up interrupt */ + if (devpriv->timer_mode) { + devpriv->timer_running = 1; + devpriv->timer.expires = jiffies + timer_period(); + add_timer(&devpriv->timer); + devpriv->control_state &= ~DAS16_INTE; + } else { + /* clear interrupt bit */ + outb(0x00, dev->iobase + DAS16_STATUS); + /* enable interrupts */ + devpriv->control_state |= DAS16_INTE; + } + devpriv->control_state |= DMA_ENABLE; + devpriv->control_state &= ~PACING_MASK; + if (cmd->convert_src == TRIG_EXT) + devpriv->control_state |= EXT_PACER; + else + devpriv->control_state |= INT_PACER; + outb(devpriv->control_state, dev->iobase + DAS16_CONTROL); + + /* Enable conversions if using das1600 mode */ + if (thisboard->size > 0x400) + outb(0, dev->iobase + DAS1600_CONV); + + + return 0; +} + +static int das16_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + /* disable interrupts, dma and pacer clocked conversions */ + devpriv->control_state &= ~DAS16_INTE & ~PACING_MASK & ~DMA_ENABLE; + outb(devpriv->control_state, dev->iobase + DAS16_CONTROL); + if (devpriv->dma_chan) + disable_dma(devpriv->dma_chan); + + /* disable SW timer */ + if (devpriv->timer_mode && devpriv->timer_running) { + devpriv->timer_running = 0; + del_timer(&devpriv->timer); + } + + /* disable burst mode */ + if (thisboard->size > 0x400) + outb(0, dev->iobase + DAS1600_BURST); + + + spin_unlock_irqrestore(&dev->spinlock, flags); + + return 0; +} + +static void das16_reset(struct comedi_device *dev) +{ + outb(0, dev->iobase + DAS16_STATUS); + outb(0, dev->iobase + DAS16_CONTROL); + outb(0, dev->iobase + DAS16_PACER); + outb(0, dev->iobase + DAS16_CNTR_CONTROL); +} + +static int das16_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, n; + int range; + int chan; + int msb, lsb; + + /* disable interrupts and pacing */ + devpriv->control_state &= ~DAS16_INTE & ~DMA_ENABLE & ~PACING_MASK; + outb(devpriv->control_state, dev->iobase + DAS16_CONTROL); + + /* set multiplexer */ + chan = CR_CHAN(insn->chanspec); + chan |= CR_CHAN(insn->chanspec) << 4; + outb(chan, dev->iobase + DAS16_MUX); + + /* set gain */ + if (thisboard->ai_pg != das16_pg_none) { + range = CR_RANGE(insn->chanspec); + outb((das16_gainlists[thisboard->ai_pg])[range], + dev->iobase + DAS16_GAIN); + } + + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outb_p(0, dev->iobase + DAS16_TRIG); + + for (i = 0; i < DAS16_TIMEOUT; i++) { + if (!(inb(dev->iobase + DAS16_STATUS) & BUSY)) + break; + } + if (i == DAS16_TIMEOUT) { + printk("das16: timeout\n"); + return -ETIME; + } + msb = inb(dev->iobase + DAS16_AI_MSB); + lsb = inb(dev->iobase + DAS16_AI_LSB); + if (thisboard->ai_nbits == 12) + data[n] = ((lsb >> 4) & 0xf) | (msb << 4); + else + data[n] = lsb | (msb << 8); + + } + + return n; +} + +static int das16_di_rbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int bits; + + bits = inb(dev->iobase + DAS16_DIO) & 0xf; + data[1] = bits; + data[0] = 0; + + return 2; +} + +static int das16_do_wbits(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int wbits; + + /* only set bits that have been masked */ + data[0] &= 0xf; + wbits = s->state; + /* zero bits that have been masked */ + wbits &= ~data[0]; + /* set masked bits */ + wbits |= data[0] & data[1]; + s->state = wbits; + data[1] = wbits; + + outb(s->state, dev->iobase + DAS16_DIO); + + return 2; +} + +static int das16_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int lsb, msb; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + if (thisboard->ao_nbits == 12) { + lsb = (data[i] << 4) & 0xff; + msb = (data[i] >> 4) & 0xff; + } else { + lsb = data[i] & 0xff; + msb = (data[i] >> 8) & 0xff; + } + outb(lsb, dev->iobase + DAS16_AO_LSB(chan)); + outb(msb, dev->iobase + DAS16_AO_MSB(chan)); + } + + return i; +} + +static irqreturn_t das16_dma_interrupt(int irq, void *d) +{ + int status; + struct comedi_device *dev = d; + + status = inb(dev->iobase + DAS16_STATUS); + + if ((status & DAS16_INT) == 0) { + DEBUG_PRINT("spurious interrupt\n"); + return IRQ_NONE; + } + + /* clear interrupt */ + outb(0x00, dev->iobase + DAS16_STATUS); + das16_interrupt(dev); + return IRQ_HANDLED; +} + +static void das16_timer_interrupt(unsigned long arg) +{ + struct comedi_device *dev = (struct comedi_device *)arg; + + das16_interrupt(dev); + + if (devpriv->timer_running) + mod_timer(&devpriv->timer, jiffies + timer_period()); +} + +/* the pc104-das16jr (at least) has problems if the dma + transfer is interrupted in the middle of transferring + a 16 bit sample, so this function takes care to get + an even transfer count after disabling dma + channel. +*/ +static int disable_dma_on_even(struct comedi_device *dev) +{ + int residue; + int i; + static const int disable_limit = 100; + static const int enable_timeout = 100; + disable_dma(devpriv->dma_chan); + residue = get_dma_residue(devpriv->dma_chan); + for (i = 0; i < disable_limit && (residue % 2); ++i) { + int j; + enable_dma(devpriv->dma_chan); + for (j = 0; j < enable_timeout; ++j) { + int new_residue; + udelay(2); + new_residue = get_dma_residue(devpriv->dma_chan); + if (new_residue != residue) + break; + } + disable_dma(devpriv->dma_chan); + residue = get_dma_residue(devpriv->dma_chan); + } + if (i == disable_limit) { + comedi_error(dev, "failed to get an even dma transfer, " + "could be trouble."); + } + return residue; +} + +static void das16_interrupt(struct comedi_device *dev) +{ + unsigned long dma_flags, spin_flags; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + struct comedi_cmd *cmd; + int num_bytes, residue; + int buffer_index; + + if (dev->attached == 0) { + comedi_error(dev, "premature interrupt"); + return; + } + /* initialize async here to make sure it is not NULL */ + async = s->async; + cmd = &async->cmd; + + if (devpriv->dma_chan == 0) { + comedi_error(dev, "interrupt with no dma channel?"); + return; + } + + spin_lock_irqsave(&dev->spinlock, spin_flags); + if ((devpriv->control_state & DMA_ENABLE) == 0) { + spin_unlock_irqrestore(&dev->spinlock, spin_flags); + DEBUG_PRINT("interrupt while dma disabled?\n"); + return; + } + + dma_flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma_chan); + residue = disable_dma_on_even(dev); + + /* figure out how many points to read */ + if (residue > devpriv->dma_transfer_size) { + comedi_error(dev, "residue > transfer size!\n"); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + num_bytes = 0; + } else + num_bytes = devpriv->dma_transfer_size - residue; + + if (cmd->stop_src == TRIG_COUNT && + num_bytes >= devpriv->adc_byte_count) { + num_bytes = devpriv->adc_byte_count; + async->events |= COMEDI_CB_EOA; + } + + buffer_index = devpriv->current_buffer; + devpriv->current_buffer = (devpriv->current_buffer + 1) % 2; + devpriv->adc_byte_count -= num_bytes; + + /* figure out how many bytes for next transfer */ + if (cmd->stop_src == TRIG_COUNT && devpriv->timer_mode == 0 && + devpriv->dma_transfer_size > devpriv->adc_byte_count) + devpriv->dma_transfer_size = devpriv->adc_byte_count; + + /* re-enable dma */ + if ((async->events & COMEDI_CB_EOA) == 0) { + set_dma_addr(devpriv->dma_chan, + devpriv->dma_buffer_addr[devpriv->current_buffer]); + set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_chan); + /* reenable conversions for das1600 mode, (stupid hardware) */ + if (thisboard->size > 0x400 && devpriv->timer_mode == 0) + outb(0x00, dev->iobase + DAS1600_CONV); + + } + release_dma_lock(dma_flags); + + spin_unlock_irqrestore(&dev->spinlock, spin_flags); + + cfc_write_array_to_buffer(s, + devpriv->dma_buffer[buffer_index], num_bytes); + + cfc_handle_events(dev, s); +} + +static unsigned int das16_set_pacer(struct comedi_device *dev, unsigned int ns, + int rounding_flags) +{ + i8253_cascade_ns_to_timer_2div(devpriv->clockbase, &(devpriv->divisor1), + &(devpriv->divisor2), &ns, + rounding_flags & TRIG_ROUND_MASK); + + /* Write the values of ctr1 and ctr2 into counters 1 and 2 */ + i8254_load(dev->iobase + DAS16_CNTR0_DATA, 0, 1, devpriv->divisor1, 2); + i8254_load(dev->iobase + DAS16_CNTR0_DATA, 0, 2, devpriv->divisor2, 2); + + return ns; +} + +static void reg_dump(struct comedi_device *dev) +{ + DEBUG_PRINT("********DAS1600 REGISTER DUMP********\n"); + DEBUG_PRINT("DAS16_MUX: %x\n", inb(dev->iobase + DAS16_MUX)); + DEBUG_PRINT("DAS16_DIO: %x\n", inb(dev->iobase + DAS16_DIO)); + DEBUG_PRINT("DAS16_STATUS: %x\n", inb(dev->iobase + DAS16_STATUS)); + DEBUG_PRINT("DAS16_CONTROL: %x\n", inb(dev->iobase + DAS16_CONTROL)); + DEBUG_PRINT("DAS16_PACER: %x\n", inb(dev->iobase + DAS16_PACER)); + DEBUG_PRINT("DAS16_GAIN: %x\n", inb(dev->iobase + DAS16_GAIN)); + DEBUG_PRINT("DAS16_CNTR_CONTROL: %x\n", + inb(dev->iobase + DAS16_CNTR_CONTROL)); + DEBUG_PRINT("DAS1600_CONV: %x\n", inb(dev->iobase + DAS1600_CONV)); + DEBUG_PRINT("DAS1600_BURST: %x\n", inb(dev->iobase + DAS1600_BURST)); + DEBUG_PRINT("DAS1600_ENABLE: %x\n", inb(dev->iobase + DAS1600_ENABLE)); + DEBUG_PRINT("DAS1600_STATUS_B: %x\n", + inb(dev->iobase + DAS1600_STATUS_B)); +} + +static int das16_probe(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int status; + int diobits; + + /* status is available on all boards */ + + status = inb(dev->iobase + DAS16_STATUS); + + if ((status & UNIPOLAR)) + devpriv->ai_unipolar = 1; + else + devpriv->ai_unipolar = 0; + + + if ((status & DAS16_MUXBIT)) + devpriv->ai_singleended = 1; + else + devpriv->ai_singleended = 0; + + + /* diobits indicates boards */ + + diobits = inb(dev->iobase + DAS16_DIO) & 0xf0; + + printk(KERN_INFO " id bits are 0x%02x\n", diobits); + if (thisboard->id != diobits) { + printk(KERN_INFO " requested board's id bits are 0x%x (ignore)\n", + thisboard->id); + } + + return 0; +} + +static int das1600_mode_detect(struct comedi_device *dev) +{ + int status = 0; + + status = inb(dev->iobase + DAS1600_STATUS_B); + + if (status & DAS1600_CLK_10MHZ) { + devpriv->clockbase = 100; + printk(KERN_INFO " 10MHz pacer clock\n"); + } else { + devpriv->clockbase = 1000; + printk(KERN_INFO " 1MHz pacer clock\n"); + } + + reg_dump(dev); + + return 0; +} + +/* + * + * Options list: + * 0 I/O base + * 1 IRQ + * 2 DMA + * 3 Clock speed (in MHz) + */ + +static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + unsigned int irq; + unsigned long iobase; + unsigned int dma_chan; + int timer_mode; + unsigned long flags; + struct comedi_krange *user_ai_range, *user_ao_range; + + iobase = it->options[0]; +#if 0 + irq = it->options[1]; + timer_mode = it->options[8]; +#endif + /* always use time_mode since using irq can drop samples while + * waiting for dma done interrupt (due to hardware limitations) */ + irq = 0; + timer_mode = 1; + if (timer_mode) + irq = 0; + + printk(KERN_INFO "comedi%d: das16:", dev->minor); + + /* check that clock setting is valid */ + if (it->options[3]) { + if (it->options[3] != 0 && + it->options[3] != 1 && it->options[3] != 10) { + printk + ("\n Invalid option. Master clock must be set " + "to 1 or 10 (MHz)\n"); + return -EINVAL; + } + } + + ret = alloc_private(dev, sizeof(struct das16_private_struct)); + if (ret < 0) + return ret; + + if (thisboard->size < 0x400) { + printk(" 0x%04lx-0x%04lx\n", iobase, iobase + thisboard->size); + if (!request_region(iobase, thisboard->size, "das16")) { + printk(KERN_ERR " I/O port conflict\n"); + return -EIO; + } + } else { + printk(KERN_INFO " 0x%04lx-0x%04lx 0x%04lx-0x%04lx\n", + iobase, iobase + 0x0f, + iobase + 0x400, + iobase + 0x400 + (thisboard->size & 0x3ff)); + if (!request_region(iobase, 0x10, "das16")) { + printk(KERN_ERR " I/O port conflict: 0x%04lx-0x%04lx\n", + iobase, iobase + 0x0f); + return -EIO; + } + if (!request_region(iobase + 0x400, thisboard->size & 0x3ff, + "das16")) { + release_region(iobase, 0x10); + printk(KERN_ERR " I/O port conflict: 0x%04lx-0x%04lx\n", + iobase + 0x400, + iobase + 0x400 + (thisboard->size & 0x3ff)); + return -EIO; + } + } + + dev->iobase = iobase; + + /* probe id bits to make sure they are consistent */ + if (das16_probe(dev, it)) { + printk(KERN_ERR " id bits do not match selected board, aborting\n"); + return -EINVAL; + } + dev->board_name = thisboard->name; + + /* get master clock speed */ + if (thisboard->size < 0x400) { + if (it->options[3]) + devpriv->clockbase = 1000 / it->options[3]; + else + devpriv->clockbase = 1000; /* 1 MHz default */ + } else { + das1600_mode_detect(dev); + } + + /* now for the irq */ + if (irq > 1 && irq < 8) { + ret = request_irq(irq, das16_dma_interrupt, 0, "das16", dev); + + if (ret < 0) + return ret; + dev->irq = irq; + printk(KERN_INFO " ( irq = %u )", irq); + } else if (irq == 0) { + printk(" ( no irq )"); + } else { + printk(" invalid irq\n"); + return -EINVAL; + } + + /* initialize dma */ + dma_chan = it->options[2]; + if (dma_chan == 1 || dma_chan == 3) { + /* allocate dma buffers */ + int i; + for (i = 0; i < 2; i++) { + devpriv->dma_buffer[i] = pci_alloc_consistent( + NULL, DAS16_DMA_SIZE, + &devpriv->dma_buffer_addr[i]); + + if (devpriv->dma_buffer[i] == NULL) + return -ENOMEM; + } + if (request_dma(dma_chan, "das16")) { + printk(KERN_ERR " failed to allocate dma channel %i\n", + dma_chan); + return -EINVAL; + } + devpriv->dma_chan = dma_chan; + flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + set_dma_mode(devpriv->dma_chan, DMA_MODE_READ); + release_dma_lock(flags); + printk(KERN_INFO " ( dma = %u)\n", dma_chan); + } else if (dma_chan == 0) { + printk(KERN_INFO " ( no dma )\n"); + } else { + printk(KERN_ERR " invalid dma channel\n"); + return -EINVAL; + } + + /* get any user-defined input range */ + if (thisboard->ai_pg == das16_pg_none && + (it->options[4] || it->options[5])) { + /* allocate single-range range table */ + devpriv->user_ai_range_table = + kmalloc(sizeof(struct comedi_lrange) + + sizeof(struct comedi_krange), GFP_KERNEL); + /* initialize ai range */ + devpriv->user_ai_range_table->length = 1; + user_ai_range = devpriv->user_ai_range_table->range; + user_ai_range->min = it->options[4]; + user_ai_range->max = it->options[5]; + user_ai_range->flags = UNIT_volt; + } + /* get any user-defined output range */ + if (it->options[6] || it->options[7]) { + /* allocate single-range range table */ + devpriv->user_ao_range_table = + kmalloc(sizeof(struct comedi_lrange) + + sizeof(struct comedi_krange), GFP_KERNEL); + /* initialize ao range */ + devpriv->user_ao_range_table->length = 1; + user_ao_range = devpriv->user_ao_range_table->range; + user_ao_range->min = it->options[6]; + user_ao_range->max = it->options[7]; + user_ao_range->flags = UNIT_volt; + } + + if (timer_mode) { + init_timer(&(devpriv->timer)); + devpriv->timer.function = das16_timer_interrupt; + devpriv->timer.data = (unsigned long)dev; + } + devpriv->timer_mode = timer_mode ? 1 : 0; + + ret = alloc_subdevices(dev, 5); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + dev->read_subdev = s; + /* ai */ + if (thisboard->ai) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + if (devpriv->ai_singleended) { + s->n_chan = 16; + s->len_chanlist = 16; + s->subdev_flags |= SDF_GROUND; + } else { + s->n_chan = 8; + s->len_chanlist = 8; + s->subdev_flags |= SDF_DIFF; + } + s->maxdata = (1 << thisboard->ai_nbits) - 1; + if (devpriv->user_ai_range_table) { /* user defined ai range */ + s->range_table = devpriv->user_ai_range_table; + } else if (devpriv->ai_unipolar) { + s->range_table = das16_ai_uni_lranges[thisboard->ai_pg]; + } else { + s->range_table = das16_ai_bip_lranges[thisboard->ai_pg]; + } + s->insn_read = thisboard->ai; + s->do_cmdtest = das16_cmd_test; + s->do_cmd = das16_cmd_exec; + s->cancel = das16_cancel; + s->munge = das16_ai_munge; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 1; + /* ao */ + if (thisboard->ao) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << thisboard->ao_nbits) - 1; + /* user defined ao range */ + if (devpriv->user_ao_range_table) + s->range_table = devpriv->user_ao_range_table; + else + s->range_table = &range_unknown; + + s->insn_write = thisboard->ao; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 2; + /* di */ + if (thisboard->di) { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = thisboard->di; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 3; + /* do */ + if (thisboard->do_) { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = thisboard->do_; + /* initialize digital output lines */ + outb(s->state, dev->iobase + DAS16_DIO); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 4; + /* 8255 */ + if (thisboard->i8255_offset != 0) { + subdev_8255_init(dev, s, NULL, (dev->iobase + + thisboard->i8255_offset)); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + das16_reset(dev); + /* set the interrupt level */ + devpriv->control_state = DAS16_IRQ(dev->irq); + outb(devpriv->control_state, dev->iobase + DAS16_CONTROL); + + /* turn on das1600 mode if available */ + if (thisboard->size > 0x400) { + outb(DAS1600_ENABLE_VAL, dev->iobase + DAS1600_ENABLE); + outb(0, dev->iobase + DAS1600_CONV); + outb(0, dev->iobase + DAS1600_BURST); + } + + return 0; +} + +static int das16_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: das16: remove\n", dev->minor); + + das16_reset(dev); + + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 4); + + if (devpriv) { + int i; + for (i = 0; i < 2; i++) { + if (devpriv->dma_buffer[i]) + pci_free_consistent(NULL, DAS16_DMA_SIZE, + devpriv->dma_buffer[i], + devpriv-> + dma_buffer_addr[i]); + } + if (devpriv->dma_chan) + free_dma(devpriv->dma_chan); + kfree(devpriv->user_ai_range_table); + kfree(devpriv->user_ao_range_table); + } + + if (dev->irq) + free_irq(dev->irq, dev); + + if (dev->iobase) { + if (thisboard->size < 0x400) { + release_region(dev->iobase, thisboard->size); + } else { + release_region(dev->iobase, 0x10); + release_region(dev->iobase + 0x400, + thisboard->size & 0x3ff); + } + } + + return 0; +} + +static int __init driver_das16_init_module(void) +{ + return comedi_driver_register(&driver_das16); +} + +static void __exit driver_das16_cleanup_module(void) +{ + comedi_driver_unregister(&driver_das16); +} + +module_init(driver_das16_init_module); +module_exit(driver_das16_cleanup_module); + +/* utility function that suggests a dma transfer size in bytes */ +static unsigned int das16_suggest_transfer_size(struct comedi_device *dev, + struct comedi_cmd cmd) +{ + unsigned int size; + unsigned int freq; + + /* if we are using timer interrupt, we don't care how long it + * will take to complete transfer since it will be interrupted + * by timer interrupt */ + if (devpriv->timer_mode) + return DAS16_DMA_SIZE; + + /* otherwise, we are relying on dma terminal count interrupt, + * so pick a reasonable size */ + if (cmd.convert_src == TRIG_TIMER) + freq = 1000000000 / cmd.convert_arg; + else if (cmd.scan_begin_src == TRIG_TIMER) + freq = (1000000000 / cmd.scan_begin_arg) * cmd.chanlist_len; + /* return some default value */ + else + freq = 0xffffffff; + + if (cmd.flags & TRIG_WAKE_EOS) { + size = sample_size * cmd.chanlist_len; + } else { + /* make buffer fill in no more than 1/3 second */ + size = (freq / 3) * sample_size; + } + + /* set a minimum and maximum size allowed */ + if (size > DAS16_DMA_SIZE) + size = DAS16_DMA_SIZE - DAS16_DMA_SIZE % sample_size; + else if (size < sample_size) + size = sample_size; + + if (cmd.stop_src == TRIG_COUNT && size > devpriv->adc_byte_count) + size = devpriv->adc_byte_count; + + return size; +} + +static void das16_ai_munge(struct comedi_device *dev, + struct comedi_subdevice *s, void *array, + unsigned int num_bytes, + unsigned int start_chan_index) +{ + unsigned int i, num_samples = num_bytes / sizeof(short); + short *data = array; + + for (i = 0; i < num_samples; i++) { + data[i] = le16_to_cpu(data[i]); + if (thisboard->ai_nbits == 12) + data[i] = (data[i] >> 4) & 0xfff; + + } +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/das16m1.c b/drivers/staging/comedi/drivers/das16m1.c new file mode 100644 index 00000000..5376e718 --- /dev/null +++ b/drivers/staging/comedi/drivers/das16m1.c @@ -0,0 +1,787 @@ +/* + comedi/drivers/das16m1.c + CIO-DAS16/M1 driver + Author: Frank Mori Hess, based on code from the das16 + driver. + Copyright (C) 2001 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ +*/ +/* +Driver: das16m1 +Description: CIO-DAS16/M1 +Author: Frank Mori Hess +Devices: [Measurement Computing] CIO-DAS16/M1 (cio-das16/m1) +Status: works + +This driver supports a single board - the CIO-DAS16/M1. +As far as I know, there are no other boards that have +the same register layout. Even the CIO-DAS16/M1/16 is +significantly different. + +I was _barely_ able to reach the full 1 MHz capability +of this board, using a hard real-time interrupt +(set the TRIG_RT flag in your struct comedi_cmd and use +rtlinux or RTAI). The board can't do dma, so the bottleneck is +pulling the data across the ISA bus. I timed the interrupt +handler, and it took my computer ~470 microseconds to pull 512 +samples from the board. So at 1 Mhz sampling rate, +expect your CPU to be spending almost all of its +time in the interrupt handler. + +This board has some unusual restrictions for its channel/gain list. If the +list has 2 or more channels in it, then two conditions must be satisfied: +(1) - even/odd channels must appear at even/odd indices in the list +(2) - the list must have an even number of entries. + +Options: + [0] - base io address + [1] - irq (optional, but you probably want it) + +irq can be omitted, although the cmd interface will not work without it. +*/ + +#include +#include +#include "../comedidev.h" + +#include "8255.h" +#include "8253.h" +#include "comedi_fc.h" + +#define DAS16M1_SIZE 16 +#define DAS16M1_SIZE2 8 + +#define DAS16M1_XTAL 100 /* 10 MHz master clock */ + +#define FIFO_SIZE 1024 /* 1024 sample fifo */ + +/* + CIO-DAS16_M1.pdf + + "cio-das16/m1" + + 0 a/d bits 0-3, mux start 12 bit + 1 a/d bits 4-11 unused + 2 status control + 3 di 4 bit do 4 bit + 4 unused clear interrupt + 5 interrupt, pacer + 6 channel/gain queue address + 7 channel/gain queue data + 89ab 8254 + cdef 8254 + 400 8255 + 404-407 8254 + +*/ + +#define DAS16M1_AI 0 /* 16-bit wide register */ +#define AI_CHAN(x) ((x) & 0xf) +#define DAS16M1_CS 2 +#define EXT_TRIG_BIT 0x1 +#define OVRUN 0x20 +#define IRQDATA 0x80 +#define DAS16M1_DIO 3 +#define DAS16M1_CLEAR_INTR 4 +#define DAS16M1_INTR_CONTROL 5 +#define EXT_PACER 0x2 +#define INT_PACER 0x3 +#define PACER_MASK 0x3 +#define INTE 0x80 +#define DAS16M1_QUEUE_ADDR 6 +#define DAS16M1_QUEUE_DATA 7 +#define Q_CHAN(x) ((x) & 0x7) +#define Q_RANGE(x) (((x) & 0xf) << 4) +#define UNIPOLAR 0x40 +#define DAS16M1_8254_FIRST 0x8 +#define DAS16M1_8254_FIRST_CNTRL 0xb +#define TOTAL_CLEAR 0x30 +#define DAS16M1_8254_SECOND 0xc +#define DAS16M1_82C55 0x400 +#define DAS16M1_8254_THIRD 0x404 + +static const struct comedi_lrange range_das16m1 = { 9, + { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + BIP_RANGE(10), + } +}; + +static int das16m1_do_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das16m1_di_rbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das16m1_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int das16m1_cmd_test(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd); +static int das16m1_cmd_exec(struct comedi_device *dev, + struct comedi_subdevice *s); +static int das16m1_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); + +static int das16m1_poll(struct comedi_device *dev, struct comedi_subdevice *s); +static irqreturn_t das16m1_interrupt(int irq, void *d); +static void das16m1_handler(struct comedi_device *dev, unsigned int status); + +static unsigned int das16m1_set_pacer(struct comedi_device *dev, + unsigned int ns, int round_flag); + +static int das16m1_irq_bits(unsigned int irq); + +struct das16m1_board { + const char *name; + unsigned int ai_speed; +}; + +static const struct das16m1_board das16m1_boards[] = { + { + .name = "cio-das16/m1", /* CIO-DAS16_M1.pdf */ + .ai_speed = 1000, /* 1MHz max speed */ + }, +}; + +static int das16m1_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int das16m1_detach(struct comedi_device *dev); +static struct comedi_driver driver_das16m1 = { + .driver_name = "das16m1", + .module = THIS_MODULE, + .attach = das16m1_attach, + .detach = das16m1_detach, + .board_name = &das16m1_boards[0].name, + .num_names = ARRAY_SIZE(das16m1_boards), + .offset = sizeof(das16m1_boards[0]), +}; + +struct das16m1_private_struct { + unsigned int control_state; + volatile unsigned int adc_count; /* number of samples completed */ + /* initial value in lower half of hardware conversion counter, + * needed to keep track of whether new count has been loaded into + * counter yet (loaded by first sample conversion) */ + u16 initial_hw_count; + short ai_buffer[FIFO_SIZE]; + unsigned int do_bits; /* saves status of digital output bits */ + unsigned int divisor1; /* divides master clock to obtain conversion speed */ + unsigned int divisor2; /* divides master clock to obtain conversion speed */ +}; +#define devpriv ((struct das16m1_private_struct *)(dev->private)) +#define thisboard ((const struct das16m1_board *)(dev->board_ptr)) + +static int __init driver_das16m1_init_module(void) +{ + return comedi_driver_register(&driver_das16m1); +} + +static void __exit driver_das16m1_cleanup_module(void) +{ + comedi_driver_unregister(&driver_das16m1); +} + +module_init(driver_das16m1_init_module); +module_exit(driver_das16m1_cleanup_module); + +static inline short munge_sample(short data) +{ + return (data >> 4) & 0xfff; +} + +static int das16m1_cmd_test(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + unsigned int err = 0, tmp, i; + + /* make sure triggers are valid */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < thisboard->ai_speed) { + cmd->convert_arg = thisboard->ai_speed; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + /* calculate counter values that give desired timing */ + i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL, + &(devpriv->divisor1), + &(devpriv->divisor2), + &(cmd->convert_arg), + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + } + + if (err) + return 4; + + /* check chanlist against board's peculiarities */ + if (cmd->chanlist && cmd->chanlist_len > 1) { + for (i = 0; i < cmd->chanlist_len; i++) { + /* even/odd channels must go into even/odd queue addresses */ + if ((i % 2) != (CR_CHAN(cmd->chanlist[i]) % 2)) { + comedi_error(dev, "bad chanlist:\n" + " even/odd channels must go have even/odd chanlist indices"); + err++; + } + } + if ((cmd->chanlist_len % 2) != 0) { + comedi_error(dev, + "chanlist must be of even length or length 1"); + err++; + } + } + + if (err) + return 5; + + return 0; +} + +static int das16m1_cmd_exec(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int byte, i; + + if (dev->irq == 0) { + comedi_error(dev, "irq required to execute comedi_cmd"); + return -1; + } + + /* disable interrupts and internal pacer */ + devpriv->control_state &= ~INTE & ~PACER_MASK; + outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); + + /* set software count */ + devpriv->adc_count = 0; + /* Initialize lower half of hardware counter, used to determine how + * many samples are in fifo. Value doesn't actually load into counter + * until counter's next clock (the next a/d conversion) */ + i8254_load(dev->iobase + DAS16M1_8254_FIRST, 0, 1, 0, 2); + /* remember current reading of counter so we know when counter has + * actually been loaded */ + devpriv->initial_hw_count = + i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1); + /* setup channel/gain queue */ + for (i = 0; i < cmd->chanlist_len; i++) { + outb(i, dev->iobase + DAS16M1_QUEUE_ADDR); + byte = + Q_CHAN(CR_CHAN(cmd->chanlist[i])) | + Q_RANGE(CR_RANGE(cmd->chanlist[i])); + outb(byte, dev->iobase + DAS16M1_QUEUE_DATA); + } + + /* set counter mode and counts */ + cmd->convert_arg = + das16m1_set_pacer(dev, cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + + /* set control & status register */ + byte = 0; + /* if we are using external start trigger (also board dislikes having + * both start and conversion triggers external simultaneously) */ + if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT) + byte |= EXT_TRIG_BIT; + + outb(byte, dev->iobase + DAS16M1_CS); + /* clear interrupt bit */ + outb(0, dev->iobase + DAS16M1_CLEAR_INTR); + + /* enable interrupts and internal pacer */ + devpriv->control_state &= ~PACER_MASK; + if (cmd->convert_src == TRIG_TIMER) + devpriv->control_state |= INT_PACER; + else + devpriv->control_state |= EXT_PACER; + + devpriv->control_state |= INTE; + outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); + + return 0; +} + +static int das16m1_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + devpriv->control_state &= ~INTE & ~PACER_MASK; + outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); + + return 0; +} + +static int das16m1_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, n; + int byte; + const int timeout = 1000; + + /* disable interrupts and internal pacer */ + devpriv->control_state &= ~INTE & ~PACER_MASK; + outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); + + /* setup channel/gain queue */ + outb(0, dev->iobase + DAS16M1_QUEUE_ADDR); + byte = + Q_CHAN(CR_CHAN(insn->chanspec)) | Q_RANGE(CR_RANGE(insn->chanspec)); + outb(byte, dev->iobase + DAS16M1_QUEUE_DATA); + + for (n = 0; n < insn->n; n++) { + /* clear IRQDATA bit */ + outb(0, dev->iobase + DAS16M1_CLEAR_INTR); + /* trigger conversion */ + outb(0, dev->iobase); + + for (i = 0; i < timeout; i++) { + if (inb(dev->iobase + DAS16M1_CS) & IRQDATA) + break; + } + if (i == timeout) { + comedi_error(dev, "timeout"); + return -ETIME; + } + data[n] = munge_sample(inw(dev->iobase)); + } + + return n; +} + +static int das16m1_di_rbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int bits; + + bits = inb(dev->iobase + DAS16M1_DIO) & 0xf; + data[1] = bits; + data[0] = 0; + + return 2; +} + +static int das16m1_do_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int wbits; + + /* only set bits that have been masked */ + data[0] &= 0xf; + wbits = devpriv->do_bits; + /* zero bits that have been masked */ + wbits &= ~data[0]; + /* set masked bits */ + wbits |= data[0] & data[1]; + devpriv->do_bits = wbits; + data[1] = wbits; + + outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO); + + return 2; +} + +static int das16m1_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags; + unsigned int status; + + /* prevent race with interrupt handler */ + spin_lock_irqsave(&dev->spinlock, flags); + status = inb(dev->iobase + DAS16M1_CS); + das16m1_handler(dev, status); + spin_unlock_irqrestore(&dev->spinlock, flags); + + return s->async->buf_write_count - s->async->buf_read_count; +} + +static irqreturn_t das16m1_interrupt(int irq, void *d) +{ + int status; + struct comedi_device *dev = d; + + if (dev->attached == 0) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + /* prevent race with comedi_poll() */ + spin_lock(&dev->spinlock); + + status = inb(dev->iobase + DAS16M1_CS); + + if ((status & (IRQDATA | OVRUN)) == 0) { + comedi_error(dev, "spurious interrupt"); + spin_unlock(&dev->spinlock); + return IRQ_NONE; + } + + das16m1_handler(dev, status); + + /* clear interrupt */ + outb(0, dev->iobase + DAS16M1_CLEAR_INTR); + + spin_unlock(&dev->spinlock); + return IRQ_HANDLED; +} + +static void munge_sample_array(short *array, unsigned int num_elements) +{ + unsigned int i; + + for (i = 0; i < num_elements; i++) + array[i] = munge_sample(array[i]); +} + +static void das16m1_handler(struct comedi_device *dev, unsigned int status) +{ + struct comedi_subdevice *s; + struct comedi_async *async; + struct comedi_cmd *cmd; + u16 num_samples; + u16 hw_counter; + + s = dev->read_subdev; + async = s->async; + async->events = 0; + cmd = &async->cmd; + + /* figure out how many samples are in fifo */ + hw_counter = i8254_read(dev->iobase + DAS16M1_8254_FIRST, 0, 1); + /* make sure hardware counter reading is not bogus due to initial value + * not having been loaded yet */ + if (devpriv->adc_count == 0 && hw_counter == devpriv->initial_hw_count) { + num_samples = 0; + } else { + /* The calculation of num_samples looks odd, but it uses the following facts. + * 16 bit hardware counter is initialized with value of zero (which really + * means 0x1000). The counter decrements by one on each conversion + * (when the counter decrements from zero it goes to 0xffff). num_samples + * is a 16 bit variable, so it will roll over in a similar fashion to the + * hardware counter. Work it out, and this is what you get. */ + num_samples = -hw_counter - devpriv->adc_count; + } + /* check if we only need some of the points */ + if (cmd->stop_src == TRIG_COUNT) { + if (num_samples > cmd->stop_arg * cmd->chanlist_len) + num_samples = cmd->stop_arg * cmd->chanlist_len; + } + /* make sure we dont try to get too many points if fifo has overrun */ + if (num_samples > FIFO_SIZE) + num_samples = FIFO_SIZE; + insw(dev->iobase, devpriv->ai_buffer, num_samples); + munge_sample_array(devpriv->ai_buffer, num_samples); + cfc_write_array_to_buffer(s, devpriv->ai_buffer, + num_samples * sizeof(short)); + devpriv->adc_count += num_samples; + + if (cmd->stop_src == TRIG_COUNT) { + if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) { /* end of acquisition */ + das16m1_cancel(dev, s); + async->events |= COMEDI_CB_EOA; + } + } + + /* this probably won't catch overruns since the card doesn't generate + * overrun interrupts, but we might as well try */ + if (status & OVRUN) { + das16m1_cancel(dev, s); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_error(dev, "fifo overflow"); + } + + comedi_event(dev, s); + +} + +/* This function takes a time in nanoseconds and sets the * + * 2 pacer clocks to the closest frequency possible. It also * + * returns the actual sampling period. */ +static unsigned int das16m1_set_pacer(struct comedi_device *dev, + unsigned int ns, int rounding_flags) +{ + i8253_cascade_ns_to_timer_2div(DAS16M1_XTAL, &(devpriv->divisor1), + &(devpriv->divisor2), &ns, + rounding_flags & TRIG_ROUND_MASK); + + /* Write the values of ctr1 and ctr2 into counters 1 and 2 */ + i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 1, devpriv->divisor1, + 2); + i8254_load(dev->iobase + DAS16M1_8254_SECOND, 0, 2, devpriv->divisor2, + 2); + + return ns; +} + +static int das16m1_irq_bits(unsigned int irq) +{ + int ret; + + switch (irq) { + case 10: + ret = 0x0; + break; + case 11: + ret = 0x1; + break; + case 12: + ret = 0x2; + break; + case 15: + ret = 0x3; + break; + case 2: + ret = 0x4; + break; + case 3: + ret = 0x5; + break; + case 5: + ret = 0x6; + break; + case 7: + ret = 0x7; + break; + default: + return -1; + break; + } + return ret << 4; +} + +/* + * Options list: + * 0 I/O base + * 1 IRQ + */ + +static int das16m1_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + unsigned int irq; + unsigned long iobase; + + iobase = it->options[0]; + + ret = alloc_private(dev, sizeof(struct das16m1_private_struct)); + if (ret < 0) + return ret; + + dev->board_name = thisboard->name; + + if (!request_region(iobase, DAS16M1_SIZE, driver_das16m1.driver_name)) { + comedi_error(dev, "I/O port conflict\n"); + return -EIO; + } + if (!request_region(iobase + DAS16M1_82C55, DAS16M1_SIZE2, + driver_das16m1.driver_name)) { + release_region(iobase, DAS16M1_SIZE); + comedi_error(dev, "I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + /* now for the irq */ + irq = it->options[1]; + /* make sure it is valid */ + if (das16m1_irq_bits(irq) >= 0) { + ret = request_irq(irq, das16m1_interrupt, 0, + driver_das16m1.driver_name, dev); + if (ret < 0) + return ret; + dev->irq = irq; + printk + ("irq %u\n", irq); + } else if (irq == 0) { + printk + (", no irq\n"); + } else { + comedi_error(dev, "invalid irq\n" + " valid irqs are 2, 3, 5, 7, 10, 11, 12, or 15\n"); + return -EINVAL; + } + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + dev->read_subdev = s; + /* ai */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 8; + s->subdev_flags = SDF_DIFF; + s->len_chanlist = 256; + s->maxdata = (1 << 12) - 1; + s->range_table = &range_das16m1; + s->insn_read = das16m1_ai_rinsn; + s->do_cmdtest = das16m1_cmd_test; + s->do_cmd = das16m1_cmd_exec; + s->cancel = das16m1_cancel; + s->poll = das16m1_poll; + + s = dev->subdevices + 1; + /* di */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das16m1_di_rbits; + + s = dev->subdevices + 2; + /* do */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das16m1_do_wbits; + + s = dev->subdevices + 3; + /* 8255 */ + subdev_8255_init(dev, s, NULL, dev->iobase + DAS16M1_82C55); + + /* disable upper half of hardware conversion counter so it doesn't mess with us */ + outb(TOTAL_CLEAR, dev->iobase + DAS16M1_8254_FIRST_CNTRL); + + /* initialize digital output lines */ + outb(devpriv->do_bits, dev->iobase + DAS16M1_DIO); + + /* set the interrupt level */ + if (dev->irq) + devpriv->control_state = das16m1_irq_bits(dev->irq); + else + devpriv->control_state = 0; + outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL); + + return 0; +} + +static int das16m1_detach(struct comedi_device *dev) +{ + +/* das16m1_reset(dev); */ + + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 3); + + if (dev->irq) + free_irq(dev->irq, dev); + + if (dev->iobase) { + release_region(dev->iobase, DAS16M1_SIZE); + release_region(dev->iobase + DAS16M1_82C55, DAS16M1_SIZE2); + } + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/das1800.c b/drivers/staging/comedi/drivers/das1800.c new file mode 100644 index 00000000..99ada5a5 --- /dev/null +++ b/drivers/staging/comedi/drivers/das1800.c @@ -0,0 +1,1816 @@ +/* + comedi/drivers/das1800.c + Driver for Keitley das1700/das1800 series boards + Copyright (C) 2000 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ +*/ +/* +Driver: das1800 +Description: Keithley Metrabyte DAS1800 (& compatibles) +Author: Frank Mori Hess +Devices: [Keithley Metrabyte] DAS-1701ST (das-1701st), + DAS-1701ST-DA (das-1701st-da), DAS-1701/AO (das-1701ao), + DAS-1702ST (das-1702st), DAS-1702ST-DA (das-1702st-da), + DAS-1702HR (das-1702hr), DAS-1702HR-DA (das-1702hr-da), + DAS-1702/AO (das-1702ao), DAS-1801ST (das-1801st), + DAS-1801ST-DA (das-1801st-da), DAS-1801HC (das-1801hc), + DAS-1801AO (das-1801ao), DAS-1802ST (das-1802st), + DAS-1802ST-DA (das-1802st-da), DAS-1802HR (das-1802hr), + DAS-1802HR-DA (das-1802hr-da), DAS-1802HC (das-1802hc), + DAS-1802AO (das-1802ao) +Status: works + +The waveform analog output on the 'ao' cards is not supported. +If you need it, send me (Frank Hess) an email. + +Configuration options: + [0] - I/O port base address + [1] - IRQ (optional, required for timed or externally triggered conversions) + [2] - DMA0 (optional, requires irq) + [3] - DMA1 (optional, requires irq and dma0) +*/ +/* + +This driver supports the following Keithley boards: + +das-1701st +das-1701st-da +das-1701ao +das-1702st +das-1702st-da +das-1702hr +das-1702hr-da +das-1702ao +das-1801st +das-1801st-da +das-1801hc +das-1801ao +das-1802st +das-1802st-da +das-1802hr +das-1802hr-da +das-1802hc +das-1802ao + +Options: + [0] - base io address + [1] - irq (optional, required for timed or externally triggered conversions) + [2] - dma0 (optional, requires irq) + [3] - dma1 (optional, requires irq and dma0) + +irq can be omitted, although the cmd interface will not work without it. + +analog input cmd triggers supported: + start_src: TRIG_NOW | TRIG_EXT + scan_begin_src: TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT + scan_end_src: TRIG_COUNT + convert_src: TRIG_TIMER | TRIG_EXT (TRIG_EXT requires scan_begin_src == TRIG_FOLLOW) + stop_src: TRIG_COUNT | TRIG_EXT | TRIG_NONE + +scan_begin_src triggers TRIG_TIMER and TRIG_EXT use the card's +'burst mode' which limits the valid conversion time to 64 microseconds +(convert_arg <= 64000). This limitation does not apply if scan_begin_src +is TRIG_FOLLOW. + +NOTES: +Only the DAS-1801ST has been tested by me. +Unipolar and bipolar ranges cannot be mixed in the channel/gain list. + +TODO: + Make it automatically allocate irq and dma channels if they are not specified + Add support for analog out on 'ao' cards + read insn for analog out +*/ + +#include +#include +#include +#include "../comedidev.h" + +#include +#include + +#include "8253.h" +#include "comedi_fc.h" + +/* misc. defines */ +#define DAS1800_SIZE 16 /* uses 16 io addresses */ +#define FIFO_SIZE 1024 /* 1024 sample fifo */ +#define TIMER_BASE 200 /* 5 Mhz master clock */ +#define UNIPOLAR 0x4 /* bit that determines whether input range is uni/bipolar */ +#define DMA_BUF_SIZE 0x1ff00 /* size in bytes of dma buffers */ + +/* Registers for the das1800 */ +#define DAS1800_FIFO 0x0 +#define DAS1800_QRAM 0x0 +#define DAS1800_DAC 0x0 +#define DAS1800_SELECT 0x2 +#define ADC 0x0 +#define QRAM 0x1 +#define DAC(a) (0x2 + a) +#define DAS1800_DIGITAL 0x3 +#define DAS1800_CONTROL_A 0x4 +#define FFEN 0x1 +#define CGEN 0x4 +#define CGSL 0x8 +#define TGEN 0x10 +#define TGSL 0x20 +#define ATEN 0x80 +#define DAS1800_CONTROL_B 0x5 +#define DMA_CH5 0x1 +#define DMA_CH6 0x2 +#define DMA_CH7 0x3 +#define DMA_CH5_CH6 0x5 +#define DMA_CH6_CH7 0x6 +#define DMA_CH7_CH5 0x7 +#define DMA_ENABLED 0x3 /* mask used to determine if dma is enabled */ +#define DMA_DUAL 0x4 +#define IRQ3 0x8 +#define IRQ5 0x10 +#define IRQ7 0x18 +#define IRQ10 0x28 +#define IRQ11 0x30 +#define IRQ15 0x38 +#define FIMD 0x40 +#define DAS1800_CONTROL_C 0X6 +#define IPCLK 0x1 +#define XPCLK 0x3 +#define BMDE 0x4 +#define CMEN 0x8 +#define UQEN 0x10 +#define SD 0x40 +#define UB 0x80 +#define DAS1800_STATUS 0x7 +/* bits that prevent interrupt status bits (and CVEN) from being cleared on write */ +#define CLEAR_INTR_MASK (CVEN_MASK | 0x1f) +#define INT 0x1 +#define DMATC 0x2 +#define CT0TC 0x8 +#define OVF 0x10 +#define FHF 0x20 +#define FNE 0x40 +#define CVEN_MASK 0x40 /* masks CVEN on write */ +#define CVEN 0x80 +#define DAS1800_BURST_LENGTH 0x8 +#define DAS1800_BURST_RATE 0x9 +#define DAS1800_QRAM_ADDRESS 0xa +#define DAS1800_COUNTER 0xc + +#define IOBASE2 0x400 /* offset of additional ioports used on 'ao' cards */ + +enum { + das1701st, das1701st_da, das1702st, das1702st_da, das1702hr, + das1702hr_da, + das1701ao, das1702ao, das1801st, das1801st_da, das1802st, das1802st_da, + das1802hr, das1802hr_da, das1801hc, das1802hc, das1801ao, das1802ao +}; + +static int das1800_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int das1800_detach(struct comedi_device *dev); +static int das1800_probe(struct comedi_device *dev); +static int das1800_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static irqreturn_t das1800_interrupt(int irq, void *d); +static int das1800_ai_poll(struct comedi_device *dev, + struct comedi_subdevice *s); +static void das1800_ai_handler(struct comedi_device *dev); +static void das1800_handle_dma(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int status); +static void das1800_flush_dma(struct comedi_device *dev, + struct comedi_subdevice *s); +static void das1800_flush_dma_channel(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int channel, uint16_t *buffer); +static void das1800_handle_fifo_half_full(struct comedi_device *dev, + struct comedi_subdevice *s); +static void das1800_handle_fifo_not_empty(struct comedi_device *dev, + struct comedi_subdevice *s); +static int das1800_ai_do_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int das1800_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); +static int das1800_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das1800_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das1800_di_rbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int das1800_do_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int das1800_set_frequency(struct comedi_device *dev); +static unsigned int burst_convert_arg(unsigned int convert_arg, int round_mode); +static unsigned int suggest_transfer_size(struct comedi_cmd *cmd); + +/* analog input ranges */ +static const struct comedi_lrange range_ai_das1801 = { + 8, + { + RANGE(-5, 5), + RANGE(-1, 1), + RANGE(-0.1, 0.1), + RANGE(-0.02, 0.02), + RANGE(0, 5), + RANGE(0, 1), + RANGE(0, 0.1), + RANGE(0, 0.02), + } +}; + +static const struct comedi_lrange range_ai_das1802 = { + 8, + { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-2.5, 2.5), + RANGE(-1.25, 1.25), + RANGE(0, 10), + RANGE(0, 5), + RANGE(0, 2.5), + RANGE(0, 1.25), + } +}; + +struct das1800_board { + const char *name; + int ai_speed; /* max conversion period in nanoseconds */ + int resolution; /* bits of ai resolution */ + int qram_len; /* length of card's channel / gain queue */ + int common; /* supports AREF_COMMON flag */ + int do_n_chan; /* number of digital output channels */ + int ao_ability; /* 0 == no analog out, 1 == basic analog out, 2 == waveform analog out */ + int ao_n_chan; /* number of analog out channels */ + const struct comedi_lrange *range_ai; /* available input ranges */ +}; + +/* Warning: the maximum conversion speeds listed below are + * not always achievable depending on board setup (see + * user manual.) + */ +static const struct das1800_board das1800_boards[] = { + { + .name = "das-1701st", + .ai_speed = 6250, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 0, + .ao_n_chan = 0, + .range_ai = &range_ai_das1801, + }, + { + .name = "das-1701st-da", + .ai_speed = 6250, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 1, + .ao_n_chan = 4, + .range_ai = &range_ai_das1801, + }, + { + .name = "das-1702st", + .ai_speed = 6250, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 0, + .ao_n_chan = 0, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1702st-da", + .ai_speed = 6250, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 1, + .ao_n_chan = 4, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1702hr", + .ai_speed = 20000, + .resolution = 16, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 0, + .ao_n_chan = 0, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1702hr-da", + .ai_speed = 20000, + .resolution = 16, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 1, + .ao_n_chan = 2, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1701ao", + .ai_speed = 6250, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 2, + .ao_n_chan = 2, + .range_ai = &range_ai_das1801, + }, + { + .name = "das-1702ao", + .ai_speed = 6250, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 2, + .ao_n_chan = 2, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1801st", + .ai_speed = 3000, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 0, + .ao_n_chan = 0, + .range_ai = &range_ai_das1801, + }, + { + .name = "das-1801st-da", + .ai_speed = 3000, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 0, + .ao_n_chan = 4, + .range_ai = &range_ai_das1801, + }, + { + .name = "das-1802st", + .ai_speed = 3000, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 0, + .ao_n_chan = 0, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1802st-da", + .ai_speed = 3000, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 1, + .ao_n_chan = 4, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1802hr", + .ai_speed = 10000, + .resolution = 16, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 0, + .ao_n_chan = 0, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1802hr-da", + .ai_speed = 10000, + .resolution = 16, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 1, + .ao_n_chan = 2, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1801hc", + .ai_speed = 3000, + .resolution = 12, + .qram_len = 64, + .common = 0, + .do_n_chan = 8, + .ao_ability = 1, + .ao_n_chan = 2, + .range_ai = &range_ai_das1801, + }, + { + .name = "das-1802hc", + .ai_speed = 3000, + .resolution = 12, + .qram_len = 64, + .common = 0, + .do_n_chan = 8, + .ao_ability = 1, + .ao_n_chan = 2, + .range_ai = &range_ai_das1802, + }, + { + .name = "das-1801ao", + .ai_speed = 3000, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 2, + .ao_n_chan = 2, + .range_ai = &range_ai_das1801, + }, + { + .name = "das-1802ao", + .ai_speed = 3000, + .resolution = 12, + .qram_len = 256, + .common = 1, + .do_n_chan = 4, + .ao_ability = 2, + .ao_n_chan = 2, + .range_ai = &range_ai_das1802, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct das1800_board *)dev->board_ptr) + +struct das1800_private { + volatile unsigned int count; /* number of data points left to be taken */ + unsigned int divisor1; /* value to load into board's counter 1 for timed conversions */ + unsigned int divisor2; /* value to load into board's counter 2 for timed conversions */ + int do_bits; /* digital output bits */ + int irq_dma_bits; /* bits for control register b */ + /* dma bits for control register b, stored so that dma can be + * turned on and off */ + int dma_bits; + unsigned int dma0; /* dma channels used */ + unsigned int dma1; + volatile unsigned int dma_current; /* dma channel currently in use */ + uint16_t *ai_buf0; /* pointers to dma buffers */ + uint16_t *ai_buf1; + uint16_t *dma_current_buf; /* pointer to dma buffer currently being used */ + unsigned int dma_transfer_size; /* size of transfer currently used, in bytes */ + unsigned long iobase2; /* secondary io address used for analog out on 'ao' boards */ + short ao_update_bits; /* remembers the last write to the 'update' dac */ +}; + +#define devpriv ((struct das1800_private *)dev->private) + +/* analog out range for boards with basic analog out */ +static const struct comedi_lrange range_ao_1 = { + 1, + { + RANGE(-10, 10), + } +}; + +/* analog out range for 'ao' boards */ +/* +static const struct comedi_lrange range_ao_2 = { + 2, + { + RANGE(-10, 10), + RANGE(-5, 5), + } +}; +*/ + +static struct comedi_driver driver_das1800 = { + .driver_name = "das1800", + .module = THIS_MODULE, + .attach = das1800_attach, + .detach = das1800_detach, + .num_names = ARRAY_SIZE(das1800_boards), + .board_name = &das1800_boards[0].name, + .offset = sizeof(struct das1800_board), +}; + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __init driver_das1800_init_module(void) +{ + return comedi_driver_register(&driver_das1800); +} + +static void __exit driver_das1800_cleanup_module(void) +{ + comedi_driver_unregister(&driver_das1800); +} + +module_init(driver_das1800_init_module); +module_exit(driver_das1800_cleanup_module); + +static int das1800_init_dma(struct comedi_device *dev, unsigned int dma0, + unsigned int dma1) +{ + unsigned long flags; + + /* need an irq to do dma */ + if (dev->irq && dma0) { + /* encode dma0 and dma1 into 2 digit hexadecimal for switch */ + switch ((dma0 & 0x7) | (dma1 << 4)) { + case 0x5: /* dma0 == 5 */ + devpriv->dma_bits |= DMA_CH5; + break; + case 0x6: /* dma0 == 6 */ + devpriv->dma_bits |= DMA_CH6; + break; + case 0x7: /* dma0 == 7 */ + devpriv->dma_bits |= DMA_CH7; + break; + case 0x65: /* dma0 == 5, dma1 == 6 */ + devpriv->dma_bits |= DMA_CH5_CH6; + break; + case 0x76: /* dma0 == 6, dma1 == 7 */ + devpriv->dma_bits |= DMA_CH6_CH7; + break; + case 0x57: /* dma0 == 7, dma1 == 5 */ + devpriv->dma_bits |= DMA_CH7_CH5; + break; + default: + dev_err(dev->hw_dev, " only supports dma channels 5 through 7\n" + " Dual dma only allows the following combinations:\n" + " dma 5,6 / 6,7 / or 7,5\n"); + return -EINVAL; + break; + } + if (request_dma(dma0, driver_das1800.driver_name)) { + dev_err(dev->hw_dev, "failed to allocate dma channel %i\n", + dma0); + return -EINVAL; + } + devpriv->dma0 = dma0; + devpriv->dma_current = dma0; + if (dma1) { + if (request_dma(dma1, driver_das1800.driver_name)) { + dev_err(dev->hw_dev, "failed to allocate dma channel %i\n", + dma1); + return -EINVAL; + } + devpriv->dma1 = dma1; + } + devpriv->ai_buf0 = kmalloc(DMA_BUF_SIZE, GFP_KERNEL | GFP_DMA); + if (devpriv->ai_buf0 == NULL) + return -ENOMEM; + devpriv->dma_current_buf = devpriv->ai_buf0; + if (dma1) { + devpriv->ai_buf1 = + kmalloc(DMA_BUF_SIZE, GFP_KERNEL | GFP_DMA); + if (devpriv->ai_buf1 == NULL) + return -ENOMEM; + } + flags = claim_dma_lock(); + disable_dma(devpriv->dma0); + set_dma_mode(devpriv->dma0, DMA_MODE_READ); + if (dma1) { + disable_dma(devpriv->dma1); + set_dma_mode(devpriv->dma1, DMA_MODE_READ); + } + release_dma_lock(flags); + } + return 0; +} + +static int das1800_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase = it->options[0]; + unsigned int irq = it->options[1]; + unsigned int dma0 = it->options[2]; + unsigned int dma1 = it->options[3]; + unsigned long iobase2; + int board; + int retval; + + /* allocate and initialize dev->private */ + if (alloc_private(dev, sizeof(struct das1800_private)) < 0) + return -ENOMEM; + + printk(KERN_DEBUG "comedi%d: %s: io 0x%lx", dev->minor, + driver_das1800.driver_name, iobase); + if (irq) { + printk(KERN_CONT ", irq %u", irq); + if (dma0) { + printk(KERN_CONT ", dma %u", dma0); + if (dma1) + printk(KERN_CONT " and %u", dma1); + } + } + printk(KERN_CONT "\n"); + + if (iobase == 0) { + dev_err(dev->hw_dev, "io base address required\n"); + return -EINVAL; + } + + /* check if io addresses are available */ + if (!request_region(iobase, DAS1800_SIZE, driver_das1800.driver_name)) { + printk + (" I/O port conflict: failed to allocate ports 0x%lx to 0x%lx\n", + iobase, iobase + DAS1800_SIZE - 1); + return -EIO; + } + dev->iobase = iobase; + + board = das1800_probe(dev); + if (board < 0) { + dev_err(dev->hw_dev, "unable to determine board type\n"); + return -ENODEV; + } + + dev->board_ptr = das1800_boards + board; + dev->board_name = thisboard->name; + + /* if it is an 'ao' board with fancy analog out then we need extra io ports */ + if (thisboard->ao_ability == 2) { + iobase2 = iobase + IOBASE2; + if (!request_region(iobase2, DAS1800_SIZE, + driver_das1800.driver_name)) { + printk + (" I/O port conflict: failed to allocate ports 0x%lx to 0x%lx\n", + iobase2, iobase2 + DAS1800_SIZE - 1); + return -EIO; + } + devpriv->iobase2 = iobase2; + } + + /* grab our IRQ */ + if (irq) { + if (request_irq(irq, das1800_interrupt, 0, + driver_das1800.driver_name, dev)) { + dev_dbg(dev->hw_dev, "unable to allocate irq %u\n", + irq); + return -EINVAL; + } + } + dev->irq = irq; + + /* set bits that tell card which irq to use */ + switch (irq) { + case 0: + break; + case 3: + devpriv->irq_dma_bits |= 0x8; + break; + case 5: + devpriv->irq_dma_bits |= 0x10; + break; + case 7: + devpriv->irq_dma_bits |= 0x18; + break; + case 10: + devpriv->irq_dma_bits |= 0x28; + break; + case 11: + devpriv->irq_dma_bits |= 0x30; + break; + case 15: + devpriv->irq_dma_bits |= 0x38; + break; + default: + dev_err(dev->hw_dev, "irq out of range\n"); + return -EINVAL; + break; + } + + retval = das1800_init_dma(dev, dma0, dma1); + if (retval < 0) + return retval; + + if (devpriv->ai_buf0 == NULL) { + devpriv->ai_buf0 = + kmalloc(FIFO_SIZE * sizeof(uint16_t), GFP_KERNEL); + if (devpriv->ai_buf0 == NULL) + return -ENOMEM; + } + + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + /* analog input subdevice */ + s = dev->subdevices + 0; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_GROUND | SDF_CMD_READ; + if (thisboard->common) + s->subdev_flags |= SDF_COMMON; + s->n_chan = thisboard->qram_len; + s->len_chanlist = thisboard->qram_len; + s->maxdata = (1 << thisboard->resolution) - 1; + s->range_table = thisboard->range_ai; + s->do_cmd = das1800_ai_do_cmd; + s->do_cmdtest = das1800_ai_do_cmdtest; + s->insn_read = das1800_ai_rinsn; + s->poll = das1800_ai_poll; + s->cancel = das1800_cancel; + + /* analog out */ + s = dev->subdevices + 1; + if (thisboard->ao_ability == 1) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = thisboard->ao_n_chan; + s->maxdata = (1 << thisboard->resolution) - 1; + s->range_table = &range_ao_1; + s->insn_write = das1800_ao_winsn; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* di */ + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das1800_di_rbits; + + /* do */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = thisboard->do_n_chan; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das1800_do_wbits; + + das1800_cancel(dev, dev->read_subdev); + + /* initialize digital out channels */ + outb(devpriv->do_bits, dev->iobase + DAS1800_DIGITAL); + + /* initialize analog out channels */ + if (thisboard->ao_ability == 1) { + /* select 'update' dac channel for baseAddress + 0x0 */ + outb(DAC(thisboard->ao_n_chan - 1), + dev->iobase + DAS1800_SELECT); + outw(devpriv->ao_update_bits, dev->iobase + DAS1800_DAC); + } + + return 0; +}; + +static int das1800_detach(struct comedi_device *dev) +{ + /* only free stuff if it has been allocated by _attach */ + if (dev->iobase) + release_region(dev->iobase, DAS1800_SIZE); + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->private) { + if (devpriv->iobase2) + release_region(devpriv->iobase2, DAS1800_SIZE); + if (devpriv->dma0) + free_dma(devpriv->dma0); + if (devpriv->dma1) + free_dma(devpriv->dma1); + kfree(devpriv->ai_buf0); + kfree(devpriv->ai_buf1); + } + + dev_dbg(dev->hw_dev, "comedi%d: %s: remove\n", dev->minor, + driver_das1800.driver_name); + + return 0; +}; + +/* probes and checks das-1800 series board type + */ +static int das1800_probe(struct comedi_device *dev) +{ + int id; + int board; + + id = (inb(dev->iobase + DAS1800_DIGITAL) >> 4) & 0xf; /* get id bits */ + board = ((struct das1800_board *)dev->board_ptr) - das1800_boards; + + switch (id) { + case 0x3: + if (board == das1801st_da || board == das1802st_da || + board == das1701st_da || board == das1702st_da) { + dev_dbg(dev->hw_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1800st-da series\n"); + return das1801st; + break; + case 0x4: + if (board == das1802hr_da || board == das1702hr_da) { + dev_dbg(dev->hw_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1802hr-da\n"); + return das1802hr; + break; + case 0x5: + if (board == das1801ao || board == das1802ao || + board == das1701ao || board == das1702ao) { + dev_dbg(dev->hw_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1800ao series\n"); + return das1801ao; + break; + case 0x6: + if (board == das1802hr || board == das1702hr) { + dev_dbg(dev->hw_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1802hr\n"); + return das1802hr; + break; + case 0x7: + if (board == das1801st || board == das1802st || + board == das1701st || board == das1702st) { + dev_dbg(dev->hw_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1800st series\n"); + return das1801st; + break; + case 0x8: + if (board == das1801hc || board == das1802hc) { + dev_dbg(dev->hw_dev, "Board model: %s\n", + das1800_boards[board].name); + return board; + } + printk + (" Board model (probed, not recommended): das-1800hc series\n"); + return das1801hc; + break; + default: + printk + (" Board model: probe returned 0x%x (unknown, please report)\n", + id); + return board; + break; + } + return -1; +} + +static int das1800_ai_poll(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long flags; + + /* prevent race with interrupt handler */ + spin_lock_irqsave(&dev->spinlock, flags); + das1800_ai_handler(dev); + spin_unlock_irqrestore(&dev->spinlock, flags); + + return s->async->buf_write_count - s->async->buf_read_count; +} + +static irqreturn_t das1800_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned int status; + + if (dev->attached == 0) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + + /* Prevent race with das1800_ai_poll() on multi processor systems. + * Also protects indirect addressing in das1800_ai_handler */ + spin_lock(&dev->spinlock); + status = inb(dev->iobase + DAS1800_STATUS); + + /* if interrupt was not caused by das-1800 */ + if (!(status & INT)) { + spin_unlock(&dev->spinlock); + return IRQ_NONE; + } + /* clear the interrupt status bit INT */ + outb(CLEAR_INTR_MASK & ~INT, dev->iobase + DAS1800_STATUS); + /* handle interrupt */ + das1800_ai_handler(dev); + + spin_unlock(&dev->spinlock); + return IRQ_HANDLED; +} + +/* the guts of the interrupt handler, that is shared with das1800_ai_poll */ +static void das1800_ai_handler(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->subdevices + 0; /* analog input subdevice */ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned int status = inb(dev->iobase + DAS1800_STATUS); + + async->events = 0; + /* select adc for base address + 0 */ + outb(ADC, dev->iobase + DAS1800_SELECT); + /* dma buffer full */ + if (devpriv->irq_dma_bits & DMA_ENABLED) { + /* look for data from dma transfer even if dma terminal count hasn't happened yet */ + das1800_handle_dma(dev, s, status); + } else if (status & FHF) { /* if fifo half full */ + das1800_handle_fifo_half_full(dev, s); + } else if (status & FNE) { /* if fifo not empty */ + das1800_handle_fifo_not_empty(dev, s); + } + + async->events |= COMEDI_CB_BLOCK; + /* if the card's fifo has overflowed */ + if (status & OVF) { + /* clear OVF interrupt bit */ + outb(CLEAR_INTR_MASK & ~OVF, dev->iobase + DAS1800_STATUS); + comedi_error(dev, "DAS1800 FIFO overflow"); + das1800_cancel(dev, s); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + comedi_event(dev, s); + return; + } + /* stop taking data if appropriate */ + /* stop_src TRIG_EXT */ + if (status & CT0TC) { + /* clear CT0TC interrupt bit */ + outb(CLEAR_INTR_MASK & ~CT0TC, dev->iobase + DAS1800_STATUS); + /* make sure we get all remaining data from board before quitting */ + if (devpriv->irq_dma_bits & DMA_ENABLED) + das1800_flush_dma(dev, s); + else + das1800_handle_fifo_not_empty(dev, s); + das1800_cancel(dev, s); /* disable hardware conversions */ + async->events |= COMEDI_CB_EOA; + } else if (cmd->stop_src == TRIG_COUNT && devpriv->count == 0) { /* stop_src TRIG_COUNT */ + das1800_cancel(dev, s); /* disable hardware conversions */ + async->events |= COMEDI_CB_EOA; + } + + comedi_event(dev, s); + + return; +} + +static void das1800_handle_dma(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int status) +{ + unsigned long flags; + const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL; + + flags = claim_dma_lock(); + das1800_flush_dma_channel(dev, s, devpriv->dma_current, + devpriv->dma_current_buf); + /* re-enable dma channel */ + set_dma_addr(devpriv->dma_current, + virt_to_bus(devpriv->dma_current_buf)); + set_dma_count(devpriv->dma_current, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_current); + release_dma_lock(flags); + + if (status & DMATC) { + /* clear DMATC interrupt bit */ + outb(CLEAR_INTR_MASK & ~DMATC, dev->iobase + DAS1800_STATUS); + /* switch dma channels for next time, if appropriate */ + if (dual_dma) { + /* read data from the other channel next time */ + if (devpriv->dma_current == devpriv->dma0) { + devpriv->dma_current = devpriv->dma1; + devpriv->dma_current_buf = devpriv->ai_buf1; + } else { + devpriv->dma_current = devpriv->dma0; + devpriv->dma_current_buf = devpriv->ai_buf0; + } + } + } + + return; +} + +static inline uint16_t munge_bipolar_sample(const struct comedi_device *dev, + uint16_t sample) +{ + sample += 1 << (thisboard->resolution - 1); + return sample; +} + +static void munge_data(struct comedi_device *dev, uint16_t * array, + unsigned int num_elements) +{ + unsigned int i; + int unipolar; + + /* see if card is using a unipolar or bipolar range so we can munge data correctly */ + unipolar = inb(dev->iobase + DAS1800_CONTROL_C) & UB; + + /* convert to unsigned type if we are in a bipolar mode */ + if (!unipolar) { + for (i = 0; i < num_elements; i++) + array[i] = munge_bipolar_sample(dev, array[i]); + } +} + +/* Utility function used by das1800_flush_dma() and das1800_handle_dma(). + * Assumes dma lock is held */ +static void das1800_flush_dma_channel(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int channel, uint16_t *buffer) +{ + unsigned int num_bytes, num_samples; + struct comedi_cmd *cmd = &s->async->cmd; + + disable_dma(channel); + + /* clear flip-flop to make sure 2-byte registers + * get set correctly */ + clear_dma_ff(channel); + + /* figure out how many points to read */ + num_bytes = devpriv->dma_transfer_size - get_dma_residue(channel); + num_samples = num_bytes / sizeof(short); + + /* if we only need some of the points */ + if (cmd->stop_src == TRIG_COUNT && devpriv->count < num_samples) + num_samples = devpriv->count; + + munge_data(dev, buffer, num_samples); + cfc_write_array_to_buffer(s, buffer, num_bytes); + if (s->async->cmd.stop_src == TRIG_COUNT) + devpriv->count -= num_samples; + + return; +} + +/* flushes remaining data from board when external trigger has stopped acquisition + * and we are using dma transfers */ +static void das1800_flush_dma(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long flags; + const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL; + + flags = claim_dma_lock(); + das1800_flush_dma_channel(dev, s, devpriv->dma_current, + devpriv->dma_current_buf); + + if (dual_dma) { + /* switch to other channel and flush it */ + if (devpriv->dma_current == devpriv->dma0) { + devpriv->dma_current = devpriv->dma1; + devpriv->dma_current_buf = devpriv->ai_buf1; + } else { + devpriv->dma_current = devpriv->dma0; + devpriv->dma_current_buf = devpriv->ai_buf0; + } + das1800_flush_dma_channel(dev, s, devpriv->dma_current, + devpriv->dma_current_buf); + } + + release_dma_lock(flags); + + /* get any remaining samples in fifo */ + das1800_handle_fifo_not_empty(dev, s); + + return; +} + +static void das1800_handle_fifo_half_full(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int numPoints = 0; /* number of points to read */ + struct comedi_cmd *cmd = &s->async->cmd; + + numPoints = FIFO_SIZE / 2; + /* if we only need some of the points */ + if (cmd->stop_src == TRIG_COUNT && devpriv->count < numPoints) + numPoints = devpriv->count; + insw(dev->iobase + DAS1800_FIFO, devpriv->ai_buf0, numPoints); + munge_data(dev, devpriv->ai_buf0, numPoints); + cfc_write_array_to_buffer(s, devpriv->ai_buf0, + numPoints * sizeof(devpriv->ai_buf0[0])); + if (cmd->stop_src == TRIG_COUNT) + devpriv->count -= numPoints; + return; +} + +static void das1800_handle_fifo_not_empty(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + short dpnt; + int unipolar; + struct comedi_cmd *cmd = &s->async->cmd; + + unipolar = inb(dev->iobase + DAS1800_CONTROL_C) & UB; + + while (inb(dev->iobase + DAS1800_STATUS) & FNE) { + if (cmd->stop_src == TRIG_COUNT && devpriv->count == 0) + break; + dpnt = inw(dev->iobase + DAS1800_FIFO); + /* convert to unsigned type if we are in a bipolar mode */ + if (!unipolar) + ; + dpnt = munge_bipolar_sample(dev, dpnt); + cfc_write_to_buffer(s, dpnt); + if (cmd->stop_src == TRIG_COUNT) + devpriv->count--; + } + + return; +} + +static int das1800_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + outb(0x0, dev->iobase + DAS1800_STATUS); /* disable conversions */ + outb(0x0, dev->iobase + DAS1800_CONTROL_B); /* disable interrupts and dma */ + outb(0x0, dev->iobase + DAS1800_CONTROL_A); /* disable and clear fifo and stop triggering */ + if (devpriv->dma0) + disable_dma(devpriv->dma0); + if (devpriv->dma1) + disable_dma(devpriv->dma1); + return 0; +} + +/* test analog input cmd */ +static int das1800_ai_do_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + unsigned int tmp_arg; + int i; + int unipolar; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_EXT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + /* uniqueness check */ + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) + err++; + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT) + err++; + /* compatibility check */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->convert_src != TRIG_TIMER) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < thisboard->ai_speed) { + cmd->convert_arg = thisboard->ai_speed; + err++; + } + } + if (!cmd->chanlist_len) { + cmd->chanlist_len = 1; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + break; + case TRIG_NONE: + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + break; + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + /* if we are not in burst mode */ + if (cmd->scan_begin_src == TRIG_FOLLOW) { + tmp_arg = cmd->convert_arg; + /* calculate counter values that give desired timing */ + i8253_cascade_ns_to_timer_2div(TIMER_BASE, + &(devpriv->divisor1), + &(devpriv->divisor2), + &(cmd->convert_arg), + cmd-> + flags & TRIG_ROUND_MASK); + if (tmp_arg != cmd->convert_arg) + err++; + } + /* if we are in burst mode */ + else { + /* check that convert_arg is compatible */ + tmp_arg = cmd->convert_arg; + cmd->convert_arg = + burst_convert_arg(cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp_arg != cmd->convert_arg) + err++; + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* if scans are timed faster than conversion rate allows */ + if (cmd->convert_arg * cmd->chanlist_len > + cmd->scan_begin_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * + cmd->chanlist_len; + err++; + } + tmp_arg = cmd->scan_begin_arg; + /* calculate counter values that give desired timing */ + i8253_cascade_ns_to_timer_2div(TIMER_BASE, + &(devpriv-> + divisor1), + &(devpriv-> + divisor2), + &(cmd-> + scan_begin_arg), + cmd-> + flags & + TRIG_ROUND_MASK); + if (tmp_arg != cmd->scan_begin_arg) + err++; + } + } + } + + if (err) + return 4; + + /* make sure user is not trying to mix unipolar and bipolar ranges */ + if (cmd->chanlist) { + unipolar = CR_RANGE(cmd->chanlist[0]) & UNIPOLAR; + for (i = 1; i < cmd->chanlist_len; i++) { + if (unipolar != (CR_RANGE(cmd->chanlist[i]) & UNIPOLAR)) { + comedi_error(dev, + "unipolar and bipolar ranges cannot be mixed in the chanlist"); + err++; + break; + } + } + } + + if (err) + return 5; + + return 0; +} + +/* analog input cmd interface */ + +/* first, some utility functions used in the main ai_do_cmd() */ + +/* returns appropriate bits for control register a, depending on command */ +static int control_a_bits(struct comedi_cmd cmd) +{ + int control_a; + + control_a = FFEN; /* enable fifo */ + if (cmd.stop_src == TRIG_EXT) + control_a |= ATEN; + switch (cmd.start_src) { + case TRIG_EXT: + control_a |= TGEN | CGSL; + break; + case TRIG_NOW: + control_a |= CGEN; + break; + default: + break; + } + + return control_a; +} + +/* returns appropriate bits for control register c, depending on command */ +static int control_c_bits(struct comedi_cmd cmd) +{ + int control_c; + int aref; + + /* set clock source to internal or external, select analog reference, + * select unipolar / bipolar + */ + aref = CR_AREF(cmd.chanlist[0]); + control_c = UQEN; /* enable upper qram addresses */ + if (aref != AREF_DIFF) + control_c |= SD; + if (aref == AREF_COMMON) + control_c |= CMEN; + /* if a unipolar range was selected */ + if (CR_RANGE(cmd.chanlist[0]) & UNIPOLAR) + control_c |= UB; + switch (cmd.scan_begin_src) { + case TRIG_FOLLOW: /* not in burst mode */ + switch (cmd.convert_src) { + case TRIG_TIMER: + /* trig on cascaded counters */ + control_c |= IPCLK; + break; + case TRIG_EXT: + /* trig on falling edge of external trigger */ + control_c |= XPCLK; + break; + default: + break; + } + break; + case TRIG_TIMER: + /* burst mode with internal pacer clock */ + control_c |= BMDE | IPCLK; + break; + case TRIG_EXT: + /* burst mode with external trigger */ + control_c |= BMDE | XPCLK; + break; + default: + break; + } + + return control_c; +} + +/* sets up counters */ +static int setup_counters(struct comedi_device *dev, struct comedi_cmd cmd) +{ + /* setup cascaded counters for conversion/scan frequency */ + switch (cmd.scan_begin_src) { + case TRIG_FOLLOW: /* not in burst mode */ + if (cmd.convert_src == TRIG_TIMER) { + /* set conversion frequency */ + i8253_cascade_ns_to_timer_2div(TIMER_BASE, + &(devpriv->divisor1), + &(devpriv->divisor2), + &(cmd.convert_arg), + cmd. + flags & TRIG_ROUND_MASK); + if (das1800_set_frequency(dev) < 0) + return -1; + } + break; + case TRIG_TIMER: /* in burst mode */ + /* set scan frequency */ + i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1), + &(devpriv->divisor2), + &(cmd.scan_begin_arg), + cmd.flags & TRIG_ROUND_MASK); + if (das1800_set_frequency(dev) < 0) + return -1; + break; + default: + break; + } + + /* setup counter 0 for 'about triggering' */ + if (cmd.stop_src == TRIG_EXT) { + /* load counter 0 in mode 0 */ + i8254_load(dev->iobase + DAS1800_COUNTER, 0, 0, 1, 0); + } + + return 0; +} + +/* sets up dma */ +static void setup_dma(struct comedi_device *dev, struct comedi_cmd cmd) +{ + unsigned long lock_flags; + const int dual_dma = devpriv->irq_dma_bits & DMA_DUAL; + + if ((devpriv->irq_dma_bits & DMA_ENABLED) == 0) + return; + + /* determine a reasonable dma transfer size */ + devpriv->dma_transfer_size = suggest_transfer_size(&cmd); + lock_flags = claim_dma_lock(); + disable_dma(devpriv->dma0); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma0); + set_dma_addr(devpriv->dma0, virt_to_bus(devpriv->ai_buf0)); + /* set appropriate size of transfer */ + set_dma_count(devpriv->dma0, devpriv->dma_transfer_size); + devpriv->dma_current = devpriv->dma0; + devpriv->dma_current_buf = devpriv->ai_buf0; + enable_dma(devpriv->dma0); + /* set up dual dma if appropriate */ + if (dual_dma) { + disable_dma(devpriv->dma1); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma1); + set_dma_addr(devpriv->dma1, virt_to_bus(devpriv->ai_buf1)); + /* set appropriate size of transfer */ + set_dma_count(devpriv->dma1, devpriv->dma_transfer_size); + enable_dma(devpriv->dma1); + } + release_dma_lock(lock_flags); + + return; +} + +/* programs channel/gain list into card */ +static void program_chanlist(struct comedi_device *dev, struct comedi_cmd cmd) +{ + int i, n, chan_range; + unsigned long irq_flags; + const int range_mask = 0x3; /* masks unipolar/bipolar bit off range */ + const int range_bitshift = 8; + + n = cmd.chanlist_len; + /* spinlock protects indirect addressing */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(QRAM, dev->iobase + DAS1800_SELECT); /* select QRAM for baseAddress + 0x0 */ + outb(n - 1, dev->iobase + DAS1800_QRAM_ADDRESS); /*set QRAM address start */ + /* make channel / gain list */ + for (i = 0; i < n; i++) { + chan_range = + CR_CHAN(cmd. + chanlist[i]) | ((CR_RANGE(cmd.chanlist[i]) & + range_mask) << range_bitshift); + outw(chan_range, dev->iobase + DAS1800_QRAM); + } + outb(n - 1, dev->iobase + DAS1800_QRAM_ADDRESS); /*finish write to QRAM */ + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + return; +} + +/* analog input do_cmd */ +static int das1800_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int ret; + int control_a, control_c; + struct comedi_async *async = s->async; + struct comedi_cmd cmd = async->cmd; + + if (!dev->irq) { + comedi_error(dev, + "no irq assigned for das-1800, cannot do hardware conversions"); + return -1; + } + + /* disable dma on TRIG_WAKE_EOS, or TRIG_RT + * (because dma in handler is unsafe at hard real-time priority) */ + if (cmd.flags & (TRIG_WAKE_EOS | TRIG_RT)) + devpriv->irq_dma_bits &= ~DMA_ENABLED; + else + devpriv->irq_dma_bits |= devpriv->dma_bits; + /* interrupt on end of conversion for TRIG_WAKE_EOS */ + if (cmd.flags & TRIG_WAKE_EOS) { + /* interrupt fifo not empty */ + devpriv->irq_dma_bits &= ~FIMD; + } else { + /* interrupt fifo half full */ + devpriv->irq_dma_bits |= FIMD; + } + /* determine how many conversions we need */ + if (cmd.stop_src == TRIG_COUNT) + devpriv->count = cmd.stop_arg * cmd.chanlist_len; + + das1800_cancel(dev, s); + + /* determine proper bits for control registers */ + control_a = control_a_bits(cmd); + control_c = control_c_bits(cmd); + + /* setup card and start */ + program_chanlist(dev, cmd); + ret = setup_counters(dev, cmd); + if (ret < 0) { + comedi_error(dev, "Error setting up counters"); + return ret; + } + setup_dma(dev, cmd); + outb(control_c, dev->iobase + DAS1800_CONTROL_C); + /* set conversion rate and length for burst mode */ + if (control_c & BMDE) { + /* program conversion period with number of microseconds minus 1 */ + outb(cmd.convert_arg / 1000 - 1, + dev->iobase + DAS1800_BURST_RATE); + outb(cmd.chanlist_len - 1, dev->iobase + DAS1800_BURST_LENGTH); + } + outb(devpriv->irq_dma_bits, dev->iobase + DAS1800_CONTROL_B); /* enable irq/dma */ + outb(control_a, dev->iobase + DAS1800_CONTROL_A); /* enable fifo and triggering */ + outb(CVEN, dev->iobase + DAS1800_STATUS); /* enable conversions */ + + return 0; +} + +/* read analog input */ +static int das1800_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, n; + int chan, range, aref, chan_range; + int timeout = 1000; + short dpnt; + int conv_flags = 0; + unsigned long irq_flags; + + /* set up analog reference and unipolar / bipolar mode */ + aref = CR_AREF(insn->chanspec); + conv_flags |= UQEN; + if (aref != AREF_DIFF) + conv_flags |= SD; + if (aref == AREF_COMMON) + conv_flags |= CMEN; + /* if a unipolar range was selected */ + if (CR_RANGE(insn->chanspec) & UNIPOLAR) + conv_flags |= UB; + + outb(conv_flags, dev->iobase + DAS1800_CONTROL_C); /* software conversion enabled */ + outb(CVEN, dev->iobase + DAS1800_STATUS); /* enable conversions */ + outb(0x0, dev->iobase + DAS1800_CONTROL_A); /* reset fifo */ + outb(FFEN, dev->iobase + DAS1800_CONTROL_A); + + chan = CR_CHAN(insn->chanspec); + /* mask of unipolar/bipolar bit from range */ + range = CR_RANGE(insn->chanspec) & 0x3; + chan_range = chan | (range << 8); + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(QRAM, dev->iobase + DAS1800_SELECT); /* select QRAM for baseAddress + 0x0 */ + outb(0x0, dev->iobase + DAS1800_QRAM_ADDRESS); /* set QRAM address start */ + outw(chan_range, dev->iobase + DAS1800_QRAM); + outb(0x0, dev->iobase + DAS1800_QRAM_ADDRESS); /*finish write to QRAM */ + outb(ADC, dev->iobase + DAS1800_SELECT); /* select ADC for baseAddress + 0x0 */ + + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outb(0, dev->iobase + DAS1800_FIFO); + for (i = 0; i < timeout; i++) { + if (inb(dev->iobase + DAS1800_STATUS) & FNE) + break; + } + if (i == timeout) { + comedi_error(dev, "timeout"); + n = -ETIME; + goto exit; + } + dpnt = inw(dev->iobase + DAS1800_FIFO); + /* shift data to offset binary for bipolar ranges */ + if ((conv_flags & UB) == 0) + dpnt += 1 << (thisboard->resolution - 1); + data[n] = dpnt; + } +exit: + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + return n; +} + +/* writes to an analog output channel */ +static int das1800_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); +/* int range = CR_RANGE(insn->chanspec); */ + int update_chan = thisboard->ao_n_chan - 1; + short output; + unsigned long irq_flags; + + /* card expects two's complement data */ + output = data[0] - (1 << (thisboard->resolution - 1)); + /* if the write is to the 'update' channel, we need to remember its value */ + if (chan == update_chan) + devpriv->ao_update_bits = output; + /* write to channel */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(DAC(chan), dev->iobase + DAS1800_SELECT); /* select dac channel for baseAddress + 0x0 */ + outw(output, dev->iobase + DAS1800_DAC); + /* now we need to write to 'update' channel to update all dac channels */ + if (chan != update_chan) { + outb(DAC(update_chan), dev->iobase + DAS1800_SELECT); /* select 'update' channel for baseAddress + 0x0 */ + outw(devpriv->ao_update_bits, dev->iobase + DAS1800_DAC); + } + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + return 1; +} + +/* reads from digital input channels */ +static int das1800_di_rbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + data[1] = inb(dev->iobase + DAS1800_DIGITAL) & 0xf; + data[0] = 0; + + return 2; +} + +/* writes to digital output channels */ +static int das1800_do_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int wbits; + + /* only set bits that have been masked */ + data[0] &= (1 << s->n_chan) - 1; + wbits = devpriv->do_bits; + wbits &= ~data[0]; + wbits |= data[0] & data[1]; + devpriv->do_bits = wbits; + + outb(devpriv->do_bits, dev->iobase + DAS1800_DIGITAL); + + data[1] = devpriv->do_bits; + + return 2; +} + +/* loads counters with divisor1, divisor2 from private structure */ +static int das1800_set_frequency(struct comedi_device *dev) +{ + int err = 0; + + /* counter 1, mode 2 */ + if (i8254_load(dev->iobase + DAS1800_COUNTER, 0, 1, devpriv->divisor1, + 2)) + err++; + /* counter 2, mode 2 */ + if (i8254_load(dev->iobase + DAS1800_COUNTER, 0, 2, devpriv->divisor2, + 2)) + err++; + if (err) + return -1; + + return 0; +} + +/* converts requested conversion timing to timing compatible with + * hardware, used only when card is in 'burst mode' + */ +static unsigned int burst_convert_arg(unsigned int convert_arg, int round_mode) +{ + unsigned int micro_sec; + + /* in burst mode, the maximum conversion time is 64 microseconds */ + if (convert_arg > 64000) + convert_arg = 64000; + + /* the conversion time must be an integral number of microseconds */ + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + micro_sec = (convert_arg + 500) / 1000; + break; + case TRIG_ROUND_DOWN: + micro_sec = convert_arg / 1000; + break; + case TRIG_ROUND_UP: + micro_sec = (convert_arg - 1) / 1000 + 1; + break; + } + + /* return number of nanoseconds */ + return micro_sec * 1000; +} + +/* utility function that suggests a dma transfer size based on the conversion period 'ns' */ +static unsigned int suggest_transfer_size(struct comedi_cmd *cmd) +{ + unsigned int size = DMA_BUF_SIZE; + static const int sample_size = 2; /* size in bytes of one sample from board */ + unsigned int fill_time = 300000000; /* target time in nanoseconds for filling dma buffer */ + unsigned int max_size; /* maximum size we will allow for a transfer */ + + /* make dma buffer fill in 0.3 seconds for timed modes */ + switch (cmd->scan_begin_src) { + case TRIG_FOLLOW: /* not in burst mode */ + if (cmd->convert_src == TRIG_TIMER) + size = (fill_time / cmd->convert_arg) * sample_size; + break; + case TRIG_TIMER: + size = (fill_time / (cmd->scan_begin_arg * cmd->chanlist_len)) * + sample_size; + break; + default: + size = DMA_BUF_SIZE; + break; + } + + /* set a minimum and maximum size allowed */ + max_size = DMA_BUF_SIZE; + /* if we are taking limited number of conversions, limit transfer size to that */ + if (cmd->stop_src == TRIG_COUNT && + cmd->stop_arg * cmd->chanlist_len * sample_size < max_size) + max_size = cmd->stop_arg * cmd->chanlist_len * sample_size; + + if (size > max_size) + size = max_size; + if (size < sample_size) + size = sample_size; + + return size; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/das6402.c b/drivers/staging/comedi/drivers/das6402.c new file mode 100644 index 00000000..f2568414 --- /dev/null +++ b/drivers/staging/comedi/drivers/das6402.c @@ -0,0 +1,368 @@ +/* + Some comments on the code.. + + - it shouldn't be necessary to use outb_p(). + + - ignoreirq creates a race condition. It needs to be fixed. + + */ + +/* + comedi/drivers/das6402.c + An experimental driver for Computerboards' DAS6402 I/O card + + Copyright (C) 1999 Oystein Svendsen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: das6402 +Description: Keithley Metrabyte DAS6402 (& compatibles) +Author: Oystein Svendsen +Status: bitrotten +Devices: [Keithley Metrabyte] DAS6402 (das6402) + +This driver has suffered bitrot. +*/ + +#include +#include "../comedidev.h" + +#include + +#define DAS6402_SIZE 16 + +#define N_WORDS (3000*64) + +#define STOP 0 +#define START 1 + +#define SCANL 0x3f00 +#define BYTE unsigned char +#define WORD unsigned short + +/*----- register 8 ----*/ +#define CLRINT 0x01 +#define CLRXTR 0x02 +#define CLRXIN 0x04 +#define EXTEND 0x10 +#define ARMED 0x20 /* enable conting of post sample conv */ +#define POSTMODE 0x40 +#define MHZ 0x80 /* 10 MHz clock */ +/*---------------------*/ + +/*----- register 9 ----*/ +#define IRQ (0x04 << 4) /* these two are */ +#define IRQV 10 /* dependent on each other */ + +#define CONVSRC 0x03 /* trig src is Intarnal pacer */ +#define BURSTEN 0x04 /* enable burst */ +#define XINTE 0x08 /* use external int. trig */ +#define INTE 0x80 /* enable analog interrupts */ +/*---------------------*/ + +/*----- register 10 ---*/ +#define TGEN 0x01 /* Use pin DI1 for externl trigging? */ +#define TGSEL 0x02 /* Use edge triggering */ +#define TGPOL 0x04 /* active edge is falling */ +#define PRETRIG 0x08 /* pretrig */ +/*---------------------*/ + +/*----- register 11 ---*/ +#define EOB 0x0c +#define FIFOHFULL 0x08 +#define GAIN 0x01 +#define FIFONEPTY 0x04 +#define MODE 0x10 +#define SEM 0x20 +#define BIP 0x40 +/*---------------------*/ + +#define M0 0x00 +#define M2 0x04 + +#define C0 0x00 +#define C1 0x40 +#define C2 0x80 +#define RWLH 0x30 + +static int das6402_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int das6402_detach(struct comedi_device *dev); +static struct comedi_driver driver_das6402 = { + .driver_name = "das6402", + .module = THIS_MODULE, + .attach = das6402_attach, + .detach = das6402_detach, +}; + +static int __init driver_das6402_init_module(void) +{ + return comedi_driver_register(&driver_das6402); +} + +static void __exit driver_das6402_cleanup_module(void) +{ + comedi_driver_unregister(&driver_das6402); +} + +module_init(driver_das6402_init_module); +module_exit(driver_das6402_cleanup_module); + +struct das6402_private { + int ai_bytes_to_read; + + int das6402_ignoreirq; +}; +#define devpriv ((struct das6402_private *)dev->private) + +static void das6402_ai_fifo_dregs(struct comedi_device *dev, + struct comedi_subdevice *s); + +static void das6402_setcounter(struct comedi_device *dev) +{ + BYTE p; + unsigned short ctrlwrd; + + /* set up counter0 first, mode 0 */ + p = M0 | C0 | RWLH; + outb_p(p, dev->iobase + 15); + ctrlwrd = 2000; + p = (BYTE) (0xff & ctrlwrd); + outb_p(p, dev->iobase + 12); + p = (BYTE) (0xff & (ctrlwrd >> 8)); + outb_p(p, dev->iobase + 12); + + /* set up counter1, mode 2 */ + p = M2 | C1 | RWLH; + outb_p(p, dev->iobase + 15); + ctrlwrd = 10; + p = (BYTE) (0xff & ctrlwrd); + outb_p(p, dev->iobase + 13); + p = (BYTE) (0xff & (ctrlwrd >> 8)); + outb_p(p, dev->iobase + 13); + + /* set up counter1, mode 2 */ + p = M2 | C2 | RWLH; + outb_p(p, dev->iobase + 15); + ctrlwrd = 1000; + p = (BYTE) (0xff & ctrlwrd); + outb_p(p, dev->iobase + 14); + p = (BYTE) (0xff & (ctrlwrd >> 8)); + outb_p(p, dev->iobase + 14); +} + +static irqreturn_t intr_handler(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices; + + if (!dev->attached || devpriv->das6402_ignoreirq) { + dev_warn(dev->hw_dev, "BUG: spurious interrupt\n"); + return IRQ_HANDLED; + } +#ifdef DEBUG + printk("das6402: interrupt! das6402_irqcount=%i\n", + devpriv->das6402_irqcount); + printk("das6402: iobase+2=%i\n", inw_p(dev->iobase + 2)); +#endif + + das6402_ai_fifo_dregs(dev, s); + + if (s->async->buf_write_count >= devpriv->ai_bytes_to_read) { + outw_p(SCANL, dev->iobase + 2); /* clears the fifo */ + outb(0x07, dev->iobase + 8); /* clears all flip-flops */ +#ifdef DEBUG + printk("das6402: Got %i samples\n\n", + devpriv->das6402_wordsread - diff); +#endif + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + } + + outb(0x01, dev->iobase + 8); /* clear only the interrupt flip-flop */ + + comedi_event(dev, s); + return IRQ_HANDLED; +} + +#if 0 +static void das6402_ai_fifo_read(struct comedi_device *dev, short *data, int n) +{ + int i; + + for (i = 0; i < n; i++) + data[i] = inw(dev->iobase); +} +#endif + +static void das6402_ai_fifo_dregs(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + while (1) { + if (!(inb(dev->iobase + 8) & 0x01)) + return; + comedi_buf_put(s->async, inw(dev->iobase)); + } +} + +static int das6402_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* + * This function should reset the board from whatever condition it + * is in (i.e., acquiring data), to a non-active state. + */ + + devpriv->das6402_ignoreirq = 1; + dev_dbg(dev->hw_dev, "Stopping acquisition\n"); + devpriv->das6402_ignoreirq = 1; + outb_p(0x02, dev->iobase + 10); /* disable external trigging */ + outw_p(SCANL, dev->iobase + 2); /* resets the card fifo */ + outb_p(0, dev->iobase + 9); /* disables interrupts */ + + outw_p(SCANL, dev->iobase + 2); + + return 0; +} + +#ifdef unused +static int das6402_ai_mode2(struct comedi_device *dev, + struct comedi_subdevice *s, comedi_trig * it) +{ + devpriv->das6402_ignoreirq = 1; + dev_dbg(dev->hw_dev, "Starting acquisition\n"); + outb_p(0x03, dev->iobase + 10); /* enable external trigging */ + outw_p(SCANL, dev->iobase + 2); /* resets the card fifo */ + outb_p(IRQ | CONVSRC | BURSTEN | INTE, dev->iobase + 9); + + devpriv->ai_bytes_to_read = it->n * sizeof(short); + + /* um... ignoreirq is a nasty race condition */ + devpriv->das6402_ignoreirq = 0; + + outw_p(SCANL, dev->iobase + 2); + + return 0; +} +#endif + +static int board_init(struct comedi_device *dev) +{ + BYTE b; + + devpriv->das6402_ignoreirq = 1; + + outb(0x07, dev->iobase + 8); + + /* register 11 */ + outb_p(MODE, dev->iobase + 11); + b = BIP | SEM | MODE | GAIN | FIFOHFULL; + outb_p(b, dev->iobase + 11); + + /* register 8 */ + outb_p(EXTEND, dev->iobase + 8); + b = EXTEND | MHZ; + outb_p(b, dev->iobase + 8); + b = MHZ | CLRINT | CLRXTR | CLRXIN; + outb_p(b, dev->iobase + 8); + + /* register 9 */ + b = IRQ | CONVSRC | BURSTEN | INTE; + outb_p(b, dev->iobase + 9); + + /* register 10 */ + b = TGSEL | TGEN; + outb_p(b, dev->iobase + 10); + + b = 0x07; + outb_p(b, dev->iobase + 8); + + das6402_setcounter(dev); + + outw_p(SCANL, dev->iobase + 2); /* reset card fifo */ + + devpriv->das6402_ignoreirq = 0; + + return 0; +} + +static int das6402_detach(struct comedi_device *dev) +{ + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->iobase) + release_region(dev->iobase, DAS6402_SIZE); + + return 0; +} + +static int das6402_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + unsigned int irq; + unsigned long iobase; + int ret; + struct comedi_subdevice *s; + + dev->board_name = "das6402"; + + iobase = it->options[0]; + if (iobase == 0) + iobase = 0x300; + + if (!request_region(iobase, DAS6402_SIZE, "das6402")) { + dev_err(dev->hw_dev, "I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + /* should do a probe here */ + + irq = it->options[0]; + dev_dbg(dev->hw_dev, "( irq = %u )\n", irq); + ret = request_irq(irq, intr_handler, 0, "das6402", dev); + if (ret < 0) + return ret; + + dev->irq = irq; + ret = alloc_private(dev, sizeof(struct das6402_private)); + if (ret < 0) + return ret; + + ret = alloc_subdevices(dev, 1); + if (ret < 0) + return ret; + + /* ai subdevice */ + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 8; + /* s->trig[2]=das6402_ai_mode2; */ + s->cancel = das6402_ai_cancel; + s->maxdata = (1 << 12) - 1; + s->len_chanlist = 16; /* ? */ + s->range_table = &range_unknown; + + board_init(dev); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/das800.c b/drivers/staging/comedi/drivers/das800.c new file mode 100644 index 00000000..6e347b40 --- /dev/null +++ b/drivers/staging/comedi/drivers/das800.c @@ -0,0 +1,923 @@ +/* + comedi/drivers/das800.c + Driver for Keitley das800 series boards and compatibles + Copyright (C) 2000 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ +*/ +/* +Driver: das800 +Description: Keithley Metrabyte DAS800 (& compatibles) +Author: Frank Mori Hess +Devices: [Keithley Metrabyte] DAS-800 (das-800), DAS-801 (das-801), + DAS-802 (das-802), + [Measurement Computing] CIO-DAS800 (cio-das800), + CIO-DAS801 (cio-das801), CIO-DAS802 (cio-das802), + CIO-DAS802/16 (cio-das802/16) +Status: works, cio-das802/16 untested - email me if you have tested it + +Configuration options: + [0] - I/O port base address + [1] - IRQ (optional, required for timed or externally triggered conversions) + +Notes: + IRQ can be omitted, although the cmd interface will not work without it. + + All entries in the channel/gain list must use the same gain and be + consecutive channels counting upwards in channel number (these are + hardware limitations.) + + I've never tested the gain setting stuff since I only have a + DAS-800 board with fixed gain. + + The cio-das802/16 does not have a fifo-empty status bit! Therefore + only fifo-half-full transfers are possible with this card. +*/ +/* + +cmd triggers supported: + start_src: TRIG_NOW | TRIG_EXT + scan_begin_src: TRIG_FOLLOW + scan_end_src: TRIG_COUNT + convert_src: TRIG_TIMER | TRIG_EXT + stop_src: TRIG_NONE | TRIG_COUNT + + +*/ + +#include +#include "../comedidev.h" + +#include +#include + +#include "8253.h" +#include "comedi_fc.h" + +#define DAS800_SIZE 8 +#define TIMER_BASE 1000 +#define N_CHAN_AI 8 /* number of analog input channels */ + +/* Registers for the das800 */ + +#define DAS800_LSB 0 +#define FIFO_EMPTY 0x1 +#define FIFO_OVF 0x2 +#define DAS800_MSB 1 +#define DAS800_CONTROL1 2 +#define CONTROL1_INTE 0x8 +#define DAS800_CONV_CONTROL 2 +#define ITE 0x1 +#define CASC 0x2 +#define DTEN 0x4 +#define IEOC 0x8 +#define EACS 0x10 +#define CONV_HCEN 0x80 +#define DAS800_SCAN_LIMITS 2 +#define DAS800_STATUS 2 +#define IRQ 0x8 +#define BUSY 0x80 +#define DAS800_GAIN 3 +#define CIO_FFOV 0x8 /* fifo overflow for cio-das802/16 */ +#define CIO_ENHF 0x90 /* interrupt fifo half full for cio-das802/16 */ +#define CONTROL1 0x80 +#define CONV_CONTROL 0xa0 +#define SCAN_LIMITS 0xc0 +#define ID 0xe0 +#define DAS800_8254 4 +#define DAS800_STATUS2 7 +#define STATUS2_HCEN 0x80 +#define STATUS2_INTE 0X20 +#define DAS800_ID 7 + +struct das800_board { + const char *name; + int ai_speed; + const struct comedi_lrange *ai_range; + int resolution; +}; + +/* analog input ranges */ +static const struct comedi_lrange range_das800_ai = { + 1, + { + RANGE(-5, 5), + } +}; + +static const struct comedi_lrange range_das801_ai = { + 9, + { + RANGE(-5, 5), + RANGE(-10, 10), + RANGE(0, 10), + RANGE(-0.5, 0.5), + RANGE(0, 1), + RANGE(-0.05, 0.05), + RANGE(0, 0.1), + RANGE(-0.01, 0.01), + RANGE(0, 0.02), + } +}; + +static const struct comedi_lrange range_cio_das801_ai = { + 9, + { + RANGE(-5, 5), + RANGE(-10, 10), + RANGE(0, 10), + RANGE(-0.5, 0.5), + RANGE(0, 1), + RANGE(-0.05, 0.05), + RANGE(0, 0.1), + RANGE(-0.005, 0.005), + RANGE(0, 0.01), + } +}; + +static const struct comedi_lrange range_das802_ai = { + 9, + { + RANGE(-5, 5), + RANGE(-10, 10), + RANGE(0, 10), + RANGE(-2.5, 2.5), + RANGE(0, 5), + RANGE(-1.25, 1.25), + RANGE(0, 2.5), + RANGE(-0.625, 0.625), + RANGE(0, 1.25), + } +}; + +static const struct comedi_lrange range_das80216_ai = { + 8, + { + RANGE(-10, 10), + RANGE(0, 10), + RANGE(-5, 5), + RANGE(0, 5), + RANGE(-2.5, 2.5), + RANGE(0, 2.5), + RANGE(-1.25, 1.25), + RANGE(0, 1.25), + } +}; + +enum { das800, ciodas800, das801, ciodas801, das802, ciodas802, ciodas80216 }; + +static const struct das800_board das800_boards[] = { + { + .name = "das-800", + .ai_speed = 25000, + .ai_range = &range_das800_ai, + .resolution = 12, + }, + { + .name = "cio-das800", + .ai_speed = 20000, + .ai_range = &range_das800_ai, + .resolution = 12, + }, + { + .name = "das-801", + .ai_speed = 25000, + .ai_range = &range_das801_ai, + .resolution = 12, + }, + { + .name = "cio-das801", + .ai_speed = 20000, + .ai_range = &range_cio_das801_ai, + .resolution = 12, + }, + { + .name = "das-802", + .ai_speed = 25000, + .ai_range = &range_das802_ai, + .resolution = 12, + }, + { + .name = "cio-das802", + .ai_speed = 20000, + .ai_range = &range_das802_ai, + .resolution = 12, + }, + { + .name = "cio-das802/16", + .ai_speed = 10000, + .ai_range = &range_das80216_ai, + .resolution = 16, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct das800_board *)dev->board_ptr) + +struct das800_private { + volatile unsigned int count; /* number of data points left to be taken */ + volatile int forever; /* flag indicating whether we should take data forever */ + unsigned int divisor1; /* value to load into board's counter 1 for timed conversions */ + unsigned int divisor2; /* value to load into board's counter 2 for timed conversions */ + volatile int do_bits; /* digital output bits */ +}; + +#define devpriv ((struct das800_private *)dev->private) + +static int das800_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int das800_detach(struct comedi_device *dev); +static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s); + +static struct comedi_driver driver_das800 = { + .driver_name = "das800", + .module = THIS_MODULE, + .attach = das800_attach, + .detach = das800_detach, + .num_names = ARRAY_SIZE(das800_boards), + .board_name = &das800_boards[0].name, + .offset = sizeof(struct das800_board), +}; + +static irqreturn_t das800_interrupt(int irq, void *d); +static void enable_das800(struct comedi_device *dev); +static void disable_das800(struct comedi_device *dev); +static int das800_ai_do_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int das800_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); +static int das800_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int das800_di_rbits(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int das800_do_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int das800_probe(struct comedi_device *dev); +static int das800_set_frequency(struct comedi_device *dev); + +/* checks and probes das-800 series board type */ +static int das800_probe(struct comedi_device *dev) +{ + int id_bits; + unsigned long irq_flags; + int board; + + /* 'comedi spin lock irqsave' disables even rt interrupts, we use them to protect indirect addressing */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(ID, dev->iobase + DAS800_GAIN); /* select base address + 7 to be ID register */ + id_bits = inb(dev->iobase + DAS800_ID) & 0x3; /* get id bits */ + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + board = thisboard - das800_boards; + + switch (id_bits) { + case 0x0: + if (board == das800) { + dev_dbg(dev->hw_dev, "Board model: DAS-800\n"); + return board; + } + if (board == ciodas800) { + dev_dbg(dev->hw_dev, "Board model: CIO-DAS800\n"); + return board; + } + dev_dbg(dev->hw_dev, "Board model (probed): DAS-800\n"); + return das800; + break; + case 0x2: + if (board == das801) { + dev_dbg(dev->hw_dev, "Board model: DAS-801\n"); + return board; + } + if (board == ciodas801) { + dev_dbg(dev->hw_dev, "Board model: CIO-DAS801\n"); + return board; + } + dev_dbg(dev->hw_dev, "Board model (probed): DAS-801\n"); + return das801; + break; + case 0x3: + if (board == das802) { + dev_dbg(dev->hw_dev, "Board model: DAS-802\n"); + return board; + } + if (board == ciodas802) { + dev_dbg(dev->hw_dev, "Board model: CIO-DAS802\n"); + return board; + } + if (board == ciodas80216) { + dev_dbg(dev->hw_dev, "Board model: CIO-DAS802/16\n"); + return board; + } + dev_dbg(dev->hw_dev, "Board model (probed): DAS-802\n"); + return das802; + break; + default: + dev_dbg(dev->hw_dev, "Board model: probe returned 0x%x (unknown)\n", + id_bits); + return board; + break; + } + return -1; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __init driver_das800_init_module(void) +{ + return comedi_driver_register(&driver_das800); +} + +static void __exit driver_das800_cleanup_module(void) +{ + comedi_driver_unregister(&driver_das800); +} + +module_init(driver_das800_init_module); +module_exit(driver_das800_cleanup_module); + +/* interrupt service routine */ +static irqreturn_t das800_interrupt(int irq, void *d) +{ + short i; /* loop index */ + short dataPoint = 0; + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; /* analog input subdevice */ + struct comedi_async *async; + int status; + unsigned long irq_flags; + static const int max_loops = 128; /* half-fifo size for cio-das802/16 */ + /* flags */ + int fifo_empty = 0; + int fifo_overflow = 0; + + status = inb(dev->iobase + DAS800_STATUS); + /* if interrupt was not generated by board or driver not attached, quit */ + if (!(status & IRQ)) + return IRQ_NONE; + if (!(dev->attached)) + return IRQ_HANDLED; + + /* wait until here to initialize async, since we will get null dereference + * if interrupt occurs before driver is fully attached! + */ + async = s->async; + + /* if hardware conversions are not enabled, then quit */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select base address + 7 to be STATUS2 register */ + status = inb(dev->iobase + DAS800_STATUS2) & STATUS2_HCEN; + /* don't release spinlock yet since we want to make sure no one else disables hardware conversions */ + if (status == 0) { + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + return IRQ_HANDLED; + } + + /* loop while card's fifo is not empty (and limit to half fifo for cio-das802/16) */ + for (i = 0; i < max_loops; i++) { + /* read 16 bits from dev->iobase and dev->iobase + 1 */ + dataPoint = inb(dev->iobase + DAS800_LSB); + dataPoint += inb(dev->iobase + DAS800_MSB) << 8; + if (thisboard->resolution == 12) { + fifo_empty = dataPoint & FIFO_EMPTY; + fifo_overflow = dataPoint & FIFO_OVF; + if (fifo_overflow) + break; + } else { + fifo_empty = 0; /* cio-das802/16 has no fifo empty status bit */ + } + if (fifo_empty) + break; + /* strip off extraneous bits for 12 bit cards */ + if (thisboard->resolution == 12) + dataPoint = (dataPoint >> 4) & 0xfff; + /* if there are more data points to collect */ + if (devpriv->count > 0 || devpriv->forever == 1) { + /* write data point to buffer */ + cfc_write_to_buffer(s, dataPoint); + if (devpriv->count > 0) + devpriv->count--; + } + } + async->events |= COMEDI_CB_BLOCK; + /* check for fifo overflow */ + if (thisboard->resolution == 12) { + fifo_overflow = dataPoint & FIFO_OVF; + /* else cio-das802/16 */ + } else { + fifo_overflow = inb(dev->iobase + DAS800_GAIN) & CIO_FFOV; + } + if (fifo_overflow) { + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + comedi_error(dev, "DAS800 FIFO overflow"); + das800_cancel(dev, dev->subdevices + 0); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + comedi_event(dev, s); + async->events = 0; + return IRQ_HANDLED; + } + if (devpriv->count > 0 || devpriv->forever == 1) { + /* Re-enable card's interrupt. + * We already have spinlock, so indirect addressing is safe */ + outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ + outb(CONTROL1_INTE | devpriv->do_bits, + dev->iobase + DAS800_CONTROL1); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + /* otherwise, stop taking data */ + } else { + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + disable_das800(dev); /* diable hardware triggered conversions */ + async->events |= COMEDI_CB_EOA; + } + comedi_event(dev, s); + async->events = 0; + return IRQ_HANDLED; +} + +static int das800_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase = it->options[0]; + unsigned int irq = it->options[1]; + unsigned long irq_flags; + int board; + + dev_info(dev->hw_dev, "comedi%d: das800: io 0x%lx\n", dev->minor, + iobase); + if (irq) + dev_dbg(dev->hw_dev, "irq %u\n", irq); + + /* allocate and initialize dev->private */ + if (alloc_private(dev, sizeof(struct das800_private)) < 0) + return -ENOMEM; + + if (iobase == 0) { + dev_err(dev->hw_dev, "io base address required for das800\n"); + return -EINVAL; + } + + /* check if io addresses are available */ + if (!request_region(iobase, DAS800_SIZE, "das800")) { + dev_err(dev->hw_dev, "I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + board = das800_probe(dev); + if (board < 0) { + dev_dbg(dev->hw_dev, "unable to determine board type\n"); + return -ENODEV; + } + dev->board_ptr = das800_boards + board; + + /* grab our IRQ */ + if (irq == 1 || irq > 7) { + dev_err(dev->hw_dev, "irq out of range\n"); + return -EINVAL; + } + if (irq) { + if (request_irq(irq, das800_interrupt, 0, "das800", dev)) { + dev_err(dev->hw_dev, "unable to allocate irq %u\n", + irq); + return -EINVAL; + } + } + dev->irq = irq; + + dev->board_name = thisboard->name; + + if (alloc_subdevices(dev, 3) < 0) + return -ENOMEM; + + /* analog input subdevice */ + s = dev->subdevices + 0; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + s->n_chan = 8; + s->len_chanlist = 8; + s->maxdata = (1 << thisboard->resolution) - 1; + s->range_table = thisboard->ai_range; + s->do_cmd = das800_ai_do_cmd; + s->do_cmdtest = das800_ai_do_cmdtest; + s->insn_read = das800_ai_rinsn; + s->cancel = das800_cancel; + + /* di */ + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 3; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das800_di_rbits; + + /* do */ + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = das800_do_wbits; + + disable_das800(dev); + + /* initialize digital out channels */ + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ + outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + return 0; +}; + +static int das800_detach(struct comedi_device *dev) +{ + dev_info(dev->hw_dev, "comedi%d: das800: remove\n", dev->minor); + + /* only free stuff if it has been allocated by _attach */ + if (dev->iobase) + release_region(dev->iobase, DAS800_SIZE); + if (dev->irq) + free_irq(dev->irq, dev); + return 0; +}; + +static int das800_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + devpriv->forever = 0; + devpriv->count = 0; + disable_das800(dev); + return 0; +} + +/* enable_das800 makes the card start taking hardware triggered conversions */ +static void enable_das800(struct comedi_device *dev) +{ + unsigned long irq_flags; + spin_lock_irqsave(&dev->spinlock, irq_flags); + /* enable fifo-half full interrupts for cio-das802/16 */ + if (thisboard->resolution == 16) + outb(CIO_ENHF, dev->iobase + DAS800_GAIN); + outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */ + outb(CONV_HCEN, dev->iobase + DAS800_CONV_CONTROL); /* enable hardware triggering */ + outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ + outb(CONTROL1_INTE | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); /* enable card's interrupt */ + spin_unlock_irqrestore(&dev->spinlock, irq_flags); +} + +/* disable_das800 stops hardware triggered conversions */ +static void disable_das800(struct comedi_device *dev) +{ + unsigned long irq_flags; + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */ + outb(0x0, dev->iobase + DAS800_CONV_CONTROL); /* disable hardware triggering of conversions */ + spin_unlock_irqrestore(&dev->spinlock, irq_flags); +} + +static int das800_ai_do_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + int gain, startChan; + int i; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < thisboard->ai_speed) { + cmd->convert_arg = thisboard->ai_speed; + err++; + } + } + if (!cmd->chanlist_len) { + cmd->chanlist_len = 1; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + /* calculate counter values that give desired timing */ + i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1), + &(devpriv->divisor2), + &(cmd->convert_arg), + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + } + + if (err) + return 4; + + /* check channel/gain list against card's limitations */ + if (cmd->chanlist) { + gain = CR_RANGE(cmd->chanlist[0]); + startChan = CR_CHAN(cmd->chanlist[0]); + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != + (startChan + i) % N_CHAN_AI) { + comedi_error(dev, + "entries in chanlist must be consecutive channels, counting upwards\n"); + err++; + } + if (CR_RANGE(cmd->chanlist[i]) != gain) { + comedi_error(dev, + "entries in chanlist must all have the same gain\n"); + err++; + } + } + } + + if (err) + return 5; + + return 0; +} + +static int das800_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int startChan, endChan, scan, gain; + int conv_bits; + unsigned long irq_flags; + struct comedi_async *async = s->async; + + if (!dev->irq) { + comedi_error(dev, + "no irq assigned for das-800, cannot do hardware conversions"); + return -1; + } + + disable_das800(dev); + + /* set channel scan limits */ + startChan = CR_CHAN(async->cmd.chanlist[0]); + endChan = (startChan + async->cmd.chanlist_len - 1) % 8; + scan = (endChan << 3) | startChan; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(SCAN_LIMITS, dev->iobase + DAS800_GAIN); /* select base address + 2 to be scan limits register */ + outb(scan, dev->iobase + DAS800_SCAN_LIMITS); /* set scan limits */ + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + /* set gain */ + gain = CR_RANGE(async->cmd.chanlist[0]); + if (thisboard->resolution == 12 && gain > 0) + gain += 0x7; + gain &= 0xf; + outb(gain, dev->iobase + DAS800_GAIN); + + switch (async->cmd.stop_src) { + case TRIG_COUNT: + devpriv->count = async->cmd.stop_arg * async->cmd.chanlist_len; + devpriv->forever = 0; + break; + case TRIG_NONE: + devpriv->forever = 1; + devpriv->count = 0; + break; + default: + break; + } + + /* enable auto channel scan, send interrupts on end of conversion + * and set clock source to internal or external + */ + conv_bits = 0; + conv_bits |= EACS | IEOC; + if (async->cmd.start_src == TRIG_EXT) + conv_bits |= DTEN; + switch (async->cmd.convert_src) { + case TRIG_TIMER: + conv_bits |= CASC | ITE; + /* set conversion frequency */ + i8253_cascade_ns_to_timer_2div(TIMER_BASE, &(devpriv->divisor1), + &(devpriv->divisor2), + &(async->cmd.convert_arg), + async->cmd. + flags & TRIG_ROUND_MASK); + if (das800_set_frequency(dev) < 0) { + comedi_error(dev, "Error setting up counters"); + return -1; + } + break; + case TRIG_EXT: + break; + default: + break; + } + + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(CONV_CONTROL, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be conversion control register */ + outb(conv_bits, dev->iobase + DAS800_CONV_CONTROL); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + async->events = 0; + enable_das800(dev); + return 0; +} + +static int das800_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i, n; + int chan; + int range; + int lsb, msb; + int timeout = 1000; + unsigned long irq_flags; + + disable_das800(dev); /* disable hardware conversions (enables software conversions) */ + + /* set multiplexer */ + chan = CR_CHAN(insn->chanspec); + + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ + outb(chan | devpriv->do_bits, dev->iobase + DAS800_CONTROL1); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + /* set gain / range */ + range = CR_RANGE(insn->chanspec); + if (thisboard->resolution == 12 && range) + range += 0x7; + range &= 0xf; + outb(range, dev->iobase + DAS800_GAIN); + + udelay(5); + + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outb_p(0, dev->iobase + DAS800_MSB); + + for (i = 0; i < timeout; i++) { + if (!(inb(dev->iobase + DAS800_STATUS) & BUSY)) + break; + } + if (i == timeout) { + comedi_error(dev, "timeout"); + return -ETIME; + } + lsb = inb(dev->iobase + DAS800_LSB); + msb = inb(dev->iobase + DAS800_MSB); + if (thisboard->resolution == 12) { + data[n] = (lsb >> 4) & 0xff; + data[n] |= (msb << 4); + } else { + data[n] = (msb << 8) | lsb; + } + } + + return n; +} + +static int das800_di_rbits(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int bits; + + bits = inb(dev->iobase + DAS800_STATUS) >> 4; + bits &= 0x7; + data[1] = bits; + data[0] = 0; + + return 2; +} + +static int das800_do_wbits(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int wbits; + unsigned long irq_flags; + + /* only set bits that have been masked */ + data[0] &= 0xf; + wbits = devpriv->do_bits >> 4; + wbits &= ~data[0]; + wbits |= data[0] & data[1]; + devpriv->do_bits = wbits << 4; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + outb(CONTROL1, dev->iobase + DAS800_GAIN); /* select dev->iobase + 2 to be control register 1 */ + outb(devpriv->do_bits | CONTROL1_INTE, dev->iobase + DAS800_CONTROL1); + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + + data[1] = wbits; + + return 2; +} + +/* loads counters with divisor1, divisor2 from private structure */ +static int das800_set_frequency(struct comedi_device *dev) +{ + int err = 0; + + if (i8254_load(dev->iobase + DAS800_8254, 0, 1, devpriv->divisor1, 2)) + err++; + if (i8254_load(dev->iobase + DAS800_8254, 0, 2, devpriv->divisor2, 2)) + err++; + if (err) + return -1; + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dmm32at.c b/drivers/staging/comedi/drivers/dmm32at.c new file mode 100644 index 00000000..2b4e6e6e --- /dev/null +++ b/drivers/staging/comedi/drivers/dmm32at.c @@ -0,0 +1,1081 @@ +/* + comedi/drivers/dmm32at.c + Diamond Systems mm32at code for a Comedi driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: dmm32at +Description: Diamond Systems mm32at driver. +Devices: +Author: Perry J. Piplani +Updated: Fri Jun 4 09:13:24 CDT 2004 +Status: experimental + +This driver is for the Diamond Systems MM-32-AT board +http://www.diamondsystems.com/products/diamondmm32at It is being used +on serveral projects inside NASA, without problems so far. For analog +input commands, TRIG_EXT is not yet supported at all.. + +Configuration Options: + comedi_config /dev/comedi0 dmm32at baseaddr,irq +*/ + +#include +#include "../comedidev.h" +#include + +/* Board register addresses */ + +#define DMM32AT_MEMSIZE 0x10 + +#define DMM32AT_CONV 0x00 +#define DMM32AT_AILSB 0x00 +#define DMM32AT_AUXDOUT 0x01 +#define DMM32AT_AIMSB 0x01 +#define DMM32AT_AILOW 0x02 +#define DMM32AT_AIHIGH 0x03 + +#define DMM32AT_DACLSB 0x04 +#define DMM32AT_DACSTAT 0x04 +#define DMM32AT_DACMSB 0x05 + +#define DMM32AT_FIFOCNTRL 0x07 +#define DMM32AT_FIFOSTAT 0x07 + +#define DMM32AT_CNTRL 0x08 +#define DMM32AT_AISTAT 0x08 + +#define DMM32AT_INTCLOCK 0x09 + +#define DMM32AT_CNTRDIO 0x0a + +#define DMM32AT_AICONF 0x0b +#define DMM32AT_AIRBACK 0x0b + +#define DMM32AT_CLK1 0x0d +#define DMM32AT_CLK2 0x0e +#define DMM32AT_CLKCT 0x0f + +#define DMM32AT_DIOA 0x0c +#define DMM32AT_DIOB 0x0d +#define DMM32AT_DIOC 0x0e +#define DMM32AT_DIOCONF 0x0f + +#define dmm_inb(cdev, reg) inb((cdev->iobase)+reg) +#define dmm_outb(cdev, reg, valu) outb(valu, (cdev->iobase)+reg) + +/* Board register values. */ + +/* DMM32AT_DACSTAT 0x04 */ +#define DMM32AT_DACBUSY 0x80 + +/* DMM32AT_FIFOCNTRL 0x07 */ +#define DMM32AT_FIFORESET 0x02 +#define DMM32AT_SCANENABLE 0x04 + +/* DMM32AT_CNTRL 0x08 */ +#define DMM32AT_RESET 0x20 +#define DMM32AT_INTRESET 0x08 +#define DMM32AT_CLKACC 0x00 +#define DMM32AT_DIOACC 0x01 + +/* DMM32AT_AISTAT 0x08 */ +#define DMM32AT_STATUS 0x80 + +/* DMM32AT_INTCLOCK 0x09 */ +#define DMM32AT_ADINT 0x80 +#define DMM32AT_CLKSEL 0x03 + +/* DMM32AT_CNTRDIO 0x0a */ +#define DMM32AT_FREQ12 0x80 + +/* DMM32AT_AICONF 0x0b */ +#define DMM32AT_RANGE_U10 0x0c +#define DMM32AT_RANGE_U5 0x0d +#define DMM32AT_RANGE_B10 0x08 +#define DMM32AT_RANGE_B5 0x00 +#define DMM32AT_SCINT_20 0x00 +#define DMM32AT_SCINT_15 0x10 +#define DMM32AT_SCINT_10 0x20 +#define DMM32AT_SCINT_5 0x30 + +/* DMM32AT_CLKCT 0x0f */ +#define DMM32AT_CLKCT1 0x56 /* mode3 counter 1 - write low byte only */ +#define DMM32AT_CLKCT2 0xb6 /* mode3 counter 2 - write high and low byte */ + +/* DMM32AT_DIOCONF 0x0f */ +#define DMM32AT_DIENABLE 0x80 +#define DMM32AT_DIRA 0x10 +#define DMM32AT_DIRB 0x02 +#define DMM32AT_DIRCL 0x01 +#define DMM32AT_DIRCH 0x08 + +/* board AI ranges in comedi structure */ +static const struct comedi_lrange dmm32at_airanges = { + 4, + { + UNI_RANGE(10), + UNI_RANGE(5), + BIP_RANGE(10), + BIP_RANGE(5), + } +}; + +/* register values for above ranges */ +static const unsigned char dmm32at_rangebits[] = { + DMM32AT_RANGE_U10, + DMM32AT_RANGE_U5, + DMM32AT_RANGE_B10, + DMM32AT_RANGE_B5, +}; + +/* only one of these ranges is valid, as set by a jumper on the + * board. The application should only use the range set by the jumper + */ +static const struct comedi_lrange dmm32at_aoranges = { + 4, + { + UNI_RANGE(10), + UNI_RANGE(5), + BIP_RANGE(10), + BIP_RANGE(5), + } +}; + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +struct dmm32at_board { + const char *name; + int ai_chans; + int ai_bits; + const struct comedi_lrange *ai_ranges; + int ao_chans; + int ao_bits; + const struct comedi_lrange *ao_ranges; + int have_dio; + int dio_chans; +}; +static const struct dmm32at_board dmm32at_boards[] = { + { + .name = "dmm32at", + .ai_chans = 32, + .ai_bits = 16, + .ai_ranges = &dmm32at_airanges, + .ao_chans = 4, + .ao_bits = 12, + .ao_ranges = &dmm32at_aoranges, + .have_dio = 1, + .dio_chans = 24, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct dmm32at_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + * several hardware drivers keep similar information in this structure, + * feel free to suggest moving the variable to the struct comedi_device struct. + */ +struct dmm32at_private { + + int data; + int ai_inuse; + unsigned int ai_scans_left; + + /* Used for AO readback */ + unsigned int ao_readback[4]; + unsigned char dio_config; + +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct dmm32at_private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int dmm32at_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dmm32at_detach(struct comedi_device *dev); +static struct comedi_driver driver_dmm32at = { + .driver_name = "dmm32at", + .module = THIS_MODULE, + .attach = dmm32at_attach, + .detach = dmm32at_detach, +/* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ +/* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in dmm32at_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + .board_name = &dmm32at_boards[0].name, + .offset = sizeof(struct dmm32at_board), + .num_names = ARRAY_SIZE(dmm32at_boards), +}; + +/* prototypes for driver functions below */ +static int dmm32at_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dmm32at_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dmm32at_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dmm32at_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dmm32at_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int dmm32at_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int dmm32at_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); +static int dmm32at_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static int dmm32at_ns_to_timer(unsigned int *ns, int round); +static irqreturn_t dmm32at_isr(int irq, void *d); +void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int dmm32at_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int ret; + struct comedi_subdevice *s; + unsigned char aihi, ailo, fifostat, aistat, intstat, airback; + unsigned long iobase; + unsigned int irq; + + iobase = it->options[0]; + irq = it->options[1]; + + printk(KERN_INFO "comedi%d: dmm32at: attaching\n", dev->minor); + printk(KERN_DEBUG "dmm32at: probing at address 0x%04lx, irq %u\n", + iobase, irq); + + /* register address space */ + if (!request_region(iobase, DMM32AT_MEMSIZE, thisboard->name)) { + printk(KERN_ERR "comedi%d: dmm32at: I/O port conflict\n", + dev->minor); + return -EIO; + } + dev->iobase = iobase; + + /* the following just makes sure the board is there and gets + it to a known state */ + + /* reset the board */ + dmm_outb(dev, DMM32AT_CNTRL, DMM32AT_RESET); + + /* allow a millisecond to reset */ + udelay(1000); + + /* zero scan and fifo control */ + dmm_outb(dev, DMM32AT_FIFOCNTRL, 0x0); + + /* zero interrupt and clock control */ + dmm_outb(dev, DMM32AT_INTCLOCK, 0x0); + + /* write a test channel range, the high 3 bits should drop */ + dmm_outb(dev, DMM32AT_AILOW, 0x80); + dmm_outb(dev, DMM32AT_AIHIGH, 0xff); + + /* set the range at 10v unipolar */ + dmm_outb(dev, DMM32AT_AICONF, DMM32AT_RANGE_U10); + + /* should take 10 us to settle, here's a hundred */ + udelay(100); + + /* read back the values */ + ailo = dmm_inb(dev, DMM32AT_AILOW); + aihi = dmm_inb(dev, DMM32AT_AIHIGH); + fifostat = dmm_inb(dev, DMM32AT_FIFOSTAT); + aistat = dmm_inb(dev, DMM32AT_AISTAT); + intstat = dmm_inb(dev, DMM32AT_INTCLOCK); + airback = dmm_inb(dev, DMM32AT_AIRBACK); + + printk(KERN_DEBUG "dmm32at: lo=0x%02x hi=0x%02x fifostat=0x%02x\n", + ailo, aihi, fifostat); + printk(KERN_DEBUG + "dmm32at: aistat=0x%02x intstat=0x%02x airback=0x%02x\n", + aistat, intstat, airback); + + if ((ailo != 0x00) || (aihi != 0x1f) || (fifostat != 0x80) || + (aistat != 0x60 || (intstat != 0x00) || airback != 0x0c)) { + printk(KERN_ERR "dmmat32: board detection failed\n"); + return -EIO; + } + + /* board is there, register interrupt */ + if (irq) { + ret = request_irq(irq, dmm32at_isr, 0, thisboard->name, dev); + if (ret < 0) { + printk(KERN_ERR "dmm32at: irq conflict\n"); + return ret; + } + dev->irq = irq; + } + +/* + * If you can probe the device to determine what device in a series + * it is, this is the place to do it. Otherwise, dev->board_ptr + * should already be initialized. + */ + /* dev->board_ptr = dmm32at_probe(dev); */ + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = thisboard->name; + +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct dmm32at_private)) < 0) + return -ENOMEM; + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 3) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + dev->read_subdev = s; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + /* we support single-ended (ground) and differential */ + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ; + s->n_chan = thisboard->ai_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = thisboard->ai_ranges; + s->len_chanlist = 32; /* This is the maximum chanlist length that + the board can handle */ + s->insn_read = dmm32at_ai_rinsn; + s->do_cmd = dmm32at_ai_cmd; + s->do_cmdtest = dmm32at_ai_cmdtest; + s->cancel = dmm32at_ai_cancel; + + s = dev->subdevices + 1; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = thisboard->ao_chans; + s->maxdata = (1 << thisboard->ao_bits) - 1; + s->range_table = thisboard->ao_ranges; + s->insn_write = dmm32at_ao_winsn; + s->insn_read = dmm32at_ao_rinsn; + + s = dev->subdevices + 2; + /* digital i/o subdevice */ + if (thisboard->have_dio) { + + /* get access to the DIO regs */ + dmm_outb(dev, DMM32AT_CNTRL, DMM32AT_DIOACC); + /* set the DIO's to the defualt input setting */ + devpriv->dio_config = DMM32AT_DIRA | DMM32AT_DIRB | + DMM32AT_DIRCL | DMM32AT_DIRCH | DMM32AT_DIENABLE; + dmm_outb(dev, DMM32AT_DIOCONF, devpriv->dio_config); + + /* set up the subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = thisboard->dio_chans; + s->maxdata = 1; + s->state = 0; + s->range_table = &range_digital; + s->insn_bits = dmm32at_dio_insn_bits; + s->insn_config = dmm32at_dio_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* success */ + printk(KERN_INFO "comedi%d: dmm32at: attached\n", dev->minor); + + return 1; + +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int dmm32at_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: dmm32at: remove\n", dev->minor); + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->iobase) + release_region(dev->iobase, DMM32AT_MEMSIZE); + + return 0; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ + +static int dmm32at_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, i; + unsigned int d; + unsigned char status; + unsigned short msb, lsb; + unsigned char chan; + int range; + + /* get the channel and range number */ + + chan = CR_CHAN(insn->chanspec) & (s->n_chan - 1); + range = CR_RANGE(insn->chanspec); + + /* printk("channel=0x%02x, range=%d\n",chan,range); */ + + /* zero scan and fifo control and reset fifo */ + dmm_outb(dev, DMM32AT_FIFOCNTRL, DMM32AT_FIFORESET); + + /* write the ai channel range regs */ + dmm_outb(dev, DMM32AT_AILOW, chan); + dmm_outb(dev, DMM32AT_AIHIGH, chan); + /* set the range bits */ + dmm_outb(dev, DMM32AT_AICONF, dmm32at_rangebits[range]); + + /* wait for circuit to settle */ + for (i = 0; i < 40000; i++) { + status = dmm_inb(dev, DMM32AT_AIRBACK); + if ((status & DMM32AT_STATUS) == 0) + break; + } + if (i == 40000) { + printk(KERN_WARNING "dmm32at: timeout\n"); + return -ETIMEDOUT; + } + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + dmm_outb(dev, DMM32AT_CONV, 0xff); + /* wait for conversion to end */ + for (i = 0; i < 40000; i++) { + status = dmm_inb(dev, DMM32AT_AISTAT); + if ((status & DMM32AT_STATUS) == 0) + break; + } + if (i == 40000) { + printk(KERN_WARNING "dmm32at: timeout\n"); + return -ETIMEDOUT; + } + + /* read data */ + lsb = dmm_inb(dev, DMM32AT_AILSB); + msb = dmm_inb(dev, DMM32AT_AIMSB); + + /* invert sign bit to make range unsigned, this is an + idiosyncrasy of the diamond board, it return + conversions as a signed value, i.e. -32768 to + 32767, flipping the bit and interpreting it as + signed gives you a range of 0 to 65535 which is + used by comedi */ + d = ((msb ^ 0x0080) << 8) + lsb; + + data[n] = d; + } + + /* return the number of samples read/written */ + return n; +} + +static int dmm32at_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + int start_chan, gain, i; + + /* printk("dmmat32 in command test\n"); */ + + /* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. */ + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER /*| TRIG_EXT */ ; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER /*| TRIG_EXT */ ; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually + * compatible */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } +#define MAX_SCAN_SPEED 1000000 /* in nanoseconds */ +#define MIN_SCAN_SPEED 1000000000 /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < MAX_SCAN_SPEED) { + cmd->scan_begin_arg = MAX_SCAN_SPEED; + err++; + } + if (cmd->scan_begin_arg > MIN_SCAN_SPEED) { + cmd->scan_begin_arg = MIN_SCAN_SPEED; + err++; + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + if (cmd->scan_begin_arg > 9) { + cmd->scan_begin_arg = 9; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg >= 17500) + cmd->convert_arg = 20000; + else if (cmd->convert_arg >= 12500) + cmd->convert_arg = 15000; + else if (cmd->convert_arg >= 7500) + cmd->convert_arg = 10000; + else + cmd->convert_arg = 5000; + + } else { + /* external trigger */ + /* see above */ + if (cmd->convert_arg > 9) { + cmd->convert_arg = 9; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg > 0xfffffff0) { + cmd->stop_arg = 0xfffffff0; + err++; + } + if (cmd->stop_arg == 0) { + cmd->stop_arg = 1; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + dmm32at_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + dmm32at_ns_to_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if (err) + return 4; + + /* step 5 check the channel list, the channel list for this + board must be consecutive and gains must be the same */ + + if (cmd->chanlist) { + gain = CR_RANGE(cmd->chanlist[0]); + start_chan = CR_CHAN(cmd->chanlist[0]); + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != + (start_chan + i) % s->n_chan) { + comedi_error(dev, + "entries in chanlist must be consecutive channels, counting upwards\n"); + err++; + } + if (CR_RANGE(cmd->chanlist[i]) != gain) { + comedi_error(dev, + "entries in chanlist must all have the same gain\n"); + err++; + } + } + } + + if (err) + return 5; + + return 0; +} + +static int dmm32at_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int i, range; + unsigned char chanlo, chanhi, status; + + if (!cmd->chanlist) + return -EINVAL; + + /* get the channel list and range */ + chanlo = CR_CHAN(cmd->chanlist[0]) & (s->n_chan - 1); + chanhi = chanlo + cmd->chanlist_len - 1; + if (chanhi >= s->n_chan) + return -EINVAL; + range = CR_RANGE(cmd->chanlist[0]); + + /* reset fifo */ + dmm_outb(dev, DMM32AT_FIFOCNTRL, DMM32AT_FIFORESET); + + /* set scan enable */ + dmm_outb(dev, DMM32AT_FIFOCNTRL, DMM32AT_SCANENABLE); + + /* write the ai channel range regs */ + dmm_outb(dev, DMM32AT_AILOW, chanlo); + dmm_outb(dev, DMM32AT_AIHIGH, chanhi); + + /* set the range bits */ + dmm_outb(dev, DMM32AT_AICONF, dmm32at_rangebits[range]); + + /* reset the interrupt just in case */ + dmm_outb(dev, DMM32AT_CNTRL, DMM32AT_INTRESET); + + if (cmd->stop_src == TRIG_COUNT) + devpriv->ai_scans_left = cmd->stop_arg; + else { /* TRIG_NONE */ + devpriv->ai_scans_left = 0xffffffff; /* indicates TRIG_NONE to + * isr */ + } + + /* wait for circuit to settle */ + for (i = 0; i < 40000; i++) { + status = dmm_inb(dev, DMM32AT_AIRBACK); + if ((status & DMM32AT_STATUS) == 0) + break; + } + if (i == 40000) { + printk(KERN_WARNING "dmm32at: timeout\n"); + return -ETIMEDOUT; + } + + if (devpriv->ai_scans_left > 1) { + /* start the clock and enable the interrupts */ + dmm32at_setaitimer(dev, cmd->scan_begin_arg); + } else { + /* start the interrups and initiate a single scan */ + dmm_outb(dev, DMM32AT_INTCLOCK, DMM32AT_ADINT); + dmm_outb(dev, DMM32AT_CONV, 0xff); + } + +/* printk("dmmat32 in command\n"); */ + +/* for(i=0;ichanlist_len;i++) */ +/* comedi_buf_put(s->async,i*100); */ + +/* s->async->events |= COMEDI_CB_EOA; */ +/* comedi_event(dev, s); */ + + return 0; + +} + +static int dmm32at_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + devpriv->ai_scans_left = 1; + return 0; +} + +static irqreturn_t dmm32at_isr(int irq, void *d) +{ + unsigned char intstat; + unsigned int samp; + unsigned short msb, lsb; + int i; + struct comedi_device *dev = d; + + if (!dev->attached) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + intstat = dmm_inb(dev, DMM32AT_INTCLOCK); + + if (intstat & DMM32AT_ADINT) { + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_cmd *cmd = &s->async->cmd; + + for (i = 0; i < cmd->chanlist_len; i++) { + /* read data */ + lsb = dmm_inb(dev, DMM32AT_AILSB); + msb = dmm_inb(dev, DMM32AT_AIMSB); + + /* invert sign bit to make range unsigned */ + samp = ((msb ^ 0x0080) << 8) + lsb; + comedi_buf_put(s->async, samp); + } + + if (devpriv->ai_scans_left != 0xffffffff) { /* TRIG_COUNT */ + devpriv->ai_scans_left--; + if (devpriv->ai_scans_left == 0) { + /* disable further interrupts and clocks */ + dmm_outb(dev, DMM32AT_INTCLOCK, 0x0); + /* set the buffer to be flushed with an EOF */ + s->async->events |= COMEDI_CB_EOA; + } + + } + /* flush the buffer */ + comedi_event(dev, s); + } + + /* reset the interrupt */ + dmm_outb(dev, DMM32AT_CNTRL, DMM32AT_INTRESET); + return IRQ_HANDLED; +} + +/* This function doesn't require a particular form, this is just + * what happens to be used in some of the drivers. It should + * convert ns nanoseconds to a counter value suitable for programming + * the device. Also, it should adjust ns so that it cooresponds to + * the actual time that the device will use. */ +static int dmm32at_ns_to_timer(unsigned int *ns, int round) +{ + /* trivial timer */ + /* if your timing is done through two cascaded timers, the + * i8253_cascade_ns_to_timer() function in 8253.h can be + * very helpful. There are also i8254_load() and i8254_mm_load() + * which can be used to load values into the ubiquitous 8254 counters + */ + + return *ns; +} + +static int dmm32at_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + unsigned char hi, lo, status; + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; i++) { + + devpriv->ao_readback[chan] = data[i]; + + /* get the low byte */ + lo = data[i] & 0x00ff; + /* high byte also contains channel number */ + hi = (data[i] >> 8) + chan * (1 << 6); + /* printk("writing 0x%02x 0x%02x\n",hi,lo); */ + /* write the low and high values to the board */ + dmm_outb(dev, DMM32AT_DACLSB, lo); + dmm_outb(dev, DMM32AT_DACMSB, hi); + + /* wait for circuit to settle */ + for (i = 0; i < 40000; i++) { + status = dmm_inb(dev, DMM32AT_DACSTAT); + if ((status & DMM32AT_DACBUSY) == 0) + break; + } + if (i == 40000) { + printk(KERN_WARNING "dmm32at: timeout\n"); + return -ETIMEDOUT; + } + /* dummy read to update trigger the output */ + status = dmm_inb(dev, DMM32AT_DACMSB); + + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int dmm32at_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +static int dmm32at_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned char diobits; + + if (insn->n != 2) + return -EINVAL; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + /* Write out the new digital output lines */ + /* outw(s->state,dev->iobase + DMM32AT_DIO); */ + } + + /* get access to the DIO regs */ + dmm_outb(dev, DMM32AT_CNTRL, DMM32AT_DIOACC); + + /* if either part of dio is set for output */ + if (((devpriv->dio_config & DMM32AT_DIRCL) == 0) || + ((devpriv->dio_config & DMM32AT_DIRCH) == 0)) { + diobits = (s->state & 0x00ff0000) >> 16; + dmm_outb(dev, DMM32AT_DIOC, diobits); + } + if ((devpriv->dio_config & DMM32AT_DIRB) == 0) { + diobits = (s->state & 0x0000ff00) >> 8; + dmm_outb(dev, DMM32AT_DIOB, diobits); + } + if ((devpriv->dio_config & DMM32AT_DIRA) == 0) { + diobits = (s->state & 0x000000ff); + dmm_outb(dev, DMM32AT_DIOA, diobits); + } + + /* now read the state back in */ + s->state = dmm_inb(dev, DMM32AT_DIOC); + s->state <<= 8; + s->state |= dmm_inb(dev, DMM32AT_DIOB); + s->state <<= 8; + s->state |= dmm_inb(dev, DMM32AT_DIOA); + data[1] = s->state; + + /* on return, data[1] contains the value of the digital + * input and output lines. */ + /* data[1]=inw(dev->iobase + DMM32AT_DIO); */ + /* or we could just return the software copy of the output values if + * it was a purely digital output subdevice */ + /* data[1]=s->state; */ + + return 2; +} + +static int dmm32at_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned char chanbit; + int chan = CR_CHAN(insn->chanspec); + + if (insn->n != 1) + return -EINVAL; + + if (chan < 8) + chanbit = DMM32AT_DIRA; + else if (chan < 16) + chanbit = DMM32AT_DIRB; + else if (chan < 20) + chanbit = DMM32AT_DIRCL; + else + chanbit = DMM32AT_DIRCH; + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + + /* if output clear the bit, otherwise set it */ + if (data[0] == COMEDI_OUTPUT) + devpriv->dio_config &= ~chanbit; + else + devpriv->dio_config |= chanbit; + /* get access to the DIO regs */ + dmm_outb(dev, DMM32AT_CNTRL, DMM32AT_DIOACC); + /* set the DIO's to the new configuration setting */ + dmm_outb(dev, DMM32AT_DIOCONF, devpriv->dio_config); + + return 1; +} + +void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) +{ + unsigned char lo1, lo2, hi2; + unsigned short both2; + + /* based on 10mhz clock */ + lo1 = 200; + both2 = nansec / 20000; + hi2 = (both2 & 0xff00) >> 8; + lo2 = both2 & 0x00ff; + + /* set the counter frequency to 10mhz */ + dmm_outb(dev, DMM32AT_CNTRDIO, 0); + + /* get access to the clock regs */ + dmm_outb(dev, DMM32AT_CNTRL, DMM32AT_CLKACC); + + /* write the counter 1 control word and low byte to counter */ + dmm_outb(dev, DMM32AT_CLKCT, DMM32AT_CLKCT1); + dmm_outb(dev, DMM32AT_CLK1, lo1); + + /* write the counter 2 control word and low byte then to counter */ + dmm_outb(dev, DMM32AT_CLKCT, DMM32AT_CLKCT2); + dmm_outb(dev, DMM32AT_CLK2, lo2); + dmm_outb(dev, DMM32AT_CLK2, hi2); + + /* enable the ai conversion interrupt and the clock to start scans */ + dmm_outb(dev, DMM32AT_INTCLOCK, DMM32AT_ADINT | DMM32AT_CLKSEL); + +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __init driver_dmm32at_init_module(void) +{ + return comedi_driver_register(&driver_dmm32at); +} + +static void __exit driver_dmm32at_cleanup_module(void) +{ + comedi_driver_unregister(&driver_dmm32at); +} + +module_init(driver_dmm32at_init_module); +module_exit(driver_dmm32at_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt2801.c b/drivers/staging/comedi/drivers/dt2801.c new file mode 100644 index 00000000..b85c8366 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2801.c @@ -0,0 +1,745 @@ +/* + * comedi/drivers/dt2801.c + * Device Driver for DataTranslation DT2801 + * + */ +/* +Driver: dt2801 +Description: Data Translation DT2801 series and DT01-EZ +Author: ds +Status: works +Devices: [Data Translation] DT2801 (dt2801), DT2801-A, DT2801/5716A, + DT2805, DT2805/5716A, DT2808, DT2818, DT2809, DT01-EZ + +This driver can autoprobe the type of board. + +Configuration options: + [0] - I/O port base address + [1] - unused + [2] - A/D reference 0=differential, 1=single-ended + [3] - A/D range + 0 = [-10, 10] + 1 = [0,10] + [4] - D/A 0 range + 0 = [-10, 10] + 1 = [-5,5] + 2 = [-2.5,2.5] + 3 = [0,10] + 4 = [0,5] + [5] - D/A 1 range (same choices) +*/ + +#include "../comedidev.h" +#include +#include + +#define DT2801_TIMEOUT 1000 + +/* Hardware Configuration */ +/* ====================== */ + +#define DT2801_MAX_DMA_SIZE (64 * 1024) + +/* Ports */ +#define DT2801_IOSIZE 2 + +/* define's */ +/* ====================== */ + +/* Commands */ +#define DT_C_RESET 0x0 +#define DT_C_CLEAR_ERR 0x1 +#define DT_C_READ_ERRREG 0x2 +#define DT_C_SET_CLOCK 0x3 + +#define DT_C_TEST 0xb +#define DT_C_STOP 0xf + +#define DT_C_SET_DIGIN 0x4 +#define DT_C_SET_DIGOUT 0x5 +#define DT_C_READ_DIG 0x6 +#define DT_C_WRITE_DIG 0x7 + +#define DT_C_WRITE_DAIM 0x8 +#define DT_C_SET_DA 0x9 +#define DT_C_WRITE_DA 0xa + +#define DT_C_READ_ADIM 0xc +#define DT_C_SET_AD 0xd +#define DT_C_READ_AD 0xe + +/* Command modifiers (only used with read/write), EXTTRIG can be + used with some other commands. +*/ +#define DT_MOD_DMA (1<<4) +#define DT_MOD_CONT (1<<5) +#define DT_MOD_EXTCLK (1<<6) +#define DT_MOD_EXTTRIG (1<<7) + +/* Bits in status register */ +#define DT_S_DATA_OUT_READY (1<<0) +#define DT_S_DATA_IN_FULL (1<<1) +#define DT_S_READY (1<<2) +#define DT_S_COMMAND (1<<3) +#define DT_S_COMPOSITE_ERROR (1<<7) + +/* registers */ +#define DT2801_DATA 0 +#define DT2801_STATUS 1 +#define DT2801_CMD 1 + +static int dt2801_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dt2801_detach(struct comedi_device *dev); +static struct comedi_driver driver_dt2801 = { + .driver_name = "dt2801", + .module = THIS_MODULE, + .attach = dt2801_attach, + .detach = dt2801_detach, +}; + +static int __init driver_dt2801_init_module(void) +{ + return comedi_driver_register(&driver_dt2801); +} + +static void __exit driver_dt2801_cleanup_module(void) +{ + comedi_driver_unregister(&driver_dt2801); +} + +module_init(driver_dt2801_init_module); +module_exit(driver_dt2801_cleanup_module); + +#if 0 +/* ignore 'defined but not used' warning */ +static const struct comedi_lrange range_dt2801_ai_pgh_bipolar = { 4, { + RANGE(-10, + 10), + RANGE(-5, + 5), + RANGE + (-2.5, + 2.5), + RANGE + (-1.25, + 1.25), + } +}; +#endif +static const struct comedi_lrange range_dt2801_ai_pgl_bipolar = { 4, { + RANGE(-10, + 10), + RANGE(-1, + 1), + RANGE + (-0.1, + 0.1), + RANGE + (-0.02, + 0.02), + } +}; + +#if 0 +/* ignore 'defined but not used' warning */ +static const struct comedi_lrange range_dt2801_ai_pgh_unipolar = { 4, { + RANGE(0, + 10), + RANGE(0, + 5), + RANGE(0, + 2.5), + RANGE(0, + 1.25), + } +}; +#endif +static const struct comedi_lrange range_dt2801_ai_pgl_unipolar = { 4, { + RANGE(0, + 10), + RANGE(0, + 1), + RANGE(0, + 0.1), + RANGE(0, + 0.02), + } +}; + +struct dt2801_board { + + const char *name; + int boardcode; + int ad_diff; + int ad_chan; + int adbits; + int adrangetype; + int dabits; +}; + +/* Typeid's for the different boards of the DT2801-series + (taken from the test-software, that comes with the board) + */ +static const struct dt2801_board boardtypes[] = { + { + .name = "dt2801", + .boardcode = 0x09, + .ad_diff = 2, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2801-a", + .boardcode = 0x52, + .ad_diff = 2, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2801/5716a", + .boardcode = 0x82, + .ad_diff = 1, + .ad_chan = 16, + .adbits = 16, + .adrangetype = 1, + .dabits = 12}, + { + .name = "dt2805", + .boardcode = 0x12, + .ad_diff = 1, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2805/5716a", + .boardcode = 0x92, + .ad_diff = 1, + .ad_chan = 16, + .adbits = 16, + .adrangetype = 1, + .dabits = 12}, + { + .name = "dt2808", + .boardcode = 0x20, + .ad_diff = 0, + .ad_chan = 16, + .adbits = 12, + .adrangetype = 2, + .dabits = 8}, + { + .name = "dt2818", + .boardcode = 0xa2, + .ad_diff = 0, + .ad_chan = 4, + .adbits = 12, + .adrangetype = 0, + .dabits = 12}, + { + .name = "dt2809", + .boardcode = 0xb0, + .ad_diff = 0, + .ad_chan = 8, + .adbits = 12, + .adrangetype = 1, + .dabits = 12}, +}; + +#define boardtype (*(const struct dt2801_board *)dev->board_ptr) + +struct dt2801_private { + + const struct comedi_lrange *dac_range_types[2]; + unsigned int ao_readback[2]; +}; + +#define devpriv ((struct dt2801_private *)dev->private) + +static int dt2801_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dt2801_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dt2801_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dt2801_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dt2801_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* These are the low-level routines: + writecommand: write a command to the board + writedata: write data byte + readdata: read data byte + */ + +/* Only checks DataOutReady-flag, not the Ready-flag as it is done + in the examples of the manual. I don't see why this should be + necessary. */ +static int dt2801_readdata(struct comedi_device *dev, int *data) +{ + int stat = 0; + int timeout = DT2801_TIMEOUT; + + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & (DT_S_COMPOSITE_ERROR | DT_S_READY)) + return stat; + if (stat & DT_S_DATA_OUT_READY) { + *data = inb_p(dev->iobase + DT2801_DATA); + return 0; + } + } while (--timeout > 0); + + return -ETIME; +} + +static int dt2801_readdata2(struct comedi_device *dev, int *data) +{ + int lb, hb; + int ret; + + ret = dt2801_readdata(dev, &lb); + if (ret) + return ret; + ret = dt2801_readdata(dev, &hb); + if (ret) + return ret; + + *data = (hb << 8) + lb; + return 0; +} + +static int dt2801_writedata(struct comedi_device *dev, unsigned int data) +{ + int stat = 0; + int timeout = DT2801_TIMEOUT; + + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + + if (stat & DT_S_COMPOSITE_ERROR) + return stat; + if (!(stat & DT_S_DATA_IN_FULL)) { + outb_p(data & 0xff, dev->iobase + DT2801_DATA); + return 0; + } +#if 0 + if (stat & DT_S_READY) { + printk + ("dt2801: ready flag set (bad!) in dt2801_writedata()\n"); + return -EIO; + } +#endif + } while (--timeout > 0); + + return -ETIME; +} + +static int dt2801_writedata2(struct comedi_device *dev, unsigned int data) +{ + int ret; + + ret = dt2801_writedata(dev, data & 0xff); + if (ret < 0) + return ret; + ret = dt2801_writedata(dev, (data >> 8)); + if (ret < 0) + return ret; + + return 0; +} + +static int dt2801_wait_for_ready(struct comedi_device *dev) +{ + int timeout = DT2801_TIMEOUT; + int stat; + + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_READY) + return 0; + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + + if (stat & DT_S_COMPOSITE_ERROR) + return stat; + if (stat & DT_S_READY) + return 0; + } while (--timeout > 0); + + return -ETIME; +} + +static int dt2801_writecmd(struct comedi_device *dev, int command) +{ + int stat; + + dt2801_wait_for_ready(dev); + + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_COMPOSITE_ERROR) { + printk + ("dt2801: composite-error in dt2801_writecmd(), ignoring\n"); + } + if (!(stat & DT_S_READY)) + printk("dt2801: !ready in dt2801_writecmd(), ignoring\n"); + outb_p(command, dev->iobase + DT2801_CMD); + + return 0; +} + +static int dt2801_reset(struct comedi_device *dev) +{ + int board_code = 0; + unsigned int stat; + int timeout; + + DPRINTK("dt2801: resetting board...\n"); + DPRINTK("fingerprint: 0x%02x 0x%02x\n", inb_p(dev->iobase), + inb_p(dev->iobase + 1)); + + /* pull random data from data port */ + inb_p(dev->iobase + DT2801_DATA); + inb_p(dev->iobase + DT2801_DATA); + inb_p(dev->iobase + DT2801_DATA); + inb_p(dev->iobase + DT2801_DATA); + + DPRINTK("dt2801: stop\n"); + /* dt2801_writecmd(dev,DT_C_STOP); */ + outb_p(DT_C_STOP, dev->iobase + DT2801_CMD); + + /* dt2801_wait_for_ready(dev); */ + udelay(100); + timeout = 10000; + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_READY) + break; + } while (timeout--); + if (!timeout) + printk("dt2801: timeout 1 status=0x%02x\n", stat); + + /* printk("dt2801: reading dummy\n"); */ + /* dt2801_readdata(dev,&board_code); */ + + DPRINTK("dt2801: reset\n"); + outb_p(DT_C_RESET, dev->iobase + DT2801_CMD); + /* dt2801_writecmd(dev,DT_C_RESET); */ + + udelay(100); + timeout = 10000; + do { + stat = inb_p(dev->iobase + DT2801_STATUS); + if (stat & DT_S_READY) + break; + } while (timeout--); + if (!timeout) + printk("dt2801: timeout 2 status=0x%02x\n", stat); + + DPRINTK("dt2801: reading code\n"); + dt2801_readdata(dev, &board_code); + + DPRINTK("dt2801: ok. code=0x%02x\n", board_code); + + return board_code; +} + +static int probe_number_of_ai_chans(struct comedi_device *dev) +{ + int n_chans; + int stat; + int data; + + for (n_chans = 0; n_chans < 16; n_chans++) { + stat = dt2801_writecmd(dev, DT_C_READ_ADIM); + dt2801_writedata(dev, 0); + dt2801_writedata(dev, n_chans); + stat = dt2801_readdata2(dev, &data); + + if (stat) + break; + } + + dt2801_reset(dev); + dt2801_reset(dev); + + return n_chans; +} + +static const struct comedi_lrange *dac_range_table[] = { + &range_bipolar10, + &range_bipolar5, + &range_bipolar2_5, + &range_unipolar10, + &range_unipolar5 +}; + +static const struct comedi_lrange *dac_range_lkup(int opt) +{ + if (opt < 0 || opt >= 5) + return &range_unknown; + return dac_range_table[opt]; +} + +static const struct comedi_lrange *ai_range_lkup(int type, int opt) +{ + switch (type) { + case 0: + return (opt) ? + &range_dt2801_ai_pgl_unipolar : + &range_dt2801_ai_pgl_bipolar; + case 1: + return (opt) ? &range_unipolar10 : &range_bipolar10; + case 2: + return &range_unipolar5; + } + return &range_unknown; +} + +/* + options: + [0] - i/o base + [1] - unused + [2] - a/d 0=differential, 1=single-ended + [3] - a/d range 0=[-10,10], 1=[0,10] + [4] - dac0 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5] + [5] - dac1 range 0=[-10,10], 1=[-5,5], 2=[-2.5,2.5] 3=[0,10], 4=[0,5] +*/ +static int dt2801_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + int board_code, type; + int ret = 0; + int n_ai_chans; + + iobase = it->options[0]; + if (!request_region(iobase, DT2801_IOSIZE, "dt2801")) { + comedi_error(dev, "I/O port conflict"); + return -EIO; + } + dev->iobase = iobase; + + /* do some checking */ + + board_code = dt2801_reset(dev); + + /* heh. if it didn't work, try it again. */ + if (!board_code) + board_code = dt2801_reset(dev); + + for (type = 0; type < ARRAY_SIZE(boardtypes); type++) { + if (boardtypes[type].boardcode == board_code) + goto havetype; + } + printk("dt2801: unrecognized board code=0x%02x, contact author\n", + board_code); + type = 0; + +havetype: + dev->board_ptr = boardtypes + type; + printk("dt2801: %s at port 0x%lx", boardtype.name, iobase); + + n_ai_chans = probe_number_of_ai_chans(dev); + printk(" (ai channels = %d)", n_ai_chans); + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + goto out; + + ret = alloc_private(dev, sizeof(struct dt2801_private)); + if (ret < 0) + goto out; + + dev->board_name = boardtype.name; + + s = dev->subdevices + 0; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; +#if 1 + s->n_chan = n_ai_chans; +#else + if (it->options[2]) + s->n_chan = boardtype.ad_chan; + else + s->n_chan = boardtype.ad_chan / 2; +#endif + s->maxdata = (1 << boardtype.adbits) - 1; + s->range_table = ai_range_lkup(boardtype.adrangetype, it->options[3]); + s->insn_read = dt2801_ai_insn_read; + + s++; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << boardtype.dabits) - 1; + s->range_table_list = devpriv->dac_range_types; + devpriv->dac_range_types[0] = dac_range_lkup(it->options[4]); + devpriv->dac_range_types[1] = dac_range_lkup(it->options[5]); + s->insn_read = dt2801_ao_insn_read; + s->insn_write = dt2801_ao_insn_write; + + s++; + /* 1st digital subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt2801_dio_insn_bits; + s->insn_config = dt2801_dio_insn_config; + + s++; + /* 2nd digital subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dt2801_dio_insn_bits; + s->insn_config = dt2801_dio_insn_config; + + ret = 0; +out: + printk("\n"); + + return ret; +} + +static int dt2801_detach(struct comedi_device *dev) +{ + if (dev->iobase) + release_region(dev->iobase, DT2801_IOSIZE); + + return 0; +} + +static int dt2801_error(struct comedi_device *dev, int stat) +{ + if (stat < 0) { + if (stat == -ETIME) + printk("dt2801: timeout\n"); + else + printk("dt2801: error %d\n", stat); + return stat; + } + printk("dt2801: error status 0x%02x, resetting...\n", stat); + + dt2801_reset(dev); + dt2801_reset(dev); + + return -EIO; +} + +static int dt2801_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int d; + int stat; + int i; + + for (i = 0; i < insn->n; i++) { + stat = dt2801_writecmd(dev, DT_C_READ_ADIM); + dt2801_writedata(dev, CR_RANGE(insn->chanspec)); + dt2801_writedata(dev, CR_CHAN(insn->chanspec)); + stat = dt2801_readdata2(dev, &d); + + if (stat != 0) + return dt2801_error(dev, stat); + + data[i] = d; + } + + return i; +} + +static int dt2801_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int dt2801_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + dt2801_writecmd(dev, DT_C_WRITE_DAIM); + dt2801_writedata(dev, CR_CHAN(insn->chanspec)); + dt2801_writedata2(dev, data[0]); + + devpriv->ao_readback[CR_CHAN(insn->chanspec)] = data[0]; + + return 1; +} + +static int dt2801_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int which = 0; + + if (s == dev->subdevices + 4) + which = 1; + + if (insn->n != 2) + return -EINVAL; + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + dt2801_writecmd(dev, DT_C_WRITE_DIG); + dt2801_writedata(dev, which); + dt2801_writedata(dev, s->state); + } + dt2801_writecmd(dev, DT_C_READ_DIG); + dt2801_writedata(dev, which); + dt2801_readdata(dev, data + 1); + + return 2; +} + +static int dt2801_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int which = 0; + + if (s == dev->subdevices + 4) + which = 1; + + /* configure */ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits = 0xff; + dt2801_writecmd(dev, DT_C_SET_DIGOUT); + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits = 0; + dt2801_writecmd(dev, DT_C_SET_DIGIN); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = s->io_bits ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + default: + return -EINVAL; + } + dt2801_writedata(dev, which); + + return 1; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt2811.c b/drivers/staging/comedi/drivers/dt2811.c new file mode 100644 index 00000000..0131d522 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2811.c @@ -0,0 +1,642 @@ +/* + comedi/drivers/dt2811.c + Hardware driver for Data Translation DT2811 + + COMEDI - Linux Control and Measurement Device Interface + History: + Base Version - David A. Schleef + December 1998 - Updated to work. David does not have a DT2811 + board any longer so this was suffering from bitrot. + Updated performed by ... + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/* +Driver: dt2811 +Description: Data Translation DT2811 +Author: ds +Devices: [Data Translation] DT2811-PGL (dt2811-pgl), DT2811-PGH (dt2811-pgh) +Status: works + +Configuration options: + [0] - I/O port base address + [1] - IRQ, although this is currently unused + [2] - A/D reference + 0 = signle-ended + 1 = differential + 2 = pseudo-differential (common reference) + [3] - A/D range + 0 = [-5, 5] + 1 = [-2.5, 2.5] + 2 = [0, 5] + [4] - D/A 0 range (same choices) + [4] - D/A 1 range (same choices) +*/ + +#include +#include "../comedidev.h" + +#include + +static const char *driver_name = "dt2811"; + +static const struct comedi_lrange range_dt2811_pgh_ai_5_unipolar = { + 4, { + RANGE(0, 5), + RANGE(0, 2.5), + RANGE(0, 1.25), + RANGE(0, 0.625) + } +}; + +static const struct comedi_lrange range_dt2811_pgh_ai_2_5_bipolar = { + 4, { + RANGE(-2.5, 2.5), + RANGE(-1.25, 1.25), + RANGE(-0.625, 0.625), + RANGE(-0.3125, 0.3125) + } +}; + +static const struct comedi_lrange range_dt2811_pgh_ai_5_bipolar = { + 4, { + RANGE(-5, 5), + RANGE(-2.5, 2.5), + RANGE(-1.25, 1.25), + RANGE(-0.625, 0.625) + } +}; + +static const struct comedi_lrange range_dt2811_pgl_ai_5_unipolar = { + 4, { + RANGE(0, 5), + RANGE(0, 0.5), + RANGE(0, 0.05), + RANGE(0, 0.01) + } +}; + +static const struct comedi_lrange range_dt2811_pgl_ai_2_5_bipolar = { + 4, { + RANGE(-2.5, 2.5), + RANGE(-0.25, 0.25), + RANGE(-0.025, 0.025), + RANGE(-0.005, 0.005) + } +}; + +static const struct comedi_lrange range_dt2811_pgl_ai_5_bipolar = { + 4, { + RANGE(-5, 5), + RANGE(-0.5, 0.5), + RANGE(-0.05, 0.05), + RANGE(-0.01, 0.01) + } +}; + +/* + + 0x00 ADCSR R/W A/D Control/Status Register + bit 7 - (R) 1 indicates A/D conversion done + reading ADDAT clears bit + (W) ignored + bit 6 - (R) 1 indicates A/D error + (W) ignored + bit 5 - (R) 1 indicates A/D busy, cleared at end + of conversion + (W) ignored + bit 4 - (R) 0 + (W) + bit 3 - (R) 0 + bit 2 - (R/W) 1 indicates interrupts enabled + bits 1,0 - (R/W) mode bits + 00 single conversion on ADGCR load + 01 continuous conversion, internal clock, + (clock enabled on ADGCR load) + 10 continuous conversion, internal clock, + external trigger + 11 continuous conversion, external clock, + external trigger + + 0x01 ADGCR R/W A/D Gain/Channel Register + bit 6,7 - (R/W) gain select + 00 gain=1, both PGH, PGL models + 01 gain=2 PGH, 10 PGL + 10 gain=4 PGH, 100 PGL + 11 gain=8 PGH, 500 PGL + bit 4,5 - reserved + bit 3-0 - (R/W) channel select + channel number from 0-15 + + 0x02,0x03 (R) ADDAT A/D Data Register + (W) DADAT0 D/A Data Register 0 + 0x02 low byte + 0x03 high byte + + 0x04,0x05 (W) DADAT0 D/A Data Register 1 + + 0x06 (R) DIO0 Digital Input Port 0 + (W) DIO1 Digital Output Port 1 + + 0x07 TMRCTR (R/W) Timer/Counter Register + bits 6,7 - reserved + bits 5-3 - Timer frequency control (mantissa) + 543 divisor freqency (kHz) + 000 1 600 + 001 10 60 + 010 2 300 + 011 3 200 + 100 4 150 + 101 5 120 + 110 6 100 + 111 12 50 + bits 2-0 - Timer frequency control (exponent) + 210 multiply divisor/divide frequency by + 000 1 + 001 10 + 010 100 + 011 1000 + 100 10000 + 101 100000 + 110 1000000 + 111 10000000 + + */ + +#define TIMEOUT 10000 + +#define DT2811_SIZE 8 + +#define DT2811_ADCSR 0 +#define DT2811_ADGCR 1 +#define DT2811_ADDATLO 2 +#define DT2811_ADDATHI 3 +#define DT2811_DADAT0LO 2 +#define DT2811_DADAT0HI 3 +#define DT2811_DADAT1LO 4 +#define DT2811_DADAT1HI 5 +#define DT2811_DIO 6 +#define DT2811_TMRCTR 7 + +/* + * flags + */ + +/* ADCSR */ + +#define DT2811_ADDONE 0x80 +#define DT2811_ADERROR 0x40 +#define DT2811_ADBUSY 0x20 +#define DT2811_CLRERROR 0x10 +#define DT2811_INTENB 0x04 +#define DT2811_ADMODE 0x03 + +struct dt2811_board { + + const char *name; + const struct comedi_lrange *bip_5; + const struct comedi_lrange *bip_2_5; + const struct comedi_lrange *unip_5; +}; + +static const struct dt2811_board boardtypes[] = { + {"dt2811-pgh", + &range_dt2811_pgh_ai_5_bipolar, + &range_dt2811_pgh_ai_2_5_bipolar, + &range_dt2811_pgh_ai_5_unipolar, + }, + {"dt2811-pgl", + &range_dt2811_pgl_ai_5_bipolar, + &range_dt2811_pgl_ai_2_5_bipolar, + &range_dt2811_pgl_ai_5_unipolar, + }, +}; + +#define this_board ((const struct dt2811_board *)dev->board_ptr) + +static int dt2811_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dt2811_detach(struct comedi_device *dev); +static struct comedi_driver driver_dt2811 = { + .driver_name = "dt2811", + .module = THIS_MODULE, + .attach = dt2811_attach, + .detach = dt2811_detach, + .board_name = &boardtypes[0].name, + .num_names = ARRAY_SIZE(boardtypes), + .offset = sizeof(struct dt2811_board), +}; + +static int __init driver_dt2811_init_module(void) +{ + return comedi_driver_register(&driver_dt2811); +} + +static void __exit driver_dt2811_cleanup_module(void) +{ + comedi_driver_unregister(&driver_dt2811); +} + +module_init(driver_dt2811_init_module); +module_exit(driver_dt2811_cleanup_module); + +static int dt2811_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dt2811_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dt2811_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dt2811_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int dt2811_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +enum { card_2811_pgh, card_2811_pgl }; + +struct dt2811_private { + int ntrig; + int curadchan; + enum { + adc_singleended, adc_diff, adc_pseudo_diff + } adc_mux; + enum { + dac_bipolar_5, dac_bipolar_2_5, dac_unipolar_5 + } dac_range[2]; + const struct comedi_lrange *range_type_list[2]; + unsigned int ao_readback[2]; +}; + +#define devpriv ((struct dt2811_private *)dev->private) + +static const struct comedi_lrange *dac_range_types[] = { + &range_bipolar5, + &range_bipolar2_5, + &range_unipolar5 +}; + +#define DT2811_TIMEOUT 5 + +#if 0 +static irqreturn_t dt2811_interrupt(int irq, void *d) +{ + int lo, hi; + int data; + struct comedi_device *dev = d; + + if (!dev->attached) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + lo = inb(dev->iobase + DT2811_ADDATLO); + hi = inb(dev->iobase + DT2811_ADDATHI); + + data = lo + (hi << 8); + + if (!(--devpriv->ntrig)) { + /* how to turn off acquisition */ + s->async->events |= COMEDI_SB_EOA; + } + comedi_event(dev, s); + return IRQ_HANDLED; +} +#endif + +/* + options[0] Board base address + options[1] IRQ + options[2] Input configuration + 0 == single-ended + 1 == differential + 2 == pseudo-differential + options[3] Analog input range configuration + 0 == bipolar 5 (-5V -- +5V) + 1 == bipolar 2.5V (-2.5V -- +2.5V) + 2 == unipolar 5V (0V -- +5V) + options[4] Analog output 0 range configuration + 0 == bipolar 5 (-5V -- +5V) + 1 == bipolar 2.5V (-2.5V -- +2.5V) + 2 == unipolar 5V (0V -- +5V) + options[5] Analog output 1 range configuration + 0 == bipolar 5 (-5V -- +5V) + 1 == bipolar 2.5V (-2.5V -- +2.5V) + 2 == unipolar 5V (0V -- +5V) +*/ + +static int dt2811_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + /* int i, irq; */ + /* unsigned long irqs; */ + /* long flags; */ + + int ret; + struct comedi_subdevice *s; + unsigned long iobase; + + iobase = it->options[0]; + + printk(KERN_INFO "comedi%d: dt2811:base=0x%04lx\n", dev->minor, iobase); + + if (!request_region(iobase, DT2811_SIZE, driver_name)) { + printk(KERN_ERR "I/O port conflict\n"); + return -EIO; + } + + dev->iobase = iobase; + dev->board_name = this_board->name; + +#if 0 + outb(0, dev->iobase + DT2811_ADCSR); + udelay(100); + i = inb(dev->iobase + DT2811_ADDATLO); + i = inb(dev->iobase + DT2811_ADDATHI); +#endif + +#if 0 + irq = it->options[1]; + if (irq < 0) { + save_flags(flags); + sti(); + irqs = probe_irq_on(); + + outb(DT2811_CLRERROR | DT2811_INTENB, + dev->iobase + DT2811_ADCSR); + outb(0, dev->iobase + DT2811_ADGCR); + + udelay(100); + + irq = probe_irq_off(irqs); + restore_flags(flags); + + /*outb(DT2811_CLRERROR|DT2811_INTENB, + dev->iobase+DT2811_ADCSR);*/ + + if (inb(dev->iobase + DT2811_ADCSR) & DT2811_ADERROR) + printk(KERN_ERR "error probing irq (bad)\n"); + dev->irq = 0; + if (irq > 0) { + i = inb(dev->iobase + DT2811_ADDATLO); + i = inb(dev->iobase + DT2811_ADDATHI); + printk(KERN_INFO "(irq = %d)\n", irq); + ret = request_irq(irq, dt2811_interrupt, 0, + driver_name, dev); + if (ret < 0) + return -EIO; + dev->irq = irq; + } else if (irq == 0) { + printk(KERN_INFO "(no irq)\n"); + } else { + printk(KERN_ERR "( multiple irq's -- this is bad! )\n"); + } + } +#endif + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + ret = alloc_private(dev, sizeof(struct dt2811_private)); + if (ret < 0) + return ret; + + switch (it->options[2]) { + case 0: + devpriv->adc_mux = adc_singleended; + break; + case 1: + devpriv->adc_mux = adc_diff; + break; + case 2: + devpriv->adc_mux = adc_pseudo_diff; + break; + default: + devpriv->adc_mux = adc_singleended; + break; + } + switch (it->options[4]) { + case 0: + devpriv->dac_range[0] = dac_bipolar_5; + break; + case 1: + devpriv->dac_range[0] = dac_bipolar_2_5; + break; + case 2: + devpriv->dac_range[0] = dac_unipolar_5; + break; + default: + devpriv->dac_range[0] = dac_bipolar_5; + break; + } + switch (it->options[5]) { + case 0: + devpriv->dac_range[1] = dac_bipolar_5; + break; + case 1: + devpriv->dac_range[1] = dac_bipolar_2_5; + break; + case 2: + devpriv->dac_range[1] = dac_unipolar_5; + break; + default: + devpriv->dac_range[1] = dac_bipolar_5; + break; + } + + s = dev->subdevices + 0; + /* initialize the ADC subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = devpriv->adc_mux == adc_diff ? 8 : 16; + s->insn_read = dt2811_ai_insn; + s->maxdata = 0xfff; + switch (it->options[3]) { + case 0: + default: + s->range_table = this_board->bip_5; + break; + case 1: + s->range_table = this_board->bip_2_5; + break; + case 2: + s->range_table = this_board->unip_5; + break; + } + + s = dev->subdevices + 1; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->insn_write = dt2811_ao_insn; + s->insn_read = dt2811_ao_insn_read; + s->maxdata = 0xfff; + s->range_table_list = devpriv->range_type_list; + devpriv->range_type_list[0] = dac_range_types[devpriv->dac_range[0]]; + devpriv->range_type_list[1] = dac_range_types[devpriv->dac_range[1]]; + + s = dev->subdevices + 2; + /* di subdevice */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->insn_bits = dt2811_di_insn_bits; + s->maxdata = 1; + s->range_table = &range_digital; + + s = dev->subdevices + 3; + /* do subdevice */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->insn_bits = dt2811_do_insn_bits; + s->maxdata = 1; + s->state = 0; + s->range_table = &range_digital; + + return 0; +} + +static int dt2811_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: dt2811: remove\n", dev->minor); + + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->iobase) + release_region(dev->iobase, DT2811_SIZE); + + return 0; +} + +static int dt2811_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + int timeout = DT2811_TIMEOUT; + int i; + + for (i = 0; i < insn->n; i++) { + outb(chan, dev->iobase + DT2811_ADGCR); + + while (timeout + && inb(dev->iobase + DT2811_ADCSR) & DT2811_ADBUSY) + timeout--; + if (!timeout) + return -ETIME; + + data[i] = inb(dev->iobase + DT2811_ADDATLO); + data[i] |= inb(dev->iobase + DT2811_ADDATHI) << 8; + data[i] &= 0xfff; + } + + return i; +} + +#if 0 +/* Wow. This is code from the Comedi stone age. But it hasn't been + * replaced, so I'll let it stay. */ +int dt2811_adtrig(kdev_t minor, comedi_adtrig *adtrig) +{ + struct comedi_device *dev = comedi_devices + minor; + + if (adtrig->n < 1) + return 0; + dev->curadchan = adtrig->chan; + switch (dev->i_admode) { + case COMEDI_MDEMAND: + dev->ntrig = adtrig->n - 1; + /* not necessary */ + /*printk("dt2811: AD soft trigger\n"); */ + /*outb(DT2811_CLRERROR|DT2811_INTENB, + dev->iobase+DT2811_ADCSR); */ + outb(dev->curadchan, dev->iobase + DT2811_ADGCR); + do_gettimeofday(&trigtime); + break; + case COMEDI_MCONTS: + dev->ntrig = adtrig->n; + break; + } + + return 0; +} +#endif + +static int dt2811_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + outb(data[i] & 0xff, dev->iobase + DT2811_DADAT0LO + 2 * chan); + outb((data[i] >> 8) & 0xff, + dev->iobase + DT2811_DADAT0HI + 2 * chan); + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +static int dt2811_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan; + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int dt2811_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + DT2811_DIO); + + return 2; +} + +static int dt2811_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + outb(s->state, dev->iobase + DT2811_DIO); + + data[1] = s->state; + + return 2; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt2814.c b/drivers/staging/comedi/drivers/dt2814.c new file mode 100644 index 00000000..1c6248cf --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2814.c @@ -0,0 +1,404 @@ +/* + comedi/drivers/dt2814.c + Hardware driver for Data Translation DT2814 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: dt2814 +Description: Data Translation DT2814 +Author: ds +Status: complete +Devices: [Data Translation] DT2814 (dt2814) + +Configuration options: + [0] - I/O port base address + [1] - IRQ + +This card has 16 analog inputs multiplexed onto a 12 bit ADC. There +is a minimally useful onboard clock. The base frequency for the +clock is selected by jumpers, and the clock divider can be selected +via programmed I/O. Unfortunately, the clock divider can only be +a power of 10, from 1 to 10^7, of which only 3 or 4 are useful. In +addition, the clock does not seem to be very accurate. +*/ + +#include +#include "../comedidev.h" + +#include +#include + +#define DT2814_SIZE 2 + +#define DT2814_CSR 0 +#define DT2814_DATA 1 + +/* + * flags + */ + +#define DT2814_FINISH 0x80 +#define DT2814_ERR 0x40 +#define DT2814_BUSY 0x20 +#define DT2814_ENB 0x10 +#define DT2814_CHANMASK 0x0f + +static int dt2814_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dt2814_detach(struct comedi_device *dev); +static struct comedi_driver driver_dt2814 = { + .driver_name = "dt2814", + .module = THIS_MODULE, + .attach = dt2814_attach, + .detach = dt2814_detach, +}; + +static int __init driver_dt2814_init_module(void) +{ + return comedi_driver_register(&driver_dt2814); +} + +static void __exit driver_dt2814_cleanup_module(void) +{ + comedi_driver_unregister(&driver_dt2814); +} + +module_init(driver_dt2814_init_module); +module_exit(driver_dt2814_cleanup_module); + +static irqreturn_t dt2814_interrupt(int irq, void *dev); + +struct dt2814_private { + + int ntrig; + int curadchan; +}; + +#define devpriv ((struct dt2814_private *)dev->private) + +#define DT2814_TIMEOUT 10 +#define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */ + +static int dt2814_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, i, hi, lo; + int chan; + int status = 0; + + for (n = 0; n < insn->n; n++) { + chan = CR_CHAN(insn->chanspec); + + outb(chan, dev->iobase + DT2814_CSR); + for (i = 0; i < DT2814_TIMEOUT; i++) { + status = inb(dev->iobase + DT2814_CSR); + printk(KERN_INFO "dt2814: status: %02x\n", status); + udelay(10); + if (status & DT2814_FINISH) + break; + } + if (i >= DT2814_TIMEOUT) { + printk(KERN_INFO "dt2814: status: %02x\n", status); + return -ETIMEDOUT; + } + + hi = inb(dev->iobase + DT2814_DATA); + lo = inb(dev->iobase + DT2814_DATA); + + data[n] = (hi << 4) | (lo >> 4); + } + + return n; +} + +static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags) +{ + int i; + unsigned int f; + + /* XXX ignores flags */ + + f = 10000; /* ns */ + for (i = 0; i < 8; i++) { + if ((2 * (*ns)) < (f * 11)) + break; + f *= 10; + } + + *ns = f; + + return i; +} + +static int dt2814_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are + * unique and mutually compatible */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->stop_src != TRIG_TIMER && cmd->stop_src != TRIG_EXT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_arg > 1000000000) { + cmd->scan_begin_arg = 1000000000; + err++; + } + if (cmd->scan_begin_arg < DT2814_MAX_SPEED) { + cmd->scan_begin_arg = DT2814_MAX_SPEED; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg < 2) { + cmd->stop_arg = 2; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + tmp = cmd->scan_begin_arg; + dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + + if (err) + return 4; + + return 0; +} + +static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int chan; + int trigvar; + + trigvar = + dt2814_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + + chan = CR_CHAN(cmd->chanlist[0]); + + devpriv->ntrig = cmd->stop_arg; + outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR); + + return 0; + +} + +static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int i, irq; + int ret; + struct comedi_subdevice *s; + unsigned long iobase; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: dt2814: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, DT2814_SIZE, "dt2814")) { + printk(KERN_ERR "I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + dev->board_name = "dt2814"; + + outb(0, dev->iobase + DT2814_CSR); + udelay(100); + if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) { + printk(KERN_ERR "reset error (fatal)\n"); + return -EIO; + } + i = inb(dev->iobase + DT2814_DATA); + i = inb(dev->iobase + DT2814_DATA); + + irq = it->options[1]; +#if 0 + if (irq < 0) { + save_flags(flags); + sti(); + irqs = probe_irq_on(); + + outb(0, dev->iobase + DT2814_CSR); + + udelay(100); + + irq = probe_irq_off(irqs); + restore_flags(flags); + if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) + printk(KERN_DEBUG "error probing irq (bad)\n"); + + + i = inb(dev->iobase + DT2814_DATA); + i = inb(dev->iobase + DT2814_DATA); + } +#endif + dev->irq = 0; + if (irq > 0) { + if (request_irq(irq, dt2814_interrupt, 0, "dt2814", dev)) { + printk(KERN_WARNING "(irq %d unavailable)\n", irq); + } else { + printk(KERN_INFO "( irq = %d )\n", irq); + dev->irq = irq; + } + } else if (irq == 0) { + printk(KERN_WARNING "(no irq)\n"); + } else { +#if 0 + printk(KERN_DEBUG "(probe returned multiple irqs--bad)\n"); +#else + printk(KERN_WARNING "(irq probe not implemented)\n"); +#endif + } + + ret = alloc_subdevices(dev, 1); + if (ret < 0) + return ret; + + ret = alloc_private(dev, sizeof(struct dt2814_private)); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + s->n_chan = 16; /* XXX */ + s->len_chanlist = 1; + s->insn_read = dt2814_ai_insn_read; + s->do_cmd = dt2814_ai_cmd; + s->do_cmdtest = dt2814_ai_cmdtest; + s->maxdata = 0xfff; + s->range_table = &range_unknown; /* XXX */ + + return 0; +} + +static int dt2814_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: dt2814: remove\n", dev->minor); + + if (dev->irq) + free_irq(dev->irq, dev); + + if (dev->iobase) + release_region(dev->iobase, DT2814_SIZE); + + return 0; +} + +static irqreturn_t dt2814_interrupt(int irq, void *d) +{ + int lo, hi; + struct comedi_device *dev = d; + struct comedi_subdevice *s; + int data; + + if (!dev->attached) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + s = dev->subdevices + 0; + + hi = inb(dev->iobase + DT2814_DATA); + lo = inb(dev->iobase + DT2814_DATA); + + data = (hi << 4) | (lo >> 4); + + if (!(--devpriv->ntrig)) { + int i; + + outb(0, dev->iobase + DT2814_CSR); + /* note: turning off timed mode triggers another + sample. */ + + for (i = 0; i < DT2814_TIMEOUT; i++) { + if (inb(dev->iobase + DT2814_CSR) & DT2814_FINISH) + break; + } + inb(dev->iobase + DT2814_DATA); + inb(dev->iobase + DT2814_DATA); + + s->async->events |= COMEDI_CB_EOA; + } + comedi_event(dev, s); + return IRQ_HANDLED; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt2815.c b/drivers/staging/comedi/drivers/dt2815.c new file mode 100644 index 00000000..4155da43 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2815.c @@ -0,0 +1,272 @@ +/* + comedi/drivers/dt2815.c + Hardware driver for Data Translation DT2815 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999 Anders Blomdell + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: dt2815 +Description: Data Translation DT2815 +Author: ds +Status: mostly complete, untested +Devices: [Data Translation] DT2815 (dt2815) + +I'm not sure anyone has ever tested this board. If you have information +contrary, please update. + +Configuration options: + [0] - I/O port base base address + [1] - IRQ (unused) + [2] - Voltage unipolar/bipolar configuration + 0 == unipolar 5V (0V -- +5V) + 1 == bipolar 5V (-5V -- +5V) + [3] - Current offset configuration + 0 == disabled (0mA -- +32mAV) + 1 == enabled (+4mA -- +20mAV) + [4] - Firmware program configuration + 0 == program 1 (see manual table 5-4) + 1 == program 2 (see manual table 5-4) + 2 == program 3 (see manual table 5-4) + 3 == program 4 (see manual table 5-4) + [5] - Analog output 0 range configuration + 0 == voltage + 1 == current + [6] - Analog output 1 range configuration (same options) + [7] - Analog output 2 range configuration (same options) + [8] - Analog output 3 range configuration (same options) + [9] - Analog output 4 range configuration (same options) + [10] - Analog output 5 range configuration (same options) + [11] - Analog output 6 range configuration (same options) + [12] - Analog output 7 range configuration (same options) +*/ + +#include "../comedidev.h" + +#include +#include + +static const struct comedi_lrange + range_dt2815_ao_32_current = {1, {RANGE_mA(0, 32)} }; + +static const struct comedi_lrange + range_dt2815_ao_20_current = {1, {RANGE_mA(4, 20)} }; + +#define DT2815_SIZE 2 + +#define DT2815_DATA 0 +#define DT2815_STATUS 1 + +static int dt2815_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dt2815_detach(struct comedi_device *dev); +static struct comedi_driver driver_dt2815 = { + .driver_name = "dt2815", + .module = THIS_MODULE, + .attach = dt2815_attach, + .detach = dt2815_detach, +}; + +static int __init driver_dt2815_init_module(void) +{ + return comedi_driver_register(&driver_dt2815); +} + +static void __exit driver_dt2815_cleanup_module(void) +{ + comedi_driver_unregister(&driver_dt2815); +} + +module_init(driver_dt2815_init_module); +module_exit(driver_dt2815_cleanup_module); + +static void dt2815_free_resources(struct comedi_device *dev); + +struct dt2815_private { + + const struct comedi_lrange *range_type_list[8]; + unsigned int ao_readback[8]; +}; + +#define devpriv ((struct dt2815_private *)dev->private) + +static int dt2815_wait_for_status(struct comedi_device *dev, int status) +{ + int i; + + for (i = 0; i < 100; i++) { + if (inb(dev->iobase + DT2815_STATUS) == status) + break; + } + return status; +} + +static int dt2815_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int dt2815_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + unsigned int status; + unsigned int lo, hi; + + for (i = 0; i < insn->n; i++) { + lo = ((data[i] & 0x0f) << 4) | (chan << 1) | 0x01; + hi = (data[i] & 0xff0) >> 4; + + status = dt2815_wait_for_status(dev, 0x00); + if (status != 0) { + printk(KERN_WARNING "dt2815: failed to write low byte " + "on %d reason %x\n", chan, status); + return -EBUSY; + } + + outb(lo, dev->iobase + DT2815_DATA); + + status = dt2815_wait_for_status(dev, 0x10); + if (status != 0x10) { + printk(KERN_WARNING "dt2815: failed to write high byte " + "on %d reason %x\n", chan, status); + return -EBUSY; + } + devpriv->ao_readback[chan] = data[i]; + } + return i; +} + +/* + options[0] Board base address + options[1] IRQ (not applicable) + options[2] Voltage unipolar/bipolar configuration + 0 == unipolar 5V (0V -- +5V) + 1 == bipolar 5V (-5V -- +5V) + options[3] Current offset configuration + 0 == disabled (0mA -- +32mAV) + 1 == enabled (+4mA -- +20mAV) + options[4] Firmware program configuration + 0 == program 1 (see manual table 5-4) + 1 == program 2 (see manual table 5-4) + 2 == program 3 (see manual table 5-4) + 3 == program 4 (see manual table 5-4) + options[5] Analog output 0 range configuration + 0 == voltage + 1 == current + options[6] Analog output 1 range configuration + ... + options[12] Analog output 7 range configuration + 0 == voltage + 1 == current + */ + +static int dt2815_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int i; + const struct comedi_lrange *current_range_type, *voltage_range_type; + unsigned long iobase; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: dt2815: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, DT2815_SIZE, "dt2815")) { + printk(KERN_WARNING "I/O port conflict\n"); + return -EIO; + } + + dev->iobase = iobase; + dev->board_name = "dt2815"; + + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + if (alloc_private(dev, sizeof(struct dt2815_private)) < 0) + return -ENOMEM; + + s = dev->subdevices; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 0xfff; + s->n_chan = 8; + s->insn_write = dt2815_ao_insn; + s->insn_read = dt2815_ao_insn_read; + s->range_table_list = devpriv->range_type_list; + + current_range_type = (it->options[3]) + ? &range_dt2815_ao_20_current : &range_dt2815_ao_32_current; + voltage_range_type = (it->options[2]) + ? &range_bipolar5 : &range_unipolar5; + for (i = 0; i < 8; i++) { + devpriv->range_type_list[i] = (it->options[5 + i]) + ? current_range_type : voltage_range_type; + } + + /* Init the 2815 */ + outb(0x00, dev->iobase + DT2815_STATUS); + for (i = 0; i < 100; i++) { + /* This is incredibly slow (approx 20 ms) */ + unsigned int status; + + udelay(1000); + status = inb(dev->iobase + DT2815_STATUS); + if (status == 4) { + unsigned int program; + program = (it->options[4] & 0x3) << 3 | 0x7; + outb(program, dev->iobase + DT2815_DATA); + printk(KERN_INFO ", program: 0x%x (@t=%d)\n", + program, i); + break; + } else if (status != 0x00) { + printk(KERN_WARNING "dt2815: unexpected status 0x%x " + "(@t=%d)\n", status, i); + if (status & 0x60) + outb(0x00, dev->iobase + DT2815_STATUS); + } + } + + return 0; +} + +static void dt2815_free_resources(struct comedi_device *dev) +{ + if (dev->iobase) + release_region(dev->iobase, DT2815_SIZE); +} + +static int dt2815_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: dt2815: remove\n", dev->minor); + + dt2815_free_resources(dev); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt2817.c b/drivers/staging/comedi/drivers/dt2817.c new file mode 100644 index 00000000..99c15841 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt2817.c @@ -0,0 +1,197 @@ +/* + comedi/drivers/dt2817.c + Hardware driver for Data Translation DT2817 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: dt2817 +Description: Data Translation DT2817 +Author: ds +Status: complete +Devices: [Data Translation] DT2817 (dt2817) + +A very simple digital I/O card. Four banks of 8 lines, each bank +is configurable for input or output. One wonders why it takes a +50 page manual to describe this thing. + +The driver (which, btw, is much less than 50 pages) has 1 subdevice +with 32 channels, configurable in groups of 8. + +Configuration options: + [0] - I/O port base base address +*/ + +#include "../comedidev.h" + +#include + +#define DT2817_SIZE 5 + +#define DT2817_CR 0 +#define DT2817_DATA 1 + +static int dt2817_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dt2817_detach(struct comedi_device *dev); +static struct comedi_driver driver_dt2817 = { + .driver_name = "dt2817", + .module = THIS_MODULE, + .attach = dt2817_attach, + .detach = dt2817_detach, +}; + +static int __init driver_dt2817_init_module(void) +{ + return comedi_driver_register(&driver_dt2817); +} + +static void __exit driver_dt2817_cleanup_module(void) +{ + comedi_driver_unregister(&driver_dt2817); +} + +module_init(driver_dt2817_init_module); +module_exit(driver_dt2817_cleanup_module); + +static int dt2817_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int mask; + int chan; + int oe = 0; + + if (insn->n != 1) + return -EINVAL; + + chan = CR_CHAN(insn->chanspec); + if (chan < 8) + mask = 0xff; + else if (chan < 16) + mask = 0xff00; + else if (chan < 24) + mask = 0xff0000; + else + mask = 0xff000000; + if (data[0]) + s->io_bits |= mask; + else + s->io_bits &= ~mask; + + if (s->io_bits & 0x000000ff) + oe |= 0x1; + if (s->io_bits & 0x0000ff00) + oe |= 0x2; + if (s->io_bits & 0x00ff0000) + oe |= 0x4; + if (s->io_bits & 0xff000000) + oe |= 0x8; + + outb(oe, dev->iobase + DT2817_CR); + + return 1; +} + +static int dt2817_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int changed; + + /* It's questionable whether it is more important in + * a driver like this to be deterministic or fast. + * We choose fast. */ + + if (data[0]) { + changed = s->state; + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + changed ^= s->state; + changed &= s->io_bits; + if (changed & 0x000000ff) + outb(s->state & 0xff, dev->iobase + DT2817_DATA + 0); + if (changed & 0x0000ff00) + outb((s->state >> 8) & 0xff, + dev->iobase + DT2817_DATA + 1); + if (changed & 0x00ff0000) + outb((s->state >> 16) & 0xff, + dev->iobase + DT2817_DATA + 2); + if (changed & 0xff000000) + outb((s->state >> 24) & 0xff, + dev->iobase + DT2817_DATA + 3); + } + data[1] = inb(dev->iobase + DT2817_DATA + 0); + data[1] |= (inb(dev->iobase + DT2817_DATA + 1) << 8); + data[1] |= (inb(dev->iobase + DT2817_DATA + 2) << 16); + data[1] |= (inb(dev->iobase + DT2817_DATA + 3) << 24); + + return 2; +} + +static int dt2817_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + struct comedi_subdevice *s; + unsigned long iobase; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: dt2817: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, DT2817_SIZE, "dt2817")) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + dev->board_name = "dt2817"; + + ret = alloc_subdevices(dev, 1); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + + s->n_chan = 32; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = dt2817_dio_insn_bits; + s->insn_config = dt2817_dio_insn_config; + + s->state = 0; + outb(0, dev->iobase + DT2817_CR); + + printk(KERN_INFO "\n"); + + return 0; +} + +static int dt2817_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: dt2817: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, DT2817_SIZE); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt282x.c b/drivers/staging/comedi/drivers/dt282x.c new file mode 100644 index 00000000..95ebc267 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt282x.c @@ -0,0 +1,1520 @@ +/* + comedi/drivers/dt282x.c + Hardware driver for Data Translation DT2821 series + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-8 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: dt282x +Description: Data Translation DT2821 series (including DT-EZ) +Author: ds +Devices: [Data Translation] DT2821 (dt2821), + DT2821-F-16SE (dt2821-f), DT2821-F-8DI (dt2821-f), + DT2821-G-16SE (dt2821-f), DT2821-G-8DI (dt2821-g), + DT2823 (dt2823), + DT2824-PGH (dt2824-pgh), DT2824-PGL (dt2824-pgl), DT2825 (dt2825), + DT2827 (dt2827), DT2828 (dt2828), DT21-EZ (dt21-ez), DT23-EZ (dt23-ez), + DT24-EZ (dt24-ez), DT24-EZ-PGL (dt24-ez-pgl) +Status: complete +Updated: Wed, 22 Aug 2001 17:11:34 -0700 + +Configuration options: + [0] - I/O port base address + [1] - IRQ + [2] - DMA 1 + [3] - DMA 2 + [4] - AI jumpered for 0=single ended, 1=differential + [5] - AI jumpered for 0=straight binary, 1=2's complement + [6] - AO 0 jumpered for 0=straight binary, 1=2's complement + [7] - AO 1 jumpered for 0=straight binary, 1=2's complement + [8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5] + [9] - AO 0 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5], + 4=[-2.5,2.5] + [10]- A0 1 jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5], + 4=[-2.5,2.5] + +Notes: + - AO commands might be broken. + - If you try to run a command on both the AI and AO subdevices + simultaneously, bad things will happen. The driver needs to + be fixed to check for this situation and return an error. +*/ + +#include "../comedidev.h" + +#include +#include +#include +#include +#include +#include "comedi_fc.h" + +#define DEBUG + +#define DT2821_TIMEOUT 100 /* 500 us */ +#define DT2821_SIZE 0x10 + +/* + * Registers in the DT282x + */ + +#define DT2821_ADCSR 0x00 /* A/D Control/Status */ +#define DT2821_CHANCSR 0x02 /* Channel Control/Status */ +#define DT2821_ADDAT 0x04 /* A/D data */ +#define DT2821_DACSR 0x06 /* D/A Control/Status */ +#define DT2821_DADAT 0x08 /* D/A data */ +#define DT2821_DIODAT 0x0a /* digital data */ +#define DT2821_SUPCSR 0x0c /* Supervisor Control/Status */ +#define DT2821_TMRCTR 0x0e /* Timer/Counter */ + +/* + * At power up, some registers are in a well-known state. The + * masks and values are as follows: + */ + +#define DT2821_ADCSR_MASK 0xfff0 +#define DT2821_ADCSR_VAL 0x7c00 + +#define DT2821_CHANCSR_MASK 0xf0f0 +#define DT2821_CHANCSR_VAL 0x70f0 + +#define DT2821_DACSR_MASK 0x7c93 +#define DT2821_DACSR_VAL 0x7c90 + +#define DT2821_SUPCSR_MASK 0xf8ff +#define DT2821_SUPCSR_VAL 0x0000 + +#define DT2821_TMRCTR_MASK 0xff00 +#define DT2821_TMRCTR_VAL 0xf000 + +/* + * Bit fields of each register + */ + +/* ADCSR */ + +#define DT2821_ADERR 0x8000 /* (R) 1 for A/D error */ +#define DT2821_ADCLK 0x0200 /* (R/W) A/D clock enable */ + /* 0x7c00 read as 1's */ +#define DT2821_MUXBUSY 0x0100 /* (R) multiplexer busy */ +#define DT2821_ADDONE 0x0080 /* (R) A/D done */ +#define DT2821_IADDONE 0x0040 /* (R/W) interrupt on A/D done */ + /* 0x0030 gain select */ + /* 0x000f channel select */ + +/* CHANCSR */ + +#define DT2821_LLE 0x8000 /* (R/W) Load List Enable */ + /* 0x7000 read as 1's */ + /* 0x0f00 (R) present address */ + /* 0x00f0 read as 1's */ + /* 0x000f (R) number of entries - 1 */ + +/* DACSR */ + +#define DT2821_DAERR 0x8000 /* (R) D/A error */ +#define DT2821_YSEL 0x0200 /* (R/W) DAC 1 select */ +#define DT2821_SSEL 0x0100 /* (R/W) single channel select */ +#define DT2821_DACRDY 0x0080 /* (R) DAC ready */ +#define DT2821_IDARDY 0x0040 /* (R/W) interrupt on DAC ready */ +#define DT2821_DACLK 0x0020 /* (R/W) D/A clock enable */ +#define DT2821_HBOE 0x0002 /* (R/W) DIO high byte output enable */ +#define DT2821_LBOE 0x0001 /* (R/W) DIO low byte output enable */ + +/* SUPCSR */ + +#define DT2821_DMAD 0x8000 /* (R) DMA done */ +#define DT2821_ERRINTEN 0x4000 /* (R/W) interrupt on error */ +#define DT2821_CLRDMADNE 0x2000 /* (W) clear DMA done */ +#define DT2821_DDMA 0x1000 /* (R/W) dual DMA */ +#define DT2821_DS1 0x0800 /* (R/W) DMA select 1 */ +#define DT2821_DS0 0x0400 /* (R/W) DMA select 0 */ +#define DT2821_BUFFB 0x0200 /* (R/W) buffer B selected */ +#define DT2821_SCDN 0x0100 /* (R) scan done */ +#define DT2821_DACON 0x0080 /* (W) DAC single conversion */ +#define DT2821_ADCINIT 0x0040 /* (W) A/D initialize */ +#define DT2821_DACINIT 0x0020 /* (W) D/A initialize */ +#define DT2821_PRLD 0x0010 /* (W) preload multiplexer */ +#define DT2821_STRIG 0x0008 /* (W) software trigger */ +#define DT2821_XTRIG 0x0004 /* (R/W) external trigger enable */ +#define DT2821_XCLK 0x0002 /* (R/W) external clock enable */ +#define DT2821_BDINIT 0x0001 /* (W) initialize board */ + +static const struct comedi_lrange range_dt282x_ai_lo_bipolar = { + 4, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-2.5, 2.5), + RANGE(-1.25, 1.25) + } +}; + +static const struct comedi_lrange range_dt282x_ai_lo_unipolar = { + 4, { + RANGE(0, 10), + RANGE(0, 5), + RANGE(0, 2.5), + RANGE(0, 1.25) + } +}; + +static const struct comedi_lrange range_dt282x_ai_5_bipolar = { + 4, { + RANGE(-5, 5), + RANGE(-2.5, 2.5), + RANGE(-1.25, 1.25), + RANGE(-0.625, 0.625) + } +}; + +static const struct comedi_lrange range_dt282x_ai_5_unipolar = { + 4, { + RANGE(0, 5), + RANGE(0, 2.5), + RANGE(0, 1.25), + RANGE(0, 0.625), + } +}; + +static const struct comedi_lrange range_dt282x_ai_hi_bipolar = { + 4, { + RANGE(-10, 10), + RANGE(-1, 1), + RANGE(-0.1, 0.1), + RANGE(-0.02, 0.02) + } +}; + +static const struct comedi_lrange range_dt282x_ai_hi_unipolar = { + 4, { + RANGE(0, 10), + RANGE(0, 1), + RANGE(0, 0.1), + RANGE(0, 0.02) + } +}; + +struct dt282x_board { + const char *name; + int adbits; + int adchan_se; + int adchan_di; + int ai_speed; + int ispgl; + int dachan; + int dabits; +}; + +static const struct dt282x_board boardtypes[] = { + {.name = "dt2821", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt2821-f", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 6500, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt2821-g", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 4000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt2823", + .adbits = 16, + .adchan_se = 0, + .adchan_di = 4, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 2, + .dabits = 16, + }, + {.name = "dt2824-pgh", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 0, + .dachan = 0, + .dabits = 0, + }, + {.name = "dt2824-pgl", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 1, + .dachan = 0, + .dabits = 0, + }, + {.name = "dt2825", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 20000, + .ispgl = 1, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt2827", + .adbits = 16, + .adchan_se = 0, + .adchan_di = 4, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt2828", + .adbits = 12, + .adchan_se = 4, + .adchan_di = 0, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt2829", + .adbits = 16, + .adchan_se = 8, + .adchan_di = 0, + .ai_speed = 33250, + .ispgl = 0, + .dachan = 2, + .dabits = 16, + }, + {.name = "dt21-ez", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt23-ez", + .adbits = 16, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 0, + .dabits = 0, + }, + {.name = "dt24-ez", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .ispgl = 0, + .dachan = 0, + .dabits = 0, + }, + {.name = "dt24-ez-pgl", + .adbits = 12, + .adchan_se = 16, + .adchan_di = 8, + .ai_speed = 10000, + .ispgl = 1, + .dachan = 0, + .dabits = 0, + }, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct dt282x_board)) +#define this_board ((const struct dt282x_board *)dev->board_ptr) + +struct dt282x_private { + int ad_2scomp; /* we have 2's comp jumper set */ + int da0_2scomp; /* same, for DAC0 */ + int da1_2scomp; /* same, for DAC1 */ + + const struct comedi_lrange *darangelist[2]; + + short ao[2]; + + volatile int dacsr; /* software copies of registers */ + volatile int adcsr; + volatile int supcsr; + + volatile int ntrig; + volatile int nread; + + struct { + int chan; + short *buf; /* DMA buffer */ + volatile int size; /* size of current transfer */ + } dma[2]; + int dma_maxsize; /* max size of DMA transfer (in bytes) */ + int usedma; /* driver uses DMA */ + volatile int current_dma_index; + int dma_dir; +}; + +#define devpriv ((struct dt282x_private *)dev->private) +#define boardtype (*(const struct dt282x_board *)dev->board_ptr) + +/* + * Some useless abstractions + */ +#define chan_to_DAC(a) ((a)&1) +#define update_dacsr(a) outw(devpriv->dacsr|(a), dev->iobase+DT2821_DACSR) +#define update_adcsr(a) outw(devpriv->adcsr|(a), dev->iobase+DT2821_ADCSR) +#define mux_busy() (inw(dev->iobase+DT2821_ADCSR)&DT2821_MUXBUSY) +#define ad_done() (inw(dev->iobase+DT2821_ADCSR)&DT2821_ADDONE) +#define update_supcsr(a) outw(devpriv->supcsr|(a), dev->iobase+DT2821_SUPCSR) + +/* + * danger! macro abuse... a is the expression to wait on, and b is + * the statement(s) to execute if it doesn't happen. + */ +#define wait_for(a, b) \ + do { \ + int _i; \ + for (_i = 0; _i < DT2821_TIMEOUT; _i++) { \ + if (a) { \ + _i = 0; \ + break; \ + } \ + udelay(5); \ + } \ + if (_i) \ + b \ + } while (0) + +static int dt282x_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dt282x_detach(struct comedi_device *dev); +static struct comedi_driver driver_dt282x = { + .driver_name = "dt282x", + .module = THIS_MODULE, + .attach = dt282x_attach, + .detach = dt282x_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct dt282x_board), +}; + +static int __init driver_dt282x_init_module(void) +{ + return comedi_driver_register(&driver_dt282x); +} + +static void __exit driver_dt282x_cleanup_module(void) +{ + comedi_driver_unregister(&driver_dt282x); +} + +module_init(driver_dt282x_init_module); +module_exit(driver_dt282x_cleanup_module); + +static void free_resources(struct comedi_device *dev); +static int prep_ai_dma(struct comedi_device *dev, int chan, int size); +static int prep_ao_dma(struct comedi_device *dev, int chan, int size); +static int dt282x_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static int dt282x_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static int dt282x_ns_to_timer(int *nanosec, int round_mode); +static void dt282x_disable_dma(struct comedi_device *dev); + +static int dt282x_grab_dma(struct comedi_device *dev, int dma1, int dma2); + +static void dt282x_munge(struct comedi_device *dev, short *buf, + unsigned int nbytes) +{ + unsigned int i; + unsigned short mask = (1 << boardtype.adbits) - 1; + unsigned short sign = 1 << (boardtype.adbits - 1); + int n; + + if (devpriv->ad_2scomp) + sign = 1 << (boardtype.adbits - 1); + else + sign = 0; + + if (nbytes % 2) + comedi_error(dev, "bug! odd number of bytes from dma xfer"); + n = nbytes / 2; + for (i = 0; i < n; i++) + buf[i] = (buf[i] & mask) ^ sign; +} + +static void dt282x_ao_dma_interrupt(struct comedi_device *dev) +{ + void *ptr; + int size; + int i; + struct comedi_subdevice *s = dev->subdevices + 1; + + update_supcsr(DT2821_CLRDMADNE); + + if (!s->async->prealloc_buf) { + printk(KERN_ERR "async->data disappeared. dang!\n"); + return; + } + + i = devpriv->current_dma_index; + ptr = devpriv->dma[i].buf; + + disable_dma(devpriv->dma[i].chan); + + devpriv->current_dma_index = 1 - i; + + size = cfc_read_array_from_buffer(s, ptr, devpriv->dma_maxsize); + if (size == 0) { + printk(KERN_ERR "dt282x: AO underrun\n"); + dt282x_ao_cancel(dev, s); + s->async->events |= COMEDI_CB_OVERFLOW; + return; + } + prep_ao_dma(dev, i, size); + return; +} + +static void dt282x_ai_dma_interrupt(struct comedi_device *dev) +{ + void *ptr; + int size; + int i; + int ret; + struct comedi_subdevice *s = dev->subdevices; + + update_supcsr(DT2821_CLRDMADNE); + + if (!s->async->prealloc_buf) { + printk(KERN_ERR "async->data disappeared. dang!\n"); + return; + } + + i = devpriv->current_dma_index; + ptr = devpriv->dma[i].buf; + size = devpriv->dma[i].size; + + disable_dma(devpriv->dma[i].chan); + + devpriv->current_dma_index = 1 - i; + + dt282x_munge(dev, ptr, size); + ret = cfc_write_array_to_buffer(s, ptr, size); + if (ret != size) { + dt282x_ai_cancel(dev, s); + return; + } + devpriv->nread -= size / 2; + + if (devpriv->nread < 0) { + printk(KERN_INFO "dt282x: off by one\n"); + devpriv->nread = 0; + } + if (!devpriv->nread) { + dt282x_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + return; + } +#if 0 + /* clear the dual dma flag, making this the last dma segment */ + /* XXX probably wrong */ + if (!devpriv->ntrig) { + devpriv->supcsr &= ~(DT2821_DDMA); + update_supcsr(0); + } +#endif + /* restart the channel */ + prep_ai_dma(dev, i, 0); +} + +static int prep_ai_dma(struct comedi_device *dev, int dma_index, int n) +{ + int dma_chan; + unsigned long dma_ptr; + unsigned long flags; + + if (!devpriv->ntrig) + return 0; + + if (n == 0) + n = devpriv->dma_maxsize; + if (n > devpriv->ntrig * 2) + n = devpriv->ntrig * 2; + devpriv->ntrig -= n / 2; + + devpriv->dma[dma_index].size = n; + dma_chan = devpriv->dma[dma_index].chan; + dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf); + + set_dma_mode(dma_chan, DMA_MODE_READ); + flags = claim_dma_lock(); + clear_dma_ff(dma_chan); + set_dma_addr(dma_chan, dma_ptr); + set_dma_count(dma_chan, n); + release_dma_lock(flags); + + enable_dma(dma_chan); + + return n; +} + +static int prep_ao_dma(struct comedi_device *dev, int dma_index, int n) +{ + int dma_chan; + unsigned long dma_ptr; + unsigned long flags; + + devpriv->dma[dma_index].size = n; + dma_chan = devpriv->dma[dma_index].chan; + dma_ptr = virt_to_bus(devpriv->dma[dma_index].buf); + + set_dma_mode(dma_chan, DMA_MODE_WRITE); + flags = claim_dma_lock(); + clear_dma_ff(dma_chan); + set_dma_addr(dma_chan, dma_ptr); + set_dma_count(dma_chan, n); + release_dma_lock(flags); + + enable_dma(dma_chan); + + return n; +} + +static irqreturn_t dt282x_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s; + struct comedi_subdevice *s_ao; + unsigned int supcsr, adcsr, dacsr; + int handled = 0; + + if (!dev->attached) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + s = dev->subdevices + 0; + s_ao = dev->subdevices + 1; + adcsr = inw(dev->iobase + DT2821_ADCSR); + dacsr = inw(dev->iobase + DT2821_DACSR); + supcsr = inw(dev->iobase + DT2821_SUPCSR); + if (supcsr & DT2821_DMAD) { + if (devpriv->dma_dir == DMA_MODE_READ) + dt282x_ai_dma_interrupt(dev); + else + dt282x_ao_dma_interrupt(dev); + handled = 1; + } + if (adcsr & DT2821_ADERR) { + if (devpriv->nread != 0) { + comedi_error(dev, "A/D error"); + dt282x_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_ERROR; + } + handled = 1; + } + if (dacsr & DT2821_DAERR) { +#if 0 + static int warn = 5; + if (--warn <= 0) { + disable_irq(dev->irq); + printk(KERN_INFO "disabling irq\n"); + } +#endif + comedi_error(dev, "D/A error"); + dt282x_ao_cancel(dev, s_ao); + s->async->events |= COMEDI_CB_ERROR; + handled = 1; + } +#if 0 + if (adcsr & DT2821_ADDONE) { + int ret; + short data; + + data = (short)inw(dev->iobase + DT2821_ADDAT); + data &= (1 << boardtype.adbits) - 1; + + if (devpriv->ad_2scomp) + data ^= 1 << (boardtype.adbits - 1); + ret = comedi_buf_put(s->async, data); + + if (ret == 0) + s->async->events |= COMEDI_CB_OVERFLOW; + + devpriv->nread--; + if (!devpriv->nread) { + s->async->events |= COMEDI_CB_EOA; + } else { + if (supcsr & DT2821_SCDN) + update_supcsr(DT2821_STRIG); + } + handled = 1; + } +#endif + comedi_event(dev, s); + /* printk("adcsr=0x%02x dacsr-0x%02x supcsr=0x%02x\n", + adcsr, dacsr, supcsr); */ + return IRQ_RETVAL(handled); +} + +static void dt282x_load_changain(struct comedi_device *dev, int n, + unsigned int *chanlist) +{ + unsigned int i; + unsigned int chan, range; + + outw(DT2821_LLE | (n - 1), dev->iobase + DT2821_CHANCSR); + for (i = 0; i < n; i++) { + chan = CR_CHAN(chanlist[i]); + range = CR_RANGE(chanlist[i]); + update_adcsr((range << 4) | (chan)); + } + outw(n - 1, dev->iobase + DT2821_CHANCSR); +} + +/* + * Performs a single A/D conversion. + * - Put channel/gain into channel-gain list + * - preload multiplexer + * - trigger conversion and wait for it to finish + */ +static int dt282x_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + + /* XXX should we really be enabling the ad clock here? */ + devpriv->adcsr = DT2821_ADCLK; + update_adcsr(0); + + dt282x_load_changain(dev, 1, &insn->chanspec); + + update_supcsr(DT2821_PRLD); + wait_for(!mux_busy(), comedi_error(dev, "timeout\n"); return -ETIME;); + + for (i = 0; i < insn->n; i++) { + update_supcsr(DT2821_STRIG); + wait_for(ad_done(), comedi_error(dev, "timeout\n"); + return -ETIME;); + + data[i] = + inw(dev->iobase + + DT2821_ADDAT) & ((1 << boardtype.adbits) - 1); + if (devpriv->ad_2scomp) + data[i] ^= (1 << (boardtype.adbits - 1)); + } + + return i; +} + +static int dt282x_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique + * and mutually compatible + */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + if (cmd->convert_arg < 4000) { + /* XXX board dependent */ + cmd->convert_arg = 4000; + err++; + } +#define SLOWEST_TIMER (250*(1<<15)*255) + if (cmd->convert_arg > SLOWEST_TIMER) { + cmd->convert_arg = SLOWEST_TIMER; + err++; + } + if (cmd->convert_arg < this_board->ai_speed) { + cmd->convert_arg = this_board->ai_speed; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + tmp = cmd->convert_arg; + dt282x_ns_to_timer(&cmd->convert_arg, cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + + if (err) + return 4; + + return 0; +} + +static int dt282x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int timer; + + if (devpriv->usedma == 0) { + comedi_error(dev, + "driver requires 2 dma channels" + " to execute command"); + return -EIO; + } + + dt282x_disable_dma(dev); + + if (cmd->convert_arg < this_board->ai_speed) + cmd->convert_arg = this_board->ai_speed; + timer = dt282x_ns_to_timer(&cmd->convert_arg, TRIG_ROUND_NEAREST); + outw(timer, dev->iobase + DT2821_TMRCTR); + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0; + } else { + /* external trigger */ + devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS0 | DT2821_DS1; + } + update_supcsr(DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_ADCINIT); + + devpriv->ntrig = cmd->stop_arg * cmd->scan_end_arg; + devpriv->nread = devpriv->ntrig; + + devpriv->dma_dir = DMA_MODE_READ; + devpriv->current_dma_index = 0; + prep_ai_dma(dev, 0, 0); + if (devpriv->ntrig) { + prep_ai_dma(dev, 1, 0); + devpriv->supcsr |= DT2821_DDMA; + update_supcsr(0); + } + + devpriv->adcsr = 0; + + dt282x_load_changain(dev, cmd->chanlist_len, cmd->chanlist); + + devpriv->adcsr = DT2821_ADCLK | DT2821_IADDONE; + update_adcsr(0); + + update_supcsr(DT2821_PRLD); + wait_for(!mux_busy(), comedi_error(dev, "timeout\n"); return -ETIME;); + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + update_supcsr(DT2821_STRIG); + } else { + devpriv->supcsr |= DT2821_XTRIG; + update_supcsr(0); + } + + return 0; +} + +static void dt282x_disable_dma(struct comedi_device *dev) +{ + if (devpriv->usedma) { + disable_dma(devpriv->dma[0].chan); + disable_dma(devpriv->dma[1].chan); + } +} + +static int dt282x_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + dt282x_disable_dma(dev); + + devpriv->adcsr = 0; + update_adcsr(0); + + devpriv->supcsr = 0; + update_supcsr(DT2821_ADCINIT); + + return 0; +} + +static int dt282x_ns_to_timer(int *nanosec, int round_mode) +{ + int prescale, base, divider; + + for (prescale = 0; prescale < 16; prescale++) { + if (prescale == 1) + continue; + base = 250 * (1 << prescale); + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + if (divider < 256) { + *nanosec = divider * base; + return (prescale << 8) | (255 - divider); + } + } + base = 250 * (1 << 15); + divider = 255; + *nanosec = divider * base; + return (15 << 8) | (255 - divider); +} + +/* + * Analog output routine. Selects single channel conversion, + * selects correct channel, converts from 2's compliment to + * offset binary if necessary, loads the data into the DAC + * data register, and performs the conversion. + */ +static int dt282x_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = devpriv->ao[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int dt282x_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + short d; + unsigned int chan; + + chan = CR_CHAN(insn->chanspec); + d = data[0]; + d &= (1 << boardtype.dabits) - 1; + devpriv->ao[chan] = d; + + devpriv->dacsr |= DT2821_SSEL; + + if (chan) { + /* select channel */ + devpriv->dacsr |= DT2821_YSEL; + if (devpriv->da0_2scomp) + d ^= (1 << (boardtype.dabits - 1)); + } else { + devpriv->dacsr &= ~DT2821_YSEL; + if (devpriv->da1_2scomp) + d ^= (1 << (boardtype.dabits - 1)); + } + + update_dacsr(0); + + outw(d, dev->iobase + DT2821_DADAT); + + update_supcsr(DT2821_DACON); + + return 1; +} + +static int dt282x_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique + * and mutually compatible + */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_arg < 5000 /* XXX unknown */) { + cmd->scan_begin_arg = 5000; + err++; + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + if (cmd->scan_end_arg > 2) { + /* XXX chanlist stuff? */ + cmd->scan_end_arg = 2; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + tmp = cmd->scan_begin_arg; + dt282x_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + + if (err) + return 4; + + return 0; + +} + +static int dt282x_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int x) +{ + int size; + + if (x != 0) + return -EINVAL; + + size = cfc_read_array_from_buffer(s, devpriv->dma[0].buf, + devpriv->dma_maxsize); + if (size == 0) { + printk(KERN_ERR "dt282x: AO underrun\n"); + return -EPIPE; + } + prep_ao_dma(dev, 0, size); + + size = cfc_read_array_from_buffer(s, devpriv->dma[1].buf, + devpriv->dma_maxsize); + if (size == 0) { + printk(KERN_ERR "dt282x: AO underrun\n"); + return -EPIPE; + } + prep_ao_dma(dev, 1, size); + + update_supcsr(DT2821_STRIG); + s->async->inttrig = NULL; + + return 1; +} + +static int dt282x_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int timer; + struct comedi_cmd *cmd = &s->async->cmd; + + if (devpriv->usedma == 0) { + comedi_error(dev, + "driver requires 2 dma channels" + " to execute command"); + return -EIO; + } + + dt282x_disable_dma(dev); + + devpriv->supcsr = DT2821_ERRINTEN | DT2821_DS1 | DT2821_DDMA; + update_supcsr(DT2821_CLRDMADNE | DT2821_BUFFB | DT2821_DACINIT); + + devpriv->ntrig = cmd->stop_arg * cmd->chanlist_len; + devpriv->nread = devpriv->ntrig; + + devpriv->dma_dir = DMA_MODE_WRITE; + devpriv->current_dma_index = 0; + + timer = dt282x_ns_to_timer(&cmd->scan_begin_arg, TRIG_ROUND_NEAREST); + outw(timer, dev->iobase + DT2821_TMRCTR); + + devpriv->dacsr = DT2821_SSEL | DT2821_DACLK | DT2821_IDARDY; + update_dacsr(0); + + s->async->inttrig = dt282x_ao_inttrig; + + return 0; +} + +static int dt282x_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + dt282x_disable_dma(dev); + + devpriv->dacsr = 0; + update_dacsr(0); + + devpriv->supcsr = 0; + update_supcsr(DT2821_DACINIT); + + return 0; +} + +static int dt282x_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + + outw(s->state, dev->iobase + DT2821_DIODAT); + } + data[1] = inw(dev->iobase + DT2821_DIODAT); + + return 2; +} + +static int dt282x_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int mask; + + mask = (CR_CHAN(insn->chanspec) < 8) ? 0x00ff : 0xff00; + if (data[0]) + s->io_bits |= mask; + else + s->io_bits &= ~mask; + + if (s->io_bits & 0x00ff) + devpriv->dacsr |= DT2821_LBOE; + else + devpriv->dacsr &= ~DT2821_LBOE; + if (s->io_bits & 0xff00) + devpriv->dacsr |= DT2821_HBOE; + else + devpriv->dacsr &= ~DT2821_HBOE; + + outw(devpriv->dacsr, dev->iobase + DT2821_DACSR); + + return 1; +} + +static const struct comedi_lrange *const ai_range_table[] = { + &range_dt282x_ai_lo_bipolar, + &range_dt282x_ai_lo_unipolar, + &range_dt282x_ai_5_bipolar, + &range_dt282x_ai_5_unipolar +}; + +static const struct comedi_lrange *const ai_range_pgl_table[] = { + &range_dt282x_ai_hi_bipolar, + &range_dt282x_ai_hi_unipolar +}; + +static const struct comedi_lrange *opt_ai_range_lkup(int ispgl, int x) +{ + if (ispgl) { + if (x < 0 || x >= 2) + x = 0; + return ai_range_pgl_table[x]; + } else { + if (x < 0 || x >= 4) + x = 0; + return ai_range_table[x]; + } +} + +static const struct comedi_lrange *const ao_range_table[] = { + &range_bipolar10, + &range_unipolar10, + &range_bipolar5, + &range_unipolar5, + &range_bipolar2_5 +}; + +static const struct comedi_lrange *opt_ao_range_lkup(int x) +{ + if (x < 0 || x >= 5) + x = 0; + return ao_range_table[x]; +} + +enum { /* i/o base, irq, dma channels */ + opt_iobase = 0, opt_irq, opt_dma1, opt_dma2, + opt_diff, /* differential */ + opt_ai_twos, opt_ao0_twos, opt_ao1_twos, /* twos comp */ + opt_ai_range, opt_ao0_range, opt_ao1_range, /* range */ +}; + +/* + options: + 0 i/o base + 1 irq + 2 dma1 + 3 dma2 + 4 0=single ended, 1=differential + 5 ai 0=straight binary, 1=2's comp + 6 ao0 0=straight binary, 1=2's comp + 7 ao1 0=straight binary, 1=2's comp + 8 ai 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V + 9 ao0 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V + 10 ao1 0=±10 V, 1=0-10 V, 2=±5 V, 3=0-5 V, 4=±2.5 V + */ +static int dt282x_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int i, irq; + int ret; + struct comedi_subdevice *s; + unsigned long iobase; + + dev->board_name = this_board->name; + + iobase = it->options[opt_iobase]; + if (!iobase) + iobase = 0x240; + + printk(KERN_INFO "comedi%d: dt282x: 0x%04lx", dev->minor, iobase); + if (!request_region(iobase, DT2821_SIZE, "dt282x")) { + printk(KERN_INFO " I/O port conflict\n"); + return -EBUSY; + } + dev->iobase = iobase; + + outw(DT2821_BDINIT, dev->iobase + DT2821_SUPCSR); + i = inw(dev->iobase + DT2821_ADCSR); +#ifdef DEBUG + printk(KERN_DEBUG " fingerprint=%x,%x,%x,%x,%x", + inw(dev->iobase + DT2821_ADCSR), + inw(dev->iobase + DT2821_CHANCSR), + inw(dev->iobase + DT2821_DACSR), + inw(dev->iobase + DT2821_SUPCSR), + inw(dev->iobase + DT2821_TMRCTR)); +#endif + + if (((inw(dev->iobase + DT2821_ADCSR) & DT2821_ADCSR_MASK) + != DT2821_ADCSR_VAL) || + ((inw(dev->iobase + DT2821_CHANCSR) & DT2821_CHANCSR_MASK) + != DT2821_CHANCSR_VAL) || + ((inw(dev->iobase + DT2821_DACSR) & DT2821_DACSR_MASK) + != DT2821_DACSR_VAL) || + ((inw(dev->iobase + DT2821_SUPCSR) & DT2821_SUPCSR_MASK) + != DT2821_SUPCSR_VAL) || + ((inw(dev->iobase + DT2821_TMRCTR) & DT2821_TMRCTR_MASK) + != DT2821_TMRCTR_VAL)) { + printk(KERN_ERR " board not found"); + return -EIO; + } + /* should do board test */ + + irq = it->options[opt_irq]; +#if 0 + if (irq < 0) { + unsigned long flags; + int irqs; + + save_flags(flags); + sti(); + irqs = probe_irq_on(); + + /* trigger interrupt */ + + udelay(100); + + irq = probe_irq_off(irqs); + restore_flags(flags); + if (0 /* error */) + printk(KERN_ERR " error probing irq (bad)"); + } +#endif + if (irq > 0) { + printk(KERN_INFO " ( irq = %d )", irq); + ret = request_irq(irq, dt282x_interrupt, 0, "dt282x", dev); + if (ret < 0) { + printk(KERN_ERR " failed to get irq\n"); + return -EIO; + } + dev->irq = irq; + } else if (irq == 0) { + printk(KERN_INFO " (no irq)"); + } else { +#if 0 + printk(KERN_INFO " (probe returned multiple irqs--bad)"); +#else + printk(KERN_INFO " (irq probe not implemented)"); +#endif + } + + ret = alloc_private(dev, sizeof(struct dt282x_private)); + if (ret < 0) + return ret; + + ret = dt282x_grab_dma(dev, it->options[opt_dma1], + it->options[opt_dma2]); + if (ret < 0) + return ret; + + ret = alloc_subdevices(dev, 3); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + + dev->read_subdev = s; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ | + ((it->options[opt_diff]) ? SDF_DIFF : SDF_COMMON); + s->n_chan = + (it->options[opt_diff]) ? boardtype.adchan_di : boardtype.adchan_se; + s->insn_read = dt282x_ai_insn_read; + s->do_cmdtest = dt282x_ai_cmdtest; + s->do_cmd = dt282x_ai_cmd; + s->cancel = dt282x_ai_cancel; + s->maxdata = (1 << boardtype.adbits) - 1; + s->len_chanlist = 16; + s->range_table = + opt_ai_range_lkup(boardtype.ispgl, it->options[opt_ai_range]); + devpriv->ad_2scomp = it->options[opt_ai_twos]; + + s++; + + s->n_chan = boardtype.dachan; + if (s->n_chan) { + /* ao subsystem */ + s->type = COMEDI_SUBD_AO; + dev->write_subdev = s; + s->subdev_flags = SDF_WRITABLE | SDF_CMD_WRITE; + s->insn_read = dt282x_ao_insn_read; + s->insn_write = dt282x_ao_insn_write; + s->do_cmdtest = dt282x_ao_cmdtest; + s->do_cmd = dt282x_ao_cmd; + s->cancel = dt282x_ao_cancel; + s->maxdata = (1 << boardtype.dabits) - 1; + s->len_chanlist = 2; + s->range_table_list = devpriv->darangelist; + devpriv->darangelist[0] = + opt_ao_range_lkup(it->options[opt_ao0_range]); + devpriv->darangelist[1] = + opt_ao_range_lkup(it->options[opt_ao1_range]); + devpriv->da0_2scomp = it->options[opt_ao0_twos]; + devpriv->da1_2scomp = it->options[opt_ao1_twos]; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s++; + /* dio subsystem */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 16; + s->insn_bits = dt282x_dio_insn_bits; + s->insn_config = dt282x_dio_insn_config; + s->maxdata = 1; + s->range_table = &range_digital; + + printk(KERN_INFO "\n"); + + return 0; +} + +static void free_resources(struct comedi_device *dev) +{ + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->iobase) + release_region(dev->iobase, DT2821_SIZE); + if (dev->private) { + if (devpriv->dma[0].chan) + free_dma(devpriv->dma[0].chan); + if (devpriv->dma[1].chan) + free_dma(devpriv->dma[1].chan); + if (devpriv->dma[0].buf) + free_page((unsigned long)devpriv->dma[0].buf); + if (devpriv->dma[1].buf) + free_page((unsigned long)devpriv->dma[1].buf); + } +} + +static int dt282x_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: dt282x: remove\n", dev->minor); + + free_resources(dev); + + return 0; +} + +static int dt282x_grab_dma(struct comedi_device *dev, int dma1, int dma2) +{ + int ret; + + devpriv->usedma = 0; + + if (!dma1 && !dma2) { + printk(KERN_ERR " (no dma)"); + return 0; + } + + if (dma1 == dma2 || dma1 < 5 || dma2 < 5 || dma1 > 7 || dma2 > 7) + return -EINVAL; + + if (dma2 < dma1) { + int i; + i = dma1; + dma1 = dma2; + dma2 = i; + } + + ret = request_dma(dma1, "dt282x A"); + if (ret) + return -EBUSY; + devpriv->dma[0].chan = dma1; + + ret = request_dma(dma2, "dt282x B"); + if (ret) + return -EBUSY; + devpriv->dma[1].chan = dma2; + + devpriv->dma_maxsize = PAGE_SIZE; + devpriv->dma[0].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); + devpriv->dma[1].buf = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); + if (!devpriv->dma[0].buf || !devpriv->dma[1].buf) { + printk(KERN_ERR " can't get DMA memory"); + return -ENOMEM; + } + + printk(KERN_INFO " (dma=%d,%d)", dma1, dma2); + + devpriv->usedma = 1; + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt3000.c b/drivers/staging/comedi/drivers/dt3000.c new file mode 100644 index 00000000..0a7979e5 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt3000.c @@ -0,0 +1,1034 @@ +/* + comedi/drivers/dt3000.c + Data Translation DT3000 series driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: dt3000 +Description: Data Translation DT3000 series +Author: ds +Devices: [Data Translation] DT3001 (dt3000), DT3001-PGL, DT3002, DT3003, + DT3003-PGL, DT3004, DT3005, DT3004-200 +Updated: Mon, 14 Apr 2008 15:41:24 +0100 +Status: works + +Configuration Options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. + +There is code to support AI commands, but it may not work. + +AO commands are not supported. +*/ + +/* + The DT3000 series is Data Translation's attempt to make a PCI + data acquisition board. The design of this series is very nice, + since each board has an on-board DSP (Texas Instruments TMS320C52). + However, a few details are a little annoying. The boards lack + bus-mastering DMA, which eliminates them from serious work. + They also are not capable of autocalibration, which is a common + feature in modern hardware. The default firmware is pretty bad, + making it nearly impossible to write an RT compatible driver. + It would make an interesting project to write a decent firmware + for these boards. + + Data Translation originally wanted an NDA for the documentation + for the 3k series. However, if you ask nicely, they might send + you the docs without one, also. +*/ + +#define DEBUG 1 + +#include +#include "../comedidev.h" +#include + +#include "comedi_pci.h" + +#define PCI_VENDOR_ID_DT 0x1116 + +static const struct comedi_lrange range_dt3000_ai = { 4, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-2.5, 2.5), + RANGE(-1.25, 1.25) + } +}; + +static const struct comedi_lrange range_dt3000_ai_pgl = { 4, { + RANGE(-10, 10), + RANGE(-1, 1), + RANGE(-0.1, 0.1), + RANGE(-0.02, 0.02) + } +}; + +struct dt3k_boardtype { + + const char *name; + unsigned int device_id; + int adchan; + int adbits; + int ai_speed; + const struct comedi_lrange *adrange; + int dachan; + int dabits; +}; + +static const struct dt3k_boardtype dt3k_boardtypes[] = { + {.name = "dt3001", + .device_id = 0x22, + .adchan = 16, + .adbits = 12, + .adrange = &range_dt3000_ai, + .ai_speed = 3000, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt3001-pgl", + .device_id = 0x27, + .adchan = 16, + .adbits = 12, + .adrange = &range_dt3000_ai_pgl, + .ai_speed = 3000, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt3002", + .device_id = 0x23, + .adchan = 32, + .adbits = 12, + .adrange = &range_dt3000_ai, + .ai_speed = 3000, + .dachan = 0, + .dabits = 0, + }, + {.name = "dt3003", + .device_id = 0x24, + .adchan = 64, + .adbits = 12, + .adrange = &range_dt3000_ai, + .ai_speed = 3000, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt3003-pgl", + .device_id = 0x28, + .adchan = 64, + .adbits = 12, + .adrange = &range_dt3000_ai_pgl, + .ai_speed = 3000, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt3004", + .device_id = 0x25, + .adchan = 16, + .adbits = 16, + .adrange = &range_dt3000_ai, + .ai_speed = 10000, + .dachan = 2, + .dabits = 12, + }, + {.name = "dt3005", /* a.k.a. 3004-200 */ + .device_id = 0x26, + .adchan = 16, + .adbits = 16, + .adrange = &range_dt3000_ai, + .ai_speed = 5000, + .dachan = 2, + .dabits = 12, + }, +}; + +#define n_dt3k_boards sizeof(dt3k_boardtypes)/sizeof(struct dt3k_boardtype) +#define this_board ((const struct dt3k_boardtype *)dev->board_ptr) + +static DEFINE_PCI_DEVICE_TABLE(dt3k_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_DT, 0x0022) }, + { PCI_DEVICE(PCI_VENDOR_ID_DT, 0x0027) }, + { PCI_DEVICE(PCI_VENDOR_ID_DT, 0x0023) }, + { PCI_DEVICE(PCI_VENDOR_ID_DT, 0x0024) }, + { PCI_DEVICE(PCI_VENDOR_ID_DT, 0x0028) }, + { PCI_DEVICE(PCI_VENDOR_ID_DT, 0x0025) }, + { PCI_DEVICE(PCI_VENDOR_ID_DT, 0x0026) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, dt3k_pci_table); + +#define DT3000_SIZE (4*0x1000) + +/* dual-ported RAM location definitions */ + +#define DPR_DAC_buffer (4*0x000) +#define DPR_ADC_buffer (4*0x800) +#define DPR_Command (4*0xfd3) +#define DPR_SubSys (4*0xfd3) +#define DPR_Encode (4*0xfd4) +#define DPR_Params(a) (4*(0xfd5+(a))) +#define DPR_Tick_Reg_Lo (4*0xff5) +#define DPR_Tick_Reg_Hi (4*0xff6) +#define DPR_DA_Buf_Front (4*0xff7) +#define DPR_DA_Buf_Rear (4*0xff8) +#define DPR_AD_Buf_Front (4*0xff9) +#define DPR_AD_Buf_Rear (4*0xffa) +#define DPR_Int_Mask (4*0xffb) +#define DPR_Intr_Flag (4*0xffc) +#define DPR_Response_Mbx (4*0xffe) +#define DPR_Command_Mbx (4*0xfff) + +#define AI_FIFO_DEPTH 2003 +#define AO_FIFO_DEPTH 2048 + +/* command list */ + +#define CMD_GETBRDINFO 0 +#define CMD_CONFIG 1 +#define CMD_GETCONFIG 2 +#define CMD_START 3 +#define CMD_STOP 4 +#define CMD_READSINGLE 5 +#define CMD_WRITESINGLE 6 +#define CMD_CALCCLOCK 7 +#define CMD_READEVENTS 8 +#define CMD_WRITECTCTRL 16 +#define CMD_READCTCTRL 17 +#define CMD_WRITECT 18 +#define CMD_READCT 19 +#define CMD_WRITEDATA 32 +#define CMD_READDATA 33 +#define CMD_WRITEIO 34 +#define CMD_READIO 35 +#define CMD_WRITECODE 36 +#define CMD_READCODE 37 +#define CMD_EXECUTE 38 +#define CMD_HALT 48 + +#define SUBS_AI 0 +#define SUBS_AO 1 +#define SUBS_DIN 2 +#define SUBS_DOUT 3 +#define SUBS_MEM 4 +#define SUBS_CT 5 + +/* interrupt flags */ +#define DT3000_CMDONE 0x80 +#define DT3000_CTDONE 0x40 +#define DT3000_DAHWERR 0x20 +#define DT3000_DASWERR 0x10 +#define DT3000_DAEMPTY 0x08 +#define DT3000_ADHWERR 0x04 +#define DT3000_ADSWERR 0x02 +#define DT3000_ADFULL 0x01 + +#define DT3000_COMPLETION_MASK 0xff00 +#define DT3000_COMMAND_MASK 0x00ff +#define DT3000_NOTPROCESSED 0x0000 +#define DT3000_NOERROR 0x5500 +#define DT3000_ERROR 0xaa00 +#define DT3000_NOTSUPPORTED 0xff00 + +#define DT3000_EXTERNAL_CLOCK 1 +#define DT3000_RISING_EDGE 2 + +#define TMODE_MASK 0x1c + +#define DT3000_AD_TRIG_INTERNAL (0<<2) +#define DT3000_AD_TRIG_EXTERNAL (1<<2) +#define DT3000_AD_RETRIG_INTERNAL (2<<2) +#define DT3000_AD_RETRIG_EXTERNAL (3<<2) +#define DT3000_AD_EXTRETRIG (4<<2) + +#define DT3000_CHANNEL_MODE_SE 0 +#define DT3000_CHANNEL_MODE_DI 1 + +struct dt3k_private { + + struct pci_dev *pci_dev; + resource_size_t phys_addr; + void *io_addr; + unsigned int lock; + unsigned int ao_readback[2]; + unsigned int ai_front; + unsigned int ai_rear; +}; + +#define devpriv ((struct dt3k_private *)dev->private) + +static int dt3000_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dt3000_detach(struct comedi_device *dev); +static struct comedi_driver driver_dt3000 = { + .driver_name = "dt3000", + .module = THIS_MODULE, + .attach = dt3000_attach, + .detach = dt3000_detach, +}; + +static int __devinit driver_dt3000_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_dt3000.driver_name); +} + +static void __devexit driver_dt3000_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_dt3000_pci_driver = { + .id_table = dt3k_pci_table, + .probe = &driver_dt3000_pci_probe, + .remove = __devexit_p(&driver_dt3000_pci_remove) +}; + +static int __init driver_dt3000_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_dt3000); + if (retval < 0) + return retval; + + driver_dt3000_pci_driver.name = (char *)driver_dt3000.driver_name; + return pci_register_driver(&driver_dt3000_pci_driver); +} + +static void __exit driver_dt3000_cleanup_module(void) +{ + pci_unregister_driver(&driver_dt3000_pci_driver); + comedi_driver_unregister(&driver_dt3000); +} + +module_init(driver_dt3000_init_module); +module_exit(driver_dt3000_cleanup_module); + +static void dt3k_ai_empty_fifo(struct comedi_device *dev, + struct comedi_subdevice *s); +static int dt3k_ns_to_timer(unsigned int timer_base, unsigned int *arg, + unsigned int round_mode); +static int dt3k_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +#ifdef DEBUG +static void debug_intr_flags(unsigned int flags); +#endif + +#define TIMEOUT 100 + +static int dt3k_send_cmd(struct comedi_device *dev, unsigned int cmd) +{ + int i; + unsigned int status = 0; + + writew(cmd, devpriv->io_addr + DPR_Command_Mbx); + + for (i = 0; i < TIMEOUT; i++) { + status = readw(devpriv->io_addr + DPR_Command_Mbx); + if ((status & DT3000_COMPLETION_MASK) != DT3000_NOTPROCESSED) + break; + udelay(1); + } + if ((status & DT3000_COMPLETION_MASK) == DT3000_NOERROR) + return 0; + + dev_dbg(dev->hw_dev, "dt3k_send_cmd() timeout/error status=0x%04x\n", + status); + + return -ETIME; +} + +static unsigned int dt3k_readsingle(struct comedi_device *dev, + unsigned int subsys, unsigned int chan, + unsigned int gain) +{ + writew(subsys, devpriv->io_addr + DPR_SubSys); + + writew(chan, devpriv->io_addr + DPR_Params(0)); + writew(gain, devpriv->io_addr + DPR_Params(1)); + + dt3k_send_cmd(dev, CMD_READSINGLE); + + return readw(devpriv->io_addr + DPR_Params(2)); +} + +static void dt3k_writesingle(struct comedi_device *dev, unsigned int subsys, + unsigned int chan, unsigned int data) +{ + writew(subsys, devpriv->io_addr + DPR_SubSys); + + writew(chan, devpriv->io_addr + DPR_Params(0)); + writew(0, devpriv->io_addr + DPR_Params(1)); + writew(data, devpriv->io_addr + DPR_Params(2)); + + dt3k_send_cmd(dev, CMD_WRITESINGLE); +} + +static int debug_n_ints; + +/* FIXME! Assumes shared interrupt is for this card. */ +/* What's this debug_n_ints stuff? Obviously needs some work... */ +static irqreturn_t dt3k_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s; + unsigned int status; + + if (!dev->attached) + return IRQ_NONE; + + s = dev->subdevices + 0; + status = readw(devpriv->io_addr + DPR_Intr_Flag); +#ifdef DEBUG + debug_intr_flags(status); +#endif + + if (status & DT3000_ADFULL) { + dt3k_ai_empty_fifo(dev, s); + s->async->events |= COMEDI_CB_BLOCK; + } + + if (status & (DT3000_ADSWERR | DT3000_ADHWERR)) + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + + debug_n_ints++; + if (debug_n_ints >= 10) { + dt3k_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + } + + comedi_event(dev, s); + return IRQ_HANDLED; +} + +#ifdef DEBUG +static char *intr_flags[] = { + "AdFull", "AdSwError", "AdHwError", "DaEmpty", + "DaSwError", "DaHwError", "CtDone", "CmDone", +}; + +static void debug_intr_flags(unsigned int flags) +{ + int i; + printk(KERN_DEBUG "dt3k: intr_flags:"); + for (i = 0; i < 8; i++) { + if (flags & (1 << i)) + printk(KERN_CONT " %s", intr_flags[i]); + } + printk(KERN_CONT "\n"); +} +#endif + +static void dt3k_ai_empty_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int front; + int rear; + int count; + int i; + short data; + + front = readw(devpriv->io_addr + DPR_AD_Buf_Front); + count = front - devpriv->ai_front; + if (count < 0) + count += AI_FIFO_DEPTH; + + dev_dbg(dev->hw_dev, "reading %d samples\n", count); + + rear = devpriv->ai_rear; + + for (i = 0; i < count; i++) { + data = readw(devpriv->io_addr + DPR_ADC_buffer + rear); + comedi_buf_put(s->async, data); + rear++; + if (rear >= AI_FIFO_DEPTH) + rear = 0; + } + + devpriv->ai_rear = rear; + writew(rear, devpriv->io_addr + DPR_AD_Buf_Rear); +} + +static int dt3k_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < this_board->ai_speed) { + cmd->scan_begin_arg = this_board->ai_speed; + err++; + } + if (cmd->scan_begin_arg > 100 * 16 * 65535) { + cmd->scan_begin_arg = 100 * 16 * 65535; + err++; + } + } else { + /* not supported */ + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < this_board->ai_speed) { + cmd->convert_arg = this_board->ai_speed; + err++; + } + if (cmd->convert_arg > 50 * 16 * 65535) { + cmd->convert_arg = 50 * 16 * 65535; + err++; + } + } else { + /* not supported */ + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg > 0x00ffffff) { + cmd->stop_arg = 0x00ffffff; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + dt3k_ns_to_timer(100, &cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } else { + /* not supported */ + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + dt3k_ns_to_timer(50, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } else { + /* not supported */ + } + + if (err) + return 4; + + return 0; +} + +static int dt3k_ns_to_timer(unsigned int timer_base, unsigned int *nanosec, + unsigned int round_mode) +{ + int divider, base, prescale; + + /* This function needs improvment */ + /* Don't know if divider==0 works. */ + + for (prescale = 0; prescale < 16; prescale++) { + base = timer_base * (prescale + 1); + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec) / base; + break; + } + if (divider < 65536) { + *nanosec = divider * base; + return (prescale << 16) | (divider); + } + } + + prescale = 15; + base = timer_base * (1 << prescale); + divider = 65535; + *nanosec = divider * base; + return (prescale << 16) | (divider); +} + +static int dt3k_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int i; + unsigned int chan, range, aref; + unsigned int divider; + unsigned int tscandiv; + int ret; + unsigned int mode; + + dev_dbg(dev->hw_dev, "dt3k_ai_cmd:\n"); + for (i = 0; i < cmd->chanlist_len; i++) { + chan = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + + writew((range << 6) | chan, + devpriv->io_addr + DPR_ADC_buffer + i); + } + aref = CR_AREF(cmd->chanlist[0]); + + writew(cmd->scan_end_arg, devpriv->io_addr + DPR_Params(0)); + dev_dbg(dev->hw_dev, "param[0]=0x%04x\n", cmd->scan_end_arg); + + if (cmd->convert_src == TRIG_TIMER) { + divider = dt3k_ns_to_timer(50, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + writew((divider >> 16), devpriv->io_addr + DPR_Params(1)); + dev_dbg(dev->hw_dev, "param[1]=0x%04x\n", divider >> 16); + writew((divider & 0xffff), devpriv->io_addr + DPR_Params(2)); + dev_dbg(dev->hw_dev, "param[2]=0x%04x\n", divider & 0xffff); + } else { + /* not supported */ + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + tscandiv = dt3k_ns_to_timer(100, &cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + writew((tscandiv >> 16), devpriv->io_addr + DPR_Params(3)); + dev_dbg(dev->hw_dev, "param[3]=0x%04x\n", tscandiv >> 16); + writew((tscandiv & 0xffff), devpriv->io_addr + DPR_Params(4)); + dev_dbg(dev->hw_dev, "param[4]=0x%04x\n", tscandiv & 0xffff); + } else { + /* not supported */ + } + + mode = DT3000_AD_RETRIG_INTERNAL | 0 | 0; + writew(mode, devpriv->io_addr + DPR_Params(5)); + dev_dbg(dev->hw_dev, "param[5]=0x%04x\n", mode); + writew(aref == AREF_DIFF, devpriv->io_addr + DPR_Params(6)); + dev_dbg(dev->hw_dev, "param[6]=0x%04x\n", aref == AREF_DIFF); + + writew(AI_FIFO_DEPTH / 2, devpriv->io_addr + DPR_Params(7)); + dev_dbg(dev->hw_dev, "param[7]=0x%04x\n", AI_FIFO_DEPTH / 2); + + writew(SUBS_AI, devpriv->io_addr + DPR_SubSys); + ret = dt3k_send_cmd(dev, CMD_CONFIG); + + writew(DT3000_ADFULL | DT3000_ADSWERR | DT3000_ADHWERR, + devpriv->io_addr + DPR_Int_Mask); + + debug_n_ints = 0; + + writew(SUBS_AI, devpriv->io_addr + DPR_SubSys); + ret = dt3k_send_cmd(dev, CMD_START); + + return 0; +} + +static int dt3k_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int ret; + + writew(SUBS_AI, devpriv->io_addr + DPR_SubSys); + ret = dt3k_send_cmd(dev, CMD_STOP); + + writew(0, devpriv->io_addr + DPR_Int_Mask); + + return 0; +} + +static int dt3k_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + unsigned int chan, gain, aref; + + chan = CR_CHAN(insn->chanspec); + gain = CR_RANGE(insn->chanspec); + /* XXX docs don't explain how to select aref */ + aref = CR_AREF(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = dt3k_readsingle(dev, SUBS_AI, chan, gain); + + return i; +} + +static int dt3k_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + unsigned int chan; + + chan = CR_CHAN(insn->chanspec); + for (i = 0; i < insn->n; i++) { + dt3k_writesingle(dev, SUBS_AO, chan, data[i]); + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +static int dt3k_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + unsigned int chan; + + chan = CR_CHAN(insn->chanspec); + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static void dt3k_dio_config(struct comedi_device *dev, int bits) +{ + /* XXX */ + writew(SUBS_DOUT, devpriv->io_addr + DPR_SubSys); + + writew(bits, devpriv->io_addr + DPR_Params(0)); +#if 0 + /* don't know */ + writew(0, devpriv->io_addr + DPR_Params(1)); + writew(0, devpriv->io_addr + DPR_Params(2)); +#endif + + dt3k_send_cmd(dev, CMD_CONFIG); +} + +static int dt3k_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int mask; + + mask = (CR_CHAN(insn->chanspec) < 4) ? 0x0f : 0xf0; + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= mask; + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~mask; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s-> + io_bits & (1 << CR_CHAN(insn->chanspec))) ? COMEDI_OUTPUT : + COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + break; + } + mask = (s->io_bits & 0x01) | ((s->io_bits & 0x10) >> 3); + dt3k_dio_config(dev, mask); + + return insn->n; +} + +static int dt3k_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[1] & data[0]; + dt3k_writesingle(dev, SUBS_DOUT, 0, s->state); + } + data[1] = dt3k_readsingle(dev, SUBS_DIN, 0, 0); + + return 2; +} + +static int dt3k_mem_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int addr = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + writew(SUBS_MEM, devpriv->io_addr + DPR_SubSys); + writew(addr, devpriv->io_addr + DPR_Params(0)); + writew(1, devpriv->io_addr + DPR_Params(1)); + + dt3k_send_cmd(dev, CMD_READCODE); + + data[i] = readw(devpriv->io_addr + DPR_Params(2)); + } + + return i; +} + +static int dt_pci_probe(struct comedi_device *dev, int bus, int slot); + +static int dt3000_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int bus, slot; + int ret = 0; + + dev_dbg(dev->hw_dev, "dt3000:\n"); + bus = it->options[0]; + slot = it->options[1]; + + ret = alloc_private(dev, sizeof(struct dt3k_private)); + if (ret < 0) + return ret; + + ret = dt_pci_probe(dev, bus, slot); + if (ret < 0) + return ret; + if (ret == 0) { + dev_warn(dev->hw_dev, "no DT board found\n"); + return -ENODEV; + } + + dev->board_name = this_board->name; + + if (request_irq(devpriv->pci_dev->irq, dt3k_interrupt, IRQF_SHARED, + "dt3000", dev)) { + dev_err(dev->hw_dev, "unable to allocate IRQ %u\n", + devpriv->pci_dev->irq); + return -EINVAL; + } + dev->irq = devpriv->pci_dev->irq; + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + s = dev->subdevices; + dev->read_subdev = s; + + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ; + s->n_chan = this_board->adchan; + s->insn_read = dt3k_ai_insn; + s->maxdata = (1 << this_board->adbits) - 1; + s->len_chanlist = 512; + s->range_table = &range_dt3000_ai; /* XXX */ + s->do_cmd = dt3k_ai_cmd; + s->do_cmdtest = dt3k_ai_cmdtest; + s->cancel = dt3k_ai_cancel; + + s++; + /* ao subsystem */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->insn_read = dt3k_ao_insn_read; + s->insn_write = dt3k_ao_insn; + s->maxdata = (1 << this_board->dabits) - 1; + s->len_chanlist = 1; + s->range_table = &range_bipolar10; + + s++; + /* dio subsystem */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->insn_config = dt3k_dio_insn_config; + s->insn_bits = dt3k_dio_insn_bits; + s->maxdata = 1; + s->len_chanlist = 8; + s->range_table = &range_digital; + + s++; + /* mem subsystem */ + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE; + s->n_chan = 0x1000; + s->insn_read = dt3k_mem_insn_read; + s->maxdata = 0xff; + s->len_chanlist = 1; + s->range_table = &range_unknown; + +#if 0 + s++; + /* proc subsystem */ + s->type = COMEDI_SUBD_PROC; +#endif + + return 0; +} + +static int dt3000_detach(struct comedi_device *dev) +{ + if (dev->irq) + free_irq(dev->irq, dev); + + if (devpriv) { + if (devpriv->pci_dev) { + if (devpriv->phys_addr) + comedi_pci_disable(devpriv->pci_dev); + pci_dev_put(devpriv->pci_dev); + } + if (devpriv->io_addr) + iounmap(devpriv->io_addr); + } + /* XXX */ + + return 0; +} + +static struct pci_dev *dt_pci_find_device(struct pci_dev *from, int *board); +static int setup_pci(struct comedi_device *dev); + +static int dt_pci_probe(struct comedi_device *dev, int bus, int slot) +{ + int board; + int ret; + struct pci_dev *pcidev; + + pcidev = NULL; + while ((pcidev = dt_pci_find_device(pcidev, &board)) != NULL) { + if ((bus == 0 && slot == 0) || + (pcidev->bus->number == bus && + PCI_SLOT(pcidev->devfn) == slot)) { + break; + } + } + devpriv->pci_dev = pcidev; + + if (board >= 0) + dev->board_ptr = dt3k_boardtypes + board; + + if (!devpriv->pci_dev) + return 0; + + ret = setup_pci(dev); + if (ret < 0) + return ret; + + return 1; +} + +static int setup_pci(struct comedi_device *dev) +{ + resource_size_t addr; + int ret; + + ret = comedi_pci_enable(devpriv->pci_dev, "dt3000"); + if (ret < 0) + return ret; + + addr = pci_resource_start(devpriv->pci_dev, 0); + devpriv->phys_addr = addr; + devpriv->io_addr = ioremap(devpriv->phys_addr, DT3000_SIZE); + if (!devpriv->io_addr) + return -ENOMEM; +#if DEBUG + printk("0x%08llx mapped to %p, ", + (unsigned long long)devpriv->phys_addr, devpriv->io_addr); +#endif + + return 0; +} + +static struct pci_dev *dt_pci_find_device(struct pci_dev *from, int *board) +{ + int i; + + for (from = pci_get_device(PCI_VENDOR_ID_DT, PCI_ANY_ID, from); + from != NULL; + from = pci_get_device(PCI_VENDOR_ID_DT, PCI_ANY_ID, from)) { + for (i = 0; i < n_dt3k_boards; i++) { + if (from->device == dt3k_boardtypes[i].device_id) { + *board = i; + return from; + } + } + printk + ("unknown Data Translation PCI device found with device_id=0x%04x\n", + from->device); + } + *board = -1; + return from; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dt9812.c b/drivers/staging/comedi/drivers/dt9812.c new file mode 100644 index 00000000..e86ab586 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt9812.c @@ -0,0 +1,1169 @@ +/* + * comedi/drivers/dt9812.c + * COMEDI driver for DataTranslation DT9812 USB module + * + * Copyright (C) 2005 Anders Blomdell + * + * COMEDI - Linux Control and Measurement Device Interface + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +Driver: dt9812 +Description: Data Translation DT9812 USB module +Author: anders.blomdell@control.lth.se (Anders Blomdell) +Status: in development +Devices: [Data Translation] DT9812 (dt9812) +Updated: Sun Nov 20 20:18:34 EST 2005 + +This driver works, but bulk transfers not implemented. Might be a starting point +for someone else. I found out too late that USB has too high latencies (>1 ms) +for my needs. +*/ + +/* + * Nota Bene: + * 1. All writes to command pipe has to be 32 bytes (ISP1181B SHRTP=0 ?) + * 2. The DDK source (as of sep 2005) is in error regarding the + * input MUX bits (example code says P4, but firmware schematics + * says P1). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../comedidev.h" + +#define DT9812_DIAGS_BOARD_INFO_ADDR 0xFBFF +#define DT9812_MAX_WRITE_CMD_PIPE_SIZE 32 +#define DT9812_MAX_READ_CMD_PIPE_SIZE 32 + +/* + * See Silican Laboratories C8051F020/1/2/3 manual + */ +#define F020_SFR_P4 0x84 +#define F020_SFR_P1 0x90 +#define F020_SFR_P2 0xa0 +#define F020_SFR_P3 0xb0 +#define F020_SFR_AMX0CF 0xba +#define F020_SFR_AMX0SL 0xbb +#define F020_SFR_ADC0CF 0xbc +#define F020_SFR_ADC0L 0xbe +#define F020_SFR_ADC0H 0xbf +#define F020_SFR_DAC0L 0xd2 +#define F020_SFR_DAC0H 0xd3 +#define F020_SFR_DAC0CN 0xd4 +#define F020_SFR_DAC1L 0xd5 +#define F020_SFR_DAC1H 0xd6 +#define F020_SFR_DAC1CN 0xd7 +#define F020_SFR_ADC0CN 0xe8 + +#define F020_MASK_ADC0CF_AMP0GN0 0x01 +#define F020_MASK_ADC0CF_AMP0GN1 0x02 +#define F020_MASK_ADC0CF_AMP0GN2 0x04 + +#define F020_MASK_ADC0CN_AD0EN 0x80 +#define F020_MASK_ADC0CN_AD0INT 0x20 +#define F020_MASK_ADC0CN_AD0BUSY 0x10 + +#define F020_MASK_DACxCN_DACxEN 0x80 + +enum { + /* A/D D/A DI DO CT */ + DT9812_DEVID_DT9812_10, /* 8 2 8 8 1 +/- 10V */ + DT9812_DEVID_DT9812_2PT5, /* 8 2 8 8 1 0-2.44V */ +#if 0 + DT9812_DEVID_DT9813, /* 16 2 4 4 1 +/- 10V */ + DT9812_DEVID_DT9814 /* 24 2 0 0 1 +/- 10V */ +#endif +}; + +enum dt9812_gain { + DT9812_GAIN_0PT25 = 1, + DT9812_GAIN_0PT5 = 2, + DT9812_GAIN_1 = 4, + DT9812_GAIN_2 = 8, + DT9812_GAIN_4 = 16, + DT9812_GAIN_8 = 32, + DT9812_GAIN_16 = 64, +}; + +enum { + DT9812_LEAST_USB_FIRMWARE_CMD_CODE = 0, + /* Write Flash memory */ + DT9812_W_FLASH_DATA = 0, + /* Read Flash memory misc config info */ + DT9812_R_FLASH_DATA = 1, + + /* + * Register read/write commands for processor + */ + + /* Read a single byte of USB memory */ + DT9812_R_SINGLE_BYTE_REG = 2, + /* Write a single byte of USB memory */ + DT9812_W_SINGLE_BYTE_REG = 3, + /* Multiple Reads of USB memory */ + DT9812_R_MULTI_BYTE_REG = 4, + /* Multiple Writes of USB memory */ + DT9812_W_MULTI_BYTE_REG = 5, + /* Read, (AND) with mask, OR value, then write (single) */ + DT9812_RMW_SINGLE_BYTE_REG = 6, + /* Read, (AND) with mask, OR value, then write (multiple) */ + DT9812_RMW_MULTI_BYTE_REG = 7, + + /* + * Register read/write commands for SMBus + */ + + /* Read a single byte of SMBus */ + DT9812_R_SINGLE_BYTE_SMBUS = 8, + /* Write a single byte of SMBus */ + DT9812_W_SINGLE_BYTE_SMBUS = 9, + /* Multiple Reads of SMBus */ + DT9812_R_MULTI_BYTE_SMBUS = 10, + /* Multiple Writes of SMBus */ + DT9812_W_MULTI_BYTE_SMBUS = 11, + + /* + * Register read/write commands for a device + */ + + /* Read a single byte of a device */ + DT9812_R_SINGLE_BYTE_DEV = 12, + /* Write a single byte of a device */ + DT9812_W_SINGLE_BYTE_DEV = 13, + /* Multiple Reads of a device */ + DT9812_R_MULTI_BYTE_DEV = 14, + /* Multiple Writes of a device */ + DT9812_W_MULTI_BYTE_DEV = 15, + + /* Not sure if we'll need this */ + DT9812_W_DAC_THRESHOLD = 16, + + /* Set interrupt on change mask */ + DT9812_W_INT_ON_CHANGE_MASK = 17, + + /* Write (or Clear) the CGL for the ADC */ + DT9812_W_CGL = 18, + /* Multiple Reads of USB memory */ + DT9812_R_MULTI_BYTE_USBMEM = 19, + /* Multiple Writes to USB memory */ + DT9812_W_MULTI_BYTE_USBMEM = 20, + + /* Issue a start command to a given subsystem */ + DT9812_START_SUBSYSTEM = 21, + /* Issue a stop command to a given subsystem */ + DT9812_STOP_SUBSYSTEM = 22, + + /* calibrate the board using CAL_POT_CMD */ + DT9812_CALIBRATE_POT = 23, + /* set the DAC FIFO size */ + DT9812_W_DAC_FIFO_SIZE = 24, + /* Write or Clear the CGL for the DAC */ + DT9812_W_CGL_DAC = 25, + /* Read a single value from a subsystem */ + DT9812_R_SINGLE_VALUE_CMD = 26, + /* Write a single value to a subsystem */ + DT9812_W_SINGLE_VALUE_CMD = 27, + /* Valid DT9812_USB_FIRMWARE_CMD_CODE's will be less than this number */ + DT9812_MAX_USB_FIRMWARE_CMD_CODE, +}; + +struct dt9812_flash_data { + u16 numbytes; + u16 address; +}; + +#define DT9812_MAX_NUM_MULTI_BYTE_RDS \ + ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / sizeof(u8)) + +struct dt9812_read_multi { + u8 count; + u8 address[DT9812_MAX_NUM_MULTI_BYTE_RDS]; +}; + +struct dt9812_write_byte { + u8 address; + u8 value; +}; + +#define DT9812_MAX_NUM_MULTI_BYTE_WRTS \ + ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / \ + sizeof(struct dt9812_write_byte)) + +struct dt9812_write_multi { + u8 count; + struct dt9812_write_byte write[DT9812_MAX_NUM_MULTI_BYTE_WRTS]; +}; + +struct dt9812_rmw_byte { + u8 address; + u8 and_mask; + u8 or_value; +}; + +#define DT9812_MAX_NUM_MULTI_BYTE_RMWS \ + ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / sizeof(struct dt9812_rmw_byte)) + +struct dt9812_rmw_multi { + u8 count; + struct dt9812_rmw_byte rmw[DT9812_MAX_NUM_MULTI_BYTE_RMWS]; +}; + +struct dt9812_usb_cmd { + u32 cmd; + union { + struct dt9812_flash_data flash_data_info; + struct dt9812_read_multi read_multi_info; + struct dt9812_write_multi write_multi_info; + struct dt9812_rmw_multi rmw_multi_info; + } u; +#if 0 + WRITE_BYTE_INFO WriteByteInfo; + READ_BYTE_INFO ReadByteInfo; + WRITE_MULTI_INFO WriteMultiInfo; + READ_MULTI_INFO ReadMultiInfo; + RMW_BYTE_INFO RMWByteInfo; + RMW_MULTI_INFO RMWMultiInfo; + DAC_THRESHOLD_INFO DacThresholdInfo; + INT_ON_CHANGE_MASK_INFO IntOnChangeMaskInfo; + CGL_INFO CglInfo; + SUBSYSTEM_INFO SubsystemInfo; + CAL_POT_CMD CalPotCmd; + WRITE_DEV_BYTE_INFO WriteDevByteInfo; + READ_DEV_BYTE_INFO ReadDevByteInfo; + WRITE_DEV_MULTI_INFO WriteDevMultiInfo; + READ_DEV_MULTI_INFO ReadDevMultiInfo; + READ_SINGLE_VALUE_INFO ReadSingleValueInfo; + WRITE_SINGLE_VALUE_INFO WriteSingleValueInfo; +#endif +}; + +#define DT9812_NUM_SLOTS 16 + +static DEFINE_SEMAPHORE(dt9812_mutex); + +static const struct usb_device_id dt9812_table[] = { + {USB_DEVICE(0x0867, 0x9812)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, dt9812_table); + +struct usb_dt9812 { + struct slot_dt9812 *slot; + struct usb_device *udev; + struct usb_interface *interface; + u16 vendor; + u16 product; + u16 device; + u32 serial; + struct { + __u8 addr; + size_t size; + } message_pipe, command_write, command_read, write_stream, read_stream; + struct kref kref; + u16 analog_out_shadow[2]; + u8 digital_out_shadow; +}; + +struct comedi_dt9812 { + struct slot_dt9812 *slot; + u32 serial; +}; + +struct slot_dt9812 { + struct semaphore mutex; + u32 serial; + struct usb_dt9812 *usb; + struct comedi_dt9812 *comedi; +}; + +static const struct comedi_lrange dt9812_10_ain_range = { 1, { + BIP_RANGE(10), + } +}; + +static const struct comedi_lrange dt9812_2pt5_ain_range = { 1, { + UNI_RANGE(2.5), + } +}; + +static const struct comedi_lrange dt9812_10_aout_range = { 1, { + BIP_RANGE(10), + } +}; + +static const struct comedi_lrange dt9812_2pt5_aout_range = { 1, { + UNI_RANGE(2.5), + } +}; + +static struct slot_dt9812 dt9812[DT9812_NUM_SLOTS]; + +/* Useful shorthand access to private data */ +#define devpriv ((struct comedi_dt9812 *)dev->private) + +static inline struct usb_dt9812 *to_dt9812_dev(struct kref *d) +{ + return container_of(d, struct usb_dt9812, kref); +} + +static void dt9812_delete(struct kref *kref) +{ + struct usb_dt9812 *dev = to_dt9812_dev(kref); + + usb_put_dev(dev->udev); + kfree(dev); +} + +static int dt9812_read_info(struct usb_dt9812 *dev, int offset, void *buf, + size_t buf_size) +{ + struct dt9812_usb_cmd cmd; + int count, retval; + + cmd.cmd = cpu_to_le32(DT9812_R_FLASH_DATA); + cmd.u.flash_data_info.address = + cpu_to_le16(DT9812_DIAGS_BOARD_INFO_ADDR + offset); + cmd.u.flash_data_info.numbytes = cpu_to_le16(buf_size); + + /* DT9812 only responds to 32 byte writes!! */ + count = 32; + retval = usb_bulk_msg(dev->udev, + usb_sndbulkpipe(dev->udev, + dev->command_write.addr), + &cmd, 32, &count, HZ * 1); + if (retval) + return retval; + retval = usb_bulk_msg(dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->command_read.addr), + buf, buf_size, &count, HZ * 1); + return retval; +} + +static int dt9812_read_multiple_registers(struct usb_dt9812 *dev, int reg_count, + u8 * address, u8 * value) +{ + struct dt9812_usb_cmd cmd; + int i, count, retval; + + cmd.cmd = cpu_to_le32(DT9812_R_MULTI_BYTE_REG); + cmd.u.read_multi_info.count = reg_count; + for (i = 0; i < reg_count; i++) + cmd.u.read_multi_info.address[i] = address[i]; + + /* DT9812 only responds to 32 byte writes!! */ + count = 32; + retval = usb_bulk_msg(dev->udev, + usb_sndbulkpipe(dev->udev, + dev->command_write.addr), + &cmd, 32, &count, HZ * 1); + if (retval) + return retval; + retval = usb_bulk_msg(dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->command_read.addr), + value, reg_count, &count, HZ * 1); + return retval; +} + +static int dt9812_write_multiple_registers(struct usb_dt9812 *dev, + int reg_count, u8 * address, + u8 * value) +{ + struct dt9812_usb_cmd cmd; + int i, count, retval; + + cmd.cmd = cpu_to_le32(DT9812_W_MULTI_BYTE_REG); + cmd.u.read_multi_info.count = reg_count; + for (i = 0; i < reg_count; i++) { + cmd.u.write_multi_info.write[i].address = address[i]; + cmd.u.write_multi_info.write[i].value = value[i]; + } + /* DT9812 only responds to 32 byte writes!! */ + retval = usb_bulk_msg(dev->udev, + usb_sndbulkpipe(dev->udev, + dev->command_write.addr), + &cmd, 32, &count, HZ * 1); + return retval; +} + +static int dt9812_rmw_multiple_registers(struct usb_dt9812 *dev, int reg_count, + struct dt9812_rmw_byte *rmw) +{ + struct dt9812_usb_cmd cmd; + int i, count, retval; + + cmd.cmd = cpu_to_le32(DT9812_RMW_MULTI_BYTE_REG); + cmd.u.rmw_multi_info.count = reg_count; + for (i = 0; i < reg_count; i++) + cmd.u.rmw_multi_info.rmw[i] = rmw[i]; + + /* DT9812 only responds to 32 byte writes!! */ + retval = usb_bulk_msg(dev->udev, + usb_sndbulkpipe(dev->udev, + dev->command_write.addr), + &cmd, 32, &count, HZ * 1); + return retval; +} + +static int dt9812_digital_in(struct slot_dt9812 *slot, u8 * bits) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + u8 reg[2] = { F020_SFR_P3, F020_SFR_P1 }; + u8 value[2]; + + result = dt9812_read_multiple_registers(slot->usb, 2, reg, + value); + if (result == 0) { + /* + * bits 0-6 in F020_SFR_P3 are bits 0-6 in the digital + * input port bit 3 in F020_SFR_P1 is bit 7 in the + * digital input port + */ + *bits = (value[0] & 0x7f) | ((value[1] & 0x08) << 4); + /* printk("%2.2x, %2.2x -> %2.2x\n", + value[0], value[1], *bits); */ + } + } + up(&slot->mutex); + + return result; +} + +static int dt9812_digital_out(struct slot_dt9812 *slot, u8 bits) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + u8 reg[1]; + u8 value[1]; + + reg[0] = F020_SFR_P2; + value[0] = bits; + result = dt9812_write_multiple_registers(slot->usb, 1, reg, + value); + slot->usb->digital_out_shadow = bits; + } + up(&slot->mutex); + return result; +} + +static int dt9812_digital_out_shadow(struct slot_dt9812 *slot, u8 * bits) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + *bits = slot->usb->digital_out_shadow; + result = 0; + } + up(&slot->mutex); + return result; +} + +static void dt9812_configure_mux(struct usb_dt9812 *dev, + struct dt9812_rmw_byte *rmw, int channel) +{ + if (dev->device == DT9812_DEVID_DT9812_10) { + /* In the DT9812/10V MUX is selected by P1.5-7 */ + rmw->address = F020_SFR_P1; + rmw->and_mask = 0xe0; + rmw->or_value = channel << 5; + } else { + /* In the DT9812/2.5V, internal mux is selected by bits 0:2 */ + rmw->address = F020_SFR_AMX0SL; + rmw->and_mask = 0xff; + rmw->or_value = channel & 0x07; + } +} + +static void dt9812_configure_gain(struct usb_dt9812 *dev, + struct dt9812_rmw_byte *rmw, + enum dt9812_gain gain) +{ + if (dev->device == DT9812_DEVID_DT9812_10) { + /* In the DT9812/10V, there is an external gain of 0.5 */ + gain <<= 1; + } + + rmw->address = F020_SFR_ADC0CF; + rmw->and_mask = F020_MASK_ADC0CF_AMP0GN2 | + F020_MASK_ADC0CF_AMP0GN1 | F020_MASK_ADC0CF_AMP0GN0; + switch (gain) { + /* + * 000 -> Gain = 1 + * 001 -> Gain = 2 + * 010 -> Gain = 4 + * 011 -> Gain = 8 + * 10x -> Gain = 16 + * 11x -> Gain = 0.5 + */ + case DT9812_GAIN_0PT5: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN2 | + F020_MASK_ADC0CF_AMP0GN1; + break; + case DT9812_GAIN_1: + rmw->or_value = 0x00; + break; + case DT9812_GAIN_2: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN0; + break; + case DT9812_GAIN_4: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN1; + break; + case DT9812_GAIN_8: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN1 | + F020_MASK_ADC0CF_AMP0GN0; + break; + case DT9812_GAIN_16: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN2; + break; + default: + err("Illegal gain %d\n", gain); + + } +} + +static int dt9812_analog_in(struct slot_dt9812 *slot, int channel, u16 * value, + enum dt9812_gain gain) +{ + struct dt9812_rmw_byte rmw[3]; + u8 reg[3] = { + F020_SFR_ADC0CN, + F020_SFR_ADC0H, + F020_SFR_ADC0L + }; + u8 val[3]; + int result = -ENODEV; + + down(&slot->mutex); + if (!slot->usb) + goto exit; + + /* 1 select the gain */ + dt9812_configure_gain(slot->usb, &rmw[0], gain); + + /* 2 set the MUX to select the channel */ + dt9812_configure_mux(slot->usb, &rmw[1], channel); + + /* 3 start conversion */ + rmw[2].address = F020_SFR_ADC0CN; + rmw[2].and_mask = 0xff; + rmw[2].or_value = F020_MASK_ADC0CN_AD0EN | F020_MASK_ADC0CN_AD0BUSY; + + result = dt9812_rmw_multiple_registers(slot->usb, 3, rmw); + if (result) + goto exit; + + /* read the status and ADC */ + result = dt9812_read_multiple_registers(slot->usb, 3, reg, val); + if (result) + goto exit; + /* + * An ADC conversion takes 16 SAR clocks cycles, i.e. about 9us. + * Therefore, between the instant that AD0BUSY was set via + * dt9812_rmw_multiple_registers and the read of AD0BUSY via + * dt9812_read_multiple_registers, the conversion should be complete + * since these two operations require two USB transactions each taking + * at least a millisecond to complete. However, lets make sure that + * conversion is finished. + */ + if ((val[0] & (F020_MASK_ADC0CN_AD0INT | F020_MASK_ADC0CN_AD0BUSY)) == + F020_MASK_ADC0CN_AD0INT) { + switch (slot->usb->device) { + case DT9812_DEVID_DT9812_10: + /* + * For DT9812-10V the personality module set the + * encoding to 2's complement. Hence, convert it before + * returning it + */ + *value = ((val[1] << 8) | val[2]) + 0x800; + break; + case DT9812_DEVID_DT9812_2PT5: + *value = (val[1] << 8) | val[2]; + break; + } + } + +exit: + up(&slot->mutex); + return result; +} + +static int dt9812_analog_out_shadow(struct slot_dt9812 *slot, int channel, + u16 * value) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + *value = slot->usb->analog_out_shadow[channel]; + result = 0; + } + up(&slot->mutex); + + return result; +} + +static int dt9812_analog_out(struct slot_dt9812 *slot, int channel, u16 value) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + struct dt9812_rmw_byte rmw[3]; + + switch (channel) { + case 0: + /* 1. Set DAC mode */ + rmw[0].address = F020_SFR_DAC0CN; + rmw[0].and_mask = 0xff; + rmw[0].or_value = F020_MASK_DACxCN_DACxEN; + + /* 2 load low byte of DAC value first */ + rmw[1].address = F020_SFR_DAC0L; + rmw[1].and_mask = 0xff; + rmw[1].or_value = value & 0xff; + + /* 3 load high byte of DAC value next to latch the + 12-bit value */ + rmw[2].address = F020_SFR_DAC0H; + rmw[2].and_mask = 0xff; + rmw[2].or_value = (value >> 8) & 0xf; + break; + + case 1: + /* 1. Set DAC mode */ + rmw[0].address = F020_SFR_DAC1CN; + rmw[0].and_mask = 0xff; + rmw[0].or_value = F020_MASK_DACxCN_DACxEN; + + /* 2 load low byte of DAC value first */ + rmw[1].address = F020_SFR_DAC1L; + rmw[1].and_mask = 0xff; + rmw[1].or_value = value & 0xff; + + /* 3 load high byte of DAC value next to latch the + 12-bit value */ + rmw[2].address = F020_SFR_DAC1H; + rmw[2].and_mask = 0xff; + rmw[2].or_value = (value >> 8) & 0xf; + break; + } + result = dt9812_rmw_multiple_registers(slot->usb, 3, rmw); + slot->usb->analog_out_shadow[channel] = value; + } + up(&slot->mutex); + + return result; +} + +/* + * USB framework functions + */ + +static int dt9812_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int retval = -ENOMEM; + struct usb_dt9812 *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int i; + u8 fw; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + goto error; + } + kref_init(&dev->kref); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* Check endpoints */ + iface_desc = interface->cur_altsetting; + + if (iface_desc->desc.bNumEndpoints != 5) { + err("Wrong number of endpints."); + retval = -ENODEV; + goto error; + } + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + int direction = -1; + endpoint = &iface_desc->endpoint[i].desc; + switch (i) { + case 0: + direction = USB_DIR_IN; + dev->message_pipe.addr = endpoint->bEndpointAddress; + dev->message_pipe.size = + le16_to_cpu(endpoint->wMaxPacketSize); + + break; + case 1: + direction = USB_DIR_OUT; + dev->command_write.addr = endpoint->bEndpointAddress; + dev->command_write.size = + le16_to_cpu(endpoint->wMaxPacketSize); + break; + case 2: + direction = USB_DIR_IN; + dev->command_read.addr = endpoint->bEndpointAddress; + dev->command_read.size = + le16_to_cpu(endpoint->wMaxPacketSize); + break; + case 3: + direction = USB_DIR_OUT; + dev->write_stream.addr = endpoint->bEndpointAddress; + dev->write_stream.size = + le16_to_cpu(endpoint->wMaxPacketSize); + break; + case 4: + direction = USB_DIR_IN; + dev->read_stream.addr = endpoint->bEndpointAddress; + dev->read_stream.size = + le16_to_cpu(endpoint->wMaxPacketSize); + break; + } + if ((endpoint->bEndpointAddress & USB_DIR_IN) != direction) { + dev_err(&interface->dev, + "Endpoint has wrong direction.\n"); + retval = -ENODEV; + goto error; + } + } + if (dt9812_read_info(dev, 0, &fw, sizeof(fw)) != 0) { + /* + * Seems like a configuration reset is necessary if driver is + * reloaded while device is attached + */ + usb_reset_configuration(dev->udev); + for (i = 0; i < 10; i++) { + retval = dt9812_read_info(dev, 1, &fw, sizeof(fw)); + if (retval == 0) { + dev_info(&interface->dev, + "usb_reset_configuration succeeded " + "after %d iterations\n", i); + break; + } + } + } + + if (dt9812_read_info(dev, 1, &dev->vendor, sizeof(dev->vendor)) != 0) { + err("Failed to read vendor."); + retval = -ENODEV; + goto error; + } + if (dt9812_read_info(dev, 3, &dev->product, sizeof(dev->product)) != 0) { + err("Failed to read product."); + retval = -ENODEV; + goto error; + } + if (dt9812_read_info(dev, 5, &dev->device, sizeof(dev->device)) != 0) { + err("Failed to read device."); + retval = -ENODEV; + goto error; + } + if (dt9812_read_info(dev, 7, &dev->serial, sizeof(dev->serial)) != 0) { + err("Failed to read serial."); + retval = -ENODEV; + goto error; + } + + dev->vendor = le16_to_cpu(dev->vendor); + dev->product = le16_to_cpu(dev->product); + dev->device = le16_to_cpu(dev->device); + dev->serial = le32_to_cpu(dev->serial); + switch (dev->device) { + case DT9812_DEVID_DT9812_10: + dev->analog_out_shadow[0] = 0x0800; + dev->analog_out_shadow[1] = 0x800; + break; + case DT9812_DEVID_DT9812_2PT5: + dev->analog_out_shadow[0] = 0x0000; + dev->analog_out_shadow[1] = 0x0000; + break; + } + dev->digital_out_shadow = 0; + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "USB DT9812 (%4.4x.%4.4x.%4.4x) #0x%8.8x\n", + dev->vendor, dev->product, dev->device, dev->serial); + + down(&dt9812_mutex); + { + /* Find a slot for the USB device */ + struct slot_dt9812 *first = NULL; + struct slot_dt9812 *best = NULL; + + for (i = 0; i < DT9812_NUM_SLOTS; i++) { + if (!first && !dt9812[i].usb && dt9812[i].serial == 0) + first = &dt9812[i]; + if (!best && dt9812[i].serial == dev->serial) + best = &dt9812[i]; + } + + if (!best) + best = first; + + if (best) { + down(&best->mutex); + best->usb = dev; + dev->slot = best; + up(&best->mutex); + } + } + up(&dt9812_mutex); + + return 0; + +error: + if (dev) + kref_put(&dev->kref, dt9812_delete); + return retval; +} + +static void dt9812_disconnect(struct usb_interface *interface) +{ + struct usb_dt9812 *dev; + int minor = interface->minor; + + down(&dt9812_mutex); + dev = usb_get_intfdata(interface); + if (dev->slot) { + down(&dev->slot->mutex); + dev->slot->usb = NULL; + up(&dev->slot->mutex); + dev->slot = NULL; + } + usb_set_intfdata(interface, NULL); + up(&dt9812_mutex); + + /* queue final destruction */ + kref_put(&dev->kref, dt9812_delete); + + dev_info(&interface->dev, "USB Dt9812 #%d now disconnected\n", minor); +} + +static struct usb_driver dt9812_usb_driver = { + .name = "dt9812", + .probe = dt9812_probe, + .disconnect = dt9812_disconnect, + .id_table = dt9812_table, +}; + +/* + * Comedi functions + */ + +static int dt9812_comedi_open(struct comedi_device *dev) +{ + int result = -ENODEV; + + down(&devpriv->slot->mutex); + if (devpriv->slot->usb) { + /* We have an attached device, fill in current range info */ + struct comedi_subdevice *s; + + s = &dev->subdevices[0]; + s->n_chan = 8; + s->maxdata = 1; + + s = &dev->subdevices[1]; + s->n_chan = 8; + s->maxdata = 1; + + s = &dev->subdevices[2]; + s->n_chan = 8; + switch (devpriv->slot->usb->device) { + case 0:{ + s->maxdata = 4095; + s->range_table = &dt9812_10_ain_range; + } + break; + case 1:{ + s->maxdata = 4095; + s->range_table = &dt9812_2pt5_ain_range; + } + break; + } + + s = &dev->subdevices[3]; + s->n_chan = 2; + switch (devpriv->slot->usb->device) { + case 0:{ + s->maxdata = 4095; + s->range_table = &dt9812_10_aout_range; + } + break; + case 1:{ + s->maxdata = 4095; + s->range_table = &dt9812_2pt5_aout_range; + } + break; + } + result = 0; + } + up(&devpriv->slot->mutex); + return result; +} + +static int dt9812_di_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n; + u8 bits = 0; + + dt9812_digital_in(devpriv->slot, &bits); + for (n = 0; n < insn->n; n++) + data[n] = ((1 << insn->chanspec) & bits) != 0; + return n; +} + +static int dt9812_do_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n; + u8 bits = 0; + + dt9812_digital_out_shadow(devpriv->slot, &bits); + for (n = 0; n < insn->n; n++) { + u8 mask = 1 << insn->chanspec; + + bits &= ~mask; + if (data[n]) + bits |= mask; + } + dt9812_digital_out(devpriv->slot, bits); + return n; +} + +static int dt9812_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n; + + for (n = 0; n < insn->n; n++) { + u16 value = 0; + + dt9812_analog_in(devpriv->slot, insn->chanspec, &value, + DT9812_GAIN_1); + data[n] = value; + } + return n; +} + +static int dt9812_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n; + u16 value; + + for (n = 0; n < insn->n; n++) { + value = 0; + dt9812_analog_out_shadow(devpriv->slot, insn->chanspec, &value); + data[n] = value; + } + return n; +} + +static int dt9812_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n; + + for (n = 0; n < insn->n; n++) + dt9812_analog_out(devpriv->slot, insn->chanspec, data[n]); + return n; +} + +static int dt9812_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int i; + struct comedi_subdevice *s; + + dev->board_name = "dt9812"; + + if (alloc_private(dev, sizeof(struct comedi_dt9812)) < 0) + return -ENOMEM; + + /* + * Special open routine, since USB unit may be unattached at + * comedi_config time, hence range can not be determined + */ + dev->open = dt9812_comedi_open; + + devpriv->serial = it->options[0]; + + /* Allocate subdevices */ + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + /* digital input subdevice */ + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_read = &dt9812_di_rinsn; + + /* digital output subdevice */ + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_write = &dt9812_do_winsn; + + /* analog input subdevice */ + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = NULL; + s->insn_read = &dt9812_ai_rinsn; + + /* analog output subdevice */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = NULL; + s->insn_write = &dt9812_ao_winsn; + s->insn_read = &dt9812_ao_rinsn; + + printk(KERN_INFO "comedi%d: successfully attached to dt9812.\n", + dev->minor); + + down(&dt9812_mutex); + /* Find a slot for the comedi device */ + { + struct slot_dt9812 *first = NULL; + struct slot_dt9812 *best = NULL; + for (i = 0; i < DT9812_NUM_SLOTS; i++) { + if (!first && !dt9812[i].comedi) { + /* First free slot from comedi side */ + first = &dt9812[i]; + } + if (!best && + dt9812[i].usb && + dt9812[i].usb->serial == devpriv->serial) { + /* We have an attaced device with matching ID */ + best = &dt9812[i]; + } + } + if (!best) + best = first; + if (best) { + down(&best->mutex); + best->comedi = devpriv; + best->serial = devpriv->serial; + devpriv->slot = best; + up(&best->mutex); + } + } + up(&dt9812_mutex); + + return 0; +} + +static int dt9812_detach(struct comedi_device *dev) +{ + return 0; +} + +static struct comedi_driver dt9812_comedi_driver = { + .module = THIS_MODULE, + .driver_name = "dt9812", + .attach = dt9812_attach, + .detach = dt9812_detach, +}; + +static int __init usb_dt9812_init(void) +{ + int result, i; + + /* Initialize all driver slots */ + for (i = 0; i < DT9812_NUM_SLOTS; i++) { + sema_init(&dt9812[i].mutex, 1); + dt9812[i].serial = 0; + dt9812[i].usb = NULL; + dt9812[i].comedi = NULL; + } + dt9812[12].serial = 0x0; + + /* register with the USB subsystem */ + result = usb_register(&dt9812_usb_driver); + if (result) { + printk(KERN_ERR KBUILD_MODNAME + ": usb_register failed. Error number %d\n", result); + return result; + } + /* register with comedi */ + result = comedi_driver_register(&dt9812_comedi_driver); + if (result) { + usb_deregister(&dt9812_usb_driver); + err("comedi_driver_register failed. Error number %d", result); + } + + return result; +} + +static void __exit usb_dt9812_exit(void) +{ + /* unregister with comedi */ + comedi_driver_unregister(&dt9812_comedi_driver); + + /* deregister this driver with the USB subsystem */ + usb_deregister(&dt9812_usb_driver); +} + +module_init(usb_dt9812_init); +module_exit(usb_dt9812_exit); + +MODULE_AUTHOR("Anders Blomdell "); +MODULE_DESCRIPTION("Comedi DT9812 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/dyna_pci10xx.c b/drivers/staging/comedi/drivers/dyna_pci10xx.c new file mode 100644 index 00000000..da8a2bf3 --- /dev/null +++ b/drivers/staging/comedi/drivers/dyna_pci10xx.c @@ -0,0 +1,462 @@ +/* + * comedi/drivers/dyna_pci10xx.c + * Copyright (C) 2011 Prashant Shah, pshah.mumbai@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + Driver: dyna_pci10xx + Devices: Dynalog India PCI DAQ Cards, http://www.dynalogindia.com/ + Author: Prashant Shah + Developed at Automation Labs, Chemical Dept., IIT Bombay, India. + Prof. Kannan Moudgalya + http://www.iitb.ac.in + Status: Stable + Version: 1.0 + Device Supported : + - Dynalog PCI 1050 + + Notes : + - Dynalog India Pvt. Ltd. does not have a registered PCI Vendor ID and + they are using the PLX Technlogies Vendor ID since that is the PCI Chip used + in the card. + - Dynalog India Pvt. Ltd. has provided the internal register specification for + their cards in their manuals. +*/ + +#include "../comedidev.h" +#include "comedi_pci.h" +#include + +#define PCI_VENDOR_ID_DYNALOG 0x10b5 +#define DRV_NAME "dyna_pci10xx" + +#define READ_TIMEOUT 50 + +static DEFINE_MUTEX(start_stop_sem); + +static DEFINE_PCI_DEVICE_TABLE(dyna_pci10xx_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_DYNALOG, 0x1050) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, dyna_pci10xx_pci_table); + +static int dyna_pci10xx_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dyna_pci10xx_detach(struct comedi_device *dev); + +static const struct comedi_lrange range_pci1050_ai = { 3, { + BIP_RANGE(10), + BIP_RANGE(5), + UNI_RANGE(10) + } +}; + +static const char range_codes_pci1050_ai[] = { 0x00, 0x10, 0x30 }; + +static const struct comedi_lrange range_pci1050_ao = { 1, { + UNI_RANGE(10) + } +}; + +static const char range_codes_pci1050_ao[] = { 0x00 }; + +struct boardtype { + const char *name; + int device_id; + int ai_chans; + int ai_bits; + int ao_chans; + int ao_bits; + int di_chans; + int di_bits; + int do_chans; + int do_bits; + const struct comedi_lrange *range_ai; + const char *range_codes_ai; + const struct comedi_lrange *range_ao; + const char *range_codes_ao; +}; + +static const struct boardtype boardtypes[] = { + { + .name = "dyna_pci1050", + .device_id = 0x1050, + .ai_chans = 16, + .ai_bits = 12, + .ao_chans = 16, + .ao_bits = 12, + .di_chans = 16, + .di_bits = 16, + .do_chans = 16, + .do_bits = 16, + .range_ai = &range_pci1050_ai, + .range_codes_ai = range_codes_pci1050_ai, + .range_ao = &range_pci1050_ao, + .range_codes_ao = range_codes_pci1050_ao, + }, + /* dummy entry corresponding to driver name */ + {.name = DRV_NAME}, +}; + +static struct comedi_driver driver_dyna_pci10xx = { + .driver_name = DRV_NAME, + .module = THIS_MODULE, + .attach = dyna_pci10xx_attach, + .detach = dyna_pci10xx_detach, + .board_name = &boardtypes[0].name, + .offset = sizeof(struct boardtype), + .num_names = ARRAY_SIZE(boardtypes), +}; + +struct dyna_pci10xx_private { + struct pci_dev *pci_dev; /* ptr to PCI device */ + char valid; /* card is usable */ + struct mutex mutex; + + /* device base address registers */ + unsigned long BADR0, BADR1, BADR2, BADR3, BADR4, BADR5; +}; + +#define thisboard ((const struct boardtype *)dev->board_ptr) +#define devpriv ((struct dyna_pci10xx_private *)dev->private) + +/******************************************************************************/ +/************************** READ WRITE FUNCTIONS ******************************/ +/******************************************************************************/ + +/* analog input callback */ +static int dyna_pci10xx_insn_read_ai(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, counter; + u16 d = 0; + unsigned int chan, range; + + /* get the channel number and range */ + chan = CR_CHAN(insn->chanspec); + range = thisboard->range_codes_ai[CR_RANGE((insn->chanspec))]; + + mutex_lock(&devpriv->mutex); + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + smp_mb(); + outw_p(0x0000 + range + chan, devpriv->BADR2 + 2); + udelay(10); + /* read data */ + for (counter = 0; counter < READ_TIMEOUT; counter++) { + d = inw_p(devpriv->BADR2); + + /* check if read is successfull if the EOC bit is set */ + if (d & (1 << 15)) + goto conv_finish; + } + data[n] = 0; + printk(KERN_DEBUG "comedi: dyna_pci10xx: " + "timeout reading analog input\n"); + continue; +conv_finish: + /* mask the first 4 bits - EOC bits */ + d &= 0x0FFF; + data[n] = d; + } + mutex_unlock(&devpriv->mutex); + + /* return the number of samples read/written */ + return n; +} + +/* analog output callback */ +static int dyna_pci10xx_insn_write_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + unsigned int chan, range; + + chan = CR_CHAN(insn->chanspec); + range = thisboard->range_codes_ai[CR_RANGE((insn->chanspec))]; + + mutex_lock(&devpriv->mutex); + for (n = 0; n < insn->n; n++) { + smp_mb(); + /* trigger conversion and write data */ + outw_p(data[n], devpriv->BADR2); + udelay(10); + } + mutex_unlock(&devpriv->mutex); + return n; +} + +/* digital input bit interface */ +static int dyna_pci10xx_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + u16 d = 0; + + if (insn->n != 2) + return -EINVAL; + + mutex_lock(&devpriv->mutex); + smp_mb(); + d = inw_p(devpriv->BADR3); + udelay(10); + + /* on return the data[0] contains output and data[1] contains input */ + data[1] = d; + data[0] = s->state; + mutex_unlock(&devpriv->mutex); + return 2; +} + +/* digital output bit interface */ +static int dyna_pci10xx_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. + * s->state contains the previous write data + */ + mutex_lock(&devpriv->mutex); + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + smp_mb(); + outw_p(s->state, devpriv->BADR3); + udelay(10); + } + + /* + * On return, data[1] contains the value of the digital + * input and output lines. We just return the software copy of the + * output values if it was a purely digital output subdevice. + */ + data[1] = s->state; + mutex_unlock(&devpriv->mutex); + return 2; +} + +/******************************************************************************/ +/*********************** INITIALIZATION FUNCTIONS *****************************/ +/******************************************************************************/ + +static int dyna_pci10xx_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + struct pci_dev *pcidev; + unsigned int opt_bus, opt_slot; + int board_index, i; + + mutex_lock(&start_stop_sem); + + if (alloc_private(dev, sizeof(struct dyna_pci10xx_private)) < 0) { + printk(KERN_ERR "comedi: dyna_pci10xx: " + "failed to allocate memory!\n"); + mutex_unlock(&start_stop_sem); + return -ENOMEM; + } + + opt_bus = it->options[0]; + opt_slot = it->options[1]; + dev->board_name = thisboard->name; + dev->irq = 0; + + /* + * Probe the PCI bus and located the matching device + */ + for (pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL); + pcidev != NULL; + pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pcidev)) { + + board_index = -1; + for (i = 0; i < ARRAY_SIZE(boardtypes); ++i) { + if ((pcidev->vendor == PCI_VENDOR_ID_DYNALOG) && + (pcidev->device == boardtypes[i].device_id)) { + board_index = i; + break; + } + } + if (board_index < 0) + continue; + + /* Found matching vendor/device. */ + if (opt_bus || opt_slot) { + /* Check bus/slot. */ + if (opt_bus != pcidev->bus->number + || opt_slot != PCI_SLOT(pcidev->devfn)) + continue; /* no match */ + } + + goto found; + } + printk(KERN_ERR "comedi: dyna_pci10xx: no supported device found!\n"); + mutex_unlock(&start_stop_sem); + return -EIO; + +found: + + if (!pcidev) { + if (opt_bus || opt_slot) { + printk(KERN_ERR "comedi: dyna_pci10xx: " + "invalid PCI device at b:s %d:%d\n", + opt_bus, opt_slot); + } else { + printk(KERN_ERR "comedi: dyna_pci10xx: " + "invalid PCI device\n"); + } + mutex_unlock(&start_stop_sem); + return -EIO; + } + + if (comedi_pci_enable(pcidev, DRV_NAME)) { + printk(KERN_ERR "comedi: dyna_pci10xx: " + "failed to enable PCI device and request regions!"); + mutex_unlock(&start_stop_sem); + return -EIO; + } + + mutex_init(&devpriv->mutex); + dev->board_ptr = &boardtypes[board_index]; + devpriv->pci_dev = pcidev; + + printk(KERN_INFO "comedi: dyna_pci10xx: device found!\n"); + + /* initialize device base address registers */ + devpriv->BADR0 = pci_resource_start(pcidev, 0); + devpriv->BADR1 = pci_resource_start(pcidev, 1); + devpriv->BADR2 = pci_resource_start(pcidev, 2); + devpriv->BADR3 = pci_resource_start(pcidev, 3); + devpriv->BADR4 = pci_resource_start(pcidev, 4); + devpriv->BADR5 = pci_resource_start(pcidev, 5); + + if (alloc_subdevices(dev, 4) < 0) { + printk(KERN_ERR "comedi: dyna_pci10xx: " + "failed allocating subdevices\n"); + mutex_unlock(&start_stop_sem); + return -ENOMEM; + } + + /* analog input */ + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = thisboard->ai_chans; + s->maxdata = 0x0FFF; + s->range_table = thisboard->range_ai; + s->len_chanlist = 16; + s->insn_read = dyna_pci10xx_insn_read_ai; + + /* analog output */ + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = thisboard->ao_chans; + s->maxdata = 0x0FFF; + s->range_table = thisboard->range_ao; + s->len_chanlist = 16; + s->insn_write = dyna_pci10xx_insn_write_ao; + + /* digital input */ + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = thisboard->di_chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->len_chanlist = thisboard->di_chans; + s->insn_bits = dyna_pci10xx_di_insn_bits; + + /* digital output */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = thisboard->do_chans; + s->maxdata = 1; + s->range_table = &range_digital; + s->len_chanlist = thisboard->do_chans; + s->state = 0; + s->insn_bits = dyna_pci10xx_do_insn_bits; + + devpriv->valid = 1; + mutex_unlock(&start_stop_sem); + + printk(KERN_INFO "comedi: dyna_pci10xx: %s - device setup completed!\n", + boardtypes[board_index].name); + + return 1; +} + +static int dyna_pci10xx_detach(struct comedi_device *dev) +{ + if (devpriv && devpriv->pci_dev) { + comedi_pci_disable(devpriv->pci_dev); + mutex_destroy(&devpriv->mutex); + } + + return 0; +} + +static int __devinit driver_dyna_pci10xx_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_dyna_pci10xx.driver_name); +} + +static void __devexit driver_dyna_pci10xx_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_dyna_pci10xx_pci_driver = { + .id_table = dyna_pci10xx_pci_table, + .probe = &driver_dyna_pci10xx_pci_probe, + .remove = __devexit_p(&driver_dyna_pci10xx_pci_remove) +}; + +static int __init driver_dyna_pci10xx_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_dyna_pci10xx); + if (retval < 0) + return retval; + + driver_dyna_pci10xx_pci_driver.name = + (char *)driver_dyna_pci10xx.driver_name; + return pci_register_driver(&driver_dyna_pci10xx_pci_driver); +} + +static void __exit driver_dyna_pci10xx_cleanup_module(void) +{ + pci_unregister_driver(&driver_dyna_pci10xx_pci_driver); + comedi_driver_unregister(&driver_dyna_pci10xx); +} + +module_init(driver_dyna_pci10xx_init_module); +module_exit(driver_dyna_pci10xx_cleanup_module); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Prashant Shah "); +MODULE_DESCRIPTION("Comedi based drivers for Dynalog PCI DAQ cards"); diff --git a/drivers/staging/comedi/drivers/fl512.c b/drivers/staging/comedi/drivers/fl512.c new file mode 100644 index 00000000..7f49add6 --- /dev/null +++ b/drivers/staging/comedi/drivers/fl512.c @@ -0,0 +1,222 @@ +/* + comedi/drivers/fl512.c + Anders Gnistrup +*/ + +/* +Driver: fl512 +Description: unknown +Author: Anders Gnistrup +Devices: [unknown] FL512 (fl512) +Status: unknown + +Digital I/O is not supported. + +Configuration options: + [0] - I/O port base address +*/ + +#define DEBUG 0 + +#include "../comedidev.h" + +#include +#include + +#define FL512_SIZE 16 /* the size of the used memory */ +struct fl512_private { + + short ao_readback[2]; +}; + +#define devpriv ((struct fl512_private *) dev->private) + +static const struct comedi_lrange range_fl512 = { 4, { + BIP_RANGE(0.5), + BIP_RANGE(1), + BIP_RANGE(5), + BIP_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(5), + UNI_RANGE(10), + } +}; + +static int fl512_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int fl512_detach(struct comedi_device *dev); + +static struct comedi_driver driver_fl512 = { + .driver_name = "fl512", + .module = THIS_MODULE, + .attach = fl512_attach, + .detach = fl512_detach, +}; + +static int __init driver_fl512_init_module(void) +{ + return comedi_driver_register(&driver_fl512); +} + +static void __exit driver_fl512_cleanup_module(void) +{ + comedi_driver_unregister(&driver_fl512); +} + +module_init(driver_fl512_init_module); +module_exit(driver_fl512_cleanup_module); + +static int fl512_ai_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int fl512_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int fl512_ao_insn_readback(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * fl512_ai_insn : this is the analog input function + */ +static int fl512_ai_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n; + unsigned int lo_byte, hi_byte; + char chan = CR_CHAN(insn->chanspec); + unsigned long iobase = dev->iobase; + + for (n = 0; n < insn->n; n++) { /* sample n times on selected channel */ + /* XXX probably can move next step out of for() loop -- will + * make AI a little bit faster. */ + outb(chan, iobase + 2); /* select chan */ + outb(0, iobase + 3); /* start conversion */ + /* XXX should test "done" flag instead of delay */ + udelay(30); /* sleep 30 usec */ + lo_byte = inb(iobase + 2); /* low 8 byte */ + hi_byte = inb(iobase + 3) & 0xf; /* high 4 bit and mask */ + data[n] = lo_byte + (hi_byte << 8); + } + return n; +} + +/* + * fl512_ao_insn : used to write to a DA port n times + */ +static int fl512_ao_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n; + int chan = CR_CHAN(insn->chanspec); /* get chan to write */ + unsigned long iobase = dev->iobase; /* get base address */ + + for (n = 0; n < insn->n; n++) { /* write n data set */ + /* write low byte */ + outb(data[n] & 0x0ff, iobase + 4 + 2 * chan); + /* write high byte */ + outb((data[n] & 0xf00) >> 8, iobase + 4 + 2 * chan); + inb(iobase + 4 + 2 * chan); /* trig */ + + devpriv->ao_readback[chan] = data[n]; + } + return n; +} + +/* + * fl512_ao_insn_readback : used to read previous values written to + * DA port + */ +static int fl512_ao_insn_readback(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_readback[chan]; + + return n; +} + +/* + * start attach + */ +static int fl512_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + unsigned long iobase; + + /* pointer to the subdevice: Analog in, Analog out, + (not made ->and Digital IO) */ + struct comedi_subdevice *s; + + iobase = it->options[0]; + printk(KERN_INFO "comedi:%d fl512: 0x%04lx", dev->minor, iobase); + if (!request_region(iobase, FL512_SIZE, "fl512")) { + printk(KERN_WARNING " I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + dev->board_name = "fl512"; + if (alloc_private(dev, sizeof(struct fl512_private)) < 0) + return -ENOMEM; + +#if DEBUG + printk(KERN_DEBUG "malloc ok\n"); +#endif + + if (alloc_subdevices(dev, 2) < 0) + return -ENOMEM; + + /* + * this if the definitions of the supdevices, 2 have been defined + */ + /* Analog indput */ + s = dev->subdevices + 0; + /* define subdevice as Analog In */ + s->type = COMEDI_SUBD_AI; + /* you can read it from userspace */ + s->subdev_flags = SDF_READABLE | SDF_GROUND; + /* Number of Analog input channels */ + s->n_chan = 16; + /* accept only 12 bits of data */ + s->maxdata = 0x0fff; + /* device use one of the ranges */ + s->range_table = &range_fl512; + /* function to call when read AD */ + s->insn_read = fl512_ai_insn; + printk(KERN_INFO "comedi: fl512: subdevice 0 initialized\n"); + + /* Analog output */ + s = dev->subdevices + 1; + /* define subdevice as Analog OUT */ + s->type = COMEDI_SUBD_AO; + /* you can write it from userspace */ + s->subdev_flags = SDF_WRITABLE; + /* Number of Analog output channels */ + s->n_chan = 2; + /* accept only 12 bits of data */ + s->maxdata = 0x0fff; + /* device use one of the ranges */ + s->range_table = &range_fl512; + /* function to call when write DA */ + s->insn_write = fl512_ao_insn; + /* function to call when reading DA */ + s->insn_read = fl512_ao_insn_readback; + printk(KERN_INFO "comedi: fl512: subdevice 1 initialized\n"); + + return 1; +} + +static int fl512_detach(struct comedi_device *dev) +{ + if (dev->iobase) + release_region(dev->iobase, FL512_SIZE); + printk(KERN_INFO "comedi%d: fl512: dummy i detach\n", dev->minor); + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/gsc_hpdi.c b/drivers/staging/comedi/drivers/gsc_hpdi.c new file mode 100644 index 00000000..bc020dea --- /dev/null +++ b/drivers/staging/comedi/drivers/gsc_hpdi.c @@ -0,0 +1,1118 @@ +/* + comedi/drivers/gsc_hpdi.c + This is a driver for the General Standards Corporation High + Speed Parallel Digital Interface rs485 boards. + + Author: Frank Mori Hess + Copyright (C) 2003 Coherent Imaging Systems + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-8 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************/ + +/* + +Driver: gsc_hpdi +Description: General Standards Corporation High + Speed Parallel Digital Interface rs485 boards +Author: Frank Mori Hess +Status: only receive mode works, transmit not supported +Updated: 2003-02-20 +Devices: [General Standards Corporation] PCI-HPDI32 (gsc_hpdi), + PMC-HPDI32 + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + +There are some additional hpdi models available from GSC for which +support could be added to this driver. + +*/ + +#include +#include "../comedidev.h" +#include + +#include "comedi_pci.h" +#include "plx9080.h" +#include "comedi_fc.h" + +static int hpdi_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int hpdi_detach(struct comedi_device *dev); +static void abort_dma(struct comedi_device *dev, unsigned int channel); +static int hpdi_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int hpdi_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int hpdi_cancel(struct comedi_device *dev, struct comedi_subdevice *s); +static irqreturn_t handle_interrupt(int irq, void *d); +static int dio_config_block_size(struct comedi_device *dev, unsigned int *data); + +#undef HPDI_DEBUG /* disable debugging messages */ +/* #define HPDI_DEBUG enable debugging code */ + +#ifdef HPDI_DEBUG +#define DEBUG_PRINT(format, args...) printk(format , ## args) +#else +#define DEBUG_PRINT(format, args...) +#endif + +#define TIMER_BASE 50 /* 20MHz master clock */ +#define DMA_BUFFER_SIZE 0x10000 +#define NUM_DMA_BUFFERS 4 +#define NUM_DMA_DESCRIPTORS 256 + +/* indices of base address regions */ +enum base_address_regions { + PLX9080_BADDRINDEX = 0, + HPDI_BADDRINDEX = 2, +}; + +enum hpdi_registers { + FIRMWARE_REV_REG = 0x0, + BOARD_CONTROL_REG = 0x4, + BOARD_STATUS_REG = 0x8, + TX_PROG_ALMOST_REG = 0xc, + RX_PROG_ALMOST_REG = 0x10, + FEATURES_REG = 0x14, + FIFO_REG = 0x18, + TX_STATUS_COUNT_REG = 0x1c, + TX_LINE_VALID_COUNT_REG = 0x20, + TX_LINE_INVALID_COUNT_REG = 0x24, + RX_STATUS_COUNT_REG = 0x28, + RX_LINE_COUNT_REG = 0x2c, + INTERRUPT_CONTROL_REG = 0x30, + INTERRUPT_STATUS_REG = 0x34, + TX_CLOCK_DIVIDER_REG = 0x38, + TX_FIFO_SIZE_REG = 0x40, + RX_FIFO_SIZE_REG = 0x44, + TX_FIFO_WORDS_REG = 0x48, + RX_FIFO_WORDS_REG = 0x4c, + INTERRUPT_EDGE_LEVEL_REG = 0x50, + INTERRUPT_POLARITY_REG = 0x54, +}; + +int command_channel_valid(unsigned int channel) +{ + if (channel == 0 || channel > 6) { + printk(KERN_WARNING + "gsc_hpdi: bug! invalid cable command channel\n"); + return 0; + } + return 1; +} + +/* bit definitions */ + +enum firmware_revision_bits { + FEATURES_REG_PRESENT_BIT = 0x8000, +}; +int firmware_revision(uint32_t fwr_bits) +{ + return fwr_bits & 0xff; +} + +int pcb_revision(uint32_t fwr_bits) +{ + return (fwr_bits >> 8) & 0xff; +} + +int hpdi_subid(uint32_t fwr_bits) +{ + return (fwr_bits >> 16) & 0xff; +} + +enum board_control_bits { + BOARD_RESET_BIT = 0x1, /* wait 10usec before accessing fifos */ + TX_FIFO_RESET_BIT = 0x2, + RX_FIFO_RESET_BIT = 0x4, + TX_ENABLE_BIT = 0x10, + RX_ENABLE_BIT = 0x20, + DEMAND_DMA_DIRECTION_TX_BIT = 0x40, + /* for ch 0, ch 1 can only transmit (when present) */ + LINE_VALID_ON_STATUS_VALID_BIT = 0x80, + START_TX_BIT = 0x10, + CABLE_THROTTLE_ENABLE_BIT = 0x20, + TEST_MODE_ENABLE_BIT = 0x80000000, +}; +uint32_t command_discrete_output_bits(unsigned int channel, int output, + int output_value) +{ + uint32_t bits = 0; + + if (command_channel_valid(channel) == 0) + return 0; + if (output) { + bits |= 0x1 << (16 + channel); + if (output_value) + bits |= 0x1 << (24 + channel); + } else + bits |= 0x1 << (24 + channel); + + return bits; +} + +enum board_status_bits { + COMMAND_LINE_STATUS_MASK = 0x7f, + TX_IN_PROGRESS_BIT = 0x80, + TX_NOT_EMPTY_BIT = 0x100, + TX_NOT_ALMOST_EMPTY_BIT = 0x200, + TX_NOT_ALMOST_FULL_BIT = 0x400, + TX_NOT_FULL_BIT = 0x800, + RX_NOT_EMPTY_BIT = 0x1000, + RX_NOT_ALMOST_EMPTY_BIT = 0x2000, + RX_NOT_ALMOST_FULL_BIT = 0x4000, + RX_NOT_FULL_BIT = 0x8000, + BOARD_JUMPER0_INSTALLED_BIT = 0x10000, + BOARD_JUMPER1_INSTALLED_BIT = 0x20000, + TX_OVERRUN_BIT = 0x200000, + RX_UNDERRUN_BIT = 0x400000, + RX_OVERRUN_BIT = 0x800000, +}; + +uint32_t almost_full_bits(unsigned int num_words) +{ +/* XXX need to add or subtract one? */ + return (num_words << 16) & 0xff0000; +} + +uint32_t almost_empty_bits(unsigned int num_words) +{ + return num_words & 0xffff; +} + +unsigned int almost_full_num_words(uint32_t bits) +{ +/* XXX need to add or subtract one? */ + return (bits >> 16) & 0xffff; +} + +unsigned int almost_empty_num_words(uint32_t bits) +{ + return bits & 0xffff; +} + +enum features_bits { + FIFO_SIZE_PRESENT_BIT = 0x1, + FIFO_WORDS_PRESENT_BIT = 0x2, + LEVEL_EDGE_INTERRUPTS_PRESENT_BIT = 0x4, + GPIO_SUPPORTED_BIT = 0x8, + PLX_DMA_CH1_SUPPORTED_BIT = 0x10, + OVERRUN_UNDERRUN_SUPPORTED_BIT = 0x20, +}; + +enum interrupt_sources { + FRAME_VALID_START_INTR = 0, + FRAME_VALID_END_INTR = 1, + TX_FIFO_EMPTY_INTR = 8, + TX_FIFO_ALMOST_EMPTY_INTR = 9, + TX_FIFO_ALMOST_FULL_INTR = 10, + TX_FIFO_FULL_INTR = 11, + RX_EMPTY_INTR = 12, + RX_ALMOST_EMPTY_INTR = 13, + RX_ALMOST_FULL_INTR = 14, + RX_FULL_INTR = 15, +}; +int command_intr_source(unsigned int channel) +{ + if (command_channel_valid(channel) == 0) + channel = 1; + return channel + 1; +} + +uint32_t intr_bit(int interrupt_source) +{ + return 0x1 << interrupt_source; +} + +uint32_t tx_clock_divisor_bits(unsigned int divisor) +{ + return divisor & 0xff; +} + +unsigned int fifo_size(uint32_t fifo_size_bits) +{ + return fifo_size_bits & 0xfffff; +} + +unsigned int fifo_words(uint32_t fifo_words_bits) +{ + return fifo_words_bits & 0xfffff; +} + +uint32_t intr_edge_bit(int interrupt_source) +{ + return 0x1 << interrupt_source; +} + +uint32_t intr_active_high_bit(int interrupt_source) +{ + return 0x1 << interrupt_source; +} + +struct hpdi_board { + + char *name; + int device_id; /* pci device id */ + int subdevice_id; /* pci subdevice id */ +}; + +static const struct hpdi_board hpdi_boards[] = { + { + .name = "pci-hpdi32", + .device_id = PCI_DEVICE_ID_PLX_9080, + .subdevice_id = 0x2400, + }, +#if 0 + { + .name = "pxi-hpdi32", + .device_id = 0x9656, + .subdevice_id = 0x2705, + }, +#endif +}; + +static DEFINE_PCI_DEVICE_TABLE(hpdi_pci_table) = { + { + PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9080, PCI_VENDOR_ID_PLX, + 0x2400, 0, 0, 0}, { + 0} +}; + +MODULE_DEVICE_TABLE(pci, hpdi_pci_table); + +static inline struct hpdi_board *board(const struct comedi_device *dev) +{ + return (struct hpdi_board *)dev->board_ptr; +} + +struct hpdi_private { + + struct pci_dev *hw_dev; /* pointer to board's pci_dev struct */ + /* base addresses (physical) */ + resource_size_t plx9080_phys_iobase; + resource_size_t hpdi_phys_iobase; + /* base addresses (ioremapped) */ + void *plx9080_iobase; + void *hpdi_iobase; + uint32_t *dio_buffer[NUM_DMA_BUFFERS]; /* dma buffers */ + /* physical addresses of dma buffers */ + dma_addr_t dio_buffer_phys_addr[NUM_DMA_BUFFERS]; + /* array of dma descriptors read by plx9080, allocated to get proper + * alignment */ + struct plx_dma_desc *dma_desc; + /* physical address of dma descriptor array */ + dma_addr_t dma_desc_phys_addr; + unsigned int num_dma_descriptors; + /* pointer to start of buffers indexed by descriptor */ + uint32_t *desc_dio_buffer[NUM_DMA_DESCRIPTORS]; + /* index of the dma descriptor that is currently being used */ + volatile unsigned int dma_desc_index; + unsigned int tx_fifo_size; + unsigned int rx_fifo_size; + volatile unsigned long dio_count; + /* software copies of values written to hpdi registers */ + volatile uint32_t bits[24]; + /* number of bytes at which to generate COMEDI_CB_BLOCK events */ + volatile unsigned int block_size; + unsigned dio_config_output:1; +}; + +static inline struct hpdi_private *priv(struct comedi_device *dev) +{ + return dev->private; +} + +static struct comedi_driver driver_hpdi = { + .driver_name = "gsc_hpdi", + .module = THIS_MODULE, + .attach = hpdi_attach, + .detach = hpdi_detach, +}; + +static int __devinit driver_hpdi_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_hpdi.driver_name); +} + +static void __devexit driver_hpdi_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_hpdi_pci_driver = { + .id_table = hpdi_pci_table, + .probe = &driver_hpdi_pci_probe, + .remove = __devexit_p(&driver_hpdi_pci_remove) +}; + +static int __init driver_hpdi_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_hpdi); + if (retval < 0) + return retval; + + driver_hpdi_pci_driver.name = (char *)driver_hpdi.driver_name; + return pci_register_driver(&driver_hpdi_pci_driver); +} + +static void __exit driver_hpdi_cleanup_module(void) +{ + pci_unregister_driver(&driver_hpdi_pci_driver); + comedi_driver_unregister(&driver_hpdi); +} + +module_init(driver_hpdi_init_module); +module_exit(driver_hpdi_cleanup_module); + +static int dio_config_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + priv(dev)->dio_config_output = 1; + return insn->n; + break; + case INSN_CONFIG_DIO_INPUT: + priv(dev)->dio_config_output = 0; + return insn->n; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + priv(dev)->dio_config_output ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + case INSN_CONFIG_BLOCK_SIZE: + return dio_config_block_size(dev, data); + break; + default: + break; + } + + return -EINVAL; +} + +static void disable_plx_interrupts(struct comedi_device *dev) +{ + writel(0, priv(dev)->plx9080_iobase + PLX_INTRCS_REG); +} + +/* initialize plx9080 chip */ +static void init_plx9080(struct comedi_device *dev) +{ + uint32_t bits; + void *plx_iobase = priv(dev)->plx9080_iobase; + + /* plx9080 dump */ + DEBUG_PRINT(" plx interrupt status 0x%x\n", + readl(plx_iobase + PLX_INTRCS_REG)); + DEBUG_PRINT(" plx id bits 0x%x\n", readl(plx_iobase + PLX_ID_REG)); + DEBUG_PRINT(" plx control reg 0x%x\n", + readl(priv(dev)->plx9080_iobase + PLX_CONTROL_REG)); + + DEBUG_PRINT(" plx revision 0x%x\n", + readl(plx_iobase + PLX_REVISION_REG)); + DEBUG_PRINT(" plx dma channel 0 mode 0x%x\n", + readl(plx_iobase + PLX_DMA0_MODE_REG)); + DEBUG_PRINT(" plx dma channel 1 mode 0x%x\n", + readl(plx_iobase + PLX_DMA1_MODE_REG)); + DEBUG_PRINT(" plx dma channel 0 pci address 0x%x\n", + readl(plx_iobase + PLX_DMA0_PCI_ADDRESS_REG)); + DEBUG_PRINT(" plx dma channel 0 local address 0x%x\n", + readl(plx_iobase + PLX_DMA0_LOCAL_ADDRESS_REG)); + DEBUG_PRINT(" plx dma channel 0 transfer size 0x%x\n", + readl(plx_iobase + PLX_DMA0_TRANSFER_SIZE_REG)); + DEBUG_PRINT(" plx dma channel 0 descriptor 0x%x\n", + readl(plx_iobase + PLX_DMA0_DESCRIPTOR_REG)); + DEBUG_PRINT(" plx dma channel 0 command status 0x%x\n", + readb(plx_iobase + PLX_DMA0_CS_REG)); + DEBUG_PRINT(" plx dma channel 0 threshold 0x%x\n", + readl(plx_iobase + PLX_DMA0_THRESHOLD_REG)); + DEBUG_PRINT(" plx bigend 0x%x\n", readl(plx_iobase + PLX_BIGEND_REG)); +#ifdef __BIG_ENDIAN + bits = BIGEND_DMA0 | BIGEND_DMA1; +#else + bits = 0; +#endif + writel(bits, priv(dev)->plx9080_iobase + PLX_BIGEND_REG); + + disable_plx_interrupts(dev); + + abort_dma(dev, 0); + abort_dma(dev, 1); + + /* configure dma0 mode */ + bits = 0; + /* enable ready input */ + bits |= PLX_DMA_EN_READYIN_BIT; + /* enable dma chaining */ + bits |= PLX_EN_CHAIN_BIT; + /* enable interrupt on dma done + * (probably don't need this, since chain never finishes) */ + bits |= PLX_EN_DMA_DONE_INTR_BIT; + /* don't increment local address during transfers + * (we are transferring from a fixed fifo register) */ + bits |= PLX_LOCAL_ADDR_CONST_BIT; + /* route dma interrupt to pci bus */ + bits |= PLX_DMA_INTR_PCI_BIT; + /* enable demand mode */ + bits |= PLX_DEMAND_MODE_BIT; + /* enable local burst mode */ + bits |= PLX_DMA_LOCAL_BURST_EN_BIT; + bits |= PLX_LOCAL_BUS_32_WIDE_BITS; + writel(bits, plx_iobase + PLX_DMA0_MODE_REG); +} + +/* Allocate and initialize the subdevice structures. + */ +static int setup_subdevices(struct comedi_device *dev) +{ + struct comedi_subdevice *s; + + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog input subdevice */ + dev->read_subdev = s; +/* dev->write_subdev = s; */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = + SDF_READABLE | SDF_WRITEABLE | SDF_LSAMPL | SDF_CMD_READ; + s->n_chan = 32; + s->len_chanlist = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = dio_config_insn; + s->do_cmd = hpdi_cmd; + s->do_cmdtest = hpdi_cmd_test; + s->cancel = hpdi_cancel; + + return 0; +} + +static int init_hpdi(struct comedi_device *dev) +{ + uint32_t plx_intcsr_bits; + + writel(BOARD_RESET_BIT, priv(dev)->hpdi_iobase + BOARD_CONTROL_REG); + udelay(10); + + writel(almost_empty_bits(32) | almost_full_bits(32), + priv(dev)->hpdi_iobase + RX_PROG_ALMOST_REG); + writel(almost_empty_bits(32) | almost_full_bits(32), + priv(dev)->hpdi_iobase + TX_PROG_ALMOST_REG); + + priv(dev)->tx_fifo_size = fifo_size(readl(priv(dev)->hpdi_iobase + + TX_FIFO_SIZE_REG)); + priv(dev)->rx_fifo_size = fifo_size(readl(priv(dev)->hpdi_iobase + + RX_FIFO_SIZE_REG)); + + writel(0, priv(dev)->hpdi_iobase + INTERRUPT_CONTROL_REG); + + /* enable interrupts */ + plx_intcsr_bits = + ICS_AERR | ICS_PERR | ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_LIE | + ICS_DMA0_E; + writel(plx_intcsr_bits, priv(dev)->plx9080_iobase + PLX_INTRCS_REG); + + return 0; +} + +/* setup dma descriptors so a link completes every 'transfer_size' bytes */ +static int setup_dma_descriptors(struct comedi_device *dev, + unsigned int transfer_size) +{ + unsigned int buffer_index, buffer_offset; + uint32_t next_bits = PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT | + PLX_XFER_LOCAL_TO_PCI; + unsigned int i; + + if (transfer_size > DMA_BUFFER_SIZE) + transfer_size = DMA_BUFFER_SIZE; + transfer_size -= transfer_size % sizeof(uint32_t); + if (transfer_size == 0) + return -1; + + DEBUG_PRINT(" transfer_size %i\n", transfer_size); + DEBUG_PRINT(" descriptors at 0x%lx\n", + (unsigned long)priv(dev)->dma_desc_phys_addr); + + buffer_offset = 0; + buffer_index = 0; + for (i = 0; i < NUM_DMA_DESCRIPTORS && + buffer_index < NUM_DMA_BUFFERS; i++) { + priv(dev)->dma_desc[i].pci_start_addr = + cpu_to_le32(priv(dev)->dio_buffer_phys_addr[buffer_index] + + buffer_offset); + priv(dev)->dma_desc[i].local_start_addr = cpu_to_le32(FIFO_REG); + priv(dev)->dma_desc[i].transfer_size = + cpu_to_le32(transfer_size); + priv(dev)->dma_desc[i].next = + cpu_to_le32((priv(dev)->dma_desc_phys_addr + (i + + 1) * + sizeof(priv(dev)->dma_desc[0])) | next_bits); + + priv(dev)->desc_dio_buffer[i] = + priv(dev)->dio_buffer[buffer_index] + + (buffer_offset / sizeof(uint32_t)); + + buffer_offset += transfer_size; + if (transfer_size + buffer_offset > DMA_BUFFER_SIZE) { + buffer_offset = 0; + buffer_index++; + } + + DEBUG_PRINT(" desc %i\n", i); + DEBUG_PRINT(" start addr virt 0x%p, phys 0x%lx\n", + priv(dev)->desc_dio_buffer[i], + (unsigned long)priv(dev)->dma_desc[i]. + pci_start_addr); + DEBUG_PRINT(" next 0x%lx\n", + (unsigned long)priv(dev)->dma_desc[i].next); + } + priv(dev)->num_dma_descriptors = i; + /* fix last descriptor to point back to first */ + priv(dev)->dma_desc[i - 1].next = + cpu_to_le32(priv(dev)->dma_desc_phys_addr | next_bits); + DEBUG_PRINT(" desc %i next fixup 0x%lx\n", i - 1, + (unsigned long)priv(dev)->dma_desc[i - 1].next); + + priv(dev)->block_size = transfer_size; + + return transfer_size; +} + +static int hpdi_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pci_dev *pcidev; + int i; + int retval; + + printk(KERN_WARNING "comedi%d: gsc_hpdi\n", dev->minor); + + if (alloc_private(dev, sizeof(struct hpdi_private)) < 0) + return -ENOMEM; + + pcidev = NULL; + for (i = 0; i < ARRAY_SIZE(hpdi_boards) && + dev->board_ptr == NULL; i++) { + do { + pcidev = pci_get_subsys(PCI_VENDOR_ID_PLX, + hpdi_boards[i].device_id, + PCI_VENDOR_ID_PLX, + hpdi_boards[i].subdevice_id, + pcidev); + /* was a particular bus/slot requested? */ + if (it->options[0] || it->options[1]) { + /* are we on the wrong bus/slot? */ + if (pcidev->bus->number != it->options[0] || + PCI_SLOT(pcidev->devfn) != it->options[1]) + continue; + } + if (pcidev) { + priv(dev)->hw_dev = pcidev; + dev->board_ptr = hpdi_boards + i; + break; + } + } while (pcidev != NULL); + } + if (dev->board_ptr == NULL) { + printk(KERN_WARNING "gsc_hpdi: no hpdi card found\n"); + return -EIO; + } + + printk(KERN_WARNING + "gsc_hpdi: found %s on bus %i, slot %i\n", board(dev)->name, + pcidev->bus->number, PCI_SLOT(pcidev->devfn)); + + if (comedi_pci_enable(pcidev, driver_hpdi.driver_name)) { + printk(KERN_WARNING + " failed enable PCI device and request regions\n"); + return -EIO; + } + pci_set_master(pcidev); + + /* Initialize dev->board_name */ + dev->board_name = board(dev)->name; + + priv(dev)->plx9080_phys_iobase = + pci_resource_start(pcidev, PLX9080_BADDRINDEX); + priv(dev)->hpdi_phys_iobase = + pci_resource_start(pcidev, HPDI_BADDRINDEX); + + /* remap, won't work with 2.0 kernels but who cares */ + priv(dev)->plx9080_iobase = ioremap(priv(dev)->plx9080_phys_iobase, + pci_resource_len(pcidev, + PLX9080_BADDRINDEX)); + priv(dev)->hpdi_iobase = + ioremap(priv(dev)->hpdi_phys_iobase, + pci_resource_len(pcidev, HPDI_BADDRINDEX)); + if (!priv(dev)->plx9080_iobase || !priv(dev)->hpdi_iobase) { + printk(KERN_WARNING " failed to remap io memory\n"); + return -ENOMEM; + } + + DEBUG_PRINT(" plx9080 remapped to 0x%p\n", priv(dev)->plx9080_iobase); + DEBUG_PRINT(" hpdi remapped to 0x%p\n", priv(dev)->hpdi_iobase); + + init_plx9080(dev); + + /* get irq */ + if (request_irq(pcidev->irq, handle_interrupt, IRQF_SHARED, + driver_hpdi.driver_name, dev)) { + printk(KERN_WARNING + " unable to allocate irq %u\n", pcidev->irq); + return -EINVAL; + } + dev->irq = pcidev->irq; + + printk(KERN_WARNING " irq %u\n", dev->irq); + + /* alocate pci dma buffers */ + for (i = 0; i < NUM_DMA_BUFFERS; i++) { + priv(dev)->dio_buffer[i] = + pci_alloc_consistent(priv(dev)->hw_dev, DMA_BUFFER_SIZE, + &priv(dev)->dio_buffer_phys_addr[i]); + DEBUG_PRINT("dio_buffer at virt 0x%p, phys 0x%lx\n", + priv(dev)->dio_buffer[i], + (unsigned long)priv(dev)->dio_buffer_phys_addr[i]); + } + /* allocate dma descriptors */ + priv(dev)->dma_desc = pci_alloc_consistent(priv(dev)->hw_dev, + sizeof(struct plx_dma_desc) * + NUM_DMA_DESCRIPTORS, + &priv(dev)-> + dma_desc_phys_addr); + if (priv(dev)->dma_desc_phys_addr & 0xf) { + printk(KERN_WARNING + " dma descriptors not quad-word aligned (bug)\n"); + return -EIO; + } + + retval = setup_dma_descriptors(dev, 0x1000); + if (retval < 0) + return retval; + + retval = setup_subdevices(dev); + if (retval < 0) + return retval; + + return init_hpdi(dev); +} + +static int hpdi_detach(struct comedi_device *dev) +{ + unsigned int i; + + printk(KERN_WARNING "comedi%d: gsc_hpdi: remove\n", dev->minor); + + if (dev->irq) + free_irq(dev->irq, dev); + if ((priv(dev)) && (priv(dev)->hw_dev)) { + if (priv(dev)->plx9080_iobase) { + disable_plx_interrupts(dev); + iounmap((void *)priv(dev)->plx9080_iobase); + } + if (priv(dev)->hpdi_iobase) + iounmap((void *)priv(dev)->hpdi_iobase); + /* free pci dma buffers */ + for (i = 0; i < NUM_DMA_BUFFERS; i++) { + if (priv(dev)->dio_buffer[i]) + pci_free_consistent(priv(dev)->hw_dev, + DMA_BUFFER_SIZE, + priv(dev)-> + dio_buffer[i], + priv + (dev)->dio_buffer_phys_addr + [i]); + } + /* free dma descriptors */ + if (priv(dev)->dma_desc) + pci_free_consistent(priv(dev)->hw_dev, + sizeof(struct plx_dma_desc) + * NUM_DMA_DESCRIPTORS, + priv(dev)->dma_desc, + priv(dev)-> + dma_desc_phys_addr); + if (priv(dev)->hpdi_phys_iobase) + comedi_pci_disable(priv(dev)->hw_dev); + pci_dev_put(priv(dev)->hw_dev); + } + return 0; +} + +static int dio_config_block_size(struct comedi_device *dev, unsigned int *data) +{ + unsigned int requested_block_size; + int retval; + + requested_block_size = data[1]; + + retval = setup_dma_descriptors(dev, requested_block_size); + if (retval < 0) + return retval; + + data[1] = retval; + + return 2; +} + +static int di_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + int i; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually + * compatible */ + + /* uniqueness check */ + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (!cmd->chanlist_len) { + cmd->chanlist_len = 32; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + break; + case TRIG_NONE: + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + break; + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (err) + return 4; + + if (!cmd->chanlist) + return 0; + + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != i) { + /* XXX could support 8 or 16 channels */ + comedi_error(dev, + "chanlist must be ch 0 to 31 in order"); + err++; + break; + } + } + + if (err) + return 5; + + return 0; +} + +static int hpdi_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + if (priv(dev)->dio_config_output) + return -EINVAL; + else + return di_cmd_test(dev, s, cmd); +} + +static inline void hpdi_writel(struct comedi_device *dev, uint32_t bits, + unsigned int offset) +{ + writel(bits | priv(dev)->bits[offset / sizeof(uint32_t)], + priv(dev)->hpdi_iobase + offset); +} + +static int di_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + uint32_t bits; + unsigned long flags; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + + hpdi_writel(dev, RX_FIFO_RESET_BIT, BOARD_CONTROL_REG); + + DEBUG_PRINT("hpdi: in di_cmd\n"); + + abort_dma(dev, 0); + + priv(dev)->dma_desc_index = 0; + + /* These register are supposedly unused during chained dma, + * but I have found that left over values from last operation + * occasionally cause problems with transfer of first dma + * block. Initializing them to zero seems to fix the problem. */ + writel(0, priv(dev)->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG); + writel(0, priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG); + writel(0, priv(dev)->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG); + /* give location of first dma descriptor */ + bits = + priv(dev)->dma_desc_phys_addr | PLX_DESC_IN_PCI_BIT | + PLX_INTR_TERM_COUNT | PLX_XFER_LOCAL_TO_PCI; + writel(bits, priv(dev)->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG); + + /* spinlock for plx dma control/status reg */ + spin_lock_irqsave(&dev->spinlock, flags); + /* enable dma transfer */ + writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT | PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + if (cmd->stop_src == TRIG_COUNT) + priv(dev)->dio_count = cmd->stop_arg; + else + priv(dev)->dio_count = 1; + + /* clear over/under run status flags */ + writel(RX_UNDERRUN_BIT | RX_OVERRUN_BIT, + priv(dev)->hpdi_iobase + BOARD_STATUS_REG); + /* enable interrupts */ + writel(intr_bit(RX_FULL_INTR), + priv(dev)->hpdi_iobase + INTERRUPT_CONTROL_REG); + + DEBUG_PRINT("hpdi: starting rx\n"); + hpdi_writel(dev, RX_ENABLE_BIT, BOARD_CONTROL_REG); + + return 0; +} + +static int hpdi_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + if (priv(dev)->dio_config_output) + return -EINVAL; + else + return di_cmd(dev, s); +} + +static void drain_dma_buffers(struct comedi_device *dev, unsigned int channel) +{ + struct comedi_async *async = dev->read_subdev->async; + uint32_t next_transfer_addr; + int j; + int num_samples = 0; + void *pci_addr_reg; + + if (channel) + pci_addr_reg = + priv(dev)->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG; + else + pci_addr_reg = + priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG; + + /* loop until we have read all the full buffers */ + j = 0; + for (next_transfer_addr = readl(pci_addr_reg); + (next_transfer_addr < + le32_to_cpu(priv(dev)->dma_desc[priv(dev)->dma_desc_index]. + pci_start_addr) + || next_transfer_addr >= + le32_to_cpu(priv(dev)->dma_desc[priv(dev)->dma_desc_index]. + pci_start_addr) + priv(dev)->block_size) + && j < priv(dev)->num_dma_descriptors; j++) { + /* transfer data from dma buffer to comedi buffer */ + num_samples = priv(dev)->block_size / sizeof(uint32_t); + if (async->cmd.stop_src == TRIG_COUNT) { + if (num_samples > priv(dev)->dio_count) + num_samples = priv(dev)->dio_count; + priv(dev)->dio_count -= num_samples; + } + cfc_write_array_to_buffer(dev->read_subdev, + priv(dev)->desc_dio_buffer[priv(dev)-> + dma_desc_index], + num_samples * sizeof(uint32_t)); + priv(dev)->dma_desc_index++; + priv(dev)->dma_desc_index %= priv(dev)->num_dma_descriptors; + + DEBUG_PRINT("next desc addr 0x%lx\n", (unsigned long) + priv(dev)->dma_desc[priv(dev)->dma_desc_index]. + next); + DEBUG_PRINT("pci addr reg 0x%x\n", next_transfer_addr); + } + /* XXX check for buffer overrun somehow */ +} + +static irqreturn_t handle_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + uint32_t hpdi_intr_status, hpdi_board_status; + uint32_t plx_status; + uint32_t plx_bits; + uint8_t dma0_status, dma1_status; + unsigned long flags; + + if (!dev->attached) + return IRQ_NONE; + + plx_status = readl(priv(dev)->plx9080_iobase + PLX_INTRCS_REG); + if ((plx_status & (ICS_DMA0_A | ICS_DMA1_A | ICS_LIA)) == 0) + return IRQ_NONE; + + hpdi_intr_status = readl(priv(dev)->hpdi_iobase + INTERRUPT_STATUS_REG); + hpdi_board_status = readl(priv(dev)->hpdi_iobase + BOARD_STATUS_REG); + + async->events = 0; + + if (hpdi_intr_status) { + DEBUG_PRINT("hpdi: intr status 0x%x, ", hpdi_intr_status); + writel(hpdi_intr_status, + priv(dev)->hpdi_iobase + INTERRUPT_STATUS_REG); + } + /* spin lock makes sure no one else changes plx dma control reg */ + spin_lock_irqsave(&dev->spinlock, flags); + dma0_status = readb(priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + if (plx_status & ICS_DMA0_A) { /* dma chan 0 interrupt */ + writeb((dma0_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG); + + DEBUG_PRINT("dma0 status 0x%x\n", dma0_status); + if (dma0_status & PLX_DMA_EN_BIT) + drain_dma_buffers(dev, 0); + DEBUG_PRINT(" cleared dma ch0 interrupt\n"); + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* spin lock makes sure no one else changes plx dma control reg */ + spin_lock_irqsave(&dev->spinlock, flags); + dma1_status = readb(priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG); + if (plx_status & ICS_DMA1_A) { /* XXX *//* dma chan 1 interrupt */ + writeb((dma1_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT, + priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG); + DEBUG_PRINT("dma1 status 0x%x\n", dma1_status); + + DEBUG_PRINT(" cleared dma ch1 interrupt\n"); + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* clear possible plx9080 interrupt sources */ + if (plx_status & ICS_LDIA) { /* clear local doorbell interrupt */ + plx_bits = readl(priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG); + writel(plx_bits, priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG); + DEBUG_PRINT(" cleared local doorbell bits 0x%x\n", plx_bits); + } + + if (hpdi_board_status & RX_OVERRUN_BIT) { + comedi_error(dev, "rx fifo overrun"); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + DEBUG_PRINT("dma0_status 0x%x\n", + (int)readb(priv(dev)->plx9080_iobase + + PLX_DMA0_CS_REG)); + } + + if (hpdi_board_status & RX_UNDERRUN_BIT) { + comedi_error(dev, "rx fifo underrun"); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + } + + if (priv(dev)->dio_count == 0) + async->events |= COMEDI_CB_EOA; + + DEBUG_PRINT("board status 0x%x, ", hpdi_board_status); + DEBUG_PRINT("plx status 0x%x\n", plx_status); + if (async->events) + DEBUG_PRINT(" events 0x%x\n", async->events); + + cfc_handle_events(dev, s); + + return IRQ_HANDLED; +} + +static void abort_dma(struct comedi_device *dev, unsigned int channel) +{ + unsigned long flags; + + /* spinlock for plx dma control/status reg */ + spin_lock_irqsave(&dev->spinlock, flags); + + plx9080_abort_dma(priv(dev)->plx9080_iobase, channel); + + spin_unlock_irqrestore(&dev->spinlock, flags); +} + +static int hpdi_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + hpdi_writel(dev, 0, BOARD_CONTROL_REG); + + writel(0, priv(dev)->hpdi_iobase + INTERRUPT_CONTROL_REG); + + abort_dma(dev, 0); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/icp_multi.c b/drivers/staging/comedi/drivers/icp_multi.c new file mode 100644 index 00000000..126550f3 --- /dev/null +++ b/drivers/staging/comedi/drivers/icp_multi.c @@ -0,0 +1,1142 @@ +/* + comedi/drivers/icp_multi.c + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2002 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* +Driver: icp_multi +Description: Inova ICP_MULTI +Author: Anne Smorthit +Devices: [Inova] ICP_MULTI (icp_multi) +Status: works + +The driver works for analog input and output and digital input and output. +It does not work with interrupts or with the counters. Currently no support +for DMA. + +It has 16 single-ended or 8 differential Analogue Input channels with 12-bit +resolution. Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA. Input +ranges can be individually programmed for each channel. Voltage or current +measurement is selected by jumper. + +There are 4 x 12-bit Analogue Outputs. Ranges : 5V, 10V, +/-5V, +/-10V + +16 x Digital Inputs, 24V + +8 x Digital Outputs, 24V, 1A + +4 x 16-bit counters + +Options: + [0] - PCI bus number - if bus number and slot number are 0, + then driver search for first unused card + [1] - PCI slot number +*/ + +#include +#include "../comedidev.h" + +#include +#include + +#include "icp_multi.h" + +#define DEVICE_ID 0x8000 /* Device ID */ + +#define ICP_MULTI_EXTDEBUG + +/* Hardware types of the cards */ +#define TYPE_ICP_MULTI 0 + +#define IORANGE_ICP_MULTI 32 + +#define ICP_MULTI_ADC_CSR 0 /* R/W: ADC command/status register */ +#define ICP_MULTI_AI 2 /* R: Analogue input data */ +#define ICP_MULTI_DAC_CSR 4 /* R/W: DAC command/status register */ +#define ICP_MULTI_AO 6 /* R/W: Analogue output data */ +#define ICP_MULTI_DI 8 /* R/W: Digital inouts */ +#define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */ +#define ICP_MULTI_INT_EN 0x0C /* R/W: Interrupt enable register */ +#define ICP_MULTI_INT_STAT 0x0E /* R/W: Interrupt status register */ +#define ICP_MULTI_CNTR0 0x10 /* R/W: Counter 0 */ +#define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */ +#define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */ +#define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */ + +#define ICP_MULTI_SIZE 0x20 /* 32 bytes */ + +/* Define bits from ADC command/status register */ +#define ADC_ST 0x0001 /* Start ADC */ +#define ADC_BSY 0x0001 /* ADC busy */ +#define ADC_BI 0x0010 /* Bipolar input range 1 = bipolar */ +#define ADC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ +#define ADC_DI 0x0040 /* Differential input mode 1 = differential */ + +/* Define bits from DAC command/status register */ +#define DAC_ST 0x0001 /* Start DAC */ +#define DAC_BSY 0x0001 /* DAC busy */ +#define DAC_BI 0x0010 /* Bipolar input range 1 = bipolar */ +#define DAC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ + +/* Define bits from interrupt enable/status registers */ +#define ADC_READY 0x0001 /* A/d conversion ready interrupt */ +#define DAC_READY 0x0002 /* D/a conversion ready interrupt */ +#define DOUT_ERROR 0x0004 /* Digital output error interrupt */ +#define DIN_STATUS 0x0008 /* Digital input status change interrupt */ +#define CIE0 0x0010 /* Counter 0 overrun interrupt */ +#define CIE1 0x0020 /* Counter 1 overrun interrupt */ +#define CIE2 0x0040 /* Counter 2 overrun interrupt */ +#define CIE3 0x0080 /* Counter 3 overrun interrupt */ + +/* Useful definitions */ +#define Status_IRQ 0x00ff /* All interrupts */ + +/* Define analogue range */ +static const struct comedi_lrange range_analog = { 4, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(10) + } +}; + +static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 }; + +/* +============================================================================== + Forward declarations +============================================================================== +*/ +static int icp_multi_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int icp_multi_detach(struct comedi_device *dev); + +/* +============================================================================== + Data & Structure declarations +============================================================================== +*/ +static unsigned short pci_list_builded; /*>0 list of card is known */ + +struct boardtype { + const char *name; /* driver name */ + int device_id; + int iorange; /* I/O range len */ + char have_irq; /* 1=card support IRQ */ + char cardtype; /* 0=ICP Multi */ + int n_aichan; /* num of A/D chans */ + int n_aichand; /* num of A/D chans in diff mode */ + int n_aochan; /* num of D/A chans */ + int n_dichan; /* num of DI chans */ + int n_dochan; /* num of DO chans */ + int n_ctrs; /* num of counters */ + int ai_maxdata; /* resolution of A/D */ + int ao_maxdata; /* resolution of D/A */ + const struct comedi_lrange *rangelist_ai; /* rangelist for A/D */ + const char *rangecode; /* range codes for programming */ + const struct comedi_lrange *rangelist_ao; /* rangelist for D/A */ +}; + +static const struct boardtype boardtypes[] = { + {"icp_multi", /* Driver name */ + DEVICE_ID, /* PCI device ID */ + IORANGE_ICP_MULTI, /* I/O range length */ + 1, /* 1=Card supports interrupts */ + TYPE_ICP_MULTI, /* Card type = ICP MULTI */ + 16, /* Num of A/D channels */ + 8, /* Num of A/D channels in diff mode */ + 4, /* Num of D/A channels */ + 16, /* Num of digital inputs */ + 8, /* Num of digital outputs */ + 4, /* Num of counters */ + 0x0fff, /* Resolution of A/D */ + 0x0fff, /* Resolution of D/A */ + &range_analog, /* Rangelist for A/D */ + range_codes_analog, /* Range codes for programming */ + &range_analog}, /* Rangelist for D/A */ +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct boardtype)) + +static struct comedi_driver driver_icp_multi = { + .driver_name = "icp_multi", + .module = THIS_MODULE, + .attach = icp_multi_attach, + .detach = icp_multi_detach, + .num_names = n_boardtypes, + .board_name = &boardtypes[0].name, + .offset = sizeof(struct boardtype), +}; + +static int __init driver_icp_multi_init_module(void) +{ + return comedi_driver_register(&driver_icp_multi); +} + +static void __exit driver_icp_multi_cleanup_module(void) +{ + comedi_driver_unregister(&driver_icp_multi); +} + +module_init(driver_icp_multi_init_module); +module_exit(driver_icp_multi_cleanup_module); + +struct icp_multi_private { + struct pcilst_struct *card; /* pointer to card */ + char valid; /* card is usable */ + void *io_addr; /* Pointer to mapped io address */ + resource_size_t phys_iobase; /* Physical io address */ + unsigned int AdcCmdStatus; /* ADC Command/Status register */ + unsigned int DacCmdStatus; /* DAC Command/Status register */ + unsigned int IntEnable; /* Interrupt Enable register */ + unsigned int IntStatus; /* Interrupt Status register */ + unsigned int act_chanlist[32]; /* list of scaned channel */ + unsigned char act_chanlist_len; /* len of scanlist */ + unsigned char act_chanlist_pos; /* actual position in MUX list */ + unsigned int *ai_chanlist; /* actaul chanlist */ + short *ai_data; /* data buffer */ + short ao_data[4]; /* data output buffer */ + short di_data; /* Digital input data */ + unsigned int do_data; /* Remember digital output data */ +}; + +#define devpriv ((struct icp_multi_private *)dev->private) +#define this_board ((const struct boardtype *)dev->board_ptr) + +/* +============================================================================== + More forward declarations +============================================================================== +*/ + +#if 0 +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan); +#endif +static void setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan); +static int icp_multi_reset(struct comedi_device *dev); + +/* +============================================================================== + Functions +============================================================================== +*/ + +/* +============================================================================== + +Name: icp_multi_insn_read_ai + +Description: + This function reads a single analogue input. + +Parameters: + struct comedi_device *dev Pointer to current device structure + struct comedi_subdevice *s Pointer to current subdevice structure + struct comedi_insn *insn Pointer to current comedi instruction + unsigned int *data Pointer to analogue input data + +Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_read_ai(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, timeout; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG "icp multi EDBG: BGN: icp_multi_insn_read_ai(...)\n"); +#endif + /* Disable A/D conversion ready interrupt */ + devpriv->IntEnable &= ~ADC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + /* Clear interrupt status */ + devpriv->IntStatus |= ADC_READY; + writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); + + /* Set up appropriate channel, mode and range data, for specified ch */ + setup_channel_list(dev, s, &insn->chanspec, 1); + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG "icp_multi A ST=%4x IO=%p\n", + readw(devpriv->io_addr + ICP_MULTI_ADC_CSR), + devpriv->io_addr + ICP_MULTI_ADC_CSR); +#endif + + for (n = 0; n < insn->n; n++) { + /* Set start ADC bit */ + devpriv->AdcCmdStatus |= ADC_ST; + writew(devpriv->AdcCmdStatus, + devpriv->io_addr + ICP_MULTI_ADC_CSR); + devpriv->AdcCmdStatus &= ~ADC_ST; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG "icp multi B n=%d ST=%4x\n", n, + readw(devpriv->io_addr + ICP_MULTI_ADC_CSR)); +#endif + + udelay(1); + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG "icp multi C n=%d ST=%4x\n", n, + readw(devpriv->io_addr + ICP_MULTI_ADC_CSR)); +#endif + + /* Wait for conversion to complete, or get fed up waiting */ + timeout = 100; + while (timeout--) { + if (!(readw(devpriv->io_addr + + ICP_MULTI_ADC_CSR) & ADC_BSY)) + goto conv_finish; + +#ifdef ICP_MULTI_EXTDEBUG + if (!(timeout % 10)) + printk(KERN_DEBUG + "icp multi D n=%d tm=%d ST=%4x\n", n, + timeout, + readw(devpriv->io_addr + + ICP_MULTI_ADC_CSR)); +#endif + + udelay(1); + } + + /* If we reach here, a timeout has occurred */ + comedi_error(dev, "A/D insn timeout"); + + /* Disable interrupt */ + devpriv->IntEnable &= ~ADC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + /* Clear interrupt status */ + devpriv->IntStatus |= ADC_READY; + writew(devpriv->IntStatus, + devpriv->io_addr + ICP_MULTI_INT_STAT); + + /* Clear data received */ + data[n] = 0; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: END: icp_multi_insn_read_ai(...) n=%d\n", + n); +#endif + return -ETIME; + +conv_finish: + data[n] = + (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff; + } + + /* Disable interrupt */ + devpriv->IntEnable &= ~ADC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + /* Clear interrupt status */ + devpriv->IntStatus |= ADC_READY; + writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: END: icp_multi_insn_read_ai(...) n=%d\n", n); +#endif + return n; +} + +/* +============================================================================== + +Name: icp_multi_insn_write_ao + +Description: + This function writes a single analogue output. + +Parameters: + struct comedi_device *dev Pointer to current device structure + struct comedi_subdevice *s Pointer to current subdevice structure + struct comedi_insn *insn Pointer to current comedi instruction + unsigned int *data Pointer to analogue output data + +Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_write_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, chan, range, timeout; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: BGN: icp_multi_insn_write_ao(...)\n"); +#endif + /* Disable D/A conversion ready interrupt */ + devpriv->IntEnable &= ~DAC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + /* Clear interrupt status */ + devpriv->IntStatus |= DAC_READY; + writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); + + /* Get channel number and range */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + /* Set up range and channel data */ + /* Bit 4 = 1 : Bipolar */ + /* Bit 5 = 0 : 5V */ + /* Bit 5 = 1 : 10V */ + /* Bits 8-9 : Channel number */ + devpriv->DacCmdStatus &= 0xfccf; + devpriv->DacCmdStatus |= this_board->rangecode[range]; + devpriv->DacCmdStatus |= (chan << 8); + + writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR); + + for (n = 0; n < insn->n; n++) { + /* Wait for analogue output data register to be + * ready for new data, or get fed up waiting */ + timeout = 100; + while (timeout--) { + if (!(readw(devpriv->io_addr + + ICP_MULTI_DAC_CSR) & DAC_BSY)) + goto dac_ready; + +#ifdef ICP_MULTI_EXTDEBUG + if (!(timeout % 10)) + printk(KERN_DEBUG + "icp multi A n=%d tm=%d ST=%4x\n", n, + timeout, + readw(devpriv->io_addr + + ICP_MULTI_DAC_CSR)); +#endif + + udelay(1); + } + + /* If we reach here, a timeout has occurred */ + comedi_error(dev, "D/A insn timeout"); + + /* Disable interrupt */ + devpriv->IntEnable &= ~DAC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + /* Clear interrupt status */ + devpriv->IntStatus |= DAC_READY; + writew(devpriv->IntStatus, + devpriv->io_addr + ICP_MULTI_INT_STAT); + + /* Clear data received */ + devpriv->ao_data[chan] = 0; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: END: icp_multi_insn_write_ao(...) n=%d\n", + n); +#endif + return -ETIME; + +dac_ready: + /* Write data to analogue output data register */ + writew(data[n], devpriv->io_addr + ICP_MULTI_AO); + + /* Set DAC_ST bit to write the data to selected channel */ + devpriv->DacCmdStatus |= DAC_ST; + writew(devpriv->DacCmdStatus, + devpriv->io_addr + ICP_MULTI_DAC_CSR); + devpriv->DacCmdStatus &= ~DAC_ST; + + /* Save analogue output data */ + devpriv->ao_data[chan] = data[n]; + } + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: END: icp_multi_insn_write_ao(...) n=%d\n", n); +#endif + return n; +} + +/* +============================================================================== + +Name: icp_multi_insn_read_ao + +Description: + This function reads a single analogue output. + +Parameters: + struct comedi_device *dev Pointer to current device structure + struct comedi_subdevice *s Pointer to current subdevice structure + struct comedi_insn *insn Pointer to current comedi instruction + unsigned int *data Pointer to analogue output data + +Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_read_ao(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, chan; + + /* Get channel number */ + chan = CR_CHAN(insn->chanspec); + + /* Read analogue outputs */ + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_data[chan]; + + return n; +} + +/* +============================================================================== + +Name: icp_multi_insn_bits_di + +Description: + This function reads the digital inputs. + +Parameters: + struct comedi_device *dev Pointer to current device structure + struct comedi_subdevice *s Pointer to current subdevice structure + struct comedi_insn *insn Pointer to current comedi instruction + unsigned int *data Pointer to analogue output data + +Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_bits_di(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); + + return 2; +} + +/* +============================================================================== + +Name: icp_multi_insn_bits_do + +Description: + This function writes the appropriate digital outputs. + +Parameters: + struct comedi_device *dev Pointer to current device structure + struct comedi_subdevice *s Pointer to current subdevice structure + struct comedi_insn *insn Pointer to current comedi instruction + unsigned int *data Pointer to analogue output data + +Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_bits_do(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG "icp multi EDBG: BGN: icp_multi_insn_bits_do(...)\n"); +#endif + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + + printk(KERN_DEBUG "Digital outputs = %4x \n", s->state); + + writew(s->state, devpriv->io_addr + ICP_MULTI_DO); + } + + data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG "icp multi EDBG: END: icp_multi_insn_bits_do(...)\n"); +#endif + return 2; +} + +/* +============================================================================== + +Name: icp_multi_insn_read_ctr + +Description: + This function reads the specified counter. + +Parameters: + struct comedi_device *dev Pointer to current device structure + struct comedi_subdevice *s Pointer to current subdevice structure + struct comedi_insn *insn Pointer to current comedi instruction + unsigned int *data Pointer to counter data + +Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_read_ctr(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + return 0; +} + +/* +============================================================================== + +Name: icp_multi_insn_write_ctr + +Description: + This function write to the specified counter. + +Parameters: + struct comedi_device *dev Pointer to current device structure + struct comedi_subdevice *s Pointer to current subdevice structure + struct comedi_insn *insn Pointer to current comedi instruction + unsigned int *data Pointer to counter data + +Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_write_ctr(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + return 0; +} + +/* +============================================================================== + +Name: interrupt_service_icp_multi + +Description: + This function is the interrupt service routine for all + interrupts generated by the icp multi board. + +Parameters: + int irq + void *d Pointer to current device + +============================================================================== +*/ +static irqreturn_t interrupt_service_icp_multi(int irq, void *d) +{ + struct comedi_device *dev = d; + int int_no; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: BGN: interrupt_service_icp_multi(%d,...)\n", + irq); +#endif + + /* Is this interrupt from our board? */ + int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ; + if (!int_no) + /* No, exit */ + return IRQ_NONE; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: interrupt_service_icp_multi() ST: %4x\n", + readw(devpriv->io_addr + ICP_MULTI_INT_STAT)); +#endif + + /* Determine which interrupt is active & handle it */ + switch (int_no) { + case ADC_READY: + break; + case DAC_READY: + break; + case DOUT_ERROR: + break; + case DIN_STATUS: + break; + case CIE0: + break; + case CIE1: + break; + case CIE2: + break; + case CIE3: + break; + default: + break; + + } + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: END: interrupt_service_icp_multi(...)\n"); +#endif + return IRQ_HANDLED; +} + +#if 0 +/* +============================================================================== + +Name: check_channel_list + +Description: + This function checks if the channel list, provided by user + is built correctly + +Parameters: + struct comedi_device *dev Pointer to current service structure + struct comedi_subdevice *s Pointer to current subdevice structure + unsigned int *chanlist Pointer to packed channel list + unsigned int n_chan Number of channels to scan + +Returns:int 0 = failure + 1 = success + +============================================================================== +*/ +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan) +{ + unsigned int i; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: check_channel_list(...,%d)\n", n_chan); +#endif + /* Check that we at least have one channel to check */ + if (n_chan < 1) { + comedi_error(dev, "range/channel list is empty!"); + return 0; + } + /* Check all channels */ + for (i = 0; i < n_chan; i++) { + /* Check that channel number is < maximum */ + if (CR_AREF(chanlist[i]) == AREF_DIFF) { + if (CR_CHAN(chanlist[i]) > this_board->n_aichand) { + comedi_error(dev, + "Incorrect differential ai ch-nr"); + return 0; + } + } else { + if (CR_CHAN(chanlist[i]) > this_board->n_aichan) { + comedi_error(dev, + "Incorrect ai channel number"); + return 0; + } + } + } + return 1; +} +#endif + +/* +============================================================================== + +Name: setup_channel_list + +Description: + This function sets the appropriate channel selection, + differential input mode and range bits in the ADC Command/ + Status register. + +Parameters: + struct comedi_device *dev Pointer to current service structure + struct comedi_subdevice *s Pointer to current subdevice structure + unsigned int *chanlist Pointer to packed channel list + unsigned int n_chan Number of channels to scan + +Returns:Void + +============================================================================== +*/ +static void setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan) +{ + unsigned int i, range, chanprog; + unsigned int diff; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: setup_channel_list(...,%d)\n", n_chan); +#endif + devpriv->act_chanlist_len = n_chan; + devpriv->act_chanlist_pos = 0; + + for (i = 0; i < n_chan; i++) { + /* Get channel */ + chanprog = CR_CHAN(chanlist[i]); + + /* Determine if it is a differential channel (Bit 15 = 1) */ + if (CR_AREF(chanlist[i]) == AREF_DIFF) { + diff = 1; + chanprog &= 0x0007; + } else { + diff = 0; + chanprog &= 0x000f; + } + + /* Clear channel, range and input mode bits + * in A/D command/status register */ + devpriv->AdcCmdStatus &= 0xf00f; + + /* Set channel number and differential mode status bit */ + if (diff) { + /* Set channel number, bits 9-11 & mode, bit 6 */ + devpriv->AdcCmdStatus |= (chanprog << 9); + devpriv->AdcCmdStatus |= ADC_DI; + } else + /* Set channel number, bits 8-11 */ + devpriv->AdcCmdStatus |= (chanprog << 8); + + /* Get range for current channel */ + range = this_board->rangecode[CR_RANGE(chanlist[i])]; + /* Set range. bits 4-5 */ + devpriv->AdcCmdStatus |= range; + + /* Output channel, range, mode to ICP Multi */ + writew(devpriv->AdcCmdStatus, + devpriv->io_addr + ICP_MULTI_ADC_CSR); + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "GS: %2d. [%4x]=%4x %4x\n", i, chanprog, range, + devpriv->act_chanlist[i]); +#endif + } + +} + +/* +============================================================================== + +Name: icp_multi_reset + +Description: + This function resets the icp multi device to a 'safe' state + +Parameters: + struct comedi_device *dev Pointer to current service structure + +Returns:int 0 = success + +============================================================================== +*/ +static int icp_multi_reset(struct comedi_device *dev) +{ + unsigned int i; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp_multi EDBG: BGN: icp_multi_reset(...)\n"); +#endif + /* Clear INT enables and requests */ + writew(0, devpriv->io_addr + ICP_MULTI_INT_EN); + writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT); + + if (this_board->n_aochan) + /* Set DACs to 0..5V range and 0V output */ + for (i = 0; i < this_board->n_aochan; i++) { + devpriv->DacCmdStatus &= 0xfcce; + + /* Set channel number */ + devpriv->DacCmdStatus |= (i << 8); + + /* Output 0V */ + writew(0, devpriv->io_addr + ICP_MULTI_AO); + + /* Set start conversion bit */ + devpriv->DacCmdStatus |= DAC_ST; + + /* Output to command / status register */ + writew(devpriv->DacCmdStatus, + devpriv->io_addr + ICP_MULTI_DAC_CSR); + + /* Delay to allow DAC time to recover */ + udelay(1); + } + /* Digital outputs to 0 */ + writew(0, devpriv->io_addr + ICP_MULTI_DO); + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "icp multi EDBG: END: icp_multi_reset(...)\n"); +#endif + return 0; +} + +/* +============================================================================== + +Name: icp_multi_attach + +Description: + This function sets up all the appropriate data for the current + device. + +Parameters: + struct comedi_device *dev Pointer to current device structure + struct comedi_devconfig *it Pointer to current device configuration + +Returns:int 0 = success + +============================================================================== +*/ +static int icp_multi_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret, subdev, n_subdevices; + unsigned int irq; + struct pcilst_struct *card = NULL; + resource_size_t io_addr[5], iobase; + unsigned char pci_bus, pci_slot, pci_func; + + printk(KERN_WARNING + "icp_multi EDBG: BGN: icp_multi_attach(...)\n"); + + /* Alocate private data storage space */ + ret = alloc_private(dev, sizeof(struct icp_multi_private)); + if (ret < 0) + return ret; + + /* Initialise list of PCI cards in system, if not already done so */ + if (pci_list_builded++ == 0) { + pci_card_list_init(PCI_VENDOR_ID_ICP, +#ifdef ICP_MULTI_EXTDEBUG + 1 +#else + 0 +#endif + ); + } + + printk(KERN_WARNING + "Anne's comedi%d: icp_multi: board=%s", dev->minor, + this_board->name); + + card = select_and_alloc_pci_card(PCI_VENDOR_ID_ICP, + this_board->device_id, it->options[0], + it->options[1]); + + if (card == NULL) + return -EIO; + + devpriv->card = card; + + if ((pci_card_data(card, &pci_bus, &pci_slot, &pci_func, &io_addr[0], + &irq)) < 0) { + printk(KERN_WARNING " - Can't get configuration data!\n"); + return -EIO; + } + + iobase = io_addr[2]; + devpriv->phys_iobase = iobase; + + printk(KERN_WARNING + ", b:s:f=%d:%d:%d, io=0x%8llx \n", pci_bus, pci_slot, pci_func, + (unsigned long long)iobase); + + devpriv->io_addr = ioremap(iobase, ICP_MULTI_SIZE); + + if (devpriv->io_addr == NULL) { + printk(KERN_WARNING "ioremap failed.\n"); + return -ENOMEM; + } +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG + "0x%08llx mapped to %p, ", (unsigned long long)iobase, + devpriv->io_addr); +#endif + + dev->board_name = this_board->name; + + n_subdevices = 0; + if (this_board->n_aichan) + n_subdevices++; + if (this_board->n_aochan) + n_subdevices++; + if (this_board->n_dichan) + n_subdevices++; + if (this_board->n_dochan) + n_subdevices++; + if (this_board->n_ctrs) + n_subdevices++; + + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) + return ret; + + icp_multi_reset(dev); + + if (this_board->have_irq) { + if (irq) { + if (request_irq(irq, interrupt_service_icp_multi, + IRQF_SHARED, "Inova Icp Multi", dev)) { + printk(KERN_WARNING + "unable to allocate IRQ %u, DISABLING IT", + irq); + irq = 0; /* Can't use IRQ */ + } else + printk(KERN_WARNING ", irq=%u", irq); + } else + printk(KERN_WARNING ", IRQ disabled"); + } else + irq = 0; + + dev->irq = irq; + + printk(KERN_WARNING ".\n"); + + subdev = 0; + + if (this_board->n_aichan) { + s = dev->subdevices + subdev; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND; + if (this_board->n_aichand) + s->subdev_flags |= SDF_DIFF; + s->n_chan = this_board->n_aichan; + s->maxdata = this_board->ai_maxdata; + s->len_chanlist = this_board->n_aichan; + s->range_table = this_board->rangelist_ai; + s->insn_read = icp_multi_insn_read_ai; + subdev++; + } + + if (this_board->n_aochan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->n_aochan; + s->range_table = this_board->rangelist_ao; + s->insn_write = icp_multi_insn_write_ao; + s->insn_read = icp_multi_insn_read_ao; + subdev++; + } + + if (this_board->n_dichan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = this_board->n_dichan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dichan; + s->range_table = &range_digital; + s->io_bits = 0; + s->insn_bits = icp_multi_insn_bits_di; + subdev++; + } + + if (this_board->n_dochan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = this_board->n_dochan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dochan; + s->range_table = &range_digital; + s->io_bits = (1 << this_board->n_dochan) - 1; + s->state = 0; + s->insn_bits = icp_multi_insn_bits_do; + subdev++; + } + + if (this_board->n_ctrs) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_ctrs; + s->maxdata = 0xffff; + s->len_chanlist = this_board->n_ctrs; + s->state = 0; + s->insn_read = icp_multi_insn_read_ctr; + s->insn_write = icp_multi_insn_write_ctr; + subdev++; + } + + devpriv->valid = 1; + +#ifdef ICP_MULTI_EXTDEBUG + printk(KERN_DEBUG "icp multi EDBG: END: icp_multi_attach(...)\n"); +#endif + + return 0; +} + +/* +============================================================================== + +Name: icp_multi_detach + +Description: + This function releases all the resources used by the current + device. + +Parameters: + struct comedi_device *dev Pointer to current device structure + +Returns:int 0 = success + +============================================================================== +*/ +static int icp_multi_detach(struct comedi_device *dev) +{ + + if (dev->private) + if (devpriv->valid) + icp_multi_reset(dev); + + if (dev->irq) + free_irq(dev->irq, dev); + + if (dev->private && devpriv->io_addr) + iounmap(devpriv->io_addr); + + if (dev->private && devpriv->card) + pci_card_free(devpriv->card); + + if (--pci_list_builded == 0) + pci_card_list_cleanup(PCI_VENDOR_ID_ICP); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/icp_multi.h b/drivers/staging/comedi/drivers/icp_multi.h new file mode 100644 index 00000000..68acefe1 --- /dev/null +++ b/drivers/staging/comedi/drivers/icp_multi.h @@ -0,0 +1,298 @@ +/* + comedi/drivers/icp_multi.h + + Stuff for ICP Multi + + Author: Anne Smorthit + +*/ + +#ifndef _ICP_MULTI_H_ +#define _ICP_MULTI_H_ + +#include "../comedidev.h" +#include "comedi_pci.h" + +/****************************************************************************/ + +struct pcilst_struct { + struct pcilst_struct *next; + int used; + struct pci_dev *pcidev; + unsigned short vendor; + unsigned short device; + unsigned char pci_bus; + unsigned char pci_slot; + unsigned char pci_func; + resource_size_t io_addr[5]; + unsigned int irq; +}; + +struct pcilst_struct *inova_devices; +/* ptr to root list of all Inova devices */ + +/****************************************************************************/ + +static void pci_card_list_init(unsigned short pci_vendor, char display); +static void pci_card_list_cleanup(unsigned short pci_vendor); +static struct pcilst_struct *find_free_pci_card_by_device(unsigned short + vendor_id, + unsigned short + device_id); +static int find_free_pci_card_by_position(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot, + struct pcilst_struct **card); +static struct pcilst_struct *select_and_alloc_pci_card(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot); + +static int pci_card_alloc(struct pcilst_struct *amcc); +static int pci_card_free(struct pcilst_struct *amcc); +static void pci_card_list_display(void); +static int pci_card_data(struct pcilst_struct *amcc, + unsigned char *pci_bus, unsigned char *pci_slot, + unsigned char *pci_func, resource_size_t * io_addr, + unsigned int *irq); + +/****************************************************************************/ + +/* build list of Inova cards in this system */ +static void pci_card_list_init(unsigned short pci_vendor, char display) +{ + struct pci_dev *pcidev = NULL; + struct pcilst_struct *inova, *last; + int i; + + inova_devices = NULL; + last = NULL; + + for_each_pci_dev(pcidev) { + if (pcidev->vendor == pci_vendor) { + inova = kzalloc(sizeof(*inova), GFP_KERNEL); + if (!inova) { + printk + ("icp_multi: pci_card_list_init: allocation failed\n"); + pci_dev_put(pcidev); + break; + } + + inova->pcidev = pci_dev_get(pcidev); + if (last) { + last->next = inova; + } else { + inova_devices = inova; + } + last = inova; + + inova->vendor = pcidev->vendor; + inova->device = pcidev->device; + inova->pci_bus = pcidev->bus->number; + inova->pci_slot = PCI_SLOT(pcidev->devfn); + inova->pci_func = PCI_FUNC(pcidev->devfn); + /* Note: resources may be invalid if PCI device + * not enabled, but they are corrected in + * pci_card_alloc. */ + for (i = 0; i < 5; i++) + inova->io_addr[i] = + pci_resource_start(pcidev, i); + inova->irq = pcidev->irq; + } + } + + if (display) + pci_card_list_display(); +} + +/****************************************************************************/ +/* free up list of amcc cards in this system */ +static void pci_card_list_cleanup(unsigned short pci_vendor) +{ + struct pcilst_struct *inova, *next; + + for (inova = inova_devices; inova; inova = next) { + next = inova->next; + pci_dev_put(inova->pcidev); + kfree(inova); + } + + inova_devices = NULL; +} + +/****************************************************************************/ +/* find first unused card with this device_id */ +static struct pcilst_struct *find_free_pci_card_by_device(unsigned short + vendor_id, + unsigned short + device_id) +{ + struct pcilst_struct *inova, *next; + + for (inova = inova_devices; inova; inova = next) { + next = inova->next; + if ((!inova->used) && (inova->device == device_id) + && (inova->vendor == vendor_id)) + return inova; + + } + + return NULL; +} + +/****************************************************************************/ +/* find card on requested position */ +static int find_free_pci_card_by_position(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot, + struct pcilst_struct **card) +{ + struct pcilst_struct *inova, *next; + + *card = NULL; + for (inova = inova_devices; inova; inova = next) { + next = inova->next; + if ((inova->vendor == vendor_id) && (inova->device == device_id) + && (inova->pci_bus == pci_bus) + && (inova->pci_slot == pci_slot)) { + if (!(inova->used)) { + *card = inova; + return 0; /* ok, card is found */ + } else { + return 2; /* card exist but is used */ + } + } + } + + return 1; /* no card found */ +} + +/****************************************************************************/ +/* mark card as used */ +static int pci_card_alloc(struct pcilst_struct *inova) +{ + int i; + + if (!inova) { + printk(" - BUG!! inova is NULL!\n"); + return -1; + } + + if (inova->used) + return 1; + if (comedi_pci_enable(inova->pcidev, "icp_multi")) { + printk(" - Can't enable PCI device and request regions!\n"); + return -1; + } + /* Resources will be accurate now. */ + for (i = 0; i < 5; i++) + inova->io_addr[i] = pci_resource_start(inova->pcidev, i); + inova->irq = inova->pcidev->irq; + inova->used = 1; + return 0; +} + +/****************************************************************************/ +/* mark card as free */ +static int pci_card_free(struct pcilst_struct *inova) +{ + if (!inova) + return -1; + + if (!inova->used) + return 1; + inova->used = 0; + comedi_pci_disable(inova->pcidev); + return 0; +} + +/****************************************************************************/ +/* display list of found cards */ +static void pci_card_list_display(void) +{ + struct pcilst_struct *inova, *next; + + printk("Anne's List of pci cards\n"); + printk("bus:slot:func vendor device io_inova io_daq irq used\n"); + + for (inova = inova_devices; inova; inova = next) { + next = inova->next; + printk + ("%2d %2d %2d 0x%4x 0x%4x 0x%8llx 0x%8llx %2u %2d\n", + inova->pci_bus, inova->pci_slot, inova->pci_func, + inova->vendor, inova->device, + (unsigned long long)inova->io_addr[0], + (unsigned long long)inova->io_addr[2], inova->irq, + inova->used); + + } +} + +/****************************************************************************/ +/* return all card information for driver */ +static int pci_card_data(struct pcilst_struct *inova, + unsigned char *pci_bus, unsigned char *pci_slot, + unsigned char *pci_func, resource_size_t * io_addr, + unsigned int *irq) +{ + int i; + + if (!inova) + return -1; + *pci_bus = inova->pci_bus; + *pci_slot = inova->pci_slot; + *pci_func = inova->pci_func; + for (i = 0; i < 5; i++) + io_addr[i] = inova->io_addr[i]; + *irq = inova->irq; + return 0; +} + +/****************************************************************************/ +/* select and alloc card */ +static struct pcilst_struct *select_and_alloc_pci_card(unsigned short vendor_id, + unsigned short device_id, + unsigned short pci_bus, + unsigned short pci_slot) +{ + struct pcilst_struct *card; + int err; + + if ((pci_bus < 1) & (pci_slot < 1)) { /* use autodetection */ + + card = find_free_pci_card_by_device(vendor_id, device_id); + if (card == NULL) { + printk(" - Unused card not found in system!\n"); + return NULL; + } + } else { + switch (find_free_pci_card_by_position(vendor_id, device_id, + pci_bus, pci_slot, + &card)) { + case 1: + printk + (" - Card not found on requested position b:s %d:%d!\n", + pci_bus, pci_slot); + return NULL; + case 2: + printk + (" - Card on requested position is used b:s %d:%d!\n", + pci_bus, pci_slot); + return NULL; + } + } + + err = pci_card_alloc(card); + if (err != 0) { + if (err > 0) + printk(" - Can't allocate card!\n"); + /* else: error already printed. */ + return NULL; + } + + return card; +} + +#endif diff --git a/drivers/staging/comedi/drivers/ii_pci20kc.c b/drivers/staging/comedi/drivers/ii_pci20kc.c new file mode 100644 index 00000000..e4711ef5 --- /dev/null +++ b/drivers/staging/comedi/drivers/ii_pci20kc.c @@ -0,0 +1,684 @@ +/* + * comedi/drivers/ii_pci20kc.c + * Driver for Intelligent Instruments PCI-20001C carrier board + * and modules. + * + * Copyright (C) 2000 Markus Kempf + * with suggestions from David Schleef + * 16.06.2000 + * + * Linux device driver for COMEDI + * Intelligent Instrumentation + * PCI-20001 C-2A Carrier Board + * PCI-20341 M-1A 16-Bit analog input module + * - differential + * - range (-5V - +5V) + * - 16 bit + * PCI-20006 M-2 16-Bit analog output module + * - ranges (-10V - +10V) (0V - +10V) (-5V - +5V) + * - 16 bit + * + * only ONE PCI-20341 module possible + * only ONE PCI-20006 module possible + * no extern trigger implemented + * + * NOT WORKING (but soon) only 4 on-board differential channels supported + * NOT WORKING (but soon) only ONE di-port and ONE do-port supported + * instead of 4 digital ports + * di-port == Port 0 + * do-port == Port 1 + * + * The state of this driver is only a starting point for a complete + * COMEDI-driver. The final driver should support all features of the + * carrier board and modules. + * + * The test configuration: + * + * kernel 2.2.14 with RTAI v1.2 and patch-2.2.14rthal2 + * COMEDI 0.7.45 + * COMEDILIB 0.7.9 + * + */ +/* +Driver: ii_pci20kc +Description: Intelligent Instruments PCI-20001C carrier board +Author: Markus Kempf +Devices: [Intelligent Instrumentation] PCI-20001C (ii_pci20kc) +Status: works + +Supports the PCI-20001 C-2a Carrier board, and could probably support +the other carrier boards with small modifications. Modules supported +are: + PCI-20006 M-2 16-bit analog output module + PCI-20341 M-1A 16-bit analog input module + +Options: + 0 Board base address + 1 IRQ + 2 first option for module 1 + 3 second option for module 1 + 4 first option for module 2 + 5 second option for module 2 + 6 first option for module 3 + 7 second option for module 3 + +options for PCI-20006M: + first: Analog output channel 0 range configuration + 0 bipolar 10 (-10V -- +10V) + 1 unipolar 10 (0V -- +10V) + 2 bipolar 5 (-5V -- 5V) + second: Analog output channel 1 range configuration + +options for PCI-20341M: + first: Analog input gain configuration + 0 1 + 1 10 + 2 100 + 3 200 +*/ + +/* XXX needs to use ioremap() for compatibility with 2.4 kernels. Should also + * check_mem_region() etc. - fmhess */ + +#include "../comedidev.h" + +#define PCI20000_ID 0x1d +#define PCI20341_ID 0x77 +#define PCI20006_ID 0xe3 +#define PCI20xxx_EMPTY_ID 0xff + +#define PCI20000_OFFSET 0x100 +#define PCI20000_MODULES 3 + +#define PCI20000_DIO_0 0x80 +#define PCI20000_DIO_1 0x81 +#define PCI20000_DIO_2 0xc0 +#define PCI20000_DIO_3 0xc1 +#define PCI20000_DIO_CONTROL_01 0x83 /* port 0, 1 control */ +#define PCI20000_DIO_CONTROL_23 0xc3 /* port 2, 3 control */ +#define PCI20000_DIO_BUFFER 0x82 /* buffer direction & enable */ +#define PCI20000_DIO_EOC 0xef /* even port, control output */ +#define PCI20000_DIO_OOC 0xfd /* odd port, control output */ +#define PCI20000_DIO_EIC 0x90 /* even port, control input */ +#define PCI20000_DIO_OIC 0x82 /* odd port, control input */ +#define DIO_CAND 0x12 /* and bit 1 & 4 of control */ +#define DIO_BE 0x01 /* buffer: port enable */ +#define DIO_BO 0x04 /* buffer: output */ +#define DIO_BI 0x05 /* buffer: input */ +#define DIO_PS_0 0x00 /* buffer: port shift 0 */ +#define DIO_PS_1 0x01 /* buffer: port shift 1 */ +#define DIO_PS_2 0x04 /* buffer: port shift 2 */ +#define DIO_PS_3 0x05 /* buffer: port shift 3 */ + +#define PCI20006_LCHAN0 0x0d +#define PCI20006_STROBE0 0x0b +#define PCI20006_LCHAN1 0x15 +#define PCI20006_STROBE1 0x13 + +#define PCI20341_INIT 0x04 +#define PCI20341_REPMODE 0x00 /* single shot mode */ +#define PCI20341_PACER 0x00 /* Hardware Pacer disabled */ +#define PCI20341_CHAN_NR 0x04 /* number of input channels */ +#define PCI20341_CONFIG_REG 0x10 +#define PCI20341_MOD_STATUS 0x01 +#define PCI20341_OPT_REG 0x11 +#define PCI20341_SET_TIME_REG 0x15 +#define PCI20341_LCHAN_ADDR_REG 0x13 +#define PCI20341_CHAN_LIST 0x80 +#define PCI20341_CC_RESET 0x1b +#define PCI20341_CHAN_RESET 0x19 +#define PCI20341_SOFT_PACER 0x04 +#define PCI20341_STATUS_REG 0x12 +#define PCI20341_LDATA 0x02 +#define PCI20341_DAISY_CHAIN 0x20 /* On-board inputs only */ +#define PCI20341_MUX 0x04 /* Enable on-board MUX */ +#define PCI20341_SCANLIST 0x80 /* Channel/Gain Scan List */ + +union pci20xxx_subdev_private { + void *iobase; + struct { + void *iobase; + const struct comedi_lrange *ao_range_list[2]; + /* range of channels of ao module */ + unsigned int last_data[2]; + } pci20006; + struct { + void *iobase; + int timebase; + int settling_time; + int ai_gain; + } pci20341; +}; + +struct pci20xxx_private { + + void *ioaddr; + union pci20xxx_subdev_private subdev_private[PCI20000_MODULES]; +}; + +#define devpriv ((struct pci20xxx_private *)dev->private) +#define CHAN (CR_CHAN(it->chanlist[0])) + +static int pci20xxx_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pci20xxx_detach(struct comedi_device *dev); + +static struct comedi_driver driver_pci20xxx = { + .driver_name = "ii_pci20kc", + .module = THIS_MODULE, + .attach = pci20xxx_attach, + .detach = pci20xxx_detach, +}; + +static int pci20006_init(struct comedi_device *dev, struct comedi_subdevice *s, + int opt0, int opt1); +static int pci20341_init(struct comedi_device *dev, struct comedi_subdevice *s, + int opt0, int opt1); +static int pci20xxx_dio_init(struct comedi_device *dev, + struct comedi_subdevice *s); + +/* + options[0] Board base address + options[1] IRQ + options[2] first option for module 1 + options[3] second option for module 1 + options[4] first option for module 2 + options[5] second option for module 2 + options[6] first option for module 3 + options[7] second option for module 3 + + options for PCI-20341M: + first Analog input gain configuration + 0 == 1 + 1 == 10 + 2 == 100 + 3 == 200 + + options for PCI-20006M: + first Analog output channel 0 range configuration + 0 == bipolar 10 (-10V -- +10V) + 1 == unipolar 10V (0V -- +10V) + 2 == bipolar 5V (-5V -- +5V) + second Analog output channel 1 range configuration + 0 == bipolar 10 (-10V -- +10V) + 1 == unipolar 10V (0V -- +10V) + 2 == bipolar 5V (-5V -- +5V) +*/ +static int pci20xxx_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + unsigned char i; + int ret; + int id; + struct comedi_subdevice *s; + union pci20xxx_subdev_private *sdp; + + ret = alloc_subdevices(dev, 1 + PCI20000_MODULES); + if (ret < 0) + return ret; + + ret = alloc_private(dev, sizeof(struct pci20xxx_private)); + if (ret < 0) + return ret; + + devpriv->ioaddr = (void *)(unsigned long)it->options[0]; + dev->board_name = "pci20kc"; + + /* Check PCI-20001 C-2A Carrier Board ID */ + if ((readb(devpriv->ioaddr) & PCI20000_ID) != PCI20000_ID) { + printk(KERN_WARNING "comedi%d: ii_pci20kc PCI-20001" + " C-2A Carrier Board at base=0x%p not found !\n", + dev->minor, devpriv->ioaddr); + return -EINVAL; + } + printk(KERN_INFO "comedi%d: ii_pci20kc: PCI-20001 C-2A at base=0x%p\n", + dev->minor, devpriv->ioaddr); + + for (i = 0; i < PCI20000_MODULES; i++) { + s = dev->subdevices + i; + id = readb(devpriv->ioaddr + (i + 1) * PCI20000_OFFSET); + s->private = devpriv->subdev_private + i; + sdp = s->private; + switch (id) { + case PCI20006_ID: + sdp->pci20006.iobase = + devpriv->ioaddr + (i + 1) * PCI20000_OFFSET; + pci20006_init(dev, s, it->options[2 * i + 2], + it->options[2 * i + 3]); + printk(KERN_INFO "comedi%d: " + "ii_pci20kc PCI-20006 module in slot %d\n", + dev->minor, i + 1); + break; + case PCI20341_ID: + sdp->pci20341.iobase = + devpriv->ioaddr + (i + 1) * PCI20000_OFFSET; + pci20341_init(dev, s, it->options[2 * i + 2], + it->options[2 * i + 3]); + printk(KERN_INFO "comedi%d: " + "ii_pci20kc PCI-20341 module in slot %d\n", + dev->minor, i + 1); + break; + default: + printk(KERN_WARNING "ii_pci20kc: unknown module " + "code 0x%02x in slot %d: module disabled\n", + id, i); /* XXX this looks like a bug! i + 1 ?? */ + /* fall through */ + case PCI20xxx_EMPTY_ID: + s->type = COMEDI_SUBD_UNUSED; + break; + } + } + + /* initialize struct pci20xxx_private */ + pci20xxx_dio_init(dev, dev->subdevices + PCI20000_MODULES); + + return 1; +} + +static int pci20xxx_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pci20xxx: remove\n", dev->minor); + + return 0; +} + +/* pci20006m */ + +static int pci20006_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int pci20006_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static const struct comedi_lrange *pci20006_range_list[] = { + &range_bipolar10, + &range_unipolar10, + &range_bipolar5, +}; + +static int pci20006_init(struct comedi_device *dev, struct comedi_subdevice *s, + int opt0, int opt1) +{ + union pci20xxx_subdev_private *sdp = s->private; + + if (opt0 < 0 || opt0 > 2) + opt0 = 0; + if (opt1 < 0 || opt1 > 2) + opt1 = 0; + + sdp->pci20006.ao_range_list[0] = pci20006_range_list[opt0]; + sdp->pci20006.ao_range_list[1] = pci20006_range_list[opt1]; + + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->len_chanlist = 2; + s->insn_read = pci20006_insn_read; + s->insn_write = pci20006_insn_write; + s->maxdata = 0xffff; + s->range_table_list = sdp->pci20006.ao_range_list; + return 0; +} + +static int pci20006_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + union pci20xxx_subdev_private *sdp = s->private; + + data[0] = sdp->pci20006.last_data[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int pci20006_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + union pci20xxx_subdev_private *sdp = s->private; + int hi, lo; + unsigned int boarddata; + + sdp->pci20006.last_data[CR_CHAN(insn->chanspec)] = data[0]; + boarddata = (((unsigned int)data[0] + 0x8000) & 0xffff); + /* comedi-data -> board-data */ + lo = (boarddata & 0xff); + hi = ((boarddata >> 8) & 0xff); + + switch (CR_CHAN(insn->chanspec)) { + case 0: + writeb(lo, sdp->iobase + PCI20006_LCHAN0); + writeb(hi, sdp->iobase + PCI20006_LCHAN0 + 1); + writeb(0x00, sdp->iobase + PCI20006_STROBE0); + break; + case 1: + writeb(lo, sdp->iobase + PCI20006_LCHAN1); + writeb(hi, sdp->iobase + PCI20006_LCHAN1 + 1); + writeb(0x00, sdp->iobase + PCI20006_STROBE1); + break; + default: + printk(KERN_WARNING + " comedi%d: pci20xxx: ao channel Error!\n", dev->minor); + return -EINVAL; + } + + return 1; +} + +/* PCI20341M */ + +static int pci20341_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static const int pci20341_timebase[] = { 0x00, 0x00, 0x00, 0x04 }; +static const int pci20341_settling_time[] = { 0x58, 0x58, 0x93, 0x99 }; + +static const struct comedi_lrange range_bipolar0_5 = { + 1, + {BIP_RANGE(0.5)} +}; + +static const struct comedi_lrange range_bipolar0_05 = { + 1, + {BIP_RANGE(0.05)} +}; + +static const struct comedi_lrange range_bipolar0_025 = { + 1, + {BIP_RANGE(0.025)} +}; + +static const struct comedi_lrange *const pci20341_ranges[] = { + &range_bipolar5, + &range_bipolar0_5, + &range_bipolar0_05, + &range_bipolar0_025, +}; + +static int pci20341_init(struct comedi_device *dev, struct comedi_subdevice *s, + int opt0, int opt1) +{ + union pci20xxx_subdev_private *sdp = s->private; + int option; + + /* options handling */ + if (opt0 < 0 || opt0 > 3) + opt0 = 0; + sdp->pci20341.timebase = pci20341_timebase[opt0]; + sdp->pci20341.settling_time = pci20341_settling_time[opt0]; + + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE; + s->n_chan = PCI20341_CHAN_NR; + s->len_chanlist = PCI20341_SCANLIST; + s->insn_read = pci20341_insn_read; + s->maxdata = 0xffff; + s->range_table = pci20341_ranges[opt0]; + + /* depends on gain, trigger, repetition mode */ + option = sdp->pci20341.timebase | PCI20341_REPMODE; + + /* initialize Module */ + writeb(PCI20341_INIT, sdp->iobase + PCI20341_CONFIG_REG); + /* set Pacer */ + writeb(PCI20341_PACER, sdp->iobase + PCI20341_MOD_STATUS); + /* option register */ + writeb(option, sdp->iobase + PCI20341_OPT_REG); + /* settling time counter */ + writeb(sdp->pci20341.settling_time, + sdp->iobase + PCI20341_SET_TIME_REG); + /* trigger not implemented */ + return 0; +} + +static int pci20341_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + union pci20xxx_subdev_private *sdp = s->private; + unsigned int i = 0, j = 0; + int lo, hi; + unsigned char eoc; /* end of conversion */ + unsigned int clb; /* channel list byte */ + unsigned int boarddata; + + /* write number of input channels */ + writeb(1, sdp->iobase + PCI20341_LCHAN_ADDR_REG); + clb = PCI20341_DAISY_CHAIN | PCI20341_MUX | (sdp->pci20341.ai_gain << 3) + | CR_CHAN(insn->chanspec); + writeb(clb, sdp->iobase + PCI20341_CHAN_LIST); + + /* reset settling time counter and trigger delay counter */ + writeb(0x00, sdp->iobase + PCI20341_CC_RESET); + + writeb(0x00, sdp->iobase + PCI20341_CHAN_RESET); + + /* generate Pacer */ + + for (i = 0; i < insn->n; i++) { + /* data polling isn't the niciest way to get the data, I know, + * but there are only 6 cycles (mean) and it is easier than + * the whole interrupt stuff + */ + j = 0; + /* generate Pacer */ + readb(sdp->iobase + PCI20341_SOFT_PACER); + + eoc = readb(sdp->iobase + PCI20341_STATUS_REG); + /* poll Interrupt Flag */ + while ((eoc < 0x80) && j < 100) { + j++; + eoc = readb(sdp->iobase + PCI20341_STATUS_REG); + } + if (j >= 100) { + printk(KERN_WARNING + "comedi%d: pci20xxx: " + "AI interrupt channel %i polling exit !\n", + dev->minor, i); + return -EINVAL; + } + lo = readb(sdp->iobase + PCI20341_LDATA); + hi = readb(sdp->iobase + PCI20341_LDATA + 1); + boarddata = lo + 0x100 * hi; + + /* board-data -> comedi-data */ + data[i] = (short)((boarddata + 0x8000) & 0xffff); + } + + return i; +} + +/* native DIO */ + +static void pci20xxx_dio_config(struct comedi_device *dev, + struct comedi_subdevice *s); +static int pci20xxx_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int pci20xxx_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +/* initialize struct pci20xxx_private */ +static int pci20xxx_dio_init(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 32; + s->insn_bits = pci20xxx_dio_insn_bits; + s->insn_config = pci20xxx_dio_insn_config; + s->maxdata = 1; + s->len_chanlist = 32; + s->range_table = &range_digital; + s->io_bits = 0; + + /* digital I/O lines default to input on board reset. */ + pci20xxx_dio_config(dev, s); + + return 0; +} + +static int pci20xxx_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int mask, bits; + + mask = 1 << CR_CHAN(insn->chanspec); + if (mask & 0x000000ff) + bits = 0x000000ff; + else if (mask & 0x0000ff00) + bits = 0x0000ff00; + else if (mask & 0x00ff0000) + bits = 0x00ff0000; + else + bits = 0xff000000; + if (data[0]) + s->io_bits |= bits; + else + s->io_bits &= ~bits; + pci20xxx_dio_config(dev, s); + + return 1; +} + +static int pci20xxx_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int mask = data[0]; + + s->state &= ~mask; + s->state |= (mask & data[1]); + + mask &= s->io_bits; + if (mask & 0x000000ff) + writeb((s->state >> 0) & 0xff, + devpriv->ioaddr + PCI20000_DIO_0); + if (mask & 0x0000ff00) + writeb((s->state >> 8) & 0xff, + devpriv->ioaddr + PCI20000_DIO_1); + if (mask & 0x00ff0000) + writeb((s->state >> 16) & 0xff, + devpriv->ioaddr + PCI20000_DIO_2); + if (mask & 0xff000000) + writeb((s->state >> 24) & 0xff, + devpriv->ioaddr + PCI20000_DIO_3); + + data[1] = readb(devpriv->ioaddr + PCI20000_DIO_0); + data[1] |= readb(devpriv->ioaddr + PCI20000_DIO_1) << 8; + data[1] |= readb(devpriv->ioaddr + PCI20000_DIO_2) << 16; + data[1] |= readb(devpriv->ioaddr + PCI20000_DIO_3) << 24; + + return 2; +} + +static void pci20xxx_dio_config(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned char control_01; + unsigned char control_23; + unsigned char buffer; + + control_01 = readb(devpriv->ioaddr + PCI20000_DIO_CONTROL_01); + control_23 = readb(devpriv->ioaddr + PCI20000_DIO_CONTROL_23); + buffer = readb(devpriv->ioaddr + PCI20000_DIO_BUFFER); + + if (s->io_bits & 0x000000ff) { + /* output port 0 */ + control_01 &= PCI20000_DIO_EOC; + buffer = (buffer & (~(DIO_BE << DIO_PS_0))) | (DIO_BO << + DIO_PS_0); + } else { + /* input port 0 */ + control_01 = (control_01 & DIO_CAND) | PCI20000_DIO_EIC; + buffer = (buffer & (~(DIO_BI << DIO_PS_0))); + } + if (s->io_bits & 0x0000ff00) { + /* output port 1 */ + control_01 &= PCI20000_DIO_OOC; + buffer = (buffer & (~(DIO_BE << DIO_PS_1))) | (DIO_BO << + DIO_PS_1); + } else { + /* input port 1 */ + control_01 = (control_01 & DIO_CAND) | PCI20000_DIO_OIC; + buffer = (buffer & (~(DIO_BI << DIO_PS_1))); + } + if (s->io_bits & 0x00ff0000) { + /* output port 2 */ + control_23 &= PCI20000_DIO_EOC; + buffer = (buffer & (~(DIO_BE << DIO_PS_2))) | (DIO_BO << + DIO_PS_2); + } else { + /* input port 2 */ + control_23 = (control_23 & DIO_CAND) | PCI20000_DIO_EIC; + buffer = (buffer & (~(DIO_BI << DIO_PS_2))); + } + if (s->io_bits & 0xff000000) { + /* output port 3 */ + control_23 &= PCI20000_DIO_OOC; + buffer = (buffer & (~(DIO_BE << DIO_PS_3))) | (DIO_BO << + DIO_PS_3); + } else { + /* input port 3 */ + control_23 = (control_23 & DIO_CAND) | PCI20000_DIO_OIC; + buffer = (buffer & (~(DIO_BI << DIO_PS_3))); + } + writeb(control_01, devpriv->ioaddr + PCI20000_DIO_CONTROL_01); + writeb(control_23, devpriv->ioaddr + PCI20000_DIO_CONTROL_23); + writeb(buffer, devpriv->ioaddr + PCI20000_DIO_BUFFER); +} + +#if 0 +static void pci20xxx_do(struct comedi_device *dev, struct comedi_subdevice *s) +{ + /* XXX if the channel is configured for input, does this + do bad things? */ + /* XXX it would be a good idea to only update the registers + that _need_ to be updated. This requires changes to + comedi, however. */ + writeb((s->state >> 0) & 0xff, devpriv->ioaddr + PCI20000_DIO_0); + writeb((s->state >> 8) & 0xff, devpriv->ioaddr + PCI20000_DIO_1); + writeb((s->state >> 16) & 0xff, devpriv->ioaddr + PCI20000_DIO_2); + writeb((s->state >> 24) & 0xff, devpriv->ioaddr + PCI20000_DIO_3); +} + +static unsigned int pci20xxx_di(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* XXX same note as above */ + unsigned int bits; + + bits = readb(devpriv->ioaddr + PCI20000_DIO_0); + bits |= readb(devpriv->ioaddr + PCI20000_DIO_1) << 8; + bits |= readb(devpriv->ioaddr + PCI20000_DIO_2) << 16; + bits |= readb(devpriv->ioaddr + PCI20000_DIO_3) << 24; + + return bits; +} +#endif + +static int __init driver_pci20xxx_init_module(void) +{ + return comedi_driver_register(&driver_pci20xxx); +} + +static void __exit driver_pci20xxx_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pci20xxx); +} + +module_init(driver_pci20xxx_init_module); +module_exit(driver_pci20xxx_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/jr3_pci.c b/drivers/staging/comedi/drivers/jr3_pci.c new file mode 100644 index 00000000..6a79ba10 --- /dev/null +++ b/drivers/staging/comedi/drivers/jr3_pci.c @@ -0,0 +1,1017 @@ +/* + comedi/drivers/jr3_pci.c + hardware driver for JR3/PCI force sensor board + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2007 Anders Blomdell + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: jr3_pci +Description: JR3/PCI force sensor board +Author: Anders Blomdell +Status: works +Devices: [JR3] PCI force sensor board (jr3_pci) + + The DSP on the board requires initialization code, which can + be loaded by placing it in /lib/firmware/comedi. + The initialization code should be somewhere on the media you got + with your card. One version is available from http://www.comedi.org + in the comedi_nonfree_firmware tarball. + + Configuration options: + [0] - PCI bus number - if bus number and slot number are 0, + then driver search for first unused card + [1] - PCI slot number + +*/ + +#include "../comedidev.h" + +#include +#include +#include +#include +#include +#include +#include +#include "comedi_pci.h" +#include "jr3_pci.h" + +#define PCI_VENDOR_ID_JR3 0x1762 +#define PCI_DEVICE_ID_JR3_1_CHANNEL 0x3111 +#define PCI_DEVICE_ID_JR3_1_CHANNEL_NEW 0x1111 +#define PCI_DEVICE_ID_JR3_2_CHANNEL 0x3112 +#define PCI_DEVICE_ID_JR3_3_CHANNEL 0x3113 +#define PCI_DEVICE_ID_JR3_4_CHANNEL 0x3114 + +static int jr3_pci_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int jr3_pci_detach(struct comedi_device *dev); + +static struct comedi_driver driver_jr3_pci = { + .driver_name = "jr3_pci", + .module = THIS_MODULE, + .attach = jr3_pci_attach, + .detach = jr3_pci_detach, +}; + +static DEFINE_PCI_DEVICE_TABLE(jr3_pci_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_1_CHANNEL) }, + { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_1_CHANNEL_NEW) }, + { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_2_CHANNEL) }, + { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_3_CHANNEL) }, + { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_4_CHANNEL) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, jr3_pci_pci_table); + +struct jr3_pci_dev_private { + + struct pci_dev *pci_dev; + int pci_enabled; + volatile struct jr3_t *iobase; + int n_channels; + struct timer_list timer; +}; + +struct poll_delay_t { + + int min; + int max; +}; + +struct jr3_pci_subdev_private { + volatile struct jr3_channel *channel; + unsigned long next_time_min; + unsigned long next_time_max; + enum { state_jr3_poll, + state_jr3_init_wait_for_offset, + state_jr3_init_transform_complete, + state_jr3_init_set_full_scale_complete, + state_jr3_init_use_offset_complete, + state_jr3_done + } state; + int channel_no; + int serial_no; + int model_no; + struct { + int length; + struct comedi_krange range; + } range[9]; + const struct comedi_lrange *range_table_list[8 * 7 + 2]; + unsigned int maxdata_list[8 * 7 + 2]; + u16 errors; + int retries; +}; + +/* Hotplug firmware loading stuff */ +static int comedi_load_firmware(struct comedi_device *dev, char *name, + int (*cb)(struct comedi_device *dev, + const u8 *data, size_t size)) +{ + int result = 0; + const struct firmware *fw; + char *firmware_path; + static const char *prefix = "comedi/"; + struct jr3_pci_dev_private *devpriv = dev->private; + + firmware_path = kmalloc(strlen(prefix) + strlen(name) + 1, GFP_KERNEL); + if (!firmware_path) { + result = -ENOMEM; + } else { + firmware_path[0] = '\0'; + strcat(firmware_path, prefix); + strcat(firmware_path, name); + result = request_firmware(&fw, firmware_path, + &devpriv->pci_dev->dev); + if (result == 0) { + if (!cb) + result = -EINVAL; + else + result = cb(dev, fw->data, fw->size); + release_firmware(fw); + } + kfree(firmware_path); + } + return result; +} + +static struct poll_delay_t poll_delay_min_max(int min, int max) +{ + struct poll_delay_t result; + + result.min = min; + result.max = max; + return result; +} + +static int is_complete(volatile struct jr3_channel *channel) +{ + return get_s16(&channel->command_word0) == 0; +} + +struct transform_t { + struct { + u16 link_type; + s16 link_amount; + } link[8]; +}; + +static void set_transforms(volatile struct jr3_channel *channel, + struct transform_t transf, short num) +{ + int i; + + num &= 0x000f; /* Make sure that 0 <= num <= 15 */ + for (i = 0; i < 8; i++) { + + set_u16(&channel->transforms[num].link[i].link_type, + transf.link[i].link_type); + udelay(1); + set_s16(&channel->transforms[num].link[i].link_amount, + transf.link[i].link_amount); + udelay(1); + if (transf.link[i].link_type == end_x_form) + break; + } +} + +static void use_transform(volatile struct jr3_channel *channel, + short transf_num) +{ + set_s16(&channel->command_word0, 0x0500 + (transf_num & 0x000f)); +} + +static void use_offset(volatile struct jr3_channel *channel, short offset_num) +{ + set_s16(&channel->command_word0, 0x0600 + (offset_num & 0x000f)); +} + +static void set_offset(volatile struct jr3_channel *channel) +{ + set_s16(&channel->command_word0, 0x0700); +} + +struct six_axis_t { + s16 fx; + s16 fy; + s16 fz; + s16 mx; + s16 my; + s16 mz; +}; + +static void set_full_scales(volatile struct jr3_channel *channel, + struct six_axis_t full_scale) +{ + printk("%d %d %d %d %d %d\n", + full_scale.fx, + full_scale.fy, + full_scale.fz, full_scale.mx, full_scale.my, full_scale.mz); + set_s16(&channel->full_scale.fx, full_scale.fx); + set_s16(&channel->full_scale.fy, full_scale.fy); + set_s16(&channel->full_scale.fz, full_scale.fz); + set_s16(&channel->full_scale.mx, full_scale.mx); + set_s16(&channel->full_scale.my, full_scale.my); + set_s16(&channel->full_scale.mz, full_scale.mz); + set_s16(&channel->command_word0, 0x0a00); +} + +static struct six_axis_t get_min_full_scales(volatile struct jr3_channel + *channel) +{ + struct six_axis_t result; + result.fx = get_s16(&channel->min_full_scale.fx); + result.fy = get_s16(&channel->min_full_scale.fy); + result.fz = get_s16(&channel->min_full_scale.fz); + result.mx = get_s16(&channel->min_full_scale.mx); + result.my = get_s16(&channel->min_full_scale.my); + result.mz = get_s16(&channel->min_full_scale.mz); + return result; +} + +static struct six_axis_t get_max_full_scales(volatile struct jr3_channel + *channel) +{ + struct six_axis_t result; + result.fx = get_s16(&channel->max_full_scale.fx); + result.fy = get_s16(&channel->max_full_scale.fy); + result.fz = get_s16(&channel->max_full_scale.fz); + result.mx = get_s16(&channel->max_full_scale.mx); + result.my = get_s16(&channel->max_full_scale.my); + result.mz = get_s16(&channel->max_full_scale.mz); + return result; +} + +static int jr3_pci_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int result; + struct jr3_pci_subdev_private *p; + int channel; + + p = s->private; + channel = CR_CHAN(insn->chanspec); + if (p == NULL || channel > 57) { + result = -EINVAL; + } else { + int i; + + result = insn->n; + if (p->state != state_jr3_done || + (get_u16(&p->channel->errors) & (watch_dog | watch_dog2 | + sensor_change))) { + /* No sensor or sensor changed */ + if (p->state == state_jr3_done) { + /* Restart polling */ + p->state = state_jr3_poll; + } + result = -EAGAIN; + } + for (i = 0; i < insn->n; i++) { + if (channel < 56) { + int axis, filter; + + axis = channel % 8; + filter = channel / 8; + if (p->state != state_jr3_done) { + data[i] = 0; + } else { + int F = 0; + switch (axis) { + case 0:{ + F = get_s16 + (&p->channel->filter + [filter].fx); + } + break; + case 1:{ + F = get_s16 + (&p->channel->filter + [filter].fy); + } + break; + case 2:{ + F = get_s16 + (&p->channel->filter + [filter].fz); + } + break; + case 3:{ + F = get_s16 + (&p->channel->filter + [filter].mx); + } + break; + case 4:{ + F = get_s16 + (&p->channel->filter + [filter].my); + } + break; + case 5:{ + F = get_s16 + (&p->channel->filter + [filter].mz); + } + break; + case 6:{ + F = get_s16 + (&p->channel->filter + [filter].v1); + } + break; + case 7:{ + F = get_s16 + (&p->channel->filter + [filter].v2); + } + break; + } + data[i] = F + 0x4000; + } + } else if (channel == 56) { + if (p->state != state_jr3_done) { + data[i] = 0; + } else { + data[i] = + get_u16(&p->channel->model_no); + } + } else if (channel == 57) { + if (p->state != state_jr3_done) { + data[i] = 0; + } else { + data[i] = + get_u16(&p->channel->serial_no); + } + } + } + } + return result; +} + +static int jr3_pci_open(struct comedi_device *dev) +{ + int i; + struct jr3_pci_dev_private *devpriv = dev->private; + + dev_dbg(dev->hw_dev, "jr3_pci_open\n"); + for (i = 0; i < devpriv->n_channels; i++) { + struct jr3_pci_subdev_private *p; + + p = dev->subdevices[i].private; + if (p) { + dev_dbg(dev->hw_dev, "serial: %p %d (%d)\n", p, + p->serial_no, p->channel_no); + } + } + return 0; +} + +int read_idm_word(const u8 * data, size_t size, int *pos, unsigned int *val) +{ + int result = 0; + if (pos != 0 && val != 0) { + /* Skip over non hex */ + for (; *pos < size && !isxdigit(data[*pos]); (*pos)++) { + } + /* Collect value */ + *val = 0; + for (; *pos < size; (*pos)++) { + int value; + value = hex_to_bin(data[*pos]); + if (value >= 0) { + result = 1; + *val = (*val << 4) + value; + } else + break; + } + } + return result; +} + +static int jr3_download_firmware(struct comedi_device *dev, const u8 * data, + size_t size) +{ + /* + * IDM file format is: + * { count, address, data } * + * ffff + */ + int result, more, pos, OK; + + result = 0; + more = 1; + pos = 0; + OK = 0; + while (more) { + unsigned int count, addr; + + more = more && read_idm_word(data, size, &pos, &count); + if (more && count == 0xffff) { + OK = 1; + break; + } + more = more && read_idm_word(data, size, &pos, &addr); + while (more && count > 0) { + unsigned int dummy; + more = more && read_idm_word(data, size, &pos, &dummy); + count--; + } + } + + if (!OK) { + result = -ENODATA; + } else { + int i; + struct jr3_pci_dev_private *p = dev->private; + + for (i = 0; i < p->n_channels; i++) { + struct jr3_pci_subdev_private *sp; + + sp = dev->subdevices[i].private; + more = 1; + pos = 0; + while (more) { + unsigned int count, addr; + more = more + && read_idm_word(data, size, &pos, &count); + if (more && count == 0xffff) + break; + more = more + && read_idm_word(data, size, &pos, &addr); + dev_dbg(dev->hw_dev, "Loading#%d %4.4x bytes at %4.4x\n", + i, count, addr); + while (more && count > 0) { + if (addr & 0x4000) { + /* 16 bit data, never seen in real life!! */ + unsigned int data1; + + more = more + && read_idm_word(data, + size, &pos, + &data1); + count--; + /* printk("jr3_data, not tested\n"); */ + /* jr3[addr + 0x20000 * pnum] = data1; */ + } else { + /* Download 24 bit program */ + unsigned int data1, data2; + + more = more + && read_idm_word(data, + size, &pos, + &data1); + more = more + && read_idm_word(data, size, + &pos, + &data2); + count -= 2; + if (more) { + set_u16(&p-> + iobase->channel + [i].program_low + [addr], data1); + udelay(1); + set_u16(&p-> + iobase->channel + [i].program_high + [addr], data2); + udelay(1); + + } + } + addr++; + } + } + } + } + return result; +} + +static struct poll_delay_t jr3_pci_poll_subdevice(struct comedi_subdevice *s) +{ + struct poll_delay_t result = poll_delay_min_max(1000, 2000); + struct jr3_pci_subdev_private *p = s->private; + int i; + + if (p) { + volatile struct jr3_channel *channel = p->channel; + int errors = get_u16(&channel->errors); + + if (errors != p->errors) { + printk("Errors: %x -> %x\n", p->errors, errors); + p->errors = errors; + } + if (errors & (watch_dog | watch_dog2 | sensor_change)) { + /* Sensor communication lost, force poll mode */ + p->state = state_jr3_poll; + + } + switch (p->state) { + case state_jr3_poll:{ + u16 model_no = get_u16(&channel->model_no); + u16 serial_no = get_u16(&channel->serial_no); + if ((errors & (watch_dog | watch_dog2)) || + model_no == 0 || serial_no == 0) { +/* + * Still no sensor, keep on polling. Since it takes up to 10 seconds + * for offsets to stabilize, polling each second should suffice. + */ + result = poll_delay_min_max(1000, 2000); + } else { + p->retries = 0; + p->state = + state_jr3_init_wait_for_offset; + result = poll_delay_min_max(1000, 2000); + } + } + break; + case state_jr3_init_wait_for_offset:{ + p->retries++; + if (p->retries < 10) { + /* Wait for offeset to stabilize (< 10 s according to manual) */ + result = poll_delay_min_max(1000, 2000); + } else { + struct transform_t transf; + + p->model_no = + get_u16(&channel->model_no); + p->serial_no = + get_u16(&channel->serial_no); + + printk + ("Setting transform for channel %d\n", + p->channel_no); + printk("Sensor Model = %i\n", + p->model_no); + printk("Sensor Serial = %i\n", + p->serial_no); + + /* Transformation all zeros */ + for (i = 0; i < ARRAY_SIZE(transf.link); i++) { + transf.link[i].link_type = + (enum link_types)0; + transf.link[i].link_amount = 0; + } + + set_transforms(channel, transf, 0); + use_transform(channel, 0); + p->state = + state_jr3_init_transform_complete; + result = poll_delay_min_max(20, 100); /* Allow 20 ms for completion */ + } + } break; + case state_jr3_init_transform_complete:{ + if (!is_complete(channel)) { + printk + ("state_jr3_init_transform_complete complete = %d\n", + is_complete(channel)); + result = poll_delay_min_max(20, 100); + } else { + /* Set full scale */ + struct six_axis_t min_full_scale; + struct six_axis_t max_full_scale; + + min_full_scale = + get_min_full_scales(channel); + printk("Obtained Min. Full Scales:\n"); + printk(KERN_DEBUG "%i ", (min_full_scale).fx); + printk(KERN_CONT "%i ", (min_full_scale).fy); + printk(KERN_CONT "%i ", (min_full_scale).fz); + printk(KERN_CONT "%i ", (min_full_scale).mx); + printk(KERN_CONT "%i ", (min_full_scale).my); + printk(KERN_CONT "%i ", (min_full_scale).mz); + printk(KERN_CONT "\n"); + + max_full_scale = + get_max_full_scales(channel); + printk("Obtained Max. Full Scales:\n"); + printk(KERN_DEBUG "%i ", (max_full_scale).fx); + printk(KERN_CONT "%i ", (max_full_scale).fy); + printk(KERN_CONT "%i ", (max_full_scale).fz); + printk(KERN_CONT "%i ", (max_full_scale).mx); + printk(KERN_CONT "%i ", (max_full_scale).my); + printk(KERN_CONT "%i ", (max_full_scale).mz); + printk(KERN_CONT "\n"); + + set_full_scales(channel, + max_full_scale); + + p->state = + state_jr3_init_set_full_scale_complete; + result = poll_delay_min_max(20, 100); /* Allow 20 ms for completion */ + } + } + break; + case state_jr3_init_set_full_scale_complete:{ + if (!is_complete(channel)) { + printk + ("state_jr3_init_set_full_scale_complete complete = %d\n", + is_complete(channel)); + result = poll_delay_min_max(20, 100); + } else { + volatile struct force_array *full_scale; + + /* Use ranges in kN or we will overflow arount 2000N! */ + full_scale = &channel->full_scale; + p->range[0].range.min = + -get_s16(&full_scale->fx) * 1000; + p->range[0].range.max = + get_s16(&full_scale->fx) * 1000; + p->range[1].range.min = + -get_s16(&full_scale->fy) * 1000; + p->range[1].range.max = + get_s16(&full_scale->fy) * 1000; + p->range[2].range.min = + -get_s16(&full_scale->fz) * 1000; + p->range[2].range.max = + get_s16(&full_scale->fz) * 1000; + p->range[3].range.min = + -get_s16(&full_scale->mx) * 100; + p->range[3].range.max = + get_s16(&full_scale->mx) * 100; + p->range[4].range.min = + -get_s16(&full_scale->my) * 100; + p->range[4].range.max = + get_s16(&full_scale->my) * 100; + p->range[5].range.min = + -get_s16(&full_scale->mz) * 100; + p->range[5].range.max = + get_s16(&full_scale->mz) * 100; + p->range[6].range.min = -get_s16(&full_scale->v1) * 100; /* ?? */ + p->range[6].range.max = get_s16(&full_scale->v1) * 100; /* ?? */ + p->range[7].range.min = -get_s16(&full_scale->v2) * 100; /* ?? */ + p->range[7].range.max = get_s16(&full_scale->v2) * 100; /* ?? */ + p->range[8].range.min = 0; + p->range[8].range.max = 65535; + + { + int i; + for (i = 0; i < 9; i++) { + printk("%d %d - %d\n", + i, + p-> + range[i].range. + min, + p-> + range[i].range. + max); + } + } + + use_offset(channel, 0); + p->state = + state_jr3_init_use_offset_complete; + result = poll_delay_min_max(40, 100); /* Allow 40 ms for completion */ + } + } + break; + case state_jr3_init_use_offset_complete:{ + if (!is_complete(channel)) { + printk + ("state_jr3_init_use_offset_complete complete = %d\n", + is_complete(channel)); + result = poll_delay_min_max(20, 100); + } else { + printk + ("Default offsets %d %d %d %d %d %d\n", + get_s16(&channel->offsets.fx), + get_s16(&channel->offsets.fy), + get_s16(&channel->offsets.fz), + get_s16(&channel->offsets.mx), + get_s16(&channel->offsets.my), + get_s16(&channel->offsets.mz)); + + set_s16(&channel->offsets.fx, 0); + set_s16(&channel->offsets.fy, 0); + set_s16(&channel->offsets.fz, 0); + set_s16(&channel->offsets.mx, 0); + set_s16(&channel->offsets.my, 0); + set_s16(&channel->offsets.mz, 0); + + set_offset(channel); + + p->state = state_jr3_done; + } + } + break; + case state_jr3_done:{ + poll_delay_min_max(10000, 20000); + } + break; + default:{ + poll_delay_min_max(1000, 2000); + } + break; + } + } + return result; +} + +static void jr3_pci_poll_dev(unsigned long data) +{ + unsigned long flags; + struct comedi_device *dev = (struct comedi_device *)data; + struct jr3_pci_dev_private *devpriv = dev->private; + unsigned long now; + int delay; + int i; + + spin_lock_irqsave(&dev->spinlock, flags); + delay = 1000; + now = jiffies; + /* Poll all channels that are ready to be polled */ + for (i = 0; i < devpriv->n_channels; i++) { + struct jr3_pci_subdev_private *subdevpriv = + dev->subdevices[i].private; + if (now > subdevpriv->next_time_min) { + struct poll_delay_t sub_delay; + + sub_delay = jr3_pci_poll_subdevice(&dev->subdevices[i]); + subdevpriv->next_time_min = + jiffies + msecs_to_jiffies(sub_delay.min); + subdevpriv->next_time_max = + jiffies + msecs_to_jiffies(sub_delay.max); + if (sub_delay.max && sub_delay.max < delay) { +/* +* Wake up as late as possible -> poll as many channels as possible +* at once +*/ + delay = sub_delay.max; + } + } + } + spin_unlock_irqrestore(&dev->spinlock, flags); + + devpriv->timer.expires = jiffies + msecs_to_jiffies(delay); + add_timer(&devpriv->timer); +} + +static int jr3_pci_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int result = 0; + struct pci_dev *card = NULL; + int opt_bus, opt_slot, i; + struct jr3_pci_dev_private *devpriv; + + opt_bus = it->options[0]; + opt_slot = it->options[1]; + + if (sizeof(struct jr3_channel) != 0xc00) { + dev_err(dev->hw_dev, "sizeof(struct jr3_channel) = %x [expected %x]\n", + (unsigned)sizeof(struct jr3_channel), 0xc00); + return -EINVAL; + } + + result = alloc_private(dev, sizeof(struct jr3_pci_dev_private)); + if (result < 0) + return -ENOMEM; + card = NULL; + devpriv = dev->private; + init_timer(&devpriv->timer); + while (1) { + card = pci_get_device(PCI_VENDOR_ID_JR3, PCI_ANY_ID, card); + if (card == NULL) { + /* No card found */ + break; + } else { + switch (card->device) { + case PCI_DEVICE_ID_JR3_1_CHANNEL:{ + devpriv->n_channels = 1; + } + break; + case PCI_DEVICE_ID_JR3_1_CHANNEL_NEW:{ + devpriv->n_channels = 1; + } + break; + case PCI_DEVICE_ID_JR3_2_CHANNEL:{ + devpriv->n_channels = 2; + } + break; + case PCI_DEVICE_ID_JR3_3_CHANNEL:{ + devpriv->n_channels = 3; + } + break; + case PCI_DEVICE_ID_JR3_4_CHANNEL:{ + devpriv->n_channels = 4; + } + break; + default:{ + devpriv->n_channels = 0; + } + } + if (devpriv->n_channels >= 1) { + if (opt_bus == 0 && opt_slot == 0) { + /* Take first available card */ + break; + } else if (opt_bus == card->bus->number && + opt_slot == PCI_SLOT(card->devfn)) { + /* Take requested card */ + break; + } + } + } + } + if (!card) { + dev_err(dev->hw_dev, "no jr3_pci found\n"); + return -EIO; + } else { + devpriv->pci_dev = card; + dev->board_name = "jr3_pci"; + } + + result = comedi_pci_enable(card, "jr3_pci"); + if (result < 0) + return -EIO; + + devpriv->pci_enabled = 1; + devpriv->iobase = ioremap(pci_resource_start(card, 0), + offsetof(struct jr3_t, channel[devpriv->n_channels])); + if (!devpriv->iobase) + return -ENOMEM; + + result = alloc_subdevices(dev, devpriv->n_channels); + if (result < 0) + goto out; + + dev->open = jr3_pci_open; + for (i = 0; i < devpriv->n_channels; i++) { + dev->subdevices[i].type = COMEDI_SUBD_AI; + dev->subdevices[i].subdev_flags = SDF_READABLE | SDF_GROUND; + dev->subdevices[i].n_chan = 8 * 7 + 2; + dev->subdevices[i].insn_read = jr3_pci_ai_insn_read; + dev->subdevices[i].private = + kzalloc(sizeof(struct jr3_pci_subdev_private), GFP_KERNEL); + if (dev->subdevices[i].private) { + struct jr3_pci_subdev_private *p; + int j; + + p = dev->subdevices[i].private; + p->channel = &devpriv->iobase->channel[i].data; + dev_dbg(dev->hw_dev, "p->channel %p %p (%tx)\n", + p->channel, devpriv->iobase, + ((char *)(p->channel) - + (char *)(devpriv->iobase))); + p->channel_no = i; + for (j = 0; j < 8; j++) { + int k; + + p->range[j].length = 1; + p->range[j].range.min = -1000000; + p->range[j].range.max = 1000000; + for (k = 0; k < 7; k++) { + p->range_table_list[j + k * 8] = + (struct comedi_lrange *)&p-> + range[j]; + p->maxdata_list[j + k * 8] = 0x7fff; + } + } + p->range[8].length = 1; + p->range[8].range.min = 0; + p->range[8].range.max = 65536; + + p->range_table_list[56] = + (struct comedi_lrange *)&p->range[8]; + p->range_table_list[57] = + (struct comedi_lrange *)&p->range[8]; + p->maxdata_list[56] = 0xffff; + p->maxdata_list[57] = 0xffff; + /* Channel specific range and maxdata */ + dev->subdevices[i].range_table = 0; + dev->subdevices[i].range_table_list = + p->range_table_list; + dev->subdevices[i].maxdata = 0; + dev->subdevices[i].maxdata_list = p->maxdata_list; + } + } + + /* Reset DSP card */ + devpriv->iobase->channel[0].reset = 0; + + result = comedi_load_firmware(dev, "jr3pci.idm", jr3_download_firmware); + dev_dbg(dev->hw_dev, "Firmare load %d\n", result); + + if (result < 0) + goto out; +/* + * TODO: use firmware to load preferred offset tables. Suggested + * format: + * model serial Fx Fy Fz Mx My Mz\n + * + * comedi_load_firmware(dev, "jr3_offsets_table", jr3_download_firmware); + */ + +/* + * It takes a few milliseconds for software to settle as much as we + * can read firmware version + */ + msleep_interruptible(25); + for (i = 0; i < 0x18; i++) { + dev_dbg(dev->hw_dev, "%c\n", + get_u16(&devpriv->iobase->channel[0]. + data.copyright[i]) >> 8); + } + + /* Start card timer */ + for (i = 0; i < devpriv->n_channels; i++) { + struct jr3_pci_subdev_private *p = dev->subdevices[i].private; + + p->next_time_min = jiffies + msecs_to_jiffies(500); + p->next_time_max = jiffies + msecs_to_jiffies(2000); + } + + devpriv->timer.data = (unsigned long)dev; + devpriv->timer.function = jr3_pci_poll_dev; + devpriv->timer.expires = jiffies + msecs_to_jiffies(1000); + add_timer(&devpriv->timer); + +out: + return result; +} + +MODULE_FIRMWARE("comedi/jr3pci.idm"); + +static int jr3_pci_detach(struct comedi_device *dev) +{ + int i; + struct jr3_pci_dev_private *devpriv = dev->private; + + if (devpriv) { + del_timer_sync(&devpriv->timer); + + if (dev->subdevices) { + for (i = 0; i < devpriv->n_channels; i++) + kfree(dev->subdevices[i].private); + } + + if (devpriv->iobase) + iounmap((void *)devpriv->iobase); + if (devpriv->pci_enabled) + comedi_pci_disable(devpriv->pci_dev); + + if (devpriv->pci_dev) + pci_dev_put(devpriv->pci_dev); + } + return 0; +} + +static int __devinit driver_jr3_pci_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_jr3_pci.driver_name); +} + +static void __devexit driver_jr3_pci_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_jr3_pci_pci_driver = { + .id_table = jr3_pci_pci_table, + .probe = &driver_jr3_pci_pci_probe, + .remove = __devexit_p(&driver_jr3_pci_pci_remove) +}; + +static int __init driver_jr3_pci_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_jr3_pci); + if (retval < 0) + return retval; + + driver_jr3_pci_pci_driver.name = (char *)driver_jr3_pci.driver_name; + return pci_register_driver(&driver_jr3_pci_pci_driver); +} + +static void __exit driver_jr3_pci_cleanup_module(void) +{ + pci_unregister_driver(&driver_jr3_pci_pci_driver); + comedi_driver_unregister(&driver_jr3_pci); +} + +module_init(driver_jr3_pci_init_module); +module_exit(driver_jr3_pci_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/jr3_pci.h b/drivers/staging/comedi/drivers/jr3_pci.h new file mode 100644 index 00000000..a1469611 --- /dev/null +++ b/drivers/staging/comedi/drivers/jr3_pci.h @@ -0,0 +1,681 @@ +/* Helper types to take care of the fact that the DSP card memory + * is 16 bits, but aligned on a 32 bit PCI boundary + */ + +static inline u16 get_u16(volatile const u32 * p) +{ + return (u16) readl(p); +} + +static inline void set_u16(volatile u32 * p, u16 val) +{ + writel(val, p); +} + +static inline s16 get_s16(volatile const s32 * p) +{ + return (s16) readl(p); +} + +static inline void set_s16(volatile s32 * p, s16 val) +{ + writel(val, p); +} + +/* The raw data is stored in a format which facilitates rapid + * processing by the JR3 DSP chip. The raw_channel structure shows the + * format for a single channel of data. Each channel takes four, + * two-byte words. + * + * Raw_time is an unsigned integer which shows the value of the JR3 + * DSP's internal clock at the time the sample was received. The clock + * runs at 1/10 the JR3 DSP cycle time. JR3's slowest DSP runs at 10 + * Mhz. At 10 Mhz raw_time would therefore clock at 1 Mhz. + * + * Raw_data is the raw data received directly from the sensor. The + * sensor data stream is capable of representing 16 different + * channels. Channel 0 shows the excitation voltage at the sensor. It + * is used to regulate the voltage over various cable lengths. + * Channels 1-6 contain the coupled force data Fx through Mz. Channel + * 7 contains the sensor's calibration data. The use of channels 8-15 + * varies with different sensors. + */ + +struct raw_channel { + u32 raw_time; + s32 raw_data; + s32 reserved[2]; +}; + +/* The force_array structure shows the layout for the decoupled and + * filtered force data. + */ +struct force_array { + s32 fx; + s32 fy; + s32 fz; + s32 mx; + s32 my; + s32 mz; + s32 v1; + s32 v2; +}; + +/* The six_axis_array structure shows the layout for the offsets and + * the full scales. + */ +struct six_axis_array { + s32 fx; + s32 fy; + s32 fz; + s32 mx; + s32 my; + s32 mz; +}; + +/* VECT_BITS */ +/* The vect_bits structure shows the layout for indicating + * which axes to use in computing the vectors. Each bit signifies + * selection of a single axis. The V1x axis bit corresponds to a hex + * value of 0x0001 and the V2z bit corresponds to a hex value of + * 0x0020. Example: to specify the axes V1x, V1y, V2x, and V2z the + * pattern would be 0x002b. Vector 1 defaults to a force vector and + * vector 2 defaults to a moment vector. It is possible to change one + * or the other so that two force vectors or two moment vectors are + * calculated. Setting the changeV1 bit or the changeV2 bit will + * change that vector to be the opposite of its default. Therefore to + * have two force vectors, set changeV1 to 1. + */ + +/* vect_bits appears to be unused at this time */ +enum { + fx = 0x0001, + fy = 0x0002, + fz = 0x0004, + mx = 0x0008, + my = 0x0010, + mz = 0x0020, + changeV2 = 0x0040, + changeV1 = 0x0080 +} vect_bits_t; + +/* WARNING_BITS */ +/* The warning_bits structure shows the bit pattern for the warning + * word. The bit fields are shown from bit 0 (lsb) to bit 15 (msb). + */ + +/* XX_NEAR_SET */ +/* The xx_near_sat bits signify that the indicated axis has reached or + * exceeded the near saturation value. + */ + +enum { + fx_near_sat = 0x0001, + fy_near_sat = 0x0002, + fz_near_sat = 0x0004, + mx_near_sat = 0x0008, + my_near_sat = 0x0010, + mz_near_sat = 0x0020 +} warning_bits_t; + +/* ERROR_BITS */ +/* XX_SAT */ +/* MEMORY_ERROR */ +/* SENSOR_CHANGE */ + +/* The error_bits structure shows the bit pattern for the error word. + * The bit fields are shown from bit 0 (lsb) to bit 15 (msb). The + * xx_sat bits signify that the indicated axis has reached or exceeded + * the saturation value. The memory_error bit indicates that a problem + * was detected in the on-board RAM during the power-up + * initialization. The sensor_change bit indicates that a sensor other + * than the one originally plugged in has passed its CRC check. This + * bit latches, and must be reset by the user. + * + */ + +/* SYSTEM_BUSY */ + +/* The system_busy bit indicates that the JR3 DSP is currently busy + * and is not calculating force data. This occurs when a new + * coordinate transformation, or new sensor full scale is set by the + * user. A very fast system using the force data for feedback might + * become unstable during the approximately 4 ms needed to accomplish + * these calculations. This bit will also become active when a new + * sensor is plugged in and the system needs to recalculate the + * calibration CRC. + */ + +/* CAL_CRC_BAD */ + +/* The cal_crc_bad bit indicates that the calibration CRC has not + * calculated to zero. CRC is short for cyclic redundancy code. It is + * a method for determining the integrity of messages in data + * communication. The calibration data stored inside the sensor is + * transmitted to the JR3 DSP along with the sensor data. The + * calibration data has a CRC attached to the end of it, to assist in + * determining the completeness and integrity of the calibration data + * received from the sensor. There are two reasons the CRC may not + * have calculated to zero. The first is that all the calibration data + * has not yet been received, the second is that the calibration data + * has been corrupted. A typical sensor transmits the entire contents + * of its calibration matrix over 30 times a second. Therefore, if + * this bit is not zero within a couple of seconds after the sensor + * has been plugged in, there is a problem with the sensor's + * calibration data. + */ + +/* WATCH_DOG */ +/* WATCH_DOG2 */ + +/* The watch_dog and watch_dog2 bits are sensor, not processor, watch + * dog bits. Watch_dog indicates that the sensor data line seems to be + * acting correctly, while watch_dog2 indicates that sensor data and + * clock are being received. It is possible for watch_dog2 to go off + * while watch_dog does not. This would indicate an improper clock + * signal, while data is acting correctly. If either watch dog barks, + * the sensor data is not being received correctly. + */ + +enum error_bits_t { + fx_sat = 0x0001, + fy_sat = 0x0002, + fz_sat = 0x0004, + mx_sat = 0x0008, + my_sat = 0x0010, + mz_sat = 0x0020, + memory_error = 0x0400, + sensor_change = 0x0800, + system_busy = 0x1000, + cal_crc_bad = 0x2000, + watch_dog2 = 0x4000, + watch_dog = 0x8000 +}; + +/* THRESH_STRUCT */ + +/* This structure shows the layout for a single threshold packet inside of a + * load envelope. Each load envelope can contain several threshold structures. + * 1. data_address contains the address of the data for that threshold. This + * includes filtered, unfiltered, raw, rate, counters, error and warning data + * 2. threshold is the is the value at which, if data is above or below, the + * bits will be set ... (pag.24). + * 3. bit_pattern contains the bits that will be set if the threshold value is + * met or exceeded. + */ + +struct thresh_struct { + s32 data_address; + s32 threshold; + s32 bit_pattern; +}; + +/* LE_STRUCT */ + +/* Layout of a load enveloped packet. Four thresholds are showed ... for more + * see manual (pag.25) + * 1. latch_bits is a bit pattern that show which bits the user wants to latch. + * The latched bits will not be reset once the threshold which set them is + * no longer true. In that case the user must reset them using the reset_bit + * command. + * 2. number_of_xx_thresholds specify how many GE/LE threshold there are. + */ +struct le_struct { + s32 latch_bits; + s32 number_of_ge_thresholds; + s32 number_of_le_thresholds; + struct thresh_struct thresholds[4]; + s32 reserved; +}; + +/* LINK_TYPES */ +/* Link types is an enumerated value showing the different possible transform + * link types. + * 0 - end transform packet + * 1 - translate along X axis (TX) + * 2 - translate along Y axis (TY) + * 3 - translate along Z axis (TZ) + * 4 - rotate about X axis (RX) + * 5 - rotate about Y axis (RY) + * 6 - rotate about Z axis (RZ) + * 7 - negate all axes (NEG) + */ + +enum link_types { + end_x_form, + tx, + ty, + tz, + rx, + ry, + rz, + neg +}; + +/* TRANSFORM */ +/* Structure used to describe a transform. */ +struct intern_transform { + struct { + u32 link_type; + s32 link_amount; + } link[8]; +}; + +/* JR3 force/torque sensor data definition. For more information see sensor and */ +/* hardware manuals. */ + +struct jr3_channel { + /* Raw_channels is the area used to store the raw data coming from */ + /* the sensor. */ + + struct raw_channel raw_channels[16]; /* offset 0x0000 */ + + /* Copyright is a null terminated ASCII string containing the JR3 */ + /* copyright notice. */ + + u32 copyright[0x0018]; /* offset 0x0040 */ + s32 reserved1[0x0008]; /* offset 0x0058 */ + + /* Shunts contains the sensor shunt readings. Some JR3 sensors have + * the ability to have their gains adjusted. This allows the + * hardware full scales to be adjusted to potentially allow + * better resolution or dynamic range. For sensors that have + * this ability, the gain of each sensor channel is measured at + * the time of calibration using a shunt resistor. The shunt + * resistor is placed across one arm of the resistor bridge, and + * the resulting change in the output of that channel is + * measured. This measurement is called the shunt reading, and + * is recorded here. If the user has changed the gain of the // + * sensor, and made new shunt measurements, those shunt + * measurements can be placed here. The JR3 DSP will then scale + * the calibration matrix such so that the gains are again + * proper for the indicated shunt readings. If shunts is 0, then + * the sensor cannot have its gain changed. For details on + * changing the sensor gain, and making shunts readings, please + * see the sensor manual. To make these values take effect the + * user must call either command (5) use transform # (pg. 33) or + * command (10) set new full scales (pg. 38). + */ + + struct six_axis_array shunts; /* offset 0x0060 */ + s32 reserved2[2]; /* offset 0x0066 */ + + /* Default_FS contains the full scale that is used if the user does */ + /* not set a full scale. */ + + struct six_axis_array default_FS; /* offset 0x0068 */ + s32 reserved3; /* offset 0x006e */ + + /* Load_envelope_num is the load envelope number that is currently + * in use. This value is set by the user after one of the load + * envelopes has been initialized. + */ + + s32 load_envelope_num; /* offset 0x006f */ + + /* Min_full_scale is the recommend minimum full scale. */ + + /* These values in conjunction with max_full_scale (pg. 9) helps + * determine the appropriate value for setting the full scales. The + * software allows the user to set the sensor full scale to an + * arbitrary value. But setting the full scales has some hazards. If + * the full scale is set too low, the data will saturate + * prematurely, and dynamic range will be lost. If the full scale is + * set too high, then resolution is lost as the data is shifted to + * the right and the least significant bits are lost. Therefore the + * maximum full scale is the maximum value at which no resolution is + * lost, and the minimum full scale is the value at which the data + * will not saturate prematurely. These values are calculated + * whenever a new coordinate transformation is calculated. It is + * possible for the recommended maximum to be less than the + * recommended minimum. This comes about primarily when using + * coordinate translations. If this is the case, it means that any + * full scale selection will be a compromise between dynamic range + * and resolution. It is usually recommended to compromise in favor + * of resolution which means that the recommend maximum full scale + * should be chosen. + * + * WARNING: Be sure that the full scale is no less than 0.4% of the + * recommended minimum full scale. Full scales below this value will + * cause erroneous results. + */ + + struct six_axis_array min_full_scale; /* offset 0x0070 */ + s32 reserved4; /* offset 0x0076 */ + + /* Transform_num is the transform number that is currently in use. + * This value is set by the JR3 DSP after the user has used command + * (5) use transform # (pg. 33). + */ + + s32 transform_num; /* offset 0x0077 */ + + /* Max_full_scale is the recommended maximum full scale. See */ + /* min_full_scale (pg. 9) for more details. */ + + struct six_axis_array max_full_scale; /* offset 0x0078 */ + s32 reserved5; /* offset 0x007e */ + + /* Peak_address is the address of the data which will be monitored + * by the peak routine. This value is set by the user. The peak + * routine will monitor any 8 contiguous addresses for peak values. + * (ex. to watch filter3 data for peaks, set this value to 0x00a8). + */ + + s32 peak_address; /* offset 0x007f */ + + /* Full_scale is the sensor full scales which are currently in use. + * Decoupled and filtered data is scaled so that +/- 16384 is equal + * to the full scales. The engineering units used are indicated by + * the units value discussed on page 16. The full scales for Fx, Fy, + * Fz, Mx, My and Mz can be written by the user prior to calling + * command (10) set new full scales (pg. 38). The full scales for V1 + * and V2 are set whenever the full scales are changed or when the + * axes used to calculate the vectors are changed. The full scale of + * V1 and V2 will always be equal to the largest full scale of the + * axes used for each vector respectively. + */ + + struct force_array full_scale; /* offset 0x0080 */ + + /* Offsets contains the sensor offsets. These values are subtracted from + * the sensor data to obtain the decoupled data. The offsets are set a + * few seconds (< 10) after the calibration data has been received. + * They are set so that the output data will be zero. These values + * can be written as well as read. The JR3 DSP will use the values + * written here within 2 ms of being written. To set future + * decoupled data to zero, add these values to the current decoupled + * data values and place the sum here. The JR3 DSP will change these + * values when a new transform is applied. So if the offsets are + * such that FX is 5 and all other values are zero, after rotating + * about Z by 90 degrees, FY would be 5 and all others would be zero. + */ + + struct six_axis_array offsets; /* offset 0x0088 */ + + /* Offset_num is the number of the offset currently in use. This + * value is set by the JR3 DSP after the user has executed the use + * offset # command (pg. 34). It can vary between 0 and 15. + */ + + s32 offset_num; /* offset 0x008e */ + + /* Vect_axes is a bit map showing which of the axes are being used + * in the vector calculations. This value is set by the JR3 DSP + * after the user has executed the set vector axes command (pg. 37). + */ + + u32 vect_axes; /* offset 0x008f */ + + /* Filter0 is the decoupled, unfiltered data from the JR3 sensor. + * This data has had the offsets removed. + * + * These force_arrays hold the filtered data. The decoupled data is + * passed through cascaded low pass filters. Each succeeding filter + * has a cutoff frequency of 1/4 of the preceding filter. The cutoff + * frequency of filter1 is 1/16 of the sample rate from the sensor. + * For a typical sensor with a sample rate of 8 kHz, the cutoff + * frequency of filter1 would be 500 Hz. The following filters would + * cutoff at 125 Hz, 31.25 Hz, 7.813 Hz, 1.953 Hz and 0.4883 Hz. + */ + + struct force_array filter[7]; /* offset 0x0090, + offset 0x0098, + offset 0x00a0, + offset 0x00a8, + offset 0x00b0, + offset 0x00b8 , + offset 0x00c0 */ + + /* Rate_data is the calculated rate data. It is a first derivative + * calculation. It is calculated at a frequency specified by the + * variable rate_divisor (pg. 12). The data on which the rate is + * calculated is specified by the variable rate_address (pg. 12). + */ + + struct force_array rate_data; /* offset 0x00c8 */ + + /* Minimum_data & maximum_data are the minimum and maximum (peak) + * data values. The JR3 DSP can monitor any 8 contiguous data items + * for minimums and maximums at full sensor bandwidth. This area is + * only updated at user request. This is done so that the user does + * not miss any peaks. To read the data, use either the read peaks + * command (pg. 40), or the read and reset peaks command (pg. 39). + * The address of the data to watch for peaks is stored in the + * variable peak_address (pg. 10). Peak data is lost when executing + * a coordinate transformation or a full scale change. Peak data is + * also lost when plugging in a new sensor. + */ + + struct force_array minimum_data; /* offset 0x00d0 */ + struct force_array maximum_data; /* offset 0x00d8 */ + + /* Near_sat_value & sat_value contain the value used to determine if + * the raw sensor is saturated. Because of decoupling and offset + * removal, it is difficult to tell from the processed data if the + * sensor is saturated. These values, in conjunction with the error + * and warning words (pg. 14), provide this critical information. + * These two values may be set by the host processor. These values + * are positive signed values, since the saturation logic uses the + * absolute values of the raw data. The near_sat_value defaults to + * approximately 80% of the ADC's full scale, which is 26214, while + * sat_value defaults to the ADC's full scale: + * + * sat_value = 32768 - 2^(16 - ADC bits) + */ + + s32 near_sat_value; /* offset 0x00e0 */ + s32 sat_value; /* offset 0x00e1 */ + + /* Rate_address, rate_divisor & rate_count contain the data used to + * control the calculations of the rates. Rate_address is the + * address of the data used for the rate calculation. The JR3 DSP + * will calculate rates for any 8 contiguous values (ex. to + * calculate rates for filter3 data set rate_address to 0x00a8). + * Rate_divisor is how often the rate is calculated. If rate_divisor + * is 1, the rates are calculated at full sensor bandwidth. If + * rate_divisor is 200, rates are calculated every 200 samples. + * Rate_divisor can be any value between 1 and 65536. Set + * rate_divisor to 0 to calculate rates every 65536 samples. + * Rate_count starts at zero and counts until it equals + * rate_divisor, at which point the rates are calculated, and + * rate_count is reset to 0. When setting a new rate divisor, it is + * a good idea to set rate_count to one less than rate divisor. This + * will minimize the time necessary to start the rate calculations. + */ + + s32 rate_address; /* offset 0x00e2 */ + u32 rate_divisor; /* offset 0x00e3 */ + u32 rate_count; /* offset 0x00e4 */ + + /* Command_word2 through command_word0 are the locations used to + * send commands to the JR3 DSP. Their usage varies with the command + * and is detailed later in the Command Definitions section (pg. + * 29). In general the user places values into various memory + * locations, and then places the command word into command_word0. + * The JR3 DSP will process the command and place a 0 into + * command_word0 to indicate successful completion. Alternatively + * the JR3 DSP will place a negative number into command_word0 to + * indicate an error condition. Please note the command locations + * are numbered backwards. (I.E. command_word2 comes before + * command_word1). + */ + + s32 command_word2; /* offset 0x00e5 */ + s32 command_word1; /* offset 0x00e6 */ + s32 command_word0; /* offset 0x00e7 */ + + /* Count1 through count6 are unsigned counters which are incremented + * every time the matching filters are calculated. Filter1 is + * calculated at the sensor data bandwidth. So this counter would + * increment at 8 kHz for a typical sensor. The rest of the counters + * are incremented at 1/4 the interval of the counter immediately + * preceding it, so they would count at 2 kHz, 500 Hz, 125 Hz etc. + * These counters can be used to wait for data. Each time the + * counter changes, the corresponding data set can be sampled, and + * this will insure that the user gets each sample, once, and only + * once. + */ + + u32 count1; /* offset 0x00e8 */ + u32 count2; /* offset 0x00e9 */ + u32 count3; /* offset 0x00ea */ + u32 count4; /* offset 0x00eb */ + u32 count5; /* offset 0x00ec */ + u32 count6; /* offset 0x00ed */ + + /* Error_count is a running count of data reception errors. If this + * counter is changing rapidly, it probably indicates a bad sensor + * cable connection or other hardware problem. In most installations + * error_count should not change at all. But it is possible in an + * extremely noisy environment to experience occasional errors even + * without a hardware problem. If the sensor is well grounded, this + * is probably unavoidable in these environments. On the occasions + * where this counter counts a bad sample, that sample is ignored. + */ + + u32 error_count; /* offset 0x00ee */ + + /* Count_x is a counter which is incremented every time the JR3 DSP + * searches its job queues and finds nothing to do. It indicates the + * amount of idle time the JR3 DSP has available. It can also be + * used to determine if the JR3 DSP is alive. See the Performance + * Issues section on pg. 49 for more details. + */ + + u32 count_x; /* offset 0x00ef */ + + /* Warnings & errors contain the warning and error bits + * respectively. The format of these two words is discussed on page + * 21 under the headings warnings_bits and error_bits. + */ + + u32 warnings; /* offset 0x00f0 */ + u32 errors; /* offset 0x00f1 */ + + /* Threshold_bits is a word containing the bits that are set by the + * load envelopes. See load_envelopes (pg. 17) and thresh_struct + * (pg. 23) for more details. + */ + + s32 threshold_bits; /* offset 0x00f2 */ + + /* Last_crc is the value that shows the actual calculated CRC. CRC + * is short for cyclic redundancy code. It should be zero. See the + * description for cal_crc_bad (pg. 21) for more information. + */ + + s32 last_CRC; /* offset 0x00f3 */ + + /* EEProm_ver_no contains the version number of the sensor EEProm. + * EEProm version numbers can vary between 0 and 255. + * Software_ver_no contains the software version number. Version + * 3.02 would be stored as 302. + */ + + s32 eeprom_ver_no; /* offset 0x00f4 */ + s32 software_ver_no; /* offset 0x00f5 */ + + /* Software_day & software_year are the release date of the software + * the JR3 DSP is currently running. Day is the day of the year, + * with January 1 being 1, and December 31, being 365 for non leap + * years. + */ + + s32 software_day; /* offset 0x00f6 */ + s32 software_year; /* offset 0x00f7 */ + + /* Serial_no & model_no are the two values which uniquely identify a + * sensor. This model number does not directly correspond to the JR3 + * model number, but it will provide a unique identifier for + * different sensor configurations. + */ + + u32 serial_no; /* offset 0x00f8 */ + u32 model_no; /* offset 0x00f9 */ + + /* Cal_day & cal_year are the sensor calibration date. Day is the + * day of the year, with January 1 being 1, and December 31, being + * 366 for leap years. + */ + + s32 cal_day; /* offset 0x00fa */ + s32 cal_year; /* offset 0x00fb */ + + /* Units is an enumerated read only value defining the engineering + * units used in the sensor full scale. The meanings of particular + * values are discussed in the section detailing the force_units + * structure on page 22. The engineering units are setto customer + * specifications during sensor manufacture and cannot be changed by + * writing to Units. + * + * Bits contains the number of bits of resolution of the ADC + * currently in use. + * + * Channels is a bit field showing which channels the current sensor + * is capable of sending. If bit 0 is active, this sensor can send + * channel 0, if bit 13 is active, this sensor can send channel 13, + * etc. This bit can be active, even if the sensor is not currently + * sending this channel. Some sensors are configurable as to which + * channels to send, and this field only contains information on the + * channels available to send, not on the current configuration. To + * find which channels are currently being sent, monitor the + * Raw_time fields (pg. 19) in the raw_channels array (pg. 7). If + * the time is changing periodically, then that channel is being + * received. + */ + + u32 units; /* offset 0x00fc */ + s32 bits; /* offset 0x00fd */ + s32 channels; /* offset 0x00fe */ + + /* Thickness specifies the overall thickness of the sensor from + * flange to flange. The engineering units for this value are + * contained in units (pg. 16). The sensor calibration is relative + * to the center of the sensor. This value allows easy coordinate + * transformation from the center of the sensor to either flange. + */ + + s32 thickness; /* offset 0x00ff */ + + /* Load_envelopes is a table containing the load envelope + * descriptions. There are 16 possible load envelope slots in the + * table. The slots are on 16 word boundaries and are numbered 0-15. + * Each load envelope needs to start at the beginning of a slot but + * need not be fully contained in that slot. That is to say that a + * single load envelope can be larger than a single slot. The + * software has been tested and ran satisfactorily with 50 + * thresholds active. A single load envelope this large would take + * up 5 of the 16 slots. The load envelope data is laid out in an + * order that is most efficient for the JR3 DSP. The structure is + * detailed later in the section showing the definition of the + * le_struct structure (pg. 23). + */ + + struct le_struct load_envelopes[0x10]; /* offset 0x0100 */ + + /* Transforms is a table containing the transform descriptions. + * There are 16 possible transform slots in the table. The slots are + * on 16 word boundaries and are numbered 0-15. Each transform needs + * to start at the beginning of a slot but need not be fully + * contained in that slot. That is to say that a single transform + * can be larger than a single slot. A transform is 2 * no of links + * + 1 words in length. So a single slot can contain a transform + * with 7 links. Two slots can contain a transform that is 15 links. + * The layout is detailed later in the section showing the + * definition of the transform structure (pg. 26). + */ + + struct intern_transform transforms[0x10]; /* offset 0x0200 */ +}; + +struct jr3_t { + struct { + u32 program_low[0x4000]; /* 0x00000 - 0x10000 */ + struct jr3_channel data; /* 0x10000 - 0x10c00 */ + char pad2[0x30000 - 0x00c00]; /* 0x10c00 - 0x40000 */ + u32 program_high[0x8000]; /* 0x40000 - 0x60000 */ + u32 reset; /* 0x60000 - 0x60004 */ + char pad3[0x20000 - 0x00004]; /* 0x60004 - 0x80000 */ + } channel[4]; +}; diff --git a/drivers/staging/comedi/drivers/ke_counter.c b/drivers/staging/comedi/drivers/ke_counter.c new file mode 100644 index 00000000..4e9e9a07 --- /dev/null +++ b/drivers/staging/comedi/drivers/ke_counter.c @@ -0,0 +1,297 @@ +/* + comedi/drivers/ke_counter.c + Comedi driver for Kolter-Electronic PCI Counter 1 Card + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: ke_counter +Description: Driver for Kolter Electronic Counter Card +Devices: [Kolter Electronic] PCI Counter Card (ke_counter) +Author: Michael Hillmann +Updated: Mon, 14 Apr 2008 15:42:42 +0100 +Status: tested + +Configuration Options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. + +This driver is a simple driver to read the counter values from +Kolter Electronic PCI Counter Card. +*/ + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#define CNT_DRIVER_NAME "ke_counter" +#define PCI_VENDOR_ID_KOLTER 0x1001 +#define CNT_CARD_DEVICE_ID 0x0014 + +/*-- function prototypes ----------------------------------------------------*/ + +static int cnt_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int cnt_detach(struct comedi_device *dev); + +static DEFINE_PCI_DEVICE_TABLE(cnt_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_KOLTER, CNT_CARD_DEVICE_ID) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, cnt_pci_table); + +/*-- board specification structure ------------------------------------------*/ + +struct cnt_board_struct { + + const char *name; + int device_id; + int cnt_channel_nbr; + int cnt_bits; +}; + +static const struct cnt_board_struct cnt_boards[] = { + { + .name = CNT_DRIVER_NAME, + .device_id = CNT_CARD_DEVICE_ID, + .cnt_channel_nbr = 3, + .cnt_bits = 24} +}; + +#define cnt_board_nbr (sizeof(cnt_boards)/sizeof(struct cnt_board_struct)) + +/*-- device private structure -----------------------------------------------*/ + +struct cnt_device_private { + + struct pci_dev *pcidev; +}; + +#define devpriv ((struct cnt_device_private *)dev->private) + +static struct comedi_driver cnt_driver = { + .driver_name = CNT_DRIVER_NAME, + .module = THIS_MODULE, + .attach = cnt_attach, + .detach = cnt_detach, +}; + +static int __devinit cnt_driver_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, cnt_driver.driver_name); +} + +static void __devexit cnt_driver_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver cnt_driver_pci_driver = { + .id_table = cnt_pci_table, + .probe = &cnt_driver_pci_probe, + .remove = __devexit_p(&cnt_driver_pci_remove) +}; + +static int __init cnt_driver_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&cnt_driver); + if (retval < 0) + return retval; + + cnt_driver_pci_driver.name = (char *)cnt_driver.driver_name; + return pci_register_driver(&cnt_driver_pci_driver); +} + +static void __exit cnt_driver_cleanup_module(void) +{ + pci_unregister_driver(&cnt_driver_pci_driver); + comedi_driver_unregister(&cnt_driver); +} + +module_init(cnt_driver_init_module); +module_exit(cnt_driver_cleanup_module); + +/*-- counter write ----------------------------------------------------------*/ + +/* This should be used only for resetting the counters; maybe it is better + to make a special command 'reset'. */ +static int cnt_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + + outb((unsigned char)((data[0] >> 24) & 0xff), + dev->iobase + chan * 0x20 + 0x10); + outb((unsigned char)((data[0] >> 16) & 0xff), + dev->iobase + chan * 0x20 + 0x0c); + outb((unsigned char)((data[0] >> 8) & 0xff), + dev->iobase + chan * 0x20 + 0x08); + outb((unsigned char)((data[0] >> 0) & 0xff), + dev->iobase + chan * 0x20 + 0x04); + + /* return the number of samples written */ + return 1; +} + +/*-- counter read -----------------------------------------------------------*/ + +static int cnt_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + unsigned char a0, a1, a2, a3, a4; + int chan = CR_CHAN(insn->chanspec); + int result; + + a0 = inb(dev->iobase + chan * 0x20); + a1 = inb(dev->iobase + chan * 0x20 + 0x04); + a2 = inb(dev->iobase + chan * 0x20 + 0x08); + a3 = inb(dev->iobase + chan * 0x20 + 0x0c); + a4 = inb(dev->iobase + chan * 0x20 + 0x10); + + result = (a1 + (a2 * 256) + (a3 * 65536)); + if (a4 > 0) + result = result - s->maxdata; + + *data = (unsigned int)result; + + /* return the number of samples read */ + return 1; +} + +/*-- attach -----------------------------------------------------------------*/ + +static int cnt_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *subdevice; + struct pci_dev *pci_device = NULL; + struct cnt_board_struct *board; + unsigned long io_base; + int error, i; + + /* allocate device private structure */ + error = alloc_private(dev, sizeof(struct cnt_device_private)); + if (error < 0) + return error; + + /* Probe the device to determine what device in the series it is. */ + for_each_pci_dev(pci_device) { + if (pci_device->vendor == PCI_VENDOR_ID_KOLTER) { + for (i = 0; i < cnt_board_nbr; i++) { + if (cnt_boards[i].device_id == + pci_device->device) { + /* was a particular bus/slot requested? */ + if ((it->options[0] != 0) + || (it->options[1] != 0)) { + /* are we on the wrong bus/slot? */ + if (pci_device->bus->number != + it->options[0] + || + PCI_SLOT(pci_device->devfn) + != it->options[1]) { + continue; + } + } + + dev->board_ptr = cnt_boards + i; + board = + (struct cnt_board_struct *) + dev->board_ptr; + goto found; + } + } + } + } + printk(KERN_WARNING + "comedi%d: no supported board found! (req. bus/slot: %d/%d)\n", + dev->minor, it->options[0], it->options[1]); + return -EIO; + +found: + printk(KERN_INFO + "comedi%d: found %s at PCI bus %d, slot %d\n", dev->minor, + board->name, pci_device->bus->number, + PCI_SLOT(pci_device->devfn)); + devpriv->pcidev = pci_device; + dev->board_name = board->name; + + /* enable PCI device and request regions */ + error = comedi_pci_enable(pci_device, CNT_DRIVER_NAME); + if (error < 0) { + printk(KERN_WARNING "comedi%d: " + "failed to enable PCI device and request regions!\n", + dev->minor); + return error; + } + + /* read register base address [PCI_BASE_ADDRESS #0] */ + io_base = pci_resource_start(pci_device, 0); + dev->iobase = io_base; + + /* allocate the subdevice structures */ + error = alloc_subdevices(dev, 1); + if (error < 0) + return error; + + subdevice = dev->subdevices + 0; + dev->read_subdev = subdevice; + + subdevice->type = COMEDI_SUBD_COUNTER; + subdevice->subdev_flags = SDF_READABLE /* | SDF_COMMON */ ; + subdevice->n_chan = board->cnt_channel_nbr; + subdevice->maxdata = (1 << board->cnt_bits) - 1; + subdevice->insn_read = cnt_rinsn; + subdevice->insn_write = cnt_winsn; + + /* select 20MHz clock */ + outb(3, dev->iobase + 248); + + /* reset all counters */ + outb(0, dev->iobase); + outb(0, dev->iobase + 0x20); + outb(0, dev->iobase + 0x40); + + printk(KERN_INFO "comedi%d: " CNT_DRIVER_NAME " attached.\n", + dev->minor); + return 0; +} + +/*-- detach -----------------------------------------------------------------*/ + +static int cnt_detach(struct comedi_device *dev) +{ + if (devpriv && devpriv->pcidev) { + if (dev->iobase) + comedi_pci_disable(devpriv->pcidev); + pci_dev_put(devpriv->pcidev); + } + printk(KERN_INFO "comedi%d: " CNT_DRIVER_NAME " remove\n", + dev->minor); + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/me4000.c b/drivers/staging/comedi/drivers/me4000.c new file mode 100644 index 00000000..b0bc6bb8 --- /dev/null +++ b/drivers/staging/comedi/drivers/me4000.c @@ -0,0 +1,2467 @@ +/* + comedi/drivers/me4000.c + Source code for the Meilhaus ME-4000 board family. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: me4000 +Description: Meilhaus ME-4000 series boards +Devices: [Meilhaus] ME-4650 (me4000), ME-4670i, ME-4680, ME-4680i, ME-4680is +Author: gg (Guenter Gebhardt ) +Updated: Mon, 18 Mar 2002 15:34:01 -0800 +Status: broken (no support for loading firmware) + +Supports: + + - Analog Input + - Analog Output + - Digital I/O + - Counter + +Configuration Options: + + [0] - PCI bus number (optional) + [1] - PCI slot number (optional) + + If bus/slot is not specified, the first available PCI + device will be used. + +The firmware required by these boards is available in the +comedi_nonfree_firmware tarball available from +http://www.comedi.org. However, the driver's support for +loading the firmware through comedi_config is currently +broken. + + */ + +#include +#include "../comedidev.h" + +#include +#include +#include + +#include "comedi_pci.h" +#include "me4000.h" +#if 0 +/* file removed due to GPL incompatibility */ +#include "me4000_fw.h" +#endif + +/*============================================================================= + PCI device table. + This is used by modprobe to translate PCI IDs to drivers. + ===========================================================================*/ + +static DEFINE_PCI_DEVICE_TABLE(me4000_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4650) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4660) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4661) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4662) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4663) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4670) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4671) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4672) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4673) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4680) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4681) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4682) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4683) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, me4000_pci_table); + +static const struct me4000_board me4000_boards[] = { + {"ME-4650", 0x4650, {0, 0}, {16, 0, 0, 0}, {4}, {0} }, + + {"ME-4660", 0x4660, {0, 0}, {32, 0, 16, 0}, {4}, {3} }, + {"ME-4660i", 0x4661, {0, 0}, {32, 0, 16, 0}, {4}, {3} }, + {"ME-4660s", 0x4662, {0, 0}, {32, 8, 16, 0}, {4}, {3} }, + {"ME-4660is", 0x4663, {0, 0}, {32, 8, 16, 0}, {4}, {3} }, + + {"ME-4670", 0x4670, {4, 0}, {32, 0, 16, 1}, {4}, {3} }, + {"ME-4670i", 0x4671, {4, 0}, {32, 0, 16, 1}, {4}, {3} }, + {"ME-4670s", 0x4672, {4, 0}, {32, 8, 16, 1}, {4}, {3} }, + {"ME-4670is", 0x4673, {4, 0}, {32, 8, 16, 1}, {4}, {3} }, + + {"ME-4680", 0x4680, {4, 4}, {32, 0, 16, 1}, {4}, {3} }, + {"ME-4680i", 0x4681, {4, 4}, {32, 0, 16, 1}, {4}, {3} }, + {"ME-4680s", 0x4682, {4, 4}, {32, 8, 16, 1}, {4}, {3} }, + {"ME-4680is", 0x4683, {4, 4}, {32, 8, 16, 1}, {4}, {3} }, + + {0}, +}; + +#define ME4000_BOARD_VERSIONS (ARRAY_SIZE(me4000_boards) - 1) + +/*----------------------------------------------------------------------------- + Comedi function prototypes + ---------------------------------------------------------------------------*/ +static int me4000_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int me4000_detach(struct comedi_device *dev); +static struct comedi_driver driver_me4000 = { + .driver_name = "me4000", + .module = THIS_MODULE, + .attach = me4000_attach, + .detach = me4000_detach, +}; + +/*----------------------------------------------------------------------------- + Meilhaus function prototypes + ---------------------------------------------------------------------------*/ +static int me4000_probe(struct comedi_device *dev, struct comedi_devconfig *it); +static int get_registers(struct comedi_device *dev, struct pci_dev *pci_dev_p); +static int init_board_info(struct comedi_device *dev, + struct pci_dev *pci_dev_p); +static int init_ao_context(struct comedi_device *dev); +static int init_ai_context(struct comedi_device *dev); +static int init_dio_context(struct comedi_device *dev); +static int init_cnt_context(struct comedi_device *dev); +static int xilinx_download(struct comedi_device *dev); +static int reset_board(struct comedi_device *dev); + +static int me4000_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int me4000_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int cnt_reset(struct comedi_device *dev, unsigned int channel); + +static int cnt_config(struct comedi_device *dev, + unsigned int channel, unsigned int mode); + +static int me4000_cnt_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int me4000_cnt_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int me4000_cnt_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int me4000_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *subdevice, + struct comedi_insn *insn, unsigned int *data); + +static int me4000_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); + +static int ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); + +static int ai_round_cmd_args(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd, + unsigned int *init_ticks, + unsigned int *scan_ticks, + unsigned int *chan_ticks); + +static int ai_prepare(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd, + unsigned int init_ticks, + unsigned int scan_ticks, unsigned int chan_ticks); + +static int ai_write_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); + +static irqreturn_t me4000_ai_isr(int irq, void *dev_id); + +static int me4000_ai_do_cmd_test(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); + +static int me4000_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); + +static int me4000_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int me4000_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/*----------------------------------------------------------------------------- + Meilhaus inline functions + ---------------------------------------------------------------------------*/ + +static inline void me4000_outb(struct comedi_device *dev, unsigned char value, + unsigned long port) +{ + PORT_PDEBUG("--> 0x%02X port 0x%04lX\n", value, port); + outb(value, port); +} + +static inline void me4000_outl(struct comedi_device *dev, unsigned long value, + unsigned long port) +{ + PORT_PDEBUG("--> 0x%08lX port 0x%04lX\n", value, port); + outl(value, port); +} + +static inline unsigned long me4000_inl(struct comedi_device *dev, + unsigned long port) +{ + unsigned long value; + value = inl(port); + PORT_PDEBUG("<-- 0x%08lX port 0x%04lX\n", value, port); + return value; +} + +static inline unsigned char me4000_inb(struct comedi_device *dev, + unsigned long port) +{ + unsigned char value; + value = inb(port); + PORT_PDEBUG("<-- 0x%08X port 0x%04lX\n", value, port); + return value; +} + +static const struct comedi_lrange me4000_ai_range = { + 4, + { + UNI_RANGE(2.5), + UNI_RANGE(10), + BIP_RANGE(2.5), + BIP_RANGE(10), + } +}; + +static const struct comedi_lrange me4000_ao_range = { + 1, + { + BIP_RANGE(10), + } +}; + +static int me4000_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int result; + + CALL_PDEBUG("In me4000_attach()\n"); + + result = me4000_probe(dev, it); + if (result) + return result; + + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. It relies on + * n_subdevices being set correctly. + */ + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + /*========================================================================= + Analog input subdevice + ========================================================================*/ + + s = dev->subdevices + 0; + + if (thisboard->ai.count) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; + s->n_chan = thisboard->ai.count; + s->maxdata = 0xFFFF; /* 16 bit ADC */ + s->len_chanlist = ME4000_AI_CHANNEL_LIST_COUNT; + s->range_table = &me4000_ai_range; + s->insn_read = me4000_ai_insn_read; + + if (info->irq > 0) { + if (request_irq(info->irq, me4000_ai_isr, + IRQF_SHARED, "ME-4000", dev)) { + printk + ("comedi%d: me4000: me4000_attach(): " + "Unable to allocate irq\n", dev->minor); + } else { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->cancel = me4000_ai_cancel; + s->do_cmdtest = me4000_ai_do_cmd_test; + s->do_cmd = me4000_ai_do_cmd; + } + } else { + printk(KERN_WARNING + "comedi%d: me4000: me4000_attach(): " + "No interrupt available\n", dev->minor); + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /*========================================================================= + Analog output subdevice + ========================================================================*/ + + s = dev->subdevices + 1; + + if (thisboard->ao.count) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_COMMON | SDF_GROUND; + s->n_chan = thisboard->ao.count; + s->maxdata = 0xFFFF; /* 16 bit DAC */ + s->range_table = &me4000_ao_range; + s->insn_write = me4000_ao_insn_write; + s->insn_read = me4000_ao_insn_read; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /*========================================================================= + Digital I/O subdevice + ========================================================================*/ + + s = dev->subdevices + 2; + + if (thisboard->dio.count) { + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = thisboard->dio.count * 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = me4000_dio_insn_bits; + s->insn_config = me4000_dio_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* + * Check for optoisolated ME-4000 version. If one the first + * port is a fixed output port and the second is a fixed input port. + */ + if (!me4000_inl(dev, info->dio_context.dir_reg)) { + s->io_bits |= 0xFF; + me4000_outl(dev, ME4000_DIO_CTRL_BIT_MODE_0, + info->dio_context.dir_reg); + } + + /*========================================================================= + Counter subdevice + ========================================================================*/ + + s = dev->subdevices + 3; + + if (thisboard->cnt.count) { + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = thisboard->cnt.count; + s->maxdata = 0xFFFF; /* 16 bit counters */ + s->insn_read = me4000_cnt_insn_read; + s->insn_write = me4000_cnt_insn_write; + s->insn_config = me4000_cnt_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +static int me4000_probe(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pci_dev *pci_device = NULL; + int result, i; + struct me4000_board *board; + + CALL_PDEBUG("In me4000_probe()\n"); + + /* Allocate private memory */ + if (alloc_private(dev, sizeof(struct me4000_info)) < 0) + return -ENOMEM; + + /* + * Probe the device to determine what device in the series it is. + */ + for_each_pci_dev(pci_device) { + if (pci_device->vendor == PCI_VENDOR_ID_MEILHAUS) { + for (i = 0; i < ME4000_BOARD_VERSIONS; i++) { + if (me4000_boards[i].device_id == + pci_device->device) { + /* + * Was a particular + * bus/slot requested? + */ + if ((it->options[0] != 0) + || (it->options[1] != 0)) { + /* + * Are we on the wrong + * bus/slot? + */ + if (pci_device->bus->number != + it->options[0] + || + PCI_SLOT(pci_device->devfn) + != it->options[1]) { + continue; + } + } + dev->board_ptr = me4000_boards + i; + board = + (struct me4000_board *) + dev->board_ptr; + info->pci_dev_p = pci_device; + goto found; + } + } + } + } + + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): " + "No supported board found (req. bus/slot : %d/%d)\n", + dev->minor, it->options[0], it->options[1]); + return -ENODEV; + +found: + + printk(KERN_INFO + "comedi%d: me4000: me4000_probe(): " + "Found %s at PCI bus %d, slot %d\n", + dev->minor, me4000_boards[i].name, pci_device->bus->number, + PCI_SLOT(pci_device->devfn)); + + /* Set data in device structure */ + dev->board_name = board->name; + + /* Enable PCI device and request regions */ + result = comedi_pci_enable(pci_device, dev->board_name); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Cannot enable PCI " + "device and request I/O regions\n", dev->minor); + return result; + } + + /* Get the PCI base registers */ + result = get_registers(dev, pci_device); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): " + "Cannot get registers\n", dev->minor); + return result; + } + /* Initialize board info */ + result = init_board_info(dev, pci_device); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): " + "Cannot init baord info\n", dev->minor); + return result; + } + + /* Init analog output context */ + result = init_ao_context(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): " + "Cannot init ao context\n", dev->minor); + return result; + } + + /* Init analog input context */ + result = init_ai_context(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): " + "Cannot init ai context\n", dev->minor); + return result; + } + + /* Init digital I/O context */ + result = init_dio_context(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): " + "Cannot init dio context\n", dev->minor); + return result; + } + + /* Init counter context */ + result = init_cnt_context(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): " + "Cannot init cnt context\n", dev->minor); + return result; + } + + /* Download the xilinx firmware */ + result = xilinx_download(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): " + "Can't download firmware\n", dev->minor); + return result; + } + + /* Make a hardware reset */ + result = reset_board(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Can't reset board\n", + dev->minor); + return result; + } + + return 0; +} + +static int get_registers(struct comedi_device *dev, struct pci_dev *pci_dev_p) +{ + + CALL_PDEBUG("In get_registers()\n"); + + /*--------------------------- plx regbase -------------------------------*/ + + info->plx_regbase = pci_resource_start(pci_dev_p, 1); + if (info->plx_regbase == 0) { + printk(KERN_ERR + "comedi%d: me4000: get_registers(): " + "PCI base address 1 is not available\n", dev->minor); + return -ENODEV; + } + info->plx_regbase_size = pci_resource_len(pci_dev_p, 1); + + /*--------------------------- me4000 regbase ----------------------------*/ + + info->me4000_regbase = pci_resource_start(pci_dev_p, 2); + if (info->me4000_regbase == 0) { + printk(KERN_ERR + "comedi%d: me4000: get_registers(): " + "PCI base address 2 is not available\n", dev->minor); + return -ENODEV; + } + info->me4000_regbase_size = pci_resource_len(pci_dev_p, 2); + + /*--------------------------- timer regbase ------------------------------*/ + + info->timer_regbase = pci_resource_start(pci_dev_p, 3); + if (info->timer_regbase == 0) { + printk(KERN_ERR + "comedi%d: me4000: get_registers(): " + "PCI base address 3 is not available\n", dev->minor); + return -ENODEV; + } + info->timer_regbase_size = pci_resource_len(pci_dev_p, 3); + + /*--------------------------- program regbase ----------------------------*/ + + info->program_regbase = pci_resource_start(pci_dev_p, 5); + if (info->program_regbase == 0) { + printk(KERN_ERR + "comedi%d: me4000: get_registers(): " + "PCI base address 5 is not available\n", dev->minor); + return -ENODEV; + } + info->program_regbase_size = pci_resource_len(pci_dev_p, 5); + + return 0; +} + +static int init_board_info(struct comedi_device *dev, struct pci_dev *pci_dev_p) +{ + int result; + + CALL_PDEBUG("In init_board_info()\n"); + + /* Init spin locks */ + /* spin_lock_init(&info->preload_lock); */ + /* spin_lock_init(&info->ai_ctrl_lock); */ + + /* Get the serial number */ + result = pci_read_config_dword(pci_dev_p, 0x2C, &info->serial_no); + if (result != PCIBIOS_SUCCESSFUL) + return result; + + /* Get the hardware revision */ + result = pci_read_config_byte(pci_dev_p, 0x08, &info->hw_revision); + if (result != PCIBIOS_SUCCESSFUL) + return result; + + /* Get the vendor id */ + info->vendor_id = pci_dev_p->vendor; + + /* Get the device id */ + info->device_id = pci_dev_p->device; + + /* Get the irq assigned to the board */ + info->irq = pci_dev_p->irq; + + return 0; +} + +static int init_ao_context(struct comedi_device *dev) +{ + int i; + + CALL_PDEBUG("In init_ao_context()\n"); + + for (i = 0; i < thisboard->ao.count; i++) { + /* spin_lock_init(&info->ao_context[i].use_lock); */ + info->ao_context[i].irq = info->irq; + + switch (i) { + case 0: + info->ao_context[i].ctrl_reg = + info->me4000_regbase + ME4000_AO_00_CTRL_REG; + info->ao_context[i].status_reg = + info->me4000_regbase + ME4000_AO_00_STATUS_REG; + info->ao_context[i].fifo_reg = + info->me4000_regbase + ME4000_AO_00_FIFO_REG; + info->ao_context[i].single_reg = + info->me4000_regbase + ME4000_AO_00_SINGLE_REG; + info->ao_context[i].timer_reg = + info->me4000_regbase + ME4000_AO_00_TIMER_REG; + info->ao_context[i].irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ao_context[i].preload_reg = + info->me4000_regbase + ME4000_AO_LOADSETREG_XX; + break; + case 1: + info->ao_context[i].ctrl_reg = + info->me4000_regbase + ME4000_AO_01_CTRL_REG; + info->ao_context[i].status_reg = + info->me4000_regbase + ME4000_AO_01_STATUS_REG; + info->ao_context[i].fifo_reg = + info->me4000_regbase + ME4000_AO_01_FIFO_REG; + info->ao_context[i].single_reg = + info->me4000_regbase + ME4000_AO_01_SINGLE_REG; + info->ao_context[i].timer_reg = + info->me4000_regbase + ME4000_AO_01_TIMER_REG; + info->ao_context[i].irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ao_context[i].preload_reg = + info->me4000_regbase + ME4000_AO_LOADSETREG_XX; + break; + case 2: + info->ao_context[i].ctrl_reg = + info->me4000_regbase + ME4000_AO_02_CTRL_REG; + info->ao_context[i].status_reg = + info->me4000_regbase + ME4000_AO_02_STATUS_REG; + info->ao_context[i].fifo_reg = + info->me4000_regbase + ME4000_AO_02_FIFO_REG; + info->ao_context[i].single_reg = + info->me4000_regbase + ME4000_AO_02_SINGLE_REG; + info->ao_context[i].timer_reg = + info->me4000_regbase + ME4000_AO_02_TIMER_REG; + info->ao_context[i].irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ao_context[i].preload_reg = + info->me4000_regbase + ME4000_AO_LOADSETREG_XX; + break; + case 3: + info->ao_context[i].ctrl_reg = + info->me4000_regbase + ME4000_AO_03_CTRL_REG; + info->ao_context[i].status_reg = + info->me4000_regbase + ME4000_AO_03_STATUS_REG; + info->ao_context[i].fifo_reg = + info->me4000_regbase + ME4000_AO_03_FIFO_REG; + info->ao_context[i].single_reg = + info->me4000_regbase + ME4000_AO_03_SINGLE_REG; + info->ao_context[i].timer_reg = + info->me4000_regbase + ME4000_AO_03_TIMER_REG; + info->ao_context[i].irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ao_context[i].preload_reg = + info->me4000_regbase + ME4000_AO_LOADSETREG_XX; + break; + default: + break; + } + } + + return 0; +} + +static int init_ai_context(struct comedi_device *dev) +{ + + CALL_PDEBUG("In init_ai_context()\n"); + + info->ai_context.irq = info->irq; + + info->ai_context.ctrl_reg = info->me4000_regbase + ME4000_AI_CTRL_REG; + info->ai_context.status_reg = + info->me4000_regbase + ME4000_AI_STATUS_REG; + info->ai_context.channel_list_reg = + info->me4000_regbase + ME4000_AI_CHANNEL_LIST_REG; + info->ai_context.data_reg = info->me4000_regbase + ME4000_AI_DATA_REG; + info->ai_context.chan_timer_reg = + info->me4000_regbase + ME4000_AI_CHAN_TIMER_REG; + info->ai_context.chan_pre_timer_reg = + info->me4000_regbase + ME4000_AI_CHAN_PRE_TIMER_REG; + info->ai_context.scan_timer_low_reg = + info->me4000_regbase + ME4000_AI_SCAN_TIMER_LOW_REG; + info->ai_context.scan_timer_high_reg = + info->me4000_regbase + ME4000_AI_SCAN_TIMER_HIGH_REG; + info->ai_context.scan_pre_timer_low_reg = + info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_LOW_REG; + info->ai_context.scan_pre_timer_high_reg = + info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_HIGH_REG; + info->ai_context.start_reg = info->me4000_regbase + ME4000_AI_START_REG; + info->ai_context.irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ai_context.sample_counter_reg = + info->me4000_regbase + ME4000_AI_SAMPLE_COUNTER_REG; + + return 0; +} + +static int init_dio_context(struct comedi_device *dev) +{ + + CALL_PDEBUG("In init_dio_context()\n"); + + info->dio_context.dir_reg = info->me4000_regbase + ME4000_DIO_DIR_REG; + info->dio_context.ctrl_reg = info->me4000_regbase + ME4000_DIO_CTRL_REG; + info->dio_context.port_0_reg = + info->me4000_regbase + ME4000_DIO_PORT_0_REG; + info->dio_context.port_1_reg = + info->me4000_regbase + ME4000_DIO_PORT_1_REG; + info->dio_context.port_2_reg = + info->me4000_regbase + ME4000_DIO_PORT_2_REG; + info->dio_context.port_3_reg = + info->me4000_regbase + ME4000_DIO_PORT_3_REG; + + return 0; +} + +static int init_cnt_context(struct comedi_device *dev) +{ + + CALL_PDEBUG("In init_cnt_context()\n"); + + info->cnt_context.ctrl_reg = info->timer_regbase + ME4000_CNT_CTRL_REG; + info->cnt_context.counter_0_reg = + info->timer_regbase + ME4000_CNT_COUNTER_0_REG; + info->cnt_context.counter_1_reg = + info->timer_regbase + ME4000_CNT_COUNTER_1_REG; + info->cnt_context.counter_2_reg = + info->timer_regbase + ME4000_CNT_COUNTER_2_REG; + + return 0; +} + +#define FIRMWARE_NOT_AVAILABLE 1 +#if FIRMWARE_NOT_AVAILABLE +extern unsigned char *xilinx_firm; +#endif + +static int xilinx_download(struct comedi_device *dev) +{ + u32 value = 0; + wait_queue_head_t queue; + int idx = 0; + int size = 0; + + CALL_PDEBUG("In xilinx_download()\n"); + + init_waitqueue_head(&queue); + + /* + * Set PLX local interrupt 2 polarity to high. + * Interrupt is thrown by init pin of xilinx. + */ + outl(0x10, info->plx_regbase + PLX_INTCSR); + + /* Set /CS and /WRITE of the Xilinx */ + value = inl(info->plx_regbase + PLX_ICR); + value |= 0x100; + outl(value, info->plx_regbase + PLX_ICR); + + /* Init Xilinx with CS1 */ + inb(info->program_regbase + 0xC8); + + /* Wait until /INIT pin is set */ + udelay(20); + if (!(inl(info->plx_regbase + PLX_INTCSR) & 0x20)) { + printk(KERN_ERR + "comedi%d: me4000: xilinx_download(): " + "Can't init Xilinx\n", dev->minor); + return -EIO; + } + + /* Reset /CS and /WRITE of the Xilinx */ + value = inl(info->plx_regbase + PLX_ICR); + value &= ~0x100; + outl(value, info->plx_regbase + PLX_ICR); + if (FIRMWARE_NOT_AVAILABLE) { + comedi_error(dev, "xilinx firmware unavailable " + "due to licensing, aborting"); + return -EIO; + } else { + /* Download Xilinx firmware */ + size = (xilinx_firm[0] << 24) + (xilinx_firm[1] << 16) + + (xilinx_firm[2] << 8) + xilinx_firm[3]; + udelay(10); + + for (idx = 0; idx < size; idx++) { + outb(xilinx_firm[16 + idx], info->program_regbase); + udelay(10); + + /* Check if BUSY flag is low */ + if (inl(info->plx_regbase + PLX_ICR) & 0x20) { + printk(KERN_ERR + "comedi%d: me4000: xilinx_download(): " + "Xilinx is still busy (idx = %d)\n", + dev->minor, idx); + return -EIO; + } + } + } + + /* If done flag is high download was successful */ + if (inl(info->plx_regbase + PLX_ICR) & 0x4) { + } else { + printk(KERN_ERR + "comedi%d: me4000: xilinx_download(): " + "DONE flag is not set\n", dev->minor); + printk(KERN_ERR + "comedi%d: me4000: xilinx_download(): " + "Download not successful\n", dev->minor); + return -EIO; + } + + /* Set /CS and /WRITE */ + value = inl(info->plx_regbase + PLX_ICR); + value |= 0x100; + outl(value, info->plx_regbase + PLX_ICR); + + return 0; +} + +static int reset_board(struct comedi_device *dev) +{ + unsigned long icr; + + CALL_PDEBUG("In reset_board()\n"); + + /* Make a hardware reset */ + icr = me4000_inl(dev, info->plx_regbase + PLX_ICR); + icr |= 0x40000000; + me4000_outl(dev, icr, info->plx_regbase + PLX_ICR); + icr &= ~0x40000000; + me4000_outl(dev, icr, info->plx_regbase + PLX_ICR); + + /* 0x8000 to the DACs means an output voltage of 0V */ + me4000_outl(dev, 0x8000, + info->me4000_regbase + ME4000_AO_00_SINGLE_REG); + me4000_outl(dev, 0x8000, + info->me4000_regbase + ME4000_AO_01_SINGLE_REG); + me4000_outl(dev, 0x8000, + info->me4000_regbase + ME4000_AO_02_SINGLE_REG); + me4000_outl(dev, 0x8000, + info->me4000_regbase + ME4000_AO_03_SINGLE_REG); + + /* Set both stop bits in the analog input control register */ + me4000_outl(dev, + ME4000_AI_CTRL_BIT_IMMEDIATE_STOP | ME4000_AI_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AI_CTRL_REG); + + /* Set both stop bits in the analog output control register */ + me4000_outl(dev, + ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AO_00_CTRL_REG); + me4000_outl(dev, + ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AO_01_CTRL_REG); + me4000_outl(dev, + ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AO_02_CTRL_REG); + me4000_outl(dev, + ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AO_03_CTRL_REG); + + /* Enable interrupts on the PLX */ + me4000_outl(dev, 0x43, info->plx_regbase + PLX_INTCSR); + + /* Set the adustment register for AO demux */ + me4000_outl(dev, ME4000_AO_DEMUX_ADJUST_VALUE, + info->me4000_regbase + ME4000_AO_DEMUX_ADJUST_REG); + + /* + * Set digital I/O direction for port 0 + * to output on isolated versions + */ + if (!(me4000_inl(dev, info->me4000_regbase + ME4000_DIO_DIR_REG) & 0x1)) { + me4000_outl(dev, 0x1, + info->me4000_regbase + ME4000_DIO_CTRL_REG); + } + + return 0; +} + +static int me4000_detach(struct comedi_device *dev) +{ + CALL_PDEBUG("In me4000_detach()\n"); + + if (info) { + if (info->pci_dev_p) { + reset_board(dev); + if (info->plx_regbase) + comedi_pci_disable(info->pci_dev_p); + pci_dev_put(info->pci_dev_p); + } + } + + return 0; +} + +/*============================================================================= + Analog input section + ===========================================================================*/ + +static int me4000_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *subdevice, + struct comedi_insn *insn, unsigned int *data) +{ + + int chan = CR_CHAN(insn->chanspec); + int rang = CR_RANGE(insn->chanspec); + int aref = CR_AREF(insn->chanspec); + + unsigned long entry = 0; + unsigned long tmp; + long lval; + + CALL_PDEBUG("In me4000_ai_insn_read()\n"); + + if (insn->n == 0) { + return 0; + } else if (insn->n > 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): " + "Invalid instruction length %d\n", dev->minor, insn->n); + return -EINVAL; + } + + switch (rang) { + case 0: + entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5; + break; + case 1: + entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10; + break; + case 2: + entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5; + break; + case 3: + entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): " + "Invalid range specified\n", dev->minor); + return -EINVAL; + } + + switch (aref) { + case AREF_GROUND: + case AREF_COMMON: + if (chan >= thisboard->ai.count) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): " + "Analog input is not available\n", dev->minor); + return -EINVAL; + } + entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED | chan; + break; + + case AREF_DIFF: + if (rang == 0 || rang == 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): " + "Range must be bipolar when aref = diff\n", + dev->minor); + return -EINVAL; + } + + if (chan >= thisboard->ai.diff_count) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): " + "Analog input is not available\n", dev->minor); + return -EINVAL; + } + entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL | chan; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): " + "Invalid aref specified\n", dev->minor); + return -EINVAL; + } + + entry |= ME4000_AI_LIST_LAST_ENTRY; + + /* Clear channel list, data fifo and both stop bits */ + tmp = me4000_inl(dev, info->ai_context.ctrl_reg); + tmp &= ~(ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO | + ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP); + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Set the acquisition mode to single */ + tmp &= ~(ME4000_AI_CTRL_BIT_MODE_0 | ME4000_AI_CTRL_BIT_MODE_1 | + ME4000_AI_CTRL_BIT_MODE_2); + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Enable channel list and data fifo */ + tmp |= ME4000_AI_CTRL_BIT_CHANNEL_FIFO | ME4000_AI_CTRL_BIT_DATA_FIFO; + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Generate channel list entry */ + me4000_outl(dev, entry, info->ai_context.channel_list_reg); + + /* Set the timer to maximum sample rate */ + me4000_outl(dev, ME4000_AI_MIN_TICKS, info->ai_context.chan_timer_reg); + me4000_outl(dev, ME4000_AI_MIN_TICKS, + info->ai_context.chan_pre_timer_reg); + + /* Start conversion by dummy read */ + me4000_inl(dev, info->ai_context.start_reg); + + /* Wait until ready */ + udelay(10); + if (! + (me4000_inl(dev, info->ai_context.status_reg) & + ME4000_AI_STATUS_BIT_EF_DATA)) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): " + "Value not available after wait\n", dev->minor); + return -EIO; + } + + /* Read value from data fifo */ + lval = me4000_inl(dev, info->ai_context.data_reg) & 0xFFFF; + data[0] = lval ^ 0x8000; + + return 1; +} + +static int me4000_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long tmp; + + CALL_PDEBUG("In me4000_ai_cancel()\n"); + + /* Stop any running conversion */ + tmp = me4000_inl(dev, info->ai_context.ctrl_reg); + tmp &= ~(ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP); + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Clear the control register */ + me4000_outl(dev, 0x0, info->ai_context.ctrl_reg); + + return 0; +} + +static int ai_check_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int aref; + int i; + + CALL_PDEBUG("In ai_check_chanlist()\n"); + + /* Check whether a channel list is available */ + if (!cmd->chanlist_len) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): " + "No channel list available\n", dev->minor); + return -EINVAL; + } + + /* Check the channel list size */ + if (cmd->chanlist_len > ME4000_AI_CHANNEL_LIST_COUNT) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): " + "Channel list is to large\n", dev->minor); + return -EINVAL; + } + + /* Check the pointer */ + if (!cmd->chanlist) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): " + "NULL pointer to channel list\n", dev->minor); + return -EFAULT; + } + + /* Check whether aref is equal for all entries */ + aref = CR_AREF(cmd->chanlist[0]); + for (i = 0; i < cmd->chanlist_len; i++) { + if (CR_AREF(cmd->chanlist[i]) != aref) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): " + "Mode is not equal for all entries\n", + dev->minor); + return -EINVAL; + } + } + + /* Check whether channels are available for this ending */ + if (aref == SDF_DIFF) { + for (i = 0; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) >= + thisboard->ai.diff_count) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist():" + " Channel number to high\n", dev->minor); + return -EINVAL; + } + } + } else { + for (i = 0; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) >= thisboard->ai.count) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): " + "Channel number to high\n", dev->minor); + return -EINVAL; + } + } + } + + /* Check if bipolar is set for all entries when in differential mode */ + if (aref == SDF_DIFF) { + for (i = 0; i < cmd->chanlist_len; i++) { + if (CR_RANGE(cmd->chanlist[i]) != 1 && + CR_RANGE(cmd->chanlist[i]) != 2) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): " + "Bipolar is not selected in " + "differential mode\n", + dev->minor); + return -EINVAL; + } + } + } + + return 0; +} + +static int ai_round_cmd_args(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd, + unsigned int *init_ticks, + unsigned int *scan_ticks, unsigned int *chan_ticks) +{ + + int rest; + + CALL_PDEBUG("In ai_round_cmd_args()\n"); + + *init_ticks = 0; + *scan_ticks = 0; + *chan_ticks = 0; + + PDEBUG("ai_round_cmd_arg(): start_arg = %d\n", cmd->start_arg); + PDEBUG("ai_round_cmd_arg(): scan_begin_arg = %d\n", + cmd->scan_begin_arg); + PDEBUG("ai_round_cmd_arg(): convert_arg = %d\n", cmd->convert_arg); + + if (cmd->start_arg) { + *init_ticks = (cmd->start_arg * 33) / 1000; + rest = (cmd->start_arg * 33) % 1000; + + if (cmd->flags & TRIG_ROUND_NEAREST) { + if (rest > 33) + (*init_ticks)++; + } else if (cmd->flags & TRIG_ROUND_UP) { + if (rest) + (*init_ticks)++; + } + } + + if (cmd->scan_begin_arg) { + *scan_ticks = (cmd->scan_begin_arg * 33) / 1000; + rest = (cmd->scan_begin_arg * 33) % 1000; + + if (cmd->flags & TRIG_ROUND_NEAREST) { + if (rest > 33) + (*scan_ticks)++; + } else if (cmd->flags & TRIG_ROUND_UP) { + if (rest) + (*scan_ticks)++; + } + } + + if (cmd->convert_arg) { + *chan_ticks = (cmd->convert_arg * 33) / 1000; + rest = (cmd->convert_arg * 33) % 1000; + + if (cmd->flags & TRIG_ROUND_NEAREST) { + if (rest > 33) + (*chan_ticks)++; + } else if (cmd->flags & TRIG_ROUND_UP) { + if (rest) + (*chan_ticks)++; + } + } + + PDEBUG("ai_round_cmd_args(): init_ticks = %d\n", *init_ticks); + PDEBUG("ai_round_cmd_args(): scan_ticks = %d\n", *scan_ticks); + PDEBUG("ai_round_cmd_args(): chan_ticks = %d\n", *chan_ticks); + + return 0; +} + +static void ai_write_timer(struct comedi_device *dev, + unsigned int init_ticks, + unsigned int scan_ticks, unsigned int chan_ticks) +{ + + CALL_PDEBUG("In ai_write_timer()\n"); + + me4000_outl(dev, init_ticks - 1, + info->ai_context.scan_pre_timer_low_reg); + me4000_outl(dev, 0x0, info->ai_context.scan_pre_timer_high_reg); + + if (scan_ticks) { + me4000_outl(dev, scan_ticks - 1, + info->ai_context.scan_timer_low_reg); + me4000_outl(dev, 0x0, info->ai_context.scan_timer_high_reg); + } + + me4000_outl(dev, chan_ticks - 1, info->ai_context.chan_pre_timer_reg); + me4000_outl(dev, chan_ticks - 1, info->ai_context.chan_timer_reg); +} + +static int ai_prepare(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd, + unsigned int init_ticks, + unsigned int scan_ticks, unsigned int chan_ticks) +{ + + unsigned long tmp = 0; + + CALL_PDEBUG("In ai_prepare()\n"); + + /* Write timer arguments */ + ai_write_timer(dev, init_ticks, scan_ticks, chan_ticks); + + /* Reset control register */ + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Start sources */ + if ((cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) || + (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER)) { + tmp = ME4000_AI_CTRL_BIT_MODE_1 | + ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO; + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_TIMER) { + tmp = ME4000_AI_CTRL_BIT_MODE_2 | + ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO; + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_EXT) { + tmp = ME4000_AI_CTRL_BIT_MODE_0 | + ME4000_AI_CTRL_BIT_MODE_1 | + ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO; + } else { + tmp = ME4000_AI_CTRL_BIT_MODE_0 | + ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO; + } + + /* Stop triggers */ + if (cmd->stop_src == TRIG_COUNT) { + me4000_outl(dev, cmd->chanlist_len * cmd->stop_arg, + info->ai_context.sample_counter_reg); + tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ; + } else if (cmd->stop_src == TRIG_NONE && + cmd->scan_end_src == TRIG_COUNT) { + me4000_outl(dev, cmd->scan_end_arg, + info->ai_context.sample_counter_reg); + tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ; + } else { + tmp |= ME4000_AI_CTRL_BIT_HF_IRQ; + } + + /* Write the setup to the control register */ + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Write the channel list */ + ai_write_chanlist(dev, s, cmd); + + return 0; +} + +static int ai_write_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + unsigned int entry; + unsigned int chan; + unsigned int rang; + unsigned int aref; + int i; + + CALL_PDEBUG("In ai_write_chanlist()\n"); + + for (i = 0; i < cmd->chanlist_len; i++) { + chan = CR_CHAN(cmd->chanlist[i]); + rang = CR_RANGE(cmd->chanlist[i]); + aref = CR_AREF(cmd->chanlist[i]); + + entry = chan; + + if (rang == 0) + entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5; + else if (rang == 1) + entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10; + else if (rang == 2) + entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5; + else + entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10; + + if (aref == SDF_DIFF) + entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL; + else + entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED; + + me4000_outl(dev, entry, info->ai_context.channel_list_reg); + } + + return 0; +} + +static int me4000_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int err; + unsigned int init_ticks = 0; + unsigned int scan_ticks = 0; + unsigned int chan_ticks = 0; + struct comedi_cmd *cmd = &s->async->cmd; + + CALL_PDEBUG("In me4000_ai_do_cmd()\n"); + + /* Reset the analog input */ + err = me4000_ai_cancel(dev, s); + if (err) + return err; + + /* Round the timer arguments */ + err = ai_round_cmd_args(dev, + s, cmd, &init_ticks, &scan_ticks, &chan_ticks); + if (err) + return err; + + /* Prepare the AI for acquisition */ + err = ai_prepare(dev, s, cmd, init_ticks, scan_ticks, chan_ticks); + if (err) + return err; + + /* Start acquistion by dummy read */ + me4000_inl(dev, info->ai_context.start_reg); + + return 0; +} + +/* + * me4000_ai_do_cmd_test(): + * + * The demo cmd.c in ./comedilib/demo specifies 6 return values: + * - success + * - invalid source + * - source conflict + * - invalid argument + * - argument conflict + * - invalid chanlist + * So I tried to adopt this scheme. + */ +static int me4000_ai_do_cmd_test(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + + unsigned int init_ticks; + unsigned int chan_ticks; + unsigned int scan_ticks; + int err = 0; + + CALL_PDEBUG("In me4000_ai_do_cmd_test()\n"); + + PDEBUG("me4000_ai_do_cmd_test(): subdev = %d\n", cmd->subdev); + PDEBUG("me4000_ai_do_cmd_test(): flags = %08X\n", cmd->flags); + PDEBUG("me4000_ai_do_cmd_test(): start_src = %08X\n", + cmd->start_src); + PDEBUG("me4000_ai_do_cmd_test(): start_arg = %d\n", + cmd->start_arg); + PDEBUG("me4000_ai_do_cmd_test(): scan_begin_src = %08X\n", + cmd->scan_begin_src); + PDEBUG("me4000_ai_do_cmd_test(): scan_begin_arg = %d\n", + cmd->scan_begin_arg); + PDEBUG("me4000_ai_do_cmd_test(): convert_src = %08X\n", + cmd->convert_src); + PDEBUG("me4000_ai_do_cmd_test(): convert_arg = %d\n", + cmd->convert_arg); + PDEBUG("me4000_ai_do_cmd_test(): scan_end_src = %08X\n", + cmd->scan_end_src); + PDEBUG("me4000_ai_do_cmd_test(): scan_end_arg = %d\n", + cmd->scan_end_arg); + PDEBUG("me4000_ai_do_cmd_test(): stop_src = %08X\n", + cmd->stop_src); + PDEBUG("me4000_ai_do_cmd_test(): stop_arg = %d\n", cmd->stop_arg); + PDEBUG("me4000_ai_do_cmd_test(): chanlist = %d\n", + (unsigned int)cmd->chanlist); + PDEBUG("me4000_ai_do_cmd_test(): chanlist_len = %d\n", + cmd->chanlist_len); + + /* Only rounding flags are implemented */ + cmd->flags &= TRIG_ROUND_NEAREST | TRIG_ROUND_UP | TRIG_ROUND_DOWN; + + /* Round the timer arguments */ + ai_round_cmd_args(dev, s, cmd, &init_ticks, &scan_ticks, &chan_ticks); + + /* + * Stage 1. Check if the trigger sources are generally valid. + */ + switch (cmd->start_src) { + case TRIG_NOW: + case TRIG_EXT: + break; + case TRIG_ANY: + cmd->start_src &= TRIG_NOW | TRIG_EXT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid start source\n", dev->minor); + cmd->start_src = TRIG_NOW; + err++; + } + switch (cmd->scan_begin_src) { + case TRIG_FOLLOW: + case TRIG_TIMER: + case TRIG_EXT: + break; + case TRIG_ANY: + cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid scan begin source\n", dev->minor); + cmd->scan_begin_src = TRIG_FOLLOW; + err++; + } + switch (cmd->convert_src) { + case TRIG_TIMER: + case TRIG_EXT: + break; + case TRIG_ANY: + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid convert source\n", dev->minor); + cmd->convert_src = TRIG_TIMER; + err++; + } + switch (cmd->scan_end_src) { + case TRIG_NONE: + case TRIG_COUNT: + break; + case TRIG_ANY: + cmd->scan_end_src &= TRIG_NONE | TRIG_COUNT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid scan end source\n", dev->minor); + cmd->scan_end_src = TRIG_NONE; + err++; + } + switch (cmd->stop_src) { + case TRIG_NONE: + case TRIG_COUNT: + break; + case TRIG_ANY: + cmd->stop_src &= TRIG_NONE | TRIG_COUNT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid stop source\n", dev->minor); + cmd->stop_src = TRIG_NONE; + err++; + } + if (err) + return 1; + + /* + * Stage 2. Check for trigger source conflicts. + */ + if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_EXT) { + } else { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid start trigger combination\n", dev->minor); + cmd->start_src = TRIG_NOW; + cmd->scan_begin_src = TRIG_FOLLOW; + cmd->convert_src = TRIG_TIMER; + err++; + } + + if (cmd->stop_src == TRIG_NONE && cmd->scan_end_src == TRIG_NONE) { + } else if (cmd->stop_src == TRIG_COUNT && + cmd->scan_end_src == TRIG_NONE) { + } else if (cmd->stop_src == TRIG_NONE && + cmd->scan_end_src == TRIG_COUNT) { + } else if (cmd->stop_src == TRIG_COUNT && + cmd->scan_end_src == TRIG_COUNT) { + } else { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid stop trigger combination\n", dev->minor); + cmd->stop_src = TRIG_NONE; + cmd->scan_end_src = TRIG_NONE; + err++; + } + if (err) + return 2; + + /* + * Stage 3. Check if arguments are generally valid. + */ + if (cmd->chanlist_len < 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "No channel list\n", dev->minor); + cmd->chanlist_len = 1; + err++; + } + if (init_ticks < 66) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Start arg to low\n", dev->minor); + cmd->start_arg = 2000; + err++; + } + if (scan_ticks && scan_ticks < 67) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Scan begin arg to low\n", dev->minor); + cmd->scan_begin_arg = 2031; + err++; + } + if (chan_ticks < 66) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Convert arg to low\n", dev->minor); + cmd->convert_arg = 2000; + err++; + } + + if (err) + return 3; + + /* + * Stage 4. Check for argument conflicts. + */ + if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid start arg\n", dev->minor); + cmd->start_arg = 2000; /* 66 ticks at least */ + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid convert arg\n", dev->minor); + cmd->convert_arg = 2000; /* 66 ticks at least */ + err++; + } + if (scan_ticks <= cmd->chanlist_len * chan_ticks) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid scan end arg\n", dev->minor); + + /* At least one tick more */ + cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31; + err++; + } + } else if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid start arg\n", dev->minor); + cmd->start_arg = 2000; /* 66 ticks at least */ + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid convert arg\n", dev->minor); + cmd->convert_arg = 2000; /* 66 ticks at least */ + err++; + } + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid start arg\n", dev->minor); + cmd->start_arg = 2000; /* 66 ticks at least */ + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid convert arg\n", dev->minor); + cmd->convert_arg = 2000; /* 66 ticks at least */ + err++; + } + if (scan_ticks <= cmd->chanlist_len * chan_ticks) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid scan end arg\n", dev->minor); + + /* At least one tick more */ + cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31; + err++; + } + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid start arg\n", dev->minor); + cmd->start_arg = 2000; /* 66 ticks at least */ + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid convert arg\n", dev->minor); + cmd->convert_arg = 2000; /* 66 ticks at least */ + err++; + } + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid start arg\n", dev->minor); + cmd->start_arg = 2000; /* 66 ticks at least */ + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid convert arg\n", dev->minor); + cmd->convert_arg = 2000; /* 66 ticks at least */ + err++; + } + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_EXT) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid start arg\n", dev->minor); + cmd->start_arg = 2000; /* 66 ticks at least */ + err++; + } + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg == 0) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid stop arg\n", dev->minor); + cmd->stop_arg = 1; + err++; + } + } + if (cmd->scan_end_src == TRIG_COUNT) { + if (cmd->scan_end_arg == 0) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): " + "Invalid scan end arg\n", dev->minor); + cmd->scan_end_arg = 1; + err++; + } + } + + if (err) + return 4; + + /* + * Stage 5. Check the channel list. + */ + if (ai_check_chanlist(dev, s, cmd)) + return 5; + + return 0; +} + +static irqreturn_t me4000_ai_isr(int irq, void *dev_id) +{ + unsigned int tmp; + struct comedi_device *dev = dev_id; + struct comedi_subdevice *s = dev->subdevices; + struct me4000_ai_context *ai_context = &info->ai_context; + int i; + int c = 0; + long lval; + + ISR_PDEBUG("me4000_ai_isr() is executed\n"); + + if (!dev->attached) { + ISR_PDEBUG("me4000_ai_isr() premature interrupt\n"); + return IRQ_NONE; + } + + /* Reset all events */ + s->async->events = 0; + + /* Check if irq number is right */ + if (irq != ai_context->irq) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): " + "Incorrect interrupt num: %d\n", dev->minor, irq); + return IRQ_HANDLED; + } + + if (me4000_inl(dev, + ai_context->irq_status_reg) & + ME4000_IRQ_STATUS_BIT_AI_HF) { + ISR_PDEBUG + ("me4000_ai_isr(): Fifo half full interrupt occurred\n"); + + /* Read status register to find out what happened */ + tmp = me4000_inl(dev, ai_context->ctrl_reg); + + if (!(tmp & ME4000_AI_STATUS_BIT_FF_DATA) && + !(tmp & ME4000_AI_STATUS_BIT_HF_DATA) && + (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) { + ISR_PDEBUG("me4000_ai_isr(): Fifo full\n"); + c = ME4000_AI_FIFO_COUNT; + + /* + * FIFO overflow, so stop conversion + * and disable all interrupts + */ + tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; + tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | + ME4000_AI_CTRL_BIT_SC_IRQ); + me4000_outl(dev, tmp, ai_context->ctrl_reg); + + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): " + "FIFO overflow\n", dev->minor); + } else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA) + && !(tmp & ME4000_AI_STATUS_BIT_HF_DATA) + && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) { + ISR_PDEBUG("me4000_ai_isr(): Fifo half full\n"); + + s->async->events |= COMEDI_CB_BLOCK; + + c = ME4000_AI_FIFO_COUNT / 2; + } else { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): " + "Can't determine state of fifo\n", dev->minor); + c = 0; + + /* + * Undefined state, so stop conversion + * and disable all interrupts + */ + tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; + tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | + ME4000_AI_CTRL_BIT_SC_IRQ); + me4000_outl(dev, tmp, ai_context->ctrl_reg); + + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): " + "Undefined FIFO state\n", dev->minor); + } + + ISR_PDEBUG("me4000_ai_isr(): Try to read %d values\n", c); + + for (i = 0; i < c; i++) { + /* Read value from data fifo */ + lval = inl(ai_context->data_reg) & 0xFFFF; + lval ^= 0x8000; + + if (!comedi_buf_put(s->async, lval)) { + /* + * Buffer overflow, so stop conversion + * and disable all interrupts + */ + tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; + tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | + ME4000_AI_CTRL_BIT_SC_IRQ); + me4000_outl(dev, tmp, ai_context->ctrl_reg); + + s->async->events |= COMEDI_CB_OVERFLOW; + + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): " + "Buffer overflow\n", dev->minor); + + break; + } + } + + /* Work is done, so reset the interrupt */ + ISR_PDEBUG("me4000_ai_isr(): Reset fifo half full interrupt\n"); + tmp |= ME4000_AI_CTRL_BIT_HF_IRQ_RESET; + me4000_outl(dev, tmp, ai_context->ctrl_reg); + tmp &= ~ME4000_AI_CTRL_BIT_HF_IRQ_RESET; + me4000_outl(dev, tmp, ai_context->ctrl_reg); + } + + if (me4000_inl(dev, + ai_context->irq_status_reg) & ME4000_IRQ_STATUS_BIT_SC) { + ISR_PDEBUG + ("me4000_ai_isr(): Sample counter interrupt occurred\n"); + + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOA; + + /* + * Acquisition is complete, so stop + * conversion and disable all interrupts + */ + tmp = me4000_inl(dev, ai_context->ctrl_reg); + tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; + tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ); + me4000_outl(dev, tmp, ai_context->ctrl_reg); + + /* Poll data until fifo empty */ + while (inl(ai_context->ctrl_reg) & ME4000_AI_STATUS_BIT_EF_DATA) { + /* Read value from data fifo */ + lval = inl(ai_context->data_reg) & 0xFFFF; + lval ^= 0x8000; + + if (!comedi_buf_put(s->async, lval)) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): " + "Buffer overflow\n", dev->minor); + s->async->events |= COMEDI_CB_OVERFLOW; + break; + } + } + + /* Work is done, so reset the interrupt */ + ISR_PDEBUG + ("me4000_ai_isr(): Reset interrupt from sample counter\n"); + tmp |= ME4000_AI_CTRL_BIT_SC_IRQ_RESET; + me4000_outl(dev, tmp, ai_context->ctrl_reg); + tmp &= ~ME4000_AI_CTRL_BIT_SC_IRQ_RESET; + me4000_outl(dev, tmp, ai_context->ctrl_reg); + } + + ISR_PDEBUG("me4000_ai_isr(): Events = 0x%X\n", s->async->events); + + if (s->async->events) + comedi_event(dev, s); + + return IRQ_HANDLED; +} + +/*============================================================================= + Analog output section + ===========================================================================*/ + +static int me4000_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + int chan = CR_CHAN(insn->chanspec); + int rang = CR_RANGE(insn->chanspec); + int aref = CR_AREF(insn->chanspec); + unsigned long tmp; + + CALL_PDEBUG("In me4000_ao_insn_write()\n"); + + if (insn->n == 0) { + return 0; + } else if (insn->n > 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ao_insn_write(): " + "Invalid instruction length %d\n", dev->minor, insn->n); + return -EINVAL; + } + + if (chan >= thisboard->ao.count) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ao_insn_write(): " + "Invalid channel %d\n", dev->minor, insn->n); + return -EINVAL; + } + + if (rang != 0) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ao_insn_write(): " + "Invalid range %d\n", dev->minor, insn->n); + return -EINVAL; + } + + if (aref != AREF_GROUND && aref != AREF_COMMON) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ao_insn_write(): " + "Invalid aref %d\n", dev->minor, insn->n); + return -EINVAL; + } + + /* Stop any running conversion */ + tmp = me4000_inl(dev, info->ao_context[chan].ctrl_reg); + tmp |= ME4000_AO_CTRL_BIT_IMMEDIATE_STOP; + me4000_outl(dev, tmp, info->ao_context[chan].ctrl_reg); + + /* Clear control register and set to single mode */ + me4000_outl(dev, 0x0, info->ao_context[chan].ctrl_reg); + + /* Write data value */ + me4000_outl(dev, data[0], info->ao_context[chan].single_reg); + + /* Store in the mirror */ + info->ao_context[chan].mirror = data[0]; + + return 1; +} + +static int me4000_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + + if (insn->n == 0) { + return 0; + } else if (insn->n > 1) { + printk + ("comedi%d: me4000: me4000_ao_insn_read(): " + "Invalid instruction length\n", dev->minor); + return -EINVAL; + } + + data[0] = info->ao_context[chan].mirror; + + return 1; +} + +/*============================================================================= + Digital I/O section + ===========================================================================*/ + +static int me4000_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + CALL_PDEBUG("In me4000_dio_insn_bits()\n"); + + /* Length of data must be 2 (mask and new data, see below) */ + if (insn->n == 0) + return 0; + + if (insn->n != 2) { + printk + ("comedi%d: me4000: me4000_dio_insn_bits(): " + "Invalid instruction length\n", dev->minor); + return -EINVAL; + } + + /* + * The insn data consists of a mask in data[0] and the new data + * in data[1]. The mask defines which bits we are concerning about. + * The new data must be anded with the mask. + * Each channel corresponds to a bit. + */ + if (data[0]) { + /* Check if requested ports are configured for output */ + if ((s->io_bits & data[0]) != data[0]) + return -EIO; + + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + + /* Write out the new digital output lines */ + me4000_outl(dev, (s->state >> 0) & 0xFF, + info->dio_context.port_0_reg); + me4000_outl(dev, (s->state >> 8) & 0xFF, + info->dio_context.port_1_reg); + me4000_outl(dev, (s->state >> 16) & 0xFF, + info->dio_context.port_2_reg); + me4000_outl(dev, (s->state >> 24) & 0xFF, + info->dio_context.port_3_reg); + } + + /* On return, data[1] contains the value of + the digital input and output lines. */ + data[1] = + ((me4000_inl(dev, info->dio_context.port_0_reg) & 0xFF) << 0) | + ((me4000_inl(dev, info->dio_context.port_1_reg) & 0xFF) << 8) | + ((me4000_inl(dev, info->dio_context.port_2_reg) & 0xFF) << 16) | + ((me4000_inl(dev, info->dio_context.port_3_reg) & 0xFF) << 24); + + return 2; +} + +static int me4000_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned long tmp; + int chan = CR_CHAN(insn->chanspec); + + CALL_PDEBUG("In me4000_dio_insn_config()\n"); + + switch (data[0]) { + default: + return -EINVAL; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + case INSN_CONFIG_DIO_INPUT: + case INSN_CONFIG_DIO_OUTPUT: + break; + } + + /* + * The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value INSN_CONFIG_DIO_INPUT or INSN_CONFIG_DIO_OUTPUT. + * On the ME-4000 it is only possible to switch port wise (8 bit) + */ + + tmp = me4000_inl(dev, info->dio_context.ctrl_reg); + + if (data[0] == INSN_CONFIG_DIO_OUTPUT) { + if (chan < 8) { + s->io_bits |= 0xFF; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 | + ME4000_DIO_CTRL_BIT_MODE_1); + tmp |= ME4000_DIO_CTRL_BIT_MODE_0; + } else if (chan < 16) { + /* + * Chech for optoisolated ME-4000 version. + * If one the first port is a fixed output + * port and the second is a fixed input port. + */ + if (!me4000_inl(dev, info->dio_context.dir_reg)) + return -ENODEV; + + s->io_bits |= 0xFF00; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 | + ME4000_DIO_CTRL_BIT_MODE_3); + tmp |= ME4000_DIO_CTRL_BIT_MODE_2; + } else if (chan < 24) { + s->io_bits |= 0xFF0000; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_4 | + ME4000_DIO_CTRL_BIT_MODE_5); + tmp |= ME4000_DIO_CTRL_BIT_MODE_4; + } else if (chan < 32) { + s->io_bits |= 0xFF000000; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_6 | + ME4000_DIO_CTRL_BIT_MODE_7); + tmp |= ME4000_DIO_CTRL_BIT_MODE_6; + } else { + return -EINVAL; + } + } else { + if (chan < 8) { + /* + * Chech for optoisolated ME-4000 version. + * If one the first port is a fixed output + * port and the second is a fixed input port. + */ + if (!me4000_inl(dev, info->dio_context.dir_reg)) + return -ENODEV; + + s->io_bits &= ~0xFF; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 | + ME4000_DIO_CTRL_BIT_MODE_1); + } else if (chan < 16) { + s->io_bits &= ~0xFF00; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 | + ME4000_DIO_CTRL_BIT_MODE_3); + } else if (chan < 24) { + s->io_bits &= ~0xFF0000; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_4 | + ME4000_DIO_CTRL_BIT_MODE_5); + } else if (chan < 32) { + s->io_bits &= ~0xFF000000; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_6 | + ME4000_DIO_CTRL_BIT_MODE_7); + } else { + return -EINVAL; + } + } + + me4000_outl(dev, tmp, info->dio_context.ctrl_reg); + + return 1; +} + +/*============================================================================= + Counter section + ===========================================================================*/ + +static int cnt_reset(struct comedi_device *dev, unsigned int channel) +{ + + CALL_PDEBUG("In cnt_reset()\n"); + + switch (channel) { + case 0: + me4000_outb(dev, 0x30, info->cnt_context.ctrl_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_0_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_0_reg); + break; + case 1: + me4000_outb(dev, 0x70, info->cnt_context.ctrl_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_1_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_1_reg); + break; + case 2: + me4000_outb(dev, 0xB0, info->cnt_context.ctrl_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_2_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_2_reg); + break; + default: + printk(KERN_ERR + "comedi%d: me4000: cnt_reset(): Invalid channel\n", + dev->minor); + return -EINVAL; + } + + return 0; +} + +static int cnt_config(struct comedi_device *dev, unsigned int channel, + unsigned int mode) +{ + int tmp = 0; + + CALL_PDEBUG("In cnt_config()\n"); + + switch (channel) { + case 0: + tmp |= ME4000_CNT_COUNTER_0; + break; + case 1: + tmp |= ME4000_CNT_COUNTER_1; + break; + case 2: + tmp |= ME4000_CNT_COUNTER_2; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: cnt_config(): Invalid channel\n", + dev->minor); + return -EINVAL; + } + + switch (mode) { + case 0: + tmp |= ME4000_CNT_MODE_0; + break; + case 1: + tmp |= ME4000_CNT_MODE_1; + break; + case 2: + tmp |= ME4000_CNT_MODE_2; + break; + case 3: + tmp |= ME4000_CNT_MODE_3; + break; + case 4: + tmp |= ME4000_CNT_MODE_4; + break; + case 5: + tmp |= ME4000_CNT_MODE_5; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: cnt_config(): Invalid counter mode\n", + dev->minor); + return -EINVAL; + } + + /* Write the control word */ + tmp |= 0x30; + me4000_outb(dev, tmp, info->cnt_context.ctrl_reg); + + return 0; +} + +static int me4000_cnt_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + int err; + + CALL_PDEBUG("In me4000_cnt_insn_config()\n"); + + switch (data[0]) { + case GPCT_RESET: + if (insn->n != 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_config(): " + "Invalid instruction length%d\n", + dev->minor, insn->n); + return -EINVAL; + } + + err = cnt_reset(dev, insn->chanspec); + if (err) + return err; + break; + case GPCT_SET_OPERATION: + if (insn->n != 2) { + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_config(): " + "Invalid instruction length%d\n", + dev->minor, insn->n); + return -EINVAL; + } + + err = cnt_config(dev, insn->chanspec, data[1]); + if (err) + return err; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_config(): " + "Invalid instruction\n", dev->minor); + return -EINVAL; + } + + return 2; +} + +static int me4000_cnt_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + unsigned short tmp; + + CALL_PDEBUG("In me4000_cnt_insn_read()\n"); + + if (insn->n == 0) + return 0; + + if (insn->n > 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_read(): " + "Invalid instruction length %d\n", + dev->minor, insn->n); + return -EINVAL; + } + + switch (insn->chanspec) { + case 0: + tmp = me4000_inb(dev, info->cnt_context.counter_0_reg); + data[0] = tmp; + tmp = me4000_inb(dev, info->cnt_context.counter_0_reg); + data[0] |= tmp << 8; + break; + case 1: + tmp = me4000_inb(dev, info->cnt_context.counter_1_reg); + data[0] = tmp; + tmp = me4000_inb(dev, info->cnt_context.counter_1_reg); + data[0] |= tmp << 8; + break; + case 2: + tmp = me4000_inb(dev, info->cnt_context.counter_2_reg); + data[0] = tmp; + tmp = me4000_inb(dev, info->cnt_context.counter_2_reg); + data[0] |= tmp << 8; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_read(): " + "Invalid channel %d\n", + dev->minor, insn->chanspec); + return -EINVAL; + } + + return 1; +} + +static int me4000_cnt_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + unsigned short tmp; + + CALL_PDEBUG("In me4000_cnt_insn_write()\n"); + + if (insn->n == 0) { + return 0; + } else if (insn->n > 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_write(): " + "Invalid instruction length %d\n", + dev->minor, insn->n); + return -EINVAL; + } + + switch (insn->chanspec) { + case 0: + tmp = data[0] & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_0_reg); + tmp = (data[0] >> 8) & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_0_reg); + break; + case 1: + tmp = data[0] & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_1_reg); + tmp = (data[0] >> 8) & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_1_reg); + break; + case 2: + tmp = data[0] & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_2_reg); + tmp = (data[0] >> 8) & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_2_reg); + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_write(): " + "Invalid channel %d\n", + dev->minor, insn->chanspec); + return -EINVAL; + } + + return 1; +} + +static int __devinit driver_me4000_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_me4000.driver_name); +} + +static void __devexit driver_me4000_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_me4000_pci_driver = { + .id_table = me4000_pci_table, + .probe = &driver_me4000_pci_probe, + .remove = __devexit_p(&driver_me4000_pci_remove) +}; + +static int __init driver_me4000_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_me4000); + if (retval < 0) + return retval; + + driver_me4000_pci_driver.name = (char *)driver_me4000.driver_name; + return pci_register_driver(&driver_me4000_pci_driver); +} + +static void __exit driver_me4000_cleanup_module(void) +{ + pci_unregister_driver(&driver_me4000_pci_driver); + comedi_driver_unregister(&driver_me4000); +} + +module_init(driver_me4000_init_module); +module_exit(driver_me4000_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/me4000.h b/drivers/staging/comedi/drivers/me4000.h new file mode 100644 index 00000000..733b1924 --- /dev/null +++ b/drivers/staging/comedi/drivers/me4000.h @@ -0,0 +1,446 @@ +/* + me4000.h + Register descriptions and defines for the ME-4000 board family + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998-9 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _ME4000_H_ +#define _ME4000_H_ + +/*============================================================================= + Debug section + ===========================================================================*/ + +#undef ME4000_CALL_DEBUG /* Debug function entry and exit */ +#undef ME4000_PORT_DEBUG /* Debug port access */ +#undef ME4000_ISR_DEBUG /* Debug the interrupt service routine */ +#undef ME4000_DEBUG /* General purpose debug masseges */ + +#ifdef ME4000_CALL_DEBUG +#undef CALL_PDEBUG +#define CALL_PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor, ##args) +#else +# define CALL_PDEBUG(fmt, args...) /* no debugging, do nothing */ +#endif + +#ifdef ME4000_PORT_DEBUG +#undef PORT_PDEBUG +#define PORT_PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor, ##args) +#else +#define PORT_PDEBUG(fmt, args...) /* no debugging, do nothing */ +#endif + +#ifdef ME4000_ISR_DEBUG +#undef ISR_PDEBUG +#define ISR_PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor, ##args) +#else +#define ISR_PDEBUG(fmt, args...) /* no debugging, do nothing */ +#endif + +#ifdef ME4000_DEBUG +#undef PDEBUG +#define PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor, ##args) +#else +#define PDEBUG(fmt, args...) /* no debugging, do nothing */ +#endif + +/*============================================================================= + PCI vendor and device IDs + ===========================================================================*/ + +#define PCI_VENDOR_ID_MEILHAUS 0x1402 + +#define PCI_DEVICE_ID_MEILHAUS_ME4650 0x4650 /* Low Cost version */ + +#define PCI_DEVICE_ID_MEILHAUS_ME4660 0x4660 /* Standard version */ +#define PCI_DEVICE_ID_MEILHAUS_ME4660I 0x4661 /* Isolated version */ +#define PCI_DEVICE_ID_MEILHAUS_ME4660S 0x4662 /* Standard version with Sample and Hold */ +#define PCI_DEVICE_ID_MEILHAUS_ME4660IS 0x4663 /* Isolated version with Sample and Hold */ + +#define PCI_DEVICE_ID_MEILHAUS_ME4670 0x4670 /* Standard version */ +#define PCI_DEVICE_ID_MEILHAUS_ME4670I 0x4671 /* Isolated version */ +#define PCI_DEVICE_ID_MEILHAUS_ME4670S 0x4672 /* Standard version with Sample and Hold */ +#define PCI_DEVICE_ID_MEILHAUS_ME4670IS 0x4673 /* Isolated version with Sample and Hold */ + +#define PCI_DEVICE_ID_MEILHAUS_ME4680 0x4680 /* Standard version */ +#define PCI_DEVICE_ID_MEILHAUS_ME4680I 0x4681 /* Isolated version */ +#define PCI_DEVICE_ID_MEILHAUS_ME4680S 0x4682 /* Standard version with Sample and Hold */ +#define PCI_DEVICE_ID_MEILHAUS_ME4680IS 0x4683 /* Isolated version with Sample and Hold */ + +/*============================================================================= + ME-4000 base register offsets + ===========================================================================*/ + +#define ME4000_AO_00_CTRL_REG 0x00 /* R/W */ +#define ME4000_AO_00_STATUS_REG 0x04 /* R/_ */ +#define ME4000_AO_00_FIFO_REG 0x08 /* _/W */ +#define ME4000_AO_00_SINGLE_REG 0x0C /* R/W */ +#define ME4000_AO_00_TIMER_REG 0x10 /* _/W */ + +#define ME4000_AO_01_CTRL_REG 0x18 /* R/W */ +#define ME4000_AO_01_STATUS_REG 0x1C /* R/_ */ +#define ME4000_AO_01_FIFO_REG 0x20 /* _/W */ +#define ME4000_AO_01_SINGLE_REG 0x24 /* R/W */ +#define ME4000_AO_01_TIMER_REG 0x28 /* _/W */ + +#define ME4000_AO_02_CTRL_REG 0x30 /* R/W */ +#define ME4000_AO_02_STATUS_REG 0x34 /* R/_ */ +#define ME4000_AO_02_FIFO_REG 0x38 /* _/W */ +#define ME4000_AO_02_SINGLE_REG 0x3C /* R/W */ +#define ME4000_AO_02_TIMER_REG 0x40 /* _/W */ + +#define ME4000_AO_03_CTRL_REG 0x48 /* R/W */ +#define ME4000_AO_03_STATUS_REG 0x4C /* R/_ */ +#define ME4000_AO_03_FIFO_REG 0x50 /* _/W */ +#define ME4000_AO_03_SINGLE_REG 0x54 /* R/W */ +#define ME4000_AO_03_TIMER_REG 0x58 /* _/W */ + +#define ME4000_AI_CTRL_REG 0x74 /* _/W */ +#define ME4000_AI_STATUS_REG 0x74 /* R/_ */ +#define ME4000_AI_CHANNEL_LIST_REG 0x78 /* _/W */ +#define ME4000_AI_DATA_REG 0x7C /* R/_ */ +#define ME4000_AI_CHAN_TIMER_REG 0x80 /* _/W */ +#define ME4000_AI_CHAN_PRE_TIMER_REG 0x84 /* _/W */ +#define ME4000_AI_SCAN_TIMER_LOW_REG 0x88 /* _/W */ +#define ME4000_AI_SCAN_TIMER_HIGH_REG 0x8C /* _/W */ +#define ME4000_AI_SCAN_PRE_TIMER_LOW_REG 0x90 /* _/W */ +#define ME4000_AI_SCAN_PRE_TIMER_HIGH_REG 0x94 /* _/W */ +#define ME4000_AI_START_REG 0x98 /* R/_ */ + +#define ME4000_IRQ_STATUS_REG 0x9C /* R/_ */ + +#define ME4000_DIO_PORT_0_REG 0xA0 /* R/W */ +#define ME4000_DIO_PORT_1_REG 0xA4 /* R/W */ +#define ME4000_DIO_PORT_2_REG 0xA8 /* R/W */ +#define ME4000_DIO_PORT_3_REG 0xAC /* R/W */ +#define ME4000_DIO_DIR_REG 0xB0 /* R/W */ + +#define ME4000_AO_LOADSETREG_XX 0xB4 /* R/W */ + +#define ME4000_DIO_CTRL_REG 0xB8 /* R/W */ + +#define ME4000_AO_DEMUX_ADJUST_REG 0xBC /* -/W */ + +#define ME4000_AI_SAMPLE_COUNTER_REG 0xC0 /* _/W */ + +/*============================================================================= + Value to adjust Demux + ===========================================================================*/ + +#define ME4000_AO_DEMUX_ADJUST_VALUE 0x4C + +/*============================================================================= + Counter base register offsets + ===========================================================================*/ + +#define ME4000_CNT_COUNTER_0_REG 0x00 +#define ME4000_CNT_COUNTER_1_REG 0x01 +#define ME4000_CNT_COUNTER_2_REG 0x02 +#define ME4000_CNT_CTRL_REG 0x03 + +/*============================================================================= + PLX base register offsets + ===========================================================================*/ + +#define PLX_INTCSR 0x4C /* Interrupt control and status register */ +#define PLX_ICR 0x50 /* Initialization control register */ + +/*============================================================================= + Bits for the PLX_ICSR register + ===========================================================================*/ + +#define PLX_INTCSR_LOCAL_INT1_EN 0x01 /* If set, local interrupt 1 is enabled (r/w) */ +#define PLX_INTCSR_LOCAL_INT1_POL 0x02 /* If set, local interrupt 1 polarity is active high (r/w) */ +#define PLX_INTCSR_LOCAL_INT1_STATE 0x04 /* If set, local interrupt 1 is active (r/_) */ +#define PLX_INTCSR_LOCAL_INT2_EN 0x08 /* If set, local interrupt 2 is enabled (r/w) */ +#define PLX_INTCSR_LOCAL_INT2_POL 0x10 /* If set, local interrupt 2 polarity is active high (r/w) */ +#define PLX_INTCSR_LOCAL_INT2_STATE 0x20 /* If set, local interrupt 2 is active (r/_) */ +#define PLX_INTCSR_PCI_INT_EN 0x40 /* If set, PCI interrupt is enabled (r/w) */ +#define PLX_INTCSR_SOFT_INT 0x80 /* If set, a software interrupt is generated (r/w) */ + +/*============================================================================= + Bits for the PLX_ICR register + ===========================================================================*/ + +#define PLX_ICR_BIT_EEPROM_CLOCK_SET 0x01000000 +#define PLX_ICR_BIT_EEPROM_CHIP_SELECT 0x02000000 +#define PLX_ICR_BIT_EEPROM_WRITE 0x04000000 +#define PLX_ICR_BIT_EEPROM_READ 0x08000000 +#define PLX_ICR_BIT_EEPROM_VALID 0x10000000 + +#define PLX_ICR_MASK_EEPROM 0x1F000000 + +#define EEPROM_DELAY 1 + +/*============================================================================= + Bits for the ME4000_AO_CTRL_REG register + ===========================================================================*/ + +#define ME4000_AO_CTRL_BIT_MODE_0 0x001 +#define ME4000_AO_CTRL_BIT_MODE_1 0x002 +#define ME4000_AO_CTRL_MASK_MODE 0x003 +#define ME4000_AO_CTRL_BIT_STOP 0x004 +#define ME4000_AO_CTRL_BIT_ENABLE_FIFO 0x008 +#define ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG 0x010 +#define ME4000_AO_CTRL_BIT_EX_TRIG_EDGE 0x020 +#define ME4000_AO_CTRL_BIT_IMMEDIATE_STOP 0x080 +#define ME4000_AO_CTRL_BIT_ENABLE_DO 0x100 +#define ME4000_AO_CTRL_BIT_ENABLE_IRQ 0x200 +#define ME4000_AO_CTRL_BIT_RESET_IRQ 0x400 + +/*============================================================================= + Bits for the ME4000_AO_STATUS_REG register + ===========================================================================*/ + +#define ME4000_AO_STATUS_BIT_FSM 0x01 +#define ME4000_AO_STATUS_BIT_FF 0x02 +#define ME4000_AO_STATUS_BIT_HF 0x04 +#define ME4000_AO_STATUS_BIT_EF 0x08 + +/*============================================================================= + Bits for the ME4000_AI_CTRL_REG register + ===========================================================================*/ + +#define ME4000_AI_CTRL_BIT_MODE_0 0x00000001 +#define ME4000_AI_CTRL_BIT_MODE_1 0x00000002 +#define ME4000_AI_CTRL_BIT_MODE_2 0x00000004 +#define ME4000_AI_CTRL_BIT_SAMPLE_HOLD 0x00000008 +#define ME4000_AI_CTRL_BIT_IMMEDIATE_STOP 0x00000010 +#define ME4000_AI_CTRL_BIT_STOP 0x00000020 +#define ME4000_AI_CTRL_BIT_CHANNEL_FIFO 0x00000040 +#define ME4000_AI_CTRL_BIT_DATA_FIFO 0x00000080 +#define ME4000_AI_CTRL_BIT_FULLSCALE 0x00000100 +#define ME4000_AI_CTRL_BIT_OFFSET 0x00000200 +#define ME4000_AI_CTRL_BIT_EX_TRIG_ANALOG 0x00000400 +#define ME4000_AI_CTRL_BIT_EX_TRIG 0x00000800 +#define ME4000_AI_CTRL_BIT_EX_TRIG_FALLING 0x00001000 +#define ME4000_AI_CTRL_BIT_EX_IRQ 0x00002000 +#define ME4000_AI_CTRL_BIT_EX_IRQ_RESET 0x00004000 +#define ME4000_AI_CTRL_BIT_LE_IRQ 0x00008000 +#define ME4000_AI_CTRL_BIT_LE_IRQ_RESET 0x00010000 +#define ME4000_AI_CTRL_BIT_HF_IRQ 0x00020000 +#define ME4000_AI_CTRL_BIT_HF_IRQ_RESET 0x00040000 +#define ME4000_AI_CTRL_BIT_SC_IRQ 0x00080000 +#define ME4000_AI_CTRL_BIT_SC_IRQ_RESET 0x00100000 +#define ME4000_AI_CTRL_BIT_SC_RELOAD 0x00200000 +#define ME4000_AI_CTRL_BIT_EX_TRIG_BOTH 0x80000000 + +/*============================================================================= + Bits for the ME4000_AI_STATUS_REG register + ===========================================================================*/ + +#define ME4000_AI_STATUS_BIT_EF_CHANNEL 0x00400000 +#define ME4000_AI_STATUS_BIT_HF_CHANNEL 0x00800000 +#define ME4000_AI_STATUS_BIT_FF_CHANNEL 0x01000000 +#define ME4000_AI_STATUS_BIT_EF_DATA 0x02000000 +#define ME4000_AI_STATUS_BIT_HF_DATA 0x04000000 +#define ME4000_AI_STATUS_BIT_FF_DATA 0x08000000 +#define ME4000_AI_STATUS_BIT_LE 0x10000000 +#define ME4000_AI_STATUS_BIT_FSM 0x20000000 + +/*============================================================================= + Bits for the ME4000_IRQ_STATUS_REG register + ===========================================================================*/ + +#define ME4000_IRQ_STATUS_BIT_EX 0x01 +#define ME4000_IRQ_STATUS_BIT_LE 0x02 +#define ME4000_IRQ_STATUS_BIT_AI_HF 0x04 +#define ME4000_IRQ_STATUS_BIT_AO_0_HF 0x08 +#define ME4000_IRQ_STATUS_BIT_AO_1_HF 0x10 +#define ME4000_IRQ_STATUS_BIT_AO_2_HF 0x20 +#define ME4000_IRQ_STATUS_BIT_AO_3_HF 0x40 +#define ME4000_IRQ_STATUS_BIT_SC 0x80 + +/*============================================================================= + Bits for the ME4000_DIO_CTRL_REG register + ===========================================================================*/ + +#define ME4000_DIO_CTRL_BIT_MODE_0 0x0001 +#define ME4000_DIO_CTRL_BIT_MODE_1 0x0002 +#define ME4000_DIO_CTRL_BIT_MODE_2 0x0004 +#define ME4000_DIO_CTRL_BIT_MODE_3 0x0008 +#define ME4000_DIO_CTRL_BIT_MODE_4 0x0010 +#define ME4000_DIO_CTRL_BIT_MODE_5 0x0020 +#define ME4000_DIO_CTRL_BIT_MODE_6 0x0040 +#define ME4000_DIO_CTRL_BIT_MODE_7 0x0080 + +#define ME4000_DIO_CTRL_BIT_FUNCTION_0 0x0100 +#define ME4000_DIO_CTRL_BIT_FUNCTION_1 0x0200 + +#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_0 0x0400 +#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_1 0x0800 +#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_2 0x1000 +#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_3 0x2000 + +/*============================================================================= + Information about the hardware capabilities + ===========================================================================*/ + +struct me4000_ao_info { + int count; + int fifo_count; +}; + +struct me4000_ai_info { + int count; + int sh_count; + int diff_count; + int ex_trig_analog; +}; + +struct me4000_dio_info { + int count; +}; + +struct me4000_cnt_info { + int count; +}; + +struct me4000_board { + const char *name; + unsigned short device_id; + struct me4000_ao_info ao; + struct me4000_ai_info ai; + struct me4000_dio_info dio; + struct me4000_cnt_info cnt; +}; + +#define thisboard ((const struct me4000_board *)dev->board_ptr) + +/*============================================================================= + Global board and subdevice information structures + ===========================================================================*/ + +struct me4000_ao_context { + int irq; + + unsigned long mirror; /* Store the last written value */ + + unsigned long ctrl_reg; + unsigned long status_reg; + unsigned long fifo_reg; + unsigned long single_reg; + unsigned long timer_reg; + unsigned long irq_status_reg; + unsigned long preload_reg; +}; + +struct me4000_ai_context { + int irq; + + unsigned long ctrl_reg; + unsigned long status_reg; + unsigned long channel_list_reg; + unsigned long data_reg; + unsigned long chan_timer_reg; + unsigned long chan_pre_timer_reg; + unsigned long scan_timer_low_reg; + unsigned long scan_timer_high_reg; + unsigned long scan_pre_timer_low_reg; + unsigned long scan_pre_timer_high_reg; + unsigned long start_reg; + unsigned long irq_status_reg; + unsigned long sample_counter_reg; +}; + +struct me4000_dio_context { + unsigned long dir_reg; + unsigned long ctrl_reg; + unsigned long port_0_reg; + unsigned long port_1_reg; + unsigned long port_2_reg; + unsigned long port_3_reg; +}; + +struct me4000_cnt_context { + unsigned long ctrl_reg; + unsigned long counter_0_reg; + unsigned long counter_1_reg; + unsigned long counter_2_reg; +}; + +struct me4000_info { + unsigned long plx_regbase; /* PLX configuration space base address */ + unsigned long me4000_regbase; /* Base address of the ME4000 */ + unsigned long timer_regbase; /* Base address of the timer circuit */ + unsigned long program_regbase; /* Base address to set the program pin for the xilinx */ + + unsigned long plx_regbase_size; /* PLX register set space */ + unsigned long me4000_regbase_size; /* ME4000 register set space */ + unsigned long timer_regbase_size; /* Timer circuit register set space */ + unsigned long program_regbase_size; /* Size of program base address of the ME4000 */ + + unsigned int serial_no; /* Serial number of the board */ + unsigned char hw_revision; /* Hardware revision of the board */ + unsigned short vendor_id; /* Meilhaus vendor id */ + unsigned short device_id; /* Device id */ + + struct pci_dev *pci_dev_p; /* General PCI information */ + + unsigned int irq; /* IRQ assigned from the PCI BIOS */ + + struct me4000_ai_context ai_context; /* Analog input specific context */ + struct me4000_ao_context ao_context[4]; /* Vector with analog output specific context */ + struct me4000_dio_context dio_context; /* Digital I/O specific context */ + struct me4000_cnt_context cnt_context; /* Counter specific context */ +}; + +#define info ((struct me4000_info *)dev->private) + +/*----------------------------------------------------------------------------- + Defines for analog input + ----------------------------------------------------------------------------*/ + +/* General stuff */ +#define ME4000_AI_FIFO_COUNT 2048 + +#define ME4000_AI_MIN_TICKS 66 +#define ME4000_AI_MIN_SAMPLE_TIME 2000 /* Minimum sample time [ns] */ +#define ME4000_AI_BASE_FREQUENCY (unsigned int) 33E6 + +/* Channel list defines and masks */ +#define ME4000_AI_CHANNEL_LIST_COUNT 1024 + +#define ME4000_AI_LIST_INPUT_SINGLE_ENDED 0x000 +#define ME4000_AI_LIST_INPUT_DIFFERENTIAL 0x020 + +#define ME4000_AI_LIST_RANGE_BIPOLAR_10 0x000 +#define ME4000_AI_LIST_RANGE_BIPOLAR_2_5 0x040 +#define ME4000_AI_LIST_RANGE_UNIPOLAR_10 0x080 +#define ME4000_AI_LIST_RANGE_UNIPOLAR_2_5 0x0C0 + +#define ME4000_AI_LIST_LAST_ENTRY 0x100 + +/*----------------------------------------------------------------------------- + Defines for counters + ----------------------------------------------------------------------------*/ + +#define ME4000_CNT_COUNTER_0 0x00 +#define ME4000_CNT_COUNTER_1 0x40 +#define ME4000_CNT_COUNTER_2 0x80 + +#define ME4000_CNT_MODE_0 0x00 /* Change state if zero crossing */ +#define ME4000_CNT_MODE_1 0x02 /* Retriggerable One-Shot */ +#define ME4000_CNT_MODE_2 0x04 /* Asymmetrical divider */ +#define ME4000_CNT_MODE_3 0x06 /* Symmetrical divider */ +#define ME4000_CNT_MODE_4 0x08 /* Counter start by software trigger */ +#define ME4000_CNT_MODE_5 0x0A /* Counter start by hardware trigger */ + +#endif diff --git a/drivers/staging/comedi/drivers/me_daq.c b/drivers/staging/comedi/drivers/me_daq.c new file mode 100644 index 00000000..8b812e41 --- /dev/null +++ b/drivers/staging/comedi/drivers/me_daq.c @@ -0,0 +1,894 @@ +/* + + comedi/drivers/me_daq.c + + Hardware driver for Meilhaus data acquisition cards: + + ME-2000i, ME-2600i, ME-3000vm1 + + Copyright (C) 2002 Michael Hillmann + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* +Driver: me_daq +Description: Meilhaus PCI data acquisition cards +Author: Michael Hillmann +Devices: [Meilhaus] ME-2600i (me_daq), ME-2000i +Status: experimental + +Supports: + + Analog Output + +Configuration options: + + [0] - PCI bus number (optional) + [1] - PCI slot number (optional) + + If bus/slot is not specified, the first available PCI + device will be used. + +The 2600 requires a firmware upload, which can be accomplished +using the -i or --init-data option of comedi_config. +The firmware can be +found in the comedi_nonfree_firmware tarball available +from http://www.comedi.org + +*/ + +#include +#include +#include "../comedidev.h" + +#include "comedi_pci.h" + +/*#include "me2600_fw.h" */ + +#define ME_DRIVER_NAME "me_daq" + +#define PCI_VENDOR_ID_MEILHAUS 0x1402 +#define ME2000_DEVICE_ID 0x2000 +#define ME2600_DEVICE_ID 0x2600 + +#define PLX_INTCSR 0x4C /* PLX interrupt status register */ +#define XILINX_DOWNLOAD_RESET 0x42 /* Xilinx registers */ + +#define ME_CONTROL_1 0x0000 /* - | W */ +#define INTERRUPT_ENABLE (1<<15) +#define COUNTER_B_IRQ (1<<12) +#define COUNTER_A_IRQ (1<<11) +#define CHANLIST_READY_IRQ (1<<10) +#define EXT_IRQ (1<<9) +#define ADFIFO_HALFFULL_IRQ (1<<8) +#define SCAN_COUNT_ENABLE (1<<5) +#define SIMULTANEOUS_ENABLE (1<<4) +#define TRIGGER_FALLING_EDGE (1<<3) +#define CONTINUOUS_MODE (1<<2) +#define DISABLE_ADC (0<<0) +#define SOFTWARE_TRIGGERED_ADC (1<<0) +#define SCAN_TRIGGERED_ADC (2<<0) +#define EXT_TRIGGERED_ADC (3<<0) +#define ME_ADC_START 0x0000 /* R | - */ +#define ME_CONTROL_2 0x0002 /* - | W */ +#define ENABLE_ADFIFO (1<<10) +#define ENABLE_CHANLIST (1<<9) +#define ENABLE_PORT_B (1<<7) +#define ENABLE_PORT_A (1<<6) +#define ENABLE_COUNTER_B (1<<4) +#define ENABLE_COUNTER_A (1<<3) +#define ENABLE_DAC (1<<1) +#define BUFFERED_DAC (1<<0) +#define ME_DAC_UPDATE 0x0002 /* R | - */ +#define ME_STATUS 0x0004 /* R | - */ +#define COUNTER_B_IRQ_PENDING (1<<12) +#define COUNTER_A_IRQ_PENDING (1<<11) +#define CHANLIST_READY_IRQ_PENDING (1<<10) +#define EXT_IRQ_PENDING (1<<9) +#define ADFIFO_HALFFULL_IRQ_PENDING (1<<8) +#define ADFIFO_FULL (1<<4) +#define ADFIFO_HALFFULL (1<<3) +#define ADFIFO_EMPTY (1<<2) +#define CHANLIST_FULL (1<<1) +#define FST_ACTIVE (1<<0) +#define ME_RESET_INTERRUPT 0x0004 /* - | W */ +#define ME_DIO_PORT_A 0x0006 /* R | W */ +#define ME_DIO_PORT_B 0x0008 /* R | W */ +#define ME_TIMER_DATA_0 0x000A /* - | W */ +#define ME_TIMER_DATA_1 0x000C /* - | W */ +#define ME_TIMER_DATA_2 0x000E /* - | W */ +#define ME_CHANNEL_LIST 0x0010 /* - | W */ +#define ADC_UNIPOLAR (1<<6) +#define ADC_GAIN_0 (0<<4) +#define ADC_GAIN_1 (1<<4) +#define ADC_GAIN_2 (2<<4) +#define ADC_GAIN_3 (3<<4) +#define ME_READ_AD_FIFO 0x0010 /* R | - */ +#define ME_DAC_CONTROL 0x0012 /* - | W */ +#define DAC_UNIPOLAR_D (0<<4) +#define DAC_BIPOLAR_D (1<<4) +#define DAC_UNIPOLAR_C (0<<5) +#define DAC_BIPOLAR_C (1<<5) +#define DAC_UNIPOLAR_B (0<<6) +#define DAC_BIPOLAR_B (1<<6) +#define DAC_UNIPOLAR_A (0<<7) +#define DAC_BIPOLAR_A (1<<7) +#define DAC_GAIN_0_D (0<<8) +#define DAC_GAIN_1_D (1<<8) +#define DAC_GAIN_0_C (0<<9) +#define DAC_GAIN_1_C (1<<9) +#define DAC_GAIN_0_B (0<<10) +#define DAC_GAIN_1_B (1<<10) +#define DAC_GAIN_0_A (0<<11) +#define DAC_GAIN_1_A (1<<11) +#define ME_DAC_CONTROL_UPDATE 0x0012 /* R | - */ +#define ME_DAC_DATA_A 0x0014 /* - | W */ +#define ME_DAC_DATA_B 0x0016 /* - | W */ +#define ME_DAC_DATA_C 0x0018 /* - | W */ +#define ME_DAC_DATA_D 0x001A /* - | W */ +#define ME_COUNTER_ENDDATA_A 0x001C /* - | W */ +#define ME_COUNTER_ENDDATA_B 0x001E /* - | W */ +#define ME_COUNTER_STARTDATA_A 0x0020 /* - | W */ +#define ME_COUNTER_VALUE_A 0x0020 /* R | - */ +#define ME_COUNTER_STARTDATA_B 0x0022 /* - | W */ +#define ME_COUNTER_VALUE_B 0x0022 /* R | - */ + +/* Function prototypes */ +static int me_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int me_detach(struct comedi_device *dev); + +static const struct comedi_lrange me2000_ai_range = { + 8, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange me2600_ai_range = { + 8, + { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25) + } +}; + +static const struct comedi_lrange me2600_ao_range = { + 3, + { + BIP_RANGE(10), + BIP_RANGE(5), + UNI_RANGE(10) + } +}; + +static DEFINE_PCI_DEVICE_TABLE(me_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, ME2600_DEVICE_ID) }, + { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, ME2000_DEVICE_ID) }, + {0} +}; + +MODULE_DEVICE_TABLE(pci, me_pci_table); + +/* Board specification structure */ +struct me_board { + const char *name; /* driver name */ + int device_id; + int ao_channel_nbr; /* DA config */ + int ao_resolution; + int ao_resolution_mask; + const struct comedi_lrange *ao_range_list; + int ai_channel_nbr; /* AD config */ + int ai_resolution; + int ai_resolution_mask; + const struct comedi_lrange *ai_range_list; + int dio_channel_nbr; /* DIO config */ +}; + +static const struct me_board me_boards[] = { + { + /* -- ME-2600i -- */ + .name = ME_DRIVER_NAME, + .device_id = ME2600_DEVICE_ID, + /* Analog Output */ + .ao_channel_nbr = 4, + .ao_resolution = 12, + .ao_resolution_mask = 0x0fff, + .ao_range_list = &me2600_ao_range, + .ai_channel_nbr = 16, + /* Analog Input */ + .ai_resolution = 12, + .ai_resolution_mask = 0x0fff, + .ai_range_list = &me2600_ai_range, + .dio_channel_nbr = 32, + }, + { + /* -- ME-2000i -- */ + .name = ME_DRIVER_NAME, + .device_id = ME2000_DEVICE_ID, + /* Analog Output */ + .ao_channel_nbr = 0, + .ao_resolution = 0, + .ao_resolution_mask = 0, + .ao_range_list = NULL, + .ai_channel_nbr = 16, + /* Analog Input */ + .ai_resolution = 12, + .ai_resolution_mask = 0x0fff, + .ai_range_list = &me2000_ai_range, + .dio_channel_nbr = 32, + } +}; + +#define me_board_nbr (sizeof(me_boards)/sizeof(struct me_board)) + +static struct comedi_driver me_driver = { + .driver_name = ME_DRIVER_NAME, + .module = THIS_MODULE, + .attach = me_attach, + .detach = me_detach, +}; + +static int __devinit me_driver_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, me_driver.driver_name); +} + +static void __devexit me_driver_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver me_driver_pci_driver = { + .id_table = me_pci_table, + .probe = &me_driver_pci_probe, + .remove = __devexit_p(&me_driver_pci_remove) +}; + +static int __init me_driver_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&me_driver); + if (retval < 0) + return retval; + + me_driver_pci_driver.name = (char *)me_driver.driver_name; + return pci_register_driver(&me_driver_pci_driver); +} + +static void __exit me_driver_cleanup_module(void) +{ + pci_unregister_driver(&me_driver_pci_driver); + comedi_driver_unregister(&me_driver); +} + +module_init(me_driver_init_module); +module_exit(me_driver_cleanup_module); + +/* Private data structure */ +struct me_private_data { + struct pci_dev *pci_device; + void __iomem *plx_regbase; /* PLX configuration base address */ + void __iomem *me_regbase; /* Base address of the Meilhaus card */ + unsigned long plx_regbase_size; /* Size of PLX configuration space */ + unsigned long me_regbase_size; /* Size of Meilhaus space */ + + unsigned short control_1; /* Mirror of CONTROL_1 register */ + unsigned short control_2; /* Mirror of CONTROL_2 register */ + unsigned short dac_control; /* Mirror of the DAC_CONTROL register */ + int ao_readback[4]; /* Mirror of analog output data */ +}; + +#define dev_private ((struct me_private_data *)dev->private) + +/* + * ------------------------------------------------------------------ + * + * Helpful functions + * + * ------------------------------------------------------------------ + */ +static inline void sleep(unsigned sec) +{ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(sec * HZ); +} + +/* + * ------------------------------------------------------------------ + * + * DIGITAL INPUT/OUTPUT SECTION + * + * ------------------------------------------------------------------ + */ +static int me_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int bits; + int mask = 1 << CR_CHAN(insn->chanspec); + + /* calculate port */ + if (mask & 0x0000ffff) { /* Port A in use */ + bits = 0x0000ffff; + + /* Enable Port A */ + dev_private->control_2 |= ENABLE_PORT_A; + writew(dev_private->control_2, + dev_private->me_regbase + ME_CONTROL_2); + } else { /* Port B in use */ + + bits = 0xffff0000; + + /* Enable Port B */ + dev_private->control_2 |= ENABLE_PORT_B; + writew(dev_private->control_2, + dev_private->me_regbase + ME_CONTROL_2); + } + + if (data[0]) { + /* Config port as output */ + s->io_bits |= bits; + } else { + /* Config port as input */ + s->io_bits &= ~bits; + } + + return 1; +} + +/* Digital instant input/outputs */ +static int me_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int mask = data[0]; + s->state &= ~mask; + s->state |= (mask & data[1]); + + mask &= s->io_bits; + if (mask & 0x0000ffff) { /* Port A */ + writew((s->state & 0xffff), + dev_private->me_regbase + ME_DIO_PORT_A); + } else { + data[1] &= ~0x0000ffff; + data[1] |= readw(dev_private->me_regbase + ME_DIO_PORT_A); + } + + if (mask & 0xffff0000) { /* Port B */ + writew(((s->state >> 16) & 0xffff), + dev_private->me_regbase + ME_DIO_PORT_B); + } else { + data[1] &= ~0xffff0000; + data[1] |= readw(dev_private->me_regbase + ME_DIO_PORT_B) << 16; + } + + return 2; +} + +/* + * ------------------------------------------------------------------ + * + * ANALOG INPUT SECTION + * + * ------------------------------------------------------------------ + */ + +/* Analog instant input */ +static int me_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *subdevice, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned short value; + int chan = CR_CHAN((&insn->chanspec)[0]); + int rang = CR_RANGE((&insn->chanspec)[0]); + int aref = CR_AREF((&insn->chanspec)[0]); + int i; + + /* stop any running conversion */ + dev_private->control_1 &= 0xFFFC; + writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); + + /* clear chanlist and ad fifo */ + dev_private->control_2 &= ~(ENABLE_ADFIFO | ENABLE_CHANLIST); + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* reset any pending interrupt */ + writew(0x00, dev_private->me_regbase + ME_RESET_INTERRUPT); + + /* enable the chanlist and ADC fifo */ + dev_private->control_2 |= (ENABLE_ADFIFO | ENABLE_CHANLIST); + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* write to channel list fifo */ + /* b3:b0 are the channel number */ + value = chan & 0x0f; + /* b5:b4 are the channel gain */ + value |= (rang & 0x03) << 4; + /* b6 channel polarity */ + value |= (rang & 0x04) << 4; + /* b7 single or differential */ + value |= ((aref & AREF_DIFF) ? 0x80 : 0); + writew(value & 0xff, dev_private->me_regbase + ME_CHANNEL_LIST); + + /* set ADC mode to software trigger */ + dev_private->control_1 |= SOFTWARE_TRIGGERED_ADC; + writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); + + /* start conversion by reading from ADC_START */ + readw(dev_private->me_regbase + ME_ADC_START); + + /* wait for ADC fifo not empty flag */ + for (i = 100000; i > 0; i--) + if (!(readw(dev_private->me_regbase + ME_STATUS) & 0x0004)) + break; + + /* get value from ADC fifo */ + if (i) { + data[0] = + (readw(dev_private->me_regbase + + ME_READ_AD_FIFO) ^ 0x800) & 0x0FFF; + } else { + printk(KERN_ERR "comedi%d: Cannot get single value\n", + dev->minor); + return -EIO; + } + + /* stop any running conversion */ + dev_private->control_1 &= 0xFFFC; + writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); + + return 1; +} + +/* + * ------------------------------------------------------------------ + * + * HARDWARE TRIGGERED ANALOG INPUT SECTION + * + * ------------------------------------------------------------------ + */ + +/* Cancel analog input autoscan */ +static int me_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + /* disable interrupts */ + + /* stop any running conversion */ + dev_private->control_1 &= 0xFFFC; + writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); + + return 0; +} + +/* Test analog input command */ +static int me_ai_do_cmd_test(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + return 0; +} + +/* Analog input command */ +static int me_ai_do_cmd(struct comedi_device *dev, + struct comedi_subdevice *subdevice) +{ + return 0; +} + +/* + * ------------------------------------------------------------------ + * + * ANALOG OUTPUT SECTION + * + * ------------------------------------------------------------------ + */ + +/* Analog instant output */ +static int me_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan; + int rang; + int i; + + /* Enable all DAC */ + dev_private->control_2 |= ENABLE_DAC; + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* and set DAC to "buffered" mode */ + dev_private->control_2 |= BUFFERED_DAC; + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* Set dac-control register */ + for (i = 0; i < insn->n; i++) { + chan = CR_CHAN((&insn->chanspec)[i]); + rang = CR_RANGE((&insn->chanspec)[i]); + + /* clear bits for this channel */ + dev_private->dac_control &= ~(0x0880 >> chan); + if (rang == 0) + dev_private->dac_control |= + ((DAC_BIPOLAR_A | DAC_GAIN_1_A) >> chan); + else if (rang == 1) + dev_private->dac_control |= + ((DAC_BIPOLAR_A | DAC_GAIN_0_A) >> chan); + } + writew(dev_private->dac_control, + dev_private->me_regbase + ME_DAC_CONTROL); + + /* Update dac-control register */ + readw(dev_private->me_regbase + ME_DAC_CONTROL_UPDATE); + + /* Set data register */ + for (i = 0; i < insn->n; i++) { + chan = CR_CHAN((&insn->chanspec)[i]); + writew((data[0] & s->maxdata), + dev_private->me_regbase + ME_DAC_DATA_A + (chan << 1)); + dev_private->ao_readback[chan] = (data[0] & s->maxdata); + } + + /* Update dac with data registers */ + readw(dev_private->me_regbase + ME_DAC_UPDATE); + + return i; +} + +/* Analog output readback */ +static int me_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i; + + for (i = 0; i < insn->n; i++) { + data[i] = + dev_private->ao_readback[CR_CHAN((&insn->chanspec)[i])]; + } + + return 1; +} + +/* + * ------------------------------------------------------------------ + * + * INITIALISATION SECTION + * + * ------------------------------------------------------------------ + */ + +/* Xilinx firmware download for card: ME-2600i */ +static int me2600_xilinx_download(struct comedi_device *dev, + unsigned char *me2600_firmware, + unsigned int length) +{ + unsigned int value; + unsigned int file_length; + unsigned int i; + + /* disable irq's on PLX */ + writel(0x00, dev_private->plx_regbase + PLX_INTCSR); + + /* First, make a dummy read to reset xilinx */ + value = readw(dev_private->me_regbase + XILINX_DOWNLOAD_RESET); + + /* Wait until reset is over */ + sleep(1); + + /* Write a dummy value to Xilinx */ + writeb(0x00, dev_private->me_regbase + 0x0); + sleep(1); + + /* + * Format of the firmware + * Build longs from the byte-wise coded header + * Byte 1-3: length of the array + * Byte 4-7: version + * Byte 8-11: date + * Byte 12-15: reserved + */ + if (length < 16) + return -EINVAL; + file_length = (((unsigned int)me2600_firmware[0] & 0xff) << 24) + + (((unsigned int)me2600_firmware[1] & 0xff) << 16) + + (((unsigned int)me2600_firmware[2] & 0xff) << 8) + + ((unsigned int)me2600_firmware[3] & 0xff); + + /* + * Loop for writing firmware byte by byte to xilinx + * Firmware data start at offfset 16 + */ + for (i = 0; i < file_length; i++) + writeb((me2600_firmware[16 + i] & 0xff), + dev_private->me_regbase + 0x0); + + /* Write 5 dummy values to xilinx */ + for (i = 0; i < 5; i++) + writeb(0x00, dev_private->me_regbase + 0x0); + + /* Test if there was an error during download -> INTB was thrown */ + value = readl(dev_private->plx_regbase + PLX_INTCSR); + if (value & 0x20) { + /* Disable interrupt */ + writel(0x00, dev_private->plx_regbase + PLX_INTCSR); + printk(KERN_ERR "comedi%d: Xilinx download failed\n", + dev->minor); + return -EIO; + } + + /* Wait until the Xilinx is ready for real work */ + sleep(1); + + /* Enable PLX-Interrupts */ + writel(0x43, dev_private->plx_regbase + PLX_INTCSR); + + return 0; +} + +/* Reset device */ +static int me_reset(struct comedi_device *dev) +{ + /* Reset board */ + writew(0x00, dev_private->me_regbase + ME_CONTROL_1); + writew(0x00, dev_private->me_regbase + ME_CONTROL_2); + writew(0x00, dev_private->me_regbase + ME_RESET_INTERRUPT); + writew(0x00, dev_private->me_regbase + ME_DAC_CONTROL); + + /* Save values in the board context */ + dev_private->dac_control = 0; + dev_private->control_1 = 0; + dev_private->control_2 = 0; + + return 0; +} + +/* + * Attach + * + * - Register PCI device + * - Declare device driver capability + */ +static int me_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pci_dev *pci_device = NULL; + struct comedi_subdevice *subdevice; + struct me_board *board; + resource_size_t plx_regbase_tmp; + unsigned long plx_regbase_size_tmp; + resource_size_t me_regbase_tmp; + unsigned long me_regbase_size_tmp; + resource_size_t swap_regbase_tmp; + unsigned long swap_regbase_size_tmp; + resource_size_t regbase_tmp; + int result, error, i; + + /* Allocate private memory */ + if (alloc_private(dev, sizeof(struct me_private_data)) < 0) + return -ENOMEM; + + /* Probe the device to determine what device in the series it is. */ + for_each_pci_dev(pci_device) { + if (pci_device->vendor == PCI_VENDOR_ID_MEILHAUS) { + for (i = 0; i < me_board_nbr; i++) { + if (me_boards[i].device_id == + pci_device->device) { + /* + * was a particular bus/slot requested? + */ + if ((it->options[0] != 0) + || (it->options[1] != 0)) { + /* + * are we on the wrong bus/slot? + */ + if (pci_device->bus->number != + it->options[0] + || + PCI_SLOT(pci_device->devfn) + != it->options[1]) { + continue; + } + } + + dev->board_ptr = me_boards + i; + board = + (struct me_board *)dev->board_ptr; + dev_private->pci_device = pci_device; + goto found; + } + } + } + } + + printk(KERN_ERR + "comedi%d: no supported board found! (req. bus/slot : %d/%d)\n", + dev->minor, it->options[0], it->options[1]); + return -EIO; + +found: + printk(KERN_INFO "comedi%d: found %s at PCI bus %d, slot %d\n", + dev->minor, me_boards[i].name, + pci_device->bus->number, PCI_SLOT(pci_device->devfn)); + + /* Enable PCI device and request PCI regions */ + if (comedi_pci_enable(pci_device, ME_DRIVER_NAME) < 0) { + printk(KERN_ERR "comedi%d: Failed to enable PCI device and " + "request regions\n", dev->minor); + return -EIO; + } + + /* Set data in device structure */ + dev->board_name = board->name; + + /* Read PLX register base address [PCI_BASE_ADDRESS #0]. */ + plx_regbase_tmp = pci_resource_start(pci_device, 0); + plx_regbase_size_tmp = pci_resource_len(pci_device, 0); + dev_private->plx_regbase = + ioremap(plx_regbase_tmp, plx_regbase_size_tmp); + dev_private->plx_regbase_size = plx_regbase_size_tmp; + if (!dev_private->plx_regbase) { + printk("comedi%d: Failed to remap I/O memory\n", dev->minor); + return -ENOMEM; + } + + /* Read Swap base address [PCI_BASE_ADDRESS #5]. */ + + swap_regbase_tmp = pci_resource_start(pci_device, 5); + swap_regbase_size_tmp = pci_resource_len(pci_device, 5); + + if (!swap_regbase_tmp) + printk(KERN_ERR "comedi%d: Swap not present\n", dev->minor); + + /*---------------------------------------------- Workaround start ---*/ + if (plx_regbase_tmp & 0x0080) { + printk(KERN_ERR "comedi%d: PLX-Bug detected\n", dev->minor); + + if (swap_regbase_tmp) { + regbase_tmp = plx_regbase_tmp; + plx_regbase_tmp = swap_regbase_tmp; + swap_regbase_tmp = regbase_tmp; + + result = pci_write_config_dword(pci_device, + PCI_BASE_ADDRESS_0, + plx_regbase_tmp); + if (result != PCIBIOS_SUCCESSFUL) + return -EIO; + + result = pci_write_config_dword(pci_device, + PCI_BASE_ADDRESS_5, + swap_regbase_tmp); + if (result != PCIBIOS_SUCCESSFUL) + return -EIO; + } else { + plx_regbase_tmp -= 0x80; + result = pci_write_config_dword(pci_device, + PCI_BASE_ADDRESS_0, + plx_regbase_tmp); + if (result != PCIBIOS_SUCCESSFUL) + return -EIO; + } + } + /*--------------------------------------------- Workaround end -----*/ + + /* Read Meilhaus register base address [PCI_BASE_ADDRESS #2]. */ + + me_regbase_tmp = pci_resource_start(pci_device, 2); + me_regbase_size_tmp = pci_resource_len(pci_device, 2); + dev_private->me_regbase_size = me_regbase_size_tmp; + dev_private->me_regbase = ioremap(me_regbase_tmp, me_regbase_size_tmp); + if (!dev_private->me_regbase) { + printk(KERN_ERR "comedi%d: Failed to remap I/O memory\n", + dev->minor); + return -ENOMEM; + } + /* Download firmware and reset card */ + if (board->device_id == ME2600_DEVICE_ID) { + unsigned char *aux_data; + int aux_len; + + aux_data = comedi_aux_data(it->options, 0); + aux_len = it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]; + + if (!aux_data || aux_len < 1) { + comedi_error(dev, "You must provide me2600 firmware " + "using the --init-data option of " + "comedi_config"); + return -EINVAL; + } + me2600_xilinx_download(dev, aux_data, aux_len); + } + + me_reset(dev); + + /* device driver capabilities */ + error = alloc_subdevices(dev, 3); + if (error < 0) + return error; + + subdevice = dev->subdevices + 0; + subdevice->type = COMEDI_SUBD_AI; + subdevice->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_CMD_READ; + subdevice->n_chan = board->ai_channel_nbr; + subdevice->maxdata = board->ai_resolution_mask; + subdevice->len_chanlist = board->ai_channel_nbr; + subdevice->range_table = board->ai_range_list; + subdevice->cancel = me_ai_cancel; + subdevice->insn_read = me_ai_insn_read; + subdevice->do_cmdtest = me_ai_do_cmd_test; + subdevice->do_cmd = me_ai_do_cmd; + + subdevice = dev->subdevices + 1; + subdevice->type = COMEDI_SUBD_AO; + subdevice->subdev_flags = SDF_WRITEABLE | SDF_COMMON; + subdevice->n_chan = board->ao_channel_nbr; + subdevice->maxdata = board->ao_resolution_mask; + subdevice->len_chanlist = board->ao_channel_nbr; + subdevice->range_table = board->ao_range_list; + subdevice->insn_read = me_ao_insn_read; + subdevice->insn_write = me_ao_insn_write; + + subdevice = dev->subdevices + 2; + subdevice->type = COMEDI_SUBD_DIO; + subdevice->subdev_flags = SDF_READABLE | SDF_WRITEABLE; + subdevice->n_chan = board->dio_channel_nbr; + subdevice->maxdata = 1; + subdevice->len_chanlist = board->dio_channel_nbr; + subdevice->range_table = &range_digital; + subdevice->insn_bits = me_dio_insn_bits; + subdevice->insn_config = me_dio_insn_config; + subdevice->io_bits = 0; + + printk(KERN_INFO "comedi%d: " ME_DRIVER_NAME " attached.\n", + dev->minor); + return 0; +} + +/* Detach */ +static int me_detach(struct comedi_device *dev) +{ + if (dev_private) { + if (dev_private->me_regbase) { + me_reset(dev); + iounmap(dev_private->me_regbase); + } + if (dev_private->plx_regbase) + iounmap(dev_private->plx_regbase); + if (dev_private->pci_device) { + if (dev_private->plx_regbase_size) + comedi_pci_disable(dev_private->pci_device); + + pci_dev_put(dev_private->pci_device); + } + } + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/mite.c b/drivers/staging/comedi/drivers/mite.c new file mode 100644 index 00000000..13e9c807 --- /dev/null +++ b/drivers/staging/comedi/drivers/mite.c @@ -0,0 +1,830 @@ +/* + comedi/drivers/mite.c + Hardware driver for NI Mite PCI interface chip + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2002 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* + The PCI-MIO E series driver was originally written by + Tomasz Motylewski <...>, and ported to comedi by ds. + + References for specifications: + + 321747b.pdf Register Level Programmer Manual (obsolete) + 321747c.pdf Register Level Programmer Manual (new) + DAQ-STC reference manual + + Other possibly relevant info: + + 320517c.pdf User manual (obsolete) + 320517f.pdf User manual (new) + 320889a.pdf delete + 320906c.pdf maximum signal ratings + 321066a.pdf about 16x + 321791a.pdf discontinuation of at-mio-16e-10 rev. c + 321808a.pdf about at-mio-16e-10 rev P + 321837a.pdf discontinuation of at-mio-16de-10 rev d + 321838a.pdf about at-mio-16de-10 rev N + + ISSUES: + +*/ + +/* #define USE_KMALLOC */ + +#include "mite.h" + +#include "comedi_fc.h" +#include "comedi_pci.h" +#include "../comedidev.h" + + +#define PCI_MITE_SIZE 4096 +#define PCI_DAQ_SIZE 4096 +#define PCI_DAQ_SIZE_660X 8192 + +struct mite_struct *mite_devices; +EXPORT_SYMBOL(mite_devices); + +#define TOP_OF_PAGE(x) ((x)|(~(PAGE_MASK))) + +void mite_init(void) +{ + struct pci_dev *pcidev = NULL; + struct mite_struct *mite; + + for_each_pci_dev(pcidev) { + if (pcidev->vendor == PCI_VENDOR_ID_NI) { + unsigned i; + + mite = kzalloc(sizeof(*mite), GFP_KERNEL); + if (!mite) { + printk(KERN_ERR "mite: allocation failed\n"); + pci_dev_put(pcidev); + return; + } + spin_lock_init(&mite->lock); + mite->pcidev = pci_dev_get(pcidev); + for (i = 0; i < MAX_MITE_DMA_CHANNELS; ++i) { + mite->channels[i].mite = mite; + mite->channels[i].channel = i; + mite->channels[i].done = 1; + } + mite->next = mite_devices; + mite_devices = mite; + } + } +} + +static void dump_chip_signature(u32 csigr_bits) +{ + printk(KERN_INFO "mite: version = %i, type = %i, mite mode = %i," + "interface mode = %i\n", + mite_csigr_version(csigr_bits), mite_csigr_type(csigr_bits), + mite_csigr_mmode(csigr_bits), mite_csigr_imode(csigr_bits)); + printk(KERN_INFO "mite: num channels = %i, write post fifo depth = %i," + "wins = %i, iowins = %i\n", + mite_csigr_dmac(csigr_bits), mite_csigr_wpdep(csigr_bits), + mite_csigr_wins(csigr_bits), mite_csigr_iowins(csigr_bits)); +} + +unsigned mite_fifo_size(struct mite_struct *mite, unsigned channel) +{ + unsigned fcr_bits = readl(mite->mite_io_addr + MITE_FCR(channel)); + unsigned empty_count = (fcr_bits >> 16) & 0xff; + unsigned full_count = fcr_bits & 0xff; + return empty_count + full_count; +} + +int mite_setup2(struct mite_struct *mite, unsigned use_iodwbsr_1) +{ + unsigned long length; + resource_size_t addr; + int i; + u32 csigr_bits; + unsigned unknown_dma_burst_bits; + + if (comedi_pci_enable(mite->pcidev, "mite")) { + printk(KERN_ERR "error enabling mite and requesting io regions\n"); + return -EIO; + } + pci_set_master(mite->pcidev); + + addr = pci_resource_start(mite->pcidev, 0); + mite->mite_phys_addr = addr; + mite->mite_io_addr = ioremap(addr, PCI_MITE_SIZE); + if (!mite->mite_io_addr) { + printk(KERN_ERR "Failed to remap mite io memory address\n"); + return -ENOMEM; + } + printk(KERN_INFO "MITE:0x%08llx mapped to %p ", + (unsigned long long)mite->mite_phys_addr, mite->mite_io_addr); + + addr = pci_resource_start(mite->pcidev, 1); + mite->daq_phys_addr = addr; + length = pci_resource_len(mite->pcidev, 1); + /* + * In case of a 660x board, DAQ size is 8k instead of 4k + * (see as shown by lspci output) + */ + mite->daq_io_addr = ioremap(mite->daq_phys_addr, length); + if (!mite->daq_io_addr) { + printk(KERN_ERR "Failed to remap daq io memory address\n"); + return -ENOMEM; + } + printk(KERN_INFO "DAQ:0x%08llx mapped to %p\n", + (unsigned long long)mite->daq_phys_addr, mite->daq_io_addr); + + if (use_iodwbsr_1) { + writel(0, mite->mite_io_addr + MITE_IODWBSR); + printk(KERN_INFO "mite: using I/O Window Base Size register 1\n"); + writel(mite->daq_phys_addr | WENAB | + MITE_IODWBSR_1_WSIZE_bits(length), + mite->mite_io_addr + MITE_IODWBSR_1); + writel(0, mite->mite_io_addr + MITE_IODWCR_1); + } else { + writel(mite->daq_phys_addr | WENAB, + mite->mite_io_addr + MITE_IODWBSR); + } + /* + * make sure dma bursts work. I got this from running a bus analyzer + * on a pxi-6281 and a pxi-6713. 6713 powered up with register value + * of 0x61f and bursts worked. 6281 powered up with register value of + * 0x1f and bursts didn't work. The NI windows driver reads the + * register, then does a bitwise-or of 0x600 with it and writes it back. + */ + unknown_dma_burst_bits = + readl(mite->mite_io_addr + MITE_UNKNOWN_DMA_BURST_REG); + unknown_dma_burst_bits |= UNKNOWN_DMA_BURST_ENABLE_BITS; + writel(unknown_dma_burst_bits, + mite->mite_io_addr + MITE_UNKNOWN_DMA_BURST_REG); + + csigr_bits = readl(mite->mite_io_addr + MITE_CSIGR); + mite->num_channels = mite_csigr_dmac(csigr_bits); + if (mite->num_channels > MAX_MITE_DMA_CHANNELS) { + printk(KERN_WARNING "mite: bug? chip claims to have %i dma " + "channels. Setting to %i.\n", + mite->num_channels, MAX_MITE_DMA_CHANNELS); + mite->num_channels = MAX_MITE_DMA_CHANNELS; + } + dump_chip_signature(csigr_bits); + for (i = 0; i < mite->num_channels; i++) { + writel(CHOR_DMARESET, mite->mite_io_addr + MITE_CHOR(i)); + /* disable interrupts */ + writel(CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | CHCR_CLR_SAR_IE | + CHCR_CLR_DONE_IE | CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE | + CHCR_CLR_LC_IE | CHCR_CLR_CONT_RB_IE, + mite->mite_io_addr + MITE_CHCR(i)); + } + mite->fifo_size = mite_fifo_size(mite, 0); + printk(KERN_INFO "mite: fifo size is %i.\n", mite->fifo_size); + mite->used = 1; + + return 0; +} +EXPORT_SYMBOL(mite_setup2); + +int mite_setup(struct mite_struct *mite) +{ + return mite_setup2(mite, 0); +} +EXPORT_SYMBOL(mite_setup); + +void mite_cleanup(void) +{ + struct mite_struct *mite, *next; + + for (mite = mite_devices; mite; mite = next) { + pci_dev_put(mite->pcidev); + next = mite->next; + kfree(mite); + } +} + +void mite_unsetup(struct mite_struct *mite) +{ + /* unsigned long offset, start, length; */ + + if (!mite) + return; + + if (mite->mite_io_addr) { + iounmap(mite->mite_io_addr); + mite->mite_io_addr = NULL; + } + if (mite->daq_io_addr) { + iounmap(mite->daq_io_addr); + mite->daq_io_addr = NULL; + } + if (mite->mite_phys_addr) { + comedi_pci_disable(mite->pcidev); + mite->mite_phys_addr = 0; + } + + mite->used = 0; +} +EXPORT_SYMBOL(mite_unsetup); + +void mite_list_devices(void) +{ + struct mite_struct *mite, *next; + + printk(KERN_INFO "Available NI device IDs:"); + if (mite_devices) + for (mite = mite_devices; mite; mite = next) { + next = mite->next; + printk(KERN_INFO " 0x%04x", mite_device_id(mite)); + if (mite->used) + printk(KERN_INFO "(used)"); + } + printk(KERN_INFO "\n"); +} +EXPORT_SYMBOL(mite_list_devices); + +struct mite_channel *mite_request_channel_in_range(struct mite_struct *mite, + struct + mite_dma_descriptor_ring + *ring, unsigned min_channel, + unsigned max_channel) +{ + int i; + unsigned long flags; + struct mite_channel *channel = NULL; + + /* spin lock so mite_release_channel can be called safely + * from interrupts + */ + spin_lock_irqsave(&mite->lock, flags); + for (i = min_channel; i <= max_channel; ++i) { + if (mite->channel_allocated[i] == 0) { + mite->channel_allocated[i] = 1; + channel = &mite->channels[i]; + channel->ring = ring; + break; + } + } + spin_unlock_irqrestore(&mite->lock, flags); + return channel; +} +EXPORT_SYMBOL(mite_request_channel_in_range); + +void mite_release_channel(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + unsigned long flags; + + /* spin lock to prevent races with mite_request_channel */ + spin_lock_irqsave(&mite->lock, flags); + if (mite->channel_allocated[mite_chan->channel]) { + mite_dma_disarm(mite_chan); + mite_dma_reset(mite_chan); + /* + * disable all channel's interrupts (do it after disarm/reset so + * MITE_CHCR reg isn't changed while dma is still active!) + */ + writel(CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | + CHCR_CLR_SAR_IE | CHCR_CLR_DONE_IE | + CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE | + CHCR_CLR_LC_IE | CHCR_CLR_CONT_RB_IE, + mite->mite_io_addr + MITE_CHCR(mite_chan->channel)); + mite->channel_allocated[mite_chan->channel] = 0; + mite_chan->ring = NULL; + mmiowb(); + } + spin_unlock_irqrestore(&mite->lock, flags); +} +EXPORT_SYMBOL(mite_release_channel); + +void mite_dma_arm(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + int chor; + unsigned long flags; + + MDPRINTK("mite_dma_arm ch%i\n", channel); + /* + * memory barrier is intended to insure any twiddling with the buffer + * is done before writing to the mite to arm dma transfer + */ + smp_mb(); + /* arm */ + chor = CHOR_START; + spin_lock_irqsave(&mite->lock, flags); + mite_chan->done = 0; + writel(chor, mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); + mmiowb(); + spin_unlock_irqrestore(&mite->lock, flags); +/* mite_dma_tcr(mite, channel); */ +} +EXPORT_SYMBOL(mite_dma_arm); + +/**************************************/ + +int mite_buf_change(struct mite_dma_descriptor_ring *ring, + struct comedi_async *async) +{ + unsigned int n_links; + int i; + + if (ring->descriptors) { + dma_free_coherent(ring->hw_dev, + ring->n_links * + sizeof(struct mite_dma_descriptor), + ring->descriptors, + ring->descriptors_dma_addr); + } + ring->descriptors = NULL; + ring->descriptors_dma_addr = 0; + ring->n_links = 0; + + if (async->prealloc_bufsz == 0) + return 0; + + n_links = async->prealloc_bufsz >> PAGE_SHIFT; + + MDPRINTK("ring->hw_dev=%p, n_links=0x%04x\n", ring->hw_dev, n_links); + + ring->descriptors = + dma_alloc_coherent(ring->hw_dev, + n_links * sizeof(struct mite_dma_descriptor), + &ring->descriptors_dma_addr, GFP_KERNEL); + if (!ring->descriptors) { + printk(KERN_ERR "mite: ring buffer allocation failed\n"); + return -ENOMEM; + } + ring->n_links = n_links; + + for (i = 0; i < n_links; i++) { + ring->descriptors[i].count = cpu_to_le32(PAGE_SIZE); + ring->descriptors[i].addr = + cpu_to_le32(async->buf_page_list[i].dma_addr); + ring->descriptors[i].next = + cpu_to_le32(ring->descriptors_dma_addr + (i + + 1) * + sizeof(struct mite_dma_descriptor)); + } + ring->descriptors[n_links - 1].next = + cpu_to_le32(ring->descriptors_dma_addr); + /* + * barrier is meant to insure that all the writes to the dma descriptors + * have completed before the dma controller is commanded to read them + */ + smp_wmb(); + return 0; +} +EXPORT_SYMBOL(mite_buf_change); + +void mite_prep_dma(struct mite_channel *mite_chan, + unsigned int num_device_bits, unsigned int num_memory_bits) +{ + unsigned int chor, chcr, mcr, dcr, lkcr; + struct mite_struct *mite = mite_chan->mite; + + MDPRINTK("mite_prep_dma ch%i\n", mite_chan->channel); + + /* reset DMA and FIFO */ + chor = CHOR_DMARESET | CHOR_FRESET; + writel(chor, mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); + + /* short link chaining mode */ + chcr = CHCR_SET_DMA_IE | CHCR_LINKSHORT | CHCR_SET_DONE_IE | + CHCR_BURSTEN; + /* + * Link Complete Interrupt: interrupt every time a link + * in MITE_RING is completed. This can generate a lot of + * extra interrupts, but right now we update the values + * of buf_int_ptr and buf_int_count at each interrupt. A + * better method is to poll the MITE before each user + * "read()" to calculate the number of bytes available. + */ + chcr |= CHCR_SET_LC_IE; + if (num_memory_bits == 32 && num_device_bits == 16) { + /* + * Doing a combined 32 and 16 bit byteswap gets the 16 bit + * samples into the fifo in the right order. Tested doing 32 bit + * memory to 16 bit device transfers to the analog out of a + * pxi-6281, which has mite version = 1, type = 4. This also + * works for dma reads from the counters on e-series boards. + */ + chcr |= CHCR_BYTE_SWAP_DEVICE | CHCR_BYTE_SWAP_MEMORY; + } + if (mite_chan->dir == COMEDI_INPUT) + chcr |= CHCR_DEV_TO_MEM; + + writel(chcr, mite->mite_io_addr + MITE_CHCR(mite_chan->channel)); + + /* to/from memory */ + mcr = CR_RL(64) | CR_ASEQUP; + switch (num_memory_bits) { + case 8: + mcr |= CR_PSIZE8; + break; + case 16: + mcr |= CR_PSIZE16; + break; + case 32: + mcr |= CR_PSIZE32; + break; + default: + printk(KERN_WARNING "mite: bug! invalid mem bit width for dma " + "transfer\n"); + break; + } + writel(mcr, mite->mite_io_addr + MITE_MCR(mite_chan->channel)); + + /* from/to device */ + dcr = CR_RL(64) | CR_ASEQUP; + dcr |= CR_PORTIO | CR_AMDEVICE | CR_REQSDRQ(mite_chan->channel); + switch (num_device_bits) { + case 8: + dcr |= CR_PSIZE8; + break; + case 16: + dcr |= CR_PSIZE16; + break; + case 32: + dcr |= CR_PSIZE32; + break; + default: + printk(KERN_WARNING "mite: bug! invalid dev bit width for dma " + "transfer\n"); + break; + } + writel(dcr, mite->mite_io_addr + MITE_DCR(mite_chan->channel)); + + /* reset the DAR */ + writel(0, mite->mite_io_addr + MITE_DAR(mite_chan->channel)); + + /* the link is 32bits */ + lkcr = CR_RL(64) | CR_ASEQUP | CR_PSIZE32; + writel(lkcr, mite->mite_io_addr + MITE_LKCR(mite_chan->channel)); + + /* starting address for link chaining */ + writel(mite_chan->ring->descriptors_dma_addr, + mite->mite_io_addr + MITE_LKAR(mite_chan->channel)); + + MDPRINTK("exit mite_prep_dma\n"); +} +EXPORT_SYMBOL(mite_prep_dma); + +u32 mite_device_bytes_transferred(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + return readl(mite->mite_io_addr + MITE_DAR(mite_chan->channel)); +} + +u32 mite_bytes_in_transit(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + return readl(mite->mite_io_addr + + MITE_FCR(mite_chan->channel)) & 0x000000FF; +} +EXPORT_SYMBOL(mite_bytes_in_transit); + +/* returns lower bound for number of bytes transferred from device to memory */ +u32 mite_bytes_written_to_memory_lb(struct mite_channel *mite_chan) +{ + u32 device_byte_count; + + device_byte_count = mite_device_bytes_transferred(mite_chan); + return device_byte_count - mite_bytes_in_transit(mite_chan); +} +EXPORT_SYMBOL(mite_bytes_written_to_memory_lb); + +/* returns upper bound for number of bytes transferred from device to memory */ +u32 mite_bytes_written_to_memory_ub(struct mite_channel *mite_chan) +{ + u32 in_transit_count; + + in_transit_count = mite_bytes_in_transit(mite_chan); + return mite_device_bytes_transferred(mite_chan) - in_transit_count; +} +EXPORT_SYMBOL(mite_bytes_written_to_memory_ub); + +/* returns lower bound for number of bytes read from memory to device */ +u32 mite_bytes_read_from_memory_lb(struct mite_channel *mite_chan) +{ + u32 device_byte_count; + + device_byte_count = mite_device_bytes_transferred(mite_chan); + return device_byte_count + mite_bytes_in_transit(mite_chan); +} +EXPORT_SYMBOL(mite_bytes_read_from_memory_lb); + +/* returns upper bound for number of bytes read from memory to device */ +u32 mite_bytes_read_from_memory_ub(struct mite_channel *mite_chan) +{ + u32 in_transit_count; + + in_transit_count = mite_bytes_in_transit(mite_chan); + return mite_device_bytes_transferred(mite_chan) + in_transit_count; +} +EXPORT_SYMBOL(mite_bytes_read_from_memory_ub); + +unsigned mite_dma_tcr(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + int tcr; + int lkar; + + lkar = readl(mite->mite_io_addr + MITE_LKAR(mite_chan->channel)); + tcr = readl(mite->mite_io_addr + MITE_TCR(mite_chan->channel)); + MDPRINTK("mite_dma_tcr ch%i, lkar=0x%08x tcr=%d\n", mite_chan->channel, + lkar, tcr); + + return tcr; +} +EXPORT_SYMBOL(mite_dma_tcr); + +void mite_dma_disarm(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + unsigned chor; + + /* disarm */ + chor = CHOR_ABORT; + writel(chor, mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); +} +EXPORT_SYMBOL(mite_dma_disarm); + +int mite_sync_input_dma(struct mite_channel *mite_chan, + struct comedi_async *async) +{ + int count; + unsigned int nbytes, old_alloc_count; + const unsigned bytes_per_scan = cfc_bytes_per_scan(async->subdevice); + + old_alloc_count = async->buf_write_alloc_count; + /* write alloc as much as we can */ + comedi_buf_write_alloc(async, async->prealloc_bufsz); + + nbytes = mite_bytes_written_to_memory_lb(mite_chan); + if ((int)(mite_bytes_written_to_memory_ub(mite_chan) - + old_alloc_count) > 0) { + printk("mite: DMA overwrite of free area\n"); + async->events |= COMEDI_CB_OVERFLOW; + return -1; + } + + count = nbytes - async->buf_write_count; + /* it's possible count will be negative due to + * conservative value returned by mite_bytes_written_to_memory_lb */ + if (count <= 0) + return 0; + + comedi_buf_write_free(async, count); + + async->scan_progress += count; + if (async->scan_progress >= bytes_per_scan) { + async->scan_progress %= bytes_per_scan; + async->events |= COMEDI_CB_EOS; + } + async->events |= COMEDI_CB_BLOCK; + return 0; +} +EXPORT_SYMBOL(mite_sync_input_dma); + +int mite_sync_output_dma(struct mite_channel *mite_chan, + struct comedi_async *async) +{ + int count; + u32 nbytes_ub, nbytes_lb; + unsigned int old_alloc_count; + u32 stop_count = + async->cmd.stop_arg * cfc_bytes_per_scan(async->subdevice); + + old_alloc_count = async->buf_read_alloc_count; + /* read alloc as much as we can */ + comedi_buf_read_alloc(async, async->prealloc_bufsz); + nbytes_lb = mite_bytes_read_from_memory_lb(mite_chan); + if (async->cmd.stop_src == TRIG_COUNT && + (int)(nbytes_lb - stop_count) > 0) + nbytes_lb = stop_count; + nbytes_ub = mite_bytes_read_from_memory_ub(mite_chan); + if (async->cmd.stop_src == TRIG_COUNT && + (int)(nbytes_ub - stop_count) > 0) + nbytes_ub = stop_count; + if ((int)(nbytes_ub - old_alloc_count) > 0) { + printk(KERN_ERR "mite: DMA underrun\n"); + async->events |= COMEDI_CB_OVERFLOW; + return -1; + } + count = nbytes_lb - async->buf_read_count; + if (count <= 0) + return 0; + + if (count) { + comedi_buf_read_free(async, count); + async->events |= COMEDI_CB_BLOCK; + } + return 0; +} +EXPORT_SYMBOL(mite_sync_output_dma); + +unsigned mite_get_status(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + unsigned status; + unsigned long flags; + + spin_lock_irqsave(&mite->lock, flags); + status = readl(mite->mite_io_addr + MITE_CHSR(mite_chan->channel)); + if (status & CHSR_DONE) { + mite_chan->done = 1; + writel(CHOR_CLRDONE, + mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); + } + mmiowb(); + spin_unlock_irqrestore(&mite->lock, flags); + return status; +} +EXPORT_SYMBOL(mite_get_status); + +int mite_done(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + unsigned long flags; + int done; + + mite_get_status(mite_chan); + spin_lock_irqsave(&mite->lock, flags); + done = mite_chan->done; + spin_unlock_irqrestore(&mite->lock, flags); + return done; +} +EXPORT_SYMBOL(mite_done); + +#ifdef DEBUG_MITE + +static void mite_decode(char **bit_str, unsigned int bits); + +/* names of bits in mite registers */ + +static const char *const mite_CHOR_strings[] = { + "start", "cont", "stop", "abort", + "freset", "clrlc", "clrrb", "clrdone", + "clr_lpause", "set_lpause", "clr_send_tc", + "set_send_tc", "12", "13", "14", + "15", "16", "17", "18", + "19", "20", "21", "22", + "23", "24", "25", "26", + "27", "28", "29", "30", + "dmareset", +}; + +static const char *const mite_CHCR_strings[] = { + "continue", "ringbuff", "2", "3", + "4", "5", "6", "7", + "8", "9", "10", "11", + "12", "13", "bursten", "fifodis", + "clr_cont_rb_ie", "set_cont_rb_ie", "clr_lc_ie", "set_lc_ie", + "clr_drdy_ie", "set_drdy_ie", "clr_mrdy_ie", "set_mrdy_ie", + "clr_done_ie", "set_done_ie", "clr_sar_ie", "set_sar_ie", + "clr_linkp_ie", "set_linkp_ie", "clr_dma_ie", "set_dma_ie", +}; + +static const char *const mite_MCR_strings[] = { + "amdevice", "1", "2", "3", + "4", "5", "portio", "portvxi", + "psizebyte", "psizehalf (byte & half = word)", "aseqxp1", "11", + "12", "13", "blocken", "berhand", + "reqsintlim/reqs0", "reqs1", "reqs2", "rd32", + "rd512", "rl1", "rl2", "rl8", + "24", "25", "26", "27", + "28", "29", "30", "stopen", +}; + +static const char *const mite_DCR_strings[] = { + "amdevice", "1", "2", "3", + "4", "5", "portio", "portvxi", + "psizebyte", "psizehalf (byte & half = word)", "aseqxp1", "aseqxp2", + "aseqxp8", "13", "blocken", "berhand", + "reqsintlim", "reqs1", "reqs2", "rd32", + "rd512", "rl1", "rl2", "rl8", + "23", "24", "25", "27", + "28", "wsdevc", "wsdevs", "rwdevpack", +}; + +static const char *const mite_LKCR_strings[] = { + "amdevice", "1", "2", "3", + "4", "5", "portio", "portvxi", + "psizebyte", "psizehalf (byte & half = word)", "asequp", "aseqdown", + "12", "13", "14", "berhand", + "16", "17", "18", "rd32", + "rd512", "rl1", "rl2", "rl8", + "24", "25", "26", "27", + "28", "29", "30", "chngend", +}; + +static const char *const mite_CHSR_strings[] = { + "d.err0", "d.err1", "m.err0", "m.err1", + "l.err0", "l.err1", "drq0", "drq1", + "end", "xferr", "operr0", "operr1", + "stops", "habort", "sabort", "error", + "16", "conts_rb", "18", "linkc", + "20", "drdy", "22", "mrdy", + "24", "done", "26", "sars", + "28", "lpauses", "30", "int", +}; + +void mite_dump_regs(struct mite_channel *mite_chan) +{ + unsigned long mite_io_addr = + (unsigned long)mite_chan->mite->mite_io_addr; + unsigned long addr = 0; + unsigned long temp = 0; + + printk(KERN_DEBUG "mite_dump_regs ch%i\n", mite_chan->channel); + printk(KERN_DEBUG "mite address is =0x%08lx\n", mite_io_addr); + + addr = mite_io_addr + MITE_CHOR(channel); + printk(KERN_DEBUG "mite status[CHOR]at 0x%08lx =0x%08lx\n", addr, + temp = readl(addr)); + mite_decode(mite_CHOR_strings, temp); + addr = mite_io_addr + MITE_CHCR(channel); + printk(KERN_DEBUG "mite status[CHCR]at 0x%08lx =0x%08lx\n", addr, + temp = readl(addr)); + mite_decode(mite_CHCR_strings, temp); + addr = mite_io_addr + MITE_TCR(channel); + printk(KERN_DEBUG "mite status[TCR] at 0x%08lx =0x%08x\n", addr, + readl(addr)); + addr = mite_io_addr + MITE_MCR(channel); + printk(KERN_DEBUG "mite status[MCR] at 0x%08lx =0x%08lx\n", addr, + temp = readl(addr)); + mite_decode(mite_MCR_strings, temp); + + addr = mite_io_addr + MITE_MAR(channel); + printk(KERN_DEBUG "mite status[MAR] at 0x%08lx =0x%08x\n", addr, + readl(addr)); + addr = mite_io_addr + MITE_DCR(channel); + printk(KERN_DEBUG "mite status[DCR] at 0x%08lx =0x%08lx\n", addr, + temp = readl(addr)); + mite_decode(mite_DCR_strings, temp); + addr = mite_io_addr + MITE_DAR(channel); + printk(KERN_DEBUG "mite status[DAR] at 0x%08lx =0x%08x\n", addr, + readl(addr)); + addr = mite_io_addr + MITE_LKCR(channel); + printk(KERN_DEBUG "mite status[LKCR]at 0x%08lx =0x%08lx\n", addr, + temp = readl(addr)); + mite_decode(mite_LKCR_strings, temp); + addr = mite_io_addr + MITE_LKAR(channel); + printk(KERN_DEBUG "mite status[LKAR]at 0x%08lx =0x%08x\n", addr, + readl(addr)); + addr = mite_io_addr + MITE_CHSR(channel); + printk(KERN_DEBUG "mite status[CHSR]at 0x%08lx =0x%08lx\n", addr, + temp = readl(addr)); + mite_decode(mite_CHSR_strings, temp); + addr = mite_io_addr + MITE_FCR(channel); + printk(KERN_DEBUG "mite status[FCR] at 0x%08lx =0x%08x\n\n", addr, + readl(addr)); +} +EXPORT_SYMBOL(mite_dump_regs); + +static void mite_decode(char **bit_str, unsigned int bits) +{ + int i; + + for (i = 31; i >= 0; i--) { + if (bits & (1 << i)) + printk(KERN_DEBUG " %s", bit_str[i]); + } + printk(KERN_DEBUG "\n"); +} +EXPORT_SYMBOL(mite_decode); +#endif + +#ifdef MODULE +int __init init_module(void) +{ + mite_init(); + mite_list_devices(); + + return 0; +} + +void __exit cleanup_module(void) +{ + mite_cleanup(); +} +#endif + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/mite.h b/drivers/staging/comedi/drivers/mite.h new file mode 100644 index 00000000..999551f5 --- /dev/null +++ b/drivers/staging/comedi/drivers/mite.h @@ -0,0 +1,480 @@ +/* + module/mite.h + Hardware driver for NI Mite PCI interface chip + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _MITE_H_ +#define _MITE_H_ + +#include +#include "../comedidev.h" + +/* #define DEBUG_MITE */ +#define PCIMIO_COMPAT + +#ifdef DEBUG_MITE +#define MDPRINTK(format, args...) printk(format , ## args) +#else +#define MDPRINTK(format, args...) +#endif + +#define MAX_MITE_DMA_CHANNELS 8 + +struct mite_dma_descriptor { + u32 count; + u32 addr; + u32 next; + u32 dar; +}; + +struct mite_dma_descriptor_ring { + struct device *hw_dev; + unsigned int n_links; + struct mite_dma_descriptor *descriptors; + dma_addr_t descriptors_dma_addr; +}; + +struct mite_channel { + struct mite_struct *mite; + unsigned channel; + int dir; + int done; + struct mite_dma_descriptor_ring *ring; +}; + +struct mite_struct { + struct mite_struct *next; + int used; + + struct pci_dev *pcidev; + resource_size_t mite_phys_addr; + void *mite_io_addr; + resource_size_t daq_phys_addr; + void *daq_io_addr; + + struct mite_channel channels[MAX_MITE_DMA_CHANNELS]; + short channel_allocated[MAX_MITE_DMA_CHANNELS]; + int num_channels; + unsigned fifo_size; + spinlock_t lock; +}; + +static inline struct mite_dma_descriptor_ring *mite_alloc_ring(struct + mite_struct + *mite) +{ + struct mite_dma_descriptor_ring *ring = + kmalloc(sizeof(struct mite_dma_descriptor_ring), GFP_KERNEL); + if (ring == NULL) + return ring; + ring->hw_dev = get_device(&mite->pcidev->dev); + if (ring->hw_dev == NULL) { + kfree(ring); + return NULL; + } + ring->n_links = 0; + ring->descriptors = NULL; + ring->descriptors_dma_addr = 0; + return ring; +}; + +static inline void mite_free_ring(struct mite_dma_descriptor_ring *ring) +{ + if (ring) { + if (ring->descriptors) { + dma_free_coherent(ring->hw_dev, + ring->n_links * + sizeof(struct mite_dma_descriptor), + ring->descriptors, + ring->descriptors_dma_addr); + } + put_device(ring->hw_dev); + kfree(ring); + } +}; + +extern struct mite_struct *mite_devices; + +static inline unsigned int mite_irq(struct mite_struct *mite) +{ + return mite->pcidev->irq; +}; + +static inline unsigned int mite_device_id(struct mite_struct *mite) +{ + return mite->pcidev->device; +}; + +void mite_init(void); +void mite_cleanup(void); +int mite_setup(struct mite_struct *mite); +int mite_setup2(struct mite_struct *mite, unsigned use_iodwbsr_1); +void mite_unsetup(struct mite_struct *mite); +void mite_list_devices(void); +struct mite_channel *mite_request_channel_in_range(struct mite_struct *mite, + struct + mite_dma_descriptor_ring + *ring, unsigned min_channel, + unsigned max_channel); +static inline struct mite_channel *mite_request_channel(struct mite_struct + *mite, + struct + mite_dma_descriptor_ring + *ring) +{ + return mite_request_channel_in_range(mite, ring, 0, + mite->num_channels - 1); +} + +void mite_release_channel(struct mite_channel *mite_chan); + +unsigned mite_dma_tcr(struct mite_channel *mite_chan); +void mite_dma_arm(struct mite_channel *mite_chan); +void mite_dma_disarm(struct mite_channel *mite_chan); +int mite_sync_input_dma(struct mite_channel *mite_chan, + struct comedi_async *async); +int mite_sync_output_dma(struct mite_channel *mite_chan, + struct comedi_async *async); +u32 mite_bytes_written_to_memory_lb(struct mite_channel *mite_chan); +u32 mite_bytes_written_to_memory_ub(struct mite_channel *mite_chan); +u32 mite_bytes_read_from_memory_lb(struct mite_channel *mite_chan); +u32 mite_bytes_read_from_memory_ub(struct mite_channel *mite_chan); +u32 mite_bytes_in_transit(struct mite_channel *mite_chan); +unsigned mite_get_status(struct mite_channel *mite_chan); +int mite_done(struct mite_channel *mite_chan); + +void mite_prep_dma(struct mite_channel *mite_chan, + unsigned int num_device_bits, unsigned int num_memory_bits); +int mite_buf_change(struct mite_dma_descriptor_ring *ring, + struct comedi_async *async); + +#ifdef DEBUG_MITE +void mite_print_chsr(unsigned int chsr); +void mite_dump_regs(struct mite_channel *mite_chan); +#endif + +static inline int CHAN_OFFSET(int channel) +{ + return 0x500 + 0x100 * channel; +}; + +enum mite_registers { + /* The bits 0x90180700 in MITE_UNKNOWN_DMA_BURST_REG can be + written and read back. The bits 0x1f always read as 1. + The rest always read as zero. */ + MITE_UNKNOWN_DMA_BURST_REG = 0x28, + MITE_IODWBSR = 0xc0, /* IO Device Window Base Size Register */ + MITE_IODWBSR_1 = 0xc4, /* IO Device Window Base Size Register 1 */ + MITE_IODWCR_1 = 0xf4, + MITE_PCI_CONFIG_OFFSET = 0x300, + MITE_CSIGR = 0x460 /* chip signature */ +}; +static inline int MITE_CHOR(int channel) +{ /* channel operation */ + return CHAN_OFFSET(channel) + 0x0; +}; + +static inline int MITE_CHCR(int channel) +{ /* channel control */ + return CHAN_OFFSET(channel) + 0x4; +}; + +static inline int MITE_TCR(int channel) +{ /* transfer count */ + return CHAN_OFFSET(channel) + 0x8; +}; + +static inline int MITE_MCR(int channel) +{ /* memory configuration */ + return CHAN_OFFSET(channel) + 0xc; +}; + +static inline int MITE_MAR(int channel) +{ /* memory address */ + return CHAN_OFFSET(channel) + 0x10; +}; + +static inline int MITE_DCR(int channel) +{ /* device configuration */ + return CHAN_OFFSET(channel) + 0x14; +}; + +static inline int MITE_DAR(int channel) +{ /* device address */ + return CHAN_OFFSET(channel) + 0x18; +}; + +static inline int MITE_LKCR(int channel) +{ /* link configuration */ + return CHAN_OFFSET(channel) + 0x1c; +}; + +static inline int MITE_LKAR(int channel) +{ /* link address */ + return CHAN_OFFSET(channel) + 0x20; +}; + +static inline int MITE_LLKAR(int channel) +{ /* see mite section of tnt5002 manual */ + return CHAN_OFFSET(channel) + 0x24; +}; + +static inline int MITE_BAR(int channel) +{ /* base address */ + return CHAN_OFFSET(channel) + 0x28; +}; + +static inline int MITE_BCR(int channel) +{ /* base count */ + return CHAN_OFFSET(channel) + 0x2c; +}; + +static inline int MITE_SAR(int channel) +{ /* ? address */ + return CHAN_OFFSET(channel) + 0x30; +}; + +static inline int MITE_WSCR(int channel) +{ /* ? */ + return CHAN_OFFSET(channel) + 0x34; +}; + +static inline int MITE_WSER(int channel) +{ /* ? */ + return CHAN_OFFSET(channel) + 0x38; +}; + +static inline int MITE_CHSR(int channel) +{ /* channel status */ + return CHAN_OFFSET(channel) + 0x3c; +}; + +static inline int MITE_FCR(int channel) +{ /* fifo count */ + return CHAN_OFFSET(channel) + 0x40; +}; + +enum MITE_IODWBSR_bits { + WENAB = 0x80, /* window enable */ +}; + +static inline unsigned MITE_IODWBSR_1_WSIZE_bits(unsigned size) +{ + unsigned order = 0; + while (size >>= 1) + ++order; + BUG_ON(order < 1); + return (order - 1) & 0x1f; +} + +enum MITE_UNKNOWN_DMA_BURST_bits { + UNKNOWN_DMA_BURST_ENABLE_BITS = 0x600 +}; + +static inline int mite_csigr_version(u32 csigr_bits) +{ + return csigr_bits & 0xf; +}; + +static inline int mite_csigr_type(u32 csigr_bits) +{ /* original mite = 0, minimite = 1 */ + return (csigr_bits >> 4) & 0xf; +}; + +static inline int mite_csigr_mmode(u32 csigr_bits) +{ /* mite mode, minimite = 1 */ + return (csigr_bits >> 8) & 0x3; +}; + +static inline int mite_csigr_imode(u32 csigr_bits) +{ /* cpu port interface mode, pci = 0x3 */ + return (csigr_bits >> 12) & 0x3; +}; + +static inline int mite_csigr_dmac(u32 csigr_bits) +{ /* number of dma channels */ + return (csigr_bits >> 16) & 0xf; +}; + +static inline int mite_csigr_wpdep(u32 csigr_bits) +{ /* write post fifo depth */ + unsigned int wpdep_bits = (csigr_bits >> 20) & 0x7; + if (wpdep_bits == 0) + return 0; + else + return 1 << (wpdep_bits - 1); +}; + +static inline int mite_csigr_wins(u32 csigr_bits) +{ + return (csigr_bits >> 24) & 0x1f; +}; + +static inline int mite_csigr_iowins(u32 csigr_bits) +{ /* number of io windows */ + return (csigr_bits >> 29) & 0x7; +}; + +enum MITE_MCR_bits { + MCRPON = 0, +}; + +enum MITE_DCR_bits { + DCR_NORMAL = (1 << 29), + DCRPON = 0, +}; + +enum MITE_CHOR_bits { + CHOR_DMARESET = (1 << 31), + CHOR_SET_SEND_TC = (1 << 11), + CHOR_CLR_SEND_TC = (1 << 10), + CHOR_SET_LPAUSE = (1 << 9), + CHOR_CLR_LPAUSE = (1 << 8), + CHOR_CLRDONE = (1 << 7), + CHOR_CLRRB = (1 << 6), + CHOR_CLRLC = (1 << 5), + CHOR_FRESET = (1 << 4), + CHOR_ABORT = (1 << 3), /* stop without emptying fifo */ + CHOR_STOP = (1 << 2), /* stop after emptying fifo */ + CHOR_CONT = (1 << 1), + CHOR_START = (1 << 0), + CHOR_PON = (CHOR_CLR_SEND_TC | CHOR_CLR_LPAUSE), +}; + +enum MITE_CHCR_bits { + CHCR_SET_DMA_IE = (1 << 31), + CHCR_CLR_DMA_IE = (1 << 30), + CHCR_SET_LINKP_IE = (1 << 29), + CHCR_CLR_LINKP_IE = (1 << 28), + CHCR_SET_SAR_IE = (1 << 27), + CHCR_CLR_SAR_IE = (1 << 26), + CHCR_SET_DONE_IE = (1 << 25), + CHCR_CLR_DONE_IE = (1 << 24), + CHCR_SET_MRDY_IE = (1 << 23), + CHCR_CLR_MRDY_IE = (1 << 22), + CHCR_SET_DRDY_IE = (1 << 21), + CHCR_CLR_DRDY_IE = (1 << 20), + CHCR_SET_LC_IE = (1 << 19), + CHCR_CLR_LC_IE = (1 << 18), + CHCR_SET_CONT_RB_IE = (1 << 17), + CHCR_CLR_CONT_RB_IE = (1 << 16), + CHCR_FIFODIS = (1 << 15), + CHCR_FIFO_ON = 0, + CHCR_BURSTEN = (1 << 14), + CHCR_NO_BURSTEN = 0, + CHCR_BYTE_SWAP_DEVICE = (1 << 6), + CHCR_BYTE_SWAP_MEMORY = (1 << 4), + CHCR_DIR = (1 << 3), + CHCR_DEV_TO_MEM = CHCR_DIR, + CHCR_MEM_TO_DEV = 0, + CHCR_NORMAL = (0 << 0), + CHCR_CONTINUE = (1 << 0), + CHCR_RINGBUFF = (2 << 0), + CHCR_LINKSHORT = (4 << 0), + CHCR_LINKLONG = (5 << 0), + CHCRPON = + (CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | CHCR_CLR_SAR_IE | + CHCR_CLR_DONE_IE | CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE | + CHCR_CLR_LC_IE | CHCR_CLR_CONT_RB_IE), +}; + +enum ConfigRegister_bits { + CR_REQS_MASK = 0x7 << 16, + CR_ASEQDONT = 0x0 << 10, + CR_ASEQUP = 0x1 << 10, + CR_ASEQDOWN = 0x2 << 10, + CR_ASEQ_MASK = 0x3 << 10, + CR_PSIZE8 = (1 << 8), + CR_PSIZE16 = (2 << 8), + CR_PSIZE32 = (3 << 8), + CR_PORTCPU = (0 << 6), + CR_PORTIO = (1 << 6), + CR_PORTVXI = (2 << 6), + CR_PORTMXI = (3 << 6), + CR_AMDEVICE = (1 << 0), +}; +static inline int CR_REQS(int source) +{ + return (source & 0x7) << 16; +}; + +static inline int CR_REQSDRQ(unsigned drq_line) +{ + /* This also works on m-series when + using channels (drq_line) 4 or 5. */ + return CR_REQS((drq_line & 0x3) | 0x4); +} + +static inline int CR_RL(unsigned int retry_limit) +{ + int value = 0; + + while (retry_limit) { + retry_limit >>= 1; + value++; + } + if (value > 0x7) + printk("comedi: bug! retry_limit too large\n"); + return (value & 0x7) << 21; +} + +enum CHSR_bits { + CHSR_INT = (1 << 31), + CHSR_LPAUSES = (1 << 29), + CHSR_SARS = (1 << 27), + CHSR_DONE = (1 << 25), + CHSR_MRDY = (1 << 23), + CHSR_DRDY = (1 << 21), + CHSR_LINKC = (1 << 19), + CHSR_CONTS_RB = (1 << 17), + CHSR_ERROR = (1 << 15), + CHSR_SABORT = (1 << 14), + CHSR_HABORT = (1 << 13), + CHSR_STOPS = (1 << 12), + CHSR_OPERR_mask = (3 << 10), + CHSR_OPERR_NOERROR = (0 << 10), + CHSR_OPERR_FIFOERROR = (1 << 10), + CHSR_OPERR_LINKERROR = (1 << 10), /* ??? */ + CHSR_XFERR = (1 << 9), + CHSR_END = (1 << 8), + CHSR_DRQ1 = (1 << 7), + CHSR_DRQ0 = (1 << 6), + CHSR_LxERR_mask = (3 << 4), + CHSR_LBERR = (1 << 4), + CHSR_LRERR = (2 << 4), + CHSR_LOERR = (3 << 4), + CHSR_MxERR_mask = (3 << 2), + CHSR_MBERR = (1 << 2), + CHSR_MRERR = (2 << 2), + CHSR_MOERR = (3 << 2), + CHSR_DxERR_mask = (3 << 0), + CHSR_DBERR = (1 << 0), + CHSR_DRERR = (2 << 0), + CHSR_DOERR = (3 << 0), +}; + +static inline void mite_dma_reset(struct mite_channel *mite_chan) +{ + writel(CHOR_DMARESET | CHOR_FRESET, + mite_chan->mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); +}; + +#endif diff --git a/drivers/staging/comedi/drivers/mpc624.c b/drivers/staging/comedi/drivers/mpc624.c new file mode 100644 index 00000000..dd09a6d4 --- /dev/null +++ b/drivers/staging/comedi/drivers/mpc624.c @@ -0,0 +1,424 @@ +/* + comedi/drivers/mpc624.c + Hardware driver for a Micro/sys inc. MPC-624 PC/104 board + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: mpc624 +Description: Micro/sys MPC-624 PC/104 board +Devices: [Micro/sys] MPC-624 (mpc624) +Author: Stanislaw Raczynski +Updated: Thu, 15 Sep 2005 12:01:18 +0200 +Status: working + + The Micro/sys MPC-624 board is based on the LTC2440 24-bit sigma-delta + ADC chip. + + Subdevices supported by the driver: + - Analog In: supported + - Digital I/O: not supported + - LEDs: not supported + - EEPROM: not supported + +Configuration Options: + [0] - I/O base address + [1] - conversion rate + Conversion rate RMS noise Effective Number Of Bits + 0 3.52kHz 23uV 17 + 1 1.76kHz 3.5uV 20 + 2 880Hz 2uV 21.3 + 3 440Hz 1.4uV 21.8 + 4 220Hz 1uV 22.4 + 5 110Hz 750uV 22.9 + 6 55Hz 510nV 23.4 + 7 27.5Hz 375nV 24 + 8 13.75Hz 250nV 24.4 + 9 6.875Hz 200nV 24.6 + [2] - voltage range + 0 -1.01V .. +1.01V + 1 -10.1V .. +10.1V +*/ + +#include "../comedidev.h" + +#include +#include + +/* Consecutive I/O port addresses */ +#define MPC624_SIZE 16 + +/* Offsets of different ports */ +#define MPC624_MASTER_CONTROL 0 /* not used */ +#define MPC624_GNMUXCH 1 /* Gain, Mux, Channel of ADC */ +#define MPC624_ADC 2 /* read/write to/from ADC */ +#define MPC624_EE 3 /* read/write to/from serial EEPROM via I2C */ +#define MPC624_LEDS 4 /* write to LEDs */ +#define MPC624_DIO 5 /* read/write to/from digital I/O ports */ +#define MPC624_IRQ_MASK 6 /* IRQ masking enable/disable */ + +/* Register bits' names */ +#define MPC624_ADBUSY (1<<5) +#define MPC624_ADSDO (1<<4) +#define MPC624_ADFO (1<<3) +#define MPC624_ADCS (1<<2) +#define MPC624_ADSCK (1<<1) +#define MPC624_ADSDI (1<<0) + +/* SDI Speed/Resolution Programming bits */ +#define MPC624_OSR4 (1<<31) +#define MPC624_OSR3 (1<<30) +#define MPC624_OSR2 (1<<29) +#define MPC624_OSR1 (1<<28) +#define MPC624_OSR0 (1<<27) + +/* 32-bit output value bits' names */ +#define MPC624_EOC_BIT (1<<31) +#define MPC624_DMY_BIT (1<<30) +#define MPC624_SGN_BIT (1<<29) + +/* Conversion speeds */ +/* OSR4 OSR3 OSR2 OSR1 OSR0 Conversion rate RMS noise ENOB^ + * X 0 0 0 1 3.52kHz 23uV 17 + * X 0 0 1 0 1.76kHz 3.5uV 20 + * X 0 0 1 1 880Hz 2uV 21.3 + * X 0 1 0 0 440Hz 1.4uV 21.8 + * X 0 1 0 1 220Hz 1uV 22.4 + * X 0 1 1 0 110Hz 750uV 22.9 + * X 0 1 1 1 55Hz 510nV 23.4 + * X 1 0 0 0 27.5Hz 375nV 24 + * X 1 0 0 1 13.75Hz 250nV 24.4 + * X 1 1 1 1 6.875Hz 200nV 24.6 + * + * ^ - Effective Number Of Bits + */ + +#define MPC624_SPEED_3_52_kHz (MPC624_OSR4 | MPC624_OSR0) +#define MPC624_SPEED_1_76_kHz (MPC624_OSR4 | MPC624_OSR1) +#define MPC624_SPEED_880_Hz (MPC624_OSR4 | MPC624_OSR1 | MPC624_OSR0) +#define MPC624_SPEED_440_Hz (MPC624_OSR4 | MPC624_OSR2) +#define MPC624_SPEED_220_Hz (MPC624_OSR4 | MPC624_OSR2 | MPC624_OSR0) +#define MPC624_SPEED_110_Hz (MPC624_OSR4 | MPC624_OSR2 | MPC624_OSR1) +#define MPC624_SPEED_55_Hz \ + (MPC624_OSR4 | MPC624_OSR2 | MPC624_OSR1 | MPC624_OSR0) +#define MPC624_SPEED_27_5_Hz (MPC624_OSR4 | MPC624_OSR3) +#define MPC624_SPEED_13_75_Hz (MPC624_OSR4 | MPC624_OSR3 | MPC624_OSR0) +#define MPC624_SPEED_6_875_Hz \ + (MPC624_OSR4 | MPC624_OSR3 | MPC624_OSR2 | MPC624_OSR1 | MPC624_OSR0) +/* -------------------------------------------------------------------------- */ +struct skel_private { + + /* set by mpc624_attach() from driver's parameters */ + unsigned long int ulConvertionRate; +}; + +#define devpriv ((struct skel_private *)dev->private) +/* -------------------------------------------------------------------------- */ +static const struct comedi_lrange range_mpc624_bipolar1 = { + 1, + { +/* BIP_RANGE(1.01) this is correct, */ + /* but my MPC-624 actually seems to have a range of 2.02 */ + BIP_RANGE(2.02) + } +}; + +static const struct comedi_lrange range_mpc624_bipolar10 = { + 1, + { +/* BIP_RANGE(10.1) this is correct, */ + /* but my MPC-624 actually seems to have a range of 20.2 */ + BIP_RANGE(20.2) + } +}; + +/* -------------------------------------------------------------------------- */ +static int mpc624_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int mpc624_detach(struct comedi_device *dev); +/* -------------------------------------------------------------------------- */ +static struct comedi_driver driver_mpc624 = { + .driver_name = "mpc624", + .module = THIS_MODULE, + .attach = mpc624_attach, + .detach = mpc624_detach +}; + +/* -------------------------------------------------------------------------- */ +static int mpc624_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +/* -------------------------------------------------------------------------- */ +static int mpc624_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: mpc624 [0x%04lx, ", dev->minor, iobase); + if (request_region(iobase, MPC624_SIZE, "mpc624") == NULL) { + printk(KERN_ERR "I/O port(s) in use\n"); + return -EIO; + } + + dev->iobase = iobase; + dev->board_name = "mpc624"; + + /* Private structure initialization */ + if (alloc_private(dev, sizeof(struct skel_private)) < 0) + return -ENOMEM; + + switch (it->options[1]) { + case 0: + devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz; + printk(KERN_INFO "3.52 kHz, "); + break; + case 1: + devpriv->ulConvertionRate = MPC624_SPEED_1_76_kHz; + printk(KERN_INFO "1.76 kHz, "); + break; + case 2: + devpriv->ulConvertionRate = MPC624_SPEED_880_Hz; + printk(KERN_INFO "880 Hz, "); + break; + case 3: + devpriv->ulConvertionRate = MPC624_SPEED_440_Hz; + printk(KERN_INFO "440 Hz, "); + break; + case 4: + devpriv->ulConvertionRate = MPC624_SPEED_220_Hz; + printk(KERN_INFO "220 Hz, "); + break; + case 5: + devpriv->ulConvertionRate = MPC624_SPEED_110_Hz; + printk(KERN_INFO "110 Hz, "); + break; + case 6: + devpriv->ulConvertionRate = MPC624_SPEED_55_Hz; + printk(KERN_INFO "55 Hz, "); + break; + case 7: + devpriv->ulConvertionRate = MPC624_SPEED_27_5_Hz; + printk(KERN_INFO "27.5 Hz, "); + break; + case 8: + devpriv->ulConvertionRate = MPC624_SPEED_13_75_Hz; + printk(KERN_INFO "13.75 Hz, "); + break; + case 9: + devpriv->ulConvertionRate = MPC624_SPEED_6_875_Hz; + printk(KERN_INFO "6.875 Hz, "); + break; + default: + printk + (KERN_ERR "illegal conversion rate setting!" + " Valid numbers are 0..9. Using 9 => 6.875 Hz, "); + devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz; + } + + /* Subdevices structures */ + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF; + s->n_chan = 8; + switch (it->options[1]) { + default: + s->maxdata = 0x3FFFFFFF; + printk(KERN_INFO "30 bit, "); + } + + switch (it->options[1]) { + case 0: + s->range_table = &range_mpc624_bipolar1; + printk(KERN_INFO "1.01V]: "); + break; + default: + s->range_table = &range_mpc624_bipolar10; + printk(KERN_INFO "10.1V]: "); + } + s->len_chanlist = 1; + s->insn_read = mpc624_ai_rinsn; + + printk(KERN_INFO "attached\n"); + + return 1; +} + +static int mpc624_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: mpc624: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, MPC624_SIZE); + + return 0; +} + +/* Timeout 200ms */ +#define TIMEOUT 200 + +static int mpc624_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n, i; + unsigned long int data_in, data_out; + unsigned char ucPort; + + /* + * WARNING: + * We always write 0 to GNSWA bit, so the channel range is +-/10.1Vdc + */ + outb(insn->chanspec, dev->iobase + MPC624_GNMUXCH); +/* printk("Channel %d:\n", insn->chanspec); */ + if (!insn->n) { + printk(KERN_INFO "MPC624: Warning, no data to acquire\n"); + return 0; + } + + for (n = 0; n < insn->n; n++) { + /* Trigger the conversion */ + outb(MPC624_ADSCK, dev->iobase + MPC624_ADC); + udelay(1); + outb(MPC624_ADCS | MPC624_ADSCK, dev->iobase + MPC624_ADC); + udelay(1); + outb(0, dev->iobase + MPC624_ADC); + udelay(1); + + /* Wait for the conversion to end */ + for (i = 0; i < TIMEOUT; i++) { + ucPort = inb(dev->iobase + MPC624_ADC); + if (ucPort & MPC624_ADBUSY) + udelay(1000); + else + break; + } + if (i == TIMEOUT) { + printk(KERN_ERR "MPC624: timeout (%dms)\n", TIMEOUT); + data[n] = 0; + return -ETIMEDOUT; + } + /* Start reading data */ + data_in = 0; + data_out = devpriv->ulConvertionRate; + udelay(1); + for (i = 0; i < 32; i++) { + /* Set the clock low */ + outb(0, dev->iobase + MPC624_ADC); + udelay(1); + + if (data_out & (1 << 31)) { /* the next bit is a 1 */ + /* Set the ADSDI line (send to MPC624) */ + outb(MPC624_ADSDI, dev->iobase + MPC624_ADC); + udelay(1); + /* Set the clock high */ + outb(MPC624_ADSCK | MPC624_ADSDI, + dev->iobase + MPC624_ADC); + } else { /* the next bit is a 0 */ + + /* Set the ADSDI line (send to MPC624) */ + outb(0, dev->iobase + MPC624_ADC); + udelay(1); + /* Set the clock high */ + outb(MPC624_ADSCK, dev->iobase + MPC624_ADC); + } + /* Read ADSDO on high clock (receive from MPC624) */ + udelay(1); + data_in <<= 1; + data_in |= + (inb(dev->iobase + MPC624_ADC) & MPC624_ADSDO) >> 4; + udelay(1); + + data_out <<= 1; + } + + /* + * Received 32-bit long value consist of: + * 31: EOC - + * (End Of Transmission) bit - should be 0 + * 30: DMY + * (Dummy) bit - should be 0 + * 29: SIG + * (Sign) bit- 1 if the voltage is positive, + * 0 if negative + * 28: MSB + * (Most Significant Bit) - the first bit of + * the conversion result + * .... + * 05: LSB + * (Least Significant Bit)- the last bit of the + * conversion result + * 04-00: sub-LSB + * - sub-LSBs are basically noise, but when + * averaged properly, they can increase conversion + * precision up to 29 bits; they can be discarded + * without loss of resolution. + */ + + if (data_in & MPC624_EOC_BIT) + printk(KERN_INFO "MPC624:EOC bit is set (data_in=%lu)!", + data_in); + if (data_in & MPC624_DMY_BIT) + printk(KERN_INFO "MPC624:DMY bit is set (data_in=%lu)!", + data_in); + if (data_in & MPC624_SGN_BIT) { /* Volatge is positive */ + /* + * comedi operates on unsigned numbers, so mask off EOC + * and DMY and don't clear the SGN bit + */ + data_in &= 0x3FFFFFFF; + data[n] = data_in; + } else { /* The voltage is negative */ + /* + * data_in contains a number in 30-bit two's complement + * code and we must deal with it + */ + data_in |= MPC624_SGN_BIT; + data_in = ~data_in; + data_in += 1; + data_in &= ~(MPC624_EOC_BIT | MPC624_DMY_BIT); + /* clear EOC and DMY bits */ + data_in = 0x20000000 - data_in; + data[n] = data_in; + } + } + + /* Return the number of samples read/written */ + return n; +} + +static int __init driver_mpc624_init_module(void) +{ + return comedi_driver_register(&driver_mpc624); +} + +static void __exit driver_mpc624_cleanup_module(void) +{ + comedi_driver_unregister(&driver_mpc624); +} + +module_init(driver_mpc624_init_module); +module_exit(driver_mpc624_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/mpc8260cpm.c b/drivers/staging/comedi/drivers/mpc8260cpm.c new file mode 100644 index 00000000..5f6816a3 --- /dev/null +++ b/drivers/staging/comedi/drivers/mpc8260cpm.c @@ -0,0 +1,186 @@ +/* + comedi/drivers/mpc8260.c + driver for digital I/O pins on the MPC 8260 CPM module + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000,2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: mpc8260cpm +Description: MPC8260 CPM module generic digital I/O lines +Devices: [Motorola] MPC8260 CPM (mpc8260cpm) +Author: ds +Status: experimental +Updated: Sat, 16 Mar 2002 17:34:48 -0800 + +This driver is specific to the Motorola MPC8260 processor, allowing +you to access the processor's generic digital I/O lines. + +It is apparently missing some code. +*/ + +#include "../comedidev.h" + +extern unsigned long mpc8260_dio_reserved[4]; + +struct mpc8260cpm_private { + + int data; + +}; + +#define devpriv ((struct mpc8260cpm_private *)dev->private) + +static int mpc8260cpm_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int mpc8260cpm_detach(struct comedi_device *dev); +static struct comedi_driver driver_mpc8260cpm = { + .driver_name = "mpc8260cpm", + .module = THIS_MODULE, + .attach = mpc8260cpm_attach, + .detach = mpc8260cpm_detach, +}; + +static int __init driver_mpc8260cpm_init_module(void) +{ + return comedi_driver_register(&driver_mpc8260cpm); +} + +static void __exit driver_mpc8260cpm_cleanup_module(void) +{ + comedi_driver_unregister(&driver_mpc8260cpm); +} + +module_init(driver_mpc8260cpm_init_module); +module_exit(driver_mpc8260cpm_cleanup_module); + +static int mpc8260cpm_dio_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int mpc8260cpm_dio_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int mpc8260cpm_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int i; + + printk("comedi%d: mpc8260cpm: ", dev->minor); + + dev->board_ptr = mpc8260cpm_boards + dev->board; + + dev->board_name = thisboard->name; + + if (alloc_private(dev, sizeof(struct mpc8260cpm_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + for (i = 0; i < 4; i++) { + s = dev->subdevices + i; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 32; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_config = mpc8260cpm_dio_config; + s->insn_bits = mpc8260cpm_dio_bits; + } + + return 1; +} + +static int mpc8260cpm_detach(struct comedi_device *dev) +{ + printk("comedi%d: mpc8260cpm: remove\n", dev->minor); + + return 0; +} + +static unsigned long *cpm_pdat(int port) +{ + switch (port) { + case 0: + return &io->iop_pdata; + case 1: + return &io->iop_pdatb; + case 2: + return &io->iop_pdatc; + case 3: + return &io->iop_pdatd; + } +} + +static int mpc8260cpm_dio_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + unsigned int d; + unsigned int mask; + int port; + + port = (int)s->private; + mask = 1 << CR_CHAN(insn->chanspec); + if (mask & cpm_reserved_bits[port]) { + return -EINVAL; + } + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= mask; + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~mask; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + } + + switch (port) { + case 0: + return &io->iop_pdira; + case 1: + return &io->iop_pdirb; + case 2: + return &io->iop_pdirc; + case 3: + return &io->iop_pdird; + } + + return 1; +} + +static int mpc8260cpm_dio_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int port; + unsigned long *p; + + p = cpm_pdat((int)s->private); + + return 2; +} diff --git a/drivers/staging/comedi/drivers/multiq3.c b/drivers/staging/comedi/drivers/multiq3.c new file mode 100644 index 00000000..dace902d --- /dev/null +++ b/drivers/staging/comedi/drivers/multiq3.c @@ -0,0 +1,355 @@ +/* + comedi/drivers/multiq3.c + Hardware driver for Quanser Consulting MultiQ-3 board + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999 Anders Blomdell + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: multiq3 +Description: Quanser Consulting MultiQ-3 +Author: Anders Blomdell +Status: works +Devices: [Quanser Consulting] MultiQ-3 (multiq3) + +*/ + +#include +#include "../comedidev.h" + +#include + +#define MULTIQ3_SIZE 16 + +/* + * MULTIQ-3 port offsets + */ +#define MULTIQ3_DIGIN_PORT 0 +#define MULTIQ3_DIGOUT_PORT 0 +#define MULTIQ3_DAC_DATA 2 +#define MULTIQ3_AD_DATA 4 +#define MULTIQ3_AD_CS 4 +#define MULTIQ3_STATUS 6 +#define MULTIQ3_CONTROL 6 +#define MULTIQ3_CLK_DATA 8 +#define MULTIQ3_ENC_DATA 12 +#define MULTIQ3_ENC_CONTROL 14 + +/* + * flags for CONTROL register + */ +#define MULTIQ3_AD_MUX_EN 0x0040 +#define MULTIQ3_AD_AUTOZ 0x0080 +#define MULTIQ3_AD_AUTOCAL 0x0100 +#define MULTIQ3_AD_SH 0x0200 +#define MULTIQ3_AD_CLOCK_4M 0x0400 +#define MULTIQ3_DA_LOAD 0x1800 + +#define MULTIQ3_CONTROL_MUST 0x0600 + +/* + * flags for STATUS register + */ +#define MULTIQ3_STATUS_EOC 0x008 +#define MULTIQ3_STATUS_EOC_I 0x010 + +/* + * flags for encoder control + */ +#define MULTIQ3_CLOCK_DATA 0x00 +#define MULTIQ3_CLOCK_SETUP 0x18 +#define MULTIQ3_INPUT_SETUP 0x41 +#define MULTIQ3_QUAD_X4 0x38 +#define MULTIQ3_BP_RESET 0x01 +#define MULTIQ3_CNTR_RESET 0x02 +#define MULTIQ3_TRSFRPR_CTR 0x08 +#define MULTIQ3_TRSFRCNTR_OL 0x10 +#define MULTIQ3_EFLAG_RESET 0x06 + +#define MULTIQ3_TIMEOUT 30 + +static int multiq3_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int multiq3_detach(struct comedi_device *dev); +static struct comedi_driver driver_multiq3 = { + .driver_name = "multiq3", + .module = THIS_MODULE, + .attach = multiq3_attach, + .detach = multiq3_detach, +}; + +static int __init driver_multiq3_init_module(void) +{ + return comedi_driver_register(&driver_multiq3); +} + +static void __exit driver_multiq3_cleanup_module(void) +{ + comedi_driver_unregister(&driver_multiq3); +} + +module_init(driver_multiq3_init_module); +module_exit(driver_multiq3_cleanup_module); + +struct multiq3_private { + unsigned int ao_readback[2]; +}; +#define devpriv ((struct multiq3_private *)dev->private) + +static int multiq3_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, n; + int chan; + unsigned int hi, lo; + + chan = CR_CHAN(insn->chanspec); + outw(MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3), + dev->iobase + MULTIQ3_CONTROL); + + for (i = 0; i < MULTIQ3_TIMEOUT; i++) { + if (inw(dev->iobase + MULTIQ3_STATUS) & MULTIQ3_STATUS_EOC) + break; + } + if (i == MULTIQ3_TIMEOUT) + return -ETIMEDOUT; + + for (n = 0; n < insn->n; n++) { + outw(0, dev->iobase + MULTIQ3_AD_CS); + for (i = 0; i < MULTIQ3_TIMEOUT; i++) { + if (inw(dev->iobase + + MULTIQ3_STATUS) & MULTIQ3_STATUS_EOC_I) + break; + } + if (i == MULTIQ3_TIMEOUT) + return -ETIMEDOUT; + + hi = inb(dev->iobase + MULTIQ3_AD_CS); + lo = inb(dev->iobase + MULTIQ3_AD_CS); + data[n] = (((hi << 8) | lo) + 0x1000) & 0x1fff; + } + + return n; +} + +static int multiq3_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int multiq3_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + outw(MULTIQ3_CONTROL_MUST | MULTIQ3_DA_LOAD | chan, + dev->iobase + MULTIQ3_CONTROL); + outw(data[i], dev->iobase + MULTIQ3_DAC_DATA); + outw(MULTIQ3_CONTROL_MUST, dev->iobase + MULTIQ3_CONTROL); + + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +static int multiq3_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inw(dev->iobase + MULTIQ3_DIGIN_PORT); + + return 2; +} + +static int multiq3_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + outw(s->state, dev->iobase + MULTIQ3_DIGOUT_PORT); + + data[1] = s->state; + + return 2; +} + +static int multiq3_encoder_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int n; + int chan = CR_CHAN(insn->chanspec); + int control = MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3); + + for (n = 0; n < insn->n; n++) { + int value; + outw(control, dev->iobase + MULTIQ3_CONTROL); + outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CONTROL); + outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CONTROL); + value = inb(dev->iobase + MULTIQ3_ENC_DATA); + value |= (inb(dev->iobase + MULTIQ3_ENC_DATA) << 8); + value |= (inb(dev->iobase + MULTIQ3_ENC_DATA) << 16); + data[n] = (value + 0x800000) & 0xffffff; + } + + return n; +} + +static void encoder_reset(struct comedi_device *dev) +{ + int chan; + for (chan = 0; chan < dev->subdevices[4].n_chan; chan++) { + int control = + MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3); + outw(control, dev->iobase + MULTIQ3_CONTROL); + outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CONTROL); + outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CONTROL); + outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA); + outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CONTROL); + outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CONTROL); + outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CONTROL); + outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CONTROL); + } +} + +/* + options[0] - I/O port + options[1] - irq + options[2] - number of encoder chips installed + */ + +static int multiq3_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int result = 0; + unsigned long iobase; + unsigned int irq; + struct comedi_subdevice *s; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: multiq3: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, MULTIQ3_SIZE, "multiq3")) { + printk(KERN_ERR "comedi%d: I/O port conflict\n", dev->minor); + return -EIO; + } + + dev->iobase = iobase; + + irq = it->options[1]; + if (irq) + printk(KERN_WARNING "comedi%d: irq = %u ignored\n", + dev->minor, irq); + else + printk(KERN_WARNING "comedi%d: no irq\n", dev->minor); + dev->board_name = "multiq3"; + result = alloc_subdevices(dev, 5); + if (result < 0) + return result; + + result = alloc_private(dev, sizeof(struct multiq3_private)); + if (result < 0) + return result; + + s = dev->subdevices + 0; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 8; + s->insn_read = multiq3_ai_insn_read; + s->maxdata = 0x1fff; + s->range_table = &range_bipolar5; + + s = dev->subdevices + 1; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->insn_read = multiq3_ao_insn_read; + s->insn_write = multiq3_ao_insn_write; + s->maxdata = 0xfff; + s->range_table = &range_bipolar5; + + s = dev->subdevices + 2; + /* di subdevice */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->insn_bits = multiq3_di_insn_bits; + s->maxdata = 1; + s->range_table = &range_digital; + + s = dev->subdevices + 3; + /* do subdevice */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->insn_bits = multiq3_do_insn_bits; + s->maxdata = 1; + s->range_table = &range_digital; + s->state = 0; + + s = dev->subdevices + 4; + /* encoder (counter) subdevice */ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_LSAMPL; + s->n_chan = it->options[2] * 2; + s->insn_read = multiq3_encoder_insn_read; + s->maxdata = 0xffffff; + s->range_table = &range_unknown; + + encoder_reset(dev); + + return 0; +} + +static int multiq3_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: multiq3: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, MULTIQ3_SIZE); + if (dev->irq) + free_irq(dev->irq, dev); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_6527.c b/drivers/staging/comedi/drivers/ni_6527.c new file mode 100644 index 00000000..54741c9e --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_6527.c @@ -0,0 +1,533 @@ +/* + comedi/drivers/ni_6527.c + driver for National Instruments PCI-6527 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999,2002,2003 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: ni_6527 +Description: National Instruments 6527 +Author: ds +Status: works +Devices: [National Instruments] PCI-6527 (ni6527), PXI-6527 +Updated: Sat, 25 Jan 2003 13:24:40 -0800 + + +*/ + +/* + Manuals (available from ftp://ftp.natinst.com/support/manuals) + + 370106b.pdf 6527 Register Level Programmer Manual + + */ + +#define DEBUG 1 +#define DEBUG_FLAGS + +#include +#include "../comedidev.h" + +#include "mite.h" + +#define NI6527_DIO_SIZE 4096 +#define NI6527_MITE_SIZE 4096 + +#define Port_Register(x) (0x00+(x)) +#define ID_Register 0x06 + +#define Clear_Register 0x07 +#define ClrEdge 0x08 +#define ClrOverflow 0x04 +#define ClrFilter 0x02 +#define ClrInterval 0x01 + +#define Filter_Interval(x) (0x08+(x)) +#define Filter_Enable(x) (0x0c+(x)) + +#define Change_Status 0x14 +#define MasterInterruptStatus 0x04 +#define Overflow 0x02 +#define EdgeStatus 0x01 + +#define Master_Interrupt_Control 0x15 +#define FallingEdgeIntEnable 0x10 +#define RisingEdgeIntEnable 0x08 +#define MasterInterruptEnable 0x04 +#define OverflowIntEnable 0x02 +#define EdgeIntEnable 0x01 + +#define Rising_Edge_Detection_Enable(x) (0x018+(x)) +#define Falling_Edge_Detection_Enable(x) (0x020+(x)) + +static int ni6527_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int ni6527_detach(struct comedi_device *dev); +static struct comedi_driver driver_ni6527 = { + .driver_name = "ni6527", + .module = THIS_MODULE, + .attach = ni6527_attach, + .detach = ni6527_detach, +}; + +struct ni6527_board { + + int dev_id; + const char *name; +}; + +static const struct ni6527_board ni6527_boards[] = { + { + .dev_id = 0x2b20, + .name = "pci-6527", + }, + { + .dev_id = 0x2b10, + .name = "pxi-6527", + }, +}; + +#define n_ni6527_boards ARRAY_SIZE(ni6527_boards) +#define this_board ((const struct ni6527_board *)dev->board_ptr) + +static DEFINE_PCI_DEVICE_TABLE(ni6527_pci_table) = { + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2b10)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2b20)}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, ni6527_pci_table); + +struct ni6527_private { + struct mite_struct *mite; + unsigned int filter_interval; + unsigned int filter_enable; +}; + +#define devpriv ((struct ni6527_private *)dev->private) + +static int ni6527_find_device(struct comedi_device *dev, int bus, int slot); + +static int ni6527_di_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + unsigned int interval; + + if (insn->n != 2) + return -EINVAL; + + if (data[0] != INSN_CONFIG_FILTER) + return -EINVAL; + + if (data[1]) { + interval = (data[1] + 100) / 200; + data[1] = interval * 200; + + if (interval != devpriv->filter_interval) { + writeb(interval & 0xff, + devpriv->mite->daq_io_addr + Filter_Interval(0)); + writeb((interval >> 8) & 0xff, + devpriv->mite->daq_io_addr + Filter_Interval(1)); + writeb((interval >> 16) & 0x0f, + devpriv->mite->daq_io_addr + Filter_Interval(2)); + + writeb(ClrInterval, + devpriv->mite->daq_io_addr + Clear_Register); + + devpriv->filter_interval = interval; + } + + devpriv->filter_enable |= 1 << chan; + } else { + devpriv->filter_enable &= ~(1 << chan); + } + + writeb(devpriv->filter_enable, + devpriv->mite->daq_io_addr + Filter_Enable(0)); + writeb(devpriv->filter_enable >> 8, + devpriv->mite->daq_io_addr + Filter_Enable(1)); + writeb(devpriv->filter_enable >> 16, + devpriv->mite->daq_io_addr + Filter_Enable(2)); + + return 2; +} + +static int ni6527_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = readb(devpriv->mite->daq_io_addr + Port_Register(0)); + data[1] |= readb(devpriv->mite->daq_io_addr + Port_Register(1)) << 8; + data[1] |= readb(devpriv->mite->daq_io_addr + Port_Register(2)) << 16; + + return 2; +} + +static int ni6527_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + + /* The open relay state on the board cooresponds to 1, + * but in Comedi, it is represented by 0. */ + if (data[0] & 0x0000ff) { + writeb((s->state ^ 0xff), + devpriv->mite->daq_io_addr + Port_Register(3)); + } + if (data[0] & 0x00ff00) { + writeb((s->state >> 8) ^ 0xff, + devpriv->mite->daq_io_addr + Port_Register(4)); + } + if (data[0] & 0xff0000) { + writeb((s->state >> 16) ^ 0xff, + devpriv->mite->daq_io_addr + Port_Register(5)); + } + } + data[1] = s->state; + + return 2; +} + +static irqreturn_t ni6527_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 2; + unsigned int status; + + status = readb(devpriv->mite->daq_io_addr + Change_Status); + if ((status & MasterInterruptStatus) == 0) + return IRQ_NONE; + if ((status & EdgeStatus) == 0) + return IRQ_NONE; + + writeb(ClrEdge | ClrOverflow, + devpriv->mite->daq_io_addr + Clear_Register); + + comedi_buf_put(s->async, 0); + s->async->events |= COMEDI_CB_EOS; + comedi_event(dev, s); + return IRQ_HANDLED; +} + +static int ni6527_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_OTHER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_FOLLOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and */ + /* are mutually compatible */ + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + + if (cmd->scan_end_arg != 1) { + cmd->scan_end_arg = 1; + err++; + } + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (err) + return 4; + + return 0; +} + +static int ni6527_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* struct comedi_cmd *cmd = &s->async->cmd; */ + + writeb(ClrEdge | ClrOverflow, + devpriv->mite->daq_io_addr + Clear_Register); + writeb(FallingEdgeIntEnable | RisingEdgeIntEnable | + MasterInterruptEnable | EdgeIntEnable, + devpriv->mite->daq_io_addr + Master_Interrupt_Control); + + return 0; +} + +static int ni6527_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writeb(0x00, devpriv->mite->daq_io_addr + Master_Interrupt_Control); + + return 0; +} + +static int ni6527_intr_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n < 1) + return -EINVAL; + + data[1] = 0; + return 2; +} + +static int ni6527_intr_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n < 1) + return -EINVAL; + if (data[0] != INSN_CONFIG_CHANGE_NOTIFY) + return -EINVAL; + + writeb(data[1], + devpriv->mite->daq_io_addr + Rising_Edge_Detection_Enable(0)); + writeb(data[1] >> 8, + devpriv->mite->daq_io_addr + Rising_Edge_Detection_Enable(1)); + writeb(data[1] >> 16, + devpriv->mite->daq_io_addr + Rising_Edge_Detection_Enable(2)); + + writeb(data[2], + devpriv->mite->daq_io_addr + Falling_Edge_Detection_Enable(0)); + writeb(data[2] >> 8, + devpriv->mite->daq_io_addr + Falling_Edge_Detection_Enable(1)); + writeb(data[2] >> 16, + devpriv->mite->daq_io_addr + Falling_Edge_Detection_Enable(2)); + + return 2; +} + +static int ni6527_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + + printk(KERN_INFO "comedi%d: ni6527\n", dev->minor); + + ret = alloc_private(dev, sizeof(struct ni6527_private)); + if (ret < 0) + return ret; + + ret = ni6527_find_device(dev, it->options[0], it->options[1]); + if (ret < 0) + return ret; + + ret = mite_setup(devpriv->mite); + if (ret < 0) { + printk(KERN_ERR "comedi: error setting up mite\n"); + return ret; + } + + dev->board_name = this_board->name; + printk(KERN_INFO "comedi board: %s, ID=0x%02x\n", dev->board_name, + readb(devpriv->mite->daq_io_addr + ID_Register)); + + ret = alloc_subdevices(dev, 3); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 24; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_config = ni6527_di_insn_config; + s->insn_bits = ni6527_di_insn_bits; + + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 24; + s->range_table = &range_unknown; /* FIXME: actually conductance */ + s->maxdata = 1; + s->insn_bits = ni6527_do_insn_bits; + + s = dev->subdevices + 2; + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 1; + s->range_table = &range_unknown; + s->maxdata = 1; + s->do_cmdtest = ni6527_intr_cmdtest; + s->do_cmd = ni6527_intr_cmd; + s->cancel = ni6527_intr_cancel; + s->insn_bits = ni6527_intr_insn_bits; + s->insn_config = ni6527_intr_insn_config; + + writeb(0x00, devpriv->mite->daq_io_addr + Filter_Enable(0)); + writeb(0x00, devpriv->mite->daq_io_addr + Filter_Enable(1)); + writeb(0x00, devpriv->mite->daq_io_addr + Filter_Enable(2)); + + writeb(ClrEdge | ClrOverflow | ClrFilter | ClrInterval, + devpriv->mite->daq_io_addr + Clear_Register); + writeb(0x00, devpriv->mite->daq_io_addr + Master_Interrupt_Control); + + ret = request_irq(mite_irq(devpriv->mite), ni6527_interrupt, + IRQF_SHARED, "ni6527", dev); + if (ret < 0) + printk(KERN_WARNING "comedi i6527 irq not available\n"); + else + dev->irq = mite_irq(devpriv->mite); + + return 0; +} + +static int ni6527_detach(struct comedi_device *dev) +{ + if (devpriv && devpriv->mite && devpriv->mite->daq_io_addr) + writeb(0x00, + devpriv->mite->daq_io_addr + Master_Interrupt_Control); + + if (dev->irq) + free_irq(dev->irq, dev); + + if (devpriv && devpriv->mite) + mite_unsetup(devpriv->mite); + + return 0; +} + +static int ni6527_find_device(struct comedi_device *dev, int bus, int slot) +{ + struct mite_struct *mite; + int i; + + for (mite = mite_devices; mite; mite = mite->next) { + if (mite->used) + continue; + if (bus || slot) { + if (bus != mite->pcidev->bus->number || + slot != PCI_SLOT(mite->pcidev->devfn)) + continue; + } + for (i = 0; i < n_ni6527_boards; i++) { + if (mite_device_id(mite) == ni6527_boards[i].dev_id) { + dev->board_ptr = ni6527_boards + i; + devpriv->mite = mite; + return 0; + } + } + } + printk(KERN_ERR "comedi 6527: no device found\n"); + mite_list_devices(); + return -EIO; +} + +static int __devinit driver_ni6527_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_ni6527.driver_name); +} + +static void __devexit driver_ni6527_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_ni6527_pci_driver = { + .id_table = ni6527_pci_table, + .probe = &driver_ni6527_pci_probe, + .remove = __devexit_p(&driver_ni6527_pci_remove) +}; + +static int __init driver_ni6527_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_ni6527); + if (retval < 0) + return retval; + + driver_ni6527_pci_driver.name = (char *)driver_ni6527.driver_name; + return pci_register_driver(&driver_ni6527_pci_driver); +} + +static void __exit driver_ni6527_cleanup_module(void) +{ + pci_unregister_driver(&driver_ni6527_pci_driver); + comedi_driver_unregister(&driver_ni6527); +} + +module_init(driver_ni6527_init_module); +module_exit(driver_ni6527_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_65xx.c b/drivers/staging/comedi/drivers/ni_65xx.c new file mode 100644 index 00000000..403fc099 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_65xx.c @@ -0,0 +1,877 @@ +/* + comedi/drivers/ni_6514.c + driver for National Instruments PCI-6514 + + Copyright (C) 2006 Jon Grierson + Copyright (C) 2006 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999,2002,2003 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: ni_65xx +Description: National Instruments 65xx static dio boards +Author: Jon Grierson , + Frank Mori Hess +Status: testing +Devices: [National Instruments] PCI-6509 (ni_65xx), PXI-6509, PCI-6510, + PCI-6511, PXI-6511, PCI-6512, PXI-6512, PCI-6513, PXI-6513, PCI-6514, + PXI-6514, PCI-6515, PXI-6515, PCI-6516, PCI-6517, PCI-6518, PCI-6519, + PCI-6520, PCI-6521, PXI-6521, PCI-6528, PXI-6528 +Updated: Wed Oct 18 08:59:11 EDT 2006 + +Based on the PCI-6527 driver by ds. +The interrupt subdevice (subdevice 3) is probably broken for all boards +except maybe the 6514. + +*/ + +/* + Manuals (available from ftp://ftp.natinst.com/support/manuals) + + 370106b.pdf 6514 Register Level Programmer Manual + + */ + +#define _GNU_SOURCE +#define DEBUG 1 +#define DEBUG_FLAGS +#include +#include +#include "../comedidev.h" + +#include "mite.h" + +#define NI6514_DIO_SIZE 4096 +#define NI6514_MITE_SIZE 4096 + +#define NI_65XX_MAX_NUM_PORTS 12 +static const unsigned ni_65xx_channels_per_port = 8; +static const unsigned ni_65xx_port_offset = 0x10; + +static inline unsigned Port_Data(unsigned port) +{ + return 0x40 + port * ni_65xx_port_offset; +} + +static inline unsigned Port_Select(unsigned port) +{ + return 0x41 + port * ni_65xx_port_offset; +} + +static inline unsigned Rising_Edge_Detection_Enable(unsigned port) +{ + return 0x42 + port * ni_65xx_port_offset; +} + +static inline unsigned Falling_Edge_Detection_Enable(unsigned port) +{ + return 0x43 + port * ni_65xx_port_offset; +} + +static inline unsigned Filter_Enable(unsigned port) +{ + return 0x44 + port * ni_65xx_port_offset; +} + +#define ID_Register 0x00 + +#define Clear_Register 0x01 +#define ClrEdge 0x08 +#define ClrOverflow 0x04 + +#define Filter_Interval 0x08 + +#define Change_Status 0x02 +#define MasterInterruptStatus 0x04 +#define Overflow 0x02 +#define EdgeStatus 0x01 + +#define Master_Interrupt_Control 0x03 +#define FallingEdgeIntEnable 0x10 +#define RisingEdgeIntEnable 0x08 +#define MasterInterruptEnable 0x04 +#define OverflowIntEnable 0x02 +#define EdgeIntEnable 0x01 + +static int ni_65xx_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int ni_65xx_detach(struct comedi_device *dev); +static struct comedi_driver driver_ni_65xx = { + .driver_name = "ni_65xx", + .module = THIS_MODULE, + .attach = ni_65xx_attach, + .detach = ni_65xx_detach, +}; + +struct ni_65xx_board { + + int dev_id; + const char *name; + unsigned num_dio_ports; + unsigned num_di_ports; + unsigned num_do_ports; + unsigned invert_outputs:1; +}; + +static const struct ni_65xx_board ni_65xx_boards[] = { + { + .dev_id = 0x7085, + .name = "pci-6509", + .num_dio_ports = 12, + .invert_outputs = 0}, + { + .dev_id = 0x1710, + .name = "pxi-6509", + .num_dio_ports = 12, + .invert_outputs = 0}, + { + .dev_id = 0x7124, + .name = "pci-6510", + .num_di_ports = 4}, + { + .dev_id = 0x70c3, + .name = "pci-6511", + .num_di_ports = 8}, + { + .dev_id = 0x70d3, + .name = "pxi-6511", + .num_di_ports = 8}, + { + .dev_id = 0x70cc, + .name = "pci-6512", + .num_do_ports = 8}, + { + .dev_id = 0x70d2, + .name = "pxi-6512", + .num_do_ports = 8}, + { + .dev_id = 0x70c8, + .name = "pci-6513", + .num_do_ports = 8, + .invert_outputs = 1}, + { + .dev_id = 0x70d1, + .name = "pxi-6513", + .num_do_ports = 8, + .invert_outputs = 1}, + { + .dev_id = 0x7088, + .name = "pci-6514", + .num_di_ports = 4, + .num_do_ports = 4, + .invert_outputs = 1}, + { + .dev_id = 0x70CD, + .name = "pxi-6514", + .num_di_ports = 4, + .num_do_ports = 4, + .invert_outputs = 1}, + { + .dev_id = 0x7087, + .name = "pci-6515", + .num_di_ports = 4, + .num_do_ports = 4, + .invert_outputs = 1}, + { + .dev_id = 0x70c9, + .name = "pxi-6515", + .num_di_ports = 4, + .num_do_ports = 4, + .invert_outputs = 1}, + { + .dev_id = 0x7125, + .name = "pci-6516", + .num_do_ports = 4, + .invert_outputs = 1}, + { + .dev_id = 0x7126, + .name = "pci-6517", + .num_do_ports = 4, + .invert_outputs = 1}, + { + .dev_id = 0x7127, + .name = "pci-6518", + .num_di_ports = 2, + .num_do_ports = 2, + .invert_outputs = 1}, + { + .dev_id = 0x7128, + .name = "pci-6519", + .num_di_ports = 2, + .num_do_ports = 2, + .invert_outputs = 1}, + { + .dev_id = 0x71c5, + .name = "pci-6520", + .num_di_ports = 1, + .num_do_ports = 1, + }, + { + .dev_id = 0x718b, + .name = "pci-6521", + .num_di_ports = 1, + .num_do_ports = 1, + }, + { + .dev_id = 0x718c, + .name = "pxi-6521", + .num_di_ports = 1, + .num_do_ports = 1, + }, + { + .dev_id = 0x70a9, + .name = "pci-6528", + .num_di_ports = 3, + .num_do_ports = 3, + }, + { + .dev_id = 0x7086, + .name = "pxi-6528", + .num_di_ports = 3, + .num_do_ports = 3, + }, +}; + +#define n_ni_65xx_boards ARRAY_SIZE(ni_65xx_boards) +static inline const struct ni_65xx_board *board(struct comedi_device *dev) +{ + return dev->board_ptr; +} + +static inline unsigned ni_65xx_port_by_channel(unsigned channel) +{ + return channel / ni_65xx_channels_per_port; +} + +static inline unsigned ni_65xx_total_num_ports(const struct ni_65xx_board + *board) +{ + return board->num_dio_ports + board->num_di_ports + board->num_do_ports; +} + +static DEFINE_PCI_DEVICE_TABLE(ni_65xx_pci_table) = { + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1710)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x7085)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x7086)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x7087)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x7088)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70a9)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70c3)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70c8)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70c9)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70cc)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70CD)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70d1)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70d2)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70d3)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x7124)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x7125)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x7126)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x7127)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x7128)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x718b)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x718c)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x71c5)}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, ni_65xx_pci_table); + +struct ni_65xx_private { + struct mite_struct *mite; + unsigned int filter_interval; + unsigned short filter_enable[NI_65XX_MAX_NUM_PORTS]; + unsigned short output_bits[NI_65XX_MAX_NUM_PORTS]; + unsigned short dio_direction[NI_65XX_MAX_NUM_PORTS]; +}; + +static inline struct ni_65xx_private *private(struct comedi_device *dev) +{ + return dev->private; +} + +struct ni_65xx_subdevice_private { + unsigned base_port; +}; + +static inline struct ni_65xx_subdevice_private *sprivate(struct comedi_subdevice + *subdev) +{ + return subdev->private; +} + +static struct ni_65xx_subdevice_private *ni_65xx_alloc_subdevice_private(void) +{ + struct ni_65xx_subdevice_private *subdev_private = + kzalloc(sizeof(struct ni_65xx_subdevice_private), GFP_KERNEL); + if (subdev_private == NULL) + return NULL; + return subdev_private; +} + +static int ni_65xx_find_device(struct comedi_device *dev, int bus, int slot); + +static int ni_65xx_config_filter(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + const unsigned chan = CR_CHAN(insn->chanspec); + const unsigned port = + sprivate(s)->base_port + ni_65xx_port_by_channel(chan); + + if (data[0] != INSN_CONFIG_FILTER) + return -EINVAL; + if (data[1]) { + static const unsigned filter_resolution_ns = 200; + static const unsigned max_filter_interval = 0xfffff; + unsigned interval = + (data[1] + + (filter_resolution_ns / 2)) / filter_resolution_ns; + if (interval > max_filter_interval) + interval = max_filter_interval; + data[1] = interval * filter_resolution_ns; + + if (interval != private(dev)->filter_interval) { + writeb(interval, + private(dev)->mite->daq_io_addr + + Filter_Interval); + private(dev)->filter_interval = interval; + } + + private(dev)->filter_enable[port] |= + 1 << (chan % ni_65xx_channels_per_port); + } else { + private(dev)->filter_enable[port] &= + ~(1 << (chan % ni_65xx_channels_per_port)); + } + + writeb(private(dev)->filter_enable[port], + private(dev)->mite->daq_io_addr + Filter_Enable(port)); + + return 2; +} + +static int ni_65xx_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned port; + + if (insn->n < 1) + return -EINVAL; + port = sprivate(s)->base_port + + ni_65xx_port_by_channel(CR_CHAN(insn->chanspec)); + switch (data[0]) { + case INSN_CONFIG_FILTER: + return ni_65xx_config_filter(dev, s, insn, data); + break; + case INSN_CONFIG_DIO_OUTPUT: + if (s->type != COMEDI_SUBD_DIO) + return -EINVAL; + private(dev)->dio_direction[port] = COMEDI_OUTPUT; + writeb(0, private(dev)->mite->daq_io_addr + Port_Select(port)); + return 1; + break; + case INSN_CONFIG_DIO_INPUT: + if (s->type != COMEDI_SUBD_DIO) + return -EINVAL; + private(dev)->dio_direction[port] = COMEDI_INPUT; + writeb(1, private(dev)->mite->daq_io_addr + Port_Select(port)); + return 1; + break; + case INSN_CONFIG_DIO_QUERY: + if (s->type != COMEDI_SUBD_DIO) + return -EINVAL; + data[1] = private(dev)->dio_direction[port]; + return insn->n; + break; + default: + break; + } + return -EINVAL; +} + +static int ni_65xx_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned base_bitfield_channel; + const unsigned max_ports_per_bitfield = 5; + unsigned read_bits = 0; + unsigned j; + if (insn->n != 2) + return -EINVAL; + base_bitfield_channel = CR_CHAN(insn->chanspec); + for (j = 0; j < max_ports_per_bitfield; ++j) { + const unsigned port_offset = + ni_65xx_port_by_channel(base_bitfield_channel) + j; + const unsigned port = + sprivate(s)->base_port + port_offset; + unsigned base_port_channel; + unsigned port_mask, port_data, port_read_bits; + int bitshift; + if (port >= ni_65xx_total_num_ports(board(dev))) + break; + base_port_channel = port_offset * ni_65xx_channels_per_port; + port_mask = data[0]; + port_data = data[1]; + bitshift = base_port_channel - base_bitfield_channel; + if (bitshift >= 32 || bitshift <= -32) + break; + if (bitshift > 0) { + port_mask >>= bitshift; + port_data >>= bitshift; + } else { + port_mask <<= -bitshift; + port_data <<= -bitshift; + } + port_mask &= 0xff; + port_data &= 0xff; + if (port_mask) { + unsigned bits; + private(dev)->output_bits[port] &= ~port_mask; + private(dev)->output_bits[port] |= + port_data & port_mask; + bits = private(dev)->output_bits[port]; + if (board(dev)->invert_outputs) + bits = ~bits; + writeb(bits, + private(dev)->mite->daq_io_addr + + Port_Data(port)); +/* printk("wrote 0x%x to port %i\n", bits, port); */ + } + port_read_bits = + readb(private(dev)->mite->daq_io_addr + Port_Data(port)); +/* printk("read 0x%x from port %i\n", port_read_bits, port); */ + if (s->type == COMEDI_SUBD_DO && board(dev)->invert_outputs) { + /* Outputs inverted, so invert value read back from + * DO subdevice. (Does not apply to boards with DIO + * subdevice.) */ + port_read_bits ^= 0xFF; + } + if (bitshift > 0) + port_read_bits <<= bitshift; + else + port_read_bits >>= -bitshift; + + read_bits |= port_read_bits; + } + data[1] = read_bits; + return insn->n; +} + +static irqreturn_t ni_65xx_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 2; + unsigned int status; + + status = readb(private(dev)->mite->daq_io_addr + Change_Status); + if ((status & MasterInterruptStatus) == 0) + return IRQ_NONE; + if ((status & EdgeStatus) == 0) + return IRQ_NONE; + + writeb(ClrEdge | ClrOverflow, + private(dev)->mite->daq_io_addr + Clear_Register); + + comedi_buf_put(s->async, 0); + s->async->events |= COMEDI_CB_EOS; + comedi_event(dev, s); + return IRQ_HANDLED; +} + +static int ni_65xx_intr_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_OTHER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_FOLLOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually + compatible */ + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + + if (cmd->scan_end_arg != 1) { + cmd->scan_end_arg = 1; + err++; + } + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (err) + return 4; + + return 0; +} + +static int ni_65xx_intr_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* struct comedi_cmd *cmd = &s->async->cmd; */ + + writeb(ClrEdge | ClrOverflow, + private(dev)->mite->daq_io_addr + Clear_Register); + writeb(FallingEdgeIntEnable | RisingEdgeIntEnable | + MasterInterruptEnable | EdgeIntEnable, + private(dev)->mite->daq_io_addr + Master_Interrupt_Control); + + return 0; +} + +static int ni_65xx_intr_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writeb(0x00, + private(dev)->mite->daq_io_addr + Master_Interrupt_Control); + + return 0; +} + +static int ni_65xx_intr_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n < 1) + return -EINVAL; + + data[1] = 0; + return 2; +} + +static int ni_65xx_intr_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + if (insn->n < 1) + return -EINVAL; + if (data[0] != INSN_CONFIG_CHANGE_NOTIFY) + return -EINVAL; + + writeb(data[1], + private(dev)->mite->daq_io_addr + + Rising_Edge_Detection_Enable(0)); + writeb(data[1] >> 8, + private(dev)->mite->daq_io_addr + + Rising_Edge_Detection_Enable(0x10)); + writeb(data[1] >> 16, + private(dev)->mite->daq_io_addr + + Rising_Edge_Detection_Enable(0x20)); + writeb(data[1] >> 24, + private(dev)->mite->daq_io_addr + + Rising_Edge_Detection_Enable(0x30)); + + writeb(data[2], + private(dev)->mite->daq_io_addr + + Falling_Edge_Detection_Enable(0)); + writeb(data[2] >> 8, + private(dev)->mite->daq_io_addr + + Falling_Edge_Detection_Enable(0x10)); + writeb(data[2] >> 16, + private(dev)->mite->daq_io_addr + + Falling_Edge_Detection_Enable(0x20)); + writeb(data[2] >> 24, + private(dev)->mite->daq_io_addr + + Falling_Edge_Detection_Enable(0x30)); + + return 2; +} + +static int ni_65xx_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned i; + int ret; + + printk(KERN_INFO "comedi%d: ni_65xx:", dev->minor); + + ret = alloc_private(dev, sizeof(struct ni_65xx_private)); + if (ret < 0) + return ret; + + ret = ni_65xx_find_device(dev, it->options[0], it->options[1]); + if (ret < 0) + return ret; + + ret = mite_setup(private(dev)->mite); + if (ret < 0) { + printk(KERN_WARNING "error setting up mite\n"); + return ret; + } + + dev->board_name = board(dev)->name; + dev->irq = mite_irq(private(dev)->mite); + printk(KERN_INFO " %s", dev->board_name); + + printk(KERN_INFO " ID=0x%02x", + readb(private(dev)->mite->daq_io_addr + ID_Register)); + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + if (board(dev)->num_di_ports) { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = + board(dev)->num_di_ports * ni_65xx_channels_per_port; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_config = ni_65xx_dio_insn_config; + s->insn_bits = ni_65xx_dio_insn_bits; + s->private = ni_65xx_alloc_subdevice_private(); + if (s->private == NULL) + return -ENOMEM; + sprivate(s)->base_port = 0; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 1; + if (board(dev)->num_do_ports) { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = + board(dev)->num_do_ports * ni_65xx_channels_per_port; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_bits = ni_65xx_dio_insn_bits; + s->private = ni_65xx_alloc_subdevice_private(); + if (s->private == NULL) + return -ENOMEM; + sprivate(s)->base_port = board(dev)->num_di_ports; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 2; + if (board(dev)->num_dio_ports) { + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = + board(dev)->num_dio_ports * ni_65xx_channels_per_port; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_config = ni_65xx_dio_insn_config; + s->insn_bits = ni_65xx_dio_insn_bits; + s->private = ni_65xx_alloc_subdevice_private(); + if (s->private == NULL) + return -ENOMEM; + sprivate(s)->base_port = 0; + for (i = 0; i < board(dev)->num_dio_ports; ++i) { + /* configure all ports for input */ + writeb(0x1, + private(dev)->mite->daq_io_addr + + Port_Select(i)); + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s = dev->subdevices + 3; + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 1; + s->range_table = &range_unknown; + s->maxdata = 1; + s->do_cmdtest = ni_65xx_intr_cmdtest; + s->do_cmd = ni_65xx_intr_cmd; + s->cancel = ni_65xx_intr_cancel; + s->insn_bits = ni_65xx_intr_insn_bits; + s->insn_config = ni_65xx_intr_insn_config; + + for (i = 0; i < ni_65xx_total_num_ports(board(dev)); ++i) { + writeb(0x00, + private(dev)->mite->daq_io_addr + Filter_Enable(i)); + if (board(dev)->invert_outputs) + writeb(0x01, + private(dev)->mite->daq_io_addr + Port_Data(i)); + else + writeb(0x00, + private(dev)->mite->daq_io_addr + Port_Data(i)); + } + writeb(ClrEdge | ClrOverflow, + private(dev)->mite->daq_io_addr + Clear_Register); + writeb(0x00, + private(dev)->mite->daq_io_addr + Master_Interrupt_Control); + + /* Set filter interval to 0 (32bit reg) */ + writeb(0x00000000, private(dev)->mite->daq_io_addr + Filter_Interval); + + ret = request_irq(dev->irq, ni_65xx_interrupt, IRQF_SHARED, + "ni_65xx", dev); + if (ret < 0) { + dev->irq = 0; + printk(KERN_WARNING " irq not available"); + } + + printk("\n"); + + return 0; +} + +static int ni_65xx_detach(struct comedi_device *dev) +{ + if (private(dev) && private(dev)->mite + && private(dev)->mite->daq_io_addr) { + writeb(0x00, + private(dev)->mite->daq_io_addr + + Master_Interrupt_Control); + } + + if (dev->irq) + free_irq(dev->irq, dev); + + if (private(dev)) { + unsigned i; + for (i = 0; i < dev->n_subdevices; ++i) { + kfree(dev->subdevices[i].private); + dev->subdevices[i].private = NULL; + } + if (private(dev)->mite) + mite_unsetup(private(dev)->mite); + } + return 0; +} + +static int ni_65xx_find_device(struct comedi_device *dev, int bus, int slot) +{ + struct mite_struct *mite; + int i; + + for (mite = mite_devices; mite; mite = mite->next) { + if (mite->used) + continue; + if (bus || slot) { + if (bus != mite->pcidev->bus->number || + slot != PCI_SLOT(mite->pcidev->devfn)) + continue; + } + for (i = 0; i < n_ni_65xx_boards; i++) { + if (mite_device_id(mite) == ni_65xx_boards[i].dev_id) { + dev->board_ptr = ni_65xx_boards + i; + private(dev)->mite = mite; + return 0; + } + } + } + printk(KERN_WARNING "no device found\n"); + mite_list_devices(); + return -EIO; +} + +static int __devinit driver_ni_65xx_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_ni_65xx.driver_name); +} + +static void __devexit driver_ni_65xx_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_ni_65xx_pci_driver = { + .id_table = ni_65xx_pci_table, + .probe = &driver_ni_65xx_pci_probe, + .remove = __devexit_p(&driver_ni_65xx_pci_remove) +}; + +static int __init driver_ni_65xx_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_ni_65xx); + if (retval < 0) + return retval; + + driver_ni_65xx_pci_driver.name = (char *)driver_ni_65xx.driver_name; + return pci_register_driver(&driver_ni_65xx_pci_driver); +} + +static void __exit driver_ni_65xx_cleanup_module(void) +{ + pci_unregister_driver(&driver_ni_65xx_pci_driver); + comedi_driver_unregister(&driver_ni_65xx); +} + +module_init(driver_ni_65xx_init_module); +module_exit(driver_ni_65xx_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_660x.c b/drivers/staging/comedi/drivers/ni_660x.c new file mode 100644 index 00000000..35f3a474 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_660x.c @@ -0,0 +1,1427 @@ +/* + comedi/drivers/ni_660x.c + Hardware driver for NI 660x devices + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* +Driver: ni_660x +Description: National Instruments 660x counter/timer boards +Devices: +[National Instruments] PCI-6601 (ni_660x), PCI-6602, PXI-6602, + PXI-6608 +Author: J.P. Mellor , + Herman.Bruyninckx@mech.kuleuven.ac.be, + Wim.Meeussen@mech.kuleuven.ac.be, + Klaas.Gadeyne@mech.kuleuven.ac.be, + Frank Mori Hess +Updated: Thu Oct 18 12:56:06 EDT 2007 +Status: experimental + +Encoders work. PulseGeneration (both single pulse and pulse train) +works. Buffered commands work for input but not output. + +References: +DAQ 660x Register-Level Programmer Manual (NI 370505A-01) +DAQ 6601/6602 User Manual (NI 322137B-01) + +*/ + +#include +#include "../comedidev.h" +#include "mite.h" +#include "ni_tio.h" + +enum ni_660x_constants { + min_counter_pfi_chan = 8, + max_dio_pfi_chan = 31, + counters_per_chip = 4 +}; + +#define NUM_PFI_CHANNELS 40 +/* really there are only up to 3 dma channels, but the register layout allows +for 4 */ +#define MAX_DMA_CHANNEL 4 + +/* See Register-Level Programmer Manual page 3.1 */ +enum NI_660x_Register { + G0InterruptAcknowledge, + G0StatusRegister, + G1InterruptAcknowledge, + G1StatusRegister, + G01StatusRegister, + G0CommandRegister, + STCDIOParallelInput, + G1CommandRegister, + G0HWSaveRegister, + G1HWSaveRegister, + STCDIOOutput, + STCDIOControl, + G0SWSaveRegister, + G1SWSaveRegister, + G0ModeRegister, + G01JointStatus1Register, + G1ModeRegister, + STCDIOSerialInput, + G0LoadARegister, + G01JointStatus2Register, + G0LoadBRegister, + G1LoadARegister, + G1LoadBRegister, + G0InputSelectRegister, + G1InputSelectRegister, + G0AutoincrementRegister, + G1AutoincrementRegister, + G01JointResetRegister, + G0InterruptEnable, + G1InterruptEnable, + G0CountingModeRegister, + G1CountingModeRegister, + G0SecondGateRegister, + G1SecondGateRegister, + G0DMAConfigRegister, + G0DMAStatusRegister, + G1DMAConfigRegister, + G1DMAStatusRegister, + G2InterruptAcknowledge, + G2StatusRegister, + G3InterruptAcknowledge, + G3StatusRegister, + G23StatusRegister, + G2CommandRegister, + G3CommandRegister, + G2HWSaveRegister, + G3HWSaveRegister, + G2SWSaveRegister, + G3SWSaveRegister, + G2ModeRegister, + G23JointStatus1Register, + G3ModeRegister, + G2LoadARegister, + G23JointStatus2Register, + G2LoadBRegister, + G3LoadARegister, + G3LoadBRegister, + G2InputSelectRegister, + G3InputSelectRegister, + G2AutoincrementRegister, + G3AutoincrementRegister, + G23JointResetRegister, + G2InterruptEnable, + G3InterruptEnable, + G2CountingModeRegister, + G3CountingModeRegister, + G3SecondGateRegister, + G2SecondGateRegister, + G2DMAConfigRegister, + G2DMAStatusRegister, + G3DMAConfigRegister, + G3DMAStatusRegister, + DIO32Input, + DIO32Output, + ClockConfigRegister, + GlobalInterruptStatusRegister, + DMAConfigRegister, + GlobalInterruptConfigRegister, + IOConfigReg0_1, + IOConfigReg2_3, + IOConfigReg4_5, + IOConfigReg6_7, + IOConfigReg8_9, + IOConfigReg10_11, + IOConfigReg12_13, + IOConfigReg14_15, + IOConfigReg16_17, + IOConfigReg18_19, + IOConfigReg20_21, + IOConfigReg22_23, + IOConfigReg24_25, + IOConfigReg26_27, + IOConfigReg28_29, + IOConfigReg30_31, + IOConfigReg32_33, + IOConfigReg34_35, + IOConfigReg36_37, + IOConfigReg38_39, + NumRegisters, +}; + +static inline unsigned IOConfigReg(unsigned pfi_channel) +{ + unsigned reg = IOConfigReg0_1 + pfi_channel / 2; + BUG_ON(reg > IOConfigReg38_39); + return reg; +} + +enum ni_660x_register_width { + DATA_1B, + DATA_2B, + DATA_4B +}; + +enum ni_660x_register_direction { + NI_660x_READ, + NI_660x_WRITE, + NI_660x_READ_WRITE +}; + +enum ni_660x_pfi_output_select { + pfi_output_select_high_Z = 0, + pfi_output_select_counter = 1, + pfi_output_select_do = 2, + num_pfi_output_selects +}; + +enum ni_660x_subdevices { + NI_660X_DIO_SUBDEV = 1, + NI_660X_GPCT_SUBDEV_0 = 2 +}; +static inline unsigned NI_660X_GPCT_SUBDEV(unsigned index) +{ + return NI_660X_GPCT_SUBDEV_0 + index; +} + +struct NI_660xRegisterData { + + const char *name; /* Register Name */ + int offset; /* Offset from base address from GPCT chip */ + enum ni_660x_register_direction direction; + enum ni_660x_register_width size; /* 1 byte, 2 bytes, or 4 bytes */ +}; + +static const struct NI_660xRegisterData registerData[NumRegisters] = { + {"G0 Interrupt Acknowledge", 0x004, NI_660x_WRITE, DATA_2B}, + {"G0 Status Register", 0x004, NI_660x_READ, DATA_2B}, + {"G1 Interrupt Acknowledge", 0x006, NI_660x_WRITE, DATA_2B}, + {"G1 Status Register", 0x006, NI_660x_READ, DATA_2B}, + {"G01 Status Register ", 0x008, NI_660x_READ, DATA_2B}, + {"G0 Command Register", 0x00C, NI_660x_WRITE, DATA_2B}, + {"STC DIO Parallel Input", 0x00E, NI_660x_READ, DATA_2B}, + {"G1 Command Register", 0x00E, NI_660x_WRITE, DATA_2B}, + {"G0 HW Save Register", 0x010, NI_660x_READ, DATA_4B}, + {"G1 HW Save Register", 0x014, NI_660x_READ, DATA_4B}, + {"STC DIO Output", 0x014, NI_660x_WRITE, DATA_2B}, + {"STC DIO Control", 0x016, NI_660x_WRITE, DATA_2B}, + {"G0 SW Save Register", 0x018, NI_660x_READ, DATA_4B}, + {"G1 SW Save Register", 0x01C, NI_660x_READ, DATA_4B}, + {"G0 Mode Register", 0x034, NI_660x_WRITE, DATA_2B}, + {"G01 Joint Status 1 Register", 0x036, NI_660x_READ, DATA_2B}, + {"G1 Mode Register", 0x036, NI_660x_WRITE, DATA_2B}, + {"STC DIO Serial Input", 0x038, NI_660x_READ, DATA_2B}, + {"G0 Load A Register", 0x038, NI_660x_WRITE, DATA_4B}, + {"G01 Joint Status 2 Register", 0x03A, NI_660x_READ, DATA_2B}, + {"G0 Load B Register", 0x03C, NI_660x_WRITE, DATA_4B}, + {"G1 Load A Register", 0x040, NI_660x_WRITE, DATA_4B}, + {"G1 Load B Register", 0x044, NI_660x_WRITE, DATA_4B}, + {"G0 Input Select Register", 0x048, NI_660x_WRITE, DATA_2B}, + {"G1 Input Select Register", 0x04A, NI_660x_WRITE, DATA_2B}, + {"G0 Autoincrement Register", 0x088, NI_660x_WRITE, DATA_2B}, + {"G1 Autoincrement Register", 0x08A, NI_660x_WRITE, DATA_2B}, + {"G01 Joint Reset Register", 0x090, NI_660x_WRITE, DATA_2B}, + {"G0 Interrupt Enable", 0x092, NI_660x_WRITE, DATA_2B}, + {"G1 Interrupt Enable", 0x096, NI_660x_WRITE, DATA_2B}, + {"G0 Counting Mode Register", 0x0B0, NI_660x_WRITE, DATA_2B}, + {"G1 Counting Mode Register", 0x0B2, NI_660x_WRITE, DATA_2B}, + {"G0 Second Gate Register", 0x0B4, NI_660x_WRITE, DATA_2B}, + {"G1 Second Gate Register", 0x0B6, NI_660x_WRITE, DATA_2B}, + {"G0 DMA Config Register", 0x0B8, NI_660x_WRITE, DATA_2B}, + {"G0 DMA Status Register", 0x0B8, NI_660x_READ, DATA_2B}, + {"G1 DMA Config Register", 0x0BA, NI_660x_WRITE, DATA_2B}, + {"G1 DMA Status Register", 0x0BA, NI_660x_READ, DATA_2B}, + {"G2 Interrupt Acknowledge", 0x104, NI_660x_WRITE, DATA_2B}, + {"G2 Status Register", 0x104, NI_660x_READ, DATA_2B}, + {"G3 Interrupt Acknowledge", 0x106, NI_660x_WRITE, DATA_2B}, + {"G3 Status Register", 0x106, NI_660x_READ, DATA_2B}, + {"G23 Status Register", 0x108, NI_660x_READ, DATA_2B}, + {"G2 Command Register", 0x10C, NI_660x_WRITE, DATA_2B}, + {"G3 Command Register", 0x10E, NI_660x_WRITE, DATA_2B}, + {"G2 HW Save Register", 0x110, NI_660x_READ, DATA_4B}, + {"G3 HW Save Register", 0x114, NI_660x_READ, DATA_4B}, + {"G2 SW Save Register", 0x118, NI_660x_READ, DATA_4B}, + {"G3 SW Save Register", 0x11C, NI_660x_READ, DATA_4B}, + {"G2 Mode Register", 0x134, NI_660x_WRITE, DATA_2B}, + {"G23 Joint Status 1 Register", 0x136, NI_660x_READ, DATA_2B}, + {"G3 Mode Register", 0x136, NI_660x_WRITE, DATA_2B}, + {"G2 Load A Register", 0x138, NI_660x_WRITE, DATA_4B}, + {"G23 Joint Status 2 Register", 0x13A, NI_660x_READ, DATA_2B}, + {"G2 Load B Register", 0x13C, NI_660x_WRITE, DATA_4B}, + {"G3 Load A Register", 0x140, NI_660x_WRITE, DATA_4B}, + {"G3 Load B Register", 0x144, NI_660x_WRITE, DATA_4B}, + {"G2 Input Select Register", 0x148, NI_660x_WRITE, DATA_2B}, + {"G3 Input Select Register", 0x14A, NI_660x_WRITE, DATA_2B}, + {"G2 Autoincrement Register", 0x188, NI_660x_WRITE, DATA_2B}, + {"G3 Autoincrement Register", 0x18A, NI_660x_WRITE, DATA_2B}, + {"G23 Joint Reset Register", 0x190, NI_660x_WRITE, DATA_2B}, + {"G2 Interrupt Enable", 0x192, NI_660x_WRITE, DATA_2B}, + {"G3 Interrupt Enable", 0x196, NI_660x_WRITE, DATA_2B}, + {"G2 Counting Mode Register", 0x1B0, NI_660x_WRITE, DATA_2B}, + {"G3 Counting Mode Register", 0x1B2, NI_660x_WRITE, DATA_2B}, + {"G3 Second Gate Register", 0x1B6, NI_660x_WRITE, DATA_2B}, + {"G2 Second Gate Register", 0x1B4, NI_660x_WRITE, DATA_2B}, + {"G2 DMA Config Register", 0x1B8, NI_660x_WRITE, DATA_2B}, + {"G2 DMA Status Register", 0x1B8, NI_660x_READ, DATA_2B}, + {"G3 DMA Config Register", 0x1BA, NI_660x_WRITE, DATA_2B}, + {"G3 DMA Status Register", 0x1BA, NI_660x_READ, DATA_2B}, + {"32 bit Digital Input", 0x414, NI_660x_READ, DATA_4B}, + {"32 bit Digital Output", 0x510, NI_660x_WRITE, DATA_4B}, + {"Clock Config Register", 0x73C, NI_660x_WRITE, DATA_4B}, + {"Global Interrupt Status Register", 0x754, NI_660x_READ, DATA_4B}, + {"DMA Configuration Register", 0x76C, NI_660x_WRITE, DATA_4B}, + {"Global Interrupt Config Register", 0x770, NI_660x_WRITE, DATA_4B}, + {"IO Config Register 0-1", 0x77C, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 2-3", 0x77E, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 4-5", 0x780, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 6-7", 0x782, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 8-9", 0x784, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 10-11", 0x786, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 12-13", 0x788, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 14-15", 0x78A, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 16-17", 0x78C, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 18-19", 0x78E, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 20-21", 0x790, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 22-23", 0x792, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 24-25", 0x794, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 26-27", 0x796, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 28-29", 0x798, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 30-31", 0x79A, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 32-33", 0x79C, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 34-35", 0x79E, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 36-37", 0x7A0, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 38-39", 0x7A2, NI_660x_READ_WRITE, DATA_2B} +}; + +/* kind of ENABLE for the second counter */ +enum clock_config_register_bits { + CounterSwap = 0x1 << 21 +}; + +/* ioconfigreg */ +static inline unsigned ioconfig_bitshift(unsigned pfi_channel) +{ + if (pfi_channel % 2) + return 0; + else + return 8; +} + +static inline unsigned pfi_output_select_mask(unsigned pfi_channel) +{ + return 0x3 << ioconfig_bitshift(pfi_channel); +} + +static inline unsigned pfi_output_select_bits(unsigned pfi_channel, + unsigned output_select) +{ + return (output_select & 0x3) << ioconfig_bitshift(pfi_channel); +} + +static inline unsigned pfi_input_select_mask(unsigned pfi_channel) +{ + return 0x7 << (4 + ioconfig_bitshift(pfi_channel)); +} + +static inline unsigned pfi_input_select_bits(unsigned pfi_channel, + unsigned input_select) +{ + return (input_select & 0x7) << (4 + ioconfig_bitshift(pfi_channel)); +} + +/* dma configuration register bits */ +static inline unsigned dma_select_mask(unsigned dma_channel) +{ + BUG_ON(dma_channel >= MAX_DMA_CHANNEL); + return 0x1f << (8 * dma_channel); +} + +enum dma_selection { + dma_selection_none = 0x1f, +}; +static inline unsigned dma_selection_counter(unsigned counter_index) +{ + BUG_ON(counter_index >= counters_per_chip); + return counter_index; +} + +static inline unsigned dma_select_bits(unsigned dma_channel, unsigned selection) +{ + BUG_ON(dma_channel >= MAX_DMA_CHANNEL); + return (selection << (8 * dma_channel)) & dma_select_mask(dma_channel); +} + +static inline unsigned dma_reset_bit(unsigned dma_channel) +{ + BUG_ON(dma_channel >= MAX_DMA_CHANNEL); + return 0x80 << (8 * dma_channel); +} + +enum global_interrupt_status_register_bits { + Counter_0_Int_Bit = 0x100, + Counter_1_Int_Bit = 0x200, + Counter_2_Int_Bit = 0x400, + Counter_3_Int_Bit = 0x800, + Cascade_Int_Bit = 0x20000000, + Global_Int_Bit = 0x80000000 +}; + +enum global_interrupt_config_register_bits { + Cascade_Int_Enable_Bit = 0x20000000, + Global_Int_Polarity_Bit = 0x40000000, + Global_Int_Enable_Bit = 0x80000000 +}; + +/* Offset of the GPCT chips from the base-address of the card */ +/* First chip is at base-address + 0x00, etc. */ +static const unsigned GPCT_OFFSET[2] = { 0x0, 0x800 }; + +/* Board description*/ +struct ni_660x_board { + unsigned short dev_id; /* `lspci` will show you this */ + const char *name; + unsigned n_chips; /* total number of TIO chips */ +}; + +static const struct ni_660x_board ni_660x_boards[] = { + { + .dev_id = 0x2c60, + .name = "PCI-6601", + .n_chips = 1, + }, + { + .dev_id = 0x1310, + .name = "PCI-6602", + .n_chips = 2, + }, + { + .dev_id = 0x1360, + .name = "PXI-6602", + .n_chips = 2, + }, + { + .dev_id = 0x2cc0, + .name = "PXI-6608", + .n_chips = 2, + }, +}; + +#define NI_660X_MAX_NUM_CHIPS 2 +#define NI_660X_MAX_NUM_COUNTERS (NI_660X_MAX_NUM_CHIPS * counters_per_chip) + +static DEFINE_PCI_DEVICE_TABLE(ni_660x_pci_table) = { + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2c60)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1310)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1360)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2cc0)}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, ni_660x_pci_table); + +struct ni_660x_private { + struct mite_struct *mite; + struct ni_gpct_device *counter_dev; + uint64_t pfi_direction_bits; + struct mite_dma_descriptor_ring + *mite_rings[NI_660X_MAX_NUM_CHIPS][counters_per_chip]; + spinlock_t mite_channel_lock; + /* interrupt_lock prevents races between interrupt and comedi_poll */ + spinlock_t interrupt_lock; + unsigned dma_configuration_soft_copies[NI_660X_MAX_NUM_CHIPS]; + spinlock_t soft_reg_copy_lock; + unsigned short pfi_output_selects[NUM_PFI_CHANNELS]; +}; + +static inline struct ni_660x_private *private(struct comedi_device *dev) +{ + return dev->private; +} + +/* initialized in ni_660x_find_device() */ +static inline const struct ni_660x_board *board(struct comedi_device *dev) +{ + return dev->board_ptr; +} + +#define n_ni_660x_boards ARRAY_SIZE(ni_660x_boards) + +static int ni_660x_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int ni_660x_detach(struct comedi_device *dev); +static void init_tio_chip(struct comedi_device *dev, int chipset); +static void ni_660x_select_pfi_output(struct comedi_device *dev, + unsigned pfi_channel, + unsigned output_select); + +static struct comedi_driver driver_ni_660x = { + .driver_name = "ni_660x", + .module = THIS_MODULE, + .attach = ni_660x_attach, + .detach = ni_660x_detach, +}; + +static int __devinit driver_ni_660x_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_ni_660x.driver_name); +} + +static void __devexit driver_ni_660x_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_ni_660x_pci_driver = { + .id_table = ni_660x_pci_table, + .probe = &driver_ni_660x_pci_probe, + .remove = __devexit_p(&driver_ni_660x_pci_remove) +}; + +static int __init driver_ni_660x_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_ni_660x); + if (retval < 0) + return retval; + + driver_ni_660x_pci_driver.name = (char *)driver_ni_660x.driver_name; + return pci_register_driver(&driver_ni_660x_pci_driver); +} + +static void __exit driver_ni_660x_cleanup_module(void) +{ + pci_unregister_driver(&driver_ni_660x_pci_driver); + comedi_driver_unregister(&driver_ni_660x); +} + +module_init(driver_ni_660x_init_module); +module_exit(driver_ni_660x_cleanup_module); + +static int ni_660x_find_device(struct comedi_device *dev, int bus, int slot); +static int ni_660x_set_pfi_routing(struct comedi_device *dev, unsigned chan, + unsigned source); + +/* Possible instructions for a GPCT */ +static int ni_660x_GPCT_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_660x_GPCT_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int ni_660x_GPCT_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* Possible instructions for Digital IO */ +static int ni_660x_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static int ni_660x_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static inline unsigned ni_660x_num_counters(struct comedi_device *dev) +{ + return board(dev)->n_chips * counters_per_chip; +} + +static enum NI_660x_Register ni_gpct_to_660x_register(enum ni_gpct_register reg) +{ + enum NI_660x_Register ni_660x_register; + switch (reg) { + case NITIO_G0_Autoincrement_Reg: + ni_660x_register = G0AutoincrementRegister; + break; + case NITIO_G1_Autoincrement_Reg: + ni_660x_register = G1AutoincrementRegister; + break; + case NITIO_G2_Autoincrement_Reg: + ni_660x_register = G2AutoincrementRegister; + break; + case NITIO_G3_Autoincrement_Reg: + ni_660x_register = G3AutoincrementRegister; + break; + case NITIO_G0_Command_Reg: + ni_660x_register = G0CommandRegister; + break; + case NITIO_G1_Command_Reg: + ni_660x_register = G1CommandRegister; + break; + case NITIO_G2_Command_Reg: + ni_660x_register = G2CommandRegister; + break; + case NITIO_G3_Command_Reg: + ni_660x_register = G3CommandRegister; + break; + case NITIO_G0_HW_Save_Reg: + ni_660x_register = G0HWSaveRegister; + break; + case NITIO_G1_HW_Save_Reg: + ni_660x_register = G1HWSaveRegister; + break; + case NITIO_G2_HW_Save_Reg: + ni_660x_register = G2HWSaveRegister; + break; + case NITIO_G3_HW_Save_Reg: + ni_660x_register = G3HWSaveRegister; + break; + case NITIO_G0_SW_Save_Reg: + ni_660x_register = G0SWSaveRegister; + break; + case NITIO_G1_SW_Save_Reg: + ni_660x_register = G1SWSaveRegister; + break; + case NITIO_G2_SW_Save_Reg: + ni_660x_register = G2SWSaveRegister; + break; + case NITIO_G3_SW_Save_Reg: + ni_660x_register = G3SWSaveRegister; + break; + case NITIO_G0_Mode_Reg: + ni_660x_register = G0ModeRegister; + break; + case NITIO_G1_Mode_Reg: + ni_660x_register = G1ModeRegister; + break; + case NITIO_G2_Mode_Reg: + ni_660x_register = G2ModeRegister; + break; + case NITIO_G3_Mode_Reg: + ni_660x_register = G3ModeRegister; + break; + case NITIO_G0_LoadA_Reg: + ni_660x_register = G0LoadARegister; + break; + case NITIO_G1_LoadA_Reg: + ni_660x_register = G1LoadARegister; + break; + case NITIO_G2_LoadA_Reg: + ni_660x_register = G2LoadARegister; + break; + case NITIO_G3_LoadA_Reg: + ni_660x_register = G3LoadARegister; + break; + case NITIO_G0_LoadB_Reg: + ni_660x_register = G0LoadBRegister; + break; + case NITIO_G1_LoadB_Reg: + ni_660x_register = G1LoadBRegister; + break; + case NITIO_G2_LoadB_Reg: + ni_660x_register = G2LoadBRegister; + break; + case NITIO_G3_LoadB_Reg: + ni_660x_register = G3LoadBRegister; + break; + case NITIO_G0_Input_Select_Reg: + ni_660x_register = G0InputSelectRegister; + break; + case NITIO_G1_Input_Select_Reg: + ni_660x_register = G1InputSelectRegister; + break; + case NITIO_G2_Input_Select_Reg: + ni_660x_register = G2InputSelectRegister; + break; + case NITIO_G3_Input_Select_Reg: + ni_660x_register = G3InputSelectRegister; + break; + case NITIO_G01_Status_Reg: + ni_660x_register = G01StatusRegister; + break; + case NITIO_G23_Status_Reg: + ni_660x_register = G23StatusRegister; + break; + case NITIO_G01_Joint_Reset_Reg: + ni_660x_register = G01JointResetRegister; + break; + case NITIO_G23_Joint_Reset_Reg: + ni_660x_register = G23JointResetRegister; + break; + case NITIO_G01_Joint_Status1_Reg: + ni_660x_register = G01JointStatus1Register; + break; + case NITIO_G23_Joint_Status1_Reg: + ni_660x_register = G23JointStatus1Register; + break; + case NITIO_G01_Joint_Status2_Reg: + ni_660x_register = G01JointStatus2Register; + break; + case NITIO_G23_Joint_Status2_Reg: + ni_660x_register = G23JointStatus2Register; + break; + case NITIO_G0_Counting_Mode_Reg: + ni_660x_register = G0CountingModeRegister; + break; + case NITIO_G1_Counting_Mode_Reg: + ni_660x_register = G1CountingModeRegister; + break; + case NITIO_G2_Counting_Mode_Reg: + ni_660x_register = G2CountingModeRegister; + break; + case NITIO_G3_Counting_Mode_Reg: + ni_660x_register = G3CountingModeRegister; + break; + case NITIO_G0_Second_Gate_Reg: + ni_660x_register = G0SecondGateRegister; + break; + case NITIO_G1_Second_Gate_Reg: + ni_660x_register = G1SecondGateRegister; + break; + case NITIO_G2_Second_Gate_Reg: + ni_660x_register = G2SecondGateRegister; + break; + case NITIO_G3_Second_Gate_Reg: + ni_660x_register = G3SecondGateRegister; + break; + case NITIO_G0_DMA_Config_Reg: + ni_660x_register = G0DMAConfigRegister; + break; + case NITIO_G0_DMA_Status_Reg: + ni_660x_register = G0DMAStatusRegister; + break; + case NITIO_G1_DMA_Config_Reg: + ni_660x_register = G1DMAConfigRegister; + break; + case NITIO_G1_DMA_Status_Reg: + ni_660x_register = G1DMAStatusRegister; + break; + case NITIO_G2_DMA_Config_Reg: + ni_660x_register = G2DMAConfigRegister; + break; + case NITIO_G2_DMA_Status_Reg: + ni_660x_register = G2DMAStatusRegister; + break; + case NITIO_G3_DMA_Config_Reg: + ni_660x_register = G3DMAConfigRegister; + break; + case NITIO_G3_DMA_Status_Reg: + ni_660x_register = G3DMAStatusRegister; + break; + case NITIO_G0_Interrupt_Acknowledge_Reg: + ni_660x_register = G0InterruptAcknowledge; + break; + case NITIO_G1_Interrupt_Acknowledge_Reg: + ni_660x_register = G1InterruptAcknowledge; + break; + case NITIO_G2_Interrupt_Acknowledge_Reg: + ni_660x_register = G2InterruptAcknowledge; + break; + case NITIO_G3_Interrupt_Acknowledge_Reg: + ni_660x_register = G3InterruptAcknowledge; + break; + case NITIO_G0_Status_Reg: + ni_660x_register = G0StatusRegister; + break; + case NITIO_G1_Status_Reg: + ni_660x_register = G1StatusRegister; + break; + case NITIO_G2_Status_Reg: + ni_660x_register = G2StatusRegister; + break; + case NITIO_G3_Status_Reg: + ni_660x_register = G3StatusRegister; + break; + case NITIO_G0_Interrupt_Enable_Reg: + ni_660x_register = G0InterruptEnable; + break; + case NITIO_G1_Interrupt_Enable_Reg: + ni_660x_register = G1InterruptEnable; + break; + case NITIO_G2_Interrupt_Enable_Reg: + ni_660x_register = G2InterruptEnable; + break; + case NITIO_G3_Interrupt_Enable_Reg: + ni_660x_register = G3InterruptEnable; + break; + default: + printk(KERN_WARNING "%s: unhandled register 0x%x in switch.\n", + __func__, reg); + BUG(); + return 0; + break; + } + return ni_660x_register; +} + +static inline void ni_660x_write_register(struct comedi_device *dev, + unsigned chip_index, unsigned bits, + enum NI_660x_Register reg) +{ + void *const write_address = + private(dev)->mite->daq_io_addr + GPCT_OFFSET[chip_index] + + registerData[reg].offset; + + switch (registerData[reg].size) { + case DATA_2B: + writew(bits, write_address); + break; + case DATA_4B: + writel(bits, write_address); + break; + default: + printk(KERN_WARNING "%s: %s: bug! unhandled case (reg=0x%x) in switch.\n", + __FILE__, __func__, reg); + BUG(); + break; + } +} + +static inline unsigned ni_660x_read_register(struct comedi_device *dev, + unsigned chip_index, + enum NI_660x_Register reg) +{ + void *const read_address = + private(dev)->mite->daq_io_addr + GPCT_OFFSET[chip_index] + + registerData[reg].offset; + + switch (registerData[reg].size) { + case DATA_2B: + return readw(read_address); + break; + case DATA_4B: + return readl(read_address); + break; + default: + printk(KERN_WARNING "%s: %s: bug! unhandled case (reg=0x%x) in switch.\n", + __FILE__, __func__, reg); + BUG(); + break; + } + return 0; +} + +static void ni_gpct_write_register(struct ni_gpct *counter, unsigned bits, + enum ni_gpct_register reg) +{ + struct comedi_device *dev = counter->counter_dev->dev; + enum NI_660x_Register ni_660x_register = ni_gpct_to_660x_register(reg); + ni_660x_write_register(dev, counter->chip_index, bits, + ni_660x_register); +} + +static unsigned ni_gpct_read_register(struct ni_gpct *counter, + enum ni_gpct_register reg) +{ + struct comedi_device *dev = counter->counter_dev->dev; + enum NI_660x_Register ni_660x_register = ni_gpct_to_660x_register(reg); + return ni_660x_read_register(dev, counter->chip_index, + ni_660x_register); +} + +static inline struct mite_dma_descriptor_ring *mite_ring(struct ni_660x_private + *priv, + struct ni_gpct + *counter) +{ + return priv->mite_rings[counter->chip_index][counter->counter_index]; +} + +static inline void ni_660x_set_dma_channel(struct comedi_device *dev, + unsigned mite_channel, + struct ni_gpct *counter) +{ + unsigned long flags; + spin_lock_irqsave(&private(dev)->soft_reg_copy_lock, flags); + private(dev)->dma_configuration_soft_copies[counter->chip_index] &= + ~dma_select_mask(mite_channel); + private(dev)->dma_configuration_soft_copies[counter->chip_index] |= + dma_select_bits(mite_channel, + dma_selection_counter(counter->counter_index)); + ni_660x_write_register(dev, counter->chip_index, + private(dev)-> + dma_configuration_soft_copies + [counter->chip_index] | + dma_reset_bit(mite_channel), DMAConfigRegister); + mmiowb(); + spin_unlock_irqrestore(&private(dev)->soft_reg_copy_lock, flags); +} + +static inline void ni_660x_unset_dma_channel(struct comedi_device *dev, + unsigned mite_channel, + struct ni_gpct *counter) +{ + unsigned long flags; + spin_lock_irqsave(&private(dev)->soft_reg_copy_lock, flags); + private(dev)->dma_configuration_soft_copies[counter->chip_index] &= + ~dma_select_mask(mite_channel); + private(dev)->dma_configuration_soft_copies[counter->chip_index] |= + dma_select_bits(mite_channel, dma_selection_none); + ni_660x_write_register(dev, counter->chip_index, + private(dev)-> + dma_configuration_soft_copies + [counter->chip_index], DMAConfigRegister); + mmiowb(); + spin_unlock_irqrestore(&private(dev)->soft_reg_copy_lock, flags); +} + +static int ni_660x_request_mite_channel(struct comedi_device *dev, + struct ni_gpct *counter, + enum comedi_io_direction direction) +{ + unsigned long flags; + struct mite_channel *mite_chan; + + spin_lock_irqsave(&private(dev)->mite_channel_lock, flags); + BUG_ON(counter->mite_chan); + mite_chan = + mite_request_channel(private(dev)->mite, mite_ring(private(dev), + counter)); + if (mite_chan == NULL) { + spin_unlock_irqrestore(&private(dev)->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for counter."); + return -EBUSY; + } + mite_chan->dir = direction; + ni_tio_set_mite_channel(counter, mite_chan); + ni_660x_set_dma_channel(dev, mite_chan->channel, counter); + spin_unlock_irqrestore(&private(dev)->mite_channel_lock, flags); + return 0; +} + +void ni_660x_release_mite_channel(struct comedi_device *dev, + struct ni_gpct *counter) +{ + unsigned long flags; + + spin_lock_irqsave(&private(dev)->mite_channel_lock, flags); + if (counter->mite_chan) { + struct mite_channel *mite_chan = counter->mite_chan; + + ni_660x_unset_dma_channel(dev, mite_chan->channel, counter); + ni_tio_set_mite_channel(counter, NULL); + mite_release_channel(mite_chan); + } + spin_unlock_irqrestore(&private(dev)->mite_channel_lock, flags); +} + +static int ni_660x_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int retval; + + struct ni_gpct *counter = subdev_to_counter(s); +/* const struct comedi_cmd *cmd = &s->async->cmd; */ + + retval = ni_660x_request_mite_channel(dev, counter, COMEDI_INPUT); + if (retval) { + comedi_error(dev, + "no dma channel available for use by counter"); + return retval; + } + ni_tio_acknowledge_and_confirm(counter, NULL, NULL, NULL, NULL); + retval = ni_tio_cmd(counter, s->async); + + return retval; +} + +static int ni_660x_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + struct ni_gpct *counter = subdev_to_counter(s); + + return ni_tio_cmdtest(counter, cmd); +} + +static int ni_660x_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_gpct *counter = subdev_to_counter(s); + int retval; + + retval = ni_tio_cancel(counter); + ni_660x_release_mite_channel(dev, counter); + return retval; +} + +static void set_tio_counterswap(struct comedi_device *dev, int chipset) +{ + /* See P. 3.5 of the Register-Level Programming manual. The + CounterSwap bit has to be set on the second chip, otherwise + it will try to use the same pins as the first chip. + */ + if (chipset) + ni_660x_write_register(dev, chipset, CounterSwap, + ClockConfigRegister); + else + ni_660x_write_register(dev, chipset, 0, ClockConfigRegister); +} + +static void ni_660x_handle_gpct_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + ni_tio_handle_interrupt(subdev_to_counter(s), s); + if (s->async->events) { + if (s->async->events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW)) { + ni_660x_cancel(dev, s); + } + comedi_event(dev, s); + } +} + +static irqreturn_t ni_660x_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s; + unsigned i; + unsigned long flags; + + if (dev->attached == 0) + return IRQ_NONE; + /* lock to avoid race with comedi_poll */ + spin_lock_irqsave(&private(dev)->interrupt_lock, flags); + smp_mb(); + for (i = 0; i < ni_660x_num_counters(dev); ++i) { + s = dev->subdevices + NI_660X_GPCT_SUBDEV(i); + ni_660x_handle_gpct_interrupt(dev, s); + } + spin_unlock_irqrestore(&private(dev)->interrupt_lock, flags); + return IRQ_HANDLED; +} + +static int ni_660x_input_poll(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned long flags; + /* lock to avoid race with comedi_poll */ + spin_lock_irqsave(&private(dev)->interrupt_lock, flags); + mite_sync_input_dma(subdev_to_counter(s)->mite_chan, s->async); + spin_unlock_irqrestore(&private(dev)->interrupt_lock, flags); + return comedi_buf_read_n_available(s->async); +} + +static int ni_660x_buf_change(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned long new_size) +{ + int ret; + + ret = mite_buf_change(mite_ring(private(dev), subdev_to_counter(s)), + s->async); + if (ret < 0) + return ret; + + return 0; +} + +static int ni_660x_allocate_private(struct comedi_device *dev) +{ + int retval; + unsigned i; + + retval = alloc_private(dev, sizeof(struct ni_660x_private)); + if (retval < 0) + return retval; + + spin_lock_init(&private(dev)->mite_channel_lock); + spin_lock_init(&private(dev)->interrupt_lock); + spin_lock_init(&private(dev)->soft_reg_copy_lock); + for (i = 0; i < NUM_PFI_CHANNELS; ++i) + private(dev)->pfi_output_selects[i] = pfi_output_select_counter; + + return 0; +} + +static int ni_660x_alloc_mite_rings(struct comedi_device *dev) +{ + unsigned i; + unsigned j; + + for (i = 0; i < board(dev)->n_chips; ++i) { + for (j = 0; j < counters_per_chip; ++j) { + private(dev)->mite_rings[i][j] = + mite_alloc_ring(private(dev)->mite); + if (private(dev)->mite_rings[i][j] == NULL) + return -ENOMEM; + } + } + return 0; +} + +static void ni_660x_free_mite_rings(struct comedi_device *dev) +{ + unsigned i; + unsigned j; + + for (i = 0; i < board(dev)->n_chips; ++i) { + for (j = 0; j < counters_per_chip; ++j) + mite_free_ring(private(dev)->mite_rings[i][j]); + } +} + +static int ni_660x_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + unsigned i; + unsigned global_interrupt_config_bits; + + printk(KERN_INFO "comedi%d: ni_660x: ", dev->minor); + + ret = ni_660x_allocate_private(dev); + if (ret < 0) + return ret; + ret = ni_660x_find_device(dev, it->options[0], it->options[1]); + if (ret < 0) + return ret; + + dev->board_name = board(dev)->name; + + ret = mite_setup2(private(dev)->mite, 1); + if (ret < 0) { + printk(KERN_WARNING "error setting up mite\n"); + return ret; + } + comedi_set_hw_dev(dev, &private(dev)->mite->pcidev->dev); + ret = ni_660x_alloc_mite_rings(dev); + if (ret < 0) + return ret; + + printk(KERN_INFO " %s ", dev->board_name); + + dev->n_subdevices = 2 + NI_660X_MAX_NUM_COUNTERS; + + if (alloc_subdevices(dev, dev->n_subdevices) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* Old GENERAL-PURPOSE COUNTER/TIME (GPCT) subdevice, no longer used */ + s->type = COMEDI_SUBD_UNUSED; + + s = dev->subdevices + NI_660X_DIO_SUBDEV; + /* DIGITAL I/O SUBDEVICE */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = NUM_PFI_CHANNELS; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ni_660x_dio_insn_bits; + s->insn_config = ni_660x_dio_insn_config; + s->io_bits = 0; /* all bits default to input */ + /* we use the ioconfig registers to control dio direction, so zero + output enables in stc dio control reg */ + ni_660x_write_register(dev, 0, 0, STCDIOControl); + + private(dev)->counter_dev = ni_gpct_device_construct(dev, + &ni_gpct_write_register, + &ni_gpct_read_register, + ni_gpct_variant_660x, + ni_660x_num_counters + (dev)); + if (private(dev)->counter_dev == NULL) + return -ENOMEM; + for (i = 0; i < NI_660X_MAX_NUM_COUNTERS; ++i) { + s = dev->subdevices + NI_660X_GPCT_SUBDEV(i); + if (i < ni_660x_num_counters(dev)) { + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = + SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL | + SDF_CMD_READ /* | SDF_CMD_WRITE */ ; + s->n_chan = 3; + s->maxdata = 0xffffffff; + s->insn_read = ni_660x_GPCT_rinsn; + s->insn_write = ni_660x_GPCT_winsn; + s->insn_config = ni_660x_GPCT_insn_config; + s->do_cmd = &ni_660x_cmd; + s->len_chanlist = 1; + s->do_cmdtest = &ni_660x_cmdtest; + s->cancel = &ni_660x_cancel; + s->poll = &ni_660x_input_poll; + s->async_dma_dir = DMA_BIDIRECTIONAL; + s->buf_change = &ni_660x_buf_change; + s->private = &private(dev)->counter_dev->counters[i]; + + private(dev)->counter_dev->counters[i].chip_index = + i / counters_per_chip; + private(dev)->counter_dev->counters[i].counter_index = + i % counters_per_chip; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + } + for (i = 0; i < board(dev)->n_chips; ++i) + init_tio_chip(dev, i); + + for (i = 0; i < ni_660x_num_counters(dev); ++i) + ni_tio_init_counter(&private(dev)->counter_dev->counters[i]); + + for (i = 0; i < NUM_PFI_CHANNELS; ++i) { + if (i < min_counter_pfi_chan) + ni_660x_set_pfi_routing(dev, i, pfi_output_select_do); + else + ni_660x_set_pfi_routing(dev, i, + pfi_output_select_counter); + ni_660x_select_pfi_output(dev, i, pfi_output_select_high_Z); + } + /* to be safe, set counterswap bits on tio chips after all the counter + outputs have been set to high impedance mode */ + for (i = 0; i < board(dev)->n_chips; ++i) + set_tio_counterswap(dev, i); + + ret = request_irq(mite_irq(private(dev)->mite), ni_660x_interrupt, + IRQF_SHARED, "ni_660x", dev); + if (ret < 0) { + printk(KERN_WARNING " irq not available\n"); + return ret; + } + dev->irq = mite_irq(private(dev)->mite); + global_interrupt_config_bits = Global_Int_Enable_Bit; + if (board(dev)->n_chips > 1) + global_interrupt_config_bits |= Cascade_Int_Enable_Bit; + ni_660x_write_register(dev, 0, global_interrupt_config_bits, + GlobalInterruptConfigRegister); + printk(KERN_INFO "attached\n"); + return 0; +} + +static int ni_660x_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: ni_660x: remove\n", dev->minor); + + /* Free irq */ + if (dev->irq) + free_irq(dev->irq, dev); + + if (dev->private) { + if (private(dev)->counter_dev) + ni_gpct_device_destroy(private(dev)->counter_dev); + if (private(dev)->mite) { + ni_660x_free_mite_rings(dev); + mite_unsetup(private(dev)->mite); + } + } + return 0; +} + +static int +ni_660x_GPCT_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + return ni_tio_rinsn(subdev_to_counter(s), insn, data); +} + +static void init_tio_chip(struct comedi_device *dev, int chipset) +{ + unsigned i; + + /* init dma configuration register */ + private(dev)->dma_configuration_soft_copies[chipset] = 0; + for (i = 0; i < MAX_DMA_CHANNEL; ++i) { + private(dev)->dma_configuration_soft_copies[chipset] |= + dma_select_bits(i, dma_selection_none) & dma_select_mask(i); + } + ni_660x_write_register(dev, chipset, + private(dev)-> + dma_configuration_soft_copies[chipset], + DMAConfigRegister); + for (i = 0; i < NUM_PFI_CHANNELS; ++i) + ni_660x_write_register(dev, chipset, 0, IOConfigReg(i)); +} + +static int +ni_660x_GPCT_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + return ni_tio_insn_config(subdev_to_counter(s), insn, data); +} + +static int ni_660x_GPCT_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + return ni_tio_winsn(subdev_to_counter(s), insn, data); +} + +static int ni_660x_find_device(struct comedi_device *dev, int bus, int slot) +{ + struct mite_struct *mite; + int i; + + for (mite = mite_devices; mite; mite = mite->next) { + if (mite->used) + continue; + if (bus || slot) { + if (bus != mite->pcidev->bus->number || + slot != PCI_SLOT(mite->pcidev->devfn)) + continue; + } + + for (i = 0; i < n_ni_660x_boards; i++) { + if (mite_device_id(mite) == ni_660x_boards[i].dev_id) { + dev->board_ptr = ni_660x_boards + i; + private(dev)->mite = mite; + return 0; + } + } + } + printk(KERN_WARNING "no device found\n"); + mite_list_devices(); + return -EIO; +} + +static int ni_660x_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned base_bitfield_channel = CR_CHAN(insn->chanspec); + + /* Check if we have to write some bits */ + if (data[0]) { + s->state &= ~(data[0] << base_bitfield_channel); + s->state |= (data[0] & data[1]) << base_bitfield_channel; + /* Write out the new digital output lines */ + ni_660x_write_register(dev, 0, s->state, DIO32Output); + } + /* on return, data[1] contains the value of the digital + * input and output lines. */ + data[1] = + (ni_660x_read_register(dev, 0, + DIO32Input) >> base_bitfield_channel); + return 2; +} + +static void ni_660x_select_pfi_output(struct comedi_device *dev, + unsigned pfi_channel, + unsigned output_select) +{ + static const unsigned counter_4_7_first_pfi = 8; + static const unsigned counter_4_7_last_pfi = 23; + unsigned active_chipset = 0; + unsigned idle_chipset = 0; + unsigned active_bits; + unsigned idle_bits; + + if (board(dev)->n_chips > 1) { + if (output_select == pfi_output_select_counter && + pfi_channel >= counter_4_7_first_pfi && + pfi_channel <= counter_4_7_last_pfi) { + active_chipset = 1; + idle_chipset = 0; + } else { + active_chipset = 0; + idle_chipset = 1; + } + } + + if (idle_chipset != active_chipset) { + idle_bits = + ni_660x_read_register(dev, idle_chipset, + IOConfigReg(pfi_channel)); + idle_bits &= ~pfi_output_select_mask(pfi_channel); + idle_bits |= + pfi_output_select_bits(pfi_channel, + pfi_output_select_high_Z); + ni_660x_write_register(dev, idle_chipset, idle_bits, + IOConfigReg(pfi_channel)); + } + + active_bits = + ni_660x_read_register(dev, active_chipset, + IOConfigReg(pfi_channel)); + active_bits &= ~pfi_output_select_mask(pfi_channel); + active_bits |= pfi_output_select_bits(pfi_channel, output_select); + ni_660x_write_register(dev, active_chipset, active_bits, + IOConfigReg(pfi_channel)); +} + +static int ni_660x_set_pfi_routing(struct comedi_device *dev, unsigned chan, + unsigned source) +{ + if (source > num_pfi_output_selects) + return -EINVAL; + if (source == pfi_output_select_high_Z) + return -EINVAL; + if (chan < min_counter_pfi_chan) { + if (source == pfi_output_select_counter) + return -EINVAL; + } else if (chan > max_dio_pfi_chan) { + if (source == pfi_output_select_do) + return -EINVAL; + } + BUG_ON(chan >= NUM_PFI_CHANNELS); + + private(dev)->pfi_output_selects[chan] = source; + if (private(dev)->pfi_direction_bits & (((uint64_t) 1) << chan)) + ni_660x_select_pfi_output(dev, chan, + private(dev)-> + pfi_output_selects[chan]); + return 0; +} + +static unsigned ni_660x_get_pfi_routing(struct comedi_device *dev, + unsigned chan) +{ + BUG_ON(chan >= NUM_PFI_CHANNELS); + return private(dev)->pfi_output_selects[chan]; +} + +static void ni660x_config_filter(struct comedi_device *dev, + unsigned pfi_channel, + enum ni_gpct_filter_select filter) +{ + unsigned bits = ni_660x_read_register(dev, 0, IOConfigReg(pfi_channel)); + bits &= ~pfi_input_select_mask(pfi_channel); + bits |= pfi_input_select_bits(pfi_channel, filter); + ni_660x_write_register(dev, 0, bits, IOConfigReg(pfi_channel)); +} + +static int ni_660x_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + private(dev)->pfi_direction_bits |= ((uint64_t) 1) << chan; + ni_660x_select_pfi_output(dev, chan, + private(dev)-> + pfi_output_selects[chan]); + break; + case INSN_CONFIG_DIO_INPUT: + private(dev)->pfi_direction_bits &= ~(((uint64_t) 1) << chan); + ni_660x_select_pfi_output(dev, chan, pfi_output_select_high_Z); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (private(dev)->pfi_direction_bits & + (((uint64_t) 1) << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return 0; + case INSN_CONFIG_SET_ROUTING: + return ni_660x_set_pfi_routing(dev, chan, data[1]); + break; + case INSN_CONFIG_GET_ROUTING: + data[1] = ni_660x_get_pfi_routing(dev, chan); + break; + case INSN_CONFIG_FILTER: + ni660x_config_filter(dev, chan, data[1]); + break; + default: + return -EINVAL; + break; + } + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_670x.c b/drivers/staging/comedi/drivers/ni_670x.c new file mode 100644 index 00000000..d8d91f90 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_670x.c @@ -0,0 +1,390 @@ +/* + comedi/drivers/ni_670x.c + Hardware driver for NI 670x devices + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: ni_670x +Description: National Instruments 670x +Author: Bart Joris +Updated: Wed, 11 Dec 2002 18:25:35 -0800 +Devices: [National Instruments] PCI-6703 (ni_670x), PCI-6704 +Status: unknown + +Commands are not supported. +*/ + +/* + Bart Joris Last updated on 20/08/2001 + + Manuals: + + 322110a.pdf PCI/PXI-6704 User Manual + 322110b.pdf PCI/PXI-6703/6704 User Manual + +*/ + +#include +#include +#include "../comedidev.h" + +#include "mite.h" + +#define AO_VALUE_OFFSET 0x00 +#define AO_CHAN_OFFSET 0x0c +#define AO_STATUS_OFFSET 0x10 +#define AO_CONTROL_OFFSET 0x10 +#define DIO_PORT0_DIR_OFFSET 0x20 +#define DIO_PORT0_DATA_OFFSET 0x24 +#define DIO_PORT1_DIR_OFFSET 0x28 +#define DIO_PORT1_DATA_OFFSET 0x2c +#define MISC_STATUS_OFFSET 0x14 +#define MISC_CONTROL_OFFSET 0x14 + +/* Board description*/ + +struct ni_670x_board { + unsigned short dev_id; + const char *name; + unsigned short ao_chans; + unsigned short ao_bits; +}; + +static const struct ni_670x_board ni_670x_boards[] = { + { + .dev_id = 0x2c90, + .name = "PCI-6703", + .ao_chans = 16, + .ao_bits = 16, + }, + { + .dev_id = 0x1920, + .name = "PXI-6704", + .ao_chans = 32, + .ao_bits = 16, + }, + { + .dev_id = 0x1290, + .name = "PCI-6704", + .ao_chans = 32, + .ao_bits = 16, + }, +}; + +static DEFINE_PCI_DEVICE_TABLE(ni_670x_pci_table) = { + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2c90)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1920)}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, ni_670x_pci_table); + +#define thisboard ((struct ni_670x_board *)dev->board_ptr) + +struct ni_670x_private { + + struct mite_struct *mite; + int boardtype; + int dio; + unsigned int ao_readback[32]; +}; + +#define devpriv ((struct ni_670x_private *)dev->private) +#define n_ni_670x_boards ARRAY_SIZE(ni_670x_boards) + +static int ni_670x_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int ni_670x_detach(struct comedi_device *dev); + +static struct comedi_driver driver_ni_670x = { + .driver_name = "ni_670x", + .module = THIS_MODULE, + .attach = ni_670x_attach, + .detach = ni_670x_detach, +}; + +static int __devinit driver_ni_670x_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_ni_670x.driver_name); +} + +static void __devexit driver_ni_670x_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_ni_670x_pci_driver = { + .id_table = ni_670x_pci_table, + .probe = &driver_ni_670x_pci_probe, + .remove = __devexit_p(&driver_ni_670x_pci_remove) +}; + +static int __init driver_ni_670x_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_ni_670x); + if (retval < 0) + return retval; + + driver_ni_670x_pci_driver.name = (char *)driver_ni_670x.driver_name; + return pci_register_driver(&driver_ni_670x_pci_driver); +} + +static void __exit driver_ni_670x_cleanup_module(void) +{ + pci_unregister_driver(&driver_ni_670x_pci_driver); + comedi_driver_unregister(&driver_ni_670x); +} + +module_init(driver_ni_670x_init_module); +module_exit(driver_ni_670x_cleanup_module); + +static struct comedi_lrange range_0_20mA = { 1, {RANGE_mA(0, 20)} }; + +static int ni_670x_find_device(struct comedi_device *dev, int bus, int slot); + +static int ni_670x_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_670x_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_670x_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_670x_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int ni_670x_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int ret; + int i; + + printk(KERN_INFO "comedi%d: ni_670x: ", dev->minor); + + ret = alloc_private(dev, sizeof(struct ni_670x_private)); + if (ret < 0) + return ret; + + ret = ni_670x_find_device(dev, it->options[0], it->options[1]); + if (ret < 0) + return ret; + + ret = mite_setup(devpriv->mite); + if (ret < 0) { + printk(KERN_WARNING "error setting up mite\n"); + return ret; + } + dev->board_name = thisboard->name; + dev->irq = mite_irq(devpriv->mite); + printk(KERN_INFO " %s", dev->board_name); + + if (alloc_subdevices(dev, 2) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = thisboard->ao_chans; + s->maxdata = 0xffff; + if (s->n_chan == 32) { + const struct comedi_lrange **range_table_list; + + range_table_list = kmalloc(sizeof(struct comedi_lrange *) * 32, + GFP_KERNEL); + if (!range_table_list) + return -ENOMEM; + s->range_table_list = range_table_list; + for (i = 0; i < 16; i++) { + range_table_list[i] = &range_bipolar10; + range_table_list[16 + i] = &range_0_20mA; + } + } else { + s->range_table = &range_bipolar10; + } + s->insn_write = &ni_670x_ao_winsn; + s->insn_read = &ni_670x_ao_rinsn; + + s = dev->subdevices + 1; + /* digital i/o subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = ni_670x_dio_insn_bits; + s->insn_config = ni_670x_dio_insn_config; + + /* Config of misc registers */ + writel(0x10, devpriv->mite->daq_io_addr + MISC_CONTROL_OFFSET); + /* Config of ao registers */ + writel(0x00, devpriv->mite->daq_io_addr + AO_CONTROL_OFFSET); + + printk(KERN_INFO "attached\n"); + + return 1; +} + +static int ni_670x_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: ni_670x: remove\n", dev->minor); + + kfree(dev->subdevices[0].range_table_list); + + if (dev->private && devpriv->mite) + mite_unsetup(devpriv->mite); + + if (dev->irq) + free_irq(dev->irq, dev); + + return 0; +} + +static int ni_670x_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + /* Channel number mapping : + + NI 6703/ NI 6704 | NI 6704 Only + ---------------------------------------------------- + vch(0) : 0 | ich(16) : 1 + vch(1) : 2 | ich(17) : 3 + . : . | . . + . : . | . . + . : . | . . + vch(15) : 30 | ich(31) : 31 */ + + for (i = 0; i < insn->n; i++) { + /* First write in channel register which channel to use */ + writel(((chan & 15) << 1) | ((chan & 16) >> 4), + devpriv->mite->daq_io_addr + AO_CHAN_OFFSET); + /* write channel value */ + writel(data[i], devpriv->mite->daq_io_addr + AO_VALUE_OFFSET); + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +static int ni_670x_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int ni_670x_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + writel(s->state, + devpriv->mite->daq_io_addr + DIO_PORT0_DATA_OFFSET); + } + + /* on return, data[1] contains the value of the digital + * input lines. */ + data[1] = readl(devpriv->mite->daq_io_addr + DIO_PORT0_DATA_OFFSET); + + return 2; +} + +static int ni_670x_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << chan; + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + break; + } + writel(s->io_bits, devpriv->mite->daq_io_addr + DIO_PORT0_DIR_OFFSET); + + return insn->n; +} + +static int ni_670x_find_device(struct comedi_device *dev, int bus, int slot) +{ + struct mite_struct *mite; + int i; + + for (mite = mite_devices; mite; mite = mite->next) { + if (mite->used) + continue; + if (bus || slot) { + if (bus != mite->pcidev->bus->number + || slot != PCI_SLOT(mite->pcidev->devfn)) + continue; + } + + for (i = 0; i < n_ni_670x_boards; i++) { + if (mite_device_id(mite) == ni_670x_boards[i].dev_id) { + dev->board_ptr = ni_670x_boards + i; + devpriv->mite = mite; + + return 0; + } + } + } + printk(KERN_INFO "no device found\n"); + mite_list_devices(); + return -EIO; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_at_a2150.c b/drivers/staging/comedi/drivers/ni_at_a2150.c new file mode 100644 index 00000000..c25e44c1 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_at_a2150.c @@ -0,0 +1,925 @@ +/* + comedi/drivers/ni_at_a2150.c + Driver for National Instruments AT-A2150 boards + Copyright (C) 2001, 2002 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ +*/ +/* +Driver: ni_at_a2150 +Description: National Instruments AT-A2150 +Author: Frank Mori Hess +Status: works +Devices: [National Instruments] AT-A2150C (at_a2150c), AT-2150S (at_a2150s) + +If you want to ac couple the board's inputs, use AREF_OTHER. + +Configuration options: + [0] - I/O port base address + [1] - IRQ (optional, required for timed conversions) + [2] - DMA (optional, required for timed conversions) + +*/ +/* +Yet another driver for obsolete hardware brought to you by Frank Hess. +Testing and debugging help provided by Dave Andruczyk. + +This driver supports the boards: + +AT-A2150C +AT-A2150S + +The only difference is their master clock frequencies. + +Options: + [0] - base io address + [1] - irq + [2] - dma channel + +References (from ftp://ftp.natinst.com/support/manuals): + + 320360.pdf AT-A2150 User Manual + +TODO: + +analog level triggering +TRIG_WAKE_EOS + +*/ + +#include +#include +#include "../comedidev.h" + +#include +#include +#include + +#include "8253.h" +#include "comedi_fc.h" + +#define A2150_SIZE 28 +#define A2150_DMA_BUFFER_SIZE 0xff00 /* size in bytes of dma buffer */ + +/* #define A2150_DEBUG enable debugging code */ +#undef A2150_DEBUG /* disable debugging code */ + +/* Registers and bits */ +#define CONFIG_REG 0x0 +#define CHANNEL_BITS(x) ((x) & 0x7) +#define CHANNEL_MASK 0x7 +#define CLOCK_SELECT_BITS(x) (((x) & 0x3) << 3) +#define CLOCK_DIVISOR_BITS(x) (((x) & 0x3) << 5) +#define CLOCK_MASK (0xf << 3) +#define ENABLE0_BIT 0x80 /* enable (don't internally ground) channels 0 and 1 */ +#define ENABLE1_BIT 0x100 /* enable (don't internally ground) channels 2 and 3 */ +#define AC0_BIT 0x200 /* ac couple channels 0,1 */ +#define AC1_BIT 0x400 /* ac couple channels 2,3 */ +#define APD_BIT 0x800 /* analog power down */ +#define DPD_BIT 0x1000 /* digital power down */ +#define TRIGGER_REG 0x2 /* trigger config register */ +#define POST_TRIGGER_BITS 0x2 +#define DELAY_TRIGGER_BITS 0x3 +#define HW_TRIG_EN 0x10 /* enable hardware trigger */ +#define FIFO_START_REG 0x6 /* software start aquistion trigger */ +#define FIFO_RESET_REG 0x8 /* clears fifo + fifo flags */ +#define FIFO_DATA_REG 0xa /* read data */ +#define DMA_TC_CLEAR_REG 0xe /* clear dma terminal count interrupt */ +#define STATUS_REG 0x12 /* read only */ +#define FNE_BIT 0x1 /* fifo not empty */ +#define OVFL_BIT 0x8 /* fifo overflow */ +#define EDAQ_BIT 0x10 /* end of acquisition interrupt */ +#define DCAL_BIT 0x20 /* offset calibration in progress */ +#define INTR_BIT 0x40 /* interrupt has occurred */ +#define DMA_TC_BIT 0x80 /* dma terminal count interrupt has occurred */ +#define ID_BITS(x) (((x) >> 8) & 0x3) +#define IRQ_DMA_CNTRL_REG 0x12 /* write only */ +#define DMA_CHAN_BITS(x) ((x) & 0x7) /* sets dma channel */ +#define DMA_EN_BIT 0x8 /* enables dma */ +#define IRQ_LVL_BITS(x) (((x) & 0xf) << 4) /* sets irq level */ +#define FIFO_INTR_EN_BIT 0x100 /* enable fifo interrupts */ +#define FIFO_INTR_FHF_BIT 0x200 /* interrupt fifo half full */ +#define DMA_INTR_EN_BIT 0x800 /* enable interrupt on dma terminal count */ +#define DMA_DEM_EN_BIT 0x1000 /* enables demand mode dma */ +#define I8253_BASE_REG 0x14 +#define I8253_MODE_REG 0x17 +#define HW_COUNT_DISABLE 0x30 /* disable hardware counting of conversions */ + +struct a2150_board { + const char *name; + int clock[4]; /* master clock periods, in nanoseconds */ + int num_clocks; /* number of available master clock speeds */ + int ai_speed; /* maximum conversion rate in nanoseconds */ +}; + +/* analog input range */ +static const struct comedi_lrange range_a2150 = { + 1, + { + RANGE(-2.828, 2.828), + } +}; + +/* enum must match board indices */ +enum { a2150_c, a2150_s }; +static const struct a2150_board a2150_boards[] = { + { + .name = "at-a2150c", + .clock = {31250, 22676, 20833, 19531}, + .num_clocks = 4, + .ai_speed = 19531, + }, + { + .name = "at-a2150s", + .clock = {62500, 50000, 41667, 0}, + .num_clocks = 3, + .ai_speed = 41667, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct a2150_board *)dev->board_ptr) + +struct a2150_private { + + volatile unsigned int count; /* number of data points left to be taken */ + unsigned int dma; /* dma channel */ + s16 *dma_buffer; /* dma buffer */ + unsigned int dma_transfer_size; /* size in bytes of dma transfers */ + int irq_dma_bits; /* irq/dma register bits */ + int config_bits; /* config register bits */ +}; + +#define devpriv ((struct a2150_private *)dev->private) + +static int a2150_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int a2150_detach(struct comedi_device *dev); +static int a2150_cancel(struct comedi_device *dev, struct comedi_subdevice *s); + +static struct comedi_driver driver_a2150 = { + .driver_name = "ni_at_a2150", + .module = THIS_MODULE, + .attach = a2150_attach, + .detach = a2150_detach, +}; + +static irqreturn_t a2150_interrupt(int irq, void *d); +static int a2150_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd); +static int a2150_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int a2150_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int a2150_get_timing(struct comedi_device *dev, unsigned int *period, + int flags); +static int a2150_probe(struct comedi_device *dev); +static int a2150_set_chanlist(struct comedi_device *dev, + unsigned int start_channel, + unsigned int num_channels); +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __init driver_a2150_init_module(void) +{ + return comedi_driver_register(&driver_a2150); +} + +static void __exit driver_a2150_cleanup_module(void) +{ + comedi_driver_unregister(&driver_a2150); +} + +module_init(driver_a2150_init_module); +module_exit(driver_a2150_cleanup_module); + +#ifdef A2150_DEBUG + +static void ni_dump_regs(struct comedi_device *dev) +{ + printk("config bits 0x%x\n", devpriv->config_bits); + printk("irq dma bits 0x%x\n", devpriv->irq_dma_bits); + printk("status bits 0x%x\n", inw(dev->iobase + STATUS_REG)); +} + +#endif + +/* interrupt service routine */ +static irqreturn_t a2150_interrupt(int irq, void *d) +{ + int i; + int status; + unsigned long flags; + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + struct comedi_cmd *cmd; + unsigned int max_points, num_points, residue, leftover; + short dpnt; + static const int sample_size = sizeof(devpriv->dma_buffer[0]); + + if (dev->attached == 0) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + /* initialize async here to make sure s is not NULL */ + async = s->async; + async->events = 0; + cmd = &async->cmd; + + status = inw(dev->iobase + STATUS_REG); + + if ((status & INTR_BIT) == 0) { + comedi_error(dev, "spurious interrupt"); + return IRQ_NONE; + } + + if (status & OVFL_BIT) { + comedi_error(dev, "fifo overflow"); + a2150_cancel(dev, s); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + } + + if ((status & DMA_TC_BIT) == 0) { + comedi_error(dev, "caught non-dma interrupt? Aborting."); + a2150_cancel(dev, s); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + comedi_event(dev, s); + return IRQ_HANDLED; + } + + flags = claim_dma_lock(); + disable_dma(devpriv->dma); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma); + + /* figure out how many points to read */ + max_points = devpriv->dma_transfer_size / sample_size; + /* residue is the number of points left to be done on the dma + * transfer. It should always be zero at this point unless + * the stop_src is set to external triggering. + */ + residue = get_dma_residue(devpriv->dma) / sample_size; + num_points = max_points - residue; + if (devpriv->count < num_points && cmd->stop_src == TRIG_COUNT) + num_points = devpriv->count; + + /* figure out how many points will be stored next time */ + leftover = 0; + if (cmd->stop_src == TRIG_NONE) { + leftover = devpriv->dma_transfer_size / sample_size; + } else if (devpriv->count > max_points) { + leftover = devpriv->count - max_points; + if (leftover > max_points) + leftover = max_points; + } + /* there should only be a residue if collection was stopped by having + * the stop_src set to an external trigger, in which case there + * will be no more data + */ + if (residue) + leftover = 0; + + for (i = 0; i < num_points; i++) { + /* write data point to comedi buffer */ + dpnt = devpriv->dma_buffer[i]; + /* convert from 2's complement to unsigned coding */ + dpnt ^= 0x8000; + cfc_write_to_buffer(s, dpnt); + if (cmd->stop_src == TRIG_COUNT) { + if (--devpriv->count == 0) { /* end of acquisition */ + a2150_cancel(dev, s); + async->events |= COMEDI_CB_EOA; + break; + } + } + } + /* re-enable dma */ + if (leftover) { + set_dma_addr(devpriv->dma, virt_to_bus(devpriv->dma_buffer)); + set_dma_count(devpriv->dma, leftover * sample_size); + enable_dma(devpriv->dma); + } + release_dma_lock(flags); + + async->events |= COMEDI_CB_BLOCK; + + comedi_event(dev, s); + + /* clear interrupt */ + outw(0x00, dev->iobase + DMA_TC_CLEAR_REG); + + return IRQ_HANDLED; +} + +/* probes board type, returns offset */ +static int a2150_probe(struct comedi_device *dev) +{ + int status = inw(dev->iobase + STATUS_REG); + return ID_BITS(status); +} + +static int a2150_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase = it->options[0]; + unsigned int irq = it->options[1]; + unsigned int dma = it->options[2]; + static const int timeout = 2000; + int i; + + printk("comedi%d: %s: io 0x%lx", dev->minor, driver_a2150.driver_name, + iobase); + if (irq) { + printk(", irq %u", irq); + } else { + printk(", no irq"); + } + if (dma) { + printk(", dma %u", dma); + } else { + printk(", no dma"); + } + printk("\n"); + + /* allocate and initialize dev->private */ + if (alloc_private(dev, sizeof(struct a2150_private)) < 0) + return -ENOMEM; + + if (iobase == 0) { + printk(" io base address required\n"); + return -EINVAL; + } + + /* check if io addresses are available */ + if (!request_region(iobase, A2150_SIZE, driver_a2150.driver_name)) { + printk(" I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + /* grab our IRQ */ + if (irq) { + /* check that irq is supported */ + if (irq < 3 || irq == 8 || irq == 13 || irq > 15) { + printk(" invalid irq line %u\n", irq); + return -EINVAL; + } + if (request_irq(irq, a2150_interrupt, 0, + driver_a2150.driver_name, dev)) { + printk("unable to allocate irq %u\n", irq); + return -EINVAL; + } + devpriv->irq_dma_bits |= IRQ_LVL_BITS(irq); + dev->irq = irq; + } + /* initialize dma */ + if (dma) { + if (dma == 4 || dma > 7) { + printk(" invalid dma channel %u\n", dma); + return -EINVAL; + } + if (request_dma(dma, driver_a2150.driver_name)) { + printk(" failed to allocate dma channel %u\n", dma); + return -EINVAL; + } + devpriv->dma = dma; + devpriv->dma_buffer = + kmalloc(A2150_DMA_BUFFER_SIZE, GFP_KERNEL | GFP_DMA); + if (devpriv->dma_buffer == NULL) + return -ENOMEM; + + disable_dma(dma); + set_dma_mode(dma, DMA_MODE_READ); + + devpriv->irq_dma_bits |= DMA_CHAN_BITS(dma); + } + + dev->board_ptr = a2150_boards + a2150_probe(dev); + dev->board_name = thisboard->name; + + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + + /* analog input subdevice */ + s = dev->subdevices + 0; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_OTHER | SDF_CMD_READ; + s->n_chan = 4; + s->len_chanlist = 4; + s->maxdata = 0xffff; + s->range_table = &range_a2150; + s->do_cmd = a2150_ai_cmd; + s->do_cmdtest = a2150_ai_cmdtest; + s->insn_read = a2150_ai_rinsn; + s->cancel = a2150_cancel; + + /* need to do this for software counting of completed conversions, to + * prevent hardware count from stopping acquisition */ + outw(HW_COUNT_DISABLE, dev->iobase + I8253_MODE_REG); + + /* set card's irq and dma levels */ + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + /* reset and sync adc clock circuitry */ + outw_p(DPD_BIT | APD_BIT, dev->iobase + CONFIG_REG); + outw_p(DPD_BIT, dev->iobase + CONFIG_REG); + /* initialize configuration register */ + devpriv->config_bits = 0; + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + /* wait until offset calibration is done, then enable analog inputs */ + for (i = 0; i < timeout; i++) { + if ((DCAL_BIT & inw(dev->iobase + STATUS_REG)) == 0) + break; + udelay(1000); + } + if (i == timeout) { + printk + (" timed out waiting for offset calibration to complete\n"); + return -ETIME; + } + devpriv->config_bits |= ENABLE0_BIT | ENABLE1_BIT; + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + + return 0; +}; + +static int a2150_detach(struct comedi_device *dev) +{ + printk("comedi%d: %s: remove\n", dev->minor, driver_a2150.driver_name); + + /* only free stuff if it has been allocated by _attach */ + if (dev->iobase) { + /* put board in power-down mode */ + outw(APD_BIT | DPD_BIT, dev->iobase + CONFIG_REG); + release_region(dev->iobase, A2150_SIZE); + } + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (devpriv->dma) + free_dma(devpriv->dma); + kfree(devpriv->dma_buffer); + } + + return 0; +}; + +static int a2150_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + /* disable dma on card */ + devpriv->irq_dma_bits &= ~DMA_INTR_EN_BIT & ~DMA_EN_BIT; + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + /* disable computer's dma */ + disable_dma(devpriv->dma); + + /* clear fifo and reset triggering circuitry */ + outw(0, dev->iobase + FIFO_RESET_REG); + + return 0; +} + +static int a2150_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + int startChan; + int i; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < thisboard->ai_speed) { + cmd->convert_arg = thisboard->ai_speed; + err++; + } + } + if (!cmd->chanlist_len) { + cmd->chanlist_len = 1; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + a2150_get_timing(dev, &cmd->scan_begin_arg, cmd->flags); + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + /* check channel/gain list against card's limitations */ + if (cmd->chanlist) { + startChan = CR_CHAN(cmd->chanlist[0]); + for (i = 1; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) != (startChan + i)) { + comedi_error(dev, + "entries in chanlist must be consecutive channels, counting upwards\n"); + err++; + } + } + if (cmd->chanlist_len == 2 && CR_CHAN(cmd->chanlist[0]) == 1) { + comedi_error(dev, + "length 2 chanlist must be channels 0,1 or channels 2,3"); + err++; + } + if (cmd->chanlist_len == 3) { + comedi_error(dev, + "chanlist must have 1,2 or 4 channels"); + err++; + } + if (CR_AREF(cmd->chanlist[0]) != CR_AREF(cmd->chanlist[1]) || + CR_AREF(cmd->chanlist[2]) != CR_AREF(cmd->chanlist[3])) { + comedi_error(dev, + "channels 0/1 and 2/3 must have the same analog reference"); + err++; + } + } + + if (err) + return 5; + + return 0; +} + +static int a2150_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + unsigned long lock_flags; + unsigned int old_config_bits = devpriv->config_bits; + unsigned int trigger_bits; + + if (!dev->irq || !devpriv->dma) { + comedi_error(dev, + " irq and dma required, cannot do hardware conversions"); + return -1; + } + if (cmd->flags & TRIG_RT) { + comedi_error(dev, + " dma incompatible with hard real-time interrupt (TRIG_RT), aborting"); + return -1; + } + /* clear fifo and reset triggering circuitry */ + outw(0, dev->iobase + FIFO_RESET_REG); + + /* setup chanlist */ + if (a2150_set_chanlist(dev, CR_CHAN(cmd->chanlist[0]), + cmd->chanlist_len) < 0) + return -1; + + /* setup ac/dc coupling */ + if (CR_AREF(cmd->chanlist[0]) == AREF_OTHER) + devpriv->config_bits |= AC0_BIT; + else + devpriv->config_bits &= ~AC0_BIT; + if (CR_AREF(cmd->chanlist[2]) == AREF_OTHER) + devpriv->config_bits |= AC1_BIT; + else + devpriv->config_bits &= ~AC1_BIT; + + /* setup timing */ + a2150_get_timing(dev, &cmd->scan_begin_arg, cmd->flags); + + /* send timing, channel, config bits */ + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + + /* initialize number of samples remaining */ + devpriv->count = cmd->stop_arg * cmd->chanlist_len; + + /* enable computer's dma */ + lock_flags = claim_dma_lock(); + disable_dma(devpriv->dma); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, virt_to_bus(devpriv->dma_buffer)); + /* set size of transfer to fill in 1/3 second */ +#define ONE_THIRD_SECOND 333333333 + devpriv->dma_transfer_size = + sizeof(devpriv->dma_buffer[0]) * cmd->chanlist_len * + ONE_THIRD_SECOND / cmd->scan_begin_arg; + if (devpriv->dma_transfer_size > A2150_DMA_BUFFER_SIZE) + devpriv->dma_transfer_size = A2150_DMA_BUFFER_SIZE; + if (devpriv->dma_transfer_size < sizeof(devpriv->dma_buffer[0])) + devpriv->dma_transfer_size = sizeof(devpriv->dma_buffer[0]); + devpriv->dma_transfer_size -= + devpriv->dma_transfer_size % sizeof(devpriv->dma_buffer[0]); + set_dma_count(devpriv->dma, devpriv->dma_transfer_size); + enable_dma(devpriv->dma); + release_dma_lock(lock_flags); + + /* clear dma interrupt before enabling it, to try and get rid of that + * one spurious interrupt that has been happening */ + outw(0x00, dev->iobase + DMA_TC_CLEAR_REG); + + /* enable dma on card */ + devpriv->irq_dma_bits |= DMA_INTR_EN_BIT | DMA_EN_BIT; + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + /* may need to wait 72 sampling periods if timing was changed */ + i8254_load(dev->iobase + I8253_BASE_REG, 0, 2, 72, 0); + + /* setup start triggering */ + trigger_bits = 0; + /* decide if we need to wait 72 periods for valid data */ + if (cmd->start_src == TRIG_NOW && + (old_config_bits & CLOCK_MASK) != + (devpriv->config_bits & CLOCK_MASK)) { + /* set trigger source to delay trigger */ + trigger_bits |= DELAY_TRIGGER_BITS; + } else { + /* otherwise no delay */ + trigger_bits |= POST_TRIGGER_BITS; + } + /* enable external hardware trigger */ + if (cmd->start_src == TRIG_EXT) { + trigger_bits |= HW_TRIG_EN; + } else if (cmd->start_src == TRIG_OTHER) { + /* XXX add support for level/slope start trigger using TRIG_OTHER */ + comedi_error(dev, "you shouldn't see this?"); + } + /* send trigger config bits */ + outw(trigger_bits, dev->iobase + TRIGGER_REG); + + /* start acquisition for soft trigger */ + if (cmd->start_src == TRIG_NOW) + outw(0, dev->iobase + FIFO_START_REG); +#ifdef A2150_DEBUG + ni_dump_regs(dev); +#endif + + return 0; +} + +static int a2150_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int i, n; + static const int timeout = 100000; + static const int filter_delay = 36; + + /* clear fifo and reset triggering circuitry */ + outw(0, dev->iobase + FIFO_RESET_REG); + + /* setup chanlist */ + if (a2150_set_chanlist(dev, CR_CHAN(insn->chanspec), 1) < 0) + return -1; + + /* set dc coupling */ + devpriv->config_bits &= ~AC0_BIT; + devpriv->config_bits &= ~AC1_BIT; + + /* send timing, channel, config bits */ + outw(devpriv->config_bits, dev->iobase + CONFIG_REG); + + /* disable dma on card */ + devpriv->irq_dma_bits &= ~DMA_INTR_EN_BIT & ~DMA_EN_BIT; + outw(devpriv->irq_dma_bits, dev->iobase + IRQ_DMA_CNTRL_REG); + + /* setup start triggering */ + outw(0, dev->iobase + TRIGGER_REG); + + /* start acquisition for soft trigger */ + outw(0, dev->iobase + FIFO_START_REG); + + /* there is a 35.6 sample delay for data to get through the antialias filter */ + for (n = 0; n < filter_delay; n++) { + for (i = 0; i < timeout; i++) { + if (inw(dev->iobase + STATUS_REG) & FNE_BIT) + break; + udelay(1); + } + if (i == timeout) { + comedi_error(dev, "timeout"); + return -ETIME; + } + inw(dev->iobase + FIFO_DATA_REG); + } + + /* read data */ + for (n = 0; n < insn->n; n++) { + for (i = 0; i < timeout; i++) { + if (inw(dev->iobase + STATUS_REG) & FNE_BIT) + break; + udelay(1); + } + if (i == timeout) { + comedi_error(dev, "timeout"); + return -ETIME; + } +#ifdef A2150_DEBUG + ni_dump_regs(dev); +#endif + data[n] = inw(dev->iobase + FIFO_DATA_REG); +#ifdef A2150_DEBUG + printk(" data is %i\n", data[n]); +#endif + data[n] ^= 0x8000; + } + + /* clear fifo and reset triggering circuitry */ + outw(0, dev->iobase + FIFO_RESET_REG); + + return n; +} + +/* sets bits in devpriv->clock_bits to nearest approximation of requested period, + * adjusts requested period to actual timing. */ +static int a2150_get_timing(struct comedi_device *dev, unsigned int *period, + int flags) +{ + int lub, glb, temp; + int lub_divisor_shift, lub_index, glb_divisor_shift, glb_index; + int i, j; + + /* initialize greatest lower and least upper bounds */ + lub_divisor_shift = 3; + lub_index = 0; + lub = thisboard->clock[lub_index] * (1 << lub_divisor_shift); + glb_divisor_shift = 0; + glb_index = thisboard->num_clocks - 1; + glb = thisboard->clock[glb_index] * (1 << glb_divisor_shift); + + /* make sure period is in available range */ + if (*period < glb) + *period = glb; + if (*period > lub) + *period = lub; + + /* we can multiply period by 1, 2, 4, or 8, using (1 << i) */ + for (i = 0; i < 4; i++) { + /* there are a maximum of 4 master clocks */ + for (j = 0; j < thisboard->num_clocks; j++) { + /* temp is the period in nanosec we are evaluating */ + temp = thisboard->clock[j] * (1 << i); + /* if it is the best match yet */ + if (temp < lub && temp >= *period) { + lub_divisor_shift = i; + lub_index = j; + lub = temp; + } + if (temp > glb && temp <= *period) { + glb_divisor_shift = i; + glb_index = j; + glb = temp; + } + } + } + flags &= TRIG_ROUND_MASK; + switch (flags) { + case TRIG_ROUND_NEAREST: + default: + /* if least upper bound is better approximation */ + if (lub - *period < *period - glb) + *period = lub; + else + *period = glb; + break; + case TRIG_ROUND_UP: + *period = lub; + break; + case TRIG_ROUND_DOWN: + *period = glb; + break; + } + + /* set clock bits for config register appropriately */ + devpriv->config_bits &= ~CLOCK_MASK; + if (*period == lub) { + devpriv->config_bits |= + CLOCK_SELECT_BITS(lub_index) | + CLOCK_DIVISOR_BITS(lub_divisor_shift); + } else { + devpriv->config_bits |= + CLOCK_SELECT_BITS(glb_index) | + CLOCK_DIVISOR_BITS(glb_divisor_shift); + } + + return 0; +} + +static int a2150_set_chanlist(struct comedi_device *dev, + unsigned int start_channel, + unsigned int num_channels) +{ + if (start_channel + num_channels > 4) + return -1; + + devpriv->config_bits &= ~CHANNEL_MASK; + + switch (num_channels) { + case 1: + devpriv->config_bits |= CHANNEL_BITS(0x4 | start_channel); + break; + case 2: + if (start_channel == 0) { + devpriv->config_bits |= CHANNEL_BITS(0x2); + } else if (start_channel == 2) { + devpriv->config_bits |= CHANNEL_BITS(0x3); + } else { + return -1; + } + break; + case 4: + devpriv->config_bits |= CHANNEL_BITS(0x1); + break; + default: + return -1; + break; + } + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_at_ao.c b/drivers/staging/comedi/drivers/ni_at_ao.c new file mode 100644 index 00000000..138dcc22 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_at_ao.c @@ -0,0 +1,476 @@ +/* + comedi/drivers/ni_at_ao.c + Driver for NI AT-AO-6/10 boards + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000,2002 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: ni_at_ao +Description: National Instruments AT-AO-6/10 +Devices: [National Instruments] AT-AO-6 (at-ao-6), AT-AO-10 (at-ao-10) +Status: should work +Author: ds +Updated: Sun Dec 26 12:26:28 EST 2004 + +Configuration options: + [0] - I/O port base address + [1] - IRQ (unused) + [2] - DMA (unused) + [3] - analog output range, set by jumpers on hardware (0 for -10 to 10V + bipolar, 1 for 0V to 10V unipolar) + +*/ +/* + * Register-level programming information can be found in NI + * document 320379.pdf. + */ + +#include "../comedidev.h" + +#include + +/* board egisters */ +/* registers with _2_ are accessed when GRP2WR is set in CFG1 */ + +#define ATAO_SIZE 0x20 + +#define ATAO_2_DMATCCLR 0x00 /* W 16 */ +#define ATAO_DIN 0x00 /* R 16 */ +#define ATAO_DOUT 0x00 /* W 16 */ + +#define ATAO_CFG2 0x02 /* W 16 */ +#define CALLD1 0x8000 +#define CALLD0 0x4000 +#define FFRTEN 0x2000 +#define DAC2S8 0x1000 +#define DAC2S6 0x0800 +#define DAC2S4 0x0400 +#define DAC2S2 0x0200 +#define DAC2S0 0x0100 +#define LDAC8 0x0080 +#define LDAC6 0x0040 +#define LDAC4 0x0020 +#define LDAC2 0x0010 +#define LDAC0 0x0008 +#define PROMEN 0x0004 +#define SCLK 0x0002 +#define SDATA 0x0001 + +#define ATAO_2_INT1CLR 0x02 /* W 16 */ + +#define ATAO_CFG3 0x04 /* W 16 */ +#define DMAMODE 0x0040 +#define CLKOUT 0x0020 +#define RCLKEN 0x0010 +#define DOUTEN2 0x0008 +#define DOUTEN1 0x0004 +#define EN2_5V 0x0002 +#define SCANEN 0x0001 + +#define ATAO_2_INT2CLR 0x04 /* W 16 */ + +#define ATAO_82C53_BASE 0x06 /* RW 8 */ + +#define ATAO_82C53_CNTR1 0x06 /* RW 8 */ +#define ATAO_82C53_CNTR2 0x07 /* RW 8 */ +#define ATAO_82C53_CNTR3 0x08 /* RW 8 */ +#define ATAO_82C53_CNTRCMD 0x09 /* W 8 */ +#define CNTRSEL1 0x80 +#define CNTRSEL0 0x40 +#define RWSEL1 0x20 +#define RWSEL0 0x10 +#define MODESEL2 0x08 +#define MODESEL1 0x04 +#define MODESEL0 0x02 +#define BCDSEL 0x01 + /* read-back command */ +#define COUNT 0x20 +#define STATUS 0x10 +#define CNTR3 0x08 +#define CNTR2 0x04 +#define CNTR1 0x02 + /* status */ +#define OUT 0x80 +#define _NULL 0x40 +#define RW1 0x20 +#define RW0 0x10 +#define MODE2 0x08 +#define MODE1 0x04 +#define MODE0 0x02 +#define BCD 0x01 + +#define ATAO_2_RTSISHFT 0x06 /* W 8 */ +#define RSI 0x01 + +#define ATAO_2_RTSISTRB 0x07 /* W 8 */ + +#define ATAO_CFG1 0x0a /* W 16 */ +#define EXTINT2EN 0x8000 +#define EXTINT1EN 0x4000 +#define CNTINT2EN 0x2000 +#define CNTINT1EN 0x1000 +#define TCINTEN 0x0800 +#define CNT1SRC 0x0400 +#define CNT2SRC 0x0200 +#define FIFOEN 0x0100 +#define GRP2WR 0x0080 +#define EXTUPDEN 0x0040 +#define DMARQ 0x0020 +#define DMAEN 0x0010 +#define CH_mask 0x000f +#define ATAO_STATUS 0x0a /* R 16 */ +#define FH 0x0040 +#define FE 0x0020 +#define FF 0x0010 +#define INT2 0x0008 +#define INT1 0x0004 +#define TCINT 0x0002 +#define PROMOUT 0x0001 + +#define ATAO_FIFO_WRITE 0x0c /* W 16 */ +#define ATAO_FIFO_CLEAR 0x0c /* R 16 */ +#define ATAO_DACn(x) (0x0c + 2*(x)) /* W */ + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +struct atao_board { + const char *name; + int n_ao_chans; +}; + +static const struct atao_board atao_boards[] = { + { + .name = "ai-ao-6", + .n_ao_chans = 6, + }, + { + .name = "ai-ao-10", + .n_ao_chans = 10, + }, +}; + +#define thisboard ((struct atao_board *)dev->board_ptr) + +struct atao_private { + + unsigned short cfg1; + unsigned short cfg2; + unsigned short cfg3; + + /* Used for AO readback */ + unsigned int ao_readback[10]; +}; + +#define devpriv ((struct atao_private *)dev->private) + +static int atao_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int atao_detach(struct comedi_device *dev); +static struct comedi_driver driver_atao = { + .driver_name = "ni_at_ao", + .module = THIS_MODULE, + .attach = atao_attach, + .detach = atao_detach, + .board_name = &atao_boards[0].name, + .offset = sizeof(struct atao_board), + .num_names = ARRAY_SIZE(atao_boards), +}; + +static int __init driver_atao_init_module(void) +{ + return comedi_driver_register(&driver_atao); +} + +static void __exit driver_atao_cleanup_module(void) +{ + comedi_driver_unregister(&driver_atao); +} + +module_init(driver_atao_init_module); +module_exit(driver_atao_cleanup_module); + +static void atao_reset(struct comedi_device *dev); + +static int atao_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int atao_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int atao_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int atao_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int atao_calib_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int atao_calib_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int atao_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + int ao_unipolar; + + iobase = it->options[0]; + if (iobase == 0) + iobase = 0x1c0; + ao_unipolar = it->options[3]; + + printk(KERN_INFO "comedi%d: ni_at_ao: 0x%04lx", dev->minor, iobase); + + if (!request_region(iobase, ATAO_SIZE, "ni_at_ao")) { + printk(" I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + /* dev->board_ptr = atao_probe(dev); */ + + dev->board_name = thisboard->name; + + if (alloc_private(dev, sizeof(struct atao_private)) < 0) + return -ENOMEM; + + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = thisboard->n_ao_chans; + s->maxdata = (1 << 12) - 1; + if (ao_unipolar) + s->range_table = &range_unipolar10; + else + s->range_table = &range_bipolar10; + s->insn_write = &atao_ao_winsn; + s->insn_read = &atao_ao_rinsn; + + s = dev->subdevices + 1; + /* digital i/o subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = atao_dio_insn_bits; + s->insn_config = atao_dio_insn_config; + + s = dev->subdevices + 2; + /* caldac subdevice */ + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 21; + s->maxdata = 0xff; + s->insn_read = atao_calib_insn_read; + s->insn_write = atao_calib_insn_write; + + s = dev->subdevices + 3; + /* eeprom subdevice */ + /* s->type=COMEDI_SUBD_EEPROM; */ + s->type = COMEDI_SUBD_UNUSED; + + atao_reset(dev); + + printk(KERN_INFO "\n"); + + return 0; +} + +static int atao_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: atao: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, ATAO_SIZE); + + return 0; +} + +static void atao_reset(struct comedi_device *dev) +{ + /* This is the reset sequence described in the manual */ + + devpriv->cfg1 = 0; + outw(devpriv->cfg1, dev->iobase + ATAO_CFG1); + + outb(RWSEL0 | MODESEL2, dev->iobase + ATAO_82C53_CNTRCMD); + outb(0x03, dev->iobase + ATAO_82C53_CNTR1); + outb(CNTRSEL0 | RWSEL0 | MODESEL2, dev->iobase + ATAO_82C53_CNTRCMD); + + devpriv->cfg2 = 0; + outw(devpriv->cfg2, dev->iobase + ATAO_CFG2); + + devpriv->cfg3 = 0; + outw(devpriv->cfg3, dev->iobase + ATAO_CFG3); + + inw(dev->iobase + ATAO_FIFO_CLEAR); + + devpriv->cfg1 |= GRP2WR; + outw(devpriv->cfg1, dev->iobase + ATAO_CFG1); + + outw(0, dev->iobase + ATAO_2_INT1CLR); + outw(0, dev->iobase + ATAO_2_INT2CLR); + outw(0, dev->iobase + ATAO_2_DMATCCLR); + + devpriv->cfg1 &= ~GRP2WR; + outw(devpriv->cfg1, dev->iobase + ATAO_CFG1); +} + +static int atao_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + short bits; + + for (i = 0; i < insn->n; i++) { + bits = data[i] - 0x800; + if (chan == 0) { + devpriv->cfg1 |= GRP2WR; + outw(devpriv->cfg1, dev->iobase + ATAO_CFG1); + } + outw(bits, dev->iobase + ATAO_DACn(chan)); + if (chan == 0) { + devpriv->cfg1 &= ~GRP2WR; + outw(devpriv->cfg1, dev->iobase + ATAO_CFG1); + } + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +static int atao_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int atao_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + outw(s->state, dev->iobase + ATAO_DOUT); + } + + data[1] = inw(dev->iobase + ATAO_DIN); + + return 2; +} + +static int atao_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + unsigned int mask, bit; + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + + mask = (chan < 4) ? 0x0f : 0xf0; + bit = (chan < 4) ? DOUTEN1 : DOUTEN2; + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= mask; + devpriv->cfg3 |= bit; + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~mask; + devpriv->cfg3 &= ~bit; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + break; + } + + outw(devpriv->cfg3, dev->iobase + ATAO_CFG3); + + return 1; +} + +/* + * Figure 2-1 in the manual shows 3 chips labeled DAC8800, which + * are 8-channel 8-bit DACs. These are most likely the calibration + * DACs. It is not explicitly stated in the manual how to access + * the caldacs, but we can guess. + */ +static int atao_calib_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + for (i = 0; i < insn->n; i++) + data[i] = 0; /* XXX */ + return insn->n; +} + +static int atao_calib_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int bitstring, bit; + unsigned int chan = CR_CHAN(insn->chanspec); + + bitstring = ((chan & 0x7) << 8) | (data[insn->n - 1] & 0xff); + + for (bit = 1 << (11 - 1); bit; bit >>= 1) { + outw(devpriv->cfg2 | ((bit & bitstring) ? SDATA : 0), + dev->iobase + ATAO_CFG2); + outw(devpriv->cfg2 | SCLK | ((bit & bitstring) ? SDATA : 0), + dev->iobase + ATAO_CFG2); + } + /* strobe the appropriate caldac */ + outw(devpriv->cfg2 | (((chan >> 3) + 1) << 14), + dev->iobase + ATAO_CFG2); + outw(devpriv->cfg2, dev->iobase + ATAO_CFG2); + + return insn->n; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_atmio.c b/drivers/staging/comedi/drivers/ni_atmio.c new file mode 100644 index 00000000..647c228a --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_atmio.c @@ -0,0 +1,539 @@ +/* + comedi/drivers/ni_atmio.c + Hardware driver for NI AT-MIO E series cards + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* +Driver: ni_atmio +Description: National Instruments AT-MIO-E series +Author: ds +Devices: [National Instruments] AT-MIO-16E-1 (ni_atmio), + AT-MIO-16E-2, AT-MIO-16E-10, AT-MIO-16DE-10, AT-MIO-64E-3, + AT-MIO-16XE-50, AT-MIO-16XE-10, AT-AI-16XE-10 +Status: works +Updated: Thu May 1 20:03:02 CDT 2003 + +The driver has 2.6 kernel isapnp support, and +will automatically probe for a supported board if the +I/O base is left unspecified with comedi_config. +However, many of +the isapnp id numbers are unknown. If your board is not +recognized, please send the output of 'cat /proc/isapnp' +(you may need to modprobe the isa-pnp module for +/proc/isapnp to exist) so the +id numbers for your board can be added to the driver. + +Otherwise, you can use the isapnptools package to configure +your board. Use isapnp to +configure the I/O base and IRQ for the board, and then pass +the same values as +parameters in comedi_config. A sample isapnp.conf file is included +in the etc/ directory of Comedilib. + +Comedilib includes a utility to autocalibrate these boards. The +boards seem to boot into a state where the all calibration DACs +are at one extreme of their range, thus the default calibration +is terrible. Calibration at boot is strongly encouraged. + +To use the extended digital I/O on some of the boards, enable the +8255 driver when configuring the Comedi source tree. + +External triggering is supported for some events. The channel index +(scan_begin_arg, etc.) maps to PFI0 - PFI9. + +Some of the more esoteric triggering possibilities of these boards +are not supported. +*/ +/* + The real guts of the driver is in ni_mio_common.c, which is included + both here and in ni_pcimio.c + + Interrupt support added by Truxton Fulton + + References for specifications: + + 340747b.pdf Register Level Programmer Manual (obsolete) + 340747c.pdf Register Level Programmer Manual (new) + DAQ-STC reference manual + + Other possibly relevant info: + + 320517c.pdf User manual (obsolete) + 320517f.pdf User manual (new) + 320889a.pdf delete + 320906c.pdf maximum signal ratings + 321066a.pdf about 16x + 321791a.pdf discontinuation of at-mio-16e-10 rev. c + 321808a.pdf about at-mio-16e-10 rev P + 321837a.pdf discontinuation of at-mio-16de-10 rev d + 321838a.pdf about at-mio-16de-10 rev N + + ISSUES: + + need to deal with external reference for DAC, and other DAC + properties in board properties + + deal with at-mio-16de-10 revision D to N changes, etc. + +*/ + +#include +#include "../comedidev.h" + +#include +#include + +#include "ni_stc.h" +#include "8255.h" + +#undef DEBUG + +#define ATMIO 1 +#undef PCIMIO + +/* + * AT specific setup + */ + +#define NI_SIZE 0x20 + +#define MAX_N_CALDACS 32 + +static const struct ni_board_struct ni_boards[] = { + {.device_id = 44, + .isapnp_id = 0x0000, /* XXX unknown */ + .name = "at-mio-16e-1", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 8192, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .has_8255 = 0, + .num_p0_dio_channels = 8, + .caldac = {mb88341}, + }, + {.device_id = 25, + .isapnp_id = 0x1900, + .name = "at-mio-16e-2", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 2048, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .has_8255 = 0, + .num_p0_dio_channels = 8, + .caldac = {mb88341}, + }, + {.device_id = 36, + .isapnp_id = 0x2400, + .name = "at-mio-16e-10", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 0, + }, + {.device_id = 37, + .isapnp_id = 0x2500, + .name = "at-mio-16de-10", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 1, + }, + {.device_id = 38, + .isapnp_id = 0x2600, + .name = "at-mio-64e-3", + .n_adchan = 64, + .adbits = 12, + .ai_fifo_depth = 2048, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .has_8255 = 0, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + }, + {.device_id = 39, + .isapnp_id = 0x2700, + .name = "at-mio-16xe-50", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_8, + .ai_speed = 50000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 50000, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043}, + .has_8255 = 0, + }, + {.device_id = 50, + .isapnp_id = 0x0000, /* XXX unknown */ + .name = "at-mio-16xe-10", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + .has_8255 = 0, + }, + {.device_id = 51, + .isapnp_id = 0x0000, /* XXX unknown */ + .name = "at-ai-16xe-10", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, /* unknown */ + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + .has_8255 = 0, + } +}; + +static const int ni_irqpin[] = { + -1, -1, -1, 0, 1, 2, -1, 3, -1, -1, 4, 5, 6, -1, -1, 7 +}; + +#define interrupt_pin(a) (ni_irqpin[(a)]) + +#define IRQ_POLARITY 0 + +#define NI_E_IRQ_FLAGS 0 + +struct ni_private { + struct pnp_dev *isapnp_dev; + NI_PRIVATE_COMMON + +}; + +#define devpriv ((struct ni_private *)dev->private) + +/* How we access registers */ + +#define ni_writel(a, b) (outl((a), (b)+dev->iobase)) +#define ni_readl(a) (inl((a)+dev->iobase)) +#define ni_writew(a, b) (outw((a), (b)+dev->iobase)) +#define ni_readw(a) (inw((a)+dev->iobase)) +#define ni_writeb(a, b) (outb((a), (b)+dev->iobase)) +#define ni_readb(a) (inb((a)+dev->iobase)) + +/* How we access windowed registers */ + +/* We automatically take advantage of STC registers that can be + * read/written directly in the I/O space of the board. The + * AT-MIO devices map the low 8 STC registers to iobase+addr*2. */ + +static void ni_atmio_win_out(struct comedi_device *dev, uint16_t data, int addr) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->window_lock, flags); + if ((addr) < 8) { + ni_writew(data, addr * 2); + } else { + ni_writew(addr, Window_Address); + ni_writew(data, Window_Data); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static uint16_t ni_atmio_win_in(struct comedi_device *dev, int addr) +{ + unsigned long flags; + uint16_t ret; + + spin_lock_irqsave(&devpriv->window_lock, flags); + if (addr < 8) { + ret = ni_readw(addr * 2); + } else { + ni_writew(addr, Window_Address); + ret = ni_readw(Window_Data); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); + + return ret; +} + +static struct pnp_device_id device_ids[] = { + {.id = "NIC1900", .driver_data = 0}, + {.id = "NIC2400", .driver_data = 0}, + {.id = "NIC2500", .driver_data = 0}, + {.id = "NIC2600", .driver_data = 0}, + {.id = "NIC2700", .driver_data = 0}, + {.id = ""} +}; + +MODULE_DEVICE_TABLE(pnp, device_ids); + +static int ni_atmio_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int ni_atmio_detach(struct comedi_device *dev); +static struct comedi_driver driver_atmio = { + .driver_name = "ni_atmio", + .module = THIS_MODULE, + .attach = ni_atmio_attach, + .detach = ni_atmio_detach, +}; + +static int __init driver_atmio_init_module(void) +{ + return comedi_driver_register(&driver_atmio); +} + +static void __exit driver_atmio_cleanup_module(void) +{ + comedi_driver_unregister(&driver_atmio); +} + +module_init(driver_atmio_init_module); +module_exit(driver_atmio_cleanup_module); + +#include "ni_mio_common.c" + +static int ni_getboardtype(struct comedi_device *dev); + +/* clean up allocated resources */ +static int ni_atmio_detach(struct comedi_device *dev) +{ + mio_common_detach(dev); + + if (dev->iobase) + release_region(dev->iobase, NI_SIZE); + if (dev->irq) + free_irq(dev->irq, dev); + + if (devpriv->isapnp_dev) + pnp_device_detach(devpriv->isapnp_dev); + + return 0; +} + +static int ni_isapnp_find_board(struct pnp_dev **dev) +{ + struct pnp_dev *isapnp_dev = NULL; + int i; + + for (i = 0; i < n_ni_boards; i++) { + isapnp_dev = pnp_find_dev(NULL, + ISAPNP_VENDOR('N', 'I', 'C'), + ISAPNP_FUNCTION(ni_boards[i]. + isapnp_id), NULL); + + if (isapnp_dev == NULL || isapnp_dev->card == NULL) + continue; + + if (pnp_device_attach(isapnp_dev) < 0) { + printk + ("ni_atmio: %s found but already active, skipping.\n", + ni_boards[i].name); + continue; + } + if (pnp_activate_dev(isapnp_dev) < 0) { + pnp_device_detach(isapnp_dev); + return -EAGAIN; + } + if (!pnp_port_valid(isapnp_dev, 0) + || !pnp_irq_valid(isapnp_dev, 0)) { + pnp_device_detach(isapnp_dev); + printk("ni_atmio: pnp invalid port or irq, aborting\n"); + return -ENOMEM; + } + break; + } + if (i == n_ni_boards) + return -ENODEV; + *dev = isapnp_dev; + return 0; +} + +static int ni_atmio_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct pnp_dev *isapnp_dev; + int ret; + unsigned long iobase; + int board; + unsigned int irq; + + /* allocate private area */ + ret = ni_alloc_private(dev); + if (ret < 0) + return ret; + + devpriv->stc_writew = &ni_atmio_win_out; + devpriv->stc_readw = &ni_atmio_win_in; + devpriv->stc_writel = &win_out2; + devpriv->stc_readl = &win_in2; + + iobase = it->options[0]; + irq = it->options[1]; + isapnp_dev = NULL; + if (iobase == 0) { + ret = ni_isapnp_find_board(&isapnp_dev); + if (ret < 0) + return ret; + + iobase = pnp_port_start(isapnp_dev, 0); + irq = pnp_irq(isapnp_dev, 0); + devpriv->isapnp_dev = isapnp_dev; + } + + /* reserve our I/O region */ + + printk("comedi%d: ni_atmio: 0x%04lx", dev->minor, iobase); + if (!request_region(iobase, NI_SIZE, "ni_atmio")) { + printk(" I/O port conflict\n"); + return -EIO; + } + + dev->iobase = iobase; + +#ifdef DEBUG + /* board existence sanity check */ + { + int i; + + printk(" board fingerprint:"); + for (i = 0; i < 16; i += 2) { + printk(" %04x %02x", inw(dev->iobase + i), + inb(dev->iobase + i + 1)); + } + } +#endif + + /* get board type */ + + board = ni_getboardtype(dev); + if (board < 0) + return -EIO; + + dev->board_ptr = ni_boards + board; + + printk(" %s", boardtype.name); + dev->board_name = boardtype.name; + + /* irq stuff */ + + if (irq != 0) { + if (irq > 15 || ni_irqpin[irq] == -1) { + printk(" invalid irq %u\n", irq); + return -EINVAL; + } + printk(" ( irq = %u )", irq); + ret = request_irq(irq, ni_E_interrupt, NI_E_IRQ_FLAGS, + "ni_atmio", dev); + + if (ret < 0) { + printk(" irq not available\n"); + return -EINVAL; + } + dev->irq = irq; + } + + /* generic E series stuff in ni_mio_common.c */ + + ret = ni_E_init(dev, it); + if (ret < 0) + return ret; + + + return 0; +} + +static int ni_getboardtype(struct comedi_device *dev) +{ + int device_id = ni_read_eeprom(dev, 511); + int i; + + for (i = 0; i < n_ni_boards; i++) { + if (ni_boards[i].device_id == device_id) + return i; + + } + if (device_id == 255) + printk(" can't find board\n"); + else if (device_id == 0) + printk(" EEPROM read error (?) or device not found\n"); + else + printk(" unknown device ID %d -- contact author\n", device_id); + + return -1; +} diff --git a/drivers/staging/comedi/drivers/ni_atmio16d.c b/drivers/staging/comedi/drivers/ni_atmio16d.c new file mode 100644 index 00000000..285b9335 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_atmio16d.c @@ -0,0 +1,904 @@ +/* + comedi/drivers/ni_atmio16d.c + Hardware driver for National Instruments AT-MIO16D board + Copyright (C) 2000 Chris R. Baugher + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: ni_atmio16d +Description: National Instruments AT-MIO-16D +Author: Chris R. Baugher +Status: unknown +Devices: [National Instruments] AT-MIO-16 (atmio16), AT-MIO-16D (atmio16d) +*/ +/* + * I must give credit here to Michal Dobes who + * wrote the driver for Advantec's pcl812 boards. I used the interrupt + * handling code from his driver as an example for this one. + * + * Chris Baugher + * 5/1/2000 + * + */ + +#include +#include "../comedidev.h" + +#include + +#include "8255.h" + +/* Configuration and Status Registers */ +#define COM_REG_1 0x00 /* wo 16 */ +#define STAT_REG 0x00 /* ro 16 */ +#define COM_REG_2 0x02 /* wo 16 */ +/* Event Strobe Registers */ +#define START_CONVERT_REG 0x08 /* wo 16 */ +#define START_DAQ_REG 0x0A /* wo 16 */ +#define AD_CLEAR_REG 0x0C /* wo 16 */ +#define EXT_STROBE_REG 0x0E /* wo 16 */ +/* Analog Output Registers */ +#define DAC0_REG 0x10 /* wo 16 */ +#define DAC1_REG 0x12 /* wo 16 */ +#define INT2CLR_REG 0x14 /* wo 16 */ +/* Analog Input Registers */ +#define MUX_CNTR_REG 0x04 /* wo 16 */ +#define MUX_GAIN_REG 0x06 /* wo 16 */ +#define AD_FIFO_REG 0x16 /* ro 16 */ +#define DMA_TC_INT_CLR_REG 0x16 /* wo 16 */ +/* AM9513A Counter/Timer Registers */ +#define AM9513A_DATA_REG 0x18 /* rw 16 */ +#define AM9513A_COM_REG 0x1A /* wo 16 */ +#define AM9513A_STAT_REG 0x1A /* ro 16 */ +/* MIO-16 Digital I/O Registers */ +#define MIO_16_DIG_IN_REG 0x1C /* ro 16 */ +#define MIO_16_DIG_OUT_REG 0x1C /* wo 16 */ +/* RTSI Switch Registers */ +#define RTSI_SW_SHIFT_REG 0x1E /* wo 8 */ +#define RTSI_SW_STROBE_REG 0x1F /* wo 8 */ +/* DIO-24 Registers */ +#define DIO_24_PORTA_REG 0x00 /* rw 8 */ +#define DIO_24_PORTB_REG 0x01 /* rw 8 */ +#define DIO_24_PORTC_REG 0x02 /* rw 8 */ +#define DIO_24_CNFG_REG 0x03 /* wo 8 */ + +/* Command Register bits */ +#define COMREG1_2SCADC 0x0001 +#define COMREG1_1632CNT 0x0002 +#define COMREG1_SCANEN 0x0008 +#define COMREG1_DAQEN 0x0010 +#define COMREG1_DMAEN 0x0020 +#define COMREG1_CONVINTEN 0x0080 +#define COMREG2_SCN2 0x0010 +#define COMREG2_INTEN 0x0080 +#define COMREG2_DOUTEN0 0x0100 +#define COMREG2_DOUTEN1 0x0200 +/* Status Register bits */ +#define STAT_AD_OVERRUN 0x0100 +#define STAT_AD_OVERFLOW 0x0200 +#define STAT_AD_DAQPROG 0x0800 +#define STAT_AD_CONVAVAIL 0x2000 +#define STAT_AD_DAQSTOPINT 0x4000 +/* AM9513A Counter/Timer defines */ +#define CLOCK_1_MHZ 0x8B25 +#define CLOCK_100_KHZ 0x8C25 +#define CLOCK_10_KHZ 0x8D25 +#define CLOCK_1_KHZ 0x8E25 +#define CLOCK_100_HZ 0x8F25 +/* Other miscellaneous defines */ +#define ATMIO16D_SIZE 32 /* bus address range */ +#define devpriv ((struct atmio16d_private *)dev->private) +#define ATMIO16D_TIMEOUT 10 + +struct atmio16_board_t { + + const char *name; + int has_8255; +}; + +static const struct atmio16_board_t atmio16_boards[] = { + { + .name = "atmio16", + .has_8255 = 0, + }, + { + .name = "atmio16d", + .has_8255 = 1, + }, +}; + +#define n_atmio16_boards ARRAY_SIZE(atmio16_boards) + +#define boardtype ((const struct atmio16_board_t *)dev->board_ptr) + +/* function prototypes */ +static int atmio16d_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int atmio16d_detach(struct comedi_device *dev); +static irqreturn_t atmio16d_interrupt(int irq, void *d); +static int atmio16d_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int atmio16d_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s); +static int atmio16d_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void reset_counters(struct comedi_device *dev); +static void reset_atmio16d(struct comedi_device *dev); + +/* main driver struct */ +static struct comedi_driver driver_atmio16d = { + .driver_name = "atmio16", + .module = THIS_MODULE, + .attach = atmio16d_attach, + .detach = atmio16d_detach, + .board_name = &atmio16_boards[0].name, + .num_names = n_atmio16_boards, + .offset = sizeof(struct atmio16_board_t), +}; + +static int __init driver_atmio16d_init_module(void) +{ + return comedi_driver_register(&driver_atmio16d); +} + +static void __exit driver_atmio16d_cleanup_module(void) +{ + comedi_driver_unregister(&driver_atmio16d); +} + +module_init(driver_atmio16d_init_module); +module_exit(driver_atmio16d_cleanup_module); + +/* range structs */ +static const struct comedi_lrange range_atmio16d_ai_10_bipolar = { 4, { + BIP_RANGE + (10), + BIP_RANGE + (1), + BIP_RANGE + (0.1), + BIP_RANGE + (0.02) + } +}; + +static const struct comedi_lrange range_atmio16d_ai_5_bipolar = { 4, { + BIP_RANGE + (5), + BIP_RANGE + (0.5), + BIP_RANGE + (0.05), + BIP_RANGE + (0.01) + } +}; + +static const struct comedi_lrange range_atmio16d_ai_unipolar = { 4, { + UNI_RANGE + (10), + UNI_RANGE + (1), + UNI_RANGE + (0.1), + UNI_RANGE + (0.02) + } +}; + +/* private data struct */ +struct atmio16d_private { + enum { adc_diff, adc_singleended } adc_mux; + enum { adc_bipolar10, adc_bipolar5, adc_unipolar10 } adc_range; + enum { adc_2comp, adc_straight } adc_coding; + enum { dac_bipolar, dac_unipolar } dac0_range, dac1_range; + enum { dac_internal, dac_external } dac0_reference, dac1_reference; + enum { dac_2comp, dac_straight } dac0_coding, dac1_coding; + const struct comedi_lrange *ao_range_type_list[2]; + unsigned int ao_readback[2]; + unsigned int com_reg_1_state; /* current state of command register 1 */ + unsigned int com_reg_2_state; /* current state of command register 2 */ +}; + +static void reset_counters(struct comedi_device *dev) +{ + /* Counter 2 */ + outw(0xFFC2, dev->iobase + AM9513A_COM_REG); + outw(0xFF02, dev->iobase + AM9513A_COM_REG); + outw(0x4, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0A, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + outw(0xFF42, dev->iobase + AM9513A_COM_REG); + outw(0xFF42, dev->iobase + AM9513A_COM_REG); + /* Counter 3 */ + outw(0xFFC4, dev->iobase + AM9513A_COM_REG); + outw(0xFF03, dev->iobase + AM9513A_COM_REG); + outw(0x4, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0B, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + outw(0xFF44, dev->iobase + AM9513A_COM_REG); + outw(0xFF44, dev->iobase + AM9513A_COM_REG); + /* Counter 4 */ + outw(0xFFC8, dev->iobase + AM9513A_COM_REG); + outw(0xFF04, dev->iobase + AM9513A_COM_REG); + outw(0x4, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0C, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + outw(0xFF48, dev->iobase + AM9513A_COM_REG); + outw(0xFF48, dev->iobase + AM9513A_COM_REG); + /* Counter 5 */ + outw(0xFFD0, dev->iobase + AM9513A_COM_REG); + outw(0xFF05, dev->iobase + AM9513A_COM_REG); + outw(0x4, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0D, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + outw(0xFF50, dev->iobase + AM9513A_COM_REG); + outw(0xFF50, dev->iobase + AM9513A_COM_REG); + + outw(0, dev->iobase + AD_CLEAR_REG); +} + +static void reset_atmio16d(struct comedi_device *dev) +{ + int i; + + /* now we need to initialize the board */ + outw(0, dev->iobase + COM_REG_1); + outw(0, dev->iobase + COM_REG_2); + outw(0, dev->iobase + MUX_GAIN_REG); + /* init AM9513A timer */ + outw(0xFFFF, dev->iobase + AM9513A_COM_REG); + outw(0xFFEF, dev->iobase + AM9513A_COM_REG); + outw(0xFF17, dev->iobase + AM9513A_COM_REG); + outw(0xF000, dev->iobase + AM9513A_DATA_REG); + for (i = 1; i <= 5; ++i) { + outw(0xFF00 + i, dev->iobase + AM9513A_COM_REG); + outw(0x0004, dev->iobase + AM9513A_DATA_REG); + outw(0xFF08 + i, dev->iobase + AM9513A_COM_REG); + outw(0x3, dev->iobase + AM9513A_DATA_REG); + } + outw(0xFF5F, dev->iobase + AM9513A_COM_REG); + /* timer init done */ + outw(0, dev->iobase + AD_CLEAR_REG); + outw(0, dev->iobase + INT2CLR_REG); + /* select straight binary mode for Analog Input */ + devpriv->com_reg_1_state |= 1; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + devpriv->adc_coding = adc_straight; + /* zero the analog outputs */ + outw(2048, dev->iobase + DAC0_REG); + outw(2048, dev->iobase + DAC1_REG); +} + +static irqreturn_t atmio16d_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + +#ifdef DEBUG1 + printk(KERN_DEBUG "atmio16d_interrupt!\n"); +#endif + + comedi_buf_put(s->async, inw(dev->iobase + AD_FIFO_REG)); + + comedi_event(dev, s); + return IRQ_HANDLED; +} + +static int atmio16d_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0, tmp; +#ifdef DEBUG1 + printk(KERN_DEBUG "atmio16d_ai_cmdtest\n"); +#endif + /* make sure triggers are valid */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique & mutually compatible */ + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } else { +#if 0 + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } +#endif + } + + if (cmd->convert_arg < 10000) { + cmd->convert_arg = 10000; + err++; + } +#if 0 + if (cmd->convert_arg > SLOWEST_TIMER) { + cmd->convert_arg = SLOWEST_TIMER; + err++; + } +#endif + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + return 0; +} + +static int atmio16d_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int timer, base_clock; + unsigned int sample_count, tmp, chan, gain; + int i; +#ifdef DEBUG1 + printk(KERN_DEBUG "atmio16d_ai_cmd\n"); +#endif + /* This is slowly becoming a working command interface. * + * It is still uber-experimental */ + + reset_counters(dev); + s->async->cur_chan = 0; + + /* check if scanning multiple channels */ + if (cmd->chanlist_len < 2) { + devpriv->com_reg_1_state &= ~COMREG1_SCANEN; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + } else { + devpriv->com_reg_1_state |= COMREG1_SCANEN; + devpriv->com_reg_2_state |= COMREG2_SCN2; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2); + } + + /* Setup the Mux-Gain Counter */ + for (i = 0; i < cmd->chanlist_len; ++i) { + chan = CR_CHAN(cmd->chanlist[i]); + gain = CR_RANGE(cmd->chanlist[i]); + outw(i, dev->iobase + MUX_CNTR_REG); + tmp = chan | (gain << 6); + if (i == cmd->scan_end_arg - 1) + tmp |= 0x0010; /* set LASTONE bit */ + outw(tmp, dev->iobase + MUX_GAIN_REG); + } + + /* Now program the sample interval timer */ + /* Figure out which clock to use then get an + * appropriate timer value */ + if (cmd->convert_arg < 65536000) { + base_clock = CLOCK_1_MHZ; + timer = cmd->convert_arg / 1000; + } else if (cmd->convert_arg < 655360000) { + base_clock = CLOCK_100_KHZ; + timer = cmd->convert_arg / 10000; + } else if (cmd->convert_arg <= 0xffffffff /* 6553600000 */) { + base_clock = CLOCK_10_KHZ; + timer = cmd->convert_arg / 100000; + } else if (cmd->convert_arg <= 0xffffffff /* 65536000000 */) { + base_clock = CLOCK_1_KHZ; + timer = cmd->convert_arg / 1000000; + } + outw(0xFF03, dev->iobase + AM9513A_COM_REG); + outw(base_clock, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0B, dev->iobase + AM9513A_COM_REG); + outw(0x2, dev->iobase + AM9513A_DATA_REG); + outw(0xFF44, dev->iobase + AM9513A_COM_REG); + outw(0xFFF3, dev->iobase + AM9513A_COM_REG); + outw(timer, dev->iobase + AM9513A_DATA_REG); + outw(0xFF24, dev->iobase + AM9513A_COM_REG); + + /* Now figure out how many samples to get */ + /* and program the sample counter */ + sample_count = cmd->stop_arg * cmd->scan_end_arg; + outw(0xFF04, dev->iobase + AM9513A_COM_REG); + outw(0x1025, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0C, dev->iobase + AM9513A_COM_REG); + if (sample_count < 65536) { + /* use only Counter 4 */ + outw(sample_count, dev->iobase + AM9513A_DATA_REG); + outw(0xFF48, dev->iobase + AM9513A_COM_REG); + outw(0xFFF4, dev->iobase + AM9513A_COM_REG); + outw(0xFF28, dev->iobase + AM9513A_COM_REG); + devpriv->com_reg_1_state &= ~COMREG1_1632CNT; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + } else { + /* Counter 4 and 5 are needed */ + + tmp = sample_count & 0xFFFF; + if (tmp) + outw(tmp - 1, dev->iobase + AM9513A_DATA_REG); + else + outw(0xFFFF, dev->iobase + AM9513A_DATA_REG); + + outw(0xFF48, dev->iobase + AM9513A_COM_REG); + outw(0, dev->iobase + AM9513A_DATA_REG); + outw(0xFF28, dev->iobase + AM9513A_COM_REG); + outw(0xFF05, dev->iobase + AM9513A_COM_REG); + outw(0x25, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0D, dev->iobase + AM9513A_COM_REG); + tmp = sample_count & 0xFFFF; + if ((tmp == 0) || (tmp == 1)) { + outw((sample_count >> 16) & 0xFFFF, + dev->iobase + AM9513A_DATA_REG); + } else { + outw(((sample_count >> 16) & 0xFFFF) + 1, + dev->iobase + AM9513A_DATA_REG); + } + outw(0xFF70, dev->iobase + AM9513A_COM_REG); + devpriv->com_reg_1_state |= COMREG1_1632CNT; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + } + + /* Program the scan interval timer ONLY IF SCANNING IS ENABLED */ + /* Figure out which clock to use then get an + * appropriate timer value */ + if (cmd->chanlist_len > 1) { + if (cmd->scan_begin_arg < 65536000) { + base_clock = CLOCK_1_MHZ; + timer = cmd->scan_begin_arg / 1000; + } else if (cmd->scan_begin_arg < 655360000) { + base_clock = CLOCK_100_KHZ; + timer = cmd->scan_begin_arg / 10000; + } else if (cmd->scan_begin_arg < 0xffffffff /* 6553600000 */) { + base_clock = CLOCK_10_KHZ; + timer = cmd->scan_begin_arg / 100000; + } else if (cmd->scan_begin_arg < 0xffffffff /* 65536000000 */) { + base_clock = CLOCK_1_KHZ; + timer = cmd->scan_begin_arg / 1000000; + } + outw(0xFF02, dev->iobase + AM9513A_COM_REG); + outw(base_clock, dev->iobase + AM9513A_DATA_REG); + outw(0xFF0A, dev->iobase + AM9513A_COM_REG); + outw(0x2, dev->iobase + AM9513A_DATA_REG); + outw(0xFF42, dev->iobase + AM9513A_COM_REG); + outw(0xFFF2, dev->iobase + AM9513A_COM_REG); + outw(timer, dev->iobase + AM9513A_DATA_REG); + outw(0xFF22, dev->iobase + AM9513A_COM_REG); + } + + /* Clear the A/D FIFO and reset the MUX counter */ + outw(0, dev->iobase + AD_CLEAR_REG); + outw(0, dev->iobase + MUX_CNTR_REG); + outw(0, dev->iobase + INT2CLR_REG); + /* enable this acquisition operation */ + devpriv->com_reg_1_state |= COMREG1_DAQEN; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + /* enable interrupts for conversion completion */ + devpriv->com_reg_1_state |= COMREG1_CONVINTEN; + devpriv->com_reg_2_state |= COMREG2_INTEN; + outw(devpriv->com_reg_1_state, dev->iobase + COM_REG_1); + outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2); + /* apply a trigger. this starts the counters! */ + outw(0, dev->iobase + START_DAQ_REG); + + return 0; +} + +/* This will cancel a running acquisition operation */ +static int atmio16d_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + reset_atmio16d(dev); + + return 0; +} + +/* Mode 0 is used to get a single conversion on demand */ +static int atmio16d_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, t; + int chan; + int gain; + int status; + +#ifdef DEBUG1 + printk(KERN_DEBUG "atmio16d_ai_insn_read\n"); +#endif + chan = CR_CHAN(insn->chanspec); + gain = CR_RANGE(insn->chanspec); + + /* reset the Analog input circuitry */ + /* outw( 0, dev->iobase+AD_CLEAR_REG ); */ + /* reset the Analog Input MUX Counter to 0 */ + /* outw( 0, dev->iobase+MUX_CNTR_REG ); */ + + /* set the Input MUX gain */ + outw(chan | (gain << 6), dev->iobase + MUX_GAIN_REG); + + for (i = 0; i < insn->n; i++) { + /* start the conversion */ + outw(0, dev->iobase + START_CONVERT_REG); + /* wait for it to finish */ + for (t = 0; t < ATMIO16D_TIMEOUT; t++) { + /* check conversion status */ + status = inw(dev->iobase + STAT_REG); +#ifdef DEBUG1 + printk(KERN_DEBUG "status=%x\n", status); +#endif + if (status & STAT_AD_CONVAVAIL) { + /* read the data now */ + data[i] = inw(dev->iobase + AD_FIFO_REG); + /* change to two's complement if need be */ + if (devpriv->adc_coding == adc_2comp) + data[i] ^= 0x800; + break; + } + if (status & STAT_AD_OVERFLOW) { + printk(KERN_INFO "atmio16d: a/d FIFO overflow\n"); + outw(0, dev->iobase + AD_CLEAR_REG); + + return -ETIME; + } + } + /* end waiting, now check if it timed out */ + if (t == ATMIO16D_TIMEOUT) { + printk(KERN_INFO "atmio16d: timeout\n"); + + return -ETIME; + } + } + + return i; +} + +static int atmio16d_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; +#ifdef DEBUG1 + printk(KERN_DEBUG "atmio16d_ao_insn_read\n"); +#endif + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + return i; +} + +static int atmio16d_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan; + int d; +#ifdef DEBUG1 + printk(KERN_DEBUG "atmio16d_ao_insn_write\n"); +#endif + + chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + d = data[i]; + switch (chan) { + case 0: + if (devpriv->dac0_coding == dac_2comp) + d ^= 0x800; + outw(d, dev->iobase + DAC0_REG); + break; + case 1: + if (devpriv->dac1_coding == dac_2comp) + d ^= 0x800; + outw(d, dev->iobase + DAC1_REG); + break; + default: + return -EINVAL; + } + devpriv->ao_readback[chan] = data[i]; + } + return i; +} + +static int atmio16d_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] | data[1]); + outw(s->state, dev->iobase + MIO_16_DIG_OUT_REG); + } + data[1] = inw(dev->iobase + MIO_16_DIG_IN_REG); + + return 2; +} + +static int atmio16d_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + int i; + int mask; + + for (i = 0; i < insn->n; i++) { + mask = (CR_CHAN(insn->chanspec) < 4) ? 0x0f : 0xf0; + s->io_bits &= ~mask; + if (data[i]) + s->io_bits |= mask; + } + devpriv->com_reg_2_state &= ~(COMREG2_DOUTEN0 | COMREG2_DOUTEN1); + if (s->io_bits & 0x0f) + devpriv->com_reg_2_state |= COMREG2_DOUTEN0; + if (s->io_bits & 0xf0) + devpriv->com_reg_2_state |= COMREG2_DOUTEN1; + outw(devpriv->com_reg_2_state, dev->iobase + COM_REG_2); + + return i; +} + +/* + options[0] - I/O port + options[1] - MIO irq + 0 == no irq + N == irq N {3,4,5,6,7,9,10,11,12,14,15} + options[2] - DIO irq + 0 == no irq + N == irq N {3,4,5,6,7,9} + options[3] - DMA1 channel + 0 == no DMA + N == DMA N {5,6,7} + options[4] - DMA2 channel + 0 == no DMA + N == DMA N {5,6,7} + + options[5] - a/d mux + 0=differential, 1=single + options[6] - a/d range + 0=bipolar10, 1=bipolar5, 2=unipolar10 + + options[7] - dac0 range + 0=bipolar, 1=unipolar + options[8] - dac0 reference + 0=internal, 1=external + options[9] - dac0 coding + 0=2's comp, 1=straight binary + + options[10] - dac1 range + options[11] - dac1 reference + options[12] - dac1 coding + */ + +static int atmio16d_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + unsigned int irq; + unsigned long iobase; + int ret; + + struct comedi_subdevice *s; + + /* make sure the address range is free and allocate it */ + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: atmio16d: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, ATMIO16D_SIZE, "ni_atmio16d")) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + /* board name */ + dev->board_name = boardtype->name; + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + ret = alloc_private(dev, sizeof(struct atmio16d_private)); + if (ret < 0) + return ret; + + /* reset the atmio16d hardware */ + reset_atmio16d(dev); + + /* check if our interrupt is available and get it */ + irq = it->options[1]; + if (irq) { + + ret = request_irq(irq, atmio16d_interrupt, 0, "atmio16d", dev); + if (ret < 0) { + printk(KERN_INFO "failed to allocate irq %u\n", irq); + return ret; + } + dev->irq = irq; + printk(KERN_INFO "( irq = %u )\n", irq); + } else { + printk(KERN_INFO "( no irq )"); + } + + /* set device options */ + devpriv->adc_mux = it->options[5]; + devpriv->adc_range = it->options[6]; + + devpriv->dac0_range = it->options[7]; + devpriv->dac0_reference = it->options[8]; + devpriv->dac0_coding = it->options[9]; + devpriv->dac1_range = it->options[10]; + devpriv->dac1_reference = it->options[11]; + devpriv->dac1_coding = it->options[12]; + + /* setup sub-devices */ + s = dev->subdevices + 0; + dev->read_subdev = s; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + s->n_chan = (devpriv->adc_mux ? 16 : 8); + s->len_chanlist = 16; + s->insn_read = atmio16d_ai_insn_read; + s->do_cmdtest = atmio16d_ai_cmdtest; + s->do_cmd = atmio16d_ai_cmd; + s->cancel = atmio16d_ai_cancel; + s->maxdata = 0xfff; /* 4095 decimal */ + switch (devpriv->adc_range) { + case adc_bipolar10: + s->range_table = &range_atmio16d_ai_10_bipolar; + break; + case adc_bipolar5: + s->range_table = &range_atmio16d_ai_5_bipolar; + break; + case adc_unipolar10: + s->range_table = &range_atmio16d_ai_unipolar; + break; + } + + /* ao subdevice */ + s++; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->insn_read = atmio16d_ao_insn_read; + s->insn_write = atmio16d_ao_insn_write; + s->maxdata = 0xfff; /* 4095 decimal */ + s->range_table_list = devpriv->ao_range_type_list; + switch (devpriv->dac0_range) { + case dac_bipolar: + devpriv->ao_range_type_list[0] = &range_bipolar10; + break; + case dac_unipolar: + devpriv->ao_range_type_list[0] = &range_unipolar10; + break; + } + switch (devpriv->dac1_range) { + case dac_bipolar: + devpriv->ao_range_type_list[1] = &range_bipolar10; + break; + case dac_unipolar: + devpriv->ao_range_type_list[1] = &range_unipolar10; + break; + } + + /* Digital I/O */ + s++; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 8; + s->insn_bits = atmio16d_dio_insn_bits; + s->insn_config = atmio16d_dio_insn_config; + s->maxdata = 1; + s->range_table = &range_digital; + + /* 8255 subdevice */ + s++; + if (boardtype->has_8255) + subdev_8255_init(dev, s, NULL, dev->iobase); + else + s->type = COMEDI_SUBD_UNUSED; + +/* don't yet know how to deal with counter/timers */ +#if 0 + s++; + /* do */ + s->type = COMEDI_SUBD_TIMER; + s->n_chan = 0; + s->maxdata = 0 +#endif + printk("\n"); + + return 0; +} + +static int atmio16d_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: atmio16d: remove\n", dev->minor); + + if (dev->subdevices && boardtype->has_8255) + subdev_8255_cleanup(dev, dev->subdevices + 3); + + if (dev->irq) + free_irq(dev->irq, dev); + + reset_atmio16d(dev); + + if (dev->iobase) + release_region(dev->iobase, ATMIO16D_SIZE); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_daq_700.c b/drivers/staging/comedi/drivers/ni_daq_700.c new file mode 100644 index 00000000..e2420123 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_daq_700.c @@ -0,0 +1,605 @@ +/* + * comedi/drivers/ni_daq_700.c + * Driver for DAQCard-700 DIO only + * copied from 8255 + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 1998 David A. Schleef + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* +Driver: ni_daq_700 +Description: National Instruments PCMCIA DAQCard-700 DIO only +Author: Fred Brooks , + based on ni_daq_dio24 by Daniel Vecino Castel +Devices: [National Instruments] PCMCIA DAQ-Card-700 (ni_daq_700) +Status: works +Updated: Thu, 21 Feb 2008 12:07:20 +0000 + +The daqcard-700 appears in Comedi as a single digital I/O subdevice with +16 channels. The channel 0 corresponds to the daqcard-700's output +port, bit 0; channel 8 corresponds to the input port, bit 0. + +Direction configuration: channels 0-7 output, 8-15 input (8225 device +emu as port A output, port B input, port C N/A). + +IRQ is assigned but not used. +*/ + +#include +#include +#include "../comedidev.h" + +#include + +#include +#include +#include + +static struct pcmcia_device *pcmcia_cur_dev; + +#define DIO700_SIZE 8 /* size of io region used by board */ + +static int dio700_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int dio700_detach(struct comedi_device *dev); + +enum dio700_bustype { pcmcia_bustype }; + +struct dio700_board { + const char *name; + int device_id; /* device id for pcmcia board */ + enum dio700_bustype bustype; /* PCMCIA */ + int have_dio; /* have daqcard-700 dio */ + /* function pointers so we can use inb/outb or readb/writeb */ + /* as appropriate */ + unsigned int (*read_byte) (unsigned int address); + void (*write_byte) (unsigned int byte, unsigned int address); +}; + +static const struct dio700_board dio700_boards[] = { + { + .name = "daqcard-700", + /* 0x10b is manufacturer id, 0x4743 is device id */ + .device_id = 0x4743, + .bustype = pcmcia_bustype, + .have_dio = 1, + }, + { + .name = "ni_daq_700", + /* 0x10b is manufacturer id, 0x4743 is device id */ + .device_id = 0x4743, + .bustype = pcmcia_bustype, + .have_dio = 1, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct dio700_board *)dev->board_ptr) + +struct dio700_private { + + int data; /* number of data points left to be taken */ +}; + +#define devpriv ((struct dio700_private *)dev->private) + +static struct comedi_driver driver_dio700 = { + .driver_name = "ni_daq_700", + .module = THIS_MODULE, + .attach = dio700_attach, + .detach = dio700_detach, + .num_names = ARRAY_SIZE(dio700_boards), + .board_name = &dio700_boards[0].name, + .offset = sizeof(struct dio700_board), +}; + +/* the real driver routines */ + +#define _700_SIZE 8 + +#define _700_DATA 0 + +#define DIO_W 0x04 +#define DIO_R 0x05 + +struct subdev_700_struct { + unsigned long cb_arg; + int (*cb_func) (int, int, int, unsigned long); + int have_irq; +}; + +#define CALLBACK_ARG (((struct subdev_700_struct *)s->private)->cb_arg) +#define CALLBACK_FUNC (((struct subdev_700_struct *)s->private)->cb_func) +#define subdevpriv ((struct subdev_700_struct *)s->private) + +static void do_config(struct comedi_device *dev, struct comedi_subdevice *s); + +void subdev_700_interrupt(struct comedi_device *dev, struct comedi_subdevice *s) +{ + short d; + + d = CALLBACK_FUNC(0, _700_DATA, 0, CALLBACK_ARG); + + comedi_buf_put(s->async, d); + s->async->events |= COMEDI_CB_EOS; + + comedi_event(dev, s); +} +EXPORT_SYMBOL(subdev_700_interrupt); + +static int subdev_700_cb(int dir, int port, int data, unsigned long arg) +{ + /* port is always A for output and B for input (8255 emu) */ + unsigned long iobase = arg; + + if (dir) { + outb(data, iobase + DIO_W); + return 0; + } else { + return inb(iobase + DIO_R); + } +} + +static int subdev_700_insn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + + if (data[0] & 0xff) + CALLBACK_FUNC(1, _700_DATA, s->state & 0xff, + CALLBACK_ARG); + } + + data[1] = s->state & 0xff; + data[1] |= CALLBACK_FUNC(0, _700_DATA, 0, CALLBACK_ARG) << 8; + + return 2; +} + +static int subdev_700_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + switch (data[0]) { + case INSN_CONFIG_DIO_INPUT: + break; + case INSN_CONFIG_DIO_OUTPUT: + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s-> + io_bits & (1 << CR_CHAN(insn->chanspec))) ? COMEDI_OUTPUT : + COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + } + + return 1; +} + +static void do_config(struct comedi_device *dev, struct comedi_subdevice *s) +{ /* use powerup defaults */ + return; +} + +static int subdev_700_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int tmp; + + /* step 1 */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_FOLLOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2 */ + + if (err) + return 2; + + /* step 3 */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + if (cmd->scan_end_arg != 1) { + cmd->scan_end_arg = 1; + err++; + } + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + + if (err) + return 3; + + /* step 4 */ + + if (err) + return 4; + + return 0; +} + +static int subdev_700_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + /* FIXME */ + + return 0; +} + +static int subdev_700_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + /* FIXME */ + + return 0; +} + +int subdev_700_init(struct comedi_device *dev, struct comedi_subdevice *s, + int (*cb) (int, int, int, unsigned long), unsigned long arg) +{ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 16; + s->range_table = &range_digital; + s->maxdata = 1; + + s->private = kmalloc(sizeof(struct subdev_700_struct), GFP_KERNEL); + if (!s->private) + return -ENOMEM; + + CALLBACK_ARG = arg; + if (cb == NULL) + CALLBACK_FUNC = subdev_700_cb; + else + CALLBACK_FUNC = cb; + + s->insn_bits = subdev_700_insn; + s->insn_config = subdev_700_insn_config; + + s->state = 0; + s->io_bits = 0x00ff; + do_config(dev, s); + + return 0; +} +EXPORT_SYMBOL(subdev_700_init); + +int subdev_700_init_irq(struct comedi_device *dev, struct comedi_subdevice *s, + int (*cb) (int, int, int, unsigned long), + unsigned long arg) +{ + int ret; + + ret = subdev_700_init(dev, s, cb, arg); + if (ret < 0) + return ret; + + s->do_cmdtest = subdev_700_cmdtest; + s->do_cmd = subdev_700_cmd; + s->cancel = subdev_700_cancel; + + subdevpriv->have_irq = 1; + + return 0; +} +EXPORT_SYMBOL(subdev_700_init_irq); + +void subdev_700_cleanup(struct comedi_device *dev, struct comedi_subdevice *s) +{ + if (s->private) + if (subdevpriv->have_irq) + + kfree(s->private); +} +EXPORT_SYMBOL(subdev_700_cleanup); + +static int dio700_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase = 0; +#ifdef incomplete + unsigned int irq = 0; +#endif + struct pcmcia_device *link; + + /* allocate and initialize dev->private */ + if (alloc_private(dev, sizeof(struct dio700_private)) < 0) + return -ENOMEM; + + /* get base address, irq etc. based on bustype */ + switch (thisboard->bustype) { + case pcmcia_bustype: + link = pcmcia_cur_dev; /* XXX hack */ + if (!link) + return -EIO; + iobase = link->resource[0]->start; +#ifdef incomplete + irq = link->irq; +#endif + break; + default: + printk(KERN_ERR "bug! couldn't determine board type\n"); + return -EINVAL; + break; + } + printk(KERN_ERR "comedi%d: ni_daq_700: %s, io 0x%lx", dev->minor, + thisboard->name, iobase); +#ifdef incomplete + if (irq) + printk(", irq %u", irq); + +#endif + + printk("\n"); + + if (iobase == 0) { + printk(KERN_ERR "io base address is zero!\n"); + return -EINVAL; + } + + dev->iobase = iobase; + +#ifdef incomplete + /* grab our IRQ */ + dev->irq = irq; +#endif + + dev->board_name = thisboard->name; + + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + + /* DAQCard-700 dio */ + s = dev->subdevices + 0; + subdev_700_init(dev, s, NULL, dev->iobase); + + return 0; +}; + +static int dio700_detach(struct comedi_device *dev) +{ + printk(KERN_ERR "comedi%d: ni_daq_700: cs-remove\n", dev->minor); + + if (dev->subdevices) + subdev_700_cleanup(dev, dev->subdevices + 0); + + if (thisboard->bustype != pcmcia_bustype && dev->iobase) + release_region(dev->iobase, DIO700_SIZE); + if (dev->irq) + free_irq(dev->irq, dev); + + return 0; +}; + +static void dio700_config(struct pcmcia_device *link); +static void dio700_release(struct pcmcia_device *link); +static int dio700_cs_suspend(struct pcmcia_device *p_dev); +static int dio700_cs_resume(struct pcmcia_device *p_dev); + +static int dio700_cs_attach(struct pcmcia_device *); +static void dio700_cs_detach(struct pcmcia_device *); + +struct local_info_t { + struct pcmcia_device *link; + int stop; + struct bus_operations *bus; +}; + +static int dio700_cs_attach(struct pcmcia_device *link) +{ + struct local_info_t *local; + + printk(KERN_INFO "ni_daq_700: cs-attach\n"); + + dev_dbg(&link->dev, "dio700_cs_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kzalloc(sizeof(struct local_info_t), GFP_KERNEL); + if (!local) + return -ENOMEM; + local->link = link; + link->priv = local; + + pcmcia_cur_dev = link; + + dio700_config(link); + + return 0; +} /* dio700_cs_attach */ + +static void dio700_cs_detach(struct pcmcia_device *link) +{ + + printk(KERN_INFO "ni_daq_700: cs-detach!\n"); + + dev_dbg(&link->dev, "dio700_cs_detach\n"); + + ((struct local_info_t *)link->priv)->stop = 1; + dio700_release(link); + + /* This points to the parent struct local_info_t struct */ + kfree(link->priv); + +} /* dio700_cs_detach */ + +static int dio700_pcmcia_config_loop(struct pcmcia_device *p_dev, + void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(p_dev); +} + +static void dio700_config(struct pcmcia_device *link) +{ + int ret; + + printk(KERN_INFO "ni_daq_700: cs-config\n"); + + dev_dbg(&link->dev, "dio700_config\n"); + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_AUDIO | + CONF_AUTO_SET_IO; + + ret = pcmcia_loop_config(link, dio700_pcmcia_config_loop, NULL); + if (ret) { + dev_warn(&link->dev, "no configuration found\n"); + goto failed; + } + + if (!link->irq) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret != 0) + goto failed; + + return; + +failed: + printk(KERN_INFO "ni_daq_700 cs failed"); + dio700_release(link); + +} /* dio700_config */ + +static void dio700_release(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "dio700_release\n"); + + pcmcia_disable_device(link); +} /* dio700_release */ + +static int dio700_cs_suspend(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + /* Mark the device as stopped, to block IO until later */ + local->stop = 1; + return 0; +} /* dio700_cs_suspend */ + +static int dio700_cs_resume(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + local->stop = 0; + return 0; +} /* dio700_cs_resume */ + +/*====================================================================*/ + +static const struct pcmcia_device_id dio700_cs_ids[] = { + /* N.B. These IDs should match those in dio700_boards */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x4743), /* daqcard-700 */ + PCMCIA_DEVICE_NULL +}; + + +MODULE_DEVICE_TABLE(pcmcia, dio700_cs_ids); +MODULE_AUTHOR("Fred Brooks "); +MODULE_DESCRIPTION("Comedi driver for National Instruments " + "PCMCIA DAQCard-700 DIO"); +MODULE_LICENSE("GPL"); + +struct pcmcia_driver dio700_cs_driver = { + .probe = dio700_cs_attach, + .remove = dio700_cs_detach, + .suspend = dio700_cs_suspend, + .resume = dio700_cs_resume, + .id_table = dio700_cs_ids, + .owner = THIS_MODULE, + .name = "ni_daq_700", +}; + +static int __init init_dio700_cs(void) +{ + pcmcia_register_driver(&dio700_cs_driver); + return 0; +} + +static void __exit exit_dio700_cs(void) +{ + pr_debug("ni_daq_700: unloading\n"); + pcmcia_unregister_driver(&dio700_cs_driver); +} + +int __init init_module(void) +{ + int ret; + + ret = init_dio700_cs(); + if (ret < 0) + return ret; + + return comedi_driver_register(&driver_dio700); +} + +void __exit cleanup_module(void) +{ + exit_dio700_cs(); + comedi_driver_unregister(&driver_dio700); +} diff --git a/drivers/staging/comedi/drivers/ni_daq_dio24.c b/drivers/staging/comedi/drivers/ni_daq_dio24.c new file mode 100644 index 00000000..c0423a8c --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_daq_dio24.c @@ -0,0 +1,353 @@ +/* + comedi/drivers/ni_daq_dio24.c + Driver for National Instruments PCMCIA DAQ-Card DIO-24 + Copyright (C) 2002 Daniel Vecino Castel + + PCMCIA crap at end of file is adapted from dummy_cs.c 1.31 2001/08/24 12:13:13 + from the pcmcia package. + The initial developer of the pcmcia dummy_cs.c code is David A. Hinds + . Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ +*/ +/* +Driver: ni_daq_dio24 +Description: National Instruments PCMCIA DAQ-Card DIO-24 +Author: Daniel Vecino Castel +Devices: [National Instruments] PCMCIA DAQ-Card DIO-24 (ni_daq_dio24) +Status: ? +Updated: Thu, 07 Nov 2002 21:53:06 -0800 + +This is just a wrapper around the 8255.o driver to properly handle +the PCMCIA interface. +*/ + + /* #define LABPC_DEBUG *//* enable debugging messages */ +#undef LABPC_DEBUG + +#include +#include +#include "../comedidev.h" + +#include + +#include "8255.h" + +#include +#include +#include + +static struct pcmcia_device *pcmcia_cur_dev; + +#define DIO24_SIZE 4 /* size of io region used by board */ + +static int dio24_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int dio24_detach(struct comedi_device *dev); + +enum dio24_bustype { pcmcia_bustype }; + +struct dio24_board_struct { + const char *name; + int device_id; /* device id for pcmcia board */ + enum dio24_bustype bustype; /* PCMCIA */ + int have_dio; /* have 8255 chip */ + /* function pointers so we can use inb/outb or readb/writeb as appropriate */ + unsigned int (*read_byte) (unsigned int address); + void (*write_byte) (unsigned int byte, unsigned int address); +}; + +static const struct dio24_board_struct dio24_boards[] = { + { + .name = "daqcard-dio24", + .device_id = 0x475c, /* 0x10b is manufacturer id, 0x475c is device id */ + .bustype = pcmcia_bustype, + .have_dio = 1, + }, + { + .name = "ni_daq_dio24", + .device_id = 0x475c, /* 0x10b is manufacturer id, 0x475c is device id */ + .bustype = pcmcia_bustype, + .have_dio = 1, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct dio24_board_struct *)dev->board_ptr) + +struct dio24_private { + + int data; /* number of data points left to be taken */ +}; + +#define devpriv ((struct dio24_private *)dev->private) + +static struct comedi_driver driver_dio24 = { + .driver_name = "ni_daq_dio24", + .module = THIS_MODULE, + .attach = dio24_attach, + .detach = dio24_detach, + .num_names = ARRAY_SIZE(dio24_boards), + .board_name = &dio24_boards[0].name, + .offset = sizeof(struct dio24_board_struct), +}; + +static int dio24_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase = 0; +#ifdef incomplete + unsigned int irq = 0; +#endif + struct pcmcia_device *link; + + /* allocate and initialize dev->private */ + if (alloc_private(dev, sizeof(struct dio24_private)) < 0) + return -ENOMEM; + + /* get base address, irq etc. based on bustype */ + switch (thisboard->bustype) { + case pcmcia_bustype: + link = pcmcia_cur_dev; /* XXX hack */ + if (!link) + return -EIO; + iobase = link->resource[0]->start; +#ifdef incomplete + irq = link->irq; +#endif + break; + default: + pr_err("bug! couldn't determine board type\n"); + return -EINVAL; + break; + } + pr_debug("comedi%d: ni_daq_dio24: %s, io 0x%lx", dev->minor, + thisboard->name, iobase); +#ifdef incomplete + if (irq) + pr_debug("irq %u\n", irq); +#endif + + if (iobase == 0) { + pr_err("io base address is zero!\n"); + return -EINVAL; + } + + dev->iobase = iobase; + +#ifdef incomplete + /* grab our IRQ */ + dev->irq = irq; +#endif + + dev->board_name = thisboard->name; + + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + + /* 8255 dio */ + s = dev->subdevices + 0; + subdev_8255_init(dev, s, NULL, dev->iobase); + + return 0; +}; + +static int dio24_detach(struct comedi_device *dev) +{ + dev_info(dev->hw_dev, "comedi%d: ni_daq_dio24: remove\n", dev->minor); + + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 0); + + if (thisboard->bustype != pcmcia_bustype && dev->iobase) + release_region(dev->iobase, DIO24_SIZE); + if (dev->irq) + free_irq(dev->irq, dev); + + return 0; +}; + +static void dio24_config(struct pcmcia_device *link); +static void dio24_release(struct pcmcia_device *link); +static int dio24_cs_suspend(struct pcmcia_device *p_dev); +static int dio24_cs_resume(struct pcmcia_device *p_dev); + +static int dio24_cs_attach(struct pcmcia_device *); +static void dio24_cs_detach(struct pcmcia_device *); + +struct local_info_t { + struct pcmcia_device *link; + int stop; + struct bus_operations *bus; +}; + +static int dio24_cs_attach(struct pcmcia_device *link) +{ + struct local_info_t *local; + + printk(KERN_INFO "ni_daq_dio24: HOLA SOY YO - CS-attach!\n"); + + dev_dbg(&link->dev, "dio24_cs_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kzalloc(sizeof(struct local_info_t), GFP_KERNEL); + if (!local) + return -ENOMEM; + local->link = link; + link->priv = local; + + pcmcia_cur_dev = link; + + dio24_config(link); + + return 0; +} /* dio24_cs_attach */ + +static void dio24_cs_detach(struct pcmcia_device *link) +{ + + printk(KERN_INFO "ni_daq_dio24: HOLA SOY YO - cs-detach!\n"); + + dev_dbg(&link->dev, "dio24_cs_detach\n"); + + ((struct local_info_t *)link->priv)->stop = 1; + dio24_release(link); + + /* This points to the parent local_info_t struct */ + kfree(link->priv); + +} /* dio24_cs_detach */ + +static int dio24_pcmcia_config_loop(struct pcmcia_device *p_dev, + void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(p_dev); +} + +static void dio24_config(struct pcmcia_device *link) +{ + int ret; + + printk(KERN_INFO "ni_daq_dio24: HOLA SOY YO! - config\n"); + + dev_dbg(&link->dev, "dio24_config\n"); + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_AUDIO | + CONF_AUTO_SET_IO; + + ret = pcmcia_loop_config(link, dio24_pcmcia_config_loop, NULL); + if (ret) { + dev_warn(&link->dev, "no configuration found\n"); + goto failed; + } + + if (!link->irq) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + return; + +failed: + printk(KERN_INFO "Fallo"); + dio24_release(link); + +} /* dio24_config */ + +static void dio24_release(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "dio24_release\n"); + + pcmcia_disable_device(link); +} /* dio24_release */ + +static int dio24_cs_suspend(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + /* Mark the device as stopped, to block IO until later */ + local->stop = 1; + return 0; +} /* dio24_cs_suspend */ + +static int dio24_cs_resume(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + local->stop = 0; + return 0; +} /* dio24_cs_resume */ + +/*====================================================================*/ + +static const struct pcmcia_device_id dio24_cs_ids[] = { + /* N.B. These IDs should match those in dio24_boards */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x475c), /* daqcard-dio24 */ + PCMCIA_DEVICE_NULL +}; + +MODULE_DEVICE_TABLE(pcmcia, dio24_cs_ids); +MODULE_AUTHOR("Daniel Vecino Castel "); +MODULE_DESCRIPTION("Comedi driver for National Instruments " + "PCMCIA DAQ-Card DIO-24"); +MODULE_LICENSE("GPL"); + +struct pcmcia_driver dio24_cs_driver = { + .probe = dio24_cs_attach, + .remove = dio24_cs_detach, + .suspend = dio24_cs_suspend, + .resume = dio24_cs_resume, + .id_table = dio24_cs_ids, + .owner = THIS_MODULE, + .name = "ni_daq_dio24", +}; + +static int __init init_dio24_cs(void) +{ + printk("ni_daq_dio24: HOLA SOY YO!\n"); + pcmcia_register_driver(&dio24_cs_driver); + return 0; +} + +static void __exit exit_dio24_cs(void) +{ + pcmcia_unregister_driver(&dio24_cs_driver); +} + +int __init init_module(void) +{ + int ret; + + ret = init_dio24_cs(); + if (ret < 0) + return ret; + + return comedi_driver_register(&driver_dio24); +} + +void __exit cleanup_module(void) +{ + exit_dio24_cs(); + comedi_driver_unregister(&driver_dio24); +} diff --git a/drivers/staging/comedi/drivers/ni_labpc.c b/drivers/staging/comedi/drivers/ni_labpc.c new file mode 100644 index 00000000..721b2be2 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_labpc.c @@ -0,0 +1,2196 @@ +/* + comedi/drivers/ni_labpc.c + Driver for National Instruments Lab-PC series boards and compatibles + Copyright (C) 2001, 2002, 2003 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ +*/ +/* +Driver: ni_labpc +Description: National Instruments Lab-PC (& compatibles) +Author: Frank Mori Hess +Devices: [National Instruments] Lab-PC-1200 (labpc-1200), + Lab-PC-1200AI (labpc-1200ai), Lab-PC+ (lab-pc+), PCI-1200 (ni_labpc) +Status: works + +Tested with lab-pc-1200. For the older Lab-PC+, not all input ranges +and analog references will work, the available ranges/arefs will +depend on how you have configured the jumpers on your board +(see your owner's manual). + +Kernel-level ISA plug-and-play support for the lab-pc-1200 +boards has not +yet been added to the driver, mainly due to the fact that +I don't know the device id numbers. If you have one +of these boards, +please file a bug report at http://comedi.org/ +so I can get the necessary information from you. + +The 1200 series boards have onboard calibration dacs for correcting +analog input/output offsets and gains. The proper settings for these +caldacs are stored on the board's eeprom. To read the caldac values +from the eeprom and store them into a file that can be then be used by +comedilib, use the comedi_calibrate program. + +Configuration options - ISA boards: + [0] - I/O port base address + [1] - IRQ (optional, required for timed or externally triggered conversions) + [2] - DMA channel (optional) + +Configuration options - PCI boards: + [0] - bus (optional) + [1] - slot (optional) + +The Lab-pc+ has quirky chanlist requirements +when scanning multiple channels. Multiple channel scan +sequence must start at highest channel, then decrement down to +channel 0. The rest of the cards can scan down like lab-pc+ or scan +up from channel zero. Chanlists consisting of all one channel +are also legal, and allow you to pace conversions in bursts. + +*/ + +/* + +NI manuals: +341309a (labpc-1200 register manual) +340914a (pci-1200) +320502b (lab-pc+) + +*/ + +#undef LABPC_DEBUG +/* #define LABPC_DEBUG enable debugging messages */ + +#include +#include +#include +#include "../comedidev.h" + +#include +#include + +#include "8253.h" +#include "8255.h" +#include "mite.h" +#include "comedi_fc.h" +#include "ni_labpc.h" + +#define DRV_NAME "ni_labpc" + +/* size of io region used by board */ +#define LABPC_SIZE 32 +/* 2 MHz master clock */ +#define LABPC_TIMER_BASE 500 + +/* Registers for the lab-pc+ */ + +/* write-only registers */ +#define COMMAND1_REG 0x0 +#define ADC_GAIN_MASK (0x7 << 4) +#define ADC_CHAN_BITS(x) ((x) & 0x7) +/* enables multi channel scans */ +#define ADC_SCAN_EN_BIT 0x80 +#define COMMAND2_REG 0x1 +/* enable pretriggering (used in conjunction with SWTRIG) */ +#define PRETRIG_BIT 0x1 +/* enable paced conversions on external trigger */ +#define HWTRIG_BIT 0x2 +/* enable paced conversions */ +#define SWTRIG_BIT 0x4 +/* use two cascaded counters for pacing */ +#define CASCADE_BIT 0x8 +#define DAC_PACED_BIT(channel) (0x40 << ((channel) & 0x1)) +#define COMMAND3_REG 0x2 +/* enable dma transfers */ +#define DMA_EN_BIT 0x1 +/* enable interrupts for 8255 */ +#define DIO_INTR_EN_BIT 0x2 +/* enable dma terminal count interrupt */ +#define DMATC_INTR_EN_BIT 0x4 +/* enable timer interrupt */ +#define TIMER_INTR_EN_BIT 0x8 +/* enable error interrupt */ +#define ERR_INTR_EN_BIT 0x10 +/* enable fifo not empty interrupt */ +#define ADC_FNE_INTR_EN_BIT 0x20 +#define ADC_CONVERT_REG 0x3 +#define DAC_LSB_REG(channel) (0x4 + 2 * ((channel) & 0x1)) +#define DAC_MSB_REG(channel) (0x5 + 2 * ((channel) & 0x1)) +#define ADC_CLEAR_REG 0x8 +#define DMATC_CLEAR_REG 0xa +#define TIMER_CLEAR_REG 0xc +/* 1200 boards only */ +#define COMMAND6_REG 0xe +/* select ground or common-mode reference */ +#define ADC_COMMON_BIT 0x1 +/* adc unipolar */ +#define ADC_UNIP_BIT 0x2 +/* dac unipolar */ +#define DAC_UNIP_BIT(channel) (0x4 << ((channel) & 0x1)) +/* enable fifo half full interrupt */ +#define ADC_FHF_INTR_EN_BIT 0x20 +/* enable interrupt on end of hardware count */ +#define A1_INTR_EN_BIT 0x40 +/* scan up from channel zero instead of down to zero */ +#define ADC_SCAN_UP_BIT 0x80 +#define COMMAND4_REG 0xf +/* enables 'interval' scanning */ +#define INTERVAL_SCAN_EN_BIT 0x1 +/* enables external signal on counter b1 output to trigger scan */ +#define EXT_SCAN_EN_BIT 0x2 +/* chooses direction (output or input) for EXTCONV* line */ +#define EXT_CONVERT_OUT_BIT 0x4 +/* chooses differential inputs for adc (in conjunction with board jumper) */ +#define ADC_DIFF_BIT 0x8 +#define EXT_CONVERT_DISABLE_BIT 0x10 +/* 1200 boards only, calibration stuff */ +#define COMMAND5_REG 0x1c +/* enable eeprom for write */ +#define EEPROM_WRITE_UNPROTECT_BIT 0x4 +/* enable dithering */ +#define DITHER_EN_BIT 0x8 +/* load calibration dac */ +#define CALDAC_LOAD_BIT 0x10 +/* serial clock - rising edge writes, falling edge reads */ +#define SCLOCK_BIT 0x20 +/* serial data bit for writing to eeprom or calibration dacs */ +#define SDATA_BIT 0x40 +/* enable eeprom for read/write */ +#define EEPROM_EN_BIT 0x80 +#define INTERVAL_COUNT_REG 0x1e +#define INTERVAL_LOAD_REG 0x1f +#define INTERVAL_LOAD_BITS 0x1 + +/* read-only registers */ +#define STATUS1_REG 0x0 +/* data is available in fifo */ +#define DATA_AVAIL_BIT 0x1 +/* overrun has occurred */ +#define OVERRUN_BIT 0x2 +/* fifo overflow */ +#define OVERFLOW_BIT 0x4 +/* timer interrupt has occurred */ +#define TIMER_BIT 0x8 +/* dma terminal count has occurred */ +#define DMATC_BIT 0x10 +/* external trigger has occurred */ +#define EXT_TRIG_BIT 0x40 +/* 1200 boards only */ +#define STATUS2_REG 0x1d +/* programmable eeprom serial output */ +#define EEPROM_OUT_BIT 0x1 +/* counter A1 terminal count */ +#define A1_TC_BIT 0x2 +/* fifo not half full */ +#define FNHF_BIT 0x4 +#define ADC_FIFO_REG 0xa + +#define DIO_BASE_REG 0x10 +#define COUNTER_A_BASE_REG 0x14 +#define COUNTER_A_CONTROL_REG (COUNTER_A_BASE_REG + 0x3) +/* check modes put conversion pacer output in harmless state (a0 mode 2) */ +#define INIT_A0_BITS 0x14 +/* put hardware conversion counter output in harmless state (a1 mode 0) */ +#define INIT_A1_BITS 0x70 +#define COUNTER_B_BASE_REG 0x18 + +static int labpc_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int labpc_cancel(struct comedi_device *dev, struct comedi_subdevice *s); +static irqreturn_t labpc_interrupt(int irq, void *d); +static int labpc_drain_fifo(struct comedi_device *dev); +#ifdef CONFIG_ISA_DMA_API +static void labpc_drain_dma(struct comedi_device *dev); +static void handle_isa_dma(struct comedi_device *dev); +#endif +static void labpc_drain_dregs(struct comedi_device *dev); +static int labpc_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd); +static int labpc_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int labpc_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int labpc_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int labpc_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int labpc_calib_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int labpc_calib_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int labpc_eeprom_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int labpc_eeprom_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); +static void labpc_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd); +#ifdef CONFIG_ISA_DMA_API +static unsigned int labpc_suggest_transfer_size(struct comedi_cmd cmd); +#endif +#ifdef CONFIG_COMEDI_PCI_DRIVERS +static int labpc_find_device(struct comedi_device *dev, int bus, int slot); +#endif +static int labpc_dio_mem_callback(int dir, int port, int data, + unsigned long arg); +static void labpc_serial_out(struct comedi_device *dev, unsigned int value, + unsigned int num_bits); +static unsigned int labpc_serial_in(struct comedi_device *dev); +static unsigned int labpc_eeprom_read(struct comedi_device *dev, + unsigned int address); +static unsigned int labpc_eeprom_read_status(struct comedi_device *dev); +static int labpc_eeprom_write(struct comedi_device *dev, + unsigned int address, + unsigned int value); +static void write_caldac(struct comedi_device *dev, unsigned int channel, + unsigned int value); + +enum scan_mode { + MODE_SINGLE_CHAN, + MODE_SINGLE_CHAN_INTERVAL, + MODE_MULT_CHAN_UP, + MODE_MULT_CHAN_DOWN, +}; + +/* analog input ranges */ +#define NUM_LABPC_PLUS_AI_RANGES 16 +/* indicates unipolar ranges */ +static const int labpc_plus_is_unipolar[NUM_LABPC_PLUS_AI_RANGES] = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, +}; + +/* map range index to gain bits */ +static const int labpc_plus_ai_gain_bits[NUM_LABPC_PLUS_AI_RANGES] = { + 0x00, + 0x10, + 0x20, + 0x30, + 0x40, + 0x50, + 0x60, + 0x70, + 0x00, + 0x10, + 0x20, + 0x30, + 0x40, + 0x50, + 0x60, + 0x70, +}; + +static const struct comedi_lrange range_labpc_plus_ai = { + NUM_LABPC_PLUS_AI_RANGES, + { + BIP_RANGE(5), + BIP_RANGE(4), + BIP_RANGE(2.5), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.25), + BIP_RANGE(0.1), + BIP_RANGE(0.05), + UNI_RANGE(10), + UNI_RANGE(8), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1), + } +}; + +#define NUM_LABPC_1200_AI_RANGES 14 +/* indicates unipolar ranges */ +const int labpc_1200_is_unipolar[NUM_LABPC_1200_AI_RANGES] = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, +}; +EXPORT_SYMBOL_GPL(labpc_1200_is_unipolar); + +/* map range index to gain bits */ +const int labpc_1200_ai_gain_bits[NUM_LABPC_1200_AI_RANGES] = { + 0x00, + 0x20, + 0x30, + 0x40, + 0x50, + 0x60, + 0x70, + 0x00, + 0x20, + 0x30, + 0x40, + 0x50, + 0x60, + 0x70, +}; +EXPORT_SYMBOL_GPL(labpc_1200_ai_gain_bits); + +const struct comedi_lrange range_labpc_1200_ai = { + NUM_LABPC_1200_AI_RANGES, + { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1), + BIP_RANGE(0.5), + BIP_RANGE(0.25), + BIP_RANGE(0.1), + BIP_RANGE(0.05), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2), + UNI_RANGE(1), + UNI_RANGE(0.5), + UNI_RANGE(0.2), + UNI_RANGE(0.1), + } +}; +EXPORT_SYMBOL_GPL(range_labpc_1200_ai); + +/* analog output ranges */ +#define AO_RANGE_IS_UNIPOLAR 0x1 +static const struct comedi_lrange range_labpc_ao = { + 2, + { + BIP_RANGE(5), + UNI_RANGE(10), + } +}; + +/* functions that do inb/outb and readb/writeb so we can use + * function pointers to decide which to use */ +static inline unsigned int labpc_inb(unsigned long address) +{ + return inb(address); +} + +static inline void labpc_outb(unsigned int byte, unsigned long address) +{ + outb(byte, address); +} + +static inline unsigned int labpc_readb(unsigned long address) +{ + return readb((void *)address); +} + +static inline void labpc_writeb(unsigned int byte, unsigned long address) +{ + writeb(byte, (void *)address); +} + +static const struct labpc_board_struct labpc_boards[] = { + { + .name = "lab-pc-1200", + .ai_speed = 10000, + .bustype = isa_bustype, + .register_layout = labpc_1200_layout, + .has_ao = 1, + .ai_range_table = &range_labpc_1200_ai, + .ai_range_code = labpc_1200_ai_gain_bits, + .ai_range_is_unipolar = labpc_1200_is_unipolar, + .ai_scan_up = 1, + .memory_mapped_io = 0, + }, + { + .name = "lab-pc-1200ai", + .ai_speed = 10000, + .bustype = isa_bustype, + .register_layout = labpc_1200_layout, + .has_ao = 0, + .ai_range_table = &range_labpc_1200_ai, + .ai_range_code = labpc_1200_ai_gain_bits, + .ai_range_is_unipolar = labpc_1200_is_unipolar, + .ai_scan_up = 1, + .memory_mapped_io = 0, + }, + { + .name = "lab-pc+", + .ai_speed = 12000, + .bustype = isa_bustype, + .register_layout = labpc_plus_layout, + .has_ao = 1, + .ai_range_table = &range_labpc_plus_ai, + .ai_range_code = labpc_plus_ai_gain_bits, + .ai_range_is_unipolar = labpc_plus_is_unipolar, + .ai_scan_up = 0, + .memory_mapped_io = 0, + }, +#ifdef CONFIG_COMEDI_PCI_DRIVERS + { + .name = "pci-1200", + .device_id = 0x161, + .ai_speed = 10000, + .bustype = pci_bustype, + .register_layout = labpc_1200_layout, + .has_ao = 1, + .ai_range_table = &range_labpc_1200_ai, + .ai_range_code = labpc_1200_ai_gain_bits, + .ai_range_is_unipolar = labpc_1200_is_unipolar, + .ai_scan_up = 1, + .memory_mapped_io = 1, + }, +/* dummy entry so pci board works when comedi_config is passed driver name */ + { + .name = DRV_NAME, + .bustype = pci_bustype, + }, +#endif +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((struct labpc_board_struct *)dev->board_ptr) + +/* size in bytes of dma buffer */ +static const int dma_buffer_size = 0xff00; +/* 2 bytes per sample */ +static const int sample_size = 2; + +#define devpriv ((struct labpc_private *)dev->private) + +static struct comedi_driver driver_labpc = { + .driver_name = DRV_NAME, + .module = THIS_MODULE, + .attach = labpc_attach, + .detach = labpc_common_detach, + .num_names = ARRAY_SIZE(labpc_boards), + .board_name = &labpc_boards[0].name, + .offset = sizeof(struct labpc_board_struct), +}; + +#ifdef CONFIG_COMEDI_PCI_DRIVERS +static DEFINE_PCI_DEVICE_TABLE(labpc_pci_table) = { + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x161)}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, labpc_pci_table); +#endif /* CONFIG_COMEDI_PCI_DRIVERS */ + +static inline int labpc_counter_load(struct comedi_device *dev, + unsigned long base_address, + unsigned int counter_number, + unsigned int count, unsigned int mode) +{ + if (thisboard->memory_mapped_io) + return i8254_mm_load((void *)base_address, 0, counter_number, + count, mode); + else + return i8254_load(base_address, 0, counter_number, count, mode); +} + +int labpc_common_attach(struct comedi_device *dev, unsigned long iobase, + unsigned int irq, unsigned int dma_chan) +{ + struct comedi_subdevice *s; + int i; + unsigned long isr_flags; +#ifdef CONFIG_ISA_DMA_API + unsigned long dma_flags; +#endif + short lsb, msb; + + printk(KERN_ERR "comedi%d: ni_labpc: %s, io 0x%lx", dev->minor, + thisboard->name, + iobase); + if (irq) + printk(", irq %u", irq); + if (dma_chan) + printk(", dma %u", dma_chan); + printk("\n"); + + if (iobase == 0) { + printk(KERN_ERR "io base address is zero!\n"); + return -EINVAL; + } + /* request io regions for isa boards */ + if (thisboard->bustype == isa_bustype) { + /* check if io addresses are available */ + if (!request_region(iobase, LABPC_SIZE, + driver_labpc.driver_name)) { + printk(KERN_ERR "I/O port conflict\n"); + return -EIO; + } + } + dev->iobase = iobase; + + if (thisboard->memory_mapped_io) { + devpriv->read_byte = labpc_readb; + devpriv->write_byte = labpc_writeb; + } else { + devpriv->read_byte = labpc_inb; + devpriv->write_byte = labpc_outb; + } + /* initialize board's command registers */ + devpriv->write_byte(devpriv->command1_bits, dev->iobase + COMMAND1_REG); + devpriv->write_byte(devpriv->command2_bits, dev->iobase + COMMAND2_REG); + devpriv->write_byte(devpriv->command3_bits, dev->iobase + COMMAND3_REG); + devpriv->write_byte(devpriv->command4_bits, dev->iobase + COMMAND4_REG); + if (thisboard->register_layout == labpc_1200_layout) { + devpriv->write_byte(devpriv->command5_bits, + dev->iobase + COMMAND5_REG); + devpriv->write_byte(devpriv->command6_bits, + dev->iobase + COMMAND6_REG); + } + + /* grab our IRQ */ + if (irq) { + isr_flags = 0; + if (thisboard->bustype == pci_bustype + || thisboard->bustype == pcmcia_bustype) + isr_flags |= IRQF_SHARED; + if (request_irq(irq, labpc_interrupt, isr_flags, + driver_labpc.driver_name, dev)) { + printk(KERN_ERR "unable to allocate irq %u\n", irq); + return -EINVAL; + } + } + dev->irq = irq; + +#ifdef CONFIG_ISA_DMA_API + /* grab dma channel */ + if (dma_chan > 3) { + printk(KERN_ERR " invalid dma channel %u\n", dma_chan); + return -EINVAL; + } else if (dma_chan) { + /* allocate dma buffer */ + devpriv->dma_buffer = + kmalloc(dma_buffer_size, GFP_KERNEL | GFP_DMA); + if (devpriv->dma_buffer == NULL) { + printk(KERN_ERR " failed to allocate dma buffer\n"); + return -ENOMEM; + } + if (request_dma(dma_chan, driver_labpc.driver_name)) { + printk(KERN_ERR " failed to allocate dma channel %u\n", + dma_chan); + return -EINVAL; + } + devpriv->dma_chan = dma_chan; + dma_flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + set_dma_mode(devpriv->dma_chan, DMA_MODE_READ); + release_dma_lock(dma_flags); + } +#endif + + dev->board_name = thisboard->name; + + if (alloc_subdevices(dev, 5) < 0) + return -ENOMEM; + + /* analog input subdevice */ + s = dev->subdevices + 0; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF | SDF_CMD_READ; + s->n_chan = 8; + s->len_chanlist = 8; + s->maxdata = (1 << 12) - 1; /* 12 bit resolution */ + s->range_table = thisboard->ai_range_table; + s->do_cmd = labpc_ai_cmd; + s->do_cmdtest = labpc_ai_cmdtest; + s->insn_read = labpc_ai_rinsn; + s->cancel = labpc_cancel; + + /* analog output */ + s = dev->subdevices + 1; + if (thisboard->has_ao) { + /* + * Could provide command support, except it only has a + * one sample hardware buffer for analog output and no + * underrun flag. + */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND; + s->n_chan = NUM_AO_CHAN; + s->maxdata = (1 << 12) - 1; /* 12 bit resolution */ + s->range_table = &range_labpc_ao; + s->insn_read = labpc_ao_rinsn; + s->insn_write = labpc_ao_winsn; + /* initialize analog outputs to a known value */ + for (i = 0; i < s->n_chan; i++) { + devpriv->ao_value[i] = s->maxdata / 2; + lsb = devpriv->ao_value[i] & 0xff; + msb = (devpriv->ao_value[i] >> 8) & 0xff; + devpriv->write_byte(lsb, dev->iobase + DAC_LSB_REG(i)); + devpriv->write_byte(msb, dev->iobase + DAC_MSB_REG(i)); + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* 8255 dio */ + s = dev->subdevices + 2; + /* if board uses io memory we have to give a custom callback + * function to the 8255 driver */ + if (thisboard->memory_mapped_io) + subdev_8255_init(dev, s, labpc_dio_mem_callback, + (unsigned long)(dev->iobase + DIO_BASE_REG)); + else + subdev_8255_init(dev, s, NULL, dev->iobase + DIO_BASE_REG); + + /* calibration subdevices for boards that have one */ + s = dev->subdevices + 3; + if (thisboard->register_layout == labpc_1200_layout) { + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 16; + s->maxdata = 0xff; + s->insn_read = labpc_calib_read_insn; + s->insn_write = labpc_calib_write_insn; + + for (i = 0; i < s->n_chan; i++) + write_caldac(dev, i, s->maxdata / 2); + } else + s->type = COMEDI_SUBD_UNUSED; + + /* EEPROM */ + s = dev->subdevices + 4; + if (thisboard->register_layout == labpc_1200_layout) { + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = EEPROM_SIZE; + s->maxdata = 0xff; + s->insn_read = labpc_eeprom_read_insn; + s->insn_write = labpc_eeprom_write_insn; + + for (i = 0; i < EEPROM_SIZE; i++) + devpriv->eeprom_data[i] = labpc_eeprom_read(dev, i); +#ifdef LABPC_DEBUG + printk(KERN_ERR " eeprom:"); + for (i = 0; i < EEPROM_SIZE; i++) + printk(" %i:0x%x ", i, devpriv->eeprom_data[i]); + printk("\n"); +#endif + } else + s->type = COMEDI_SUBD_UNUSED; + + return 0; +} +EXPORT_SYMBOL_GPL(labpc_common_attach); + +static int labpc_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + unsigned long iobase = 0; + unsigned int irq = 0; + unsigned int dma_chan = 0; +#ifdef CONFIG_COMEDI_PCI_DRIVERS + int retval; +#endif + + /* allocate and initialize dev->private */ + if (alloc_private(dev, sizeof(struct labpc_private)) < 0) + return -ENOMEM; + + /* get base address, irq etc. based on bustype */ + switch (thisboard->bustype) { + case isa_bustype: +#ifdef CONFIG_ISA_DMA_API + iobase = it->options[0]; + irq = it->options[1]; + dma_chan = it->options[2]; +#else + printk(KERN_ERR " this driver has not been built with ISA DMA " + "support.\n"); + return -EINVAL; +#endif + break; + case pci_bustype: +#ifdef CONFIG_COMEDI_PCI_DRIVERS + retval = labpc_find_device(dev, it->options[0], it->options[1]); + if (retval < 0) + return retval; + retval = mite_setup(devpriv->mite); + if (retval < 0) + return retval; + iobase = (unsigned long)devpriv->mite->daq_io_addr; + irq = mite_irq(devpriv->mite); +#else + printk(KERN_ERR " this driver has not been built with PCI " + "support.\n"); + return -EINVAL; +#endif + break; + case pcmcia_bustype: + printk + (" this driver does not support pcmcia cards, use ni_labpc_cs.o\n"); + return -EINVAL; + break; + default: + printk(KERN_ERR "bug! couldn't determine board type\n"); + return -EINVAL; + break; + } + + return labpc_common_attach(dev, iobase, irq, dma_chan); +} + +/* adapted from ni_pcimio for finding mite based boards (pc-1200) */ +#ifdef CONFIG_COMEDI_PCI_DRIVERS +static int labpc_find_device(struct comedi_device *dev, int bus, int slot) +{ + struct mite_struct *mite; + int i; + for (mite = mite_devices; mite; mite = mite->next) { + if (mite->used) + continue; +/* if bus/slot are specified then make sure we have the right bus/slot */ + if (bus || slot) { + if (bus != mite->pcidev->bus->number + || slot != PCI_SLOT(mite->pcidev->devfn)) + continue; + } + for (i = 0; i < driver_labpc.num_names; i++) { + if (labpc_boards[i].bustype != pci_bustype) + continue; + if (mite_device_id(mite) == labpc_boards[i].device_id) { + devpriv->mite = mite; +/* fixup board pointer, in case we were using the dummy "ni_labpc" entry */ + dev->board_ptr = &labpc_boards[i]; + return 0; + } + } + } + printk(KERN_ERR "no device found\n"); + mite_list_devices(); + return -EIO; +} +#endif + +int labpc_common_detach(struct comedi_device *dev) +{ + printk(KERN_ERR "comedi%d: ni_labpc: detach\n", dev->minor); + + if (dev->subdevices) + subdev_8255_cleanup(dev, dev->subdevices + 2); + +#ifdef CONFIG_ISA_DMA_API + /* only free stuff if it has been allocated by _attach */ + kfree(devpriv->dma_buffer); + if (devpriv->dma_chan) + free_dma(devpriv->dma_chan); +#endif + if (dev->irq) + free_irq(dev->irq, dev); + if (thisboard->bustype == isa_bustype && dev->iobase) + release_region(dev->iobase, LABPC_SIZE); +#ifdef CONFIG_COMEDI_PCI_DRIVERS + if (devpriv->mite) + mite_unsetup(devpriv->mite); +#endif + + return 0; +}; +EXPORT_SYMBOL_GPL(labpc_common_detach); + +static void labpc_clear_adc_fifo(const struct comedi_device *dev) +{ + devpriv->write_byte(0x1, dev->iobase + ADC_CLEAR_REG); + devpriv->read_byte(dev->iobase + ADC_FIFO_REG); + devpriv->read_byte(dev->iobase + ADC_FIFO_REG); +} + +static int labpc_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->command2_bits &= ~SWTRIG_BIT & ~HWTRIG_BIT & ~PRETRIG_BIT; + devpriv->write_byte(devpriv->command2_bits, dev->iobase + COMMAND2_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + devpriv->command3_bits = 0; + devpriv->write_byte(devpriv->command3_bits, dev->iobase + COMMAND3_REG); + + return 0; +} + +static enum scan_mode labpc_ai_scan_mode(const struct comedi_cmd *cmd) +{ + if (cmd->chanlist_len == 1) + return MODE_SINGLE_CHAN; + + /* chanlist may be NULL during cmdtest. */ + if (cmd->chanlist == NULL) + return MODE_MULT_CHAN_UP; + + if (CR_CHAN(cmd->chanlist[0]) == CR_CHAN(cmd->chanlist[1])) + return MODE_SINGLE_CHAN_INTERVAL; + + if (CR_CHAN(cmd->chanlist[0]) < CR_CHAN(cmd->chanlist[1])) + return MODE_MULT_CHAN_UP; + + if (CR_CHAN(cmd->chanlist[0]) > CR_CHAN(cmd->chanlist[1])) + return MODE_MULT_CHAN_DOWN; + + printk(KERN_ERR "ni_labpc: bug! this should never happen\n"); + + return 0; +} + +static int labpc_ai_chanlist_invalid(const struct comedi_device *dev, + const struct comedi_cmd *cmd) +{ + int mode, channel, range, aref, i; + + if (cmd->chanlist == NULL) + return 0; + + mode = labpc_ai_scan_mode(cmd); + + if (mode == MODE_SINGLE_CHAN) + return 0; + + if (mode == MODE_SINGLE_CHAN_INTERVAL) { + if (cmd->chanlist_len > 0xff) { + comedi_error(dev, + "ni_labpc: chanlist too long for single channel interval mode\n"); + return 1; + } + } + + channel = CR_CHAN(cmd->chanlist[0]); + range = CR_RANGE(cmd->chanlist[0]); + aref = CR_AREF(cmd->chanlist[0]); + + for (i = 0; i < cmd->chanlist_len; i++) { + + switch (mode) { + case MODE_SINGLE_CHAN_INTERVAL: + if (CR_CHAN(cmd->chanlist[i]) != channel) { + comedi_error(dev, + "channel scanning order specified in chanlist is not supported by hardware.\n"); + return 1; + } + break; + case MODE_MULT_CHAN_UP: + if (CR_CHAN(cmd->chanlist[i]) != i) { + comedi_error(dev, + "channel scanning order specified in chanlist is not supported by hardware.\n"); + return 1; + } + break; + case MODE_MULT_CHAN_DOWN: + if (CR_CHAN(cmd->chanlist[i]) != + cmd->chanlist_len - i - 1) { + comedi_error(dev, + "channel scanning order specified in chanlist is not supported by hardware.\n"); + return 1; + } + break; + default: + printk(KERN_ERR "ni_labpc: bug! in chanlist check\n"); + return 1; + break; + } + + if (CR_RANGE(cmd->chanlist[i]) != range) { + comedi_error(dev, + "entries in chanlist must all have the same range\n"); + return 1; + } + + if (CR_AREF(cmd->chanlist[i]) != aref) { + comedi_error(dev, + "entries in chanlist must all have the same reference\n"); + return 1; + } + } + + return 0; +} + +static int labpc_use_continuous_mode(const struct comedi_cmd *cmd) +{ + if (labpc_ai_scan_mode(cmd) == MODE_SINGLE_CHAN) + return 1; + + if (cmd->scan_begin_src == TRIG_FOLLOW) + return 1; + + return 0; +} + +static unsigned int labpc_ai_convert_period(const struct comedi_cmd *cmd) +{ + if (cmd->convert_src != TRIG_TIMER) + return 0; + + if (labpc_ai_scan_mode(cmd) == MODE_SINGLE_CHAN && + cmd->scan_begin_src == TRIG_TIMER) + return cmd->scan_begin_arg; + + return cmd->convert_arg; +} + +static void labpc_set_ai_convert_period(struct comedi_cmd *cmd, unsigned int ns) +{ + if (cmd->convert_src != TRIG_TIMER) + return; + + if (labpc_ai_scan_mode(cmd) == MODE_SINGLE_CHAN && + cmd->scan_begin_src == TRIG_TIMER) { + cmd->scan_begin_arg = ns; + if (cmd->convert_arg > cmd->scan_begin_arg) + cmd->convert_arg = cmd->scan_begin_arg; + } else + cmd->convert_arg = ns; +} + +static unsigned int labpc_ai_scan_period(const struct comedi_cmd *cmd) +{ + if (cmd->scan_begin_src != TRIG_TIMER) + return 0; + + if (labpc_ai_scan_mode(cmd) == MODE_SINGLE_CHAN && + cmd->convert_src == TRIG_TIMER) + return 0; + + return cmd->scan_begin_arg; +} + +static void labpc_set_ai_scan_period(struct comedi_cmd *cmd, unsigned int ns) +{ + if (cmd->scan_begin_src != TRIG_TIMER) + return; + + if (labpc_ai_scan_mode(cmd) == MODE_SINGLE_CHAN && + cmd->convert_src == TRIG_TIMER) + return; + + cmd->scan_begin_arg = ns; +} + +static int labpc_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp, tmp2; + int stop_mask; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + stop_mask = TRIG_COUNT | TRIG_NONE; + if (thisboard->register_layout == labpc_1200_layout) + stop_mask |= TRIG_EXT; + cmd->stop_src &= stop_mask; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT) + err++; + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_EXT && cmd->stop_src != TRIG_NONE) + err++; + + /* can't have external stop and start triggers at once */ + if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg == TRIG_NOW && cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (!cmd->chanlist_len) + err++; + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < thisboard->ai_speed) { + cmd->convert_arg = thisboard->ai_speed; + err++; + } + } + /* make sure scan timing is not too fast */ + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->convert_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->chanlist_len) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->chanlist_len; + err++; + } + if (cmd->scan_begin_arg < + thisboard->ai_speed * cmd->chanlist_len) { + cmd->scan_begin_arg = + thisboard->ai_speed * cmd->chanlist_len; + err++; + } + } + /* stop source */ + switch (cmd->stop_src) { + case TRIG_COUNT: + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + break; + case TRIG_NONE: + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + break; + /* + * TRIG_EXT doesn't care since it doesn't + * trigger off a numbered channel + */ + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + tmp = cmd->convert_arg; + tmp2 = cmd->scan_begin_arg; + labpc_adc_timing(dev, cmd); + if (tmp != cmd->convert_arg || tmp2 != cmd->scan_begin_arg) + err++; + + if (err) + return 4; + + if (labpc_ai_chanlist_invalid(dev, cmd)) + return 5; + + return 0; +} + +static int labpc_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int channel, range, aref; +#ifdef CONFIG_ISA_DMA_API + unsigned long irq_flags; +#endif + int ret; + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + enum transfer_type xfer; + unsigned long flags; + + if (!dev->irq) { + comedi_error(dev, "no irq assigned, cannot perform command"); + return -1; + } + + range = CR_RANGE(cmd->chanlist[0]); + aref = CR_AREF(cmd->chanlist[0]); + + /* make sure board is disabled before setting up acquisition */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->command2_bits &= ~SWTRIG_BIT & ~HWTRIG_BIT & ~PRETRIG_BIT; + devpriv->write_byte(devpriv->command2_bits, dev->iobase + COMMAND2_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + devpriv->command3_bits = 0; + devpriv->write_byte(devpriv->command3_bits, dev->iobase + COMMAND3_REG); + + /* initialize software conversion count */ + if (cmd->stop_src == TRIG_COUNT) + devpriv->count = cmd->stop_arg * cmd->chanlist_len; + + /* setup hardware conversion counter */ + if (cmd->stop_src == TRIG_EXT) { + /* + * load counter a1 with count of 3 + * (pc+ manual says this is minimum allowed) using mode 0 + */ + ret = labpc_counter_load(dev, dev->iobase + COUNTER_A_BASE_REG, + 1, 3, 0); + if (ret < 0) { + comedi_error(dev, "error loading counter a1"); + return -1; + } + } else /* + * otherwise, just put a1 in mode 0 + * with no count to set its output low + */ + devpriv->write_byte(INIT_A1_BITS, + dev->iobase + COUNTER_A_CONTROL_REG); + +#ifdef CONFIG_ISA_DMA_API + /* figure out what method we will use to transfer data */ + if (devpriv->dma_chan && /* need a dma channel allocated */ + /* + * dma unsafe at RT priority, + * and too much setup time for TRIG_WAKE_EOS for + */ + (cmd->flags & (TRIG_WAKE_EOS | TRIG_RT)) == 0 && + /* only available on the isa boards */ + thisboard->bustype == isa_bustype) { + xfer = isa_dma_transfer; + /* pc-plus has no fifo-half full interrupt */ + } else +#endif + if (thisboard->register_layout == labpc_1200_layout && + /* wake-end-of-scan should interrupt on fifo not empty */ + (cmd->flags & TRIG_WAKE_EOS) == 0 && + /* make sure we are taking more than just a few points */ + (cmd->stop_src != TRIG_COUNT || devpriv->count > 256)) { + xfer = fifo_half_full_transfer; + } else + xfer = fifo_not_empty_transfer; + devpriv->current_transfer = xfer; + + /* setup command6 register for 1200 boards */ + if (thisboard->register_layout == labpc_1200_layout) { + /* reference inputs to ground or common? */ + if (aref != AREF_GROUND) + devpriv->command6_bits |= ADC_COMMON_BIT; + else + devpriv->command6_bits &= ~ADC_COMMON_BIT; + /* bipolar or unipolar range? */ + if (thisboard->ai_range_is_unipolar[range]) + devpriv->command6_bits |= ADC_UNIP_BIT; + else + devpriv->command6_bits &= ~ADC_UNIP_BIT; + /* interrupt on fifo half full? */ + if (xfer == fifo_half_full_transfer) + devpriv->command6_bits |= ADC_FHF_INTR_EN_BIT; + else + devpriv->command6_bits &= ~ADC_FHF_INTR_EN_BIT; + /* enable interrupt on counter a1 terminal count? */ + if (cmd->stop_src == TRIG_EXT) + devpriv->command6_bits |= A1_INTR_EN_BIT; + else + devpriv->command6_bits &= ~A1_INTR_EN_BIT; + /* are we scanning up or down through channels? */ + if (labpc_ai_scan_mode(cmd) == MODE_MULT_CHAN_UP) + devpriv->command6_bits |= ADC_SCAN_UP_BIT; + else + devpriv->command6_bits &= ~ADC_SCAN_UP_BIT; + /* write to register */ + devpriv->write_byte(devpriv->command6_bits, + dev->iobase + COMMAND6_REG); + } + + /* setup channel list, etc (command1 register) */ + devpriv->command1_bits = 0; + if (labpc_ai_scan_mode(cmd) == MODE_MULT_CHAN_UP) + channel = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); + else + channel = CR_CHAN(cmd->chanlist[0]); + /* munge channel bits for differential / scan disabled mode */ + if (labpc_ai_scan_mode(cmd) != MODE_SINGLE_CHAN && aref == AREF_DIFF) + channel *= 2; + devpriv->command1_bits |= ADC_CHAN_BITS(channel); + devpriv->command1_bits |= thisboard->ai_range_code[range]; + devpriv->write_byte(devpriv->command1_bits, dev->iobase + COMMAND1_REG); + /* manual says to set scan enable bit on second pass */ + if (labpc_ai_scan_mode(cmd) == MODE_MULT_CHAN_UP || + labpc_ai_scan_mode(cmd) == MODE_MULT_CHAN_DOWN) { + devpriv->command1_bits |= ADC_SCAN_EN_BIT; + /* need a brief delay before enabling scan, or scan + * list will get screwed when you switch + * between scan up to scan down mode - dunno why */ + udelay(1); + devpriv->write_byte(devpriv->command1_bits, + dev->iobase + COMMAND1_REG); + } + /* setup any external triggering/pacing (command4 register) */ + devpriv->command4_bits = 0; + if (cmd->convert_src != TRIG_EXT) + devpriv->command4_bits |= EXT_CONVERT_DISABLE_BIT; + /* XXX should discard first scan when using interval scanning + * since manual says it is not synced with scan clock */ + if (labpc_use_continuous_mode(cmd) == 0) { + devpriv->command4_bits |= INTERVAL_SCAN_EN_BIT; + if (cmd->scan_begin_src == TRIG_EXT) + devpriv->command4_bits |= EXT_SCAN_EN_BIT; + } + /* single-ended/differential */ + if (aref == AREF_DIFF) + devpriv->command4_bits |= ADC_DIFF_BIT; + devpriv->write_byte(devpriv->command4_bits, dev->iobase + COMMAND4_REG); + + devpriv->write_byte(cmd->chanlist_len, + dev->iobase + INTERVAL_COUNT_REG); + /* load count */ + devpriv->write_byte(INTERVAL_LOAD_BITS, + dev->iobase + INTERVAL_LOAD_REG); + + if (cmd->convert_src == TRIG_TIMER || cmd->scan_begin_src == TRIG_TIMER) { + /* set up pacing */ + labpc_adc_timing(dev, cmd); + /* load counter b0 in mode 3 */ + ret = labpc_counter_load(dev, dev->iobase + COUNTER_B_BASE_REG, + 0, devpriv->divisor_b0, 3); + if (ret < 0) { + comedi_error(dev, "error loading counter b0"); + return -1; + } + } + /* set up conversion pacing */ + if (labpc_ai_convert_period(cmd)) { + /* load counter a0 in mode 2 */ + ret = labpc_counter_load(dev, dev->iobase + COUNTER_A_BASE_REG, + 0, devpriv->divisor_a0, 2); + if (ret < 0) { + comedi_error(dev, "error loading counter a0"); + return -1; + } + } else + devpriv->write_byte(INIT_A0_BITS, + dev->iobase + COUNTER_A_CONTROL_REG); + + /* set up scan pacing */ + if (labpc_ai_scan_period(cmd)) { + /* load counter b1 in mode 2 */ + ret = labpc_counter_load(dev, dev->iobase + COUNTER_B_BASE_REG, + 1, devpriv->divisor_b1, 2); + if (ret < 0) { + comedi_error(dev, "error loading counter b1"); + return -1; + } + } + + labpc_clear_adc_fifo(dev); + +#ifdef CONFIG_ISA_DMA_API + /* set up dma transfer */ + if (xfer == isa_dma_transfer) { + irq_flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma_chan); + set_dma_addr(devpriv->dma_chan, + virt_to_bus(devpriv->dma_buffer)); + /* set appropriate size of transfer */ + devpriv->dma_transfer_size = labpc_suggest_transfer_size(*cmd); + if (cmd->stop_src == TRIG_COUNT && + devpriv->count * sample_size < devpriv->dma_transfer_size) { + devpriv->dma_transfer_size = + devpriv->count * sample_size; + } + set_dma_count(devpriv->dma_chan, devpriv->dma_transfer_size); + enable_dma(devpriv->dma_chan); + release_dma_lock(irq_flags); + /* enable board's dma */ + devpriv->command3_bits |= DMA_EN_BIT | DMATC_INTR_EN_BIT; + } else + devpriv->command3_bits &= ~DMA_EN_BIT & ~DMATC_INTR_EN_BIT; +#endif + + /* enable error interrupts */ + devpriv->command3_bits |= ERR_INTR_EN_BIT; + /* enable fifo not empty interrupt? */ + if (xfer == fifo_not_empty_transfer) + devpriv->command3_bits |= ADC_FNE_INTR_EN_BIT; + else + devpriv->command3_bits &= ~ADC_FNE_INTR_EN_BIT; + devpriv->write_byte(devpriv->command3_bits, dev->iobase + COMMAND3_REG); + + /* startup acquisition */ + + /* command2 reg */ + /* use 2 cascaded counters for pacing */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->command2_bits |= CASCADE_BIT; + switch (cmd->start_src) { + case TRIG_EXT: + devpriv->command2_bits |= HWTRIG_BIT; + devpriv->command2_bits &= ~PRETRIG_BIT & ~SWTRIG_BIT; + break; + case TRIG_NOW: + devpriv->command2_bits |= SWTRIG_BIT; + devpriv->command2_bits &= ~PRETRIG_BIT & ~HWTRIG_BIT; + break; + default: + comedi_error(dev, "bug with start_src"); + return -1; + break; + } + switch (cmd->stop_src) { + case TRIG_EXT: + devpriv->command2_bits |= HWTRIG_BIT | PRETRIG_BIT; + break; + case TRIG_COUNT: + case TRIG_NONE: + break; + default: + comedi_error(dev, "bug with stop_src"); + return -1; + } + devpriv->write_byte(devpriv->command2_bits, dev->iobase + COMMAND2_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + return 0; +} + +/* interrupt service routine */ +static irqreturn_t labpc_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async; + struct comedi_cmd *cmd; + + if (dev->attached == 0) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + + async = s->async; + cmd = &async->cmd; + async->events = 0; + + /* read board status */ + devpriv->status1_bits = devpriv->read_byte(dev->iobase + STATUS1_REG); + if (thisboard->register_layout == labpc_1200_layout) + devpriv->status2_bits = + devpriv->read_byte(dev->iobase + STATUS2_REG); + + if ((devpriv->status1_bits & (DMATC_BIT | TIMER_BIT | OVERFLOW_BIT | + OVERRUN_BIT | DATA_AVAIL_BIT)) == 0 + && (devpriv->status2_bits & A1_TC_BIT) == 0 + && (devpriv->status2_bits & FNHF_BIT)) { + return IRQ_NONE; + } + + if (devpriv->status1_bits & OVERRUN_BIT) { + /* clear error interrupt */ + devpriv->write_byte(0x1, dev->iobase + ADC_CLEAR_REG); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + comedi_event(dev, s); + comedi_error(dev, "overrun"); + return IRQ_HANDLED; + } + +#ifdef CONFIG_ISA_DMA_API + if (devpriv->current_transfer == isa_dma_transfer) { + /* + * if a dma terminal count of external stop trigger + * has occurred + */ + if (devpriv->status1_bits & DMATC_BIT || + (thisboard->register_layout == labpc_1200_layout + && devpriv->status2_bits & A1_TC_BIT)) { + handle_isa_dma(dev); + } + } else +#endif + labpc_drain_fifo(dev); + + if (devpriv->status1_bits & TIMER_BIT) { + comedi_error(dev, "handled timer interrupt?"); + /* clear it */ + devpriv->write_byte(0x1, dev->iobase + TIMER_CLEAR_REG); + } + + if (devpriv->status1_bits & OVERFLOW_BIT) { + /* clear error interrupt */ + devpriv->write_byte(0x1, dev->iobase + ADC_CLEAR_REG); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + comedi_event(dev, s); + comedi_error(dev, "overflow"); + return IRQ_HANDLED; + } + /* handle external stop trigger */ + if (cmd->stop_src == TRIG_EXT) { + if (devpriv->status2_bits & A1_TC_BIT) { + labpc_drain_dregs(dev); + labpc_cancel(dev, s); + async->events |= COMEDI_CB_EOA; + } + } + + /* TRIG_COUNT end of acquisition */ + if (cmd->stop_src == TRIG_COUNT) { + if (devpriv->count == 0) { + labpc_cancel(dev, s); + async->events |= COMEDI_CB_EOA; + } + } + + comedi_event(dev, s); + return IRQ_HANDLED; +} + +/* read all available samples from ai fifo */ +static int labpc_drain_fifo(struct comedi_device *dev) +{ + unsigned int lsb, msb; + short data; + struct comedi_async *async = dev->read_subdev->async; + const int timeout = 10000; + unsigned int i; + + devpriv->status1_bits = devpriv->read_byte(dev->iobase + STATUS1_REG); + + for (i = 0; (devpriv->status1_bits & DATA_AVAIL_BIT) && i < timeout; + i++) { + /* quit if we have all the data we want */ + if (async->cmd.stop_src == TRIG_COUNT) { + if (devpriv->count == 0) + break; + devpriv->count--; + } + lsb = devpriv->read_byte(dev->iobase + ADC_FIFO_REG); + msb = devpriv->read_byte(dev->iobase + ADC_FIFO_REG); + data = (msb << 8) | lsb; + cfc_write_to_buffer(dev->read_subdev, data); + devpriv->status1_bits = + devpriv->read_byte(dev->iobase + STATUS1_REG); + } + if (i == timeout) { + comedi_error(dev, "ai timeout, fifo never empties"); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + return -1; + } + + return 0; +} + +#ifdef CONFIG_ISA_DMA_API +static void labpc_drain_dma(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->read_subdev; + struct comedi_async *async = s->async; + int status; + unsigned long flags; + unsigned int max_points, num_points, residue, leftover; + int i; + + status = devpriv->status1_bits; + + flags = claim_dma_lock(); + disable_dma(devpriv->dma_chan); + /* clear flip-flop to make sure 2-byte registers for + * count and address get set correctly */ + clear_dma_ff(devpriv->dma_chan); + + /* figure out how many points to read */ + max_points = devpriv->dma_transfer_size / sample_size; + /* residue is the number of points left to be done on the dma + * transfer. It should always be zero at this point unless + * the stop_src is set to external triggering. + */ + residue = get_dma_residue(devpriv->dma_chan) / sample_size; + num_points = max_points - residue; + if (devpriv->count < num_points && async->cmd.stop_src == TRIG_COUNT) + num_points = devpriv->count; + + /* figure out how many points will be stored next time */ + leftover = 0; + if (async->cmd.stop_src != TRIG_COUNT) { + leftover = devpriv->dma_transfer_size / sample_size; + } else if (devpriv->count > num_points) { + leftover = devpriv->count - num_points; + if (leftover > max_points) + leftover = max_points; + } + + /* write data to comedi buffer */ + for (i = 0; i < num_points; i++) + cfc_write_to_buffer(s, devpriv->dma_buffer[i]); + + if (async->cmd.stop_src == TRIG_COUNT) + devpriv->count -= num_points; + + /* set address and count for next transfer */ + set_dma_addr(devpriv->dma_chan, virt_to_bus(devpriv->dma_buffer)); + set_dma_count(devpriv->dma_chan, leftover * sample_size); + release_dma_lock(flags); + + async->events |= COMEDI_CB_BLOCK; +} + +static void handle_isa_dma(struct comedi_device *dev) +{ + labpc_drain_dma(dev); + + enable_dma(devpriv->dma_chan); + + /* clear dma tc interrupt */ + devpriv->write_byte(0x1, dev->iobase + DMATC_CLEAR_REG); +} +#endif + +/* makes sure all data acquired by board is transferred to comedi (used + * when acquisition is terminated by stop_src == TRIG_EXT). */ +static void labpc_drain_dregs(struct comedi_device *dev) +{ +#ifdef CONFIG_ISA_DMA_API + if (devpriv->current_transfer == isa_dma_transfer) + labpc_drain_dma(dev); +#endif + + labpc_drain_fifo(dev); +} + +static int labpc_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, n; + int chan, range; + int lsb, msb; + int timeout = 1000; + unsigned long flags; + + /* disable timed conversions */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->command2_bits &= ~SWTRIG_BIT & ~HWTRIG_BIT & ~PRETRIG_BIT; + devpriv->write_byte(devpriv->command2_bits, dev->iobase + COMMAND2_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* disable interrupt generation and dma */ + devpriv->command3_bits = 0; + devpriv->write_byte(devpriv->command3_bits, dev->iobase + COMMAND3_REG); + + /* set gain and channel */ + devpriv->command1_bits = 0; + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + devpriv->command1_bits |= thisboard->ai_range_code[range]; + /* munge channel bits for differential/scan disabled mode */ + if (CR_AREF(insn->chanspec) == AREF_DIFF) + chan *= 2; + devpriv->command1_bits |= ADC_CHAN_BITS(chan); + devpriv->write_byte(devpriv->command1_bits, dev->iobase + COMMAND1_REG); + + /* setup command6 register for 1200 boards */ + if (thisboard->register_layout == labpc_1200_layout) { + /* reference inputs to ground or common? */ + if (CR_AREF(insn->chanspec) != AREF_GROUND) + devpriv->command6_bits |= ADC_COMMON_BIT; + else + devpriv->command6_bits &= ~ADC_COMMON_BIT; + /* bipolar or unipolar range? */ + if (thisboard->ai_range_is_unipolar[range]) + devpriv->command6_bits |= ADC_UNIP_BIT; + else + devpriv->command6_bits &= ~ADC_UNIP_BIT; + /* don't interrupt on fifo half full */ + devpriv->command6_bits &= ~ADC_FHF_INTR_EN_BIT; + /* don't enable interrupt on counter a1 terminal count? */ + devpriv->command6_bits &= ~A1_INTR_EN_BIT; + /* write to register */ + devpriv->write_byte(devpriv->command6_bits, + dev->iobase + COMMAND6_REG); + } + /* setup command4 register */ + devpriv->command4_bits = 0; + devpriv->command4_bits |= EXT_CONVERT_DISABLE_BIT; + /* single-ended/differential */ + if (CR_AREF(insn->chanspec) == AREF_DIFF) + devpriv->command4_bits |= ADC_DIFF_BIT; + devpriv->write_byte(devpriv->command4_bits, dev->iobase + COMMAND4_REG); + + /* + * initialize pacer counter output to make sure it doesn't + * cause any problems + */ + devpriv->write_byte(INIT_A0_BITS, dev->iobase + COUNTER_A_CONTROL_REG); + + labpc_clear_adc_fifo(dev); + + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + devpriv->write_byte(0x1, dev->iobase + ADC_CONVERT_REG); + + for (i = 0; i < timeout; i++) { + if (devpriv->read_byte(dev->iobase + + STATUS1_REG) & DATA_AVAIL_BIT) + break; + udelay(1); + } + if (i == timeout) { + comedi_error(dev, "timeout"); + return -ETIME; + } + lsb = devpriv->read_byte(dev->iobase + ADC_FIFO_REG); + msb = devpriv->read_byte(dev->iobase + ADC_FIFO_REG); + data[n] = (msb << 8) | lsb; + } + + return n; +} + +/* analog output insn */ +static int labpc_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int channel, range; + unsigned long flags; + int lsb, msb; + + channel = CR_CHAN(insn->chanspec); + + /* turn off pacing of analog output channel */ + /* note: hardware bug in daqcard-1200 means pacing cannot + * be independently enabled/disabled for its the two channels */ + spin_lock_irqsave(&dev->spinlock, flags); + devpriv->command2_bits &= ~DAC_PACED_BIT(channel); + devpriv->write_byte(devpriv->command2_bits, dev->iobase + COMMAND2_REG); + spin_unlock_irqrestore(&dev->spinlock, flags); + + /* set range */ + if (thisboard->register_layout == labpc_1200_layout) { + range = CR_RANGE(insn->chanspec); + if (range & AO_RANGE_IS_UNIPOLAR) + devpriv->command6_bits |= DAC_UNIP_BIT(channel); + else + devpriv->command6_bits &= ~DAC_UNIP_BIT(channel); + /* write to register */ + devpriv->write_byte(devpriv->command6_bits, + dev->iobase + COMMAND6_REG); + } + /* send data */ + lsb = data[0] & 0xff; + msb = (data[0] >> 8) & 0xff; + devpriv->write_byte(lsb, dev->iobase + DAC_LSB_REG(channel)); + devpriv->write_byte(msb, dev->iobase + DAC_MSB_REG(channel)); + + /* remember value for readback */ + devpriv->ao_value[channel] = data[0]; + + return 1; +} + +/* analog output readback insn */ +static int labpc_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = devpriv->ao_value[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int labpc_calib_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = devpriv->caldac[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int labpc_calib_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int channel = CR_CHAN(insn->chanspec); + + write_caldac(dev, channel, data[0]); + return 1; +} + +static int labpc_eeprom_read_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = devpriv->eeprom_data[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int labpc_eeprom_write_insn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int channel = CR_CHAN(insn->chanspec); + int ret; + + /* only allow writes to user area of eeprom */ + if (channel < 16 || channel > 127) { + printk + ("eeprom writes are only allowed to channels 16 through 127 (the pointer and user areas)"); + return -EINVAL; + } + + ret = labpc_eeprom_write(dev, channel, data[0]); + if (ret < 0) + return ret; + + return 1; +} + +#ifdef CONFIG_ISA_DMA_API +/* utility function that suggests a dma transfer size in bytes */ +static unsigned int labpc_suggest_transfer_size(struct comedi_cmd cmd) +{ + unsigned int size; + unsigned int freq; + + if (cmd.convert_src == TRIG_TIMER) + freq = 1000000000 / cmd.convert_arg; + /* return some default value */ + else + freq = 0xffffffff; + + /* make buffer fill in no more than 1/3 second */ + size = (freq / 3) * sample_size; + + /* set a minimum and maximum size allowed */ + if (size > dma_buffer_size) + size = dma_buffer_size - dma_buffer_size % sample_size; + else if (size < sample_size) + size = sample_size; + + return size; +} +#endif + +/* figures out what counter values to use based on command */ +static void labpc_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd) +{ + /* max value for 16 bit counter in mode 2 */ + const int max_counter_value = 0x10000; + /* min value for 16 bit counter in mode 2 */ + const int min_counter_value = 2; + unsigned int base_period; + + /* + * if both convert and scan triggers are TRIG_TIMER, then they + * both rely on counter b0 + */ + if (labpc_ai_convert_period(cmd) && labpc_ai_scan_period(cmd)) { + /* + * pick the lowest b0 divisor value we can (for maximum input + * clock speed on convert and scan counters) + */ + devpriv->divisor_b0 = (labpc_ai_scan_period(cmd) - 1) / + (LABPC_TIMER_BASE * max_counter_value) + 1; + if (devpriv->divisor_b0 < min_counter_value) + devpriv->divisor_b0 = min_counter_value; + if (devpriv->divisor_b0 > max_counter_value) + devpriv->divisor_b0 = max_counter_value; + + base_period = LABPC_TIMER_BASE * devpriv->divisor_b0; + + /* set a0 for conversion frequency and b1 for scan frequency */ + switch (cmd->flags & TRIG_ROUND_MASK) { + default: + case TRIG_ROUND_NEAREST: + devpriv->divisor_a0 = + (labpc_ai_convert_period(cmd) + + (base_period / 2)) / base_period; + devpriv->divisor_b1 = + (labpc_ai_scan_period(cmd) + + (base_period / 2)) / base_period; + break; + case TRIG_ROUND_UP: + devpriv->divisor_a0 = + (labpc_ai_convert_period(cmd) + (base_period - + 1)) / base_period; + devpriv->divisor_b1 = + (labpc_ai_scan_period(cmd) + (base_period - + 1)) / base_period; + break; + case TRIG_ROUND_DOWN: + devpriv->divisor_a0 = + labpc_ai_convert_period(cmd) / base_period; + devpriv->divisor_b1 = + labpc_ai_scan_period(cmd) / base_period; + break; + } + /* make sure a0 and b1 values are acceptable */ + if (devpriv->divisor_a0 < min_counter_value) + devpriv->divisor_a0 = min_counter_value; + if (devpriv->divisor_a0 > max_counter_value) + devpriv->divisor_a0 = max_counter_value; + if (devpriv->divisor_b1 < min_counter_value) + devpriv->divisor_b1 = min_counter_value; + if (devpriv->divisor_b1 > max_counter_value) + devpriv->divisor_b1 = max_counter_value; + /* write corrected timings to command */ + labpc_set_ai_convert_period(cmd, + base_period * devpriv->divisor_a0); + labpc_set_ai_scan_period(cmd, + base_period * devpriv->divisor_b1); + /* + * if only one TRIG_TIMER is used, we can employ the generic + * cascaded timing functions + */ + } else if (labpc_ai_scan_period(cmd)) { + unsigned int scan_period; + + scan_period = labpc_ai_scan_period(cmd); + /* + * calculate cascaded counter values + * that give desired scan timing + */ + i8253_cascade_ns_to_timer_2div(LABPC_TIMER_BASE, + &(devpriv->divisor_b1), + &(devpriv->divisor_b0), + &scan_period, + cmd->flags & TRIG_ROUND_MASK); + labpc_set_ai_scan_period(cmd, scan_period); + } else if (labpc_ai_convert_period(cmd)) { + unsigned int convert_period; + + convert_period = labpc_ai_convert_period(cmd); + /* + * calculate cascaded counter values + * that give desired conversion timing + */ + i8253_cascade_ns_to_timer_2div(LABPC_TIMER_BASE, + &(devpriv->divisor_a0), + &(devpriv->divisor_b0), + &convert_period, + cmd->flags & TRIG_ROUND_MASK); + labpc_set_ai_convert_period(cmd, convert_period); + } +} + +static int labpc_dio_mem_callback(int dir, int port, int data, + unsigned long iobase) +{ + if (dir) { + writeb(data, (void *)(iobase + port)); + return 0; + } else { + return readb((void *)(iobase + port)); + } +} + +/* lowlevel write to eeprom/dac */ +static void labpc_serial_out(struct comedi_device *dev, unsigned int value, + unsigned int value_width) +{ + int i; + + for (i = 1; i <= value_width; i++) { + /* clear serial clock */ + devpriv->command5_bits &= ~SCLOCK_BIT; + /* send bits most significant bit first */ + if (value & (1 << (value_width - i))) + devpriv->command5_bits |= SDATA_BIT; + else + devpriv->command5_bits &= ~SDATA_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, + dev->iobase + COMMAND5_REG); + /* set clock to load bit */ + devpriv->command5_bits |= SCLOCK_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, + dev->iobase + COMMAND5_REG); + } +} + +/* lowlevel read from eeprom */ +static unsigned int labpc_serial_in(struct comedi_device *dev) +{ + unsigned int value = 0; + int i; + const int value_width = 8; /* number of bits wide values are */ + + for (i = 1; i <= value_width; i++) { + /* set serial clock */ + devpriv->command5_bits |= SCLOCK_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, + dev->iobase + COMMAND5_REG); + /* clear clock bit */ + devpriv->command5_bits &= ~SCLOCK_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, + dev->iobase + COMMAND5_REG); + /* read bits most significant bit first */ + udelay(1); + devpriv->status2_bits = + devpriv->read_byte(dev->iobase + STATUS2_REG); + if (devpriv->status2_bits & EEPROM_OUT_BIT) + value |= 1 << (value_width - i); + } + + return value; +} + +static unsigned int labpc_eeprom_read(struct comedi_device *dev, + unsigned int address) +{ + unsigned int value; + /* bits to tell eeprom to expect a read */ + const int read_instruction = 0x3; + /* 8 bit write lengths to eeprom */ + const int write_length = 8; + + /* enable read/write to eeprom */ + devpriv->command5_bits &= ~EEPROM_EN_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + devpriv->command5_bits |= EEPROM_EN_BIT | EEPROM_WRITE_UNPROTECT_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + + /* send read instruction */ + labpc_serial_out(dev, read_instruction, write_length); + /* send 8 bit address to read from */ + labpc_serial_out(dev, address, write_length); + /* read result */ + value = labpc_serial_in(dev); + + /* disable read/write to eeprom */ + devpriv->command5_bits &= ~EEPROM_EN_BIT & ~EEPROM_WRITE_UNPROTECT_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + + return value; +} + +static int labpc_eeprom_write(struct comedi_device *dev, + unsigned int address, unsigned int value) +{ + const int write_enable_instruction = 0x6; + const int write_instruction = 0x2; + const int write_length = 8; /* 8 bit write lengths to eeprom */ + const int write_in_progress_bit = 0x1; + const int timeout = 10000; + int i; + + /* make sure there isn't already a write in progress */ + for (i = 0; i < timeout; i++) { + if ((labpc_eeprom_read_status(dev) & write_in_progress_bit) == + 0) + break; + } + if (i == timeout) { + comedi_error(dev, "eeprom write timed out"); + return -ETIME; + } + /* update software copy of eeprom */ + devpriv->eeprom_data[address] = value; + + /* enable read/write to eeprom */ + devpriv->command5_bits &= ~EEPROM_EN_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + devpriv->command5_bits |= EEPROM_EN_BIT | EEPROM_WRITE_UNPROTECT_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + + /* send write_enable instruction */ + labpc_serial_out(dev, write_enable_instruction, write_length); + devpriv->command5_bits &= ~EEPROM_EN_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + + /* send write instruction */ + devpriv->command5_bits |= EEPROM_EN_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + labpc_serial_out(dev, write_instruction, write_length); + /* send 8 bit address to write to */ + labpc_serial_out(dev, address, write_length); + /* write value */ + labpc_serial_out(dev, value, write_length); + devpriv->command5_bits &= ~EEPROM_EN_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + + /* disable read/write to eeprom */ + devpriv->command5_bits &= ~EEPROM_EN_BIT & ~EEPROM_WRITE_UNPROTECT_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + + return 0; +} + +static unsigned int labpc_eeprom_read_status(struct comedi_device *dev) +{ + unsigned int value; + const int read_status_instruction = 0x5; + const int write_length = 8; /* 8 bit write lengths to eeprom */ + + /* enable read/write to eeprom */ + devpriv->command5_bits &= ~EEPROM_EN_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + devpriv->command5_bits |= EEPROM_EN_BIT | EEPROM_WRITE_UNPROTECT_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + + /* send read status instruction */ + labpc_serial_out(dev, read_status_instruction, write_length); + /* read result */ + value = labpc_serial_in(dev); + + /* disable read/write to eeprom */ + devpriv->command5_bits &= ~EEPROM_EN_BIT & ~EEPROM_WRITE_UNPROTECT_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + + return value; +} + +/* writes to 8 bit calibration dacs */ +static void write_caldac(struct comedi_device *dev, unsigned int channel, + unsigned int value) +{ + if (value == devpriv->caldac[channel]) + return; + devpriv->caldac[channel] = value; + + /* clear caldac load bit and make sure we don't write to eeprom */ + devpriv->command5_bits &= + ~CALDAC_LOAD_BIT & ~EEPROM_EN_BIT & ~EEPROM_WRITE_UNPROTECT_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + + /* write 4 bit channel */ + labpc_serial_out(dev, channel, 4); + /* write 8 bit caldac value */ + labpc_serial_out(dev, value, 8); + + /* set and clear caldac bit to load caldac value */ + devpriv->command5_bits |= CALDAC_LOAD_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); + devpriv->command5_bits &= ~CALDAC_LOAD_BIT; + udelay(1); + devpriv->write_byte(devpriv->command5_bits, dev->iobase + COMMAND5_REG); +} + +#ifdef CONFIG_COMEDI_PCI_DRIVERS +static int __devinit driver_labpc_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_labpc.driver_name); +} + +static void __devexit driver_labpc_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_labpc_pci_driver = { + .id_table = labpc_pci_table, + .probe = &driver_labpc_pci_probe, + .remove = __devexit_p(&driver_labpc_pci_remove) +}; + +static int __init driver_labpc_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_labpc); + if (retval < 0) + return retval; + + driver_labpc_pci_driver.name = (char *)driver_labpc.driver_name; + return pci_register_driver(&driver_labpc_pci_driver); +} + +static void __exit driver_labpc_cleanup_module(void) +{ + pci_unregister_driver(&driver_labpc_pci_driver); + comedi_driver_unregister(&driver_labpc); +} + +module_init(driver_labpc_init_module); +module_exit(driver_labpc_cleanup_module); +#else +static int __init driver_labpc_init_module(void) +{ + return comedi_driver_register(&driver_labpc); +} + +static void __exit driver_labpc_cleanup_module(void) +{ + comedi_driver_unregister(&driver_labpc); +} + +module_init(driver_labpc_init_module); +module_exit(driver_labpc_cleanup_module); +#endif + + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_labpc.h b/drivers/staging/comedi/drivers/ni_labpc.h new file mode 100644 index 00000000..422cee58 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_labpc.h @@ -0,0 +1,112 @@ +/* + ni_labpc.h + + Header for ni_labpc.c and ni_labpc_cs.c + + Copyright (C) 2003 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _NI_LABPC_H +#define _NI_LABPC_H + +#define EEPROM_SIZE 256 /* 256 byte eeprom */ +#define NUM_AO_CHAN 2 /* boards have two analog output channels */ + +enum labpc_bustype { isa_bustype, pci_bustype, pcmcia_bustype }; +enum labpc_register_layout { labpc_plus_layout, labpc_1200_layout }; +enum transfer_type { fifo_not_empty_transfer, fifo_half_full_transfer, + isa_dma_transfer +}; + +struct labpc_board_struct { + const char *name; + int device_id; /* device id for pci and pcmcia boards */ + int ai_speed; /* maximum input speed in nanoseconds */ + enum labpc_bustype bustype; /* ISA/PCI/etc. */ + + /* 1200 has extra registers compared to pc+ */ + enum labpc_register_layout register_layout; + int has_ao; /* has analog output true/false */ + const struct comedi_lrange *ai_range_table; + const int *ai_range_code; + const int *ai_range_is_unipolar; + + /* board can auto scan up in ai channels, not just down */ + unsigned ai_scan_up:1; + + /* uses memory mapped io instead of ioports */ + unsigned memory_mapped_io:1; +}; + +struct labpc_private { + struct mite_struct *mite; /* for mite chip on pci-1200 */ + /* number of data points left to be taken */ + volatile unsigned long long count; + /* software copy of analog output values */ + unsigned int ao_value[NUM_AO_CHAN]; + /* software copys of bits written to command registers */ + volatile unsigned int command1_bits; + volatile unsigned int command2_bits; + volatile unsigned int command3_bits; + volatile unsigned int command4_bits; + volatile unsigned int command5_bits; + volatile unsigned int command6_bits; + /* store last read of board status registers */ + volatile unsigned int status1_bits; + volatile unsigned int status2_bits; + /* + * value to load into board's counter a0 (conversion pacing) for timed + * conversions + */ + unsigned int divisor_a0; + /* + * value to load into board's counter b0 (master) for timed conversions + */ + unsigned int divisor_b0; + /* + * value to load into board's counter b1 (scan pacing) for timed + * conversions + */ + unsigned int divisor_b1; + unsigned int dma_chan; /* dma channel to use */ + u16 *dma_buffer; /* buffer ai will dma into */ + /* transfer size in bytes for current transfer */ + unsigned int dma_transfer_size; + /* we are using dma/fifo-half-full/etc. */ + enum transfer_type current_transfer; + /* stores contents of board's eeprom */ + unsigned int eeprom_data[EEPROM_SIZE]; + /* stores settings of calibration dacs */ + unsigned int caldac[16]; + /* + * function pointers so we can use inb/outb or readb/writeb as + * appropriate + */ + unsigned int (*read_byte) (unsigned long address); + void (*write_byte) (unsigned int byte, unsigned long address); +}; + +int labpc_common_attach(struct comedi_device *dev, unsigned long iobase, + unsigned int irq, unsigned int dma); +int labpc_common_detach(struct comedi_device *dev); + +extern const int labpc_1200_is_unipolar[]; +extern const int labpc_1200_ai_gain_bits[]; +extern const struct comedi_lrange range_labpc_1200_ai; + +#endif /* _NI_LABPC_H */ diff --git a/drivers/staging/comedi/drivers/ni_labpc_cs.c b/drivers/staging/comedi/drivers/ni_labpc_cs.c new file mode 100644 index 00000000..ff384054 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_labpc_cs.c @@ -0,0 +1,320 @@ +/* + comedi/drivers/ni_labpc_cs.c + Driver for National Instruments daqcard-1200 boards + Copyright (C) 2001, 2002, 2003 Frank Mori Hess + + PCMCIA crap is adapted from dummy_cs.c 1.31 2001/08/24 12:13:13 + from the pcmcia package. + The initial developer of the pcmcia dummy_cs.c code is David A. Hinds + . Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************ +*/ +/* +Driver: ni_labpc_cs +Description: National Instruments Lab-PC (& compatibles) +Author: Frank Mori Hess +Devices: [National Instruments] DAQCard-1200 (daqcard-1200) +Status: works + +Thanks go to Fredrik Lingvall for much testing and perseverance in +helping to debug daqcard-1200 support. + +The 1200 series boards have onboard calibration dacs for correcting +analog input/output offsets and gains. The proper settings for these +caldacs are stored on the board's eeprom. To read the caldac values +from the eeprom and store them into a file that can be then be used by +comedilib, use the comedi_calibrate program. + +Configuration options: + none + +The daqcard-1200 has quirky chanlist requirements +when scanning multiple channels. Multiple channel scan +sequence must start at highest channel, then decrement down to +channel 0. Chanlists consisting of all one channel +are also legal, and allow you to pace conversions in bursts. + +*/ + +/* + +NI manuals: +340988a (daqcard-1200) + +*/ + +#undef LABPC_DEBUG /* debugging messages */ + +#include "../comedidev.h" + +#include +#include + +#include "8253.h" +#include "8255.h" +#include "comedi_fc.h" +#include "ni_labpc.h" + +#include +#include +#include + +static struct pcmcia_device *pcmcia_cur_dev; + +static int labpc_attach(struct comedi_device *dev, struct comedi_devconfig *it); + +static const struct labpc_board_struct labpc_cs_boards[] = { + { + .name = "daqcard-1200", + .device_id = 0x103, /* 0x10b is manufacturer id, + 0x103 is device id */ + .ai_speed = 10000, + .bustype = pcmcia_bustype, + .register_layout = labpc_1200_layout, + .has_ao = 1, + .ai_range_table = &range_labpc_1200_ai, + .ai_range_code = labpc_1200_ai_gain_bits, + .ai_range_is_unipolar = labpc_1200_is_unipolar, + .ai_scan_up = 0, + .memory_mapped_io = 0, + }, + /* duplicate entry, to support using alternate name */ + { + .name = "ni_labpc_cs", + .device_id = 0x103, + .ai_speed = 10000, + .bustype = pcmcia_bustype, + .register_layout = labpc_1200_layout, + .has_ao = 1, + .ai_range_table = &range_labpc_1200_ai, + .ai_range_code = labpc_1200_ai_gain_bits, + .ai_range_is_unipolar = labpc_1200_is_unipolar, + .ai_scan_up = 0, + .memory_mapped_io = 0, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct labpc_board_struct *)dev->board_ptr) + +static struct comedi_driver driver_labpc_cs = { + .driver_name = "ni_labpc_cs", + .module = THIS_MODULE, + .attach = &labpc_attach, + .detach = &labpc_common_detach, + .num_names = ARRAY_SIZE(labpc_cs_boards), + .board_name = &labpc_cs_boards[0].name, + .offset = sizeof(struct labpc_board_struct), +}; + +static int labpc_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + unsigned long iobase = 0; + unsigned int irq = 0; + struct pcmcia_device *link; + + /* allocate and initialize dev->private */ + if (alloc_private(dev, sizeof(struct labpc_private)) < 0) + return -ENOMEM; + + /* get base address, irq etc. based on bustype */ + switch (thisboard->bustype) { + case pcmcia_bustype: + link = pcmcia_cur_dev; /* XXX hack */ + if (!link) + return -EIO; + iobase = link->resource[0]->start; + irq = link->irq; + break; + default: + pr_err("bug! couldn't determine board type\n"); + return -EINVAL; + break; + } + return labpc_common_attach(dev, iobase, irq, 0); +} + +static void labpc_config(struct pcmcia_device *link); +static void labpc_release(struct pcmcia_device *link); +static int labpc_cs_suspend(struct pcmcia_device *p_dev); +static int labpc_cs_resume(struct pcmcia_device *p_dev); + +static int labpc_cs_attach(struct pcmcia_device *); +static void labpc_cs_detach(struct pcmcia_device *); + +struct local_info_t { + struct pcmcia_device *link; + int stop; + struct bus_operations *bus; +}; + +static int labpc_cs_attach(struct pcmcia_device *link) +{ + struct local_info_t *local; + + dev_dbg(&link->dev, "labpc_cs_attach()\n"); + + /* Allocate space for private device-specific data */ + local = kzalloc(sizeof(struct local_info_t), GFP_KERNEL); + if (!local) + return -ENOMEM; + local->link = link; + link->priv = local; + + pcmcia_cur_dev = link; + + labpc_config(link); + + return 0; +} /* labpc_cs_attach */ + +static void labpc_cs_detach(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "labpc_cs_detach\n"); + + /* + If the device is currently configured and active, we won't + actually delete it yet. Instead, it is marked so that when + the release() function is called, that will trigger a proper + detach(). + */ + ((struct local_info_t *)link->priv)->stop = 1; + labpc_release(link); + + /* This points to the parent local_info_t struct (may be null) */ + kfree(link->priv); + +} /* labpc_cs_detach */ + +static int labpc_pcmcia_config_loop(struct pcmcia_device *p_dev, + void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(p_dev); +} + + +static void labpc_config(struct pcmcia_device *link) +{ + int ret; + + dev_dbg(&link->dev, "labpc_config\n"); + + link->config_flags |= CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ | + CONF_AUTO_AUDIO | CONF_AUTO_SET_IO; + + ret = pcmcia_loop_config(link, labpc_pcmcia_config_loop, NULL); + if (ret) { + dev_warn(&link->dev, "no configuration found\n"); + goto failed; + } + + if (!link->irq) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + return; + +failed: + labpc_release(link); + +} /* labpc_config */ + +static void labpc_release(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "labpc_release\n"); + + pcmcia_disable_device(link); +} /* labpc_release */ + +static int labpc_cs_suspend(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + /* Mark the device as stopped, to block IO until later */ + local->stop = 1; + return 0; +} /* labpc_cs_suspend */ + +static int labpc_cs_resume(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + local->stop = 0; + return 0; +} /* labpc_cs_resume */ + +static const struct pcmcia_device_id labpc_cs_ids[] = { + /* N.B. These IDs should match those in labpc_cs_boards (ni_labpc.c) */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0103), /* daqcard-1200 */ + PCMCIA_DEVICE_NULL +}; + +MODULE_DEVICE_TABLE(pcmcia, labpc_cs_ids); +MODULE_AUTHOR("Frank Mori Hess "); +MODULE_DESCRIPTION("Comedi driver for National Instruments Lab-PC"); +MODULE_LICENSE("GPL"); + +struct pcmcia_driver labpc_cs_driver = { + .probe = labpc_cs_attach, + .remove = labpc_cs_detach, + .suspend = labpc_cs_suspend, + .resume = labpc_cs_resume, + .id_table = labpc_cs_ids, + .owner = THIS_MODULE, + .name = "daqcard-1200", +}; + +static int __init init_labpc_cs(void) +{ + pcmcia_register_driver(&labpc_cs_driver); + return 0; +} + +static void __exit exit_labpc_cs(void) +{ + pcmcia_unregister_driver(&labpc_cs_driver); +} + +int __init labpc_init_module(void) +{ + int ret; + + ret = init_labpc_cs(); + if (ret < 0) + return ret; + + return comedi_driver_register(&driver_labpc_cs); +} + +void __exit labpc_exit_module(void) +{ + exit_labpc_cs(); + comedi_driver_unregister(&driver_labpc_cs); +} + +module_init(labpc_init_module); +module_exit(labpc_exit_module); diff --git a/drivers/staging/comedi/drivers/ni_mio_common.c b/drivers/staging/comedi/drivers/ni_mio_common.c new file mode 100644 index 00000000..fd232bc5 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_mio_common.c @@ -0,0 +1,6005 @@ +/* + comedi/drivers/ni_mio_common.c + Hardware driver for DAQ-STC based boards + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2001 David A. Schleef + Copyright (C) 2002-2006 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* + This file is meant to be included by another file, e.g., + ni_atmio.c or ni_pcimio.c. + + Interrupt support originally added by Truxton Fulton + + + References (from ftp://ftp.natinst.com/support/manuals): + + 340747b.pdf AT-MIO E series Register Level Programmer Manual + 341079b.pdf PCI E Series RLPM + 340934b.pdf DAQ-STC reference manual + 67xx and 611x registers (from ftp://ftp.ni.com/support/daq/mhddk/documentation/) + release_ni611x.pdf + release_ni67xx.pdf + Other possibly relevant info: + + 320517c.pdf User manual (obsolete) + 320517f.pdf User manual (new) + 320889a.pdf delete + 320906c.pdf maximum signal ratings + 321066a.pdf about 16x + 321791a.pdf discontinuation of at-mio-16e-10 rev. c + 321808a.pdf about at-mio-16e-10 rev P + 321837a.pdf discontinuation of at-mio-16de-10 rev d + 321838a.pdf about at-mio-16de-10 rev N + + ISSUES: + + - the interrupt routine needs to be cleaned up + + 2006-02-07: S-Series PCI-6143: Support has been added but is not + fully tested as yet. Terry Barnaby, BEAM Ltd. +*/ + +/* #define DEBUG_INTERRUPT */ +/* #define DEBUG_STATUS_A */ +/* #define DEBUG_STATUS_B */ + +#include +#include +#include "8255.h" +#include "mite.h" +#include "comedi_fc.h" + +#ifndef MDPRINTK +#define MDPRINTK(format, args...) +#endif + +/* A timeout count */ +#define NI_TIMEOUT 1000 +static const unsigned old_RTSI_clock_channel = 7; + +/* Note: this table must match the ai_gain_* definitions */ +static const short ni_gainlkup[][16] = { + [ai_gain_16] = {0, 1, 2, 3, 4, 5, 6, 7, + 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107}, + [ai_gain_8] = {1, 2, 4, 7, 0x101, 0x102, 0x104, 0x107}, + [ai_gain_14] = {1, 2, 3, 4, 5, 6, 7, + 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107}, + [ai_gain_4] = {0, 1, 4, 7}, + [ai_gain_611x] = {0x00a, 0x00b, 0x001, 0x002, + 0x003, 0x004, 0x005, 0x006}, + [ai_gain_622x] = {0, 1, 4, 5}, + [ai_gain_628x] = {1, 2, 3, 4, 5, 6, 7}, + [ai_gain_6143] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, +}; + +static const struct comedi_lrange range_ni_E_ai = { 16, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-2.5, 2.5), + RANGE(-1, 1), + RANGE(-0.5, 0.5), + RANGE(-0.25, 0.25), + RANGE(-0.1, 0.1), + RANGE(-0.05, 0.05), + RANGE(0, 20), + RANGE(0, 10), + RANGE(0, 5), + RANGE(0, 2), + RANGE(0, 1), + RANGE(0, 0.5), + RANGE(0, 0.2), + RANGE(0, 0.1), + } +}; + +static const struct comedi_lrange range_ni_E_ai_limited = { 8, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-1, 1), + RANGE(-0.1, + 0.1), + RANGE(0, 10), + RANGE(0, 5), + RANGE(0, 1), + RANGE(0, 0.1), + } +}; + +static const struct comedi_lrange range_ni_E_ai_limited14 = { 14, { + RANGE(-10, + 10), + RANGE(-5, 5), + RANGE(-2, 2), + RANGE(-1, 1), + RANGE(-0.5, + 0.5), + RANGE(-0.2, + 0.2), + RANGE(-0.1, + 0.1), + RANGE(0, 10), + RANGE(0, 5), + RANGE(0, 2), + RANGE(0, 1), + RANGE(0, + 0.5), + RANGE(0, + 0.2), + RANGE(0, + 0.1), + } +}; + +static const struct comedi_lrange range_ni_E_ai_bipolar4 = { 4, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-0.5, + 0.5), + RANGE(-0.05, + 0.05), + } +}; + +static const struct comedi_lrange range_ni_E_ai_611x = { 8, { + RANGE(-50, 50), + RANGE(-20, 20), + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-2, 2), + RANGE(-1, 1), + RANGE(-0.5, 0.5), + RANGE(-0.2, 0.2), + } +}; + +static const struct comedi_lrange range_ni_M_ai_622x = { 4, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-1, 1), + RANGE(-0.2, 0.2), + } +}; + +static const struct comedi_lrange range_ni_M_ai_628x = { 7, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-2, 2), + RANGE(-1, 1), + RANGE(-0.5, 0.5), + RANGE(-0.2, 0.2), + RANGE(-0.1, 0.1), + } +}; + +static const struct comedi_lrange range_ni_S_ai_6143 = { 1, { + RANGE(-5, +5), + } +}; + +static const struct comedi_lrange range_ni_E_ao_ext = { 4, { + RANGE(-10, 10), + RANGE(0, 10), + RANGE_ext(-1, 1), + RANGE_ext(0, 1), + } +}; + +static const struct comedi_lrange *const ni_range_lkup[] = { + [ai_gain_16] = &range_ni_E_ai, + [ai_gain_8] = &range_ni_E_ai_limited, + [ai_gain_14] = &range_ni_E_ai_limited14, + [ai_gain_4] = &range_ni_E_ai_bipolar4, + [ai_gain_611x] = &range_ni_E_ai_611x, + [ai_gain_622x] = &range_ni_M_ai_622x, + [ai_gain_628x] = &range_ni_M_ai_628x, + [ai_gain_6143] = &range_ni_S_ai_6143 +}; + +static int ni_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_cdio_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd); +static int ni_cdio_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int ni_cdio_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void handle_cdio_interrupt(struct comedi_device *dev); +static int ni_cdo_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum); + +static int ni_serial_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_serial_hw_readwrite8(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned char data_out, + unsigned char *data_in); +static int ni_serial_sw_readwrite8(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned char data_out, + unsigned char *data_in); + +static int ni_calib_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_calib_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int ni_eeprom_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_m_series_eeprom_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int ni_pfi_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_pfi_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static unsigned ni_old_get_pfi_routing(struct comedi_device *dev, + unsigned chan); + +static void ni_rtsi_init(struct comedi_device *dev); +static int ni_rtsi_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_rtsi_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static void caldac_setup(struct comedi_device *dev, struct comedi_subdevice *s); +static int ni_read_eeprom(struct comedi_device *dev, int addr); + +#ifdef DEBUG_STATUS_A +static void ni_mio_print_status_a(int status); +#else +#define ni_mio_print_status_a(a) +#endif +#ifdef DEBUG_STATUS_B +static void ni_mio_print_status_b(int status); +#else +#define ni_mio_print_status_b(a) +#endif + +static int ni_ai_reset(struct comedi_device *dev, struct comedi_subdevice *s); +#ifndef PCIDMA +static void ni_handle_fifo_half_full(struct comedi_device *dev); +static int ni_ao_fifo_half_empty(struct comedi_device *dev, + struct comedi_subdevice *s); +#endif +static void ni_handle_fifo_dregs(struct comedi_device *dev); +static int ni_ai_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum); +static void ni_load_channelgain_list(struct comedi_device *dev, + unsigned int n_chan, unsigned int *list); +static void shutdown_ai_command(struct comedi_device *dev); + +static int ni_ao_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum); + +static int ni_ao_reset(struct comedi_device *dev, struct comedi_subdevice *s); + +static int ni_8255_callback(int dir, int port, int data, unsigned long arg); + +static int ni_gpct_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_gpct_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_gpct_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_gpct_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int ni_gpct_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd); +static int ni_gpct_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void handle_gpct_interrupt(struct comedi_device *dev, + unsigned short counter_index); + +static int init_cs5529(struct comedi_device *dev); +static int cs5529_do_conversion(struct comedi_device *dev, + unsigned short *data); +static int cs5529_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +#ifdef NI_CS5529_DEBUG +static unsigned int cs5529_config_read(struct comedi_device *dev, + unsigned int reg_select_bits); +#endif +static void cs5529_config_write(struct comedi_device *dev, unsigned int value, + unsigned int reg_select_bits); + +static int ni_m_series_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ni_6143_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int ni_set_master_clock(struct comedi_device *dev, unsigned source, + unsigned period_ns); +static void ack_a_interrupt(struct comedi_device *dev, unsigned short a_status); +static void ack_b_interrupt(struct comedi_device *dev, unsigned short b_status); + +enum aimodes { + AIMODE_NONE = 0, + AIMODE_HALF_FULL = 1, + AIMODE_SCAN = 2, + AIMODE_SAMPLE = 3, +}; + +enum ni_common_subdevices { + NI_AI_SUBDEV, + NI_AO_SUBDEV, + NI_DIO_SUBDEV, + NI_8255_DIO_SUBDEV, + NI_UNUSED_SUBDEV, + NI_CALIBRATION_SUBDEV, + NI_EEPROM_SUBDEV, + NI_PFI_DIO_SUBDEV, + NI_CS5529_CALIBRATION_SUBDEV, + NI_SERIAL_SUBDEV, + NI_RTSI_SUBDEV, + NI_GPCT0_SUBDEV, + NI_GPCT1_SUBDEV, + NI_FREQ_OUT_SUBDEV, + NI_NUM_SUBDEVICES +}; +static inline unsigned NI_GPCT_SUBDEV(unsigned counter_index) +{ + switch (counter_index) { + case 0: + return NI_GPCT0_SUBDEV; + break; + case 1: + return NI_GPCT1_SUBDEV; + break; + default: + break; + } + BUG(); + return NI_GPCT0_SUBDEV; +} + +enum timebase_nanoseconds { + TIMEBASE_1_NS = 50, + TIMEBASE_2_NS = 10000 +}; + +#define SERIAL_DISABLED 0 +#define SERIAL_600NS 600 +#define SERIAL_1_2US 1200 +#define SERIAL_10US 10000 + +static const int num_adc_stages_611x = 3; + +static void handle_a_interrupt(struct comedi_device *dev, unsigned short status, + unsigned ai_mite_status); +static void handle_b_interrupt(struct comedi_device *dev, unsigned short status, + unsigned ao_mite_status); +static void get_last_sample_611x(struct comedi_device *dev); +static void get_last_sample_6143(struct comedi_device *dev); + +static inline void ni_set_bitfield(struct comedi_device *dev, int reg, + unsigned bit_mask, unsigned bit_values) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags); + switch (reg) { + case Interrupt_A_Enable_Register: + devpriv->int_a_enable_reg &= ~bit_mask; + devpriv->int_a_enable_reg |= bit_values & bit_mask; + devpriv->stc_writew(dev, devpriv->int_a_enable_reg, + Interrupt_A_Enable_Register); + break; + case Interrupt_B_Enable_Register: + devpriv->int_b_enable_reg &= ~bit_mask; + devpriv->int_b_enable_reg |= bit_values & bit_mask; + devpriv->stc_writew(dev, devpriv->int_b_enable_reg, + Interrupt_B_Enable_Register); + break; + case IO_Bidirection_Pin_Register: + devpriv->io_bidirection_pin_reg &= ~bit_mask; + devpriv->io_bidirection_pin_reg |= bit_values & bit_mask; + devpriv->stc_writew(dev, devpriv->io_bidirection_pin_reg, + IO_Bidirection_Pin_Register); + break; + case AI_AO_Select: + devpriv->ai_ao_select_reg &= ~bit_mask; + devpriv->ai_ao_select_reg |= bit_values & bit_mask; + ni_writeb(devpriv->ai_ao_select_reg, AI_AO_Select); + break; + case G0_G1_Select: + devpriv->g0_g1_select_reg &= ~bit_mask; + devpriv->g0_g1_select_reg |= bit_values & bit_mask; + ni_writeb(devpriv->g0_g1_select_reg, G0_G1_Select); + break; + default: + printk("Warning %s() called with invalid register\n", __func__); + printk("reg is %d\n", reg); + break; + } + mmiowb(); + spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags); +} + +#ifdef PCIDMA +static int ni_ai_drain_dma(struct comedi_device *dev); + +/* DMA channel setup */ + +/* negative channel means no channel */ +static inline void ni_set_ai_dma_channel(struct comedi_device *dev, int channel) +{ + unsigned bitfield; + + if (channel >= 0) { + bitfield = + (ni_stc_dma_channel_select_bitfield(channel) << + AI_DMA_Select_Shift) & AI_DMA_Select_Mask; + } else { + bitfield = 0; + } + ni_set_bitfield(dev, AI_AO_Select, AI_DMA_Select_Mask, bitfield); +} + +/* negative channel means no channel */ +static inline void ni_set_ao_dma_channel(struct comedi_device *dev, int channel) +{ + unsigned bitfield; + + if (channel >= 0) { + bitfield = + (ni_stc_dma_channel_select_bitfield(channel) << + AO_DMA_Select_Shift) & AO_DMA_Select_Mask; + } else { + bitfield = 0; + } + ni_set_bitfield(dev, AI_AO_Select, AO_DMA_Select_Mask, bitfield); +} + +/* negative mite_channel means no channel */ +static inline void ni_set_gpct_dma_channel(struct comedi_device *dev, + unsigned gpct_index, + int mite_channel) +{ + unsigned bitfield; + + if (mite_channel >= 0) { + bitfield = GPCT_DMA_Select_Bits(gpct_index, mite_channel); + } else { + bitfield = 0; + } + ni_set_bitfield(dev, G0_G1_Select, GPCT_DMA_Select_Mask(gpct_index), + bitfield); +} + +/* negative mite_channel means no channel */ +static inline void ni_set_cdo_dma_channel(struct comedi_device *dev, + int mite_channel) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags); + devpriv->cdio_dma_select_reg &= ~CDO_DMA_Select_Mask; + if (mite_channel >= 0) { + /*XXX just guessing ni_stc_dma_channel_select_bitfield() returns the right bits, + under the assumption the cdio dma selection works just like ai/ao/gpct. + Definitely works for dma channels 0 and 1. */ + devpriv->cdio_dma_select_reg |= + (ni_stc_dma_channel_select_bitfield(mite_channel) << + CDO_DMA_Select_Shift) & CDO_DMA_Select_Mask; + } + ni_writeb(devpriv->cdio_dma_select_reg, M_Offset_CDIO_DMA_Select); + mmiowb(); + spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags); +} + +static int ni_request_ai_mite_channel(struct comedi_device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + BUG_ON(devpriv->ai_mite_chan); + devpriv->ai_mite_chan = + mite_request_channel(devpriv->mite, devpriv->ai_mite_ring); + if (devpriv->ai_mite_chan == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for analog input."); + return -EBUSY; + } + devpriv->ai_mite_chan->dir = COMEDI_INPUT; + ni_set_ai_dma_channel(dev, devpriv->ai_mite_chan->channel); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return 0; +} + +static int ni_request_ao_mite_channel(struct comedi_device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + BUG_ON(devpriv->ao_mite_chan); + devpriv->ao_mite_chan = + mite_request_channel(devpriv->mite, devpriv->ao_mite_ring); + if (devpriv->ao_mite_chan == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for analog outut."); + return -EBUSY; + } + devpriv->ao_mite_chan->dir = COMEDI_OUTPUT; + ni_set_ao_dma_channel(dev, devpriv->ao_mite_chan->channel); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return 0; +} + +static int ni_request_gpct_mite_channel(struct comedi_device *dev, + unsigned gpct_index, + enum comedi_io_direction direction) +{ + unsigned long flags; + struct mite_channel *mite_chan; + + BUG_ON(gpct_index >= NUM_GPCT); + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + BUG_ON(devpriv->counter_dev->counters[gpct_index].mite_chan); + mite_chan = + mite_request_channel(devpriv->mite, + devpriv->gpct_mite_ring[gpct_index]); + if (mite_chan == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for counter."); + return -EBUSY; + } + mite_chan->dir = direction; + ni_tio_set_mite_channel(&devpriv->counter_dev->counters[gpct_index], + mite_chan); + ni_set_gpct_dma_channel(dev, gpct_index, mite_chan->channel); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return 0; +} + +#endif /* PCIDMA */ + +static int ni_request_cdo_mite_channel(struct comedi_device *dev) +{ +#ifdef PCIDMA + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + BUG_ON(devpriv->cdo_mite_chan); + devpriv->cdo_mite_chan = + mite_request_channel(devpriv->mite, devpriv->cdo_mite_ring); + if (devpriv->cdo_mite_chan == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, + "failed to reserve mite dma channel for correlated digital outut."); + return -EBUSY; + } + devpriv->cdo_mite_chan->dir = COMEDI_OUTPUT; + ni_set_cdo_dma_channel(dev, devpriv->cdo_mite_chan->channel); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +#endif /* PCIDMA */ + return 0; +} + +static void ni_release_ai_mite_channel(struct comedi_device *dev) +{ +#ifdef PCIDMA + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ai_mite_chan) { + ni_set_ai_dma_channel(dev, -1); + mite_release_channel(devpriv->ai_mite_chan); + devpriv->ai_mite_chan = NULL; + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +#endif /* PCIDMA */ +} + +static void ni_release_ao_mite_channel(struct comedi_device *dev) +{ +#ifdef PCIDMA + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ao_mite_chan) { + ni_set_ao_dma_channel(dev, -1); + mite_release_channel(devpriv->ao_mite_chan); + devpriv->ao_mite_chan = NULL; + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +#endif /* PCIDMA */ +} + +void ni_release_gpct_mite_channel(struct comedi_device *dev, + unsigned gpct_index) +{ +#ifdef PCIDMA + unsigned long flags; + + BUG_ON(gpct_index >= NUM_GPCT); + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->counter_dev->counters[gpct_index].mite_chan) { + struct mite_channel *mite_chan = + devpriv->counter_dev->counters[gpct_index].mite_chan; + + ni_set_gpct_dma_channel(dev, gpct_index, -1); + ni_tio_set_mite_channel(&devpriv-> + counter_dev->counters[gpct_index], + NULL); + mite_release_channel(mite_chan); + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +#endif /* PCIDMA */ +} + +static void ni_release_cdo_mite_channel(struct comedi_device *dev) +{ +#ifdef PCIDMA + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->cdo_mite_chan) { + ni_set_cdo_dma_channel(dev, -1); + mite_release_channel(devpriv->cdo_mite_chan); + devpriv->cdo_mite_chan = NULL; + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +#endif /* PCIDMA */ +} + +/* e-series boards use the second irq signals to generate dma requests for their counters */ +#ifdef PCIDMA +static void ni_e_series_enable_second_irq(struct comedi_device *dev, + unsigned gpct_index, short enable) +{ + if (boardtype.reg_type & ni_reg_m_series_mask) + return; + switch (gpct_index) { + case 0: + if (enable) { + devpriv->stc_writew(dev, G0_Gate_Second_Irq_Enable, + Second_IRQ_A_Enable_Register); + } else { + devpriv->stc_writew(dev, 0, + Second_IRQ_A_Enable_Register); + } + break; + case 1: + if (enable) { + devpriv->stc_writew(dev, G1_Gate_Second_Irq_Enable, + Second_IRQ_B_Enable_Register); + } else { + devpriv->stc_writew(dev, 0, + Second_IRQ_B_Enable_Register); + } + break; + default: + BUG(); + break; + } +} +#endif /* PCIDMA */ + +static void ni_clear_ai_fifo(struct comedi_device *dev) +{ + if (boardtype.reg_type == ni_reg_6143) { + /* Flush the 6143 data FIFO */ + ni_writel(0x10, AIFIFO_Control_6143); /* Flush fifo */ + ni_writel(0x00, AIFIFO_Control_6143); /* Flush fifo */ + while (ni_readl(AIFIFO_Status_6143) & 0x10) ; /* Wait for complete */ + } else { + devpriv->stc_writew(dev, 1, ADC_FIFO_Clear); + if (boardtype.reg_type == ni_reg_625x) { + ni_writeb(0, M_Offset_Static_AI_Control(0)); + ni_writeb(1, M_Offset_Static_AI_Control(0)); +#if 0 + /* the NI example code does 3 convert pulses for 625x boards, + but that appears to be wrong in practice. */ + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); +#endif + } + } +} + +static void win_out2(struct comedi_device *dev, uint32_t data, int reg) +{ + devpriv->stc_writew(dev, data >> 16, reg); + devpriv->stc_writew(dev, data & 0xffff, reg + 1); +} + +static uint32_t win_in2(struct comedi_device *dev, int reg) +{ + uint32_t bits; + bits = devpriv->stc_readw(dev, reg) << 16; + bits |= devpriv->stc_readw(dev, reg + 1); + return bits; +} + +#define ao_win_out(data, addr) ni_ao_win_outw(dev, data, addr) +static inline void ni_ao_win_outw(struct comedi_device *dev, uint16_t data, + int addr) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(addr, AO_Window_Address_611x); + ni_writew(data, AO_Window_Data_611x); + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static inline void ni_ao_win_outl(struct comedi_device *dev, uint32_t data, + int addr) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(addr, AO_Window_Address_611x); + ni_writel(data, AO_Window_Data_611x); + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static inline unsigned short ni_ao_win_inw(struct comedi_device *dev, int addr) +{ + unsigned long flags; + unsigned short data; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(addr, AO_Window_Address_611x); + data = ni_readw(AO_Window_Data_611x); + spin_unlock_irqrestore(&devpriv->window_lock, flags); + return data; +} + +/* ni_set_bits( ) allows different parts of the ni_mio_common driver to +* share registers (such as Interrupt_A_Register) without interfering with +* each other. +* +* NOTE: the switch/case statements are optimized out for a constant argument +* so this is actually quite fast--- If you must wrap another function around this +* make it inline to avoid a large speed penalty. +* +* value should only be 1 or 0. +*/ +static inline void ni_set_bits(struct comedi_device *dev, int reg, + unsigned bits, unsigned value) +{ + unsigned bit_values; + + if (value) + bit_values = bits; + else + bit_values = 0; + ni_set_bitfield(dev, reg, bits, bit_values); +} + +static irqreturn_t ni_E_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + unsigned short a_status; + unsigned short b_status; + unsigned int ai_mite_status = 0; + unsigned int ao_mite_status = 0; + unsigned long flags; +#ifdef PCIDMA + struct mite_struct *mite = devpriv->mite; +#endif + + if (dev->attached == 0) + return IRQ_NONE; + smp_mb(); /* make sure dev->attached is checked before handler does anything else. */ + + /* lock to avoid race with comedi_poll */ + spin_lock_irqsave(&dev->spinlock, flags); + a_status = devpriv->stc_readw(dev, AI_Status_1_Register); + b_status = devpriv->stc_readw(dev, AO_Status_1_Register); +#ifdef PCIDMA + if (mite) { + unsigned long flags_too; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags_too); + if (devpriv->ai_mite_chan) { + ai_mite_status = mite_get_status(devpriv->ai_mite_chan); + if (ai_mite_status & CHSR_LINKC) + writel(CHOR_CLRLC, + devpriv->mite->mite_io_addr + + MITE_CHOR(devpriv-> + ai_mite_chan->channel)); + } + if (devpriv->ao_mite_chan) { + ao_mite_status = mite_get_status(devpriv->ao_mite_chan); + if (ao_mite_status & CHSR_LINKC) + writel(CHOR_CLRLC, + mite->mite_io_addr + + MITE_CHOR(devpriv-> + ao_mite_chan->channel)); + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags_too); + } +#endif + ack_a_interrupt(dev, a_status); + ack_b_interrupt(dev, b_status); + if ((a_status & Interrupt_A_St) || (ai_mite_status & CHSR_INT)) + handle_a_interrupt(dev, a_status, ai_mite_status); + if ((b_status & Interrupt_B_St) || (ao_mite_status & CHSR_INT)) + handle_b_interrupt(dev, b_status, ao_mite_status); + handle_gpct_interrupt(dev, 0); + handle_gpct_interrupt(dev, 1); + handle_cdio_interrupt(dev); + + spin_unlock_irqrestore(&dev->spinlock, flags); + return IRQ_HANDLED; +} + +#ifdef PCIDMA +static void ni_sync_ai_dma(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AI_SUBDEV; + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ai_mite_chan) + mite_sync_input_dma(devpriv->ai_mite_chan, s->async); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +} + +static void mite_handle_b_linkc(struct mite_struct *mite, + struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AO_SUBDEV; + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ao_mite_chan) { + mite_sync_output_dma(devpriv->ao_mite_chan, s->async); + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +} + +static int ni_ao_wait_for_dma_load(struct comedi_device *dev) +{ + static const int timeout = 10000; + int i; + for (i = 0; i < timeout; i++) { + unsigned short b_status; + + b_status = devpriv->stc_readw(dev, AO_Status_1_Register); + if (b_status & AO_FIFO_Half_Full_St) + break; + /* if we poll too often, the pci bus activity seems + to slow the dma transfer down */ + udelay(10); + } + if (i == timeout) { + comedi_error(dev, "timed out waiting for dma load"); + return -EPIPE; + } + return 0; +} + +#endif /* PCIDMA */ +static void ni_handle_eos(struct comedi_device *dev, struct comedi_subdevice *s) +{ + if (devpriv->aimode == AIMODE_SCAN) { +#ifdef PCIDMA + static const int timeout = 10; + int i; + + for (i = 0; i < timeout; i++) { + ni_sync_ai_dma(dev); + if ((s->async->events & COMEDI_CB_EOS)) + break; + udelay(1); + } +#else + ni_handle_fifo_dregs(dev); + s->async->events |= COMEDI_CB_EOS; +#endif + } + /* handle special case of single scan using AI_End_On_End_Of_Scan */ + if ((devpriv->ai_cmd2 & AI_End_On_End_Of_Scan)) { + shutdown_ai_command(dev); + } +} + +static void shutdown_ai_command(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AI_SUBDEV; + +#ifdef PCIDMA + ni_ai_drain_dma(dev); +#endif + ni_handle_fifo_dregs(dev); + get_last_sample_611x(dev); + get_last_sample_6143(dev); + + s->async->events |= COMEDI_CB_EOA; +} + +static void ni_event(struct comedi_device *dev, struct comedi_subdevice *s) +{ + if (s-> + async->events & (COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW | + COMEDI_CB_EOA)) { + switch (s - dev->subdevices) { + case NI_AI_SUBDEV: + ni_ai_reset(dev, s); + break; + case NI_AO_SUBDEV: + ni_ao_reset(dev, s); + break; + case NI_GPCT0_SUBDEV: + case NI_GPCT1_SUBDEV: + ni_gpct_cancel(dev, s); + break; + case NI_DIO_SUBDEV: + ni_cdio_cancel(dev, s); + break; + default: + break; + } + } + comedi_event(dev, s); +} + +static void handle_gpct_interrupt(struct comedi_device *dev, + unsigned short counter_index) +{ +#ifdef PCIDMA + struct comedi_subdevice *s = + dev->subdevices + NI_GPCT_SUBDEV(counter_index); + + ni_tio_handle_interrupt(&devpriv->counter_dev->counters[counter_index], + s); + if (s->async->events) + ni_event(dev, s); +#endif +} + +static void ack_a_interrupt(struct comedi_device *dev, unsigned short a_status) +{ + unsigned short ack = 0; + + if (a_status & AI_SC_TC_St) { + ack |= AI_SC_TC_Interrupt_Ack; + } + if (a_status & AI_START1_St) { + ack |= AI_START1_Interrupt_Ack; + } + if (a_status & AI_START_St) { + ack |= AI_START_Interrupt_Ack; + } + if (a_status & AI_STOP_St) { + /* not sure why we used to ack the START here also, instead of doing it independently. Frank Hess 2007-07-06 */ + ack |= AI_STOP_Interrupt_Ack /*| AI_START_Interrupt_Ack */ ; + } + if (ack) + devpriv->stc_writew(dev, ack, Interrupt_A_Ack_Register); +} + +static void handle_a_interrupt(struct comedi_device *dev, unsigned short status, + unsigned ai_mite_status) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AI_SUBDEV; + + /* 67xx boards don't have ai subdevice, but their gpct0 might generate an a interrupt */ + if (s->type == COMEDI_SUBD_UNUSED) + return; + +#ifdef DEBUG_INTERRUPT + printk + ("ni_mio_common: interrupt: a_status=%04x ai_mite_status=%08x\n", + status, ai_mite_status); + ni_mio_print_status_a(status); +#endif +#ifdef PCIDMA + if (ai_mite_status & CHSR_LINKC) { + ni_sync_ai_dma(dev); + } + + if (ai_mite_status & ~(CHSR_INT | CHSR_LINKC | CHSR_DONE | CHSR_MRDY | + CHSR_DRDY | CHSR_DRQ1 | CHSR_DRQ0 | CHSR_ERROR | + CHSR_SABORT | CHSR_XFERR | CHSR_LxERR_mask)) { + printk + ("unknown mite interrupt, ack! (ai_mite_status=%08x)\n", + ai_mite_status); + /* mite_print_chsr(ai_mite_status); */ + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + /* disable_irq(dev->irq); */ + } +#endif + + /* test for all uncommon interrupt events at the same time */ + if (status & (AI_Overrun_St | AI_Overflow_St | AI_SC_TC_Error_St | + AI_SC_TC_St | AI_START1_St)) { + if (status == 0xffff) { + printk + ("ni_mio_common: a_status=0xffff. Card removed?\n"); + /* we probably aren't even running a command now, + * so it's a good idea to be careful. */ + if (comedi_get_subdevice_runflags(s) & SRF_RUNNING) { + s->async->events |= + COMEDI_CB_ERROR | COMEDI_CB_EOA; + ni_event(dev, s); + } + return; + } + if (status & (AI_Overrun_St | AI_Overflow_St | + AI_SC_TC_Error_St)) { + printk("ni_mio_common: ai error a_status=%04x\n", + status); + ni_mio_print_status_a(status); + + shutdown_ai_command(dev); + + s->async->events |= COMEDI_CB_ERROR; + if (status & (AI_Overrun_St | AI_Overflow_St)) + s->async->events |= COMEDI_CB_OVERFLOW; + + ni_event(dev, s); + + return; + } + if (status & AI_SC_TC_St) { +#ifdef DEBUG_INTERRUPT + printk("ni_mio_common: SC_TC interrupt\n"); +#endif + if (!devpriv->ai_continuous) { + shutdown_ai_command(dev); + } + } + } +#ifndef PCIDMA + if (status & AI_FIFO_Half_Full_St) { + int i; + static const int timeout = 10; + /* pcmcia cards (at least 6036) seem to stop producing interrupts if we + *fail to get the fifo less than half full, so loop to be sure.*/ + for (i = 0; i < timeout; ++i) { + ni_handle_fifo_half_full(dev); + if ((devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Half_Full_St) == 0) + break; + } + } +#endif /* !PCIDMA */ + + if ((status & AI_STOP_St)) { + ni_handle_eos(dev, s); + } + + ni_event(dev, s); + +#ifdef DEBUG_INTERRUPT + status = devpriv->stc_readw(dev, AI_Status_1_Register); + if (status & Interrupt_A_St) { + printk + ("handle_a_interrupt: didn't clear interrupt? status=0x%x\n", + status); + } +#endif +} + +static void ack_b_interrupt(struct comedi_device *dev, unsigned short b_status) +{ + unsigned short ack = 0; + if (b_status & AO_BC_TC_St) { + ack |= AO_BC_TC_Interrupt_Ack; + } + if (b_status & AO_Overrun_St) { + ack |= AO_Error_Interrupt_Ack; + } + if (b_status & AO_START_St) { + ack |= AO_START_Interrupt_Ack; + } + if (b_status & AO_START1_St) { + ack |= AO_START1_Interrupt_Ack; + } + if (b_status & AO_UC_TC_St) { + ack |= AO_UC_TC_Interrupt_Ack; + } + if (b_status & AO_UI2_TC_St) { + ack |= AO_UI2_TC_Interrupt_Ack; + } + if (b_status & AO_UPDATE_St) { + ack |= AO_UPDATE_Interrupt_Ack; + } + if (ack) + devpriv->stc_writew(dev, ack, Interrupt_B_Ack_Register); +} + +static void handle_b_interrupt(struct comedi_device *dev, + unsigned short b_status, unsigned ao_mite_status) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AO_SUBDEV; + /* unsigned short ack=0; */ +#ifdef DEBUG_INTERRUPT + printk("ni_mio_common: interrupt: b_status=%04x m1_status=%08x\n", + b_status, ao_mite_status); + ni_mio_print_status_b(b_status); +#endif + +#ifdef PCIDMA + /* Currently, mite.c requires us to handle LINKC */ + if (ao_mite_status & CHSR_LINKC) { + mite_handle_b_linkc(devpriv->mite, dev); + } + + if (ao_mite_status & ~(CHSR_INT | CHSR_LINKC | CHSR_DONE | CHSR_MRDY | + CHSR_DRDY | CHSR_DRQ1 | CHSR_DRQ0 | CHSR_ERROR | + CHSR_SABORT | CHSR_XFERR | CHSR_LxERR_mask)) { + printk + ("unknown mite interrupt, ack! (ao_mite_status=%08x)\n", + ao_mite_status); + /* mite_print_chsr(ao_mite_status); */ + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + } +#endif + + if (b_status == 0xffff) + return; + if (b_status & AO_Overrun_St) { + printk + ("ni_mio_common: AO FIFO underrun status=0x%04x status2=0x%04x\n", + b_status, devpriv->stc_readw(dev, AO_Status_2_Register)); + s->async->events |= COMEDI_CB_OVERFLOW; + } + + if (b_status & AO_BC_TC_St) { + MDPRINTK + ("ni_mio_common: AO BC_TC status=0x%04x status2=0x%04x\n", + b_status, devpriv->stc_readw(dev, AO_Status_2_Register)); + s->async->events |= COMEDI_CB_EOA; + } +#ifndef PCIDMA + if (b_status & AO_FIFO_Request_St) { + int ret; + + ret = ni_ao_fifo_half_empty(dev, s); + if (!ret) { + printk("ni_mio_common: AO buffer underrun\n"); + ni_set_bits(dev, Interrupt_B_Enable_Register, + AO_FIFO_Interrupt_Enable | + AO_Error_Interrupt_Enable, 0); + s->async->events |= COMEDI_CB_OVERFLOW; + } + } +#endif + + ni_event(dev, s); +} + +#ifdef DEBUG_STATUS_A +static const char *const status_a_strings[] = { + "passthru0", "fifo", "G0_gate", "G0_TC", + "stop", "start", "sc_tc", "start1", + "start2", "sc_tc_error", "overflow", "overrun", + "fifo_empty", "fifo_half_full", "fifo_full", "interrupt_a" +}; + +static void ni_mio_print_status_a(int status) +{ + int i; + + printk("A status:"); + for (i = 15; i >= 0; i--) { + if (status & (1 << i)) { + printk(" %s", status_a_strings[i]); + } + } + printk("\n"); +} +#endif + +#ifdef DEBUG_STATUS_B +static const char *const status_b_strings[] = { + "passthru1", "fifo", "G1_gate", "G1_TC", + "UI2_TC", "UPDATE", "UC_TC", "BC_TC", + "start1", "overrun", "start", "bc_tc_error", + "fifo_empty", "fifo_half_full", "fifo_full", "interrupt_b" +}; + +static void ni_mio_print_status_b(int status) +{ + int i; + + printk("B status:"); + for (i = 15; i >= 0; i--) { + if (status & (1 << i)) { + printk(" %s", status_b_strings[i]); + } + } + printk("\n"); +} +#endif + +#ifndef PCIDMA + +static void ni_ao_fifo_load(struct comedi_device *dev, + struct comedi_subdevice *s, int n) +{ + struct comedi_async *async = s->async; + struct comedi_cmd *cmd = &async->cmd; + int chan; + int i; + short d; + u32 packed_data; + int range; + int err = 1; + + chan = async->cur_chan; + for (i = 0; i < n; i++) { + err &= comedi_buf_get(async, &d); + if (err == 0) + break; + + range = CR_RANGE(cmd->chanlist[chan]); + + if (boardtype.reg_type & ni_reg_6xxx_mask) { + packed_data = d & 0xffff; + /* 6711 only has 16 bit wide ao fifo */ + if (boardtype.reg_type != ni_reg_6711) { + err &= comedi_buf_get(async, &d); + if (err == 0) + break; + chan++; + i++; + packed_data |= (d << 16) & 0xffff0000; + } + ni_writel(packed_data, DAC_FIFO_Data_611x); + } else { + ni_writew(d, DAC_FIFO_Data); + } + chan++; + chan %= cmd->chanlist_len; + } + async->cur_chan = chan; + if (err == 0) { + async->events |= COMEDI_CB_OVERFLOW; + } +} + +/* + * There's a small problem if the FIFO gets really low and we + * don't have the data to fill it. Basically, if after we fill + * the FIFO with all the data available, the FIFO is _still_ + * less than half full, we never clear the interrupt. If the + * IRQ is in edge mode, we never get another interrupt, because + * this one wasn't cleared. If in level mode, we get flooded + * with interrupts that we can't fulfill, because nothing ever + * gets put into the buffer. + * + * This kind of situation is recoverable, but it is easier to + * just pretend we had a FIFO underrun, since there is a good + * chance it will happen anyway. This is _not_ the case for + * RT code, as RT code might purposely be running close to the + * metal. Needs to be fixed eventually. + */ +static int ni_ao_fifo_half_empty(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int n; + + n = comedi_buf_read_n_available(s->async); + if (n == 0) { + s->async->events |= COMEDI_CB_OVERFLOW; + return 0; + } + + n /= sizeof(short); + if (n > boardtype.ao_fifo_depth / 2) + n = boardtype.ao_fifo_depth / 2; + + ni_ao_fifo_load(dev, s, n); + + s->async->events |= COMEDI_CB_BLOCK; + + return 1; +} + +static int ni_ao_prep_fifo(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int n; + + /* reset fifo */ + devpriv->stc_writew(dev, 1, DAC_FIFO_Clear); + if (boardtype.reg_type & ni_reg_6xxx_mask) + ni_ao_win_outl(dev, 0x6, AO_FIFO_Offset_Load_611x); + + /* load some data */ + n = comedi_buf_read_n_available(s->async); + if (n == 0) + return 0; + + n /= sizeof(short); + if (n > boardtype.ao_fifo_depth) + n = boardtype.ao_fifo_depth; + + ni_ao_fifo_load(dev, s, n); + + return n; +} + +static void ni_ai_fifo_read(struct comedi_device *dev, + struct comedi_subdevice *s, int n) +{ + struct comedi_async *async = s->async; + int i; + + if (boardtype.reg_type == ni_reg_611x) { + short data[2]; + u32 dl; + + for (i = 0; i < n / 2; i++) { + dl = ni_readl(ADC_FIFO_Data_611x); + /* This may get the hi/lo data in the wrong order */ + data[0] = (dl >> 16) & 0xffff; + data[1] = dl & 0xffff; + cfc_write_array_to_buffer(s, data, sizeof(data)); + } + /* Check if there's a single sample stuck in the FIFO */ + if (n % 2) { + dl = ni_readl(ADC_FIFO_Data_611x); + data[0] = dl & 0xffff; + cfc_write_to_buffer(s, data[0]); + } + } else if (boardtype.reg_type == ni_reg_6143) { + short data[2]; + u32 dl; + + /* This just reads the FIFO assuming the data is present, no checks on the FIFO status are performed */ + for (i = 0; i < n / 2; i++) { + dl = ni_readl(AIFIFO_Data_6143); + + data[0] = (dl >> 16) & 0xffff; + data[1] = dl & 0xffff; + cfc_write_array_to_buffer(s, data, sizeof(data)); + } + if (n % 2) { + /* Assume there is a single sample stuck in the FIFO */ + ni_writel(0x01, AIFIFO_Control_6143); /* Get stranded sample into FIFO */ + dl = ni_readl(AIFIFO_Data_6143); + data[0] = (dl >> 16) & 0xffff; + cfc_write_to_buffer(s, data[0]); + } + } else { + if (n > sizeof(devpriv->ai_fifo_buffer) / + sizeof(devpriv->ai_fifo_buffer[0])) { + comedi_error(dev, "bug! ai_fifo_buffer too small"); + async->events |= COMEDI_CB_ERROR; + return; + } + for (i = 0; i < n; i++) { + devpriv->ai_fifo_buffer[i] = + ni_readw(ADC_FIFO_Data_Register); + } + cfc_write_array_to_buffer(s, devpriv->ai_fifo_buffer, + n * + sizeof(devpriv->ai_fifo_buffer[0])); + } +} + +static void ni_handle_fifo_half_full(struct comedi_device *dev) +{ + int n; + struct comedi_subdevice *s = dev->subdevices + NI_AI_SUBDEV; + + n = boardtype.ai_fifo_depth / 2; + + ni_ai_fifo_read(dev, s, n); +} +#endif + +#ifdef PCIDMA +static int ni_ai_drain_dma(struct comedi_device *dev) +{ + int i; + static const int timeout = 10000; + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ai_mite_chan) { + for (i = 0; i < timeout; i++) { + if ((devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St) + && mite_bytes_in_transit(devpriv->ai_mite_chan) == + 0) + break; + udelay(5); + } + if (i == timeout) { + printk("ni_mio_common: wait for dma drain timed out\n"); + printk + ("mite_bytes_in_transit=%i, AI_Status1_Register=0x%x\n", + mite_bytes_in_transit(devpriv->ai_mite_chan), + devpriv->stc_readw(dev, AI_Status_1_Register)); + retval = -1; + } + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + ni_sync_ai_dma(dev); + + return retval; +} +#endif +/* + Empties the AI fifo +*/ +static void ni_handle_fifo_dregs(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AI_SUBDEV; + short data[2]; + u32 dl; + short fifo_empty; + int i; + + if (boardtype.reg_type == ni_reg_611x) { + while ((devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St) == 0) { + dl = ni_readl(ADC_FIFO_Data_611x); + + /* This may get the hi/lo data in the wrong order */ + data[0] = (dl >> 16); + data[1] = (dl & 0xffff); + cfc_write_array_to_buffer(s, data, sizeof(data)); + } + } else if (boardtype.reg_type == ni_reg_6143) { + i = 0; + while (ni_readl(AIFIFO_Status_6143) & 0x04) { + dl = ni_readl(AIFIFO_Data_6143); + + /* This may get the hi/lo data in the wrong order */ + data[0] = (dl >> 16); + data[1] = (dl & 0xffff); + cfc_write_array_to_buffer(s, data, sizeof(data)); + i += 2; + } + /* Check if stranded sample is present */ + if (ni_readl(AIFIFO_Status_6143) & 0x01) { + ni_writel(0x01, AIFIFO_Control_6143); /* Get stranded sample into FIFO */ + dl = ni_readl(AIFIFO_Data_6143); + data[0] = (dl >> 16) & 0xffff; + cfc_write_to_buffer(s, data[0]); + } + + } else { + fifo_empty = + devpriv->stc_readw(dev, + AI_Status_1_Register) & AI_FIFO_Empty_St; + while (fifo_empty == 0) { + for (i = 0; + i < + sizeof(devpriv->ai_fifo_buffer) / + sizeof(devpriv->ai_fifo_buffer[0]); i++) { + fifo_empty = + devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St; + if (fifo_empty) + break; + devpriv->ai_fifo_buffer[i] = + ni_readw(ADC_FIFO_Data_Register); + } + cfc_write_array_to_buffer(s, devpriv->ai_fifo_buffer, + i * + sizeof(devpriv-> + ai_fifo_buffer[0])); + } + } +} + +static void get_last_sample_611x(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AI_SUBDEV; + short data; + u32 dl; + + if (boardtype.reg_type != ni_reg_611x) + return; + + /* Check if there's a single sample stuck in the FIFO */ + if (ni_readb(XXX_Status) & 0x80) { + dl = ni_readl(ADC_FIFO_Data_611x); + data = (dl & 0xffff); + cfc_write_to_buffer(s, data); + } +} + +static void get_last_sample_6143(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AI_SUBDEV; + short data; + u32 dl; + + if (boardtype.reg_type != ni_reg_6143) + return; + + /* Check if there's a single sample stuck in the FIFO */ + if (ni_readl(AIFIFO_Status_6143) & 0x01) { + ni_writel(0x01, AIFIFO_Control_6143); /* Get stranded sample into FIFO */ + dl = ni_readl(AIFIFO_Data_6143); + + /* This may get the hi/lo data in the wrong order */ + data = (dl >> 16) & 0xffff; + cfc_write_to_buffer(s, data); + } +} + +static void ni_ai_munge(struct comedi_device *dev, struct comedi_subdevice *s, + void *data, unsigned int num_bytes, + unsigned int chan_index) +{ + struct comedi_async *async = s->async; + unsigned int i; + unsigned int length = num_bytes / bytes_per_sample(s); + short *array = data; + unsigned int *larray = data; + for (i = 0; i < length; i++) { +#ifdef PCIDMA + if (s->subdev_flags & SDF_LSAMPL) + larray[i] = le32_to_cpu(larray[i]); + else + array[i] = le16_to_cpu(array[i]); +#endif + if (s->subdev_flags & SDF_LSAMPL) + larray[i] += devpriv->ai_offset[chan_index]; + else + array[i] += devpriv->ai_offset[chan_index]; + chan_index++; + chan_index %= async->cmd.chanlist_len; + } +} + +#ifdef PCIDMA + +static int ni_ai_setup_MITE_dma(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AI_SUBDEV; + int retval; + unsigned long flags; + + retval = ni_request_ai_mite_channel(dev); + if (retval) + return retval; +/* printk("comedi_debug: using mite channel %i for ai.\n", devpriv->ai_mite_chan->channel); */ + + /* write alloc the entire buffer */ + comedi_buf_write_alloc(s->async, s->async->prealloc_bufsz); + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ai_mite_chan == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return -EIO; + } + + switch (boardtype.reg_type) { + case ni_reg_611x: + case ni_reg_6143: + mite_prep_dma(devpriv->ai_mite_chan, 32, 16); + break; + case ni_reg_628x: + mite_prep_dma(devpriv->ai_mite_chan, 32, 32); + break; + default: + mite_prep_dma(devpriv->ai_mite_chan, 16, 16); + break; + } + /*start the MITE */ + mite_dma_arm(devpriv->ai_mite_chan); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + return 0; +} + +static int ni_ao_setup_MITE_dma(struct comedi_device *dev) +{ + struct comedi_subdevice *s = dev->subdevices + NI_AO_SUBDEV; + int retval; + unsigned long flags; + + retval = ni_request_ao_mite_channel(dev); + if (retval) + return retval; + + /* read alloc the entire buffer */ + comedi_buf_read_alloc(s->async, s->async->prealloc_bufsz); + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->ao_mite_chan) { + if (boardtype.reg_type & (ni_reg_611x | ni_reg_6713)) { + mite_prep_dma(devpriv->ao_mite_chan, 32, 32); + } else { + /* doing 32 instead of 16 bit wide transfers from memory + makes the mite do 32 bit pci transfers, doubling pci bandwidth. */ + mite_prep_dma(devpriv->ao_mite_chan, 16, 32); + } + mite_dma_arm(devpriv->ao_mite_chan); + } else + retval = -EIO; + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + return retval; +} + +#endif /* PCIDMA */ + +/* + used for both cancel ioctl and board initialization + + this is pretty harsh for a cancel, but it works... + */ + +static int ni_ai_reset(struct comedi_device *dev, struct comedi_subdevice *s) +{ + ni_release_ai_mite_channel(dev); + /* ai configuration */ + devpriv->stc_writew(dev, AI_Configuration_Start | AI_Reset, + Joint_Reset_Register); + + ni_set_bits(dev, Interrupt_A_Enable_Register, + AI_SC_TC_Interrupt_Enable | AI_START1_Interrupt_Enable | + AI_START2_Interrupt_Enable | AI_START_Interrupt_Enable | + AI_STOP_Interrupt_Enable | AI_Error_Interrupt_Enable | + AI_FIFO_Interrupt_Enable, 0); + + ni_clear_ai_fifo(dev); + + if (boardtype.reg_type != ni_reg_6143) + ni_writeb(0, Misc_Command); + + devpriv->stc_writew(dev, AI_Disarm, AI_Command_1_Register); /* reset pulses */ + devpriv->stc_writew(dev, + AI_Start_Stop | AI_Mode_1_Reserved + /*| AI_Trigger_Once */ , + AI_Mode_1_Register); + devpriv->stc_writew(dev, 0x0000, AI_Mode_2_Register); + /* generate FIFO interrupts on non-empty */ + devpriv->stc_writew(dev, (0 << 6) | 0x0000, AI_Mode_3_Register); + if (boardtype.reg_type == ni_reg_611x) { + devpriv->stc_writew(dev, AI_SHIFTIN_Pulse_Width | + AI_SOC_Polarity | + AI_LOCALMUX_CLK_Pulse_Width, + AI_Personal_Register); + devpriv->stc_writew(dev, + AI_SCAN_IN_PROG_Output_Select(3) | + AI_EXTMUX_CLK_Output_Select(0) | + AI_LOCALMUX_CLK_Output_Select(2) | + AI_SC_TC_Output_Select(3) | + AI_CONVERT_Output_Select + (AI_CONVERT_Output_Enable_High), + AI_Output_Control_Register); + } else if (boardtype.reg_type == ni_reg_6143) { + devpriv->stc_writew(dev, AI_SHIFTIN_Pulse_Width | + AI_SOC_Polarity | + AI_LOCALMUX_CLK_Pulse_Width, + AI_Personal_Register); + devpriv->stc_writew(dev, + AI_SCAN_IN_PROG_Output_Select(3) | + AI_EXTMUX_CLK_Output_Select(0) | + AI_LOCALMUX_CLK_Output_Select(2) | + AI_SC_TC_Output_Select(3) | + AI_CONVERT_Output_Select + (AI_CONVERT_Output_Enable_Low), + AI_Output_Control_Register); + } else { + unsigned ai_output_control_bits; + devpriv->stc_writew(dev, AI_SHIFTIN_Pulse_Width | + AI_SOC_Polarity | + AI_CONVERT_Pulse_Width | + AI_LOCALMUX_CLK_Pulse_Width, + AI_Personal_Register); + ai_output_control_bits = + AI_SCAN_IN_PROG_Output_Select(3) | + AI_EXTMUX_CLK_Output_Select(0) | + AI_LOCALMUX_CLK_Output_Select(2) | + AI_SC_TC_Output_Select(3); + if (boardtype.reg_type == ni_reg_622x) + ai_output_control_bits |= + AI_CONVERT_Output_Select + (AI_CONVERT_Output_Enable_High); + else + ai_output_control_bits |= + AI_CONVERT_Output_Select + (AI_CONVERT_Output_Enable_Low); + devpriv->stc_writew(dev, ai_output_control_bits, + AI_Output_Control_Register); + } + /* the following registers should not be changed, because there + * are no backup registers in devpriv. If you want to change + * any of these, add a backup register and other appropriate code: + * AI_Mode_1_Register + * AI_Mode_3_Register + * AI_Personal_Register + * AI_Output_Control_Register + */ + devpriv->stc_writew(dev, AI_SC_TC_Error_Confirm | AI_START_Interrupt_Ack | AI_START2_Interrupt_Ack | AI_START1_Interrupt_Ack | AI_SC_TC_Interrupt_Ack | AI_Error_Interrupt_Ack | AI_STOP_Interrupt_Ack, Interrupt_A_Ack_Register); /* clear interrupts */ + + devpriv->stc_writew(dev, AI_Configuration_End, Joint_Reset_Register); + + return 0; +} + +static int ni_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags = 0; + int count; + + /* lock to avoid race with interrupt handler */ + if (in_interrupt() == 0) + spin_lock_irqsave(&dev->spinlock, flags); +#ifndef PCIDMA + ni_handle_fifo_dregs(dev); +#else + ni_sync_ai_dma(dev); +#endif + count = s->async->buf_write_count - s->async->buf_read_count; + if (in_interrupt() == 0) + spin_unlock_irqrestore(&dev->spinlock, flags); + + return count; +} + +static int ni_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i, n; + const unsigned int mask = (1 << boardtype.adbits) - 1; + unsigned signbits; + unsigned short d; + unsigned long dl; + + ni_load_channelgain_list(dev, 1, &insn->chanspec); + + ni_clear_ai_fifo(dev); + + signbits = devpriv->ai_offset[0]; + if (boardtype.reg_type == ni_reg_611x) { + for (n = 0; n < num_adc_stages_611x; n++) { + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + udelay(1); + } + for (n = 0; n < insn->n; n++) { + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + /* The 611x has screwy 32-bit FIFOs. */ + d = 0; + for (i = 0; i < NI_TIMEOUT; i++) { + if (ni_readb(XXX_Status) & 0x80) { + d = (ni_readl(ADC_FIFO_Data_611x) >> 16) + & 0xffff; + break; + } + if (!(devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St)) { + d = ni_readl(ADC_FIFO_Data_611x) & + 0xffff; + break; + } + } + if (i == NI_TIMEOUT) { + printk + ("ni_mio_common: timeout in 611x ni_ai_insn_read\n"); + return -ETIME; + } + d += signbits; + data[n] = d; + } + } else if (boardtype.reg_type == ni_reg_6143) { + for (n = 0; n < insn->n; n++) { + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + + /* The 6143 has 32-bit FIFOs. You need to strobe a bit to move a single 16bit stranded sample into the FIFO */ + dl = 0; + for (i = 0; i < NI_TIMEOUT; i++) { + if (ni_readl(AIFIFO_Status_6143) & 0x01) { + ni_writel(0x01, AIFIFO_Control_6143); /* Get stranded sample into FIFO */ + dl = ni_readl(AIFIFO_Data_6143); + break; + } + } + if (i == NI_TIMEOUT) { + printk + ("ni_mio_common: timeout in 6143 ni_ai_insn_read\n"); + return -ETIME; + } + data[n] = (((dl >> 16) & 0xFFFF) + signbits) & 0xFFFF; + } + } else { + for (n = 0; n < insn->n; n++) { + devpriv->stc_writew(dev, AI_CONVERT_Pulse, + AI_Command_1_Register); + for (i = 0; i < NI_TIMEOUT; i++) { + if (!(devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St)) + break; + } + if (i == NI_TIMEOUT) { + printk + ("ni_mio_common: timeout in ni_ai_insn_read\n"); + return -ETIME; + } + if (boardtype.reg_type & ni_reg_m_series_mask) { + data[n] = + ni_readl(M_Offset_AI_FIFO_Data) & mask; + } else { + d = ni_readw(ADC_FIFO_Data_Register); + d += signbits; /* subtle: needs to be short addition */ + data[n] = d; + } + } + } + return insn->n; +} + +void ni_prime_channelgain_list(struct comedi_device *dev) +{ + int i; + devpriv->stc_writew(dev, AI_CONVERT_Pulse, AI_Command_1_Register); + for (i = 0; i < NI_TIMEOUT; ++i) { + if (!(devpriv->stc_readw(dev, + AI_Status_1_Register) & + AI_FIFO_Empty_St)) { + devpriv->stc_writew(dev, 1, ADC_FIFO_Clear); + return; + } + udelay(1); + } + printk("ni_mio_common: timeout loading channel/gain list\n"); +} + +static void ni_m_series_load_channelgain_list(struct comedi_device *dev, + unsigned int n_chan, + unsigned int *list) +{ + unsigned int chan, range, aref; + unsigned int i; + unsigned offset; + unsigned int dither; + unsigned range_code; + + devpriv->stc_writew(dev, 1, Configuration_Memory_Clear); + +/* offset = 1 << (boardtype.adbits - 1); */ + if ((list[0] & CR_ALT_SOURCE)) { + unsigned bypass_bits; + chan = CR_CHAN(list[0]); + range = CR_RANGE(list[0]); + range_code = ni_gainlkup[boardtype.gainlkup][range]; + dither = ((list[0] & CR_ALT_FILTER) != 0); + bypass_bits = MSeries_AI_Bypass_Config_FIFO_Bit; + bypass_bits |= chan; + bypass_bits |= + (devpriv->ai_calib_source) & + (MSeries_AI_Bypass_Cal_Sel_Pos_Mask | + MSeries_AI_Bypass_Cal_Sel_Neg_Mask | + MSeries_AI_Bypass_Mode_Mux_Mask | + MSeries_AO_Bypass_AO_Cal_Sel_Mask); + bypass_bits |= MSeries_AI_Bypass_Gain_Bits(range_code); + if (dither) + bypass_bits |= MSeries_AI_Bypass_Dither_Bit; + /* don't use 2's complement encoding */ + bypass_bits |= MSeries_AI_Bypass_Polarity_Bit; + ni_writel(bypass_bits, M_Offset_AI_Config_FIFO_Bypass); + } else { + ni_writel(0, M_Offset_AI_Config_FIFO_Bypass); + } + offset = 0; + for (i = 0; i < n_chan; i++) { + unsigned config_bits = 0; + chan = CR_CHAN(list[i]); + aref = CR_AREF(list[i]); + range = CR_RANGE(list[i]); + dither = ((list[i] & CR_ALT_FILTER) != 0); + + range_code = ni_gainlkup[boardtype.gainlkup][range]; + devpriv->ai_offset[i] = offset; + switch (aref) { + case AREF_DIFF: + config_bits |= + MSeries_AI_Config_Channel_Type_Differential_Bits; + break; + case AREF_COMMON: + config_bits |= + MSeries_AI_Config_Channel_Type_Common_Ref_Bits; + break; + case AREF_GROUND: + config_bits |= + MSeries_AI_Config_Channel_Type_Ground_Ref_Bits; + break; + case AREF_OTHER: + break; + } + config_bits |= MSeries_AI_Config_Channel_Bits(chan); + config_bits |= + MSeries_AI_Config_Bank_Bits(boardtype.reg_type, chan); + config_bits |= MSeries_AI_Config_Gain_Bits(range_code); + if (i == n_chan - 1) + config_bits |= MSeries_AI_Config_Last_Channel_Bit; + if (dither) + config_bits |= MSeries_AI_Config_Dither_Bit; + /* don't use 2's complement encoding */ + config_bits |= MSeries_AI_Config_Polarity_Bit; + ni_writew(config_bits, M_Offset_AI_Config_FIFO_Data); + } + ni_prime_channelgain_list(dev); +} + +/* + * Notes on the 6110 and 6111: + * These boards a slightly different than the rest of the series, since + * they have multiple A/D converters. + * From the driver side, the configuration memory is a + * little different. + * Configuration Memory Low: + * bits 15-9: same + * bit 8: unipolar/bipolar (should be 0 for bipolar) + * bits 0-3: gain. This is 4 bits instead of 3 for the other boards + * 1001 gain=0.1 (+/- 50) + * 1010 0.2 + * 1011 0.1 + * 0001 1 + * 0010 2 + * 0011 5 + * 0100 10 + * 0101 20 + * 0110 50 + * Configuration Memory High: + * bits 12-14: Channel Type + * 001 for differential + * 000 for calibration + * bit 11: coupling (this is not currently handled) + * 1 AC coupling + * 0 DC coupling + * bits 0-2: channel + * valid channels are 0-3 + */ +static void ni_load_channelgain_list(struct comedi_device *dev, + unsigned int n_chan, unsigned int *list) +{ + unsigned int chan, range, aref; + unsigned int i; + unsigned int hi, lo; + unsigned offset; + unsigned int dither; + + if (boardtype.reg_type & ni_reg_m_series_mask) { + ni_m_series_load_channelgain_list(dev, n_chan, list); + return; + } + if (n_chan == 1 && (boardtype.reg_type != ni_reg_611x) + && (boardtype.reg_type != ni_reg_6143)) { + if (devpriv->changain_state + && devpriv->changain_spec == list[0]) { + /* ready to go. */ + return; + } + devpriv->changain_state = 1; + devpriv->changain_spec = list[0]; + } else { + devpriv->changain_state = 0; + } + + devpriv->stc_writew(dev, 1, Configuration_Memory_Clear); + + /* Set up Calibration mode if required */ + if (boardtype.reg_type == ni_reg_6143) { + if ((list[0] & CR_ALT_SOURCE) + && !devpriv->ai_calib_source_enabled) { + /* Strobe Relay enable bit */ + ni_writew(devpriv->ai_calib_source | + Calibration_Channel_6143_RelayOn, + Calibration_Channel_6143); + ni_writew(devpriv->ai_calib_source, + Calibration_Channel_6143); + devpriv->ai_calib_source_enabled = 1; + msleep_interruptible(100); /* Allow relays to change */ + } else if (!(list[0] & CR_ALT_SOURCE) + && devpriv->ai_calib_source_enabled) { + /* Strobe Relay disable bit */ + ni_writew(devpriv->ai_calib_source | + Calibration_Channel_6143_RelayOff, + Calibration_Channel_6143); + ni_writew(devpriv->ai_calib_source, + Calibration_Channel_6143); + devpriv->ai_calib_source_enabled = 0; + msleep_interruptible(100); /* Allow relays to change */ + } + } + + offset = 1 << (boardtype.adbits - 1); + for (i = 0; i < n_chan; i++) { + if ((boardtype.reg_type != ni_reg_6143) + && (list[i] & CR_ALT_SOURCE)) { + chan = devpriv->ai_calib_source; + } else { + chan = CR_CHAN(list[i]); + } + aref = CR_AREF(list[i]); + range = CR_RANGE(list[i]); + dither = ((list[i] & CR_ALT_FILTER) != 0); + + /* fix the external/internal range differences */ + range = ni_gainlkup[boardtype.gainlkup][range]; + if (boardtype.reg_type == ni_reg_611x) + devpriv->ai_offset[i] = offset; + else + devpriv->ai_offset[i] = (range & 0x100) ? 0 : offset; + + hi = 0; + if ((list[i] & CR_ALT_SOURCE)) { + if (boardtype.reg_type == ni_reg_611x) + ni_writew(CR_CHAN(list[i]) & 0x0003, + Calibration_Channel_Select_611x); + } else { + if (boardtype.reg_type == ni_reg_611x) + aref = AREF_DIFF; + else if (boardtype.reg_type == ni_reg_6143) + aref = AREF_OTHER; + switch (aref) { + case AREF_DIFF: + hi |= AI_DIFFERENTIAL; + break; + case AREF_COMMON: + hi |= AI_COMMON; + break; + case AREF_GROUND: + hi |= AI_GROUND; + break; + case AREF_OTHER: + break; + } + } + hi |= AI_CONFIG_CHANNEL(chan); + + ni_writew(hi, Configuration_Memory_High); + + if (boardtype.reg_type != ni_reg_6143) { + lo = range; + if (i == n_chan - 1) + lo |= AI_LAST_CHANNEL; + if (dither) + lo |= AI_DITHER; + + ni_writew(lo, Configuration_Memory_Low); + } + } + + /* prime the channel/gain list */ + if ((boardtype.reg_type != ni_reg_611x) + && (boardtype.reg_type != ni_reg_6143)) { + ni_prime_channelgain_list(dev); + } +} + +static int ni_ns_to_timer(const struct comedi_device *dev, unsigned nanosec, + int round_mode) +{ + int divider; + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (nanosec + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + divider = (nanosec) / devpriv->clock_ns; + break; + case TRIG_ROUND_UP: + divider = (nanosec + devpriv->clock_ns - 1) / devpriv->clock_ns; + break; + } + return divider - 1; +} + +static unsigned ni_timer_to_ns(const struct comedi_device *dev, int timer) +{ + return devpriv->clock_ns * (timer + 1); +} + +static unsigned ni_min_ai_scan_period_ns(struct comedi_device *dev, + unsigned num_channels) +{ + switch (boardtype.reg_type) { + case ni_reg_611x: + case ni_reg_6143: + /* simultaneously-sampled inputs */ + return boardtype.ai_speed; + break; + default: + /* multiplexed inputs */ + break; + } + return boardtype.ai_speed * num_channels; +} + +static int ni_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + int sources; + + /* step 1: make sure trigger sources are trivially valid */ + + if ((cmd->flags & CMDF_WRITE)) { + cmd->flags &= ~CMDF_WRITE; + } + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + sources = TRIG_TIMER | TRIG_EXT; + if ((boardtype.reg_type == ni_reg_611x) + || (boardtype.reg_type == ni_reg_6143)) + sources |= TRIG_NOW; + cmd->convert_src &= sources; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->start_src != TRIG_NOW && + cmd->start_src != TRIG_INT && cmd->start_src != TRIG_EXT) + err++; + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_OTHER) + err++; + if (cmd->convert_src != TRIG_TIMER && + cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_src == TRIG_EXT) { + /* external trigger */ + unsigned int tmp = CR_CHAN(cmd->start_arg); + + if (tmp > 16) + tmp = 16; + tmp |= (cmd->start_arg & (CR_INVERT | CR_EDGE)); + if (cmd->start_arg != tmp) { + cmd->start_arg = tmp; + err++; + } + } else { + if (cmd->start_arg != 0) { + /* true for both TRIG_NOW and TRIG_INT */ + cmd->start_arg = 0; + err++; + } + } + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < ni_min_ai_scan_period_ns(dev, + cmd-> + chanlist_len)) + { + cmd->scan_begin_arg = + ni_min_ai_scan_period_ns(dev, cmd->chanlist_len); + err++; + } + if (cmd->scan_begin_arg > devpriv->clock_ns * 0xffffff) { + cmd->scan_begin_arg = devpriv->clock_ns * 0xffffff; + err++; + } + } else if (cmd->scan_begin_src == TRIG_EXT) { + /* external trigger */ + unsigned int tmp = CR_CHAN(cmd->scan_begin_arg); + + if (tmp > 16) + tmp = 16; + tmp |= (cmd->scan_begin_arg & (CR_INVERT | CR_EDGE)); + if (cmd->scan_begin_arg != tmp) { + cmd->scan_begin_arg = tmp; + err++; + } + } else { /* TRIG_OTHER */ + if (cmd->scan_begin_arg) { + cmd->scan_begin_arg = 0; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if ((boardtype.reg_type == ni_reg_611x) + || (boardtype.reg_type == ni_reg_6143)) { + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + } else { + if (cmd->convert_arg < boardtype.ai_speed) { + cmd->convert_arg = boardtype.ai_speed; + err++; + } + if (cmd->convert_arg > devpriv->clock_ns * 0xffff) { + cmd->convert_arg = devpriv->clock_ns * 0xffff; + err++; + } + } + } else if (cmd->convert_src == TRIG_EXT) { + /* external trigger */ + unsigned int tmp = CR_CHAN(cmd->convert_arg); + + if (tmp > 16) + tmp = 16; + tmp |= (cmd->convert_arg & (CR_ALT_FILTER | CR_INVERT)); + if (cmd->convert_arg != tmp) { + cmd->convert_arg = tmp; + err++; + } + } else if (cmd->convert_src == TRIG_NOW) { + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + unsigned int max_count = 0x01000000; + + if (boardtype.reg_type == ni_reg_611x) + max_count -= num_adc_stages_611x; + if (cmd->stop_arg > max_count) { + cmd->stop_arg = max_count; + err++; + } + if (cmd->stop_arg < 1) { + cmd->stop_arg = 1; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + cmd->scan_begin_arg = + ni_timer_to_ns(dev, ni_ns_to_timer(dev, + cmd->scan_begin_arg, + cmd-> + flags & + TRIG_ROUND_MASK)); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + if ((boardtype.reg_type != ni_reg_611x) + && (boardtype.reg_type != ni_reg_6143)) { + tmp = cmd->convert_arg; + cmd->convert_arg = + ni_timer_to_ns(dev, ni_ns_to_timer(dev, + cmd->convert_arg, + cmd-> + flags & + TRIG_ROUND_MASK)); + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + } + + if (err) + return 4; + + return 0; +} + +static int ni_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct comedi_cmd *cmd = &s->async->cmd; + int timer; + int mode1 = 0; /* mode1 is needed for both stop and convert */ + int mode2 = 0; + int start_stop_select = 0; + unsigned int stop_count; + int interrupt_a_enable = 0; + + MDPRINTK("ni_ai_cmd\n"); + if (dev->irq == 0) { + comedi_error(dev, "cannot run command without an irq"); + return -EIO; + } + ni_clear_ai_fifo(dev); + + ni_load_channelgain_list(dev, cmd->chanlist_len, cmd->chanlist); + + /* start configuration */ + devpriv->stc_writew(dev, AI_Configuration_Start, Joint_Reset_Register); + + /* disable analog triggering for now, since it + * interferes with the use of pfi0 */ + devpriv->an_trig_etc_reg &= ~Analog_Trigger_Enable; + devpriv->stc_writew(dev, devpriv->an_trig_etc_reg, + Analog_Trigger_Etc_Register); + + switch (cmd->start_src) { + case TRIG_INT: + case TRIG_NOW: + devpriv->stc_writew(dev, AI_START2_Select(0) | + AI_START1_Sync | AI_START1_Edge | + AI_START1_Select(0), + AI_Trigger_Select_Register); + break; + case TRIG_EXT: + { + int chan = CR_CHAN(cmd->start_arg); + unsigned int bits = AI_START2_Select(0) | + AI_START1_Sync | AI_START1_Select(chan + 1); + + if (cmd->start_arg & CR_INVERT) + bits |= AI_START1_Polarity; + if (cmd->start_arg & CR_EDGE) + bits |= AI_START1_Edge; + devpriv->stc_writew(dev, bits, + AI_Trigger_Select_Register); + break; + } + } + + mode2 &= ~AI_Pre_Trigger; + mode2 &= ~AI_SC_Initial_Load_Source; + mode2 &= ~AI_SC_Reload_Mode; + devpriv->stc_writew(dev, mode2, AI_Mode_2_Register); + + if (cmd->chanlist_len == 1 || (boardtype.reg_type == ni_reg_611x) + || (boardtype.reg_type == ni_reg_6143)) { + start_stop_select |= AI_STOP_Polarity; + start_stop_select |= AI_STOP_Select(31); /* logic low */ + start_stop_select |= AI_STOP_Sync; + } else { + start_stop_select |= AI_STOP_Select(19); /* ai configuration memory */ + } + devpriv->stc_writew(dev, start_stop_select, + AI_START_STOP_Select_Register); + + devpriv->ai_cmd2 = 0; + switch (cmd->stop_src) { + case TRIG_COUNT: + stop_count = cmd->stop_arg - 1; + + if (boardtype.reg_type == ni_reg_611x) { + /* have to take 3 stage adc pipeline into account */ + stop_count += num_adc_stages_611x; + } + /* stage number of scans */ + devpriv->stc_writel(dev, stop_count, AI_SC_Load_A_Registers); + + mode1 |= AI_Start_Stop | AI_Mode_1_Reserved | AI_Trigger_Once; + devpriv->stc_writew(dev, mode1, AI_Mode_1_Register); + /* load SC (Scan Count) */ + devpriv->stc_writew(dev, AI_SC_Load, AI_Command_1_Register); + + devpriv->ai_continuous = 0; + if (stop_count == 0) { + devpriv->ai_cmd2 |= AI_End_On_End_Of_Scan; + interrupt_a_enable |= AI_STOP_Interrupt_Enable; + /* this is required to get the last sample for chanlist_len > 1, not sure why */ + if (cmd->chanlist_len > 1) + start_stop_select |= + AI_STOP_Polarity | AI_STOP_Edge; + } + break; + case TRIG_NONE: + /* stage number of scans */ + devpriv->stc_writel(dev, 0, AI_SC_Load_A_Registers); + + mode1 |= AI_Start_Stop | AI_Mode_1_Reserved | AI_Continuous; + devpriv->stc_writew(dev, mode1, AI_Mode_1_Register); + + /* load SC (Scan Count) */ + devpriv->stc_writew(dev, AI_SC_Load, AI_Command_1_Register); + + devpriv->ai_continuous = 1; + + break; + } + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + /* + stop bits for non 611x boards + AI_SI_Special_Trigger_Delay=0 + AI_Pre_Trigger=0 + AI_START_STOP_Select_Register: + AI_START_Polarity=0 (?) rising edge + AI_START_Edge=1 edge triggered + AI_START_Sync=1 (?) + AI_START_Select=0 SI_TC + AI_STOP_Polarity=0 rising edge + AI_STOP_Edge=0 level + AI_STOP_Sync=1 + AI_STOP_Select=19 external pin (configuration mem) + */ + start_stop_select |= AI_START_Edge | AI_START_Sync; + devpriv->stc_writew(dev, start_stop_select, + AI_START_STOP_Select_Register); + + mode2 |= AI_SI_Reload_Mode(0); + /* AI_SI_Initial_Load_Source=A */ + mode2 &= ~AI_SI_Initial_Load_Source; + /* mode2 |= AI_SC_Reload_Mode; */ + devpriv->stc_writew(dev, mode2, AI_Mode_2_Register); + + /* load SI */ + timer = ni_ns_to_timer(dev, cmd->scan_begin_arg, + TRIG_ROUND_NEAREST); + devpriv->stc_writel(dev, timer, AI_SI_Load_A_Registers); + devpriv->stc_writew(dev, AI_SI_Load, AI_Command_1_Register); + break; + case TRIG_EXT: + if (cmd->scan_begin_arg & CR_EDGE) + start_stop_select |= AI_START_Edge; + /* AI_START_Polarity==1 is falling edge */ + if (cmd->scan_begin_arg & CR_INVERT) + start_stop_select |= AI_START_Polarity; + if (cmd->scan_begin_src != cmd->convert_src || + (cmd->scan_begin_arg & ~CR_EDGE) != + (cmd->convert_arg & ~CR_EDGE)) + start_stop_select |= AI_START_Sync; + start_stop_select |= + AI_START_Select(1 + CR_CHAN(cmd->scan_begin_arg)); + devpriv->stc_writew(dev, start_stop_select, + AI_START_STOP_Select_Register); + break; + } + + switch (cmd->convert_src) { + case TRIG_TIMER: + case TRIG_NOW: + if (cmd->convert_arg == 0 || cmd->convert_src == TRIG_NOW) + timer = 1; + else + timer = ni_ns_to_timer(dev, cmd->convert_arg, + TRIG_ROUND_NEAREST); + devpriv->stc_writew(dev, 1, AI_SI2_Load_A_Register); /* 0,0 does not work. */ + devpriv->stc_writew(dev, timer, AI_SI2_Load_B_Register); + + /* AI_SI2_Reload_Mode = alternate */ + /* AI_SI2_Initial_Load_Source = A */ + mode2 &= ~AI_SI2_Initial_Load_Source; + mode2 |= AI_SI2_Reload_Mode; + devpriv->stc_writew(dev, mode2, AI_Mode_2_Register); + + /* AI_SI2_Load */ + devpriv->stc_writew(dev, AI_SI2_Load, AI_Command_1_Register); + + mode2 |= AI_SI2_Reload_Mode; /* alternate */ + mode2 |= AI_SI2_Initial_Load_Source; /* B */ + + devpriv->stc_writew(dev, mode2, AI_Mode_2_Register); + break; + case TRIG_EXT: + mode1 |= AI_CONVERT_Source_Select(1 + cmd->convert_arg); + if ((cmd->convert_arg & CR_INVERT) == 0) + mode1 |= AI_CONVERT_Source_Polarity; + devpriv->stc_writew(dev, mode1, AI_Mode_1_Register); + + mode2 |= AI_Start_Stop_Gate_Enable | AI_SC_Gate_Enable; + devpriv->stc_writew(dev, mode2, AI_Mode_2_Register); + + break; + } + + if (dev->irq) { + + /* interrupt on FIFO, errors, SC_TC */ + interrupt_a_enable |= AI_Error_Interrupt_Enable | + AI_SC_TC_Interrupt_Enable; + +#ifndef PCIDMA + interrupt_a_enable |= AI_FIFO_Interrupt_Enable; +#endif + + if (cmd->flags & TRIG_WAKE_EOS + || (devpriv->ai_cmd2 & AI_End_On_End_Of_Scan)) { + /* wake on end-of-scan */ + devpriv->aimode = AIMODE_SCAN; + } else { + devpriv->aimode = AIMODE_HALF_FULL; + } + + switch (devpriv->aimode) { + case AIMODE_HALF_FULL: + /*generate FIFO interrupts and DMA requests on half-full */ +#ifdef PCIDMA + devpriv->stc_writew(dev, AI_FIFO_Mode_HF_to_E, + AI_Mode_3_Register); +#else + devpriv->stc_writew(dev, AI_FIFO_Mode_HF, + AI_Mode_3_Register); +#endif + break; + case AIMODE_SAMPLE: + /*generate FIFO interrupts on non-empty */ + devpriv->stc_writew(dev, AI_FIFO_Mode_NE, + AI_Mode_3_Register); + break; + case AIMODE_SCAN: +#ifdef PCIDMA + devpriv->stc_writew(dev, AI_FIFO_Mode_NE, + AI_Mode_3_Register); +#else + devpriv->stc_writew(dev, AI_FIFO_Mode_HF, + AI_Mode_3_Register); +#endif + interrupt_a_enable |= AI_STOP_Interrupt_Enable; + break; + default: + break; + } + + devpriv->stc_writew(dev, AI_Error_Interrupt_Ack | AI_STOP_Interrupt_Ack | AI_START_Interrupt_Ack | AI_START2_Interrupt_Ack | AI_START1_Interrupt_Ack | AI_SC_TC_Interrupt_Ack | AI_SC_TC_Error_Confirm, Interrupt_A_Ack_Register); /* clear interrupts */ + + ni_set_bits(dev, Interrupt_A_Enable_Register, + interrupt_a_enable, 1); + + MDPRINTK("Interrupt_A_Enable_Register = 0x%04x\n", + devpriv->int_a_enable_reg); + } else { + /* interrupt on nothing */ + ni_set_bits(dev, Interrupt_A_Enable_Register, ~0, 0); + + /* XXX start polling if necessary */ + MDPRINTK("interrupting on nothing\n"); + } + + /* end configuration */ + devpriv->stc_writew(dev, AI_Configuration_End, Joint_Reset_Register); + + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + devpriv->stc_writew(dev, + AI_SI2_Arm | AI_SI_Arm | AI_DIV_Arm | + AI_SC_Arm, AI_Command_1_Register); + break; + case TRIG_EXT: + /* XXX AI_SI_Arm? */ + devpriv->stc_writew(dev, + AI_SI2_Arm | AI_SI_Arm | AI_DIV_Arm | + AI_SC_Arm, AI_Command_1_Register); + break; + } + +#ifdef PCIDMA + { + int retval = ni_ai_setup_MITE_dma(dev); + if (retval) + return retval; + } + /* mite_dump_regs(devpriv->mite); */ +#endif + + switch (cmd->start_src) { + case TRIG_NOW: + /* AI_START1_Pulse */ + devpriv->stc_writew(dev, AI_START1_Pulse | devpriv->ai_cmd2, + AI_Command_2_Register); + s->async->inttrig = NULL; + break; + case TRIG_EXT: + s->async->inttrig = NULL; + break; + case TRIG_INT: + s->async->inttrig = &ni_ai_inttrig; + break; + } + + MDPRINTK("exit ni_ai_cmd\n"); + + return 0; +} + +static int ni_ai_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum) +{ + if (trignum != 0) + return -EINVAL; + + devpriv->stc_writew(dev, AI_START1_Pulse | devpriv->ai_cmd2, + AI_Command_2_Register); + s->async->inttrig = NULL; + + return 1; +} + +static int ni_ai_config_analog_trig(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data); + +static int ni_ai_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n < 1) + return -EINVAL; + + switch (data[0]) { + case INSN_CONFIG_ANALOG_TRIG: + return ni_ai_config_analog_trig(dev, s, insn, data); + case INSN_CONFIG_ALT_SOURCE: + if (boardtype.reg_type & ni_reg_m_series_mask) { + if (data[1] & ~(MSeries_AI_Bypass_Cal_Sel_Pos_Mask | + MSeries_AI_Bypass_Cal_Sel_Neg_Mask | + MSeries_AI_Bypass_Mode_Mux_Mask | + MSeries_AO_Bypass_AO_Cal_Sel_Mask)) { + return -EINVAL; + } + devpriv->ai_calib_source = data[1]; + } else if (boardtype.reg_type == ni_reg_6143) { + unsigned int calib_source; + + calib_source = data[1] & 0xf; + + if (calib_source > 0xF) + return -EINVAL; + + devpriv->ai_calib_source = calib_source; + ni_writew(calib_source, Calibration_Channel_6143); + } else { + unsigned int calib_source; + unsigned int calib_source_adjust; + + calib_source = data[1] & 0xf; + calib_source_adjust = (data[1] >> 4) & 0xff; + + if (calib_source >= 8) + return -EINVAL; + devpriv->ai_calib_source = calib_source; + if (boardtype.reg_type == ni_reg_611x) { + ni_writeb(calib_source_adjust, + Cal_Gain_Select_611x); + } + } + return 2; + default: + break; + } + + return -EINVAL; +} + +static int ni_ai_config_analog_trig(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + unsigned int a, b, modebits; + int err = 0; + + /* data[1] is flags + * data[2] is analog line + * data[3] is set level + * data[4] is reset level */ + if (!boardtype.has_analog_trig) + return -EINVAL; + if ((data[1] & 0xffff0000) != COMEDI_EV_SCAN_BEGIN) { + data[1] &= (COMEDI_EV_SCAN_BEGIN | 0xffff); + err++; + } + if (data[2] >= boardtype.n_adchan) { + data[2] = boardtype.n_adchan - 1; + err++; + } + if (data[3] > 255) { /* a */ + data[3] = 255; + err++; + } + if (data[4] > 255) { /* b */ + data[4] = 255; + err++; + } + /* + * 00 ignore + * 01 set + * 10 reset + * + * modes: + * 1 level: +b- +a- + * high mode 00 00 01 10 + * low mode 00 00 10 01 + * 2 level: (a> 4); + } + devpriv->atrig_low = a; + devpriv->atrig_high = b; + switch (modebits) { + case 0x81: /* low hysteresis mode */ + devpriv->atrig_mode = 6; + break; + case 0x42: /* high hysteresis mode */ + devpriv->atrig_mode = 3; + break; + case 0x96: /* middle window mode */ + devpriv->atrig_mode = 2; + break; + default: + data[1] &= ~0xff; + err++; + } + } else { + /* one level mode */ + if (b != 0) { + data[4] = 0; + err++; + } + switch (modebits) { + case 0x06: /* high window mode */ + devpriv->atrig_high = a; + devpriv->atrig_mode = 0; + break; + case 0x09: /* low window mode */ + devpriv->atrig_low = a; + devpriv->atrig_mode = 1; + break; + default: + data[1] &= ~0xff; + err++; + } + } + if (err) + return -EAGAIN; + return 5; +} + +/* munge data from unsigned to 2's complement for analog output bipolar modes */ +static void ni_ao_munge(struct comedi_device *dev, struct comedi_subdevice *s, + void *data, unsigned int num_bytes, + unsigned int chan_index) +{ + struct comedi_async *async = s->async; + unsigned int range; + unsigned int i; + unsigned int offset; + unsigned int length = num_bytes / sizeof(short); + short *array = data; + + offset = 1 << (boardtype.aobits - 1); + for (i = 0; i < length; i++) { + range = CR_RANGE(async->cmd.chanlist[chan_index]); + if (boardtype.ao_unipolar == 0 || (range & 1) == 0) + array[i] -= offset; +#ifdef PCIDMA + array[i] = cpu_to_le16(array[i]); +#endif + chan_index++; + chan_index %= async->cmd.chanlist_len; + } +} + +static int ni_m_series_ao_config_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chanspec[], + unsigned int n_chans, int timed) +{ + unsigned int range; + unsigned int chan; + unsigned int conf; + int i; + int invert = 0; + + if (timed) { + for (i = 0; i < boardtype.n_aochan; ++i) { + devpriv->ao_conf[i] &= ~MSeries_AO_Update_Timed_Bit; + ni_writeb(devpriv->ao_conf[i], + M_Offset_AO_Config_Bank(i)); + ni_writeb(0xf, M_Offset_AO_Waveform_Order(i)); + } + } + for (i = 0; i < n_chans; i++) { + const struct comedi_krange *krange; + chan = CR_CHAN(chanspec[i]); + range = CR_RANGE(chanspec[i]); + krange = s->range_table->range + range; + invert = 0; + conf = 0; + switch (krange->max - krange->min) { + case 20000000: + conf |= MSeries_AO_DAC_Reference_10V_Internal_Bits; + ni_writeb(0, M_Offset_AO_Reference_Attenuation(chan)); + break; + case 10000000: + conf |= MSeries_AO_DAC_Reference_5V_Internal_Bits; + ni_writeb(0, M_Offset_AO_Reference_Attenuation(chan)); + break; + case 4000000: + conf |= MSeries_AO_DAC_Reference_10V_Internal_Bits; + ni_writeb(MSeries_Attenuate_x5_Bit, + M_Offset_AO_Reference_Attenuation(chan)); + break; + case 2000000: + conf |= MSeries_AO_DAC_Reference_5V_Internal_Bits; + ni_writeb(MSeries_Attenuate_x5_Bit, + M_Offset_AO_Reference_Attenuation(chan)); + break; + default: + printk("%s: bug! unhandled ao reference voltage\n", + __func__); + break; + } + switch (krange->max + krange->min) { + case 0: + conf |= MSeries_AO_DAC_Offset_0V_Bits; + break; + case 10000000: + conf |= MSeries_AO_DAC_Offset_5V_Bits; + break; + default: + printk("%s: bug! unhandled ao offset voltage\n", + __func__); + break; + } + if (timed) + conf |= MSeries_AO_Update_Timed_Bit; + ni_writeb(conf, M_Offset_AO_Config_Bank(chan)); + devpriv->ao_conf[chan] = conf; + ni_writeb(i, M_Offset_AO_Waveform_Order(chan)); + } + return invert; +} + +static int ni_old_ao_config_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chanspec[], + unsigned int n_chans) +{ + unsigned int range; + unsigned int chan; + unsigned int conf; + int i; + int invert = 0; + + for (i = 0; i < n_chans; i++) { + chan = CR_CHAN(chanspec[i]); + range = CR_RANGE(chanspec[i]); + conf = AO_Channel(chan); + + if (boardtype.ao_unipolar) { + if ((range & 1) == 0) { + conf |= AO_Bipolar; + invert = (1 << (boardtype.aobits - 1)); + } else { + invert = 0; + } + if (range & 2) + conf |= AO_Ext_Ref; + } else { + conf |= AO_Bipolar; + invert = (1 << (boardtype.aobits - 1)); + } + + /* not all boards can deglitch, but this shouldn't hurt */ + if (chanspec[i] & CR_DEGLITCH) + conf |= AO_Deglitch; + + /* analog reference */ + /* AREF_OTHER connects AO ground to AI ground, i think */ + conf |= (CR_AREF(chanspec[i]) == + AREF_OTHER) ? AO_Ground_Ref : 0; + + ni_writew(conf, AO_Configuration); + devpriv->ao_conf[chan] = conf; + } + return invert; +} + +static int ni_ao_config_chanlist(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int chanspec[], unsigned int n_chans, + int timed) +{ + if (boardtype.reg_type & ni_reg_m_series_mask) + return ni_m_series_ao_config_chanlist(dev, s, chanspec, n_chans, + timed); + else + return ni_old_ao_config_chanlist(dev, s, chanspec, n_chans); +} + +static int ni_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + data[0] = devpriv->ao[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int ni_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int invert; + + invert = ni_ao_config_chanlist(dev, s, &insn->chanspec, 1, 0); + + devpriv->ao[chan] = data[0]; + + if (boardtype.reg_type & ni_reg_m_series_mask) { + ni_writew(data[0], M_Offset_DAC_Direct_Data(chan)); + } else + ni_writew(data[0] ^ invert, + (chan) ? DAC1_Direct_Data : DAC0_Direct_Data); + + return 1; +} + +static int ni_ao_insn_write_671x(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int invert; + + ao_win_out(1 << chan, AO_Immediate_671x); + invert = 1 << (boardtype.aobits - 1); + + ni_ao_config_chanlist(dev, s, &insn->chanspec, 1, 0); + + devpriv->ao[chan] = data[0]; + ao_win_out(data[0] ^ invert, DACx_Direct_Data_671x(chan)); + + return 1; +} + +static int ni_ao_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + switch (data[0]) { + case INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE: + switch (data[1]) { + case COMEDI_OUTPUT: + data[2] = 1 + boardtype.ao_fifo_depth * sizeof(short); + if (devpriv->mite) + data[2] += devpriv->mite->fifo_size; + break; + case COMEDI_INPUT: + data[2] = 0; + break; + default: + return -EINVAL; + break; + } + return 0; + default: + break; + } + + return -EINVAL; +} + +static int ni_ao_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum) +{ + int ret; + int interrupt_b_bits; + int i; + static const int timeout = 1000; + + if (trignum != 0) + return -EINVAL; + + /* Null trig at beginning prevent ao start trigger from executing more than + once per command (and doing things like trying to allocate the ao dma channel + multiple times) */ + s->async->inttrig = NULL; + + ni_set_bits(dev, Interrupt_B_Enable_Register, + AO_FIFO_Interrupt_Enable | AO_Error_Interrupt_Enable, 0); + interrupt_b_bits = AO_Error_Interrupt_Enable; +#ifdef PCIDMA + devpriv->stc_writew(dev, 1, DAC_FIFO_Clear); + if (boardtype.reg_type & ni_reg_6xxx_mask) + ni_ao_win_outl(dev, 0x6, AO_FIFO_Offset_Load_611x); + ret = ni_ao_setup_MITE_dma(dev); + if (ret) + return ret; + ret = ni_ao_wait_for_dma_load(dev); + if (ret < 0) + return ret; +#else + ret = ni_ao_prep_fifo(dev, s); + if (ret == 0) + return -EPIPE; + + interrupt_b_bits |= AO_FIFO_Interrupt_Enable; +#endif + + devpriv->stc_writew(dev, devpriv->ao_mode3 | AO_Not_An_UPDATE, + AO_Mode_3_Register); + devpriv->stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register); + /* wait for DACs to be loaded */ + for (i = 0; i < timeout; i++) { + udelay(1); + if ((devpriv->stc_readw(dev, + Joint_Status_2_Register) & + AO_TMRDACWRs_In_Progress_St) == 0) + break; + } + if (i == timeout) { + comedi_error(dev, + "timed out waiting for AO_TMRDACWRs_In_Progress_St to clear"); + return -EIO; + } + /* stc manual says we are need to clear error interrupt after AO_TMRDACWRs_In_Progress_St clears */ + devpriv->stc_writew(dev, AO_Error_Interrupt_Ack, + Interrupt_B_Ack_Register); + + ni_set_bits(dev, Interrupt_B_Enable_Register, interrupt_b_bits, 1); + + devpriv->stc_writew(dev, + devpriv->ao_cmd1 | AO_UI_Arm | AO_UC_Arm | AO_BC_Arm + | AO_DAC1_Update_Mode | AO_DAC0_Update_Mode, + AO_Command_1_Register); + + devpriv->stc_writew(dev, devpriv->ao_cmd2 | AO_START1_Pulse, + AO_Command_2_Register); + + return 0; +} + +static int ni_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct comedi_cmd *cmd = &s->async->cmd; + int bits; + int i; + unsigned trigvar; + + if (dev->irq == 0) { + comedi_error(dev, "cannot run command without an irq"); + return -EIO; + } + + devpriv->stc_writew(dev, AO_Configuration_Start, Joint_Reset_Register); + + devpriv->stc_writew(dev, AO_Disarm, AO_Command_1_Register); + + if (boardtype.reg_type & ni_reg_6xxx_mask) { + ao_win_out(CLEAR_WG, AO_Misc_611x); + + bits = 0; + for (i = 0; i < cmd->chanlist_len; i++) { + int chan; + + chan = CR_CHAN(cmd->chanlist[i]); + bits |= 1 << chan; + ao_win_out(chan, AO_Waveform_Generation_611x); + } + ao_win_out(bits, AO_Timed_611x); + } + + ni_ao_config_chanlist(dev, s, cmd->chanlist, cmd->chanlist_len, 1); + + if (cmd->stop_src == TRIG_NONE) { + devpriv->ao_mode1 |= AO_Continuous; + devpriv->ao_mode1 &= ~AO_Trigger_Once; + } else { + devpriv->ao_mode1 &= ~AO_Continuous; + devpriv->ao_mode1 |= AO_Trigger_Once; + } + devpriv->stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register); + switch (cmd->start_src) { + case TRIG_INT: + case TRIG_NOW: + devpriv->ao_trigger_select &= + ~(AO_START1_Polarity | AO_START1_Select(-1)); + devpriv->ao_trigger_select |= AO_START1_Edge | AO_START1_Sync; + devpriv->stc_writew(dev, devpriv->ao_trigger_select, + AO_Trigger_Select_Register); + break; + case TRIG_EXT: + devpriv->ao_trigger_select = + AO_START1_Select(CR_CHAN(cmd->start_arg) + 1); + if (cmd->start_arg & CR_INVERT) + devpriv->ao_trigger_select |= AO_START1_Polarity; /* 0=active high, 1=active low. see daq-stc 3-24 (p186) */ + if (cmd->start_arg & CR_EDGE) + devpriv->ao_trigger_select |= AO_START1_Edge; /* 0=edge detection disabled, 1=enabled */ + devpriv->stc_writew(dev, devpriv->ao_trigger_select, + AO_Trigger_Select_Register); + break; + default: + BUG(); + break; + } + devpriv->ao_mode3 &= ~AO_Trigger_Length; + devpriv->stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register); + + devpriv->stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register); + devpriv->ao_mode2 &= ~AO_BC_Initial_Load_Source; + devpriv->stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register); + if (cmd->stop_src == TRIG_NONE) { + devpriv->stc_writel(dev, 0xffffff, AO_BC_Load_A_Register); + } else { + devpriv->stc_writel(dev, 0, AO_BC_Load_A_Register); + } + devpriv->stc_writew(dev, AO_BC_Load, AO_Command_1_Register); + devpriv->ao_mode2 &= ~AO_UC_Initial_Load_Source; + devpriv->stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register); + switch (cmd->stop_src) { + case TRIG_COUNT: + if (boardtype.reg_type & ni_reg_m_series_mask) { + /* this is how the NI example code does it for m-series boards, verified correct with 6259 */ + devpriv->stc_writel(dev, cmd->stop_arg - 1, + AO_UC_Load_A_Register); + devpriv->stc_writew(dev, AO_UC_Load, + AO_Command_1_Register); + } else { + devpriv->stc_writel(dev, cmd->stop_arg, + AO_UC_Load_A_Register); + devpriv->stc_writew(dev, AO_UC_Load, + AO_Command_1_Register); + devpriv->stc_writel(dev, cmd->stop_arg - 1, + AO_UC_Load_A_Register); + } + break; + case TRIG_NONE: + devpriv->stc_writel(dev, 0xffffff, AO_UC_Load_A_Register); + devpriv->stc_writew(dev, AO_UC_Load, AO_Command_1_Register); + devpriv->stc_writel(dev, 0xffffff, AO_UC_Load_A_Register); + break; + default: + devpriv->stc_writel(dev, 0, AO_UC_Load_A_Register); + devpriv->stc_writew(dev, AO_UC_Load, AO_Command_1_Register); + devpriv->stc_writel(dev, cmd->stop_arg, AO_UC_Load_A_Register); + } + + devpriv->ao_mode1 &= + ~(AO_UI_Source_Select(0x1f) | AO_UI_Source_Polarity | + AO_UPDATE_Source_Select(0x1f) | AO_UPDATE_Source_Polarity); + switch (cmd->scan_begin_src) { + case TRIG_TIMER: + devpriv->ao_cmd2 &= ~AO_BC_Gate_Enable; + trigvar = + ni_ns_to_timer(dev, cmd->scan_begin_arg, + TRIG_ROUND_NEAREST); + devpriv->stc_writel(dev, 1, AO_UI_Load_A_Register); + devpriv->stc_writew(dev, AO_UI_Load, AO_Command_1_Register); + devpriv->stc_writel(dev, trigvar, AO_UI_Load_A_Register); + break; + case TRIG_EXT: + devpriv->ao_mode1 |= + AO_UPDATE_Source_Select(cmd->scan_begin_arg); + if (cmd->scan_begin_arg & CR_INVERT) + devpriv->ao_mode1 |= AO_UPDATE_Source_Polarity; + devpriv->ao_cmd2 |= AO_BC_Gate_Enable; + break; + default: + BUG(); + break; + } + devpriv->stc_writew(dev, devpriv->ao_cmd2, AO_Command_2_Register); + devpriv->stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register); + devpriv->ao_mode2 &= + ~(AO_UI_Reload_Mode(3) | AO_UI_Initial_Load_Source); + devpriv->stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register); + + if (cmd->scan_end_arg > 1) { + devpriv->ao_mode1 |= AO_Multiple_Channels; + devpriv->stc_writew(dev, + AO_Number_Of_Channels(cmd->scan_end_arg - + 1) | + AO_UPDATE_Output_Select + (AO_Update_Output_High_Z), + AO_Output_Control_Register); + } else { + unsigned bits; + devpriv->ao_mode1 &= ~AO_Multiple_Channels; + bits = AO_UPDATE_Output_Select(AO_Update_Output_High_Z); + if (boardtype. + reg_type & (ni_reg_m_series_mask | ni_reg_6xxx_mask)) { + bits |= AO_Number_Of_Channels(0); + } else { + bits |= + AO_Number_Of_Channels(CR_CHAN(cmd->chanlist[0])); + } + devpriv->stc_writew(dev, bits, AO_Output_Control_Register); + } + devpriv->stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register); + + devpriv->stc_writew(dev, AO_DAC0_Update_Mode | AO_DAC1_Update_Mode, + AO_Command_1_Register); + + devpriv->ao_mode3 |= AO_Stop_On_Overrun_Error; + devpriv->stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register); + + devpriv->ao_mode2 &= ~AO_FIFO_Mode_Mask; +#ifdef PCIDMA + devpriv->ao_mode2 |= AO_FIFO_Mode_HF_to_F; +#else + devpriv->ao_mode2 |= AO_FIFO_Mode_HF; +#endif + devpriv->ao_mode2 &= ~AO_FIFO_Retransmit_Enable; + devpriv->stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register); + + bits = AO_BC_Source_Select | AO_UPDATE_Pulse_Width | + AO_TMRDACWR_Pulse_Width; + if (boardtype.ao_fifo_depth) + bits |= AO_FIFO_Enable; + else + bits |= AO_DMA_PIO_Control; +#if 0 + /* F Hess: windows driver does not set AO_Number_Of_DAC_Packages bit for 6281, + verified with bus analyzer. */ + if (boardtype.reg_type & ni_reg_m_series_mask) + bits |= AO_Number_Of_DAC_Packages; +#endif + devpriv->stc_writew(dev, bits, AO_Personal_Register); + /* enable sending of ao dma requests */ + devpriv->stc_writew(dev, AO_AOFREQ_Enable, AO_Start_Select_Register); + + devpriv->stc_writew(dev, AO_Configuration_End, Joint_Reset_Register); + + if (cmd->stop_src == TRIG_COUNT) { + devpriv->stc_writew(dev, AO_BC_TC_Interrupt_Ack, + Interrupt_B_Ack_Register); + ni_set_bits(dev, Interrupt_B_Enable_Register, + AO_BC_TC_Interrupt_Enable, 1); + } + + s->async->inttrig = &ni_ao_inttrig; + + return 0; +} + +static int ni_ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + if ((cmd->flags & CMDF_WRITE) == 0) { + cmd->flags |= CMDF_WRITE; + } + + tmp = cmd->start_src; + cmd->start_src &= TRIG_INT | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_src == TRIG_EXT) { + /* external trigger */ + unsigned int tmp = CR_CHAN(cmd->start_arg); + + if (tmp > 18) + tmp = 18; + tmp |= (cmd->start_arg & (CR_INVERT | CR_EDGE)); + if (cmd->start_arg != tmp) { + cmd->start_arg = tmp; + err++; + } + } else { + if (cmd->start_arg != 0) { + /* true for both TRIG_NOW and TRIG_INT */ + cmd->start_arg = 0; + err++; + } + } + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < boardtype.ao_speed) { + cmd->scan_begin_arg = boardtype.ao_speed; + err++; + } + if (cmd->scan_begin_arg > devpriv->clock_ns * 0xffffff) { /* XXX check */ + cmd->scan_begin_arg = devpriv->clock_ns * 0xffffff; + err++; + } + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { /* XXX check */ + if (cmd->stop_arg > 0x00ffffff) { + cmd->stop_arg = 0x00ffffff; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + cmd->scan_begin_arg = + ni_timer_to_ns(dev, ni_ns_to_timer(dev, + cmd->scan_begin_arg, + cmd-> + flags & + TRIG_ROUND_MASK)); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (err) + return 4; + + /* step 5: fix up chanlist */ + + if (err) + return 5; + + return 0; +} + +static int ni_ao_reset(struct comedi_device *dev, struct comedi_subdevice *s) +{ + /* devpriv->ao0p=0x0000; */ + /* ni_writew(devpriv->ao0p,AO_Configuration); */ + + /* devpriv->ao1p=AO_Channel(1); */ + /* ni_writew(devpriv->ao1p,AO_Configuration); */ + + ni_release_ao_mite_channel(dev); + + devpriv->stc_writew(dev, AO_Configuration_Start, Joint_Reset_Register); + devpriv->stc_writew(dev, AO_Disarm, AO_Command_1_Register); + ni_set_bits(dev, Interrupt_B_Enable_Register, ~0, 0); + devpriv->stc_writew(dev, AO_BC_Source_Select, AO_Personal_Register); + devpriv->stc_writew(dev, 0x3f98, Interrupt_B_Ack_Register); + devpriv->stc_writew(dev, AO_BC_Source_Select | AO_UPDATE_Pulse_Width | + AO_TMRDACWR_Pulse_Width, AO_Personal_Register); + devpriv->stc_writew(dev, 0, AO_Output_Control_Register); + devpriv->stc_writew(dev, 0, AO_Start_Select_Register); + devpriv->ao_cmd1 = 0; + devpriv->stc_writew(dev, devpriv->ao_cmd1, AO_Command_1_Register); + devpriv->ao_cmd2 = 0; + devpriv->stc_writew(dev, devpriv->ao_cmd2, AO_Command_2_Register); + devpriv->ao_mode1 = 0; + devpriv->stc_writew(dev, devpriv->ao_mode1, AO_Mode_1_Register); + devpriv->ao_mode2 = 0; + devpriv->stc_writew(dev, devpriv->ao_mode2, AO_Mode_2_Register); + if (boardtype.reg_type & ni_reg_m_series_mask) + devpriv->ao_mode3 = AO_Last_Gate_Disable; + else + devpriv->ao_mode3 = 0; + devpriv->stc_writew(dev, devpriv->ao_mode3, AO_Mode_3_Register); + devpriv->ao_trigger_select = 0; + devpriv->stc_writew(dev, devpriv->ao_trigger_select, + AO_Trigger_Select_Register); + if (boardtype.reg_type & ni_reg_6xxx_mask) { + unsigned immediate_bits = 0; + unsigned i; + for (i = 0; i < s->n_chan; ++i) { + immediate_bits |= 1 << i; + } + ao_win_out(immediate_bits, AO_Immediate_671x); + ao_win_out(CLEAR_WG, AO_Misc_611x); + } + devpriv->stc_writew(dev, AO_Configuration_End, Joint_Reset_Register); + + return 0; +} + +/* digital io */ + +static int ni_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ +#ifdef DEBUG_DIO + printk("ni_dio_insn_config() chan=%d io=%d\n", + CR_CHAN(insn->chanspec), data[0]); +#endif + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << CR_CHAN(insn->chanspec); + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << CR_CHAN(insn->chanspec)); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s-> + io_bits & (1 << CR_CHAN(insn->chanspec))) ? COMEDI_OUTPUT : + COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + } + + devpriv->dio_control &= ~DIO_Pins_Dir_Mask; + devpriv->dio_control |= DIO_Pins_Dir(s->io_bits); + devpriv->stc_writew(dev, devpriv->dio_control, DIO_Control_Register); + + return 1; +} + +static int ni_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ +#ifdef DEBUG_DIO + printk("ni_dio_insn_bits() mask=0x%x bits=0x%x\n", data[0], data[1]); +#endif + if (insn->n != 2) + return -EINVAL; + if (data[0]) { + /* Perform check to make sure we're not using the + serial part of the dio */ + if ((data[0] & (DIO_SDIN | DIO_SDOUT)) + && devpriv->serial_interval_ns) + return -EBUSY; + + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + devpriv->dio_output &= ~DIO_Parallel_Data_Mask; + devpriv->dio_output |= DIO_Parallel_Data_Out(s->state); + devpriv->stc_writew(dev, devpriv->dio_output, + DIO_Output_Register); + } + data[1] = devpriv->stc_readw(dev, DIO_Parallel_Input_Register); + + return 2; +} + +static int ni_m_series_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ +#ifdef DEBUG_DIO + printk("ni_m_series_dio_insn_config() chan=%d io=%d\n", + CR_CHAN(insn->chanspec), data[0]); +#endif + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << CR_CHAN(insn->chanspec); + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << CR_CHAN(insn->chanspec)); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s-> + io_bits & (1 << CR_CHAN(insn->chanspec))) ? COMEDI_OUTPUT : + COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + } + + ni_writel(s->io_bits, M_Offset_DIO_Direction); + + return 1; +} + +static int ni_m_series_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ +#ifdef DEBUG_DIO + printk("ni_m_series_dio_insn_bits() mask=0x%x bits=0x%x\n", data[0], + data[1]); +#endif + if (insn->n != 2) + return -EINVAL; + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + ni_writel(s->state, M_Offset_Static_Digital_Output); + } + data[1] = ni_readl(M_Offset_Static_Digital_Input); + + return 2; +} + +static int ni_cdio_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + int sources; + unsigned i; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + sources = TRIG_INT; + cmd->start_src &= sources; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique... */ + + if (cmd->start_src != TRIG_INT) + err++; + if (cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_NOW) + err++; + if (cmd->stop_src != TRIG_NONE) + err++; + /* ... and mutually compatible */ + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + if (cmd->start_src == TRIG_INT) { + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + } + if (cmd->scan_begin_src == TRIG_EXT) { + tmp = cmd->scan_begin_arg; + tmp &= CR_PACK_FLAGS(CDO_Sample_Source_Select_Mask, 0, 0, + CR_INVERT); + if (tmp != cmd->scan_begin_arg) { + err++; + } + } + if (cmd->convert_src == TRIG_NOW) { + if (cmd->convert_arg) { + cmd->convert_arg = 0; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_NONE) { + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (err) + return 4; + + /* step 5: check chanlist */ + + for (i = 0; i < cmd->chanlist_len; ++i) { + if (cmd->chanlist[i] != i) + err = 1; + } + + if (err) + return 5; + + return 0; +} + +static int ni_cdio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + const struct comedi_cmd *cmd = &s->async->cmd; + unsigned cdo_mode_bits = CDO_FIFO_Mode_Bit | CDO_Halt_On_Error_Bit; + int retval; + + ni_writel(CDO_Reset_Bit, M_Offset_CDIO_Command); + switch (cmd->scan_begin_src) { + case TRIG_EXT: + cdo_mode_bits |= + CR_CHAN(cmd->scan_begin_arg) & + CDO_Sample_Source_Select_Mask; + break; + default: + BUG(); + break; + } + if (cmd->scan_begin_arg & CR_INVERT) + cdo_mode_bits |= CDO_Polarity_Bit; + ni_writel(cdo_mode_bits, M_Offset_CDO_Mode); + if (s->io_bits) { + ni_writel(s->state, M_Offset_CDO_FIFO_Data); + ni_writel(CDO_SW_Update_Bit, M_Offset_CDIO_Command); + ni_writel(s->io_bits, M_Offset_CDO_Mask_Enable); + } else { + comedi_error(dev, + "attempted to run digital output command with no lines configured as outputs"); + return -EIO; + } + retval = ni_request_cdo_mite_channel(dev); + if (retval < 0) { + return retval; + } + s->async->inttrig = &ni_cdo_inttrig; + return 0; +} + +static int ni_cdo_inttrig(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum) +{ +#ifdef PCIDMA + unsigned long flags; +#endif + int retval = 0; + unsigned i; + const unsigned timeout = 1000; + + s->async->inttrig = NULL; + + /* read alloc the entire buffer */ + comedi_buf_read_alloc(s->async, s->async->prealloc_bufsz); + +#ifdef PCIDMA + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->cdo_mite_chan) { + mite_prep_dma(devpriv->cdo_mite_chan, 32, 32); + mite_dma_arm(devpriv->cdo_mite_chan); + } else { + comedi_error(dev, "BUG: no cdo mite channel?"); + retval = -EIO; + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + if (retval < 0) + return retval; +#endif +/* +* XXX not sure what interrupt C group does +* ni_writeb(Interrupt_Group_C_Enable_Bit, +* M_Offset_Interrupt_C_Enable); wait for dma to fill output fifo +*/ + for (i = 0; i < timeout; ++i) { + if (ni_readl(M_Offset_CDIO_Status) & CDO_FIFO_Full_Bit) + break; + udelay(10); + } + if (i == timeout) { + comedi_error(dev, "dma failed to fill cdo fifo!"); + ni_cdio_cancel(dev, s); + return -EIO; + } + ni_writel(CDO_Arm_Bit | CDO_Error_Interrupt_Enable_Set_Bit | + CDO_Empty_FIFO_Interrupt_Enable_Set_Bit, + M_Offset_CDIO_Command); + return retval; +} + +static int ni_cdio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + ni_writel(CDO_Disarm_Bit | CDO_Error_Interrupt_Enable_Clear_Bit | + CDO_Empty_FIFO_Interrupt_Enable_Clear_Bit | + CDO_FIFO_Request_Interrupt_Enable_Clear_Bit, + M_Offset_CDIO_Command); +/* +* XXX not sure what interrupt C group does ni_writeb(0, +* M_Offset_Interrupt_C_Enable); +*/ + ni_writel(0, M_Offset_CDO_Mask_Enable); + ni_release_cdo_mite_channel(dev); + return 0; +} + +static void handle_cdio_interrupt(struct comedi_device *dev) +{ + unsigned cdio_status; + struct comedi_subdevice *s = dev->subdevices + NI_DIO_SUBDEV; +#ifdef PCIDMA + unsigned long flags; +#endif + + if ((boardtype.reg_type & ni_reg_m_series_mask) == 0) { + return; + } +#ifdef PCIDMA + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->cdo_mite_chan) { + unsigned cdo_mite_status = + mite_get_status(devpriv->cdo_mite_chan); + if (cdo_mite_status & CHSR_LINKC) { + writel(CHOR_CLRLC, + devpriv->mite->mite_io_addr + + MITE_CHOR(devpriv->cdo_mite_chan->channel)); + } + mite_sync_output_dma(devpriv->cdo_mite_chan, s->async); + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +#endif + + cdio_status = ni_readl(M_Offset_CDIO_Status); + if (cdio_status & (CDO_Overrun_Bit | CDO_Underflow_Bit)) { +/* printk("cdio error: statux=0x%x\n", cdio_status); */ + ni_writel(CDO_Error_Interrupt_Confirm_Bit, M_Offset_CDIO_Command); /* XXX just guessing this is needed and does something useful */ + s->async->events |= COMEDI_CB_OVERFLOW; + } + if (cdio_status & CDO_FIFO_Empty_Bit) { +/* printk("cdio fifo empty\n"); */ + ni_writel(CDO_Empty_FIFO_Interrupt_Enable_Clear_Bit, + M_Offset_CDIO_Command); +/* s->async->events |= COMEDI_CB_EOA; */ + } + ni_event(dev, s); +} + +static int ni_serial_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int err = insn->n; + unsigned char byte_out, byte_in = 0; + + if (insn->n != 2) + return -EINVAL; + + switch (data[0]) { + case INSN_CONFIG_SERIAL_CLOCK: + +#ifdef DEBUG_DIO + printk("SPI serial clock Config cd\n", data[1]); +#endif + devpriv->serial_hw_mode = 1; + devpriv->dio_control |= DIO_HW_Serial_Enable; + + if (data[1] == SERIAL_DISABLED) { + devpriv->serial_hw_mode = 0; + devpriv->dio_control &= ~(DIO_HW_Serial_Enable | + DIO_Software_Serial_Control); + data[1] = SERIAL_DISABLED; + devpriv->serial_interval_ns = data[1]; + } else if (data[1] <= SERIAL_600NS) { + /* Warning: this clock speed is too fast to reliably + control SCXI. */ + devpriv->dio_control &= ~DIO_HW_Serial_Timebase; + devpriv->clock_and_fout |= Slow_Internal_Timebase; + devpriv->clock_and_fout &= ~DIO_Serial_Out_Divide_By_2; + data[1] = SERIAL_600NS; + devpriv->serial_interval_ns = data[1]; + } else if (data[1] <= SERIAL_1_2US) { + devpriv->dio_control &= ~DIO_HW_Serial_Timebase; + devpriv->clock_and_fout |= Slow_Internal_Timebase | + DIO_Serial_Out_Divide_By_2; + data[1] = SERIAL_1_2US; + devpriv->serial_interval_ns = data[1]; + } else if (data[1] <= SERIAL_10US) { + devpriv->dio_control |= DIO_HW_Serial_Timebase; + devpriv->clock_and_fout |= Slow_Internal_Timebase | + DIO_Serial_Out_Divide_By_2; + /* Note: DIO_Serial_Out_Divide_By_2 only affects + 600ns/1.2us. If you turn divide_by_2 off with the + slow clock, you will still get 10us, except then + all your delays are wrong. */ + data[1] = SERIAL_10US; + devpriv->serial_interval_ns = data[1]; + } else { + devpriv->dio_control &= ~(DIO_HW_Serial_Enable | + DIO_Software_Serial_Control); + devpriv->serial_hw_mode = 0; + data[1] = (data[1] / 1000) * 1000; + devpriv->serial_interval_ns = data[1]; + } + + devpriv->stc_writew(dev, devpriv->dio_control, + DIO_Control_Register); + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + return 1; + + break; + + case INSN_CONFIG_BIDIRECTIONAL_DATA: + + if (devpriv->serial_interval_ns == 0) { + return -EINVAL; + } + + byte_out = data[1] & 0xFF; + + if (devpriv->serial_hw_mode) { + err = ni_serial_hw_readwrite8(dev, s, byte_out, + &byte_in); + } else if (devpriv->serial_interval_ns > 0) { + err = ni_serial_sw_readwrite8(dev, s, byte_out, + &byte_in); + } else { + printk("ni_serial_insn_config: serial disabled!\n"); + return -EINVAL; + } + if (err < 0) + return err; + data[1] = byte_in & 0xFF; + return insn->n; + + break; + default: + return -EINVAL; + } + +} + +static int ni_serial_hw_readwrite8(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned char data_out, + unsigned char *data_in) +{ + unsigned int status1; + int err = 0, count = 20; + +#ifdef DEBUG_DIO + printk("ni_serial_hw_readwrite8: outputting 0x%x\n", data_out); +#endif + + devpriv->dio_output &= ~DIO_Serial_Data_Mask; + devpriv->dio_output |= DIO_Serial_Data_Out(data_out); + devpriv->stc_writew(dev, devpriv->dio_output, DIO_Output_Register); + + status1 = devpriv->stc_readw(dev, Joint_Status_1_Register); + if (status1 & DIO_Serial_IO_In_Progress_St) { + err = -EBUSY; + goto Error; + } + + devpriv->dio_control |= DIO_HW_Serial_Start; + devpriv->stc_writew(dev, devpriv->dio_control, DIO_Control_Register); + devpriv->dio_control &= ~DIO_HW_Serial_Start; + + /* Wait until STC says we're done, but don't loop infinitely. */ + while ((status1 = + devpriv->stc_readw(dev, + Joint_Status_1_Register)) & + DIO_Serial_IO_In_Progress_St) { + /* Delay one bit per loop */ + udelay((devpriv->serial_interval_ns + 999) / 1000); + if (--count < 0) { + printk + ("ni_serial_hw_readwrite8: SPI serial I/O didn't finish in time!\n"); + err = -ETIME; + goto Error; + } + } + + /* Delay for last bit. This delay is absolutely necessary, because + DIO_Serial_IO_In_Progress_St goes high one bit too early. */ + udelay((devpriv->serial_interval_ns + 999) / 1000); + + if (data_in != NULL) { + *data_in = devpriv->stc_readw(dev, DIO_Serial_Input_Register); +#ifdef DEBUG_DIO + printk("ni_serial_hw_readwrite8: inputted 0x%x\n", *data_in); +#endif + } + +Error: + devpriv->stc_writew(dev, devpriv->dio_control, DIO_Control_Register); + + return err; +} + +static int ni_serial_sw_readwrite8(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned char data_out, + unsigned char *data_in) +{ + unsigned char mask, input = 0; + +#ifdef DEBUG_DIO + printk("ni_serial_sw_readwrite8: outputting 0x%x\n", data_out); +#endif + + /* Wait for one bit before transfer */ + udelay((devpriv->serial_interval_ns + 999) / 1000); + + for (mask = 0x80; mask; mask >>= 1) { + /* Output current bit; note that we cannot touch s->state + because it is a per-subdevice field, and serial is + a separate subdevice from DIO. */ + devpriv->dio_output &= ~DIO_SDOUT; + if (data_out & mask) { + devpriv->dio_output |= DIO_SDOUT; + } + devpriv->stc_writew(dev, devpriv->dio_output, + DIO_Output_Register); + + /* Assert SDCLK (active low, inverted), wait for half of + the delay, deassert SDCLK, and wait for the other half. */ + devpriv->dio_control |= DIO_Software_Serial_Control; + devpriv->stc_writew(dev, devpriv->dio_control, + DIO_Control_Register); + + udelay((devpriv->serial_interval_ns + 999) / 2000); + + devpriv->dio_control &= ~DIO_Software_Serial_Control; + devpriv->stc_writew(dev, devpriv->dio_control, + DIO_Control_Register); + + udelay((devpriv->serial_interval_ns + 999) / 2000); + + /* Input current bit */ + if (devpriv->stc_readw(dev, + DIO_Parallel_Input_Register) & DIO_SDIN) + { +/* printk("DIO_P_I_R: 0x%x\n", devpriv->stc_readw(dev, DIO_Parallel_Input_Register)); */ + input |= mask; + } + } +#ifdef DEBUG_DIO + printk("ni_serial_sw_readwrite8: inputted 0x%x\n", input); +#endif + if (data_in) + *data_in = input; + + return 0; +} + +static void mio_common_detach(struct comedi_device *dev) +{ + if (dev->private) { + if (devpriv->counter_dev) { + ni_gpct_device_destroy(devpriv->counter_dev); + } + } + if (dev->subdevices && boardtype.has_8255) + subdev_8255_cleanup(dev, dev->subdevices + NI_8255_DIO_SUBDEV); +} + +static void init_ao_67xx(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int i; + + for (i = 0; i < s->n_chan; i++) { + ni_ao_win_outw(dev, AO_Channel(i) | 0x0, + AO_Configuration_2_67xx); + } + ao_win_out(0x0, AO_Later_Single_Point_Updates); +} + +static unsigned ni_gpct_to_stc_register(enum ni_gpct_register reg) +{ + unsigned stc_register; + switch (reg) { + case NITIO_G0_Autoincrement_Reg: + stc_register = G_Autoincrement_Register(0); + break; + case NITIO_G1_Autoincrement_Reg: + stc_register = G_Autoincrement_Register(1); + break; + case NITIO_G0_Command_Reg: + stc_register = G_Command_Register(0); + break; + case NITIO_G1_Command_Reg: + stc_register = G_Command_Register(1); + break; + case NITIO_G0_HW_Save_Reg: + stc_register = G_HW_Save_Register(0); + break; + case NITIO_G1_HW_Save_Reg: + stc_register = G_HW_Save_Register(1); + break; + case NITIO_G0_SW_Save_Reg: + stc_register = G_Save_Register(0); + break; + case NITIO_G1_SW_Save_Reg: + stc_register = G_Save_Register(1); + break; + case NITIO_G0_Mode_Reg: + stc_register = G_Mode_Register(0); + break; + case NITIO_G1_Mode_Reg: + stc_register = G_Mode_Register(1); + break; + case NITIO_G0_LoadA_Reg: + stc_register = G_Load_A_Register(0); + break; + case NITIO_G1_LoadA_Reg: + stc_register = G_Load_A_Register(1); + break; + case NITIO_G0_LoadB_Reg: + stc_register = G_Load_B_Register(0); + break; + case NITIO_G1_LoadB_Reg: + stc_register = G_Load_B_Register(1); + break; + case NITIO_G0_Input_Select_Reg: + stc_register = G_Input_Select_Register(0); + break; + case NITIO_G1_Input_Select_Reg: + stc_register = G_Input_Select_Register(1); + break; + case NITIO_G01_Status_Reg: + stc_register = G_Status_Register; + break; + case NITIO_G01_Joint_Reset_Reg: + stc_register = Joint_Reset_Register; + break; + case NITIO_G01_Joint_Status1_Reg: + stc_register = Joint_Status_1_Register; + break; + case NITIO_G01_Joint_Status2_Reg: + stc_register = Joint_Status_2_Register; + break; + case NITIO_G0_Interrupt_Acknowledge_Reg: + stc_register = Interrupt_A_Ack_Register; + break; + case NITIO_G1_Interrupt_Acknowledge_Reg: + stc_register = Interrupt_B_Ack_Register; + break; + case NITIO_G0_Status_Reg: + stc_register = AI_Status_1_Register; + break; + case NITIO_G1_Status_Reg: + stc_register = AO_Status_1_Register; + break; + case NITIO_G0_Interrupt_Enable_Reg: + stc_register = Interrupt_A_Enable_Register; + break; + case NITIO_G1_Interrupt_Enable_Reg: + stc_register = Interrupt_B_Enable_Register; + break; + default: + printk("%s: unhandled register 0x%x in switch.\n", + __func__, reg); + BUG(); + return 0; + break; + } + return stc_register; +} + +static void ni_gpct_write_register(struct ni_gpct *counter, unsigned bits, + enum ni_gpct_register reg) +{ + struct comedi_device *dev = counter->counter_dev->dev; + unsigned stc_register; + /* bits in the join reset register which are relevant to counters */ + static const unsigned gpct_joint_reset_mask = G0_Reset | G1_Reset; + static const unsigned gpct_interrupt_a_enable_mask = + G0_Gate_Interrupt_Enable | G0_TC_Interrupt_Enable; + static const unsigned gpct_interrupt_b_enable_mask = + G1_Gate_Interrupt_Enable | G1_TC_Interrupt_Enable; + + switch (reg) { + /* m-series-only registers */ + case NITIO_G0_Counting_Mode_Reg: + ni_writew(bits, M_Offset_G0_Counting_Mode); + break; + case NITIO_G1_Counting_Mode_Reg: + ni_writew(bits, M_Offset_G1_Counting_Mode); + break; + case NITIO_G0_Second_Gate_Reg: + ni_writew(bits, M_Offset_G0_Second_Gate); + break; + case NITIO_G1_Second_Gate_Reg: + ni_writew(bits, M_Offset_G1_Second_Gate); + break; + case NITIO_G0_DMA_Config_Reg: + ni_writew(bits, M_Offset_G0_DMA_Config); + break; + case NITIO_G1_DMA_Config_Reg: + ni_writew(bits, M_Offset_G1_DMA_Config); + break; + case NITIO_G0_ABZ_Reg: + ni_writew(bits, M_Offset_G0_MSeries_ABZ); + break; + case NITIO_G1_ABZ_Reg: + ni_writew(bits, M_Offset_G1_MSeries_ABZ); + break; + + /* 32 bit registers */ + case NITIO_G0_LoadA_Reg: + case NITIO_G1_LoadA_Reg: + case NITIO_G0_LoadB_Reg: + case NITIO_G1_LoadB_Reg: + stc_register = ni_gpct_to_stc_register(reg); + devpriv->stc_writel(dev, bits, stc_register); + break; + + /* 16 bit registers */ + case NITIO_G0_Interrupt_Enable_Reg: + BUG_ON(bits & ~gpct_interrupt_a_enable_mask); + ni_set_bitfield(dev, Interrupt_A_Enable_Register, + gpct_interrupt_a_enable_mask, bits); + break; + case NITIO_G1_Interrupt_Enable_Reg: + BUG_ON(bits & ~gpct_interrupt_b_enable_mask); + ni_set_bitfield(dev, Interrupt_B_Enable_Register, + gpct_interrupt_b_enable_mask, bits); + break; + case NITIO_G01_Joint_Reset_Reg: + BUG_ON(bits & ~gpct_joint_reset_mask); + /* fall-through */ + default: + stc_register = ni_gpct_to_stc_register(reg); + devpriv->stc_writew(dev, bits, stc_register); + } +} + +static unsigned ni_gpct_read_register(struct ni_gpct *counter, + enum ni_gpct_register reg) +{ + struct comedi_device *dev = counter->counter_dev->dev; + unsigned stc_register; + switch (reg) { + /* m-series only registers */ + case NITIO_G0_DMA_Status_Reg: + return ni_readw(M_Offset_G0_DMA_Status); + break; + case NITIO_G1_DMA_Status_Reg: + return ni_readw(M_Offset_G1_DMA_Status); + break; + + /* 32 bit registers */ + case NITIO_G0_HW_Save_Reg: + case NITIO_G1_HW_Save_Reg: + case NITIO_G0_SW_Save_Reg: + case NITIO_G1_SW_Save_Reg: + stc_register = ni_gpct_to_stc_register(reg); + return devpriv->stc_readl(dev, stc_register); + break; + + /* 16 bit registers */ + default: + stc_register = ni_gpct_to_stc_register(reg); + return devpriv->stc_readw(dev, stc_register); + break; + } + return 0; +} + +static int ni_freq_out_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = devpriv->clock_and_fout & FOUT_Divider_mask; + return 1; +} + +static int ni_freq_out_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + devpriv->clock_and_fout &= ~FOUT_Enable; + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + devpriv->clock_and_fout &= ~FOUT_Divider_mask; + devpriv->clock_and_fout |= FOUT_Divider(data[0]); + devpriv->clock_and_fout |= FOUT_Enable; + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + return insn->n; +} + +static int ni_set_freq_out_clock(struct comedi_device *dev, + unsigned int clock_source) +{ + switch (clock_source) { + case NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC: + devpriv->clock_and_fout &= ~FOUT_Timebase_Select; + break; + case NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC: + devpriv->clock_and_fout |= FOUT_Timebase_Select; + break; + default: + return -EINVAL; + } + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + return 3; +} + +static void ni_get_freq_out_clock(struct comedi_device *dev, + unsigned int *clock_source, + unsigned int *clock_period_ns) +{ + if (devpriv->clock_and_fout & FOUT_Timebase_Select) { + *clock_source = NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC; + *clock_period_ns = TIMEBASE_2_NS; + } else { + *clock_source = NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC; + *clock_period_ns = TIMEBASE_1_NS * 2; + } +} + +static int ni_freq_out_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + switch (data[0]) { + case INSN_CONFIG_SET_CLOCK_SRC: + return ni_set_freq_out_clock(dev, data[1]); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + ni_get_freq_out_clock(dev, &data[1], &data[2]); + return 3; + default: + break; + } + return -EINVAL; +} + +static int ni_alloc_private(struct comedi_device *dev) +{ + int ret; + + ret = alloc_private(dev, sizeof(struct ni_private)); + if (ret < 0) + return ret; + + spin_lock_init(&devpriv->window_lock); + spin_lock_init(&devpriv->soft_reg_copy_lock); + spin_lock_init(&devpriv->mite_channel_lock); + + return 0; +}; + +static int ni_E_init(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned j; + enum ni_gpct_variant counter_variant; + + if (boardtype.n_aochan > MAX_N_AO_CHAN) { + printk("bug! boardtype.n_aochan > MAX_N_AO_CHAN\n"); + return -EINVAL; + } + + if (alloc_subdevices(dev, NI_NUM_SUBDEVICES) < 0) + return -ENOMEM; + + /* analog input subdevice */ + + s = dev->subdevices + NI_AI_SUBDEV; + dev->read_subdev = s; + if (boardtype.n_adchan) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_DIFF | SDF_DITHER | SDF_CMD_READ; + if (boardtype.reg_type != ni_reg_611x) + s->subdev_flags |= SDF_GROUND | SDF_COMMON | SDF_OTHER; + if (boardtype.adbits > 16) + s->subdev_flags |= SDF_LSAMPL; + if (boardtype.reg_type & ni_reg_m_series_mask) + s->subdev_flags |= SDF_SOFT_CALIBRATED; + s->n_chan = boardtype.n_adchan; + s->len_chanlist = 512; + s->maxdata = (1 << boardtype.adbits) - 1; + s->range_table = ni_range_lkup[boardtype.gainlkup]; + s->insn_read = &ni_ai_insn_read; + s->insn_config = &ni_ai_insn_config; + s->do_cmdtest = &ni_ai_cmdtest; + s->do_cmd = &ni_ai_cmd; + s->cancel = &ni_ai_reset; + s->poll = &ni_ai_poll; + s->munge = &ni_ai_munge; +#ifdef PCIDMA + s->async_dma_dir = DMA_FROM_DEVICE; +#endif + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* analog output subdevice */ + + s = dev->subdevices + NI_AO_SUBDEV; + if (boardtype.n_aochan) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_DEGLITCH | SDF_GROUND; + if (boardtype.reg_type & ni_reg_m_series_mask) + s->subdev_flags |= SDF_SOFT_CALIBRATED; + s->n_chan = boardtype.n_aochan; + s->maxdata = (1 << boardtype.aobits) - 1; + s->range_table = boardtype.ao_range_table; + s->insn_read = &ni_ao_insn_read; + if (boardtype.reg_type & ni_reg_6xxx_mask) { + s->insn_write = &ni_ao_insn_write_671x; + } else { + s->insn_write = &ni_ao_insn_write; + } + s->insn_config = &ni_ao_insn_config; +#ifdef PCIDMA + if (boardtype.n_aochan) { + s->async_dma_dir = DMA_TO_DEVICE; +#else + if (boardtype.ao_fifo_depth) { +#endif + dev->write_subdev = s; + s->subdev_flags |= SDF_CMD_WRITE; + s->do_cmd = &ni_ao_cmd; + s->do_cmdtest = &ni_ao_cmdtest; + s->len_chanlist = boardtype.n_aochan; + if ((boardtype.reg_type & ni_reg_m_series_mask) == 0) + s->munge = ni_ao_munge; + } + s->cancel = &ni_ao_reset; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + if ((boardtype.reg_type & ni_reg_67xx_mask)) + init_ao_67xx(dev, s); + + /* digital i/o subdevice */ + + s = dev->subdevices + NI_DIO_SUBDEV; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->maxdata = 1; + s->io_bits = 0; /* all bits input */ + s->range_table = &range_digital; + s->n_chan = boardtype.num_p0_dio_channels; + if (boardtype.reg_type & ni_reg_m_series_mask) { + s->subdev_flags |= + SDF_LSAMPL | SDF_CMD_WRITE /* | SDF_CMD_READ */ ; + s->insn_bits = &ni_m_series_dio_insn_bits; + s->insn_config = &ni_m_series_dio_insn_config; + s->do_cmd = &ni_cdio_cmd; + s->do_cmdtest = &ni_cdio_cmdtest; + s->cancel = &ni_cdio_cancel; + s->async_dma_dir = DMA_BIDIRECTIONAL; + s->len_chanlist = s->n_chan; + + ni_writel(CDO_Reset_Bit | CDI_Reset_Bit, M_Offset_CDIO_Command); + ni_writel(s->io_bits, M_Offset_DIO_Direction); + } else { + s->insn_bits = &ni_dio_insn_bits; + s->insn_config = &ni_dio_insn_config; + devpriv->dio_control = DIO_Pins_Dir(s->io_bits); + ni_writew(devpriv->dio_control, DIO_Control_Register); + } + + /* 8255 device */ + s = dev->subdevices + NI_8255_DIO_SUBDEV; + if (boardtype.has_8255) { + subdev_8255_init(dev, s, ni_8255_callback, (unsigned long)dev); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* formerly general purpose counter/timer device, but no longer used */ + s = dev->subdevices + NI_UNUSED_SUBDEV; + s->type = COMEDI_SUBD_UNUSED; + + /* calibration subdevice -- ai and ao */ + s = dev->subdevices + NI_CALIBRATION_SUBDEV; + s->type = COMEDI_SUBD_CALIB; + if (boardtype.reg_type & ni_reg_m_series_mask) { + /* internal PWM analog output used for AI nonlinearity calibration */ + s->subdev_flags = SDF_INTERNAL; + s->insn_config = &ni_m_series_pwm_config; + s->n_chan = 1; + s->maxdata = 0; + ni_writel(0x0, M_Offset_Cal_PWM); + } else if (boardtype.reg_type == ni_reg_6143) { + /* internal PWM analog output used for AI nonlinearity calibration */ + s->subdev_flags = SDF_INTERNAL; + s->insn_config = &ni_6143_pwm_config; + s->n_chan = 1; + s->maxdata = 0; + } else { + s->subdev_flags = SDF_WRITABLE | SDF_INTERNAL; + s->insn_read = &ni_calib_insn_read; + s->insn_write = &ni_calib_insn_write; + caldac_setup(dev, s); + } + + /* EEPROM */ + s = dev->subdevices + NI_EEPROM_SUBDEV; + s->type = COMEDI_SUBD_MEMORY; + s->subdev_flags = SDF_READABLE | SDF_INTERNAL; + s->maxdata = 0xff; + if (boardtype.reg_type & ni_reg_m_series_mask) { + s->n_chan = M_SERIES_EEPROM_SIZE; + s->insn_read = &ni_m_series_eeprom_insn_read; + } else { + s->n_chan = 512; + s->insn_read = &ni_eeprom_insn_read; + } + + /* PFI */ + s = dev->subdevices + NI_PFI_DIO_SUBDEV; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + if (boardtype.reg_type & ni_reg_m_series_mask) { + unsigned i; + s->n_chan = 16; + ni_writew(s->state, M_Offset_PFI_DO); + for (i = 0; i < NUM_PFI_OUTPUT_SELECT_REGS; ++i) { + ni_writew(devpriv->pfi_output_select_reg[i], + M_Offset_PFI_Output_Select(i + 1)); + } + } else { + s->n_chan = 10; + } + s->maxdata = 1; + if (boardtype.reg_type & ni_reg_m_series_mask) { + s->insn_bits = &ni_pfi_insn_bits; + } + s->insn_config = &ni_pfi_insn_config; + ni_set_bits(dev, IO_Bidirection_Pin_Register, ~0, 0); + + /* cs5529 calibration adc */ + s = dev->subdevices + NI_CS5529_CALIBRATION_SUBDEV; + if (boardtype.reg_type & ni_reg_67xx_mask) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_INTERNAL; + /* one channel for each analog output channel */ + s->n_chan = boardtype.n_aochan; + s->maxdata = (1 << 16) - 1; + s->range_table = &range_unknown; /* XXX */ + s->insn_read = cs5529_ai_insn_read; + s->insn_config = NULL; + init_cs5529(dev); + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* Serial */ + s = dev->subdevices + NI_SERIAL_SUBDEV; + s->type = COMEDI_SUBD_SERIAL; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 1; + s->maxdata = 0xff; + s->insn_config = ni_serial_insn_config; + devpriv->serial_interval_ns = 0; + devpriv->serial_hw_mode = 0; + + /* RTSI */ + s = dev->subdevices + NI_RTSI_SUBDEV; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 8; + s->maxdata = 1; + s->insn_bits = ni_rtsi_insn_bits; + s->insn_config = ni_rtsi_insn_config; + ni_rtsi_init(dev); + + if (boardtype.reg_type & ni_reg_m_series_mask) { + counter_variant = ni_gpct_variant_m_series; + } else { + counter_variant = ni_gpct_variant_e_series; + } + devpriv->counter_dev = ni_gpct_device_construct(dev, + &ni_gpct_write_register, + &ni_gpct_read_register, + counter_variant, + NUM_GPCT); + /* General purpose counters */ + for (j = 0; j < NUM_GPCT; ++j) { + s = dev->subdevices + NI_GPCT_SUBDEV(j); + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = + SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL | SDF_CMD_READ + /* | SDF_CMD_WRITE */ ; + s->n_chan = 3; + if (boardtype.reg_type & ni_reg_m_series_mask) + s->maxdata = 0xffffffff; + else + s->maxdata = 0xffffff; + s->insn_read = &ni_gpct_insn_read; + s->insn_write = &ni_gpct_insn_write; + s->insn_config = &ni_gpct_insn_config; + s->do_cmd = &ni_gpct_cmd; + s->len_chanlist = 1; + s->do_cmdtest = &ni_gpct_cmdtest; + s->cancel = &ni_gpct_cancel; + s->async_dma_dir = DMA_BIDIRECTIONAL; + s->private = &devpriv->counter_dev->counters[j]; + + devpriv->counter_dev->counters[j].chip_index = 0; + devpriv->counter_dev->counters[j].counter_index = j; + ni_tio_init_counter(&devpriv->counter_dev->counters[j]); + } + + /* Frequency output */ + s = dev->subdevices + NI_FREQ_OUT_SUBDEV; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 1; + s->maxdata = 0xf; + s->insn_read = &ni_freq_out_insn_read; + s->insn_write = &ni_freq_out_insn_write; + s->insn_config = &ni_freq_out_insn_config; + + /* ai configuration */ + ni_ai_reset(dev, dev->subdevices + NI_AI_SUBDEV); + if ((boardtype.reg_type & ni_reg_6xxx_mask) == 0) { + /* BEAM is this needed for PCI-6143 ?? */ + devpriv->clock_and_fout = + Slow_Internal_Time_Divide_By_2 | + Slow_Internal_Timebase | + Clock_To_Board_Divide_By_2 | + Clock_To_Board | + AI_Output_Divide_By_2 | AO_Output_Divide_By_2; + } else { + devpriv->clock_and_fout = + Slow_Internal_Time_Divide_By_2 | + Slow_Internal_Timebase | + Clock_To_Board_Divide_By_2 | Clock_To_Board; + } + devpriv->stc_writew(dev, devpriv->clock_and_fout, + Clock_and_FOUT_Register); + + /* analog output configuration */ + ni_ao_reset(dev, dev->subdevices + NI_AO_SUBDEV); + + if (dev->irq) { + devpriv->stc_writew(dev, + (IRQ_POLARITY ? Interrupt_Output_Polarity : + 0) | (Interrupt_Output_On_3_Pins & 0) | + Interrupt_A_Enable | Interrupt_B_Enable | + Interrupt_A_Output_Select(interrupt_pin + (dev->irq)) | + Interrupt_B_Output_Select(interrupt_pin + (dev->irq)), + Interrupt_Control_Register); + } + + /* DMA setup */ + ni_writeb(devpriv->ai_ao_select_reg, AI_AO_Select); + ni_writeb(devpriv->g0_g1_select_reg, G0_G1_Select); + + if (boardtype.reg_type & ni_reg_6xxx_mask) { + ni_writeb(0, Magic_611x); + } else if (boardtype.reg_type & ni_reg_m_series_mask) { + int channel; + for (channel = 0; channel < boardtype.n_aochan; ++channel) { + ni_writeb(0xf, M_Offset_AO_Waveform_Order(channel)); + ni_writeb(0x0, + M_Offset_AO_Reference_Attenuation(channel)); + } + ni_writeb(0x0, M_Offset_AO_Calibration); + } + + printk("\n"); + return 0; +} + +static int ni_8255_callback(int dir, int port, int data, unsigned long arg) +{ + struct comedi_device *dev = (struct comedi_device *)arg; + + if (dir) { + ni_writeb(data, Port_A + 2 * port); + return 0; + } else { + return ni_readb(Port_A + 2 * port); + } +} + +/* + presents the EEPROM as a subdevice +*/ + +static int ni_eeprom_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = ni_read_eeprom(dev, CR_CHAN(insn->chanspec)); + + return 1; +} + +/* + reads bytes out of eeprom +*/ + +static int ni_read_eeprom(struct comedi_device *dev, int addr) +{ + int bit; + int bitstring; + + bitstring = 0x0300 | ((addr & 0x100) << 3) | (addr & 0xff); + ni_writeb(0x04, Serial_Command); + for (bit = 0x8000; bit; bit >>= 1) { + ni_writeb(0x04 | ((bit & bitstring) ? 0x02 : 0), + Serial_Command); + ni_writeb(0x05 | ((bit & bitstring) ? 0x02 : 0), + Serial_Command); + } + bitstring = 0; + for (bit = 0x80; bit; bit >>= 1) { + ni_writeb(0x04, Serial_Command); + ni_writeb(0x05, Serial_Command); + bitstring |= ((ni_readb(XXX_Status) & PROMOUT) ? bit : 0); + } + ni_writeb(0x00, Serial_Command); + + return bitstring; +} + +static int ni_m_series_eeprom_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + data[0] = devpriv->eeprom_buffer[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int ni_get_pwm_config(struct comedi_device *dev, unsigned int *data) +{ + data[1] = devpriv->pwm_up_count * devpriv->clock_ns; + data[2] = devpriv->pwm_down_count * devpriv->clock_ns; + return 3; +} + +static int ni_m_series_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned up_count, down_count; + switch (data[0]) { + case INSN_CONFIG_PWM_OUTPUT: + switch (data[1]) { + case TRIG_ROUND_NEAREST: + up_count = + (data[2] + + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + up_count = data[2] / devpriv->clock_ns; + break; + case TRIG_ROUND_UP: + up_count = + (data[2] + devpriv->clock_ns - + 1) / devpriv->clock_ns; + break; + default: + return -EINVAL; + break; + } + switch (data[3]) { + case TRIG_ROUND_NEAREST: + down_count = + (data[4] + + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + down_count = data[4] / devpriv->clock_ns; + break; + case TRIG_ROUND_UP: + down_count = + (data[4] + devpriv->clock_ns - + 1) / devpriv->clock_ns; + break; + default: + return -EINVAL; + break; + } + if (up_count * devpriv->clock_ns != data[2] || + down_count * devpriv->clock_ns != data[4]) { + data[2] = up_count * devpriv->clock_ns; + data[4] = down_count * devpriv->clock_ns; + return -EAGAIN; + } + ni_writel(MSeries_Cal_PWM_High_Time_Bits(up_count) | + MSeries_Cal_PWM_Low_Time_Bits(down_count), + M_Offset_Cal_PWM); + devpriv->pwm_up_count = up_count; + devpriv->pwm_down_count = down_count; + return 5; + break; + case INSN_CONFIG_GET_PWM_OUTPUT: + return ni_get_pwm_config(dev, data); + break; + default: + return -EINVAL; + break; + } + return 0; +} + +static int ni_6143_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned up_count, down_count; + switch (data[0]) { + case INSN_CONFIG_PWM_OUTPUT: + switch (data[1]) { + case TRIG_ROUND_NEAREST: + up_count = + (data[2] + + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + up_count = data[2] / devpriv->clock_ns; + break; + case TRIG_ROUND_UP: + up_count = + (data[2] + devpriv->clock_ns - + 1) / devpriv->clock_ns; + break; + default: + return -EINVAL; + break; + } + switch (data[3]) { + case TRIG_ROUND_NEAREST: + down_count = + (data[4] + + devpriv->clock_ns / 2) / devpriv->clock_ns; + break; + case TRIG_ROUND_DOWN: + down_count = data[4] / devpriv->clock_ns; + break; + case TRIG_ROUND_UP: + down_count = + (data[4] + devpriv->clock_ns - + 1) / devpriv->clock_ns; + break; + default: + return -EINVAL; + break; + } + if (up_count * devpriv->clock_ns != data[2] || + down_count * devpriv->clock_ns != data[4]) { + data[2] = up_count * devpriv->clock_ns; + data[4] = down_count * devpriv->clock_ns; + return -EAGAIN; + } + ni_writel(up_count, Calibration_HighTime_6143); + devpriv->pwm_up_count = up_count; + ni_writel(down_count, Calibration_LowTime_6143); + devpriv->pwm_down_count = down_count; + return 5; + break; + case INSN_CONFIG_GET_PWM_OUTPUT: + return ni_get_pwm_config(dev, data); + default: + return -EINVAL; + break; + } + return 0; +} + +static void ni_write_caldac(struct comedi_device *dev, int addr, int val); +/* + calibration subdevice +*/ +static int ni_calib_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + ni_write_caldac(dev, CR_CHAN(insn->chanspec), data[0]); + + return 1; +} + +static int ni_calib_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + data[0] = devpriv->caldacs[CR_CHAN(insn->chanspec)]; + + return 1; +} + +static int pack_mb88341(int addr, int val, int *bitstring); +static int pack_dac8800(int addr, int val, int *bitstring); +static int pack_dac8043(int addr, int val, int *bitstring); +static int pack_ad8522(int addr, int val, int *bitstring); +static int pack_ad8804(int addr, int val, int *bitstring); +static int pack_ad8842(int addr, int val, int *bitstring); + +struct caldac_struct { + int n_chans; + int n_bits; + int (*packbits) (int, int, int *); +}; + +static struct caldac_struct caldacs[] = { + [mb88341] = {12, 8, pack_mb88341}, + [dac8800] = {8, 8, pack_dac8800}, + [dac8043] = {1, 12, pack_dac8043}, + [ad8522] = {2, 12, pack_ad8522}, + [ad8804] = {12, 8, pack_ad8804}, + [ad8842] = {8, 8, pack_ad8842}, + [ad8804_debug] = {16, 8, pack_ad8804}, +}; + +static void caldac_setup(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int i, j; + int n_dacs; + int n_chans = 0; + int n_bits; + int diffbits = 0; + int type; + int chan; + + type = boardtype.caldac[0]; + if (type == caldac_none) + return; + n_bits = caldacs[type].n_bits; + for (i = 0; i < 3; i++) { + type = boardtype.caldac[i]; + if (type == caldac_none) + break; + if (caldacs[type].n_bits != n_bits) + diffbits = 1; + n_chans += caldacs[type].n_chans; + } + n_dacs = i; + s->n_chan = n_chans; + + if (diffbits) { + unsigned int *maxdata_list; + + if (n_chans > MAX_N_CALDACS) { + printk("BUG! MAX_N_CALDACS too small\n"); + } + s->maxdata_list = maxdata_list = devpriv->caldac_maxdata_list; + chan = 0; + for (i = 0; i < n_dacs; i++) { + type = boardtype.caldac[i]; + for (j = 0; j < caldacs[type].n_chans; j++) { + maxdata_list[chan] = + (1 << caldacs[type].n_bits) - 1; + chan++; + } + } + + for (chan = 0; chan < s->n_chan; chan++) + ni_write_caldac(dev, i, s->maxdata_list[i] / 2); + } else { + type = boardtype.caldac[0]; + s->maxdata = (1 << caldacs[type].n_bits) - 1; + + for (chan = 0; chan < s->n_chan; chan++) + ni_write_caldac(dev, i, s->maxdata / 2); + } +} + +static void ni_write_caldac(struct comedi_device *dev, int addr, int val) +{ + unsigned int loadbit = 0, bits = 0, bit, bitstring = 0; + int i; + int type; + + /* printk("ni_write_caldac: chan=%d val=%d\n",addr,val); */ + if (devpriv->caldacs[addr] == val) + return; + devpriv->caldacs[addr] = val; + + for (i = 0; i < 3; i++) { + type = boardtype.caldac[i]; + if (type == caldac_none) + break; + if (addr < caldacs[type].n_chans) { + bits = caldacs[type].packbits(addr, val, &bitstring); + loadbit = SerDacLd(i); + /* printk("caldac: using i=%d addr=%d %x\n",i,addr,bitstring); */ + break; + } + addr -= caldacs[type].n_chans; + } + + for (bit = 1 << (bits - 1); bit; bit >>= 1) { + ni_writeb(((bit & bitstring) ? 0x02 : 0), Serial_Command); + udelay(1); + ni_writeb(1 | ((bit & bitstring) ? 0x02 : 0), Serial_Command); + udelay(1); + } + ni_writeb(loadbit, Serial_Command); + udelay(1); + ni_writeb(0, Serial_Command); +} + +static int pack_mb88341(int addr, int val, int *bitstring) +{ + /* + Fujitsu MB 88341 + Note that address bits are reversed. Thanks to + Ingo Keen for noticing this. + + Note also that the 88341 expects address values from + 1-12, whereas we use channel numbers 0-11. The NI + docs use 1-12, also, so be careful here. + */ + addr++; + *bitstring = ((addr & 0x1) << 11) | + ((addr & 0x2) << 9) | + ((addr & 0x4) << 7) | ((addr & 0x8) << 5) | (val & 0xff); + return 12; +} + +static int pack_dac8800(int addr, int val, int *bitstring) +{ + *bitstring = ((addr & 0x7) << 8) | (val & 0xff); + return 11; +} + +static int pack_dac8043(int addr, int val, int *bitstring) +{ + *bitstring = val & 0xfff; + return 12; +} + +static int pack_ad8522(int addr, int val, int *bitstring) +{ + *bitstring = (val & 0xfff) | (addr ? 0xc000 : 0xa000); + return 16; +} + +static int pack_ad8804(int addr, int val, int *bitstring) +{ + *bitstring = ((addr & 0xf) << 8) | (val & 0xff); + return 12; +} + +static int pack_ad8842(int addr, int val, int *bitstring) +{ + *bitstring = ((addr + 1) << 8) | (val & 0xff); + return 12; +} + +#if 0 +/* + * Read the GPCTs current value. + */ +static int GPCT_G_Watch(struct comedi_device *dev, int chan) +{ + unsigned int hi1, hi2, lo; + + devpriv->gpct_command[chan] &= ~G_Save_Trace; + devpriv->stc_writew(dev, devpriv->gpct_command[chan], + G_Command_Register(chan)); + + devpriv->gpct_command[chan] |= G_Save_Trace; + devpriv->stc_writew(dev, devpriv->gpct_command[chan], + G_Command_Register(chan)); + + /* This procedure is used because the two registers cannot + * be read atomically. */ + do { + hi1 = devpriv->stc_readw(dev, G_Save_Register_High(chan)); + lo = devpriv->stc_readw(dev, G_Save_Register_Low(chan)); + hi2 = devpriv->stc_readw(dev, G_Save_Register_High(chan)); + } while (hi1 != hi2); + + return (hi1 << 16) | lo; +} + +static void GPCT_Reset(struct comedi_device *dev, int chan) +{ + int temp_ack_reg = 0; + + /* printk("GPCT_Reset..."); */ + devpriv->gpct_cur_operation[chan] = GPCT_RESET; + + switch (chan) { + case 0: + devpriv->stc_writew(dev, G0_Reset, Joint_Reset_Register); + ni_set_bits(dev, Interrupt_A_Enable_Register, + G0_TC_Interrupt_Enable, 0); + ni_set_bits(dev, Interrupt_A_Enable_Register, + G0_Gate_Interrupt_Enable, 0); + temp_ack_reg |= G0_Gate_Error_Confirm; + temp_ack_reg |= G0_TC_Error_Confirm; + temp_ack_reg |= G0_TC_Interrupt_Ack; + temp_ack_reg |= G0_Gate_Interrupt_Ack; + devpriv->stc_writew(dev, temp_ack_reg, + Interrupt_A_Ack_Register); + + /* problem...this interferes with the other ctr... */ + devpriv->an_trig_etc_reg |= GPFO_0_Output_Enable; + devpriv->stc_writew(dev, devpriv->an_trig_etc_reg, + Analog_Trigger_Etc_Register); + break; + case 1: + devpriv->stc_writew(dev, G1_Reset, Joint_Reset_Register); + ni_set_bits(dev, Interrupt_B_Enable_Register, + G1_TC_Interrupt_Enable, 0); + ni_set_bits(dev, Interrupt_B_Enable_Register, + G0_Gate_Interrupt_Enable, 0); + temp_ack_reg |= G1_Gate_Error_Confirm; + temp_ack_reg |= G1_TC_Error_Confirm; + temp_ack_reg |= G1_TC_Interrupt_Ack; + temp_ack_reg |= G1_Gate_Interrupt_Ack; + devpriv->stc_writew(dev, temp_ack_reg, + Interrupt_B_Ack_Register); + + devpriv->an_trig_etc_reg |= GPFO_1_Output_Enable; + devpriv->stc_writew(dev, devpriv->an_trig_etc_reg, + Analog_Trigger_Etc_Register); + break; + } + + devpriv->gpct_mode[chan] = 0; + devpriv->gpct_input_select[chan] = 0; + devpriv->gpct_command[chan] = 0; + + devpriv->gpct_command[chan] |= G_Synchronized_Gate; + + devpriv->stc_writew(dev, devpriv->gpct_mode[chan], + G_Mode_Register(chan)); + devpriv->stc_writew(dev, devpriv->gpct_input_select[chan], + G_Input_Select_Register(chan)); + devpriv->stc_writew(dev, 0, G_Autoincrement_Register(chan)); + + /* printk("exit GPCT_Reset\n"); */ +} + +#endif + +static int ni_gpct_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct ni_gpct *counter = s->private; + return ni_tio_insn_config(counter, insn, data); +} + +static int ni_gpct_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct ni_gpct *counter = s->private; + return ni_tio_rinsn(counter, insn, data); +} + +static int ni_gpct_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct ni_gpct *counter = s->private; + return ni_tio_winsn(counter, insn, data); +} + +static int ni_gpct_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int retval; +#ifdef PCIDMA + struct ni_gpct *counter = s->private; +/* const struct comedi_cmd *cmd = &s->async->cmd; */ + + retval = ni_request_gpct_mite_channel(dev, counter->counter_index, + COMEDI_INPUT); + if (retval) { + comedi_error(dev, + "no dma channel available for use by counter"); + return retval; + } + ni_tio_acknowledge_and_confirm(counter, NULL, NULL, NULL, NULL); + ni_e_series_enable_second_irq(dev, counter->counter_index, 1); + retval = ni_tio_cmd(counter, s->async); +#else + retval = -ENOTSUPP; +#endif + return retval; +} + +static int ni_gpct_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ +#ifdef PCIDMA + struct ni_gpct *counter = s->private; + + return ni_tio_cmdtest(counter, cmd); +#else + return -ENOTSUPP; +#endif +} + +static int ni_gpct_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ +#ifdef PCIDMA + struct ni_gpct *counter = s->private; + int retval; + + retval = ni_tio_cancel(counter); + ni_e_series_enable_second_irq(dev, counter->counter_index, 0); + ni_release_gpct_mite_channel(dev, counter->counter_index); + return retval; +#else + return 0; +#endif +} + +/* + * + * Programmable Function Inputs + * + */ + +static int ni_m_series_set_pfi_routing(struct comedi_device *dev, unsigned chan, + unsigned source) +{ + unsigned pfi_reg_index; + unsigned array_offset; + if ((source & 0x1f) != source) + return -EINVAL; + pfi_reg_index = 1 + chan / 3; + array_offset = pfi_reg_index - 1; + devpriv->pfi_output_select_reg[array_offset] &= + ~MSeries_PFI_Output_Select_Mask(chan); + devpriv->pfi_output_select_reg[array_offset] |= + MSeries_PFI_Output_Select_Bits(chan, source); + ni_writew(devpriv->pfi_output_select_reg[array_offset], + M_Offset_PFI_Output_Select(pfi_reg_index)); + return 2; +} + +static int ni_old_set_pfi_routing(struct comedi_device *dev, unsigned chan, + unsigned source) +{ + /* pre-m-series boards have fixed signals on pfi pins */ + if (source != ni_old_get_pfi_routing(dev, chan)) + return -EINVAL; + return 2; +} + +static int ni_set_pfi_routing(struct comedi_device *dev, unsigned chan, + unsigned source) +{ + if (boardtype.reg_type & ni_reg_m_series_mask) + return ni_m_series_set_pfi_routing(dev, chan, source); + else + return ni_old_set_pfi_routing(dev, chan, source); +} + +static unsigned ni_m_series_get_pfi_routing(struct comedi_device *dev, + unsigned chan) +{ + const unsigned array_offset = chan / 3; + return MSeries_PFI_Output_Select_Source(chan, + devpriv-> + pfi_output_select_reg + [array_offset]); +} + +static unsigned ni_old_get_pfi_routing(struct comedi_device *dev, unsigned chan) +{ + /* pre-m-series boards have fixed signals on pfi pins */ + switch (chan) { + case 0: + return NI_PFI_OUTPUT_AI_START1; + break; + case 1: + return NI_PFI_OUTPUT_AI_START2; + break; + case 2: + return NI_PFI_OUTPUT_AI_CONVERT; + break; + case 3: + return NI_PFI_OUTPUT_G_SRC1; + break; + case 4: + return NI_PFI_OUTPUT_G_GATE1; + break; + case 5: + return NI_PFI_OUTPUT_AO_UPDATE_N; + break; + case 6: + return NI_PFI_OUTPUT_AO_START1; + break; + case 7: + return NI_PFI_OUTPUT_AI_START_PULSE; + break; + case 8: + return NI_PFI_OUTPUT_G_SRC0; + break; + case 9: + return NI_PFI_OUTPUT_G_GATE0; + break; + default: + printk("%s: bug, unhandled case in switch.\n", __func__); + break; + } + return 0; +} + +static unsigned ni_get_pfi_routing(struct comedi_device *dev, unsigned chan) +{ + if (boardtype.reg_type & ni_reg_m_series_mask) + return ni_m_series_get_pfi_routing(dev, chan); + else + return ni_old_get_pfi_routing(dev, chan); +} + +static int ni_config_filter(struct comedi_device *dev, unsigned pfi_channel, + enum ni_pfi_filter_select filter) +{ + unsigned bits; + if ((boardtype.reg_type & ni_reg_m_series_mask) == 0) { + return -ENOTSUPP; + } + bits = ni_readl(M_Offset_PFI_Filter); + bits &= ~MSeries_PFI_Filter_Select_Mask(pfi_channel); + bits |= MSeries_PFI_Filter_Select_Bits(pfi_channel, filter); + ni_writel(bits, M_Offset_PFI_Filter); + return 0; +} + +static int ni_pfi_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if ((boardtype.reg_type & ni_reg_m_series_mask) == 0) { + return -ENOTSUPP; + } + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + ni_writew(s->state, M_Offset_PFI_DO); + } + data[1] = ni_readw(M_Offset_PFI_DI); + return 2; +} + +static int ni_pfi_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int chan; + + if (insn->n < 1) + return -EINVAL; + + chan = CR_CHAN(insn->chanspec); + + switch (data[0]) { + case COMEDI_OUTPUT: + ni_set_bits(dev, IO_Bidirection_Pin_Register, 1 << chan, 1); + break; + case COMEDI_INPUT: + ni_set_bits(dev, IO_Bidirection_Pin_Register, 1 << chan, 0); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (devpriv->io_bidirection_pin_reg & (1 << chan)) ? + COMEDI_OUTPUT : COMEDI_INPUT; + return 0; + break; + case INSN_CONFIG_SET_ROUTING: + return ni_set_pfi_routing(dev, chan, data[1]); + break; + case INSN_CONFIG_GET_ROUTING: + data[1] = ni_get_pfi_routing(dev, chan); + break; + case INSN_CONFIG_FILTER: + return ni_config_filter(dev, chan, data[1]); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * + * NI RTSI Bus Functions + * + */ +static void ni_rtsi_init(struct comedi_device *dev) +{ + /* Initialises the RTSI bus signal switch to a default state */ + + /* Set clock mode to internal */ + devpriv->clock_and_fout2 = MSeries_RTSI_10MHz_Bit; + if (ni_set_master_clock(dev, NI_MIO_INTERNAL_CLOCK, 0) < 0) { + printk("ni_set_master_clock failed, bug?"); + } + /* default internal lines routing to RTSI bus lines */ + devpriv->rtsi_trig_a_output_reg = + RTSI_Trig_Output_Bits(0, + NI_RTSI_OUTPUT_ADR_START1) | + RTSI_Trig_Output_Bits(1, + NI_RTSI_OUTPUT_ADR_START2) | + RTSI_Trig_Output_Bits(2, + NI_RTSI_OUTPUT_SCLKG) | + RTSI_Trig_Output_Bits(3, NI_RTSI_OUTPUT_DACUPDN); + devpriv->stc_writew(dev, devpriv->rtsi_trig_a_output_reg, + RTSI_Trig_A_Output_Register); + devpriv->rtsi_trig_b_output_reg = + RTSI_Trig_Output_Bits(4, + NI_RTSI_OUTPUT_DA_START1) | + RTSI_Trig_Output_Bits(5, + NI_RTSI_OUTPUT_G_SRC0) | + RTSI_Trig_Output_Bits(6, NI_RTSI_OUTPUT_G_GATE0); + if (boardtype.reg_type & ni_reg_m_series_mask) + devpriv->rtsi_trig_b_output_reg |= + RTSI_Trig_Output_Bits(7, NI_RTSI_OUTPUT_RTSI_OSC); + devpriv->stc_writew(dev, devpriv->rtsi_trig_b_output_reg, + RTSI_Trig_B_Output_Register); + +/* +* Sets the source and direction of the 4 on board lines +* devpriv->stc_writew(dev, 0x0000, RTSI_Board_Register); +*/ +} + +static int ni_rtsi_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = 0; + + return 2; +} + +/* Find best multiplier/divider to try and get the PLL running at 80 MHz + * given an arbitrary frequency input clock */ +static int ni_mseries_get_pll_parameters(unsigned reference_period_ns, + unsigned *freq_divider, + unsigned *freq_multiplier, + unsigned *actual_period_ns) +{ + unsigned div; + unsigned best_div = 1; + static const unsigned max_div = 0x10; + unsigned mult; + unsigned best_mult = 1; + static const unsigned max_mult = 0x100; + static const unsigned pico_per_nano = 1000; + + const unsigned reference_picosec = reference_period_ns * pico_per_nano; + /* m-series wants the phased-locked loop to output 80MHz, which is divided by 4 to + * 20 MHz for most timing clocks */ + static const unsigned target_picosec = 12500; + static const unsigned fudge_factor_80_to_20Mhz = 4; + int best_period_picosec = 0; + for (div = 1; div <= max_div; ++div) { + for (mult = 1; mult <= max_mult; ++mult) { + unsigned new_period_ps = + (reference_picosec * div) / mult; + if (abs(new_period_ps - target_picosec) < + abs(best_period_picosec - target_picosec)) { + best_period_picosec = new_period_ps; + best_div = div; + best_mult = mult; + } + } + } + if (best_period_picosec == 0) { + printk("%s: bug, failed to find pll parameters\n", __func__); + return -EIO; + } + *freq_divider = best_div; + *freq_multiplier = best_mult; + *actual_period_ns = + (best_period_picosec * fudge_factor_80_to_20Mhz + + (pico_per_nano / 2)) / pico_per_nano; + return 0; +} + +static inline unsigned num_configurable_rtsi_channels(struct comedi_device *dev) +{ + if (boardtype.reg_type & ni_reg_m_series_mask) + return 8; + else + return 7; +} + +static int ni_mseries_set_pll_master_clock(struct comedi_device *dev, + unsigned source, unsigned period_ns) +{ + static const unsigned min_period_ns = 50; + static const unsigned max_period_ns = 1000; + static const unsigned timeout = 1000; + unsigned pll_control_bits; + unsigned freq_divider; + unsigned freq_multiplier; + unsigned i; + int retval; + if (source == NI_MIO_PLL_PXI10_CLOCK) + period_ns = 100; + /* these limits are somewhat arbitrary, but NI advertises 1 to 20MHz range so we'll use that */ + if (period_ns < min_period_ns || period_ns > max_period_ns) { + printk + ("%s: you must specify an input clock frequency between %i and %i nanosec " + "for the phased-lock loop.\n", __func__, + min_period_ns, max_period_ns); + return -EINVAL; + } + devpriv->rtsi_trig_direction_reg &= ~Use_RTSI_Clock_Bit; + devpriv->stc_writew(dev, devpriv->rtsi_trig_direction_reg, + RTSI_Trig_Direction_Register); + pll_control_bits = + MSeries_PLL_Enable_Bit | MSeries_PLL_VCO_Mode_75_150MHz_Bits; + devpriv->clock_and_fout2 |= + MSeries_Timebase1_Select_Bit | MSeries_Timebase3_Select_Bit; + devpriv->clock_and_fout2 &= ~MSeries_PLL_In_Source_Select_Mask; + switch (source) { + case NI_MIO_PLL_PXI_STAR_TRIGGER_CLOCK: + devpriv->clock_and_fout2 |= + MSeries_PLL_In_Source_Select_Star_Trigger_Bits; + retval = ni_mseries_get_pll_parameters(period_ns, &freq_divider, + &freq_multiplier, + &devpriv->clock_ns); + if (retval < 0) + return retval; + break; + case NI_MIO_PLL_PXI10_CLOCK: + /* pxi clock is 10MHz */ + devpriv->clock_and_fout2 |= + MSeries_PLL_In_Source_Select_PXI_Clock10; + retval = ni_mseries_get_pll_parameters(period_ns, &freq_divider, + &freq_multiplier, + &devpriv->clock_ns); + if (retval < 0) + return retval; + break; + default: + { + unsigned rtsi_channel; + static const unsigned max_rtsi_channel = 7; + for (rtsi_channel = 0; rtsi_channel <= max_rtsi_channel; + ++rtsi_channel) { + if (source == + NI_MIO_PLL_RTSI_CLOCK(rtsi_channel)) { + devpriv->clock_and_fout2 |= + MSeries_PLL_In_Source_Select_RTSI_Bits + (rtsi_channel); + break; + } + } + if (rtsi_channel > max_rtsi_channel) + return -EINVAL; + retval = ni_mseries_get_pll_parameters(period_ns, + &freq_divider, + &freq_multiplier, + &devpriv-> + clock_ns); + if (retval < 0) + return retval; + } + break; + } + ni_writew(devpriv->clock_and_fout2, M_Offset_Clock_and_Fout2); + pll_control_bits |= + MSeries_PLL_Divisor_Bits(freq_divider) | + MSeries_PLL_Multiplier_Bits(freq_multiplier); + + /* printk("using divider=%i, multiplier=%i for PLL. pll_control_bits = 0x%x\n", + * freq_divider, freq_multiplier, pll_control_bits); */ + /* printk("clock_ns=%d\n", devpriv->clock_ns); */ + ni_writew(pll_control_bits, M_Offset_PLL_Control); + devpriv->clock_source = source; + /* it seems to typically take a few hundred microseconds for PLL to lock */ + for (i = 0; i < timeout; ++i) { + if (ni_readw(M_Offset_PLL_Status) & MSeries_PLL_Locked_Bit) { + break; + } + udelay(1); + } + if (i == timeout) { + printk + ("%s: timed out waiting for PLL to lock to reference clock source %i with period %i ns.\n", + __func__, source, period_ns); + return -ETIMEDOUT; + } + return 3; +} + +static int ni_set_master_clock(struct comedi_device *dev, unsigned source, + unsigned period_ns) +{ + if (source == NI_MIO_INTERNAL_CLOCK) { + devpriv->rtsi_trig_direction_reg &= ~Use_RTSI_Clock_Bit; + devpriv->stc_writew(dev, devpriv->rtsi_trig_direction_reg, + RTSI_Trig_Direction_Register); + devpriv->clock_ns = TIMEBASE_1_NS; + if (boardtype.reg_type & ni_reg_m_series_mask) { + devpriv->clock_and_fout2 &= + ~(MSeries_Timebase1_Select_Bit | + MSeries_Timebase3_Select_Bit); + ni_writew(devpriv->clock_and_fout2, + M_Offset_Clock_and_Fout2); + ni_writew(0, M_Offset_PLL_Control); + } + devpriv->clock_source = source; + } else { + if (boardtype.reg_type & ni_reg_m_series_mask) { + return ni_mseries_set_pll_master_clock(dev, source, + period_ns); + } else { + if (source == NI_MIO_RTSI_CLOCK) { + devpriv->rtsi_trig_direction_reg |= + Use_RTSI_Clock_Bit; + devpriv->stc_writew(dev, + devpriv-> + rtsi_trig_direction_reg, + RTSI_Trig_Direction_Register); + if (period_ns == 0) { + printk + ("%s: we don't handle an unspecified clock period correctly yet, returning error.\n", + __func__); + return -EINVAL; + } else { + devpriv->clock_ns = period_ns; + } + devpriv->clock_source = source; + } else + return -EINVAL; + } + } + return 3; +} + +static int ni_valid_rtsi_output_source(struct comedi_device *dev, unsigned chan, + unsigned source) +{ + if (chan >= num_configurable_rtsi_channels(dev)) { + if (chan == old_RTSI_clock_channel) { + if (source == NI_RTSI_OUTPUT_RTSI_OSC) + return 1; + else { + printk + ("%s: invalid source for channel=%i, channel %i is always the RTSI clock for pre-m-series boards.\n", + __func__, chan, old_RTSI_clock_channel); + return 0; + } + } + return 0; + } + switch (source) { + case NI_RTSI_OUTPUT_ADR_START1: + case NI_RTSI_OUTPUT_ADR_START2: + case NI_RTSI_OUTPUT_SCLKG: + case NI_RTSI_OUTPUT_DACUPDN: + case NI_RTSI_OUTPUT_DA_START1: + case NI_RTSI_OUTPUT_G_SRC0: + case NI_RTSI_OUTPUT_G_GATE0: + case NI_RTSI_OUTPUT_RGOUT0: + case NI_RTSI_OUTPUT_RTSI_BRD_0: + return 1; + break; + case NI_RTSI_OUTPUT_RTSI_OSC: + if (boardtype.reg_type & ni_reg_m_series_mask) + return 1; + else + return 0; + break; + default: + return 0; + break; + } +} + +static int ni_set_rtsi_routing(struct comedi_device *dev, unsigned chan, + unsigned source) +{ + if (ni_valid_rtsi_output_source(dev, chan, source) == 0) + return -EINVAL; + if (chan < 4) { + devpriv->rtsi_trig_a_output_reg &= ~RTSI_Trig_Output_Mask(chan); + devpriv->rtsi_trig_a_output_reg |= + RTSI_Trig_Output_Bits(chan, source); + devpriv->stc_writew(dev, devpriv->rtsi_trig_a_output_reg, + RTSI_Trig_A_Output_Register); + } else if (chan < 8) { + devpriv->rtsi_trig_b_output_reg &= ~RTSI_Trig_Output_Mask(chan); + devpriv->rtsi_trig_b_output_reg |= + RTSI_Trig_Output_Bits(chan, source); + devpriv->stc_writew(dev, devpriv->rtsi_trig_b_output_reg, + RTSI_Trig_B_Output_Register); + } + return 2; +} + +static unsigned ni_get_rtsi_routing(struct comedi_device *dev, unsigned chan) +{ + if (chan < 4) { + return RTSI_Trig_Output_Source(chan, + devpriv->rtsi_trig_a_output_reg); + } else if (chan < num_configurable_rtsi_channels(dev)) { + return RTSI_Trig_Output_Source(chan, + devpriv->rtsi_trig_b_output_reg); + } else { + if (chan == old_RTSI_clock_channel) + return NI_RTSI_OUTPUT_RTSI_OSC; + printk("%s: bug! should never get here?\n", __func__); + return 0; + } +} + +static int ni_rtsi_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int chan = CR_CHAN(insn->chanspec); + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + if (chan < num_configurable_rtsi_channels(dev)) { + devpriv->rtsi_trig_direction_reg |= + RTSI_Output_Bit(chan, + (boardtype. + reg_type & ni_reg_m_series_mask) != + 0); + } else if (chan == old_RTSI_clock_channel) { + devpriv->rtsi_trig_direction_reg |= + Drive_RTSI_Clock_Bit; + } + devpriv->stc_writew(dev, devpriv->rtsi_trig_direction_reg, + RTSI_Trig_Direction_Register); + break; + case INSN_CONFIG_DIO_INPUT: + if (chan < num_configurable_rtsi_channels(dev)) { + devpriv->rtsi_trig_direction_reg &= + ~RTSI_Output_Bit(chan, + (boardtype. + reg_type & ni_reg_m_series_mask) + != 0); + } else if (chan == old_RTSI_clock_channel) { + devpriv->rtsi_trig_direction_reg &= + ~Drive_RTSI_Clock_Bit; + } + devpriv->stc_writew(dev, devpriv->rtsi_trig_direction_reg, + RTSI_Trig_Direction_Register); + break; + case INSN_CONFIG_DIO_QUERY: + if (chan < num_configurable_rtsi_channels(dev)) { + data[1] = + (devpriv->rtsi_trig_direction_reg & + RTSI_Output_Bit(chan, + (boardtype.reg_type & + ni_reg_m_series_mask) + != 0)) ? INSN_CONFIG_DIO_OUTPUT : + INSN_CONFIG_DIO_INPUT; + } else if (chan == old_RTSI_clock_channel) { + data[1] = + (devpriv->rtsi_trig_direction_reg & + Drive_RTSI_Clock_Bit) + ? INSN_CONFIG_DIO_OUTPUT : INSN_CONFIG_DIO_INPUT; + } + return 2; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + return ni_set_master_clock(dev, data[1], data[2]); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + data[1] = devpriv->clock_source; + data[2] = devpriv->clock_ns; + return 3; + break; + case INSN_CONFIG_SET_ROUTING: + return ni_set_rtsi_routing(dev, chan, data[1]); + break; + case INSN_CONFIG_GET_ROUTING: + data[1] = ni_get_rtsi_routing(dev, chan); + return 2; + break; + default: + return -EINVAL; + break; + } + return 1; +} + +static int cs5529_wait_for_idle(struct comedi_device *dev) +{ + unsigned short status; + const int timeout = HZ; + int i; + + for (i = 0; i < timeout; i++) { + status = ni_ao_win_inw(dev, CAL_ADC_Status_67xx); + if ((status & CSS_ADC_BUSY) == 0) { + break; + } + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(1)) { + return -EIO; + } + } +/* printk("looped %i times waiting for idle\n", i); */ + if (i == timeout) { + printk("%s: %s: timeout\n", __FILE__, __func__); + return -ETIME; + } + return 0; +} + +static void cs5529_command(struct comedi_device *dev, unsigned short value) +{ + static const int timeout = 100; + int i; + + ni_ao_win_outw(dev, value, CAL_ADC_Command_67xx); + /* give time for command to start being serially clocked into cs5529. + * this insures that the CSS_ADC_BUSY bit will get properly + * set before we exit this function. + */ + for (i = 0; i < timeout; i++) { + if ((ni_ao_win_inw(dev, CAL_ADC_Status_67xx) & CSS_ADC_BUSY)) + break; + udelay(1); + } +/* printk("looped %i times writing command to cs5529\n", i); */ + if (i == timeout) { + comedi_error(dev, "possible problem - never saw adc go busy?"); + } +} + +/* write to cs5529 register */ +static void cs5529_config_write(struct comedi_device *dev, unsigned int value, + unsigned int reg_select_bits) +{ + ni_ao_win_outw(dev, ((value >> 16) & 0xff), + CAL_ADC_Config_Data_High_Word_67xx); + ni_ao_win_outw(dev, (value & 0xffff), + CAL_ADC_Config_Data_Low_Word_67xx); + reg_select_bits &= CSCMD_REGISTER_SELECT_MASK; + cs5529_command(dev, CSCMD_COMMAND | reg_select_bits); + if (cs5529_wait_for_idle(dev)) + comedi_error(dev, "time or signal in cs5529_config_write()"); +} + +#ifdef NI_CS5529_DEBUG +/* read from cs5529 register */ +static unsigned int cs5529_config_read(struct comedi_device *dev, + unsigned int reg_select_bits) +{ + unsigned int value; + + reg_select_bits &= CSCMD_REGISTER_SELECT_MASK; + cs5529_command(dev, CSCMD_COMMAND | CSCMD_READ | reg_select_bits); + if (cs5529_wait_for_idle(dev)) + comedi_error(dev, "timeout or signal in cs5529_config_read()"); + value = (ni_ao_win_inw(dev, + CAL_ADC_Config_Data_High_Word_67xx) << 16) & + 0xff0000; + value |= ni_ao_win_inw(dev, CAL_ADC_Config_Data_Low_Word_67xx) & 0xffff; + return value; +} +#endif + +static int cs5529_do_conversion(struct comedi_device *dev, unsigned short *data) +{ + int retval; + unsigned short status; + + cs5529_command(dev, CSCMD_COMMAND | CSCMD_SINGLE_CONVERSION); + retval = cs5529_wait_for_idle(dev); + if (retval) { + comedi_error(dev, + "timeout or signal in cs5529_do_conversion()"); + return -ETIME; + } + status = ni_ao_win_inw(dev, CAL_ADC_Status_67xx); + if (status & CSS_OSC_DETECT) { + printk + ("ni_mio_common: cs5529 conversion error, status CSS_OSC_DETECT\n"); + return -EIO; + } + if (status & CSS_OVERRANGE) { + printk + ("ni_mio_common: cs5529 conversion error, overrange (ignoring)\n"); + } + if (data) { + *data = ni_ao_win_inw(dev, CAL_ADC_Data_67xx); + /* cs5529 returns 16 bit signed data in bipolar mode */ + *data ^= (1 << 15); + } + return 0; +} + +static int cs5529_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, retval; + unsigned short sample; + unsigned int channel_select; + const unsigned int INTERNAL_REF = 0x1000; + + /* Set calibration adc source. Docs lie, reference select bits 8 to 11 + * do nothing. bit 12 seems to chooses internal reference voltage, bit + * 13 causes the adc input to go overrange (maybe reads external reference?) */ + if (insn->chanspec & CR_ALT_SOURCE) + channel_select = INTERNAL_REF; + else + channel_select = CR_CHAN(insn->chanspec); + ni_ao_win_outw(dev, channel_select, AO_Calibration_Channel_Select_67xx); + + for (n = 0; n < insn->n; n++) { + retval = cs5529_do_conversion(dev, &sample); + if (retval < 0) + return retval; + data[n] = sample; + } + return insn->n; +} + +static int init_cs5529(struct comedi_device *dev) +{ + unsigned int config_bits = + CSCFG_PORT_MODE | CSCFG_WORD_RATE_2180_CYCLES; + +#if 1 + /* do self-calibration */ + cs5529_config_write(dev, config_bits | CSCFG_SELF_CAL_OFFSET_GAIN, + CSCMD_CONFIG_REGISTER); + /* need to force a conversion for calibration to run */ + cs5529_do_conversion(dev, NULL); +#else + /* force gain calibration to 1 */ + cs5529_config_write(dev, 0x400000, CSCMD_GAIN_REGISTER); + cs5529_config_write(dev, config_bits | CSCFG_SELF_CAL_OFFSET, + CSCMD_CONFIG_REGISTER); + if (cs5529_wait_for_idle(dev)) + comedi_error(dev, "timeout or signal in init_cs5529()\n"); +#endif +#ifdef NI_CS5529_DEBUG + printk("config: 0x%x\n", cs5529_config_read(dev, + CSCMD_CONFIG_REGISTER)); + printk("gain: 0x%x\n", cs5529_config_read(dev, CSCMD_GAIN_REGISTER)); + printk("offset: 0x%x\n", cs5529_config_read(dev, + CSCMD_OFFSET_REGISTER)); +#endif + return 0; +} diff --git a/drivers/staging/comedi/drivers/ni_mio_cs.c b/drivers/staging/comedi/drivers/ni_mio_cs.c new file mode 100644 index 00000000..53ec24bb --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_mio_cs.c @@ -0,0 +1,459 @@ +/* + comedi/drivers/ni_mio_cs.c + Hardware driver for NI PCMCIA MIO E series cards + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: ni_mio_cs +Description: National Instruments DAQCard E series +Author: ds +Status: works +Devices: [National Instruments] DAQCard-AI-16XE-50 (ni_mio_cs), + DAQCard-AI-16E-4, DAQCard-6062E, DAQCard-6024E, DAQCard-6036E +Updated: Thu Oct 23 19:43:17 CDT 2003 + +See the notes in the ni_atmio.o driver. +*/ +/* + The real guts of the driver is in ni_mio_common.c, which is + included by all the E series drivers. + + References for specifications: + + 341080a.pdf DAQCard E Series Register Level Programmer Manual + +*/ + +#include "../comedidev.h" + +#include + +#include "ni_stc.h" +#include "8255.h" + +#include +#include + +#undef DEBUG + +#define ATMIO 1 +#undef PCIMIO + +/* + * AT specific setup + */ + +#define NI_SIZE 0x20 + +#define MAX_N_CALDACS 32 + +static const struct ni_board_struct ni_boards[] = { + {.device_id = 0x010d, + .name = "DAQCard-ai-16xe-50", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 1024, + .alwaysdither = 0, + .gainlkup = ai_gain_8, + .ai_speed = 5000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .has_8255 = 0, + .caldac = {dac8800, dac8043}, + }, + {.device_id = 0x010c, + .name = "DAQCard-ai-16e-4", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 1024, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 4000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .has_8255 = 0, + .caldac = {mb88341}, /* verified */ + }, + {.device_id = 0x02c4, + .name = "DAQCard-6062E", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 8192, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 1176, + .num_p0_dio_channels = 8, + .has_8255 = 0, + .caldac = {ad8804_debug}, /* verified */ + }, + {.device_id = 0x075e, + .name = "DAQCard-6024E", /* specs incorrect! */ + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 1024, + .alwaysdither = 0, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 1000000, + .num_p0_dio_channels = 8, + .has_8255 = 0, + .caldac = {ad8804_debug}, + }, + {.device_id = 0x0245, + .name = "DAQCard-6036E", /* specs incorrect! */ + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 1024, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 1000000, + .num_p0_dio_channels = 8, + .has_8255 = 0, + .caldac = {ad8804_debug}, + }, +#if 0 + {.device_id = 0x0000, /* unknown */ + .name = "DAQCard-6715", + .n_adchan = 0, + .n_aochan = 8, + .aobits = 12, + .ao_671x = 8192, + .num_p0_dio_channels = 8, + .caldac = {mb88341, mb88341}, + }, +#endif + /* N.B. Update ni_mio_cs_ids[] when entries added above. */ +}; + +#define interrupt_pin(a) 0 + +#define IRQ_POLARITY 1 + +#define NI_E_IRQ_FLAGS IRQF_SHARED + +struct ni_private { + + struct pcmcia_device *link; + + NI_PRIVATE_COMMON}; + +#define devpriv ((struct ni_private *)dev->private) + +/* How we access registers */ + +#define ni_writel(a, b) (outl((a), (b)+dev->iobase)) +#define ni_readl(a) (inl((a)+dev->iobase)) +#define ni_writew(a, b) (outw((a), (b)+dev->iobase)) +#define ni_readw(a) (inw((a)+dev->iobase)) +#define ni_writeb(a, b) (outb((a), (b)+dev->iobase)) +#define ni_readb(a) (inb((a)+dev->iobase)) + +/* How we access windowed registers */ + +/* We automatically take advantage of STC registers that can be + * read/written directly in the I/O space of the board. The + * DAQCard devices map the low 8 STC registers to iobase+addr*2. */ + +static void mio_cs_win_out(struct comedi_device *dev, uint16_t data, int addr) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->window_lock, flags); + if (addr < 8) { + ni_writew(data, addr * 2); + } else { + ni_writew(addr, Window_Address); + ni_writew(data, Window_Data); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static uint16_t mio_cs_win_in(struct comedi_device *dev, int addr) +{ + unsigned long flags; + uint16_t ret; + + spin_lock_irqsave(&devpriv->window_lock, flags); + if (addr < 8) { + ret = ni_readw(addr * 2); + } else { + ni_writew(addr, Window_Address); + ret = ni_readw(Window_Data); + } + spin_unlock_irqrestore(&devpriv->window_lock, flags); + + return ret; +} + +static int mio_cs_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int mio_cs_detach(struct comedi_device *dev); +static struct comedi_driver driver_ni_mio_cs = { + .driver_name = "ni_mio_cs", + .module = THIS_MODULE, + .attach = mio_cs_attach, + .detach = mio_cs_detach, +}; + +#include "ni_mio_common.c" + +static int ni_getboardtype(struct comedi_device *dev, + struct pcmcia_device *link); + +/* clean up allocated resources */ +/* called when driver is removed */ +static int mio_cs_detach(struct comedi_device *dev) +{ + mio_common_detach(dev); + + /* PCMCIA layer frees the IO region */ + + if (dev->irq) + free_irq(dev->irq, dev); + + return 0; +} + +static void mio_cs_config(struct pcmcia_device *link); +static void cs_release(struct pcmcia_device *link); +static void cs_detach(struct pcmcia_device *); + +static struct pcmcia_device *cur_dev = NULL; + +static int cs_attach(struct pcmcia_device *link) +{ + cur_dev = link; + + mio_cs_config(link); + + return 0; +} + +static void cs_release(struct pcmcia_device *link) +{ + pcmcia_disable_device(link); +} + +static void cs_detach(struct pcmcia_device *link) +{ + DPRINTK("cs_detach(link=%p)\n", link); + + cs_release(link); +} + +static int mio_cs_suspend(struct pcmcia_device *link) +{ + DPRINTK("pm suspend\n"); + + return 0; +} + +static int mio_cs_resume(struct pcmcia_device *link) +{ + DPRINTK("pm resume\n"); + return 0; +} + + +static int mio_pcmcia_config_loop(struct pcmcia_device *p_dev, void *priv_data) +{ + int base, ret; + + p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_16; + + for (base = 0x000; base < 0x400; base += 0x20) { + p_dev->resource[0]->start = base; + ret = pcmcia_request_io(p_dev); + if (!ret) + return 0; + } + return -ENODEV; +} + + +static void mio_cs_config(struct pcmcia_device *link) +{ + int ret; + + DPRINTK("mio_cs_config(link=%p)\n", link); + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + ret = pcmcia_loop_config(link, mio_pcmcia_config_loop, NULL); + if (ret) { + dev_warn(&link->dev, "no configuration found\n"); + return; + } + + if (!link->irq) + dev_info(&link->dev, "no IRQ available\n"); + + ret = pcmcia_enable_device(link); +} + +static int mio_cs_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct pcmcia_device *link; + unsigned int irq; + int ret; + + DPRINTK("mio_cs_attach(dev=%p,it=%p)\n", dev, it); + + link = cur_dev; /* XXX hack */ + if (!link) + return -EIO; + + dev->driver = &driver_ni_mio_cs; + dev->iobase = link->resource[0]->start; + + irq = link->irq; + + printk("comedi%d: %s: DAQCard: io 0x%04lx, irq %u, ", + dev->minor, dev->driver->driver_name, dev->iobase, irq); + +#if 0 + { + int i; + + printk(" board fingerprint:"); + for (i = 0; i < 32; i += 2) { + printk(" %04x %02x", inw(dev->iobase + i), + inb(dev->iobase + i + 1)); + } + printk("\n"); + printk(" board fingerprint (windowed):"); + for (i = 0; i < 10; i++) + printk(" 0x%04x", win_in(i)); + printk("\n"); + } +#endif + + dev->board_ptr = ni_boards + ni_getboardtype(dev, link); + + printk(" %s", boardtype.name); + dev->board_name = boardtype.name; + + ret = request_irq(irq, ni_E_interrupt, NI_E_IRQ_FLAGS, + "ni_mio_cs", dev); + if (ret < 0) { + printk(" irq not available\n"); + return -EINVAL; + } + dev->irq = irq; + + /* allocate private area */ + ret = ni_alloc_private(dev); + if (ret < 0) + return ret; + + devpriv->stc_writew = &mio_cs_win_out; + devpriv->stc_readw = &mio_cs_win_in; + devpriv->stc_writel = &win_out2; + devpriv->stc_readl = &win_in2; + + ret = ni_E_init(dev, it); + + if (ret < 0) + return ret; + + return 0; +} + +static int ni_getboardtype(struct comedi_device *dev, + struct pcmcia_device *link) +{ + int i; + + for (i = 0; i < n_ni_boards; i++) { + if (ni_boards[i].device_id == link->card_id) + return i; + } + + printk("unknown board 0x%04x -- pretend it is a ", link->card_id); + + return 0; +} + +#ifdef MODULE + +static const struct pcmcia_device_id ni_mio_cs_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x010d), /* DAQCard-ai-16xe-50 */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x010c), /* DAQCard-ai-16e-4 */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x02c4), /* DAQCard-6062E */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x075e), /* DAQCard-6024E */ + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0245), /* DAQCard-6036E */ + PCMCIA_DEVICE_NULL +}; + +MODULE_DEVICE_TABLE(pcmcia, ni_mio_cs_ids); +MODULE_AUTHOR("David A. Schleef "); +MODULE_DESCRIPTION("Comedi driver for National Instruments DAQCard E series"); +MODULE_LICENSE("GPL"); + +struct pcmcia_driver ni_mio_cs_driver = { + .probe = &cs_attach, + .remove = &cs_detach, + .suspend = &mio_cs_suspend, + .resume = &mio_cs_resume, + .id_table = ni_mio_cs_ids, + .owner = THIS_MODULE, + .name = "ni_mio_cs", +}; + +int init_module(void) +{ + pcmcia_register_driver(&ni_mio_cs_driver); + comedi_driver_register(&driver_ni_mio_cs); + return 0; +} + +void cleanup_module(void) +{ + pcmcia_unregister_driver(&ni_mio_cs_driver); +#if 0 + while (cur_dev != NULL) + cs_detach(cur_dev->handle); +#endif + comedi_driver_unregister(&driver_ni_mio_cs); +} +#endif diff --git a/drivers/staging/comedi/drivers/ni_pcidio.c b/drivers/staging/comedi/drivers/ni_pcidio.c new file mode 100644 index 00000000..1df8fcbc --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_pcidio.c @@ -0,0 +1,1399 @@ +/* + comedi/drivers/ni_pcidio.c + driver for National Instruments PCI-DIO-96/PCI-6508 + National Instruments PCI-DIO-32HS + National Instruments PCI-6503 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999,2002 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: ni_pcidio +Description: National Instruments PCI-DIO32HS, PCI-DIO96, PCI-6533, PCI-6503 +Author: ds +Status: works +Devices: [National Instruments] PCI-DIO-32HS (ni_pcidio), PXI-6533, + PCI-DIO-96, PCI-DIO-96B, PXI-6508, PCI-6503, PCI-6503B, PCI-6503X, + PXI-6503, PCI-6533, PCI-6534 +Updated: Mon, 09 Jan 2012 14:27:23 +0000 + +The DIO-96 appears as four 8255 subdevices. See the 8255 +driver notes for details. + +The DIO32HS board appears as one subdevice, with 32 channels. +Each channel is individually I/O configurable. The channel order +is 0=A0, 1=A1, 2=A2, ... 8=B0, 16=C0, 24=D0. The driver only +supports simple digital I/O; no handshaking is supported. + +DMA mostly works for the PCI-DIO32HS, but only in timed input mode. + +The PCI-DIO-32HS/PCI-6533 has a configurable external trigger. Setting +scan_begin_arg to 0 or CR_EDGE triggers on the leading edge. Setting +scan_begin_arg to CR_INVERT or (CR_EDGE | CR_INVERT) triggers on the +trailing edge. + +This driver could be easily modified to support AT-MIO32HS and +AT-MIO96. + +The PCI-6534 requires a firmware upload after power-up to work, the +firmware data and instructions for loading it with comedi_config +it are contained in the +comedi_nonfree_firmware tarball available from http://www.comedi.org +*/ + +/* + This driver is for both the NI PCI-DIO-32HS and the PCI-DIO-96, + which have very different architectures. But, since the '96 is + so simple, it is included here. + + Manuals (available from ftp://ftp.natinst.com/support/manuals) + + 320938c.pdf PCI-DIO-96/PXI-6508/PCI-6503 User Manual + 321464b.pdf AT/PCI-DIO-32HS User Manual + 341329A.pdf PCI-6533 Register-Level Programmer Manual + 341330A.pdf DAQ-DIO Technical Reference Manual + + */ + +#define USE_DMA +/* #define DEBUG 1 */ +/* #define DEBUG_FLAGS */ + +#include +#include +#include "../comedidev.h" + +#include "mite.h" +#include "8255.h" + +#undef DPRINTK +#ifdef DEBUG +#define DPRINTK(format, args...) printk(format, ## args) +#else +#define DPRINTK(format, args...) +#endif + +#define PCI_DIO_SIZE 4096 +#define PCI_MITE_SIZE 4096 + +/* defines for the PCI-DIO-96 */ + +#define NIDIO_8255_BASE(x) ((x)*4) +#define NIDIO_A 0 +#define NIDIO_B 4 +#define NIDIO_C 8 +#define NIDIO_D 12 + +/* defines for the PCI-DIO-32HS */ + +#define Window_Address 4 /* W */ +#define Interrupt_And_Window_Status 4 /* R */ +#define IntStatus1 (1<<0) +#define IntStatus2 (1<<1) +#define WindowAddressStatus_mask 0x7c + +#define Master_DMA_And_Interrupt_Control 5 /* W */ +#define InterruptLine(x) ((x)&3) +#define OpenInt (1<<2) +#define Group_Status 5 /* R */ +#define DataLeft (1<<0) +#define Req (1<<2) +#define StopTrig (1<<3) + +#define Group_1_Flags 6 /* R */ +#define Group_2_Flags 7 /* R */ +#define TransferReady (1<<0) +#define CountExpired (1<<1) +#define Waited (1<<5) +#define PrimaryTC (1<<6) +#define SecondaryTC (1<<7) + /* #define SerialRose */ + /* #define ReqRose */ + /* #define Paused */ + +#define Group_1_First_Clear 6 /* W */ +#define Group_2_First_Clear 7 /* W */ +#define ClearWaited (1<<3) +#define ClearPrimaryTC (1<<4) +#define ClearSecondaryTC (1<<5) +#define DMAReset (1<<6) +#define FIFOReset (1<<7) +#define ClearAll 0xf8 + +#define Group_1_FIFO 8 /* W */ +#define Group_2_FIFO 12 /* W */ + +#define Transfer_Count 20 +#define Chip_ID_D 24 +#define Chip_ID_I 25 +#define Chip_ID_O 26 +#define Chip_Version 27 +#define Port_IO(x) (28+(x)) +#define Port_Pin_Directions(x) (32+(x)) +#define Port_Pin_Mask(x) (36+(x)) +#define Port_Pin_Polarities(x) (40+(x)) + +#define Master_Clock_Routing 45 +#define RTSIClocking(x) (((x)&3)<<4) + +#define Group_1_Second_Clear 46 /* W */ +#define Group_2_Second_Clear 47 /* W */ +#define ClearExpired (1<<0) + +#define Port_Pattern(x) (48+(x)) + +#define Data_Path 64 +#define FIFOEnableA (1<<0) +#define FIFOEnableB (1<<1) +#define FIFOEnableC (1<<2) +#define FIFOEnableD (1<<3) +#define Funneling(x) (((x)&3)<<4) +#define GroupDirection (1<<7) + +#define Protocol_Register_1 65 +#define OpMode Protocol_Register_1 +#define RunMode(x) ((x)&7) +#define Numbered (1<<3) + +#define Protocol_Register_2 66 +#define ClockReg Protocol_Register_2 +#define ClockLine(x) (((x)&3)<<5) +#define InvertStopTrig (1<<7) +#define DataLatching(x) (((x)&3)<<5) + +#define Protocol_Register_3 67 +#define Sequence Protocol_Register_3 + +#define Protocol_Register_14 68 /* 16 bit */ +#define ClockSpeed Protocol_Register_14 + +#define Protocol_Register_4 70 +#define ReqReg Protocol_Register_4 +#define ReqConditioning(x) (((x)&7)<<3) + +#define Protocol_Register_5 71 +#define BlockMode Protocol_Register_5 + +#define FIFO_Control 72 +#define ReadyLevel(x) ((x)&7) + +#define Protocol_Register_6 73 +#define LinePolarities Protocol_Register_6 +#define InvertAck (1<<0) +#define InvertReq (1<<1) +#define InvertClock (1<<2) +#define InvertSerial (1<<3) +#define OpenAck (1<<4) +#define OpenClock (1<<5) + +#define Protocol_Register_7 74 +#define AckSer Protocol_Register_7 +#define AckLine(x) (((x)&3)<<2) +#define ExchangePins (1<<7) + +#define Interrupt_Control 75 + /* bits same as flags */ + +#define DMA_Line_Control_Group1 76 +#define DMA_Line_Control_Group2 108 +/* channel zero is none */ +static inline unsigned primary_DMAChannel_bits(unsigned channel) +{ + return channel & 0x3; +} + +static inline unsigned secondary_DMAChannel_bits(unsigned channel) +{ + return (channel << 2) & 0xc; +} + +#define Transfer_Size_Control 77 +#define TransferWidth(x) ((x)&3) +#define TransferLength(x) (((x)&3)<<3) +#define RequireRLevel (1<<5) + +#define Protocol_Register_15 79 +#define DAQOptions Protocol_Register_15 +#define StartSource(x) ((x)&0x3) +#define InvertStart (1<<2) +#define StopSource(x) (((x)&0x3)<<3) +#define ReqStart (1<<6) +#define PreStart (1<<7) + +#define Pattern_Detection 81 +#define DetectionMethod (1<<0) +#define InvertMatch (1<<1) +#define IE_Pattern_Detection (1<<2) + +#define Protocol_Register_9 82 +#define ReqDelay Protocol_Register_9 + +#define Protocol_Register_10 83 +#define ReqNotDelay Protocol_Register_10 + +#define Protocol_Register_11 84 +#define AckDelay Protocol_Register_11 + +#define Protocol_Register_12 85 +#define AckNotDelay Protocol_Register_12 + +#define Protocol_Register_13 86 +#define Data1Delay Protocol_Register_13 + +#define Protocol_Register_8 88 /* 32 bit */ +#define StartDelay Protocol_Register_8 + +enum pci_6534_firmware_registers { /* 16 bit */ + Firmware_Control_Register = 0x100, + Firmware_Status_Register = 0x104, + Firmware_Data_Register = 0x108, + Firmware_Mask_Register = 0x10c, + Firmware_Debug_Register = 0x110, +}; +/* main fpga registers (32 bit)*/ +enum pci_6534_fpga_registers { + FPGA_Control1_Register = 0x200, + FPGA_Control2_Register = 0x204, + FPGA_Irq_Mask_Register = 0x208, + FPGA_Status_Register = 0x20c, + FPGA_Signature_Register = 0x210, + FPGA_SCALS_Counter_Register = 0x280, /*write-clear */ + FPGA_SCAMS_Counter_Register = 0x284, /*write-clear */ + FPGA_SCBLS_Counter_Register = 0x288, /*write-clear */ + FPGA_SCBMS_Counter_Register = 0x28c, /*write-clear */ + FPGA_Temp_Control_Register = 0x2a0, + FPGA_DAR_Register = 0x2a8, + FPGA_ELC_Read_Register = 0x2b8, + FPGA_ELC_Write_Register = 0x2bc, +}; +enum FPGA_Control_Bits { + FPGA_Enable_Bit = 0x8000, +}; + +#define TIMER_BASE 50 /* nanoseconds */ + +#ifdef USE_DMA +#define IntEn (CountExpired|Waited|PrimaryTC|SecondaryTC) +#else +#define IntEn (TransferReady|CountExpired|Waited|PrimaryTC|SecondaryTC) +#endif + +static int nidio_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int nidio_detach(struct comedi_device *dev); +static int ni_pcidio_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); + +static struct comedi_driver driver_pcidio = { + .driver_name = "ni_pcidio", + .module = THIS_MODULE, + .attach = nidio_attach, + .detach = nidio_detach, +}; + +struct nidio_board { + + int dev_id; + const char *name; + int n_8255; + unsigned int is_diodaq:1; + unsigned int uses_firmware:1; +}; + +static const struct nidio_board nidio_boards[] = { + { + .dev_id = 0x1150, + .name = "pci-dio-32hs", + .n_8255 = 0, + .is_diodaq = 1, + }, + { + .dev_id = 0x1320, + .name = "pxi-6533", + .n_8255 = 0, + .is_diodaq = 1, + }, + { + .dev_id = 0x12b0, + .name = "pci-6534", + .n_8255 = 0, + .is_diodaq = 1, + .uses_firmware = 1, + }, + { + .dev_id = 0x0160, + .name = "pci-dio-96", + .n_8255 = 4, + .is_diodaq = 0, + }, + { + .dev_id = 0x1630, + .name = "pci-dio-96b", + .n_8255 = 4, + .is_diodaq = 0, + }, + { + .dev_id = 0x13c0, + .name = "pxi-6508", + .n_8255 = 4, + .is_diodaq = 0, + }, + { + .dev_id = 0x0400, + .name = "pci-6503", + .n_8255 = 1, + .is_diodaq = 0, + }, + { + .dev_id = 0x1250, + .name = "pci-6503b", + .n_8255 = 1, + .is_diodaq = 0, + }, + { + .dev_id = 0x17d0, + .name = "pci-6503x", + .n_8255 = 1, + .is_diodaq = 0, + }, + { + .dev_id = 0x1800, + .name = "pxi-6503", + .n_8255 = 1, + .is_diodaq = 0, + }, +}; + +#define n_nidio_boards ARRAY_SIZE(nidio_boards) +#define this_board ((const struct nidio_board *)dev->board_ptr) + +static DEFINE_PCI_DEVICE_TABLE(ni_pcidio_pci_table) = { + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1150)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1320)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x12b0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x0160)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1630)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x13c0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x0400)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1250)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x17d0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1800)}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, ni_pcidio_pci_table); + +struct nidio96_private { + struct mite_struct *mite; + int boardtype; + int dio; + unsigned short OpModeBits; + struct mite_channel *di_mite_chan; + struct mite_dma_descriptor_ring *di_mite_ring; + spinlock_t mite_channel_lock; +}; +#define devpriv ((struct nidio96_private *)dev->private) + +static int ni_pcidio_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int ni_pcidio_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int ni_pcidio_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum); +static int nidio_find_device(struct comedi_device *dev, int bus, int slot); +static int ni_pcidio_ns_to_timer(int *nanosec, int round_mode); +static int setup_mite_dma(struct comedi_device *dev, + struct comedi_subdevice *s); + +#ifdef DEBUG_FLAGS +static void ni_pcidio_print_flags(unsigned int flags); +static void ni_pcidio_print_status(unsigned int status); +#else +#define ni_pcidio_print_flags(x) +#define ni_pcidio_print_status(x) +#endif + +static int ni_pcidio_request_di_mite_channel(struct comedi_device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + BUG_ON(devpriv->di_mite_chan); + devpriv->di_mite_chan = + mite_request_channel_in_range(devpriv->mite, + devpriv->di_mite_ring, 1, 2); + if (devpriv->di_mite_chan == NULL) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + comedi_error(dev, "failed to reserve mite dma channel."); + return -EBUSY; + } + devpriv->di_mite_chan->dir = COMEDI_INPUT; + writeb(primary_DMAChannel_bits(devpriv->di_mite_chan->channel) | + secondary_DMAChannel_bits(devpriv->di_mite_chan->channel), + devpriv->mite->daq_io_addr + DMA_Line_Control_Group1); + mmiowb(); + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + return 0; +} + +static void ni_pcidio_release_di_mite_channel(struct comedi_device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->di_mite_chan) { + mite_dma_disarm(devpriv->di_mite_chan); + mite_dma_reset(devpriv->di_mite_chan); + mite_release_channel(devpriv->di_mite_chan); + devpriv->di_mite_chan = NULL; + writeb(primary_DMAChannel_bits(0) | + secondary_DMAChannel_bits(0), + devpriv->mite->daq_io_addr + DMA_Line_Control_Group1); + mmiowb(); + } + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); +} + +static int nidio96_8255_cb(int dir, int port, int data, unsigned long iobase) +{ + if (dir) { + writeb(data, (void *)(iobase + port)); + return 0; + } else { + return readb((void *)(iobase + port)); + } +} + +void ni_pcidio_event(struct comedi_device *dev, struct comedi_subdevice *s) +{ + if (s-> + async->events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW)) { + ni_pcidio_cancel(dev, s); + } + comedi_event(dev, s); +} + +static int ni_pcidio_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long irq_flags; + int count; + + spin_lock_irqsave(&dev->spinlock, irq_flags); + spin_lock(&devpriv->mite_channel_lock); + if (devpriv->di_mite_chan) + mite_sync_input_dma(devpriv->di_mite_chan, s->async); + spin_unlock(&devpriv->mite_channel_lock); + count = s->async->buf_write_count - s->async->buf_read_count; + spin_unlock_irqrestore(&dev->spinlock, irq_flags); + return count; +} + +static irqreturn_t nidio_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices; + struct comedi_async *async = s->async; + struct mite_struct *mite = devpriv->mite; + + /* int i, j; */ + long int AuxData = 0; + short data1 = 0; + short data2 = 0; + int flags; + int status; + int work = 0; + unsigned int m_status = 0; + + /* interrupcions parasites */ + if (dev->attached == 0) { + /* assume it's from another card */ + return IRQ_NONE; + } + + /* Lock to avoid race with comedi_poll */ + spin_lock(&dev->spinlock); + + status = readb(devpriv->mite->daq_io_addr + + Interrupt_And_Window_Status); + flags = readb(devpriv->mite->daq_io_addr + Group_1_Flags); + + DPRINTK("ni_pcidio_interrupt: status=0x%02x,flags=0x%02x\n", + status, flags); + ni_pcidio_print_flags(flags); + ni_pcidio_print_status(status); + + /* printk("buf[0]=%08x\n",*(unsigned int *)async->prealloc_buf); */ + /* printk("buf[4096]=%08x\n", + *(unsigned int *)(async->prealloc_buf+4096)); */ + + spin_lock(&devpriv->mite_channel_lock); + if (devpriv->di_mite_chan) + m_status = mite_get_status(devpriv->di_mite_chan); +#ifdef MITE_DEBUG + mite_print_chsr(m_status); +#endif + /* printk("mite_bytes_transferred: %d\n", + mite_bytes_transferred(mite,DI_DMA_CHAN)); */ + + /* mite_dump_regs(mite); */ + if (m_status & CHSR_INT) { + if (m_status & CHSR_LINKC) { + writel(CHOR_CLRLC, + mite->mite_io_addr + + MITE_CHOR(devpriv->di_mite_chan->channel)); + mite_sync_input_dma(devpriv->di_mite_chan, s->async); + /* XXX need to byteswap */ + } + if (m_status & ~(CHSR_INT | CHSR_LINKC | CHSR_DONE | CHSR_DRDY | + CHSR_DRQ1 | CHSR_MRDY)) { + DPRINTK("unknown mite interrupt, disabling IRQ\n"); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + disable_irq(dev->irq); + } + } + spin_unlock(&devpriv->mite_channel_lock); + + while (status & DataLeft) { + work++; + if (work > 20) { + DPRINTK("too much work in interrupt\n"); + writeb(0x00, + devpriv->mite->daq_io_addr + + Master_DMA_And_Interrupt_Control); + break; + } + + flags &= IntEn; + + if (flags & TransferReady) { + /* DPRINTK("TransferReady\n"); */ + while (flags & TransferReady) { + work++; + if (work > 100) { + DPRINTK("too much work in interrupt\n"); + writeb(0x00, + devpriv->mite->daq_io_addr + + Master_DMA_And_Interrupt_Control + ); + goto out; + } + AuxData = + readl(devpriv->mite->daq_io_addr + + Group_1_FIFO); + data1 = AuxData & 0xffff; + data2 = (AuxData & 0xffff0000) >> 16; + comedi_buf_put(async, data1); + comedi_buf_put(async, data2); + /* DPRINTK("read:%d, %d\n",data1,data2); */ + flags = readb(devpriv->mite->daq_io_addr + + Group_1_Flags); + } + /* DPRINTK("buf_int_count: %d\n", + async->buf_int_count); */ + /* DPRINTK("1) IntEn=%d,flags=%d,status=%d\n", + IntEn,flags,status); */ + /* ni_pcidio_print_flags(flags); */ + /* ni_pcidio_print_status(status); */ + async->events |= COMEDI_CB_BLOCK; + } + + if (flags & CountExpired) { + DPRINTK("CountExpired\n"); + writeb(ClearExpired, + devpriv->mite->daq_io_addr + + Group_1_Second_Clear); + async->events |= COMEDI_CB_EOA; + + writeb(0x00, devpriv->mite->daq_io_addr + OpMode); + break; + } else if (flags & Waited) { + DPRINTK("Waited\n"); + writeb(ClearWaited, + devpriv->mite->daq_io_addr + + Group_1_First_Clear); + async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + break; + } else if (flags & PrimaryTC) { + DPRINTK("PrimaryTC\n"); + writeb(ClearPrimaryTC, + devpriv->mite->daq_io_addr + + Group_1_First_Clear); + async->events |= COMEDI_CB_EOA; + } else if (flags & SecondaryTC) { + DPRINTK("SecondaryTC\n"); + writeb(ClearSecondaryTC, + devpriv->mite->daq_io_addr + + Group_1_First_Clear); + async->events |= COMEDI_CB_EOA; + } +#if 0 + else { + printk("ni_pcidio: unknown interrupt\n"); + async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + writeb(0x00, + devpriv->mite->daq_io_addr + + Master_DMA_And_Interrupt_Control); + } +#endif + flags = readb(devpriv->mite->daq_io_addr + Group_1_Flags); + status = readb(devpriv->mite->daq_io_addr + + Interrupt_And_Window_Status); + /* DPRINTK("loop end: IntEn=0x%02x,flags=0x%02x," + "status=0x%02x\n", IntEn, flags, status); */ + /* ni_pcidio_print_flags(flags); */ + /* ni_pcidio_print_status(status); */ + } + +out: + ni_pcidio_event(dev, s); +#if 0 + if (!tag) { + writeb(0x03, + devpriv->mite->daq_io_addr + + Master_DMA_And_Interrupt_Control); + } +#endif + + spin_unlock(&dev->spinlock); + return IRQ_HANDLED; +} + +#ifdef DEBUG_FLAGS +static const char *const flags_strings[] = { + "TransferReady", "CountExpired", "2", "3", + "4", "Waited", "PrimaryTC", "SecondaryTC", +}; + +static void ni_pcidio_print_flags(unsigned int flags) +{ + int i; + + printk(KERN_INFO "group_1_flags:"); + for (i = 7; i >= 0; i--) { + if (flags & (1 << i)) + printk(" %s", flags_strings[i]); + } + printk("\n"); +} + +static char *status_strings[] = { + "DataLeft1", "Reserved1", "Req1", "StopTrig1", + "DataLeft2", "Reserved2", "Req2", "StopTrig2", +}; + +static void ni_pcidio_print_status(unsigned int flags) +{ + int i; + + printk(KERN_INFO "group_status:"); + for (i = 7; i >= 0; i--) { + if (flags & (1 << i)) + printk(" %s", status_strings[i]); + } + printk("\n"); +} +#endif + +#ifdef unused +static void debug_int(struct comedi_device *dev) +{ + int a, b; + static int n_int; + struct timeval tv; + + do_gettimeofday(&tv); + a = readb(devpriv->mite->daq_io_addr + Group_Status); + b = readb(devpriv->mite->daq_io_addr + Group_1_Flags); + + if (n_int < 10) { + DPRINTK("status 0x%02x flags 0x%02x time %06d\n", a, b, + (int)tv.tv_usec); + } + + while (b & 1) { + writew(0xff, devpriv->mite->daq_io_addr + Group_1_FIFO); + b = readb(devpriv->mite->daq_io_addr + Group_1_Flags); + } + + b = readb(devpriv->mite->daq_io_addr + Group_1_Flags); + + if (n_int < 10) { + DPRINTK("new status 0x%02x\n", b); + n_int++; + } +} +#endif + +static int ni_pcidio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 1) + return -EINVAL; + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << CR_CHAN(insn->chanspec); + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << CR_CHAN(insn->chanspec)); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s-> + io_bits & (1 << CR_CHAN(insn->chanspec))) ? COMEDI_OUTPUT : + COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + } + writel(s->io_bits, devpriv->mite->daq_io_addr + Port_Pin_Directions(0)); + + return 1; +} + +static int ni_pcidio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + writel(s->state, devpriv->mite->daq_io_addr + Port_IO(0)); + } + data[1] = readl(devpriv->mite->daq_io_addr + Port_IO(0)); + + return 2; +} + +static int ni_pcidio_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually + compatible */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_INT) + err++; + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + /* same for both TRIG_INT and TRIG_NOW */ + cmd->start_arg = 0; + err++; + } +#define MAX_SPEED (TIMER_BASE) /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < MAX_SPEED) { + cmd->scan_begin_arg = MAX_SPEED; + err++; + } + /* no minimum speed */ + } else { + /* TRIG_EXT */ + /* should be level/edge, hi/lo specification here */ + if ((cmd->scan_begin_arg & ~(CR_EDGE | CR_INVERT)) != 0) { + cmd->scan_begin_arg &= (CR_EDGE | CR_INVERT); + err++; + } + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + /* no limit */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + ni_pcidio_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + return 0; +} + +static int ni_pcidio_ns_to_timer(int *nanosec, int round_mode) +{ + int divider, base; + + base = TIMER_BASE; + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + + *nanosec = base * divider; + return divider; +} + +static int ni_pcidio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + + /* XXX configure ports for input */ + writel(0x0000, devpriv->mite->daq_io_addr + Port_Pin_Directions(0)); + + if (1) { + /* enable fifos A B C D */ + writeb(0x0f, devpriv->mite->daq_io_addr + Data_Path); + + /* set transfer width a 32 bits */ + writeb(TransferWidth(0) | TransferLength(0), + devpriv->mite->daq_io_addr + Transfer_Size_Control); + } else { + writeb(0x03, devpriv->mite->daq_io_addr + Data_Path); + writeb(TransferWidth(3) | TransferLength(0), + devpriv->mite->daq_io_addr + Transfer_Size_Control); + } + + /* protocol configuration */ + if (cmd->scan_begin_src == TRIG_TIMER) { + /* page 4-5, "input with internal REQs" */ + writeb(0, devpriv->mite->daq_io_addr + OpMode); + writeb(0x00, devpriv->mite->daq_io_addr + ClockReg); + writeb(1, devpriv->mite->daq_io_addr + Sequence); + writeb(0x04, devpriv->mite->daq_io_addr + ReqReg); + writeb(4, devpriv->mite->daq_io_addr + BlockMode); + writeb(3, devpriv->mite->daq_io_addr + LinePolarities); + writeb(0xc0, devpriv->mite->daq_io_addr + AckSer); + writel(ni_pcidio_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_NEAREST), + devpriv->mite->daq_io_addr + StartDelay); + writeb(1, devpriv->mite->daq_io_addr + ReqDelay); + writeb(1, devpriv->mite->daq_io_addr + ReqNotDelay); + writeb(1, devpriv->mite->daq_io_addr + AckDelay); + writeb(0x0b, devpriv->mite->daq_io_addr + AckNotDelay); + writeb(0x01, devpriv->mite->daq_io_addr + Data1Delay); + /* manual, page 4-5: ClockSpeed comment is incorrectly listed + * on DAQOptions */ + writew(0, devpriv->mite->daq_io_addr + ClockSpeed); + writeb(0, devpriv->mite->daq_io_addr + DAQOptions); + } else { + /* TRIG_EXT */ + /* page 4-5, "input with external REQs" */ + writeb(0, devpriv->mite->daq_io_addr + OpMode); + writeb(0x00, devpriv->mite->daq_io_addr + ClockReg); + writeb(0, devpriv->mite->daq_io_addr + Sequence); + writeb(0x00, devpriv->mite->daq_io_addr + ReqReg); + writeb(4, devpriv->mite->daq_io_addr + BlockMode); + if (!(cmd->scan_begin_arg & CR_INVERT)) { + /* Leading Edge pulse mode */ + writeb(0, devpriv->mite->daq_io_addr + LinePolarities); + } else { + /* Trailing Edge pulse mode */ + writeb(2, devpriv->mite->daq_io_addr + LinePolarities); + } + writeb(0x00, devpriv->mite->daq_io_addr + AckSer); + writel(1, devpriv->mite->daq_io_addr + StartDelay); + writeb(1, devpriv->mite->daq_io_addr + ReqDelay); + writeb(1, devpriv->mite->daq_io_addr + ReqNotDelay); + writeb(1, devpriv->mite->daq_io_addr + AckDelay); + writeb(0x0C, devpriv->mite->daq_io_addr + AckNotDelay); + writeb(0x10, devpriv->mite->daq_io_addr + Data1Delay); + writew(0, devpriv->mite->daq_io_addr + ClockSpeed); + writeb(0x60, devpriv->mite->daq_io_addr + DAQOptions); + } + + if (cmd->stop_src == TRIG_COUNT) { + writel(cmd->stop_arg, + devpriv->mite->daq_io_addr + Transfer_Count); + } else { + /* XXX */ + } + +#ifdef USE_DMA + writeb(ClearPrimaryTC | ClearSecondaryTC, + devpriv->mite->daq_io_addr + Group_1_First_Clear); + + { + int retval = setup_mite_dma(dev, s); + if (retval) + return retval; + } +#else + writeb(0x00, devpriv->mite->daq_io_addr + DMA_Line_Control_Group1); +#endif + writeb(0x00, devpriv->mite->daq_io_addr + DMA_Line_Control_Group2); + + /* clear and enable interrupts */ + writeb(0xff, devpriv->mite->daq_io_addr + Group_1_First_Clear); + /* writeb(ClearExpired, + devpriv->mite->daq_io_addr+Group_1_Second_Clear); */ + + writeb(IntEn, devpriv->mite->daq_io_addr + Interrupt_Control); + writeb(0x03, + devpriv->mite->daq_io_addr + Master_DMA_And_Interrupt_Control); + + if (cmd->stop_src == TRIG_NONE) { + devpriv->OpModeBits = DataLatching(0) | RunMode(7); + } else { /* TRIG_TIMER */ + devpriv->OpModeBits = Numbered | RunMode(7); + } + if (cmd->start_src == TRIG_NOW) { + /* start */ + writeb(devpriv->OpModeBits, + devpriv->mite->daq_io_addr + OpMode); + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + s->async->inttrig = ni_pcidio_inttrig; + } + + DPRINTK("ni_pcidio: command started\n"); + return 0; +} + +static int setup_mite_dma(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int retval; + unsigned long flags; + + retval = ni_pcidio_request_di_mite_channel(dev); + if (retval) + return retval; + + /* write alloc the entire buffer */ + comedi_buf_write_alloc(s->async, s->async->prealloc_bufsz); + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + if (devpriv->di_mite_chan) { + mite_prep_dma(devpriv->di_mite_chan, 32, 32); + mite_dma_arm(devpriv->di_mite_chan); + } else + retval = -EIO; + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + + return retval; +} + +static int ni_pcidio_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum) +{ + if (trignum != 0) + return -EINVAL; + + writeb(devpriv->OpModeBits, devpriv->mite->daq_io_addr + OpMode); + s->async->inttrig = NULL; + + return 1; +} + +static int ni_pcidio_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + writeb(0x00, + devpriv->mite->daq_io_addr + Master_DMA_And_Interrupt_Control); + ni_pcidio_release_di_mite_channel(dev); + + return 0; +} + +static int ni_pcidio_change(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size) +{ + int ret; + + ret = mite_buf_change(devpriv->di_mite_ring, s->async); + if (ret < 0) + return ret; + + memset(s->async->prealloc_buf, 0xaa, s->async->prealloc_bufsz); + + return 0; +} + +static int pci_6534_load_fpga(struct comedi_device *dev, int fpga_index, + u8 *data, int data_len) +{ + static const int timeout = 1000; + int i, j; + writew(0x80 | fpga_index, + devpriv->mite->daq_io_addr + Firmware_Control_Register); + writew(0xc0 | fpga_index, + devpriv->mite->daq_io_addr + Firmware_Control_Register); + for (i = 0; + (readw(devpriv->mite->daq_io_addr + + Firmware_Status_Register) & 0x2) == 0 && i < timeout; ++i) { + udelay(1); + } + if (i == timeout) { + printk(KERN_WARNING "ni_pcidio: failed to load fpga %i, " + "waiting for status 0x2\n", fpga_index); + return -EIO; + } + writew(0x80 | fpga_index, + devpriv->mite->daq_io_addr + Firmware_Control_Register); + for (i = 0; + readw(devpriv->mite->daq_io_addr + Firmware_Status_Register) != + 0x3 && i < timeout; ++i) { + udelay(1); + } + if (i == timeout) { + printk(KERN_WARNING "ni_pcidio: failed to load fpga %i, " + "waiting for status 0x3\n", fpga_index); + return -EIO; + } + for (j = 0; j + 1 < data_len;) { + unsigned int value = data[j++]; + value |= data[j++] << 8; + writew(value, + devpriv->mite->daq_io_addr + Firmware_Data_Register); + for (i = 0; + (readw(devpriv->mite->daq_io_addr + + Firmware_Status_Register) & 0x2) == 0 + && i < timeout; ++i) { + udelay(1); + } + if (i == timeout) { + printk("ni_pcidio: failed to load word into fpga %i\n", + fpga_index); + return -EIO; + } + if (need_resched()) + schedule(); + } + writew(0x0, devpriv->mite->daq_io_addr + Firmware_Control_Register); + return 0; +} + +static int pci_6534_reset_fpga(struct comedi_device *dev, int fpga_index) +{ + return pci_6534_load_fpga(dev, fpga_index, NULL, 0); +} + +static int pci_6534_reset_fpgas(struct comedi_device *dev) +{ + int ret; + int i; + writew(0x0, devpriv->mite->daq_io_addr + Firmware_Control_Register); + for (i = 0; i < 3; ++i) { + ret = pci_6534_reset_fpga(dev, i); + if (ret < 0) + break; + } + writew(0x0, devpriv->mite->daq_io_addr + Firmware_Mask_Register); + return ret; +} + +static void pci_6534_init_main_fpga(struct comedi_device *dev) +{ + writel(0, devpriv->mite->daq_io_addr + FPGA_Control1_Register); + writel(0, devpriv->mite->daq_io_addr + FPGA_Control2_Register); + writel(0, devpriv->mite->daq_io_addr + FPGA_SCALS_Counter_Register); + writel(0, devpriv->mite->daq_io_addr + FPGA_SCAMS_Counter_Register); + writel(0, devpriv->mite->daq_io_addr + FPGA_SCBLS_Counter_Register); + writel(0, devpriv->mite->daq_io_addr + FPGA_SCBMS_Counter_Register); +} + +static int pci_6534_upload_firmware(struct comedi_device *dev, int options[]) +{ + int ret; + void *main_fpga_data, *scarab_a_data, *scarab_b_data; + int main_fpga_data_len, scarab_a_data_len, scarab_b_data_len; + + if (options[COMEDI_DEVCONF_AUX_DATA_LENGTH] == 0) + return 0; + ret = pci_6534_reset_fpgas(dev); + if (ret < 0) + return ret; + main_fpga_data = comedi_aux_data(options, 0); + main_fpga_data_len = options[COMEDI_DEVCONF_AUX_DATA0_LENGTH]; + ret = pci_6534_load_fpga(dev, 2, main_fpga_data, main_fpga_data_len); + if (ret < 0) + return ret; + pci_6534_init_main_fpga(dev); + scarab_a_data = comedi_aux_data(options, 1); + scarab_a_data_len = options[COMEDI_DEVCONF_AUX_DATA1_LENGTH]; + ret = pci_6534_load_fpga(dev, 0, scarab_a_data, scarab_a_data_len); + if (ret < 0) + return ret; + scarab_b_data = comedi_aux_data(options, 2); + scarab_b_data_len = options[COMEDI_DEVCONF_AUX_DATA2_LENGTH]; + ret = pci_6534_load_fpga(dev, 1, scarab_b_data, scarab_b_data_len); + if (ret < 0) + return ret; + return 0; +} + +static int nidio_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int i; + int ret; + int n_subdevices; + unsigned int irq; + + printk(KERN_INFO "comedi%d: nidio:", dev->minor); + + ret = alloc_private(dev, sizeof(struct nidio96_private)); + if (ret < 0) + return ret; + spin_lock_init(&devpriv->mite_channel_lock); + + ret = nidio_find_device(dev, it->options[0], it->options[1]); + if (ret < 0) + return ret; + + ret = mite_setup(devpriv->mite); + if (ret < 0) { + printk(KERN_WARNING "error setting up mite\n"); + return ret; + } + comedi_set_hw_dev(dev, &devpriv->mite->pcidev->dev); + devpriv->di_mite_ring = mite_alloc_ring(devpriv->mite); + if (devpriv->di_mite_ring == NULL) + return -ENOMEM; + + dev->board_name = this_board->name; + irq = mite_irq(devpriv->mite); + printk(KERN_INFO " %s", dev->board_name); + if (this_board->uses_firmware) { + ret = pci_6534_upload_firmware(dev, it->options); + if (ret < 0) + return ret; + } + if (!this_board->is_diodaq) + n_subdevices = this_board->n_8255; + else + n_subdevices = 1; + + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) + return ret; + + if (!this_board->is_diodaq) { + for (i = 0; i < this_board->n_8255; i++) { + subdev_8255_init(dev, dev->subdevices + i, + nidio96_8255_cb, + (unsigned long)(devpriv->mite-> + daq_io_addr + + NIDIO_8255_BASE(i))); + } + } else { + + printk(KERN_INFO " rev=%d", + readb(devpriv->mite->daq_io_addr + Chip_Version)); + + s = dev->subdevices + 0; + + dev->read_subdev = s; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = + SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL | SDF_PACKED | + SDF_CMD_READ; + s->n_chan = 32; + s->range_table = &range_digital; + s->maxdata = 1; + s->insn_config = &ni_pcidio_insn_config; + s->insn_bits = &ni_pcidio_insn_bits; + s->do_cmd = &ni_pcidio_cmd; + s->do_cmdtest = &ni_pcidio_cmdtest; + s->cancel = &ni_pcidio_cancel; + s->len_chanlist = 32; /* XXX */ + s->buf_change = &ni_pcidio_change; + s->async_dma_dir = DMA_BIDIRECTIONAL; + s->poll = &ni_pcidio_poll; + + writel(0, devpriv->mite->daq_io_addr + Port_IO(0)); + writel(0, devpriv->mite->daq_io_addr + Port_Pin_Directions(0)); + writel(0, devpriv->mite->daq_io_addr + Port_Pin_Mask(0)); + + /* disable interrupts on board */ + writeb(0x00, + devpriv->mite->daq_io_addr + + Master_DMA_And_Interrupt_Control); + + ret = request_irq(irq, nidio_interrupt, IRQF_SHARED, + "ni_pcidio", dev); + if (ret < 0) + printk(KERN_WARNING " irq not available"); + + dev->irq = irq; + } + + printk("\n"); + + return 0; +} + +static int nidio_detach(struct comedi_device *dev) +{ + int i; + + if (this_board && !this_board->is_diodaq) { + for (i = 0; i < this_board->n_8255; i++) + subdev_8255_cleanup(dev, dev->subdevices + i); + } + + if (dev->irq) + free_irq(dev->irq, dev); + + if (devpriv) { + if (devpriv->di_mite_ring) { + mite_free_ring(devpriv->di_mite_ring); + devpriv->di_mite_ring = NULL; + } + if (devpriv->mite) + mite_unsetup(devpriv->mite); + } + return 0; +} + +static int nidio_find_device(struct comedi_device *dev, int bus, int slot) +{ + struct mite_struct *mite; + int i; + + for (mite = mite_devices; mite; mite = mite->next) { + if (mite->used) + continue; + if (bus || slot) { + if (bus != mite->pcidev->bus->number || + slot != PCI_SLOT(mite->pcidev->devfn)) + continue; + } + for (i = 0; i < n_nidio_boards; i++) { + if (mite_device_id(mite) == nidio_boards[i].dev_id) { + dev->board_ptr = nidio_boards + i; + devpriv->mite = mite; + + return 0; + } + } + } + printk(KERN_WARNING "no device found\n"); + mite_list_devices(); + return -EIO; +} + +static int __devinit driver_pcidio_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_pcidio.driver_name); +} + +static void __devexit driver_pcidio_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_pcidio_pci_driver = { + .id_table = ni_pcidio_pci_table, + .probe = &driver_pcidio_pci_probe, + .remove = __devexit_p(&driver_pcidio_pci_remove) +}; + +static int __init driver_pcidio_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_pcidio); + if (retval < 0) + return retval; + + driver_pcidio_pci_driver.name = (char *)driver_pcidio.driver_name; + return pci_register_driver(&driver_pcidio_pci_driver); +} + +static void __exit driver_pcidio_cleanup_module(void) +{ + pci_unregister_driver(&driver_pcidio_pci_driver); + comedi_driver_unregister(&driver_pcidio); +} + +module_init(driver_pcidio_init_module); +module_exit(driver_pcidio_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_pcimio.c b/drivers/staging/comedi/drivers/ni_pcimio.c new file mode 100644 index 00000000..27baefa3 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_pcimio.c @@ -0,0 +1,1879 @@ +/* + comedi/drivers/ni_pcimio.c + Hardware driver for NI PCI-MIO E series cards + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-8 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* +Driver: ni_pcimio +Description: National Instruments PCI-MIO-E series and M series (all boards) +Author: ds, John Hallen, Frank Mori Hess, Rolf Mueller, Herbert Peremans, + Herman Bruyninckx, Terry Barnaby +Status: works +Devices: [National Instruments] PCI-MIO-16XE-50 (ni_pcimio), + PCI-MIO-16XE-10, PXI-6030E, PCI-MIO-16E-1, PCI-MIO-16E-4, PCI-6014, PCI-6040E, + PXI-6040E, PCI-6030E, PCI-6031E, PCI-6032E, PCI-6033E, PCI-6071E, PCI-6023E, + PCI-6024E, PCI-6025E, PXI-6025E, PCI-6034E, PCI-6035E, PCI-6052E, + PCI-6110, PCI-6111, PCI-6220, PCI-6221, PCI-6224, PXI-6224, + PCI-6225, PXI-6225, PCI-6229, PCI-6250, PCI-6251, PCIe-6251, PXIe-6251, + PCI-6254, PCI-6259, PCIe-6259, + PCI-6280, PCI-6281, PXI-6281, PCI-6284, PCI-6289, + PCI-6711, PXI-6711, PCI-6713, PXI-6713, + PXI-6071E, PCI-6070E, PXI-6070E, + PXI-6052E, PCI-6036E, PCI-6731, PCI-6733, PXI-6733, + PCI-6143, PXI-6143 +Updated: Mon, 09 Jan 2012 14:52:48 +0000 + +These boards are almost identical to the AT-MIO E series, except that +they use the PCI bus instead of ISA (i.e., AT). See the notes for +the ni_atmio.o driver for additional information about these boards. + +Autocalibration is supported on many of the devices, using the +comedi_calibrate (or comedi_soft_calibrate for m-series) utility. +M-Series boards do analog input and analog output calibration entirely +in software. The software calibration corrects +the analog input for offset, gain and +nonlinearity. The analog outputs are corrected for offset and gain. +See the comedilib documentation on comedi_get_softcal_converter() for +more information. + +By default, the driver uses DMA to transfer analog input data to +memory. When DMA is enabled, not all triggering features are +supported. + +Digital I/O may not work on 673x. + +Note that the PCI-6143 is a simultaineous sampling device with 8 convertors. +With this board all of the convertors perform one simultaineous sample during +a scan interval. The period for a scan is used for the convert time in a +Comedi cmd. The convert trigger source is normally set to TRIG_NOW by default. + +The RTSI trigger bus is supported on these cards on +subdevice 10. See the comedilib documentation for details. + +Information (number of channels, bits, etc.) for some devices may be +incorrect. Please check this and submit a bug if there are problems +for your device. + +SCXI is probably broken for m-series boards. + +Bugs: + - When DMA is enabled, COMEDI_EV_CONVERT does + not work correctly. + +*/ +/* + The PCI-MIO E series driver was originally written by + Tomasz Motylewski <...>, and ported to comedi by ds. + + References: + + 341079b.pdf PCI E Series Register-Level Programmer Manual + 340934b.pdf DAQ-STC reference manual + + 322080b.pdf 6711/6713/6715 User Manual + + 320945c.pdf PCI E Series User Manual + 322138a.pdf PCI-6052E and DAQPad-6052E User Manual + + ISSUES: + + need to deal with external reference for DAC, and other DAC + properties in board properties + + deal with at-mio-16de-10 revision D to N changes, etc. + + need to add other CALDAC type + + need to slow down DAC loading. I don't trust NI's claim that + two writes to the PCI bus slows IO enough. I would prefer to + use udelay(). Timing specs: (clock) + AD8522 30ns + DAC8043 120ns + DAC8800 60ns + MB88341 ? + +*/ + +#include "../comedidev.h" + +#include +#include + +#include "ni_stc.h" +#include "mite.h" + +/* #define PCI_DEBUG */ + +#define PCIDMA + +#define PCIMIO 1 +#undef ATMIO + +#define MAX_N_CALDACS (16+16+2) + +#define DRV_NAME "ni_pcimio" + +/* The following two tables must be in the same order */ +static DEFINE_PCI_DEVICE_TABLE(ni_pci_table) = { + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x0162)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1170)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1180)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1190)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x11b0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x11c0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x11d0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1270)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1330)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1340)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1350)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x14e0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x14f0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1580)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x15b0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1880)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1870)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x18b0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x18c0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2410)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2420)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2430)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2890)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x28c0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2a60)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2a70)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2a80)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2ab0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2b80)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2b90)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2c80)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2ca0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70aa)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70ab)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70ac)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70af)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70b0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70b4)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70b6)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70b7)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70b8)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70bc)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70bd)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70bf)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70c0)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x70f2)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x710d)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x716c)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x716d)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x717f)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x71bc)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x717d)}, + {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x72e8)}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, ni_pci_table); + +/* These are not all the possible ao ranges for 628x boards. + They can do OFFSET +- REFERENCE where OFFSET can be + 0V, 5V, APFI<0,1>, or AO<0...3> and RANGE can + be 10V, 5V, 2V, 1V, APFI<0,1>, AO<0...3>. That's + 63 different possibilities. An AO channel + can not act as it's own OFFSET or REFERENCE. +*/ +static const struct comedi_lrange range_ni_M_628x_ao = { 8, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE(-2, 2), + RANGE(-1, 1), + RANGE(-5, 15), + RANGE(0, 10), + RANGE(3, 7), + RANGE(4, 6), + RANGE_ext(-1, 1) + } +}; + +static const struct comedi_lrange range_ni_M_625x_ao = { 3, { + RANGE(-10, 10), + RANGE(-5, 5), + RANGE_ext(-1, 1) + } +}; + +static const struct comedi_lrange range_ni_M_622x_ao = { 1, { + RANGE(-10, 10), + } +}; + +static const struct ni_board_struct ni_boards[] = { + { + .device_id = 0x0162, /* NI also says 0x1620. typo? */ + .name = "pci-mio-16xe-50", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 2048, + .alwaysdither = 1, + .gainlkup = ai_gain_8, + .ai_speed = 50000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 50000, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043}, + .has_8255 = 0, + }, + { + .device_id = 0x1170, + .name = "pci-mio-16xe-10", /* aka pci-6030E */ + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + .has_8255 = 0, + }, + { + .device_id = 0x28c0, + .name = "pci-6014", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 0, + }, + { + .device_id = 0x11d0, + .name = "pxi-6030e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + .has_8255 = 0, + }, + { + .device_id = 0x1180, + .name = "pci-mio-16e-1", /* aka pci-6070e */ + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = {mb88341}, + .has_8255 = 0, + }, + { + .device_id = 0x1190, + .name = "pci-mio-16e-4", /* aka pci-6040e */ + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + /* .Note = there have been reported problems with full speed + * on this board */ + .ai_speed = 2000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 512, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, /* doc says mb88341 */ + .has_8255 = 0, + }, + { + .device_id = 0x11c0, + .name = "pxi-6040e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_16, + .ai_speed = 2000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 512, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = {mb88341}, + .has_8255 = 0, + }, + + { + .device_id = 0x1330, + .name = "pci-6031e", + .n_adchan = 64, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + .has_8255 = 0, + }, + { + .device_id = 0x1270, + .name = "pci-6032e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + .has_8255 = 0, + }, + { + .device_id = 0x1340, + .name = "pci-6033e", + .n_adchan = 64, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + .has_8255 = 0, + }, + { + .device_id = 0x1350, + .name = "pci-6071e", + .n_adchan = 64, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 0, + }, + { + .device_id = 0x2a60, + .name = "pci-6023e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 0, + .aobits = 0, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, /* manual is wrong */ + .has_8255 = 0, + }, + { + .device_id = 0x2a70, + .name = "pci-6024e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, /* manual is wrong */ + .has_8255 = 0, + }, + { + .device_id = 0x2a80, + .name = "pci-6025e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, /* manual is wrong */ + .has_8255 = 1, + }, + { + .device_id = 0x2ab0, + .name = "pxi-6025e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 0, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, /* manual is wrong */ + .has_8255 = 1, + }, + + { + .device_id = 0x2ca0, + .name = "pci-6034e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 0, + }, + { + .device_id = 0x2c80, + .name = "pci-6035e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 0, + }, + { + .device_id = 0x18b0, + .name = "pci-6052e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 3000, + .n_aochan = 2, + .aobits = 16, + .ao_unipolar = 1, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 3000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug, ad8804_debug, ad8522}, /* manual is wrong */ + }, + {.device_id = 0x14e0, + .name = "pci-6110", + .n_adchan = 4, + .adbits = 12, + .ai_fifo_depth = 8192, + .alwaysdither = 0, + .gainlkup = ai_gain_611x, + .ai_speed = 200, + .n_aochan = 2, + .aobits = 16, + .reg_type = ni_reg_611x, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .num_p0_dio_channels = 8, + .caldac = {ad8804, ad8804}, + }, + { + .device_id = 0x14f0, + .name = "pci-6111", + .n_adchan = 2, + .adbits = 12, + .ai_fifo_depth = 8192, + .alwaysdither = 0, + .gainlkup = ai_gain_611x, + .ai_speed = 200, + .n_aochan = 2, + .aobits = 16, + .reg_type = ni_reg_611x, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .num_p0_dio_channels = 8, + .caldac = {ad8804, ad8804}, + }, +#if 0 + /* The 6115 boards probably need their own driver */ + { + .device_id = 0x2ed0, + .name = "pci-6115", + .n_adchan = 4, + .adbits = 12, + .ai_fifo_depth = 8192, + .alwaysdither = 0, + .gainlkup = ai_gain_611x, + .ai_speed = 100, + .n_aochan = 2, + .aobits = 16, + .ao_671x = 1, + .ao_unipolar = 0, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .num_p0_dio_channels = 8, + .reg_611x = 1, + .caldac = {ad8804_debug, ad8804_debug, ad8804_debug}, /* XXX */ + }, +#endif +#if 0 + { + .device_id = 0x0000, + .name = "pxi-6115", + .n_adchan = 4, + .adbits = 12, + .ai_fifo_depth = 8192, + .alwaysdither = 0, + .gainlkup = ai_gain_611x, + .ai_speed = 100, + .n_aochan = 2, + .aobits = 16, + .ao_671x = 1, + .ao_unipolar = 0, + .ao_fifo_depth = 2048, + .ao_speed = 250, + .reg_611x = 1, + .num_p0_dio_channels = 8, + caldac = {ad8804_debug, ad8804_debug, ad8804_debug}, /* XXX */ + }, +#endif + { + .device_id = 0x1880, + .name = "pci-6711", + .n_adchan = 0, /* no analog input */ + .n_aochan = 4, + .aobits = 12, + .ao_unipolar = 0, + .ao_fifo_depth = 16384, + /* data sheet says 8192, but fifo really holds 16384 samples */ + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6711, + .caldac = {ad8804_debug}, + }, + { + .device_id = 0x2b90, + .name = "pxi-6711", + .n_adchan = 0, /* no analog input */ + .n_aochan = 4, + .aobits = 12, + .ao_unipolar = 0, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6711, + .caldac = {ad8804_debug}, + }, + { + .device_id = 0x1870, + .name = "pci-6713", + .n_adchan = 0, /* no analog input */ + .n_aochan = 8, + .aobits = 12, + .ao_unipolar = 0, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6713, + .caldac = {ad8804_debug, ad8804_debug}, + }, + { + .device_id = 0x2b80, + .name = "pxi-6713", + .n_adchan = 0, /* no analog input */ + .n_aochan = 8, + .aobits = 12, + .ao_unipolar = 0, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6713, + .caldac = {ad8804_debug, ad8804_debug}, + }, + { + .device_id = 0x2430, + .name = "pci-6731", + .n_adchan = 0, /* no analog input */ + .n_aochan = 4, + .aobits = 16, + .ao_unipolar = 0, + .ao_fifo_depth = 8192, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6711, + .caldac = {ad8804_debug}, + }, +#if 0 /* need device ids */ + { + .device_id = 0x0, + .name = "pxi-6731", + .n_adchan = 0, /* no analog input */ + .n_aochan = 4, + .aobits = 16, + .ao_unipolar = 0, + .ao_fifo_depth = 8192, + .ao_range_table = &range_bipolar10, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6711, + .caldac = {ad8804_debug}, + }, +#endif + { + .device_id = 0x2410, + .name = "pci-6733", + .n_adchan = 0, /* no analog input */ + .n_aochan = 8, + .aobits = 16, + .ao_unipolar = 0, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6713, + .caldac = {ad8804_debug, ad8804_debug}, + }, + { + .device_id = 0x2420, + .name = "pxi-6733", + .n_adchan = 0, /* no analog input */ + .n_aochan = 8, + .aobits = 16, + .ao_unipolar = 0, + .ao_fifo_depth = 16384, + .ao_range_table = &range_bipolar10, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_6713, + .caldac = {ad8804_debug, ad8804_debug}, + }, + { + .device_id = 0x15b0, + .name = "pxi-6071e", + .n_adchan = 64, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 0, + }, + { + .device_id = 0x11b0, + .name = "pxi-6070e", + .n_adchan = 16, + .adbits = 12, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 12, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 1000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 0, + }, + { + .device_id = 0x18c0, + .name = "pxi-6052e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_16, + .ai_speed = 3000, + .n_aochan = 2, + .aobits = 16, + .ao_unipolar = 1, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_speed = 3000, + .num_p0_dio_channels = 8, + .caldac = {mb88341, mb88341, ad8522}, + }, + { + .device_id = 0x1580, + .name = "pxi-6031e", + .n_adchan = 64, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_14, + .ai_speed = 10000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 2048, + .ao_range_table = &range_ni_E_ao_ext, + .ao_unipolar = 1, + .ao_speed = 10000, + .num_p0_dio_channels = 8, + .caldac = {dac8800, dac8043, ad8522}, + }, + { + .device_id = 0x2890, + .name = "pci-6036e", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + .alwaysdither = 1, + .gainlkup = ai_gain_4, + .ai_speed = 5000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 0, + .ao_range_table = &range_bipolar10, + .ao_unipolar = 0, + .ao_speed = 100000, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug}, + .has_8255 = 0, + }, + { + .device_id = 0x70b0, + .name = "pci-6220", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 512, + /* .FIXME = guess */ + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .num_p0_dio_channels = 8, + .reg_type = ni_reg_622x, + .ao_unipolar = 0, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70af, + .name = "pci-6221", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_622x_ao, + .reg_type = ni_reg_622x, + .ao_unipolar = 0, + .ao_speed = 1200, + .num_p0_dio_channels = 8, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x71bc, + .name = "pci-6221_37pin", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_622x_ao, + .reg_type = ni_reg_622x, + .ao_unipolar = 0, + .ao_speed = 1200, + .num_p0_dio_channels = 8, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70f2, + .name = "pci-6224", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .reg_type = ni_reg_622x, + .ao_unipolar = 0, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70f3, + .name = "pxi-6224", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .reg_type = ni_reg_622x, + .ao_unipolar = 0, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x716c, + .name = "pci-6225", + .n_adchan = 80, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_622x_ao, + .reg_type = ni_reg_622x, + .ao_unipolar = 0, + .ao_speed = 1200, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x716d, + .name = "pxi-6225", + .n_adchan = 80, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_622x_ao, + .reg_type = ni_reg_622x, + .ao_unipolar = 0, + .ao_speed = 1200, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70aa, + .name = "pci-6229", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_622x, + .ai_speed = 4000, + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_622x_ao, + .reg_type = ni_reg_622x, + .ao_unipolar = 0, + .ao_speed = 1200, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70b4, + .name = "pci-6250", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .reg_type = ni_reg_625x, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70b8, + .name = "pci-6251", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_unipolar = 0, + .ao_speed = 357, + .num_p0_dio_channels = 8, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x717d, + .name = "pcie-6251", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_unipolar = 0, + .ao_speed = 357, + .num_p0_dio_channels = 8, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x72e8, + .name = "pxie-6251", + .n_adchan = 16, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_unipolar = 0, + .ao_speed = 357, + .num_p0_dio_channels = 8, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70b7, + .name = "pci-6254", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .reg_type = ni_reg_625x, + .ao_unipolar = 0, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70ab, + .name = "pci-6259", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_unipolar = 0, + .ao_speed = 357, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x717f, + .name = "pcie-6259", + .n_adchan = 32, + .adbits = 16, + .ai_fifo_depth = 4095, + .gainlkup = ai_gain_628x, + .ai_speed = 800, + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_625x_ao, + .reg_type = ni_reg_625x, + .ao_unipolar = 0, + .ao_speed = 357, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70b6, + .name = "pci-6280", + .n_adchan = 16, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 8191, + .reg_type = ni_reg_628x, + .ao_unipolar = 0, + .num_p0_dio_channels = 8, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70bd, + .name = "pci-6281", + .n_adchan = 16, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_unipolar = 1, + .ao_speed = 357, + .num_p0_dio_channels = 8, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70bf, + .name = "pxi-6281", + .n_adchan = 16, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 2, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_unipolar = 1, + .ao_speed = 357, + .num_p0_dio_channels = 8, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70bc, + .name = "pci-6284", + .n_adchan = 32, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 0, + .aobits = 0, + .ao_fifo_depth = 0, + .reg_type = ni_reg_628x, + .ao_unipolar = 0, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70ac, + .name = "pci-6289", + .n_adchan = 32, + .adbits = 18, + .ai_fifo_depth = 2047, + .gainlkup = ai_gain_628x, + .ai_speed = 1600, + .n_aochan = 4, + .aobits = 16, + .ao_fifo_depth = 8191, + .ao_range_table = &range_ni_M_628x_ao, + .reg_type = ni_reg_628x, + .ao_unipolar = 1, + .ao_speed = 357, + .num_p0_dio_channels = 32, + .caldac = {caldac_none}, + .has_8255 = 0, + }, + { + .device_id = 0x70C0, + .name = "pci-6143", + .n_adchan = 8, + .adbits = 16, + .ai_fifo_depth = 1024, + .alwaysdither = 0, + .gainlkup = ai_gain_6143, + .ai_speed = 4000, + .n_aochan = 0, + .aobits = 0, + .reg_type = ni_reg_6143, + .ao_unipolar = 0, + .ao_fifo_depth = 0, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug, ad8804_debug}, + }, + { + .device_id = 0x710D, + .name = "pxi-6143", + .n_adchan = 8, + .adbits = 16, + .ai_fifo_depth = 1024, + .alwaysdither = 0, + .gainlkup = ai_gain_6143, + .ai_speed = 4000, + .n_aochan = 0, + .aobits = 0, + .reg_type = ni_reg_6143, + .ao_unipolar = 0, + .ao_fifo_depth = 0, + .num_p0_dio_channels = 8, + .caldac = {ad8804_debug, ad8804_debug}, + }, +}; + +#define n_pcimio_boards ARRAY_SIZE(ni_boards) + +static int pcimio_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcimio_detach(struct comedi_device *dev); +static struct comedi_driver driver_pcimio = { + .driver_name = DRV_NAME, + .module = THIS_MODULE, + .attach = pcimio_attach, + .detach = pcimio_detach, +}; + +static int __devinit driver_pcimio_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_pcimio.driver_name); +} + +static void __devexit driver_pcimio_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_pcimio_pci_driver = { + .id_table = ni_pci_table, + .probe = &driver_pcimio_pci_probe, + .remove = __devexit_p(&driver_pcimio_pci_remove) +}; + +static int __init driver_pcimio_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_pcimio); + if (retval < 0) + return retval; + + driver_pcimio_pci_driver.name = (char *)driver_pcimio.driver_name; + return pci_register_driver(&driver_pcimio_pci_driver); +} + +static void __exit driver_pcimio_cleanup_module(void) +{ + pci_unregister_driver(&driver_pcimio_pci_driver); + comedi_driver_unregister(&driver_pcimio); +} + +module_init(driver_pcimio_init_module); +module_exit(driver_pcimio_cleanup_module); + +struct ni_private { +NI_PRIVATE_COMMON}; +#define devpriv ((struct ni_private *)dev->private) + +/* How we access registers */ + +#define ni_writel(a, b) (writel((a), devpriv->mite->daq_io_addr + (b))) +#define ni_readl(a) (readl(devpriv->mite->daq_io_addr + (a))) +#define ni_writew(a, b) (writew((a), devpriv->mite->daq_io_addr + (b))) +#define ni_readw(a) (readw(devpriv->mite->daq_io_addr + (a))) +#define ni_writeb(a, b) (writeb((a), devpriv->mite->daq_io_addr + (b))) +#define ni_readb(a) (readb(devpriv->mite->daq_io_addr + (a))) + +/* How we access STC registers */ + +/* We automatically take advantage of STC registers that can be + * read/written directly in the I/O space of the board. Most + * PCIMIO devices map the low 8 STC registers to iobase+addr*2. + * The 611x devices map the write registers to iobase+addr*2, and + * the read registers to iobase+(addr-1)*2. */ +/* However, the 611x boards still aren't working, so I'm disabling + * non-windowed STC access temporarily */ + +static void e_series_win_out(struct comedi_device *dev, uint16_t data, int reg) +{ + unsigned long flags; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(reg, Window_Address); + ni_writew(data, Window_Data); + spin_unlock_irqrestore(&devpriv->window_lock, flags); +} + +static uint16_t e_series_win_in(struct comedi_device *dev, int reg) +{ + unsigned long flags; + uint16_t ret; + + spin_lock_irqsave(&devpriv->window_lock, flags); + ni_writew(reg, Window_Address); + ret = ni_readw(Window_Data); + spin_unlock_irqrestore(&devpriv->window_lock, flags); + + return ret; +} + +static void m_series_stc_writew(struct comedi_device *dev, uint16_t data, + int reg) +{ + unsigned offset; + switch (reg) { + case ADC_FIFO_Clear: + offset = M_Offset_AI_FIFO_Clear; + break; + case AI_Command_1_Register: + offset = M_Offset_AI_Command_1; + break; + case AI_Command_2_Register: + offset = M_Offset_AI_Command_2; + break; + case AI_Mode_1_Register: + offset = M_Offset_AI_Mode_1; + break; + case AI_Mode_2_Register: + offset = M_Offset_AI_Mode_2; + break; + case AI_Mode_3_Register: + offset = M_Offset_AI_Mode_3; + break; + case AI_Output_Control_Register: + offset = M_Offset_AI_Output_Control; + break; + case AI_Personal_Register: + offset = M_Offset_AI_Personal; + break; + case AI_SI2_Load_A_Register: + /* this is actually a 32 bit register on m series boards */ + ni_writel(data, M_Offset_AI_SI2_Load_A); + return; + break; + case AI_SI2_Load_B_Register: + /* this is actually a 32 bit register on m series boards */ + ni_writel(data, M_Offset_AI_SI2_Load_B); + return; + break; + case AI_START_STOP_Select_Register: + offset = M_Offset_AI_START_STOP_Select; + break; + case AI_Trigger_Select_Register: + offset = M_Offset_AI_Trigger_Select; + break; + case Analog_Trigger_Etc_Register: + offset = M_Offset_Analog_Trigger_Etc; + break; + case AO_Command_1_Register: + offset = M_Offset_AO_Command_1; + break; + case AO_Command_2_Register: + offset = M_Offset_AO_Command_2; + break; + case AO_Mode_1_Register: + offset = M_Offset_AO_Mode_1; + break; + case AO_Mode_2_Register: + offset = M_Offset_AO_Mode_2; + break; + case AO_Mode_3_Register: + offset = M_Offset_AO_Mode_3; + break; + case AO_Output_Control_Register: + offset = M_Offset_AO_Output_Control; + break; + case AO_Personal_Register: + offset = M_Offset_AO_Personal; + break; + case AO_Start_Select_Register: + offset = M_Offset_AO_Start_Select; + break; + case AO_Trigger_Select_Register: + offset = M_Offset_AO_Trigger_Select; + break; + case Clock_and_FOUT_Register: + offset = M_Offset_Clock_and_FOUT; + break; + case Configuration_Memory_Clear: + offset = M_Offset_Configuration_Memory_Clear; + break; + case DAC_FIFO_Clear: + offset = M_Offset_AO_FIFO_Clear; + break; + case DIO_Control_Register: + printk + ("%s: FIXME: register 0x%x does not map cleanly on to m-series boards.\n", + __func__, reg); + return; + break; + case G_Autoincrement_Register(0): + offset = M_Offset_G0_Autoincrement; + break; + case G_Autoincrement_Register(1): + offset = M_Offset_G1_Autoincrement; + break; + case G_Command_Register(0): + offset = M_Offset_G0_Command; + break; + case G_Command_Register(1): + offset = M_Offset_G1_Command; + break; + case G_Input_Select_Register(0): + offset = M_Offset_G0_Input_Select; + break; + case G_Input_Select_Register(1): + offset = M_Offset_G1_Input_Select; + break; + case G_Mode_Register(0): + offset = M_Offset_G0_Mode; + break; + case G_Mode_Register(1): + offset = M_Offset_G1_Mode; + break; + case Interrupt_A_Ack_Register: + offset = M_Offset_Interrupt_A_Ack; + break; + case Interrupt_A_Enable_Register: + offset = M_Offset_Interrupt_A_Enable; + break; + case Interrupt_B_Ack_Register: + offset = M_Offset_Interrupt_B_Ack; + break; + case Interrupt_B_Enable_Register: + offset = M_Offset_Interrupt_B_Enable; + break; + case Interrupt_Control_Register: + offset = M_Offset_Interrupt_Control; + break; + case IO_Bidirection_Pin_Register: + offset = M_Offset_IO_Bidirection_Pin; + break; + case Joint_Reset_Register: + offset = M_Offset_Joint_Reset; + break; + case RTSI_Trig_A_Output_Register: + offset = M_Offset_RTSI_Trig_A_Output; + break; + case RTSI_Trig_B_Output_Register: + offset = M_Offset_RTSI_Trig_B_Output; + break; + case RTSI_Trig_Direction_Register: + offset = M_Offset_RTSI_Trig_Direction; + break; + /* FIXME: DIO_Output_Register (16 bit reg) is replaced by M_Offset_Static_Digital_Output (32 bit) + and M_Offset_SCXI_Serial_Data_Out (8 bit) */ + default: + printk(KERN_WARNING "%s: bug! unhandled register=0x%x in switch.\n", + __func__, reg); + BUG(); + return; + break; + } + ni_writew(data, offset); +} + +static uint16_t m_series_stc_readw(struct comedi_device *dev, int reg) +{ + unsigned offset; + switch (reg) { + case AI_Status_1_Register: + offset = M_Offset_AI_Status_1; + break; + case AO_Status_1_Register: + offset = M_Offset_AO_Status_1; + break; + case AO_Status_2_Register: + offset = M_Offset_AO_Status_2; + break; + case DIO_Serial_Input_Register: + return ni_readb(M_Offset_SCXI_Serial_Data_In); + break; + case Joint_Status_1_Register: + offset = M_Offset_Joint_Status_1; + break; + case Joint_Status_2_Register: + offset = M_Offset_Joint_Status_2; + break; + case G_Status_Register: + offset = M_Offset_G01_Status; + break; + default: + printk(KERN_WARNING "%s: bug! unhandled register=0x%x in switch.\n", + __func__, reg); + BUG(); + return 0; + break; + } + return ni_readw(offset); +} + +static void m_series_stc_writel(struct comedi_device *dev, uint32_t data, + int reg) +{ + unsigned offset; + switch (reg) { + case AI_SC_Load_A_Registers: + offset = M_Offset_AI_SC_Load_A; + break; + case AI_SI_Load_A_Registers: + offset = M_Offset_AI_SI_Load_A; + break; + case AO_BC_Load_A_Register: + offset = M_Offset_AO_BC_Load_A; + break; + case AO_UC_Load_A_Register: + offset = M_Offset_AO_UC_Load_A; + break; + case AO_UI_Load_A_Register: + offset = M_Offset_AO_UI_Load_A; + break; + case G_Load_A_Register(0): + offset = M_Offset_G0_Load_A; + break; + case G_Load_A_Register(1): + offset = M_Offset_G1_Load_A; + break; + case G_Load_B_Register(0): + offset = M_Offset_G0_Load_B; + break; + case G_Load_B_Register(1): + offset = M_Offset_G1_Load_B; + break; + default: + printk(KERN_WARNING "%s: bug! unhandled register=0x%x in switch.\n", + __func__, reg); + BUG(); + return; + break; + } + ni_writel(data, offset); +} + +static uint32_t m_series_stc_readl(struct comedi_device *dev, int reg) +{ + unsigned offset; + switch (reg) { + case G_HW_Save_Register(0): + offset = M_Offset_G0_HW_Save; + break; + case G_HW_Save_Register(1): + offset = M_Offset_G1_HW_Save; + break; + case G_Save_Register(0): + offset = M_Offset_G0_Save; + break; + case G_Save_Register(1): + offset = M_Offset_G1_Save; + break; + default: + printk(KERN_WARNING "%s: bug! unhandled register=0x%x in switch.\n", + __func__, reg); + BUG(); + return 0; + break; + } + return ni_readl(offset); +} + +#define interrupt_pin(a) 0 +#define IRQ_POLARITY 1 + +#define NI_E_IRQ_FLAGS IRQF_SHARED + +#include "ni_mio_common.c" + +static int pcimio_find_device(struct comedi_device *dev, int bus, int slot); +static int pcimio_ai_change(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size); +static int pcimio_ao_change(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size); +static int pcimio_gpct0_change(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned long new_size); +static int pcimio_gpct1_change(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned long new_size); +static int pcimio_dio_change(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned long new_size); + +static void m_series_init_eeprom_buffer(struct comedi_device *dev) +{ + static const int Start_Cal_EEPROM = 0x400; + static const unsigned window_size = 10; + static const int serial_number_eeprom_offset = 0x4; + static const int serial_number_eeprom_length = 0x4; + unsigned old_iodwbsr_bits; + unsigned old_iodwbsr1_bits; + unsigned old_iodwcr1_bits; + int i; + + old_iodwbsr_bits = readl(devpriv->mite->mite_io_addr + MITE_IODWBSR); + old_iodwbsr1_bits = readl(devpriv->mite->mite_io_addr + MITE_IODWBSR_1); + old_iodwcr1_bits = readl(devpriv->mite->mite_io_addr + MITE_IODWCR_1); + writel(0x0, devpriv->mite->mite_io_addr + MITE_IODWBSR); + writel(((0x80 | window_size) | devpriv->mite->daq_phys_addr), + devpriv->mite->mite_io_addr + MITE_IODWBSR_1); + writel(0x1 | old_iodwcr1_bits, + devpriv->mite->mite_io_addr + MITE_IODWCR_1); + writel(0xf, devpriv->mite->mite_io_addr + 0x30); + + BUG_ON(serial_number_eeprom_length > sizeof(devpriv->serial_number)); + for (i = 0; i < serial_number_eeprom_length; ++i) { + char *byte_ptr = (char *)&devpriv->serial_number + i; + *byte_ptr = ni_readb(serial_number_eeprom_offset + i); + } + devpriv->serial_number = be32_to_cpu(devpriv->serial_number); + + for (i = 0; i < M_SERIES_EEPROM_SIZE; ++i) + devpriv->eeprom_buffer[i] = ni_readb(Start_Cal_EEPROM + i); + + writel(old_iodwbsr1_bits, devpriv->mite->mite_io_addr + MITE_IODWBSR_1); + writel(old_iodwbsr_bits, devpriv->mite->mite_io_addr + MITE_IODWBSR); + writel(old_iodwcr1_bits, devpriv->mite->mite_io_addr + MITE_IODWCR_1); + writel(0x0, devpriv->mite->mite_io_addr + 0x30); +} + +static void init_6143(struct comedi_device *dev) +{ + /* Disable interrupts */ + devpriv->stc_writew(dev, 0, Interrupt_Control_Register); + + /* Initialise 6143 AI specific bits */ + ni_writeb(0x00, Magic_6143); /* Set G0,G1 DMA mode to E series version */ + ni_writeb(0x80, PipelineDelay_6143); /* Set EOCMode, ADCMode and pipelinedelay */ + ni_writeb(0x00, EOC_Set_6143); /* Set EOC Delay */ + + ni_writel(boardtype.ai_fifo_depth / 2, AIFIFO_Flag_6143); /* Set the FIFO half full level */ + + /* Strobe Relay disable bit */ + devpriv->ai_calib_source_enabled = 0; + ni_writew(devpriv->ai_calib_source | Calibration_Channel_6143_RelayOff, + Calibration_Channel_6143); + ni_writew(devpriv->ai_calib_source, Calibration_Channel_6143); +} + +/* cleans up allocated resources */ +static int pcimio_detach(struct comedi_device *dev) +{ + mio_common_detach(dev); + if (dev->irq) + free_irq(dev->irq, dev); + + if (dev->private) { + mite_free_ring(devpriv->ai_mite_ring); + mite_free_ring(devpriv->ao_mite_ring); + mite_free_ring(devpriv->cdo_mite_ring); + mite_free_ring(devpriv->gpct_mite_ring[0]); + mite_free_ring(devpriv->gpct_mite_ring[1]); + if (devpriv->mite) + mite_unsetup(devpriv->mite); + } + + return 0; +} + +static int pcimio_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + + dev_info(dev->hw_dev, "comedi%d: ni_pcimio:\n", dev->minor); + + ret = ni_alloc_private(dev); + if (ret < 0) + return ret; + + ret = pcimio_find_device(dev, it->options[0], it->options[1]); + if (ret < 0) + return ret; + + dev_dbg(dev->hw_dev, "%s\n", boardtype.name); + dev->board_name = boardtype.name; + + if (boardtype.reg_type & ni_reg_m_series_mask) { + devpriv->stc_writew = &m_series_stc_writew; + devpriv->stc_readw = &m_series_stc_readw; + devpriv->stc_writel = &m_series_stc_writel; + devpriv->stc_readl = &m_series_stc_readl; + } else { + devpriv->stc_writew = &e_series_win_out; + devpriv->stc_readw = &e_series_win_in; + devpriv->stc_writel = &win_out2; + devpriv->stc_readl = &win_in2; + } + + ret = mite_setup(devpriv->mite); + if (ret < 0) { + pr_warn("error setting up mite\n"); + return ret; + } + comedi_set_hw_dev(dev, &devpriv->mite->pcidev->dev); + devpriv->ai_mite_ring = mite_alloc_ring(devpriv->mite); + if (devpriv->ai_mite_ring == NULL) + return -ENOMEM; + devpriv->ao_mite_ring = mite_alloc_ring(devpriv->mite); + if (devpriv->ao_mite_ring == NULL) + return -ENOMEM; + devpriv->cdo_mite_ring = mite_alloc_ring(devpriv->mite); + if (devpriv->cdo_mite_ring == NULL) + return -ENOMEM; + devpriv->gpct_mite_ring[0] = mite_alloc_ring(devpriv->mite); + if (devpriv->gpct_mite_ring[0] == NULL) + return -ENOMEM; + devpriv->gpct_mite_ring[1] = mite_alloc_ring(devpriv->mite); + if (devpriv->gpct_mite_ring[1] == NULL) + return -ENOMEM; + + if (boardtype.reg_type & ni_reg_m_series_mask) + m_series_init_eeprom_buffer(dev); + if (boardtype.reg_type == ni_reg_6143) + init_6143(dev); + + dev->irq = mite_irq(devpriv->mite); + + if (dev->irq == 0) { + pr_warn("unknown irq (bad)\n"); + } else { + pr_debug("( irq = %u )\n", dev->irq); + ret = request_irq(dev->irq, ni_E_interrupt, NI_E_IRQ_FLAGS, + DRV_NAME, dev); + if (ret < 0) { + pr_warn("irq not available\n"); + dev->irq = 0; + } + } + + ret = ni_E_init(dev, it); + if (ret < 0) + return ret; + + dev->subdevices[NI_AI_SUBDEV].buf_change = &pcimio_ai_change; + dev->subdevices[NI_AO_SUBDEV].buf_change = &pcimio_ao_change; + dev->subdevices[NI_GPCT_SUBDEV(0)].buf_change = &pcimio_gpct0_change; + dev->subdevices[NI_GPCT_SUBDEV(1)].buf_change = &pcimio_gpct1_change; + dev->subdevices[NI_DIO_SUBDEV].buf_change = &pcimio_dio_change; + + return ret; +} + +static int pcimio_find_device(struct comedi_device *dev, int bus, int slot) +{ + struct mite_struct *mite; + int i; + + for (mite = mite_devices; mite; mite = mite->next) { + if (mite->used) + continue; + if (bus || slot) { + if (bus != mite->pcidev->bus->number || + slot != PCI_SLOT(mite->pcidev->devfn)) + continue; + } + + for (i = 0; i < n_pcimio_boards; i++) { + if (mite_device_id(mite) == ni_boards[i].device_id) { + dev->board_ptr = ni_boards + i; + devpriv->mite = mite; + + return 0; + } + } + } + pr_warn("no device found\n"); + mite_list_devices(); + return -EIO; +} + +static int pcimio_ai_change(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size) +{ + int ret; + + ret = mite_buf_change(devpriv->ai_mite_ring, s->async); + if (ret < 0) + return ret; + + return 0; +} + +static int pcimio_ao_change(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size) +{ + int ret; + + ret = mite_buf_change(devpriv->ao_mite_ring, s->async); + if (ret < 0) + return ret; + + return 0; +} + +static int pcimio_gpct0_change(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned long new_size) +{ + int ret; + + ret = mite_buf_change(devpriv->gpct_mite_ring[0], s->async); + if (ret < 0) + return ret; + + return 0; +} + +static int pcimio_gpct1_change(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned long new_size) +{ + int ret; + + ret = mite_buf_change(devpriv->gpct_mite_ring[1], s->async); + if (ret < 0) + return ret; + + return 0; +} + +static int pcimio_dio_change(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned long new_size) +{ + int ret; + + ret = mite_buf_change(devpriv->cdo_mite_ring, s->async); + if (ret < 0) + return ret; + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ni_stc.h b/drivers/staging/comedi/drivers/ni_stc.h new file mode 100644 index 00000000..504ea715 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_stc.h @@ -0,0 +1,1516 @@ +/* + module/ni_stc.h + Register descriptions for NI DAQ-STC chip + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998-9 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* + References: + DAQ-STC Technical Reference Manual +*/ + +#ifndef _COMEDI_NI_STC_H +#define _COMEDI_NI_STC_H + +#include "ni_tio.h" + +#define _bit15 0x8000 +#define _bit14 0x4000 +#define _bit13 0x2000 +#define _bit12 0x1000 +#define _bit11 0x0800 +#define _bit10 0x0400 +#define _bit9 0x0200 +#define _bit8 0x0100 +#define _bit7 0x0080 +#define _bit6 0x0040 +#define _bit5 0x0020 +#define _bit4 0x0010 +#define _bit3 0x0008 +#define _bit2 0x0004 +#define _bit1 0x0002 +#define _bit0 0x0001 + +#define NUM_PFI_OUTPUT_SELECT_REGS 6 + +/* Registers in the National Instruments DAQ-STC chip */ + +#define Interrupt_A_Ack_Register 2 +#define G0_Gate_Interrupt_Ack _bit15 +#define G0_TC_Interrupt_Ack _bit14 +#define AI_Error_Interrupt_Ack _bit13 +#define AI_STOP_Interrupt_Ack _bit12 +#define AI_START_Interrupt_Ack _bit11 +#define AI_START2_Interrupt_Ack _bit10 +#define AI_START1_Interrupt_Ack _bit9 +#define AI_SC_TC_Interrupt_Ack _bit8 +#define AI_SC_TC_Error_Confirm _bit7 +#define G0_TC_Error_Confirm _bit6 +#define G0_Gate_Error_Confirm _bit5 + +#define AI_Status_1_Register 2 +#define Interrupt_A_St 0x8000 +#define AI_FIFO_Full_St 0x4000 +#define AI_FIFO_Half_Full_St 0x2000 +#define AI_FIFO_Empty_St 0x1000 +#define AI_Overrun_St 0x0800 +#define AI_Overflow_St 0x0400 +#define AI_SC_TC_Error_St 0x0200 +#define AI_START2_St 0x0100 +#define AI_START1_St 0x0080 +#define AI_SC_TC_St 0x0040 +#define AI_START_St 0x0020 +#define AI_STOP_St 0x0010 +#define G0_TC_St 0x0008 +#define G0_Gate_Interrupt_St 0x0004 +#define AI_FIFO_Request_St 0x0002 +#define Pass_Thru_0_Interrupt_St 0x0001 + +#define AI_Status_2_Register 5 + +#define Interrupt_B_Ack_Register 3 +enum Interrupt_B_Ack_Bits { + G1_Gate_Error_Confirm = _bit1, + G1_TC_Error_Confirm = _bit2, + AO_BC_TC_Trigger_Error_Confirm = _bit3, + AO_BC_TC_Error_Confirm = _bit4, + AO_UI2_TC_Error_Confrim = _bit5, + AO_UI2_TC_Interrupt_Ack = _bit6, + AO_UC_TC_Interrupt_Ack = _bit7, + AO_BC_TC_Interrupt_Ack = _bit8, + AO_START1_Interrupt_Ack = _bit9, + AO_UPDATE_Interrupt_Ack = _bit10, + AO_START_Interrupt_Ack = _bit11, + AO_STOP_Interrupt_Ack = _bit12, + AO_Error_Interrupt_Ack = _bit13, + G1_TC_Interrupt_Ack = _bit14, + G1_Gate_Interrupt_Ack = _bit15 +}; + +#define AO_Status_1_Register 3 +#define Interrupt_B_St _bit15 +#define AO_FIFO_Full_St _bit14 +#define AO_FIFO_Half_Full_St _bit13 +#define AO_FIFO_Empty_St _bit12 +#define AO_BC_TC_Error_St _bit11 +#define AO_START_St _bit10 +#define AO_Overrun_St _bit9 +#define AO_START1_St _bit8 +#define AO_BC_TC_St _bit7 +#define AO_UC_TC_St _bit6 +#define AO_UPDATE_St _bit5 +#define AO_UI2_TC_St _bit4 +#define G1_TC_St _bit3 +#define G1_Gate_Interrupt_St _bit2 +#define AO_FIFO_Request_St _bit1 +#define Pass_Thru_1_Interrupt_St _bit0 + +#define AI_Command_2_Register 4 +#define AI_End_On_SC_TC _bit15 +#define AI_End_On_End_Of_Scan _bit14 +#define AI_START1_Disable _bit11 +#define AI_SC_Save_Trace _bit10 +#define AI_SI_Switch_Load_On_SC_TC _bit9 +#define AI_SI_Switch_Load_On_STOP _bit8 +#define AI_SI_Switch_Load_On_TC _bit7 +#define AI_SC_Switch_Load_On_TC _bit4 +#define AI_STOP_Pulse _bit3 +#define AI_START_Pulse _bit2 +#define AI_START2_Pulse _bit1 +#define AI_START1_Pulse _bit0 + +#define AO_Command_2_Register 5 +#define AO_End_On_BC_TC(x) (((x) & 0x3) << 14) +#define AO_Start_Stop_Gate_Enable _bit13 +#define AO_UC_Save_Trace _bit12 +#define AO_BC_Gate_Enable _bit11 +#define AO_BC_Save_Trace _bit10 +#define AO_UI_Switch_Load_On_BC_TC _bit9 +#define AO_UI_Switch_Load_On_Stop _bit8 +#define AO_UI_Switch_Load_On_TC _bit7 +#define AO_UC_Switch_Load_On_BC_TC _bit6 +#define AO_UC_Switch_Load_On_TC _bit5 +#define AO_BC_Switch_Load_On_TC _bit4 +#define AO_Mute_B _bit3 +#define AO_Mute_A _bit2 +#define AO_UPDATE2_Pulse _bit1 +#define AO_START1_Pulse _bit0 + +#define AO_Status_2_Register 6 + +#define DIO_Parallel_Input_Register 7 + +#define AI_Command_1_Register 8 +#define AI_Analog_Trigger_Reset _bit14 +#define AI_Disarm _bit13 +#define AI_SI2_Arm _bit12 +#define AI_SI2_Load _bit11 +#define AI_SI_Arm _bit10 +#define AI_SI_Load _bit9 +#define AI_DIV_Arm _bit8 +#define AI_DIV_Load _bit7 +#define AI_SC_Arm _bit6 +#define AI_SC_Load _bit5 +#define AI_SCAN_IN_PROG_Pulse _bit4 +#define AI_EXTMUX_CLK_Pulse _bit3 +#define AI_LOCALMUX_CLK_Pulse _bit2 +#define AI_SC_TC_Pulse _bit1 +#define AI_CONVERT_Pulse _bit0 + +#define AO_Command_1_Register 9 +#define AO_Analog_Trigger_Reset _bit15 +#define AO_START_Pulse _bit14 +#define AO_Disarm _bit13 +#define AO_UI2_Arm_Disarm _bit12 +#define AO_UI2_Load _bit11 +#define AO_UI_Arm _bit10 +#define AO_UI_Load _bit9 +#define AO_UC_Arm _bit8 +#define AO_UC_Load _bit7 +#define AO_BC_Arm _bit6 +#define AO_BC_Load _bit5 +#define AO_DAC1_Update_Mode _bit4 +#define AO_LDAC1_Source_Select _bit3 +#define AO_DAC0_Update_Mode _bit2 +#define AO_LDAC0_Source_Select _bit1 +#define AO_UPDATE_Pulse _bit0 + +#define DIO_Output_Register 10 +#define DIO_Parallel_Data_Out(a) ((a)&0xff) +#define DIO_Parallel_Data_Mask 0xff +#define DIO_SDOUT _bit0 +#define DIO_SDIN _bit4 +#define DIO_Serial_Data_Out(a) (((a)&0xff)<<8) +#define DIO_Serial_Data_Mask 0xff00 + +#define DIO_Control_Register 11 +#define DIO_Software_Serial_Control _bit11 +#define DIO_HW_Serial_Timebase _bit10 +#define DIO_HW_Serial_Enable _bit9 +#define DIO_HW_Serial_Start _bit8 +#define DIO_Pins_Dir(a) ((a)&0xff) +#define DIO_Pins_Dir_Mask 0xff + +#define AI_Mode_1_Register 12 +#define AI_CONVERT_Source_Select(a) (((a) & 0x1f) << 11) +#define AI_SI_Source_select(a) (((a) & 0x1f) << 6) +#define AI_CONVERT_Source_Polarity _bit5 +#define AI_SI_Source_Polarity _bit4 +#define AI_Start_Stop _bit3 +#define AI_Mode_1_Reserved _bit2 +#define AI_Continuous _bit1 +#define AI_Trigger_Once _bit0 + +#define AI_Mode_2_Register 13 +#define AI_SC_Gate_Enable _bit15 +#define AI_Start_Stop_Gate_Enable _bit14 +#define AI_Pre_Trigger _bit13 +#define AI_External_MUX_Present _bit12 +#define AI_SI2_Initial_Load_Source _bit9 +#define AI_SI2_Reload_Mode _bit8 +#define AI_SI_Initial_Load_Source _bit7 +#define AI_SI_Reload_Mode(a) (((a) & 0x7)<<4) +#define AI_SI_Write_Switch _bit3 +#define AI_SC_Initial_Load_Source _bit2 +#define AI_SC_Reload_Mode _bit1 +#define AI_SC_Write_Switch _bit0 + +#define AI_SI_Load_A_Registers 14 +#define AI_SI_Load_B_Registers 16 +#define AI_SC_Load_A_Registers 18 +#define AI_SC_Load_B_Registers 20 +#define AI_SI_Save_Registers 64 +#define AI_SC_Save_Registers 66 + +#define AI_SI2_Load_A_Register 23 +#define AI_SI2_Load_B_Register 25 + +#define Joint_Status_1_Register 27 +#define DIO_Serial_IO_In_Progress_St _bit12 + +#define DIO_Serial_Input_Register 28 +#define Joint_Status_2_Register 29 +enum Joint_Status_2_Bits { + AO_TMRDACWRs_In_Progress_St = 0x20, +}; + +#define AO_Mode_1_Register 38 +#define AO_UPDATE_Source_Select(x) (((x)&0x1f)<<11) +#define AO_UI_Source_Select(x) (((x)&0x1f)<<6) +#define AO_Multiple_Channels _bit5 +#define AO_UPDATE_Source_Polarity _bit4 +#define AO_UI_Source_Polarity _bit3 +#define AO_UC_Switch_Load_Every_TC _bit2 +#define AO_Continuous _bit1 +#define AO_Trigger_Once _bit0 + +#define AO_Mode_2_Register 39 +#define AO_FIFO_Mode_Mask (0x3 << 14) +enum AO_FIFO_Mode_Bits { + AO_FIFO_Mode_HF_to_F = (3 << 14), + AO_FIFO_Mode_F = (2 << 14), + AO_FIFO_Mode_HF = (1 << 14), + AO_FIFO_Mode_E = (0 << 14), +}; +#define AO_FIFO_Retransmit_Enable _bit13 +#define AO_START1_Disable _bit12 +#define AO_UC_Initial_Load_Source _bit11 +#define AO_UC_Write_Switch _bit10 +#define AO_UI2_Initial_Load_Source _bit9 +#define AO_UI2_Reload_Mode _bit8 +#define AO_UI_Initial_Load_Source _bit7 +#define AO_UI_Reload_Mode(x) (((x) & 0x7) << 4) +#define AO_UI_Write_Switch _bit3 +#define AO_BC_Initial_Load_Source _bit2 +#define AO_BC_Reload_Mode _bit1 +#define AO_BC_Write_Switch _bit0 + +#define AO_UI_Load_A_Register 40 +#define AO_UI_Load_A_Register_High 40 +#define AO_UI_Load_A_Register_Low 41 +#define AO_UI_Load_B_Register 42 +#define AO_UI_Save_Registers 16 +#define AO_BC_Load_A_Register 44 +#define AO_BC_Load_A_Register_High 44 +#define AO_BC_Load_A_Register_Low 45 +#define AO_BC_Load_B_Register 46 +#define AO_BC_Load_B_Register_High 46 +#define AO_BC_Load_B_Register_Low 47 +#define AO_BC_Save_Registers 18 +#define AO_UC_Load_A_Register 48 +#define AO_UC_Load_A_Register_High 48 +#define AO_UC_Load_A_Register_Low 49 +#define AO_UC_Load_B_Register 50 +#define AO_UC_Save_Registers 20 + +#define Clock_and_FOUT_Register 56 +enum Clock_and_FOUT_bits { + FOUT_Enable = _bit15, + FOUT_Timebase_Select = _bit14, + DIO_Serial_Out_Divide_By_2 = _bit13, + Slow_Internal_Time_Divide_By_2 = _bit12, + Slow_Internal_Timebase = _bit11, + G_Source_Divide_By_2 = _bit10, + Clock_To_Board_Divide_By_2 = _bit9, + Clock_To_Board = _bit8, + AI_Output_Divide_By_2 = _bit7, + AI_Source_Divide_By_2 = _bit6, + AO_Output_Divide_By_2 = _bit5, + AO_Source_Divide_By_2 = _bit4, + FOUT_Divider_mask = 0xf +}; +static inline unsigned FOUT_Divider(unsigned divider) +{ + return divider & FOUT_Divider_mask; +} + +#define IO_Bidirection_Pin_Register 57 +#define RTSI_Trig_Direction_Register 58 +enum RTSI_Trig_Direction_Bits { + Drive_RTSI_Clock_Bit = 0x1, + Use_RTSI_Clock_Bit = 0x2, +}; +static inline unsigned RTSI_Output_Bit(unsigned channel, int is_mseries) +{ + unsigned max_channel; + unsigned base_bit_shift; + if (is_mseries) { + base_bit_shift = 8; + max_channel = 7; + } else { + base_bit_shift = 9; + max_channel = 6; + } + if (channel > max_channel) { + printk("%s: bug, invalid RTSI_channel=%i\n", __func__, channel); + return 0; + } + return 1 << (base_bit_shift + channel); +} + +#define Interrupt_Control_Register 59 +#define Interrupt_B_Enable _bit15 +#define Interrupt_B_Output_Select(x) ((x)<<12) +#define Interrupt_A_Enable _bit11 +#define Interrupt_A_Output_Select(x) ((x)<<8) +#define Pass_Thru_0_Interrupt_Polarity _bit3 +#define Pass_Thru_1_Interrupt_Polarity _bit2 +#define Interrupt_Output_On_3_Pins _bit1 +#define Interrupt_Output_Polarity _bit0 + +#define AI_Output_Control_Register 60 +#define AI_START_Output_Select _bit10 +#define AI_SCAN_IN_PROG_Output_Select(x) (((x) & 0x3) << 8) +#define AI_EXTMUX_CLK_Output_Select(x) (((x) & 0x3) << 6) +#define AI_LOCALMUX_CLK_Output_Select(x) ((x)<<4) +#define AI_SC_TC_Output_Select(x) ((x)<<2) +enum ai_convert_output_selection { + AI_CONVERT_Output_High_Z = 0, + AI_CONVERT_Output_Ground = 1, + AI_CONVERT_Output_Enable_Low = 2, + AI_CONVERT_Output_Enable_High = 3 +}; +static unsigned AI_CONVERT_Output_Select(enum ai_convert_output_selection + selection) +{ + return selection & 0x3; +} + +#define AI_START_STOP_Select_Register 62 +#define AI_START_Polarity _bit15 +#define AI_STOP_Polarity _bit14 +#define AI_STOP_Sync _bit13 +#define AI_STOP_Edge _bit12 +#define AI_STOP_Select(a) (((a) & 0x1f)<<7) +#define AI_START_Sync _bit6 +#define AI_START_Edge _bit5 +#define AI_START_Select(a) ((a) & 0x1f) + +#define AI_Trigger_Select_Register 63 +#define AI_START1_Polarity _bit15 +#define AI_START2_Polarity _bit14 +#define AI_START2_Sync _bit13 +#define AI_START2_Edge _bit12 +#define AI_START2_Select(a) (((a) & 0x1f) << 7) +#define AI_START1_Sync _bit6 +#define AI_START1_Edge _bit5 +#define AI_START1_Select(a) ((a) & 0x1f) + +#define AI_DIV_Load_A_Register 64 + +#define AO_Start_Select_Register 66 +#define AO_UI2_Software_Gate _bit15 +#define AO_UI2_External_Gate_Polarity _bit14 +#define AO_START_Polarity _bit13 +#define AO_AOFREQ_Enable _bit12 +#define AO_UI2_External_Gate_Select(a) (((a) & 0x1f) << 7) +#define AO_START_Sync _bit6 +#define AO_START_Edge _bit5 +#define AO_START_Select(a) ((a) & 0x1f) + +#define AO_Trigger_Select_Register 67 +#define AO_UI2_External_Gate_Enable _bit15 +#define AO_Delayed_START1 _bit14 +#define AO_START1_Polarity _bit13 +#define AO_UI2_Source_Polarity _bit12 +#define AO_UI2_Source_Select(x) (((x)&0x1f)<<7) +#define AO_START1_Sync _bit6 +#define AO_START1_Edge _bit5 +#define AO_START1_Select(x) (((x)&0x1f)<<0) + +#define AO_Mode_3_Register 70 +#define AO_UI2_Switch_Load_Next_TC _bit13 +#define AO_UC_Switch_Load_Every_BC_TC _bit12 +#define AO_Trigger_Length _bit11 +#define AO_Stop_On_Overrun_Error _bit5 +#define AO_Stop_On_BC_TC_Trigger_Error _bit4 +#define AO_Stop_On_BC_TC_Error _bit3 +#define AO_Not_An_UPDATE _bit2 +#define AO_Software_Gate _bit1 +#define AO_Last_Gate_Disable _bit0 /* M Series only */ + +#define Joint_Reset_Register 72 +#define Software_Reset _bit11 +#define AO_Configuration_End _bit9 +#define AI_Configuration_End _bit8 +#define AO_Configuration_Start _bit5 +#define AI_Configuration_Start _bit4 +#define G1_Reset _bit3 +#define G0_Reset _bit2 +#define AO_Reset _bit1 +#define AI_Reset _bit0 + +#define Interrupt_A_Enable_Register 73 +#define Pass_Thru_0_Interrupt_Enable _bit9 +#define G0_Gate_Interrupt_Enable _bit8 +#define AI_FIFO_Interrupt_Enable _bit7 +#define G0_TC_Interrupt_Enable _bit6 +#define AI_Error_Interrupt_Enable _bit5 +#define AI_STOP_Interrupt_Enable _bit4 +#define AI_START_Interrupt_Enable _bit3 +#define AI_START2_Interrupt_Enable _bit2 +#define AI_START1_Interrupt_Enable _bit1 +#define AI_SC_TC_Interrupt_Enable _bit0 + +#define Interrupt_B_Enable_Register 75 +#define Pass_Thru_1_Interrupt_Enable _bit11 +#define G1_Gate_Interrupt_Enable _bit10 +#define G1_TC_Interrupt_Enable _bit9 +#define AO_FIFO_Interrupt_Enable _bit8 +#define AO_UI2_TC_Interrupt_Enable _bit7 +#define AO_UC_TC_Interrupt_Enable _bit6 +#define AO_Error_Interrupt_Enable _bit5 +#define AO_STOP_Interrupt_Enable _bit4 +#define AO_START_Interrupt_Enable _bit3 +#define AO_UPDATE_Interrupt_Enable _bit2 +#define AO_START1_Interrupt_Enable _bit1 +#define AO_BC_TC_Interrupt_Enable _bit0 + +#define Second_IRQ_A_Enable_Register 74 +enum Second_IRQ_A_Enable_Bits { + AI_SC_TC_Second_Irq_Enable = _bit0, + AI_START1_Second_Irq_Enable = _bit1, + AI_START2_Second_Irq_Enable = _bit2, + AI_START_Second_Irq_Enable = _bit3, + AI_STOP_Second_Irq_Enable = _bit4, + AI_Error_Second_Irq_Enable = _bit5, + G0_TC_Second_Irq_Enable = _bit6, + AI_FIFO_Second_Irq_Enable = _bit7, + G0_Gate_Second_Irq_Enable = _bit8, + Pass_Thru_0_Second_Irq_Enable = _bit9 +}; + +#define Second_IRQ_B_Enable_Register 76 +enum Second_IRQ_B_Enable_Bits { + AO_BC_TC_Second_Irq_Enable = _bit0, + AO_START1_Second_Irq_Enable = _bit1, + AO_UPDATE_Second_Irq_Enable = _bit2, + AO_START_Second_Irq_Enable = _bit3, + AO_STOP_Second_Irq_Enable = _bit4, + AO_Error_Second_Irq_Enable = _bit5, + AO_UC_TC_Second_Irq_Enable = _bit6, + AO_UI2_TC_Second_Irq_Enable = _bit7, + AO_FIFO_Second_Irq_Enable = _bit8, + G1_TC_Second_Irq_Enable = _bit9, + G1_Gate_Second_Irq_Enable = _bit10, + Pass_Thru_1_Second_Irq_Enable = _bit11 +}; + +#define AI_Personal_Register 77 +#define AI_SHIFTIN_Pulse_Width _bit15 +#define AI_EOC_Polarity _bit14 +#define AI_SOC_Polarity _bit13 +#define AI_SHIFTIN_Polarity _bit12 +#define AI_CONVERT_Pulse_Timebase _bit11 +#define AI_CONVERT_Pulse_Width _bit10 +#define AI_CONVERT_Original_Pulse _bit9 +#define AI_FIFO_Flags_Polarity _bit8 +#define AI_Overrun_Mode _bit7 +#define AI_EXTMUX_CLK_Pulse_Width _bit6 +#define AI_LOCALMUX_CLK_Pulse_Width _bit5 +#define AI_AIFREQ_Polarity _bit4 + +#define AO_Personal_Register 78 +enum AO_Personal_Bits { + AO_Interval_Buffer_Mode = 1 << 3, + AO_BC_Source_Select = 1 << 4, + AO_UPDATE_Pulse_Width = 1 << 5, + AO_UPDATE_Pulse_Timebase = 1 << 6, + AO_UPDATE_Original_Pulse = 1 << 7, + AO_DMA_PIO_Control = 1 << 8, /* M Series: reserved */ + AO_AOFREQ_Polarity = 1 << 9, /* M Series: reserved */ + AO_FIFO_Enable = 1 << 10, + AO_FIFO_Flags_Polarity = 1 << 11, /* M Series: reserved */ + AO_TMRDACWR_Pulse_Width = 1 << 12, + AO_Fast_CPU = 1 << 13, /* M Series: reserved */ + AO_Number_Of_DAC_Packages = 1 << 14, /* 1 for "single" mode, 0 for "dual" */ + AO_Multiple_DACS_Per_Package = 1 << 15 /* m-series only */ +}; +#define RTSI_Trig_A_Output_Register 79 +#define RTSI_Trig_B_Output_Register 80 +enum RTSI_Trig_B_Output_Bits { + RTSI_Sub_Selection_1_Bit = 0x8000 /* not for m-series */ +}; +static inline unsigned RTSI_Trig_Output_Bits(unsigned rtsi_channel, + unsigned source) +{ + return (source & 0xf) << ((rtsi_channel % 4) * 4); +}; + +static inline unsigned RTSI_Trig_Output_Mask(unsigned rtsi_channel) +{ + return 0xf << ((rtsi_channel % 4) * 4); +}; + +/* inverse to RTSI_Trig_Output_Bits() */ +static inline unsigned RTSI_Trig_Output_Source(unsigned rtsi_channel, + unsigned bits) +{ + return (bits >> ((rtsi_channel % 4) * 4)) & 0xf; +}; + +#define RTSI_Board_Register 81 +#define Write_Strobe_0_Register 82 +#define Write_Strobe_1_Register 83 +#define Write_Strobe_2_Register 84 +#define Write_Strobe_3_Register 85 + +#define AO_Output_Control_Register 86 +#define AO_External_Gate_Enable _bit15 +#define AO_External_Gate_Select(x) (((x)&0x1f)<<10) +#define AO_Number_Of_Channels(x) (((x)&0xf)<<6) +#define AO_UPDATE2_Output_Select(x) (((x)&0x3)<<4) +#define AO_External_Gate_Polarity _bit3 +#define AO_UPDATE2_Output_Toggle _bit2 +enum ao_update_output_selection { + AO_Update_Output_High_Z = 0, + AO_Update_Output_Ground = 1, + AO_Update_Output_Enable_Low = 2, + AO_Update_Output_Enable_High = 3 +}; +static unsigned AO_UPDATE_Output_Select(enum ao_update_output_selection + selection) +{ + return selection & 0x3; +} + +#define AI_Mode_3_Register 87 +#define AI_Trigger_Length _bit15 +#define AI_Delay_START _bit14 +#define AI_Software_Gate _bit13 +#define AI_SI_Special_Trigger_Delay _bit12 +#define AI_SI2_Source_Select _bit11 +#define AI_Delayed_START2 _bit10 +#define AI_Delayed_START1 _bit9 +#define AI_External_Gate_Mode _bit8 +#define AI_FIFO_Mode_HF_to_E (3<<6) +#define AI_FIFO_Mode_F (2<<6) +#define AI_FIFO_Mode_HF (1<<6) +#define AI_FIFO_Mode_NE (0<<6) +#define AI_External_Gate_Polarity _bit5 +#define AI_External_Gate_Select(a) ((a) & 0x1f) + +#define G_Autoincrement_Register(a) (68+(a)) +#define G_Command_Register(a) (6+(a)) +#define G_HW_Save_Register(a) (8+(a)*2) +#define G_HW_Save_Register_High(a) (8+(a)*2) +#define G_HW_Save_Register_Low(a) (9+(a)*2) +#define G_Input_Select_Register(a) (36+(a)) +#define G_Load_A_Register(a) (28+(a)*4) +#define G_Load_A_Register_High(a) (28+(a)*4) +#define G_Load_A_Register_Low(a) (29+(a)*4) +#define G_Load_B_Register(a) (30+(a)*4) +#define G_Load_B_Register_High(a) (30+(a)*4) +#define G_Load_B_Register_Low(a) (31+(a)*4) +#define G_Mode_Register(a) (26+(a)) +#define G_Save_Register(a) (12+(a)*2) +#define G_Save_Register_High(a) (12+(a)*2) +#define G_Save_Register_Low(a) (13+(a)*2) +#define G_Status_Register 4 +#define Analog_Trigger_Etc_Register 61 + +/* command register */ +#define G_Disarm_Copy _bit15 /* strobe */ +#define G_Save_Trace_Copy _bit14 +#define G_Arm_Copy _bit13 /* strobe */ +#define G_Bank_Switch_Start _bit10 /* strobe */ +#define G_Little_Big_Endian _bit9 +#define G_Synchronized_Gate _bit8 +#define G_Write_Switch _bit7 +#define G_Up_Down(a) (((a)&0x03)<<5) +#define G_Disarm _bit4 /* strobe */ +#define G_Analog_Trigger_Reset _bit3 /* strobe */ +#define G_Save_Trace _bit1 +#define G_Arm _bit0 /* strobe */ + +/*channel agnostic names for the command register #defines */ +#define G_Bank_Switch_Enable _bit12 +#define G_Bank_Switch_Mode _bit11 +#define G_Load _bit2 /* strobe */ + +/* input select register */ +#define G_Gate_Select(a) (((a)&0x1f)<<7) +#define G_Source_Select(a) (((a)&0x1f)<<2) +#define G_Write_Acknowledges_Irq _bit1 +#define G_Read_Acknowledges_Irq _bit0 + +/* same input select register, but with channel agnostic names */ +#define G_Source_Polarity _bit15 +#define G_Output_Polarity _bit14 +#define G_OR_Gate _bit13 +#define G_Gate_Select_Load_Source _bit12 + +/* mode register */ +#define G_Loading_On_TC _bit12 +#define G_Output_Mode(a) (((a)&0x03)<<8) +#define G_Trigger_Mode_For_Edge_Gate(a) (((a)&0x03)<<3) +#define G_Gating_Mode(a) (((a)&0x03)<<0) + +/* same input mode register, but with channel agnostic names */ +#define G_Load_Source_Select _bit7 +#define G_Reload_Source_Switching _bit15 +#define G_Loading_On_Gate _bit14 +#define G_Gate_Polarity _bit13 + +#define G_Counting_Once(a) (((a)&0x03)<<10) +#define G_Stop_Mode(a) (((a)&0x03)<<5) +#define G_Gate_On_Both_Edges _bit2 + +/* G_Status_Register */ +#define G1_Gate_Error_St _bit15 +#define G0_Gate_Error_St _bit14 +#define G1_TC_Error_St _bit13 +#define G0_TC_Error_St _bit12 +#define G1_No_Load_Between_Gates_St _bit11 +#define G0_No_Load_Between_Gates_St _bit10 +#define G1_Armed_St _bit9 +#define G0_Armed_St _bit8 +#define G1_Stale_Data_St _bit7 +#define G0_Stale_Data_St _bit6 +#define G1_Next_Load_Source_St _bit5 +#define G0_Next_Load_Source_St _bit4 +#define G1_Counting_St _bit3 +#define G0_Counting_St _bit2 +#define G1_Save_St _bit1 +#define G0_Save_St _bit0 + +/* general purpose counter timer */ +#define G_Autoincrement(a) ((a)<<0) + +/*Analog_Trigger_Etc_Register*/ +#define Analog_Trigger_Mode(x) ((x) & 0x7) +#define Analog_Trigger_Enable _bit3 +#define Analog_Trigger_Drive _bit4 +#define GPFO_1_Output_Select _bit7 +#define GPFO_0_Output_Select(a) ((a)<<11) +#define GPFO_0_Output_Enable _bit14 +#define GPFO_1_Output_Enable _bit15 + +/* Additional windowed registers unique to E series */ + +/* 16 bit registers shadowed from DAQ-STC */ +#define Window_Address 0x00 +#define Window_Data 0x02 + +#define Configuration_Memory_Clear 82 +#define ADC_FIFO_Clear 83 +#define DAC_FIFO_Clear 84 + +/* i/o port offsets */ + +/* 8 bit registers */ +#define XXX_Status 0x01 +enum XXX_Status_Bits { + PROMOUT = 0x1, + AI_FIFO_LOWER_NOT_EMPTY = 0x8, +}; +#define Serial_Command 0x0d +#define Misc_Command 0x0f +#define Port_A 0x19 +#define Port_B 0x1b +#define Port_C 0x1d +#define Configuration 0x1f +#define Strobes 0x01 +#define Channel_A_Mode 0x03 +#define Channel_B_Mode 0x05 +#define Channel_C_Mode 0x07 +#define AI_AO_Select 0x09 +enum AI_AO_Select_Bits { + AI_DMA_Select_Shift = 0, + AI_DMA_Select_Mask = 0xf, + AO_DMA_Select_Shift = 4, + AO_DMA_Select_Mask = 0xf << AO_DMA_Select_Shift +}; +#define G0_G1_Select 0x0b +static inline unsigned ni_stc_dma_channel_select_bitfield(unsigned channel) +{ + if (channel < 4) + return 1 << channel; + if (channel == 4) + return 0x3; + if (channel == 5) + return 0x5; + BUG(); + return 0; +} + +static inline unsigned GPCT_DMA_Select_Bits(unsigned gpct_index, + unsigned mite_channel) +{ + BUG_ON(gpct_index > 1); + return ni_stc_dma_channel_select_bitfield(mite_channel) << (4 * + gpct_index); +} + +static inline unsigned GPCT_DMA_Select_Mask(unsigned gpct_index) +{ + BUG_ON(gpct_index > 1); + return 0xf << (4 * gpct_index); +} + +/* 16 bit registers */ + +#define Configuration_Memory_Low 0x10 +enum Configuration_Memory_Low_Bits { + AI_DITHER = 0x200, + AI_LAST_CHANNEL = 0x8000, +}; +#define Configuration_Memory_High 0x12 +enum Configuration_Memory_High_Bits { + AI_AC_COUPLE = 0x800, + AI_DIFFERENTIAL = 0x1000, + AI_COMMON = 0x2000, + AI_GROUND = 0x3000, +}; +static inline unsigned int AI_CONFIG_CHANNEL(unsigned int channel) +{ + return channel & 0x3f; +} + +#define ADC_FIFO_Data_Register 0x1c + +#define AO_Configuration 0x16 +#define AO_Bipolar _bit0 +#define AO_Deglitch _bit1 +#define AO_Ext_Ref _bit2 +#define AO_Ground_Ref _bit3 +#define AO_Channel(x) ((x) << 8) + +#define DAC_FIFO_Data 0x1e +#define DAC0_Direct_Data 0x18 +#define DAC1_Direct_Data 0x1a + +/* 611x registers (these boards differ from the e-series) */ + +#define Magic_611x 0x19 /* w8 (new) */ +#define Calibration_Channel_Select_611x 0x1a /* w16 (new) */ +#define ADC_FIFO_Data_611x 0x1c /* r32 (incompatible) */ +#define AI_FIFO_Offset_Load_611x 0x05 /* r8 (new) */ +#define DAC_FIFO_Data_611x 0x14 /* w32 (incompatible) */ +#define Cal_Gain_Select_611x 0x05 /* w8 (new) */ + +#define AO_Window_Address_611x 0x18 +#define AO_Window_Data_611x 0x1e + +/* 6143 registers */ +#define Magic_6143 0x19 /* w8 */ +#define G0G1_DMA_Select_6143 0x0B /* w8 */ +#define PipelineDelay_6143 0x1f /* w8 */ +#define EOC_Set_6143 0x1D /* w8 */ +#define AIDMA_Select_6143 0x09 /* w8 */ +#define AIFIFO_Data_6143 0x8C /* w32 */ +#define AIFIFO_Flag_6143 0x84 /* w32 */ +#define AIFIFO_Control_6143 0x88 /* w32 */ +#define AIFIFO_Status_6143 0x88 /* w32 */ +#define AIFIFO_DMAThreshold_6143 0x90 /* w32 */ +#define AIFIFO_Words_Available_6143 0x94 /* w32 */ + +#define Calibration_Channel_6143 0x42 /* w16 */ +#define Calibration_LowTime_6143 0x20 /* w16 */ +#define Calibration_HighTime_6143 0x22 /* w16 */ +#define Relay_Counter_Load_Val__6143 0x4C /* w32 */ +#define Signature_6143 0x50 /* w32 */ +#define Release_Date_6143 0x54 /* w32 */ +#define Release_Oldest_Date_6143 0x58 /* w32 */ + +#define Calibration_Channel_6143_RelayOn 0x8000 /* Calibration relay switch On */ +#define Calibration_Channel_6143_RelayOff 0x4000 /* Calibration relay switch Off */ +#define Calibration_Channel_Gnd_Gnd 0x00 /* Offset Calibration */ +#define Calibration_Channel_2v5_Gnd 0x02 /* 2.5V Reference */ +#define Calibration_Channel_Pwm_Gnd 0x05 /* +/- 5V Self Cal */ +#define Calibration_Channel_2v5_Pwm 0x0a /* PWM Calibration */ +#define Calibration_Channel_Pwm_Pwm 0x0d /* CMRR */ +#define Calibration_Channel_Gnd_Pwm 0x0e /* PWM Calibration */ + +/* 671x, 611x registers */ + +/* 671xi, 611x windowed ao registers */ +enum windowed_regs_67xx_61xx { + AO_Immediate_671x = 0x11, /* W 16 */ + AO_Timed_611x = 0x10, /* W 16 */ + AO_FIFO_Offset_Load_611x = 0x13, /* W32 */ + AO_Later_Single_Point_Updates = 0x14, /* W 16 */ + AO_Waveform_Generation_611x = 0x15, /* W 16 */ + AO_Misc_611x = 0x16, /* W 16 */ + AO_Calibration_Channel_Select_67xx = 0x17, /* W 16 */ + AO_Configuration_2_67xx = 0x18, /* W 16 */ + CAL_ADC_Command_67xx = 0x19, /* W 8 */ + CAL_ADC_Status_67xx = 0x1a, /* R 8 */ + CAL_ADC_Data_67xx = 0x1b, /* R 16 */ + CAL_ADC_Config_Data_High_Word_67xx = 0x1c, /* RW 16 */ + CAL_ADC_Config_Data_Low_Word_67xx = 0x1d, /* RW 16 */ +}; +static inline unsigned int DACx_Direct_Data_671x(int channel) +{ + return channel; +} + +enum AO_Misc_611x_Bits { + CLEAR_WG = 1, +}; +enum cs5529_configuration_bits { + CSCFG_CAL_CONTROL_MASK = 0x7, + CSCFG_SELF_CAL_OFFSET = 0x1, + CSCFG_SELF_CAL_GAIN = 0x2, + CSCFG_SELF_CAL_OFFSET_GAIN = 0x3, + CSCFG_SYSTEM_CAL_OFFSET = 0x5, + CSCFG_SYSTEM_CAL_GAIN = 0x6, + CSCFG_DONE = 1 << 3, + CSCFG_POWER_SAVE_SELECT = 1 << 4, + CSCFG_PORT_MODE = 1 << 5, + CSCFG_RESET_VALID = 1 << 6, + CSCFG_RESET = 1 << 7, + CSCFG_UNIPOLAR = 1 << 12, + CSCFG_WORD_RATE_2180_CYCLES = 0x0 << 13, + CSCFG_WORD_RATE_1092_CYCLES = 0x1 << 13, + CSCFG_WORD_RATE_532_CYCLES = 0x2 << 13, + CSCFG_WORD_RATE_388_CYCLES = 0x3 << 13, + CSCFG_WORD_RATE_324_CYCLES = 0x4 << 13, + CSCFG_WORD_RATE_17444_CYCLES = 0x5 << 13, + CSCFG_WORD_RATE_8724_CYCLES = 0x6 << 13, + CSCFG_WORD_RATE_4364_CYCLES = 0x7 << 13, + CSCFG_WORD_RATE_MASK = 0x7 << 13, + CSCFG_LOW_POWER = 1 << 16, +}; +static inline unsigned int CS5529_CONFIG_DOUT(int output) +{ + return 1 << (18 + output); +} + +static inline unsigned int CS5529_CONFIG_AOUT(int output) +{ + return 1 << (22 + output); +} + +enum cs5529_command_bits { + CSCMD_POWER_SAVE = 0x1, + CSCMD_REGISTER_SELECT_MASK = 0xe, + CSCMD_OFFSET_REGISTER = 0x0, + CSCMD_GAIN_REGISTER = 0x2, + CSCMD_CONFIG_REGISTER = 0x4, + CSCMD_READ = 0x10, + CSCMD_CONTINUOUS_CONVERSIONS = 0x20, + CSCMD_SINGLE_CONVERSION = 0x40, + CSCMD_COMMAND = 0x80, +}; +enum cs5529_status_bits { + CSS_ADC_BUSY = 0x1, + CSS_OSC_DETECT = 0x2, /* indicates adc error */ + CSS_OVERRANGE = 0x4, +}; +#define SerDacLd(x) (0x08<<(x)) + +/* + This is stuff unique to the NI E series drivers, + but I thought I'd put it here anyway. +*/ + +enum { ai_gain_16 = + 0, ai_gain_8, ai_gain_14, ai_gain_4, ai_gain_611x, ai_gain_622x, + ai_gain_628x, ai_gain_6143 +}; +enum caldac_enum { caldac_none = 0, mb88341, dac8800, dac8043, ad8522, + ad8804, ad8842, ad8804_debug +}; +enum ni_reg_type { + ni_reg_normal = 0x0, + ni_reg_611x = 0x1, + ni_reg_6711 = 0x2, + ni_reg_6713 = 0x4, + ni_reg_67xx_mask = 0x6, + ni_reg_6xxx_mask = 0x7, + ni_reg_622x = 0x8, + ni_reg_625x = 0x10, + ni_reg_628x = 0x18, + ni_reg_m_series_mask = 0x18, + ni_reg_6143 = 0x20 +}; + +static const struct comedi_lrange range_ni_E_ao_ext; + +enum m_series_register_offsets { + M_Offset_CDIO_DMA_Select = 0x7, /* write */ + M_Offset_SCXI_Status = 0x7, /* read */ + M_Offset_AI_AO_Select = 0x9, /* write, same offset as e-series */ + M_Offset_SCXI_Serial_Data_In = 0x9, /* read */ + M_Offset_G0_G1_Select = 0xb, /* write, same offset as e-series */ + M_Offset_Misc_Command = 0xf, + M_Offset_SCXI_Serial_Data_Out = 0x11, + M_Offset_SCXI_Control = 0x13, + M_Offset_SCXI_Output_Enable = 0x15, + M_Offset_AI_FIFO_Data = 0x1c, + M_Offset_Static_Digital_Output = 0x24, /* write */ + M_Offset_Static_Digital_Input = 0x24, /* read */ + M_Offset_DIO_Direction = 0x28, + M_Offset_Cal_PWM = 0x40, + M_Offset_AI_Config_FIFO_Data = 0x5e, + M_Offset_Interrupt_C_Enable = 0x88, /* write */ + M_Offset_Interrupt_C_Status = 0x88, /* read */ + M_Offset_Analog_Trigger_Control = 0x8c, + M_Offset_AO_Serial_Interrupt_Enable = 0xa0, + M_Offset_AO_Serial_Interrupt_Ack = 0xa1, /* write */ + M_Offset_AO_Serial_Interrupt_Status = 0xa1, /* read */ + M_Offset_AO_Calibration = 0xa3, + M_Offset_AO_FIFO_Data = 0xa4, + M_Offset_PFI_Filter = 0xb0, + M_Offset_RTSI_Filter = 0xb4, + M_Offset_SCXI_Legacy_Compatibility = 0xbc, + M_Offset_Interrupt_A_Ack = 0x104, /* write */ + M_Offset_AI_Status_1 = 0x104, /* read */ + M_Offset_Interrupt_B_Ack = 0x106, /* write */ + M_Offset_AO_Status_1 = 0x106, /* read */ + M_Offset_AI_Command_2 = 0x108, /* write */ + M_Offset_G01_Status = 0x108, /* read */ + M_Offset_AO_Command_2 = 0x10a, + M_Offset_AO_Status_2 = 0x10c, /* read */ + M_Offset_G0_Command = 0x10c, /* write */ + M_Offset_G1_Command = 0x10e, /* write */ + M_Offset_G0_HW_Save = 0x110, + M_Offset_G0_HW_Save_High = 0x110, + M_Offset_AI_Command_1 = 0x110, + M_Offset_G0_HW_Save_Low = 0x112, + M_Offset_AO_Command_1 = 0x112, + M_Offset_G1_HW_Save = 0x114, + M_Offset_G1_HW_Save_High = 0x114, + M_Offset_G1_HW_Save_Low = 0x116, + M_Offset_AI_Mode_1 = 0x118, + M_Offset_G0_Save = 0x118, + M_Offset_G0_Save_High = 0x118, + M_Offset_AI_Mode_2 = 0x11a, + M_Offset_G0_Save_Low = 0x11a, + M_Offset_AI_SI_Load_A = 0x11c, + M_Offset_G1_Save = 0x11c, + M_Offset_G1_Save_High = 0x11c, + M_Offset_G1_Save_Low = 0x11e, + M_Offset_AI_SI_Load_B = 0x120, /* write */ + M_Offset_AO_UI_Save = 0x120, /* read */ + M_Offset_AI_SC_Load_A = 0x124, /* write */ + M_Offset_AO_BC_Save = 0x124, /* read */ + M_Offset_AI_SC_Load_B = 0x128, /* write */ + M_Offset_AO_UC_Save = 0x128, /* read */ + M_Offset_AI_SI2_Load_A = 0x12c, + M_Offset_AI_SI2_Load_B = 0x130, + M_Offset_G0_Mode = 0x134, + M_Offset_G1_Mode = 0x136, /* write */ + M_Offset_Joint_Status_1 = 0x136, /* read */ + M_Offset_G0_Load_A = 0x138, + M_Offset_Joint_Status_2 = 0x13a, + M_Offset_G0_Load_B = 0x13c, + M_Offset_G1_Load_A = 0x140, + M_Offset_G1_Load_B = 0x144, + M_Offset_G0_Input_Select = 0x148, + M_Offset_G1_Input_Select = 0x14a, + M_Offset_AO_Mode_1 = 0x14c, + M_Offset_AO_Mode_2 = 0x14e, + M_Offset_AO_UI_Load_A = 0x150, + M_Offset_AO_UI_Load_B = 0x154, + M_Offset_AO_BC_Load_A = 0x158, + M_Offset_AO_BC_Load_B = 0x15c, + M_Offset_AO_UC_Load_A = 0x160, + M_Offset_AO_UC_Load_B = 0x164, + M_Offset_Clock_and_FOUT = 0x170, + M_Offset_IO_Bidirection_Pin = 0x172, + M_Offset_RTSI_Trig_Direction = 0x174, + M_Offset_Interrupt_Control = 0x176, + M_Offset_AI_Output_Control = 0x178, + M_Offset_Analog_Trigger_Etc = 0x17a, + M_Offset_AI_START_STOP_Select = 0x17c, + M_Offset_AI_Trigger_Select = 0x17e, + M_Offset_AI_SI_Save = 0x180, /* read */ + M_Offset_AI_DIV_Load_A = 0x180, /* write */ + M_Offset_AI_SC_Save = 0x184, /* read */ + M_Offset_AO_Start_Select = 0x184, /* write */ + M_Offset_AO_Trigger_Select = 0x186, + M_Offset_AO_Mode_3 = 0x18c, + M_Offset_G0_Autoincrement = 0x188, + M_Offset_G1_Autoincrement = 0x18a, + M_Offset_Joint_Reset = 0x190, + M_Offset_Interrupt_A_Enable = 0x192, + M_Offset_Interrupt_B_Enable = 0x196, + M_Offset_AI_Personal = 0x19a, + M_Offset_AO_Personal = 0x19c, + M_Offset_RTSI_Trig_A_Output = 0x19e, + M_Offset_RTSI_Trig_B_Output = 0x1a0, + M_Offset_RTSI_Shared_MUX = 0x1a2, + M_Offset_AO_Output_Control = 0x1ac, + M_Offset_AI_Mode_3 = 0x1ae, + M_Offset_Configuration_Memory_Clear = 0x1a4, + M_Offset_AI_FIFO_Clear = 0x1a6, + M_Offset_AO_FIFO_Clear = 0x1a8, + M_Offset_G0_Counting_Mode = 0x1b0, + M_Offset_G1_Counting_Mode = 0x1b2, + M_Offset_G0_Second_Gate = 0x1b4, + M_Offset_G1_Second_Gate = 0x1b6, + M_Offset_G0_DMA_Config = 0x1b8, /* write */ + M_Offset_G0_DMA_Status = 0x1b8, /* read */ + M_Offset_G1_DMA_Config = 0x1ba, /* write */ + M_Offset_G1_DMA_Status = 0x1ba, /* read */ + M_Offset_G0_MSeries_ABZ = 0x1c0, + M_Offset_G1_MSeries_ABZ = 0x1c2, + M_Offset_Clock_and_Fout2 = 0x1c4, + M_Offset_PLL_Control = 0x1c6, + M_Offset_PLL_Status = 0x1c8, + M_Offset_PFI_Output_Select_1 = 0x1d0, + M_Offset_PFI_Output_Select_2 = 0x1d2, + M_Offset_PFI_Output_Select_3 = 0x1d4, + M_Offset_PFI_Output_Select_4 = 0x1d6, + M_Offset_PFI_Output_Select_5 = 0x1d8, + M_Offset_PFI_Output_Select_6 = 0x1da, + M_Offset_PFI_DI = 0x1dc, + M_Offset_PFI_DO = 0x1de, + M_Offset_AI_Config_FIFO_Bypass = 0x218, + M_Offset_SCXI_DIO_Enable = 0x21c, + M_Offset_CDI_FIFO_Data = 0x220, /* read */ + M_Offset_CDO_FIFO_Data = 0x220, /* write */ + M_Offset_CDIO_Status = 0x224, /* read */ + M_Offset_CDIO_Command = 0x224, /* write */ + M_Offset_CDI_Mode = 0x228, + M_Offset_CDO_Mode = 0x22c, + M_Offset_CDI_Mask_Enable = 0x230, + M_Offset_CDO_Mask_Enable = 0x234, +}; +static inline int M_Offset_AO_Waveform_Order(int channel) +{ + return 0xc2 + 0x4 * channel; +}; + +static inline int M_Offset_AO_Config_Bank(int channel) +{ + return 0xc3 + 0x4 * channel; +}; + +static inline int M_Offset_DAC_Direct_Data(int channel) +{ + return 0xc0 + 0x4 * channel; +} + +static inline int M_Offset_Gen_PWM(int channel) +{ + return 0x44 + 0x2 * channel; +} + +static inline int M_Offset_Static_AI_Control(int i) +{ + int offset[] = { + 0x64, + 0x261, + 0x262, + 0x263, + }; + if (((unsigned)i) >= ARRAY_SIZE(offset)) { + printk("%s: invalid channel=%i\n", __func__, i); + return offset[0]; + } + return offset[i]; +}; + +static inline int M_Offset_AO_Reference_Attenuation(int channel) +{ + int offset[] = { + 0x264, + 0x265, + 0x266, + 0x267 + }; + if (((unsigned)channel) >= ARRAY_SIZE(offset)) { + printk("%s: invalid channel=%i\n", __func__, channel); + return offset[0]; + } + return offset[channel]; +}; + +static inline unsigned M_Offset_PFI_Output_Select(unsigned n) +{ + if (n < 1 || n > NUM_PFI_OUTPUT_SELECT_REGS) { + printk("%s: invalid pfi output select register=%i\n", + __func__, n); + return M_Offset_PFI_Output_Select_1; + } + return M_Offset_PFI_Output_Select_1 + (n - 1) * 2; +} + +enum MSeries_AI_Config_FIFO_Data_Bits { + MSeries_AI_Config_Channel_Type_Mask = 0x7 << 6, + MSeries_AI_Config_Channel_Type_Calibration_Bits = 0x0, + MSeries_AI_Config_Channel_Type_Differential_Bits = 0x1 << 6, + MSeries_AI_Config_Channel_Type_Common_Ref_Bits = 0x2 << 6, + MSeries_AI_Config_Channel_Type_Ground_Ref_Bits = 0x3 << 6, + MSeries_AI_Config_Channel_Type_Aux_Bits = 0x5 << 6, + MSeries_AI_Config_Channel_Type_Ghost_Bits = 0x7 << 6, + MSeries_AI_Config_Polarity_Bit = 0x1000, /* 0 for 2's complement encoding */ + MSeries_AI_Config_Dither_Bit = 0x2000, + MSeries_AI_Config_Last_Channel_Bit = 0x4000, +}; +static inline unsigned MSeries_AI_Config_Channel_Bits(unsigned channel) +{ + return channel & 0xf; +} + +static inline unsigned MSeries_AI_Config_Bank_Bits(enum ni_reg_type reg_type, + unsigned channel) +{ + unsigned bits = channel & 0x30; + if (reg_type == ni_reg_622x) { + if (channel & 0x40) + bits |= 0x400; + } + return bits; +} + +static inline unsigned MSeries_AI_Config_Gain_Bits(unsigned range) +{ + return (range & 0x7) << 9; +} + +enum MSeries_Clock_and_Fout2_Bits { + MSeries_PLL_In_Source_Select_RTSI0_Bits = 0xb, + MSeries_PLL_In_Source_Select_Star_Trigger_Bits = 0x14, + MSeries_PLL_In_Source_Select_RTSI7_Bits = 0x1b, + MSeries_PLL_In_Source_Select_PXI_Clock10 = 0x1d, + MSeries_PLL_In_Source_Select_Mask = 0x1f, + MSeries_Timebase1_Select_Bit = 0x20, /* use PLL for timebase 1 */ + MSeries_Timebase3_Select_Bit = 0x40, /* use PLL for timebase 3 */ + /* use 10MHz instead of 20MHz for RTSI clock frequency. Appears + to have no effect, at least on pxi-6281, which always uses + 20MHz rtsi clock frequency */ + MSeries_RTSI_10MHz_Bit = 0x80 +}; +static inline unsigned MSeries_PLL_In_Source_Select_RTSI_Bits(unsigned + RTSI_channel) +{ + if (RTSI_channel > 7) { + printk("%s: bug, invalid RTSI_channel=%i\n", __func__, + RTSI_channel); + return 0; + } + if (RTSI_channel == 7) + return MSeries_PLL_In_Source_Select_RTSI7_Bits; + else + return MSeries_PLL_In_Source_Select_RTSI0_Bits + RTSI_channel; +} + +enum MSeries_PLL_Control_Bits { + MSeries_PLL_Enable_Bit = 0x1000, + MSeries_PLL_VCO_Mode_200_325MHz_Bits = 0x0, + MSeries_PLL_VCO_Mode_175_225MHz_Bits = 0x2000, + MSeries_PLL_VCO_Mode_100_225MHz_Bits = 0x4000, + MSeries_PLL_VCO_Mode_75_150MHz_Bits = 0x6000, +}; +static inline unsigned MSeries_PLL_Divisor_Bits(unsigned divisor) +{ + static const unsigned max_divisor = 0x10; + if (divisor < 1 || divisor > max_divisor) { + printk("%s: bug, invalid divisor=%i\n", __func__, divisor); + return 0; + } + return (divisor & 0xf) << 8; +} + +static inline unsigned MSeries_PLL_Multiplier_Bits(unsigned multiplier) +{ + static const unsigned max_multiplier = 0x100; + if (multiplier < 1 || multiplier > max_multiplier) { + printk("%s: bug, invalid multiplier=%i\n", __func__, + multiplier); + return 0; + } + return multiplier & 0xff; +} + +enum MSeries_PLL_Status { + MSeries_PLL_Locked_Bit = 0x1 +}; + +enum MSeries_AI_Config_FIFO_Bypass_Bits { + MSeries_AI_Bypass_Channel_Mask = 0x7, + MSeries_AI_Bypass_Bank_Mask = 0x78, + MSeries_AI_Bypass_Cal_Sel_Pos_Mask = 0x380, + MSeries_AI_Bypass_Cal_Sel_Neg_Mask = 0x1c00, + MSeries_AI_Bypass_Mode_Mux_Mask = 0x6000, + MSeries_AO_Bypass_AO_Cal_Sel_Mask = 0x38000, + MSeries_AI_Bypass_Gain_Mask = 0x1c0000, + MSeries_AI_Bypass_Dither_Bit = 0x200000, + MSeries_AI_Bypass_Polarity_Bit = 0x400000, /* 0 for 2's complement encoding */ + MSeries_AI_Bypass_Config_FIFO_Bit = 0x80000000 +}; +static inline unsigned MSeries_AI_Bypass_Cal_Sel_Pos_Bits(int + calibration_source) +{ + return (calibration_source << 7) & MSeries_AI_Bypass_Cal_Sel_Pos_Mask; +} + +static inline unsigned MSeries_AI_Bypass_Cal_Sel_Neg_Bits(int + calibration_source) +{ + return (calibration_source << 10) & MSeries_AI_Bypass_Cal_Sel_Pos_Mask; +} + +static inline unsigned MSeries_AI_Bypass_Gain_Bits(int gain) +{ + return (gain << 18) & MSeries_AI_Bypass_Gain_Mask; +} + +enum MSeries_AO_Config_Bank_Bits { + MSeries_AO_DAC_Offset_Select_Mask = 0x7, + MSeries_AO_DAC_Offset_0V_Bits = 0x0, + MSeries_AO_DAC_Offset_5V_Bits = 0x1, + MSeries_AO_DAC_Reference_Mask = 0x38, + MSeries_AO_DAC_Reference_10V_Internal_Bits = 0x0, + MSeries_AO_DAC_Reference_5V_Internal_Bits = 0x8, + MSeries_AO_Update_Timed_Bit = 0x40, + MSeries_AO_Bipolar_Bit = 0x80 /* turns on 2's complement encoding */ +}; + +enum MSeries_AO_Reference_Attenuation_Bits { + MSeries_Attenuate_x5_Bit = 0x1 +}; + +static inline unsigned MSeries_Cal_PWM_High_Time_Bits(unsigned count) +{ + return (count << 16) & 0xffff0000; +} + +static inline unsigned MSeries_Cal_PWM_Low_Time_Bits(unsigned count) +{ + return count & 0xffff; +} + +static inline unsigned MSeries_PFI_Output_Select_Mask(unsigned channel) +{ + return 0x1f << (channel % 3) * 5; +}; + +static inline unsigned MSeries_PFI_Output_Select_Bits(unsigned channel, + unsigned source) +{ + return (source & 0x1f) << ((channel % 3) * 5); +}; + +/* inverse to MSeries_PFI_Output_Select_Bits */ +static inline unsigned MSeries_PFI_Output_Select_Source(unsigned channel, + unsigned bits) +{ + return (bits >> ((channel % 3) * 5)) & 0x1f; +}; + +enum MSeries_Gi_DMA_Config_Bits { + Gi_DMA_BankSW_Error_Bit = 0x10, + Gi_DMA_Reset_Bit = 0x8, + Gi_DMA_Int_Enable_Bit = 0x4, + Gi_DMA_Write_Bit = 0x2, + Gi_DMA_Enable_Bit = 0x1, +}; + +static inline unsigned MSeries_PFI_Filter_Select_Mask(unsigned channel) +{ + return 0x3 << (channel * 2); +} + +static inline unsigned MSeries_PFI_Filter_Select_Bits(unsigned channel, + unsigned filter) +{ + return (filter << (channel * + 2)) & MSeries_PFI_Filter_Select_Mask(channel); +} + +enum CDIO_DMA_Select_Bits { + CDI_DMA_Select_Shift = 0, + CDI_DMA_Select_Mask = 0xf, + CDO_DMA_Select_Shift = 4, + CDO_DMA_Select_Mask = 0xf << CDO_DMA_Select_Shift +}; + +enum CDIO_Status_Bits { + CDO_FIFO_Empty_Bit = 0x1, + CDO_FIFO_Full_Bit = 0x2, + CDO_FIFO_Request_Bit = 0x4, + CDO_Overrun_Bit = 0x8, + CDO_Underflow_Bit = 0x10, + CDI_FIFO_Empty_Bit = 0x10000, + CDI_FIFO_Full_Bit = 0x20000, + CDI_FIFO_Request_Bit = 0x40000, + CDI_Overrun_Bit = 0x80000, + CDI_Overflow_Bit = 0x100000 +}; + +enum CDIO_Command_Bits { + CDO_Disarm_Bit = 0x1, + CDO_Arm_Bit = 0x2, + CDI_Disarm_Bit = 0x4, + CDI_Arm_Bit = 0x8, + CDO_Reset_Bit = 0x10, + CDI_Reset_Bit = 0x20, + CDO_Error_Interrupt_Enable_Set_Bit = 0x40, + CDO_Error_Interrupt_Enable_Clear_Bit = 0x80, + CDI_Error_Interrupt_Enable_Set_Bit = 0x100, + CDI_Error_Interrupt_Enable_Clear_Bit = 0x200, + CDO_FIFO_Request_Interrupt_Enable_Set_Bit = 0x400, + CDO_FIFO_Request_Interrupt_Enable_Clear_Bit = 0x800, + CDI_FIFO_Request_Interrupt_Enable_Set_Bit = 0x1000, + CDI_FIFO_Request_Interrupt_Enable_Clear_Bit = 0x2000, + CDO_Error_Interrupt_Confirm_Bit = 0x4000, + CDI_Error_Interrupt_Confirm_Bit = 0x8000, + CDO_Empty_FIFO_Interrupt_Enable_Set_Bit = 0x10000, + CDO_Empty_FIFO_Interrupt_Enable_Clear_Bit = 0x20000, + CDO_SW_Update_Bit = 0x80000, + CDI_SW_Update_Bit = 0x100000 +}; + +enum CDI_Mode_Bits { + CDI_Sample_Source_Select_Mask = 0x3f, + CDI_Halt_On_Error_Bit = 0x200, + CDI_Polarity_Bit = 0x400, /* sample clock on falling edge */ + CDI_FIFO_Mode_Bit = 0x800, /* set for half full mode, clear for not empty mode */ + CDI_Data_Lane_Mask = 0x3000, /* data lanes specify which dio channels map to byte or word accesses to the dio fifos */ + CDI_Data_Lane_0_15_Bits = 0x0, + CDI_Data_Lane_16_31_Bits = 0x1000, + CDI_Data_Lane_0_7_Bits = 0x0, + CDI_Data_Lane_8_15_Bits = 0x1000, + CDI_Data_Lane_16_23_Bits = 0x2000, + CDI_Data_Lane_24_31_Bits = 0x3000 +}; + +enum CDO_Mode_Bits { + CDO_Sample_Source_Select_Mask = 0x3f, + CDO_Retransmit_Bit = 0x100, + CDO_Halt_On_Error_Bit = 0x200, + CDO_Polarity_Bit = 0x400, /* sample clock on falling edge */ + CDO_FIFO_Mode_Bit = 0x800, /* set for half full mode, clear for not full mode */ + CDO_Data_Lane_Mask = 0x3000, /* data lanes specify which dio channels map to byte or word accesses to the dio fifos */ + CDO_Data_Lane_0_15_Bits = 0x0, + CDO_Data_Lane_16_31_Bits = 0x1000, + CDO_Data_Lane_0_7_Bits = 0x0, + CDO_Data_Lane_8_15_Bits = 0x1000, + CDO_Data_Lane_16_23_Bits = 0x2000, + CDO_Data_Lane_24_31_Bits = 0x3000 +}; + +enum Interrupt_C_Enable_Bits { + Interrupt_Group_C_Enable_Bit = 0x1 +}; + +enum Interrupt_C_Status_Bits { + Interrupt_Group_C_Status_Bit = 0x1 +}; + +#define M_SERIES_EEPROM_SIZE 1024 + +struct ni_board_struct { + int device_id; + int isapnp_id; + char *name; + + int n_adchan; + int adbits; + + int ai_fifo_depth; + unsigned int alwaysdither:1; + int gainlkup; + int ai_speed; + + int n_aochan; + int aobits; + int ao_fifo_depth; + const struct comedi_lrange *ao_range_table; + unsigned ao_speed; + + unsigned num_p0_dio_channels; + + int reg_type; + unsigned int ao_unipolar:1; + unsigned int has_8255:1; + unsigned int has_analog_trig:1; + + enum caldac_enum caldac[3]; +}; + +#define n_ni_boards (sizeof(ni_boards)/sizeof(struct ni_board_struct)) + +#define boardtype (*(struct ni_board_struct *)dev->board_ptr) + +#define MAX_N_AO_CHAN 8 +#define NUM_GPCT 2 + +#define NI_PRIVATE_COMMON \ + uint16_t (*stc_readw)(struct comedi_device *dev, int register); \ + uint32_t (*stc_readl)(struct comedi_device *dev, int register); \ + void (*stc_writew)(struct comedi_device *dev, uint16_t value, int register); \ + void (*stc_writel)(struct comedi_device *dev, uint32_t value, int register); \ + \ + unsigned short dio_output; \ + unsigned short dio_control; \ + int ao0p, ao1p; \ + int lastchan; \ + int last_do; \ + int rt_irq; \ + int irqmask; \ + int aimode; \ + int ai_continuous; \ + int blocksize; \ + int n_left; \ + unsigned int ai_calib_source; \ + unsigned int ai_calib_source_enabled; \ + spinlock_t window_lock; \ + spinlock_t soft_reg_copy_lock; \ + spinlock_t mite_channel_lock; \ + \ + int changain_state; \ + unsigned int changain_spec; \ + \ + unsigned int caldac_maxdata_list[MAX_N_CALDACS]; \ + unsigned short ao[MAX_N_AO_CHAN]; \ + unsigned short caldacs[MAX_N_CALDACS]; \ + \ + unsigned short ai_cmd2; \ + \ + unsigned short ao_conf[MAX_N_AO_CHAN]; \ + unsigned short ao_mode1; \ + unsigned short ao_mode2; \ + unsigned short ao_mode3; \ + unsigned short ao_cmd1; \ + unsigned short ao_cmd2; \ + unsigned short ao_cmd3; \ + unsigned short ao_trigger_select; \ + \ + struct ni_gpct_device *counter_dev; \ + unsigned short an_trig_etc_reg; \ + \ + unsigned ai_offset[512]; \ + \ + unsigned long serial_interval_ns; \ + unsigned char serial_hw_mode; \ + unsigned short clock_and_fout; \ + unsigned short clock_and_fout2; \ + \ + unsigned short int_a_enable_reg; \ + unsigned short int_b_enable_reg; \ + unsigned short io_bidirection_pin_reg; \ + unsigned short rtsi_trig_direction_reg; \ + unsigned short rtsi_trig_a_output_reg; \ + unsigned short rtsi_trig_b_output_reg; \ + unsigned short pfi_output_select_reg[NUM_PFI_OUTPUT_SELECT_REGS]; \ + unsigned short ai_ao_select_reg; \ + unsigned short g0_g1_select_reg; \ + unsigned short cdio_dma_select_reg; \ + \ + unsigned clock_ns; \ + unsigned clock_source; \ + \ + unsigned short atrig_mode; \ + unsigned short atrig_high; \ + unsigned short atrig_low; \ + \ + unsigned short pwm_up_count; \ + unsigned short pwm_down_count; \ + \ + short ai_fifo_buffer[0x2000]; \ + uint8_t eeprom_buffer[M_SERIES_EEPROM_SIZE]; \ + uint32_t serial_number; \ + \ + struct mite_struct *mite; \ + struct mite_channel *ai_mite_chan; \ + struct mite_channel *ao_mite_chan;\ + struct mite_channel *cdo_mite_chan;\ + struct mite_dma_descriptor_ring *ai_mite_ring; \ + struct mite_dma_descriptor_ring *ao_mite_ring; \ + struct mite_dma_descriptor_ring *cdo_mite_ring; \ + struct mite_dma_descriptor_ring *gpct_mite_ring[NUM_GPCT]; + +#endif /* _COMEDI_NI_STC_H */ diff --git a/drivers/staging/comedi/drivers/ni_tio.c b/drivers/staging/comedi/drivers/ni_tio.c new file mode 100644 index 00000000..98f87897 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_tio.c @@ -0,0 +1,1734 @@ +/* + comedi/drivers/ni_tio.c + Support for NI general purpose counters + + Copyright (C) 2006 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* +Driver: ni_tio +Description: National Instruments general purpose counters +Devices: +Author: J.P. Mellor , + Herman.Bruyninckx@mech.kuleuven.ac.be, + Wim.Meeussen@mech.kuleuven.ac.be, + Klaas.Gadeyne@mech.kuleuven.ac.be, + Frank Mori Hess +Updated: Thu Nov 16 09:50:32 EST 2006 +Status: works + +This module is not used directly by end-users. Rather, it +is used by other drivers (for example ni_660x and ni_pcimio) +to provide support for NI's general purpose counters. It was +originally based on the counter code from ni_660x.c and +ni_mio_common.c. + +References: +DAQ 660x Register-Level Programmer Manual (NI 370505A-01) +DAQ 6601/6602 User Manual (NI 322137B-01) +340934b.pdf DAQ-STC reference manual + +*/ +/* +TODO: + Support use of both banks X and Y +*/ + +#include "ni_tio_internal.h" + +static uint64_t ni_tio_clock_period_ps(const struct ni_gpct *counter, + unsigned generic_clock_source); +static unsigned ni_tio_generic_clock_src_select(const struct ni_gpct *counter); + +MODULE_AUTHOR("Comedi "); +MODULE_DESCRIPTION("Comedi support for NI general-purpose counters"); +MODULE_LICENSE("GPL"); + +static inline enum Gi_Counting_Mode_Reg_Bits Gi_Alternate_Sync_Bit(enum + ni_gpct_variant + variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + return Gi_M_Series_Alternate_Sync_Bit; + break; + case ni_gpct_variant_660x: + return Gi_660x_Alternate_Sync_Bit; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum Gi_Counting_Mode_Reg_Bits Gi_Prescale_X2_Bit(enum + ni_gpct_variant + variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + return Gi_M_Series_Prescale_X2_Bit; + break; + case ni_gpct_variant_660x: + return Gi_660x_Prescale_X2_Bit; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum Gi_Counting_Mode_Reg_Bits Gi_Prescale_X8_Bit(enum + ni_gpct_variant + variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + return Gi_M_Series_Prescale_X8_Bit; + break; + case ni_gpct_variant_660x: + return Gi_660x_Prescale_X8_Bit; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum Gi_Counting_Mode_Reg_Bits Gi_HW_Arm_Select_Mask(enum + ni_gpct_variant + variant) +{ + switch (variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + return Gi_M_Series_HW_Arm_Select_Mask; + break; + case ni_gpct_variant_660x: + return Gi_660x_HW_Arm_Select_Mask; + break; + default: + BUG(); + break; + } + return 0; +} + +/* clock sources for ni_660x boards, get bits with Gi_Source_Select_Bits() */ +enum ni_660x_clock_source { + NI_660x_Timebase_1_Clock = 0x0, /* 20MHz */ + NI_660x_Source_Pin_i_Clock = 0x1, + NI_660x_Next_Gate_Clock = 0xa, + NI_660x_Timebase_2_Clock = 0x12, /* 100KHz */ + NI_660x_Next_TC_Clock = 0x13, + NI_660x_Timebase_3_Clock = 0x1e, /* 80MHz */ + NI_660x_Logic_Low_Clock = 0x1f, +}; +static const unsigned ni_660x_max_rtsi_channel = 6; +static inline unsigned NI_660x_RTSI_Clock(unsigned n) +{ + BUG_ON(n > ni_660x_max_rtsi_channel); + return 0xb + n; +} + +static const unsigned ni_660x_max_source_pin = 7; +static inline unsigned NI_660x_Source_Pin_Clock(unsigned n) +{ + BUG_ON(n > ni_660x_max_source_pin); + return 0x2 + n; +} + +/* clock sources for ni e and m series boards, get bits with Gi_Source_Select_Bits() */ +enum ni_m_series_clock_source { + NI_M_Series_Timebase_1_Clock = 0x0, /* 20MHz */ + NI_M_Series_Timebase_2_Clock = 0x12, /* 100KHz */ + NI_M_Series_Next_TC_Clock = 0x13, + NI_M_Series_Next_Gate_Clock = 0x14, /* when Gi_Src_SubSelect = 0 */ + NI_M_Series_PXI_Star_Trigger_Clock = 0x14, /* when Gi_Src_SubSelect = 1 */ + NI_M_Series_PXI10_Clock = 0x1d, + NI_M_Series_Timebase_3_Clock = 0x1e, /* 80MHz, when Gi_Src_SubSelect = 0 */ + NI_M_Series_Analog_Trigger_Out_Clock = 0x1e, /* when Gi_Src_SubSelect = 1 */ + NI_M_Series_Logic_Low_Clock = 0x1f, +}; +static const unsigned ni_m_series_max_pfi_channel = 15; +static inline unsigned NI_M_Series_PFI_Clock(unsigned n) +{ + BUG_ON(n > ni_m_series_max_pfi_channel); + if (n < 10) + return 1 + n; + else + return 0xb + n; +} + +static const unsigned ni_m_series_max_rtsi_channel = 7; +static inline unsigned NI_M_Series_RTSI_Clock(unsigned n) +{ + BUG_ON(n > ni_m_series_max_rtsi_channel); + if (n == 7) + return 0x1b; + else + return 0xb + n; +} + +enum ni_660x_gate_select { + NI_660x_Source_Pin_i_Gate_Select = 0x0, + NI_660x_Gate_Pin_i_Gate_Select = 0x1, + NI_660x_Next_SRC_Gate_Select = 0xa, + NI_660x_Next_Out_Gate_Select = 0x14, + NI_660x_Logic_Low_Gate_Select = 0x1f, +}; +static const unsigned ni_660x_max_gate_pin = 7; +static inline unsigned NI_660x_Gate_Pin_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_660x_max_gate_pin); + return 0x2 + n; +} + +static inline unsigned NI_660x_RTSI_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_660x_max_rtsi_channel); + return 0xb + n; +} + +enum ni_m_series_gate_select { + NI_M_Series_Timestamp_Mux_Gate_Select = 0x0, + NI_M_Series_AI_START2_Gate_Select = 0x12, + NI_M_Series_PXI_Star_Trigger_Gate_Select = 0x13, + NI_M_Series_Next_Out_Gate_Select = 0x14, + NI_M_Series_AI_START1_Gate_Select = 0x1c, + NI_M_Series_Next_SRC_Gate_Select = 0x1d, + NI_M_Series_Analog_Trigger_Out_Gate_Select = 0x1e, + NI_M_Series_Logic_Low_Gate_Select = 0x1f, +}; +static inline unsigned NI_M_Series_RTSI_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_m_series_max_rtsi_channel); + if (n == 7) + return 0x1b; + return 0xb + n; +} + +static inline unsigned NI_M_Series_PFI_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_m_series_max_pfi_channel); + if (n < 10) + return 1 + n; + return 0xb + n; +} + +static inline unsigned Gi_Source_Select_Bits(unsigned source) +{ + return (source << Gi_Source_Select_Shift) & Gi_Source_Select_Mask; +} + +static inline unsigned Gi_Gate_Select_Bits(unsigned gate_select) +{ + return (gate_select << Gi_Gate_Select_Shift) & Gi_Gate_Select_Mask; +} + +enum ni_660x_second_gate_select { + NI_660x_Source_Pin_i_Second_Gate_Select = 0x0, + NI_660x_Up_Down_Pin_i_Second_Gate_Select = 0x1, + NI_660x_Next_SRC_Second_Gate_Select = 0xa, + NI_660x_Next_Out_Second_Gate_Select = 0x14, + NI_660x_Selected_Gate_Second_Gate_Select = 0x1e, + NI_660x_Logic_Low_Second_Gate_Select = 0x1f, +}; +static const unsigned ni_660x_max_up_down_pin = 7; +static inline unsigned NI_660x_Up_Down_Pin_Second_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_660x_max_up_down_pin); + return 0x2 + n; +} + +static inline unsigned NI_660x_RTSI_Second_Gate_Select(unsigned n) +{ + BUG_ON(n > ni_660x_max_rtsi_channel); + return 0xb + n; +} + +static const unsigned int counter_status_mask = + COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING; + +static int __init ni_tio_init_module(void) +{ + return 0; +} + +module_init(ni_tio_init_module); + +static void __exit ni_tio_cleanup_module(void) +{ +} + +module_exit(ni_tio_cleanup_module); + +struct ni_gpct_device *ni_gpct_device_construct(struct comedi_device *dev, + void (*write_register) (struct + ni_gpct + * + counter, + unsigned + bits, + enum + ni_gpct_register + reg), + unsigned (*read_register) + (struct ni_gpct *counter, + enum ni_gpct_register reg), + enum ni_gpct_variant variant, + unsigned num_counters) +{ + unsigned i; + + struct ni_gpct_device *counter_dev = + kzalloc(sizeof(struct ni_gpct_device), GFP_KERNEL); + if (counter_dev == NULL) + return NULL; + counter_dev->dev = dev; + counter_dev->write_register = write_register; + counter_dev->read_register = read_register; + counter_dev->variant = variant; + spin_lock_init(&counter_dev->regs_lock); + BUG_ON(num_counters == 0); + counter_dev->counters = + kzalloc(sizeof(struct ni_gpct) * num_counters, GFP_KERNEL); + if (counter_dev->counters == NULL) { + kfree(counter_dev); + return NULL; + } + for (i = 0; i < num_counters; ++i) { + counter_dev->counters[i].counter_dev = counter_dev; + spin_lock_init(&counter_dev->counters[i].lock); + } + counter_dev->num_counters = num_counters; + return counter_dev; +} +EXPORT_SYMBOL_GPL(ni_gpct_device_construct); + +void ni_gpct_device_destroy(struct ni_gpct_device *counter_dev) +{ + if (counter_dev->counters == NULL) + return; + kfree(counter_dev->counters); + kfree(counter_dev); +} +EXPORT_SYMBOL_GPL(ni_gpct_device_destroy); + +static int ni_tio_second_gate_registers_present(const struct ni_gpct_device + *counter_dev) +{ + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + return 1; + break; + default: + BUG(); + break; + } + return 0; +} + +static void ni_tio_reset_count_and_disarm(struct ni_gpct *counter) +{ + write_register(counter, Gi_Reset_Bit(counter->counter_index), + NITIO_Gxx_Joint_Reset_Reg(counter->counter_index)); +} + +void ni_tio_init_counter(struct ni_gpct *counter) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + + ni_tio_reset_count_and_disarm(counter); + /* initialize counter registers */ + counter_dev->regs[NITIO_Gi_Autoincrement_Reg(counter->counter_index)] = + 0x0; + write_register(counter, + counter_dev-> + regs[NITIO_Gi_Autoincrement_Reg(counter->counter_index)], + NITIO_Gi_Autoincrement_Reg(counter->counter_index)); + ni_tio_set_bits(counter, NITIO_Gi_Command_Reg(counter->counter_index), + ~0, Gi_Synchronize_Gate_Bit); + ni_tio_set_bits(counter, NITIO_Gi_Mode_Reg(counter->counter_index), ~0, + 0); + counter_dev->regs[NITIO_Gi_LoadA_Reg(counter->counter_index)] = 0x0; + write_register(counter, + counter_dev-> + regs[NITIO_Gi_LoadA_Reg(counter->counter_index)], + NITIO_Gi_LoadA_Reg(counter->counter_index)); + counter_dev->regs[NITIO_Gi_LoadB_Reg(counter->counter_index)] = 0x0; + write_register(counter, + counter_dev-> + regs[NITIO_Gi_LoadB_Reg(counter->counter_index)], + NITIO_Gi_LoadB_Reg(counter->counter_index)); + ni_tio_set_bits(counter, + NITIO_Gi_Input_Select_Reg(counter->counter_index), ~0, + 0); + if (ni_tio_counting_mode_registers_present(counter_dev)) { + ni_tio_set_bits(counter, + NITIO_Gi_Counting_Mode_Reg(counter-> + counter_index), ~0, + 0); + } + if (ni_tio_second_gate_registers_present(counter_dev)) { + counter_dev-> + regs[NITIO_Gi_Second_Gate_Reg(counter->counter_index)] = + 0x0; + write_register(counter, + counter_dev-> + regs[NITIO_Gi_Second_Gate_Reg + (counter->counter_index)], + NITIO_Gi_Second_Gate_Reg(counter-> + counter_index)); + } + ni_tio_set_bits(counter, + NITIO_Gi_DMA_Config_Reg(counter->counter_index), ~0, + 0x0); + ni_tio_set_bits(counter, + NITIO_Gi_Interrupt_Enable_Reg(counter->counter_index), + ~0, 0x0); +} +EXPORT_SYMBOL_GPL(ni_tio_init_counter); + +static unsigned int ni_tio_counter_status(struct ni_gpct *counter) +{ + unsigned int status = 0; + const unsigned bits = read_register(counter, + NITIO_Gxx_Status_Reg(counter-> + counter_index)); + if (bits & Gi_Armed_Bit(counter->counter_index)) { + status |= COMEDI_COUNTER_ARMED; + if (bits & Gi_Counting_Bit(counter->counter_index)) + status |= COMEDI_COUNTER_COUNTING; + } + return status; +} + +static void ni_tio_set_sync_mode(struct ni_gpct *counter, int force_alt_sync) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned counting_mode_reg = + NITIO_Gi_Counting_Mode_Reg(counter->counter_index); + static const uint64_t min_normal_sync_period_ps = 25000; + const uint64_t clock_period_ps = ni_tio_clock_period_ps(counter, + ni_tio_generic_clock_src_select + (counter)); + + if (ni_tio_counting_mode_registers_present(counter_dev) == 0) + return; + + switch (ni_tio_get_soft_copy(counter, counting_mode_reg) & Gi_Counting_Mode_Mask) { + case Gi_Counting_Mode_QuadratureX1_Bits: + case Gi_Counting_Mode_QuadratureX2_Bits: + case Gi_Counting_Mode_QuadratureX4_Bits: + case Gi_Counting_Mode_Sync_Source_Bits: + force_alt_sync = 1; + break; + default: + break; + } + /* It's not clear what we should do if clock_period is unknown, so we are not + using the alt sync bit in that case, but allow the caller to decide by using the + force_alt_sync parameter. */ + if (force_alt_sync || + (clock_period_ps && clock_period_ps < min_normal_sync_period_ps)) { + ni_tio_set_bits(counter, counting_mode_reg, + Gi_Alternate_Sync_Bit(counter_dev->variant), + Gi_Alternate_Sync_Bit(counter_dev->variant)); + } else { + ni_tio_set_bits(counter, counting_mode_reg, + Gi_Alternate_Sync_Bit(counter_dev->variant), + 0x0); + } +} + +static int ni_tio_set_counter_mode(struct ni_gpct *counter, unsigned mode) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned mode_reg_mask; + unsigned mode_reg_values; + unsigned input_select_bits = 0; + /* these bits map directly on to the mode register */ + static const unsigned mode_reg_direct_mask = + NI_GPCT_GATE_ON_BOTH_EDGES_BIT | NI_GPCT_EDGE_GATE_MODE_MASK | + NI_GPCT_STOP_MODE_MASK | NI_GPCT_OUTPUT_MODE_MASK | + NI_GPCT_HARDWARE_DISARM_MASK | NI_GPCT_LOADING_ON_TC_BIT | + NI_GPCT_LOADING_ON_GATE_BIT | NI_GPCT_LOAD_B_SELECT_BIT; + + mode_reg_mask = mode_reg_direct_mask | Gi_Reload_Source_Switching_Bit; + mode_reg_values = mode & mode_reg_direct_mask; + switch (mode & NI_GPCT_RELOAD_SOURCE_MASK) { + case NI_GPCT_RELOAD_SOURCE_FIXED_BITS: + break; + case NI_GPCT_RELOAD_SOURCE_SWITCHING_BITS: + mode_reg_values |= Gi_Reload_Source_Switching_Bit; + break; + case NI_GPCT_RELOAD_SOURCE_GATE_SELECT_BITS: + input_select_bits |= Gi_Gate_Select_Load_Source_Bit; + mode_reg_mask |= Gi_Gating_Mode_Mask; + mode_reg_values |= Gi_Level_Gating_Bits; + break; + default: + break; + } + ni_tio_set_bits(counter, NITIO_Gi_Mode_Reg(counter->counter_index), + mode_reg_mask, mode_reg_values); + + if (ni_tio_counting_mode_registers_present(counter_dev)) { + unsigned counting_mode_bits = 0; + counting_mode_bits |= + (mode >> NI_GPCT_COUNTING_MODE_SHIFT) & + Gi_Counting_Mode_Mask; + counting_mode_bits |= + ((mode >> NI_GPCT_INDEX_PHASE_BITSHIFT) << + Gi_Index_Phase_Bitshift) & Gi_Index_Phase_Mask; + if (mode & NI_GPCT_INDEX_ENABLE_BIT) + counting_mode_bits |= Gi_Index_Mode_Bit; + ni_tio_set_bits(counter, + NITIO_Gi_Counting_Mode_Reg(counter-> + counter_index), + Gi_Counting_Mode_Mask | Gi_Index_Phase_Mask | + Gi_Index_Mode_Bit, counting_mode_bits); + ni_tio_set_sync_mode(counter, 0); + } + + ni_tio_set_bits(counter, NITIO_Gi_Command_Reg(counter->counter_index), + Gi_Up_Down_Mask, + (mode >> NI_GPCT_COUNTING_DIRECTION_SHIFT) << + Gi_Up_Down_Shift); + + if (mode & NI_GPCT_OR_GATE_BIT) + input_select_bits |= Gi_Or_Gate_Bit; + if (mode & NI_GPCT_INVERT_OUTPUT_BIT) + input_select_bits |= Gi_Output_Polarity_Bit; + ni_tio_set_bits(counter, + NITIO_Gi_Input_Select_Reg(counter->counter_index), + Gi_Gate_Select_Load_Source_Bit | Gi_Or_Gate_Bit | + Gi_Output_Polarity_Bit, input_select_bits); + + return 0; +} + +int ni_tio_arm(struct ni_gpct *counter, int arm, unsigned start_trigger) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + + unsigned command_transient_bits = 0; + + if (arm) { + switch (start_trigger) { + case NI_GPCT_ARM_IMMEDIATE: + command_transient_bits |= Gi_Arm_Bit; + break; + case NI_GPCT_ARM_PAIRED_IMMEDIATE: + command_transient_bits |= Gi_Arm_Bit | Gi_Arm_Copy_Bit; + break; + default: + break; + } + if (ni_tio_counting_mode_registers_present(counter_dev)) { + unsigned counting_mode_bits = 0; + + switch (start_trigger) { + case NI_GPCT_ARM_IMMEDIATE: + case NI_GPCT_ARM_PAIRED_IMMEDIATE: + break; + default: + if (start_trigger & NI_GPCT_ARM_UNKNOWN) { + /* pass-through the least significant bits so we can figure out what select later */ + unsigned hw_arm_select_bits = + (start_trigger << + Gi_HW_Arm_Select_Shift) & + Gi_HW_Arm_Select_Mask + (counter_dev->variant); + + counting_mode_bits |= + Gi_HW_Arm_Enable_Bit | + hw_arm_select_bits; + } else { + return -EINVAL; + } + break; + } + ni_tio_set_bits(counter, + NITIO_Gi_Counting_Mode_Reg + (counter->counter_index), + Gi_HW_Arm_Select_Mask + (counter_dev->variant) | + Gi_HW_Arm_Enable_Bit, + counting_mode_bits); + } + } else { + command_transient_bits |= Gi_Disarm_Bit; + } + ni_tio_set_bits_transient(counter, + NITIO_Gi_Command_Reg(counter->counter_index), + 0, 0, command_transient_bits); + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_arm); + +static unsigned ni_660x_source_select_bits(unsigned int clock_source) +{ + unsigned ni_660x_clock; + unsigned i; + const unsigned clock_select_bits = + clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK; + + switch (clock_select_bits) { + case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Timebase_1_Clock; + break; + case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Timebase_2_Clock; + break; + case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Timebase_3_Clock; + break; + case NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Logic_Low_Clock; + break; + case NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Source_Pin_i_Clock; + break; + case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Next_Gate_Clock; + break; + case NI_GPCT_NEXT_TC_CLOCK_SRC_BITS: + ni_660x_clock = NI_660x_Next_TC_Clock; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (clock_select_bits == NI_GPCT_RTSI_CLOCK_SRC_BITS(i)) { + ni_660x_clock = NI_660x_RTSI_Clock(i); + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_source_pin; ++i) { + if (clock_select_bits == + NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(i)) { + ni_660x_clock = NI_660x_Source_Pin_Clock(i); + break; + } + } + if (i <= ni_660x_max_source_pin) + break; + ni_660x_clock = 0; + BUG(); + break; + } + return Gi_Source_Select_Bits(ni_660x_clock); +} + +static unsigned ni_m_series_source_select_bits(unsigned int clock_source) +{ + unsigned ni_m_series_clock; + unsigned i; + const unsigned clock_select_bits = + clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK; + switch (clock_select_bits) { + case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Timebase_1_Clock; + break; + case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Timebase_2_Clock; + break; + case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Timebase_3_Clock; + break; + case NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Logic_Low_Clock; + break; + case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Next_Gate_Clock; + break; + case NI_GPCT_NEXT_TC_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Next_TC_Clock; + break; + case NI_GPCT_PXI10_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_PXI10_Clock; + break; + case NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_PXI_Star_Trigger_Clock; + break; + case NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS: + ni_m_series_clock = NI_M_Series_Analog_Trigger_Out_Clock; + break; + default: + for (i = 0; i <= ni_m_series_max_rtsi_channel; ++i) { + if (clock_select_bits == NI_GPCT_RTSI_CLOCK_SRC_BITS(i)) { + ni_m_series_clock = NI_M_Series_RTSI_Clock(i); + break; + } + } + if (i <= ni_m_series_max_rtsi_channel) + break; + for (i = 0; i <= ni_m_series_max_pfi_channel; ++i) { + if (clock_select_bits == NI_GPCT_PFI_CLOCK_SRC_BITS(i)) { + ni_m_series_clock = NI_M_Series_PFI_Clock(i); + break; + } + } + if (i <= ni_m_series_max_pfi_channel) + break; + printk(KERN_ERR "invalid clock source 0x%lx\n", + (unsigned long)clock_source); + BUG(); + ni_m_series_clock = 0; + break; + } + return Gi_Source_Select_Bits(ni_m_series_clock); +}; + +static void ni_tio_set_source_subselect(struct ni_gpct *counter, + unsigned int clock_source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned second_gate_reg = + NITIO_Gi_Second_Gate_Reg(counter->counter_index); + + if (counter_dev->variant != ni_gpct_variant_m_series) + return; + switch (clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK) { + /* Gi_Source_Subselect is zero */ + case NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS: + case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS: + counter_dev->regs[second_gate_reg] &= ~Gi_Source_Subselect_Bit; + break; + /* Gi_Source_Subselect is one */ + case NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS: + case NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS: + counter_dev->regs[second_gate_reg] |= Gi_Source_Subselect_Bit; + break; + /* Gi_Source_Subselect doesn't matter */ + default: + return; + break; + } + write_register(counter, counter_dev->regs[second_gate_reg], + second_gate_reg); +} + +static int ni_tio_set_clock_src(struct ni_gpct *counter, + unsigned int clock_source, + unsigned int period_ns) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned input_select_bits = 0; + static const uint64_t pico_per_nano = 1000; + +/*FIXME: validate clock source */ + switch (counter_dev->variant) { + case ni_gpct_variant_660x: + input_select_bits |= ni_660x_source_select_bits(clock_source); + break; + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + input_select_bits |= + ni_m_series_source_select_bits(clock_source); + break; + default: + BUG(); + break; + } + if (clock_source & NI_GPCT_INVERT_CLOCK_SRC_BIT) + input_select_bits |= Gi_Source_Polarity_Bit; + ni_tio_set_bits(counter, + NITIO_Gi_Input_Select_Reg(counter->counter_index), + Gi_Source_Select_Mask | Gi_Source_Polarity_Bit, + input_select_bits); + ni_tio_set_source_subselect(counter, clock_source); + if (ni_tio_counting_mode_registers_present(counter_dev)) { + const unsigned prescaling_mode = + clock_source & NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK; + unsigned counting_mode_bits = 0; + + switch (prescaling_mode) { + case NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS: + break; + case NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS: + counting_mode_bits |= + Gi_Prescale_X2_Bit(counter_dev->variant); + break; + case NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS: + counting_mode_bits |= + Gi_Prescale_X8_Bit(counter_dev->variant); + break; + default: + return -EINVAL; + break; + } + ni_tio_set_bits(counter, + NITIO_Gi_Counting_Mode_Reg(counter-> + counter_index), + Gi_Prescale_X2_Bit(counter_dev->variant) | + Gi_Prescale_X8_Bit(counter_dev->variant), + counting_mode_bits); + } + counter->clock_period_ps = pico_per_nano * period_ns; + ni_tio_set_sync_mode(counter, 0); + return 0; +} + +static unsigned ni_tio_clock_src_modifiers(const struct ni_gpct *counter) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned counting_mode_bits = ni_tio_get_soft_copy(counter, + NITIO_Gi_Counting_Mode_Reg + (counter-> + counter_index)); + unsigned bits = 0; + + if (ni_tio_get_soft_copy(counter, + NITIO_Gi_Input_Select_Reg + (counter->counter_index)) & + Gi_Source_Polarity_Bit) + bits |= NI_GPCT_INVERT_CLOCK_SRC_BIT; + if (counting_mode_bits & Gi_Prescale_X2_Bit(counter_dev->variant)) + bits |= NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS; + if (counting_mode_bits & Gi_Prescale_X8_Bit(counter_dev->variant)) + bits |= NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS; + return bits; +} + +static unsigned ni_m_series_clock_src_select(const struct ni_gpct *counter) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned second_gate_reg = + NITIO_Gi_Second_Gate_Reg(counter->counter_index); + unsigned clock_source = 0; + unsigned i; + const unsigned input_select = (ni_tio_get_soft_copy(counter, + NITIO_Gi_Input_Select_Reg + (counter->counter_index)) + & Gi_Source_Select_Mask) >> + Gi_Source_Select_Shift; + + switch (input_select) { + case NI_M_Series_Timebase_1_Clock: + clock_source = NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS; + break; + case NI_M_Series_Timebase_2_Clock: + clock_source = NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS; + break; + case NI_M_Series_Timebase_3_Clock: + if (counter_dev->regs[second_gate_reg] & + Gi_Source_Subselect_Bit) + clock_source = + NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS; + else + clock_source = NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS; + break; + case NI_M_Series_Logic_Low_Clock: + clock_source = NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS; + break; + case NI_M_Series_Next_Gate_Clock: + if (counter_dev->regs[second_gate_reg] & + Gi_Source_Subselect_Bit) + clock_source = NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS; + else + clock_source = NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS; + break; + case NI_M_Series_PXI10_Clock: + clock_source = NI_GPCT_PXI10_CLOCK_SRC_BITS; + break; + case NI_M_Series_Next_TC_Clock: + clock_source = NI_GPCT_NEXT_TC_CLOCK_SRC_BITS; + break; + default: + for (i = 0; i <= ni_m_series_max_rtsi_channel; ++i) { + if (input_select == NI_M_Series_RTSI_Clock(i)) { + clock_source = NI_GPCT_RTSI_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= ni_m_series_max_rtsi_channel) + break; + for (i = 0; i <= ni_m_series_max_pfi_channel; ++i) { + if (input_select == NI_M_Series_PFI_Clock(i)) { + clock_source = NI_GPCT_PFI_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= ni_m_series_max_pfi_channel) + break; + BUG(); + break; + } + clock_source |= ni_tio_clock_src_modifiers(counter); + return clock_source; +} + +static unsigned ni_660x_clock_src_select(const struct ni_gpct *counter) +{ + unsigned clock_source = 0; + unsigned i; + const unsigned input_select = (ni_tio_get_soft_copy(counter, + NITIO_Gi_Input_Select_Reg + (counter->counter_index)) + & Gi_Source_Select_Mask) >> + Gi_Source_Select_Shift; + + switch (input_select) { + case NI_660x_Timebase_1_Clock: + clock_source = NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS; + break; + case NI_660x_Timebase_2_Clock: + clock_source = NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS; + break; + case NI_660x_Timebase_3_Clock: + clock_source = NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS; + break; + case NI_660x_Logic_Low_Clock: + clock_source = NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS; + break; + case NI_660x_Source_Pin_i_Clock: + clock_source = NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS; + break; + case NI_660x_Next_Gate_Clock: + clock_source = NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS; + break; + case NI_660x_Next_TC_Clock: + clock_source = NI_GPCT_NEXT_TC_CLOCK_SRC_BITS; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (input_select == NI_660x_RTSI_Clock(i)) { + clock_source = NI_GPCT_RTSI_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_source_pin; ++i) { + if (input_select == NI_660x_Source_Pin_Clock(i)) { + clock_source = + NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(i); + break; + } + } + if (i <= ni_660x_max_source_pin) + break; + BUG(); + break; + } + clock_source |= ni_tio_clock_src_modifiers(counter); + return clock_source; +} + +static unsigned ni_tio_generic_clock_src_select(const struct ni_gpct *counter) +{ + switch (counter->counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + return ni_m_series_clock_src_select(counter); + break; + case ni_gpct_variant_660x: + return ni_660x_clock_src_select(counter); + break; + default: + BUG(); + break; + } + return 0; +} + +static uint64_t ni_tio_clock_period_ps(const struct ni_gpct *counter, + unsigned generic_clock_source) +{ + uint64_t clock_period_ps; + + switch (generic_clock_source & NI_GPCT_CLOCK_SRC_SELECT_MASK) { + case NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS: + clock_period_ps = 50000; + break; + case NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS: + clock_period_ps = 10000000; + break; + case NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS: + clock_period_ps = 12500; + break; + case NI_GPCT_PXI10_CLOCK_SRC_BITS: + clock_period_ps = 100000; + break; + default: + /* clock period is specified by user with prescaling already taken into account. */ + return counter->clock_period_ps; + break; + } + + switch (generic_clock_source & NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK) { + case NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS: + break; + case NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS: + clock_period_ps *= 2; + break; + case NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS: + clock_period_ps *= 8; + break; + default: + BUG(); + break; + } + return clock_period_ps; +} + +static void ni_tio_get_clock_src(struct ni_gpct *counter, + unsigned int *clock_source, + unsigned int *period_ns) +{ + static const unsigned pico_per_nano = 1000; + uint64_t temp64; + *clock_source = ni_tio_generic_clock_src_select(counter); + temp64 = ni_tio_clock_period_ps(counter, *clock_source); + do_div(temp64, pico_per_nano); + *period_ns = temp64; +} + +static void ni_tio_set_first_gate_modifiers(struct ni_gpct *counter, + unsigned int gate_source) +{ + const unsigned mode_mask = Gi_Gate_Polarity_Bit | Gi_Gating_Mode_Mask; + unsigned mode_values = 0; + + if (gate_source & CR_INVERT) + mode_values |= Gi_Gate_Polarity_Bit; + if (gate_source & CR_EDGE) + mode_values |= Gi_Rising_Edge_Gating_Bits; + else + mode_values |= Gi_Level_Gating_Bits; + ni_tio_set_bits(counter, NITIO_Gi_Mode_Reg(counter->counter_index), + mode_mask, mode_values); +} + +static int ni_660x_set_first_gate(struct ni_gpct *counter, + unsigned int gate_source) +{ + const unsigned selected_gate = CR_CHAN(gate_source); + /* bits of selected_gate that may be meaningful to input select register */ + const unsigned selected_gate_mask = 0x1f; + unsigned ni_660x_gate_select; + unsigned i; + + switch (selected_gate) { + case NI_GPCT_NEXT_SOURCE_GATE_SELECT: + ni_660x_gate_select = NI_660x_Next_SRC_Gate_Select; + break; + case NI_GPCT_NEXT_OUT_GATE_SELECT: + case NI_GPCT_LOGIC_LOW_GATE_SELECT: + case NI_GPCT_SOURCE_PIN_i_GATE_SELECT: + case NI_GPCT_GATE_PIN_i_GATE_SELECT: + ni_660x_gate_select = selected_gate & selected_gate_mask; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (selected_gate == NI_GPCT_RTSI_GATE_SELECT(i)) { + ni_660x_gate_select = + selected_gate & selected_gate_mask; + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_gate_pin; ++i) { + if (selected_gate == NI_GPCT_GATE_PIN_GATE_SELECT(i)) { + ni_660x_gate_select = + selected_gate & selected_gate_mask; + break; + } + } + if (i <= ni_660x_max_gate_pin) + break; + return -EINVAL; + break; + } + ni_tio_set_bits(counter, + NITIO_Gi_Input_Select_Reg(counter->counter_index), + Gi_Gate_Select_Mask, + Gi_Gate_Select_Bits(ni_660x_gate_select)); + return 0; +} + +static int ni_m_series_set_first_gate(struct ni_gpct *counter, + unsigned int gate_source) +{ + const unsigned selected_gate = CR_CHAN(gate_source); + /* bits of selected_gate that may be meaningful to input select register */ + const unsigned selected_gate_mask = 0x1f; + unsigned ni_m_series_gate_select; + unsigned i; + + switch (selected_gate) { + case NI_GPCT_TIMESTAMP_MUX_GATE_SELECT: + case NI_GPCT_AI_START2_GATE_SELECT: + case NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT: + case NI_GPCT_NEXT_OUT_GATE_SELECT: + case NI_GPCT_AI_START1_GATE_SELECT: + case NI_GPCT_NEXT_SOURCE_GATE_SELECT: + case NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT: + case NI_GPCT_LOGIC_LOW_GATE_SELECT: + ni_m_series_gate_select = selected_gate & selected_gate_mask; + break; + default: + for (i = 0; i <= ni_m_series_max_rtsi_channel; ++i) { + if (selected_gate == NI_GPCT_RTSI_GATE_SELECT(i)) { + ni_m_series_gate_select = + selected_gate & selected_gate_mask; + break; + } + } + if (i <= ni_m_series_max_rtsi_channel) + break; + for (i = 0; i <= ni_m_series_max_pfi_channel; ++i) { + if (selected_gate == NI_GPCT_PFI_GATE_SELECT(i)) { + ni_m_series_gate_select = + selected_gate & selected_gate_mask; + break; + } + } + if (i <= ni_m_series_max_pfi_channel) + break; + return -EINVAL; + break; + } + ni_tio_set_bits(counter, + NITIO_Gi_Input_Select_Reg(counter->counter_index), + Gi_Gate_Select_Mask, + Gi_Gate_Select_Bits(ni_m_series_gate_select)); + return 0; +} + +static int ni_660x_set_second_gate(struct ni_gpct *counter, + unsigned int gate_source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned second_gate_reg = + NITIO_Gi_Second_Gate_Reg(counter->counter_index); + const unsigned selected_second_gate = CR_CHAN(gate_source); + /* bits of second_gate that may be meaningful to second gate register */ + static const unsigned selected_second_gate_mask = 0x1f; + unsigned ni_660x_second_gate_select; + unsigned i; + + switch (selected_second_gate) { + case NI_GPCT_SOURCE_PIN_i_GATE_SELECT: + case NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT: + case NI_GPCT_SELECTED_GATE_GATE_SELECT: + case NI_GPCT_NEXT_OUT_GATE_SELECT: + case NI_GPCT_LOGIC_LOW_GATE_SELECT: + ni_660x_second_gate_select = + selected_second_gate & selected_second_gate_mask; + break; + case NI_GPCT_NEXT_SOURCE_GATE_SELECT: + ni_660x_second_gate_select = + NI_660x_Next_SRC_Second_Gate_Select; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (selected_second_gate == NI_GPCT_RTSI_GATE_SELECT(i)) { + ni_660x_second_gate_select = + selected_second_gate & + selected_second_gate_mask; + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_up_down_pin; ++i) { + if (selected_second_gate == + NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i)) { + ni_660x_second_gate_select = + selected_second_gate & + selected_second_gate_mask; + break; + } + } + if (i <= ni_660x_max_up_down_pin) + break; + return -EINVAL; + break; + } + counter_dev->regs[second_gate_reg] |= Gi_Second_Gate_Mode_Bit; + counter_dev->regs[second_gate_reg] &= ~Gi_Second_Gate_Select_Mask; + counter_dev->regs[second_gate_reg] |= + Gi_Second_Gate_Select_Bits(ni_660x_second_gate_select); + write_register(counter, counter_dev->regs[second_gate_reg], + second_gate_reg); + return 0; +} + +static int ni_m_series_set_second_gate(struct ni_gpct *counter, + unsigned int gate_source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned second_gate_reg = + NITIO_Gi_Second_Gate_Reg(counter->counter_index); + const unsigned selected_second_gate = CR_CHAN(gate_source); + /* bits of second_gate that may be meaningful to second gate register */ + static const unsigned selected_second_gate_mask = 0x1f; + unsigned ni_m_series_second_gate_select; + + /* FIXME: We don't know what the m-series second gate codes are, so we'll just pass + the bits through for now. */ + switch (selected_second_gate) { + default: + ni_m_series_second_gate_select = + selected_second_gate & selected_second_gate_mask; + break; + } + counter_dev->regs[second_gate_reg] |= Gi_Second_Gate_Mode_Bit; + counter_dev->regs[second_gate_reg] &= ~Gi_Second_Gate_Select_Mask; + counter_dev->regs[second_gate_reg] |= + Gi_Second_Gate_Select_Bits(ni_m_series_second_gate_select); + write_register(counter, counter_dev->regs[second_gate_reg], + second_gate_reg); + return 0; +} + +int ni_tio_set_gate_src(struct ni_gpct *counter, unsigned gate_index, + unsigned int gate_source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned second_gate_reg = + NITIO_Gi_Second_Gate_Reg(counter->counter_index); + + switch (gate_index) { + case 0: + if (CR_CHAN(gate_source) == NI_GPCT_DISABLED_GATE_SELECT) { + ni_tio_set_bits(counter, + NITIO_Gi_Mode_Reg(counter-> + counter_index), + Gi_Gating_Mode_Mask, + Gi_Gating_Disabled_Bits); + return 0; + } + ni_tio_set_first_gate_modifiers(counter, gate_source); + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + return ni_m_series_set_first_gate(counter, gate_source); + break; + case ni_gpct_variant_660x: + return ni_660x_set_first_gate(counter, gate_source); + break; + default: + BUG(); + break; + } + break; + case 1: + if (ni_tio_second_gate_registers_present(counter_dev) == 0) + return -EINVAL; + if (CR_CHAN(gate_source) == NI_GPCT_DISABLED_GATE_SELECT) { + counter_dev->regs[second_gate_reg] &= + ~Gi_Second_Gate_Mode_Bit; + write_register(counter, + counter_dev->regs[second_gate_reg], + second_gate_reg); + return 0; + } + if (gate_source & CR_INVERT) { + counter_dev->regs[second_gate_reg] |= + Gi_Second_Gate_Polarity_Bit; + } else { + counter_dev->regs[second_gate_reg] &= + ~Gi_Second_Gate_Polarity_Bit; + } + switch (counter_dev->variant) { + case ni_gpct_variant_m_series: + return ni_m_series_set_second_gate(counter, + gate_source); + break; + case ni_gpct_variant_660x: + return ni_660x_set_second_gate(counter, gate_source); + break; + default: + BUG(); + break; + } + break; + default: + return -EINVAL; + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_set_gate_src); + +static int ni_tio_set_other_src(struct ni_gpct *counter, unsigned index, + unsigned int source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + + if (counter_dev->variant == ni_gpct_variant_m_series) { + unsigned int abz_reg, shift, mask; + + abz_reg = NITIO_Gi_ABZ_Reg(counter->counter_index); + switch (index) { + case NI_GPCT_SOURCE_ENCODER_A: + shift = 10; + break; + case NI_GPCT_SOURCE_ENCODER_B: + shift = 5; + break; + case NI_GPCT_SOURCE_ENCODER_Z: + shift = 0; + break; + default: + return -EINVAL; + break; + } + mask = 0x1f << shift; + if (source > 0x1f) { + /* Disable gate */ + source = 0x1f; + } + counter_dev->regs[abz_reg] &= ~mask; + counter_dev->regs[abz_reg] |= (source << shift) & mask; + write_register(counter, counter_dev->regs[abz_reg], abz_reg); +/* printk("%s %x %d %d\n", __func__, counter_dev->regs[abz_reg], index, source); */ + return 0; + } + return -EINVAL; +} + +static unsigned ni_660x_first_gate_to_generic_gate_source(unsigned + ni_660x_gate_select) +{ + unsigned i; + + switch (ni_660x_gate_select) { + case NI_660x_Source_Pin_i_Gate_Select: + return NI_GPCT_SOURCE_PIN_i_GATE_SELECT; + break; + case NI_660x_Gate_Pin_i_Gate_Select: + return NI_GPCT_GATE_PIN_i_GATE_SELECT; + break; + case NI_660x_Next_SRC_Gate_Select: + return NI_GPCT_NEXT_SOURCE_GATE_SELECT; + break; + case NI_660x_Next_Out_Gate_Select: + return NI_GPCT_NEXT_OUT_GATE_SELECT; + break; + case NI_660x_Logic_Low_Gate_Select: + return NI_GPCT_LOGIC_LOW_GATE_SELECT; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (ni_660x_gate_select == NI_660x_RTSI_Gate_Select(i)) { + return NI_GPCT_RTSI_GATE_SELECT(i); + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_gate_pin; ++i) { + if (ni_660x_gate_select == + NI_660x_Gate_Pin_Gate_Select(i)) { + return NI_GPCT_GATE_PIN_GATE_SELECT(i); + break; + } + } + if (i <= ni_660x_max_gate_pin) + break; + BUG(); + break; + } + return 0; +}; + +static unsigned ni_m_series_first_gate_to_generic_gate_source(unsigned + ni_m_series_gate_select) +{ + unsigned i; + + switch (ni_m_series_gate_select) { + case NI_M_Series_Timestamp_Mux_Gate_Select: + return NI_GPCT_TIMESTAMP_MUX_GATE_SELECT; + break; + case NI_M_Series_AI_START2_Gate_Select: + return NI_GPCT_AI_START2_GATE_SELECT; + break; + case NI_M_Series_PXI_Star_Trigger_Gate_Select: + return NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT; + break; + case NI_M_Series_Next_Out_Gate_Select: + return NI_GPCT_NEXT_OUT_GATE_SELECT; + break; + case NI_M_Series_AI_START1_Gate_Select: + return NI_GPCT_AI_START1_GATE_SELECT; + break; + case NI_M_Series_Next_SRC_Gate_Select: + return NI_GPCT_NEXT_SOURCE_GATE_SELECT; + break; + case NI_M_Series_Analog_Trigger_Out_Gate_Select: + return NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT; + break; + case NI_M_Series_Logic_Low_Gate_Select: + return NI_GPCT_LOGIC_LOW_GATE_SELECT; + break; + default: + for (i = 0; i <= ni_m_series_max_rtsi_channel; ++i) { + if (ni_m_series_gate_select == + NI_M_Series_RTSI_Gate_Select(i)) { + return NI_GPCT_RTSI_GATE_SELECT(i); + break; + } + } + if (i <= ni_m_series_max_rtsi_channel) + break; + for (i = 0; i <= ni_m_series_max_pfi_channel; ++i) { + if (ni_m_series_gate_select == + NI_M_Series_PFI_Gate_Select(i)) { + return NI_GPCT_PFI_GATE_SELECT(i); + break; + } + } + if (i <= ni_m_series_max_pfi_channel) + break; + BUG(); + break; + } + return 0; +}; + +static unsigned ni_660x_second_gate_to_generic_gate_source(unsigned + ni_660x_gate_select) +{ + unsigned i; + + switch (ni_660x_gate_select) { + case NI_660x_Source_Pin_i_Second_Gate_Select: + return NI_GPCT_SOURCE_PIN_i_GATE_SELECT; + break; + case NI_660x_Up_Down_Pin_i_Second_Gate_Select: + return NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT; + break; + case NI_660x_Next_SRC_Second_Gate_Select: + return NI_GPCT_NEXT_SOURCE_GATE_SELECT; + break; + case NI_660x_Next_Out_Second_Gate_Select: + return NI_GPCT_NEXT_OUT_GATE_SELECT; + break; + case NI_660x_Selected_Gate_Second_Gate_Select: + return NI_GPCT_SELECTED_GATE_GATE_SELECT; + break; + case NI_660x_Logic_Low_Second_Gate_Select: + return NI_GPCT_LOGIC_LOW_GATE_SELECT; + break; + default: + for (i = 0; i <= ni_660x_max_rtsi_channel; ++i) { + if (ni_660x_gate_select == + NI_660x_RTSI_Second_Gate_Select(i)) { + return NI_GPCT_RTSI_GATE_SELECT(i); + break; + } + } + if (i <= ni_660x_max_rtsi_channel) + break; + for (i = 0; i <= ni_660x_max_up_down_pin; ++i) { + if (ni_660x_gate_select == + NI_660x_Up_Down_Pin_Second_Gate_Select(i)) { + return NI_GPCT_UP_DOWN_PIN_GATE_SELECT(i); + break; + } + } + if (i <= ni_660x_max_up_down_pin) + break; + BUG(); + break; + } + return 0; +}; + +static unsigned ni_m_series_second_gate_to_generic_gate_source(unsigned + ni_m_series_gate_select) +{ + /*FIXME: the second gate sources for the m series are undocumented, so we just return + * the raw bits for now. */ + switch (ni_m_series_gate_select) { + default: + return ni_m_series_gate_select; + break; + } + return 0; +}; + +static int ni_tio_get_gate_src(struct ni_gpct *counter, unsigned gate_index, + unsigned int *gate_source) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned mode_bits = ni_tio_get_soft_copy(counter, + NITIO_Gi_Mode_Reg + (counter-> + counter_index)); + const unsigned second_gate_reg = + NITIO_Gi_Second_Gate_Reg(counter->counter_index); + unsigned gate_select_bits; + + switch (gate_index) { + case 0: + if ((mode_bits & Gi_Gating_Mode_Mask) == + Gi_Gating_Disabled_Bits) { + *gate_source = NI_GPCT_DISABLED_GATE_SELECT; + return 0; + } else { + gate_select_bits = + (ni_tio_get_soft_copy(counter, + NITIO_Gi_Input_Select_Reg + (counter->counter_index)) & + Gi_Gate_Select_Mask) >> Gi_Gate_Select_Shift; + } + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + *gate_source = + ni_m_series_first_gate_to_generic_gate_source + (gate_select_bits); + break; + case ni_gpct_variant_660x: + *gate_source = + ni_660x_first_gate_to_generic_gate_source + (gate_select_bits); + break; + default: + BUG(); + break; + } + if (mode_bits & Gi_Gate_Polarity_Bit) + *gate_source |= CR_INVERT; + if ((mode_bits & Gi_Gating_Mode_Mask) != Gi_Level_Gating_Bits) + *gate_source |= CR_EDGE; + break; + case 1: + if ((mode_bits & Gi_Gating_Mode_Mask) == Gi_Gating_Disabled_Bits + || (counter_dev->regs[second_gate_reg] & + Gi_Second_Gate_Mode_Bit) + == 0) { + *gate_source = NI_GPCT_DISABLED_GATE_SELECT; + return 0; + } else { + gate_select_bits = + (counter_dev->regs[second_gate_reg] & + Gi_Second_Gate_Select_Mask) >> + Gi_Second_Gate_Select_Shift; + } + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + case ni_gpct_variant_m_series: + *gate_source = + ni_m_series_second_gate_to_generic_gate_source + (gate_select_bits); + break; + case ni_gpct_variant_660x: + *gate_source = + ni_660x_second_gate_to_generic_gate_source + (gate_select_bits); + break; + default: + BUG(); + break; + } + if (counter_dev->regs[second_gate_reg] & + Gi_Second_Gate_Polarity_Bit) { + *gate_source |= CR_INVERT; + } + /* second gate can't have edge/level mode set independently */ + if ((mode_bits & Gi_Gating_Mode_Mask) != Gi_Level_Gating_Bits) + *gate_source |= CR_EDGE; + break; + default: + return -EINVAL; + break; + } + return 0; +} + +int ni_tio_insn_config(struct ni_gpct *counter, + struct comedi_insn *insn, unsigned int *data) +{ + switch (data[0]) { + case INSN_CONFIG_SET_COUNTER_MODE: + return ni_tio_set_counter_mode(counter, data[1]); + break; + case INSN_CONFIG_ARM: + return ni_tio_arm(counter, 1, data[1]); + break; + case INSN_CONFIG_DISARM: + ni_tio_arm(counter, 0, 0); + return 0; + break; + case INSN_CONFIG_GET_COUNTER_STATUS: + data[1] = ni_tio_counter_status(counter); + data[2] = counter_status_mask; + return 0; + break; + case INSN_CONFIG_SET_CLOCK_SRC: + return ni_tio_set_clock_src(counter, data[1], data[2]); + break; + case INSN_CONFIG_GET_CLOCK_SRC: + ni_tio_get_clock_src(counter, &data[1], &data[2]); + return 0; + break; + case INSN_CONFIG_SET_GATE_SRC: + return ni_tio_set_gate_src(counter, data[1], data[2]); + break; + case INSN_CONFIG_GET_GATE_SRC: + return ni_tio_get_gate_src(counter, data[1], &data[2]); + break; + case INSN_CONFIG_SET_OTHER_SRC: + return ni_tio_set_other_src(counter, data[1], data[2]); + break; + case INSN_CONFIG_RESET: + ni_tio_reset_count_and_disarm(counter); + return 0; + break; + default: + break; + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(ni_tio_insn_config); + +int ni_tio_rinsn(struct ni_gpct *counter, struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned channel = CR_CHAN(insn->chanspec); + unsigned first_read; + unsigned second_read; + unsigned correct_read; + + if (insn->n < 1) + return 0; + switch (channel) { + case 0: + ni_tio_set_bits(counter, + NITIO_Gi_Command_Reg(counter->counter_index), + Gi_Save_Trace_Bit, 0); + ni_tio_set_bits(counter, + NITIO_Gi_Command_Reg(counter->counter_index), + Gi_Save_Trace_Bit, Gi_Save_Trace_Bit); + /* The count doesn't get latched until the next clock edge, so it is possible the count + may change (once) while we are reading. Since the read of the SW_Save_Reg isn't + atomic (apparently even when it's a 32 bit register according to 660x docs), + we need to read twice and make sure the reading hasn't changed. If it has, + a third read will be correct since the count value will definitely have latched by then. */ + first_read = + read_register(counter, + NITIO_Gi_SW_Save_Reg(counter->counter_index)); + second_read = + read_register(counter, + NITIO_Gi_SW_Save_Reg(counter->counter_index)); + if (first_read != second_read) + correct_read = + read_register(counter, + NITIO_Gi_SW_Save_Reg(counter-> + counter_index)); + else + correct_read = first_read; + data[0] = correct_read; + return 0; + break; + case 1: + data[0] = + counter_dev-> + regs[NITIO_Gi_LoadA_Reg(counter->counter_index)]; + break; + case 2: + data[0] = + counter_dev-> + regs[NITIO_Gi_LoadB_Reg(counter->counter_index)]; + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_rinsn); + +static unsigned ni_tio_next_load_register(struct ni_gpct *counter) +{ + const unsigned bits = read_register(counter, + NITIO_Gxx_Status_Reg(counter-> + counter_index)); + + if (bits & Gi_Next_Load_Source_Bit(counter->counter_index)) + return NITIO_Gi_LoadB_Reg(counter->counter_index); + else + return NITIO_Gi_LoadA_Reg(counter->counter_index); +} + +int ni_tio_winsn(struct ni_gpct *counter, struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + const unsigned channel = CR_CHAN(insn->chanspec); + unsigned load_reg; + + if (insn->n < 1) + return 0; + switch (channel) { + case 0: + /* Unsafe if counter is armed. Should probably check status and return -EBUSY if armed. */ + /* Don't disturb load source select, just use whichever load register is already selected. */ + load_reg = ni_tio_next_load_register(counter); + write_register(counter, data[0], load_reg); + ni_tio_set_bits_transient(counter, + NITIO_Gi_Command_Reg(counter-> + counter_index), + 0, 0, Gi_Load_Bit); + /* restore state of load reg to whatever the user set last set it to */ + write_register(counter, counter_dev->regs[load_reg], load_reg); + break; + case 1: + counter_dev->regs[NITIO_Gi_LoadA_Reg(counter->counter_index)] = + data[0]; + write_register(counter, data[0], + NITIO_Gi_LoadA_Reg(counter->counter_index)); + break; + case 2: + counter_dev->regs[NITIO_Gi_LoadB_Reg(counter->counter_index)] = + data[0]; + write_register(counter, data[0], + NITIO_Gi_LoadB_Reg(counter->counter_index)); + break; + default: + return -EINVAL; + break; + } + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_winsn); diff --git a/drivers/staging/comedi/drivers/ni_tio.h b/drivers/staging/comedi/drivers/ni_tio.h new file mode 100644 index 00000000..b0588202 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_tio.h @@ -0,0 +1,174 @@ +/* + drivers/ni_tio.h + Header file for NI general purpose counter support code (ni_tio.c) + + COMEDI - Linux Control and Measurement Device Interface + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _COMEDI_NI_TIO_H +#define _COMEDI_NI_TIO_H + +#include "../comedidev.h" + +/* forward declarations */ +struct mite_struct; +struct ni_gpct_device; + +enum ni_gpct_register { + NITIO_G0_Autoincrement_Reg, + NITIO_G1_Autoincrement_Reg, + NITIO_G2_Autoincrement_Reg, + NITIO_G3_Autoincrement_Reg, + NITIO_G0_Command_Reg, + NITIO_G1_Command_Reg, + NITIO_G2_Command_Reg, + NITIO_G3_Command_Reg, + NITIO_G0_HW_Save_Reg, + NITIO_G1_HW_Save_Reg, + NITIO_G2_HW_Save_Reg, + NITIO_G3_HW_Save_Reg, + NITIO_G0_SW_Save_Reg, + NITIO_G1_SW_Save_Reg, + NITIO_G2_SW_Save_Reg, + NITIO_G3_SW_Save_Reg, + NITIO_G0_Mode_Reg, + NITIO_G1_Mode_Reg, + NITIO_G2_Mode_Reg, + NITIO_G3_Mode_Reg, + NITIO_G0_LoadA_Reg, + NITIO_G1_LoadA_Reg, + NITIO_G2_LoadA_Reg, + NITIO_G3_LoadA_Reg, + NITIO_G0_LoadB_Reg, + NITIO_G1_LoadB_Reg, + NITIO_G2_LoadB_Reg, + NITIO_G3_LoadB_Reg, + NITIO_G0_Input_Select_Reg, + NITIO_G1_Input_Select_Reg, + NITIO_G2_Input_Select_Reg, + NITIO_G3_Input_Select_Reg, + NITIO_G0_Counting_Mode_Reg, + NITIO_G1_Counting_Mode_Reg, + NITIO_G2_Counting_Mode_Reg, + NITIO_G3_Counting_Mode_Reg, + NITIO_G0_Second_Gate_Reg, + NITIO_G1_Second_Gate_Reg, + NITIO_G2_Second_Gate_Reg, + NITIO_G3_Second_Gate_Reg, + NITIO_G01_Status_Reg, + NITIO_G23_Status_Reg, + NITIO_G01_Joint_Reset_Reg, + NITIO_G23_Joint_Reset_Reg, + NITIO_G01_Joint_Status1_Reg, + NITIO_G23_Joint_Status1_Reg, + NITIO_G01_Joint_Status2_Reg, + NITIO_G23_Joint_Status2_Reg, + NITIO_G0_DMA_Config_Reg, + NITIO_G1_DMA_Config_Reg, + NITIO_G2_DMA_Config_Reg, + NITIO_G3_DMA_Config_Reg, + NITIO_G0_DMA_Status_Reg, + NITIO_G1_DMA_Status_Reg, + NITIO_G2_DMA_Status_Reg, + NITIO_G3_DMA_Status_Reg, + NITIO_G0_ABZ_Reg, + NITIO_G1_ABZ_Reg, + NITIO_G0_Interrupt_Acknowledge_Reg, + NITIO_G1_Interrupt_Acknowledge_Reg, + NITIO_G2_Interrupt_Acknowledge_Reg, + NITIO_G3_Interrupt_Acknowledge_Reg, + NITIO_G0_Status_Reg, + NITIO_G1_Status_Reg, + NITIO_G2_Status_Reg, + NITIO_G3_Status_Reg, + NITIO_G0_Interrupt_Enable_Reg, + NITIO_G1_Interrupt_Enable_Reg, + NITIO_G2_Interrupt_Enable_Reg, + NITIO_G3_Interrupt_Enable_Reg, + NITIO_Num_Registers, +}; + +enum ni_gpct_variant { + ni_gpct_variant_e_series, + ni_gpct_variant_m_series, + ni_gpct_variant_660x +}; + +struct ni_gpct { + struct ni_gpct_device *counter_dev; + unsigned counter_index; + unsigned chip_index; + uint64_t clock_period_ps; /* clock period in picoseconds */ + struct mite_channel *mite_chan; + spinlock_t lock; +}; + +struct ni_gpct_device { + struct comedi_device *dev; + void (*write_register) (struct ni_gpct * counter, unsigned bits, + enum ni_gpct_register reg); + unsigned (*read_register) (struct ni_gpct * counter, + enum ni_gpct_register reg); + enum ni_gpct_variant variant; + struct ni_gpct *counters; + unsigned num_counters; + unsigned regs[NITIO_Num_Registers]; + spinlock_t regs_lock; +}; + +extern struct ni_gpct_device *ni_gpct_device_construct(struct comedi_device + *dev, + void (*write_register) + (struct ni_gpct * + counter, unsigned bits, + enum ni_gpct_register + reg), + unsigned (*read_register) + (struct ni_gpct * + counter, + enum ni_gpct_register + reg), + enum ni_gpct_variant + variant, + unsigned num_counters); +extern void ni_gpct_device_destroy(struct ni_gpct_device *counter_dev); +extern void ni_tio_init_counter(struct ni_gpct *counter); +extern int ni_tio_rinsn(struct ni_gpct *counter, + struct comedi_insn *insn, unsigned int *data); +extern int ni_tio_insn_config(struct ni_gpct *counter, + struct comedi_insn *insn, unsigned int *data); +extern int ni_tio_winsn(struct ni_gpct *counter, + struct comedi_insn *insn, unsigned int *data); +extern int ni_tio_cmd(struct ni_gpct *counter, struct comedi_async *async); +extern int ni_tio_cmdtest(struct ni_gpct *counter, struct comedi_cmd *cmd); +extern int ni_tio_cancel(struct ni_gpct *counter); +extern void ni_tio_handle_interrupt(struct ni_gpct *counter, + struct comedi_subdevice *s); +extern void ni_tio_set_mite_channel(struct ni_gpct *counter, + struct mite_channel *mite_chan); +extern void ni_tio_acknowledge_and_confirm(struct ni_gpct *counter, + int *gate_error, int *tc_error, + int *perm_stale_data, + int *stale_data); + +static inline struct ni_gpct *subdev_to_counter(struct comedi_subdevice *s) +{ + return s->private; +} + +#endif /* _COMEDI_NI_TIO_H */ diff --git a/drivers/staging/comedi/drivers/ni_tio_internal.h b/drivers/staging/comedi/drivers/ni_tio_internal.h new file mode 100644 index 00000000..c4ca5378 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_tio_internal.h @@ -0,0 +1,784 @@ +/* + drivers/ni_tio_internal.h + Header file for NI general purpose counter support code (ni_tio.c and + ni_tiocmd.c) + + COMEDI - Linux Control and Measurement Device Interface + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _COMEDI_NI_TIO_INTERNAL_H +#define _COMEDI_NI_TIO_INTERNAL_H + +#include "ni_tio.h" + +static inline enum ni_gpct_register NITIO_Gi_Autoincrement_Reg(unsigned + counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_Autoincrement_Reg; + break; + case 1: + return NITIO_G1_Autoincrement_Reg; + break; + case 2: + return NITIO_G2_Autoincrement_Reg; + break; + case 3: + return NITIO_G3_Autoincrement_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_Command_Reg(unsigned counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_Command_Reg; + break; + case 1: + return NITIO_G1_Command_Reg; + break; + case 2: + return NITIO_G2_Command_Reg; + break; + case 3: + return NITIO_G3_Command_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_Counting_Mode_Reg(unsigned + counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_Counting_Mode_Reg; + break; + case 1: + return NITIO_G1_Counting_Mode_Reg; + break; + case 2: + return NITIO_G2_Counting_Mode_Reg; + break; + case 3: + return NITIO_G3_Counting_Mode_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_Input_Select_Reg(unsigned + counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_Input_Select_Reg; + break; + case 1: + return NITIO_G1_Input_Select_Reg; + break; + case 2: + return NITIO_G2_Input_Select_Reg; + break; + case 3: + return NITIO_G3_Input_Select_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gxx_Joint_Reset_Reg(unsigned + counter_index) +{ + switch (counter_index) { + case 0: + case 1: + return NITIO_G01_Joint_Reset_Reg; + break; + case 2: + case 3: + return NITIO_G23_Joint_Reset_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gxx_Joint_Status1_Reg(unsigned + counter_index) +{ + switch (counter_index) { + case 0: + case 1: + return NITIO_G01_Joint_Status1_Reg; + break; + case 2: + case 3: + return NITIO_G23_Joint_Status1_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gxx_Joint_Status2_Reg(unsigned + counter_index) +{ + switch (counter_index) { + case 0: + case 1: + return NITIO_G01_Joint_Status2_Reg; + break; + case 2: + case 3: + return NITIO_G23_Joint_Status2_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gxx_Status_Reg(unsigned counter_index) +{ + switch (counter_index) { + case 0: + case 1: + return NITIO_G01_Status_Reg; + break; + case 2: + case 3: + return NITIO_G23_Status_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_LoadA_Reg(unsigned counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_LoadA_Reg; + break; + case 1: + return NITIO_G1_LoadA_Reg; + break; + case 2: + return NITIO_G2_LoadA_Reg; + break; + case 3: + return NITIO_G3_LoadA_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_LoadB_Reg(unsigned counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_LoadB_Reg; + break; + case 1: + return NITIO_G1_LoadB_Reg; + break; + case 2: + return NITIO_G2_LoadB_Reg; + break; + case 3: + return NITIO_G3_LoadB_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_Mode_Reg(unsigned counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_Mode_Reg; + break; + case 1: + return NITIO_G1_Mode_Reg; + break; + case 2: + return NITIO_G2_Mode_Reg; + break; + case 3: + return NITIO_G3_Mode_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_SW_Save_Reg(int counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_SW_Save_Reg; + break; + case 1: + return NITIO_G1_SW_Save_Reg; + break; + case 2: + return NITIO_G2_SW_Save_Reg; + break; + case 3: + return NITIO_G3_SW_Save_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_Second_Gate_Reg(int counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_Second_Gate_Reg; + break; + case 1: + return NITIO_G1_Second_Gate_Reg; + break; + case 2: + return NITIO_G2_Second_Gate_Reg; + break; + case 3: + return NITIO_G3_Second_Gate_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_DMA_Config_Reg(int counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_DMA_Config_Reg; + break; + case 1: + return NITIO_G1_DMA_Config_Reg; + break; + case 2: + return NITIO_G2_DMA_Config_Reg; + break; + case 3: + return NITIO_G3_DMA_Config_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_DMA_Status_Reg(int counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_DMA_Status_Reg; + break; + case 1: + return NITIO_G1_DMA_Status_Reg; + break; + case 2: + return NITIO_G2_DMA_Status_Reg; + break; + case 3: + return NITIO_G3_DMA_Status_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_ABZ_Reg(int counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_ABZ_Reg; + break; + case 1: + return NITIO_G1_ABZ_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_Interrupt_Acknowledge_Reg(int + counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_Interrupt_Acknowledge_Reg; + break; + case 1: + return NITIO_G1_Interrupt_Acknowledge_Reg; + break; + case 2: + return NITIO_G2_Interrupt_Acknowledge_Reg; + break; + case 3: + return NITIO_G3_Interrupt_Acknowledge_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_Status_Reg(int counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_Status_Reg; + break; + case 1: + return NITIO_G1_Status_Reg; + break; + case 2: + return NITIO_G2_Status_Reg; + break; + case 3: + return NITIO_G3_Status_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline enum ni_gpct_register NITIO_Gi_Interrupt_Enable_Reg(int + counter_index) +{ + switch (counter_index) { + case 0: + return NITIO_G0_Interrupt_Enable_Reg; + break; + case 1: + return NITIO_G1_Interrupt_Enable_Reg; + break; + case 2: + return NITIO_G2_Interrupt_Enable_Reg; + break; + case 3: + return NITIO_G3_Interrupt_Enable_Reg; + break; + default: + BUG(); + break; + } + return 0; +} + +enum Gi_Auto_Increment_Reg_Bits { + Gi_Auto_Increment_Mask = 0xff +}; + +#define Gi_Up_Down_Shift 5 +enum Gi_Command_Reg_Bits { + Gi_Arm_Bit = 0x1, + Gi_Save_Trace_Bit = 0x2, + Gi_Load_Bit = 0x4, + Gi_Disarm_Bit = 0x10, + Gi_Up_Down_Mask = 0x3 << Gi_Up_Down_Shift, + Gi_Always_Down_Bits = 0x0 << Gi_Up_Down_Shift, + Gi_Always_Up_Bits = 0x1 << Gi_Up_Down_Shift, + Gi_Up_Down_Hardware_IO_Bits = 0x2 << Gi_Up_Down_Shift, + Gi_Up_Down_Hardware_Gate_Bits = 0x3 << Gi_Up_Down_Shift, + Gi_Write_Switch_Bit = 0x80, + Gi_Synchronize_Gate_Bit = 0x100, + Gi_Little_Big_Endian_Bit = 0x200, + Gi_Bank_Switch_Start_Bit = 0x400, + Gi_Bank_Switch_Mode_Bit = 0x800, + Gi_Bank_Switch_Enable_Bit = 0x1000, + Gi_Arm_Copy_Bit = 0x2000, + Gi_Save_Trace_Copy_Bit = 0x4000, + Gi_Disarm_Copy_Bit = 0x8000 +}; + +#define Gi_Index_Phase_Bitshift 5 +#define Gi_HW_Arm_Select_Shift 8 +enum Gi_Counting_Mode_Reg_Bits { + Gi_Counting_Mode_Mask = 0x7, + Gi_Counting_Mode_Normal_Bits = 0x0, + Gi_Counting_Mode_QuadratureX1_Bits = 0x1, + Gi_Counting_Mode_QuadratureX2_Bits = 0x2, + Gi_Counting_Mode_QuadratureX4_Bits = 0x3, + Gi_Counting_Mode_Two_Pulse_Bits = 0x4, + Gi_Counting_Mode_Sync_Source_Bits = 0x6, + Gi_Index_Mode_Bit = 0x10, + Gi_Index_Phase_Mask = 0x3 << Gi_Index_Phase_Bitshift, + Gi_Index_Phase_LowA_LowB = 0x0 << Gi_Index_Phase_Bitshift, + Gi_Index_Phase_LowA_HighB = 0x1 << Gi_Index_Phase_Bitshift, + Gi_Index_Phase_HighA_LowB = 0x2 << Gi_Index_Phase_Bitshift, + Gi_Index_Phase_HighA_HighB = 0x3 << Gi_Index_Phase_Bitshift, + Gi_HW_Arm_Enable_Bit = 0x80, /* from m-series example code, not documented in 660x register level manual */ + Gi_660x_HW_Arm_Select_Mask = 0x7 << Gi_HW_Arm_Select_Shift, /* from m-series example code, not documented in 660x register level manual */ + Gi_660x_Prescale_X8_Bit = 0x1000, + Gi_M_Series_Prescale_X8_Bit = 0x2000, + Gi_M_Series_HW_Arm_Select_Mask = 0x1f << Gi_HW_Arm_Select_Shift, + /* must be set for clocks over 40MHz, which includes synchronous counting and quadrature modes */ + Gi_660x_Alternate_Sync_Bit = 0x2000, + Gi_M_Series_Alternate_Sync_Bit = 0x4000, + Gi_660x_Prescale_X2_Bit = 0x4000, /* from m-series example code, not documented in 660x register level manual */ + Gi_M_Series_Prescale_X2_Bit = 0x8000, +}; + +#define Gi_Source_Select_Shift 2 +#define Gi_Gate_Select_Shift 7 +enum Gi_Input_Select_Bits { + Gi_Read_Acknowledges_Irq = 0x1, /* not present on 660x */ + Gi_Write_Acknowledges_Irq = 0x2, /* not present on 660x */ + Gi_Source_Select_Mask = 0x7c, + Gi_Gate_Select_Mask = 0x1f << Gi_Gate_Select_Shift, + Gi_Gate_Select_Load_Source_Bit = 0x1000, + Gi_Or_Gate_Bit = 0x2000, + Gi_Output_Polarity_Bit = 0x4000, /* set to invert */ + Gi_Source_Polarity_Bit = 0x8000 /* set to invert */ +}; + +enum Gi_Mode_Bits { + Gi_Gating_Mode_Mask = 0x3, + Gi_Gating_Disabled_Bits = 0x0, + Gi_Level_Gating_Bits = 0x1, + Gi_Rising_Edge_Gating_Bits = 0x2, + Gi_Falling_Edge_Gating_Bits = 0x3, + Gi_Gate_On_Both_Edges_Bit = 0x4, /* used in conjunction with rising edge gating mode */ + Gi_Trigger_Mode_for_Edge_Gate_Mask = 0x18, + Gi_Edge_Gate_Starts_Stops_Bits = 0x0, + Gi_Edge_Gate_Stops_Starts_Bits = 0x8, + Gi_Edge_Gate_Starts_Bits = 0x10, + Gi_Edge_Gate_No_Starts_or_Stops_Bits = 0x18, + Gi_Stop_Mode_Mask = 0x60, + Gi_Stop_on_Gate_Bits = 0x00, + Gi_Stop_on_Gate_or_TC_Bits = 0x20, + Gi_Stop_on_Gate_or_Second_TC_Bits = 0x40, + Gi_Load_Source_Select_Bit = 0x80, + Gi_Output_Mode_Mask = 0x300, + Gi_Output_TC_Pulse_Bits = 0x100, + Gi_Output_TC_Toggle_Bits = 0x200, + Gi_Output_TC_or_Gate_Toggle_Bits = 0x300, + Gi_Counting_Once_Mask = 0xc00, + Gi_No_Hardware_Disarm_Bits = 0x000, + Gi_Disarm_at_TC_Bits = 0x400, + Gi_Disarm_at_Gate_Bits = 0x800, + Gi_Disarm_at_TC_or_Gate_Bits = 0xc00, + Gi_Loading_On_TC_Bit = 0x1000, + Gi_Gate_Polarity_Bit = 0x2000, + Gi_Loading_On_Gate_Bit = 0x4000, + Gi_Reload_Source_Switching_Bit = 0x8000 +}; + +#define Gi_Second_Gate_Select_Shift 7 +/*FIXME: m-series has a second gate subselect bit */ +/*FIXME: m-series second gate sources are undocumented (by NI)*/ +enum Gi_Second_Gate_Bits { + Gi_Second_Gate_Mode_Bit = 0x1, + Gi_Second_Gate_Select_Mask = 0x1f << Gi_Second_Gate_Select_Shift, + Gi_Second_Gate_Polarity_Bit = 0x2000, + Gi_Second_Gate_Subselect_Bit = 0x4000, /* m-series only */ + Gi_Source_Subselect_Bit = 0x8000 /* m-series only */ +}; +static inline unsigned Gi_Second_Gate_Select_Bits(unsigned second_gate_select) +{ + return (second_gate_select << Gi_Second_Gate_Select_Shift) & + Gi_Second_Gate_Select_Mask; +} + +enum Gxx_Status_Bits { + G0_Save_Bit = 0x1, + G1_Save_Bit = 0x2, + G0_Counting_Bit = 0x4, + G1_Counting_Bit = 0x8, + G0_Next_Load_Source_Bit = 0x10, + G1_Next_Load_Source_Bit = 0x20, + G0_Stale_Data_Bit = 0x40, + G1_Stale_Data_Bit = 0x80, + G0_Armed_Bit = 0x100, + G1_Armed_Bit = 0x200, + G0_No_Load_Between_Gates_Bit = 0x400, + G1_No_Load_Between_Gates_Bit = 0x800, + G0_TC_Error_Bit = 0x1000, + G1_TC_Error_Bit = 0x2000, + G0_Gate_Error_Bit = 0x4000, + G1_Gate_Error_Bit = 0x8000 +}; +static inline enum Gxx_Status_Bits Gi_Counting_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Counting_Bit; + return G0_Counting_Bit; +} + +static inline enum Gxx_Status_Bits Gi_Armed_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Armed_Bit; + return G0_Armed_Bit; +} + +static inline enum Gxx_Status_Bits Gi_Next_Load_Source_Bit(unsigned + counter_index) +{ + if (counter_index % 2) + return G1_Next_Load_Source_Bit; + return G0_Next_Load_Source_Bit; +} + +static inline enum Gxx_Status_Bits Gi_Stale_Data_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Stale_Data_Bit; + return G0_Stale_Data_Bit; +} + +static inline enum Gxx_Status_Bits Gi_TC_Error_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_TC_Error_Bit; + return G0_TC_Error_Bit; +} + +static inline enum Gxx_Status_Bits Gi_Gate_Error_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Gate_Error_Bit; + return G0_Gate_Error_Bit; +} + +/* joint reset register bits */ +static inline unsigned Gi_Reset_Bit(unsigned counter_index) +{ + return 0x1 << (2 + (counter_index % 2)); +} + +enum Gxx_Joint_Status2_Bits { + G0_Output_Bit = 0x1, + G1_Output_Bit = 0x2, + G0_HW_Save_Bit = 0x1000, + G1_HW_Save_Bit = 0x2000, + G0_Permanent_Stale_Bit = 0x4000, + G1_Permanent_Stale_Bit = 0x8000 +}; +static inline enum Gxx_Joint_Status2_Bits Gi_Permanent_Stale_Bit(unsigned + counter_index) +{ + if (counter_index % 2) + return G1_Permanent_Stale_Bit; + return G0_Permanent_Stale_Bit; +} + +enum Gi_DMA_Config_Reg_Bits { + Gi_DMA_Enable_Bit = 0x1, + Gi_DMA_Write_Bit = 0x2, + Gi_DMA_Int_Bit = 0x4 +}; + +enum Gi_DMA_Status_Reg_Bits { + Gi_DMA_Readbank_Bit = 0x2000, + Gi_DRQ_Error_Bit = 0x4000, + Gi_DRQ_Status_Bit = 0x8000 +}; + +enum G02_Interrupt_Acknowledge_Bits { + G0_Gate_Error_Confirm_Bit = 0x20, + G0_TC_Error_Confirm_Bit = 0x40 +}; +enum G13_Interrupt_Acknowledge_Bits { + G1_Gate_Error_Confirm_Bit = 0x2, + G1_TC_Error_Confirm_Bit = 0x4 +}; +static inline unsigned Gi_Gate_Error_Confirm_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_Gate_Error_Confirm_Bit; + return G0_Gate_Error_Confirm_Bit; +} + +static inline unsigned Gi_TC_Error_Confirm_Bit(unsigned counter_index) +{ + if (counter_index % 2) + return G1_TC_Error_Confirm_Bit; + return G0_TC_Error_Confirm_Bit; +} + +/* bits that are the same in G0/G2 and G1/G3 interrupt acknowledge registers */ +enum Gxx_Interrupt_Acknowledge_Bits { + Gi_TC_Interrupt_Ack_Bit = 0x4000, + Gi_Gate_Interrupt_Ack_Bit = 0x8000 +}; + +enum Gi_Status_Bits { + Gi_Gate_Interrupt_Bit = 0x4, + Gi_TC_Bit = 0x8, + Gi_Interrupt_Bit = 0x8000 +}; + +enum G02_Interrupt_Enable_Bits { + G0_TC_Interrupt_Enable_Bit = 0x40, + G0_Gate_Interrupt_Enable_Bit = 0x100 +}; +enum G13_Interrupt_Enable_Bits { + G1_TC_Interrupt_Enable_Bit = 0x200, + G1_Gate_Interrupt_Enable_Bit = 0x400 +}; +static inline unsigned Gi_Gate_Interrupt_Enable_Bit(unsigned counter_index) +{ + unsigned bit; + + if (counter_index % 2) { + bit = G1_Gate_Interrupt_Enable_Bit; + } else { + bit = G0_Gate_Interrupt_Enable_Bit; + } + return bit; +} + +static inline void write_register(struct ni_gpct *counter, unsigned bits, + enum ni_gpct_register reg) +{ + BUG_ON(reg >= NITIO_Num_Registers); + counter->counter_dev->write_register(counter, bits, reg); +} + +static inline unsigned read_register(struct ni_gpct *counter, + enum ni_gpct_register reg) +{ + BUG_ON(reg >= NITIO_Num_Registers); + return counter->counter_dev->read_register(counter, reg); +} + +static inline int ni_tio_counting_mode_registers_present(const struct + ni_gpct_device + *counter_dev) +{ + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + return 0; + break; + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + return 1; + break; + default: + BUG(); + break; + } + return 0; +} + +static inline void ni_tio_set_bits_transient(struct ni_gpct *counter, + enum ni_gpct_register + register_index, unsigned bit_mask, + unsigned bit_values, + unsigned transient_bit_values) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned long flags; + + BUG_ON(register_index >= NITIO_Num_Registers); + spin_lock_irqsave(&counter_dev->regs_lock, flags); + counter_dev->regs[register_index] &= ~bit_mask; + counter_dev->regs[register_index] |= (bit_values & bit_mask); + write_register(counter, + counter_dev->regs[register_index] | transient_bit_values, + register_index); + mmiowb(); + spin_unlock_irqrestore(&counter_dev->regs_lock, flags); +} + +/* ni_tio_set_bits( ) is for safely writing to registers whose bits may be +twiddled in interrupt context, or whose software copy may be read in interrupt context. +*/ +static inline void ni_tio_set_bits(struct ni_gpct *counter, + enum ni_gpct_register register_index, + unsigned bit_mask, unsigned bit_values) +{ + ni_tio_set_bits_transient(counter, register_index, bit_mask, bit_values, + 0x0); +} + +/* ni_tio_get_soft_copy( ) is for safely reading the software copy of a register +whose bits might be modified in interrupt context, or whose software copy +might need to be read in interrupt context. +*/ +static inline unsigned ni_tio_get_soft_copy(const struct ni_gpct *counter, + enum ni_gpct_register + register_index) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned long flags; + unsigned value; + + BUG_ON(register_index >= NITIO_Num_Registers); + spin_lock_irqsave(&counter_dev->regs_lock, flags); + value = counter_dev->regs[register_index]; + spin_unlock_irqrestore(&counter_dev->regs_lock, flags); + return value; +} + +int ni_tio_arm(struct ni_gpct *counter, int arm, unsigned start_trigger); +int ni_tio_set_gate_src(struct ni_gpct *counter, unsigned gate_index, + unsigned int gate_source); + +#endif /* _COMEDI_NI_TIO_INTERNAL_H */ diff --git a/drivers/staging/comedi/drivers/ni_tiocmd.c b/drivers/staging/comedi/drivers/ni_tiocmd.c new file mode 100644 index 00000000..a9611587 --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_tiocmd.c @@ -0,0 +1,533 @@ +/* + comedi/drivers/ni_tiocmd.c + Command support for NI general purpose counters + + Copyright (C) 2006 Frank Mori Hess + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* +Driver: ni_tiocmd +Description: National Instruments general purpose counters command support +Devices: +Author: J.P. Mellor , + Herman.Bruyninckx@mech.kuleuven.ac.be, + Wim.Meeussen@mech.kuleuven.ac.be, + Klaas.Gadeyne@mech.kuleuven.ac.be, + Frank Mori Hess +Updated: Fri, 11 Apr 2008 12:32:35 +0100 +Status: works + +This module is not used directly by end-users. Rather, it +is used by other drivers (for example ni_660x and ni_pcimio) +to provide command support for NI's general purpose counters. +It was originally split out of ni_tio.c to stop the 'ni_tio' +module depending on the 'mite' module. + +References: +DAQ 660x Register-Level Programmer Manual (NI 370505A-01) +DAQ 6601/6602 User Manual (NI 322137B-01) +340934b.pdf DAQ-STC reference manual + +*/ +/* +TODO: + Support use of both banks X and Y +*/ + +#include "ni_tio_internal.h" +#include "mite.h" + +MODULE_AUTHOR("Comedi "); +MODULE_DESCRIPTION("Comedi command support for NI general-purpose counters"); +MODULE_LICENSE("GPL"); + +static void ni_tio_configure_dma(struct ni_gpct *counter, short enable, + short read_not_write) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + unsigned input_select_bits = 0; + + if (enable) { + if (read_not_write) + input_select_bits |= Gi_Read_Acknowledges_Irq; + else + input_select_bits |= Gi_Write_Acknowledges_Irq; + } + ni_tio_set_bits(counter, + NITIO_Gi_Input_Select_Reg(counter->counter_index), + Gi_Read_Acknowledges_Irq | Gi_Write_Acknowledges_Irq, + input_select_bits); + switch (counter_dev->variant) { + case ni_gpct_variant_e_series: + break; + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + { + unsigned gi_dma_config_bits = 0; + + if (enable) { + gi_dma_config_bits |= Gi_DMA_Enable_Bit; + gi_dma_config_bits |= Gi_DMA_Int_Bit; + } + if (read_not_write == 0) + gi_dma_config_bits |= Gi_DMA_Write_Bit; + ni_tio_set_bits(counter, + NITIO_Gi_DMA_Config_Reg(counter-> + counter_index), + Gi_DMA_Enable_Bit | Gi_DMA_Int_Bit | + Gi_DMA_Write_Bit, gi_dma_config_bits); + } + break; + } +} + +static int ni_tio_input_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trignum) +{ + unsigned long flags; + int retval = 0; + struct ni_gpct *counter = s->private; + + BUG_ON(counter == NULL); + if (trignum != 0) + return -EINVAL; + + spin_lock_irqsave(&counter->lock, flags); + if (counter->mite_chan) + mite_dma_arm(counter->mite_chan); + else + retval = -EIO; + spin_unlock_irqrestore(&counter->lock, flags); + if (retval < 0) + return retval; + retval = ni_tio_arm(counter, 1, NI_GPCT_ARM_IMMEDIATE); + s->async->inttrig = NULL; + + return retval; +} + +static int ni_tio_input_cmd(struct ni_gpct *counter, struct comedi_async *async) +{ + struct ni_gpct_device *counter_dev = counter->counter_dev; + struct comedi_cmd *cmd = &async->cmd; + int retval = 0; + + /* write alloc the entire buffer */ + comedi_buf_write_alloc(async, async->prealloc_bufsz); + counter->mite_chan->dir = COMEDI_INPUT; + switch (counter_dev->variant) { + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + mite_prep_dma(counter->mite_chan, 32, 32); + break; + case ni_gpct_variant_e_series: + mite_prep_dma(counter->mite_chan, 16, 32); + break; + default: + BUG(); + break; + } + ni_tio_set_bits(counter, NITIO_Gi_Command_Reg(counter->counter_index), + Gi_Save_Trace_Bit, 0); + ni_tio_configure_dma(counter, 1, 1); + switch (cmd->start_src) { + case TRIG_NOW: + async->inttrig = NULL; + mite_dma_arm(counter->mite_chan); + retval = ni_tio_arm(counter, 1, NI_GPCT_ARM_IMMEDIATE); + break; + case TRIG_INT: + async->inttrig = &ni_tio_input_inttrig; + break; + case TRIG_EXT: + async->inttrig = NULL; + mite_dma_arm(counter->mite_chan); + retval = ni_tio_arm(counter, 1, cmd->start_arg); + case TRIG_OTHER: + async->inttrig = NULL; + mite_dma_arm(counter->mite_chan); + break; + default: + BUG(); + break; + } + return retval; +} + +static int ni_tio_output_cmd(struct ni_gpct *counter, + struct comedi_async *async) +{ + printk(KERN_ERR "ni_tio: output commands not yet implemented.\n"); + return -ENOTSUPP; + + counter->mite_chan->dir = COMEDI_OUTPUT; + mite_prep_dma(counter->mite_chan, 32, 32); + ni_tio_configure_dma(counter, 1, 0); + mite_dma_arm(counter->mite_chan); + return ni_tio_arm(counter, 1, NI_GPCT_ARM_IMMEDIATE); +} + +static int ni_tio_cmd_setup(struct ni_gpct *counter, struct comedi_async *async) +{ + struct comedi_cmd *cmd = &async->cmd; + int set_gate_source = 0; + unsigned gate_source; + int retval = 0; + + if (cmd->scan_begin_src == TRIG_EXT) { + set_gate_source = 1; + gate_source = cmd->scan_begin_arg; + } else if (cmd->convert_src == TRIG_EXT) { + set_gate_source = 1; + gate_source = cmd->convert_arg; + } + if (set_gate_source) + retval = ni_tio_set_gate_src(counter, 0, gate_source); + if (cmd->flags & TRIG_WAKE_EOS) { + ni_tio_set_bits(counter, + NITIO_Gi_Interrupt_Enable_Reg(counter-> + counter_index), + Gi_Gate_Interrupt_Enable_Bit(counter-> + counter_index), + Gi_Gate_Interrupt_Enable_Bit(counter-> + counter_index)); + } + return retval; +} + +int ni_tio_cmd(struct ni_gpct *counter, struct comedi_async *async) +{ + struct comedi_cmd *cmd = &async->cmd; + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&counter->lock, flags); + if (counter->mite_chan == NULL) { + printk(KERN_ERR "ni_tio: commands only supported with DMA. Interrupt-driven commands not yet implemented.\n"); + retval = -EIO; + } else { + retval = ni_tio_cmd_setup(counter, async); + if (retval == 0) { + if (cmd->flags & CMDF_WRITE) + retval = ni_tio_output_cmd(counter, async); + else + retval = ni_tio_input_cmd(counter, async); + } + } + spin_unlock_irqrestore(&counter->lock, flags); + return retval; +} +EXPORT_SYMBOL_GPL(ni_tio_cmd); + +int ni_tio_cmdtest(struct ni_gpct *counter, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + int sources; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + sources = TRIG_NOW | TRIG_INT | TRIG_OTHER; + if (ni_tio_counting_mode_registers_present(counter->counter_dev)) + sources |= TRIG_EXT; + cmd->start_src &= sources; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_EXT | TRIG_OTHER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + sources = TRIG_NOW | TRIG_EXT | TRIG_OTHER; + cmd->convert_src &= sources; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique... */ + + if (cmd->start_src != TRIG_NOW && + cmd->start_src != TRIG_INT && + cmd->start_src != TRIG_EXT && cmd->start_src != TRIG_OTHER) + err++; + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_OTHER) + err++; + if (cmd->convert_src != TRIG_OTHER && + cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW) + err++; + if (cmd->stop_src != TRIG_NONE) + err++; + /* ... and mutually compatible */ + if (cmd->convert_src != TRIG_NOW && cmd->scan_begin_src != TRIG_FOLLOW) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + if (cmd->start_src != TRIG_EXT) { + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + } + if (cmd->scan_begin_src != TRIG_EXT) { + if (cmd->scan_begin_arg) { + cmd->scan_begin_arg = 0; + err++; + } + } + if (cmd->convert_src != TRIG_EXT) { + if (cmd->convert_arg) { + cmd->convert_arg = 0; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_NONE) { + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (err) + return 4; + + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_cmdtest); + +int ni_tio_cancel(struct ni_gpct *counter) +{ + unsigned long flags; + + ni_tio_arm(counter, 0, 0); + spin_lock_irqsave(&counter->lock, flags); + if (counter->mite_chan) + mite_dma_disarm(counter->mite_chan); + spin_unlock_irqrestore(&counter->lock, flags); + ni_tio_configure_dma(counter, 0, 0); + + ni_tio_set_bits(counter, + NITIO_Gi_Interrupt_Enable_Reg(counter->counter_index), + Gi_Gate_Interrupt_Enable_Bit(counter->counter_index), + 0x0); + return 0; +} +EXPORT_SYMBOL_GPL(ni_tio_cancel); + + /* During buffered input counter operation for e-series, the gate + interrupt is acked automatically by the dma controller, due to the + Gi_Read/Write_Acknowledges_IRQ bits in the input select register. */ +static int should_ack_gate(struct ni_gpct *counter) +{ + unsigned long flags; + int retval = 0; + + switch (counter->counter_dev->variant) { + case ni_gpct_variant_m_series: + /* not sure if 660x really supports gate + interrupts (the bits are not listed + in register-level manual) */ + case ni_gpct_variant_660x: + return 1; + break; + case ni_gpct_variant_e_series: + spin_lock_irqsave(&counter->lock, flags); + { + if (counter->mite_chan == NULL || + counter->mite_chan->dir != COMEDI_INPUT || + (mite_done(counter->mite_chan))) { + retval = 1; + } + } + spin_unlock_irqrestore(&counter->lock, flags); + break; + } + return retval; +} + +void ni_tio_acknowledge_and_confirm(struct ni_gpct *counter, int *gate_error, + int *tc_error, int *perm_stale_data, + int *stale_data) +{ + const unsigned short gxx_status = read_register(counter, + NITIO_Gxx_Status_Reg + (counter-> + counter_index)); + const unsigned short gi_status = read_register(counter, + NITIO_Gi_Status_Reg + (counter-> + counter_index)); + unsigned ack = 0; + + if (gate_error) + *gate_error = 0; + if (tc_error) + *tc_error = 0; + if (perm_stale_data) + *perm_stale_data = 0; + if (stale_data) + *stale_data = 0; + + if (gxx_status & Gi_Gate_Error_Bit(counter->counter_index)) { + ack |= Gi_Gate_Error_Confirm_Bit(counter->counter_index); + if (gate_error) { + /*660x don't support automatic acknowledgement + of gate interrupt via dma read/write + and report bogus gate errors */ + if (counter->counter_dev->variant != + ni_gpct_variant_660x) { + *gate_error = 1; + } + } + } + if (gxx_status & Gi_TC_Error_Bit(counter->counter_index)) { + ack |= Gi_TC_Error_Confirm_Bit(counter->counter_index); + if (tc_error) + *tc_error = 1; + } + if (gi_status & Gi_TC_Bit) + ack |= Gi_TC_Interrupt_Ack_Bit; + if (gi_status & Gi_Gate_Interrupt_Bit) { + if (should_ack_gate(counter)) + ack |= Gi_Gate_Interrupt_Ack_Bit; + } + if (ack) + write_register(counter, ack, + NITIO_Gi_Interrupt_Acknowledge_Reg + (counter->counter_index)); + if (ni_tio_get_soft_copy + (counter, + NITIO_Gi_Mode_Reg(counter->counter_index)) & + Gi_Loading_On_Gate_Bit) { + if (gxx_status & Gi_Stale_Data_Bit(counter->counter_index)) { + if (stale_data) + *stale_data = 1; + } + if (read_register(counter, + NITIO_Gxx_Joint_Status2_Reg + (counter->counter_index)) & + Gi_Permanent_Stale_Bit(counter->counter_index)) { + printk(KERN_INFO "%s: Gi_Permanent_Stale_Data detected.\n", + __func__); + if (perm_stale_data) + *perm_stale_data = 1; + } + } +} +EXPORT_SYMBOL_GPL(ni_tio_acknowledge_and_confirm); + +void ni_tio_handle_interrupt(struct ni_gpct *counter, + struct comedi_subdevice *s) +{ + unsigned gpct_mite_status; + unsigned long flags; + int gate_error; + int tc_error; + int perm_stale_data; + + ni_tio_acknowledge_and_confirm(counter, &gate_error, &tc_error, + &perm_stale_data, NULL); + if (gate_error) { + printk(KERN_NOTICE "%s: Gi_Gate_Error detected.\n", __func__); + s->async->events |= COMEDI_CB_OVERFLOW; + } + if (perm_stale_data) + s->async->events |= COMEDI_CB_ERROR; + switch (counter->counter_dev->variant) { + case ni_gpct_variant_m_series: + case ni_gpct_variant_660x: + if (read_register(counter, + NITIO_Gi_DMA_Status_Reg + (counter->counter_index)) & Gi_DRQ_Error_Bit) { + printk(KERN_NOTICE "%s: Gi_DRQ_Error detected.\n", + __func__); + s->async->events |= COMEDI_CB_OVERFLOW; + } + break; + case ni_gpct_variant_e_series: + break; + } + spin_lock_irqsave(&counter->lock, flags); + if (counter->mite_chan == NULL) { + spin_unlock_irqrestore(&counter->lock, flags); + return; + } + gpct_mite_status = mite_get_status(counter->mite_chan); + if (gpct_mite_status & CHSR_LINKC) { + writel(CHOR_CLRLC, + counter->mite_chan->mite->mite_io_addr + + MITE_CHOR(counter->mite_chan->channel)); + } + mite_sync_input_dma(counter->mite_chan, s->async); + spin_unlock_irqrestore(&counter->lock, flags); +} +EXPORT_SYMBOL_GPL(ni_tio_handle_interrupt); + +void ni_tio_set_mite_channel(struct ni_gpct *counter, + struct mite_channel *mite_chan) +{ + unsigned long flags; + + spin_lock_irqsave(&counter->lock, flags); + counter->mite_chan = mite_chan; + spin_unlock_irqrestore(&counter->lock, flags); +} +EXPORT_SYMBOL_GPL(ni_tio_set_mite_channel); + +static int __init ni_tiocmd_init_module(void) +{ + return 0; +} + +module_init(ni_tiocmd_init_module); + +static void __exit ni_tiocmd_cleanup_module(void) +{ +} + +module_exit(ni_tiocmd_cleanup_module); diff --git a/drivers/staging/comedi/drivers/pcl711.c b/drivers/staging/comedi/drivers/pcl711.c new file mode 100644 index 00000000..b44386a6 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl711.c @@ -0,0 +1,645 @@ +/* + comedi/drivers/pcl711.c + hardware driver for PC-LabCard PCL-711 and AdSys ACL-8112 + and compatibles + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + Janne Jalkanen + Eric Bunn + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: pcl711 +Description: Advantech PCL-711 and 711b, ADLink ACL-8112 +Author: ds, Janne Jalkanen , Eric Bunn +Status: mostly complete +Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b), + [AdLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg) + +Since these boards do not have DMA or FIFOs, only immediate mode is +supported. + +*/ + +/* + Dave Andruczyk also wrote a + driver for the PCL-711. I used a few ideas from his driver + here. His driver also has more comments, if you are + interested in understanding how this driver works. + http://tech.buffalostate.edu/~dave/driver/ + + The ACL-8112 driver was hacked from the sources of the PCL-711 + driver (the 744 chip used on the 8112 is almost the same as + the 711b chip, but it has more I/O channels) by + Janne Jalkanen (jalkanen@cs.hut.fi) and + Erik Bunn (ebu@cs.hut.fi). Remerged with the PCL-711 driver + by ds. + + [acl-8112] + This driver supports both TRIGNOW and TRIGCLK, + but does not yet support DMA transfers. It also supports + both high (HG) and low (DG) versions of the card, though + the HG version has been untested. + + */ + +#include +#include "../comedidev.h" + +#include +#include + +#include "8253.h" + +#define PCL711_SIZE 16 + +#define PCL711_CTR0 0 +#define PCL711_CTR1 1 +#define PCL711_CTR2 2 +#define PCL711_CTRCTL 3 +#define PCL711_AD_LO 4 +#define PCL711_DA0_LO 4 +#define PCL711_AD_HI 5 +#define PCL711_DA0_HI 5 +#define PCL711_DI_LO 6 +#define PCL711_DA1_LO 6 +#define PCL711_DI_HI 7 +#define PCL711_DA1_HI 7 +#define PCL711_CLRINTR 8 +#define PCL711_GAIN 9 +#define PCL711_MUX 10 +#define PCL711_MODE 11 +#define PCL711_SOFTTRIG 12 +#define PCL711_DO_LO 13 +#define PCL711_DO_HI 14 + +static const struct comedi_lrange range_pcl711b_ai = { 5, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + BIP_RANGE(0.3125) + } +}; + +static const struct comedi_lrange range_acl8112hg_ai = { 12, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01), + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01) + } +}; + +static const struct comedi_lrange range_acl8112dg_ai = { 9, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + BIP_RANGE(10) + } +}; + +/* + * flags + */ + +#define PCL711_TIMEOUT 100 +#define PCL711_DRDY 0x10 + +static const int i8253_osc_base = 500; /* 2 Mhz */ + +struct pcl711_board { + + const char *name; + int is_pcl711b; + int is_8112; + int is_dg; + int n_ranges; + int n_aichan; + int n_aochan; + int maxirq; + const struct comedi_lrange *ai_range_type; +}; + +static const struct pcl711_board boardtypes[] = { + {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5}, + {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai}, + {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai}, + {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl711_board)) +#define this_board ((const struct pcl711_board *)dev->board_ptr) + +static int pcl711_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcl711_detach(struct comedi_device *dev); +static struct comedi_driver driver_pcl711 = { + .driver_name = "pcl711", + .module = THIS_MODULE, + .attach = pcl711_attach, + .detach = pcl711_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct pcl711_board), +}; + +static int __init driver_pcl711_init_module(void) +{ + return comedi_driver_register(&driver_pcl711); +} + +static void __exit driver_pcl711_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcl711); +} + +module_init(driver_pcl711_init_module); +module_exit(driver_pcl711_cleanup_module); + +struct pcl711_private { + + int board; + int adchan; + int ntrig; + int aip[8]; + int mode; + unsigned int ao_readback[2]; + unsigned int divisor1; + unsigned int divisor2; +}; + +#define devpriv ((struct pcl711_private *)dev->private) + +static irqreturn_t pcl711_interrupt(int irq, void *d) +{ + int lo, hi; + int data; + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + + if (!dev->attached) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + + hi = inb(dev->iobase + PCL711_AD_HI); + lo = inb(dev->iobase + PCL711_AD_LO); + outb(0, dev->iobase + PCL711_CLRINTR); + + data = (hi << 8) | lo; + + /* FIXME! Nothing else sets ntrig! */ + if (!(--devpriv->ntrig)) { + if (this_board->is_8112) + outb(1, dev->iobase + PCL711_MODE); + else + outb(0, dev->iobase + PCL711_MODE); + + s->async->events |= COMEDI_CB_EOA; + } + comedi_event(dev, s); + return IRQ_HANDLED; +} + +static void pcl711_set_changain(struct comedi_device *dev, int chan) +{ + int chan_register; + + outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN); + + chan_register = CR_CHAN(chan); + + if (this_board->is_8112) { + + /* + * Set the correct channel. The two channel banks are switched + * using the mask value. + * NB: To use differential channels, you should use + * mask = 0x30, but I haven't written the support for this + * yet. /JJ + */ + + if (chan_register >= 8) + chan_register = 0x20 | (chan_register & 0x7); + else + chan_register |= 0x10; + } else { + outb(chan_register, dev->iobase + PCL711_MUX); + } +} + +static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, n; + int hi, lo; + + pcl711_set_changain(dev, insn->chanspec); + + for (n = 0; n < insn->n; n++) { + /* + * Write the correct mode (software polling) and start polling + * by writing to the trigger register + */ + outb(1, dev->iobase + PCL711_MODE); + + if (!this_board->is_8112) + outb(0, dev->iobase + PCL711_SOFTTRIG); + + i = PCL711_TIMEOUT; + while (--i) { + hi = inb(dev->iobase + PCL711_AD_HI); + if (!(hi & PCL711_DRDY)) + goto ok; + udelay(1); + } + printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor); + return -ETIME; + +ok: + lo = inb(dev->iobase + PCL711_AD_LO); + + data[n] = ((hi & 0xf) << 8) | lo; + } + + return n; +} + +static int pcl711_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int tmp; + int err = 0; + + /* step 1 */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2 */ + + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3 */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_src == TRIG_EXT) { + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } else { +#define MAX_SPEED 1000 +#define TIMER_BASE 100 + if (cmd->scan_begin_arg < MAX_SPEED) { + cmd->scan_begin_arg = MAX_SPEED; + err++; + } + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_NONE) { + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } else { + /* ignore */ + } + + if (err) + return 3; + + /* step 4 */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + i8253_cascade_ns_to_timer_2div(TIMER_BASE, + &devpriv->divisor1, + &devpriv->divisor2, + &cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (err) + return 4; + + return 0; +} + +static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int timer1, timer2; + struct comedi_cmd *cmd = &s->async->cmd; + + pcl711_set_changain(dev, cmd->chanlist[0]); + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* + * Set timers + * timer chip is an 8253, with timers 1 and 2 + * cascaded + * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary + * Mode 2 = Rate generator + * + * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary + */ + + timer1 = timer2 = 0; + i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2, + &cmd->scan_begin_arg, + TRIG_ROUND_NEAREST); + + outb(0x74, dev->iobase + PCL711_CTRCTL); + outb(timer1 & 0xff, dev->iobase + PCL711_CTR1); + outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1); + outb(0xb4, dev->iobase + PCL711_CTRCTL); + outb(timer2 & 0xff, dev->iobase + PCL711_CTR2); + outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2); + + /* clear pending interrupts (just in case) */ + outb(0, dev->iobase + PCL711_CLRINTR); + + /* + * Set mode to IRQ transfer + */ + outb(devpriv->mode | 6, dev->iobase + PCL711_MODE); + } else { + /* external trigger */ + outb(devpriv->mode | 3, dev->iobase + PCL711_MODE); + } + + return 0; +} + +/* + analog output +*/ +static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) { + outb((data[n] & 0xff), + dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO)); + outb((data[n] >> 8), + dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI)); + + devpriv->ao_readback[chan] = data[n]; + } + + return n; +} + +static int pcl711_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_readback[chan]; + + return n; + +} + +/* Digital port read - Untested on 8112 */ +static int pcl711_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + PCL711_DI_LO) | + (inb(dev->iobase + PCL711_DI_HI) << 8); + + return 2; +} + +/* Digital port write - Untested on 8112 */ +static int pcl711_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + } + if (data[0] & 0x00ff) + outb(s->state & 0xff, dev->iobase + PCL711_DO_LO); + if (data[0] & 0xff00) + outb((s->state >> 8), dev->iobase + PCL711_DO_HI); + + data[1] = s->state; + + return 2; +} + +/* Free any resources that we have claimed */ +static int pcl711_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pcl711: remove\n", dev->minor); + + if (dev->irq) + free_irq(dev->irq, dev); + + if (dev->iobase) + release_region(dev->iobase, PCL711_SIZE); + + return 0; +} + +/* Initialization */ +static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + unsigned long iobase; + unsigned int irq; + struct comedi_subdevice *s; + + /* claim our I/O space */ + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: pcl711: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, PCL711_SIZE, "pcl711")) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + /* there should be a sanity check here */ + + /* set up some name stuff */ + dev->board_name = this_board->name; + + /* grab our IRQ */ + irq = it->options[1]; + if (irq > this_board->maxirq) { + printk(KERN_ERR "irq out of range\n"); + return -EINVAL; + } + if (irq) { + if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) { + printk(KERN_ERR "unable to allocate irq %u\n", irq); + return -EINVAL; + } else { + printk(KERN_INFO "( irq = %u )\n", irq); + } + } + dev->irq = irq; + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + ret = alloc_private(dev, sizeof(struct pcl711_private)); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + /* AI subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = this_board->n_aichan; + s->maxdata = 0xfff; + s->len_chanlist = 1; + s->range_table = this_board->ai_range_type; + s->insn_read = pcl711_ai_insn; + if (irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->do_cmdtest = pcl711_ai_cmdtest; + s->do_cmd = pcl711_ai_cmd; + } + + s++; + /* AO subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = this_board->n_aochan; + s->maxdata = 0xfff; + s->len_chanlist = 1; + s->range_table = &range_bipolar5; + s->insn_write = pcl711_ao_insn; + s->insn_read = pcl711_ao_insn_read; + + s++; + /* 16-bit digital input */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->len_chanlist = 16; + s->range_table = &range_digital; + s->insn_bits = pcl711_di_insn_bits; + + s++; + /* 16-bit digital out */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->len_chanlist = 16; + s->range_table = &range_digital; + s->state = 0; + s->insn_bits = pcl711_do_insn_bits; + + /* + this is the "base value" for the mode register, which is + used for the irq on the PCL711 + */ + if (this_board->is_pcl711b) + devpriv->mode = (dev->irq << 4); + + /* clear DAC */ + outb(0, dev->iobase + PCL711_DA0_LO); + outb(0, dev->iobase + PCL711_DA0_HI); + outb(0, dev->iobase + PCL711_DA1_LO); + outb(0, dev->iobase + PCL711_DA1_HI); + + printk(KERN_INFO "\n"); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcl724.c b/drivers/staging/comedi/drivers/pcl724.c new file mode 100644 index 00000000..61b075db --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl724.c @@ -0,0 +1,238 @@ +/* + comedi/drivers/pcl724.c + + Michal Dobes + + hardware driver for Advantech cards: + card: PCL-724, PCL-722, PCL-731 + driver: pcl724, pcl722, pcl731 + and ADLink cards: + card: ACL-7122, ACL-7124, PET-48DIO + driver: acl7122, acl7124, pet48dio + + Options for PCL-724, PCL-731, ACL-7124 and PET-48DIO: + [0] - IO Base + + Options for PCL-722 and ACL-7122: + [0] - IO Base + [1] - IRQ (0=disable IRQ) IRQ isn't supported at this time! + [2] -number of DIO: + 0, 144: 144 DIO configuration + 1, 96: 96 DIO configuration +*/ +/* +Driver: pcl724 +Description: Advantech PCL-724, PCL-722, PCL-731 ADLink ACL-7122, ACL-7124, + PET-48DIO +Author: Michal Dobes +Devices: [Advantech] PCL-724 (pcl724), PCL-722 (pcl722), PCL-731 (pcl731), + [ADLink] ACL-7122 (acl7122), ACL-7124 (acl7124), PET-48DIO (pet48dio) +Status: untested + +This is driver for digital I/O boards PCL-722/724/731 with 144/24/48 DIO +and for digital I/O boards ACL-7122/7124/PET-48DIO with 144/24/48 DIO. +It need 8255.o for operations and only immediate mode is supported. +See the source for configuration details. +*/ +/* + * check_driver overrides: + * struct comedi_insn + */ + +#include "../comedidev.h" + +#include +#include + +#include "8255.h" + +#define PCL722_SIZE 32 +#define PCL722_96_SIZE 16 +#define PCL724_SIZE 4 +#define PCL731_SIZE 8 +#define PET48_SIZE 2 + +#define SIZE_8255 4 + +/* #define PCL724_IRQ 1 no IRQ support now */ + +static int pcl724_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcl724_detach(struct comedi_device *dev); + +struct pcl724_board { + + const char *name; /* board name */ + int dio; /* num of DIO */ + int numofports; /* num of 8255 subdevices */ + unsigned int IRQbits; /* allowed interrupts */ + unsigned int io_range; /* len of IO space */ + char can_have96; + char is_pet48; +}; + +static const struct pcl724_board boardtypes[] = { + {"pcl724", 24, 1, 0x00fc, PCL724_SIZE, 0, 0,}, + {"pcl722", 144, 6, 0x00fc, PCL722_SIZE, 1, 0,}, + {"pcl731", 48, 2, 0x9cfc, PCL731_SIZE, 0, 0,}, + {"acl7122", 144, 6, 0x9ee8, PCL722_SIZE, 1, 0,}, + {"acl7124", 24, 1, 0x00fc, PCL724_SIZE, 0, 0,}, + {"pet48dio", 48, 2, 0x9eb8, PET48_SIZE, 0, 1,}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl724_board)) +#define this_board ((const struct pcl724_board *)dev->board_ptr) + +static struct comedi_driver driver_pcl724 = { + .driver_name = "pcl724", + .module = THIS_MODULE, + .attach = pcl724_attach, + .detach = pcl724_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct pcl724_board), +}; + +static int __init driver_pcl724_init_module(void) +{ + return comedi_driver_register(&driver_pcl724); +} + +static void __exit driver_pcl724_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcl724); +} + +module_init(driver_pcl724_init_module); +module_exit(driver_pcl724_cleanup_module); + +static int subdev_8255_cb(int dir, int port, int data, unsigned long arg) +{ + unsigned long iobase = arg; + + if (dir) { + outb(data, iobase + port); + return 0; + } else { + return inb(iobase + port); + } +} + +static int subdev_8255mapped_cb(int dir, int port, int data, + unsigned long iobase) +{ + int movport = SIZE_8255 * (iobase >> 12); + + iobase &= 0x0fff; + + if (dir) { + outb(port + movport, iobase); + outb(data, iobase + 1); + return 0; + } else { + outb(port + movport, iobase); + return inb(iobase + 1); + } +} + +static int pcl724_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + unsigned long iobase; + unsigned int iorange; + int ret, i, n_subdevices; +#ifdef PCL724_IRQ + unsigned int irq; +#endif + + iobase = it->options[0]; + iorange = this_board->io_range; + if ((this_board->can_have96) && ((it->options[1] == 1) + || (it->options[1] == 96))) + iorange = PCL722_96_SIZE; /* PCL-724 in 96 DIO configuration */ + printk(KERN_INFO "comedi%d: pcl724: board=%s, 0x%03lx ", dev->minor, + this_board->name, iobase); + if (!request_region(iobase, iorange, "pcl724")) { + printk("I/O port conflict\n"); + return -EIO; + } + + dev->iobase = iobase; + + dev->board_name = this_board->name; + +#ifdef PCL724_IRQ + irq = 0; + if (this_board->IRQbits != 0) { /* board support IRQ */ + irq = it->options[1]; + if (irq) { /* we want to use IRQ */ + if (((1 << irq) & this_board->IRQbits) == 0) { + printk(KERN_WARNING + ", IRQ %u is out of allowed range, " + "DISABLING IT", irq); + irq = 0; /* Bad IRQ */ + } else { + if (request_irq + (irq, interrupt_pcl724, 0, "pcl724", dev)) { + printk(KERN_WARNING + ", unable to allocate IRQ %u, " + "DISABLING IT", irq); + irq = 0; /* Can't use IRQ */ + } else { + printk(", irq=%u", irq); + } + } + } + } + + dev->irq = irq; +#endif + + printk("\n"); + + n_subdevices = this_board->numofports; + if ((this_board->can_have96) && ((it->options[1] == 1) + || (it->options[1] == 96))) + n_subdevices = 4; /* PCL-724 in 96 DIO configuration */ + + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) + return ret; + + for (i = 0; i < dev->n_subdevices; i++) { + if (this_board->is_pet48) { + subdev_8255_init(dev, dev->subdevices + i, + subdev_8255mapped_cb, + (unsigned long)(dev->iobase + + i * 0x1000)); + } else + subdev_8255_init(dev, dev->subdevices + i, + subdev_8255_cb, + (unsigned long)(dev->iobase + + SIZE_8255 * i)); + } + + return 0; +} + +static int pcl724_detach(struct comedi_device *dev) +{ + int i; + + /* printk("comedi%d: pcl724: remove\n",dev->minor); */ + + for (i = 0; i < dev->n_subdevices; i++) + subdev_8255_cleanup(dev, dev->subdevices + i); + +#ifdef PCL724_IRQ + if (dev->irq) + free_irq(dev->irq, dev); +#endif + + release_region(dev->iobase, this_board->io_range); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcl725.c b/drivers/staging/comedi/drivers/pcl725.c new file mode 100644 index 00000000..24b223ca --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl725.c @@ -0,0 +1,127 @@ +/* + * comedi/drivers/pcl725.c + * Driver for PCL725 and clones + * David A. Schleef + */ +/* +Driver: pcl725 +Description: Advantech PCL-725 (& compatibles) +Author: ds +Status: unknown +Devices: [Advantech] PCL-725 (pcl725) +*/ + +#include "../comedidev.h" + +#include + +#define PCL725_SIZE 2 + +#define PCL725_DO 0 +#define PCL725_DI 1 + +static int pcl725_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcl725_detach(struct comedi_device *dev); +static struct comedi_driver driver_pcl725 = { + .driver_name = "pcl725", + .module = THIS_MODULE, + .attach = pcl725_attach, + .detach = pcl725_detach, +}; + +static int __init driver_pcl725_init_module(void) +{ + return comedi_driver_register(&driver_pcl725); +} + +static void __exit driver_pcl725_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcl725); +} + +module_init(driver_pcl725_init_module); +module_exit(driver_pcl725_cleanup_module); + +static int pcl725_do_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + outb(s->state, dev->iobase + PCL725_DO); + } + + data[1] = s->state; + + return 2; +} + +static int pcl725_di_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + PCL725_DI); + + return 2; +} + +static int pcl725_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: pcl725: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, PCL725_SIZE, "pcl725")) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->board_name = "pcl725"; + dev->iobase = iobase; + dev->irq = 0; + + if (alloc_subdevices(dev, 2) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* do */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 1; + s->n_chan = 8; + s->insn_bits = pcl725_do_insn; + s->range_table = &range_digital; + + s = dev->subdevices + 1; + /* di */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->maxdata = 1; + s->n_chan = 8; + s->insn_bits = pcl725_di_insn; + s->range_table = &range_digital; + + printk(KERN_INFO "\n"); + + return 0; +} + +static int pcl725_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pcl725: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, PCL725_SIZE); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcl726.c b/drivers/staging/comedi/drivers/pcl726.c new file mode 100644 index 00000000..897cd808 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl726.c @@ -0,0 +1,398 @@ +/* + comedi/drivers/pcl726.c + + hardware driver for Advantech cards: + card: PCL-726, PCL-727, PCL-728 + driver: pcl726, pcl727, pcl728 + and for ADLink cards: + card: ACL-6126, ACL-6128 + driver: acl6126, acl6128 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: pcl726 +Description: Advantech PCL-726 & compatibles +Author: ds +Status: untested +Devices: [Advantech] PCL-726 (pcl726), PCL-727 (pcl727), PCL-728 (pcl728), + [ADLink] ACL-6126 (acl6126), ACL-6128 (acl6128) + +Interrupts are not supported. + + Options for PCL-726: + [0] - IO Base + [2]...[7] - D/A output range for channel 1-6: + 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, + 4: 4-20mA, 5: unknown (external reference) + + Options for PCL-727: + [0] - IO Base + [2]...[13] - D/A output range for channel 1-12: + 0: 0-5V, 1: 0-10V, 2: +/-5V, + 3: 4-20mA + + Options for PCL-728 and ACL-6128: + [0] - IO Base + [2], [3] - D/A output range for channel 1 and 2: + 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, + 4: 4-20mA, 5: 0-20mA + + Options for ACL-6126: + [0] - IO Base + [1] - IRQ (0=disable, 3, 5, 6, 7, 9, 10, 11, 12, 15) (currently ignored) + [2]...[7] - D/A output range for channel 1-6: + 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, + 4: 4-20mA +*/ + +/* + Thanks to Circuit Specialists for having programming info (!) on + their web page. (http://www.cir.com/) +*/ + +#include "../comedidev.h" + +#include + +#undef ACL6126_IRQ /* no interrupt support (yet) */ + +#define PCL726_SIZE 16 +#define PCL727_SIZE 32 +#define PCL728_SIZE 8 + +#define PCL726_DAC0_HI 0 +#define PCL726_DAC0_LO 1 + +#define PCL726_DO_HI 12 +#define PCL726_DO_LO 13 +#define PCL726_DI_HI 14 +#define PCL726_DI_LO 15 + +#define PCL727_DO_HI 24 +#define PCL727_DO_LO 25 +#define PCL727_DI_HI 0 +#define PCL727_DI_LO 1 + +static const struct comedi_lrange range_4_20mA = { 1, {RANGE_mA(4, 20)} }; +static const struct comedi_lrange range_0_20mA = { 1, {RANGE_mA(0, 20)} }; + +static const struct comedi_lrange *const rangelist_726[] = { + &range_unipolar5, &range_unipolar10, + &range_bipolar5, &range_bipolar10, + &range_4_20mA, &range_unknown +}; + +static const struct comedi_lrange *const rangelist_727[] = { + &range_unipolar5, &range_unipolar10, + &range_bipolar5, + &range_4_20mA +}; + +static const struct comedi_lrange *const rangelist_728[] = { + &range_unipolar5, &range_unipolar10, + &range_bipolar5, &range_bipolar10, + &range_4_20mA, &range_0_20mA +}; + +static int pcl726_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcl726_detach(struct comedi_device *dev); + +struct pcl726_board { + + const char *name; /* driver name */ + int n_aochan; /* num of D/A chans */ + int num_of_ranges; /* num of ranges */ + unsigned int IRQbits; /* allowed interrupts */ + unsigned int io_range; /* len of IO space */ + char have_dio; /* 1=card have DI/DO ports */ + int di_hi; /* ports for DI/DO operations */ + int di_lo; + int do_hi; + int do_lo; + const struct comedi_lrange *const *range_type_list; + /* list of supported ranges */ +}; + +static const struct pcl726_board boardtypes[] = { + {"pcl726", 6, 6, 0x0000, PCL726_SIZE, 1, + PCL726_DI_HI, PCL726_DI_LO, PCL726_DO_HI, PCL726_DO_LO, + &rangelist_726[0],}, + {"pcl727", 12, 4, 0x0000, PCL727_SIZE, 1, + PCL727_DI_HI, PCL727_DI_LO, PCL727_DO_HI, PCL727_DO_LO, + &rangelist_727[0],}, + {"pcl728", 2, 6, 0x0000, PCL728_SIZE, 0, + 0, 0, 0, 0, + &rangelist_728[0],}, + {"acl6126", 6, 5, 0x96e8, PCL726_SIZE, 1, + PCL726_DI_HI, PCL726_DI_LO, PCL726_DO_HI, PCL726_DO_LO, + &rangelist_726[0],}, + {"acl6128", 2, 6, 0x0000, PCL728_SIZE, 0, + 0, 0, 0, 0, + &rangelist_728[0],}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl726_board)) +#define this_board ((const struct pcl726_board *)dev->board_ptr) + +static struct comedi_driver driver_pcl726 = { + .driver_name = "pcl726", + .module = THIS_MODULE, + .attach = pcl726_attach, + .detach = pcl726_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct pcl726_board), +}; + +static int __init driver_pcl726_init_module(void) +{ + return comedi_driver_register(&driver_pcl726); +} + +static void __exit driver_pcl726_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcl726); +} + +module_init(driver_pcl726_init_module); +module_exit(driver_pcl726_cleanup_module); + +struct pcl726_private { + + int bipolar[12]; + const struct comedi_lrange *rangelist[12]; + unsigned int ao_readback[12]; +}; + +#define devpriv ((struct pcl726_private *)dev->private) + +static int pcl726_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int hi, lo; + int n; + int chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) { + lo = data[n] & 0xff; + hi = (data[n] >> 8) & 0xf; + if (devpriv->bipolar[chan]) + hi ^= 0x8; + /* + * the programming info did not say which order + * to write bytes. switch the order of the next + * two lines if you get glitches. + */ + outb(hi, dev->iobase + PCL726_DAC0_HI + 2 * chan); + outb(lo, dev->iobase + PCL726_DAC0_LO + 2 * chan); + devpriv->ao_readback[chan] = data[n]; + } + + return n; +} + +static int pcl726_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + int n; + + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_readback[chan]; + return n; +} + +static int pcl726_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + this_board->di_lo) | + (inb(dev->iobase + this_board->di_hi) << 8); + + return 2; +} + +static int pcl726_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + } + if (data[1] & 0x00ff) + outb(s->state & 0xff, dev->iobase + this_board->do_lo); + if (data[1] & 0xff00) + outb((s->state >> 8), dev->iobase + this_board->do_hi); + + data[1] = s->state; + + return 2; +} + +static int pcl726_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + unsigned int iorange; + int ret, i; +#ifdef ACL6126_IRQ + unsigned int irq; +#endif + + iobase = it->options[0]; + iorange = this_board->io_range; + printk(KERN_WARNING "comedi%d: pcl726: board=%s, 0x%03lx ", dev->minor, + this_board->name, iobase); + if (!request_region(iobase, iorange, "pcl726")) { + printk(KERN_WARNING "I/O port conflict\n"); + return -EIO; + } + + dev->iobase = iobase; + + dev->board_name = this_board->name; + + ret = alloc_private(dev, sizeof(struct pcl726_private)); + if (ret < 0) + return -ENOMEM; + + for (i = 0; i < 12; i++) { + devpriv->bipolar[i] = 0; + devpriv->rangelist[i] = &range_unknown; + } + +#ifdef ACL6126_IRQ + irq = 0; + if (boardtypes[board].IRQbits != 0) { /* board support IRQ */ + irq = it->options[1]; + devpriv->first_chan = 2; + if (irq) { /* we want to use IRQ */ + if (((1 << irq) & boardtypes[board].IRQbits) == 0) { + printk(KERN_WARNING + ", IRQ %d is out of allowed range," + " DISABLING IT", irq); + irq = 0; /* Bad IRQ */ + } else { + if (request_irq(irq, interrupt_pcl818, 0, + "pcl726", dev)) { + printk(KERN_WARNING + ", unable to allocate IRQ %d," + " DISABLING IT", irq); + irq = 0; /* Can't use IRQ */ + } else { + printk(", irq=%d", irq); + } + } + } + } + + dev->irq = irq; +#endif + + printk("\n"); + + ret = alloc_subdevices(dev, 3); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + /* ao */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = this_board->n_aochan; + s->maxdata = 0xfff; + s->len_chanlist = 1; + s->insn_write = pcl726_ao_insn; + s->insn_read = pcl726_ao_insn_read; + s->range_table_list = devpriv->rangelist; + for (i = 0; i < this_board->n_aochan; i++) { + int j; + + j = it->options[2 + 1]; + if ((j < 0) || (j >= this_board->num_of_ranges)) { + printk + ("Invalid range for channel %d! Must be 0<=%d<%d\n", + i, j, this_board->num_of_ranges - 1); + j = 0; + } + devpriv->rangelist[i] = this_board->range_type_list[j]; + if (devpriv->rangelist[i]->range[0].min == + -devpriv->rangelist[i]->range[0].max) + devpriv->bipolar[i] = 1; /* bipolar range */ + } + + s = dev->subdevices + 1; + /* di */ + if (!this_board->have_dio) { + s->type = COMEDI_SUBD_UNUSED; + } else { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 16; + s->maxdata = 1; + s->len_chanlist = 1; + s->insn_bits = pcl726_di_insn_bits; + s->range_table = &range_digital; + } + + s = dev->subdevices + 2; + /* do */ + if (!this_board->have_dio) { + s->type = COMEDI_SUBD_UNUSED; + } else { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = 16; + s->maxdata = 1; + s->len_chanlist = 1; + s->insn_bits = pcl726_do_insn_bits; + s->range_table = &range_digital; + } + + return 0; +} + +static int pcl726_detach(struct comedi_device *dev) +{ +/* printk("comedi%d: pcl726: remove\n",dev->minor); */ + +#ifdef ACL6126_IRQ + if (dev->irq) + free_irq(dev->irq, dev); +#endif + + if (dev->iobase) + release_region(dev->iobase, this_board->io_range); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcl730.c b/drivers/staging/comedi/drivers/pcl730.c new file mode 100644 index 00000000..c9682d61 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl730.c @@ -0,0 +1,183 @@ +/* + * comedi/drivers/pcl730.c + * Driver for Advantech PCL-730 and clones + * José Luis Sánchez + */ +/* +Driver: pcl730 +Description: Advantech PCL-730 (& compatibles) +Author: José Luis Sánchez (jsanchezv@teleline.es) +Status: untested +Devices: [Advantech] PCL-730 (pcl730), [ICP] ISO-730 (iso730), + [Adlink] ACL-7130 (acl7130) + +Interrupts are not supported. +The ACL-7130 card have an 8254 timer/counter not supported by this driver. +*/ + +#include "../comedidev.h" + +#include + +#define PCL730_SIZE 4 +#define ACL7130_SIZE 8 +#define PCL730_IDIO_LO 0 /* Isolated Digital I/O low byte (ID0-ID7) */ +#define PCL730_IDIO_HI 1 /* Isolated Digital I/O high byte (ID8-ID15) */ +#define PCL730_DIO_LO 2 /* TTL Digital I/O low byte (D0-D7) */ +#define PCL730_DIO_HI 3 /* TTL Digital I/O high byte (D8-D15) */ + +static int pcl730_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcl730_detach(struct comedi_device *dev); + +struct pcl730_board { + + const char *name; /* board name */ + unsigned int io_range; /* len of I/O space */ +}; + +static const struct pcl730_board boardtypes[] = { + {"pcl730", PCL730_SIZE,}, + {"iso730", PCL730_SIZE,}, + {"acl7130", ACL7130_SIZE,}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl730_board)) +#define this_board ((const struct pcl730_board *)dev->board_ptr) + +static struct comedi_driver driver_pcl730 = { + .driver_name = "pcl730", + .module = THIS_MODULE, + .attach = pcl730_attach, + .detach = pcl730_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct pcl730_board), +}; + +static int __init driver_pcl730_init_module(void) +{ + return comedi_driver_register(&driver_pcl730); +} + +static void __exit driver_pcl730_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcl730); +} + +module_init(driver_pcl730_init_module); +module_exit(driver_pcl730_cleanup_module); + +static int pcl730_do_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + } + if (data[0] & 0x00ff) + outb(s->state & 0xff, + dev->iobase + ((unsigned long)s->private)); + if (data[0] & 0xff00) + outb((s->state >> 8), + dev->iobase + ((unsigned long)s->private) + 1); + + data[1] = s->state; + + return 2; +} + +static int pcl730_di_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + ((unsigned long)s->private)) | + (inb(dev->iobase + ((unsigned long)s->private) + 1) << 8); + + return 2; +} + +static int pcl730_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + unsigned int iorange; + + iobase = it->options[0]; + iorange = this_board->io_range; + printk(KERN_INFO "comedi%d: pcl730: board=%s 0x%04lx ", dev->minor, + this_board->name, iobase); + if (!request_region(iobase, iorange, "pcl730")) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->board_name = this_board->name; + dev->iobase = iobase; + dev->irq = 0; + + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* Isolated do */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 1; + s->n_chan = 16; + s->insn_bits = pcl730_do_insn; + s->range_table = &range_digital; + s->private = (void *)PCL730_IDIO_LO; + + s = dev->subdevices + 1; + /* Isolated di */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->maxdata = 1; + s->n_chan = 16; + s->insn_bits = pcl730_di_insn; + s->range_table = &range_digital; + s->private = (void *)PCL730_IDIO_LO; + + s = dev->subdevices + 2; + /* TTL do */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 1; + s->n_chan = 16; + s->insn_bits = pcl730_do_insn; + s->range_table = &range_digital; + s->private = (void *)PCL730_DIO_LO; + + s = dev->subdevices + 3; + /* TTL di */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->maxdata = 1; + s->n_chan = 16; + s->insn_bits = pcl730_di_insn; + s->range_table = &range_digital; + s->private = (void *)PCL730_DIO_LO; + + printk(KERN_INFO "\n"); + + return 0; +} + +static int pcl730_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pcl730: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, this_board->io_range); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcl812.c b/drivers/staging/comedi/drivers/pcl812.c new file mode 100644 index 00000000..6fc74645 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl812.c @@ -0,0 +1,1720 @@ +/* + * comedi/drivers/pcl812.c + * + * Author: Michal Dobes + * + * hardware driver for Advantech cards + * card: PCL-812, PCL-812PG, PCL-813, PCL-813B + * driver: pcl812, pcl812pg, pcl813, pcl813b + * and for ADlink cards + * card: ACL-8112DG, ACL-8112HG, ACL-8112PG, ACL-8113, ACL-8216 + * driver: acl8112dg, acl8112hg, acl8112pg, acl8113, acl8216 + * and for ICP DAS cards + * card: ISO-813, A-821PGH, A-821PGL, A-821PGL-NDA, A-822PGH, A-822PGL, + * driver: iso813, a821pgh, a-821pgl, a-821pglnda, a822pgh, a822pgl, + * card: A-823PGH, A-823PGL, A-826PG + * driver: a823pgh, a823pgl, a826pg + */ + +/* + * Driver: pcl812 + * Description: Advantech PCL-812/PG, PCL-813/B, + * ADLink ACL-8112DG/HG/PG, ACL-8113, ACL-8216, + * ICP DAS A-821PGH/PGL/PGL-NDA, A-822PGH/PGL, A-823PGH/PGL, A-826PG, + * ICP DAS ISO-813 + * Author: Michal Dobes + * Devices: [Advantech] PCL-812 (pcl812), PCL-812PG (pcl812pg), + * PCL-813 (pcl813), PCL-813B (pcl813b), [ADLink] ACL-8112DG (acl8112dg), + * ACL-8112HG (acl8112hg), ACL-8113 (acl-8113), ACL-8216 (acl8216), + * [ICP] ISO-813 (iso813), A-821PGH (a821pgh), A-821PGL (a821pgl), + * A-821PGL-NDA (a821pclnda), A-822PGH (a822pgh), A-822PGL (a822pgl), + * A-823PGH (a823pgh), A-823PGL (a823pgl), A-826PG (a826pg) + * Updated: Mon, 06 Aug 2007 12:03:15 +0100 + * Status: works (I hope. My board fire up under my hands + * and I cann't test all features.) + * + * This driver supports insn and cmd interfaces. Some boards support only insn + * because their hardware don't allow more (PCL-813/B, ACL-8113, ISO-813). + * Data transfer over DMA is supported only when you measure only one + * channel, this is too hardware limitation of these boards. + * + * Options for PCL-812: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0=trigger source is internal 8253 with 2MHz clock + * 1=trigger source is external + * [4] - 0=A/D input range is +/-10V + * 1=A/D input range is +/-5V + * 2=A/D input range is +/-2.5V + * 3=A/D input range is +/-1.25V + * 4=A/D input range is +/-0.625V + * 5=A/D input range is +/-0.3125V + * [5] - 0=D/A outputs 0-5V (internal reference -5V) + * 1=D/A outputs 0-10V (internal reference -10V) + * 2=D/A outputs unknown (external reference) + * + * Options for PCL-812PG, ACL-8112PG: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0=trigger source is internal 8253 with 2MHz clock + * 1=trigger source is external + * [4] - 0=A/D have max +/-5V input + * 1=A/D have max +/-10V input + * [5] - 0=D/A outputs 0-5V (internal reference -5V) + * 1=D/A outputs 0-10V (internal reference -10V) + * 2=D/A outputs unknown (external reference) + * + * Options for ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH, ACL-8216, A-826PG: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7; 10, 11, 12, 14, 15) + * [2] - DMA (0=disable, 1, 3) + * [3] - 0=trigger source is internal 8253 with 2MHz clock + * 1=trigger source is external + * [4] - 0=A/D channels are S.E. + * 1=A/D channels are DIFF + * [5] - 0=D/A outputs 0-5V (internal reference -5V) + * 1=D/A outputs 0-10V (internal reference -10V) + * 2=D/A outputs unknown (external reference) + * + * Options for A-821PGL/PGH: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + * [2] - 0=A/D channels are S.E. + * 1=A/D channels are DIFF + * [3] - 0=D/A output 0-5V (internal reference -5V) + * 1=D/A output 0-10V (internal reference -10V) + * + * Options for A-821PGL-NDA: + * [0] - IO Base + * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + * [2] - 0=A/D channels are S.E. + * 1=A/D channels are DIFF + * + * Options for PCL-813: + * [0] - IO Base + * + * Options for PCL-813B: + * [0] - IO Base + * [1] - 0= bipolar inputs + * 1= unipolar inputs + * + * Options for ACL-8113, ISO-813: + * [0] - IO Base + * [1] - 0= 10V bipolar inputs + * 1= 10V unipolar inputs + * 2= 20V bipolar inputs + * 3= 20V unipolar inputs + */ + +#include +#include +#include "../comedidev.h" + +#include +#include +#include +#include + +#include "8253.h" + +/* if this is defined then a lot of messages is printed */ +#undef PCL812_EXTDEBUG + +/* hardware types of the cards */ +#define boardPCL812PG 0 /* and ACL-8112PG */ +#define boardPCL813B 1 +#define boardPCL812 2 +#define boardPCL813 3 +#define boardISO813 5 +#define boardACL8113 6 +#define boardACL8112 7 /* ACL-8112DG/HG, A-822PGL/PGH, A-823PGL/PGH */ +#define boardACL8216 8 /* and ICP DAS A-826PG */ +#define boardA821 9 /* PGH, PGL, PGL/NDA versions */ + +#define PCLx1x_IORANGE 16 + +#define PCL812_CTR0 0 +#define PCL812_CTR1 1 +#define PCL812_CTR2 2 +#define PCL812_CTRCTL 3 +#define PCL812_AD_LO 4 +#define PCL812_DA1_LO 4 +#define PCL812_AD_HI 5 +#define PCL812_DA1_HI 5 +#define PCL812_DA2_LO 6 +#define PCL812_DI_LO 6 +#define PCL812_DA2_HI 7 +#define PCL812_DI_HI 7 +#define PCL812_CLRINT 8 +#define PCL812_GAIN 9 +#define PCL812_MUX 10 +#define PCL812_MODE 11 +#define PCL812_CNTENABLE 10 +#define PCL812_SOFTTRIG 12 +#define PCL812_DO_LO 13 +#define PCL812_DO_HI 14 + +#define PCL812_DRDY 0x10 /* =0 data ready */ + +#define ACL8216_STATUS 8 /* 5. bit signalize data ready */ + +#define ACL8216_DRDY 0x20 /* =0 data ready */ + +#define MAX_CHANLIST_LEN 256 /* length of scan list */ + +static const struct comedi_lrange range_pcl812pg_ai = { 5, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + BIP_RANGE(0.3125), + } +}; + +static const struct comedi_lrange range_pcl812pg2_ai = { 5, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + } +}; + +static const struct comedi_lrange range812_bipolar1_25 = { 1, { + BIP_RANGE(1.25), + } +}; + +static const struct comedi_lrange range812_bipolar0_625 = { 1, { + BIP_RANGE + (0.625), + } +}; + +static const struct comedi_lrange range812_bipolar0_3125 = { 1, { + BIP_RANGE + (0.3125), + } +}; + +static const struct comedi_lrange range_pcl813b_ai = { 4, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + } +}; + +static const struct comedi_lrange range_pcl813b2_ai = { 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; + +static const struct comedi_lrange range_iso813_1_ai = { 5, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + BIP_RANGE(0.3125), + } +}; + +static const struct comedi_lrange range_iso813_1_2_ai = { 5, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + UNI_RANGE(0.625), + } +}; + +static const struct comedi_lrange range_iso813_2_ai = { 4, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + } +}; + +static const struct comedi_lrange range_iso813_2_2_ai = { 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; + +static const struct comedi_lrange range_acl8113_1_ai = { 4, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + } +}; + +static const struct comedi_lrange range_acl8113_1_2_ai = { 4, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; + +static const struct comedi_lrange range_acl8113_2_ai = { 3, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + } +}; + +static const struct comedi_lrange range_acl8113_2_2_ai = { 3, { + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + } +}; + +static const struct comedi_lrange range_acl8112dg_ai = { 9, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + BIP_RANGE(10), + } +}; + +static const struct comedi_lrange range_acl8112hg_ai = { 12, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01), + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01), + } +}; + +static const struct comedi_lrange range_a821pgh_ai = { 4, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + } +}; + +static int pcl812_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcl812_detach(struct comedi_device *dev); + +struct pcl812_board { + + const char *name; /* board name */ + int board_type; /* type of this board */ + int n_aichan; /* num of AI chans in S.E. */ + int n_aichan_diff; /* DIFF num of chans */ + int n_aochan; /* num of DA chans */ + int n_dichan; /* DI and DO chans */ + int n_dochan; + int ai_maxdata; /* AI resolution */ + unsigned int ai_ns_min; /* max sample speed of card v ns */ + unsigned int i8254_osc_base; /* clock base */ + const struct comedi_lrange *rangelist_ai; /* rangelist for A/D */ + const struct comedi_lrange *rangelist_ao; /* rangelist for D/A */ + unsigned int IRQbits; /* allowed IRQ */ + unsigned char DMAbits; /* allowed DMA chans */ + unsigned char io_range; /* iorange for this board */ + unsigned char haveMPC508; /* 1=board use MPC508A multiplexor */ +}; + +static const struct pcl812_board boardtypes[] = { + {"pcl812", boardPCL812, 16, 0, 2, 16, 16, 0x0fff, + 33000, 500, &range_bipolar10, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 0}, + {"pcl812pg", boardPCL812PG, 16, 0, 2, 16, 16, 0x0fff, + 33000, 500, &range_pcl812pg_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 0}, + {"acl8112pg", boardPCL812PG, 16, 0, 2, 16, 16, 0x0fff, + 10000, 500, &range_pcl812pg_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 0}, + {"acl8112dg", boardACL8112, 16, 8, 2, 16, 16, 0x0fff, + 10000, 500, &range_acl8112dg_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 1}, + {"acl8112hg", boardACL8112, 16, 8, 2, 16, 16, 0x0fff, + 10000, 500, &range_acl8112hg_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 1}, + {"a821pgl", boardA821, 16, 8, 1, 16, 16, 0x0fff, + 10000, 500, &range_pcl813b_ai, &range_unipolar5, + 0x000c, 0x00, PCLx1x_IORANGE, 0}, + {"a821pglnda", boardA821, 16, 8, 0, 0, 0, 0x0fff, + 10000, 500, &range_pcl813b_ai, NULL, + 0x000c, 0x00, PCLx1x_IORANGE, 0}, + {"a821pgh", boardA821, 16, 8, 1, 16, 16, 0x0fff, + 10000, 500, &range_a821pgh_ai, &range_unipolar5, + 0x000c, 0x00, PCLx1x_IORANGE, 0}, + {"a822pgl", boardACL8112, 16, 8, 2, 16, 16, 0x0fff, + 10000, 500, &range_acl8112dg_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 0}, + {"a822pgh", boardACL8112, 16, 8, 2, 16, 16, 0x0fff, + 10000, 500, &range_acl8112hg_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 0}, + {"a823pgl", boardACL8112, 16, 8, 2, 16, 16, 0x0fff, + 8000, 500, &range_acl8112dg_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 0}, + {"a823pgh", boardACL8112, 16, 8, 2, 16, 16, 0x0fff, + 8000, 500, &range_acl8112hg_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 0}, + {"pcl813", boardPCL813, 32, 0, 0, 0, 0, 0x0fff, + 0, 0, &range_pcl813b_ai, NULL, + 0x0000, 0x00, PCLx1x_IORANGE, 0}, + {"pcl813b", boardPCL813B, 32, 0, 0, 0, 0, 0x0fff, + 0, 0, &range_pcl813b_ai, NULL, + 0x0000, 0x00, PCLx1x_IORANGE, 0}, + {"acl8113", boardACL8113, 32, 0, 0, 0, 0, 0x0fff, + 0, 0, &range_acl8113_1_ai, NULL, + 0x0000, 0x00, PCLx1x_IORANGE, 0}, + {"iso813", boardISO813, 32, 0, 0, 0, 0, 0x0fff, + 0, 0, &range_iso813_1_ai, NULL, + 0x0000, 0x00, PCLx1x_IORANGE, 0}, + {"acl8216", boardACL8216, 16, 8, 2, 16, 16, 0xffff, + 10000, 500, &range_pcl813b2_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 1}, + {"a826pg", boardACL8216, 16, 8, 2, 16, 16, 0xffff, + 10000, 500, &range_pcl813b2_ai, &range_unipolar5, + 0xdcfc, 0x0a, PCLx1x_IORANGE, 0}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl812_board)) +#define this_board ((const struct pcl812_board *)dev->board_ptr) + +static struct comedi_driver driver_pcl812 = { + .driver_name = "pcl812", + .module = THIS_MODULE, + .attach = pcl812_attach, + .detach = pcl812_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct pcl812_board), +}; + +static int __init driver_pcl812_init_module(void) +{ + return comedi_driver_register(&driver_pcl812); +} + +static void __exit driver_pcl812_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcl812); +} + +module_init(driver_pcl812_init_module); +module_exit(driver_pcl812_cleanup_module); + +struct pcl812_private { + + unsigned char valid; /* =1 device is OK */ + unsigned char dma; /* >0 use dma ( usedDMA channel) */ + unsigned char use_diff; /* =1 diff inputs */ + unsigned char use_MPC; /* 1=board uses MPC508A multiplexor */ + unsigned char use_ext_trg; /* 1=board uses external trigger */ + unsigned char range_correction; /* =1 we must add 1 to range number */ + unsigned char old_chan_reg; /* lastly used chan/gain pair */ + unsigned char old_gain_reg; + unsigned char mode_reg_int; /* there is stored INT number for some card */ + unsigned char ai_neverending; /* =1 we do unlimited AI */ + unsigned char ai_eos; /* 1=EOS wake up */ + unsigned char ai_dma; /* =1 we use DMA */ + unsigned int ai_poll_ptr; /* how many sampes transfer poll */ + unsigned int ai_scans; /* len of scanlist */ + unsigned int ai_act_scan; /* how many scans we finished */ + unsigned int ai_chanlist[MAX_CHANLIST_LEN]; /* our copy of channel/range list */ + unsigned int ai_n_chan; /* how many channels is measured */ + unsigned int ai_flags; /* flaglist */ + unsigned int ai_data_len; /* len of data buffer */ + short *ai_data; /* data buffer */ + unsigned int ai_is16b; /* =1 we have 16 bit card */ + unsigned long dmabuf[2]; /* PTR to DMA buf */ + unsigned int dmapages[2]; /* how many pages we have allocated */ + unsigned int hwdmaptr[2]; /* HW PTR to DMA buf */ + unsigned int hwdmasize[2]; /* DMA buf size in bytes */ + unsigned int dmabytestomove[2]; /* how many bytes DMA transfer */ + int next_dma_buf; /* which buffer is next to use */ + unsigned int dma_runs_to_end; /* how many times we must switch DMA buffers */ + unsigned int last_dma_run; /* how many bytes to transfer on last DMA buffer */ + unsigned int max_812_ai_mode0_rangewait; /* setling time for gain */ + unsigned int ao_readback[2]; /* data for AO readback */ +}; + +#define devpriv ((struct pcl812_private *)dev->private) + +/* +============================================================================== +*/ +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2); +static void setup_range_channel(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int rangechan, char wait); +static int pcl812_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +/* +============================================================================== +*/ +static int pcl812_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int timeout, hi; + + /* select software trigger */ + outb(devpriv->mode_reg_int | 1, dev->iobase + PCL812_MODE); + /* select channel and renge */ + setup_range_channel(dev, s, insn->chanspec, 1); + for (n = 0; n < insn->n; n++) { + /* start conversion */ + outb(255, dev->iobase + PCL812_SOFTTRIG); + udelay(5); + timeout = 50; /* wait max 50us, it must finish under 33us */ + while (timeout--) { + hi = inb(dev->iobase + PCL812_AD_HI); + if (!(hi & PCL812_DRDY)) + goto conv_finish; + udelay(1); + } + printk + ("comedi%d: pcl812: (%s at 0x%lx) A/D insn read timeout\n", + dev->minor, dev->board_name, dev->iobase); + outb(devpriv->mode_reg_int | 0, dev->iobase + PCL812_MODE); + return -ETIME; + +conv_finish: + data[n] = ((hi & 0xf) << 8) | inb(dev->iobase + PCL812_AD_LO); + } + outb(devpriv->mode_reg_int | 0, dev->iobase + PCL812_MODE); + return n; +} + +/* +============================================================================== +*/ +static int acl8216_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int timeout; + + /* select software trigger */ + outb(1, dev->iobase + PCL812_MODE); + /* select channel and renge */ + setup_range_channel(dev, s, insn->chanspec, 1); + for (n = 0; n < insn->n; n++) { + /* start conversion */ + outb(255, dev->iobase + PCL812_SOFTTRIG); + udelay(5); + timeout = 50; /* wait max 50us, it must finish under 33us */ + while (timeout--) { + if (!(inb(dev->iobase + ACL8216_STATUS) & ACL8216_DRDY)) + goto conv_finish; + udelay(1); + } + printk + ("comedi%d: pcl812: (%s at 0x%lx) A/D insn read timeout\n", + dev->minor, dev->board_name, dev->iobase); + outb(0, dev->iobase + PCL812_MODE); + return -ETIME; + +conv_finish: + data[n] = + (inb(dev->iobase + + PCL812_AD_HI) << 8) | inb(dev->iobase + PCL812_AD_LO); + } + outb(0, dev->iobase + PCL812_MODE); + return n; +} + +/* +============================================================================== +*/ +static int pcl812_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) { + outb((data[i] & 0xff), + dev->iobase + (chan ? PCL812_DA2_LO : PCL812_DA1_LO)); + outb((data[i] >> 8) & 0x0f, + dev->iobase + (chan ? PCL812_DA2_HI : PCL812_DA1_HI)); + devpriv->ao_readback[chan] = data[i]; + } + + return i; +} + +/* +============================================================================== +*/ +static int pcl812_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +/* +============================================================================== +*/ +static int pcl812_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + PCL812_DI_LO); + data[1] |= inb(dev->iobase + PCL812_DI_HI) << 8; + + return 2; +} + +/* +============================================================================== +*/ +static int pcl812_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + outb(s->state & 0xff, dev->iobase + PCL812_DO_LO); + outb((s->state >> 8), dev->iobase + PCL812_DO_HI); + } + data[1] = s->state; + + return 2; +} + +#ifdef PCL812_EXTDEBUG +/* +============================================================================== +*/ +static void pcl812_cmdtest_out(int e, struct comedi_cmd *cmd) +{ + printk(KERN_INFO "pcl812 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e, + cmd->start_src, cmd->scan_begin_src, cmd->convert_src); + printk(KERN_INFO "pcl812 e=%d startarg=%d scanarg=%d convarg=%d\n", e, + cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg); + printk(KERN_INFO "pcl812 e=%d stopsrc=%x scanend=%x\n", e, + cmd->stop_src, cmd->scan_end_src); + printk(KERN_INFO "pcl812 e=%d stoparg=%d scanendarg=%d " + "chanlistlen=%d\n", e, cmd->stop_arg, cmd->scan_end_arg, + cmd->chanlist_len); +} +#endif + +/* +============================================================================== +*/ +static int pcl812_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp, divisor1, divisor2; + +#ifdef PCL812_EXTDEBUG + printk("pcl812 EDBG: BGN: pcl812_ai_cmdtest(...)\n"); + pcl812_cmdtest_out(-1, cmd); +#endif + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + if (devpriv->use_ext_trg) + cmd->convert_src &= TRIG_EXT; + else + cmd->convert_src &= TRIG_TIMER; + + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) { +#ifdef PCL812_EXTDEBUG + pcl812_cmdtest_out(1, cmd); + printk + ("pcl812 EDBG: BGN: pcl812_ai_cmdtest(...) err=%d ret=1\n", + err); +#endif + return 1; + } + + /* + * step 2: make sure trigger sources are + * unique and mutually compatible + */ + + if (cmd->start_src != TRIG_NOW) { + cmd->start_src = TRIG_NOW; + err++; + } + + if (cmd->scan_begin_src != TRIG_FOLLOW) { + cmd->scan_begin_src = TRIG_FOLLOW; + err++; + } + + if (devpriv->use_ext_trg) { + if (cmd->convert_src != TRIG_EXT) { + cmd->convert_src = TRIG_EXT; + err++; + } + } else { + if (cmd->convert_src != TRIG_TIMER) { + cmd->convert_src = TRIG_TIMER; + err++; + } + } + + if (cmd->scan_end_src != TRIG_COUNT) { + cmd->scan_end_src = TRIG_COUNT; + err++; + } + + if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) + err++; + + if (err) { +#ifdef PCL812_EXTDEBUG + pcl812_cmdtest_out(2, cmd); + printk + ("pcl812 EDBG: BGN: pcl812_ai_cmdtest(...) err=%d ret=2\n", + err); +#endif + return 2; + } + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < this_board->ai_ns_min) { + cmd->convert_arg = this_board->ai_ns_min; + err++; + } + } else { /* TRIG_EXT */ + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + } + + if (!cmd->chanlist_len) { + cmd->chanlist_len = 1; + err++; + } + if (cmd->chanlist_len > MAX_CHANLIST_LEN) { + cmd->chanlist_len = this_board->n_aichan; + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) { +#ifdef PCL812_EXTDEBUG + pcl812_cmdtest_out(3, cmd); + printk + ("pcl812 EDBG: BGN: pcl812_ai_cmdtest(...) err=%d ret=3\n", + err); +#endif + return 3; + } + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + i8253_cascade_ns_to_timer(this_board->i8254_osc_base, &divisor1, + &divisor2, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (cmd->convert_arg < this_board->ai_ns_min) + cmd->convert_arg = this_board->ai_ns_min; + if (tmp != cmd->convert_arg) + err++; + } + + if (err) { +#ifdef PCL812_EXTDEBUG + printk + ("pcl812 EDBG: BGN: pcl812_ai_cmdtest(...) err=%d ret=4\n", + err); +#endif + return 4; + } + + return 0; +} + +/* +============================================================================== +*/ +static int pcl812_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned int divisor1 = 0, divisor2 = 0, i, dma_flags, bytes; + struct comedi_cmd *cmd = &s->async->cmd; + +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: BGN: pcl812_ai_cmd(...)\n"); +#endif + + if (cmd->start_src != TRIG_NOW) + return -EINVAL; + if (cmd->scan_begin_src != TRIG_FOLLOW) + return -EINVAL; + if (devpriv->use_ext_trg) { + if (cmd->convert_src != TRIG_EXT) + return -EINVAL; + } else { + if (cmd->convert_src != TRIG_TIMER) + return -EINVAL; + } + if (cmd->scan_end_src != TRIG_COUNT) + return -EINVAL; + if (cmd->scan_end_arg != cmd->chanlist_len) + return -EINVAL; + if (cmd->chanlist_len > MAX_CHANLIST_LEN) + return -EINVAL; + + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < this_board->ai_ns_min) + cmd->convert_arg = this_board->ai_ns_min; + i8253_cascade_ns_to_timer(this_board->i8254_osc_base, + &divisor1, &divisor2, + &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + } + + start_pacer(dev, -1, 0, 0); /* stop pacer */ + + devpriv->ai_n_chan = cmd->chanlist_len; + memcpy(devpriv->ai_chanlist, cmd->chanlist, + sizeof(unsigned int) * cmd->scan_end_arg); + /* select first channel and range */ + setup_range_channel(dev, s, devpriv->ai_chanlist[0], 1); + + if (devpriv->dma) { /* check if we can use DMA transfer */ + devpriv->ai_dma = 1; + for (i = 1; i < devpriv->ai_n_chan; i++) + if (devpriv->ai_chanlist[0] != devpriv->ai_chanlist[i]) { + /* we cann't use DMA :-( */ + devpriv->ai_dma = 0; + break; + } + } else + devpriv->ai_dma = 0; + + devpriv->ai_flags = cmd->flags; + devpriv->ai_data_len = s->async->prealloc_bufsz; + devpriv->ai_data = s->async->prealloc_buf; + if (cmd->stop_src == TRIG_COUNT) { + devpriv->ai_scans = cmd->stop_arg; + devpriv->ai_neverending = 0; + } else { + devpriv->ai_scans = 0; + devpriv->ai_neverending = 1; + } + + devpriv->ai_act_scan = 0; + devpriv->ai_poll_ptr = 0; + s->async->cur_chan = 0; + + /* don't we want wake up every scan? */ + if ((devpriv->ai_flags & TRIG_WAKE_EOS)) { + devpriv->ai_eos = 1; + + /* DMA is useless for this situation */ + if (devpriv->ai_n_chan == 1) + devpriv->ai_dma = 0; + } + + if (devpriv->ai_dma) { + /* we use EOS, so adapt DMA buffer to one scan */ + if (devpriv->ai_eos) { + devpriv->dmabytestomove[0] = + devpriv->ai_n_chan * sizeof(short); + devpriv->dmabytestomove[1] = + devpriv->ai_n_chan * sizeof(short); + devpriv->dma_runs_to_end = 1; + } else { + devpriv->dmabytestomove[0] = devpriv->hwdmasize[0]; + devpriv->dmabytestomove[1] = devpriv->hwdmasize[1]; + if (devpriv->ai_data_len < devpriv->hwdmasize[0]) + devpriv->dmabytestomove[0] = + devpriv->ai_data_len; + if (devpriv->ai_data_len < devpriv->hwdmasize[1]) + devpriv->dmabytestomove[1] = + devpriv->ai_data_len; + if (devpriv->ai_neverending) { + devpriv->dma_runs_to_end = 1; + } else { + /* how many samples we must transfer? */ + bytes = devpriv->ai_n_chan * + devpriv->ai_scans * sizeof(short); + + /* how many DMA pages we must fill */ + devpriv->dma_runs_to_end = + bytes / devpriv->dmabytestomove[0]; + + /* on last dma transfer must be moved */ + devpriv->last_dma_run = + bytes % devpriv->dmabytestomove[0]; + if (devpriv->dma_runs_to_end == 0) + devpriv->dmabytestomove[0] = + devpriv->last_dma_run; + devpriv->dma_runs_to_end--; + } + } + if (devpriv->dmabytestomove[0] > devpriv->hwdmasize[0]) { + devpriv->dmabytestomove[0] = devpriv->hwdmasize[0]; + devpriv->ai_eos = 0; + } + if (devpriv->dmabytestomove[1] > devpriv->hwdmasize[1]) { + devpriv->dmabytestomove[1] = devpriv->hwdmasize[1]; + devpriv->ai_eos = 0; + } + devpriv->next_dma_buf = 0; + set_dma_mode(devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]); + set_dma_count(devpriv->dma, devpriv->dmabytestomove[0]); + release_dma_lock(dma_flags); + enable_dma(devpriv->dma); +#ifdef PCL812_EXTDEBUG + printk + ("pcl812 EDBG: DMA %d PTR 0x%0x/0x%0x LEN %u/%u EOS %d\n", + devpriv->dma, devpriv->hwdmaptr[0], + devpriv->hwdmaptr[1], devpriv->dmabytestomove[0], + devpriv->dmabytestomove[1], devpriv->ai_eos); +#endif + } + + switch (cmd->convert_src) { + case TRIG_TIMER: + start_pacer(dev, 1, divisor1, divisor2); + break; + } + + if (devpriv->ai_dma) /* let's go! */ + outb(devpriv->mode_reg_int | 2, dev->iobase + PCL812_MODE); + else /* let's go! */ + outb(devpriv->mode_reg_int | 6, dev->iobase + PCL812_MODE); + +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: END: pcl812_ai_cmd(...)\n"); +#endif + + return 0; +} + +/* +============================================================================== +*/ +static irqreturn_t interrupt_pcl812_ai_int(int irq, void *d) +{ + char err = 1; + unsigned int mask, timeout; + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + unsigned int next_chan; + + s->async->events = 0; + + timeout = 50; /* wait max 50us, it must finish under 33us */ + if (devpriv->ai_is16b) { + mask = 0xffff; + while (timeout--) { + if (!(inb(dev->iobase + ACL8216_STATUS) & ACL8216_DRDY)) { + err = 0; + break; + } + udelay(1); + } + } else { + mask = 0x0fff; + while (timeout--) { + if (!(inb(dev->iobase + PCL812_AD_HI) & PCL812_DRDY)) { + err = 0; + break; + } + udelay(1); + } + } + + if (err) { + printk + ("comedi%d: pcl812: (%s at 0x%lx) " + "A/D cmd IRQ without DRDY!\n", + dev->minor, dev->board_name, dev->iobase); + pcl812_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + } + + comedi_buf_put(s->async, + ((inb(dev->iobase + PCL812_AD_HI) << 8) | + inb(dev->iobase + PCL812_AD_LO)) & mask); + + /* Set up next channel. Added by abbotti 2010-01-20, but untested. */ + next_chan = s->async->cur_chan + 1; + if (next_chan >= devpriv->ai_n_chan) + next_chan = 0; + if (devpriv->ai_chanlist[s->async->cur_chan] != + devpriv->ai_chanlist[next_chan]) + setup_range_channel(dev, s, devpriv->ai_chanlist[next_chan], 0); + + outb(0, dev->iobase + PCL812_CLRINT); /* clear INT request */ + + s->async->cur_chan = next_chan; + if (next_chan == 0) { /* one scan done */ + devpriv->ai_act_scan++; + if (!(devpriv->ai_neverending)) + /* all data sampled */ + if (devpriv->ai_act_scan >= devpriv->ai_scans) { + pcl812_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + } + } + + comedi_event(dev, s); + return IRQ_HANDLED; +} + +/* +============================================================================== +*/ +static void transfer_from_dma_buf(struct comedi_device *dev, + struct comedi_subdevice *s, short *ptr, + unsigned int bufptr, unsigned int len) +{ + unsigned int i; + + s->async->events = 0; + for (i = len; i; i--) { + /* get one sample */ + comedi_buf_put(s->async, ptr[bufptr++]); + + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_chan) { + s->async->cur_chan = 0; + devpriv->ai_act_scan++; + if (!devpriv->ai_neverending) + /* all data sampled */ + if (devpriv->ai_act_scan >= devpriv->ai_scans) { + pcl812_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + break; + } + } + } + + comedi_event(dev, s); +} + +/* +============================================================================== +*/ +static irqreturn_t interrupt_pcl812_ai_dma(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + unsigned long dma_flags; + int len, bufptr; + short *ptr; + +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: BGN: interrupt_pcl812_ai_dma(...)\n"); +#endif + ptr = (short *)devpriv->dmabuf[devpriv->next_dma_buf]; + len = (devpriv->dmabytestomove[devpriv->next_dma_buf] >> 1) - + devpriv->ai_poll_ptr; + + devpriv->next_dma_buf = 1 - devpriv->next_dma_buf; + disable_dma(devpriv->dma); + set_dma_mode(devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock(); + set_dma_addr(devpriv->dma, devpriv->hwdmaptr[devpriv->next_dma_buf]); + if (devpriv->ai_eos) { + set_dma_count(devpriv->dma, + devpriv->dmabytestomove[devpriv->next_dma_buf]); + } else { + if (devpriv->dma_runs_to_end) { + set_dma_count(devpriv->dma, + devpriv->dmabytestomove[devpriv-> + next_dma_buf]); + } else { + set_dma_count(devpriv->dma, devpriv->last_dma_run); + } + devpriv->dma_runs_to_end--; + } + release_dma_lock(dma_flags); + enable_dma(devpriv->dma); + + outb(0, dev->iobase + PCL812_CLRINT); /* clear INT request */ + + bufptr = devpriv->ai_poll_ptr; + devpriv->ai_poll_ptr = 0; + + transfer_from_dma_buf(dev, s, ptr, bufptr, len); + +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: END: interrupt_pcl812_ai_dma(...)\n"); +#endif + return IRQ_HANDLED; +} + +/* +============================================================================== +*/ +static irqreturn_t interrupt_pcl812(int irq, void *d) +{ + struct comedi_device *dev = d; + + if (!dev->attached) { + comedi_error(dev, "spurious interrupt"); + return IRQ_HANDLED; + } + if (devpriv->ai_dma) + return interrupt_pcl812_ai_dma(irq, d); + else + return interrupt_pcl812_ai_int(irq, d); +} + +/* +============================================================================== +*/ +static int pcl812_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags; + unsigned int top1, top2, i; + + if (!devpriv->ai_dma) + return 0; /* poll is valid only for DMA transfer */ + + spin_lock_irqsave(&dev->spinlock, flags); + + for (i = 0; i < 10; i++) { + /* where is now DMA */ + top1 = get_dma_residue(devpriv->ai_dma); + top2 = get_dma_residue(devpriv->ai_dma); + if (top1 == top2) + break; + } + + if (top1 != top2) { + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + /* where is now DMA in buffer */ + top1 = devpriv->dmabytestomove[1 - devpriv->next_dma_buf] - top1; + top1 >>= 1; /* sample position */ + top2 = top1 - devpriv->ai_poll_ptr; + if (top2 < 1) { /* no new samples */ + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + + transfer_from_dma_buf(dev, s, + (void *)devpriv->dmabuf[1 - + devpriv->next_dma_buf], + devpriv->ai_poll_ptr, top2); + + devpriv->ai_poll_ptr = top1; /* new buffer position */ + + spin_unlock_irqrestore(&dev->spinlock, flags); + + return s->async->buf_write_count - s->async->buf_read_count; +} + +/* +============================================================================== +*/ +static void setup_range_channel(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int rangechan, char wait) +{ + unsigned char chan_reg = CR_CHAN(rangechan); /* normal board */ + /* gain index */ + unsigned char gain_reg = CR_RANGE(rangechan) + + devpriv->range_correction; + + if ((chan_reg == devpriv->old_chan_reg) + && (gain_reg == devpriv->old_gain_reg)) + return; /* we can return, no change */ + + devpriv->old_chan_reg = chan_reg; + devpriv->old_gain_reg = gain_reg; + + if (devpriv->use_MPC) { + if (devpriv->use_diff) { + chan_reg = chan_reg | 0x30; /* DIFF inputs */ + } else { + if (chan_reg & 0x80) + /* SE inputs 8-15 */ + chan_reg = chan_reg | 0x20; + else + /* SE inputs 0-7 */ + chan_reg = chan_reg | 0x10; + } + } + + outb(chan_reg, dev->iobase + PCL812_MUX); /* select channel */ + outb(gain_reg, dev->iobase + PCL812_GAIN); /* select gain */ + + + if (wait) + /* + * XXX this depends on selected range and can be very long for + * some high gain ranges! + */ + udelay(devpriv->max_812_ai_mode0_rangewait); +} + +/* +============================================================================== +*/ +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2) +{ +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: BGN: start_pacer(%d,%u,%u)\n", mode, + divisor1, divisor2); +#endif + outb(0xb4, dev->iobase + PCL812_CTRCTL); + outb(0x74, dev->iobase + PCL812_CTRCTL); + udelay(1); + + if (mode == 1) { + outb(divisor2 & 0xff, dev->iobase + PCL812_CTR2); + outb((divisor2 >> 8) & 0xff, dev->iobase + PCL812_CTR2); + outb(divisor1 & 0xff, dev->iobase + PCL812_CTR1); + outb((divisor1 >> 8) & 0xff, dev->iobase + PCL812_CTR1); + } +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: END: start_pacer(...)\n"); +#endif +} + +/* +============================================================================== +*/ +static void free_resources(struct comedi_device *dev) +{ + + if (dev->private) { + if (devpriv->dmabuf[0]) + free_pages(devpriv->dmabuf[0], devpriv->dmapages[0]); + if (devpriv->dmabuf[1]) + free_pages(devpriv->dmabuf[1], devpriv->dmapages[1]); + if (devpriv->dma) + free_dma(devpriv->dma); + } + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->iobase) + release_region(dev->iobase, this_board->io_range); +} + +/* +============================================================================== +*/ +static int pcl812_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: BGN: pcl812_ai_cancel(...)\n"); +#endif + if (devpriv->ai_dma) + disable_dma(devpriv->dma); + outb(0, dev->iobase + PCL812_CLRINT); /* clear INT request */ + /* Stop A/D */ + outb(devpriv->mode_reg_int | 0, dev->iobase + PCL812_MODE); + start_pacer(dev, -1, 0, 0); /* stop 8254 */ + outb(0, dev->iobase + PCL812_CLRINT); /* clear INT request */ +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: END: pcl812_ai_cancel(...)\n"); +#endif + return 0; +} + +/* +============================================================================== +*/ +static void pcl812_reset(struct comedi_device *dev) +{ +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: BGN: pcl812_reset(...)\n"); +#endif + outb(0, dev->iobase + PCL812_MUX); + outb(0 + devpriv->range_correction, dev->iobase + PCL812_GAIN); + devpriv->old_chan_reg = -1; /* invalidate chain/gain memory */ + devpriv->old_gain_reg = -1; + + switch (this_board->board_type) { + case boardPCL812PG: + case boardPCL812: + case boardACL8112: + case boardACL8216: + outb(0, dev->iobase + PCL812_DA2_LO); + outb(0, dev->iobase + PCL812_DA2_HI); + case boardA821: + outb(0, dev->iobase + PCL812_DA1_LO); + outb(0, dev->iobase + PCL812_DA1_HI); + start_pacer(dev, -1, 0, 0); /* stop 8254 */ + outb(0, dev->iobase + PCL812_DO_HI); + outb(0, dev->iobase + PCL812_DO_LO); + outb(devpriv->mode_reg_int | 0, dev->iobase + PCL812_MODE); + outb(0, dev->iobase + PCL812_CLRINT); + break; + case boardPCL813B: + case boardPCL813: + case boardISO813: + case boardACL8113: + udelay(5); + break; + } + udelay(5); +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "pcl812 EDBG: END: pcl812_reset(...)\n"); +#endif +} + +/* +============================================================================== +*/ +static int pcl812_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret, subdev; + unsigned long iobase; + unsigned int irq; + unsigned int dma; + unsigned long pages; + struct comedi_subdevice *s; + int n_subdevices; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: pcl812: board=%s, ioport=0x%03lx", + dev->minor, this_board->name, iobase); + + if (!request_region(iobase, this_board->io_range, "pcl812")) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + ret = alloc_private(dev, sizeof(struct pcl812_private)); + if (ret < 0) { + free_resources(dev); + return ret; /* Can't alloc mem */ + } + + dev->board_name = this_board->name; + + irq = 0; + if (this_board->IRQbits != 0) { /* board support IRQ */ + irq = it->options[1]; + if (irq) { /* we want to use IRQ */ + if (((1 << irq) & this_board->IRQbits) == 0) { + printk + (", IRQ %u is out of allowed range, " + "DISABLING IT", irq); + irq = 0; /* Bad IRQ */ + } else { + if (request_irq + (irq, interrupt_pcl812, 0, "pcl812", dev)) { + printk + (", unable to allocate IRQ %u, " + "DISABLING IT", irq); + irq = 0; /* Can't use IRQ */ + } else { + printk(KERN_INFO ", irq=%u", irq); + } + } + } + } + + dev->irq = irq; + + dma = 0; + devpriv->dma = dma; + if (!dev->irq) + goto no_dma; /* if we haven't IRQ, we can't use DMA */ + if (this_board->DMAbits != 0) { /* board support DMA */ + dma = it->options[2]; + if (((1 << dma) & this_board->DMAbits) == 0) { + printk(", DMA is out of allowed range, FAIL!\n"); + return -EINVAL; /* Bad DMA */ + } + ret = request_dma(dma, "pcl812"); + if (ret) { + printk(KERN_ERR ", unable to allocate DMA %u, FAIL!\n", + dma); + return -EBUSY; /* DMA isn't free */ + } + devpriv->dma = dma; + printk(KERN_INFO ", dma=%u", dma); + pages = 1; /* we want 8KB */ + devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages); + if (!devpriv->dmabuf[0]) { + printk(", unable to allocate DMA buffer, FAIL!\n"); + /* + * maybe experiment with try_to_free_pages() + * will help .... + */ + free_resources(dev); + return -EBUSY; /* no buffer :-( */ + } + devpriv->dmapages[0] = pages; + devpriv->hwdmaptr[0] = virt_to_bus((void *)devpriv->dmabuf[0]); + devpriv->hwdmasize[0] = PAGE_SIZE * (1 << pages); + devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages); + if (!devpriv->dmabuf[1]) { + printk(KERN_ERR ", unable to allocate DMA buffer, FAIL!\n"); + free_resources(dev); + return -EBUSY; + } + devpriv->dmapages[1] = pages; + devpriv->hwdmaptr[1] = virt_to_bus((void *)devpriv->dmabuf[1]); + devpriv->hwdmasize[1] = PAGE_SIZE * (1 << pages); + } +no_dma: + + n_subdevices = 0; + if (this_board->n_aichan > 0) + n_subdevices++; + if (this_board->n_aochan > 0) + n_subdevices++; + if (this_board->n_dichan > 0) + n_subdevices++; + if (this_board->n_dochan > 0) + n_subdevices++; + + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) { + free_resources(dev); + return ret; + } + + subdev = 0; + + /* analog input */ + if (this_board->n_aichan > 0) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE; + switch (this_board->board_type) { + case boardA821: + if (it->options[2] == 1) { + s->n_chan = this_board->n_aichan_diff; + s->subdev_flags |= SDF_DIFF; + devpriv->use_diff = 1; + } else { + s->n_chan = this_board->n_aichan; + s->subdev_flags |= SDF_GROUND; + } + break; + case boardACL8112: + case boardACL8216: + if (it->options[4] == 1) { + s->n_chan = this_board->n_aichan_diff; + s->subdev_flags |= SDF_DIFF; + devpriv->use_diff = 1; + } else { + s->n_chan = this_board->n_aichan; + s->subdev_flags |= SDF_GROUND; + } + break; + default: + s->n_chan = this_board->n_aichan; + s->subdev_flags |= SDF_GROUND; + break; + } + s->maxdata = this_board->ai_maxdata; + s->len_chanlist = MAX_CHANLIST_LEN; + s->range_table = this_board->rangelist_ai; + if (this_board->board_type == boardACL8216) + s->insn_read = acl8216_ai_insn_read; + else + s->insn_read = pcl812_ai_insn_read; + + devpriv->use_MPC = this_board->haveMPC508; + s->cancel = pcl812_ai_cancel; + if (dev->irq) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->do_cmdtest = pcl812_ai_cmdtest; + s->do_cmd = pcl812_ai_cmd; + s->poll = pcl812_ai_poll; + } + switch (this_board->board_type) { + case boardPCL812PG: + if (it->options[4] == 1) + s->range_table = &range_pcl812pg2_ai; + break; + case boardPCL812: + switch (it->options[4]) { + case 0: + s->range_table = &range_bipolar10; + break; + case 1: + s->range_table = &range_bipolar5; + break; + case 2: + s->range_table = &range_bipolar2_5; + break; + case 3: + s->range_table = &range812_bipolar1_25; + break; + case 4: + s->range_table = &range812_bipolar0_625; + break; + case 5: + s->range_table = &range812_bipolar0_3125; + break; + default: + s->range_table = &range_bipolar10; + break; + printk + (", incorrect range number %d, changing " + "to 0 (+/-10V)", it->options[4]); + break; + } + break; + break; + case boardPCL813B: + if (it->options[1] == 1) + s->range_table = &range_pcl813b2_ai; + break; + case boardISO813: + switch (it->options[1]) { + case 0: + s->range_table = &range_iso813_1_ai; + break; + case 1: + s->range_table = &range_iso813_1_2_ai; + break; + case 2: + s->range_table = &range_iso813_2_ai; + devpriv->range_correction = 1; + break; + case 3: + s->range_table = &range_iso813_2_2_ai; + devpriv->range_correction = 1; + break; + default: + s->range_table = &range_iso813_1_ai; + break; + printk + (", incorrect range number %d, " + "changing to 0 ", it->options[1]); + break; + } + break; + case boardACL8113: + switch (it->options[1]) { + case 0: + s->range_table = &range_acl8113_1_ai; + break; + case 1: + s->range_table = &range_acl8113_1_2_ai; + break; + case 2: + s->range_table = &range_acl8113_2_ai; + devpriv->range_correction = 1; + break; + case 3: + s->range_table = &range_acl8113_2_2_ai; + devpriv->range_correction = 1; + break; + default: + s->range_table = &range_acl8113_1_ai; + break; + printk + (", incorrect range number %d, " + "changing to 0 ", it->options[1]); + break; + } + break; + } + subdev++; + } + + /* analog output */ + if (this_board->n_aochan > 0) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = this_board->n_aochan; + s->maxdata = 0xfff; + s->len_chanlist = 1; + s->range_table = this_board->rangelist_ao; + s->insn_read = pcl812_ao_insn_read; + s->insn_write = pcl812_ao_insn_write; + switch (this_board->board_type) { + case boardA821: + if (it->options[3] == 1) + s->range_table = &range_unipolar10; + break; + case boardPCL812: + case boardACL8112: + case boardPCL812PG: + case boardACL8216: + if (it->options[5] == 1) + s->range_table = &range_unipolar10; + if (it->options[5] == 2) + s->range_table = &range_unknown; + break; + } + subdev++; + } + + /* digital input */ + if (this_board->n_dichan > 0) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = this_board->n_dichan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dichan; + s->range_table = &range_digital; + s->insn_bits = pcl812_di_insn_bits; + subdev++; + } + + /* digital output */ + if (this_board->n_dochan > 0) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = this_board->n_dochan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dochan; + s->range_table = &range_digital; + s->insn_bits = pcl812_do_insn_bits; + subdev++; + } + + switch (this_board->board_type) { + case boardACL8216: + devpriv->ai_is16b = 1; + case boardPCL812PG: + case boardPCL812: + case boardACL8112: + devpriv->max_812_ai_mode0_rangewait = 1; + if (it->options[3] > 0) + /* we use external trigger */ + devpriv->use_ext_trg = 1; + case boardA821: + devpriv->max_812_ai_mode0_rangewait = 1; + devpriv->mode_reg_int = (irq << 4) & 0xf0; + break; + case boardPCL813B: + case boardPCL813: + case boardISO813: + case boardACL8113: + /* maybe there must by greatest timeout */ + devpriv->max_812_ai_mode0_rangewait = 5; + break; + } + + printk(KERN_INFO "\n"); + devpriv->valid = 1; + + pcl812_reset(dev); + + return 0; +} + +/* +============================================================================== + */ +static int pcl812_detach(struct comedi_device *dev) +{ + +#ifdef PCL812_EXTDEBUG + printk(KERN_DEBUG "comedi%d: pcl812: remove\n", dev->minor); +#endif + free_resources(dev); + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcl816.c b/drivers/staging/comedi/drivers/pcl816.c new file mode 100644 index 00000000..96cd7ec2 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl816.c @@ -0,0 +1,1360 @@ +/* + comedi/drivers/pcl816.c + + Author: Juan Grigera + based on pcl818 by Michal Dobes and bits of pcl812 + + hardware driver for Advantech cards: + card: PCL-816, PCL814B + driver: pcl816 +*/ +/* +Driver: pcl816 +Description: Advantech PCL-816 cards, PCL-814 +Author: Juan Grigera +Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b) +Status: works +Updated: Tue, 2 Apr 2002 23:15:21 -0800 + +PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO. +Differences are at resolution (16 vs 12 bits). + +The driver support AI command mode, other subdevices not written. + +Analog output and digital input and output are not supported. + +Configuration Options: + [0] - IO Base + [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + [2] - DMA (0=disable, 1, 3) + [3] - 0, 10=10MHz clock for 8254 + 1= 1MHz clock for 8254 + +*/ + +#include "../comedidev.h" + +#include +#include +#include +#include +#include +#include + +#include "8253.h" + +#define DEBUG(x) x + +/* boards constants */ +/* IO space len */ +#define PCLx1x_RANGE 16 + +/* #define outb(x,y) printk("OUTB(%x, 200+%d)\n", x,y-0x200); outb(x,y) */ + +/* INTEL 8254 counters */ +#define PCL816_CTR0 4 +#define PCL816_CTR1 5 +#define PCL816_CTR2 6 +/* R: counter read-back register W: counter control */ +#define PCL816_CTRCTL 7 + +/* R: A/D high byte W: A/D range control */ +#define PCL816_RANGE 9 +/* W: clear INT request */ +#define PCL816_CLRINT 10 +/* R: next mux scan channel W: mux scan channel & range control pointer */ +#define PCL816_MUX 11 +/* R/W: operation control register */ +#define PCL816_CONTROL 12 + +/* R: return status byte W: set DMA/IRQ */ +#define PCL816_STATUS 13 +#define PCL816_STATUS_DRDY_MASK 0x80 + +/* R: low byte of A/D W: soft A/D trigger */ +#define PCL816_AD_LO 8 +/* R: high byte of A/D W: A/D range control */ +#define PCL816_AD_HI 9 + +/* type of interrupt handler */ +#define INT_TYPE_AI1_INT 1 +#define INT_TYPE_AI1_DMA 2 +#define INT_TYPE_AI3_INT 4 +#define INT_TYPE_AI3_DMA 5 +#ifdef unused +#define INT_TYPE_AI1_DMA_RTC 9 +#define INT_TYPE_AI3_DMA_RTC 10 + +/* RTC stuff... */ +#define RTC_IRQ 8 +#define RTC_IO_EXTENT 0x10 +#endif + +#define MAGIC_DMA_WORD 0x5a5a + +static const struct comedi_lrange range_pcl816 = { 8, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + } +}; + +struct pcl816_board { + + const char *name; /* board name */ + int n_ranges; /* len of range list */ + int n_aichan; /* num of A/D chans in diferencial mode */ + unsigned int ai_ns_min; /* minimal allowed delay between samples (in ns) */ + int n_aochan; /* num of D/A chans */ + int n_dichan; /* num of DI chans */ + int n_dochan; /* num of DO chans */ + const struct comedi_lrange *ai_range_type; /* default A/D rangelist */ + const struct comedi_lrange *ao_range_type; /* default D/A rangelist */ + unsigned int io_range; /* len of IO space */ + unsigned int IRQbits; /* allowed interrupts */ + unsigned int DMAbits; /* allowed DMA chans */ + int ai_maxdata; /* maxdata for A/D */ + int ao_maxdata; /* maxdata for D/A */ + int ai_chanlist; /* allowed len of channel list A/D */ + int ao_chanlist; /* allowed len of channel list D/A */ + int i8254_osc_base; /* 1/frequency of on board oscilator in ns */ +}; + +static const struct pcl816_board boardtypes[] = { + {"pcl816", 8, 16, 10000, 1, 16, 16, &range_pcl816, + &range_pcl816, PCLx1x_RANGE, + 0x00fc, /* IRQ mask */ + 0x0a, /* DMA mask */ + 0xffff, /* 16-bit card */ + 0xffff, /* D/A maxdata */ + 1024, + 1, /* ao chan list */ + 100}, + {"pcl814b", 8, 16, 10000, 1, 16, 16, &range_pcl816, + &range_pcl816, PCLx1x_RANGE, + 0x00fc, + 0x0a, + 0x3fff, /* 14 bit card */ + 0x3fff, + 1024, + 1, + 100}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl816_board)) +#define devpriv ((struct pcl816_private *)dev->private) +#define this_board ((const struct pcl816_board *)dev->board_ptr) + +static int pcl816_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcl816_detach(struct comedi_device *dev); + +#ifdef unused +static int RTC_lock; /* RTC lock */ +static int RTC_timer_lock; /* RTC int lock */ +#endif + +static struct comedi_driver driver_pcl816 = { + .driver_name = "pcl816", + .module = THIS_MODULE, + .attach = pcl816_attach, + .detach = pcl816_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct pcl816_board), +}; + +static int __init driver_pcl816_init_module(void) +{ + return comedi_driver_register(&driver_pcl816); +} + +static void __exit driver_pcl816_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcl816); +} + +module_init(driver_pcl816_init_module); +module_exit(driver_pcl816_cleanup_module); + +struct pcl816_private { + + unsigned int dma; /* used DMA, 0=don't use DMA */ + int dma_rtc; /* 1=RTC used with DMA, 0=no RTC alloc */ +#ifdef unused + unsigned long rtc_iobase; /* RTC port region */ + unsigned int rtc_iosize; + unsigned int rtc_irq; +#endif + unsigned long dmabuf[2]; /* pointers to begin of DMA buffers */ + unsigned int dmapages[2]; /* len of DMA buffers in PAGE_SIZEs */ + unsigned int hwdmaptr[2]; /* hardware address of DMA buffers */ + unsigned int hwdmasize[2]; /* len of DMA buffers in Bytes */ + unsigned int dmasamplsize; /* size in samples hwdmasize[0]/2 */ + unsigned int last_top_dma; /* DMA pointer in last RTC int */ + int next_dma_buf; /* which DMA buffer will be used next round */ + long dma_runs_to_end; /* how many we must permorm DMA transfer to end of record */ + unsigned long last_dma_run; /* how many bytes we must transfer on last DMA page */ + + unsigned int ai_scans; /* len of scanlist */ + unsigned char ai_neverending; /* if=1, then we do neverending record (you must use cancel()) */ + int irq_free; /* 1=have allocated IRQ */ + int irq_blocked; /* 1=IRQ now uses any subdev */ +#ifdef unused + int rtc_irq_blocked; /* 1=we now do AI with DMA&RTC */ +#endif + int irq_was_now_closed; /* when IRQ finish, there's stored int816_mode for last interrupt */ + int int816_mode; /* who now uses IRQ - 1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma */ + struct comedi_subdevice *last_int_sub; /* ptr to subdevice which now finish */ + int ai_act_scan; /* how many scans we finished */ + unsigned int ai_act_chanlist[16]; /* MUX setting for actual AI operations */ + unsigned int ai_act_chanlist_len; /* how long is actual MUX list */ + unsigned int ai_act_chanlist_pos; /* actual position in MUX list */ + unsigned int ai_n_chan; /* how many channels per scan */ + unsigned int ai_poll_ptr; /* how many sampes transfer poll */ + struct comedi_subdevice *sub_ai; /* ptr to AI subdevice */ +#ifdef unused + struct timer_list rtc_irq_timer; /* timer for RTC sanity check */ + unsigned long rtc_freq; /* RTC int freq */ +#endif +}; + +/* +============================================================================== +*/ +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int chanlen); +static void setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int seglen); +static int pcl816_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2); +#ifdef unused +static int set_rtc_irq_bit(unsigned char bit); +#endif + +static int pcl816_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); + +/* +============================================================================== + ANALOG INPUT MODE0, 816 cards, slow version +*/ +static int pcl816_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int timeout; + + DPRINTK("mode 0 analog input\n"); + /* software trigger, DMA and INT off */ + outb(0, dev->iobase + PCL816_CONTROL); + /* clear INT (conversion end) flag */ + outb(0, dev->iobase + PCL816_CLRINT); + + /* Set the input channel */ + outb(CR_CHAN(insn->chanspec) & 0xf, dev->iobase + PCL816_MUX); + /* select gain */ + outb(CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE); + + for (n = 0; n < insn->n; n++) { + + outb(0, dev->iobase + PCL816_AD_LO); /* start conversion */ + + timeout = 100; + while (timeout--) { + if (!(inb(dev->iobase + PCL816_STATUS) & + PCL816_STATUS_DRDY_MASK)) { + /* return read value */ + data[n] = + ((inb(dev->iobase + + PCL816_AD_HI) << 8) | + (inb(dev->iobase + PCL816_AD_LO))); + /* clear INT (conversion end) flag */ + outb(0, dev->iobase + PCL816_CLRINT); + break; + } + udelay(1); + } + /* Return timeout error */ + if (!timeout) { + comedi_error(dev, "A/D insn timeout\n"); + data[0] = 0; + /* clear INT (conversion end) flag */ + outb(0, dev->iobase + PCL816_CLRINT); + return -EIO; + } + + } + return n; +} + +/* +============================================================================== + analog input interrupt mode 1 & 3, 818 cards + one sample per interrupt version +*/ +static irqreturn_t interrupt_pcl816_ai_mode13_int(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + int low, hi; + int timeout = 50; /* wait max 50us */ + + while (timeout--) { + if (!(inb(dev->iobase + PCL816_STATUS) & + PCL816_STATUS_DRDY_MASK)) + break; + udelay(1); + } + if (!timeout) { /* timeout, bail error */ + outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + comedi_error(dev, "A/D mode1/3 IRQ without DRDY!"); + pcl816_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + + } + + /* get the sample */ + low = inb(dev->iobase + PCL816_AD_LO); + hi = inb(dev->iobase + PCL816_AD_HI); + + comedi_buf_put(s->async, (hi << 8) | low); + + outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + + if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len) + devpriv->ai_act_chanlist_pos = 0; + + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_chan) { + s->async->cur_chan = 0; + devpriv->ai_act_scan++; + } + + if (!devpriv->ai_neverending) + /* all data sampled */ + if (devpriv->ai_act_scan >= devpriv->ai_scans) { + /* all data sampled */ + pcl816_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + } + comedi_event(dev, s); + return IRQ_HANDLED; +} + +/* +============================================================================== + analog input dma mode 1 & 3, 816 cards +*/ +static void transfer_from_dma_buf(struct comedi_device *dev, + struct comedi_subdevice *s, short *ptr, + unsigned int bufptr, unsigned int len) +{ + int i; + + s->async->events = 0; + + for (i = 0; i < len; i++) { + + comedi_buf_put(s->async, ptr[bufptr++]); + + if (++devpriv->ai_act_chanlist_pos >= + devpriv->ai_act_chanlist_len) { + devpriv->ai_act_chanlist_pos = 0; + } + + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_chan) { + s->async->cur_chan = 0; + devpriv->ai_act_scan++; + } + + if (!devpriv->ai_neverending) + /* all data sampled */ + if (devpriv->ai_act_scan >= devpriv->ai_scans) { + pcl816_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_BLOCK; + break; + } + } + + comedi_event(dev, s); +} + +static irqreturn_t interrupt_pcl816_ai_mode13_dma(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + int len, bufptr, this_dma_buf; + unsigned long dma_flags; + short *ptr; + + disable_dma(devpriv->dma); + this_dma_buf = devpriv->next_dma_buf; + + /* switch dma bufs */ + if ((devpriv->dma_runs_to_end > -1) || devpriv->ai_neverending) { + + devpriv->next_dma_buf = 1 - devpriv->next_dma_buf; + set_dma_mode(devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock(); +/* clear_dma_ff (devpriv->dma); */ + set_dma_addr(devpriv->dma, + devpriv->hwdmaptr[devpriv->next_dma_buf]); + if (devpriv->dma_runs_to_end) { + set_dma_count(devpriv->dma, + devpriv->hwdmasize[devpriv-> + next_dma_buf]); + } else { + set_dma_count(devpriv->dma, devpriv->last_dma_run); + } + release_dma_lock(dma_flags); + enable_dma(devpriv->dma); + } + + devpriv->dma_runs_to_end--; + outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + + ptr = (short *)devpriv->dmabuf[this_dma_buf]; + + len = (devpriv->hwdmasize[0] >> 1) - devpriv->ai_poll_ptr; + bufptr = devpriv->ai_poll_ptr; + devpriv->ai_poll_ptr = 0; + + transfer_from_dma_buf(dev, s, ptr, bufptr, len); + return IRQ_HANDLED; +} + +/* +============================================================================== + INT procedure +*/ +static irqreturn_t interrupt_pcl816(int irq, void *d) +{ + struct comedi_device *dev = d; + DPRINTK(""); + + if (!dev->attached) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + + switch (devpriv->int816_mode) { + case INT_TYPE_AI1_DMA: + case INT_TYPE_AI3_DMA: + return interrupt_pcl816_ai_mode13_dma(irq, d); + case INT_TYPE_AI1_INT: + case INT_TYPE_AI3_INT: + return interrupt_pcl816_ai_mode13_int(irq, d); + } + + outb(0, dev->iobase + PCL816_CLRINT); /* clear INT request */ + if ((!dev->irq) | (!devpriv->irq_free) | (!devpriv->irq_blocked) | + (!devpriv->int816_mode)) { + if (devpriv->irq_was_now_closed) { + devpriv->irq_was_now_closed = 0; + /* comedi_error(dev,"last IRQ.."); */ + return IRQ_HANDLED; + } + comedi_error(dev, "bad IRQ!"); + return IRQ_NONE; + } + comedi_error(dev, "IRQ from unknown source!"); + return IRQ_NONE; +} + +/* +============================================================================== + COMMAND MODE +*/ +static void pcl816_cmdtest_out(int e, struct comedi_cmd *cmd) +{ + printk(KERN_INFO "pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e, + cmd->start_src, cmd->scan_begin_src, cmd->convert_src); + printk(KERN_INFO "pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n", e, + cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg); + printk(KERN_INFO "pcl816 e=%d stopsrc=%x scanend=%x\n", e, + cmd->stop_src, cmd->scan_end_src); + printk(KERN_INFO "pcl816 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n", + e, cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len); +} + +/* +============================================================================== +*/ +static int pcl816_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp, divisor1 = 0, divisor2 = 0; + + DEBUG(printk(KERN_INFO "pcl816 pcl812_ai_cmdtest\n"); + pcl816_cmdtest_out(-1, cmd); + ); + + /* step 1: make sure trigger sources are trivially valid */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_EXT | TRIG_TIMER; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + + /* + * step 2: make sure trigger sources + * are unique and mutually compatible + */ + + if (cmd->start_src != TRIG_NOW) { + cmd->start_src = TRIG_NOW; + err++; + } + + if (cmd->scan_begin_src != TRIG_FOLLOW) { + cmd->scan_begin_src = TRIG_FOLLOW; + err++; + } + + if (cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_TIMER) { + cmd->convert_src = TRIG_TIMER; + err++; + } + + if (cmd->scan_end_src != TRIG_COUNT) { + cmd->scan_end_src = TRIG_COUNT; + err++; + } + + if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) + err++; + + if (err) + return 2; + + + /* step 3: make sure arguments are trivially compatible */ + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < this_board->ai_ns_min) { + cmd->convert_arg = this_board->ai_ns_min; + err++; + } + } else { /* TRIG_EXT */ + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + + /* step 4: fix up any arguments */ + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + i8253_cascade_ns_to_timer(this_board->i8254_osc_base, + &divisor1, &divisor2, + &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (cmd->convert_arg < this_board->ai_ns_min) + cmd->convert_arg = this_board->ai_ns_min; + if (tmp != cmd->convert_arg) + err++; + } + + if (err) + return 4; + + + /* step 5: complain about special chanlist considerations */ + + if (cmd->chanlist) { + if (!check_channel_list(dev, s, cmd->chanlist, + cmd->chanlist_len)) + return 5; /* incorrect channels list */ + } + + return 0; +} + +static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned int divisor1 = 0, divisor2 = 0, dma_flags, bytes, dmairq; + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int seglen; + + if (cmd->start_src != TRIG_NOW) + return -EINVAL; + if (cmd->scan_begin_src != TRIG_FOLLOW) + return -EINVAL; + if (cmd->scan_end_src != TRIG_COUNT) + return -EINVAL; + if (cmd->scan_end_arg != cmd->chanlist_len) + return -EINVAL; +/* if(cmd->chanlist_len>MAX_CHANLIST_LEN) return -EINVAL; */ + if (devpriv->irq_blocked) + return -EBUSY; + + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < this_board->ai_ns_min) + cmd->convert_arg = this_board->ai_ns_min; + + i8253_cascade_ns_to_timer(this_board->i8254_osc_base, &divisor1, + &divisor2, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + + /* PCL816 crash if any divisor is set to 1 */ + if (divisor1 == 1) { + divisor1 = 2; + divisor2 /= 2; + } + if (divisor2 == 1) { + divisor2 = 2; + divisor1 /= 2; + } + } + + start_pacer(dev, -1, 0, 0); /* stop pacer */ + + seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len); + if (seglen < 1) + return -EINVAL; + setup_channel_list(dev, s, cmd->chanlist, seglen); + udelay(1); + + devpriv->ai_n_chan = cmd->chanlist_len; + devpriv->ai_act_scan = 0; + s->async->cur_chan = 0; + devpriv->irq_blocked = 1; + devpriv->ai_poll_ptr = 0; + devpriv->irq_was_now_closed = 0; + + if (cmd->stop_src == TRIG_COUNT) { + devpriv->ai_scans = cmd->stop_arg; + devpriv->ai_neverending = 0; + } else { + devpriv->ai_scans = 0; + devpriv->ai_neverending = 1; + } + + /* don't we want wake up every scan? */ + if ((cmd->flags & TRIG_WAKE_EOS)) { + printk(KERN_INFO + "pl816: You wankt WAKE_EOS but I dont want handle it"); + /* devpriv->ai_eos=1; */ + /* if (devpriv->ai_n_chan==1) */ + /* devpriv->dma=0; // DMA is useless for this situation */ + } + + if (devpriv->dma) { + bytes = devpriv->hwdmasize[0]; + if (!devpriv->ai_neverending) { + /* how many */ + bytes = s->async->cmd.chanlist_len * + s->async->cmd.chanlist_len * + sizeof(short); + + /* how many DMA pages we must fill */ + devpriv->dma_runs_to_end = bytes / + devpriv->hwdmasize[0]; + + /* on last dma transfer must be moved */ + devpriv->last_dma_run = bytes % devpriv->hwdmasize[0]; + devpriv->dma_runs_to_end--; + if (devpriv->dma_runs_to_end >= 0) + bytes = devpriv->hwdmasize[0]; + } else + devpriv->dma_runs_to_end = -1; + + devpriv->next_dma_buf = 0; + set_dma_mode(devpriv->dma, DMA_MODE_READ); + dma_flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]); + set_dma_count(devpriv->dma, bytes); + release_dma_lock(dma_flags); + enable_dma(devpriv->dma); + } + + start_pacer(dev, 1, divisor1, divisor2); + dmairq = ((devpriv->dma & 0x3) << 4) | (dev->irq & 0x7); + + switch (cmd->convert_src) { + case TRIG_TIMER: + devpriv->int816_mode = INT_TYPE_AI1_DMA; + + /* Pacer+IRQ+DMA */ + outb(0x32, dev->iobase + PCL816_CONTROL); + + /* write irq and DMA to card */ + outb(dmairq, dev->iobase + PCL816_STATUS); + break; + + default: + devpriv->int816_mode = INT_TYPE_AI3_DMA; + + /* Ext trig+IRQ+DMA */ + outb(0x34, dev->iobase + PCL816_CONTROL); + + /* write irq to card */ + outb(dmairq, dev->iobase + PCL816_STATUS); + break; + } + + DPRINTK("pcl816 END: pcl812_ai_cmd()\n"); + return 0; +} + +static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags; + unsigned int top1, top2, i; + + if (!devpriv->dma) + return 0; /* poll is valid only for DMA transfer */ + + spin_lock_irqsave(&dev->spinlock, flags); + + for (i = 0; i < 20; i++) { + top1 = get_dma_residue(devpriv->dma); /* where is now DMA */ + top2 = get_dma_residue(devpriv->dma); + if (top1 == top2) + break; + } + if (top1 != top2) { + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + + /* where is now DMA in buffer */ + top1 = devpriv->hwdmasize[0] - top1; + top1 >>= 1; /* sample position */ + top2 = top1 - devpriv->ai_poll_ptr; + if (top2 < 1) { /* no new samples */ + spin_unlock_irqrestore(&dev->spinlock, flags); + return 0; + } + + transfer_from_dma_buf(dev, s, + (short *)devpriv->dmabuf[devpriv->next_dma_buf], + devpriv->ai_poll_ptr, top2); + + devpriv->ai_poll_ptr = top1; /* new buffer position */ + spin_unlock_irqrestore(&dev->spinlock, flags); + + return s->async->buf_write_count - s->async->buf_read_count; +} + +/* +============================================================================== + cancel any mode 1-4 AI +*/ +static int pcl816_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ +/* DEBUG(printk("pcl816_ai_cancel()\n");) */ + + if (devpriv->irq_blocked > 0) { + switch (devpriv->int816_mode) { +#ifdef unused + case INT_TYPE_AI1_DMA_RTC: + case INT_TYPE_AI3_DMA_RTC: + set_rtc_irq_bit(0); /* stop RTC */ + del_timer(&devpriv->rtc_irq_timer); +#endif + case INT_TYPE_AI1_DMA: + case INT_TYPE_AI3_DMA: + disable_dma(devpriv->dma); + case INT_TYPE_AI1_INT: + case INT_TYPE_AI3_INT: + outb(inb(dev->iobase + PCL816_CONTROL) & 0x73, + dev->iobase + PCL816_CONTROL); /* Stop A/D */ + udelay(1); + outb(0, dev->iobase + PCL816_CONTROL); /* Stop A/D */ + + /* Stop pacer */ + outb(0xb0, dev->iobase + PCL816_CTRCTL); + outb(0x70, dev->iobase + PCL816_CTRCTL); + outb(0, dev->iobase + PCL816_AD_LO); + inb(dev->iobase + PCL816_AD_LO); + inb(dev->iobase + PCL816_AD_HI); + + /* clear INT request */ + outb(0, dev->iobase + PCL816_CLRINT); + + /* Stop A/D */ + outb(0, dev->iobase + PCL816_CONTROL); + devpriv->irq_blocked = 0; + devpriv->irq_was_now_closed = devpriv->int816_mode; + devpriv->int816_mode = 0; + devpriv->last_int_sub = s; +/* s->busy = 0; */ + break; + } + } + + DEBUG(printk("comedi: pcl816_ai_cancel() successful\n");) + return 0; +} + +/* +============================================================================== + chech for PCL816 +*/ +static int pcl816_check(unsigned long iobase) +{ + outb(0x00, iobase + PCL816_MUX); + udelay(1); + if (inb(iobase + PCL816_MUX) != 0x00) + return 1; /* there isn't card */ + outb(0x55, iobase + PCL816_MUX); + udelay(1); + if (inb(iobase + PCL816_MUX) != 0x55) + return 1; /* there isn't card */ + outb(0x00, iobase + PCL816_MUX); + udelay(1); + outb(0x18, iobase + PCL816_CONTROL); + udelay(1); + if (inb(iobase + PCL816_CONTROL) != 0x18) + return 1; /* there isn't card */ + return 0; /* ok, card exist */ +} + +/* +============================================================================== + reset whole PCL-816 cards +*/ +static void pcl816_reset(struct comedi_device *dev) +{ +/* outb (0, dev->iobase + PCL818_DA_LO); DAC=0V */ +/* outb (0, dev->iobase + PCL818_DA_HI); */ +/* udelay (1); */ +/* outb (0, dev->iobase + PCL818_DO_HI); DO=$0000 */ +/* outb (0, dev->iobase + PCL818_DO_LO); */ +/* udelay (1); */ + outb(0, dev->iobase + PCL816_CONTROL); + outb(0, dev->iobase + PCL816_MUX); + outb(0, dev->iobase + PCL816_CLRINT); + outb(0xb0, dev->iobase + PCL816_CTRCTL); /* Stop pacer */ + outb(0x70, dev->iobase + PCL816_CTRCTL); + outb(0x30, dev->iobase + PCL816_CTRCTL); + outb(0, dev->iobase + PCL816_RANGE); +} + +/* +============================================================================== + Start/stop pacer onboard pacer +*/ +static void +start_pacer(struct comedi_device *dev, int mode, unsigned int divisor1, + unsigned int divisor2) +{ + outb(0x32, dev->iobase + PCL816_CTRCTL); + outb(0xff, dev->iobase + PCL816_CTR0); + outb(0x00, dev->iobase + PCL816_CTR0); + udelay(1); + + /* set counter 2 as mode 3 */ + outb(0xb4, dev->iobase + PCL816_CTRCTL); + /* set counter 1 as mode 3 */ + outb(0x74, dev->iobase + PCL816_CTRCTL); + udelay(1); + + if (mode == 1) { + DPRINTK("mode %d, divisor1 %d, divisor2 %d\n", mode, divisor1, + divisor2); + outb(divisor2 & 0xff, dev->iobase + PCL816_CTR2); + outb((divisor2 >> 8) & 0xff, dev->iobase + PCL816_CTR2); + outb(divisor1 & 0xff, dev->iobase + PCL816_CTR1); + outb((divisor1 >> 8) & 0xff, dev->iobase + PCL816_CTR1); + } + + /* clear pending interrupts (just in case) */ +/* outb(0, dev->iobase + PCL816_CLRINT); */ +} + +/* +============================================================================== + Check if channel list from user is builded correctly + If it's ok, then return non-zero length of repeated segment of channel list +*/ +static int +check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int *chanlist, + unsigned int chanlen) +{ + unsigned int chansegment[16]; + unsigned int i, nowmustbechan, seglen, segpos; + + /* correct channel and range number check itself comedi/range.c */ + if (chanlen < 1) { + comedi_error(dev, "range/channel list is empty!"); + return 0; + } + + if (chanlen > 1) { + /* first channel is every time ok */ + chansegment[0] = chanlist[0]; + for (i = 1, seglen = 1; i < chanlen; i++, seglen++) { + /* build part of chanlist */ + DEBUG(printk(KERN_INFO "%d. %d %d\n", i, + CR_CHAN(chanlist[i]), + CR_RANGE(chanlist[i]));) + + /* we detect loop, this must by finish */ + if (chanlist[0] == chanlist[i]) + break; + nowmustbechan = + (CR_CHAN(chansegment[i - 1]) + 1) % chanlen; + if (nowmustbechan != CR_CHAN(chanlist[i])) { + /* channel list isn't continuous :-( */ + printk(KERN_WARNING + "comedi%d: pcl816: channel list must " + "be continuous! chanlist[%i]=%d but " + "must be %d or %d!\n", dev->minor, + i, CR_CHAN(chanlist[i]), nowmustbechan, + CR_CHAN(chanlist[0])); + return 0; + } + /* well, this is next correct channel in list */ + chansegment[i] = chanlist[i]; + } + + /* check whole chanlist */ + for (i = 0, segpos = 0; i < chanlen; i++) { + DEBUG(printk("%d %d=%d %d\n", + CR_CHAN(chansegment[i % seglen]), + CR_RANGE(chansegment[i % seglen]), + CR_CHAN(chanlist[i]), + CR_RANGE(chanlist[i]));) + if (chanlist[i] != chansegment[i % seglen]) { + printk(KERN_WARNING + "comedi%d: pcl816: bad channel or range" + " number! chanlist[%i]=%d,%d,%d and not" + " %d,%d,%d!\n", dev->minor, i, + CR_CHAN(chansegment[i]), + CR_RANGE(chansegment[i]), + CR_AREF(chansegment[i]), + CR_CHAN(chanlist[i % seglen]), + CR_RANGE(chanlist[i % seglen]), + CR_AREF(chansegment[i % seglen])); + return 0; /* chan/gain list is strange */ + } + } + } else { + seglen = 1; + } + + return seglen; /* we can serve this with MUX logic */ +} + +/* +============================================================================== + Program scan/gain logic with channel list. +*/ +static void +setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int *chanlist, + unsigned int seglen) +{ + unsigned int i; + + devpriv->ai_act_chanlist_len = seglen; + devpriv->ai_act_chanlist_pos = 0; + + for (i = 0; i < seglen; i++) { /* store range list to card */ + devpriv->ai_act_chanlist[i] = CR_CHAN(chanlist[i]); + outb(CR_CHAN(chanlist[0]) & 0xf, dev->iobase + PCL816_MUX); + /* select gain */ + outb(CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE); + } + + udelay(1); + /* select channel interval to scan */ + outb(devpriv->ai_act_chanlist[0] | + (devpriv->ai_act_chanlist[seglen - 1] << 4), + dev->iobase + PCL816_MUX); +} + +#ifdef unused +/* +============================================================================== + Enable(1)/disable(0) periodic interrupts from RTC +*/ +static int set_rtc_irq_bit(unsigned char bit) +{ + unsigned char val; + unsigned long flags; + + if (bit == 1) { + RTC_timer_lock++; + if (RTC_timer_lock > 1) + return 0; + } else { + RTC_timer_lock--; + if (RTC_timer_lock < 0) + RTC_timer_lock = 0; + if (RTC_timer_lock > 0) + return 0; + } + + save_flags(flags); + cli(); + val = CMOS_READ(RTC_CONTROL); + if (bit) + val |= RTC_PIE; + else + val &= ~RTC_PIE; + + CMOS_WRITE(val, RTC_CONTROL); + CMOS_READ(RTC_INTR_FLAGS); + restore_flags(flags); + return 0; +} +#endif + +/* +============================================================================== + Free any resources that we have claimed +*/ +static void free_resources(struct comedi_device *dev) +{ + /* printk("free_resource()\n"); */ + if (dev->private) { + pcl816_ai_cancel(dev, devpriv->sub_ai); + pcl816_reset(dev); + if (devpriv->dma) + free_dma(devpriv->dma); + if (devpriv->dmabuf[0]) + free_pages(devpriv->dmabuf[0], devpriv->dmapages[0]); + if (devpriv->dmabuf[1]) + free_pages(devpriv->dmabuf[1], devpriv->dmapages[1]); +#ifdef unused + if (devpriv->rtc_irq) + free_irq(devpriv->rtc_irq, dev); + if ((devpriv->dma_rtc) && (RTC_lock == 1)) { + if (devpriv->rtc_iobase) + release_region(devpriv->rtc_iobase, + devpriv->rtc_iosize); + } +#endif + } + + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->iobase) + release_region(dev->iobase, this_board->io_range); + /* printk("free_resource() end\n"); */ +} + +/* +============================================================================== + + Initialization + +*/ +static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + unsigned long iobase; + unsigned int irq, dma; + unsigned long pages; + /* int i; */ + struct comedi_subdevice *s; + + /* claim our I/O space */ + iobase = it->options[0]; + printk("comedi%d: pcl816: board=%s, ioport=0x%03lx", dev->minor, + this_board->name, iobase); + + if (!request_region(iobase, this_board->io_range, "pcl816")) { + printk("I/O port conflict\n"); + return -EIO; + } + + dev->iobase = iobase; + + if (pcl816_check(iobase)) { + printk(KERN_ERR ", I cann't detect board. FAIL!\n"); + return -EIO; + } + + ret = alloc_private(dev, sizeof(struct pcl816_private)); + if (ret < 0) + return ret; /* Can't alloc mem */ + + /* set up some name stuff */ + dev->board_name = this_board->name; + + /* grab our IRQ */ + irq = 0; + if (this_board->IRQbits != 0) { /* board support IRQ */ + irq = it->options[1]; + if (irq) { /* we want to use IRQ */ + if (((1 << irq) & this_board->IRQbits) == 0) { + printk + (", IRQ %u is out of allowed range, " + "DISABLING IT", irq); + irq = 0; /* Bad IRQ */ + } else { + if (request_irq + (irq, interrupt_pcl816, 0, "pcl816", dev)) { + printk + (", unable to allocate IRQ %u, " + "DISABLING IT", irq); + irq = 0; /* Can't use IRQ */ + } else { + printk(KERN_INFO ", irq=%u", irq); + } + } + } + } + + dev->irq = irq; + if (irq) /* 1=we have allocated irq */ + devpriv->irq_free = 1; + else + devpriv->irq_free = 0; + + devpriv->irq_blocked = 0; /* number of subdevice which use IRQ */ + devpriv->int816_mode = 0; /* mode of irq */ + +#ifdef unused + /* grab RTC for DMA operations */ + devpriv->dma_rtc = 0; + if (it->options[2] > 0) { /* we want to use DMA */ + if (RTC_lock == 0) { + if (!request_region(RTC_PORT(0), RTC_IO_EXTENT, + "pcl816 (RTC)")) + goto no_rtc; + } + devpriv->rtc_iobase = RTC_PORT(0); + devpriv->rtc_iosize = RTC_IO_EXTENT; + RTC_lock++; +#ifdef UNTESTED_CODE + if (!request_irq(RTC_IRQ, interrupt_pcl816_ai_mode13_dma_rtc, 0, + "pcl816 DMA (RTC)", dev)) { + devpriv->dma_rtc = 1; + devpriv->rtc_irq = RTC_IRQ; + printk(", dma_irq=%u", devpriv->rtc_irq); + } else { + RTC_lock--; + if (RTC_lock == 0) { + if (devpriv->rtc_iobase) + release_region(devpriv->rtc_iobase, + devpriv->rtc_iosize); + } + devpriv->rtc_iobase = 0; + devpriv->rtc_iosize = 0; + } +#else + printk("pcl816: RTC code missing"); +#endif + + } + +no_rtc: +#endif + /* grab our DMA */ + dma = 0; + devpriv->dma = dma; + if ((devpriv->irq_free == 0) && (devpriv->dma_rtc == 0)) + goto no_dma; /* if we haven't IRQ, we can't use DMA */ + + if (this_board->DMAbits != 0) { /* board support DMA */ + dma = it->options[2]; + if (dma < 1) + goto no_dma; /* DMA disabled */ + + if (((1 << dma) & this_board->DMAbits) == 0) { + printk(", DMA is out of allowed range, FAIL!\n"); + return -EINVAL; /* Bad DMA */ + } + ret = request_dma(dma, "pcl816"); + if (ret) { + printk(KERN_ERR + ", unable to allocate DMA %u, FAIL!\n", dma); + return -EBUSY; /* DMA isn't free */ + } + + devpriv->dma = dma; + printk(KERN_INFO ", dma=%u", dma); + pages = 2; /* we need 16KB */ + devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages); + + if (!devpriv->dmabuf[0]) { + printk(", unable to allocate DMA buffer, FAIL!\n"); + /* + * maybe experiment with try_to_free_pages() + * will help .... + */ + return -EBUSY; /* no buffer :-( */ + } + devpriv->dmapages[0] = pages; + devpriv->hwdmaptr[0] = virt_to_bus((void *)devpriv->dmabuf[0]); + devpriv->hwdmasize[0] = (1 << pages) * PAGE_SIZE; + /* printk("%d %d %ld, ",devpriv->dmapages[0],devpriv->hwdmasize[0],PAGE_SIZE); */ + + if (devpriv->dma_rtc == 0) { /* we must do duble buff :-( */ + devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages); + if (!devpriv->dmabuf[1]) { + printk(KERN_ERR + ", unable to allocate DMA buffer, " + "FAIL!\n"); + return -EBUSY; + } + devpriv->dmapages[1] = pages; + devpriv->hwdmaptr[1] = + virt_to_bus((void *)devpriv->dmabuf[1]); + devpriv->hwdmasize[1] = (1 << pages) * PAGE_SIZE; + } + } + +no_dma: + +/* if (this_board->n_aochan > 0) + subdevs[1] = COMEDI_SUBD_AO; + if (this_board->n_dichan > 0) + subdevs[2] = COMEDI_SUBD_DI; + if (this_board->n_dochan > 0) + subdevs[3] = COMEDI_SUBD_DO; +*/ + + ret = alloc_subdevices(dev, 1); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + if (this_board->n_aichan > 0) { + s->type = COMEDI_SUBD_AI; + devpriv->sub_ai = s; + dev->read_subdev = s; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = this_board->n_aichan; + s->subdev_flags |= SDF_DIFF; + /* printk (", %dchans DIFF DAC - %d", s->n_chan, i); */ + s->maxdata = this_board->ai_maxdata; + s->len_chanlist = this_board->ai_chanlist; + s->range_table = this_board->ai_range_type; + s->cancel = pcl816_ai_cancel; + s->do_cmdtest = pcl816_ai_cmdtest; + s->do_cmd = pcl816_ai_cmd; + s->poll = pcl816_ai_poll; + s->insn_read = pcl816_ai_insn_read; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + +#if 0 +case COMEDI_SUBD_AO: + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->ao_chanlist; + s->range_table = this_board->ao_range_type; + break; + +case COMEDI_SUBD_DI: + s->subdev_flags = SDF_READABLE; + s->n_chan = this_board->n_dichan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dichan; + s->range_table = &range_digital; + break; + +case COMEDI_SUBD_DO: + s->subdev_flags = SDF_WRITABLE; + s->n_chan = this_board->n_dochan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dochan; + s->range_table = &range_digital; + break; +#endif + + pcl816_reset(dev); + + printk("\n"); + + return 0; +} + +/* +============================================================================== + Removes device + */ +static int pcl816_detach(struct comedi_device *dev) +{ + DEBUG(printk(KERN_INFO "comedi%d: pcl816: remove\n", dev->minor);) + free_resources(dev); +#ifdef unused + if (devpriv->dma_rtc) + RTC_lock--; +#endif + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcl818.c b/drivers/staging/comedi/drivers/pcl818.c new file mode 100644 index 00000000..7344a53a --- /dev/null +++ b/drivers/staging/comedi/drivers/pcl818.c @@ -0,0 +1,2036 @@ +/* + comedi/drivers/pcl818.c + + Author: Michal Dobes + + hardware driver for Advantech cards: + card: PCL-818L, PCL-818H, PCL-818HD, PCL-818HG, PCL-818, PCL-718 + driver: pcl818l, pcl818h, pcl818hd, pcl818hg, pcl818, pcl718 +*/ +/* +Driver: pcl818 +Description: Advantech PCL-818 cards, PCL-718 +Author: Michal Dobes +Devices: [Advantech] PCL-818L (pcl818l), PCL-818H (pcl818h), + PCL-818HD (pcl818hd), PCL-818HG (pcl818hg), PCL-818 (pcl818), + PCL-718 (pcl718) +Status: works + +All cards have 16 SE/8 DIFF ADCs, one or two DACs, 16 DI and 16 DO. +Differences are only at maximal sample speed, range list and FIFO +support. +The driver support AI mode 0, 1, 3 other subdevices (AO, DI, DO) support +only mode 0. If DMA/FIFO/INT are disabled then AI support only mode 0. +PCL-818HD and PCL-818HG support 1kword FIFO. Driver support this FIFO +but this code is untested. +A word or two about DMA. Driver support DMA operations at two ways: +1) DMA uses two buffers and after one is filled then is generated + INT and DMA restart with second buffer. With this mode I'm unable run + more that 80Ksamples/secs without data dropouts on K6/233. +2) DMA uses one buffer and run in autoinit mode and the data are + from DMA buffer moved on the fly with 2kHz interrupts from RTC. + This mode is used if the interrupt 8 is available for allocation. + If not, then first DMA mode is used. With this I can run at + full speed one card (100ksamples/secs) or two cards with + 60ksamples/secs each (more is problem on account of ISA limitations). + To use this mode you must have compiled kernel with disabled + "Enhanced Real Time Clock Support". + Maybe you can have problems if you use xntpd or similar. + If you've data dropouts with DMA mode 2 then: + a) disable IDE DMA + b) switch text mode console to fb. + + Options for PCL-818L: + [0] - IO Base + [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + [2] - DMA (0=disable, 1, 3) + [3] - 0, 10=10MHz clock for 8254 + 1= 1MHz clock for 8254 + [4] - 0, 5=A/D input -5V.. +5V + 1, 10=A/D input -10V..+10V + [5] - 0, 5=D/A output 0-5V (internal reference -5V) + 1, 10=D/A output 0-10V (internal reference -10V) + 2 =D/A output unknown (external reference) + + Options for PCL-818, PCL-818H: + [0] - IO Base + [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + [2] - DMA (0=disable, 1, 3) + [3] - 0, 10=10MHz clock for 8254 + 1= 1MHz clock for 8254 + [4] - 0, 5=D/A output 0-5V (internal reference -5V) + 1, 10=D/A output 0-10V (internal reference -10V) + 2 =D/A output unknown (external reference) + + Options for PCL-818HD, PCL-818HG: + [0] - IO Base + [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + [2] - DMA/FIFO (-1=use FIFO, 0=disable both FIFO and DMA, + 1=use DMA ch 1, 3=use DMA ch 3) + [3] - 0, 10=10MHz clock for 8254 + 1= 1MHz clock for 8254 + [4] - 0, 5=D/A output 0-5V (internal reference -5V) + 1, 10=D/A output 0-10V (internal reference -10V) + 2 =D/A output unknown (external reference) + + Options for PCL-718: + [0] - IO Base + [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) + [2] - DMA (0=disable, 1, 3) + [3] - 0, 10=10MHz clock for 8254 + 1= 1MHz clock for 8254 + [4] - 0=A/D Range is +/-10V + 1= +/-5V + 2= +/-2.5V + 3= +/-1V + 4= +/-0.5V + 5= user defined bipolar + 6= 0-10V + 7= 0-5V + 8= 0-2V + 9= 0-1V + 10= user defined unipolar + [5] - 0, 5=D/A outputs 0-5V (internal reference -5V) + 1, 10=D/A outputs 0-10V (internal reference -10V) + 2=D/A outputs unknown (external reference) + [6] - 0, 60=max 60kHz A/D sampling + 1,100=max 100kHz A/D sampling (PCL-718 with Option 001 installed) + +*/ + +#include "../comedidev.h" + +#include +#include +#include +#include +#include +#include + +#include "8253.h" + +/* #define PCL818_MODE13_AO 1 */ + +/* boards constants */ + +#define boardPCL818L 0 +#define boardPCL818H 1 +#define boardPCL818HD 2 +#define boardPCL818HG 3 +#define boardPCL818 4 +#define boardPCL718 5 + +/* IO space len */ +#define PCLx1x_RANGE 16 +/* IO space len if we use FIFO */ +#define PCLx1xFIFO_RANGE 32 + +/* W: clear INT request */ +#define PCL818_CLRINT 8 +/* R: return status byte */ +#define PCL818_STATUS 8 +/* R: A/D high byte W: A/D range control */ +#define PCL818_RANGE 1 +/* R: next mux scan channel W: mux scan channel & range control pointer */ +#define PCL818_MUX 2 +/* R/W: operation control register */ +#define PCL818_CONTROL 9 +/* W: counter enable */ +#define PCL818_CNTENABLE 10 + +/* R: low byte of A/D W: soft A/D trigger */ +#define PCL818_AD_LO 0 +/* R: high byte of A/D W: A/D range control */ +#define PCL818_AD_HI 1 +/* W: D/A low&high byte */ +#define PCL818_DA_LO 4 +#define PCL818_DA_HI 5 +/* R: low&high byte of DI */ +#define PCL818_DI_LO 3 +#define PCL818_DI_HI 11 +/* W: low&high byte of DO */ +#define PCL818_DO_LO 3 +#define PCL818_DO_HI 11 +/* W: PCL718 second D/A */ +#define PCL718_DA2_LO 6 +#define PCL718_DA2_HI 7 +/* counters */ +#define PCL818_CTR0 12 +#define PCL818_CTR1 13 +#define PCL818_CTR2 14 +/* W: counter control */ +#define PCL818_CTRCTL 15 + +/* W: fifo enable/disable */ +#define PCL818_FI_ENABLE 6 +/* W: fifo interrupt clear */ +#define PCL818_FI_INTCLR 20 +/* W: fifo interrupt clear */ +#define PCL818_FI_FLUSH 25 +/* R: fifo status */ +#define PCL818_FI_STATUS 25 +/* R: one record from FIFO */ +#define PCL818_FI_DATALO 23 +#define PCL818_FI_DATAHI 23 + +/* type of interrupt handler */ +#define INT_TYPE_AI1_INT 1 +#define INT_TYPE_AI1_DMA 2 +#define INT_TYPE_AI1_FIFO 3 +#define INT_TYPE_AI3_INT 4 +#define INT_TYPE_AI3_DMA 5 +#define INT_TYPE_AI3_FIFO 6 +#ifdef PCL818_MODE13_AO +#define INT_TYPE_AO1_INT 7 +#define INT_TYPE_AO3_INT 8 +#endif + +#ifdef unused +/* RTC stuff... */ +#define INT_TYPE_AI1_DMA_RTC 9 +#define INT_TYPE_AI3_DMA_RTC 10 + +#define RTC_IRQ 8 +#define RTC_IO_EXTENT 0x10 +#endif + +#define MAGIC_DMA_WORD 0x5a5a + +static const struct comedi_lrange range_pcl818h_ai = { 9, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + UNI_RANGE(10), + UNI_RANGE(5), + UNI_RANGE(2.5), + UNI_RANGE(1.25), + BIP_RANGE(10), + } +}; + +static const struct comedi_lrange range_pcl818hg_ai = { 10, { + BIP_RANGE(5), + BIP_RANGE(0.5), + BIP_RANGE(0.05), + BIP_RANGE(0.005), + UNI_RANGE(10), + UNI_RANGE(1), + UNI_RANGE(0.1), + UNI_RANGE(0.01), + BIP_RANGE(10), + BIP_RANGE(1), + BIP_RANGE(0.1), + BIP_RANGE(0.01), + } +}; + +static const struct comedi_lrange range_pcl818l_l_ai = { 4, { + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + BIP_RANGE(0.625), + } +}; + +static const struct comedi_lrange range_pcl818l_h_ai = { 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25), + } +}; + +static const struct comedi_lrange range718_bipolar1 = { 1, {BIP_RANGE(1),} }; +static const struct comedi_lrange range718_bipolar0_5 = + { 1, {BIP_RANGE(0.5),} }; +static const struct comedi_lrange range718_unipolar2 = { 1, {UNI_RANGE(2),} }; +static const struct comedi_lrange range718_unipolar1 = { 1, {BIP_RANGE(1),} }; + +static int pcl818_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcl818_detach(struct comedi_device *dev); + +#ifdef unused +static int RTC_lock; /* RTC lock */ +static int RTC_timer_lock; /* RTC int lock */ +#endif + +struct pcl818_board { + + const char *name; /* driver name */ + int n_ranges; /* len of range list */ + int n_aichan_se; /* num of A/D chans in single ended mode */ + int n_aichan_diff; /* num of A/D chans in diferencial mode */ + unsigned int ns_min; /* minimal allowed delay between samples (in ns) */ + int n_aochan; /* num of D/A chans */ + int n_dichan; /* num of DI chans */ + int n_dochan; /* num of DO chans */ + const struct comedi_lrange *ai_range_type; /* default A/D rangelist */ + const struct comedi_lrange *ao_range_type; /* default D/A rangelist */ + unsigned int io_range; /* len of IO space */ + unsigned int IRQbits; /* allowed interrupts */ + unsigned int DMAbits; /* allowed DMA chans */ + int ai_maxdata; /* maxdata for A/D */ + int ao_maxdata; /* maxdata for D/A */ + unsigned char fifo; /* 1=board has FIFO */ + int is_818; +}; + +static const struct pcl818_board boardtypes[] = { + {"pcl818l", 4, 16, 8, 25000, 1, 16, 16, &range_pcl818l_l_ai, + &range_unipolar5, PCLx1x_RANGE, 0x00fc, + 0x0a, 0xfff, 0xfff, 0, 1}, + {"pcl818h", 9, 16, 8, 10000, 1, 16, 16, &range_pcl818h_ai, + &range_unipolar5, PCLx1x_RANGE, 0x00fc, + 0x0a, 0xfff, 0xfff, 0, 1}, + {"pcl818hd", 9, 16, 8, 10000, 1, 16, 16, &range_pcl818h_ai, + &range_unipolar5, PCLx1x_RANGE, 0x00fc, + 0x0a, 0xfff, 0xfff, 1, 1}, + {"pcl818hg", 12, 16, 8, 10000, 1, 16, 16, &range_pcl818hg_ai, + &range_unipolar5, PCLx1x_RANGE, 0x00fc, + 0x0a, 0xfff, 0xfff, 1, 1}, + {"pcl818", 9, 16, 8, 10000, 2, 16, 16, &range_pcl818h_ai, + &range_unipolar5, PCLx1x_RANGE, 0x00fc, + 0x0a, 0xfff, 0xfff, 0, 1}, + {"pcl718", 1, 16, 8, 16000, 2, 16, 16, &range_unipolar5, + &range_unipolar5, PCLx1x_RANGE, 0x00fc, + 0x0a, 0xfff, 0xfff, 0, 0}, + /* pcm3718 */ + {"pcm3718", 9, 16, 8, 10000, 0, 16, 16, &range_pcl818h_ai, + &range_unipolar5, PCLx1x_RANGE, 0x00fc, + 0x0a, 0xfff, 0xfff, 0, 1 /* XXX ? */ }, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl818_board)) + +static struct comedi_driver driver_pcl818 = { + .driver_name = "pcl818", + .module = THIS_MODULE, + .attach = pcl818_attach, + .detach = pcl818_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct pcl818_board), +}; + +static int __init driver_pcl818_init_module(void) +{ + return comedi_driver_register(&driver_pcl818); +} + +static void __exit driver_pcl818_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcl818); +} + +module_init(driver_pcl818_init_module); +module_exit(driver_pcl818_cleanup_module); + +struct pcl818_private { + + unsigned int dma; /* used DMA, 0=don't use DMA */ + int dma_rtc; /* 1=RTC used with DMA, 0=no RTC alloc */ + unsigned int io_range; +#ifdef unused + unsigned long rtc_iobase; /* RTC port region */ + unsigned int rtc_iosize; + unsigned int rtc_irq; + struct timer_list rtc_irq_timer; /* timer for RTC sanity check */ + unsigned long rtc_freq; /* RTC int freq */ + int rtc_irq_blocked; /* 1=we now do AI with DMA&RTC */ +#endif + unsigned long dmabuf[2]; /* pointers to begin of DMA buffers */ + unsigned int dmapages[2]; /* len of DMA buffers in PAGE_SIZEs */ + unsigned int hwdmaptr[2]; /* hardware address of DMA buffers */ + unsigned int hwdmasize[2]; /* len of DMA buffers in Bytes */ + unsigned int dmasamplsize; /* size in samples hwdmasize[0]/2 */ + unsigned int last_top_dma; /* DMA pointer in last RTC int */ + int next_dma_buf; /* which DMA buffer will be used next round */ + long dma_runs_to_end; /* how many we must permorm DMA transfer to end of record */ + unsigned long last_dma_run; /* how many bytes we must transfer on last DMA page */ + unsigned char neverending_ai; /* if=1, then we do neverending record (you must use cancel()) */ + unsigned int ns_min; /* manimal allowed delay between samples (in us) for actual card */ + int i8253_osc_base; /* 1/frequency of on board oscilator in ns */ + int irq_free; /* 1=have allocated IRQ */ + int irq_blocked; /* 1=IRQ now uses any subdev */ + int irq_was_now_closed; /* when IRQ finish, there's stored int818_mode for last interrupt */ + int ai_mode; /* who now uses IRQ - 1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma */ + struct comedi_subdevice *last_int_sub; /* ptr to subdevice which now finish */ + int ai_act_scan; /* how many scans we finished */ + int ai_act_chan; /* actual position in actual scan */ + unsigned int act_chanlist[16]; /* MUX setting for actual AI operations */ + unsigned int act_chanlist_len; /* how long is actual MUX list */ + unsigned int act_chanlist_pos; /* actual position in MUX list */ + unsigned int ai_scans; /* len of scanlist */ + unsigned int ai_n_chan; /* how many channels is measured */ + unsigned int *ai_chanlist; /* actaul chanlist */ + unsigned int ai_flags; /* flaglist */ + unsigned int ai_data_len; /* len of data buffer */ + short *ai_data; /* data buffer */ + unsigned int ai_timer1; /* timers */ + unsigned int ai_timer2; + struct comedi_subdevice *sub_ai; /* ptr to AI subdevice */ + unsigned char usefifo; /* 1=use fifo */ + unsigned int ao_readback[2]; +}; + +static const unsigned int muxonechan[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, /* used for gain list programming */ + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff +}; + +#define devpriv ((struct pcl818_private *)dev->private) +#define this_board ((const struct pcl818_board *)dev->board_ptr) + +/* +============================================================================== +*/ +static void setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan, + unsigned int seglen); +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan); + +static int pcl818_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2); + +#ifdef unused +static int set_rtc_irq_bit(unsigned char bit); +static void rtc_dropped_irq(unsigned long data); +static int rtc_setfreq_irq(int freq); +#endif + +/* +============================================================================== + ANALOG INPUT MODE0, 818 cards, slow version +*/ +static int pcl818_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int timeout; + + /* software trigger, DMA and INT off */ + outb(0, dev->iobase + PCL818_CONTROL); + + /* select channel */ + outb(muxonechan[CR_CHAN(insn->chanspec)], dev->iobase + PCL818_MUX); + + /* select gain */ + outb(CR_RANGE(insn->chanspec), dev->iobase + PCL818_RANGE); + + for (n = 0; n < insn->n; n++) { + + /* clear INT (conversion end) flag */ + outb(0, dev->iobase + PCL818_CLRINT); + + /* start conversion */ + outb(0, dev->iobase + PCL818_AD_LO); + + timeout = 100; + while (timeout--) { + if (inb(dev->iobase + PCL818_STATUS) & 0x10) + goto conv_finish; + udelay(1); + } + comedi_error(dev, "A/D insn timeout"); + /* clear INT (conversion end) flag */ + outb(0, dev->iobase + PCL818_CLRINT); + return -EIO; + +conv_finish: + data[n] = ((inb(dev->iobase + PCL818_AD_HI) << 4) | + (inb(dev->iobase + PCL818_AD_LO) >> 4)); + } + + return n; +} + +/* +============================================================================== + ANALOG OUTPUT MODE0, 818 cards + only one sample per call is supported +*/ +static int pcl818_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_readback[chan]; + + return n; +} + +static int pcl818_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) { + devpriv->ao_readback[chan] = data[n]; + outb((data[n] & 0x000f) << 4, dev->iobase + + (chan ? PCL718_DA2_LO : PCL818_DA_LO)); + outb((data[n] & 0x0ff0) >> 4, dev->iobase + + (chan ? PCL718_DA2_HI : PCL818_DA_HI)); + } + + return n; +} + +/* +============================================================================== + DIGITAL INPUT MODE0, 818 cards + + only one sample per call is supported +*/ +static int pcl818_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + PCL818_DI_LO) | + (inb(dev->iobase + PCL818_DI_HI) << 8); + + return 2; +} + +/* +============================================================================== + DIGITAL OUTPUT MODE0, 818 cards + + only one sample per call is supported +*/ +static int pcl818_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + + outb(s->state & 0xff, dev->iobase + PCL818_DO_LO); + outb((s->state >> 8), dev->iobase + PCL818_DO_HI); + + data[1] = s->state; + + return 2; +} + +/* +============================================================================== + analog input interrupt mode 1 & 3, 818 cards + one sample per interrupt version +*/ +static irqreturn_t interrupt_pcl818_ai_mode13_int(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + int low; + int timeout = 50; /* wait max 50us */ + + while (timeout--) { + if (inb(dev->iobase + PCL818_STATUS) & 0x10) + goto conv_finish; + udelay(1); + } + outb(0, dev->iobase + PCL818_STATUS); /* clear INT request */ + comedi_error(dev, "A/D mode1/3 IRQ without DRDY!"); + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + +conv_finish: + low = inb(dev->iobase + PCL818_AD_LO); + comedi_buf_put(s->async, ((inb(dev->iobase + PCL818_AD_HI) << 4) | (low >> 4))); /* get one sample */ + outb(0, dev->iobase + PCL818_CLRINT); /* clear INT request */ + + if ((low & 0xf) != devpriv->act_chanlist[devpriv->act_chanlist_pos]) { /* dropout! */ + printk + ("comedi: A/D mode1/3 IRQ - channel dropout %x!=%x !\n", + (low & 0xf), + devpriv->act_chanlist[devpriv->act_chanlist_pos]); + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + } + devpriv->act_chanlist_pos++; + if (devpriv->act_chanlist_pos >= devpriv->act_chanlist_len) + devpriv->act_chanlist_pos = 0; + + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_chan) { + /* printk("E"); */ + s->async->cur_chan = 0; + devpriv->ai_act_scan--; + } + + if (!devpriv->neverending_ai) { + if (devpriv->ai_act_scan == 0) { /* all data sampled */ + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + } + } + comedi_event(dev, s); + return IRQ_HANDLED; +} + +/* +============================================================================== + analog input dma mode 1 & 3, 818 cards +*/ +static irqreturn_t interrupt_pcl818_ai_mode13_dma(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + int i, len, bufptr; + unsigned long flags; + short *ptr; + + disable_dma(devpriv->dma); + devpriv->next_dma_buf = 1 - devpriv->next_dma_buf; + if ((devpriv->dma_runs_to_end) > -1 || devpriv->neverending_ai) { /* switch dma bufs */ + set_dma_mode(devpriv->dma, DMA_MODE_READ); + flags = claim_dma_lock(); + set_dma_addr(devpriv->dma, + devpriv->hwdmaptr[devpriv->next_dma_buf]); + if (devpriv->dma_runs_to_end || devpriv->neverending_ai) { + set_dma_count(devpriv->dma, + devpriv->hwdmasize[devpriv-> + next_dma_buf]); + } else { + set_dma_count(devpriv->dma, devpriv->last_dma_run); + } + release_dma_lock(flags); + enable_dma(devpriv->dma); + } + printk("comedi: A/D mode1/3 IRQ \n"); + + devpriv->dma_runs_to_end--; + outb(0, dev->iobase + PCL818_CLRINT); /* clear INT request */ + ptr = (short *)devpriv->dmabuf[1 - devpriv->next_dma_buf]; + + len = devpriv->hwdmasize[0] >> 1; + bufptr = 0; + + for (i = 0; i < len; i++) { + if ((ptr[bufptr] & 0xf) != devpriv->act_chanlist[devpriv->act_chanlist_pos]) { /* dropout! */ + printk + ("comedi: A/D mode1/3 DMA - channel dropout %d(card)!=%d(chanlist) at %d !\n", + (ptr[bufptr] & 0xf), + devpriv->act_chanlist[devpriv->act_chanlist_pos], + devpriv->act_chanlist_pos); + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + } + + comedi_buf_put(s->async, ptr[bufptr++] >> 4); /* get one sample */ + + devpriv->act_chanlist_pos++; + if (devpriv->act_chanlist_pos >= devpriv->act_chanlist_len) + devpriv->act_chanlist_pos = 0; + + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_chan) { + s->async->cur_chan = 0; + devpriv->ai_act_scan--; + } + + if (!devpriv->neverending_ai) + if (devpriv->ai_act_scan == 0) { /* all data sampled */ + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + /* printk("done int ai13 dma\n"); */ + return IRQ_HANDLED; + } + } + + if (len > 0) + comedi_event(dev, s); + return IRQ_HANDLED; +} + +#ifdef unused +/* +============================================================================== + analog input dma mode 1 & 3 over RTC, 818 cards +*/ +static irqreturn_t interrupt_pcl818_ai_mode13_dma_rtc(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + unsigned long tmp; + unsigned int top1, top2, i, bufptr; + long ofs_dats; + short *dmabuf = (short *)devpriv->dmabuf[0]; + + /* outb(2,0x378); */ + switch (devpriv->ai_mode) { + case INT_TYPE_AI1_DMA_RTC: + case INT_TYPE_AI3_DMA_RTC: + tmp = (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); + mod_timer(&devpriv->rtc_irq_timer, + jiffies + HZ / devpriv->rtc_freq + 2 * HZ / 100); + + for (i = 0; i < 10; i++) { + top1 = get_dma_residue(devpriv->dma); + top2 = get_dma_residue(devpriv->dma); + if (top1 == top2) + break; + } + + if (top1 != top2) + return IRQ_HANDLED; + top1 = devpriv->hwdmasize[0] - top1; /* where is now DMA in buffer */ + top1 >>= 1; + ofs_dats = top1 - devpriv->last_top_dma; /* new samples from last call */ + if (ofs_dats < 0) + ofs_dats = (devpriv->dmasamplsize) + ofs_dats; + if (!ofs_dats) + return IRQ_HANDLED; /* exit=no new samples from last call */ + /* obsluz data */ + i = devpriv->last_top_dma - 1; + i &= (devpriv->dmasamplsize - 1); + + if (dmabuf[i] != MAGIC_DMA_WORD) { /* DMA overflow! */ + comedi_error(dev, "A/D mode1/3 DMA buffer overflow!"); + /* printk("I %d dmabuf[i] %d %d\n",i,dmabuf[i],devpriv->dmasamplsize); */ + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + } + /* printk("r %ld ",ofs_dats); */ + + bufptr = devpriv->last_top_dma; + + for (i = 0; i < ofs_dats; i++) { + if ((dmabuf[bufptr] & 0xf) != devpriv->act_chanlist[devpriv->act_chanlist_pos]) { /* dropout! */ + printk + ("comedi: A/D mode1/3 DMA - channel dropout %d!=%d !\n", + (dmabuf[bufptr] & 0xf), + devpriv-> + act_chanlist[devpriv->act_chanlist_pos]); + pcl818_ai_cancel(dev, s); + s->async->events |= + COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + } + + comedi_buf_put(s->async, dmabuf[bufptr++] >> 4); /* get one sample */ + bufptr &= (devpriv->dmasamplsize - 1); + + devpriv->act_chanlist_pos++; + if (devpriv->act_chanlist_pos >= + devpriv->act_chanlist_len) { + devpriv->act_chanlist_pos = 0; + } + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_chan) { + s->async->cur_chan = 0; + devpriv->ai_act_scan--; + } + + if (!devpriv->neverending_ai) + if (devpriv->ai_act_scan == 0) { /* all data sampled */ + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + /* printk("done int ai13 dma\n"); */ + return IRQ_HANDLED; + } + } + + devpriv->last_top_dma = bufptr; + bufptr--; + bufptr &= (devpriv->dmasamplsize - 1); + dmabuf[bufptr] = MAGIC_DMA_WORD; + comedi_event(dev, s); + /* outb(0,0x378); */ + return IRQ_HANDLED; + } + + /* outb(0,0x378); */ + return IRQ_HANDLED; +} +#endif + +/* +============================================================================== + analog input interrupt mode 1 & 3, 818HD/HG cards +*/ +static irqreturn_t interrupt_pcl818_ai_mode13_fifo(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s = dev->subdevices + 0; + int i, len, lo; + + outb(0, dev->iobase + PCL818_FI_INTCLR); /* clear fifo int request */ + + lo = inb(dev->iobase + PCL818_FI_STATUS); + + if (lo & 4) { + comedi_error(dev, "A/D mode1/3 FIFO overflow!"); + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + } + + if (lo & 1) { + comedi_error(dev, "A/D mode1/3 FIFO interrupt without data!"); + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + } + + if (lo & 2) + len = 512; + else + len = 0; + + for (i = 0; i < len; i++) { + lo = inb(dev->iobase + PCL818_FI_DATALO); + if ((lo & 0xf) != devpriv->act_chanlist[devpriv->act_chanlist_pos]) { /* dropout! */ + printk + ("comedi: A/D mode1/3 FIFO - channel dropout %d!=%d !\n", + (lo & 0xf), + devpriv->act_chanlist[devpriv->act_chanlist_pos]); + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + comedi_event(dev, s); + return IRQ_HANDLED; + } + + comedi_buf_put(s->async, (lo >> 4) | (inb(dev->iobase + PCL818_FI_DATAHI) << 4)); /* get one sample */ + + devpriv->act_chanlist_pos++; + if (devpriv->act_chanlist_pos >= devpriv->act_chanlist_len) + devpriv->act_chanlist_pos = 0; + + s->async->cur_chan++; + if (s->async->cur_chan >= devpriv->ai_n_chan) { + s->async->cur_chan = 0; + devpriv->ai_act_scan--; + } + + if (!devpriv->neverending_ai) + if (devpriv->ai_act_scan == 0) { /* all data sampled */ + pcl818_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + comedi_event(dev, s); + return IRQ_HANDLED; + } + } + + if (len > 0) + comedi_event(dev, s); + return IRQ_HANDLED; +} + +/* +============================================================================== + INT procedure +*/ +static irqreturn_t interrupt_pcl818(int irq, void *d) +{ + struct comedi_device *dev = d; + + if (!dev->attached) { + comedi_error(dev, "premature interrupt"); + return IRQ_HANDLED; + } + /* printk("I\n"); */ + + if (devpriv->irq_blocked && devpriv->irq_was_now_closed) { + if ((devpriv->neverending_ai || (!devpriv->neverending_ai && + devpriv->ai_act_scan > 0)) && + (devpriv->ai_mode == INT_TYPE_AI1_DMA || + devpriv->ai_mode == INT_TYPE_AI3_DMA)) { + /* The cleanup from ai_cancel() has been delayed + until now because the card doesn't seem to like + being reprogrammed while a DMA transfer is in + progress. + */ + struct comedi_subdevice *s = dev->subdevices + 0; + devpriv->ai_act_scan = 0; + devpriv->neverending_ai = 0; + pcl818_ai_cancel(dev, s); + } + + outb(0, dev->iobase + PCL818_CLRINT); /* clear INT request */ + + return IRQ_HANDLED; + } + + switch (devpriv->ai_mode) { + case INT_TYPE_AI1_DMA: + case INT_TYPE_AI3_DMA: + return interrupt_pcl818_ai_mode13_dma(irq, d); + case INT_TYPE_AI1_INT: + case INT_TYPE_AI3_INT: + return interrupt_pcl818_ai_mode13_int(irq, d); + case INT_TYPE_AI1_FIFO: + case INT_TYPE_AI3_FIFO: + return interrupt_pcl818_ai_mode13_fifo(irq, d); +#ifdef PCL818_MODE13_AO + case INT_TYPE_AO1_INT: + case INT_TYPE_AO3_INT: + return interrupt_pcl818_ao_mode13_int(irq, d); +#endif + default: + break; + } + + outb(0, dev->iobase + PCL818_CLRINT); /* clear INT request */ + + if ((!dev->irq) || (!devpriv->irq_free) || (!devpriv->irq_blocked) + || (!devpriv->ai_mode)) { + comedi_error(dev, "bad IRQ!"); + return IRQ_NONE; + } + + comedi_error(dev, "IRQ from unknown source!"); + return IRQ_NONE; +} + +/* +============================================================================== + ANALOG INPUT MODE 1 or 3 DMA , 818 cards +*/ +static void pcl818_ai_mode13dma_int(int mode, struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int flags; + unsigned int bytes; + + printk("mode13dma_int, mode: %d\n", mode); + disable_dma(devpriv->dma); /* disable dma */ + bytes = devpriv->hwdmasize[0]; + if (!devpriv->neverending_ai) { + bytes = devpriv->ai_n_chan * devpriv->ai_scans * sizeof(short); /* how many */ + devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize[0]; /* how many DMA pages we must fiil */ + devpriv->last_dma_run = bytes % devpriv->hwdmasize[0]; /* on last dma transfer must be moved */ + devpriv->dma_runs_to_end--; + if (devpriv->dma_runs_to_end >= 0) + bytes = devpriv->hwdmasize[0]; + } + + devpriv->next_dma_buf = 0; + set_dma_mode(devpriv->dma, DMA_MODE_READ); + flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]); + set_dma_count(devpriv->dma, bytes); + release_dma_lock(flags); + enable_dma(devpriv->dma); + + if (mode == 1) { + devpriv->ai_mode = INT_TYPE_AI1_DMA; + outb(0x87 | (dev->irq << 4), dev->iobase + PCL818_CONTROL); /* Pacer+IRQ+DMA */ + } else { + devpriv->ai_mode = INT_TYPE_AI3_DMA; + outb(0x86 | (dev->irq << 4), dev->iobase + PCL818_CONTROL); /* Ext trig+IRQ+DMA */ + }; +} + +#ifdef unused +/* +============================================================================== + ANALOG INPUT MODE 1 or 3 DMA rtc, 818 cards +*/ +static void pcl818_ai_mode13dma_rtc(int mode, struct comedi_device *dev, + struct comedi_subdevice *s) +{ + unsigned int flags; + short *pole; + + set_dma_mode(devpriv->dma, DMA_MODE_READ | DMA_AUTOINIT); + flags = claim_dma_lock(); + clear_dma_ff(devpriv->dma); + set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]); + set_dma_count(devpriv->dma, devpriv->hwdmasize[0]); + release_dma_lock(flags); + enable_dma(devpriv->dma); + devpriv->last_top_dma = 0; /* devpriv->hwdmasize[0]; */ + pole = (short *)devpriv->dmabuf[0]; + devpriv->dmasamplsize = devpriv->hwdmasize[0] / 2; + pole[devpriv->dmasamplsize - 1] = MAGIC_DMA_WORD; +#ifdef unused + devpriv->rtc_freq = rtc_setfreq_irq(2048); + devpriv->rtc_irq_timer.expires = + jiffies + HZ / devpriv->rtc_freq + 2 * HZ / 100; + devpriv->rtc_irq_timer.data = (unsigned long)dev; + devpriv->rtc_irq_timer.function = rtc_dropped_irq; + + add_timer(&devpriv->rtc_irq_timer); +#endif + + if (mode == 1) { + devpriv->int818_mode = INT_TYPE_AI1_DMA_RTC; + outb(0x07 | (dev->irq << 4), dev->iobase + PCL818_CONTROL); /* Pacer+DMA */ + } else { + devpriv->int818_mode = INT_TYPE_AI3_DMA_RTC; + outb(0x06 | (dev->irq << 4), dev->iobase + PCL818_CONTROL); /* Ext trig+DMA */ + }; +} +#endif + +/* +============================================================================== + ANALOG INPUT MODE 1 or 3, 818 cards +*/ +static int pcl818_ai_cmd_mode(int mode, struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int divisor1 = 0, divisor2 = 0; + unsigned int seglen; + + dev_dbg(dev->hw_dev, "pcl818_ai_cmd_mode()\n"); + if ((!dev->irq) && (!devpriv->dma_rtc)) { + comedi_error(dev, "IRQ not defined!"); + return -EINVAL; + } + + if (devpriv->irq_blocked) + return -EBUSY; + + start_pacer(dev, -1, 0, 0); /* stop pacer */ + + seglen = check_channel_list(dev, s, devpriv->ai_chanlist, + devpriv->ai_n_chan); + if (seglen < 1) + return -EINVAL; + setup_channel_list(dev, s, devpriv->ai_chanlist, + devpriv->ai_n_chan, seglen); + + udelay(1); + + devpriv->ai_act_scan = devpriv->ai_scans; + devpriv->ai_act_chan = 0; + devpriv->irq_blocked = 1; + devpriv->irq_was_now_closed = 0; + devpriv->neverending_ai = 0; + devpriv->act_chanlist_pos = 0; + devpriv->dma_runs_to_end = 0; + + if ((devpriv->ai_scans == 0) || (devpriv->ai_scans == -1)) + devpriv->neverending_ai = 1; /* well, user want neverending */ + + if (mode == 1) { + i8253_cascade_ns_to_timer(devpriv->i8253_osc_base, &divisor1, + &divisor2, &cmd->convert_arg, + TRIG_ROUND_NEAREST); + if (divisor1 == 1) { /* PCL718/818 crash if any divisor is set to 1 */ + divisor1 = 2; + divisor2 /= 2; + } + if (divisor2 == 1) { + divisor2 = 2; + divisor1 /= 2; + } + } + + outb(0, dev->iobase + PCL818_CNTENABLE); /* enable pacer */ + + switch (devpriv->dma) { + case 1: /* DMA */ + case 3: + if (devpriv->dma_rtc == 0) { + pcl818_ai_mode13dma_int(mode, dev, s); + } +#ifdef unused + else { + pcl818_ai_mode13dma_rtc(mode, dev, s); + } +#else + else { + return -EINVAL; + } +#endif + break; + case 0: + if (!devpriv->usefifo) { + /* IRQ */ + /* printk("IRQ\n"); */ + if (mode == 1) { + devpriv->ai_mode = INT_TYPE_AI1_INT; + /* Pacer+IRQ */ + outb(0x83 | (dev->irq << 4), + dev->iobase + PCL818_CONTROL); + } else { + devpriv->ai_mode = INT_TYPE_AI3_INT; + /* Ext trig+IRQ */ + outb(0x82 | (dev->irq << 4), + dev->iobase + PCL818_CONTROL); + } + } else { + /* FIFO */ + /* enable FIFO */ + outb(1, dev->iobase + PCL818_FI_ENABLE); + if (mode == 1) { + devpriv->ai_mode = INT_TYPE_AI1_FIFO; + /* Pacer */ + outb(0x03, dev->iobase + PCL818_CONTROL); + } else { + devpriv->ai_mode = INT_TYPE_AI3_FIFO; + outb(0x02, dev->iobase + PCL818_CONTROL); + } + } + } + + start_pacer(dev, mode, divisor1, divisor2); + +#ifdef unused + switch (devpriv->ai_mode) { + case INT_TYPE_AI1_DMA_RTC: + case INT_TYPE_AI3_DMA_RTC: + set_rtc_irq_bit(1); /* start RTC */ + break; + } +#endif + dev_dbg(dev->hw_dev, "pcl818_ai_cmd_mode() end\n"); + return 0; +} + +#ifdef unused +/* +============================================================================== + ANALOG OUTPUT MODE 1 or 3, 818 cards +*/ +#ifdef PCL818_MODE13_AO +static int pcl818_ao_mode13(int mode, struct comedi_device *dev, + struct comedi_subdevice *s, comedi_trig * it) +{ + int divisor1 = 0, divisor2 = 0; + + if (!dev->irq) { + comedi_error(dev, "IRQ not defined!"); + return -EINVAL; + } + + if (devpriv->irq_blocked) + return -EBUSY; + + start_pacer(dev, -1, 0, 0); /* stop pacer */ + + devpriv->int13_act_scan = it->n; + devpriv->int13_act_chan = 0; + devpriv->irq_blocked = 1; + devpriv->irq_was_now_closed = 0; + devpriv->neverending_ai = 0; + devpriv->act_chanlist_pos = 0; + + if (mode == 1) { + i8253_cascade_ns_to_timer(devpriv->i8253_osc_base, &divisor1, + &divisor2, &it->trigvar, + TRIG_ROUND_NEAREST); + if (divisor1 == 1) { /* PCL818 crash if any divisor is set to 1 */ + divisor1 = 2; + divisor2 /= 2; + } + if (divisor2 == 1) { + divisor2 = 2; + divisor1 /= 2; + } + } + + outb(0, dev->iobase + PCL818_CNTENABLE); /* enable pacer */ + if (mode == 1) { + devpriv->int818_mode = INT_TYPE_AO1_INT; + outb(0x83 | (dev->irq << 4), dev->iobase + PCL818_CONTROL); /* Pacer+IRQ */ + } else { + devpriv->int818_mode = INT_TYPE_AO3_INT; + outb(0x82 | (dev->irq << 4), dev->iobase + PCL818_CONTROL); /* Ext trig+IRQ */ + }; + + start_pacer(dev, mode, divisor1, divisor2); + + return 0; +} + +/* +============================================================================== + ANALOG OUTPUT MODE 1, 818 cards +*/ +static int pcl818_ao_mode1(struct comedi_device *dev, + struct comedi_subdevice *s, comedi_trig * it) +{ + return pcl818_ao_mode13(1, dev, s, it); +} + +/* +============================================================================== + ANALOG OUTPUT MODE 3, 818 cards +*/ +static int pcl818_ao_mode3(struct comedi_device *dev, + struct comedi_subdevice *s, comedi_trig * it) +{ + return pcl818_ao_mode13(3, dev, s, it); +} +#endif +#endif + +/* +============================================================================== + Start/stop pacer onboard pacer +*/ +static void start_pacer(struct comedi_device *dev, int mode, + unsigned int divisor1, unsigned int divisor2) +{ + outb(0xb4, dev->iobase + PCL818_CTRCTL); + outb(0x74, dev->iobase + PCL818_CTRCTL); + udelay(1); + + if (mode == 1) { + outb(divisor2 & 0xff, dev->iobase + PCL818_CTR2); + outb((divisor2 >> 8) & 0xff, dev->iobase + PCL818_CTR2); + outb(divisor1 & 0xff, dev->iobase + PCL818_CTR1); + outb((divisor1 >> 8) & 0xff, dev->iobase + PCL818_CTR1); + } +} + +/* +============================================================================== + Check if channel list from user is builded correctly + If it's ok, then program scan/gain logic +*/ +static int check_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan) +{ + unsigned int chansegment[16]; + unsigned int i, nowmustbechan, seglen, segpos; + + /* correct channel and range number check itself comedi/range.c */ + if (n_chan < 1) { + comedi_error(dev, "range/channel list is empty!"); + return 0; + } + + if (n_chan > 1) { + /* first channel is every time ok */ + chansegment[0] = chanlist[0]; + /* build part of chanlist */ + for (i = 1, seglen = 1; i < n_chan; i++, seglen++) { + + /* printk("%d. %d * %d\n",i, + * CR_CHAN(it->chanlist[i]),CR_RANGE(it->chanlist[i]));*/ + + /* we detect loop, this must by finish */ + + if (chanlist[0] == chanlist[i]) + break; + nowmustbechan = + (CR_CHAN(chansegment[i - 1]) + 1) % s->n_chan; + if (nowmustbechan != CR_CHAN(chanlist[i])) { /* channel list isn't continuous :-( */ + printk + ("comedi%d: pcl818: channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n", + dev->minor, i, CR_CHAN(chanlist[i]), + nowmustbechan, CR_CHAN(chanlist[0])); + return 0; + } + /* well, this is next correct channel in list */ + chansegment[i] = chanlist[i]; + } + + /* check whole chanlist */ + for (i = 0, segpos = 0; i < n_chan; i++) { + /* printk("%d %d=%d %d\n",CR_CHAN(chansegment[i%seglen]),CR_RANGE(chansegment[i%seglen]),CR_CHAN(it->chanlist[i]),CR_RANGE(it->chanlist[i])); */ + if (chanlist[i] != chansegment[i % seglen]) { + printk + ("comedi%d: pcl818: bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", + dev->minor, i, CR_CHAN(chansegment[i]), + CR_RANGE(chansegment[i]), + CR_AREF(chansegment[i]), + CR_CHAN(chanlist[i % seglen]), + CR_RANGE(chanlist[i % seglen]), + CR_AREF(chansegment[i % seglen])); + return 0; /* chan/gain list is strange */ + } + } + } else { + seglen = 1; + } + printk("check_channel_list: seglen %d\n", seglen); + return seglen; +} + +static void setup_channel_list(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int *chanlist, unsigned int n_chan, + unsigned int seglen) +{ + int i; + + devpriv->act_chanlist_len = seglen; + devpriv->act_chanlist_pos = 0; + + for (i = 0; i < seglen; i++) { /* store range list to card */ + devpriv->act_chanlist[i] = CR_CHAN(chanlist[i]); + outb(muxonechan[CR_CHAN(chanlist[i])], dev->iobase + PCL818_MUX); /* select channel */ + outb(CR_RANGE(chanlist[i]), dev->iobase + PCL818_RANGE); /* select gain */ + } + + udelay(1); + + /* select channel interval to scan */ + outb(devpriv->act_chanlist[0] | (devpriv->act_chanlist[seglen - + 1] << 4), + dev->iobase + PCL818_MUX); +} + +/* +============================================================================== + Check if board is switched to SE (1) or DIFF(0) mode +*/ +static int check_single_ended(unsigned int port) +{ + if (inb(port + PCL818_STATUS) & 0x20) + return 1; + return 0; +} + +/* +============================================================================== +*/ +static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0; + int tmp, divisor1 = 0, divisor2 = 0; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->start_src != TRIG_NOW) { + cmd->start_src = TRIG_NOW; + err++; + } + if (cmd->scan_begin_src != TRIG_FOLLOW) { + cmd->scan_begin_src = TRIG_FOLLOW; + err++; + } + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + + if (cmd->scan_end_src != TRIG_COUNT) { + cmd->scan_end_src = TRIG_COUNT; + err++; + } + + if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < this_board->ns_min) { + cmd->convert_arg = this_board->ns_min; + err++; + } + } else { /* TRIG_EXT */ + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + i8253_cascade_ns_to_timer(devpriv->i8253_osc_base, &divisor1, + &divisor2, &cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (cmd->convert_arg < this_board->ns_min) + cmd->convert_arg = this_board->ns_min; + if (tmp != cmd->convert_arg) + err++; + } + + if (err) + return 4; + + /* step 5: complain about special chanlist considerations */ + + if (cmd->chanlist) { + if (!check_channel_list(dev, s, cmd->chanlist, + cmd->chanlist_len)) + return 5; /* incorrect channels list */ + } + + return 0; +} + +/* +============================================================================== +*/ +static int ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int retval; + + dev_dbg(dev->hw_dev, "pcl818_ai_cmd()\n"); + devpriv->ai_n_chan = cmd->chanlist_len; + devpriv->ai_chanlist = cmd->chanlist; + devpriv->ai_flags = cmd->flags; + devpriv->ai_data_len = s->async->prealloc_bufsz; + devpriv->ai_data = s->async->prealloc_buf; + devpriv->ai_timer1 = 0; + devpriv->ai_timer2 = 0; + + if (cmd->stop_src == TRIG_COUNT) + devpriv->ai_scans = cmd->stop_arg; + else + devpriv->ai_scans = 0; + + if (cmd->scan_begin_src == TRIG_FOLLOW) { /* mode 1, 3 */ + if (cmd->convert_src == TRIG_TIMER) { /* mode 1 */ + devpriv->ai_timer1 = cmd->convert_arg; + retval = pcl818_ai_cmd_mode(1, dev, s); + dev_dbg(dev->hw_dev, "pcl818_ai_cmd() end\n"); + return retval; + } + if (cmd->convert_src == TRIG_EXT) { /* mode 3 */ + return pcl818_ai_cmd_mode(3, dev, s); + } + } + + return -1; +} + +/* +============================================================================== + cancel any mode 1-4 AI +*/ +static int pcl818_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + if (devpriv->irq_blocked > 0) { + dev_dbg(dev->hw_dev, "pcl818_ai_cancel()\n"); + devpriv->irq_was_now_closed = 1; + + switch (devpriv->ai_mode) { +#ifdef unused + case INT_TYPE_AI1_DMA_RTC: + case INT_TYPE_AI3_DMA_RTC: + set_rtc_irq_bit(0); /* stop RTC */ + del_timer(&devpriv->rtc_irq_timer); +#endif + case INT_TYPE_AI1_DMA: + case INT_TYPE_AI3_DMA: + if (devpriv->neverending_ai || + (!devpriv->neverending_ai && + devpriv->ai_act_scan > 0)) { + /* wait for running dma transfer to end, do cleanup in interrupt */ + goto end; + } + disable_dma(devpriv->dma); + case INT_TYPE_AI1_INT: + case INT_TYPE_AI3_INT: + case INT_TYPE_AI1_FIFO: + case INT_TYPE_AI3_FIFO: +#ifdef PCL818_MODE13_AO + case INT_TYPE_AO1_INT: + case INT_TYPE_AO3_INT: +#endif + outb(inb(dev->iobase + PCL818_CONTROL) & 0x73, dev->iobase + PCL818_CONTROL); /* Stop A/D */ + udelay(1); + start_pacer(dev, -1, 0, 0); + outb(0, dev->iobase + PCL818_AD_LO); + inb(dev->iobase + PCL818_AD_LO); + inb(dev->iobase + PCL818_AD_HI); + outb(0, dev->iobase + PCL818_CLRINT); /* clear INT request */ + outb(0, dev->iobase + PCL818_CONTROL); /* Stop A/D */ + if (devpriv->usefifo) { /* FIFO shutdown */ + outb(0, dev->iobase + PCL818_FI_INTCLR); + outb(0, dev->iobase + PCL818_FI_FLUSH); + outb(0, dev->iobase + PCL818_FI_ENABLE); + } + devpriv->irq_blocked = 0; + devpriv->last_int_sub = s; + devpriv->neverending_ai = 0; + devpriv->ai_mode = 0; + devpriv->irq_was_now_closed = 0; + break; + } + } + +end: + dev_dbg(dev->hw_dev, "pcl818_ai_cancel() end\n"); + return 0; +} + +/* +============================================================================== + chech for PCL818 +*/ +static int pcl818_check(unsigned long iobase) +{ + outb(0x00, iobase + PCL818_MUX); + udelay(1); + if (inb(iobase + PCL818_MUX) != 0x00) + return 1; /* there isn't card */ + outb(0x55, iobase + PCL818_MUX); + udelay(1); + if (inb(iobase + PCL818_MUX) != 0x55) + return 1; /* there isn't card */ + outb(0x00, iobase + PCL818_MUX); + udelay(1); + outb(0x18, iobase + PCL818_CONTROL); + udelay(1); + if (inb(iobase + PCL818_CONTROL) != 0x18) + return 1; /* there isn't card */ + return 0; /* ok, card exist */ +} + +/* +============================================================================== + reset whole PCL-818 cards +*/ +static void pcl818_reset(struct comedi_device *dev) +{ + if (devpriv->usefifo) { /* FIFO shutdown */ + outb(0, dev->iobase + PCL818_FI_INTCLR); + outb(0, dev->iobase + PCL818_FI_FLUSH); + outb(0, dev->iobase + PCL818_FI_ENABLE); + } + outb(0, dev->iobase + PCL818_DA_LO); /* DAC=0V */ + outb(0, dev->iobase + PCL818_DA_HI); + udelay(1); + outb(0, dev->iobase + PCL818_DO_HI); /* DO=$0000 */ + outb(0, dev->iobase + PCL818_DO_LO); + udelay(1); + outb(0, dev->iobase + PCL818_CONTROL); + outb(0, dev->iobase + PCL818_CNTENABLE); + outb(0, dev->iobase + PCL818_MUX); + outb(0, dev->iobase + PCL818_CLRINT); + outb(0xb0, dev->iobase + PCL818_CTRCTL); /* Stop pacer */ + outb(0x70, dev->iobase + PCL818_CTRCTL); + outb(0x30, dev->iobase + PCL818_CTRCTL); + if (this_board->is_818) { + outb(0, dev->iobase + PCL818_RANGE); + } else { + outb(0, dev->iobase + PCL718_DA2_LO); + outb(0, dev->iobase + PCL718_DA2_HI); + } +} + +#ifdef unused +/* +============================================================================== + Enable(1)/disable(0) periodic interrupts from RTC +*/ +static int set_rtc_irq_bit(unsigned char bit) +{ + unsigned char val; + unsigned long flags; + + if (bit == 1) { + RTC_timer_lock++; + if (RTC_timer_lock > 1) + return 0; + } else { + RTC_timer_lock--; + if (RTC_timer_lock < 0) + RTC_timer_lock = 0; + if (RTC_timer_lock > 0) + return 0; + } + + save_flags(flags); + cli(); + val = CMOS_READ(RTC_CONTROL); + if (bit) + val |= RTC_PIE; + else + val &= ~RTC_PIE; + + CMOS_WRITE(val, RTC_CONTROL); + CMOS_READ(RTC_INTR_FLAGS); + restore_flags(flags); + return 0; +} + +/* +============================================================================== + Restart RTC if something stop it (xntpd every 11 mins or large IDE transfers) +*/ +static void rtc_dropped_irq(unsigned long data) +{ + struct comedi_device *dev = (void *)data; + unsigned long flags, tmp; + + switch (devpriv->int818_mode) { + case INT_TYPE_AI1_DMA_RTC: + case INT_TYPE_AI3_DMA_RTC: + mod_timer(&devpriv->rtc_irq_timer, + jiffies + HZ / devpriv->rtc_freq + 2 * HZ / 100); + save_flags(flags); + cli(); + tmp = (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); /* restart */ + restore_flags(flags); + break; + } +} + +/* +============================================================================== + Set frequency of interrupts from RTC +*/ +static int rtc_setfreq_irq(int freq) +{ + int tmp = 0; + int rtc_freq; + unsigned char val; + unsigned long flags; + + if (freq < 2) + freq = 2; + if (freq > 8192) + freq = 8192; + + while (freq > (1 << tmp)) + tmp++; + + rtc_freq = 1 << tmp; + + save_flags(flags); + cli(); + val = CMOS_READ(RTC_FREQ_SELECT) & 0xf0; + val |= (16 - tmp); + CMOS_WRITE(val, RTC_FREQ_SELECT); + restore_flags(flags); + return rtc_freq; +} +#endif + +/* +============================================================================== + Free any resources that we have claimed +*/ +static void free_resources(struct comedi_device *dev) +{ + /* printk("free_resource()\n"); */ + if (dev->private) { + pcl818_ai_cancel(dev, devpriv->sub_ai); + pcl818_reset(dev); + if (devpriv->dma) + free_dma(devpriv->dma); + if (devpriv->dmabuf[0]) + free_pages(devpriv->dmabuf[0], devpriv->dmapages[0]); + if (devpriv->dmabuf[1]) + free_pages(devpriv->dmabuf[1], devpriv->dmapages[1]); +#ifdef unused + if (devpriv->rtc_irq) + free_irq(devpriv->rtc_irq, dev); + if ((devpriv->dma_rtc) && (RTC_lock == 1)) { + if (devpriv->rtc_iobase) + release_region(devpriv->rtc_iobase, + devpriv->rtc_iosize); + } + if (devpriv->dma_rtc) + RTC_lock--; +#endif + } + + if (dev->irq) + free_irq(dev->irq, dev); + if (dev->iobase) + release_region(dev->iobase, devpriv->io_range); + /* printk("free_resource() end\n"); */ +} + +/* +============================================================================== + + Initialization + +*/ +static int pcl818_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + unsigned long iobase; + unsigned int irq; + int dma; + unsigned long pages; + struct comedi_subdevice *s; + + ret = alloc_private(dev, sizeof(struct pcl818_private)); + if (ret < 0) + return ret; /* Can't alloc mem */ + + /* claim our I/O space */ + iobase = it->options[0]; + printk + ("comedi%d: pcl818: board=%s, ioport=0x%03lx", + dev->minor, this_board->name, iobase); + devpriv->io_range = this_board->io_range; + if ((this_board->fifo) && (it->options[2] == -1)) { /* we've board with FIFO and we want to use FIFO */ + devpriv->io_range = PCLx1xFIFO_RANGE; + devpriv->usefifo = 1; + } + if (!request_region(iobase, devpriv->io_range, "pcl818")) { + comedi_error(dev, "I/O port conflict\n"); + return -EIO; + } + + dev->iobase = iobase; + + if (pcl818_check(iobase)) { + comedi_error(dev, "I can't detect board. FAIL!\n"); + return -EIO; + } + + /* set up some name stuff */ + dev->board_name = this_board->name; + /* grab our IRQ */ + irq = 0; + if (this_board->IRQbits != 0) { /* board support IRQ */ + irq = it->options[1]; + if (irq) { /* we want to use IRQ */ + if (((1 << irq) & this_board->IRQbits) == 0) { + printk + (", IRQ %u is out of allowed range, DISABLING IT", + irq); + irq = 0; /* Bad IRQ */ + } else { + if (request_irq + (irq, interrupt_pcl818, 0, "pcl818", dev)) { + printk + (", unable to allocate IRQ %u, DISABLING IT", + irq); + irq = 0; /* Can't use IRQ */ + } else { + printk(KERN_DEBUG "irq=%u", irq); + } + } + } + } + + dev->irq = irq; + if (irq) + devpriv->irq_free = 1; /* 1=we have allocated irq */ + else + devpriv->irq_free = 0; + + devpriv->irq_blocked = 0; /* number of subdevice which use IRQ */ + devpriv->ai_mode = 0; /* mode of irq */ + +#ifdef unused + /* grab RTC for DMA operations */ + devpriv->dma_rtc = 0; + if (it->options[2] > 0) { /* we want to use DMA */ + if (RTC_lock == 0) { + if (!request_region(RTC_PORT(0), RTC_IO_EXTENT, + "pcl818 (RTC)")) + goto no_rtc; + } + devpriv->rtc_iobase = RTC_PORT(0); + devpriv->rtc_iosize = RTC_IO_EXTENT; + RTC_lock++; + if (!request_irq(RTC_IRQ, interrupt_pcl818_ai_mode13_dma_rtc, 0, + "pcl818 DMA (RTC)", dev)) { + devpriv->dma_rtc = 1; + devpriv->rtc_irq = RTC_IRQ; + printk(KERN_DEBUG "dma_irq=%u", devpriv->rtc_irq); + } else { + RTC_lock--; + if (RTC_lock == 0) { + if (devpriv->rtc_iobase) + release_region(devpriv->rtc_iobase, + devpriv->rtc_iosize); + } + devpriv->rtc_iobase = 0; + devpriv->rtc_iosize = 0; + } + } + +no_rtc: +#endif + /* grab our DMA */ + dma = 0; + devpriv->dma = dma; + if ((devpriv->irq_free == 0) && (devpriv->dma_rtc == 0)) + goto no_dma; /* if we haven't IRQ, we can't use DMA */ + if (this_board->DMAbits != 0) { /* board support DMA */ + dma = it->options[2]; + if (dma < 1) + goto no_dma; /* DMA disabled */ + if (((1 << dma) & this_board->DMAbits) == 0) { + printk(KERN_ERR "DMA is out of allowed range, FAIL!\n"); + return -EINVAL; /* Bad DMA */ + } + ret = request_dma(dma, "pcl818"); + if (ret) + return -EBUSY; /* DMA isn't free */ + devpriv->dma = dma; + pages = 2; /* we need 16KB */ + devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages); + if (!devpriv->dmabuf[0]) + /* maybe experiment with try_to_free_pages() will help .... */ + return -EBUSY; /* no buffer :-( */ + devpriv->dmapages[0] = pages; + devpriv->hwdmaptr[0] = virt_to_bus((void *)devpriv->dmabuf[0]); + devpriv->hwdmasize[0] = (1 << pages) * PAGE_SIZE; + /* printk("%d %d %ld, ",devpriv->dmapages[0],devpriv->hwdmasize[0],PAGE_SIZE); */ + if (devpriv->dma_rtc == 0) { /* we must do duble buff :-( */ + devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages); + if (!devpriv->dmabuf[1]) + return -EBUSY; + devpriv->dmapages[1] = pages; + devpriv->hwdmaptr[1] = + virt_to_bus((void *)devpriv->dmabuf[1]); + devpriv->hwdmasize[1] = (1 << pages) * PAGE_SIZE; + } + } + +no_dma: + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + if (!this_board->n_aichan_se) { + s->type = COMEDI_SUBD_UNUSED; + } else { + s->type = COMEDI_SUBD_AI; + devpriv->sub_ai = s; + s->subdev_flags = SDF_READABLE; + if (check_single_ended(dev->iobase)) { + s->n_chan = this_board->n_aichan_se; + s->subdev_flags |= SDF_COMMON | SDF_GROUND; + printk(", %dchans S.E. DAC", s->n_chan); + } else { + s->n_chan = this_board->n_aichan_diff; + s->subdev_flags |= SDF_DIFF; + printk(", %dchans DIFF DAC", s->n_chan); + } + s->maxdata = this_board->ai_maxdata; + s->len_chanlist = s->n_chan; + s->range_table = this_board->ai_range_type; + s->cancel = pcl818_ai_cancel; + s->insn_read = pcl818_ai_insn_read; + if ((irq) || (devpriv->dma_rtc)) { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->do_cmdtest = ai_cmdtest; + s->do_cmd = ai_cmd; + } + if (this_board->is_818) { + if ((it->options[4] == 1) || (it->options[4] == 10)) + s->range_table = &range_pcl818l_h_ai; /* secondary range list jumper selectable */ + } else { + switch (it->options[4]) { + case 0: + s->range_table = &range_bipolar10; + break; + case 1: + s->range_table = &range_bipolar5; + break; + case 2: + s->range_table = &range_bipolar2_5; + break; + case 3: + s->range_table = &range718_bipolar1; + break; + case 4: + s->range_table = &range718_bipolar0_5; + break; + case 6: + s->range_table = &range_unipolar10; + break; + case 7: + s->range_table = &range_unipolar5; + break; + case 8: + s->range_table = &range718_unipolar2; + break; + case 9: + s->range_table = &range718_unipolar1; + break; + default: + s->range_table = &range_unknown; + break; + } + } + } + + s = dev->subdevices + 1; + if (!this_board->n_aochan) { + s->type = COMEDI_SUBD_UNUSED; + } else { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->n_aochan; + s->range_table = this_board->ao_range_type; + s->insn_read = pcl818_ao_insn_read; + s->insn_write = pcl818_ao_insn_write; +#ifdef unused +#ifdef PCL818_MODE13_AO + if (irq) { + s->trig[1] = pcl818_ao_mode1; + s->trig[3] = pcl818_ao_mode3; + } +#endif +#endif + if (this_board->is_818) { + if ((it->options[4] == 1) || (it->options[4] == 10)) + s->range_table = &range_unipolar10; + if (it->options[4] == 2) + s->range_table = &range_unknown; + } else { + if ((it->options[5] == 1) || (it->options[5] == 10)) + s->range_table = &range_unipolar10; + if (it->options[5] == 2) + s->range_table = &range_unknown; + } + } + + s = dev->subdevices + 2; + if (!this_board->n_dichan) { + s->type = COMEDI_SUBD_UNUSED; + } else { + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = this_board->n_dichan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dichan; + s->range_table = &range_digital; + s->insn_bits = pcl818_di_insn_bits; + } + + s = dev->subdevices + 3; + if (!this_board->n_dochan) { + s->type = COMEDI_SUBD_UNUSED; + } else { + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = this_board->n_dochan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dochan; + s->range_table = &range_digital; + s->insn_bits = pcl818_do_insn_bits; + } + + /* select 1/10MHz oscilator */ + if ((it->options[3] == 0) || (it->options[3] == 10)) + devpriv->i8253_osc_base = 100; + else + devpriv->i8253_osc_base = 1000; + + /* max sampling speed */ + devpriv->ns_min = this_board->ns_min; + + if (!this_board->is_818) { + if ((it->options[6] == 1) || (it->options[6] == 100)) + devpriv->ns_min = 10000; /* extended PCL718 to 100kHz DAC */ + } + + pcl818_reset(dev); + + printk("\n"); + + return 0; +} + +/* +============================================================================== + Removes device + */ +static int pcl818_detach(struct comedi_device *dev) +{ + /* printk("comedi%d: pcl818: remove\n", dev->minor); */ + free_resources(dev); + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcm3724.c b/drivers/staging/comedi/drivers/pcm3724.c new file mode 100644 index 00000000..f5c0bd17 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcm3724.c @@ -0,0 +1,324 @@ +/* + comedi/drivers/pcm724.c + + Drew Csillag + + hardware driver for Advantech card: + card: PCM-3724 + driver: pcm3724 + + Options for PCM-3724 + [0] - IO Base +*/ +/* +Driver: pcm3724 +Description: Advantech PCM-3724 +Author: Drew Csillag +Devices: [Advantech] PCM-3724 (pcm724) +Status: tested + +This is driver for digital I/O boards PCM-3724 with 48 DIO. +It needs 8255.o for operations and only immediate mode is supported. +See the source for configuration details. + +Copy/pasted/hacked from pcm724.c +*/ +/* + * check_driver overrides: + * struct comedi_insn + */ + +#include "../comedidev.h" + +#include +#include + +#include "8255.h" + +#define PCM3724_SIZE 16 +#define SIZE_8255 4 + +#define BUF_C0 0x1 +#define BUF_B0 0x2 +#define BUF_A0 0x4 +#define BUF_C1 0x8 +#define BUF_B1 0x10 +#define BUF_A1 0x20 + +#define GATE_A0 0x4 +#define GATE_B0 0x2 +#define GATE_C0 0x1 +#define GATE_A1 0x20 +#define GATE_B1 0x10 +#define GATE_C1 0x8 + +/* from 8255.c */ +#define CR_CW 0x80 +#define _8255_CR 3 +#define CR_B_IO 0x02 +#define CR_B_MODE 0x04 +#define CR_C_IO 0x09 +#define CR_A_IO 0x10 +#define CR_A_MODE(a) ((a)<<5) +#define CR_CW 0x80 + +static int pcm3724_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcm3724_detach(struct comedi_device *dev); + +struct pcm3724_board { + const char *name; /* driver name */ + int dio; /* num of DIO */ + int numofports; /* num of 8255 subdevices */ + unsigned int IRQbits; /* allowed interrupts */ + unsigned int io_range; /* len of IO space */ +}; + +/* used to track configured dios */ +struct priv_pcm3724 { + int dio_1; + int dio_2; +}; + +static const struct pcm3724_board boardtypes[] = { + {"pcm3724", 48, 2, 0x00fc, PCM3724_SIZE,}, +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcm3724_board)) +#define this_board ((const struct pcm3724_board *)dev->board_ptr) + +static struct comedi_driver driver_pcm3724 = { + .driver_name = "pcm3724", + .module = THIS_MODULE, + .attach = pcm3724_attach, + .detach = pcm3724_detach, + .board_name = &boardtypes[0].name, + .num_names = n_boardtypes, + .offset = sizeof(struct pcm3724_board), +}; + +static int __init driver_pcm3724_init_module(void) +{ + return comedi_driver_register(&driver_pcm3724); +} + +static void __exit driver_pcm3724_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcm3724); +} + +module_init(driver_pcm3724_init_module); +module_exit(driver_pcm3724_cleanup_module); + +/* (setq c-basic-offset 8) */ + +static int subdev_8255_cb(int dir, int port, int data, unsigned long arg) +{ + unsigned long iobase = arg; + unsigned char inbres; + /* printk("8255cb %d %d %d %lx\n", dir,port,data,arg); */ + if (dir) { + /* printk("8255 cb outb(%x, %lx)\n", data, iobase+port); */ + outb(data, iobase + port); + return 0; + } else { + inbres = inb(iobase + port); + /* printk("8255 cb inb(%lx) = %x\n", iobase+port, inbres); */ + return inbres; + } +} + +static int compute_buffer(int config, int devno, struct comedi_subdevice *s) +{ + /* 1 in io_bits indicates output */ + if (s->io_bits & 0x0000ff) { + if (devno == 0) + config |= BUF_A0; + else + config |= BUF_A1; + } + if (s->io_bits & 0x00ff00) { + if (devno == 0) + config |= BUF_B0; + else + config |= BUF_B1; + } + if (s->io_bits & 0xff0000) { + if (devno == 0) + config |= BUF_C0; + else + config |= BUF_C1; + } + return config; +} + +static void do_3724_config(struct comedi_device *dev, + struct comedi_subdevice *s, int chanspec) +{ + int config; + int buffer_config; + unsigned long port_8255_cfg; + + config = CR_CW; + buffer_config = 0; + + /* 1 in io_bits indicates output, 1 in config indicates input */ + if (!(s->io_bits & 0x0000ff)) + config |= CR_A_IO; + + if (!(s->io_bits & 0x00ff00)) + config |= CR_B_IO; + + if (!(s->io_bits & 0xff0000)) + config |= CR_C_IO; + + buffer_config = compute_buffer(0, 0, dev->subdevices); + buffer_config = compute_buffer(buffer_config, 1, (dev->subdevices) + 1); + + if (s == dev->subdevices) + port_8255_cfg = dev->iobase + _8255_CR; + else + port_8255_cfg = dev->iobase + SIZE_8255 + _8255_CR; + + outb(buffer_config, dev->iobase + 8); /* update buffer register */ + /* printk("pcm3724 buffer_config (%lx) %d, %x\n", + dev->iobase + _8255_CR, chanspec, buffer_config); */ + + outb(config, port_8255_cfg); +} + +static void enable_chan(struct comedi_device *dev, struct comedi_subdevice *s, + int chanspec) +{ + unsigned int mask; + int gatecfg; + struct priv_pcm3724 *priv; + + gatecfg = 0; + priv = dev->private; + + mask = 1 << CR_CHAN(chanspec); + if (s == dev->subdevices) /* subdev 0 */ + priv->dio_1 |= mask; + else /* subdev 1 */ + priv->dio_2 |= mask; + + if (priv->dio_1 & 0xff0000) + gatecfg |= GATE_C0; + + if (priv->dio_1 & 0xff00) + gatecfg |= GATE_B0; + + if (priv->dio_1 & 0xff) + gatecfg |= GATE_A0; + + if (priv->dio_2 & 0xff0000) + gatecfg |= GATE_C1; + + if (priv->dio_2 & 0xff00) + gatecfg |= GATE_B1; + + if (priv->dio_2 & 0xff) + gatecfg |= GATE_A1; + + /* printk("gate control %x\n", gatecfg); */ + outb(gatecfg, dev->iobase + 9); +} + +/* overriding the 8255 insn config */ +static int subdev_3724_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + unsigned int mask; + unsigned int bits; + + mask = 1 << CR_CHAN(insn->chanspec); + if (mask & 0x0000ff) + bits = 0x0000ff; + else if (mask & 0x00ff00) + bits = 0x00ff00; + else if (mask & 0x0f0000) + bits = 0x0f0000; + else + bits = 0xf00000; + + switch (data[0]) { + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~bits; + break; + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= bits; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + } + + do_3724_config(dev, s, insn->chanspec); + enable_chan(dev, s, insn->chanspec); + return 1; +} + +static int pcm3724_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + unsigned long iobase; + unsigned int iorange; + int ret, i, n_subdevices; + + iobase = it->options[0]; + iorange = this_board->io_range; + + ret = alloc_private(dev, sizeof(struct priv_pcm3724)); + if (ret < 0) + return -ENOMEM; + + ((struct priv_pcm3724 *)(dev->private))->dio_1 = 0; + ((struct priv_pcm3724 *)(dev->private))->dio_2 = 0; + + printk(KERN_INFO "comedi%d: pcm3724: board=%s, 0x%03lx ", dev->minor, + this_board->name, iobase); + if (!iobase || !request_region(iobase, iorange, "pcm3724")) { + printk("I/O port conflict\n"); + return -EIO; + } + + dev->iobase = iobase; + dev->board_name = this_board->name; + printk(KERN_INFO "\n"); + + n_subdevices = this_board->numofports; + + ret = alloc_subdevices(dev, n_subdevices); + if (ret < 0) + return ret; + + for (i = 0; i < dev->n_subdevices; i++) { + subdev_8255_init(dev, dev->subdevices + i, subdev_8255_cb, + (unsigned long)(dev->iobase + SIZE_8255 * i)); + ((dev->subdevices) + i)->insn_config = subdev_3724_insn_config; + } + return 0; +} + +static int pcm3724_detach(struct comedi_device *dev) +{ + int i; + + if (dev->subdevices) { + for (i = 0; i < dev->n_subdevices; i++) + subdev_8255_cleanup(dev, dev->subdevices + i); + } + if (dev->iobase) + release_region(dev->iobase, this_board->io_range); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcm3730.c b/drivers/staging/comedi/drivers/pcm3730.c new file mode 100644 index 00000000..bada6b23 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcm3730.c @@ -0,0 +1,171 @@ +/* + * comedi/drivers/pcm3730.c + * Driver for PCM3730 and clones + * Blaine Lee + * from pcl725 by David S. + */ +/* +Driver: pcm3730 +Description: PCM3730 +Author: Blaine Lee +Devices: [Advantech] PCM-3730 (pcm3730) +Status: unknown + +Configuration options: + [0] - I/O port base +*/ + +#include "../comedidev.h" + +#include + +#define PCM3730_SIZE 4 /* consecutive io port addresses */ + +#define PCM3730_DOA 0 /* offsets for each port */ +#define PCM3730_DOB 2 +#define PCM3730_DOC 3 +#define PCM3730_DIA 0 +#define PCM3730_DIB 2 +#define PCM3730_DIC 3 + +static int pcm3730_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcm3730_detach(struct comedi_device *dev); +static struct comedi_driver driver_pcm3730 = { + .driver_name = "pcm3730", + .module = THIS_MODULE, + .attach = pcm3730_attach, + .detach = pcm3730_detach, +}; + +static int __init driver_pcm3730_init_module(void) +{ + return comedi_driver_register(&driver_pcm3730); +} + +static void __exit driver_pcm3730_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcm3730); +} + +module_init(driver_pcm3730_init_module); +module_exit(driver_pcm3730_cleanup_module); + +static int pcm3730_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + outb(s->state, dev->iobase + (unsigned long)(s->private)); + } + data[1] = s->state; + + return 2; +} + +static int pcm3730_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + data[1] = inb(dev->iobase + (unsigned long)(s->private)); + return 2; +} + +static int pcm3730_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: pcm3730: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, PCM3730_SIZE, "pcm3730")) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + dev->board_name = "pcm3730"; + dev->iobase = dev->iobase; + dev->irq = 0; + + if (alloc_subdevices(dev, 6) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 1; + s->n_chan = 8; + s->insn_bits = pcm3730_do_insn_bits; + s->range_table = &range_digital; + s->private = (void *)PCM3730_DOA; + + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 1; + s->n_chan = 8; + s->insn_bits = pcm3730_do_insn_bits; + s->range_table = &range_digital; + s->private = (void *)PCM3730_DOB; + + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 1; + s->n_chan = 8; + s->insn_bits = pcm3730_do_insn_bits; + s->range_table = &range_digital; + s->private = (void *)PCM3730_DOC; + + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->maxdata = 1; + s->n_chan = 8; + s->insn_bits = pcm3730_di_insn_bits; + s->range_table = &range_digital; + s->private = (void *)PCM3730_DIA; + + s = dev->subdevices + 4; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->maxdata = 1; + s->n_chan = 8; + s->insn_bits = pcm3730_di_insn_bits; + s->range_table = &range_digital; + s->private = (void *)PCM3730_DIB; + + s = dev->subdevices + 5; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->maxdata = 1; + s->n_chan = 8; + s->insn_bits = pcm3730_di_insn_bits; + s->range_table = &range_digital; + s->private = (void *)PCM3730_DIC; + + printk(KERN_INFO "\n"); + + return 0; +} + +static int pcm3730_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pcm3730: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, PCM3730_SIZE); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcm_common.c b/drivers/staging/comedi/drivers/pcm_common.c new file mode 100644 index 00000000..474af7bc --- /dev/null +++ b/drivers/staging/comedi/drivers/pcm_common.c @@ -0,0 +1,115 @@ +#include "../comedidev.h" +#include "pcm_common.h" + +/* + * 'do_cmdtest' function for an 'INTERRUPT' subdevice. This is for + * the PCM drivers. + */ +int comedi_pcm_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + unsigned int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= (TRIG_NOW | TRIG_INT); + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= (TRIG_COUNT | TRIG_NONE); + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and + * mutually compatible */ + + /* these tests are true if more than one _src bit is set */ + if ((cmd->start_src & (cmd->start_src - 1)) != 0) + err++; + if ((cmd->scan_begin_src & (cmd->scan_begin_src - 1)) != 0) + err++; + if ((cmd->convert_src & (cmd->convert_src - 1)) != 0) + err++; + if ((cmd->scan_end_src & (cmd->scan_end_src - 1)) != 0) + err++; + if ((cmd->stop_src & (cmd->stop_src - 1)) != 0) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + /* cmd->start_src == TRIG_NOW || cmd->start_src == TRIG_INT */ + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + /* cmd->scan_begin_src == TRIG_EXT */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + + /* cmd->convert_src == TRIG_NOW */ + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + + /* cmd->scan_end_src == TRIG_COUNT */ + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + /* any count allowed */ + break; + case TRIG_NONE: + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + break; + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + /* if (err) return 4; */ + + return 0; +} +EXPORT_SYMBOL(comedi_pcm_cmdtest); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcm_common.h b/drivers/staging/comedi/drivers/pcm_common.h new file mode 100644 index 00000000..cd4840c1 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcm_common.h @@ -0,0 +1,8 @@ +#ifndef _comedi_common_H +#define _comedi_common_H + +extern int comedi_pcm_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd); + +#endif diff --git a/drivers/staging/comedi/drivers/pcmad.c b/drivers/staging/comedi/drivers/pcmad.c new file mode 100644 index 00000000..23b3d777 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcmad.c @@ -0,0 +1,193 @@ +/* + comedi/drivers/pcmad.c + Hardware driver for Winsystems PCM-A/D12 and PCM-A/D16 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000,2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: pcmad +Description: Winsystems PCM-A/D12, PCM-A/D16 +Author: ds +Devices: [Winsystems] PCM-A/D12 (pcmad12), PCM-A/D16 (pcmad16) +Status: untested + +This driver was written on a bet that I couldn't write a driver +in less than 2 hours. I won the bet, but never got paid. =( + +Configuration options: + [0] - I/O port base + [1] - unused + [2] - Analog input reference + 0 = single ended + 1 = differential + [3] - Analog input encoding (must match jumpers) + 0 = straight binary + 1 = two's complement +*/ + +#include +#include "../comedidev.h" + +#include + +#define PCMAD_SIZE 4 + +#define PCMAD_STATUS 0 +#define PCMAD_LSB 1 +#define PCMAD_MSB 2 +#define PCMAD_CONVERT 1 + +struct pcmad_board_struct { + const char *name; + int n_ai_bits; +}; +static const struct pcmad_board_struct pcmad_boards[] = { + { + .name = "pcmad12", + .n_ai_bits = 12, + }, + { + .name = "pcmad16", + .n_ai_bits = 16, + }, +}; + +#define this_board ((const struct pcmad_board_struct *)(dev->board_ptr)) +#define n_pcmad_boards ARRAY_SIZE(pcmad_boards) + +struct pcmad_priv_struct { + int differential; + int twos_comp; +}; +#define devpriv ((struct pcmad_priv_struct *)dev->private) + +static int pcmad_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int pcmad_detach(struct comedi_device *dev); +static struct comedi_driver driver_pcmad = { + .driver_name = "pcmad", + .module = THIS_MODULE, + .attach = pcmad_attach, + .detach = pcmad_detach, + .board_name = &pcmad_boards[0].name, + .num_names = n_pcmad_boards, + .offset = sizeof(pcmad_boards[0]), +}; + +static int __init driver_pcmad_init_module(void) +{ + return comedi_driver_register(&driver_pcmad); +} + +static void __exit driver_pcmad_cleanup_module(void) +{ + comedi_driver_unregister(&driver_pcmad); +} + +module_init(driver_pcmad_init_module); +module_exit(driver_pcmad_cleanup_module); + +#define TIMEOUT 100 + +static int pcmad_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan; + int n; + + chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) { + outb(chan, dev->iobase + PCMAD_CONVERT); + + for (i = 0; i < TIMEOUT; i++) { + if ((inb(dev->iobase + PCMAD_STATUS) & 0x3) == 0x3) + break; + } + data[n] = inb(dev->iobase + PCMAD_LSB); + data[n] |= (inb(dev->iobase + PCMAD_MSB) << 8); + + if (devpriv->twos_comp) + data[n] ^= (1 << (this_board->n_ai_bits - 1)); + } + + return n; +} + +/* + * options: + * 0 i/o base + * 1 unused + * 2 0=single ended 1=differential + * 3 0=straight binary 1=two's comp + */ +static int pcmad_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + struct comedi_subdevice *s; + unsigned long iobase; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: pcmad: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, PCMAD_SIZE, "pcmad")) { + printk(KERN_CONT "I/O port conflict\n"); + return -EIO; + } + printk(KERN_CONT "\n"); + dev->iobase = iobase; + + ret = alloc_subdevices(dev, 1); + if (ret < 0) + return ret; + + ret = alloc_private(dev, sizeof(struct pcmad_priv_struct)); + if (ret < 0) + return ret; + + dev->board_name = this_board->name; + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | AREF_GROUND; + s->n_chan = 16; /* XXX */ + s->len_chanlist = 1; + s->insn_read = pcmad_ai_insn_read; + s->maxdata = (1 << this_board->n_ai_bits) - 1; + s->range_table = &range_unknown; + + return 0; +} + +static int pcmad_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: pcmad: remove\n", dev->minor); + + if (dev->irq) + free_irq(dev->irq, dev); + + if (dev->iobase) + release_region(dev->iobase, PCMAD_SIZE); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcmda12.c b/drivers/staging/comedi/drivers/pcmda12.c new file mode 100644 index 00000000..0e9ffa28 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcmda12.c @@ -0,0 +1,323 @@ +/* + comedi/drivers/pcmda12.c + Driver for Winsystems PC-104 based PCM-D/A-12 8-channel AO board. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2006 Calin A. Culianu + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* +Driver: pcmda12 +Description: A driver for the Winsystems PCM-D/A-12 +Devices: [Winsystems] PCM-D/A-12 (pcmda12) +Author: Calin Culianu +Updated: Fri, 13 Jan 2006 12:01:01 -0500 +Status: works + +A driver for the relatively straightforward-to-program PCM-D/A-12. +This board doesn't support commands, and the only way to set its +analog output range is to jumper the board. As such, +comedi_data_write() ignores the range value specified. + +The board uses 16 consecutive I/O addresses starting at the I/O port +base address. Each address corresponds to the LSB then MSB of a +particular channel from 0-7. + +Note that the board is not ISA-PNP capable and thus +needs the I/O port comedi_config parameter. + +Note that passing a nonzero value as the second config option will +enable "simultaneous xfer" mode for this board, in which AO writes +will not take effect until a subsequent read of any AO channel. This +is so that one can speed up programming by preloading all AO registers +with values before simultaneously setting them to take effect with one +read command. + +Configuration Options: + [0] - I/O port base address + [1] - Do Simultaneous Xfer (see description) +*/ + +#include "../comedidev.h" + +#include /* for PCI devices */ + +#define SDEV_NO ((int)(s - dev->subdevices)) +#define CHANS 8 +#define IOSIZE 16 +#define LSB(x) ((unsigned char)((x) & 0xff)) +#define MSB(x) ((unsigned char)((((unsigned short)(x))>>8) & 0xff)) +#define LSB_PORT(chan) (dev->iobase + (chan)*2) +#define MSB_PORT(chan) (LSB_PORT(chan)+1) +#define BITS 12 + +/* + * Bords + */ +struct pcmda12_board { + const char *name; +}; + +/* note these have no effect and are merely here for reference.. + these are configured by jumpering the board! */ +static const struct comedi_lrange pcmda12_ranges = { + 3, + { + UNI_RANGE(5), UNI_RANGE(10), BIP_RANGE(5) + } +}; + +static const struct pcmda12_board pcmda12_boards[] = { + { + .name = "pcmda12", + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct pcmda12_board *)dev->board_ptr) + +struct pcmda12_private { + + unsigned int ao_readback[CHANS]; + int simultaneous_xfer_mode; +}; + +#define devpriv ((struct pcmda12_private *)(dev->private)) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pcmda12_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcmda12_detach(struct comedi_device *dev); + +static void zero_chans(struct comedi_device *dev); + +static struct comedi_driver driver = { + .driver_name = "pcmda12", + .module = THIS_MODULE, + .attach = pcmda12_attach, + .detach = pcmda12_detach, +/* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in pcmda12_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + .board_name = &pcmda12_boards[0].name, + .offset = sizeof(struct pcmda12_board), + .num_names = ARRAY_SIZE(pcmda12_boards), +}; + +static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pcmda12_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + + iobase = it->options[0]; + printk(KERN_INFO + "comedi%d: %s: io: %lx %s ", dev->minor, driver.driver_name, + iobase, it->options[1] ? "simultaneous xfer mode enabled" : ""); + + if (!request_region(iobase, IOSIZE, driver.driver_name)) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = thisboard->name; + +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct pcmda12_private)) < 0) { + printk(KERN_ERR "cannot allocate private data structure\n"); + return -ENOMEM; + } + + devpriv->simultaneous_xfer_mode = it->options[1]; + + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + * + * Allocate 2 subdevs (32 + 16 DIO lines) or 3 32 DIO subdevs for the + * 96-channel version of the board. + */ + if (alloc_subdevices(dev, 1) < 0) { + printk(KERN_ERR "cannot allocate subdevice data structures\n"); + return -ENOMEM; + } + + s = dev->subdevices; + s->private = NULL; + s->maxdata = (0x1 << BITS) - 1; + s->range_table = &pcmda12_ranges; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = CHANS; + s->insn_write = &ao_winsn; + s->insn_read = &ao_rinsn; + + zero_chans(dev); /* clear out all the registers, basically */ + + printk(KERN_INFO "attached\n"); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pcmda12_detach(struct comedi_device *dev) +{ + printk(KERN_INFO + "comedi%d: %s: remove\n", dev->minor, driver.driver_name); + if (dev->iobase) + release_region(dev->iobase, IOSIZE); + return 0; +} + +static void zero_chans(struct comedi_device *dev) +{ /* sets up an + ASIC chip to defaults */ + int i; + for (i = 0; i < CHANS; ++i) { +/* /\* do this as one instruction?? *\/ */ +/* outw(0, LSB_PORT(chan)); */ + outb(0, LSB_PORT(i)); + outb(0, MSB_PORT(i)); + } + inb(LSB_PORT(0)); /* update chans. */ +} + +static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; ++i) { + +/* /\* do this as one instruction?? *\/ */ +/* outw(data[i], LSB_PORT(chan)); */ + + /* Need to do this as two instructions due to 8-bit bus?? */ + /* first, load the low byte */ + outb(LSB(data[i]), LSB_PORT(chan)); + /* next, write the high byte */ + outb(MSB(data[i]), MSB_PORT(chan)); + + /* save shadow register */ + devpriv->ao_readback[chan] = data[i]; + + if (!devpriv->simultaneous_xfer_mode) + inb(LSB_PORT(chan)); + } + + /* return the number of samples written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + + Usually this means copying a value stored in devpriv->ao_readback. + However, since this driver supports simultaneous xfer then sometimes + this function actually accomplishes work. + + Simultaneaous xfer mode is accomplished by loading ALL the values + you want for AO in all the channels, then READing off one of the AO + registers to initiate the instantaneous simultaneous update of all + DAC outputs, which makes all AO channels update simultaneously. + This is useful for some control applications, I would imagine. +*/ +static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + if (devpriv->simultaneous_xfer_mode) + inb(LSB_PORT(chan)); + /* read back shadow register */ + data[i] = devpriv->ao_readback[chan]; + } + + return i; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __init driver_init_module(void) +{ + return comedi_driver_register(&driver); +} + +static void __exit driver_cleanup_module(void) +{ + comedi_driver_unregister(&driver); +} + +module_init(driver_init_module); +module_exit(driver_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcmmio.c b/drivers/staging/comedi/drivers/pcmmio.c new file mode 100644 index 00000000..eddac00e --- /dev/null +++ b/drivers/staging/comedi/drivers/pcmmio.c @@ -0,0 +1,1362 @@ +/* + comedi/drivers/pcmmio.c + Driver for Winsystems PC-104 based multifunction IO board. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2007 Calin A. Culianu + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* +Driver: pcmmio +Description: A driver for the PCM-MIO multifunction board +Devices: [Winsystems] PCM-MIO (pcmmio) +Author: Calin Culianu +Updated: Wed, May 16 2007 16:21:10 -0500 +Status: works + +A driver for the relatively new PCM-MIO multifunction board from +Winsystems. This board is a PC-104 based I/O board. It contains +four subdevices: + subdevice 0 - 16 channels of 16-bit AI + subdevice 1 - 8 channels of 16-bit AO + subdevice 2 - first 24 channels of the 48 channel of DIO + (with edge-triggered interrupt support) + subdevice 3 - last 24 channels of the 48 channel DIO + (no interrupt support for this bank of channels) + + Some notes: + + Synchronous reads and writes are the only things implemented for AI and AO, + even though the hardware itself can do streaming acquisition, etc. Anyone + want to add asynchronous I/O for AI/AO as a feature? Be my guest... + + Asynchronous I/O for the DIO subdevices *is* implemented, however! They are + basically edge-triggered interrupts for any configuration of the first + 24 DIO-lines. + + Also note that this interrupt support is untested. + + A few words about edge-detection IRQ support (commands on DIO): + + * To use edge-detection IRQ support for the DIO subdevice, pass the IRQ + of the board to the comedi_config command. The board IRQ is not jumpered + but rather configured through software, so any IRQ from 1-15 is OK. + + * Due to the genericity of the comedi API, you need to create a special + comedi_command in order to use edge-triggered interrupts for DIO. + + * Use comedi_commands with TRIG_NOW. Your callback will be called each + time an edge is detected on the specified DIO line(s), and the data + values will be two sample_t's, which should be concatenated to form + one 32-bit unsigned int. This value is the mask of channels that had + edges detected from your channel list. Note that the bits positions + in the mask correspond to positions in your chanlist when you + specified the command and *not* channel id's! + + * To set the polarity of the edge-detection interrupts pass a nonzero value + for either CR_RANGE or CR_AREF for edge-up polarity, or a zero + value for both CR_RANGE and CR_AREF if you want edge-down polarity. + +Configuration Options: + [0] - I/O port base address + [1] - IRQ (optional -- for edge-detect interrupt support only, + leave out if you don't need this feature) +*/ + +#include +#include +#include "../comedidev.h" +#include "pcm_common.h" +#include /* for PCI devices */ + +/* This stuff is all from pcmuio.c -- it refers to the DIO subdevices only */ +#define CHANS_PER_PORT 8 +#define PORTS_PER_ASIC 6 +#define INTR_PORTS_PER_ASIC 3 +#define MAX_CHANS_PER_SUBDEV 24 /* number of channels per comedi subdevice */ +#define PORTS_PER_SUBDEV (MAX_CHANS_PER_SUBDEV/CHANS_PER_PORT) +#define CHANS_PER_ASIC (CHANS_PER_PORT*PORTS_PER_ASIC) +#define INTR_CHANS_PER_ASIC 24 +#define INTR_PORTS_PER_SUBDEV (INTR_CHANS_PER_ASIC/CHANS_PER_PORT) +#define MAX_DIO_CHANS (PORTS_PER_ASIC*1*CHANS_PER_PORT) +#define MAX_ASICS (MAX_DIO_CHANS/CHANS_PER_ASIC) +#define SDEV_NO ((int)(s - dev->subdevices)) +#define CALC_N_DIO_SUBDEVS(nchans) ((nchans)/MAX_CHANS_PER_SUBDEV + (!!((nchans)%MAX_CHANS_PER_SUBDEV)) /*+ (nchans > INTR_CHANS_PER_ASIC ? 2 : 1)*/) +/* IO Memory sizes */ +#define ASIC_IOSIZE (0x0B) +#define PCMMIO48_IOSIZE ASIC_IOSIZE + +/* Some offsets - these are all in the 16byte IO memory offset from + the base address. Note that there is a paging scheme to swap out + offsets 0x8-0xA using the PAGELOCK register. See the table below. + + Register(s) Pages R/W? Description + -------------------------------------------------------------- + REG_PORTx All R/W Read/Write/Configure IO + REG_INT_PENDING All ReadOnly Quickly see which INT_IDx has int. + REG_PAGELOCK All WriteOnly Select a page + REG_POLx Pg. 1 only WriteOnly Select edge-detection polarity + REG_ENABx Pg. 2 only WriteOnly Enable/Disable edge-detect. int. + REG_INT_IDx Pg. 3 only R/W See which ports/bits have ints. + */ +#define REG_PORT0 0x0 +#define REG_PORT1 0x1 +#define REG_PORT2 0x2 +#define REG_PORT3 0x3 +#define REG_PORT4 0x4 +#define REG_PORT5 0x5 +#define REG_INT_PENDING 0x6 +#define REG_PAGELOCK 0x7 /* + * page selector register, upper 2 bits select + * a page and bits 0-5 are used to 'lock down' + * a particular port above to make it readonly. + */ +#define REG_POL0 0x8 +#define REG_POL1 0x9 +#define REG_POL2 0xA +#define REG_ENAB0 0x8 +#define REG_ENAB1 0x9 +#define REG_ENAB2 0xA +#define REG_INT_ID0 0x8 +#define REG_INT_ID1 0x9 +#define REG_INT_ID2 0xA + +#define NUM_PAGED_REGS 3 +#define NUM_PAGES 4 +#define FIRST_PAGED_REG 0x8 +#define REG_PAGE_BITOFFSET 6 +#define REG_LOCK_BITOFFSET 0 +#define REG_PAGE_MASK (~((0x1<board_ptr) + +/* this structure is for data unique to this subdevice. */ +struct pcmmio_subdev_private { + + union { + /* for DIO: mapping of halfwords (bytes) + in port/chanarray to iobase */ + unsigned long iobases[PORTS_PER_SUBDEV]; + + /* for AI/AO */ + unsigned long iobase; + }; + union { + struct { + + /* The below is only used for intr subdevices */ + struct { + /* + * if non-negative, this subdev has an + * interrupt asic + */ + int asic; + /* + * if nonnegative, the first channel id for + * interrupts. + */ + int first_chan; + /* + * the number of asic channels in this subdev + * that have interrutps + */ + int num_asic_chans; + /* + * if nonnegative, the first channel id with + * respect to the asic that has interrupts + */ + int asic_chan; + /* + * subdev-relative channel mask for channels + * we are interested in + */ + int enabled_mask; + int active; + int stop_count; + int continuous; + spinlock_t spinlock; + } intr; + } dio; + struct { + /* the last unsigned int data written */ + unsigned int shadow_samples[8]; + } ao; + }; +}; + +/* + * this structure is for data unique to this hardware driver. If + * several hardware drivers keep similar information in this structure, + * feel free to suggest moving the variable to the struct comedi_device struct. + */ +struct pcmmio_private { + /* stuff for DIO */ + struct { + unsigned char pagelock; /* current page and lock */ + /* shadow of POLx registers */ + unsigned char pol[NUM_PAGED_REGS]; + /* shadow of ENABx registers */ + unsigned char enab[NUM_PAGED_REGS]; + int num; + unsigned long iobase; + unsigned int irq; + spinlock_t spinlock; + } asics[MAX_ASICS]; + struct pcmmio_subdev_private *sprivs; +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct pcmmio_private *)dev->private) +#define subpriv ((struct pcmmio_subdev_private *)s->private) +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pcmmio_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcmmio_detach(struct comedi_device *dev); + +static struct comedi_driver driver = { + .driver_name = "pcmmio", + .module = THIS_MODULE, + .attach = pcmmio_attach, + .detach = pcmmio_detach, +/* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in pcmmio_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + .board_name = &pcmmio_boards[0].name, + .offset = sizeof(struct pcmmio_board), + .num_names = ARRAY_SIZE(pcmmio_boards), +}; + +static int pcmmio_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int pcmmio_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static irqreturn_t interrupt_pcmmio(int irq, void *d); +static void pcmmio_stop_intr(struct comedi_device *, struct comedi_subdevice *); +static int pcmmio_cancel(struct comedi_device *dev, struct comedi_subdevice *s); +static int pcmmio_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int pcmmio_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); + +/* some helper functions to deal with specifics of this device's registers */ +/* sets up/clears ASIC chips to defaults */ +static void init_asics(struct comedi_device *dev); +static void switch_page(struct comedi_device *dev, int asic, int page); +#ifdef notused +static void lock_port(struct comedi_device *dev, int asic, int port); +static void unlock_port(struct comedi_device *dev, int asic, int port); +#endif + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pcmmio_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int sdev_no, chans_left, n_dio_subdevs, n_subdevs, port, asic, + thisasic_chanct = 0; + unsigned long iobase; + unsigned int irq[MAX_ASICS]; + + iobase = it->options[0]; + irq[0] = it->options[1]; + + printk(KERN_INFO "comedi%d: %s: io: %lx attaching...\n", dev->minor, + driver.driver_name, iobase); + + dev->iobase = iobase; + + if (!iobase || !request_region(iobase, + thisboard->total_iosize, + driver.driver_name)) { + printk(KERN_ERR "comedi%d: I/O port conflict\n", dev->minor); + return -EIO; + } + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = thisboard->name; + +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct pcmmio_private)) < 0) { + printk(KERN_ERR "comedi%d: cannot allocate private data structure\n", + dev->minor); + return -ENOMEM; + } + + for (asic = 0; asic < MAX_ASICS; ++asic) { + devpriv->asics[asic].num = asic; + devpriv->asics[asic].iobase = + dev->iobase + 16 + asic * ASIC_IOSIZE; + /* + * this gets actually set at the end of this function when we + * request_irqs + */ + devpriv->asics[asic].irq = 0; + spin_lock_init(&devpriv->asics[asic].spinlock); + } + + chans_left = CHANS_PER_ASIC * thisboard->dio_num_asics; + n_dio_subdevs = CALC_N_DIO_SUBDEVS(chans_left); + n_subdevs = n_dio_subdevs + 2; + devpriv->sprivs = + kcalloc(n_subdevs, sizeof(struct pcmmio_subdev_private), + GFP_KERNEL); + if (!devpriv->sprivs) { + printk(KERN_ERR "comedi%d: cannot allocate subdevice private data structures\n", + dev->minor); + return -ENOMEM; + } + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + * + * Allocate 1 AI + 1 AO + 2 DIO subdevs (24 lines per DIO) + */ + if (alloc_subdevices(dev, n_subdevs) < 0) { + printk(KERN_ERR "comedi%d: cannot allocate subdevice data structures\n", + dev->minor); + return -ENOMEM; + } + + /* First, AI */ + sdev_no = 0; + s = dev->subdevices + sdev_no; + s->private = devpriv->sprivs + sdev_no; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = thisboard->ai_range_table; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->type = COMEDI_SUBD_AI; + s->n_chan = thisboard->n_ai_chans; + s->len_chanlist = s->n_chan; + s->insn_read = thisboard->ai_rinsn; + subpriv->iobase = dev->iobase + 0; + /* initialize the resource enable register by clearing it */ + outb(0, subpriv->iobase + 3); + outb(0, subpriv->iobase + 4 + 3); + + /* Next, AO */ + ++sdev_no; + s = dev->subdevices + sdev_no; + s->private = devpriv->sprivs + sdev_no; + s->maxdata = (1 << thisboard->ao_bits) - 1; + s->range_table = thisboard->ao_range_table; + s->subdev_flags = SDF_READABLE; + s->type = COMEDI_SUBD_AO; + s->n_chan = thisboard->n_ao_chans; + s->len_chanlist = s->n_chan; + s->insn_read = thisboard->ao_rinsn; + s->insn_write = thisboard->ao_winsn; + subpriv->iobase = dev->iobase + 8; + /* initialize the resource enable register by clearing it */ + outb(0, subpriv->iobase + 3); + outb(0, subpriv->iobase + 4 + 3); + + ++sdev_no; + port = 0; + asic = 0; + for (; sdev_no < (int)dev->n_subdevices; ++sdev_no) { + int byte_no; + + s = dev->subdevices + sdev_no; + s->private = devpriv->sprivs + sdev_no; + s->maxdata = 1; + s->range_table = &range_digital; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->type = COMEDI_SUBD_DIO; + s->insn_bits = pcmmio_dio_insn_bits; + s->insn_config = pcmmio_dio_insn_config; + s->n_chan = min(chans_left, MAX_CHANS_PER_SUBDEV); + subpriv->dio.intr.asic = -1; + subpriv->dio.intr.first_chan = -1; + subpriv->dio.intr.asic_chan = -1; + subpriv->dio.intr.num_asic_chans = -1; + subpriv->dio.intr.active = 0; + s->len_chanlist = 1; + + /* save the ioport address for each 'port' of 8 channels in the + subdevice */ + for (byte_no = 0; byte_no < PORTS_PER_SUBDEV; ++byte_no, ++port) { + if (port >= PORTS_PER_ASIC) { + port = 0; + ++asic; + thisasic_chanct = 0; + } + subpriv->iobases[byte_no] = + devpriv->asics[asic].iobase + port; + + if (thisasic_chanct < + CHANS_PER_PORT * INTR_PORTS_PER_ASIC + && subpriv->dio.intr.asic < 0) { + /* + * this is an interrupt subdevice, + * so setup the struct + */ + subpriv->dio.intr.asic = asic; + subpriv->dio.intr.active = 0; + subpriv->dio.intr.stop_count = 0; + subpriv->dio.intr.first_chan = byte_no * 8; + subpriv->dio.intr.asic_chan = thisasic_chanct; + subpriv->dio.intr.num_asic_chans = + s->n_chan - subpriv->dio.intr.first_chan; + s->cancel = pcmmio_cancel; + s->do_cmd = pcmmio_cmd; + s->do_cmdtest = pcmmio_cmdtest; + s->len_chanlist = + subpriv->dio.intr.num_asic_chans; + } + thisasic_chanct += CHANS_PER_PORT; + } + spin_lock_init(&subpriv->dio.intr.spinlock); + + chans_left -= s->n_chan; + + if (!chans_left) { + /* + * reset the asic to our first asic, + * to do intr subdevs + */ + asic = 0; + port = 0; + } + + } + + init_asics(dev); /* clear out all the registers, basically */ + + for (asic = 0; irq[0] && asic < MAX_ASICS; ++asic) { + if (irq[asic] + && request_irq(irq[asic], interrupt_pcmmio, + IRQF_SHARED, thisboard->name, dev)) { + int i; + /* unroll the allocated irqs.. */ + for (i = asic - 1; i >= 0; --i) { + free_irq(irq[i], dev); + devpriv->asics[i].irq = irq[i] = 0; + } + irq[asic] = 0; + } + devpriv->asics[asic].irq = irq[asic]; + } + + dev->irq = irq[0]; /* + * grr.. wish comedi dev struct supported + * multiple irqs.. + */ + + if (irq[0]) { + printk(KERN_DEBUG "comedi%d: irq: %u\n", dev->minor, irq[0]); + if (thisboard->dio_num_asics == 2 && irq[1]) + printk(KERN_DEBUG "comedi%d: second ASIC irq: %u\n", + dev->minor, irq[1]); + } else { + printk(KERN_INFO "comedi%d: (IRQ mode disabled)\n", dev->minor); + } + + printk(KERN_INFO "comedi%d: attached\n", dev->minor); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pcmmio_detach(struct comedi_device *dev) +{ + int i; + + printk(KERN_INFO "comedi%d: %s: remove\n", dev->minor, driver.driver_name); + if (dev->iobase) + release_region(dev->iobase, thisboard->total_iosize); + + for (i = 0; i < MAX_ASICS; ++i) { + if (devpriv && devpriv->asics[i].irq) + free_irq(devpriv->asics[i].irq, dev); + } + + if (devpriv && devpriv->sprivs) + kfree(devpriv->sprivs); + + return 0; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +static int pcmmio_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int byte_no; + if (insn->n != 2) + return -EINVAL; + + /* NOTE: + reading a 0 means this channel was high + writine a 0 sets the channel high + reading a 1 means this channel was low + writing a 1 means set this channel low + + Therefore everything is always inverted. */ + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + +#ifdef DAMMIT_ITS_BROKEN + /* DEBUG */ + printk(KERN_DEBUG "write mask: %08x data: %08x\n", data[0], data[1]); +#endif + + s->state = 0; + + for (byte_no = 0; byte_no < s->n_chan / CHANS_PER_PORT; ++byte_no) { + /* address of 8-bit port */ + unsigned long ioaddr = subpriv->iobases[byte_no], + /* bit offset of port in 32-bit doubleword */ + offset = byte_no * 8; + /* this 8-bit port's data */ + unsigned char byte = 0, + /* The write mask for this port (if any) */ + write_mask_byte = (data[0] >> offset) & 0xff, + /* The data byte for this port */ + data_byte = (data[1] >> offset) & 0xff; + + byte = inb(ioaddr); /* read all 8-bits for this port */ + +#ifdef DAMMIT_ITS_BROKEN + /* DEBUG */ + printk + (KERN_DEBUG "byte %d wmb %02x db %02x offset %02d io %04x," + " data_in %02x ", byte_no, (unsigned)write_mask_byte, + (unsigned)data_byte, offset, ioaddr, (unsigned)byte); +#endif + + if (write_mask_byte) { + /* + * this byte has some write_bits + * -- so set the output lines + */ + /* clear bits for write mask */ + byte &= ~write_mask_byte; + /* set to inverted data_byte */ + byte |= ~data_byte & write_mask_byte; + /* Write out the new digital output state */ + outb(byte, ioaddr); + } +#ifdef DAMMIT_ITS_BROKEN + /* DEBUG */ + printk("data_out_byte %02x\n", (unsigned)byte); +#endif + /* save the digital input lines for this byte.. */ + s->state |= ((unsigned int)byte) << offset; + } + + /* now return the DIO lines to data[1] - note they came inverted! */ + data[1] = ~s->state; + +#ifdef DAMMIT_ITS_BROKEN + /* DEBUG */ + printk(KERN_DEBUG "s->state %08x data_out %08x\n", s->state, data[1]); +#endif + + return 2; +} + +/* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ +static int pcmmio_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec), byte_no = chan / 8, bit_no = + chan % 8; + unsigned long ioaddr; + unsigned char byte; + + /* Compute ioaddr for this channel */ + ioaddr = subpriv->iobases[byte_no]; + + /* NOTE: + writing a 0 an IO channel's bit sets the channel to INPUT + and pulls the line high as well + + writing a 1 to an IO channel's bit pulls the line low + + All channels are implicitly always in OUTPUT mode -- but when + they are high they can be considered to be in INPUT mode.. + + Thus, we only force channels low if the config request was INPUT, + otherwise we do nothing to the hardware. */ + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + /* save to io_bits -- don't actually do anything since + all input channels are also output channels... */ + s->io_bits |= 1 << chan; + break; + case INSN_CONFIG_DIO_INPUT: + /* write a 0 to the actual register representing the channel + to set it to 'input'. 0 means "float high". */ + byte = inb(ioaddr); + byte &= ~(1 << bit_no); + /**< set input channel to '0' */ + + /* + * write out byte -- this is the only time we actually affect + * the hardware as all channels are implicitly output + * -- but input channels are set to float-high + */ + outb(byte, ioaddr); + + /* save to io_bits */ + s->io_bits &= ~(1 << chan); + break; + + case INSN_CONFIG_DIO_QUERY: + /* retrieve from shadow register */ + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + + default: + return -EINVAL; + break; + } + + return insn->n; +} + +static void init_asics(struct comedi_device *dev) +{ /* sets up an + ASIC chip to defaults */ + int asic; + + for (asic = 0; asic < thisboard->dio_num_asics; ++asic) { + int port, page; + unsigned long baseaddr = devpriv->asics[asic].iobase; + + switch_page(dev, asic, 0); /* switch back to page 0 */ + + /* first, clear all the DIO port bits */ + for (port = 0; port < PORTS_PER_ASIC; ++port) + outb(0, baseaddr + REG_PORT0 + port); + + /* Next, clear all the paged registers for each page */ + for (page = 1; page < NUM_PAGES; ++page) { + int reg; + /* now clear all the paged registers */ + switch_page(dev, asic, page); + for (reg = FIRST_PAGED_REG; + reg < FIRST_PAGED_REG + NUM_PAGED_REGS; ++reg) + outb(0, baseaddr + reg); + } + + /* DEBUG set rising edge interrupts on port0 of both asics */ + /*switch_page(dev, asic, PAGE_POL); + outb(0xff, baseaddr + REG_POL0); + switch_page(dev, asic, PAGE_ENAB); + outb(0xff, baseaddr + REG_ENAB0); */ + /* END DEBUG */ + + /* switch back to default page 0 */ + switch_page(dev, asic, 0); + } +} + +static void switch_page(struct comedi_device *dev, int asic, int page) +{ + if (asic < 0 || asic >= thisboard->dio_num_asics) + return; /* paranoia */ + if (page < 0 || page >= NUM_PAGES) + return; /* more paranoia */ + + devpriv->asics[asic].pagelock &= ~REG_PAGE_MASK; + devpriv->asics[asic].pagelock |= page << REG_PAGE_BITOFFSET; + + /* now write out the shadow register */ + outb(devpriv->asics[asic].pagelock, + devpriv->asics[asic].iobase + REG_PAGELOCK); +} + +#ifdef notused +static void lock_port(struct comedi_device *dev, int asic, int port) +{ + if (asic < 0 || asic >= thisboard->dio_num_asics) + return; /* paranoia */ + if (port < 0 || port >= PORTS_PER_ASIC) + return; /* more paranoia */ + + devpriv->asics[asic].pagelock |= 0x1 << port; + /* now write out the shadow register */ + outb(devpriv->asics[asic].pagelock, + devpriv->asics[asic].iobase + REG_PAGELOCK); + return; +} + +static void unlock_port(struct comedi_device *dev, int asic, int port) +{ + if (asic < 0 || asic >= thisboard->dio_num_asics) + return; /* paranoia */ + if (port < 0 || port >= PORTS_PER_ASIC) + return; /* more paranoia */ + devpriv->asics[asic].pagelock &= ~(0x1 << port) | REG_LOCK_MASK; + /* now write out the shadow register */ + outb(devpriv->asics[asic].pagelock, + devpriv->asics[asic].iobase + REG_PAGELOCK); +} +#endif /* notused */ + +static irqreturn_t interrupt_pcmmio(int irq, void *d) +{ + int asic, got1 = 0; + struct comedi_device *dev = (struct comedi_device *)d; + + for (asic = 0; asic < MAX_ASICS; ++asic) { + if (irq == devpriv->asics[asic].irq) { + unsigned long flags; + unsigned triggered = 0; + unsigned long iobase = devpriv->asics[asic].iobase; + /* it is an interrupt for ASIC #asic */ + unsigned char int_pend; + + spin_lock_irqsave(&devpriv->asics[asic].spinlock, + flags); + + int_pend = inb(iobase + REG_INT_PENDING) & 0x07; + + if (int_pend) { + int port; + for (port = 0; port < INTR_PORTS_PER_ASIC; + ++port) { + if (int_pend & (0x1 << port)) { + unsigned char + io_lines_with_edges = 0; + switch_page(dev, asic, + PAGE_INT_ID); + io_lines_with_edges = + inb(iobase + + REG_INT_ID0 + port); + + if (io_lines_with_edges) + /* + * clear pending + * interrupt + */ + outb(0, iobase + + REG_INT_ID0 + + port); + + triggered |= + io_lines_with_edges << + port * 8; + } + } + + ++got1; + } + + spin_unlock_irqrestore(&devpriv->asics[asic].spinlock, + flags); + + if (triggered) { + struct comedi_subdevice *s; + /* + * TODO here: dispatch io lines to subdevs + * with commands.. + */ + printk + (KERN_DEBUG "got edge detect interrupt %d asic %d which_chans: %06x\n", + irq, asic, triggered); + for (s = dev->subdevices + 2; + s < dev->subdevices + dev->n_subdevices; + ++s) { + /* + * this is an interrupt subdev, + * and it matches this asic! + */ + if (subpriv->dio.intr.asic == asic) { + unsigned long flags; + unsigned oldevents; + + spin_lock_irqsave(&subpriv->dio. + intr.spinlock, + flags); + + oldevents = s->async->events; + + if (subpriv->dio.intr.active) { + unsigned mytrig = + ((triggered >> + subpriv->dio.intr.asic_chan) + & + ((0x1 << subpriv-> + dio.intr. + num_asic_chans) - + 1)) << subpriv-> + dio.intr.first_chan; + if (mytrig & + subpriv->dio. + intr.enabled_mask) { + unsigned int val + = 0; + unsigned int n, + ch, len; + + len = + s-> + async->cmd.chanlist_len; + for (n = 0; + n < len; + n++) { + ch = CR_CHAN(s->async->cmd.chanlist[n]); + if (mytrig & (1U << ch)) + val |= (1U << n); + } + /* Write the scan to the buffer. */ + if (comedi_buf_put(s->async, ((short *)&val)[0]) + && + comedi_buf_put + (s->async, + ((short *) + &val)[1])) { + s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS); + } else { + /* Overflow! Stop acquisition!! */ + /* TODO: STOP_ACQUISITION_CALL_HERE!! */ + pcmmio_stop_intr + (dev, + s); + } + + /* Check for end of acquisition. */ + if (!subpriv->dio.intr.continuous) { + /* stop_src == TRIG_COUNT */ + if (subpriv->dio.intr.stop_count > 0) { + subpriv->dio.intr.stop_count--; + if (subpriv->dio.intr.stop_count == 0) { + s->async->events |= COMEDI_CB_EOA; + /* TODO: STOP_ACQUISITION_CALL_HERE!! */ + pcmmio_stop_intr + (dev, + s); + } + } + } + } + } + + spin_unlock_irqrestore + (&subpriv->dio.intr. + spinlock, flags); + + if (oldevents != + s->async->events) { + comedi_event(dev, s); + } + + } + + } + } + + } + } + if (!got1) + return IRQ_NONE; /* interrupt from other source */ + return IRQ_HANDLED; +} + +static void pcmmio_stop_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int nports, firstport, asic, port; + + asic = subpriv->dio.intr.asic; + if (asic < 0) + return; /* not an interrupt subdev */ + + subpriv->dio.intr.enabled_mask = 0; + subpriv->dio.intr.active = 0; + s->async->inttrig = 0; + nports = subpriv->dio.intr.num_asic_chans / CHANS_PER_PORT; + firstport = subpriv->dio.intr.asic_chan / CHANS_PER_PORT; + switch_page(dev, asic, PAGE_ENAB); + for (port = firstport; port < firstport + nports; ++port) { + /* disable all intrs for this subdev.. */ + outb(0, devpriv->asics[asic].iobase + REG_ENAB0 + port); + } +} + +static int pcmmio_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + if (!subpriv->dio.intr.continuous && subpriv->dio.intr.stop_count == 0) { + /* An empty acquisition! */ + s->async->events |= COMEDI_CB_EOA; + subpriv->dio.intr.active = 0; + return 1; + } else { + unsigned bits = 0, pol_bits = 0, n; + int nports, firstport, asic, port; + struct comedi_cmd *cmd = &s->async->cmd; + + asic = subpriv->dio.intr.asic; + if (asic < 0) + return 1; /* not an interrupt + subdev */ + subpriv->dio.intr.enabled_mask = 0; + subpriv->dio.intr.active = 1; + nports = subpriv->dio.intr.num_asic_chans / CHANS_PER_PORT; + firstport = subpriv->dio.intr.asic_chan / CHANS_PER_PORT; + if (cmd->chanlist) { + for (n = 0; n < cmd->chanlist_len; n++) { + bits |= (1U << CR_CHAN(cmd->chanlist[n])); + pol_bits |= (CR_AREF(cmd->chanlist[n]) + || CR_RANGE(cmd-> + chanlist[n]) ? 1U : 0U) + << CR_CHAN(cmd->chanlist[n]); + } + } + bits &= ((0x1 << subpriv->dio.intr.num_asic_chans) - + 1) << subpriv->dio.intr.first_chan; + subpriv->dio.intr.enabled_mask = bits; + + { + /* + * the below code configures the board + * to use a specific IRQ from 0-15. + */ + unsigned char b; + /* + * set resource enable register + * to enable IRQ operation + */ + outb(1 << 4, dev->iobase + 3); + /* set bits 0-3 of b to the irq number from 0-15 */ + b = dev->irq & ((1 << 4) - 1); + outb(b, dev->iobase + 2); + /* done, we told the board what irq to use */ + } + + switch_page(dev, asic, PAGE_ENAB); + for (port = firstport; port < firstport + nports; ++port) { + unsigned enab = + bits >> (subpriv->dio.intr.first_chan + (port - + firstport) + * 8) & 0xff, pol = + pol_bits >> (subpriv->dio.intr.first_chan + + (port - firstport) * 8) & 0xff; + /* set enab intrs for this subdev.. */ + outb(enab, + devpriv->asics[asic].iobase + REG_ENAB0 + port); + switch_page(dev, asic, PAGE_POL); + outb(pol, + devpriv->asics[asic].iobase + REG_ENAB0 + port); + } + } + return 0; +} + +static int pcmmio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags; + + spin_lock_irqsave(&subpriv->dio.intr.spinlock, flags); + if (subpriv->dio.intr.active) + pcmmio_stop_intr(dev, s); + spin_unlock_irqrestore(&subpriv->dio.intr.spinlock, flags); + + return 0; +} + +/* + * Internal trigger function to start acquisition for an 'INTERRUPT' subdevice. + */ +static int +pcmmio_inttrig_start_intr(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum) +{ + unsigned long flags; + int event = 0; + + if (trignum != 0) + return -EINVAL; + + spin_lock_irqsave(&subpriv->dio.intr.spinlock, flags); + s->async->inttrig = 0; + if (subpriv->dio.intr.active) + event = pcmmio_start_intr(dev, s); + spin_unlock_irqrestore(&subpriv->dio.intr.spinlock, flags); + + if (event) + comedi_event(dev, s); + + return 1; +} + +/* + * 'do_cmd' function for an 'INTERRUPT' subdevice. + */ +static int pcmmio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + int event = 0; + + spin_lock_irqsave(&subpriv->dio.intr.spinlock, flags); + subpriv->dio.intr.active = 1; + + /* Set up end of acquisition. */ + switch (cmd->stop_src) { + case TRIG_COUNT: + subpriv->dio.intr.continuous = 0; + subpriv->dio.intr.stop_count = cmd->stop_arg; + break; + default: + /* TRIG_NONE */ + subpriv->dio.intr.continuous = 1; + subpriv->dio.intr.stop_count = 0; + break; + } + + /* Set up start of acquisition. */ + switch (cmd->start_src) { + case TRIG_INT: + s->async->inttrig = pcmmio_inttrig_start_intr; + break; + default: + /* TRIG_NOW */ + event = pcmmio_start_intr(dev, s); + break; + } + spin_unlock_irqrestore(&subpriv->dio.intr.spinlock, flags); + + if (event) + comedi_event(dev, s); + + return 0; +} + +static int +pcmmio_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + return comedi_pcm_cmdtest(dev, s, cmd); +} + +static int adc_wait_ready(unsigned long iobase) +{ + unsigned long retry = 100000; + while (retry--) + if (inb(iobase + 3) & 0x80) + return 0; + return 1; +} + +/* All this is for AI and AO */ +static int ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + unsigned long iobase = subpriv->iobase; + + /* + 1. write the CMD byte (to BASE+2) + 2. read junk lo byte (BASE+0) + 3. read junk hi byte (BASE+1) + 4. (mux settled so) write CMD byte again (BASE+2) + 5. read valid lo byte(BASE+0) + 6. read valid hi byte(BASE+1) + + Additionally note that the BASE += 4 if the channel >= 8 + */ + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + unsigned chan = CR_CHAN(insn->chanspec), range = + CR_RANGE(insn->chanspec), aref = CR_AREF(insn->chanspec); + unsigned char command_byte = 0; + unsigned iooffset = 0; + short sample, adc_adjust = 0; + + if (chan > 7) + chan -= 8, iooffset = 4; /* + * use the second dword + * for channels > 7 + */ + + if (aref != AREF_DIFF) { + aref = AREF_GROUND; + command_byte |= 1 << 7; /* + * set bit 7 to indicate + * single-ended + */ + } + if (range < 2) + adc_adjust = 0x8000; /* + * bipolar ranges + * (-5,5 .. -10,10 need to be + * adjusted -- that is.. they + * need to wrap around by + * adding 0x8000 + */ + + if (chan % 2) { + command_byte |= 1 << 6; /* + * odd-numbered channels + * have bit 6 set + */ + } + + /* select the channel, bits 4-5 == chan/2 */ + command_byte |= ((chan / 2) & 0x3) << 4; + + /* set the range, bits 2-3 */ + command_byte |= (range & 0x3) << 2; + + /* need to do this twice to make sure mux settled */ + /* chan/range/aref select */ + outb(command_byte, iobase + iooffset + 2); + + /* wait for the adc to say it finised the conversion */ + adc_wait_ready(iobase + iooffset); + + /* select the chan/range/aref AGAIN */ + outb(command_byte, iobase + iooffset + 2); + + adc_wait_ready(iobase + iooffset); + + /* read data lo byte */ + sample = inb(iobase + iooffset + 0); + + /* read data hi byte */ + sample |= inb(iobase + iooffset + 1) << 8; + sample += adc_adjust; /* adjustment .. munge data */ + data[n] = sample; + } + /* return the number of samples read/written */ + return n; +} + +static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + for (n = 0; n < insn->n; n++) { + unsigned chan = CR_CHAN(insn->chanspec); + if (chan < s->n_chan) + data[n] = subpriv->ao.shadow_samples[chan]; + } + return n; +} + +static int wait_dac_ready(unsigned long iobase) +{ + unsigned long retry = 100000L; + + /* This may seem like an absurd way to handle waiting and violates the + "no busy waiting" policy. The fact is that the hardware is + normally so fast that we usually only need one time through the loop + anyway. The longer timeout is for rare occasions and for detecting + non-existent hardware. */ + + while (retry--) { + if (inb(iobase + 3) & 0x80) + return 0; + + } + return 1; +} + +static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + unsigned iobase = subpriv->iobase, iooffset = 0; + + for (n = 0; n < insn->n; n++) { + unsigned chan = CR_CHAN(insn->chanspec), range = + CR_RANGE(insn->chanspec); + if (chan < s->n_chan) { + unsigned char command_byte = 0, range_byte = + range & ((1 << 4) - 1); + if (chan >= 4) + chan -= 4, iooffset += 4; + /* set the range.. */ + outb(range_byte, iobase + iooffset + 0); + outb(0, iobase + iooffset + 1); + + /* tell it to begin */ + command_byte = (chan << 1) | 0x60; + outb(command_byte, iobase + iooffset + 2); + + wait_dac_ready(iobase + iooffset); + + /* low order byte */ + outb(data[n] & 0xff, iobase + iooffset + 0); + + /* high order byte */ + outb((data[n] >> 8) & 0xff, iobase + iooffset + 1); + + /* + * set bit 4 of command byte to indicate + * data is loaded and trigger conversion + */ + command_byte = 0x70 | (chan << 1); + /* trigger converion */ + outb(command_byte, iobase + iooffset + 2); + + wait_dac_ready(iobase + iooffset); + + /* save to shadow register for ao_rinsn */ + subpriv->ao.shadow_samples[chan] = data[n]; + } + } + return n; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __init driver_init_module(void) +{ + return comedi_driver_register(&driver); +} + +static void __exit driver_cleanup_module(void) +{ + comedi_driver_unregister(&driver); +} + +module_init(driver_init_module); +module_exit(driver_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/pcmuio.c b/drivers/staging/comedi/drivers/pcmuio.c new file mode 100644 index 00000000..661ba2e0 --- /dev/null +++ b/drivers/staging/comedi/drivers/pcmuio.c @@ -0,0 +1,1036 @@ +/* + comedi/drivers/pcmuio.c + Driver for Winsystems PC-104 based 48-channel and 96-channel DIO boards. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2006 Calin A. Culianu + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* +Driver: pcmuio +Description: A driver for the PCM-UIO48A and PCM-UIO96A boards from Winsystems. +Devices: [Winsystems] PCM-UIO48A (pcmuio48), PCM-UIO96A (pcmuio96) +Author: Calin Culianu +Updated: Fri, 13 Jan 2006 12:01:01 -0500 +Status: works + +A driver for the relatively straightforward-to-program PCM-UIO48A and +PCM-UIO96A boards from Winsystems. These boards use either one or two +(in the 96-DIO version) WS16C48 ASIC HighDensity I/O Chips (HDIO). +This chip is interesting in that each I/O line is individually +programmable for INPUT or OUTPUT (thus comedi_dio_config can be done +on a per-channel basis). Also, each chip supports edge-triggered +interrupts for the first 24 I/O lines. Of course, since the +96-channel version of the board has two ASICs, it can detect polarity +changes on up to 48 I/O lines. Since this is essentially an (non-PnP) +ISA board, I/O Address and IRQ selection are done through jumpers on +the board. You need to pass that information to this driver as the +first and second comedi_config option, respectively. Note that the +48-channel version uses 16 bytes of IO memory and the 96-channel +version uses 32-bytes (in case you are worried about conflicts). The +48-channel board is split into two 24-channel comedi subdevices. +The 96-channel board is split into 4 24-channel DIO subdevices. + +Note that IRQ support has been added, but it is untested. + +To use edge-detection IRQ support, pass the IRQs of both ASICS +(for the 96 channel version) or just 1 ASIC (for 48-channel version). +Then, use use comedi_commands with TRIG_NOW. +Your callback will be called each time an edge is triggered, and the data +values will be two sample_t's, which should be concatenated to form one +32-bit unsigned int. This value is the mask of channels that had +edges detected from your channel list. Note that the bits positions +in the mask correspond to positions in your chanlist when you specified +the command and *not* channel id's! + +To set the polarity of the edge-detection interrupts pass a nonzero value for +either CR_RANGE or CR_AREF for edge-up polarity, or a zero value for both +CR_RANGE and CR_AREF if you want edge-down polarity. + +In the 48-channel version: + +On subdev 0, the first 24 channels channels are edge-detect channels. + +In the 96-channel board you have the collowing channels that can do edge detection: + +subdev 0, channels 0-24 (first 24 channels of 1st ASIC) +subdev 2, channels 0-24 (first 24 channels of 2nd ASIC) + +Configuration Options: + [0] - I/O port base address + [1] - IRQ (for first ASIC, or first 24 channels) + [2] - IRQ for second ASIC (pcmuio96 only - IRQ for chans 48-72 .. can be the same as first irq!) +*/ + +#include +#include +#include "../comedidev.h" +#include "pcm_common.h" + +#include /* for PCI devices */ + +#define CHANS_PER_PORT 8 +#define PORTS_PER_ASIC 6 +#define INTR_PORTS_PER_ASIC 3 +#define MAX_CHANS_PER_SUBDEV 24 /* number of channels per comedi subdevice */ +#define PORTS_PER_SUBDEV (MAX_CHANS_PER_SUBDEV/CHANS_PER_PORT) +#define CHANS_PER_ASIC (CHANS_PER_PORT*PORTS_PER_ASIC) +#define INTR_CHANS_PER_ASIC 24 +#define INTR_PORTS_PER_SUBDEV (INTR_CHANS_PER_ASIC/CHANS_PER_PORT) +#define MAX_DIO_CHANS (PORTS_PER_ASIC*2*CHANS_PER_PORT) +#define MAX_ASICS (MAX_DIO_CHANS/CHANS_PER_ASIC) +#define SDEV_NO ((int)(s - dev->subdevices)) +#define CALC_N_SUBDEVS(nchans) ((nchans)/MAX_CHANS_PER_SUBDEV + (!!((nchans)%MAX_CHANS_PER_SUBDEV)) /*+ (nchans > INTR_CHANS_PER_ASIC ? 2 : 1)*/) +/* IO Memory sizes */ +#define ASIC_IOSIZE (0x10) +#define PCMUIO48_IOSIZE ASIC_IOSIZE +#define PCMUIO96_IOSIZE (ASIC_IOSIZE*2) + +/* Some offsets - these are all in the 16byte IO memory offset from + the base address. Note that there is a paging scheme to swap out + offsets 0x8-0xA using the PAGELOCK register. See the table below. + + Register(s) Pages R/W? Description + -------------------------------------------------------------- + REG_PORTx All R/W Read/Write/Configure IO + REG_INT_PENDING All ReadOnly Quickly see which INT_IDx has int. + REG_PAGELOCK All WriteOnly Select a page + REG_POLx Pg. 1 only WriteOnly Select edge-detection polarity + REG_ENABx Pg. 2 only WriteOnly Enable/Disable edge-detect. int. + REG_INT_IDx Pg. 3 only R/W See which ports/bits have ints. + */ +#define REG_PORT0 0x0 +#define REG_PORT1 0x1 +#define REG_PORT2 0x2 +#define REG_PORT3 0x3 +#define REG_PORT4 0x4 +#define REG_PORT5 0x5 +#define REG_INT_PENDING 0x6 +#define REG_PAGELOCK 0x7 /* page selector register, upper 2 bits select a page + and bits 0-5 are used to 'lock down' a particular + port above to make it readonly. */ +#define REG_POL0 0x8 +#define REG_POL1 0x9 +#define REG_POL2 0xA +#define REG_ENAB0 0x8 +#define REG_ENAB1 0x9 +#define REG_ENAB2 0xA +#define REG_INT_ID0 0x8 +#define REG_INT_ID1 0x9 +#define REG_INT_ID2 0xA + +#define NUM_PAGED_REGS 3 +#define NUM_PAGES 4 +#define FIRST_PAGED_REG 0x8 +#define REG_PAGE_BITOFFSET 6 +#define REG_LOCK_BITOFFSET 0 +#define REG_PAGE_MASK (~((0x1<board_ptr) + +/* this structure is for data unique to this subdevice. */ +struct pcmuio_subdev_private { + /* mapping of halfwords (bytes) in port/chanarray to iobase */ + unsigned long iobases[PORTS_PER_SUBDEV]; + + /* The below is only used for intr subdevices */ + struct { + int asic; /* if non-negative, this subdev has an interrupt asic */ + int first_chan; /* if nonnegative, the first channel id for + interrupts. */ + int num_asic_chans; /* the number of asic channels in this subdev + that have interrutps */ + int asic_chan; /* if nonnegative, the first channel id with + respect to the asic that has interrupts */ + int enabled_mask; /* subdev-relative channel mask for channels + we are interested in */ + int active; + int stop_count; + int continuous; + spinlock_t spinlock; + } intr; +}; + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. */ +struct pcmuio_private { + struct { + unsigned char pagelock; /* current page and lock */ + unsigned char pol[NUM_PAGED_REGS]; /* shadow of POLx registers */ + unsigned char enab[NUM_PAGED_REGS]; /* shadow of ENABx registers */ + int num; + unsigned long iobase; + unsigned int irq; + spinlock_t spinlock; + } asics[MAX_ASICS]; + struct pcmuio_subdev_private *sprivs; +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct pcmuio_private *)dev->private) +#define subpriv ((struct pcmuio_subdev_private *)s->private) +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int pcmuio_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int pcmuio_detach(struct comedi_device *dev); + +static struct comedi_driver driver = { + .driver_name = "pcmuio", + .module = THIS_MODULE, + .attach = pcmuio_attach, + .detach = pcmuio_detach, +/* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in pcmuio_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + .board_name = &pcmuio_boards[0].name, + .offset = sizeof(struct pcmuio_board), + .num_names = ARRAY_SIZE(pcmuio_boards), +}; + +static int pcmuio_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int pcmuio_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static irqreturn_t interrupt_pcmuio(int irq, void *d); +static void pcmuio_stop_intr(struct comedi_device *, struct comedi_subdevice *); +static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s); +static int pcmuio_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int pcmuio_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); + +/* some helper functions to deal with specifics of this device's registers */ +static void init_asics(struct comedi_device *dev); /* sets up/clears ASIC chips to defaults */ +static void switch_page(struct comedi_device *dev, int asic, int page); +#ifdef notused +static void lock_port(struct comedi_device *dev, int asic, int port); +static void unlock_port(struct comedi_device *dev, int asic, int port); +#endif + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int sdev_no, chans_left, n_subdevs, port, asic, thisasic_chanct = 0; + unsigned long iobase; + unsigned int irq[MAX_ASICS]; + + iobase = it->options[0]; + irq[0] = it->options[1]; + irq[1] = it->options[2]; + + dev_dbg(dev->hw_dev, "comedi%d: %s: io: %lx attached\n", dev->minor, + driver.driver_name, iobase); + + dev->iobase = iobase; + + if (!iobase || !request_region(iobase, + thisboard->num_asics * ASIC_IOSIZE, + driver.driver_name)) { + dev_err(dev->hw_dev, "I/O port conflict\n"); + return -EIO; + } + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = thisboard->name; + +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct pcmuio_private)) < 0) { + dev_warn(dev->hw_dev, "cannot allocate private data structure\n"); + return -ENOMEM; + } + + for (asic = 0; asic < MAX_ASICS; ++asic) { + devpriv->asics[asic].num = asic; + devpriv->asics[asic].iobase = dev->iobase + asic * ASIC_IOSIZE; + devpriv->asics[asic].irq = 0; /* this gets actually set at the end of + this function when we + request_irqs */ + spin_lock_init(&devpriv->asics[asic].spinlock); + } + + chans_left = CHANS_PER_ASIC * thisboard->num_asics; + n_subdevs = CALC_N_SUBDEVS(chans_left); + devpriv->sprivs = + kcalloc(n_subdevs, sizeof(struct pcmuio_subdev_private), + GFP_KERNEL); + if (!devpriv->sprivs) { + dev_warn(dev->hw_dev, "cannot allocate subdevice private data structures\n"); + return -ENOMEM; + } + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + * + * Allocate 2 subdevs (32 + 16 DIO lines) or 3 32 DIO subdevs for the + * 96-channel version of the board. + */ + if (alloc_subdevices(dev, n_subdevs) < 0) { + dev_dbg(dev->hw_dev, "cannot allocate subdevice data structures\n"); + return -ENOMEM; + } + + port = 0; + asic = 0; + for (sdev_no = 0; sdev_no < (int)dev->n_subdevices; ++sdev_no) { + int byte_no; + + s = dev->subdevices + sdev_no; + s->private = devpriv->sprivs + sdev_no; + s->maxdata = 1; + s->range_table = &range_digital; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->type = COMEDI_SUBD_DIO; + s->insn_bits = pcmuio_dio_insn_bits; + s->insn_config = pcmuio_dio_insn_config; + s->n_chan = min(chans_left, MAX_CHANS_PER_SUBDEV); + subpriv->intr.asic = -1; + subpriv->intr.first_chan = -1; + subpriv->intr.asic_chan = -1; + subpriv->intr.num_asic_chans = -1; + subpriv->intr.active = 0; + s->len_chanlist = 1; + + /* save the ioport address for each 'port' of 8 channels in the + subdevice */ + for (byte_no = 0; byte_no < PORTS_PER_SUBDEV; ++byte_no, ++port) { + if (port >= PORTS_PER_ASIC) { + port = 0; + ++asic; + thisasic_chanct = 0; + } + subpriv->iobases[byte_no] = + devpriv->asics[asic].iobase + port; + + if (thisasic_chanct < + CHANS_PER_PORT * INTR_PORTS_PER_ASIC + && subpriv->intr.asic < 0) { + /* this is an interrupt subdevice, so setup the struct */ + subpriv->intr.asic = asic; + subpriv->intr.active = 0; + subpriv->intr.stop_count = 0; + subpriv->intr.first_chan = byte_no * 8; + subpriv->intr.asic_chan = thisasic_chanct; + subpriv->intr.num_asic_chans = + s->n_chan - subpriv->intr.first_chan; + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->cancel = pcmuio_cancel; + s->do_cmd = pcmuio_cmd; + s->do_cmdtest = pcmuio_cmdtest; + s->len_chanlist = subpriv->intr.num_asic_chans; + } + thisasic_chanct += CHANS_PER_PORT; + } + spin_lock_init(&subpriv->intr.spinlock); + + chans_left -= s->n_chan; + + if (!chans_left) { + asic = 0; /* reset the asic to our first asic, to do intr subdevs */ + port = 0; + } + + } + + init_asics(dev); /* clear out all the registers, basically */ + + for (asic = 0; irq[0] && asic < MAX_ASICS; ++asic) { + if (irq[asic] + && request_irq(irq[asic], interrupt_pcmuio, + IRQF_SHARED, thisboard->name, dev)) { + int i; + /* unroll the allocated irqs.. */ + for (i = asic - 1; i >= 0; --i) { + free_irq(irq[i], dev); + devpriv->asics[i].irq = irq[i] = 0; + } + irq[asic] = 0; + } + devpriv->asics[asic].irq = irq[asic]; + } + + dev->irq = irq[0]; /* grr.. wish comedi dev struct supported multiple + irqs.. */ + + if (irq[0]) { + dev_dbg(dev->hw_dev, "irq: %u\n", irq[0]); + if (irq[1] && thisboard->num_asics == 2) + dev_dbg(dev->hw_dev, "second ASIC irq: %u\n", irq[1]); + } else { + dev_dbg(dev->hw_dev, "(IRQ mode disabled)\n"); + } + + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int pcmuio_detach(struct comedi_device *dev) +{ + int i; + + dev_dbg(dev->hw_dev, "comedi%d: %s: remove\n", dev->minor, + driver.driver_name); + if (dev->iobase) + release_region(dev->iobase, ASIC_IOSIZE * thisboard->num_asics); + + for (i = 0; i < MAX_ASICS; ++i) { + if (devpriv->asics[i].irq) + free_irq(devpriv->asics[i].irq, dev); + } + + if (devpriv && devpriv->sprivs) + kfree(devpriv->sprivs); + + return 0; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +static int pcmuio_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int byte_no; + if (insn->n != 2) + return -EINVAL; + + /* NOTE: + reading a 0 means this channel was high + writine a 0 sets the channel high + reading a 1 means this channel was low + writing a 1 means set this channel low + + Therefore everything is always inverted. */ + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + +#ifdef DAMMIT_ITS_BROKEN + /* DEBUG */ + dev_dbg(dev->hw_dev, "write mask: %08x data: %08x\n", data[0], + data[1]); +#endif + + s->state = 0; + + for (byte_no = 0; byte_no < s->n_chan / CHANS_PER_PORT; ++byte_no) { + /* address of 8-bit port */ + unsigned long ioaddr = subpriv->iobases[byte_no], + /* bit offset of port in 32-bit doubleword */ + offset = byte_no * 8; + /* this 8-bit port's data */ + unsigned char byte = 0, + /* The write mask for this port (if any) */ + write_mask_byte = (data[0] >> offset) & 0xff, + /* The data byte for this port */ + data_byte = (data[1] >> offset) & 0xff; + + byte = inb(ioaddr); /* read all 8-bits for this port */ + +#ifdef DAMMIT_ITS_BROKEN + /* DEBUG */ + printk + ("byte %d wmb %02x db %02x offset %02d io %04x, data_in %02x ", + byte_no, (unsigned)write_mask_byte, (unsigned)data_byte, + offset, ioaddr, (unsigned)byte); +#endif + + if (write_mask_byte) { + /* this byte has some write_bits -- so set the output lines */ + byte &= ~write_mask_byte; /* clear bits for write mask */ + byte |= ~data_byte & write_mask_byte; /* set to inverted data_byte */ + /* Write out the new digital output state */ + outb(byte, ioaddr); + } +#ifdef DAMMIT_ITS_BROKEN + /* DEBUG */ + dev_dbg(dev->hw_dev, "data_out_byte %02x\n", (unsigned)byte); +#endif + /* save the digital input lines for this byte.. */ + s->state |= ((unsigned int)byte) << offset; + } + + /* now return the DIO lines to data[1] - note they came inverted! */ + data[1] = ~s->state; + +#ifdef DAMMIT_ITS_BROKEN + /* DEBUG */ + dev_dbg(dev->hw_dev, "s->state %08x data_out %08x\n", s->state, + data[1]); +#endif + + return 2; +} + +/* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ +static int pcmuio_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec), byte_no = chan / 8, bit_no = + chan % 8; + unsigned long ioaddr; + unsigned char byte; + + /* Compute ioaddr for this channel */ + ioaddr = subpriv->iobases[byte_no]; + + /* NOTE: + writing a 0 an IO channel's bit sets the channel to INPUT + and pulls the line high as well + + writing a 1 to an IO channel's bit pulls the line low + + All channels are implicitly always in OUTPUT mode -- but when + they are high they can be considered to be in INPUT mode.. + + Thus, we only force channels low if the config request was INPUT, + otherwise we do nothing to the hardware. */ + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + /* save to io_bits -- don't actually do anything since + all input channels are also output channels... */ + s->io_bits |= 1 << chan; + break; + case INSN_CONFIG_DIO_INPUT: + /* write a 0 to the actual register representing the channel + to set it to 'input'. 0 means "float high". */ + byte = inb(ioaddr); + byte &= ~(1 << bit_no); + /**< set input channel to '0' */ + + /* write out byte -- this is the only time we actually affect the + hardware as all channels are implicitly output -- but input + channels are set to float-high */ + outb(byte, ioaddr); + + /* save to io_bits */ + s->io_bits &= ~(1 << chan); + break; + + case INSN_CONFIG_DIO_QUERY: + /* retrieve from shadow register */ + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + + default: + return -EINVAL; + break; + } + + return insn->n; +} + +static void init_asics(struct comedi_device *dev) +{ /* sets up an + ASIC chip to defaults */ + int asic; + + for (asic = 0; asic < thisboard->num_asics; ++asic) { + int port, page; + unsigned long baseaddr = dev->iobase + asic * ASIC_IOSIZE; + + switch_page(dev, asic, 0); /* switch back to page 0 */ + + /* first, clear all the DIO port bits */ + for (port = 0; port < PORTS_PER_ASIC; ++port) + outb(0, baseaddr + REG_PORT0 + port); + + /* Next, clear all the paged registers for each page */ + for (page = 1; page < NUM_PAGES; ++page) { + int reg; + /* now clear all the paged registers */ + switch_page(dev, asic, page); + for (reg = FIRST_PAGED_REG; + reg < FIRST_PAGED_REG + NUM_PAGED_REGS; ++reg) + outb(0, baseaddr + reg); + } + + /* DEBUG set rising edge interrupts on port0 of both asics */ + /*switch_page(dev, asic, PAGE_POL); + outb(0xff, baseaddr + REG_POL0); + switch_page(dev, asic, PAGE_ENAB); + outb(0xff, baseaddr + REG_ENAB0); */ + /* END DEBUG */ + + switch_page(dev, asic, 0); /* switch back to default page 0 */ + + } +} + +static void switch_page(struct comedi_device *dev, int asic, int page) +{ + if (asic < 0 || asic >= thisboard->num_asics) + return; /* paranoia */ + if (page < 0 || page >= NUM_PAGES) + return; /* more paranoia */ + + devpriv->asics[asic].pagelock &= ~REG_PAGE_MASK; + devpriv->asics[asic].pagelock |= page << REG_PAGE_BITOFFSET; + + /* now write out the shadow register */ + outb(devpriv->asics[asic].pagelock, + dev->iobase + ASIC_IOSIZE * asic + REG_PAGELOCK); +} + +#ifdef notused +static void lock_port(struct comedi_device *dev, int asic, int port) +{ + if (asic < 0 || asic >= thisboard->num_asics) + return; /* paranoia */ + if (port < 0 || port >= PORTS_PER_ASIC) + return; /* more paranoia */ + + devpriv->asics[asic].pagelock |= 0x1 << port; + /* now write out the shadow register */ + outb(devpriv->asics[asic].pagelock, + dev->iobase + ASIC_IOSIZE * asic + REG_PAGELOCK); +} + +static void unlock_port(struct comedi_device *dev, int asic, int port) +{ + if (asic < 0 || asic >= thisboard->num_asics) + return; /* paranoia */ + if (port < 0 || port >= PORTS_PER_ASIC) + return; /* more paranoia */ + devpriv->asics[asic].pagelock &= ~(0x1 << port) | REG_LOCK_MASK; + /* now write out the shadow register */ + outb(devpriv->asics[asic].pagelock, + dev->iobase + ASIC_IOSIZE * asic + REG_PAGELOCK); +} +#endif /* notused */ + +static irqreturn_t interrupt_pcmuio(int irq, void *d) +{ + int asic, got1 = 0; + struct comedi_device *dev = (struct comedi_device *)d; + + for (asic = 0; asic < MAX_ASICS; ++asic) { + if (irq == devpriv->asics[asic].irq) { + unsigned long flags; + unsigned triggered = 0; + unsigned long iobase = devpriv->asics[asic].iobase; + /* it is an interrupt for ASIC #asic */ + unsigned char int_pend; + + spin_lock_irqsave(&devpriv->asics[asic].spinlock, + flags); + + int_pend = inb(iobase + REG_INT_PENDING) & 0x07; + + if (int_pend) { + int port; + for (port = 0; port < INTR_PORTS_PER_ASIC; + ++port) { + if (int_pend & (0x1 << port)) { + unsigned char + io_lines_with_edges = 0; + switch_page(dev, asic, + PAGE_INT_ID); + io_lines_with_edges = + inb(iobase + + REG_INT_ID0 + port); + + if (io_lines_with_edges) + /* clear pending interrupt */ + outb(0, iobase + + REG_INT_ID0 + + port); + + triggered |= + io_lines_with_edges << + port * 8; + } + } + + ++got1; + } + + spin_unlock_irqrestore(&devpriv->asics[asic].spinlock, + flags); + + if (triggered) { + struct comedi_subdevice *s; + /* TODO here: dispatch io lines to subdevs with commands.. */ + printk + ("PCMUIO DEBUG: got edge detect interrupt %d asic %d which_chans: %06x\n", + irq, asic, triggered); + for (s = dev->subdevices; + s < dev->subdevices + dev->n_subdevices; + ++s) { + if (subpriv->intr.asic == asic) { /* this is an interrupt subdev, and it matches this asic! */ + unsigned long flags; + unsigned oldevents; + + spin_lock_irqsave(&subpriv-> + intr.spinlock, + flags); + + oldevents = s->async->events; + + if (subpriv->intr.active) { + unsigned mytrig = + ((triggered >> + subpriv->intr.asic_chan) + & + ((0x1 << subpriv-> + intr. + num_asic_chans) - + 1)) << subpriv-> + intr.first_chan; + if (mytrig & + subpriv->intr.enabled_mask) + { + unsigned int val + = 0; + unsigned int n, + ch, len; + + len = + s-> + async->cmd.chanlist_len; + for (n = 0; + n < len; + n++) { + ch = CR_CHAN(s->async->cmd.chanlist[n]); + if (mytrig & (1U << ch)) { + val |= (1U << n); + } + } + /* Write the scan to the buffer. */ + if (comedi_buf_put(s->async, ((short *)&val)[0]) + && + comedi_buf_put + (s->async, + ((short *) + &val)[1])) + { + s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS); + } else { + /* Overflow! Stop acquisition!! */ + /* TODO: STOP_ACQUISITION_CALL_HERE!! */ + pcmuio_stop_intr + (dev, + s); + } + + /* Check for end of acquisition. */ + if (!subpriv->intr.continuous) { + /* stop_src == TRIG_COUNT */ + if (subpriv->intr.stop_count > 0) { + subpriv->intr.stop_count--; + if (subpriv->intr.stop_count == 0) { + s->async->events |= COMEDI_CB_EOA; + /* TODO: STOP_ACQUISITION_CALL_HERE!! */ + pcmuio_stop_intr + (dev, + s); + } + } + } + } + } + + spin_unlock_irqrestore + (&subpriv->intr.spinlock, + flags); + + if (oldevents != + s->async->events) { + comedi_event(dev, s); + } + + } + + } + } + + } + } + if (!got1) + return IRQ_NONE; /* interrupt from other source */ + return IRQ_HANDLED; +} + +static void pcmuio_stop_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int nports, firstport, asic, port; + + asic = subpriv->intr.asic; + if (asic < 0) + return; /* not an interrupt subdev */ + + subpriv->intr.enabled_mask = 0; + subpriv->intr.active = 0; + s->async->inttrig = 0; + nports = subpriv->intr.num_asic_chans / CHANS_PER_PORT; + firstport = subpriv->intr.asic_chan / CHANS_PER_PORT; + switch_page(dev, asic, PAGE_ENAB); + for (port = firstport; port < firstport + nports; ++port) { + /* disable all intrs for this subdev.. */ + outb(0, devpriv->asics[asic].iobase + REG_ENAB0 + port); + } +} + +static int pcmuio_start_intr(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + if (!subpriv->intr.continuous && subpriv->intr.stop_count == 0) { + /* An empty acquisition! */ + s->async->events |= COMEDI_CB_EOA; + subpriv->intr.active = 0; + return 1; + } else { + unsigned bits = 0, pol_bits = 0, n; + int nports, firstport, asic, port; + struct comedi_cmd *cmd = &s->async->cmd; + + asic = subpriv->intr.asic; + if (asic < 0) + return 1; /* not an interrupt + subdev */ + subpriv->intr.enabled_mask = 0; + subpriv->intr.active = 1; + nports = subpriv->intr.num_asic_chans / CHANS_PER_PORT; + firstport = subpriv->intr.asic_chan / CHANS_PER_PORT; + if (cmd->chanlist) { + for (n = 0; n < cmd->chanlist_len; n++) { + bits |= (1U << CR_CHAN(cmd->chanlist[n])); + pol_bits |= (CR_AREF(cmd->chanlist[n]) + || CR_RANGE(cmd-> + chanlist[n]) ? 1U : 0U) + << CR_CHAN(cmd->chanlist[n]); + } + } + bits &= ((0x1 << subpriv->intr.num_asic_chans) - + 1) << subpriv->intr.first_chan; + subpriv->intr.enabled_mask = bits; + + switch_page(dev, asic, PAGE_ENAB); + for (port = firstport; port < firstport + nports; ++port) { + unsigned enab = + bits >> (subpriv->intr.first_chan + (port - + firstport) * + 8) & 0xff, pol = + pol_bits >> (subpriv->intr.first_chan + + (port - firstport) * 8) & 0xff; + /* set enab intrs for this subdev.. */ + outb(enab, + devpriv->asics[asic].iobase + REG_ENAB0 + port); + switch_page(dev, asic, PAGE_POL); + outb(pol, + devpriv->asics[asic].iobase + REG_ENAB0 + port); + } + } + return 0; +} + +static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + unsigned long flags; + + spin_lock_irqsave(&subpriv->intr.spinlock, flags); + if (subpriv->intr.active) + pcmuio_stop_intr(dev, s); + spin_unlock_irqrestore(&subpriv->intr.spinlock, flags); + + return 0; +} + +/* + * Internal trigger function to start acquisition for an 'INTERRUPT' subdevice. + */ +static int +pcmuio_inttrig_start_intr(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned int trignum) +{ + unsigned long flags; + int event = 0; + + if (trignum != 0) + return -EINVAL; + + spin_lock_irqsave(&subpriv->intr.spinlock, flags); + s->async->inttrig = 0; + if (subpriv->intr.active) + event = pcmuio_start_intr(dev, s); + + spin_unlock_irqrestore(&subpriv->intr.spinlock, flags); + + if (event) + comedi_event(dev, s); + + return 1; +} + +/* + * 'do_cmd' function for an 'INTERRUPT' subdevice. + */ +static int pcmuio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned long flags; + int event = 0; + + spin_lock_irqsave(&subpriv->intr.spinlock, flags); + subpriv->intr.active = 1; + + /* Set up end of acquisition. */ + switch (cmd->stop_src) { + case TRIG_COUNT: + subpriv->intr.continuous = 0; + subpriv->intr.stop_count = cmd->stop_arg; + break; + default: + /* TRIG_NONE */ + subpriv->intr.continuous = 1; + subpriv->intr.stop_count = 0; + break; + } + + /* Set up start of acquisition. */ + switch (cmd->start_src) { + case TRIG_INT: + s->async->inttrig = pcmuio_inttrig_start_intr; + break; + default: + /* TRIG_NOW */ + event = pcmuio_start_intr(dev, s); + break; + } + spin_unlock_irqrestore(&subpriv->intr.spinlock, flags); + + if (event) + comedi_event(dev, s); + + return 0; +} + +static int +pcmuio_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + return comedi_pcm_cmdtest(dev, s, cmd); +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __init driver_init_module(void) +{ + return comedi_driver_register(&driver); +} + +static void __exit driver_cleanup_module(void) +{ + comedi_driver_unregister(&driver); +} + +module_init(driver_init_module); +module_exit(driver_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/plx9052.h b/drivers/staging/comedi/drivers/plx9052.h new file mode 100644 index 00000000..5894739f --- /dev/null +++ b/drivers/staging/comedi/drivers/plx9052.h @@ -0,0 +1,86 @@ +/* + comedi/drivers/plx9052.h + Definitions for the PLX-9052 PCI interface chip + + Copyright (C) 2002 MEV Ltd. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _PLX9052_H_ +#define _PLX9052_H_ + +/* + * PLX PCI9052 INTCSR register. + */ +#define PLX9052_INTCSR 0x4C /* Offset in Local Configuration Registers */ +/* Local Interrupt 1 Enable */ +#define PLX9052_INTCSR_LI1ENAB_MASK 0x0001 +#define PLX9052_INTCSR_LI1ENAB_DISABLED 0x0000 +#define PLX9052_INTCSR_LI1ENAB_ENABLED 0x0001 +/* Local Interrupt 1 Polarity */ +#define PLX9052_INTCSR_LI1POL_MASK 0x0002 +#define PLX9052_INTCSR_LI1POL_LOW 0x0000 +#define PLX9052_INTCSR_LI1POL_HIGH 0x0002 +/* Local Interrupt 1 Status (read-only) */ +#define PLX9052_INTCSR_LI1STAT_MASK 0x0004 +#define PLX9052_INTCSR_LI1STAT_INACTIVE 0x0000 +#define PLX9052_INTCSR_LI1STAT_ACTIVE 0x0004 +/* Local Interrupt 2 Enable */ +#define PLX9052_INTCSR_LI2ENAB_MASK 0x0008 +#define PLX9052_INTCSR_LI2ENAB_DISABLED 0x0000 +#define PLX9052_INTCSR_LI2ENAB_ENABLED 0x0008 +/* Local Interrupt 2 Polarity */ +#define PLX9052_INTCSR_LI2POL_MASK 0x0010 +#define PLX9052_INTCSR_LI2POL_LOW 0x0000 +#define PLX9052_INTCSR_LI2POL_HIGH 0x0010 +/* Local Interrupt 2 Status (read-only) */ +#define PLX9052_INTCSR_LI2STAT_MASK 0x0020 +#define PLX9052_INTCSR_LI2STAT_INACTIVE 0x0000 +#define PLX9052_INTCSR_LI2STAT_ACTIVE 0x0020 +/* PCI Interrupt Enable */ +#define PLX9052_INTCSR_PCIENAB_MASK 0x0040 +#define PLX9052_INTCSR_PCIENAB_DISABLED 0x0000 +#define PLX9052_INTCSR_PCIENAB_ENABLED 0x0040 +/* Software Interrupt */ +#define PLX9052_INTCSR_SOFTINT_MASK 0x0080 +#define PLX9052_INTCSR_SOFTINT_UNASSERTED 0x0000 +#define PLX9052_INTCSR_SOFTINT_ASSERTED 0x0080 +/* Local Interrupt 1 Select Enable */ +#define PLX9052_INTCSR_LI1SEL_MASK 0x0100 +#define PLX9052_INTCSR_LI1SEL_LEVEL 0x0000 +#define PLX9052_INTCSR_LI1SEL_EDGE 0x0100 +/* Local Interrupt 2 Select Enable */ +#define PLX9052_INTCSR_LI2SEL_MASK 0x0200 +#define PLX9052_INTCSR_LI2SEL_LEVEL 0x0000 +#define PLX9052_INTCSR_LI2SEL_EDGE 0x0200 +/* Local Edge Triggerable Interrupt 1 Clear Bit */ +#define PLX9052_INTCSR_LI1CLRINT_MASK 0x0400 +#define PLX9052_INTCSR_LI1CLRINT_UNASSERTED 0x0000 +#define PLX9052_INTCSR_LI1CLRINT_ASSERTED 0x0400 +/* Local Edge Triggerable Interrupt 2 Clear Bit */ +#define PLX9052_INTCSR_LI2CLRINT_MASK 0x0800 +#define PLX9052_INTCSR_LI2CLRINT_UNASSERTED 0x0000 +#define PLX9052_INTCSR_LI2CLRINT_ASSERTED 0x0800 +/* ISA Interface Mode Enable (read-only over PCI bus) */ +#define PLX9052_INTCSR_ISAMODE_MASK 0x1000 +#define PLX9052_INTCSR_ISAMODE_DISABLED 0x0000 +#define PLX9052_INTCSR_ISAMODE_ENABLED 0x1000 + +#endif /* _PLX9052_H_ */ diff --git a/drivers/staging/comedi/drivers/plx9080.h b/drivers/staging/comedi/drivers/plx9080.h new file mode 100644 index 00000000..0d254a1b --- /dev/null +++ b/drivers/staging/comedi/drivers/plx9080.h @@ -0,0 +1,429 @@ +/* plx9080.h + * + * Copyright (C) 2002,2003 Frank Mori Hess + * + * I modified this file from the plx9060.h header for the + * wanXL device driver in the linux kernel, + * for the register offsets and bit definitions. Made minor modifications, + * added plx9080 registers and + * stripped out stuff that was specifically for the wanXL driver. + * Note: I've only made sure the definitions are correct as far + * as I make use of them. There are still various plx9060-isms + * left in this header file. + * + ******************************************************************** + * + * Copyright (C) 1999 RG Studio s.c. + * Written by Krzysztof Halasa + * + * Portions (C) SBE Inc., used by permission. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef __COMEDI_PLX9080_H +#define __COMEDI_PLX9080_H + +/* descriptor block used for chained dma transfers */ +struct plx_dma_desc { + volatile uint32_t pci_start_addr; + volatile uint32_t local_start_addr; + /* transfer_size is in bytes, only first 23 bits of register are used */ + volatile uint32_t transfer_size; + /* address of next descriptor (quad word aligned), plus some + * additional bits (see PLX_DMA0_DESCRIPTOR_REG) */ + volatile uint32_t next; +}; + +/********************************************************************** +** Register Offsets and Bit Definitions +** +** Note: All offsets zero relative. IE. Some standard base address +** must be added to the Register Number to properly access the register. +** +**********************************************************************/ + +#define PLX_LAS0RNG_REG 0x0000 /* L, Local Addr Space 0 Range Register */ +#define PLX_LAS1RNG_REG 0x00f0 /* L, Local Addr Space 1 Range Register */ +#define LRNG_IO 0x00000001 /* Map to: 1=I/O, 0=Mem */ +#define LRNG_ANY32 0x00000000 /* Locate anywhere in 32 bit */ +#define LRNG_LT1MB 0x00000002 /* Locate in 1st meg */ +#define LRNG_ANY64 0x00000004 /* Locate anywhere in 64 bit */ +#define LRNG_MEM_MASK 0xfffffff0 /* bits that specify range for memory io */ +#define LRNG_IO_MASK 0xfffffffa /* bits that specify range for normal io */ + +#define PLX_LAS0MAP_REG 0x0004 /* L, Local Addr Space 0 Remap Register */ +#define PLX_LAS1MAP_REG 0x00f4 /* L, Local Addr Space 1 Remap Register */ +#define LMAP_EN 0x00000001 /* Enable slave decode */ +#define LMAP_MEM_MASK 0xfffffff0 /* bits that specify decode for memory io */ +#define LMAP_IO_MASK 0xfffffffa /* bits that specify decode bits for normal io */ + +/* Mode/Arbitration Register. +*/ +#define PLX_MARB_REG 0x8 /* L, Local Arbitration Register */ +#define PLX_DMAARB_REG 0xac +enum marb_bits { + MARB_LLT_MASK = 0x000000ff, /* Local Bus Latency Timer */ + MARB_LPT_MASK = 0x0000ff00, /* Local Bus Pause Timer */ + MARB_LTEN = 0x00010000, /* Latency Timer Enable */ + MARB_LPEN = 0x00020000, /* Pause Timer Enable */ + MARB_BREQ = 0x00040000, /* Local Bus BREQ Enable */ + MARB_DMA_PRIORITY_MASK = 0x00180000, + MARB_LBDS_GIVE_UP_BUS_MODE = 0x00200000, /* local bus direct slave give up bus mode */ + MARB_DS_LLOCK_ENABLE = 0x00400000, /* direct slave LLOCKo# enable */ + MARB_PCI_REQUEST_MODE = 0x00800000, + MARB_PCIv21_MODE = 0x01000000, /* pci specification v2.1 mode */ + MARB_PCI_READ_NO_WRITE_MODE = 0x02000000, + MARB_PCI_READ_WITH_WRITE_FLUSH_MODE = 0x04000000, + MARB_GATE_TIMER_WITH_BREQ = 0x08000000, /* gate local bus latency timer with BREQ */ + MARB_PCI_READ_NO_FLUSH_MODE = 0x10000000, + MARB_USE_SUBSYSTEM_IDS = 0x20000000, +}; + +#define PLX_BIGEND_REG 0xc +enum bigend_bits { + BIGEND_CONFIG = 0x1, /* use big endian ordering for configuration register accesses */ + BIGEND_DIRECT_MASTER = 0x2, + BIGEND_DIRECT_SLAVE_LOCAL0 = 0x4, + BIGEND_ROM = 0x8, + BIGEND_BYTE_LANE = 0x10, /* use byte lane consisting of most significant bits instead of least significant */ + BIGEND_DIRECT_SLAVE_LOCAL1 = 0x20, + BIGEND_DMA1 = 0x40, + BIGEND_DMA0 = 0x80, +}; + +/* Note: The Expansion ROM stuff is only relevant to the PC environment. +** This expansion ROM code is executed by the host CPU at boot time. +** For this reason no bit definitions are provided here. +*/ +#define PLX_ROMRNG_REG 0x0010 /* L, Expn ROM Space Range Register */ +#define PLX_ROMMAP_REG 0x0014 /* L, Local Addr Space Range Register */ + +#define PLX_REGION0_REG 0x0018 /* L, Local Bus Region 0 Descriptor */ +#define RGN_WIDTH 0x00000002 /* Local bus width bits */ +#define RGN_8BITS 0x00000000 /* 08 bit Local Bus */ +#define RGN_16BITS 0x00000001 /* 16 bit Local Bus */ +#define RGN_32BITS 0x00000002 /* 32 bit Local Bus */ +#define RGN_MWS 0x0000003C /* Memory Access Wait States */ +#define RGN_0MWS 0x00000000 +#define RGN_1MWS 0x00000004 +#define RGN_2MWS 0x00000008 +#define RGN_3MWS 0x0000000C +#define RGN_4MWS 0x00000010 +#define RGN_6MWS 0x00000018 +#define RGN_8MWS 0x00000020 +#define RGN_MRE 0x00000040 /* Memory Space Ready Input Enable */ +#define RGN_MBE 0x00000080 /* Memory Space Bterm Input Enable */ +#define RGN_READ_PREFETCH_DISABLE 0x00000100 +#define RGN_ROM_PREFETCH_DISABLE 0x00000200 +#define RGN_READ_PREFETCH_COUNT_ENABLE 0x00000400 +#define RGN_RWS 0x003C0000 /* Expn ROM Wait States */ +#define RGN_RRE 0x00400000 /* ROM Space Ready Input Enable */ +#define RGN_RBE 0x00800000 /* ROM Space Bterm Input Enable */ +#define RGN_MBEN 0x01000000 /* Memory Space Burst Enable */ +#define RGN_RBEN 0x04000000 /* ROM Space Burst Enable */ +#define RGN_THROT 0x08000000 /* De-assert TRDY when FIFO full */ +#define RGN_TRD 0xF0000000 /* Target Ready Delay /8 */ + +#define PLX_REGION1_REG 0x00f8 /* L, Local Bus Region 1 Descriptor */ + +#define PLX_DMRNG_REG 0x001C /* L, Direct Master Range Register */ + +#define PLX_LBAPMEM_REG 0x0020 /* L, Lcl Base Addr for PCI mem space */ + +#define PLX_LBAPIO_REG 0x0024 /* L, Lcl Base Addr for PCI I/O space */ + +#define PLX_DMMAP_REG 0x0028 /* L, Direct Master Remap Register */ +#define DMM_MAE 0x00000001 /* Direct Mstr Memory Acc Enable */ +#define DMM_IAE 0x00000002 /* Direct Mstr I/O Acc Enable */ +#define DMM_LCK 0x00000004 /* LOCK Input Enable */ +#define DMM_PF4 0x00000008 /* Prefetch 4 Mode Enable */ +#define DMM_THROT 0x00000010 /* Assert IRDY when read FIFO full */ +#define DMM_PAF0 0x00000000 /* Programmable Almost fill level */ +#define DMM_PAF1 0x00000020 /* Programmable Almost fill level */ +#define DMM_PAF2 0x00000040 /* Programmable Almost fill level */ +#define DMM_PAF3 0x00000060 /* Programmable Almost fill level */ +#define DMM_PAF4 0x00000080 /* Programmable Almost fill level */ +#define DMM_PAF5 0x000000A0 /* Programmable Almost fill level */ +#define DMM_PAF6 0x000000C0 /* Programmable Almost fill level */ +#define DMM_PAF7 0x000000D0 /* Programmable Almost fill level */ +#define DMM_MAP 0xFFFF0000 /* Remap Address Bits */ + +#define PLX_CAR_REG 0x002C /* L, Configuration Address Register */ +#define CAR_CT0 0x00000000 /* Config Type 0 */ +#define CAR_CT1 0x00000001 /* Config Type 1 */ +#define CAR_REG 0x000000FC /* Register Number Bits */ +#define CAR_FUN 0x00000700 /* Function Number Bits */ +#define CAR_DEV 0x0000F800 /* Device Number Bits */ +#define CAR_BUS 0x00FF0000 /* Bus Number Bits */ +#define CAR_CFG 0x80000000 /* Config Spc Access Enable */ + +#define PLX_DBR_IN_REG 0x0060 /* L, PCI to Local Doorbell Register */ + +#define PLX_DBR_OUT_REG 0x0064 /* L, Local to PCI Doorbell Register */ + +#define PLX_INTRCS_REG 0x0068 /* L, Interrupt Control/Status Reg */ +#define ICS_AERR 0x00000001 /* Assert LSERR on ABORT */ +#define ICS_PERR 0x00000002 /* Assert LSERR on Parity Error */ +#define ICS_SERR 0x00000004 /* Generate PCI SERR# */ +#define ICS_MBIE 0x00000008 /* mailbox interrupt enable */ +#define ICS_PIE 0x00000100 /* PCI Interrupt Enable */ +#define ICS_PDIE 0x00000200 /* PCI Doorbell Interrupt Enable */ +#define ICS_PAIE 0x00000400 /* PCI Abort Interrupt Enable */ +#define ICS_PLIE 0x00000800 /* PCI Local Int Enable */ +#define ICS_RAE 0x00001000 /* Retry Abort Enable */ +#define ICS_PDIA 0x00002000 /* PCI Doorbell Interrupt Active */ +#define ICS_PAIA 0x00004000 /* PCI Abort Interrupt Active */ +#define ICS_LIA 0x00008000 /* Local Interrupt Active */ +#define ICS_LIE 0x00010000 /* Local Interrupt Enable */ +#define ICS_LDIE 0x00020000 /* Local Doorbell Int Enable */ +#define ICS_DMA0_E 0x00040000 /* DMA #0 Interrupt Enable */ +#define ICS_DMA1_E 0x00080000 /* DMA #1 Interrupt Enable */ +#define ICS_LDIA 0x00100000 /* Local Doorbell Int Active */ +#define ICS_DMA0_A 0x00200000 /* DMA #0 Interrupt Active */ +#define ICS_DMA1_A 0x00400000 /* DMA #1 Interrupt Active */ +#define ICS_BIA 0x00800000 /* BIST Interrupt Active */ +#define ICS_TA_DM 0x01000000 /* Target Abort - Direct Master */ +#define ICS_TA_DMA0 0x02000000 /* Target Abort - DMA #0 */ +#define ICS_TA_DMA1 0x04000000 /* Target Abort - DMA #1 */ +#define ICS_TA_RA 0x08000000 /* Target Abort - Retry Timeout */ +#define ICS_MBIA(x) (0x10000000 << ((x) & 0x3)) /* mailbox x is active */ + +#define PLX_CONTROL_REG 0x006C /* L, EEPROM Cntl & PCI Cmd Codes */ +#define CTL_RDMA 0x0000000E /* DMA Read Command */ +#define CTL_WDMA 0x00000070 /* DMA Write Command */ +#define CTL_RMEM 0x00000600 /* Memory Read Command */ +#define CTL_WMEM 0x00007000 /* Memory Write Command */ +#define CTL_USERO 0x00010000 /* USERO output pin control bit */ +#define CTL_USERI 0x00020000 /* USERI input pin bit */ +#define CTL_EE_CLK 0x01000000 /* EEPROM Clock line */ +#define CTL_EE_CS 0x02000000 /* EEPROM Chip Select */ +#define CTL_EE_W 0x04000000 /* EEPROM Write bit */ +#define CTL_EE_R 0x08000000 /* EEPROM Read bit */ +#define CTL_EECHK 0x10000000 /* EEPROM Present bit */ +#define CTL_EERLD 0x20000000 /* EEPROM Reload Register */ +#define CTL_RESET 0x40000000 /* !! Adapter Reset !! */ +#define CTL_READY 0x80000000 /* Local Init Done */ + +#define PLX_ID_REG 0x70 /* hard-coded plx vendor and device ids */ + +#define PLX_REVISION_REG 0x74 /* silicon revision */ + +#define PLX_DMA0_MODE_REG 0x80 /* dma channel 0 mode register */ +#define PLX_DMA1_MODE_REG 0x94 /* dma channel 0 mode register */ +#define PLX_LOCAL_BUS_16_WIDE_BITS 0x1 +#define PLX_LOCAL_BUS_32_WIDE_BITS 0x3 +#define PLX_LOCAL_BUS_WIDTH_MASK 0x3 +#define PLX_DMA_EN_READYIN_BIT 0x40 /* enable ready in input */ +#define PLX_EN_BTERM_BIT 0x80 /* enable BTERM# input */ +#define PLX_DMA_LOCAL_BURST_EN_BIT 0x100 /* enable local burst mode */ +#define PLX_EN_CHAIN_BIT 0x200 /* enables chaining */ +#define PLX_EN_DMA_DONE_INTR_BIT 0x400 /* enables interrupt on dma done */ +#define PLX_LOCAL_ADDR_CONST_BIT 0x800 /* hold local address constant (don't increment) */ +#define PLX_DEMAND_MODE_BIT 0x1000 /* enables demand-mode for dma transfer */ +#define PLX_EOT_ENABLE_BIT 0x4000 +#define PLX_STOP_MODE_BIT 0x8000 +#define PLX_DMA_INTR_PCI_BIT 0x20000 /* routes dma interrupt to pci bus (instead of local bus) */ + +#define PLX_DMA0_PCI_ADDRESS_REG 0x84 /* pci address that dma transfers start at */ +#define PLX_DMA1_PCI_ADDRESS_REG 0x98 + +#define PLX_DMA0_LOCAL_ADDRESS_REG 0x88 /* local address that dma transfers start at */ +#define PLX_DMA1_LOCAL_ADDRESS_REG 0x9c + +#define PLX_DMA0_TRANSFER_SIZE_REG 0x8c /* number of bytes to transfer (first 23 bits) */ +#define PLX_DMA1_TRANSFER_SIZE_REG 0xa0 + +#define PLX_DMA0_DESCRIPTOR_REG 0x90 /* descriptor pointer register */ +#define PLX_DMA1_DESCRIPTOR_REG 0xa4 +#define PLX_DESC_IN_PCI_BIT 0x1 /* descriptor is located in pci space (not local space) */ +#define PLX_END_OF_CHAIN_BIT 0x2 /* end of chain bit */ +#define PLX_INTR_TERM_COUNT 0x4 /* interrupt when this descriptor's transfer is finished */ +#define PLX_XFER_LOCAL_TO_PCI 0x8 /* transfer from local to pci bus (not pci to local) */ + +#define PLX_DMA0_CS_REG 0xa8 /* command status register */ +#define PLX_DMA1_CS_REG 0xa9 +#define PLX_DMA_EN_BIT 0x1 /* enable dma channel */ +#define PLX_DMA_START_BIT 0x2 /* start dma transfer */ +#define PLX_DMA_ABORT_BIT 0x4 /* abort dma transfer */ +#define PLX_CLEAR_DMA_INTR_BIT 0x8 /* clear dma interrupt */ +#define PLX_DMA_DONE_BIT 0x10 /* transfer done status bit */ + +#define PLX_DMA0_THRESHOLD_REG 0xb0 /* command status register */ + +/* + * Accesses near the end of memory can cause the PLX chip + * to pre-fetch data off of end-of-ram. Limit the size of + * memory so host-side accesses cannot occur. + */ + +#define PLX_PREFETCH 32 + +/* + * The PCI Interface, via the PCI-9060 Chip, has up to eight (8) Mailbox + * Registers. The PUTS (Power-Up Test Suite) handles the board-side + * interface/interaction using the first 4 registers. Specifications for + * the use of the full PUTS' command and status interface is contained + * within a separate SBE PUTS Manual. The Host-Side Device Driver only + * uses a subset of the full PUTS interface. + */ + +/*****************************************/ +/*** MAILBOX #(-1) - MEM ACCESS STS ***/ +/*****************************************/ + +#define MBX_STS_VALID 0x57584744 /* 'WXGD' */ +#define MBX_STS_DILAV 0x44475857 /* swapped = 'DGXW' */ + +/*****************************************/ +/*** MAILBOX #0 - PUTS STATUS ***/ +/*****************************************/ + +#define MBX_STS_MASK 0x000000ff /* PUTS Status Register bits */ +#define MBX_STS_TMASK 0x0000000f /* register bits for TEST number */ + +#define MBX_STS_PCIRESET 0x00000100 /* Host issued PCI reset request */ +#define MBX_STS_BUSY 0x00000080 /* PUTS is in progress */ +#define MBX_STS_ERROR 0x00000040 /* PUTS has failed */ +#define MBX_STS_RESERVED 0x000000c0 /* Undefined -> status in transition. + We are in process of changing + bits; we SET Error bit before + RESET of Busy bit */ + +#define MBX_RESERVED_5 0x00000020 /* FYI: reserved/unused bit */ +#define MBX_RESERVED_4 0x00000010 /* FYI: reserved/unused bit */ + +/******************************************/ +/*** MAILBOX #1 - PUTS COMMANDS ***/ +/******************************************/ + +/* + * Any attempt to execute an unimplement command results in the PUTS + * interface executing a NOOP and continuing as if the offending command + * completed normally. Note: this supplies a simple method to interrogate + * mailbox command processing functionality. + */ + +#define MBX_CMD_MASK 0xffff0000 /* PUTS Command Register bits */ + +#define MBX_CMD_ABORTJ 0x85000000 /* abort and jump */ +#define MBX_CMD_RESETP 0x86000000 /* reset and pause at start */ +#define MBX_CMD_PAUSE 0x87000000 /* pause immediately */ +#define MBX_CMD_PAUSEC 0x88000000 /* pause on completion */ +#define MBX_CMD_RESUME 0x89000000 /* resume operation */ +#define MBX_CMD_STEP 0x8a000000 /* single step tests */ + +#define MBX_CMD_BSWAP 0x8c000000 /* identify byte swap scheme */ +#define MBX_CMD_BSWAP_0 0x8c000000 /* use scheme 0 */ +#define MBX_CMD_BSWAP_1 0x8c000001 /* use scheme 1 */ + +#define MBX_CMD_SETHMS 0x8d000000 /* setup host memory access window + size */ +#define MBX_CMD_SETHBA 0x8e000000 /* setup host memory access base + address */ +#define MBX_CMD_MGO 0x8f000000 /* perform memory setup and continue + (IE. Done) */ +#define MBX_CMD_NOOP 0xFF000000 /* dummy, illegal command */ + +/*****************************************/ +/*** MAILBOX #2 - MEMORY SIZE ***/ +/*****************************************/ + +#define MBX_MEMSZ_MASK 0xffff0000 /* PUTS Memory Size Register bits */ + +#define MBX_MEMSZ_128KB 0x00020000 /* 128 kilobyte board */ +#define MBX_MEMSZ_256KB 0x00040000 /* 256 kilobyte board */ +#define MBX_MEMSZ_512KB 0x00080000 /* 512 kilobyte board */ +#define MBX_MEMSZ_1MB 0x00100000 /* 1 megabyte board */ +#define MBX_MEMSZ_2MB 0x00200000 /* 2 megabyte board */ +#define MBX_MEMSZ_4MB 0x00400000 /* 4 megabyte board */ +#define MBX_MEMSZ_8MB 0x00800000 /* 8 megabyte board */ +#define MBX_MEMSZ_16MB 0x01000000 /* 16 megabyte board */ + +/***************************************/ +/*** MAILBOX #2 - BOARD TYPE ***/ +/***************************************/ + +#define MBX_BTYPE_MASK 0x0000ffff /* PUTS Board Type Register */ +#define MBX_BTYPE_FAMILY_MASK 0x0000ff00 /* PUTS Board Family Register */ +#define MBX_BTYPE_SUBTYPE_MASK 0x000000ff /* PUTS Board Subtype */ + +#define MBX_BTYPE_PLX9060 0x00000100 /* PLX family type */ +#define MBX_BTYPE_PLX9080 0x00000300 /* PLX wanXL100s family type */ + +#define MBX_BTYPE_WANXL_4 0x00000104 /* wanXL400, 4-port */ +#define MBX_BTYPE_WANXL_2 0x00000102 /* wanXL200, 2-port */ +#define MBX_BTYPE_WANXL_1s 0x00000301 /* wanXL100s, 1-port */ +#define MBX_BTYPE_WANXL_1t 0x00000401 /* wanXL100T1, 1-port */ + +/*****************************************/ +/*** MAILBOX #3 - SHMQ MAILBOX ***/ +/*****************************************/ + +#define MBX_SMBX_MASK 0x000000ff /* PUTS SHMQ Mailbox bits */ + +/***************************************/ +/*** GENERIC HOST-SIDE DRIVER ***/ +/***************************************/ + +#define MBX_ERR 0 +#define MBX_OK 1 + +/* mailbox check routine - type of testing */ +#define MBXCHK_STS 0x00 /* check for PUTS status */ +#define MBXCHK_NOWAIT 0x01 /* dont care about PUTS status */ + +/* system allocates this many bytes for address mapping mailbox space */ +#define MBX_ADDR_SPACE_360 0x80 /* wanXL100s/200/400 */ +#define MBX_ADDR_MASK_360 (MBX_ADDR_SPACE_360-1) + +static inline int plx9080_abort_dma(void __iomem *iobase, unsigned int channel) +{ + void __iomem *dma_cs_addr; + uint8_t dma_status; + const int timeout = 10000; + unsigned int i; + + if (channel) + dma_cs_addr = iobase + PLX_DMA1_CS_REG; + else + dma_cs_addr = iobase + PLX_DMA0_CS_REG; + + /* abort dma transfer if necessary */ + dma_status = readb(dma_cs_addr); + if ((dma_status & PLX_DMA_EN_BIT) == 0) + return 0; + + /* wait to make sure done bit is zero */ + for (i = 0; (dma_status & PLX_DMA_DONE_BIT) && i < timeout; i++) { + udelay(1); + dma_status = readb(dma_cs_addr); + } + if (i == timeout) { + printk + ("plx9080: cancel() timed out waiting for dma %i done clear\n", + channel); + return -ETIMEDOUT; + } + /* disable and abort channel */ + writeb(PLX_DMA_ABORT_BIT, dma_cs_addr); + /* wait for dma done bit */ + dma_status = readb(dma_cs_addr); + for (i = 0; (dma_status & PLX_DMA_DONE_BIT) == 0 && i < timeout; i++) { + udelay(1); + dma_status = readb(dma_cs_addr); + } + if (i == timeout) { + printk + ("plx9080: cancel() timed out waiting for dma %i done set\n", + channel); + return -ETIMEDOUT; + } + + return 0; +} + +#endif /* __COMEDI_PLX9080_H */ diff --git a/drivers/staging/comedi/drivers/poc.c b/drivers/staging/comedi/drivers/poc.c new file mode 100644 index 00000000..831a576c --- /dev/null +++ b/drivers/staging/comedi/drivers/poc.c @@ -0,0 +1,266 @@ +/* + comedi/drivers/poc.c + Mini-drivers for POC (Piece of Crap) boards + Copyright (C) 2000 Frank Mori Hess + Copyright (C) 2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* +Driver: poc +Description: Generic driver for very simple devices +Author: ds +Devices: [Keithley Metrabyte] DAC-02 (dac02), [Advantech] PCL-733 (pcl733), + PCL-734 (pcl734) +Updated: Sat, 16 Mar 2002 17:34:48 -0800 +Status: unknown + +This driver is indended to support very simple ISA-based devices, +including: + dac02 - Keithley DAC-02 analog output board + pcl733 - Advantech PCL-733 + pcl734 - Advantech PCL-734 + +Configuration options: + [0] - I/O port base +*/ + +#include "../comedidev.h" + +#include + +static int poc_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int poc_detach(struct comedi_device *dev); +static int readback_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int dac02_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int pcl733_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int pcl734_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +struct boarddef_struct { + const char *name; + unsigned int iosize; + int (*setup) (struct comedi_device *); + int type; + int n_chan; + int n_bits; + int (*winsn) (struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*rinsn) (struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + int (*insnbits) (struct comedi_device *, struct comedi_subdevice *, + struct comedi_insn *, unsigned int *); + const struct comedi_lrange *range; +}; +static const struct boarddef_struct boards[] = { + { + .name = "dac02", + .iosize = 8, + /* .setup = dac02_setup, */ + .type = COMEDI_SUBD_AO, + .n_chan = 2, + .n_bits = 12, + .winsn = dac02_ao_winsn, + .rinsn = readback_insn, + .range = &range_unknown, + }, + { + .name = "pcl733", + .iosize = 4, + .type = COMEDI_SUBD_DI, + .n_chan = 32, + .n_bits = 1, + .insnbits = pcl733_insn_bits, + .range = &range_digital, + }, + { + .name = "pcl734", + .iosize = 4, + .type = COMEDI_SUBD_DO, + .n_chan = 32, + .n_bits = 1, + .insnbits = pcl734_insn_bits, + .range = &range_digital, + }, +}; + +#define n_boards ARRAY_SIZE(boards) +#define this_board ((const struct boarddef_struct *)dev->board_ptr) + +static struct comedi_driver driver_poc = { + .driver_name = "poc", + .module = THIS_MODULE, + .attach = poc_attach, + .detach = poc_detach, + .board_name = &boards[0].name, + .num_names = n_boards, + .offset = sizeof(boards[0]), +}; + +static int poc_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + unsigned long iobase; + unsigned int iosize; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: poc: using %s iobase 0x%lx\n", dev->minor, + this_board->name, iobase); + + dev->board_name = this_board->name; + + if (iobase == 0) { + printk(KERN_ERR "io base address required\n"); + return -EINVAL; + } + + iosize = this_board->iosize; + /* check if io addresses are available */ + if (!request_region(iobase, iosize, "dac02")) { + printk(KERN_ERR "I/O port conflict: failed to allocate ports " + "0x%lx to 0x%lx\n", iobase, iobase + iosize - 1); + return -EIO; + } + dev->iobase = iobase; + + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + if (alloc_private(dev, sizeof(unsigned int) * this_board->n_chan) < 0) + return -ENOMEM; + + /* analog output subdevice */ + s = dev->subdevices + 0; + s->type = this_board->type; + s->n_chan = this_board->n_chan; + s->maxdata = (1 << this_board->n_bits) - 1; + s->range_table = this_board->range; + s->insn_write = this_board->winsn; + s->insn_read = this_board->rinsn; + s->insn_bits = this_board->insnbits; + if (s->type == COMEDI_SUBD_AO || s->type == COMEDI_SUBD_DO) + s->subdev_flags = SDF_WRITABLE; + + return 0; +} + +static int poc_detach(struct comedi_device *dev) +{ + /* only free stuff if it has been allocated by _attach */ + if (dev->iobase) + release_region(dev->iobase, this_board->iosize); + + printk(KERN_INFO "comedi%d: dac02: remove\n", dev->minor); + + return 0; +} + +static int readback_insn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan; + + chan = CR_CHAN(insn->chanspec); + data[0] = ((unsigned int *)dev->private)[chan]; + + return 1; +} + +/* DAC-02 registers */ +#define DAC02_LSB(a) (2 * a) +#define DAC02_MSB(a) (2 * a + 1) + +static int dac02_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int temp; + int chan; + int output; + + chan = CR_CHAN(insn->chanspec); + ((unsigned int *)dev->private)[chan] = data[0]; + output = data[0]; +#ifdef wrong + /* convert to complementary binary if range is bipolar */ + if ((CR_RANGE(insn->chanspec) & 0x2) == 0) + output = ~output; +#endif + temp = (output << 4) & 0xf0; + outb(temp, dev->iobase + DAC02_LSB(chan)); + temp = (output >> 4) & 0xff; + outb(temp, dev->iobase + DAC02_MSB(chan)); + + return 1; +} + +static int pcl733_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + data[1] = inb(dev->iobase + 0); + data[1] |= (inb(dev->iobase + 1) << 8); + data[1] |= (inb(dev->iobase + 2) << 16); + data[1] |= (inb(dev->iobase + 3) << 24); + + return 2; +} + +static int pcl734_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + if ((data[0] >> 0) & 0xff) + outb((s->state >> 0) & 0xff, dev->iobase + 0); + if ((data[0] >> 8) & 0xff) + outb((s->state >> 8) & 0xff, dev->iobase + 1); + if ((data[0] >> 16) & 0xff) + outb((s->state >> 16) & 0xff, dev->iobase + 2); + if ((data[0] >> 24) & 0xff) + outb((s->state >> 24) & 0xff, dev->iobase + 3); + } + data[1] = s->state; + + return 2; +} + +static int __init driver_poc_init_module(void) +{ + return comedi_driver_register(&driver_poc); +} + +static void __exit driver_poc_cleanup_module(void) +{ + comedi_driver_unregister(&driver_poc); +} + +module_init(driver_poc_init_module); +module_exit(driver_poc_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/quatech_daqp_cs.c b/drivers/staging/comedi/drivers/quatech_daqp_cs.c new file mode 100644 index 00000000..e0bb7344 --- /dev/null +++ b/drivers/staging/comedi/drivers/quatech_daqp_cs.c @@ -0,0 +1,1123 @@ +/*====================================================================== + + comedi/drivers/quatech_daqp_cs.c + + Quatech DAQP PCMCIA data capture cards COMEDI client driver + Copyright (C) 2000, 2003 Brent Baccala + The DAQP interface code in this file is released into the public domain. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + http://www.comedi.org/ + + quatech_daqp_cs.c 1.10 + + Documentation for the DAQP PCMCIA cards can be found on Quatech's site: + + ftp://ftp.quatech.com/Manuals/daqp-208.pdf + + This manual is for both the DAQP-208 and the DAQP-308. + + What works: + + - A/D conversion + - 8 channels + - 4 gain ranges + - ground ref or differential + - single-shot and timed both supported + - D/A conversion, single-shot + - digital I/O + + What doesn't: + + - any kind of triggering - external or D/A channel 1 + - the card's optional expansion board + - the card's timer (for anything other than A/D conversion) + - D/A update modes other than immediate (i.e, timed) + - fancier timing modes + - setting card's FIFO buffer thresholds to anything but default + +======================================================================*/ + +/* +Driver: quatech_daqp_cs +Description: Quatech DAQP PCMCIA data capture cards +Author: Brent Baccala +Status: works +Devices: [Quatech] DAQP-208 (daqp), DAQP-308 +*/ + +#include "../comedidev.h" +#include + +#include +#include +#include + +#include + +/* Maximum number of separate DAQP devices we'll allow */ +#define MAX_DEV 4 + +struct local_info_t { + struct pcmcia_device *link; + int stop; + int table_index; + char board_name[32]; + + enum { semaphore, buffer } interrupt_mode; + + struct completion eos; + + struct comedi_device *dev; + struct comedi_subdevice *s; + int count; +}; + +/* A list of "instances" of the device. */ + +static struct local_info_t *dev_table[MAX_DEV] = { NULL, /* ... */ }; + +/* The DAQP communicates with the system through a 16 byte I/O window. */ + +#define DAQP_FIFO_SIZE 4096 + +#define DAQP_FIFO 0 +#define DAQP_SCANLIST 1 +#define DAQP_CONTROL 2 +#define DAQP_STATUS 2 +#define DAQP_DIGITAL_IO 3 +#define DAQP_PACER_LOW 4 +#define DAQP_PACER_MID 5 +#define DAQP_PACER_HIGH 6 +#define DAQP_COMMAND 7 +#define DAQP_DA 8 +#define DAQP_TIMER 10 +#define DAQP_AUX 15 + +#define DAQP_SCANLIST_DIFFERENTIAL 0x4000 +#define DAQP_SCANLIST_GAIN(x) ((x)<<12) +#define DAQP_SCANLIST_CHANNEL(x) ((x)<<8) +#define DAQP_SCANLIST_START 0x0080 +#define DAQP_SCANLIST_EXT_GAIN(x) ((x)<<4) +#define DAQP_SCANLIST_EXT_CHANNEL(x) (x) + +#define DAQP_CONTROL_PACER_100kHz 0xc0 +#define DAQP_CONTROL_PACER_1MHz 0x80 +#define DAQP_CONTROL_PACER_5MHz 0x40 +#define DAQP_CONTROL_PACER_EXTERNAL 0x00 +#define DAQP_CONTORL_EXPANSION 0x20 +#define DAQP_CONTROL_EOS_INT_ENABLE 0x10 +#define DAQP_CONTROL_FIFO_INT_ENABLE 0x08 +#define DAQP_CONTROL_TRIGGER_ONESHOT 0x00 +#define DAQP_CONTROL_TRIGGER_CONTINUOUS 0x04 +#define DAQP_CONTROL_TRIGGER_INTERNAL 0x00 +#define DAQP_CONTROL_TRIGGER_EXTERNAL 0x02 +#define DAQP_CONTROL_TRIGGER_RISING 0x00 +#define DAQP_CONTROL_TRIGGER_FALLING 0x01 + +#define DAQP_STATUS_IDLE 0x80 +#define DAQP_STATUS_RUNNING 0x40 +#define DAQP_STATUS_EVENTS 0x38 +#define DAQP_STATUS_DATA_LOST 0x20 +#define DAQP_STATUS_END_OF_SCAN 0x10 +#define DAQP_STATUS_FIFO_THRESHOLD 0x08 +#define DAQP_STATUS_FIFO_FULL 0x04 +#define DAQP_STATUS_FIFO_NEARFULL 0x02 +#define DAQP_STATUS_FIFO_EMPTY 0x01 + +#define DAQP_COMMAND_ARM 0x80 +#define DAQP_COMMAND_RSTF 0x40 +#define DAQP_COMMAND_RSTQ 0x20 +#define DAQP_COMMAND_STOP 0x10 +#define DAQP_COMMAND_LATCH 0x08 +#define DAQP_COMMAND_100kHz 0x00 +#define DAQP_COMMAND_50kHz 0x02 +#define DAQP_COMMAND_25kHz 0x04 +#define DAQP_COMMAND_FIFO_DATA 0x01 +#define DAQP_COMMAND_FIFO_PROGRAM 0x00 + +#define DAQP_AUX_TRIGGER_TTL 0x00 +#define DAQP_AUX_TRIGGER_ANALOG 0x80 +#define DAQP_AUX_TRIGGER_PRETRIGGER 0x40 +#define DAQP_AUX_TIMER_INT_ENABLE 0x20 +#define DAQP_AUX_TIMER_RELOAD 0x00 +#define DAQP_AUX_TIMER_PAUSE 0x08 +#define DAQP_AUX_TIMER_GO 0x10 +#define DAQP_AUX_TIMER_GO_EXTERNAL 0x18 +#define DAQP_AUX_TIMER_EXTERNAL_SRC 0x04 +#define DAQP_AUX_TIMER_INTERNAL_SRC 0x00 +#define DAQP_AUX_DA_DIRECT 0x00 +#define DAQP_AUX_DA_OVERFLOW 0x01 +#define DAQP_AUX_DA_EXTERNAL 0x02 +#define DAQP_AUX_DA_PACER 0x03 + +#define DAQP_AUX_RUNNING 0x80 +#define DAQP_AUX_TRIGGERED 0x40 +#define DAQP_AUX_DA_BUFFER 0x20 +#define DAQP_AUX_TIMER_OVERFLOW 0x10 +#define DAQP_AUX_CONVERSION 0x08 +#define DAQP_AUX_DATA_LOST 0x04 +#define DAQP_AUX_FIFO_NEARFULL 0x02 +#define DAQP_AUX_FIFO_EMPTY 0x01 + +/* These range structures tell COMEDI how the sample values map to + * voltages. The A/D converter has four .ranges = +/- 10V through + * +/- 1.25V, and the D/A converter has only .one = +/- 5V. + */ + +static const struct comedi_lrange range_daqp_ai = { 4, { + BIP_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(2.5), + BIP_RANGE(1.25) + } +}; + +static const struct comedi_lrange range_daqp_ao = { 1, {BIP_RANGE(5)} }; + +/*====================================================================*/ + +/* comedi interface code */ + +static int daqp_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int daqp_detach(struct comedi_device *dev); +static struct comedi_driver driver_daqp = { + .driver_name = "quatech_daqp_cs", + .module = THIS_MODULE, + .attach = daqp_attach, + .detach = daqp_detach, +}; + +#ifdef DAQP_DEBUG + +static void daqp_dump(struct comedi_device *dev) +{ + printk(KERN_INFO "DAQP: status %02x; aux status %02x\n", + inb(dev->iobase + DAQP_STATUS), inb(dev->iobase + DAQP_AUX)); +} + +static void hex_dump(char *str, void *ptr, int len) +{ + unsigned char *cptr = ptr; + int i; + + printk(str); + + for (i = 0; i < len; i++) { + if (i % 16 == 0) + printk("\n%p:", cptr); + + printk(" %02x", *(cptr++)); + } + printk("\n"); +} + +#endif + +/* Cancel a running acquisition */ + +static int daqp_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct local_info_t *local = (struct local_info_t *)s->private; + + if (local->stop) + return -EIO; + + + outb(DAQP_COMMAND_STOP, dev->iobase + DAQP_COMMAND); + + /* flush any linguring data in FIFO - superfluous here */ + /* outb(DAQP_COMMAND_RSTF, dev->iobase+DAQP_COMMAND); */ + + local->interrupt_mode = semaphore; + + return 0; +} + +/* Interrupt handler + * + * Operates in one of two modes. If local->interrupt_mode is + * 'semaphore', just signal the local->eos completion and return + * (one-shot mode). Otherwise (continuous mode), read data in from + * the card, transfer it to the buffer provided by the higher-level + * comedi kernel module, and signal various comedi callback routines, + * which run pretty quick. + */ +static enum irqreturn daqp_interrupt(int irq, void *dev_id) +{ + struct local_info_t *local = (struct local_info_t *)dev_id; + struct comedi_device *dev; + struct comedi_subdevice *s; + int loop_limit = 10000; + int status; + + if (local == NULL) { + printk(KERN_WARNING + "daqp_interrupt(): irq %d for unknown device.\n", irq); + return IRQ_NONE; + } + + dev = local->dev; + if (dev == NULL) { + printk(KERN_WARNING "daqp_interrupt(): NULL comedi_device.\n"); + return IRQ_NONE; + } + + if (!dev->attached) { + printk(KERN_WARNING + "daqp_interrupt(): struct comedi_device not yet attached.\n"); + return IRQ_NONE; + } + + s = local->s; + if (s == NULL) { + printk(KERN_WARNING + "daqp_interrupt(): NULL comedi_subdevice.\n"); + return IRQ_NONE; + } + + if ((struct local_info_t *)s->private != local) { + printk(KERN_WARNING + "daqp_interrupt(): invalid comedi_subdevice.\n"); + return IRQ_NONE; + } + + switch (local->interrupt_mode) { + + case semaphore: + + complete(&local->eos); + break; + + case buffer: + + while (!((status = inb(dev->iobase + DAQP_STATUS)) + & DAQP_STATUS_FIFO_EMPTY)) { + + short data; + + if (status & DAQP_STATUS_DATA_LOST) { + s->async->events |= + COMEDI_CB_EOA | COMEDI_CB_OVERFLOW; + printk("daqp: data lost\n"); + daqp_ai_cancel(dev, s); + break; + } + + data = inb(dev->iobase + DAQP_FIFO); + data |= inb(dev->iobase + DAQP_FIFO) << 8; + data ^= 0x8000; + + comedi_buf_put(s->async, data); + + /* If there's a limit, decrement it + * and stop conversion if zero + */ + + if (local->count > 0) { + local->count--; + if (local->count == 0) { + daqp_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA; + break; + } + } + + if ((loop_limit--) <= 0) + break; + } + + if (loop_limit <= 0) { + printk(KERN_WARNING + "loop_limit reached in daqp_interrupt()\n"); + daqp_ai_cancel(dev, s); + s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR; + } + + s->async->events |= COMEDI_CB_BLOCK; + + comedi_event(dev, s); + } + return IRQ_HANDLED; +} + +/* One-shot analog data acquisition routine */ + +static int daqp_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct local_info_t *local = (struct local_info_t *)s->private; + int i; + int v; + int counter = 10000; + + if (local->stop) + return -EIO; + + + /* Stop any running conversion */ + daqp_ai_cancel(dev, s); + + outb(0, dev->iobase + DAQP_AUX); + + /* Reset scan list queue */ + outb(DAQP_COMMAND_RSTQ, dev->iobase + DAQP_COMMAND); + + /* Program one scan list entry */ + + v = DAQP_SCANLIST_CHANNEL(CR_CHAN(insn->chanspec)) + | DAQP_SCANLIST_GAIN(CR_RANGE(insn->chanspec)); + + if (CR_AREF(insn->chanspec) == AREF_DIFF) + v |= DAQP_SCANLIST_DIFFERENTIAL; + + + v |= DAQP_SCANLIST_START; + + outb(v & 0xff, dev->iobase + DAQP_SCANLIST); + outb(v >> 8, dev->iobase + DAQP_SCANLIST); + + /* Reset data FIFO (see page 28 of DAQP User's Manual) */ + + outb(DAQP_COMMAND_RSTF, dev->iobase + DAQP_COMMAND); + + /* Set trigger */ + + v = DAQP_CONTROL_TRIGGER_ONESHOT | DAQP_CONTROL_TRIGGER_INTERNAL + | DAQP_CONTROL_PACER_100kHz | DAQP_CONTROL_EOS_INT_ENABLE; + + outb(v, dev->iobase + DAQP_CONTROL); + + /* Reset any pending interrupts (my card has a tendency to require + * require multiple reads on the status register to achieve this) + */ + + while (--counter + && (inb(dev->iobase + DAQP_STATUS) & DAQP_STATUS_EVENTS)) ; + if (!counter) { + printk("daqp: couldn't clear interrupts in status register\n"); + return -1; + } + + init_completion(&local->eos); + local->interrupt_mode = semaphore; + local->dev = dev; + local->s = s; + + for (i = 0; i < insn->n; i++) { + + /* Start conversion */ + outb(DAQP_COMMAND_ARM | DAQP_COMMAND_FIFO_DATA, + dev->iobase + DAQP_COMMAND); + + /* Wait for interrupt service routine to unblock completion */ + /* Maybe could use a timeout here, but it's interruptible */ + if (wait_for_completion_interruptible(&local->eos)) + return -EINTR; + + data[i] = inb(dev->iobase + DAQP_FIFO); + data[i] |= inb(dev->iobase + DAQP_FIFO) << 8; + data[i] ^= 0x8000; + } + + return insn->n; +} + +/* This function converts ns nanoseconds to a counter value suitable + * for programming the device. We always use the DAQP's 5 MHz clock, + * which with its 24-bit counter, allows values up to 84 seconds. + * Also, the function adjusts ns so that it cooresponds to the actual + * time that the device will use. + */ + +static int daqp_ns_to_timer(unsigned int *ns, int round) +{ + int timer; + + timer = *ns / 200; + *ns = timer * 200; + + return timer; +} + +/* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executed by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. + */ + +static int daqp_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources + * are unique and mutually compatible + */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_FOLLOW) + err++; + if (cmd->convert_src != TRIG_NOW && cmd->convert_src != TRIG_TIMER) + err++; + if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } +#define MAX_SPEED 10000 /* 100 kHz - in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER + && cmd->scan_begin_arg < MAX_SPEED) { + cmd->scan_begin_arg = MAX_SPEED; + err++; + } + + /* If both scan_begin and convert are both timer values, the only + * way that can make sense is if the scan time is the number of + * conversions times the convert time + */ + + if (cmd->scan_begin_src == TRIG_TIMER && cmd->convert_src == TRIG_TIMER + && cmd->scan_begin_arg != cmd->convert_arg * cmd->scan_end_arg) { + err++; + } + + if (cmd->convert_src == TRIG_TIMER && cmd->convert_arg < MAX_SPEED) { + cmd->convert_arg = MAX_SPEED; + err++; + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg > 0x00ffffff) { + cmd->stop_arg = 0x00ffffff; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + daqp_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + daqp_ns_to_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + } + + if (err) + return 4; + + return 0; +} + +static int daqp_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct local_info_t *local = (struct local_info_t *)s->private; + struct comedi_cmd *cmd = &s->async->cmd; + int counter; + int scanlist_start_on_every_entry; + int threshold; + + int i; + int v; + + if (local->stop) + return -EIO; + + + /* Stop any running conversion */ + daqp_ai_cancel(dev, s); + + outb(0, dev->iobase + DAQP_AUX); + + /* Reset scan list queue */ + outb(DAQP_COMMAND_RSTQ, dev->iobase + DAQP_COMMAND); + + /* Program pacer clock + * + * There's two modes we can operate in. If convert_src is + * TRIG_TIMER, then convert_arg specifies the time between + * each conversion, so we program the pacer clock to that + * frequency and set the SCANLIST_START bit on every scanlist + * entry. Otherwise, convert_src is TRIG_NOW, which means + * we want the fastest possible conversions, scan_begin_src + * is TRIG_TIMER, and scan_begin_arg specifies the time between + * each scan, so we program the pacer clock to this frequency + * and only set the SCANLIST_START bit on the first entry. + */ + + if (cmd->convert_src == TRIG_TIMER) { + counter = daqp_ns_to_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + outb(counter & 0xff, dev->iobase + DAQP_PACER_LOW); + outb((counter >> 8) & 0xff, dev->iobase + DAQP_PACER_MID); + outb((counter >> 16) & 0xff, dev->iobase + DAQP_PACER_HIGH); + scanlist_start_on_every_entry = 1; + } else { + counter = daqp_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + outb(counter & 0xff, dev->iobase + DAQP_PACER_LOW); + outb((counter >> 8) & 0xff, dev->iobase + DAQP_PACER_MID); + outb((counter >> 16) & 0xff, dev->iobase + DAQP_PACER_HIGH); + scanlist_start_on_every_entry = 0; + } + + /* Program scan list */ + + for (i = 0; i < cmd->chanlist_len; i++) { + + int chanspec = cmd->chanlist[i]; + + /* Program one scan list entry */ + + v = DAQP_SCANLIST_CHANNEL(CR_CHAN(chanspec)) + | DAQP_SCANLIST_GAIN(CR_RANGE(chanspec)); + + if (CR_AREF(chanspec) == AREF_DIFF) + v |= DAQP_SCANLIST_DIFFERENTIAL; + + if (i == 0 || scanlist_start_on_every_entry) + v |= DAQP_SCANLIST_START; + + outb(v & 0xff, dev->iobase + DAQP_SCANLIST); + outb(v >> 8, dev->iobase + DAQP_SCANLIST); + } + + /* Now it's time to program the FIFO threshold, basically the + * number of samples the card will buffer before it interrupts + * the CPU. + * + * If we don't have a stop count, then use half the size of + * the FIFO (the manufacturer's recommendation). Consider + * that the FIFO can hold 2K samples (4K bytes). With the + * threshold set at half the FIFO size, we have a margin of + * error of 1024 samples. At the chip's maximum sample rate + * of 100,000 Hz, the CPU would have to delay interrupt + * service for a full 10 milliseconds in order to lose data + * here (as opposed to higher up in the kernel). I've never + * seen it happen. However, for slow sample rates it may + * buffer too much data and introduce too much delay for the + * user application. + * + * If we have a stop count, then things get more interesting. + * If the stop count is less than the FIFO size (actually + * three-quarters of the FIFO size - see below), we just use + * the stop count itself as the threshold, the card interrupts + * us when that many samples have been taken, and we kill the + * acquisition at that point and are done. If the stop count + * is larger than that, then we divide it by 2 until it's less + * than three quarters of the FIFO size (we always leave the + * top quarter of the FIFO as protection against sluggish CPU + * interrupt response) and use that as the threshold. So, if + * the stop count is 4000 samples, we divide by two twice to + * get 1000 samples, use that as the threshold, take four + * interrupts to get our 4000 samples and are done. + * + * The algorithm could be more clever. For example, if 81000 + * samples are requested, we could set the threshold to 1500 + * samples and take 54 interrupts to get 81000. But 54 isn't + * a power of two, so this algorithm won't find that option. + * Instead, it'll set the threshold at 1266 and take 64 + * interrupts to get 81024 samples, of which the last 24 will + * be discarded... but we won't get the last interrupt until + * they've been collected. To find the first option, the + * computer could look at the prime decomposition of the + * sample count (81000 = 3^4 * 5^3 * 2^3) and factor it into a + * threshold (1500 = 3 * 5^3 * 2^2) and an interrupt count (54 + * = 3^3 * 2). Hmmm... a one-line while loop or prime + * decomposition of integers... I'll leave it the way it is. + * + * I'll also note a mini-race condition before ignoring it in + * the code. Let's say we're taking 4000 samples, as before. + * After 1000 samples, we get an interrupt. But before that + * interrupt is completely serviced, another sample is taken + * and loaded into the FIFO. Since the interrupt handler + * empties the FIFO before returning, it will read 1001 samples. + * If that happens four times, we'll end up taking 4004 samples, + * not 4000. The interrupt handler will discard the extra four + * samples (by halting the acquisition with four samples still + * in the FIFO), but we will have to wait for them. + * + * In short, this code works pretty well, but for either of + * the two reasons noted, might end up waiting for a few more + * samples than actually requested. Shouldn't make too much + * of a difference. + */ + + /* Save away the number of conversions we should perform, and + * compute the FIFO threshold (in bytes, not samples - that's + * why we multiple local->count by 2 = sizeof(sample)) + */ + + if (cmd->stop_src == TRIG_COUNT) { + local->count = cmd->stop_arg * cmd->scan_end_arg; + threshold = 2 * local->count; + while (threshold > DAQP_FIFO_SIZE * 3 / 4) + threshold /= 2; + } else { + local->count = -1; + threshold = DAQP_FIFO_SIZE / 2; + } + + /* Reset data FIFO (see page 28 of DAQP User's Manual) */ + + outb(DAQP_COMMAND_RSTF, dev->iobase + DAQP_COMMAND); + + /* Set FIFO threshold. First two bytes are near-empty + * threshold, which is unused; next two bytes are near-full + * threshold. We computed the number of bytes we want in the + * FIFO when the interrupt is generated, what the card wants + * is actually the number of available bytes left in the FIFO + * when the interrupt is to happen. + */ + + outb(0x00, dev->iobase + DAQP_FIFO); + outb(0x00, dev->iobase + DAQP_FIFO); + + outb((DAQP_FIFO_SIZE - threshold) & 0xff, dev->iobase + DAQP_FIFO); + outb((DAQP_FIFO_SIZE - threshold) >> 8, dev->iobase + DAQP_FIFO); + + /* Set trigger */ + + v = DAQP_CONTROL_TRIGGER_CONTINUOUS | DAQP_CONTROL_TRIGGER_INTERNAL + | DAQP_CONTROL_PACER_5MHz | DAQP_CONTROL_FIFO_INT_ENABLE; + + outb(v, dev->iobase + DAQP_CONTROL); + + /* Reset any pending interrupts (my card has a tendency to require + * require multiple reads on the status register to achieve this) + */ + counter = 100; + while (--counter + && (inb(dev->iobase + DAQP_STATUS) & DAQP_STATUS_EVENTS)) ; + if (!counter) { + printk(KERN_ERR + "daqp: couldn't clear interrupts in status register\n"); + return -1; + } + + local->interrupt_mode = buffer; + local->dev = dev; + local->s = s; + + /* Start conversion */ + outb(DAQP_COMMAND_ARM | DAQP_COMMAND_FIFO_DATA, + dev->iobase + DAQP_COMMAND); + + return 0; +} + +/* Single-shot analog output routine */ + +static int daqp_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct local_info_t *local = (struct local_info_t *)s->private; + int d; + unsigned int chan; + + if (local->stop) + return -EIO; + + chan = CR_CHAN(insn->chanspec); + d = data[0]; + d &= 0x0fff; + d ^= 0x0800; /* Flip the sign */ + d |= chan << 12; + + /* Make sure D/A update mode is direct update */ + outb(0, dev->iobase + DAQP_AUX); + + outw(d, dev->iobase + DAQP_DA); + + return 1; +} + +/* Digital input routine */ + +static int daqp_di_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct local_info_t *local = (struct local_info_t *)s->private; + + if (local->stop) + return -EIO; + + data[0] = inb(dev->iobase + DAQP_DIGITAL_IO); + + return 1; +} + +/* Digital output routine */ + +static int daqp_do_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct local_info_t *local = (struct local_info_t *)s->private; + + if (local->stop) + return -EIO; + + outw(data[0] & 0xf, dev->iobase + DAQP_DIGITAL_IO); + + return 1; +} + +/* daqp_attach is called via comedi_config to attach a comedi device + * to a /dev/comedi*. Note that this is different from daqp_cs_attach() + * which is called by the pcmcia subsystem to attach the PCMCIA card + * when it is inserted. + */ + +static int daqp_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + struct local_info_t *local = dev_table[it->options[0]]; + struct comedi_subdevice *s; + + if (it->options[0] < 0 || it->options[0] >= MAX_DEV || !local) { + printk("comedi%d: No such daqp device %d\n", + dev->minor, it->options[0]); + return -EIO; + } + + /* Typically brittle code that I don't completely understand, + * but "it works on my card". The intent is to pull the model + * number of the card out the PCMCIA CIS and stash it away as + * the COMEDI board_name. Looks like the third field in + * CISTPL_VERS_1 (offset 2) holds what we're looking for. If + * it doesn't work, who cares, just leave it as "DAQP". + */ + + strcpy(local->board_name, "DAQP"); + dev->board_name = local->board_name; + if (local->link->prod_id[2]) { + if (strncmp(local->link->prod_id[2], "DAQP", 4) == 0) { + strncpy(local->board_name, local->link->prod_id[2], + sizeof(local->board_name)); + } + } + + dev->iobase = local->link->resource[0]->start; + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + printk(KERN_INFO "comedi%d: attaching daqp%d (io 0x%04lx)\n", + dev->minor, it->options[0], dev->iobase); + + s = dev->subdevices + 0; + dev->read_subdev = s; + s->private = local; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ; + s->n_chan = 8; + s->len_chanlist = 2048; + s->maxdata = 0xffff; + s->range_table = &range_daqp_ai; + s->insn_read = daqp_ai_insn_read; + s->do_cmdtest = daqp_ai_cmdtest; + s->do_cmd = daqp_ai_cmd; + s->cancel = daqp_ai_cancel; + + s = dev->subdevices + 1; + dev->write_subdev = s; + s->private = local; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 2; + s->len_chanlist = 1; + s->maxdata = 0x0fff; + s->range_table = &range_daqp_ao; + s->insn_write = daqp_ao_insn_write; + + s = dev->subdevices + 2; + s->private = local; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 1; + s->len_chanlist = 1; + s->insn_read = daqp_di_insn_read; + + s = dev->subdevices + 3; + s->private = local; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 1; + s->len_chanlist = 1; + s->insn_write = daqp_do_insn_write; + + return 1; +} + +/* daqp_detach (called from comedi_comdig) does nothing. If the PCMCIA + * card is removed, daqp_cs_detach() is called by the pcmcia subsystem. + */ + +static int daqp_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: detaching daqp\n", dev->minor); + + return 0; +} + +/*==================================================================== + + PCMCIA interface code + + The rest of the code in this file is based on dummy_cs.c v1.24 + from the Linux pcmcia_cs distribution v3.1.8 and is subject + to the following license agreement. + + The remaining contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + . Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +static void daqp_cs_config(struct pcmcia_device *link); +static void daqp_cs_release(struct pcmcia_device *link); +static int daqp_cs_suspend(struct pcmcia_device *p_dev); +static int daqp_cs_resume(struct pcmcia_device *p_dev); + +static int daqp_cs_attach(struct pcmcia_device *); +static void daqp_cs_detach(struct pcmcia_device *); + +static int daqp_cs_attach(struct pcmcia_device *link) +{ + struct local_info_t *local; + int i; + + dev_dbg(&link->dev, "daqp_cs_attach()\n"); + + for (i = 0; i < MAX_DEV; i++) + if (dev_table[i] == NULL) + break; + if (i == MAX_DEV) { + printk(KERN_NOTICE "daqp_cs: no devices available\n"); + return -ENODEV; + } + + /* Allocate space for private device-specific data */ + local = kzalloc(sizeof(struct local_info_t), GFP_KERNEL); + if (!local) + return -ENOMEM; + + local->table_index = i; + dev_table[i] = local; + local->link = link; + link->priv = local; + + daqp_cs_config(link); + + return 0; +} /* daqp_cs_attach */ + +static void daqp_cs_detach(struct pcmcia_device *link) +{ + struct local_info_t *dev = link->priv; + + dev_dbg(&link->dev, "daqp_cs_detach\n"); + + dev->stop = 1; + daqp_cs_release(link); + + /* Unlink device structure, and free it */ + dev_table[dev->table_index] = NULL; + kfree(dev); + +} /* daqp_cs_detach */ + +static int daqp_pcmcia_config_loop(struct pcmcia_device *p_dev, void *priv_data) +{ + if (p_dev->config_index == 0) + return -EINVAL; + + return pcmcia_request_io(p_dev); +} + +static void daqp_cs_config(struct pcmcia_device *link) +{ + int ret; + + dev_dbg(&link->dev, "daqp_cs_config\n"); + + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + ret = pcmcia_loop_config(link, daqp_pcmcia_config_loop, NULL); + if (ret) { + dev_warn(&link->dev, "no configuration found\n"); + goto failed; + } + + ret = pcmcia_request_irq(link, daqp_interrupt); + if (ret) + goto failed; + + ret = pcmcia_enable_device(link); + if (ret) + goto failed; + + return; + +failed: + daqp_cs_release(link); + +} /* daqp_cs_config */ + +static void daqp_cs_release(struct pcmcia_device *link) +{ + dev_dbg(&link->dev, "daqp_cs_release\n"); + + pcmcia_disable_device(link); +} /* daqp_cs_release */ + +static int daqp_cs_suspend(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + /* Mark the device as stopped, to block IO until later */ + local->stop = 1; + return 0; +} + +static int daqp_cs_resume(struct pcmcia_device *link) +{ + struct local_info_t *local = link->priv; + + local->stop = 0; + + return 0; +} + +/*====================================================================*/ + +#ifdef MODULE + +static const struct pcmcia_device_id daqp_cs_id_table[] = { + PCMCIA_DEVICE_MANF_CARD(0x0137, 0x0027), + PCMCIA_DEVICE_NULL +}; + +MODULE_DEVICE_TABLE(pcmcia, daqp_cs_id_table); +MODULE_AUTHOR("Brent Baccala "); +MODULE_DESCRIPTION("Comedi driver for Quatech DAQP PCMCIA data capture cards"); +MODULE_LICENSE("GPL"); + +static struct pcmcia_driver daqp_cs_driver = { + .probe = daqp_cs_attach, + .remove = daqp_cs_detach, + .suspend = daqp_cs_suspend, + .resume = daqp_cs_resume, + .id_table = daqp_cs_id_table, + .owner = THIS_MODULE, + .name = "quatech_daqp_cs", +}; + +int __init init_module(void) +{ + pcmcia_register_driver(&daqp_cs_driver); + comedi_driver_register(&driver_daqp); + return 0; +} + +void __exit cleanup_module(void) +{ + comedi_driver_unregister(&driver_daqp); + pcmcia_unregister_driver(&daqp_cs_driver); +} + +#endif diff --git a/drivers/staging/comedi/drivers/rtd520.c b/drivers/staging/comedi/drivers/rtd520.c new file mode 100644 index 00000000..13844196 --- /dev/null +++ b/drivers/staging/comedi/drivers/rtd520.c @@ -0,0 +1,2395 @@ +/* + comedi/drivers/rtd520.c + Comedi driver for Real Time Devices (RTD) PCI4520/DM7520 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +/* +Driver: rtd520 +Description: Real Time Devices PCI4520/DM7520 +Author: Dan Christian +Devices: [Real Time Devices] DM7520HR-1 (rtd520), DM7520HR-8, + PCI4520, PCI4520-8 +Status: Works. Only tested on DM7520-8. Not SMP safe. + +Configuration options: + [0] - PCI bus of device (optional) + If bus / slot is not specified, the first available PCI + device will be used. + [1] - PCI slot of device (optional) +*/ +/* + Created by Dan Christian, NASA Ames Research Center. + + The PCI4520 is a PCI card. The DM7520 is a PC/104-plus card. + Both have: + 8/16 12 bit ADC with FIFO and channel gain table + 8 bits high speed digital out (for external MUX) (or 8 in or 8 out) + 8 bits high speed digital in with FIFO and interrupt on change (or 8 IO) + 2 12 bit DACs with FIFOs + 2 bits output + 2 bits input + bus mastering DMA + timers: ADC sample, pacer, burst, about, delay, DA1, DA2 + sample counter + 3 user timer/counters (8254) + external interrupt + + The DM7520 has slightly fewer features (fewer gain steps). + + These boards can support external multiplexors and multi-board + synchronization, but this driver doesn't support that. + + Board docs: http://www.rtdusa.com/PC104/DM/analog%20IO/dm7520.htm + Data sheet: http://www.rtdusa.com/pdf/dm7520.pdf + Example source: http://www.rtdusa.com/examples/dm/dm7520.zip + Call them and ask for the register level manual. + PCI chip: http://www.plxtech.com/products/io/pci9080 + + Notes: + This board is memory mapped. There is some IO stuff, but it isn't needed. + + I use a pretty loose naming style within the driver (rtd_blah). + All externally visible names should be rtd520_blah. + I use camelCase for structures (and inside them). + I may also use upper CamelCase for function names (old habit). + + This board is somewhat related to the RTD PCI4400 board. + + I borrowed heavily from the ni_mio_common, ni_atmio16d, mite, and + das1800, since they have the best documented code. Driver + cb_pcidas64.c uses the same DMA controller. + + As far as I can tell, the About interrupt doesn't work if Sample is + also enabled. It turns out that About really isn't needed, since + we always count down samples read. + + There was some timer/counter code, but it didn't follow the right API. + +*/ + +/* + driver status: + + Analog-In supports instruction and command mode. + + With DMA, you can sample at 1.15Mhz with 70% idle on a 400Mhz K6-2 + (single channel, 64K read buffer). I get random system lockups when + using DMA with ALI-15xx based systems. I haven't been able to test + any other chipsets. The lockups happen soon after the start of an + acquistion, not in the middle of a long run. + + Without DMA, you can do 620Khz sampling with 20% idle on a 400Mhz K6-2 + (with a 256K read buffer). + + Digital-IO and Analog-Out only support instruction mode. + +*/ + +#include +#include + +#include "../comedidev.h" +#include "comedi_pci.h" + +#define DRV_NAME "rtd520" + +/*====================================================================== + Driver specific stuff (tunable) +======================================================================*/ +/* Enable this to test the new DMA support. You may get hard lock ups */ +/*#define USE_DMA*/ + +/* We really only need 2 buffers. More than that means being much + smarter about knowing which ones are full. */ +#define DMA_CHAIN_COUNT 2 /* max DMA segments/buffers in a ring (min 2) */ + +/* Target period for periodic transfers. This sets the user read latency. */ +/* Note: There are certain rates where we give this up and transfer 1/2 FIFO */ +/* If this is too low, efficiency is poor */ +#define TRANS_TARGET_PERIOD 10000000 /* 10 ms (in nanoseconds) */ + +/* Set a practical limit on how long a list to support (affects memory use) */ +/* The board support a channel list up to the FIFO length (1K or 8K) */ +#define RTD_MAX_CHANLIST 128 /* max channel list that we allow */ + +/* tuning for ai/ao instruction done polling */ +#ifdef FAST_SPIN +#define WAIT_QUIETLY /* as nothing, spin on done bit */ +#define RTD_ADC_TIMEOUT 66000 /* 2 msec at 33mhz bus rate */ +#define RTD_DAC_TIMEOUT 66000 +#define RTD_DMA_TIMEOUT 33000 /* 1 msec */ +#else +/* by delaying, power and electrical noise are reduced somewhat */ +#define WAIT_QUIETLY udelay(1) +#define RTD_ADC_TIMEOUT 2000 /* in usec */ +#define RTD_DAC_TIMEOUT 2000 /* in usec */ +#define RTD_DMA_TIMEOUT 1000 /* in usec */ +#endif + +/*====================================================================== + Board specific stuff +======================================================================*/ + +/* registers */ +#define PCI_VENDOR_ID_RTD 0x1435 +/* + The board has three memory windows: las0, las1, and lcfg (the PCI chip) + Las1 has the data and can be burst DMAed 32bits at a time. +*/ +#define LCFG_PCIINDEX 0 +/* PCI region 1 is a 256 byte IO space mapping. Use??? */ +#define LAS0_PCIINDEX 2 /* PCI memory resources */ +#define LAS1_PCIINDEX 3 +#define LCFG_PCISIZE 0x100 +#define LAS0_PCISIZE 0x200 +#define LAS1_PCISIZE 0x10 + +#define RTD_CLOCK_RATE 8000000 /* 8Mhz onboard clock */ +#define RTD_CLOCK_BASE 125 /* clock period in ns */ + +/* Note: these speed are slower than the spec, but fit the counter resolution*/ +#define RTD_MAX_SPEED 1625 /* when sampling, in nanoseconds */ +/* max speed if we don't have to wait for settling */ +#define RTD_MAX_SPEED_1 875 /* if single channel, in nanoseconds */ + +#define RTD_MIN_SPEED 2097151875 /* (24bit counter) in nanoseconds */ +/* min speed when only 1 channel (no burst counter) */ +#define RTD_MIN_SPEED_1 5000000 /* 200Hz, in nanoseconds */ + +#include "rtd520.h" +#include "plx9080.h" + +/* Setup continuous ring of 1/2 FIFO transfers. See RTD manual p91 */ +#define DMA_MODE_BITS (\ + PLX_LOCAL_BUS_16_WIDE_BITS \ + | PLX_DMA_EN_READYIN_BIT \ + | PLX_DMA_LOCAL_BURST_EN_BIT \ + | PLX_EN_CHAIN_BIT \ + | PLX_DMA_INTR_PCI_BIT \ + | PLX_LOCAL_ADDR_CONST_BIT \ + | PLX_DEMAND_MODE_BIT) + +#define DMA_TRANSFER_BITS (\ +/* descriptors in PCI memory*/ PLX_DESC_IN_PCI_BIT \ +/* interrupt at end of block */ | PLX_INTR_TERM_COUNT \ +/* from board to PCI */ | PLX_XFER_LOCAL_TO_PCI) + +/*====================================================================== + Comedi specific stuff +======================================================================*/ + +/* + The board has 3 input modes and the gains of 1,2,4,...32 (, 64, 128) +*/ +static const struct comedi_lrange rtd_ai_7520_range = { 18, { + /* +-5V input range gain steps */ + BIP_RANGE(5.0), + BIP_RANGE(5.0 / 2), + BIP_RANGE(5.0 / 4), + BIP_RANGE(5.0 / 8), + BIP_RANGE(5.0 / + 16), + BIP_RANGE(5.0 / + 32), + /* +-10V input range gain steps */ + BIP_RANGE(10.0), + BIP_RANGE(10.0 / + 2), + BIP_RANGE(10.0 / + 4), + BIP_RANGE(10.0 / + 8), + BIP_RANGE(10.0 / + 16), + BIP_RANGE(10.0 / + 32), + /* +10V input range gain steps */ + UNI_RANGE(10.0), + UNI_RANGE(10.0 / + 2), + UNI_RANGE(10.0 / + 4), + UNI_RANGE(10.0 / + 8), + UNI_RANGE(10.0 / + 16), + UNI_RANGE(10.0 / + 32), + + } +}; + +/* PCI4520 has two more gains (6 more entries) */ +static const struct comedi_lrange rtd_ai_4520_range = { 24, { + /* +-5V input range gain steps */ + BIP_RANGE(5.0), + BIP_RANGE(5.0 / 2), + BIP_RANGE(5.0 / 4), + BIP_RANGE(5.0 / 8), + BIP_RANGE(5.0 / + 16), + BIP_RANGE(5.0 / + 32), + BIP_RANGE(5.0 / + 64), + BIP_RANGE(5.0 / + 128), + /* +-10V input range gain steps */ + BIP_RANGE(10.0), + BIP_RANGE(10.0 / + 2), + BIP_RANGE(10.0 / + 4), + BIP_RANGE(10.0 / + 8), + BIP_RANGE(10.0 / + 16), + BIP_RANGE(10.0 / + 32), + BIP_RANGE(10.0 / + 64), + BIP_RANGE(10.0 / + 128), + /* +10V input range gain steps */ + UNI_RANGE(10.0), + UNI_RANGE(10.0 / + 2), + UNI_RANGE(10.0 / + 4), + UNI_RANGE(10.0 / + 8), + UNI_RANGE(10.0 / + 16), + UNI_RANGE(10.0 / + 32), + UNI_RANGE(10.0 / + 64), + UNI_RANGE(10.0 / + 128), + } +}; + +/* Table order matches range values */ +static const struct comedi_lrange rtd_ao_range = { 4, { + RANGE(0, 5), + RANGE(0, 10), + RANGE(-5, 5), + RANGE(-10, 10), + } +}; + +/* + Board descriptions + */ +struct rtdBoard { + const char *name; /* must be first */ + int device_id; + int aiChans; + int aiBits; + int aiMaxGain; + int range10Start; /* start of +-10V range */ + int rangeUniStart; /* start of +10V range */ +}; + +static const struct rtdBoard rtd520Boards[] = { + { + .name = "DM7520", + .device_id = 0x7520, + .aiChans = 16, + .aiBits = 12, + .aiMaxGain = 32, + .range10Start = 6, + .rangeUniStart = 12, + }, + { + .name = "PCI4520", + .device_id = 0x4520, + .aiChans = 16, + .aiBits = 12, + .aiMaxGain = 128, + .range10Start = 8, + .rangeUniStart = 16, + }, +}; + +static DEFINE_PCI_DEVICE_TABLE(rtd520_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_RTD, 0x7520) }, + { PCI_DEVICE(PCI_VENDOR_ID_RTD, 0x4520) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, rtd520_pci_table); + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct rtdBoard *)dev->board_ptr) + +/* + This structure is for data unique to this hardware driver. + This is also unique for each board in the system. +*/ +struct rtdPrivate { + /* memory mapped board structures */ + void *las0; + void *las1; + void *lcfg; + + unsigned long intCount; /* interrupt count */ + long aiCount; /* total transfer size (samples) */ + int transCount; /* # to tranfer data. 0->1/2FIFO */ + int flags; /* flag event modes */ + + /* PCI device info */ + struct pci_dev *pci_dev; + int got_regions; /* non-zero if PCI regions owned */ + + /* channel list info */ + /* chanBipolar tracks whether a channel is bipolar (and needs +2048) */ + unsigned char chanBipolar[RTD_MAX_CHANLIST / 8]; /* bit array */ + + /* read back data */ + unsigned int aoValue[2]; /* Used for AO read back */ + + /* timer gate (when enabled) */ + u8 utcGate[4]; /* 1 extra allows simple range check */ + + /* shadow registers affect other registers, but can't be read back */ + /* The macros below update these on writes */ + u16 intMask; /* interrupt mask */ + u16 intClearMask; /* interrupt clear mask */ + u8 utcCtrl[4]; /* crtl mode for 3 utc + read back */ + u8 dioStatus; /* could be read back (dio0Ctrl) */ +#ifdef USE_DMA + /* Always DMA 1/2 FIFO. Buffer (dmaBuff?) is (at least) twice that size. + After transferring, interrupt processes 1/2 FIFO and passes to comedi */ + s16 dma0Offset; /* current processing offset (0, 1/2) */ + uint16_t *dma0Buff[DMA_CHAIN_COUNT]; /* DMA buffers (for ADC) */ + dma_addr_t dma0BuffPhysAddr[DMA_CHAIN_COUNT]; /* physical addresses */ + struct plx_dma_desc *dma0Chain; /* DMA descriptor ring for dmaBuff */ + dma_addr_t dma0ChainPhysAddr; /* physical addresses */ + /* shadow registers */ + u8 dma0Control; + u8 dma1Control; +#endif /* USE_DMA */ + unsigned fifoLen; +}; + +/* bit defines for "flags" */ +#define SEND_EOS 0x01 /* send End Of Scan events */ +#define DMA0_ACTIVE 0x02 /* DMA0 is active */ +#define DMA1_ACTIVE 0x04 /* DMA1 is active */ + +/* Macros for accessing channel list bit array */ +#define CHAN_ARRAY_TEST(array, index) \ + (((array)[(index)/8] >> ((index) & 0x7)) & 0x1) +#define CHAN_ARRAY_SET(array, index) \ + (((array)[(index)/8] |= 1 << ((index) & 0x7))) +#define CHAN_ARRAY_CLEAR(array, index) \ + (((array)[(index)/8] &= ~(1 << ((index) & 0x7)))) + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct rtdPrivate *)dev->private) + +/* Macros to access registers */ + +/* Reset board */ +#define RtdResetBoard(dev) \ + writel(0, devpriv->las0+LAS0_BOARD_RESET) + +/* Reset channel gain table read pointer */ +#define RtdResetCGT(dev) \ + writel(0, devpriv->las0+LAS0_CGT_RESET) + +/* Reset channel gain table read and write pointers */ +#define RtdClearCGT(dev) \ + writel(0, devpriv->las0+LAS0_CGT_CLEAR) + +/* Reset channel gain table read and write pointers */ +#define RtdEnableCGT(dev, v) \ + writel((v > 0) ? 1 : 0, devpriv->las0+LAS0_CGT_ENABLE) + +/* Write channel gain table entry */ +#define RtdWriteCGTable(dev, v) \ + writel(v, devpriv->las0+LAS0_CGT_WRITE) + +/* Write Channel Gain Latch */ +#define RtdWriteCGLatch(dev, v) \ + writel(v, devpriv->las0+LAS0_CGL_WRITE) + +/* Reset ADC FIFO */ +#define RtdAdcClearFifo(dev) \ + writel(0, devpriv->las0+LAS0_ADC_FIFO_CLEAR) + +/* Set ADC start conversion source select (write only) */ +#define RtdAdcConversionSource(dev, v) \ + writel(v, devpriv->las0+LAS0_ADC_CONVERSION) + +/* Set burst start source select (write only) */ +#define RtdBurstStartSource(dev, v) \ + writel(v, devpriv->las0+LAS0_BURST_START) + +/* Set Pacer start source select (write only) */ +#define RtdPacerStartSource(dev, v) \ + writel(v, devpriv->las0+LAS0_PACER_START) + +/* Set Pacer stop source select (write only) */ +#define RtdPacerStopSource(dev, v) \ + writel(v, devpriv->las0+LAS0_PACER_STOP) + +/* Set Pacer clock source select (write only) 0=external 1=internal */ +#define RtdPacerClockSource(dev, v) \ + writel((v > 0) ? 1 : 0, devpriv->las0+LAS0_PACER_SELECT) + +/* Set sample counter source select (write only) */ +#define RtdAdcSampleCounterSource(dev, v) \ + writel(v, devpriv->las0+LAS0_ADC_SCNT_SRC) + +/* Set Pacer trigger mode select (write only) 0=single cycle, 1=repeat */ +#define RtdPacerTriggerMode(dev, v) \ + writel((v > 0) ? 1 : 0, devpriv->las0+LAS0_PACER_REPEAT) + +/* Set About counter stop enable (write only) */ +#define RtdAboutStopEnable(dev, v) \ + writel((v > 0) ? 1 : 0, devpriv->las0+LAS0_ACNT_STOP_ENABLE) + +/* Set external trigger polarity (write only) 0=positive edge, 1=negative */ +#define RtdTriggerPolarity(dev, v) \ + writel((v > 0) ? 1 : 0, devpriv->las0+LAS0_ETRG_POLARITY) + +/* Start single ADC conversion */ +#define RtdAdcStart(dev) \ + writew(0, devpriv->las0+LAS0_ADC) + +/* Read one ADC data value (12bit (with sign extend) as 16bit) */ +/* Note: matches what DMA would get. Actual value >> 3 */ +#define RtdAdcFifoGet(dev) \ + readw(devpriv->las1+LAS1_ADC_FIFO) + +/* Read two ADC data values (DOESN'T WORK) */ +#define RtdAdcFifoGet2(dev) \ + readl(devpriv->las1+LAS1_ADC_FIFO) + +/* FIFO status */ +#define RtdFifoStatus(dev) \ + readl(devpriv->las0+LAS0_ADC) + +/* pacer start/stop read=start, write=stop*/ +#define RtdPacerStart(dev) \ + readl(devpriv->las0+LAS0_PACER) +#define RtdPacerStop(dev) \ + writel(0, devpriv->las0+LAS0_PACER) + +/* Interrupt status */ +#define RtdInterruptStatus(dev) \ + readw(devpriv->las0+LAS0_IT) + +/* Interrupt mask */ +#define RtdInterruptMask(dev, v) \ + writew((devpriv->intMask = (v)), devpriv->las0+LAS0_IT) + +/* Interrupt status clear (only bits set in mask) */ +#define RtdInterruptClear(dev) \ + readw(devpriv->las0+LAS0_CLEAR) + +/* Interrupt clear mask */ +#define RtdInterruptClearMask(dev, v) \ + writew((devpriv->intClearMask = (v)), devpriv->las0+LAS0_CLEAR) + +/* Interrupt overrun status */ +#define RtdInterruptOverrunStatus(dev) \ + readl(devpriv->las0+LAS0_OVERRUN) + +/* Interrupt overrun clear */ +#define RtdInterruptOverrunClear(dev) \ + writel(0, devpriv->las0+LAS0_OVERRUN) + +/* Pacer counter, 24bit */ +#define RtdPacerCount(dev) \ + readl(devpriv->las0+LAS0_PCLK) +#define RtdPacerCounter(dev, v) \ + writel((v) & 0xffffff, devpriv->las0+LAS0_PCLK) + +/* Burst counter, 10bit */ +#define RtdBurstCount(dev) \ + readl(devpriv->las0+LAS0_BCLK) +#define RtdBurstCounter(dev, v) \ + writel((v) & 0x3ff, devpriv->las0+LAS0_BCLK) + +/* Delay counter, 16bit */ +#define RtdDelayCount(dev) \ + readl(devpriv->las0+LAS0_DCLK) +#define RtdDelayCounter(dev, v) \ + writel((v) & 0xffff, devpriv->las0+LAS0_DCLK) + +/* About counter, 16bit */ +#define RtdAboutCount(dev) \ + readl(devpriv->las0+LAS0_ACNT) +#define RtdAboutCounter(dev, v) \ + writel((v) & 0xffff, devpriv->las0+LAS0_ACNT) + +/* ADC sample counter, 10bit */ +#define RtdAdcSampleCount(dev) \ + readl(devpriv->las0+LAS0_ADC_SCNT) +#define RtdAdcSampleCounter(dev, v) \ + writel((v) & 0x3ff, devpriv->las0+LAS0_ADC_SCNT) + +/* User Timer/Counter (8254) */ +#define RtdUtcCounterGet(dev, n) \ + readb(devpriv->las0 \ + + ((n <= 0) ? LAS0_UTC0 : ((1 == n) ? LAS0_UTC1 : LAS0_UTC2))) + +#define RtdUtcCounterPut(dev, n, v) \ + writeb((v) & 0xff, devpriv->las0 \ + + ((n <= 0) ? LAS0_UTC0 : ((1 == n) ? LAS0_UTC1 : LAS0_UTC2))) + +/* Set UTC (8254) control byte */ +#define RtdUtcCtrlPut(dev, n, v) \ + writeb(devpriv->utcCtrl[(n) & 3] = (((n) & 3) << 6) | ((v) & 0x3f), \ + devpriv->las0 + LAS0_UTC_CTRL) + +/* Set UTCn clock source (write only) */ +#define RtdUtcClockSource(dev, n, v) \ + writew(v, devpriv->las0 \ + + ((n <= 0) ? LAS0_UTC0_CLOCK : \ + ((1 == n) ? LAS0_UTC1_CLOCK : LAS0_UTC2_CLOCK))) + +/* Set UTCn gate source (write only) */ +#define RtdUtcGateSource(dev, n, v) \ + writew(v, devpriv->las0 \ + + ((n <= 0) ? LAS0_UTC0_GATE : \ + ((1 == n) ? LAS0_UTC1_GATE : LAS0_UTC2_GATE))) + +/* User output N source select (write only) */ +#define RtdUsrOutSource(dev, n, v) \ + writel(v, devpriv->las0+((n <= 0) ? LAS0_UOUT0_SELECT : LAS0_UOUT1_SELECT)) + +/* Digital IO */ +#define RtdDio0Read(dev) \ + (readw(devpriv->las0+LAS0_DIO0) & 0xff) +#define RtdDio0Write(dev, v) \ + writew((v) & 0xff, devpriv->las0+LAS0_DIO0) + +#define RtdDio1Read(dev) \ + (readw(devpriv->las0+LAS0_DIO1) & 0xff) +#define RtdDio1Write(dev, v) \ + writew((v) & 0xff, devpriv->las0+LAS0_DIO1) + +#define RtdDioStatusRead(dev) \ + (readw(devpriv->las0+LAS0_DIO_STATUS) & 0xff) +#define RtdDioStatusWrite(dev, v) \ + writew((devpriv->dioStatus = (v)), devpriv->las0+LAS0_DIO_STATUS) + +#define RtdDio0CtrlRead(dev) \ + (readw(devpriv->las0+LAS0_DIO0_CTRL) & 0xff) +#define RtdDio0CtrlWrite(dev, v) \ + writew((v) & 0xff, devpriv->las0+LAS0_DIO0_CTRL) + +/* Digital to Analog converter */ +/* Write one data value (sign + 12bit + marker bits) */ +/* Note: matches what DMA would put. Actual value << 3 */ +#define RtdDacFifoPut(dev, n, v) \ + writew((v), devpriv->las1 + (((n) == 0) ? LAS1_DAC1_FIFO : LAS1_DAC2_FIFO)) + +/* Start single DAC conversion */ +#define RtdDacUpdate(dev, n) \ + writew(0, devpriv->las0 + (((n) == 0) ? LAS0_DAC1 : LAS0_DAC2)) + +/* Start single DAC conversion on both DACs */ +#define RtdDacBothUpdate(dev) \ + writew(0, devpriv->las0+LAS0_DAC) + +/* Set DAC output type and range */ +#define RtdDacRange(dev, n, v) \ + writew((v) & 7, devpriv->las0 \ + +(((n) == 0) ? LAS0_DAC1_CTRL : LAS0_DAC2_CTRL)) + +/* Reset DAC FIFO */ +#define RtdDacClearFifo(dev, n) \ + writel(0, devpriv->las0+(((n) == 0) ? LAS0_DAC1_RESET : LAS0_DAC2_RESET)) + +/* Set source for DMA 0 (write only, shadow?) */ +#define RtdDma0Source(dev, n) \ + writel((n) & 0xf, devpriv->las0+LAS0_DMA0_SRC) + +/* Set source for DMA 1 (write only, shadow?) */ +#define RtdDma1Source(dev, n) \ + writel((n) & 0xf, devpriv->las0+LAS0_DMA1_SRC) + +/* Reset board state for DMA 0 */ +#define RtdDma0Reset(dev) \ + writel(0, devpriv->las0+LAS0_DMA0_RESET) + +/* Reset board state for DMA 1 */ +#define RtdDma1Reset(dev) \ + writel(0, devpriv->las0+LAS0_DMA1_SRC) + +/* PLX9080 interrupt mask and status */ +#define RtdPlxInterruptRead(dev) \ + readl(devpriv->lcfg+LCFG_ITCSR) +#define RtdPlxInterruptWrite(dev, v) \ + writel(v, devpriv->lcfg+LCFG_ITCSR) + +/* Set mode for DMA 0 */ +#define RtdDma0Mode(dev, m) \ + writel((m), devpriv->lcfg+LCFG_DMAMODE0) + +/* Set PCI address for DMA 0 */ +#define RtdDma0PciAddr(dev, a) \ + writel((a), devpriv->lcfg+LCFG_DMAPADR0) + +/* Set local address for DMA 0 */ +#define RtdDma0LocalAddr(dev, a) \ + writel((a), devpriv->lcfg+LCFG_DMALADR0) + +/* Set byte count for DMA 0 */ +#define RtdDma0Count(dev, c) \ + writel((c), devpriv->lcfg+LCFG_DMASIZ0) + +/* Set next descriptor for DMA 0 */ +#define RtdDma0Next(dev, a) \ + writel((a), devpriv->lcfg+LCFG_DMADPR0) + +/* Set mode for DMA 1 */ +#define RtdDma1Mode(dev, m) \ + writel((m), devpriv->lcfg+LCFG_DMAMODE1) + +/* Set PCI address for DMA 1 */ +#define RtdDma1PciAddr(dev, a) \ + writel((a), devpriv->lcfg+LCFG_DMAADR1) + +/* Set local address for DMA 1 */ +#define RtdDma1LocalAddr(dev, a) \ + writel((a), devpriv->lcfg+LCFG_DMALADR1) + +/* Set byte count for DMA 1 */ +#define RtdDma1Count(dev, c) \ + writel((c), devpriv->lcfg+LCFG_DMASIZ1) + +/* Set next descriptor for DMA 1 */ +#define RtdDma1Next(dev, a) \ + writel((a), devpriv->lcfg+LCFG_DMADPR1) + +/* Set control for DMA 0 (write only, shadow?) */ +#define RtdDma0Control(dev, n) \ + writeb(devpriv->dma0Control = (n), devpriv->lcfg+LCFG_DMACSR0) + +/* Get status for DMA 0 */ +#define RtdDma0Status(dev) \ + readb(devpriv->lcfg+LCFG_DMACSR0) + +/* Set control for DMA 1 (write only, shadow?) */ +#define RtdDma1Control(dev, n) \ + writeb(devpriv->dma1Control = (n), devpriv->lcfg+LCFG_DMACSR1) + +/* Get status for DMA 1 */ +#define RtdDma1Status(dev) \ + readb(devpriv->lcfg+LCFG_DMACSR1) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attac/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int rtd_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int rtd_detach(struct comedi_device *dev); + +static struct comedi_driver rtd520Driver = { + .driver_name = DRV_NAME, + .module = THIS_MODULE, + .attach = rtd_attach, + .detach = rtd_detach, +}; + +static int rtd_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int rtd_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int rtd_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int rtd_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int rtd_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int rtd_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_cmd *cmd); +static int rtd_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int rtd_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s); +/* static int rtd_ai_poll (struct comedi_device *dev,struct comedi_subdevice *s); */ +static int rtd_ns_to_timer(unsigned int *ns, int roundMode); +static irqreturn_t rtd_interrupt(int irq, void *d); +static int rtd520_probe_fifo_depth(struct comedi_device *dev); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int rtd_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ /* board name and options flags */ + struct comedi_subdevice *s; + struct pci_dev *pcidev; + int ret; + resource_size_t physLas0; /* configuration */ + resource_size_t physLas1; /* data area */ + resource_size_t physLcfg; /* PLX9080 */ +#ifdef USE_DMA + int index; +#endif + + printk(KERN_INFO "comedi%d: rtd520 attaching.\n", dev->minor); + +#if defined(CONFIG_COMEDI_DEBUG) && defined(USE_DMA) + /* You can set this a load time: modprobe comedi comedi_debug=1 */ + if (0 == comedi_debug) /* force DMA debug printks */ + comedi_debug = 1; +#endif + + /* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct rtdPrivate)) < 0) + return -ENOMEM; + + /* + * Probe the device to determine what device in the series it is. + */ + for (pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, NULL); + pcidev != NULL; + pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, pcidev)) { + int i; + + if (it->options[0] || it->options[1]) { + if (pcidev->bus->number != it->options[0] + || PCI_SLOT(pcidev->devfn) != it->options[1]) { + continue; + } + } + for (i = 0; i < ARRAY_SIZE(rtd520Boards); ++i) { + if (pcidev->device == rtd520Boards[i].device_id) { + dev->board_ptr = &rtd520Boards[i]; + break; + } + } + if (dev->board_ptr) + break; /* found one */ + } + if (!pcidev) { + if (it->options[0] && it->options[1]) { + printk(KERN_INFO "No RTD card at bus=%d slot=%d.\n", + it->options[0], it->options[1]); + } else { + printk(KERN_INFO "No RTD card found.\n"); + } + return -EIO; + } + devpriv->pci_dev = pcidev; + dev->board_name = thisboard->name; + + ret = comedi_pci_enable(pcidev, DRV_NAME); + if (ret < 0) { + printk(KERN_INFO "Failed to enable PCI device and request regions.\n"); + return ret; + } + devpriv->got_regions = 1; + + /* + * Initialize base addresses + */ + /* Get the physical address from PCI config */ + physLas0 = pci_resource_start(devpriv->pci_dev, LAS0_PCIINDEX); + physLas1 = pci_resource_start(devpriv->pci_dev, LAS1_PCIINDEX); + physLcfg = pci_resource_start(devpriv->pci_dev, LCFG_PCIINDEX); + /* Now have the kernel map this into memory */ + /* ASSUME page aligned */ + devpriv->las0 = ioremap_nocache(physLas0, LAS0_PCISIZE); + devpriv->las1 = ioremap_nocache(physLas1, LAS1_PCISIZE); + devpriv->lcfg = ioremap_nocache(physLcfg, LCFG_PCISIZE); + + if (!devpriv->las0 || !devpriv->las1 || !devpriv->lcfg) + return -ENOMEM; + + + DPRINTK("%s: LAS0=%llx, LAS1=%llx, CFG=%llx.\n", dev->board_name, + (unsigned long long)physLas0, (unsigned long long)physLas1, + (unsigned long long)physLcfg); + { /* The RTD driver does this */ + unsigned char pci_latency; + u16 revision; + /*uint32_t epld_version; */ + + pci_read_config_word(devpriv->pci_dev, PCI_REVISION_ID, + &revision); + DPRINTK("%s: PCI revision %d.\n", dev->board_name, revision); + + pci_read_config_byte(devpriv->pci_dev, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < 32) { + printk(KERN_INFO "%s: PCI latency changed from %d to %d\n", + dev->board_name, pci_latency, 32); + pci_write_config_byte(devpriv->pci_dev, + PCI_LATENCY_TIMER, 32); + } else { + DPRINTK("rtd520: PCI latency = %d\n", pci_latency); + } + + /* Undocumented EPLD version (doesn't match RTD driver results) */ + /*DPRINTK ("rtd520: Reading epld from %p\n", + devpriv->las0+0); + epld_version = readl (devpriv->las0+0); + if ((epld_version & 0xF0) >> 4 == 0x0F) { + DPRINTK("rtd520: pre-v8 EPLD. (%x)\n", epld_version); + } else { + DPRINTK("rtd520: EPLD version %x.\n", epld_version >> 4); + } */ + } + + /* Show board configuration */ + printk(KERN_INFO "%s:", dev->board_name); + + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + + s = dev->subdevices + 0; + dev->read_subdev = s; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF | SDF_CMD_READ; + s->n_chan = thisboard->aiChans; + s->maxdata = (1 << thisboard->aiBits) - 1; + if (thisboard->aiMaxGain <= 32) + s->range_table = &rtd_ai_7520_range; + else + s->range_table = &rtd_ai_4520_range; + + s->len_chanlist = RTD_MAX_CHANLIST; /* devpriv->fifoLen */ + s->insn_read = rtd_ai_rinsn; + s->do_cmd = rtd_ai_cmd; + s->do_cmdtest = rtd_ai_cmdtest; + s->cancel = rtd_ai_cancel; + /* s->poll = rtd_ai_poll; *//* not ready yet */ + + s = dev->subdevices + 1; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << thisboard->aiBits) - 1; + s->range_table = &rtd_ao_range; + s->insn_write = rtd_ao_winsn; + s->insn_read = rtd_ao_rinsn; + + s = dev->subdevices + 2; + /* digital i/o subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + /* we only support port 0 right now. Ignoring port 1 and user IO */ + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = rtd_dio_insn_bits; + s->insn_config = rtd_dio_insn_config; + + /* timer/counter subdevices (not currently supported) */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 3; + s->maxdata = 0xffff; + + /* initialize board, per RTD spec */ + /* also, initialize shadow registers */ + RtdResetBoard(dev); + udelay(100); /* needed? */ + RtdPlxInterruptWrite(dev, 0); + RtdInterruptMask(dev, 0); /* and sets shadow */ + RtdInterruptClearMask(dev, ~0); /* and sets shadow */ + RtdInterruptClear(dev); /* clears bits set by mask */ + RtdInterruptOverrunClear(dev); + RtdClearCGT(dev); + RtdAdcClearFifo(dev); + RtdDacClearFifo(dev, 0); + RtdDacClearFifo(dev, 1); + /* clear digital IO fifo */ + RtdDioStatusWrite(dev, 0); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 0, 0x30); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 1, 0x30); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 2, 0x30); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 3, 0); /* safe state, set shadow */ + /* TODO: set user out source ??? */ + + /* check if our interrupt is available and get it */ + ret = request_irq(devpriv->pci_dev->irq, rtd_interrupt, + IRQF_SHARED, DRV_NAME, dev); + + if (ret < 0) { + printk("Could not get interrupt! (%u)\n", + devpriv->pci_dev->irq); + return ret; + } + dev->irq = devpriv->pci_dev->irq; + printk(KERN_INFO "( irq=%u )", dev->irq); + + ret = rtd520_probe_fifo_depth(dev); + if (ret < 0) + return ret; + + devpriv->fifoLen = ret; + printk("( fifoLen=%d )", devpriv->fifoLen); + +#ifdef USE_DMA + if (dev->irq > 0) { + printk("( DMA buff=%d )\n", DMA_CHAIN_COUNT); + /* The PLX9080 has 2 DMA controllers, but there could be 4 sources: + ADC, digital, DAC1, and DAC2. Since only the ADC supports cmd mode + right now, this isn't an issue (yet) */ + devpriv->dma0Offset = 0; + + for (index = 0; index < DMA_CHAIN_COUNT; index++) { + devpriv->dma0Buff[index] = + pci_alloc_consistent(devpriv->pci_dev, + sizeof(u16) * + devpriv->fifoLen / 2, + &devpriv-> + dma0BuffPhysAddr[index]); + if (devpriv->dma0Buff[index] == NULL) { + ret = -ENOMEM; + goto rtd_attach_die_error; + } + /*DPRINTK ("buff[%d] @ %p virtual, %x PCI\n", + index, + devpriv->dma0Buff[index], devpriv->dma0BuffPhysAddr[index]); */ + } + + /* setup DMA descriptor ring (use cpu_to_le32 for byte ordering?) */ + devpriv->dma0Chain = + pci_alloc_consistent(devpriv->pci_dev, + sizeof(struct plx_dma_desc) * + DMA_CHAIN_COUNT, + &devpriv->dma0ChainPhysAddr); + for (index = 0; index < DMA_CHAIN_COUNT; index++) { + devpriv->dma0Chain[index].pci_start_addr = + devpriv->dma0BuffPhysAddr[index]; + devpriv->dma0Chain[index].local_start_addr = + DMALADDR_ADC; + devpriv->dma0Chain[index].transfer_size = + sizeof(u16) * devpriv->fifoLen / 2; + devpriv->dma0Chain[index].next = + (devpriv->dma0ChainPhysAddr + ((index + + 1) % + (DMA_CHAIN_COUNT)) + * sizeof(devpriv->dma0Chain[0])) + | DMA_TRANSFER_BITS; + /*DPRINTK ("ring[%d] @%lx PCI: %x, local: %x, N: 0x%x, next: %x\n", + index, + ((long)devpriv->dma0ChainPhysAddr + + (index * sizeof(devpriv->dma0Chain[0]))), + devpriv->dma0Chain[index].pci_start_addr, + devpriv->dma0Chain[index].local_start_addr, + devpriv->dma0Chain[index].transfer_size, + devpriv->dma0Chain[index].next); */ + } + + if (devpriv->dma0Chain == NULL) { + ret = -ENOMEM; + goto rtd_attach_die_error; + } + + RtdDma0Mode(dev, DMA_MODE_BITS); + /* set DMA trigger source */ + RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL); + } else { + printk(KERN_INFO "( no IRQ->no DMA )"); + } +#endif /* USE_DMA */ + + if (dev->irq) { /* enable plx9080 interrupts */ + RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE); + } + + printk("\ncomedi%d: rtd520 driver attached.\n", dev->minor); + + return 1; + +#if 0 + /* hit an error, clean up memory and return ret */ +/* rtd_attach_die_error: */ +#ifdef USE_DMA + for (index = 0; index < DMA_CHAIN_COUNT; index++) { + if (NULL != devpriv->dma0Buff[index]) { /* free buffer memory */ + pci_free_consistent(devpriv->pci_dev, + sizeof(u16) * devpriv->fifoLen / 2, + devpriv->dma0Buff[index], + devpriv->dma0BuffPhysAddr[index]); + devpriv->dma0Buff[index] = NULL; + } + } + if (NULL != devpriv->dma0Chain) { + pci_free_consistent(devpriv->pci_dev, + sizeof(struct plx_dma_desc) + * DMA_CHAIN_COUNT, + devpriv->dma0Chain, + devpriv->dma0ChainPhysAddr); + devpriv->dma0Chain = NULL; + } +#endif /* USE_DMA */ + /* subdevices and priv are freed by the core */ + if (dev->irq) { + /* disable interrupt controller */ + RtdPlxInterruptWrite(dev, RtdPlxInterruptRead(dev) + & ~(ICS_PLIE | ICS_DMA0_E | ICS_DMA1_E)); + free_irq(dev->irq, dev); + } + + /* release all regions that were allocated */ + if (devpriv->las0) + iounmap(devpriv->las0); + + if (devpriv->las1) + iounmap(devpriv->las1); + + if (devpriv->lcfg) + iounmap(devpriv->lcfg); + + if (devpriv->pci_dev) + pci_dev_put(devpriv->pci_dev); + + return ret; +#endif +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int rtd_detach(struct comedi_device *dev) +{ +#ifdef USE_DMA + int index; +#endif + + DPRINTK("comedi%d: rtd520: removing (%ld ints)\n", + dev->minor, (devpriv ? devpriv->intCount : 0L)); + if (devpriv && devpriv->lcfg) { + DPRINTK + ("(int status 0x%x, overrun status 0x%x, fifo status 0x%x)...\n", + 0xffff & RtdInterruptStatus(dev), + 0xffff & RtdInterruptOverrunStatus(dev), + (0xffff & RtdFifoStatus(dev)) ^ 0x6666); + } + + if (devpriv) { + /* Shut down any board ops by resetting it */ +#ifdef USE_DMA + if (devpriv->lcfg) { + RtdDma0Control(dev, 0); /* disable DMA */ + RtdDma1Control(dev, 0); /* disable DMA */ + RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE); + } +#endif /* USE_DMA */ + if (devpriv->las0) { + RtdResetBoard(dev); + RtdInterruptMask(dev, 0); + RtdInterruptClearMask(dev, ~0); + RtdInterruptClear(dev); /* clears bits set by mask */ + } +#ifdef USE_DMA + /* release DMA */ + for (index = 0; index < DMA_CHAIN_COUNT; index++) { + if (NULL != devpriv->dma0Buff[index]) { + pci_free_consistent(devpriv->pci_dev, + sizeof(u16) * + devpriv->fifoLen / 2, + devpriv->dma0Buff[index], + devpriv-> + dma0BuffPhysAddr[index]); + devpriv->dma0Buff[index] = NULL; + } + } + if (NULL != devpriv->dma0Chain) { + pci_free_consistent(devpriv->pci_dev, + sizeof(struct plx_dma_desc) * + DMA_CHAIN_COUNT, devpriv->dma0Chain, + devpriv->dma0ChainPhysAddr); + devpriv->dma0Chain = NULL; + } +#endif /* USE_DMA */ + + /* release IRQ */ + if (dev->irq) { + /* disable interrupt controller */ + RtdPlxInterruptWrite(dev, RtdPlxInterruptRead(dev) + & ~(ICS_PLIE | ICS_DMA0_E | + ICS_DMA1_E)); + free_irq(dev->irq, dev); + } + + /* release all regions that were allocated */ + if (devpriv->las0) + iounmap(devpriv->las0); + + if (devpriv->las1) + iounmap(devpriv->las1); + + if (devpriv->lcfg) + iounmap(devpriv->lcfg); + + if (devpriv->pci_dev) { + if (devpriv->got_regions) + comedi_pci_disable(devpriv->pci_dev); + + pci_dev_put(devpriv->pci_dev); + } + } + + printk(KERN_INFO "comedi%d: rtd520: removed.\n", dev->minor); + + return 0; +} + +/* + Convert a single comedi channel-gain entry to a RTD520 table entry +*/ +static unsigned short rtdConvertChanGain(struct comedi_device *dev, + unsigned int comediChan, int chanIndex) +{ /* index in channel list */ + unsigned int chan, range, aref; + unsigned short r = 0; + + chan = CR_CHAN(comediChan); + range = CR_RANGE(comediChan); + aref = CR_AREF(comediChan); + + r |= chan & 0xf; + + /* Note: we also setup the channel list bipolar flag array */ + if (range < thisboard->range10Start) { /* first batch are +-5 */ + r |= 0x000; /* +-5 range */ + r |= (range & 0x7) << 4; /* gain */ + CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex); + } else if (range < thisboard->rangeUniStart) { /* second batch are +-10 */ + r |= 0x100; /* +-10 range */ + /* gain */ + r |= ((range - thisboard->range10Start) & 0x7) << 4; + CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex); + } else { /* last batch is +10 */ + r |= 0x200; /* +10 range */ + /* gain */ + r |= ((range - thisboard->rangeUniStart) & 0x7) << 4; + CHAN_ARRAY_CLEAR(devpriv->chanBipolar, chanIndex); + } + + switch (aref) { + case AREF_GROUND: /* on-board ground */ + break; + + case AREF_COMMON: + r |= 0x80; /* ref external analog common */ + break; + + case AREF_DIFF: + r |= 0x400; /* differential inputs */ + break; + + case AREF_OTHER: /* ??? */ + break; + } + /*printk ("chan=%d r=%d a=%d -> 0x%x\n", + chan, range, aref, r); */ + return r; +} + +/* + Setup the channel-gain table from a comedi list +*/ +static void rtd_load_channelgain_list(struct comedi_device *dev, + unsigned int n_chan, unsigned int *list) +{ + if (n_chan > 1) { /* setup channel gain table */ + int ii; + RtdClearCGT(dev); + RtdEnableCGT(dev, 1); /* enable table */ + for (ii = 0; ii < n_chan; ii++) { + RtdWriteCGTable(dev, rtdConvertChanGain(dev, list[ii], + ii)); + } + } else { /* just use the channel gain latch */ + RtdEnableCGT(dev, 0); /* disable table, enable latch */ + RtdWriteCGLatch(dev, rtdConvertChanGain(dev, list[0], 0)); + } +} + +/* determine fifo size by doing adc conversions until the fifo half +empty status flag clears */ +static int rtd520_probe_fifo_depth(struct comedi_device *dev) +{ + unsigned int chanspec = CR_PACK(0, 0, AREF_GROUND); + unsigned i; + static const unsigned limit = 0x2000; + unsigned fifo_size = 0; + + RtdAdcClearFifo(dev); + rtd_load_channelgain_list(dev, 1, &chanspec); + RtdAdcConversionSource(dev, 0); /* software */ + /* convert samples */ + for (i = 0; i < limit; ++i) { + unsigned fifo_status; + /* trigger conversion */ + RtdAdcStart(dev); + udelay(1); + fifo_status = RtdFifoStatus(dev); + if ((fifo_status & FS_ADC_HEMPTY) == 0) { + fifo_size = 2 * i; + break; + } + } + if (i == limit) { + printk(KERN_INFO "\ncomedi: %s: failed to probe fifo size.\n", DRV_NAME); + return -EIO; + } + RtdAdcClearFifo(dev); + if (fifo_size != 0x400 && fifo_size != 0x2000) { + printk + (KERN_INFO "\ncomedi: %s: unexpected fifo size of %i, expected 1024 or 8192.\n", + DRV_NAME, fifo_size); + return -EIO; + } + return fifo_size; +} + +/* + "instructions" read/write data in "one-shot" or "software-triggered" + mode (simplest case). + This doesn't use interrupts. + + Note, we don't do any settling delays. Use a instruction list to + select, delay, then read. + */ +static int rtd_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int n, ii; + int stat; + + /* clear any old fifo data */ + RtdAdcClearFifo(dev); + + /* write channel to multiplexer and clear channel gain table */ + rtd_load_channelgain_list(dev, 1, &insn->chanspec); + + /* set conversion source */ + RtdAdcConversionSource(dev, 0); /* software */ + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + s16 d; + /* trigger conversion */ + RtdAdcStart(dev); + + for (ii = 0; ii < RTD_ADC_TIMEOUT; ++ii) { + stat = RtdFifoStatus(dev); + if (stat & FS_ADC_NOT_EMPTY) /* 1 -> not empty */ + break; + WAIT_QUIETLY; + } + if (ii >= RTD_ADC_TIMEOUT) { + DPRINTK + ("rtd520: Error: ADC never finished! FifoStatus=0x%x\n", + stat ^ 0x6666); + return -ETIMEDOUT; + } + + /* read data */ + d = RtdAdcFifoGet(dev); /* get 2s comp value */ + /*printk ("rtd520: Got 0x%x after %d usec\n", d, ii+1); */ + d = d >> 3; /* low 3 bits are marker lines */ + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, 0)) + /* convert to comedi unsigned data */ + data[n] = d + 2048; + else + data[n] = d; + } + + /* return the number of samples read/written */ + return n; +} + +/* + Get what we know is there.... Fast! + This uses 1/2 the bus cycles of read_dregs (below). + + The manual claims that we can do a lword read, but it doesn't work here. +*/ +static int ai_read_n(struct comedi_device *dev, struct comedi_subdevice *s, + int count) +{ + int ii; + + for (ii = 0; ii < count; ii++) { + short sample; + s16 d; + + if (0 == devpriv->aiCount) { /* done */ + d = RtdAdcFifoGet(dev); /* Read N and discard */ + continue; + } +#if 0 + if (0 == (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY)) { /* DEBUG */ + DPRINTK("comedi: READ OOPS on %d of %d\n", ii + 1, + count); + break; + } +#endif + d = RtdAdcFifoGet(dev); /* get 2s comp value */ + + d = d >> 3; /* low 3 bits are marker lines */ + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) + sample = d + 2048; /* convert to comedi unsigned data */ + else + sample = d; + + if (!comedi_buf_put(s->async, sample)) + return -1; + + if (devpriv->aiCount > 0) /* < 0, means read forever */ + devpriv->aiCount--; + } + return 0; +} + +/* + unknown amout of data is waiting in fifo. +*/ +static int ai_read_dregs(struct comedi_device *dev, struct comedi_subdevice *s) +{ + while (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY) { /* 1 -> not empty */ + short sample; + s16 d = RtdAdcFifoGet(dev); /* get 2s comp value */ + + if (0 == devpriv->aiCount) { /* done */ + continue; /* read rest */ + } + + d = d >> 3; /* low 3 bits are marker lines */ + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) + sample = d + 2048; /* convert to comedi unsigned data */ + else + sample = d; + + if (!comedi_buf_put(s->async, sample)) + return -1; + + if (devpriv->aiCount > 0) /* < 0, means read forever */ + devpriv->aiCount--; + } + return 0; +} + +#ifdef USE_DMA +/* + Terminate a DMA transfer and wait for everything to quiet down +*/ +void abort_dma(struct comedi_device *dev, unsigned int channel) +{ /* DMA channel 0, 1 */ + unsigned long dma_cs_addr; /* the control/status register */ + uint8_t status; + unsigned int ii; + /* unsigned long flags; */ + + dma_cs_addr = (unsigned long)devpriv->lcfg + + ((channel == 0) ? LCFG_DMACSR0 : LCFG_DMACSR1); + + /* spinlock for plx dma control/status reg */ + /* spin_lock_irqsave( &dev->spinlock, flags ); */ + + /* abort dma transfer if necessary */ + status = readb(dma_cs_addr); + if ((status & PLX_DMA_EN_BIT) == 0) { /* not enabled (Error?) */ + DPRINTK("rtd520: AbortDma on non-active channel %d (0x%x)\n", + channel, status); + goto abortDmaExit; + } + + /* wait to make sure done bit is zero (needed?) */ + for (ii = 0; (status & PLX_DMA_DONE_BIT) && ii < RTD_DMA_TIMEOUT; ii++) { + WAIT_QUIETLY; + status = readb(dma_cs_addr); + } + if (status & PLX_DMA_DONE_BIT) { + printk("rtd520: Timeout waiting for dma %i done clear\n", + channel); + goto abortDmaExit; + } + + /* disable channel (required) */ + writeb(0, dma_cs_addr); + udelay(1); /* needed?? */ + /* set abort bit for channel */ + writeb(PLX_DMA_ABORT_BIT, dma_cs_addr); + + /* wait for dma done bit to be set */ + status = readb(dma_cs_addr); + for (ii = 0; + (status & PLX_DMA_DONE_BIT) == 0 && ii < RTD_DMA_TIMEOUT; ii++) { + status = readb(dma_cs_addr); + WAIT_QUIETLY; + } + if ((status & PLX_DMA_DONE_BIT) == 0) { + printk("rtd520: Timeout waiting for dma %i done set\n", + channel); + } + +abortDmaExit: + /* spin_unlock_irqrestore( &dev->spinlock, flags ); */ +} + +/* + Process what is in the DMA transfer buffer and pass to comedi + Note: this is not re-entrant +*/ +static int ai_process_dma(struct comedi_device *dev, struct comedi_subdevice *s) +{ + int ii, n; + s16 *dp; + + if (devpriv->aiCount == 0) /* transfer already complete */ + return 0; + + dp = devpriv->dma0Buff[devpriv->dma0Offset]; + for (ii = 0; ii < devpriv->fifoLen / 2;) { /* convert samples */ + short sample; + + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) { + sample = (*dp >> 3) + 2048; /* convert to comedi unsigned data */ + else + sample = *dp >> 3; /* low 3 bits are marker lines */ + + *dp++ = sample; /* put processed value back */ + + if (++s->async->cur_chan >= s->async->cmd.chanlist_len) + s->async->cur_chan = 0; + + ++ii; /* number ready to transfer */ + if (devpriv->aiCount > 0) { /* < 0, means read forever */ + if (--devpriv->aiCount == 0) { /* done */ + /*DPRINTK ("rtd520: Final %d samples\n", ii); */ + break; + } + } + } + + /* now pass the whole array to the comedi buffer */ + dp = devpriv->dma0Buff[devpriv->dma0Offset]; + n = comedi_buf_write_alloc(s->async, ii * sizeof(s16)); + if (n < (ii * sizeof(s16))) { /* any residual is an error */ + DPRINTK("rtd520:ai_process_dma buffer overflow %d samples!\n", + ii - (n / sizeof(s16))); + s->async->events |= COMEDI_CB_ERROR; + return -1; + } + comedi_buf_memcpy_to(s->async, 0, dp, n); + comedi_buf_write_free(s->async, n); + + /* always at least 1 scan -- 1/2 FIFO is larger than our max scan list */ + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + + if (++devpriv->dma0Offset >= DMA_CHAIN_COUNT) { /* next buffer */ + devpriv->dma0Offset = 0; + } + return 0; +} +#endif /* USE_DMA */ + +/* + Handle all rtd520 interrupts. + Runs atomically and is never re-entered. + This is a "slow handler"; other interrupts may be active. + The data conversion may someday happen in a "bottom half". +*/ +static irqreturn_t rtd_interrupt(int irq, /* interrupt number (ignored) */ + void *d) +{ /* our data *//* cpu context (ignored) */ + struct comedi_device *dev = d; /* must be called "dev" for devpriv */ + u16 status; + u16 fifoStatus; + struct comedi_subdevice *s = dev->subdevices + 0; /* analog in subdevice */ + + if (!dev->attached) + return IRQ_NONE; + + devpriv->intCount++; /* DEBUG statistics */ + + fifoStatus = RtdFifoStatus(dev); + /* check for FIFO full, this automatically halts the ADC! */ + if (!(fifoStatus & FS_ADC_NOT_FULL)) { /* 0 -> full */ + DPRINTK("rtd520: FIFO full! fifo_status=0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); /* should be all 0s */ + goto abortTransfer; + } +#ifdef USE_DMA + if (devpriv->flags & DMA0_ACTIVE) { /* Check DMA */ + u32 istatus = RtdPlxInterruptRead(dev); + + if (istatus & ICS_DMA0_A) { + if (ai_process_dma(dev, s) < 0) { + DPRINTK + ("rtd520: comedi read buffer overflow (DMA) with %ld to go!\n", + devpriv->aiCount); + RtdDma0Control(dev, + (devpriv->dma0Control & + ~PLX_DMA_START_BIT) + | PLX_CLEAR_DMA_INTR_BIT); + goto abortTransfer; + } + + /*DPRINTK ("rtd520: DMA transfer: %ld to go, istatus %x\n", + devpriv->aiCount, istatus); */ + RtdDma0Control(dev, + (devpriv-> + dma0Control & ~PLX_DMA_START_BIT) + | PLX_CLEAR_DMA_INTR_BIT); + if (0 == devpriv->aiCount) { /* counted down */ + DPRINTK("rtd520: Samples Done (DMA).\n"); + goto transferDone; + } + comedi_event(dev, s); + } else { + /*DPRINTK ("rtd520: No DMA ready: istatus %x\n", istatus); */ + } + } + /* Fall through and check for other interrupt sources */ +#endif /* USE_DMA */ + + status = RtdInterruptStatus(dev); + /* if interrupt was not caused by our board, or handled above */ + if (0 == status) + return IRQ_HANDLED; + + if (status & IRQM_ADC_ABOUT_CNT) { /* sample count -> read FIFO */ + /* since the priority interrupt controller may have queued a sample + counter interrupt, even though we have already finished, + we must handle the possibility that there is no data here */ + if (!(fifoStatus & FS_ADC_HEMPTY)) { /* 0 -> 1/2 full */ + /*DPRINTK("rtd520: Sample int, reading 1/2FIFO. fifo_status 0x%x\n", + (fifoStatus ^ 0x6666) & 0x7777); */ + if (ai_read_n(dev, s, devpriv->fifoLen / 2) < 0) { + DPRINTK + ("rtd520: comedi read buffer overflow (1/2FIFO) with %ld to go!\n", + devpriv->aiCount); + goto abortTransfer; + } + if (0 == devpriv->aiCount) { /* counted down */ + DPRINTK("rtd520: Samples Done (1/2). fifo_status was 0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); /* should be all 0s */ + goto transferDone; + } + comedi_event(dev, s); + } else if (devpriv->transCount > 0) { /* read often */ + /*DPRINTK("rtd520: Sample int, reading %d fifo_status 0x%x\n", + devpriv->transCount, (fifoStatus ^ 0x6666) & 0x7777); */ + if (fifoStatus & FS_ADC_NOT_EMPTY) { /* 1 -> not empty */ + if (ai_read_n(dev, s, devpriv->transCount) < 0) { + DPRINTK + ("rtd520: comedi read buffer overflow (N) with %ld to go!\n", + devpriv->aiCount); + goto abortTransfer; + } + if (0 == devpriv->aiCount) { /* counted down */ + DPRINTK + ("rtd520: Samples Done (N). fifo_status was 0x%x\n", + (fifoStatus ^ 0x6666) & 0x7777); + goto transferDone; + } + comedi_event(dev, s); + } + } else { /* wait for 1/2 FIFO (old) */ + DPRINTK + ("rtd520: Sample int. Wait for 1/2. fifo_status 0x%x\n", + (fifoStatus ^ 0x6666) & 0x7777); + } + } else { + DPRINTK("rtd520: unknown interrupt source!\n"); + } + + if (0xffff & RtdInterruptOverrunStatus(dev)) { /* interrupt overrun */ + DPRINTK + ("rtd520: Interrupt overrun with %ld to go! over_status=0x%x\n", + devpriv->aiCount, 0xffff & RtdInterruptOverrunStatus(dev)); + goto abortTransfer; + } + + /* clear the interrupt */ + RtdInterruptClearMask(dev, status); + RtdInterruptClear(dev); + return IRQ_HANDLED; + +abortTransfer: + RtdAdcClearFifo(dev); /* clears full flag */ + s->async->events |= COMEDI_CB_ERROR; + devpriv->aiCount = 0; /* stop and don't transfer any more */ + /* fall into transferDone */ + +transferDone: + RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ + RtdPacerStop(dev); /* Stop PACER */ + RtdAdcConversionSource(dev, 0); /* software trigger only */ + RtdInterruptMask(dev, 0); /* mask out SAMPLE */ +#ifdef USE_DMA + if (devpriv->flags & DMA0_ACTIVE) { + RtdPlxInterruptWrite(dev, /* disable any more interrupts */ + RtdPlxInterruptRead(dev) & ~ICS_DMA0_E); + abort_dma(dev, 0); + devpriv->flags &= ~DMA0_ACTIVE; + /* if Using DMA, then we should have read everything by now */ + if (devpriv->aiCount > 0) { + DPRINTK("rtd520: Lost DMA data! %ld remain\n", + devpriv->aiCount); + } + } +#endif /* USE_DMA */ + + if (devpriv->aiCount > 0) { /* there shouldn't be anything left */ + fifoStatus = RtdFifoStatus(dev); + DPRINTK("rtd520: Finishing up. %ld remain, fifoStat=%x\n", devpriv->aiCount, (fifoStatus ^ 0x6666) & 0x7777); /* should read all 0s */ + ai_read_dregs(dev, s); /* read anything left in FIFO */ + } + + s->async->events |= COMEDI_CB_EOA; /* signal end to comedi */ + comedi_event(dev, s); + + /* clear the interrupt */ + status = RtdInterruptStatus(dev); + RtdInterruptClearMask(dev, status); + RtdInterruptClear(dev); + + fifoStatus = RtdFifoStatus(dev); /* DEBUG */ + DPRINTK + ("rtd520: Acquisition complete. %ld ints, intStat=%x, overStat=%x\n", + devpriv->intCount, status, + 0xffff & RtdInterruptOverrunStatus(dev)); + + return IRQ_HANDLED; +} + +#if 0 +/* + return the number of samples available +*/ +static int rtd_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) +{ + /* TODO: This needs to mask interrupts, read_dregs, and then re-enable */ + /* Not sure what to do if DMA is active */ + return s->async->buf_write_count - s->async->buf_read_count; +} +#endif + +/* + cmdtest tests a particular command to see if it is valid. + Using the cmdtest ioctl, a user can create a valid cmd + and then have it executed by the cmd ioctl (asyncronously). + + cmdtest returns 1,2,3,4 or 0, depending on which tests + the command passes. +*/ + +static int rtd_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique + and mutually compatible */ + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) { + err++; + } + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Note: these are time periods, not actual rates */ + if (1 == cmd->chanlist_len) { /* no scanning */ + if (cmd->scan_begin_arg < RTD_MAX_SPEED_1) { + cmd->scan_begin_arg = RTD_MAX_SPEED_1; + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_UP); + err++; + } + if (cmd->scan_begin_arg > RTD_MIN_SPEED_1) { + cmd->scan_begin_arg = RTD_MIN_SPEED_1; + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_DOWN); + err++; + } + } else { + if (cmd->scan_begin_arg < RTD_MAX_SPEED) { + cmd->scan_begin_arg = RTD_MAX_SPEED; + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_UP); + err++; + } + if (cmd->scan_begin_arg > RTD_MIN_SPEED) { + cmd->scan_begin_arg = RTD_MIN_SPEED; + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_DOWN); + err++; + } + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + if (cmd->scan_begin_arg > 9) { + cmd->scan_begin_arg = 9; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if (1 == cmd->chanlist_len) { /* no scanning */ + if (cmd->convert_arg < RTD_MAX_SPEED_1) { + cmd->convert_arg = RTD_MAX_SPEED_1; + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_UP); + err++; + } + if (cmd->convert_arg > RTD_MIN_SPEED_1) { + cmd->convert_arg = RTD_MIN_SPEED_1; + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_DOWN); + err++; + } + } else { + if (cmd->convert_arg < RTD_MAX_SPEED) { + cmd->convert_arg = RTD_MAX_SPEED; + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_UP); + err++; + } + if (cmd->convert_arg > RTD_MIN_SPEED) { + cmd->convert_arg = RTD_MIN_SPEED; + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_DOWN); + err++; + } + } + } else { + /* external trigger */ + /* see above */ + if (cmd->convert_arg > 9) { + cmd->convert_arg = 9; + err++; + } + } + +#if 0 + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } +#endif + if (cmd->stop_src == TRIG_COUNT) { + /* TODO check for rounding error due to counter wrap */ + + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + + /* step 4: fix up any arguments */ + + if (cmd->chanlist_len > RTD_MAX_CHANLIST) { + cmd->chanlist_len = RTD_MAX_CHANLIST; + err++; + } + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + rtd_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + rtd_ns_to_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + + if (cmd->scan_begin_src == TRIG_TIMER + && (cmd->scan_begin_arg + < (cmd->convert_arg * cmd->scan_end_arg))) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if (err) + return 4; + + return 0; +} + +/* + Execute a analog in command with many possible triggering options. + The data get stored in the async structure of the subdevice. + This is usually done by an interrupt handler. + Userland gets to the data using read calls. +*/ +static int rtd_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + int timer; + + /* stop anything currently running */ + RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ + RtdPacerStop(dev); /* make sure PACER is stopped */ + RtdAdcConversionSource(dev, 0); /* software trigger only */ + RtdInterruptMask(dev, 0); +#ifdef USE_DMA + if (devpriv->flags & DMA0_ACTIVE) { /* cancel anything running */ + RtdPlxInterruptWrite(dev, /* disable any more interrupts */ + RtdPlxInterruptRead(dev) & ~ICS_DMA0_E); + abort_dma(dev, 0); + devpriv->flags &= ~DMA0_ACTIVE; + if (RtdPlxInterruptRead(dev) & ICS_DMA0_A) { /*clear pending int */ + RtdDma0Control(dev, PLX_CLEAR_DMA_INTR_BIT); + } + } + RtdDma0Reset(dev); /* reset onboard state */ +#endif /* USE_DMA */ + RtdAdcClearFifo(dev); /* clear any old data */ + RtdInterruptOverrunClear(dev); + devpriv->intCount = 0; + + if (!dev->irq) { /* we need interrupts for this */ + DPRINTK("rtd520: ERROR! No interrupt available!\n"); + return -ENXIO; + } + + /* start configuration */ + /* load channel list and reset CGT */ + rtd_load_channelgain_list(dev, cmd->chanlist_len, cmd->chanlist); + + /* setup the common case and override if needed */ + if (cmd->chanlist_len > 1) { + /*DPRINTK ("rtd520: Multi channel setup\n"); */ + RtdPacerStartSource(dev, 0); /* software triggers pacer */ + RtdBurstStartSource(dev, 1); /* PACER triggers burst */ + RtdAdcConversionSource(dev, 2); /* BURST triggers ADC */ + } else { /* single channel */ + /*DPRINTK ("rtd520: single channel setup\n"); */ + RtdPacerStartSource(dev, 0); /* software triggers pacer */ + RtdAdcConversionSource(dev, 1); /* PACER triggers ADC */ + } + RtdAboutCounter(dev, devpriv->fifoLen / 2 - 1); /* 1/2 FIFO */ + + if (TRIG_TIMER == cmd->scan_begin_src) { + /* scan_begin_arg is in nanoseconds */ + /* find out how many samples to wait before transferring */ + if (cmd->flags & TRIG_WAKE_EOS) { + /* this may generate un-sustainable interrupt rates */ + /* the application is responsible for doing the right thing */ + devpriv->transCount = cmd->chanlist_len; + devpriv->flags |= SEND_EOS; + } else { + /* arrange to transfer data periodically */ + devpriv->transCount + = + (TRANS_TARGET_PERIOD * cmd->chanlist_len) / + cmd->scan_begin_arg; + if (devpriv->transCount < cmd->chanlist_len) { + /* tranfer after each scan (and avoid 0) */ + devpriv->transCount = cmd->chanlist_len; + } else { /* make a multiple of scan length */ + devpriv->transCount = + (devpriv->transCount + + cmd->chanlist_len - 1) + / cmd->chanlist_len; + devpriv->transCount *= cmd->chanlist_len; + } + devpriv->flags |= SEND_EOS; + } + if (devpriv->transCount >= (devpriv->fifoLen / 2)) { + /* out of counter range, use 1/2 fifo instead */ + devpriv->transCount = 0; + devpriv->flags &= ~SEND_EOS; + } else { + /* interrupt for each tranfer */ + RtdAboutCounter(dev, devpriv->transCount - 1); + } + + DPRINTK + ("rtd520: scanLen=%d tranferCount=%d fifoLen=%d\n scanTime(ns)=%d flags=0x%x\n", + cmd->chanlist_len, devpriv->transCount, devpriv->fifoLen, + cmd->scan_begin_arg, devpriv->flags); + } else { /* unknown timing, just use 1/2 FIFO */ + devpriv->transCount = 0; + devpriv->flags &= ~SEND_EOS; + } + RtdPacerClockSource(dev, 1); /* use INTERNAL 8Mhz clock source */ + RtdAboutStopEnable(dev, 1); /* just interrupt, dont stop */ + + /* BUG??? these look like enumerated values, but they are bit fields */ + + /* First, setup when to stop */ + switch (cmd->stop_src) { + case TRIG_COUNT: /* stop after N scans */ + devpriv->aiCount = cmd->stop_arg * cmd->chanlist_len; + if ((devpriv->transCount > 0) + && (devpriv->transCount > devpriv->aiCount)) { + devpriv->transCount = devpriv->aiCount; + } + break; + + case TRIG_NONE: /* stop when cancel is called */ + devpriv->aiCount = -1; /* read forever */ + break; + + default: + DPRINTK("rtd520: Warning! ignoring stop_src mode %d\n", + cmd->stop_src); + } + + /* Scan timing */ + switch (cmd->scan_begin_src) { + case TRIG_TIMER: /* periodic scanning */ + timer = rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_NEAREST); + /* set PACER clock */ + /*DPRINTK ("rtd520: loading %d into pacer\n", timer); */ + RtdPacerCounter(dev, timer); + + break; + + case TRIG_EXT: + RtdPacerStartSource(dev, 1); /* EXTERNALy trigger pacer */ + break; + + default: + DPRINTK("rtd520: Warning! ignoring scan_begin_src mode %d\n", + cmd->scan_begin_src); + } + + /* Sample timing within a scan */ + switch (cmd->convert_src) { + case TRIG_TIMER: /* periodic */ + if (cmd->chanlist_len > 1) { /* only needed for multi-channel */ + timer = rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_NEAREST); + /* setup BURST clock */ + /*DPRINTK ("rtd520: loading %d into burst\n", timer); */ + RtdBurstCounter(dev, timer); + } + + break; + + case TRIG_EXT: /* external */ + RtdBurstStartSource(dev, 2); /* EXTERNALy trigger burst */ + break; + + default: + DPRINTK("rtd520: Warning! ignoring convert_src mode %d\n", + cmd->convert_src); + } + /* end configuration */ + + /* This doesn't seem to work. There is no way to clear an interrupt + that the priority controller has queued! */ + RtdInterruptClearMask(dev, ~0); /* clear any existing flags */ + RtdInterruptClear(dev); + + /* TODO: allow multiple interrupt sources */ + if (devpriv->transCount > 0) { /* transfer every N samples */ + RtdInterruptMask(dev, IRQM_ADC_ABOUT_CNT); + DPRINTK("rtd520: Transferring every %d\n", devpriv->transCount); + } else { /* 1/2 FIFO transfers */ +#ifdef USE_DMA + devpriv->flags |= DMA0_ACTIVE; + + /* point to first transfer in ring */ + devpriv->dma0Offset = 0; + RtdDma0Mode(dev, DMA_MODE_BITS); + RtdDma0Next(dev, /* point to first block */ + devpriv->dma0Chain[DMA_CHAIN_COUNT - 1].next); + RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL); /* set DMA trigger source */ + + RtdPlxInterruptWrite(dev, /* enable interrupt */ + RtdPlxInterruptRead(dev) | ICS_DMA0_E); + /* Must be 2 steps. See PLX app note about "Starting a DMA transfer" */ + RtdDma0Control(dev, PLX_DMA_EN_BIT); /* enable DMA (clear INTR?) */ + RtdDma0Control(dev, PLX_DMA_EN_BIT | PLX_DMA_START_BIT); /*start DMA */ + DPRINTK("rtd520: Using DMA0 transfers. plxInt %x RtdInt %x\n", + RtdPlxInterruptRead(dev), devpriv->intMask); +#else /* USE_DMA */ + RtdInterruptMask(dev, IRQM_ADC_ABOUT_CNT); + DPRINTK("rtd520: Transferring every 1/2 FIFO\n"); +#endif /* USE_DMA */ + } + + /* BUG: start_src is ASSUMED to be TRIG_NOW */ + /* BUG? it seems like things are running before the "start" */ + RtdPacerStart(dev); /* Start PACER */ + return 0; +} + +/* + Stop a running data acquisition. +*/ +static int rtd_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + u16 status; + + RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ + RtdPacerStop(dev); /* Stop PACER */ + RtdAdcConversionSource(dev, 0); /* software trigger only */ + RtdInterruptMask(dev, 0); + devpriv->aiCount = 0; /* stop and don't transfer any more */ +#ifdef USE_DMA + if (devpriv->flags & DMA0_ACTIVE) { + RtdPlxInterruptWrite(dev, /* disable any more interrupts */ + RtdPlxInterruptRead(dev) & ~ICS_DMA0_E); + abort_dma(dev, 0); + devpriv->flags &= ~DMA0_ACTIVE; + } +#endif /* USE_DMA */ + status = RtdInterruptStatus(dev); + DPRINTK + ("rtd520: Acquisition canceled. %ld ints, intStat=%x, overStat=%x\n", + devpriv->intCount, status, + 0xffff & RtdInterruptOverrunStatus(dev)); + return 0; +} + +/* + Given a desired period and the clock period (both in ns), + return the proper counter value (divider-1). + Sets the original period to be the true value. + Note: you have to check if the value is larger than the counter range! +*/ +static int rtd_ns_to_timer_base(unsigned int *nanosec, /* desired period (in ns) */ + int round_mode, int base) +{ /* clock period (in ns) */ + int divider; + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + if (divider < 2) + divider = 2; /* min is divide by 2 */ + + /* Note: we don't check for max, because different timers + have different ranges */ + + *nanosec = base * divider; + return divider - 1; /* countdown is divisor+1 */ +} + +/* + Given a desired period (in ns), + return the proper counter value (divider-1) for the internal clock. + Sets the original period to be the true value. +*/ +static int rtd_ns_to_timer(unsigned int *ns, int round_mode) +{ + return rtd_ns_to_timer_base(ns, round_mode, RTD_CLOCK_BASE); +} + +/* + Output one (or more) analog values to a single port as fast as possible. +*/ +static int rtd_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + int range = CR_RANGE(insn->chanspec); + + /* Configure the output range (table index matches the range values) */ + RtdDacRange(dev, chan, range); + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; ++i) { + int val = data[i] << 3; + int stat = 0; /* initialize to avoid bogus warning */ + int ii; + + /* VERIFY: comedi range and offset conversions */ + + if ((range > 1) /* bipolar */ + && (data[i] < 2048)) { + /* offset and sign extend */ + val = (((int)data[i]) - 2048) << 3; + } else { /* unipolor */ + val = data[i] << 3; + } + + DPRINTK + ("comedi: rtd520 DAC chan=%d range=%d writing %d as 0x%x\n", + chan, range, data[i], val); + + /* a typical programming sequence */ + RtdDacFifoPut(dev, chan, val); /* put the value in */ + RtdDacUpdate(dev, chan); /* trigger the conversion */ + + devpriv->aoValue[chan] = data[i]; /* save for read back */ + + for (ii = 0; ii < RTD_DAC_TIMEOUT; ++ii) { + stat = RtdFifoStatus(dev); + /* 1 -> not empty */ + if (stat & ((0 == chan) ? FS_DAC1_NOT_EMPTY : + FS_DAC2_NOT_EMPTY)) + break; + WAIT_QUIETLY; + } + if (ii >= RTD_DAC_TIMEOUT) { + DPRINTK + ("rtd520: Error: DAC never finished! FifoStatus=0x%x\n", + stat ^ 0x6666); + return -ETIMEDOUT; + } + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int rtd_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->aoValue[chan]; + + + return i; +} + +/* + Write a masked set of bits and the read back the port. + We track what the bits should be (i.e. we don't read the port first). + + DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write + */ +static int rtd_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + + /* Write out the new digital output lines */ + RtdDio0Write(dev, s->state); + } + /* on return, data[1] contains the value of the digital + * input lines. */ + data[1] = RtdDio0Read(dev); + + /*DPRINTK("rtd520:port_0 wrote: 0x%x read: 0x%x\n", s->state, data[1]); */ + + return 2; +} + +/* + Configure one bit on a IO port as Input or Output (hence the name :-). +*/ +static int rtd_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << chan; /* 1 means Out */ + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + } + + DPRINTK("rtd520: port_0_direction=0x%x (1 means out)\n", s->io_bits); + /* TODO support digital match interrupts and strobes */ + RtdDioStatusWrite(dev, 0x01); /* make Dio0Ctrl point to direction */ + RtdDio0CtrlWrite(dev, s->io_bits); /* set direction 1 means Out */ + RtdDioStatusWrite(dev, 0); /* make Dio0Ctrl clear interrupts */ + + /* port1 can only be all input or all output */ + + /* there are also 2 user input lines and 2 user output lines */ + + return 1; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __devinit rtd520Driver_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, rtd520Driver.driver_name); +} + +static void __devexit rtd520Driver_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver rtd520Driver_pci_driver = { + .id_table = rtd520_pci_table, + .probe = &rtd520Driver_pci_probe, + .remove = __devexit_p(&rtd520Driver_pci_remove) +}; + +static int __init rtd520Driver_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&rtd520Driver); + if (retval < 0) + return retval; + + rtd520Driver_pci_driver.name = (char *)rtd520Driver.driver_name; + return pci_register_driver(&rtd520Driver_pci_driver); +} + +static void __exit rtd520Driver_cleanup_module(void) +{ + pci_unregister_driver(&rtd520Driver_pci_driver); + comedi_driver_unregister(&rtd520Driver); +} + +module_init(rtd520Driver_init_module); +module_exit(rtd520Driver_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/rtd520.h b/drivers/staging/comedi/drivers/rtd520.h new file mode 100644 index 00000000..a3ec2599 --- /dev/null +++ b/drivers/staging/comedi/drivers/rtd520.h @@ -0,0 +1,412 @@ +/* + comedi/drivers/rtd520.h + Comedi driver defines for Real Time Devices (RTD) PCI4520/DM7520 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2001 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + Created by Dan Christian, NASA Ames Research Center. + See board notes in rtd520.c +*/ + +/* + LAS0 Runtime Area + Local Address Space 0 Offset Read Function Write Function +*/ +#define LAS0_SPARE_00 0x0000 /* - - */ +#define LAS0_SPARE_04 0x0004 /* - - */ +#define LAS0_USER_IO 0x0008 /* Read User Inputs Write User Outputs */ +#define LAS0_SPARE_0C 0x000C /* - - */ +#define LAS0_ADC 0x0010 /* Read FIFO Status Software A/D Start */ +#define LAS0_DAC1 0x0014 /* - Software D/A1 Update */ +#define LAS0_DAC2 0x0018 /* - Software D/A2 Update */ +#define LAS0_SPARE_1C 0x001C /* - - */ +#define LAS0_SPARE_20 0x0020 /* - - */ +#define LAS0_DAC 0x0024 /* - Software Simultaneous D/A1 and D/A2 Update */ +#define LAS0_PACER 0x0028 /* Software Pacer Start Software Pacer Stop */ +#define LAS0_TIMER 0x002C /* Read Timer Counters Status HDIN Software Trigger */ +#define LAS0_IT 0x0030 /* Read Interrupt Status Write Interrupt Enable Mask Register */ +#define LAS0_CLEAR 0x0034 /* Clear ITs set by Clear Mask Set Interrupt Clear Mask */ +#define LAS0_OVERRUN 0x0038 /* Read pending interrupts Clear Overrun Register */ +#define LAS0_SPARE_3C 0x003C /* - - */ + +/* + LAS0 Runtime Area Timer/Counter,Dig.IO + Name Local Address Function +*/ +#define LAS0_PCLK 0x0040 /* Pacer Clock value (24bit) Pacer Clock load (24bit) */ +#define LAS0_BCLK 0x0044 /* Burst Clock value (10bit) Burst Clock load (10bit) */ +#define LAS0_ADC_SCNT 0x0048 /* A/D Sample counter value (10bit) A/D Sample counter load (10bit) */ +#define LAS0_DAC1_UCNT 0x004C /* D/A1 Update counter value (10 bit) D/A1 Update counter load (10bit) */ +#define LAS0_DAC2_UCNT 0x0050 /* D/A2 Update counter value (10 bit) D/A2 Update counter load (10bit) */ +#define LAS0_DCNT 0x0054 /* Delay counter value (16 bit) Delay counter load (16bit) */ +#define LAS0_ACNT 0x0058 /* About counter value (16 bit) About counter load (16bit) */ +#define LAS0_DAC_CLK 0x005C /* DAC clock value (16bit) DAC clock load (16bit) */ +#define LAS0_UTC0 0x0060 /* 8254 TC Counter 0 User TC 0 value Load count in TC Counter 0 */ +#define LAS0_UTC1 0x0064 /* 8254 TC Counter 1 User TC 1 value Load count in TC Counter 1 */ +#define LAS0_UTC2 0x0068 /* 8254 TC Counter 2 User TC 2 value Load count in TC Counter 2 */ +#define LAS0_UTC_CTRL 0x006C /* 8254 TC Control Word Program counter mode for TC */ +#define LAS0_DIO0 0x0070 /* Digital I/O Port 0 Read Port Digital I/O Port 0 Write Port */ +#define LAS0_DIO1 0x0074 /* Digital I/O Port 1 Read Port Digital I/O Port 1 Write Port */ +#define LAS0_DIO0_CTRL 0x0078 /* Clear digital IRQ status flag/read Clear digital chip/program Port 0 */ +#define LAS0_DIO_STATUS 0x007C /* Read Digital I/O Status word Program digital control register & */ + +/* + LAS0 Setup Area + Name Local Address Function +*/ +#define LAS0_BOARD_RESET 0x0100 /* Board reset */ +#define LAS0_DMA0_SRC 0x0104 /* DMA 0 Sources select */ +#define LAS0_DMA1_SRC 0x0108 /* DMA 1 Sources select */ +#define LAS0_ADC_CONVERSION 0x010C /* A/D Conversion Signal select */ +#define LAS0_BURST_START 0x0110 /* Burst Clock Start Trigger select */ +#define LAS0_PACER_START 0x0114 /* Pacer Clock Start Trigger select */ +#define LAS0_PACER_STOP 0x0118 /* Pacer Clock Stop Trigger select */ +#define LAS0_ACNT_STOP_ENABLE 0x011C /* About Counter Stop Enable */ +#define LAS0_PACER_REPEAT 0x0120 /* Pacer Start Trigger Mode select */ +#define LAS0_DIN_START 0x0124 /* High Speed Digital Input Sampling Signal select */ +#define LAS0_DIN_FIFO_CLEAR 0x0128 /* Digital Input FIFO Clear */ +#define LAS0_ADC_FIFO_CLEAR 0x012C /* A/D FIFO Clear */ +#define LAS0_CGT_WRITE 0x0130 /* Channel Gain Table Write */ +#define LAS0_CGL_WRITE 0x0134 /* Channel Gain Latch Write */ +#define LAS0_CG_DATA 0x0138 /* Digital Table Write */ +#define LAS0_CGT_ENABLE 0x013C /* Channel Gain Table Enable */ +#define LAS0_CG_ENABLE 0x0140 /* Digital Table Enable */ +#define LAS0_CGT_PAUSE 0x0144 /* Table Pause Enable */ +#define LAS0_CGT_RESET 0x0148 /* Reset Channel Gain Table */ +#define LAS0_CGT_CLEAR 0x014C /* Clear Channel Gain Table */ +#define LAS0_DAC1_CTRL 0x0150 /* D/A1 output type/range */ +#define LAS0_DAC1_SRC 0x0154 /* D/A1 update source */ +#define LAS0_DAC1_CYCLE 0x0158 /* D/A1 cycle mode */ +#define LAS0_DAC1_RESET 0x015C /* D/A1 FIFO reset */ +#define LAS0_DAC1_FIFO_CLEAR 0x0160 /* D/A1 FIFO clear */ +#define LAS0_DAC2_CTRL 0x0164 /* D/A2 output type/range */ +#define LAS0_DAC2_SRC 0x0168 /* D/A2 update source */ +#define LAS0_DAC2_CYCLE 0x016C /* D/A2 cycle mode */ +#define LAS0_DAC2_RESET 0x0170 /* D/A2 FIFO reset */ +#define LAS0_DAC2_FIFO_CLEAR 0x0174 /* D/A2 FIFO clear */ +#define LAS0_ADC_SCNT_SRC 0x0178 /* A/D Sample Counter Source select */ +#define LAS0_PACER_SELECT 0x0180 /* Pacer Clock select */ +#define LAS0_SBUS0_SRC 0x0184 /* SyncBus 0 Source select */ +#define LAS0_SBUS0_ENABLE 0x0188 /* SyncBus 0 enable */ +#define LAS0_SBUS1_SRC 0x018C /* SyncBus 1 Source select */ +#define LAS0_SBUS1_ENABLE 0x0190 /* SyncBus 1 enable */ +#define LAS0_SBUS2_SRC 0x0198 /* SyncBus 2 Source select */ +#define LAS0_SBUS2_ENABLE 0x019C /* SyncBus 2 enable */ +#define LAS0_ETRG_POLARITY 0x01A4 /* External Trigger polarity select */ +#define LAS0_EINT_POLARITY 0x01A8 /* External Interrupt polarity select */ +#define LAS0_UTC0_CLOCK 0x01AC /* UTC0 Clock select */ +#define LAS0_UTC0_GATE 0x01B0 /* UTC0 Gate select */ +#define LAS0_UTC1_CLOCK 0x01B4 /* UTC1 Clock select */ +#define LAS0_UTC1_GATE 0x01B8 /* UTC1 Gate select */ +#define LAS0_UTC2_CLOCK 0x01BC /* UTC2 Clock select */ +#define LAS0_UTC2_GATE 0x01C0 /* UTC2 Gate select */ +#define LAS0_UOUT0_SELECT 0x01C4 /* User Output 0 source select */ +#define LAS0_UOUT1_SELECT 0x01C8 /* User Output 1 source select */ +#define LAS0_DMA0_RESET 0x01CC /* DMA0 Request state machine reset */ +#define LAS0_DMA1_RESET 0x01D0 /* DMA1 Request state machine reset */ + +/* + LAS1 + Name Local Address Function +*/ +#define LAS1_ADC_FIFO 0x0000 /* Read A/D FIFO (16bit) - */ +#define LAS1_HDIO_FIFO 0x0004 /* Read High Speed Digital Input FIFO (16bit) - */ +#define LAS1_DAC1_FIFO 0x0008 /* - Write D/A1 FIFO (16bit) */ +#define LAS1_DAC2_FIFO 0x000C /* - Write D/A2 FIFO (16bit) */ + +/* + LCFG: PLX 9080 local config & runtime registers + Name Local Address Function +*/ +#define LCFG_ITCSR 0x0068 /* INTCSR, Interrupt Control/Status Register */ +#define LCFG_DMAMODE0 0x0080 /* DMA Channel 0 Mode Register */ +#define LCFG_DMAPADR0 0x0084 /* DMA Channel 0 PCI Address Register */ +#define LCFG_DMALADR0 0x0088 /* DMA Channel 0 Local Address Reg */ +#define LCFG_DMASIZ0 0x008C /* DMA Channel 0 Transfer Size (Bytes) Register */ +#define LCFG_DMADPR0 0x0090 /* DMA Channel 0 Descriptor Pointer Register */ +#define LCFG_DMAMODE1 0x0094 /* DMA Channel 1 Mode Register */ +#define LCFG_DMAPADR1 0x0098 /* DMA Channel 1 PCI Address Register */ +#define LCFG_DMALADR1 0x009C /* DMA Channel 1 Local Address Register */ +#define LCFG_DMASIZ1 0x00A0 /* DMA Channel 1 Transfer Size (Bytes) Register */ +#define LCFG_DMADPR1 0x00A4 /* DMA Channel 1 Descriptor Pointer Register */ +#define LCFG_DMACSR0 0x00A8 /* DMA Channel 0 Command/Status Register */ +#define LCFG_DMACSR1 0x00A9 /* DMA Channel 0 Command/Status Register */ +#define LCFG_DMAARB 0x00AC /* DMA Arbitration Register */ +#define LCFG_DMATHR 0x00B0 /* DMA Threshold Register */ + +/*====================================================================== + Resister bit definitions +======================================================================*/ + +/* FIFO Status Word Bits (RtdFifoStatus) */ +#define FS_DAC1_NOT_EMPTY 0x0001 /* D0 - DAC1 FIFO not empty */ +#define FS_DAC1_HEMPTY 0x0002 /* D1 - DAC1 FIFO half empty */ +#define FS_DAC1_NOT_FULL 0x0004 /* D2 - DAC1 FIFO not full */ +#define FS_DAC2_NOT_EMPTY 0x0010 /* D4 - DAC2 FIFO not empty */ +#define FS_DAC2_HEMPTY 0x0020 /* D5 - DAC2 FIFO half empty */ +#define FS_DAC2_NOT_FULL 0x0040 /* D6 - DAC2 FIFO not full */ +#define FS_ADC_NOT_EMPTY 0x0100 /* D8 - ADC FIFO not empty */ +#define FS_ADC_HEMPTY 0x0200 /* D9 - ADC FIFO half empty */ +#define FS_ADC_NOT_FULL 0x0400 /* D10 - ADC FIFO not full */ +#define FS_DIN_NOT_EMPTY 0x1000 /* D12 - DIN FIFO not empty */ +#define FS_DIN_HEMPTY 0x2000 /* D13 - DIN FIFO half empty */ +#define FS_DIN_NOT_FULL 0x4000 /* D14 - DIN FIFO not full */ + +/* Timer Status Word Bits (GetTimerStatus) */ +#define TS_PCLK_GATE 0x0001 +/* D0 - Pacer Clock Gate [0 - gated, 1 - enabled] */ +#define TS_BCLK_GATE 0x0002 +/* D1 - Burst Clock Gate [0 - disabled, 1 - running] */ +#define TS_DCNT_GATE 0x0004 +/* D2 - Pacer Clock Delayed Start Trigger [0 - delay over, 1 - delay in */ +/* progress] */ +#define TS_ACNT_GATE 0x0008 +/* D3 - Pacer Clock About Trigger [0 - completed, 1 - in progress] */ +#define TS_PCLK_RUN 0x0010 +/* D4 - Pacer Clock Shutdown Flag [0 - Pacer Clock cannot be start */ +/* triggered only by Software Pacer Start Command, 1 - Pacer Clock can */ +/* be start triggered] */ + +/* External Trigger polarity select */ +/* External Interrupt polarity select */ +#define POL_POSITIVE 0x0 /* positive edge */ +#define POL_NEGATIVE 0x1 /* negative edge */ + +/* User Output Signal select (SetUout0Source, SetUout1Source) */ +#define UOUT_ADC 0x0 /* A/D Conversion Signal */ +#define UOUT_DAC1 0x1 /* D/A1 Update */ +#define UOUT_DAC2 0x2 /* D/A2 Update */ +#define UOUT_SOFTWARE 0x3 /* Software Programmable */ + +/* Pacer clock select (SetPacerSource) */ +#define PCLK_INTERNAL 1 /* Internal Pacer Clock */ +#define PCLK_EXTERNAL 0 /* External Pacer Clock */ + +/* A/D Sample Counter Sources (SetAdcntSource, SetupSampleCounter) */ +#define ADC_SCNT_CGT_RESET 0x0 /* needs restart with StartPacer */ +#define ADC_SCNT_FIFO_WRITE 0x1 + +/* A/D Conversion Signal Select (for SetConversionSelect) */ +#define ADC_START_SOFTWARE 0x0 /* Software A/D Start */ +#define ADC_START_PCLK 0x1 /* Pacer Clock (Ext. Int. see Func.509) */ +#define ADC_START_BCLK 0x2 /* Burst Clock */ +#define ADC_START_DIGITAL_IT 0x3 /* Digital Interrupt */ +#define ADC_START_DAC1_MARKER1 0x4 /* D/A 1 Data Marker 1 */ +#define ADC_START_DAC2_MARKER1 0x5 /* D/A 2 Data Marker 1 */ +#define ADC_START_SBUS0 0x6 /* SyncBus 0 */ +#define ADC_START_SBUS1 0x7 /* SyncBus 1 */ +#define ADC_START_SBUS2 0x8 /* SyncBus 2 */ + +/* Burst Clock start trigger select (SetBurstStart) */ +#define BCLK_START_SOFTWARE 0x0 /* Software A/D Start (StartBurst) */ +#define BCLK_START_PCLK 0x1 /* Pacer Clock */ +#define BCLK_START_ETRIG 0x2 /* External Trigger */ +#define BCLK_START_DIGITAL_IT 0x3 /* Digital Interrupt */ +#define BCLK_START_SBUS0 0x4 /* SyncBus 0 */ +#define BCLK_START_SBUS1 0x5 /* SyncBus 1 */ +#define BCLK_START_SBUS2 0x6 /* SyncBus 2 */ + +/* Pacer Clock start trigger select (SetPacerStart) */ +#define PCLK_START_SOFTWARE 0x0 /* Software Pacer Start (StartPacer) */ +#define PCLK_START_ETRIG 0x1 /* External trigger */ +#define PCLK_START_DIGITAL_IT 0x2 /* Digital interrupt */ +#define PCLK_START_UTC2 0x3 /* User TC 2 out */ +#define PCLK_START_SBUS0 0x4 /* SyncBus 0 */ +#define PCLK_START_SBUS1 0x5 /* SyncBus 1 */ +#define PCLK_START_SBUS2 0x6 /* SyncBus 2 */ +#define PCLK_START_D_SOFTWARE 0x8 /* Delayed Software Pacer Start */ +#define PCLK_START_D_ETRIG 0x9 /* Delayed external trigger */ +#define PCLK_START_D_DIGITAL_IT 0xA /* Delayed digital interrupt */ +#define PCLK_START_D_UTC2 0xB /* Delayed User TC 2 out */ +#define PCLK_START_D_SBUS0 0xC /* Delayed SyncBus 0 */ +#define PCLK_START_D_SBUS1 0xD /* Delayed SyncBus 1 */ +#define PCLK_START_D_SBUS2 0xE /* Delayed SyncBus 2 */ +#define PCLK_START_ETRIG_GATED 0xF /* External Trigger Gated controlled mode */ + +/* Pacer Clock Stop Trigger select (SetPacerStop) */ +#define PCLK_STOP_SOFTWARE 0x0 /* Software Pacer Stop (StopPacer) */ +#define PCLK_STOP_ETRIG 0x1 /* External Trigger */ +#define PCLK_STOP_DIGITAL_IT 0x2 /* Digital Interrupt */ +#define PCLK_STOP_ACNT 0x3 /* About Counter */ +#define PCLK_STOP_UTC2 0x4 /* User TC2 out */ +#define PCLK_STOP_SBUS0 0x5 /* SyncBus 0 */ +#define PCLK_STOP_SBUS1 0x6 /* SyncBus 1 */ +#define PCLK_STOP_SBUS2 0x7 /* SyncBus 2 */ +#define PCLK_STOP_A_SOFTWARE 0x8 /* About Software Pacer Stop */ +#define PCLK_STOP_A_ETRIG 0x9 /* About External Trigger */ +#define PCLK_STOP_A_DIGITAL_IT 0xA /* About Digital Interrupt */ +#define PCLK_STOP_A_UTC2 0xC /* About User TC2 out */ +#define PCLK_STOP_A_SBUS0 0xD /* About SyncBus 0 */ +#define PCLK_STOP_A_SBUS1 0xE /* About SyncBus 1 */ +#define PCLK_STOP_A_SBUS2 0xF /* About SyncBus 2 */ + +/* About Counter Stop Enable */ +#define ACNT_STOP 0x0 /* stop enable */ +#define ACNT_NO_STOP 0x1 /* stop disabled */ + +/* DAC update source (SetDAC1Start & SetDAC2Start) */ +#define DAC_START_SOFTWARE 0x0 /* Software Update */ +#define DAC_START_CGT 0x1 /* CGT controlled Update */ +#define DAC_START_DAC_CLK 0x2 /* D/A Clock */ +#define DAC_START_EPCLK 0x3 /* External Pacer Clock */ +#define DAC_START_SBUS0 0x4 /* SyncBus 0 */ +#define DAC_START_SBUS1 0x5 /* SyncBus 1 */ +#define DAC_START_SBUS2 0x6 /* SyncBus 2 */ + +/* DAC Cycle Mode (SetDAC1Cycle, SetDAC2Cycle, SetupDAC) */ +#define DAC_CYCLE_SINGLE 0x0 /* not cycle */ +#define DAC_CYCLE_MULTI 0x1 /* cycle */ + +/* 8254 Operation Modes (Set8254Mode, SetupTimerCounter) */ +#define M8254_EVENT_COUNTER 0 /* Event Counter */ +#define M8254_HW_ONE_SHOT 1 /* Hardware-Retriggerable One-Shot */ +#define M8254_RATE_GENERATOR 2 /* Rate Generator */ +#define M8254_SQUARE_WAVE 3 /* Square Wave Mode */ +#define M8254_SW_STROBE 4 /* Software Triggered Strobe */ +#define M8254_HW_STROBE 5 /* Hardware Triggered Strobe (Retriggerable) */ + +/* User Timer/Counter 0 Clock Select (SetUtc0Clock) */ +#define CUTC0_8MHZ 0x0 /* 8MHz */ +#define CUTC0_EXT_TC_CLOCK1 0x1 /* Ext. TC Clock 1 */ +#define CUTC0_EXT_TC_CLOCK2 0x2 /* Ext. TC Clock 2 */ +#define CUTC0_EXT_PCLK 0x3 /* Ext. Pacer Clock */ + +/* User Timer/Counter 1 Clock Select (SetUtc1Clock) */ +#define CUTC1_8MHZ 0x0 /* 8MHz */ +#define CUTC1_EXT_TC_CLOCK1 0x1 /* Ext. TC Clock 1 */ +#define CUTC1_EXT_TC_CLOCK2 0x2 /* Ext. TC Clock 2 */ +#define CUTC1_EXT_PCLK 0x3 /* Ext. Pacer Clock */ +#define CUTC1_UTC0_OUT 0x4 /* User Timer/Counter 0 out */ +#define CUTC1_DIN_SIGNAL 0x5 /* High-Speed Digital Input Sampling signal */ + +/* User Timer/Counter 2 Clock Select (SetUtc2Clock) */ +#define CUTC2_8MHZ 0x0 /* 8MHz */ +#define CUTC2_EXT_TC_CLOCK1 0x1 /* Ext. TC Clock 1 */ +#define CUTC2_EXT_TC_CLOCK2 0x2 /* Ext. TC Clock 2 */ +#define CUTC2_EXT_PCLK 0x3 /* Ext. Pacer Clock */ +#define CUTC2_UTC1_OUT 0x4 /* User Timer/Counter 1 out */ + +/* User Timer/Counter 0 Gate Select (SetUtc0Gate) */ +#define GUTC0_NOT_GATED 0x0 /* Not gated */ +#define GUTC0_GATED 0x1 /* Gated */ +#define GUTC0_EXT_TC_GATE1 0x2 /* Ext. TC Gate 1 */ +#define GUTC0_EXT_TC_GATE2 0x3 /* Ext. TC Gate 2 */ + +/* User Timer/Counter 1 Gate Select (SetUtc1Gate) */ +#define GUTC1_NOT_GATED 0x0 /* Not gated */ +#define GUTC1_GATED 0x1 /* Gated */ +#define GUTC1_EXT_TC_GATE1 0x2 /* Ext. TC Gate 1 */ +#define GUTC1_EXT_TC_GATE2 0x3 /* Ext. TC Gate 2 */ +#define GUTC1_UTC0_OUT 0x4 /* User Timer/Counter 0 out */ + +/* User Timer/Counter 2 Gate Select (SetUtc2Gate) */ +#define GUTC2_NOT_GATED 0x0 /* Not gated */ +#define GUTC2_GATED 0x1 /* Gated */ +#define GUTC2_EXT_TC_GATE1 0x2 /* Ext. TC Gate 1 */ +#define GUTC2_EXT_TC_GATE2 0x3 /* Ext. TC Gate 2 */ +#define GUTC2_UTC1_OUT 0x4 /* User Timer/Counter 1 out */ + +/* Interrupt Source Masks (SetITMask, ClearITMask, GetITStatus) */ +#define IRQM_ADC_FIFO_WRITE 0x0001 /* ADC FIFO Write */ +#define IRQM_CGT_RESET 0x0002 /* Reset CGT */ +#define IRQM_CGT_PAUSE 0x0008 /* Pause CGT */ +#define IRQM_ADC_ABOUT_CNT 0x0010 /* About Counter out */ +#define IRQM_ADC_DELAY_CNT 0x0020 /* Delay Counter out */ +#define IRQM_ADC_SAMPLE_CNT 0x0040 /* ADC Sample Counter */ +#define IRQM_DAC1_UCNT 0x0080 /* DAC1 Update Counter */ +#define IRQM_DAC2_UCNT 0x0100 /* DAC2 Update Counter */ +#define IRQM_UTC1 0x0200 /* User TC1 out */ +#define IRQM_UTC1_INV 0x0400 /* User TC1 out, inverted */ +#define IRQM_UTC2 0x0800 /* User TC2 out */ +#define IRQM_DIGITAL_IT 0x1000 /* Digital Interrupt */ +#define IRQM_EXTERNAL_IT 0x2000 /* External Interrupt */ +#define IRQM_ETRIG_RISING 0x4000 /* External Trigger rising-edge */ +#define IRQM_ETRIG_FALLING 0x8000 /* External Trigger falling-edge */ + +/* DMA Request Sources (LAS0) */ +#define DMAS_DISABLED 0x0 /* DMA Disabled */ +#define DMAS_ADC_SCNT 0x1 /* ADC Sample Counter */ +#define DMAS_DAC1_UCNT 0x2 /* D/A1 Update Counter */ +#define DMAS_DAC2_UCNT 0x3 /* D/A2 Update Counter */ +#define DMAS_UTC1 0x4 /* User TC1 out */ +#define DMAS_ADFIFO_HALF_FULL 0x8 /* A/D FIFO half full */ +#define DMAS_DAC1_FIFO_HALF_EMPTY 0x9 /* D/A1 FIFO half empty */ +#define DMAS_DAC2_FIFO_HALF_EMPTY 0xA /* D/A2 FIFO half empty */ + +/* DMA Local Addresses (0x40000000+LAS1 offset) */ +#define DMALADDR_ADC 0x40000000 /* A/D FIFO */ +#define DMALADDR_HDIN 0x40000004 /* High Speed Digital Input FIFO */ +#define DMALADDR_DAC1 0x40000008 /* D/A1 FIFO */ +#define DMALADDR_DAC2 0x4000000C /* D/A2 FIFO */ + +/* Port 0 compare modes (SetDIO0CompareMode) */ +#define DIO_MODE_EVENT 0 /* Event Mode */ +#define DIO_MODE_MATCH 1 /* Match Mode */ + +/* Digital Table Enable (Port 1 disable) */ +#define DTBL_DISABLE 0 /* Enable Digital Table */ +#define DTBL_ENABLE 1 /* Disable Digital Table */ + +/* Sampling Signal for High Speed Digital Input (SetHdinStart) */ +#define HDIN_SOFTWARE 0x0 /* Software Trigger */ +#define HDIN_ADC 0x1 /* A/D Conversion Signal */ +#define HDIN_UTC0 0x2 /* User TC out 0 */ +#define HDIN_UTC1 0x3 /* User TC out 1 */ +#define HDIN_UTC2 0x4 /* User TC out 2 */ +#define HDIN_EPCLK 0x5 /* External Pacer Clock */ +#define HDIN_ETRG 0x6 /* External Trigger */ + +/* Channel Gain Table / Channel Gain Latch */ +#define CSC_LATCH 0 /* Channel Gain Latch mode */ +#define CSC_CGT 1 /* Channel Gain Table mode */ + +/* Channel Gain Table Pause Enable */ +#define CGT_PAUSE_DISABLE 0 /* Channel Gain Table Pause Disable */ +#define CGT_PAUSE_ENABLE 1 /* Channel Gain Table Pause Enable */ + +/* DAC output type/range (p63) */ +#define AOUT_UNIP5 0 /* 0..+5 Volt */ +#define AOUT_UNIP10 1 /* 0..+10 Volt */ +#define AOUT_BIP5 2 /* -5..+5 Volt */ +#define AOUT_BIP10 3 /* -10..+10 Volt */ + +/* Ghannel Gain Table field definitions (p61) */ +/* Gain */ +#define GAIN1 0 +#define GAIN2 1 +#define GAIN4 2 +#define GAIN8 3 +#define GAIN16 4 +#define GAIN32 5 +#define GAIN64 6 +#define GAIN128 7 + +/* Input range/polarity */ +#define AIN_BIP5 0 /* -5..+5 Volt */ +#define AIN_BIP10 1 /* -10..+10 Volt */ +#define AIN_UNIP10 2 /* 0..+10 Volt */ + +/* non referenced single ended select bit */ +#define NRSE_AGND 0 /* AGND referenced SE input */ +#define NRSE_AINS 1 /* AIN SENSE referenced SE input */ + +/* single ended vs differential */ +#define GND_SE 0 /* Single-Ended */ +#define GND_DIFF 1 /* Differential */ diff --git a/drivers/staging/comedi/drivers/rti800.c b/drivers/staging/comedi/drivers/rti800.c new file mode 100644 index 00000000..72042b81 --- /dev/null +++ b/drivers/staging/comedi/drivers/rti800.c @@ -0,0 +1,492 @@ +/* + comedi/drivers/rti800.c + Hardware driver for Analog Devices RTI-800/815 board + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: rti800 +Description: Analog Devices RTI-800/815 +Author: ds +Status: unknown +Updated: Fri, 05 Sep 2008 14:50:44 +0100 +Devices: [Analog Devices] RTI-800 (rti800), RTI-815 (rti815) + +Configuration options: + [0] - I/O port base address + [1] - IRQ + [2] - A/D reference + 0 = differential + 1 = pseudodifferential (common) + 2 = single-ended + [3] - A/D range + 0 = [-10,10] + 1 = [-5,5] + 2 = [0,10] + [4] - A/D encoding + 0 = two's complement + 1 = straight binary + [5] - DAC 0 range + 0 = [-10,10] + 1 = [0,10] + [6] - DAC 0 encoding + 0 = two's complement + 1 = straight binary + [7] - DAC 1 range (same as DAC 0) + [8] - DAC 1 encoding (same as DAC 0) +*/ + +#include +#include "../comedidev.h" + +#include + +#define RTI800_SIZE 16 + +#define RTI800_CSR 0 +#define RTI800_MUXGAIN 1 +#define RTI800_CONVERT 2 +#define RTI800_ADCLO 3 +#define RTI800_ADCHI 4 +#define RTI800_DAC0LO 5 +#define RTI800_DAC0HI 6 +#define RTI800_DAC1LO 7 +#define RTI800_DAC1HI 8 +#define RTI800_CLRFLAGS 9 +#define RTI800_DI 10 +#define RTI800_DO 11 +#define RTI800_9513A_DATA 12 +#define RTI800_9513A_CNTRL 13 +#define RTI800_9513A_STATUS 13 + +/* + * flags for CSR register + */ + +#define RTI800_BUSY 0x80 +#define RTI800_DONE 0x40 +#define RTI800_OVERRUN 0x20 +#define RTI800_TCR 0x10 +#define RTI800_DMA_ENAB 0x08 +#define RTI800_INTR_TC 0x04 +#define RTI800_INTR_EC 0x02 +#define RTI800_INTR_OVRN 0x01 + +#define Am9513_8BITBUS + +#define Am9513_output_control(a) outb(a, dev->iobase+RTI800_9513A_CNTRL) +#define Am9513_output_data(a) outb(a, dev->iobase+RTI800_9513A_DATA) +#define Am9513_input_data() inb(dev->iobase+RTI800_9513A_DATA) +#define Am9513_input_status() inb(dev->iobase+RTI800_9513A_STATUS) + +#include "am9513.h" + +static const struct comedi_lrange range_rti800_ai_10_bipolar = { 4, { + BIP_RANGE + (10), + BIP_RANGE + (1), + BIP_RANGE + (0.1), + BIP_RANGE + (0.02) + } +}; + +static const struct comedi_lrange range_rti800_ai_5_bipolar = { 4, { + BIP_RANGE + (5), + BIP_RANGE + (0.5), + BIP_RANGE + (0.05), + BIP_RANGE + (0.01) + } +}; + +static const struct comedi_lrange range_rti800_ai_unipolar = { 4, { + UNI_RANGE + (10), + UNI_RANGE(1), + UNI_RANGE + (0.1), + UNI_RANGE + (0.02) + } +}; + +struct rti800_board { + + const char *name; + int has_ao; +}; + +static const struct rti800_board boardtypes[] = { + {"rti800", 0}, + {"rti815", 1}, +}; + +#define this_board ((const struct rti800_board *)dev->board_ptr) + +static int rti800_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int rti800_detach(struct comedi_device *dev); +static struct comedi_driver driver_rti800 = { + .driver_name = "rti800", + .module = THIS_MODULE, + .attach = rti800_attach, + .detach = rti800_detach, + .num_names = ARRAY_SIZE(boardtypes), + .board_name = &boardtypes[0].name, + .offset = sizeof(struct rti800_board), +}; + +static int __init driver_rti800_init_module(void) +{ + return comedi_driver_register(&driver_rti800); +} + +static void __exit driver_rti800_cleanup_module(void) +{ + comedi_driver_unregister(&driver_rti800); +} + +module_init(driver_rti800_init_module); +module_exit(driver_rti800_cleanup_module); + +static irqreturn_t rti800_interrupt(int irq, void *dev); + +struct rti800_private { + enum { + adc_diff, adc_pseudodiff, adc_singleended + } adc_mux; + enum { + adc_bipolar10, adc_bipolar5, adc_unipolar10 + } adc_range; + enum { + adc_2comp, adc_straight + } adc_coding; + enum { + dac_bipolar10, dac_unipolar10 + } dac0_range, dac1_range; + enum { + dac_2comp, dac_straight + } dac0_coding, dac1_coding; + const struct comedi_lrange *ao_range_type_list[2]; + unsigned int ao_readback[2]; + int muxgain_bits; +}; + +#define devpriv ((struct rti800_private *)dev->private) + +#define RTI800_TIMEOUT 100 + +static irqreturn_t rti800_interrupt(int irq, void *dev) +{ + return IRQ_HANDLED; +} + +/* settling delay times in usec for different gains */ +static const int gaindelay[] = { 10, 20, 40, 80 }; + +static int rti800_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, t; + int status; + int chan = CR_CHAN(insn->chanspec); + unsigned gain = CR_RANGE(insn->chanspec); + unsigned muxgain_bits; + + inb(dev->iobase + RTI800_ADCHI); + outb(0, dev->iobase + RTI800_CLRFLAGS); + + muxgain_bits = chan | (gain << 5); + if (muxgain_bits != devpriv->muxgain_bits) { + devpriv->muxgain_bits = muxgain_bits; + outb(devpriv->muxgain_bits, dev->iobase + RTI800_MUXGAIN); + /* without a delay here, the RTI_OVERRUN bit + * gets set, and you will have an error. */ + if (insn->n > 0) { + BUG_ON(gain >= ARRAY_SIZE(gaindelay)); + udelay(gaindelay[gain]); + } + } + + for (i = 0; i < insn->n; i++) { + outb(0, dev->iobase + RTI800_CONVERT); + for (t = RTI800_TIMEOUT; t; t--) { + status = inb(dev->iobase + RTI800_CSR); + if (status & RTI800_OVERRUN) { + printk(KERN_WARNING "rti800: a/d overrun\n"); + outb(0, dev->iobase + RTI800_CLRFLAGS); + return -EIO; + } + if (status & RTI800_DONE) + break; + udelay(1); + } + if (t == 0) { + printk(KERN_WARNING "rti800: timeout\n"); + return -ETIME; + } + data[i] = inb(dev->iobase + RTI800_ADCLO); + data[i] |= (0xf & inb(dev->iobase + RTI800_ADCHI)) << 8; + + if (devpriv->adc_coding == adc_2comp) + data[i] ^= 0x800; + } + + return i; +} + +static int rti800_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +static int rti800_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + int d; + int i; + + for (i = 0; i < insn->n; i++) { + devpriv->ao_readback[chan] = d = data[i]; + if (devpriv->dac0_coding == dac_2comp) + d ^= 0x800; + + outb(d & 0xff, + dev->iobase + (chan ? RTI800_DAC1LO : RTI800_DAC0LO)); + outb(d >> 8, + dev->iobase + (chan ? RTI800_DAC1HI : RTI800_DAC0HI)); + } + return i; +} + +static int rti800_di_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + data[1] = inb(dev->iobase + RTI800_DI); + return 2; +} + +static int rti800_do_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + /* Outputs are inverted... */ + outb(s->state ^ 0xff, dev->iobase + RTI800_DO); + } + + data[1] = s->state; + + return 2; +} + +/* + options[0] - I/O port + options[1] - irq + options[2] - a/d mux + 0=differential, 1=pseudodiff, 2=single + options[3] - a/d range + 0=bipolar10, 1=bipolar5, 2=unipolar10 + options[4] - a/d coding + 0=2's comp, 1=straight binary + options[5] - dac0 range + 0=bipolar10, 1=unipolar10 + options[6] - dac0 coding + 0=2's comp, 1=straight binary + options[7] - dac1 range + options[8] - dac1 coding + */ + +static int rti800_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + unsigned int irq; + unsigned long iobase; + int ret; + struct comedi_subdevice *s; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: rti800: 0x%04lx\n", dev->minor, iobase); + if (!request_region(iobase, RTI800_SIZE, "rti800")) { + printk(KERN_WARNING "I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + +#ifdef DEBUG + printk(KERN_DEBUG "fingerprint=%x,%x,%x,%x,%x ", + inb(dev->iobase + 0), + inb(dev->iobase + 1), + inb(dev->iobase + 2), + inb(dev->iobase + 3), inb(dev->iobase + 4)); +#endif + + outb(0, dev->iobase + RTI800_CSR); + inb(dev->iobase + RTI800_ADCHI); + outb(0, dev->iobase + RTI800_CLRFLAGS); + + irq = it->options[1]; + if (irq) { + printk(KERN_INFO "( irq = %u )\n", irq); + ret = request_irq(irq, rti800_interrupt, 0, "rti800", dev); + if (ret < 0) { + printk(KERN_WARNING " Failed to allocate IRQ\n"); + return ret; + } + dev->irq = irq; + } else { + printk(KERN_INFO "( no irq )\n"); + } + + dev->board_name = this_board->name; + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + + ret = alloc_private(dev, sizeof(struct rti800_private)); + if (ret < 0) + return ret; + + devpriv->adc_mux = it->options[2]; + devpriv->adc_range = it->options[3]; + devpriv->adc_coding = it->options[4]; + devpriv->dac0_range = it->options[5]; + devpriv->dac0_coding = it->options[6]; + devpriv->dac1_range = it->options[7]; + devpriv->dac1_coding = it->options[8]; + devpriv->muxgain_bits = -1; + + s = dev->subdevices + 0; + /* ai subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = (devpriv->adc_mux ? 16 : 8); + s->insn_read = rti800_ai_insn_read; + s->maxdata = 0xfff; + switch (devpriv->adc_range) { + case adc_bipolar10: + s->range_table = &range_rti800_ai_10_bipolar; + break; + case adc_bipolar5: + s->range_table = &range_rti800_ai_5_bipolar; + break; + case adc_unipolar10: + s->range_table = &range_rti800_ai_unipolar; + break; + } + + s++; + if (this_board->has_ao) { + /* ao subdevice (only on rti815) */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->insn_read = rti800_ao_insn_read; + s->insn_write = rti800_ao_insn_write; + s->maxdata = 0xfff; + s->range_table_list = devpriv->ao_range_type_list; + switch (devpriv->dac0_range) { + case dac_bipolar10: + devpriv->ao_range_type_list[0] = &range_bipolar10; + break; + case dac_unipolar10: + devpriv->ao_range_type_list[0] = &range_unipolar10; + break; + } + switch (devpriv->dac1_range) { + case dac_bipolar10: + devpriv->ao_range_type_list[1] = &range_bipolar10; + break; + case dac_unipolar10: + devpriv->ao_range_type_list[1] = &range_unipolar10; + break; + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + s++; + /* di */ + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 8; + s->insn_bits = rti800_di_insn_bits; + s->maxdata = 1; + s->range_table = &range_digital; + + s++; + /* do */ + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 8; + s->insn_bits = rti800_do_insn_bits; + s->maxdata = 1; + s->range_table = &range_digital; + +/* don't yet know how to deal with counter/timers */ +#if 0 + s++; + /* do */ + s->type = COMEDI_SUBD_TIMER; +#endif + + return 0; +} + +static int rti800_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: rti800: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, RTI800_SIZE); + + if (dev->irq) + free_irq(dev->irq, dev); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/rti802.c b/drivers/staging/comedi/drivers/rti802.c new file mode 100644 index 00000000..f59cb115 --- /dev/null +++ b/drivers/staging/comedi/drivers/rti802.c @@ -0,0 +1,167 @@ +/* + comedi/drivers/rti802.c + Hardware driver for Analog Devices RTI-802 board + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999 Anders Blomdell + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: rti802 +Description: Analog Devices RTI-802 +Author: Anders Blomdell +Devices: [Analog Devices] RTI-802 (rti802) +Status: works + +Configuration Options: + [0] - i/o base + [1] - unused + [2] - dac#0 0=two's comp, 1=straight + [3] - dac#0 0=bipolar, 1=unipolar + [4] - dac#1 ... + ... + [17] - dac#7 ... +*/ + +#include "../comedidev.h" + +#include + +#define RTI802_SIZE 4 + +#define RTI802_SELECT 0 +#define RTI802_DATALOW 1 +#define RTI802_DATAHIGH 2 + +static int rti802_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int rti802_detach(struct comedi_device *dev); +static struct comedi_driver driver_rti802 = { + .driver_name = "rti802", + .module = THIS_MODULE, + .attach = rti802_attach, + .detach = rti802_detach, +}; + +static int __init driver_rti802_init_module(void) +{ + return comedi_driver_register(&driver_rti802); +} + +static void __exit driver_rti802_cleanup_module(void) +{ + comedi_driver_unregister(&driver_rti802); +} + +module_init(driver_rti802_init_module); +module_exit(driver_rti802_cleanup_module); + +struct rti802_private { + enum { + dac_2comp, dac_straight + } dac_coding[8]; + const struct comedi_lrange *range_type_list[8]; + unsigned int ao_readback[8]; +}; + +#define devpriv ((struct rti802_private *)dev->private) + +static int rti802_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + + return i; +} + +static int rti802_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, d; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + d = devpriv->ao_readback[chan] = data[i]; + if (devpriv->dac_coding[chan] == dac_2comp) + d ^= 0x800; + outb(chan, dev->iobase + RTI802_SELECT); + outb(d & 0xff, dev->iobase + RTI802_DATALOW); + outb(d >> 8, dev->iobase + RTI802_DATAHIGH); + } + return i; +} + +static int rti802_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int i; + unsigned long iobase; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: rti802: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, RTI802_SIZE, "rti802")) { + printk(KERN_WARNING "I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + dev->board_name = "rti802"; + + if (alloc_subdevices(dev, 1) < 0 + || alloc_private(dev, sizeof(struct rti802_private))) { + return -ENOMEM; + } + + s = dev->subdevices; + /* ao subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->maxdata = 0xfff; + s->n_chan = 8; + s->insn_read = rti802_ao_insn_read; + s->insn_write = rti802_ao_insn_write; + s->range_table_list = devpriv->range_type_list; + + for (i = 0; i < 8; i++) { + devpriv->dac_coding[i] = (it->options[3 + 2 * i]) + ? (dac_straight) + : (dac_2comp); + devpriv->range_type_list[i] = (it->options[2 + 2 * i]) + ? &range_unipolar10 : &range_bipolar10; + } + + return 0; +} + +static int rti802_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: rti802: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, RTI802_SIZE); + + return 0; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/s526.c b/drivers/staging/comedi/drivers/s526.c new file mode 100644 index 00000000..2b34daed --- /dev/null +++ b/drivers/staging/comedi/drivers/s526.c @@ -0,0 +1,1045 @@ +/* + comedi/drivers/s526.c + Sensoray s526 Comedi driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: s526 +Description: Sensoray 526 driver +Devices: [Sensoray] 526 (s526) +Author: Richie + Everett Wang +Updated: Thu, 14 Sep. 2006 +Status: experimental + +Encoder works +Analog input works +Analog output works +PWM output works +Commands are not supported yet. + +Configuration Options: + +comedi_config /dev/comedi0 s526 0x2C0,0x3 + +*/ + +#include "../comedidev.h" +#include +#include + +#define S526_SIZE 64 + +#define S526_START_AI_CONV 0 +#define S526_AI_READ 0 + +/* Ports */ +#define S526_IOSIZE 0x40 +#define S526_NUM_PORTS 27 + +/* registers */ +#define REG_TCR 0x00 +#define REG_WDC 0x02 +#define REG_DAC 0x04 +#define REG_ADC 0x06 +#define REG_ADD 0x08 +#define REG_DIO 0x0A +#define REG_IER 0x0C +#define REG_ISR 0x0E +#define REG_MSC 0x10 +#define REG_C0L 0x12 +#define REG_C0H 0x14 +#define REG_C0M 0x16 +#define REG_C0C 0x18 +#define REG_C1L 0x1A +#define REG_C1H 0x1C +#define REG_C1M 0x1E +#define REG_C1C 0x20 +#define REG_C2L 0x22 +#define REG_C2H 0x24 +#define REG_C2M 0x26 +#define REG_C2C 0x28 +#define REG_C3L 0x2A +#define REG_C3H 0x2C +#define REG_C3M 0x2E +#define REG_C3C 0x30 +#define REG_EED 0x32 +#define REG_EEC 0x34 + +static const int s526_ports[] = { + REG_TCR, + REG_WDC, + REG_DAC, + REG_ADC, + REG_ADD, + REG_DIO, + REG_IER, + REG_ISR, + REG_MSC, + REG_C0L, + REG_C0H, + REG_C0M, + REG_C0C, + REG_C1L, + REG_C1H, + REG_C1M, + REG_C1C, + REG_C2L, + REG_C2H, + REG_C2M, + REG_C2C, + REG_C3L, + REG_C3H, + REG_C3M, + REG_C3C, + REG_EED, + REG_EEC +}; + +struct counter_mode_register_t { +#if defined(__LITTLE_ENDIAN_BITFIELD) + unsigned short coutSource:1; + unsigned short coutPolarity:1; + unsigned short autoLoadResetRcap:3; + unsigned short hwCtEnableSource:2; + unsigned short ctEnableCtrl:2; + unsigned short clockSource:2; + unsigned short countDir:1; + unsigned short countDirCtrl:1; + unsigned short outputRegLatchCtrl:1; + unsigned short preloadRegSel:1; + unsigned short reserved:1; + #elif defined(__BIG_ENDIAN_BITFIELD) + unsigned short reserved:1; + unsigned short preloadRegSel:1; + unsigned short outputRegLatchCtrl:1; + unsigned short countDirCtrl:1; + unsigned short countDir:1; + unsigned short clockSource:2; + unsigned short ctEnableCtrl:2; + unsigned short hwCtEnableSource:2; + unsigned short autoLoadResetRcap:3; + unsigned short coutPolarity:1; + unsigned short coutSource:1; +#else +#error Unknown bit field order +#endif +}; + +union cmReg { + struct counter_mode_register_t reg; + unsigned short value; +}; + +#define MAX_GPCT_CONFIG_DATA 6 + +/* Different Application Classes for GPCT Subdevices */ +/* The list is not exhaustive and needs discussion! */ +enum S526_GPCT_APP_CLASS { + CountingAndTimeMeasurement, + SinglePulseGeneration, + PulseTrainGeneration, + PositionMeasurement, + Miscellaneous +}; + +/* Config struct for different GPCT subdevice Application Classes and + their options +*/ +struct s526GPCTConfig { + enum S526_GPCT_APP_CLASS app; + int data[MAX_GPCT_CONFIG_DATA]; +}; + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +struct s526_board { + const char *name; + int gpct_chans; + int gpct_bits; + int ad_chans; + int ad_bits; + int da_chans; + int da_bits; + int have_dio; +}; + +static const struct s526_board s526_boards[] = { + { + .name = "s526", + .gpct_chans = 4, + .gpct_bits = 24, + .ad_chans = 8, + .ad_bits = 16, + .da_chans = 4, + .da_bits = 16, + .have_dio = 1, + } +}; + +#define ADDR_REG(reg) (dev->iobase + (reg)) +#define ADDR_CHAN_REG(reg, chan) (dev->iobase + (reg) + (chan) * 8) + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct s526_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device + struct. +*/ +struct s526_private { + + int data; + + /* would be useful for a PCI device */ + struct pci_dev *pci_dev; + + /* Used for AO readback */ + unsigned int ao_readback[2]; + + struct s526GPCTConfig s526_gpct_config[4]; + unsigned short s526_ai_config; +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct s526_private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int s526_detach(struct comedi_device *dev); +static struct comedi_driver driver_s526 = { + .driver_name = "s526", + .module = THIS_MODULE, + .attach = s526_attach, + .detach = s526_detach, +/* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in s526_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + .board_name = &s526_boards[0].name, + .offset = sizeof(struct s526_board), + .num_names = ARRAY_SIZE(s526_boards), +}; + +static int s526_gpct_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int s526_gpct_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s526_gpct_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data); +static int s526_ai_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s526_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s526_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s526_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s526_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + int iobase; + int i, n; +/* short value; */ +/* int subdev_channel = 0; */ + union cmReg cmReg; + + printk(KERN_INFO "comedi%d: s526: ", dev->minor); + + iobase = it->options[0]; + if (!iobase || !request_region(iobase, S526_IOSIZE, thisboard->name)) { + comedi_error(dev, "I/O port conflict"); + return -EIO; + } + dev->iobase = iobase; + + printk("iobase=0x%lx\n", dev->iobase); + + /*** make it a little quieter, exw, 8/29/06 + for (i = 0; i < S526_NUM_PORTS; i++) { + printk("0x%02x: 0x%04x\n", ADDR_REG(s526_ports[i]), + inw(ADDR_REG(s526_ports[i]))); + } + ***/ + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_ptr = &s526_boards[0]; + + dev->board_name = thisboard->name; + +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct s526_private)) < 0) + return -ENOMEM; + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + dev->n_subdevices = 4; + if (alloc_subdevices(dev, dev->n_subdevices) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; + /* KG: What does SDF_LSAMPL (see multiq3.c) mean? */ + s->n_chan = thisboard->gpct_chans; + s->maxdata = 0x00ffffff; /* 24 bit counter */ + s->insn_read = s526_gpct_rinsn; + s->insn_config = s526_gpct_insn_config; + s->insn_write = s526_gpct_winsn; + + /* Command are not implemented yet, however they are necessary to + allocate the necessary memory for the comedi_async struct (used + to trigger the GPCT in case of pulsegenerator function */ + /* s->do_cmd = s526_gpct_cmd; */ + /* s->do_cmdtest = s526_gpct_cmdtest; */ + /* s->cancel = s526_gpct_cancel; */ + + s = dev->subdevices + 1; + /* dev->read_subdev=s; */ + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + /* we support differential */ + s->subdev_flags = SDF_READABLE | SDF_DIFF; + /* channels 0 to 7 are the regular differential inputs */ + /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */ + s->n_chan = 10; + s->maxdata = 0xffff; + s->range_table = &range_bipolar10; + s->len_chanlist = 16; /* This is the maximum chanlist length that + the board can handle */ + s->insn_read = s526_ai_rinsn; + s->insn_config = s526_ai_insn_config; + + s = dev->subdevices + 2; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 0xffff; + s->range_table = &range_bipolar10; + s->insn_write = s526_ao_winsn; + s->insn_read = s526_ao_rinsn; + + s = dev->subdevices + 3; + /* digital i/o subdevice */ + if (thisboard->have_dio) { + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = s526_dio_insn_bits; + s->insn_config = s526_dio_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + printk(KERN_INFO "attached\n"); + + return 1; + +#if 0 + /* Example of Counter Application */ + /* One-shot (software trigger) */ + cmReg.reg.coutSource = 0; /* out RCAP */ + cmReg.reg.coutPolarity = 1; /* Polarity inverted */ + cmReg.reg.autoLoadResetRcap = 1;/* Auto load 0:disabled, 1:enabled */ + cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */ + cmReg.reg.ctEnableCtrl = 2; /* Hardware */ + cmReg.reg.clockSource = 2; /* Internal */ + cmReg.reg.countDir = 1; /* Down */ + cmReg.reg.countDirCtrl = 1; /* Software */ + cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */ + cmReg.reg.preloadRegSel = 0; /* PR0 */ + cmReg.reg.reserved = 0; + + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + outw(0x0001, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + outw(0x3C68, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Reset the counter */ + outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + /* Load the counter from PR0 */ + outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + /* Reset RCAP (fires one-shot) */ + outw(0x0008, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + +#else + + /* Set Counter Mode Register */ + cmReg.reg.coutSource = 0; /* out RCAP */ + cmReg.reg.coutPolarity = 0; /* Polarity inverted */ + cmReg.reg.autoLoadResetRcap = 0; /* Auto load disabled */ + cmReg.reg.hwCtEnableSource = 2; /* NOT RCAP */ + cmReg.reg.ctEnableCtrl = 1; /* 1: Software, >1 : Hardware */ + cmReg.reg.clockSource = 3; /* x4 */ + cmReg.reg.countDir = 0; /* up */ + cmReg.reg.countDirCtrl = 0; /* quadrature */ + cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */ + cmReg.reg.preloadRegSel = 0; /* PR0 */ + cmReg.reg.reserved = 0; + + n = 0; + printk(KERN_INFO "Mode reg=0x%04x, 0x%04lx\n", + cmReg.value, ADDR_CHAN_REG(REG_C0M, n)); + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, n)); + udelay(1000); + printk(KERN_INFO "Read back mode reg=0x%04x\n", + inw(ADDR_CHAN_REG(REG_C0M, n))); + + /* Load the pre-load register high word */ +/* value = (short) (0x55); */ +/* outw(value, ADDR_CHAN_REG(REG_C0H, n)); */ + + /* Load the pre-load register low word */ +/* value = (short)(0xaa55); */ +/* outw(value, ADDR_CHAN_REG(REG_C0L, n)); */ + + /* Write the Counter Control Register */ +/* outw(value, ADDR_CHAN_REG(REG_C0C, 0)); */ + + /* Reset the counter if it is software preload */ + if (cmReg.reg.autoLoadResetRcap == 0) { + /* Reset the counter */ + outw(0x8000, ADDR_CHAN_REG(REG_C0C, n)); + /* Load the counter from PR0 */ + outw(0x4000, ADDR_CHAN_REG(REG_C0C, n)); + } + + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, n)); + udelay(1000); + printk(KERN_INFO "Read back mode reg=0x%04x\n", + inw(ADDR_CHAN_REG(REG_C0M, n))); + +#endif + printk(KERN_INFO "Current registres:\n"); + + for (i = 0; i < S526_NUM_PORTS; i++) { + printk(KERN_INFO "0x%02lx: 0x%04x\n", + ADDR_REG(s526_ports[i]), inw(ADDR_REG(s526_ports[i]))); + } + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int s526_detach(struct comedi_device *dev) +{ + printk(KERN_INFO "comedi%d: s526: remove\n", dev->minor); + + if (dev->iobase > 0) + release_region(dev->iobase, S526_IOSIZE); + + return 0; +} + +static int s526_gpct_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int i; /* counts the Data */ + int counter_channel = CR_CHAN(insn->chanspec); + unsigned short datalow; + unsigned short datahigh; + + /* Check if (n > 0) */ + if (insn->n <= 0) { + printk(KERN_ERR "s526: INSN_READ: n should be > 0\n"); + return -EINVAL; + } + /* Read the low word first */ + for (i = 0; i < insn->n; i++) { + datalow = inw(ADDR_CHAN_REG(REG_C0L, counter_channel)); + datahigh = inw(ADDR_CHAN_REG(REG_C0H, counter_channel)); + data[i] = (int)(datahigh & 0x00FF); + data[i] = (data[i] << 16) | (datalow & 0xFFFF); + /* printk("s526 GPCT[%d]: %x(0x%04x, 0x%04x)\n", + counter_channel, data[i], datahigh, datalow); */ + } + return i; +} + +static int s526_gpct_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int subdev_channel = CR_CHAN(insn->chanspec); /* Unpack chanspec */ + int i; + short value; + union cmReg cmReg; + + /* printk("s526: GPCT_INSN_CONFIG: Configuring Channel %d\n", + subdev_channel); */ + + for (i = 0; i < MAX_GPCT_CONFIG_DATA; i++) { + devpriv->s526_gpct_config[subdev_channel].data[i] = + insn->data[i]; +/* printk("data[%d]=%x\n", i, insn->data[i]); */ + } + + /* Check what type of Counter the user requested, data[0] contains */ + /* the Application type */ + switch (insn->data[0]) { + case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: + /* + data[0]: Application Type + data[1]: Counter Mode Register Value + data[2]: Pre-load Register Value + data[3]: Conter Control Register + */ + printk(KERN_INFO "s526: GPCT_INSN_CONFIG: Configuring Encoder\n"); + devpriv->s526_gpct_config[subdev_channel].app = + PositionMeasurement; + +#if 0 + /* Example of Counter Application */ + /* One-shot (software trigger) */ + cmReg.reg.coutSource = 0; /* out RCAP */ + cmReg.reg.coutPolarity = 1; /* Polarity inverted */ + cmReg.reg.autoLoadResetRcap = 0;/* Auto load disabled */ + cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */ + cmReg.reg.ctEnableCtrl = 2; /* Hardware */ + cmReg.reg.clockSource = 2; /* Internal */ + cmReg.reg.countDir = 1; /* Down */ + cmReg.reg.countDirCtrl = 1; /* Software */ + cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */ + cmReg.reg.preloadRegSel = 0; /* PR0 */ + cmReg.reg.reserved = 0; + + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + outw(0x0001, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + outw(0x3C68, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Reset the counter */ + outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + /* Load the counter from PR0 */ + outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + + /* Reset RCAP (fires one-shot) */ + outw(0x0008, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + +#endif + +#if 1 + /* Set Counter Mode Register */ + cmReg.value = insn->data[1] & 0xFFFF; + +/* printk("s526: Counter Mode register=%x\n", cmReg.value); */ + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Reset the counter if it is software preload */ + if (cmReg.reg.autoLoadResetRcap == 0) { + /* Reset the counter */ + outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + /* Load the counter from PR0 + * outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + */ + } +#else + /* 0 quadrature, 1 software control */ + cmReg.reg.countDirCtrl = 0; + + /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */ + if (insn->data[1] == GPCT_X2) + cmReg.reg.clockSource = 1; + else if (insn->data[1] == GPCT_X4) + cmReg.reg.clockSource = 2; + else + cmReg.reg.clockSource = 0; + + /* When to take into account the indexpulse: */ + /*if (insn->data[2] == GPCT_IndexPhaseLowLow) { + } else if (insn->data[2] == GPCT_IndexPhaseLowHigh) { + } else if (insn->data[2] == GPCT_IndexPhaseHighLow) { + } else if (insn->data[2] == GPCT_IndexPhaseHighHigh) { + }*/ + /* Take into account the index pulse? */ + if (insn->data[3] == GPCT_RESET_COUNTER_ON_INDEX) + /* Auto load with INDEX^ */ + cmReg.reg.autoLoadResetRcap = 4; + + /* Set Counter Mode Register */ + cmReg.value = (short)(insn->data[1] & 0xFFFF); + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register high word */ + value = (short)((insn->data[2] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register low word */ + value = (short)(insn->data[2] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Write the Counter Control Register */ + if (insn->data[3] != 0) { + value = (short)(insn->data[3] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + } + /* Reset the counter if it is software preload */ + if (cmReg.reg.autoLoadResetRcap == 0) { + /* Reset the counter */ + outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + /* Load the counter from PR0 */ + outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + } +#endif + break; + + case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: + /* + data[0]: Application Type + data[1]: Counter Mode Register Value + data[2]: Pre-load Register 0 Value + data[3]: Pre-load Register 1 Value + data[4]: Conter Control Register + */ + printk(KERN_INFO "s526: GPCT_INSN_CONFIG: Configuring SPG\n"); + devpriv->s526_gpct_config[subdev_channel].app = + SinglePulseGeneration; + + /* Set Counter Mode Register */ + cmReg.value = (short)(insn->data[1] & 0xFFFF); + cmReg.reg.preloadRegSel = 0; /* PR0 */ + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register 0 high word */ + value = (short)((insn->data[2] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register 0 low word */ + value = (short)(insn->data[2] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Set Counter Mode Register */ + cmReg.value = (short)(insn->data[1] & 0xFFFF); + cmReg.reg.preloadRegSel = 1; /* PR1 */ + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register 1 high word */ + value = (short)((insn->data[3] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register 1 low word */ + value = (short)(insn->data[3] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Write the Counter Control Register */ + if (insn->data[4] != 0) { + value = (short)(insn->data[4] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + } + break; + + case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: + /* + data[0]: Application Type + data[1]: Counter Mode Register Value + data[2]: Pre-load Register 0 Value + data[3]: Pre-load Register 1 Value + data[4]: Conter Control Register + */ + printk(KERN_INFO "s526: GPCT_INSN_CONFIG: Configuring PTG\n"); + devpriv->s526_gpct_config[subdev_channel].app = + PulseTrainGeneration; + + /* Set Counter Mode Register */ + cmReg.value = (short)(insn->data[1] & 0xFFFF); + cmReg.reg.preloadRegSel = 0; /* PR0 */ + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register 0 high word */ + value = (short)((insn->data[2] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register 0 low word */ + value = (short)(insn->data[2] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Set Counter Mode Register */ + cmReg.value = (short)(insn->data[1] & 0xFFFF); + cmReg.reg.preloadRegSel = 1; /* PR1 */ + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register 1 high word */ + value = (short)((insn->data[3] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register 1 low word */ + value = (short)(insn->data[3] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Write the Counter Control Register */ + if (insn->data[4] != 0) { + value = (short)(insn->data[4] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + } + break; + + default: + printk(KERN_ERR "s526: unsupported GPCT_insn_config\n"); + return -EINVAL; + break; + } + + return insn->n; +} + +static int s526_gpct_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_insn *insn, + unsigned int *data) +{ + int subdev_channel = CR_CHAN(insn->chanspec); /* Unpack chanspec */ + short value; + union cmReg cmReg; + + printk(KERN_INFO "s526: GPCT_INSN_WRITE on channel %d\n", + subdev_channel); + cmReg.value = inw(ADDR_CHAN_REG(REG_C0M, subdev_channel)); + printk(KERN_INFO "s526: Counter Mode Register: %x\n", cmReg.value); + /* Check what Application of Counter this channel is configured for */ + switch (devpriv->s526_gpct_config[subdev_channel].app) { + case PositionMeasurement: + printk(KERN_INFO "S526: INSN_WRITE: PM\n"); + outw(0xFFFF & ((*data) >> 16), ADDR_CHAN_REG(REG_C0H, + subdev_channel)); + outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel)); + break; + + case SinglePulseGeneration: + printk(KERN_INFO "S526: INSN_WRITE: SPG\n"); + outw(0xFFFF & ((*data) >> 16), ADDR_CHAN_REG(REG_C0H, + subdev_channel)); + outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel)); + break; + + case PulseTrainGeneration: + /* data[0] contains the PULSE_WIDTH + data[1] contains the PULSE_PERIOD + @pre PULSE_PERIOD > PULSE_WIDTH > 0 + The above periods must be expressed as a multiple of the + pulse frequency on the selected source + */ + printk(KERN_INFO "S526: INSN_WRITE: PTG\n"); + if ((insn->data[1] > insn->data[0]) && (insn->data[0] > 0)) { + (devpriv->s526_gpct_config[subdev_channel]).data[0] = + insn->data[0]; + (devpriv->s526_gpct_config[subdev_channel]).data[1] = + insn->data[1]; + } else { + printk(KERN_ERR "s526: INSN_WRITE: PTG: Problem with Pulse params -> %d %d\n", + insn->data[0], insn->data[1]); + return -EINVAL; + } + + value = (short)((*data >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + value = (short)(*data & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + break; + default: /* Impossible */ + printk + ("s526: INSN_WRITE: Functionality %d not implemented yet\n", + devpriv->s526_gpct_config[subdev_channel].app); + return -EINVAL; + break; + } + /* return the number of samples written */ + return insn->n; +} + +#define ISR_ADC_DONE 0x4 +static int s526_ai_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int result = -EINVAL; + + if (insn->n < 1) + return result; + + result = insn->n; + + /* data[0] : channels was set in relevant bits. + data[1] : delay + */ + /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to + * enable channels here. The channel should be enabled in the + * INSN_READ handler. */ + + /* Enable ADC interrupt */ + outw(ISR_ADC_DONE, ADDR_REG(REG_IER)); +/* printk("s526: ADC current value: 0x%04x\n", inw(ADDR_REG(REG_ADC))); */ + devpriv->s526_ai_config = (data[0] & 0x3FF) << 5; + if (data[1] > 0) + devpriv->s526_ai_config |= 0x8000; /* set the delay */ + + devpriv->s526_ai_config |= 0x0001; /* ADC start bit. */ + + return result; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ +static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, i; + int chan = CR_CHAN(insn->chanspec); + unsigned short value; + unsigned int d; + unsigned int status; + + /* Set configured delay, enable channel for this channel only, + * select "ADC read" channel, set "ADC start" bit. */ + value = (devpriv->s526_ai_config & 0x8000) | + ((1 << 5) << chan) | (chan << 1) | 0x0001; + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + outw(value, ADDR_REG(REG_ADC)); +/* printk("s526: Wrote 0x%04x to ADC\n", value); */ +/* printk("s526: ADC reg=0x%04x\n", inw(ADDR_REG(REG_ADC))); */ + +#define TIMEOUT 100 + /* wait for conversion to end */ + for (i = 0; i < TIMEOUT; i++) { + status = inw(ADDR_REG(REG_ISR)); + if (status & ISR_ADC_DONE) { + outw(ISR_ADC_DONE, ADDR_REG(REG_ISR)); + break; + } + } + if (i == TIMEOUT) { + /* printk() should be used instead of printk() + * whenever the code can be called from real-time. */ + printk(KERN_ERR "s526: ADC(0x%04x) timeout\n", + inw(ADDR_REG(REG_ISR))); + return -ETIMEDOUT; + } + + /* read data */ + d = inw(ADDR_REG(REG_ADD)); +/* printk("AI[%d]=0x%04x\n", n, (unsigned short)(d & 0xFFFF)); */ + + /* munge data */ + data[n] = d ^ 0x8000; + } + + /* return the number of samples read/written */ + return n; +} + +static int s526_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + unsigned short val; + +/* printk("s526_ao_winsn\n"); */ + val = chan << 1; +/* outw(val, dev->iobase + REG_DAC); */ + outw(val, ADDR_REG(REG_DAC)); + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; i++) { + /* a typical programming sequence */ + /* write the data to preload register + * outw(data[i], dev->iobase + REG_ADD); + */ + /* write the data to preload register */ + outw(data[i], ADDR_REG(REG_ADD)); + devpriv->ao_readback[chan] = data[i]; +/* outw(val + 1, dev->iobase + REG_DAC); starts the D/A conversion. */ + outw(val + 1, ADDR_REG(REG_DAC)); /*starts the D/A conversion.*/ + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int s526_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +static int s526_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + /* Write out the new digital output lines */ + outw(s->state, ADDR_REG(REG_DIO)); + } + + /* on return, data[1] contains the value of the digital + * input and output lines. */ + data[1] = inw(ADDR_REG(REG_DIO)) & 0xFF; /* low 8 bits are the data */ + /* or we could just return the software copy of the output values if + * it was a purely digital output subdevice */ + /* data[1]=s->state & 0xFF; */ + + return 2; +} + +static int s526_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + int group, mask; + + printk(KERN_INFO "S526 DIO insn_config\n"); + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + + group = chan >> 2; + mask = 0xF << (group << 2); + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + /* bit 10/11 set the group 1/2's mode */ + s->state |= 1 << (group + 10); + s->io_bits |= mask; + break; + case INSN_CONFIG_DIO_INPUT: + s->state &= ~(1 << (group + 10)); /* 1 is output, 0 is input. */ + s->io_bits &= ~mask; + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + default: + return -EINVAL; + } + outw(s->state, ADDR_REG(REG_DIO)); + + return 1; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +static int __init driver_s526_init_module(void) +{ + return comedi_driver_register(&driver_s526); +} + +static void __exit driver_s526_cleanup_module(void) +{ + comedi_driver_unregister(&driver_s526); +} + +module_init(driver_s526_init_module); +module_exit(driver_s526_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/s626.c b/drivers/staging/comedi/drivers/s626.c new file mode 100644 index 00000000..23fc64b9 --- /dev/null +++ b/drivers/staging/comedi/drivers/s626.c @@ -0,0 +1,3369 @@ +/* + comedi/drivers/s626.c + Sensoray s626 Comedi driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + Based on Sensoray Model 626 Linux driver Version 0.2 + Copyright (C) 2002-2004 Sensoray Co., Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* +Driver: s626 +Description: Sensoray 626 driver +Devices: [Sensoray] 626 (s626) +Authors: Gianluca Palli , +Updated: Fri, 15 Feb 2008 10:28:42 +0000 +Status: experimental + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. + +INSN_CONFIG instructions: + analog input: + none + + analog output: + none + + digital channel: + s626 has 3 dio subdevices (2,3 and 4) each with 16 i/o channels + supported configuration options: + INSN_CONFIG_DIO_QUERY + COMEDI_INPUT + COMEDI_OUTPUT + + encoder: + Every channel must be configured before reading. + + Example code + + insn.insn=INSN_CONFIG; //configuration instruction + insn.n=1; //number of operation (must be 1) + insn.data=&initialvalue; //initial value loaded into encoder + //during configuration + insn.subdev=5; //encoder subdevice + insn.chanspec=CR_PACK(encoder_channel,0,AREF_OTHER); //encoder_channel + //to configure + + comedi_do_insn(cf,&insn); //executing configuration +*/ + +#include +#include +#include + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#include "comedi_fc.h" +#include "s626.h" + +MODULE_AUTHOR("Gianluca Palli "); +MODULE_DESCRIPTION("Sensoray 626 Comedi driver module"); +MODULE_LICENSE("GPL"); + +struct s626_board { + const char *name; + int ai_chans; + int ai_bits; + int ao_chans; + int ao_bits; + int dio_chans; + int dio_banks; + int enc_chans; +}; + +static const struct s626_board s626_boards[] = { + { + .name = "s626", + .ai_chans = S626_ADC_CHANNELS, + .ai_bits = 14, + .ao_chans = S626_DAC_CHANNELS, + .ao_bits = 13, + .dio_chans = S626_DIO_CHANNELS, + .dio_banks = S626_DIO_BANKS, + .enc_chans = S626_ENCODER_CHANNELS, + } +}; + +#define thisboard ((const struct s626_board *)dev->board_ptr) +#define PCI_VENDOR_ID_S626 0x1131 +#define PCI_DEVICE_ID_S626 0x7146 + +/* + * For devices with vendor:device id == 0x1131:0x7146 you must specify + * also subvendor:subdevice ids, because otherwise it will conflict with + * Philips SAA7146 media/dvb based cards. + */ +static DEFINE_PCI_DEVICE_TABLE(s626_pci_table) = { + {PCI_VENDOR_ID_S626, PCI_DEVICE_ID_S626, 0x6000, 0x0272, 0, 0, 0}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, s626_pci_table); + +static int s626_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int s626_detach(struct comedi_device *dev); + +static struct comedi_driver driver_s626 = { + .driver_name = "s626", + .module = THIS_MODULE, + .attach = s626_attach, + .detach = s626_detach, +}; + +struct s626_private { + struct pci_dev *pdev; + void *base_addr; + int got_regions; + short allocatedBuf; + uint8_t ai_cmd_running; /* ai_cmd is running */ + uint8_t ai_continous; /* continous acquisition */ + int ai_sample_count; /* number of samples to acquire */ + unsigned int ai_sample_timer; + /* time between samples in units of the timer */ + int ai_convert_count; /* conversion counter */ + unsigned int ai_convert_timer; + /* time between conversion in units of the timer */ + uint16_t CounterIntEnabs; + /* Counter interrupt enable mask for MISC2 register. */ + uint8_t AdcItems; /* Number of items in ADC poll list. */ + struct bufferDMA RPSBuf; /* DMA buffer used to hold ADC (RPS1) program. */ + struct bufferDMA ANABuf; + /* DMA buffer used to receive ADC data and hold DAC data. */ + uint32_t *pDacWBuf; + /* Pointer to logical adrs of DMA buffer used to hold DAC data. */ + uint16_t Dacpol; /* Image of DAC polarity register. */ + uint8_t TrimSetpoint[12]; /* Images of TrimDAC setpoints */ + uint16_t ChargeEnabled; /* Image of MISC2 Battery */ + /* Charge Enabled (0 or WRMISC2_CHARGE_ENABLE). */ + uint16_t WDInterval; /* Image of MISC2 watchdog interval control bits. */ + uint32_t I2CAdrs; + /* I2C device address for onboard EEPROM (board rev dependent). */ + /* short I2Cards; */ + unsigned int ao_readback[S626_DAC_CHANNELS]; +}; + +struct dio_private { + uint16_t RDDIn; + uint16_t WRDOut; + uint16_t RDEdgSel; + uint16_t WREdgSel; + uint16_t RDCapSel; + uint16_t WRCapSel; + uint16_t RDCapFlg; + uint16_t RDIntSel; + uint16_t WRIntSel; +}; + +static struct dio_private dio_private_A = { + .RDDIn = LP_RDDINA, + .WRDOut = LP_WRDOUTA, + .RDEdgSel = LP_RDEDGSELA, + .WREdgSel = LP_WREDGSELA, + .RDCapSel = LP_RDCAPSELA, + .WRCapSel = LP_WRCAPSELA, + .RDCapFlg = LP_RDCAPFLGA, + .RDIntSel = LP_RDINTSELA, + .WRIntSel = LP_WRINTSELA, +}; + +static struct dio_private dio_private_B = { + .RDDIn = LP_RDDINB, + .WRDOut = LP_WRDOUTB, + .RDEdgSel = LP_RDEDGSELB, + .WREdgSel = LP_WREDGSELB, + .RDCapSel = LP_RDCAPSELB, + .WRCapSel = LP_WRCAPSELB, + .RDCapFlg = LP_RDCAPFLGB, + .RDIntSel = LP_RDINTSELB, + .WRIntSel = LP_WRINTSELB, +}; + +static struct dio_private dio_private_C = { + .RDDIn = LP_RDDINC, + .WRDOut = LP_WRDOUTC, + .RDEdgSel = LP_RDEDGSELC, + .WREdgSel = LP_WREDGSELC, + .RDCapSel = LP_RDCAPSELC, + .WRCapSel = LP_WRCAPSELC, + .RDCapFlg = LP_RDCAPFLGC, + .RDIntSel = LP_RDINTSELC, + .WRIntSel = LP_WRINTSELC, +}; + +/* to group dio devices (48 bits mask and data are not allowed ???) +static struct dio_private *dio_private_word[]={ + &dio_private_A, + &dio_private_B, + &dio_private_C, +}; +*/ + +#define devpriv ((struct s626_private *)dev->private) +#define diopriv ((struct dio_private *)s->private) + +static int __devinit driver_s626_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_s626.driver_name); +} + +static void __devexit driver_s626_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_s626_pci_driver = { + .id_table = s626_pci_table, + .probe = &driver_s626_pci_probe, + .remove = __devexit_p(&driver_s626_pci_remove) +}; + +static int __init driver_s626_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_s626); + if (retval < 0) + return retval; + + driver_s626_pci_driver.name = (char *)driver_s626.driver_name; + return pci_register_driver(&driver_s626_pci_driver); +} + +static void __exit driver_s626_cleanup_module(void) +{ + pci_unregister_driver(&driver_s626_pci_driver); + comedi_driver_unregister(&driver_s626); +} + +module_init(driver_s626_init_module); +module_exit(driver_s626_cleanup_module); + +/* ioctl routines */ +static int s626_ai_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +/* static int s626_ai_rinsn(struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data); */ +static int s626_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s626_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); +static int s626_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd); +static int s626_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s); +static int s626_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s626_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s626_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s626_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s626_dio_set_irq(struct comedi_device *dev, unsigned int chan); +static int s626_dio_reset_irq(struct comedi_device *dev, unsigned int gruop, + unsigned int mask); +static int s626_dio_clear_irq(struct comedi_device *dev); +static int s626_enc_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s626_enc_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s626_enc_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int s626_ns_to_timer(int *nanosec, int round_mode); +static int s626_ai_load_polllist(uint8_t *ppl, struct comedi_cmd *cmd); +static int s626_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum); +static irqreturn_t s626_irq_handler(int irq, void *d); +static unsigned int s626_ai_reg_to_uint(int data); +/* static unsigned int s626_uint_to_reg(struct comedi_subdevice *s, int data); */ + +/* end ioctl routines */ + +/* internal routines */ +static void s626_dio_init(struct comedi_device *dev); +static void ResetADC(struct comedi_device *dev, uint8_t * ppl); +static void LoadTrimDACs(struct comedi_device *dev); +static void WriteTrimDAC(struct comedi_device *dev, uint8_t LogicalChan, + uint8_t DacData); +static uint8_t I2Cread(struct comedi_device *dev, uint8_t addr); +static uint32_t I2Chandshake(struct comedi_device *dev, uint32_t val); +static void SetDAC(struct comedi_device *dev, uint16_t chan, short dacdata); +static void SendDAC(struct comedi_device *dev, uint32_t val); +static void WriteMISC2(struct comedi_device *dev, uint16_t NewImage); +static void DEBItransfer(struct comedi_device *dev); +static uint16_t DEBIread(struct comedi_device *dev, uint16_t addr); +static void DEBIwrite(struct comedi_device *dev, uint16_t addr, uint16_t wdata); +static void DEBIreplace(struct comedi_device *dev, uint16_t addr, uint16_t mask, + uint16_t wdata); +static void CloseDMAB(struct comedi_device *dev, struct bufferDMA *pdma, + size_t bsize); + +/* COUNTER OBJECT ------------------------------------------------ */ +struct enc_private { + /* Pointers to functions that differ for A and B counters: */ + uint16_t(*GetEnable) (struct comedi_device *dev, struct enc_private *); /* Return clock enable. */ + uint16_t(*GetIntSrc) (struct comedi_device *dev, struct enc_private *); /* Return interrupt source. */ + uint16_t(*GetLoadTrig) (struct comedi_device *dev, struct enc_private *); /* Return preload trigger source. */ + uint16_t(*GetMode) (struct comedi_device *dev, struct enc_private *); /* Return standardized operating mode. */ + void (*PulseIndex) (struct comedi_device *dev, struct enc_private *); /* Generate soft index strobe. */ + void (*SetEnable) (struct comedi_device *dev, struct enc_private *, uint16_t enab); /* Program clock enable. */ + void (*SetIntSrc) (struct comedi_device *dev, struct enc_private *, uint16_t IntSource); /* Program interrupt source. */ + void (*SetLoadTrig) (struct comedi_device *dev, struct enc_private *, uint16_t Trig); /* Program preload trigger source. */ + void (*SetMode) (struct comedi_device *dev, struct enc_private *, uint16_t Setup, uint16_t DisableIntSrc); /* Program standardized operating mode. */ + void (*ResetCapFlags) (struct comedi_device *dev, struct enc_private *); /* Reset event capture flags. */ + + uint16_t MyCRA; /* Address of CRA register. */ + uint16_t MyCRB; /* Address of CRB register. */ + uint16_t MyLatchLsw; /* Address of Latch least-significant-word */ + /* register. */ + uint16_t MyEventBits[4]; /* Bit translations for IntSrc -->RDMISC2. */ +}; + +#define encpriv ((struct enc_private *)(dev->subdevices+5)->private) + +/* counters routines */ +static void s626_timer_load(struct comedi_device *dev, struct enc_private *k, + int tick); +static uint32_t ReadLatch(struct comedi_device *dev, struct enc_private *k); +static void ResetCapFlags_A(struct comedi_device *dev, struct enc_private *k); +static void ResetCapFlags_B(struct comedi_device *dev, struct enc_private *k); +static uint16_t GetMode_A(struct comedi_device *dev, struct enc_private *k); +static uint16_t GetMode_B(struct comedi_device *dev, struct enc_private *k); +static void SetMode_A(struct comedi_device *dev, struct enc_private *k, + uint16_t Setup, uint16_t DisableIntSrc); +static void SetMode_B(struct comedi_device *dev, struct enc_private *k, + uint16_t Setup, uint16_t DisableIntSrc); +static void SetEnable_A(struct comedi_device *dev, struct enc_private *k, + uint16_t enab); +static void SetEnable_B(struct comedi_device *dev, struct enc_private *k, + uint16_t enab); +static uint16_t GetEnable_A(struct comedi_device *dev, struct enc_private *k); +static uint16_t GetEnable_B(struct comedi_device *dev, struct enc_private *k); +static void SetLatchSource(struct comedi_device *dev, struct enc_private *k, + uint16_t value); +/* static uint16_t GetLatchSource(struct comedi_device *dev, struct enc_private *k ); */ +static void SetLoadTrig_A(struct comedi_device *dev, struct enc_private *k, + uint16_t Trig); +static void SetLoadTrig_B(struct comedi_device *dev, struct enc_private *k, + uint16_t Trig); +static uint16_t GetLoadTrig_A(struct comedi_device *dev, struct enc_private *k); +static uint16_t GetLoadTrig_B(struct comedi_device *dev, struct enc_private *k); +static void SetIntSrc_B(struct comedi_device *dev, struct enc_private *k, + uint16_t IntSource); +static void SetIntSrc_A(struct comedi_device *dev, struct enc_private *k, + uint16_t IntSource); +static uint16_t GetIntSrc_A(struct comedi_device *dev, struct enc_private *k); +static uint16_t GetIntSrc_B(struct comedi_device *dev, struct enc_private *k); +/* static void SetClkMult(struct comedi_device *dev, struct enc_private *k, uint16_t value ) ; */ +/* static uint16_t GetClkMult(struct comedi_device *dev, struct enc_private *k ) ; */ +/* static void SetIndexPol(struct comedi_device *dev, struct enc_private *k, uint16_t value ); */ +/* static uint16_t GetClkPol(struct comedi_device *dev, struct enc_private *k ) ; */ +/* static void SetIndexSrc( struct comedi_device *dev,struct enc_private *k, uint16_t value ); */ +/* static uint16_t GetClkSrc( struct comedi_device *dev,struct enc_private *k ); */ +/* static void SetIndexSrc( struct comedi_device *dev,struct enc_private *k, uint16_t value ); */ +/* static uint16_t GetIndexSrc( struct comedi_device *dev,struct enc_private *k ); */ +static void PulseIndex_A(struct comedi_device *dev, struct enc_private *k); +static void PulseIndex_B(struct comedi_device *dev, struct enc_private *k); +static void Preload(struct comedi_device *dev, struct enc_private *k, + uint32_t value); +static void CountersInit(struct comedi_device *dev); +/* end internal routines */ + +/* Counter objects constructor. */ + +/* Counter overflow/index event flag masks for RDMISC2. */ +#define INDXMASK(C) (1 << (((C) > 2) ? ((C) * 2 - 1) : ((C) * 2 + 4))) +#define OVERMASK(C) (1 << (((C) > 2) ? ((C) * 2 + 5) : ((C) * 2 + 10))) +#define EVBITS(C) { 0, OVERMASK(C), INDXMASK(C), OVERMASK(C) | INDXMASK(C) } + +/* Translation table to map IntSrc into equivalent RDMISC2 event flag bits. */ +/* static const uint16_t EventBits[][4] = { EVBITS(0), EVBITS(1), EVBITS(2), EVBITS(3), EVBITS(4), EVBITS(5) }; */ + +/* struct enc_private; */ +static struct enc_private enc_private_data[] = { + { + .GetEnable = GetEnable_A, + .GetIntSrc = GetIntSrc_A, + .GetLoadTrig = GetLoadTrig_A, + .GetMode = GetMode_A, + .PulseIndex = PulseIndex_A, + .SetEnable = SetEnable_A, + .SetIntSrc = SetIntSrc_A, + .SetLoadTrig = SetLoadTrig_A, + .SetMode = SetMode_A, + .ResetCapFlags = ResetCapFlags_A, + .MyCRA = LP_CR0A, + .MyCRB = LP_CR0B, + .MyLatchLsw = LP_CNTR0ALSW, + .MyEventBits = EVBITS(0), + }, + { + .GetEnable = GetEnable_A, + .GetIntSrc = GetIntSrc_A, + .GetLoadTrig = GetLoadTrig_A, + .GetMode = GetMode_A, + .PulseIndex = PulseIndex_A, + .SetEnable = SetEnable_A, + .SetIntSrc = SetIntSrc_A, + .SetLoadTrig = SetLoadTrig_A, + .SetMode = SetMode_A, + .ResetCapFlags = ResetCapFlags_A, + .MyCRA = LP_CR1A, + .MyCRB = LP_CR1B, + .MyLatchLsw = LP_CNTR1ALSW, + .MyEventBits = EVBITS(1), + }, + { + .GetEnable = GetEnable_A, + .GetIntSrc = GetIntSrc_A, + .GetLoadTrig = GetLoadTrig_A, + .GetMode = GetMode_A, + .PulseIndex = PulseIndex_A, + .SetEnable = SetEnable_A, + .SetIntSrc = SetIntSrc_A, + .SetLoadTrig = SetLoadTrig_A, + .SetMode = SetMode_A, + .ResetCapFlags = ResetCapFlags_A, + .MyCRA = LP_CR2A, + .MyCRB = LP_CR2B, + .MyLatchLsw = LP_CNTR2ALSW, + .MyEventBits = EVBITS(2), + }, + { + .GetEnable = GetEnable_B, + .GetIntSrc = GetIntSrc_B, + .GetLoadTrig = GetLoadTrig_B, + .GetMode = GetMode_B, + .PulseIndex = PulseIndex_B, + .SetEnable = SetEnable_B, + .SetIntSrc = SetIntSrc_B, + .SetLoadTrig = SetLoadTrig_B, + .SetMode = SetMode_B, + .ResetCapFlags = ResetCapFlags_B, + .MyCRA = LP_CR0A, + .MyCRB = LP_CR0B, + .MyLatchLsw = LP_CNTR0BLSW, + .MyEventBits = EVBITS(3), + }, + { + .GetEnable = GetEnable_B, + .GetIntSrc = GetIntSrc_B, + .GetLoadTrig = GetLoadTrig_B, + .GetMode = GetMode_B, + .PulseIndex = PulseIndex_B, + .SetEnable = SetEnable_B, + .SetIntSrc = SetIntSrc_B, + .SetLoadTrig = SetLoadTrig_B, + .SetMode = SetMode_B, + .ResetCapFlags = ResetCapFlags_B, + .MyCRA = LP_CR1A, + .MyCRB = LP_CR1B, + .MyLatchLsw = LP_CNTR1BLSW, + .MyEventBits = EVBITS(4), + }, + { + .GetEnable = GetEnable_B, + .GetIntSrc = GetIntSrc_B, + .GetLoadTrig = GetLoadTrig_B, + .GetMode = GetMode_B, + .PulseIndex = PulseIndex_B, + .SetEnable = SetEnable_B, + .SetIntSrc = SetIntSrc_B, + .SetLoadTrig = SetLoadTrig_B, + .SetMode = SetMode_B, + .ResetCapFlags = ResetCapFlags_B, + .MyCRA = LP_CR2A, + .MyCRB = LP_CR2B, + .MyLatchLsw = LP_CNTR2BLSW, + .MyEventBits = EVBITS(5), + }, +}; + +/* enab/disable a function or test status bit(s) that are accessed */ +/* through Main Control Registers 1 or 2. */ +#define MC_ENABLE(REGADRS, CTRLWORD) writel(((uint32_t)(CTRLWORD) << 16) | (uint32_t)(CTRLWORD), devpriv->base_addr+(REGADRS)) + +#define MC_DISABLE(REGADRS, CTRLWORD) writel((uint32_t)(CTRLWORD) << 16 , devpriv->base_addr+(REGADRS)) + +#define MC_TEST(REGADRS, CTRLWORD) ((readl(devpriv->base_addr+(REGADRS)) & CTRLWORD) != 0) + +/* #define WR7146(REGARDS,CTRLWORD) + writel(CTRLWORD,(uint32_t)(devpriv->base_addr+(REGARDS))) */ +#define WR7146(REGARDS, CTRLWORD) writel(CTRLWORD, devpriv->base_addr+(REGARDS)) + +/* #define RR7146(REGARDS) + readl((uint32_t)(devpriv->base_addr+(REGARDS))) */ +#define RR7146(REGARDS) readl(devpriv->base_addr+(REGARDS)) + +#define BUGFIX_STREG(REGADRS) (REGADRS - 4) + +/* Write a time slot control record to TSL2. */ +#define VECTPORT(VECTNUM) (P_TSL2 + ((VECTNUM) << 2)) +#define SETVECT(VECTNUM, VECTVAL) WR7146(VECTPORT(VECTNUM), (VECTVAL)) + +/* Code macros used for constructing I2C command bytes. */ +#define I2C_B2(ATTR, VAL) (((ATTR) << 6) | ((VAL) << 24)) +#define I2C_B1(ATTR, VAL) (((ATTR) << 4) | ((VAL) << 16)) +#define I2C_B0(ATTR, VAL) (((ATTR) << 2) | ((VAL) << 8)) + +static const struct comedi_lrange s626_range_table = { 2, { + RANGE(-5, 5), + RANGE(-10, 10), + } +}; + +static int s626_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ +/* uint8_t PollList; */ +/* uint16_t AdcData; */ +/* uint16_t StartVal; */ +/* uint16_t index; */ +/* unsigned int data[16]; */ + int result; + int i; + int ret; + resource_size_t resourceStart; + dma_addr_t appdma; + struct comedi_subdevice *s; + const struct pci_device_id *ids; + struct pci_dev *pdev = NULL; + + if (alloc_private(dev, sizeof(struct s626_private)) < 0) + return -ENOMEM; + + for (i = 0; i < (ARRAY_SIZE(s626_pci_table) - 1) && !pdev; i++) { + ids = &s626_pci_table[i]; + do { + pdev = pci_get_subsys(ids->vendor, ids->device, + ids->subvendor, ids->subdevice, + pdev); + + if ((it->options[0] || it->options[1]) && pdev) { + /* matches requested bus/slot */ + if (pdev->bus->number == it->options[0] && + PCI_SLOT(pdev->devfn) == it->options[1]) + break; + } else + break; + } while (1); + } + devpriv->pdev = pdev; + + if (pdev == NULL) { + printk(KERN_ERR "s626_attach: Board not present!!!\n"); + return -ENODEV; + } + + result = comedi_pci_enable(pdev, "s626"); + if (result < 0) { + printk(KERN_ERR "s626_attach: comedi_pci_enable fails\n"); + return -ENODEV; + } + devpriv->got_regions = 1; + + resourceStart = pci_resource_start(devpriv->pdev, 0); + + devpriv->base_addr = ioremap(resourceStart, SIZEOF_ADDRESS_SPACE); + if (devpriv->base_addr == NULL) { + printk(KERN_ERR "s626_attach: IOREMAP failed\n"); + return -ENODEV; + } + + if (devpriv->base_addr) { + /* disable master interrupt */ + writel(0, devpriv->base_addr + P_IER); + + /* soft reset */ + writel(MC1_SOFT_RESET, devpriv->base_addr + P_MC1); + + /* DMA FIXME DMA// */ + DEBUG("s626_attach: DMA ALLOCATION\n"); + + /* adc buffer allocation */ + devpriv->allocatedBuf = 0; + + devpriv->ANABuf.LogicalBase = + pci_alloc_consistent(devpriv->pdev, DMABUF_SIZE, &appdma); + + if (devpriv->ANABuf.LogicalBase == NULL) { + printk(KERN_ERR "s626_attach: DMA Memory mapping error\n"); + return -ENOMEM; + } + + devpriv->ANABuf.PhysicalBase = appdma; + + DEBUG + ("s626_attach: AllocDMAB ADC Logical=%p, bsize=%d, Physical=0x%x\n", + devpriv->ANABuf.LogicalBase, DMABUF_SIZE, + (uint32_t) devpriv->ANABuf.PhysicalBase); + + devpriv->allocatedBuf++; + + devpriv->RPSBuf.LogicalBase = + pci_alloc_consistent(devpriv->pdev, DMABUF_SIZE, &appdma); + + if (devpriv->RPSBuf.LogicalBase == NULL) { + printk(KERN_ERR "s626_attach: DMA Memory mapping error\n"); + return -ENOMEM; + } + + devpriv->RPSBuf.PhysicalBase = appdma; + + DEBUG + ("s626_attach: AllocDMAB RPS Logical=%p, bsize=%d, Physical=0x%x\n", + devpriv->RPSBuf.LogicalBase, DMABUF_SIZE, + (uint32_t) devpriv->RPSBuf.PhysicalBase); + + devpriv->allocatedBuf++; + + } + + dev->board_ptr = s626_boards; + dev->board_name = thisboard->name; + + if (alloc_subdevices(dev, 6) < 0) + return -ENOMEM; + + dev->iobase = (unsigned long)devpriv->base_addr; + dev->irq = devpriv->pdev->irq; + + /* set up interrupt handler */ + if (dev->irq == 0) { + printk(KERN_ERR " unknown irq (bad)\n"); + } else { + ret = request_irq(dev->irq, s626_irq_handler, IRQF_SHARED, + "s626", dev); + + if (ret < 0) { + printk(KERN_ERR " irq not available\n"); + dev->irq = 0; + } + } + + DEBUG("s626_attach: -- it opts %d,%d --\n", + it->options[0], it->options[1]); + + s = dev->subdevices + 0; + /* analog input subdevice */ + dev->read_subdev = s; + /* we support single-ended (ground) and differential */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_CMD_READ; + s->n_chan = thisboard->ai_chans; + s->maxdata = (0xffff >> 2); + s->range_table = &s626_range_table; + s->len_chanlist = thisboard->ai_chans; /* This is the maximum chanlist + length that the board can + handle */ + s->insn_config = s626_ai_insn_config; + s->insn_read = s626_ai_insn_read; + s->do_cmd = s626_ai_cmd; + s->do_cmdtest = s626_ai_cmdtest; + s->cancel = s626_ai_cancel; + + s = dev->subdevices + 1; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = thisboard->ao_chans; + s->maxdata = (0x3fff); + s->range_table = &range_bipolar10; + s->insn_write = s626_ao_winsn; + s->insn_read = s626_ao_rinsn; + + s = dev->subdevices + 2; + /* digital I/O subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = S626_DIO_CHANNELS; + s->maxdata = 1; + s->io_bits = 0xffff; + s->private = &dio_private_A; + s->range_table = &range_digital; + s->insn_config = s626_dio_insn_config; + s->insn_bits = s626_dio_insn_bits; + + s = dev->subdevices + 3; + /* digital I/O subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->io_bits = 0xffff; + s->private = &dio_private_B; + s->range_table = &range_digital; + s->insn_config = s626_dio_insn_config; + s->insn_bits = s626_dio_insn_bits; + + s = dev->subdevices + 4; + /* digital I/O subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->io_bits = 0xffff; + s->private = &dio_private_C; + s->range_table = &range_digital; + s->insn_config = s626_dio_insn_config; + s->insn_bits = s626_dio_insn_bits; + + s = dev->subdevices + 5; + /* encoder (counter) subdevice */ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE | SDF_LSAMPL; + s->n_chan = thisboard->enc_chans; + s->private = enc_private_data; + s->insn_config = s626_enc_insn_config; + s->insn_read = s626_enc_insn_read; + s->insn_write = s626_enc_insn_write; + s->maxdata = 0xffffff; + s->range_table = &range_unknown; + + /* stop ai_command */ + devpriv->ai_cmd_running = 0; + + if (devpriv->base_addr && (devpriv->allocatedBuf == 2)) { + dma_addr_t pPhysBuf; + uint16_t chan; + + /* enab DEBI and audio pins, enable I2C interface. */ + MC_ENABLE(P_MC1, MC1_DEBI | MC1_AUDIO | MC1_I2C); + /* Configure DEBI operating mode. */ + WR7146(P_DEBICFG, DEBI_CFG_SLAVE16 /* Local bus is 16 */ + /* bits wide. */ + | (DEBI_TOUT << DEBI_CFG_TOUT_BIT) + + /* Declare DEBI */ + /* transfer timeout */ + /* interval. */ + |DEBI_SWAP /* Set up byte lane */ + /* steering. */ + | DEBI_CFG_INTEL); /* Intel-compatible */ + /* local bus (DEBI */ + /* never times out). */ + DEBUG("s626_attach: %d debi init -- %d\n", + DEBI_CFG_SLAVE16 | (DEBI_TOUT << DEBI_CFG_TOUT_BIT) | + DEBI_SWAP | DEBI_CFG_INTEL, + DEBI_CFG_INTEL | DEBI_CFG_TOQ | DEBI_CFG_INCQ | + DEBI_CFG_16Q); + + /* DEBI INIT S626 WR7146( P_DEBICFG, DEBI_CFG_INTEL | DEBI_CFG_TOQ */ + /* | DEBI_CFG_INCQ| DEBI_CFG_16Q); //end */ + + /* Paging is disabled. */ + WR7146(P_DEBIPAGE, DEBI_PAGE_DISABLE); /* Disable MMU paging. */ + + /* Init GPIO so that ADC Start* is negated. */ + WR7146(P_GPIO, GPIO_BASE | GPIO1_HI); + + /* IsBoardRevA is a boolean that indicates whether the board is RevA. + * + * VERSION 2.01 CHANGE: REV A & B BOARDS NOW SUPPORTED BY DYNAMIC + * EEPROM ADDRESS SELECTION. Initialize the I2C interface, which + * is used to access the onboard serial EEPROM. The EEPROM's I2C + * DeviceAddress is hardwired to a value that is dependent on the + * 626 board revision. On all board revisions, the EEPROM stores + * TrimDAC calibration constants for analog I/O. On RevB and + * higher boards, the DeviceAddress is hardwired to 0 to enable + * the EEPROM to also store the PCI SubVendorID and SubDeviceID; + * this is the address at which the SAA7146 expects a + * configuration EEPROM to reside. On RevA boards, the EEPROM + * device address, which is hardwired to 4, prevents the SAA7146 + * from retrieving PCI sub-IDs, so the SAA7146 uses its built-in + * default values, instead. + */ + + /* devpriv->I2Cards= IsBoardRevA ? 0xA8 : 0xA0; // Set I2C EEPROM */ + /* DeviceType (0xA0) */ + /* and DeviceAddress<<1. */ + + devpriv->I2CAdrs = 0xA0; /* I2C device address for onboard */ + /* eeprom(revb) */ + + /* Issue an I2C ABORT command to halt any I2C operation in */ + /* progress and reset BUSY flag. */ + WR7146(P_I2CSTAT, I2C_CLKSEL | I2C_ABORT); + /* Write I2C control: abort any I2C activity. */ + MC_ENABLE(P_MC2, MC2_UPLD_IIC); + /* Invoke command upload */ + while ((RR7146(P_MC2) & MC2_UPLD_IIC) == 0) + ; + /* and wait for upload to complete. */ + + /* Per SAA7146 data sheet, write to STATUS reg twice to + * reset all I2C error flags. */ + for (i = 0; i < 2; i++) { + WR7146(P_I2CSTAT, I2C_CLKSEL); + /* Write I2C control: reset error flags. */ + MC_ENABLE(P_MC2, MC2_UPLD_IIC); /* Invoke command upload */ + while (!MC_TEST(P_MC2, MC2_UPLD_IIC)) + ; + /* and wait for upload to complete. */ + } + + /* Init audio interface functional attributes: set DAC/ADC + * serial clock rates, invert DAC serial clock so that + * DAC data setup times are satisfied, enable DAC serial + * clock out. + */ + + WR7146(P_ACON2, ACON2_INIT); + + /* Set up TSL1 slot list, which is used to control the + * accumulation of ADC data: RSD1 = shift data in on SD1. + * SIB_A1 = store data uint8_t at next available location in + * FB BUFFER1 register. */ + WR7146(P_TSL1, RSD1 | SIB_A1); + /* Fetch ADC high data uint8_t. */ + WR7146(P_TSL1 + 4, RSD1 | SIB_A1 | EOS); + /* Fetch ADC low data uint8_t; end of TSL1. */ + + /* enab TSL1 slot list so that it executes all the time. */ + WR7146(P_ACON1, ACON1_ADCSTART); + + /* Initialize RPS registers used for ADC. */ + + /* Physical start of RPS program. */ + WR7146(P_RPSADDR1, (uint32_t) devpriv->RPSBuf.PhysicalBase); + + WR7146(P_RPSPAGE1, 0); + /* RPS program performs no explicit mem writes. */ + WR7146(P_RPS1_TOUT, 0); /* Disable RPS timeouts. */ + + /* SAA7146 BUG WORKAROUND. Initialize SAA7146 ADC interface + * to a known state by invoking ADCs until FB BUFFER 1 + * register shows that it is correctly receiving ADC data. + * This is necessary because the SAA7146 ADC interface does + * not start up in a defined state after a PCI reset. + */ + +/* PollList = EOPL; // Create a simple polling */ +/* // list for analog input */ +/* // channel 0. */ +/* ResetADC( dev, &PollList ); */ + +/* s626_ai_rinsn(dev,dev->subdevices,NULL,data); //( &AdcData ); // */ +/* //Get initial ADC */ +/* //value. */ + +/* StartVal = data[0]; */ + +/* // VERSION 2.01 CHANGE: TIMEOUT ADDED TO PREVENT HANGED EXECUTION. */ +/* // Invoke ADCs until the new ADC value differs from the initial */ +/* // value or a timeout occurs. The timeout protects against the */ +/* // possibility that the driver is restarting and the ADC data is a */ +/* // fixed value resulting from the applied ADC analog input being */ +/* // unusually quiet or at the rail. */ + +/* for ( index = 0; index < 500; index++ ) */ +/* { */ +/* s626_ai_rinsn(dev,dev->subdevices,NULL,data); */ +/* AdcData = data[0]; //ReadADC( &AdcData ); */ +/* if ( AdcData != StartVal ) */ +/* break; */ +/* } */ + + /* end initADC */ + + /* init the DAC interface */ + + /* Init Audio2's output DMAC attributes: burst length = 1 + * DWORD, threshold = 1 DWORD. + */ + WR7146(P_PCI_BT_A, 0); + + /* Init Audio2's output DMA physical addresses. The protection + * address is set to 1 DWORD past the base address so that a + * single DWORD will be transferred each time a DMA transfer is + * enabled. */ + + pPhysBuf = + devpriv->ANABuf.PhysicalBase + + (DAC_WDMABUF_OS * sizeof(uint32_t)); + + WR7146(P_BASEA2_OUT, (uint32_t) pPhysBuf); /* Buffer base adrs. */ + WR7146(P_PROTA2_OUT, (uint32_t) (pPhysBuf + sizeof(uint32_t))); /* Protection address. */ + + /* Cache Audio2's output DMA buffer logical address. This is + * where DAC data is buffered for A2 output DMA transfers. */ + devpriv->pDacWBuf = + (uint32_t *) devpriv->ANABuf.LogicalBase + DAC_WDMABUF_OS; + + /* Audio2's output channels does not use paging. The protection + * violation handling bit is set so that the DMAC will + * automatically halt and its PCI address pointer will be reset + * when the protection address is reached. */ + + WR7146(P_PAGEA2_OUT, 8); + + /* Initialize time slot list 2 (TSL2), which is used to control + * the clock generation for and serialization of data to be sent + * to the DAC devices. Slot 0 is a NOP that is used to trap TSL + * execution; this permits other slots to be safely modified + * without first turning off the TSL sequencer (which is + * apparently impossible to do). Also, SD3 (which is driven by a + * pull-up resistor) is shifted in and stored to the MSB of + * FB_BUFFER2 to be used as evidence that the slot sequence has + * not yet finished executing. + */ + + SETVECT(0, XSD2 | RSD3 | SIB_A2 | EOS); + /* Slot 0: Trap TSL execution, shift 0xFF into FB_BUFFER2. */ + + /* Initialize slot 1, which is constant. Slot 1 causes a + * DWORD to be transferred from audio channel 2's output FIFO + * to the FIFO's output buffer so that it can be serialized + * and sent to the DAC during subsequent slots. All remaining + * slots are dynamically populated as required by the target + * DAC device. + */ + SETVECT(1, LF_A2); + /* Slot 1: Fetch DWORD from Audio2's output FIFO. */ + + /* Start DAC's audio interface (TSL2) running. */ + WR7146(P_ACON1, ACON1_DACSTART); + + /* end init DAC interface */ + + /* Init Trim DACs to calibrated values. Do it twice because the + * SAA7146 audio channel does not always reset properly and + * sometimes causes the first few TrimDAC writes to malfunction. + */ + + LoadTrimDACs(dev); + LoadTrimDACs(dev); /* Insurance. */ + + /* Manually init all gate array hardware in case this is a soft + * reset (we have no way of determining whether this is a warm + * or cold start). This is necessary because the gate array will + * reset only in response to a PCI hard reset; there is no soft + * reset function. */ + + /* Init all DAC outputs to 0V and init all DAC setpoint and + * polarity images. + */ + for (chan = 0; chan < S626_DAC_CHANNELS; chan++) + SetDAC(dev, chan, 0); + + /* Init image of WRMISC2 Battery Charger Enabled control bit. + * This image is used when the state of the charger control bit, + * which has no direct hardware readback mechanism, is queried. + */ + devpriv->ChargeEnabled = 0; + + /* Init image of watchdog timer interval in WRMISC2. This image + * maintains the value of the control bits of MISC2 are + * continuously reset to zero as long as the WD timer is disabled. + */ + devpriv->WDInterval = 0; + + /* Init Counter Interrupt enab mask for RDMISC2. This mask is + * applied against MISC2 when testing to determine which timer + * events are requesting interrupt service. + */ + devpriv->CounterIntEnabs = 0; + + /* Init counters. */ + CountersInit(dev); + + /* Without modifying the state of the Battery Backup enab, disable + * the watchdog timer, set DIO channels 0-5 to operate in the + * standard DIO (vs. counter overflow) mode, disable the battery + * charger, and reset the watchdog interval selector to zero. + */ + WriteMISC2(dev, (uint16_t) (DEBIread(dev, + LP_RDMISC2) & + MISC2_BATT_ENABLE)); + + /* Initialize the digital I/O subsystem. */ + s626_dio_init(dev); + + /* enable interrupt test */ + /* writel(IRQ_GPIO3 | IRQ_RPS1,devpriv->base_addr+P_IER); */ + } + + DEBUG("s626_attach: comedi%d s626 attached %04x\n", dev->minor, + (uint32_t) devpriv->base_addr); + + return 1; +} + +static unsigned int s626_ai_reg_to_uint(int data) +{ + unsigned int tempdata; + + tempdata = (data >> 18); + if (tempdata & 0x2000) + tempdata &= 0x1fff; + else + tempdata += (1 << 13); + + return tempdata; +} + +/* static unsigned int s626_uint_to_reg(struct comedi_subdevice *s, int data){ */ +/* return 0; */ +/* } */ + +static irqreturn_t s626_irq_handler(int irq, void *d) +{ + struct comedi_device *dev = d; + struct comedi_subdevice *s; + struct comedi_cmd *cmd; + struct enc_private *k; + unsigned long flags; + int32_t *readaddr; + uint32_t irqtype, irqstatus; + int i = 0; + short tempdata; + uint8_t group; + uint16_t irqbit; + + DEBUG("s626_irq_handler: interrupt request received!!!\n"); + + if (dev->attached == 0) + return IRQ_NONE; + /* lock to avoid race with comedi_poll */ + spin_lock_irqsave(&dev->spinlock, flags); + + /* save interrupt enable register state */ + irqstatus = readl(devpriv->base_addr + P_IER); + + /* read interrupt type */ + irqtype = readl(devpriv->base_addr + P_ISR); + + /* disable master interrupt */ + writel(0, devpriv->base_addr + P_IER); + + /* clear interrupt */ + writel(irqtype, devpriv->base_addr + P_ISR); + + /* do somethings */ + DEBUG("s626_irq_handler: interrupt type %d\n", irqtype); + + switch (irqtype) { + case IRQ_RPS1: /* end_of_scan occurs */ + + DEBUG("s626_irq_handler: RPS1 irq detected\n"); + + /* manage ai subdevice */ + s = dev->subdevices; + cmd = &(s->async->cmd); + + /* Init ptr to DMA buffer that holds new ADC data. We skip the + * first uint16_t in the buffer because it contains junk data from + * the final ADC of the previous poll list scan. + */ + readaddr = (int32_t *) devpriv->ANABuf.LogicalBase + 1; + + /* get the data and hand it over to comedi */ + for (i = 0; i < (s->async->cmd.chanlist_len); i++) { + /* Convert ADC data to 16-bit integer values and copy to application */ + /* buffer. */ + tempdata = s626_ai_reg_to_uint((int)*readaddr); + readaddr++; + + /* put data into read buffer */ + /* comedi_buf_put(s->async, tempdata); */ + if (cfc_write_to_buffer(s, tempdata) == 0) + printk + ("s626_irq_handler: cfc_write_to_buffer error!\n"); + + DEBUG("s626_irq_handler: ai channel %d acquired: %d\n", + i, tempdata); + } + + /* end of scan occurs */ + s->async->events |= COMEDI_CB_EOS; + + if (!(devpriv->ai_continous)) + devpriv->ai_sample_count--; + if (devpriv->ai_sample_count <= 0) { + devpriv->ai_cmd_running = 0; + + /* Stop RPS program. */ + MC_DISABLE(P_MC1, MC1_ERPS1); + + /* send end of acquisition */ + s->async->events |= COMEDI_CB_EOA; + + /* disable master interrupt */ + irqstatus = 0; + } + + if (devpriv->ai_cmd_running && cmd->scan_begin_src == TRIG_EXT) { + DEBUG + ("s626_irq_handler: enable interrupt on dio channel %d\n", + cmd->scan_begin_arg); + + s626_dio_set_irq(dev, cmd->scan_begin_arg); + + DEBUG("s626_irq_handler: External trigger is set!!!\n"); + } + /* tell comedi that data is there */ + DEBUG("s626_irq_handler: events %d\n", s->async->events); + comedi_event(dev, s); + break; + case IRQ_GPIO3: /* check dio and conter interrupt */ + + DEBUG("s626_irq_handler: GPIO3 irq detected\n"); + + /* manage ai subdevice */ + s = dev->subdevices; + cmd = &(s->async->cmd); + + /* s626_dio_clear_irq(dev); */ + + for (group = 0; group < S626_DIO_BANKS; group++) { + irqbit = 0; + /* read interrupt type */ + irqbit = DEBIread(dev, + ((struct dio_private *)(dev-> + subdevices + + 2 + + group)-> + private)->RDCapFlg); + + /* check if interrupt is generated from dio channels */ + if (irqbit) { + s626_dio_reset_irq(dev, group, irqbit); + DEBUG + ("s626_irq_handler: check interrupt on dio group %d %d\n", + group, i); + if (devpriv->ai_cmd_running) { + /* check if interrupt is an ai acquisition start trigger */ + if ((irqbit >> (cmd->start_arg - + (16 * group))) + == 1 && cmd->start_src == TRIG_EXT) { + DEBUG + ("s626_irq_handler: Edge capture interrupt received from channel %d\n", + cmd->start_arg); + + /* Start executing the RPS program. */ + MC_ENABLE(P_MC1, MC1_ERPS1); + + DEBUG + ("s626_irq_handler: acquisition start triggered!!!\n"); + + if (cmd->scan_begin_src == + TRIG_EXT) { + DEBUG + ("s626_ai_cmd: enable interrupt on dio channel %d\n", + cmd-> + scan_begin_arg); + + s626_dio_set_irq(dev, + cmd->scan_begin_arg); + + DEBUG + ("s626_irq_handler: External scan trigger is set!!!\n"); + } + } + if ((irqbit >> (cmd->scan_begin_arg - + (16 * group))) + == 1 + && cmd->scan_begin_src == + TRIG_EXT) { + DEBUG + ("s626_irq_handler: Edge capture interrupt received from channel %d\n", + cmd->scan_begin_arg); + + /* Trigger ADC scan loop start by setting RPS Signal 0. */ + MC_ENABLE(P_MC2, MC2_ADC_RPS); + + DEBUG + ("s626_irq_handler: scan triggered!!! %d\n", + devpriv->ai_sample_count); + if (cmd->convert_src == + TRIG_EXT) { + + DEBUG + ("s626_ai_cmd: enable interrupt on dio channel %d group %d\n", + cmd->convert_arg - + (16 * group), + group); + + devpriv->ai_convert_count + = cmd->chanlist_len; + + s626_dio_set_irq(dev, + cmd->convert_arg); + + DEBUG + ("s626_irq_handler: External convert trigger is set!!!\n"); + } + + if (cmd->convert_src == + TRIG_TIMER) { + k = &encpriv[5]; + devpriv->ai_convert_count + = cmd->chanlist_len; + k->SetEnable(dev, k, + CLKENAB_ALWAYS); + } + } + if ((irqbit >> (cmd->convert_arg - + (16 * group))) + == 1 + && cmd->convert_src == TRIG_EXT) { + DEBUG + ("s626_irq_handler: Edge capture interrupt received from channel %d\n", + cmd->convert_arg); + + /* Trigger ADC scan loop start by setting RPS Signal 0. */ + MC_ENABLE(P_MC2, MC2_ADC_RPS); + + DEBUG + ("s626_irq_handler: adc convert triggered!!!\n"); + + devpriv->ai_convert_count--; + + if (devpriv->ai_convert_count > + 0) { + + DEBUG + ("s626_ai_cmd: enable interrupt on dio channel %d group %d\n", + cmd->convert_arg - + (16 * group), + group); + + s626_dio_set_irq(dev, + cmd->convert_arg); + + DEBUG + ("s626_irq_handler: External trigger is set!!!\n"); + } + } + } + break; + } + } + + /* read interrupt type */ + irqbit = DEBIread(dev, LP_RDMISC2); + + /* check interrupt on counters */ + DEBUG("s626_irq_handler: check counters interrupt %d\n", + irqbit); + + if (irqbit & IRQ_COINT1A) { + DEBUG + ("s626_irq_handler: interrupt on counter 1A overflow\n"); + k = &encpriv[0]; + + /* clear interrupt capture flag */ + k->ResetCapFlags(dev, k); + } + if (irqbit & IRQ_COINT2A) { + DEBUG + ("s626_irq_handler: interrupt on counter 2A overflow\n"); + k = &encpriv[1]; + + /* clear interrupt capture flag */ + k->ResetCapFlags(dev, k); + } + if (irqbit & IRQ_COINT3A) { + DEBUG + ("s626_irq_handler: interrupt on counter 3A overflow\n"); + k = &encpriv[2]; + + /* clear interrupt capture flag */ + k->ResetCapFlags(dev, k); + } + if (irqbit & IRQ_COINT1B) { + DEBUG + ("s626_irq_handler: interrupt on counter 1B overflow\n"); + k = &encpriv[3]; + + /* clear interrupt capture flag */ + k->ResetCapFlags(dev, k); + } + if (irqbit & IRQ_COINT2B) { + DEBUG + ("s626_irq_handler: interrupt on counter 2B overflow\n"); + k = &encpriv[4]; + + /* clear interrupt capture flag */ + k->ResetCapFlags(dev, k); + + if (devpriv->ai_convert_count > 0) { + devpriv->ai_convert_count--; + if (devpriv->ai_convert_count == 0) + k->SetEnable(dev, k, CLKENAB_INDEX); + + if (cmd->convert_src == TRIG_TIMER) { + DEBUG + ("s626_irq_handler: conver timer trigger!!! %d\n", + devpriv->ai_convert_count); + + /* Trigger ADC scan loop start by setting RPS Signal 0. */ + MC_ENABLE(P_MC2, MC2_ADC_RPS); + } + } + } + if (irqbit & IRQ_COINT3B) { + DEBUG + ("s626_irq_handler: interrupt on counter 3B overflow\n"); + k = &encpriv[5]; + + /* clear interrupt capture flag */ + k->ResetCapFlags(dev, k); + + if (cmd->scan_begin_src == TRIG_TIMER) { + DEBUG + ("s626_irq_handler: scan timer trigger!!!\n"); + + /* Trigger ADC scan loop start by setting RPS Signal 0. */ + MC_ENABLE(P_MC2, MC2_ADC_RPS); + } + + if (cmd->convert_src == TRIG_TIMER) { + DEBUG + ("s626_irq_handler: convert timer trigger is set\n"); + k = &encpriv[4]; + devpriv->ai_convert_count = cmd->chanlist_len; + k->SetEnable(dev, k, CLKENAB_ALWAYS); + } + } + } + + /* enable interrupt */ + writel(irqstatus, devpriv->base_addr + P_IER); + + DEBUG("s626_irq_handler: exit interrupt service routine.\n"); + + spin_unlock_irqrestore(&dev->spinlock, flags); + return IRQ_HANDLED; +} + +static int s626_detach(struct comedi_device *dev) +{ + if (devpriv) { + /* stop ai_command */ + devpriv->ai_cmd_running = 0; + + if (devpriv->base_addr) { + /* interrupt mask */ + WR7146(P_IER, 0); /* Disable master interrupt. */ + WR7146(P_ISR, IRQ_GPIO3 | IRQ_RPS1); /* Clear board's IRQ status flag. */ + + /* Disable the watchdog timer and battery charger. */ + WriteMISC2(dev, 0); + + /* Close all interfaces on 7146 device. */ + WR7146(P_MC1, MC1_SHUTDOWN); + WR7146(P_ACON1, ACON1_BASE); + + CloseDMAB(dev, &devpriv->RPSBuf, DMABUF_SIZE); + CloseDMAB(dev, &devpriv->ANABuf, DMABUF_SIZE); + } + + if (dev->irq) + free_irq(dev->irq, dev); + + if (devpriv->base_addr) + iounmap(devpriv->base_addr); + + if (devpriv->pdev) { + if (devpriv->got_regions) + comedi_pci_disable(devpriv->pdev); + pci_dev_put(devpriv->pdev); + } + } + + DEBUG("s626_detach: S626 detached!\n"); + + return 0; +} + +/* + * this functions build the RPS program for hardware driven acquistion + */ +void ResetADC(struct comedi_device *dev, uint8_t * ppl) +{ + register uint32_t *pRPS; + uint32_t JmpAdrs; + uint16_t i; + uint16_t n; + uint32_t LocalPPL; + struct comedi_cmd *cmd = &(dev->subdevices->async->cmd); + + /* Stop RPS program in case it is currently running. */ + MC_DISABLE(P_MC1, MC1_ERPS1); + + /* Set starting logical address to write RPS commands. */ + pRPS = (uint32_t *) devpriv->RPSBuf.LogicalBase; + + /* Initialize RPS instruction pointer. */ + WR7146(P_RPSADDR1, (uint32_t) devpriv->RPSBuf.PhysicalBase); + + /* Construct RPS program in RPSBuf DMA buffer */ + + if (cmd != NULL && cmd->scan_begin_src != TRIG_FOLLOW) { + DEBUG("ResetADC: scan_begin pause inserted\n"); + /* Wait for Start trigger. */ + *pRPS++ = RPS_PAUSE | RPS_SIGADC; + *pRPS++ = RPS_CLRSIGNAL | RPS_SIGADC; + } + + /* SAA7146 BUG WORKAROUND Do a dummy DEBI Write. This is necessary + * because the first RPS DEBI Write following a non-RPS DEBI write + * seems to always fail. If we don't do this dummy write, the ADC + * gain might not be set to the value required for the first slot in + * the poll list; the ADC gain would instead remain unchanged from + * the previously programmed value. + */ + *pRPS++ = RPS_LDREG | (P_DEBICMD >> 2); + /* Write DEBI Write command and address to shadow RAM. */ + + *pRPS++ = DEBI_CMD_WRWORD | LP_GSEL; + *pRPS++ = RPS_LDREG | (P_DEBIAD >> 2); + /* Write DEBI immediate data to shadow RAM: */ + + *pRPS++ = GSEL_BIPOLAR5V; + /* arbitrary immediate data value. */ + + *pRPS++ = RPS_CLRSIGNAL | RPS_DEBI; + /* Reset "shadow RAM uploaded" flag. */ + *pRPS++ = RPS_UPLOAD | RPS_DEBI; /* Invoke shadow RAM upload. */ + *pRPS++ = RPS_PAUSE | RPS_DEBI; /* Wait for shadow upload to finish. */ + + /* Digitize all slots in the poll list. This is implemented as a + * for loop to limit the slot count to 16 in case the application + * forgot to set the EOPL flag in the final slot. + */ + for (devpriv->AdcItems = 0; devpriv->AdcItems < 16; devpriv->AdcItems++) { + /* Convert application's poll list item to private board class + * format. Each app poll list item is an uint8_t with form + * (EOPL,x,x,RANGE,CHAN<3:0>), where RANGE code indicates 0 = + * +-10V, 1 = +-5V, and EOPL = End of Poll List marker. + */ + LocalPPL = + (*ppl << 8) | (*ppl & 0x10 ? GSEL_BIPOLAR5V : + GSEL_BIPOLAR10V); + + /* Switch ADC analog gain. */ + *pRPS++ = RPS_LDREG | (P_DEBICMD >> 2); /* Write DEBI command */ + /* and address to */ + /* shadow RAM. */ + *pRPS++ = DEBI_CMD_WRWORD | LP_GSEL; + *pRPS++ = RPS_LDREG | (P_DEBIAD >> 2); /* Write DEBI */ + /* immediate data to */ + /* shadow RAM. */ + *pRPS++ = LocalPPL; + *pRPS++ = RPS_CLRSIGNAL | RPS_DEBI; /* Reset "shadow RAM uploaded" */ + /* flag. */ + *pRPS++ = RPS_UPLOAD | RPS_DEBI; /* Invoke shadow RAM upload. */ + *pRPS++ = RPS_PAUSE | RPS_DEBI; /* Wait for shadow upload to */ + /* finish. */ + + /* Select ADC analog input channel. */ + *pRPS++ = RPS_LDREG | (P_DEBICMD >> 2); + /* Write DEBI command and address to shadow RAM. */ + *pRPS++ = DEBI_CMD_WRWORD | LP_ISEL; + *pRPS++ = RPS_LDREG | (P_DEBIAD >> 2); + /* Write DEBI immediate data to shadow RAM. */ + *pRPS++ = LocalPPL; + *pRPS++ = RPS_CLRSIGNAL | RPS_DEBI; + /* Reset "shadow RAM uploaded" flag. */ + + *pRPS++ = RPS_UPLOAD | RPS_DEBI; + /* Invoke shadow RAM upload. */ + + *pRPS++ = RPS_PAUSE | RPS_DEBI; + /* Wait for shadow upload to finish. */ + + /* Delay at least 10 microseconds for analog input settling. + * Instead of padding with NOPs, we use RPS_JUMP instructions + * here; this allows us to produce a longer delay than is + * possible with NOPs because each RPS_JUMP flushes the RPS' + * instruction prefetch pipeline. + */ + JmpAdrs = + (uint32_t) devpriv->RPSBuf.PhysicalBase + + (uint32_t) ((unsigned long)pRPS - + (unsigned long)devpriv->RPSBuf.LogicalBase); + for (i = 0; i < (10 * RPSCLK_PER_US / 2); i++) { + JmpAdrs += 8; /* Repeat to implement time delay: */ + *pRPS++ = RPS_JUMP; /* Jump to next RPS instruction. */ + *pRPS++ = JmpAdrs; + } + + if (cmd != NULL && cmd->convert_src != TRIG_NOW) { + DEBUG("ResetADC: convert pause inserted\n"); + /* Wait for Start trigger. */ + *pRPS++ = RPS_PAUSE | RPS_SIGADC; + *pRPS++ = RPS_CLRSIGNAL | RPS_SIGADC; + } + /* Start ADC by pulsing GPIO1. */ + *pRPS++ = RPS_LDREG | (P_GPIO >> 2); /* Begin ADC Start pulse. */ + *pRPS++ = GPIO_BASE | GPIO1_LO; + *pRPS++ = RPS_NOP; + /* VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. */ + *pRPS++ = RPS_LDREG | (P_GPIO >> 2); /* End ADC Start pulse. */ + *pRPS++ = GPIO_BASE | GPIO1_HI; + + /* Wait for ADC to complete (GPIO2 is asserted high when ADC not + * busy) and for data from previous conversion to shift into FB + * BUFFER 1 register. + */ + *pRPS++ = RPS_PAUSE | RPS_GPIO2; /* Wait for ADC done. */ + + /* Transfer ADC data from FB BUFFER 1 register to DMA buffer. */ + *pRPS++ = RPS_STREG | (BUGFIX_STREG(P_FB_BUFFER1) >> 2); + *pRPS++ = + (uint32_t) devpriv->ANABuf.PhysicalBase + + (devpriv->AdcItems << 2); + + /* If this slot's EndOfPollList flag is set, all channels have */ + /* now been processed. */ + if (*ppl++ & EOPL) { + devpriv->AdcItems++; /* Adjust poll list item count. */ + break; /* Exit poll list processing loop. */ + } + } + DEBUG("ResetADC: ADC items %d\n", devpriv->AdcItems); + + /* VERSION 2.01 CHANGE: DELAY CHANGED FROM 250NS to 2US. Allow the + * ADC to stabilize for 2 microseconds before starting the final + * (dummy) conversion. This delay is necessary to allow sufficient + * time between last conversion finished and the start of the dummy + * conversion. Without this delay, the last conversion's data value + * is sometimes set to the previous conversion's data value. + */ + for (n = 0; n < (2 * RPSCLK_PER_US); n++) + *pRPS++ = RPS_NOP; + + /* Start a dummy conversion to cause the data from the last + * conversion of interest to be shifted in. + */ + *pRPS++ = RPS_LDREG | (P_GPIO >> 2); /* Begin ADC Start pulse. */ + *pRPS++ = GPIO_BASE | GPIO1_LO; + *pRPS++ = RPS_NOP; + /* VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. */ + *pRPS++ = RPS_LDREG | (P_GPIO >> 2); /* End ADC Start pulse. */ + *pRPS++ = GPIO_BASE | GPIO1_HI; + + /* Wait for the data from the last conversion of interest to arrive + * in FB BUFFER 1 register. + */ + *pRPS++ = RPS_PAUSE | RPS_GPIO2; /* Wait for ADC done. */ + + /* Transfer final ADC data from FB BUFFER 1 register to DMA buffer. */ + *pRPS++ = RPS_STREG | (BUGFIX_STREG(P_FB_BUFFER1) >> 2); /* */ + *pRPS++ = + (uint32_t) devpriv->ANABuf.PhysicalBase + (devpriv->AdcItems << 2); + + /* Indicate ADC scan loop is finished. */ + /* *pRPS++= RPS_CLRSIGNAL | RPS_SIGADC ; // Signal ReadADC() that scan is done. */ + + /* invoke interrupt */ + if (devpriv->ai_cmd_running == 1) { + DEBUG("ResetADC: insert irq in ADC RPS task\n"); + *pRPS++ = RPS_IRQ; + } + /* Restart RPS program at its beginning. */ + *pRPS++ = RPS_JUMP; /* Branch to start of RPS program. */ + *pRPS++ = (uint32_t) devpriv->RPSBuf.PhysicalBase; + + /* End of RPS program build */ +} + +/* TO COMPLETE, IF NECESSARY */ +static int s626_ai_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + return -EINVAL; +} + +/* static int s626_ai_rinsn(struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data) */ +/* { */ +/* register uint8_t i; */ +/* register int32_t *readaddr; */ + +/* DEBUG("as626_ai_rinsn: ai_rinsn enter\n"); */ + +/* Trigger ADC scan loop start by setting RPS Signal 0. */ +/* MC_ENABLE( P_MC2, MC2_ADC_RPS ); */ + +/* Wait until ADC scan loop is finished (RPS Signal 0 reset). */ +/* while ( MC_TEST( P_MC2, MC2_ADC_RPS ) ); */ + +/* Init ptr to DMA buffer that holds new ADC data. We skip the + * first uint16_t in the buffer because it contains junk data from + * the final ADC of the previous poll list scan. + */ +/* readaddr = (uint32_t *)devpriv->ANABuf.LogicalBase + 1; */ + +/* Convert ADC data to 16-bit integer values and copy to application buffer. */ +/* for ( i = 0; i < devpriv->AdcItems; i++ ) { */ +/* *data = s626_ai_reg_to_uint( *readaddr++ ); */ +/* DEBUG("s626_ai_rinsn: data %d\n",*data); */ +/* data++; */ +/* } */ + +/* DEBUG("s626_ai_rinsn: ai_rinsn escape\n"); */ +/* return i; */ +/* } */ + +static int s626_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + uint16_t chan = CR_CHAN(insn->chanspec); + uint16_t range = CR_RANGE(insn->chanspec); + uint16_t AdcSpec = 0; + uint32_t GpioImage; + int n; + + /* interrupt call test */ +/* writel(IRQ_GPIO3,devpriv->base_addr+P_PSR); */ + /* Writing a logical 1 into any of the RPS_PSR bits causes the + * corresponding interrupt to be generated if enabled + */ + + DEBUG("s626_ai_insn_read: entering\n"); + + /* Convert application's ADC specification into form + * appropriate for register programming. + */ + if (range == 0) + AdcSpec = (chan << 8) | (GSEL_BIPOLAR5V); + else + AdcSpec = (chan << 8) | (GSEL_BIPOLAR10V); + + /* Switch ADC analog gain. */ + DEBIwrite(dev, LP_GSEL, AdcSpec); /* Set gain. */ + + /* Select ADC analog input channel. */ + DEBIwrite(dev, LP_ISEL, AdcSpec); /* Select channel. */ + + for (n = 0; n < insn->n; n++) { + + /* Delay 10 microseconds for analog input settling. */ + udelay(10); + + /* Start ADC by pulsing GPIO1 low. */ + GpioImage = RR7146(P_GPIO); + /* Assert ADC Start command */ + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + /* and stretch it out. */ + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + /* Negate ADC Start command. */ + WR7146(P_GPIO, GpioImage | GPIO1_HI); + + /* Wait for ADC to complete (GPIO2 is asserted high when */ + /* ADC not busy) and for data from previous conversion to */ + /* shift into FB BUFFER 1 register. */ + + /* Wait for ADC done. */ + while (!(RR7146(P_PSR) & PSR_GPIO2)) + ; + + /* Fetch ADC data. */ + if (n != 0) + data[n - 1] = s626_ai_reg_to_uint(RR7146(P_FB_BUFFER1)); + + /* Allow the ADC to stabilize for 4 microseconds before + * starting the next (final) conversion. This delay is + * necessary to allow sufficient time between last + * conversion finished and the start of the next + * conversion. Without this delay, the last conversion's + * data value is sometimes set to the previous + * conversion's data value. + */ + udelay(4); + } + + /* Start a dummy conversion to cause the data from the + * previous conversion to be shifted in. */ + GpioImage = RR7146(P_GPIO); + + /* Assert ADC Start command */ + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + /* and stretch it out. */ + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + /* Negate ADC Start command. */ + WR7146(P_GPIO, GpioImage | GPIO1_HI); + + /* Wait for the data to arrive in FB BUFFER 1 register. */ + + /* Wait for ADC done. */ + while (!(RR7146(P_PSR) & PSR_GPIO2)) + ; + + /* Fetch ADC data from audio interface's input shift register. */ + + /* Fetch ADC data. */ + if (n != 0) + data[n - 1] = s626_ai_reg_to_uint(RR7146(P_FB_BUFFER1)); + + DEBUG("s626_ai_insn_read: samples %d, data %d\n", n, data[n - 1]); + + return n; +} + +static int s626_ai_load_polllist(uint8_t *ppl, struct comedi_cmd *cmd) +{ + + int n; + + for (n = 0; n < cmd->chanlist_len; n++) { + if (CR_RANGE((cmd->chanlist)[n]) == 0) + ppl[n] = (CR_CHAN((cmd->chanlist)[n])) | (RANGE_5V); + else + ppl[n] = (CR_CHAN((cmd->chanlist)[n])) | (RANGE_10V); + } + if (n != 0) + ppl[n - 1] |= EOPL; + + return n; +} + +static int s626_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum) +{ + if (trignum != 0) + return -EINVAL; + + DEBUG("s626_ai_inttrig: trigger adc start..."); + + /* Start executing the RPS program. */ + MC_ENABLE(P_MC1, MC1_ERPS1); + + s->async->inttrig = NULL; + + DEBUG(" done\n"); + + return 1; +} + +/* TO COMPLETE */ +static int s626_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + + uint8_t ppl[16]; + struct comedi_cmd *cmd = &s->async->cmd; + struct enc_private *k; + int tick; + + DEBUG("s626_ai_cmd: entering command function\n"); + + if (devpriv->ai_cmd_running) { + printk(KERN_ERR "s626_ai_cmd: Another ai_cmd is running %d\n", + dev->minor); + return -EBUSY; + } + /* disable interrupt */ + writel(0, devpriv->base_addr + P_IER); + + /* clear interrupt request */ + writel(IRQ_RPS1 | IRQ_GPIO3, devpriv->base_addr + P_ISR); + + /* clear any pending interrupt */ + s626_dio_clear_irq(dev); + /* s626_enc_clear_irq(dev); */ + + /* reset ai_cmd_running flag */ + devpriv->ai_cmd_running = 0; + + /* test if cmd is valid */ + if (cmd == NULL) { + DEBUG("s626_ai_cmd: NULL command\n"); + return -EINVAL; + } else { + DEBUG("s626_ai_cmd: command received!!!\n"); + } + + if (dev->irq == 0) { + comedi_error(dev, + "s626_ai_cmd: cannot run command without an irq"); + return -EIO; + } + + s626_ai_load_polllist(ppl, cmd); + devpriv->ai_cmd_running = 1; + devpriv->ai_convert_count = 0; + + switch (cmd->scan_begin_src) { + case TRIG_FOLLOW: + break; + case TRIG_TIMER: + /* set a conter to generate adc trigger at scan_begin_arg interval */ + k = &encpriv[5]; + tick = s626_ns_to_timer((int *)&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + + /* load timer value and enable interrupt */ + s626_timer_load(dev, k, tick); + k->SetEnable(dev, k, CLKENAB_ALWAYS); + + DEBUG("s626_ai_cmd: scan trigger timer is set with value %d\n", + tick); + + break; + case TRIG_EXT: + /* set the digital line and interrupt for scan trigger */ + if (cmd->start_src != TRIG_EXT) + s626_dio_set_irq(dev, cmd->scan_begin_arg); + + DEBUG("s626_ai_cmd: External scan trigger is set!!!\n"); + + break; + } + + switch (cmd->convert_src) { + case TRIG_NOW: + break; + case TRIG_TIMER: + /* set a conter to generate adc trigger at convert_arg interval */ + k = &encpriv[4]; + tick = s626_ns_to_timer((int *)&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + + /* load timer value and enable interrupt */ + s626_timer_load(dev, k, tick); + k->SetEnable(dev, k, CLKENAB_INDEX); + + DEBUG + ("s626_ai_cmd: convert trigger timer is set with value %d\n", + tick); + break; + case TRIG_EXT: + /* set the digital line and interrupt for convert trigger */ + if (cmd->scan_begin_src != TRIG_EXT + && cmd->start_src == TRIG_EXT) + s626_dio_set_irq(dev, cmd->convert_arg); + + DEBUG("s626_ai_cmd: External convert trigger is set!!!\n"); + + break; + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + /* data arrives as one packet */ + devpriv->ai_sample_count = cmd->stop_arg; + devpriv->ai_continous = 0; + break; + case TRIG_NONE: + /* continous acquisition */ + devpriv->ai_continous = 1; + devpriv->ai_sample_count = 0; + break; + } + + ResetADC(dev, ppl); + + switch (cmd->start_src) { + case TRIG_NOW: + /* Trigger ADC scan loop start by setting RPS Signal 0. */ + /* MC_ENABLE( P_MC2, MC2_ADC_RPS ); */ + + /* Start executing the RPS program. */ + MC_ENABLE(P_MC1, MC1_ERPS1); + + DEBUG("s626_ai_cmd: ADC triggered\n"); + s->async->inttrig = NULL; + break; + case TRIG_EXT: + /* configure DIO channel for acquisition trigger */ + s626_dio_set_irq(dev, cmd->start_arg); + + DEBUG("s626_ai_cmd: External start trigger is set!!!\n"); + + s->async->inttrig = NULL; + break; + case TRIG_INT: + s->async->inttrig = s626_ai_inttrig; + break; + } + + /* enable interrupt */ + writel(IRQ_GPIO3 | IRQ_RPS1, devpriv->base_addr + P_IER); + + DEBUG("s626_ai_cmd: command function terminated\n"); + + return 0; +} + +static int s626_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* cmdtest tests a particular command to see if it is valid. Using + * the cmdtest ioctl, a user can create a valid cmd and then have it + * executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests the + * command passes. */ + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT | TRIG_EXT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT | TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT | TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually + compatible */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT + && cmd->scan_begin_src != TRIG_FOLLOW) + err++; + if (cmd->convert_src != TRIG_TIMER && + cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_src != TRIG_EXT && cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->start_src == TRIG_EXT && cmd->start_arg > 39) { + cmd->start_arg = 39; + err++; + } + + if (cmd->scan_begin_src == TRIG_EXT && cmd->scan_begin_arg > 39) { + cmd->scan_begin_arg = 39; + err++; + } + + if (cmd->convert_src == TRIG_EXT && cmd->convert_arg > 39) { + cmd->convert_arg = 39; + err++; + } +#define MAX_SPEED 200000 /* in nanoseconds */ +#define MIN_SPEED 2000000000 /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < MAX_SPEED) { + cmd->scan_begin_arg = MAX_SPEED; + err++; + } + if (cmd->scan_begin_arg > MIN_SPEED) { + cmd->scan_begin_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ +/* if(cmd->scan_begin_arg>9){ */ +/* cmd->scan_begin_arg=9; */ +/* err++; */ +/* } */ + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < MAX_SPEED) { + cmd->convert_arg = MAX_SPEED; + err++; + } + if (cmd->convert_arg > MIN_SPEED) { + cmd->convert_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* see above */ +/* if(cmd->convert_arg>9){ */ +/* cmd->convert_arg=9; */ +/* err++; */ +/* } */ + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg > 0x00ffffff) { + cmd->stop_arg = 0x00ffffff; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + s626_ns_to_timer((int *)&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + s626_ns_to_timer((int *)&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if (err) + return 4; + + return 0; +} + +static int s626_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + /* Stop RPS program in case it is currently running. */ + MC_DISABLE(P_MC1, MC1_ERPS1); + + /* disable master interrupt */ + writel(0, devpriv->base_addr + P_IER); + + devpriv->ai_cmd_running = 0; + + return 0; +} + +/* This function doesn't require a particular form, this is just what + * happens to be used in some of the drivers. It should convert ns + * nanoseconds to a counter value suitable for programming the device. + * Also, it should adjust ns so that it cooresponds to the actual time + * that the device will use. */ +static int s626_ns_to_timer(int *nanosec, int round_mode) +{ + int divider, base; + + base = 500; /* 2MHz internal clock */ + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + + *nanosec = base * divider; + return divider - 1; +} + +static int s626_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + int i; + uint16_t chan = CR_CHAN(insn->chanspec); + int16_t dacdata; + + for (i = 0; i < insn->n; i++) { + dacdata = (int16_t) data[i]; + devpriv->ao_readback[CR_CHAN(insn->chanspec)] = data[i]; + dacdata -= (0x1fff); + + SetDAC(dev, chan, dacdata); + } + + return i; +} + +static int s626_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + + return i; +} + +/* *************** DIGITAL I/O FUNCTIONS *************** + * All DIO functions address a group of DIO channels by means of + * "group" argument. group may be 0, 1 or 2, which correspond to DIO + * ports A, B and C, respectively. + */ + +static void s626_dio_init(struct comedi_device *dev) +{ + uint16_t group; + struct comedi_subdevice *s; + + /* Prepare to treat writes to WRCapSel as capture disables. */ + DEBIwrite(dev, LP_MISC1, MISC1_NOEDCAP); + + /* For each group of sixteen channels ... */ + for (group = 0; group < S626_DIO_BANKS; group++) { + s = dev->subdevices + 2 + group; + DEBIwrite(dev, diopriv->WRIntSel, 0); /* Disable all interrupts. */ + DEBIwrite(dev, diopriv->WRCapSel, 0xFFFF); /* Disable all event */ + /* captures. */ + DEBIwrite(dev, diopriv->WREdgSel, 0); /* Init all DIOs to */ + /* default edge */ + /* polarity. */ + DEBIwrite(dev, diopriv->WRDOut, 0); /* Program all outputs */ + /* to inactive state. */ + } + DEBUG("s626_dio_init: DIO initialized\n"); +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The comedi + * core can convert between insn_bits and insn_read/write */ + +static int s626_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + /* Length of data must be 2 (mask and new data, see below) */ + if (insn->n == 0) + return 0; + + if (insn->n != 2) { + printk + ("comedi%d: s626: s626_dio_insn_bits(): Invalid instruction length\n", + dev->minor); + return -EINVAL; + } + + /* + * The insn data consists of a mask in data[0] and the new data in + * data[1]. The mask defines which bits we are concerning about. + * The new data must be anded with the mask. Each channel + * corresponds to a bit. + */ + if (data[0]) { + /* Check if requested ports are configured for output */ + if ((s->io_bits & data[0]) != data[0]) + return -EIO; + + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + + /* Write out the new digital output lines */ + + DEBIwrite(dev, diopriv->WRDOut, s->state); + } + data[1] = DEBIread(dev, diopriv->RDDIn); + + return 2; +} + +static int s626_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + switch (data[0]) { + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s-> + io_bits & (1 << CR_CHAN(insn->chanspec))) ? COMEDI_OUTPUT : + COMEDI_INPUT; + return insn->n; + break; + case COMEDI_INPUT: + s->io_bits &= ~(1 << CR_CHAN(insn->chanspec)); + break; + case COMEDI_OUTPUT: + s->io_bits |= 1 << CR_CHAN(insn->chanspec); + break; + default: + return -EINVAL; + break; + } + DEBIwrite(dev, diopriv->WRDOut, s->io_bits); + + return 1; +} + +static int s626_dio_set_irq(struct comedi_device *dev, unsigned int chan) +{ + unsigned int group; + unsigned int bitmask; + unsigned int status; + + /* select dio bank */ + group = chan / 16; + bitmask = 1 << (chan - (16 * group)); + DEBUG("s626_dio_set_irq: enable interrupt on dio channel %d group %d\n", + chan - (16 * group), group); + + /* set channel to capture positive edge */ + status = DEBIread(dev, + ((struct dio_private *)(dev->subdevices + 2 + + group)->private)->RDEdgSel); + DEBIwrite(dev, + ((struct dio_private *)(dev->subdevices + 2 + + group)->private)->WREdgSel, + bitmask | status); + + /* enable interrupt on selected channel */ + status = DEBIread(dev, + ((struct dio_private *)(dev->subdevices + 2 + + group)->private)->RDIntSel); + DEBIwrite(dev, + ((struct dio_private *)(dev->subdevices + 2 + + group)->private)->WRIntSel, + bitmask | status); + + /* enable edge capture write command */ + DEBIwrite(dev, LP_MISC1, MISC1_EDCAP); + + /* enable edge capture on selected channel */ + status = DEBIread(dev, + ((struct dio_private *)(dev->subdevices + 2 + + group)->private)->RDCapSel); + DEBIwrite(dev, + ((struct dio_private *)(dev->subdevices + 2 + + group)->private)->WRCapSel, + bitmask | status); + + return 0; +} + +static int s626_dio_reset_irq(struct comedi_device *dev, unsigned int group, + unsigned int mask) +{ + DEBUG + ("s626_dio_reset_irq: disable interrupt on dio channel %d group %d\n", + mask, group); + + /* disable edge capture write command */ + DEBIwrite(dev, LP_MISC1, MISC1_NOEDCAP); + + /* enable edge capture on selected channel */ + DEBIwrite(dev, + ((struct dio_private *)(dev->subdevices + 2 + + group)->private)->WRCapSel, mask); + + return 0; +} + +static int s626_dio_clear_irq(struct comedi_device *dev) +{ + unsigned int group; + + /* disable edge capture write command */ + DEBIwrite(dev, LP_MISC1, MISC1_NOEDCAP); + + for (group = 0; group < S626_DIO_BANKS; group++) { + /* clear pending events and interrupt */ + DEBIwrite(dev, + ((struct dio_private *)(dev->subdevices + 2 + + group)->private)->WRCapSel, + 0xffff); + } + + return 0; +} + +/* Now this function initializes the value of the counter (data[0]) + and set the subdevice. To complete with trigger and interrupt + configuration */ +static int s626_enc_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + uint16_t Setup = (LOADSRC_INDX << BF_LOADSRC) | /* Preload upon */ + /* index. */ + (INDXSRC_SOFT << BF_INDXSRC) | /* Disable hardware index. */ + (CLKSRC_COUNTER << BF_CLKSRC) | /* Operating mode is Counter. */ + (CLKPOL_POS << BF_CLKPOL) | /* Active high clock. */ + /* ( CNTDIR_UP << BF_CLKPOL ) | // Count direction is Down. */ + (CLKMULT_1X << BF_CLKMULT) | /* Clock multiplier is 1x. */ + (CLKENAB_INDEX << BF_CLKENAB); + /* uint16_t DisableIntSrc=TRUE; */ + /* uint32_t Preloadvalue; //Counter initial value */ + uint16_t valueSrclatch = LATCHSRC_AB_READ; + uint16_t enab = CLKENAB_ALWAYS; + struct enc_private *k = &encpriv[CR_CHAN(insn->chanspec)]; + + DEBUG("s626_enc_insn_config: encoder config\n"); + + /* (data==NULL) ? (Preloadvalue=0) : (Preloadvalue=data[0]); */ + + k->SetMode(dev, k, Setup, TRUE); + Preload(dev, k, *(insn->data)); + k->PulseIndex(dev, k); + SetLatchSource(dev, k, valueSrclatch); + k->SetEnable(dev, k, (uint16_t) (enab != 0)); + + return insn->n; +} + +static int s626_enc_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + int n; + struct enc_private *k = &encpriv[CR_CHAN(insn->chanspec)]; + + DEBUG("s626_enc_insn_read: encoder read channel %d\n", + CR_CHAN(insn->chanspec)); + + for (n = 0; n < insn->n; n++) + data[n] = ReadLatch(dev, k); + + DEBUG("s626_enc_insn_read: encoder sample %d\n", data[n]); + + return n; +} + +static int s626_enc_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + struct enc_private *k = &encpriv[CR_CHAN(insn->chanspec)]; + + DEBUG("s626_enc_insn_write: encoder write channel %d\n", + CR_CHAN(insn->chanspec)); + + /* Set the preload register */ + Preload(dev, k, data[0]); + + /* Software index pulse forces the preload register to load */ + /* into the counter */ + k->SetLoadTrig(dev, k, 0); + k->PulseIndex(dev, k); + k->SetLoadTrig(dev, k, 2); + + DEBUG("s626_enc_insn_write: End encoder write\n"); + + return 1; +} + +static void s626_timer_load(struct comedi_device *dev, struct enc_private *k, + int tick) +{ + uint16_t Setup = (LOADSRC_INDX << BF_LOADSRC) | /* Preload upon */ + /* index. */ + (INDXSRC_SOFT << BF_INDXSRC) | /* Disable hardware index. */ + (CLKSRC_TIMER << BF_CLKSRC) | /* Operating mode is Timer. */ + (CLKPOL_POS << BF_CLKPOL) | /* Active high clock. */ + (CNTDIR_DOWN << BF_CLKPOL) | /* Count direction is Down. */ + (CLKMULT_1X << BF_CLKMULT) | /* Clock multiplier is 1x. */ + (CLKENAB_INDEX << BF_CLKENAB); + uint16_t valueSrclatch = LATCHSRC_A_INDXA; + /* uint16_t enab=CLKENAB_ALWAYS; */ + + k->SetMode(dev, k, Setup, FALSE); + + /* Set the preload register */ + Preload(dev, k, tick); + + /* Software index pulse forces the preload register to load */ + /* into the counter */ + k->SetLoadTrig(dev, k, 0); + k->PulseIndex(dev, k); + + /* set reload on counter overflow */ + k->SetLoadTrig(dev, k, 1); + + /* set interrupt on overflow */ + k->SetIntSrc(dev, k, INTSRC_OVER); + + SetLatchSource(dev, k, valueSrclatch); + /* k->SetEnable(dev,k,(uint16_t)(enab != 0)); */ +} + +/* *********** DAC FUNCTIONS *********** */ + +/* Slot 0 base settings. */ +#define VECT0 (XSD2 | RSD3 | SIB_A2) +/* Slot 0 always shifts in 0xFF and store it to FB_BUFFER2. */ + +/* TrimDac LogicalChan-to-PhysicalChan mapping table. */ +static uint8_t trimchan[] = { 10, 9, 8, 3, 2, 7, 6, 1, 0, 5, 4 }; + +/* TrimDac LogicalChan-to-EepromAdrs mapping table. */ +static uint8_t trimadrs[] = { 0x40, 0x41, 0x42, 0x50, 0x51, 0x52, 0x53, 0x60, 0x61, 0x62, 0x63 }; + +static void LoadTrimDACs(struct comedi_device *dev) +{ + register uint8_t i; + + /* Copy TrimDac setpoint values from EEPROM to TrimDacs. */ + for (i = 0; i < ARRAY_SIZE(trimchan); i++) + WriteTrimDAC(dev, i, I2Cread(dev, trimadrs[i])); +} + +static void WriteTrimDAC(struct comedi_device *dev, uint8_t LogicalChan, + uint8_t DacData) +{ + uint32_t chan; + + /* Save the new setpoint in case the application needs to read it back later. */ + devpriv->TrimSetpoint[LogicalChan] = (uint8_t) DacData; + + /* Map logical channel number to physical channel number. */ + chan = (uint32_t) trimchan[LogicalChan]; + + /* Set up TSL2 records for TrimDac write operation. All slots shift + * 0xFF in from pulled-up SD3 so that the end of the slot sequence + * can be detected. + */ + + SETVECT(2, XSD2 | XFIFO_1 | WS3); + /* Slot 2: Send high uint8_t to target TrimDac. */ + SETVECT(3, XSD2 | XFIFO_0 | WS3); + /* Slot 3: Send low uint8_t to target TrimDac. */ + SETVECT(4, XSD2 | XFIFO_3 | WS1); + /* Slot 4: Send NOP high uint8_t to DAC0 to keep clock running. */ + SETVECT(5, XSD2 | XFIFO_2 | WS1 | EOS); + /* Slot 5: Send NOP low uint8_t to DAC0. */ + + /* Construct and transmit target DAC's serial packet: + * ( 0000 AAAA ), ( DDDD DDDD ),( 0x00 ),( 0x00 ) where A<3:0> is the + * DAC channel's address, and D<7:0> is the DAC setpoint. Append a + * WORD value (that writes a channel 0 NOP command to a non-existent + * main DAC channel) that serves to keep the clock running after the + * packet has been sent to the target DAC. + */ + + /* Address the DAC channel within the trimdac device. */ + SendDAC(dev, ((uint32_t) chan << 8) + | (uint32_t) DacData); /* Include DAC setpoint data. */ +} + +/* ************** EEPROM ACCESS FUNCTIONS ************** */ +/* Read uint8_t from EEPROM. */ + +static uint8_t I2Cread(struct comedi_device *dev, uint8_t addr) +{ + uint8_t rtnval; + + /* Send EEPROM target address. */ + if (I2Chandshake(dev, I2C_B2(I2C_ATTRSTART, I2CW) + /* Byte2 = I2C command: write to I2C EEPROM device. */ + | I2C_B1(I2C_ATTRSTOP, addr) + /* Byte1 = EEPROM internal target address. */ + | I2C_B0(I2C_ATTRNOP, 0))) { /* Byte0 = Not sent. */ + /* Abort function and declare error if handshake failed. */ + DEBUG("I2Cread: error handshake I2Cread a\n"); + return 0; + } + /* Execute EEPROM read. */ + if (I2Chandshake(dev, I2C_B2(I2C_ATTRSTART, I2CR) + + /* Byte2 = I2C */ + /* command: read */ + /* from I2C EEPROM */ + /* device. */ + |I2C_B1(I2C_ATTRSTOP, 0) + + /* Byte1 receives */ + /* uint8_t from */ + /* EEPROM. */ + |I2C_B0(I2C_ATTRNOP, 0))) { /* Byte0 = Not sent. */ + + /* Abort function and declare error if handshake failed. */ + DEBUG("I2Cread: error handshake I2Cread b\n"); + return 0; + } + /* Return copy of EEPROM value. */ + rtnval = (uint8_t) (RR7146(P_I2CCTRL) >> 16); + return rtnval; +} + +static uint32_t I2Chandshake(struct comedi_device *dev, uint32_t val) +{ + /* Write I2C command to I2C Transfer Control shadow register. */ + WR7146(P_I2CCTRL, val); + + /* Upload I2C shadow registers into working registers and wait for */ + /* upload confirmation. */ + + MC_ENABLE(P_MC2, MC2_UPLD_IIC); + while (!MC_TEST(P_MC2, MC2_UPLD_IIC)) + ; + + /* Wait until I2C bus transfer is finished or an error occurs. */ + while ((RR7146(P_I2CCTRL) & (I2C_BUSY | I2C_ERR)) == I2C_BUSY) + ; + + /* Return non-zero if I2C error occurred. */ + return RR7146(P_I2CCTRL) & I2C_ERR; + +} + +/* Private helper function: Write setpoint to an application DAC channel. */ + +static void SetDAC(struct comedi_device *dev, uint16_t chan, short dacdata) +{ + register uint16_t signmask; + register uint32_t WSImage; + + /* Adjust DAC data polarity and set up Polarity Control Register */ + /* image. */ + signmask = 1 << chan; + if (dacdata < 0) { + dacdata = -dacdata; + devpriv->Dacpol |= signmask; + } else + devpriv->Dacpol &= ~signmask; + + /* Limit DAC setpoint value to valid range. */ + if ((uint16_t) dacdata > 0x1FFF) + dacdata = 0x1FFF; + + /* Set up TSL2 records (aka "vectors") for DAC update. Vectors V2 + * and V3 transmit the setpoint to the target DAC. V4 and V5 send + * data to a non-existent TrimDac channel just to keep the clock + * running after sending data to the target DAC. This is necessary + * to eliminate the clock glitch that would otherwise occur at the + * end of the target DAC's serial data stream. When the sequence + * restarts at V0 (after executing V5), the gate array automatically + * disables gating for the DAC clock and all DAC chip selects. + */ + + WSImage = (chan & 2) ? WS1 : WS2; + /* Choose DAC chip select to be asserted. */ + SETVECT(2, XSD2 | XFIFO_1 | WSImage); + /* Slot 2: Transmit high data byte to target DAC. */ + SETVECT(3, XSD2 | XFIFO_0 | WSImage); + /* Slot 3: Transmit low data byte to target DAC. */ + SETVECT(4, XSD2 | XFIFO_3 | WS3); + /* Slot 4: Transmit to non-existent TrimDac channel to keep clock */ + SETVECT(5, XSD2 | XFIFO_2 | WS3 | EOS); + /* Slot 5: running after writing target DAC's low data byte. */ + + /* Construct and transmit target DAC's serial packet: + * ( A10D DDDD ),( DDDD DDDD ),( 0x0F ),( 0x00 ) where A is chan<0>, + * and D<12:0> is the DAC setpoint. Append a WORD value (that writes + * to a non-existent TrimDac channel) that serves to keep the clock + * running after the packet has been sent to the target DAC. + */ + SendDAC(dev, 0x0F000000 + /* Continue clock after target DAC data (write to non-existent trimdac). */ + | 0x00004000 + /* Address the two main dual-DAC devices (TSL's chip select enables + * target device). */ + | ((uint32_t) (chan & 1) << 15) + /* Address the DAC channel within the device. */ + | (uint32_t) dacdata); /* Include DAC setpoint data. */ + +} + +/* Private helper function: Transmit serial data to DAC via Audio + * channel 2. Assumes: (1) TSL2 slot records initialized, and (2) + * Dacpol contains valid target image. + */ + +static void SendDAC(struct comedi_device *dev, uint32_t val) +{ + + /* START THE SERIAL CLOCK RUNNING ------------- */ + + /* Assert DAC polarity control and enable gating of DAC serial clock + * and audio bit stream signals. At this point in time we must be + * assured of being in time slot 0. If we are not in slot 0, the + * serial clock and audio stream signals will be disabled; this is + * because the following DEBIwrite statement (which enables signals + * to be passed through the gate array) would execute before the + * trailing edge of WS1/WS3 (which turns off the signals), thus + * causing the signals to be inactive during the DAC write. + */ + DEBIwrite(dev, LP_DACPOL, devpriv->Dacpol); + + /* TRANSFER OUTPUT DWORD VALUE INTO A2'S OUTPUT FIFO ---------------- */ + + /* Copy DAC setpoint value to DAC's output DMA buffer. */ + + /* WR7146( (uint32_t)devpriv->pDacWBuf, val ); */ + *devpriv->pDacWBuf = val; + + /* enab the output DMA transfer. This will cause the DMAC to copy + * the DAC's data value to A2's output FIFO. The DMA transfer will + * then immediately terminate because the protection address is + * reached upon transfer of the first DWORD value. + */ + MC_ENABLE(P_MC1, MC1_A2OUT); + + /* While the DMA transfer is executing ... */ + + /* Reset Audio2 output FIFO's underflow flag (along with any other + * FIFO underflow/overflow flags). When set, this flag will + * indicate that we have emerged from slot 0. + */ + WR7146(P_ISR, ISR_AFOU); + + /* Wait for the DMA transfer to finish so that there will be data + * available in the FIFO when time slot 1 tries to transfer a DWORD + * from the FIFO to the output buffer register. We test for DMA + * Done by polling the DMAC enable flag; this flag is automatically + * cleared when the transfer has finished. + */ + while ((RR7146(P_MC1) & MC1_A2OUT) != 0) + ; + + /* START THE OUTPUT STREAM TO THE TARGET DAC -------------------- */ + + /* FIFO data is now available, so we enable execution of time slots + * 1 and higher by clearing the EOS flag in slot 0. Note that SD3 + * will be shifted in and stored in FB_BUFFER2 for end-of-slot-list + * detection. + */ + SETVECT(0, XSD2 | RSD3 | SIB_A2); + + /* Wait for slot 1 to execute to ensure that the Packet will be + * transmitted. This is detected by polling the Audio2 output FIFO + * underflow flag, which will be set when slot 1 execution has + * finished transferring the DAC's data DWORD from the output FIFO + * to the output buffer register. + */ + while ((RR7146(P_SSR) & SSR_AF2_OUT) == 0) + ; + + /* Set up to trap execution at slot 0 when the TSL sequencer cycles + * back to slot 0 after executing the EOS in slot 5. Also, + * simultaneously shift out and in the 0x00 that is ALWAYS the value + * stored in the last byte to be shifted out of the FIFO's DWORD + * buffer register. + */ + SETVECT(0, XSD2 | XFIFO_2 | RSD2 | SIB_A2 | EOS); + + /* WAIT FOR THE TRANSACTION TO FINISH ----------------------- */ + + /* Wait for the TSL to finish executing all time slots before + * exiting this function. We must do this so that the next DAC + * write doesn't start, thereby enabling clock/chip select signals: + * + * 1. Before the TSL sequence cycles back to slot 0, which disables + * the clock/cs signal gating and traps slot // list execution. + * we have not yet finished slot 5 then the clock/cs signals are + * still gated and we have not finished transmitting the stream. + * + * 2. While slots 2-5 are executing due to a late slot 0 trap. In + * this case, the slot sequence is currently repeating, but with + * clock/cs signals disabled. We must wait for slot 0 to trap + * execution before setting up the next DAC setpoint DMA transfer + * and enabling the clock/cs signals. To detect the end of slot 5, + * we test for the FB_BUFFER2 MSB contents to be equal to 0xFF. If + * the TSL has not yet finished executing slot 5 ... + */ + if ((RR7146(P_FB_BUFFER2) & 0xFF000000) != 0) { + /* The trap was set on time and we are still executing somewhere + * in slots 2-5, so we now wait for slot 0 to execute and trap + * TSL execution. This is detected when FB_BUFFER2 MSB changes + * from 0xFF to 0x00, which slot 0 causes to happen by shifting + * out/in on SD2 the 0x00 that is always referenced by slot 5. + */ + while ((RR7146(P_FB_BUFFER2) & 0xFF000000) != 0) + ; + } + /* Either (1) we were too late setting the slot 0 trap; the TSL + * sequencer restarted slot 0 before we could set the EOS trap flag, + * or (2) we were not late and execution is now trapped at slot 0. + * In either case, we must now change slot 0 so that it will store + * value 0xFF (instead of 0x00) to FB_BUFFER2 next time it executes. + * In order to do this, we reprogram slot 0 so that it will shift in + * SD3, which is driven only by a pull-up resistor. + */ + SETVECT(0, RSD3 | SIB_A2 | EOS); + + /* Wait for slot 0 to execute, at which time the TSL is setup for + * the next DAC write. This is detected when FB_BUFFER2 MSB changes + * from 0x00 to 0xFF. + */ + while ((RR7146(P_FB_BUFFER2) & 0xFF000000) == 0) + ; +} + +static void WriteMISC2(struct comedi_device *dev, uint16_t NewImage) +{ + DEBIwrite(dev, LP_MISC1, MISC1_WENABLE); /* enab writes to */ + /* MISC2 register. */ + DEBIwrite(dev, LP_WRMISC2, NewImage); /* Write new image to MISC2. */ + DEBIwrite(dev, LP_MISC1, MISC1_WDISABLE); /* Disable writes to MISC2. */ +} + +/* Initialize the DEBI interface for all transfers. */ + +static uint16_t DEBIread(struct comedi_device *dev, uint16_t addr) +{ + uint16_t retval; + + /* Set up DEBI control register value in shadow RAM. */ + WR7146(P_DEBICMD, DEBI_CMD_RDWORD | addr); + + /* Execute the DEBI transfer. */ + DEBItransfer(dev); + + /* Fetch target register value. */ + retval = (uint16_t) RR7146(P_DEBIAD); + + /* Return register value. */ + return retval; +} + +/* Execute a DEBI transfer. This must be called from within a */ +/* critical section. */ +static void DEBItransfer(struct comedi_device *dev) +{ + /* Initiate upload of shadow RAM to DEBI control register. */ + MC_ENABLE(P_MC2, MC2_UPLD_DEBI); + + /* Wait for completion of upload from shadow RAM to DEBI control */ + /* register. */ + while (!MC_TEST(P_MC2, MC2_UPLD_DEBI)) + ; + + /* Wait until DEBI transfer is done. */ + while (RR7146(P_PSR) & PSR_DEBI_S) + ; +} + +/* Write a value to a gate array register. */ +static void DEBIwrite(struct comedi_device *dev, uint16_t addr, uint16_t wdata) +{ + + /* Set up DEBI control register value in shadow RAM. */ + WR7146(P_DEBICMD, DEBI_CMD_WRWORD | addr); + WR7146(P_DEBIAD, wdata); + + /* Execute the DEBI transfer. */ + DEBItransfer(dev); +} + +/* Replace the specified bits in a gate array register. Imports: mask + * specifies bits that are to be preserved, wdata is new value to be + * or'd with the masked original. + */ +static void DEBIreplace(struct comedi_device *dev, uint16_t addr, uint16_t mask, + uint16_t wdata) +{ + + /* Copy target gate array register into P_DEBIAD register. */ + WR7146(P_DEBICMD, DEBI_CMD_RDWORD | addr); + /* Set up DEBI control reg value in shadow RAM. */ + DEBItransfer(dev); /* Execute the DEBI Read transfer. */ + + /* Write back the modified image. */ + WR7146(P_DEBICMD, DEBI_CMD_WRWORD | addr); + /* Set up DEBI control reg value in shadow RAM. */ + + WR7146(P_DEBIAD, wdata | ((uint16_t) RR7146(P_DEBIAD) & mask)); + /* Modify the register image. */ + DEBItransfer(dev); /* Execute the DEBI Write transfer. */ +} + +static void CloseDMAB(struct comedi_device *dev, struct bufferDMA *pdma, + size_t bsize) +{ + void *vbptr; + dma_addr_t vpptr; + + DEBUG("CloseDMAB: Entering S626DRV_CloseDMAB():\n"); + if (pdma == NULL) + return; + /* find the matching allocation from the board struct */ + + vbptr = pdma->LogicalBase; + vpptr = pdma->PhysicalBase; + if (vbptr) { + pci_free_consistent(devpriv->pdev, bsize, vbptr, vpptr); + pdma->LogicalBase = 0; + pdma->PhysicalBase = 0; + + DEBUG("CloseDMAB(): Logical=%p, bsize=%d, Physical=0x%x\n", + vbptr, bsize, (uint32_t) vpptr); + } +} + +/* ****** COUNTER FUNCTIONS ******* */ +/* All counter functions address a specific counter by means of the + * "Counter" argument, which is a logical counter number. The Counter + * argument may have any of the following legal values: 0=0A, 1=1A, + * 2=2A, 3=0B, 4=1B, 5=2B. + */ + +/* Forward declarations for functions that are common to both A and B counters: */ + +/* ****** PRIVATE COUNTER FUNCTIONS ****** */ + +/* Read a counter's output latch. */ + +static uint32_t ReadLatch(struct comedi_device *dev, struct enc_private *k) +{ + register uint32_t value; + /* DEBUG FIXME DEBUG("ReadLatch: Read Latch enter\n"); */ + + /* Latch counts and fetch LSW of latched counts value. */ + value = (uint32_t) DEBIread(dev, k->MyLatchLsw); + + /* Fetch MSW of latched counts and combine with LSW. */ + value |= ((uint32_t) DEBIread(dev, k->MyLatchLsw + 2) << 16); + + /* DEBUG FIXME DEBUG("ReadLatch: Read Latch exit\n"); */ + + /* Return latched counts. */ + return value; +} + +/* Reset a counter's index and overflow event capture flags. */ + +static void ResetCapFlags_A(struct comedi_device *dev, struct enc_private *k) +{ + DEBIreplace(dev, k->MyCRB, (uint16_t) (~CRBMSK_INTCTRL), + CRBMSK_INTRESETCMD | CRBMSK_INTRESET_A); +} + +static void ResetCapFlags_B(struct comedi_device *dev, struct enc_private *k) +{ + DEBIreplace(dev, k->MyCRB, (uint16_t) (~CRBMSK_INTCTRL), + CRBMSK_INTRESETCMD | CRBMSK_INTRESET_B); +} + +/* Return counter setup in a format (COUNTER_SETUP) that is consistent */ +/* for both A and B counters. */ + +static uint16_t GetMode_A(struct comedi_device *dev, struct enc_private *k) +{ + register uint16_t cra; + register uint16_t crb; + register uint16_t setup; + + /* Fetch CRA and CRB register images. */ + cra = DEBIread(dev, k->MyCRA); + crb = DEBIread(dev, k->MyCRB); + + /* Populate the standardized counter setup bit fields. Note: */ + /* IndexSrc is restricted to ENC_X or IndxPol. */ + setup = ((cra & STDMSK_LOADSRC) /* LoadSrc = LoadSrcA. */ + |((crb << (STDBIT_LATCHSRC - CRBBIT_LATCHSRC)) & STDMSK_LATCHSRC) /* LatchSrc = LatchSrcA. */ + |((cra << (STDBIT_INTSRC - CRABIT_INTSRC_A)) & STDMSK_INTSRC) /* IntSrc = IntSrcA. */ + |((cra << (STDBIT_INDXSRC - (CRABIT_INDXSRC_A + 1))) & STDMSK_INDXSRC) /* IndxSrc = IndxSrcA<1>. */ + |((cra >> (CRABIT_INDXPOL_A - STDBIT_INDXPOL)) & STDMSK_INDXPOL) /* IndxPol = IndxPolA. */ + |((crb >> (CRBBIT_CLKENAB_A - STDBIT_CLKENAB)) & STDMSK_CLKENAB)); /* ClkEnab = ClkEnabA. */ + + /* Adjust mode-dependent parameters. */ + if (cra & (2 << CRABIT_CLKSRC_A)) /* If Timer mode (ClkSrcA<1> == 1): */ + setup |= ((CLKSRC_TIMER << STDBIT_CLKSRC) /* Indicate Timer mode. */ + |((cra << (STDBIT_CLKPOL - CRABIT_CLKSRC_A)) & STDMSK_CLKPOL) /* Set ClkPol to indicate count direction (ClkSrcA<0>). */ + |(MULT_X1 << STDBIT_CLKMULT)); /* ClkMult must be 1x in Timer mode. */ + + else /* If Counter mode (ClkSrcA<1> == 0): */ + setup |= ((CLKSRC_COUNTER << STDBIT_CLKSRC) /* Indicate Counter mode. */ + |((cra >> (CRABIT_CLKPOL_A - STDBIT_CLKPOL)) & STDMSK_CLKPOL) /* Pass through ClkPol. */ + |(((cra & CRAMSK_CLKMULT_A) == (MULT_X0 << CRABIT_CLKMULT_A)) ? /* Force ClkMult to 1x if not legal, else pass through. */ + (MULT_X1 << STDBIT_CLKMULT) : + ((cra >> (CRABIT_CLKMULT_A - + STDBIT_CLKMULT)) & STDMSK_CLKMULT))); + + /* Return adjusted counter setup. */ + return setup; +} + +static uint16_t GetMode_B(struct comedi_device *dev, struct enc_private *k) +{ + register uint16_t cra; + register uint16_t crb; + register uint16_t setup; + + /* Fetch CRA and CRB register images. */ + cra = DEBIread(dev, k->MyCRA); + crb = DEBIread(dev, k->MyCRB); + + /* Populate the standardized counter setup bit fields. Note: */ + /* IndexSrc is restricted to ENC_X or IndxPol. */ + setup = (((crb << (STDBIT_INTSRC - CRBBIT_INTSRC_B)) & STDMSK_INTSRC) /* IntSrc = IntSrcB. */ + |((crb << (STDBIT_LATCHSRC - CRBBIT_LATCHSRC)) & STDMSK_LATCHSRC) /* LatchSrc = LatchSrcB. */ + |((crb << (STDBIT_LOADSRC - CRBBIT_LOADSRC_B)) & STDMSK_LOADSRC) /* LoadSrc = LoadSrcB. */ + |((crb << (STDBIT_INDXPOL - CRBBIT_INDXPOL_B)) & STDMSK_INDXPOL) /* IndxPol = IndxPolB. */ + |((crb >> (CRBBIT_CLKENAB_B - STDBIT_CLKENAB)) & STDMSK_CLKENAB) /* ClkEnab = ClkEnabB. */ + |((cra >> ((CRABIT_INDXSRC_B + 1) - STDBIT_INDXSRC)) & STDMSK_INDXSRC)); /* IndxSrc = IndxSrcB<1>. */ + + /* Adjust mode-dependent parameters. */ + if ((crb & CRBMSK_CLKMULT_B) == (MULT_X0 << CRBBIT_CLKMULT_B)) /* If Extender mode (ClkMultB == MULT_X0): */ + setup |= ((CLKSRC_EXTENDER << STDBIT_CLKSRC) /* Indicate Extender mode. */ + |(MULT_X1 << STDBIT_CLKMULT) /* Indicate multiplier is 1x. */ + |((cra >> (CRABIT_CLKSRC_B - STDBIT_CLKPOL)) & STDMSK_CLKPOL)); /* Set ClkPol equal to Timer count direction (ClkSrcB<0>). */ + + else if (cra & (2 << CRABIT_CLKSRC_B)) /* If Timer mode (ClkSrcB<1> == 1): */ + setup |= ((CLKSRC_TIMER << STDBIT_CLKSRC) /* Indicate Timer mode. */ + |(MULT_X1 << STDBIT_CLKMULT) /* Indicate multiplier is 1x. */ + |((cra >> (CRABIT_CLKSRC_B - STDBIT_CLKPOL)) & STDMSK_CLKPOL)); /* Set ClkPol equal to Timer count direction (ClkSrcB<0>). */ + + else /* If Counter mode (ClkSrcB<1> == 0): */ + setup |= ((CLKSRC_COUNTER << STDBIT_CLKSRC) /* Indicate Timer mode. */ + |((crb >> (CRBBIT_CLKMULT_B - STDBIT_CLKMULT)) & STDMSK_CLKMULT) /* Clock multiplier is passed through. */ + |((crb << (STDBIT_CLKPOL - CRBBIT_CLKPOL_B)) & STDMSK_CLKPOL)); /* Clock polarity is passed through. */ + + /* Return adjusted counter setup. */ + return setup; +} + +/* + * Set the operating mode for the specified counter. The setup + * parameter is treated as a COUNTER_SETUP data type. The following + * parameters are programmable (all other parms are ignored): ClkMult, + * ClkPol, ClkEnab, IndexSrc, IndexPol, LoadSrc. + */ + +static void SetMode_A(struct comedi_device *dev, struct enc_private *k, + uint16_t Setup, uint16_t DisableIntSrc) +{ + register uint16_t cra; + register uint16_t crb; + register uint16_t setup = Setup; /* Cache the Standard Setup. */ + + /* Initialize CRA and CRB images. */ + cra = ((setup & CRAMSK_LOADSRC_A) /* Preload trigger is passed through. */ + |((setup & STDMSK_INDXSRC) >> (STDBIT_INDXSRC - (CRABIT_INDXSRC_A + 1)))); /* IndexSrc is restricted to ENC_X or IndxPol. */ + + crb = (CRBMSK_INTRESETCMD | CRBMSK_INTRESET_A /* Reset any pending CounterA event captures. */ + | ((setup & STDMSK_CLKENAB) << (CRBBIT_CLKENAB_A - STDBIT_CLKENAB))); /* Clock enable is passed through. */ + + /* Force IntSrc to Disabled if DisableIntSrc is asserted. */ + if (!DisableIntSrc) + cra |= ((setup & STDMSK_INTSRC) >> (STDBIT_INTSRC - + CRABIT_INTSRC_A)); + + /* Populate all mode-dependent attributes of CRA & CRB images. */ + switch ((setup & STDMSK_CLKSRC) >> STDBIT_CLKSRC) { + case CLKSRC_EXTENDER: /* Extender Mode: Force to Timer mode */ + /* (Extender valid only for B counters). */ + + case CLKSRC_TIMER: /* Timer Mode: */ + cra |= ((2 << CRABIT_CLKSRC_A) /* ClkSrcA<1> selects system clock */ + |((setup & STDMSK_CLKPOL) >> (STDBIT_CLKPOL - CRABIT_CLKSRC_A)) /* with count direction (ClkSrcA<0>) obtained from ClkPol. */ + |(1 << CRABIT_CLKPOL_A) /* ClkPolA behaves as always-on clock enable. */ + |(MULT_X1 << CRABIT_CLKMULT_A)); /* ClkMult must be 1x. */ + break; + + default: /* Counter Mode: */ + cra |= (CLKSRC_COUNTER /* Select ENC_C and ENC_D as clock/direction inputs. */ + | ((setup & STDMSK_CLKPOL) << (CRABIT_CLKPOL_A - STDBIT_CLKPOL)) /* Clock polarity is passed through. */ + |(((setup & STDMSK_CLKMULT) == (MULT_X0 << STDBIT_CLKMULT)) ? /* Force multiplier to x1 if not legal, otherwise pass through. */ + (MULT_X1 << CRABIT_CLKMULT_A) : + ((setup & STDMSK_CLKMULT) << (CRABIT_CLKMULT_A - + STDBIT_CLKMULT)))); + } + + /* Force positive index polarity if IndxSrc is software-driven only, */ + /* otherwise pass it through. */ + if (~setup & STDMSK_INDXSRC) + cra |= ((setup & STDMSK_INDXPOL) << (CRABIT_INDXPOL_A - + STDBIT_INDXPOL)); + + /* If IntSrc has been forced to Disabled, update the MISC2 interrupt */ + /* enable mask to indicate the counter interrupt is disabled. */ + if (DisableIntSrc) + devpriv->CounterIntEnabs &= ~k->MyEventBits[3]; + + /* While retaining CounterB and LatchSrc configurations, program the */ + /* new counter operating mode. */ + DEBIreplace(dev, k->MyCRA, CRAMSK_INDXSRC_B | CRAMSK_CLKSRC_B, cra); + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_INTCTRL | CRBMSK_CLKENAB_A)), crb); +} + +static void SetMode_B(struct comedi_device *dev, struct enc_private *k, + uint16_t Setup, uint16_t DisableIntSrc) +{ + register uint16_t cra; + register uint16_t crb; + register uint16_t setup = Setup; /* Cache the Standard Setup. */ + + /* Initialize CRA and CRB images. */ + cra = ((setup & STDMSK_INDXSRC) << ((CRABIT_INDXSRC_B + 1) - STDBIT_INDXSRC)); /* IndexSrc field is restricted to ENC_X or IndxPol. */ + + crb = (CRBMSK_INTRESETCMD | CRBMSK_INTRESET_B /* Reset event captures and disable interrupts. */ + | ((setup & STDMSK_CLKENAB) << (CRBBIT_CLKENAB_B - STDBIT_CLKENAB)) /* Clock enable is passed through. */ + |((setup & STDMSK_LOADSRC) >> (STDBIT_LOADSRC - CRBBIT_LOADSRC_B))); /* Preload trigger source is passed through. */ + + /* Force IntSrc to Disabled if DisableIntSrc is asserted. */ + if (!DisableIntSrc) + crb |= ((setup & STDMSK_INTSRC) >> (STDBIT_INTSRC - + CRBBIT_INTSRC_B)); + + /* Populate all mode-dependent attributes of CRA & CRB images. */ + switch ((setup & STDMSK_CLKSRC) >> STDBIT_CLKSRC) { + case CLKSRC_TIMER: /* Timer Mode: */ + cra |= ((2 << CRABIT_CLKSRC_B) /* ClkSrcB<1> selects system clock */ + |((setup & STDMSK_CLKPOL) << (CRABIT_CLKSRC_B - STDBIT_CLKPOL))); /* with direction (ClkSrcB<0>) obtained from ClkPol. */ + crb |= ((1 << CRBBIT_CLKPOL_B) /* ClkPolB behaves as always-on clock enable. */ + |(MULT_X1 << CRBBIT_CLKMULT_B)); /* ClkMultB must be 1x. */ + break; + + case CLKSRC_EXTENDER: /* Extender Mode: */ + cra |= ((2 << CRABIT_CLKSRC_B) /* ClkSrcB source is OverflowA (same as "timer") */ + |((setup & STDMSK_CLKPOL) << (CRABIT_CLKSRC_B - STDBIT_CLKPOL))); /* with direction obtained from ClkPol. */ + crb |= ((1 << CRBBIT_CLKPOL_B) /* ClkPolB controls IndexB -- always set to active. */ + |(MULT_X0 << CRBBIT_CLKMULT_B)); /* ClkMultB selects OverflowA as the clock source. */ + break; + + default: /* Counter Mode: */ + cra |= (CLKSRC_COUNTER << CRABIT_CLKSRC_B); /* Select ENC_C and ENC_D as clock/direction inputs. */ + crb |= (((setup & STDMSK_CLKPOL) >> (STDBIT_CLKPOL - CRBBIT_CLKPOL_B)) /* ClkPol is passed through. */ + |(((setup & STDMSK_CLKMULT) == (MULT_X0 << STDBIT_CLKMULT)) ? /* Force ClkMult to x1 if not legal, otherwise pass through. */ + (MULT_X1 << CRBBIT_CLKMULT_B) : + ((setup & STDMSK_CLKMULT) << (CRBBIT_CLKMULT_B - + STDBIT_CLKMULT)))); + } + + /* Force positive index polarity if IndxSrc is software-driven only, */ + /* otherwise pass it through. */ + if (~setup & STDMSK_INDXSRC) + crb |= ((setup & STDMSK_INDXPOL) >> (STDBIT_INDXPOL - + CRBBIT_INDXPOL_B)); + + /* If IntSrc has been forced to Disabled, update the MISC2 interrupt */ + /* enable mask to indicate the counter interrupt is disabled. */ + if (DisableIntSrc) + devpriv->CounterIntEnabs &= ~k->MyEventBits[3]; + + /* While retaining CounterA and LatchSrc configurations, program the */ + /* new counter operating mode. */ + DEBIreplace(dev, k->MyCRA, + (uint16_t) (~(CRAMSK_INDXSRC_B | CRAMSK_CLKSRC_B)), cra); + DEBIreplace(dev, k->MyCRB, CRBMSK_CLKENAB_A | CRBMSK_LATCHSRC, crb); +} + +/* Return/set a counter's enable. enab: 0=always enabled, 1=enabled by index. */ + +static void SetEnable_A(struct comedi_device *dev, struct enc_private *k, + uint16_t enab) +{ + DEBUG("SetEnable_A: SetEnable_A enter 3541\n"); + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_INTCTRL | CRBMSK_CLKENAB_A)), + (uint16_t) (enab << CRBBIT_CLKENAB_A)); +} + +static void SetEnable_B(struct comedi_device *dev, struct enc_private *k, + uint16_t enab) +{ + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_INTCTRL | CRBMSK_CLKENAB_B)), + (uint16_t) (enab << CRBBIT_CLKENAB_B)); +} + +static uint16_t GetEnable_A(struct comedi_device *dev, struct enc_private *k) +{ + return (DEBIread(dev, k->MyCRB) >> CRBBIT_CLKENAB_A) & 1; +} + +static uint16_t GetEnable_B(struct comedi_device *dev, struct enc_private *k) +{ + return (DEBIread(dev, k->MyCRB) >> CRBBIT_CLKENAB_B) & 1; +} + +/* Return/set a counter pair's latch trigger source. 0: On read + * access, 1: A index latches A, 2: B index latches B, 3: A overflow + * latches B. + */ + +static void SetLatchSource(struct comedi_device *dev, struct enc_private *k, + uint16_t value) +{ + DEBUG("SetLatchSource: SetLatchSource enter 3550\n"); + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_INTCTRL | CRBMSK_LATCHSRC)), + (uint16_t) (value << CRBBIT_LATCHSRC)); + + DEBUG("SetLatchSource: SetLatchSource exit\n"); +} + +/* + * static uint16_t GetLatchSource(struct comedi_device *dev, struct enc_private *k ) + * { + * return ( DEBIread( dev, k->MyCRB) >> CRBBIT_LATCHSRC ) & 3; + * } + */ + +/* + * Return/set the event that will trigger transfer of the preload + * register into the counter. 0=ThisCntr_Index, 1=ThisCntr_Overflow, + * 2=OverflowA (B counters only), 3=disabled. + */ + +static void SetLoadTrig_A(struct comedi_device *dev, struct enc_private *k, + uint16_t Trig) +{ + DEBIreplace(dev, k->MyCRA, (uint16_t) (~CRAMSK_LOADSRC_A), + (uint16_t) (Trig << CRABIT_LOADSRC_A)); +} + +static void SetLoadTrig_B(struct comedi_device *dev, struct enc_private *k, + uint16_t Trig) +{ + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_LOADSRC_B | CRBMSK_INTCTRL)), + (uint16_t) (Trig << CRBBIT_LOADSRC_B)); +} + +static uint16_t GetLoadTrig_A(struct comedi_device *dev, struct enc_private *k) +{ + return (DEBIread(dev, k->MyCRA) >> CRABIT_LOADSRC_A) & 3; +} + +static uint16_t GetLoadTrig_B(struct comedi_device *dev, struct enc_private *k) +{ + return (DEBIread(dev, k->MyCRB) >> CRBBIT_LOADSRC_B) & 3; +} + +/* Return/set counter interrupt source and clear any captured + * index/overflow events. IntSource: 0=Disabled, 1=OverflowOnly, + * 2=IndexOnly, 3=IndexAndOverflow. + */ + +static void SetIntSrc_A(struct comedi_device *dev, struct enc_private *k, + uint16_t IntSource) +{ + /* Reset any pending counter overflow or index captures. */ + DEBIreplace(dev, k->MyCRB, (uint16_t) (~CRBMSK_INTCTRL), + CRBMSK_INTRESETCMD | CRBMSK_INTRESET_A); + + /* Program counter interrupt source. */ + DEBIreplace(dev, k->MyCRA, ~CRAMSK_INTSRC_A, + (uint16_t) (IntSource << CRABIT_INTSRC_A)); + + /* Update MISC2 interrupt enable mask. */ + devpriv->CounterIntEnabs = + (devpriv->CounterIntEnabs & ~k-> + MyEventBits[3]) | k->MyEventBits[IntSource]; +} + +static void SetIntSrc_B(struct comedi_device *dev, struct enc_private *k, + uint16_t IntSource) +{ + uint16_t crb; + + /* Cache writeable CRB register image. */ + crb = DEBIread(dev, k->MyCRB) & ~CRBMSK_INTCTRL; + + /* Reset any pending counter overflow or index captures. */ + DEBIwrite(dev, k->MyCRB, + (uint16_t) (crb | CRBMSK_INTRESETCMD | CRBMSK_INTRESET_B)); + + /* Program counter interrupt source. */ + DEBIwrite(dev, k->MyCRB, + (uint16_t) ((crb & ~CRBMSK_INTSRC_B) | (IntSource << + CRBBIT_INTSRC_B))); + + /* Update MISC2 interrupt enable mask. */ + devpriv->CounterIntEnabs = + (devpriv->CounterIntEnabs & ~k-> + MyEventBits[3]) | k->MyEventBits[IntSource]; +} + +static uint16_t GetIntSrc_A(struct comedi_device *dev, struct enc_private *k) +{ + return (DEBIread(dev, k->MyCRA) >> CRABIT_INTSRC_A) & 3; +} + +static uint16_t GetIntSrc_B(struct comedi_device *dev, struct enc_private *k) +{ + return (DEBIread(dev, k->MyCRB) >> CRBBIT_INTSRC_B) & 3; +} + +/* Return/set the clock multiplier. */ + +/* static void SetClkMult(struct comedi_device *dev, struct enc_private *k, uint16_t value ) */ +/* { */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_CLKMULT ) | ( value << STDBIT_CLKMULT ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetClkMult(struct comedi_device *dev, struct enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_CLKMULT ) & 3; */ +/* } */ + +/* Return/set the clock polarity. */ + +/* static void SetClkPol( struct comedi_device *dev,struct enc_private *k, uint16_t value ) */ +/* { */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_CLKPOL ) | ( value << STDBIT_CLKPOL ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetClkPol(struct comedi_device *dev, struct enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_CLKPOL ) & 1; */ +/* } */ + +/* Return/set the clock source. */ + +/* static void SetClkSrc( struct comedi_device *dev,struct enc_private *k, uint16_t value ) */ +/* { */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_CLKSRC ) | ( value << STDBIT_CLKSRC ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetClkSrc( struct comedi_device *dev,struct enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_CLKSRC ) & 3; */ +/* } */ + +/* Return/set the index polarity. */ + +/* static void SetIndexPol(struct comedi_device *dev, struct enc_private *k, uint16_t value ) */ +/* { */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_INDXPOL ) | ( (value != 0) << STDBIT_INDXPOL ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetIndexPol(struct comedi_device *dev, struct enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_INDXPOL ) & 1; */ +/* } */ + +/* Return/set the index source. */ + +/* static void SetIndexSrc(struct comedi_device *dev, struct enc_private *k, uint16_t value ) */ +/* { */ +/* DEBUG("SetIndexSrc: set index src enter 3700\n"); */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_INDXSRC ) | ( (value != 0) << STDBIT_INDXSRC ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetIndexSrc(struct comedi_device *dev, struct enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_INDXSRC ) & 1; */ +/* } */ + +/* Generate an index pulse. */ + +static void PulseIndex_A(struct comedi_device *dev, struct enc_private *k) +{ + register uint16_t cra; + + DEBUG("PulseIndex_A: pulse index enter\n"); + + cra = DEBIread(dev, k->MyCRA); /* Pulse index. */ + DEBIwrite(dev, k->MyCRA, (uint16_t) (cra ^ CRAMSK_INDXPOL_A)); + DEBUG("PulseIndex_A: pulse index step1\n"); + DEBIwrite(dev, k->MyCRA, cra); +} + +static void PulseIndex_B(struct comedi_device *dev, struct enc_private *k) +{ + register uint16_t crb; + + crb = DEBIread(dev, k->MyCRB) & ~CRBMSK_INTCTRL; /* Pulse index. */ + DEBIwrite(dev, k->MyCRB, (uint16_t) (crb ^ CRBMSK_INDXPOL_B)); + DEBIwrite(dev, k->MyCRB, crb); +} + +/* Write value into counter preload register. */ + +static void Preload(struct comedi_device *dev, struct enc_private *k, + uint32_t value) +{ + DEBUG("Preload: preload enter\n"); + DEBIwrite(dev, (uint16_t) (k->MyLatchLsw), (uint16_t) value); /* Write value to preload register. */ + DEBUG("Preload: preload step 1\n"); + DEBIwrite(dev, (uint16_t) (k->MyLatchLsw + 2), + (uint16_t) (value >> 16)); +} + +static void CountersInit(struct comedi_device *dev) +{ + int chan; + struct enc_private *k; + uint16_t Setup = (LOADSRC_INDX << BF_LOADSRC) | /* Preload upon */ + /* index. */ + (INDXSRC_SOFT << BF_INDXSRC) | /* Disable hardware index. */ + (CLKSRC_COUNTER << BF_CLKSRC) | /* Operating mode is counter. */ + (CLKPOL_POS << BF_CLKPOL) | /* Active high clock. */ + (CNTDIR_UP << BF_CLKPOL) | /* Count direction is up. */ + (CLKMULT_1X << BF_CLKMULT) | /* Clock multiplier is 1x. */ + (CLKENAB_INDEX << BF_CLKENAB); /* Enabled by index */ + + /* Disable all counter interrupts and clear any captured counter events. */ + for (chan = 0; chan < S626_ENCODER_CHANNELS; chan++) { + k = &encpriv[chan]; + k->SetMode(dev, k, Setup, TRUE); + k->SetIntSrc(dev, k, 0); + k->ResetCapFlags(dev, k); + k->SetEnable(dev, k, CLKENAB_ALWAYS); + } + DEBUG("CountersInit: counters initialized\n"); + +} diff --git a/drivers/staging/comedi/drivers/s626.h b/drivers/staging/comedi/drivers/s626.h new file mode 100644 index 00000000..2d1afecb --- /dev/null +++ b/drivers/staging/comedi/drivers/s626.h @@ -0,0 +1,727 @@ +/* + comedi/drivers/s626.h + Sensoray s626 Comedi driver, header file + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + Based on Sensoray Model 626 Linux driver Version 0.2 + Copyright (C) 2002-2004 Sensoray Co., Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* + Driver: s626.o (s626.ko) + Description: Sensoray 626 driver + Devices: Sensoray s626 + Authors: Gianluca Palli , + Updated: Thu, 12 Jul 2005 + Status: experimental + + Configuration Options: + analog input: + none + + analog output: + none + + digital channel: + s626 has 3 dio subdevices (2,3 and 4) each with 16 i/o channels + supported configuration options: + INSN_CONFIG_DIO_QUERY + COMEDI_INPUT + COMEDI_OUTPUT + + encoder: + Every channel must be configured before reading. + + Example code + + insn.insn=INSN_CONFIG; // configuration instruction + insn.n=1; // number of operation (must be 1) + insn.data=&initialvalue; // initial value loaded into encoder + // during configuration + insn.subdev=5; // encoder subdevice + insn.chanspec=CR_PACK(encoder_channel,0,AREF_OTHER); // encoder_channel + // to configure + + comedi_do_insn(cf,&insn); // executing configuration +*/ + +#ifdef _DEBUG_ +#define DEBUG(...); printk(__VA_ARGS__); +#else +#define DEBUG(...) +#endif + +#if !defined(TRUE) +#define TRUE (1) +#endif + +#if !defined(FALSE) +#define FALSE (0) +#endif + +#if !defined(INLINE) +#define INLINE static __inline +#endif + +#include + +#define S626_SIZE 0x0200 +#define SIZEOF_ADDRESS_SPACE 0x0200 +#define DMABUF_SIZE 4096 /* 4k pages */ + +#define S626_ADC_CHANNELS 16 +#define S626_DAC_CHANNELS 4 +#define S626_ENCODER_CHANNELS 6 +#define S626_DIO_CHANNELS 48 +#define S626_DIO_BANKS 3 /* Number of DIO groups. */ +#define S626_DIO_EXTCHANS 40 /* Number of */ + /* extended-capability */ + /* DIO channels. */ + +#define NUM_TRIMDACS 12 /* Number of valid TrimDAC channels. */ + +/* PCI bus interface types. */ +#define INTEL 1 /* Intel bus type. */ +#define MOTOROLA 2 /* Motorola bus type. */ + +#define PLATFORM INTEL /* *** SELECT PLATFORM TYPE *** */ + +#define RANGE_5V 0x10 /* +/-5V range */ +#define RANGE_10V 0x00 /* +/-10V range */ + +#define EOPL 0x80 /* End of ADC poll list marker. */ +#define GSEL_BIPOLAR5V 0x00F0 /* LP_GSEL setting for 5V bipolar range. */ +#define GSEL_BIPOLAR10V 0x00A0 /* LP_GSEL setting for 10V bipolar range. */ + +/* Error codes that must be visible to this base class. */ +#define ERR_ILLEGAL_PARM 0x00010000 /* Illegal function parameter value was specified. */ +#define ERR_I2C 0x00020000 /* I2C error. */ +#define ERR_COUNTERSETUP 0x00200000 /* Illegal setup specified for counter channel. */ +#define ERR_DEBI_TIMEOUT 0x00400000 /* DEBI transfer timed out. */ + +/* Organization (physical order) and size (in DWORDs) of logical DMA buffers contained by ANA_DMABUF. */ +#define ADC_DMABUF_DWORDS 40 /* ADC DMA buffer must hold 16 samples, plus pre/post garbage samples. */ +#define DAC_WDMABUF_DWORDS 1 /* DAC output DMA buffer holds a single sample. */ + +/* All remaining space in 4KB DMA buffer is available for the RPS1 program. */ + +/* Address offsets, in DWORDS, from base of DMA buffer. */ +#define DAC_WDMABUF_OS ADC_DMABUF_DWORDS + +/* Interrupt enab bit in ISR and IER. */ +#define IRQ_GPIO3 0x00000040 /* IRQ enable for GPIO3. */ +#define IRQ_RPS1 0x10000000 +#define ISR_AFOU 0x00000800 +/* Audio fifo under/overflow detected. */ + +#define IRQ_COINT1A 0x0400 /* conter 1A overflow interrupt mask */ +#define IRQ_COINT1B 0x0800 /* conter 1B overflow interrupt mask */ +#define IRQ_COINT2A 0x1000 /* conter 2A overflow interrupt mask */ +#define IRQ_COINT2B 0x2000 /* conter 2B overflow interrupt mask */ +#define IRQ_COINT3A 0x4000 /* conter 3A overflow interrupt mask */ +#define IRQ_COINT3B 0x8000 /* conter 3B overflow interrupt mask */ + +/* RPS command codes. */ +#define RPS_CLRSIGNAL 0x00000000 /* CLEAR SIGNAL */ +#define RPS_SETSIGNAL 0x10000000 /* SET SIGNAL */ +#define RPS_NOP 0x00000000 /* NOP */ +#define RPS_PAUSE 0x20000000 /* PAUSE */ +#define RPS_UPLOAD 0x40000000 /* UPLOAD */ +#define RPS_JUMP 0x80000000 /* JUMP */ +#define RPS_LDREG 0x90000100 /* LDREG (1 uint32_t only) */ +#define RPS_STREG 0xA0000100 /* STREG (1 uint32_t only) */ +#define RPS_STOP 0x50000000 /* STOP */ +#define RPS_IRQ 0x60000000 /* IRQ */ + +#define RPS_LOGICAL_OR 0x08000000 /* Logical OR conditionals. */ +#define RPS_INVERT 0x04000000 /* Test for negated semaphores. */ +#define RPS_DEBI 0x00000002 /* DEBI done */ + +#define RPS_SIG0 0x00200000 /* RPS semaphore 0 (used by ADC). */ +#define RPS_SIG1 0x00400000 /* RPS semaphore 1 (used by DAC). */ +#define RPS_SIG2 0x00800000 /* RPS semaphore 2 (not used). */ +#define RPS_GPIO2 0x00080000 /* RPS GPIO2 */ +#define RPS_GPIO3 0x00100000 /* RPS GPIO3 */ + +#define RPS_SIGADC RPS_SIG0 /* Trigger/status for ADC's RPS program. */ +#define RPS_SIGDAC RPS_SIG1 /* Trigger/status for DAC's RPS program. */ + +/* RPS clock parameters. */ +#define RPSCLK_SCALAR 8 /* This is apparent ratio of PCI/RPS clks (undocumented!!). */ +#define RPSCLK_PER_US (33 / RPSCLK_SCALAR) /* Number of RPS clocks in one microsecond. */ + +/* Event counter source addresses. */ +#define SBA_RPS_A0 0x27 /* Time of RPS0 busy, in PCI clocks. */ + +/* GPIO constants. */ +#define GPIO_BASE 0x10004000 /* GPIO 0,2,3 = inputs, GPIO3 = IRQ; GPIO1 = out. */ +#define GPIO1_LO 0x00000000 /* GPIO1 set to LOW. */ +#define GPIO1_HI 0x00001000 /* GPIO1 set to HIGH. */ + +/* Primary Status Register (PSR) constants. */ +#define PSR_DEBI_E 0x00040000 /* DEBI event flag. */ +#define PSR_DEBI_S 0x00080000 /* DEBI status flag. */ +#define PSR_A2_IN 0x00008000 /* Audio output DMA2 protection address reached. */ +#define PSR_AFOU 0x00000800 /* Audio FIFO under/overflow detected. */ +#define PSR_GPIO2 0x00000020 /* GPIO2 input pin: 0=AdcBusy, 1=AdcIdle. */ +#define PSR_EC0S 0x00000001 /* Event counter 0 threshold reached. */ + +/* Secondary Status Register (SSR) constants. */ +#define SSR_AF2_OUT 0x00000200 /* Audio 2 output FIFO under/overflow detected. */ + +/* Master Control Register 1 (MC1) constants. */ +#define MC1_SOFT_RESET 0x80000000 /* Invoke 7146 soft reset. */ +#define MC1_SHUTDOWN 0x3FFF0000 /* Shut down all MC1-controlled enables. */ + +#define MC1_ERPS1 0x2000 /* enab/disable RPS task 1. */ +#define MC1_ERPS0 0x1000 /* enab/disable RPS task 0. */ +#define MC1_DEBI 0x0800 /* enab/disable DEBI pins. */ +#define MC1_AUDIO 0x0200 /* enab/disable audio port pins. */ +#define MC1_I2C 0x0100 /* enab/disable I2C interface. */ +#define MC1_A2OUT 0x0008 /* enab/disable transfer on A2 out. */ +#define MC1_A2IN 0x0004 /* enab/disable transfer on A2 in. */ +#define MC1_A1IN 0x0001 /* enab/disable transfer on A1 in. */ + +/* Master Control Register 2 (MC2) constants. */ +#define MC2_UPLD_DEBIq 0x00020002 /* Upload DEBI registers. */ +#define MC2_UPLD_IICq 0x00010001 /* Upload I2C registers. */ +#define MC2_RPSSIG2_ONq 0x20002000 /* Assert RPS_SIG2. */ +#define MC2_RPSSIG1_ONq 0x10001000 /* Assert RPS_SIG1. */ +#define MC2_RPSSIG0_ONq 0x08000800 /* Assert RPS_SIG0. */ +#define MC2_UPLD_DEBI_MASKq 0x00000002 /* Upload DEBI mask. */ +#define MC2_UPLD_IIC_MASKq 0x00000001 /* Upload I2C mask. */ +#define MC2_RPSSIG2_MASKq 0x00002000 /* RPS_SIG2 bit mask. */ +#define MC2_RPSSIG1_MASKq 0x00001000 /* RPS_SIG1 bit mask. */ +#define MC2_RPSSIG0_MASKq 0x00000800 /* RPS_SIG0 bit mask. */ + +#define MC2_DELAYTRIG_4USq MC2_RPSSIG1_ON +#define MC2_DELAYBUSY_4USq MC2_RPSSIG1_MASK + +#define MC2_DELAYTRIG_6USq MC2_RPSSIG2_ON +#define MC2_DELAYBUSY_6USq MC2_RPSSIG2_MASK + +#define MC2_UPLD_DEBI 0x0002 /* Upload DEBI. */ +#define MC2_UPLD_IIC 0x0001 /* Upload I2C. */ +#define MC2_RPSSIG2 0x2000 /* RPS signal 2 (not used). */ +#define MC2_RPSSIG1 0x1000 /* RPS signal 1 (DAC RPS busy). */ +#define MC2_RPSSIG0 0x0800 /* RPS signal 0 (ADC RPS busy). */ + +#define MC2_ADC_RPS MC2_RPSSIG0 /* ADC RPS busy. */ +#define MC2_DAC_RPS MC2_RPSSIG1 /* DAC RPS busy. */ + +/* ***** oldies ***** */ +#define MC2_UPLD_DEBIQ 0x00020002 /* Upload DEBI registers. */ +#define MC2_UPLD_IICQ 0x00010001 /* Upload I2C registers. */ + +/* PCI BUS (SAA7146) REGISTER ADDRESS OFFSETS */ +#define P_PCI_BT_A 0x004C /* Audio DMA burst/threshold control. */ +#define P_DEBICFG 0x007C /* DEBI configuration. */ +#define P_DEBICMD 0x0080 /* DEBI command. */ +#define P_DEBIPAGE 0x0084 /* DEBI page. */ +#define P_DEBIAD 0x0088 /* DEBI target address. */ +#define P_I2CCTRL 0x008C /* I2C control. */ +#define P_I2CSTAT 0x0090 /* I2C status. */ +#define P_BASEA2_IN 0x00AC /* Audio input 2 base physical DMAbuf + * address. */ +#define P_PROTA2_IN 0x00B0 /* Audio input 2 physical DMAbuf + * protection address. */ +#define P_PAGEA2_IN 0x00B4 /* Audio input 2 paging attributes. */ +#define P_BASEA2_OUT 0x00B8 /* Audio output 2 base physical DMAbuf + * address. */ +#define P_PROTA2_OUT 0x00BC /* Audio output 2 physical DMAbuf + * protection address. */ +#define P_PAGEA2_OUT 0x00C0 /* Audio output 2 paging attributes. */ +#define P_RPSPAGE0 0x00C4 /* RPS0 page. */ +#define P_RPSPAGE1 0x00C8 /* RPS1 page. */ +#define P_RPS0_TOUT 0x00D4 /* RPS0 time-out. */ +#define P_RPS1_TOUT 0x00D8 /* RPS1 time-out. */ +#define P_IER 0x00DC /* Interrupt enable. */ +#define P_GPIO 0x00E0 /* General-purpose I/O. */ +#define P_EC1SSR 0x00E4 /* Event counter set 1 source select. */ +#define P_ECT1R 0x00EC /* Event counter threshold set 1. */ +#define P_ACON1 0x00F4 /* Audio control 1. */ +#define P_ACON2 0x00F8 /* Audio control 2. */ +#define P_MC1 0x00FC /* Master control 1. */ +#define P_MC2 0x0100 /* Master control 2. */ +#define P_RPSADDR0 0x0104 /* RPS0 instruction pointer. */ +#define P_RPSADDR1 0x0108 /* RPS1 instruction pointer. */ +#define P_ISR 0x010C /* Interrupt status. */ +#define P_PSR 0x0110 /* Primary status. */ +#define P_SSR 0x0114 /* Secondary status. */ +#define P_EC1R 0x0118 /* Event counter set 1. */ +#define P_ADP4 0x0138 /* Logical audio DMA pointer of audio + * input FIFO A2_IN. */ +#define P_FB_BUFFER1 0x0144 /* Audio feedback buffer 1. */ +#define P_FB_BUFFER2 0x0148 /* Audio feedback buffer 2. */ +#define P_TSL1 0x0180 /* Audio time slot list 1. */ +#define P_TSL2 0x01C0 /* Audio time slot list 2. */ + +/* LOCAL BUS (GATE ARRAY) REGISTER ADDRESS OFFSETS */ +/* Analog I/O registers: */ +#define LP_DACPOL 0x0082 /* Write DAC polarity. */ +#define LP_GSEL 0x0084 /* Write ADC gain. */ +#define LP_ISEL 0x0086 /* Write ADC channel select. */ +/* Digital I/O (write only): */ +#define LP_WRINTSELA 0x0042 /* Write A interrupt enable. */ +#define LP_WREDGSELA 0x0044 /* Write A edge selection. */ +#define LP_WRCAPSELA 0x0046 /* Write A capture enable. */ +#define LP_WRDOUTA 0x0048 /* Write A digital output. */ +#define LP_WRINTSELB 0x0052 /* Write B interrupt enable. */ +#define LP_WREDGSELB 0x0054 /* Write B edge selection. */ +#define LP_WRCAPSELB 0x0056 /* Write B capture enable. */ +#define LP_WRDOUTB 0x0058 /* Write B digital output. */ +#define LP_WRINTSELC 0x0062 /* Write C interrupt enable. */ +#define LP_WREDGSELC 0x0064 /* Write C edge selection. */ +#define LP_WRCAPSELC 0x0066 /* Write C capture enable. */ +#define LP_WRDOUTC 0x0068 /* Write C digital output. */ + +/* Digital I/O (read only): */ +#define LP_RDDINA 0x0040 /* Read digital input. */ +#define LP_RDCAPFLGA 0x0048 /* Read edges captured. */ +#define LP_RDINTSELA 0x004A /* Read interrupt enable register. */ +#define LP_RDEDGSELA 0x004C /* Read edge selection register. */ +#define LP_RDCAPSELA 0x004E /* Read capture enable register. */ +#define LP_RDDINB 0x0050 /* Read digital input. */ +#define LP_RDCAPFLGB 0x0058 /* Read edges captured. */ +#define LP_RDINTSELB 0x005A /* Read interrupt enable register. */ +#define LP_RDEDGSELB 0x005C /* Read edge selection register. */ +#define LP_RDCAPSELB 0x005E /* Read capture enable register. */ +#define LP_RDDINC 0x0060 /* Read digital input. */ +#define LP_RDCAPFLGC 0x0068 /* Read edges captured. */ +#define LP_RDINTSELC 0x006A /* Read interrupt enable register. */ +#define LP_RDEDGSELC 0x006C /* Read edge selection register. */ +#define LP_RDCAPSELC 0x006E /* Read capture enable register. */ + +/* Counter Registers (read/write): */ +#define LP_CR0A 0x0000 /* 0A setup register. */ +#define LP_CR0B 0x0002 /* 0B setup register. */ +#define LP_CR1A 0x0004 /* 1A setup register. */ +#define LP_CR1B 0x0006 /* 1B setup register. */ +#define LP_CR2A 0x0008 /* 2A setup register. */ +#define LP_CR2B 0x000A /* 2B setup register. */ + +/* Counter PreLoad (write) and Latch (read) Registers: */ +#define LP_CNTR0ALSW 0x000C /* 0A lsw. */ +#define LP_CNTR0AMSW 0x000E /* 0A msw. */ +#define LP_CNTR0BLSW 0x0010 /* 0B lsw. */ +#define LP_CNTR0BMSW 0x0012 /* 0B msw. */ +#define LP_CNTR1ALSW 0x0014 /* 1A lsw. */ +#define LP_CNTR1AMSW 0x0016 /* 1A msw. */ +#define LP_CNTR1BLSW 0x0018 /* 1B lsw. */ +#define LP_CNTR1BMSW 0x001A /* 1B msw. */ +#define LP_CNTR2ALSW 0x001C /* 2A lsw. */ +#define LP_CNTR2AMSW 0x001E /* 2A msw. */ +#define LP_CNTR2BLSW 0x0020 /* 2B lsw. */ +#define LP_CNTR2BMSW 0x0022 /* 2B msw. */ + +/* Miscellaneous Registers (read/write): */ +#define LP_MISC1 0x0088 /* Read/write Misc1. */ +#define LP_WRMISC2 0x0090 /* Write Misc2. */ +#define LP_RDMISC2 0x0082 /* Read Misc2. */ + +/* Bit masks for MISC1 register that are the same for reads and writes. */ +#define MISC1_WENABLE 0x8000 /* enab writes to MISC2 (except Clear + * Watchdog bit). */ +#define MISC1_WDISABLE 0x0000 /* Disable writes to MISC2. */ +#define MISC1_EDCAP 0x1000 /* enab edge capture on DIO chans + * specified by LP_WRCAPSELx. */ +#define MISC1_NOEDCAP 0x0000 /* Disable edge capture on specified + * DIO chans. */ + +/* Bit masks for MISC1 register reads. */ +#define RDMISC1_WDTIMEOUT 0x4000 /* Watchdog timer timed out. */ + +/* Bit masks for MISC2 register writes. */ +#define WRMISC2_WDCLEAR 0x8000 /* Reset watchdog timer to zero. */ +#define WRMISC2_CHARGE_ENABLE 0x4000 /* enab battery trickle charging. */ + +/* Bit masks for MISC2 register that are the same for reads and writes. */ +#define MISC2_BATT_ENABLE 0x0008 /* Backup battery enable. */ +#define MISC2_WDENABLE 0x0004 /* Watchdog timer enable. */ +#define MISC2_WDPERIOD_MASK 0x0003 /* Watchdog interval */ + /* select mask. */ + +/* Bit masks for ACON1 register. */ +#define A2_RUN 0x40000000 /* Run A2 based on TSL2. */ +#define A1_RUN 0x20000000 /* Run A1 based on TSL1. */ +#define A1_SWAP 0x00200000 /* Use big-endian for A1. */ +#define A2_SWAP 0x00100000 /* Use big-endian for A2. */ +#define WS_MODES 0x00019999 /* WS0 = TSL1 trigger */ + /* input, WS1-WS4 = */ + /* CS* outputs. */ + +#if PLATFORM == INTEL /* Base ACON1 config: always run A1 based + * on TSL1. */ +#define ACON1_BASE (WS_MODES | A1_RUN) +#elif PLATFORM == MOTOROLA +#define ACON1_BASE (WS_MODES | A1_RUN | A1_SWAP | A2_SWAP) +#endif + +#define ACON1_ADCSTART ACON1_BASE /* Start ADC: run A1 + * based on TSL1. */ +#define ACON1_DACSTART (ACON1_BASE | A2_RUN) +/* Start transmit to DAC: run A2 based on TSL2. */ +#define ACON1_DACSTOP ACON1_BASE /* Halt A2. */ + +/* Bit masks for ACON2 register. */ +#define A1_CLKSRC_BCLK1 0x00000000 /* A1 bit rate = BCLK1 (ADC). */ +#define A2_CLKSRC_X1 0x00800000 /* A2 bit rate = ACLK/1 (DACs). */ +#define A2_CLKSRC_X2 0x00C00000 /* A2 bit rate = ACLK/2 (DACs). */ +#define A2_CLKSRC_X4 0x01400000 /* A2 bit rate = ACLK/4 (DACs). */ +#define INVERT_BCLK2 0x00100000 /* Invert BCLK2 (DACs). */ +#define BCLK2_OE 0x00040000 /* enab BCLK2 (DACs). */ +#define ACON2_XORMASK 0x000C0000 /* XOR mask for ACON2 */ + /* active-low bits. */ + +#define ACON2_INIT (ACON2_XORMASK ^ (A1_CLKSRC_BCLK1 | A2_CLKSRC_X2 | INVERT_BCLK2 | BCLK2_OE)) + +/* Bit masks for timeslot records. */ +#define WS1 0x40000000 /* WS output to assert. */ +#define WS2 0x20000000 +#define WS3 0x10000000 +#define WS4 0x08000000 +#define RSD1 0x01000000 /* Shift A1 data in on SD1. */ +#define SDW_A1 0x00800000 /* Store rcv'd char at next + * char slot of DWORD1 buffer. */ +#define SIB_A1 0x00400000 /* Store rcv'd char at next + * char slot of FB1 buffer. */ +#define SF_A1 0x00200000 /* Write unsigned long + * buffer to input FIFO. */ + +/* Select parallel-to-serial converter's data source: */ +#define XFIFO_0 0x00000000 /* Data fifo byte 0. */ +#define XFIFO_1 0x00000010 /* Data fifo byte 1. */ +#define XFIFO_2 0x00000020 /* Data fifo byte 2. */ +#define XFIFO_3 0x00000030 /* Data fifo byte 3. */ +#define XFB0 0x00000040 /* FB_BUFFER byte 0. */ +#define XFB1 0x00000050 /* FB_BUFFER byte 1. */ +#define XFB2 0x00000060 /* FB_BUFFER byte 2. */ +#define XFB3 0x00000070 /* FB_BUFFER byte 3. */ +#define SIB_A2 0x00000200 /* Store next dword from A2's + * input shifter to FB2 buffer. */ +#define SF_A2 0x00000100 /* Store next dword from A2's + * input shifter to its input + * fifo. */ +#define LF_A2 0x00000080 /* Load next dword from A2's + * output fifo into its + * output dword buffer. */ +#define XSD2 0x00000008 /* Shift data out on SD2. */ +#define RSD3 0x00001800 /* Shift data in on SD3. */ +#define RSD2 0x00001000 /* Shift data in on SD2. */ +#define LOW_A2 0x00000002 /* Drive last SD low */ + /* for 7 clks, then */ + /* tri-state. */ +#define EOS 0x00000001 /* End of superframe. */ + +/* I2C configuration constants. */ +#define I2C_CLKSEL 0x0400 +/* I2C bit rate = PCIclk/480 = 68.75 KHz. */ + +#define I2C_BITRATE 68.75 +/* I2C bus data bit rate (determined by I2C_CLKSEL) in KHz. */ + +#define I2C_WRTIME 15.0 +/* Worst case time, in msec, for EEPROM internal write op. */ + +/* I2C manifest constants. */ + +/* Max retries to wait for EEPROM write. */ +#define I2C_RETRIES (I2C_WRTIME * I2C_BITRATE / 9.0) +#define I2C_ERR 0x0002 /* I2C control/status */ + /* flag ERROR. */ +#define I2C_BUSY 0x0001 /* I2C control/status */ + /* flag BUSY. */ +#define I2C_ABORT 0x0080 /* I2C status flag ABORT. */ +#define I2C_ATTRSTART 0x3 /* I2C attribute START. */ +#define I2C_ATTRCONT 0x2 /* I2C attribute CONT. */ +#define I2C_ATTRSTOP 0x1 /* I2C attribute STOP. */ +#define I2C_ATTRNOP 0x0 /* I2C attribute NOP. */ + +/* I2C read command | EEPROM address. */ +#define I2CR (devpriv->I2CAdrs | 1) + +/* I2C write command | EEPROM address. */ +#define I2CW (devpriv->I2CAdrs) + +/* Code macros used for constructing I2C command bytes. */ +#define I2C_B2(ATTR, VAL) (((ATTR) << 6) | ((VAL) << 24)) +#define I2C_B1(ATTR, VAL) (((ATTR) << 4) | ((VAL) << 16)) +#define I2C_B0(ATTR, VAL) (((ATTR) << 2) | ((VAL) << 8)) + +/* oldest */ +#define P_DEBICFGq 0x007C /* DEBI configuration. */ +#define P_DEBICMDq 0x0080 /* DEBI command. */ +#define P_DEBIPAGEq 0x0084 /* DEBI page. */ +#define P_DEBIADq 0x0088 /* DEBI target address. */ + +#define DEBI_CFG_TOQ 0x03C00000 /* timeout (15 PCI cycles) */ +#define DEBI_CFG_FASTQ 0x10000000 /* fast mode enable */ +#define DEBI_CFG_16Q 0x00080000 /* 16-bit access enable */ +#define DEBI_CFG_INCQ 0x00040000 /* enable address increment */ +#define DEBI_CFG_TIMEROFFQ 0x00010000 /* disable timer */ +#define DEBI_CMD_RDQ 0x00050000 /* read immediate 2 bytes */ +#define DEBI_CMD_WRQ 0x00040000 /* write immediate 2 bytes */ +#define DEBI_PAGE_DISABLEQ 0x00000000 /* paging disable */ + +/* DEBI command constants. */ +#define DEBI_CMD_SIZE16 (2 << 17) /* Transfer size is */ + /* always 2 bytes. */ +#define DEBI_CMD_READ 0x00010000 /* Read operation. */ +#define DEBI_CMD_WRITE 0x00000000 /* Write operation. */ + +/* Read immediate 2 bytes. */ +#define DEBI_CMD_RDWORD (DEBI_CMD_READ | DEBI_CMD_SIZE16) + +/* Write immediate 2 bytes. */ +#define DEBI_CMD_WRWORD (DEBI_CMD_WRITE | DEBI_CMD_SIZE16) + +/* DEBI configuration constants. */ +#define DEBI_CFG_XIRQ_EN 0x80000000 /* enab external */ + /* interrupt on GPIO3. */ +#define DEBI_CFG_XRESUME 0x40000000 /* Resume block */ + /* transfer when XIRQ */ + /* deasserted. */ +#define DEBI_CFG_FAST 0x10000000 /* Fast mode enable. */ + +/* 4-bit field that specifies DEBI timeout value in PCI clock cycles: */ +#define DEBI_CFG_TOUT_BIT 22 /* Finish DEBI cycle after */ + /* this many clocks. */ + +/* 2-bit field that specifies Endian byte lane steering: */ +#define DEBI_CFG_SWAP_NONE 0x00000000 /* Straight - don't */ + /* swap any bytes */ + /* (Intel). */ +#define DEBI_CFG_SWAP_2 0x00100000 /* 2-byte swap (Motorola). */ +#define DEBI_CFG_SWAP_4 0x00200000 /* 4-byte swap. */ +#define DEBI_CFG_16 0x00080000 /* Slave is able to */ + /* serve 16-bit */ + /* cycles. */ + +#define DEBI_CFG_SLAVE16 0x00080000 /* Slave is able to */ + /* serve 16-bit */ + /* cycles. */ +#define DEBI_CFG_INC 0x00040000 /* enab address */ + /* increment for block */ + /* transfers. */ +#define DEBI_CFG_INTEL 0x00020000 /* Intel style local bus. */ +#define DEBI_CFG_TIMEROFF 0x00010000 /* Disable timer. */ + +#if PLATFORM == INTEL + +#define DEBI_TOUT 7 /* Wait 7 PCI clocks */ + /* (212 ns) before */ + /* polling RDY. */ + +/* Intel byte lane steering (pass through all byte lanes). */ +#define DEBI_SWAP DEBI_CFG_SWAP_NONE + +#elif PLATFORM == MOTOROLA + +#define DEBI_TOUT 15 /* Wait 15 PCI clocks (454 ns) */ + /* maximum before timing out. */ +#define DEBI_SWAP DEBI_CFG_SWAP_2 /* Motorola byte lane steering. */ + +#endif + +/* DEBI page table constants. */ +#define DEBI_PAGE_DISABLE 0x00000000 /* Paging disable. */ + +/* ******* EXTRA FROM OTHER SANSORAY * .h ******* */ + +/* LoadSrc values: */ +#define LOADSRC_INDX 0 /* Preload core in response to */ + /* Index. */ +#define LOADSRC_OVER 1 /* Preload core in response to */ + /* Overflow. */ +#define LOADSRCB_OVERA 2 /* Preload B core in response */ + /* to A Overflow. */ +#define LOADSRC_NONE 3 /* Never preload core. */ + +/* IntSrc values: */ +#define INTSRC_NONE 0 /* Interrupts disabled. */ +#define INTSRC_OVER 1 /* Interrupt on Overflow. */ +#define INTSRC_INDX 2 /* Interrupt on Index. */ +#define INTSRC_BOTH 3 /* Interrupt on Index or Overflow. */ + +/* LatchSrc values: */ +#define LATCHSRC_AB_READ 0 /* Latch on read. */ +#define LATCHSRC_A_INDXA 1 /* Latch A on A Index. */ +#define LATCHSRC_B_INDXB 2 /* Latch B on B Index. */ +#define LATCHSRC_B_OVERA 3 /* Latch B on A Overflow. */ + +/* IndxSrc values: */ +#define INDXSRC_HARD 0 /* Hardware or software index. */ +#define INDXSRC_SOFT 1 /* Software index only. */ + +/* IndxPol values: */ +#define INDXPOL_POS 0 /* Index input is active high. */ +#define INDXPOL_NEG 1 /* Index input is active low. */ + +/* ClkSrc values: */ +#define CLKSRC_COUNTER 0 /* Counter mode. */ +#define CLKSRC_TIMER 2 /* Timer mode. */ +#define CLKSRC_EXTENDER 3 /* Extender mode. */ + +/* ClkPol values: */ +#define CLKPOL_POS 0 /* Counter/Extender clock is */ + /* active high. */ +#define CLKPOL_NEG 1 /* Counter/Extender clock is */ + /* active low. */ +#define CNTDIR_UP 0 /* Timer counts up. */ +#define CNTDIR_DOWN 1 /* Timer counts down. */ + +/* ClkEnab values: */ +#define CLKENAB_ALWAYS 0 /* Clock always enabled. */ +#define CLKENAB_INDEX 1 /* Clock is enabled by index. */ + +/* ClkMult values: */ +#define CLKMULT_4X 0 /* 4x clock multiplier. */ +#define CLKMULT_2X 1 /* 2x clock multiplier. */ +#define CLKMULT_1X 2 /* 1x clock multiplier. */ + +/* Bit Field positions in COUNTER_SETUP structure: */ +#define BF_LOADSRC 9 /* Preload trigger. */ +#define BF_INDXSRC 7 /* Index source. */ +#define BF_INDXPOL 6 /* Index polarity. */ +#define BF_CLKSRC 4 /* Clock source. */ +#define BF_CLKPOL 3 /* Clock polarity/count direction. */ +#define BF_CLKMULT 1 /* Clock multiplier. */ +#define BF_CLKENAB 0 /* Clock enable. */ + +/* Enumerated counter operating modes specified by ClkSrc bit field in */ +/* a COUNTER_SETUP. */ + +#define CLKSRC_COUNTER 0 /* Counter: ENC_C clock, ENC_D */ + /* direction. */ +#define CLKSRC_TIMER 2 /* Timer: SYS_C clock, */ + /* direction specified by */ + /* ClkPol. */ +#define CLKSRC_EXTENDER 3 /* Extender: OVR_A clock, */ + /* ENC_D direction. */ + +/* Enumerated counter clock multipliers. */ + +#define MULT_X0 0x0003 /* Supports no multipliers; */ + /* fixed physical multiplier = */ + /* 3. */ +#define MULT_X1 0x0002 /* Supports multiplier x1; */ + /* fixed physical multiplier = */ + /* 2. */ +#define MULT_X2 0x0001 /* Supports multipliers x1, */ + /* x2; physical multipliers = */ + /* 1 or 2. */ +#define MULT_X4 0x0000 /* Supports multipliers x1, */ + /* x2, x4; physical */ + /* multipliers = 0, 1 or 2. */ + +/* Sanity-check limits for parameters. */ + +#define NUM_COUNTERS 6 /* Maximum valid counter */ + /* logical channel number. */ +#define NUM_INTSOURCES 4 +#define NUM_LATCHSOURCES 4 +#define NUM_CLKMULTS 4 +#define NUM_CLKSOURCES 4 +#define NUM_CLKPOLS 2 +#define NUM_INDEXPOLS 2 +#define NUM_INDEXSOURCES 2 +#define NUM_LOADTRIGS 4 + +/* Bit field positions in CRA and CRB counter control registers. */ + +/* Bit field positions in CRA: */ +#define CRABIT_INDXSRC_B 14 /* B index source. */ +#define CRABIT_CLKSRC_B 12 /* B clock source. */ +#define CRABIT_INDXPOL_A 11 /* A index polarity. */ +#define CRABIT_LOADSRC_A 9 /* A preload trigger. */ +#define CRABIT_CLKMULT_A 7 /* A clock multiplier. */ +#define CRABIT_INTSRC_A 5 /* A interrupt source. */ +#define CRABIT_CLKPOL_A 4 /* A clock polarity. */ +#define CRABIT_INDXSRC_A 2 /* A index source. */ +#define CRABIT_CLKSRC_A 0 /* A clock source. */ + +/* Bit field positions in CRB: */ +#define CRBBIT_INTRESETCMD 15 /* Interrupt reset command. */ +#define CRBBIT_INTRESET_B 14 /* B interrupt reset enable. */ +#define CRBBIT_INTRESET_A 13 /* A interrupt reset enable. */ +#define CRBBIT_CLKENAB_A 12 /* A clock enable. */ +#define CRBBIT_INTSRC_B 10 /* B interrupt source. */ +#define CRBBIT_LATCHSRC 8 /* A/B latch source. */ +#define CRBBIT_LOADSRC_B 6 /* B preload trigger. */ +#define CRBBIT_CLKMULT_B 3 /* B clock multiplier. */ +#define CRBBIT_CLKENAB_B 2 /* B clock enable. */ +#define CRBBIT_INDXPOL_B 1 /* B index polarity. */ +#define CRBBIT_CLKPOL_B 0 /* B clock polarity. */ + +/* Bit field masks for CRA and CRB. */ + +#define CRAMSK_INDXSRC_B ((uint16_t)(3 << CRABIT_INDXSRC_B)) +#define CRAMSK_CLKSRC_B ((uint16_t)(3 << CRABIT_CLKSRC_B)) +#define CRAMSK_INDXPOL_A ((uint16_t)(1 << CRABIT_INDXPOL_A)) +#define CRAMSK_LOADSRC_A ((uint16_t)(3 << CRABIT_LOADSRC_A)) +#define CRAMSK_CLKMULT_A ((uint16_t)(3 << CRABIT_CLKMULT_A)) +#define CRAMSK_INTSRC_A ((uint16_t)(3 << CRABIT_INTSRC_A)) +#define CRAMSK_CLKPOL_A ((uint16_t)(3 << CRABIT_CLKPOL_A)) +#define CRAMSK_INDXSRC_A ((uint16_t)(3 << CRABIT_INDXSRC_A)) +#define CRAMSK_CLKSRC_A ((uint16_t)(3 << CRABIT_CLKSRC_A)) + +#define CRBMSK_INTRESETCMD ((uint16_t)(1 << CRBBIT_INTRESETCMD)) +#define CRBMSK_INTRESET_B ((uint16_t)(1 << CRBBIT_INTRESET_B)) +#define CRBMSK_INTRESET_A ((uint16_t)(1 << CRBBIT_INTRESET_A)) +#define CRBMSK_CLKENAB_A ((uint16_t)(1 << CRBBIT_CLKENAB_A)) +#define CRBMSK_INTSRC_B ((uint16_t)(3 << CRBBIT_INTSRC_B)) +#define CRBMSK_LATCHSRC ((uint16_t)(3 << CRBBIT_LATCHSRC)) +#define CRBMSK_LOADSRC_B ((uint16_t)(3 << CRBBIT_LOADSRC_B)) +#define CRBMSK_CLKMULT_B ((uint16_t)(3 << CRBBIT_CLKMULT_B)) +#define CRBMSK_CLKENAB_B ((uint16_t)(1 << CRBBIT_CLKENAB_B)) +#define CRBMSK_INDXPOL_B ((uint16_t)(1 << CRBBIT_INDXPOL_B)) +#define CRBMSK_CLKPOL_B ((uint16_t)(1 << CRBBIT_CLKPOL_B)) + +#define CRBMSK_INTCTRL (CRBMSK_INTRESETCMD | CRBMSK_INTRESET_A | CRBMSK_INTRESET_B) /* Interrupt reset control bits. */ + +/* Bit field positions for standardized SETUP structure. */ + +#define STDBIT_INTSRC 13 +#define STDBIT_LATCHSRC 11 +#define STDBIT_LOADSRC 9 +#define STDBIT_INDXSRC 7 +#define STDBIT_INDXPOL 6 +#define STDBIT_CLKSRC 4 +#define STDBIT_CLKPOL 3 +#define STDBIT_CLKMULT 1 +#define STDBIT_CLKENAB 0 + +/* Bit field masks for standardized SETUP structure. */ + +#define STDMSK_INTSRC ((uint16_t)(3 << STDBIT_INTSRC)) +#define STDMSK_LATCHSRC ((uint16_t)(3 << STDBIT_LATCHSRC)) +#define STDMSK_LOADSRC ((uint16_t)(3 << STDBIT_LOADSRC)) +#define STDMSK_INDXSRC ((uint16_t)(1 << STDBIT_INDXSRC)) +#define STDMSK_INDXPOL ((uint16_t)(1 << STDBIT_INDXPOL)) +#define STDMSK_CLKSRC ((uint16_t)(3 << STDBIT_CLKSRC)) +#define STDMSK_CLKPOL ((uint16_t)(1 << STDBIT_CLKPOL)) +#define STDMSK_CLKMULT ((uint16_t)(3 << STDBIT_CLKMULT)) +#define STDMSK_CLKENAB ((uint16_t)(1 << STDBIT_CLKENAB)) + +struct bufferDMA { + dma_addr_t PhysicalBase; + void *LogicalBase; + uint32_t DMAHandle; +}; diff --git a/drivers/staging/comedi/drivers/serial2002.c b/drivers/staging/comedi/drivers/serial2002.c new file mode 100644 index 00000000..d880c2f6 --- /dev/null +++ b/drivers/staging/comedi/drivers/serial2002.c @@ -0,0 +1,919 @@ +/* + comedi/drivers/serial2002.c + Skeleton code for a Comedi driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2002 Anders Blomdell + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* +Driver: serial2002 +Description: Driver for serial connected hardware +Devices: +Author: Anders Blomdell +Updated: Fri, 7 Jun 2002 12:56:45 -0700 +Status: in development + +*/ + +#include "../comedidev.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +struct serial2002_board { + const char *name; +}; + +static const struct serial2002_board serial2002_boards[] = { + { + .name = "serial2002"} +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct serial2002_board *)dev->board_ptr) + +struct serial2002_range_table_t { + + /* HACK... */ + int length; + struct comedi_krange range; +}; + +struct serial2002_private { + + int port; /* /dev/ttyS */ + int speed; /* baudrate */ + struct file *tty; + unsigned int ao_readback[32]; + unsigned char digital_in_mapping[32]; + unsigned char digital_out_mapping[32]; + unsigned char analog_in_mapping[32]; + unsigned char analog_out_mapping[32]; + unsigned char encoder_in_mapping[32]; + struct serial2002_range_table_t in_range[32], out_range[32]; +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct serial2002_private *)dev->private) + +static int serial2002_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int serial2002_detach(struct comedi_device *dev); +struct comedi_driver driver_serial2002 = { + .driver_name = "serial2002", + .module = THIS_MODULE, + .attach = serial2002_attach, + .detach = serial2002_detach, + .board_name = &serial2002_boards[0].name, + .offset = sizeof(struct serial2002_board), + .num_names = ARRAY_SIZE(serial2002_boards), +}; + +static int serial2002_di_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int serial2002_do_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int serial2002_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int serial2002_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int serial2002_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +struct serial_data { + enum { is_invalid, is_digital, is_channel } kind; + int index; + unsigned long value; +}; + +static long tty_ioctl(struct file *f, unsigned op, unsigned long param) +{ + if (f->f_op->unlocked_ioctl) + return f->f_op->unlocked_ioctl(f, op, param); + + return -ENOSYS; +} + +static int tty_write(struct file *f, unsigned char *buf, int count) +{ + int result; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + f->f_pos = 0; + result = f->f_op->write(f, buf, count, &f->f_pos); + set_fs(oldfs); + return result; +} + +#if 0 +/* + * On 2.6.26.3 this occaisonally gave me page faults, worked around by + * settings.c_cc[VMIN] = 0; settings.c_cc[VTIME] = 0 + */ +static int tty_available(struct file *f) +{ + long result = 0; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + tty_ioctl(f, FIONREAD, (unsigned long)&result); + set_fs(oldfs); + return result; +} +#endif + +static int tty_read(struct file *f, int timeout) +{ + int result; + + result = -1; + if (!IS_ERR(f)) { + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + if (f->f_op->poll) { + struct poll_wqueues table; + struct timeval start, now; + + do_gettimeofday(&start); + poll_initwait(&table); + while (1) { + long elapsed; + int mask; + + mask = f->f_op->poll(f, &table.pt); + if (mask & (POLLRDNORM | POLLRDBAND | POLLIN | + POLLHUP | POLLERR)) { + break; + } + do_gettimeofday(&now); + elapsed = + (1000000 * (now.tv_sec - start.tv_sec) + + now.tv_usec - start.tv_usec); + if (elapsed > timeout) + break; + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(((timeout - + elapsed) * HZ) / 10000); + } + poll_freewait(&table); + { + unsigned char ch; + + f->f_pos = 0; + if (f->f_op->read(f, &ch, 1, &f->f_pos) == 1) + result = ch; + } + } else { + /* Device does not support poll, busy wait */ + int retries = 0; + while (1) { + unsigned char ch; + + retries++; + if (retries >= timeout) + break; + + f->f_pos = 0; + if (f->f_op->read(f, &ch, 1, &f->f_pos) == 1) { + result = ch; + break; + } + udelay(100); + } + } + set_fs(oldfs); + } + return result; +} + +static void tty_setspeed(struct file *f, int speed) +{ + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + { + /* Set speed */ + struct termios settings; + + tty_ioctl(f, TCGETS, (unsigned long)&settings); +/* printk("Speed: %d\n", settings.c_cflag & (CBAUD | CBAUDEX)); */ + settings.c_iflag = 0; + settings.c_oflag = 0; + settings.c_lflag = 0; + settings.c_cflag = CLOCAL | CS8 | CREAD; + settings.c_cc[VMIN] = 0; + settings.c_cc[VTIME] = 0; + switch (speed) { + case 2400:{ + settings.c_cflag |= B2400; + } + break; + case 4800:{ + settings.c_cflag |= B4800; + } + break; + case 9600:{ + settings.c_cflag |= B9600; + } + break; + case 19200:{ + settings.c_cflag |= B19200; + } + break; + case 38400:{ + settings.c_cflag |= B38400; + } + break; + case 57600:{ + settings.c_cflag |= B57600; + } + break; + case 115200:{ + settings.c_cflag |= B115200; + } + break; + default:{ + settings.c_cflag |= B9600; + } + break; + } + tty_ioctl(f, TCSETS, (unsigned long)&settings); +/* printk("Speed: %d\n", settings.c_cflag & (CBAUD | CBAUDEX)); */ + } + { + /* Set low latency */ + struct serial_struct settings; + + tty_ioctl(f, TIOCGSERIAL, (unsigned long)&settings); + settings.flags |= ASYNC_LOW_LATENCY; + tty_ioctl(f, TIOCSSERIAL, (unsigned long)&settings); + } + + set_fs(oldfs); +} + +static void poll_digital(struct file *f, int channel) +{ + char cmd; + + cmd = 0x40 | (channel & 0x1f); + tty_write(f, &cmd, 1); +} + +static void poll_channel(struct file *f, int channel) +{ + char cmd; + + cmd = 0x60 | (channel & 0x1f); + tty_write(f, &cmd, 1); +} + +static struct serial_data serial_read(struct file *f, int timeout) +{ + struct serial_data result; + int length; + + result.kind = is_invalid; + result.index = 0; + result.value = 0; + length = 0; + while (1) { + int data = tty_read(f, timeout); + + length++; + if (data < 0) { + printk(KERN_ERR "serial2002 error\n"); + break; + } else if (data & 0x80) { + result.value = (result.value << 7) | (data & 0x7f); + } else { + if (length == 1) { + switch ((data >> 5) & 0x03) { + case 0:{ + result.value = 0; + result.kind = is_digital; + } + break; + case 1:{ + result.value = 1; + result.kind = is_digital; + } + break; + } + } else { + result.value = + (result.value << 2) | ((data & 0x60) >> 5); + result.kind = is_channel; + } + result.index = data & 0x1f; + break; + } + } + return result; + +} + +static void serial_write(struct file *f, struct serial_data data) +{ + if (data.kind == is_digital) { + unsigned char ch = + ((data.value << 5) & 0x20) | (data.index & 0x1f); + tty_write(f, &ch, 1); + } else { + unsigned char ch[6]; + int i = 0; + if (data.value >= (1L << 30)) { + ch[i] = 0x80 | ((data.value >> 30) & 0x03); + i++; + } + if (data.value >= (1L << 23)) { + ch[i] = 0x80 | ((data.value >> 23) & 0x7f); + i++; + } + if (data.value >= (1L << 16)) { + ch[i] = 0x80 | ((data.value >> 16) & 0x7f); + i++; + } + if (data.value >= (1L << 9)) { + ch[i] = 0x80 | ((data.value >> 9) & 0x7f); + i++; + } + ch[i] = 0x80 | ((data.value >> 2) & 0x7f); + i++; + ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f); + i++; + tty_write(f, ch, i); + } +} + +static int serial_2002_open(struct comedi_device *dev) +{ + int result; + char port[20]; + + sprintf(port, "/dev/ttyS%d", devpriv->port); + devpriv->tty = filp_open(port, O_RDWR, 0); + if (IS_ERR(devpriv->tty)) { + result = (int)PTR_ERR(devpriv->tty); + printk(KERN_ERR "serial_2002: file open error = %d\n", result); + } else { + struct config_t { + + short int kind; + short int bits; + int min; + int max; + }; + + struct config_t *dig_in_config; + struct config_t *dig_out_config; + struct config_t *chan_in_config; + struct config_t *chan_out_config; + int i; + + result = 0; + dig_in_config = kcalloc(32, sizeof(struct config_t), + GFP_KERNEL); + dig_out_config = kcalloc(32, sizeof(struct config_t), + GFP_KERNEL); + chan_in_config = kcalloc(32, sizeof(struct config_t), + GFP_KERNEL); + chan_out_config = kcalloc(32, sizeof(struct config_t), + GFP_KERNEL); + if (!dig_in_config || !dig_out_config + || !chan_in_config || !chan_out_config) { + result = -ENOMEM; + goto err_alloc_configs; + } + + tty_setspeed(devpriv->tty, devpriv->speed); + poll_channel(devpriv->tty, 31); /* Start reading configuration */ + while (1) { + struct serial_data data; + + data = serial_read(devpriv->tty, 1000); + if (data.kind != is_channel || data.index != 31 + || !(data.value & 0xe0)) { + break; + } else { + int command, channel, kind; + struct config_t *cur_config = NULL; + + channel = data.value & 0x1f; + kind = (data.value >> 5) & 0x7; + command = (data.value >> 8) & 0x3; + switch (kind) { + case 1:{ + cur_config = dig_in_config; + } + break; + case 2:{ + cur_config = dig_out_config; + } + break; + case 3:{ + cur_config = chan_in_config; + } + break; + case 4:{ + cur_config = chan_out_config; + } + break; + case 5:{ + cur_config = chan_in_config; + } + break; + } + + if (cur_config) { + cur_config[channel].kind = kind; + switch (command) { + case 0:{ + cur_config[channel].bits + = + (data.value >> 10) & + 0x3f; + } + break; + case 1:{ + int unit, sign, min; + unit = + (data.value >> 10) & + 0x7; + sign = + (data.value >> 13) & + 0x1; + min = + (data.value >> 14) & + 0xfffff; + + switch (unit) { + case 0:{ + min = + min + * + 1000000; + } + break; + case 1:{ + min = + min + * + 1000; + } + break; + case 2:{ + min = + min + * 1; + } + break; + } + if (sign) + min = -min; + cur_config[channel].min + = min; + } + break; + case 2:{ + int unit, sign, max; + unit = + (data.value >> 10) & + 0x7; + sign = + (data.value >> 13) & + 0x1; + max = + (data.value >> 14) & + 0xfffff; + + switch (unit) { + case 0:{ + max = + max + * + 1000000; + } + break; + case 1:{ + max = + max + * + 1000; + } + break; + case 2:{ + max = + max + * 1; + } + break; + } + if (sign) + max = -max; + cur_config[channel].max + = max; + } + break; + } + } + } + } + for (i = 0; i <= 4; i++) { + /* Fill in subdev data */ + struct config_t *c; + unsigned char *mapping = NULL; + struct serial2002_range_table_t *range = NULL; + int kind = 0; + + switch (i) { + case 0:{ + c = dig_in_config; + mapping = devpriv->digital_in_mapping; + kind = 1; + } + break; + case 1:{ + c = dig_out_config; + mapping = devpriv->digital_out_mapping; + kind = 2; + } + break; + case 2:{ + c = chan_in_config; + mapping = devpriv->analog_in_mapping; + range = devpriv->in_range; + kind = 3; + } + break; + case 3:{ + c = chan_out_config; + mapping = devpriv->analog_out_mapping; + range = devpriv->out_range; + kind = 4; + } + break; + case 4:{ + c = chan_in_config; + mapping = devpriv->encoder_in_mapping; + range = devpriv->in_range; + kind = 5; + } + break; + default:{ + c = NULL; + } + break; + } + if (c) { + struct comedi_subdevice *s; + const struct comedi_lrange **range_table_list = + NULL; + unsigned int *maxdata_list; + int j, chan; + + for (chan = 0, j = 0; j < 32; j++) { + if (c[j].kind == kind) + chan++; + } + s = &dev->subdevices[i]; + s->n_chan = chan; + s->maxdata = 0; + kfree(s->maxdata_list); + s->maxdata_list = maxdata_list = + kmalloc(sizeof(unsigned int) * s->n_chan, + GFP_KERNEL); + if (!s->maxdata_list) + break; /* error handled below */ + kfree(s->range_table_list); + s->range_table = NULL; + s->range_table_list = NULL; + if (range) { + s->range_table_list = range_table_list = + kmalloc(sizeof + (struct + serial2002_range_table_t) * + s->n_chan, GFP_KERNEL); + if (!s->range_table_list) + break; /* err handled below */ + } + for (chan = 0, j = 0; j < 32; j++) { + if (c[j].kind == kind) { + if (mapping) + mapping[chan] = j; + if (range) { + range[j].length = 1; + range[j].range.min = + c[j].min; + range[j].range.max = + c[j].max; + range_table_list[chan] = + (const struct + comedi_lrange *) + &range[j]; + } + maxdata_list[chan] = + ((long long)1 << c[j].bits) + - 1; + chan++; + } + } + } + } + if (i <= 4) { + /* Failed to allocate maxdata_list or range_table_list + * for a subdevice that needed it. */ + result = -ENOMEM; + for (i = 0; i <= 4; i++) { + struct comedi_subdevice *s; + + s = &dev->subdevices[i]; + kfree(s->maxdata_list); + s->maxdata_list = NULL; + kfree(s->range_table_list); + s->range_table_list = NULL; + } + } + +err_alloc_configs: + kfree(dig_in_config); + kfree(dig_out_config); + kfree(chan_in_config); + kfree(chan_out_config); + + if (result) { + if (devpriv->tty) { + filp_close(devpriv->tty, 0); + devpriv->tty = NULL; + } + } + } + return result; +} + +static void serial_2002_close(struct comedi_device *dev) +{ + if (!IS_ERR(devpriv->tty) && (devpriv->tty != 0)) + filp_close(devpriv->tty, 0); +} + +static int serial2002_di_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan; + + chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data read; + + poll_digital(devpriv->tty, chan); + while (1) { + read = serial_read(devpriv->tty, 1000); + if (read.kind != is_digital || read.index == chan) + break; + } + data[n] = read.value; + } + return n; +} + +static int serial2002_do_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan; + + chan = devpriv->digital_out_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data write; + + write.kind = is_digital; + write.index = chan; + write.value = data[n]; + serial_write(devpriv->tty, write); + } + return n; +} + +static int serial2002_ai_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan; + + chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data read; + + poll_channel(devpriv->tty, chan); + while (1) { + read = serial_read(devpriv->tty, 1000); + if (read.kind != is_channel || read.index == chan) + break; + } + data[n] = read.value; + } + return n; +} + +static int serial2002_ao_winsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan; + + chan = devpriv->analog_out_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data write; + + write.kind = is_channel; + write.index = chan; + write.value = data[n]; + serial_write(devpriv->tty, write); + devpriv->ao_readback[chan] = data[n]; + } + return n; +} + +static int serial2002_ao_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_readback[chan]; + + return n; +} + +static int serial2002_ei_rinsn(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n; + int chan; + + chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data read; + + poll_channel(devpriv->tty, chan); + while (1) { + read = serial_read(devpriv->tty, 1000); + if (read.kind != is_channel || read.index == chan) + break; + } + data[n] = read.value; + } + return n; +} + +static int serial2002_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + + dev_dbg(dev->hw_dev, "comedi%d: attached\n", dev->minor); + dev->board_name = thisboard->name; + if (alloc_private(dev, sizeof(struct serial2002_private)) < 0) + return -ENOMEM; + dev->open = serial_2002_open; + dev->close = serial_2002_close; + devpriv->port = it->options[0]; + devpriv->speed = it->options[1]; + dev_dbg(dev->hw_dev, "/dev/ttyS%d @ %d\n", devpriv->port, + devpriv->speed); + + if (alloc_subdevices(dev, 5) < 0) + return -ENOMEM; + + /* digital input subdevice */ + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_read = &serial2002_di_rinsn; + + /* digital output subdevice */ + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_write = &serial2002_do_winsn; + + /* analog input subdevice */ + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = 0; + s->insn_read = &serial2002_ai_rinsn; + + /* analog output subdevice */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = 0; + s->insn_write = &serial2002_ao_winsn; + s->insn_read = &serial2002_ao_rinsn; + + /* encoder input subdevice */ + s = dev->subdevices + 4; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_LSAMPL; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = 0; + s->insn_read = &serial2002_ei_rinsn; + + return 1; +} + +static int serial2002_detach(struct comedi_device *dev) +{ + struct comedi_subdevice *s; + int i; + + dev_dbg(dev->hw_dev, "comedi%d: remove\n", dev->minor); + for (i = 0; i < 5; i++) { + s = &dev->subdevices[i]; + kfree(s->maxdata_list); + kfree(s->range_table_list); + } + return 0; +} + +static int __init driver_serial2002_init_module(void) +{ + return comedi_driver_register(&driver_serial2002); +} + +static void __exit driver_serial2002_cleanup_module(void) +{ + comedi_driver_unregister(&driver_serial2002); +} + +module_init(driver_serial2002_init_module); +module_exit(driver_serial2002_cleanup_module); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/skel.c b/drivers/staging/comedi/drivers/skel.c new file mode 100644 index 00000000..ed69008f --- /dev/null +++ b/drivers/staging/comedi/drivers/skel.c @@ -0,0 +1,677 @@ +/* + comedi/drivers/skel.c + Skeleton code for a Comedi driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: skel +Description: Skeleton driver, an example for driver writers +Devices: +Author: ds +Updated: Mon, 18 Mar 2002 15:34:01 -0800 +Status: works + +This driver is a documented example on how Comedi drivers are +written. + +Configuration Options: + none +*/ + +/* + * The previous block comment is used to automatically generate + * documentation in Comedi and Comedilib. The fields: + * + * Driver: the name of the driver + * Description: a short phrase describing the driver. Don't list boards. + * Devices: a full list of the boards that attempt to be supported by + * the driver. Format is "(manufacturer) board name [comedi name]", + * where comedi_name is the name that is used to configure the board. + * See the comment near board_name: in the struct comedi_driver structure + * below. If (manufacturer) or [comedi name] is missing, the previous + * value is used. + * Author: you + * Updated: date when the _documentation_ was last updated. Use 'date -R' + * to get a value for this. + * Status: a one-word description of the status. Valid values are: + * works - driver works correctly on most boards supported, and + * passes comedi_test. + * unknown - unknown. Usually put there by ds. + * experimental - may not work in any particular release. Author + * probably wants assistance testing it. + * bitrotten - driver has not been update in a long time, probably + * doesn't work, and probably is missing support for significant + * Comedi interface features. + * untested - author probably wrote it "blind", and is believed to + * work, but no confirmation. + * + * These headers should be followed by a blank line, and any comments + * you wish to say about the driver. The comment area is the place + * to put any known bugs, limitations, unsupported features, supported + * command triggers, whether or not commands are supported on particular + * subdevices, etc. + * + * Somewhere in the comment should be information about configuration + * options that are used with comedi_config. + */ + +#include "../comedidev.h" + +#include /* for PCI devices */ + +/* Imaginary registers for the imaginary board */ + +#define SKEL_SIZE 0 + +#define SKEL_START_AI_CONV 0 +#define SKEL_AI_READ 0 + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +struct skel_board { + const char *name; + int ai_chans; + int ai_bits; + int have_dio; +}; + +static const struct skel_board skel_boards[] = { + { + .name = "skel-100", + .ai_chans = 16, + .ai_bits = 12, + .have_dio = 1, + }, + { + .name = "skel-200", + .ai_chans = 8, + .ai_bits = 16, + .have_dio = 0, + }, +}; + +/* This is used by modprobe to translate PCI IDs to drivers. Should + * only be used for PCI and ISA-PnP devices */ +/* Please add your PCI vendor ID to comedidev.h, and it will be forwarded + * upstream. */ +#define PCI_VENDOR_ID_SKEL 0xdafe +static DEFINE_PCI_DEVICE_TABLE(skel_pci_table) = { + { PCI_DEVICE(PCI_VENDOR_ID_SKEL, 0x0100) }, + { PCI_DEVICE(PCI_VENDOR_ID_SKEL, 0x0200) }, + { 0 } +}; + +MODULE_DEVICE_TABLE(pci, skel_pci_table); + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct skel_board *)dev->board_ptr) + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the struct comedi_device struct. + */ +struct skel_private { + + int data; + + /* would be useful for a PCI device */ + struct pci_dev *pci_dev; + + /* Used for AO readback */ + unsigned int ao_readback[2]; +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct skel_private *)dev->private) + +/* + * The struct comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attach/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int skel_detach(struct comedi_device *dev); +static struct comedi_driver driver_skel = { + .driver_name = "dummy", + .module = THIS_MODULE, + .attach = skel_attach, + .detach = skel_detach, +/* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in skel_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + .board_name = &skel_boards[0].name, + .offset = sizeof(struct skel_board), + .num_names = ARRAY_SIZE(skel_boards), +}; + +static int skel_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int skel_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int skel_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int skel_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int skel_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +static int skel_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd); +static int skel_ns_to_timer(unsigned int *ns, int round); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + struct comedi_subdevice *s; + + pr_info("comedi%d: skel: ", dev->minor); + +/* + * If you can probe the device to determine what device in a series + * it is, this is the place to do it. Otherwise, dev->board_ptr + * should already be initialized. + */ + /* dev->board_ptr = skel_probe(dev, it); */ + +/* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = thisboard->name; + +/* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct skel_private)) < 0) + return -ENOMEM; + +/* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 3) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* dev->read_subdev=s; */ + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + /* we support single-ended (ground) and differential */ + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; + s->n_chan = thisboard->ai_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &range_bipolar10; + s->len_chanlist = 16; /* This is the maximum chanlist length that + the board can handle */ + s->insn_read = skel_ai_rinsn; +/* +* s->subdev_flags |= SDF_CMD_READ; +* s->do_cmd = skel_ai_cmd; +*/ + s->do_cmdtest = skel_ai_cmdtest; + + s = dev->subdevices + 1; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 1; + s->maxdata = 0xffff; + s->range_table = &range_bipolar5; + s->insn_write = skel_ao_winsn; + s->insn_read = skel_ao_rinsn; + + s = dev->subdevices + 2; + /* digital i/o subdevice */ + if (thisboard->have_dio) { + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 16; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = skel_dio_insn_bits; + s->insn_config = skel_dio_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + pr_info("attached\n"); + + return 0; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int skel_detach(struct comedi_device *dev) +{ + pr_info("comedi%d: skel: remove\n", dev->minor); + + return 0; +} + +/* + * "instructions" read/write data in "one-shot" or "software-triggered" + * mode. + */ +static int skel_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int n, i; + unsigned int d; + unsigned int status; + + /* a typical programming sequence */ + + /* write channel to multiplexer */ + /* outw(chan,dev->iobase + SKEL_MUX); */ + + /* don't wait for mux to settle */ + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + /* trigger conversion */ + /* outw(0,dev->iobase + SKEL_CONVERT); */ + +#define TIMEOUT 100 + /* wait for conversion to end */ + for (i = 0; i < TIMEOUT; i++) { + status = 1; + /* status = inb(dev->iobase + SKEL_STATUS); */ + if (status) + break; + } + if (i == TIMEOUT) { + /* printk() should be used instead of printk() + * whenever the code can be called from real-time. */ + pr_info("timeout\n"); + return -ETIMEDOUT; + } + + /* read data */ + /* d = inw(dev->iobase + SKEL_AI_DATA); */ + d = 0; + + /* mangle the data as necessary */ + d ^= 1 << (thisboard->ai_bits - 1); + + data[n] = d; + } + + /* return the number of samples read/written */ + return n; +} + +static int skel_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* cmdtest tests a particular command to see if it is valid. + * Using the cmdtest ioctl, a user can create a valid cmd + * and then have it executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests + * the command passes. */ + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible + */ + + /* note that mutual compatibility is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } +#define MAX_SPEED 10000 /* in nanoseconds */ +#define MIN_SPEED 1000000000 /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < MAX_SPEED) { + cmd->scan_begin_arg = MAX_SPEED; + err++; + } + if (cmd->scan_begin_arg > MIN_SPEED) { + cmd->scan_begin_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + if (cmd->scan_begin_arg > 9) { + cmd->scan_begin_arg = 9; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < MAX_SPEED) { + cmd->convert_arg = MAX_SPEED; + err++; + } + if (cmd->convert_arg > MIN_SPEED) { + cmd->convert_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* see above */ + if (cmd->convert_arg > 9) { + cmd->convert_arg = 9; + err++; + } + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg > 0x00ffffff) { + cmd->stop_arg = 0x00ffffff; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + skel_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + skel_ns_to_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if (err) + return 4; + + return 0; +} + +/* This function doesn't require a particular form, this is just + * what happens to be used in some of the drivers. It should + * convert ns nanoseconds to a counter value suitable for programming + * the device. Also, it should adjust ns so that it cooresponds to + * the actual time that the device will use. */ +static int skel_ns_to_timer(unsigned int *ns, int round) +{ + /* trivial timer */ + /* if your timing is done through two cascaded timers, the + * i8253_cascade_ns_to_timer() function in 8253.h can be + * very helpful. There are also i8254_load() and i8254_mm_load() + * which can be used to load values into the ubiquitous 8254 counters + */ + + return *ns; +} + +static int skel_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + pr_info("skel_ao_winsn\n"); + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; i++) { + /* a typical programming sequence */ + /* outw(data[i],dev->iobase + SKEL_DA0 + chan); */ + devpriv->ao_readback[chan] = data[i]; + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int skel_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_readback[chan]; + + return i; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +static int skel_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + if (insn->n != 2) + return -EINVAL; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + /* Write out the new digital output lines */ + /* outw(s->state,dev->iobase + SKEL_DIO); */ + } + + /* on return, data[1] contains the value of the digital + * input and output lines. */ + /* data[1]=inw(dev->iobase + SKEL_DIO); */ + /* or we could just return the software copy of the output values if + * it was a purely digital output subdevice */ + /* data[1]=s->state; */ + + return 2; +} + +static int skel_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << chan; + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + break; + } + /* outw(s->io_bits,dev->iobase + SKEL_DIO_CONFIG); */ + + return insn->n; +} + +#ifdef CONFIG_COMEDI_PCI +static int __devinit driver_skel_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + return comedi_pci_auto_config(dev, driver_skel.driver_name); +} + +static void __devexit driver_skel_pci_remove(struct pci_dev *dev) +{ + comedi_pci_auto_unconfig(dev); +} + +static struct pci_driver driver_skel_pci_driver = { + .id_table = skel_pci_table, + .probe = &driver_skel_pci_probe, + .remove = __devexit_p(&driver_skel_pci_remove) +}; + +static int __init driver_skel_init_module(void) +{ + int retval; + + retval = comedi_driver_register(&driver_skel); + if (retval < 0) + return retval; + + driver_skel_pci_driver.name = (char *)driver_skel.driver_name; + return pci_register_driver(&driver_skel_pci_driver); +} + +static void __exit driver_skel_cleanup_module(void) +{ + pci_unregister_driver(&driver_skel_pci_driver); + comedi_driver_unregister(&driver_skel); +} + +module_init(driver_skel_init_module); +module_exit(driver_skel_cleanup_module); +#else +static int __init driver_skel_init_module(void) +{ + return comedi_driver_register(&driver_skel); +} + +static void __exit driver_skel_cleanup_module(void) +{ + comedi_driver_unregister(&driver_skel); +} + +module_init(driver_skel_init_module); +module_exit(driver_skel_cleanup_module); +#endif + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/ssv_dnp.c b/drivers/staging/comedi/drivers/ssv_dnp.c new file mode 100644 index 00000000..526de2ef --- /dev/null +++ b/drivers/staging/comedi/drivers/ssv_dnp.c @@ -0,0 +1,331 @@ +/* + comedi/drivers/ssv_dnp.c + generic comedi driver for SSV Embedded Systems' DIL/Net-PCs + Copyright (C) 2001 Robert Schwebel + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: ssv_dnp +Description: SSV Embedded Systems DIL/Net-PC +Author: Robert Schwebel +Devices: [SSV Embedded Systems] DIL/Net-PC 1486 (dnp-1486) +Status: unknown +*/ + +/* include files ----------------------------------------------------------- */ + +#include "../comedidev.h" + +/* Some global definitions: the registers of the DNP ----------------------- */ +/* */ +/* For port A and B the mode register has bits corresponding to the output */ +/* pins, where Bit-N = 0 -> input, Bit-N = 1 -> output. Note that bits */ +/* 4 to 7 correspond to pin 0..3 for port C data register. Ensure that bits */ +/* 0..3 remain unchanged! For details about Port C Mode Register see */ +/* the remarks in dnp_insn_config() below. */ + +#define CSCIR 0x22 /* Chip Setup and Control Index Register */ +#define CSCDR 0x23 /* Chip Setup and Control Data Register */ +#define PAMR 0xa5 /* Port A Mode Register */ +#define PADR 0xa9 /* Port A Data Register */ +#define PBMR 0xa4 /* Port B Mode Register */ +#define PBDR 0xa8 /* Port B Data Register */ +#define PCMR 0xa3 /* Port C Mode Register */ +#define PCDR 0xa7 /* Port C Data Register */ + +/* This data structure holds information about the supported boards -------- */ + +struct dnp_board { + const char *name; + int ai_chans; + int ai_bits; + int have_dio; +}; + +/* We only support one DNP 'board' variant at the moment */ +static const struct dnp_board dnp_boards[] = { +{ + .name = "dnp-1486", + .ai_chans = 16, + .ai_bits = 12, + .have_dio = 1, + }, +}; + +/* Useful for shorthand access to the particular board structure ----------- */ +#define thisboard ((const struct dnp_board *)dev->board_ptr) + +/* This structure is for data unique to the DNP driver --------------------- */ +struct dnp_private_data { + +}; + +/* Shorthand macro for faster access to the private data ------------------- */ +#define devpriv ((dnp_private *)dev->private) + +/* ------------------------------------------------------------------------- */ +/* The struct comedi_driver structure tells the Comedi core module which */ +/* functions to call to configure/deconfigure (attach/detach) the board, and */ +/* also about the kernel module that contains the device code. */ +/* */ +/* In the following section we define the API of this driver. */ +/* ------------------------------------------------------------------------- */ + +static int dnp_attach(struct comedi_device *dev, struct comedi_devconfig *it); +static int dnp_detach(struct comedi_device *dev); + +static struct comedi_driver driver_dnp = { + .driver_name = "ssv_dnp", + .module = THIS_MODULE, + .attach = dnp_attach, + .detach = dnp_detach, + .board_name = &dnp_boards[0].name, + /* only necessary for non-PnP devs */ + .offset = sizeof(struct dnp_board), /* like ISA-PnP, PCI or PCMCIA */ + .num_names = ARRAY_SIZE(dnp_boards), +}; + +static int __init driver_dnp_init_module(void) +{ + return comedi_driver_register(&driver_dnp); +} + +static void __exit driver_dnp_cleanup_module(void) +{ + comedi_driver_unregister(&driver_dnp); +} + +module_init(driver_dnp_init_module); +module_exit(driver_dnp_cleanup_module); + +static int dnp_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +static int dnp_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); + +/* ------------------------------------------------------------------------- */ +/* Attach is called by comedi core to configure the driver for a particular */ +/* board. If you specified a board_name array in the driver structure, */ +/* dev->board_ptr contains that address. */ +/* ------------------------------------------------------------------------- */ + +static int dnp_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + + struct comedi_subdevice *s; + + printk(KERN_INFO "comedi%d: dnp: ", dev->minor); + + /* Autoprobing: this should find out which board we have. Currently */ + /* only the 1486 board is supported and autoprobing is not */ + /* implemented :-) */ + /* dev->board_ptr = dnp_probe(dev); */ + + /* Initialize the name of the board. */ + /* We can use the "thisboard" macro now. */ + dev->board_name = thisboard->name; + + /* Allocate the private structure area. alloc_private() is a */ + /* convenient macro defined in comedidev.h. */ + if (alloc_private(dev, sizeof(struct dnp_private_data)) < 0) + return -ENOMEM; + + /* Allocate the subdevice structures. alloc_subdevice() is a */ + /* convenient macro defined in comedidev.h. */ + + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* digital i/o subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 20; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = dnp_dio_insn_bits; + s->insn_config = dnp_dio_insn_config; + + printk("attached\n"); + + /* We use the I/O ports 0x22,0x23 and 0xa3-0xa9, which are always + * allocated for the primary 8259, so we don't need to allocate them + * ourselves. */ + + /* configure all ports as input (default) */ + outb(PAMR, CSCIR); + outb(0x00, CSCDR); + outb(PBMR, CSCIR); + outb(0x00, CSCDR); + outb(PCMR, CSCIR); + outb((inb(CSCDR) & 0xAA), CSCDR); + + return 1; + +} + +/* ------------------------------------------------------------------------- */ +/* detach is called to deconfigure a device. It should deallocate the */ +/* resources. This function is also called when _attach() fails, so it */ +/* should be careful not to release resources that were not necessarily */ +/* allocated by _attach(). dev->private and dev->subdevices are */ +/* deallocated automatically by the core. */ +/* ------------------------------------------------------------------------- */ + +static int dnp_detach(struct comedi_device *dev) +{ + + /* configure all ports as input (default) */ + outb(PAMR, CSCIR); + outb(0x00, CSCDR); + outb(PBMR, CSCIR); + outb(0x00, CSCDR); + outb(PCMR, CSCIR); + outb((inb(CSCDR) & 0xAA), CSCDR); + + /* announce that we are finished */ + printk(KERN_INFO "comedi%d: dnp: remove\n", dev->minor); + + return 0; + +} + +/* ------------------------------------------------------------------------- */ +/* The insn_bits interface allows packed reading/writing of DIO channels. */ +/* The comedi core can convert between insn_bits and insn_read/write, so you */ +/* are able to use these instructions as well. */ +/* ------------------------------------------------------------------------- */ + +static int dnp_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + if (insn->n != 2) + return -EINVAL; /* insn uses data[0] and data[1] */ + + /* The insn data is a mask in data[0] and the new data in data[1], */ + /* each channel cooresponding to a bit. */ + + /* Ports A and B are straight forward: each bit corresponds to an */ + /* output pin with the same order. Port C is different: bits 0...3 */ + /* correspond to bits 4...7 of the output register (PCDR). */ + + if (data[0]) { + + outb(PADR, CSCIR); + outb((inb(CSCDR) + & ~(u8) (data[0] & 0x0000FF)) + | (u8) (data[1] & 0x0000FF), CSCDR); + + outb(PBDR, CSCIR); + outb((inb(CSCDR) + & ~(u8) ((data[0] & 0x00FF00) >> 8)) + | (u8) ((data[1] & 0x00FF00) >> 8), CSCDR); + + outb(PCDR, CSCIR); + outb((inb(CSCDR) + & ~(u8) ((data[0] & 0x0F0000) >> 12)) + | (u8) ((data[1] & 0x0F0000) >> 12), CSCDR); + } + + /* on return, data[1] contains the value of the digital input lines. */ + outb(PADR, CSCIR); + data[0] = inb(CSCDR); + outb(PBDR, CSCIR); + data[0] += inb(CSCDR) << 8; + outb(PCDR, CSCIR); + data[0] += ((inb(CSCDR) & 0xF0) << 12); + + return 2; + +} + +/* ------------------------------------------------------------------------- */ +/* Configure the direction of the bidirectional digital i/o pins. chanspec */ +/* contains the channel to be changed and data[0] contains either */ +/* COMEDI_INPUT or COMEDI_OUTPUT. */ +/* ------------------------------------------------------------------------- */ + +static int dnp_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + u8 register_buffer; + + /* reduces chanspec to lower 16 bits */ + int chan = CR_CHAN(insn->chanspec); + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + case INSN_CONFIG_DIO_INPUT: + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (inb(CSCDR) & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + break; + } + /* Test: which port does the channel belong to? */ + + /* We have to pay attention with port C: this is the meaning of PCMR: */ + /* Bit in PCMR: 7 6 5 4 3 2 1 0 */ + /* Corresponding port C pin: d 3 d 2 d 1 d 0 d= don't touch */ + + if ((chan >= 0) && (chan <= 7)) { + /* this is port A */ + outb(PAMR, CSCIR); + } else if ((chan >= 8) && (chan <= 15)) { + /* this is port B */ + chan -= 8; + outb(PBMR, CSCIR); + } else if ((chan >= 16) && (chan <= 19)) { + /* this is port C; multiplication with 2 brings bits into */ + /* correct position for PCMR! */ + chan -= 16; + chan *= 2; + outb(PCMR, CSCIR); + } else { + return -EINVAL; + } + + /* read 'old' direction of the port and set bits (out=1, in=0) */ + register_buffer = inb(CSCDR); + if (data[0] == COMEDI_OUTPUT) + register_buffer |= (1 << chan); + else + register_buffer &= ~(1 << chan); + + outb(register_buffer, CSCDR); + + return 1; + +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/unioxx5.c b/drivers/staging/comedi/drivers/unioxx5.c new file mode 100644 index 00000000..f45824f0 --- /dev/null +++ b/drivers/staging/comedi/drivers/unioxx5.c @@ -0,0 +1,558 @@ +/*************************************************************************** + * * + * comedi/drivers/unioxx5.c * + * Driver for Fastwel UNIOxx-5 (analog and digital i/o) boards. * + * * + * Copyright (C) 2006 Kruchinin Daniil (asgard) [asgard@etersoft.ru] * + * * + * COMEDI - Linux Control and Measurement Device Interface * + * Copyright (C) 1998,2000 David A. Schleef * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + * * + ***************************************************************************/ +/* + +Driver: unioxx5 +Description: Driver for Fastwel UNIOxx-5 (analog and digital i/o) boards. +Author: Kruchinin Daniil (asgard) +Status: unknown +Updated: 2006-10-09 +Devices: [Fastwel] UNIOxx-5 (unioxx5), + + This card supports digital and analog I/O. It written for g01 + subdevices only. + channels range: 0 .. 23 dio channels + and 0 .. 11 analog modules range + During attaching unioxx5 module displays modules identifiers + (see dmesg after comedi_config) in format: + | [module_number] module_id | + +*/ + +#include "../comedidev.h" +#include +#include + +#define DRIVER_NAME "unioxx5" +#define UNIOXX5_SIZE 0x10 +#define UNIOXX5_SUBDEV_BASE 0xA000 /* base addr of first subdev */ +#define UNIOXX5_SUBDEV_ODDS 0x400 + +/* modules types */ +#define MODULE_DIGITAL 0 +#define MODULE_OUTPUT_MASK 0x80 /* analog input/output */ + +/* constants for digital i/o */ +#define UNIOXX5_NUM_OF_CHANS 24 + +/* constants for analog i/o */ +#define TxBE 0x10 /* transmit buffer enable */ +#define RxCA 0x20 /* 1 receive character available */ +#define Rx2CA 0x40 /* 2 receive character available */ +#define Rx4CA 0x80 /* 4 receive character available */ + +/* bytes mask errors */ +#define Rx2CA_ERR_MASK 0x04 /* 2 bytes receiving error */ +#define Rx4CA_ERR_MASK 0x08 /* 4 bytes receiving error */ + +/* channel modes */ +#define ALL_2_INPUT 0 /* config all digital channels to input */ +#define ALL_2_OUTPUT 1 /* config all digital channels to output */ + +/* 'private' structure for each subdevice */ +struct unioxx5_subd_priv { + int usp_iobase; + /* 12 modules. each can be 70L or 73L */ + unsigned char usp_module_type[12]; + /* for saving previous written value for analog modules */ + unsigned char usp_extra_data[12][4]; + unsigned char usp_prev_wr_val[3]; /* previous written value */ + unsigned char usp_prev_cn_val[3]; /* previous channel value */ +}; + +static int unioxx5_attach(struct comedi_device *dev, + struct comedi_devconfig *it); +static int unioxx5_subdev_write(struct comedi_device *dev, + struct comedi_subdevice *subdev, + struct comedi_insn *insn, unsigned int *data); +static int unioxx5_subdev_read(struct comedi_device *dev, + struct comedi_subdevice *subdev, + struct comedi_insn *insn, unsigned int *data); +static int unioxx5_insn_config(struct comedi_device *dev, + struct comedi_subdevice *subdev, + struct comedi_insn *insn, unsigned int *data); +static int unioxx5_detach(struct comedi_device *dev); +static int __unioxx5_subdev_init(struct comedi_subdevice *subdev, + int subdev_iobase, int minor); +static int __unioxx5_digital_write(struct unioxx5_subd_priv *usp, + unsigned int *data, int channel, int minor); +static int __unioxx5_digital_read(struct unioxx5_subd_priv *usp, + unsigned int *data, int channel, int minor); +/* static void __unioxx5_digital_config(struct unioxx5_subd_priv* usp, int mode); */ +static int __unioxx5_analog_write(struct unioxx5_subd_priv *usp, + unsigned int *data, int channel, int minor); +static int __unioxx5_analog_read(struct unioxx5_subd_priv *usp, + unsigned int *data, int channel, int minor); +static int __unioxx5_define_chan_offset(int chan_num); +static void __unioxx5_analog_config(struct unioxx5_subd_priv *usp, int channel); + +static struct comedi_driver unioxx5_driver = { + .driver_name = DRIVER_NAME, + .module = THIS_MODULE, + .attach = unioxx5_attach, + .detach = unioxx5_detach +}; + +static int __init unioxx5_driver_init_module(void) +{ + return comedi_driver_register(&unioxx5_driver); +} + +static void __exit unioxx5_driver_cleanup_module(void) +{ + comedi_driver_unregister(&unioxx5_driver); +} + +module_init(unioxx5_driver_init_module); +module_exit(unioxx5_driver_cleanup_module); + +static int unioxx5_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int iobase, i, n_subd; + int id, num, ba; + + iobase = it->options[0]; + + dev->board_name = DRIVER_NAME; + dev->iobase = iobase; + iobase += UNIOXX5_SUBDEV_BASE; + + /* defining number of subdevices and getting they types (it must be 'g01') */ + for (i = n_subd = 0, ba = iobase; i < 4; i++, ba += UNIOXX5_SUBDEV_ODDS) { + id = inb(ba + 0xE); + num = inb(ba + 0xF); + + if (id != 'g' || num != 1) + continue; + + n_subd++; + } + + /* unioxx5 can has from two to four subdevices */ + if (n_subd < 2) { + printk(KERN_ERR + "your card must has at least 2 'g01' subdevices\n"); + return -1; + } + + if (alloc_subdevices(dev, n_subd) < 0) { + printk(KERN_ERR "out of memory\n"); + return -ENOMEM; + } + + /* initializing each of for same subdevices */ + for (i = 0; i < n_subd; i++, iobase += UNIOXX5_SUBDEV_ODDS) { + if (__unioxx5_subdev_init(&dev->subdevices[i], iobase, + dev->minor) < 0) + return -1; + } + + printk(KERN_INFO "attached\n"); + return 0; +} + +static int unioxx5_subdev_read(struct comedi_device *dev, + struct comedi_subdevice *subdev, + struct comedi_insn *insn, unsigned int *data) +{ + struct unioxx5_subd_priv *usp = subdev->private; + int channel, type; + + channel = CR_CHAN(insn->chanspec); + /* defining module type(analog or digital) */ + type = usp->usp_module_type[channel / 2]; + + if (type == MODULE_DIGITAL) { + if (!__unioxx5_digital_read(usp, data, channel, dev->minor)) + return -1; + } else { + if (!__unioxx5_analog_read(usp, data, channel, dev->minor)) + return -1; + } + + return 1; +} + +static int unioxx5_subdev_write(struct comedi_device *dev, + struct comedi_subdevice *subdev, + struct comedi_insn *insn, unsigned int *data) +{ + struct unioxx5_subd_priv *usp = subdev->private; + int channel, type; + + channel = CR_CHAN(insn->chanspec); + /* defining module type(analog or digital) */ + type = usp->usp_module_type[channel / 2]; + + if (type == MODULE_DIGITAL) { + if (!__unioxx5_digital_write(usp, data, channel, dev->minor)) + return -1; + } else { + if (!__unioxx5_analog_write(usp, data, channel, dev->minor)) + return -1; + } + + return 1; +} + +/* for digital modules only */ +static int unioxx5_insn_config(struct comedi_device *dev, + struct comedi_subdevice *subdev, + struct comedi_insn *insn, unsigned int *data) +{ + int channel_offset, flags, channel = CR_CHAN(insn->chanspec), type; + struct unioxx5_subd_priv *usp = subdev->private; + int mask = 1 << (channel & 0x07); + + type = usp->usp_module_type[channel / 2]; + + if (type != MODULE_DIGITAL) { + printk(KERN_ERR + "comedi%d: channel configuration accessible only for digital modules\n", + dev->minor); + return -1; + } + + channel_offset = __unioxx5_define_chan_offset(channel); + if (channel_offset < 0) { + printk(KERN_ERR + "comedi%d: undefined channel %d. channel range is 0 .. 23\n", + dev->minor, channel); + return -1; + } + + /* gets previously written value */ + flags = usp->usp_prev_cn_val[channel_offset - 1]; + + switch (*data) { + case COMEDI_INPUT: + flags &= ~mask; + break; + case COMEDI_OUTPUT: + flags |= mask; + break; + default: + printk(KERN_ERR "comedi%d: unknown flag\n", dev->minor); + return -1; + } + + /* *\ + * sets channels buffer to 1(after this we are allowed to * + * change channel type on input or output) * + \* */ + outb(1, usp->usp_iobase + 0); + /* changes type of _one_ channel */ + outb(flags, usp->usp_iobase + channel_offset); + /* sets channels bank to 0(allows directly input/output) */ + outb(0, usp->usp_iobase + 0); + /* saves written value */ + usp->usp_prev_cn_val[channel_offset - 1] = flags; + + return 0; +} + +static int unioxx5_detach(struct comedi_device *dev) +{ + int i; + struct comedi_subdevice *subdev; + struct unioxx5_subd_priv *usp; + + for (i = 0; i < dev->n_subdevices; i++) { + subdev = &dev->subdevices[i]; + usp = subdev->private; + release_region(usp->usp_iobase, UNIOXX5_SIZE); + kfree(subdev->private); + } + + return 0; +} + +/* initializing subdevice with given address */ +static int __unioxx5_subdev_init(struct comedi_subdevice *subdev, + int subdev_iobase, int minor) +{ + struct unioxx5_subd_priv *usp; + int i, to, ndef_flag = 0; + + if (!request_region(subdev_iobase, UNIOXX5_SIZE, DRIVER_NAME)) { + printk(KERN_ERR "comedi%d: I/O port conflict\n", minor); + return -EIO; + } + + usp = kzalloc(sizeof(*usp), GFP_KERNEL); + + if (usp == NULL) { + printk(KERN_ERR "comedi%d: error! --> out of memory!\n", minor); + return -1; + } + + usp->usp_iobase = subdev_iobase; + printk(KERN_INFO "comedi%d: |", minor); + + /* defining modules types */ + for (i = 0; i < 12; i++) { + to = 10000; + + __unioxx5_analog_config(usp, i * 2); + /* sends channel number to card */ + outb(i + 1, subdev_iobase + 5); + outb('H', subdev_iobase + 6); /* requests EEPROM world */ + while (!(inb(subdev_iobase + 0) & TxBE)) + ; /* waits while writting will be allowed */ + outb(0, subdev_iobase + 6); + + /* waits while reading of two bytes will be allowed */ + while (!(inb(subdev_iobase + 0) & Rx2CA)) { + if (--to <= 0) { + ndef_flag = 1; + break; + } + } + + if (ndef_flag) { + usp->usp_module_type[i] = 0; + ndef_flag = 0; + } else + usp->usp_module_type[i] = inb(subdev_iobase + 6); + + printk(" [%d] 0x%02x |", i, usp->usp_module_type[i]); + udelay(1); + } + + printk("\n"); + + /* initial subdevice for digital or analog i/o */ + subdev->type = COMEDI_SUBD_DIO; + subdev->private = usp; + subdev->subdev_flags = SDF_READABLE | SDF_WRITABLE; + subdev->n_chan = UNIOXX5_NUM_OF_CHANS; + subdev->maxdata = 0xFFF; + subdev->range_table = &range_digital; + subdev->insn_read = unioxx5_subdev_read; + subdev->insn_write = unioxx5_subdev_write; + /* for digital modules only!!! */ + subdev->insn_config = unioxx5_insn_config; + + printk(KERN_INFO "subdevice configured\n"); + + return 0; +} + +static int __unioxx5_digital_write(struct unioxx5_subd_priv *usp, + unsigned int *data, int channel, int minor) +{ + int channel_offset, val; + int mask = 1 << (channel & 0x07); + + channel_offset = __unioxx5_define_chan_offset(channel); + if (channel_offset < 0) { + printk(KERN_ERR + "comedi%d: undefined channel %d. channel range is 0 .. 23\n", + minor, channel); + return 0; + } + + /* getting previous written value */ + val = usp->usp_prev_wr_val[channel_offset - 1]; + + if (*data) + val |= mask; + else + val &= ~mask; + + outb(val, usp->usp_iobase + channel_offset); + /* saving new written value */ + usp->usp_prev_wr_val[channel_offset - 1] = val; + + return 1; +} + +/* function for digital reading */ +static int __unioxx5_digital_read(struct unioxx5_subd_priv *usp, + unsigned int *data, int channel, int minor) +{ + int channel_offset, mask = 1 << (channel & 0x07); + + channel_offset = __unioxx5_define_chan_offset(channel); + if (channel_offset < 0) { + printk(KERN_ERR + "comedi%d: undefined channel %d. channel range is 0 .. 23\n", + minor, channel); + return 0; + } + + *data = inb(usp->usp_iobase + channel_offset); + *data &= mask; + + if (channel_offset > 1) + channel -= 2 << channel_offset; /* this operation is created for correct readed value to 0 or 1 */ + *data >>= channel; + return 1; +} + +#if 0 /* not used? */ +static void __unioxx5_digital_config(struct unioxx5_subd_priv *usp, int mode) +{ + int i, mask; + + mask = (mode == ALL_2_OUTPUT) ? 0xFF : 0x00; + printk("COMEDI: mode = %d\n", mask); + + outb(1, usp->usp_iobase + 0); + + for (i = 0; i < 3; i++) + outb(mask, usp->usp_iobase + i); + + outb(0, usp->usp_iobase + 0); +} +#endif + +static int __unioxx5_analog_write(struct unioxx5_subd_priv *usp, + unsigned int *data, int channel, int minor) +{ + int module, i; + + module = channel / 2; /* definig module number(0 .. 11) */ + i = (channel % 2) << 1; /* depends on type of channel (A or B) */ + + /* defining if given module can work on output */ + if (!(usp->usp_module_type[module] & MODULE_OUTPUT_MASK)) { + printk(KERN_ERR + "comedi%d: module in position %d with id 0x%0x is for input only!\n", + minor, module, usp->usp_module_type[module]); + return 0; + } + + __unioxx5_analog_config(usp, channel); + /* saving minor byte */ + usp->usp_extra_data[module][i++] = (unsigned char)(*data & 0x00FF); + /* saving major byte */ + usp->usp_extra_data[module][i] = (unsigned char)((*data & 0xFF00) >> 8); + + /* while(!((inb(usp->usp_iobase + 0)) & TxBE)); */ + /* sending module number to card(1 .. 12) */ + outb(module + 1, usp->usp_iobase + 5); + outb('W', usp->usp_iobase + 6); /* sends (W)rite command to module */ + + /* sending for bytes to module(one byte per cycle iteration) */ + for (i = 0; i < 4; i++) { + while (!((inb(usp->usp_iobase + 0)) & TxBE)) + ; /* waits while writting will be allowed */ + outb(usp->usp_extra_data[module][i], usp->usp_iobase + 6); + } + + return 1; +} + +static int __unioxx5_analog_read(struct unioxx5_subd_priv *usp, + unsigned int *data, int channel, int minor) +{ + int module_no, read_ch; + char control; + + module_no = channel / 2; + read_ch = channel % 2; /* depend on type of channel (A or B) */ + + /* defining if given module can work on input */ + if (usp->usp_module_type[module_no] & MODULE_OUTPUT_MASK) { + printk(KERN_ERR + "comedi%d: module in position %d with id 0x%02x is for output only", + minor, module_no, usp->usp_module_type[module_no]); + return 0; + } + + __unioxx5_analog_config(usp, channel); + /* sends module number to card(1 .. 12) */ + outb(module_no + 1, usp->usp_iobase + 5); + outb('V', usp->usp_iobase + 6); /* sends to module (V)erify command */ + control = inb(usp->usp_iobase); /* get control register byte */ + + /* waits while reading four bytes will be allowed */ + while (!((control = inb(usp->usp_iobase + 0)) & Rx4CA)) + ; + + /* if four bytes readding error occurs - return 0(false) */ + if ((control & Rx4CA_ERR_MASK)) { + printk("COMEDI: 4 bytes error\n"); + return 0; + } + + if (read_ch) + *data = inw(usp->usp_iobase + 6); /* channel B */ + else + *data = inw(usp->usp_iobase + 4); /* channel A */ + + return 1; +} + +/* configure channels for analog i/o (even to output, odd to input) */ +static void __unioxx5_analog_config(struct unioxx5_subd_priv *usp, int channel) +{ + int chan_a, chan_b, conf, channel_offset; + + channel_offset = __unioxx5_define_chan_offset(channel); + conf = usp->usp_prev_cn_val[channel_offset - 1]; + chan_a = chan_b = 1; + + /* setting channel A and channel B mask */ + if (channel % 2 == 0) { + chan_a <<= channel & 0x07; + chan_b <<= (channel + 1) & 0x07; + } else { + chan_a <<= (channel - 1) & 0x07; + chan_b <<= channel & 0x07; + } + + conf |= chan_a; /* even channel ot output */ + conf &= ~chan_b; /* odd channel to input */ + + outb(1, usp->usp_iobase + 0); + outb(conf, usp->usp_iobase + channel_offset); + outb(0, usp->usp_iobase + 0); + + usp->usp_prev_cn_val[channel_offset - 1] = conf; +} + +/* *\ + * this function defines if the given channel number * + * enters in default numeric interspace(from 0 to 23) * + * and it returns address offset for usage needed * + * channel. * +\* */ + +static int __unioxx5_define_chan_offset(int chan_num) +{ + + if (chan_num < 0 || chan_num > 23) + return -1; + + return (chan_num >> 3) + 1; +} + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/usbdux.c b/drivers/staging/comedi/drivers/usbdux.c new file mode 100644 index 00000000..bf62e0dd --- /dev/null +++ b/drivers/staging/comedi/drivers/usbdux.c @@ -0,0 +1,2868 @@ +#define DRIVER_VERSION "v2.4" +#define DRIVER_AUTHOR "Bernd Porr, BerndPorr@f2s.com" +#define DRIVER_DESC "Stirling/ITL USB-DUX -- Bernd.Porr@f2s.com" +/* + comedi/drivers/usbdux.c + Copyright (C) 2003-2007 Bernd Porr, Bernd.Porr@f2s.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: usbdux +Description: University of Stirling USB DAQ & INCITE Technology Limited +Devices: [ITL] USB-DUX (usbdux.o) +Author: Bernd Porr +Updated: 8 Dec 2008 +Status: Stable +Configuration options: + You have to upload firmware with the -i option. The + firmware is usually installed under /usr/share/usb or + /usr/local/share/usb or /lib/firmware. + +Connection scheme for the counter at the digital port: + 0=/CLK0, 1=UP/DOWN0, 2=RESET0, 4=/CLK1, 5=UP/DOWN1, 6=RESET1. + The sampling rate of the counter is approximately 500Hz. + +Please note that under USB2.0 the length of the channel list determines +the max sampling rate. If you sample only one channel you get 8kHz +sampling rate. If you sample two channels you get 4kHz and so on. +*/ +/* + * I must give credit here to Chris Baugher who + * wrote the driver for AT-MIO-16d. I used some parts of this + * driver. I also must give credits to David Brownell + * who supported me with the USB development. + * + * Bernd Porr + * + * + * Revision history: + * 0.94: D/A output should work now with any channel list combinations + * 0.95: .owner commented out for kernel vers below 2.4.19 + * sanity checks in ai/ao_cmd + * 0.96: trying to get it working with 2.6, moved all memory alloc to comedi's + * attach final USB IDs + * moved memory allocation completely to the corresponding comedi + * functions firmware upload is by fxload and no longer by comedi (due to + * enumeration) + * 0.97: USB IDs received, adjusted table + * 0.98: SMP, locking, memroy alloc: moved all usb memory alloc + * to the usb subsystem and moved all comedi related memory + * alloc to comedi. + * | kernel | registration | usbdux-usb | usbdux-comedi | comedi | + * 0.99: USB 2.0: changed protocol to isochronous transfer + * IRQ transfer is too buggy and too risky in 2.0 + * for the high speed ISO transfer is now a working version + * available + * 0.99b: Increased the iso transfer buffer for high sp.to 10 buffers. Some VIA + * chipsets miss out IRQs. Deeper buffering is needed. + * 1.00: full USB 2.0 support for the A/D converter. Now: max 8kHz sampling + * rate. + * Firmware vers 1.00 is needed for this. + * Two 16 bit up/down/reset counter with a sampling rate of 1kHz + * And loads of cleaning up, in particular streamlining the + * bulk transfers. + * 1.1: moved EP4 transfers to EP1 to make space for a PWM output on EP4 + * 1.2: added PWM suport via EP4 + * 2.0: PWM seems to be stable and is not interfering with the other functions + * 2.1: changed PWM API + * 2.2: added firmware kernel request to fix an udev problem + * 2.3: corrected a bug in bulk timeouts which were far too short + * 2.4: fixed a bug which causes the driver to hang when it ran out of data. + * Thanks to Jan-Matthias Braun and Ian to spot the bug and fix it. + * + */ + +/* generates loads of debug info */ +/* #define NOISY_DUX_DEBUGBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../comedidev.h" + +#define BOARDNAME "usbdux" + +/* timeout for the USB-transfer in ms*/ +#define BULK_TIMEOUT 1000 + +/* constants for "firmware" upload and download */ +#define USBDUXSUB_FIRMWARE 0xA0 +#define VENDOR_DIR_IN 0xC0 +#define VENDOR_DIR_OUT 0x40 + +/* internal addresses of the 8051 processor */ +#define USBDUXSUB_CPUCS 0xE600 + +/* + * the minor device number, major is 180 only for debugging purposes and to + * upload special firmware (programming the eeprom etc) which is not compatible + * with the comedi framwork + */ +#define USBDUXSUB_MINOR 32 + +/* max lenghth of the transfer-buffer for software upload */ +#define TB_LEN 0x2000 + +/* Input endpoint number: ISO/IRQ */ +#define ISOINEP 6 + +/* Output endpoint number: ISO/IRQ */ +#define ISOOUTEP 2 + +/* This EP sends DUX commands to USBDUX */ +#define COMMAND_OUT_EP 1 + +/* This EP receives the DUX commands from USBDUX */ +#define COMMAND_IN_EP 8 + +/* Output endpoint for PWM */ +#define PWM_EP 4 + +/* 300Hz max frequ under PWM */ +#define MIN_PWM_PERIOD ((long)(1E9/300)) + +/* Default PWM frequency */ +#define PWM_DEFAULT_PERIOD ((long)(1E9/100)) + +/* Number of channels */ +#define NUMCHANNELS 8 + +/* Size of one A/D value */ +#define SIZEADIN ((sizeof(int16_t))) + +/* + * Size of the input-buffer IN BYTES + * Always multiple of 8 for 8 microframes which is needed in the highspeed mode + */ +#define SIZEINBUF ((8*SIZEADIN)) + +/* 16 bytes. */ +#define SIZEINSNBUF 16 + +/* Number of DA channels */ +#define NUMOUTCHANNELS 8 + +/* size of one value for the D/A converter: channel and value */ +#define SIZEDAOUT ((sizeof(int8_t)+sizeof(int16_t))) + +/* + * Size of the output-buffer in bytes + * Actually only the first 4 triplets are used but for the + * high speed mode we need to pad it to 8 (microframes). + */ +#define SIZEOUTBUF ((8*SIZEDAOUT)) + +/* + * Size of the buffer for the dux commands: just now max size is determined + * by the analogue out + command byte + panic bytes... + */ +#define SIZEOFDUXBUFFER ((8*SIZEDAOUT+2)) + +/* Number of in-URBs which receive the data: min=2 */ +#define NUMOFINBUFFERSFULL 5 + +/* Number of out-URBs which send the data: min=2 */ +#define NUMOFOUTBUFFERSFULL 5 + +/* Number of in-URBs which receive the data: min=5 */ +/* must have more buffers due to buggy USB ctr */ +#define NUMOFINBUFFERSHIGH 10 + +/* Number of out-URBs which send the data: min=5 */ +/* must have more buffers due to buggy USB ctr */ +#define NUMOFOUTBUFFERSHIGH 10 + +/* Total number of usbdux devices */ +#define NUMUSBDUX 16 + +/* Analogue in subdevice */ +#define SUBDEV_AD 0 + +/* Analogue out subdevice */ +#define SUBDEV_DA 1 + +/* Digital I/O */ +#define SUBDEV_DIO 2 + +/* counter */ +#define SUBDEV_COUNTER 3 + +/* timer aka pwm output */ +#define SUBDEV_PWM 4 + +/* number of retries to get the right dux command */ +#define RETRIES 10 + +/**************************************************/ +/* comedi constants */ +static const struct comedi_lrange range_usbdux_ai_range = { 4, { + BIP_RANGE + (4.096), + BIP_RANGE(4.096 + / 2), + UNI_RANGE + (4.096), + UNI_RANGE(4.096 + / 2) + } +}; + +static const struct comedi_lrange range_usbdux_ao_range = { 2, { + BIP_RANGE + (4.096), + UNI_RANGE + (4.096), + } +}; + +/* + * private structure of one subdevice + */ + +/* + * This is the structure which holds all the data of + * this driver one sub device just now: A/D + */ +struct usbduxsub { + /* attached? */ + int attached; + /* is it associated with a subdevice? */ + int probed; + /* pointer to the usb-device */ + struct usb_device *usbdev; + /* actual number of in-buffers */ + int numOfInBuffers; + /* actual number of out-buffers */ + int numOfOutBuffers; + /* ISO-transfer handling: buffers */ + struct urb **urbIn; + struct urb **urbOut; + /* pwm-transfer handling */ + struct urb *urbPwm; + /* PWM period */ + unsigned int pwmPeriod; + /* PWM internal delay for the GPIF in the FX2 */ + int8_t pwmDelay; + /* size of the PWM buffer which holds the bit pattern */ + int sizePwmBuf; + /* input buffer for the ISO-transfer */ + int16_t *inBuffer; + /* input buffer for single insn */ + int16_t *insnBuffer; + /* output buffer for single DA outputs */ + int16_t *outBuffer; + /* interface number */ + int ifnum; + /* interface structure in 2.6 */ + struct usb_interface *interface; + /* comedi device for the interrupt context */ + struct comedi_device *comedidev; + /* is it USB_SPEED_HIGH or not? */ + short int high_speed; + /* asynchronous command is running */ + short int ai_cmd_running; + short int ao_cmd_running; + /* pwm is running */ + short int pwm_cmd_running; + /* continous acquisition */ + short int ai_continous; + short int ao_continous; + /* number of samples to acquire */ + int ai_sample_count; + int ao_sample_count; + /* time between samples in units of the timer */ + unsigned int ai_timer; + unsigned int ao_timer; + /* counter between aquisitions */ + unsigned int ai_counter; + unsigned int ao_counter; + /* interval in frames/uframes */ + unsigned int ai_interval; + /* D/A commands */ + int8_t *dac_commands; + /* commands */ + int8_t *dux_commands; + struct semaphore sem; +}; + +/* + * The pointer to the private usb-data of the driver is also the private data + * for the comedi-device. This has to be global as the usb subsystem needs + * global variables. The other reason is that this structure must be there + * _before_ any comedi command is issued. The usb subsystem must be initialised + * before comedi can access it. + */ +static struct usbduxsub usbduxsub[NUMUSBDUX]; + +static DEFINE_SEMAPHORE(start_stop_sem); + +/* + * Stops the data acquision + * It should be safe to call this function from any context + */ +static int usbduxsub_unlink_InURBs(struct usbduxsub *usbduxsub_tmp) +{ + int i = 0; + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbIn) { + for (i = 0; i < usbduxsub_tmp->numOfInBuffers; i++) { + if (usbduxsub_tmp->urbIn[i]) { + /* We wait here until all transfers have been + * cancelled. */ + usb_kill_urb(usbduxsub_tmp->urbIn[i]); + } + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi: usbdux: unlinked InURB %d, err=%d\n", + i, err); + } + } + return err; +} + +/* + * This will stop a running acquisition operation + * Is called from within this driver from both the + * interrupt context and from comedi + */ +static int usbdux_ai_stop(struct usbduxsub *this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) { + pr_err("comedi?: usbdux_ai_stop: this_usbduxsub=NULL!\n"); + return -EFAULT; + } + dev_dbg(&this_usbduxsub->interface->dev, "comedi: usbdux_ai_stop\n"); + + if (do_unlink) { + /* stop aquistion */ + ret = usbduxsub_unlink_InURBs(this_usbduxsub); + } + + this_usbduxsub->ai_cmd_running = 0; + + return ret; +} + +/* + * This will cancel a running acquisition operation. + * This is called by comedi but never from inside the driver. + */ +static int usbdux_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxsub *this_usbduxsub; + int res = 0; + + /* force unlink of all urbs */ + this_usbduxsub = dev->private; + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, "comedi: usbdux_ai_cancel\n"); + + /* prevent other CPUs from submitting new commands just now */ + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + /* unlink only if the urb really has been submitted */ + res = usbdux_ai_stop(this_usbduxsub, this_usbduxsub->ai_cmd_running); + up(&this_usbduxsub->sem); + return res; +} + +/* analogue IN - interrupt service routine */ +static void usbduxsub_ai_IsocIrq(struct urb *urb) +{ + int i, err, n; + struct usbduxsub *this_usbduxsub; + struct comedi_device *this_comedidev; + struct comedi_subdevice *s; + + /* the context variable points to the subdevice */ + this_comedidev = urb->context; + /* the private structure of the subdevice is struct usbduxsub */ + this_usbduxsub = this_comedidev->private; + /* subdevice which is the AD converter */ + s = this_comedidev->subdevices + SUBDEV_AD; + + /* first we test if something unusual has just happened */ + switch (urb->status) { + case 0: + /* copy the result in the transfer buffer */ + memcpy(this_usbduxsub->inBuffer, + urb->transfer_buffer, SIZEINBUF); + break; + case -EILSEQ: + /* error in the ISOchronous data */ + /* we don't copy the data into the transfer buffer */ + /* and recycle the last data byte */ + dev_dbg(&urb->dev->dev, + "comedi%d: usbdux: CRC error in ISO IN stream.\n", + this_usbduxsub->comedidev->minor); + + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* happens after an unlink command */ + if (this_usbduxsub->ai_cmd_running) { + /* we are still running a command */ + /* tell this comedi */ + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* stop the transfer w/o unlink */ + usbdux_ai_stop(this_usbduxsub, 0); + } + return; + + default: + /* a real error on the bus */ + /* pass error to comedi if we are really running a command */ + if (this_usbduxsub->ai_cmd_running) { + dev_err(&urb->dev->dev, + "Non-zero urb status received in ai intr " + "context: %d\n", urb->status); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* don't do an unlink here */ + usbdux_ai_stop(this_usbduxsub, 0); + } + return; + } + + /* + * at this point we are reasonably sure that nothing dodgy has happened + * are we running a command? + */ + if (unlikely((!(this_usbduxsub->ai_cmd_running)))) { + /* + * not running a command, do not continue execution if no + * asynchronous command is running in particular not resubmit + */ + return; + } + + urb->dev = this_usbduxsub->usbdev; + + /* resubmit the urb */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + dev_err(&urb->dev->dev, + "comedi_: urb resubmit failed in int-context! err=%d\n", + err); + if (err == -EL2NSYNC) + dev_err(&urb->dev->dev, + "buggy USB host controller or bug in IRQ " + "handler!\n"); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* don't do an unlink here */ + usbdux_ai_stop(this_usbduxsub, 0); + return; + } + + this_usbduxsub->ai_counter--; + if (likely(this_usbduxsub->ai_counter > 0)) + return; + + /* timer zero, transfer measurements to comedi */ + this_usbduxsub->ai_counter = this_usbduxsub->ai_timer; + + /* test, if we transmit only a fixed number of samples */ + if (!(this_usbduxsub->ai_continous)) { + /* not continuous, fixed number of samples */ + this_usbduxsub->ai_sample_count--; + /* all samples received? */ + if (this_usbduxsub->ai_sample_count < 0) { + /* prevent a resubmit next time */ + usbdux_ai_stop(this_usbduxsub, 0); + /* say comedi that the acquistion is over */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + return; + } + } + /* get the data from the USB bus and hand it over to comedi */ + n = s->async->cmd.chanlist_len; + for (i = 0; i < n; i++) { + /* transfer data */ + if (CR_RANGE(s->async->cmd.chanlist[i]) <= 1) { + err = comedi_buf_put + (s->async, + le16_to_cpu(this_usbduxsub->inBuffer[i]) ^ 0x800); + } else { + err = comedi_buf_put + (s->async, + le16_to_cpu(this_usbduxsub->inBuffer[i])); + } + if (unlikely(err == 0)) { + /* buffer overflow */ + usbdux_ai_stop(this_usbduxsub, 0); + return; + } + } + /* tell comedi that data is there */ + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + comedi_event(this_usbduxsub->comedidev, s); +} + +static int usbduxsub_unlink_OutURBs(struct usbduxsub *usbduxsub_tmp) +{ + int i = 0; + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbOut) { + for (i = 0; i < usbduxsub_tmp->numOfOutBuffers; i++) { + if (usbduxsub_tmp->urbOut[i]) + usb_kill_urb(usbduxsub_tmp->urbOut[i]); + + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi: usbdux: unlinked OutURB %d: res=%d\n", + i, err); + } + } + return err; +} + +/* This will cancel a running acquisition operation + * in any context. + */ +static int usbdux_ao_stop(struct usbduxsub *this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) + return -EFAULT; + dev_dbg(&this_usbduxsub->interface->dev, "comedi: usbdux_ao_cancel\n"); + + if (do_unlink) + ret = usbduxsub_unlink_OutURBs(this_usbduxsub); + + this_usbduxsub->ao_cmd_running = 0; + + return ret; +} + +/* force unlink, is called by comedi */ +static int usbdux_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int res = 0; + + if (!this_usbduxsub) + return -EFAULT; + + /* prevent other CPUs from submitting a command just now */ + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + /* unlink only if it is really running */ + res = usbdux_ao_stop(this_usbduxsub, this_usbduxsub->ao_cmd_running); + up(&this_usbduxsub->sem); + return res; +} + +static void usbduxsub_ao_IsocIrq(struct urb *urb) +{ + int i, ret; + int8_t *datap; + struct usbduxsub *this_usbduxsub; + struct comedi_device *this_comedidev; + struct comedi_subdevice *s; + + /* the context variable points to the subdevice */ + this_comedidev = urb->context; + /* the private structure of the subdevice is struct usbduxsub */ + this_usbduxsub = this_comedidev->private; + + s = this_comedidev->subdevices + SUBDEV_DA; + + switch (urb->status) { + case 0: + /* success */ + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* after an unlink command, unplug, ... etc */ + /* no unlink needed here. Already shutting down. */ + if (this_usbduxsub->ao_cmd_running) { + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + usbdux_ao_stop(this_usbduxsub, 0); + } + return; + + default: + /* a real error */ + if (this_usbduxsub->ao_cmd_running) { + dev_err(&urb->dev->dev, + "comedi_: Non-zero urb status received in ao " + "intr context: %d\n", urb->status); + s->async->events |= COMEDI_CB_ERROR; + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + /* we do an unlink if we are in the high speed mode */ + usbdux_ao_stop(this_usbduxsub, 0); + } + return; + } + + /* are we actually running? */ + if (!(this_usbduxsub->ao_cmd_running)) + return; + + /* normal operation: executing a command in this subdevice */ + this_usbduxsub->ao_counter--; + if ((int)this_usbduxsub->ao_counter <= 0) { + /* timer zero */ + this_usbduxsub->ao_counter = this_usbduxsub->ao_timer; + + /* handle non continous acquisition */ + if (!(this_usbduxsub->ao_continous)) { + /* fixed number of samples */ + this_usbduxsub->ao_sample_count--; + if (this_usbduxsub->ao_sample_count < 0) { + /* all samples transmitted */ + usbdux_ao_stop(this_usbduxsub, 0); + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + /* no resubmit of the urb */ + return; + } + } + /* transmit data to the USB bus */ + ((uint8_t *) (urb->transfer_buffer))[0] = + s->async->cmd.chanlist_len; + for (i = 0; i < s->async->cmd.chanlist_len; i++) { + short temp; + if (i >= NUMOUTCHANNELS) + break; + + /* pointer to the DA */ + datap = + (&(((int8_t *) urb->transfer_buffer)[i * 3 + 1])); + /* get the data from comedi */ + ret = comedi_buf_get(s->async, &temp); + datap[0] = temp; + datap[1] = temp >> 8; + datap[2] = this_usbduxsub->dac_commands[i]; + /* printk("data[0]=%x, data[1]=%x, data[2]=%x\n", */ + /* datap[0],datap[1],datap[2]); */ + if (ret < 0) { + dev_err(&urb->dev->dev, + "comedi: buffer underflow\n"); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_OVERFLOW; + } + /* transmit data to comedi */ + s->async->events |= COMEDI_CB_BLOCK; + comedi_event(this_usbduxsub->comedidev, s); + } + } + urb->transfer_buffer_length = SIZEOUTBUF; + urb->dev = this_usbduxsub->usbdev; + urb->status = 0; + if (this_usbduxsub->ao_cmd_running) { + if (this_usbduxsub->high_speed) { + /* uframes */ + urb->interval = 8; + } else { + /* frames */ + urb->interval = 1; + } + urb->number_of_packets = 1; + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = SIZEOUTBUF; + urb->iso_frame_desc[0].status = 0; + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(&urb->dev->dev, + "comedi_: ao urb resubm failed in int-cont. " + "ret=%d", ret); + if (ret == EL2NSYNC) + dev_err(&urb->dev->dev, + "buggy USB host controller or bug in " + "IRQ handling!\n"); + + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* don't do an unlink here */ + usbdux_ao_stop(this_usbduxsub, 0); + } + } +} + +static int usbduxsub_start(struct usbduxsub *usbduxsub) +{ + int errcode = 0; + uint8_t local_transfer_buffer[16]; + + /* 7f92 to zero */ + local_transfer_buffer[0] = 0; + errcode = usb_control_msg(usbduxsub->usbdev, + /* create a pipe for a control transfer */ + usb_sndctrlpipe(usbduxsub->usbdev, 0), + /* bRequest, "Firmware" */ + USBDUXSUB_FIRMWARE, + /* bmRequestType */ + VENDOR_DIR_OUT, + /* Value */ + USBDUXSUB_CPUCS, + /* Index */ + 0x0000, + /* address of the transfer buffer */ + local_transfer_buffer, + /* Length */ + 1, + /* Timeout */ + BULK_TIMEOUT); + if (errcode < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: control msg failed (start)\n"); + return errcode; + } + return 0; +} + +static int usbduxsub_stop(struct usbduxsub *usbduxsub) +{ + int errcode = 0; + + uint8_t local_transfer_buffer[16]; + + /* 7f92 to one */ + local_transfer_buffer[0] = 1; + errcode = usb_control_msg(usbduxsub->usbdev, + usb_sndctrlpipe(usbduxsub->usbdev, 0), + /* bRequest, "Firmware" */ + USBDUXSUB_FIRMWARE, + /* bmRequestType */ + VENDOR_DIR_OUT, + /* Value */ + USBDUXSUB_CPUCS, + /* Index */ + 0x0000, local_transfer_buffer, + /* Length */ + 1, + /* Timeout */ + BULK_TIMEOUT); + if (errcode < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: control msg failed (stop)\n"); + return errcode; + } + return 0; +} + +static int usbduxsub_upload(struct usbduxsub *usbduxsub, + uint8_t *local_transfer_buffer, + unsigned int startAddr, unsigned int len) +{ + int errcode; + + errcode = usb_control_msg(usbduxsub->usbdev, + usb_sndctrlpipe(usbduxsub->usbdev, 0), + /* brequest, firmware */ + USBDUXSUB_FIRMWARE, + /* bmRequestType */ + VENDOR_DIR_OUT, + /* value */ + startAddr, + /* index */ + 0x0000, + /* our local safe buffer */ + local_transfer_buffer, + /* length */ + len, + /* timeout */ + BULK_TIMEOUT); + dev_dbg(&usbduxsub->interface->dev, "comedi_: result=%d\n", errcode); + if (errcode < 0) { + dev_err(&usbduxsub->interface->dev, "comedi_: upload failed\n"); + return errcode; + } + return 0; +} + +#define FIRMWARE_MAX_LEN 0x2000 + +static int firmwareUpload(struct usbduxsub *usbduxsub, + const u8 *firmwareBinary, int sizeFirmware) +{ + int ret; + uint8_t *fwBuf; + + if (!firmwareBinary) + return 0; + + if (sizeFirmware > FIRMWARE_MAX_LEN) { + dev_err(&usbduxsub->interface->dev, + "usbdux firmware binary it too large for FX2.\n"); + return -ENOMEM; + } + + /* we generate a local buffer for the firmware */ + fwBuf = kmemdup(firmwareBinary, sizeFirmware, GFP_KERNEL); + if (!fwBuf) { + dev_err(&usbduxsub->interface->dev, + "comedi_: mem alloc for firmware failed\n"); + return -ENOMEM; + } + + ret = usbduxsub_stop(usbduxsub); + if (ret < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: can not stop firmware\n"); + kfree(fwBuf); + return ret; + } + + ret = usbduxsub_upload(usbduxsub, fwBuf, 0, sizeFirmware); + if (ret < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: firmware upload failed\n"); + kfree(fwBuf); + return ret; + } + ret = usbduxsub_start(usbduxsub); + if (ret < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: can not start firmware\n"); + kfree(fwBuf); + return ret; + } + kfree(fwBuf); + return 0; +} + +static int usbduxsub_submit_InURBs(struct usbduxsub *usbduxsub) +{ + int i, errFlag; + + if (!usbduxsub) + return -EFAULT; + + /* Submit all URBs and start the transfer on the bus */ + for (i = 0; i < usbduxsub->numOfInBuffers; i++) { + /* in case of a resubmission after an unlink... */ + usbduxsub->urbIn[i]->interval = usbduxsub->ai_interval; + usbduxsub->urbIn[i]->context = usbduxsub->comedidev; + usbduxsub->urbIn[i]->dev = usbduxsub->usbdev; + usbduxsub->urbIn[i]->status = 0; + usbduxsub->urbIn[i]->transfer_flags = URB_ISO_ASAP; + dev_dbg(&usbduxsub->interface->dev, + "comedi%d: submitting in-urb[%d]: %p,%p intv=%d\n", + usbduxsub->comedidev->minor, i, + (usbduxsub->urbIn[i]->context), + (usbduxsub->urbIn[i]->dev), + (usbduxsub->urbIn[i]->interval)); + errFlag = usb_submit_urb(usbduxsub->urbIn[i], GFP_ATOMIC); + if (errFlag) { + dev_err(&usbduxsub->interface->dev, + "comedi_: ai: usb_submit_urb(%d) error %d\n", + i, errFlag); + return errFlag; + } + } + return 0; +} + +static int usbduxsub_submit_OutURBs(struct usbduxsub *usbduxsub) +{ + int i, errFlag; + + if (!usbduxsub) + return -EFAULT; + + for (i = 0; i < usbduxsub->numOfOutBuffers; i++) { + dev_dbg(&usbduxsub->interface->dev, + "comedi_: submitting out-urb[%d]\n", i); + /* in case of a resubmission after an unlink... */ + usbduxsub->urbOut[i]->context = usbduxsub->comedidev; + usbduxsub->urbOut[i]->dev = usbduxsub->usbdev; + usbduxsub->urbOut[i]->status = 0; + usbduxsub->urbOut[i]->transfer_flags = URB_ISO_ASAP; + errFlag = usb_submit_urb(usbduxsub->urbOut[i], GFP_ATOMIC); + if (errFlag) { + dev_err(&usbduxsub->interface->dev, + "comedi_: ao: usb_submit_urb(%d) error %d\n", + i, errFlag); + return errFlag; + } + } + return 0; +} + +static int usbdux_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0, tmp, i; + unsigned int tmpTimer; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!(this_usbduxsub->probed)) + return -ENODEV; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_cmdtest\n", dev->minor); + + /* make sure triggers are valid */ + /* Only immediate triggers are allowed */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + /* trigger should happen timed */ + tmp = cmd->scan_begin_src; + /* start a new _scan_ with a timer */ + cmd->scan_begin_src &= TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + /* scanning is continuous */ + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + /* issue a trigger when scan is finished and start a new scan */ + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + /* trigger at the end of count events or not, stop condition or not */ + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique and mutually compatible + * note that mutual compatibility is not an issue here + */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (this_usbduxsub->high_speed) { + /* + * In high speed mode microframes are possible. + * However, during one microframe we can roughly + * sample one channel. Thus, the more channels + * are in the channel list the more time we need. + */ + i = 1; + /* find a power of 2 for the number of channels */ + while (i < (cmd->chanlist_len)) + i = i * 2; + + if (cmd->scan_begin_arg < (1000000 / 8 * i)) { + cmd->scan_begin_arg = 1000000 / 8 * i; + err++; + } + /* now calc the real sampling rate with all the + * rounding errors */ + tmpTimer = + ((unsigned int)(cmd->scan_begin_arg / 125000)) * + 125000; + if (cmd->scan_begin_arg != tmpTimer) { + cmd->scan_begin_arg = tmpTimer; + err++; + } + } else { + /* full speed */ + /* 1kHz scans every USB frame */ + if (cmd->scan_begin_arg < 1000000) { + cmd->scan_begin_arg = 1000000; + err++; + } + /* + * calc the real sampling rate with the rounding errors + */ + tmpTimer = ((unsigned int)(cmd->scan_begin_arg / + 1000000)) * 1000000; + if (cmd->scan_begin_arg != tmpTimer) { + cmd->scan_begin_arg = tmpTimer; + err++; + } + } + } + /* the same argument */ + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + return 0; +} + +/* + * creates the ADC command for the MAX1271 + * range is the range value from comedi + */ +static int8_t create_adc_command(unsigned int chan, int range) +{ + int8_t p = (range <= 1); + int8_t r = ((range % 2) == 0); + return (chan << 4) | ((p == 1) << 2) | ((r == 1) << 3); +} + +/* bulk transfers to usbdux */ + +#define SENDADCOMMANDS 0 +#define SENDDACOMMANDS 1 +#define SENDDIOCONFIGCOMMAND 2 +#define SENDDIOBITSCOMMAND 3 +#define SENDSINGLEAD 4 +#define READCOUNTERCOMMAND 5 +#define WRITECOUNTERCOMMAND 6 +#define SENDPWMON 7 +#define SENDPWMOFF 8 + +static int send_dux_commands(struct usbduxsub *this_usbduxsub, int cmd_type) +{ + int result, nsent; + + this_usbduxsub->dux_commands[0] = cmd_type; +#ifdef NOISY_DUX_DEBUGBUG + printk(KERN_DEBUG "comedi%d: usbdux: dux_commands: ", + this_usbduxsub->comedidev->minor); + for (result = 0; result < SIZEOFDUXBUFFER; result++) + printk(" %02x", this_usbduxsub->dux_commands[result]); + printk("\n"); +#endif + result = usb_bulk_msg(this_usbduxsub->usbdev, + usb_sndbulkpipe(this_usbduxsub->usbdev, + COMMAND_OUT_EP), + this_usbduxsub->dux_commands, SIZEOFDUXBUFFER, + &nsent, BULK_TIMEOUT); + if (result < 0) + dev_err(&this_usbduxsub->interface->dev, "comedi%d: " + "could not transmit dux_command to the usb-device, " + "err=%d\n", this_usbduxsub->comedidev->minor, result); + + return result; +} + +static int receive_dux_commands(struct usbduxsub *this_usbduxsub, int command) +{ + int result = (-EFAULT); + int nrec; + int i; + + for (i = 0; i < RETRIES; i++) { + result = usb_bulk_msg(this_usbduxsub->usbdev, + usb_rcvbulkpipe(this_usbduxsub->usbdev, + COMMAND_IN_EP), + this_usbduxsub->insnBuffer, SIZEINSNBUF, + &nrec, BULK_TIMEOUT); + if (result < 0) { + dev_err(&this_usbduxsub->interface->dev, "comedi%d: " + "insn: USB error %d while receiving DUX command" + "\n", this_usbduxsub->comedidev->minor, result); + return result; + } + if (le16_to_cpu(this_usbduxsub->insnBuffer[0]) == command) + return result; + } + /* this is only reached if the data has been requested a couple of + * times */ + dev_err(&this_usbduxsub->interface->dev, "comedi%d: insn: " + "wrong data returned from firmware: want cmd %d, got cmd %d.\n", + this_usbduxsub->comedidev->minor, command, + le16_to_cpu(this_usbduxsub->insnBuffer[0])); + return -EFAULT; +} + +static int usbdux_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum) +{ + int ret; + struct usbduxsub *this_usbduxsub = dev->private; + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_inttrig\n", dev->minor); + + if (trignum != 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_inttrig: invalid trignum\n", + dev->minor); + up(&this_usbduxsub->sem); + return -EINVAL; + } + if (!(this_usbduxsub->ai_cmd_running)) { + this_usbduxsub->ai_cmd_running = 1; + ret = usbduxsub_submit_InURBs(this_usbduxsub); + if (ret < 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_inttrig: " + "urbSubmit: err=%d\n", dev->minor, ret); + this_usbduxsub->ai_cmd_running = 0; + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ai_inttrig but acqu is already running\n", + dev->minor); + } + up(&this_usbduxsub->sem); + return 1; +} + +static int usbdux_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int chan, range; + int i, ret; + struct usbduxsub *this_usbduxsub = dev->private; + int result; + + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_cmd\n", dev->minor); + + /* block other CPUs from starting an ai_cmd */ + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ai_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, "comedi%d: " + "ai_cmd not possible. Another ai_cmd is running.\n", + dev->minor); + up(&this_usbduxsub->sem); + return -EBUSY; + } + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + + this_usbduxsub->dux_commands[1] = cmd->chanlist_len; + for (i = 0; i < cmd->chanlist_len; ++i) { + chan = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + if (i >= NUMCHANNELS) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: channel list too long\n", + dev->minor); + break; + } + this_usbduxsub->dux_commands[i + 2] = + create_adc_command(chan, range); + } + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi %d: sending commands to the usb device: size=%u\n", + dev->minor, NUMCHANNELS); + + result = send_dux_commands(this_usbduxsub, SENDADCOMMANDS); + if (result < 0) { + up(&this_usbduxsub->sem); + return result; + } + + if (this_usbduxsub->high_speed) { + /* + * every channel gets a time window of 125us. Thus, if we + * sample all 8 channels we need 1ms. If we sample only one + * channel we need only 125us + */ + this_usbduxsub->ai_interval = 1; + /* find a power of 2 for the interval */ + while ((this_usbduxsub->ai_interval) < (cmd->chanlist_len)) { + this_usbduxsub->ai_interval = + (this_usbduxsub->ai_interval) * 2; + } + this_usbduxsub->ai_timer = cmd->scan_begin_arg / (125000 * + (this_usbduxsub-> + ai_interval)); + } else { + /* interval always 1ms */ + this_usbduxsub->ai_interval = 1; + this_usbduxsub->ai_timer = cmd->scan_begin_arg / 1000000; + } + if (this_usbduxsub->ai_timer < 1) { + dev_err(&this_usbduxsub->interface->dev, "comedi%d: ai_cmd: " + "timer=%d, scan_begin_arg=%d. " + "Not properly tested by cmdtest?\n", dev->minor, + this_usbduxsub->ai_timer, cmd->scan_begin_arg); + up(&this_usbduxsub->sem); + return -EINVAL; + } + this_usbduxsub->ai_counter = this_usbduxsub->ai_timer; + + if (cmd->stop_src == TRIG_COUNT) { + /* data arrives as one packet */ + this_usbduxsub->ai_sample_count = cmd->stop_arg; + this_usbduxsub->ai_continous = 0; + } else { + /* continous acquisition */ + this_usbduxsub->ai_continous = 1; + this_usbduxsub->ai_sample_count = 0; + } + + if (cmd->start_src == TRIG_NOW) { + /* enable this acquisition operation */ + this_usbduxsub->ai_cmd_running = 1; + ret = usbduxsub_submit_InURBs(this_usbduxsub); + if (ret < 0) { + this_usbduxsub->ai_cmd_running = 0; + /* fixme: unlink here?? */ + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + /* don't enable the acquision operation */ + /* wait for an internal signal */ + s->async->inttrig = usbdux_ai_inttrig; + } + up(&this_usbduxsub->sem); + return 0; +} + +/* Mode 0 is used to get a single conversion on demand */ +static int usbdux_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + unsigned int one = 0; + int chan, range; + int err; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return 0; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ai_insn_read, insn->n=%d, insn->subdev=%d\n", + dev->minor, insn->n, insn->subdev); + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ai_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ai_insn_read not possible. " + "Async Command is running.\n", dev->minor); + up(&this_usbduxsub->sem); + return 0; + } + + /* sample one channel */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + /* set command for the first channel */ + this_usbduxsub->dux_commands[1] = create_adc_command(chan, range); + + /* adc commands */ + err = send_dux_commands(this_usbduxsub, SENDSINGLEAD); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + for (i = 0; i < insn->n; i++) { + err = receive_dux_commands(this_usbduxsub, SENDSINGLEAD); + if (err < 0) { + up(&this_usbduxsub->sem); + return 0; + } + one = le16_to_cpu(this_usbduxsub->insnBuffer[1]); + if (CR_RANGE(insn->chanspec) <= 1) + one = one ^ 0x800; + + data[i] = one; + } + up(&this_usbduxsub->sem); + return i; +} + +/************************************/ +/* analog out */ + +static int usbdux_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + for (i = 0; i < insn->n; i++) + data[i] = this_usbduxsub->outBuffer[chan]; + + up(&this_usbduxsub->sem); + return i; +} + +static int usbdux_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, err; + int chan = CR_CHAN(insn->chanspec); + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ao_insn_write\n", dev->minor); + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ao_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ao_insn_write: " + "ERROR: asynchronous ao_cmd is running\n", dev->minor); + up(&this_usbduxsub->sem); + return 0; + } + + for (i = 0; i < insn->n; i++) { + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ao_insn_write: data[chan=%d,i=%d]=%d\n", + dev->minor, chan, i, data[i]); + + /* number of channels: 1 */ + this_usbduxsub->dux_commands[1] = 1; + /* one 16 bit value */ + *((int16_t *) (this_usbduxsub->dux_commands + 2)) = + cpu_to_le16(data[i]); + this_usbduxsub->outBuffer[chan] = data[i]; + /* channel number */ + this_usbduxsub->dux_commands[4] = (chan << 6); + err = send_dux_commands(this_usbduxsub, SENDDACOMMANDS); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + } + up(&this_usbduxsub->sem); + + return i; +} + +static int usbdux_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum) +{ + int ret; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (trignum != 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ao_inttrig: invalid trignum\n", + dev->minor); + up(&this_usbduxsub->sem); + return -EINVAL; + } + if (!(this_usbduxsub->ao_cmd_running)) { + this_usbduxsub->ao_cmd_running = 1; + ret = usbduxsub_submit_OutURBs(this_usbduxsub); + if (ret < 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ao_inttrig: submitURB: " + "err=%d\n", dev->minor, ret); + this_usbduxsub->ao_cmd_running = 0; + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ao_inttrig but acqu is already running.\n", + dev->minor); + } + up(&this_usbduxsub->sem); + return 1; +} + +static int usbdux_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, struct comedi_cmd *cmd) +{ + int err = 0, tmp; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + if (!(this_usbduxsub->probed)) + return -ENODEV; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ao_cmdtest\n", dev->minor); + + /* make sure triggers are valid */ + /* Only immediate triggers are allowed */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + /* trigger should happen timed */ + tmp = cmd->scan_begin_src; + /* just now we scan also in the high speed mode every frame */ + /* this is due to ehci driver limitations */ + if (0) { /* (this_usbduxsub->high_speed) */ + /* start immediately a new scan */ + /* the sampling rate is set by the coversion rate */ + cmd->scan_begin_src &= TRIG_FOLLOW; + } else { + /* start a new scan (output at once) with a timer */ + cmd->scan_begin_src &= TRIG_TIMER; + } + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + /* scanning is continuous */ + tmp = cmd->convert_src; + /* we always output at 1kHz just now all channels at once */ + if (0) { /* (this_usbduxsub->high_speed) */ + /* + * in usb-2.0 only one conversion it transmitted but with 8kHz/n + */ + cmd->convert_src &= TRIG_TIMER; + } else { + /* all conversion events happen simultaneously with a rate of + * 1kHz/n */ + cmd->convert_src &= TRIG_NOW; + } + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + /* issue a trigger when scan is finished and start a new scan */ + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + /* trigger at the end of count events or not, stop condition or not */ + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique and mutually compatible + * note that mutual compatibility is not an issue here + */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* timer */ + if (cmd->scan_begin_arg < 1000000) { + cmd->scan_begin_arg = 1000000; + err++; + } + } + /* not used now, is for later use */ + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < 125000) { + cmd->convert_arg = 125000; + err++; + } + } + + /* the same argument */ + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + dev_dbg(&this_usbduxsub->interface->dev, "comedi%d: err=%d, " + "scan_begin_src=%d, scan_begin_arg=%d, convert_src=%d, " + "convert_arg=%d\n", dev->minor, err, cmd->scan_begin_src, + cmd->scan_begin_arg, cmd->convert_src, cmd->convert_arg); + + if (err) + return 3; + + return 0; +} + +static int usbdux_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int chan, gain; + int i, ret; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s\n", dev->minor, __func__); + + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + for (i = 0; i < cmd->chanlist_len; ++i) { + chan = CR_CHAN(cmd->chanlist[i]); + gain = CR_RANGE(cmd->chanlist[i]); + if (i >= NUMOUTCHANNELS) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: %s: channel list too long\n", + dev->minor, __func__); + break; + } + this_usbduxsub->dac_commands[i] = (chan << 6); + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: dac command for ch %d is %x\n", + dev->minor, i, this_usbduxsub->dac_commands[i]); + } + + /* we count in steps of 1ms (125us) */ + /* 125us mode not used yet */ + if (0) { /* (this_usbduxsub->high_speed) */ + /* 125us */ + /* timing of the conversion itself: every 125 us */ + this_usbduxsub->ao_timer = cmd->convert_arg / 125000; + } else { + /* 1ms */ + /* timing of the scan: we get all channels at once */ + this_usbduxsub->ao_timer = cmd->scan_begin_arg / 1000000; + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: scan_begin_src=%d, scan_begin_arg=%d, " + "convert_src=%d, convert_arg=%d\n", dev->minor, + cmd->scan_begin_src, cmd->scan_begin_arg, + cmd->convert_src, cmd->convert_arg); + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ao_timer=%d (ms)\n", + dev->minor, this_usbduxsub->ao_timer); + if (this_usbduxsub->ao_timer < 1) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux: ao_timer=%d, " + "scan_begin_arg=%d. " + "Not properly tested by cmdtest?\n", + dev->minor, this_usbduxsub->ao_timer, + cmd->scan_begin_arg); + up(&this_usbduxsub->sem); + return -EINVAL; + } + } + this_usbduxsub->ao_counter = this_usbduxsub->ao_timer; + + if (cmd->stop_src == TRIG_COUNT) { + /* not continuous */ + /* counter */ + /* high speed also scans everything at once */ + if (0) { /* (this_usbduxsub->high_speed) */ + this_usbduxsub->ao_sample_count = + (cmd->stop_arg) * (cmd->scan_end_arg); + } else { + /* there's no scan as the scan has been */ + /* perf inside the FX2 */ + /* data arrives as one packet */ + this_usbduxsub->ao_sample_count = cmd->stop_arg; + } + this_usbduxsub->ao_continous = 0; + } else { + /* continous acquisition */ + this_usbduxsub->ao_continous = 1; + this_usbduxsub->ao_sample_count = 0; + } + + if (cmd->start_src == TRIG_NOW) { + /* enable this acquisition operation */ + this_usbduxsub->ao_cmd_running = 1; + ret = usbduxsub_submit_OutURBs(this_usbduxsub); + if (ret < 0) { + this_usbduxsub->ao_cmd_running = 0; + /* fixme: unlink here?? */ + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + /* submit the urbs later */ + /* wait for an internal signal */ + s->async->inttrig = usbdux_ao_inttrig; + } + + up(&this_usbduxsub->sem); + return 0; +} + +static int usbdux_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << chan; /* 1 means Out */ + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + break; + default: + return -EINVAL; + break; + } + /* we don't tell the firmware here as it would take 8 frames */ + /* to submit the information. We do it in the insn_bits. */ + return insn->n; +} + +static int usbdux_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + + struct usbduxsub *this_usbduxsub = dev->private; + int err; + + if (!this_usbduxsub) + return -EFAULT; + + if (insn->n != 2) + return -EINVAL; + + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + this_usbduxsub->dux_commands[1] = s->io_bits; + this_usbduxsub->dux_commands[2] = s->state; + + /* This command also tells the firmware to return */ + /* the digital input lines */ + err = send_dux_commands(this_usbduxsub, SENDDIOBITSCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + err = receive_dux_commands(this_usbduxsub, SENDDIOBITSCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + data[1] = le16_to_cpu(this_usbduxsub->insnBuffer[1]); + up(&this_usbduxsub->sem); + return 2; +} + +/* reads the 4 counters, only two are used just now */ +static int usbdux_counter_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int chan = insn->chanspec; + int err; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + + err = send_dux_commands(this_usbduxsub, READCOUNTERCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + err = receive_dux_commands(this_usbduxsub, READCOUNTERCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + data[0] = le16_to_cpu(this_usbduxsub->insnBuffer[chan + 1]); + up(&this_usbduxsub->sem); + return 1; +} + +static int usbdux_counter_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int err; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + + this_usbduxsub->dux_commands[1] = insn->chanspec; + *((int16_t *) (this_usbduxsub->dux_commands + 2)) = cpu_to_le16(*data); + + err = send_dux_commands(this_usbduxsub, WRITECOUNTERCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + up(&this_usbduxsub->sem); + + return 1; +} + +static int usbdux_counter_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + /* nothing to do so far */ + return 2; +} + +/***********************************/ +/* PWM */ + +static int usbduxsub_unlink_PwmURBs(struct usbduxsub *usbduxsub_tmp) +{ + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbPwm) { + if (usbduxsub_tmp->urbPwm) + usb_kill_urb(usbduxsub_tmp->urbPwm); + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi: unlinked PwmURB: res=%d\n", err); + } + return err; +} + +/* This cancels a running acquisition operation + * in any context. + */ +static int usbdux_pwm_stop(struct usbduxsub *this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, "comedi: %s\n", __func__); + if (do_unlink) + ret = usbduxsub_unlink_PwmURBs(this_usbduxsub); + + this_usbduxsub->pwm_cmd_running = 0; + + return ret; +} + +/* force unlink - is called by comedi */ +static int usbdux_pwm_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int res = 0; + + /* unlink only if it is really running */ + res = usbdux_pwm_stop(this_usbduxsub, this_usbduxsub->pwm_cmd_running); + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi %d: sending pwm off command to the usb device.\n", + dev->minor); + + return send_dux_commands(this_usbduxsub, SENDPWMOFF); +} + +static void usbduxsub_pwm_irq(struct urb *urb) +{ + int ret; + struct usbduxsub *this_usbduxsub; + struct comedi_device *this_comedidev; + struct comedi_subdevice *s; + + /* printk(KERN_DEBUG "PWM: IRQ\n"); */ + + /* the context variable points to the subdevice */ + this_comedidev = urb->context; + /* the private structure of the subdevice is struct usbduxsub */ + this_usbduxsub = this_comedidev->private; + + s = this_comedidev->subdevices + SUBDEV_DA; + + switch (urb->status) { + case 0: + /* success */ + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* + * after an unlink command, unplug, ... etc + * no unlink needed here. Already shutting down. + */ + if (this_usbduxsub->pwm_cmd_running) + usbdux_pwm_stop(this_usbduxsub, 0); + + return; + + default: + /* a real error */ + if (this_usbduxsub->pwm_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi_: Non-zero urb status received in " + "pwm intr context: %d\n", urb->status); + usbdux_pwm_stop(this_usbduxsub, 0); + } + return; + } + + /* are we actually running? */ + if (!(this_usbduxsub->pwm_cmd_running)) + return; + + urb->transfer_buffer_length = this_usbduxsub->sizePwmBuf; + urb->dev = this_usbduxsub->usbdev; + urb->status = 0; + if (this_usbduxsub->pwm_cmd_running) { + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi_: pwm urb resubm failed in int-cont. " + "ret=%d", ret); + if (ret == EL2NSYNC) + dev_err(&this_usbduxsub->interface->dev, + "buggy USB host controller or bug in " + "IRQ handling!\n"); + + /* don't do an unlink here */ + usbdux_pwm_stop(this_usbduxsub, 0); + } + } +} + +static int usbduxsub_submit_PwmURBs(struct usbduxsub *usbduxsub) +{ + int errFlag; + + if (!usbduxsub) + return -EFAULT; + + dev_dbg(&usbduxsub->interface->dev, "comedi_: submitting pwm-urb\n"); + + /* in case of a resubmission after an unlink... */ + usb_fill_bulk_urb(usbduxsub->urbPwm, + usbduxsub->usbdev, + usb_sndbulkpipe(usbduxsub->usbdev, PWM_EP), + usbduxsub->urbPwm->transfer_buffer, + usbduxsub->sizePwmBuf, usbduxsub_pwm_irq, + usbduxsub->comedidev); + + errFlag = usb_submit_urb(usbduxsub->urbPwm, GFP_ATOMIC); + if (errFlag) { + dev_err(&usbduxsub->interface->dev, + "comedi_: usbdux: pwm: usb_submit_urb error %d\n", + errFlag); + return errFlag; + } + return 0; +} + +static int usbdux_pwm_period(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int period) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int fx2delay = 255; + + if (period < MIN_PWM_PERIOD) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: illegal period setting for pwm.\n", + dev->minor); + return -EAGAIN; + } else { + fx2delay = period / ((int)(6 * 512 * (1.0 / 0.033))) - 6; + if (fx2delay > 255) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: period %d for pwm is too low.\n", + dev->minor, period); + return -EAGAIN; + } + } + this_usbduxsub->pwmDelay = fx2delay; + this_usbduxsub->pwmPeriod = period; + dev_dbg(&this_usbduxsub->interface->dev, "%s: frequ=%d, period=%d\n", + __func__, period, fx2delay); + return 0; +} + +/* is called from insn so there's no need to do all the sanity checks */ +static int usbdux_pwm_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int ret, i; + struct usbduxsub *this_usbduxsub = dev->private; + + dev_dbg(&this_usbduxsub->interface->dev, "comedi%d: %s\n", + dev->minor, __func__); + + if (this_usbduxsub->pwm_cmd_running) { + /* already running */ + return 0; + } + + this_usbduxsub->dux_commands[1] = ((int8_t) this_usbduxsub->pwmDelay); + ret = send_dux_commands(this_usbduxsub, SENDPWMON); + if (ret < 0) + return ret; + + /* initialise the buffer */ + for (i = 0; i < this_usbduxsub->sizePwmBuf; i++) + ((char *)(this_usbduxsub->urbPwm->transfer_buffer))[i] = 0; + + this_usbduxsub->pwm_cmd_running = 1; + ret = usbduxsub_submit_PwmURBs(this_usbduxsub); + if (ret < 0) { + this_usbduxsub->pwm_cmd_running = 0; + return ret; + } + return 0; +} + +/* generates the bit pattern for PWM with the optional sign bit */ +static int usbdux_pwm_pattern(struct comedi_device *dev, + struct comedi_subdevice *s, int channel, + unsigned int value, unsigned int sign) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int i, szbuf; + char *pBuf; + char pwm_mask; + char sgn_mask; + char c; + + if (!this_usbduxsub) + return -EFAULT; + + /* this is the DIO bit which carries the PWM data */ + pwm_mask = (1 << channel); + /* this is the DIO bit which carries the optional direction bit */ + sgn_mask = (16 << channel); + /* this is the buffer which will be filled with the with bit */ + /* pattern for one period */ + szbuf = this_usbduxsub->sizePwmBuf; + pBuf = (char *)(this_usbduxsub->urbPwm->transfer_buffer); + for (i = 0; i < szbuf; i++) { + c = *pBuf; + /* reset bits */ + c = c & (~pwm_mask); + /* set the bit as long as the index is lower than the value */ + if (i < value) + c = c | pwm_mask; + /* set the optional sign bit for a relay */ + if (!sign) { + /* positive value */ + c = c & (~sgn_mask); + } else { + /* negative value */ + c = c | sgn_mask; + } + *(pBuf++) = c; + } + return 1; +} + +static int usbdux_pwm_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + if ((insn->n) != 1) { + /* + * doesn't make sense to have more than one value here because + * it would just overwrite the PWM buffer a couple of times + */ + return -EINVAL; + } + + /* + * the sign is set via a special INSN only, this gives us 8 bits for + * normal operation + * relay sign 0 by default + */ + return usbdux_pwm_pattern(dev, s, CR_CHAN(insn->chanspec), data[0], 0); +} + +static int usbdux_pwm_read(struct comedi_device *x1, + struct comedi_subdevice *x2, struct comedi_insn *x3, + unsigned int *x4) +{ + /* not needed */ + return -EINVAL; +}; + +/* switches on/off PWM */ +static int usbdux_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + switch (data[0]) { + case INSN_CONFIG_ARM: + /* switch it on */ + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s: pwm on\n", dev->minor, __func__); + /* + * if not zero the PWM is limited to a certain time which is + * not supported here + */ + if (data[1] != 0) + return -EINVAL; + return usbdux_pwm_start(dev, s); + case INSN_CONFIG_DISARM: + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s: pwm off\n", dev->minor, __func__); + return usbdux_pwm_cancel(dev, s); + case INSN_CONFIG_GET_PWM_STATUS: + /* + * to check if the USB transmission has failed or in case PWM + * was limited to n cycles to check if it has terminated + */ + data[1] = this_usbduxsub->pwm_cmd_running; + return 0; + case INSN_CONFIG_PWM_SET_PERIOD: + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s: setting period\n", dev->minor, __func__); + return usbdux_pwm_period(dev, s, data[1]); + case INSN_CONFIG_PWM_GET_PERIOD: + data[1] = this_usbduxsub->pwmPeriod; + return 0; + case INSN_CONFIG_PWM_SET_H_BRIDGE: + /* value in the first byte and the sign in the second for a + relay */ + return usbdux_pwm_pattern(dev, s, + /* the channel number */ + CR_CHAN(insn->chanspec), + /* actual PWM data */ + data[1], + /* just a sign */ + (data[2] != 0)); + case INSN_CONFIG_PWM_GET_H_BRIDGE: + /* values are not kept in this driver, nothing to return here */ + return -EINVAL; + } + return -EINVAL; +} + +/* end of PWM */ +/*****************************************************************/ + +static void tidy_up(struct usbduxsub *usbduxsub_tmp) +{ + int i; + + if (!usbduxsub_tmp) + return; + dev_dbg(&usbduxsub_tmp->interface->dev, "comedi_: tiding up\n"); + + /* shows the usb subsystem that the driver is down */ + if (usbduxsub_tmp->interface) + usb_set_intfdata(usbduxsub_tmp->interface, NULL); + + usbduxsub_tmp->probed = 0; + + if (usbduxsub_tmp->urbIn) { + if (usbduxsub_tmp->ai_cmd_running) { + usbduxsub_tmp->ai_cmd_running = 0; + usbduxsub_unlink_InURBs(usbduxsub_tmp); + } + for (i = 0; i < usbduxsub_tmp->numOfInBuffers; i++) { + kfree(usbduxsub_tmp->urbIn[i]->transfer_buffer); + usbduxsub_tmp->urbIn[i]->transfer_buffer = NULL; + usb_kill_urb(usbduxsub_tmp->urbIn[i]); + usb_free_urb(usbduxsub_tmp->urbIn[i]); + usbduxsub_tmp->urbIn[i] = NULL; + } + kfree(usbduxsub_tmp->urbIn); + usbduxsub_tmp->urbIn = NULL; + } + if (usbduxsub_tmp->urbOut) { + if (usbduxsub_tmp->ao_cmd_running) { + usbduxsub_tmp->ao_cmd_running = 0; + usbduxsub_unlink_OutURBs(usbduxsub_tmp); + } + for (i = 0; i < usbduxsub_tmp->numOfOutBuffers; i++) { + kfree(usbduxsub_tmp->urbOut[i]->transfer_buffer); + usbduxsub_tmp->urbOut[i]->transfer_buffer = NULL; + if (usbduxsub_tmp->urbOut[i]) { + usb_kill_urb(usbduxsub_tmp->urbOut[i]); + usb_free_urb(usbduxsub_tmp->urbOut[i]); + usbduxsub_tmp->urbOut[i] = NULL; + } + } + kfree(usbduxsub_tmp->urbOut); + usbduxsub_tmp->urbOut = NULL; + } + if (usbduxsub_tmp->urbPwm) { + if (usbduxsub_tmp->pwm_cmd_running) { + usbduxsub_tmp->pwm_cmd_running = 0; + usbduxsub_unlink_PwmURBs(usbduxsub_tmp); + } + kfree(usbduxsub_tmp->urbPwm->transfer_buffer); + usbduxsub_tmp->urbPwm->transfer_buffer = NULL; + usb_kill_urb(usbduxsub_tmp->urbPwm); + usb_free_urb(usbduxsub_tmp->urbPwm); + usbduxsub_tmp->urbPwm = NULL; + } + kfree(usbduxsub_tmp->inBuffer); + usbduxsub_tmp->inBuffer = NULL; + kfree(usbduxsub_tmp->insnBuffer); + usbduxsub_tmp->insnBuffer = NULL; + kfree(usbduxsub_tmp->outBuffer); + usbduxsub_tmp->outBuffer = NULL; + kfree(usbduxsub_tmp->dac_commands); + usbduxsub_tmp->dac_commands = NULL; + kfree(usbduxsub_tmp->dux_commands); + usbduxsub_tmp->dux_commands = NULL; + usbduxsub_tmp->ai_cmd_running = 0; + usbduxsub_tmp->ao_cmd_running = 0; + usbduxsub_tmp->pwm_cmd_running = 0; +} + +static void usbdux_firmware_request_complete_handler(const struct firmware *fw, + void *context) +{ + struct usbduxsub *usbduxsub_tmp = context; + struct usb_device *usbdev = usbduxsub_tmp->usbdev; + int ret; + + if (fw == NULL) { + dev_err(&usbdev->dev, + "Firmware complete handler without firmware!\n"); + return; + } + + /* + * we need to upload the firmware here because fw will be + * freed once we've left this function + */ + ret = firmwareUpload(usbduxsub_tmp, fw->data, fw->size); + + if (ret) { + dev_err(&usbdev->dev, + "Could not upload firmware (err=%d)\n", ret); + goto out; + } + comedi_usb_auto_config(usbdev, BOARDNAME); + out: + release_firmware(fw); +} + +/* allocate memory for the urbs and initialise them */ +static int usbduxsub_probe(struct usb_interface *uinterf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(uinterf); + struct device *dev = &uinterf->dev; + int i; + int index; + int ret; + + dev_dbg(dev, "comedi_: usbdux_: " + "finding a free structure for the usb-device\n"); + + down(&start_stop_sem); + /* look for a free place in the usbdux array */ + index = -1; + for (i = 0; i < NUMUSBDUX; i++) { + if (!(usbduxsub[i].probed)) { + index = i; + break; + } + } + + /* no more space */ + if (index == -1) { + dev_err(dev, "Too many usbdux-devices connected.\n"); + up(&start_stop_sem); + return -EMFILE; + } + dev_dbg(dev, "comedi_: usbdux: " + "usbduxsub[%d] is ready to connect to comedi.\n", index); + + sema_init(&(usbduxsub[index].sem), 1); + /* save a pointer to the usb device */ + usbduxsub[index].usbdev = udev; + + /* 2.6: save the interface itself */ + usbduxsub[index].interface = uinterf; + /* get the interface number from the interface */ + usbduxsub[index].ifnum = uinterf->altsetting->desc.bInterfaceNumber; + /* hand the private data over to the usb subsystem */ + /* will be needed for disconnect */ + usb_set_intfdata(uinterf, &(usbduxsub[index])); + + dev_dbg(dev, "comedi_: usbdux: ifnum=%d\n", usbduxsub[index].ifnum); + + /* test if it is high speed (USB 2.0) */ + usbduxsub[index].high_speed = + (usbduxsub[index].usbdev->speed == USB_SPEED_HIGH); + + /* create space for the commands of the DA converter */ + usbduxsub[index].dac_commands = kzalloc(NUMOUTCHANNELS, GFP_KERNEL); + if (!usbduxsub[index].dac_commands) { + dev_err(dev, "comedi_: usbdux: " + "error alloc space for dac commands\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space for the commands going to the usb device */ + usbduxsub[index].dux_commands = kzalloc(SIZEOFDUXBUFFER, GFP_KERNEL); + if (!usbduxsub[index].dux_commands) { + dev_err(dev, "comedi_: usbdux: " + "error alloc space for dux commands\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space for the in buffer and set it to zero */ + usbduxsub[index].inBuffer = kzalloc(SIZEINBUF, GFP_KERNEL); + if (!(usbduxsub[index].inBuffer)) { + dev_err(dev, "comedi_: usbdux: " + "could not alloc space for inBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space of the instruction buffer */ + usbduxsub[index].insnBuffer = kzalloc(SIZEINSNBUF, GFP_KERNEL); + if (!(usbduxsub[index].insnBuffer)) { + dev_err(dev, "comedi_: usbdux: " + "could not alloc space for insnBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space for the outbuffer */ + usbduxsub[index].outBuffer = kzalloc(SIZEOUTBUF, GFP_KERNEL); + if (!(usbduxsub[index].outBuffer)) { + dev_err(dev, "comedi_: usbdux: " + "could not alloc space for outBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* setting to alternate setting 3: enabling iso ep and bulk ep. */ + i = usb_set_interface(usbduxsub[index].usbdev, + usbduxsub[index].ifnum, 3); + if (i < 0) { + dev_err(dev, "comedi_: usbdux%d: " + "could not set alternate setting 3 in high speed.\n", + index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENODEV; + } + if (usbduxsub[index].high_speed) + usbduxsub[index].numOfInBuffers = NUMOFINBUFFERSHIGH; + else + usbduxsub[index].numOfInBuffers = NUMOFINBUFFERSFULL; + + usbduxsub[index].urbIn = + kzalloc(sizeof(struct urb *) * usbduxsub[index].numOfInBuffers, + GFP_KERNEL); + if (!(usbduxsub[index].urbIn)) { + dev_err(dev, "comedi_: usbdux: Could not alloc. urbIn array\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + for (i = 0; i < usbduxsub[index].numOfInBuffers; i++) { + /* one frame: 1ms */ + usbduxsub[index].urbIn[i] = usb_alloc_urb(1, GFP_KERNEL); + if (usbduxsub[index].urbIn[i] == NULL) { + dev_err(dev, "comedi_: usbdux%d: " + "Could not alloc. urb(%d)\n", index, i); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbIn[i]->dev = usbduxsub[index].usbdev; + /* will be filled later with a pointer to the comedi-device */ + /* and ONLY then the urb should be submitted */ + usbduxsub[index].urbIn[i]->context = NULL; + usbduxsub[index].urbIn[i]->pipe = + usb_rcvisocpipe(usbduxsub[index].usbdev, ISOINEP); + usbduxsub[index].urbIn[i]->transfer_flags = URB_ISO_ASAP; + usbduxsub[index].urbIn[i]->transfer_buffer = + kzalloc(SIZEINBUF, GFP_KERNEL); + if (!(usbduxsub[index].urbIn[i]->transfer_buffer)) { + dev_err(dev, "comedi_: usbdux%d: " + "could not alloc. transb.\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbIn[i]->complete = usbduxsub_ai_IsocIrq; + usbduxsub[index].urbIn[i]->number_of_packets = 1; + usbduxsub[index].urbIn[i]->transfer_buffer_length = SIZEINBUF; + usbduxsub[index].urbIn[i]->iso_frame_desc[0].offset = 0; + usbduxsub[index].urbIn[i]->iso_frame_desc[0].length = SIZEINBUF; + } + + /* out */ + if (usbduxsub[index].high_speed) + usbduxsub[index].numOfOutBuffers = NUMOFOUTBUFFERSHIGH; + else + usbduxsub[index].numOfOutBuffers = NUMOFOUTBUFFERSFULL; + + usbduxsub[index].urbOut = + kzalloc(sizeof(struct urb *) * usbduxsub[index].numOfOutBuffers, + GFP_KERNEL); + if (!(usbduxsub[index].urbOut)) { + dev_err(dev, "comedi_: usbdux: " + "Could not alloc. urbOut array\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + for (i = 0; i < usbduxsub[index].numOfOutBuffers; i++) { + /* one frame: 1ms */ + usbduxsub[index].urbOut[i] = usb_alloc_urb(1, GFP_KERNEL); + if (usbduxsub[index].urbOut[i] == NULL) { + dev_err(dev, "comedi_: usbdux%d: " + "Could not alloc. urb(%d)\n", index, i); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbOut[i]->dev = usbduxsub[index].usbdev; + /* will be filled later with a pointer to the comedi-device */ + /* and ONLY then the urb should be submitted */ + usbduxsub[index].urbOut[i]->context = NULL; + usbduxsub[index].urbOut[i]->pipe = + usb_sndisocpipe(usbduxsub[index].usbdev, ISOOUTEP); + usbduxsub[index].urbOut[i]->transfer_flags = URB_ISO_ASAP; + usbduxsub[index].urbOut[i]->transfer_buffer = + kzalloc(SIZEOUTBUF, GFP_KERNEL); + if (!(usbduxsub[index].urbOut[i]->transfer_buffer)) { + dev_err(dev, "comedi_: usbdux%d: " + "could not alloc. transb.\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbOut[i]->complete = usbduxsub_ao_IsocIrq; + usbduxsub[index].urbOut[i]->number_of_packets = 1; + usbduxsub[index].urbOut[i]->transfer_buffer_length = SIZEOUTBUF; + usbduxsub[index].urbOut[i]->iso_frame_desc[0].offset = 0; + usbduxsub[index].urbOut[i]->iso_frame_desc[0].length = + SIZEOUTBUF; + if (usbduxsub[index].high_speed) { + /* uframes */ + usbduxsub[index].urbOut[i]->interval = 8; + } else { + /* frames */ + usbduxsub[index].urbOut[i]->interval = 1; + } + } + + /* pwm */ + if (usbduxsub[index].high_speed) { + /* max bulk ep size in high speed */ + usbduxsub[index].sizePwmBuf = 512; + usbduxsub[index].urbPwm = usb_alloc_urb(0, GFP_KERNEL); + if (usbduxsub[index].urbPwm == NULL) { + dev_err(dev, "comedi_: usbdux%d: " + "Could not alloc. pwm urb\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbPwm->transfer_buffer = + kzalloc(usbduxsub[index].sizePwmBuf, GFP_KERNEL); + if (!(usbduxsub[index].urbPwm->transfer_buffer)) { + dev_err(dev, "comedi_: usbdux%d: " + "could not alloc. transb. for pwm\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + } else { + usbduxsub[index].urbPwm = NULL; + usbduxsub[index].sizePwmBuf = 0; + } + + usbduxsub[index].ai_cmd_running = 0; + usbduxsub[index].ao_cmd_running = 0; + usbduxsub[index].pwm_cmd_running = 0; + + /* we've reached the bottom of the function */ + usbduxsub[index].probed = 1; + up(&start_stop_sem); + + ret = request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, + "usbdux_firmware.bin", + &udev->dev, + GFP_KERNEL, + usbduxsub + index, + usbdux_firmware_request_complete_handler); + + if (ret) { + dev_err(dev, "Could not load firmware (err=%d)\n", ret); + return ret; + } + + dev_info(dev, "comedi_: usbdux%d " + "has been successfully initialised.\n", index); + /* success */ + return 0; +} + +static void usbduxsub_disconnect(struct usb_interface *intf) +{ + struct usbduxsub *usbduxsub_tmp = usb_get_intfdata(intf); + struct usb_device *udev = interface_to_usbdev(intf); + + if (!usbduxsub_tmp) { + dev_err(&intf->dev, + "comedi_: disconnect called with null pointer.\n"); + return; + } + if (usbduxsub_tmp->usbdev != udev) { + dev_err(&intf->dev, "comedi_: BUG! called with wrong ptr!!!\n"); + return; + } + comedi_usb_auto_unconfig(udev); + down(&start_stop_sem); + down(&usbduxsub_tmp->sem); + tidy_up(usbduxsub_tmp); + up(&usbduxsub_tmp->sem); + up(&start_stop_sem); + dev_dbg(&intf->dev, "comedi_: disconnected from the usb\n"); +} + +/* is called when comedi-config is called */ +static int usbdux_attach(struct comedi_device *dev, struct comedi_devconfig *it) +{ + int ret; + int index; + int i; + struct usbduxsub *udev; + + struct comedi_subdevice *s = NULL; + dev->private = NULL; + + down(&start_stop_sem); + /* find a valid device which has been detected by the probe function of + * the usb */ + index = -1; + for (i = 0; i < NUMUSBDUX; i++) { + if ((usbduxsub[i].probed) && (!usbduxsub[i].attached)) { + index = i; + break; + } + } + + if (index < 0) { + printk(KERN_ERR "comedi%d: usbdux: error: attach failed, no " + "usbdux devs connected to the usb bus.\n", dev->minor); + up(&start_stop_sem); + return -ENODEV; + } + + udev = &usbduxsub[index]; + down(&udev->sem); + /* pointer back to the corresponding comedi device */ + udev->comedidev = dev; + + /* trying to upload the firmware into the chip */ + if (comedi_aux_data(it->options, 0) && + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { + firmwareUpload(udev, comedi_aux_data(it->options, 0), + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]); + } + + dev->board_name = BOARDNAME; + + /* set number of subdevices */ + if (udev->high_speed) { + /* with pwm */ + dev->n_subdevices = 5; + } else { + /* without pwm */ + dev->n_subdevices = 4; + } + + /* allocate space for the subdevices */ + ret = alloc_subdevices(dev, dev->n_subdevices); + if (ret < 0) { + dev_err(&udev->interface->dev, + "comedi%d: error alloc space for subdev\n", dev->minor); + up(&udev->sem); + up(&start_stop_sem); + return ret; + } + + dev_info(&udev->interface->dev, + "comedi%d: usb-device %d is attached to comedi.\n", + dev->minor, index); + /* private structure is also simply the usb-structure */ + dev->private = udev; + + /* the first subdevice is the A/D converter */ + s = dev->subdevices + SUBDEV_AD; + /* the URBs get the comedi subdevice */ + /* which is responsible for reading */ + /* this is the subdevice which reads data */ + dev->read_subdev = s; + /* the subdevice receives as private structure the */ + /* usb-structure */ + s->private = NULL; + /* analog input */ + s->type = COMEDI_SUBD_AI; + /* readable and ref is to ground */ + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + /* 8 channels */ + s->n_chan = 8; + /* length of the channellist */ + s->len_chanlist = 8; + /* callback functions */ + s->insn_read = usbdux_ai_insn_read; + s->do_cmdtest = usbdux_ai_cmdtest; + s->do_cmd = usbdux_ai_cmd; + s->cancel = usbdux_ai_cancel; + /* max value from the A/D converter (12bit) */ + s->maxdata = 0xfff; + /* range table to convert to physical units */ + s->range_table = (&range_usbdux_ai_range); + + /* analog out */ + s = dev->subdevices + SUBDEV_DA; + /* analog out */ + s->type = COMEDI_SUBD_AO; + /* backward pointer */ + dev->write_subdev = s; + /* the subdevice receives as private structure the */ + /* usb-structure */ + s->private = NULL; + /* are writable */ + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; + /* 4 channels */ + s->n_chan = 4; + /* length of the channellist */ + s->len_chanlist = 4; + /* 12 bit resolution */ + s->maxdata = 0x0fff; + /* bipolar range */ + s->range_table = (&range_usbdux_ao_range); + /* callback */ + s->do_cmdtest = usbdux_ao_cmdtest; + s->do_cmd = usbdux_ao_cmd; + s->cancel = usbdux_ao_cancel; + s->insn_read = usbdux_ao_insn_read; + s->insn_write = usbdux_ao_insn_write; + + /* digital I/O */ + s = dev->subdevices + SUBDEV_DIO; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = (&range_digital); + s->insn_bits = usbdux_dio_insn_bits; + s->insn_config = usbdux_dio_insn_config; + /* we don't use it */ + s->private = NULL; + + /* counter */ + s = dev->subdevices + SUBDEV_COUNTER; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 4; + s->maxdata = 0xFFFF; + s->insn_read = usbdux_counter_read; + s->insn_write = usbdux_counter_write; + s->insn_config = usbdux_counter_config; + + if (udev->high_speed) { + /* timer / pwm */ + s = dev->subdevices + SUBDEV_PWM; + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = SDF_WRITABLE | SDF_PWM_HBRIDGE; + s->n_chan = 8; + /* this defines the max duty cycle resolution */ + s->maxdata = udev->sizePwmBuf; + s->insn_write = usbdux_pwm_write; + s->insn_read = usbdux_pwm_read; + s->insn_config = usbdux_pwm_config; + usbdux_pwm_period(dev, s, PWM_DEFAULT_PERIOD); + } + /* finally decide that it's attached */ + udev->attached = 1; + + up(&udev->sem); + + up(&start_stop_sem); + + dev_info(&udev->interface->dev, "comedi%d: attached to usbdux.\n", + dev->minor); + + return 0; +} + +static int usbdux_detach(struct comedi_device *dev) +{ + struct usbduxsub *usbduxsub_tmp; + + if (!dev) { + printk(KERN_ERR + "comedi?: usbdux: detach without dev variable...\n"); + return -EFAULT; + } + + usbduxsub_tmp = dev->private; + if (!usbduxsub_tmp) { + printk(KERN_ERR + "comedi?: usbdux: detach without ptr to usbduxsub[]\n"); + return -EFAULT; + } + + dev_dbg(&usbduxsub_tmp->interface->dev, "comedi%d: detach usb device\n", + dev->minor); + + down(&usbduxsub_tmp->sem); + /* Don't allow detach to free the private structure */ + /* It's one entry of of usbduxsub[] */ + dev->private = NULL; + usbduxsub_tmp->attached = 0; + usbduxsub_tmp->comedidev = NULL; + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi%d: detach: successfully removed\n", dev->minor); + up(&usbduxsub_tmp->sem); + return 0; +} + +/* main driver struct */ +static struct comedi_driver driver_usbdux = { + .driver_name = "usbdux", + .module = THIS_MODULE, + .attach = usbdux_attach, + .detach = usbdux_detach, +}; + +/* Table with the USB-devices: just now only testing IDs */ +static const struct usb_device_id usbduxsub_table[] = { + {USB_DEVICE(0x13d8, 0x0001)}, + {USB_DEVICE(0x13d8, 0x0002)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usbduxsub_table); + +/* The usbduxsub-driver */ +static struct usb_driver usbduxsub_driver = { + .name = BOARDNAME, + .probe = usbduxsub_probe, + .disconnect = usbduxsub_disconnect, + .id_table = usbduxsub_table, +}; + +/* Can't use the nice macro as I have also to initialise the USB */ +/* subsystem: */ +/* registering the usb-system _and_ the comedi-driver */ +static int __init init_usbdux(void) +{ + printk(KERN_INFO KBUILD_MODNAME ": " + DRIVER_VERSION ":" DRIVER_DESC "\n"); + usb_register(&usbduxsub_driver); + comedi_driver_register(&driver_usbdux); + return 0; +} + +/* deregistering the comedi driver and the usb-subsystem */ +static void __exit exit_usbdux(void) +{ + comedi_driver_unregister(&driver_usbdux); + usb_deregister(&usbduxsub_driver); +} + +module_init(init_usbdux); +module_exit(exit_usbdux); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/usbduxfast.c b/drivers/staging/comedi/drivers/usbduxfast.c new file mode 100644 index 00000000..2a8e725b --- /dev/null +++ b/drivers/staging/comedi/drivers/usbduxfast.c @@ -0,0 +1,1825 @@ +/* + * Copyright (C) 2004 Bernd Porr, Bernd.Porr@f2s.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * I must give credit here to Chris Baugher who + * wrote the driver for AT-MIO-16d. I used some parts of this + * driver. I also must give credits to David Brownell + * who supported me with the USB development. + * + * Bernd Porr + * + * + * Revision history: + * 0.9: Dropping the first data packet which seems to be from the last transfer. + * Buffer overflows in the FX2 are handed over to comedi. + * 0.92: Dropping now 4 packets. The quad buffer has to be emptied. + * Added insn command basically for testing. Sample rate is + * 1MHz/16ch=62.5kHz + * 0.99: Ian Abbott pointed out a bug which has been corrected. Thanks! + * 0.99a: added external trigger. + * 1.00: added firmware kernel request to the driver which fixed + * udev coldplug problem + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "comedi_fc.h" +#include "../comedidev.h" + +#define DRIVER_VERSION "v1.0" +#define DRIVER_AUTHOR "Bernd Porr, BerndPorr@f2s.com" +#define DRIVER_DESC "USB-DUXfast, BerndPorr@f2s.com" +#define BOARDNAME "usbduxfast" + +/* + * timeout for the USB-transfer + */ +#define EZTIMEOUT 30 + +/* + * constants for "firmware" upload and download + */ +#define USBDUXFASTSUB_FIRMWARE 0xA0 +#define VENDOR_DIR_IN 0xC0 +#define VENDOR_DIR_OUT 0x40 + +/* + * internal addresses of the 8051 processor + */ +#define USBDUXFASTSUB_CPUCS 0xE600 + +/* + * max lenghth of the transfer-buffer for software upload + */ +#define TB_LEN 0x2000 + +/* + * input endpoint number + */ +#define BULKINEP 6 + +/* + * endpoint for the A/D channellist: bulk OUT + */ +#define CHANNELLISTEP 4 + +/* + * number of channels + */ +#define NUMCHANNELS 32 + +/* + * size of the waveform descriptor + */ +#define WAVESIZE 0x20 + +/* + * size of one A/D value + */ +#define SIZEADIN (sizeof(int16_t)) + +/* + * size of the input-buffer IN BYTES + */ +#define SIZEINBUF 512 + +/* + * 16 bytes + */ +#define SIZEINSNBUF 512 + +/* + * size of the buffer for the dux commands in bytes + */ +#define SIZEOFDUXBUFFER 256 + +/* + * number of in-URBs which receive the data: min=5 + */ +#define NUMOFINBUFFERSHIGH 10 + +/* + * total number of usbduxfast devices + */ +#define NUMUSBDUXFAST 16 + +/* + * number of subdevices + */ +#define N_SUBDEVICES 1 + +/* + * analogue in subdevice + */ +#define SUBDEV_AD 0 + +/* + * min delay steps for more than one channel + * basically when the mux gives up ;-) + * + * steps at 30MHz in the FX2 + */ +#define MIN_SAMPLING_PERIOD 9 + +/* + * max number of 1/30MHz delay steps + */ +#define MAX_SAMPLING_PERIOD 500 + +/* + * number of received packets to ignore before we start handing data + * over to comedi, it's quad buffering and we have to ignore 4 packets + */ +#define PACKETS_TO_IGNORE 4 + +/* + * comedi constants + */ +static const struct comedi_lrange range_usbduxfast_ai_range = { + 2, {BIP_RANGE(0.75), BIP_RANGE(0.5)} +}; + +/* + * private structure of one subdevice + * + * this is the structure which holds all the data of this driver + * one sub device just now: A/D + */ +struct usbduxfastsub_s { + int attached; /* is attached? */ + int probed; /* is it associated with a subdevice? */ + struct usb_device *usbdev; /* pointer to the usb-device */ + struct urb *urbIn; /* BULK-transfer handling: urb */ + int8_t *transfer_buffer; + int16_t *insnBuffer; /* input buffer for single insn */ + int ifnum; /* interface number */ + struct usb_interface *interface; /* interface structure */ + /* comedi device for the interrupt context */ + struct comedi_device *comedidev; + short int ai_cmd_running; /* asynchronous command is running */ + short int ai_continous; /* continous acquisition */ + long int ai_sample_count; /* number of samples to acquire */ + uint8_t *dux_commands; /* commands */ + int ignore; /* counter which ignores the first + buffers */ + struct semaphore sem; +}; + +/* + * The pointer to the private usb-data of the driver + * is also the private data for the comedi-device. + * This has to be global as the usb subsystem needs + * global variables. The other reason is that this + * structure must be there _before_ any comedi + * command is issued. The usb subsystem must be + * initialised before comedi can access it. + */ +static struct usbduxfastsub_s usbduxfastsub[NUMUSBDUXFAST]; + +static DEFINE_SEMAPHORE(start_stop_sem); + +/* + * bulk transfers to usbduxfast + */ +#define SENDADCOMMANDS 0 +#define SENDINITEP6 1 + +static int send_dux_commands(struct usbduxfastsub_s *udfs, int cmd_type) +{ + int tmp, nsent; + + udfs->dux_commands[0] = cmd_type; + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: usbduxfast: dux_commands: ", + udfs->comedidev->minor); + for (tmp = 0; tmp < SIZEOFDUXBUFFER; tmp++) + printk(" %02x", udfs->dux_commands[tmp]); + printk("\n"); +#endif + + tmp = usb_bulk_msg(udfs->usbdev, + usb_sndbulkpipe(udfs->usbdev, CHANNELLISTEP), + udfs->dux_commands, SIZEOFDUXBUFFER, &nsent, 10000); + if (tmp < 0) + printk(KERN_ERR "comedi%d: could not transmit dux_commands to" + "the usb-device, err=%d\n", udfs->comedidev->minor, tmp); + return tmp; +} + +/* + * Stops the data acquision. + * It should be safe to call this function from any context. + */ +static int usbduxfastsub_unlink_InURBs(struct usbduxfastsub_s *udfs) +{ + int j = 0; + int err = 0; + + if (udfs && udfs->urbIn) { + udfs->ai_cmd_running = 0; + /* waits until a running transfer is over */ + usb_kill_urb(udfs->urbIn); + j = 0; + } +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi: usbduxfast: unlinked InURB: res=%d\n", j); +#endif + return err; +} + +/* + * This will stop a running acquisition operation. + * Is called from within this driver from both the + * interrupt context and from comedi. + */ +static int usbduxfast_ai_stop(struct usbduxfastsub_s *udfs, int do_unlink) +{ + int ret = 0; + + if (!udfs) { + printk(KERN_ERR "comedi?: usbduxfast_ai_stop: udfs=NULL!\n"); + return -EFAULT; + } +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi: usbduxfast_ai_stop\n"); +#endif + + udfs->ai_cmd_running = 0; + + if (do_unlink) + /* stop aquistion */ + ret = usbduxfastsub_unlink_InURBs(udfs); + + return ret; +} + +/* + * This will cancel a running acquisition operation. + * This is called by comedi but never from inside the driver. + */ +static int usbduxfast_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxfastsub_s *udfs; + int ret; + + /* force unlink of all urbs */ +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi: usbduxfast_ai_cancel\n"); +#endif + udfs = dev->private; + if (!udfs) { + printk(KERN_ERR "comedi: usbduxfast_ai_cancel: udfs=NULL\n"); + return -EFAULT; + } + down(&udfs->sem); + if (!udfs->probed) { + up(&udfs->sem); + return -ENODEV; + } + /* unlink */ + ret = usbduxfast_ai_stop(udfs, 1); + up(&udfs->sem); + + return ret; +} + +/* + * analogue IN + * interrupt service routine + */ +static void usbduxfastsub_ai_Irq(struct urb *urb) +{ + int n, err; + struct usbduxfastsub_s *udfs; + struct comedi_device *this_comedidev; + struct comedi_subdevice *s; + uint16_t *p; + + /* sanity checks - is the urb there? */ + if (!urb) { + printk(KERN_ERR "comedi_: usbduxfast_: ao int-handler called " + "with urb=NULL!\n"); + return; + } + /* the context variable points to the subdevice */ + this_comedidev = urb->context; + if (!this_comedidev) { + printk(KERN_ERR "comedi_: usbduxfast_: urb context is a NULL " + "pointer!\n"); + return; + } + /* the private structure of the subdevice is usbduxfastsub_s */ + udfs = this_comedidev->private; + if (!udfs) { + printk(KERN_ERR "comedi_: usbduxfast_: private of comedi " + "subdev is a NULL pointer!\n"); + return; + } + /* are we running a command? */ + if (unlikely(!udfs->ai_cmd_running)) { + /* + * not running a command + * do not continue execution if no asynchronous command + * is running in particular not resubmit + */ + return; + } + + if (unlikely(!udfs->attached)) { + /* no comedi device there */ + return; + } + /* subdevice which is the AD converter */ + s = this_comedidev->subdevices + SUBDEV_AD; + + /* first we test if something unusual has just happened */ + switch (urb->status) { + case 0: + break; + + /* + * happens after an unlink command or when the device + * is plugged out + */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* tell this comedi */ + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(udfs->comedidev, s); + /* stop the transfer w/o unlink */ + usbduxfast_ai_stop(udfs, 0); + return; + + default: + printk("comedi%d: usbduxfast: non-zero urb status received in " + "ai intr context: %d\n", + udfs->comedidev->minor, urb->status); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(udfs->comedidev, s); + usbduxfast_ai_stop(udfs, 0); + return; + } + + p = urb->transfer_buffer; + if (!udfs->ignore) { + if (!udfs->ai_continous) { + /* not continuous, fixed number of samples */ + n = urb->actual_length / sizeof(uint16_t); + if (unlikely(udfs->ai_sample_count < n)) { + /* + * we have send only a fraction of the bytes + * received + */ + cfc_write_array_to_buffer(s, + urb->transfer_buffer, + udfs->ai_sample_count + * sizeof(uint16_t)); + usbduxfast_ai_stop(udfs, 0); + /* tell comedi that the acquistion is over */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(udfs->comedidev, s); + return; + } + udfs->ai_sample_count -= n; + } + /* write the full buffer to comedi */ + err = cfc_write_array_to_buffer(s, urb->transfer_buffer, + urb->actual_length); + if (unlikely(err == 0)) { + /* buffer overflow */ + usbduxfast_ai_stop(udfs, 0); + return; + } + + /* tell comedi that data is there */ + comedi_event(udfs->comedidev, s); + + } else { + /* ignore this packet */ + udfs->ignore--; + } + + /* + * command is still running + * resubmit urb for BULK transfer + */ + urb->dev = udfs->usbdev; + urb->status = 0; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + printk(KERN_ERR "comedi%d: usbduxfast: urb resubm failed: %d", + udfs->comedidev->minor, err); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(udfs->comedidev, s); + usbduxfast_ai_stop(udfs, 0); + } +} + +static int usbduxfastsub_start(struct usbduxfastsub_s *udfs) +{ + int ret; + unsigned char local_transfer_buffer[16]; + + /* 7f92 to zero */ + local_transfer_buffer[0] = 0; + /* bRequest, "Firmware" */ + ret = usb_control_msg(udfs->usbdev, usb_sndctrlpipe(udfs->usbdev, 0), USBDUXFASTSUB_FIRMWARE, + VENDOR_DIR_OUT, /* bmRequestType */ + USBDUXFASTSUB_CPUCS, /* Value */ + 0x0000, /* Index */ + /* address of the transfer buffer */ + local_transfer_buffer, + 1, /* Length */ + EZTIMEOUT); /* Timeout */ + if (ret < 0) { + printk("comedi_: usbduxfast_: control msg failed (start)\n"); + return ret; + } + + return 0; +} + +static int usbduxfastsub_stop(struct usbduxfastsub_s *udfs) +{ + int ret; + unsigned char local_transfer_buffer[16]; + + /* 7f92 to one */ + local_transfer_buffer[0] = 1; + /* bRequest, "Firmware" */ + ret = usb_control_msg(udfs->usbdev, usb_sndctrlpipe(udfs->usbdev, 0), USBDUXFASTSUB_FIRMWARE, + VENDOR_DIR_OUT, /* bmRequestType */ + USBDUXFASTSUB_CPUCS, /* Value */ + 0x0000, /* Index */ + local_transfer_buffer, 1, /* Length */ + EZTIMEOUT); /* Timeout */ + if (ret < 0) { + printk(KERN_ERR "comedi_: usbduxfast: control msg failed " + "(stop)\n"); + return ret; + } + + return 0; +} + +static int usbduxfastsub_upload(struct usbduxfastsub_s *udfs, + unsigned char *local_transfer_buffer, + unsigned int startAddr, unsigned int len) +{ + int ret; + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi: usbduxfast: uploading %d bytes", len); + printk(KERN_DEBUG " to addr %d, first byte=%d.\n", + startAddr, local_transfer_buffer[0]); +#endif + /* brequest, firmware */ + ret = usb_control_msg(udfs->usbdev, usb_sndctrlpipe(udfs->usbdev, 0), USBDUXFASTSUB_FIRMWARE, + VENDOR_DIR_OUT, /* bmRequestType */ + startAddr, /* value */ + 0x0000, /* index */ + /* our local safe buffer */ + local_transfer_buffer, + len, /* length */ + EZTIMEOUT); /* timeout */ + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi_: usbduxfast: result=%d\n", ret); +#endif + + if (ret < 0) { + printk(KERN_ERR "comedi_: usbduxfast: uppload failed\n"); + return ret; + } + + return 0; +} + +static int usbduxfastsub_submit_InURBs(struct usbduxfastsub_s *udfs) +{ + int ret; + + if (!udfs) + return -EFAULT; + + usb_fill_bulk_urb(udfs->urbIn, udfs->usbdev, + usb_rcvbulkpipe(udfs->usbdev, BULKINEP), + udfs->transfer_buffer, + SIZEINBUF, usbduxfastsub_ai_Irq, udfs->comedidev); + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: usbduxfast: submitting in-urb: " + "0x%p,0x%p\n", udfs->comedidev->minor, udfs->urbIn->context, + udfs->urbIn->dev); +#endif + ret = usb_submit_urb(udfs->urbIn, GFP_ATOMIC); + if (ret) { + printk(KERN_ERR "comedi_: usbduxfast: ai: usb_submit_urb error" + " %d\n", ret); + return ret; + } + return 0; +} + +static int usbduxfast_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0, stop_mask = 0; + long int steps, tmp; + int minSamplPer; + struct usbduxfastsub_s *udfs = dev->private; + + if (!udfs->probed) + return -ENODEV; + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: usbduxfast_ai_cmdtest\n", dev->minor); + printk(KERN_DEBUG "comedi%d: usbduxfast: convert_arg=%u " + "scan_begin_arg=%u\n", + dev->minor, cmd->convert_arg, cmd->scan_begin_arg); +#endif + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + stop_mask = TRIG_COUNT | TRIG_NONE; + cmd->stop_src &= stop_mask; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique and mutually compatible + */ + + if (cmd->start_src != TRIG_NOW && + cmd->start_src != TRIG_EXT && cmd->start_src != TRIG_INT) + err++; + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_EXT && cmd->stop_src != TRIG_NONE) + err++; + + /* can't have external stop and start triggers at once */ + if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_src == TRIG_NOW && cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (!cmd->chanlist_len) + err++; + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->chanlist_len == 1) + minSamplPer = 1; + else + minSamplPer = MIN_SAMPLING_PERIOD; + + if (cmd->convert_src == TRIG_TIMER) { + steps = cmd->convert_arg * 30; + if (steps < (minSamplPer * 1000)) + steps = minSamplPer * 1000; + + if (steps > (MAX_SAMPLING_PERIOD * 1000)) + steps = MAX_SAMPLING_PERIOD * 1000; + + /* calc arg again */ + tmp = steps / 30; + if (cmd->convert_arg != tmp) { + cmd->convert_arg = tmp; + err++; + } + } + + if (cmd->scan_begin_src == TRIG_TIMER) + err++; + + /* stop source */ + switch (cmd->stop_src) { + case TRIG_COUNT: + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + break; + case TRIG_NONE: + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + break; + /* + * TRIG_EXT doesn't care since it doesn't trigger + * off a numbered channel + */ + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + return 0; + +} + +static int usbduxfast_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, + unsigned int trignum) +{ + int ret; + struct usbduxfastsub_s *udfs = dev->private; + + if (!udfs) + return -EFAULT; + + down(&udfs->sem); + if (!udfs->probed) { + up(&udfs->sem); + return -ENODEV; + } +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: usbduxfast_ai_inttrig\n", dev->minor); +#endif + + if (trignum != 0) { + printk(KERN_ERR "comedi%d: usbduxfast_ai_inttrig: invalid" + " trignum\n", dev->minor); + up(&udfs->sem); + return -EINVAL; + } + if (!udfs->ai_cmd_running) { + udfs->ai_cmd_running = 1; + ret = usbduxfastsub_submit_InURBs(udfs); + if (ret < 0) { + printk(KERN_ERR "comedi%d: usbduxfast_ai_inttrig: " + "urbSubmit: err=%d\n", dev->minor, ret); + udfs->ai_cmd_running = 0; + up(&udfs->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + printk(KERN_ERR "comedi%d: ai_inttrig but acqu is already" + " running\n", dev->minor); + } + up(&udfs->sem); + return 1; +} + +/* + * offsets for the GPIF bytes + * the first byte is the command byte + */ +#define LENBASE (1+0x00) +#define OPBASE (1+0x08) +#define OUTBASE (1+0x10) +#define LOGBASE (1+0x18) + +static int usbduxfast_ai_cmd(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int chan, gain, rngmask = 0xff; + int i, j, ret; + struct usbduxfastsub_s *udfs; + int result; + long steps, steps_tmp; + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: usbduxfast_ai_cmd\n", dev->minor); +#endif + udfs = dev->private; + if (!udfs) + return -EFAULT; + + down(&udfs->sem); + if (!udfs->probed) { + up(&udfs->sem); + return -ENODEV; + } + if (udfs->ai_cmd_running) { + printk(KERN_ERR "comedi%d: ai_cmd not possible. Another ai_cmd" + " is running.\n", dev->minor); + up(&udfs->sem); + return -EBUSY; + } + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + + /* + * ignore the first buffers from the device if there + * is an error condition + */ + udfs->ignore = PACKETS_TO_IGNORE; + + if (cmd->chanlist_len > 0) { + gain = CR_RANGE(cmd->chanlist[0]); + for (i = 0; i < cmd->chanlist_len; ++i) { + chan = CR_CHAN(cmd->chanlist[i]); + if (chan != i) { + printk(KERN_ERR "comedi%d: cmd is accepting " + "only consecutive channels.\n", + dev->minor); + up(&udfs->sem); + return -EINVAL; + } + if ((gain != CR_RANGE(cmd->chanlist[i])) + && (cmd->chanlist_len > 3)) { + printk(KERN_ERR "comedi%d: the gain must be" + " the same for all channels.\n", + dev->minor); + up(&udfs->sem); + return -EINVAL; + } + if (i >= NUMCHANNELS) { + printk(KERN_ERR "comedi%d: channel list too" + " long\n", dev->minor); + break; + } + } + } + steps = 0; + if (cmd->scan_begin_src == TRIG_TIMER) { + printk(KERN_ERR "comedi%d: usbduxfast: " + "scan_begin_src==TRIG_TIMER not valid.\n", dev->minor); + up(&udfs->sem); + return -EINVAL; + } + if (cmd->convert_src == TRIG_TIMER) + steps = (cmd->convert_arg * 30) / 1000; + + if ((steps < MIN_SAMPLING_PERIOD) && (cmd->chanlist_len != 1)) { + printk(KERN_ERR "comedi%d: usbduxfast: ai_cmd: steps=%ld, " + "scan_begin_arg=%d. Not properly tested by cmdtest?\n", + dev->minor, steps, cmd->scan_begin_arg); + up(&udfs->sem); + return -EINVAL; + } + if (steps > MAX_SAMPLING_PERIOD) { + printk(KERN_ERR "comedi%d: usbduxfast: ai_cmd: sampling rate " + "too low.\n", dev->minor); + up(&udfs->sem); + return -EINVAL; + } + if ((cmd->start_src == TRIG_EXT) && (cmd->chanlist_len != 1) + && (cmd->chanlist_len != 16)) { + printk(KERN_ERR "comedi%d: usbduxfast: ai_cmd: TRIG_EXT only" + " with 1 or 16 channels possible.\n", dev->minor); + up(&udfs->sem); + return -EINVAL; + } +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: usbduxfast: steps=%ld, convert_arg=%u\n", + dev->minor, steps, cmd->convert_arg); +#endif + + switch (cmd->chanlist_len) { + case 1: + /* + * one channel + */ + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* + * for external trigger: looping in this state until + * the RDY0 pin becomes zero + */ + + /* we loop here until ready has been set */ + if (cmd->start_src == TRIG_EXT) { + /* branch back to state 0 */ + udfs->dux_commands[LENBASE + 0] = 0x01; + /* deceision state w/o data */ + udfs->dux_commands[OPBASE + 0] = 0x01; + udfs->dux_commands[OUTBASE + 0] = 0xFF & rngmask; + /* RDY0 = 0 */ + udfs->dux_commands[LOGBASE + 0] = 0x00; + } else { /* we just proceed to state 1 */ + udfs->dux_commands[LENBASE + 0] = 1; + udfs->dux_commands[OPBASE + 0] = 0; + udfs->dux_commands[OUTBASE + 0] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 0] = 0; + } + + if (steps < MIN_SAMPLING_PERIOD) { + /* for fast single channel aqu without mux */ + if (steps <= 1) { + /* + * we just stay here at state 1 and rexecute + * the same state this gives us 30MHz sampling + * rate + */ + + /* branch back to state 1 */ + udfs->dux_commands[LENBASE + 1] = 0x89; + /* deceision state with data */ + udfs->dux_commands[OPBASE + 1] = 0x03; + udfs->dux_commands[OUTBASE + 1] = + 0xFF & rngmask; + /* doesn't matter */ + udfs->dux_commands[LOGBASE + 1] = 0xFF; + } else { + /* + * we loop through two states: data and delay + * max rate is 15MHz + */ + udfs->dux_commands[LENBASE + 1] = steps - 1; + /* data */ + udfs->dux_commands[OPBASE + 1] = 0x02; + udfs->dux_commands[OUTBASE + 1] = + 0xFF & rngmask; + /* doesn't matter */ + udfs->dux_commands[LOGBASE + 1] = 0; + /* branch back to state 1 */ + udfs->dux_commands[LENBASE + 2] = 0x09; + /* deceision state w/o data */ + udfs->dux_commands[OPBASE + 2] = 0x01; + udfs->dux_commands[OUTBASE + 2] = + 0xFF & rngmask; + /* doesn't matter */ + udfs->dux_commands[LOGBASE + 2] = 0xFF; + } + } else { + /* + * we loop through 3 states: 2x delay and 1x data + * this gives a min sampling rate of 60kHz + */ + + /* we have 1 state with duration 1 */ + steps = steps - 1; + + /* do the first part of the delay */ + udfs->dux_commands[LENBASE + 1] = steps / 2; + udfs->dux_commands[OPBASE + 1] = 0; + udfs->dux_commands[OUTBASE + 1] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 1] = 0; + + /* and the second part */ + udfs->dux_commands[LENBASE + 2] = steps - steps / 2; + udfs->dux_commands[OPBASE + 2] = 0; + udfs->dux_commands[OUTBASE + 2] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 2] = 0; + + /* get the data and branch back */ + + /* branch back to state 1 */ + udfs->dux_commands[LENBASE + 3] = 0x09; + /* deceision state w data */ + udfs->dux_commands[OPBASE + 3] = 0x03; + udfs->dux_commands[OUTBASE + 3] = 0xFF & rngmask; + /* doesn't matter */ + udfs->dux_commands[LOGBASE + 3] = 0xFF; + } + break; + + case 2: + /* + * two channels + * commit data to the FIFO + */ + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + udfs->dux_commands[LENBASE + 0] = 1; + /* data */ + udfs->dux_commands[OPBASE + 0] = 0x02; + udfs->dux_commands[OUTBASE + 0] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 0] = 0; + + /* we have 1 state with duration 1: state 0 */ + steps_tmp = steps - 1; + + if (CR_RANGE(cmd->chanlist[1]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the first part of the delay */ + udfs->dux_commands[LENBASE + 1] = steps_tmp / 2; + udfs->dux_commands[OPBASE + 1] = 0; + /* count */ + udfs->dux_commands[OUTBASE + 1] = 0xFE & rngmask; + udfs->dux_commands[LOGBASE + 1] = 0; + + /* and the second part */ + udfs->dux_commands[LENBASE + 2] = steps_tmp - steps_tmp / 2; + udfs->dux_commands[OPBASE + 2] = 0; + udfs->dux_commands[OUTBASE + 2] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 2] = 0; + + udfs->dux_commands[LENBASE + 3] = 1; + /* data */ + udfs->dux_commands[OPBASE + 3] = 0x02; + udfs->dux_commands[OUTBASE + 3] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 3] = 0; + + /* + * we have 2 states with duration 1: step 6 and + * the IDLE state + */ + steps_tmp = steps - 2; + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the first part of the delay */ + udfs->dux_commands[LENBASE + 4] = steps_tmp / 2; + udfs->dux_commands[OPBASE + 4] = 0; + /* reset */ + udfs->dux_commands[OUTBASE + 4] = (0xFF - 0x02) & rngmask; + udfs->dux_commands[LOGBASE + 4] = 0; + + /* and the second part */ + udfs->dux_commands[LENBASE + 5] = steps_tmp - steps_tmp / 2; + udfs->dux_commands[OPBASE + 5] = 0; + udfs->dux_commands[OUTBASE + 5] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 5] = 0; + + udfs->dux_commands[LENBASE + 6] = 1; + udfs->dux_commands[OPBASE + 6] = 0; + udfs->dux_commands[OUTBASE + 6] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 6] = 0; + break; + + case 3: + /* + * three channels + */ + for (j = 0; j < 1; j++) { + if (CR_RANGE(cmd->chanlist[j]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + /* + * commit data to the FIFO and do the first part + * of the delay + */ + udfs->dux_commands[LENBASE + j * 2] = steps / 2; + /* data */ + udfs->dux_commands[OPBASE + j * 2] = 0x02; + /* no change */ + udfs->dux_commands[OUTBASE + j * 2] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + j * 2] = 0; + + if (CR_RANGE(cmd->chanlist[j + 1]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the second part of the delay */ + udfs->dux_commands[LENBASE + j * 2 + 1] = + steps - steps / 2; + /* no data */ + udfs->dux_commands[OPBASE + j * 2 + 1] = 0; + /* count */ + udfs->dux_commands[OUTBASE + j * 2 + 1] = + 0xFE & rngmask; + udfs->dux_commands[LOGBASE + j * 2 + 1] = 0; + } + + /* 2 steps with duration 1: the idele step and step 6: */ + steps_tmp = steps - 2; + + /* commit data to the FIFO and do the first part of the delay */ + udfs->dux_commands[LENBASE + 4] = steps_tmp / 2; + /* data */ + udfs->dux_commands[OPBASE + 4] = 0x02; + udfs->dux_commands[OUTBASE + 4] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 4] = 0; + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* do the second part of the delay */ + udfs->dux_commands[LENBASE + 5] = steps_tmp - steps_tmp / 2; + /* no data */ + udfs->dux_commands[OPBASE + 5] = 0; + /* reset */ + udfs->dux_commands[OUTBASE + 5] = (0xFF - 0x02) & rngmask; + udfs->dux_commands[LOGBASE + 5] = 0; + + udfs->dux_commands[LENBASE + 6] = 1; + udfs->dux_commands[OPBASE + 6] = 0; + udfs->dux_commands[OUTBASE + 6] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 6] = 0; + + case 16: + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + if (cmd->start_src == TRIG_EXT) { + /* + * we loop here until ready has been set + */ + + /* branch back to state 0 */ + udfs->dux_commands[LENBASE + 0] = 0x01; + /* deceision state w/o data */ + udfs->dux_commands[OPBASE + 0] = 0x01; + /* reset */ + udfs->dux_commands[OUTBASE + 0] = + (0xFF - 0x02) & rngmask; + /* RDY0 = 0 */ + udfs->dux_commands[LOGBASE + 0] = 0x00; + } else { + /* + * we just proceed to state 1 + */ + + /* 30us reset pulse */ + udfs->dux_commands[LENBASE + 0] = 255; + udfs->dux_commands[OPBASE + 0] = 0; + /* reset */ + udfs->dux_commands[OUTBASE + 0] = + (0xFF - 0x02) & rngmask; + udfs->dux_commands[LOGBASE + 0] = 0; + } + + /* commit data to the FIFO */ + udfs->dux_commands[LENBASE + 1] = 1; + /* data */ + udfs->dux_commands[OPBASE + 1] = 0x02; + udfs->dux_commands[OUTBASE + 1] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 1] = 0; + + /* we have 2 states with duration 1 */ + steps = steps - 2; + + /* do the first part of the delay */ + udfs->dux_commands[LENBASE + 2] = steps / 2; + udfs->dux_commands[OPBASE + 2] = 0; + udfs->dux_commands[OUTBASE + 2] = 0xFE & rngmask; + udfs->dux_commands[LOGBASE + 2] = 0; + + /* and the second part */ + udfs->dux_commands[LENBASE + 3] = steps - steps / 2; + udfs->dux_commands[OPBASE + 3] = 0; + udfs->dux_commands[OUTBASE + 3] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 3] = 0; + + /* branch back to state 1 */ + udfs->dux_commands[LENBASE + 4] = 0x09; + /* deceision state w/o data */ + udfs->dux_commands[OPBASE + 4] = 0x01; + udfs->dux_commands[OUTBASE + 4] = 0xFF & rngmask; + /* doesn't matter */ + udfs->dux_commands[LOGBASE + 4] = 0xFF; + + break; + + default: + printk(KERN_ERR "comedi %d: unsupported combination of " + "channels\n", dev->minor); + up(&udfs->sem); + return -EFAULT; + } + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi %d: sending commands to the usb device\n", + dev->minor); +#endif + /* 0 means that the AD commands are sent */ + result = send_dux_commands(udfs, SENDADCOMMANDS); + if (result < 0) { + printk(KERN_ERR "comedi%d: adc command could not be submitted." + "Aborting...\n", dev->minor); + up(&udfs->sem); + return result; + } + if (cmd->stop_src == TRIG_COUNT) { + udfs->ai_sample_count = cmd->stop_arg * cmd->scan_end_arg; + if (udfs->ai_sample_count < 1) { + printk(KERN_ERR "comedi%d: " + "(cmd->stop_arg)*(cmd->scan_end_arg)<1, " + "aborting.\n", dev->minor); + up(&udfs->sem); + return -EFAULT; + } + udfs->ai_continous = 0; + } else { + /* continous acquisition */ + udfs->ai_continous = 1; + udfs->ai_sample_count = 0; + } + + if ((cmd->start_src == TRIG_NOW) || (cmd->start_src == TRIG_EXT)) { + /* enable this acquisition operation */ + udfs->ai_cmd_running = 1; + ret = usbduxfastsub_submit_InURBs(udfs); + if (ret < 0) { + udfs->ai_cmd_running = 0; + /* fixme: unlink here?? */ + up(&udfs->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + /* + * TRIG_INT + * don't enable the acquision operation + * wait for an internal signal + */ + s->async->inttrig = usbduxfast_ai_inttrig; + } + up(&udfs->sem); + + return 0; +} + +/* + * Mode 0 is used to get a single conversion on demand. + */ +static int usbduxfast_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, j, n, actual_length; + int chan, range, rngmask; + int err; + struct usbduxfastsub_s *udfs; + + udfs = dev->private; + if (!udfs) { + printk(KERN_ERR "comedi%d: ai_insn_read: no usb dev.\n", + dev->minor); + return -ENODEV; + } +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: ai_insn_read, insn->n=%d, " + "insn->subdev=%d\n", dev->minor, insn->n, insn->subdev); +#endif + down(&udfs->sem); + if (!udfs->probed) { + up(&udfs->sem); + return -ENODEV; + } + if (udfs->ai_cmd_running) { + printk(KERN_ERR "comedi%d: ai_insn_read not possible. Async " + "Command is running.\n", dev->minor); + up(&udfs->sem); + return -EBUSY; + } + /* sample one channel */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + /* set command for the first channel */ + + if (range > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + /* commit data to the FIFO */ + udfs->dux_commands[LENBASE + 0] = 1; + /* data */ + udfs->dux_commands[OPBASE + 0] = 0x02; + udfs->dux_commands[OUTBASE + 0] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 0] = 0; + + /* do the first part of the delay */ + udfs->dux_commands[LENBASE + 1] = 12; + udfs->dux_commands[OPBASE + 1] = 0; + udfs->dux_commands[OUTBASE + 1] = 0xFE & rngmask; + udfs->dux_commands[LOGBASE + 1] = 0; + + udfs->dux_commands[LENBASE + 2] = 1; + udfs->dux_commands[OPBASE + 2] = 0; + udfs->dux_commands[OUTBASE + 2] = 0xFE & rngmask; + udfs->dux_commands[LOGBASE + 2] = 0; + + udfs->dux_commands[LENBASE + 3] = 1; + udfs->dux_commands[OPBASE + 3] = 0; + udfs->dux_commands[OUTBASE + 3] = 0xFE & rngmask; + udfs->dux_commands[LOGBASE + 3] = 0; + + udfs->dux_commands[LENBASE + 4] = 1; + udfs->dux_commands[OPBASE + 4] = 0; + udfs->dux_commands[OUTBASE + 4] = 0xFE & rngmask; + udfs->dux_commands[LOGBASE + 4] = 0; + + /* second part */ + udfs->dux_commands[LENBASE + 5] = 12; + udfs->dux_commands[OPBASE + 5] = 0; + udfs->dux_commands[OUTBASE + 5] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 5] = 0; + + udfs->dux_commands[LENBASE + 6] = 1; + udfs->dux_commands[OPBASE + 6] = 0; + udfs->dux_commands[OUTBASE + 6] = 0xFF & rngmask; + udfs->dux_commands[LOGBASE + 0] = 0; + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi %d: sending commands to the usb device\n", + dev->minor); +#endif + /* 0 means that the AD commands are sent */ + err = send_dux_commands(udfs, SENDADCOMMANDS); + if (err < 0) { + printk(KERN_ERR "comedi%d: adc command could not be submitted." + "Aborting...\n", dev->minor); + up(&udfs->sem); + return err; + } +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: usbduxfast: submitting in-urb: " + "0x%p,0x%p\n", udfs->comedidev->minor, udfs->urbIn->context, + udfs->urbIn->dev); +#endif + for (i = 0; i < PACKETS_TO_IGNORE; i++) { + err = usb_bulk_msg(udfs->usbdev, + usb_rcvbulkpipe(udfs->usbdev, BULKINEP), + udfs->transfer_buffer, SIZEINBUF, + &actual_length, 10000); + if (err < 0) { + printk(KERN_ERR "comedi%d: insn timeout. No data.\n", + dev->minor); + up(&udfs->sem); + return err; + } + } + /* data points */ + for (i = 0; i < insn->n;) { + err = usb_bulk_msg(udfs->usbdev, + usb_rcvbulkpipe(udfs->usbdev, BULKINEP), + udfs->transfer_buffer, SIZEINBUF, + &actual_length, 10000); + if (err < 0) { + printk(KERN_ERR "comedi%d: insn data error: %d\n", + dev->minor, err); + up(&udfs->sem); + return err; + } + n = actual_length / sizeof(uint16_t); + if ((n % 16) != 0) { + printk(KERN_ERR "comedi%d: insn data packet " + "corrupted.\n", dev->minor); + up(&udfs->sem); + return -EINVAL; + } + for (j = chan; (j < n) && (i < insn->n); j = j + 16) { + data[i] = ((uint16_t *) (udfs->transfer_buffer))[j]; + i++; + } + } + up(&udfs->sem); + return i; +} + +#define FIRMWARE_MAX_LEN 0x2000 + +static int firmwareUpload(struct usbduxfastsub_s *usbduxfastsub, + const u8 *firmwareBinary, int sizeFirmware) +{ + int ret; + uint8_t *fwBuf; + + if (!firmwareBinary) + return 0; + + if (sizeFirmware > FIRMWARE_MAX_LEN) { + dev_err(&usbduxfastsub->interface->dev, + "comedi_: usbduxfast firmware binary it too large for FX2.\n"); + return -ENOMEM; + } + + /* we generate a local buffer for the firmware */ + fwBuf = kmemdup(firmwareBinary, sizeFirmware, GFP_KERNEL); + if (!fwBuf) { + dev_err(&usbduxfastsub->interface->dev, + "comedi_: mem alloc for firmware failed\n"); + return -ENOMEM; + } + + ret = usbduxfastsub_stop(usbduxfastsub); + if (ret < 0) { + dev_err(&usbduxfastsub->interface->dev, + "comedi_: can not stop firmware\n"); + kfree(fwBuf); + return ret; + } + + ret = usbduxfastsub_upload(usbduxfastsub, fwBuf, 0, sizeFirmware); + if (ret < 0) { + dev_err(&usbduxfastsub->interface->dev, + "comedi_: firmware upload failed\n"); + kfree(fwBuf); + return ret; + } + ret = usbduxfastsub_start(usbduxfastsub); + if (ret < 0) { + dev_err(&usbduxfastsub->interface->dev, + "comedi_: can not start firmware\n"); + kfree(fwBuf); + return ret; + } + kfree(fwBuf); + return 0; +} + +static void tidy_up(struct usbduxfastsub_s *udfs) +{ +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi_: usbduxfast: tiding up\n"); +#endif + + if (!udfs) + return; + + /* shows the usb subsystem that the driver is down */ + if (udfs->interface) + usb_set_intfdata(udfs->interface, NULL); + + udfs->probed = 0; + + if (udfs->urbIn) { + /* waits until a running transfer is over */ + usb_kill_urb(udfs->urbIn); + + kfree(udfs->transfer_buffer); + udfs->transfer_buffer = NULL; + + usb_free_urb(udfs->urbIn); + udfs->urbIn = NULL; + } + + kfree(udfs->insnBuffer); + udfs->insnBuffer = NULL; + + kfree(udfs->dux_commands); + udfs->dux_commands = NULL; + + udfs->ai_cmd_running = 0; +} + +static void usbduxfast_firmware_request_complete_handler(const struct firmware + *fw, void *context) +{ + struct usbduxfastsub_s *usbduxfastsub_tmp = context; + struct usb_device *usbdev = usbduxfastsub_tmp->usbdev; + int ret; + + if (fw == NULL) + return; + + /* + * we need to upload the firmware here because fw will be + * freed once we've left this function + */ + ret = firmwareUpload(usbduxfastsub_tmp, fw->data, fw->size); + + if (ret) { + dev_err(&usbdev->dev, + "Could not upload firmware (err=%d)\n", ret); + goto out; + } + + comedi_usb_auto_config(usbdev, BOARDNAME); + out: + release_firmware(fw); +} + +/* + * allocate memory for the urbs and initialise them + */ +static int usbduxfastsub_probe(struct usb_interface *uinterf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(uinterf); + int i; + int index; + int ret; + + if (udev->speed != USB_SPEED_HIGH) { + printk(KERN_ERR "comedi_: usbduxfast_: This driver needs" + "USB 2.0 to operate. Aborting...\n"); + return -ENODEV; + } +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi_: usbduxfast_: finding a free structure for " + "the usb-device\n"); +#endif + down(&start_stop_sem); + /* look for a free place in the usbduxfast array */ + index = -1; + for (i = 0; i < NUMUSBDUXFAST; i++) { + if (!usbduxfastsub[i].probed) { + index = i; + break; + } + } + + /* no more space */ + if (index == -1) { + printk(KERN_ERR "Too many usbduxfast-devices connected.\n"); + up(&start_stop_sem); + return -EMFILE; + } +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi_: usbduxfast: usbduxfastsub[%d] is ready to " + "connect to comedi.\n", index); +#endif + + sema_init(&(usbduxfastsub[index].sem), 1); + /* save a pointer to the usb device */ + usbduxfastsub[index].usbdev = udev; + + /* save the interface itself */ + usbduxfastsub[index].interface = uinterf; + /* get the interface number from the interface */ + usbduxfastsub[index].ifnum = uinterf->altsetting->desc.bInterfaceNumber; + /* + * hand the private data over to the usb subsystem + * will be needed for disconnect + */ + usb_set_intfdata(uinterf, &(usbduxfastsub[index])); + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi_: usbduxfast: ifnum=%d\n", + usbduxfastsub[index].ifnum); +#endif + /* create space for the commands going to the usb device */ + usbduxfastsub[index].dux_commands = kmalloc(SIZEOFDUXBUFFER, + GFP_KERNEL); + if (!usbduxfastsub[index].dux_commands) { + printk(KERN_ERR "comedi_: usbduxfast: error alloc space for " + "dac commands\n"); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space of the instruction buffer */ + usbduxfastsub[index].insnBuffer = kmalloc(SIZEINSNBUF, GFP_KERNEL); + if (!usbduxfastsub[index].insnBuffer) { + printk(KERN_ERR "comedi_: usbduxfast: could not alloc space " + "for insnBuffer\n"); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* setting to alternate setting 1: enabling bulk ep */ + i = usb_set_interface(usbduxfastsub[index].usbdev, + usbduxfastsub[index].ifnum, 1); + if (i < 0) { + printk(KERN_ERR "comedi_: usbduxfast%d: could not switch to " + "alternate setting 1.\n", index); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENODEV; + } + usbduxfastsub[index].urbIn = usb_alloc_urb(0, GFP_KERNEL); + if (!usbduxfastsub[index].urbIn) { + printk(KERN_ERR "comedi_: usbduxfast%d: Could not alloc." + "urb\n", index); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxfastsub[index].transfer_buffer = kmalloc(SIZEINBUF, GFP_KERNEL); + if (!usbduxfastsub[index].transfer_buffer) { + printk(KERN_ERR "comedi_: usbduxfast%d: could not alloc. " + "transb.\n", index); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* we've reached the bottom of the function */ + usbduxfastsub[index].probed = 1; + up(&start_stop_sem); + + ret = request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, + "usbduxfast_firmware.bin", + &udev->dev, + GFP_KERNEL, + usbduxfastsub + index, + usbduxfast_firmware_request_complete_handler); + + if (ret) { + dev_err(&udev->dev, "could not load firmware (err=%d)\n", ret); + return ret; + } + + printk(KERN_INFO "comedi_: usbduxfast%d has been successfully " + "initialized.\n", index); + /* success */ + return 0; +} + +static void usbduxfastsub_disconnect(struct usb_interface *intf) +{ + struct usbduxfastsub_s *udfs = usb_get_intfdata(intf); + struct usb_device *udev = interface_to_usbdev(intf); + + if (!udfs) { + printk(KERN_ERR "comedi_: usbduxfast: disconnect called with " + "null pointer.\n"); + return; + } + if (udfs->usbdev != udev) { + printk(KERN_ERR "comedi_: usbduxfast: BUG! called with wrong " + "ptr!!!\n"); + return; + } + + comedi_usb_auto_unconfig(udev); + + down(&start_stop_sem); + down(&udfs->sem); + tidy_up(udfs); + up(&udfs->sem); + up(&start_stop_sem); + +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi_: usbduxfast: disconnected from the usb\n"); +#endif +} + +/* + * is called when comedi-config is called + */ +static int usbduxfast_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int ret; + int index; + int i; + struct comedi_subdevice *s = NULL; + dev->private = NULL; + + down(&start_stop_sem); + /* + * find a valid device which has been detected by the + * probe function of the usb + */ + index = -1; + for (i = 0; i < NUMUSBDUXFAST; i++) { + if (usbduxfastsub[i].probed && !usbduxfastsub[i].attached) { + index = i; + break; + } + } + + if (index < 0) { + printk(KERN_ERR "comedi%d: usbduxfast: error: attach failed, " + "no usbduxfast devs connected to the usb bus.\n", + dev->minor); + up(&start_stop_sem); + return -ENODEV; + } + + down(&(usbduxfastsub[index].sem)); + /* pointer back to the corresponding comedi device */ + usbduxfastsub[index].comedidev = dev; + + /* trying to upload the firmware into the chip */ + if (comedi_aux_data(it->options, 0) && + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { + firmwareUpload(&usbduxfastsub[index], + comedi_aux_data(it->options, 0), + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]); + } + + dev->board_name = BOARDNAME; + + /* set number of subdevices */ + dev->n_subdevices = N_SUBDEVICES; + + /* allocate space for the subdevices */ + ret = alloc_subdevices(dev, N_SUBDEVICES); + if (ret < 0) { + printk(KERN_ERR "comedi%d: usbduxfast: error alloc space for " + "subdev\n", dev->minor); + up(&(usbduxfastsub[index].sem)); + up(&start_stop_sem); + return ret; + } + + printk(KERN_INFO "comedi%d: usbduxfast: usb-device %d is attached to " + "comedi.\n", dev->minor, index); + /* private structure is also simply the usb-structure */ + dev->private = usbduxfastsub + index; + /* the first subdevice is the A/D converter */ + s = dev->subdevices + SUBDEV_AD; + /* + * the URBs get the comedi subdevice which is responsible for reading + * this is the subdevice which reads data + */ + dev->read_subdev = s; + /* the subdevice receives as private structure the usb-structure */ + s->private = NULL; + /* analog input */ + s->type = COMEDI_SUBD_AI; + /* readable and ref is to ground */ + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + /* 16 channels */ + s->n_chan = 16; + /* length of the channellist */ + s->len_chanlist = 16; + /* callback functions */ + s->insn_read = usbduxfast_ai_insn_read; + s->do_cmdtest = usbduxfast_ai_cmdtest; + s->do_cmd = usbduxfast_ai_cmd; + s->cancel = usbduxfast_ai_cancel; + /* max value from the A/D converter (12bit+1 bit for overflow) */ + s->maxdata = 0x1000; + /* range table to convert to physical units */ + s->range_table = &range_usbduxfast_ai_range; + + /* finally decide that it's attached */ + usbduxfastsub[index].attached = 1; + + up(&(usbduxfastsub[index].sem)); + up(&start_stop_sem); + printk(KERN_INFO "comedi%d: successfully attached to usbduxfast.\n", + dev->minor); + + return 0; +} + +static int usbduxfast_detach(struct comedi_device *dev) +{ + struct usbduxfastsub_s *udfs; + + if (!dev) { + printk(KERN_ERR "comedi?: usbduxfast: detach without dev " + "variable...\n"); + return -EFAULT; + } +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: usbduxfast: detach usb device\n", + dev->minor); +#endif + + udfs = dev->private; + if (!udfs) { + printk(KERN_ERR "comedi?: usbduxfast: detach without ptr to " + "usbduxfastsub[]\n"); + return -EFAULT; + } + + down(&udfs->sem); + down(&start_stop_sem); + /* + * Don't allow detach to free the private structure + * It's one entry of of usbduxfastsub[] + */ + dev->private = NULL; + udfs->attached = 0; + udfs->comedidev = NULL; +#ifdef CONFIG_COMEDI_DEBUG + printk(KERN_DEBUG "comedi%d: usbduxfast: detach: successfully " + "removed\n", dev->minor); +#endif + up(&start_stop_sem); + up(&udfs->sem); + return 0; +} + +/* + * main driver struct + */ +static struct comedi_driver driver_usbduxfast = { + .driver_name = "usbduxfast", + .module = THIS_MODULE, + .attach = usbduxfast_attach, + .detach = usbduxfast_detach +}; + +/* + * Table with the USB-devices: just now only testing IDs + */ +static const struct usb_device_id usbduxfastsub_table[] = { + /* { USB_DEVICE(0x4b4, 0x8613) }, testing */ + {USB_DEVICE(0x13d8, 0x0010)}, /* real ID */ + {USB_DEVICE(0x13d8, 0x0011)}, /* real ID */ + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usbduxfastsub_table); + +/* + * The usbduxfastsub-driver + */ +static struct usb_driver usbduxfastsub_driver = { +#ifdef COMEDI_HAVE_USB_DRIVER_OWNER + .owner = THIS_MODULE, +#endif + .name = BOARDNAME, + .probe = usbduxfastsub_probe, + .disconnect = usbduxfastsub_disconnect, + .id_table = usbduxfastsub_table +}; + +/* + * Can't use the nice macro as I have also to initialise the USB subsystem: + * registering the usb-system _and_ the comedi-driver + */ +static int __init init_usbduxfast(void) +{ + printk(KERN_INFO + KBUILD_MODNAME ": " DRIVER_VERSION ":" DRIVER_DESC "\n"); + usb_register(&usbduxfastsub_driver); + comedi_driver_register(&driver_usbduxfast); + return 0; +} + +/* + * deregistering the comedi driver and the usb-subsystem + */ +static void __exit exit_usbduxfast(void) +{ + comedi_driver_unregister(&driver_usbduxfast); + usb_deregister(&usbduxfastsub_driver); +} + +module_init(init_usbduxfast); +module_exit(exit_usbduxfast); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/usbduxsigma.c b/drivers/staging/comedi/drivers/usbduxsigma.c new file mode 100644 index 00000000..63c9b6db --- /dev/null +++ b/drivers/staging/comedi/drivers/usbduxsigma.c @@ -0,0 +1,2885 @@ +#define DRIVER_VERSION "v0.6" +#define DRIVER_AUTHOR "Bernd Porr, BerndPorr@f2s.com" +#define DRIVER_DESC "Stirling/ITL USB-DUX SIGMA -- Bernd.Porr@f2s.com" +/* + comedi/drivers/usbdux.c + Copyright (C) 2011 Bernd Porr, Bernd.Porr@f2s.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: usbduxsigma +Description: University of Stirling USB DAQ & INCITE Technology Limited +Devices: [ITL] USB-DUX (usbduxsigma.o) +Author: Bernd Porr +Updated: 8 Nov 2011 +Status: testing +*/ +/* + * I must give credit here to Chris Baugher who + * wrote the driver for AT-MIO-16d. I used some parts of this + * driver. I also must give credits to David Brownell + * who supported me with the USB development. + * + * Note: the raw data from the A/D converter is 24 bit big endian + * anything else is little endian to/from the dux board + * + * + * Revision history: + * 0.1: initial version + * 0.2: all basic functions implemented, digital I/O only for one port + * 0.3: proper vendor ID and driver name + * 0.4: fixed D/A voltage range + * 0.5: various bug fixes, health check at startup + * 0.6: corrected wrong input range + */ + +/* generates loads of debug info */ +/* #define NOISY_DUX_DEBUGBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "comedi_fc.h" +#include "../comedidev.h" + +#define BOARDNAME "usbduxsigma" + +/* timeout for the USB-transfer in ms*/ +#define BULK_TIMEOUT 1000 + +/* constants for "firmware" upload and download */ +#define USBDUXSUB_FIRMWARE 0xA0 +#define VENDOR_DIR_IN 0xC0 +#define VENDOR_DIR_OUT 0x40 + +/* internal addresses of the 8051 processor */ +#define USBDUXSUB_CPUCS 0xE600 + +/* + * the minor device number, major is 180 only for debugging purposes and to + * upload special firmware (programming the eeprom etc) which is not + * compatible with the comedi framwork + */ +#define USBDUXSUB_MINOR 32 + +/* max lenghth of the transfer-buffer for software upload */ +#define TB_LEN 0x2000 + +/* Input endpoint number: ISO/IRQ */ +#define ISOINEP 6 + +/* Output endpoint number: ISO/IRQ */ +#define ISOOUTEP 2 + +/* This EP sends DUX commands to USBDUX */ +#define COMMAND_OUT_EP 1 + +/* This EP receives the DUX commands from USBDUX */ +#define COMMAND_IN_EP 8 + +/* Output endpoint for PWM */ +#define PWM_EP 4 + +/* 300Hz max frequ under PWM */ +#define MIN_PWM_PERIOD ((long)(1E9/300)) + +/* Default PWM frequency */ +#define PWM_DEFAULT_PERIOD ((long)(1E9/100)) + +/* Number of channels (16 AD and offset)*/ +#define NUMCHANNELS 16 + +/* Size of one A/D value */ +#define SIZEADIN ((sizeof(int32_t))) + +/* + * Size of the async input-buffer IN BYTES, the DIO state is transmitted + * as the first byte. + */ +#define SIZEINBUF (((NUMCHANNELS+1)*SIZEADIN)) + +/* 16 bytes. */ +#define SIZEINSNBUF 16 + +/* Number of DA channels */ +#define NUMOUTCHANNELS 8 + +/* size of one value for the D/A converter: channel and value */ +#define SIZEDAOUT ((sizeof(uint8_t)+sizeof(int16_t))) + +/* + * Size of the output-buffer in bytes + * Actually only the first 4 triplets are used but for the + * high speed mode we need to pad it to 8 (microframes). + */ +#define SIZEOUTBUF ((8*SIZEDAOUT)) + +/* + * Size of the buffer for the dux commands: just now max size is determined + * by the analogue out + command byte + panic bytes... + */ +#define SIZEOFDUXBUFFER ((8*SIZEDAOUT+2)) + +/* Number of in-URBs which receive the data: min=2 */ +#define NUMOFINBUFFERSFULL 5 + +/* Number of out-URBs which send the data: min=2 */ +#define NUMOFOUTBUFFERSFULL 5 + +/* Number of in-URBs which receive the data: min=5 */ +/* must have more buffers due to buggy USB ctr */ +#define NUMOFINBUFFERSHIGH 10 + +/* Number of out-URBs which send the data: min=5 */ +/* must have more buffers due to buggy USB ctr */ +#define NUMOFOUTBUFFERSHIGH 10 + +/* Total number of usbdux devices */ +#define NUMUSBDUX 16 + +/* Analogue in subdevice */ +#define SUBDEV_AD 0 + +/* Analogue out subdevice */ +#define SUBDEV_DA 1 + +/* Digital I/O */ +#define SUBDEV_DIO 2 + +/* timer aka pwm output */ +#define SUBDEV_PWM 3 + +/* number of retries to get the right dux command */ +#define RETRIES 10 + +/**************************************************/ +/* comedi constants */ +static const struct comedi_lrange range_usbdux_ai_range = { 1, { + BIP_RANGE + (2.65/2.0) + } +}; + +static const struct comedi_lrange range_usbdux_ao_range = { 1, { + UNI_RANGE + (2.5), + } +}; + +/* + * private structure of one subdevice + */ + +/* + * This is the structure which holds all the data of + * this driver one sub device just now: A/D + */ +struct usbduxsub { + /* attached? */ + int attached; + /* is it associated with a subdevice? */ + int probed; + /* pointer to the usb-device */ + struct usb_device *usbdev; + /* actual number of in-buffers */ + int numOfInBuffers; + /* actual number of out-buffers */ + int numOfOutBuffers; + /* ISO-transfer handling: buffers */ + struct urb **urbIn; + struct urb **urbOut; + /* pwm-transfer handling */ + struct urb *urbPwm; + /* PWM period */ + unsigned int pwmPeriod; + /* PWM internal delay for the GPIF in the FX2 */ + uint8_t pwmDelay; + /* size of the PWM buffer which holds the bit pattern */ + int sizePwmBuf; + /* input buffer for the ISO-transfer */ + int32_t *inBuffer; + /* input buffer for single insn */ + int8_t *insnBuffer; + /* output buffer for single DA outputs */ + int16_t *outBuffer; + /* interface number */ + int ifnum; + /* interface structure in 2.6 */ + struct usb_interface *interface; + /* comedi device for the interrupt context */ + struct comedi_device *comedidev; + /* is it USB_SPEED_HIGH or not? */ + short int high_speed; + /* asynchronous command is running */ + short int ai_cmd_running; + short int ao_cmd_running; + /* pwm is running */ + short int pwm_cmd_running; + /* continuous acquisition */ + short int ai_continuous; + short int ao_continuous; + /* number of samples to acquire */ + int ai_sample_count; + int ao_sample_count; + /* time between samples in units of the timer */ + unsigned int ai_timer; + unsigned int ao_timer; + /* counter between acquisitions */ + unsigned int ai_counter; + unsigned int ao_counter; + /* interval in frames/uframes */ + unsigned int ai_interval; + /* D/A commands */ + uint8_t *dac_commands; + /* commands */ + uint8_t *dux_commands; + struct semaphore sem; +}; + +/* + * The pointer to the private usb-data of the driver is also the private data + * for the comedi-device. This has to be global as the usb subsystem needs + * global variables. The other reason is that this structure must be there + * _before_ any comedi command is issued. The usb subsystem must be initialised + * before comedi can access it. + */ +static struct usbduxsub usbduxsub[NUMUSBDUX]; + +static DEFINE_SEMAPHORE(start_stop_sem); + +/* + * Stops the data acquision + * It should be safe to call this function from any context + */ +static int usbduxsub_unlink_InURBs(struct usbduxsub *usbduxsub_tmp) +{ + int i = 0; + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbIn) { + for (i = 0; i < usbduxsub_tmp->numOfInBuffers; i++) { + if (usbduxsub_tmp->urbIn[i]) { + /* We wait here until all transfers have been + * cancelled. */ + usb_kill_urb(usbduxsub_tmp->urbIn[i]); + } + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi: usbdux: unlinked InURB %d, err=%d\n", + i, err); + } + } + return err; +} + +/* + * This will stop a running acquisition operation + * Is called from within this driver from both the + * interrupt context and from comedi + */ +static int usbdux_ai_stop(struct usbduxsub *this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) { + pr_err("comedi?: usbdux_ai_stop: this_usbduxsub=NULL!\n"); + return -EFAULT; + } + dev_dbg(&this_usbduxsub->interface->dev, "comedi: usbdux_ai_stop\n"); + + if (do_unlink) { + /* stop aquistion */ + ret = usbduxsub_unlink_InURBs(this_usbduxsub); + } + + this_usbduxsub->ai_cmd_running = 0; + + return ret; +} + +/* + * This will cancel a running acquisition operation. + * This is called by comedi but never from inside the driver. + */ +static int usbdux_ai_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxsub *this_usbduxsub; + int res = 0; + + /* force unlink of all urbs */ + this_usbduxsub = dev->private; + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, "comedi: usbdux_ai_cancel\n"); + + /* prevent other CPUs from submitting new commands just now */ + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + /* unlink only if the urb really has been submitted */ + res = usbdux_ai_stop(this_usbduxsub, this_usbduxsub->ai_cmd_running); + up(&this_usbduxsub->sem); + return res; +} + +/* analogue IN - interrupt service routine */ +static void usbduxsub_ai_IsocIrq(struct urb *urb) +{ + int i, err, n; + struct usbduxsub *this_usbduxsub; + struct comedi_device *this_comedidev; + struct comedi_subdevice *s; + int32_t v; + unsigned int dio_state; + + /* the context variable points to the comedi device */ + this_comedidev = urb->context; + /* the private structure of the subdevice is struct usbduxsub */ + this_usbduxsub = this_comedidev->private; + /* subdevice which is the AD converter */ + s = this_comedidev->subdevices + SUBDEV_AD; + + /* first we test if something unusual has just happened */ + switch (urb->status) { + case 0: + /* copy the result in the transfer buffer */ + memcpy(this_usbduxsub->inBuffer, + urb->transfer_buffer, SIZEINBUF); + break; + case -EILSEQ: + /* error in the ISOchronous data */ + /* we don't copy the data into the transfer buffer */ + /* and recycle the last data byte */ + dev_dbg(&urb->dev->dev, + "comedi%d: usbdux: CRC error in ISO IN stream.\n", + this_usbduxsub->comedidev->minor); + + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* happens after an unlink command */ + if (this_usbduxsub->ai_cmd_running) { + /* we are still running a command */ + /* tell this comedi */ + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* stop the transfer w/o unlink */ + usbdux_ai_stop(this_usbduxsub, 0); + } + return; + + default: + /* a real error on the bus */ + /* pass error to comedi if we are really running a command */ + if (this_usbduxsub->ai_cmd_running) { + dev_err(&urb->dev->dev, + "Non-zero urb status received in ai intr " + "context: %d\n", urb->status); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* don't do an unlink here */ + usbdux_ai_stop(this_usbduxsub, 0); + } + return; + } + + /* + * at this point we are reasonably sure that nothing dodgy has happened + * are we running a command? + */ + if (unlikely((!(this_usbduxsub->ai_cmd_running)))) { + /* + * not running a command, do not continue execution if no + * asynchronous command is running in particular not resubmit + */ + return; + } + + urb->dev = this_usbduxsub->usbdev; + + /* resubmit the urb */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + dev_err(&urb->dev->dev, + "comedi_: urb resubmit failed in int-context!" + "err=%d\n", + err); + if (err == -EL2NSYNC) + dev_err(&urb->dev->dev, + "buggy USB host controller or bug in IRQ " + "handler!\n"); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* don't do an unlink here */ + usbdux_ai_stop(this_usbduxsub, 0); + return; + } + + /* get the state of the dio pins to allow external trigger */ + dio_state = be32_to_cpu(this_usbduxsub->inBuffer[0]); + + this_usbduxsub->ai_counter--; + if (likely(this_usbduxsub->ai_counter > 0)) + return; + + /* timer zero, transfer measurements to comedi */ + this_usbduxsub->ai_counter = this_usbduxsub->ai_timer; + + /* test, if we transmit only a fixed number of samples */ + if (!(this_usbduxsub->ai_continuous)) { + /* not continuous, fixed number of samples */ + this_usbduxsub->ai_sample_count--; + /* all samples received? */ + if (this_usbduxsub->ai_sample_count < 0) { + /* prevent a resubmit next time */ + usbdux_ai_stop(this_usbduxsub, 0); + /* say comedi that the acquistion is over */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + return; + } + } + /* get the data from the USB bus and hand it over to comedi */ + n = s->async->cmd.chanlist_len; + for (i = 0; i < n; i++) { + /* transfer data, note first byte is the DIO state */ + v = be32_to_cpu(this_usbduxsub->inBuffer[i+1]); + /* strip status byte */ + v = v & 0x00ffffff; + /* convert to unsigned */ + v = v ^ 0x00800000; + /* write the byte to the buffer */ + err = cfc_write_array_to_buffer(s, &v, sizeof(uint32_t)); + if (unlikely(err == 0)) { + /* buffer overflow */ + usbdux_ai_stop(this_usbduxsub, 0); + return; + } + } + /* tell comedi that data is there */ + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + comedi_event(this_usbduxsub->comedidev, s); +} + +static int usbduxsub_unlink_OutURBs(struct usbduxsub *usbduxsub_tmp) +{ + int i = 0; + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbOut) { + for (i = 0; i < usbduxsub_tmp->numOfOutBuffers; i++) { + if (usbduxsub_tmp->urbOut[i]) + usb_kill_urb(usbduxsub_tmp->urbOut[i]); + + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi: usbdux: unlinked OutURB %d: res=%d\n", + i, err); + } + } + return err; +} + +/* This will cancel a running acquisition operation + * in any context. + */ +static int usbdux_ao_stop(struct usbduxsub *this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) + return -EFAULT; + dev_dbg(&this_usbduxsub->interface->dev, "comedi: usbdux_ao_cancel\n"); + + if (do_unlink) + ret = usbduxsub_unlink_OutURBs(this_usbduxsub); + + this_usbduxsub->ao_cmd_running = 0; + + return ret; +} + +/* force unlink, is called by comedi */ +static int usbdux_ao_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int res = 0; + + if (!this_usbduxsub) + return -EFAULT; + + /* prevent other CPUs from submitting a command just now */ + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + /* unlink only if it is really running */ + res = usbdux_ao_stop(this_usbduxsub, this_usbduxsub->ao_cmd_running); + up(&this_usbduxsub->sem); + return res; +} + +static void usbduxsub_ao_IsocIrq(struct urb *urb) +{ + int i, ret; + uint8_t *datap; + struct usbduxsub *this_usbduxsub; + struct comedi_device *this_comedidev; + struct comedi_subdevice *s; + + /* the context variable points to the subdevice */ + this_comedidev = urb->context; + /* the private structure of the subdevice is struct usbduxsub */ + this_usbduxsub = this_comedidev->private; + + s = this_comedidev->subdevices + SUBDEV_DA; + + switch (urb->status) { + case 0: + /* success */ + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* after an unlink command, unplug, ... etc */ + /* no unlink needed here. Already shutting down. */ + if (this_usbduxsub->ao_cmd_running) { + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + usbdux_ao_stop(this_usbduxsub, 0); + } + return; + + default: + /* a real error */ + if (this_usbduxsub->ao_cmd_running) { + dev_err(&urb->dev->dev, + "comedi_: Non-zero urb status received in ao " + "intr context: %d\n", urb->status); + s->async->events |= COMEDI_CB_ERROR; + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + /* we do an unlink if we are in the high speed mode */ + usbdux_ao_stop(this_usbduxsub, 0); + } + return; + } + + /* are we actually running? */ + if (!(this_usbduxsub->ao_cmd_running)) + return; + + /* normal operation: executing a command in this subdevice */ + this_usbduxsub->ao_counter--; + if ((int)this_usbduxsub->ao_counter <= 0) { + /* timer zero */ + this_usbduxsub->ao_counter = this_usbduxsub->ao_timer; + + /* handle non continuous acquisition */ + if (!(this_usbduxsub->ao_continuous)) { + /* fixed number of samples */ + this_usbduxsub->ao_sample_count--; + if (this_usbduxsub->ao_sample_count < 0) { + /* all samples transmitted */ + usbdux_ao_stop(this_usbduxsub, 0); + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + /* no resubmit of the urb */ + return; + } + } + /* transmit data to the USB bus */ + ((uint8_t *) (urb->transfer_buffer))[0] = + s->async->cmd.chanlist_len; + for (i = 0; i < s->async->cmd.chanlist_len; i++) { + short temp; + if (i >= NUMOUTCHANNELS) + break; + + /* pointer to the DA */ + datap = + (&(((uint8_t *) urb->transfer_buffer)[i * 2 + 1])); + /* get the data from comedi */ + ret = comedi_buf_get(s->async, &temp); + datap[0] = temp; + datap[1] = this_usbduxsub->dac_commands[i]; + /* printk("data[0]=%x, data[1]=%x, data[2]=%x\n", */ + /* datap[0],datap[1],datap[2]); */ + if (ret < 0) { + dev_err(&urb->dev->dev, + "comedi: buffer underflow\n"); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_OVERFLOW; + } + /* transmit data to comedi */ + s->async->events |= COMEDI_CB_BLOCK; + comedi_event(this_usbduxsub->comedidev, s); + } + } + urb->transfer_buffer_length = SIZEOUTBUF; + urb->dev = this_usbduxsub->usbdev; + urb->status = 0; + if (this_usbduxsub->ao_cmd_running) { + if (this_usbduxsub->high_speed) { + /* uframes */ + urb->interval = 8; + } else { + /* frames */ + urb->interval = 1; + } + urb->number_of_packets = 1; + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = SIZEOUTBUF; + urb->iso_frame_desc[0].status = 0; + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(&urb->dev->dev, + "comedi_: ao urb resubm failed in int-cont. " + "ret=%d", ret); + if (ret == EL2NSYNC) + dev_err(&urb->dev->dev, + "buggy USB host controller or bug in " + "IRQ handling!\n"); + + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* don't do an unlink here */ + usbdux_ao_stop(this_usbduxsub, 0); + } + } +} + +static int usbduxsub_start(struct usbduxsub *usbduxsub) +{ + int errcode = 0; + uint8_t local_transfer_buffer[16]; + + /* 7f92 to zero */ + local_transfer_buffer[0] = 0; + errcode = usb_control_msg(usbduxsub->usbdev, + /* create a pipe for a control transfer */ + usb_sndctrlpipe(usbduxsub->usbdev, 0), + /* bRequest, "Firmware" */ + USBDUXSUB_FIRMWARE, + /* bmRequestType */ + VENDOR_DIR_OUT, + /* Value */ + USBDUXSUB_CPUCS, + /* Index */ + 0x0000, + /* address of the transfer buffer */ + local_transfer_buffer, + /* Length */ + 1, + /* Timeout */ + BULK_TIMEOUT); + if (errcode < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: control msg failed (start)\n"); + return errcode; + } + return 0; +} + +static int usbduxsub_stop(struct usbduxsub *usbduxsub) +{ + int errcode = 0; + + uint8_t local_transfer_buffer[16]; + + /* 7f92 to one */ + local_transfer_buffer[0] = 1; + errcode = usb_control_msg(usbduxsub->usbdev, + usb_sndctrlpipe(usbduxsub->usbdev, 0), + /* bRequest, "Firmware" */ + USBDUXSUB_FIRMWARE, + /* bmRequestType */ + VENDOR_DIR_OUT, + /* Value */ + USBDUXSUB_CPUCS, + /* Index */ + 0x0000, local_transfer_buffer, + /* Length */ + 1, + /* Timeout */ + BULK_TIMEOUT); + if (errcode < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: control msg failed (stop)\n"); + return errcode; + } + return 0; +} + +static int usbduxsub_upload(struct usbduxsub *usbduxsub, + uint8_t *local_transfer_buffer, + unsigned int startAddr, unsigned int len) +{ + int errcode; + + errcode = usb_control_msg(usbduxsub->usbdev, + usb_sndctrlpipe(usbduxsub->usbdev, 0), + /* brequest, firmware */ + USBDUXSUB_FIRMWARE, + /* bmRequestType */ + VENDOR_DIR_OUT, + /* value */ + startAddr, + /* index */ + 0x0000, + /* our local safe buffer */ + local_transfer_buffer, + /* length */ + len, + /* timeout */ + BULK_TIMEOUT); + dev_dbg(&usbduxsub->interface->dev, "comedi_: result=%d\n", errcode); + if (errcode < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: upload failed\n"); + return errcode; + } + return 0; +} + +/* the FX2LP has twice as much as the standard FX2 */ +#define FIRMWARE_MAX_LEN 0x4000 + +static int firmwareUpload(struct usbduxsub *usbduxsub, + const u8 *firmwareBinary, int sizeFirmware) +{ + int ret; + uint8_t *fwBuf; + + if (!firmwareBinary) + return 0; + + if (sizeFirmware > FIRMWARE_MAX_LEN) { + dev_err(&usbduxsub->interface->dev, + "usbduxsigma firmware binary it too large for FX2.\n"); + return -ENOMEM; + } + + /* we generate a local buffer for the firmware */ + fwBuf = kmemdup(firmwareBinary, sizeFirmware, GFP_KERNEL); + if (!fwBuf) { + dev_err(&usbduxsub->interface->dev, + "comedi_: mem alloc for firmware failed\n"); + return -ENOMEM; + } + + ret = usbduxsub_stop(usbduxsub); + if (ret < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: can not stop firmware\n"); + kfree(fwBuf); + return ret; + } + + ret = usbduxsub_upload(usbduxsub, fwBuf, 0, sizeFirmware); + if (ret < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: firmware upload failed\n"); + kfree(fwBuf); + return ret; + } + ret = usbduxsub_start(usbduxsub); + if (ret < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: can not start firmware\n"); + kfree(fwBuf); + return ret; + } + kfree(fwBuf); + return 0; +} + +static int usbduxsub_submit_InURBs(struct usbduxsub *usbduxsub) +{ + int i, errFlag; + + if (!usbduxsub) + return -EFAULT; + + /* Submit all URBs and start the transfer on the bus */ + for (i = 0; i < usbduxsub->numOfInBuffers; i++) { + /* in case of a resubmission after an unlink... */ + usbduxsub->urbIn[i]->interval = usbduxsub->ai_interval; + usbduxsub->urbIn[i]->context = usbduxsub->comedidev; + usbduxsub->urbIn[i]->dev = usbduxsub->usbdev; + usbduxsub->urbIn[i]->status = 0; + usbduxsub->urbIn[i]->transfer_flags = URB_ISO_ASAP; + dev_dbg(&usbduxsub->interface->dev, + "comedi%d: submitting in-urb[%d]: %p,%p intv=%d\n", + usbduxsub->comedidev->minor, i, + (usbduxsub->urbIn[i]->context), + (usbduxsub->urbIn[i]->dev), + (usbduxsub->urbIn[i]->interval)); + errFlag = usb_submit_urb(usbduxsub->urbIn[i], GFP_ATOMIC); + if (errFlag) { + dev_err(&usbduxsub->interface->dev, + "comedi_: ai: usb_submit_urb(%d) error %d\n", + i, errFlag); + return errFlag; + } + } + return 0; +} + +static int usbduxsub_submit_OutURBs(struct usbduxsub *usbduxsub) +{ + int i, errFlag; + + if (!usbduxsub) + return -EFAULT; + + for (i = 0; i < usbduxsub->numOfOutBuffers; i++) { + dev_dbg(&usbduxsub->interface->dev, + "comedi_: submitting out-urb[%d]\n", i); + /* in case of a resubmission after an unlink... */ + usbduxsub->urbOut[i]->context = usbduxsub->comedidev; + usbduxsub->urbOut[i]->dev = usbduxsub->usbdev; + usbduxsub->urbOut[i]->status = 0; + usbduxsub->urbOut[i]->transfer_flags = URB_ISO_ASAP; + errFlag = usb_submit_urb(usbduxsub->urbOut[i], GFP_ATOMIC); + if (errFlag) { + dev_err(&usbduxsub->interface->dev, + "comedi_: ao: usb_submit_urb(%d) error %d\n", + i, errFlag); + return errFlag; + } + } + return 0; +} + +static int chanToInterval(int nChannels) +{ + if (nChannels <= 2) + /* 4kHz */ + return 2; + if (nChannels <= 8) + /* 2kHz */ + return 4; + /* 1kHz */ + return 8; +} + +static int usbdux_ai_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0, tmp, i; + unsigned int tmpTimer; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!(this_usbduxsub->probed)) + return -ENODEV; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_cmdtest\n", dev->minor); + + /* make sure triggers are valid */ + /* Only immediate triggers are allowed */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + /* trigger should happen timed */ + tmp = cmd->scan_begin_src; + /* start a new _scan_ with a timer */ + cmd->scan_begin_src &= TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + /* scanning is continuous */ + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + /* issue a trigger when scan is finished and start a new scan */ + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + /* trigger at the end of count events or not, stop condition or not */ + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique and mutually compatible + * note that mutual compatibility is not an issue here + */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (this_usbduxsub->high_speed) { + /* + * In high speed mode microframes are possible. + * However, during one microframe we can roughly + * sample two channels. Thus, the more channels + * are in the channel list the more time we need. + */ + i = chanToInterval(cmd->chanlist_len); + if (cmd->scan_begin_arg < (1000000 / 8 * i)) { + cmd->scan_begin_arg = 1000000 / 8 * i; + err++; + } + /* now calc the real sampling rate with all the + * rounding errors */ + tmpTimer = + ((unsigned int)(cmd->scan_begin_arg / 125000)) * + 125000; + if (cmd->scan_begin_arg != tmpTimer) { + cmd->scan_begin_arg = tmpTimer; + err++; + } + } else { + /* full speed */ + /* 1kHz scans every USB frame */ + if (cmd->scan_begin_arg < 1000000) { + cmd->scan_begin_arg = 1000000; + err++; + } + /* + * calc the real sampling rate with the rounding errors + */ + tmpTimer = ((unsigned int)(cmd->scan_begin_arg / + 1000000)) * 1000000; + if (cmd->scan_begin_arg != tmpTimer) { + cmd->scan_begin_arg = tmpTimer; + err++; + } + } + } + /* the same argument */ + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + return 0; +} + +/* + * creates the ADC command for the MAX1271 + * range is the range value from comedi + */ +static void create_adc_command(unsigned int chan, + uint8_t *muxsg0, + uint8_t *muxsg1) +{ + if (chan < 8) + (*muxsg0) = (*muxsg0) | (1 << chan); + else if (chan < 16) + (*muxsg1) = (*muxsg1) | (1 << (chan-8)); +} + + +/* bulk transfers to usbdux */ + +#define SENDADCOMMANDS 0 +#define SENDDACOMMANDS 1 +#define SENDDIOCONFIGCOMMAND 2 +#define SENDDIOBITSCOMMAND 3 +#define SENDSINGLEAD 4 +#define SENDPWMON 7 +#define SENDPWMOFF 8 + +static int send_dux_commands(struct usbduxsub *this_usbduxsub, int cmd_type) +{ + int result, nsent; + + this_usbduxsub->dux_commands[0] = cmd_type; +#ifdef NOISY_DUX_DEBUGBUG + printk(KERN_DEBUG "comedi%d: usbdux: dux_commands: ", + this_usbduxsub->comedidev->minor); + for (result = 0; result < SIZEOFDUXBUFFER; result++) + printk(" %02x", this_usbduxsub->dux_commands[result]); + printk("\n"); +#endif + result = usb_bulk_msg(this_usbduxsub->usbdev, + usb_sndbulkpipe(this_usbduxsub->usbdev, + COMMAND_OUT_EP), + this_usbduxsub->dux_commands, SIZEOFDUXBUFFER, + &nsent, BULK_TIMEOUT); + if (result < 0) + dev_err(&this_usbduxsub->interface->dev, "comedi%d: " + "could not transmit dux_command to the usb-device, " + "err=%d\n", this_usbduxsub->comedidev->minor, result); + + return result; +} + +static int receive_dux_commands(struct usbduxsub *this_usbduxsub, int command) +{ + int result = (-EFAULT); + int nrec; + int i; + + for (i = 0; i < RETRIES; i++) { + result = usb_bulk_msg(this_usbduxsub->usbdev, + usb_rcvbulkpipe(this_usbduxsub->usbdev, + COMMAND_IN_EP), + this_usbduxsub->insnBuffer, SIZEINSNBUF, + &nrec, BULK_TIMEOUT); + if (result < 0) { + dev_err(&this_usbduxsub->interface->dev, "comedi%d: " + "insn: USB error %d " + "while receiving DUX command" + "\n", this_usbduxsub->comedidev->minor, + result); + return result; + } + if (this_usbduxsub->insnBuffer[0] == command) + return result; + } + /* this is only reached if the data has been requested a couple of + * times */ + dev_err(&this_usbduxsub->interface->dev, "comedi%d: insn: " + "wrong data returned from firmware: want %d, got %d.\n", + this_usbduxsub->comedidev->minor, command, + this_usbduxsub->insnBuffer[0]); + return -EFAULT; +} + +static int usbdux_ai_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum) +{ + int ret; + struct usbduxsub *this_usbduxsub = dev->private; + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_inttrig\n", dev->minor); + + if (trignum != 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_inttrig: invalid trignum\n", + dev->minor); + up(&this_usbduxsub->sem); + return -EINVAL; + } + if (!(this_usbduxsub->ai_cmd_running)) { + this_usbduxsub->ai_cmd_running = 1; + ret = usbduxsub_submit_InURBs(this_usbduxsub); + if (ret < 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_inttrig: " + "urbSubmit: err=%d\n", dev->minor, ret); + this_usbduxsub->ai_cmd_running = 0; + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ai_inttrig but acqu is already running\n", + dev->minor); + } + up(&this_usbduxsub->sem); + return 1; +} + +static int usbdux_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int chan; + int i, ret; + struct usbduxsub *this_usbduxsub = dev->private; + int result; + uint8_t muxsg0 = 0; + uint8_t muxsg1 = 0; + uint8_t sysred = 0; + + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_cmd\n", dev->minor); + + /* block other CPUs from starting an ai_cmd */ + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ai_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, "comedi%d: " + "ai_cmd not possible. Another ai_cmd is running.\n", + dev->minor); + up(&this_usbduxsub->sem); + return -EBUSY; + } + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + + /* first the number of channels per time step */ + this_usbduxsub->dux_commands[1] = cmd->chanlist_len; + + /* CONFIG0 */ + this_usbduxsub->dux_commands[2] = 0x12; + + /* CONFIG1: 23kHz sampling rate, delay = 0us, */ + this_usbduxsub->dux_commands[3] = 0x03; + + /* CONFIG3: differential channels off */ + this_usbduxsub->dux_commands[4] = 0x00; + + for (i = 0; i < cmd->chanlist_len; i++) { + chan = CR_CHAN(cmd->chanlist[i]); + create_adc_command(chan, &muxsg0, &muxsg1); + if (i >= NUMCHANNELS) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: channel list too long\n", + dev->minor); + break; + } + } + this_usbduxsub->dux_commands[5] = muxsg0; + this_usbduxsub->dux_commands[6] = muxsg1; + this_usbduxsub->dux_commands[7] = sysred; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi %d: sending commands to the usb device: size=%u\n", + dev->minor, NUMCHANNELS); + + result = send_dux_commands(this_usbduxsub, SENDADCOMMANDS); + if (result < 0) { + up(&this_usbduxsub->sem); + return result; + } + + if (this_usbduxsub->high_speed) { + /* + * every 2 channels get a time window of 125us. Thus, if we + * sample all 16 channels we need 1ms. If we sample only one + * channel we need only 125us + */ + this_usbduxsub->ai_interval = + chanToInterval(cmd->chanlist_len); + this_usbduxsub->ai_timer = cmd->scan_begin_arg / (125000 * + (this_usbduxsub-> + ai_interval)); + } else { + /* interval always 1ms */ + this_usbduxsub->ai_interval = 1; + this_usbduxsub->ai_timer = cmd->scan_begin_arg / 1000000; + } + if (this_usbduxsub->ai_timer < 1) { + dev_err(&this_usbduxsub->interface->dev, "comedi%d: ai_cmd: " + "timer=%d, scan_begin_arg=%d. " + "Not properly tested by cmdtest?\n", dev->minor, + this_usbduxsub->ai_timer, cmd->scan_begin_arg); + up(&this_usbduxsub->sem); + return -EINVAL; + } + this_usbduxsub->ai_counter = this_usbduxsub->ai_timer; + + if (cmd->stop_src == TRIG_COUNT) { + /* data arrives as one packet */ + this_usbduxsub->ai_sample_count = cmd->stop_arg; + this_usbduxsub->ai_continuous = 0; + } else { + /* continuous acquisition */ + this_usbduxsub->ai_continuous = 1; + this_usbduxsub->ai_sample_count = 0; + } + + if (cmd->start_src == TRIG_NOW) { + /* enable this acquisition operation */ + this_usbduxsub->ai_cmd_running = 1; + ret = usbduxsub_submit_InURBs(this_usbduxsub); + if (ret < 0) { + this_usbduxsub->ai_cmd_running = 0; + /* fixme: unlink here?? */ + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + /* don't enable the acquision operation */ + /* wait for an internal signal */ + s->async->inttrig = usbdux_ai_inttrig; + } + up(&this_usbduxsub->sem); + return 0; +} + +/* Mode 0 is used to get a single conversion on demand */ +static int usbdux_ai_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int32_t one = 0; + int chan; + int err; + struct usbduxsub *this_usbduxsub = dev->private; + uint8_t muxsg0 = 0; + uint8_t muxsg1 = 0; + uint8_t sysred = 0; + + if (!this_usbduxsub) + return 0; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ai_insn_read, insn->n=%d, insn->subdev=%d\n", + dev->minor, insn->n, insn->subdev); + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ai_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ai_insn_read not possible. " + "Async Command is running.\n", dev->minor); + up(&this_usbduxsub->sem); + return 0; + } + + /* sample one channel */ + /* CONFIG0: chopper on */ + this_usbduxsub->dux_commands[1] = 0x16; + + /* CONFIG1: 2kHz sampling rate */ + this_usbduxsub->dux_commands[2] = 0x80; + + /* CONFIG3: differential channels off */ + this_usbduxsub->dux_commands[3] = 0x00; + + chan = CR_CHAN(insn->chanspec); + create_adc_command(chan, &muxsg0, &muxsg1); + + this_usbduxsub->dux_commands[4] = muxsg0; + this_usbduxsub->dux_commands[5] = muxsg1; + this_usbduxsub->dux_commands[6] = sysred; + + /* adc commands */ + err = send_dux_commands(this_usbduxsub, SENDSINGLEAD); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + for (i = 0; i < insn->n; i++) { + err = receive_dux_commands(this_usbduxsub, SENDSINGLEAD); + if (err < 0) { + up(&this_usbduxsub->sem); + return 0; + } + /* 32 bits big endian from the A/D converter */ + one = be32_to_cpu(*((int32_t *) + ((this_usbduxsub->insnBuffer)+1))); + /* mask out the staus byte */ + one = one & 0x00ffffff; + /* turn it into an unsigned integer */ + one = one ^ 0x00800000; + data[i] = one; + } + up(&this_usbduxsub->sem); + return i; +} + + + + +static int usbdux_getstatusinfo(struct comedi_device *dev, int chan) +{ + struct usbduxsub *this_usbduxsub = dev->private; + uint8_t sysred = 0; + uint32_t one; + int err; + + if (!this_usbduxsub) + return 0; + + if (this_usbduxsub->ai_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: status read not possible. " + "Async Command is running.\n", dev->minor); + return 0; + } + + /* CONFIG0 */ + this_usbduxsub->dux_commands[1] = 0x12; + + /* CONFIG1: 2kHz sampling rate */ + this_usbduxsub->dux_commands[2] = 0x80; + + /* CONFIG3: differential channels off */ + this_usbduxsub->dux_commands[3] = 0x00; + + if (chan == 1) { + /* ADC offset */ + sysred = sysred | 1; + } else if (chan == 2) { + /* VCC */ + sysred = sysred | 4; + } else if (chan == 3) { + /* temperature */ + sysred = sysred | 8; + } else if (chan == 4) { + /* gain */ + sysred = sysred | 16; + } else if (chan == 5) { + /* ref */ + sysred = sysred | 32; + } + + this_usbduxsub->dux_commands[4] = 0; + this_usbduxsub->dux_commands[5] = 0; + this_usbduxsub->dux_commands[6] = sysred; + + /* adc commands */ + err = send_dux_commands(this_usbduxsub, SENDSINGLEAD); + if (err < 0) + return err; + + err = receive_dux_commands(this_usbduxsub, SENDSINGLEAD); + if (err < 0) + return err; + + /* 32 bits big endian from the A/D converter */ + one = be32_to_cpu(*((int32_t *)((this_usbduxsub->insnBuffer)+1))); + /* mask out the staus byte */ + one = one & 0x00ffffff; + one = one ^ 0x00800000; + + return (int)one; +} + + + + + + +/************************************/ +/* analog out */ + +static int usbdux_ao_insn_read(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + for (i = 0; i < insn->n; i++) + data[i] = this_usbduxsub->outBuffer[chan]; + + up(&this_usbduxsub->sem); + return i; +} + +static int usbdux_ao_insn_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int i, err; + int chan = CR_CHAN(insn->chanspec); + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ao_insn_write\n", dev->minor); + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ao_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ao_insn_write: " + "ERROR: asynchronous ao_cmd is running\n", dev->minor); + up(&this_usbduxsub->sem); + return 0; + } + + for (i = 0; i < insn->n; i++) { + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ao_insn_write: data[chan=%d,i=%d]=%d\n", + dev->minor, chan, i, data[i]); + + /* number of channels: 1 */ + this_usbduxsub->dux_commands[1] = 1; + /* channel number */ + this_usbduxsub->dux_commands[2] = data[i]; + this_usbduxsub->outBuffer[chan] = data[i]; + this_usbduxsub->dux_commands[3] = chan; + err = send_dux_commands(this_usbduxsub, SENDDACOMMANDS); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + } + up(&this_usbduxsub->sem); + + return i; +} + +static int usbdux_ao_inttrig(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int trignum) +{ + int ret; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + ret = -ENODEV; + goto out; + } + if (trignum != 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ao_inttrig: invalid trignum\n", + dev->minor); + ret = -EINVAL; + goto out; + } + if (!(this_usbduxsub->ao_cmd_running)) { + this_usbduxsub->ao_cmd_running = 1; + ret = usbduxsub_submit_OutURBs(this_usbduxsub); + if (ret < 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ao_inttrig: submitURB: " + "err=%d\n", dev->minor, ret); + this_usbduxsub->ao_cmd_running = 0; + goto out; + } + s->async->inttrig = NULL; + } else { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ao_inttrig but acqu is already running.\n", + dev->minor); + } + ret = 1; +out: + up(&this_usbduxsub->sem); + return ret; +} + +static int usbdux_ao_cmdtest(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_cmd *cmd) +{ + int err = 0, tmp; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + if (!(this_usbduxsub->probed)) + return -ENODEV; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ao_cmdtest\n", dev->minor); + + /* make sure triggers are valid */ + /* Only immediate triggers are allowed */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + /* trigger should happen timed */ + tmp = cmd->scan_begin_src; + /* just now we scan also in the high speed mode every frame */ + /* this is due to ehci driver limitations */ + if (0) { /* (this_usbduxsub->high_speed) */ + /* start immediately a new scan */ + /* the sampling rate is set by the coversion rate */ + cmd->scan_begin_src &= TRIG_FOLLOW; + } else { + /* start a new scan (output at once) with a timer */ + cmd->scan_begin_src &= TRIG_TIMER; + } + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + /* scanning is continuous */ + tmp = cmd->convert_src; + + /* all conversion events happen simultaneously */ + cmd->convert_src &= TRIG_NOW; + + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + /* issue a trigger when scan is finished and start a new scan */ + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + /* trigger at the end of count events or not, stop condition or not */ + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources + * are unique and mutually compatible + * note that mutual compatibility is not an issue here + */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* timer */ + if (cmd->scan_begin_arg < 1000000) { + cmd->scan_begin_arg = 1000000; + err++; + } + } + /* not used now, is for later use */ + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < 125000) { + cmd->convert_arg = 125000; + err++; + } + } + + /* the same argument */ + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + dev_dbg(&this_usbduxsub->interface->dev, "comedi%d: err=%d, " + "scan_begin_src=%d, scan_begin_arg=%d, convert_src=%d, " + "convert_arg=%d\n", dev->minor, err, cmd->scan_begin_src, + cmd->scan_begin_arg, cmd->convert_src, cmd->convert_arg); + + if (err) + return 3; + + return 0; +} + +static int usbdux_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct comedi_cmd *cmd = &s->async->cmd; + unsigned int chan, gain; + int i, ret; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s\n", dev->minor, __func__); + + /* set current channel of the running acquisition to zero */ + s->async->cur_chan = 0; + for (i = 0; i < cmd->chanlist_len; ++i) { + chan = CR_CHAN(cmd->chanlist[i]); + gain = CR_RANGE(cmd->chanlist[i]); + if (i >= NUMOUTCHANNELS) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: %s: channel list too long\n", + dev->minor, __func__); + break; + } + this_usbduxsub->dac_commands[i] = chan; + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: dac command for ch %d is %x\n", + dev->minor, i, this_usbduxsub->dac_commands[i]); + } + + /* we count in steps of 1ms (125us) */ + /* 125us mode not used yet */ + if (0) { /* (this_usbduxsub->high_speed) */ + /* 125us */ + /* timing of the conversion itself: every 125 us */ + this_usbduxsub->ao_timer = cmd->convert_arg / 125000; + } else { + /* 1ms */ + /* timing of the scan: we get all channels at once */ + this_usbduxsub->ao_timer = cmd->scan_begin_arg / 1000000; + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: scan_begin_src=%d, scan_begin_arg=%d, " + "convert_src=%d, convert_arg=%d\n", dev->minor, + cmd->scan_begin_src, cmd->scan_begin_arg, + cmd->convert_src, cmd->convert_arg); + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ao_timer=%d (ms)\n", + dev->minor, this_usbduxsub->ao_timer); + if (this_usbduxsub->ao_timer < 1) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux: ao_timer=%d, " + "scan_begin_arg=%d. " + "Not properly tested by cmdtest?\n", + dev->minor, this_usbduxsub->ao_timer, + cmd->scan_begin_arg); + up(&this_usbduxsub->sem); + return -EINVAL; + } + } + this_usbduxsub->ao_counter = this_usbduxsub->ao_timer; + + if (cmd->stop_src == TRIG_COUNT) { + /* not continuous */ + /* counter */ + /* high speed also scans everything at once */ + if (0) { /* (this_usbduxsub->high_speed) */ + this_usbduxsub->ao_sample_count = + (cmd->stop_arg) * (cmd->scan_end_arg); + } else { + /* there's no scan as the scan has been */ + /* perf inside the FX2 */ + /* data arrives as one packet */ + this_usbduxsub->ao_sample_count = cmd->stop_arg; + } + this_usbduxsub->ao_continuous = 0; + } else { + /* continuous acquisition */ + this_usbduxsub->ao_continuous = 1; + this_usbduxsub->ao_sample_count = 0; + } + + if (cmd->start_src == TRIG_NOW) { + /* enable this acquisition operation */ + this_usbduxsub->ao_cmd_running = 1; + ret = usbduxsub_submit_OutURBs(this_usbduxsub); + if (ret < 0) { + this_usbduxsub->ao_cmd_running = 0; + /* fixme: unlink here?? */ + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + /* submit the urbs later */ + /* wait for an internal signal */ + s->async->inttrig = usbdux_ao_inttrig; + } + + up(&this_usbduxsub->sem); + return 0; +} + +static int usbdux_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + int chan = CR_CHAN(insn->chanspec); + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << chan; /* 1 means Out */ + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + break; + default: + return -EINVAL; + break; + } + /* we don't tell the firmware here as it would take 8 frames */ + /* to submit the information. We do it in the insn_bits. */ + return insn->n; +} + +static int usbdux_dio_insn_bits(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + + struct usbduxsub *this_usbduxsub = dev->private; + int err; + + if (!this_usbduxsub) + return -EFAULT; + + if (insn->n != 2) + return -EINVAL; + + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + /* The commands are 8 bits wide */ + this_usbduxsub->dux_commands[1] = (s->io_bits) & 0x000000FF; + this_usbduxsub->dux_commands[4] = (s->state) & 0x000000FF; + this_usbduxsub->dux_commands[2] = ((s->io_bits) & 0x0000FF00) >> 8; + this_usbduxsub->dux_commands[5] = ((s->state) & 0x0000FF00) >> 8; + this_usbduxsub->dux_commands[3] = ((s->io_bits) & 0x00FF0000) >> 16; + this_usbduxsub->dux_commands[6] = ((s->state) & 0x00FF0000) >> 16; + + /* This command also tells the firmware to return */ + /* the digital input lines */ + err = send_dux_commands(this_usbduxsub, SENDDIOBITSCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + err = receive_dux_commands(this_usbduxsub, SENDDIOBITSCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + data[1] = (((unsigned int)(this_usbduxsub->insnBuffer[1]))&0xff) | + ((((unsigned int)(this_usbduxsub->insnBuffer[2]))&0xff) << 8) | + ((((unsigned int)(this_usbduxsub->insnBuffer[3]))&0xff) << 16); + + s->state = data[1]; + + up(&this_usbduxsub->sem); + return 2; +} + +/***********************************/ +/* PWM */ + +static int usbduxsub_unlink_PwmURBs(struct usbduxsub *usbduxsub_tmp) +{ + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbPwm) { + if (usbduxsub_tmp->urbPwm) + usb_kill_urb(usbduxsub_tmp->urbPwm); + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi: unlinked PwmURB: res=%d\n", err); + } + return err; +} + +/* This cancels a running acquisition operation + * in any context. + */ +static int usbdux_pwm_stop(struct usbduxsub *this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, "comedi: %s\n", __func__); + if (do_unlink) + ret = usbduxsub_unlink_PwmURBs(this_usbduxsub); + + this_usbduxsub->pwm_cmd_running = 0; + + return ret; +} + +/* force unlink - is called by comedi */ +static int usbdux_pwm_cancel(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int res = 0; + + /* unlink only if it is really running */ + res = usbdux_pwm_stop(this_usbduxsub, this_usbduxsub->pwm_cmd_running); + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi %d: sending pwm off command to the usb device.\n", + dev->minor); + res = send_dux_commands(this_usbduxsub, SENDPWMOFF); + if (res < 0) + return res; + + return res; +} + +static void usbduxsub_pwm_irq(struct urb *urb) +{ + int ret; + struct usbduxsub *this_usbduxsub; + struct comedi_device *this_comedidev; + struct comedi_subdevice *s; + + /* printk(KERN_DEBUG "PWM: IRQ\n"); */ + + /* the context variable points to the subdevice */ + this_comedidev = urb->context; + /* the private structure of the subdevice is struct usbduxsub */ + this_usbduxsub = this_comedidev->private; + + s = this_comedidev->subdevices + SUBDEV_DA; + + switch (urb->status) { + case 0: + /* success */ + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* + * after an unlink command, unplug, ... etc + * no unlink needed here. Already shutting down. + */ + if (this_usbduxsub->pwm_cmd_running) + usbdux_pwm_stop(this_usbduxsub, 0); + + return; + + default: + /* a real error */ + if (this_usbduxsub->pwm_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi_: Non-zero urb status received in " + "pwm intr context: %d\n", urb->status); + usbdux_pwm_stop(this_usbduxsub, 0); + } + return; + } + + /* are we actually running? */ + if (!(this_usbduxsub->pwm_cmd_running)) + return; + + urb->transfer_buffer_length = this_usbduxsub->sizePwmBuf; + urb->dev = this_usbduxsub->usbdev; + urb->status = 0; + if (this_usbduxsub->pwm_cmd_running) { + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi_: pwm urb resubm failed in int-cont. " + "ret=%d", ret); + if (ret == EL2NSYNC) + dev_err(&this_usbduxsub->interface->dev, + "buggy USB host controller or bug in " + "IRQ handling!\n"); + + /* don't do an unlink here */ + usbdux_pwm_stop(this_usbduxsub, 0); + } + } +} + +static int usbduxsub_submit_PwmURBs(struct usbduxsub *usbduxsub) +{ + int errFlag; + + if (!usbduxsub) + return -EFAULT; + + dev_dbg(&usbduxsub->interface->dev, "comedi_: submitting pwm-urb\n"); + + /* in case of a resubmission after an unlink... */ + usb_fill_bulk_urb(usbduxsub->urbPwm, + usbduxsub->usbdev, + usb_sndbulkpipe(usbduxsub->usbdev, PWM_EP), + usbduxsub->urbPwm->transfer_buffer, + usbduxsub->sizePwmBuf, usbduxsub_pwm_irq, + usbduxsub->comedidev); + + errFlag = usb_submit_urb(usbduxsub->urbPwm, GFP_ATOMIC); + if (errFlag) { + dev_err(&usbduxsub->interface->dev, + "comedi_: usbduxsigma: pwm: usb_submit_urb error %d\n", + errFlag); + return errFlag; + } + return 0; +} + +static int usbdux_pwm_period(struct comedi_device *dev, + struct comedi_subdevice *s, unsigned int period) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int fx2delay = 255; + + if (period < MIN_PWM_PERIOD) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: illegal period setting for pwm.\n", + dev->minor); + return -EAGAIN; + } else { + fx2delay = period / ((int)(6 * 512 * (1.0 / 0.033))) - 6; + if (fx2delay > 255) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: period %d for pwm is too low.\n", + dev->minor, period); + return -EAGAIN; + } + } + this_usbduxsub->pwmDelay = fx2delay; + this_usbduxsub->pwmPeriod = period; + dev_dbg(&this_usbduxsub->interface->dev, "%s: frequ=%d, period=%d\n", + __func__, period, fx2delay); + return 0; +} + +/* is called from insn so there's no need to do all the sanity checks */ +static int usbdux_pwm_start(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + int ret, i; + struct usbduxsub *this_usbduxsub = dev->private; + + dev_dbg(&this_usbduxsub->interface->dev, "comedi%d: %s\n", + dev->minor, __func__); + + if (this_usbduxsub->pwm_cmd_running) { + /* already running */ + return 0; + } + + this_usbduxsub->dux_commands[1] = ((uint8_t) this_usbduxsub->pwmDelay); + ret = send_dux_commands(this_usbduxsub, SENDPWMON); + if (ret < 0) + return ret; + + /* initialise the buffer */ + for (i = 0; i < this_usbduxsub->sizePwmBuf; i++) + ((char *)(this_usbduxsub->urbPwm->transfer_buffer))[i] = 0; + + this_usbduxsub->pwm_cmd_running = 1; + ret = usbduxsub_submit_PwmURBs(this_usbduxsub); + if (ret < 0) { + this_usbduxsub->pwm_cmd_running = 0; + return ret; + } + return 0; +} + +/* generates the bit pattern for PWM with the optional sign bit */ +static int usbdux_pwm_pattern(struct comedi_device *dev, + struct comedi_subdevice *s, int channel, + unsigned int value, unsigned int sign) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int i, szbuf; + char *pBuf; + char pwm_mask; + char sgn_mask; + char c; + + if (!this_usbduxsub) + return -EFAULT; + + /* this is the DIO bit which carries the PWM data */ + pwm_mask = (1 << channel); + /* this is the DIO bit which carries the optional direction bit */ + sgn_mask = (16 << channel); + /* this is the buffer which will be filled with the with bit */ + /* pattern for one period */ + szbuf = this_usbduxsub->sizePwmBuf; + pBuf = (char *)(this_usbduxsub->urbPwm->transfer_buffer); + for (i = 0; i < szbuf; i++) { + c = *pBuf; + /* reset bits */ + c = c & (~pwm_mask); + /* set the bit as long as the index is lower than the value */ + if (i < value) + c = c | pwm_mask; + /* set the optional sign bit for a relay */ + if (!sign) { + /* positive value */ + c = c & (~sgn_mask); + } else { + /* negative value */ + c = c | sgn_mask; + } + *(pBuf++) = c; + } + return 1; +} + +static int usbdux_pwm_write(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + if ((insn->n) != 1) { + /* + * doesn't make sense to have more than one value here because + * it would just overwrite the PWM buffer a couple of times + */ + return -EINVAL; + } + + /* + * the sign is set via a special INSN only, this gives us 8 bits for + * normal operation + * relay sign 0 by default + */ + return usbdux_pwm_pattern(dev, s, CR_CHAN(insn->chanspec), data[0], 0); +} + +static int usbdux_pwm_read(struct comedi_device *x1, + struct comedi_subdevice *x2, struct comedi_insn *x3, + unsigned int *x4) +{ + /* not needed */ + return -EINVAL; +}; + +/* switches on/off PWM */ +static int usbdux_pwm_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + switch (data[0]) { + case INSN_CONFIG_ARM: + /* switch it on */ + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s: pwm on\n", dev->minor, __func__); + /* + * if not zero the PWM is limited to a certain time which is + * not supported here + */ + if (data[1] != 0) + return -EINVAL; + return usbdux_pwm_start(dev, s); + case INSN_CONFIG_DISARM: + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s: pwm off\n", dev->minor, __func__); + return usbdux_pwm_cancel(dev, s); + case INSN_CONFIG_GET_PWM_STATUS: + /* + * to check if the USB transmission has failed or in case PWM + * was limited to n cycles to check if it has terminated + */ + data[1] = this_usbduxsub->pwm_cmd_running; + return 0; + case INSN_CONFIG_PWM_SET_PERIOD: + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s: setting period\n", dev->minor, + __func__); + return usbdux_pwm_period(dev, s, data[1]); + case INSN_CONFIG_PWM_GET_PERIOD: + data[1] = this_usbduxsub->pwmPeriod; + return 0; + case INSN_CONFIG_PWM_SET_H_BRIDGE: + /* value in the first byte and the sign in the second for a + relay */ + return usbdux_pwm_pattern(dev, s, + /* the channel number */ + CR_CHAN(insn->chanspec), + /* actual PWM data */ + data[1], + /* just a sign */ + (data[2] != 0)); + case INSN_CONFIG_PWM_GET_H_BRIDGE: + /* values are not kept in this driver, nothing to return */ + return -EINVAL; + } + return -EINVAL; +} + +/* end of PWM */ +/*****************************************************************/ + +static void tidy_up(struct usbduxsub *usbduxsub_tmp) +{ + int i; + + if (!usbduxsub_tmp) + return; + dev_dbg(&usbduxsub_tmp->interface->dev, "comedi_: tiding up\n"); + + /* shows the usb subsystem that the driver is down */ + if (usbduxsub_tmp->interface) + usb_set_intfdata(usbduxsub_tmp->interface, NULL); + + usbduxsub_tmp->probed = 0; + + if (usbduxsub_tmp->urbIn) { + if (usbduxsub_tmp->ai_cmd_running) { + usbduxsub_tmp->ai_cmd_running = 0; + usbduxsub_unlink_InURBs(usbduxsub_tmp); + } + for (i = 0; i < usbduxsub_tmp->numOfInBuffers; i++) { + kfree(usbduxsub_tmp->urbIn[i]->transfer_buffer); + usbduxsub_tmp->urbIn[i]->transfer_buffer = NULL; + usb_kill_urb(usbduxsub_tmp->urbIn[i]); + usb_free_urb(usbduxsub_tmp->urbIn[i]); + usbduxsub_tmp->urbIn[i] = NULL; + } + kfree(usbduxsub_tmp->urbIn); + usbduxsub_tmp->urbIn = NULL; + } + if (usbduxsub_tmp->urbOut) { + if (usbduxsub_tmp->ao_cmd_running) { + usbduxsub_tmp->ao_cmd_running = 0; + usbduxsub_unlink_OutURBs(usbduxsub_tmp); + } + for (i = 0; i < usbduxsub_tmp->numOfOutBuffers; i++) { + if (usbduxsub_tmp->urbOut[i]->transfer_buffer) { + kfree(usbduxsub_tmp-> + urbOut[i]->transfer_buffer); + usbduxsub_tmp->urbOut[i]->transfer_buffer = + NULL; + } + if (usbduxsub_tmp->urbOut[i]) { + usb_kill_urb(usbduxsub_tmp->urbOut[i]); + usb_free_urb(usbduxsub_tmp->urbOut[i]); + usbduxsub_tmp->urbOut[i] = NULL; + } + } + kfree(usbduxsub_tmp->urbOut); + usbduxsub_tmp->urbOut = NULL; + } + if (usbduxsub_tmp->urbPwm) { + if (usbduxsub_tmp->pwm_cmd_running) { + usbduxsub_tmp->pwm_cmd_running = 0; + usbduxsub_unlink_PwmURBs(usbduxsub_tmp); + } + kfree(usbduxsub_tmp->urbPwm->transfer_buffer); + usbduxsub_tmp->urbPwm->transfer_buffer = NULL; + usb_kill_urb(usbduxsub_tmp->urbPwm); + usb_free_urb(usbduxsub_tmp->urbPwm); + usbduxsub_tmp->urbPwm = NULL; + } + kfree(usbduxsub_tmp->inBuffer); + usbduxsub_tmp->inBuffer = NULL; + kfree(usbduxsub_tmp->insnBuffer); + usbduxsub_tmp->insnBuffer = NULL; + kfree(usbduxsub_tmp->outBuffer); + usbduxsub_tmp->outBuffer = NULL; + kfree(usbduxsub_tmp->dac_commands); + usbduxsub_tmp->dac_commands = NULL; + kfree(usbduxsub_tmp->dux_commands); + usbduxsub_tmp->dux_commands = NULL; + usbduxsub_tmp->ai_cmd_running = 0; + usbduxsub_tmp->ao_cmd_running = 0; + usbduxsub_tmp->pwm_cmd_running = 0; +} + +static void usbdux_firmware_request_complete_handler(const struct firmware *fw, + void *context) +{ + struct usbduxsub *usbduxsub_tmp = context; + struct usb_device *usbdev = usbduxsub_tmp->usbdev; + int ret; + + if (fw == NULL) { + dev_err(&usbdev->dev, + "Firmware complete handler without firmware!\n"); + return; + } + + /* + * we need to upload the firmware here because fw will be + * freed once we've left this function + */ + ret = firmwareUpload(usbduxsub_tmp, fw->data, fw->size); + + if (ret) { + dev_err(&usbdev->dev, + "Could not upload firmware (err=%d)\n", ret); + goto out; + } + comedi_usb_auto_config(usbdev, BOARDNAME); +out: + release_firmware(fw); +} + +/* allocate memory for the urbs and initialise them */ +static int usbduxsigma_probe(struct usb_interface *uinterf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(uinterf); + struct device *dev = &uinterf->dev; + int i; + int index; + int ret; + + dev_dbg(dev, "comedi_: usbdux_: " + "finding a free structure for the usb-device\n"); + + down(&start_stop_sem); + /* look for a free place in the usbdux array */ + index = -1; + for (i = 0; i < NUMUSBDUX; i++) { + if (!(usbduxsub[i].probed)) { + index = i; + break; + } + } + + /* no more space */ + if (index == -1) { + dev_err(dev, "Too many usbduxsigma-devices connected.\n"); + up(&start_stop_sem); + return -EMFILE; + } + dev_dbg(dev, "comedi_: usbdux: " + "usbduxsub[%d] is ready to connect to comedi.\n", index); + + sema_init(&(usbduxsub[index].sem), 1); + /* save a pointer to the usb device */ + usbduxsub[index].usbdev = udev; + + /* save the interface itself */ + usbduxsub[index].interface = uinterf; + /* get the interface number from the interface */ + usbduxsub[index].ifnum = uinterf->altsetting->desc.bInterfaceNumber; + /* hand the private data over to the usb subsystem */ + /* will be needed for disconnect */ + usb_set_intfdata(uinterf, &(usbduxsub[index])); + + dev_dbg(dev, "comedi_: usbdux: ifnum=%d\n", usbduxsub[index].ifnum); + + /* test if it is high speed (USB 2.0) */ + usbduxsub[index].high_speed = + (usbduxsub[index].usbdev->speed == USB_SPEED_HIGH); + + /* create space for the commands of the DA converter */ + usbduxsub[index].dac_commands = kzalloc(NUMOUTCHANNELS, GFP_KERNEL); + if (!usbduxsub[index].dac_commands) { + dev_err(dev, "comedi_: usbduxsigma: " + "error alloc space for dac commands\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space for the commands going to the usb device */ + usbduxsub[index].dux_commands = kzalloc(SIZEOFDUXBUFFER, GFP_KERNEL); + if (!usbduxsub[index].dux_commands) { + dev_err(dev, "comedi_: usbduxsigma: " + "error alloc space for dux commands\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space for the in buffer and set it to zero */ + usbduxsub[index].inBuffer = kzalloc(SIZEINBUF, GFP_KERNEL); + if (!(usbduxsub[index].inBuffer)) { + dev_err(dev, "comedi_: usbduxsigma: " + "could not alloc space for inBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space of the instruction buffer */ + usbduxsub[index].insnBuffer = kzalloc(SIZEINSNBUF, GFP_KERNEL); + if (!(usbduxsub[index].insnBuffer)) { + dev_err(dev, "comedi_: usbduxsigma: " + "could not alloc space for insnBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space for the outbuffer */ + usbduxsub[index].outBuffer = kzalloc(SIZEOUTBUF, GFP_KERNEL); + if (!(usbduxsub[index].outBuffer)) { + dev_err(dev, "comedi_: usbduxsigma: " + "could not alloc space for outBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* setting to alternate setting 3: enabling iso ep and bulk ep. */ + i = usb_set_interface(usbduxsub[index].usbdev, + usbduxsub[index].ifnum, 3); + if (i < 0) { + dev_err(dev, "comedi_: usbduxsigma%d: " + "could not set alternate setting 3 in high speed.\n", + index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENODEV; + } + if (usbduxsub[index].high_speed) + usbduxsub[index].numOfInBuffers = NUMOFINBUFFERSHIGH; + else + usbduxsub[index].numOfInBuffers = NUMOFINBUFFERSFULL; + + usbduxsub[index].urbIn = + kzalloc(sizeof(struct urb *) * usbduxsub[index].numOfInBuffers, + GFP_KERNEL); + if (!(usbduxsub[index].urbIn)) { + dev_err(dev, "comedi_: usbduxsigma: " + "Could not alloc. urbIn array\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + for (i = 0; i < usbduxsub[index].numOfInBuffers; i++) { + /* one frame: 1ms */ + usbduxsub[index].urbIn[i] = usb_alloc_urb(1, GFP_KERNEL); + if (usbduxsub[index].urbIn[i] == NULL) { + dev_err(dev, "comedi_: usbduxsigma%d: " + "Could not alloc. urb(%d)\n", index, i); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbIn[i]->dev = usbduxsub[index].usbdev; + /* will be filled later with a pointer to the comedi-device */ + /* and ONLY then the urb should be submitted */ + usbduxsub[index].urbIn[i]->context = NULL; + usbduxsub[index].urbIn[i]->pipe = + usb_rcvisocpipe(usbduxsub[index].usbdev, ISOINEP); + usbduxsub[index].urbIn[i]->transfer_flags = URB_ISO_ASAP; + usbduxsub[index].urbIn[i]->transfer_buffer = + kzalloc(SIZEINBUF, GFP_KERNEL); + if (!(usbduxsub[index].urbIn[i]->transfer_buffer)) { + dev_err(dev, "comedi_: usbduxsigma%d: " + "could not alloc. transb.\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbIn[i]->complete = usbduxsub_ai_IsocIrq; + usbduxsub[index].urbIn[i]->number_of_packets = 1; + usbduxsub[index].urbIn[i]->transfer_buffer_length = SIZEINBUF; + usbduxsub[index].urbIn[i]->iso_frame_desc[0].offset = 0; + usbduxsub[index].urbIn[i]->iso_frame_desc[0].length = + SIZEINBUF; + } + + /* out */ + if (usbduxsub[index].high_speed) + usbduxsub[index].numOfOutBuffers = NUMOFOUTBUFFERSHIGH; + else + usbduxsub[index].numOfOutBuffers = NUMOFOUTBUFFERSFULL; + + usbduxsub[index].urbOut = + kzalloc(sizeof(struct urb *) * usbduxsub[index].numOfOutBuffers, + GFP_KERNEL); + if (!(usbduxsub[index].urbOut)) { + dev_err(dev, "comedi_: usbduxsigma: " + "Could not alloc. urbOut array\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + for (i = 0; i < usbduxsub[index].numOfOutBuffers; i++) { + /* one frame: 1ms */ + usbduxsub[index].urbOut[i] = usb_alloc_urb(1, GFP_KERNEL); + if (usbduxsub[index].urbOut[i] == NULL) { + dev_err(dev, "comedi_: usbduxsigma%d: " + "Could not alloc. urb(%d)\n", index, i); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbOut[i]->dev = usbduxsub[index].usbdev; + /* will be filled later with a pointer to the comedi-device */ + /* and ONLY then the urb should be submitted */ + usbduxsub[index].urbOut[i]->context = NULL; + usbduxsub[index].urbOut[i]->pipe = + usb_sndisocpipe(usbduxsub[index].usbdev, ISOOUTEP); + usbduxsub[index].urbOut[i]->transfer_flags = URB_ISO_ASAP; + usbduxsub[index].urbOut[i]->transfer_buffer = + kzalloc(SIZEOUTBUF, GFP_KERNEL); + if (!(usbduxsub[index].urbOut[i]->transfer_buffer)) { + dev_err(dev, "comedi_: usbduxsigma%d: " + "could not alloc. transb.\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbOut[i]->complete = usbduxsub_ao_IsocIrq; + usbduxsub[index].urbOut[i]->number_of_packets = 1; + usbduxsub[index].urbOut[i]->transfer_buffer_length = + SIZEOUTBUF; + usbduxsub[index].urbOut[i]->iso_frame_desc[0].offset = 0; + usbduxsub[index].urbOut[i]->iso_frame_desc[0].length = + SIZEOUTBUF; + if (usbduxsub[index].high_speed) { + /* uframes */ + usbduxsub[index].urbOut[i]->interval = 8; + } else { + /* frames */ + usbduxsub[index].urbOut[i]->interval = 1; + } + } + + /* pwm */ + if (usbduxsub[index].high_speed) { + /* max bulk ep size in high speed */ + usbduxsub[index].sizePwmBuf = 512; + usbduxsub[index].urbPwm = usb_alloc_urb(0, GFP_KERNEL); + if (usbduxsub[index].urbPwm == NULL) { + dev_err(dev, "comedi_: usbduxsigma%d: " + "Could not alloc. pwm urb\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbPwm->transfer_buffer = + kzalloc(usbduxsub[index].sizePwmBuf, GFP_KERNEL); + if (!(usbduxsub[index].urbPwm->transfer_buffer)) { + dev_err(dev, "comedi_: usbduxsigma%d: " + "could not alloc. transb. for pwm\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + } else { + usbduxsub[index].urbPwm = NULL; + usbduxsub[index].sizePwmBuf = 0; + } + + usbduxsub[index].ai_cmd_running = 0; + usbduxsub[index].ao_cmd_running = 0; + usbduxsub[index].pwm_cmd_running = 0; + + /* we've reached the bottom of the function */ + usbduxsub[index].probed = 1; + up(&start_stop_sem); + + ret = request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, + "usbduxsigma_firmware.bin", + &udev->dev, + GFP_KERNEL, + usbduxsub + index, + usbdux_firmware_request_complete_handler + ); + + if (ret) { + dev_err(dev, "Could not load firmware (err=%d)\n", ret); + return ret; + } + + dev_info(dev, "comedi_: successfully initialised.\n"); + /* success */ + return 0; +} + +static void usbduxsigma_disconnect(struct usb_interface *intf) +{ + struct usbduxsub *usbduxsub_tmp = usb_get_intfdata(intf); + struct usb_device *udev = interface_to_usbdev(intf); + + if (!usbduxsub_tmp) { + dev_err(&intf->dev, + "comedi_: disconnect called with null pointer.\n"); + return; + } + if (usbduxsub_tmp->usbdev != udev) { + dev_err(&intf->dev, "comedi_: BUG! wrong ptr!\n"); + return; + } + if (usbduxsub_tmp->ai_cmd_running) + /* we are still running a command */ + usbdux_ai_stop(usbduxsub_tmp, 1); + if (usbduxsub_tmp->ao_cmd_running) + /* we are still running a command */ + usbdux_ao_stop(usbduxsub_tmp, 1); + comedi_usb_auto_unconfig(udev); + down(&start_stop_sem); + down(&usbduxsub_tmp->sem); + tidy_up(usbduxsub_tmp); + up(&usbduxsub_tmp->sem); + up(&start_stop_sem); + dev_info(&intf->dev, "comedi_: disconnected from the usb\n"); +} + +/* is called when comedi-config is called */ +static int usbduxsigma_attach(struct comedi_device *dev, + struct comedi_devconfig *it) +{ + int ret; + int index; + int i; + struct usbduxsub *udev; + + int offset; + + struct comedi_subdevice *s = NULL; + dev->private = NULL; + + down(&start_stop_sem); + /* find a valid device which has been detected by the probe function of + * the usb */ + index = -1; + for (i = 0; i < NUMUSBDUX; i++) { + if ((usbduxsub[i].probed) && (!usbduxsub[i].attached)) { + index = i; + break; + } + } + + if (index < 0) { + printk(KERN_ERR "comedi%d: usbduxsigma: error: attach failed," + "dev not connected to the usb bus.\n", dev->minor); + up(&start_stop_sem); + return -ENODEV; + } + + udev = &usbduxsub[index]; + down(&udev->sem); + /* pointer back to the corresponding comedi device */ + udev->comedidev = dev; + + /* trying to upload the firmware into the FX2 */ + if (comedi_aux_data(it->options, 0) && + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { + firmwareUpload(udev, comedi_aux_data(it->options, 0), + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]); + } + + dev->board_name = BOARDNAME; + + /* set number of subdevices */ + if (udev->high_speed) { + /* with pwm */ + dev->n_subdevices = 4; + } else { + /* without pwm */ + dev->n_subdevices = 3; + } + + /* allocate space for the subdevices */ + ret = alloc_subdevices(dev, dev->n_subdevices); + if (ret < 0) { + dev_err(&udev->interface->dev, + "comedi%d: no space for subdev\n", dev->minor); + up(&udev->sem); + up(&start_stop_sem); + return ret; + } + + /* private structure is also simply the usb-structure */ + dev->private = udev; + + /* the first subdevice is the A/D converter */ + s = dev->subdevices + SUBDEV_AD; + /* the URBs get the comedi subdevice */ + /* which is responsible for reading */ + /* this is the subdevice which reads data */ + dev->read_subdev = s; + /* the subdevice receives as private structure the */ + /* usb-structure */ + s->private = NULL; + /* analog input */ + s->type = COMEDI_SUBD_AI; + /* readable and ref is to ground, 32 bit wide data! */ + s->subdev_flags = SDF_READABLE | SDF_GROUND | + SDF_CMD_READ | SDF_LSAMPL; + /* 16 A/D channels */ + s->n_chan = NUMCHANNELS; + /* length of the channellist */ + s->len_chanlist = NUMCHANNELS; + /* callback functions */ + s->insn_read = usbdux_ai_insn_read; + s->do_cmdtest = usbdux_ai_cmdtest; + s->do_cmd = usbdux_ai_cmd; + s->cancel = usbdux_ai_cancel; + /* max value from the A/D converter (24bit) */ + s->maxdata = 0x00FFFFFF; + /* range table to convert to physical units */ + s->range_table = (&range_usbdux_ai_range); + + /* analog out */ + s = dev->subdevices + SUBDEV_DA; + /* analog out */ + s->type = COMEDI_SUBD_AO; + /* backward pointer */ + dev->write_subdev = s; + /* the subdevice receives as private structure the */ + /* usb-structure */ + s->private = NULL; + /* are writable */ + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; + /* 4 channels */ + s->n_chan = 4; + /* length of the channellist */ + s->len_chanlist = 4; + /* 8 bit resolution */ + s->maxdata = 0x00ff; + /* unipolar range */ + s->range_table = (&range_usbdux_ao_range); + /* callback */ + s->do_cmdtest = usbdux_ao_cmdtest; + s->do_cmd = usbdux_ao_cmd; + s->cancel = usbdux_ao_cancel; + s->insn_read = usbdux_ao_insn_read; + s->insn_write = usbdux_ao_insn_write; + + /* digital I/O */ + s = dev->subdevices + SUBDEV_DIO; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + /* 8 external and 16 internal channels */ + s->n_chan = 24; + s->maxdata = 1; + s->range_table = (&range_digital); + s->insn_bits = usbdux_dio_insn_bits; + s->insn_config = usbdux_dio_insn_config; + /* we don't use it */ + s->private = NULL; + + if (udev->high_speed) { + /* timer / pwm */ + s = dev->subdevices + SUBDEV_PWM; + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = SDF_WRITABLE | SDF_PWM_HBRIDGE; + s->n_chan = 8; + /* this defines the max duty cycle resolution */ + s->maxdata = udev->sizePwmBuf; + s->insn_write = usbdux_pwm_write; + s->insn_read = usbdux_pwm_read; + s->insn_config = usbdux_pwm_config; + usbdux_pwm_period(dev, s, PWM_DEFAULT_PERIOD); + } + /* finally decide that it's attached */ + udev->attached = 1; + + up(&udev->sem); + + up(&start_stop_sem); + + offset = usbdux_getstatusinfo(dev, 0); + if (offset < 0) + dev_err(&udev->interface->dev, + "Communication to USBDUXSIGMA failed!" + "Check firmware and cabling."); + + dev_info(&udev->interface->dev, + "comedi%d: attached, ADC_zero = %x", dev->minor, offset); + + return 0; +} + +static int usbduxsigma_detach(struct comedi_device *dev) +{ + struct usbduxsub *usbduxsub_tmp; + + if (!dev) { + printk(KERN_ERR + "comedi? usbduxsigma detach: dev=NULL\n"); + return -EFAULT; + } + + usbduxsub_tmp = dev->private; + if (!usbduxsub_tmp) { + printk(KERN_ERR + "comedi?: usbduxsigma detach: private=NULL\n"); + return -EFAULT; + } + + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi%d: detach usb device\n", + dev->minor); + + down(&usbduxsub_tmp->sem); + /* Don't allow detach to free the private structure */ + /* It's one entry of of usbduxsub[] */ + dev->private = NULL; + usbduxsub_tmp->attached = 0; + usbduxsub_tmp->comedidev = NULL; + dev_info(&usbduxsub_tmp->interface->dev, + "comedi%d: successfully detached.\n", dev->minor); + up(&usbduxsub_tmp->sem); + return 0; +} + +/* main driver struct */ +static struct comedi_driver driver_usbduxsigma = { + .driver_name = "usbduxsigma", + .module = THIS_MODULE, + .attach = usbduxsigma_attach, + .detach = usbduxsigma_detach, +}; + +/* Table with the USB-devices */ +static const struct usb_device_id usbduxsigma_table[] = { + {USB_DEVICE(0x13d8, 0x0020)}, + {USB_DEVICE(0x13d8, 0x0021)}, + {USB_DEVICE(0x13d8, 0x0022)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usbduxsigma_table); + +/* The usbduxsub-driver */ +static struct usb_driver usbduxsigma_driver = { + .name = BOARDNAME, + .probe = usbduxsigma_probe, + .disconnect = usbduxsigma_disconnect, + .id_table = usbduxsigma_table, +}; + +/* Can't use the nice macro as I have also to initialise the USB */ +/* subsystem: */ +/* registering the usb-system _and_ the comedi-driver */ +static int __init init_usbduxsigma(void) +{ + printk(KERN_INFO KBUILD_MODNAME ": " + DRIVER_VERSION ":" DRIVER_DESC "\n"); + usb_register(&usbduxsigma_driver); + comedi_driver_register(&driver_usbduxsigma); + return 0; +} + +/* deregistering the comedi driver and the usb-subsystem */ +static void __exit exit_usbduxsigma(void) +{ + comedi_driver_unregister(&driver_usbduxsigma); + usb_deregister(&usbduxsigma_driver); +} + +module_init(init_usbduxsigma); +module_exit(exit_usbduxsigma); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/vmk80xx.c b/drivers/staging/comedi/drivers/vmk80xx.c new file mode 100644 index 00000000..3d13ca6e --- /dev/null +++ b/drivers/staging/comedi/drivers/vmk80xx.c @@ -0,0 +1,1561 @@ +/* + comedi/drivers/vmk80xx.c + Velleman USB Board Low-Level Driver + + Copyright (C) 2009 Manuel Gebele , Germany + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/* +Driver: vmk80xx +Description: Velleman USB Board Low-Level Driver +Devices: K8055/K8061 aka VM110/VM140 +Author: Manuel Gebele +Updated: Sun, 10 May 2009 11:14:59 +0200 +Status: works + +Supports: + - analog input + - analog output + - digital input + - digital output + - counter + - pwm +*/ +/* +Changelog: + +0.8.81 -3- code completely rewritten (adjust driver logic) +0.8.81 -2- full support for K8061 +0.8.81 -1- fix some mistaken among others the number of + supported boards and I/O handling + +0.7.76 -4- renamed to vmk80xx +0.7.76 -3- detect K8061 (only theoretically supported) +0.7.76 -2- code completely rewritten (adjust driver logic) +0.7.76 -1- support for digital and counter subdevice +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../comedidev.h" + +#define BOARDNAME "vmk80xx" + +MODULE_AUTHOR("Manuel Gebele "); +MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver"); +MODULE_SUPPORTED_DEVICE("K8055/K8061 aka VM110/VM140"); +MODULE_VERSION("0.8.01"); +MODULE_LICENSE("GPL"); + +enum { + DEVICE_VMK8055, + DEVICE_VMK8061 +}; + +static const struct usb_device_id vmk80xx_id_table[] = { + {USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055}, + {USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055}, + {USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055}, + {USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055}, + {USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061}, + {USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061}, + {USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061}, + {USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061}, + {USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061}, + {USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061}, + {USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061}, + {USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061}, + {} /* terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, vmk80xx_id_table); + +#define VMK8055_DI_REG 0x00 +#define VMK8055_DO_REG 0x01 +#define VMK8055_AO1_REG 0x02 +#define VMK8055_AO2_REG 0x03 +#define VMK8055_AI1_REG 0x02 +#define VMK8055_AI2_REG 0x03 +#define VMK8055_CNT1_REG 0x04 +#define VMK8055_CNT2_REG 0x06 + +#define VMK8061_CH_REG 0x01 +#define VMK8061_DI_REG 0x01 +#define VMK8061_DO_REG 0x01 +#define VMK8061_PWM_REG1 0x01 +#define VMK8061_PWM_REG2 0x02 +#define VMK8061_CNT_REG 0x02 +#define VMK8061_AO_REG 0x02 +#define VMK8061_AI_REG1 0x02 +#define VMK8061_AI_REG2 0x03 + +#define VMK8055_CMD_RST 0x00 +#define VMK8055_CMD_DEB1_TIME 0x01 +#define VMK8055_CMD_DEB2_TIME 0x02 +#define VMK8055_CMD_RST_CNT1 0x03 +#define VMK8055_CMD_RST_CNT2 0x04 +#define VMK8055_CMD_WRT_AD 0x05 + +#define VMK8061_CMD_RD_AI 0x00 +#define VMK8061_CMR_RD_ALL_AI 0x01 /* !non-active! */ +#define VMK8061_CMD_SET_AO 0x02 +#define VMK8061_CMD_SET_ALL_AO 0x03 /* !non-active! */ +#define VMK8061_CMD_OUT_PWM 0x04 +#define VMK8061_CMD_RD_DI 0x05 +#define VMK8061_CMD_DO 0x06 /* !non-active! */ +#define VMK8061_CMD_CLR_DO 0x07 +#define VMK8061_CMD_SET_DO 0x08 +#define VMK8061_CMD_RD_CNT 0x09 /* TODO: completely pointless? */ +#define VMK8061_CMD_RST_CNT 0x0a /* TODO: completely pointless? */ +#define VMK8061_CMD_RD_VERSION 0x0b /* internal usage */ +#define VMK8061_CMD_RD_JMP_STAT 0x0c /* TODO: not implemented yet */ +#define VMK8061_CMD_RD_PWR_STAT 0x0d /* internal usage */ +#define VMK8061_CMD_RD_DO 0x0e +#define VMK8061_CMD_RD_AO 0x0f +#define VMK8061_CMD_RD_PWM 0x10 + +#define VMK80XX_MAX_BOARDS COMEDI_NUM_BOARD_MINORS + +#define TRANS_OUT_BUSY 1 +#define TRANS_IN_BUSY 2 +#define TRANS_IN_RUNNING 3 + +#define IC3_VERSION (1 << 0) +#define IC6_VERSION (1 << 1) + +#define URB_RCV_FLAG (1 << 0) +#define URB_SND_FLAG (1 << 1) + +#define CONFIG_VMK80XX_DEBUG +#undef CONFIG_VMK80XX_DEBUG + +#ifdef CONFIG_VMK80XX_DEBUG +static int dbgvm = 1; +#else +static int dbgvm; +#endif + +#ifdef CONFIG_COMEDI_DEBUG +static int dbgcm = 1; +#else +static int dbgcm; +#endif + +#define dbgvm(fmt, arg...) \ +do { \ + if (dbgvm) \ + printk(KERN_DEBUG fmt, ##arg); \ +} while (0) + +#define dbgcm(fmt, arg...) \ +do { \ + if (dbgcm) \ + printk(KERN_DEBUG fmt, ##arg); \ +} while (0) + +enum vmk80xx_model { + VMK8055_MODEL, + VMK8061_MODEL +}; + +struct firmware_version { + unsigned char ic3_vers[32]; /* USB-Controller */ + unsigned char ic6_vers[32]; /* CPU */ +}; + +static const struct comedi_lrange vmk8055_range = { + 1, {UNI_RANGE(5)} +}; + +static const struct comedi_lrange vmk8061_range = { + 2, {UNI_RANGE(5), UNI_RANGE(10)} +}; + +struct vmk80xx_board { + const char *name; + enum vmk80xx_model model; + const struct comedi_lrange *range; + __u8 ai_chans; + __le16 ai_bits; + __u8 ao_chans; + __le16 ao_bits; + __u8 di_chans; + __le16 di_bits; + __u8 do_chans; + __le16 do_bits; + __u8 cnt_chans; + __le16 cnt_bits; + __u8 pwm_chans; + __le16 pwm_bits; +}; + +enum { + VMK80XX_SUBD_AI, + VMK80XX_SUBD_AO, + VMK80XX_SUBD_DI, + VMK80XX_SUBD_DO, + VMK80XX_SUBD_CNT, + VMK80XX_SUBD_PWM, +}; + +struct vmk80xx_usb { + struct usb_device *udev; + struct usb_interface *intf; + struct usb_endpoint_descriptor *ep_rx; + struct usb_endpoint_descriptor *ep_tx; + struct usb_anchor rx_anchor; + struct usb_anchor tx_anchor; + struct vmk80xx_board board; + struct firmware_version fw; + struct semaphore limit_sem; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + unsigned char *usb_rx_buf; + unsigned char *usb_tx_buf; + unsigned long flags; + int probed; + int attached; + int count; +}; + +static struct vmk80xx_usb vmb[VMK80XX_MAX_BOARDS]; + +static DEFINE_MUTEX(glb_mutex); + +static void vmk80xx_tx_callback(struct urb *urb) +{ + struct vmk80xx_usb *dev = urb->context; + int stat = urb->status; + + dbgvm("vmk80xx: %s\n", __func__); + + if (stat && !(stat == -ENOENT + || stat == -ECONNRESET || stat == -ESHUTDOWN)) + dbgcm("comedi#: vmk80xx: %s - nonzero urb status (%d)\n", + __func__, stat); + + if (!test_bit(TRANS_OUT_BUSY, &dev->flags)) + return; + + clear_bit(TRANS_OUT_BUSY, &dev->flags); + + wake_up_interruptible(&dev->write_wait); +} + +static void vmk80xx_rx_callback(struct urb *urb) +{ + struct vmk80xx_usb *dev = urb->context; + int stat = urb->status; + + dbgvm("vmk80xx: %s\n", __func__); + + switch (stat) { + case 0: + break; + case -ENOENT: + case -ECONNRESET: + case -ESHUTDOWN: + break; + default: + dbgcm("comedi#: vmk80xx: %s - nonzero urb status (%d)\n", + __func__, stat); + goto resubmit; + } + + goto exit; +resubmit: + if (test_bit(TRANS_IN_RUNNING, &dev->flags) && dev->intf) { + usb_anchor_urb(urb, &dev->rx_anchor); + + if (!usb_submit_urb(urb, GFP_KERNEL)) + goto exit; + + err("comedi#: vmk80xx: %s - submit urb failed\n", __func__); + + usb_unanchor_urb(urb); + } +exit: + clear_bit(TRANS_IN_BUSY, &dev->flags); + + wake_up_interruptible(&dev->read_wait); +} + +static int vmk80xx_check_data_link(struct vmk80xx_usb *dev) +{ + unsigned int tx_pipe; + unsigned int rx_pipe; + unsigned char tx[1]; + unsigned char rx[2]; + + dbgvm("vmk80xx: %s\n", __func__); + + tx_pipe = usb_sndbulkpipe(dev->udev, 0x01); + rx_pipe = usb_rcvbulkpipe(dev->udev, 0x81); + + tx[0] = VMK8061_CMD_RD_PWR_STAT; + + /* + * Check that IC6 (PIC16F871) is powered and + * running and the data link between IC3 and + * IC6 is working properly + */ + usb_bulk_msg(dev->udev, tx_pipe, tx, 1, NULL, dev->ep_tx->bInterval); + usb_bulk_msg(dev->udev, rx_pipe, rx, 2, NULL, HZ * 10); + + return (int)rx[1]; +} + +static void vmk80xx_read_eeprom(struct vmk80xx_usb *dev, int flag) +{ + unsigned int tx_pipe; + unsigned int rx_pipe; + unsigned char tx[1]; + unsigned char rx[64]; + int cnt; + + dbgvm("vmk80xx: %s\n", __func__); + + tx_pipe = usb_sndbulkpipe(dev->udev, 0x01); + rx_pipe = usb_rcvbulkpipe(dev->udev, 0x81); + + tx[0] = VMK8061_CMD_RD_VERSION; + + /* + * Read the firmware version info of IC3 and + * IC6 from the internal EEPROM of the IC + */ + usb_bulk_msg(dev->udev, tx_pipe, tx, 1, NULL, dev->ep_tx->bInterval); + usb_bulk_msg(dev->udev, rx_pipe, rx, 64, &cnt, HZ * 10); + + rx[cnt] = '\0'; + + if (flag & IC3_VERSION) + strncpy(dev->fw.ic3_vers, rx + 1, 24); + else /* IC6_VERSION */ + strncpy(dev->fw.ic6_vers, rx + 25, 24); +} + +static int vmk80xx_reset_device(struct vmk80xx_usb *dev) +{ + struct urb *urb; + unsigned int tx_pipe; + int ival; + size_t size; + + dbgvm("vmk80xx: %s\n", __func__); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + tx_pipe = usb_sndintpipe(dev->udev, 0x01); + + ival = dev->ep_tx->bInterval; + size = le16_to_cpu(dev->ep_tx->wMaxPacketSize); + + dev->usb_tx_buf[0] = VMK8055_CMD_RST; + dev->usb_tx_buf[1] = 0x00; + dev->usb_tx_buf[2] = 0x00; + dev->usb_tx_buf[3] = 0x00; + dev->usb_tx_buf[4] = 0x00; + dev->usb_tx_buf[5] = 0x00; + dev->usb_tx_buf[6] = 0x00; + dev->usb_tx_buf[7] = 0x00; + + usb_fill_int_urb(urb, dev->udev, tx_pipe, dev->usb_tx_buf, + size, vmk80xx_tx_callback, dev, ival); + + usb_anchor_urb(urb, &dev->tx_anchor); + + return usb_submit_urb(urb, GFP_KERNEL); +} + +static void vmk80xx_build_int_urb(struct urb *urb, int flag) +{ + struct vmk80xx_usb *dev = urb->context; + __u8 rx_addr; + __u8 tx_addr; + unsigned int pipe; + unsigned char *buf; + size_t size; + void (*callback) (struct urb *); + int ival; + + dbgvm("vmk80xx: %s\n", __func__); + + if (flag & URB_RCV_FLAG) { + rx_addr = dev->ep_rx->bEndpointAddress; + pipe = usb_rcvintpipe(dev->udev, rx_addr); + buf = dev->usb_rx_buf; + size = le16_to_cpu(dev->ep_rx->wMaxPacketSize); + callback = vmk80xx_rx_callback; + ival = dev->ep_rx->bInterval; + } else { /* URB_SND_FLAG */ + tx_addr = dev->ep_tx->bEndpointAddress; + pipe = usb_sndintpipe(dev->udev, tx_addr); + buf = dev->usb_tx_buf; + size = le16_to_cpu(dev->ep_tx->wMaxPacketSize); + callback = vmk80xx_tx_callback; + ival = dev->ep_tx->bInterval; + } + + usb_fill_int_urb(urb, dev->udev, pipe, buf, size, callback, dev, ival); +} + +static void vmk80xx_do_bulk_msg(struct vmk80xx_usb *dev) +{ + __u8 tx_addr; + __u8 rx_addr; + unsigned int tx_pipe; + unsigned int rx_pipe; + size_t size; + + dbgvm("vmk80xx: %s\n", __func__); + + set_bit(TRANS_IN_BUSY, &dev->flags); + set_bit(TRANS_OUT_BUSY, &dev->flags); + + tx_addr = dev->ep_tx->bEndpointAddress; + rx_addr = dev->ep_rx->bEndpointAddress; + tx_pipe = usb_sndbulkpipe(dev->udev, tx_addr); + rx_pipe = usb_rcvbulkpipe(dev->udev, rx_addr); + + /* + * The max packet size attributes of the K8061 + * input/output endpoints are identical + */ + size = le16_to_cpu(dev->ep_tx->wMaxPacketSize); + + usb_bulk_msg(dev->udev, tx_pipe, dev->usb_tx_buf, + size, NULL, dev->ep_tx->bInterval); + usb_bulk_msg(dev->udev, rx_pipe, dev->usb_rx_buf, size, NULL, HZ * 10); + + clear_bit(TRANS_OUT_BUSY, &dev->flags); + clear_bit(TRANS_IN_BUSY, &dev->flags); +} + +static int vmk80xx_read_packet(struct vmk80xx_usb *dev) +{ + struct urb *urb; + int retval; + + dbgvm("vmk80xx: %s\n", __func__); + + if (!dev->intf) + return -ENODEV; + + /* Only useful for interrupt transfers */ + if (test_bit(TRANS_IN_BUSY, &dev->flags)) + if (wait_event_interruptible(dev->read_wait, + !test_bit(TRANS_IN_BUSY, + &dev->flags))) + return -ERESTART; + + if (dev->board.model == VMK8061_MODEL) { + vmk80xx_do_bulk_msg(dev); + + return 0; + } + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + urb->context = dev; + vmk80xx_build_int_urb(urb, URB_RCV_FLAG); + + set_bit(TRANS_IN_RUNNING, &dev->flags); + set_bit(TRANS_IN_BUSY, &dev->flags); + + usb_anchor_urb(urb, &dev->rx_anchor); + + retval = usb_submit_urb(urb, GFP_KERNEL); + if (!retval) + goto exit; + + clear_bit(TRANS_IN_RUNNING, &dev->flags); + usb_unanchor_urb(urb); + +exit: + usb_free_urb(urb); + + return retval; +} + +static int vmk80xx_write_packet(struct vmk80xx_usb *dev, int cmd) +{ + struct urb *urb; + int retval; + + dbgvm("vmk80xx: %s\n", __func__); + + if (!dev->intf) + return -ENODEV; + + if (test_bit(TRANS_OUT_BUSY, &dev->flags)) + if (wait_event_interruptible(dev->write_wait, + !test_bit(TRANS_OUT_BUSY, + &dev->flags))) + return -ERESTART; + + if (dev->board.model == VMK8061_MODEL) { + dev->usb_tx_buf[0] = cmd; + vmk80xx_do_bulk_msg(dev); + + return 0; + } + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return -ENOMEM; + + urb->context = dev; + vmk80xx_build_int_urb(urb, URB_SND_FLAG); + + set_bit(TRANS_OUT_BUSY, &dev->flags); + + usb_anchor_urb(urb, &dev->tx_anchor); + + dev->usb_tx_buf[0] = cmd; + + retval = usb_submit_urb(urb, GFP_KERNEL); + if (!retval) + goto exit; + + clear_bit(TRANS_OUT_BUSY, &dev->flags); + usb_unanchor_urb(urb); + +exit: + usb_free_urb(urb); + + return retval; +} + +#define DIR_IN 1 +#define DIR_OUT 2 + +static int rudimentary_check(struct vmk80xx_usb *dev, int dir) +{ + if (!dev) + return -EFAULT; + if (!dev->probed) + return -ENODEV; + if (!dev->attached) + return -ENODEV; + if (dir & DIR_IN) { + if (test_bit(TRANS_IN_BUSY, &dev->flags)) + return -EBUSY; + } + if (dir & DIR_OUT) { + if (test_bit(TRANS_OUT_BUSY, &dev->flags)) + return -EBUSY; + } + + return 0; +} + +static int vmk80xx_ai_rinsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + int chan; + int reg[2]; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_IN); + if (n) + return n; + + down(&dev->limit_sem); + chan = CR_CHAN(insn->chanspec); + + switch (dev->board.model) { + case VMK8055_MODEL: + if (!chan) + reg[0] = VMK8055_AI1_REG; + else + reg[0] = VMK8055_AI2_REG; + break; + case VMK8061_MODEL: + reg[0] = VMK8061_AI_REG1; + reg[1] = VMK8061_AI_REG2; + dev->usb_tx_buf[0] = VMK8061_CMD_RD_AI; + dev->usb_tx_buf[VMK8061_CH_REG] = chan; + break; + } + + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + if (dev->board.model == VMK8055_MODEL) { + data[n] = dev->usb_rx_buf[reg[0]]; + continue; + } + + /* VMK8061_MODEL */ + data[n] = dev->usb_rx_buf[reg[0]] + 256 * + dev->usb_rx_buf[reg[1]]; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_ao_winsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + int chan; + int cmd; + int reg; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_OUT); + if (n) + return n; + + down(&dev->limit_sem); + chan = CR_CHAN(insn->chanspec); + + switch (dev->board.model) { + case VMK8055_MODEL: + cmd = VMK8055_CMD_WRT_AD; + if (!chan) + reg = VMK8055_AO1_REG; + else + reg = VMK8055_AO2_REG; + break; + default: /* NOTE: avoid compiler warnings */ + cmd = VMK8061_CMD_SET_AO; + reg = VMK8061_AO_REG; + dev->usb_tx_buf[VMK8061_CH_REG] = chan; + break; + } + + for (n = 0; n < insn->n; n++) { + dev->usb_tx_buf[reg] = data[n]; + + if (vmk80xx_write_packet(dev, cmd)) + break; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_ao_rinsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + int chan; + int reg; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_IN); + if (n) + return n; + + down(&dev->limit_sem); + chan = CR_CHAN(insn->chanspec); + + reg = VMK8061_AO_REG - 1; + + dev->usb_tx_buf[0] = VMK8061_CMD_RD_AO; + + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + data[n] = dev->usb_rx_buf[reg + chan]; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_di_bits(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + unsigned char *rx_buf; + int reg; + int retval; + + dbgvm("vmk80xx: %s\n", __func__); + + retval = rudimentary_check(dev, DIR_IN); + if (retval) + return retval; + + down(&dev->limit_sem); + + rx_buf = dev->usb_rx_buf; + + if (dev->board.model == VMK8061_MODEL) { + reg = VMK8061_DI_REG; + dev->usb_tx_buf[0] = VMK8061_CMD_RD_DI; + } else { + reg = VMK8055_DI_REG; + } + + retval = vmk80xx_read_packet(dev); + + if (!retval) { + if (dev->board.model == VMK8055_MODEL) + data[1] = (((rx_buf[reg] >> 4) & 0x03) | + ((rx_buf[reg] << 2) & 0x04) | + ((rx_buf[reg] >> 3) & 0x18)); + else + data[1] = rx_buf[reg]; + + retval = 2; + } + + up(&dev->limit_sem); + + return retval; +} + +static int vmk80xx_di_rinsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + int chan; + unsigned char *rx_buf; + int reg; + int inp; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_IN); + if (n) + return n; + + down(&dev->limit_sem); + chan = CR_CHAN(insn->chanspec); + + rx_buf = dev->usb_rx_buf; + + if (dev->board.model == VMK8061_MODEL) { + reg = VMK8061_DI_REG; + dev->usb_tx_buf[0] = VMK8061_CMD_RD_DI; + } else { + reg = VMK8055_DI_REG; + } + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + if (dev->board.model == VMK8055_MODEL) + inp = (((rx_buf[reg] >> 4) & 0x03) | + ((rx_buf[reg] << 2) & 0x04) | + ((rx_buf[reg] >> 3) & 0x18)); + else + inp = rx_buf[reg]; + + data[n] = (inp >> chan) & 1; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_do_winsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + int chan; + unsigned char *tx_buf; + int reg; + int cmd; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_OUT); + if (n) + return n; + + down(&dev->limit_sem); + chan = CR_CHAN(insn->chanspec); + + tx_buf = dev->usb_tx_buf; + + for (n = 0; n < insn->n; n++) { + if (dev->board.model == VMK8055_MODEL) { + reg = VMK8055_DO_REG; + cmd = VMK8055_CMD_WRT_AD; + if (data[n] == 1) + tx_buf[reg] |= (1 << chan); + else + tx_buf[reg] ^= (1 << chan); + } else { /* VMK8061_MODEL */ + reg = VMK8061_DO_REG; + if (data[n] == 1) { + cmd = VMK8061_CMD_SET_DO; + tx_buf[reg] = 1 << chan; + } else { + cmd = VMK8061_CMD_CLR_DO; + tx_buf[reg] = 0xff - (1 << chan); + } + } + + if (vmk80xx_write_packet(dev, cmd)) + break; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_do_rinsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + int chan; + int reg; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_IN); + if (n) + return n; + + down(&dev->limit_sem); + chan = CR_CHAN(insn->chanspec); + + reg = VMK8061_DO_REG; + + dev->usb_tx_buf[0] = VMK8061_CMD_RD_DO; + + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + data[n] = (dev->usb_rx_buf[reg] >> chan) & 1; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_do_bits(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + unsigned char *rx_buf, *tx_buf; + int dir, reg, cmd; + int retval; + + dbgvm("vmk80xx: %s\n", __func__); + + dir = 0; + + if (data[0]) + dir |= DIR_OUT; + + if (dev->board.model == VMK8061_MODEL) + dir |= DIR_IN; + + retval = rudimentary_check(dev, dir); + if (retval) + return retval; + + down(&dev->limit_sem); + + rx_buf = dev->usb_rx_buf; + tx_buf = dev->usb_tx_buf; + + if (data[0]) { + if (dev->board.model == VMK8055_MODEL) { + reg = VMK8055_DO_REG; + cmd = VMK8055_CMD_WRT_AD; + } else { /* VMK8061_MODEL */ + reg = VMK8061_DO_REG; + cmd = VMK8061_CMD_DO; + } + + tx_buf[reg] &= ~data[0]; + tx_buf[reg] |= (data[0] & data[1]); + + retval = vmk80xx_write_packet(dev, cmd); + + if (retval) + goto out; + } + + if (dev->board.model == VMK8061_MODEL) { + reg = VMK8061_DO_REG; + tx_buf[0] = VMK8061_CMD_RD_DO; + + retval = vmk80xx_read_packet(dev); + + if (!retval) { + data[1] = rx_buf[reg]; + retval = 2; + } + } else { + data[1] = tx_buf[reg]; + retval = 2; + } + +out: + up(&dev->limit_sem); + + return retval; +} + +static int vmk80xx_cnt_rinsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + int chan; + int reg[2]; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_IN); + if (n) + return n; + + down(&dev->limit_sem); + chan = CR_CHAN(insn->chanspec); + + switch (dev->board.model) { + case VMK8055_MODEL: + if (!chan) + reg[0] = VMK8055_CNT1_REG; + else + reg[0] = VMK8055_CNT2_REG; + break; + case VMK8061_MODEL: + reg[0] = VMK8061_CNT_REG; + reg[1] = VMK8061_CNT_REG; + dev->usb_tx_buf[0] = VMK8061_CMD_RD_CNT; + break; + } + + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + if (dev->board.model == VMK8055_MODEL) + data[n] = dev->usb_rx_buf[reg[0]]; + else /* VMK8061_MODEL */ + data[n] = dev->usb_rx_buf[reg[0] * (chan + 1) + 1] + + 256 * dev->usb_rx_buf[reg[1] * 2 + 2]; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_cnt_cinsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + unsigned int insn_cmd; + int chan; + int cmd; + int reg; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_OUT); + if (n) + return n; + + down(&dev->limit_sem); + + insn_cmd = data[0]; + if (insn_cmd != INSN_CONFIG_RESET && insn_cmd != GPCT_RESET) + return -EINVAL; + + chan = CR_CHAN(insn->chanspec); + + if (dev->board.model == VMK8055_MODEL) { + if (!chan) { + cmd = VMK8055_CMD_RST_CNT1; + reg = VMK8055_CNT1_REG; + } else { + cmd = VMK8055_CMD_RST_CNT2; + reg = VMK8055_CNT2_REG; + } + + dev->usb_tx_buf[reg] = 0x00; + } else { + cmd = VMK8061_CMD_RST_CNT; + } + + for (n = 0; n < insn->n; n++) + if (vmk80xx_write_packet(dev, cmd)) + break; + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_cnt_winsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + unsigned long debtime; + unsigned long val; + int chan; + int cmd; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_OUT); + if (n) + return n; + + down(&dev->limit_sem); + chan = CR_CHAN(insn->chanspec); + + if (!chan) + cmd = VMK8055_CMD_DEB1_TIME; + else + cmd = VMK8055_CMD_DEB2_TIME; + + for (n = 0; n < insn->n; n++) { + debtime = data[n]; + if (debtime == 0) + debtime = 1; + + /* TODO: Prevent overflows */ + if (debtime > 7450) + debtime = 7450; + + val = int_sqrt(debtime * 1000 / 115); + if (((val + 1) * val) < debtime * 1000 / 115) + val += 1; + + dev->usb_tx_buf[6 + chan] = val; + + if (vmk80xx_write_packet(dev, cmd)) + break; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_pwm_rinsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + int reg[2]; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_IN); + if (n) + return n; + + down(&dev->limit_sem); + + reg[0] = VMK8061_PWM_REG1; + reg[1] = VMK8061_PWM_REG2; + + dev->usb_tx_buf[0] = VMK8061_CMD_RD_PWM; + + for (n = 0; n < insn->n; n++) { + if (vmk80xx_read_packet(dev)) + break; + + data[n] = dev->usb_rx_buf[reg[0]] + 4 * dev->usb_rx_buf[reg[1]]; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_pwm_winsn(struct comedi_device *cdev, + struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data) +{ + struct vmk80xx_usb *dev = cdev->private; + unsigned char *tx_buf; + int reg[2]; + int cmd; + int n; + + dbgvm("vmk80xx: %s\n", __func__); + + n = rudimentary_check(dev, DIR_OUT); + if (n) + return n; + + down(&dev->limit_sem); + + tx_buf = dev->usb_tx_buf; + + reg[0] = VMK8061_PWM_REG1; + reg[1] = VMK8061_PWM_REG2; + + cmd = VMK8061_CMD_OUT_PWM; + + /* + * The followin piece of code was translated from the inline + * assembler code in the DLL source code. + * + * asm + * mov eax, k ; k is the value (data[n]) + * and al, 03h ; al are the lower 8 bits of eax + * mov lo, al ; lo is the low part (tx_buf[reg[0]]) + * mov eax, k + * shr eax, 2 ; right shift eax register by 2 + * mov hi, al ; hi is the high part (tx_buf[reg[1]]) + * end; + */ + for (n = 0; n < insn->n; n++) { + tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03); + tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff; + + if (vmk80xx_write_packet(dev, cmd)) + break; + } + + up(&dev->limit_sem); + + return n; +} + +static int vmk80xx_attach(struct comedi_device *cdev, + struct comedi_devconfig *it) +{ + int i; + struct vmk80xx_usb *dev; + int n_subd; + struct comedi_subdevice *s; + int minor; + + dbgvm("vmk80xx: %s\n", __func__); + + mutex_lock(&glb_mutex); + + for (i = 0; i < VMK80XX_MAX_BOARDS; i++) + if (vmb[i].probed && !vmb[i].attached) + break; + + if (i == VMK80XX_MAX_BOARDS) { + mutex_unlock(&glb_mutex); + return -ENODEV; + } + + dev = &vmb[i]; + + down(&dev->limit_sem); + + cdev->board_name = dev->board.name; + cdev->private = dev; + + if (dev->board.model == VMK8055_MODEL) + n_subd = 5; + else + n_subd = 6; + + if (alloc_subdevices(cdev, n_subd) < 0) { + up(&dev->limit_sem); + mutex_unlock(&glb_mutex); + return -ENOMEM; + } + + /* Analog input subdevice */ + s = cdev->subdevices + VMK80XX_SUBD_AI; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = dev->board.ai_chans; + s->maxdata = (1 << dev->board.ai_bits) - 1; + s->range_table = dev->board.range; + s->insn_read = vmk80xx_ai_rinsn; + + /* Analog output subdevice */ + s = cdev->subdevices + VMK80XX_SUBD_AO; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND; + s->n_chan = dev->board.ao_chans; + s->maxdata = (1 << dev->board.ao_bits) - 1; + s->range_table = dev->board.range; + s->insn_write = vmk80xx_ao_winsn; + + if (dev->board.model == VMK8061_MODEL) { + s->subdev_flags |= SDF_READABLE; + s->insn_read = vmk80xx_ao_rinsn; + } + + /* Digital input subdevice */ + s = cdev->subdevices + VMK80XX_SUBD_DI; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = dev->board.di_chans; + s->maxdata = 1; + s->insn_read = vmk80xx_di_rinsn; + s->insn_bits = vmk80xx_di_bits; + + /* Digital output subdevice */ + s = cdev->subdevices + VMK80XX_SUBD_DO; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND; + s->n_chan = dev->board.do_chans; + s->maxdata = 1; + s->insn_write = vmk80xx_do_winsn; + s->insn_bits = vmk80xx_do_bits; + + if (dev->board.model == VMK8061_MODEL) { + s->subdev_flags |= SDF_READABLE; + s->insn_read = vmk80xx_do_rinsn; + } + + /* Counter subdevice */ + s = cdev->subdevices + VMK80XX_SUBD_CNT; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE; + s->n_chan = dev->board.cnt_chans; + s->insn_read = vmk80xx_cnt_rinsn; + s->insn_config = vmk80xx_cnt_cinsn; + + if (dev->board.model == VMK8055_MODEL) { + s->subdev_flags |= SDF_WRITEABLE; + s->maxdata = (1 << dev->board.cnt_bits) - 1; + s->insn_write = vmk80xx_cnt_winsn; + } + + /* PWM subdevice */ + if (dev->board.model == VMK8061_MODEL) { + s = cdev->subdevices + VMK80XX_SUBD_PWM; + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = SDF_READABLE | SDF_WRITEABLE; + s->n_chan = dev->board.pwm_chans; + s->maxdata = (1 << dev->board.pwm_bits) - 1; + s->insn_read = vmk80xx_pwm_rinsn; + s->insn_write = vmk80xx_pwm_winsn; + } + + dev->attached = 1; + + minor = cdev->minor; + + printk(KERN_INFO + "comedi%d: vmk80xx: board #%d [%s] attached to comedi\n", + minor, dev->count, dev->board.name); + + up(&dev->limit_sem); + mutex_unlock(&glb_mutex); + + return 0; +} + +static int vmk80xx_detach(struct comedi_device *cdev) +{ + struct vmk80xx_usb *dev; + int minor; + + dbgvm("vmk80xx: %s\n", __func__); + + if (!cdev) + return -EFAULT; + + dev = cdev->private; + if (!dev) + return -EFAULT; + + down(&dev->limit_sem); + + cdev->private = NULL; + dev->attached = 0; + + minor = cdev->minor; + + printk(KERN_INFO + "comedi%d: vmk80xx: board #%d [%s] detached from comedi\n", + minor, dev->count, dev->board.name); + + up(&dev->limit_sem); + + return 0; +} + +static int vmk80xx_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int i; + struct vmk80xx_usb *dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *ep_desc; + size_t size; + + dbgvm("vmk80xx: %s\n", __func__); + + mutex_lock(&glb_mutex); + + for (i = 0; i < VMK80XX_MAX_BOARDS; i++) + if (!vmb[i].probed) + break; + + if (i == VMK80XX_MAX_BOARDS) { + mutex_unlock(&glb_mutex); + return -EMFILE; + } + + dev = &vmb[i]; + + memset(dev, 0x00, sizeof(struct vmk80xx_usb)); + dev->count = i; + + iface_desc = intf->cur_altsetting; + if (iface_desc->desc.bNumEndpoints != 2) + goto error; + + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + ep_desc = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(ep_desc)) { + dev->ep_rx = ep_desc; + continue; + } + + if (usb_endpoint_is_int_out(ep_desc)) { + dev->ep_tx = ep_desc; + continue; + } + + if (usb_endpoint_is_bulk_in(ep_desc)) { + dev->ep_rx = ep_desc; + continue; + } + + if (usb_endpoint_is_bulk_out(ep_desc)) { + dev->ep_tx = ep_desc; + continue; + } + } + + if (!dev->ep_rx || !dev->ep_tx) + goto error; + + size = le16_to_cpu(dev->ep_rx->wMaxPacketSize); + dev->usb_rx_buf = kmalloc(size, GFP_KERNEL); + if (!dev->usb_rx_buf) { + mutex_unlock(&glb_mutex); + return -ENOMEM; + } + + size = le16_to_cpu(dev->ep_tx->wMaxPacketSize); + dev->usb_tx_buf = kmalloc(size, GFP_KERNEL); + if (!dev->usb_tx_buf) { + kfree(dev->usb_rx_buf); + mutex_unlock(&glb_mutex); + return -ENOMEM; + } + + dev->udev = interface_to_usbdev(intf); + dev->intf = intf; + + sema_init(&dev->limit_sem, 8); + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + init_usb_anchor(&dev->rx_anchor); + init_usb_anchor(&dev->tx_anchor); + + usb_set_intfdata(intf, dev); + + switch (id->driver_info) { + case DEVICE_VMK8055: + dev->board.name = "K8055 (VM110)"; + dev->board.model = VMK8055_MODEL; + dev->board.range = &vmk8055_range; + dev->board.ai_chans = 2; + dev->board.ai_bits = 8; + dev->board.ao_chans = 2; + dev->board.ao_bits = 8; + dev->board.di_chans = 5; + dev->board.di_bits = 1; + dev->board.do_chans = 8; + dev->board.do_bits = 1; + dev->board.cnt_chans = 2; + dev->board.cnt_bits = 16; + dev->board.pwm_chans = 0; + dev->board.pwm_bits = 0; + break; + case DEVICE_VMK8061: + dev->board.name = "K8061 (VM140)"; + dev->board.model = VMK8061_MODEL; + dev->board.range = &vmk8061_range; + dev->board.ai_chans = 8; + dev->board.ai_bits = 10; + dev->board.ao_chans = 8; + dev->board.ao_bits = 8; + dev->board.di_chans = 8; + dev->board.di_bits = 1; + dev->board.do_chans = 8; + dev->board.do_bits = 1; + dev->board.cnt_chans = 2; + dev->board.cnt_bits = 0; + dev->board.pwm_chans = 1; + dev->board.pwm_bits = 10; + break; + } + + if (dev->board.model == VMK8061_MODEL) { + vmk80xx_read_eeprom(dev, IC3_VERSION); + printk(KERN_INFO "comedi#: vmk80xx: %s\n", dev->fw.ic3_vers); + + if (vmk80xx_check_data_link(dev)) { + vmk80xx_read_eeprom(dev, IC6_VERSION); + printk(KERN_INFO "comedi#: vmk80xx: %s\n", + dev->fw.ic6_vers); + } else { + dbgcm("comedi#: vmk80xx: no conn. to CPU\n"); + } + } + + if (dev->board.model == VMK8055_MODEL) + vmk80xx_reset_device(dev); + + dev->probed = 1; + + printk(KERN_INFO "comedi#: vmk80xx: board #%d [%s] now attached\n", + dev->count, dev->board.name); + + mutex_unlock(&glb_mutex); + + comedi_usb_auto_config(dev->udev, BOARDNAME); + + return 0; +error: + mutex_unlock(&glb_mutex); + + return -ENODEV; +} + +static void vmk80xx_disconnect(struct usb_interface *intf) +{ + struct vmk80xx_usb *dev = usb_get_intfdata(intf); + + dbgvm("vmk80xx: %s\n", __func__); + + if (!dev) + return; + + comedi_usb_auto_unconfig(dev->udev); + + mutex_lock(&glb_mutex); + down(&dev->limit_sem); + + dev->probed = 0; + usb_set_intfdata(dev->intf, NULL); + + usb_kill_anchored_urbs(&dev->rx_anchor); + usb_kill_anchored_urbs(&dev->tx_anchor); + + kfree(dev->usb_rx_buf); + kfree(dev->usb_tx_buf); + + printk(KERN_INFO "comedi#: vmk80xx: board #%d [%s] now detached\n", + dev->count, dev->board.name); + + up(&dev->limit_sem); + mutex_unlock(&glb_mutex); +} + +/* TODO: Add support for suspend, resume, pre_reset, + * post_reset and flush */ +static struct usb_driver vmk80xx_driver = { + .name = "vmk80xx", + .probe = vmk80xx_probe, + .disconnect = vmk80xx_disconnect, + .id_table = vmk80xx_id_table +}; + +static struct comedi_driver driver_vmk80xx = { + .module = THIS_MODULE, + .driver_name = "vmk80xx", + .attach = vmk80xx_attach, + .detach = vmk80xx_detach +}; + +static int __init vmk80xx_init(void) +{ + int retval; + + printk(KERN_INFO "vmk80xx: version 0.8.01 " + "Manuel Gebele \n"); + + retval = comedi_driver_register(&driver_vmk80xx); + if (retval < 0) + return retval; + + return usb_register(&vmk80xx_driver); +} + +static void __exit vmk80xx_exit(void) +{ + comedi_driver_unregister(&driver_vmk80xx); + usb_deregister(&vmk80xx_driver); +} + +module_init(vmk80xx_init); +module_exit(vmk80xx_exit); diff --git a/drivers/staging/comedi/internal.h b/drivers/staging/comedi/internal.h new file mode 100644 index 00000000..434ce343 --- /dev/null +++ b/drivers/staging/comedi/internal.h @@ -0,0 +1,12 @@ +/* + * various internal comedi functions + */ +int do_rangeinfo_ioctl(struct comedi_device *dev, + struct comedi_rangeinfo __user *arg); +int insn_inval(struct comedi_device *dev, struct comedi_subdevice *s, + struct comedi_insn *insn, unsigned int *data); +int comedi_alloc_board_minor(struct device *hardware_device); +void comedi_free_board_minor(unsigned minor); +void comedi_reset_async_buf(struct comedi_async *async); +int comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s, + unsigned long new_size); diff --git a/drivers/staging/comedi/kcomedilib/Makefile b/drivers/staging/comedi/kcomedilib/Makefile new file mode 100644 index 00000000..18ee99bd --- /dev/null +++ b/drivers/staging/comedi/kcomedilib/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_COMEDI_KCOMEDILIB) += kcomedilib.o + +kcomedilib-objs := kcomedilib_main.o diff --git a/drivers/staging/comedi/kcomedilib/kcomedilib_main.c b/drivers/staging/comedi/kcomedilib/kcomedilib_main.c new file mode 100644 index 00000000..0252b440 --- /dev/null +++ b/drivers/staging/comedi/kcomedilib/kcomedilib_main.c @@ -0,0 +1,195 @@ +/* + kcomedilib/kcomedilib.c + a comedlib interface for kernel modules + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#define __NO_VERSION__ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../comedi.h" +#include "../comedilib.h" +#include "../comedidev.h" + +MODULE_AUTHOR("David Schleef "); +MODULE_DESCRIPTION("Comedi kernel library"); +MODULE_LICENSE("GPL"); + +struct comedi_device *comedi_open(const char *filename) +{ + struct comedi_device_file_info *dev_file_info; + struct comedi_device *dev; + unsigned int minor; + + if (strncmp(filename, "/dev/comedi", 11) != 0) + return NULL; + + minor = simple_strtoul(filename + 11, NULL, 0); + + if (minor >= COMEDI_NUM_BOARD_MINORS) + return NULL; + + dev_file_info = comedi_get_device_file_info(minor); + if (dev_file_info == NULL) + return NULL; + dev = dev_file_info->device; + + if (dev == NULL || !dev->attached) + return NULL; + + if (!try_module_get(dev->driver->module)) + return NULL; + + return dev; +} +EXPORT_SYMBOL(comedi_open); + +int comedi_close(struct comedi_device *d) +{ + struct comedi_device *dev = (struct comedi_device *)d; + + module_put(dev->driver->module); + + return 0; +} +EXPORT_SYMBOL(comedi_close); + +static int comedi_do_insn(struct comedi_device *dev, struct comedi_insn *insn) +{ + struct comedi_subdevice *s; + int ret = 0; + + /* a subdevice instruction */ + if (insn->subdev >= dev->n_subdevices) { + ret = -EINVAL; + goto error; + } + s = dev->subdevices + insn->subdev; + + if (s->type == COMEDI_SUBD_UNUSED) { + printk(KERN_ERR "%d not useable subdevice\n", insn->subdev); + ret = -EIO; + goto error; + } + + /* XXX check lock */ + + ret = comedi_check_chanlist(s, 1, &insn->chanspec); + if (ret < 0) { + printk(KERN_ERR "bad chanspec\n"); + ret = -EINVAL; + goto error; + } + + if (s->busy) { + ret = -EBUSY; + goto error; + } + s->busy = dev; + + switch (insn->insn) { + case INSN_BITS: + ret = s->insn_bits(dev, s, insn, insn->data); + break; + case INSN_CONFIG: + /* XXX should check instruction length */ + ret = s->insn_config(dev, s, insn, insn->data); + break; + default: + ret = -EINVAL; + break; + } + + s->busy = NULL; +error: + + return ret; +} + +int comedi_dio_config(struct comedi_device *dev, unsigned int subdev, + unsigned int chan, unsigned int io) +{ + struct comedi_insn insn; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_CONFIG; + insn.n = 1; + insn.data = &io; + insn.subdev = subdev; + insn.chanspec = CR_PACK(chan, 0, 0); + + return comedi_do_insn(dev, &insn); +} +EXPORT_SYMBOL(comedi_dio_config); + +int comedi_dio_bitfield(struct comedi_device *dev, unsigned int subdev, + unsigned int mask, unsigned int *bits) +{ + struct comedi_insn insn; + unsigned int data[2]; + int ret; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_BITS; + insn.n = 2; + insn.data = data; + insn.subdev = subdev; + + data[0] = mask; + data[1] = *bits; + + ret = comedi_do_insn(dev, &insn); + + *bits = data[1]; + + return ret; +} +EXPORT_SYMBOL(comedi_dio_bitfield); + +int comedi_find_subdevice_by_type(struct comedi_device *dev, int type, + unsigned int subd) +{ + if (subd > dev->n_subdevices) + return -ENODEV; + + for (; subd < dev->n_subdevices; subd++) { + if (dev->subdevices[subd].type == type) + return subd; + } + return -1; +} +EXPORT_SYMBOL(comedi_find_subdevice_by_type); + +int comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice) +{ + struct comedi_subdevice *s = dev->subdevices + subdevice; + + return s->n_chan; +} +EXPORT_SYMBOL(comedi_get_n_channels); diff --git a/drivers/staging/comedi/proc.c b/drivers/staging/comedi/proc.c new file mode 100644 index 00000000..2aa487b6 --- /dev/null +++ b/drivers/staging/comedi/proc.c @@ -0,0 +1,99 @@ +/* + module/proc.c + /proc interface for comedi + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* + This is some serious bloatware. + + Taken from Dave A.'s PCL-711 driver, 'cuz I thought it + was cool. +*/ + +#define __NO_VERSION__ +#include "comedidev.h" +#include "comedi_fops.h" +#include +#include + +#ifdef CONFIG_PROC_FS +static int comedi_read(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + int i; + int devices_q = 0; + int l = 0; + struct comedi_driver *driv; + + l += sprintf(buf + l, + "comedi version " COMEDI_RELEASE "\n" + "format string: %s\n", + "\"%2d: %-20s %-20s %4d\", i, " + "driver_name, board_name, n_subdevices"); + + for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) { + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(i); + struct comedi_device *dev; + + if (dev_file_info == NULL) + continue; + dev = dev_file_info->device; + + if (dev->attached) { + devices_q = 1; + l += sprintf(buf + l, "%2d: %-20s %-20s %4d\n", + i, + dev->driver->driver_name, + dev->board_name, dev->n_subdevices); + } + } + if (!devices_q) + l += sprintf(buf + l, "no devices\n"); + + for (driv = comedi_drivers; driv; driv = driv->next) { + l += sprintf(buf + l, "%s:\n", driv->driver_name); + for (i = 0; i < driv->num_names; i++) { + l += sprintf(buf + l, " %s\n", + *(char **)((char *)driv->board_name + + i * driv->offset)); + } + if (!driv->num_names) + l += sprintf(buf + l, " %s\n", driv->driver_name); + } + + return l; +} + +void comedi_proc_init(void) +{ + struct proc_dir_entry *comedi_proc; + + comedi_proc = create_proc_entry("comedi", S_IFREG | S_IRUGO, NULL); + if (comedi_proc) + comedi_proc->read_proc = comedi_read; +} + +void comedi_proc_cleanup(void) +{ + remove_proc_entry("comedi", NULL); +} +#endif diff --git a/drivers/staging/comedi/range.c b/drivers/staging/comedi/range.c new file mode 100644 index 00000000..148ec6fd --- /dev/null +++ b/drivers/staging/comedi/range.c @@ -0,0 +1,166 @@ +/* + module/range.c + comedi routines for voltage ranges + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-8 David A. Schleef + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include "comedidev.h" +#include "internal.h" + +const struct comedi_lrange range_bipolar10 = { 1, {BIP_RANGE(10)} }; +EXPORT_SYMBOL(range_bipolar10); +const struct comedi_lrange range_bipolar5 = { 1, {BIP_RANGE(5)} }; +EXPORT_SYMBOL(range_bipolar5); +const struct comedi_lrange range_bipolar2_5 = { 1, {BIP_RANGE(2.5)} }; +EXPORT_SYMBOL(range_bipolar2_5); +const struct comedi_lrange range_unipolar10 = { 1, {UNI_RANGE(10)} }; +EXPORT_SYMBOL(range_unipolar10); +const struct comedi_lrange range_unipolar5 = { 1, {UNI_RANGE(5)} }; +EXPORT_SYMBOL(range_unipolar5); +const struct comedi_lrange range_unknown = { 1, {{0, 1000000, UNIT_none} } }; +EXPORT_SYMBOL(range_unknown); + +/* + COMEDI_RANGEINFO + range information ioctl + + arg: + pointer to rangeinfo structure + + reads: + range info structure + + writes: + n struct comedi_krange structures to rangeinfo->range_ptr +*/ +int do_rangeinfo_ioctl(struct comedi_device *dev, + struct comedi_rangeinfo __user *arg) +{ + struct comedi_rangeinfo it; + int subd, chan; + const struct comedi_lrange *lr; + struct comedi_subdevice *s; + + if (copy_from_user(&it, arg, sizeof(struct comedi_rangeinfo))) + return -EFAULT; + subd = (it.range_type >> 24) & 0xf; + chan = (it.range_type >> 16) & 0xff; + + if (!dev->attached) + return -EINVAL; + if (subd >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + subd; + if (s->range_table) { + lr = s->range_table; + } else if (s->range_table_list) { + if (chan >= s->n_chan) + return -EINVAL; + lr = s->range_table_list[chan]; + } else { + return -EINVAL; + } + + if (RANGE_LENGTH(it.range_type) != lr->length) { + DPRINTK("wrong length %d should be %d (0x%08x)\n", + RANGE_LENGTH(it.range_type), lr->length, it.range_type); + return -EINVAL; + } + + if (copy_to_user(it.range_ptr, lr->range, + sizeof(struct comedi_krange) * lr->length)) + return -EFAULT; + + return 0; +} + +static int aref_invalid(struct comedi_subdevice *s, unsigned int chanspec) +{ + unsigned int aref; + + /* disable reporting invalid arefs... maybe someday */ + return 0; + + aref = CR_AREF(chanspec); + switch (aref) { + case AREF_DIFF: + if (s->subdev_flags & SDF_DIFF) + return 0; + break; + case AREF_COMMON: + if (s->subdev_flags & SDF_COMMON) + return 0; + break; + case AREF_GROUND: + if (s->subdev_flags & SDF_GROUND) + return 0; + break; + case AREF_OTHER: + if (s->subdev_flags & SDF_OTHER) + return 0; + break; + default: + break; + } + DPRINTK("subdevice does not support aref %i", aref); + return 1; +} + +/* + This function checks each element in a channel/gain list to make + make sure it is valid. +*/ +int comedi_check_chanlist(struct comedi_subdevice *s, int n, + unsigned int *chanlist) +{ + int i; + int chan; + + if (s->range_table) { + for (i = 0; i < n; i++) + if (CR_CHAN(chanlist[i]) >= s->n_chan || + CR_RANGE(chanlist[i]) >= s->range_table->length + || aref_invalid(s, chanlist[i])) { + printk(KERN_ERR "bad chanlist[%d]=0x%08x " + "in_chan=%d range length=%d\n", i, + chanlist[i], s->n_chan, + s->range_table->length); + return -EINVAL; + } + } else if (s->range_table_list) { + for (i = 0; i < n; i++) { + chan = CR_CHAN(chanlist[i]); + if (chan >= s->n_chan || + CR_RANGE(chanlist[i]) >= + s->range_table_list[chan]->length + || aref_invalid(s, chanlist[i])) { + printk(KERN_ERR "bad chanlist[%d]=0x%08x\n", + i, chanlist[i]); + return -EINVAL; + } + } + } else { + printk(KERN_ERR "comedi: (bug) no range type list!\n"); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL(comedi_check_chanlist); diff --git a/drivers/staging/cptm1217/Kconfig b/drivers/staging/cptm1217/Kconfig new file mode 100644 index 00000000..43b1cc0a --- /dev/null +++ b/drivers/staging/cptm1217/Kconfig @@ -0,0 +1,12 @@ +config TOUCHSCREEN_CLEARPAD_TM1217 + tristate "Synaptics Clearpad TM1217" + depends on I2C + depends on GPIOLIB + depends on INPUT + help + Say Y here if you have a Synaptics Clearpad TM1217 Controller + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called clearpad_tm1217. diff --git a/drivers/staging/cptm1217/Makefile b/drivers/staging/cptm1217/Makefile new file mode 100644 index 00000000..8961fafa --- /dev/null +++ b/drivers/staging/cptm1217/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_TOUCHSCREEN_CLEARPAD_TM1217) += clearpad_tm1217.o + diff --git a/drivers/staging/cptm1217/TODO b/drivers/staging/cptm1217/TODO new file mode 100644 index 00000000..30392246 --- /dev/null +++ b/drivers/staging/cptm1217/TODO @@ -0,0 +1,5 @@ +- Wait for the official upstream general clearpad drivers as promised over + the past few months +- Merge any device support needed from this driver into it +- Delete this driver + diff --git a/drivers/staging/cptm1217/clearpad_tm1217.c b/drivers/staging/cptm1217/clearpad_tm1217.c new file mode 100644 index 00000000..5456f82c --- /dev/null +++ b/drivers/staging/cptm1217/clearpad_tm1217.c @@ -0,0 +1,676 @@ +/* + * clearpad_tm1217.c - Touch Screen driver for Synaptics Clearpad + * TM1217 controller + * + * Copyright (C) 2008 Intel Corp + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; ifnot, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * Questions/Comments/Bug fixes to Ramesh Agarwal (ramesh.agarwal@intel.com) + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cp_tm1217.h" + +#define CPTM1217_DEVICE_NAME "cptm1217" +#define CPTM1217_DRIVER_NAME CPTM1217_DEVICE_NAME + +#define MAX_TOUCH_SUPPORTED 2 +#define TOUCH_SUPPORTED 1 +#define SAMPLING_FREQ 80 /* Frequency in HZ */ +#define DELAY_BTWIN_SAMPLE (1000 / SAMPLING_FREQ) +#define WAIT_FOR_RESPONSE 5 /* 5msec just works */ +#define MAX_RETRIES 5 /* As above */ +#define INCREMENTAL_DELAY 5 /* As above */ + +/* Regster Definitions */ +#define TMA1217_DEV_STATUS 0x13 /* Device Status */ +#define TMA1217_INT_STATUS 0x14 /* Interrupt Status */ + +/* Controller can detect up to 2 possible finger touches. + * Each finger touch provides 12 bit X Y co-ordinates, the values are split + * across 2 registers, and an 8 bit Z value */ +#define TMA1217_FINGER_STATE 0x18 /* Finger State */ +#define TMA1217_FINGER1_X_HIGHER8 0x19 /* Higher 8 bit of X coordinate */ +#define TMA1217_FINGER1_Y_HIGHER8 0x1A /* Higher 8 bit of Y coordinate */ +#define TMA1217_FINGER1_XY_LOWER4 0x1B /* Lower 4 bits of X and Y */ +#define TMA1217_FINGER1_Z_VALUE 0x1D /* 8 bit Z value for finger 1 */ +#define TMA1217_FINGER2_X_HIGHER8 0x1E /* Higher 8 bit of X coordinate */ +#define TMA1217_FINGER2_Y_HIGHER8 0x1F /* Higher 8 bit of Y coordinate */ +#define TMA1217_FINGER2_XY_LOWER4 0x20 /* Lower 4 bits of X and Y */ +#define TMA1217_FINGER2_Z_VALUE 0x22 /* 8 bit Z value for finger 2 */ +#define TMA1217_DEVICE_CTRL 0x23 /* Device Control */ +#define TMA1217_INTERRUPT_ENABLE 0x24 /* Interrupt Enable */ +#define TMA1217_REPORT_MODE 0x2B /* Reporting Mode */ +#define TMA1217_MAX_X_LOWER8 0x31 /* Bit 0-7 for Max X */ +#define TMA1217_MAX_X_HIGHER4 0x32 /* Bit 8-11 for Max X */ +#define TMA1217_MAX_Y_LOWER8 0x33 /* Bit 0-7 for Max Y */ +#define TMA1217_MAX_Y_HIGHER4 0x34 /* Bit 8-11 for Max Y */ +#define TMA1217_DEVICE_CMD_RESET 0x67 /* Device CMD reg for reset */ +#define TMA1217_DEVICE_CMD_REZERO 0x69 /* Device CMD reg for rezero */ + +#define TMA1217_MANUFACTURER_ID 0x73 /* Manufacturer Id */ +#define TMA1217_PRODUCT_FAMILY 0x75 /* Product Family */ +#define TMA1217_FIRMWARE_REVISION 0x76 /* Firmware Revision */ +#define TMA1217_SERIAL_NO_HIGH 0x7C /* Bit 8-15 of device serial no. */ +#define TMA1217_SERIAL_NO_LOW 0x7D /* Bit 0-7 of device serial no. */ +#define TMA1217_PRODUCT_ID_START 0x7E /* Start address for 10 byte ID */ +#define TMA1217_DEVICE_CAPABILITY 0x8B /* Reporting capability */ + + +/* + * The touch position structure. + */ +struct touch_state { + int x; + int y; + bool button; +}; + +/* Device Specific info given by the controller */ +struct cp_dev_info { + u16 maxX; + u16 maxY; +}; + +/* Vendor related info given by the controller */ +struct cp_vendor_info { + u8 vendor_id; + u8 product_family; + u8 firmware_rev; + u16 serial_no; +}; + +/* + * Private structure to store the device details + */ +struct cp_tm1217_device { + struct i2c_client *client; + struct device *dev; + struct cp_vendor_info vinfo; + struct cp_dev_info dinfo; + struct input_dev_info { + char phys[32]; + char name[128]; + struct input_dev *input; + struct touch_state touch; + } cp_input_info[MAX_TOUCH_SUPPORTED]; + + int thread_running; + struct mutex thread_mutex; + + int gpio; +}; + + +/* The following functions are used to read/write registers on the device + * as per the RMI prorocol. Technically, a page select should be written + * before doing read/write but since the register offsets are below 0xFF + * we can use the default value of page which is 0x00 + */ +static int cp_tm1217_read(struct cp_tm1217_device *ts, + u8 *req, int size) +{ + int i, retval; + + /* Send the address */ + retval = i2c_master_send(ts->client, &req[0], 1); + if (retval != 1) { + dev_err(ts->dev, "cp_tm1217: I2C send failed\n"); + return retval; + } + msleep(WAIT_FOR_RESPONSE); + for (i = 0; i < MAX_RETRIES; i++) { + retval = i2c_master_recv(ts->client, &req[1], size); + if (retval == size) { + break; + } else { + msleep(INCREMENTAL_DELAY); + dev_dbg(ts->dev, "cp_tm1217: Retry count is %d\n", i); + } + } + if (retval != size) + dev_err(ts->dev, "cp_tm1217: Read from device failed\n"); + + return retval; +} + +static int cp_tm1217_write(struct cp_tm1217_device *ts, + u8 *req, int size) +{ + int retval; + + /* Send the address and the data to be written */ + retval = i2c_master_send(ts->client, &req[0], size + 1); + if (retval != size + 1) { + dev_err(ts->dev, "cp_tm1217: I2C write failed: %d\n", retval); + return retval; + } + /* Wait for the write to complete. TBD why this is required */ + msleep(WAIT_FOR_RESPONSE); + + return size; +} + +static int cp_tm1217_mask_interrupt(struct cp_tm1217_device *ts) +{ + u8 req[2]; + int retval; + + req[0] = TMA1217_INTERRUPT_ENABLE; + req[1] = 0x0; + retval = cp_tm1217_write(ts, req, 1); + if (retval != 1) + return -EIO; + + return 0; +} + +static int cp_tm1217_unmask_interrupt(struct cp_tm1217_device *ts) +{ + u8 req[2]; + int retval; + + req[0] = TMA1217_INTERRUPT_ENABLE; + req[1] = 0xa; + retval = cp_tm1217_write(ts, req, 1); + if (retval != 1) + return -EIO; + + return 0; +} + +static void process_touch(struct cp_tm1217_device *ts, int index) +{ + int retval; + struct input_dev_info *input_info = + (struct input_dev_info *)&ts->cp_input_info[index]; + u8 xy_data[6]; + + if (index == 0) + xy_data[0] = TMA1217_FINGER1_X_HIGHER8; + else + xy_data[0] = TMA1217_FINGER2_X_HIGHER8; + + retval = cp_tm1217_read(ts, xy_data, 5); + if (retval < 5) { + dev_err(ts->dev, "cp_tm1217: XY read from device failed\n"); + return; + } + + /* Note: Currently not using the Z values but may be requried in + the future. */ + input_info->touch.x = (xy_data[1] << 4) + | (xy_data[3] & 0x0F); + input_info->touch.y = (xy_data[2] << 4) + | ((xy_data[3] & 0xF0) >> 4); + input_report_abs(input_info->input, ABS_X, input_info->touch.x); + input_report_abs(input_info->input, ABS_Y, input_info->touch.y); + input_sync(input_info->input); +} + +static void cp_tm1217_get_data(struct cp_tm1217_device *ts) +{ + u8 req[2]; + int retval, i, finger_touched = 0; + + do { + req[0] = TMA1217_FINGER_STATE; + retval = cp_tm1217_read(ts, req, 1); + if (retval != 1) { + dev_err(ts->dev, + "cp_tm1217: Read from device failed\n"); + continue; + } + finger_touched = 0; + /* Start sampling until the pressure is below + threshold */ + for (i = 0; i < TOUCH_SUPPORTED; i++) { + if (req[1] & 0x3) { + finger_touched++; + if (ts->cp_input_info[i].touch.button == 0) { + /* send the button touch event */ + input_report_key( + ts->cp_input_info[i].input, + BTN_TOUCH, 1); + ts->cp_input_info[i].touch.button = 1; + } + process_touch(ts, i); + } else { + if (ts->cp_input_info[i].touch.button == 1) { + /* send the button release event */ + input_report_key( + ts->cp_input_info[i].input, + BTN_TOUCH, 0); + input_sync(ts->cp_input_info[i].input); + ts->cp_input_info[i].touch.button = 0; + } + } + req[1] = req[1] >> 2; + } + msleep(DELAY_BTWIN_SAMPLE); + } while (finger_touched > 0); +} + +static irqreturn_t cp_tm1217_sample_thread(int irq, void *handle) +{ + struct cp_tm1217_device *ts = (struct cp_tm1217_device *) handle; + u8 req[2]; + int retval; + + /* Chedk if another thread is already running */ + mutex_lock(&ts->thread_mutex); + if (ts->thread_running == 1) { + mutex_unlock(&ts->thread_mutex); + return IRQ_HANDLED; + } else { + ts->thread_running = 1; + mutex_unlock(&ts->thread_mutex); + } + + /* Mask the interrupts */ + retval = cp_tm1217_mask_interrupt(ts); + + /* Read the Interrupt Status register to find the cause of the + Interrupt */ + req[0] = TMA1217_INT_STATUS; + retval = cp_tm1217_read(ts, req, 1); + if (retval != 1) + goto exit_thread; + + if (!(req[1] & 0x8)) + goto exit_thread; + + cp_tm1217_get_data(ts); + +exit_thread: + /* Unmask the interrupts before going to sleep */ + retval = cp_tm1217_unmask_interrupt(ts); + + mutex_lock(&ts->thread_mutex); + ts->thread_running = 0; + mutex_unlock(&ts->thread_mutex); + + return IRQ_HANDLED; +} + +static int cp_tm1217_init_data(struct cp_tm1217_device *ts) +{ + int retval; + u8 req[2]; + + /* Read the vendor id/ fw revision etc. Ignoring return check as this + is non critical info */ + req[0] = TMA1217_MANUFACTURER_ID; + retval = cp_tm1217_read(ts, req, 1); + ts->vinfo.vendor_id = req[1]; + + req[0] = TMA1217_PRODUCT_FAMILY; + retval = cp_tm1217_read(ts, req, 1); + ts->vinfo.product_family = req[1]; + + req[0] = TMA1217_FIRMWARE_REVISION; + retval = cp_tm1217_read(ts, req, 1); + ts->vinfo.firmware_rev = req[1]; + + req[0] = TMA1217_SERIAL_NO_HIGH; + retval = cp_tm1217_read(ts, req, 1); + ts->vinfo.serial_no = (req[1] << 8); + + req[0] = TMA1217_SERIAL_NO_LOW; + retval = cp_tm1217_read(ts, req, 1); + ts->vinfo.serial_no = ts->vinfo.serial_no | req[1]; + + req[0] = TMA1217_MAX_X_HIGHER4; + retval = cp_tm1217_read(ts, req, 1); + ts->dinfo.maxX = (req[1] & 0xF) << 8; + + req[0] = TMA1217_MAX_X_LOWER8; + retval = cp_tm1217_read(ts, req, 1); + ts->dinfo.maxX = ts->dinfo.maxX | req[1]; + + req[0] = TMA1217_MAX_Y_HIGHER4; + retval = cp_tm1217_read(ts, req, 1); + ts->dinfo.maxY = (req[1] & 0xF) << 8; + + req[0] = TMA1217_MAX_Y_LOWER8; + retval = cp_tm1217_read(ts, req, 1); + ts->dinfo.maxY = ts->dinfo.maxY | req[1]; + + return 0; + +} + +/* + * Set up a GPIO for use as the interrupt. We can't simply do this at + * boot time because the GPIO drivers themselves may not be around at + * boot/firmware set up time to do the work. Instead defer it to driver + * detection. + */ + +static int cp_tm1217_setup_gpio_irq(struct cp_tm1217_device *ts) +{ + int retval; + + /* Hook up the irq handler */ + retval = gpio_request(ts->gpio, "cp_tm1217_touch"); + if (retval < 0) { + dev_err(ts->dev, "cp_tm1217: GPIO request failed error %d\n", + retval); + return retval; + } + + retval = gpio_direction_input(ts->gpio); + if (retval < 0) { + dev_err(ts->dev, + "cp_tm1217: GPIO direction configuration failed, error %d\n", + retval); + gpio_free(ts->gpio); + return retval; + } + + retval = gpio_to_irq(ts->gpio); + if (retval < 0) { + dev_err(ts->dev, "cp_tm1217: GPIO to IRQ failedi," + " error %d\n", retval); + gpio_free(ts->gpio); + } + dev_dbg(ts->dev, + "cp_tm1217: Got IRQ number is %d for GPIO %d\n", + retval, ts->gpio); + return retval; +} + +static int cp_tm1217_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cp_tm1217_device *ts; + struct input_dev *input_dev; + struct input_dev_info *input_info; + struct cp_tm1217_platform_data *pdata; + u8 req[2]; + int i, retval; + + /* No pdata is fine - we then use "normal" IRQ mode */ + + pdata = client->dev.platform_data; + + ts = kzalloc(sizeof(struct cp_tm1217_device), GFP_KERNEL); + if (!ts) { + dev_err(&client->dev, + "cp_tm1217: Private Device Struct alloc failed\n"); + return -ENOMEM; + } + + ts->client = client; + ts->dev = &client->dev; + i2c_set_clientdata(client, ts); + + ts->thread_running = 0; + mutex_init(&ts->thread_mutex); + + /* Reset the Controller */ + req[0] = TMA1217_DEVICE_CMD_RESET; + req[1] = 0x1; + retval = cp_tm1217_write(ts, req, 1); + if (retval != 1) { + dev_err(ts->dev, "cp_tm1217: Controller reset failed\n"); + kfree(ts); + return -EIO; + } + + /* Clear up the interrupt status from reset. */ + req[0] = TMA1217_INT_STATUS; + retval = cp_tm1217_read(ts, req, 1); + + /* Mask all the interrupts */ + retval = cp_tm1217_mask_interrupt(ts); + + /* Read the controller information */ + cp_tm1217_init_data(ts); + + /* The following code will register multiple event devices when + multi-pointer is enabled, the code has not been tested + with MPX */ + for (i = 0; i < TOUCH_SUPPORTED; i++) { + input_dev = input_allocate_device(); + if (input_dev == NULL) { + dev_err(ts->dev, + "cp_tm1217:Input Device Struct alloc failed\n"); + retval = -ENOMEM; + goto fail; + } + input_info = &ts->cp_input_info[i]; + snprintf(input_info->name, sizeof(input_info->name), + "cp_tm1217_touchscreen_%d", i); + input_dev->name = input_info->name; + snprintf(input_info->phys, sizeof(input_info->phys), + "%s/input%d", dev_name(&client->dev), i); + + input_dev->phys = input_info->phys; + input_dev->id.bustype = BUS_I2C; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(input_dev, ABS_X, 0, ts->dinfo.maxX, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, ts->dinfo.maxY, 0, 0); + + retval = input_register_device(input_dev); + if (retval) { + dev_err(ts->dev, + "Input dev registration failed for %s\n", + input_dev->name); + input_free_device(input_dev); + goto fail; + } + input_info->input = input_dev; + } + + /* Setup the reporting mode to send an interrupt only when + finger arrives or departs. */ + req[0] = TMA1217_REPORT_MODE; + req[1] = 0x02; + retval = cp_tm1217_write(ts, req, 1); + + /* Setup the device to no sleep mode for now and make it configured */ + req[0] = TMA1217_DEVICE_CTRL; + req[1] = 0x84; + retval = cp_tm1217_write(ts, req, 1); + + /* Check for the status of the device */ + req[0] = TMA1217_DEV_STATUS; + retval = cp_tm1217_read(ts, req, 1); + if (req[1] != 0) { + dev_err(ts->dev, + "cp_tm1217: Device Status 0x%x != 0: config failed\n", + req[1]); + + retval = -EIO; + goto fail; + } + + if (pdata && pdata->gpio) { + ts->gpio = pdata->gpio; + retval = cp_tm1217_setup_gpio_irq(ts); + } else + retval = client->irq; + + if (retval < 0) { + dev_err(ts->dev, "cp_tm1217: GPIO request failed error %d\n", + retval); + goto fail; + } + + client->irq = retval; + + + retval = request_threaded_irq(client->irq, + NULL, cp_tm1217_sample_thread, + IRQF_TRIGGER_FALLING, "cp_tm1217_touch", ts); + if (retval < 0) { + dev_err(ts->dev, "cp_tm1217: Request IRQ error %d\n", retval); + goto fail_gpio; + } + + /* Unmask the interrupts */ + retval = cp_tm1217_unmask_interrupt(ts); + if (retval == 0) + return 0; + + free_irq(client->irq, ts); +fail_gpio: + if (ts->gpio) + gpio_free(ts->gpio); +fail: + /* Clean up before returning failure */ + for (i = 0; i < TOUCH_SUPPORTED; i++) { + if (ts->cp_input_info[i].input) { + input_unregister_device(ts->cp_input_info[i].input); + input_free_device(ts->cp_input_info[i].input); + } + } + kfree(ts); + return retval; + +} + +/* + * cp_tm1217 suspend + * + */ +static int cp_tm1217_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cp_tm1217_device *ts = i2c_get_clientdata(client); + u8 req[2]; + int retval; + + /* Put the controller to sleep */ + req[0] = TMA1217_DEVICE_CTRL; + retval = cp_tm1217_read(ts, req, 1); + req[1] = (req[1] & 0xF8) | 0x1; + retval = cp_tm1217_write(ts, req, 1); + + if (device_may_wakeup(&client->dev)) + enable_irq_wake(client->irq); + + return 0; +} + +/* + * cp_tm1217_resume + * + */ +static int cp_tm1217_resume(struct i2c_client *client) +{ + struct cp_tm1217_device *ts = i2c_get_clientdata(client); + u8 req[2]; + int retval; + + /* Take the controller out of sleep */ + req[0] = TMA1217_DEVICE_CTRL; + retval = cp_tm1217_read(ts, req, 1); + req[1] = (req[1] & 0xF8) | 0x4; + retval = cp_tm1217_write(ts, req, 1); + + /* Restore the register settings sinc the power to the + could have been cut off */ + + /* Setup the reporting mode to send an interrupt only when + finger arrives or departs. */ + req[0] = TMA1217_REPORT_MODE; + req[1] = 0x02; + retval = cp_tm1217_write(ts, req, 1); + + /* Setup the device to no sleep mode for now and make it configured */ + req[0] = TMA1217_DEVICE_CTRL; + req[1] = 0x84; + retval = cp_tm1217_write(ts, req, 1); + + /* Setup the interrupt mask */ + retval = cp_tm1217_unmask_interrupt(ts); + + if (device_may_wakeup(&client->dev)) + disable_irq_wake(client->irq); + + return 0; +} + +/* + * cp_tm1217_remove + * + */ +static int cp_tm1217_remove(struct i2c_client *client) +{ + struct cp_tm1217_device *ts = i2c_get_clientdata(client); + int i; + + free_irq(client->irq, ts); + if (ts->gpio) + gpio_free(ts->gpio); + for (i = 0; i < TOUCH_SUPPORTED; i++) + input_unregister_device(ts->cp_input_info[i].input); + kfree(ts); + return 0; +} + +static struct i2c_device_id cp_tm1217_idtable[] = { + { CPTM1217_DEVICE_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, cp_tm1217_idtable); + +static struct i2c_driver cp_tm1217_driver = { + .driver = { + .owner = THIS_MODULE, + .name = CPTM1217_DRIVER_NAME, + }, + .id_table = cp_tm1217_idtable, + .probe = cp_tm1217_probe, + .remove = cp_tm1217_remove, + .suspend = cp_tm1217_suspend, + .resume = cp_tm1217_resume, +}; + +static int __init clearpad_tm1217_init(void) +{ + return i2c_add_driver(&cp_tm1217_driver); +} + +static void __exit clearpad_tm1217_exit(void) +{ + i2c_del_driver(&cp_tm1217_driver); +} + +module_init(clearpad_tm1217_init); +module_exit(clearpad_tm1217_exit); + +MODULE_AUTHOR("Ramesh Agarwal "); +MODULE_DESCRIPTION("Synaptics TM1217 TouchScreen Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/cptm1217/cp_tm1217.h b/drivers/staging/cptm1217/cp_tm1217.h new file mode 100644 index 00000000..a0ce31db --- /dev/null +++ b/drivers/staging/cptm1217/cp_tm1217.h @@ -0,0 +1,9 @@ +#ifndef __LINUX_I2C_CP_TM1217_H +#define __LINUX_I2C_CP_TM1217_H + +struct cp_tm1217_platform_data +{ + int gpio; /* If not set uses the IRQ resource 0 */ +}; + +#endif diff --git a/drivers/staging/crystalhd/Kconfig b/drivers/staging/crystalhd/Kconfig new file mode 100644 index 00000000..56b414bc --- /dev/null +++ b/drivers/staging/crystalhd/Kconfig @@ -0,0 +1,6 @@ +config CRYSTALHD + tristate "Broadcom Crystal HD video decoder support" + depends on PCI + default n + help + Support for the Broadcom Crystal HD video decoder chipset diff --git a/drivers/staging/crystalhd/Makefile b/drivers/staging/crystalhd/Makefile new file mode 100644 index 00000000..c31657a9 --- /dev/null +++ b/drivers/staging/crystalhd/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_CRYSTALHD) += crystalhd.o + +crystalhd-y := crystalhd_cmds.o \ + crystalhd_hw.o \ + crystalhd_lnx.o \ + crystalhd_misc.o diff --git a/drivers/staging/crystalhd/TODO b/drivers/staging/crystalhd/TODO new file mode 100644 index 00000000..daca2d4d --- /dev/null +++ b/drivers/staging/crystalhd/TODO @@ -0,0 +1,15 @@ +- Testing +- Cleanup return codes +- Cleanup typedefs +- Allocate an Accelerator device class specific Major number, + since we don't have any other open sourced accelerators, it is the only + one in that category for now. + A somewhat similar device is the DXR2/3 + +Please send patches to: +Greg Kroah-Hartman +Naren Sankar +Jarod Wilson +Scott Davilla +Manu Abraham + diff --git a/drivers/staging/crystalhd/bc_dts_defs.h b/drivers/staging/crystalhd/bc_dts_defs.h new file mode 100644 index 00000000..647e116e --- /dev/null +++ b/drivers/staging/crystalhd/bc_dts_defs.h @@ -0,0 +1,572 @@ +/******************************************************************** + * Copyright(c) 2006-2009 Broadcom Corporation. + * + * Name: bc_dts_defs.h + * + * Description: Common definitions for all components. Only types + * is allowed to be included from this file. + * + * AU + * + * HISTORY: + * + ******************************************************************** + * This header is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 2.1 of the License. + * + * This header is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this header. If not, see . + *******************************************************************/ + +#ifndef _BC_DTS_DEFS_H_ +#define _BC_DTS_DEFS_H_ + +#include + +/* BIT Mask */ +#define BC_BIT(_x) (1 << (_x)) + +enum BC_STATUS { + BC_STS_SUCCESS = 0, + BC_STS_INV_ARG = 1, + BC_STS_BUSY = 2, + BC_STS_NOT_IMPL = 3, + BC_STS_PGM_QUIT = 4, + BC_STS_NO_ACCESS = 5, + BC_STS_INSUFF_RES = 6, + BC_STS_IO_ERROR = 7, + BC_STS_NO_DATA = 8, + BC_STS_VER_MISMATCH = 9, + BC_STS_TIMEOUT = 10, + BC_STS_FW_CMD_ERR = 11, + BC_STS_DEC_NOT_OPEN = 12, + BC_STS_ERR_USAGE = 13, + BC_STS_IO_USER_ABORT = 14, + BC_STS_IO_XFR_ERROR = 15, + BC_STS_DEC_NOT_STARTED = 16, + BC_STS_FWHEX_NOT_FOUND = 17, + BC_STS_FMT_CHANGE = 18, + BC_STS_HIF_ACCESS = 19, + BC_STS_CMD_CANCELLED = 20, + BC_STS_FW_AUTH_FAILED = 21, + BC_STS_BOOTLOADER_FAILED = 22, + BC_STS_CERT_VERIFY_ERROR = 23, + BC_STS_DEC_EXIST_OPEN = 24, + BC_STS_PENDING = 25, + BC_STS_CLK_NOCHG = 26, + + /* Must be the last one.*/ + BC_STS_ERROR = -1 +}; + +/*------------------------------------------------------* + * Registry Key Definitions * + *------------------------------------------------------*/ +#define BC_REG_KEY_MAIN_PATH "Software\\Broadcom\\MediaPC\\70010" +#define BC_REG_KEY_FWPATH "FirmwareFilePath" +#define BC_REG_KEY_SEC_OPT "DbgOptions" + +/* + * Options: + * + * b[5] = Enable RSA KEY in EEPROM Support + * b[6] = Enable Old PIB scheme. (0 = Use PIB with video scheme) + * + * b[12] = Enable send message to NotifyIcon + * + */ + +enum BC_SW_OPTIONS { + BC_OPT_DOSER_OUT_ENCRYPT = BC_BIT(3), + BC_OPT_LINK_OUT_ENCRYPT = BC_BIT(29), +}; + +struct BC_REG_CONFIG { + uint32_t DbgOptions; +}; + +#if defined(__KERNEL__) || defined(__LINUX_USER__) +#else +/* Align data structures */ +#define ALIGN(x) __declspec(align(x)) +#endif + +/* mode + * b[0]..b[7] = _DtsDeviceOpenMode + * b[8] = Load new FW + * b[9] = Load file play back FW + * b[10] = Disk format (0 for HD DVD and 1 for BLU ray) + * b[11]-b[15] = default output resolution + * b[16] = Skip TX CPB Buffer Check + * b[17] = Adaptive Output Encrypt/Scramble Scheme + * b[18]-b[31] = reserved for future use + */ + +/* To allow multiple apps to open the device. */ +enum DtsDeviceOpenMode { + DTS_PLAYBACK_MODE = 0, + DTS_DIAG_MODE, + DTS_MONITOR_MODE, + DTS_HWINIT_MODE +}; + +/* To enable the filter to selectively enable/disable fixes or erratas */ +enum DtsDeviceFixMode { + DTS_LOAD_NEW_FW = BC_BIT(8), + DTS_LOAD_FILE_PLAY_FW = BC_BIT(9), + DTS_DISK_FMT_BD = BC_BIT(10), + /* b[11]-b[15] : Default output resolution */ + DTS_SKIP_TX_CHK_CPB = BC_BIT(16), + DTS_ADAPTIVE_OUTPUT_PER = BC_BIT(17), + DTS_INTELLIMAP = BC_BIT(18), + /* b[19]-b[21] : select clock frequency */ + DTS_PLAYBACK_DROP_RPT_MODE = BC_BIT(22) +}; + +#define DTS_DFLT_RESOLUTION(x) (x<<11) + +#define DTS_DFLT_CLOCK(x) (x<<19) + +/* F/W File Version corresponding to S/W Releases */ +enum FW_FILE_VER { + /* S/W release: 02.04.02 F/W release 2.12.2.0 */ + BC_FW_VER_020402 = ((12<<16) | (2<<8) | (0)) +}; + +/*------------------------------------------------------* + * Stream Types for DtsOpenDecoder() * + *------------------------------------------------------*/ +enum DtsOpenDecStreamTypes { + BC_STREAM_TYPE_ES = 0, + BC_STREAM_TYPE_PES = 1, + BC_STREAM_TYPE_TS = 2, + BC_STREAM_TYPE_ES_TSTAMP = 6, +}; + +/*------------------------------------------------------* + * Video Algorithms for DtsSetVideoParams() * + *------------------------------------------------------*/ +enum DtsSetVideoParamsAlgo { + BC_VID_ALGO_H264 = 0, + BC_VID_ALGO_MPEG2 = 1, + BC_VID_ALGO_VC1 = 4, + BC_VID_ALGO_VC1MP = 7, +}; + +/*------------------------------------------------------* + * MPEG Extension to the PPB * + *------------------------------------------------------*/ +#define BC_MPEG_VALID_PANSCAN (1) + +struct BC_PIB_EXT_MPEG { + uint32_t valid; + /* Always valid, defaults to picture size if no + * sequence display extension in the stream. */ + uint32_t display_horizontal_size; + uint32_t display_vertical_size; + + /* MPEG_VALID_PANSCAN + * Offsets are a copy values from the MPEG stream. */ + uint32_t offset_count; + int32_t horizontal_offset[3]; + int32_t vertical_offset[3]; +}; + +/*------------------------------------------------------* + * H.264 Extension to the PPB * + *------------------------------------------------------*/ +/* Bit definitions for 'other.h264.valid' field */ +#define H264_VALID_PANSCAN (1) +#define H264_VALID_SPS_CROP (2) +#define H264_VALID_VUI (4) + +struct BC_PIB_EXT_H264 { + /* 'valid' specifies which fields (or sets of + * fields) below are valid. If the corresponding + * bit in 'valid' is NOT set then that field(s) + * is (are) not initialized. */ + uint32_t valid; + + /* H264_VALID_PANSCAN */ + uint32_t pan_scan_count; + int32_t pan_scan_left[3]; + int32_t pan_scan_right[3]; + int32_t pan_scan_top[3]; + int32_t pan_scan_bottom[3]; + + /* H264_VALID_SPS_CROP */ + int32_t sps_crop_left; + int32_t sps_crop_right; + int32_t sps_crop_top; + int32_t sps_crop_bottom; + + /* H264_VALID_VUI */ + uint32_t chroma_top; + uint32_t chroma_bottom; +}; + +/*------------------------------------------------------* + * VC1 Extension to the PPB * + *------------------------------------------------------*/ +#define VC1_VALID_PANSCAN (1) + +struct BC_PIB_EXT_VC1 { + uint32_t valid; + + /* Always valid, defaults to picture size if no + * sequence display extension in the stream. */ + uint32_t display_horizontal_size; + uint32_t display_vertical_size; + + /* VC1 pan scan windows */ + uint32_t num_panscan_windows; + int32_t ps_horiz_offset[4]; + int32_t ps_vert_offset[4]; + int32_t ps_width[4]; + int32_t ps_height[4]; +}; + +/*------------------------------------------------------* + * Picture Information Block * + *------------------------------------------------------*/ +#if defined(__LINUX_USER__) +/* Values for 'pulldown' field. '0' means no pulldown information + * was present for this picture. */ +enum { + vdecNoPulldownInfo = 0, + vdecTop = 1, + vdecBottom = 2, + vdecTopBottom = 3, + vdecBottomTop = 4, + vdecTopBottomTop = 5, + vdecBottomTopBottom = 6, + vdecFrame_X2 = 7, + vdecFrame_X3 = 8, + vdecFrame_X1 = 9, + vdecFrame_X4 = 10, +}; + +/* Values for the 'frame_rate' field. */ +enum { + vdecFrameRateUnknown = 0, + vdecFrameRate23_97, + vdecFrameRate24, + vdecFrameRate25, + vdecFrameRate29_97, + vdecFrameRate30, + vdecFrameRate50, + vdecFrameRate59_94, + vdecFrameRate60, +}; + +/* Values for the 'aspect_ratio' field. */ +enum { + vdecAspectRatioUnknown = 0, + vdecAspectRatioSquare, + vdecAspectRatio12_11, + vdecAspectRatio10_11, + vdecAspectRatio16_11, + vdecAspectRatio40_33, + vdecAspectRatio24_11, + vdecAspectRatio20_11, + vdecAspectRatio32_11, + vdecAspectRatio80_33, + vdecAspectRatio18_11, + vdecAspectRatio15_11, + vdecAspectRatio64_33, + vdecAspectRatio160_99, + vdecAspectRatio4_3, + vdecAspectRatio16_9, + vdecAspectRatio221_1, + vdecAspectRatioOther = 255, +}; + +/* Values for the 'colour_primaries' field. */ +enum { + vdecColourPrimariesUnknown = 0, + vdecColourPrimariesBT709, + vdecColourPrimariesUnspecified, + vdecColourPrimariesReserved, + vdecColourPrimariesBT470_2M = 4, + vdecColourPrimariesBT470_2BG, + vdecColourPrimariesSMPTE170M, + vdecColourPrimariesSMPTE240M, + vdecColourPrimariesGenericFilm, +}; +/** + * @vdecRESOLUTION_CUSTOM: custom + * @vdecRESOLUTION_480i: 480i + * @vdecRESOLUTION_1080i: 1080i (1920x1080, 60i) + * @vdecRESOLUTION_NTSC: NTSC (720x483, 60i) + * @vdecRESOLUTION_480p: 480p (720x480, 60p) + * @vdecRESOLUTION_720p: 720p (1280x720, 60p) + * @vdecRESOLUTION_PAL1: PAL_1 (720x576, 50i) + * @vdecRESOLUTION_1080i25: 1080i25 (1920x1080, 50i) + * @vdecRESOLUTION_720p50: 720p50 (1280x720, 50p) + * @vdecRESOLUTION_576p: 576p (720x576, 50p) + * @vdecRESOLUTION_1080i29_97: 1080i (1920x1080, 59.94i) + * @vdecRESOLUTION_720p59_94: 720p (1280x720, 59.94p) + * @vdecRESOLUTION_SD_DVD: SD DVD (720x483, 60i) + * @vdecRESOLUTION_480p656: 480p (720x480, 60p), + * output bus width 8 bit, clock 74.25MHz + * @vdecRESOLUTION_1080p23_976: 1080p23_976 (1920x1080, 23.976p) + * @vdecRESOLUTION_720p23_976: 720p23_976 (1280x720p, 23.976p) + * @vdecRESOLUTION_240p29_97: 240p (1440x240, 29.97p ) + * @vdecRESOLUTION_240p30: 240p (1440x240, 30p) + * @vdecRESOLUTION_288p25: 288p (1440x288p, 25p) + * @vdecRESOLUTION_1080p29_97: 1080p29_97 (1920x1080, 29.97p) + * @vdecRESOLUTION_1080p30: 1080p30 (1920x1080, 30p) + * @vdecRESOLUTION_1080p24: 1080p24 (1920x1080, 24p) + * @vdecRESOLUTION_1080p25: 1080p25 (1920x1080, 25p) + * @vdecRESOLUTION_720p24: 720p24 (1280x720, 25p) + * @vdecRESOLUTION_720p29_97: 720p29.97 (1280x720, 29.97p) + * @vdecRESOLUTION_480p23_976: 480p23.976 (720*480, 23.976) + * @vdecRESOLUTION_480p29_97: 480p29.976 (720*480, 29.97p) + * @vdecRESOLUTION_576p25: 576p25 (720*576, 25p) + * @vdecRESOLUTION_480p0: 480p (720x480, 0p) + * @vdecRESOLUTION_480i0: 480i (720x480, 0i) + * @vdecRESOLUTION_576p0: 576p (720x576, 0p) + * @vdecRESOLUTION_720p0: 720p (1280x720, 0p) + * @vdecRESOLUTION_1080p0: 1080p (1920x1080, 0p) + * @vdecRESOLUTION_1080i0: 1080i (1920x1080, 0i) + */ +enum { + vdecRESOLUTION_CUSTOM = 0x00000000, + vdecRESOLUTION_480i = 0x00000001, + vdecRESOLUTION_1080i = 0x00000002, + vdecRESOLUTION_NTSC = 0x00000003, + vdecRESOLUTION_480p = 0x00000004, + vdecRESOLUTION_720p = 0x00000005, + vdecRESOLUTION_PAL1 = 0x00000006, + vdecRESOLUTION_1080i25 = 0x00000007, + vdecRESOLUTION_720p50 = 0x00000008, + vdecRESOLUTION_576p = 0x00000009, + vdecRESOLUTION_1080i29_97 = 0x0000000A, + vdecRESOLUTION_720p59_94 = 0x0000000B, + vdecRESOLUTION_SD_DVD = 0x0000000C, + vdecRESOLUTION_480p656 = 0x0000000D, + vdecRESOLUTION_1080p23_976 = 0x0000000E, + vdecRESOLUTION_720p23_976 = 0x0000000F, + vdecRESOLUTION_240p29_97 = 0x00000010, + vdecRESOLUTION_240p30 = 0x00000011, + vdecRESOLUTION_288p25 = 0x00000012, + vdecRESOLUTION_1080p29_97 = 0x00000013, + vdecRESOLUTION_1080p30 = 0x00000014, + vdecRESOLUTION_1080p24 = 0x00000015, + vdecRESOLUTION_1080p25 = 0x00000016, + vdecRESOLUTION_720p24 = 0x00000017, + vdecRESOLUTION_720p29_97 = 0x00000018, + vdecRESOLUTION_480p23_976 = 0x00000019, + vdecRESOLUTION_480p29_97 = 0x0000001A, + vdecRESOLUTION_576p25 = 0x0000001B, + /* For Zero Frame Rate */ + vdecRESOLUTION_480p0 = 0x0000001C, + vdecRESOLUTION_480i0 = 0x0000001D, + vdecRESOLUTION_576p0 = 0x0000001E, + vdecRESOLUTION_720p0 = 0x0000001F, + vdecRESOLUTION_1080p0 = 0x00000020, + vdecRESOLUTION_1080i0 = 0x00000021, +}; + +/* Bit definitions for 'flags' field */ +#define VDEC_FLAG_EOS (0x0004) + +#define VDEC_FLAG_FRAME (0x0000) +#define VDEC_FLAG_FIELDPAIR (0x0008) +#define VDEC_FLAG_TOPFIELD (0x0010) +#define VDEC_FLAG_BOTTOMFIELD (0x0018) + +#define VDEC_FLAG_PROGRESSIVE_SRC (0x0000) +#define VDEC_FLAG_INTERLACED_SRC (0x0020) +#define VDEC_FLAG_UNKNOWN_SRC (0x0040) + +#define VDEC_FLAG_BOTTOM_FIRST (0x0080) +#define VDEC_FLAG_LAST_PICTURE (0x0100) + +#define VDEC_FLAG_PICTURE_META_DATA_PRESENT (0x40000) + +#endif /* __LINUX_USER__ */ + +enum _BC_OUTPUT_FORMAT { + MODE420 = 0x0, + MODE422_YUY2 = 0x1, + MODE422_UYVY = 0x2, +}; +/** + * struct BC_PIC_INFO_BLOCK + * @timeStam;: Timestamp + * @picture_number: Ordinal display number + * @width: pixels + * @height: pixels + * @chroma_format: 0x420, 0x422 or 0x444 + * @n_drop;: number of non-reference frames + * remaining to be dropped + */ +struct BC_PIC_INFO_BLOCK { + /* Common fields. */ + uint64_t timeStamp; + uint32_t picture_number; + uint32_t width; + uint32_t height; + uint32_t chroma_format; + uint32_t pulldown; + uint32_t flags; + uint32_t frame_rate; + uint32_t aspect_ratio; + uint32_t colour_primaries; + uint32_t picture_meta_payload; + uint32_t sess_num; + uint32_t ycom; + uint32_t custom_aspect_ratio_width_height; + uint32_t n_drop; /* number of non-reference frames + remaining to be dropped */ + + /* Protocol-specific extensions. */ + union { + struct BC_PIB_EXT_H264 h264; + struct BC_PIB_EXT_MPEG mpeg; + struct BC_PIB_EXT_VC1 vc1; + } other; + +}; + +/*------------------------------------------------------* + * ProcOut Info * + *------------------------------------------------------*/ + +/** + * enum POUT_OPTIONAL_IN_FLAGS - Optional flags for ProcOut Interface. + * @BC_POUT_FLAGS_YV12: Copy Data in YV12 format + * @BC_POUT_FLAGS_STRIDE: Stride size is valid. + * @BC_POUT_FLAGS_SIZE: Take size information from Application + * @BC_POUT_FLAGS_INTERLACED: copy only half the bytes + * @BC_POUT_FLAGS_INTERLEAVED: interleaved frame + * @: * @BC_POUT_FLAGS_FMT_CHANGE: Data is not VALID when this flag is set + * @BC_POUT_FLAGS_PIB_VALID: PIB Information valid + * @BC_POUT_FLAGS_ENCRYPTED: Data is encrypted. + * @BC_POUT_FLAGS_FLD_BOT: Bottom Field data + */ +enum POUT_OPTIONAL_IN_FLAGS_ { + /* Flags from App to Device */ + BC_POUT_FLAGS_YV12 = 0x01, + BC_POUT_FLAGS_STRIDE = 0x02, + BC_POUT_FLAGS_SIZE = 0x04, + BC_POUT_FLAGS_INTERLACED = 0x08, + BC_POUT_FLAGS_INTERLEAVED = 0x10, + + /* Flags from Device to APP */ + BC_POUT_FLAGS_FMT_CHANGE = 0x10000, + BC_POUT_FLAGS_PIB_VALID = 0x20000, + BC_POUT_FLAGS_ENCRYPTED = 0x40000, + BC_POUT_FLAGS_FLD_BOT = 0x80000, +}; + +typedef enum BC_STATUS(*dts_pout_callback)(void *shnd, uint32_t width, + uint32_t height, uint32_t stride, void *pOut); + +/* Line 21 Closed Caption */ +/* User Data */ +#define MAX_UD_SIZE 1792 /* 1920 - 128 */ + +/** + * struct BC_DTS_PROC_OUT + * @Ybuff: Caller Supplied buffer for Y data + * @YbuffSz: Caller Supplied Y buffer size + * @YBuffDoneSz: Transferred Y datasize + * @*UVbuff: Caller Supplied buffer for UV data + * @UVbuffSz: Caller Supplied UV buffer size + * @UVBuffDoneSz: Transferred UV data size + * @StrideSz: Caller supplied Stride Size + * @PoutFlags: Call IN Flags + * @discCnt: Picture discontinuity count + * @PicInfo: Picture Information Block Data + * @b422Mode: Picture output Mode + * @bPibEnc: PIB encrypted + */ +struct BC_DTS_PROC_OUT { + uint8_t *Ybuff; + uint32_t YbuffSz; + uint32_t YBuffDoneSz; + + uint8_t *UVbuff; + uint32_t UVbuffSz; + uint32_t UVBuffDoneSz; + + uint32_t StrideSz; + uint32_t PoutFlags; + + uint32_t discCnt; + + struct BC_PIC_INFO_BLOCK PicInfo; + + /* Line 21 Closed Caption */ + /* User Data */ + uint32_t UserDataSz; + uint8_t UserData[MAX_UD_SIZE]; + + void *hnd; + dts_pout_callback AppCallBack; + uint8_t DropFrames; + uint8_t b422Mode; + uint8_t bPibEnc; + uint8_t bRevertScramble; + +}; +/** + * struct BC_DTS_STATUS + * @ReadyListCount: Number of frames in ready list (reported by driver) + * @PowerStateChange: Number of active state power + * transitions (reported by driver) + * @FramesDropped: Number of frames dropped. (reported by DIL) + * @FramesCaptured: Number of frames captured. (reported by DIL) + * @FramesRepeated: Number of frames repeated. (reported by DIL) + * @InputCount: Times compressed video has been sent to the HW. + * i.e. Successful DtsProcInput() calls (reported by DIL) + * @InputTotalSize: Amount of compressed video that has been sent to the HW. + * (reported by DIL) + * @InputBusyCount: Times compressed video has attempted to be sent to the HW + * but the input FIFO was full. (reported by DIL) + * @PIBMissCount: Amount of times a PIB is invalid. (reported by DIL) + * @cpbEmptySize: supported only for H.264, specifically changed for + * Adobe. Report size of CPB buffer available. (reported by DIL) + * @NextTimeStamp: TimeStamp of the next picture that will be returned + * by a call to ProcOutput. Added for Adobe. Reported + * back from the driver + */ +struct BC_DTS_STATUS { + uint8_t ReadyListCount; + uint8_t FreeListCount; + uint8_t PowerStateChange; + uint8_t reserved_[1]; + uint32_t FramesDropped; + uint32_t FramesCaptured; + uint32_t FramesRepeated; + uint32_t InputCount; + uint64_t InputTotalSize; + uint32_t InputBusyCount; + uint32_t PIBMissCount; + uint32_t cpbEmptySize; + uint64_t NextTimeStamp; + uint8_t reserved__[16]; +}; + +#define BC_SWAP32(_v) \ + ((((_v) & 0xFF000000)>>24)| \ + (((_v) & 0x00FF0000)>>8)| \ + (((_v) & 0x0000FF00)<<8)| \ + (((_v) & 0x000000FF)<<24)) + +#define WM_AGENT_TRAYICON_DECODER_OPEN 10001 +#define WM_AGENT_TRAYICON_DECODER_CLOSE 10002 +#define WM_AGENT_TRAYICON_DECODER_START 10003 +#define WM_AGENT_TRAYICON_DECODER_STOP 10004 +#define WM_AGENT_TRAYICON_DECODER_RUN 10005 +#define WM_AGENT_TRAYICON_DECODER_PAUSE 10006 + + +#endif /* _BC_DTS_DEFS_H_ */ diff --git a/drivers/staging/crystalhd/bc_dts_glob_lnx.h b/drivers/staging/crystalhd/bc_dts_glob_lnx.h new file mode 100644 index 00000000..fd1a6e68 --- /dev/null +++ b/drivers/staging/crystalhd/bc_dts_glob_lnx.h @@ -0,0 +1,297 @@ +/******************************************************************** + * Copyright(c) 2006-2009 Broadcom Corporation. + * + * Name: bc_dts_glob_lnx.h + * + * Description: Wrapper to Windows dts_glob.h for Link-Linux usage. + * The idea is to define additional Linux related defs + * in this file to avoid changes to existing Windows + * glob file. + * + * AU + * + * HISTORY: + * + ******************************************************************** + * This header is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 2.1 of the License. + * + * This header is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this header. If not, see . + *******************************************************************/ + +#ifndef _BC_DTS_GLOB_LNX_H_ +#define _BC_DTS_GLOB_LNX_H_ + +#ifdef __LINUX_USER__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVIFLIB_INT_API + +#endif + +#include "crystalhd.h" + +#define CRYSTALHD_API_NAME "crystalhd" +#define CRYSTALHD_API_DEV_NAME "/dev/crystalhd" + +/* + * These are SW stack tunable parameters shared + * between the driver and the application. + */ +enum BC_DTS_GLOBALS { + BC_MAX_FW_CMD_BUFF_SZ = 0x40, /* FW passthrough cmd/rsp buffer size */ + PCI_CFG_SIZE = 256, /* PCI config size buffer */ + BC_IOCTL_DATA_POOL_SIZE = 8, /* BC_IOCTL_DATA Pool size */ + BC_LINK_MAX_OPENS = 3, /* Maximum simultaneous opens*/ + BC_LINK_MAX_SGLS = 1024, /* Maximum SG elements 4M/4K */ + BC_TX_LIST_CNT = 2, /* Max Tx DMA Rings */ + BC_RX_LIST_CNT = 8, /* Max Rx DMA Rings*/ + BC_PROC_OUTPUT_TIMEOUT = 3000, /* Milliseconds */ + BC_INFIFO_THRESHOLD = 0x10000, +}; + +struct BC_CMD_REG_ACC { + uint32_t Offset; + uint32_t Value; +}; + +struct BC_CMD_DEV_MEM { + uint32_t StartOff; + uint32_t NumDwords; + uint32_t Rsrd; +}; + +/* FW Passthrough command structure */ +enum bc_fw_cmd_flags { + BC_FW_CMD_FLAGS_NONE = 0, + BC_FW_CMD_PIB_QS = 0x01, +}; + +struct BC_FW_CMD { + uint32_t cmd[BC_MAX_FW_CMD_BUFF_SZ]; + uint32_t rsp[BC_MAX_FW_CMD_BUFF_SZ]; + uint32_t flags; + uint32_t add_data; +}; + +struct BC_HW_TYPE { + uint16_t PciDevId; + uint16_t PciVenId; + uint8_t HwRev; + uint8_t Align[3]; +}; + +struct BC_PCI_CFG { + uint32_t Size; + uint32_t Offset; + uint8_t pci_cfg_space[PCI_CFG_SIZE]; +}; + +struct BC_VERSION_INFO { + uint8_t DriverMajor; + uint8_t DriverMinor; + uint16_t DriverRevision; +}; + +struct BC_START_RX_CAP { + uint32_t Rsrd; + uint32_t StartDeliveryThsh; + uint32_t PauseThsh; + uint32_t ResumeThsh; +}; + +struct BC_FLUSH_RX_CAP { + uint32_t Rsrd; + uint32_t bDiscardOnly; +}; + +struct BC_DTS_STATS { + uint8_t drvRLL; + uint8_t drvFLL; + uint8_t eosDetected; + uint8_t pwr_state_change; + + /* Stats from App */ + uint32_t opFrameDropped; + uint32_t opFrameCaptured; + uint32_t ipSampleCnt; + uint64_t ipTotalSize; + uint32_t reptdFrames; + uint32_t pauseCount; + uint32_t pibMisses; + uint32_t discCounter; + + /* Stats from Driver */ + uint32_t TxFifoBsyCnt; + uint32_t intCount; + uint32_t DrvIgnIntrCnt; + uint32_t DrvTotalFrmDropped; + uint32_t DrvTotalHWErrs; + uint32_t DrvTotalPIBFlushCnt; + uint32_t DrvTotalFrmCaptured; + uint32_t DrvPIBMisses; + uint32_t DrvPauseTime; + uint32_t DrvRepeatedFrms; + uint32_t res1[13]; + +}; + +struct BC_PROC_INPUT { + uint8_t *pDmaBuff; + uint32_t BuffSz; + uint8_t Mapped; + uint8_t Encrypted; + uint8_t Rsrd[2]; + uint32_t DramOffset; /* For debug use only */ +}; + +struct BC_DEC_YUV_BUFFS { + uint32_t b422Mode; + uint8_t *YuvBuff; + uint32_t YuvBuffSz; + uint32_t UVbuffOffset; + uint32_t YBuffDoneSz; + uint32_t UVBuffDoneSz; + uint32_t RefCnt; +}; + +enum DECOUT_COMPLETION_FLAGS { + COMP_FLAG_NO_INFO = 0x00, + COMP_FLAG_FMT_CHANGE = 0x01, + COMP_FLAG_PIB_VALID = 0x02, + COMP_FLAG_DATA_VALID = 0x04, + COMP_FLAG_DATA_ENC = 0x08, + COMP_FLAG_DATA_BOT = 0x10, +}; + +struct BC_DEC_OUT_BUFF { + struct BC_DEC_YUV_BUFFS OutPutBuffs; + struct BC_PIC_INFO_BLOCK PibInfo; + uint32_t Flags; + uint32_t BadFrCnt; +}; + +struct BC_NOTIFY_MODE { + uint32_t Mode; + uint32_t Rsvr[3]; +}; + +struct BC_CLOCK { + uint32_t clk; + uint32_t Rsvr[3]; +}; + +struct BC_IOCTL_DATA { + enum BC_STATUS RetSts; + uint32_t IoctlDataSz; + uint32_t Timeout; + union { + struct BC_CMD_REG_ACC regAcc; + struct BC_CMD_DEV_MEM devMem; + struct BC_FW_CMD fwCmd; + struct BC_HW_TYPE hwType; + struct BC_PCI_CFG pciCfg; + struct BC_VERSION_INFO VerInfo; + struct BC_PROC_INPUT ProcInput; + struct BC_DEC_YUV_BUFFS RxBuffs; + struct BC_DEC_OUT_BUFF DecOutData; + struct BC_START_RX_CAP RxCap; + struct BC_FLUSH_RX_CAP FlushRxCap; + struct BC_DTS_STATS drvStat; + struct BC_NOTIFY_MODE NotifyMode; + struct BC_CLOCK clockValue; + } u; + struct _BC_IOCTL_DATA *next; +}; + +enum BC_DRV_CMD { + DRV_CMD_VERSION = 0, /* Get SW version */ + DRV_CMD_GET_HWTYPE, /* Get HW version and type Dozer/Tank */ + DRV_CMD_REG_RD, /* Read Device Register */ + DRV_CMD_REG_WR, /* Write Device Register */ + DRV_CMD_FPGA_RD, /* Read FPGA Register */ + DRV_CMD_FPGA_WR, /* Wrtie FPGA Reister */ + DRV_CMD_MEM_RD, /* Read Device Memory */ + DRV_CMD_MEM_WR, /* Write Device Memory */ + DRV_CMD_RD_PCI_CFG, /* Read PCI Config Space */ + DRV_CMD_WR_PCI_CFG, /* Write the PCI Configuration Space*/ + DRV_CMD_FW_DOWNLOAD, /* Download Firmware */ + DRV_ISSUE_FW_CMD, /* Issue FW Cmd (pass through mode) */ + DRV_CMD_PROC_INPUT, /* Process Input Sample */ + DRV_CMD_ADD_RXBUFFS, /* Add Rx side buffers to driver pool */ + DRV_CMD_FETCH_RXBUFF, /* Get Rx DMAed buffer */ + DRV_CMD_START_RX_CAP, /* Start Rx Buffer Capture */ + DRV_CMD_FLUSH_RX_CAP, /* Stop the capture for now...we will enhance this later*/ + DRV_CMD_GET_DRV_STAT, /* Get Driver Internal Statistics */ + DRV_CMD_RST_DRV_STAT, /* Reset Driver Internal Statistics */ + DRV_CMD_NOTIFY_MODE, /* Notify the Mode to driver in which the application is Operating*/ + DRV_CMD_CHANGE_CLOCK, /* Change the core clock to either save power or improve performance */ + + /* MUST be the last one.. */ + DRV_CMD_END, /* End of the List.. */ +}; + +#define BC_IOC_BASE 'b' +#define BC_IOC_VOID _IOC_NONE +#define BC_IOC_IOWR(nr, type) _IOWR(BC_IOC_BASE, nr, type) +#define BC_IOCTL_MB struct BC_IOCTL_DATA + +#define BCM_IOC_GET_VERSION BC_IOC_IOWR(DRV_CMD_VERSION, BC_IOCTL_MB) +#define BCM_IOC_GET_HWTYPE BC_IOC_IOWR(DRV_CMD_GET_HWTYPE, BC_IOCTL_MB) +#define BCM_IOC_REG_RD BC_IOC_IOWR(DRV_CMD_REG_RD, BC_IOCTL_MB) +#define BCM_IOC_REG_WR BC_IOC_IOWR(DRV_CMD_REG_WR, BC_IOCTL_MB) +#define BCM_IOC_MEM_RD BC_IOC_IOWR(DRV_CMD_MEM_RD, BC_IOCTL_MB) +#define BCM_IOC_MEM_WR BC_IOC_IOWR(DRV_CMD_MEM_WR, BC_IOCTL_MB) +#define BCM_IOC_FPGA_RD BC_IOC_IOWR(DRV_CMD_FPGA_RD, BC_IOCTL_MB) +#define BCM_IOC_FPGA_WR BC_IOC_IOWR(DRV_CMD_FPGA_WR, BC_IOCTL_MB) +#define BCM_IOC_RD_PCI_CFG BC_IOC_IOWR(DRV_CMD_RD_PCI_CFG, BC_IOCTL_MB) +#define BCM_IOC_WR_PCI_CFG BC_IOC_IOWR(DRV_CMD_WR_PCI_CFG, BC_IOCTL_MB) +#define BCM_IOC_PROC_INPUT BC_IOC_IOWR(DRV_CMD_PROC_INPUT, BC_IOCTL_MB) +#define BCM_IOC_ADD_RXBUFFS BC_IOC_IOWR(DRV_CMD_ADD_RXBUFFS, BC_IOCTL_MB) +#define BCM_IOC_FETCH_RXBUFF BC_IOC_IOWR(DRV_CMD_FETCH_RXBUFF, BC_IOCTL_MB) +#define BCM_IOC_FW_CMD BC_IOC_IOWR(DRV_ISSUE_FW_CMD, BC_IOCTL_MB) +#define BCM_IOC_START_RX_CAP BC_IOC_IOWR(DRV_CMD_START_RX_CAP, BC_IOCTL_MB) +#define BCM_IOC_FLUSH_RX_CAP BC_IOC_IOWR(DRV_CMD_FLUSH_RX_CAP, BC_IOCTL_MB) +#define BCM_IOC_GET_DRV_STAT BC_IOC_IOWR(DRV_CMD_GET_DRV_STAT, BC_IOCTL_MB) +#define BCM_IOC_RST_DRV_STAT BC_IOC_IOWR(DRV_CMD_RST_DRV_STAT, BC_IOCTL_MB) +#define BCM_IOC_NOTIFY_MODE BC_IOC_IOWR(DRV_CMD_NOTIFY_MODE, BC_IOCTL_MB) +#define BCM_IOC_FW_DOWNLOAD BC_IOC_IOWR(DRV_CMD_FW_DOWNLOAD, BC_IOCTL_MB) +#define BCM_IOC_CHG_CLK BC_IOC_IOWR(DRV_CMD_CHANGE_CLOCK, BC_IOCTL_MB) +#define BCM_IOC_END BC_IOC_VOID + +/* Wrapper for main IOCTL data */ +struct crystalhd_ioctl_data { + struct BC_IOCTL_DATA udata; /* IOCTL from App..*/ + uint32_t u_id; /* Driver specific user ID */ + uint32_t cmd; /* Cmd ID for driver's use. */ + void *add_cdata; /* Additional command specific data..*/ + uint32_t add_cdata_sz; /* Additional command specific data size */ + struct crystalhd_ioctl_data *next; /* List/Fifo management */ +}; + +enum crystalhd_kmod_ver { + crystalhd_kmod_major = 0, + crystalhd_kmod_minor = 9, + crystalhd_kmod_rev = 27, +}; + +#endif diff --git a/drivers/staging/crystalhd/bcm_70012_regs.h b/drivers/staging/crystalhd/bcm_70012_regs.h new file mode 100644 index 00000000..f3ab3146 --- /dev/null +++ b/drivers/staging/crystalhd/bcm_70012_regs.h @@ -0,0 +1,757 @@ +/*************************************************************************** + * Copyright (c) 1999-2009, Broadcom Corporation. + * + * Name: bcm_70012_regs.h + * + * Description: BCM70012 registers + * + ******************************************************************** + * This header is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 2.1 of the License. + * + * This header is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this header. If not, see . + ***************************************************************************/ + +#ifndef MACFILE_H__ +#define MACFILE_H__ + +/** + * m = memory, c = core, r = register, f = field, d = data. + */ +#if !defined(GET_FIELD) && !defined(SET_FIELD) +#define BRCM_ALIGN(c, r, f) c##_##r##_##f##_ALIGN +#define BRCM_BITS(c, r, f) c##_##r##_##f##_BITS +#define BRCM_MASK(c, r, f) c##_##r##_##f##_MASK +#define BRCM_SHIFT(c, r, f) c##_##r##_##f##_SHIFT + +#define GET_FIELD(m, c, r, f) \ + ((((m) & BRCM_MASK(c, r, f)) >> BRCM_SHIFT(c, r, f)) << BRCM_ALIGN(c, r, f)) + +#define SET_FIELD(m, c, r, f, d) \ + ((m) = (((m) & ~BRCM_MASK(c, r, f)) | ((((d) >> BRCM_ALIGN(c, r, f)) << \ + BRCM_SHIFT(c, r, f)) & BRCM_MASK(c, r, f))) \ + ) + +#define SET_TYPE_FIELD(m, c, r, f, d) SET_FIELD(m, c, r, f, c##_##d) +#define SET_NAME_FIELD(m, c, r, f, d) SET_FIELD(m, c, r, f, c##_##r##_##f##_##d) +#define SET_VALUE_FIELD(m, c, r, f, d) SET_FIELD(m, c, r, f, d) + +#endif /* GET & SET */ + +/**************************************************************************** + * Core Enums. + ***************************************************************************/ +/**************************************************************************** + * Enums: AES_RGR_BRIDGE_RESET_CTRL + ***************************************************************************/ +#define AES_RGR_BRIDGE_RESET_CTRL_DEASSERT 0 +#define AES_RGR_BRIDGE_RESET_CTRL_ASSERT 1 + +/**************************************************************************** + * Enums: CCE_RGR_BRIDGE_RESET_CTRL + ***************************************************************************/ +#define CCE_RGR_BRIDGE_RESET_CTRL_DEASSERT 0 +#define CCE_RGR_BRIDGE_RESET_CTRL_ASSERT 1 + +/**************************************************************************** + * Enums: DBU_RGR_BRIDGE_RESET_CTRL + ***************************************************************************/ +#define DBU_RGR_BRIDGE_RESET_CTRL_DEASSERT 0 +#define DBU_RGR_BRIDGE_RESET_CTRL_ASSERT 1 + +/**************************************************************************** + * Enums: DCI_RGR_BRIDGE_RESET_CTRL + ***************************************************************************/ +#define DCI_RGR_BRIDGE_RESET_CTRL_DEASSERT 0 +#define DCI_RGR_BRIDGE_RESET_CTRL_ASSERT 1 + +/**************************************************************************** + * Enums: GISB_ARBITER_DEASSERT_ASSERT + ***************************************************************************/ +#define GISB_ARBITER_DEASSERT_ASSERT_DEASSERT 0 +#define GISB_ARBITER_DEASSERT_ASSERT_ASSERT 1 + +/**************************************************************************** + * Enums: GISB_ARBITER_UNMASK_MASK + ***************************************************************************/ +#define GISB_ARBITER_UNMASK_MASK_UNMASK 0 +#define GISB_ARBITER_UNMASK_MASK_MASK 1 + +/**************************************************************************** + * Enums: GISB_ARBITER_DISABLE_ENABLE + ***************************************************************************/ +#define GISB_ARBITER_DISABLE_ENABLE_DISABLE 0 +#define GISB_ARBITER_DISABLE_ENABLE_ENABLE 1 + +/**************************************************************************** + * Enums: I2C_GR_BRIDGE_RESET_CTRL + ***************************************************************************/ +#define I2C_GR_BRIDGE_RESET_CTRL_DEASSERT 0 +#define I2C_GR_BRIDGE_RESET_CTRL_ASSERT 1 + +/**************************************************************************** + * Enums: MISC_GR_BRIDGE_RESET_CTRL + ***************************************************************************/ +#define MISC_GR_BRIDGE_RESET_CTRL_DEASSERT 0 +#define MISC_GR_BRIDGE_RESET_CTRL_ASSERT 1 + +/**************************************************************************** + * Enums: OTP_GR_BRIDGE_RESET_CTRL + ***************************************************************************/ +#define OTP_GR_BRIDGE_RESET_CTRL_DEASSERT 0 +#define OTP_GR_BRIDGE_RESET_CTRL_ASSERT 1 + +/**************************************************************************** + * BCM70012_TGT_TOP_PCIE_CFG + ***************************************************************************/ +#define PCIE_CFG_DEVICE_VENDOR_ID 0x00000000 /* DEVICE_VENDOR_ID Register */ +#define PCIE_CFG_STATUS_COMMAND 0x00000004 /* STATUS_COMMAND Register */ +#define PCIE_CFG_PCI_CLASSCODE_AND_REVISION_ID 0x00000008 /* PCI_CLASSCODE_AND_REVISION_ID Register */ +#define PCIE_CFG_BIST_HEADER_TYPE_LATENCY_TIMER_CACHE_LINE_SIZE 0x0000000c /* BIST_HEADER_TYPE_LATENCY_TIMER_CACHE_LINE_SIZE Register */ +#define PCIE_CFG_BASE_ADDRESS_1 0x00000010 /* BASE_ADDRESS_1 Register */ +#define PCIE_CFG_BASE_ADDRESS_2 0x00000014 /* BASE_ADDRESS_2 Register */ +#define PCIE_CFG_BASE_ADDRESS_3 0x00000018 /* BASE_ADDRESS_3 Register */ +#define PCIE_CFG_BASE_ADDRESS_4 0x0000001c /* BASE_ADDRESS_4 Register */ +#define PCIE_CFG_CARDBUS_CIS_POINTER 0x00000028 /* CARDBUS_CIS_POINTER Register */ +#define PCIE_CFG_SUBSYSTEM_DEVICE_VENDOR_ID 0x0000002c /* SUBSYSTEM_DEVICE_VENDOR_ID Register */ +#define PCIE_CFG_EXPANSION_ROM_BASE_ADDRESS 0x00000030 /* EXPANSION_ROM_BASE_ADDRESS Register */ +#define PCIE_CFG_CAPABILITIES_POINTER 0x00000034 /* CAPABILITIES_POINTER Register */ +#define PCIE_CFG_INTERRUPT 0x0000003c /* INTERRUPT Register */ +#define PCIE_CFG_VPD_CAPABILITIES 0x00000040 /* VPD_CAPABILITIES Register */ +#define PCIE_CFG_VPD_DATA 0x00000044 /* VPD_DATA Register */ +#define PCIE_CFG_POWER_MANAGEMENT_CAPABILITY 0x00000048 /* POWER_MANAGEMENT_CAPABILITY Register */ +#define PCIE_CFG_POWER_MANAGEMENT_CONTROL_STATUS 0x0000004c /* POWER_MANAGEMENT_CONTROL_STATUS Register */ +#define PCIE_CFG_MSI_CAPABILITY_HEADER 0x00000050 /* MSI_CAPABILITY_HEADER Register */ +#define PCIE_CFG_MSI_LOWER_ADDRESS 0x00000054 /* MSI_LOWER_ADDRESS Register */ +#define PCIE_CFG_MSI_UPPER_ADDRESS_REGISTER 0x00000058 /* MSI_UPPER_ADDRESS_REGISTER Register */ +#define PCIE_CFG_MSI_DATA 0x0000005c /* MSI_DATA Register */ +#define PCIE_CFG_BROADCOM_VENDOR_SPECIFIC_CAPABILITY_HEADER 0x00000060 /* BROADCOM_VENDOR_SPECIFIC_CAPABILITY_HEADER Register */ +#define PCIE_CFG_RESET_COUNTERS_INITIAL_VALUES 0x00000064 /* RESET_COUNTERS_INITIAL_VALUES Register */ +#define PCIE_CFG_MISCELLANEOUS_HOST_CONTROL 0x00000068 /* MISCELLANEOUS_HOST_CONTROL Register */ +#define PCIE_CFG_SPARE 0x0000006c /* SPARE Register */ +#define PCIE_CFG_PCI_STATE 0x00000070 /* PCI_STATE Register */ +#define PCIE_CFG_CLOCK_CONTROL 0x00000074 /* CLOCK_CONTROL Register */ +#define PCIE_CFG_REGISTER_BASE 0x00000078 /* REGISTER_BASE Register */ +#define PCIE_CFG_MEMORY_BASE 0x0000007c /* MEMORY_BASE Register */ +#define PCIE_CFG_REGISTER_DATA 0x00000080 /* REGISTER_DATA Register */ +#define PCIE_CFG_MEMORY_DATA 0x00000084 /* MEMORY_DATA Register */ +#define PCIE_CFG_EXPANSION_ROM_BAR_SIZE 0x00000088 /* EXPANSION_ROM_BAR_SIZE Register */ +#define PCIE_CFG_EXPANSION_ROM_ADDRESS 0x0000008c /* EXPANSION_ROM_ADDRESS Register */ +#define PCIE_CFG_EXPANSION_ROM_DATA 0x00000090 /* EXPANSION_ROM_DATA Register */ +#define PCIE_CFG_VPD_INTERFACE 0x00000094 /* VPD_INTERFACE Register */ +#define PCIE_CFG_UNDI_RECEIVE_BD_STANDARD_PRODUCER_RING_PRODUCER_INDEX_MAILBOX_UPPER 0x00000098 /* UNDI_RECEIVE_BD_STANDARD_PRODUCER_RING_PRODUCER_INDEX_MAILBOX_UPPER Register */ +#define PCIE_CFG_UNDI_RECEIVE_BD_STANDARD_PRODUCER_RING_PRODUCER_INDEX_MAILBOX_LOWER 0x0000009c /* UNDI_RECEIVE_BD_STANDARD_PRODUCER_RING_PRODUCER_INDEX_MAILBOX_LOWER Register */ +#define PCIE_CFG_UNDI_RECEIVE_RETURN_RING_CONSUMER_INDEX_UPPER 0x000000a0 /* UNDI_RECEIVE_RETURN_RING_CONSUMER_INDEX_UPPER Register */ +#define PCIE_CFG_UNDI_RECEIVE_RETURN_RING_CONSUMER_INDEX_LOWER 0x000000a4 /* UNDI_RECEIVE_RETURN_RING_CONSUMER_INDEX_LOWER Register */ +#define PCIE_CFG_UNDI_SEND_BD_PRODUCER_INDEX_MAILBOX_UPPER 0x000000a8 /* UNDI_SEND_BD_PRODUCER_INDEX_MAILBOX_UPPER Register */ +#define PCIE_CFG_UNDI_SEND_BD_PRODUCER_INDEX_MAILBOX_LOWER 0x000000ac /* UNDI_SEND_BD_PRODUCER_INDEX_MAILBOX_LOWER Register */ +#define PCIE_CFG_INT_MAILBOX_UPPER 0x000000b0 /* INT_MAILBOX_UPPER Register */ +#define PCIE_CFG_INT_MAILBOX_LOWER 0x000000b4 /* INT_MAILBOX_LOWER Register */ +#define PCIE_CFG_PRODUCT_ID_AND_ASIC_REVISION 0x000000bc /* PRODUCT_ID_AND_ASIC_REVISION Register */ +#define PCIE_CFG_FUNCTION_EVENT 0x000000c0 /* FUNCTION_EVENT Register */ +#define PCIE_CFG_FUNCTION_EVENT_MASK 0x000000c4 /* FUNCTION_EVENT_MASK Register */ +#define PCIE_CFG_FUNCTION_PRESENT 0x000000c8 /* FUNCTION_PRESENT Register */ +#define PCIE_CFG_PCIE_CAPABILITIES 0x000000cc /* PCIE_CAPABILITIES Register */ +#define PCIE_CFG_DEVICE_CAPABILITIES 0x000000d0 /* DEVICE_CAPABILITIES Register */ +#define PCIE_CFG_DEVICE_STATUS_CONTROL 0x000000d4 /* DEVICE_STATUS_CONTROL Register */ +#define PCIE_CFG_LINK_CAPABILITY 0x000000d8 /* LINK_CAPABILITY Register */ +#define PCIE_CFG_LINK_STATUS_CONTROL 0x000000dc /* LINK_STATUS_CONTROL Register */ +#define PCIE_CFG_DEVICE_CAPABILITIES_2 0x000000f0 /* DEVICE_CAPABILITIES_2 Register */ +#define PCIE_CFG_DEVICE_STATUS_CONTROL_2 0x000000f4 /* DEVICE_STATUS_CONTROL_2 Register */ +#define PCIE_CFG_LINK_CAPABILITIES_2 0x000000f8 /* LINK_CAPABILITIES_2 Register */ +#define PCIE_CFG_LINK_STATUS_CONTROL_2 0x000000fc /* LINK_STATUS_CONTROL_2 Register */ +#define PCIE_CFG_ADVANCED_ERROR_REPORTING_ENHANCED_CAPABILITY_HEADER 0x00000100 /* ADVANCED_ERROR_REPORTING_ENHANCED_CAPABILITY_HEADER Register */ +#define PCIE_CFG_UNCORRECTABLE_ERROR_STATUS 0x00000104 /* UNCORRECTABLE_ERROR_STATUS Register */ +#define PCIE_CFG_UNCORRECTABLE_ERROR_MASK 0x00000108 /* UNCORRECTABLE_ERROR_MASK Register */ +#define PCIE_CFG_UNCORRECTABLE_ERROR_SEVERITY 0x0000010c /* UNCORRECTABLE_ERROR_SEVERITY Register */ +#define PCIE_CFG_CORRECTABLE_ERROR_STATUS 0x00000110 /* CORRECTABLE_ERROR_STATUS Register */ +#define PCIE_CFG_CORRECTABLE_ERROR_MASK 0x00000114 /* CORRECTABLE_ERROR_MASK Register */ +#define PCIE_CFG_ADVANCED_ERROR_CAPABILITIES_AND_CONTROL 0x00000118 /* ADVANCED_ERROR_CAPABILITIES_AND_CONTROL Register */ +#define PCIE_CFG_HEADER_LOG_1 0x0000011c /* HEADER_LOG_1 Register */ +#define PCIE_CFG_HEADER_LOG_2 0x00000120 /* HEADER_LOG_2 Register */ +#define PCIE_CFG_HEADER_LOG_3 0x00000124 /* HEADER_LOG_3 Register */ +#define PCIE_CFG_HEADER_LOG_4 0x00000128 /* HEADER_LOG_4 Register */ +#define PCIE_CFG_VIRTUAL_CHANNEL_ENHANCED_CAPABILITY_HEADER 0x0000013c /* VIRTUAL_CHANNEL_ENHANCED_CAPABILITY_HEADER Register */ +#define PCIE_CFG_PORT_VC_CAPABILITY 0x00000140 /* PORT_VC_CAPABILITY Register */ +#define PCIE_CFG_PORT_VC_CAPABILITY_2 0x00000144 /* PORT_VC_CAPABILITY_2 Register */ +#define PCIE_CFG_PORT_VC_STATUS_CONTROL 0x00000148 /* PORT_VC_STATUS_CONTROL Register */ +#define PCIE_CFG_VC_RESOURCE_CAPABILITY 0x0000014c /* VC_RESOURCE_CAPABILITY Register */ +#define PCIE_CFG_VC_RESOURCE_CONTROL 0x00000150 /* VC_RESOURCE_CONTROL Register */ +#define PCIE_CFG_VC_RESOURCE_STATUS 0x00000154 /* VC_RESOURCE_STATUS Register */ +#define PCIE_CFG_DEVICE_SERIAL_NO_ENHANCED_CAPABILITY_HEADER 0x00000160 /* DEVICE_SERIAL_NO_ENHANCED_CAPABILITY_HEADER Register */ +#define PCIE_CFG_DEVICE_SERIAL_NO_LOWER_DW 0x00000164 /* DEVICE_SERIAL_NO_LOWER_DW Register */ +#define PCIE_CFG_DEVICE_SERIAL_NO_UPPER_DW 0x00000168 /* DEVICE_SERIAL_NO_UPPER_DW Register */ +#define PCIE_CFG_POWER_BUDGETING_ENHANCED_CAPABILITY_HEADER 0x0000016c /* POWER_BUDGETING_ENHANCED_CAPABILITY_HEADER Register */ +#define PCIE_CFG_POWER_BUDGETING_DATA_SELECT 0x00000170 /* POWER_BUDGETING_DATA_SELECT Register */ +#define PCIE_CFG_POWER_BUDGETING_DATA 0x00000174 /* POWER_BUDGETING_DATA Register */ +#define PCIE_CFG_POWER_BUDGETING_CAPABILITY 0x00000178 /* POWER_BUDGETING_CAPABILITY Register */ +#define PCIE_CFG_FIRMWARE_POWER_BUDGETING_2_1 0x0000017c /* FIRMWARE_POWER_BUDGETING_2_1 Register */ +#define PCIE_CFG_FIRMWARE_POWER_BUDGETING_4_3 0x00000180 /* FIRMWARE_POWER_BUDGETING_4_3 Register */ +#define PCIE_CFG_FIRMWARE_POWER_BUDGETING_6_5 0x00000184 /* FIRMWARE_POWER_BUDGETING_6_5 Register */ +#define PCIE_CFG_FIRMWARE_POWER_BUDGETING_8_7 0x00000188 /* FIRMWARE_POWER_BUDGETING_8_7 Register */ +#define PCIE_CFG_PCIE_1_1_ADVISORY_NON_FATAL_ERROR_MASKING 0x0000018c /* PCIE_1_1_ADVISORY_NON_FATAL_ERROR_MASKING Register */ + + +/**************************************************************************** + * BCM70012_TGT_TOP_PCIE_TL + ***************************************************************************/ +#define PCIE_TL_TL_CONTROL 0x00000400 /* TL_CONTROL Register */ +#define PCIE_TL_TRANSACTION_CONFIGURATION 0x00000404 /* TRANSACTION_CONFIGURATION Register */ + + +/**************************************************************************** + * BCM70012_TGT_TOP_PCIE_DLL + ***************************************************************************/ +#define PCIE_DLL_DATA_LINK_CONTROL 0x00000500 /* DATA_LINK_CONTROL Register */ +#define PCIE_DLL_DATA_LINK_STATUS 0x00000504 /* DATA_LINK_STATUS Register */ + + +/**************************************************************************** + * BCM70012_TGT_TOP_INTR + ***************************************************************************/ +#define INTR_INTR_STATUS 0x00000700 /* Interrupt Status Register */ +#define INTR_INTR_SET 0x00000704 /* Interrupt Set Register */ +#define INTR_INTR_CLR_REG 0x00000708 /* Interrupt Clear Register */ +#define INTR_INTR_MSK_STS_REG 0x0000070c /* Interrupt Mask Status Register */ +#define INTR_INTR_MSK_SET_REG 0x00000710 /* Interrupt Mask Set Register */ +#define INTR_INTR_MSK_CLR_REG 0x00000714 /* Interrupt Mask Clear Register */ +#define INTR_EOI_CTRL 0x00000720 /* End of interrupt control register */ + + +/**************************************************************************** + * BCM70012_MISC_TOP_MISC1 + ***************************************************************************/ +#define MISC1_TX_FIRST_DESC_L_ADDR_LIST0 0x00000c00 /* Tx DMA Descriptor List0 First Descriptor lower Address */ +#define MISC1_TX_FIRST_DESC_U_ADDR_LIST0 0x00000c04 /* Tx DMA Descriptor List0 First Descriptor Upper Address */ +#define MISC1_TX_FIRST_DESC_L_ADDR_LIST1 0x00000c08 /* Tx DMA Descriptor List1 First Descriptor Lower Address */ +#define MISC1_TX_FIRST_DESC_U_ADDR_LIST1 0x00000c0c /* Tx DMA Descriptor List1 First Descriptor Upper Address */ +#define MISC1_TX_SW_DESC_LIST_CTRL_STS 0x00000c10 /* Tx DMA Software Descriptor List Control and Status */ +#define MISC1_TX_DMA_ERROR_STATUS 0x00000c18 /* Tx DMA Engine Error Status */ +#define MISC1_TX_DMA_LIST0_CUR_DESC_L_ADDR 0x00000c1c /* Tx DMA List0 Current Descriptor Lower Address */ +#define MISC1_TX_DMA_LIST0_CUR_DESC_U_ADDR 0x00000c20 /* Tx DMA List0 Current Descriptor Upper Address */ +#define MISC1_TX_DMA_LIST0_CUR_BYTE_CNT_REM 0x00000c24 /* Tx DMA List0 Current Descriptor Upper Address */ +#define MISC1_TX_DMA_LIST1_CUR_DESC_L_ADDR 0x00000c28 /* Tx DMA List1 Current Descriptor Lower Address */ +#define MISC1_TX_DMA_LIST1_CUR_DESC_U_ADDR 0x00000c2c /* Tx DMA List1 Current Descriptor Upper Address */ +#define MISC1_TX_DMA_LIST1_CUR_BYTE_CNT_REM 0x00000c30 /* Tx DMA List1 Current Descriptor Upper Address */ +#define MISC1_Y_RX_FIRST_DESC_L_ADDR_LIST0 0x00000c34 /* Y Rx Descriptor List0 First Descriptor Lower Address */ +#define MISC1_Y_RX_FIRST_DESC_U_ADDR_LIST0 0x00000c38 /* Y Rx Descriptor List0 First Descriptor Upper Address */ +#define MISC1_Y_RX_FIRST_DESC_L_ADDR_LIST1 0x00000c3c /* Y Rx Descriptor List1 First Descriptor Lower Address */ +#define MISC1_Y_RX_FIRST_DESC_U_ADDR_LIST1 0x00000c40 /* Y Rx Descriptor List1 First Descriptor Upper Address */ +#define MISC1_Y_RX_SW_DESC_LIST_CTRL_STS 0x00000c44 /* Y Rx Software Descriptor List Control and Status */ +#define MISC1_Y_RX_ERROR_STATUS 0x00000c4c /* Y Rx Engine Error Status */ +#define MISC1_Y_RX_LIST0_CUR_DESC_L_ADDR 0x00000c50 /* Y Rx List0 Current Descriptor Lower Address */ +#define MISC1_Y_RX_LIST0_CUR_DESC_U_ADDR 0x00000c54 /* Y Rx List0 Current Descriptor Upper Address */ +#define MISC1_Y_RX_LIST0_CUR_BYTE_CNT 0x00000c58 /* Y Rx List0 Current Descriptor Byte Count */ +#define MISC1_Y_RX_LIST1_CUR_DESC_L_ADDR 0x00000c5c /* Y Rx List1 Current Descriptor Lower address */ +#define MISC1_Y_RX_LIST1_CUR_DESC_U_ADDR 0x00000c60 /* Y Rx List1 Current Descriptor Upper address */ +#define MISC1_Y_RX_LIST1_CUR_BYTE_CNT 0x00000c64 /* Y Rx List1 Current Descriptor Byte Count */ +#define MISC1_UV_RX_FIRST_DESC_L_ADDR_LIST0 0x00000c68 /* UV Rx Descriptor List0 First Descriptor lower Address */ +#define MISC1_UV_RX_FIRST_DESC_U_ADDR_LIST0 0x00000c6c /* UV Rx Descriptor List0 First Descriptor Upper Address */ +#define MISC1_UV_RX_FIRST_DESC_L_ADDR_LIST1 0x00000c70 /* UV Rx Descriptor List1 First Descriptor Lower Address */ +#define MISC1_UV_RX_FIRST_DESC_U_ADDR_LIST1 0x00000c74 /* UV Rx Descriptor List1 First Descriptor Upper Address */ +#define MISC1_UV_RX_SW_DESC_LIST_CTRL_STS 0x00000c78 /* UV Rx Software Descriptor List Control and Status */ +#define MISC1_UV_RX_ERROR_STATUS 0x00000c7c /* UV Rx Engine Error Status */ +#define MISC1_UV_RX_LIST0_CUR_DESC_L_ADDR 0x00000c80 /* UV Rx List0 Current Descriptor Lower Address */ +#define MISC1_UV_RX_LIST0_CUR_DESC_U_ADDR 0x00000c84 /* UV Rx List0 Current Descriptor Upper Address */ +#define MISC1_UV_RX_LIST0_CUR_BYTE_CNT 0x00000c88 /* UV Rx List0 Current Descriptor Byte Count */ +#define MISC1_UV_RX_LIST1_CUR_DESC_L_ADDR 0x00000c8c /* UV Rx List1 Current Descriptor Lower Address */ +#define MISC1_UV_RX_LIST1_CUR_DESC_U_ADDR 0x00000c90 /* UV Rx List1 Current Descriptor Upper Address */ +#define MISC1_UV_RX_LIST1_CUR_BYTE_CNT 0x00000c94 /* UV Rx List1 Current Descriptor Byte Count */ +#define MISC1_DMA_DEBUG_OPTIONS_REG 0x00000c98 /* DMA Debug Options Register */ +#define MISC1_READ_CHANNEL_ERROR_STATUS 0x00000c9c /* Read Channel Error Status */ +#define MISC1_PCIE_DMA_CTRL 0x00000ca0 /* PCIE DMA Control Register */ + + +/**************************************************************************** + * BCM70012_MISC_TOP_MISC2 + ***************************************************************************/ +#define MISC2_GLOBAL_CTRL 0x00000d00 /* Global Control Register */ +#define MISC2_INTERNAL_STATUS 0x00000d04 /* Internal Status Register */ +#define MISC2_INTERNAL_STATUS_MUX_CTRL 0x00000d08 /* Internal Debug Mux Control */ +#define MISC2_DEBUG_FIFO_LENGTH 0x00000d0c /* Debug FIFO Length */ + + +/**************************************************************************** + * BCM70012_MISC_TOP_MISC3 + ***************************************************************************/ +#define MISC3_RESET_CTRL 0x00000e00 /* Reset Control Register */ +#define MISC3_BIST_CTRL 0x00000e04 /* BIST Control Register */ +#define MISC3_BIST_STATUS 0x00000e08 /* BIST Status Register */ +#define MISC3_RX_CHECKSUM 0x00000e0c /* Receive Checksum */ +#define MISC3_TX_CHECKSUM 0x00000e10 /* Transmit Checksum */ +#define MISC3_ECO_CTRL_CORE 0x00000e14 /* ECO Core Reset Control Register */ +#define MISC3_CSI_TEST_CTRL 0x00000e18 /* CSI Test Control Register */ +#define MISC3_HD_DVI_TEST_CTRL 0x00000e1c /* HD DVI Test Control Register */ + + +/**************************************************************************** + * BCM70012_MISC_TOP_MISC_PERST + ***************************************************************************/ +#define MISC_PERST_ECO_CTRL_PERST 0x00000e80 /* ECO PCIE Reset Control Register */ +#define MISC_PERST_DECODER_CTRL 0x00000e84 /* Decoder Control Register */ +#define MISC_PERST_CCE_STATUS 0x00000e88 /* Config Copy Engine Status */ +#define MISC_PERST_PCIE_DEBUG 0x00000e8c /* PCIE Debug Control Register */ +#define MISC_PERST_PCIE_DEBUG_STATUS 0x00000e90 /* PCIE Debug Status Register */ +#define MISC_PERST_VREG_CTRL 0x00000e94 /* Voltage Regulator Control Register */ +#define MISC_PERST_MEM_CTRL 0x00000e98 /* Memory Control Register */ +#define MISC_PERST_CLOCK_CTRL 0x00000e9c /* Clock Control Register */ + + +/**************************************************************************** + * BCM70012_MISC_TOP_GISB_ARBITER + ***************************************************************************/ +#define GISB_ARBITER_REVISION 0x00000f00 /* GISB ARBITER REVISION */ +#define GISB_ARBITER_SCRATCH 0x00000f04 /* GISB ARBITER Scratch Register */ +#define GISB_ARBITER_REQ_MASK 0x00000f08 /* GISB ARBITER Master Request Mask Register */ +#define GISB_ARBITER_TIMER 0x00000f0c /* GISB ARBITER Timer Value Register */ + + +/**************************************************************************** + * BCM70012_OTP_TOP_OTP + ***************************************************************************/ +#define OTP_CONFIG_INFO 0x00001400 /* OTP Configuration Register */ +#define OTP_CMD 0x00001404 /* OTP Command Register */ +#define OTP_STATUS 0x00001408 /* OTP Status Register */ +#define OTP_CONTENT_MISC 0x0000140c /* Content : Miscellaneous Register */ +#define OTP_CONTENT_AES_0 0x00001410 /* Content : AES Key 0 Register */ +#define OTP_CONTENT_AES_1 0x00001414 /* Content : AES Key 1 Register */ +#define OTP_CONTENT_AES_2 0x00001418 /* Content : AES Key 2 Register */ +#define OTP_CONTENT_AES_3 0x0000141c /* Content : AES Key 3 Register */ +#define OTP_CONTENT_SHA_0 0x00001420 /* Content : SHA Key 0 Register */ +#define OTP_CONTENT_SHA_1 0x00001424 /* Content : SHA Key 1 Register */ +#define OTP_CONTENT_SHA_2 0x00001428 /* Content : SHA Key 2 Register */ +#define OTP_CONTENT_SHA_3 0x0000142c /* Content : SHA Key 3 Register */ +#define OTP_CONTENT_SHA_4 0x00001430 /* Content : SHA Key 4 Register */ +#define OTP_CONTENT_SHA_5 0x00001434 /* Content : SHA Key 5 Register */ +#define OTP_CONTENT_SHA_6 0x00001438 /* Content : SHA Key 6 Register */ +#define OTP_CONTENT_SHA_7 0x0000143c /* Content : SHA Key 7 Register */ +#define OTP_CONTENT_CHECKSUM 0x00001440 /* Content : Checksum Register */ +#define OTP_PROG_CTRL 0x00001444 /* Programming Control Register */ +#define OTP_PROG_STATUS 0x00001448 /* Programming Status Register */ +#define OTP_PROG_PULSE 0x0000144c /* Program Pulse Width Register */ +#define OTP_VERIFY_PULSE 0x00001450 /* Verify Pulse Width Register */ +#define OTP_PROG_MASK 0x00001454 /* Program Mask Register */ +#define OTP_DATA_INPUT 0x00001458 /* Data Input Register */ +#define OTP_DATA_OUTPUT 0x0000145c /* Data Output Register */ + + +/**************************************************************************** + * BCM70012_AES_TOP_AES + ***************************************************************************/ +#define AES_CONFIG_INFO 0x00001800 /* AES Configuration Information Register */ +#define AES_CMD 0x00001804 /* AES Command Register */ +#define AES_STATUS 0x00001808 /* AES Status Register */ +#define AES_EEPROM_CONFIG 0x0000180c /* AES EEPROM Configuration Register */ +#define AES_EEPROM_DATA_0 0x00001810 /* AES EEPROM Data Register 0 */ +#define AES_EEPROM_DATA_1 0x00001814 /* AES EEPROM Data Register 1 */ +#define AES_EEPROM_DATA_2 0x00001818 /* AES EEPROM Data Register 2 */ +#define AES_EEPROM_DATA_3 0x0000181c /* AES EEPROM Data Register 3 */ + + +/**************************************************************************** + * BCM70012_DCI_TOP_DCI + ***************************************************************************/ +#define DCI_CMD 0x00001c00 /* DCI Command Register */ +#define DCI_STATUS 0x00001c04 /* DCI Status Register */ +#define DCI_DRAM_BASE_ADDR 0x00001c08 /* DRAM Base Address Register */ +#define DCI_FIRMWARE_ADDR 0x00001c0c /* Firmware Address Register */ +#define DCI_FIRMWARE_DATA 0x00001c10 /* Firmware Data Register */ +#define DCI_SIGNATURE_DATA_0 0x00001c14 /* Signature Data Register 0 */ +#define DCI_SIGNATURE_DATA_1 0x00001c18 /* Signature Data Register 1 */ +#define DCI_SIGNATURE_DATA_2 0x00001c1c /* Signature Data Register 2 */ +#define DCI_SIGNATURE_DATA_3 0x00001c20 /* Signature Data Register 3 */ +#define DCI_SIGNATURE_DATA_4 0x00001c24 /* Signature Data Register 4 */ +#define DCI_SIGNATURE_DATA_5 0x00001c28 /* Signature Data Register 5 */ +#define DCI_SIGNATURE_DATA_6 0x00001c2c /* Signature Data Register 6 */ +#define DCI_SIGNATURE_DATA_7 0x00001c30 /* Signature Data Register 7 */ + + +/**************************************************************************** + * BCM70012_TGT_TOP_INTR + ***************************************************************************/ +/**************************************************************************** + * INTR :: INTR_STATUS + ***************************************************************************/ +/* INTR :: INTR_STATUS :: reserved0 [31:26] */ +#define INTR_INTR_STATUS_reserved0_MASK 0xfc000000 +#define INTR_INTR_STATUS_reserved0_ALIGN 0 +#define INTR_INTR_STATUS_reserved0_BITS 6 +#define INTR_INTR_STATUS_reserved0_SHIFT 26 + +/* INTR :: INTR_STATUS :: PCIE_TGT_CA_ATTN [25:25] */ +#define INTR_INTR_STATUS_PCIE_TGT_CA_ATTN_MASK 0x02000000 +#define INTR_INTR_STATUS_PCIE_TGT_CA_ATTN_ALIGN 0 +#define INTR_INTR_STATUS_PCIE_TGT_CA_ATTN_BITS 1 +#define INTR_INTR_STATUS_PCIE_TGT_CA_ATTN_SHIFT 25 + +/* INTR :: INTR_STATUS :: PCIE_TGT_UR_ATTN [24:24] */ +#define INTR_INTR_STATUS_PCIE_TGT_UR_ATTN_MASK 0x01000000 +#define INTR_INTR_STATUS_PCIE_TGT_UR_ATTN_ALIGN 0 +#define INTR_INTR_STATUS_PCIE_TGT_UR_ATTN_BITS 1 +#define INTR_INTR_STATUS_PCIE_TGT_UR_ATTN_SHIFT 24 + +/* INTR :: INTR_STATUS :: reserved1 [23:14] */ +#define INTR_INTR_STATUS_reserved1_MASK 0x00ffc000 +#define INTR_INTR_STATUS_reserved1_ALIGN 0 +#define INTR_INTR_STATUS_reserved1_BITS 10 +#define INTR_INTR_STATUS_reserved1_SHIFT 14 + +/* INTR :: INTR_STATUS :: L1_UV_RX_DMA_ERR_INTR [13:13] */ +#define INTR_INTR_STATUS_L1_UV_RX_DMA_ERR_INTR_MASK 0x00002000 +#define INTR_INTR_STATUS_L1_UV_RX_DMA_ERR_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L1_UV_RX_DMA_ERR_INTR_BITS 1 +#define INTR_INTR_STATUS_L1_UV_RX_DMA_ERR_INTR_SHIFT 13 + +/* INTR :: INTR_STATUS :: L1_UV_RX_DMA_DONE_INTR [12:12] */ +#define INTR_INTR_STATUS_L1_UV_RX_DMA_DONE_INTR_MASK 0x00001000 +#define INTR_INTR_STATUS_L1_UV_RX_DMA_DONE_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L1_UV_RX_DMA_DONE_INTR_BITS 1 +#define INTR_INTR_STATUS_L1_UV_RX_DMA_DONE_INTR_SHIFT 12 + +/* INTR :: INTR_STATUS :: L1_Y_RX_DMA_ERR_INTR [11:11] */ +#define INTR_INTR_STATUS_L1_Y_RX_DMA_ERR_INTR_MASK 0x00000800 +#define INTR_INTR_STATUS_L1_Y_RX_DMA_ERR_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L1_Y_RX_DMA_ERR_INTR_BITS 1 +#define INTR_INTR_STATUS_L1_Y_RX_DMA_ERR_INTR_SHIFT 11 + +/* INTR :: INTR_STATUS :: L1_Y_RX_DMA_DONE_INTR [10:10] */ +#define INTR_INTR_STATUS_L1_Y_RX_DMA_DONE_INTR_MASK 0x00000400 +#define INTR_INTR_STATUS_L1_Y_RX_DMA_DONE_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L1_Y_RX_DMA_DONE_INTR_BITS 1 +#define INTR_INTR_STATUS_L1_Y_RX_DMA_DONE_INTR_SHIFT 10 + +/* INTR :: INTR_STATUS :: L1_TX_DMA_ERR_INTR [09:09] */ +#define INTR_INTR_STATUS_L1_TX_DMA_ERR_INTR_MASK 0x00000200 +#define INTR_INTR_STATUS_L1_TX_DMA_ERR_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L1_TX_DMA_ERR_INTR_BITS 1 +#define INTR_INTR_STATUS_L1_TX_DMA_ERR_INTR_SHIFT 9 + +/* INTR :: INTR_STATUS :: L1_TX_DMA_DONE_INTR [08:08] */ +#define INTR_INTR_STATUS_L1_TX_DMA_DONE_INTR_MASK 0x00000100 +#define INTR_INTR_STATUS_L1_TX_DMA_DONE_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L1_TX_DMA_DONE_INTR_BITS 1 +#define INTR_INTR_STATUS_L1_TX_DMA_DONE_INTR_SHIFT 8 + +/* INTR :: INTR_STATUS :: reserved2 [07:06] */ +#define INTR_INTR_STATUS_reserved2_MASK 0x000000c0 +#define INTR_INTR_STATUS_reserved2_ALIGN 0 +#define INTR_INTR_STATUS_reserved2_BITS 2 +#define INTR_INTR_STATUS_reserved2_SHIFT 6 + +/* INTR :: INTR_STATUS :: L0_UV_RX_DMA_ERR_INTR [05:05] */ +#define INTR_INTR_STATUS_L0_UV_RX_DMA_ERR_INTR_MASK 0x00000020 +#define INTR_INTR_STATUS_L0_UV_RX_DMA_ERR_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L0_UV_RX_DMA_ERR_INTR_BITS 1 +#define INTR_INTR_STATUS_L0_UV_RX_DMA_ERR_INTR_SHIFT 5 + +/* INTR :: INTR_STATUS :: L0_UV_RX_DMA_DONE_INTR [04:04] */ +#define INTR_INTR_STATUS_L0_UV_RX_DMA_DONE_INTR_MASK 0x00000010 +#define INTR_INTR_STATUS_L0_UV_RX_DMA_DONE_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L0_UV_RX_DMA_DONE_INTR_BITS 1 +#define INTR_INTR_STATUS_L0_UV_RX_DMA_DONE_INTR_SHIFT 4 + +/* INTR :: INTR_STATUS :: L0_Y_RX_DMA_ERR_INTR [03:03] */ +#define INTR_INTR_STATUS_L0_Y_RX_DMA_ERR_INTR_MASK 0x00000008 +#define INTR_INTR_STATUS_L0_Y_RX_DMA_ERR_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L0_Y_RX_DMA_ERR_INTR_BITS 1 +#define INTR_INTR_STATUS_L0_Y_RX_DMA_ERR_INTR_SHIFT 3 + +/* INTR :: INTR_STATUS :: L0_Y_RX_DMA_DONE_INTR [02:02] */ +#define INTR_INTR_STATUS_L0_Y_RX_DMA_DONE_INTR_MASK 0x00000004 +#define INTR_INTR_STATUS_L0_Y_RX_DMA_DONE_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L0_Y_RX_DMA_DONE_INTR_BITS 1 +#define INTR_INTR_STATUS_L0_Y_RX_DMA_DONE_INTR_SHIFT 2 + +/* INTR :: INTR_STATUS :: L0_TX_DMA_ERR_INTR [01:01] */ +#define INTR_INTR_STATUS_L0_TX_DMA_ERR_INTR_MASK 0x00000002 +#define INTR_INTR_STATUS_L0_TX_DMA_ERR_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L0_TX_DMA_ERR_INTR_BITS 1 +#define INTR_INTR_STATUS_L0_TX_DMA_ERR_INTR_SHIFT 1 + +/* INTR :: INTR_STATUS :: L0_TX_DMA_DONE_INTR [00:00] */ +#define INTR_INTR_STATUS_L0_TX_DMA_DONE_INTR_MASK 0x00000001 +#define INTR_INTR_STATUS_L0_TX_DMA_DONE_INTR_ALIGN 0 +#define INTR_INTR_STATUS_L0_TX_DMA_DONE_INTR_BITS 1 +#define INTR_INTR_STATUS_L0_TX_DMA_DONE_INTR_SHIFT 0 + + +/**************************************************************************** + * MISC1 :: TX_SW_DESC_LIST_CTRL_STS + ***************************************************************************/ +/* MISC1 :: TX_SW_DESC_LIST_CTRL_STS :: reserved0 [31:04] */ +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_reserved0_MASK 0xfffffff0 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_reserved0_ALIGN 0 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_reserved0_BITS 28 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_reserved0_SHIFT 4 + +/* MISC1 :: TX_SW_DESC_LIST_CTRL_STS :: DMA_DATA_SERV_PTR [03:03] */ +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_DMA_DATA_SERV_PTR_MASK 0x00000008 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_DMA_DATA_SERV_PTR_ALIGN 0 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_DMA_DATA_SERV_PTR_BITS 1 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_DMA_DATA_SERV_PTR_SHIFT 3 + +/* MISC1 :: TX_SW_DESC_LIST_CTRL_STS :: DESC_SERV_PTR [02:02] */ +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_DESC_SERV_PTR_MASK 0x00000004 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_DESC_SERV_PTR_ALIGN 0 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_DESC_SERV_PTR_BITS 1 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_DESC_SERV_PTR_SHIFT 2 + +/* MISC1 :: TX_SW_DESC_LIST_CTRL_STS :: TX_DMA_HALT_ON_ERROR [01:01] */ +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_TX_DMA_HALT_ON_ERROR_MASK 0x00000002 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_TX_DMA_HALT_ON_ERROR_ALIGN 0 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_TX_DMA_HALT_ON_ERROR_BITS 1 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_TX_DMA_HALT_ON_ERROR_SHIFT 1 + +/* MISC1 :: TX_SW_DESC_LIST_CTRL_STS :: TX_DMA_RUN_STOP [00:00] */ +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_TX_DMA_RUN_STOP_MASK 0x00000001 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_TX_DMA_RUN_STOP_ALIGN 0 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_TX_DMA_RUN_STOP_BITS 1 +#define MISC1_TX_SW_DESC_LIST_CTRL_STS_TX_DMA_RUN_STOP_SHIFT 0 + + +/**************************************************************************** + * MISC1 :: TX_DMA_ERROR_STATUS + ***************************************************************************/ +/* MISC1 :: TX_DMA_ERROR_STATUS :: reserved0 [31:10] */ +#define MISC1_TX_DMA_ERROR_STATUS_reserved0_MASK 0xfffffc00 +#define MISC1_TX_DMA_ERROR_STATUS_reserved0_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_reserved0_BITS 22 +#define MISC1_TX_DMA_ERROR_STATUS_reserved0_SHIFT 10 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: TX_L1_DESC_TX_ABORT_ERRORS [09:09] */ +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_DESC_TX_ABORT_ERRORS_MASK 0x00000200 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_DESC_TX_ABORT_ERRORS_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_DESC_TX_ABORT_ERRORS_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_DESC_TX_ABORT_ERRORS_SHIFT 9 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: reserved1 [08:08] */ +#define MISC1_TX_DMA_ERROR_STATUS_reserved1_MASK 0x00000100 +#define MISC1_TX_DMA_ERROR_STATUS_reserved1_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_reserved1_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_reserved1_SHIFT 8 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: TX_L0_DESC_TX_ABORT_ERRORS [07:07] */ +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_DESC_TX_ABORT_ERRORS_MASK 0x00000080 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_DESC_TX_ABORT_ERRORS_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_DESC_TX_ABORT_ERRORS_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_DESC_TX_ABORT_ERRORS_SHIFT 7 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: reserved2 [06:06] */ +#define MISC1_TX_DMA_ERROR_STATUS_reserved2_MASK 0x00000040 +#define MISC1_TX_DMA_ERROR_STATUS_reserved2_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_reserved2_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_reserved2_SHIFT 6 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: TX_L1_DMA_DATA_TX_ABORT_ERRORS [05:05] */ +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_DMA_DATA_TX_ABORT_ERRORS_MASK 0x00000020 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_DMA_DATA_TX_ABORT_ERRORS_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_DMA_DATA_TX_ABORT_ERRORS_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_DMA_DATA_TX_ABORT_ERRORS_SHIFT 5 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: TX_L1_FIFO_FULL_ERRORS [04:04] */ +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_FIFO_FULL_ERRORS_MASK 0x00000010 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_FIFO_FULL_ERRORS_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_FIFO_FULL_ERRORS_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L1_FIFO_FULL_ERRORS_SHIFT 4 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: reserved3 [03:03] */ +#define MISC1_TX_DMA_ERROR_STATUS_reserved3_MASK 0x00000008 +#define MISC1_TX_DMA_ERROR_STATUS_reserved3_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_reserved3_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_reserved3_SHIFT 3 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: TX_L0_DMA_DATA_TX_ABORT_ERRORS [02:02] */ +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_DMA_DATA_TX_ABORT_ERRORS_MASK 0x00000004 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_DMA_DATA_TX_ABORT_ERRORS_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_DMA_DATA_TX_ABORT_ERRORS_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_DMA_DATA_TX_ABORT_ERRORS_SHIFT 2 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: TX_L0_FIFO_FULL_ERRORS [01:01] */ +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_FIFO_FULL_ERRORS_MASK 0x00000002 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_FIFO_FULL_ERRORS_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_FIFO_FULL_ERRORS_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_TX_L0_FIFO_FULL_ERRORS_SHIFT 1 + +/* MISC1 :: TX_DMA_ERROR_STATUS :: reserved4 [00:00] */ +#define MISC1_TX_DMA_ERROR_STATUS_reserved4_MASK 0x00000001 +#define MISC1_TX_DMA_ERROR_STATUS_reserved4_ALIGN 0 +#define MISC1_TX_DMA_ERROR_STATUS_reserved4_BITS 1 +#define MISC1_TX_DMA_ERROR_STATUS_reserved4_SHIFT 0 + + +/**************************************************************************** + * MISC1 :: Y_RX_ERROR_STATUS + ***************************************************************************/ +/* MISC1 :: Y_RX_ERROR_STATUS :: reserved0 [31:14] */ +#define MISC1_Y_RX_ERROR_STATUS_reserved0_MASK 0xffffc000 +#define MISC1_Y_RX_ERROR_STATUS_reserved0_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_reserved0_BITS 18 +#define MISC1_Y_RX_ERROR_STATUS_reserved0_SHIFT 14 + +/* MISC1 :: Y_RX_ERROR_STATUS :: RX_L1_UNDERRUN_ERROR [13:13] */ +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_MASK 0x00002000 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_SHIFT 13 + +/* MISC1 :: Y_RX_ERROR_STATUS :: RX_L1_OVERRUN_ERROR [12:12] */ +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_MASK 0x00001000 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_SHIFT 12 + +/* MISC1 :: Y_RX_ERROR_STATUS :: RX_L0_UNDERRUN_ERROR [11:11] */ +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_MASK 0x00000800 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_SHIFT 11 + +/* MISC1 :: Y_RX_ERROR_STATUS :: RX_L0_OVERRUN_ERROR [10:10] */ +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_MASK 0x00000400 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_SHIFT 10 + +/* MISC1 :: Y_RX_ERROR_STATUS :: RX_L1_DESC_TX_ABORT_ERRORS [09:09] */ +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_MASK 0x00000200 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_SHIFT 9 + +/* MISC1 :: Y_RX_ERROR_STATUS :: reserved1 [08:08] */ +#define MISC1_Y_RX_ERROR_STATUS_reserved1_MASK 0x00000100 +#define MISC1_Y_RX_ERROR_STATUS_reserved1_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_reserved1_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_reserved1_SHIFT 8 + +/* MISC1 :: Y_RX_ERROR_STATUS :: RX_L0_DESC_TX_ABORT_ERRORS [07:07] */ +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_MASK 0x00000080 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_SHIFT 7 + +/* MISC1 :: Y_RX_ERROR_STATUS :: reserved2 [06:05] */ +#define MISC1_Y_RX_ERROR_STATUS_reserved2_MASK 0x00000060 +#define MISC1_Y_RX_ERROR_STATUS_reserved2_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_reserved2_BITS 2 +#define MISC1_Y_RX_ERROR_STATUS_reserved2_SHIFT 5 + +/* MISC1 :: Y_RX_ERROR_STATUS :: RX_L1_FIFO_FULL_ERRORS [04:04] */ +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_MASK 0x00000010 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_SHIFT 4 + +/* MISC1 :: Y_RX_ERROR_STATUS :: reserved3 [03:02] */ +#define MISC1_Y_RX_ERROR_STATUS_reserved3_MASK 0x0000000c +#define MISC1_Y_RX_ERROR_STATUS_reserved3_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_reserved3_BITS 2 +#define MISC1_Y_RX_ERROR_STATUS_reserved3_SHIFT 2 + +/* MISC1 :: Y_RX_ERROR_STATUS :: RX_L0_FIFO_FULL_ERRORS [01:01] */ +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_MASK 0x00000002 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_SHIFT 1 + +/* MISC1 :: Y_RX_ERROR_STATUS :: reserved4 [00:00] */ +#define MISC1_Y_RX_ERROR_STATUS_reserved4_MASK 0x00000001 +#define MISC1_Y_RX_ERROR_STATUS_reserved4_ALIGN 0 +#define MISC1_Y_RX_ERROR_STATUS_reserved4_BITS 1 +#define MISC1_Y_RX_ERROR_STATUS_reserved4_SHIFT 0 + + +/**************************************************************************** + * MISC1 :: UV_RX_ERROR_STATUS + ***************************************************************************/ +/* MISC1 :: UV_RX_ERROR_STATUS :: reserved0 [31:14] */ +#define MISC1_UV_RX_ERROR_STATUS_reserved0_MASK 0xffffc000 +#define MISC1_UV_RX_ERROR_STATUS_reserved0_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_reserved0_BITS 18 +#define MISC1_UV_RX_ERROR_STATUS_reserved0_SHIFT 14 + +/* MISC1 :: UV_RX_ERROR_STATUS :: RX_L1_UNDERRUN_ERROR [13:13] */ +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_MASK 0x00002000 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_SHIFT 13 + +/* MISC1 :: UV_RX_ERROR_STATUS :: RX_L1_OVERRUN_ERROR [12:12] */ +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_MASK 0x00001000 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_SHIFT 12 + +/* MISC1 :: UV_RX_ERROR_STATUS :: RX_L0_UNDERRUN_ERROR [11:11] */ +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_MASK 0x00000800 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_SHIFT 11 + +/* MISC1 :: UV_RX_ERROR_STATUS :: RX_L0_OVERRUN_ERROR [10:10] */ +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_MASK 0x00000400 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_SHIFT 10 + +/* MISC1 :: UV_RX_ERROR_STATUS :: RX_L1_DESC_TX_ABORT_ERRORS [09:09] */ +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_MASK 0x00000200 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_SHIFT 9 + +/* MISC1 :: UV_RX_ERROR_STATUS :: reserved1 [08:08] */ +#define MISC1_UV_RX_ERROR_STATUS_reserved1_MASK 0x00000100 +#define MISC1_UV_RX_ERROR_STATUS_reserved1_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_reserved1_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_reserved1_SHIFT 8 + +/* MISC1 :: UV_RX_ERROR_STATUS :: RX_L0_DESC_TX_ABORT_ERRORS [07:07] */ +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_MASK 0x00000080 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_SHIFT 7 + +/* MISC1 :: UV_RX_ERROR_STATUS :: reserved2 [06:05] */ +#define MISC1_UV_RX_ERROR_STATUS_reserved2_MASK 0x00000060 +#define MISC1_UV_RX_ERROR_STATUS_reserved2_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_reserved2_BITS 2 +#define MISC1_UV_RX_ERROR_STATUS_reserved2_SHIFT 5 + +/* MISC1 :: UV_RX_ERROR_STATUS :: RX_L1_FIFO_FULL_ERRORS [04:04] */ +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_MASK 0x00000010 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_SHIFT 4 + +/* MISC1 :: UV_RX_ERROR_STATUS :: reserved3 [03:02] */ +#define MISC1_UV_RX_ERROR_STATUS_reserved3_MASK 0x0000000c +#define MISC1_UV_RX_ERROR_STATUS_reserved3_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_reserved3_BITS 2 +#define MISC1_UV_RX_ERROR_STATUS_reserved3_SHIFT 2 + +/* MISC1 :: UV_RX_ERROR_STATUS :: RX_L0_FIFO_FULL_ERRORS [01:01] */ +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_MASK 0x00000002 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_SHIFT 1 + +/* MISC1 :: UV_RX_ERROR_STATUS :: reserved4 [00:00] */ +#define MISC1_UV_RX_ERROR_STATUS_reserved4_MASK 0x00000001 +#define MISC1_UV_RX_ERROR_STATUS_reserved4_ALIGN 0 +#define MISC1_UV_RX_ERROR_STATUS_reserved4_BITS 1 +#define MISC1_UV_RX_ERROR_STATUS_reserved4_SHIFT 0 + +/**************************************************************************** + * Datatype Definitions. + ***************************************************************************/ +#endif /* #ifndef MACFILE_H__ */ + +/* End of File */ + diff --git a/drivers/staging/crystalhd/crystalhd.h b/drivers/staging/crystalhd/crystalhd.h new file mode 100644 index 00000000..b3a550bd --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd.h @@ -0,0 +1,13 @@ +#ifndef _CRYSTALHD_H_ +#define _CRYSTALHD_H_ + +#include "bc_dts_defs.h" +#include "crystalhd_misc.h" +#include "bc_dts_glob_lnx.h" +#include "crystalhd_hw.h" +#include "crystalhd_cmds.h" +#include "crystalhd_lnx.h" +#include "bcm_70012_regs.h" +#include "crystalhd_fw_if.h" + +#endif diff --git a/drivers/staging/crystalhd/crystalhd_cmds.c b/drivers/staging/crystalhd/crystalhd_cmds.c new file mode 100644 index 00000000..05fe7874 --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd_cmds.c @@ -0,0 +1,1061 @@ +/*************************************************************************** + * Copyright (c) 2005-2009, Broadcom Corporation. + * + * Name: crystalhd_cmds . c + * + * Description: + * BCM70010 Linux driver user command interfaces. + * + * HISTORY: + * + ********************************************************************** + * This file is part of the crystalhd device driver. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver. If not, see . + **********************************************************************/ + +#include "crystalhd.h" + +static struct crystalhd_user *bc_cproc_get_uid(struct crystalhd_cmd *ctx) +{ + struct crystalhd_user *user = NULL; + int i; + + for (i = 0; i < BC_LINK_MAX_OPENS; i++) { + if (!ctx->user[i].in_use) { + user = &ctx->user[i]; + break; + } + } + + return user; +} + +static int bc_cproc_get_user_count(struct crystalhd_cmd *ctx) +{ + int i, count = 0; + + for (i = 0; i < BC_LINK_MAX_OPENS; i++) { + if (ctx->user[i].in_use) + count++; + } + + return count; +} + +static void bc_cproc_mark_pwr_state(struct crystalhd_cmd *ctx) +{ + int i; + + for (i = 0; i < BC_LINK_MAX_OPENS; i++) { + if (!ctx->user[i].in_use) + continue; + if (ctx->user[i].mode == DTS_DIAG_MODE || + ctx->user[i].mode == DTS_PLAYBACK_MODE) { + ctx->pwr_state_change = 1; + break; + } + } +} + +static enum BC_STATUS bc_cproc_notify_mode(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + int rc = 0, i = 0; + + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + if (ctx->user[idata->u_id].mode != DTS_MODE_INV) { + BCMLOG_ERR("Close the handle first..\n"); + return BC_STS_ERR_USAGE; + } + if (idata->udata.u.NotifyMode.Mode == DTS_MONITOR_MODE) { + ctx->user[idata->u_id].mode = idata->udata.u.NotifyMode.Mode; + return BC_STS_SUCCESS; + } + if (ctx->state != BC_LINK_INVALID) { + BCMLOG_ERR("Link invalid state %d\n", ctx->state); + return BC_STS_ERR_USAGE; + } + /* Check for duplicate playback sessions..*/ + for (i = 0; i < BC_LINK_MAX_OPENS; i++) { + if (ctx->user[i].mode == DTS_DIAG_MODE || + ctx->user[i].mode == DTS_PLAYBACK_MODE) { + BCMLOG_ERR("multiple playback sessions are not " + "supported..\n"); + return BC_STS_ERR_USAGE; + } + } + ctx->cin_wait_exit = 0; + ctx->user[idata->u_id].mode = idata->udata.u.NotifyMode.Mode; + /* Setup mmap pool for uaddr sgl mapping..*/ + rc = crystalhd_create_dio_pool(ctx->adp, BC_LINK_MAX_SGLS); + if (rc) + return BC_STS_ERROR; + + /* Setup Hardware DMA rings */ + return crystalhd_hw_setup_dma_rings(&ctx->hw_ctx); +} + +static enum BC_STATUS bc_cproc_get_version(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + idata->udata.u.VerInfo.DriverMajor = crystalhd_kmod_major; + idata->udata.u.VerInfo.DriverMinor = crystalhd_kmod_minor; + idata->udata.u.VerInfo.DriverRevision = crystalhd_kmod_rev; + return BC_STS_SUCCESS; +} + + +static enum BC_STATUS bc_cproc_get_hwtype(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + crystalhd_pci_cfg_rd(ctx->adp, 0, 2, + (uint32_t *)&idata->udata.u.hwType.PciVenId); + crystalhd_pci_cfg_rd(ctx->adp, 2, 2, + (uint32_t *)&idata->udata.u.hwType.PciDevId); + crystalhd_pci_cfg_rd(ctx->adp, 8, 1, + (uint32_t *)&idata->udata.u.hwType.HwRev); + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_reg_rd(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + if (!ctx || !idata) + return BC_STS_INV_ARG; + idata->udata.u.regAcc.Value = bc_dec_reg_rd(ctx->adp, + idata->udata.u.regAcc.Offset); + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_reg_wr(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + if (!ctx || !idata) + return BC_STS_INV_ARG; + + bc_dec_reg_wr(ctx->adp, idata->udata.u.regAcc.Offset, + idata->udata.u.regAcc.Value); + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_link_reg_rd(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + if (!ctx || !idata) + return BC_STS_INV_ARG; + + idata->udata.u.regAcc.Value = crystalhd_reg_rd(ctx->adp, + idata->udata.u.regAcc.Offset); + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_link_reg_wr(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + if (!ctx || !idata) + return BC_STS_INV_ARG; + + crystalhd_reg_wr(ctx->adp, idata->udata.u.regAcc.Offset, + idata->udata.u.regAcc.Value); + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_mem_rd(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + enum BC_STATUS sts = BC_STS_SUCCESS; + + if (!ctx || !idata || !idata->add_cdata) + return BC_STS_INV_ARG; + + if (idata->udata.u.devMem.NumDwords > (idata->add_cdata_sz / 4)) { + BCMLOG_ERR("insufficient buffer\n"); + return BC_STS_INV_ARG; + } + sts = crystalhd_mem_rd(ctx->adp, idata->udata.u.devMem.StartOff, + idata->udata.u.devMem.NumDwords, + (uint32_t *)idata->add_cdata); + return sts; + +} + +static enum BC_STATUS bc_cproc_mem_wr(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + enum BC_STATUS sts = BC_STS_SUCCESS; + + if (!ctx || !idata || !idata->add_cdata) + return BC_STS_INV_ARG; + + if (idata->udata.u.devMem.NumDwords > (idata->add_cdata_sz / 4)) { + BCMLOG_ERR("insufficient buffer\n"); + return BC_STS_INV_ARG; + } + + sts = crystalhd_mem_wr(ctx->adp, idata->udata.u.devMem.StartOff, + idata->udata.u.devMem.NumDwords, + (uint32_t *)idata->add_cdata); + return sts; +} + +static enum BC_STATUS bc_cproc_cfg_rd(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + uint32_t ix, cnt, off, len; + enum BC_STATUS sts = BC_STS_SUCCESS; + uint32_t *temp; + + if (!ctx || !idata) + return BC_STS_INV_ARG; + + temp = (uint32_t *) idata->udata.u.pciCfg.pci_cfg_space; + off = idata->udata.u.pciCfg.Offset; + len = idata->udata.u.pciCfg.Size; + + if (len <= 4) + return crystalhd_pci_cfg_rd(ctx->adp, off, len, temp); + + /* Truncate to dword alignment..*/ + len = 4; + cnt = idata->udata.u.pciCfg.Size / len; + for (ix = 0; ix < cnt; ix++) { + sts = crystalhd_pci_cfg_rd(ctx->adp, off, len, &temp[ix]); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("config read : %d\n", sts); + return sts; + } + off += len; + } + + return sts; +} + +static enum BC_STATUS bc_cproc_cfg_wr(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + uint32_t ix, cnt, off, len; + enum BC_STATUS sts = BC_STS_SUCCESS; + uint32_t *temp; + + if (!ctx || !idata) + return BC_STS_INV_ARG; + + temp = (uint32_t *) idata->udata.u.pciCfg.pci_cfg_space; + off = idata->udata.u.pciCfg.Offset; + len = idata->udata.u.pciCfg.Size; + + if (len <= 4) + return crystalhd_pci_cfg_wr(ctx->adp, off, len, temp[0]); + + /* Truncate to dword alignment..*/ + len = 4; + cnt = idata->udata.u.pciCfg.Size / len; + for (ix = 0; ix < cnt; ix++) { + sts = crystalhd_pci_cfg_wr(ctx->adp, off, len, temp[ix]); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("config write : %d\n", sts); + return sts; + } + off += len; + } + + return sts; +} + +static enum BC_STATUS bc_cproc_download_fw(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + enum BC_STATUS sts = BC_STS_SUCCESS; + + if (!ctx || !idata || !idata->add_cdata || !idata->add_cdata_sz) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + if (ctx->state != BC_LINK_INVALID) { + BCMLOG_ERR("Link invalid state %d\n", ctx->state); + return BC_STS_ERR_USAGE; + } + + sts = crystalhd_download_fw(ctx->adp, (uint8_t *)idata->add_cdata, + idata->add_cdata_sz); + + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("Firmware Download Failure!! - %d\n", sts); + } else + ctx->state |= BC_LINK_INIT; + + return sts; +} + +/* + * We use the FW_CMD interface to sync up playback state with application + * and firmware. This function will perform the required pre and post + * processing of the Firmware commands. + * + * Pause - + * Disable capture after decoder pause. + * Resume - + * First enable capture and issue decoder resume command. + * Flush - + * Abort pending input transfers and issue decoder flush command. + * + */ +static enum BC_STATUS bc_cproc_do_fw_cmd(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + enum BC_STATUS sts; + uint32_t *cmd; + + if (!(ctx->state & BC_LINK_INIT)) { + BCMLOG_ERR("Link invalid state %d\n", ctx->state); + return BC_STS_ERR_USAGE; + } + + cmd = idata->udata.u.fwCmd.cmd; + + /* Pre-Process */ + if (cmd[0] == eCMD_C011_DEC_CHAN_PAUSE) { + if (!cmd[3]) { + ctx->state &= ~BC_LINK_PAUSED; + crystalhd_hw_unpause(&ctx->hw_ctx); + } + } else if (cmd[0] == eCMD_C011_DEC_CHAN_FLUSH) { + BCMLOG(BCMLOG_INFO, "Flush issued\n"); + if (cmd[3]) + ctx->cin_wait_exit = 1; + } + + sts = crystalhd_do_fw_cmd(&ctx->hw_ctx, &idata->udata.u.fwCmd); + + if (sts != BC_STS_SUCCESS) { + BCMLOG(BCMLOG_INFO, "fw cmd %x failed\n", cmd[0]); + return sts; + } + + /* Post-Process */ + if (cmd[0] == eCMD_C011_DEC_CHAN_PAUSE) { + if (cmd[3]) { + ctx->state |= BC_LINK_PAUSED; + crystalhd_hw_pause(&ctx->hw_ctx); + } + } + + return sts; +} + +static void bc_proc_in_completion(struct crystalhd_dio_req *dio_hnd, + wait_queue_head_t *event, enum BC_STATUS sts) +{ + if (!dio_hnd || !event) { + BCMLOG_ERR("Invalid Arg!!\n"); + return; + } + if (sts == BC_STS_IO_USER_ABORT) + return; + + dio_hnd->uinfo.comp_sts = sts; + dio_hnd->uinfo.ev_sts = 1; + crystalhd_set_event(event); +} + +static enum BC_STATUS bc_cproc_codein_sleep(struct crystalhd_cmd *ctx) +{ + wait_queue_head_t sleep_ev; + int rc = 0; + + if (ctx->state & BC_LINK_SUSPEND) + return BC_STS_IO_USER_ABORT; + + if (ctx->cin_wait_exit) { + ctx->cin_wait_exit = 0; + return BC_STS_CMD_CANCELLED; + } + crystalhd_create_event(&sleep_ev); + crystalhd_wait_on_event(&sleep_ev, 0, 100, rc, 0); + if (rc == -EINTR) + return BC_STS_IO_USER_ABORT; + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_hw_txdma(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata, + struct crystalhd_dio_req *dio) +{ + uint32_t tx_listid = 0; + enum BC_STATUS sts = BC_STS_SUCCESS; + wait_queue_head_t event; + int rc = 0; + + if (!ctx || !idata || !dio) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + crystalhd_create_event(&event); + + ctx->tx_list_id = 0; + /* msleep_interruptible(2000); */ + sts = crystalhd_hw_post_tx(&ctx->hw_ctx, dio, bc_proc_in_completion, + &event, &tx_listid, + idata->udata.u.ProcInput.Encrypted); + + while (sts == BC_STS_BUSY) { + sts = bc_cproc_codein_sleep(ctx); + if (sts != BC_STS_SUCCESS) + break; + sts = crystalhd_hw_post_tx(&ctx->hw_ctx, dio, + bc_proc_in_completion, + &event, &tx_listid, + idata->udata.u.ProcInput.Encrypted); + } + if (sts != BC_STS_SUCCESS) { + BCMLOG(BCMLOG_DBG, "_hw_txdma returning sts:%d\n", sts); + return sts; + } + if (ctx->cin_wait_exit) + ctx->cin_wait_exit = 0; + + ctx->tx_list_id = tx_listid; + + /* _post() succeeded.. wait for the completion. */ + crystalhd_wait_on_event(&event, (dio->uinfo.ev_sts), 3000, rc, 0); + ctx->tx_list_id = 0; + if (!rc) { + return dio->uinfo.comp_sts; + } else if (rc == -EBUSY) { + BCMLOG(BCMLOG_DBG, "_tx_post() T/O\n"); + sts = BC_STS_TIMEOUT; + } else if (rc == -EINTR) { + BCMLOG(BCMLOG_DBG, "Tx Wait Signal int.\n"); + sts = BC_STS_IO_USER_ABORT; + } else { + sts = BC_STS_IO_ERROR; + } + + /* We are cancelling the IO from the same context as the _post(). + * so no need to wait on the event again.. the return itself + * ensures the release of our resources. + */ + crystalhd_hw_cancel_tx(&ctx->hw_ctx, tx_listid); + + return sts; +} + +/* Helper function to check on user buffers */ +static enum BC_STATUS bc_cproc_check_inbuffs(bool pin, void *ubuff, uint32_t ub_sz, + uint32_t uv_off, bool en_422) +{ + if (!ubuff || !ub_sz) { + BCMLOG_ERR("%s->Invalid Arg %p %x\n", + ((pin) ? "TX" : "RX"), ubuff, ub_sz); + return BC_STS_INV_ARG; + } + + /* Check for alignment */ + if (((uintptr_t)ubuff) & 0x03) { + BCMLOG_ERR("%s-->Un-aligned address not implemented yet.. %p\n", + ((pin) ? "TX" : "RX"), ubuff); + return BC_STS_NOT_IMPL; + } + if (pin) + return BC_STS_SUCCESS; + + if (!en_422 && !uv_off) { + BCMLOG_ERR("Need UV offset for 420 mode.\n"); + return BC_STS_INV_ARG; + } + + if (en_422 && uv_off) { + BCMLOG_ERR("UV offset in 422 mode ??\n"); + return BC_STS_INV_ARG; + } + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_proc_input(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + void *ubuff; + uint32_t ub_sz; + struct crystalhd_dio_req *dio_hnd = NULL; + enum BC_STATUS sts = BC_STS_SUCCESS; + + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + ubuff = idata->udata.u.ProcInput.pDmaBuff; + ub_sz = idata->udata.u.ProcInput.BuffSz; + + sts = bc_cproc_check_inbuffs(1, ubuff, ub_sz, 0, 0); + if (sts != BC_STS_SUCCESS) + return sts; + + sts = crystalhd_map_dio(ctx->adp, ubuff, ub_sz, 0, 0, 1, &dio_hnd); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("dio map - %d\n", sts); + return sts; + } + + if (!dio_hnd) + return BC_STS_ERROR; + + sts = bc_cproc_hw_txdma(ctx, idata, dio_hnd); + + crystalhd_unmap_dio(ctx->adp, dio_hnd); + + return sts; +} + +static enum BC_STATUS bc_cproc_add_cap_buff(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + void *ubuff; + uint32_t ub_sz, uv_off; + bool en_422; + struct crystalhd_dio_req *dio_hnd = NULL; + enum BC_STATUS sts = BC_STS_SUCCESS; + + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + ubuff = idata->udata.u.RxBuffs.YuvBuff; + ub_sz = idata->udata.u.RxBuffs.YuvBuffSz; + uv_off = idata->udata.u.RxBuffs.UVbuffOffset; + en_422 = idata->udata.u.RxBuffs.b422Mode; + + sts = bc_cproc_check_inbuffs(0, ubuff, ub_sz, uv_off, en_422); + if (sts != BC_STS_SUCCESS) + return sts; + + sts = crystalhd_map_dio(ctx->adp, ubuff, ub_sz, uv_off, + en_422, 0, &dio_hnd); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("dio map - %d\n", sts); + return sts; + } + + if (!dio_hnd) + return BC_STS_ERROR; + + sts = crystalhd_hw_add_cap_buffer(&ctx->hw_ctx, dio_hnd, (ctx->state == BC_LINK_READY)); + if ((sts != BC_STS_SUCCESS) && (sts != BC_STS_BUSY)) { + crystalhd_unmap_dio(ctx->adp, dio_hnd); + return sts; + } + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_fmt_change(struct crystalhd_cmd *ctx, + struct crystalhd_dio_req *dio) +{ + enum BC_STATUS sts = BC_STS_SUCCESS; + + sts = crystalhd_hw_add_cap_buffer(&ctx->hw_ctx, dio, 0); + if (sts != BC_STS_SUCCESS) + return sts; + + ctx->state |= BC_LINK_FMT_CHG; + if (ctx->state == BC_LINK_READY) + sts = crystalhd_hw_start_capture(&ctx->hw_ctx); + + return sts; +} + +static enum BC_STATUS bc_cproc_fetch_frame(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + struct crystalhd_dio_req *dio = NULL; + enum BC_STATUS sts = BC_STS_SUCCESS; + struct BC_DEC_OUT_BUFF *frame; + + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + if (!(ctx->state & BC_LINK_CAP_EN)) { + BCMLOG(BCMLOG_DBG, "Capture not enabled..%x\n", ctx->state); + return BC_STS_ERR_USAGE; + } + + frame = &idata->udata.u.DecOutData; + + sts = crystalhd_hw_get_cap_buffer(&ctx->hw_ctx, &frame->PibInfo, &dio); + if (sts != BC_STS_SUCCESS) + return (ctx->state & BC_LINK_SUSPEND) ? BC_STS_IO_USER_ABORT : sts; + + frame->Flags = dio->uinfo.comp_flags; + + if (frame->Flags & COMP_FLAG_FMT_CHANGE) + return bc_cproc_fmt_change(ctx, dio); + + frame->OutPutBuffs.YuvBuff = dio->uinfo.xfr_buff; + frame->OutPutBuffs.YuvBuffSz = dio->uinfo.xfr_len; + frame->OutPutBuffs.UVbuffOffset = dio->uinfo.uv_offset; + frame->OutPutBuffs.b422Mode = dio->uinfo.b422mode; + + frame->OutPutBuffs.YBuffDoneSz = dio->uinfo.y_done_sz; + frame->OutPutBuffs.UVBuffDoneSz = dio->uinfo.uv_done_sz; + + crystalhd_unmap_dio(ctx->adp, dio); + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_start_capture(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + ctx->state |= BC_LINK_CAP_EN; + if (ctx->state == BC_LINK_READY) + return crystalhd_hw_start_capture(&ctx->hw_ctx); + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_flush_cap_buffs(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + struct crystalhd_dio_req *dio = NULL; + enum BC_STATUS sts = BC_STS_SUCCESS; + struct BC_DEC_OUT_BUFF *frame; + uint32_t count; + + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + if (!(ctx->state & BC_LINK_CAP_EN)) + return BC_STS_ERR_USAGE; + + /* We should ack flush even when we are in paused/suspend state */ + if (!(ctx->state & BC_LINK_READY)) + return crystalhd_hw_stop_capture(&ctx->hw_ctx); + + ctx->state &= ~(BC_LINK_CAP_EN|BC_LINK_FMT_CHG); + + frame = &idata->udata.u.DecOutData; + for (count = 0; count < BC_RX_LIST_CNT; count++) { + + sts = crystalhd_hw_get_cap_buffer(&ctx->hw_ctx, &frame->PibInfo, &dio); + if (sts != BC_STS_SUCCESS) + break; + + crystalhd_unmap_dio(ctx->adp, dio); + } + + return crystalhd_hw_stop_capture(&ctx->hw_ctx); +} + +static enum BC_STATUS bc_cproc_get_stats(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + struct BC_DTS_STATS *stats; + struct crystalhd_hw_stats hw_stats; + + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + crystalhd_hw_stats(&ctx->hw_ctx, &hw_stats); + + stats = &idata->udata.u.drvStat; + stats->drvRLL = hw_stats.rdyq_count; + stats->drvFLL = hw_stats.freeq_count; + stats->DrvTotalFrmDropped = hw_stats.rx_errors; + stats->DrvTotalHWErrs = hw_stats.rx_errors + hw_stats.tx_errors; + stats->intCount = hw_stats.num_interrupts; + stats->DrvIgnIntrCnt = hw_stats.num_interrupts - + hw_stats.dev_interrupts; + stats->TxFifoBsyCnt = hw_stats.cin_busy; + stats->pauseCount = hw_stats.pause_cnt; + + if (ctx->pwr_state_change) + stats->pwr_state_change = 1; + if (ctx->state & BC_LINK_PAUSED) + stats->DrvPauseTime = 1; + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_reset_stats(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + crystalhd_hw_stats(&ctx->hw_ctx, NULL); + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS bc_cproc_chg_clk(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + struct BC_CLOCK *clock; + uint32_t oldClk; + enum BC_STATUS sts = BC_STS_SUCCESS; + + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + clock = &idata->udata.u.clockValue; + oldClk = ctx->hw_ctx.core_clock_mhz; + ctx->hw_ctx.core_clock_mhz = clock->clk; + + if (ctx->state & BC_LINK_READY) { + sts = crystalhd_hw_set_core_clock(&ctx->hw_ctx); + if (sts == BC_STS_CLK_NOCHG) + ctx->hw_ctx.core_clock_mhz = oldClk; + } + + clock->clk = ctx->hw_ctx.core_clock_mhz; + + return sts; +} + +/*=============== Cmd Proc Table.. ======================================*/ +static const struct crystalhd_cmd_tbl g_crystalhd_cproc_tbl[] = { + { BCM_IOC_GET_VERSION, bc_cproc_get_version, 0}, + { BCM_IOC_GET_HWTYPE, bc_cproc_get_hwtype, 0}, + { BCM_IOC_REG_RD, bc_cproc_reg_rd, 0}, + { BCM_IOC_REG_WR, bc_cproc_reg_wr, 0}, + { BCM_IOC_FPGA_RD, bc_cproc_link_reg_rd, 0}, + { BCM_IOC_FPGA_WR, bc_cproc_link_reg_wr, 0}, + { BCM_IOC_MEM_RD, bc_cproc_mem_rd, 0}, + { BCM_IOC_MEM_WR, bc_cproc_mem_wr, 0}, + { BCM_IOC_RD_PCI_CFG, bc_cproc_cfg_rd, 0}, + { BCM_IOC_WR_PCI_CFG, bc_cproc_cfg_wr, 1}, + { BCM_IOC_FW_DOWNLOAD, bc_cproc_download_fw, 1}, + { BCM_IOC_FW_CMD, bc_cproc_do_fw_cmd, 1}, + { BCM_IOC_PROC_INPUT, bc_cproc_proc_input, 1}, + { BCM_IOC_ADD_RXBUFFS, bc_cproc_add_cap_buff, 1}, + { BCM_IOC_FETCH_RXBUFF, bc_cproc_fetch_frame, 1}, + { BCM_IOC_START_RX_CAP, bc_cproc_start_capture, 1}, + { BCM_IOC_FLUSH_RX_CAP, bc_cproc_flush_cap_buffs, 1}, + { BCM_IOC_GET_DRV_STAT, bc_cproc_get_stats, 0}, + { BCM_IOC_RST_DRV_STAT, bc_cproc_reset_stats, 0}, + { BCM_IOC_NOTIFY_MODE, bc_cproc_notify_mode, 0}, + { BCM_IOC_CHG_CLK, bc_cproc_chg_clk, 0}, + { BCM_IOC_END, NULL}, +}; + +/*=============== Cmd Proc Functions.. ===================================*/ + +/** + * crystalhd_suspend - Power management suspend request. + * @ctx: Command layer context. + * @idata: Iodata - required for internal use. + * + * Return: + * status + * + * 1. Set the state to Suspend. + * 2. Flush the Rx Buffers it will unmap all the buffers and + * stop the RxDMA engine. + * 3. Cancel The TX Io and Stop Dma Engine. + * 4. Put the DDR in to deep sleep. + * 5. Stop the hardware putting it in to Reset State. + * + * Current gstreamer frame work does not provide any power management + * related notification to user mode decoder plug-in. As a work-around + * we pass on the power mangement notification to our plug-in by completing + * all outstanding requests with BC_STS_IO_USER_ABORT return code. + */ +enum BC_STATUS crystalhd_suspend(struct crystalhd_cmd *ctx, + struct crystalhd_ioctl_data *idata) +{ + enum BC_STATUS sts = BC_STS_SUCCESS; + + if (!ctx || !idata) { + BCMLOG_ERR("Invalid Parameters\n"); + return BC_STS_ERROR; + } + + if (ctx->state & BC_LINK_SUSPEND) + return BC_STS_SUCCESS; + + if (ctx->state == BC_LINK_INVALID) { + BCMLOG(BCMLOG_DBG, "Nothing To Do Suspend Success\n"); + return BC_STS_SUCCESS; + } + + ctx->state |= BC_LINK_SUSPEND; + + bc_cproc_mark_pwr_state(ctx); + + if (ctx->state & BC_LINK_CAP_EN) { + sts = bc_cproc_flush_cap_buffs(ctx, idata); + if (sts != BC_STS_SUCCESS) + return sts; + } + + if (ctx->tx_list_id) { + sts = crystalhd_hw_cancel_tx(&ctx->hw_ctx, ctx->tx_list_id); + if (sts != BC_STS_SUCCESS) + return sts; + } + + sts = crystalhd_hw_suspend(&ctx->hw_ctx); + if (sts != BC_STS_SUCCESS) + return sts; + + BCMLOG(BCMLOG_DBG, "BCM70012 suspend success\n"); + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_resume - Resume frame capture. + * @ctx: Command layer contextx. + * + * Return: + * status + * + * + * Resume frame capture. + * + * PM_Resume can't resume the playback state back to pre-suspend state + * because we don't keep video clip related information within driver. + * To get back to the pre-suspend state App will re-open the device and + * start a new playback session from the pre-suspend clip position. + * + */ +enum BC_STATUS crystalhd_resume(struct crystalhd_cmd *ctx) +{ + BCMLOG(BCMLOG_DBG, "crystalhd_resume Success %x\n", ctx->state); + + bc_cproc_mark_pwr_state(ctx); + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_user_open - Create application handle. + * @ctx: Command layer contextx. + * @user_ctx: User ID context. + * + * Return: + * status + * + * Creates an application specific UID and allocates + * application specific resources. HW layer initialization + * is done for the first open request. + */ +enum BC_STATUS crystalhd_user_open(struct crystalhd_cmd *ctx, + struct crystalhd_user **user_ctx) +{ + struct crystalhd_user *uc; + + if (!ctx || !user_ctx) { + BCMLOG_ERR("Invalid arg..\n"); + return BC_STS_INV_ARG; + } + + uc = bc_cproc_get_uid(ctx); + if (!uc) { + BCMLOG(BCMLOG_INFO, "No free user context...\n"); + return BC_STS_BUSY; + } + + BCMLOG(BCMLOG_INFO, "Opening new user[%x] handle\n", uc->uid); + + crystalhd_hw_open(&ctx->hw_ctx, ctx->adp); + + uc->in_use = 1; + + *user_ctx = uc; + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_user_close - Close application handle. + * @ctx: Command layer contextx. + * @uc: User ID context. + * + * Return: + * status + * + * Closer application handle and release app specific + * resources. + */ +enum BC_STATUS crystalhd_user_close(struct crystalhd_cmd *ctx, struct crystalhd_user *uc) +{ + uint32_t mode = uc->mode; + + ctx->user[uc->uid].mode = DTS_MODE_INV; + ctx->user[uc->uid].in_use = 0; + ctx->cin_wait_exit = 1; + ctx->pwr_state_change = 0; + + BCMLOG(BCMLOG_INFO, "Closing user[%x] handle\n", uc->uid); + + if ((mode == DTS_DIAG_MODE) || (mode == DTS_PLAYBACK_MODE)) { + crystalhd_hw_free_dma_rings(&ctx->hw_ctx); + crystalhd_destroy_dio_pool(ctx->adp); + } else if (bc_cproc_get_user_count(ctx)) { + return BC_STS_SUCCESS; + } + + crystalhd_hw_close(&ctx->hw_ctx); + + ctx->state = BC_LINK_INVALID; + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_setup_cmd_context - Setup Command layer resources. + * @ctx: Command layer contextx. + * @adp: Adapter context + * + * Return: + * status + * + * Called at the time of driver load. + */ +enum BC_STATUS __devinit crystalhd_setup_cmd_context(struct crystalhd_cmd *ctx, + struct crystalhd_adp *adp) +{ + int i = 0; + + if (!ctx || !adp) { + BCMLOG_ERR("Invalid arg!!\n"); + return BC_STS_INV_ARG; + } + + if (ctx->adp) + BCMLOG(BCMLOG_DBG, "Resetting Cmd context delete missing..\n"); + + ctx->adp = adp; + for (i = 0; i < BC_LINK_MAX_OPENS; i++) { + ctx->user[i].uid = i; + ctx->user[i].in_use = 0; + ctx->user[i].mode = DTS_MODE_INV; + } + + /*Open and Close the Hardware to put it in to sleep state*/ + crystalhd_hw_open(&ctx->hw_ctx, ctx->adp); + crystalhd_hw_close(&ctx->hw_ctx); + return BC_STS_SUCCESS; +} + +/** + * crystalhd_delete_cmd_context - Release Command layer resources. + * @ctx: Command layer contextx. + * + * Return: + * status + * + * Called at the time of driver un-load. + */ +enum BC_STATUS __devexit crystalhd_delete_cmd_context(struct crystalhd_cmd *ctx) +{ + BCMLOG(BCMLOG_DBG, "Deleting Command context..\n"); + + ctx->adp = NULL; + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_get_cmd_proc - Cproc table lookup. + * @ctx: Command layer contextx. + * @cmd: IOCTL command code. + * @uc: User ID context. + * + * Return: + * command proc function pointer + * + * This function checks the process context, application's + * mode of operation and returns the function pointer + * from the cproc table. + */ +crystalhd_cmd_proc crystalhd_get_cmd_proc(struct crystalhd_cmd *ctx, uint32_t cmd, + struct crystalhd_user *uc) +{ + crystalhd_cmd_proc cproc = NULL; + unsigned int i, tbl_sz; + + if (!ctx) { + BCMLOG_ERR("Invalid arg.. Cmd[%d]\n", cmd); + return NULL; + } + + if ((cmd != BCM_IOC_GET_DRV_STAT) && (ctx->state & BC_LINK_SUSPEND)) { + BCMLOG_ERR("Invalid State [suspend Set].. Cmd[%d]\n", cmd); + return NULL; + } + + tbl_sz = sizeof(g_crystalhd_cproc_tbl) / sizeof(struct crystalhd_cmd_tbl); + for (i = 0; i < tbl_sz; i++) { + if (g_crystalhd_cproc_tbl[i].cmd_id == cmd) { + if ((uc->mode == DTS_MONITOR_MODE) && + (g_crystalhd_cproc_tbl[i].block_mon)) { + BCMLOG(BCMLOG_INFO, "Blocking cmd %d\n", cmd); + break; + } + cproc = g_crystalhd_cproc_tbl[i].cmd_proc; + break; + } + } + + return cproc; +} + +/** + * crystalhd_cmd_interrupt - ISR entry point + * @ctx: Command layer contextx. + * + * Return: + * TRUE: If interrupt from bcm70012 device. + * + * + * ISR entry point from OS layer. + */ +bool crystalhd_cmd_interrupt(struct crystalhd_cmd *ctx) +{ + if (!ctx) { + BCMLOG_ERR("Invalid arg..\n"); + return 0; + } + + return crystalhd_hw_interrupt(ctx->adp, &ctx->hw_ctx); +} diff --git a/drivers/staging/crystalhd/crystalhd_cmds.h b/drivers/staging/crystalhd/crystalhd_cmds.h new file mode 100644 index 00000000..4066ba39 --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd_cmds.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (c) 2005-2009, Broadcom Corporation. + * + * Name: crystalhd_cmds . h + * + * Description: + * BCM70010 Linux driver user command interfaces. + * + * HISTORY: + * + ********************************************************************** + * This file is part of the crystalhd device driver. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver. If not, see . + **********************************************************************/ + +#ifndef _CRYSTALHD_CMDS_H_ +#define _CRYSTALHD_CMDS_H_ + +/* + * NOTE:: This is the main interface file between the Linux layer + * and the harware layer. This file will use the definitions + * from _dts_glob and dts_defs etc.. which are defined for + * windows. + */ + +#include "crystalhd.h" + +enum crystalhd_state { + BC_LINK_INVALID = 0x00, + BC_LINK_INIT = 0x01, + BC_LINK_CAP_EN = 0x02, + BC_LINK_FMT_CHG = 0x04, + BC_LINK_SUSPEND = 0x10, + BC_LINK_PAUSED = 0x20, + BC_LINK_READY = (BC_LINK_INIT | BC_LINK_CAP_EN | BC_LINK_FMT_CHG), +}; + +struct crystalhd_user { + uint32_t uid; + uint32_t in_use; + uint32_t mode; +}; + +#define DTS_MODE_INV (-1) + +struct crystalhd_cmd { + uint32_t state; + struct crystalhd_adp *adp; + struct crystalhd_user user[BC_LINK_MAX_OPENS]; + + spinlock_t ctx_lock; + uint32_t tx_list_id; + uint32_t cin_wait_exit; + uint32_t pwr_state_change; + struct crystalhd_hw hw_ctx; +}; + +typedef enum BC_STATUS(*crystalhd_cmd_proc)(struct crystalhd_cmd *, struct crystalhd_ioctl_data *); + +struct crystalhd_cmd_tbl { + uint32_t cmd_id; + const crystalhd_cmd_proc cmd_proc; + uint32_t block_mon; +}; + +enum BC_STATUS crystalhd_suspend(struct crystalhd_cmd *ctx, struct crystalhd_ioctl_data *idata); +enum BC_STATUS crystalhd_resume(struct crystalhd_cmd *ctx); +crystalhd_cmd_proc crystalhd_get_cmd_proc(struct crystalhd_cmd *ctx, uint32_t cmd, + struct crystalhd_user *uc); +enum BC_STATUS crystalhd_user_open(struct crystalhd_cmd *ctx, struct crystalhd_user **user_ctx); +enum BC_STATUS crystalhd_user_close(struct crystalhd_cmd *ctx, struct crystalhd_user *uc); +enum BC_STATUS crystalhd_setup_cmd_context(struct crystalhd_cmd *ctx, struct crystalhd_adp *adp); +enum BC_STATUS crystalhd_delete_cmd_context(struct crystalhd_cmd *ctx); +bool crystalhd_cmd_interrupt(struct crystalhd_cmd *ctx); + +#endif diff --git a/drivers/staging/crystalhd/crystalhd_fw_if.h b/drivers/staging/crystalhd/crystalhd_fw_if.h new file mode 100644 index 00000000..9e2831e6 --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd_fw_if.h @@ -0,0 +1,363 @@ +/*************************************************************************** + * Copyright (c) 2005-2009, Broadcom Corporation. + * + * Name: crystalhd_fw_if . h + * + * Description: + * BCM70012 Firmware interface definitions. + * + * HISTORY: + * + ********************************************************************** + * This file is part of the crystalhd device driver. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver. If not, see . + **********************************************************************/ + +#ifndef _CRYSTALHD_FW_IF_H_ +#define _CRYSTALHD_FW_IF_H_ + +/* TBD: Pull in only required defs into this file.. */ + +/* User Data Header */ +struct user_data { + struct user_data *next; + uint32_t type; + uint32_t size; +}; + +/*------------------------------------------------------* + * MPEG Extension to the PPB * + *------------------------------------------------------*/ +struct ppb_mpeg { + uint32_t to_be_defined; + uint32_t valid; + + /* Always valid, defaults to picture size if no + sequence display extension in the stream. */ + uint32_t display_horizontal_size; + uint32_t display_vertical_size; + + /* MPEG_VALID_PANSCAN + Offsets are a copy values from the MPEG stream. */ + uint32_t offset_count; + int32_t horizontal_offset[3]; + int32_t vertical_offset[3]; + + /* MPEG_VALID_USERDATA + User data is in the form of a linked list. */ + int32_t userDataSize; + struct user_data *userData; + +}; + + +/*------------------------------------------------------* + * VC1 Extension to the PPB * + *------------------------------------------------------*/ +struct ppb_vc1 { + uint32_t to_be_defined; + uint32_t valid; + + /* Always valid, defaults to picture size if no + sequence display extension in the stream. */ + uint32_t display_horizontal_size; + uint32_t display_vertical_size; + + /* VC1 pan scan windows */ + uint32_t num_panscan_windows; + int32_t ps_horiz_offset[4]; + int32_t ps_vert_offset[4]; + int32_t ps_width[4]; + int32_t ps_height[4]; + + /* VC1_VALID_USERDATA + User data is in the form of a linked list. */ + int32_t userDataSize; + struct user_data *userData; + +}; + +/*------------------------------------------------------* + * H.264 Extension to the PPB * + *------------------------------------------------------*/ + +/** + * @brief Film grain SEI message. + * + * Content of the film grain SEI message. + */ + +/* maximum number of model-values as for Thomson spec(standard says 5) */ +#define MAX_FGT_MODEL_VALUE (3) + +/* maximum number of intervals(as many as 256 intervals?) */ +#define MAX_FGT_VALUE_INTERVAL (256) + +struct fgt_sei { + struct fgt_sei *next; + unsigned char model_values[3][MAX_FGT_VALUE_INTERVAL][MAX_FGT_MODEL_VALUE]; + unsigned char upper_bound[3][MAX_FGT_VALUE_INTERVAL]; + unsigned char lower_bound[3][MAX_FGT_VALUE_INTERVAL]; + + unsigned char cancel_flag; /* Cancel flag: 1 no film grain. */ + unsigned char model_id; /* Model id. */ + + /* +unused SE based on Thomson spec */ + unsigned char color_desc_flag; /* Separate color descrition flag. */ + unsigned char bit_depth_luma; /* Bit depth luma minus 8. */ + unsigned char bit_depth_chroma; /* Bit depth chroma minus 8. */ + unsigned char full_range_flag; /* Full range flag. */ + unsigned char color_primaries; /* Color primaries. */ + unsigned char transfer_charact; /* Transfer characteristics. */ + unsigned char matrix_coeff; /*< Matrix coefficients. */ + /* -unused SE based on Thomson spec */ + + unsigned char blending_mode_id; /* Blending mode. */ + unsigned char log2_scale_factor; /* Log2 scale factor (2-7). */ + unsigned char comp_flag[3]; /* Components [0,2] parameters present flag. */ + unsigned char num_intervals_minus1[3]; /* Number of intensity level intervals. */ + unsigned char num_model_values[3]; /* Number of model values. */ + uint16_t repetition_period; /* Repetition period (0-16384) */ + +}; + +struct ppb_h264 { + /* 'valid' specifies which fields (or sets of + * fields) below are valid. If the corresponding + * bit in 'valid' is NOT set then that field(s) + * is (are) not initialized. */ + uint32_t valid; + + int32_t poc_top; /* POC for Top Field/Frame */ + int32_t poc_bottom; /* POC for Bottom Field */ + uint32_t idr_pic_id; + + /* H264_VALID_PANSCAN */ + uint32_t pan_scan_count; + int32_t pan_scan_left[3]; + int32_t pan_scan_right[3]; + int32_t pan_scan_top[3]; + int32_t pan_scan_bottom[3]; + + /* H264_VALID_CT_TYPE */ + uint32_t ct_type_count; + uint32_t ct_type[3]; + + /* H264_VALID_SPS_CROP */ + int32_t sps_crop_left; + int32_t sps_crop_right; + int32_t sps_crop_top; + int32_t sps_crop_bottom; + + /* H264_VALID_VUI */ + uint32_t chroma_top; + uint32_t chroma_bottom; + + /* H264_VALID_USER */ + uint32_t user_data_size; + struct user_data *user_data; + + /* H264 VALID FGT */ + struct fgt_sei *pfgt; + +}; + +struct ppb { + /* Common fields. */ + uint32_t picture_number; /* Ordinal display number */ + uint32_t video_buffer; /* Video (picbuf) number */ + uint32_t video_address; /* Address of picbuf Y */ + uint32_t video_address_uv; /* Address of picbuf UV */ + uint32_t video_stripe; /* Picbuf stripe */ + uint32_t video_width; /* Picbuf width */ + uint32_t video_height; /* Picbuf height */ + + uint32_t channel_id; /* Decoder channel ID */ + uint32_t status; /* reserved */ + uint32_t width; /* pixels */ + uint32_t height; /* pixels */ + uint32_t chroma_format; /* see above */ + uint32_t pulldown; /* see above */ + uint32_t flags; /* see above */ + uint32_t pts; /* 32 LSBs of PTS */ + uint32_t protocol; /* protocolXXX (above) */ + + uint32_t frame_rate; /* see above */ + uint32_t matrix_coeff; /* see above */ + uint32_t aspect_ratio; /* see above */ + uint32_t colour_primaries; /* see above */ + uint32_t transfer_char; /* see above */ + uint32_t pcr_offset; /* 45kHz if PCR type; else 27MHz */ + uint32_t n_drop; /* Number of pictures to be dropped */ + + uint32_t custom_aspect_ratio_width_height; + /* upper 16-bits is Y and lower 16-bits is X */ + + uint32_t picture_tag; /* Indexing tag from BUD packets */ + uint32_t picture_done_payload; + uint32_t picture_meta_payload; + uint32_t reserved[1]; + + /* Protocol-specific extensions. */ + union { + struct ppb_h264 h264; + struct ppb_mpeg mpeg; + struct ppb_vc1 vc1; + } other; + +}; + +struct c011_pib { + uint32_t bFormatChange; + uint32_t resolution; + uint32_t channelId; + uint32_t ppbPtr; + int32_t ptsStcOffset; + uint32_t zeroPanscanValid; + uint32_t dramOutBufAddr; + uint32_t yComponent; + struct ppb ppb; + +}; + +struct dec_rsp_channel_start_video { + uint32_t command; + uint32_t sequence; + uint32_t status; + uint32_t picBuf; + uint32_t picRelBuf; + uint32_t picInfoDeliveryQ; + uint32_t picInfoReleaseQ; + uint32_t channelStatus; + uint32_t userDataDeliveryQ; + uint32_t userDataReleaseQ; + uint32_t transportStreamCaptureAddr; + uint32_t asyncEventQ; + +}; + +#define eCMD_C011_CMD_BASE (0x73763000) + +/* host commands */ +enum c011_ts_cmd { + eCMD_TS_GET_NEXT_PIC = 0x7376F100, /* debug get next picture */ + eCMD_TS_GET_LAST_PIC = 0x7376F102, /* debug get last pic status */ + eCMD_TS_READ_WRITE_MEM = 0x7376F104, /* debug read write memory */ + + /* New API commands */ + /* General commands */ + eCMD_C011_INIT = eCMD_C011_CMD_BASE + 0x01, + eCMD_C011_RESET = eCMD_C011_CMD_BASE + 0x02, + eCMD_C011_SELF_TEST = eCMD_C011_CMD_BASE + 0x03, + eCMD_C011_GET_VERSION = eCMD_C011_CMD_BASE + 0x04, + eCMD_C011_GPIO = eCMD_C011_CMD_BASE + 0x05, + eCMD_C011_DEBUG_SETUP = eCMD_C011_CMD_BASE + 0x06, + + /* Decoding commands */ + eCMD_C011_DEC_CHAN_OPEN = eCMD_C011_CMD_BASE + 0x100, + eCMD_C011_DEC_CHAN_CLOSE = eCMD_C011_CMD_BASE + 0x101, + eCMD_C011_DEC_CHAN_ACTIVATE = eCMD_C011_CMD_BASE + 0x102, + eCMD_C011_DEC_CHAN_STATUS = eCMD_C011_CMD_BASE + 0x103, + eCMD_C011_DEC_CHAN_FLUSH = eCMD_C011_CMD_BASE + 0x104, + eCMD_C011_DEC_CHAN_TRICK_PLAY = eCMD_C011_CMD_BASE + 0x105, + eCMD_C011_DEC_CHAN_TS_PIDS = eCMD_C011_CMD_BASE + 0x106, + eCMD_C011_DEC_CHAN_PS_STREAM_ID = eCMD_C011_CMD_BASE + 0x107, + eCMD_C011_DEC_CHAN_INPUT_PARAMS = eCMD_C011_CMD_BASE + 0x108, + eCMD_C011_DEC_CHAN_VIDEO_OUTPUT = eCMD_C011_CMD_BASE + 0x109, + eCMD_C011_DEC_CHAN_OUTPUT_FORMAT = eCMD_C011_CMD_BASE + 0x10A, + eCMD_C011_DEC_CHAN_SCALING_FILTERS = eCMD_C011_CMD_BASE + 0x10B, + eCMD_C011_DEC_CHAN_OSD_MODE = eCMD_C011_CMD_BASE + 0x10D, + eCMD_C011_DEC_CHAN_DROP = eCMD_C011_CMD_BASE + 0x10E, + eCMD_C011_DEC_CHAN_RELEASE = eCMD_C011_CMD_BASE + 0x10F, + eCMD_C011_DEC_CHAN_STREAM_SETTINGS = eCMD_C011_CMD_BASE + 0x110, + eCMD_C011_DEC_CHAN_PAUSE_OUTPUT = eCMD_C011_CMD_BASE + 0x111, + eCMD_C011_DEC_CHAN_CHANGE = eCMD_C011_CMD_BASE + 0x112, + eCMD_C011_DEC_CHAN_SET_STC = eCMD_C011_CMD_BASE + 0x113, + eCMD_C011_DEC_CHAN_SET_PTS = eCMD_C011_CMD_BASE + 0x114, + eCMD_C011_DEC_CHAN_CC_MODE = eCMD_C011_CMD_BASE + 0x115, + eCMD_C011_DEC_CREATE_AUDIO_CONTEXT = eCMD_C011_CMD_BASE + 0x116, + eCMD_C011_DEC_COPY_AUDIO_CONTEXT = eCMD_C011_CMD_BASE + 0x117, + eCMD_C011_DEC_DELETE_AUDIO_CONTEXT = eCMD_C011_CMD_BASE + 0x118, + eCMD_C011_DEC_CHAN_SET_DECYPTION = eCMD_C011_CMD_BASE + 0x119, + eCMD_C011_DEC_CHAN_START_VIDEO = eCMD_C011_CMD_BASE + 0x11A, + eCMD_C011_DEC_CHAN_STOP_VIDEO = eCMD_C011_CMD_BASE + 0x11B, + eCMD_C011_DEC_CHAN_PIC_CAPTURE = eCMD_C011_CMD_BASE + 0x11C, + eCMD_C011_DEC_CHAN_PAUSE = eCMD_C011_CMD_BASE + 0x11D, + eCMD_C011_DEC_CHAN_PAUSE_STATE = eCMD_C011_CMD_BASE + 0x11E, + eCMD_C011_DEC_CHAN_SET_SLOWM_RATE = eCMD_C011_CMD_BASE + 0x11F, + eCMD_C011_DEC_CHAN_GET_SLOWM_RATE = eCMD_C011_CMD_BASE + 0x120, + eCMD_C011_DEC_CHAN_SET_FF_RATE = eCMD_C011_CMD_BASE + 0x121, + eCMD_C011_DEC_CHAN_GET_FF_RATE = eCMD_C011_CMD_BASE + 0x122, + eCMD_C011_DEC_CHAN_FRAME_ADVANCE = eCMD_C011_CMD_BASE + 0x123, + eCMD_C011_DEC_CHAN_SET_SKIP_PIC_MODE = eCMD_C011_CMD_BASE + 0x124, + eCMD_C011_DEC_CHAN_GET_SKIP_PIC_MODE = eCMD_C011_CMD_BASE + 0x125, + eCMD_C011_DEC_CHAN_FILL_PIC_BUF = eCMD_C011_CMD_BASE + 0x126, + eCMD_C011_DEC_CHAN_SET_CONTINUITY_CHECK = eCMD_C011_CMD_BASE + 0x127, + eCMD_C011_DEC_CHAN_GET_CONTINUITY_CHECK = eCMD_C011_CMD_BASE + 0x128, + eCMD_C011_DEC_CHAN_SET_BRCM_TRICK_MODE = eCMD_C011_CMD_BASE + 0x129, + eCMD_C011_DEC_CHAN_GET_BRCM_TRICK_MODE = eCMD_C011_CMD_BASE + 0x12A, + eCMD_C011_DEC_CHAN_REVERSE_FIELD_STATUS = eCMD_C011_CMD_BASE + 0x12B, + eCMD_C011_DEC_CHAN_I_PICTURE_FOUND = eCMD_C011_CMD_BASE + 0x12C, + eCMD_C011_DEC_CHAN_SET_PARAMETER = eCMD_C011_CMD_BASE + 0x12D, + eCMD_C011_DEC_CHAN_SET_USER_DATA_MODE = eCMD_C011_CMD_BASE + 0x12E, + eCMD_C011_DEC_CHAN_SET_PAUSE_DISPLAY_MODE = eCMD_C011_CMD_BASE + 0x12F, + eCMD_C011_DEC_CHAN_SET_SLOW_DISPLAY_MODE = eCMD_C011_CMD_BASE + 0x130, + eCMD_C011_DEC_CHAN_SET_FF_DISPLAY_MODE = eCMD_C011_CMD_BASE + 0x131, + eCMD_C011_DEC_CHAN_SET_DISPLAY_TIMING_MODE = eCMD_C011_CMD_BASE + 0x132, + eCMD_C011_DEC_CHAN_SET_DISPLAY_MODE = eCMD_C011_CMD_BASE + 0x133, + eCMD_C011_DEC_CHAN_GET_DISPLAY_MODE = eCMD_C011_CMD_BASE + 0x134, + eCMD_C011_DEC_CHAN_SET_REVERSE_FIELD = eCMD_C011_CMD_BASE + 0x135, + eCMD_C011_DEC_CHAN_STREAM_OPEN = eCMD_C011_CMD_BASE + 0x136, + eCMD_C011_DEC_CHAN_SET_PCR_PID = eCMD_C011_CMD_BASE + 0x137, + eCMD_C011_DEC_CHAN_SET_VID_PID = eCMD_C011_CMD_BASE + 0x138, + eCMD_C011_DEC_CHAN_SET_PAN_SCAN_MODE = eCMD_C011_CMD_BASE + 0x139, + eCMD_C011_DEC_CHAN_START_DISPLAY_AT_PTS = eCMD_C011_CMD_BASE + 0x140, + eCMD_C011_DEC_CHAN_STOP_DISPLAY_AT_PTS = eCMD_C011_CMD_BASE + 0x141, + eCMD_C011_DEC_CHAN_SET_DISPLAY_ORDER = eCMD_C011_CMD_BASE + 0x142, + eCMD_C011_DEC_CHAN_GET_DISPLAY_ORDER = eCMD_C011_CMD_BASE + 0x143, + eCMD_C011_DEC_CHAN_SET_HOST_TRICK_MODE = eCMD_C011_CMD_BASE + 0x144, + eCMD_C011_DEC_CHAN_SET_OPERATION_MODE = eCMD_C011_CMD_BASE + 0x145, + eCMD_C011_DEC_CHAN_DISPLAY_PAUSE_UNTO_PTS = eCMD_C011_CMD_BASE + 0x146, + eCMD_C011_DEC_CHAN_SET_PTS_STC_DIFF_THRESHOLD = eCMD_C011_CMD_BASE + 0x147, + eCMD_C011_DEC_CHAN_SEND_COMPRESSED_BUF = eCMD_C011_CMD_BASE + 0x148, + eCMD_C011_DEC_CHAN_SET_CLIPPING = eCMD_C011_CMD_BASE + 0x149, + eCMD_C011_DEC_CHAN_SET_PARAMETERS_FOR_HARD_RESET_INTERRUPT_TO_HOST + = eCMD_C011_CMD_BASE + 0x150, + + /* Decoder RevD commands */ + eCMD_C011_DEC_CHAN_SET_CSC = eCMD_C011_CMD_BASE + 0x180, /* color space conversion */ + eCMD_C011_DEC_CHAN_SET_RANGE_REMAP = eCMD_C011_CMD_BASE + 0x181, + eCMD_C011_DEC_CHAN_SET_FGT = eCMD_C011_CMD_BASE + 0x182, + /* Note: 0x183 not implemented yet in Rev D main */ + eCMD_C011_DEC_CHAN_SET_LASTPICTURE_PADDING = eCMD_C011_CMD_BASE + 0x183, + + /* Decoder 7412 commands (7412-only) */ + eCMD_C011_DEC_CHAN_SET_CONTENT_KEY = eCMD_C011_CMD_BASE + 0x190, + eCMD_C011_DEC_CHAN_SET_SESSION_KEY = eCMD_C011_CMD_BASE + 0x191, + eCMD_C011_DEC_CHAN_FMT_CHANGE_ACK = eCMD_C011_CMD_BASE + 0x192, + + eCMD_C011_DEC_CHAN_CUSTOM_VIDOUT = eCMD_C011_CMD_BASE + 0x1FF, + + /* Encoding commands */ + eCMD_C011_ENC_CHAN_OPEN = eCMD_C011_CMD_BASE + 0x200, + eCMD_C011_ENC_CHAN_CLOSE = eCMD_C011_CMD_BASE + 0x201, + eCMD_C011_ENC_CHAN_ACTIVATE = eCMD_C011_CMD_BASE + 0x202, + eCMD_C011_ENC_CHAN_CONTROL = eCMD_C011_CMD_BASE + 0x203, + eCMD_C011_ENC_CHAN_STATISTICS = eCMD_C011_CMD_BASE + 0x204, + + eNOTIFY_C011_ENC_CHAN_EVENT = eCMD_C011_CMD_BASE + 0x210, + +}; + +#endif diff --git a/drivers/staging/crystalhd/crystalhd_hw.c b/drivers/staging/crystalhd/crystalhd_hw.c new file mode 100644 index 00000000..e617d2fc --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd_hw.c @@ -0,0 +1,2390 @@ +/*************************************************************************** + * Copyright (c) 2005-2009, Broadcom Corporation. + * + * Name: crystalhd_hw . c + * + * Description: + * BCM70010 Linux driver HW layer. + * + ********************************************************************** + * This file is part of the crystalhd device driver. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver. If not, see . + **********************************************************************/ + +#include "crystalhd.h" + +#include +#include +#include + +/* Functions internal to this file */ + +static void crystalhd_enable_uarts(struct crystalhd_adp *adp) +{ + bc_dec_reg_wr(adp, UartSelectA, BSVS_UART_STREAM); + bc_dec_reg_wr(adp, UartSelectB, BSVS_UART_DEC_OUTER); +} + + +static void crystalhd_start_dram(struct crystalhd_adp *adp) +{ + bc_dec_reg_wr(adp, SDRAM_PARAM, ((40 / 5 - 1) << 0) | + /* tras (40ns tras)/(5ns period) -1 ((15/5 - 1) << 4) | // trcd */ + ((15 / 5 - 1) << 7) | /* trp */ + ((10 / 5 - 1) << 10) | /* trrd */ + ((15 / 5 + 1) << 12) | /* twr */ + ((2 + 1) << 16) | /* twtr */ + ((70 / 5 - 2) << 19) | /* trfc */ + (0 << 23)); + + bc_dec_reg_wr(adp, SDRAM_PRECHARGE, 0); + bc_dec_reg_wr(adp, SDRAM_EXT_MODE, 2); + bc_dec_reg_wr(adp, SDRAM_MODE, 0x132); + bc_dec_reg_wr(adp, SDRAM_PRECHARGE, 0); + bc_dec_reg_wr(adp, SDRAM_REFRESH, 0); + bc_dec_reg_wr(adp, SDRAM_REFRESH, 0); + bc_dec_reg_wr(adp, SDRAM_MODE, 0x32); + /* setting the refresh rate here */ + bc_dec_reg_wr(adp, SDRAM_REF_PARAM, ((1 << 12) | 96)); +} + + +static bool crystalhd_bring_out_of_rst(struct crystalhd_adp *adp) +{ + union link_misc_perst_deco_ctrl rst_deco_cntrl; + union link_misc_perst_clk_ctrl rst_clk_cntrl; + uint32_t temp; + + /* + * Link clocks: MISC_PERST_CLOCK_CTRL Clear PLL power down bit, + * delay to allow PLL to lock Clear alternate clock, stop clock bits + */ + rst_clk_cntrl.whole_reg = crystalhd_reg_rd(adp, MISC_PERST_CLOCK_CTRL); + rst_clk_cntrl.pll_pwr_dn = 0; + crystalhd_reg_wr(adp, MISC_PERST_CLOCK_CTRL, rst_clk_cntrl.whole_reg); + msleep_interruptible(50); + + rst_clk_cntrl.whole_reg = crystalhd_reg_rd(adp, MISC_PERST_CLOCK_CTRL); + rst_clk_cntrl.stop_core_clk = 0; + rst_clk_cntrl.sel_alt_clk = 0; + + crystalhd_reg_wr(adp, MISC_PERST_CLOCK_CTRL, rst_clk_cntrl.whole_reg); + msleep_interruptible(50); + + /* + * Bus Arbiter Timeout: GISB_ARBITER_TIMER + * Set internal bus arbiter timeout to 40us based on core clock speed + * (63MHz * 40us = 0x9D8) + */ + crystalhd_reg_wr(adp, GISB_ARBITER_TIMER, 0x9D8); + + /* + * Decoder clocks: MISC_PERST_DECODER_CTRL + * Enable clocks while 7412 reset is asserted, delay + * De-assert 7412 reset + */ + rst_deco_cntrl.whole_reg = crystalhd_reg_rd(adp, MISC_PERST_DECODER_CTRL); + rst_deco_cntrl.stop_bcm_7412_clk = 0; + rst_deco_cntrl.bcm7412_rst = 1; + crystalhd_reg_wr(adp, MISC_PERST_DECODER_CTRL, rst_deco_cntrl.whole_reg); + msleep_interruptible(10); + + rst_deco_cntrl.whole_reg = crystalhd_reg_rd(adp, MISC_PERST_DECODER_CTRL); + rst_deco_cntrl.bcm7412_rst = 0; + crystalhd_reg_wr(adp, MISC_PERST_DECODER_CTRL, rst_deco_cntrl.whole_reg); + msleep_interruptible(50); + + /* Disable OTP_CONTENT_MISC to 0 to disable all secure modes */ + crystalhd_reg_wr(adp, OTP_CONTENT_MISC, 0); + + /* Clear bit 29 of 0x404 */ + temp = crystalhd_reg_rd(adp, PCIE_TL_TRANSACTION_CONFIGURATION); + temp &= ~BC_BIT(29); + crystalhd_reg_wr(adp, PCIE_TL_TRANSACTION_CONFIGURATION, temp); + + /* 2.5V regulator must be set to 2.6 volts (+6%) */ + /* FIXME: jarod: what's the point of this reg read? */ + temp = crystalhd_reg_rd(adp, MISC_PERST_VREG_CTRL); + crystalhd_reg_wr(adp, MISC_PERST_VREG_CTRL, 0xF3); + + return true; +} + +static bool crystalhd_put_in_reset(struct crystalhd_adp *adp) +{ + union link_misc_perst_deco_ctrl rst_deco_cntrl; + union link_misc_perst_clk_ctrl rst_clk_cntrl; + uint32_t temp; + + /* + * Decoder clocks: MISC_PERST_DECODER_CTRL + * Assert 7412 reset, delay + * Assert 7412 stop clock + */ + rst_deco_cntrl.whole_reg = crystalhd_reg_rd(adp, MISC_PERST_DECODER_CTRL); + rst_deco_cntrl.stop_bcm_7412_clk = 1; + crystalhd_reg_wr(adp, MISC_PERST_DECODER_CTRL, rst_deco_cntrl.whole_reg); + msleep_interruptible(50); + + /* Bus Arbiter Timeout: GISB_ARBITER_TIMER + * Set internal bus arbiter timeout to 40us based on core clock speed + * (6.75MHZ * 40us = 0x10E) + */ + crystalhd_reg_wr(adp, GISB_ARBITER_TIMER, 0x10E); + + /* Link clocks: MISC_PERST_CLOCK_CTRL + * Stop core clk, delay + * Set alternate clk, delay, set PLL power down + */ + rst_clk_cntrl.whole_reg = crystalhd_reg_rd(adp, MISC_PERST_CLOCK_CTRL); + rst_clk_cntrl.stop_core_clk = 1; + rst_clk_cntrl.sel_alt_clk = 1; + crystalhd_reg_wr(adp, MISC_PERST_CLOCK_CTRL, rst_clk_cntrl.whole_reg); + msleep_interruptible(50); + + rst_clk_cntrl.whole_reg = crystalhd_reg_rd(adp, MISC_PERST_CLOCK_CTRL); + rst_clk_cntrl.pll_pwr_dn = 1; + crystalhd_reg_wr(adp, MISC_PERST_CLOCK_CTRL, rst_clk_cntrl.whole_reg); + + /* + * Read and restore the Transaction Configuration Register + * after core reset + */ + temp = crystalhd_reg_rd(adp, PCIE_TL_TRANSACTION_CONFIGURATION); + + /* + * Link core soft reset: MISC3_RESET_CTRL + * - Write BIT[0]=1 and read it back for core reset to take place + */ + crystalhd_reg_wr(adp, MISC3_RESET_CTRL, 1); + rst_deco_cntrl.whole_reg = crystalhd_reg_rd(adp, MISC3_RESET_CTRL); + msleep_interruptible(50); + + /* restore the transaction configuration register */ + crystalhd_reg_wr(adp, PCIE_TL_TRANSACTION_CONFIGURATION, temp); + + return true; +} + +static void crystalhd_disable_interrupts(struct crystalhd_adp *adp) +{ + union intr_mask_reg intr_mask; + intr_mask.whole_reg = crystalhd_reg_rd(adp, INTR_INTR_MSK_STS_REG); + intr_mask.mask_pcie_err = 1; + intr_mask.mask_pcie_rbusmast_err = 1; + intr_mask.mask_pcie_rgr_bridge = 1; + intr_mask.mask_rx_done = 1; + intr_mask.mask_rx_err = 1; + intr_mask.mask_tx_done = 1; + intr_mask.mask_tx_err = 1; + crystalhd_reg_wr(adp, INTR_INTR_MSK_SET_REG, intr_mask.whole_reg); + + return; +} + +static void crystalhd_enable_interrupts(struct crystalhd_adp *adp) +{ + union intr_mask_reg intr_mask; + intr_mask.whole_reg = crystalhd_reg_rd(adp, INTR_INTR_MSK_STS_REG); + intr_mask.mask_pcie_err = 1; + intr_mask.mask_pcie_rbusmast_err = 1; + intr_mask.mask_pcie_rgr_bridge = 1; + intr_mask.mask_rx_done = 1; + intr_mask.mask_rx_err = 1; + intr_mask.mask_tx_done = 1; + intr_mask.mask_tx_err = 1; + crystalhd_reg_wr(adp, INTR_INTR_MSK_CLR_REG, intr_mask.whole_reg); + + return; +} + +static void crystalhd_clear_errors(struct crystalhd_adp *adp) +{ + uint32_t reg; + + /* FIXME: jarod: wouldn't we want to write a 0 to the reg? Or does the write clear the bits specified? */ + reg = crystalhd_reg_rd(adp, MISC1_Y_RX_ERROR_STATUS); + if (reg) + crystalhd_reg_wr(adp, MISC1_Y_RX_ERROR_STATUS, reg); + + reg = crystalhd_reg_rd(adp, MISC1_UV_RX_ERROR_STATUS); + if (reg) + crystalhd_reg_wr(adp, MISC1_UV_RX_ERROR_STATUS, reg); + + reg = crystalhd_reg_rd(adp, MISC1_TX_DMA_ERROR_STATUS); + if (reg) + crystalhd_reg_wr(adp, MISC1_TX_DMA_ERROR_STATUS, reg); +} + +static void crystalhd_clear_interrupts(struct crystalhd_adp *adp) +{ + uint32_t intr_sts = crystalhd_reg_rd(adp, INTR_INTR_STATUS); + + if (intr_sts) { + crystalhd_reg_wr(adp, INTR_INTR_CLR_REG, intr_sts); + + /* Write End Of Interrupt for PCIE */ + crystalhd_reg_wr(adp, INTR_EOI_CTRL, 1); + } +} + +static void crystalhd_soft_rst(struct crystalhd_adp *adp) +{ + uint32_t val; + + /* Assert c011 soft reset*/ + bc_dec_reg_wr(adp, DecHt_HostSwReset, 0x00000001); + msleep_interruptible(50); + + /* Release c011 soft reset*/ + bc_dec_reg_wr(adp, DecHt_HostSwReset, 0x00000000); + + /* Disable Stuffing..*/ + val = crystalhd_reg_rd(adp, MISC2_GLOBAL_CTRL); + val |= BC_BIT(8); + crystalhd_reg_wr(adp, MISC2_GLOBAL_CTRL, val); +} + +static bool crystalhd_load_firmware_config(struct crystalhd_adp *adp) +{ + uint32_t i = 0, reg; + + crystalhd_reg_wr(adp, DCI_DRAM_BASE_ADDR, (BC_DRAM_FW_CFG_ADDR >> 19)); + + crystalhd_reg_wr(adp, AES_CMD, 0); + crystalhd_reg_wr(adp, AES_CONFIG_INFO, (BC_DRAM_FW_CFG_ADDR & 0x7FFFF)); + crystalhd_reg_wr(adp, AES_CMD, 0x1); + + /* FIXME: jarod: I've seen this fail, and introducing extra delays helps... */ + for (i = 0; i < 100; ++i) { + reg = crystalhd_reg_rd(adp, AES_STATUS); + if (reg & 0x1) + return true; + msleep_interruptible(10); + } + + return false; +} + + +static bool crystalhd_start_device(struct crystalhd_adp *adp) +{ + uint32_t dbg_options, glb_cntrl = 0, reg_pwrmgmt = 0; + + BCMLOG(BCMLOG_INFO, "Starting BCM70012 Device\n"); + + reg_pwrmgmt = crystalhd_reg_rd(adp, PCIE_DLL_DATA_LINK_CONTROL); + reg_pwrmgmt &= ~ASPM_L1_ENABLE; + + crystalhd_reg_wr(adp, PCIE_DLL_DATA_LINK_CONTROL, reg_pwrmgmt); + + if (!crystalhd_bring_out_of_rst(adp)) { + BCMLOG_ERR("Failed To Bring Link Out Of Reset\n"); + return false; + } + + crystalhd_disable_interrupts(adp); + + crystalhd_clear_errors(adp); + + crystalhd_clear_interrupts(adp); + + crystalhd_enable_interrupts(adp); + + /* Enable the option for getting the total no. of DWORDS + * that have been transferred by the RXDMA engine + */ + dbg_options = crystalhd_reg_rd(adp, MISC1_DMA_DEBUG_OPTIONS_REG); + dbg_options |= 0x10; + crystalhd_reg_wr(adp, MISC1_DMA_DEBUG_OPTIONS_REG, dbg_options); + + /* Enable PCI Global Control options */ + glb_cntrl = crystalhd_reg_rd(adp, MISC2_GLOBAL_CTRL); + glb_cntrl |= 0x100; + glb_cntrl |= 0x8000; + crystalhd_reg_wr(adp, MISC2_GLOBAL_CTRL, glb_cntrl); + + crystalhd_enable_interrupts(adp); + + crystalhd_soft_rst(adp); + crystalhd_start_dram(adp); + crystalhd_enable_uarts(adp); + + return true; +} + +static bool crystalhd_stop_device(struct crystalhd_adp *adp) +{ + uint32_t reg; + + BCMLOG(BCMLOG_INFO, "Stopping BCM70012 Device\n"); + /* Clear and disable interrupts */ + crystalhd_disable_interrupts(adp); + crystalhd_clear_errors(adp); + crystalhd_clear_interrupts(adp); + + if (!crystalhd_put_in_reset(adp)) + BCMLOG_ERR("Failed to Put Link To Reset State\n"); + + reg = crystalhd_reg_rd(adp, PCIE_DLL_DATA_LINK_CONTROL); + reg |= ASPM_L1_ENABLE; + crystalhd_reg_wr(adp, PCIE_DLL_DATA_LINK_CONTROL, reg); + + /* Set PCI Clk Req */ + reg = crystalhd_reg_rd(adp, PCIE_CLK_REQ_REG); + reg |= PCI_CLK_REQ_ENABLE; + crystalhd_reg_wr(adp, PCIE_CLK_REQ_REG, reg); + + return true; +} + +static struct crystalhd_rx_dma_pkt *crystalhd_hw_alloc_rx_pkt(struct crystalhd_hw *hw) +{ + unsigned long flags = 0; + struct crystalhd_rx_dma_pkt *temp = NULL; + + if (!hw) + return NULL; + + spin_lock_irqsave(&hw->lock, flags); + temp = hw->rx_pkt_pool_head; + if (temp) { + hw->rx_pkt_pool_head = hw->rx_pkt_pool_head->next; + temp->dio_req = NULL; + temp->pkt_tag = 0; + temp->flags = 0; + } + spin_unlock_irqrestore(&hw->lock, flags); + + return temp; +} + +static void crystalhd_hw_free_rx_pkt(struct crystalhd_hw *hw, + struct crystalhd_rx_dma_pkt *pkt) +{ + unsigned long flags = 0; + + if (!hw || !pkt) + return; + + spin_lock_irqsave(&hw->lock, flags); + pkt->next = hw->rx_pkt_pool_head; + hw->rx_pkt_pool_head = pkt; + spin_unlock_irqrestore(&hw->lock, flags); +} + +/* + * Call back from TX - IOQ deletion. + * + * This routine will release the TX DMA rings allocated + * druing setup_dma rings interface. + * + * Memory is allocated per DMA ring basis. This is just + * a place holder to be able to create the dio queues. + */ +static void crystalhd_tx_desc_rel_call_back(void *context, void *data) +{ +} + +/* + * Rx Packet release callback.. + * + * Release All user mapped capture buffers and Our DMA packets + * back to our free pool. The actual cleanup of the DMA + * ring descriptors happen during dma ring release. + */ +static void crystalhd_rx_pkt_rel_call_back(void *context, void *data) +{ + struct crystalhd_hw *hw = (struct crystalhd_hw *)context; + struct crystalhd_rx_dma_pkt *pkt = (struct crystalhd_rx_dma_pkt *)data; + + if (!pkt || !hw) { + BCMLOG_ERR("Invalid arg - %p %p\n", hw, pkt); + return; + } + + if (pkt->dio_req) + crystalhd_unmap_dio(hw->adp, pkt->dio_req); + else + BCMLOG_ERR("Missing dio_req: 0x%x\n", pkt->pkt_tag); + + crystalhd_hw_free_rx_pkt(hw, pkt); +} + +#define crystalhd_hw_delete_ioq(adp, q) \ + if (q) { \ + crystalhd_delete_dioq(adp, q); \ + q = NULL; \ + } + +static void crystalhd_hw_delete_ioqs(struct crystalhd_hw *hw) +{ + if (!hw) + return; + + BCMLOG(BCMLOG_DBG, "Deleting IOQs\n"); + crystalhd_hw_delete_ioq(hw->adp, hw->tx_actq); + crystalhd_hw_delete_ioq(hw->adp, hw->tx_freeq); + crystalhd_hw_delete_ioq(hw->adp, hw->rx_actq); + crystalhd_hw_delete_ioq(hw->adp, hw->rx_freeq); + crystalhd_hw_delete_ioq(hw->adp, hw->rx_rdyq); +} + +#define crystalhd_hw_create_ioq(sts, hw, q, cb) \ +do { \ + sts = crystalhd_create_dioq(hw->adp, &q, cb, hw); \ + if (sts != BC_STS_SUCCESS) \ + goto hw_create_ioq_err; \ +} while (0) + +/* + * Create IOQs.. + * + * TX - Active & Free + * RX - Active, Ready and Free. + */ +static enum BC_STATUS crystalhd_hw_create_ioqs(struct crystalhd_hw *hw) +{ + enum BC_STATUS sts = BC_STS_SUCCESS; + + if (!hw) { + BCMLOG_ERR("Invalid Arg!!\n"); + return BC_STS_INV_ARG; + } + + crystalhd_hw_create_ioq(sts, hw, hw->tx_freeq, + crystalhd_tx_desc_rel_call_back); + crystalhd_hw_create_ioq(sts, hw, hw->tx_actq, + crystalhd_tx_desc_rel_call_back); + + crystalhd_hw_create_ioq(sts, hw, hw->rx_freeq, + crystalhd_rx_pkt_rel_call_back); + crystalhd_hw_create_ioq(sts, hw, hw->rx_rdyq, + crystalhd_rx_pkt_rel_call_back); + crystalhd_hw_create_ioq(sts, hw, hw->rx_actq, + crystalhd_rx_pkt_rel_call_back); + + return sts; + +hw_create_ioq_err: + crystalhd_hw_delete_ioqs(hw); + + return sts; +} + + +static bool crystalhd_code_in_full(struct crystalhd_adp *adp, uint32_t needed_sz, + bool b_188_byte_pkts, uint8_t flags) +{ + uint32_t base, end, writep, readp; + uint32_t cpbSize, cpbFullness, fifoSize; + + if (flags & 0x02) { /* ASF Bit is set */ + base = bc_dec_reg_rd(adp, REG_Dec_TsAudCDB2Base); + end = bc_dec_reg_rd(adp, REG_Dec_TsAudCDB2End); + writep = bc_dec_reg_rd(adp, REG_Dec_TsAudCDB2Wrptr); + readp = bc_dec_reg_rd(adp, REG_Dec_TsAudCDB2Rdptr); + } else if (b_188_byte_pkts) { /*Encrypted 188 byte packets*/ + base = bc_dec_reg_rd(adp, REG_Dec_TsUser0Base); + end = bc_dec_reg_rd(adp, REG_Dec_TsUser0End); + writep = bc_dec_reg_rd(adp, REG_Dec_TsUser0Wrptr); + readp = bc_dec_reg_rd(adp, REG_Dec_TsUser0Rdptr); + } else { + base = bc_dec_reg_rd(adp, REG_DecCA_RegCinBase); + end = bc_dec_reg_rd(adp, REG_DecCA_RegCinEnd); + writep = bc_dec_reg_rd(adp, REG_DecCA_RegCinWrPtr); + readp = bc_dec_reg_rd(adp, REG_DecCA_RegCinRdPtr); + } + + cpbSize = end - base; + if (writep >= readp) + cpbFullness = writep - readp; + else + cpbFullness = (end - base) - (readp - writep); + + fifoSize = cpbSize - cpbFullness; + + if (fifoSize < BC_INFIFO_THRESHOLD) + return true; + + if (needed_sz > (fifoSize - BC_INFIFO_THRESHOLD)) + return true; + + return false; +} + +static enum BC_STATUS crystalhd_hw_tx_req_complete(struct crystalhd_hw *hw, + uint32_t list_id, enum BC_STATUS cs) +{ + struct tx_dma_pkt *tx_req; + + if (!hw || !list_id) { + BCMLOG_ERR("Invalid Arg..\n"); + return BC_STS_INV_ARG; + } + + hw->pwr_lock--; + + tx_req = (struct tx_dma_pkt *)crystalhd_dioq_find_and_fetch(hw->tx_actq, list_id); + if (!tx_req) { + if (cs != BC_STS_IO_USER_ABORT) + BCMLOG_ERR("Find and Fetch Did not find req\n"); + return BC_STS_NO_DATA; + } + + if (tx_req->call_back) { + tx_req->call_back(tx_req->dio_req, tx_req->cb_event, cs); + tx_req->dio_req = NULL; + tx_req->cb_event = NULL; + tx_req->call_back = NULL; + } else { + BCMLOG(BCMLOG_DBG, "Missing Tx Callback - %X\n", + tx_req->list_tag); + } + + /* Now put back the tx_list back in FreeQ */ + tx_req->list_tag = 0; + + return crystalhd_dioq_add(hw->tx_freeq, tx_req, false, 0); +} + +static bool crystalhd_tx_list0_handler(struct crystalhd_hw *hw, uint32_t err_sts) +{ + uint32_t err_mask, tmp; + unsigned long flags = 0; + + err_mask = MISC1_TX_DMA_ERROR_STATUS_TX_L0_DESC_TX_ABORT_ERRORS_MASK | + MISC1_TX_DMA_ERROR_STATUS_TX_L0_DMA_DATA_TX_ABORT_ERRORS_MASK | + MISC1_TX_DMA_ERROR_STATUS_TX_L0_FIFO_FULL_ERRORS_MASK; + + if (!(err_sts & err_mask)) + return false; + + BCMLOG_ERR("Error on Tx-L0 %x\n", err_sts); + + tmp = err_mask; + + if (err_sts & MISC1_TX_DMA_ERROR_STATUS_TX_L0_FIFO_FULL_ERRORS_MASK) + tmp &= ~MISC1_TX_DMA_ERROR_STATUS_TX_L0_FIFO_FULL_ERRORS_MASK; + + if (tmp) { + spin_lock_irqsave(&hw->lock, flags); + /* reset list index.*/ + hw->tx_list_post_index = 0; + spin_unlock_irqrestore(&hw->lock, flags); + } + + tmp = err_sts & err_mask; + crystalhd_reg_wr(hw->adp, MISC1_TX_DMA_ERROR_STATUS, tmp); + + return true; +} + +static bool crystalhd_tx_list1_handler(struct crystalhd_hw *hw, uint32_t err_sts) +{ + uint32_t err_mask, tmp; + unsigned long flags = 0; + + err_mask = MISC1_TX_DMA_ERROR_STATUS_TX_L1_DESC_TX_ABORT_ERRORS_MASK | + MISC1_TX_DMA_ERROR_STATUS_TX_L1_DMA_DATA_TX_ABORT_ERRORS_MASK | + MISC1_TX_DMA_ERROR_STATUS_TX_L1_FIFO_FULL_ERRORS_MASK; + + if (!(err_sts & err_mask)) + return false; + + BCMLOG_ERR("Error on Tx-L1 %x\n", err_sts); + + tmp = err_mask; + + if (err_sts & MISC1_TX_DMA_ERROR_STATUS_TX_L1_FIFO_FULL_ERRORS_MASK) + tmp &= ~MISC1_TX_DMA_ERROR_STATUS_TX_L1_FIFO_FULL_ERRORS_MASK; + + if (tmp) { + spin_lock_irqsave(&hw->lock, flags); + /* reset list index.*/ + hw->tx_list_post_index = 0; + spin_unlock_irqrestore(&hw->lock, flags); + } + + tmp = err_sts & err_mask; + crystalhd_reg_wr(hw->adp, MISC1_TX_DMA_ERROR_STATUS, tmp); + + return true; +} + +static void crystalhd_tx_isr(struct crystalhd_hw *hw, uint32_t int_sts) +{ + uint32_t err_sts; + + if (int_sts & INTR_INTR_STATUS_L0_TX_DMA_DONE_INTR_MASK) + crystalhd_hw_tx_req_complete(hw, hw->tx_ioq_tag_seed + 0, + BC_STS_SUCCESS); + + if (int_sts & INTR_INTR_STATUS_L1_TX_DMA_DONE_INTR_MASK) + crystalhd_hw_tx_req_complete(hw, hw->tx_ioq_tag_seed + 1, + BC_STS_SUCCESS); + + if (!(int_sts & (INTR_INTR_STATUS_L0_TX_DMA_ERR_INTR_MASK | + INTR_INTR_STATUS_L1_TX_DMA_ERR_INTR_MASK))) { + /* No error mask set.. */ + return; + } + + /* Handle Tx errors. */ + err_sts = crystalhd_reg_rd(hw->adp, MISC1_TX_DMA_ERROR_STATUS); + + if (crystalhd_tx_list0_handler(hw, err_sts)) + crystalhd_hw_tx_req_complete(hw, hw->tx_ioq_tag_seed + 0, + BC_STS_ERROR); + + if (crystalhd_tx_list1_handler(hw, err_sts)) + crystalhd_hw_tx_req_complete(hw, hw->tx_ioq_tag_seed + 1, + BC_STS_ERROR); + + hw->stats.tx_errors++; +} + +static void crystalhd_hw_dump_desc(struct dma_descriptor *p_dma_desc, + uint32_t ul_desc_index, uint32_t cnt) +{ + uint32_t ix, ll = 0; + + if (!p_dma_desc || !cnt) + return; + + /* FIXME: jarod: perhaps a modparam desc_debug to enable this, rather than + * setting ll (log level, I presume) to non-zero? */ + if (!ll) + return; + + for (ix = ul_desc_index; ix < (ul_desc_index + cnt); ix++) { + BCMLOG(ll, "%s[%d] Buff[%x:%x] Next:[%x:%x] XferSz:%x Intr:%x,Last:%x\n", + ((p_dma_desc[ul_desc_index].dma_dir) ? "TDesc" : "RDesc"), + ul_desc_index, + p_dma_desc[ul_desc_index].buff_addr_high, + p_dma_desc[ul_desc_index].buff_addr_low, + p_dma_desc[ul_desc_index].next_desc_addr_high, + p_dma_desc[ul_desc_index].next_desc_addr_low, + p_dma_desc[ul_desc_index].xfer_size, + p_dma_desc[ul_desc_index].intr_enable, + p_dma_desc[ul_desc_index].last_rec_indicator); + } + +} + +static enum BC_STATUS crystalhd_hw_fill_desc(struct crystalhd_dio_req *ioreq, + struct dma_descriptor *desc, + dma_addr_t desc_paddr_base, + uint32_t sg_cnt, uint32_t sg_st_ix, + uint32_t sg_st_off, uint32_t xfr_sz) +{ + uint32_t count = 0, ix = 0, sg_ix = 0, len = 0, last_desc_ix = 0; + dma_addr_t desc_phy_addr = desc_paddr_base; + union addr_64 addr_temp; + + if (!ioreq || !desc || !desc_paddr_base || !xfr_sz || + (!sg_cnt && !ioreq->uinfo.dir_tx)) { + BCMLOG_ERR("Invalid Args\n"); + return BC_STS_INV_ARG; + } + + for (ix = 0; ix < sg_cnt; ix++) { + + /* Setup SGLE index. */ + sg_ix = ix + sg_st_ix; + + /* Get SGLE length */ + len = crystalhd_get_sgle_len(ioreq, sg_ix); + if (len % 4) { + BCMLOG_ERR(" len in sg %d %d %d\n", len, sg_ix, sg_cnt); + return BC_STS_NOT_IMPL; + } + /* Setup DMA desc with Phy addr & Length at current index. */ + addr_temp.full_addr = crystalhd_get_sgle_paddr(ioreq, sg_ix); + if (sg_ix == sg_st_ix) { + addr_temp.full_addr += sg_st_off; + len -= sg_st_off; + } + memset(&desc[ix], 0, sizeof(desc[ix])); + desc[ix].buff_addr_low = addr_temp.low_part; + desc[ix].buff_addr_high = addr_temp.high_part; + desc[ix].dma_dir = ioreq->uinfo.dir_tx; + + /* Chain DMA descriptor. */ + addr_temp.full_addr = desc_phy_addr + sizeof(struct dma_descriptor); + desc[ix].next_desc_addr_low = addr_temp.low_part; + desc[ix].next_desc_addr_high = addr_temp.high_part; + + if ((count + len) > xfr_sz) + len = xfr_sz - count; + + /* Debug.. */ + if ((!len) || (len > crystalhd_get_sgle_len(ioreq, sg_ix))) { + BCMLOG_ERR("inv-len(%x) Ix(%d) count:%x xfr_sz:%x sg_cnt:%d\n", + len, ix, count, xfr_sz, sg_cnt); + return BC_STS_ERROR; + } + /* Length expects Multiple of 4 */ + desc[ix].xfer_size = (len / 4); + + crystalhd_hw_dump_desc(desc, ix, 1); + + count += len; + desc_phy_addr += sizeof(struct dma_descriptor); + } + + last_desc_ix = ix - 1; + + if (ioreq->fb_size) { + memset(&desc[ix], 0, sizeof(desc[ix])); + addr_temp.full_addr = ioreq->fb_pa; + desc[ix].buff_addr_low = addr_temp.low_part; + desc[ix].buff_addr_high = addr_temp.high_part; + desc[ix].dma_dir = ioreq->uinfo.dir_tx; + desc[ix].xfer_size = 1; + desc[ix].fill_bytes = 4 - ioreq->fb_size; + count += ioreq->fb_size; + last_desc_ix++; + } + + /* setup last descriptor..*/ + desc[last_desc_ix].last_rec_indicator = 1; + desc[last_desc_ix].next_desc_addr_low = 0; + desc[last_desc_ix].next_desc_addr_high = 0; + desc[last_desc_ix].intr_enable = 1; + + crystalhd_hw_dump_desc(desc, last_desc_ix, 1); + + if (count != xfr_sz) { + BCMLOG_ERR("internal error sz curr:%x exp:%x\n", count, xfr_sz); + return BC_STS_ERROR; + } + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS crystalhd_xlat_sgl_to_dma_desc(struct crystalhd_dio_req *ioreq, + struct dma_desc_mem *pdesc_mem, + uint32_t *uv_desc_index) +{ + struct dma_descriptor *desc = NULL; + dma_addr_t desc_paddr_base = 0; + uint32_t sg_cnt = 0, sg_st_ix = 0, sg_st_off = 0; + uint32_t xfr_sz = 0; + enum BC_STATUS sts = BC_STS_SUCCESS; + + /* Check params.. */ + if (!ioreq || !pdesc_mem || !uv_desc_index) { + BCMLOG_ERR("Invalid Args\n"); + return BC_STS_INV_ARG; + } + + if (!pdesc_mem->sz || !pdesc_mem->pdma_desc_start || + !ioreq->sg || (!ioreq->sg_cnt && !ioreq->uinfo.dir_tx)) { + BCMLOG_ERR("Invalid Args\n"); + return BC_STS_INV_ARG; + } + + if ((ioreq->uinfo.dir_tx) && (ioreq->uinfo.uv_offset)) { + BCMLOG_ERR("UV offset for TX??\n"); + return BC_STS_INV_ARG; + + } + + desc = pdesc_mem->pdma_desc_start; + desc_paddr_base = pdesc_mem->phy_addr; + + if (ioreq->uinfo.dir_tx || (ioreq->uinfo.uv_offset == 0)) { + sg_cnt = ioreq->sg_cnt; + xfr_sz = ioreq->uinfo.xfr_len; + } else { + sg_cnt = ioreq->uinfo.uv_sg_ix + 1; + xfr_sz = ioreq->uinfo.uv_offset; + } + + sts = crystalhd_hw_fill_desc(ioreq, desc, desc_paddr_base, sg_cnt, + sg_st_ix, sg_st_off, xfr_sz); + + if ((sts != BC_STS_SUCCESS) || !ioreq->uinfo.uv_offset) + return sts; + + /* Prepare for UV mapping.. */ + desc = &pdesc_mem->pdma_desc_start[sg_cnt]; + desc_paddr_base = pdesc_mem->phy_addr + + (sg_cnt * sizeof(struct dma_descriptor)); + + /* Done with desc addr.. now update sg stuff.*/ + sg_cnt = ioreq->sg_cnt - ioreq->uinfo.uv_sg_ix; + xfr_sz = ioreq->uinfo.xfr_len - ioreq->uinfo.uv_offset; + sg_st_ix = ioreq->uinfo.uv_sg_ix; + sg_st_off = ioreq->uinfo.uv_sg_off; + + sts = crystalhd_hw_fill_desc(ioreq, desc, desc_paddr_base, sg_cnt, + sg_st_ix, sg_st_off, xfr_sz); + if (sts != BC_STS_SUCCESS) + return sts; + + *uv_desc_index = sg_st_ix; + + return sts; +} + +static void crystalhd_start_tx_dma_engine(struct crystalhd_hw *hw) +{ + uint32_t dma_cntrl; + + dma_cntrl = crystalhd_reg_rd(hw->adp, MISC1_TX_SW_DESC_LIST_CTRL_STS); + if (!(dma_cntrl & DMA_START_BIT)) { + dma_cntrl |= DMA_START_BIT; + crystalhd_reg_wr(hw->adp, MISC1_TX_SW_DESC_LIST_CTRL_STS, + dma_cntrl); + } + + return; +} + +/* _CHECK_THIS_ + * + * Verify if the Stop generates a completion interrupt or not. + * if it does not generate an interrupt, then add polling here. + */ +static enum BC_STATUS crystalhd_stop_tx_dma_engine(struct crystalhd_hw *hw) +{ + uint32_t dma_cntrl, cnt = 30; + uint32_t l1 = 1, l2 = 1; + unsigned long flags = 0; + + dma_cntrl = crystalhd_reg_rd(hw->adp, MISC1_TX_SW_DESC_LIST_CTRL_STS); + + BCMLOG(BCMLOG_DBG, "Stopping TX DMA Engine..\n"); + + if (!(dma_cntrl & DMA_START_BIT)) { + BCMLOG(BCMLOG_DBG, "Already Stopped\n"); + return BC_STS_SUCCESS; + } + + crystalhd_disable_interrupts(hw->adp); + + /* Issue stop to HW */ + /* This bit when set gave problems. Please check*/ + dma_cntrl &= ~DMA_START_BIT; + crystalhd_reg_wr(hw->adp, MISC1_TX_SW_DESC_LIST_CTRL_STS, dma_cntrl); + + BCMLOG(BCMLOG_DBG, "Cleared the DMA Start bit\n"); + + /* Poll for 3seconds (30 * 100ms) on both the lists..*/ + while ((l1 || l2) && cnt) { + + if (l1) { + l1 = crystalhd_reg_rd(hw->adp, MISC1_TX_FIRST_DESC_L_ADDR_LIST0); + l1 &= DMA_START_BIT; + } + + if (l2) { + l2 = crystalhd_reg_rd(hw->adp, MISC1_TX_FIRST_DESC_L_ADDR_LIST1); + l2 &= DMA_START_BIT; + } + + msleep_interruptible(100); + + cnt--; + } + + if (!cnt) { + BCMLOG_ERR("Failed to stop TX DMA.. l1 %d, l2 %d\n", l1, l2); + crystalhd_enable_interrupts(hw->adp); + return BC_STS_ERROR; + } + + spin_lock_irqsave(&hw->lock, flags); + hw->tx_list_post_index = 0; + spin_unlock_irqrestore(&hw->lock, flags); + BCMLOG(BCMLOG_DBG, "stopped TX DMA..\n"); + crystalhd_enable_interrupts(hw->adp); + + return BC_STS_SUCCESS; +} + +static uint32_t crystalhd_get_pib_avail_cnt(struct crystalhd_hw *hw) +{ + /* + * Position of the PIB Entries can be found at + * 0th and the 1st location of the Circular list. + */ + uint32_t Q_addr; + uint32_t pib_cnt, r_offset, w_offset; + + Q_addr = hw->pib_del_Q_addr; + + /* Get the Read Pointer */ + crystalhd_mem_rd(hw->adp, Q_addr, 1, &r_offset); + + /* Get the Write Pointer */ + crystalhd_mem_rd(hw->adp, Q_addr + sizeof(uint32_t), 1, &w_offset); + + if (r_offset == w_offset) + return 0; /* Queue is empty */ + + if (w_offset > r_offset) + pib_cnt = w_offset - r_offset; + else + pib_cnt = (w_offset + MAX_PIB_Q_DEPTH) - + (r_offset + MIN_PIB_Q_DEPTH); + + if (pib_cnt > MAX_PIB_Q_DEPTH) { + BCMLOG_ERR("Invalid PIB Count (%u)\n", pib_cnt); + return 0; + } + + return pib_cnt; +} + +static uint32_t crystalhd_get_addr_from_pib_Q(struct crystalhd_hw *hw) +{ + uint32_t Q_addr; + uint32_t addr_entry, r_offset, w_offset; + + Q_addr = hw->pib_del_Q_addr; + + /* Get the Read Pointer 0Th Location is Read Pointer */ + crystalhd_mem_rd(hw->adp, Q_addr, 1, &r_offset); + + /* Get the Write Pointer 1st Location is Write pointer */ + crystalhd_mem_rd(hw->adp, Q_addr + sizeof(uint32_t), 1, &w_offset); + + /* Queue is empty */ + if (r_offset == w_offset) + return 0; + + if ((r_offset < MIN_PIB_Q_DEPTH) || (r_offset >= MAX_PIB_Q_DEPTH)) + return 0; + + /* Get the Actual Address of the PIB */ + crystalhd_mem_rd(hw->adp, Q_addr + (r_offset * sizeof(uint32_t)), + 1, &addr_entry); + + /* Increment the Read Pointer */ + r_offset++; + + if (MAX_PIB_Q_DEPTH == r_offset) + r_offset = MIN_PIB_Q_DEPTH; + + /* Write back the read pointer to It's Location */ + crystalhd_mem_wr(hw->adp, Q_addr, 1, &r_offset); + + return addr_entry; +} + +static bool crystalhd_rel_addr_to_pib_Q(struct crystalhd_hw *hw, uint32_t addr_to_rel) +{ + uint32_t Q_addr; + uint32_t r_offset, w_offset, n_offset; + + Q_addr = hw->pib_rel_Q_addr; + + /* Get the Read Pointer */ + crystalhd_mem_rd(hw->adp, Q_addr, 1, &r_offset); + + /* Get the Write Pointer */ + crystalhd_mem_rd(hw->adp, Q_addr + sizeof(uint32_t), 1, &w_offset); + + if ((r_offset < MIN_PIB_Q_DEPTH) || + (r_offset >= MAX_PIB_Q_DEPTH)) + return false; + + n_offset = w_offset + 1; + + if (MAX_PIB_Q_DEPTH == n_offset) + n_offset = MIN_PIB_Q_DEPTH; + + if (r_offset == n_offset) + return false; /* should never happen */ + + /* Write the DRAM ADDR to the Queue at Next Offset */ + crystalhd_mem_wr(hw->adp, Q_addr + (w_offset * sizeof(uint32_t)), + 1, &addr_to_rel); + + /* Put the New value of the write pointer in Queue */ + crystalhd_mem_wr(hw->adp, Q_addr + sizeof(uint32_t), 1, &n_offset); + + return true; +} + +static void cpy_pib_to_app(struct c011_pib *src_pib, struct BC_PIC_INFO_BLOCK *dst_pib) +{ + if (!src_pib || !dst_pib) { + BCMLOG_ERR("Invalid Arguments\n"); + return; + } + + dst_pib->timeStamp = 0; + dst_pib->picture_number = src_pib->ppb.picture_number; + dst_pib->width = src_pib->ppb.width; + dst_pib->height = src_pib->ppb.height; + dst_pib->chroma_format = src_pib->ppb.chroma_format; + dst_pib->pulldown = src_pib->ppb.pulldown; + dst_pib->flags = src_pib->ppb.flags; + dst_pib->sess_num = src_pib->ptsStcOffset; + dst_pib->aspect_ratio = src_pib->ppb.aspect_ratio; + dst_pib->colour_primaries = src_pib->ppb.colour_primaries; + dst_pib->picture_meta_payload = src_pib->ppb.picture_meta_payload; + dst_pib->frame_rate = src_pib->resolution ; + return; +} + +static void crystalhd_hw_proc_pib(struct crystalhd_hw *hw) +{ + unsigned int cnt; + struct c011_pib src_pib; + uint32_t pib_addr, pib_cnt; + struct BC_PIC_INFO_BLOCK *AppPib; + struct crystalhd_rx_dma_pkt *rx_pkt = NULL; + + pib_cnt = crystalhd_get_pib_avail_cnt(hw); + + if (!pib_cnt) + return; + + for (cnt = 0; cnt < pib_cnt; cnt++) { + + pib_addr = crystalhd_get_addr_from_pib_Q(hw); + crystalhd_mem_rd(hw->adp, pib_addr, sizeof(struct c011_pib) / 4, + (uint32_t *)&src_pib); + + if (src_pib.bFormatChange) { + rx_pkt = (struct crystalhd_rx_dma_pkt *)crystalhd_dioq_fetch(hw->rx_freeq); + if (!rx_pkt) + return; + rx_pkt->flags = 0; + rx_pkt->flags |= COMP_FLAG_PIB_VALID | COMP_FLAG_FMT_CHANGE; + AppPib = &rx_pkt->pib; + cpy_pib_to_app(&src_pib, AppPib); + + BCMLOG(BCMLOG_DBG, + "App PIB:%x %x %x %x %x %x %x %x %x %x\n", + rx_pkt->pib.picture_number, + rx_pkt->pib.aspect_ratio, + rx_pkt->pib.chroma_format, + rx_pkt->pib.colour_primaries, + rx_pkt->pib.frame_rate, + rx_pkt->pib.height, + rx_pkt->pib.height, + rx_pkt->pib.n_drop, + rx_pkt->pib.pulldown, + rx_pkt->pib.ycom); + + crystalhd_dioq_add(hw->rx_rdyq, (void *)rx_pkt, true, rx_pkt->pkt_tag); + + } + + crystalhd_rel_addr_to_pib_Q(hw, pib_addr); + } +} + +static void crystalhd_start_rx_dma_engine(struct crystalhd_hw *hw) +{ + uint32_t dma_cntrl; + + dma_cntrl = crystalhd_reg_rd(hw->adp, MISC1_Y_RX_SW_DESC_LIST_CTRL_STS); + if (!(dma_cntrl & DMA_START_BIT)) { + dma_cntrl |= DMA_START_BIT; + crystalhd_reg_wr(hw->adp, MISC1_Y_RX_SW_DESC_LIST_CTRL_STS, dma_cntrl); + } + + dma_cntrl = crystalhd_reg_rd(hw->adp, MISC1_UV_RX_SW_DESC_LIST_CTRL_STS); + if (!(dma_cntrl & DMA_START_BIT)) { + dma_cntrl |= DMA_START_BIT; + crystalhd_reg_wr(hw->adp, MISC1_UV_RX_SW_DESC_LIST_CTRL_STS, dma_cntrl); + } + + return; +} + +static void crystalhd_stop_rx_dma_engine(struct crystalhd_hw *hw) +{ + uint32_t dma_cntrl = 0, count = 30; + uint32_t l0y = 1, l0uv = 1, l1y = 1, l1uv = 1; + + dma_cntrl = crystalhd_reg_rd(hw->adp, MISC1_Y_RX_SW_DESC_LIST_CTRL_STS); + if ((dma_cntrl & DMA_START_BIT)) { + dma_cntrl &= ~DMA_START_BIT; + crystalhd_reg_wr(hw->adp, MISC1_Y_RX_SW_DESC_LIST_CTRL_STS, dma_cntrl); + } + + dma_cntrl = crystalhd_reg_rd(hw->adp, MISC1_UV_RX_SW_DESC_LIST_CTRL_STS); + if ((dma_cntrl & DMA_START_BIT)) { + dma_cntrl &= ~DMA_START_BIT; + crystalhd_reg_wr(hw->adp, MISC1_UV_RX_SW_DESC_LIST_CTRL_STS, dma_cntrl); + } + + /* Poll for 3seconds (30 * 100ms) on both the lists..*/ + while ((l0y || l0uv || l1y || l1uv) && count) { + + if (l0y) { + l0y = crystalhd_reg_rd(hw->adp, MISC1_Y_RX_FIRST_DESC_L_ADDR_LIST0); + l0y &= DMA_START_BIT; + if (!l0y) + hw->rx_list_sts[0] &= ~rx_waiting_y_intr; + } + + if (l1y) { + l1y = crystalhd_reg_rd(hw->adp, MISC1_Y_RX_FIRST_DESC_L_ADDR_LIST1); + l1y &= DMA_START_BIT; + if (!l1y) + hw->rx_list_sts[1] &= ~rx_waiting_y_intr; + } + + if (l0uv) { + l0uv = crystalhd_reg_rd(hw->adp, MISC1_UV_RX_FIRST_DESC_L_ADDR_LIST0); + l0uv &= DMA_START_BIT; + if (!l0uv) + hw->rx_list_sts[0] &= ~rx_waiting_uv_intr; + } + + if (l1uv) { + l1uv = crystalhd_reg_rd(hw->adp, MISC1_UV_RX_FIRST_DESC_L_ADDR_LIST1); + l1uv &= DMA_START_BIT; + if (!l1uv) + hw->rx_list_sts[1] &= ~rx_waiting_uv_intr; + } + msleep_interruptible(100); + count--; + } + + hw->rx_list_post_index = 0; + + BCMLOG(BCMLOG_SSTEP, "Capture Stop: %d List0:Sts:%x List1:Sts:%x\n", + count, hw->rx_list_sts[0], hw->rx_list_sts[1]); +} + +static enum BC_STATUS crystalhd_hw_prog_rxdma(struct crystalhd_hw *hw, struct crystalhd_rx_dma_pkt *rx_pkt) +{ + uint32_t y_low_addr_reg, y_high_addr_reg; + uint32_t uv_low_addr_reg, uv_high_addr_reg; + union addr_64 desc_addr; + unsigned long flags; + + if (!hw || !rx_pkt) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + if (hw->rx_list_post_index >= DMA_ENGINE_CNT) { + BCMLOG_ERR("List Out Of bounds %x\n", hw->rx_list_post_index); + return BC_STS_INV_ARG; + } + + spin_lock_irqsave(&hw->rx_lock, flags); + /* FIXME: jarod: sts_free is an enum for 0, in crystalhd_hw.h... yuk... */ + if (sts_free != hw->rx_list_sts[hw->rx_list_post_index]) { + spin_unlock_irqrestore(&hw->rx_lock, flags); + return BC_STS_BUSY; + } + + if (!hw->rx_list_post_index) { + y_low_addr_reg = MISC1_Y_RX_FIRST_DESC_L_ADDR_LIST0; + y_high_addr_reg = MISC1_Y_RX_FIRST_DESC_U_ADDR_LIST0; + uv_low_addr_reg = MISC1_UV_RX_FIRST_DESC_L_ADDR_LIST0; + uv_high_addr_reg = MISC1_UV_RX_FIRST_DESC_U_ADDR_LIST0; + } else { + y_low_addr_reg = MISC1_Y_RX_FIRST_DESC_L_ADDR_LIST1; + y_high_addr_reg = MISC1_Y_RX_FIRST_DESC_U_ADDR_LIST1; + uv_low_addr_reg = MISC1_UV_RX_FIRST_DESC_L_ADDR_LIST1; + uv_high_addr_reg = MISC1_UV_RX_FIRST_DESC_U_ADDR_LIST1; + } + rx_pkt->pkt_tag = hw->rx_pkt_tag_seed + hw->rx_list_post_index; + hw->rx_list_sts[hw->rx_list_post_index] |= rx_waiting_y_intr; + if (rx_pkt->uv_phy_addr) + hw->rx_list_sts[hw->rx_list_post_index] |= rx_waiting_uv_intr; + hw->rx_list_post_index = (hw->rx_list_post_index + 1) % DMA_ENGINE_CNT; + spin_unlock_irqrestore(&hw->rx_lock, flags); + + crystalhd_dioq_add(hw->rx_actq, (void *)rx_pkt, false, rx_pkt->pkt_tag); + + crystalhd_start_rx_dma_engine(hw); + /* Program the Y descriptor */ + desc_addr.full_addr = rx_pkt->desc_mem.phy_addr; + crystalhd_reg_wr(hw->adp, y_high_addr_reg, desc_addr.high_part); + crystalhd_reg_wr(hw->adp, y_low_addr_reg, desc_addr.low_part | 0x01); + + if (rx_pkt->uv_phy_addr) { + /* Program the UV descriptor */ + desc_addr.full_addr = rx_pkt->uv_phy_addr; + crystalhd_reg_wr(hw->adp, uv_high_addr_reg, desc_addr.high_part); + crystalhd_reg_wr(hw->adp, uv_low_addr_reg, desc_addr.low_part | 0x01); + } + + return BC_STS_SUCCESS; +} + +static enum BC_STATUS crystalhd_hw_post_cap_buff(struct crystalhd_hw *hw, + struct crystalhd_rx_dma_pkt *rx_pkt) +{ + enum BC_STATUS sts = crystalhd_hw_prog_rxdma(hw, rx_pkt); + + if (sts == BC_STS_BUSY) + crystalhd_dioq_add(hw->rx_freeq, (void *)rx_pkt, + false, rx_pkt->pkt_tag); + + return sts; +} + +static void crystalhd_get_dnsz(struct crystalhd_hw *hw, uint32_t list_index, + uint32_t *y_dw_dnsz, uint32_t *uv_dw_dnsz) +{ + uint32_t y_dn_sz_reg, uv_dn_sz_reg; + + if (!list_index) { + y_dn_sz_reg = MISC1_Y_RX_LIST0_CUR_BYTE_CNT; + uv_dn_sz_reg = MISC1_UV_RX_LIST0_CUR_BYTE_CNT; + } else { + y_dn_sz_reg = MISC1_Y_RX_LIST1_CUR_BYTE_CNT; + uv_dn_sz_reg = MISC1_UV_RX_LIST1_CUR_BYTE_CNT; + } + + *y_dw_dnsz = crystalhd_reg_rd(hw->adp, y_dn_sz_reg); + *uv_dw_dnsz = crystalhd_reg_rd(hw->adp, uv_dn_sz_reg); +} + +/* + * This function should be called only after making sure that the two DMA + * lists are free. This function does not check if DMA's are active, before + * turning off the DMA. + */ +static void crystalhd_hw_finalize_pause(struct crystalhd_hw *hw) +{ + uint32_t dma_cntrl, aspm; + + hw->stop_pending = 0; + + dma_cntrl = crystalhd_reg_rd(hw->adp, MISC1_Y_RX_SW_DESC_LIST_CTRL_STS); + if (dma_cntrl & DMA_START_BIT) { + dma_cntrl &= ~DMA_START_BIT; + crystalhd_reg_wr(hw->adp, MISC1_Y_RX_SW_DESC_LIST_CTRL_STS, dma_cntrl); + } + + dma_cntrl = crystalhd_reg_rd(hw->adp, MISC1_UV_RX_SW_DESC_LIST_CTRL_STS); + if (dma_cntrl & DMA_START_BIT) { + dma_cntrl &= ~DMA_START_BIT; + crystalhd_reg_wr(hw->adp, MISC1_UV_RX_SW_DESC_LIST_CTRL_STS, dma_cntrl); + } + hw->rx_list_post_index = 0; + + aspm = crystalhd_reg_rd(hw->adp, PCIE_DLL_DATA_LINK_CONTROL); + aspm |= ASPM_L1_ENABLE; + /* NAREN BCMLOG(BCMLOG_INFO, "aspm on\n"); */ + crystalhd_reg_wr(hw->adp, PCIE_DLL_DATA_LINK_CONTROL, aspm); +} + +static enum BC_STATUS crystalhd_rx_pkt_done(struct crystalhd_hw *hw, uint32_t list_index, + enum BC_STATUS comp_sts) +{ + struct crystalhd_rx_dma_pkt *rx_pkt = NULL; + uint32_t y_dw_dnsz, uv_dw_dnsz; + enum BC_STATUS sts = BC_STS_SUCCESS; + + if (!hw || list_index >= DMA_ENGINE_CNT) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + rx_pkt = crystalhd_dioq_find_and_fetch(hw->rx_actq, + hw->rx_pkt_tag_seed + list_index); + if (!rx_pkt) { + BCMLOG_ERR("Act-Q:PostIx:%x L0Sts:%x L1Sts:%x current L:%x tag:%x comp:%x\n", + hw->rx_list_post_index, hw->rx_list_sts[0], + hw->rx_list_sts[1], list_index, + hw->rx_pkt_tag_seed + list_index, comp_sts); + return BC_STS_INV_ARG; + } + + if (comp_sts == BC_STS_SUCCESS) { + crystalhd_get_dnsz(hw, list_index, &y_dw_dnsz, &uv_dw_dnsz); + rx_pkt->dio_req->uinfo.y_done_sz = y_dw_dnsz; + rx_pkt->flags = COMP_FLAG_DATA_VALID; + if (rx_pkt->uv_phy_addr) + rx_pkt->dio_req->uinfo.uv_done_sz = uv_dw_dnsz; + crystalhd_dioq_add(hw->rx_rdyq, rx_pkt, true, + hw->rx_pkt_tag_seed + list_index); + return sts; + } + + /* Check if we can post this DIO again. */ + return crystalhd_hw_post_cap_buff(hw, rx_pkt); +} + +static bool crystalhd_rx_list0_handler(struct crystalhd_hw *hw, uint32_t int_sts, + uint32_t y_err_sts, uint32_t uv_err_sts) +{ + uint32_t tmp; + enum list_sts tmp_lsts; + + if (!(y_err_sts & GET_Y0_ERR_MSK) && !(uv_err_sts & GET_UV0_ERR_MSK)) + return false; + + tmp_lsts = hw->rx_list_sts[0]; + + /* Y0 - DMA */ + tmp = y_err_sts & GET_Y0_ERR_MSK; + if (int_sts & INTR_INTR_STATUS_L0_Y_RX_DMA_DONE_INTR_MASK) + hw->rx_list_sts[0] &= ~rx_waiting_y_intr; + + if (y_err_sts & MISC1_Y_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_MASK) { + hw->rx_list_sts[0] &= ~rx_waiting_y_intr; + tmp &= ~MISC1_Y_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_MASK; + } + + if (y_err_sts & MISC1_Y_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_MASK) { + hw->rx_list_sts[0] &= ~rx_y_mask; + hw->rx_list_sts[0] |= rx_y_error; + tmp &= ~MISC1_Y_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_MASK; + } + + if (tmp) { + hw->rx_list_sts[0] &= ~rx_y_mask; + hw->rx_list_sts[0] |= rx_y_error; + hw->rx_list_post_index = 0; + } + + /* UV0 - DMA */ + tmp = uv_err_sts & GET_UV0_ERR_MSK; + if (int_sts & INTR_INTR_STATUS_L0_UV_RX_DMA_DONE_INTR_MASK) + hw->rx_list_sts[0] &= ~rx_waiting_uv_intr; + + if (uv_err_sts & MISC1_UV_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_MASK) { + hw->rx_list_sts[0] &= ~rx_waiting_uv_intr; + tmp &= ~MISC1_UV_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_MASK; + } + + if (uv_err_sts & MISC1_UV_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_MASK) { + hw->rx_list_sts[0] &= ~rx_uv_mask; + hw->rx_list_sts[0] |= rx_uv_error; + tmp &= ~MISC1_UV_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_MASK; + } + + if (tmp) { + hw->rx_list_sts[0] &= ~rx_uv_mask; + hw->rx_list_sts[0] |= rx_uv_error; + hw->rx_list_post_index = 0; + } + + if (y_err_sts & GET_Y0_ERR_MSK) { + tmp = y_err_sts & GET_Y0_ERR_MSK; + crystalhd_reg_wr(hw->adp, MISC1_Y_RX_ERROR_STATUS, tmp); + } + + if (uv_err_sts & GET_UV0_ERR_MSK) { + tmp = uv_err_sts & GET_UV0_ERR_MSK; + crystalhd_reg_wr(hw->adp, MISC1_UV_RX_ERROR_STATUS, tmp); + } + + return (tmp_lsts != hw->rx_list_sts[0]); +} + +static bool crystalhd_rx_list1_handler(struct crystalhd_hw *hw, uint32_t int_sts, + uint32_t y_err_sts, uint32_t uv_err_sts) +{ + uint32_t tmp; + enum list_sts tmp_lsts; + + if (!(y_err_sts & GET_Y1_ERR_MSK) && !(uv_err_sts & GET_UV1_ERR_MSK)) + return false; + + tmp_lsts = hw->rx_list_sts[1]; + + /* Y1 - DMA */ + tmp = y_err_sts & GET_Y1_ERR_MSK; + if (int_sts & INTR_INTR_STATUS_L1_Y_RX_DMA_DONE_INTR_MASK) + hw->rx_list_sts[1] &= ~rx_waiting_y_intr; + + if (y_err_sts & MISC1_Y_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_MASK) { + hw->rx_list_sts[1] &= ~rx_waiting_y_intr; + tmp &= ~MISC1_Y_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_MASK; + } + + if (y_err_sts & MISC1_Y_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_MASK) { + /* Add retry-support..*/ + hw->rx_list_sts[1] &= ~rx_y_mask; + hw->rx_list_sts[1] |= rx_y_error; + tmp &= ~MISC1_Y_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_MASK; + } + + if (tmp) { + hw->rx_list_sts[1] &= ~rx_y_mask; + hw->rx_list_sts[1] |= rx_y_error; + hw->rx_list_post_index = 0; + } + + /* UV1 - DMA */ + tmp = uv_err_sts & GET_UV1_ERR_MSK; + if (int_sts & INTR_INTR_STATUS_L1_UV_RX_DMA_DONE_INTR_MASK) + hw->rx_list_sts[1] &= ~rx_waiting_uv_intr; + + if (uv_err_sts & MISC1_UV_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_MASK) { + hw->rx_list_sts[1] &= ~rx_waiting_uv_intr; + tmp &= ~MISC1_UV_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_MASK; + } + + if (uv_err_sts & MISC1_UV_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_MASK) { + /* Add retry-support*/ + hw->rx_list_sts[1] &= ~rx_uv_mask; + hw->rx_list_sts[1] |= rx_uv_error; + tmp &= ~MISC1_UV_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_MASK; + } + + if (tmp) { + hw->rx_list_sts[1] &= ~rx_uv_mask; + hw->rx_list_sts[1] |= rx_uv_error; + hw->rx_list_post_index = 0; + } + + if (y_err_sts & GET_Y1_ERR_MSK) { + tmp = y_err_sts & GET_Y1_ERR_MSK; + crystalhd_reg_wr(hw->adp, MISC1_Y_RX_ERROR_STATUS, tmp); + } + + if (uv_err_sts & GET_UV1_ERR_MSK) { + tmp = uv_err_sts & GET_UV1_ERR_MSK; + crystalhd_reg_wr(hw->adp, MISC1_UV_RX_ERROR_STATUS, tmp); + } + + return (tmp_lsts != hw->rx_list_sts[1]); +} + + +static void crystalhd_rx_isr(struct crystalhd_hw *hw, uint32_t intr_sts) +{ + unsigned long flags; + uint32_t i, list_avail = 0; + enum BC_STATUS comp_sts = BC_STS_NO_DATA; + uint32_t y_err_sts, uv_err_sts, y_dn_sz = 0, uv_dn_sz = 0; + bool ret = 0; + + if (!hw) { + BCMLOG_ERR("Invalid Arguments\n"); + return; + } + + if (!(intr_sts & GET_RX_INTR_MASK)) + return; + + y_err_sts = crystalhd_reg_rd(hw->adp, MISC1_Y_RX_ERROR_STATUS); + uv_err_sts = crystalhd_reg_rd(hw->adp, MISC1_UV_RX_ERROR_STATUS); + + for (i = 0; i < DMA_ENGINE_CNT; i++) { + /* Update States..*/ + spin_lock_irqsave(&hw->rx_lock, flags); + if (i == 0) + ret = crystalhd_rx_list0_handler(hw, intr_sts, y_err_sts, uv_err_sts); + else + ret = crystalhd_rx_list1_handler(hw, intr_sts, y_err_sts, uv_err_sts); + if (ret) { + switch (hw->rx_list_sts[i]) { + case sts_free: + comp_sts = BC_STS_SUCCESS; + list_avail = 1; + break; + case rx_y_error: + case rx_uv_error: + case rx_sts_error: + /* We got error on both or Y or uv. */ + hw->stats.rx_errors++; + crystalhd_get_dnsz(hw, i, &y_dn_sz, &uv_dn_sz); + /* FIXME: jarod: this is where my mini pci-e card is tripping up */ + BCMLOG(BCMLOG_DBG, "list_index:%x rx[%d] Y:%x " + "UV:%x Int:%x YDnSz:%x UVDnSz:%x\n", + i, hw->stats.rx_errors, y_err_sts, + uv_err_sts, intr_sts, y_dn_sz, uv_dn_sz); + hw->rx_list_sts[i] = sts_free; + comp_sts = BC_STS_ERROR; + break; + default: + /* Wait for completion..*/ + comp_sts = BC_STS_NO_DATA; + break; + } + } + spin_unlock_irqrestore(&hw->rx_lock, flags); + + /* handle completion...*/ + if (comp_sts != BC_STS_NO_DATA) { + crystalhd_rx_pkt_done(hw, i, comp_sts); + comp_sts = BC_STS_NO_DATA; + } + } + + if (list_avail) { + if (hw->stop_pending) { + if ((hw->rx_list_sts[0] == sts_free) && + (hw->rx_list_sts[1] == sts_free)) + crystalhd_hw_finalize_pause(hw); + } else { + crystalhd_hw_start_capture(hw); + } + } +} + +static enum BC_STATUS crystalhd_fw_cmd_post_proc(struct crystalhd_hw *hw, + struct BC_FW_CMD *fw_cmd) +{ + enum BC_STATUS sts = BC_STS_SUCCESS; + struct dec_rsp_channel_start_video *st_rsp = NULL; + + switch (fw_cmd->cmd[0]) { + case eCMD_C011_DEC_CHAN_START_VIDEO: + st_rsp = (struct dec_rsp_channel_start_video *)fw_cmd->rsp; + hw->pib_del_Q_addr = st_rsp->picInfoDeliveryQ; + hw->pib_rel_Q_addr = st_rsp->picInfoReleaseQ; + BCMLOG(BCMLOG_DBG, "DelQAddr:%x RelQAddr:%x\n", + hw->pib_del_Q_addr, hw->pib_rel_Q_addr); + break; + case eCMD_C011_INIT: + if (!(crystalhd_load_firmware_config(hw->adp))) { + BCMLOG_ERR("Invalid Params.\n"); + sts = BC_STS_FW_AUTH_FAILED; + } + break; + default: + break; + } + return sts; +} + +static enum BC_STATUS crystalhd_put_ddr2sleep(struct crystalhd_hw *hw) +{ + uint32_t reg; + union link_misc_perst_decoder_ctrl rst_cntrl_reg; + + /* Pulse reset pin of 7412 (MISC_PERST_DECODER_CTRL) */ + rst_cntrl_reg.whole_reg = crystalhd_reg_rd(hw->adp, MISC_PERST_DECODER_CTRL); + + rst_cntrl_reg.bcm_7412_rst = 1; + crystalhd_reg_wr(hw->adp, MISC_PERST_DECODER_CTRL, rst_cntrl_reg.whole_reg); + msleep_interruptible(50); + + rst_cntrl_reg.bcm_7412_rst = 0; + crystalhd_reg_wr(hw->adp, MISC_PERST_DECODER_CTRL, rst_cntrl_reg.whole_reg); + + /* Close all banks, put DDR in idle */ + bc_dec_reg_wr(hw->adp, SDRAM_PRECHARGE, 0); + + /* Set bit 25 (drop CKE pin of DDR) */ + reg = bc_dec_reg_rd(hw->adp, SDRAM_PARAM); + reg |= 0x02000000; + bc_dec_reg_wr(hw->adp, SDRAM_PARAM, reg); + + /* Reset the audio block */ + bc_dec_reg_wr(hw->adp, AUD_DSP_MISC_SOFT_RESET, 0x1); + + /* Power down Raptor PLL */ + reg = bc_dec_reg_rd(hw->adp, DecHt_PllCCtl); + reg |= 0x00008000; + bc_dec_reg_wr(hw->adp, DecHt_PllCCtl, reg); + + /* Power down all Audio PLL */ + bc_dec_reg_wr(hw->adp, AIO_MISC_PLL_RESET, 0x1); + + /* Power down video clock (75MHz) */ + reg = bc_dec_reg_rd(hw->adp, DecHt_PllECtl); + reg |= 0x00008000; + bc_dec_reg_wr(hw->adp, DecHt_PllECtl, reg); + + /* Power down video clock (75MHz) */ + reg = bc_dec_reg_rd(hw->adp, DecHt_PllDCtl); + reg |= 0x00008000; + bc_dec_reg_wr(hw->adp, DecHt_PllDCtl, reg); + + /* Power down core clock (200MHz) */ + reg = bc_dec_reg_rd(hw->adp, DecHt_PllACtl); + reg |= 0x00008000; + bc_dec_reg_wr(hw->adp, DecHt_PllACtl, reg); + + /* Power down core clock (200MHz) */ + reg = bc_dec_reg_rd(hw->adp, DecHt_PllBCtl); + reg |= 0x00008000; + bc_dec_reg_wr(hw->adp, DecHt_PllBCtl, reg); + + return BC_STS_SUCCESS; +} + +/************************************************ +** +*************************************************/ + +enum BC_STATUS crystalhd_download_fw(struct crystalhd_adp *adp, void *buffer, uint32_t sz) +{ + uint32_t reg_data, cnt, *temp_buff; + uint32_t fw_sig_len = 36; + uint32_t dram_offset = BC_FWIMG_ST_ADDR, sig_reg; + + + if (!adp || !buffer || !sz) { + BCMLOG_ERR("Invalid Params.\n"); + return BC_STS_INV_ARG; + } + + reg_data = crystalhd_reg_rd(adp, OTP_CMD); + if (!(reg_data & 0x02)) { + BCMLOG_ERR("Invalid hw config.. otp not programmed\n"); + return BC_STS_ERROR; + } + + reg_data = 0; + crystalhd_reg_wr(adp, DCI_CMD, 0); + reg_data |= BC_BIT(0); + crystalhd_reg_wr(adp, DCI_CMD, reg_data); + + reg_data = 0; + cnt = 1000; + msleep_interruptible(10); + + while (reg_data != BC_BIT(4)) { + reg_data = crystalhd_reg_rd(adp, DCI_STATUS); + reg_data &= BC_BIT(4); + if (--cnt == 0) { + BCMLOG_ERR("Firmware Download RDY Timeout.\n"); + return BC_STS_TIMEOUT; + } + } + + msleep_interruptible(10); + /* Load the FW to the FW_ADDR field in the DCI_FIRMWARE_ADDR */ + crystalhd_reg_wr(adp, DCI_FIRMWARE_ADDR, dram_offset); + temp_buff = (uint32_t *)buffer; + for (cnt = 0; cnt < (sz - fw_sig_len); cnt += 4) { + crystalhd_reg_wr(adp, DCI_DRAM_BASE_ADDR, (dram_offset >> 19)); + crystalhd_reg_wr(adp, DCI_FIRMWARE_DATA, *temp_buff); + dram_offset += 4; + temp_buff++; + } + msleep_interruptible(10); + + temp_buff++; + + sig_reg = (uint32_t)DCI_SIGNATURE_DATA_7; + for (cnt = 0; cnt < 8; cnt++) { + uint32_t swapped_data = *temp_buff; + swapped_data = bswap_32_1(swapped_data); + crystalhd_reg_wr(adp, sig_reg, swapped_data); + sig_reg -= 4; + temp_buff++; + } + msleep_interruptible(10); + + reg_data = 0; + reg_data |= BC_BIT(1); + crystalhd_reg_wr(adp, DCI_CMD, reg_data); + msleep_interruptible(10); + + reg_data = 0; + reg_data = crystalhd_reg_rd(adp, DCI_STATUS); + + if ((reg_data & BC_BIT(9)) == BC_BIT(9)) { + cnt = 1000; + while ((reg_data & BC_BIT(0)) != BC_BIT(0)) { + reg_data = crystalhd_reg_rd(adp, DCI_STATUS); + reg_data &= BC_BIT(0); + if (!(--cnt)) + break; + msleep_interruptible(10); + } + reg_data = 0; + reg_data = crystalhd_reg_rd(adp, DCI_CMD); + reg_data |= BC_BIT(4); + crystalhd_reg_wr(adp, DCI_CMD, reg_data); + + } else { + BCMLOG_ERR("F/w Signature mismatch\n"); + return BC_STS_FW_AUTH_FAILED; + } + + BCMLOG(BCMLOG_INFO, "Firmware Downloaded Successfully\n"); + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_do_fw_cmd(struct crystalhd_hw *hw, + struct BC_FW_CMD *fw_cmd) +{ + uint32_t cnt = 0, cmd_res_addr; + uint32_t *cmd_buff, *res_buff; + wait_queue_head_t fw_cmd_event; + int rc = 0; + enum BC_STATUS sts; + + crystalhd_create_event(&fw_cmd_event); + + if (!hw || !fw_cmd) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + cmd_buff = fw_cmd->cmd; + res_buff = fw_cmd->rsp; + + if (!cmd_buff || !res_buff) { + BCMLOG_ERR("Invalid Parameters for F/W Command\n"); + return BC_STS_INV_ARG; + } + + hw->pwr_lock++; + + hw->fwcmd_evt_sts = 0; + hw->pfw_cmd_event = &fw_cmd_event; + + /*Write the command to the memory*/ + crystalhd_mem_wr(hw->adp, TS_Host2CpuSnd, FW_CMD_BUFF_SZ, cmd_buff); + + /*Memory Read for memory arbitrator flush*/ + crystalhd_mem_rd(hw->adp, TS_Host2CpuSnd, 1, &cnt); + + /* Write the command address to mailbox */ + bc_dec_reg_wr(hw->adp, Hst2CpuMbx1, TS_Host2CpuSnd); + msleep_interruptible(50); + + crystalhd_wait_on_event(&fw_cmd_event, hw->fwcmd_evt_sts, 20000, rc, 0); + + if (!rc) { + sts = BC_STS_SUCCESS; + } else if (rc == -EBUSY) { + BCMLOG_ERR("Firmware command T/O\n"); + sts = BC_STS_TIMEOUT; + } else if (rc == -EINTR) { + BCMLOG(BCMLOG_DBG, "FwCmd Wait Signal int.\n"); + sts = BC_STS_IO_USER_ABORT; + } else { + BCMLOG_ERR("FwCmd IO Error.\n"); + sts = BC_STS_IO_ERROR; + } + + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("FwCmd Failed.\n"); + hw->pwr_lock--; + return sts; + } + + /*Get the Response Address*/ + cmd_res_addr = bc_dec_reg_rd(hw->adp, Cpu2HstMbx1); + + /*Read the Response*/ + crystalhd_mem_rd(hw->adp, cmd_res_addr, FW_CMD_BUFF_SZ, res_buff); + + hw->pwr_lock--; + + if (res_buff[2] != C011_RET_SUCCESS) { + BCMLOG_ERR("res_buff[2] != C011_RET_SUCCESS\n"); + return BC_STS_FW_CMD_ERR; + } + + sts = crystalhd_fw_cmd_post_proc(hw, fw_cmd); + if (sts != BC_STS_SUCCESS) + BCMLOG_ERR("crystalhd_fw_cmd_post_proc Failed.\n"); + + return sts; +} + +bool crystalhd_hw_interrupt(struct crystalhd_adp *adp, struct crystalhd_hw *hw) +{ + uint32_t intr_sts = 0; + uint32_t deco_intr = 0; + bool rc = 0; + + if (!adp || !hw->dev_started) + return rc; + + hw->stats.num_interrupts++; + hw->pwr_lock++; + + deco_intr = bc_dec_reg_rd(adp, Stream2Host_Intr_Sts); + intr_sts = crystalhd_reg_rd(adp, INTR_INTR_STATUS); + + if (intr_sts) { + /* let system know we processed interrupt..*/ + rc = 1; + hw->stats.dev_interrupts++; + } + + if (deco_intr && (deco_intr != 0xdeaddead)) { + + if (deco_intr & 0x80000000) { + /*Set the Event and the status flag*/ + if (hw->pfw_cmd_event) { + hw->fwcmd_evt_sts = 1; + crystalhd_set_event(hw->pfw_cmd_event); + } + } + + if (deco_intr & BC_BIT(1)) + crystalhd_hw_proc_pib(hw); + + bc_dec_reg_wr(adp, Stream2Host_Intr_Sts, deco_intr); + /* FIXME: jarod: No udelay? might this be the real reason mini pci-e cards were stalling out? */ + bc_dec_reg_wr(adp, Stream2Host_Intr_Sts, 0); + rc = 1; + } + + /* Rx interrupts */ + crystalhd_rx_isr(hw, intr_sts); + + /* Tx interrupts*/ + crystalhd_tx_isr(hw, intr_sts); + + /* Clear interrupts */ + if (rc) { + if (intr_sts) + crystalhd_reg_wr(adp, INTR_INTR_CLR_REG, intr_sts); + + crystalhd_reg_wr(adp, INTR_EOI_CTRL, 1); + } + + hw->pwr_lock--; + + return rc; +} + +enum BC_STATUS crystalhd_hw_open(struct crystalhd_hw *hw, struct crystalhd_adp *adp) +{ + if (!hw || !adp) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + if (hw->dev_started) + return BC_STS_SUCCESS; + + memset(hw, 0, sizeof(struct crystalhd_hw)); + + hw->adp = adp; + spin_lock_init(&hw->lock); + spin_lock_init(&hw->rx_lock); + /* FIXME: jarod: what are these magic numbers?!? */ + hw->tx_ioq_tag_seed = 0x70023070; + hw->rx_pkt_tag_seed = 0x70029070; + + hw->stop_pending = 0; + crystalhd_start_device(hw->adp); + hw->dev_started = true; + + /* set initial core clock */ + hw->core_clock_mhz = CLOCK_PRESET; + hw->prev_n = 0; + hw->pwr_lock = 0; + crystalhd_hw_set_core_clock(hw); + + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_hw_close(struct crystalhd_hw *hw) +{ + if (!hw) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + if (!hw->dev_started) + return BC_STS_SUCCESS; + + /* Stop and DDR sleep will happen in here */ + crystalhd_hw_suspend(hw); + hw->dev_started = false; + + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_hw_setup_dma_rings(struct crystalhd_hw *hw) +{ + unsigned int i; + void *mem; + size_t mem_len; + dma_addr_t phy_addr; + enum BC_STATUS sts = BC_STS_SUCCESS; + struct crystalhd_rx_dma_pkt *rpkt; + + if (!hw || !hw->adp) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + sts = crystalhd_hw_create_ioqs(hw); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("Failed to create IOQs..\n"); + return sts; + } + + mem_len = BC_LINK_MAX_SGLS * sizeof(struct dma_descriptor); + + for (i = 0; i < BC_TX_LIST_CNT; i++) { + mem = bc_kern_dma_alloc(hw->adp, mem_len, &phy_addr); + if (mem) { + memset(mem, 0, mem_len); + } else { + BCMLOG_ERR("Insufficient Memory For TX\n"); + crystalhd_hw_free_dma_rings(hw); + return BC_STS_INSUFF_RES; + } + /* rx_pkt_pool -- static memory allocation */ + hw->tx_pkt_pool[i].desc_mem.pdma_desc_start = mem; + hw->tx_pkt_pool[i].desc_mem.phy_addr = phy_addr; + hw->tx_pkt_pool[i].desc_mem.sz = BC_LINK_MAX_SGLS * + sizeof(struct dma_descriptor); + hw->tx_pkt_pool[i].list_tag = 0; + + /* Add TX dma requests to Free Queue..*/ + sts = crystalhd_dioq_add(hw->tx_freeq, + &hw->tx_pkt_pool[i], false, 0); + if (sts != BC_STS_SUCCESS) { + crystalhd_hw_free_dma_rings(hw); + return sts; + } + } + + for (i = 0; i < BC_RX_LIST_CNT; i++) { + rpkt = kzalloc(sizeof(*rpkt), GFP_KERNEL); + if (!rpkt) { + BCMLOG_ERR("Insufficient Memory For RX\n"); + crystalhd_hw_free_dma_rings(hw); + return BC_STS_INSUFF_RES; + } + + mem = bc_kern_dma_alloc(hw->adp, mem_len, &phy_addr); + if (mem) { + memset(mem, 0, mem_len); + } else { + BCMLOG_ERR("Insufficient Memory For RX\n"); + crystalhd_hw_free_dma_rings(hw); + kfree(rpkt); + return BC_STS_INSUFF_RES; + } + rpkt->desc_mem.pdma_desc_start = mem; + rpkt->desc_mem.phy_addr = phy_addr; + rpkt->desc_mem.sz = BC_LINK_MAX_SGLS * sizeof(struct dma_descriptor); + rpkt->pkt_tag = hw->rx_pkt_tag_seed + i; + crystalhd_hw_free_rx_pkt(hw, rpkt); + } + + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_hw_free_dma_rings(struct crystalhd_hw *hw) +{ + unsigned int i; + struct crystalhd_rx_dma_pkt *rpkt = NULL; + + if (!hw || !hw->adp) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + /* Delete all IOQs.. */ + crystalhd_hw_delete_ioqs(hw); + + for (i = 0; i < BC_TX_LIST_CNT; i++) { + if (hw->tx_pkt_pool[i].desc_mem.pdma_desc_start) { + bc_kern_dma_free(hw->adp, + hw->tx_pkt_pool[i].desc_mem.sz, + hw->tx_pkt_pool[i].desc_mem.pdma_desc_start, + hw->tx_pkt_pool[i].desc_mem.phy_addr); + + hw->tx_pkt_pool[i].desc_mem.pdma_desc_start = NULL; + } + } + + BCMLOG(BCMLOG_DBG, "Releasing RX Pkt pool\n"); + do { + rpkt = crystalhd_hw_alloc_rx_pkt(hw); + if (!rpkt) + break; + bc_kern_dma_free(hw->adp, rpkt->desc_mem.sz, + rpkt->desc_mem.pdma_desc_start, + rpkt->desc_mem.phy_addr); + kfree(rpkt); + } while (rpkt); + + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_hw_post_tx(struct crystalhd_hw *hw, struct crystalhd_dio_req *ioreq, + hw_comp_callback call_back, + wait_queue_head_t *cb_event, uint32_t *list_id, + uint8_t data_flags) +{ + struct tx_dma_pkt *tx_dma_packet = NULL; + uint32_t first_desc_u_addr, first_desc_l_addr; + uint32_t low_addr, high_addr; + union addr_64 desc_addr; + enum BC_STATUS sts, add_sts; + uint32_t dummy_index = 0; + unsigned long flags; + bool rc; + + if (!hw || !ioreq || !call_back || !cb_event || !list_id) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + /* + * Since we hit code in busy condition very frequently, + * we will check the code in status first before + * checking the availability of free elem. + * + * This will avoid the Q fetch/add in normal condition. + */ + rc = crystalhd_code_in_full(hw->adp, ioreq->uinfo.xfr_len, + false, data_flags); + if (rc) { + hw->stats.cin_busy++; + return BC_STS_BUSY; + } + + /* Get a list from TxFreeQ */ + tx_dma_packet = (struct tx_dma_pkt *)crystalhd_dioq_fetch(hw->tx_freeq); + if (!tx_dma_packet) { + BCMLOG_ERR("No empty elements..\n"); + return BC_STS_ERR_USAGE; + } + + sts = crystalhd_xlat_sgl_to_dma_desc(ioreq, + &tx_dma_packet->desc_mem, + &dummy_index); + if (sts != BC_STS_SUCCESS) { + add_sts = crystalhd_dioq_add(hw->tx_freeq, tx_dma_packet, + false, 0); + if (add_sts != BC_STS_SUCCESS) + BCMLOG_ERR("double fault..\n"); + + return sts; + } + + hw->pwr_lock++; + + desc_addr.full_addr = tx_dma_packet->desc_mem.phy_addr; + low_addr = desc_addr.low_part; + high_addr = desc_addr.high_part; + + tx_dma_packet->call_back = call_back; + tx_dma_packet->cb_event = cb_event; + tx_dma_packet->dio_req = ioreq; + + spin_lock_irqsave(&hw->lock, flags); + + if (hw->tx_list_post_index == 0) { + first_desc_u_addr = MISC1_TX_FIRST_DESC_U_ADDR_LIST0; + first_desc_l_addr = MISC1_TX_FIRST_DESC_L_ADDR_LIST0; + } else { + first_desc_u_addr = MISC1_TX_FIRST_DESC_U_ADDR_LIST1; + first_desc_l_addr = MISC1_TX_FIRST_DESC_L_ADDR_LIST1; + } + + *list_id = tx_dma_packet->list_tag = hw->tx_ioq_tag_seed + + hw->tx_list_post_index; + + hw->tx_list_post_index = (hw->tx_list_post_index + 1) % DMA_ENGINE_CNT; + + spin_unlock_irqrestore(&hw->lock, flags); + + + /* Insert in Active Q..*/ + crystalhd_dioq_add(hw->tx_actq, tx_dma_packet, false, + tx_dma_packet->list_tag); + + /* + * Interrupt will come as soon as you write + * the valid bit. So be ready for that. All + * the initialization should happen before that. + */ + crystalhd_start_tx_dma_engine(hw); + crystalhd_reg_wr(hw->adp, first_desc_u_addr, desc_addr.high_part); + + crystalhd_reg_wr(hw->adp, first_desc_l_addr, desc_addr.low_part | 0x01); + /* Be sure we set the valid bit ^^^^ */ + + return BC_STS_SUCCESS; +} + +/* + * This is a force cancel and we are racing with ISR. + * + * Will try to remove the req from ActQ before ISR gets it. + * If ISR gets it first then the completion happens in the + * normal path and we will return _STS_NO_DATA from here. + * + * FIX_ME: Not Tested the actual condition.. + */ +enum BC_STATUS crystalhd_hw_cancel_tx(struct crystalhd_hw *hw, uint32_t list_id) +{ + if (!hw || !list_id) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + crystalhd_stop_tx_dma_engine(hw); + crystalhd_hw_tx_req_complete(hw, list_id, BC_STS_IO_USER_ABORT); + + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_hw_add_cap_buffer(struct crystalhd_hw *hw, + struct crystalhd_dio_req *ioreq, bool en_post) +{ + struct crystalhd_rx_dma_pkt *rpkt; + uint32_t tag, uv_desc_ix = 0; + enum BC_STATUS sts; + + if (!hw || !ioreq) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + rpkt = crystalhd_hw_alloc_rx_pkt(hw); + if (!rpkt) { + BCMLOG_ERR("Insufficient resources\n"); + return BC_STS_INSUFF_RES; + } + + rpkt->dio_req = ioreq; + tag = rpkt->pkt_tag; + + sts = crystalhd_xlat_sgl_to_dma_desc(ioreq, &rpkt->desc_mem, &uv_desc_ix); + if (sts != BC_STS_SUCCESS) + return sts; + + rpkt->uv_phy_addr = 0; + + /* Store the address of UV in the rx packet for post*/ + if (uv_desc_ix) + rpkt->uv_phy_addr = rpkt->desc_mem.phy_addr + + (sizeof(struct dma_descriptor) * (uv_desc_ix + 1)); + + if (en_post) + sts = crystalhd_hw_post_cap_buff(hw, rpkt); + else + sts = crystalhd_dioq_add(hw->rx_freeq, rpkt, false, tag); + + return sts; +} + +enum BC_STATUS crystalhd_hw_get_cap_buffer(struct crystalhd_hw *hw, + struct BC_PIC_INFO_BLOCK *pib, + struct crystalhd_dio_req **ioreq) +{ + struct crystalhd_rx_dma_pkt *rpkt; + uint32_t timeout = BC_PROC_OUTPUT_TIMEOUT / 1000; + uint32_t sig_pending = 0; + + + if (!hw || !ioreq || !pib) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + rpkt = crystalhd_dioq_fetch_wait(hw->rx_rdyq, timeout, &sig_pending); + if (!rpkt) { + if (sig_pending) { + BCMLOG(BCMLOG_INFO, "wait on frame time out %d\n", sig_pending); + return BC_STS_IO_USER_ABORT; + } else { + return BC_STS_TIMEOUT; + } + } + + rpkt->dio_req->uinfo.comp_flags = rpkt->flags; + + if (rpkt->flags & COMP_FLAG_PIB_VALID) + memcpy(pib, &rpkt->pib, sizeof(*pib)); + + *ioreq = rpkt->dio_req; + + crystalhd_hw_free_rx_pkt(hw, rpkt); + + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_hw_start_capture(struct crystalhd_hw *hw) +{ + struct crystalhd_rx_dma_pkt *rx_pkt; + enum BC_STATUS sts; + uint32_t i; + + if (!hw) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + /* This is start of capture.. Post to both the lists.. */ + for (i = 0; i < DMA_ENGINE_CNT; i++) { + rx_pkt = crystalhd_dioq_fetch(hw->rx_freeq); + if (!rx_pkt) + return BC_STS_NO_DATA; + sts = crystalhd_hw_post_cap_buff(hw, rx_pkt); + if (BC_STS_SUCCESS != sts) + break; + + } + + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_hw_stop_capture(struct crystalhd_hw *hw) +{ + void *temp = NULL; + + if (!hw) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + crystalhd_stop_rx_dma_engine(hw); + + do { + temp = crystalhd_dioq_fetch(hw->rx_freeq); + if (temp) + crystalhd_rx_pkt_rel_call_back(hw, temp); + } while (temp); + + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_hw_pause(struct crystalhd_hw *hw) +{ + hw->stats.pause_cnt++; + hw->stop_pending = 1; + + if ((hw->rx_list_sts[0] == sts_free) && + (hw->rx_list_sts[1] == sts_free)) + crystalhd_hw_finalize_pause(hw); + + return BC_STS_SUCCESS; +} + +enum BC_STATUS crystalhd_hw_unpause(struct crystalhd_hw *hw) +{ + enum BC_STATUS sts; + uint32_t aspm; + + hw->stop_pending = 0; + + aspm = crystalhd_reg_rd(hw->adp, PCIE_DLL_DATA_LINK_CONTROL); + aspm &= ~ASPM_L1_ENABLE; +/* NAREN BCMLOG(BCMLOG_INFO, "aspm off\n"); */ + crystalhd_reg_wr(hw->adp, PCIE_DLL_DATA_LINK_CONTROL, aspm); + + sts = crystalhd_hw_start_capture(hw); + return sts; +} + +enum BC_STATUS crystalhd_hw_suspend(struct crystalhd_hw *hw) +{ + enum BC_STATUS sts; + + if (!hw) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + sts = crystalhd_put_ddr2sleep(hw); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("Failed to Put DDR To Sleep!!\n"); + return BC_STS_ERROR; + } + + if (!crystalhd_stop_device(hw->adp)) { + BCMLOG_ERR("Failed to Stop Device!!\n"); + return BC_STS_ERROR; + } + + return BC_STS_SUCCESS; +} + +void crystalhd_hw_stats(struct crystalhd_hw *hw, struct crystalhd_hw_stats *stats) +{ + if (!hw) { + BCMLOG_ERR("Invalid Arguments\n"); + return; + } + + /* if called w/NULL stats, its a req to zero out the stats */ + if (!stats) { + memset(&hw->stats, 0, sizeof(hw->stats)); + return; + } + + hw->stats.freeq_count = crystalhd_dioq_count(hw->rx_freeq); + hw->stats.rdyq_count = crystalhd_dioq_count(hw->rx_rdyq); + memcpy(stats, &hw->stats, sizeof(*stats)); +} + +enum BC_STATUS crystalhd_hw_set_core_clock(struct crystalhd_hw *hw) +{ + uint32_t reg, n, i; + uint32_t vco_mg, refresh_reg; + + if (!hw) { + BCMLOG_ERR("Invalid Arguments\n"); + return BC_STS_INV_ARG; + } + + /* FIXME: jarod: wha? */ + /*n = (hw->core_clock_mhz * 3) / 20 + 1; */ + n = hw->core_clock_mhz/5; + + if (n == hw->prev_n) + return BC_STS_CLK_NOCHG; + + if (hw->pwr_lock > 0) { + /* BCMLOG(BCMLOG_INFO,"pwr_lock is %u\n", hw->pwr_lock) */ + return BC_STS_CLK_NOCHG; + } + + i = n * 27; + if (i < 560) + vco_mg = 0; + else if (i < 900) + vco_mg = 1; + else if (i < 1030) + vco_mg = 2; + else + vco_mg = 3; + + reg = bc_dec_reg_rd(hw->adp, DecHt_PllACtl); + + reg &= 0xFFFFCFC0; + reg |= n; + reg |= vco_mg << 12; + + BCMLOG(BCMLOG_INFO, "clock is moving to %d with n %d with vco_mg %d\n", + hw->core_clock_mhz, n, vco_mg); + + /* Change the DRAM refresh rate to accommodate the new frequency */ + /* refresh reg = ((refresh_rate * clock_rate)/16) - 1; rounding up*/ + refresh_reg = (7 * hw->core_clock_mhz / 16); + bc_dec_reg_wr(hw->adp, SDRAM_REF_PARAM, ((1 << 12) | refresh_reg)); + + bc_dec_reg_wr(hw->adp, DecHt_PllACtl, reg); + + i = 0; + + for (i = 0; i < 10; i++) { + reg = bc_dec_reg_rd(hw->adp, DecHt_PllACtl); + + if (reg & 0x00020000) { + hw->prev_n = n; + /* FIXME: jarod: outputting a random "C" is... confusing... */ + BCMLOG(BCMLOG_INFO, "C"); + return BC_STS_SUCCESS; + } else { + msleep_interruptible(10); + } + } + BCMLOG(BCMLOG_INFO, "clk change failed\n"); + return BC_STS_CLK_NOCHG; +} diff --git a/drivers/staging/crystalhd/crystalhd_hw.h b/drivers/staging/crystalhd/crystalhd_hw.h new file mode 100644 index 00000000..2d0e6c60 --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd_hw.h @@ -0,0 +1,390 @@ +/*************************************************************************** + * Copyright (c) 2005-2009, Broadcom Corporation. + * + * Name: crystalhd_hw . h + * + * Description: + * BCM70012 Linux driver hardware layer. + * + * HISTORY: + * + ********************************************************************** + * This file is part of the crystalhd device driver. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver. If not, see . + **********************************************************************/ + +#ifndef _CRYSTALHD_HW_H_ +#define _CRYSTALHD_HW_H_ + +#include "crystalhd.h" + +/* HW constants..*/ +#define DMA_ENGINE_CNT 2 +#define MAX_PIB_Q_DEPTH 64 +#define MIN_PIB_Q_DEPTH 2 +#define WR_POINTER_OFF 4 + +#define ASPM_L1_ENABLE (BC_BIT(27)) + +/************************************************* + 7412 Decoder Registers. +**************************************************/ +#define FW_CMD_BUFF_SZ 64 +#define TS_Host2CpuSnd 0x00000100 +#define Hst2CpuMbx1 0x00100F00 +#define Cpu2HstMbx1 0x00100F04 +#define MbxStat1 0x00100F08 +#define Stream2Host_Intr_Sts 0x00100F24 +#define C011_RET_SUCCESS 0x0 /* Reutrn status of firmware command. */ + +/* TS input status register */ +#define TS_StreamAFIFOStatus 0x0010044C +#define TS_StreamBFIFOStatus 0x0010084C + +/*UART Selection definitions*/ +#define UartSelectA 0x00100300 +#define UartSelectB 0x00100304 + +#define BSVS_UART_DEC_NONE 0x00 +#define BSVS_UART_DEC_OUTER 0x01 +#define BSVS_UART_DEC_INNER 0x02 +#define BSVS_UART_STREAM 0x03 + +/* Code-In fifo */ +#define REG_DecCA_RegCinCTL 0xa00 +#define REG_DecCA_RegCinBase 0xa0c +#define REG_DecCA_RegCinEnd 0xa10 +#define REG_DecCA_RegCinWrPtr 0xa04 +#define REG_DecCA_RegCinRdPtr 0xa08 + +#define REG_Dec_TsUser0Base 0x100864 +#define REG_Dec_TsUser0Rdptr 0x100868 +#define REG_Dec_TsUser0Wrptr 0x10086C +#define REG_Dec_TsUser0End 0x100874 + +/* ASF Case ...*/ +#define REG_Dec_TsAudCDB2Base 0x10036c +#define REG_Dec_TsAudCDB2Rdptr 0x100378 +#define REG_Dec_TsAudCDB2Wrptr 0x100374 +#define REG_Dec_TsAudCDB2End 0x100370 + +/* DRAM bringup Registers */ +#define SDRAM_PARAM 0x00040804 +#define SDRAM_PRECHARGE 0x000408B0 +#define SDRAM_EXT_MODE 0x000408A4 +#define SDRAM_MODE 0x000408A0 +#define SDRAM_REFRESH 0x00040890 +#define SDRAM_REF_PARAM 0x00040808 + +#define DecHt_PllACtl 0x34000C +#define DecHt_PllBCtl 0x340010 +#define DecHt_PllCCtl 0x340014 +#define DecHt_PllDCtl 0x340034 +#define DecHt_PllECtl 0x340038 +#define AUD_DSP_MISC_SOFT_RESET 0x00240104 +#define AIO_MISC_PLL_RESET 0x0026000C +#define PCIE_CLK_REQ_REG 0xDC +#define PCI_CLK_REQ_ENABLE (BC_BIT(8)) + +/************************************************* + F/W Copy engine definitions.. +**************************************************/ +#define BC_FWIMG_ST_ADDR 0x00000000 +/* FIXME: jarod: there's a kernel function that'll do this for us... */ +#define rotr32_1(x, n) (((x) >> n) | ((x) << (32 - n))) +#define bswap_32_1(x) ((rotr32_1((x), 24) & 0x00ff00ff) | (rotr32_1((x), 8) & 0xff00ff00)) + +#define DecHt_HostSwReset 0x340000 +#define BC_DRAM_FW_CFG_ADDR 0x001c2000 + +union addr_64 { + struct { + uint32_t low_part; + uint32_t high_part; + }; + + uint64_t full_addr; + +}; + +union intr_mask_reg { + struct { + uint32_t mask_tx_done:1; + uint32_t mask_tx_err:1; + uint32_t mask_rx_done:1; + uint32_t mask_rx_err:1; + uint32_t mask_pcie_err:1; + uint32_t mask_pcie_rbusmast_err:1; + uint32_t mask_pcie_rgr_bridge:1; + uint32_t reserved:25; + }; + + uint32_t whole_reg; + +}; + +union link_misc_perst_deco_ctrl { + struct { + uint32_t bcm7412_rst:1; /* 1 -> BCM7412 is held in reset. Reset value 1.*/ + uint32_t reserved0:3; /* Reserved.No Effect*/ + uint32_t stop_bcm_7412_clk:1; /* 1 ->Stops branch of 27MHz clk used to clk BCM7412*/ + uint32_t reserved1:27; /* Reseved. No Effect*/ + }; + + uint32_t whole_reg; + +}; + +union link_misc_perst_clk_ctrl { + struct { + uint32_t sel_alt_clk:1; /* When set, selects a 6.75MHz clock as the source of core_clk */ + uint32_t stop_core_clk:1; /* When set, stops the branch of core_clk that is not needed for low power operation */ + uint32_t pll_pwr_dn:1; /* When set, powers down the main PLL. The alternate clock bit should be set + to select an alternate clock before setting this bit.*/ + uint32_t reserved0:5; /* Reserved */ + uint32_t pll_mult:8; /* This setting controls the multiplier for the PLL. */ + uint32_t pll_div:4; /* This setting controls the divider for the PLL. */ + uint32_t reserved1:12; /* Reserved */ + }; + + uint32_t whole_reg; + +}; + +union link_misc_perst_decoder_ctrl { + struct { + uint32_t bcm_7412_rst:1; /* 1 -> BCM7412 is held in reset. Reset value 1.*/ + uint32_t res0:3; /* Reserved.No Effect*/ + uint32_t stop_7412_clk:1; /* 1 ->Stops branch of 27MHz clk used to clk BCM7412*/ + uint32_t res1:27; /* Reseved. No Effect */ + }; + + uint32_t whole_reg; + +}; + +union desc_low_addr_reg { + struct { + uint32_t list_valid:1; + uint32_t reserved:4; + uint32_t low_addr:27; + }; + + uint32_t whole_reg; + +}; + +struct dma_descriptor { /* 8 32-bit values */ + /* 0th u32 */ + uint32_t sdram_buff_addr:28; /* bits 0-27: SDRAM Address */ + uint32_t res0:4; /* bits 28-31: Reserved */ + + /* 1st u32 */ + uint32_t buff_addr_low; /* 1 buffer address low */ + uint32_t buff_addr_high; /* 2 buffer address high */ + + /* 3rd u32 */ + uint32_t res2:2; /* 0-1 - Reserved */ + uint32_t xfer_size:23; /* 2-24 = Xfer size in words */ + uint32_t res3:6; /* 25-30 reserved */ + uint32_t intr_enable:1; /* 31 - Interrupt After this desc */ + + /* 4th u32 */ + uint32_t endian_xlat_align:2; /* 0-1 Endian Translation */ + uint32_t next_desc_cont:1; /* 2 - Next desc is in contig memory */ + uint32_t res4:25; /* 3 - 27 Reserved bits */ + uint32_t fill_bytes:2; /* 28-29 Bits Fill Bytes */ + uint32_t dma_dir:1; /* 30 bit DMA Direction */ + uint32_t last_rec_indicator:1; /* 31 bit Last Record Indicator */ + + /* 5th u32 */ + uint32_t next_desc_addr_low; /* 32-bits Next Desc Addr lower */ + + /* 6th u32 */ + uint32_t next_desc_addr_high; /* 32-bits Next Desc Addr Higher */ + + /* 7th u32 */ + uint32_t res8; /* Last 32bits reserved */ + +}; + +/* + * We will allocate the memory in 4K pages + * the linked list will be a list of 32 byte descriptors. + * The virtual address will determine what should be freed. + */ +struct dma_desc_mem { + struct dma_descriptor *pdma_desc_start; /* 32-bytes for dma descriptor. should be first element */ + dma_addr_t phy_addr; /* physical address of each DMA desc */ + uint32_t sz; + struct _dma_desc_mem_ *Next; /* points to Next Descriptor in chain */ + +}; + +enum list_sts { + sts_free = 0, + + /* RX-Y Bits 0:7 */ + rx_waiting_y_intr = 0x00000001, + rx_y_error = 0x00000004, + + /* RX-UV Bits 8:16 */ + rx_waiting_uv_intr = 0x0000100, + rx_uv_error = 0x0000400, + + rx_sts_waiting = (rx_waiting_y_intr|rx_waiting_uv_intr), + rx_sts_error = (rx_y_error|rx_uv_error), + + rx_y_mask = 0x000000FF, + rx_uv_mask = 0x0000FF00, +}; + +struct tx_dma_pkt { + struct dma_desc_mem desc_mem; + hw_comp_callback call_back; + struct crystalhd_dio_req *dio_req; + wait_queue_head_t *cb_event; + uint32_t list_tag; +}; + +struct crystalhd_rx_dma_pkt { + struct dma_desc_mem desc_mem; + struct crystalhd_dio_req *dio_req; + uint32_t pkt_tag; + uint32_t flags; + struct BC_PIC_INFO_BLOCK pib; + dma_addr_t uv_phy_addr; + struct crystalhd_rx_dma_pkt *next; +}; + +struct crystalhd_hw_stats { + uint32_t rx_errors; + uint32_t tx_errors; + uint32_t freeq_count; + uint32_t rdyq_count; + uint32_t num_interrupts; + uint32_t dev_interrupts; + uint32_t cin_busy; + uint32_t pause_cnt; +}; + +struct crystalhd_hw { + struct tx_dma_pkt tx_pkt_pool[DMA_ENGINE_CNT]; + spinlock_t lock; + + uint32_t tx_ioq_tag_seed; + uint32_t tx_list_post_index; + + struct crystalhd_rx_dma_pkt *rx_pkt_pool_head; + uint32_t rx_pkt_tag_seed; + + bool dev_started; + void *adp; + + wait_queue_head_t *pfw_cmd_event; + int fwcmd_evt_sts; + + uint32_t pib_del_Q_addr; + uint32_t pib_rel_Q_addr; + + struct crystalhd_dioq *tx_freeq; + struct crystalhd_dioq *tx_actq; + + /* Rx DMA Engine Specific Locks */ + spinlock_t rx_lock; + uint32_t rx_list_post_index; + enum list_sts rx_list_sts[DMA_ENGINE_CNT]; + struct crystalhd_dioq *rx_rdyq; + struct crystalhd_dioq *rx_freeq; + struct crystalhd_dioq *rx_actq; + uint32_t stop_pending; + + /* HW counters.. */ + struct crystalhd_hw_stats stats; + + /* Core clock in MHz */ + uint32_t core_clock_mhz; + uint32_t prev_n; + uint32_t pwr_lock; +}; + +/* Clock defines for power control */ +#define CLOCK_PRESET 175 + +/* DMA engine register BIT mask wrappers.. */ +#define DMA_START_BIT MISC1_TX_SW_DESC_LIST_CTRL_STS_TX_DMA_RUN_STOP_MASK + +#define GET_RX_INTR_MASK (INTR_INTR_STATUS_L1_UV_RX_DMA_ERR_INTR_MASK | \ + INTR_INTR_STATUS_L1_UV_RX_DMA_DONE_INTR_MASK | \ + INTR_INTR_STATUS_L1_Y_RX_DMA_ERR_INTR_MASK | \ + INTR_INTR_STATUS_L1_Y_RX_DMA_DONE_INTR_MASK | \ + INTR_INTR_STATUS_L0_UV_RX_DMA_ERR_INTR_MASK | \ + INTR_INTR_STATUS_L0_UV_RX_DMA_DONE_INTR_MASK | \ + INTR_INTR_STATUS_L0_Y_RX_DMA_ERR_INTR_MASK | \ + INTR_INTR_STATUS_L0_Y_RX_DMA_DONE_INTR_MASK) + +#define GET_Y0_ERR_MSK (MISC1_Y_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_MASK | \ + MISC1_Y_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_MASK | \ + MISC1_Y_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_MASK | \ + MISC1_Y_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_MASK) + +#define GET_UV0_ERR_MSK (MISC1_UV_RX_ERROR_STATUS_RX_L0_OVERRUN_ERROR_MASK | \ + MISC1_UV_RX_ERROR_STATUS_RX_L0_UNDERRUN_ERROR_MASK | \ + MISC1_UV_RX_ERROR_STATUS_RX_L0_DESC_TX_ABORT_ERRORS_MASK | \ + MISC1_UV_RX_ERROR_STATUS_RX_L0_FIFO_FULL_ERRORS_MASK) + +#define GET_Y1_ERR_MSK (MISC1_Y_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_MASK | \ + MISC1_Y_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_MASK | \ + MISC1_Y_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_MASK | \ + MISC1_Y_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_MASK) + +#define GET_UV1_ERR_MSK (MISC1_UV_RX_ERROR_STATUS_RX_L1_OVERRUN_ERROR_MASK | \ + MISC1_UV_RX_ERROR_STATUS_RX_L1_UNDERRUN_ERROR_MASK | \ + MISC1_UV_RX_ERROR_STATUS_RX_L1_DESC_TX_ABORT_ERRORS_MASK | \ + MISC1_UV_RX_ERROR_STATUS_RX_L1_FIFO_FULL_ERRORS_MASK) + + +/**** API Exposed to the other layers ****/ +enum BC_STATUS crystalhd_download_fw(struct crystalhd_adp *adp, + void *buffer, uint32_t sz); +enum BC_STATUS crystalhd_do_fw_cmd(struct crystalhd_hw *hw, struct BC_FW_CMD *fw_cmd); +bool crystalhd_hw_interrupt(struct crystalhd_adp *adp, struct crystalhd_hw *hw); +enum BC_STATUS crystalhd_hw_open(struct crystalhd_hw *, struct crystalhd_adp *); +enum BC_STATUS crystalhd_hw_close(struct crystalhd_hw *); +enum BC_STATUS crystalhd_hw_setup_dma_rings(struct crystalhd_hw *); +enum BC_STATUS crystalhd_hw_free_dma_rings(struct crystalhd_hw *); + + +enum BC_STATUS crystalhd_hw_post_tx(struct crystalhd_hw *hw, struct crystalhd_dio_req *ioreq, + hw_comp_callback call_back, + wait_queue_head_t *cb_event, + uint32_t *list_id, uint8_t data_flags); + +enum BC_STATUS crystalhd_hw_pause(struct crystalhd_hw *hw); +enum BC_STATUS crystalhd_hw_unpause(struct crystalhd_hw *hw); +enum BC_STATUS crystalhd_hw_suspend(struct crystalhd_hw *hw); +enum BC_STATUS crystalhd_hw_cancel_tx(struct crystalhd_hw *hw, uint32_t list_id); +enum BC_STATUS crystalhd_hw_add_cap_buffer(struct crystalhd_hw *hw, + struct crystalhd_dio_req *ioreq, bool en_post); +enum BC_STATUS crystalhd_hw_get_cap_buffer(struct crystalhd_hw *hw, + struct BC_PIC_INFO_BLOCK *pib, + struct crystalhd_dio_req **ioreq); +enum BC_STATUS crystalhd_hw_stop_capture(struct crystalhd_hw *hw); +enum BC_STATUS crystalhd_hw_start_capture(struct crystalhd_hw *hw); +void crystalhd_hw_stats(struct crystalhd_hw *hw, struct crystalhd_hw_stats *stats); + +/* API to program the core clock on the decoder */ +enum BC_STATUS crystalhd_hw_set_core_clock(struct crystalhd_hw *); + +#endif diff --git a/drivers/staging/crystalhd/crystalhd_lnx.c b/drivers/staging/crystalhd/crystalhd_lnx.c new file mode 100644 index 00000000..d9e3d618 --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd_lnx.c @@ -0,0 +1,773 @@ +/*************************************************************************** + BCM70010 Linux driver + Copyright (c) 2005-2009, Broadcom Corporation. + + This driver is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, version 2 of the License. + + This driver is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this driver. If not, see . +***************************************************************************/ + +#include "crystalhd.h" + +#include +#include + + +static DEFINE_MUTEX(chd_dec_mutex); +static struct class *crystalhd_class; + +static struct crystalhd_adp *g_adp_info; + +static irqreturn_t chd_dec_isr(int irq, void *arg) +{ + struct crystalhd_adp *adp = (struct crystalhd_adp *) arg; + int rc = 0; + if (adp) + rc = crystalhd_cmd_interrupt(&adp->cmds); + + return IRQ_RETVAL(rc); +} + +static int chd_dec_enable_int(struct crystalhd_adp *adp) +{ + int rc = 0; + + if (!adp || !adp->pdev) { + BCMLOG_ERR("Invalid arg!!\n"); + return -EINVAL; + } + + if (adp->pdev->msi_enabled) + adp->msi = 1; + else + adp->msi = pci_enable_msi(adp->pdev); + + rc = request_irq(adp->pdev->irq, chd_dec_isr, IRQF_SHARED, + adp->name, (void *)adp); + if (rc) { + BCMLOG_ERR("Interrupt request failed..\n"); + pci_disable_msi(adp->pdev); + } + + return rc; +} + +static int chd_dec_disable_int(struct crystalhd_adp *adp) +{ + if (!adp || !adp->pdev) { + BCMLOG_ERR("Invalid arg!!\n"); + return -EINVAL; + } + + free_irq(adp->pdev->irq, adp); + + if (adp->msi) + pci_disable_msi(adp->pdev); + + return 0; +} + +struct crystalhd_ioctl_data *chd_dec_alloc_iodata(struct crystalhd_adp *adp, bool isr) +{ + unsigned long flags = 0; + struct crystalhd_ioctl_data *temp; + + if (!adp) + return NULL; + + spin_lock_irqsave(&adp->lock, flags); + + temp = adp->idata_free_head; + if (temp) { + adp->idata_free_head = adp->idata_free_head->next; + memset(temp, 0, sizeof(*temp)); + } + + spin_unlock_irqrestore(&adp->lock, flags); + return temp; +} + +void chd_dec_free_iodata(struct crystalhd_adp *adp, struct crystalhd_ioctl_data *iodata, + bool isr) +{ + unsigned long flags = 0; + + if (!adp || !iodata) + return; + + spin_lock_irqsave(&adp->lock, flags); + iodata->next = adp->idata_free_head; + adp->idata_free_head = iodata; + spin_unlock_irqrestore(&adp->lock, flags); +} + +static inline int crystalhd_user_data(unsigned long ud, void *dr, int size, int set) +{ + int rc; + + if (!ud || !dr) { + BCMLOG_ERR("Invalid arg\n"); + return -EINVAL; + } + + if (set) + rc = copy_to_user((void *)ud, dr, size); + else + rc = copy_from_user(dr, (void *)ud, size); + + if (rc) { + BCMLOG_ERR("Invalid args for command\n"); + rc = -EFAULT; + } + + return rc; +} + +static int chd_dec_fetch_cdata(struct crystalhd_adp *adp, struct crystalhd_ioctl_data *io, + uint32_t m_sz, unsigned long ua) +{ + unsigned long ua_off; + int rc = 0; + + if (!adp || !io || !ua || !m_sz) { + BCMLOG_ERR("Invalid Arg!!\n"); + return -EINVAL; + } + + io->add_cdata = vmalloc(m_sz); + if (!io->add_cdata) { + BCMLOG_ERR("kalloc fail for sz:%x\n", m_sz); + return -ENOMEM; + } + + io->add_cdata_sz = m_sz; + ua_off = ua + sizeof(io->udata); + rc = crystalhd_user_data(ua_off, io->add_cdata, io->add_cdata_sz, 0); + if (rc) { + BCMLOG_ERR("failed to pull add_cdata sz:%x ua_off:%x\n", + io->add_cdata_sz, (unsigned int)ua_off); + kfree(io->add_cdata); + io->add_cdata = NULL; + return -ENODATA; + } + + return rc; +} + +static int chd_dec_release_cdata(struct crystalhd_adp *adp, + struct crystalhd_ioctl_data *io, unsigned long ua) +{ + unsigned long ua_off; + int rc; + + if (!adp || !io || !ua) { + BCMLOG_ERR("Invalid Arg!!\n"); + return -EINVAL; + } + + if (io->cmd != BCM_IOC_FW_DOWNLOAD) { + ua_off = ua + sizeof(io->udata); + rc = crystalhd_user_data(ua_off, io->add_cdata, + io->add_cdata_sz, 1); + if (rc) { + BCMLOG_ERR("failed to push add_cdata sz:%x ua_off:%x\n", + io->add_cdata_sz, (unsigned int)ua_off); + return -ENODATA; + } + } + + if (io->add_cdata) { + vfree(io->add_cdata); + io->add_cdata = NULL; + } + + return 0; +} + +static int chd_dec_proc_user_data(struct crystalhd_adp *adp, + struct crystalhd_ioctl_data *io, + unsigned long ua, int set) +{ + int rc; + uint32_t m_sz = 0; + + if (!adp || !io || !ua) { + BCMLOG_ERR("Invalid Arg!!\n"); + return -EINVAL; + } + + rc = crystalhd_user_data(ua, &io->udata, sizeof(io->udata), set); + if (rc) { + BCMLOG_ERR("failed to %s iodata\n", (set ? "set" : "get")); + return rc; + } + + switch (io->cmd) { + case BCM_IOC_MEM_RD: + case BCM_IOC_MEM_WR: + case BCM_IOC_FW_DOWNLOAD: + m_sz = io->udata.u.devMem.NumDwords * 4; + if (set) + rc = chd_dec_release_cdata(adp, io, ua); + else + rc = chd_dec_fetch_cdata(adp, io, m_sz, ua); + break; + default: + break; + } + + return rc; +} + +static int chd_dec_api_cmd(struct crystalhd_adp *adp, unsigned long ua, + uint32_t uid, uint32_t cmd, crystalhd_cmd_proc func) +{ + int rc; + struct crystalhd_ioctl_data *temp; + enum BC_STATUS sts = BC_STS_SUCCESS; + + temp = chd_dec_alloc_iodata(adp, 0); + if (!temp) { + BCMLOG_ERR("Failed to get iodata..\n"); + return -EINVAL; + } + + temp->u_id = uid; + temp->cmd = cmd; + + rc = chd_dec_proc_user_data(adp, temp, ua, 0); + if (!rc) { + sts = func(&adp->cmds, temp); + if (sts == BC_STS_PENDING) + sts = BC_STS_NOT_IMPL; + temp->udata.RetSts = sts; + rc = chd_dec_proc_user_data(adp, temp, ua, 1); + } + + if (temp) { + chd_dec_free_iodata(adp, temp, 0); + temp = NULL; + } + + return rc; +} + +/* API interfaces */ +static long chd_dec_ioctl(struct file *fd, unsigned int cmd, unsigned long ua) +{ + struct crystalhd_adp *adp = chd_get_adp(); + crystalhd_cmd_proc cproc; + struct crystalhd_user *uc; + int ret; + + if (!adp || !fd) { + BCMLOG_ERR("Invalid adp\n"); + return -EINVAL; + } + + uc = fd->private_data; + if (!uc) { + BCMLOG_ERR("Failed to get uc\n"); + return -ENODATA; + } + + mutex_lock(&chd_dec_mutex); + cproc = crystalhd_get_cmd_proc(&adp->cmds, cmd, uc); + if (!cproc) { + BCMLOG_ERR("Unhandled command: %d\n", cmd); + mutex_unlock(&chd_dec_mutex); + return -EINVAL; + } + + ret = chd_dec_api_cmd(adp, ua, uc->uid, cmd, cproc); + mutex_unlock(&chd_dec_mutex); + return ret; +} + +static int chd_dec_open(struct inode *in, struct file *fd) +{ + struct crystalhd_adp *adp = chd_get_adp(); + int rc = 0; + enum BC_STATUS sts = BC_STS_SUCCESS; + struct crystalhd_user *uc = NULL; + + if (!adp) { + BCMLOG_ERR("Invalid adp\n"); + return -EINVAL; + } + + if (adp->cfg_users >= BC_LINK_MAX_OPENS) { + BCMLOG(BCMLOG_INFO, "Already in use.%d\n", adp->cfg_users); + return -EBUSY; + } + + sts = crystalhd_user_open(&adp->cmds, &uc); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("cmd_user_open - %d\n", sts); + rc = -EBUSY; + } + + adp->cfg_users++; + + fd->private_data = uc; + + return rc; +} + +static int chd_dec_close(struct inode *in, struct file *fd) +{ + struct crystalhd_adp *adp = chd_get_adp(); + struct crystalhd_user *uc; + + if (!adp) { + BCMLOG_ERR("Invalid adp\n"); + return -EINVAL; + } + + uc = fd->private_data; + if (!uc) { + BCMLOG_ERR("Failed to get uc\n"); + return -ENODATA; + } + + crystalhd_user_close(&adp->cmds, uc); + + adp->cfg_users--; + + return 0; +} + +static const struct file_operations chd_dec_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = chd_dec_ioctl, + .open = chd_dec_open, + .release = chd_dec_close, + .llseek = noop_llseek, +}; + +static int __devinit chd_dec_init_chdev(struct crystalhd_adp *adp) +{ + struct crystalhd_ioctl_data *temp; + struct device *dev; + int rc = -ENODEV, i = 0; + + if (!adp) + goto fail; + + adp->chd_dec_major = register_chrdev(0, CRYSTALHD_API_NAME, + &chd_dec_fops); + if (adp->chd_dec_major < 0) { + BCMLOG_ERR("Failed to create config dev\n"); + rc = adp->chd_dec_major; + goto fail; + } + + /* register crystalhd class */ + crystalhd_class = class_create(THIS_MODULE, "crystalhd"); + if (IS_ERR(crystalhd_class)) { + BCMLOG_ERR("failed to create class\n"); + goto fail; + } + + dev = device_create(crystalhd_class, NULL, MKDEV(adp->chd_dec_major, 0), + NULL, "crystalhd"); + if (IS_ERR(dev)) { + BCMLOG_ERR("failed to create device\n"); + goto device_create_fail; + } + + rc = crystalhd_create_elem_pool(adp, BC_LINK_ELEM_POOL_SZ); + if (rc) { + BCMLOG_ERR("failed to create device\n"); + goto elem_pool_fail; + } + + /* Allocate general purpose ioctl pool. */ + for (i = 0; i < CHD_IODATA_POOL_SZ; i++) { + temp = kzalloc(sizeof(struct crystalhd_ioctl_data), GFP_KERNEL); + if (!temp) { + BCMLOG_ERR("ioctl data pool kzalloc failed\n"); + rc = -ENOMEM; + goto kzalloc_fail; + } + /* Add to global pool.. */ + chd_dec_free_iodata(adp, temp, 0); + } + + return 0; + +kzalloc_fail: + crystalhd_delete_elem_pool(adp); +elem_pool_fail: + device_destroy(crystalhd_class, MKDEV(adp->chd_dec_major, 0)); +device_create_fail: + class_destroy(crystalhd_class); +fail: + return rc; +} + +static void __devexit chd_dec_release_chdev(struct crystalhd_adp *adp) +{ + struct crystalhd_ioctl_data *temp = NULL; + if (!adp) + return; + + if (adp->chd_dec_major > 0) { + /* unregister crystalhd class */ + device_destroy(crystalhd_class, MKDEV(adp->chd_dec_major, 0)); + unregister_chrdev(adp->chd_dec_major, CRYSTALHD_API_NAME); + BCMLOG(BCMLOG_INFO, "released api device - %d\n", + adp->chd_dec_major); + class_destroy(crystalhd_class); + } + adp->chd_dec_major = 0; + + /* Clear iodata pool.. */ + do { + temp = chd_dec_alloc_iodata(adp, 0); + kfree(temp); + } while (temp); + + crystalhd_delete_elem_pool(adp); +} + +static int __devinit chd_pci_reserve_mem(struct crystalhd_adp *pinfo) +{ + int rc; + unsigned long bar2 = pci_resource_start(pinfo->pdev, 2); + uint32_t mem_len = pci_resource_len(pinfo->pdev, 2); + unsigned long bar0 = pci_resource_start(pinfo->pdev, 0); + uint32_t i2o_len = pci_resource_len(pinfo->pdev, 0); + + BCMLOG(BCMLOG_SSTEP, "bar2:0x%lx-0x%08x bar0:0x%lx-0x%08x\n", + bar2, mem_len, bar0, i2o_len); + + rc = check_mem_region(bar2, mem_len); + if (rc) { + BCMLOG_ERR("No valid mem region...\n"); + return -ENOMEM; + } + + pinfo->addr = ioremap_nocache(bar2, mem_len); + if (!pinfo->addr) { + BCMLOG_ERR("Failed to remap mem region...\n"); + return -ENOMEM; + } + + pinfo->pci_mem_start = bar2; + pinfo->pci_mem_len = mem_len; + + rc = check_mem_region(bar0, i2o_len); + if (rc) { + BCMLOG_ERR("No valid mem region...\n"); + return -ENOMEM; + } + + pinfo->i2o_addr = ioremap_nocache(bar0, i2o_len); + if (!pinfo->i2o_addr) { + BCMLOG_ERR("Failed to remap mem region...\n"); + return -ENOMEM; + } + + pinfo->pci_i2o_start = bar0; + pinfo->pci_i2o_len = i2o_len; + + rc = pci_request_regions(pinfo->pdev, pinfo->name); + if (rc < 0) { + BCMLOG_ERR("Region request failed: %d\n", rc); + return rc; + } + + BCMLOG(BCMLOG_SSTEP, "Mapped addr:0x%08lx i2o_addr:0x%08lx\n", + (unsigned long)pinfo->addr, (unsigned long)pinfo->i2o_addr); + + return 0; +} + +static void __devexit chd_pci_release_mem(struct crystalhd_adp *pinfo) +{ + if (!pinfo) + return; + + if (pinfo->addr) + iounmap(pinfo->addr); + + if (pinfo->i2o_addr) + iounmap(pinfo->i2o_addr); + + pci_release_regions(pinfo->pdev); +} + + +static void __devexit chd_dec_pci_remove(struct pci_dev *pdev) +{ + struct crystalhd_adp *pinfo; + enum BC_STATUS sts = BC_STS_SUCCESS; + + pinfo = pci_get_drvdata(pdev); + if (!pinfo) { + BCMLOG_ERR("could not get adp\n"); + return; + } + + sts = crystalhd_delete_cmd_context(&pinfo->cmds); + if (sts != BC_STS_SUCCESS) + BCMLOG_ERR("cmd delete :%d\n", sts); + + chd_dec_release_chdev(pinfo); + + chd_dec_disable_int(pinfo); + + chd_pci_release_mem(pinfo); + pci_disable_device(pinfo->pdev); + + kfree(pinfo); + g_adp_info = NULL; +} + +static int __devinit chd_dec_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *entry) +{ + struct crystalhd_adp *pinfo; + int rc; + enum BC_STATUS sts = BC_STS_SUCCESS; + + BCMLOG(BCMLOG_DBG, "PCI_INFO: Vendor:0x%04x Device:0x%04x " + "s_vendor:0x%04x s_device: 0x%04x\n", + pdev->vendor, pdev->device, pdev->subsystem_vendor, + pdev->subsystem_device); + + pinfo = kzalloc(sizeof(struct crystalhd_adp), GFP_KERNEL); + if (!pinfo) { + BCMLOG_ERR("Failed to allocate memory\n"); + return -ENOMEM; + } + + pinfo->pdev = pdev; + + rc = pci_enable_device(pdev); + if (rc) { + BCMLOG_ERR("Failed to enable PCI device\n"); + goto err; + } + + snprintf(pinfo->name, sizeof(pinfo->name), "crystalhd_pci_e:%d:%d:%d", + pdev->bus->number, PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn)); + + rc = chd_pci_reserve_mem(pinfo); + if (rc) { + BCMLOG_ERR("Failed to setup memory regions.\n"); + pci_disable_device(pdev); + rc = -ENOMEM; + goto err; + } + + pinfo->present = 1; + pinfo->drv_data = entry->driver_data; + + /* Setup adapter level lock.. */ + spin_lock_init(&pinfo->lock); + + /* setup api stuff.. */ + chd_dec_init_chdev(pinfo); + rc = chd_dec_enable_int(pinfo); + if (rc) { + BCMLOG_ERR("_enable_int err:%d\n", rc); + pci_disable_device(pdev); + rc = -ENODEV; + goto err; + } + + /* Set dma mask... */ + if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) { + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)); + pinfo->dmabits = 64; + } else if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + pinfo->dmabits = 32; + } else { + BCMLOG_ERR("Unabled to setup DMA %d\n", rc); + pci_disable_device(pdev); + rc = -ENODEV; + goto err; + } + + sts = crystalhd_setup_cmd_context(&pinfo->cmds, pinfo); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("cmd setup :%d\n", sts); + pci_disable_device(pdev); + rc = -ENODEV; + goto err; + } + + pci_set_master(pdev); + + pci_set_drvdata(pdev, pinfo); + + g_adp_info = pinfo; + + return 0; + +err: + kfree(pinfo); + return rc; +} + +#ifdef CONFIG_PM +int chd_dec_pci_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct crystalhd_adp *adp; + struct crystalhd_ioctl_data *temp; + enum BC_STATUS sts = BC_STS_SUCCESS; + + adp = pci_get_drvdata(pdev); + if (!adp) { + BCMLOG_ERR("could not get adp\n"); + return -ENODEV; + } + + temp = chd_dec_alloc_iodata(adp, false); + if (!temp) { + BCMLOG_ERR("could not get ioctl data\n"); + return -ENODEV; + } + + sts = crystalhd_suspend(&adp->cmds, temp); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("BCM70012 Suspend %d\n", sts); + return -ENODEV; + } + + chd_dec_free_iodata(adp, temp, false); + chd_dec_disable_int(adp); + pci_save_state(pdev); + + /* Disable IO/bus master/irq router */ + pci_disable_device(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + return 0; +} + +int chd_dec_pci_resume(struct pci_dev *pdev) +{ + struct crystalhd_adp *adp; + enum BC_STATUS sts = BC_STS_SUCCESS; + int rc; + + adp = pci_get_drvdata(pdev); + if (!adp) { + BCMLOG_ERR("could not get adp\n"); + return -ENODEV; + } + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + + /* device's irq possibly is changed, driver should take care */ + if (pci_enable_device(pdev)) { + BCMLOG_ERR("Failed to enable PCI device\n"); + return 1; + } + + pci_set_master(pdev); + + rc = chd_dec_enable_int(adp); + if (rc) { + BCMLOG_ERR("_enable_int err:%d\n", rc); + pci_disable_device(pdev); + return -ENODEV; + } + + sts = crystalhd_resume(&adp->cmds); + if (sts != BC_STS_SUCCESS) { + BCMLOG_ERR("BCM70012 Resume %d\n", sts); + pci_disable_device(pdev); + return -ENODEV; + } + + return 0; +} +#endif + +static DEFINE_PCI_DEVICE_TABLE(chd_dec_pci_id_table) = { + { PCI_VDEVICE(BROADCOM, 0x1612), 8 }, + { 0, }, +}; +MODULE_DEVICE_TABLE(pci, chd_dec_pci_id_table); + +static struct pci_driver bc_chd_70012_driver = { + .name = "Broadcom 70012 Decoder", + .probe = chd_dec_pci_probe, + .remove = __devexit_p(chd_dec_pci_remove), + .id_table = chd_dec_pci_id_table, +#ifdef CONFIG_PM + .suspend = chd_dec_pci_suspend, + .resume = chd_dec_pci_resume +#endif +}; + +void chd_set_log_level(struct crystalhd_adp *adp, char *arg) +{ + if ((!arg) || (strlen(arg) < 3)) + g_linklog_level = BCMLOG_ERROR | BCMLOG_DATA; + else if (!strncmp(arg, "sstep", 5)) + g_linklog_level = BCMLOG_INFO | BCMLOG_DATA | BCMLOG_DBG | + BCMLOG_SSTEP | BCMLOG_ERROR; + else if (!strncmp(arg, "info", 4)) + g_linklog_level = BCMLOG_ERROR | BCMLOG_DATA | BCMLOG_INFO; + else if (!strncmp(arg, "debug", 5)) + g_linklog_level = BCMLOG_ERROR | BCMLOG_DATA | BCMLOG_INFO | + BCMLOG_DBG; + else if (!strncmp(arg, "pball", 5)) + g_linklog_level = 0xFFFFFFFF & ~(BCMLOG_SPINLOCK); + else if (!strncmp(arg, "silent", 6)) + g_linklog_level = 0; + else + g_linklog_level = 0; +} + +struct crystalhd_adp *chd_get_adp(void) +{ + return g_adp_info; +} + +static int __init chd_dec_module_init(void) +{ + int rc; + + chd_set_log_level(NULL, "debug"); + BCMLOG(BCMLOG_DATA, "Loading crystalhd %d.%d.%d\n", + crystalhd_kmod_major, crystalhd_kmod_minor, crystalhd_kmod_rev); + + rc = pci_register_driver(&bc_chd_70012_driver); + + if (rc < 0) + BCMLOG_ERR("Could not find any devices. err:%d\n", rc); + + return rc; +} +module_init(chd_dec_module_init); + +static void __exit chd_dec_module_cleanup(void) +{ + BCMLOG(BCMLOG_DATA, "unloading crystalhd %d.%d.%d\n", + crystalhd_kmod_major, crystalhd_kmod_minor, crystalhd_kmod_rev); + + pci_unregister_driver(&bc_chd_70012_driver); +} +module_exit(chd_dec_module_cleanup); + +MODULE_AUTHOR("Naren Sankar "); +MODULE_AUTHOR("Prasad Bolisetty "); +MODULE_DESCRIPTION(CRYSTAL_HD_NAME); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("bcm70012"); diff --git a/drivers/staging/crystalhd/crystalhd_lnx.h b/drivers/staging/crystalhd/crystalhd_lnx.h new file mode 100644 index 00000000..a9e36336 --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd_lnx.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (c) 2005-2009, Broadcom Corporation. + * + * Name: crystalhd_lnx . h + * + * Description: + * BCM70012 Linux driver + * + * HISTORY: + * + ********************************************************************** + * This file is part of the crystalhd device driver. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver. If not, see . + **********************************************************************/ + +#ifndef _CRYSTALHD_LNX_H_ +#define _CRYSTALHD_LNX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "crystalhd.h" + +#define CRYSTAL_HD_NAME "Broadcom Crystal HD Decoder (BCM70012) Driver" + +/* OS specific PCI information structure and adapter information. */ +struct crystalhd_adp { + /* Hardware borad/PCI specifics */ + char name[32]; + struct pci_dev *pdev; + + unsigned long pci_mem_start; + uint32_t pci_mem_len; + void *addr; + + unsigned long pci_i2o_start; + uint32_t pci_i2o_len; + void *i2o_addr; + + unsigned int drv_data; + unsigned int dmabits; /* 32 | 64 */ + unsigned int registered; + unsigned int present; + unsigned int msi; + + spinlock_t lock; + + /* API Related */ + int chd_dec_major; + unsigned int cfg_users; + + struct crystalhd_ioctl_data *idata_free_head; /* ioctl data pool */ + struct crystalhd_elem *elem_pool_head; /* Queue element pool */ + + struct crystalhd_cmd cmds; + + struct crystalhd_dio_req *ua_map_free_head; + struct pci_pool *fill_byte_pool; +}; + + +struct crystalhd_adp *chd_get_adp(void); +void chd_set_log_level(struct crystalhd_adp *adp, char *arg); + +#endif + diff --git a/drivers/staging/crystalhd/crystalhd_misc.c b/drivers/staging/crystalhd/crystalhd_misc.c new file mode 100644 index 00000000..b3a63781 --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd_misc.c @@ -0,0 +1,1031 @@ +/*************************************************************************** + * Copyright (c) 2005-2009, Broadcom Corporation. + * + * Name: crystalhd_misc . c + * + * Description: + * BCM70012 Linux driver misc routines. + * + * HISTORY: + * + ********************************************************************** + * This file is part of the crystalhd device driver. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver. If not, see . + **********************************************************************/ + +#include "crystalhd.h" + +#include + +uint32_t g_linklog_level; + +static inline uint32_t crystalhd_dram_rd(struct crystalhd_adp *adp, uint32_t mem_off) +{ + crystalhd_reg_wr(adp, DCI_DRAM_BASE_ADDR, (mem_off >> 19)); + return bc_dec_reg_rd(adp, (0x00380000 | (mem_off & 0x0007FFFF))); +} + +static inline void crystalhd_dram_wr(struct crystalhd_adp *adp, uint32_t mem_off, uint32_t val) +{ + crystalhd_reg_wr(adp, DCI_DRAM_BASE_ADDR, (mem_off >> 19)); + bc_dec_reg_wr(adp, (0x00380000 | (mem_off & 0x0007FFFF)), val); +} + +static inline enum BC_STATUS bc_chk_dram_range(struct crystalhd_adp *adp, uint32_t start_off, uint32_t cnt) +{ + return BC_STS_SUCCESS; +} + +static struct crystalhd_dio_req *crystalhd_alloc_dio(struct crystalhd_adp *adp) +{ + unsigned long flags = 0; + struct crystalhd_dio_req *temp = NULL; + + if (!adp) { + BCMLOG_ERR("Invalid Arg!!\n"); + return temp; + } + + spin_lock_irqsave(&adp->lock, flags); + temp = adp->ua_map_free_head; + if (temp) + adp->ua_map_free_head = adp->ua_map_free_head->next; + spin_unlock_irqrestore(&adp->lock, flags); + + return temp; +} + +static void crystalhd_free_dio(struct crystalhd_adp *adp, struct crystalhd_dio_req *dio) +{ + unsigned long flags = 0; + + if (!adp || !dio) + return; + spin_lock_irqsave(&adp->lock, flags); + dio->sig = crystalhd_dio_inv; + dio->page_cnt = 0; + dio->fb_size = 0; + memset(&dio->uinfo, 0, sizeof(dio->uinfo)); + dio->next = adp->ua_map_free_head; + adp->ua_map_free_head = dio; + spin_unlock_irqrestore(&adp->lock, flags); +} + +static struct crystalhd_elem *crystalhd_alloc_elem(struct crystalhd_adp *adp) +{ + unsigned long flags = 0; + struct crystalhd_elem *temp = NULL; + + if (!adp) + return temp; + spin_lock_irqsave(&adp->lock, flags); + temp = adp->elem_pool_head; + if (temp) { + adp->elem_pool_head = adp->elem_pool_head->flink; + memset(temp, 0, sizeof(*temp)); + } + spin_unlock_irqrestore(&adp->lock, flags); + + return temp; +} +static void crystalhd_free_elem(struct crystalhd_adp *adp, struct crystalhd_elem *elem) +{ + unsigned long flags = 0; + + if (!adp || !elem) + return; + spin_lock_irqsave(&adp->lock, flags); + elem->flink = adp->elem_pool_head; + adp->elem_pool_head = elem; + spin_unlock_irqrestore(&adp->lock, flags); +} + +static inline void crystalhd_set_sg(struct scatterlist *sg, struct page *page, + unsigned int len, unsigned int offset) +{ + sg_set_page(sg, page, len, offset); +#ifdef CONFIG_X86_64 + sg->dma_length = len; +#endif +} + +static inline void crystalhd_init_sg(struct scatterlist *sg, unsigned int entries) +{ + /* http://lkml.org/lkml/2007/11/27/68 */ + sg_init_table(sg, entries); +} + +/*========================== Extern ========================================*/ +/** + * bc_dec_reg_rd - Read 7412's device register. + * @adp: Adapter instance + * @reg_off: Register offset. + * + * Return: + * 32bit value read + * + * 7412's device register read routine. This interface use + * 7412's device access range mapped from BAR-2 (4M) of PCIe + * configuration space. + */ +uint32_t bc_dec_reg_rd(struct crystalhd_adp *adp, uint32_t reg_off) +{ + if (!adp || (reg_off > adp->pci_mem_len)) { + BCMLOG_ERR("dec_rd_reg_off outof range: 0x%08x\n", reg_off); + return 0; + } + + return readl(adp->addr + reg_off); +} + +/** + * bc_dec_reg_wr - Write 7412's device register + * @adp: Adapter instance + * @reg_off: Register offset. + * @val: Dword value to be written. + * + * Return: + * none. + * + * 7412's device register write routine. This interface use + * 7412's device access range mapped from BAR-2 (4M) of PCIe + * configuration space. + */ +void bc_dec_reg_wr(struct crystalhd_adp *adp, uint32_t reg_off, uint32_t val) +{ + if (!adp || (reg_off > adp->pci_mem_len)) { + BCMLOG_ERR("dec_wr_reg_off outof range: 0x%08x\n", reg_off); + return; + } + writel(val, adp->addr + reg_off); + udelay(8); +} + +/** + * crystalhd_reg_rd - Read Link's device register. + * @adp: Adapter instance + * @reg_off: Register offset. + * + * Return: + * 32bit value read + * + * Link device register read routine. This interface use + * Link's device access range mapped from BAR-1 (64K) of PCIe + * configuration space. + * + */ +uint32_t crystalhd_reg_rd(struct crystalhd_adp *adp, uint32_t reg_off) +{ + if (!adp || (reg_off > adp->pci_i2o_len)) { + BCMLOG_ERR("link_rd_reg_off outof range: 0x%08x\n", reg_off); + return 0; + } + return readl(adp->i2o_addr + reg_off); +} + +/** + * crystalhd_reg_wr - Write Link's device register + * @adp: Adapter instance + * @reg_off: Register offset. + * @val: Dword value to be written. + * + * Return: + * none. + * + * Link device register write routine. This interface use + * Link's device access range mapped from BAR-1 (64K) of PCIe + * configuration space. + * + */ +void crystalhd_reg_wr(struct crystalhd_adp *adp, uint32_t reg_off, uint32_t val) +{ + if (!adp || (reg_off > adp->pci_i2o_len)) { + BCMLOG_ERR("link_wr_reg_off outof range: 0x%08x\n", reg_off); + return; + } + writel(val, adp->i2o_addr + reg_off); +} + +/** + * crystalhd_mem_rd - Read data from 7412's DRAM area. + * @adp: Adapter instance + * @start_off: Start offset. + * @dw_cnt: Count in dwords. + * @rd_buff: Buffer to copy the data from dram. + * + * Return: + * Status. + * + * 7412's Dram read routine. + */ +enum BC_STATUS crystalhd_mem_rd(struct crystalhd_adp *adp, uint32_t start_off, + uint32_t dw_cnt, uint32_t *rd_buff) +{ + uint32_t ix = 0; + + if (!adp || !rd_buff || + (bc_chk_dram_range(adp, start_off, dw_cnt) != BC_STS_SUCCESS)) { + BCMLOG_ERR("Invalid arg\n"); + return BC_STS_INV_ARG; + } + for (ix = 0; ix < dw_cnt; ix++) + rd_buff[ix] = crystalhd_dram_rd(adp, (start_off + (ix * 4))); + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_mem_wr - Write data to 7412's DRAM area. + * @adp: Adapter instance + * @start_off: Start offset. + * @dw_cnt: Count in dwords. + * @wr_buff: Data Buffer to be written. + * + * Return: + * Status. + * + * 7412's Dram write routine. + */ +enum BC_STATUS crystalhd_mem_wr(struct crystalhd_adp *adp, uint32_t start_off, + uint32_t dw_cnt, uint32_t *wr_buff) +{ + uint32_t ix = 0; + + if (!adp || !wr_buff || + (bc_chk_dram_range(adp, start_off, dw_cnt) != BC_STS_SUCCESS)) { + BCMLOG_ERR("Invalid arg\n"); + return BC_STS_INV_ARG; + } + + for (ix = 0; ix < dw_cnt; ix++) + crystalhd_dram_wr(adp, (start_off + (ix * 4)), wr_buff[ix]); + + return BC_STS_SUCCESS; +} +/** + * crystalhd_pci_cfg_rd - PCIe config read + * @adp: Adapter instance + * @off: PCI config space offset. + * @len: Size -- Byte, Word & dword. + * @val: Value read + * + * Return: + * Status. + * + * Get value from Link's PCIe config space. + */ +enum BC_STATUS crystalhd_pci_cfg_rd(struct crystalhd_adp *adp, uint32_t off, + uint32_t len, uint32_t *val) +{ + enum BC_STATUS sts = BC_STS_SUCCESS; + int rc = 0; + + if (!adp || !val) { + BCMLOG_ERR("Invalid arg\n"); + return BC_STS_INV_ARG; + } + + switch (len) { + case 1: + rc = pci_read_config_byte(adp->pdev, off, (u8 *)val); + break; + case 2: + rc = pci_read_config_word(adp->pdev, off, (u16 *)val); + break; + case 4: + rc = pci_read_config_dword(adp->pdev, off, (u32 *)val); + break; + default: + rc = -EINVAL; + sts = BC_STS_INV_ARG; + BCMLOG_ERR("Invalid len:%d\n", len); + } + + if (rc && (sts == BC_STS_SUCCESS)) + sts = BC_STS_ERROR; + + return sts; +} + +/** + * crystalhd_pci_cfg_wr - PCIe config write + * @adp: Adapter instance + * @off: PCI config space offset. + * @len: Size -- Byte, Word & dword. + * @val: Value to be written + * + * Return: + * Status. + * + * Set value to Link's PCIe config space. + */ +enum BC_STATUS crystalhd_pci_cfg_wr(struct crystalhd_adp *adp, uint32_t off, + uint32_t len, uint32_t val) +{ + enum BC_STATUS sts = BC_STS_SUCCESS; + int rc = 0; + + if (!adp || !val) { + BCMLOG_ERR("Invalid arg\n"); + return BC_STS_INV_ARG; + } + + switch (len) { + case 1: + rc = pci_write_config_byte(adp->pdev, off, (u8)val); + break; + case 2: + rc = pci_write_config_word(adp->pdev, off, (u16)val); + break; + case 4: + rc = pci_write_config_dword(adp->pdev, off, val); + break; + default: + rc = -EINVAL; + sts = BC_STS_INV_ARG; + BCMLOG_ERR("Invalid len:%d\n", len); + } + + if (rc && (sts == BC_STS_SUCCESS)) + sts = BC_STS_ERROR; + + return sts; +} + +/** + * bc_kern_dma_alloc - Allocate memory for Dma rings + * @adp: Adapter instance + * @sz: Size of the memory to allocate. + * @phy_addr: Physical address of the memory allocated. + * Typedef to system's dma_addr_t (u64) + * + * Return: + * Pointer to allocated memory.. + * + * Wrapper to Linux kernel interface. + * + */ +void *bc_kern_dma_alloc(struct crystalhd_adp *adp, uint32_t sz, + dma_addr_t *phy_addr) +{ + void *temp = NULL; + + if (!adp || !sz || !phy_addr) { + BCMLOG_ERR("Invalide Arg..\n"); + return temp; + } + + temp = pci_alloc_consistent(adp->pdev, sz, phy_addr); + if (temp) + memset(temp, 0, sz); + + return temp; +} + +/** + * bc_kern_dma_free - Release Dma ring memory. + * @adp: Adapter instance + * @sz: Size of the memory to allocate. + * @ka: Kernel virtual address returned during _dio_alloc() + * @phy_addr: Physical address of the memory allocated. + * Typedef to system's dma_addr_t (u64) + * + * Return: + * none. + */ +void bc_kern_dma_free(struct crystalhd_adp *adp, uint32_t sz, void *ka, + dma_addr_t phy_addr) +{ + if (!adp || !ka || !sz || !phy_addr) { + BCMLOG_ERR("Invalide Arg..\n"); + return; + } + + pci_free_consistent(adp->pdev, sz, ka, phy_addr); +} + +/** + * crystalhd_create_dioq - Create Generic DIO queue + * @adp: Adapter instance + * @dioq_hnd: Handle to the dio queue created + * @cb : Optional - Call back To free the element. + * @cbctx: Context to pass to callback. + * + * Return: + * status + * + * Initialize Generic DIO queue to hold any data. Callback + * will be used to free elements while deleting the queue. + */ +enum BC_STATUS crystalhd_create_dioq(struct crystalhd_adp *adp, + struct crystalhd_dioq **dioq_hnd, + crystalhd_data_free_cb cb, void *cbctx) +{ + struct crystalhd_dioq *dioq = NULL; + + if (!adp || !dioq_hnd) { + BCMLOG_ERR("Invalid arg!!\n"); + return BC_STS_INV_ARG; + } + + dioq = kzalloc(sizeof(*dioq), GFP_KERNEL); + if (!dioq) + return BC_STS_INSUFF_RES; + + spin_lock_init(&dioq->lock); + dioq->sig = BC_LINK_DIOQ_SIG; + dioq->head = (struct crystalhd_elem *)&dioq->head; + dioq->tail = (struct crystalhd_elem *)&dioq->head; + crystalhd_create_event(&dioq->event); + dioq->adp = adp; + dioq->data_rel_cb = cb; + dioq->cb_context = cbctx; + *dioq_hnd = dioq; + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_delete_dioq - Delete Generic DIO queue + * @adp: Adapter instance + * @dioq: DIOQ instance.. + * + * Return: + * None. + * + * Release Generic DIO queue. This function will remove + * all the entries from the Queue and will release data + * by calling the call back provided during creation. + * + */ +void crystalhd_delete_dioq(struct crystalhd_adp *adp, struct crystalhd_dioq *dioq) +{ + void *temp; + + if (!dioq || (dioq->sig != BC_LINK_DIOQ_SIG)) + return; + + do { + temp = crystalhd_dioq_fetch(dioq); + if (temp && dioq->data_rel_cb) + dioq->data_rel_cb(dioq->cb_context, temp); + } while (temp); + dioq->sig = 0; + kfree(dioq); +} + +/** + * crystalhd_dioq_add - Add new DIO request element. + * @ioq: DIO queue instance + * @t: DIO request to be added. + * @wake: True - Wake up suspended process. + * @tag: Special tag to assign - For search and get. + * + * Return: + * Status. + * + * Insert new element to Q tail. + */ +enum BC_STATUS crystalhd_dioq_add(struct crystalhd_dioq *ioq, void *data, + bool wake, uint32_t tag) +{ + unsigned long flags = 0; + struct crystalhd_elem *tmp; + + if (!ioq || (ioq->sig != BC_LINK_DIOQ_SIG) || !data) { + BCMLOG_ERR("Invalid arg!!\n"); + return BC_STS_INV_ARG; + } + + tmp = crystalhd_alloc_elem(ioq->adp); + if (!tmp) { + BCMLOG_ERR("No free elements.\n"); + return BC_STS_INSUFF_RES; + } + + tmp->data = data; + tmp->tag = tag; + spin_lock_irqsave(&ioq->lock, flags); + tmp->flink = (struct crystalhd_elem *)&ioq->head; + tmp->blink = ioq->tail; + tmp->flink->blink = tmp; + tmp->blink->flink = tmp; + ioq->count++; + spin_unlock_irqrestore(&ioq->lock, flags); + + if (wake) + crystalhd_set_event(&ioq->event); + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_dioq_fetch - Fetch element from head. + * @ioq: DIO queue instance + * + * Return: + * data element from the head.. + * + * Remove an element from Queue. + */ +void *crystalhd_dioq_fetch(struct crystalhd_dioq *ioq) +{ + unsigned long flags = 0; + struct crystalhd_elem *tmp; + struct crystalhd_elem *ret = NULL; + void *data = NULL; + + if (!ioq || (ioq->sig != BC_LINK_DIOQ_SIG)) { + BCMLOG_ERR("Invalid arg!!\n"); + return data; + } + + spin_lock_irqsave(&ioq->lock, flags); + tmp = ioq->head; + if (tmp != (struct crystalhd_elem *)&ioq->head) { + ret = tmp; + tmp->flink->blink = tmp->blink; + tmp->blink->flink = tmp->flink; + ioq->count--; + } + spin_unlock_irqrestore(&ioq->lock, flags); + if (ret) { + data = ret->data; + crystalhd_free_elem(ioq->adp, ret); + } + + return data; +} +/** + * crystalhd_dioq_find_and_fetch - Search the tag and Fetch element + * @ioq: DIO queue instance + * @tag: Tag to search for. + * + * Return: + * element from the head.. + * + * Search TAG and remove the element. + */ +void *crystalhd_dioq_find_and_fetch(struct crystalhd_dioq *ioq, uint32_t tag) +{ + unsigned long flags = 0; + struct crystalhd_elem *tmp; + struct crystalhd_elem *ret = NULL; + void *data = NULL; + + if (!ioq || (ioq->sig != BC_LINK_DIOQ_SIG)) { + BCMLOG_ERR("Invalid arg!!\n"); + return data; + } + + spin_lock_irqsave(&ioq->lock, flags); + tmp = ioq->head; + while (tmp != (struct crystalhd_elem *)&ioq->head) { + if (tmp->tag == tag) { + ret = tmp; + tmp->flink->blink = tmp->blink; + tmp->blink->flink = tmp->flink; + ioq->count--; + break; + } + tmp = tmp->flink; + } + spin_unlock_irqrestore(&ioq->lock, flags); + + if (ret) { + data = ret->data; + crystalhd_free_elem(ioq->adp, ret); + } + + return data; +} + +/** + * crystalhd_dioq_fetch_wait - Fetch element from Head. + * @ioq: DIO queue instance + * @to_secs: Wait timeout in seconds.. + * + * Return: + * element from the head.. + * + * Return element from head if Q is not empty. Wait for new element + * if Q is empty for Timeout seconds. + */ +void *crystalhd_dioq_fetch_wait(struct crystalhd_dioq *ioq, uint32_t to_secs, + uint32_t *sig_pend) +{ + unsigned long flags = 0; + int rc = 0, count; + void *tmp = NULL; + + if (!ioq || (ioq->sig != BC_LINK_DIOQ_SIG) || !to_secs || !sig_pend) { + BCMLOG_ERR("Invalid arg!!\n"); + return tmp; + } + + count = to_secs; + spin_lock_irqsave(&ioq->lock, flags); + while ((ioq->count == 0) && count) { + spin_unlock_irqrestore(&ioq->lock, flags); + + crystalhd_wait_on_event(&ioq->event, (ioq->count > 0), 1000, rc, 0); + if (rc == 0) { + goto out; + } else if (rc == -EINTR) { + BCMLOG(BCMLOG_INFO, "Cancelling fetch wait\n"); + *sig_pend = 1; + return tmp; + } + spin_lock_irqsave(&ioq->lock, flags); + count--; + } + spin_unlock_irqrestore(&ioq->lock, flags); + +out: + return crystalhd_dioq_fetch(ioq); +} + +/** + * crystalhd_map_dio - Map user address for DMA + * @adp: Adapter instance + * @ubuff: User buffer to map. + * @ubuff_sz: User buffer size. + * @uv_offset: UV buffer offset. + * @en_422mode: TRUE:422 FALSE:420 Capture mode. + * @dir_tx: TRUE for Tx (To device from host) + * @dio_hnd: Handle to mapped DIO request. + * + * Return: + * Status. + * + * This routine maps user address and lock pages for DMA. + * + */ +enum BC_STATUS crystalhd_map_dio(struct crystalhd_adp *adp, void *ubuff, + uint32_t ubuff_sz, uint32_t uv_offset, + bool en_422mode, bool dir_tx, + struct crystalhd_dio_req **dio_hnd) +{ + struct crystalhd_dio_req *dio; + /* FIXME: jarod: should some of these unsigned longs be uint32_t or uintptr_t? */ + unsigned long start = 0, end = 0, uaddr = 0, count = 0; + unsigned long spsz = 0, uv_start = 0; + int i = 0, rw = 0, res = 0, nr_pages = 0, skip_fb_sg = 0; + + if (!adp || !ubuff || !ubuff_sz || !dio_hnd) { + BCMLOG_ERR("Invalid arg\n"); + return BC_STS_INV_ARG; + } + /* Compute pages */ + uaddr = (unsigned long)ubuff; + count = (unsigned long)ubuff_sz; + end = (uaddr + count + PAGE_SIZE - 1) >> PAGE_SHIFT; + start = uaddr >> PAGE_SHIFT; + nr_pages = end - start; + + if (!count || ((uaddr + count) < uaddr)) { + BCMLOG_ERR("User addr overflow!!\n"); + return BC_STS_INV_ARG; + } + + dio = crystalhd_alloc_dio(adp); + if (!dio) { + BCMLOG_ERR("dio pool empty..\n"); + return BC_STS_INSUFF_RES; + } + + if (dir_tx) { + rw = WRITE; + dio->direction = DMA_TO_DEVICE; + } else { + rw = READ; + dio->direction = DMA_FROM_DEVICE; + } + + if (nr_pages > dio->max_pages) { + BCMLOG_ERR("max_pages(%d) exceeded(%d)!!\n", + dio->max_pages, nr_pages); + crystalhd_unmap_dio(adp, dio); + return BC_STS_INSUFF_RES; + } + + if (uv_offset) { + uv_start = (uaddr + (unsigned long)uv_offset) >> PAGE_SHIFT; + dio->uinfo.uv_sg_ix = uv_start - start; + dio->uinfo.uv_sg_off = ((uaddr + (unsigned long)uv_offset) & ~PAGE_MASK); + } + + dio->fb_size = ubuff_sz & 0x03; + if (dio->fb_size) { + res = copy_from_user(dio->fb_va, + (void *)(uaddr + count - dio->fb_size), + dio->fb_size); + if (res) { + BCMLOG_ERR("failed %d to copy %u fill bytes from %p\n", + res, dio->fb_size, + (void *)(uaddr + count-dio->fb_size)); + crystalhd_unmap_dio(adp, dio); + return BC_STS_INSUFF_RES; + } + } + + down_read(¤t->mm->mmap_sem); + res = get_user_pages(current, current->mm, uaddr, nr_pages, rw == READ, + 0, dio->pages, NULL); + up_read(¤t->mm->mmap_sem); + + /* Save for release..*/ + dio->sig = crystalhd_dio_locked; + if (res < nr_pages) { + BCMLOG_ERR("get pages failed: %d-%d\n", nr_pages, res); + dio->page_cnt = res; + crystalhd_unmap_dio(adp, dio); + return BC_STS_ERROR; + } + + dio->page_cnt = nr_pages; + /* Get scatter/gather */ + crystalhd_init_sg(dio->sg, dio->page_cnt); + crystalhd_set_sg(&dio->sg[0], dio->pages[0], 0, uaddr & ~PAGE_MASK); + if (nr_pages > 1) { + dio->sg[0].length = PAGE_SIZE - dio->sg[0].offset; + +#ifdef CONFIG_X86_64 + dio->sg[0].dma_length = dio->sg[0].length; +#endif + count -= dio->sg[0].length; + for (i = 1; i < nr_pages; i++) { + if (count < 4) { + spsz = count; + skip_fb_sg = 1; + } else { + spsz = (count < PAGE_SIZE) ? + (count & ~0x03) : PAGE_SIZE; + } + crystalhd_set_sg(&dio->sg[i], dio->pages[i], spsz, 0); + count -= spsz; + } + } else { + if (count < 4) { + dio->sg[0].length = count; + skip_fb_sg = 1; + } else { + dio->sg[0].length = count - dio->fb_size; + } +#ifdef CONFIG_X86_64 + dio->sg[0].dma_length = dio->sg[0].length; +#endif + } + dio->sg_cnt = pci_map_sg(adp->pdev, dio->sg, + dio->page_cnt, dio->direction); + if (dio->sg_cnt <= 0) { + BCMLOG_ERR("sg map %d-%d\n", dio->sg_cnt, dio->page_cnt); + crystalhd_unmap_dio(adp, dio); + return BC_STS_ERROR; + } + if (dio->sg_cnt && skip_fb_sg) + dio->sg_cnt -= 1; + dio->sig = crystalhd_dio_sg_mapped; + /* Fill in User info.. */ + dio->uinfo.xfr_len = ubuff_sz; + dio->uinfo.xfr_buff = ubuff; + dio->uinfo.uv_offset = uv_offset; + dio->uinfo.b422mode = en_422mode; + dio->uinfo.dir_tx = dir_tx; + + *dio_hnd = dio; + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_unmap_sgl - Release mapped resources + * @adp: Adapter instance + * @dio: DIO request instance + * + * Return: + * Status. + * + * This routine is to unmap the user buffer pages. + */ +enum BC_STATUS crystalhd_unmap_dio(struct crystalhd_adp *adp, struct crystalhd_dio_req *dio) +{ + struct page *page = NULL; + int j = 0; + + if (!adp || !dio) { + BCMLOG_ERR("Invalid arg\n"); + return BC_STS_INV_ARG; + } + + if ((dio->page_cnt > 0) && (dio->sig != crystalhd_dio_inv)) { + for (j = 0; j < dio->page_cnt; j++) { + page = dio->pages[j]; + if (page) { + if (!PageReserved(page) && + (dio->direction == DMA_FROM_DEVICE)) + SetPageDirty(page); + page_cache_release(page); + } + } + } + if (dio->sig == crystalhd_dio_sg_mapped) + pci_unmap_sg(adp->pdev, dio->sg, dio->page_cnt, dio->direction); + + crystalhd_free_dio(adp, dio); + + return BC_STS_SUCCESS; +} + +/** + * crystalhd_create_dio_pool - Allocate mem pool for DIO management. + * @adp: Adapter instance + * @max_pages: Max pages for size calculation. + * + * Return: + * system error. + * + * This routine creates a memory pool to hold dio context for + * for HW Direct IO operation. + */ +int crystalhd_create_dio_pool(struct crystalhd_adp *adp, uint32_t max_pages) +{ + uint32_t asz = 0, i = 0; + uint8_t *temp; + struct crystalhd_dio_req *dio; + + if (!adp || !max_pages) { + BCMLOG_ERR("Invalid Arg!!\n"); + return -EINVAL; + } + + /* Get dma memory for fill byte handling..*/ + adp->fill_byte_pool = pci_pool_create("crystalhd_fbyte", + adp->pdev, 8, 8, 0); + if (!adp->fill_byte_pool) { + BCMLOG_ERR("failed to create fill byte pool\n"); + return -ENOMEM; + } + + /* Get the max size from user based on 420/422 modes */ + asz = (sizeof(*dio->pages) * max_pages) + + (sizeof(*dio->sg) * max_pages) + sizeof(*dio); + + BCMLOG(BCMLOG_DBG, "Initializing Dio pool %d %d %x %p\n", + BC_LINK_SG_POOL_SZ, max_pages, asz, adp->fill_byte_pool); + + for (i = 0; i < BC_LINK_SG_POOL_SZ; i++) { + temp = kzalloc(asz, GFP_KERNEL); + if ((temp) == NULL) { + BCMLOG_ERR("Failed to alloc %d mem\n", asz); + return -ENOMEM; + } + + dio = (struct crystalhd_dio_req *)temp; + temp += sizeof(*dio); + dio->pages = (struct page **)temp; + temp += (sizeof(*dio->pages) * max_pages); + dio->sg = (struct scatterlist *)temp; + dio->max_pages = max_pages; + dio->fb_va = pci_pool_alloc(adp->fill_byte_pool, GFP_KERNEL, + &dio->fb_pa); + if (!dio->fb_va) { + BCMLOG_ERR("fill byte alloc failed.\n"); + return -ENOMEM; + } + + crystalhd_free_dio(adp, dio); + } + + return 0; +} + +/** + * crystalhd_destroy_dio_pool - Release DIO mem pool. + * @adp: Adapter instance + * + * Return: + * none. + * + * This routine releases dio memory pool during close. + */ +void crystalhd_destroy_dio_pool(struct crystalhd_adp *adp) +{ + struct crystalhd_dio_req *dio; + int count = 0; + + if (!adp) { + BCMLOG_ERR("Invalid Arg!!\n"); + return; + } + + do { + dio = crystalhd_alloc_dio(adp); + if (dio) { + if (dio->fb_va) + pci_pool_free(adp->fill_byte_pool, + dio->fb_va, dio->fb_pa); + count++; + kfree(dio); + } + } while (dio); + + if (adp->fill_byte_pool) { + pci_pool_destroy(adp->fill_byte_pool); + adp->fill_byte_pool = NULL; + } + + BCMLOG(BCMLOG_DBG, "Released dio pool %d\n", count); +} + +/** + * crystalhd_create_elem_pool - List element pool creation. + * @adp: Adapter instance + * @pool_size: Number of elements in the pool. + * + * Return: + * 0 - success, <0 error + * + * Create general purpose list element pool to hold pending, + * and active requests. + */ +int __devinit crystalhd_create_elem_pool(struct crystalhd_adp *adp, + uint32_t pool_size) +{ + uint32_t i; + struct crystalhd_elem *temp; + + if (!adp || !pool_size) + return -EINVAL; + + for (i = 0; i < pool_size; i++) { + temp = kzalloc(sizeof(*temp), GFP_KERNEL); + if (!temp) { + BCMLOG_ERR("kalloc failed\n"); + return -ENOMEM; + } + crystalhd_free_elem(adp, temp); + } + BCMLOG(BCMLOG_DBG, "allocated %d elem\n", pool_size); + return 0; +} + +/** + * crystalhd_delete_elem_pool - List element pool deletion. + * @adp: Adapter instance + * + * Return: + * none + * + * Delete general purpose list element pool. + */ +void crystalhd_delete_elem_pool(struct crystalhd_adp *adp) +{ + struct crystalhd_elem *temp; + int dbg_cnt = 0; + + if (!adp) + return; + + do { + temp = crystalhd_alloc_elem(adp); + if (temp) { + kfree(temp); + dbg_cnt++; + } + } while (temp); + + BCMLOG(BCMLOG_DBG, "released %d elem\n", dbg_cnt); +} + +/*================ Debug support routines.. ================================*/ +void crystalhd_show_buffer(uint32_t off, uint8_t *buff, uint32_t dwcount) +{ + uint32_t i, k = 1; + + for (i = 0; i < dwcount; i++) { + if (k == 1) + BCMLOG(BCMLOG_DATA, "0x%08X : ", off); + + BCMLOG(BCMLOG_DATA, " 0x%08X ", *((uint32_t *)buff)); + + buff += sizeof(uint32_t); + off += sizeof(uint32_t); + k++; + if ((i == dwcount - 1) || (k > 4)) { + BCMLOG(BCMLOG_DATA, "\n"); + k = 1; + } + } +} diff --git a/drivers/staging/crystalhd/crystalhd_misc.h b/drivers/staging/crystalhd/crystalhd_misc.h new file mode 100644 index 00000000..8cdaa7a3 --- /dev/null +++ b/drivers/staging/crystalhd/crystalhd_misc.h @@ -0,0 +1,221 @@ +/*************************************************************************** + * Copyright (c) 2005-2009, Broadcom Corporation. + * + * Name: crystalhd_misc . h + * + * Description: + * BCM70012 Linux driver general purpose routines. + * Includes reg/mem read and write routines. + * + * HISTORY: + * + ********************************************************************** + * This file is part of the crystalhd device driver. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver. If not, see . + **********************************************************************/ + +#ifndef _CRYSTALHD_MISC_H_ +#define _CRYSTALHD_MISC_H_ + +#include "crystalhd.h" + +#include +#include +#include +#include +#include +#include +#include +#include "bc_dts_glob_lnx.h" + +/* Global log level variable defined in crystal_misc.c file */ +extern uint32_t g_linklog_level; + +/* Global element pool for all Queue management. + * TX: Active = BC_TX_LIST_CNT, Free = BC_TX_LIST_CNT. + * RX: Free = BC_RX_LIST_CNT, Active = 2 + * FW-CMD: 4 + */ +#define BC_LINK_ELEM_POOL_SZ ((BC_TX_LIST_CNT * 2) + BC_RX_LIST_CNT + 2 + 4) + +/* Driver's IODATA pool count */ +#define CHD_IODATA_POOL_SZ (BC_IOCTL_DATA_POOL_SIZE * BC_LINK_MAX_OPENS) + +/* Scatter Gather memory pool size for Tx and Rx */ +#define BC_LINK_SG_POOL_SZ (BC_TX_LIST_CNT + BC_RX_LIST_CNT) + +enum crystalhd_dio_sig { + crystalhd_dio_inv = 0, + crystalhd_dio_locked, + crystalhd_dio_sg_mapped, +}; + +struct crystalhd_dio_user_info { + void *xfr_buff; + uint32_t xfr_len; + uint32_t uv_offset; + bool dir_tx; + + uint32_t uv_sg_ix; + uint32_t uv_sg_off; + int comp_sts; + int ev_sts; + uint32_t y_done_sz; + uint32_t uv_done_sz; + uint32_t comp_flags; + bool b422mode; +}; + +struct crystalhd_dio_req { + uint32_t sig; + uint32_t max_pages; + struct page **pages; + struct scatterlist *sg; + int sg_cnt; + int page_cnt; + int direction; + struct crystalhd_dio_user_info uinfo; + void *fb_va; + uint32_t fb_size; + dma_addr_t fb_pa; + struct crystalhd_dio_req *next; +}; + +#define BC_LINK_DIOQ_SIG (0x09223280) + +struct crystalhd_elem { + struct crystalhd_elem *flink; + struct crystalhd_elem *blink; + void *data; + uint32_t tag; +}; + +typedef void (*crystalhd_data_free_cb)(void *context, void *data); + +struct crystalhd_dioq { + uint32_t sig; + struct crystalhd_adp *adp; + struct crystalhd_elem *head; + struct crystalhd_elem *tail; + uint32_t count; + spinlock_t lock; + wait_queue_head_t event; + crystalhd_data_free_cb data_rel_cb; + void *cb_context; +}; + +typedef void (*hw_comp_callback)(struct crystalhd_dio_req *, + wait_queue_head_t *event, enum BC_STATUS sts); + +/*========= Decoder (7412) register access routines.================= */ +uint32_t bc_dec_reg_rd(struct crystalhd_adp *, uint32_t); +void bc_dec_reg_wr(struct crystalhd_adp *, uint32_t, uint32_t); + +/*========= Link (70012) register access routines.. =================*/ +uint32_t crystalhd_reg_rd(struct crystalhd_adp *, uint32_t); +void crystalhd_reg_wr(struct crystalhd_adp *, uint32_t, uint32_t); + +/*========= Decoder (7412) memory access routines..=================*/ +enum BC_STATUS crystalhd_mem_rd(struct crystalhd_adp *, uint32_t, uint32_t, uint32_t *); +enum BC_STATUS crystalhd_mem_wr(struct crystalhd_adp *, uint32_t, uint32_t, uint32_t *); + +/*==========Link (70012) PCIe Config access routines.================*/ +enum BC_STATUS crystalhd_pci_cfg_rd(struct crystalhd_adp *, uint32_t, uint32_t, uint32_t *); +enum BC_STATUS crystalhd_pci_cfg_wr(struct crystalhd_adp *, uint32_t, uint32_t, uint32_t); + +/*========= Linux Kernel Interface routines. ======================= */ +void *bc_kern_dma_alloc(struct crystalhd_adp *, uint32_t, dma_addr_t *); +void bc_kern_dma_free(struct crystalhd_adp *, uint32_t, + void *, dma_addr_t); +#define crystalhd_create_event(_ev) init_waitqueue_head(_ev) +#define crystalhd_set_event(_ev) wake_up_interruptible(_ev) +#define crystalhd_wait_on_event(ev, condition, timeout, ret, nosig) \ +do { \ + DECLARE_WAITQUEUE(entry, current); \ + unsigned long end = jiffies + ((timeout * HZ) / 1000); \ + ret = 0; \ + add_wait_queue(ev, &entry); \ + for (;;) { \ + __set_current_state(TASK_INTERRUPTIBLE); \ + if (condition) { \ + break; \ + } \ + if (time_after_eq(jiffies, end)) { \ + ret = -EBUSY; \ + break; \ + } \ + schedule_timeout((HZ / 100 > 1) ? HZ / 100 : 1); \ + if (!nosig && signal_pending(current)) { \ + ret = -EINTR; \ + break; \ + } \ + } \ + __set_current_state(TASK_RUNNING); \ + remove_wait_queue(ev, &entry); \ +} while (0) + +/*================ Direct IO mapping routines ==================*/ +extern int crystalhd_create_dio_pool(struct crystalhd_adp *, uint32_t); +extern void crystalhd_destroy_dio_pool(struct crystalhd_adp *); +extern enum BC_STATUS crystalhd_map_dio(struct crystalhd_adp *, void *, uint32_t, + uint32_t, bool, bool, struct crystalhd_dio_req**); + +extern enum BC_STATUS crystalhd_unmap_dio(struct crystalhd_adp *, struct crystalhd_dio_req*); +#define crystalhd_get_sgle_paddr(_dio, _ix) (cpu_to_le64(sg_dma_address(&_dio->sg[_ix]))) +#define crystalhd_get_sgle_len(_dio, _ix) (cpu_to_le32(sg_dma_len(&_dio->sg[_ix]))) + +/*================ General Purpose Queues ==================*/ +extern enum BC_STATUS crystalhd_create_dioq(struct crystalhd_adp *, struct crystalhd_dioq **, crystalhd_data_free_cb , void *); +extern void crystalhd_delete_dioq(struct crystalhd_adp *, struct crystalhd_dioq *); +extern enum BC_STATUS crystalhd_dioq_add(struct crystalhd_dioq *ioq, void *data, bool wake, uint32_t tag); +extern void *crystalhd_dioq_fetch(struct crystalhd_dioq *ioq); +extern void *crystalhd_dioq_find_and_fetch(struct crystalhd_dioq *ioq, uint32_t tag); +extern void *crystalhd_dioq_fetch_wait(struct crystalhd_dioq *ioq, uint32_t to_secs, uint32_t *sig_pend); + +#define crystalhd_dioq_count(_ioq) ((_ioq) ? _ioq->count : 0) + +extern int crystalhd_create_elem_pool(struct crystalhd_adp *, uint32_t); +extern void crystalhd_delete_elem_pool(struct crystalhd_adp *); + + +/*================ Debug routines/macros .. ================================*/ +extern void crystalhd_show_buffer(uint32_t off, uint8_t *buff, uint32_t dwcount); + +enum _chd_log_levels { + BCMLOG_ERROR = 0x80000000, /* Don't disable this option */ + BCMLOG_DATA = 0x40000000, /* Data, enable by default */ + BCMLOG_SPINLOCK = 0x20000000, /* Spcial case for Spin locks*/ + + /* Following are allowed only in debug mode */ + BCMLOG_INFO = 0x00000001, /* Generic informational */ + BCMLOG_DBG = 0x00000002, /* First level Debug info */ + BCMLOG_SSTEP = 0x00000004, /* Stepping information */ +}; + + +#define BCMLOG(trace, fmt, args...) \ +do { \ + if (g_linklog_level & trace) \ + printk(fmt, ##args); \ +} while (0) + + +#define BCMLOG_ERR(fmt, args...) \ +do { \ + if (g_linklog_level & BCMLOG_ERROR) \ + printk(KERN_ERR "*ERR*:%s:%d: "fmt, \ + __FILE__, __LINE__, ##args); \ +} while (0) + +#endif diff --git a/drivers/staging/cxt1e1/Kconfig b/drivers/staging/cxt1e1/Kconfig new file mode 100644 index 00000000..947f42a6 --- /dev/null +++ b/drivers/staging/cxt1e1/Kconfig @@ -0,0 +1,21 @@ +config CXT1E1 + tristate "SBE wanPMC-C[421]E1T1 hardware support" + depends on HDLC && PCI + ---help--- + This driver supports the SBE wanPMC-CxT1E1 1, 2 and 4 port T3 + channelized stream WAN adapter card which contains a HDLC/Transparent + mode controller. + + If you want to compile this driver as a module say M here. + The module will be called 'cxt1e1'. + + If unsure, say N. + +config SBE_PMCC4_NCOMM + bool "SBE PMCC4 NCOMM support" + depends on CXT1E1 + ---help--- + SBE supplies optional support for NCOMM products. + + If you have purchased this optional support you must say Y + here to allow the driver to operate with the NCOMM product. diff --git a/drivers/staging/cxt1e1/Makefile b/drivers/staging/cxt1e1/Makefile new file mode 100644 index 00000000..e99b8231 --- /dev/null +++ b/drivers/staging/cxt1e1/Makefile @@ -0,0 +1,19 @@ +obj-$(CONFIG_CXT1E1) += cxt1e1.o + +ccflags-y := -DSBE_PMCC4_ENABLE +ccflags-y += -DSBE_ISR_TASKLET +ccflags-y += -DSBE_INCLUDE_SYMBOLS + +cxt1e1-y := \ + ossiRelease.o \ + musycc.o \ + pmcc4_drv.o \ + comet.o \ + linux.o \ + functions.o \ + hwprobe.o \ + sbeproc.o \ + pmc93x6_eeprom.o \ + sbecrc.o \ + comet_tables.o \ + sbeid.o diff --git a/drivers/staging/cxt1e1/comet.c b/drivers/staging/cxt1e1/comet.c new file mode 100644 index 00000000..52224cdc --- /dev/null +++ b/drivers/staging/cxt1e1/comet.c @@ -0,0 +1,568 @@ +/* Copyright (C) 2003-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include "pmcc4_sysdep.h" +#include "sbecom_inline_linux.h" +#include "libsbew.h" +#include "pmcc4.h" +#include "comet.h" +#include "comet_tables.h" + +#ifdef SBE_INCLUDE_SYMBOLS +#define STATIC +#else +#define STATIC static +#endif + + +extern int cxt1e1_log_level; + +#define COMET_NUM_SAMPLES 24 /* Number of entries in the waveform table */ +#define COMET_NUM_UNITS 5 /* Number of points per entry in table */ + +/* forward references */ +STATIC void SetPwrLevel (comet_t * comet); +STATIC void WrtRcvEqualizerTbl (ci_t * ci, comet_t * comet, u_int32_t *table); +STATIC void WrtXmtWaveformTbl (ci_t * ci, comet_t * comet, u_int8_t table[COMET_NUM_SAMPLES][COMET_NUM_UNITS]); + + +void *TWV_table[12] = { + TWVLongHaul0DB, TWVLongHaul7_5DB, TWVLongHaul15DB, TWVLongHaul22_5DB, + TWVShortHaul0, TWVShortHaul1, TWVShortHaul2, TWVShortHaul3, TWVShortHaul4, + TWVShortHaul5, + TWV_E1_75Ohm, /** PORT POINT - 75 Ohm not supported **/ + TWV_E1_120Ohm +}; + + +static int +lbo_tbl_lkup (int t1, int lbo) +{ + if ((lbo < CFG_LBO_LH0) || (lbo > CFG_LBO_E120)) /* error switches to + * default */ + { + if (t1) + lbo = CFG_LBO_LH0; /* default T1 waveform table */ + else + lbo = CFG_LBO_E120; /* default E1 waveform table */ + } + return (lbo - 1); /* make index ZERO relative */ +} + + +void +init_comet (void *ci, comet_t * comet, u_int32_t port_mode, int clockmaster, + u_int8_t moreParams) +{ + u_int8_t isT1mode; + u_int8_t tix = CFG_LBO_LH0; /* T1 default */ + + isT1mode = IS_FRAME_ANY_T1 (port_mode); + /* T1 or E1 */ + if (isT1mode) + { + pci_write_32 ((u_int32_t *) &comet->gbl_cfg, 0xa0); /* Select T1 Mode & PIO + * output enabled */ + tix = lbo_tbl_lkup (isT1mode, CFG_LBO_LH0); /* default T1 waveform + * table */ + } else + { + pci_write_32 ((u_int32_t *) &comet->gbl_cfg, 0x81); /* Select E1 Mode & PIO + * output enabled */ + tix = lbo_tbl_lkup (isT1mode, CFG_LBO_E120); /* default E1 waveform + * table */ + } + + if (moreParams & CFG_LBO_MASK) + tix = lbo_tbl_lkup (isT1mode, moreParams & CFG_LBO_MASK); /* dial-in requested + * waveform table */ + + /* Tx line Intfc cfg ** Set for analog & no special patterns */ + pci_write_32 ((u_int32_t *) &comet->tx_line_cfg, 0x00); /* Transmit Line + * Interface Config. */ + + /* master test ** Ignore Test settings for now */ + pci_write_32 ((u_int32_t *) &comet->mtest, 0x00); /* making sure it's + * Default value */ + + /* Turn on Center (CENT) and everything else off */ + pci_write_32 ((u_int32_t *) &comet->rjat_cfg, 0x10); /* RJAT cfg */ + /* Set Jitter Attenuation to recommend T1 values */ + if (isT1mode) + { + pci_write_32 ((u_int32_t *) &comet->rjat_n1clk, 0x2F); /* RJAT Divider N1 + * Control */ + pci_write_32 ((u_int32_t *) &comet->rjat_n2clk, 0x2F); /* RJAT Divider N2 + * Control */ + } else + { + pci_write_32 ((u_int32_t *) &comet->rjat_n1clk, 0xFF); /* RJAT Divider N1 + * Control */ + pci_write_32 ((u_int32_t *) &comet->rjat_n2clk, 0xFF); /* RJAT Divider N2 + * Control */ + } + + /* Turn on Center (CENT) and everything else off */ + pci_write_32 ((u_int32_t *) &comet->tjat_cfg, 0x10); /* TJAT Config. */ + + /* Do not bypass jitter attenuation and bypass elastic store */ + pci_write_32 ((u_int32_t *) &comet->rx_opt, 0x00); /* rx opts */ + + /* TJAT ctrl & TJAT divider ctrl */ + /* Set Jitter Attenuation to recommended T1 values */ + if (isT1mode) + { + pci_write_32 ((u_int32_t *) &comet->tjat_n1clk, 0x2F); /* TJAT Divider N1 + * Control */ + pci_write_32 ((u_int32_t *) &comet->tjat_n2clk, 0x2F); /* TJAT Divider N2 + * Control */ + } else + { + pci_write_32 ((u_int32_t *) &comet->tjat_n1clk, 0xFF); /* TJAT Divider N1 + * Control */ + pci_write_32 ((u_int32_t *) &comet->tjat_n2clk, 0xFF); /* TJAT Divider N2 + * Control */ + } + + /* 1c: rx ELST cfg 20: tx ELST cfg 28&38: rx&tx data link ctrl */ + if (isT1mode) + { /* Select 193-bit frame format */ + pci_write_32 ((u_int32_t *) &comet->rx_elst_cfg, 0x00); + pci_write_32 ((u_int32_t *) &comet->tx_elst_cfg, 0x00); + } else + { /* Select 256-bit frame format */ + pci_write_32 ((u_int32_t *) &comet->rx_elst_cfg, 0x03); + pci_write_32 ((u_int32_t *) &comet->tx_elst_cfg, 0x03); + pci_write_32 ((u_int32_t *) &comet->rxce1_ctl, 0x00); /* disable T1 data link + * receive */ + pci_write_32 ((u_int32_t *) &comet->txci1_ctl, 0x00); /* disable T1 data link + * transmit */ + } + + /* the following is a default value */ + /* Enable 8 out of 10 validation */ + pci_write_32 ((u_int32_t *) &comet->t1_rboc_ena, 0x00); /* t1RBOC + * enable(BOC:BitOriented + * Code) */ + if (isT1mode) + { + + /* IBCD cfg: aka Inband Code Detection ** loopback code length set to */ + pci_write_32 ((u_int32_t *) &comet->ibcd_cfg, 0x04); /* 6 bit down, 5 bit up + * (assert) */ + pci_write_32 ((u_int32_t *) &comet->ibcd_act, 0x08); /* line loopback + * activate pattern */ + pci_write_32 ((u_int32_t *) &comet->ibcd_deact, 0x24); /* deactivate code + * pattern (i.e.001) */ + } + /* 10: CDRC cfg 28&38: rx&tx data link 1 ctrl 48: t1 frmr cfg */ + /* 50: SIGX cfg, COSS (change of signaling state) 54: XBAS cfg */ + /* 60: t1 ALMI cfg */ + /* Configure Line Coding */ + + switch (port_mode) + { + case CFG_FRAME_SF: /* 1 - T1 B8ZS */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->t1_frmr_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->t1_xbas_cfg, 0x20); /* 5:B8ZS */ + pci_write_32 ((u_int32_t *) &comet->t1_almi_cfg, 0); + break; + case CFG_FRAME_ESF: /* 2 - T1 B8ZS */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->rxce1_ctl, 0x20); /* Bit 5: T1 DataLink + * Enable */ + pci_write_32 ((u_int32_t *) &comet->txci1_ctl, 0x20); /* 5: T1 DataLink Enable */ + pci_write_32 ((u_int32_t *) &comet->t1_frmr_cfg, 0x30); /* 4:ESF 5:ESFFA */ + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0x04); /* 2:ESF */ + pci_write_32 ((u_int32_t *) &comet->t1_xbas_cfg, 0x30); /* 4:ESF 5:B8ZS */ + pci_write_32 ((u_int32_t *) &comet->t1_almi_cfg, 0x10); /* 4:ESF */ + break; + case CFG_FRAME_E1PLAIN: /* 3 - HDB3 */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->e1_tran_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->e1_frmr_aopts, 0x40); + break; + case CFG_FRAME_E1CAS: /* 4 - HDB3 */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->e1_tran_cfg, 0x60); + pci_write_32 ((u_int32_t *) &comet->e1_frmr_aopts, 0); + break; + case CFG_FRAME_E1CRC: /* 5 - HDB3 */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->e1_tran_cfg, 0x10); + pci_write_32 ((u_int32_t *) &comet->e1_frmr_aopts, 0xc2); + break; + case CFG_FRAME_E1CRC_CAS: /* 6 - HDB3 */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->e1_tran_cfg, 0x70); + pci_write_32 ((u_int32_t *) &comet->e1_frmr_aopts, 0x82); + break; + case CFG_FRAME_SF_AMI: /* 7 - T1 AMI */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0x80); /* Enable AMI Line + * Decoding */ + pci_write_32 ((u_int32_t *) &comet->t1_frmr_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->t1_xbas_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->t1_almi_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + break; + case CFG_FRAME_ESF_AMI: /* 8 - T1 AMI */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0x80); /* Enable AMI Line + * Decoding */ + pci_write_32 ((u_int32_t *) &comet->rxce1_ctl, 0x20); /* 5: T1 DataLink Enable */ + pci_write_32 ((u_int32_t *) &comet->txci1_ctl, 0x20); /* 5: T1 DataLink Enable */ + pci_write_32 ((u_int32_t *) &comet->t1_frmr_cfg, 0x30); /* Bit 4:ESF 5:ESFFA */ + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0x04); /* 2:ESF */ + pci_write_32 ((u_int32_t *) &comet->t1_xbas_cfg, 0x10); /* 4:ESF */ + pci_write_32 ((u_int32_t *) &comet->t1_almi_cfg, 0x10); /* 4:ESF */ + break; + case CFG_FRAME_E1PLAIN_AMI: /* 9 - AMI */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0x80); /* Enable AMI Line + * Decoding */ + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->e1_tran_cfg, 0x80); + pci_write_32 ((u_int32_t *) &comet->e1_frmr_aopts, 0x40); + break; + case CFG_FRAME_E1CAS_AMI: /* 10 - AMI */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0x80); /* Enable AMI Line + * Decoding */ + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->e1_tran_cfg, 0xe0); + pci_write_32 ((u_int32_t *) &comet->e1_frmr_aopts, 0); + break; + case CFG_FRAME_E1CRC_AMI: /* 11 - AMI */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0x80); /* Enable AMI Line + * Decoding */ + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->e1_tran_cfg, 0x90); + pci_write_32 ((u_int32_t *) &comet->e1_frmr_aopts, 0xc2); + break; + case CFG_FRAME_E1CRC_CAS_AMI: /* 12 - AMI */ + pci_write_32 ((u_int32_t *) &comet->cdrc_cfg, 0x80); /* Enable AMI Line + * Decoding */ + pci_write_32 ((u_int32_t *) &comet->sigx_cfg, 0); + pci_write_32 ((u_int32_t *) &comet->e1_tran_cfg, 0xf0); + pci_write_32 ((u_int32_t *) &comet->e1_frmr_aopts, 0x82); + break; + } /* end switch */ + + /*** + * Set Full Frame mode (NXDSO[1] = 0, NXDSO[0] = 0) + * CMODE=1: Clock slave mode with BRCLK as an input, + * DE=0: Use falling edge of BRCLK for data, + * FE=0: Use falling edge of BRCLK for frame, + * CMS=0: Use backplane freq, + * RATE[1:0]=0,0: T1 + ***/ + + + /* 0x30: "BRIF cfg"; 0x20 is 'CMODE', 0x03 is (bit) rate */ + /* note "rate bits can only be set once after reset" */ + if (clockmaster) + { /* CMODE == clockMode, 0=clock master (so + * all 3 others should be slave) */ + if (isT1mode) /* rate = 1.544 Mb/s */ + pci_write_32 ((u_int32_t *) &comet->brif_cfg, 0x00); /* Comet 0 Master + * Mode(CMODE=0) */ + else /* rate = 2.048 Mb/s */ + pci_write_32 ((u_int32_t *) &comet->brif_cfg, 0x01); /* Comet 0 Master + * Mode(CMODE=0) */ + + /* 31: BRIF frame pulse cfg 06: tx timing options */ + pci_write_32 ((u_int32_t *) &comet->brif_fpcfg, 0x00); /* Master Mode + * i.e.FPMODE=0 (@0x20) */ + if ((moreParams & CFG_CLK_PORT_MASK) == CFG_CLK_PORT_INTERNAL) + { + if (cxt1e1_log_level >= LOG_SBEBUG12) + pr_info(">> %s: clockmaster internal clock\n", __func__); + pci_write_32 ((u_int32_t *) &comet->tx_time, 0x0d); /* internal oscillator */ + } else /* external clock source */ + { + if (cxt1e1_log_level >= LOG_SBEBUG12) + pr_info(">> %s: clockmaster external clock\n", __func__); + pci_write_32 ((u_int32_t *) &comet->tx_time, 0x09); /* loop timing + * (external) */ + } + + } else /* slave */ + { + if (isT1mode) + pci_write_32 ((u_int32_t *) &comet->brif_cfg, 0x20); /* Slave Mode(CMODE=1, + * see above) */ + else + pci_write_32 ((u_int32_t *) &comet->brif_cfg, 0x21); /* Slave Mode (CMODE=1) */ + pci_write_32 ((u_int32_t *) &comet->brif_fpcfg, 0x20); /* Slave Mode i.e. + * FPMODE=1 (@0x20) */ + if (cxt1e1_log_level >= LOG_SBEBUG12) + pr_info(">> %s: clockslave internal clock\n", __func__); + pci_write_32 ((u_int32_t *) &comet->tx_time, 0x0d); /* oscillator timing */ + } + + /* 32: BRIF parity F-bit cfg */ + /* Totem-pole operation */ + pci_write_32 ((u_int32_t *) &comet->brif_pfcfg, 0x01); /* Receive Backplane + * Parity/F-bit */ + + /* dc: RLPS equalizer V ref */ + /* Configuration */ + if (isT1mode) + pci_write_32 ((u_int32_t *) &comet->rlps_eqvr, 0x2c); /* RLPS Equalizer + * Voltage */ + else + pci_write_32 ((u_int32_t *) &comet->rlps_eqvr, 0x34); /* RLPS Equalizer + * Voltage */ + + /* Reserved bit set and SQUELCH enabled */ + /* f8: RLPS cfg & status f9: RLPS ALOS detect/clear threshold */ + pci_write_32 ((u_int32_t *) &comet->rlps_cfgsts, 0x11); /* RLPS Configuration + * Status */ + if (isT1mode) + pci_write_32 ((u_int32_t *) &comet->rlps_alos_thresh, 0x55); /* ? */ + else + pci_write_32 ((u_int32_t *) &comet->rlps_alos_thresh, 0x22); /* ? */ + + + /* Set Full Frame mode (NXDSO[1] = 0, NXDSO[0] = 0) */ + /* CMODE=0: Clock slave mode with BTCLK as an input, DE=1: Use rising */ + /* edge of BTCLK for data, FE=1: Use rising edge of BTCLK for frame, */ + /* CMS=0: Use backplane freq, RATE[1:0]=0,0: T1 */ +/*** Transmit side is always an Input, Slave Clock*/ + /* 40: BTIF cfg 41: BTIF frame pulse cfg */ + if (isT1mode) + pci_write_32 ((u_int32_t *) &comet->btif_cfg, 0x38); /* BTIF Configuration + * Reg. */ + else + pci_write_32 ((u_int32_t *) &comet->btif_cfg, 0x39); /* BTIF Configuration + * Reg. */ + + pci_write_32 ((u_int32_t *) &comet->btif_fpcfg, 0x01); /* BTIF Frame Pulse + * Config. */ + + /* 0a: master diag 06: tx timing options */ + /* if set Comet to loop back */ + + /* Comets set to normal */ + pci_write_32 ((u_int32_t *) &comet->mdiag, 0x00); + + /* BTCLK driven by TCLKI internally (crystal driven) and Xmt Elasted */ + /* Store is enabled. */ + + WrtXmtWaveformTbl (ci, comet, TWV_table[tix]); + if (isT1mode) + WrtRcvEqualizerTbl ((ci_t *) ci, comet, &T1_Equalizer[0]); + else + WrtRcvEqualizerTbl ((ci_t *) ci, comet, &E1_Equalizer[0]); + SetPwrLevel (comet); +} + +/* +** Name: WrtXmtWaveform +** Description: Formulate the Data for the Pulse Waveform Storage +** Write register, (F2), from the sample and unit inputs. +** Write the data to the Pulse Waveform Storage Data register. +** Returns: Nothing +*/ +STATIC void +WrtXmtWaveform (ci_t * ci, comet_t * comet, u_int32_t sample, u_int32_t unit, u_int8_t data) +{ + u_int8_t WaveformAddr; + + WaveformAddr = (sample << 3) + (unit & 7); + pci_write_32 ((u_int32_t *) &comet->xlpg_pwave_addr, WaveformAddr); + pci_flush_write (ci); /* for write order preservation when + * Optimizing driver */ + pci_write_32 ((u_int32_t *) &comet->xlpg_pwave_data, 0x7F & data); +} + +/* +** Name: WrtXmtWaveformTbl +** Description: Fill in the Transmit Waveform Values +** for driving the transmitter DAC. +** Returns: Nothing +*/ +STATIC void +WrtXmtWaveformTbl (ci_t * ci, comet_t * comet, + u_int8_t table[COMET_NUM_SAMPLES][COMET_NUM_UNITS]) +{ + u_int32_t sample, unit; + + for (sample = 0; sample < COMET_NUM_SAMPLES; sample++) + { + for (unit = 0; unit < COMET_NUM_UNITS; unit++) + WrtXmtWaveform (ci, comet, sample, unit, table[sample][unit]); + } + + /* Enable transmitter and set output amplitude */ + pci_write_32 ((u_int32_t *) &comet->xlpg_cfg, table[COMET_NUM_SAMPLES][0]); +} + + +/* +** Name: WrtXmtWaveform +** Description: Fill in the Receive Equalizer RAM from the desired +** table. +** Returns: Nothing +** +** Remarks: Per PM4351 Device Errata, Receive Equalizer RAM Initialization +** is coded with early setup of indirect address. +*/ + +STATIC void +WrtRcvEqualizerTbl (ci_t * ci, comet_t * comet, u_int32_t *table) +{ + u_int32_t ramaddr; + volatile u_int32_t value; + + for (ramaddr = 0; ramaddr < 256; ramaddr++) + { + /*** the following lines are per Errata 7, 2.5 ***/ + { + pci_write_32 ((u_int32_t *) &comet->rlps_eq_rwsel, 0x80); /* Set up for a read + * operation */ + pci_flush_write (ci); /* for write order preservation when + * Optimizing driver */ + pci_write_32 ((u_int32_t *) &comet->rlps_eq_iaddr, (u_int8_t) ramaddr); /* write the addr, + * initiate a read */ + pci_flush_write (ci); /* for write order preservation when + * Optimizing driver */ + /* + * wait 3 line rate clock cycles to ensure address bits are + * captured by T1/E1 clock + */ + OS_uwait (4, "wret"); /* 683ns * 3 = 1366 ns, approx 2us (but + * use 4us) */ + } + + value = *table++; + pci_write_32 ((u_int32_t *) &comet->rlps_idata3, (u_int8_t) (value >> 24)); + pci_write_32 ((u_int32_t *) &comet->rlps_idata2, (u_int8_t) (value >> 16)); + pci_write_32 ((u_int32_t *) &comet->rlps_idata1, (u_int8_t) (value >> 8)); + pci_write_32 ((u_int32_t *) &comet->rlps_idata0, (u_int8_t) value); + pci_flush_write (ci); /* for write order preservation when + * Optimizing driver */ + + /* Storing RAM address, causes RAM to be updated */ + + pci_write_32 ((u_int32_t *) &comet->rlps_eq_rwsel, 0); /* Set up for a write + * operation */ + pci_flush_write (ci); /* for write order preservation when + * Optimizing driver */ + pci_write_32 ((u_int32_t *) &comet->rlps_eq_iaddr, (u_int8_t) ramaddr); /* write the addr, + * initiate a read */ + pci_flush_write (ci); /* for write order preservation when + * Optimizing driver */ + /* + * wait 3 line rate clock cycles to ensure address bits are captured + * by T1/E1 clock + */ + OS_uwait (4, "wret"); /* 683ns * 3 = 1366 ns, approx 2us (but + * use 4us) */ + } + + pci_write_32 ((u_int32_t *) &comet->rlps_eq_cfg, 0xCB); /* Enable Equalizer & + * set it to use 256 + * periods */ +} + + +/* +** Name: SetPwrLevel +** Description: Implement power level setting algorithm described below +** Returns: Nothing +*/ + +STATIC void +SetPwrLevel (comet_t * comet) +{ + volatile u_int32_t temp; + +/* +** Algorithm to Balance the Power Distribution of Ttip Tring +** +** Zero register F6 +** Write 0x01 to register F4 +** Write another 0x01 to register F4 +** Read register F4 +** Remove the 0x01 bit by Anding register F4 with 0xFE +** Write the resultant value to register F4 +** Repeat these steps for register F5 +** Write 0x01 to register F6 +*/ + pci_write_32 ((u_int32_t *) &comet->xlpg_fdata_sel, 0x00); /* XLPG Fuse Data Select */ + + pci_write_32 ((u_int32_t *) &comet->xlpg_atest_pctl, 0x01); /* XLPG Analog Test + * Positive control */ + pci_write_32 ((u_int32_t *) &comet->xlpg_atest_pctl, 0x01); + + temp = pci_read_32 ((u_int32_t *) &comet->xlpg_atest_pctl) & 0xfe; + pci_write_32 ((u_int32_t *) &comet->xlpg_atest_pctl, temp); + + pci_write_32 ((u_int32_t *) &comet->xlpg_atest_nctl, 0x01); /* XLPG Analog Test + * Negative control */ + pci_write_32 ((u_int32_t *) &comet->xlpg_atest_nctl, 0x01); + + temp = pci_read_32 ((u_int32_t *) &comet->xlpg_atest_nctl) & 0xfe; + pci_write_32 ((u_int32_t *) &comet->xlpg_atest_nctl, temp); + pci_write_32 ((u_int32_t *) &comet->xlpg_fdata_sel, 0x01); /* XLPG */ +} + + +/* +** Name: SetCometOps +** Description: Set up the selected Comet's clock edge drive for both +** the transmit out the analog side and receive to the +** backplane side. +** Returns: Nothing +*/ +#if 0 +STATIC void +SetCometOps (comet_t * comet) +{ + volatile u_int8_t rd_value; + + if (comet == mConfig.C4Func1Base + (COMET0_OFFSET >> 2)) + { + rd_value = (u_int8_t) pci_read_32 ((u_int32_t *) &comet->brif_cfg); /* read the BRIF + * Configuration */ + rd_value &= ~0x20; + pci_write_32 ((u_int32_t *) &comet->brif_cfg, (u_int32_t) rd_value); + + rd_value = (u_int8_t) pci_read_32 ((u_int32_t *) &comet->brif_fpcfg); /* read the BRIF Frame + * Pulse Configuration */ + rd_value &= ~0x20; + pci_write_32 ((u_int32_t *) &comet->brif_fpcfg, (u_int8_t) rd_value); + } else + { + rd_value = (u_int8_t) pci_read_32 ((u_int32_t *) &comet->brif_cfg); /* read the BRIF + * Configuration */ + rd_value |= 0x20; + pci_write_32 ((u_int32_t *) &comet->brif_cfg, (u_int32_t) rd_value); + + rd_value = (u_int8_t) pci_read_32 ((u_int32_t *) &comet->brif_fpcfg); /* read the BRIF Frame + * Pulse Configuration */ + rd_value |= 0x20; + pci_write_32 ((u_int32_t *) &comet->brif_fpcfg, (u_int8_t) rd_value); + } +} +#endif + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/comet.h b/drivers/staging/cxt1e1/comet.h new file mode 100644 index 00000000..e06da4a6 --- /dev/null +++ b/drivers/staging/cxt1e1/comet.h @@ -0,0 +1,344 @@ +#ifndef _INC_COMET_H_ +#define _INC_COMET_H_ + +/*----------------------------------------------------------------------------- + * comet.h - + * + * Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + +#include + +#define VINT32 volatile u_int32_t + +struct s_comet_reg +{ + VINT32 gbl_cfg; /* 00 Global Cfg */ + VINT32 clkmon; /* 01 Clk Monitor */ + VINT32 rx_opt; /* 02 RX Options */ + VINT32 rx_line_cfg; /* 03 RX Line Interface Cfg */ + VINT32 tx_line_cfg; /* 04 TX Line Interface Cfg */ + VINT32 tx_frpass; /* 05 TX Framing & Bypass Options */ + VINT32 tx_time; /* 06 TX Timing Options */ + VINT32 intr_1; /* 07 Intr Source #1 */ + VINT32 intr_2; /* 08 Intr Source #2 */ + VINT32 intr_3; /* 09 Intr Source #3 */ + VINT32 mdiag; /* 0A Master Diagnostics */ + VINT32 mtest; /* 0B Master Test */ + VINT32 adiag; /* 0C Analog Diagnostics */ + VINT32 rev_id; /* 0D Rev/Chip Id/Global PMON Update */ +#define pmon rev_id + VINT32 reset; /* 0E Reset */ + VINT32 prgd_phctl; /* 0F PRGD Positioning/Ctl & HDLC Ctl */ + VINT32 cdrc_cfg; /* 10 CDRC Cfg */ + VINT32 cdrc_ien; /* 11 CDRC Intr Enable */ + VINT32 cdrc_ists; /* 12 CDRC Intr Sts */ + VINT32 cdrc_alos; /* 13 CDRC Alternate Loss of Signal */ + + VINT32 rjat_ists; /* 14 RJAT Intr Sts */ + VINT32 rjat_n1clk; /* 15 RJAT Reference Clk Divisor (N1) Ctl */ + VINT32 rjat_n2clk; /* 16 RJAT Output Clk Divisor (N2) Ctl */ + VINT32 rjat_cfg; /* 17 RJAT Cfg */ + + VINT32 tjat_ists; /* 18 TJAT Intr Sts */ + VINT32 tjat_n1clk; /* 19 TJAT Reference Clk Divisor (N1) Ctl */ + VINT32 tjat_n2clk; /* 1A TJAT Output Clk Divisor (N2) Ctl */ + VINT32 tjat_cfg; /* 1B TJAT Cfg */ + + VINT32 rx_elst_cfg; /* 1C RX-ELST Cfg */ + VINT32 rx_elst_ists; /* 1D RX-ELST Intr Sts */ + VINT32 rx_elst_idle; /* 1E RX-ELST Idle Code */ + VINT32 _rx_elst_res1f; /* 1F RX-ELST Reserved */ + + VINT32 tx_elst_cfg; /* 20 TX-ELST Cfg */ + VINT32 tx_elst_ists; /* 21 TX-ELST Intr Sts */ + VINT32 _tx_elst_res22; /* 22 TX-ELST Reserved */ + VINT32 _tx_elst_res23; /* 23 TX-ELST Reserved */ + VINT32 __res24; /* 24 Reserved */ + VINT32 __res25; /* 25 Reserved */ + VINT32 __res26; /* 26 Reserved */ + VINT32 __res27; /* 27 Reserved */ + + VINT32 rxce1_ctl; /* 28 RXCE RX Data Link 1 Ctl */ + VINT32 rxce1_bits; /* 29 RXCE RX Data Link 1 Bit Select */ + VINT32 rxce2_ctl; /* 2A RXCE RX Data Link 2 Ctl */ + VINT32 rxce2_bits; /* 2B RXCE RX Data Link 2 Bit Select */ + VINT32 rxce3_ctl; /* 2C RXCE RX Data Link 3 Ctl */ + VINT32 rxce3_bits; /* 2D RXCE RX Data Link 3 Bit Select */ + VINT32 _rxce_res2E; /* 2E RXCE Reserved */ + VINT32 _rxce_res2F; /* 2F RXCE Reserved */ + + VINT32 brif_cfg; /* 30 BRIF RX Backplane Cfg */ + VINT32 brif_fpcfg; /* 31 BRIF RX Backplane Frame Pulse Cfg */ + VINT32 brif_pfcfg; /* 32 BRIF RX Backplane Parity/F-Bit Cfg */ + VINT32 brif_tsoff; /* 33 BRIF RX Backplane Time Slot Offset */ + VINT32 brif_boff; /* 34 BRIF RX Backplane Bit Offset */ + VINT32 _brif_res35; /* 35 BRIF RX Backplane Reserved */ + VINT32 _brif_res36; /* 36 BRIF RX Backplane Reserved */ + VINT32 _brif_res37; /* 37 BRIF RX Backplane Reserved */ + + VINT32 txci1_ctl; /* 38 TXCI TX Data Link 1 Ctl */ + VINT32 txci1_bits; /* 39 TXCI TX Data Link 2 Bit Select */ + VINT32 txci2_ctl; /* 3A TXCI TX Data Link 1 Ctl */ + VINT32 txci2_bits; /* 3B TXCI TX Data Link 2 Bit Select */ + VINT32 txci3_ctl; /* 3C TXCI TX Data Link 1 Ctl */ + VINT32 txci3_bits; /* 3D TXCI TX Data Link 2 Bit Select */ + VINT32 _txci_res3E; /* 3E TXCI Reserved */ + VINT32 _txci_res3F; /* 3F TXCI Reserved */ + + VINT32 btif_cfg; /* 40 BTIF TX Backplane Cfg */ + VINT32 btif_fpcfg; /* 41 BTIF TX Backplane Frame Pulse Cfg */ + VINT32 btif_pcfgsts; /* 42 BTIF TX Backplane Parity Cfg & Sts */ + VINT32 btif_tsoff; /* 43 BTIF TX Backplane Time Slot Offset */ + VINT32 btif_boff; /* 44 BTIF TX Backplane Bit Offset */ + VINT32 _btif_res45; /* 45 BTIF TX Backplane Reserved */ + VINT32 _btif_res46; /* 46 BTIF TX Backplane Reserved */ + VINT32 _btif_res47; /* 47 BTIF TX Backplane Reserved */ + VINT32 t1_frmr_cfg; /* 48 T1 FRMR Cfg */ + VINT32 t1_frmr_ien; /* 49 T1 FRMR Intr Enable */ + VINT32 t1_frmr_ists; /* 4A T1 FRMR Intr Sts */ + VINT32 __res_4B; /* 4B Reserved */ + VINT32 ibcd_cfg; /* 4C IBCD Cfg */ + VINT32 ibcd_ies; /* 4D IBCD Intr Enable/Sts */ + VINT32 ibcd_act; /* 4E IBCD Activate Code */ + VINT32 ibcd_deact; /* 4F IBCD Deactivate Code */ + + VINT32 sigx_cfg; /* 50 SIGX Cfg/Change of Signaling State */ + VINT32 sigx_acc_cos; /* 51 SIGX uP Access Sts/Change of Signaling State */ + VINT32 sigx_iac_cos; /* 52 SIGX Channel Indirect + * Addr/Ctl/Change of Signaling State */ + VINT32 sigx_idb_cos; /* 53 SIGX Channel Indirect Data + * Buffer/Change of Signaling State */ + + VINT32 t1_xbas_cfg; /* 54 T1 XBAS Cfg */ + VINT32 t1_xbas_altx; /* 55 T1 XBAS Alarm TX */ + VINT32 t1_xibc_ctl; /* 56 T1 XIBC Ctl */ + VINT32 t1_xibc_lbcode; /* 57 T1 XIBC Loopback Code */ + + VINT32 pmon_ies; /* 58 PMON Intr Enable/Sts */ + VINT32 pmon_fberr; /* 59 PMON Framing Bit Err Cnt */ + VINT32 pmon_feb_lsb; /* 5A PMON OFF/COFA/Far End Block Err Cnt (LSB) */ + VINT32 pmon_feb_msb; /* 5B PMON OFF/COFA/Far End Block Err Cnt (MSB) */ + VINT32 pmon_bed_lsb; /* 5C PMON Bit/Err/CRCE Cnt (LSB) */ + VINT32 pmon_bed_msb; /* 5D PMON Bit/Err/CRCE Cnt (MSB) */ + VINT32 pmon_lvc_lsb; /* 5E PMON LVC Cnt (LSB) */ + VINT32 pmon_lvc_msb; /* 5F PMON LVC Cnt (MSB) */ + + VINT32 t1_almi_cfg; /* 60 T1 ALMI Cfg */ + VINT32 t1_almi_ien; /* 61 T1 ALMI Intr Enable */ + VINT32 t1_almi_ists; /* 62 T1 ALMI Intr Sts */ + VINT32 t1_almi_detsts; /* 63 T1 ALMI Alarm Detection Sts */ + + VINT32 _t1_pdvd_res64; /* 64 T1 PDVD Reserved */ + VINT32 t1_pdvd_ies; /* 65 T1 PDVD Intr Enable/Sts */ + VINT32 _t1_xboc_res66; /* 66 T1 XBOC Reserved */ + VINT32 t1_xboc_code; /* 67 T1 XBOC Code */ + VINT32 _t1_xpde_res68; /* 68 T1 XPDE Reserved */ + VINT32 t1_xpde_ies; /* 69 T1 XPDE Intr Enable/Sts */ + + VINT32 t1_rboc_ena; /* 6A T1 RBOC Enable */ + VINT32 t1_rboc_sts; /* 6B T1 RBOC Code Sts */ + + VINT32 t1_tpsc_cfg; /* 6C TPSC Cfg */ + VINT32 t1_tpsc_sts; /* 6D TPSC uP Access Sts */ + VINT32 t1_tpsc_ciaddr; /* 6E TPSC Channel Indirect + * Addr/Ctl */ + VINT32 t1_tpsc_cidata; /* 6F TPSC Channel Indirect Data + * Buffer */ + VINT32 t1_rpsc_cfg; /* 70 RPSC Cfg */ + VINT32 t1_rpsc_sts; /* 71 RPSC uP Access Sts */ + VINT32 t1_rpsc_ciaddr; /* 72 RPSC Channel Indirect + * Addr/Ctl */ + VINT32 t1_rpsc_cidata; /* 73 RPSC Channel Indirect Data + * Buffer */ + VINT32 __res74; /* 74 Reserved */ + VINT32 __res75; /* 75 Reserved */ + VINT32 __res76; /* 76 Reserved */ + VINT32 __res77; /* 77 Reserved */ + + VINT32 t1_aprm_cfg; /* 78 T1 APRM Cfg/Ctl */ + VINT32 t1_aprm_load; /* 79 T1 APRM Manual Load */ + VINT32 t1_aprm_ists; /* 7A T1 APRM Intr Sts */ + VINT32 t1_aprm_1sec_2; /* 7B T1 APRM One Second Content Octet 2 */ + VINT32 t1_aprm_1sec_3; /* 7C T1 APRM One Second Content Octet 3 */ + VINT32 t1_aprm_1sec_4; /* 7D T1 APRM One Second Content Octet 4 */ + VINT32 t1_aprm_1sec_5; /* 7E T1 APRM One Second Content MSB (Octect 5) */ + VINT32 t1_aprm_1sec_6; /* 7F T1 APRM One Second Content MSB (Octect 6) */ + + VINT32 e1_tran_cfg; /* 80 E1 TRAN Cfg */ + VINT32 e1_tran_txalarm; /* 81 E1 TRAN TX Alarm/Diagnostic Ctl */ + VINT32 e1_tran_intctl; /* 82 E1 TRAN International Ctl */ + VINT32 e1_tran_extrab; /* 83 E1 TRAN Extra Bits Ctl */ + VINT32 e1_tran_ien; /* 84 E1 TRAN Intr Enable */ + VINT32 e1_tran_ists; /* 85 E1 TRAN Intr Sts */ + VINT32 e1_tran_nats; /* 86 E1 TRAN National Bit Codeword + * Select */ + VINT32 e1_tran_nat; /* 87 E1 TRAN National Bit Codeword */ + VINT32 __res88; /* 88 Reserved */ + VINT32 __res89; /* 89 Reserved */ + VINT32 __res8A; /* 8A Reserved */ + VINT32 __res8B; /* 8B Reserved */ + + VINT32 _t1_frmr_res8C; /* 8C T1 FRMR Reserved */ + VINT32 _t1_frmr_res8D; /* 8D T1 FRMR Reserved */ + VINT32 __res8E; /* 8E Reserved */ + VINT32 __res8F; /* 8F Reserved */ + + VINT32 e1_frmr_aopts; /* 90 E1 FRMR Frame Alignment Options */ + VINT32 e1_frmr_mopts; /* 91 E1 FRMR Maintenance Mode Options */ + VINT32 e1_frmr_ien; /* 92 E1 FRMR Framing Sts Intr Enable */ + VINT32 e1_frmr_mien; /* 93 E1 FRMR Maintenance/Alarm Sts Intr Enable */ + VINT32 e1_frmr_ists; /* 94 E1 FRMR Framing Sts Intr Indication */ + VINT32 e1_frmr_mists; /* 95 E1 FRMR Maintenance/Alarm Sts Indication Enable */ + VINT32 e1_frmr_sts; /* 96 E1 FRMR Framing Sts */ + VINT32 e1_frmr_masts; /* 97 E1 FRMR Maintenance/Alarm Sts */ + VINT32 e1_frmr_nat_bits; /* 98 E1 FRMR International/National Bits */ + VINT32 e1_frmr_crc_lsb; /* 99 E1 FRMR CRC Err Cnt - LSB */ + VINT32 e1_frmr_crc_msb; /* 9A E1 FRMR CRC Err Cnt - MSB */ + VINT32 e1_frmr_nat_ien; /* 9B E1 FRMR National Bit Codeword Intr Enables */ + VINT32 e1_frmr_nat_ists; /* 9C E1 FRMR National Bit Codeword Intr/Sts */ + VINT32 e1_frmr_nat; /* 9D E1 FRMR National Bit Codewords */ + VINT32 e1_frmr_fp_ien; /* 9E E1 FRMR Frame Pulse/Alarm Intr Enables */ + VINT32 e1_frmr_fp_ists; /* 9F E1 FRMR Frame Pulse/Alarm Intr/Sts */ + + VINT32 __resA0; /* A0 Reserved */ + VINT32 __resA1; /* A1 Reserved */ + VINT32 __resA2; /* A2 Reserved */ + VINT32 __resA3; /* A3 Reserved */ + VINT32 __resA4; /* A4 Reserved */ + VINT32 __resA5; /* A5 Reserved */ + VINT32 __resA6; /* A6 Reserved */ + VINT32 __resA7; /* A7 Reserved */ + + VINT32 tdpr1_cfg; /* A8 TDPR #1 Cfg */ + VINT32 tdpr1_utl; /* A9 TDPR #1 Upper TX Threshold */ + VINT32 tdpr1_ltl; /* AA TDPR #1 Lower TX Threshold */ + VINT32 tdpr1_ien; /* AB TDPR #1 Intr Enable */ + VINT32 tdpr1_ists; /* AC TDPR #1 Intr Sts/UDR Clear */ + VINT32 tdpr1_data; /* AD TDPR #1 TX Data */ + VINT32 __resAE; /* AE Reserved */ + VINT32 __resAF; /* AF Reserved */ + VINT32 tdpr2_cfg; /* B0 TDPR #2 Cfg */ + VINT32 tdpr2_utl; /* B1 TDPR #2 Upper TX Threshold */ + VINT32 tdpr2_ltl; /* B2 TDPR #2 Lower TX Threshold */ + VINT32 tdpr2_ien; /* B3 TDPR #2 Intr Enable */ + VINT32 tdpr2_ists; /* B4 TDPR #2 Intr Sts/UDR Clear */ + VINT32 tdpr2_data; /* B5 TDPR #2 TX Data */ + VINT32 __resB6; /* B6 Reserved */ + VINT32 __resB7; /* B7 Reserved1 */ + VINT32 tdpr3_cfg; /* B8 TDPR #3 Cfg */ + VINT32 tdpr3_utl; /* B9 TDPR #3 Upper TX Threshold */ + VINT32 tdpr3_ltl; /* BA TDPR #3 Lower TX Threshold */ + VINT32 tdpr3_ien; /* BB TDPR #3 Intr Enable */ + VINT32 tdpr3_ists; /* BC TDPR #3 Intr Sts/UDR Clear */ + VINT32 tdpr3_data; /* BD TDPR #3 TX Data */ + VINT32 __resBE; /* BE Reserved */ + VINT32 __resBF; /* BF Reserved */ + + VINT32 rdlc1_cfg; /* C0 RDLC #1 Cfg */ + VINT32 rdlc1_intctl; /* C1 RDLC #1 Intr Ctl */ + VINT32 rdlc1_sts; /* C2 RDLC #1 Sts */ + VINT32 rdlc1_data; /* C3 RDLC #1 Data */ + VINT32 rdlc1_paddr; /* C4 RDLC #1 Primary Addr Match */ + VINT32 rdlc1_saddr; /* C5 RDLC #1 Secondary Addr Match */ + VINT32 __resC6; /* C6 Reserved */ + VINT32 __resC7; /* C7 Reserved */ + VINT32 rdlc2_cfg; /* C8 RDLC #2 Cfg */ + VINT32 rdlc2_intctl; /* C9 RDLC #2 Intr Ctl */ + VINT32 rdlc2_sts; /* CA RDLC #2 Sts */ + VINT32 rdlc2_data; /* CB RDLC #2 Data */ + VINT32 rdlc2_paddr; /* CC RDLC #2 Primary Addr Match */ + VINT32 rdlc2_saddr; /* CD RDLC #2 Secondary Addr Match */ + VINT32 __resCE; /* CE Reserved */ + VINT32 __resCF; /* CF Reserved */ + VINT32 rdlc3_cfg; /* D0 RDLC #3 Cfg */ + VINT32 rdlc3_intctl; /* D1 RDLC #3 Intr Ctl */ + VINT32 rdlc3_sts; /* D2 RDLC #3 Sts */ + VINT32 rdlc3_data; /* D3 RDLC #3 Data */ + VINT32 rdlc3_paddr; /* D4 RDLC #3 Primary Addr Match */ + VINT32 rdlc3_saddr; /* D5 RDLC #3 Secondary Addr Match */ + + VINT32 csu_cfg; /* D6 CSU Cfg */ + VINT32 _csu_resD7; /* D7 CSU Reserved */ + + VINT32 rlps_idata3; /* D8 RLPS Indirect Data, 24-31 */ + VINT32 rlps_idata2; /* D9 RLPS Indirect Data, 16-23 */ + VINT32 rlps_idata1; /* DA RLPS Indirect Data, 8-15 */ + VINT32 rlps_idata0; /* DB RLPS Indirect Data, 0-7 */ + VINT32 rlps_eqvr; /* DC RLPS Equalizer Voltage Reference + * (E1 missing) */ + VINT32 _rlps_resDD; /* DD RLPS Reserved */ + VINT32 _rlps_resDE; /* DE RLPS Reserved */ + VINT32 _rlps_resDF; /* DF RLPS Reserved */ + + VINT32 prgd_ctl; /* E0 PRGD Ctl */ + VINT32 prgd_ies; /* E1 PRGD Intr Enable/Sts */ + VINT32 prgd_shift_len; /* E2 PRGD Shift Length */ + VINT32 prgd_tap; /* E3 PRGD Tap */ + VINT32 prgd_errin; /* E4 PRGD Err Insertion */ + VINT32 _prgd_resE5; /* E5 PRGD Reserved */ + VINT32 _prgd_resE6; /* E6 PRGD Reserved */ + VINT32 _prgd_resE7; /* E7 PRGD Reserved */ + VINT32 prgd_patin1; /* E8 PRGD Pattern Insertion #1 */ + VINT32 prgd_patin2; /* E9 PRGD Pattern Insertion #2 */ + VINT32 prgd_patin3; /* EA PRGD Pattern Insertion #3 */ + VINT32 prgd_patin4; /* EB PRGD Pattern Insertion #4 */ + VINT32 prgd_patdet1; /* EC PRGD Pattern Detector #1 */ + VINT32 prgd_patdet2; /* ED PRGD Pattern Detector #2 */ + VINT32 prgd_patdet3; /* EE PRGD Pattern Detector #3 */ + VINT32 prgd_patdet4; /* EF PRGD Pattern Detector #4 */ + + VINT32 xlpg_cfg; /* F0 XLPG Line Driver Cfg */ + VINT32 xlpg_ctlsts; /* F1 XLPG Ctl/Sts */ + VINT32 xlpg_pwave_addr; /* F2 XLPG Pulse Waveform Storage Write Addr */ + VINT32 xlpg_pwave_data; /* F3 XLPG Pulse Waveform Storage Data */ + VINT32 xlpg_atest_pctl; /* F4 XLPG Analog Test Positive Ctl */ + VINT32 xlpg_atest_nctl; /* F5 XLPG Analog Test Negative Ctl */ + VINT32 xlpg_fdata_sel; /* F6 XLPG Fuse Data Select */ + VINT32 _xlpg_resF7; /* F7 XLPG Reserved */ + + VINT32 rlps_cfgsts; /* F8 RLPS Cfg & Sts */ + VINT32 rlps_alos_thresh; /* F9 RLPS ALOS Detection/Clearance Threshold */ + VINT32 rlps_alos_dper; /* FA RLPS ALOS Detection Period */ + VINT32 rlps_alos_cper; /* FB RLPS ALOS Clearance Period */ + VINT32 rlps_eq_iaddr; /* FC RLPS Equalization Indirect Addr */ + VINT32 rlps_eq_rwsel; /* FD RLPS Equalization Read/WriteB Select */ + VINT32 rlps_eq_ctlsts; /* FE RLPS Equalizer Loop Sts & Ctl */ + VINT32 rlps_eq_cfg; /* FF RLPS Equalizer Cfg */ +}; + +typedef struct s_comet_reg comet_t; + +/* 00AH: MDIAG Register bit definitions */ +#define COMET_MDIAG_ID5 0x40 +#define COMET_MDIAG_LBMASK 0x3F +#define COMET_MDIAG_PAYLB 0x20 +#define COMET_MDIAG_LINELB 0x10 +#define COMET_MDIAG_RAIS 0x08 +#define COMET_MDIAG_DDLB 0x04 +#define COMET_MDIAG_TXMFP 0x02 +#define COMET_MDIAG_TXLOS 0x01 +#define COMET_MDIAG_LBOFF 0x00 + +#undef VINT32 + +#ifdef __KERNEL__ +extern void +init_comet (void *, comet_t *, u_int32_t, int, u_int8_t); +#endif + +#endif /* _INC_COMET_H_ */ diff --git a/drivers/staging/cxt1e1/comet_tables.c b/drivers/staging/cxt1e1/comet_tables.c new file mode 100644 index 00000000..84931117 --- /dev/null +++ b/drivers/staging/cxt1e1/comet_tables.c @@ -0,0 +1,537 @@ +/*----------------------------------------------------------------------------- + * comet_tables.c - waveform tables for the PM4351 'COMET' + * + * Copyright (C) 2003-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + +#include + +/***************************************************************************** +* +* Array names: +* +* TWVLongHaul0DB +* TWVLongHaul7_5DB +* TWVLongHaul15DB +* TWVLongHaul22_5DB +* TWVShortHaul0 +* TWVShortHaul1 +* TWVShortHaul2 +* TWVShortHaul3 +* TWVShortHaul4 +* TWVShortHaul5 +* TWV_E1_120Ohm +* TWV_E1_75Ohm +* T1_Equalizer +* E1_Equalizer +* +*****************************************************************************/ + +u_int8_t TWVLongHaul0DB[25][5] =/* T1 Long Haul 0 DB */ +{ + {0x00, 0x44, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x0A, 0x44, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x20, 0x43, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x32, 0x43, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x3E, 0x42, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x3D, 0x42, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x3C, 0x41, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x3B, 0x41, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x39, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x39, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x38, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x37, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x36, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x34, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x29, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x4F, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x4C, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x49, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x46, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x46, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ + {0x0C} /* PMC's suggested value */ +/* {0x14} Output Amplitude */ +}; + +u_int8_t TWVLongHaul7_5DB[25][5] = /* T1 Long Haul 7.5 DB */ +{ + {0x00, 0x10, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x01, 0x0E, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x02, 0x0C, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x04, 0x0A, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x08, 0x08, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x0C, 0x06, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x10, 0x04, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x16, 0x02, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x1A, 0x01, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x1E, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x22, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x26, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x2A, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x2B, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x2C, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x2D, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x2C, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x28, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x24, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x20, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x1C, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x18, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x14, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x12, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ + {0x07} /* PMC's suggested value */ +/* { 0x0A } Output Amplitude */ +}; + +u_int8_t TWVLongHaul15DB[25][5] = /* T1 Long Haul 15 DB */ +{ + {0x00, 0x2A, 0x09, 0x01, 0x00}, /* Sample 0 */ + {0x00, 0x28, 0x08, 0x01, 0x00}, /* Sample 1 */ + {0x00, 0x26, 0x08, 0x01, 0x00}, /* Sample 2 */ + {0x00, 0x24, 0x07, 0x01, 0x00}, /* Sample 3 */ + {0x01, 0x22, 0x07, 0x01, 0x00}, /* Sample 4 */ + {0x02, 0x20, 0x06, 0x01, 0x00}, /* Sample 5 */ + {0x04, 0x1E, 0x06, 0x01, 0x00}, /* Sample 6 */ + {0x07, 0x1C, 0x05, 0x00, 0x00}, /* Sample 7 */ + {0x0A, 0x1B, 0x05, 0x00, 0x00}, /* Sample 8 */ + {0x0D, 0x19, 0x05, 0x00, 0x00}, /* Sample 9 */ + {0x10, 0x18, 0x04, 0x00, 0x00}, /* Sample 10 */ + {0x14, 0x16, 0x04, 0x00, 0x00}, /* Sample 11 */ + {0x18, 0x15, 0x04, 0x00, 0x00}, /* Sample 12 */ + {0x1B, 0x13, 0x03, 0x00, 0x00}, /* Sample 13 */ + {0x1E, 0x12, 0x03, 0x00, 0x00}, /* Sample 14 */ + {0x21, 0x10, 0x03, 0x00, 0x00}, /* Sample 15 */ + {0x24, 0x0F, 0x03, 0x00, 0x00}, /* Sample 16 */ + {0x27, 0x0D, 0x03, 0x00, 0x00}, /* Sample 17 */ + {0x2A, 0x0D, 0x02, 0x00, 0x00}, /* Sample 18 */ + {0x2D, 0x0B, 0x02, 0x00, 0x00}, /* Sample 19 */ + {0x30, 0x0B, 0x02, 0x00, 0x00}, /* Sample 20 */ + {0x30, 0x0A, 0x02, 0x00, 0x00}, /* Sample 21 */ + {0x2E, 0x0A, 0x02, 0x00, 0x00}, /* Sample 22 */ + {0x2C, 0x09, 0x02, 0x00, 0x00}, /* Sample 23 */ + {0x03} /* Output Amplitude */ +}; + +u_int8_t TWVLongHaul22_5DB[25][5] = /* T1 Long Haul 22.5 DB */ +{ + {0x00, 0x1F, 0x16, 0x06, 0x01}, /* Sample 0 */ + {0x00, 0x20, 0x15, 0x05, 0x01}, /* Sample 1 */ + {0x00, 0x21, 0x15, 0x05, 0x01}, /* Sample 2 */ + {0x00, 0x22, 0x14, 0x05, 0x01}, /* Sample 3 */ + {0x00, 0x22, 0x13, 0x04, 0x00}, /* Sample 4 */ + {0x00, 0x23, 0x12, 0x04, 0x00}, /* Sample 5 */ + {0x01, 0x23, 0x12, 0x04, 0x00}, /* Sample 6 */ + {0x01, 0x24, 0x11, 0x03, 0x00}, /* Sample 7 */ + {0x01, 0x23, 0x10, 0x03, 0x00}, /* Sample 8 */ + {0x02, 0x23, 0x10, 0x03, 0x00}, /* Sample 9 */ + {0x03, 0x22, 0x0F, 0x03, 0x00}, /* Sample 10 */ + {0x05, 0x22, 0x0E, 0x03, 0x00}, /* Sample 11 */ + {0x07, 0x21, 0x0E, 0x02, 0x00}, /* Sample 12 */ + {0x09, 0x20, 0x0D, 0x02, 0x00}, /* Sample 13 */ + {0x0B, 0x1E, 0x0C, 0x02, 0x00}, /* Sample 14 */ + {0x0E, 0x1D, 0x0C, 0x02, 0x00}, /* Sample 15 */ + {0x10, 0x1B, 0x0B, 0x02, 0x00}, /* Sample 16 */ + {0x13, 0x1B, 0x0A, 0x02, 0x00}, /* Sample 17 */ + {0x15, 0x1A, 0x0A, 0x02, 0x00}, /* Sample 18 */ + {0x17, 0x19, 0x09, 0x01, 0x00}, /* Sample 19 */ + {0x19, 0x19, 0x08, 0x01, 0x00}, /* Sample 20 */ + {0x1B, 0x18, 0x08, 0x01, 0x00}, /* Sample 21 */ + {0x1D, 0x17, 0x07, 0x01, 0x00}, /* Sample 22 */ + {0x1E, 0x17, 0x06, 0x01, 0x00}, /* Sample 23 */ + {0x02} /* Output Amplitude */ +}; + +u_int8_t TWVShortHaul0[25][5] = /* T1 Short Haul 0 - 110 ft */ +{ + {0x00, 0x45, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x0A, 0x44, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x20, 0x43, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x3F, 0x42, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x3F, 0x42, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x3C, 0x41, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x3B, 0x41, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x39, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x39, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x38, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x37, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x36, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x34, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x29, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x59, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x55, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x50, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x4D, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x48, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x46, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x46, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ + {0x0C} /* Output Amplitude */ +}; + +u_int8_t TWVShortHaul1[25][5] = /* T1 Short Haul 110 - 220 ft */ +{ + {0x00, 0x44, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x0A, 0x44, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x36, 0x42, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x34, 0x42, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x30, 0x41, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x2F, 0x41, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x2E, 0x00, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x2D, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x2C, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x2B, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x2A, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x28, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x26, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x68, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x54, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x4F, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x49, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x46, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ + {0x10} /* Output Amplitude */ +}; + +u_int8_t TWVShortHaul2[25][5] = /* T1 Short Haul 220 - 330 ft */ +{ + {0x00, 0x44, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x0A, 0x44, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x3A, 0x43, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x3A, 0x42, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x38, 0x42, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x30, 0x41, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x2F, 0x41, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x2E, 0x00, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x2D, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x2C, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x2B, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x2A, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x29, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x23, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x6C, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x60, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x4F, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x49, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x46, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ + {0x11} /* Output Amplitude */ +}; + +u_int8_t TWVShortHaul3[25][5] = /* T1 Short Haul 330 - 440 ft */ +{ + {0x00, 0x44, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x0A, 0x44, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x3F, 0x42, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x3F, 0x42, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x2F, 0x41, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x2E, 0x41, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x2D, 0x00, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x2C, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x2B, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x2A, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x29, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x28, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x19, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x7F, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x60, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x4F, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x49, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x46, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ + {0x12} /* Output Amplitude */ +}; + +u_int8_t TWVShortHaul4[25][5] = /* T1 Short Haul 440 - 550 ft */ +{ + {0x00, 0x44, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x0A, 0x44, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x3F, 0x42, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x3F, 0x42, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x30, 0x41, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x2B, 0x41, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x2A, 0x00, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x29, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x28, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x27, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x26, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x26, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x24, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x7F, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x7F, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x4F, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x49, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x46, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ + {0x14} /* Output Amplitude */ +}; + +u_int8_t TWVShortHaul5[25][5] = /* T1 Short Haul 550 - 660 ft */ +{ + {0x00, 0x44, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x0A, 0x44, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x3F, 0x43, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x3F, 0x42, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x3F, 0x42, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x3F, 0x41, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x30, 0x41, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x2A, 0x00, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x29, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x28, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x27, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x26, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x25, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x24, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x4A, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x7F, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x7F, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x5F, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x50, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x49, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x47, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x46, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ + {0x15} /* Output Amplitude */ +}; + +u_int8_t TWV_E1_120Ohm[25][5] = /* E1 120 Ohm */ +{ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x0A, 0x00, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x3F, 0x00, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x3F, 0x00, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x39, 0x00, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x38, 0x00, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x36, 0x00, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x36, 0x00, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x35, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x35, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x35, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x35, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x35, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x35, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x2D, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ + {0x0C} /* PMC's suggested value */ +/* { 0x10 } Output Amplitude */ +}; + + + +u_int8_t TWV_E1_75Ohm[25][5] = /* E1 75 Ohm */ +{ +#ifdef PMCC4_DOES_NOT_SUPPORT + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 0 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 1 */ + {0x0A, 0x00, 0x00, 0x00, 0x00}, /* Sample 2 */ + {0x28, 0x00, 0x00, 0x00, 0x00}, /* Sample 3 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 4 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 5 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 6 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 7 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 8 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 9 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 10 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 11 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 12 */ + {0x3A, 0x00, 0x00, 0x00, 0x00}, /* Sample 13 */ + {0x32, 0x00, 0x00, 0x00, 0x00}, /* Sample 14 */ + {0x14, 0x00, 0x00, 0x00, 0x00}, /* Sample 15 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 16 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 17 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 18 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 19 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 20 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 21 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 22 */ + {0x00, 0x00, 0x00, 0x00, 0x00}, /* Sample 23 */ +#endif + {0x0C} /* Output Amplitude */ +}; + + +u_int32_t T1_Equalizer[256] = /* T1 Receiver Equalizer */ +{ + 0x03FE1840, 0x03F61840, 0x03EE1840, 0x03E61840, /* 000 - 003 */ + 0x03DE1840, 0x03D61840, 0x03D61840, 0x03D61840, /* 004 - 007 */ + 0x03CE1840, 0x03CE1840, 0x03CE1840, 0x03CE1840, /* 008 - 011 */ + 0x03C61840, 0x03C61840, 0x03C61840, 0x0BBE1840, /* 012 - 015 */ + 0x0BBE1840, 0x0BBE1840, 0x0BBE1840, 0x0BB61840, /* 016 - 019 */ + 0x0BB61840, 0x0BB61840, 0x0BB61840, 0x13AE1838, /* 020 - 023 */ + 0x13AE183C, 0x13AE1840, 0x13AE1840, 0x13AE1840, /* 024 - 027 */ + 0x13AE1840, 0x1BB618B8, 0x1BAE18B8, 0x1BAE18BC, /* 028 - 031 */ + 0x1BAE18C0, 0x1BAE18C0, 0x23A618C0, 0x23A618C0, /* 032 - 035 */ + 0x23A618C0, 0x23A618C0, 0x23A618C0, 0x239E18C0, /* 036 - 039 */ + 0x239E18C0, 0x239E18C0, 0x239E18C0, 0x239E18C0, /* 040 - 043 */ + 0x2B9618C0, 0x2B9618C0, 0x2B9618C0, 0x33961940, /* 044 - 047 */ + 0x37961940, 0x37961940, 0x37961940, 0x3F9E19C0, /* 048 - 051 */ + 0x3F9E19C0, 0x3F9E19C0, 0x3FA61A40, 0x3FA61A40, /* 052 - 055 */ + 0x3FA61A40, 0x3FA61A40, 0x3F9619C0, 0x3F9619C0, /* 056 - 059 */ + 0x3F9619C0, 0x3F9619C0, 0x479E1A40, 0x479E1A40, /* 060 - 063 */ + 0x479E1A40, 0x47961A40, 0x47961A40, 0x47961A40, /* 064 - 067 */ + 0x47961A40, 0x4F8E1A40, 0x4F8E1A40, 0x4F8E1A40, /* 068 - 071 */ + 0x4F8E1A40, 0x4F8E1A40, 0x57861A40, 0x57861A40, /* 072 - 075 */ + 0x57861A40, 0x57861A40, 0x57861A40, 0x5F861AC0, /* 076 - 079 */ + 0x5F861AC0, 0x5F861AC0, 0x5F861AC0, 0x5F861AC0, /* 080 - 083 */ + 0x5F861AC0, 0x5F7E1AC0, 0x5F7E1AC0, 0x5F7E1AC0, /* 084 - 087 */ + 0x5F7E1AC0, 0x5F7E1AC0, 0x677E2AC0, 0x677E2AC0, /* 088 - 091 */ + 0x677E2AC0, 0x677E2AC0, 0x67762AC0, 0x67762AC0, /* 092 - 095 */ + 0x67762AC0, 0x67762AC0, 0x67762AC0, 0x6F6E2AC0, /* 096 - 099 */ + 0x6F6E2AC0, 0x6F6E2AC0, 0x6F6E2AC0, 0x776E3AC0, /* 100 - 103 */ + 0x776E3AC0, 0x776E3AC0, 0x776E3AC0, 0x7F663AC0, /* 104 - 107 */ + 0x7F663AC0, 0x7F664AC0, 0x7F664AC0, 0x7F664AC0, /* 108 - 111 */ + 0x7F664AC0, 0x87665AC0, 0x87665AC0, 0x87665AC0, /* 112 - 115 */ + 0x87665AC0, 0x87665AC0, 0x875E5AC0, 0x875E5AC0, /* 116 - 119 */ + 0x875E5AC0, 0x875E5AC0, 0x875E5AC0, 0x8F5E6AC0, /* 120 - 123 */ + 0x8F5E6AC0, 0x8F5E6AC0, 0x8F5E6AC0, 0x975E7AC0, /* 124 - 127 */ + 0x975E7AC0, 0x975E7AC0, 0x975E7AC0, 0x9F5E8AC0, /* 128 - 131 */ + 0x9F5E8AC0, 0x9F5E8AC0, 0x9F5E8AC0, 0x9F5E8AC0, /* 132 - 135 */ + 0xA7569AC0, 0xA7569AC0, 0xA7569AC0, 0xA7569AC0, /* 136 - 139 */ + 0xA756AAC0, 0xA756AAC0, 0xA756AAC0, 0xAF4EAAC0, /* 140 - 143 */ + 0xAF4EAAC0, 0xAF4EAAC0, 0xAF4EAAC0, 0xAF4EAAC0, /* 144 - 147 */ + 0xB746AAC0, 0xB746AAC0, 0xB746AAC0, 0xB746AAC0, /* 148 - 151 */ + 0xB746AAC0, 0xB746AAC0, 0xB746AAC0, 0xB746BAC0, /* 152 - 155 */ + 0xB746BAC0, 0xB746BAC0, 0xBF4EBB40, 0xBF4EBB40, /* 156 - 159 */ + 0xBF4EBB40, 0xBF4EBB40, 0xBF4EBB40, 0xBF4EBB40, /* 160 - 163 */ + 0xBF4EBB40, 0xBF4EBB40, 0xBF4EBB40, 0xBE46CB40, /* 164 - 167 */ + 0xBE46CB40, 0xBE46CB40, 0xBE46CB40, 0xBE46CB40, /* 168 - 171 */ + 0xBE46CB40, 0xBE46DB40, 0xBE46DB40, 0xBE46DB40, /* 172 - 175 */ + 0xC63ECB40, 0xC63ECB40, 0xC63EDB40, 0xC63EDB40, /* 176 - 179 */ + 0xC63EDB40, 0xC644DB40, 0xC644DB40, 0xC644DB40, /* 180 - 183 */ + 0xC644DB40, 0xC63CDB40, 0xC63CDB40, 0xC63CDB40, /* 184 - 187 */ + 0xC63CDB40, 0xD634DB40, 0xD634DB40, 0xD634DB40, /* 188 - 191 */ + 0xD634DB40, 0xD634DB40, 0xDE2CDB3C, 0xDE2CDB3C, /* 192 - 195 */ + 0xDE2CDB3C, 0xE62CDB40, 0xE62CDB40, 0xE62CDB40, /* 196 - 199 */ + 0xE62CDB40, 0xE62CDB40, 0xE62CEB40, 0xE62CEB40, /* 200 - 203 */ + 0xE62CEB40, 0xEE2CFB40, 0xEE2CFB40, 0xEE2CFB40, /* 204 - 207 */ + 0xEE2D0B40, 0xEE2D0B40, 0xEE2D0B40, 0xEE2D0B40, /* 208 - 211 */ + 0xEE2D0B40, 0xF5250B38, 0xF5250B3C, 0xF5250B40, /* 212 - 215 */ + 0xF5251B40, 0xF5251B40, 0xF5251B40, 0xF5251B40, /* 216 - 219 */ + 0xF5251B40, 0xFD252B40, 0xFD252B40, 0xFD252B40, /* 220 - 223 */ + 0xFD252B40, 0xFD252740, 0xFD252740, 0xFD252740, /* 224 - 227 */ + 0xFD252340, 0xFD252340, 0xFD252340, 0xFD253340, /* 228 - 231 */ + 0xFD253340, 0xFD253340, 0xFD253340, 0xFD253340, /* 232 - 235 */ + 0xFD253340, 0xFD253340, 0xFD253340, 0xFC254340, /* 236 - 239 */ + 0xFD254340, 0xFD254340, 0xFD254344, 0xFC254348, /* 240 - 243 */ + 0xFC25434C, 0xFD2543BC, 0xFD2543C0, 0xFC2543C0, /* 244 - 247 */ + 0xFC2343C0, 0xFC2343C0, 0xFD2343C0, 0xFC2143C0, /* 248 - 251 */ + 0xFC2143C0, 0xFC2153C0, 0xFD2153C0, 0xFC2153C0 /* 252 - 255 */ +}; + + +u_int32_t E1_Equalizer[256] = /* E1 Receiver Equalizer */ +{ + 0x07DE182C, 0x07DE182C, 0x07D6182C, 0x07D6182C, /* 000 - 003 */ + 0x07D6182C, 0x07CE182C, 0x07CE182C, 0x07CE182C, /* 004 - 007 */ + 0x07C6182C, 0x07C6182C, 0x07C6182C, 0x07BE182C, /* 008 - 011 */ + 0x07BE182C, 0x07BE182C, 0x07BE182C, 0x07BE182C, /* 012 - 015 */ + 0x07B6182C, 0x07B6182C, 0x07B6182C, 0x07B6182C, /* 016 - 019 */ + 0x07B6182C, 0x07AE182C, 0x07AE182C, 0x07AE182C, /* 020 - 023 */ + 0x07AE182C, 0x07AE182C, 0x07B618AC, 0x07AE18AC, /* 024 - 027 */ + 0x07AE18AC, 0x07AE18AC, 0x07AE18AC, 0x07A618AC, /* 028 - 031 */ + 0x07A618AC, 0x07A618AC, 0x07A618AC, 0x079E18AC, /* 032 - 035 */ + 0x07A6192C, 0x07A6192C, 0x07A6192C, 0x0FA6192C, /* 036 - 039 */ + 0x0FA6192C, 0x0F9E192C, 0x0F9E192C, 0x0F9E192C, /* 040 - 043 */ + 0x179E192C, 0x17A619AC, 0x179E19AC, 0x179E19AC, /* 044 - 047 */ + 0x179619AC, 0x1F9619AC, 0x1F9619AC, 0x1F8E19AC, /* 048 - 051 */ + 0x1F8E19AC, 0x1F8E19AC, 0x278E19AC, 0x278E1A2C, /* 052 - 055 */ + 0x278E1A2C, 0x278E1A2C, 0x278E1A2C, 0x2F861A2C, /* 056 - 059 */ + 0x2F861A2C, 0x2F861A2C, 0x2F7E1A2C, 0x2F7E1A2C, /* 060 - 063 */ + 0x2F7E1A2C, 0x377E1A2C, 0x377E1AAC, 0x377E1AAC, /* 064 - 067 */ + 0x377E1AAC, 0x377E1AAC, 0x3F7E2AAC, 0x3F7E2AAC, /* 068 - 071 */ + 0x3F762AAC, 0x3F862B2C, 0x3F7E2B2C, 0x477E2B2C, /* 072 - 075 */ + 0x477E2F2C, 0x477E2F2C, 0x477E2F2C, 0x47762F2C, /* 076 - 079 */ + 0x4F762F2C, 0x4F762F2C, 0x4F6E2F2C, 0x4F6E2F2C, /* 080 - 083 */ + 0x4F6E2F2C, 0x576E2F2C, 0x576E2F2C, 0x576E3F2C, /* 084 - 087 */ + 0x576E3F2C, 0x576E3F2C, 0x5F6E3F2C, 0x5F6E4F2C, /* 088 - 091 */ + 0x5F6E4F2C, 0x5F6E4F2C, 0x5F664F2C, 0x67664F2C, /* 092 - 095 */ + 0x67664F2C, 0x675E4F2C, 0x675E4F2C, 0x67664F2C, /* 096 - 099 */ + 0x67664F2C, 0x67665F2C, 0x6F6E5F2C, 0x6F6E6F2C, /* 100 - 103 */ + 0x6F6E6F2C, 0x6F6E7F2C, 0x6F6E7F2C, 0x6F6E7F2C, /* 104 - 107 */ + 0x77667F2C, 0x77667F2C, 0x775E6F2C, 0x775E7F2C, /* 108 - 111 */ + 0x775E7F2C, 0x7F5E7F2C, 0x7F5E8F2C, 0x7F5E8F2C, /* 112 - 115 */ + 0x7F5E8F2C, 0x87568F2C, 0x87568F2C, 0x87568F2C, /* 116 - 119 */ + 0x874E8F2C, 0x874E8F2C, 0x874E8F2C, 0x8F4E9F2C, /* 120 - 123 */ + 0x8F4E9F2C, 0x8F4EAF2C, 0x8F4EAF2C, 0x8F4EAF2C, /* 124 - 127 */ + 0x974EAF2C, 0x974EAF2C, 0x974EAB2C, 0x974EAB2C, /* 128 - 131 */ + 0x974EAB2C, 0x9F4EAB2C, 0x9F4EBB2C, 0x9F4EBB2C, /* 132 - 135 */ + 0x9F4EBB2C, 0x9F4ECB2C, 0xA74ECB2C, 0xA74ECB2C, /* 136 - 139 */ + 0xA746CB2C, 0xA746CB2C, 0xA746CB2C, 0xA746DB2C, /* 140 - 143 */ + 0xAF46DB2C, 0xAF46EB2C, 0xAF46EB2C, 0xAF4EEB2C, /* 144 - 147 */ + 0xAE4EEB2C, 0xAE4EEB2C, 0xB546FB2C, 0xB554FB2C, /* 148 - 151 */ + 0xB54CEB2C, 0xB554FB2C, 0xB554FB2C, 0xBD54FB2C, /* 152 - 155 */ + 0xBD4CFB2C, 0xBD4CFB2C, 0xBD4CFB2C, 0xBD44EB2C, /* 156 - 159 */ + 0xC544FB2C, 0xC544FB2C, 0xC544FB2C, 0xC5450B2C, /* 160 - 163 */ + 0xC5450B2C, 0xC5450B2C, 0xCD450B2C, 0xCD450B2C, /* 164 - 167 */ + 0xCD3D0B2C, 0xCD3D0B2C, 0xCD3D0B2C, 0xD53D0B2C, /* 168 - 171 */ + 0xD53D0B2C, 0xD53D1B2C, 0xD53D1B2C, 0xD53D1B2C, /* 172 - 175 */ + 0xDD3D1B2C, 0xDD3D1B2C, 0xDD351B2C, 0xDD351B2C, /* 176 - 179 */ + 0xDD351B2C, 0xE5351B2C, 0xE5351B2C, 0xE52D1B2C, /* 180 - 183 */ + 0xE52D1B2C, 0xE52D3B2C, 0xED2D4B2C, 0xED2D1BA8, /* 184 - 187 */ + 0xED2D1BAC, 0xED2D17AC, 0xED2D17AC, 0xED2D27AC, /* 188 - 191 */ + 0xF52D27AC, 0xF52D27AC, 0xF52D2BAC, 0xF52D2BAC, /* 192 - 195 */ + 0xF52D2BAC, 0xFD2D2BAC, 0xFD2B2BAC, 0xFD2B2BAC, /* 196 - 199 */ + 0xFD2B2BAC, 0xFD2B2BAC, 0xFD232BAC, 0xFD232BAC, /* 200 - 203 */ + 0xFD232BAC, 0xFD212BAC, 0xFD212BAC, 0xFD292BAC, /* 204 - 207 */ + 0xFD292BAC, 0xFD2927AC, 0xFD2937AC, 0xFD2923AC, /* 208 - 211 */ + 0xFD2923AC, 0xFD2923AC, 0xFD2923AC, 0xFD2123AC, /* 212 - 215 */ + 0xFD2123AC, 0xFD2123AC, 0xFD2133AC, 0xFD2133AC, /* 216 - 219 */ + 0xFD2133AC, 0xFD2143AC, 0xFD2143AC, 0xFD2143AC, /* 220 - 223 */ + 0xFC2143AC, 0xFC2143AC, 0xFC1943AC, 0xFC1943AC, /* 224 - 227 */ + 0xFC1943AC, 0xFC1943AC, 0xFC1953AC, 0xFC1953AC, /* 228 - 231 */ + 0xFC1953AC, 0xFC1953AC, 0xFC1963AC, 0xFC1963AC, /* 232 - 235 */ + 0xFC1963AC, 0xFC1973AC, 0xFC1973AC, 0xFC1973AC, /* 236 - 239 */ + 0xFC1973AC, 0xFC1973AC, 0xFC1983AC, 0xFC1983AC, /* 240 - 243 */ + 0xFC1983AC, 0xFC1983AC, 0xFC1983AC, 0xFC1993AC, /* 244 - 247 */ + 0xFC1993AC, 0xFC1993AC, 0xFC19A3AC, 0xFC19A3AC, /* 248 - 251 */ + 0xFC19B3AC, 0xFC19B3AC, 0xFC19B3AC, 0xFC19B3AC /* 252 - 255 */ +}; + +/*** End-of-Files ***/ diff --git a/drivers/staging/cxt1e1/comet_tables.h b/drivers/staging/cxt1e1/comet_tables.h new file mode 100644 index 00000000..3e2e5bad --- /dev/null +++ b/drivers/staging/cxt1e1/comet_tables.h @@ -0,0 +1,61 @@ +#ifndef _INC_COMET_TBLS_H_ +#define _INC_COMET_TBLS_H_ + +/*----------------------------------------------------------------------------- + * comet_tables.h - Waveform Tables for the PM4351 'COMET' + * + * Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + + +/***************************************************************************** +* +* Array names: +* +* TWVLongHaul0DB +* TWVLongHaul7_5DB +* TWVLongHaul15DB +* TWVLongHaul22_5DB +* TWVShortHaul0 +* TWVShortHaul1 +* TWVShortHaul2 +* TWVShortHaul3 +* TWVShortHaul4 +* TWVShortHaul5 +* TWV_E1_120Ohm +* TWV_E1_75Ohm +* T1_Equalizer +* E1_Equalizer +* +*****************************************************************************/ + +extern u_int8_t TWVLongHaul0DB[25][5]; /* T1 Long Haul 0 DB */ +extern u_int8_t TWVLongHaul7_5DB[25][5]; /* T1 Long Haul 7.5 DB */ +extern u_int8_t TWVLongHaul15DB[25][5]; /* T1 Long Haul 15 DB */ +extern u_int8_t TWVLongHaul22_5DB[25][5]; /* T1 Long Haul 22.5 DB */ +extern u_int8_t TWVShortHaul0[25][5]; /* T1 Short Haul 0-110 ft */ +extern u_int8_t TWVShortHaul1[25][5]; /* T1 Short Haul 110-220 ft */ +extern u_int8_t TWVShortHaul2[25][5]; /* T1 Short Haul 220-330 ft */ +extern u_int8_t TWVShortHaul3[25][5]; /* T1 Short Haul 330-440 ft */ +extern u_int8_t TWVShortHaul4[25][5]; /* T1 Short Haul 440-550 ft */ +extern u_int8_t TWVShortHaul5[25][5]; /* T1 Short Haul 550-660 ft */ +extern u_int8_t TWV_E1_75Ohm[25][5]; /* E1 75 Ohm */ +extern u_int8_t TWV_E1_120Ohm[25][5]; /* E1 120 Ohm */ +extern u_int32_t T1_Equalizer[256]; /* T1 Receiver Equalizer */ +extern u_int32_t E1_Equalizer[256]; /* E1 Receiver Equalizer */ + +#endif /* _INC_COMET_TBLS_H_ */ diff --git a/drivers/staging/cxt1e1/functions.c b/drivers/staging/cxt1e1/functions.c new file mode 100644 index 00000000..d9a9aa35 --- /dev/null +++ b/drivers/staging/cxt1e1/functions.c @@ -0,0 +1,356 @@ +/* Copyright (C) 2003-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include "pmcc4_sysdep.h" +#include "sbecom_inline_linux.h" +#include "libsbew.h" +#include "pmcc4.h" + + +#ifdef SBE_INCLUDE_SYMBOLS +#define STATIC +#else +#define STATIC static +#endif + +#if defined(CONFIG_SBE_HDLC_V7) || defined(CONFIG_SBE_WAN256T3_HDLC_V7) || \ + defined(CONFIG_SBE_HDLC_V7_MODULE) || defined(CONFIG_SBE_WAN256T3_HDLC_V7_MODULE) +#define _v7_hdlc_ 1 +#else +#define _v7_hdlc_ 0 +#endif + +#if _v7_hdlc_ +#define V7(x) (x ## _v7) +extern int hdlc_netif_rx_v7 (hdlc_device *, struct sk_buff *); +extern int register_hdlc_device_v7 (hdlc_device *); +extern int unregister_hdlc_device_v7 (hdlc_device *); + +#else +#define V7(x) x +#endif + + +#ifndef USE_MAX_INT_DELAY +static int dummy = 0; + +#endif + +extern int cxt1e1_log_level; +extern int drvr_state; + + +#if 1 +u_int32_t +pci_read_32 (u_int32_t *p) +{ +#ifdef FLOW_DEBUG + u_int32_t v; + + FLUSH_PCI_READ (); + v = le32_to_cpu (*p); + if (cxt1e1_log_level >= LOG_DEBUG) + pr_info("pci_read : %x = %x\n", (u_int32_t) p, v); + return v; +#else + FLUSH_PCI_READ (); /* */ + return le32_to_cpu (*p); +#endif +} + +void +pci_write_32 (u_int32_t *p, u_int32_t v) +{ +#ifdef FLOW_DEBUG + if (cxt1e1_log_level >= LOG_DEBUG) + pr_info("pci_write: %x = %x\n", (u_int32_t) p, v); +#endif + *p = cpu_to_le32 (v); + FLUSH_PCI_WRITE (); /* This routine is called from routines + * which do multiple register writes + * which themselves need flushing between + * writes in order to guarantee write + * ordering. It is less code-cumbersome + * to flush here-in then to investigate + * and code the many other register + * writing routines. */ +} +#endif + + +void +pci_flush_write (ci_t * ci) +{ + volatile u_int32_t v; + + /* issue a PCI read to flush PCI write thru bridge */ + v = *(u_int32_t *) &ci->reg->glcd; /* any address would do */ + + /* + * return nothing, this just reads PCI bridge interface to flush + * previously written data + */ +} + + +STATIC void +watchdog_func (unsigned long arg) +{ + struct watchdog *wd = (void *) arg; + + if (drvr_state != SBE_DRVR_AVAILABLE) + { + if (cxt1e1_log_level >= LOG_MONITOR) + pr_warning("%s: drvr not available (%x)\n", __func__, drvr_state); + return; + } + schedule_work (&wd->work); + mod_timer (&wd->h, jiffies + wd->ticks); +} + +int OS_init_watchdog(struct watchdog *wdp, void (*f) (void *), void *c, int usec) +{ + wdp->func = f; + wdp->softc = c; + wdp->ticks = (HZ) * (usec / 1000) / 1000; + INIT_WORK(&wdp->work, (void *)f); + init_timer (&wdp->h); + { + ci_t *ci = (ci_t *) c; + + wdp->h.data = (unsigned long) &ci->wd; + } + wdp->h.function = watchdog_func; + return 0; +} + +void +OS_uwait (int usec, char *description) +{ + int tmp; + + if (usec >= 1000) + { + mdelay (usec / 1000); + /* now delay residual */ + tmp = (usec / 1000) * 1000; /* round */ + tmp = usec - tmp; /* residual */ + if (tmp) + { /* wait on residual */ + udelay (tmp); + } + } else + { + udelay (usec); + } +} + +/* dummy short delay routine called as a subroutine so that compiler + * does not optimize/remove its intent (a short delay) + */ + +void +OS_uwait_dummy (void) +{ +#ifndef USE_MAX_INT_DELAY + dummy++; +#else + udelay (1); +#endif +} + + +void +OS_sem_init (void *sem, int state) +{ + switch (state) + { + case SEM_TAKEN: + sema_init((struct semaphore *) sem, 0); + break; + case SEM_AVAILABLE: + sema_init((struct semaphore *) sem, 1); + break; + default: /* otherwise, set sem.count to state's + * value */ + sema_init (sem, state); + break; + } +} + + +int +sd_line_is_ok (void *user) +{ + struct net_device *ndev = (struct net_device *) user; + + return (netif_carrier_ok (ndev)); +} + +void +sd_line_is_up (void *user) +{ + struct net_device *ndev = (struct net_device *) user; + + netif_carrier_on (ndev); + return; +} + +void +sd_line_is_down (void *user) +{ + struct net_device *ndev = (struct net_device *) user; + + netif_carrier_off (ndev); + return; +} + +void +sd_disable_xmit (void *user) +{ + struct net_device *dev = (struct net_device *) user; + + netif_stop_queue (dev); + return; +} + +void +sd_enable_xmit (void *user) +{ + struct net_device *dev = (struct net_device *) user; + + netif_wake_queue (dev); + return; +} + +int +sd_queue_stopped (void *user) +{ + struct net_device *ndev = (struct net_device *) user; + + return (netif_queue_stopped (ndev)); +} + +void sd_recv_consume(void *token, size_t len, void *user) +{ + struct net_device *ndev = user; + struct sk_buff *skb = token; + + skb->dev = ndev; + skb_put (skb, len); + skb->protocol = hdlc_type_trans(skb, ndev); + netif_rx(skb); +} + + +/** + ** Read some reserved location w/in the COMET chip as a usable + ** VMETRO trigger point or other trace marking event. + **/ + +#include "comet.h" + +extern ci_t *CI; /* dummy pointer to board ZERO's data */ +void +VMETRO_TRACE (void *x) +{ + u_int32_t y = (u_int32_t) x; + + pci_write_32 ((u_int32_t *) &CI->cpldbase->leds, y); +} + + +void +VMETRO_TRIGGER (ci_t * ci, int x) +{ + comet_t *comet; + volatile u_int32_t data; + + comet = ci->port[0].cometbase; /* default to COMET # 0 */ + + switch (x) + { + default: + case 0: + data = pci_read_32 ((u_int32_t *) &comet->__res24); /* 0x90 */ + break; + case 1: + data = pci_read_32 ((u_int32_t *) &comet->__res25); /* 0x94 */ + break; + case 2: + data = pci_read_32 ((u_int32_t *) &comet->__res26); /* 0x98 */ + break; + case 3: + data = pci_read_32 ((u_int32_t *) &comet->__res27); /* 0x9C */ + break; + case 4: + data = pci_read_32 ((u_int32_t *) &comet->__res88); /* 0x220 */ + break; + case 5: + data = pci_read_32 ((u_int32_t *) &comet->__res89); /* 0x224 */ + break; + case 6: + data = pci_read_32 ((u_int32_t *) &comet->__res8A); /* 0x228 */ + break; + case 7: + data = pci_read_32 ((u_int32_t *) &comet->__res8B); /* 0x22C */ + break; + case 8: + data = pci_read_32 ((u_int32_t *) &comet->__resA0); /* 0x280 */ + break; + case 9: + data = pci_read_32 ((u_int32_t *) &comet->__resA1); /* 0x284 */ + break; + case 10: + data = pci_read_32 ((u_int32_t *) &comet->__resA2); /* 0x288 */ + break; + case 11: + data = pci_read_32 ((u_int32_t *) &comet->__resA3); /* 0x28C */ + break; + case 12: + data = pci_read_32 ((u_int32_t *) &comet->__resA4); /* 0x290 */ + break; + case 13: + data = pci_read_32 ((u_int32_t *) &comet->__resA5); /* 0x294 */ + break; + case 14: + data = pci_read_32 ((u_int32_t *) &comet->__resA6); /* 0x298 */ + break; + case 15: + data = pci_read_32 ((u_int32_t *) &comet->__resA7); /* 0x29C */ + break; + case 16: + data = pci_read_32 ((u_int32_t *) &comet->__res74); /* 0x1D0 */ + break; + case 17: + data = pci_read_32 ((u_int32_t *) &comet->__res75); /* 0x1D4 */ + break; + case 18: + data = pci_read_32 ((u_int32_t *) &comet->__res76); /* 0x1D8 */ + break; + case 19: + data = pci_read_32 ((u_int32_t *) &comet->__res77); /* 0x1DC */ + break; + } +} + + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/hwprobe.c b/drivers/staging/cxt1e1/hwprobe.c new file mode 100644 index 00000000..de8ac0bc --- /dev/null +++ b/drivers/staging/cxt1e1/hwprobe.c @@ -0,0 +1,396 @@ +/* Copyright (C) 2007 One Stop Systems + * Copyright (C) 2003-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include "pmcc4_sysdep.h" +#include "sbecom_inline_linux.h" +#include "libsbew.h" +#include "pmcc4_private.h" +#include "pmcc4.h" +#include "pmcc4_ioctls.h" +#include "pmc93x6_eeprom.h" +#ifdef CONFIG_PROC_FS +#include "sbeproc.h" +#endif + +#ifdef SBE_INCLUDE_SYMBOLS +#define STATIC +#else +#define STATIC static +#endif + +extern int cxt1e1_log_level; +extern int error_flag; +extern int drvr_state; + +/* forward references */ +void c4_stopwd (ci_t *); +struct net_device * __init c4_add_dev (hdw_info_t *, int, unsigned long, unsigned long, int, int); + + +struct s_hdw_info hdw_info[MAX_BOARDS]; + + +void __init +show_two (hdw_info_t * hi, int brdno) +{ + ci_t *ci; + struct pci_dev *pdev; + char *bid; + char *bp, banner[80]; + char sn[6]; + + bp = banner; + memset (banner, 0, 80); /* clear print buffer */ + + ci = (ci_t *)(netdev_priv(hi->ndev)); + bid = sbeid_get_bdname (ci); + switch (hi->promfmt) + { + case PROM_FORMAT_TYPE1: + memcpy (sn, (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6); + break; + case PROM_FORMAT_TYPE2: + memcpy (sn, (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6); + break; + default: + memset (sn, 0, 6); + break; + } + + sprintf (banner, "%s: %s S/N %06X, MUSYCC Rev %02X", + hi->devname, bid, + ((sn[3] << 16) & 0xff0000) | + ((sn[4] << 8) & 0x00ff00) | + (sn[5] & 0x0000ff), + (u_int8_t) hi->revid[0]); + + pr_info("%s\n", banner); + + pdev = hi->pdev[0]; + pr_info("%s: %s at v/p=%lx/%lx (%02x:%02x.%x) irq %d\n", + hi->devname, "MUSYCC", + (unsigned long) hi->addr_mapped[0], hi->addr[0], + hi->pci_busno, (u_int8_t) PCI_SLOT (pdev->devfn), + (u_int8_t) PCI_FUNC (pdev->devfn), pdev->irq); + + pdev = hi->pdev[1]; + pr_info("%s: %s at v/p=%lx/%lx (%02x:%02x.%x) irq %d\n", + hi->devname, "EBUS ", + (unsigned long) hi->addr_mapped[1], hi->addr[1], + hi->pci_busno, (u_int8_t) PCI_SLOT (pdev->devfn), + (u_int8_t) PCI_FUNC (pdev->devfn), pdev->irq); +} + + +void __init +hdw_sn_get (hdw_info_t * hi, int brdno) +{ + /* obtain hardware EEPROM information */ + long addr; + + addr = (long) hi->addr_mapped[1] + EEPROM_OFFSET; + + /* read EEPROM with largest known format size... */ + pmc_eeprom_read_buffer (addr, 0, (char *) hi->mfg_info.data, sizeof (FLD_TYPE2)); + +#if 0 + { + unsigned char *ucp = (unsigned char *) &hi->mfg_info.data; + + pr_info("eeprom[00]: %02x %02x %02x %02x %02x %02x %02x %02x\n", + *(ucp + 0), *(ucp + 1), *(ucp + 2), *(ucp + 3), *(ucp + 4), *(ucp + 5), *(ucp + 6), *(ucp + 7)); + pr_info("eeprom[08]: %02x %02x %02x %02x %02x %02x %02x %02x\n", + *(ucp + 8), *(ucp + 9), *(ucp + 10), *(ucp + 11), *(ucp + 12), *(ucp + 13), *(ucp + 14), *(ucp + 15)); + pr_info("eeprom[16]: %02x %02x %02x %02x %02x %02x %02x %02x\n", + *(ucp + 16), *(ucp + 17), *(ucp + 18), *(ucp + 19), *(ucp + 20), *(ucp + 21), *(ucp + 22), *(ucp + 23)); + pr_info("eeprom[24]: %02x %02x %02x %02x %02x %02x %02x %02x\n", + *(ucp + 24), *(ucp + 25), *(ucp + 26), *(ucp + 27), *(ucp + 28), *(ucp + 29), *(ucp + 30), *(ucp + 31)); + pr_info("eeprom[32]: %02x %02x %02x %02x %02x %02x %02x %02x\n", + *(ucp + 32), *(ucp + 33), *(ucp + 34), *(ucp + 35), *(ucp + 36), *(ucp + 37), *(ucp + 38), *(ucp + 39)); + pr_info("eeprom[40]: %02x %02x %02x %02x %02x %02x %02x %02x\n", + *(ucp + 40), *(ucp + 41), *(ucp + 42), *(ucp + 43), *(ucp + 44), *(ucp + 45), *(ucp + 46), *(ucp + 47)); + } +#endif +#if 0 + pr_info("sn: %x %x %x %x %x %x\n", + hi->mfg_info.Serial[0], + hi->mfg_info.Serial[1], + hi->mfg_info.Serial[2], + hi->mfg_info.Serial[3], + hi->mfg_info.Serial[4], + hi->mfg_info.Serial[5]); +#endif + + if ((hi->promfmt = pmc_verify_cksum (&hi->mfg_info.data)) == PROM_FORMAT_Unk) + { + /* bad crc, data is suspect */ + if (cxt1e1_log_level >= LOG_WARN) + pr_info("%s: EEPROM cksum error\n", hi->devname); + hi->mfg_info_sts = EEPROM_CRCERR; + } else + hi->mfg_info_sts = EEPROM_OK; +} + + +void __init +prep_hdw_info (void) +{ + hdw_info_t *hi; + int i; + + for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++) + { + hi->pci_busno = 0xff; + hi->pci_slot = 0xff; + hi->pci_pin[0] = 0; + hi->pci_pin[1] = 0; + hi->ndev = 0; + hi->addr[0] = 0L; + hi->addr[1] = 0L; + hi->addr_mapped[0] = 0L; + hi->addr_mapped[1] = 0L; + } +} + +void +cleanup_ioremap (void) +{ + hdw_info_t *hi; + int i; + + for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++) + { + if (hi->pci_slot == 0xff) + break; + if (hi->addr_mapped[0]) + { + iounmap ((void *) (hi->addr_mapped[0])); + release_mem_region ((long) hi->addr[0], hi->len[0]); + hi->addr_mapped[0] = 0; + } + if (hi->addr_mapped[1]) + { + iounmap ((void *) (hi->addr_mapped[1])); + release_mem_region ((long) hi->addr[1], hi->len[1]); + hi->addr_mapped[1] = 0; + } + } +} + + +void +cleanup_devs (void) +{ + hdw_info_t *hi; + int i; + + for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++) + { + if (hi->pci_slot == 0xff || !hi->ndev) + break; + c4_stopwd(netdev_priv(hi->ndev)); +#ifdef CONFIG_PROC_FS + sbecom_proc_brd_cleanup(netdev_priv(hi->ndev)); +#endif + unregister_netdev (hi->ndev); + free_irq (hi->pdev[0]->irq, hi->ndev); +#ifdef CONFIG_SBE_PMCC4_NCOMM + free_irq (hi->pdev[1]->irq, hi->ndev); +#endif + OS_kfree (hi->ndev); + } +} + + +STATIC int __init +c4_hdw_init (struct pci_dev * pdev, int found) +{ + hdw_info_t *hi; + int i; + int fun, slot; + unsigned char busno = 0xff; + + /* our MUSYCC chip supports two functions, 0 & 1 */ + if ((fun = PCI_FUNC (pdev->devfn)) > 1) + { + pr_warning("unexpected devfun: 0x%x\n", pdev->devfn); + return 0; + } + if (pdev->bus) /* obtain bus number */ + busno = pdev->bus->number; + else + busno = 0; /* default for system PCI inconsistency */ + slot = pdev->devfn & ~0x07; + + /* + * Functions 0 & 1 for a given board (identified by same bus(busno) and + * slot(slot)) are placed into the same 'hardware' structure. The first + * part of the board's functionality will be placed into an unpopulated + * element, identified by "slot==(0xff)". The second part of a board's + * functionality will match the previously loaded slot/busno. + */ + for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++) + { + /* + * match with board's first found interface, otherwise this is first + * found + */ + if ((hi->pci_slot == 0xff) || /* new board */ + ((hi->pci_slot == slot) && (hi->bus == pdev->bus))) + break; /* found for-loop exit */ + } + if (i == MAX_BOARDS) /* no match in above loop means MAX + * exceeded */ + { + pr_warning("exceeded number of allowed devices (>%d)?\n", MAX_BOARDS); + return 0; + } + if (pdev->bus) + hi->pci_busno = pdev->bus->number; + else + hi->pci_busno = 0; /* default for system PCI inconsistency */ + hi->pci_slot = slot; + pci_read_config_byte (pdev, PCI_INTERRUPT_PIN, &hi->pci_pin[fun]); + pci_read_config_byte (pdev, PCI_REVISION_ID, &hi->revid[fun]); + hi->bus = pdev->bus; + hi->addr[fun] = pci_resource_start (pdev, 0); + hi->len[fun] = pci_resource_end (pdev, 0) - hi->addr[fun] + 1; + hi->pdev[fun] = pdev; + + { + /* + * create device name from module name, plus add the appropriate + * board number + */ + char *cp = hi->devname; + + strcpy (cp, KBUILD_MODNAME); + cp += strlen (cp); /* reposition */ + *cp++ = '-'; + *cp++ = '0' + (found / 2); /* there are two found interfaces per + * board */ + *cp = 0; /* termination */ + } + + return 1; +} + + +status_t __init +c4hw_attach_all (void) +{ + hdw_info_t *hi; + struct pci_dev *pdev = NULL; + int found = 0, i, j; + + error_flag = 0; + prep_hdw_info (); + /*** scan PCI bus for all possible boards */ + while ((pdev = pci_get_device (PCI_VENDOR_ID_CONEXANT, + PCI_DEVICE_ID_CN8474, + pdev))) + { + if (c4_hdw_init (pdev, found)) + found++; + } + if (!found) + { + pr_warning("No boards found\n"); + return ENODEV; + } + /* sanity check for consistent hardware found */ + for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++) + { + if (hi->pci_slot != 0xff && (!hi->addr[0] || !hi->addr[1])) + { + pr_warning("%s: something very wrong with pci_get_device\n", + hi->devname); + return EIO; + } + } + /* bring board's memory regions on/line */ + for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++) + { + if (hi->pci_slot == 0xff) + break; + for (j = 0; j < 2; j++) + { + if (request_mem_region (hi->addr[j], hi->len[j], hi->devname) == 0) + { + pr_warning("%s: memory in use, addr=0x%lx, len=0x%lx ?\n", + hi->devname, hi->addr[j], hi->len[j]); + cleanup_ioremap (); + return ENOMEM; + } + hi->addr_mapped[j] = (unsigned long) ioremap (hi->addr[j], hi->len[j]); + if (!hi->addr_mapped[j]) + { + pr_warning("%s: ioremap fails, addr=0x%lx, len=0x%lx ?\n", + hi->devname, hi->addr[j], hi->len[j]); + cleanup_ioremap (); + return ENOMEM; + } +#ifdef SBE_MAP_DEBUG + pr_warning("%s: io remapped from phys %x to virt %x\n", + hi->devname, (u_int32_t) hi->addr[j], (u_int32_t) hi->addr_mapped[j]); +#endif + } + } + + drvr_state = SBE_DRVR_AVAILABLE; + + /* Have now memory mapped all boards. Now allow board's access to system */ + for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++) + { + if (hi->pci_slot == 0xff) + break; + if (pci_enable_device (hi->pdev[0]) || + pci_enable_device (hi->pdev[1])) + { + drvr_state = SBE_DRVR_DOWN; + pr_warning("%s: failed to enable card %d slot %d\n", + hi->devname, i, hi->pci_slot); + cleanup_devs (); + cleanup_ioremap (); + return EIO; + } + pci_set_master (hi->pdev[0]); + pci_set_master (hi->pdev[1]); + if (!(hi->ndev = c4_add_dev (hi, i, (long) hi->addr_mapped[0], + (long) hi->addr_mapped[1], + hi->pdev[0]->irq, + hi->pdev[1]->irq))) + { + drvr_state = SBE_DRVR_DOWN; + cleanup_ioremap (); + /* NOTE: c4_add_dev() does its own device cleanup */ +#if 0 + cleanup_devs (); +#endif + return error_flag; /* error_flag set w/in add_dev() */ + } + show_two (hi, i); /* displays found information */ + } + return 0; +} + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/libsbew.h b/drivers/staging/cxt1e1/libsbew.h new file mode 100644 index 00000000..4254c042 --- /dev/null +++ b/drivers/staging/cxt1e1/libsbew.h @@ -0,0 +1,549 @@ +#ifndef _INC_LIBSBEW_H_ +#define _INC_LIBSBEW_H_ + +/*----------------------------------------------------------------------------- + * libsbew.h - common library elements, charge across mulitple boards + * + * This file contains common Ioctl structures and contents definitions. + * + * Copyright (C) 2004-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + +/********************************/ +/** set driver logging level **/ +/********************************/ + +/* routine/ioctl: wancfg_set_loglevel() - SBE_IOC_SET_LOGLEVEL */ + +#define LOG_NONE 0 +#define LOG_ERROR 1 +#define LOG_SBEBUG3 3 /* hidden, for development/debug usage */ +#define LOG_LSCHANGE 5 /* line state change logging */ +#define LOG_LSIMMEDIATE 6 /* line state change logging w/o hysterisis */ +#define LOG_WARN 8 +#define LOG_MONITOR 10 +#define LOG_SBEBUG12 12 /* hidden, for development/debug usage */ +#define LOG_MONITOR2 14 /* hidden, for development/debug usage */ +#define LOG_DEBUG 16 + + /* TEMPORARY DEFINES *//* RLD DEBUG */ +#define c4_LOG_NONE LOG_NONE +#define c4_LOG_ERROR LOG_ERROR +#define c4_LOG_WARN LOG_WARN +#define c4_LOG_sTrace LOG_MONITOR /* do some trace logging into + * functions */ +#define c4_LOG_DEBUG LOG_DEBUG +#define c4_LOG_MAX LOG_DEBUG + + + +/******************************/ +/** get driver information **/ +/******************************/ + +/* routine/ioctl: wancfg_get_drvinfo() - SBE_IOC_GET_DRVINFO */ + +#define REL_STRLEN 80 + struct sbe_drv_info + { + int rel_strlen; + char release[REL_STRLEN]; + }; + + +/*****************************/ +/** get board information **/ +/*****************************/ + +/* routine/ioctl: wancfg_get_brdinfo() - SBE_IOC_GET_BRDINFO */ + +#define CHNM_STRLEN 16 + struct sbe_brd_info + { + u_int32_t brd_id; /* SBE's unique PCI VENDOR/DEVID */ + u_int32_t brd_sn; + int brd_chan_cnt; /* number of channels being used */ + int brd_port_cnt; /* number of ports being used */ + unsigned char brdno; /* our board number */ + unsigned char brd_pci_speed; /* PCI speed, 33/66Mhz */ + u_int8_t brd_mac_addr[6]; + char first_iname[CHNM_STRLEN]; /* first assigned channel's + * interface name */ + char last_iname[CHNM_STRLEN]; /* last assigned channel's + * interface name */ + u_int8_t brd_hdw_id; /* on/board unique hdw ID */ + u_int8_t reserved8[3]; /* alignment preservation */ + u_int32_t reserved32[3]; /* size preservation */ + }; + +/* These IDs are sometimes available thru pci_ids.h, but not currently. */ + +#define PCI_VENDOR_ID_SBE 0x1176 +#define PCI_DEVICE_ID_WANPMC_C4T1E1 0x0701 /* BID 0x0X, BTYP 0x0X */ +#define PCI_DEVICE_ID_WANPTMC_C4T1E1 0x0702 /* BID 0x41 */ +#define PCI_DEVICE_ID_WANADAPT_HC4T1E1 0x0703 /* BID 0x44 */ +#define PCI_DEVICE_ID_WANPTMC_256T3_T1 0x0704 /* BID 0x42 (T1 Version) */ +#define PCI_DEVICE_ID_WANPCI_C4T1E1 0x0705 /* BID 0x1X, BTYP 0x0X */ +#define PCI_DEVICE_ID_WANPMC_C1T3 0x0706 /* BID 0x45 */ +#define PCI_DEVICE_ID_WANPCI_C2T1E1 0x0707 /* BID 0x1X, BTYP 0x2X */ +#define PCI_DEVICE_ID_WANPCI_C1T1E1 0x0708 /* BID 0x1X, BTYP 0x1X */ +#define PCI_DEVICE_ID_WANPMC_C2T1E1 0x0709 /* BID 0x0X, BTYP 0x2X */ +#define PCI_DEVICE_ID_WANPMC_C1T1E1 0x070A /* BID 0x0X, BTYP 0x1X */ +#define PCI_DEVICE_ID_WANPTMC_256T3_E1 0x070B /* BID 0x46 (E1 Version) */ +#define PCI_DEVICE_ID_WANPTMC_C24TE1 0x070C /* BID 0x47 */ +#define PCI_DEVICE_ID_WANPMC_C4T1E1_L 0x070D /* BID 0x2X, BTYPE 0x0X w/FP + * LEDs */ +#define PCI_DEVICE_ID_WANPMC_C2T1E1_L 0x070E /* BID 0x2X, BTYPE 0x2X w/FP + * LEDs */ +#define PCI_DEVICE_ID_WANPMC_C1T1E1_L 0x070F /* BID 0x2X, BTYPE 0x1X w/FP + * LEDs */ +#define PCI_DEVICE_ID_WANPMC_2SSI 0x0801 +#define PCI_DEVICE_ID_WANPCI_4SSI 0x0802 +#define PCI_DEVICE_ID_WANPMC_2T3E3 0x0900 /* BID 0x43 */ +#define SBE_BOARD_ID(v,id) ((v<<16) | id) + +#define BINFO_PCI_SPEED_unk 0 +#define BINFO_PCI_SPEED_33 1 +#define BINFO_PCI_SPEED_66 2 + +/***************************/ +/** obtain interface ID **/ +/***************************/ + +/* routine/ioctl: wancfg_get_iid() - SBE_IOC_IID_GET */ + + struct sbe_iid_info + { + u_int32_t channum; /* channel requested */ + char iname[CHNM_STRLEN]; /* channel's interface name */ + }; + +/**************************************/ +/** get board address information **/ +/**************************************/ + +/* routine/ioctl: wancfg_get_brdaddr() - SBE_IOC_BRDADDR_GET */ + + struct sbe_brd_addr + { + unsigned char func; /* select PCI address space function */ + unsigned char brdno; /* returns brdno requested */ + unsigned char irq; + unsigned char size; /* returns size of address */ +#define BRDADDR_SIZE_64 1 +#define BRDADDR_SIZE_32 2 + int reserved1; /* mod64 align, reserved for future use */ + + union + { + unsigned long virt64; /* virtual/mapped address */ + u_int32_t virt32[2]; + } v; + union + { + unsigned long phys64; /* physical bus address */ + u_int32_t phys32[2]; + } p; + int reserved2[4]; /* reserved for future use */ + }; + +/**********************************/ +/** read/write board registers **/ +/**********************************/ + +/* routine/ioctl: wancfg_read_vec() - SBE_IOC_READ_VEC */ +/* routine/ioctl: wancfg_write_vec() - SBE_IOC_WRITE_VEC */ + + struct sbecom_wrt_vec + { + u_int32_t reg; + u_int32_t data; + }; + +#define C1T3_CHIP_MSCC_32 0x01000000 +#define C1T3_CHIP_TECT3_8 0x02000000 +#define C1T3_CHIP_CPLD_8 0x03000000 +#define C1T3_CHIP_EEPROM_8 0x04000000 + +#define W256T3_CHIP_MUSYCC_32 0x02000000 +#define W256T3_CHIP_TEMUX_8 0x10000000 +#define W256T3_CHIP_T8110_8 0x20000000 +#define W256T3_CHIP_T8110_32 0x22000000 +#define W256T3_CHIP_CPLD_8 0x30000000 +#define W256T3_CHIP_EEPROM_8 0x40000000 + + +/**********************************/ +/** read write port parameters **/ +/**********************************/ + +/* routine/ioctl: wancfg_getset_port_param() - SBE_IOC_PORT_GET */ +/* routine/ioctl: wancfg_set_port_param() - SBE_IOC_PORT_SET */ + +/* NOTE: this structure supports hardware which supports individual per/port control */ + +struct sbecom_port_param +{ + u_int8_t portnum; + u_int8_t port_mode; /* variations of T1 or E1 mode */ + u_int8_t portStatus; + u_int8_t portP; /* more port parameters (clock source - 0x80; + * and LBO - 0xf; */ + /* bits 0x70 are reserved for future use ) */ +#ifdef SBE_PMCC4_ENABLE + u_int32_t hypersize; /* RLD DEBUG - add this in until I learn how to make this entry obsolete */ +#endif + int reserved[3-1]; /* reserved for future use */ + int _res[4]; +}; + +#define CFG_CLK_PORT_MASK 0x80 /* Loop timing */ +#define CFG_CLK_PORT_INTERNAL 0x80 /* Loop timing */ +#define CFG_CLK_PORT_EXTERNAL 0x00 /* Loop timing */ + +#define CFG_LBO_MASK 0x0F +#define CFG_LBO_unk 0 /* */ +#define CFG_LBO_LH0 1 /* T1 Long Haul (default) */ +#define CFG_LBO_LH7_5 2 /* T1 Long Haul */ +#define CFG_LBO_LH15 3 /* T1 Long Haul */ +#define CFG_LBO_LH22_5 4 /* T1 Long Haul */ +#define CFG_LBO_SH110 5 /* T1 Short Haul */ +#define CFG_LBO_SH220 6 /* T1 Short Haul */ +#define CFG_LBO_SH330 7 /* T1 Short Haul */ +#define CFG_LBO_SH440 8 /* T1 Short Haul */ +#define CFG_LBO_SH550 9 /* T1 Short Haul */ +#define CFG_LBO_SH660 10 /* T1 Short Haul */ +#define CFG_LBO_E75 11 /* E1 75 Ohm */ +#define CFG_LBO_E120 12 /* E1 120 Ohm (default) */ + + +/*************************************/ +/** read write channel parameters **/ +/*************************************/ + +/* routine/ioctl: wancfg_getset_chan_param() - SBE_IOC_CHAN_GET */ +/* routine/ioctl: wancfg_set_chan_param() - SBE_IOC_CHAN_SET */ + +/* NOTE: this structure supports hardware which supports individual per/channel control */ + + struct sbecom_chan_param + { + u_int32_t channum; /* 0: */ +#ifdef SBE_PMCC4_ENABLE + u_int32_t card; /* RLD DEBUG - add this in until I learn how to make this entry obsolete */ + u_int32_t port; /* RLD DEBUG - add this in until I learn how to make this entry obsolete */ + u_int8_t bitmask[32]; +#endif + u_int32_t intr_mask; /* 4: interrupt mask, specify ored + * (SS7_)INTR_* to disable */ + u_int8_t status; /* 8: channel transceiver status (TX_ENABLED, + * RX_ENABLED) */ + u_int8_t chan_mode; /* 9: protocol mode */ + u_int8_t idlecode; /* A: idle code, in (FLAG_7E, FLAG_FF, + * FLAG_00) */ + u_int8_t pad_fill_count; /* B: pad fill count (1-127), 0 - pad + * fill disabled */ + u_int8_t data_inv; /* C: channel data inversion selection */ + u_int8_t mode_56k; /* D: 56kbps mode */ + u_int8_t reserved[2 + 8]; /* E: */ + }; + +/* SS7 interrupt signals */ +#define SS7_INTR_SFILT 0x00000020 +#define SS7_INTR_SDEC 0x00000040 +#define SS7_INTR_SINC 0x00000080 +#define SS7_INTR_SUERR 0x00000100 +/* Other interrupts that can be masked */ +#define INTR_BUFF 0x00000002 +#define INTR_EOM 0x00000004 +#define INTR_MSG 0x00000008 +#define INTR_IDLE 0x00000010 + +/* transceiver status flags */ +#define TX_ENABLED 0x01 +#define RX_ENABLED 0x02 + +/* Protocol modes */ +#define CFG_CH_PROTO_TRANS 0 +#define CFG_CH_PROTO_SS7 1 +#define CFG_CH_PROTO_HDLC_FCS16 2 +#define CFG_CH_PROTO_HDLC_FCS32 3 +#define CFG_CH_PROTO_ISLP_MODE 4 + +/* Possible idle code assignments */ +#define CFG_CH_FLAG_7E 0 +#define CFG_CH_FLAG_FF 1 +#define CFG_CH_FLAG_00 2 + +/* data inversion selection */ +#define CFG_CH_DINV_NONE 0x00 +#define CFG_CH_DINV_RX 0x01 +#define CFG_CH_DINV_TX 0x02 + + +/* Possible resettable chipsets/functions */ +#define RESET_DEV_TEMUX 1 +#define RESET_DEV_TECT3 RESET_DEV_TEMUX +#define RESET_DEV_PLL 2 + + +/*********************************************/ +/** read reset channel thruput statistics **/ +/*********************************************/ + +/* routine/ioctl: wancfg_get_chan_stats() - SBE_IOC_CHAN_GET_STAT */ +/* routine/ioctl: wancfg_del_chan_stats() - SBE_IOC_CHAN_DEL_STAT */ +/* routine/ioctl: wancfg_get_card_chan_stats() - SBE_IOC_CARD_CHAN_STAT */ + + struct sbecom_chan_stats + { + unsigned long rx_packets; /* total packets received */ + unsigned long tx_packets; /* total packets transmitted */ + unsigned long rx_bytes; /* total bytes received */ + unsigned long tx_bytes; /* total bytes transmitted */ + unsigned long rx_errors;/* bad packets received */ + unsigned long tx_errors;/* packet transmit problems */ + unsigned long rx_dropped; /* no space in linux buffers */ + unsigned long tx_dropped; /* no space available in linux */ + + /* detailed rx_errors: */ + unsigned long rx_length_errors; + unsigned long rx_over_errors; /* receiver ring buff overflow */ + unsigned long rx_crc_errors; /* recved pkt with crc error */ + unsigned long rx_frame_errors; /* recv'd frame alignment error */ + unsigned long rx_fifo_errors; /* recv'r fifo overrun */ + unsigned long rx_missed_errors; /* receiver missed packet */ + + /* detailed tx_errors */ + unsigned long tx_aborted_errors; + unsigned long tx_fifo_errors; + unsigned long tx_pending; + }; + + +/****************************************/ +/** read write card level parameters **/ +/****************************************/ + + /* NOTE: this structure supports hardware which supports per/card control */ + + struct sbecom_card_param + { + u_int8_t framing_type; /* 0: CBP or M13 */ + u_int8_t loopback; /* 1: one of LOOPBACK_* */ + u_int8_t line_build_out; /* 2: boolean */ + u_int8_t receive_eq; /* 3: boolean */ + u_int8_t transmit_ones; /* 4: boolean */ + u_int8_t clock; /* 5: 0 - internal, i>0 - external (recovered + * from framer i) */ + u_int8_t h110enable; /* 6: */ + u_int8_t disable_leds; /* 7: */ + u_int8_t reserved1; /* 8: available - old 256t3 hypersized, but + * never used */ + u_int8_t rear_io; /* 9: rear I/O off/on */ + u_int8_t disable_tx; /* A: disable TX off/on */ + u_int8_t mute_los; /* B: mute LOS off/on */ + u_int8_t los_threshold; /* C: LOS threshold norm/low + * (default: norm) */ + u_int8_t ds1_mode; /* D: DS1 mode T1/E1 (default: T1) */ + u_int8_t ds3_unchan; /* E: DS3 unchannelized mode off/on */ + u_int8_t reserved[1 + 16]; /* reserved for expansion - must be + * ZERO filled */ + }; + +/* framing types */ +#define FRAMING_M13 0 +#define FRAMING_CBP 1 + +/* card level loopback options */ +#define CFG_CARD_LOOPBACK_NONE 0x00 +#define CFG_CARD_LOOPBACK_DIAG 0x01 +#define CFG_CARD_LOOPBACK_LINE 0x02 +#define CFG_CARD_LOOPBACK_PAYLOAD 0x03 + +/* line level loopback options */ +#define CFG_LIU_LOOPBACK_NONE 0x00 +#define CFG_LIU_LOOPBACK_ANALOG 0x10 +#define CFG_LIU_LOOPBACK_DIGITAL 0x11 +#define CFG_LIU_LOOPBACK_REMOTE 0x12 + +/* card level clock options */ +#define CFG_CLK_INTERNAL 0x00 +#define CFG_CLK_EXTERNAL 0x01 + +/* legacy 256T3 loopback values */ +#define LOOPBACK_NONE 0 +#define LOOPBACK_LIU_ANALOG 1 +#define LOOPBACK_LIU_DIGITAL 2 +#define LOOPBACK_FRAMER_DS3 3 +#define LOOPBACK_FRAMER_T1 4 +#define LOOPBACK_LIU_REMOTE 5 + +/* DS1 mode */ +#define CFG_DS1_MODE_MASK 0x0f +#define CFG_DS1_MODE_T1 0x00 +#define CFG_DS1_MODE_E1 0x01 +#define CFG_DS1_MODE_CHANGE 0x80 + +/* DS3 unchannelized values */ +#define CFG_DS3_UNCHAN_MASK 0x01 +#define CFG_DS3_UNCHAN_OFF 0x00 +#define CFG_DS3_UNCHAN_ON 0x01 + + +/************************************/ +/** read write framer parameters **/ +/************************************/ + +/* routine/ioctl: wancfg_get_framer() - SBE_IOC_FRAMER_GET */ +/* routine/ioctl: wancfg_set_framer() - SBE_IOC_FRAMER_SET */ + + struct sbecom_framer_param + { + u_int8_t framer_num; + u_int8_t frame_type; /* SF, ESF, E1PLAIN, E1CAS, E1CRC, E1CRC+CAS */ + u_int8_t loopback_type; /* DIGITAL, LINE, PAYLOAD */ + u_int8_t auto_alarms;/* auto alarms */ + u_int8_t reserved[12]; /* reserved for expansion - must be + * ZERO filled */ + }; + +/* frame types */ +#define CFG_FRAME_NONE 0 +#define CFG_FRAME_SF 1 /* T1 B8ZS */ +#define CFG_FRAME_ESF 2 /* T1 B8ZS */ +#define CFG_FRAME_E1PLAIN 3 /* HDB3 w/o CAS,CRC */ +#define CFG_FRAME_E1CAS 4 /* HDB3 */ +#define CFG_FRAME_E1CRC 5 /* HDB3 */ +#define CFG_FRAME_E1CRC_CAS 6 /* HDB3 */ +#define CFG_FRAME_SF_AMI 7 /* T1 AMI */ +#define CFG_FRAME_ESF_AMI 8 /* T1 AMI */ +#define CFG_FRAME_E1PLAIN_AMI 9 /* E1 AMI w/o CAS,CRC */ +#define CFG_FRAME_E1CAS_AMI 10 /* E1 AMI */ +#define CFG_FRAME_E1CRC_AMI 11 /* E1 AMI */ +#define CFG_FRAME_E1CRC_CAS_AMI 12 /* E1 AMI */ + +#define IS_FRAME_ANY_T1(field) \ + (((field) == CFG_FRAME_NONE) || \ + ((field) == CFG_FRAME_SF) || \ + ((field) == CFG_FRAME_ESF) || \ + ((field) == CFG_FRAME_SF_AMI) || \ + ((field) == CFG_FRAME_ESF_AMI)) + +#define IS_FRAME_ANY_T1ESF(field) \ + (((field) == CFG_FRAME_ESF) || \ + ((field) == CFG_FRAME_ESF_AMI)) + +#define IS_FRAME_ANY_E1(field) \ + (((field) == CFG_FRAME_E1PLAIN) || \ + ((field) == CFG_FRAME_E1CAS) || \ + ((field) == CFG_FRAME_E1CRC) || \ + ((field) == CFG_FRAME_E1CRC_CAS) || \ + ((field) == CFG_FRAME_E1PLAIN_AMI) || \ + ((field) == CFG_FRAME_E1CAS_AMI) || \ + ((field) == CFG_FRAME_E1CRC_AMI) || \ + ((field) == CFG_FRAME_E1CRC_CAS_AMI)) + +#define IS_FRAME_ANY_AMI(field) \ + (((field) == CFG_FRAME_SF_AMI) || \ + ((field) == CFG_FRAME_ESF_AMI) || \ + ((field) == CFG_FRAME_E1PLAIN_AMI) || \ + ((field) == CFG_FRAME_E1CAS_AMI) || \ + ((field) == CFG_FRAME_E1CRC_AMI) || \ + ((field) == CFG_FRAME_E1CRC_CAS_AMI)) + +/* frame level loopback options */ +#define CFG_FRMR_LOOPBACK_NONE 0 +#define CFG_FRMR_LOOPBACK_DIAG 1 +#define CFG_FRMR_LOOPBACK_LINE 2 +#define CFG_FRMR_LOOPBACK_PAYLOAD 3 + + +/****************************************/ +/** read reset card error statistics **/ +/****************************************/ + +/* routine/ioctl: wancfg_get_card_stats() - SBE_IOC_CARD_GET_STAT */ +/* routine/ioctl: wancfg_del_card_stats() - SBE_IOC_CARD_DEL_STAT */ + + struct temux_card_stats + { + struct temux_stats + { + /* TEMUX DS3 PMON counters */ + u_int32_t lcv; + u_int32_t err_framing; + u_int32_t febe; + u_int32_t err_cpbit; + u_int32_t err_parity; + /* TEMUX DS3 FRMR status */ + u_int8_t los; + u_int8_t oof; + u_int8_t red; + u_int8_t yellow; + u_int8_t idle; + u_int8_t ais; + u_int8_t cbit; + /* TEMUX DS3 FEAC receiver */ + u_int8_t feac; + u_int8_t feac_last; + } t; + u_int32_t tx_pending; /* total */ + }; + +/**************************************************************/ + + struct wancfg + { + int cs, ds; + char *p; + }; + typedef struct wancfg wcfg_t; + + extern wcfg_t *wancfg_init (char *, char *); + extern int wancfg_card_blink (wcfg_t *, int); + extern int wancfg_ctl (wcfg_t *, int, void *, int, void *, int); + extern int wancfg_del_card_stats (wcfg_t *); + extern int wancfg_del_chan_stats (wcfg_t *, int); + extern int wancfg_enable_ports (wcfg_t *, int); + extern int wancfg_free (wcfg_t *); + extern int wancfg_get_brdaddr (wcfg_t *, struct sbe_brd_addr *); + extern int wancfg_get_brdinfo (wcfg_t *, struct sbe_brd_info *); + extern int wancfg_get_card (wcfg_t *, struct sbecom_card_param *); + extern int wancfg_get_card_chan_stats (wcfg_t *, struct sbecom_chan_stats *); + extern int wancfg_get_card_sn (wcfg_t *); + extern int wancfg_get_card_stats (wcfg_t *, struct temux_card_stats *); + extern int wancfg_get_chan (wcfg_t *, int, struct sbecom_chan_param *); + extern int wancfg_get_chan_stats (wcfg_t *, int, struct sbecom_chan_stats *); + extern int wancfg_get_drvinfo (wcfg_t *, int, struct sbe_drv_info *); + extern int wancfg_get_framer (wcfg_t *, int, struct sbecom_framer_param *); + extern int wancfg_get_iid (wcfg_t *, int, struct sbe_iid_info *); + extern int wancfg_get_sn (wcfg_t *, unsigned int *); + extern int wancfg_read (wcfg_t *, int, struct sbecom_wrt_vec *); + extern int wancfg_reset_device (wcfg_t *, int); + extern int wancfg_set_card (wcfg_t *, struct sbecom_card_param *); + extern int wancfg_set_chan (wcfg_t *, int, struct sbecom_chan_param *); + extern int wancfg_set_framer (wcfg_t *, int, struct sbecom_framer_param *); + extern int wancfg_set_loglevel (wcfg_t *, uint); + extern int wancfg_write (wcfg_t *, int, struct sbecom_wrt_vec *); + +#ifdef NOT_YET_COMMON + extern int wancfg_get_tsioc (wcfg_t *, struct wanc1t3_ts_hdr *, struct wanc1t3_ts_param *); + extern int wancfg_set_tsioc (wcfg_t *, struct wanc1t3_ts_param *); +#endif + +#endif /*** _INC_LIBSBEW_H_ ***/ diff --git a/drivers/staging/cxt1e1/linux.c b/drivers/staging/cxt1e1/linux.c new file mode 100644 index 00000000..911c0e43 --- /dev/null +++ b/drivers/staging/cxt1e1/linux.c @@ -0,0 +1,1188 @@ +/* Copyright (C) 2007-2008 One Stop Systems + * Copyright (C) 2003-2006 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmcc4_sysdep.h" +#include "sbecom_inline_linux.h" +#include "libsbew.h" +#include "pmcc4.h" +#include "pmcc4_ioctls.h" +#include "pmcc4_private.h" +#include "sbeproc.h" + +/***************************************************************************************** + * Error out early if we have compiler trouble. + * + * (This section is included from the kernel's init/main.c as a friendly + * spiderman recommendation...) + * + * Versions of gcc older than that listed below may actually compile and link + * okay, but the end product can have subtle run time bugs. To avoid associated + * bogus bug reports, we flatly refuse to compile with a gcc that is known to be + * too old from the very beginning. + */ +#if (__GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 2) +#error Sorry, your GCC is too old. It builds incorrect kernels. +#endif + +#if __GNUC__ == 4 && __GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ == 0 +#warning gcc-4.1.0 is known to miscompile the kernel. A different compiler version is recommended. +#endif + +/*****************************************************************************************/ + +#ifdef SBE_INCLUDE_SYMBOLS +#define STATIC +#else +#define STATIC static +#endif + +#define CHANNAME "hdlc" + +/*******************************************************************/ +/* forward references */ +status_t c4_chan_work_init (mpi_t *, mch_t *); +void musycc_wq_chan_restart (void *); +status_t __init c4_init (ci_t *, u_char *, u_char *); +status_t __init c4_init2 (ci_t *); +ci_t *__init c4_new (void *); +int __init c4hw_attach_all (void); +void __init hdw_sn_get (hdw_info_t *, int); + +#ifdef CONFIG_SBE_PMCC4_NCOMM +irqreturn_t c4_ebus_intr_th_handler (void *); + +#endif +int c4_frame_rw (ci_t *, struct sbecom_port_param *); +status_t c4_get_port (ci_t *, int); +int c4_loop_port (ci_t *, int, u_int8_t); +int c4_musycc_rw (ci_t *, struct c4_musycc_param *); +int c4_new_chan (ci_t *, int, int, void *); +status_t c4_set_port (ci_t *, int); +int c4_pld_rw (ci_t *, struct sbecom_port_param *); +void cleanup_devs (void); +void cleanup_ioremap (void); +status_t musycc_chan_down (ci_t *, int); +irqreturn_t musycc_intr_th_handler (void *); +int musycc_start_xmit (ci_t *, int, void *); + +extern char pmcc4_OSSI_release[]; +extern ci_t *CI; +extern struct s_hdw_info hdw_info[]; + +#if defined(CONFIG_SBE_HDLC_V7) || defined(CONFIG_SBE_WAN256T3_HDLC_V7) || \ + defined(CONFIG_SBE_HDLC_V7_MODULE) || defined(CONFIG_SBE_WAN256T3_HDLC_V7_MODULE) +#define _v7_hdlc_ 1 +#else +#define _v7_hdlc_ 0 +#endif + +#if _v7_hdlc_ +#define V7(x) (x ## _v7) +extern int hdlc_netif_rx_v7 (hdlc_device *, struct sk_buff *); +extern int register_hdlc_device_v7 (hdlc_device *); +extern int unregister_hdlc_device_v7 (hdlc_device *); + +#else +#define V7(x) x +#endif + +int error_flag; /* module load error reporting */ +int cxt1e1_log_level = LOG_ERROR; +int log_level_default = LOG_ERROR; +module_param(cxt1e1_log_level, int, 0444); + +int cxt1e1_max_mru = MUSYCC_MRU; +int max_mru_default = MUSYCC_MRU; +module_param(cxt1e1_max_mru, int, 0444); + +int cxt1e1_max_mtu = MUSYCC_MTU; +int max_mtu_default = MUSYCC_MTU; +module_param(cxt1e1_max_mtu, int, 0444); + +int max_txdesc_used = MUSYCC_TXDESC_MIN; +int max_txdesc_default = MUSYCC_TXDESC_MIN; +module_param(max_txdesc_used, int, 0444); + +int max_rxdesc_used = MUSYCC_RXDESC_MIN; +int max_rxdesc_default = MUSYCC_RXDESC_MIN; +module_param(max_rxdesc_used, int, 0444); + +/****************************************************************************/ +/****************************************************************************/ +/****************************************************************************/ + +void * +getuserbychan (int channum) +{ + mch_t *ch; + + ch = c4_find_chan (channum); + return ch ? ch->user : 0; +} + + +char * +get_hdlc_name (hdlc_device * hdlc) +{ + struct c4_priv *priv = hdlc->priv; + struct net_device *dev = getuserbychan (priv->channum); + + return dev->name; +} + + +static status_t +mkret (int bsd) +{ + if (bsd > 0) + return -bsd; + else + return bsd; +} + +/***************************************************************************/ +#include + +/*** + * One workqueue (wq) per port (since musycc allows simultaneous group + * commands), with individual data for each channel: + * + * mpi_t -> struct workqueue_struct *wq_port; (dynamically allocated using + * create_workqueue()) + * + * With work structure (work) statically allocated for each channel: + * + * mch_t -> struct work_struct ch_work; (statically allocated using ???) + * + ***/ + + +/* + * Called by the start transmit routine when a channel TX_ENABLE is to be + * issued. This queues the transmission start request among other channels + * within a port's group. + */ +void +c4_wk_chan_restart (mch_t * ch) +{ + mpi_t *pi = ch->up; + +#ifdef RLD_RESTART_DEBUG + pr_info(">> %s: queueing Port %d Chan %d, mch_t @ %p\n", + __func__, pi->portnum, ch->channum, ch); +#endif + + /* create new entry w/in workqueue for this channel and let'er rip */ + + /** queue_work (struct workqueue_struct *queue, + ** struct work_struct *work); + **/ + queue_work (pi->wq_port, &ch->ch_work); +} + +status_t +c4_wk_chan_init (mpi_t * pi, mch_t * ch) +{ + /* + * this will be used to restart a stopped channel + */ + + /** INIT_WORK (struct work_struct *work, + ** void (*function)(void *), + ** void *data); + **/ + INIT_WORK(&ch->ch_work, (void *)musycc_wq_chan_restart); + return 0; /* success */ +} + +status_t +c4_wq_port_init (mpi_t * pi) +{ + + char name[16], *np; /* NOTE: name of the queue limited by system + * to 10 characters */ + + if (pi->wq_port) + return 0; /* already initialized */ + + np = name; + memset (name, 0, 16); + sprintf (np, "%s%d", pi->up->devname, pi->portnum); /* IE pmcc4-01) */ + +#ifdef RLD_RESTART_DEBUG + pr_info(">> %s: creating workqueue <%s> for Port %d.\n", + __func__, name, pi->portnum); /* RLD DEBUG */ +#endif + if (!(pi->wq_port = create_singlethread_workqueue (name))) + return ENOMEM; + return 0; /* success */ +} + +void +c4_wq_port_cleanup (mpi_t * pi) +{ + /* + * PORT POINT: cannot call this if WQ is statically allocated w/in + * structure since it calls kfree(wq); + */ + if (pi->wq_port) + { + destroy_workqueue (pi->wq_port); /* this also calls + * flush_workqueue() */ + pi->wq_port = 0; + } +} + +/***************************************************************************/ + +irqreturn_t +c4_linux_interrupt (int irq, void *dev_instance) +{ + struct net_device *ndev = dev_instance; + + return musycc_intr_th_handler(netdev_priv(ndev)); +} + + +#ifdef CONFIG_SBE_PMCC4_NCOMM +irqreturn_t +c4_ebus_interrupt (int irq, void *dev_instance) +{ + struct net_device *ndev = dev_instance; + + return c4_ebus_intr_th_handler(netdev_priv(ndev)); +} +#endif + + +static int +void_open (struct net_device * ndev) +{ + pr_info("%s: trying to open master device !\n", ndev->name); + return -1; +} + + +STATIC int +chan_open (struct net_device * ndev) +{ + hdlc_device *hdlc = dev_to_hdlc (ndev); + const struct c4_priv *priv = hdlc->priv; + int ret; + + if ((ret = hdlc_open (ndev))) + { + pr_info("hdlc_open failure, err %d.\n", ret); + return ret; + } + if ((ret = c4_chan_up (priv->ci, priv->channum))) + return -ret; + try_module_get (THIS_MODULE); + netif_start_queue (ndev); + return 0; /* no error = success */ +} + + +STATIC int +chan_close (struct net_device * ndev) +{ + hdlc_device *hdlc = dev_to_hdlc (ndev); + const struct c4_priv *priv = hdlc->priv; + + netif_stop_queue (ndev); + musycc_chan_down ((ci_t *) 0, priv->channum); + hdlc_close (ndev); + module_put (THIS_MODULE); + return 0; +} + + +STATIC int +chan_dev_ioctl (struct net_device * dev, struct ifreq * ifr, int cmd) +{ + return hdlc_ioctl (dev, ifr, cmd); +} + + +STATIC int +chan_attach_noop (struct net_device * ndev, unsigned short foo_1, unsigned short foo_2) +{ + return 0; /* our driver has nothing to do here, show's + * over, go home */ +} + + +STATIC struct net_device_stats * +chan_get_stats (struct net_device * ndev) +{ + mch_t *ch; + struct net_device_stats *nstats; + struct sbecom_chan_stats *stats; + int channum; + + { + struct c4_priv *priv; + + priv = (struct c4_priv *) dev_to_hdlc (ndev)->priv; + channum = priv->channum; + } + + ch = c4_find_chan (channum); + if (ch == NULL) + return NULL; + + nstats = &ndev->stats; + stats = &ch->s; + + memset (nstats, 0, sizeof (struct net_device_stats)); + nstats->rx_packets = stats->rx_packets; + nstats->tx_packets = stats->tx_packets; + nstats->rx_bytes = stats->rx_bytes; + nstats->tx_bytes = stats->tx_bytes; + nstats->rx_errors = stats->rx_length_errors + + stats->rx_over_errors + + stats->rx_crc_errors + + stats->rx_frame_errors + + stats->rx_fifo_errors + + stats->rx_missed_errors; + nstats->tx_errors = stats->tx_dropped + + stats->tx_aborted_errors + + stats->tx_fifo_errors; + nstats->rx_dropped = stats->rx_dropped; + nstats->tx_dropped = stats->tx_dropped; + + nstats->rx_length_errors = stats->rx_length_errors; + nstats->rx_over_errors = stats->rx_over_errors; + nstats->rx_crc_errors = stats->rx_crc_errors; + nstats->rx_frame_errors = stats->rx_frame_errors; + nstats->rx_fifo_errors = stats->rx_fifo_errors; + nstats->rx_missed_errors = stats->rx_missed_errors; + + nstats->tx_aborted_errors = stats->tx_aborted_errors; + nstats->tx_fifo_errors = stats->tx_fifo_errors; + + return nstats; +} + + +static ci_t * +get_ci_by_dev (struct net_device * ndev) +{ + return (ci_t *)(netdev_priv(ndev)); +} + + +STATIC int +c4_linux_xmit (struct sk_buff * skb, struct net_device * ndev) +{ + const struct c4_priv *priv; + int rval; + + hdlc_device *hdlc = dev_to_hdlc (ndev); + + priv = hdlc->priv; + + rval = musycc_start_xmit (priv->ci, priv->channum, skb); + return -rval; +} + +static const struct net_device_ops chan_ops = { + .ndo_open = chan_open, + .ndo_stop = chan_close, + .ndo_start_xmit = c4_linux_xmit, + .ndo_do_ioctl = chan_dev_ioctl, + .ndo_get_stats = chan_get_stats, +}; + +STATIC struct net_device * +create_chan (struct net_device * ndev, ci_t * ci, + struct sbecom_chan_param * cp) +{ + hdlc_device *hdlc; + struct net_device *dev; + hdw_info_t *hi; + int ret; + + if (c4_find_chan (cp->channum)) + return 0; /* channel already exists */ + + { + struct c4_priv *priv; + + /* allocate then fill in private data structure */ + priv = OS_kmalloc (sizeof (struct c4_priv)); + if (!priv) + { + pr_warning("%s: no memory for net_device !\n", ci->devname); + return 0; + } + dev = alloc_hdlcdev (priv); + if (!dev) + { + pr_warning("%s: no memory for hdlc_device !\n", ci->devname); + OS_kfree (priv); + return 0; + } + priv->ci = ci; + priv->channum = cp->channum; + } + + hdlc = dev_to_hdlc (dev); + + dev->base_addr = 0; /* not I/O mapped */ + dev->irq = ndev->irq; + dev->type = ARPHRD_RAWHDLC; + *dev->name = 0; /* default ifconfig name = "hdlc" */ + + hi = (hdw_info_t *) ci->hdw_info; + if (hi->mfg_info_sts == EEPROM_OK) + { + switch (hi->promfmt) + { + case PROM_FORMAT_TYPE1: + memcpy (dev->dev_addr, (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6); + break; + case PROM_FORMAT_TYPE2: + memcpy (dev->dev_addr, (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6); + break; + default: + memset (dev->dev_addr, 0, 6); + break; + } + } else + { + memset (dev->dev_addr, 0, 6); + } + + hdlc->xmit = c4_linux_xmit; + + dev->netdev_ops = &chan_ops; + /* + * The native hdlc stack calls this 'attach' routine during + * hdlc_raw_ioctl(), passing parameters for line encoding and parity. + * Since hdlc_raw_ioctl() stack does not interrogate whether an 'attach' + * routine is actually registered or not, we supply a dummy routine which + * does nothing (since encoding and parity are setup for our driver via a + * special configuration application). + */ + + hdlc->attach = chan_attach_noop; + + rtnl_unlock (); /* needed due to Ioctl calling sequence */ + ret = register_hdlc_device (dev); + /* NOTE: setting must occur AFTER registration in order to "take" */ + dev->tx_queue_len = MAX_DEFAULT_IFQLEN; + + rtnl_lock (); /* needed due to Ioctl calling sequence */ + if (ret) + { + if (cxt1e1_log_level >= LOG_WARN) + pr_info("%s: create_chan[%d] registration error = %d.\n", + ci->devname, cp->channum, ret); + free_netdev (dev); /* cleanup */ + return 0; /* failed to register */ + } + return dev; +} + + +/* the idea here is to get port information and pass it back (using pointer) */ +STATIC status_t +do_get_port (struct net_device * ndev, void *data) +{ + int ret; + ci_t *ci; /* ci stands for card information */ + struct sbecom_port_param pp;/* copy data to kernel land */ + + if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) + return -EFAULT; + if (pp.portnum >= MUSYCC_NPORTS) + return -EFAULT; + ci = get_ci_by_dev (ndev); + if (!ci) + return -EINVAL; /* get card info */ + + ret = mkret (c4_get_port (ci, pp.portnum)); + if (ret) + return ret; + if (copy_to_user (data, &ci->port[pp.portnum].p, + sizeof (struct sbecom_port_param))) + return -EFAULT; + return 0; +} + +/* this function copys the user data and then calls the real action function */ +STATIC status_t +do_set_port (struct net_device * ndev, void *data) +{ + ci_t *ci; /* ci stands for card information */ + struct sbecom_port_param pp;/* copy data to kernel land */ + + if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) + return -EFAULT; + if (pp.portnum >= MUSYCC_NPORTS) + return -EFAULT; + ci = get_ci_by_dev (ndev); + if (!ci) + return -EINVAL; /* get card info */ + + if (pp.portnum >= ci->max_port) /* sanity check */ + return -ENXIO; + + memcpy (&ci->port[pp.portnum].p, &pp, sizeof (struct sbecom_port_param)); + return mkret (c4_set_port (ci, pp.portnum)); +} + +/* work the port loopback mode as per directed */ +STATIC status_t +do_port_loop (struct net_device * ndev, void *data) +{ + struct sbecom_port_param pp; + ci_t *ci; + + if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) + return -EFAULT; + ci = get_ci_by_dev (ndev); + if (!ci) + return -EINVAL; + return mkret (c4_loop_port (ci, pp.portnum, pp.port_mode)); +} + +/* set the specified register with the given value / or just read it */ +STATIC status_t +do_framer_rw (struct net_device * ndev, void *data) +{ + struct sbecom_port_param pp; + ci_t *ci; + int ret; + + if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) + return -EFAULT; + ci = get_ci_by_dev (ndev); + if (!ci) + return -EINVAL; + ret = mkret (c4_frame_rw (ci, &pp)); + if (ret) + return ret; + if (copy_to_user (data, &pp, sizeof (struct sbecom_port_param))) + return -EFAULT; + return 0; +} + +/* set the specified register with the given value / or just read it */ +STATIC status_t +do_pld_rw (struct net_device * ndev, void *data) +{ + struct sbecom_port_param pp; + ci_t *ci; + int ret; + + if (copy_from_user (&pp, data, sizeof (struct sbecom_port_param))) + return -EFAULT; + ci = get_ci_by_dev (ndev); + if (!ci) + return -EINVAL; + ret = mkret (c4_pld_rw (ci, &pp)); + if (ret) + return ret; + if (copy_to_user (data, &pp, sizeof (struct sbecom_port_param))) + return -EFAULT; + return 0; +} + +/* set the specified register with the given value / or just read it */ +STATIC status_t +do_musycc_rw (struct net_device * ndev, void *data) +{ + struct c4_musycc_param mp; + ci_t *ci; + int ret; + + if (copy_from_user (&mp, data, sizeof (struct c4_musycc_param))) + return -EFAULT; + ci = get_ci_by_dev (ndev); + if (!ci) + return -EINVAL; + ret = mkret (c4_musycc_rw (ci, &mp)); + if (ret) + return ret; + if (copy_to_user (data, &mp, sizeof (struct c4_musycc_param))) + return -EFAULT; + return 0; +} + +STATIC status_t +do_get_chan (struct net_device * ndev, void *data) +{ + struct sbecom_chan_param cp; + int ret; + + if (copy_from_user (&cp, data, + sizeof (struct sbecom_chan_param))) + return -EFAULT; + + if ((ret = mkret (c4_get_chan (cp.channum, &cp)))) + return ret; + + if (copy_to_user (data, &cp, sizeof (struct sbecom_chan_param))) + return -EFAULT; + return 0; +} + +STATIC status_t +do_set_chan (struct net_device * ndev, void *data) +{ + struct sbecom_chan_param cp; + int ret; + ci_t *ci; + + if (copy_from_user (&cp, data, sizeof (struct sbecom_chan_param))) + return -EFAULT; + ci = get_ci_by_dev (ndev); + if (!ci) + return -EINVAL; + switch (ret = mkret (c4_set_chan (cp.channum, &cp))) + { + case 0: + return 0; + default: + return ret; + } +} + +STATIC status_t +do_create_chan (struct net_device * ndev, void *data) +{ + ci_t *ci; + struct net_device *dev; + struct sbecom_chan_param cp; + int ret; + + if (copy_from_user (&cp, data, sizeof (struct sbecom_chan_param))) + return -EFAULT; + ci = get_ci_by_dev (ndev); + if (!ci) + return -EINVAL; + dev = create_chan (ndev, ci, &cp); + if (!dev) + return -EBUSY; + ret = mkret (c4_new_chan (ci, cp.port, cp.channum, dev)); + if (ret) + { + rtnl_unlock (); /* needed due to Ioctl calling sequence */ + unregister_hdlc_device (dev); + rtnl_lock (); /* needed due to Ioctl calling sequence */ + free_netdev (dev); + } + return ret; +} + +STATIC status_t +do_get_chan_stats (struct net_device * ndev, void *data) +{ + struct c4_chan_stats_wrap ccs; + int ret; + + if (copy_from_user (&ccs, data, + sizeof (struct c4_chan_stats_wrap))) + return -EFAULT; + switch (ret = mkret (c4_get_chan_stats (ccs.channum, &ccs.stats))) + { + case 0: + break; + default: + return ret; + } + if (copy_to_user (data, &ccs, + sizeof (struct c4_chan_stats_wrap))) + return -EFAULT; + return 0; +} +STATIC status_t +do_set_loglevel (struct net_device * ndev, void *data) +{ + unsigned int cxt1e1_log_level; + + if (copy_from_user (&cxt1e1_log_level, data, sizeof (int))) + return -EFAULT; + sbecom_set_loglevel (cxt1e1_log_level); + return 0; +} + +STATIC status_t +do_deluser (struct net_device * ndev, int lockit) +{ + if (ndev->flags & IFF_UP) + return -EBUSY; + + { + ci_t *ci; + mch_t *ch; + const struct c4_priv *priv; + int channum; + + priv = (struct c4_priv *) dev_to_hdlc (ndev)->priv; + ci = priv->ci; + channum = priv->channum; + + ch = c4_find_chan (channum); + if (ch == NULL) + return -ENOENT; + ch->user = 0; /* will be freed, below */ + } + + if (lockit) + rtnl_unlock (); /* needed if Ioctl calling sequence */ + unregister_hdlc_device (ndev); + if (lockit) + rtnl_lock (); /* needed if Ioctl calling sequence */ + free_netdev (ndev); + return 0; +} + +int +do_del_chan (struct net_device * musycc_dev, void *data) +{ + struct sbecom_chan_param cp; + char buf[sizeof (CHANNAME) + 3]; + struct net_device *dev; + int ret; + + if (copy_from_user (&cp, data, + sizeof (struct sbecom_chan_param))) + return -EFAULT; + sprintf (buf, CHANNAME "%d", cp.channum); + if (!(dev = dev_get_by_name (&init_net, buf))) + return -ENOENT; + dev_put (dev); + ret = do_deluser (dev, 1); + if (ret) + return ret; + return c4_del_chan (cp.channum); +} +int c4_reset_board (void *); + +int +do_reset (struct net_device * musycc_dev, void *data) +{ + const struct c4_priv *priv; + int i; + + for (i = 0; i < 128; i++) + { + struct net_device *ndev; + char buf[sizeof (CHANNAME) + 3]; + + sprintf (buf, CHANNAME "%d", i); + if (!(ndev = dev_get_by_name(&init_net, buf))) + continue; + priv = dev_to_hdlc (ndev)->priv; + + if ((unsigned long) (priv->ci) == + (unsigned long) (netdev_priv(musycc_dev))) + { + ndev->flags &= ~IFF_UP; + dev_put (ndev); + netif_stop_queue (ndev); + do_deluser (ndev, 1); + } else + dev_put (ndev); + } + return 0; +} + +int +do_reset_chan_stats (struct net_device * musycc_dev, void *data) +{ + struct sbecom_chan_param cp; + + if (copy_from_user (&cp, data, + sizeof (struct sbecom_chan_param))) + return -EFAULT; + return mkret (c4_del_chan_stats (cp.channum)); +} + +STATIC status_t +c4_ioctl (struct net_device * ndev, struct ifreq * ifr, int cmd) +{ + ci_t *ci; + void *data; + int iocmd, iolen; + status_t ret; + static struct data + { + union + { + u_int8_t c; + u_int32_t i; + struct sbe_brd_info bip; + struct sbe_drv_info dip; + struct sbe_iid_info iip; + struct sbe_brd_addr bap; + struct sbecom_chan_stats stats; + struct sbecom_chan_param param; + struct temux_card_stats cards; + struct sbecom_card_param cardp; + struct sbecom_framer_param frp; + } u; + } arg; + + + if (!capable (CAP_SYS_ADMIN)) + return -EPERM; + if (cmd != SIOCDEVPRIVATE + 15) + return -EINVAL; + if (!(ci = get_ci_by_dev (ndev))) + return -EINVAL; + if (ci->state != C_RUNNING) + return -ENODEV; + if (copy_from_user (&iocmd, ifr->ifr_data, sizeof (iocmd))) + return -EFAULT; +#if 0 + if (copy_from_user (&len, ifr->ifr_data + sizeof (iocmd), sizeof (len))) + return -EFAULT; +#endif + +#if 0 + pr_info("c4_ioctl: iocmd %x, dir %x type %x nr %x iolen %d.\n", iocmd, + _IOC_DIR (iocmd), _IOC_TYPE (iocmd), _IOC_NR (iocmd), + _IOC_SIZE (iocmd)); +#endif + iolen = _IOC_SIZE (iocmd); + data = ifr->ifr_data + sizeof (iocmd); + if (copy_from_user (&arg, data, iolen)) + return -EFAULT; + + ret = 0; + switch (iocmd) + { + case SBE_IOC_PORT_GET: + //pr_info(">> SBE_IOC_PORT_GET Ioctl...\n"); + ret = do_get_port (ndev, data); + break; + case SBE_IOC_PORT_SET: + //pr_info(">> SBE_IOC_PORT_SET Ioctl...\n"); + ret = do_set_port (ndev, data); + break; + case SBE_IOC_CHAN_GET: + //pr_info(">> SBE_IOC_CHAN_GET Ioctl...\n"); + ret = do_get_chan (ndev, data); + break; + case SBE_IOC_CHAN_SET: + //pr_info(">> SBE_IOC_CHAN_SET Ioctl...\n"); + ret = do_set_chan (ndev, data); + break; + case C4_DEL_CHAN: + //pr_info(">> C4_DEL_CHAN Ioctl...\n"); + ret = do_del_chan (ndev, data); + break; + case SBE_IOC_CHAN_NEW: + ret = do_create_chan (ndev, data); + break; + case SBE_IOC_CHAN_GET_STAT: + ret = do_get_chan_stats (ndev, data); + break; + case SBE_IOC_LOGLEVEL: + ret = do_set_loglevel (ndev, data); + break; + case SBE_IOC_RESET_DEV: + ret = do_reset (ndev, data); + break; + case SBE_IOC_CHAN_DEL_STAT: + ret = do_reset_chan_stats (ndev, data); + break; + case C4_LOOP_PORT: + ret = do_port_loop (ndev, data); + break; + case C4_RW_FRMR: + ret = do_framer_rw (ndev, data); + break; + case C4_RW_MSYC: + ret = do_musycc_rw (ndev, data); + break; + case C4_RW_PLD: + ret = do_pld_rw (ndev, data); + break; + case SBE_IOC_IID_GET: + ret = (iolen == sizeof (struct sbe_iid_info)) ? c4_get_iidinfo (ci, &arg.u.iip) : -EFAULT; + if (ret == 0) /* no error, copy data */ + if (copy_to_user (data, &arg, iolen)) + return -EFAULT; + break; + default: + //pr_info(">> c4_ioctl: EINVAL - unknown iocmd <%x>\n", iocmd); + ret = -EINVAL; + break; + } + return mkret (ret); +} + +static const struct net_device_ops c4_ops = { + .ndo_open = void_open, + .ndo_start_xmit = c4_linux_xmit, + .ndo_do_ioctl = c4_ioctl, +}; + +static void c4_setup(struct net_device *dev) +{ + dev->type = ARPHRD_VOID; + dev->netdev_ops = &c4_ops; +} + +struct net_device *__init +c4_add_dev (hdw_info_t * hi, int brdno, unsigned long f0, unsigned long f1, + int irq0, int irq1) +{ + struct net_device *ndev; + ci_t *ci; + + ndev = alloc_netdev(sizeof(ci_t), SBE_IFACETMPL, c4_setup); + if (!ndev) + { + pr_warning("%s: no memory for struct net_device !\n", hi->devname); + error_flag = ENOMEM; + return 0; + } + ci = (ci_t *)(netdev_priv(ndev)); + ndev->irq = irq0; + + ci->hdw_info = hi; + ci->state = C_INIT; /* mark as hardware not available */ + ci->next = c4_list; + c4_list = ci; + ci->brdno = ci->next ? ci->next->brdno + 1 : 0; + + if (CI == 0) + CI = ci; /* DEBUG, only board 0 usage */ + + strcpy (ci->devname, hi->devname); + ci->release = &pmcc4_OSSI_release[0]; + + /* tasklet */ +#if defined(SBE_ISR_TASKLET) + tasklet_init (&ci->ci_musycc_isr_tasklet, + (void (*) (unsigned long)) musycc_intr_bh_tasklet, + (unsigned long) ci); + + if (atomic_read (&ci->ci_musycc_isr_tasklet.count) == 0) + tasklet_disable_nosync (&ci->ci_musycc_isr_tasklet); +#elif defined(SBE_ISR_IMMEDIATE) + ci->ci_musycc_isr_tq.routine = (void *) (unsigned long) musycc_intr_bh_tasklet; + ci->ci_musycc_isr_tq.data = ci; +#endif + + + if (register_netdev (ndev) || + (c4_init (ci, (u_char *) f0, (u_char *) f1) != SBE_DRVR_SUCCESS)) + { + OS_kfree (netdev_priv(ndev)); + OS_kfree (ndev); + error_flag = ENODEV; + return 0; + } + /************************************************************* + * int request_irq(unsigned int irq, + * void (*handler)(int, void *, struct pt_regs *), + * unsigned long flags, const char *dev_name, void *dev_id); + * wherein: + * irq -> The interrupt number that is being requested. + * handler -> Pointer to handling function being installed. + * flags -> A bit mask of options related to interrupt management. + * dev_name -> String used in /proc/interrupts to show owner of interrupt. + * dev_id -> Pointer (for shared interrupt lines) to point to its own + * private data area (to identify which device is interrupting). + * + * extern void free_irq(unsigned int irq, void *dev_id); + **************************************************************/ + + if (request_irq (irq0, &c4_linux_interrupt, + IRQF_SHARED, + ndev->name, ndev)) + { + pr_warning("%s: MUSYCC could not get irq: %d\n", ndev->name, irq0); + unregister_netdev (ndev); + OS_kfree (netdev_priv(ndev)); + OS_kfree (ndev); + error_flag = EIO; + return 0; + } +#ifdef CONFIG_SBE_PMCC4_NCOMM + if (request_irq (irq1, &c4_ebus_interrupt, IRQF_SHARED, ndev->name, ndev)) + { + pr_warning("%s: EBUS could not get irq: %d\n", hi->devname, irq1); + unregister_netdev (ndev); + free_irq (irq0, ndev); + OS_kfree (netdev_priv(ndev)); + OS_kfree (ndev); + error_flag = EIO; + return 0; + } +#endif + + /* setup board identification information */ + + { + u_int32_t tmp; + + hdw_sn_get (hi, brdno); /* also sets PROM format type (promfmt) + * for later usage */ + + switch (hi->promfmt) + { + case PROM_FORMAT_TYPE1: + memcpy (ndev->dev_addr, (FLD_TYPE1 *) (hi->mfg_info.pft1.Serial), 6); + memcpy (&tmp, (FLD_TYPE1 *) (hi->mfg_info.pft1.Id), 4); /* unaligned data + * acquisition */ + ci->brd_id = cpu_to_be32 (tmp); + break; + case PROM_FORMAT_TYPE2: + memcpy (ndev->dev_addr, (FLD_TYPE2 *) (hi->mfg_info.pft2.Serial), 6); + memcpy (&tmp, (FLD_TYPE2 *) (hi->mfg_info.pft2.Id), 4); /* unaligned data + * acquisition */ + ci->brd_id = cpu_to_be32 (tmp); + break; + default: + ci->brd_id = 0; + memset (ndev->dev_addr, 0, 6); + break; + } + +#if 1 + sbeid_set_hdwbid (ci); /* requires bid to be preset */ +#else + sbeid_set_bdtype (ci); /* requires hdw_bid to be preset */ +#endif + + } + +#ifdef CONFIG_PROC_FS + sbecom_proc_brd_init (ci); +#endif +#if defined(SBE_ISR_TASKLET) + tasklet_enable (&ci->ci_musycc_isr_tasklet); +#endif + + + if ((error_flag = c4_init2 (ci)) != SBE_DRVR_SUCCESS) + { +#ifdef CONFIG_PROC_FS + sbecom_proc_brd_cleanup (ci); +#endif + unregister_netdev (ndev); + free_irq (irq1, ndev); + free_irq (irq0, ndev); + OS_kfree (netdev_priv(ndev)); + OS_kfree (ndev); + return 0; /* failure, error_flag is set */ + } + return ndev; +} + +STATIC int __init +c4_mod_init (void) +{ + int rtn; + + pr_warning("%s\n", pmcc4_OSSI_release); + if ((rtn = c4hw_attach_all ())) + return -rtn; /* installation failure - see system log */ + + /* housekeeping notifications */ + if (cxt1e1_log_level != log_level_default) + pr_info("NOTE: driver parameter changed from default %d to %d.\n", + log_level_default, cxt1e1_log_level); + if (cxt1e1_max_mru != max_mru_default) + pr_info("NOTE: driver parameter changed from default %d to %d.\n", + max_mru_default, cxt1e1_max_mru); + if (cxt1e1_max_mtu != max_mtu_default) + pr_info("NOTE: driver parameter changed from default %d to %d.\n", + max_mtu_default, cxt1e1_max_mtu); + if (max_rxdesc_used != max_rxdesc_default) + { + if (max_rxdesc_used > 2000) + max_rxdesc_used = 2000; /* out-of-bounds reset */ + pr_info("NOTE: driver parameter changed from default %d to %d.\n", + max_rxdesc_default, max_rxdesc_used); + } + if (max_txdesc_used != max_txdesc_default) + { + if (max_txdesc_used > 1000) + max_txdesc_used = 1000; /* out-of-bounds reset */ + pr_info("NOTE: driver parameter changed from default %d to %d.\n", + max_txdesc_default, max_txdesc_used); + } + return 0; /* installation success */ +} + + + /* + * find any still allocated hdlc registrations and unregister via call to + * do_deluser() + */ + +STATIC void __exit +cleanup_hdlc (void) +{ + hdw_info_t *hi; + ci_t *ci; + struct net_device *ndev; + int i, j, k; + + for (i = 0, hi = hdw_info; i < MAX_BOARDS; i++, hi++) + { + if (hi->ndev) /* a board has been attached */ + { + ci = (ci_t *)(netdev_priv(hi->ndev)); + for (j = 0; j < ci->max_port; j++) + for (k = 0; k < MUSYCC_NCHANS; k++) + if ((ndev = ci->port[j].chan[k]->user)) + { + do_deluser (ndev, 0); + } + } + } +} + + +STATIC void __exit +c4_mod_remove (void) +{ + cleanup_hdlc (); /* delete any missed channels */ + cleanup_devs (); + c4_cleanup (); + cleanup_ioremap (); + pr_info("SBE - driver removed.\n"); +} + +module_init (c4_mod_init); +module_exit (c4_mod_remove); + +MODULE_AUTHOR ("SBE Technical Services "); +MODULE_DESCRIPTION ("wanPCI-CxT1E1 Generic HDLC WAN Driver module"); +#ifdef MODULE_LICENSE +MODULE_LICENSE ("GPL"); +#endif + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/musycc.c b/drivers/staging/cxt1e1/musycc.c new file mode 100644 index 00000000..90c0f1e3 --- /dev/null +++ b/drivers/staging/cxt1e1/musycc.c @@ -0,0 +1,1932 @@ +unsigned int max_intcnt = 0; +unsigned int max_bh = 0; + +/*----------------------------------------------------------------------------- + * musycc.c - + * + * Copyright (C) 2007 One Stop Systems, Inc. + * Copyright (C) 2003-2006 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@onestopsystems.com + * One Stop Systems, Inc. Escondido, California U.S.A. + *----------------------------------------------------------------------------- + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include "pmcc4_sysdep.h" +#include +#include +#include +#include "sbecom_inline_linux.h" +#include "libsbew.h" +#include "pmcc4_private.h" +#include "pmcc4.h" +#include "musycc.h" + +#ifdef SBE_INCLUDE_SYMBOLS +#define STATIC +#else +#define STATIC static +#endif + +#define sd_find_chan(ci,ch) c4_find_chan(ch) + + +/*******************************************************************/ +/* global driver variables */ +extern ci_t *c4_list; +extern int drvr_state; +extern int cxt1e1_log_level; + +extern int cxt1e1_max_mru; +extern int cxt1e1_max_mtu; +extern int max_rxdesc_used; +extern int max_txdesc_used; +extern ci_t *CI; /* dummy pointr to board ZEROE's data - DEBUG + * USAGE */ + + +/*******************************************************************/ +/* forward references */ +void c4_fifo_free (mpi_t *, int); +void c4_wk_chan_restart (mch_t *); +void musycc_bh_tx_eom (mpi_t *, int); +int musycc_chan_up (ci_t *, int); +status_t __init musycc_init (ci_t *); +STATIC void __init musycc_init_port (mpi_t *); +void musycc_intr_bh_tasklet (ci_t *); +void musycc_serv_req (mpi_t *, u_int32_t); +void musycc_update_timeslots (mpi_t *); + +/*******************************************************************/ + +#if 1 +STATIC int +musycc_dump_rxbuffer_ring (mch_t * ch, int lockit) +{ + struct mdesc *m; + unsigned long flags = 0; + + u_int32_t status; + int n; + + if (lockit) + { + spin_lock_irqsave (&ch->ch_rxlock, flags); + } + if (ch->rxd_num == 0) + { + pr_info(" ZERO receive buffers allocated for this channel."); + } else + { + FLUSH_MEM_READ (); + m = &ch->mdr[ch->rxix_irq_srv]; + for (n = ch->rxd_num; n; n--) + { + status = le32_to_cpu (m->status); + { + pr_info("%c %08lx[%2d]: sts %08x (%c%c%c%c:%d.) Data [%08x] Next [%08x]\n", + (m == &ch->mdr[ch->rxix_irq_srv]) ? 'F' : ' ', + (unsigned long) m, n, + status, + m->data ? (status & HOST_RX_OWNED ? 'H' : 'M') : '-', + status & POLL_DISABLED ? 'P' : '-', + status & EOBIRQ_ENABLE ? 'b' : '-', + status & EOMIRQ_ENABLE ? 'm' : '-', + status & LENGTH_MASK, + le32_to_cpu (m->data), le32_to_cpu (m->next)); +#ifdef RLD_DUMP_BUFDATA + { + u_int32_t *dp; + int len = status & LENGTH_MASK; + +#if 1 + if (m->data && (status & HOST_RX_OWNED)) +#else + if (m->data) /* always dump regardless of valid RX + * data */ +#endif + { + dp = (u_int32_t *) OS_phystov ((void *) (le32_to_cpu (m->data))); + if (len >= 0x10) + pr_info(" %x[%x]: %08X %08X %08X %08x\n", (u_int32_t) dp, len, + *dp, *(dp + 1), *(dp + 2), *(dp + 3)); + else if (len >= 0x08) + pr_info(" %x[%x]: %08X %08X\n", (u_int32_t) dp, len, + *dp, *(dp + 1)); + else + pr_info(" %x[%x]: %08X\n", (u_int32_t) dp, len, *dp); + } + } +#endif + } + m = m->snext; + } + } /* -for- */ + pr_info("\n"); + + if (lockit) + { + spin_unlock_irqrestore (&ch->ch_rxlock, flags); + } + return 0; +} +#endif + +#if 1 +STATIC int +musycc_dump_txbuffer_ring (mch_t * ch, int lockit) +{ + struct mdesc *m; + unsigned long flags = 0; + u_int32_t status; + int n; + + if (lockit) + { + spin_lock_irqsave (&ch->ch_txlock, flags); + } + if (ch->txd_num == 0) + { + pr_info(" ZERO transmit buffers allocated for this channel."); + } else + { + FLUSH_MEM_READ (); + m = ch->txd_irq_srv; + for (n = ch->txd_num; n; n--) + { + status = le32_to_cpu (m->status); + { + pr_info("%c%c %08lx[%2d]: sts %08x (%c%c%c%c:%d.) Data [%08x] Next [%08x]\n", + (m == ch->txd_usr_add) ? 'F' : ' ', + (m == ch->txd_irq_srv) ? 'L' : ' ', + (unsigned long) m, n, + status, + m->data ? (status & MUSYCC_TX_OWNED ? 'M' : 'H') : '-', + status & POLL_DISABLED ? 'P' : '-', + status & EOBIRQ_ENABLE ? 'b' : '-', + status & EOMIRQ_ENABLE ? 'm' : '-', + status & LENGTH_MASK, + le32_to_cpu (m->data), le32_to_cpu (m->next)); +#ifdef RLD_DUMP_BUFDATA + { + u_int32_t *dp; + int len = status & LENGTH_MASK; + + if (m->data) + { + dp = (u_int32_t *) OS_phystov ((void *) (le32_to_cpu (m->data))); + if (len >= 0x10) + pr_info(" %x[%x]: %08X %08X %08X %08x\n", (u_int32_t) dp, len, + *dp, *(dp + 1), *(dp + 2), *(dp + 3)); + else if (len >= 0x08) + pr_info(" %x[%x]: %08X %08X\n", (u_int32_t) dp, len, + *dp, *(dp + 1)); + else + pr_info(" %x[%x]: %08X\n", (u_int32_t) dp, len, *dp); + } + } +#endif + } + m = m->snext; + } + } /* -for- */ + pr_info("\n"); + + if (lockit) + { + spin_unlock_irqrestore (&ch->ch_txlock, flags); + } + return 0; +} +#endif + + +/* + * The following supports a backdoor debug facility which can be used to + * display the state of a board's channel. + */ + +status_t +musycc_dump_ring (ci_t * ci, unsigned int chan) +{ + mch_t *ch; + + if (chan >= MAX_CHANS_USED) + { + return SBE_DRVR_FAIL; /* E2BIG */ + } + { + int bh; + + bh = atomic_read (&ci->bh_pending); + pr_info(">> bh_pend %d [%d] ihead %d itail %d [%d] th_cnt %d bh_cnt %d wdcnt %d note %d\n", + bh, max_bh, ci->iqp_headx, ci->iqp_tailx, max_intcnt, + ci->intlog.drvr_intr_thcount, + ci->intlog.drvr_intr_bhcount, + ci->wdcount, ci->wd_notify); + max_bh = 0; /* reset counter */ + max_intcnt = 0; /* reset counter */ + } + + if (!(ch = sd_find_chan (dummy, chan))) + { + pr_info(">> musycc_dump_ring: channel %d not up.\n", chan); + return ENOENT; + } + pr_info(">> CI %p CHANNEL %3d @ %p: state %x status/p %x/%x\n", ci, chan, ch, ch->state, + ch->status, ch->p.status); + pr_info("--------------------------------\nTX Buffer Ring - Channel %d, txd_num %d. (bd/ch pend %d %d), TXD required %d, txpkt %lu\n", + chan, ch->txd_num, + (u_int32_t) atomic_read (&ci->tx_pending), (u_int32_t) atomic_read (&ch->tx_pending), ch->txd_required, ch->s.tx_packets); + pr_info("++ User 0x%p IRQ_SRV 0x%p USR_ADD 0x%p QStopped %x, start_tx %x tx_full %d txd_free %d mode %x\n", + ch->user, ch->txd_irq_srv, ch->txd_usr_add, + sd_queue_stopped (ch->user), + ch->ch_start_tx, ch->tx_full, ch->txd_free, ch->p.chan_mode); + musycc_dump_txbuffer_ring (ch, 1); + pr_info("RX Buffer Ring - Channel %d, rxd_num %d. IRQ_SRV[%d] 0x%p, start_rx %x rxpkt %lu\n", + chan, ch->rxd_num, ch->rxix_irq_srv, + &ch->mdr[ch->rxix_irq_srv], ch->ch_start_rx, ch->s.rx_packets); + musycc_dump_rxbuffer_ring (ch, 1); + + return SBE_DRVR_SUCCESS; +} + + +status_t +musycc_dump_rings (ci_t * ci, unsigned int start_chan) +{ + unsigned int chan; + + for (chan = start_chan; chan < (start_chan + 5); chan++) + musycc_dump_ring (ci, chan); + return SBE_DRVR_SUCCESS; +} + + +/* + * NOTE on musycc_init_mdt(): These MUSYCC writes are only operational after + * a MUSYCC GROUP_INIT command has been issued. + */ + +void +musycc_init_mdt (mpi_t * pi) +{ + u_int32_t *addr, cfg; + int i; + + /* + * This Idle Code insertion takes effect prior to channel's first + * transmitted message. After that, each message contains its own Idle + * Code information which is to be issued after the message is + * transmitted (Ref.MUSYCC 5.2.2.3: MCENBL bit in Group Configuration + * Descriptor). + */ + + addr = (u_int32_t *) ((u_long) pi->reg + MUSYCC_MDT_BASE03_ADDR); + cfg = CFG_CH_FLAG_7E << IDLE_CODE; + + for (i = 0; i < 32; addr++, i++) + { + pci_write_32 (addr, cfg); + } +} + + +/* Set TX thp to the next unprocessed md */ + +void +musycc_update_tx_thp (mch_t * ch) +{ + struct mdesc *md; + unsigned long flags; + + spin_lock_irqsave (&ch->ch_txlock, flags); + while (1) + { + md = ch->txd_irq_srv; + FLUSH_MEM_READ (); + if (!md->data) + { + /* No MDs with buffers to process */ + spin_unlock_irqrestore (&ch->ch_txlock, flags); + return; + } + if ((le32_to_cpu (md->status)) & MUSYCC_TX_OWNED) + { + /* this is the MD to restart TX with */ + break; + } + /* + * Otherwise, we have a valid, host-owned message descriptor which + * has been successfully transmitted and whose buffer can be freed, + * so... process this MD, it's owned by the host. (This might give + * as a new, updated txd_irq_srv.) + */ + musycc_bh_tx_eom (ch->up, ch->gchan); + } + md = ch->txd_irq_srv; + ch->up->regram->thp[ch->gchan] = cpu_to_le32 (OS_vtophys (md)); + FLUSH_MEM_WRITE (); + + if (ch->tx_full) + { + ch->tx_full = 0; + ch->txd_required = 0; + sd_enable_xmit (ch->user); /* re-enable to catch flow controlled + * channel */ + } + spin_unlock_irqrestore (&ch->ch_txlock, flags); + +#ifdef RLD_TRANS_DEBUG + pr_info("++ musycc_update_tx_thp[%d]: setting thp = %p, sts %x\n", ch->channum, md, md->status); +#endif +} + + +/* + * This is the workq task executed by the OS when our queue_work() is + * scheduled and run. It can fire off either RX or TX ACTIVATION depending + * upon the channel's ch_start_tx and ch_start_rx variables. This routine + * is implemented as a work queue so that the call to the service request is + * able to sleep, awaiting an interrupt acknowledgment response (SACK) from + * the hardware. + */ + +void +musycc_wq_chan_restart (void *arg) /* channel private structure */ +{ + mch_t *ch; + mpi_t *pi; + struct mdesc *md; +#if 0 + unsigned long flags; +#endif + + ch = container_of(arg, struct c4_chan_info, ch_work); + pi = ch->up; + +#ifdef RLD_TRANS_DEBUG + pr_info("wq_chan_restart[%d]: start_RT[%d/%d] status %x\n", + ch->channum, ch->ch_start_rx, ch->ch_start_tx, ch->status); + +#endif + + /**********************************/ + /** check for RX restart request **/ + /**********************************/ + + if ((ch->ch_start_rx) && (ch->status & RX_ENABLED)) + { + + ch->ch_start_rx = 0; +#if defined(RLD_TRANS_DEBUG) || defined(RLD_RXACT_DEBUG) + { + static int hereb4 = 7; + + if (hereb4) /* RLD DEBUG */ + { + hereb4--; +#ifdef RLD_TRANS_DEBUG + md = &ch->mdr[ch->rxix_irq_srv]; + pr_info("++ musycc_wq_chan_restart[%d] CHAN RX ACTIVATE: rxix_irq_srv %d, md %p sts %x, rxpkt %lu\n", + ch->channum, ch->rxix_irq_srv, md, le32_to_cpu (md->status), + ch->s.rx_packets); +#elif defined(RLD_RXACT_DEBUG) + md = &ch->mdr[ch->rxix_irq_srv]; + pr_info("++ musycc_wq_chan_restart[%d] CHAN RX ACTIVATE: rxix_irq_srv %d, md %p sts %x, rxpkt %lu\n", + ch->channum, ch->rxix_irq_srv, md, le32_to_cpu (md->status), + ch->s.rx_packets); + musycc_dump_rxbuffer_ring (ch, 1); /* RLD DEBUG */ +#endif + } + } +#endif + musycc_serv_req (pi, SR_CHANNEL_ACTIVATE | SR_RX_DIRECTION | ch->gchan); + } + /**********************************/ + /** check for TX restart request **/ + /**********************************/ + + if ((ch->ch_start_tx) && (ch->status & TX_ENABLED)) + { + /* find next unprocessed message, then set TX thp to it */ + musycc_update_tx_thp (ch); + +#if 0 + spin_lock_irqsave (&ch->ch_txlock, flags); +#endif + md = ch->txd_irq_srv; + if (!md) + { +#ifdef RLD_TRANS_DEBUG + pr_info("-- musycc_wq_chan_restart[%d]: WARNING, starting NULL md\n", ch->channum); +#endif +#if 0 + spin_unlock_irqrestore (&ch->ch_txlock, flags); +#endif + } else if (md->data && ((le32_to_cpu (md->status)) & MUSYCC_TX_OWNED)) + { + ch->ch_start_tx = 0; +#if 0 + spin_unlock_irqrestore (&ch->ch_txlock, flags); /* allow interrupts for service request */ +#endif +#ifdef RLD_TRANS_DEBUG + pr_info("++ musycc_wq_chan_restart() CHAN TX ACTIVATE: chan %d txd_irq_srv %p = sts %x, txpkt %lu\n", + ch->channum, ch->txd_irq_srv, ch->txd_irq_srv->status, ch->s.tx_packets); +#endif + musycc_serv_req (pi, SR_CHANNEL_ACTIVATE | SR_TX_DIRECTION | ch->gchan); + } +#ifdef RLD_RESTART_DEBUG + else + { + /* retain request to start until retried and we have data to xmit */ + pr_info("-- musycc_wq_chan_restart[%d]: DELAYED due to md %p sts %x data %x, start_tx %x\n", + ch->channum, md, + le32_to_cpu (md->status), + le32_to_cpu (md->data), ch->ch_start_tx); + musycc_dump_txbuffer_ring (ch, 0); +#if 0 + spin_unlock_irqrestore (&ch->ch_txlock, flags); /* allow interrupts for service request */ +#endif + } +#endif + } +} + + + /* + * Channel restart either fires of a workqueue request (2.6) or lodges a + * watchdog activation sequence (2.4). + */ + +void +musycc_chan_restart (mch_t * ch) +{ +#ifdef RLD_RESTART_DEBUG + pr_info("++ musycc_chan_restart[%d]: txd_irq_srv @ %p = sts %x\n", + ch->channum, ch->txd_irq_srv, ch->txd_irq_srv->status); +#endif + + /* 2.6 - find next unprocessed message, then set TX thp to it */ +#ifdef RLD_RESTART_DEBUG + pr_info(">> musycc_chan_restart: scheduling Chan %x workQ @ %p\n", ch->channum, &ch->ch_work); +#endif + c4_wk_chan_restart (ch); /* work queue mechanism fires off: Ref: + * musycc_wq_chan_restart () */ + +} + + +void +rld_put_led (mpi_t * pi, u_int32_t ledval) +{ + static u_int32_t led = 0; + + if (ledval == 0) + led = 0; + else + led |= ledval; + + pci_write_32 ((u_int32_t *) &pi->up->cpldbase->leds, led); /* RLD DEBUG TRANHANG */ +} + + +#define MUSYCC_SR_RETRY_CNT 9 + +void +musycc_serv_req (mpi_t * pi, u_int32_t req) +{ + volatile u_int32_t r; + int rcnt; + + /* + * PORT NOTE: Semaphore protect service loop guarantees only a single + * operation at a time. Per MUSYCC Manual - "Issuing service requests to + * the same channel group without first receiving ACK from each request + * may cause the host to lose track of which service request has been + * acknowledged." + */ + + SD_SEM_TAKE (&pi->sr_sem_busy, "serv"); /* only 1 thru here, per + * group */ + + if (pi->sr_last == req) + { +#ifdef RLD_TRANS_DEBUG + pr_info(">> same SR, Port %d Req %x\n", pi->portnum, req); +#endif + + /* + * The most likely repeated request is the channel activation command + * which follows the occurrence of a Transparent mode TX ONR or a + * BUFF error. If the previous command was a CHANNEL ACTIVATE, + * precede it with a NOOP command in order maintain coherent control + * of this current (re)ACTIVATE. + */ + + r = (pi->sr_last & ~SR_GCHANNEL_MASK); + if ((r == (SR_CHANNEL_ACTIVATE | SR_TX_DIRECTION)) || + (r == (SR_CHANNEL_ACTIVATE | SR_RX_DIRECTION))) + { +#ifdef RLD_TRANS_DEBUG + pr_info(">> same CHAN ACT SR, Port %d Req %x => issue SR_NOOP CMD\n", pi->portnum, req); +#endif + SD_SEM_GIVE (&pi->sr_sem_busy); /* allow this next request */ + musycc_serv_req (pi, SR_NOOP); + SD_SEM_TAKE (&pi->sr_sem_busy, "serv"); /* relock & continue w/ + * original req */ + } else if (req == SR_NOOP) + { + /* no need to issue back-to-back SR_NOOP commands at this time */ +#ifdef RLD_TRANS_DEBUG + pr_info(">> same Port SR_NOOP skipped, Port %d\n", pi->portnum); +#endif + SD_SEM_GIVE (&pi->sr_sem_busy); /* allow this next request */ + return; + } + } + rcnt = 0; + pi->sr_last = req; +rewrite: + pci_write_32 ((u_int32_t *) &pi->reg->srd, req); + FLUSH_MEM_WRITE (); + + /* + * Per MUSYCC Manual, Section 6.1,2 - "When writing an SCR service + * request, the host must ensure at least one PCI bus clock cycle has + * elapsed before writing another service request. To meet this minimum + * elapsed service request write timing interval, it is recommended that + * the host follow any SCR write with another operation which reads from + * the same address." + */ + r = pci_read_32 ((u_int32_t *) &pi->reg->srd); /* adhere to write + * timing imposition */ + + + if ((r != req) && (req != SR_CHIP_RESET) && (++rcnt <= MUSYCC_SR_RETRY_CNT)) + { + if (cxt1e1_log_level >= LOG_MONITOR) + pr_info("%s: %d - reissue srv req/last %x/%x (hdw reads %x), Chan %d.\n", + pi->up->devname, rcnt, req, pi->sr_last, r, + (pi->portnum * MUSYCC_NCHANS) + (req & 0x1f)); + OS_uwait_dummy (); /* this delay helps reduce reissue counts + * (reason not yet researched) */ + goto rewrite; + } + if (rcnt > MUSYCC_SR_RETRY_CNT) + { + pr_warning("%s: failed service request (#%d)= %x, group %d.\n", + pi->up->devname, MUSYCC_SR_RETRY_CNT, req, pi->portnum); + SD_SEM_GIVE (&pi->sr_sem_busy); /* allow any next request */ + return; + } + if (req == SR_CHIP_RESET) + { + /* + * PORT NOTE: the CHIP_RESET command is NOT ack'd by the MUSYCC, thus + * the upcoming delay is used. Though the MUSYCC documentation + * suggests a read-after-write would supply the required delay, it's + * unclear what CPU/BUS clock speeds might have been assumed when + * suggesting this 'lack of ACK' workaround. Thus the use of uwait. + */ + OS_uwait (100000, "icard"); /* 100ms */ + } else + { + FLUSH_MEM_READ (); + SD_SEM_TAKE (&pi->sr_sem_wait, "sakack"); /* sleep until SACK + * interrupt occurs */ + } + SD_SEM_GIVE (&pi->sr_sem_busy); /* allow any next request */ +} + + +#ifdef SBE_PMCC4_ENABLE +void +musycc_update_timeslots (mpi_t * pi) +{ + int i, ch; + char e1mode = IS_FRAME_ANY_E1 (pi->p.port_mode); + + for (i = 0; i < 32; i++) + { + int usedby = 0, last = 0, ts, j, bits[8]; + + u_int8_t lastval = 0; + + if (((i == 0) && e1mode) || /* disable if E1 mode */ + ((i == 16) && ((pi->p.port_mode == CFG_FRAME_E1CRC_CAS) || (pi->p.port_mode == CFG_FRAME_E1CRC_CAS_AMI))) + || ((i > 23) && (!e1mode))) /* disable if T1 mode */ + { + pi->tsm[i] = 0xff; /* make tslot unavailable for this mode */ + } else + { + pi->tsm[i] = 0x00; /* make tslot available for assignment */ + } + for (j = 0; j < 8; j++) + bits[j] = -1; + for (ch = 0; ch < MUSYCC_NCHANS; ch++) + { + if ((pi->chan[ch]->state == UP) && (pi->chan[ch]->p.bitmask[i])) + { + usedby++; + last = ch; + lastval = pi->chan[ch]->p.bitmask[i]; + for (j = 0; j < 8; j++) + if (lastval & (1 << j)) + bits[j] = ch; + pi->tsm[i] |= lastval; + } + } + if (!usedby) + ts = 0; + else if ((usedby == 1) && (lastval == 0xff)) + ts = (4 << 5) | last; + else if ((usedby == 1) && (lastval == 0x7f)) + ts = (5 << 5) | last; + else + { + int idx; + + if (bits[0] < 0) + ts = (6 << 5) | (idx = last); + else + ts = (7 << 5) | (idx = bits[0]); + for (j = 1; j < 8; j++) + { + pi->regram->rscm[idx * 8 + j] = (bits[j] < 0) ? 0 : (0x80 | bits[j]); + pi->regram->tscm[idx * 8 + j] = (bits[j] < 0) ? 0 : (0x80 | bits[j]); + } + } + pi->regram->rtsm[i] = ts; + pi->regram->ttsm[i] = ts; + } + FLUSH_MEM_WRITE (); + + musycc_serv_req (pi, SR_TIMESLOT_MAP | SR_RX_DIRECTION); + musycc_serv_req (pi, SR_TIMESLOT_MAP | SR_TX_DIRECTION); + musycc_serv_req (pi, SR_SUBCHANNEL_MAP | SR_RX_DIRECTION); + musycc_serv_req (pi, SR_SUBCHANNEL_MAP | SR_TX_DIRECTION); +} +#endif + + +#ifdef SBE_WAN256T3_ENABLE +void +musycc_update_timeslots (mpi_t * pi) +{ + mch_t *ch; + + u_int8_t ts, hmask, tsen; + int gchan; + int i; + +#ifdef SBE_PMCC4_ENABLE + hmask = (0x1f << pi->up->p.hypersize) & 0x1f; +#endif +#ifdef SBE_WAN256T3_ENABLE + hmask = (0x1f << hyperdummy) & 0x1f; +#endif + for (i = 0; i < 128; i++) + { + gchan = ((pi->portnum * MUSYCC_NCHANS) + (i & hmask)) % MUSYCC_NCHANS; + ch = pi->chan[gchan]; + if (ch->p.mode_56k) + tsen = MODE_56KBPS; + else + tsen = MODE_64KBPS; /* also the default */ + ts = ((pi->portnum % 4) == (i / 32)) ? (tsen << 5) | (i & hmask) : 0; + pi->regram->rtsm[i] = ts; + pi->regram->ttsm[i] = ts; + } + FLUSH_MEM_WRITE (); + musycc_serv_req (pi, SR_TIMESLOT_MAP | SR_RX_DIRECTION); + musycc_serv_req (pi, SR_TIMESLOT_MAP | SR_TX_DIRECTION); +} +#endif + + + /* + * This routine converts a generic library channel configuration parameter + * into a hardware specific register value (IE. MUSYCC CCD Register). + */ +u_int32_t +musycc_chan_proto (int proto) +{ + int reg; + + switch (proto) + { + case CFG_CH_PROTO_TRANS: /* 0 */ + reg = MUSYCC_CCD_TRANS; + break; + case CFG_CH_PROTO_SS7: /* 1 */ + reg = MUSYCC_CCD_SS7; + break; + default: + case CFG_CH_PROTO_ISLP_MODE: /* 4 */ + case CFG_CH_PROTO_HDLC_FCS16: /* 2 */ + reg = MUSYCC_CCD_HDLC_FCS16; + break; + case CFG_CH_PROTO_HDLC_FCS32: /* 3 */ + reg = MUSYCC_CCD_HDLC_FCS32; + break; + } + + return reg; +} + +#ifdef SBE_WAN256T3_ENABLE +STATIC void __init +musycc_init_port (mpi_t * pi) +{ + pci_write_32 ((u_int32_t *) &pi->reg->gbp, OS_vtophys (pi->regram)); + + pi->regram->grcd = + __constant_cpu_to_le32 (MUSYCC_GRCD_RX_ENABLE | + MUSYCC_GRCD_TX_ENABLE | + MUSYCC_GRCD_SF_ALIGN | + MUSYCC_GRCD_SUBCHAN_DISABLE | + MUSYCC_GRCD_OOFMP_DISABLE | + MUSYCC_GRCD_COFAIRQ_DISABLE | + MUSYCC_GRCD_MC_ENABLE | + (MUSYCC_GRCD_POLLTH_32 << MUSYCC_GRCD_POLLTH_SHIFT)); + + pi->regram->pcd = + __constant_cpu_to_le32 (MUSYCC_PCD_E1X4_MODE | + MUSYCC_PCD_TXDATA_RISING | + MUSYCC_PCD_TX_DRIVEN); + + /* Message length descriptor */ + pi->regram->mld = __constant_cpu_to_le32 (cxt1e1_max_mru | (cxt1e1_max_mru << 16)); + FLUSH_MEM_WRITE (); + + musycc_serv_req (pi, SR_GROUP_INIT | SR_RX_DIRECTION); + musycc_serv_req (pi, SR_GROUP_INIT | SR_TX_DIRECTION); + + musycc_init_mdt (pi); + + musycc_update_timeslots (pi); +} +#endif + + +status_t __init +musycc_init (ci_t * ci) +{ + char *regaddr; /* temp for address boundary calculations */ + int i, gchan; + + OS_sem_init (&ci->sem_wdbusy, SEM_AVAILABLE); /* watchdog exclusion */ + + /* + * Per MUSYCC manual, Section 6.3.4 - "The host must allocate a dword + * aligned memory segment for interrupt queue pointers." + */ + +#define INT_QUEUE_BOUNDARY 4 + + regaddr = OS_kmalloc ((INT_QUEUE_SIZE + 1) * sizeof (u_int32_t)); + if (regaddr == 0) + return ENOMEM; + ci->iqd_p_saved = regaddr; /* save orig value for free's usage */ + ci->iqd_p = (u_int32_t *) ((unsigned long) (regaddr + INT_QUEUE_BOUNDARY - 1) & + (~(INT_QUEUE_BOUNDARY - 1))); /* this calculates + * closest boundary */ + + for (i = 0; i < INT_QUEUE_SIZE; i++) + { + ci->iqd_p[i] = __constant_cpu_to_le32 (INT_EMPTY_ENTRY); + } + + for (i = 0; i < ci->max_port; i++) + { + mpi_t *pi = &ci->port[i]; + + /* + * Per MUSYCC manual, Section 6.3.2 - "The host must allocate a 2KB + * bound memory segment for Channel Group 0." + */ + +#define GROUP_BOUNDARY 0x800 + + regaddr = OS_kmalloc (sizeof (struct musycc_groupr) + GROUP_BOUNDARY); + if (regaddr == 0) + { + for (gchan = 0; gchan < i; gchan++) + { + pi = &ci->port[gchan]; + OS_kfree (pi->reg); + pi->reg = 0; + } + return ENOMEM; + } + pi->regram_saved = regaddr; /* save orig value for free's usage */ + pi->regram = (struct musycc_groupr *) ((unsigned long) (regaddr + GROUP_BOUNDARY - 1) & + (~(GROUP_BOUNDARY - 1))); /* this calculates + * closest boundary */ + } + + /* any board centric MUSYCC commands will use group ZERO as its "home" */ + ci->regram = ci->port[0].regram; + musycc_serv_req (&ci->port[0], SR_CHIP_RESET); + + pci_write_32 ((u_int32_t *) &ci->reg->gbp, OS_vtophys (ci->regram)); + pci_flush_write (ci); +#ifdef CONFIG_SBE_PMCC4_NCOMM + ci->regram->__glcd = __constant_cpu_to_le32 (GCD_MAGIC); +#else + /* standard driver POLLS for INTB via CPLD register */ + ci->regram->__glcd = __constant_cpu_to_le32 (GCD_MAGIC | MUSYCC_GCD_INTB_DISABLE); +#endif + + ci->regram->__iqp = cpu_to_le32 (OS_vtophys (&ci->iqd_p[0])); + ci->regram->__iql = __constant_cpu_to_le32 (INT_QUEUE_SIZE - 1); + pci_write_32 ((u_int32_t *) &ci->reg->dacbp, 0); + FLUSH_MEM_WRITE (); + + ci->state = C_RUNNING; /* mark as full interrupt processing + * available */ + + musycc_serv_req (&ci->port[0], SR_GLOBAL_INIT); /* FIRST INTERRUPT ! */ + + /* sanity check settable parameters */ + + if (cxt1e1_max_mru > 0xffe) + { + pr_warning("Maximum allowed MRU exceeded, resetting %d to %d.\n", + cxt1e1_max_mru, 0xffe); + cxt1e1_max_mru = 0xffe; + } + if (cxt1e1_max_mtu > 0xffe) + { + pr_warning("Maximum allowed MTU exceeded, resetting %d to %d.\n", + cxt1e1_max_mtu, 0xffe); + cxt1e1_max_mtu = 0xffe; + } +#ifdef SBE_WAN256T3_ENABLE + for (i = 0; i < MUSYCC_NPORTS; i++) + musycc_init_port (&ci->port[i]); +#endif + + return SBE_DRVR_SUCCESS; /* no error */ +} + + +void +musycc_bh_tx_eom (mpi_t * pi, int gchan) +{ + mch_t *ch; + struct mdesc *md; + +#if 0 +#ifndef SBE_ISR_INLINE + unsigned long flags; + +#endif +#endif + volatile u_int32_t status; + + ch = pi->chan[gchan]; + if (ch == 0 || ch->state != UP) + { + if (cxt1e1_log_level >= LOG_ERROR) + pr_info("%s: intr: xmit EOM on uninitialized channel %d\n", + pi->up->devname, gchan); + } + if (ch == 0 || ch->mdt == 0) + return; /* note: mdt==0 implies a malloc() + * failure w/in chan_up() routine */ + +#if 0 +#ifdef SBE_ISR_INLINE + spin_lock_irq (&ch->ch_txlock); +#else + spin_lock_irqsave (&ch->ch_txlock, flags); +#endif +#endif + do + { + FLUSH_MEM_READ (); + md = ch->txd_irq_srv; + status = le32_to_cpu (md->status); + + /* + * Note: Per MUSYCC Ref 6.4.9, the host does not poll a host-owned + * Transmit Buffer Descriptor during Transparent Mode. + */ + if (status & MUSYCC_TX_OWNED) + { + int readCount, loopCount; + + /***********************************************************/ + /* HW Bug Fix */ + /* ---------- */ + /* Under certain PCI Bus loading conditions, the data */ + /* associated with an update of Shared Memory is delayed */ + /* relative to its PCI Interrupt. This is caught when */ + /* the host determines it does not yet OWN the descriptor. */ + /***********************************************************/ + + readCount = 0; + while (status & MUSYCC_TX_OWNED) + { + for (loopCount = 0; loopCount < 0x30; loopCount++) + OS_uwait_dummy (); /* use call to avoid optimization + * removal of dummy delay */ + FLUSH_MEM_READ (); + status = le32_to_cpu (md->status); + if (readCount++ > 40) + break; /* don't wait any longer */ + } + if (status & MUSYCC_TX_OWNED) + { + if (cxt1e1_log_level >= LOG_MONITOR) + { + pr_info("%s: Port %d Chan %2d - unexpected TX msg ownership intr (md %p sts %x)\n", + pi->up->devname, pi->portnum, ch->channum, + md, status); + pr_info("++ User 0x%p IRQ_SRV 0x%p USR_ADD 0x%p QStopped %x, start_tx %x tx_full %d txd_free %d mode %x\n", + ch->user, ch->txd_irq_srv, ch->txd_usr_add, + sd_queue_stopped (ch->user), + ch->ch_start_tx, ch->tx_full, ch->txd_free, ch->p.chan_mode); + musycc_dump_txbuffer_ring (ch, 0); + } + break; /* Not our mdesc, done */ + } else + { + if (cxt1e1_log_level >= LOG_MONITOR) + pr_info("%s: Port %d Chan %2d - recovered TX msg ownership [%d] (md %p sts %x)\n", + pi->up->devname, pi->portnum, ch->channum, readCount, md, status); + } + } + ch->txd_irq_srv = md->snext; + + md->data = 0; + if (md->mem_token != 0) + { + /* upcount channel */ + atomic_sub (OS_mem_token_tlen (md->mem_token), &ch->tx_pending); + /* upcount card */ + atomic_sub (OS_mem_token_tlen (md->mem_token), &pi->up->tx_pending); +#ifdef SBE_WAN256T3_ENABLE + if (!atomic_read (&pi->up->tx_pending)) + wan256t3_led (pi->up, LED_TX, 0); +#endif + +#ifdef CONFIG_SBE_WAN256T3_NCOMM + /* callback that our packet was sent */ + { + int hdlcnum = (pi->portnum * 32 + gchan); + + if (hdlcnum >= 228) + { + if (nciProcess_TX_complete) + (*nciProcess_TX_complete) (hdlcnum, + getuserbychan (gchan)); + } + } +#endif /*** CONFIG_SBE_WAN256T3_NCOMM ***/ + + OS_mem_token_free_irq (md->mem_token); + md->mem_token = 0; + } + md->status = 0; +#ifdef RLD_TXFULL_DEBUG + if (cxt1e1_log_level >= LOG_MONITOR2) + pr_info("~~ tx_eom: tx_full %x txd_free %d -> %d\n", + ch->tx_full, ch->txd_free, ch->txd_free + 1); +#endif + ++ch->txd_free; + FLUSH_MEM_WRITE (); + + if ((ch->p.chan_mode != CFG_CH_PROTO_TRANS) && (status & EOBIRQ_ENABLE)) + { + if (cxt1e1_log_level >= LOG_MONITOR) + pr_info("%s: Mode (%x) incorrect EOB status (%x)\n", + pi->up->devname, ch->p.chan_mode, status); + if ((status & EOMIRQ_ENABLE) == 0) + break; + } + } + while ((ch->p.chan_mode != CFG_CH_PROTO_TRANS) && ((status & EOMIRQ_ENABLE) == 0)); + /* + * NOTE: (The above 'while' is coupled w/ previous 'do', way above.) Each + * Transparent data buffer has the EOB bit, and NOT the EOM bit, set and + * will furthermore have a separate IQD associated with each messages + * buffer. + */ + + FLUSH_MEM_READ (); + /* + * Smooth flow control hysterisis by maintaining task stoppage until half + * the available write buffers are available. + */ + if (ch->tx_full && (ch->txd_free >= (ch->txd_num / 2))) + { + /* + * Then, only releave task stoppage if we actually have enough + * buffers to service the last requested packet. It may require MORE + * than half the available! + */ + if (ch->txd_free >= ch->txd_required) + { + +#ifdef RLD_TXFULL_DEBUG + if (cxt1e1_log_level >= LOG_MONITOR2) + pr_info("tx_eom[%d]: enable xmit tx_full no more, txd_free %d txd_num/2 %d\n", + ch->channum, + ch->txd_free, ch->txd_num / 2); +#endif + ch->tx_full = 0; + ch->txd_required = 0; + sd_enable_xmit (ch->user); /* re-enable to catch flow controlled + * channel */ + } + } +#ifdef RLD_TXFULL_DEBUG + else if (ch->tx_full) + { + if (cxt1e1_log_level >= LOG_MONITOR2) + pr_info("tx_eom[%d]: bypass TX enable though room available? (txd_free %d txd_num/2 %d)\n", + ch->channum, + ch->txd_free, ch->txd_num / 2); + } +#endif + + FLUSH_MEM_WRITE (); +#if 0 +#ifdef SBE_ISR_INLINE + spin_unlock_irq (&ch->ch_txlock); +#else + spin_unlock_irqrestore (&ch->ch_txlock, flags); +#endif +#endif +} + + +STATIC void +musycc_bh_rx_eom (mpi_t * pi, int gchan) +{ + mch_t *ch; + void *m, *m2; + struct mdesc *md; + volatile u_int32_t status; + u_int32_t error; + + ch = pi->chan[gchan]; + if (ch == 0 || ch->state != UP) + { + if (cxt1e1_log_level > LOG_ERROR) + pr_info("%s: intr: receive EOM on uninitialized channel %d\n", + pi->up->devname, gchan); + return; + } + if (ch->mdr == 0) + return; /* can this happen ? */ + + for (;;) + { + FLUSH_MEM_READ (); + md = &ch->mdr[ch->rxix_irq_srv]; + status = le32_to_cpu (md->status); + if (!(status & HOST_RX_OWNED)) + break; /* Not our mdesc, done */ + m = md->mem_token; + error = (status >> 16) & 0xf; + if (error == 0) + { +#ifdef CONFIG_SBE_WAN256T3_NCOMM + int hdlcnum = (pi->portnum * 32 + gchan); + + /* + * if the packet number belongs to NCOMM, then send it to the TMS + * driver + */ + if (hdlcnum >= 228) + { + if (nciProcess_RX_packet) + (*nciProcess_RX_packet) (hdlcnum, status & 0x3fff, m, ch->user); + } else +#endif /*** CONFIG_SBE_WAN256T3_NCOMM ***/ + + { + if ((m2 = OS_mem_token_alloc (cxt1e1_max_mru))) + { + /* substitute the mbuf+cluster */ + md->mem_token = m2; + md->data = cpu_to_le32 (OS_vtophys (OS_mem_token_data (m2))); + + /* pass the received mbuf upward */ + sd_recv_consume (m, status & LENGTH_MASK, ch->user); + ch->s.rx_packets++; + ch->s.rx_bytes += status & LENGTH_MASK; + } else + { + ch->s.rx_dropped++; + } + } + } else if (error == ERR_FCS) + { + ch->s.rx_crc_errors++; + } else if (error == ERR_ALIGN) + { + ch->s.rx_missed_errors++; + } else if (error == ERR_ABT) + { + ch->s.rx_missed_errors++; + } else if (error == ERR_LNG) + { + ch->s.rx_length_errors++; + } else if (error == ERR_SHT) + { + ch->s.rx_length_errors++; + } + FLUSH_MEM_WRITE (); + status = cxt1e1_max_mru; + if (ch->p.chan_mode == CFG_CH_PROTO_TRANS) + status |= EOBIRQ_ENABLE; + md->status = cpu_to_le32 (status); + + /* Check next mdesc in the ring */ + if (++ch->rxix_irq_srv >= ch->rxd_num) + ch->rxix_irq_srv = 0; + FLUSH_MEM_WRITE (); + } +} + + +irqreturn_t +musycc_intr_th_handler (void *devp) +{ + ci_t *ci = (ci_t *) devp; + volatile u_int32_t status, currInt = 0; + u_int32_t nextInt, intCnt; + + /* + * Hardware not available, potential interrupt hang. But since interrupt + * might be shared, just return. + */ + if (ci->state == C_INIT) + { + return IRQ_NONE; + } + /* + * Marked as hardware available. Don't service interrupts, just clear the + * event. + */ + + if (ci->state == C_IDLE) + { + status = pci_read_32 ((u_int32_t *) &ci->reg->isd); + + /* clear the interrupt but process nothing else */ + pci_write_32 ((u_int32_t *) &ci->reg->isd, status); + return IRQ_HANDLED; + } + FLUSH_PCI_READ (); + FLUSH_MEM_READ (); + + status = pci_read_32 ((u_int32_t *) &ci->reg->isd); + nextInt = INTRPTS_NEXTINT (status); + intCnt = INTRPTS_INTCNT (status); + ci->intlog.drvr_intr_thcount++; + + /*********************************************************/ + /* HW Bug Fix */ + /* ---------- */ + /* Under certain PCI Bus loading conditions, the */ + /* MUSYCC looses the data associated with an update */ + /* of its ISD and erroneously returns the immediately */ + /* preceding 'nextInt' value. However, the 'intCnt' */ + /* value appears to be correct. By not starting service */ + /* where the 'missing' 'nextInt' SHOULD point causes */ + /* the IQD not to be serviced - the 'not serviced' */ + /* entries then remain and continue to increase as more */ + /* incorrect ISD's are encountered. */ + /*********************************************************/ + + if (nextInt != INTRPTS_NEXTINT (ci->intlog.this_status_new)) + { + if (cxt1e1_log_level >= LOG_MONITOR) + { + pr_info("%s: note - updated ISD from %08x to %08x\n", + ci->devname, status, + (status & (~INTRPTS_NEXTINT_M)) | ci->intlog.this_status_new); + } + /* + * Replace bogus status with software corrected value. + * + * It's not known whether, during this problem occurrence, if the + * INTFULL bit is correctly reported or not. + */ + status = (status & (~INTRPTS_NEXTINT_M)) | (ci->intlog.this_status_new); + nextInt = INTRPTS_NEXTINT (status); + } + /**********************************************/ + /* Cn847x Bug Fix */ + /* -------------- */ + /* Fix for inability to write back same index */ + /* as read for a full interrupt queue. */ + /**********************************************/ + + if (intCnt == INT_QUEUE_SIZE) + { + currInt = ((intCnt - 1) + nextInt) & (INT_QUEUE_SIZE - 1); + } else + /************************************************/ + /* Interrupt Write Location Issues */ + /* ------------------------------- */ + /* When the interrupt status descriptor is */ + /* written, the interrupt line is de-asserted */ + /* by the Cn847x. In the case of MIPS */ + /* microprocessors, this must occur at the */ + /* beginning of the interrupt handler so that */ + /* the interrupt handle is not re-entered due */ + /* to interrupt dis-assertion latency. */ + /* In the case of all other processors, this */ + /* action should occur at the end of the */ + /* interrupt handler to avoid overwriting the */ + /* interrupt queue. */ + /************************************************/ + + if (intCnt) + { + currInt = (intCnt + nextInt) & (INT_QUEUE_SIZE - 1); + } else + { + /* + * NOTE: Servicing an interrupt whose ISD contains a count of ZERO + * can be indicative of a Shared Interrupt chain. Our driver can be + * called from the system's interrupt handler as a matter of the OS + * walking the chain. As the chain is walked, the interrupt will + * eventually be serviced by the correct driver/handler. + */ +#if 0 + /* chained interrupt = not ours */ + pr_info(">> %s: intCnt NULL, sts %x, possibly a chained interrupt!\n", + ci->devname, status); +#endif + return IRQ_NONE; + } + + ci->iqp_tailx = currInt; + + currInt <<= INTRPTS_NEXTINT_S; + ci->intlog.last_status_new = ci->intlog.this_status_new; + ci->intlog.this_status_new = currInt; + + if ((cxt1e1_log_level >= LOG_WARN) && (status & INTRPTS_INTFULL_M)) + { + pr_info("%s: Interrupt queue full condition occurred\n", ci->devname); + } + if (cxt1e1_log_level >= LOG_DEBUG) + pr_info("%s: interrupts pending, isd @ 0x%p: %x curr %d cnt %d NEXT %d\n", + ci->devname, &ci->reg->isd, + status, nextInt, intCnt, (intCnt + nextInt) & (INT_QUEUE_SIZE - 1)); + + FLUSH_MEM_WRITE (); +#if defined(SBE_ISR_TASKLET) + pci_write_32 ((u_int32_t *) &ci->reg->isd, currInt); + atomic_inc (&ci->bh_pending); + tasklet_schedule (&ci->ci_musycc_isr_tasklet); +#elif defined(SBE_ISR_IMMEDIATE) + pci_write_32 ((u_int32_t *) &ci->reg->isd, currInt); + atomic_inc (&ci->bh_pending); + queue_task (&ci->ci_musycc_isr_tq, &tq_immediate); + mark_bh (IMMEDIATE_BH); +#elif defined(SBE_ISR_INLINE) + (void) musycc_intr_bh_tasklet (ci); + pci_write_32 ((u_int32_t *) &ci->reg->isd, currInt); +#endif + return IRQ_HANDLED; +} + + +#if defined(SBE_ISR_IMMEDIATE) +unsigned long +#else +void +#endif +musycc_intr_bh_tasklet (ci_t * ci) +{ + mpi_t *pi; + mch_t *ch; + unsigned int intCnt; + volatile u_int32_t currInt = 0; + volatile unsigned int headx, tailx; + int readCount, loopCount; + int group, gchan, event, err, tx; + u_int32_t badInt = INT_EMPTY_ENTRY; + u_int32_t badInt2 = INT_EMPTY_ENTRY2; + + /* + * Hardware not available, potential interrupt hang. But since interrupt + * might be shared, just return. + */ + if ((drvr_state != SBE_DRVR_AVAILABLE) || (ci->state == C_INIT)) + { +#if defined(SBE_ISR_IMMEDIATE) + return 0L; +#else + return; +#endif + } +#if defined(SBE_ISR_TASKLET) || defined(SBE_ISR_IMMEDIATE) + if (drvr_state != SBE_DRVR_AVAILABLE) + { +#if defined(SBE_ISR_TASKLET) + return; +#elif defined(SBE_ISR_IMMEDIATE) + return 0L; +#endif + } +#elif defined(SBE_ISR_INLINE) + /* no semaphore taken, no double checks */ +#endif + + ci->intlog.drvr_intr_bhcount++; + FLUSH_MEM_READ (); + { + unsigned int bh = atomic_read (&ci->bh_pending); + + max_bh = max (bh, max_bh); + } + atomic_set (&ci->bh_pending, 0);/* if here, no longer pending */ + while ((headx = ci->iqp_headx) != (tailx = ci->iqp_tailx)) + { + intCnt = (tailx >= headx) ? (tailx - headx) : (tailx - headx + INT_QUEUE_SIZE); + currInt = le32_to_cpu (ci->iqd_p[headx]); + + max_intcnt = max (intCnt, max_intcnt); /* RLD DEBUG */ + + /**************************************************/ + /* HW Bug Fix */ + /* ---------- */ + /* The following code checks for the condition */ + /* of interrupt assertion before interrupt */ + /* queue update. This is a problem on several */ + /* PCI-Local bridge chips found on some products. */ + /**************************************************/ + + readCount = 0; + if ((currInt == badInt) || (currInt == badInt2)) + ci->intlog.drvr_int_failure++; + + while ((currInt == badInt) || (currInt == badInt2)) + { + for (loopCount = 0; loopCount < 0x30; loopCount++) + OS_uwait_dummy (); /* use call to avoid optimization removal + * of dummy delay */ + FLUSH_MEM_READ (); + currInt = le32_to_cpu (ci->iqd_p[headx]); + if (readCount++ > 20) + break; + } + + if ((currInt == badInt) || (currInt == badInt2)) /* catch failure of Bug + * Fix checking */ + { + if (cxt1e1_log_level >= LOG_WARN) + pr_info("%s: Illegal Interrupt Detected @ 0x%p, mod %d.)\n", + ci->devname, &ci->iqd_p[headx], headx); + + /* + * If the descriptor has not recovered, then leaving the EMPTY + * entry set will not signal to the MUSYCC that this descriptor + * has been serviced. The Interrupt Queue can then start losing + * available descriptors and MUSYCC eventually encounters and + * reports the INTFULL condition. Per manual, changing any bit + * marks descriptor as available, thus the use of different + * EMPTY_ENTRY values. + */ + + if (currInt == badInt) + { + ci->iqd_p[headx] = __constant_cpu_to_le32 (INT_EMPTY_ENTRY2); + } else + { + ci->iqd_p[headx] = __constant_cpu_to_le32 (INT_EMPTY_ENTRY); + } + ci->iqp_headx = (headx + 1) & (INT_QUEUE_SIZE - 1); /* insure wrapness */ + FLUSH_MEM_WRITE (); + FLUSH_MEM_READ (); + continue; + } + group = INTRPT_GRP (currInt); + gchan = INTRPT_CH (currInt); + event = INTRPT_EVENT (currInt); + err = INTRPT_ERROR (currInt); + tx = currInt & INTRPT_DIR_M; + + ci->iqd_p[headx] = __constant_cpu_to_le32 (INT_EMPTY_ENTRY); + FLUSH_MEM_WRITE (); + + if (cxt1e1_log_level >= LOG_DEBUG) + { + if (err != 0) + pr_info(" %08x -> err: %2d,", currInt, err); + + pr_info("+ interrupt event: %d, grp: %d, chan: %2d, side: %cX\n", + event, group, gchan, tx ? 'T' : 'R'); + } + pi = &ci->port[group]; /* notice that here we assume 1-1 group - + * port mapping */ + ch = pi->chan[gchan]; + switch (event) + { + case EVE_SACK: /* Service Request Acknowledge */ + if (cxt1e1_log_level >= LOG_DEBUG) + { + volatile u_int32_t r; + + r = pci_read_32 ((u_int32_t *) &pi->reg->srd); + pr_info("- SACK cmd: %08x (hdw= %08x)\n", pi->sr_last, r); + } + SD_SEM_GIVE (&pi->sr_sem_wait); /* wake up waiting process */ + break; + case EVE_CHABT: /* Change To Abort Code (0x7e -> 0xff) */ + case EVE_CHIC: /* Change To Idle Code (0xff -> 0x7e) */ + break; + case EVE_EOM: /* End Of Message */ + case EVE_EOB: /* End Of Buffer (Transparent mode) */ + if (tx) + { + musycc_bh_tx_eom (pi, gchan); + } else + { + musycc_bh_rx_eom (pi, gchan); + } +#if 0 + break; +#else + /* + * MUSYCC Interrupt Descriptor section states that EOB and EOM + * can be combined with the NONE error (as well as others). So + * drop thru to catch this... + */ +#endif + case EVE_NONE: + if (err == ERR_SHT) + { + ch->s.rx_length_errors++; + } + break; + default: + if (cxt1e1_log_level >= LOG_WARN) + pr_info("%s: unexpected interrupt event: %d, iqd[%d]: %08x, port: %d\n", ci->devname, + event, headx, currInt, group); + break; + } /* switch on event */ + + + /* + * Per MUSYCC Manual, Section 6.4.8.3 [Transmit Errors], TX errors + * are service-affecting and require action to resume normal + * bit-level processing. + */ + + switch (err) + { + case ERR_ONR: + /* + * Per MUSYCC manual, Section 6.4.8.3 [Transmit Errors], this + * error requires Transmit channel reactivation. + * + * Per MUSYCC manual, Section 6.4.8.4 [Receive Errors], this error + * requires Receive channel reactivation. + */ + if (tx) + { + + /* + * TX ONR Error only occurs when channel is configured for + * Transparent Mode. However, this code will catch and + * re-activate on ANY TX ONR error. + */ + + /* + * Set flag to re-enable on any next transmit attempt. + */ + ch->ch_start_tx = CH_START_TX_ONR; + + { +#ifdef RLD_TRANS_DEBUG + if (1 || cxt1e1_log_level >= LOG_MONITOR) +#else + if (cxt1e1_log_level >= LOG_MONITOR) +#endif + { + pr_info("%s: TX buffer underflow [ONR] on channel %d, mode %x QStopped %x free %d\n", + ci->devname, ch->channum, ch->p.chan_mode, sd_queue_stopped (ch->user), ch->txd_free); +#ifdef RLD_DEBUG + if (ch->p.chan_mode == 2) /* problem = ONR on HDLC + * mode */ + { + pr_info("++ Failed Last %x Next %x QStopped %x, start_tx %x tx_full %d txd_free %d mode %x\n", + (u_int32_t) ch->txd_irq_srv, (u_int32_t) ch->txd_usr_add, + sd_queue_stopped (ch->user), + ch->ch_start_tx, ch->tx_full, ch->txd_free, ch->p.chan_mode); + musycc_dump_txbuffer_ring (ch, 0); + } +#endif + } + } + } else /* RX buffer overrun */ + { + /* + * Per MUSYCC manual, Section 6.4.8.4 [Receive Errors], + * channel recovery for this RX ONR error IS required. It is + * also suggested to increase the number of receive buffers + * for this channel. Receive channel reactivation IS + * required, and data has been lost. + */ + ch->s.rx_over_errors++; + ch->ch_start_rx = CH_START_RX_ONR; + + if (cxt1e1_log_level >= LOG_WARN) + { + pr_info("%s: RX buffer overflow [ONR] on channel %d, mode %x\n", + ci->devname, ch->channum, ch->p.chan_mode); + //musycc_dump_rxbuffer_ring (ch, 0); /* RLD DEBUG */ + } + } + musycc_chan_restart (ch); + break; + case ERR_BUF: + if (tx) + { + ch->s.tx_fifo_errors++; + ch->ch_start_tx = CH_START_TX_BUF; + /* + * Per MUSYCC manual, Section 6.4.8.3 [Transmit Errors], + * this BUFF error requires Transmit channel reactivation. + */ + if (cxt1e1_log_level >= LOG_MONITOR) + pr_info("%s: TX buffer underrun [BUFF] on channel %d, mode %x\n", + ci->devname, ch->channum, ch->p.chan_mode); + } else /* RX buffer overrun */ + { + ch->s.rx_over_errors++; + /* + * Per MUSYCC manual, Section 6.4.8.4 [Receive Errors], HDLC + * mode requires NO recovery for this RX BUFF error is + * required. It is suggested to increase the FIFO buffer + * space for this channel. Receive channel reactivation is + * not required, but data has been lost. + */ + if (cxt1e1_log_level >= LOG_WARN) + pr_info("%s: RX buffer overrun [BUFF] on channel %d, mode %x\n", + ci->devname, ch->channum, ch->p.chan_mode); + /* + * Per MUSYCC manual, Section 6.4.9.4 [Receive Errors], + * Transparent mode DOES require recovery for the RX BUFF + * error. It is suggested to increase the FIFO buffer space + * for this channel. Receive channel reactivation IS + * required and data has been lost. + */ + if (ch->p.chan_mode == CFG_CH_PROTO_TRANS) + ch->ch_start_rx = CH_START_RX_BUF; + } + + if (tx || (ch->p.chan_mode == CFG_CH_PROTO_TRANS)) + musycc_chan_restart (ch); + break; + default: + break; + } /* switch on err */ + + /* Check for interrupt lost condition */ + if ((currInt & INTRPT_ILOST_M) && (cxt1e1_log_level >= LOG_ERROR)) + { + pr_info("%s: Interrupt queue overflow - ILOST asserted\n", + ci->devname); + } + ci->iqp_headx = (headx + 1) & (INT_QUEUE_SIZE - 1); /* insure wrapness */ + FLUSH_MEM_WRITE (); + FLUSH_MEM_READ (); + } /* while */ + if ((cxt1e1_log_level >= LOG_MONITOR2) && (ci->iqp_headx != ci->iqp_tailx)) + { + int bh; + + bh = atomic_read (&CI->bh_pending); + pr_info("_bh_: late arrivals, head %d != tail %d, pending %d\n", + ci->iqp_headx, ci->iqp_tailx, bh); + } +#if defined(SBE_ISR_IMMEDIATE) + return 0L; +#endif + /* else, nothing returned */ +} + +#if 0 +int __init +musycc_new_chan (ci_t * ci, int channum, void *user) +{ + mch_t *ch; + + ch = ci->port[channum / MUSYCC_NCHANS].chan[channum % MUSYCC_NCHANS]; + + if (ch->state != UNASSIGNED) + return EEXIST; + /* NOTE: mch_t already cleared during OS_kmalloc() */ + ch->state = DOWN; + ch->user = user; +#if 0 + ch->status = 0; + ch->p.status = 0; + ch->p.intr_mask = 0; +#endif + ch->p.chan_mode = CFG_CH_PROTO_HDLC_FCS16; + ch->p.idlecode = CFG_CH_FLAG_7E; + ch->p.pad_fill_count = 2; + spin_lock_init (&ch->ch_rxlock); + spin_lock_init (&ch->ch_txlock); + + return 0; +} +#endif + + +#ifdef SBE_PMCC4_ENABLE +status_t +musycc_chan_down (ci_t * dummy, int channum) +{ + mpi_t *pi; + mch_t *ch; + int i, gchan; + + if (!(ch = sd_find_chan (dummy, channum))) + return EINVAL; + pi = ch->up; + gchan = ch->gchan; + + /* Deactivate the channel */ + musycc_serv_req (pi, SR_CHANNEL_DEACTIVATE | SR_RX_DIRECTION | gchan); + ch->ch_start_rx = 0; + musycc_serv_req (pi, SR_CHANNEL_DEACTIVATE | SR_TX_DIRECTION | gchan); + ch->ch_start_tx = 0; + + if (ch->state == DOWN) + return 0; + ch->state = DOWN; + + pi->regram->thp[gchan] = 0; + pi->regram->tmp[gchan] = 0; + pi->regram->rhp[gchan] = 0; + pi->regram->rmp[gchan] = 0; + FLUSH_MEM_WRITE (); + for (i = 0; i < ch->txd_num; i++) + { + if (ch->mdt[i].mem_token != 0) + OS_mem_token_free (ch->mdt[i].mem_token); + } + + for (i = 0; i < ch->rxd_num; i++) + { + if (ch->mdr[i].mem_token != 0) + OS_mem_token_free (ch->mdr[i].mem_token); + } + + OS_kfree (ch->mdr); + ch->mdr = 0; + ch->rxd_num = 0; + OS_kfree (ch->mdt); + ch->mdt = 0; + ch->txd_num = 0; + + musycc_update_timeslots (pi); + c4_fifo_free (pi, ch->gchan); + + pi->openchans--; + return 0; +} +#endif + + +int +musycc_del_chan (ci_t * ci, int channum) +{ + mch_t *ch; + + if ((channum < 0) || (channum >= (MUSYCC_NPORTS * MUSYCC_NCHANS))) /* sanity chk param */ + return ECHRNG; + if (!(ch = sd_find_chan (ci, channum))) + return ENOENT; + if (ch->state == UP) + musycc_chan_down (ci, channum); + ch->state = UNASSIGNED; + return 0; +} + + +int +musycc_del_chan_stats (ci_t * ci, int channum) +{ + mch_t *ch; + + if (channum < 0 || channum >= (MUSYCC_NPORTS * MUSYCC_NCHANS)) /* sanity chk param */ + return ECHRNG; + if (!(ch = sd_find_chan (ci, channum))) + return ENOENT; + + memset (&ch->s, 0, sizeof (struct sbecom_chan_stats)); + return 0; +} + + +int +musycc_start_xmit (ci_t * ci, int channum, void *mem_token) +{ + mch_t *ch; + struct mdesc *md; + void *m2; +#if 0 + unsigned long flags; +#endif + int txd_need_cnt; + u_int32_t len; + + if (!(ch = sd_find_chan (ci, channum))) + return ENOENT; + + if (ci->state != C_RUNNING) /* full interrupt processing available */ + return EINVAL; + if (ch->state != UP) + return EINVAL; + + if (!(ch->status & TX_ENABLED)) + return EROFS; /* how else to flag unwritable state ? */ + +#ifdef RLD_TRANS_DEBUGx + if (1 || cxt1e1_log_level >= LOG_MONITOR2) +#else + if (cxt1e1_log_level >= LOG_MONITOR2) +#endif + { + pr_info("++ start_xmt[%d]: state %x start %x full %d free %d required %d stopped %x\n", + channum, ch->state, ch->ch_start_tx, ch->tx_full, + ch->txd_free, ch->txd_required, sd_queue_stopped (ch->user)); + } + /***********************************************/ + /** Determine total amount of data to be sent **/ + /***********************************************/ + m2 = mem_token; + txd_need_cnt = 0; + for (len = OS_mem_token_tlen (m2); len > 0; + m2 = (void *) OS_mem_token_next (m2)) + { + if (!OS_mem_token_len (m2)) + continue; + txd_need_cnt++; + len -= OS_mem_token_len (m2); + } + + if (txd_need_cnt == 0) + { + if (cxt1e1_log_level >= LOG_MONITOR2) + pr_info("%s channel %d: no TX data in User buffer\n", ci->devname, channum); + OS_mem_token_free (mem_token); + return 0; /* no data to send */ + } + /*************************************************/ + /** Are there sufficient descriptors available? **/ + /*************************************************/ + if (txd_need_cnt > ch->txd_num) /* never enough descriptors for this + * large a buffer */ + { + if (cxt1e1_log_level >= LOG_DEBUG) + { + pr_info("start_xmit: discarding buffer, insufficient descriptor cnt %d, need %d.\n", + ch->txd_num, txd_need_cnt + 1); + } + ch->s.tx_dropped++; + OS_mem_token_free (mem_token); + return 0; + } +#if 0 + spin_lock_irqsave (&ch->ch_txlock, flags); +#endif + /************************************************************/ + /** flow control the line if not enough descriptors remain **/ + /************************************************************/ + if (txd_need_cnt > ch->txd_free) + { + if (cxt1e1_log_level >= LOG_MONITOR2) + { + pr_info("start_xmit[%d]: EBUSY - need more descriptors, have %d of %d need %d\n", + channum, ch->txd_free, ch->txd_num, txd_need_cnt); + } + ch->tx_full = 1; + ch->txd_required = txd_need_cnt; + sd_disable_xmit (ch->user); +#if 0 + spin_unlock_irqrestore (&ch->ch_txlock, flags); +#endif + return EBUSY; /* tell user to try again later */ + } + /**************************************************/ + /** Put the user data into MUSYCC data buffer(s) **/ + /**************************************************/ + m2 = mem_token; + md = ch->txd_usr_add; /* get current available descriptor */ + + for (len = OS_mem_token_tlen (m2); len > 0; m2 = OS_mem_token_next (m2)) + { + int u = OS_mem_token_len (m2); + + if (!u) + continue; + len -= u; + + /* + * Enable following chunks, yet wait to enable the FIRST chunk until + * after ALL subsequent chunks are setup. + */ + if (md != ch->txd_usr_add) /* not first chunk */ + u |= MUSYCC_TX_OWNED; /* transfer ownership from HOST to MUSYCC */ + + if (len) /* not last chunk */ + u |= EOBIRQ_ENABLE; + else if (ch->p.chan_mode == CFG_CH_PROTO_TRANS) + { + /* + * Per MUSYCC Ref 6.4.9 for Transparent Mode, the host must + * always clear EOMIRQ_ENABLE in every Transmit Buffer Descriptor + * (IE. don't set herein). + */ + u |= EOBIRQ_ENABLE; + } else + u |= EOMIRQ_ENABLE; /* EOM, last HDLC chunk */ + + + /* last chunk in hdlc mode */ + u |= (ch->p.idlecode << IDLE_CODE); + if (ch->p.pad_fill_count) + { +#if 0 + /* NOOP NOTE: u_int8_t cannot be > 0xFF */ + /* sanitize pad_fill_count for maximums allowed by hardware */ + if (ch->p.pad_fill_count > EXTRA_FLAGS_MASK) + ch->p.pad_fill_count = EXTRA_FLAGS_MASK; +#endif + u |= (PADFILL_ENABLE | (ch->p.pad_fill_count << EXTRA_FLAGS)); + } + md->mem_token = len ? 0 : mem_token; /* Fill in mds on last + * segment, others set ZERO + * so that entire token is + * removed ONLY when ALL + * segments have been + * transmitted. */ + + md->data = cpu_to_le32 (OS_vtophys (OS_mem_token_data (m2))); + FLUSH_MEM_WRITE (); + md->status = cpu_to_le32 (u); + --ch->txd_free; + md = md->snext; + } + FLUSH_MEM_WRITE (); + + + /* + * Now transfer ownership of first chunk from HOST to MUSYCC in order to + * fire-off this XMIT. + */ + ch->txd_usr_add->status |= __constant_cpu_to_le32 (MUSYCC_TX_OWNED); + FLUSH_MEM_WRITE (); + ch->txd_usr_add = md; + + len = OS_mem_token_tlen (mem_token); + atomic_add (len, &ch->tx_pending); + atomic_add (len, &ci->tx_pending); + ch->s.tx_packets++; + ch->s.tx_bytes += len; + /* + * If an ONR was seen, then channel requires poking to restart + * transmission. + */ + if (ch->ch_start_tx) + { + musycc_chan_restart (ch); + } +#ifdef SBE_WAN256T3_ENABLE + wan256t3_led (ci, LED_TX, LEDV_G); +#endif + return 0; +} + + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/musycc.h b/drivers/staging/cxt1e1/musycc.h new file mode 100644 index 00000000..cf6b54e1 --- /dev/null +++ b/drivers/staging/cxt1e1/musycc.h @@ -0,0 +1,429 @@ +#ifndef _INC_MUSYCC_H_ +#define _INC_MUSYCC_H_ + +/*----------------------------------------------------------------------------- + * musycc.h - Multichannel Synchronous Communications Controller + * CN8778/8474A/8472A/8471A + * + * Copyright (C) 2002-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + +#include + +#define VINT8 volatile u_int8_t +#define VINT32 volatile u_int32_t + +#include "pmcc4_defs.h" + + +/*------------------------------------------------------------------------ +// Vendor, Board Identification definitions +//------------------------------------------------------------------------ +*/ + +#define PCI_VENDOR_ID_CONEXANT 0x14f1 +#define PCI_DEVICE_ID_CN8471 0x8471 +#define PCI_DEVICE_ID_CN8472 0x8472 +#define PCI_DEVICE_ID_CN8474 0x8474 +#define PCI_DEVICE_ID_CN8478 0x8478 +#define PCI_DEVICE_ID_CN8500 0x8500 +#define PCI_DEVICE_ID_CN8501 0x8501 +#define PCI_DEVICE_ID_CN8502 0x8502 +#define PCI_DEVICE_ID_CN8503 0x8503 + +#define INT_QUEUE_SIZE MUSYCC_NIQD + +/* RAM image of MUSYCC registers laid out as a C structure */ + struct musycc_groupr + { + VINT32 thp[32]; /* Transmit Head Pointer [5-29] */ + VINT32 tmp[32]; /* Transmit Message Pointer [5-30] */ + VINT32 rhp[32]; /* Receive Head Pointer [5-29] */ + VINT32 rmp[32]; /* Receive Message Pointer [5-30] */ + VINT8 ttsm[128]; /* Time Slot Map [5-22] */ + VINT8 tscm[256]; /* Subchannel Map [5-24] */ + VINT32 tcct[32]; /* Channel Configuration [5-26] */ + VINT8 rtsm[128]; /* Time Slot Map [5-22] */ + VINT8 rscm[256]; /* Subchannel Map [5-24] */ + VINT32 rcct[32]; /* Channel Configuration [5-26] */ + VINT32 __glcd; /* Global Configuration Descriptor [5-10] */ + VINT32 __iqp; /* Interrupt Queue Pointer [5-36] */ + VINT32 __iql; /* Interrupt Queue Length [5-36] */ + VINT32 grcd; /* Group Configuration Descriptor [5-16] */ + VINT32 mpd; /* Memory Protection Descriptor [5-18] */ + VINT32 mld; /* Message Length Descriptor [5-20] */ + VINT32 pcd; /* Port Configuration Descriptor [5-19] */ + }; + +/* hardware MUSYCC registers laid out as a C structure */ + struct musycc_globalr + { + VINT32 gbp; /* Group Base Pointer */ + VINT32 dacbp; /* Dual Address Cycle Base Pointer */ + VINT32 srd; /* Service Request Descriptor */ + VINT32 isd; /* Interrupt Service Descriptor */ + /* + * adjust __thp due to above 4 registers, which are not contained + * within musycc_groupr[]. All __XXX[] are just place holders, + * anyhow. + */ + VINT32 __thp[32 - 4]; /* Transmit Head Pointer [5-29] */ + VINT32 __tmp[32]; /* Transmit Message Pointer [5-30] */ + VINT32 __rhp[32]; /* Receive Head Pointer [5-29] */ + VINT32 __rmp[32]; /* Receive Message Pointer [5-30] */ + VINT8 ttsm[128]; /* Time Slot Map [5-22] */ + VINT8 tscm[256]; /* Subchannel Map [5-24] */ + VINT32 tcct[32]; /* Channel Configuration [5-26] */ + VINT8 rtsm[128]; /* Time Slot Map [5-22] */ + VINT8 rscm[256]; /* Subchannel Map [5-24] */ + VINT32 rcct[32]; /* Channel Configuration [5-26] */ + VINT32 glcd; /* Global Configuration Descriptor [5-10] */ + VINT32 iqp; /* Interrupt Queue Pointer [5-36] */ + VINT32 iql; /* Interrupt Queue Length [5-36] */ + VINT32 grcd; /* Group Configuration Descriptor [5-16] */ + VINT32 mpd; /* Memory Protection Descriptor [5-18] */ + VINT32 mld; /* Message Length Descriptor [5-20] */ + VINT32 pcd; /* Port Configuration Descriptor [5-19] */ + VINT32 rbist; /* Receive BIST status [5-4] */ + VINT32 tbist; /* Receive BIST status [5-4] */ + }; + +/* Global Config Descriptor bit macros */ +#define MUSYCC_GCD_ECLK_ENABLE 0x00000800 /* EBUS clock enable */ +#define MUSYCC_GCD_INTEL_SELECT 0x00000400 /* MPU type select */ +#define MUSYCC_GCD_INTA_DISABLE 0x00000008 /* PCI INTA disable */ +#define MUSYCC_GCD_INTB_DISABLE 0x00000004 /* PCI INTB disable */ +#define MUSYCC_GCD_BLAPSE 12 /* Position index for BLAPSE bit + * field */ +#define MUSYCC_GCD_ALAPSE 8 /* Position index for ALAPSE bit + * field */ +#define MUSYCC_GCD_ELAPSE 4 /* Position index for ELAPSE bit + * field */ +#define MUSYCC_GCD_PORTMAP_3 3 /* Reserved */ +#define MUSYCC_GCD_PORTMAP_2 2 /* Port 0=>Grp 0,1,2,3; Port 1=>Grp + * 4,5,6,7 */ +#define MUSYCC_GCD_PORTMAP_1 1 /* Port 0=>Grp 0,1; Port 1=>Grp 2,3, + * etc... */ +#define MUSYCC_GCD_PORTMAP_0 0 /* Port 0=>Grp 0; Port 1=>Grp 2, + * etc... */ + +/* and board specific assignments... */ +#ifdef SBE_WAN256T3_ENABLE +#define BLAPSE_VAL 0 +#define ALAPSE_VAL 0 +#define ELAPSE_VAL 7 +#define PORTMAP_VAL MUSYCC_GCD_PORTMAP_2 +#endif + +#ifdef SBE_PMCC4_ENABLE +#define BLAPSE_VAL 7 +#define ALAPSE_VAL 3 +#define ELAPSE_VAL 7 +#define PORTMAP_VAL MUSYCC_GCD_PORTMAP_0 +#endif + +#define GCD_MAGIC (((BLAPSE_VAL)<<(MUSYCC_GCD_BLAPSE)) | \ + ((ALAPSE_VAL)<<(MUSYCC_GCD_ALAPSE)) | \ + ((ELAPSE_VAL)<<(MUSYCC_GCD_ELAPSE)) | \ + (MUSYCC_GCD_ECLK_ENABLE) | PORTMAP_VAL) + +/* Group Config Descriptor bit macros */ +#define MUSYCC_GRCD_RX_ENABLE 0x00000001 /* Enable receive processing */ +#define MUSYCC_GRCD_TX_ENABLE 0x00000002 /* Enable transmit processing */ +#define MUSYCC_GRCD_SUBCHAN_DISABLE 0x00000004 /* Master disable for + * subchanneling */ +#define MUSYCC_GRCD_OOFMP_DISABLE 0x00000008 /* Out of Frame message + * processing disabled all + * channels */ +#define MUSYCC_GRCD_OOFIRQ_DISABLE 0x00000010 /* Out of Frame/In Frame irqs + * disabled */ +#define MUSYCC_GRCD_COFAIRQ_DISABLE 0x00000020 /* Change of Frame Alignment + * irq disabled */ +#define MUSYCC_GRCD_INHRBSD 0x00000100 /* Receive Buffer Status + * overwrite disabled */ +#define MUSYCC_GRCD_INHTBSD 0x00000200 /* Transmit Buffer Status + * overwrite disabled */ +#define MUSYCC_GRCD_SF_ALIGN 0x00008000 /* External frame sync */ +#define MUSYCC_GRCD_MC_ENABLE 0x00000040 /* Message configuration bits + * copy enable. Conexant sez + * turn this on */ +#define MUSYCC_GRCD_POLLTH_16 0x00000001 /* Poll every 16th frame */ +#define MUSYCC_GRCD_POLLTH_32 0x00000002 /* Poll every 32nd frame */ +#define MUSYCC_GRCD_POLLTH_64 0x00000003 /* Poll every 64th frame */ +#define MUSYCC_GRCD_POLLTH_SHIFT 10 /* Position index for poll throttle + * bit field */ +#define MUSYCC_GRCD_SUERM_THRESH_SHIFT 16 /* Position index for SUERM + * count threshold */ + +/* Port Config Descriptor bit macros */ +#define MUSYCC_PCD_E1X2_MODE 2 /* Port mode in bits 0-2. T1 and E1 */ +#define MUSYCC_PCD_E1X4_MODE 3 /* are defined in cn847x.h */ +#define MUSYCC_PCD_NX64_MODE 4 +#define MUSYCC_PCD_TXDATA_RISING 0x00000010 /* Sample Tx data on TCLK + * rising edge */ +#define MUSYCC_PCD_TXSYNC_RISING 0x00000020 /* Sample Tx frame sync on + * TCLK rising edge */ +#define MUSYCC_PCD_RXDATA_RISING 0x00000040 /* Sample Rx data on RCLK + * rising edge */ +#define MUSYCC_PCD_RXSYNC_RISING 0x00000080 /* Sample Rx frame sync on + * RCLK rising edge */ +#define MUSYCC_PCD_ROOF_RISING 0x00000100 /* Sample Rx Out Of Frame + * signal on RCLK rising edge */ +#define MUSYCC_PCD_TX_DRIVEN 0x00000200 /* No mapped timeslots causes + * logic 1 on output, else + * tristate */ +#define MUSYCC_PCD_PORTMODE_MASK 0xfffffff8 /* For changing the port mode + * between E1 and T1 */ + +/* Time Slot Descriptor bit macros */ +#define MUSYCC_TSD_MODE_64KBPS 4 +#define MUSYCC_TSD_MODE_56KBPS 5 +#define MUSYCC_TSD_SUBCHANNEL_WO_FIRST 6 +#define MUSYCC_TSD_SUBCHANNEL_WITH_FIRST 7 + +/* Message Descriptor bit macros */ +#define MUSYCC_MDT_BASE03_ADDR 0x00006000 + +/* Channel Config Descriptor bit macros */ +#define MUSYCC_CCD_BUFIRQ_DISABLE 0x00000002 /* BUFF and ONR irqs disabled */ +#define MUSYCC_CCD_EOMIRQ_DISABLE 0x00000004 /* EOM irq disabled */ +#define MUSYCC_CCD_MSGIRQ_DISABLE 0x00000008 /* LNG, FCS, ALIGN, and ABT + * irqs disabled */ +#define MUSYCC_CCD_IDLEIRQ_DISABLE 0x00000010 /* CHABT, CHIC, and SHT irqs + * disabled */ +#define MUSYCC_CCD_FILTIRQ_DISABLE 0x00000020 /* SFILT irq disabled */ +#define MUSYCC_CCD_SDECIRQ_DISABLE 0x00000040 /* SDEC irq disabled */ +#define MUSYCC_CCD_SINCIRQ_DISABLE 0x00000080 /* SINC irq disabled */ +#define MUSYCC_CCD_SUERIRQ_DISABLE 0x00000100 /* SUERR irq disabled */ +#define MUSYCC_CCD_FCS_XFER 0x00000200 /* Propagate FCS along with + * received data */ +#define MUSYCC_CCD_PROTO_SHIFT 12 /* Position index for protocol bit + * field */ +#define MUSYCC_CCD_TRANS 0 /* Protocol mode in bits 12-14 */ +#define MUSYCC_CCD_SS7 1 +#define MUSYCC_CCD_HDLC_FCS16 2 +#define MUSYCC_CCD_HDLC_FCS32 3 +#define MUSYCC_CCD_EOPIRQ_DISABLE 0x00008000 /* EOP irq disabled */ +#define MUSYCC_CCD_INVERT_DATA 0x00800000 /* Invert data */ +#define MUSYCC_CCD_MAX_LENGTH 10 /* Position index for max length bit + * field */ +#define MUSYCC_CCD_BUFFER_LENGTH 16 /* Position index for internal data + * buffer length */ +#define MUSYCC_CCD_BUFFER_LOC 24 /* Position index for internal data + * buffer starting location */ + +/**************************************************************************** + * Interrupt Descriptor Information */ + +#define INT_EMPTY_ENTRY 0xfeedface +#define INT_EMPTY_ENTRY2 0xdeadface + +/**************************************************************************** + * Interrupt Status Descriptor + * + * NOTE: One must first fetch the value of the interrupt status descriptor + * into a local variable, then pass that value into the read macros. This + * is required to avoid race conditions. + ***/ + +#define INTRPTS_NEXTINT_M 0x7FFF0000 +#define INTRPTS_NEXTINT_S 16 +#define INTRPTS_NEXTINT(x) ((x & INTRPTS_NEXTINT_M) >> INTRPTS_NEXTINT_S) + +#define INTRPTS_INTFULL_M 0x00008000 +#define INTRPTS_INTFULL_S 15 +#define INTRPTS_INTFULL(x) ((x & INTRPTS_INTFULL_M) >> INTRPTS_INTFULL_S) + +#define INTRPTS_INTCNT_M 0x00007FFF +#define INTRPTS_INTCNT_S 0 +#define INTRPTS_INTCNT(x) ((x & INTRPTS_INTCNT_M) >> INTRPTS_INTCNT_S) + + +/**************************************************************************** + * Interrupt Descriptor + ***/ + +#define INTRPT_DIR_M 0x80000000 +#define INTRPT_DIR_S 31 +#define INTRPT_DIR(x) ((x & INTRPT_DIR_M) >> INTRPT_DIR_S) + +#define INTRPT_GRP_M 0x60000000 +#define INTRPT_GRP_MSB_M 0x00004000 +#define INTRPT_GRP_S 29 +#define INTRPT_GRP_MSB_S 12 +#define INTRPT_GRP(x) (((x & INTRPT_GRP_M) >> INTRPT_GRP_S) | \ + ((x & INTRPT_GRP_MSB_M) >> INTRPT_GRP_MSB_S)) + +#define INTRPT_CH_M 0x1F000000 +#define INTRPT_CH_S 24 +#define INTRPT_CH(x) ((x & INTRPT_CH_M) >> INTRPT_CH_S) + +#define INTRPT_EVENT_M 0x00F00000 +#define INTRPT_EVENT_S 20 +#define INTRPT_EVENT(x) ((x & INTRPT_EVENT_M) >> INTRPT_EVENT_S) + +#define INTRPT_ERROR_M 0x000F0000 +#define INTRPT_ERROR_S 16 +#define INTRPT_ERROR(x) ((x & INTRPT_ERROR_M) >> INTRPT_ERROR_S) + +#define INTRPT_ILOST_M 0x00008000 +#define INTRPT_ILOST_S 15 +#define INTRPT_ILOST(x) ((x & INTRPT_ILOST_M) >> INTRPT_ILOST_S) + +#define INTRPT_PERR_M 0x00004000 +#define INTRPT_PERR_S 14 +#define INTRPT_PERR(x) ((x & INTRPT_PERR_M) >> INTRPT_PERR_S) + +#define INTRPT_BLEN_M 0x00003FFF +#define INTRPT_BLEN_S 0 +#define INTRPT_BLEN(x) ((x & INTRPT_BLEN_M) >> INTRPT_BLEN_S) + + +/* Buffer Descriptor bit macros */ +#define OWNER_BIT 0x80000000 /* Set for MUSYCC owner on xmit, host + * owner on receive */ +#define HOST_TX_OWNED 0x00000000 /* Host owns descriptor */ +#define MUSYCC_TX_OWNED 0x80000000 /* MUSYCC owns descriptor */ +#define HOST_RX_OWNED 0x80000000 /* Host owns descriptor */ +#define MUSYCC_RX_OWNED 0x00000000 /* MUSYCC owns descriptor */ + +#define POLL_DISABLED 0x40000000 /* MUSYCC not allowed to poll buffer + * for ownership */ +#define EOMIRQ_ENABLE 0x20000000 /* This buffer contains the end of + * the message */ +#define EOBIRQ_ENABLE 0x10000000 /* EOB irq enabled */ +#define PADFILL_ENABLE 0x01000000 /* Enable padfill */ +#define REPEAT_BIT 0x00008000 /* Bit on for FISU descriptor */ +#define LENGTH_MASK 0X3fff /* This part of status descriptor is + * length */ +#define IDLE_CODE 25 /* Position index for idle code (2 + * bits) */ +#define EXTRA_FLAGS 16 /* Position index for minimum flags + * between messages (8 bits) */ +#define IDLE_CODE_MASK 0x03 /* Gets rid of garbage before the + * pattern is OR'd in */ +#define EXTRA_FLAGS_MASK 0xff /* Gets rid of garbage before the + * pattern is OR'd in */ +#define PCI_PERMUTED_OWNER_BIT 0x00000080 /* For flipping the bit on + * the polled mode descriptor */ + +/* Service Request Descriptor bit macros */ +#define SREQ 8 /* Position index for service request bit + * field */ +#define SR_NOOP (0<<(SREQ)) /* No Operation. Generates SACK */ +#define SR_CHIP_RESET (1<<(SREQ)) /* Soft chip reset */ +#define SR_GROUP_RESET (2<<(SREQ)) /* Group reset */ +#define SR_GLOBAL_INIT (4<<(SREQ)) /* Global init: read global + * config deswc and interrupt + * queue desc */ +#define SR_GROUP_INIT (5<<(SREQ)) /* Group init: read Timeslot + * and Subchannel maps, + * Channel Config, */ + /* + * Group Config, Memory Protect, Message Length, and Port Config + * Descriptors + */ +#define SR_CHANNEL_ACTIVATE (8<<(SREQ)) /* Init channel, read Head + * Pointer, process first + * Message Descriptor */ +#define SR_GCHANNEL_MASK 0x001F /* channel portion (gchan) */ +#define SR_CHANNEL_DEACTIVATE (9<<(SREQ)) /* Stop channel processing */ +#define SR_JUMP (10<<(SREQ)) /* a: Process new Message + * List */ +#define SR_CHANNEL_CONFIG (11<<(SREQ)) /* b: Read channel + * Configuration Descriptor */ +#define SR_GLOBAL_CONFIG (16<<(SREQ)) /* 10: Read Global + * Configuration Descriptor */ +#define SR_INTERRUPT_Q (17<<(SREQ)) /* 11: Read Interrupt Queue + * Descriptor */ +#define SR_GROUP_CONFIG (18<<(SREQ)) /* 12: Read Group + * Configuration Descriptor */ +#define SR_MEMORY_PROTECT (19<<(SREQ)) /* 13: Read Memory Protection + * Descriptor */ +#define SR_MESSAGE_LENGTH (20<<(SREQ)) /* 14: Read Message Length + * Descriptor */ +#define SR_PORT_CONFIG (21<<(SREQ)) /* 15: Read Port + * Configuration Descriptor */ +#define SR_TIMESLOT_MAP (24<<(SREQ)) /* 18: Read Timeslot Map */ +#define SR_SUBCHANNEL_MAP (25<<(SREQ)) /* 19: Read Subchannel Map */ +#define SR_CHAN_CONFIG_TABLE (26<<(SREQ)) /* 20: Read Channel + * Configuration Table for + * the group */ +#define SR_TX_DIRECTION 0x00000020 /* Transmit direction bit. + * Bit off indicates receive + * direction */ +#define SR_RX_DIRECTION 0x00000000 + +/* Interrupt Descriptor bit macros */ +#define GROUP10 29 /* Position index for the 2 LS group + * bits */ +#define CHANNEL 24 /* Position index for channel bits */ +#define INT_IQD_TX 0x80000000 +#define INT_IQD_GRP 0x60000000 +#define INT_IQD_CHAN 0x1f000000 +#define INT_IQD_EVENT 0x00f00000 +#define INT_IQD_ERROR 0x000f0000 +#define INT_IQD_ILOST 0x00008000 +#define INT_IQD_PERR 0x00004000 +#define INT_IQD_BLEN 0x00003fff + +/* Interrupt Descriptor Events */ +#define EVE_EVENT 20 /* Position index for event bits */ +#define EVE_NONE 0 /* No event to report in this + * interrupt */ +#define EVE_SACK 1 /* Service Request acknowledge */ +#define EVE_EOB 2 /* End of Buffer */ +#define EVE_EOM 3 /* End of Message */ +#define EVE_EOP 4 /* End of Padfill */ +#define EVE_CHABT 5 /* Change to Abort Code */ +#define EVE_CHIC 6 /* Change to Idle Code */ +#define EVE_FREC 7 /* Frame Recovery */ +#define EVE_SINC 8 /* MTP2 SUERM Increment */ +#define EVE_SDEC 9 /* MTP2 SUERM Decrement */ +#define EVE_SFILT 10 /* MTP2 SUERM Filtered Message */ +/* Interrupt Descriptor Errors */ +#define ERR_ERRORS 16 /* Position index for error bits */ +#define ERR_BUF 1 /* Buffer Error */ +#define ERR_COFA 2 /* Change of Frame Alignment Error */ +#define ERR_ONR 3 /* Owner Bit Error */ +#define ERR_PROT 4 /* Memory Protection Error */ +#define ERR_OOF 8 /* Out of Frame Error */ +#define ERR_FCS 9 /* FCS Error */ +#define ERR_ALIGN 10 /* Octet Alignment Error */ +#define ERR_ABT 11 /* Abort Termination */ +#define ERR_LNG 12 /* Long Message Error */ +#define ERR_SHT 13 /* Short Message Error */ +#define ERR_SUERR 14 /* SUERM threshold exceeded */ +#define ERR_PERR 15 /* PCI Parity Error */ +/* Other Stuff */ +#define TRANSMIT_DIRECTION 0x80000000 /* Transmit direction bit. Bit off + * indicates receive direction */ +#define ILOST 0x00008000 /* Interrupt Lost */ +#define GROUPMSB 0x00004000 /* Group number MSB */ +#define SACK_IMAGE 0x00100000 /* Used in IRQ for semaphore test */ +#define INITIAL_STATUS 0x10000 /* IRQ status should be this after + * reset */ + +/* This must be defined on an entire channel group (Port) basis */ +#define SUERM_THRESHOLD 0x1f + +#undef VINT32 +#undef VINT8 + +#endif /*** _INC_MUSYCC_H_ ***/ + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/ossiRelease.c b/drivers/staging/cxt1e1/ossiRelease.c new file mode 100644 index 00000000..f17a902e --- /dev/null +++ b/drivers/staging/cxt1e1/ossiRelease.c @@ -0,0 +1,29 @@ +/*----------------------------------------------------------------------------- + * ossiRelease.c - + * + * This string will be embedded into the executable and will track the + * release. The embedded string may be displayed using the following: + * + * strings | grep \$Rel + * + * Copyright (C) 2002-2008 One Stop Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@onestopsystems.com + * One Stop Systems, Inc. Escondido, California U.S.A. + * + *----------------------------------------------------------------------------- + */ + +char pmcc4_OSSI_release[] = "$Release: PMCC4_3_1B, Copyright (c) 2008 One Stop Systems$"; + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/pmc93x6_eeprom.c b/drivers/staging/cxt1e1/pmc93x6_eeprom.c new file mode 100644 index 00000000..62b12fb4 --- /dev/null +++ b/drivers/staging/cxt1e1/pmc93x6_eeprom.c @@ -0,0 +1,557 @@ +/* pmc93x6_eeprom.c - PMC's 93LC46 EEPROM Device + * + * The 93LC46 is a low-power, serial Electrically Erasable and + * Programmable Read Only Memory organized as 128 8-bit bytes. + * + * Accesses to the 93LC46 are done in a bit serial stream, organized + * in a 3 wire format. Writes are internally timed by the device + * (the In data bit is pulled low until the write is complete and + * then is pulled high) and take about 6 milliseconds. + * + * Copyright (C) 2003-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include "pmcc4_sysdep.h" +#include "sbecom_inline_linux.h" +#include "pmcc4.h" +#include "sbe_promformat.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#ifdef SBE_INCLUDE_SYMBOLS +#define STATIC +#else +#define STATIC static +#endif + + +/*------------------------------------------------------------------------ + * EEPROM address definitions + *------------------------------------------------------------------------ + * + * The offset in the definitions below allows the test to skip over + * areas of the EEPROM that other programs (such a VxWorks) are + * using. + */ + +#define EE_MFG (long)0 /* Index to manufacturing record */ +#define EE_FIRST 0x28 /* Index to start testing at */ +#define EE_LIMIT 128 /* Index to end testing at */ + + +/* Bit Ordering for Instructions +** +** A0, A1, A2, A3, A4, A5, A6, OP0, OP1, SB (lsb, or 1st bit out) +** +*/ + +#define EPROM_EWEN 0x0019 /* Erase/Write enable (reversed) */ +#define EPROM_EWDS 0x0001 /* Erase/Write disable (reversed) */ +#define EPROM_READ 0x0003 /* Read (reversed) */ +#define EPROM_WRITE 0x0005 /* Write (reversed) */ +#define EPROM_ERASE 0x0007 /* Erase (reversed) */ +#define EPROM_ERAL 0x0009 /* Erase All (reversed) */ +#define EPROM_WRAL 0x0011 /* Write All (reversed) */ + +#define EPROM_ADR_SZ 7 /* Number of bits in offset address */ +#define EPROM_OP_SZ 3 /* Number of bits in command */ +#define SIZE_ADDR_OP (EPROM_ADR_SZ + EPROM_OP_SZ) +#define LC46A_MAX_OPS 10 /* Number of bits in Instruction */ +#define NUM_OF_BITS 8 /* Number of bits in data */ + + +/* EEPROM signal bits */ +#define EPROM_ACTIVE_OUT_BIT 0x0001 /* Out data bit */ +#define EPROM_ACTIVE_IN_BIT 0x0002 /* In data bit */ +#define ACTIVE_IN_BIT_SHIFT 0x0001 /* Shift In data bit to LSB */ +#define EPROM_ENCS 0x0004 /* Set EEPROM CS during operation */ + + +/*------------------------------------------------------------------------ + * The ByteReverse table is used to reverses the 8 bits within a byte + *------------------------------------------------------------------------ + */ + +static unsigned char ByteReverse[256]; +static int ByteReverseBuilt = FALSE; + + +/*------------------------------------------------------------------------ + * mfg_template - initial serial EEPROM data structure + *------------------------------------------------------------------------ + */ + +short mfg_template[sizeof (FLD_TYPE2)] = +{ + PROM_FORMAT_TYPE2, /* type; */ + 0x00, 0x1A, /* length[2]; */ + 0x00, 0x00, 0x00, 0x00, /* Crc32[4]; */ + 0x11, 0x76, /* Id[2]; */ + 0x07, 0x05, /* SubId[2] E1; */ + 0x00, 0xA0, 0xD6, 0x00, 0x00, 0x00, /* Serial[6]; */ + 0x00, 0x00, 0x00, 0x00, /* CreateTime[4]; */ + 0x00, 0x00, 0x00, 0x00, /* HeatRunTime[4]; */ + 0x00, 0x00, 0x00, 0x00, /* HeatRunIterations[4]; */ + 0x00, 0x00, 0x00, 0x00, /* HeatRunErrors[4]; */ +}; + + +/*------------------------------------------------------------------------ + * BuildByteReverse - build the 8-bit reverse table + *------------------------------------------------------------------------ + * + * The 'ByteReverse' table reverses the 8 bits within a byte + * (the MSB becomes the LSB etc.). + */ + +STATIC void +BuildByteReverse (void) +{ + long half; /* Used to build by powers to 2 */ + int i; + + ByteReverse[0] = 0; + + for (half = 1; half < sizeof (ByteReverse); half <<= 1) + for (i = 0; i < half; i++) + ByteReverse[half + i] = (char) (ByteReverse[i] | (0x80 / half)); + + ByteReverseBuilt = TRUE; +} + + +/*------------------------------------------------------------------------ + * eeprom_delay - small delay for EEPROM timing + *------------------------------------------------------------------------ + */ + +STATIC void +eeprom_delay (void) +{ + int timeout; + + for (timeout = 20; timeout; --timeout) + { + OS_uwait_dummy (); + } +} + + +/*------------------------------------------------------------------------ + * eeprom_put_byte - Send a byte to the EEPROM serially + *------------------------------------------------------------------------ + * + * Given the PCI address and the data, this routine serially sends + * the data to the EEPROM. + */ + +void +eeprom_put_byte (long addr, long data, int count) +{ + u_int32_t output; + + while (--count >= 0) + { + output = (data & EPROM_ACTIVE_OUT_BIT) ? 1 : 0; /* Get next data bit */ + output |= EPROM_ENCS; /* Add Chip Select */ + data >>= 1; + + eeprom_delay (); + pci_write_32 ((u_int32_t *) addr, output); /* Output it */ + } +} + + +/*------------------------------------------------------------------------ + * eeprom_get_byte - Receive a byte from the EEPROM serially + *------------------------------------------------------------------------ + * + * Given the PCI address, this routine serially fetches the data + * from the EEPROM. + */ + +u_int32_t +eeprom_get_byte (long addr) +{ + u_int32_t input; + u_int32_t data; + int count; + +/* Start the Reading of DATA +** +** The first read is a dummy as the data is latched in the +** EPLD and read on the next read access to the EEPROM. +*/ + + input = pci_read_32 ((u_int32_t *) addr); + + data = 0; + count = NUM_OF_BITS; + while (--count >= 0) + { + eeprom_delay (); + input = pci_read_32 ((u_int32_t *) addr); + + data <<= 1; /* Shift data over */ + data |= (input & EPROM_ACTIVE_IN_BIT) ? 1 : 0; + + } + + return data; +} + + +/*------------------------------------------------------------------------ + * disable_pmc_eeprom - Disable writes to the EEPROM + *------------------------------------------------------------------------ + * + * Issue the EEPROM command to disable writes. + */ + +STATIC void +disable_pmc_eeprom (long addr) +{ + eeprom_put_byte (addr, EPROM_EWDS, SIZE_ADDR_OP); + + pci_write_32 ((u_int32_t *) addr, 0); /* this removes Chip Select + * from EEPROM */ +} + + +/*------------------------------------------------------------------------ + * enable_pmc_eeprom - Enable writes to the EEPROM + *------------------------------------------------------------------------ + * + * Issue the EEPROM command to enable writes. + */ + +STATIC void +enable_pmc_eeprom (long addr) +{ + eeprom_put_byte (addr, EPROM_EWEN, SIZE_ADDR_OP); + + pci_write_32 ((u_int32_t *) addr, 0); /* this removes Chip Select + * from EEPROM */ +} + + +/*------------------------------------------------------------------------ + * pmc_eeprom_read - EEPROM location read + *------------------------------------------------------------------------ + * + * Given a EEPROM PCI address and location offset, this routine returns + * the contents of the specified location to the calling routine. + */ + +u_int32_t +pmc_eeprom_read (long addr, long mem_offset) +{ + u_int32_t data; /* Data from chip */ + + if (!ByteReverseBuilt) + BuildByteReverse (); + + mem_offset = ByteReverse[0x7F & mem_offset]; /* Reverse address */ + /* + * NOTE: The max offset address is 128 or half the reversal table. So the + * LSB is always zero and counts as a built in shift of one bit. So even + * though we need to shift 3 bits to make room for the command, we only + * need to shift twice more because of the built in shift. + */ + mem_offset <<= 2; /* Shift for command */ + mem_offset |= EPROM_READ; /* Add command */ + + eeprom_put_byte (addr, mem_offset, SIZE_ADDR_OP); /* Output chip address */ + + data = eeprom_get_byte (addr); /* Read chip data */ + + pci_write_32 ((u_int32_t *) addr, 0); /* Remove Chip Select from + * EEPROM */ + + return (data & 0x000000FF); +} + + +/*------------------------------------------------------------------------ + * pmc_eeprom_write - EEPROM location write + *------------------------------------------------------------------------ + * + * Given a EEPROM PCI address, location offset and value, this + * routine writes the value to the specified location. + * + * Note: it is up to the caller to determine if the write + * operation succeeded. + */ + +int +pmc_eeprom_write (long addr, long mem_offset, u_int32_t data) +{ + volatile u_int32_t temp; + int count; + + if (!ByteReverseBuilt) + BuildByteReverse (); + + mem_offset = ByteReverse[0x7F & mem_offset]; /* Reverse address */ + /* + * NOTE: The max offset address is 128 or half the reversal table. So the + * LSB is always zero and counts as a built in shift of one bit. So even + * though we need to shift 3 bits to make room for the command, we only + * need to shift twice more because of the built in shift. + */ + mem_offset <<= 2; /* Shift for command */ + mem_offset |= EPROM_WRITE; /* Add command */ + + eeprom_put_byte (addr, mem_offset, SIZE_ADDR_OP); /* Output chip address */ + + data = ByteReverse[0xFF & data];/* Reverse data */ + eeprom_put_byte (addr, data, NUM_OF_BITS); /* Output chip data */ + + pci_write_32 ((u_int32_t *) addr, 0); /* Remove Chip Select from + * EEPROM */ + +/* +** Must see Data In at a low state before completing this transaction. +** +** Afterwards, the data bit will return to a high state, ~6 ms, terminating +** the operation. +*/ + pci_write_32 ((u_int32_t *) addr, EPROM_ENCS); /* Re-enable Chip Select */ + temp = pci_read_32 ((u_int32_t *) addr); /* discard first read */ + temp = pci_read_32 ((u_int32_t *) addr); + if (temp & EPROM_ACTIVE_IN_BIT) + { + temp = pci_read_32 ((u_int32_t *) addr); + if (temp & EPROM_ACTIVE_IN_BIT) + { + pci_write_32 ((u_int32_t *) addr, 0); /* Remove Chip Select + * from EEPROM */ + return (1); + } + } + count = 1000; + while (count--) + { + for (temp = 0; temp < 0x10; temp++) + OS_uwait_dummy (); + + if (pci_read_32 ((u_int32_t *) addr) & EPROM_ACTIVE_IN_BIT) + break; + } + + if (count == -1) + return (2); + + return (0); +} + + +/*------------------------------------------------------------------------ + * pmcGetBuffValue - read the specified value from buffer + *------------------------------------------------------------------------ + */ + +long +pmcGetBuffValue (char *ptr, int size) +{ + long value = 0; + int index; + + for (index = 0; index < size; ++index) + { + value <<= 8; + value |= ptr[index] & 0xFF; + } + + return value; +} + + +/*------------------------------------------------------------------------ + * pmcSetBuffValue - save the specified value to buffer + *------------------------------------------------------------------------ + */ + +void +pmcSetBuffValue (char *ptr, long value, int size) +{ + int index = size; + + while (--index >= 0) + { + ptr[index] = (char) (value & 0xFF); + value >>= 8; + } +} + + +/*------------------------------------------------------------------------ + * pmc_eeprom_read_buffer - read EEPROM data into specified buffer + *------------------------------------------------------------------------ + */ + +void +pmc_eeprom_read_buffer (long addr, long mem_offset, char *dest_ptr, int size) +{ + while (--size >= 0) + *dest_ptr++ = (char) pmc_eeprom_read (addr, mem_offset++); +} + + +/*------------------------------------------------------------------------ + * pmc_eeprom_write_buffer - write EEPROM data from specified buffer + *------------------------------------------------------------------------ + */ + +void +pmc_eeprom_write_buffer (long addr, long mem_offset, char *dest_ptr, int size) +{ + enable_pmc_eeprom (addr); + + while (--size >= 0) + pmc_eeprom_write (addr, mem_offset++, *dest_ptr++); + + disable_pmc_eeprom (addr); +} + + +/*------------------------------------------------------------------------ + * pmcCalcCrc - calculate the CRC for the serial EEPROM structure + *------------------------------------------------------------------------ + */ + +u_int32_t +pmcCalcCrc_T01 (void *bufp) +{ + FLD_TYPE2 *buf = bufp; + u_int32_t crc; /* CRC of the structure */ + + /* Calc CRC for type and length fields */ + sbeCrc ( + (u_int8_t *) &buf->type, + (u_int32_t) STRUCT_OFFSET (FLD_TYPE1, Crc32), + (u_int32_t) 0, + (u_int32_t *) &crc); + +#ifdef EEPROM_TYPE_DEBUG + pr_info("sbeCrc: crc 1 calculated as %08x\n", crc); /* RLD DEBUG */ +#endif + return ~crc; +} + +u_int32_t +pmcCalcCrc_T02 (void *bufp) +{ + FLD_TYPE2 *buf = bufp; + u_int32_t crc; /* CRC of the structure */ + + /* Calc CRC for type and length fields */ + sbeCrc ( + (u_int8_t *) &buf->type, + (u_int32_t) STRUCT_OFFSET (FLD_TYPE2, Crc32), + (u_int32_t) 0, + (u_int32_t *) &crc); + + /* Calc CRC for remaining fields */ + sbeCrc ( + (u_int8_t *) &buf->Id[0], + (u_int32_t) (sizeof (FLD_TYPE2) - STRUCT_OFFSET (FLD_TYPE2, Id)), + (u_int32_t) crc, + (u_int32_t *) &crc); + +#ifdef EEPROM_TYPE_DEBUG + pr_info("sbeCrc: crc 2 calculated as %08x\n", crc); /* RLD DEBUG */ +#endif + return crc; +} + + +/*------------------------------------------------------------------------ + * pmc_init_seeprom - initialize the serial EEPROM structure + *------------------------------------------------------------------------ + * + * At the front of the serial EEPROM there is a record that contains + * manufacturing information. If the info does not already exist, it + * is created. The only field modifiable by the operator is the + * serial number field. + */ + +void +pmc_init_seeprom (u_int32_t addr, u_int32_t serialNum) +{ + PROMFORMAT buffer; /* Memory image of structure */ + u_int32_t crc; /* CRC of structure */ + time_t createTime; + int i; + + createTime = get_seconds (); + + /* use template data */ + for (i = 0; i < sizeof (FLD_TYPE2); ++i) + buffer.bytes[i] = mfg_template[i]; + + /* Update serial number field in buffer */ + pmcSetBuffValue (&buffer.fldType2.Serial[3], serialNum, 3); + + /* Update create time field in buffer */ + pmcSetBuffValue (&buffer.fldType2.CreateTime[0], createTime, 4); + + /* Update CRC field in buffer */ + crc = pmcCalcCrc_T02 (&buffer); + pmcSetBuffValue (&buffer.fldType2.Crc32[0], crc, 4); + +#ifdef DEBUG + for (i = 0; i < sizeof (FLD_TYPE2); ++i) + pr_info("[%02X] = %02X\n", i, buffer.bytes[i] & 0xFF); +#endif + + /* Write structure to serial EEPROM */ + pmc_eeprom_write_buffer (addr, EE_MFG, (char *) &buffer, sizeof (FLD_TYPE2)); +} + + +char +pmc_verify_cksum (void *bufp) +{ + FLD_TYPE1 *buf1 = bufp; + FLD_TYPE2 *buf2 = bufp; + u_int32_t crc1, crc2; /* CRC read from EEPROM */ + + /* Retrieve contents of CRC field */ + crc1 = pmcGetBuffValue (&buf1->Crc32[0], sizeof (buf1->Crc32)); +#ifdef EEPROM_TYPE_DEBUG + pr_info("EEPROM: chksum 1 reads as %08x\n", crc1); /* RLD DEBUG */ +#endif + if ((buf1->type == PROM_FORMAT_TYPE1) && + (pmcCalcCrc_T01 ((void *) buf1) == crc1)) + return PROM_FORMAT_TYPE1; /* checksum type 1 verified */ + + crc2 = pmcGetBuffValue (&buf2->Crc32[0], sizeof (buf2->Crc32)); +#ifdef EEPROM_TYPE_DEBUG + pr_info("EEPROM: chksum 2 reads as %08x\n", crc2); /* RLD DEBUG */ +#endif + if ((buf2->type == PROM_FORMAT_TYPE2) && + (pmcCalcCrc_T02 ((void *) buf2) == crc2)) + return PROM_FORMAT_TYPE2; /* checksum type 2 verified */ + + return PROM_FORMAT_Unk; /* failed to validate */ +} + + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/pmc93x6_eeprom.h b/drivers/staging/cxt1e1/pmc93x6_eeprom.h new file mode 100644 index 00000000..96c48cb8 --- /dev/null +++ b/drivers/staging/cxt1e1/pmc93x6_eeprom.h @@ -0,0 +1,39 @@ +#ifndef _INC_PMC93X6_EEPROM_H_ +#define _INC_PMC93X6_EEPROM_H_ + +/*----------------------------------------------------------------------------- + * pmc93x6_eeprom.h - + * + * Copyright (C) 2002-2004 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + +#include + +#ifdef __KERNEL__ + +#include "pmcc4_private.h" + +void pmc_eeprom_read_buffer (long, long, char *, int); +void pmc_eeprom_write_buffer (long, long, char *, int); +void pmc_init_seeprom (u_int32_t, u_int32_t); +char pmc_verify_cksum (void *); + +#endif /*** __KERNEL__ ***/ + +#endif + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/pmcc4.h b/drivers/staging/cxt1e1/pmcc4.h new file mode 100644 index 00000000..b0ed4ad1 --- /dev/null +++ b/drivers/staging/cxt1e1/pmcc4.h @@ -0,0 +1,109 @@ +#ifndef _INC_PMCC4_H_ +#define _INC_PMCC4_H_ + +/*----------------------------------------------------------------------------- + * pmcc4.h - + * + * Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + +#include + +typedef int status_t; + +#define SBE_DRVR_FAIL 0 +#define SBE_DRVR_SUCCESS 1 + +/********************/ +/* PMCC4 memory Map */ +/********************/ + +#define COMET_OFFSET(x) (0x80000+(x)*0x10000) +#define EEPROM_OFFSET 0xC0000 +#define CPLD_OFFSET 0xD0000 + + struct pmcc4_timeslot_param + { + u_int8_t card; /* the card number */ + u_int8_t port; /* the port number */ + u_int8_t _reserved1; + u_int8_t _reserved2; + + /* + * each byte in bitmask below represents one timeslot (bitmask[0] is + * for timeslot 0 and so on), each bit in the byte selects timeslot + * bits for this channel (0xff - whole timeslot, 0x7f - 56kbps mode) + */ + u_int8_t bitmask[32]; + }; + + struct c4_musycc_param + { + u_int8_t RWportnum; + u_int16_t offset; + u_int32_t value; + }; + +/*Alarm values */ +#define sbeE1RMAI 0x100 +#define sbeYelAlm 0x04 +#define sbeRedAlm 0x02 +#define sbeAISAlm 0x01 + +#define sbeE1errSMF 0x02 +#define sbeE1CRC 0x01 + +#ifdef __KERNEL__ + +/* + * Device Driver interface, routines are for internal use only. + */ + +#include "pmcc4_private.h" + +char *get_hdlc_name (hdlc_device *); + +/* + * external interface + */ + +void c4_cleanup (void); +status_t c4_chan_up (ci_t *, int channum); +status_t c4_del_chan_stats (int channum); +status_t c4_del_chan (int channum); +status_t c4_get_iidinfo (ci_t * ci, struct sbe_iid_info * iip); +int c4_is_chan_up (int channum); + +void *getuserbychan (int channum); +void pci_flush_write (ci_t * ci); +void sbecom_set_loglevel (int debuglevel); +char *sbeid_get_bdname (ci_t * ci); +void sbeid_set_bdtype (ci_t * ci); +void sbeid_set_hdwbid (ci_t * ci); +u_int32_t sbeCrc (u_int8_t *, u_int32_t, u_int32_t, u_int32_t *); + +void VMETRO_TRACE (void *); /* put data into 8 LEDs */ +void VMETRO_TRIGGER (ci_t *, int); /* Note: int = 0(default) + * thru 15 */ + +#if defined (SBE_ISR_TASKLET) +void musycc_intr_bh_tasklet (ci_t *); + +#endif + +#endif /*** __KERNEL __ ***/ +#endif /* _INC_PMCC4_H_ */ diff --git a/drivers/staging/cxt1e1/pmcc4_cpld.h b/drivers/staging/cxt1e1/pmcc4_cpld.h new file mode 100644 index 00000000..a51209bc --- /dev/null +++ b/drivers/staging/cxt1e1/pmcc4_cpld.h @@ -0,0 +1,91 @@ +#ifndef _INC_PMCC4_CPLD_H_ +#define _INC_PMCC4_CPLD_H_ + +/*----------------------------------------------------------------------------- + * pmcc4_cpld.h - + * + * Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + +#include + +/********************************/ +/* iSPLD control chip registers */ +/********************************/ + +#if 0 +#define CPLD_MCSR 0x0 +#define CPLD_MCLK 0x1 +#define CPLD_LEDS 0x2 +#define CPLD_INTR 0x3 +#endif + + struct c4_cpld + { + volatile u_int32_t mcsr;/* r/w: Master Clock Source Register */ + volatile u_int32_t mclk;/* r/w: Master Clock Register */ + volatile u_int32_t leds;/* r/w: LED Register */ + volatile u_int32_t intr;/* r: Interrupt Register */ + }; + + typedef struct c4_cpld c4cpld_t; + +/* mcsr note: sourcing COMET must be initialized to Master Mode */ +#define PMCC4_CPLD_MCSR_IND 0 /* ports used individual BP Clk as + * source, no slaves */ +#define PMCC4_CPLD_MCSR_CMT_1 1 /* COMET 1 BP Clk is source, 2,3,4 + * are Clk slaves */ +#define PMCC4_CPLD_MCSR_CMT_2 2 /* COMET 2 BP Clk is source, 1,3,4 + * are Clk slaves */ +#define PMCC4_CPLD_MCSR_CMT_3 3 /* COMET 3 BP Clk is source, 1,2,4 + * are Clk slaves */ +#define PMCC4_CPLD_MCSR_CMT_4 4 /* COMET 4 BP Clk is source, 1,2,3 + * are Clk slaves */ + +#define PMCC4_CPLD_MCLK_MASK 0x0f +#define PMCC4_CPLD_MCLK_P1 0x1 +#define PMCC4_CPLD_MCLK_P2 0x2 +#define PMCC4_CPLD_MCLK_P3 0x4 +#define PMCC4_CPLD_MCLK_P4 0x8 +#define PMCC4_CPLD_MCLK_T1 0x00 +#define PMCC4_CPLD_MCLK_P1_E1 0x01 +#define PMCC4_CPLD_MCLK_P2_E1 0x02 +#define PMCC4_CPLD_MCLK_P3_E1 0x04 +#define PMCC4_CPLD_MCLK_P4_E1 0x08 + +#define PMCC4_CPLD_LED_OFF 0 +#define PMCC4_CPLD_LED_ON 1 +#define PMCC4_CPLD_LED_GP0 0x01 /* port 0, green */ +#define PMCC4_CPLD_LED_YP0 0x02 /* port 0, yellow */ +#define PMCC4_CPLD_LED_GP1 0x04 /* port 1, green */ +#define PMCC4_CPLD_LED_YP1 0x08 /* port 1, yellow */ +#define PMCC4_CPLD_LED_GP2 0x10 /* port 2, green */ +#define PMCC4_CPLD_LED_YP2 0x20 /* port 2, yellow */ +#define PMCC4_CPLD_LED_GP3 0x40 /* port 3, green */ +#define PMCC4_CPLD_LED_YP3 0x80 /* port 3, yellow */ +#define PMCC4_CPLD_LED_GREEN (PMCC4_CPLD_LED_GP0 | PMCC4_CPLD_LED_GP1 | \ + PMCC4_CPLD_LED_GP2 | PMCC4_CPLD_LED_GP3 ) +#define PMCC4_CPLD_LED_YELLOW (PMCC4_CPLD_LED_YP0 | PMCC4_CPLD_LED_YP1 | \ + PMCC4_CPLD_LED_YP2 | PMCC4_CPLD_LED_YP3) + +#define PMCC4_CPLD_INTR_MASK 0x0f +#define PMCC4_CPLD_INTR_CMT_1 0x01 +#define PMCC4_CPLD_INTR_CMT_2 0x02 +#define PMCC4_CPLD_INTR_CMT_3 0x04 +#define PMCC4_CPLD_INTR_CMT_4 0x08 + +#endif /* _INC_PMCC4_CPLD_H_ */ diff --git a/drivers/staging/cxt1e1/pmcc4_defs.h b/drivers/staging/cxt1e1/pmcc4_defs.h new file mode 100644 index 00000000..83ceae43 --- /dev/null +++ b/drivers/staging/cxt1e1/pmcc4_defs.h @@ -0,0 +1,68 @@ +#ifndef _INC_PMCC4_DEFS_H_ +#define _INC_PMCC4_DEFS_H_ + +/*----------------------------------------------------------------------------- + * c4_defs.h - + * + * Implementation elements of the wanPMC-C4T1E1 device driver + * + * Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + + +#define MAX_BOARDS 8 +#define MAX_CHANS_USED 128 + +#ifdef SBE_PMCC4_ENABLE +#define MUSYCC_NPORTS 4 /* CN8474 */ +#endif +#ifdef SBE_WAN256T3_ENABLE +#define MUSYCC_NPORTS 8 /* CN8478 */ +#endif +#define MUSYCC_NCHANS 32 /* actually, chans per port */ + +#define MUSYCC_NIQD 0x1000 /* power of 2 */ +#define MUSYCC_MRU 2048 /* default */ +#define MUSYCC_MTU 2048 /* default */ +#define MUSYCC_TXDESC_MIN 10 /* HDLC mode default */ +#define MUSYCC_RXDESC_MIN 18 /* HDLC mode default */ +#define MUSYCC_TXDESC_TRANS 4 /* Transparent mode minimum # of TX descriptors */ +#define MUSYCC_RXDESC_TRANS 12 /* Transparent mode minimum # of RX descriptors */ + +#define MAX_DEFAULT_IFQLEN 32 /* network qlen */ + + +#define SBE_IFACETMPL "pmcc4-%d" +#ifdef IFNAMSIZ +#define SBE_IFACETMPL_SIZE IFNAMSIZ +#else +#define SBE_IFACETMPL_SIZE 16 +#endif + +/* we want the PMCC4 watchdog to fire off every 250ms */ +#define WATCHDOG_TIMEOUT 250000 + +/* if we restart the watchdog every 250ms, then we'll time out + * an additional 300ms later */ +#define WATCHDOG_UTIMEOUT (WATCHDOG_TIMEOUT+300000) + +#if !defined(SBE_ISR_TASKLET) && !defined(SBE_ISR_IMMEDIATE) && !defined(SBE_ISR_INLINE) +#define SBE_ISR_TASKLET +#endif + +#endif /*** _INC_PMCC4_DEFS_H_ ***/ + diff --git a/drivers/staging/cxt1e1/pmcc4_drv.c b/drivers/staging/cxt1e1/pmcc4_drv.c new file mode 100644 index 00000000..8a7b3a64 --- /dev/null +++ b/drivers/staging/cxt1e1/pmcc4_drv.c @@ -0,0 +1,1632 @@ +/*----------------------------------------------------------------------------- + * pmcc4_drv.c - + * + * Copyright (C) 2007 One Stop Systems, Inc. + * Copyright (C) 2002-2006 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@onestopsystems.com + * One Stop Systems, Inc. Escondido, California U.S.A. + *----------------------------------------------------------------------------- + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include "pmcc4_sysdep.h" +#include +#include +#include /* include for timer */ +#include /* include for timer */ +#include +#include + +#include "sbecom_inline_linux.h" +#include "libsbew.h" +#include "pmcc4_private.h" +#include "pmcc4.h" +#include "pmcc4_ioctls.h" +#include "musycc.h" +#include "comet.h" +#include "sbe_bid.h" + +#ifdef SBE_INCLUDE_SYMBOLS +#define STATIC +#else +#define STATIC static +#endif + + +#define KERN_WARN KERN_WARNING + +/* forward references */ +status_t c4_wk_chan_init (mpi_t *, mch_t *); +void c4_wq_port_cleanup (mpi_t *); +status_t c4_wq_port_init (mpi_t *); + +int c4_loop_port (ci_t *, int, u_int8_t); +status_t c4_set_port (ci_t *, int); +status_t musycc_chan_down (ci_t *, int); + +u_int32_t musycc_chan_proto (int); +status_t musycc_dump_ring (ci_t *, unsigned int); +status_t __init musycc_init (ci_t *); +void musycc_init_mdt (mpi_t *); +void musycc_serv_req (mpi_t *, u_int32_t); +void musycc_update_timeslots (mpi_t *); + +extern void musycc_update_tx_thp (mch_t *); +extern int cxt1e1_log_level; +extern int cxt1e1_max_mru; +extern int cxt1e1_max_mtu; +extern int max_rxdesc_used, max_rxdesc_default; +extern int max_txdesc_used, max_txdesc_default; + +#if defined (__powerpc__) +extern void *memset (void *s, int c, size_t n); + +#endif + +int drvr_state = SBE_DRVR_INIT; +ci_t *c4_list = 0; +ci_t *CI; /* dummy pointer to board ZEROE's data - + * DEBUG USAGE */ + + +void +sbecom_set_loglevel (int d) +{ + /* + * The code within the following -if- clause is a backdoor debug facility + * which can be used to display the state of a board's channel. + */ + if (d > LOG_DEBUG) + { + unsigned int channum = d - (LOG_DEBUG + 1); /* convert to ZERO + * relativity */ + + (void) musycc_dump_ring ((ci_t *) CI, channum); /* CI implies support + * for card 0 only */ + } else + { + if (cxt1e1_log_level != d) + { + pr_info("log level changed from %d to %d\n", cxt1e1_log_level, d); + cxt1e1_log_level = d; /* set new */ + } else + pr_info("log level is %d\n", cxt1e1_log_level); + } +} + + +mch_t * +c4_find_chan (int channum) +{ + ci_t *ci; + mch_t *ch; + int portnum, gchan; + + for (ci = c4_list; ci; ci = ci->next) + for (portnum = 0; portnum < ci->max_port; portnum++) + for (gchan = 0; gchan < MUSYCC_NCHANS; gchan++) + { + if ((ch = ci->port[portnum].chan[gchan])) + { + if ((ch->state != UNASSIGNED) && + (ch->channum == channum)) + return (ch); + } + } + return 0; +} + + +ci_t *__init +c4_new (void *hi) +{ + ci_t *ci; + +#ifdef SBE_MAP_DEBUG + pr_warning("c4_new() entered, ci needs %u.\n", + (unsigned int) sizeof (ci_t)); +#endif + + ci = (ci_t *) OS_kmalloc (sizeof (ci_t)); + if (ci) + { + ci->hdw_info = hi; + ci->state = C_INIT; /* mark as hardware not available */ + ci->next = c4_list; + c4_list = ci; + ci->brdno = ci->next ? ci->next->brdno + 1 : 0; + } else + pr_warning("failed CI malloc, size %u.\n", + (unsigned int) sizeof (ci_t)); + + if (CI == 0) + CI = ci; /* DEBUG, only board 0 usage */ + return ci; +} + + +/*** + * Check port state and set LED states using watchdog or ioctl... + * also check for in-band SF loopback commands (& cause results if they are there) + * + * Alarm function depends on comet bits indicating change in + * link status (linkMask) to keep the link status indication straight. + * + * Indications are only LED and system log -- except when ioctl is invoked. + * + * "alarmed" record (a.k.a. copyVal, in some cases below) decodes as: + * + * RMAI (E1 only) 0x100 + * alarm LED on 0x80 + * link LED on 0x40 + * link returned 0x20 (link was down, now it's back and 'port get' hasn't run) + * change in LED 0x10 (update LED register because value has changed) + * link is down 0x08 + * YelAlm(RAI) 0x04 + * RedAlm 0x02 + * AIS(blue)Alm 0x01 + * + * note "link has returned" indication is reset on read + * (e.g. by use of the c4_control port get command) + */ + +#define sbeLinkMask 0x41 /* change in signal status (lost/recovered) + + * state */ +#define sbeLinkChange 0x40 +#define sbeLinkDown 0x01 +#define sbeAlarmsMask 0x07 /* red / yellow / blue alarm conditions */ +#define sbeE1AlarmsMask 0x107 /* alarm conditions */ + +#define COMET_LBCMD_READ 0x80 /* read only (do not set, return read value) */ + +void +checkPorts (ci_t * ci) +{ +#ifndef CONFIG_SBE_PMCC4_NCOMM + /* + * PORT POINT - NCOMM needs to avoid this code since the polling of + * alarms conflicts with NCOMM's interrupt servicing implementation. + */ + + comet_t *comet; + volatile u_int32_t value; + u_int32_t copyVal, LEDval; + + u_int8_t portnum; + + LEDval = 0; + for (portnum = 0; portnum < ci->max_port; portnum++) + { + copyVal = 0x12f & (ci->alarmed[portnum]); /* port's alarm record */ + comet = ci->port[portnum].cometbase; + value = pci_read_32 ((u_int32_t *) &comet->cdrc_ists) & sbeLinkMask; /* link loss reg */ + + if (value & sbeLinkChange) /* is there a change in the link stuff */ + { + /* if there's been a change (above) and yet it's the same (below) */ + if (!(((copyVal >> 3) & sbeLinkDown) ^ (value & sbeLinkDown))) + { + if (value & sbeLinkDown) + pr_warning("%s: Port %d momentarily recovered.\n", + ci->devname, portnum); + else + pr_warning("%s: Warning: Port %d link was briefly down.\n", + ci->devname, portnum); + } else if (value & sbeLinkDown) + pr_warning("%s: Warning: Port %d link is down.\n", + ci->devname, portnum); + else + { + pr_warning("%s: Port %d link has recovered.\n", + ci->devname, portnum); + copyVal |= 0x20; /* record link transition to up */ + } + copyVal |= 0x10; /* change (link) --> update LEDs */ + } + copyVal &= 0x137; /* clear LED & link old history bits & + * save others */ + if (value & sbeLinkDown) + copyVal |= 0x08; /* record link status (now) */ + else + { /* if link is up, do this */ + copyVal |= 0x40; /* LED indicate link is up */ + /* Alarm things & the like ... first if E1, then if T1 */ + if (IS_FRAME_ANY_E1 (ci->port[portnum].p.port_mode)) + { + /* + * first check Codeword (SaX) changes & CRC and + * sub-multi-frame errors + */ + /* + * note these errors are printed every time they are detected + * vs. alarms + */ + value = pci_read_32 ((u_int32_t *) &comet->e1_frmr_nat_ists); /* codeword */ + if (value & 0x1f) + { /* if errors (crc or smf only) */ + if (value & 0x10) + pr_warning("%s: E1 Port %d Codeword Sa4 change detected.\n", + ci->devname, portnum); + if (value & 0x08) + pr_warning("%s: E1 Port %d Codeword Sa5 change detected.\n", + ci->devname, portnum); + if (value & 0x04) + pr_warning("%s: E1 Port %d Codeword Sa6 change detected.\n", + ci->devname, portnum); + if (value & 0x02) + pr_warning("%s: E1 Port %d Codeword Sa7 change detected.\n", + ci->devname, portnum); + if (value & 0x01) + pr_warning("%s: E1 Port %d Codeword Sa8 change detected.\n", + ci->devname, portnum); + } + value = pci_read_32 ((u_int32_t *) &comet->e1_frmr_mists); /* crc & smf */ + if (value & 0x3) + { /* if errors (crc or smf only) */ + if (value & sbeE1CRC) + pr_warning("%s: E1 Port %d CRC-4 error(s) detected.\n", + ci->devname, portnum); + if (value & sbeE1errSMF) /* error in sub-multiframe */ + pr_warning("%s: E1 Port %d received errored SMF.\n", + ci->devname, portnum); + } + value = pci_read_32 ((u_int32_t *) &comet->e1_frmr_masts) & 0xcc; /* alarms */ + /* + * pack alarms together (bitmiser), and construct similar to + * T1 + */ + /* RAI,RMAI,.,.,LOF,AIS,.,. ==> RMAI,.,.,.,.,.,RAI,LOF,AIS */ + /* see 0x97 */ + value = (value >> 2); + if (value & 0x30) + { + if (value & 0x20) + value |= 0x40; /* RAI */ + if (value & 0x10) + value |= 0x100; /* RMAI */ + value &= ~0x30; + } /* finished packing alarm in handy order */ + if (value != (copyVal & sbeE1AlarmsMask)) + { /* if alarms changed */ + copyVal |= 0x10;/* change LED status */ + if ((copyVal & sbeRedAlm) && !(value & sbeRedAlm)) + { + copyVal &= ~sbeRedAlm; + pr_warning("%s: E1 Port %d LOF alarm ended.\n", + ci->devname, portnum); + } else if (!(copyVal & sbeRedAlm) && (value & sbeRedAlm)) + { + copyVal |= sbeRedAlm; + pr_warning("%s: E1 Warning: Port %d LOF alarm.\n", + ci->devname, portnum); + } else if ((copyVal & sbeYelAlm) && !(value & sbeYelAlm)) + { + copyVal &= ~sbeYelAlm; + pr_warning("%s: E1 Port %d RAI alarm ended.\n", + ci->devname, portnum); + } else if (!(copyVal & sbeYelAlm) && (value & sbeYelAlm)) + { + copyVal |= sbeYelAlm; + pr_warning("%s: E1 Warning: Port %d RAI alarm.\n", + ci->devname, portnum); + } else if ((copyVal & sbeE1RMAI) && !(value & sbeE1RMAI)) + { + copyVal &= ~sbeE1RMAI; + pr_warning("%s: E1 Port %d RMAI alarm ended.\n", + ci->devname, portnum); + } else if (!(copyVal & sbeE1RMAI) && (value & sbeE1RMAI)) + { + copyVal |= sbeE1RMAI; + pr_warning("%s: E1 Warning: Port %d RMAI alarm.\n", + ci->devname, portnum); + } else if ((copyVal & sbeAISAlm) && !(value & sbeAISAlm)) + { + copyVal &= ~sbeAISAlm; + pr_warning("%s: E1 Port %d AIS alarm ended.\n", + ci->devname, portnum); + } else if (!(copyVal & sbeAISAlm) && (value & sbeAISAlm)) + { + copyVal |= sbeAISAlm; + pr_warning("%s: E1 Warning: Port %d AIS alarm.\n", + ci->devname, portnum); + } + } + /* end of E1 alarm code */ + } else + { /* if a T1 mode */ + value = pci_read_32 ((u_int32_t *) &comet->t1_almi_ists); /* alarms */ + value &= sbeAlarmsMask; + if (value != (copyVal & sbeAlarmsMask)) + { /* if alarms changed */ + copyVal |= 0x10;/* change LED status */ + if ((copyVal & sbeRedAlm) && !(value & sbeRedAlm)) + { + copyVal &= ~sbeRedAlm; + pr_warning("%s: Port %d red alarm ended.\n", + ci->devname, portnum); + } else if (!(copyVal & sbeRedAlm) && (value & sbeRedAlm)) + { + copyVal |= sbeRedAlm; + pr_warning("%s: Warning: Port %d red alarm.\n", + ci->devname, portnum); + } else if ((copyVal & sbeYelAlm) && !(value & sbeYelAlm)) + { + copyVal &= ~sbeYelAlm; + pr_warning("%s: Port %d yellow (RAI) alarm ended.\n", + ci->devname, portnum); + } else if (!(copyVal & sbeYelAlm) && (value & sbeYelAlm)) + { + copyVal |= sbeYelAlm; + pr_warning("%s: Warning: Port %d yellow (RAI) alarm.\n", + ci->devname, portnum); + } else if ((copyVal & sbeAISAlm) && !(value & sbeAISAlm)) + { + copyVal &= ~sbeAISAlm; + pr_warning("%s: Port %d blue (AIS) alarm ended.\n", + ci->devname, portnum); + } else if (!(copyVal & sbeAISAlm) && (value & sbeAISAlm)) + { + copyVal |= sbeAISAlm; + pr_warning("%s: Warning: Port %d blue (AIS) alarm.\n", + ci->devname, portnum); + } + } + } /* end T1 mode alarm checks */ + } + if (copyVal & sbeAlarmsMask) + copyVal |= 0x80; /* if alarm turn yel LED on */ + if (copyVal & 0x10) + LEDval |= 0x100; /* tag if LED values have changed */ + LEDval |= ((copyVal & 0xc0) >> (6 - (portnum * 2))); + + ci->alarmed[portnum] &= 0xfffff000; /* out with the old (it's fff + * ... foo) */ + ci->alarmed[portnum] |= (copyVal); /* in with the new */ + + /* + * enough with the alarms and LED's, now let's check for loopback + * requests + */ + + if (IS_FRAME_ANY_T1 (ci->port[portnum].p.port_mode)) + { /* if a T1 mode */ + /* + * begin in-band (SF) loopback code detection -- start by reading + * command + */ + value = pci_read_32 ((u_int32_t *) &comet->ibcd_ies); /* detect reg. */ + value &= 0x3; /* trim to handy bits */ + if (value & 0x2) + { /* activate loopback (sets for deactivate + * code length) */ + copyVal = c4_loop_port (ci, portnum, COMET_LBCMD_READ); /* read line loopback + * mode */ + if (copyVal != COMET_MDIAG_LINELB) /* don't do it again if + * already in that mode */ + c4_loop_port (ci, portnum, COMET_MDIAG_LINELB); /* put port in line + * loopback mode */ + } + if (value & 0x1) + { /* deactivate loopback (sets for activate + * code length) */ + copyVal = c4_loop_port (ci, portnum, COMET_LBCMD_READ); /* read line loopback + * mode */ + if (copyVal != COMET_MDIAG_LBOFF) /* don't do it again if + * already in that mode */ + c4_loop_port (ci, portnum, COMET_MDIAG_LBOFF); /* take port out of any + * loopback mode */ + } + } + if (IS_FRAME_ANY_T1ESF (ci->port[portnum].p.port_mode)) + { /* if a T1 ESF mode */ + /* begin ESF loopback code */ + value = pci_read_32 ((u_int32_t *) &comet->t1_rboc_sts) & 0x3f; /* read command */ + if (value == 0x07) + c4_loop_port (ci, portnum, COMET_MDIAG_LINELB); /* put port in line + * loopback mode */ + if (value == 0x0a) + c4_loop_port (ci, portnum, COMET_MDIAG_PAYLB); /* put port in payload + * loopbk mode */ + if ((value == 0x1c) || (value == 0x19) || (value == 0x12)) + c4_loop_port (ci, portnum, COMET_MDIAG_LBOFF); /* take port out of any + * loopbk mode */ + if (cxt1e1_log_level >= LOG_DEBUG) + if (value != 0x3f) + pr_warning("%s: BOC value = %x on Port %d\n", + ci->devname, value, portnum); + /* end ESF loopback code */ + } + } + + /* if something is new, update LED's */ + if (LEDval & 0x100) + pci_write_32 ((u_int32_t *) &ci->cpldbase->leds, LEDval & 0xff); +#endif /*** CONFIG_SBE_PMCC4_NCOMM ***/ +} + + +STATIC void +c4_watchdog (ci_t * ci) +{ + if (drvr_state != SBE_DRVR_AVAILABLE) + { + if (cxt1e1_log_level >= LOG_MONITOR) + pr_info("drvr not available (%x)\n", drvr_state); + return; + } + ci->wdcount++; + checkPorts (ci); + ci->wd_notify = 0; +} + + +void +c4_cleanup (void) +{ + ci_t *ci, *next; + mpi_t *pi; + int portnum, j; + + ci = c4_list; + while (ci) + { + next = ci->next; /* protect from upcoming */ + pci_write_32 ((u_int32_t *) &ci->cpldbase->leds, PMCC4_CPLD_LED_OFF); + for (portnum = 0; portnum < ci->max_port; portnum++) + { + pi = &ci->port[portnum]; + c4_wq_port_cleanup (pi); + for (j = 0; j < MUSYCC_NCHANS; j++) + { + if (pi->chan[j]) + OS_kfree (pi->chan[j]); /* free mch_t struct */ + } + OS_kfree (pi->regram_saved); + } + OS_kfree (ci->iqd_p_saved); + OS_kfree (ci); + ci = next; /* cleanup next board, if any */ + } +} + + +/* + * This function issues a write to all comet chips and expects the same data + * to be returned from the subsequent read. This determines the board build + * to be a 1-port, 2-port, or 4-port build. The value returned represents a + * bit-mask of the found ports. Only certain configurations are considered + * VALID or LEGAL builds. + */ + +int +c4_get_portcfg (ci_t * ci) +{ + comet_t *comet; + int portnum, mask; + u_int32_t wdata, rdata; + + wdata = COMET_MDIAG_LBOFF; /* take port out of any loopback mode */ + + mask = 0; + for (portnum = 0; portnum < MUSYCC_NPORTS; portnum++) + { + comet = ci->port[portnum].cometbase; + pci_write_32 ((u_int32_t *) &comet->mdiag, wdata); + rdata = pci_read_32 ((u_int32_t *) &comet->mdiag) & COMET_MDIAG_LBMASK; + if (wdata == rdata) + mask |= 1 << portnum; + } + return mask; +} + + +/* nothing herein should generate interrupts */ + +status_t __init +c4_init (ci_t * ci, u_char *func0, u_char *func1) +{ + mpi_t *pi; + mch_t *ch; + static u_int32_t count = 0; + int portnum, j; + + ci->state = C_INIT; + ci->brdno = count++; + ci->intlog.this_status_new = 0; + atomic_set (&ci->bh_pending, 0); + + ci->reg = (struct musycc_globalr *) func0; + ci->eeprombase = (u_int32_t *) (func1 + EEPROM_OFFSET); + ci->cpldbase = (c4cpld_t *) ((u_int32_t *) (func1 + ISPLD_OFFSET)); + + /*** PORT POINT - the following is the first access of any type to the hardware ***/ +#ifdef CONFIG_SBE_PMCC4_NCOMM + /* NCOMM driver uses INTB interrupt to monitor CPLD register */ + pci_write_32 ((u_int32_t *) &ci->reg->glcd, GCD_MAGIC); +#else + /* standard driver POLLS for INTB via CPLD register */ + pci_write_32 ((u_int32_t *) &ci->reg->glcd, GCD_MAGIC | MUSYCC_GCD_INTB_DISABLE); +#endif + + { + int pmsk; + + /* need comet addresses available for determination of hardware build */ + for (portnum = 0; portnum < MUSYCC_NPORTS; portnum++) + { + pi = &ci->port[portnum]; + pi->cometbase = (comet_t *) ((u_int32_t *) (func1 + COMET_OFFSET (portnum))); + pi->reg = (struct musycc_globalr *) ((u_char *) ci->reg + (portnum * 0x800)); + pi->portnum = portnum; + pi->p.portnum = portnum; + pi->openchans = 0; +#ifdef SBE_MAP_DEBUG + pr_info("Comet-%d: addr = %p\n", portnum, pi->cometbase); +#endif + } + pmsk = c4_get_portcfg (ci); + switch (pmsk) + { + case 0x1: + ci->max_port = 1; + break; + case 0x3: + ci->max_port = 2; + break; +#if 0 + case 0x7: /* not built, but could be... */ + ci->max_port = 3; + break; +#endif + case 0xf: + ci->max_port = 4; + break; + default: + ci->max_port = 0; + pr_warning("%s: illegal port configuration (%x)\n", + ci->devname, pmsk); + return SBE_DRVR_FAIL; + } +#ifdef SBE_MAP_DEBUG + pr_info(">> %s: c4_get_build - pmsk %x max_port %x\n", + ci->devname, pmsk, ci->max_port); +#endif + } + + for (portnum = 0; portnum < ci->max_port; portnum++) + { + pi = &ci->port[portnum]; + pi->up = ci; + pi->sr_last = 0xffffffff; + pi->p.port_mode = CFG_FRAME_SF; /* T1 B8ZS, the default */ + pi->p.portP = (CFG_CLK_PORT_EXTERNAL | CFG_LBO_LH0); /* T1 defaults */ + + OS_sem_init (&pi->sr_sem_busy, SEM_AVAILABLE); + OS_sem_init (&pi->sr_sem_wait, SEM_TAKEN); + + for (j = 0; j < 32; j++) + { + pi->fifomap[j] = -1; + pi->tsm[j] = 0; /* no assignments, all available */ + } + + /* allocate channel structures for this port */ + for (j = 0; j < MUSYCC_NCHANS; j++) + { + ch = OS_kmalloc (sizeof (mch_t)); + if (ch) + { + pi->chan[j] = ch; + ch->state = UNASSIGNED; + ch->up = pi; + ch->gchan = (-1); /* channel assignment not yet known */ + ch->channum = (-1); /* channel assignment not yet known */ + ch->p.card = ci->brdno; + ch->p.port = portnum; + ch->p.channum = (-1); /* channel assignment not yet known */ + ch->p.mode_56k = 0; /* default is 64kbps mode */ + } else + { + pr_warning("failed mch_t malloc, port %d channel %d size %u.\n", + portnum, j, (unsigned int) sizeof (mch_t)); + break; + } + } + } + + + { + /* + * Set LEDs through their paces to supply visual proof that LEDs are + * functional and not burnt out nor broken. + * + * YELLOW + GREEN -> OFF. + */ + + pci_write_32 ((u_int32_t *) &ci->cpldbase->leds, + PMCC4_CPLD_LED_GREEN | PMCC4_CPLD_LED_YELLOW); + OS_uwait (750000, "leds"); + pci_write_32 ((u_int32_t *) &ci->cpldbase->leds, PMCC4_CPLD_LED_OFF); + } + + OS_init_watchdog (&ci->wd, (void (*) (void *)) c4_watchdog, ci, WATCHDOG_TIMEOUT); + return SBE_DRVR_SUCCESS; +} + + +/* better be fully setup to handle interrupts when you call this */ + +status_t __init +c4_init2 (ci_t * ci) +{ + status_t ret; + + /* PORT POINT: this routine generates first interrupt */ + if ((ret = musycc_init (ci)) != SBE_DRVR_SUCCESS) + return ret; + +#if 0 + ci->p.framing_type = FRAMING_CBP; + ci->p.h110enable = 1; +#if 0 + ci->p.hypersize = 0; +#else + hyperdummy = 0; +#endif + ci->p.clock = 0; /* Use internal clocking until set to + * external */ + c4_card_set_params (ci, &ci->p); +#endif + OS_start_watchdog (&ci->wd); + return SBE_DRVR_SUCCESS; +} + + +/* This function sets the loopback mode (or clears it, as the case may be). */ + +int +c4_loop_port (ci_t * ci, int portnum, u_int8_t cmd) +{ + comet_t *comet; + volatile u_int32_t loopValue; + + comet = ci->port[portnum].cometbase; + loopValue = pci_read_32 ((u_int32_t *) &comet->mdiag) & COMET_MDIAG_LBMASK; + + if (cmd & COMET_LBCMD_READ) + return loopValue; /* return the read value */ + + if (loopValue != cmd) + { + switch (cmd) + { + case COMET_MDIAG_LINELB: + /* set(SF)loopback down (turn off) code length to 6 bits */ + pci_write_32 ((u_int32_t *) &comet->ibcd_cfg, 0x05); + break; + case COMET_MDIAG_LBOFF: + /* set (SF) loopback up (turn on) code length to 5 bits */ + pci_write_32 ((u_int32_t *) &comet->ibcd_cfg, 0x00); + break; + } + + pci_write_32 ((u_int32_t *) &comet->mdiag, cmd); + if (cxt1e1_log_level >= LOG_WARN) + pr_info("%s: loopback mode changed to %2x from %2x on Port %d\n", + ci->devname, cmd, loopValue, portnum); + loopValue = pci_read_32 ((u_int32_t *) &comet->mdiag) & COMET_MDIAG_LBMASK; + if (loopValue != cmd) + { + if (cxt1e1_log_level >= LOG_ERROR) + pr_info("%s: write to loop register failed, unknown state for Port %d\n", + ci->devname, portnum); + } + } else + { + if (cxt1e1_log_level >= LOG_WARN) + pr_info("%s: loopback already in that mode (%2x)\n", + ci->devname, loopValue); + } + return 0; +} + + +/* c4_frame_rw: read or write the comet register specified + * (modifies use of port_param to non-standard use of struct) + * Specifically: + * pp.portnum (one guess) + * pp.port_mode offset of register + * pp.portP write (or not, i.e. read) + * pp.portStatus write value + * BTW: + * pp.portStatus also used to return read value + * pp.portP also used during write, to return old reg value + */ + +status_t +c4_frame_rw (ci_t * ci, struct sbecom_port_param * pp) +{ + comet_t *comet; + volatile u_int32_t data; + + if (pp->portnum >= ci->max_port)/* sanity check */ + return ENXIO; + + comet = ci->port[pp->portnum].cometbase; + data = pci_read_32 ((u_int32_t *) comet + pp->port_mode) & 0xff; + + if (pp->portP) + { /* control says this is a register + * _write_ */ + if (pp->portStatus == data) + pr_info("%s: Port %d already that value! Writing again anyhow.\n", + ci->devname, pp->portnum); + pp->portP = (u_int8_t) data; + pci_write_32 ((u_int32_t *) comet + pp->port_mode, + pp->portStatus); + data = pci_read_32 ((u_int32_t *) comet + pp->port_mode) & 0xff; + } + pp->portStatus = (u_int8_t) data; + return 0; +} + + +/* c4_pld_rw: read or write the pld register specified + * (modifies use of port_param to non-standard use of struct) + * Specifically: + * pp.port_mode offset of register + * pp.portP write (or not, i.e. read) + * pp.portStatus write value + * BTW: + * pp.portStatus also used to return read value + * pp.portP also used during write, to return old reg value + */ + +status_t +c4_pld_rw (ci_t * ci, struct sbecom_port_param * pp) +{ + volatile u_int32_t *regaddr; + volatile u_int32_t data; + int regnum = pp->port_mode; + + regaddr = (u_int32_t *) ci->cpldbase + regnum; + data = pci_read_32 ((u_int32_t *) regaddr) & 0xff; + + if (pp->portP) + { /* control says this is a register + * _write_ */ + pp->portP = (u_int8_t) data; + pci_write_32 ((u_int32_t *) regaddr, pp->portStatus); + data = pci_read_32 ((u_int32_t *) regaddr) & 0xff; + } + pp->portStatus = (u_int8_t) data; + return 0; +} + +/* c4_musycc_rw: read or write the musycc register specified + * (modifies use of port_param to non-standard use of struct) + * Specifically: + * mcp.RWportnum port number and write indication bit (0x80) + * mcp.offset offset of register + * mcp.value write value going in and read value returning + */ + +/* PORT POINT: TX Subchannel Map registers are write-only + * areas within the MUSYCC and always return FF */ +/* PORT POINT: regram and reg structures are minorly different and ioctl + * settings are aligned with the struct musycc_globalr{} usage. + * Also, regram is separately allocated shared memory, allocated for each port. + * PORT POINT: access offsets of 0x6000 for Msg Cfg Desc Tbl are for 4-port MUSYCC + * only. (An 8-port MUSYCC has 0x16000 offsets for accessing its upper 4 tables.) + */ + +status_t +c4_musycc_rw (ci_t * ci, struct c4_musycc_param * mcp) +{ + mpi_t *pi; + volatile u_int32_t *dph; /* hardware implemented register */ + u_int32_t *dpr = 0; /* RAM image of registers for group command + * usage */ + int offset = mcp->offset % 0x800; /* group relative address + * offset, mcp->portnum is + * not used */ + int portnum, ramread = 0; + volatile u_int32_t data; + + /* + * Sanity check hardware accessibility. The 0x6000 portion handles port + * numbers associated with Msg Descr Tbl decoding. + */ + portnum = (mcp->offset % 0x6000) / 0x800; + if (portnum >= ci->max_port) + return ENXIO; + pi = &ci->port[portnum]; + if (mcp->offset >= 0x6000) + offset += 0x6000; /* put back in MsgCfgDesc address offset */ + dph = (u_int32_t *) ((u_long) pi->reg + offset); + + /* read of TX are from RAM image, since hardware returns FF */ + dpr = (u_int32_t *) ((u_long) pi->regram + offset); + if (mcp->offset < 0x6000) /* non MsgDesc Tbl accesses might require + * RAM access */ + { + if (offset >= 0x200 && offset < 0x380) + ramread = 1; + if (offset >= 0x10 && offset < 0x200) + ramread = 1; + } + /* read register from RAM or hardware, depending... */ + if (ramread) + { + data = *dpr; + //pr_info("c4_musycc_rw: RAM addr %p read data %x (portno %x offset %x RAM ramread %x)\n", dpr, data, portnum, offset, ramread); /* RLD DEBUG */ + } else + { + data = pci_read_32 ((u_int32_t *) dph); + //pr_info("c4_musycc_rw: REG addr %p read data %x (portno %x offset %x RAM ramread %x)\n", dph, data, portnum, offset, ramread); /* RLD DEBUG */ + } + + + if (mcp->RWportnum & 0x80) + { /* control says this is a register + * _write_ */ + if (mcp->value == data) + pr_info("%s: musycc grp%d already that value! writing again anyhow.\n", + ci->devname, (mcp->RWportnum & 0x7)); + /* write register RAM */ + if (ramread) + *dpr = mcp->value; + /* write hardware register */ + pci_write_32 ((u_int32_t *) dph, mcp->value); + } + mcp->value = data; /* return the read value (or the 'old + * value', if is write) */ + return 0; +} + +status_t +c4_get_port (ci_t * ci, int portnum) +{ + if (portnum >= ci->max_port) /* sanity check */ + return ENXIO; + + SD_SEM_TAKE (&ci->sem_wdbusy, "_wd_"); /* only 1 thru here, per + * board */ + checkPorts (ci); + ci->port[portnum].p.portStatus = (u_int8_t) ci->alarmed[portnum]; + ci->alarmed[portnum] &= 0xdf; + SD_SEM_GIVE (&ci->sem_wdbusy); /* release per-board hold */ + return 0; +} + +status_t +c4_set_port (ci_t * ci, int portnum) +{ + mpi_t *pi; + struct sbecom_port_param *pp; + int e1mode; + u_int8_t clck; + int i; + + if (portnum >= ci->max_port) /* sanity check */ + return ENXIO; + + pi = &ci->port[portnum]; + pp = &ci->port[portnum].p; + e1mode = IS_FRAME_ANY_E1 (pp->port_mode); + if (cxt1e1_log_level >= LOG_MONITOR2) + { + pr_info("%s: c4_set_port[%d]: entered, e1mode = %x, openchans %d.\n", + ci->devname, + portnum, e1mode, pi->openchans); + } + if (pi->openchans) + return EBUSY; /* group needs initialization only for + * first channel of a group */ + + { + status_t ret; + + if ((ret = c4_wq_port_init (pi))) /* create/init + * workqueue_struct */ + return (ret); + } + + init_comet (ci, pi->cometbase, pp->port_mode, 1 /* clockmaster == true */ , pp->portP); + clck = pci_read_32 ((u_int32_t *) &ci->cpldbase->mclk) & PMCC4_CPLD_MCLK_MASK; + if (e1mode) + clck |= 1 << portnum; + else + clck &= 0xf ^ (1 << portnum); + + pci_write_32 ((u_int32_t *) &ci->cpldbase->mclk, clck); + pci_write_32 ((u_int32_t *) &ci->cpldbase->mcsr, PMCC4_CPLD_MCSR_IND); + pci_write_32 ((u_int32_t *) &pi->reg->gbp, OS_vtophys (pi->regram)); + + /*********************************************************************/ + /* ERRATA: If transparent mode is used, do not set OOFMP_DISABLE bit */ + /*********************************************************************/ + + pi->regram->grcd = + __constant_cpu_to_le32 (MUSYCC_GRCD_RX_ENABLE | + MUSYCC_GRCD_TX_ENABLE | + MUSYCC_GRCD_OOFMP_DISABLE | + MUSYCC_GRCD_SF_ALIGN | /* per MUSYCC ERRATA, + * for T1 * fix */ + MUSYCC_GRCD_COFAIRQ_DISABLE | + MUSYCC_GRCD_MC_ENABLE | + (MUSYCC_GRCD_POLLTH_32 << MUSYCC_GRCD_POLLTH_SHIFT)); + + pi->regram->pcd = + __constant_cpu_to_le32 ((e1mode ? 1 : 0) | + MUSYCC_PCD_TXSYNC_RISING | + MUSYCC_PCD_RXSYNC_RISING | + MUSYCC_PCD_RXDATA_RISING); + + /* Message length descriptor */ + pi->regram->mld = __constant_cpu_to_le32 (cxt1e1_max_mru | (cxt1e1_max_mru << 16)); + + /* tsm algorithm */ + for (i = 0; i < 32; i++) + { + + /*** ASSIGNMENT NOTES: ***/ + /*** Group's channel ZERO unavailable if E1. ***/ + /*** Group's channel 16 unavailable if E1 CAS. ***/ + /*** Group's channels 24-31 unavailable if T1. ***/ + + if (((i == 0) && e1mode) || + ((i == 16) && ((pp->port_mode == CFG_FRAME_E1CRC_CAS) || (pp->port_mode == CFG_FRAME_E1CRC_CAS_AMI))) + || ((i > 23) && (!e1mode))) + { + pi->tsm[i] = 0xff; /* make tslot unavailable for this mode */ + } else + { + pi->tsm[i] = 0x00; /* make tslot available for assignment */ + } + } + for (i = 0; i < MUSYCC_NCHANS; i++) + { + pi->regram->ttsm[i] = 0; + pi->regram->rtsm[i] = 0; + } + FLUSH_MEM_WRITE (); + musycc_serv_req (pi, SR_GROUP_INIT | SR_RX_DIRECTION); + musycc_serv_req (pi, SR_GROUP_INIT | SR_TX_DIRECTION); + + musycc_init_mdt (pi); + + pi->group_is_set = 1; + pi->p = *pp; + return 0; +} + + +unsigned int max_int = 0; + +status_t +c4_new_chan (ci_t * ci, int portnum, int channum, void *user) +{ + mpi_t *pi; + mch_t *ch; + int gchan; + + if (c4_find_chan (channum)) /* a new channel shouldn't already exist */ + return EEXIST; + + if (portnum >= ci->max_port) /* sanity check */ + return ENXIO; + + pi = &(ci->port[portnum]); + /* find any available channel within this port */ + for (gchan = 0; gchan < MUSYCC_NCHANS; gchan++) + { + ch = pi->chan[gchan]; + if (ch && ch->state == UNASSIGNED) /* no assignment is good! */ + break; + } + if (gchan == MUSYCC_NCHANS) /* exhausted table, all were assigned */ + return ENFILE; + + ch->up = pi; + + /* NOTE: mch_t already cleared during OS_kmalloc() */ + ch->state = DOWN; + ch->user = user; + ch->gchan = gchan; + ch->channum = channum; /* mark our channel assignment */ + ch->p.channum = channum; +#if 1 + ch->p.card = ci->brdno; + ch->p.port = portnum; +#endif + ch->p.chan_mode = CFG_CH_PROTO_HDLC_FCS16; + ch->p.idlecode = CFG_CH_FLAG_7E; + ch->p.pad_fill_count = 2; + spin_lock_init (&ch->ch_rxlock); + spin_lock_init (&ch->ch_txlock); + + { + status_t ret; + + if ((ret = c4_wk_chan_init (pi, ch))) + return ret; + } + + /* save off interface assignments which bound a board */ + if (ci->first_if == 0) /* first channel registered is assumed to + * be the lowest channel */ + { + ci->first_if = ci->last_if = user; + ci->first_channum = ci->last_channum = channum; + } else + { + ci->last_if = user; + if (ci->last_channum < channum) /* higher number channel found */ + ci->last_channum = channum; + } + return 0; +} + +status_t +c4_del_chan (int channum) +{ + mch_t *ch; + + if (!(ch = c4_find_chan (channum))) + return ENOENT; + if (ch->state == UP) + musycc_chan_down ((ci_t *) 0, channum); + ch->state = UNASSIGNED; + ch->gchan = (-1); + ch->channum = (-1); + ch->p.channum = (-1); + return 0; +} + +status_t +c4_del_chan_stats (int channum) +{ + mch_t *ch; + + if (!(ch = c4_find_chan (channum))) + return ENOENT; + + memset (&ch->s, 0, sizeof (struct sbecom_chan_stats)); + return 0; +} + + +status_t +c4_set_chan (int channum, struct sbecom_chan_param * p) +{ + mch_t *ch; + int i, x = 0; + + if (!(ch = c4_find_chan (channum))) + return ENOENT; + +#if 1 + if (ch->p.card != p->card || + ch->p.port != p->port || + ch->p.channum != p->channum) + return EINVAL; +#endif + + if (!(ch->up->group_is_set)) + { + return EIO; /* out of order, SET_PORT command + * required prior to first group's + * SET_CHAN command */ + } + /* + * Check for change of parameter settings in order to invoke closing of + * channel prior to hardware poking. + */ + + if (ch->p.status != p->status || ch->p.chan_mode != p->chan_mode || + ch->p.data_inv != p->data_inv || ch->p.intr_mask != p->intr_mask || + ch->txd_free < ch->txd_num) /* to clear out queued messages */ + x = 1; /* we have a change requested */ + for (i = 0; i < 32; i++) /* check for timeslot mapping changes */ + if (ch->p.bitmask[i] != p->bitmask[i]) + x = 1; /* we have a change requested */ + ch->p = *p; + if (x && (ch->state == UP)) /* if change request and channel is + * open... */ + { + status_t ret; + + if ((ret = musycc_chan_down ((ci_t *) 0, channum))) + return ret; + if ((ret = c4_chan_up (ch->up->up, channum))) + return ret; + sd_enable_xmit (ch->user); /* re-enable to catch flow controlled + * channel */ + } + return 0; +} + + +status_t +c4_get_chan (int channum, struct sbecom_chan_param * p) +{ + mch_t *ch; + + if (!(ch = c4_find_chan (channum))) + return ENOENT; + *p = ch->p; + return 0; +} + +status_t +c4_get_chan_stats (int channum, struct sbecom_chan_stats * p) +{ + mch_t *ch; + + if (!(ch = c4_find_chan (channum))) + return ENOENT; + *p = ch->s; + p->tx_pending = atomic_read (&ch->tx_pending); + return 0; +} + +STATIC int +c4_fifo_alloc (mpi_t * pi, int chan, int *len) +{ + int i, l = 0, start = 0, max = 0, maxstart = 0; + + for (i = 0; i < 32; i++) + { + if (pi->fifomap[i] != -1) + { + l = 0; + start = i + 1; + continue; + } + ++l; + if (l > max) + { + max = l; + maxstart = start; + } + if (max == *len) + break; + } + if (max != *len) + { + if (cxt1e1_log_level >= LOG_WARN) + pr_info("%s: wanted to allocate %d fifo space, but got only %d\n", + pi->up->devname, *len, max); + *len = max; + } + if (cxt1e1_log_level >= LOG_DEBUG) + pr_info("%s: allocated %d fifo at %d for channel %d/%d\n", + pi->up->devname, max, start, chan, pi->p.portnum); + for (i = maxstart; i < (maxstart + max); i++) + pi->fifomap[i] = chan; + return start; +} + +void +c4_fifo_free (mpi_t * pi, int chan) +{ + int i; + + if (cxt1e1_log_level >= LOG_DEBUG) + pr_info("%s: deallocated fifo for channel %d/%d\n", + pi->up->devname, chan, pi->p.portnum); + for (i = 0; i < 32; i++) + if (pi->fifomap[i] == chan) + pi->fifomap[i] = -1; +} + + +status_t +c4_chan_up (ci_t * ci, int channum) +{ + mpi_t *pi; + mch_t *ch; + struct mbuf *m; + struct mdesc *md; + int nts, nbuf, txnum, rxnum; + int addr, i, j, gchan; + u_int32_t tmp; /* for optimizing conversion across BE + * platform */ + + if (!(ch = c4_find_chan (channum))) + return ENOENT; + if (ch->state == UP) + { + if (cxt1e1_log_level >= LOG_MONITOR) + pr_info("%s: channel already UP, graceful early exit\n", + ci->devname); + return 0; + } + pi = ch->up; + gchan = ch->gchan; + /* find nts ('number of timeslots') */ + nts = 0; + for (i = 0; i < 32; i++) + { + if (ch->p.bitmask[i] & pi->tsm[i]) + { + if (1 || cxt1e1_log_level >= LOG_WARN) + { + pr_info("%s: c4_chan_up[%d] EINVAL (attempt to cfg in-use or unavailable TimeSlot[%d])\n", + ci->devname, channum, i); + pr_info("+ ask4 %x, currently %x\n", + ch->p.bitmask[i], pi->tsm[i]); + } + return EINVAL; + } + for (j = 0; j < 8; j++) + if (ch->p.bitmask[i] & (1 << j)) + nts++; + } + + nbuf = nts / 8 ? nts / 8 : 1; + if (!nbuf) + { + /* if( cxt1e1_log_level >= LOG_WARN) */ + pr_info("%s: c4_chan_up[%d] ENOBUFS (no TimeSlots assigned)\n", + ci->devname, channum); + return ENOBUFS; /* this should not happen */ + } + addr = c4_fifo_alloc (pi, gchan, &nbuf); + ch->state = UP; + + /* Setup the Time Slot Map */ + musycc_update_timeslots (pi); + + /* ch->tx_limit = nts; */ + ch->s.tx_pending = 0; + + /* Set Channel Configuration Descriptors */ + { + u_int32_t ccd; + + ccd = musycc_chan_proto (ch->p.chan_mode) << MUSYCC_CCD_PROTO_SHIFT; + if ((ch->p.chan_mode == CFG_CH_PROTO_ISLP_MODE) || + (ch->p.chan_mode == CFG_CH_PROTO_TRANS)) + { + ccd |= MUSYCC_CCD_FCS_XFER; /* Non FSC Mode */ + } + ccd |= 2 << MUSYCC_CCD_MAX_LENGTH; /* Select second MTU */ + ccd |= ch->p.intr_mask; + ccd |= addr << MUSYCC_CCD_BUFFER_LOC; + if (ch->p.chan_mode == CFG_CH_PROTO_TRANS) + ccd |= (nbuf) << MUSYCC_CCD_BUFFER_LENGTH; + else + ccd |= (nbuf - 1) << MUSYCC_CCD_BUFFER_LENGTH; + + if (ch->p.data_inv & CFG_CH_DINV_TX) + ccd |= MUSYCC_CCD_INVERT_DATA; /* Invert data */ + pi->regram->tcct[gchan] = cpu_to_le32 (ccd); + + if (ch->p.data_inv & CFG_CH_DINV_RX) + ccd |= MUSYCC_CCD_INVERT_DATA; /* Invert data */ + else + ccd &= ~MUSYCC_CCD_INVERT_DATA; /* take away data inversion */ + pi->regram->rcct[gchan] = cpu_to_le32 (ccd); + FLUSH_MEM_WRITE (); + } + + /* Reread the Channel Configuration Descriptor for this channel */ + musycc_serv_req (pi, SR_CHANNEL_CONFIG | SR_RX_DIRECTION | gchan); + musycc_serv_req (pi, SR_CHANNEL_CONFIG | SR_TX_DIRECTION | gchan); + + /* + * Figure out how many buffers we want. If the customer has changed from + * the defaults, then use the changed values. Otherwise, use Transparent + * mode's specific minimum default settings. + */ + if (ch->p.chan_mode == CFG_CH_PROTO_TRANS) + { + if (max_rxdesc_used == max_rxdesc_default) /* use default setting */ + max_rxdesc_used = MUSYCC_RXDESC_TRANS; + if (max_txdesc_used == max_txdesc_default) /* use default setting */ + max_txdesc_used = MUSYCC_TXDESC_TRANS; + } + /* + * Increase counts when hyperchanneling, since this implies an increase + * in throughput per channel + */ + rxnum = max_rxdesc_used + (nts / 4); + txnum = max_txdesc_used + (nts / 4); + +#if 0 + /* DEBUG INFO */ + if (cxt1e1_log_level >= LOG_MONITOR) + pr_info("%s: mode %x rxnum %d (rxused %d def %d) txnum %d (txused %d def %d)\n", + ci->devname, ch->p.chan_mode, + rxnum, max_rxdesc_used, max_rxdesc_default, + txnum, max_txdesc_used, max_txdesc_default); +#endif + + ch->rxd_num = rxnum; + ch->txd_num = txnum; + ch->rxix_irq_srv = 0; + + ch->mdr = OS_kmalloc (sizeof (struct mdesc) * rxnum); + ch->mdt = OS_kmalloc (sizeof (struct mdesc) * txnum); + if (ch->p.chan_mode == CFG_CH_PROTO_TRANS) + tmp = __constant_cpu_to_le32 (cxt1e1_max_mru | EOBIRQ_ENABLE); + else + tmp = __constant_cpu_to_le32 (cxt1e1_max_mru); + + for (i = 0, md = ch->mdr; i < rxnum; i++, md++) + { + if (i == (rxnum - 1)) + { + md->snext = &ch->mdr[0];/* wrapness */ + } else + { + md->snext = &ch->mdr[i + 1]; + } + md->next = cpu_to_le32 (OS_vtophys (md->snext)); + + if (!(m = OS_mem_token_alloc (cxt1e1_max_mru))) + { + if (cxt1e1_log_level >= LOG_MONITOR) + pr_info("%s: c4_chan_up[%d] - token alloc failure, size = %d.\n", + ci->devname, channum, cxt1e1_max_mru); + goto errfree; + } + md->mem_token = m; + md->data = cpu_to_le32 (OS_vtophys (OS_mem_token_data (m))); + md->status = tmp | MUSYCC_RX_OWNED; /* MUSYCC owns RX descriptor ** + * CODING NOTE: + * MUSYCC_RX_OWNED = 0 so no + * need to byteSwap */ + } + + for (i = 0, md = ch->mdt; i < txnum; i++, md++) + { + md->status = HOST_TX_OWNED; /* Host owns TX descriptor ** CODING + * NOTE: HOST_TX_OWNED = 0 so no need to + * byteSwap */ + md->mem_token = 0; + md->data = 0; + if (i == (txnum - 1)) + { + md->snext = &ch->mdt[0];/* wrapness */ + } else + { + md->snext = &ch->mdt[i + 1]; + } + md->next = cpu_to_le32 (OS_vtophys (md->snext)); + } + ch->txd_irq_srv = ch->txd_usr_add = &ch->mdt[0]; + ch->txd_free = txnum; + ch->tx_full = 0; + ch->txd_required = 0; + + /* Configure it into the chip */ + tmp = cpu_to_le32 (OS_vtophys (&ch->mdt[0])); + pi->regram->thp[gchan] = tmp; + pi->regram->tmp[gchan] = tmp; + + tmp = cpu_to_le32 (OS_vtophys (&ch->mdr[0])); + pi->regram->rhp[gchan] = tmp; + pi->regram->rmp[gchan] = tmp; + + /* Activate the Channel */ + FLUSH_MEM_WRITE (); + if (ch->p.status & RX_ENABLED) + { +#ifdef RLD_TRANS_DEBUG + pr_info("++ c4_chan_up() CHAN RX ACTIVATE: chan %d\n", ch->channum); +#endif + ch->ch_start_rx = 0; /* we are restarting RX... */ + musycc_serv_req (pi, SR_CHANNEL_ACTIVATE | SR_RX_DIRECTION | gchan); + } + if (ch->p.status & TX_ENABLED) + { +#ifdef RLD_TRANS_DEBUG + pr_info("++ c4_chan_up() CHAN TX ACTIVATE: chan %d \n", ch->channum); +#endif + ch->ch_start_tx = CH_START_TX_1ST; /* we are delaying start + * until receipt from user of + * first packet to transmit. */ + } + ch->status = ch->p.status; + pi->openchans++; + return 0; + +errfree: + while (i > 0) + { + /* Don't leak all the previously allocated mbufs in this loop */ + i--; + OS_mem_token_free (ch->mdr[i].mem_token); + } + OS_kfree (ch->mdt); + ch->mdt = 0; + ch->txd_num = 0; + OS_kfree (ch->mdr); + ch->mdr = 0; + ch->rxd_num = 0; + ch->state = DOWN; + return ENOBUFS; +} + +/* stop the hardware from servicing & interrupting */ + +void +c4_stopwd (ci_t * ci) +{ + OS_stop_watchdog (&ci->wd); + SD_SEM_TAKE (&ci->sem_wdbusy, "_stop_"); /* ensure WD not running */ + SD_SEM_GIVE (&ci->sem_wdbusy); +} + + +void +sbecom_get_brdinfo (ci_t * ci, struct sbe_brd_info * bip, u_int8_t *bsn) +{ + char *np; + u_int32_t sn = 0; + int i; + + bip->brdno = ci->brdno; /* our board number */ + bip->brd_id = ci->brd_id; + bip->brd_hdw_id = ci->hdw_bid; + bip->brd_chan_cnt = MUSYCC_NCHANS * ci->max_port; /* number of channels + * being used */ + bip->brd_port_cnt = ci->max_port; /* number of ports being used */ + bip->brd_pci_speed = BINFO_PCI_SPEED_unk; /* PCI speed not yet + * determinable */ + + if (ci->first_if) + { + { + struct net_device *dev; + + dev = (struct net_device *) ci->first_if; + np = (char *) dev->name; + } + strncpy (bip->first_iname, np, CHNM_STRLEN - 1); + } else + strcpy (bip->first_iname, ""); + if (ci->last_if) + { + { + struct net_device *dev; + + dev = (struct net_device *) ci->last_if; + np = (char *) dev->name; + } + strncpy (bip->last_iname, np, CHNM_STRLEN - 1); + } else + strcpy (bip->last_iname, ""); + + if (bsn) + { + for (i = 0; i < 3; i++) + { + bip->brd_mac_addr[i] = *bsn++; + } + for (; i < 6; i++) + { + bip->brd_mac_addr[i] = *bsn; + sn = (sn << 8) | *bsn++; + } + } else + { + for (i = 0; i < 6; i++) + bip->brd_mac_addr[i] = 0; + } + bip->brd_sn = sn; +} + + +status_t +c4_get_iidinfo (ci_t * ci, struct sbe_iid_info * iip) +{ + struct net_device *dev; + char *np; + + if (!(dev = getuserbychan (iip->channum))) + return ENOENT; + + np = dev->name; + strncpy (iip->iname, np, CHNM_STRLEN - 1); + return 0; +} + + +#ifdef CONFIG_SBE_PMCC4_NCOMM +void (*nciInterrupt[MAX_BOARDS][4]) (void); +extern void wanpmcC4T1E1_hookInterrupt (int cardID, int deviceID, void *handler); + +void +wanpmcC4T1E1_hookInterrupt (int cardID, int deviceID, void *handler) +{ + if (cardID < MAX_BOARDS) /* sanity check */ + nciInterrupt[cardID][deviceID] = handler; +} + +irqreturn_t +c4_ebus_intr_th_handler (void *devp) +{ + ci_t *ci = (ci_t *) devp; + volatile u_int32_t ists; + int handled = 0; + int brdno; + + /* which COMET caused the interrupt */ + brdno = ci->brdno; + ists = pci_read_32 ((u_int32_t *) &ci->cpldbase->intr); + if (ists & PMCC4_CPLD_INTR_CMT_1) + { + handled = 0x1; + if (nciInterrupt[brdno][0] != NULL) + (*nciInterrupt[brdno][0]) (); + } + if (ists & PMCC4_CPLD_INTR_CMT_2) + { + handled |= 0x2; + if (nciInterrupt[brdno][1] != NULL) + (*nciInterrupt[brdno][1]) (); + } + if (ists & PMCC4_CPLD_INTR_CMT_3) + { + handled |= 0x4; + if (nciInterrupt[brdno][2] != NULL) + (*nciInterrupt[brdno][2]) (); + } + if (ists & PMCC4_CPLD_INTR_CMT_4) + { + handled |= 0x8; + if (nciInterrupt[brdno][3] != NULL) + (*nciInterrupt[brdno][3]) (); + } +#if 0 + /*** Test code just de-implements the asserted interrupt. Alternate + vendor will supply COMET interrupt handling code herein or such. + ***/ + pci_write_32 ((u_int32_t *) &ci->reg->glcd, GCD_MAGIC | MUSYCC_GCD_INTB_DISABLE); +#endif + + return IRQ_RETVAL (handled); +} + + +unsigned long +wanpmcC4T1E1_getBaseAddress (int cardID, int deviceID) +{ + ci_t *ci; + unsigned long base = 0; + + ci = c4_list; + while (ci) + { + if (ci->brdno == cardID) /* found valid device */ + { + if (deviceID < ci->max_port) /* comet is supported */ + base = ((unsigned long) ci->port[deviceID].cometbase); + break; + } + ci = ci->next; /* next board, if any */ + } + return (base); +} + +#endif /*** CONFIG_SBE_PMCC4_NCOMM ***/ + + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/pmcc4_ioctls.h b/drivers/staging/cxt1e1/pmcc4_ioctls.h new file mode 100644 index 00000000..56a1ee39 --- /dev/null +++ b/drivers/staging/cxt1e1/pmcc4_ioctls.h @@ -0,0 +1,65 @@ +#ifndef _INC_PMCC4_IOCTLS_H_ +#define _INC_PMCC4_IOCTLS_H_ + +/*----------------------------------------------------------------------------- + * pmcc4_ioctls.h - + * + * Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + +#include "sbew_ioc.h" + +enum +{ + // C4_GET_PORT = 0, + // C4_SET_PORT, + // C4_GET_CHAN, + // C4_SET_CHAN, + C4_DEL_CHAN = 0, + // C4_CREATE_CHAN, + // C4_GET_CHAN_STATS, + // C4_RESET, + // C4_DEBUG, + C4_RESET_STATS, + C4_LOOP_PORT, + C4_RW_FRMR, + C4_RW_MSYC, + C4_RW_PLD +}; + +#define C4_GET_PORT SBE_IOC_PORT_GET +#define C4_SET_PORT SBE_IOC_PORT_SET +#define C4_GET_CHAN SBE_IOC_CHAN_GET +#define C4_SET_CHAN SBE_IOC_CHAN_SET +// #define C4_DEL_CHAN XXX +#define C4_CREATE_CHAN SBE_IOC_CHAN_NEW +#define C4_GET_CHAN_STATS SBE_IOC_CHAN_GET_STAT +#define C4_RESET SBE_IOC_RESET_DEV +#define C4_DEBUG SBE_IOC_LOGLEVEL +// #define C4_RESET_STATS XXX +// #define C4_LOOP_PORT XXX +// #define C4_RW_FRMR XXX +// #define C4_RW_MSYC XXX +// #define C4_RW_PLD XXX + +struct c4_chan_stats_wrap +{ + int channum; + struct sbecom_chan_stats stats; +}; + +#endif /* _INC_PMCC4_IOCTLS_H_ */ diff --git a/drivers/staging/cxt1e1/pmcc4_private.h b/drivers/staging/cxt1e1/pmcc4_private.h new file mode 100644 index 00000000..b2b6e370 --- /dev/null +++ b/drivers/staging/cxt1e1/pmcc4_private.h @@ -0,0 +1,296 @@ +#ifndef _INC_PMCC4_PRIVATE_H_ +#define _INC_PMCC4_PRIVATE_H_ + +/*----------------------------------------------------------------------------- + * pmcc4_private.h - + * + * Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include /* support for tasklets */ +#include /* support for timer */ +#include +#include + +#include "libsbew.h" +#include "pmcc4_defs.h" +#include "pmcc4_cpld.h" +#include "musycc.h" +#include "sbe_promformat.h" +#include "comet.h" + + +/* driver state */ +#define SBE_DRVR_INIT 0x0 +#define SBE_DRVR_AVAILABLE 0x69734F4E +#define SBE_DRVR_DOWN 0x1 + +/****************************************************************************** + * MUSYCC Message Descriptor - coupled to hardware implementation, the first + * three u_int32 must not be reordered. + */ + +struct mdesc +{ + volatile u_int32_t status; /* Buffer Descriptor */ + u_int32_t data; /* Data Pointer */ + u_int32_t next; /* MUSYCC view of Next Pointer */ + void *mem_token; /* Data */ + struct mdesc *snext; +}; + + +/************************************************************************* + * Private driver data structures, internal use only. + */ + +struct c4_chan_info +{ + int gchan; /* channel number within group/port 0-31 */ + int channum; /* absolute channel number 0-128 */ + u_int8_t status; +#define TX_RECOVERY_MASK 0x0f +#define TX_ONR_RECOVERY 0x01 +#define TX_BUFF_RECOVERY 0x02 +#define RX_RECOVERY_MASK 0xf0 +#define RX_ONR_RECOVERY 0x10 + + unsigned char ch_start_rx; +#define CH_START_RX_NOW 1 +#define CH_START_RX_ONR 2 +#define CH_START_RX_BUF 3 + + unsigned char ch_start_tx; +#define CH_START_TX_1ST 1 +#define CH_START_TX_ONR 2 +#define CH_START_TX_BUF 3 + + char tx_full; /* boolean */ + short txd_free; /* count of TX Desc available */ + short txd_required; /* count of TX Desc needed by mesg */ + unsigned short rxd_num; /* must support range up to 2000 */ + unsigned short txd_num; /* must support range up to 1000 */ + int rxix_irq_srv; + + enum + { + UNASSIGNED, /* AVAILABLE, NOTINUSE */ + DOWN, /* ASSIGNED, NOTINUSE */ + UP /* ASSIGNED and INUSE */ + } state; + + struct c4_port_info *up; + void *user; + + struct work_struct ch_work; + struct mdesc *mdt; + struct mdesc *mdr; + struct mdesc *txd_irq_srv; + struct mdesc *txd_usr_add; + +#if 0 + /* + * FUTURE CODE MIGHT SEPARATE TIMESLOT MAP SETUPS INTO SINGLE IOCTL and + * REMOVE MAPS FROM CHANNEL PARAMETER STRUCTURE + */ + /* + * each byte in bitmask below represents one timeslot (bitmask[0] is for + * timeslot 0 and so on), each bit in the byte selects timeslot bits for + * this channel (0xff - whole timeslot, 0x7f - 56kbps mode) + */ + + u_int8_t ts_bitmask[32]; +#endif + spinlock_t ch_rxlock; + spinlock_t ch_txlock; + atomic_t tx_pending; + + struct sbecom_chan_stats s; + struct sbecom_chan_param p; +}; +typedef struct c4_chan_info mch_t; + +struct c4_port_info +{ + + struct musycc_globalr *reg; + struct musycc_groupr *regram; + void *regram_saved; /* Original malloc value may have non-2KB + * boundary. Need to save for use when + * freeing. */ + comet_t *cometbase; + struct sbe_card_info *up; + + /* + * The workqueue is used for TX restart of ONR'd channels when in + * Transparent mode. + */ + + struct workqueue_struct *wq_port; /* chan restart work queue */ + struct semaphore sr_sem_busy; /* service request exclusion + * semaphore */ + struct semaphore sr_sem_wait; /* service request handshake + * semaphore */ + u_int32_t sr_last; + short openchans; + char portnum; + char group_is_set; /* GROUP_INIT command issued to MUSYCC, + * otherwise SET_CHAN Ioctl fails */ + + mch_t *chan[MUSYCC_NCHANS]; + struct sbecom_port_param p; + + /* + * The MUSYCC timeslot mappings are maintained within the driver and are + * modified and reloaded as each of a group's channels are configured. + */ + u_int8_t tsm[32]; /* tsm (time slot map) */ + int fifomap[32]; +}; +typedef struct c4_port_info mpi_t; + + +#define COMET_OFFSET(x) (0x80000+(x)*0x10000) +#define EEPROM_OFFSET 0xC0000 +#define ISPLD_OFFSET 0xD0000 + +/* iSPLD control chip registers */ +#define ISPLD_MCSR 0x0 +#define ISPLD_MCLK 0x1 +#define ISPLD_LEDS 0x2 +#define ISPLD_INTR 0x3 +#define ISPLD_MAX 0x3 + +struct sbe_card_info +{ + struct musycc_globalr *reg; + struct musycc_groupr *regram; + u_int32_t *iqd_p; /* pointer to dword aligned interrupt queue + * descriptors */ + void *iqd_p_saved; /* Original malloc value may have non-dword + * aligned boundary. Need to save for use + * when freeing. */ + unsigned int iqp_headx, iqp_tailx; + + struct semaphore sem_wdbusy;/* watchdog exclusion semaphore */ + struct watchdog wd; /* statically allocated watchdog structure */ + atomic_t bh_pending; /* bh queued, but not yet running */ + u_int32_t brd_id; /* unique PCI ID */ + u_int16_t hdw_bid; /* on/board hardware ID */ + unsigned short wdcount; + unsigned char max_port; + unsigned char brdno; /* our board number */ + unsigned char wd_notify; +#define WD_NOTIFY_1TX 1 +#define WD_NOTIFY_BUF 2 +#define WD_NOTIFY_ONR 4 + enum /* state as regards interrupt processing */ + { + C_INIT, /* of-board-address not configured or are in + * process of being removed, don't access + * hardware */ + C_IDLE, /* off-board-addresses are configured, but + * don't service interrupts, just clear them + * from hardware */ + C_RUNNING /* life is good, service away */ + } state; + + struct sbe_card_info *next; + u_int32_t *eeprombase; /* mapped address of board's EEPROM */ + c4cpld_t *cpldbase; /* mapped address of board's CPLD hardware */ + char *release; /* SBE ID string w/in sbeRelease.c */ + void *hdw_info; +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *dir_dev; +#endif + + /* saved off interface assignments which bound a board */ + hdlc_device *first_if; + hdlc_device *last_if; + short first_channum, last_channum; + + struct intlog + { + u_int32_t this_status_new; + u_int32_t last_status_new; + u_int32_t drvr_intr_thcount; + u_int32_t drvr_intr_bhcount; + u_int32_t drvr_int_failure; + } intlog; + + mpi_t port[MUSYCC_NPORTS]; + char devname[SBE_IFACETMPL_SIZE + 1]; + atomic_t tx_pending; + u_int32_t alarmed[4]; /* dpm211 */ + +#if defined(SBE_ISR_TASKLET) + struct tasklet_struct ci_musycc_isr_tasklet; +#elif defined(SBE_ISR_IMMEDIATE) + struct tq_struct ci_musycc_isr_tq; +#endif +}; +typedef struct sbe_card_info ci_t; + +struct s_hdw_info +{ + u_int8_t pci_busno; + u_int8_t pci_slot; + u_int8_t pci_pin[2]; + u_int8_t revid[2]; + u_int8_t mfg_info_sts; +#define EEPROM_OK 0x00 +#define EEPROM_CRCERR 0x01 + char promfmt; /* prom type, from sbe_promformat.h */ + + char devname[SBE_IFACETMPL_SIZE]; + struct pci_bus *bus; + struct net_device *ndev; + struct pci_dev *pdev[2]; + + unsigned long addr[2]; + unsigned long addr_mapped[2]; + unsigned long len[2]; + + union + { + char data[128]; + FLD_TYPE1 pft1; /* prom field, type #1 */ + FLD_TYPE2 pft2; /* prom field, type #2 */ + } mfg_info; +}; +typedef struct s_hdw_info hdw_info_t; + +/*****************************************************************/ + +struct c4_priv +{ + int channum; + struct sbe_card_info *ci; +}; + + +/*****************************************************************/ + +extern ci_t *c4_list; + +mch_t *c4_find_chan (int); +int c4_set_chan (int channum, struct sbecom_chan_param *); +int c4_get_chan (int channum, struct sbecom_chan_param *); +int c4_get_chan_stats (int channum, struct sbecom_chan_stats *); + +#endif /* _INC_PMCC4_PRIVATE_H_ */ diff --git a/drivers/staging/cxt1e1/pmcc4_sysdep.h b/drivers/staging/cxt1e1/pmcc4_sysdep.h new file mode 100644 index 00000000..697f1943 --- /dev/null +++ b/drivers/staging/cxt1e1/pmcc4_sysdep.h @@ -0,0 +1,62 @@ +#ifndef _INC_PMCC4_SYSDEP_H_ +#define _INC_PMCC4_SYSDEP_H_ + +/*----------------------------------------------------------------------------- + * pmcc4_sysdep.h - + * + * Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* reduce multiple autoconf entries to a single definition */ + +#ifdef CONFIG_SBE_PMCC4_HDLC_V7_MODULE +#undef CONFIG_SBE_PMCC4_HDLC_V7 +#define CONFIG_SBE_PMCC4_HDLC_V7 1 +#endif + +#ifdef CONFIG_SBE_PMCC4_NCOMM_MODULE +#undef CONFIG_SBE_PMCC4_NCOMM +#define CONFIG_SBE_PMCC4_NCOMM 1 +#endif + + +/* FLUSH MACROS - if using ioremap_nocache(), then these can be NOOPS, + * otherwise a memory barrier needs to be inserted. + */ + +#define FLUSH_PCI_READ() rmb() +#define FLUSH_PCI_WRITE() wmb() +#define FLUSH_MEM_READ() rmb() +#define FLUSH_MEM_WRITE() wmb() + + +/* + * System dependent callbacks routines, not inlined... + * For inlined system dependent routines, see include/sbecom_inlinux_linux.h + */ + +/* + * passes received memory token back to the system, is parameter from + * sd_new_chan() used to create the channel which the data arrived on + */ + +void sd_recv_consume(void *token, size_t len, void *user); + +void sd_disable_xmit (void *user); +void sd_enable_xmit (void *user); +int sd_line_is_ok (void *user); +void sd_line_is_up (void *user); +void sd_line_is_down (void *user); +int sd_queue_stopped (void *user); + +#endif /*** _INC_PMCC4_SYSDEP_H_ ***/ diff --git a/drivers/staging/cxt1e1/sbe_bid.h b/drivers/staging/cxt1e1/sbe_bid.h new file mode 100644 index 00000000..abc2e55f --- /dev/null +++ b/drivers/staging/cxt1e1/sbe_bid.h @@ -0,0 +1,47 @@ +#ifndef _INC_SBEBID_H_ +#define _INC_SBEBID_H_ + +/*----------------------------------------------------------------------------- + * sbe_bid.h - + * + * Copyright (C) 2004-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + * + *----------------------------------------------------------------------------- + */ + +#define SBE_BID_REG 0x00000000 /* Board ID Register */ + +#define SBE_BID_256T3_E1 0x46 /* SBE wanPTMC-256T3 (E1 Version) */ +#define SBE_BID_256T3_T1 0x42 /* SBE wanPTMC-256T3 (T1 Version) */ +#define SBE_BID_2T3E3 0x43 /* SBE wanPMC-2T3E3 */ +#define SBE_BID_C1T3 0x45 /* SBE wanPMC-C1T3 */ +#define SBE_BID_C24TE1 0x47 /* SBE wanPTMC-C24TE1 */ +#define SBE_BID_C24TE1_RTM_24 0x48 /* C24TE1 RTM (24 Port) */ +#define SBE_BID_C24TE1_RTM_12 0x49 /* C24TE1 RTM (12 Port) */ +#define SBE_BID_C24TE1_RTM_12DSU 0x4A /* C24TE1 RTM (12 Port/DSU) */ +#define SBE_BID_C24TE1_RTM_T3 0x4B /* C24TE1 RTM (T3) */ +#define SBE_BID_C4T1E1 0x41 /* SBE wanPTMC-C4T1E1 */ +#define SBE_BID_HC4T1E1 0x44 /* SBE wanADAPT-HC4T1E1 */ + +/* bogus temporary usage values */ +#define SBE_BID_PMC_C4T1E1 0xC4 /* SBE wanPMC-C4T1E1 (4 Port) */ +#define SBE_BID_PMC_C2T1E1 0xC2 /* SBE wanPMC-C2T1E1 (2 Port) */ +#define SBE_BID_PMC_C1T1E1 0xC1 /* SBE wanPMC-C1T1E1 (1 Port) */ +#define SBE_BID_PCI_C4T1E1 0x04 /* SBE wanPCI-C4T1E1 (4 Port) */ +#define SBE_BID_PCI_C2T1E1 0x02 /* SBE wanPCI-C2T1E1 (2 Port) */ +#define SBE_BID_PCI_C1T1E1 0x01 /* SBE wanPCI-C1T1E1 (1 Port) */ + +#endif /*** _INC_SBEBID_H_ ***/ diff --git a/drivers/staging/cxt1e1/sbe_promformat.h b/drivers/staging/cxt1e1/sbe_promformat.h new file mode 100644 index 00000000..aad411d1 --- /dev/null +++ b/drivers/staging/cxt1e1/sbe_promformat.h @@ -0,0 +1,130 @@ +#ifndef _INC_SBE_PROMFORMAT_H_ +#define _INC_SBE_PROMFORMAT_H_ + +/*----------------------------------------------------------------------------- + * sbe_promformat.h - Contents of seeprom used by dvt and manufacturing tests + * + * Copyright (C) 2002-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + * + *----------------------------------------------------------------------------- + */ + + +/*** + * PMCC4 SAMPLE EEPROM IMAGE + * + * eeprom[00]: 01 11 76 07 01 00 a0 d6 + * eeprom[08]: 22 34 56 3e 5b c1 1c 3e + * eeprom[16]: 5b e1 b6 00 00 00 01 00 + * eeprom[24]: 00 08 46 d3 7b 5e a8 fb + * eeprom[32]: f7 ef df bf 7f 55 00 01 + * eeprom[40]: 02 04 08 10 20 40 80 ff + * eeprom[48]: fe fd fb f7 ef df bf 7f + * + ***/ + + +/*------------------------------------------------------------------------ + * Type 1 Format + * byte: + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 + * ------------------------------------------------------------------------- + * 01 11 76 SS SS 00 0A D6 + * SBE SUB SERIAL # (BCD) (time_t) (time_t) + * ID VENDOR (format) (format) + * + * 19 20 21 22 23 24 25 26 + * Heat Run Heat Run + * Iterations Errors + *------------------------------------------------------------------------ + * + * + * + * Type 2 Format - Added length, CRC in fixed position + * byte: + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + * ------------------------------------------------------------------------- + * 02 00 1A CC CC CC CC 11 76 07 03 00 0A D6 + * Payload SBE Crc32 SUB System System SERIAL/MAC + * Length VENDOR ID ID + * + * 17 18 19 20 21 22 23 24 25 26 27 28 29 39 31 32 + * -------------------------------------------------------------------------- + * Heat Run Heat Run + * (time_t) (time_t) Iterations Errors + * + */ + +#define STRUCT_OFFSET(type, symbol) ((long)&(((type *)0)->symbol)) + +/*------------------------------------------------------------------------ + * Historically different Prom format types. + * + * For diagnostic and failure purposes, do not create a type 0x00 or a + * type 0xff + *------------------------------------------------------------------------ + */ +#define PROM_FORMAT_Unk (-1) +#define PROM_FORMAT_TYPE1 1 +#define PROM_FORMAT_TYPE2 2 + + +/****** bit fields for a type 1 formatted seeprom **************************/ + typedef struct + { + char type; /* 0x00 */ + char Id[2]; /* 0x01-0x02 */ + char SubId[2]; /* 0x03-0x04 */ + char Serial[6]; /* 0x05-0x0a */ + char CreateTime[4]; /* 0x0b-0x0e */ + char HeatRunTime[4]; /* 0x0f-0x12 */ + char HeatRunIterations[4]; /* 0x13-0x16 */ + char HeatRunErrors[4]; /* 0x17-0x1a */ + char Crc32[4]; /* 0x1b-0x1e */ + } FLD_TYPE1; + + +/****** bit fields for a type 2 formatted seeprom **************************/ + typedef struct + { + char type; /* 0x00 */ + char length[2]; /* 0x01-0x02 */ + char Crc32[4]; /* 0x03-0x06 */ + char Id[2]; /* 0x07-0x08 */ + char SubId[2]; /* 0x09-0x0a */ + char Serial[6]; /* 0x0b-0x10 */ + char CreateTime[4]; /* 0x11-0x14 */ + char HeatRunTime[4]; /* 0x15-0x18 */ + char HeatRunIterations[4]; /* 0x19-0x1c */ + char HeatRunErrors[4]; /* 0x1d-0x20 */ + } FLD_TYPE2; + + + +/***** this union allows us to access the seeprom as an array of bytes ***/ +/***** or as individual fields ***/ + +#define SBE_EEPROM_SIZE 128 +#define SBE_MFG_INFO_SIZE sizeof(FLD_TYPE2) + + typedef union + { + char bytes[128]; + FLD_TYPE1 fldType1; + FLD_TYPE2 fldType2; + } PROMFORMAT; + +#endif /*** _INC_SBE_PROMFORMAT_H_ ***/ diff --git a/drivers/staging/cxt1e1/sbecom_inline_linux.h b/drivers/staging/cxt1e1/sbecom_inline_linux.h new file mode 100644 index 00000000..68ed445a --- /dev/null +++ b/drivers/staging/cxt1e1/sbecom_inline_linux.h @@ -0,0 +1,214 @@ +#ifndef _INC_SBECOM_INLNX_H_ +#define _INC_SBECOM_INLNX_H_ + +/*----------------------------------------------------------------------------- + * sbecom_inline_linux.h - SBE common Linux inlined routines + * + * Copyright (C) 2007 One Stop Systems, Inc. + * Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@onestopsystems.com + * One Stop Systems, Inc. Escondido, California U.S.A. + *----------------------------------------------------------------------------- + */ + + +#include +#include +#include /* resolves kmalloc references */ +#include /* resolves skb references */ +#include /* resolves dev_kree_skb_any */ +#include /* resolves cpu_to_le32 */ + +/* forward reference */ +u_int32_t pci_read_32 (u_int32_t *p); +void pci_write_32 (u_int32_t *p, u_int32_t v); + + +/* + * system dependent callbacks + */ + +/**********/ +/* malloc */ +/**********/ + +static inline void * +OS_kmalloc (size_t size) +{ + char *ptr = kmalloc (size, GFP_KERNEL | GFP_DMA); + + if (ptr) + memset (ptr, 0, size); + return ptr; +} + +static inline void +OS_kfree (void *x) +{ + kfree (x); +} + + +/****************/ +/* memory token */ +/****************/ + +static inline void * +OS_mem_token_alloc (size_t size) +{ + struct sk_buff *skb; + + skb = dev_alloc_skb (size); + if (!skb) + { + //pr_warning("no mem in OS_mem_token_alloc !\n"); + return 0; + } + return skb; +} + + +static inline void +OS_mem_token_free (void *token) +{ + dev_kfree_skb_any (token); +} + + +static inline void +OS_mem_token_free_irq (void *token) +{ + dev_kfree_skb_irq (token); +} + + +static inline void * +OS_mem_token_data (void *token) +{ + return ((struct sk_buff *) token)->data; +} + + +static inline void * +OS_mem_token_next (void *token) +{ + return 0; +} + + +static inline int +OS_mem_token_len (void *token) +{ + return ((struct sk_buff *) token)->len; +} + + +static inline int +OS_mem_token_tlen (void *token) +{ + return ((struct sk_buff *) token)->len; +} + + +/***************************************/ +/* virtual to physical addr conversion */ +/***************************************/ + +static inline u_long +OS_phystov (void *addr) +{ + return (u_long) __va (addr); +} + + +static inline u_long +OS_vtophys (void *addr) +{ + return __pa (addr); +} + + +/**********/ +/* semops */ +/**********/ + +void OS_sem_init (void *, int); + + +static inline void +OS_sem_free (void *sem) +{ + /* + * NOOP - since semaphores structures predeclared w/in structures, no + * longer malloc'd + */ +} + +#define SD_SEM_TAKE(sem,desc) down(sem) +#define SD_SEM_GIVE(sem) up(sem) +#define SEM_AVAILABLE 1 +#define SEM_TAKEN 0 + + +/**********************/ +/* watchdog functions */ +/**********************/ + +struct watchdog +{ + struct timer_list h; + struct work_struct work; + void *softc; + void (*func) (void *softc); + int ticks; + int init_tq; +}; + + +static inline int +OS_start_watchdog (struct watchdog * wd) +{ + wd->h.expires = jiffies + wd->ticks; + add_timer (&wd->h); + return 0; +} + + +static inline int +OS_stop_watchdog (struct watchdog * wd) +{ + del_timer_sync (&wd->h); + return 0; +} + + +static inline int +OS_free_watchdog (struct watchdog * wd) +{ + OS_stop_watchdog (wd); + OS_kfree (wd); + return 0; +} + + +/* sleep in microseconds */ +void OS_uwait (int usec, char *description); +void OS_uwait_dummy (void); + + +/* watchdog functions */ +int OS_init_watchdog(struct watchdog *wdp, void (*f) (void *), void *ci, int usec); + + +#endif /*** _INC_SBECOM_INLNX_H_ ***/ diff --git a/drivers/staging/cxt1e1/sbecrc.c b/drivers/staging/cxt1e1/sbecrc.c new file mode 100644 index 00000000..3f3cd60a --- /dev/null +++ b/drivers/staging/cxt1e1/sbecrc.c @@ -0,0 +1,137 @@ +/* Based on "File Verification Using CRC" by Mark R. Nelson in Dr. Dobbs' + * Journal, May 1992, pp. 64-67. This algorithm generates the same CRC + * values as ZMODEM and PKZIP + * + * Copyright (C) 2002-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "pmcc4_sysdep.h" +#include "sbecom_inline_linux.h" +#include "sbe_promformat.h" + +/* defines */ +#define CRC32_POLYNOMIAL 0xEDB88320L +#define CRC_TABLE_ENTRIES 256 + + + +static u_int32_t crcTableInit; + +#ifdef STATIC_CRC_TABLE +static u_int32_t CRCTable[CRC_TABLE_ENTRIES]; + +#endif + + +/*************************************************************************** +* +* genCrcTable - fills in CRCTable, as used by sbeCrc() +* +* RETURNS: N/A +* +* ERRNO: N/A +***************************************************************************/ + +static void +genCrcTable (u_int32_t *CRCTable) +{ + int ii, jj; + u_int32_t crc; + + for (ii = 0; ii < CRC_TABLE_ENTRIES; ii++) + { + crc = ii; + for (jj = 8; jj > 0; jj--) + { + if (crc & 1) + crc = (crc >> 1) ^ CRC32_POLYNOMIAL; + else + crc >>= 1; + } + CRCTable[ii] = crc; + } + + crcTableInit++; +} + + +/*************************************************************************** +* +* sbeCrc - generates a CRC on a given buffer, and initial CRC +* +* This routine calculates the CRC for a buffer of data using the +* table lookup method. It accepts an original value for the crc, +* and returns the updated value. This permits "catenation" of +* discontiguous buffers. An original value of 0 for the "first" +* buffer is the norm. +* +* Based on "File Verification Using CRC" by Mark R. Nelson in Dr. Dobb's +* Journal, May 1992, pp. 64-67. This algorithm generates the same CRC +* values as ZMODEM and PKZIP. +* +* RETURNS: calculated crc of block +* +*/ + +void +sbeCrc (u_int8_t *buffer, /* data buffer to crc */ + u_int32_t count, /* length of block in bytes */ + u_int32_t initialCrc, /* starting CRC */ + u_int32_t *result) +{ + u_int32_t *tbl = 0; + u_int32_t temp1, temp2, crc; + + /* + * if table not yet created, do so. Don't care about "extra" time + * checking this every time sbeCrc() is called, since CRC calculations are + * already time consuming + */ + if (!crcTableInit) + { +#ifdef STATIC_CRC_TABLE + tbl = &CRCTable; + genCrcTable (tbl); +#else + tbl = (u_int32_t *) OS_kmalloc (CRC_TABLE_ENTRIES * sizeof (u_int32_t)); + if (tbl == 0) + { + *result = 0; /* dummy up return value due to malloc + * failure */ + return; + } + genCrcTable (tbl); +#endif + } + /* inverting bits makes ZMODEM & PKZIP compatible */ + crc = initialCrc ^ 0xFFFFFFFFL; + + while (count-- != 0) + { + temp1 = (crc >> 8) & 0x00FFFFFFL; + temp2 = tbl[((int) crc ^ *buffer++) & 0xff]; + crc = temp1 ^ temp2; + } + + crc ^= 0xFFFFFFFFL; + + *result = crc; + +#ifndef STATIC_CRC_TABLE + crcTableInit = 0; + OS_kfree (tbl); +#endif +} + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/sbeid.c b/drivers/staging/cxt1e1/sbeid.c new file mode 100644 index 00000000..a2243b10 --- /dev/null +++ b/drivers/staging/cxt1e1/sbeid.c @@ -0,0 +1,217 @@ +/* Copyright (C) 2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "pmcc4_sysdep.h" +#include "sbecom_inline_linux.h" +#include "libsbew.h" +#include "pmcc4_private.h" +#include "pmcc4.h" +#include "sbe_bid.h" + +#ifdef SBE_INCLUDE_SYMBOLS +#define STATIC +#else +#define STATIC static +#endif + + +char * +sbeid_get_bdname (ci_t * ci) +{ + char *np = 0; + + switch (ci->brd_id) + { + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_E1): + np = "wanPTMC-256T3 "; + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_T1): + np = "wanPTMC-256T3 "; + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C4T1E1): + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C4T1E1_L): + np = "wanPMC-C4T1E1"; + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C2T1E1): + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C2T1E1_L): + np = "wanPMC-C2T1E1"; + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T1E1): + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T1E1_L): + np = "wanPMC-C1T1E1"; + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C4T1E1): + np = "wanPCI-C4T1E1"; + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C2T1E1): + np = "wanPCI-C2T1E1"; + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C1T1E1): + np = "wanPCI-C1T1E1"; + break; + default: + /*** np = ""; ***/ + np = "wanPCI-CxT1E1"; + break; + } + + return np; +} + + +/* given the presetting of brd_id, set the corresponding hdw_id */ + +void +sbeid_set_hdwbid (ci_t * ci) +{ + /* + * set SBE's unique hardware identification (for legacy boards might not + * have this register implemented) + */ + + switch (ci->brd_id) + { + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_E1): + ci->hdw_bid = SBE_BID_256T3_E1; /* 0x46 - SBE wanPTMC-256T3 (E1 + * Version) */ + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_T1): + ci->hdw_bid = SBE_BID_256T3_T1; /* 0x42 - SBE wanPTMC-256T3 (T1 + * Version) */ + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C4T1E1): + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C4T1E1_L): + /* + * This Board ID is a generic identification. Use the found number + * of ports to further define this hardware. + */ + switch (ci->max_port) + { + default: /* shouldn't need a default, but have one + * anyway */ + case 4: + ci->hdw_bid = SBE_BID_PMC_C4T1E1; /* 0xC4 - SBE wanPMC-C4T1E1 */ + break; + case 2: + ci->hdw_bid = SBE_BID_PMC_C2T1E1; /* 0xC2 - SBE wanPMC-C2T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C2T1E1); + break; + case 1: + ci->hdw_bid = SBE_BID_PMC_C1T1E1; /* 0xC1 - SBE wanPMC-C1T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T1E1); + break; + } + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C2T1E1): + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C2T1E1_L): + ci->hdw_bid = SBE_BID_PMC_C2T1E1; /* 0xC2 - SBE wanPMC-C2T1E1 */ + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T1E1): + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T1E1_L): + ci->hdw_bid = SBE_BID_PMC_C1T1E1; /* 0xC1 - SBE wanPMC-C1T1E1 */ + break; +#ifdef SBE_PMCC4_ENABLE + /* + * This case is entered as a result of the inability to obtain the + * from the board's EEPROM. Assume a PCI board and set + * according to the number ofr found ports. + */ + case 0: + /* start by assuming 4-port for ZERO casing */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C4T1E1); + /* drop thru to set hdw_bid and alternate PCI CxT1E1 settings */ +#endif + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C4T1E1): + /* + * This Board ID is a generic identification. Use the number of + * found ports to further define this hardware. + */ + switch (ci->max_port) + { + default: /* shouldn't need a default, but have one + * anyway */ + case 4: + ci->hdw_bid = SBE_BID_PCI_C4T1E1; /* 0x04 - SBE wanPCI-C4T1E1 */ + break; + case 2: + ci->hdw_bid = SBE_BID_PCI_C2T1E1; /* 0x02 - SBE wanPCI-C2T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C2T1E1); + break; + case 1: + ci->hdw_bid = SBE_BID_PCI_C1T1E1; /* 0x01 - SBE wanPCI-C1T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C1T1E1); + break; + } + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C2T1E1): + ci->hdw_bid = SBE_BID_PCI_C2T1E1; /* 0x02 - SBE wanPCI-C2T1E1 */ + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C1T1E1): + ci->hdw_bid = SBE_BID_PCI_C1T1E1; /* 0x01 - SBE wanPCI-C1T1E1 */ + break; + default: + /*** bid = ""; ***/ + ci->hdw_bid = SBE_BID_PMC_C4T1E1; /* 0x41 - SBE wanPTMC-C4T1E1 */ + break; + } +} + +/* given the presetting of hdw_bid, set the corresponding brd_id */ + +void +sbeid_set_bdtype (ci_t * ci) +{ + /* set SBE's unique PCI VENDOR/DEVID */ + switch (ci->hdw_bid) + { + case SBE_BID_C1T3: /* SBE wanPMC-C1T3 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T3); + break; + case SBE_BID_C24TE1: /* SBE wanPTMC-C24TE1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_C24TE1); + break; + case SBE_BID_256T3_E1: /* SBE wanPTMC-256T3 E1 Version */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_E1); + break; + case SBE_BID_256T3_T1: /* SBE wanPTMC-256T3 T1 Version */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_T1); + break; + case SBE_BID_PMC_C4T1E1: /* 0xC4 - SBE wanPMC-C4T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C4T1E1); + break; + case SBE_BID_PMC_C2T1E1: /* 0xC2 - SBE wanPMC-C2T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C2T1E1); + break; + case SBE_BID_PMC_C1T1E1: /* 0xC1 - SBE wanPMC-C1T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T1E1); + break; + case SBE_BID_PCI_C4T1E1: /* 0x04 - SBE wanPCI-C4T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C4T1E1); + break; + case SBE_BID_PCI_C2T1E1: /* 0x02 - SBE wanPCI-C2T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C2T1E1); + break; + case SBE_BID_PCI_C1T1E1: /* 0x01 - SBE wanPCI-C1T1E1 */ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C1T1E1); + break; + + default: + /*** hdw_bid = ""; ***/ + ci->brd_id = SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C4T1E1); + break; + } +} + + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/sbeproc.c b/drivers/staging/cxt1e1/sbeproc.c new file mode 100644 index 00000000..f42531c3 --- /dev/null +++ b/drivers/staging/cxt1e1/sbeproc.c @@ -0,0 +1,357 @@ +/* Copyright (C) 2004-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmcc4_sysdep.h" +#include "sbecom_inline_linux.h" +#include "pmcc4_private.h" +#include "sbeproc.h" + +/* forwards */ +void sbecom_get_brdinfo (ci_t *, struct sbe_brd_info *, u_int8_t *); +extern struct s_hdw_info hdw_info[MAX_BOARDS]; + +#ifdef CONFIG_PROC_FS + +/********************************************************************/ +/* procfs stuff */ +/********************************************************************/ + + +void +sbecom_proc_brd_cleanup (ci_t * ci) +{ + if (ci->dir_dev) + { + char dir[7 + SBE_IFACETMPL_SIZE + 1]; + snprintf(dir, sizeof(dir), "driver/%s", ci->devname); + remove_proc_entry("info", ci->dir_dev); + remove_proc_entry(dir, NULL); + ci->dir_dev = NULL; + } +} + + +static int +sbecom_proc_get_sbe_info (char *buffer, char **start, off_t offset, + int length, int *eof, void *priv) +{ + ci_t *ci = (ci_t *) priv; + int len = 0; + char *spd; + struct sbe_brd_info *bip; + + if (!(bip = OS_kmalloc (sizeof (struct sbe_brd_info)))) + { + return -ENOMEM; + } +#if 0 + /** RLD DEBUG **/ + pr_info(">> sbecom_proc_get_sbe_info: entered, offset %d. length %d.\n", + (int) offset, (int) length); +#endif + + { + hdw_info_t *hi = &hdw_info[ci->brdno]; + + u_int8_t *bsn = 0; + + switch (hi->promfmt) + { + case PROM_FORMAT_TYPE1: + bsn = (u_int8_t *) hi->mfg_info.pft1.Serial; + break; + case PROM_FORMAT_TYPE2: + bsn = (u_int8_t *) hi->mfg_info.pft2.Serial; + break; + } + + sbecom_get_brdinfo (ci, bip, bsn); + } + +#if 0 + /** RLD DEBUG **/ + pr_info(">> sbecom_get_brdinfo: returned, first_if %p <%s> last_if %p <%s>\n", + (char *) &bip->first_iname, (char *) &bip->first_iname, + (char *) &bip->last_iname, (char *) &bip->last_iname); +#endif + len += sprintf (buffer + len, "Board Type: "); + switch (bip->brd_id) + { + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T3): + len += sprintf (buffer + len, "wanPMC-C1T3"); + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_E1): + len += sprintf (buffer + len, "wanPTMC-256T3 "); + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_256T3_T1): + len += sprintf (buffer + len, "wanPTMC-256T3 "); + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPTMC_C24TE1): + len += sprintf (buffer + len, "wanPTMC-C24TE1"); + break; + + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C4T1E1): + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C4T1E1_L): + len += sprintf (buffer + len, "wanPMC-C4T1E1"); + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C2T1E1): + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C2T1E1_L): + len += sprintf (buffer + len, "wanPMC-C2T1E1"); + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T1E1): + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPMC_C1T1E1_L): + len += sprintf (buffer + len, "wanPMC-C1T1E1"); + break; + + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C4T1E1): + len += sprintf (buffer + len, "wanPCI-C4T1E1"); + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C2T1E1): + len += sprintf (buffer + len, "wanPCI-C2T1E1"); + break; + case SBE_BOARD_ID (PCI_VENDOR_ID_SBE, PCI_DEVICE_ID_WANPCI_C1T1E1): + len += sprintf (buffer + len, "wanPCI-C1T1E1"); + break; + + default: + len += sprintf (buffer + len, "unknown"); + break; + } + len += sprintf (buffer + len, " [%08X]\n", bip->brd_id); + + len += sprintf (buffer + len, "Board Number: %d\n", bip->brdno); + len += sprintf (buffer + len, "Hardware ID: 0x%02X\n", ci->hdw_bid); + len += sprintf (buffer + len, "Board SN: %06X\n", bip->brd_sn); + len += sprintf(buffer + len, "Board MAC: %pMF\n", + bip->brd_mac_addr); + len += sprintf (buffer + len, "Ports: %d\n", ci->max_port); + len += sprintf (buffer + len, "Channels: %d\n", bip->brd_chan_cnt); +#if 1 + len += sprintf (buffer + len, "Interface: %s -> %s\n", + (char *) &bip->first_iname, (char *) &bip->last_iname); +#else + len += sprintf (buffer + len, "Interface: 1st %p lst %p\n", + (char *) &bip->first_iname, (char *) &bip->last_iname); +#endif + + switch (bip->brd_pci_speed) + { + case BINFO_PCI_SPEED_33: + spd = "33Mhz"; + break; + case BINFO_PCI_SPEED_66: + spd = "66Mhz"; + break; + default: + spd = ""; + break; + } + len += sprintf (buffer + len, "PCI Bus Speed: %s\n", spd); + len += sprintf (buffer + len, "Release: %s\n", ci->release); + +#ifdef SBE_PMCC4_ENABLE + { + extern int cxt1e1_max_mru; +#if 0 + extern int max_chans_used; + extern int cxt1e1_max_mtu; +#endif + extern int max_rxdesc_used, max_txdesc_used; + + len += sprintf (buffer + len, "\ncxt1e1_max_mru: %d\n", cxt1e1_max_mru); +#if 0 + len += sprintf (buffer + len, "\nmax_chans_used: %d\n", max_chans_used); + len += sprintf (buffer + len, "cxt1e1_max_mtu: %d\n", cxt1e1_max_mtu); +#endif + len += sprintf (buffer + len, "max_rxdesc_used: %d\n", max_rxdesc_used); + len += sprintf (buffer + len, "max_txdesc_used: %d\n", max_txdesc_used); + } +#endif + + OS_kfree (bip); /* cleanup */ + + /*** + * How to be a proc read function + * ------------------------------ + * Prototype: + * int f(char *buffer, char **start, off_t offset, + * int count, int *peof, void *dat) + * + * Assume that the buffer is "count" bytes in size. + * + * If you know you have supplied all the data you + * have, set *peof. + * + * You have three ways to return data: + * 0) Leave *start = NULL. (This is the default.) + * Put the data of the requested offset at that + * offset within the buffer. Return the number (n) + * of bytes there are from the beginning of the + * buffer up to the last byte of data. If the + * number of supplied bytes (= n - offset) is + * greater than zero and you didn't signal eof + * and the reader is prepared to take more data + * you will be called again with the requested + * offset advanced by the number of bytes + * absorbed. This interface is useful for files + * no larger than the buffer. + * 1) Set *start = an unsigned long value less than + * the buffer address but greater than zero. + * Put the data of the requested offset at the + * beginning of the buffer. Return the number of + * bytes of data placed there. If this number is + * greater than zero and you didn't signal eof + * and the reader is prepared to take more data + * you will be called again with the requested + * offset advanced by *start. This interface is + * useful when you have a large file consisting + * of a series of blocks which you want to count + * and return as wholes. + * (Hack by Paul.Russell@rustcorp.com.au) + * 2) Set *start = an address within the buffer. + * Put the data of the requested offset at *start. + * Return the number of bytes of data placed there. + * If this number is greater than zero and you + * didn't signal eof and the reader is prepared to + * take more data you will be called again with the + * requested offset advanced by the number of bytes + * absorbed. + */ + +#if 1 + /* #4 - interpretation of above = set EOF, return len */ + *eof = 1; +#endif + +#if 0 + /* + * #1 - from net/wireless/atmel.c RLD NOTE -there's something wrong with + * this plagarized code which results in this routine being called TWICE. + * The second call returns ZERO, resulting in hidden failure, but at + * least only a single message set is being displayed. + */ + if (len <= offset + length) + *eof = 1; + *start = buffer + offset; + len -= offset; + if (len > length) + len = length; + if (len < 0) + len = 0; +#endif + +#if 0 /* #2 from net/tokenring/olympic.c + + * lanstreamer.c */ + { + off_t begin = 0; + int size = 0; + off_t pos = 0; + + size = len; + pos = begin + size; + if (pos < offset) + { + len = 0; + begin = pos; + } + *start = buffer + (offset - begin); /* Start of wanted data */ + len -= (offset - begin); /* Start slop */ + if (len > length) + len = length; /* Ending slop */ + } +#endif + +#if 0 /* #3 from + * char/ftape/lowlevel/ftape-proc.c */ + len = strlen (buffer); + *start = NULL; + if (offset + length >= len) + *eof = 1; + else + *eof = 0; +#endif + +#if 0 + pr_info(">> proc_fs: returned len = %d., start %p\n", len, start); /* RLD DEBUG */ +#endif + +/*** + using NONE: returns = 314.314.314. + using #1 : returns = 314, 0. + using #2 : returns = 314, 0, 0. + using #3 : returns = 314, 314. + using #4 : returns = 314, 314. +***/ + + return len; +} + +/* initialize the /proc subsystem for the specific SBE driver */ + +int __init +sbecom_proc_brd_init (ci_t * ci) +{ + struct proc_dir_entry *e; + char dir[7 + SBE_IFACETMPL_SIZE + 1]; + + /* create a directory in the root procfs */ + snprintf(dir, sizeof(dir), "driver/%s", ci->devname); + ci->dir_dev = proc_mkdir(dir, NULL); + if (!ci->dir_dev) + { + pr_err("Unable to create directory /proc/driver/%s\n", ci->devname); + goto fail; + } + e = create_proc_read_entry ("info", S_IFREG | S_IRUGO, + ci->dir_dev, sbecom_proc_get_sbe_info, ci); + if (!e) + { + pr_err("Unable to create entry /proc/driver/%s/info\n", ci->devname); + goto fail; + } + return 0; + +fail: + sbecom_proc_brd_cleanup (ci); + return 1; +} + +#else /*** ! CONFIG_PROC_FS ***/ + +/* stubbed off dummy routines */ + +void +sbecom_proc_brd_cleanup (ci_t * ci) +{ +} + +int __init +sbecom_proc_brd_init (ci_t * ci) +{ + return 0; +} + +#endif /*** CONFIG_PROC_FS ***/ + + +/*** End-of-File ***/ diff --git a/drivers/staging/cxt1e1/sbeproc.h b/drivers/staging/cxt1e1/sbeproc.h new file mode 100644 index 00000000..e82be6af --- /dev/null +++ b/drivers/staging/cxt1e1/sbeproc.h @@ -0,0 +1,32 @@ +#ifndef _INC_SBEPROC_H_ +#define _INC_SBEPROC_H_ + +/*----------------------------------------------------------------------------- + * sbeproc.h - + * + * Copyright (C) 2004-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + *----------------------------------------------------------------------------- + */ + + +#ifdef CONFIG_PROC_FS +#ifdef __KERNEL__ +void sbecom_proc_brd_cleanup (ci_t *); +int __init sbecom_proc_brd_init (ci_t *); + +#endif /*** __KERNEL__ ***/ +#endif /*** CONFIG_PROC_FS ***/ +#endif /*** _INC_SBEPROC_H_ ***/ diff --git a/drivers/staging/cxt1e1/sbew_ioc.h b/drivers/staging/cxt1e1/sbew_ioc.h new file mode 100644 index 00000000..ce9b15c7 --- /dev/null +++ b/drivers/staging/cxt1e1/sbew_ioc.h @@ -0,0 +1,81 @@ +#ifndef _INC_SBEWIOC_H_ +#define _INC_SBEWIOC_H_ + +/*----------------------------------------------------------------------------- + * sbew_ioc.h - + * + * Copyright (C) 2002-2005 SBE, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For further information, contact via email: support@sbei.com + * SBE, Inc. San Ramon, California U.S.A. + * + *----------------------------------------------------------------------------- + */ + +#include + +#define SBE_LOCKFILE "/tmp/.sbewan.LCK" + +#define SBE_IOC_COOKIE 0x19780926 +#define SBE_IOC_MAGIC ('s') + +/* IOW write - data has to go into driver from application */ +/* IOR read - data has to be returned to application from driver */ + +/* + * Note: for an IOWR Ioctl, the read and write data do not have to + * be the same size, but the entity declared within the IOC must be + * the larger of the two. + */ + +#define SBE_IOC_LOGLEVEL _IOW(SBE_IOC_MAGIC, 0x00, int) +#define SBE_IOC_CHAN_NEW _IOW(SBE_IOC_MAGIC, 0x01,int) /* unused */ +#define SBE_IOC_CHAN_UP _IOW(SBE_IOC_MAGIC, 0x02,int) /* unused */ +#define SBE_IOC_CHAN_DOWN _IOW(SBE_IOC_MAGIC, 0x03,int) /* unused */ +#define SBE_IOC_CHAN_GET _IOWR(SBE_IOC_MAGIC,0x04, struct sbecom_chan_param) +#define SBE_IOC_CHAN_SET _IOW(SBE_IOC_MAGIC, 0x05, struct sbecom_chan_param) +#define SBE_IOC_CHAN_GET_STAT _IOWR(SBE_IOC_MAGIC,0x06, struct sbecom_chan_stats) +#define SBE_IOC_CHAN_DEL_STAT _IOW(SBE_IOC_MAGIC, 0x07, int) +#define SBE_IOC_PORTS_ENABLE _IOW(SBE_IOC_MAGIC, 0x0A, int) +#define SBE_IOC_PORT_GET _IOWR(SBE_IOC_MAGIC,0x0C, struct sbecom_port_param) +#define SBE_IOC_PORT_SET _IOW(SBE_IOC_MAGIC, 0x0D, struct sbecom_port_param) +#define SBE_IOC_READ_VEC _IOWR(SBE_IOC_MAGIC,0x10, struct sbecom_wrt_vec) +#define SBE_IOC_WRITE_VEC _IOWR(SBE_IOC_MAGIC,0x11, struct sbecom_wrt_vec) +#define SBE_IOC_GET_SN _IOR(SBE_IOC_MAGIC, 0x12, u_int32_t) +#define SBE_IOC_RESET_DEV _IOW(SBE_IOC_MAGIC, 0x13, int) +#define SBE_IOC_FRAMER_GET _IOWR(SBE_IOC_MAGIC,0x14, struct sbecom_framer_param) +#define SBE_IOC_FRAMER_SET _IOW(SBE_IOC_MAGIC, 0x15, struct sbecom_framer_param) +#define SBE_IOC_CARD_GET _IOR(SBE_IOC_MAGIC, 0x20, struct sbecom_card_param) +#define SBE_IOC_CARD_SET _IOW(SBE_IOC_MAGIC, 0x21, struct sbecom_card_param) +#define SBE_IOC_CARD_GET_STAT _IOR(SBE_IOC_MAGIC, 0x22, struct temux_card_stats) +#define SBE_IOC_CARD_DEL_STAT _IO(SBE_IOC_MAGIC, 0x23) +#define SBE_IOC_CARD_CHAN_STAT _IOR(SBE_IOC_MAGIC, 0x24, struct sbecom_chan_stats) +#define SBE_IOC_CARD_BLINK _IOW(SBE_IOC_MAGIC, 0x30, int) +#define SBE_IOC_DRVINFO_GET _IOWR(SBE_IOC_MAGIC,0x31, struct sbe_drv_info) +#define SBE_IOC_BRDINFO_GET _IOR(SBE_IOC_MAGIC, 0x32, struct sbe_brd_info) +#define SBE_IOC_IID_GET _IOWR(SBE_IOC_MAGIC,0x33, struct sbe_iid_info) +#define SBE_IOC_BRDADDR_GET _IOWR(SBE_IOC_MAGIC, 0x34, struct sbe_brd_addr) + +#ifdef NOT_YET_COMMON +#define SBE_IOC_TSIOC_GET _IOWR(SBE_IOC_MAGIC,0x16, struct wanc1t3_ts_param) +#define SBE_IOC_TSIOC_SET _IOW(SBE_IOC_MAGIC, 0x17, struct wanc1t3_ts_param) +#endif + +/* + * Restrict SBE_IOC_WRITE_VEC & READ_VEC to a single parameter pair, application + * then must issue multiple Ioctls for large blocks of contiguous data. + */ + +#define SBE_IOC_MAXVEC 1 + +#endif /*** _INC_SBEWIOC_H_ ***/ diff --git a/drivers/staging/echo/Kconfig b/drivers/staging/echo/Kconfig new file mode 100644 index 00000000..f1d41ea9 --- /dev/null +++ b/drivers/staging/echo/Kconfig @@ -0,0 +1,9 @@ +config ECHO + tristate "Line Echo Canceller support" + default n + ---help--- + This driver provides line echo cancelling support for mISDN and + Zaptel drivers. + + To compile this driver as a module, choose M here. The module + will be called echo. diff --git a/drivers/staging/echo/Makefile b/drivers/staging/echo/Makefile new file mode 100644 index 00000000..7d4caac1 --- /dev/null +++ b/drivers/staging/echo/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ECHO) += echo.o diff --git a/drivers/staging/echo/TODO b/drivers/staging/echo/TODO new file mode 100644 index 00000000..72a311a5 --- /dev/null +++ b/drivers/staging/echo/TODO @@ -0,0 +1,5 @@ +TODO: + - send to lkml for review + +Please send patches to Greg Kroah-Hartman and Cc: Steve +Underwood and David Rowe diff --git a/drivers/staging/echo/echo.c b/drivers/staging/echo/echo.c new file mode 100644 index 00000000..afbf5442 --- /dev/null +++ b/drivers/staging/echo/echo.c @@ -0,0 +1,659 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * echo.c - A line echo canceller. This code is being developed + * against and partially complies with G168. + * + * Written by Steve Underwood + * and David Rowe + * + * Copyright (C) 2001, 2003 Steve Underwood, 2007 David Rowe + * + * Based on a bit from here, a bit from there, eye of toad, ear of + * bat, 15 years of failed attempts by David and a few fried brain + * cells. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/*! \file */ + +/* Implementation Notes + David Rowe + April 2007 + + This code started life as Steve's NLMS algorithm with a tap + rotation algorithm to handle divergence during double talk. I + added a Geigel Double Talk Detector (DTD) [2] and performed some + G168 tests. However I had trouble meeting the G168 requirements, + especially for double talk - there were always cases where my DTD + failed, for example where near end speech was under the 6dB + threshold required for declaring double talk. + + So I tried a two path algorithm [1], which has so far given better + results. The original tap rotation/Geigel algorithm is available + in SVN http://svn.rowetel.com/software/oslec/tags/before_16bit. + It's probably possible to make it work if some one wants to put some + serious work into it. + + At present no special treatment is provided for tones, which + generally cause NLMS algorithms to diverge. Initial runs of a + subset of the G168 tests for tones (e.g ./echo_test 6) show the + current algorithm is passing OK, which is kind of surprising. The + full set of tests needs to be performed to confirm this result. + + One other interesting change is that I have managed to get the NLMS + code to work with 16 bit coefficients, rather than the original 32 + bit coefficents. This reduces the MIPs and storage required. + I evaulated the 16 bit port using g168_tests.sh and listening tests + on 4 real-world samples. + + I also attempted the implementation of a block based NLMS update + [2] but although this passes g168_tests.sh it didn't converge well + on the real-world samples. I have no idea why, perhaps a scaling + problem. The block based code is also available in SVN + http://svn.rowetel.com/software/oslec/tags/before_16bit. If this + code can be debugged, it will lead to further reduction in MIPS, as + the block update code maps nicely onto DSP instruction sets (it's a + dot product) compared to the current sample-by-sample update. + + Steve also has some nice notes on echo cancellers in echo.h + + References: + + [1] Ochiai, Areseki, and Ogihara, "Echo Canceller with Two Echo + Path Models", IEEE Transactions on communications, COM-25, + No. 6, June + 1977. + http://www.rowetel.com/images/echo/dual_path_paper.pdf + + [2] The classic, very useful paper that tells you how to + actually build a real world echo canceller: + Messerschmitt, Hedberg, Cole, Haoui, Winship, "Digital Voice + Echo Canceller with a TMS320020, + http://www.rowetel.com/images/echo/spra129.pdf + + [3] I have written a series of blog posts on this work, here is + Part 1: http://www.rowetel.com/blog/?p=18 + + [4] The source code http://svn.rowetel.com/software/oslec/ + + [5] A nice reference on LMS filters: + http://en.wikipedia.org/wiki/Least_mean_squares_filter + + Credits: + + Thanks to Steve Underwood, Jean-Marc Valin, and Ramakrishnan + Muthukrishnan for their suggestions and email discussions. Thanks + also to those people who collected echo samples for me such as + Mark, Pawel, and Pavel. +*/ + +#include +#include +#include + +#include "echo.h" + +#define MIN_TX_POWER_FOR_ADAPTION 64 +#define MIN_RX_POWER_FOR_ADAPTION 64 +#define DTD_HANGOVER 600 /* 600 samples, or 75ms */ +#define DC_LOG2BETA 3 /* log2() of DC filter Beta */ + +/* adapting coeffs using the traditional stochastic descent (N)LMS algorithm */ + +#ifdef __bfin__ +static inline void lms_adapt_bg(struct oslec_state *ec, int clean, int shift) +{ + int i, j; + int offset1; + int offset2; + int factor; + int exp; + int16_t *phist; + int n; + + if (shift > 0) + factor = clean << shift; + else + factor = clean >> -shift; + + /* Update the FIR taps */ + + offset2 = ec->curr_pos; + offset1 = ec->taps - offset2; + phist = &ec->fir_state_bg.history[offset2]; + + /* st: and en: help us locate the assembler in echo.s */ + + /* asm("st:"); */ + n = ec->taps; + for (i = 0, j = offset2; i < n; i++, j++) { + exp = *phist++ * factor; + ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15); + } + /* asm("en:"); */ + + /* Note the asm for the inner loop above generated by Blackfin gcc + 4.1.1 is pretty good (note even parallel instructions used): + + R0 = W [P0++] (X); + R0 *= R2; + R0 = R0 + R3 (NS) || + R1 = W [P1] (X) || + nop; + R0 >>>= 15; + R0 = R0 + R1; + W [P1++] = R0; + + A block based update algorithm would be much faster but the + above can't be improved on much. Every instruction saved in + the loop above is 2 MIPs/ch! The for loop above is where the + Blackfin spends most of it's time - about 17 MIPs/ch measured + with speedtest.c with 256 taps (32ms). Write-back and + Write-through cache gave about the same performance. + */ +} + +/* + IDEAS for further optimisation of lms_adapt_bg(): + + 1/ The rounding is quite costly. Could we keep as 32 bit coeffs + then make filter pluck the MS 16-bits of the coeffs when filtering? + However this would lower potential optimisation of filter, as I + think the dual-MAC architecture requires packed 16 bit coeffs. + + 2/ Block based update would be more efficient, as per comments above, + could use dual MAC architecture. + + 3/ Look for same sample Blackfin LMS code, see if we can get dual-MAC + packing. + + 4/ Execute the whole e/c in a block of say 20ms rather than sample + by sample. Processing a few samples every ms is inefficient. +*/ + +#else +static inline void lms_adapt_bg(struct oslec_state *ec, int clean, int shift) +{ + int i; + + int offset1; + int offset2; + int factor; + int exp; + + if (shift > 0) + factor = clean << shift; + else + factor = clean >> -shift; + + /* Update the FIR taps */ + + offset2 = ec->curr_pos; + offset1 = ec->taps - offset2; + + for (i = ec->taps - 1; i >= offset1; i--) { + exp = (ec->fir_state_bg.history[i - offset1] * factor); + ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15); + } + for (; i >= 0; i--) { + exp = (ec->fir_state_bg.history[i + offset2] * factor); + ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15); + } +} +#endif + +static inline int top_bit(unsigned int bits) +{ + if (bits == 0) + return -1; + else + return (int)fls((int32_t) bits) - 1; +} + +struct oslec_state *oslec_create(int len, int adaption_mode) +{ + struct oslec_state *ec; + int i; + + ec = kzalloc(sizeof(*ec), GFP_KERNEL); + if (!ec) + return NULL; + + ec->taps = len; + ec->log2taps = top_bit(len); + ec->curr_pos = ec->taps - 1; + + for (i = 0; i < 2; i++) { + ec->fir_taps16[i] = + kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL); + if (!ec->fir_taps16[i]) + goto error_oom; + } + + fir16_create(&ec->fir_state, ec->fir_taps16[0], ec->taps); + fir16_create(&ec->fir_state_bg, ec->fir_taps16[1], ec->taps); + + for (i = 0; i < 5; i++) + ec->xvtx[i] = ec->yvtx[i] = ec->xvrx[i] = ec->yvrx[i] = 0; + + ec->cng_level = 1000; + oslec_adaption_mode(ec, adaption_mode); + + ec->snapshot = kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL); + if (!ec->snapshot) + goto error_oom; + + ec->cond_met = 0; + ec->Pstates = 0; + ec->Ltxacc = ec->Lrxacc = ec->Lcleanacc = ec->Lclean_bgacc = 0; + ec->Ltx = ec->Lrx = ec->Lclean = ec->Lclean_bg = 0; + ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0; + ec->Lbgn = ec->Lbgn_acc = 0; + ec->Lbgn_upper = 200; + ec->Lbgn_upper_acc = ec->Lbgn_upper << 13; + + return ec; + +error_oom: + for (i = 0; i < 2; i++) + kfree(ec->fir_taps16[i]); + + kfree(ec); + return NULL; +} +EXPORT_SYMBOL_GPL(oslec_create); + +void oslec_free(struct oslec_state *ec) +{ + int i; + + fir16_free(&ec->fir_state); + fir16_free(&ec->fir_state_bg); + for (i = 0; i < 2; i++) + kfree(ec->fir_taps16[i]); + kfree(ec->snapshot); + kfree(ec); +} +EXPORT_SYMBOL_GPL(oslec_free); + +void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode) +{ + ec->adaption_mode = adaption_mode; +} +EXPORT_SYMBOL_GPL(oslec_adaption_mode); + +void oslec_flush(struct oslec_state *ec) +{ + int i; + + ec->Ltxacc = ec->Lrxacc = ec->Lcleanacc = ec->Lclean_bgacc = 0; + ec->Ltx = ec->Lrx = ec->Lclean = ec->Lclean_bg = 0; + ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0; + + ec->Lbgn = ec->Lbgn_acc = 0; + ec->Lbgn_upper = 200; + ec->Lbgn_upper_acc = ec->Lbgn_upper << 13; + + ec->nonupdate_dwell = 0; + + fir16_flush(&ec->fir_state); + fir16_flush(&ec->fir_state_bg); + ec->fir_state.curr_pos = ec->taps - 1; + ec->fir_state_bg.curr_pos = ec->taps - 1; + for (i = 0; i < 2; i++) + memset(ec->fir_taps16[i], 0, ec->taps * sizeof(int16_t)); + + ec->curr_pos = ec->taps - 1; + ec->Pstates = 0; +} +EXPORT_SYMBOL_GPL(oslec_flush); + +void oslec_snapshot(struct oslec_state *ec) +{ + memcpy(ec->snapshot, ec->fir_taps16[0], ec->taps * sizeof(int16_t)); +} +EXPORT_SYMBOL_GPL(oslec_snapshot); + +/* Dual Path Echo Canceller */ + +int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx) +{ + int32_t echo_value; + int clean_bg; + int tmp, tmp1; + + /* + * Input scaling was found be required to prevent problems when tx + * starts clipping. Another possible way to handle this would be the + * filter coefficent scaling. + */ + + ec->tx = tx; + ec->rx = rx; + tx >>= 1; + rx >>= 1; + + /* + * Filter DC, 3dB point is 160Hz (I think), note 32 bit precision + * required otherwise values do not track down to 0. Zero at DC, Pole + * at (1-Beta) on real axis. Some chip sets (like Si labs) don't + * need this, but something like a $10 X100P card does. Any DC really + * slows down convergence. + * + * Note: removes some low frequency from the signal, this reduces the + * speech quality when listening to samples through headphones but may + * not be obvious through a telephone handset. + * + * Note that the 3dB frequency in radians is approx Beta, e.g. for Beta + * = 2^(-3) = 0.125, 3dB freq is 0.125 rads = 159Hz. + */ + + if (ec->adaption_mode & ECHO_CAN_USE_RX_HPF) { + tmp = rx << 15; + + /* + * Make sure the gain of the HPF is 1.0. This can still + * saturate a little under impulse conditions, and it might + * roll to 32768 and need clipping on sustained peak level + * signals. However, the scale of such clipping is small, and + * the error due to any saturation should not markedly affect + * the downstream processing. + */ + tmp -= (tmp >> 4); + + ec->rx_1 += -(ec->rx_1 >> DC_LOG2BETA) + tmp - ec->rx_2; + + /* + * hard limit filter to prevent clipping. Note that at this + * stage rx should be limited to +/- 16383 due to right shift + * above + */ + tmp1 = ec->rx_1 >> 15; + if (tmp1 > 16383) + tmp1 = 16383; + if (tmp1 < -16383) + tmp1 = -16383; + rx = tmp1; + ec->rx_2 = tmp; + } + + /* Block average of power in the filter states. Used for + adaption power calculation. */ + + { + int new, old; + + /* efficient "out with the old and in with the new" algorithm so + we don't have to recalculate over the whole block of + samples. */ + new = (int)tx * (int)tx; + old = (int)ec->fir_state.history[ec->fir_state.curr_pos] * + (int)ec->fir_state.history[ec->fir_state.curr_pos]; + ec->Pstates += + ((new - old) + (1 << (ec->log2taps - 1))) >> ec->log2taps; + if (ec->Pstates < 0) + ec->Pstates = 0; + } + + /* Calculate short term average levels using simple single pole IIRs */ + + ec->Ltxacc += abs(tx) - ec->Ltx; + ec->Ltx = (ec->Ltxacc + (1 << 4)) >> 5; + ec->Lrxacc += abs(rx) - ec->Lrx; + ec->Lrx = (ec->Lrxacc + (1 << 4)) >> 5; + + /* Foreground filter */ + + ec->fir_state.coeffs = ec->fir_taps16[0]; + echo_value = fir16(&ec->fir_state, tx); + ec->clean = rx - echo_value; + ec->Lcleanacc += abs(ec->clean) - ec->Lclean; + ec->Lclean = (ec->Lcleanacc + (1 << 4)) >> 5; + + /* Background filter */ + + echo_value = fir16(&ec->fir_state_bg, tx); + clean_bg = rx - echo_value; + ec->Lclean_bgacc += abs(clean_bg) - ec->Lclean_bg; + ec->Lclean_bg = (ec->Lclean_bgacc + (1 << 4)) >> 5; + + /* Background Filter adaption */ + + /* Almost always adap bg filter, just simple DT and energy + detection to minimise adaption in cases of strong double talk. + However this is not critical for the dual path algorithm. + */ + ec->factor = 0; + ec->shift = 0; + if ((ec->nonupdate_dwell == 0)) { + int P, logP, shift; + + /* Determine: + + f = Beta * clean_bg_rx/P ------ (1) + + where P is the total power in the filter states. + + The Boffins have shown that if we obey (1) we converge + quickly and avoid instability. + + The correct factor f must be in Q30, as this is the fixed + point format required by the lms_adapt_bg() function, + therefore the scaled version of (1) is: + + (2^30) * f = (2^30) * Beta * clean_bg_rx/P + factor = (2^30) * Beta * clean_bg_rx/P ----- (2) + + We have chosen Beta = 0.25 by experiment, so: + + factor = (2^30) * (2^-2) * clean_bg_rx/P + + (30 - 2 - log2(P)) + factor = clean_bg_rx 2 ----- (3) + + To avoid a divide we approximate log2(P) as top_bit(P), + which returns the position of the highest non-zero bit in + P. This approximation introduces an error as large as a + factor of 2, but the algorithm seems to handle it OK. + + Come to think of it a divide may not be a big deal on a + modern DSP, so its probably worth checking out the cycles + for a divide versus a top_bit() implementation. + */ + + P = MIN_TX_POWER_FOR_ADAPTION + ec->Pstates; + logP = top_bit(P) + ec->log2taps; + shift = 30 - 2 - logP; + ec->shift = shift; + + lms_adapt_bg(ec, clean_bg, shift); + } + + /* very simple DTD to make sure we dont try and adapt with strong + near end speech */ + + ec->adapt = 0; + if ((ec->Lrx > MIN_RX_POWER_FOR_ADAPTION) && (ec->Lrx > ec->Ltx)) + ec->nonupdate_dwell = DTD_HANGOVER; + if (ec->nonupdate_dwell) + ec->nonupdate_dwell--; + + /* Transfer logic */ + + /* These conditions are from the dual path paper [1], I messed with + them a bit to improve performance. */ + + if ((ec->adaption_mode & ECHO_CAN_USE_ADAPTION) && + (ec->nonupdate_dwell == 0) && + /* (ec->Lclean_bg < 0.875*ec->Lclean) */ + (8 * ec->Lclean_bg < 7 * ec->Lclean) && + /* (ec->Lclean_bg < 0.125*ec->Ltx) */ + (8 * ec->Lclean_bg < ec->Ltx)) { + if (ec->cond_met == 6) { + /* + * BG filter has had better results for 6 consecutive + * samples + */ + ec->adapt = 1; + memcpy(ec->fir_taps16[0], ec->fir_taps16[1], + ec->taps * sizeof(int16_t)); + } else + ec->cond_met++; + } else + ec->cond_met = 0; + + /* Non-Linear Processing */ + + ec->clean_nlp = ec->clean; + if (ec->adaption_mode & ECHO_CAN_USE_NLP) { + /* + * Non-linear processor - a fancy way to say "zap small + * signals, to avoid residual echo due to (uLaw/ALaw) + * non-linearity in the channel.". + */ + + if ((16 * ec->Lclean < ec->Ltx)) { + /* + * Our e/c has improved echo by at least 24 dB (each + * factor of 2 is 6dB, so 2*2*2*2=16 is the same as + * 6+6+6+6=24dB) + */ + if (ec->adaption_mode & ECHO_CAN_USE_CNG) { + ec->cng_level = ec->Lbgn; + + /* + * Very elementary comfort noise generation. + * Just random numbers rolled off very vaguely + * Hoth-like. DR: This noise doesn't sound + * quite right to me - I suspect there are some + * overflow issues in the filtering as it's too + * "crackly". + * TODO: debug this, maybe just play noise at + * high level or look at spectrum. + */ + + ec->cng_rndnum = + 1664525U * ec->cng_rndnum + 1013904223U; + ec->cng_filter = + ((ec->cng_rndnum & 0xFFFF) - 32768 + + 5 * ec->cng_filter) >> 3; + ec->clean_nlp = + (ec->cng_filter * ec->cng_level * 8) >> 14; + + } else if (ec->adaption_mode & ECHO_CAN_USE_CLIP) { + /* This sounds much better than CNG */ + if (ec->clean_nlp > ec->Lbgn) + ec->clean_nlp = ec->Lbgn; + if (ec->clean_nlp < -ec->Lbgn) + ec->clean_nlp = -ec->Lbgn; + } else { + /* + * just mute the residual, doesn't sound very + * good, used mainly in G168 tests + */ + ec->clean_nlp = 0; + } + } else { + /* + * Background noise estimator. I tried a few + * algorithms here without much luck. This very simple + * one seems to work best, we just average the level + * using a slow (1 sec time const) filter if the + * current level is less than a (experimentally + * derived) constant. This means we dont include high + * level signals like near end speech. When combined + * with CNG or especially CLIP seems to work OK. + */ + if (ec->Lclean < 40) { + ec->Lbgn_acc += abs(ec->clean) - ec->Lbgn; + ec->Lbgn = (ec->Lbgn_acc + (1 << 11)) >> 12; + } + } + } + + /* Roll around the taps buffer */ + if (ec->curr_pos <= 0) + ec->curr_pos = ec->taps; + ec->curr_pos--; + + if (ec->adaption_mode & ECHO_CAN_DISABLE) + ec->clean_nlp = rx; + + /* Output scaled back up again to match input scaling */ + + return (int16_t) ec->clean_nlp << 1; +} +EXPORT_SYMBOL_GPL(oslec_update); + +/* This function is separated from the echo canceller is it is usually called + as part of the tx process. See rx HP (DC blocking) filter above, it's + the same design. + + Some soft phones send speech signals with a lot of low frequency + energy, e.g. down to 20Hz. This can make the hybrid non-linear + which causes the echo canceller to fall over. This filter can help + by removing any low frequency before it gets to the tx port of the + hybrid. + + It can also help by removing and DC in the tx signal. DC is bad + for LMS algorithms. + + This is one of the classic DC removal filters, adjusted to provide + sufficient bass rolloff to meet the above requirement to protect hybrids + from things that upset them. The difference between successive samples + produces a lousy HPF, and then a suitably placed pole flattens things out. + The final result is a nicely rolled off bass end. The filtering is + implemented with extended fractional precision, which noise shapes things, + giving very clean DC removal. +*/ + +int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx) +{ + int tmp, tmp1; + + if (ec->adaption_mode & ECHO_CAN_USE_TX_HPF) { + tmp = tx << 15; + + /* + * Make sure the gain of the HPF is 1.0. The first can still + * saturate a little under impulse conditions, and it might + * roll to 32768 and need clipping on sustained peak level + * signals. However, the scale of such clipping is small, and + * the error due to any saturation should not markedly affect + * the downstream processing. + */ + tmp -= (tmp >> 4); + + ec->tx_1 += -(ec->tx_1 >> DC_LOG2BETA) + tmp - ec->tx_2; + tmp1 = ec->tx_1 >> 15; + if (tmp1 > 32767) + tmp1 = 32767; + if (tmp1 < -32767) + tmp1 = -32767; + tx = tmp1; + ec->tx_2 = tmp; + } + + return tx; +} +EXPORT_SYMBOL_GPL(oslec_hpf_tx); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Rowe"); +MODULE_DESCRIPTION("Open Source Line Echo Canceller"); +MODULE_VERSION("0.3.0"); diff --git a/drivers/staging/echo/echo.h b/drivers/staging/echo/echo.h new file mode 100644 index 00000000..754e66d3 --- /dev/null +++ b/drivers/staging/echo/echo.h @@ -0,0 +1,175 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * echo.c - A line echo canceller. This code is being developed + * against and partially complies with G168. + * + * Written by Steve Underwood + * and David Rowe + * + * Copyright (C) 2001 Steve Underwood and 2007 David Rowe + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ECHO_H +#define __ECHO_H + +/* +Line echo cancellation for voice + +What does it do? + +This module aims to provide G.168-2002 compliant echo cancellation, to remove +electrical echoes (e.g. from 2-4 wire hybrids) from voice calls. + + +How does it work? + +The heart of the echo cancellor is FIR filter. This is adapted to match the +echo impulse response of the telephone line. It must be long enough to +adequately cover the duration of that impulse response. The signal transmitted +to the telephone line is passed through the FIR filter. Once the FIR is +properly adapted, the resulting output is an estimate of the echo signal +received from the line. This is subtracted from the received signal. The result +is an estimate of the signal which originated at the far end of the line, free +from echos of our own transmitted signal. + +The least mean squares (LMS) algorithm is attributed to Widrow and Hoff, and +was introduced in 1960. It is the commonest form of filter adaption used in +things like modem line equalisers and line echo cancellers. There it works very +well. However, it only works well for signals of constant amplitude. It works +very poorly for things like speech echo cancellation, where the signal level +varies widely. This is quite easy to fix. If the signal level is normalised - +similar to applying AGC - LMS can work as well for a signal of varying +amplitude as it does for a modem signal. This normalised least mean squares +(NLMS) algorithm is the commonest one used for speech echo cancellation. Many +other algorithms exist - e.g. RLS (essentially the same as Kalman filtering), +FAP, etc. Some perform significantly better than NLMS. However, factors such +as computational complexity and patents favour the use of NLMS. + +A simple refinement to NLMS can improve its performance with speech. NLMS tends +to adapt best to the strongest parts of a signal. If the signal is white noise, +the NLMS algorithm works very well. However, speech has more low frequency than +high frequency content. Pre-whitening (i.e. filtering the signal to flatten its +spectrum) the echo signal improves the adapt rate for speech, and ensures the +final residual signal is not heavily biased towards high frequencies. A very +low complexity filter is adequate for this, so pre-whitening adds little to the +compute requirements of the echo canceller. + +An FIR filter adapted using pre-whitened NLMS performs well, provided certain +conditions are met: + + - The transmitted signal has poor self-correlation. + - There is no signal being generated within the environment being + cancelled. + +The difficulty is that neither of these can be guaranteed. + +If the adaption is performed while transmitting noise (or something fairly +noise like, such as voice) the adaption works very well. If the adaption is +performed while transmitting something highly correlative (typically narrow +band energy such as signalling tones or DTMF), the adaption can go seriously +wrong. The reason is there is only one solution for the adaption on a near +random signal - the impulse response of the line. For a repetitive signal, +there are any number of solutions which converge the adaption, and nothing +guides the adaption to choose the generalised one. Allowing an untrained +canceller to converge on this kind of narrowband energy probably a good thing, +since at least it cancels the tones. Allowing a well converged canceller to +continue converging on such energy is just a way to ruin its generalised +adaption. A narrowband detector is needed, so adapation can be suspended at +appropriate times. + +The adaption process is based on trying to eliminate the received signal. When +there is any signal from within the environment being cancelled it may upset +the adaption process. Similarly, if the signal we are transmitting is small, +noise may dominate and disturb the adaption process. If we can ensure that the +adaption is only performed when we are transmitting a significant signal level, +and the environment is not, things will be OK. Clearly, it is easy to tell when +we are sending a significant signal. Telling, if the environment is generating +a significant signal, and doing it with sufficient speed that the adaption will +not have diverged too much more we stop it, is a little harder. + +The key problem in detecting when the environment is sourcing significant +energy is that we must do this very quickly. Given a reasonably long sample of +the received signal, there are a number of strategies which may be used to +assess whether that signal contains a strong far end component. However, by the +time that assessment is complete the far end signal will have already caused +major mis-convergence in the adaption process. An assessment algorithm is +needed which produces a fairly accurate result from a very short burst of far +end energy. + +How do I use it? + +The echo cancellor processes both the transmit and receive streams sample by +sample. The processing function is not declared inline. Unfortunately, +cancellation requires many operations per sample, so the call overhead is only +a minor burden. +*/ + +#include "fir.h" +#include "oslec.h" + +/* + G.168 echo canceller descriptor. This defines the working state for a line + echo canceller. +*/ +struct oslec_state { + int16_t tx, rx; + int16_t clean; + int16_t clean_nlp; + + int nonupdate_dwell; + int curr_pos; + int taps; + int log2taps; + int adaption_mode; + + int cond_met; + int32_t Pstates; + int16_t adapt; + int32_t factor; + int16_t shift; + + /* Average levels and averaging filter states */ + int Ltxacc, Lrxacc, Lcleanacc, Lclean_bgacc; + int Ltx, Lrx; + int Lclean; + int Lclean_bg; + int Lbgn, Lbgn_acc, Lbgn_upper, Lbgn_upper_acc; + + /* foreground and background filter states */ + struct fir16_state_t fir_state; + struct fir16_state_t fir_state_bg; + int16_t *fir_taps16[2]; + + /* DC blocking filter states */ + int tx_1, tx_2, rx_1, rx_2; + + /* optional High Pass Filter states */ + int32_t xvtx[5], yvtx[5]; + int32_t xvrx[5], yvrx[5]; + + /* Parameters for the optional Hoth noise generator */ + int cng_level; + int cng_rndnum; + int cng_filter; + + /* snapshot sample of coeffs used for development */ + int16_t *snapshot; +}; + +#endif /* __ECHO_H */ diff --git a/drivers/staging/echo/fir.h b/drivers/staging/echo/fir.h new file mode 100644 index 00000000..7b9fabf1 --- /dev/null +++ b/drivers/staging/echo/fir.h @@ -0,0 +1,216 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * fir.h - General telephony FIR routines + * + * Written by Steve Underwood + * + * Copyright (C) 2002 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(_FIR_H_) +#define _FIR_H_ + +/* + Blackfin NOTES & IDEAS: + + A simple dot product function is used to implement the filter. This performs + just one MAC/cycle which is inefficient but was easy to implement as a first + pass. The current Blackfin code also uses an unrolled form of the filter + history to avoid 0 length hardware loop issues. This is wasteful of + memory. + + Ideas for improvement: + + 1/ Rewrite filter for dual MAC inner loop. The issue here is handling + history sample offsets that are 16 bit aligned - the dual MAC needs + 32 bit aligmnent. There are some good examples in libbfdsp. + + 2/ Use the hardware circular buffer facility tohalve memory usage. + + 3/ Consider using internal memory. + + Using less memory might also improve speed as cache misses will be + reduced. A drop in MIPs and memory approaching 50% should be + possible. + + The foreground and background filters currenlty use a total of + about 10 MIPs/ch as measured with speedtest.c on a 256 TAP echo + can. +*/ + +/* + * 16 bit integer FIR descriptor. This defines the working state for a single + * instance of an FIR filter using 16 bit integer coefficients. + */ +struct fir16_state_t { + int taps; + int curr_pos; + const int16_t *coeffs; + int16_t *history; +}; + +/* + * 32 bit integer FIR descriptor. This defines the working state for a single + * instance of an FIR filter using 32 bit integer coefficients, and filtering + * 16 bit integer data. + */ +struct fir32_state_t { + int taps; + int curr_pos; + const int32_t *coeffs; + int16_t *history; +}; + +/* + * Floating point FIR descriptor. This defines the working state for a single + * instance of an FIR filter using floating point coefficients and data. + */ +struct fir_float_state_t { + int taps; + int curr_pos; + const float *coeffs; + float *history; +}; + +static inline const int16_t *fir16_create(struct fir16_state_t *fir, + const int16_t *coeffs, int taps) +{ + fir->taps = taps; + fir->curr_pos = taps - 1; + fir->coeffs = coeffs; +#if defined(__bfin__) + fir->history = kcalloc(2 * taps, sizeof(int16_t), GFP_KERNEL); +#else + fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL); +#endif + return fir->history; +} + +static inline void fir16_flush(struct fir16_state_t *fir) +{ +#if defined(__bfin__) + memset(fir->history, 0, 2 * fir->taps * sizeof(int16_t)); +#else + memset(fir->history, 0, fir->taps * sizeof(int16_t)); +#endif +} + +static inline void fir16_free(struct fir16_state_t *fir) +{ + kfree(fir->history); +} + +#ifdef __bfin__ +static inline int32_t dot_asm(short *x, short *y, int len) +{ + int dot; + + len--; + + __asm__("I0 = %1;\n\t" + "I1 = %2;\n\t" + "A0 = 0;\n\t" + "R0.L = W[I0++] || R1.L = W[I1++];\n\t" + "LOOP dot%= LC0 = %3;\n\t" + "LOOP_BEGIN dot%=;\n\t" + "A0 += R0.L * R1.L (IS) || R0.L = W[I0++] || R1.L = W[I1++];\n\t" + "LOOP_END dot%=;\n\t" + "A0 += R0.L*R1.L (IS);\n\t" + "R0 = A0;\n\t" + "%0 = R0;\n\t" + : "=&d"(dot) + : "a"(x), "a"(y), "a"(len) + : "I0", "I1", "A1", "A0", "R0", "R1" + ); + + return dot; +} +#endif + +static inline int16_t fir16(struct fir16_state_t *fir, int16_t sample) +{ + int32_t y; +#if defined(__bfin__) + fir->history[fir->curr_pos] = sample; + fir->history[fir->curr_pos + fir->taps] = sample; + y = dot_asm((int16_t *) fir->coeffs, &fir->history[fir->curr_pos], + fir->taps); +#else + int i; + int offset1; + int offset2; + + fir->history[fir->curr_pos] = sample; + + offset2 = fir->curr_pos; + offset1 = fir->taps - offset2; + y = 0; + for (i = fir->taps - 1; i >= offset1; i--) + y += fir->coeffs[i] * fir->history[i - offset1]; + for (; i >= 0; i--) + y += fir->coeffs[i] * fir->history[i + offset2]; +#endif + if (fir->curr_pos <= 0) + fir->curr_pos = fir->taps; + fir->curr_pos--; + return (int16_t) (y >> 15); +} + +static inline const int16_t *fir32_create(struct fir32_state_t *fir, + const int32_t *coeffs, int taps) +{ + fir->taps = taps; + fir->curr_pos = taps - 1; + fir->coeffs = coeffs; + fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL); + return fir->history; +} + +static inline void fir32_flush(struct fir32_state_t *fir) +{ + memset(fir->history, 0, fir->taps * sizeof(int16_t)); +} + +static inline void fir32_free(struct fir32_state_t *fir) +{ + kfree(fir->history); +} + +static inline int16_t fir32(struct fir32_state_t *fir, int16_t sample) +{ + int i; + int32_t y; + int offset1; + int offset2; + + fir->history[fir->curr_pos] = sample; + offset2 = fir->curr_pos; + offset1 = fir->taps - offset2; + y = 0; + for (i = fir->taps - 1; i >= offset1; i--) + y += fir->coeffs[i] * fir->history[i - offset1]; + for (; i >= 0; i--) + y += fir->coeffs[i] * fir->history[i + offset2]; + if (fir->curr_pos <= 0) + fir->curr_pos = fir->taps; + fir->curr_pos--; + return (int16_t) (y >> 15); +} + +#endif diff --git a/drivers/staging/echo/oslec.h b/drivers/staging/echo/oslec.h new file mode 100644 index 00000000..f4175360 --- /dev/null +++ b/drivers/staging/echo/oslec.h @@ -0,0 +1,94 @@ +/* + * OSLEC - A line echo canceller. This code is being developed + * against and partially complies with G168. Using code from SpanDSP + * + * Written by Steve Underwood + * and David Rowe + * + * Copyright (C) 2001 Steve Underwood and 2007-2008 David Rowe + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __OSLEC_H +#define __OSLEC_H + +/* Mask bits for the adaption mode */ +#define ECHO_CAN_USE_ADAPTION 0x01 +#define ECHO_CAN_USE_NLP 0x02 +#define ECHO_CAN_USE_CNG 0x04 +#define ECHO_CAN_USE_CLIP 0x08 +#define ECHO_CAN_USE_TX_HPF 0x10 +#define ECHO_CAN_USE_RX_HPF 0x20 +#define ECHO_CAN_DISABLE 0x40 + +/** + * oslec_state: G.168 echo canceller descriptor. + * + * This defines the working state for a line echo canceller. + */ +struct oslec_state; + +/** + * oslec_create - Create a voice echo canceller context. + * @len: The length of the canceller, in samples. + * @return: The new canceller context, or NULL if the canceller could not be + * created. + */ +struct oslec_state *oslec_create(int len, int adaption_mode); + +/** + * oslec_free - Free a voice echo canceller context. + * @ec: The echo canceller context. + */ +void oslec_free(struct oslec_state *ec); + +/** + * oslec_flush - Flush (reinitialise) a voice echo canceller context. + * @ec: The echo canceller context. + */ +void oslec_flush(struct oslec_state *ec); + +/** + * oslec_adaption_mode - set the adaption mode of a voice echo canceller context. + * @ec The echo canceller context. + * @adaption_mode: The mode. + */ +void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode); + +void oslec_snapshot(struct oslec_state *ec); + +/** + * oslec_update: Process a sample through a voice echo canceller. + * @ec: The echo canceller context. + * @tx: The transmitted audio sample. + * @rx: The received audio sample. + * + * The return value is the clean (echo cancelled) received sample. + */ +int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx); + +/** + * oslec_hpf_tx: Process to high pass filter the tx signal. + * @ec: The echo canceller context. + * @tx: The transmitted auio sample. + * + * The return value is the HP filtered transmit sample, send this to your D/A. + */ +int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx); + +#endif /* __OSLEC_H */ diff --git a/drivers/staging/et131x/Kconfig b/drivers/staging/et131x/Kconfig new file mode 100644 index 00000000..8190f2aa --- /dev/null +++ b/drivers/staging/et131x/Kconfig @@ -0,0 +1,10 @@ +config ET131X + tristate "Agere ET-1310 Gigabit Ethernet support" + depends on PCI && NET && NETDEVICES + select PHYLIB + default n + ---help--- + This driver supports Agere ET-1310 ethernet adapters. + + To compile this driver as a module, choose M here. The module + will be called et131x. diff --git a/drivers/staging/et131x/Makefile b/drivers/staging/et131x/Makefile new file mode 100644 index 00000000..027ff945 --- /dev/null +++ b/drivers/staging/et131x/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Agere ET-131x ethernet driver +# + +obj-$(CONFIG_ET131X) += et131x.o diff --git a/drivers/staging/et131x/README b/drivers/staging/et131x/README new file mode 100644 index 00000000..82657233 --- /dev/null +++ b/drivers/staging/et131x/README @@ -0,0 +1,16 @@ +This is a driver for the ET1310 network device. + +Based on the driver found at https://sourceforge.net/projects/et131x/ + +Cleaned up immensely by Olaf Hartman and Christoph Hellwig + +Note, the powermanagement options were removed from the vendor provided +driver as they did not build properly at the time. + +TODO: + - Use of kmem_cache seems a bit unusual + +Please send patches to: + Greg Kroah-Hartman + Mark Einon + diff --git a/drivers/staging/et131x/et131x.c b/drivers/staging/et131x/et131x.c new file mode 100644 index 00000000..886f5650 --- /dev/null +++ b/drivers/staging/et131x/et131x.c @@ -0,0 +1,5469 @@ +/* + * Agere Systems Inc. + * 10/100/1000 Base-T Ethernet Driver for the ET1301 and ET131x series MACs + * + * Copyright © 2005 Agere Systems Inc. + * All rights reserved. + * http://www.agere.com + * + * Copyright (c) 2011 Mark Einon + * + *------------------------------------------------------------------------------ + * + * SOFTWARE LICENSE + * + * This software is provided subject to the following terms and conditions, + * which you should read carefully before using the software. Using this + * software indicates your acceptance of these terms and conditions. If you do + * not agree with these terms and conditions, do not use the software. + * + * Copyright © 2005 Agere Systems Inc. + * All rights reserved. + * + * Redistribution and use in source or binary forms, with or without + * modifications, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following Disclaimer as comments in the code as + * well as in the documentation and/or other materials provided with the + * distribution. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following Disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name of Agere Systems Inc. nor the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Disclaimer + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, INFRINGEMENT AND THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ANY + * USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE IS SOLELY AT THE USERS OWN + * RISK. IN NO EVENT SHALL AGERE SYSTEMS INC. OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, INCLUDING, BUT NOT LIMITED TO, CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + */ + +#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 "et131x.h" + +MODULE_AUTHOR("Victor Soriano "); +MODULE_AUTHOR("Mark Einon "); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("10/100/1000 Base-T Ethernet Driver " + "for the ET1310 by Agere Systems"); + +/* EEPROM defines */ +#define MAX_NUM_REGISTER_POLLS 1000 +#define MAX_NUM_WRITE_RETRIES 2 + +/* MAC defines */ +#define COUNTER_WRAP_16_BIT 0x10000 +#define COUNTER_WRAP_12_BIT 0x1000 + +/* PCI defines */ +#define INTERNAL_MEM_SIZE 0x400 /* 1024 of internal memory */ +#define INTERNAL_MEM_RX_OFFSET 0x1FF /* 50% Tx, 50% Rx */ + +/* ISR defines */ +/* + * For interrupts, normal running is: + * rxdma_xfr_done, phy_interrupt, mac_stat_interrupt, + * watchdog_interrupt & txdma_xfer_done + * + * In both cases, when flow control is enabled for either Tx or bi-direction, + * we additional enable rx_fbr0_low and rx_fbr1_low, so we know when the + * buffer rings are running low. + */ +#define INT_MASK_DISABLE 0xffffffff + +/* NOTE: Masking out MAC_STAT Interrupt for now... + * #define INT_MASK_ENABLE 0xfff6bf17 + * #define INT_MASK_ENABLE_NO_FLOW 0xfff6bfd7 + */ +#define INT_MASK_ENABLE 0xfffebf17 +#define INT_MASK_ENABLE_NO_FLOW 0xfffebfd7 + +/* General defines */ +/* Packet and header sizes */ +#define NIC_MIN_PACKET_SIZE 60 + +/* Multicast list size */ +#define NIC_MAX_MCAST_LIST 128 + +/* Supported Filters */ +#define ET131X_PACKET_TYPE_DIRECTED 0x0001 +#define ET131X_PACKET_TYPE_MULTICAST 0x0002 +#define ET131X_PACKET_TYPE_BROADCAST 0x0004 +#define ET131X_PACKET_TYPE_PROMISCUOUS 0x0008 +#define ET131X_PACKET_TYPE_ALL_MULTICAST 0x0010 + +/* Tx Timeout */ +#define ET131X_TX_TIMEOUT (1 * HZ) +#define NIC_SEND_HANG_THRESHOLD 0 + +/* MP_TCB flags */ +#define fMP_DEST_MULTI 0x00000001 +#define fMP_DEST_BROAD 0x00000002 + +/* MP_ADAPTER flags */ +#define fMP_ADAPTER_RECV_LOOKASIDE 0x00000004 +#define fMP_ADAPTER_INTERRUPT_IN_USE 0x00000008 + +/* MP_SHARED flags */ +#define fMP_ADAPTER_LOWER_POWER 0x00200000 + +#define fMP_ADAPTER_NON_RECOVER_ERROR 0x00800000 +#define fMP_ADAPTER_HARDWARE_ERROR 0x04000000 + +#define fMP_ADAPTER_FAIL_SEND_MASK 0x3ff00000 + +/* Some offsets in PCI config space that are actually used. */ +#define ET1310_PCI_MAC_ADDRESS 0xA4 +#define ET1310_PCI_EEPROM_STATUS 0xB2 +#define ET1310_PCI_ACK_NACK 0xC0 +#define ET1310_PCI_REPLAY 0xC2 +#define ET1310_PCI_L0L1LATENCY 0xCF + +/* PCI Product IDs */ +#define ET131X_PCI_DEVICE_ID_GIG 0xED00 /* ET1310 1000 Base-T 8 */ +#define ET131X_PCI_DEVICE_ID_FAST 0xED01 /* ET1310 100 Base-T */ + +/* Define order of magnitude converter */ +#define NANO_IN_A_MICRO 1000 + +#define PARM_RX_NUM_BUFS_DEF 4 +#define PARM_RX_TIME_INT_DEF 10 +#define PARM_RX_MEM_END_DEF 0x2bc +#define PARM_TX_TIME_INT_DEF 40 +#define PARM_TX_NUM_BUFS_DEF 4 +#define PARM_DMA_CACHE_DEF 0 + +/* RX defines */ +#define USE_FBR0 1 +#define FBR_CHUNKS 32 +#define MAX_DESC_PER_RING_RX 1024 + +/* number of RFDs - default and min */ +#ifdef USE_FBR0 +#define RFD_LOW_WATER_MARK 40 +#define NIC_DEFAULT_NUM_RFD 1024 +#define NUM_FBRS 2 +#else +#define RFD_LOW_WATER_MARK 20 +#define NIC_DEFAULT_NUM_RFD 256 +#define NUM_FBRS 1 +#endif + +#define NIC_MIN_NUM_RFD 64 +#define NUM_PACKETS_HANDLED 256 + +#define ALCATEL_MULTICAST_PKT 0x01000000 +#define ALCATEL_BROADCAST_PKT 0x02000000 + +/* typedefs for Free Buffer Descriptors */ +struct fbr_desc { + u32 addr_lo; + u32 addr_hi; + u32 word2; /* Bits 10-31 reserved, 0-9 descriptor */ +}; + +/* Packet Status Ring Descriptors + * + * Word 0: + * + * top 16 bits are from the Alcatel Status Word as enumerated in + * PE-MCXMAC Data Sheet IPD DS54 0210-1 (also IPD-DS80 0205-2) + * + * 0: hp hash pass + * 1: ipa IP checksum assist + * 2: ipp IP checksum pass + * 3: tcpa TCP checksum assist + * 4: tcpp TCP checksum pass + * 5: wol WOL Event + * 6: rxmac_error RXMAC Error Indicator + * 7: drop Drop packet + * 8: ft Frame Truncated + * 9: jp Jumbo Packet + * 10: vp VLAN Packet + * 11-15: unused + * 16: asw_prev_pkt_dropped e.g. IFG too small on previous + * 17: asw_RX_DV_event short receive event detected + * 18: asw_false_carrier_event bad carrier since last good packet + * 19: asw_code_err one or more nibbles signalled as errors + * 20: asw_CRC_err CRC error + * 21: asw_len_chk_err frame length field incorrect + * 22: asw_too_long frame length > 1518 bytes + * 23: asw_OK valid CRC + no code error + * 24: asw_multicast has a multicast address + * 25: asw_broadcast has a broadcast address + * 26: asw_dribble_nibble spurious bits after EOP + * 27: asw_control_frame is a control frame + * 28: asw_pause_frame is a pause frame + * 29: asw_unsupported_op unsupported OP code + * 30: asw_VLAN_tag VLAN tag detected + * 31: asw_long_evt Rx long event + * + * Word 1: + * 0-15: length length in bytes + * 16-25: bi Buffer Index + * 26-27: ri Ring Index + * 28-31: reserved + */ + +struct pkt_stat_desc { + u32 word0; + u32 word1; +}; + +/* Typedefs for the RX DMA status word */ + +/* + * rx status word 0 holds part of the status bits of the Rx DMA engine + * that get copied out to memory by the ET-1310. Word 0 is a 32 bit word + * which contains the Free Buffer ring 0 and 1 available offset. + * + * bit 0-9 FBR1 offset + * bit 10 Wrap flag for FBR1 + * bit 16-25 FBR0 offset + * bit 26 Wrap flag for FBR0 + */ + +/* + * RXSTAT_WORD1_t structure holds part of the status bits of the Rx DMA engine + * that get copied out to memory by the ET-1310. Word 3 is a 32 bit word + * which contains the Packet Status Ring available offset. + * + * bit 0-15 reserved + * bit 16-27 PSRoffset + * bit 28 PSRwrap + * bit 29-31 unused + */ + +/* + * struct rx_status_block is a structure representing the status of the Rx + * DMA engine it sits in free memory, and is pointed to by 0x101c / 0x1020 + */ +struct rx_status_block { + u32 word0; + u32 word1; +}; + +/* + * Structure for look-up table holding free buffer ring pointers, addresses + * and state. + */ +struct fbr_lookup { + void *virt[MAX_DESC_PER_RING_RX]; + void *buffer1[MAX_DESC_PER_RING_RX]; + void *buffer2[MAX_DESC_PER_RING_RX]; + u32 bus_high[MAX_DESC_PER_RING_RX]; + u32 bus_low[MAX_DESC_PER_RING_RX]; + void *ring_virtaddr; + dma_addr_t ring_physaddr; + void *mem_virtaddrs[MAX_DESC_PER_RING_RX / FBR_CHUNKS]; + dma_addr_t mem_physaddrs[MAX_DESC_PER_RING_RX / FBR_CHUNKS]; + u64 real_physaddr; + u64 offset; + u32 local_full; + u32 num_entries; + u32 buffsize; +}; + +/* + * struct rx_ring is the sructure representing the adaptor's local + * reference(s) to the rings + * + ****************************************************************************** + * IMPORTANT NOTE :- fbr_lookup *fbr[NUM_FBRS] uses index 0 to refer to FBR1 + * and index 1 to refer to FRB0 + ****************************************************************************** + */ +struct rx_ring { + struct fbr_lookup *fbr[NUM_FBRS]; + void *ps_ring_virtaddr; + dma_addr_t ps_ring_physaddr; + u32 local_psr_full; + u32 psr_num_entries; + + struct rx_status_block *rx_status_block; + dma_addr_t rx_status_bus; + + /* RECV */ + struct list_head recv_list; + u32 num_ready_recv; + + u32 num_rfd; + + bool unfinished_receives; + + /* lookaside lists */ + struct kmem_cache *recv_lookaside; +}; + +/* TX defines */ +/* + * word 2 of the control bits in the Tx Descriptor ring for the ET-1310 + * + * 0-15: length of packet + * 16-27: VLAN tag + * 28: VLAN CFI + * 29-31: VLAN priority + * + * word 3 of the control bits in the Tx Descriptor ring for the ET-1310 + * + * 0: last packet in the sequence + * 1: first packet in the sequence + * 2: interrupt the processor when this pkt sent + * 3: Control word - no packet data + * 4: Issue half-duplex backpressure : XON/XOFF + * 5: send pause frame + * 6: Tx frame has error + * 7: append CRC + * 8: MAC override + * 9: pad packet + * 10: Packet is a Huge packet + * 11: append VLAN tag + * 12: IP checksum assist + * 13: TCP checksum assist + * 14: UDP checksum assist + */ + +/* struct tx_desc represents each descriptor on the ring */ +struct tx_desc { + u32 addr_hi; + u32 addr_lo; + u32 len_vlan; /* control words how to xmit the */ + u32 flags; /* data (detailed above) */ +}; + +/* + * The status of the Tx DMA engine it sits in free memory, and is pointed to + * by 0x101c / 0x1020. This is a DMA10 type + */ + +/* TCB (Transmit Control Block: Host Side) */ +struct tcb { + struct tcb *next; /* Next entry in ring */ + u32 flags; /* Our flags for the packet */ + u32 count; /* Used to spot stuck/lost packets */ + u32 stale; /* Used to spot stuck/lost packets */ + struct sk_buff *skb; /* Network skb we are tied to */ + u32 index; /* Ring indexes */ + u32 index_start; +}; + +/* Structure representing our local reference(s) to the ring */ +struct tx_ring { + /* TCB (Transmit Control Block) memory and lists */ + struct tcb *tcb_ring; + + /* List of TCBs that are ready to be used */ + struct tcb *tcb_qhead; + struct tcb *tcb_qtail; + + /* list of TCBs that are currently being sent. NOTE that access to all + * three of these (including used) are controlled via the + * TCBSendQLock. This lock should be secured prior to incementing / + * decrementing used, or any queue manipulation on send_head / + * tail + */ + struct tcb *send_head; + struct tcb *send_tail; + int used; + + /* The actual descriptor ring */ + struct tx_desc *tx_desc_ring; + dma_addr_t tx_desc_ring_pa; + + /* send_idx indicates where we last wrote to in the descriptor ring. */ + u32 send_idx; + + /* The location of the write-back status block */ + u32 *tx_status; + dma_addr_t tx_status_pa; + + /* Packets since the last IRQ: used for interrupt coalescing */ + int since_irq; +}; + +/* + * Do not change these values: if changed, then change also in respective + * TXdma and Rxdma engines + */ +#define NUM_DESC_PER_RING_TX 512 /* TX Do not change these values */ +#define NUM_TCB 64 + +/* + * These values are all superseded by registry entries to facilitate tuning. + * Once the desired performance has been achieved, the optimal registry values + * should be re-populated to these #defines: + */ +#define TX_ERROR_PERIOD 1000 + +#define LO_MARK_PERCENT_FOR_PSR 15 +#define LO_MARK_PERCENT_FOR_RX 15 + +/* RFD (Receive Frame Descriptor) */ +struct rfd { + struct list_head list_node; + struct sk_buff *skb; + u32 len; /* total size of receive frame */ + u16 bufferindex; + u8 ringindex; +}; + +/* Flow Control */ +#define FLOW_BOTH 0 +#define FLOW_TXONLY 1 +#define FLOW_RXONLY 2 +#define FLOW_NONE 3 + +/* Struct to define some device statistics */ +struct ce_stats { + /* MIB II variables + * + * NOTE: atomic_t types are only guaranteed to store 24-bits; if we + * MUST have 32, then we'll need another way to perform atomic + * operations + */ + u32 unicast_pkts_rcvd; + atomic_t unicast_pkts_xmtd; + u32 multicast_pkts_rcvd; + atomic_t multicast_pkts_xmtd; + u32 broadcast_pkts_rcvd; + atomic_t broadcast_pkts_xmtd; + u32 rcvd_pkts_dropped; + + /* Tx Statistics. */ + u32 tx_underflows; + + u32 tx_collisions; + u32 tx_excessive_collisions; + u32 tx_first_collisions; + u32 tx_late_collisions; + u32 tx_max_pkt_errs; + u32 tx_deferred; + + /* Rx Statistics. */ + u32 rx_overflows; + + u32 rx_length_errs; + u32 rx_align_errs; + u32 rx_crc_errs; + u32 rx_code_violations; + u32 rx_other_errs; + + u32 synchronous_iterations; + u32 interrupt_status; +}; + +/* The private adapter structure */ +struct et131x_adapter { + struct net_device *netdev; + struct pci_dev *pdev; + struct mii_bus *mii_bus; + struct phy_device *phydev; + struct work_struct task; + + /* Flags that indicate current state of the adapter */ + u32 flags; + + /* local link state, to determine if a state change has occurred */ + int link; + + /* Configuration */ + u8 rom_addr[ETH_ALEN]; + u8 addr[ETH_ALEN]; + bool has_eeprom; + u8 eeprom_data[2]; + + /* Spinlocks */ + spinlock_t lock; + + spinlock_t tcb_send_qlock; + spinlock_t tcb_ready_qlock; + spinlock_t send_hw_lock; + + spinlock_t rcv_lock; + spinlock_t rcv_pend_lock; + spinlock_t fbr_lock; + + spinlock_t phy_lock; + + /* Packet Filter and look ahead size */ + u32 packet_filter; + + /* multicast list */ + u32 multicast_addr_count; + u8 multicast_list[NIC_MAX_MCAST_LIST][ETH_ALEN]; + + /* Pointer to the device's PCI register space */ + struct address_map __iomem *regs; + + /* Registry parameters */ + u8 wanted_flow; /* Flow we want for 802.3x flow control */ + u32 registry_jumbo_packet; /* Max supported ethernet packet size */ + + /* Derived from the registry: */ + u8 flowcontrol; /* flow control validated by the far-end */ + + /* Minimize init-time */ + struct timer_list error_timer; + + /* variable putting the phy into coma mode when boot up with no cable + * plugged in after 5 seconds + */ + u8 boot_coma; + + /* Next two used to save power information at power down. This + * information will be used during power up to set up parts of Power + * Management in JAGCore + */ + u16 pdown_speed; + u8 pdown_duplex; + + /* Tx Memory Variables */ + struct tx_ring tx_ring; + + /* Rx Memory Variables */ + struct rx_ring rx_ring; + + /* Stats */ + struct ce_stats stats; + + struct net_device_stats net_stats; +}; + +static int eeprom_wait_ready(struct pci_dev *pdev, u32 *status) +{ + u32 reg; + int i; + + /* + * 1. Check LBCIF Status Register for bits 6 & 3:2 all equal to 0 and + * bits 7,1:0 both equal to 1, at least once after reset. + * Subsequent operations need only to check that bits 1:0 are equal + * to 1 prior to starting a single byte read/write + */ + + for (i = 0; i < MAX_NUM_REGISTER_POLLS; i++) { + /* Read registers grouped in DWORD1 */ + if (pci_read_config_dword(pdev, LBCIF_DWORD1_GROUP, ®)) + return -EIO; + + /* I2C idle and Phy Queue Avail both true */ + if ((reg & 0x3000) == 0x3000) { + if (status) + *status = reg; + return reg & 0xFF; + } + } + return -ETIMEDOUT; +} + + +/** + * eeprom_write - Write a byte to the ET1310's EEPROM + * @adapter: pointer to our private adapter structure + * @addr: the address to write + * @data: the value to write + * + * Returns 1 for a successful write. + */ +static int eeprom_write(struct et131x_adapter *adapter, u32 addr, u8 data) +{ + struct pci_dev *pdev = adapter->pdev; + int index = 0; + int retries; + int err = 0; + int i2c_wack = 0; + int writeok = 0; + u32 status; + u32 val = 0; + + /* + * For an EEPROM, an I2C single byte write is defined as a START + * condition followed by the device address, EEPROM address, one byte + * of data and a STOP condition. The STOP condition will trigger the + * EEPROM's internally timed write cycle to the nonvolatile memory. + * All inputs are disabled during this write cycle and the EEPROM will + * not respond to any access until the internal write is complete. + */ + + err = eeprom_wait_ready(pdev, NULL); + if (err) + return err; + + /* + * 2. Write to the LBCIF Control Register: bit 7=1, bit 6=1, bit 3=0, + * and bits 1:0 both =0. Bit 5 should be set according to the + * type of EEPROM being accessed (1=two byte addressing, 0=one + * byte addressing). + */ + if (pci_write_config_byte(pdev, LBCIF_CONTROL_REGISTER, + LBCIF_CONTROL_LBCIF_ENABLE | LBCIF_CONTROL_I2C_WRITE)) + return -EIO; + + i2c_wack = 1; + + /* Prepare EEPROM address for Step 3 */ + + for (retries = 0; retries < MAX_NUM_WRITE_RETRIES; retries++) { + /* Write the address to the LBCIF Address Register */ + if (pci_write_config_dword(pdev, LBCIF_ADDRESS_REGISTER, addr)) + break; + /* + * Write the data to the LBCIF Data Register (the I2C write + * will begin). + */ + if (pci_write_config_byte(pdev, LBCIF_DATA_REGISTER, data)) + break; + /* + * Monitor bit 1:0 of the LBCIF Status Register. When bits + * 1:0 are both equal to 1, the I2C write has completed and the + * internal write cycle of the EEPROM is about to start. + * (bits 1:0 = 01 is a legal state while waiting from both + * equal to 1, but bits 1:0 = 10 is invalid and implies that + * something is broken). + */ + err = eeprom_wait_ready(pdev, &status); + if (err < 0) + return 0; + + /* + * Check bit 3 of the LBCIF Status Register. If equal to 1, + * an error has occurred.Don't break here if we are revision + * 1, this is so we do a blind write for load bug. + */ + if ((status & LBCIF_STATUS_GENERAL_ERROR) + && adapter->pdev->revision == 0) + break; + + /* + * Check bit 2 of the LBCIF Status Register. If equal to 1 an + * ACK error has occurred on the address phase of the write. + * This could be due to an actual hardware failure or the + * EEPROM may still be in its internal write cycle from a + * previous write. This write operation was ignored and must be + *repeated later. + */ + if (status & LBCIF_STATUS_ACK_ERROR) { + /* + * This could be due to an actual hardware failure + * or the EEPROM may still be in its internal write + * cycle from a previous write. This write operation + * was ignored and must be repeated later. + */ + udelay(10); + continue; + } + + writeok = 1; + break; + } + + /* + * Set bit 6 of the LBCIF Control Register = 0. + */ + udelay(10); + + while (i2c_wack) { + if (pci_write_config_byte(pdev, LBCIF_CONTROL_REGISTER, + LBCIF_CONTROL_LBCIF_ENABLE)) + writeok = 0; + + /* Do read until internal ACK_ERROR goes away meaning write + * completed + */ + do { + pci_write_config_dword(pdev, + LBCIF_ADDRESS_REGISTER, + addr); + do { + pci_read_config_dword(pdev, + LBCIF_DATA_REGISTER, &val); + } while ((val & 0x00010000) == 0); + } while (val & 0x00040000); + + if ((val & 0xFF00) != 0xC000 || index == 10000) + break; + index++; + } + return writeok ? 0 : -EIO; +} + +/** + * eeprom_read - Read a byte from the ET1310's EEPROM + * @adapter: pointer to our private adapter structure + * @addr: the address from which to read + * @pdata: a pointer to a byte in which to store the value of the read + * @eeprom_id: the ID of the EEPROM + * @addrmode: how the EEPROM is to be accessed + * + * Returns 1 for a successful read + */ +static int eeprom_read(struct et131x_adapter *adapter, u32 addr, u8 *pdata) +{ + struct pci_dev *pdev = adapter->pdev; + int err; + u32 status; + + /* + * A single byte read is similar to the single byte write, with the + * exception of the data flow: + */ + + err = eeprom_wait_ready(pdev, NULL); + if (err) + return err; + /* + * Write to the LBCIF Control Register: bit 7=1, bit 6=0, bit 3=0, + * and bits 1:0 both =0. Bit 5 should be set according to the type + * of EEPROM being accessed (1=two byte addressing, 0=one byte + * addressing). + */ + if (pci_write_config_byte(pdev, LBCIF_CONTROL_REGISTER, + LBCIF_CONTROL_LBCIF_ENABLE)) + return -EIO; + /* + * Write the address to the LBCIF Address Register (I2C read will + * begin). + */ + if (pci_write_config_dword(pdev, LBCIF_ADDRESS_REGISTER, addr)) + return -EIO; + /* + * Monitor bit 0 of the LBCIF Status Register. When = 1, I2C read + * is complete. (if bit 1 =1 and bit 0 stays = 0, a hardware failure + * has occurred). + */ + err = eeprom_wait_ready(pdev, &status); + if (err < 0) + return err; + /* + * Regardless of error status, read data byte from LBCIF Data + * Register. + */ + *pdata = err; + /* + * Check bit 2 of the LBCIF Status Register. If = 1, + * then an error has occurred. + */ + return (status & LBCIF_STATUS_ACK_ERROR) ? -EIO : 0; +} + +static int et131x_init_eeprom(struct et131x_adapter *adapter) +{ + struct pci_dev *pdev = adapter->pdev; + u8 eestatus; + + /* We first need to check the EEPROM Status code located at offset + * 0xB2 of config space + */ + pci_read_config_byte(pdev, ET1310_PCI_EEPROM_STATUS, + &eestatus); + + /* THIS IS A WORKAROUND: + * I need to call this function twice to get my card in a + * LG M1 Express Dual running. I tried also a msleep before this + * function, because I thought there could be some time condidions + * but it didn't work. Call the whole function twice also work. + */ + if (pci_read_config_byte(pdev, ET1310_PCI_EEPROM_STATUS, &eestatus)) { + dev_err(&pdev->dev, + "Could not read PCI config space for EEPROM Status\n"); + return -EIO; + } + + /* Determine if the error(s) we care about are present. If they are + * present we need to fail. + */ + if (eestatus & 0x4C) { + int write_failed = 0; + if (pdev->revision == 0x01) { + int i; + static const u8 eedata[4] = { 0xFE, 0x13, 0x10, 0xFF }; + + /* Re-write the first 4 bytes if we have an eeprom + * present and the revision id is 1, this fixes the + * corruption seen with 1310 B Silicon + */ + for (i = 0; i < 3; i++) + if (eeprom_write(adapter, i, eedata[i]) < 0) + write_failed = 1; + } + if (pdev->revision != 0x01 || write_failed) { + dev_err(&pdev->dev, + "Fatal EEPROM Status Error - 0x%04x\n", eestatus); + + /* This error could mean that there was an error + * reading the eeprom or that the eeprom doesn't exist. + * We will treat each case the same and not try to + * gather additional information that normally would + * come from the eeprom, like MAC Address + */ + adapter->has_eeprom = 0; + return -EIO; + } + } + adapter->has_eeprom = 1; + + /* Read the EEPROM for information regarding LED behavior. Refer to + * ET1310_phy.c, et131x_xcvr_init(), for its use. + */ + eeprom_read(adapter, 0x70, &adapter->eeprom_data[0]); + eeprom_read(adapter, 0x71, &adapter->eeprom_data[1]); + + if (adapter->eeprom_data[0] != 0xcd) + /* Disable all optional features */ + adapter->eeprom_data[1] = 0x00; + + return 0; +} + +/** + * et131x_rx_dma_enable - re-start of Rx_DMA on the ET1310. + * @adapter: pointer to our adapter structure + */ +static void et131x_rx_dma_enable(struct et131x_adapter *adapter) +{ + /* Setup the receive dma configuration register for normal operation */ + u32 csr = 0x2000; /* FBR1 enable */ + + if (adapter->rx_ring.fbr[0]->buffsize == 4096) + csr |= 0x0800; + else if (adapter->rx_ring.fbr[0]->buffsize == 8192) + csr |= 0x1000; + else if (adapter->rx_ring.fbr[0]->buffsize == 16384) + csr |= 0x1800; +#ifdef USE_FBR0 + csr |= 0x0400; /* FBR0 enable */ + if (adapter->rx_ring.fbr[1]->buffsize == 256) + csr |= 0x0100; + else if (adapter->rx_ring.fbr[1]->buffsize == 512) + csr |= 0x0200; + else if (adapter->rx_ring.fbr[1]->buffsize == 1024) + csr |= 0x0300; +#endif + writel(csr, &adapter->regs->rxdma.csr); + + csr = readl(&adapter->regs->rxdma.csr); + if ((csr & 0x00020000) != 0) { + udelay(5); + csr = readl(&adapter->regs->rxdma.csr); + if ((csr & 0x00020000) != 0) { + dev_err(&adapter->pdev->dev, + "RX Dma failed to exit halt state. CSR 0x%08x\n", + csr); + } + } +} + +/** + * et131x_rx_dma_disable - Stop of Rx_DMA on the ET1310 + * @adapter: pointer to our adapter structure + */ +static void et131x_rx_dma_disable(struct et131x_adapter *adapter) +{ + u32 csr; + /* Setup the receive dma configuration register */ + writel(0x00002001, &adapter->regs->rxdma.csr); + csr = readl(&adapter->regs->rxdma.csr); + if ((csr & 0x00020000) == 0) { /* Check halt status (bit 17) */ + udelay(5); + csr = readl(&adapter->regs->rxdma.csr); + if ((csr & 0x00020000) == 0) + dev_err(&adapter->pdev->dev, + "RX Dma failed to enter halt state. CSR 0x%08x\n", + csr); + } +} + +/** + * et131x_tx_dma_enable - re-start of Tx_DMA on the ET1310. + * @adapter: pointer to our adapter structure + * + * Mainly used after a return to the D0 (full-power) state from a lower state. + */ +static void et131x_tx_dma_enable(struct et131x_adapter *adapter) +{ + /* Setup the transmit dma configuration register for normal + * operation + */ + writel(ET_TXDMA_SNGL_EPKT|(PARM_DMA_CACHE_DEF << ET_TXDMA_CACHE_SHIFT), + &adapter->regs->txdma.csr); +} + +static inline void add_10bit(u32 *v, int n) +{ + *v = INDEX10(*v + n) | (*v & ET_DMA10_WRAP); +} + +static inline void add_12bit(u32 *v, int n) +{ + *v = INDEX12(*v + n) | (*v & ET_DMA12_WRAP); +} + +/** + * et1310_config_mac_regs1 - Initialize the first part of MAC regs + * @adapter: pointer to our adapter structure + */ +static void et1310_config_mac_regs1(struct et131x_adapter *adapter) +{ + struct mac_regs __iomem *macregs = &adapter->regs->mac; + u32 station1; + u32 station2; + u32 ipg; + + /* First we need to reset everything. Write to MAC configuration + * register 1 to perform reset. + */ + writel(0xC00F0000, ¯egs->cfg1); + + /* Next lets configure the MAC Inter-packet gap register */ + ipg = 0x38005860; /* IPG1 0x38 IPG2 0x58 B2B 0x60 */ + ipg |= 0x50 << 8; /* ifg enforce 0x50 */ + writel(ipg, ¯egs->ipg); + + /* Next lets configure the MAC Half Duplex register */ + /* BEB trunc 0xA, Ex Defer, Rexmit 0xF Coll 0x37 */ + writel(0x00A1F037, ¯egs->hfdp); + + /* Next lets configure the MAC Interface Control register */ + writel(0, ¯egs->if_ctrl); + + /* Let's move on to setting up the mii management configuration */ + writel(0x07, ¯egs->mii_mgmt_cfg); /* Clock reset 0x7 */ + + /* Next lets configure the MAC Station Address register. These + * values are read from the EEPROM during initialization and stored + * in the adapter structure. We write what is stored in the adapter + * structure to the MAC Station Address registers high and low. This + * station address is used for generating and checking pause control + * packets. + */ + station2 = (adapter->addr[1] << ET_MAC_STATION_ADDR2_OC2_SHIFT) | + (adapter->addr[0] << ET_MAC_STATION_ADDR2_OC1_SHIFT); + station1 = (adapter->addr[5] << ET_MAC_STATION_ADDR1_OC6_SHIFT) | + (adapter->addr[4] << ET_MAC_STATION_ADDR1_OC5_SHIFT) | + (adapter->addr[3] << ET_MAC_STATION_ADDR1_OC4_SHIFT) | + adapter->addr[2]; + writel(station1, ¯egs->station_addr_1); + writel(station2, ¯egs->station_addr_2); + + /* Max ethernet packet in bytes that will be passed by the mac without + * being truncated. Allow the MAC to pass 4 more than our max packet + * size. This is 4 for the Ethernet CRC. + * + * Packets larger than (registry_jumbo_packet) that do not contain a + * VLAN ID will be dropped by the Rx function. + */ + writel(adapter->registry_jumbo_packet + 4, ¯egs->max_fm_len); + + /* clear out MAC config reset */ + writel(0, ¯egs->cfg1); +} + +/** + * et1310_config_mac_regs2 - Initialize the second part of MAC regs + * @adapter: pointer to our adapter structure + */ +static void et1310_config_mac_regs2(struct et131x_adapter *adapter) +{ + int32_t delay = 0; + struct mac_regs __iomem *mac = &adapter->regs->mac; + struct phy_device *phydev = adapter->phydev; + u32 cfg1; + u32 cfg2; + u32 ifctrl; + u32 ctl; + + ctl = readl(&adapter->regs->txmac.ctl); + cfg1 = readl(&mac->cfg1); + cfg2 = readl(&mac->cfg2); + ifctrl = readl(&mac->if_ctrl); + + /* Set up the if mode bits */ + cfg2 &= ~0x300; + if (phydev && phydev->speed == SPEED_1000) { + cfg2 |= 0x200; + /* Phy mode bit */ + ifctrl &= ~(1 << 24); + } else { + cfg2 |= 0x100; + ifctrl |= (1 << 24); + } + + /* We need to enable Rx/Tx */ + cfg1 |= CFG1_RX_ENABLE | CFG1_TX_ENABLE | CFG1_TX_FLOW; + /* Initialize loop back to off */ + cfg1 &= ~(CFG1_LOOPBACK | CFG1_RX_FLOW); + if (adapter->flowcontrol == FLOW_RXONLY || + adapter->flowcontrol == FLOW_BOTH) + cfg1 |= CFG1_RX_FLOW; + writel(cfg1, &mac->cfg1); + + /* Now we need to initialize the MAC Configuration 2 register */ + /* preamble 7, check length, huge frame off, pad crc, crc enable + full duplex off */ + cfg2 |= 0x7016; + cfg2 &= ~0x0021; + + /* Turn on duplex if needed */ + if (phydev && phydev->duplex == DUPLEX_FULL) + cfg2 |= 0x01; + + ifctrl &= ~(1 << 26); + if (phydev && phydev->duplex == DUPLEX_HALF) + ifctrl |= (1<<26); /* Enable ghd */ + + writel(ifctrl, &mac->if_ctrl); + writel(cfg2, &mac->cfg2); + + do { + udelay(10); + delay++; + cfg1 = readl(&mac->cfg1); + } while ((cfg1 & CFG1_WAIT) != CFG1_WAIT && delay < 100); + + if (delay == 100) { + dev_warn(&adapter->pdev->dev, + "Syncd bits did not respond correctly cfg1 word 0x%08x\n", + cfg1); + } + + /* Enable txmac */ + ctl |= 0x09; /* TX mac enable, FC disable */ + writel(ctl, &adapter->regs->txmac.ctl); + + /* Ready to start the RXDMA/TXDMA engine */ + if (adapter->flags & fMP_ADAPTER_LOWER_POWER) { + et131x_rx_dma_enable(adapter); + et131x_tx_dma_enable(adapter); + } +} + +/** + * et1310_in_phy_coma - check if the device is in phy coma + * @adapter: pointer to our adapter structure + * + * Returns 0 if the device is not in phy coma, 1 if it is in phy coma + */ +static int et1310_in_phy_coma(struct et131x_adapter *adapter) +{ + u32 pmcsr; + + pmcsr = readl(&adapter->regs->global.pm_csr); + + return ET_PM_PHY_SW_COMA & pmcsr ? 1 : 0; +} + +static void et1310_setup_device_for_multicast(struct et131x_adapter *adapter) +{ + struct rxmac_regs __iomem *rxmac = &adapter->regs->rxmac; + u32 hash1 = 0; + u32 hash2 = 0; + u32 hash3 = 0; + u32 hash4 = 0; + u32 pm_csr; + + /* If ET131X_PACKET_TYPE_MULTICAST is specified, then we provision + * the multi-cast LIST. If it is NOT specified, (and "ALL" is not + * specified) then we should pass NO multi-cast addresses to the + * driver. + */ + if (adapter->packet_filter & ET131X_PACKET_TYPE_MULTICAST) { + int i; + + /* Loop through our multicast array and set up the device */ + for (i = 0; i < adapter->multicast_addr_count; i++) { + u32 result; + + result = ether_crc(6, adapter->multicast_list[i]); + + result = (result & 0x3F800000) >> 23; + + if (result < 32) { + hash1 |= (1 << result); + } else if ((31 < result) && (result < 64)) { + result -= 32; + hash2 |= (1 << result); + } else if ((63 < result) && (result < 96)) { + result -= 64; + hash3 |= (1 << result); + } else { + result -= 96; + hash4 |= (1 << result); + } + } + } + + /* Write out the new hash to the device */ + pm_csr = readl(&adapter->regs->global.pm_csr); + if (!et1310_in_phy_coma(adapter)) { + writel(hash1, &rxmac->multi_hash1); + writel(hash2, &rxmac->multi_hash2); + writel(hash3, &rxmac->multi_hash3); + writel(hash4, &rxmac->multi_hash4); + } +} + +static void et1310_setup_device_for_unicast(struct et131x_adapter *adapter) +{ + struct rxmac_regs __iomem *rxmac = &adapter->regs->rxmac; + u32 uni_pf1; + u32 uni_pf2; + u32 uni_pf3; + u32 pm_csr; + + /* Set up unicast packet filter reg 3 to be the first two octets of + * the MAC address for both address + * + * Set up unicast packet filter reg 2 to be the octets 2 - 5 of the + * MAC address for second address + * + * Set up unicast packet filter reg 3 to be the octets 2 - 5 of the + * MAC address for first address + */ + uni_pf3 = (adapter->addr[0] << ET_UNI_PF_ADDR2_1_SHIFT) | + (adapter->addr[1] << ET_UNI_PF_ADDR2_2_SHIFT) | + (adapter->addr[0] << ET_UNI_PF_ADDR1_1_SHIFT) | + adapter->addr[1]; + + uni_pf2 = (adapter->addr[2] << ET_UNI_PF_ADDR2_3_SHIFT) | + (adapter->addr[3] << ET_UNI_PF_ADDR2_4_SHIFT) | + (adapter->addr[4] << ET_UNI_PF_ADDR2_5_SHIFT) | + adapter->addr[5]; + + uni_pf1 = (adapter->addr[2] << ET_UNI_PF_ADDR1_3_SHIFT) | + (adapter->addr[3] << ET_UNI_PF_ADDR1_4_SHIFT) | + (adapter->addr[4] << ET_UNI_PF_ADDR1_5_SHIFT) | + adapter->addr[5]; + + pm_csr = readl(&adapter->regs->global.pm_csr); + if (!et1310_in_phy_coma(adapter)) { + writel(uni_pf1, &rxmac->uni_pf_addr1); + writel(uni_pf2, &rxmac->uni_pf_addr2); + writel(uni_pf3, &rxmac->uni_pf_addr3); + } +} + +static void et1310_config_rxmac_regs(struct et131x_adapter *adapter) +{ + struct rxmac_regs __iomem *rxmac = &adapter->regs->rxmac; + struct phy_device *phydev = adapter->phydev; + u32 sa_lo; + u32 sa_hi = 0; + u32 pf_ctrl = 0; + + /* Disable the MAC while it is being configured (also disable WOL) */ + writel(0x8, &rxmac->ctrl); + + /* Initialize WOL to disabled. */ + writel(0, &rxmac->crc0); + writel(0, &rxmac->crc12); + writel(0, &rxmac->crc34); + + /* We need to set the WOL mask0 - mask4 next. We initialize it to + * its default Values of 0x00000000 because there are not WOL masks + * as of this time. + */ + writel(0, &rxmac->mask0_word0); + writel(0, &rxmac->mask0_word1); + writel(0, &rxmac->mask0_word2); + writel(0, &rxmac->mask0_word3); + + writel(0, &rxmac->mask1_word0); + writel(0, &rxmac->mask1_word1); + writel(0, &rxmac->mask1_word2); + writel(0, &rxmac->mask1_word3); + + writel(0, &rxmac->mask2_word0); + writel(0, &rxmac->mask2_word1); + writel(0, &rxmac->mask2_word2); + writel(0, &rxmac->mask2_word3); + + writel(0, &rxmac->mask3_word0); + writel(0, &rxmac->mask3_word1); + writel(0, &rxmac->mask3_word2); + writel(0, &rxmac->mask3_word3); + + writel(0, &rxmac->mask4_word0); + writel(0, &rxmac->mask4_word1); + writel(0, &rxmac->mask4_word2); + writel(0, &rxmac->mask4_word3); + + /* Lets setup the WOL Source Address */ + sa_lo = (adapter->addr[2] << ET_WOL_LO_SA3_SHIFT) | + (adapter->addr[3] << ET_WOL_LO_SA4_SHIFT) | + (adapter->addr[4] << ET_WOL_LO_SA5_SHIFT) | + adapter->addr[5]; + writel(sa_lo, &rxmac->sa_lo); + + sa_hi = (u32) (adapter->addr[0] << ET_WOL_HI_SA1_SHIFT) | + adapter->addr[1]; + writel(sa_hi, &rxmac->sa_hi); + + /* Disable all Packet Filtering */ + writel(0, &rxmac->pf_ctrl); + + /* Let's initialize the Unicast Packet filtering address */ + if (adapter->packet_filter & ET131X_PACKET_TYPE_DIRECTED) { + et1310_setup_device_for_unicast(adapter); + pf_ctrl |= 4; /* Unicast filter */ + } else { + writel(0, &rxmac->uni_pf_addr1); + writel(0, &rxmac->uni_pf_addr2); + writel(0, &rxmac->uni_pf_addr3); + } + + /* Let's initialize the Multicast hash */ + if (!(adapter->packet_filter & ET131X_PACKET_TYPE_ALL_MULTICAST)) { + pf_ctrl |= 2; /* Multicast filter */ + et1310_setup_device_for_multicast(adapter); + } + + /* Runt packet filtering. Didn't work in version A silicon. */ + pf_ctrl |= (NIC_MIN_PACKET_SIZE + 4) << 16; + pf_ctrl |= 8; /* Fragment filter */ + + if (adapter->registry_jumbo_packet > 8192) + /* In order to transmit jumbo packets greater than 8k, the + * FIFO between RxMAC and RxDMA needs to be reduced in size + * to (16k - Jumbo packet size). In order to implement this, + * we must use "cut through" mode in the RxMAC, which chops + * packets down into segments which are (max_size * 16). In + * this case we selected 256 bytes, since this is the size of + * the PCI-Express TLP's that the 1310 uses. + * + * seg_en on, fc_en off, size 0x10 + */ + writel(0x41, &rxmac->mcif_ctrl_max_seg); + else + writel(0, &rxmac->mcif_ctrl_max_seg); + + /* Initialize the MCIF water marks */ + writel(0, &rxmac->mcif_water_mark); + + /* Initialize the MIF control */ + writel(0, &rxmac->mif_ctrl); + + /* Initialize the Space Available Register */ + writel(0, &rxmac->space_avail); + + /* Initialize the the mif_ctrl register + * bit 3: Receive code error. One or more nibbles were signaled as + * errors during the reception of the packet. Clear this + * bit in Gigabit, set it in 100Mbit. This was derived + * experimentally at UNH. + * bit 4: Receive CRC error. The packet's CRC did not match the + * internally generated CRC. + * bit 5: Receive length check error. Indicates that frame length + * field value in the packet does not match the actual data + * byte length and is not a type field. + * bit 16: Receive frame truncated. + * bit 17: Drop packet enable + */ + if (phydev && phydev->speed == SPEED_100) + writel(0x30038, &rxmac->mif_ctrl); + else + writel(0x30030, &rxmac->mif_ctrl); + + /* Finally we initialize RxMac to be enabled & WOL disabled. Packet + * filter is always enabled since it is where the runt packets are + * supposed to be dropped. For version A silicon, runt packet + * dropping doesn't work, so it is disabled in the pf_ctrl register, + * but we still leave the packet filter on. + */ + writel(pf_ctrl, &rxmac->pf_ctrl); + writel(0x9, &rxmac->ctrl); +} + +static void et1310_config_txmac_regs(struct et131x_adapter *adapter) +{ + struct txmac_regs __iomem *txmac = &adapter->regs->txmac; + + /* We need to update the Control Frame Parameters + * cfpt - control frame pause timer set to 64 (0x40) + * cfep - control frame extended pause timer set to 0x0 + */ + if (adapter->flowcontrol == FLOW_NONE) + writel(0, &txmac->cf_param); + else + writel(0x40, &txmac->cf_param); +} + +static void et1310_config_macstat_regs(struct et131x_adapter *adapter) +{ + struct macstat_regs __iomem *macstat = + &adapter->regs->macstat; + + /* Next we need to initialize all the macstat registers to zero on + * the device. + */ + writel(0, &macstat->txrx_0_64_byte_frames); + writel(0, &macstat->txrx_65_127_byte_frames); + writel(0, &macstat->txrx_128_255_byte_frames); + writel(0, &macstat->txrx_256_511_byte_frames); + writel(0, &macstat->txrx_512_1023_byte_frames); + writel(0, &macstat->txrx_1024_1518_byte_frames); + writel(0, &macstat->txrx_1519_1522_gvln_frames); + + writel(0, &macstat->rx_bytes); + writel(0, &macstat->rx_packets); + writel(0, &macstat->rx_fcs_errs); + writel(0, &macstat->rx_multicast_packets); + writel(0, &macstat->rx_broadcast_packets); + writel(0, &macstat->rx_control_frames); + writel(0, &macstat->rx_pause_frames); + writel(0, &macstat->rx_unknown_opcodes); + writel(0, &macstat->rx_align_errs); + writel(0, &macstat->rx_frame_len_errs); + writel(0, &macstat->rx_code_errs); + writel(0, &macstat->rx_carrier_sense_errs); + writel(0, &macstat->rx_undersize_packets); + writel(0, &macstat->rx_oversize_packets); + writel(0, &macstat->rx_fragment_packets); + writel(0, &macstat->rx_jabbers); + writel(0, &macstat->rx_drops); + + writel(0, &macstat->tx_bytes); + writel(0, &macstat->tx_packets); + writel(0, &macstat->tx_multicast_packets); + writel(0, &macstat->tx_broadcast_packets); + writel(0, &macstat->tx_pause_frames); + writel(0, &macstat->tx_deferred); + writel(0, &macstat->tx_excessive_deferred); + writel(0, &macstat->tx_single_collisions); + writel(0, &macstat->tx_multiple_collisions); + writel(0, &macstat->tx_late_collisions); + writel(0, &macstat->tx_excessive_collisions); + writel(0, &macstat->tx_total_collisions); + writel(0, &macstat->tx_pause_honored_frames); + writel(0, &macstat->tx_drops); + writel(0, &macstat->tx_jabbers); + writel(0, &macstat->tx_fcs_errs); + writel(0, &macstat->tx_control_frames); + writel(0, &macstat->tx_oversize_frames); + writel(0, &macstat->tx_undersize_frames); + writel(0, &macstat->tx_fragments); + writel(0, &macstat->carry_reg1); + writel(0, &macstat->carry_reg2); + + /* Unmask any counters that we want to track the overflow of. + * Initially this will be all counters. It may become clear later + * that we do not need to track all counters. + */ + writel(0xFFFFBE32, &macstat->carry_reg1_mask); + writel(0xFFFE7E8B, &macstat->carry_reg2_mask); +} + +/** + * et131x_phy_mii_read - Read from the PHY through the MII Interface on the MAC + * @adapter: pointer to our private adapter structure + * @addr: the address of the transceiver + * @reg: the register to read + * @value: pointer to a 16-bit value in which the value will be stored + * + * Returns 0 on success, errno on failure (as defined in errno.h) + */ +static int et131x_phy_mii_read(struct et131x_adapter *adapter, u8 addr, + u8 reg, u16 *value) +{ + struct mac_regs __iomem *mac = &adapter->regs->mac; + int status = 0; + u32 delay = 0; + u32 mii_addr; + u32 mii_cmd; + u32 mii_indicator; + + /* Save a local copy of the registers we are dealing with so we can + * set them back + */ + mii_addr = readl(&mac->mii_mgmt_addr); + mii_cmd = readl(&mac->mii_mgmt_cmd); + + /* Stop the current operation */ + writel(0, &mac->mii_mgmt_cmd); + + /* Set up the register we need to read from on the correct PHY */ + writel(MII_ADDR(addr, reg), &mac->mii_mgmt_addr); + + writel(0x1, &mac->mii_mgmt_cmd); + + do { + udelay(50); + delay++; + mii_indicator = readl(&mac->mii_mgmt_indicator); + } while ((mii_indicator & MGMT_WAIT) && delay < 50); + + /* If we hit the max delay, we could not read the register */ + if (delay == 50) { + dev_warn(&adapter->pdev->dev, + "reg 0x%08x could not be read\n", reg); + dev_warn(&adapter->pdev->dev, "status is 0x%08x\n", + mii_indicator); + + status = -EIO; + } + + /* If we hit here we were able to read the register and we need to + * return the value to the caller */ + *value = readl(&mac->mii_mgmt_stat) & 0xFFFF; + + /* Stop the read operation */ + writel(0, &mac->mii_mgmt_cmd); + + /* set the registers we touched back to the state at which we entered + * this function + */ + writel(mii_addr, &mac->mii_mgmt_addr); + writel(mii_cmd, &mac->mii_mgmt_cmd); + + return status; +} + +static int et131x_mii_read(struct et131x_adapter *adapter, u8 reg, u16 *value) +{ + struct phy_device *phydev = adapter->phydev; + + if (!phydev) + return -EIO; + + return et131x_phy_mii_read(adapter, phydev->addr, reg, value); +} + +/** + * et131x_mii_write - Write to a PHY register through the MII interface of the MAC + * @adapter: pointer to our private adapter structure + * @reg: the register to read + * @value: 16-bit value to write + * + * FIXME: one caller in netdev still + * + * Return 0 on success, errno on failure (as defined in errno.h) + */ +static int et131x_mii_write(struct et131x_adapter *adapter, u8 reg, u16 value) +{ + struct mac_regs __iomem *mac = &adapter->regs->mac; + struct phy_device *phydev = adapter->phydev; + int status = 0; + u8 addr; + u32 delay = 0; + u32 mii_addr; + u32 mii_cmd; + u32 mii_indicator; + + if (!phydev) + return -EIO; + + addr = phydev->addr; + + /* Save a local copy of the registers we are dealing with so we can + * set them back + */ + mii_addr = readl(&mac->mii_mgmt_addr); + mii_cmd = readl(&mac->mii_mgmt_cmd); + + /* Stop the current operation */ + writel(0, &mac->mii_mgmt_cmd); + + /* Set up the register we need to write to on the correct PHY */ + writel(MII_ADDR(addr, reg), &mac->mii_mgmt_addr); + + /* Add the value to write to the registers to the mac */ + writel(value, &mac->mii_mgmt_ctrl); + + do { + udelay(50); + delay++; + mii_indicator = readl(&mac->mii_mgmt_indicator); + } while ((mii_indicator & MGMT_BUSY) && delay < 100); + + /* If we hit the max delay, we could not write the register */ + if (delay == 100) { + u16 tmp; + + dev_warn(&adapter->pdev->dev, + "reg 0x%08x could not be written", reg); + dev_warn(&adapter->pdev->dev, "status is 0x%08x\n", + mii_indicator); + dev_warn(&adapter->pdev->dev, "command is 0x%08x\n", + readl(&mac->mii_mgmt_cmd)); + + et131x_mii_read(adapter, reg, &tmp); + + status = -EIO; + } + /* Stop the write operation */ + writel(0, &mac->mii_mgmt_cmd); + + /* + * set the registers we touched back to the state at which we entered + * this function + */ + writel(mii_addr, &mac->mii_mgmt_addr); + writel(mii_cmd, &mac->mii_mgmt_cmd); + + return status; +} + +/* Still used from _mac for BIT_READ */ +static void et1310_phy_access_mii_bit(struct et131x_adapter *adapter, + u16 action, u16 regnum, u16 bitnum, + u8 *value) +{ + u16 reg; + u16 mask = 0x0001 << bitnum; + + /* Read the requested register */ + et131x_mii_read(adapter, regnum, ®); + + switch (action) { + case TRUEPHY_BIT_READ: + *value = (reg & mask) >> bitnum; + break; + + case TRUEPHY_BIT_SET: + et131x_mii_write(adapter, regnum, reg | mask); + break; + + case TRUEPHY_BIT_CLEAR: + et131x_mii_write(adapter, regnum, reg & ~mask); + break; + + default: + break; + } +} + +static void et1310_config_flow_control(struct et131x_adapter *adapter) +{ + struct phy_device *phydev = adapter->phydev; + + if (phydev->duplex == DUPLEX_HALF) { + adapter->flowcontrol = FLOW_NONE; + } else { + char remote_pause, remote_async_pause; + + et1310_phy_access_mii_bit(adapter, + TRUEPHY_BIT_READ, 5, 10, &remote_pause); + et1310_phy_access_mii_bit(adapter, + TRUEPHY_BIT_READ, 5, 11, + &remote_async_pause); + + if ((remote_pause == TRUEPHY_BIT_SET) && + (remote_async_pause == TRUEPHY_BIT_SET)) { + adapter->flowcontrol = adapter->wanted_flow; + } else if ((remote_pause == TRUEPHY_BIT_SET) && + (remote_async_pause == TRUEPHY_BIT_CLEAR)) { + if (adapter->wanted_flow == FLOW_BOTH) + adapter->flowcontrol = FLOW_BOTH; + else + adapter->flowcontrol = FLOW_NONE; + } else if ((remote_pause == TRUEPHY_BIT_CLEAR) && + (remote_async_pause == TRUEPHY_BIT_CLEAR)) { + adapter->flowcontrol = FLOW_NONE; + } else {/* if (remote_pause == TRUEPHY_CLEAR_BIT && + remote_async_pause == TRUEPHY_SET_BIT) */ + if (adapter->wanted_flow == FLOW_BOTH) + adapter->flowcontrol = FLOW_RXONLY; + else + adapter->flowcontrol = FLOW_NONE; + } + } +} + +/** + * et1310_update_macstat_host_counters - Update the local copy of the statistics + * @adapter: pointer to the adapter structure + */ +static void et1310_update_macstat_host_counters(struct et131x_adapter *adapter) +{ + struct ce_stats *stats = &adapter->stats; + struct macstat_regs __iomem *macstat = + &adapter->regs->macstat; + + stats->tx_collisions += readl(&macstat->tx_total_collisions); + stats->tx_first_collisions += readl(&macstat->tx_single_collisions); + stats->tx_deferred += readl(&macstat->tx_deferred); + stats->tx_excessive_collisions += + readl(&macstat->tx_multiple_collisions); + stats->tx_late_collisions += readl(&macstat->tx_late_collisions); + stats->tx_underflows += readl(&macstat->tx_undersize_frames); + stats->tx_max_pkt_errs += readl(&macstat->tx_oversize_frames); + + stats->rx_align_errs += readl(&macstat->rx_align_errs); + stats->rx_crc_errs += readl(&macstat->rx_code_errs); + stats->rcvd_pkts_dropped += readl(&macstat->rx_drops); + stats->rx_overflows += readl(&macstat->rx_oversize_packets); + stats->rx_code_violations += readl(&macstat->rx_fcs_errs); + stats->rx_length_errs += readl(&macstat->rx_frame_len_errs); + stats->rx_other_errs += readl(&macstat->rx_fragment_packets); +} + +/** + * et1310_handle_macstat_interrupt + * @adapter: pointer to the adapter structure + * + * One of the MACSTAT counters has wrapped. Update the local copy of + * the statistics held in the adapter structure, checking the "wrap" + * bit for each counter. + */ +static void et1310_handle_macstat_interrupt(struct et131x_adapter *adapter) +{ + u32 carry_reg1; + u32 carry_reg2; + + /* Read the interrupt bits from the register(s). These are Clear On + * Write. + */ + carry_reg1 = readl(&adapter->regs->macstat.carry_reg1); + carry_reg2 = readl(&adapter->regs->macstat.carry_reg2); + + writel(carry_reg1, &adapter->regs->macstat.carry_reg1); + writel(carry_reg2, &adapter->regs->macstat.carry_reg2); + + /* We need to do update the host copy of all the MAC_STAT counters. + * For each counter, check it's overflow bit. If the overflow bit is + * set, then increment the host version of the count by one complete + * revolution of the counter. This routine is called when the counter + * block indicates that one of the counters has wrapped. + */ + if (carry_reg1 & (1 << 14)) + adapter->stats.rx_code_violations += COUNTER_WRAP_16_BIT; + if (carry_reg1 & (1 << 8)) + adapter->stats.rx_align_errs += COUNTER_WRAP_12_BIT; + if (carry_reg1 & (1 << 7)) + adapter->stats.rx_length_errs += COUNTER_WRAP_16_BIT; + if (carry_reg1 & (1 << 2)) + adapter->stats.rx_other_errs += COUNTER_WRAP_16_BIT; + if (carry_reg1 & (1 << 6)) + adapter->stats.rx_crc_errs += COUNTER_WRAP_16_BIT; + if (carry_reg1 & (1 << 3)) + adapter->stats.rx_overflows += COUNTER_WRAP_16_BIT; + if (carry_reg1 & (1 << 0)) + adapter->stats.rcvd_pkts_dropped += COUNTER_WRAP_16_BIT; + if (carry_reg2 & (1 << 16)) + adapter->stats.tx_max_pkt_errs += COUNTER_WRAP_12_BIT; + if (carry_reg2 & (1 << 15)) + adapter->stats.tx_underflows += COUNTER_WRAP_12_BIT; + if (carry_reg2 & (1 << 6)) + adapter->stats.tx_first_collisions += COUNTER_WRAP_12_BIT; + if (carry_reg2 & (1 << 8)) + adapter->stats.tx_deferred += COUNTER_WRAP_12_BIT; + if (carry_reg2 & (1 << 5)) + adapter->stats.tx_excessive_collisions += COUNTER_WRAP_12_BIT; + if (carry_reg2 & (1 << 4)) + adapter->stats.tx_late_collisions += COUNTER_WRAP_12_BIT; + if (carry_reg2 & (1 << 2)) + adapter->stats.tx_collisions += COUNTER_WRAP_12_BIT; +} + +static int et131x_mdio_read(struct mii_bus *bus, int phy_addr, int reg) +{ + struct net_device *netdev = bus->priv; + struct et131x_adapter *adapter = netdev_priv(netdev); + u16 value; + int ret; + + ret = et131x_phy_mii_read(adapter, phy_addr, reg, &value); + + if (ret < 0) + return ret; + else + return value; +} + +static int et131x_mdio_write(struct mii_bus *bus, int phy_addr, int reg, u16 value) +{ + struct net_device *netdev = bus->priv; + struct et131x_adapter *adapter = netdev_priv(netdev); + + return et131x_mii_write(adapter, reg, value); +} + +static int et131x_mdio_reset(struct mii_bus *bus) +{ + struct net_device *netdev = bus->priv; + struct et131x_adapter *adapter = netdev_priv(netdev); + + et131x_mii_write(adapter, MII_BMCR, BMCR_RESET); + + return 0; +} + +/** + * et1310_phy_power_down - PHY power control + * @adapter: device to control + * @down: true for off/false for back on + * + * one hundred, ten, one thousand megs + * How would you like to have your LAN accessed + * Can't you see that this code processed + * Phy power, phy power.. + */ +static void et1310_phy_power_down(struct et131x_adapter *adapter, bool down) +{ + u16 data; + + et131x_mii_read(adapter, MII_BMCR, &data); + data &= ~BMCR_PDOWN; + if (down) + data |= BMCR_PDOWN; + et131x_mii_write(adapter, MII_BMCR, data); +} + +/** + * et131x_xcvr_init - Init the phy if we are setting it into force mode + * @adapter: pointer to our private adapter structure + * + */ +static void et131x_xcvr_init(struct et131x_adapter *adapter) +{ + u16 imr; + u16 isr; + u16 lcr2; + + et131x_mii_read(adapter, PHY_INTERRUPT_STATUS, &isr); + et131x_mii_read(adapter, PHY_INTERRUPT_MASK, &imr); + + /* Set the link status interrupt only. Bad behavior when link status + * and auto neg are set, we run into a nested interrupt problem + */ + imr |= (ET_PHY_INT_MASK_AUTONEGSTAT & + ET_PHY_INT_MASK_LINKSTAT & + ET_PHY_INT_MASK_ENABLE); + + et131x_mii_write(adapter, PHY_INTERRUPT_MASK, imr); + + /* Set the LED behavior such that LED 1 indicates speed (off = + * 10Mbits, blink = 100Mbits, on = 1000Mbits) and LED 2 indicates + * link and activity (on for link, blink off for activity). + * + * NOTE: Some customizations have been added here for specific + * vendors; The LED behavior is now determined by vendor data in the + * EEPROM. However, the above description is the default. + */ + if ((adapter->eeprom_data[1] & 0x4) == 0) { + et131x_mii_read(adapter, PHY_LED_2, &lcr2); + + lcr2 &= (ET_LED2_LED_100TX & ET_LED2_LED_1000T); + lcr2 |= (LED_VAL_LINKON_ACTIVE << LED_LINK_SHIFT); + + if ((adapter->eeprom_data[1] & 0x8) == 0) + lcr2 |= (LED_VAL_1000BT_100BTX << LED_TXRX_SHIFT); + else + lcr2 |= (LED_VAL_LINKON << LED_TXRX_SHIFT); + + et131x_mii_write(adapter, PHY_LED_2, lcr2); + } +} + +/** + * et131x_configure_global_regs - configure JAGCore global regs + * @adapter: pointer to our adapter structure + * + * Used to configure the global registers on the JAGCore + */ +static void et131x_configure_global_regs(struct et131x_adapter *adapter) +{ + struct global_regs __iomem *regs = &adapter->regs->global; + + writel(0, ®s->rxq_start_addr); + writel(INTERNAL_MEM_SIZE - 1, ®s->txq_end_addr); + + if (adapter->registry_jumbo_packet < 2048) { + /* Tx / RxDMA and Tx/Rx MAC interfaces have a 1k word + * block of RAM that the driver can split between Tx + * and Rx as it desires. Our default is to split it + * 50/50: + */ + writel(PARM_RX_MEM_END_DEF, ®s->rxq_end_addr); + writel(PARM_RX_MEM_END_DEF + 1, ®s->txq_start_addr); + } else if (adapter->registry_jumbo_packet < 8192) { + /* For jumbo packets > 2k but < 8k, split 50-50. */ + writel(INTERNAL_MEM_RX_OFFSET, ®s->rxq_end_addr); + writel(INTERNAL_MEM_RX_OFFSET + 1, ®s->txq_start_addr); + } else { + /* 9216 is the only packet size greater than 8k that + * is available. The Tx buffer has to be big enough + * for one whole packet on the Tx side. We'll make + * the Tx 9408, and give the rest to Rx + */ + writel(0x01b3, ®s->rxq_end_addr); + writel(0x01b4, ®s->txq_start_addr); + } + + /* Initialize the loopback register. Disable all loopbacks. */ + writel(0, ®s->loopback); + + /* MSI Register */ + writel(0, ®s->msi_config); + + /* By default, disable the watchdog timer. It will be enabled when + * a packet is queued. + */ + writel(0, ®s->watchdog_timer); +} + +/** + * et131x_config_rx_dma_regs - Start of Rx_DMA init sequence + * @adapter: pointer to our adapter structure + */ +static void et131x_config_rx_dma_regs(struct et131x_adapter *adapter) +{ + struct rxdma_regs __iomem *rx_dma = &adapter->regs->rxdma; + struct rx_ring *rx_local = &adapter->rx_ring; + struct fbr_desc *fbr_entry; + u32 entry; + u32 psr_num_des; + unsigned long flags; + + /* Halt RXDMA to perform the reconfigure. */ + et131x_rx_dma_disable(adapter); + + /* Load the completion writeback physical address + * + * NOTE : dma_alloc_coherent(), used above to alloc DMA regions, + * ALWAYS returns SAC (32-bit) addresses. If DAC (64-bit) addresses + * are ever returned, make sure the high part is retrieved here + * before storing the adjusted address. + */ + writel((u32) ((u64)rx_local->rx_status_bus >> 32), + &rx_dma->dma_wb_base_hi); + writel((u32) rx_local->rx_status_bus, &rx_dma->dma_wb_base_lo); + + memset(rx_local->rx_status_block, 0, sizeof(struct rx_status_block)); + + /* Set the address and parameters of the packet status ring into the + * 1310's registers + */ + writel((u32) ((u64)rx_local->ps_ring_physaddr >> 32), + &rx_dma->psr_base_hi); + writel((u32) rx_local->ps_ring_physaddr, &rx_dma->psr_base_lo); + writel(rx_local->psr_num_entries - 1, &rx_dma->psr_num_des); + writel(0, &rx_dma->psr_full_offset); + + psr_num_des = readl(&rx_dma->psr_num_des) & 0xFFF; + writel((psr_num_des * LO_MARK_PERCENT_FOR_PSR) / 100, + &rx_dma->psr_min_des); + + spin_lock_irqsave(&adapter->rcv_lock, flags); + + /* These local variables track the PSR in the adapter structure */ + rx_local->local_psr_full = 0; + + /* Now's the best time to initialize FBR1 contents */ + fbr_entry = (struct fbr_desc *) rx_local->fbr[0]->ring_virtaddr; + for (entry = 0; entry < rx_local->fbr[0]->num_entries; entry++) { + fbr_entry->addr_hi = rx_local->fbr[0]->bus_high[entry]; + fbr_entry->addr_lo = rx_local->fbr[0]->bus_low[entry]; + fbr_entry->word2 = entry; + fbr_entry++; + } + + /* Set the address and parameters of Free buffer ring 1 (and 0 if + * required) into the 1310's registers + */ + writel((u32) (rx_local->fbr[0]->real_physaddr >> 32), + &rx_dma->fbr1_base_hi); + writel((u32) rx_local->fbr[0]->real_physaddr, &rx_dma->fbr1_base_lo); + writel(rx_local->fbr[0]->num_entries - 1, &rx_dma->fbr1_num_des); + writel(ET_DMA10_WRAP, &rx_dma->fbr1_full_offset); + + /* This variable tracks the free buffer ring 1 full position, so it + * has to match the above. + */ + rx_local->fbr[0]->local_full = ET_DMA10_WRAP; + writel( + ((rx_local->fbr[0]->num_entries * LO_MARK_PERCENT_FOR_RX) / 100) - 1, + &rx_dma->fbr1_min_des); + +#ifdef USE_FBR0 + /* Now's the best time to initialize FBR0 contents */ + fbr_entry = (struct fbr_desc *) rx_local->fbr[1]->ring_virtaddr; + for (entry = 0; entry < rx_local->fbr[1]->num_entries; entry++) { + fbr_entry->addr_hi = rx_local->fbr[1]->bus_high[entry]; + fbr_entry->addr_lo = rx_local->fbr[1]->bus_low[entry]; + fbr_entry->word2 = entry; + fbr_entry++; + } + + writel((u32) (rx_local->fbr[1]->real_physaddr >> 32), + &rx_dma->fbr0_base_hi); + writel((u32) rx_local->fbr[1]->real_physaddr, &rx_dma->fbr0_base_lo); + writel(rx_local->fbr[1]->num_entries - 1, &rx_dma->fbr0_num_des); + writel(ET_DMA10_WRAP, &rx_dma->fbr0_full_offset); + + /* This variable tracks the free buffer ring 0 full position, so it + * has to match the above. + */ + rx_local->fbr[1]->local_full = ET_DMA10_WRAP; + writel( + ((rx_local->fbr[1]->num_entries * LO_MARK_PERCENT_FOR_RX) / 100) - 1, + &rx_dma->fbr0_min_des); +#endif + + /* Program the number of packets we will receive before generating an + * interrupt. + * For version B silicon, this value gets updated once autoneg is + *complete. + */ + writel(PARM_RX_NUM_BUFS_DEF, &rx_dma->num_pkt_done); + + /* The "time_done" is not working correctly to coalesce interrupts + * after a given time period, but rather is giving us an interrupt + * regardless of whether we have received packets. + * This value gets updated once autoneg is complete. + */ + writel(PARM_RX_TIME_INT_DEF, &rx_dma->max_pkt_time); + + spin_unlock_irqrestore(&adapter->rcv_lock, flags); +} + +/** + * et131x_config_tx_dma_regs - Set up the tx dma section of the JAGCore. + * @adapter: pointer to our private adapter structure + * + * Configure the transmit engine with the ring buffers we have created + * and prepare it for use. + */ +static void et131x_config_tx_dma_regs(struct et131x_adapter *adapter) +{ + struct txdma_regs __iomem *txdma = &adapter->regs->txdma; + + /* Load the hardware with the start of the transmit descriptor ring. */ + writel((u32) ((u64)adapter->tx_ring.tx_desc_ring_pa >> 32), + &txdma->pr_base_hi); + writel((u32) adapter->tx_ring.tx_desc_ring_pa, + &txdma->pr_base_lo); + + /* Initialise the transmit DMA engine */ + writel(NUM_DESC_PER_RING_TX - 1, &txdma->pr_num_des); + + /* Load the completion writeback physical address */ + writel((u32)((u64)adapter->tx_ring.tx_status_pa >> 32), + &txdma->dma_wb_base_hi); + writel((u32)adapter->tx_ring.tx_status_pa, &txdma->dma_wb_base_lo); + + *adapter->tx_ring.tx_status = 0; + + writel(0, &txdma->service_request); + adapter->tx_ring.send_idx = 0; +} + +/** + * et131x_adapter_setup - Set the adapter up as per cassini+ documentation + * @adapter: pointer to our private adapter structure + * + * Returns 0 on success, errno on failure (as defined in errno.h) + */ +static void et131x_adapter_setup(struct et131x_adapter *adapter) +{ + /* Configure the JAGCore */ + et131x_configure_global_regs(adapter); + + et1310_config_mac_regs1(adapter); + + /* Configure the MMC registers */ + /* All we need to do is initialize the Memory Control Register */ + writel(ET_MMC_ENABLE, &adapter->regs->mmc.mmc_ctrl); + + et1310_config_rxmac_regs(adapter); + et1310_config_txmac_regs(adapter); + + et131x_config_rx_dma_regs(adapter); + et131x_config_tx_dma_regs(adapter); + + et1310_config_macstat_regs(adapter); + + et1310_phy_power_down(adapter, 0); + et131x_xcvr_init(adapter); +} + +/** + * et131x_soft_reset - Issue a soft reset to the hardware, complete for ET1310 + * @adapter: pointer to our private adapter structure + */ +static void et131x_soft_reset(struct et131x_adapter *adapter) +{ + /* Disable MAC Core */ + writel(0xc00f0000, &adapter->regs->mac.cfg1); + + /* Set everything to a reset value */ + writel(0x7F, &adapter->regs->global.sw_reset); + writel(0x000f0000, &adapter->regs->mac.cfg1); + writel(0x00000000, &adapter->regs->mac.cfg1); +} + +/** + * et131x_enable_interrupts - enable interrupt + * @adapter: et131x device + * + * Enable the appropriate interrupts on the ET131x according to our + * configuration + */ +static void et131x_enable_interrupts(struct et131x_adapter *adapter) +{ + u32 mask; + + /* Enable all global interrupts */ + if (adapter->flowcontrol == FLOW_TXONLY || + adapter->flowcontrol == FLOW_BOTH) + mask = INT_MASK_ENABLE; + else + mask = INT_MASK_ENABLE_NO_FLOW; + + writel(mask, &adapter->regs->global.int_mask); +} + +/** + * et131x_disable_interrupts - interrupt disable + * @adapter: et131x device + * + * Block all interrupts from the et131x device at the device itself + */ +static void et131x_disable_interrupts(struct et131x_adapter *adapter) +{ + /* Disable all global interrupts */ + writel(INT_MASK_DISABLE, &adapter->regs->global.int_mask); +} + +/** + * et131x_tx_dma_disable - Stop of Tx_DMA on the ET1310 + * @adapter: pointer to our adapter structure + */ +static void et131x_tx_dma_disable(struct et131x_adapter *adapter) +{ + /* Setup the tramsmit dma configuration register */ + writel(ET_TXDMA_CSR_HALT|ET_TXDMA_SNGL_EPKT, + &adapter->regs->txdma.csr); +} + +/** + * et131x_enable_txrx - Enable tx/rx queues + * @netdev: device to be enabled + */ +static void et131x_enable_txrx(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + + /* Enable the Tx and Rx DMA engines (if not already enabled) */ + et131x_rx_dma_enable(adapter); + et131x_tx_dma_enable(adapter); + + /* Enable device interrupts */ + if (adapter->flags & fMP_ADAPTER_INTERRUPT_IN_USE) + et131x_enable_interrupts(adapter); + + /* We're ready to move some data, so start the queue */ + netif_start_queue(netdev); +} + +/** + * et131x_disable_txrx - Disable tx/rx queues + * @netdev: device to be disabled + */ +static void et131x_disable_txrx(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + + /* First thing is to stop the queue */ + netif_stop_queue(netdev); + + /* Stop the Tx and Rx DMA engines */ + et131x_rx_dma_disable(adapter); + et131x_tx_dma_disable(adapter); + + /* Disable device interrupts */ + et131x_disable_interrupts(adapter); +} + +/** + * et131x_init_send - Initialize send data structures + * @adapter: pointer to our private adapter structure + */ +static void et131x_init_send(struct et131x_adapter *adapter) +{ + struct tcb *tcb; + u32 ct; + struct tx_ring *tx_ring; + + /* Setup some convenience pointers */ + tx_ring = &adapter->tx_ring; + tcb = adapter->tx_ring.tcb_ring; + + tx_ring->tcb_qhead = tcb; + + memset(tcb, 0, sizeof(struct tcb) * NUM_TCB); + + /* Go through and set up each TCB */ + for (ct = 0; ct++ < NUM_TCB; tcb++) + /* Set the link pointer in HW TCB to the next TCB in the + * chain + */ + tcb->next = tcb + 1; + + /* Set the tail pointer */ + tcb--; + tx_ring->tcb_qtail = tcb; + tcb->next = NULL; + /* Curr send queue should now be empty */ + tx_ring->send_head = NULL; + tx_ring->send_tail = NULL; +} + +/** + * et1310_enable_phy_coma - called when network cable is unplugged + * @adapter: pointer to our adapter structure + * + * driver receive an phy status change interrupt while in D0 and check that + * phy_status is down. + * + * -- gate off JAGCore; + * -- set gigE PHY in Coma mode + * -- wake on phy_interrupt; Perform software reset JAGCore, + * re-initialize jagcore and gigE PHY + * + * Add D0-ASPM-PhyLinkDown Support: + * -- while in D0, when there is a phy_interrupt indicating phy link + * down status, call the MPSetPhyComa routine to enter this active + * state power saving mode + * -- while in D0-ASPM-PhyLinkDown mode, when there is a phy_interrupt + * indicating linkup status, call the MPDisablePhyComa routine to + * restore JAGCore and gigE PHY + */ +static void et1310_enable_phy_coma(struct et131x_adapter *adapter) +{ + unsigned long flags; + u32 pmcsr; + + pmcsr = readl(&adapter->regs->global.pm_csr); + + /* Save the GbE PHY speed and duplex modes. Need to restore this + * when cable is plugged back in + */ + /* + * TODO - when PM is re-enabled, check if we need to + * perform a similar task as this - + * adapter->pdown_speed = adapter->ai_force_speed; + * adapter->pdown_duplex = adapter->ai_force_duplex; + */ + + /* Stop sending packets. */ + spin_lock_irqsave(&adapter->send_hw_lock, flags); + adapter->flags |= fMP_ADAPTER_LOWER_POWER; + spin_unlock_irqrestore(&adapter->send_hw_lock, flags); + + /* Wait for outstanding Receive packets */ + + et131x_disable_txrx(adapter->netdev); + + /* Gate off JAGCore 3 clock domains */ + pmcsr &= ~ET_PMCSR_INIT; + writel(pmcsr, &adapter->regs->global.pm_csr); + + /* Program gigE PHY in to Coma mode */ + pmcsr |= ET_PM_PHY_SW_COMA; + writel(pmcsr, &adapter->regs->global.pm_csr); +} + +/** + * et1310_disable_phy_coma - Disable the Phy Coma Mode + * @adapter: pointer to our adapter structure + */ +static void et1310_disable_phy_coma(struct et131x_adapter *adapter) +{ + u32 pmcsr; + + pmcsr = readl(&adapter->regs->global.pm_csr); + + /* Disable phy_sw_coma register and re-enable JAGCore clocks */ + pmcsr |= ET_PMCSR_INIT; + pmcsr &= ~ET_PM_PHY_SW_COMA; + writel(pmcsr, &adapter->regs->global.pm_csr); + + /* Restore the GbE PHY speed and duplex modes; + * Reset JAGCore; re-configure and initialize JAGCore and gigE PHY + */ + /* TODO - when PM is re-enabled, check if we need to + * perform a similar task as this - + * adapter->ai_force_speed = adapter->pdown_speed; + * adapter->ai_force_duplex = adapter->pdown_duplex; + */ + + /* Re-initialize the send structures */ + et131x_init_send(adapter); + + /* Bring the device back to the state it was during init prior to + * autonegotiation being complete. This way, when we get the auto-neg + * complete interrupt, we can complete init by calling ConfigMacREGS2. + */ + et131x_soft_reset(adapter); + + /* setup et1310 as per the documentation ?? */ + et131x_adapter_setup(adapter); + + /* Allow Tx to restart */ + adapter->flags &= ~fMP_ADAPTER_LOWER_POWER; + + et131x_enable_txrx(adapter->netdev); +} + +static inline u32 bump_free_buff_ring(u32 *free_buff_ring, u32 limit) +{ + u32 tmp_free_buff_ring = *free_buff_ring; + tmp_free_buff_ring++; + /* This works for all cases where limit < 1024. The 1023 case + works because 1023++ is 1024 which means the if condition is not + taken but the carry of the bit into the wrap bit toggles the wrap + value correctly */ + if ((tmp_free_buff_ring & ET_DMA10_MASK) > limit) { + tmp_free_buff_ring &= ~ET_DMA10_MASK; + tmp_free_buff_ring ^= ET_DMA10_WRAP; + } + /* For the 1023 case */ + tmp_free_buff_ring &= (ET_DMA10_MASK|ET_DMA10_WRAP); + *free_buff_ring = tmp_free_buff_ring; + return tmp_free_buff_ring; +} + +/** + * et131x_align_allocated_memory - Align allocated memory on a given boundary + * @adapter: pointer to our adapter structure + * @phys_addr: pointer to Physical address + * @offset: pointer to the offset variable + * @mask: correct mask + */ +static void et131x_align_allocated_memory(struct et131x_adapter *adapter, + u64 *phys_addr, u64 *offset, + u64 mask) +{ + u64 new_addr = *phys_addr & ~mask; + + *offset = 0; + + if (new_addr != *phys_addr) { + /* Move to next aligned block */ + new_addr += mask + 1; + /* Return offset for adjusting virt addr */ + *offset = new_addr - *phys_addr; + /* Return new physical address */ + *phys_addr = new_addr; + } +} + +/** + * et131x_rx_dma_memory_alloc + * @adapter: pointer to our private adapter structure + * + * Returns 0 on success and errno on failure (as defined in errno.h) + * + * Allocates Free buffer ring 1 for sure, free buffer ring 0 if required, + * and the Packet Status Ring. + */ +static int et131x_rx_dma_memory_alloc(struct et131x_adapter *adapter) +{ + u32 i, j; + u32 bufsize; + u32 pktstat_ringsize, fbr_chunksize; + struct rx_ring *rx_ring; + + /* Setup some convenience pointers */ + rx_ring = &adapter->rx_ring; + + /* Alloc memory for the lookup table */ +#ifdef USE_FBR0 + rx_ring->fbr[1] = kmalloc(sizeof(struct fbr_lookup), GFP_KERNEL); +#endif + rx_ring->fbr[0] = kmalloc(sizeof(struct fbr_lookup), GFP_KERNEL); + + /* The first thing we will do is configure the sizes of the buffer + * rings. These will change based on jumbo packet support. Larger + * jumbo packets increases the size of each entry in FBR0, and the + * number of entries in FBR0, while at the same time decreasing the + * number of entries in FBR1. + * + * FBR1 holds "large" frames, FBR0 holds "small" frames. If FBR1 + * entries are huge in order to accommodate a "jumbo" frame, then it + * will have less entries. Conversely, FBR1 will now be relied upon + * to carry more "normal" frames, thus it's entry size also increases + * and the number of entries goes up too (since it now carries + * "small" + "regular" packets. + * + * In this scheme, we try to maintain 512 entries between the two + * rings. Also, FBR1 remains a constant size - when it's size doubles + * the number of entries halves. FBR0 increases in size, however. + */ + + if (adapter->registry_jumbo_packet < 2048) { +#ifdef USE_FBR0 + rx_ring->fbr[1]->buffsize = 256; + rx_ring->fbr[1]->num_entries = 512; +#endif + rx_ring->fbr[0]->buffsize = 2048; + rx_ring->fbr[0]->num_entries = 512; + } else if (adapter->registry_jumbo_packet < 4096) { +#ifdef USE_FBR0 + rx_ring->fbr[1]->buffsize = 512; + rx_ring->fbr[1]->num_entries = 1024; +#endif + rx_ring->fbr[0]->buffsize = 4096; + rx_ring->fbr[0]->num_entries = 512; + } else { +#ifdef USE_FBR0 + rx_ring->fbr[1]->buffsize = 1024; + rx_ring->fbr[1]->num_entries = 768; +#endif + rx_ring->fbr[0]->buffsize = 16384; + rx_ring->fbr[0]->num_entries = 128; + } + +#ifdef USE_FBR0 + adapter->rx_ring.psr_num_entries = + adapter->rx_ring.fbr[1]->num_entries + + adapter->rx_ring.fbr[0]->num_entries; +#else + adapter->rx_ring.psr_num_entries = adapter->rx_ring.fbr[0]->num_entries; +#endif + + /* Allocate an area of memory for Free Buffer Ring 1 */ + bufsize = (sizeof(struct fbr_desc) * rx_ring->fbr[0]->num_entries) + + 0xfff; + rx_ring->fbr[0]->ring_virtaddr = dma_alloc_coherent(&adapter->pdev->dev, + bufsize, + &rx_ring->fbr[0]->ring_physaddr, + GFP_KERNEL); + if (!rx_ring->fbr[0]->ring_virtaddr) { + dev_err(&adapter->pdev->dev, + "Cannot alloc memory for Free Buffer Ring 1\n"); + return -ENOMEM; + } + + /* Save physical address + * + * NOTE: dma_alloc_coherent(), used above to alloc DMA regions, + * ALWAYS returns SAC (32-bit) addresses. If DAC (64-bit) addresses + * are ever returned, make sure the high part is retrieved here + * before storing the adjusted address. + */ + rx_ring->fbr[0]->real_physaddr = rx_ring->fbr[0]->ring_physaddr; + + /* Align Free Buffer Ring 1 on a 4K boundary */ + et131x_align_allocated_memory(adapter, + &rx_ring->fbr[0]->real_physaddr, + &rx_ring->fbr[0]->offset, 0x0FFF); + + rx_ring->fbr[0]->ring_virtaddr = + (void *)((u8 *) rx_ring->fbr[0]->ring_virtaddr + + rx_ring->fbr[0]->offset); + +#ifdef USE_FBR0 + /* Allocate an area of memory for Free Buffer Ring 0 */ + bufsize = (sizeof(struct fbr_desc) * rx_ring->fbr[1]->num_entries) + + 0xfff; + rx_ring->fbr[1]->ring_virtaddr = dma_alloc_coherent(&adapter->pdev->dev, + bufsize, + &rx_ring->fbr[1]->ring_physaddr, + GFP_KERNEL); + if (!rx_ring->fbr[1]->ring_virtaddr) { + dev_err(&adapter->pdev->dev, + "Cannot alloc memory for Free Buffer Ring 0\n"); + return -ENOMEM; + } + + /* Save physical address + * + * NOTE: dma_alloc_coherent(), used above to alloc DMA regions, + * ALWAYS returns SAC (32-bit) addresses. If DAC (64-bit) addresses + * are ever returned, make sure the high part is retrieved here before + * storing the adjusted address. + */ + rx_ring->fbr[1]->real_physaddr = rx_ring->fbr[1]->ring_physaddr; + + /* Align Free Buffer Ring 0 on a 4K boundary */ + et131x_align_allocated_memory(adapter, + &rx_ring->fbr[1]->real_physaddr, + &rx_ring->fbr[1]->offset, 0x0FFF); + + rx_ring->fbr[1]->ring_virtaddr = + (void *)((u8 *) rx_ring->fbr[1]->ring_virtaddr + + rx_ring->fbr[1]->offset); +#endif + for (i = 0; i < (rx_ring->fbr[0]->num_entries / FBR_CHUNKS); i++) { + u64 fbr1_tmp_physaddr; + u64 fbr1_offset; + u32 fbr1_align; + + /* This code allocates an area of memory big enough for N + * free buffers + (buffer_size - 1) so that the buffers can + * be aligned on 4k boundaries. If each buffer were aligned + * to a buffer_size boundary, the effect would be to double + * the size of FBR0. By allocating N buffers at once, we + * reduce this overhead. + */ + if (rx_ring->fbr[0]->buffsize > 4096) + fbr1_align = 4096; + else + fbr1_align = rx_ring->fbr[0]->buffsize; + + fbr_chunksize = + (FBR_CHUNKS * rx_ring->fbr[0]->buffsize) + fbr1_align - 1; + rx_ring->fbr[0]->mem_virtaddrs[i] = + dma_alloc_coherent(&adapter->pdev->dev, fbr_chunksize, + &rx_ring->fbr[0]->mem_physaddrs[i], + GFP_KERNEL); + + if (!rx_ring->fbr[0]->mem_virtaddrs[i]) { + dev_err(&adapter->pdev->dev, + "Could not alloc memory\n"); + return -ENOMEM; + } + + /* See NOTE in "Save Physical Address" comment above */ + fbr1_tmp_physaddr = rx_ring->fbr[0]->mem_physaddrs[i]; + + et131x_align_allocated_memory(adapter, + &fbr1_tmp_physaddr, + &fbr1_offset, (fbr1_align - 1)); + + for (j = 0; j < FBR_CHUNKS; j++) { + u32 index = (i * FBR_CHUNKS) + j; + + /* Save the Virtual address of this index for quick + * access later + */ + rx_ring->fbr[0]->virt[index] = + (u8 *) rx_ring->fbr[0]->mem_virtaddrs[i] + + (j * rx_ring->fbr[0]->buffsize) + fbr1_offset; + + /* now store the physical address in the descriptor + * so the device can access it + */ + rx_ring->fbr[0]->bus_high[index] = + (u32) (fbr1_tmp_physaddr >> 32); + rx_ring->fbr[0]->bus_low[index] = + (u32) fbr1_tmp_physaddr; + + fbr1_tmp_physaddr += rx_ring->fbr[0]->buffsize; + + rx_ring->fbr[0]->buffer1[index] = + rx_ring->fbr[0]->virt[index]; + rx_ring->fbr[0]->buffer2[index] = + rx_ring->fbr[0]->virt[index] - 4; + } + } + +#ifdef USE_FBR0 + /* Same for FBR0 (if in use) */ + for (i = 0; i < (rx_ring->fbr[1]->num_entries / FBR_CHUNKS); i++) { + u64 fbr0_tmp_physaddr; + u64 fbr0_offset; + + fbr_chunksize = + ((FBR_CHUNKS + 1) * rx_ring->fbr[1]->buffsize) - 1; + rx_ring->fbr[1]->mem_virtaddrs[i] = + dma_alloc_coherent(&adapter->pdev->dev, fbr_chunksize, + &rx_ring->fbr[1]->mem_physaddrs[i], + GFP_KERNEL); + + if (!rx_ring->fbr[1]->mem_virtaddrs[i]) { + dev_err(&adapter->pdev->dev, + "Could not alloc memory\n"); + return -ENOMEM; + } + + /* See NOTE in "Save Physical Address" comment above */ + fbr0_tmp_physaddr = rx_ring->fbr[1]->mem_physaddrs[i]; + + et131x_align_allocated_memory(adapter, + &fbr0_tmp_physaddr, + &fbr0_offset, + rx_ring->fbr[1]->buffsize - 1); + + for (j = 0; j < FBR_CHUNKS; j++) { + u32 index = (i * FBR_CHUNKS) + j; + + rx_ring->fbr[1]->virt[index] = + (u8 *) rx_ring->fbr[1]->mem_virtaddrs[i] + + (j * rx_ring->fbr[1]->buffsize) + fbr0_offset; + + rx_ring->fbr[1]->bus_high[index] = + (u32) (fbr0_tmp_physaddr >> 32); + rx_ring->fbr[1]->bus_low[index] = + (u32) fbr0_tmp_physaddr; + + fbr0_tmp_physaddr += rx_ring->fbr[1]->buffsize; + + rx_ring->fbr[1]->buffer1[index] = + rx_ring->fbr[1]->virt[index]; + rx_ring->fbr[1]->buffer2[index] = + rx_ring->fbr[1]->virt[index] - 4; + } + } +#endif + + /* Allocate an area of memory for FIFO of Packet Status ring entries */ + pktstat_ringsize = + sizeof(struct pkt_stat_desc) * adapter->rx_ring.psr_num_entries; + + rx_ring->ps_ring_virtaddr = dma_alloc_coherent(&adapter->pdev->dev, + pktstat_ringsize, + &rx_ring->ps_ring_physaddr, + GFP_KERNEL); + + if (!rx_ring->ps_ring_virtaddr) { + dev_err(&adapter->pdev->dev, + "Cannot alloc memory for Packet Status Ring\n"); + return -ENOMEM; + } + printk(KERN_INFO "Packet Status Ring %lx\n", + (unsigned long) rx_ring->ps_ring_physaddr); + + /* + * NOTE : dma_alloc_coherent(), used above to alloc DMA regions, + * ALWAYS returns SAC (32-bit) addresses. If DAC (64-bit) addresses + * are ever returned, make sure the high part is retrieved here before + * storing the adjusted address. + */ + + /* Allocate an area of memory for writeback of status information */ + rx_ring->rx_status_block = dma_alloc_coherent(&adapter->pdev->dev, + sizeof(struct rx_status_block), + &rx_ring->rx_status_bus, + GFP_KERNEL); + if (!rx_ring->rx_status_block) { + dev_err(&adapter->pdev->dev, + "Cannot alloc memory for Status Block\n"); + return -ENOMEM; + } + rx_ring->num_rfd = NIC_DEFAULT_NUM_RFD; + printk(KERN_INFO "PRS %lx\n", (unsigned long)rx_ring->rx_status_bus); + + /* Recv + * kmem_cache_create initializes a lookaside list. After successful + * creation, nonpaged fixed-size blocks can be allocated from and + * freed to the lookaside list. + * RFDs will be allocated from this pool. + */ + rx_ring->recv_lookaside = kmem_cache_create(adapter->netdev->name, + sizeof(struct rfd), + 0, + SLAB_CACHE_DMA | + SLAB_HWCACHE_ALIGN, + NULL); + + adapter->flags |= fMP_ADAPTER_RECV_LOOKASIDE; + + /* The RFDs are going to be put on lists later on, so initialize the + * lists now. + */ + INIT_LIST_HEAD(&rx_ring->recv_list); + return 0; +} + +/** + * et131x_rx_dma_memory_free - Free all memory allocated within this module. + * @adapter: pointer to our private adapter structure + */ +static void et131x_rx_dma_memory_free(struct et131x_adapter *adapter) +{ + u32 index; + u32 bufsize; + u32 pktstat_ringsize; + struct rfd *rfd; + struct rx_ring *rx_ring; + + /* Setup some convenience pointers */ + rx_ring = &adapter->rx_ring; + + /* Free RFDs and associated packet descriptors */ + WARN_ON(rx_ring->num_ready_recv != rx_ring->num_rfd); + + while (!list_empty(&rx_ring->recv_list)) { + rfd = (struct rfd *) list_entry(rx_ring->recv_list.next, + struct rfd, list_node); + + list_del(&rfd->list_node); + rfd->skb = NULL; + kmem_cache_free(adapter->rx_ring.recv_lookaside, rfd); + } + + /* Free Free Buffer Ring 1 */ + if (rx_ring->fbr[0]->ring_virtaddr) { + /* First the packet memory */ + for (index = 0; index < + (rx_ring->fbr[0]->num_entries / FBR_CHUNKS); index++) { + if (rx_ring->fbr[0]->mem_virtaddrs[index]) { + u32 fbr1_align; + + if (rx_ring->fbr[0]->buffsize > 4096) + fbr1_align = 4096; + else + fbr1_align = rx_ring->fbr[0]->buffsize; + + bufsize = + (rx_ring->fbr[0]->buffsize * FBR_CHUNKS) + + fbr1_align - 1; + + dma_free_coherent(&adapter->pdev->dev, + bufsize, + rx_ring->fbr[0]->mem_virtaddrs[index], + rx_ring->fbr[0]->mem_physaddrs[index]); + + rx_ring->fbr[0]->mem_virtaddrs[index] = NULL; + } + } + + /* Now the FIFO itself */ + rx_ring->fbr[0]->ring_virtaddr = (void *)((u8 *) + rx_ring->fbr[0]->ring_virtaddr - rx_ring->fbr[0]->offset); + + bufsize = + (sizeof(struct fbr_desc) * rx_ring->fbr[0]->num_entries) + + 0xfff; + + dma_free_coherent(&adapter->pdev->dev, bufsize, + rx_ring->fbr[0]->ring_virtaddr, + rx_ring->fbr[0]->ring_physaddr); + + rx_ring->fbr[0]->ring_virtaddr = NULL; + } + +#ifdef USE_FBR0 + /* Now the same for Free Buffer Ring 0 */ + if (rx_ring->fbr[1]->ring_virtaddr) { + /* First the packet memory */ + for (index = 0; index < + (rx_ring->fbr[1]->num_entries / FBR_CHUNKS); index++) { + if (rx_ring->fbr[1]->mem_virtaddrs[index]) { + bufsize = + (rx_ring->fbr[1]->buffsize * + (FBR_CHUNKS + 1)) - 1; + + dma_free_coherent(&adapter->pdev->dev, + bufsize, + rx_ring->fbr[1]->mem_virtaddrs[index], + rx_ring->fbr[1]->mem_physaddrs[index]); + + rx_ring->fbr[1]->mem_virtaddrs[index] = NULL; + } + } + + /* Now the FIFO itself */ + rx_ring->fbr[1]->ring_virtaddr = (void *)((u8 *) + rx_ring->fbr[1]->ring_virtaddr - rx_ring->fbr[1]->offset); + + bufsize = + (sizeof(struct fbr_desc) * rx_ring->fbr[1]->num_entries) + + 0xfff; + + dma_free_coherent(&adapter->pdev->dev, + bufsize, + rx_ring->fbr[1]->ring_virtaddr, + rx_ring->fbr[1]->ring_physaddr); + + rx_ring->fbr[1]->ring_virtaddr = NULL; + } +#endif + + /* Free Packet Status Ring */ + if (rx_ring->ps_ring_virtaddr) { + pktstat_ringsize = + sizeof(struct pkt_stat_desc) * + adapter->rx_ring.psr_num_entries; + + dma_free_coherent(&adapter->pdev->dev, pktstat_ringsize, + rx_ring->ps_ring_virtaddr, + rx_ring->ps_ring_physaddr); + + rx_ring->ps_ring_virtaddr = NULL; + } + + /* Free area of memory for the writeback of status information */ + if (rx_ring->rx_status_block) { + dma_free_coherent(&adapter->pdev->dev, + sizeof(struct rx_status_block), + rx_ring->rx_status_block, rx_ring->rx_status_bus); + rx_ring->rx_status_block = NULL; + } + + /* Destroy the lookaside (RFD) pool */ + if (adapter->flags & fMP_ADAPTER_RECV_LOOKASIDE) { + kmem_cache_destroy(rx_ring->recv_lookaside); + adapter->flags &= ~fMP_ADAPTER_RECV_LOOKASIDE; + } + + /* Free the FBR Lookup Table */ +#ifdef USE_FBR0 + kfree(rx_ring->fbr[1]); +#endif + + kfree(rx_ring->fbr[0]); + + /* Reset Counters */ + rx_ring->num_ready_recv = 0; +} + +/** + * et131x_init_recv - Initialize receive data structures. + * @adapter: pointer to our private adapter structure + * + * Returns 0 on success and errno on failure (as defined in errno.h) + */ +static int et131x_init_recv(struct et131x_adapter *adapter) +{ + int status = -ENOMEM; + struct rfd *rfd = NULL; + u32 rfdct; + u32 numrfd = 0; + struct rx_ring *rx_ring; + + /* Setup some convenience pointers */ + rx_ring = &adapter->rx_ring; + + /* Setup each RFD */ + for (rfdct = 0; rfdct < rx_ring->num_rfd; rfdct++) { + rfd = kmem_cache_alloc(rx_ring->recv_lookaside, + GFP_ATOMIC | GFP_DMA); + + if (!rfd) { + dev_err(&adapter->pdev->dev, + "Couldn't alloc RFD out of kmem_cache\n"); + status = -ENOMEM; + continue; + } + + rfd->skb = NULL; + + /* Add this RFD to the recv_list */ + list_add_tail(&rfd->list_node, &rx_ring->recv_list); + + /* Increment both the available RFD's, and the total RFD's. */ + rx_ring->num_ready_recv++; + numrfd++; + } + + if (numrfd > NIC_MIN_NUM_RFD) + status = 0; + + rx_ring->num_rfd = numrfd; + + if (status != 0) { + kmem_cache_free(rx_ring->recv_lookaside, rfd); + dev_err(&adapter->pdev->dev, + "Allocation problems in et131x_init_recv\n"); + } + return status; +} + +/** + * et131x_set_rx_dma_timer - Set the heartbeat timer according to line rate. + * @adapter: pointer to our adapter structure + */ +static void et131x_set_rx_dma_timer(struct et131x_adapter *adapter) +{ + struct phy_device *phydev = adapter->phydev; + + if (!phydev) + return; + + /* For version B silicon, we do not use the RxDMA timer for 10 and 100 + * Mbits/s line rates. We do not enable and RxDMA interrupt coalescing. + */ + if ((phydev->speed == SPEED_100) || (phydev->speed == SPEED_10)) { + writel(0, &adapter->regs->rxdma.max_pkt_time); + writel(1, &adapter->regs->rxdma.num_pkt_done); + } +} + +/** + * NICReturnRFD - Recycle a RFD and put it back onto the receive list + * @adapter: pointer to our adapter + * @rfd: pointer to the RFD + */ +static void nic_return_rfd(struct et131x_adapter *adapter, struct rfd *rfd) +{ + struct rx_ring *rx_local = &adapter->rx_ring; + struct rxdma_regs __iomem *rx_dma = &adapter->regs->rxdma; + u16 buff_index = rfd->bufferindex; + u8 ring_index = rfd->ringindex; + unsigned long flags; + + /* We don't use any of the OOB data besides status. Otherwise, we + * need to clean up OOB data + */ + if ( +#ifdef USE_FBR0 + (ring_index == 0 && buff_index < rx_local->fbr[1]->num_entries) || +#endif + (ring_index == 1 && buff_index < rx_local->fbr[0]->num_entries)) { + spin_lock_irqsave(&adapter->fbr_lock, flags); + + if (ring_index == 1) { + struct fbr_desc *next = (struct fbr_desc *) + (rx_local->fbr[0]->ring_virtaddr) + + INDEX10(rx_local->fbr[0]->local_full); + + /* Handle the Free Buffer Ring advancement here. Write + * the PA / Buffer Index for the returned buffer into + * the oldest (next to be freed)FBR entry + */ + next->addr_hi = rx_local->fbr[0]->bus_high[buff_index]; + next->addr_lo = rx_local->fbr[0]->bus_low[buff_index]; + next->word2 = buff_index; + + writel(bump_free_buff_ring( + &rx_local->fbr[0]->local_full, + rx_local->fbr[0]->num_entries - 1), + &rx_dma->fbr1_full_offset); + } +#ifdef USE_FBR0 + else { + struct fbr_desc *next = (struct fbr_desc *) + rx_local->fbr[1]->ring_virtaddr + + INDEX10(rx_local->fbr[1]->local_full); + + /* Handle the Free Buffer Ring advancement here. Write + * the PA / Buffer Index for the returned buffer into + * the oldest (next to be freed) FBR entry + */ + next->addr_hi = rx_local->fbr[1]->bus_high[buff_index]; + next->addr_lo = rx_local->fbr[1]->bus_low[buff_index]; + next->word2 = buff_index; + + writel(bump_free_buff_ring( + &rx_local->fbr[1]->local_full, + rx_local->fbr[1]->num_entries - 1), + &rx_dma->fbr0_full_offset); + } +#endif + spin_unlock_irqrestore(&adapter->fbr_lock, flags); + } else { + dev_err(&adapter->pdev->dev, + "%s illegal Buffer Index returned\n", __func__); + } + + /* The processing on this RFD is done, so put it back on the tail of + * our list + */ + spin_lock_irqsave(&adapter->rcv_lock, flags); + list_add_tail(&rfd->list_node, &rx_local->recv_list); + rx_local->num_ready_recv++; + spin_unlock_irqrestore(&adapter->rcv_lock, flags); + + WARN_ON(rx_local->num_ready_recv > rx_local->num_rfd); +} + +/** + * nic_rx_pkts - Checks the hardware for available packets + * @adapter: pointer to our adapter + * + * Returns rfd, a pointer to our MPRFD. + * + * Checks the hardware for available packets, using completion ring + * If packets are available, it gets an RFD from the recv_list, attaches + * the packet to it, puts the RFD in the RecvPendList, and also returns + * the pointer to the RFD. + */ +static struct rfd *nic_rx_pkts(struct et131x_adapter *adapter) +{ + struct rx_ring *rx_local = &adapter->rx_ring; + struct rx_status_block *status; + struct pkt_stat_desc *psr; + struct rfd *rfd; + u32 i; + u8 *buf; + unsigned long flags; + struct list_head *element; + u8 ring_index; + u16 buff_index; + u32 len; + u32 word0; + u32 word1; + + /* RX Status block is written by the DMA engine prior to every + * interrupt. It contains the next to be used entry in the Packet + * Status Ring, and also the two Free Buffer rings. + */ + status = rx_local->rx_status_block; + word1 = status->word1 >> 16; /* Get the useful bits */ + + /* Check the PSR and wrap bits do not match */ + if ((word1 & 0x1FFF) == (rx_local->local_psr_full & 0x1FFF)) + /* Looks like this ring is not updated yet */ + return NULL; + + /* The packet status ring indicates that data is available. */ + psr = (struct pkt_stat_desc *) (rx_local->ps_ring_virtaddr) + + (rx_local->local_psr_full & 0xFFF); + + /* Grab any information that is required once the PSR is + * advanced, since we can no longer rely on the memory being + * accurate + */ + len = psr->word1 & 0xFFFF; + ring_index = (psr->word1 >> 26) & 0x03; + buff_index = (psr->word1 >> 16) & 0x3FF; + word0 = psr->word0; + + /* Indicate that we have used this PSR entry. */ + /* FIXME wrap 12 */ + add_12bit(&rx_local->local_psr_full, 1); + if ( + (rx_local->local_psr_full & 0xFFF) > rx_local->psr_num_entries - 1) { + /* Clear psr full and toggle the wrap bit */ + rx_local->local_psr_full &= ~0xFFF; + rx_local->local_psr_full ^= 0x1000; + } + + writel(rx_local->local_psr_full, + &adapter->regs->rxdma.psr_full_offset); + +#ifndef USE_FBR0 + if (ring_index != 1) + return NULL; +#endif + +#ifdef USE_FBR0 + if (ring_index > 1 || + (ring_index == 0 && + buff_index > rx_local->fbr[1]->num_entries - 1) || + (ring_index == 1 && + buff_index > rx_local->fbr[0]->num_entries - 1)) +#else + if (ring_index != 1 || buff_index > rx_local->fbr[0]->num_entries - 1) +#endif + { + /* Illegal buffer or ring index cannot be used by S/W*/ + dev_err(&adapter->pdev->dev, + "NICRxPkts PSR Entry %d indicates " + "length of %d and/or bad bi(%d)\n", + rx_local->local_psr_full & 0xFFF, + len, buff_index); + return NULL; + } + + /* Get and fill the RFD. */ + spin_lock_irqsave(&adapter->rcv_lock, flags); + + rfd = NULL; + element = rx_local->recv_list.next; + rfd = (struct rfd *) list_entry(element, struct rfd, list_node); + + if (rfd == NULL) { + spin_unlock_irqrestore(&adapter->rcv_lock, flags); + return NULL; + } + + list_del(&rfd->list_node); + rx_local->num_ready_recv--; + + spin_unlock_irqrestore(&adapter->rcv_lock, flags); + + rfd->bufferindex = buff_index; + rfd->ringindex = ring_index; + + /* In V1 silicon, there is a bug which screws up filtering of + * runt packets. Therefore runt packet filtering is disabled + * in the MAC and the packets are dropped here. They are + * also counted here. + */ + if (len < (NIC_MIN_PACKET_SIZE + 4)) { + adapter->stats.rx_other_errs++; + len = 0; + } + + if (len) { + /* Determine if this is a multicast packet coming in */ + if ((word0 & ALCATEL_MULTICAST_PKT) && + !(word0 & ALCATEL_BROADCAST_PKT)) { + /* Promiscuous mode and Multicast mode are + * not mutually exclusive as was first + * thought. I guess Promiscuous is just + * considered a super-set of the other + * filters. Generally filter is 0x2b when in + * promiscuous mode. + */ + if ((adapter->packet_filter & + ET131X_PACKET_TYPE_MULTICAST) + && !(adapter->packet_filter & + ET131X_PACKET_TYPE_PROMISCUOUS) + && !(adapter->packet_filter & + ET131X_PACKET_TYPE_ALL_MULTICAST)) { + /* + * Note - ring_index for fbr[] array is reversed + * 1 for FBR0 etc + */ + buf = rx_local->fbr[(ring_index == 0 ? 1 : 0)]-> + virt[buff_index]; + + /* Loop through our list to see if the + * destination address of this packet + * matches one in our list. + */ + for (i = 0; i < adapter->multicast_addr_count; + i++) { + if (buf[0] == + adapter->multicast_list[i][0] + && buf[1] == + adapter->multicast_list[i][1] + && buf[2] == + adapter->multicast_list[i][2] + && buf[3] == + adapter->multicast_list[i][3] + && buf[4] == + adapter->multicast_list[i][4] + && buf[5] == + adapter->multicast_list[i][5]) { + break; + } + } + + /* If our index is equal to the number + * of Multicast address we have, then + * this means we did not find this + * packet's matching address in our + * list. Set the len to zero, + * so we free our RFD when we return + * from this function. + */ + if (i == adapter->multicast_addr_count) + len = 0; + } + + if (len > 0) + adapter->stats.multicast_pkts_rcvd++; + } else if (word0 & ALCATEL_BROADCAST_PKT) + adapter->stats.broadcast_pkts_rcvd++; + else + /* Not sure what this counter measures in + * promiscuous mode. Perhaps we should check + * the MAC address to see if it is directed + * to us in promiscuous mode. + */ + adapter->stats.unicast_pkts_rcvd++; + } + + if (len > 0) { + struct sk_buff *skb = NULL; + + /*rfd->len = len - 4; */ + rfd->len = len; + + skb = dev_alloc_skb(rfd->len + 2); + if (!skb) { + dev_err(&adapter->pdev->dev, + "Couldn't alloc an SKB for Rx\n"); + return NULL; + } + + adapter->net_stats.rx_bytes += rfd->len; + + /* + * Note - ring_index for fbr[] array is reversed, + * 1 for FBR0 etc + */ + memcpy(skb_put(skb, rfd->len), + rx_local->fbr[(ring_index == 0 ? 1 : 0)]->virt[buff_index], + rfd->len); + + skb->dev = adapter->netdev; + skb->protocol = eth_type_trans(skb, adapter->netdev); + skb->ip_summed = CHECKSUM_NONE; + + netif_rx_ni(skb); + } else { + rfd->len = 0; + } + + nic_return_rfd(adapter, rfd); + return rfd; +} + +/** + * et131x_handle_recv_interrupt - Interrupt handler for receive processing + * @adapter: pointer to our adapter + * + * Assumption, Rcv spinlock has been acquired. + */ +static void et131x_handle_recv_interrupt(struct et131x_adapter *adapter) +{ + struct rfd *rfd = NULL; + u32 count = 0; + bool done = true; + + /* Process up to available RFD's */ + while (count < NUM_PACKETS_HANDLED) { + if (list_empty(&adapter->rx_ring.recv_list)) { + WARN_ON(adapter->rx_ring.num_ready_recv != 0); + done = false; + break; + } + + rfd = nic_rx_pkts(adapter); + + if (rfd == NULL) + break; + + /* Do not receive any packets until a filter has been set. + * Do not receive any packets until we have link. + * If length is zero, return the RFD in order to advance the + * Free buffer ring. + */ + if (!adapter->packet_filter || + !netif_carrier_ok(adapter->netdev) || + rfd->len == 0) + continue; + + /* Increment the number of packets we received */ + adapter->net_stats.rx_packets++; + + /* Set the status on the packet, either resources or success */ + if (adapter->rx_ring.num_ready_recv < RFD_LOW_WATER_MARK) { + dev_warn(&adapter->pdev->dev, + "RFD's are running out\n"); + } + count++; + } + + if (count == NUM_PACKETS_HANDLED || !done) { + adapter->rx_ring.unfinished_receives = true; + writel(PARM_TX_TIME_INT_DEF * NANO_IN_A_MICRO, + &adapter->regs->global.watchdog_timer); + } else + /* Watchdog timer will disable itself if appropriate. */ + adapter->rx_ring.unfinished_receives = false; +} + +/** + * et131x_tx_dma_memory_alloc + * @adapter: pointer to our private adapter structure + * + * Returns 0 on success and errno on failure (as defined in errno.h). + * + * Allocates memory that will be visible both to the device and to the CPU. + * The OS will pass us packets, pointers to which we will insert in the Tx + * Descriptor queue. The device will read this queue to find the packets in + * memory. The device will update the "status" in memory each time it xmits a + * packet. + */ +static int et131x_tx_dma_memory_alloc(struct et131x_adapter *adapter) +{ + int desc_size = 0; + struct tx_ring *tx_ring = &adapter->tx_ring; + + /* Allocate memory for the TCB's (Transmit Control Block) */ + adapter->tx_ring.tcb_ring = + kcalloc(NUM_TCB, sizeof(struct tcb), GFP_ATOMIC | GFP_DMA); + if (!adapter->tx_ring.tcb_ring) { + dev_err(&adapter->pdev->dev, "Cannot alloc memory for TCBs\n"); + return -ENOMEM; + } + + /* Allocate enough memory for the Tx descriptor ring, and allocate + * some extra so that the ring can be aligned on a 4k boundary. + */ + desc_size = (sizeof(struct tx_desc) * NUM_DESC_PER_RING_TX) + 4096 - 1; + tx_ring->tx_desc_ring = + (struct tx_desc *) dma_alloc_coherent(&adapter->pdev->dev, + desc_size, + &tx_ring->tx_desc_ring_pa, + GFP_KERNEL); + if (!adapter->tx_ring.tx_desc_ring) { + dev_err(&adapter->pdev->dev, + "Cannot alloc memory for Tx Ring\n"); + return -ENOMEM; + } + + /* Save physical address + * + * NOTE: dma_alloc_coherent(), used above to alloc DMA regions, + * ALWAYS returns SAC (32-bit) addresses. If DAC (64-bit) addresses + * are ever returned, make sure the high part is retrieved here before + * storing the adjusted address. + */ + /* Allocate memory for the Tx status block */ + tx_ring->tx_status = dma_alloc_coherent(&adapter->pdev->dev, + sizeof(u32), + &tx_ring->tx_status_pa, + GFP_KERNEL); + if (!adapter->tx_ring.tx_status_pa) { + dev_err(&adapter->pdev->dev, + "Cannot alloc memory for Tx status block\n"); + return -ENOMEM; + } + return 0; +} + +/** + * et131x_tx_dma_memory_free - Free all memory allocated within this module + * @adapter: pointer to our private adapter structure + * + * Returns 0 on success and errno on failure (as defined in errno.h). + */ +static void et131x_tx_dma_memory_free(struct et131x_adapter *adapter) +{ + int desc_size = 0; + + if (adapter->tx_ring.tx_desc_ring) { + /* Free memory relating to Tx rings here */ + desc_size = (sizeof(struct tx_desc) * NUM_DESC_PER_RING_TX) + + 4096 - 1; + dma_free_coherent(&adapter->pdev->dev, + desc_size, + adapter->tx_ring.tx_desc_ring, + adapter->tx_ring.tx_desc_ring_pa); + adapter->tx_ring.tx_desc_ring = NULL; + } + + /* Free memory for the Tx status block */ + if (adapter->tx_ring.tx_status) { + dma_free_coherent(&adapter->pdev->dev, + sizeof(u32), + adapter->tx_ring.tx_status, + adapter->tx_ring.tx_status_pa); + + adapter->tx_ring.tx_status = NULL; + } + /* Free the memory for the tcb structures */ + kfree(adapter->tx_ring.tcb_ring); +} + +/** + * nic_send_packet - NIC specific send handler for version B silicon. + * @adapter: pointer to our adapter + * @tcb: pointer to struct tcb + * + * Returns 0 or errno. + */ +static int nic_send_packet(struct et131x_adapter *adapter, struct tcb *tcb) +{ + u32 i; + struct tx_desc desc[24]; /* 24 x 16 byte */ + u32 frag = 0; + u32 thiscopy, remainder; + struct sk_buff *skb = tcb->skb; + u32 nr_frags = skb_shinfo(skb)->nr_frags + 1; + struct skb_frag_struct *frags = &skb_shinfo(skb)->frags[0]; + unsigned long flags; + struct phy_device *phydev = adapter->phydev; + + /* Part of the optimizations of this send routine restrict us to + * sending 24 fragments at a pass. In practice we should never see + * more than 5 fragments. + * + * NOTE: The older version of this function (below) can handle any + * number of fragments. If needed, we can call this function, + * although it is less efficient. + */ + if (nr_frags > 23) + return -EIO; + + memset(desc, 0, sizeof(struct tx_desc) * (nr_frags + 1)); + + for (i = 0; i < nr_frags; i++) { + /* If there is something in this element, lets get a + * descriptor from the ring and get the necessary data + */ + if (i == 0) { + /* If the fragments are smaller than a standard MTU, + * then map them to a single descriptor in the Tx + * Desc ring. However, if they're larger, as is + * possible with support for jumbo packets, then + * split them each across 2 descriptors. + * + * This will work until we determine why the hardware + * doesn't seem to like large fragments. + */ + if ((skb->len - skb->data_len) <= 1514) { + desc[frag].addr_hi = 0; + /* Low 16bits are length, high is vlan and + unused currently so zero */ + desc[frag].len_vlan = + skb->len - skb->data_len; + + /* NOTE: Here, the dma_addr_t returned from + * dma_map_single() is implicitly cast as a + * u32. Although dma_addr_t can be + * 64-bit, the address returned by + * dma_map_single() is always 32-bit + * addressable (as defined by the pci/dma + * subsystem) + */ + desc[frag++].addr_lo = + dma_map_single(&adapter->pdev->dev, + skb->data, + skb->len - + skb->data_len, + DMA_TO_DEVICE); + } else { + desc[frag].addr_hi = 0; + desc[frag].len_vlan = + (skb->len - skb->data_len) / 2; + + /* NOTE: Here, the dma_addr_t returned from + * dma_map_single() is implicitly cast as a + * u32. Although dma_addr_t can be + * 64-bit, the address returned by + * dma_map_single() is always 32-bit + * addressable (as defined by the pci/dma + * subsystem) + */ + desc[frag++].addr_lo = + dma_map_single(&adapter->pdev->dev, + skb->data, + ((skb->len - + skb->data_len) / 2), + DMA_TO_DEVICE); + desc[frag].addr_hi = 0; + + desc[frag].len_vlan = + (skb->len - skb->data_len) / 2; + + /* NOTE: Here, the dma_addr_t returned from + * dma_map_single() is implicitly cast as a + * u32. Although dma_addr_t can be + * 64-bit, the address returned by + * dma_map_single() is always 32-bit + * addressable (as defined by the pci/dma + * subsystem) + */ + desc[frag++].addr_lo = + dma_map_single(&adapter->pdev->dev, + skb->data + + ((skb->len - + skb->data_len) / 2), + ((skb->len - + skb->data_len) / 2), + DMA_TO_DEVICE); + } + } else { + desc[frag].addr_hi = 0; + desc[frag].len_vlan = + frags[i - 1].size; + + /* NOTE: Here, the dma_addr_t returned from + * dma_map_page() is implicitly cast as a u32. + * Although dma_addr_t can be 64-bit, the address + * returned by dma_map_page() is always 32-bit + * addressable (as defined by the pci/dma subsystem) + */ + desc[frag++].addr_lo = skb_frag_dma_map( + &adapter->pdev->dev, + &frags[i - 1], + 0, + frags[i - 1].size, + DMA_TO_DEVICE); + } + } + + if (phydev && phydev->speed == SPEED_1000) { + if (++adapter->tx_ring.since_irq == PARM_TX_NUM_BUFS_DEF) { + /* Last element & Interrupt flag */ + desc[frag - 1].flags = 0x5; + adapter->tx_ring.since_irq = 0; + } else { /* Last element */ + desc[frag - 1].flags = 0x1; + } + } else + desc[frag - 1].flags = 0x5; + + desc[0].flags |= 2; /* First element flag */ + + tcb->index_start = adapter->tx_ring.send_idx; + tcb->stale = 0; + + spin_lock_irqsave(&adapter->send_hw_lock, flags); + + thiscopy = NUM_DESC_PER_RING_TX - + INDEX10(adapter->tx_ring.send_idx); + + if (thiscopy >= frag) { + remainder = 0; + thiscopy = frag; + } else { + remainder = frag - thiscopy; + } + + memcpy(adapter->tx_ring.tx_desc_ring + + INDEX10(adapter->tx_ring.send_idx), desc, + sizeof(struct tx_desc) * thiscopy); + + add_10bit(&adapter->tx_ring.send_idx, thiscopy); + + if (INDEX10(adapter->tx_ring.send_idx) == 0 || + INDEX10(adapter->tx_ring.send_idx) == NUM_DESC_PER_RING_TX) { + adapter->tx_ring.send_idx &= ~ET_DMA10_MASK; + adapter->tx_ring.send_idx ^= ET_DMA10_WRAP; + } + + if (remainder) { + memcpy(adapter->tx_ring.tx_desc_ring, + desc + thiscopy, + sizeof(struct tx_desc) * remainder); + + add_10bit(&adapter->tx_ring.send_idx, remainder); + } + + if (INDEX10(adapter->tx_ring.send_idx) == 0) { + if (adapter->tx_ring.send_idx) + tcb->index = NUM_DESC_PER_RING_TX - 1; + else + tcb->index = ET_DMA10_WRAP|(NUM_DESC_PER_RING_TX - 1); + } else + tcb->index = adapter->tx_ring.send_idx - 1; + + spin_lock(&adapter->tcb_send_qlock); + + if (adapter->tx_ring.send_tail) + adapter->tx_ring.send_tail->next = tcb; + else + adapter->tx_ring.send_head = tcb; + + adapter->tx_ring.send_tail = tcb; + + WARN_ON(tcb->next != NULL); + + adapter->tx_ring.used++; + + spin_unlock(&adapter->tcb_send_qlock); + + /* Write the new write pointer back to the device. */ + writel(adapter->tx_ring.send_idx, + &adapter->regs->txdma.service_request); + + /* For Gig only, we use Tx Interrupt coalescing. Enable the software + * timer to wake us up if this packet isn't followed by N more. + */ + if (phydev && phydev->speed == SPEED_1000) { + writel(PARM_TX_TIME_INT_DEF * NANO_IN_A_MICRO, + &adapter->regs->global.watchdog_timer); + } + spin_unlock_irqrestore(&adapter->send_hw_lock, flags); + + return 0; +} + +/** + * send_packet - Do the work to send a packet + * @skb: the packet(s) to send + * @adapter: a pointer to the device's private adapter structure + * + * Return 0 in almost all cases; non-zero value in extreme hard failure only. + * + * Assumption: Send spinlock has been acquired + */ +static int send_packet(struct sk_buff *skb, struct et131x_adapter *adapter) +{ + int status; + struct tcb *tcb = NULL; + u16 *shbufva; + unsigned long flags; + + /* All packets must have at least a MAC address and a protocol type */ + if (skb->len < ETH_HLEN) + return -EIO; + + /* Get a TCB for this packet */ + spin_lock_irqsave(&adapter->tcb_ready_qlock, flags); + + tcb = adapter->tx_ring.tcb_qhead; + + if (tcb == NULL) { + spin_unlock_irqrestore(&adapter->tcb_ready_qlock, flags); + return -ENOMEM; + } + + adapter->tx_ring.tcb_qhead = tcb->next; + + if (adapter->tx_ring.tcb_qhead == NULL) + adapter->tx_ring.tcb_qtail = NULL; + + spin_unlock_irqrestore(&adapter->tcb_ready_qlock, flags); + + tcb->skb = skb; + + if (skb->data != NULL && skb->len - skb->data_len >= 6) { + shbufva = (u16 *) skb->data; + + if ((shbufva[0] == 0xffff) && + (shbufva[1] == 0xffff) && (shbufva[2] == 0xffff)) { + tcb->flags |= fMP_DEST_BROAD; + } else if ((shbufva[0] & 0x3) == 0x0001) { + tcb->flags |= fMP_DEST_MULTI; + } + } + + tcb->next = NULL; + + /* Call the NIC specific send handler. */ + status = nic_send_packet(adapter, tcb); + + if (status != 0) { + spin_lock_irqsave(&adapter->tcb_ready_qlock, flags); + + if (adapter->tx_ring.tcb_qtail) + adapter->tx_ring.tcb_qtail->next = tcb; + else + /* Apparently ready Q is empty. */ + adapter->tx_ring.tcb_qhead = tcb; + + adapter->tx_ring.tcb_qtail = tcb; + spin_unlock_irqrestore(&adapter->tcb_ready_qlock, flags); + return status; + } + WARN_ON(adapter->tx_ring.used > NUM_TCB); + return 0; +} + +/** + * et131x_send_packets - This function is called by the OS to send packets + * @skb: the packet(s) to send + * @netdev:device on which to TX the above packet(s) + * + * Return 0 in almost all cases; non-zero value in extreme hard failure only + */ +static int et131x_send_packets(struct sk_buff *skb, struct net_device *netdev) +{ + int status = 0; + struct et131x_adapter *adapter = netdev_priv(netdev); + + /* Send these packets + * + * NOTE: The Linux Tx entry point is only given one packet at a time + * to Tx, so the PacketCount and it's array used makes no sense here + */ + + /* TCB is not available */ + if (adapter->tx_ring.used >= NUM_TCB) { + /* NOTE: If there's an error on send, no need to queue the + * packet under Linux; if we just send an error up to the + * netif layer, it will resend the skb to us. + */ + status = -ENOMEM; + } else { + /* We need to see if the link is up; if it's not, make the + * netif layer think we're good and drop the packet + */ + if ((adapter->flags & fMP_ADAPTER_FAIL_SEND_MASK) || + !netif_carrier_ok(netdev)) { + dev_kfree_skb_any(skb); + skb = NULL; + + adapter->net_stats.tx_dropped++; + } else { + status = send_packet(skb, adapter); + if (status != 0 && status != -ENOMEM) { + /* On any other error, make netif think we're + * OK and drop the packet + */ + dev_kfree_skb_any(skb); + skb = NULL; + adapter->net_stats.tx_dropped++; + } + } + } + return status; +} + +/** + * free_send_packet - Recycle a struct tcb + * @adapter: pointer to our adapter + * @tcb: pointer to struct tcb + * + * Complete the packet if necessary + * Assumption - Send spinlock has been acquired + */ +static inline void free_send_packet(struct et131x_adapter *adapter, + struct tcb *tcb) +{ + unsigned long flags; + struct tx_desc *desc = NULL; + struct net_device_stats *stats = &adapter->net_stats; + + if (tcb->flags & fMP_DEST_BROAD) + atomic_inc(&adapter->stats.broadcast_pkts_xmtd); + else if (tcb->flags & fMP_DEST_MULTI) + atomic_inc(&adapter->stats.multicast_pkts_xmtd); + else + atomic_inc(&adapter->stats.unicast_pkts_xmtd); + + if (tcb->skb) { + stats->tx_bytes += tcb->skb->len; + + /* Iterate through the TX descriptors on the ring + * corresponding to this packet and umap the fragments + * they point to + */ + do { + desc = (struct tx_desc *) + (adapter->tx_ring.tx_desc_ring + + INDEX10(tcb->index_start)); + + dma_unmap_single(&adapter->pdev->dev, + desc->addr_lo, + desc->len_vlan, DMA_TO_DEVICE); + + add_10bit(&tcb->index_start, 1); + if (INDEX10(tcb->index_start) >= + NUM_DESC_PER_RING_TX) { + tcb->index_start &= ~ET_DMA10_MASK; + tcb->index_start ^= ET_DMA10_WRAP; + } + } while (desc != (adapter->tx_ring.tx_desc_ring + + INDEX10(tcb->index))); + + dev_kfree_skb_any(tcb->skb); + } + + memset(tcb, 0, sizeof(struct tcb)); + + /* Add the TCB to the Ready Q */ + spin_lock_irqsave(&adapter->tcb_ready_qlock, flags); + + adapter->net_stats.tx_packets++; + + if (adapter->tx_ring.tcb_qtail) + adapter->tx_ring.tcb_qtail->next = tcb; + else + /* Apparently ready Q is empty. */ + adapter->tx_ring.tcb_qhead = tcb; + + adapter->tx_ring.tcb_qtail = tcb; + + spin_unlock_irqrestore(&adapter->tcb_ready_qlock, flags); + WARN_ON(adapter->tx_ring.used < 0); +} + +/** + * et131x_free_busy_send_packets - Free and complete the stopped active sends + * @adapter: pointer to our adapter + * + * Assumption - Send spinlock has been acquired + */ +static void et131x_free_busy_send_packets(struct et131x_adapter *adapter) +{ + struct tcb *tcb; + unsigned long flags; + u32 freed = 0; + + /* Any packets being sent? Check the first TCB on the send list */ + spin_lock_irqsave(&adapter->tcb_send_qlock, flags); + + tcb = adapter->tx_ring.send_head; + + while (tcb != NULL && freed < NUM_TCB) { + struct tcb *next = tcb->next; + + adapter->tx_ring.send_head = next; + + if (next == NULL) + adapter->tx_ring.send_tail = NULL; + + adapter->tx_ring.used--; + + spin_unlock_irqrestore(&adapter->tcb_send_qlock, flags); + + freed++; + free_send_packet(adapter, tcb); + + spin_lock_irqsave(&adapter->tcb_send_qlock, flags); + + tcb = adapter->tx_ring.send_head; + } + + WARN_ON(freed == NUM_TCB); + + spin_unlock_irqrestore(&adapter->tcb_send_qlock, flags); + + adapter->tx_ring.used = 0; +} + +/** + * et131x_handle_send_interrupt - Interrupt handler for sending processing + * @adapter: pointer to our adapter + * + * Re-claim the send resources, complete sends and get more to send from + * the send wait queue. + * + * Assumption - Send spinlock has been acquired + */ +static void et131x_handle_send_interrupt(struct et131x_adapter *adapter) +{ + unsigned long flags; + u32 serviced; + struct tcb *tcb; + u32 index; + + serviced = readl(&adapter->regs->txdma.new_service_complete); + index = INDEX10(serviced); + + /* Has the ring wrapped? Process any descriptors that do not have + * the same "wrap" indicator as the current completion indicator + */ + spin_lock_irqsave(&adapter->tcb_send_qlock, flags); + + tcb = adapter->tx_ring.send_head; + + while (tcb && + ((serviced ^ tcb->index) & ET_DMA10_WRAP) && + index < INDEX10(tcb->index)) { + adapter->tx_ring.used--; + adapter->tx_ring.send_head = tcb->next; + if (tcb->next == NULL) + adapter->tx_ring.send_tail = NULL; + + spin_unlock_irqrestore(&adapter->tcb_send_qlock, flags); + free_send_packet(adapter, tcb); + spin_lock_irqsave(&adapter->tcb_send_qlock, flags); + + /* Goto the next packet */ + tcb = adapter->tx_ring.send_head; + } + while (tcb && + !((serviced ^ tcb->index) & ET_DMA10_WRAP) + && index > (tcb->index & ET_DMA10_MASK)) { + adapter->tx_ring.used--; + adapter->tx_ring.send_head = tcb->next; + if (tcb->next == NULL) + adapter->tx_ring.send_tail = NULL; + + spin_unlock_irqrestore(&adapter->tcb_send_qlock, flags); + free_send_packet(adapter, tcb); + spin_lock_irqsave(&adapter->tcb_send_qlock, flags); + + /* Goto the next packet */ + tcb = adapter->tx_ring.send_head; + } + + /* Wake up the queue when we hit a low-water mark */ + if (adapter->tx_ring.used <= NUM_TCB / 3) + netif_wake_queue(adapter->netdev); + + spin_unlock_irqrestore(&adapter->tcb_send_qlock, flags); +} + +static int et131x_get_settings(struct net_device *netdev, + struct ethtool_cmd *cmd) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + + return phy_ethtool_gset(adapter->phydev, cmd); +} + +static int et131x_set_settings(struct net_device *netdev, + struct ethtool_cmd *cmd) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + + return phy_ethtool_sset(adapter->phydev, cmd); +} + +static int et131x_get_regs_len(struct net_device *netdev) +{ +#define ET131X_REGS_LEN 256 + return ET131X_REGS_LEN * sizeof(u32); +} + +static void et131x_get_regs(struct net_device *netdev, + struct ethtool_regs *regs, void *regs_data) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + struct address_map __iomem *aregs = adapter->regs; + u32 *regs_buff = regs_data; + u32 num = 0; + + memset(regs_data, 0, et131x_get_regs_len(netdev)); + + regs->version = (1 << 24) | (adapter->pdev->revision << 16) | + adapter->pdev->device; + + /* PHY regs */ + et131x_mii_read(adapter, MII_BMCR, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, MII_BMSR, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, MII_PHYSID1, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, MII_PHYSID2, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, MII_ADVERTISE, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, MII_LPA, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, MII_EXPANSION, (u16 *)®s_buff[num++]); + /* Autoneg next page transmit reg */ + et131x_mii_read(adapter, 0x07, (u16 *)®s_buff[num++]); + /* Link partner next page reg */ + et131x_mii_read(adapter, 0x08, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, MII_CTRL1000, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, MII_STAT1000, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, MII_ESTATUS, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_INDEX_REG, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_DATA_REG, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_MPHY_CONTROL_REG, + (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_LOOPBACK_CONTROL, + (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_LOOPBACK_CONTROL+1, + (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_REGISTER_MGMT_CONTROL, + (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_CONFIG, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_PHY_CONTROL, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_INTERRUPT_MASK, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_INTERRUPT_STATUS, + (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_PHY_STATUS, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_LED_1, (u16 *)®s_buff[num++]); + et131x_mii_read(adapter, PHY_LED_2, (u16 *)®s_buff[num++]); + + /* Global regs */ + regs_buff[num++] = readl(&aregs->global.txq_start_addr); + regs_buff[num++] = readl(&aregs->global.txq_end_addr); + regs_buff[num++] = readl(&aregs->global.rxq_start_addr); + regs_buff[num++] = readl(&aregs->global.rxq_end_addr); + regs_buff[num++] = readl(&aregs->global.pm_csr); + regs_buff[num++] = adapter->stats.interrupt_status; + regs_buff[num++] = readl(&aregs->global.int_mask); + regs_buff[num++] = readl(&aregs->global.int_alias_clr_en); + regs_buff[num++] = readl(&aregs->global.int_status_alias); + regs_buff[num++] = readl(&aregs->global.sw_reset); + regs_buff[num++] = readl(&aregs->global.slv_timer); + regs_buff[num++] = readl(&aregs->global.msi_config); + regs_buff[num++] = readl(&aregs->global.loopback); + regs_buff[num++] = readl(&aregs->global.watchdog_timer); + + /* TXDMA regs */ + regs_buff[num++] = readl(&aregs->txdma.csr); + regs_buff[num++] = readl(&aregs->txdma.pr_base_hi); + regs_buff[num++] = readl(&aregs->txdma.pr_base_lo); + regs_buff[num++] = readl(&aregs->txdma.pr_num_des); + regs_buff[num++] = readl(&aregs->txdma.txq_wr_addr); + regs_buff[num++] = readl(&aregs->txdma.txq_wr_addr_ext); + regs_buff[num++] = readl(&aregs->txdma.txq_rd_addr); + regs_buff[num++] = readl(&aregs->txdma.dma_wb_base_hi); + regs_buff[num++] = readl(&aregs->txdma.dma_wb_base_lo); + regs_buff[num++] = readl(&aregs->txdma.service_request); + regs_buff[num++] = readl(&aregs->txdma.service_complete); + regs_buff[num++] = readl(&aregs->txdma.cache_rd_index); + regs_buff[num++] = readl(&aregs->txdma.cache_wr_index); + regs_buff[num++] = readl(&aregs->txdma.tx_dma_error); + regs_buff[num++] = readl(&aregs->txdma.desc_abort_cnt); + regs_buff[num++] = readl(&aregs->txdma.payload_abort_cnt); + regs_buff[num++] = readl(&aregs->txdma.writeback_abort_cnt); + regs_buff[num++] = readl(&aregs->txdma.desc_timeout_cnt); + regs_buff[num++] = readl(&aregs->txdma.payload_timeout_cnt); + regs_buff[num++] = readl(&aregs->txdma.writeback_timeout_cnt); + regs_buff[num++] = readl(&aregs->txdma.desc_error_cnt); + regs_buff[num++] = readl(&aregs->txdma.payload_error_cnt); + regs_buff[num++] = readl(&aregs->txdma.writeback_error_cnt); + regs_buff[num++] = readl(&aregs->txdma.dropped_tlp_cnt); + regs_buff[num++] = readl(&aregs->txdma.new_service_complete); + regs_buff[num++] = readl(&aregs->txdma.ethernet_packet_cnt); + + /* RXDMA regs */ + regs_buff[num++] = readl(&aregs->rxdma.csr); + regs_buff[num++] = readl(&aregs->rxdma.dma_wb_base_hi); + regs_buff[num++] = readl(&aregs->rxdma.dma_wb_base_lo); + regs_buff[num++] = readl(&aregs->rxdma.num_pkt_done); + regs_buff[num++] = readl(&aregs->rxdma.max_pkt_time); + regs_buff[num++] = readl(&aregs->rxdma.rxq_rd_addr); + regs_buff[num++] = readl(&aregs->rxdma.rxq_rd_addr_ext); + regs_buff[num++] = readl(&aregs->rxdma.rxq_wr_addr); + regs_buff[num++] = readl(&aregs->rxdma.psr_base_hi); + regs_buff[num++] = readl(&aregs->rxdma.psr_base_lo); + regs_buff[num++] = readl(&aregs->rxdma.psr_num_des); + regs_buff[num++] = readl(&aregs->rxdma.psr_avail_offset); + regs_buff[num++] = readl(&aregs->rxdma.psr_full_offset); + regs_buff[num++] = readl(&aregs->rxdma.psr_access_index); + regs_buff[num++] = readl(&aregs->rxdma.psr_min_des); + regs_buff[num++] = readl(&aregs->rxdma.fbr0_base_lo); + regs_buff[num++] = readl(&aregs->rxdma.fbr0_base_hi); + regs_buff[num++] = readl(&aregs->rxdma.fbr0_num_des); + regs_buff[num++] = readl(&aregs->rxdma.fbr0_avail_offset); + regs_buff[num++] = readl(&aregs->rxdma.fbr0_full_offset); + regs_buff[num++] = readl(&aregs->rxdma.fbr0_rd_index); + regs_buff[num++] = readl(&aregs->rxdma.fbr0_min_des); + regs_buff[num++] = readl(&aregs->rxdma.fbr1_base_lo); + regs_buff[num++] = readl(&aregs->rxdma.fbr1_base_hi); + regs_buff[num++] = readl(&aregs->rxdma.fbr1_num_des); + regs_buff[num++] = readl(&aregs->rxdma.fbr1_avail_offset); + regs_buff[num++] = readl(&aregs->rxdma.fbr1_full_offset); + regs_buff[num++] = readl(&aregs->rxdma.fbr1_rd_index); + regs_buff[num++] = readl(&aregs->rxdma.fbr1_min_des); +} + +#define ET131X_DRVINFO_LEN 32 /* value from ethtool.h */ +static void et131x_get_drvinfo(struct net_device *netdev, + struct ethtool_drvinfo *info) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + + strncpy(info->driver, DRIVER_NAME, ET131X_DRVINFO_LEN); + strncpy(info->version, DRIVER_VERSION, ET131X_DRVINFO_LEN); + strncpy(info->bus_info, pci_name(adapter->pdev), ET131X_DRVINFO_LEN); +} + +static struct ethtool_ops et131x_ethtool_ops = { + .get_settings = et131x_get_settings, + .set_settings = et131x_set_settings, + .get_drvinfo = et131x_get_drvinfo, + .get_regs_len = et131x_get_regs_len, + .get_regs = et131x_get_regs, + .get_link = ethtool_op_get_link, +}; + +static void et131x_set_ethtool_ops(struct net_device *netdev) +{ + SET_ETHTOOL_OPS(netdev, &et131x_ethtool_ops); +} + +/** + * et131x_hwaddr_init - set up the MAC Address on the ET1310 + * @adapter: pointer to our private adapter structure + */ +static void et131x_hwaddr_init(struct et131x_adapter *adapter) +{ + /* If have our default mac from init and no mac address from + * EEPROM then we need to generate the last octet and set it on the + * device + */ + if (adapter->rom_addr[0] == 0x00 && + adapter->rom_addr[1] == 0x00 && + adapter->rom_addr[2] == 0x00 && + adapter->rom_addr[3] == 0x00 && + adapter->rom_addr[4] == 0x00 && + adapter->rom_addr[5] == 0x00) { + /* + * We need to randomly generate the last octet so we + * decrease our chances of setting the mac address to + * same as another one of our cards in the system + */ + get_random_bytes(&adapter->addr[5], 1); + /* + * We have the default value in the register we are + * working with so we need to copy the current + * address into the permanent address + */ + memcpy(adapter->rom_addr, + adapter->addr, ETH_ALEN); + } else { + /* We do not have an override address, so set the + * current address to the permanent address and add + * it to the device + */ + memcpy(adapter->addr, + adapter->rom_addr, ETH_ALEN); + } +} + +/** + * et131x_pci_init - initial PCI setup + * @adapter: pointer to our private adapter structure + * @pdev: our PCI device + * + * Perform the initial setup of PCI registers and if possible initialise + * the MAC address. At this point the I/O registers have yet to be mapped + */ +static int et131x_pci_init(struct et131x_adapter *adapter, + struct pci_dev *pdev) +{ + int cap = pci_pcie_cap(pdev); + u16 max_payload; + u16 ctl; + int i, rc; + + rc = et131x_init_eeprom(adapter); + if (rc < 0) + goto out; + + if (!cap) { + dev_err(&pdev->dev, "Missing PCIe capabilities\n"); + goto err_out; + } + + /* Let's set up the PORT LOGIC Register. First we need to know what + * the max_payload_size is + */ + if (pci_read_config_word(pdev, cap + PCI_EXP_DEVCAP, &max_payload)) { + dev_err(&pdev->dev, + "Could not read PCI config space for Max Payload Size\n"); + goto err_out; + } + + /* Program the Ack/Nak latency and replay timers */ + max_payload &= 0x07; + + if (max_payload < 2) { + static const u16 acknak[2] = { 0x76, 0xD0 }; + static const u16 replay[2] = { 0x1E0, 0x2ED }; + + if (pci_write_config_word(pdev, ET1310_PCI_ACK_NACK, + acknak[max_payload])) { + dev_err(&pdev->dev, + "Could not write PCI config space for ACK/NAK\n"); + goto err_out; + } + if (pci_write_config_word(pdev, ET1310_PCI_REPLAY, + replay[max_payload])) { + dev_err(&pdev->dev, + "Could not write PCI config space for Replay Timer\n"); + goto err_out; + } + } + + /* l0s and l1 latency timers. We are using default values. + * Representing 001 for L0s and 010 for L1 + */ + if (pci_write_config_byte(pdev, ET1310_PCI_L0L1LATENCY, 0x11)) { + dev_err(&pdev->dev, + "Could not write PCI config space for Latency Timers\n"); + goto err_out; + } + + /* Change the max read size to 2k */ + if (pci_read_config_word(pdev, cap + PCI_EXP_DEVCTL, &ctl)) { + dev_err(&pdev->dev, + "Could not read PCI config space for Max read size\n"); + goto err_out; + } + + ctl = (ctl & ~PCI_EXP_DEVCTL_READRQ) | ( 0x04 << 12); + + if (pci_write_config_word(pdev, cap + PCI_EXP_DEVCTL, ctl)) { + dev_err(&pdev->dev, + "Could not write PCI config space for Max read size\n"); + goto err_out; + } + + /* Get MAC address from config space if an eeprom exists, otherwise + * the MAC address there will not be valid + */ + if (!adapter->has_eeprom) { + et131x_hwaddr_init(adapter); + return 0; + } + + for (i = 0; i < ETH_ALEN; i++) { + if (pci_read_config_byte(pdev, ET1310_PCI_MAC_ADDRESS + i, + adapter->rom_addr + i)) { + dev_err(&pdev->dev, "Could not read PCI config space for MAC address\n"); + goto err_out; + } + } + memcpy(adapter->addr, adapter->rom_addr, ETH_ALEN); +out: + return rc; +err_out: + rc = -EIO; + goto out; +} + +/** + * et131x_error_timer_handler + * @data: timer-specific variable; here a pointer to our adapter structure + * + * The routine called when the error timer expires, to track the number of + * recurring errors. + */ +static void et131x_error_timer_handler(unsigned long data) +{ + struct et131x_adapter *adapter = (struct et131x_adapter *) data; + struct phy_device *phydev = adapter->phydev; + + if (et1310_in_phy_coma(adapter)) { + /* Bring the device immediately out of coma, to + * prevent it from sleeping indefinitely, this + * mechanism could be improved! */ + et1310_disable_phy_coma(adapter); + adapter->boot_coma = 20; + } else { + et1310_update_macstat_host_counters(adapter); + } + + if (!phydev->link && adapter->boot_coma < 11) + adapter->boot_coma++; + + if (adapter->boot_coma == 10) { + if (!phydev->link) { + if (!et1310_in_phy_coma(adapter)) { + /* NOTE - This was originally a 'sync with + * interrupt'. How to do that under Linux? + */ + et131x_enable_interrupts(adapter); + et1310_enable_phy_coma(adapter); + } + } + } + + /* This is a periodic timer, so reschedule */ + mod_timer(&adapter->error_timer, jiffies + + TX_ERROR_PERIOD * HZ / 1000); +} + +/** + * et131x_adapter_memory_alloc + * @adapter: pointer to our private adapter structure + * + * Returns 0 on success, errno on failure (as defined in errno.h). + * + * Allocate all the memory blocks for send, receive and others. + */ +static int et131x_adapter_memory_alloc(struct et131x_adapter *adapter) +{ + int status; + + /* Allocate memory for the Tx Ring */ + status = et131x_tx_dma_memory_alloc(adapter); + if (status != 0) { + dev_err(&adapter->pdev->dev, + "et131x_tx_dma_memory_alloc FAILED\n"); + return status; + } + /* Receive buffer memory allocation */ + status = et131x_rx_dma_memory_alloc(adapter); + if (status != 0) { + dev_err(&adapter->pdev->dev, + "et131x_rx_dma_memory_alloc FAILED\n"); + et131x_tx_dma_memory_free(adapter); + return status; + } + + /* Init receive data structures */ + status = et131x_init_recv(adapter); + if (status != 0) { + dev_err(&adapter->pdev->dev, + "et131x_init_recv FAILED\n"); + et131x_tx_dma_memory_free(adapter); + et131x_rx_dma_memory_free(adapter); + } + return status; +} + +/** + * et131x_adapter_memory_free - Free all memory allocated for use by Tx & Rx + * @adapter: pointer to our private adapter structure + */ +static void et131x_adapter_memory_free(struct et131x_adapter *adapter) +{ + /* Free DMA memory */ + et131x_tx_dma_memory_free(adapter); + et131x_rx_dma_memory_free(adapter); +} + +static void et131x_adjust_link(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + struct phy_device *phydev = adapter->phydev; + + if (netif_carrier_ok(netdev)) { + adapter->boot_coma = 20; + + if (phydev && phydev->speed == SPEED_10) { + /* + * NOTE - Is there a way to query this without + * TruePHY? + * && TRU_QueryCoreType(adapter->hTruePhy, 0)== + * EMI_TRUEPHY_A13O) { + */ + u16 register18; + + et131x_mii_read(adapter, PHY_MPHY_CONTROL_REG, + ®ister18); + et131x_mii_write(adapter, PHY_MPHY_CONTROL_REG, + register18 | 0x4); + et131x_mii_write(adapter, PHY_INDEX_REG, + register18 | 0x8402); + et131x_mii_write(adapter, PHY_DATA_REG, + register18 | 511); + et131x_mii_write(adapter, PHY_MPHY_CONTROL_REG, + register18); + } + + et1310_config_flow_control(adapter); + + if (phydev && phydev->speed == SPEED_1000 && + adapter->registry_jumbo_packet > 2048) { + u16 reg; + + et131x_mii_read(adapter, PHY_CONFIG, ®); + reg &= ~ET_PHY_CONFIG_TX_FIFO_DEPTH; + reg |= ET_PHY_CONFIG_FIFO_DEPTH_32; + et131x_mii_write(adapter, PHY_CONFIG, reg); + } + + et131x_set_rx_dma_timer(adapter); + et1310_config_mac_regs2(adapter); + } + + if (phydev && phydev->link != adapter->link) { + /* + * Check to see if we are in coma mode and if + * so, disable it because we will not be able + * to read PHY values until we are out. + */ + if (et1310_in_phy_coma(adapter)) + et1310_disable_phy_coma(adapter); + + if (phydev->link) { + adapter->boot_coma = 20; + } else { + dev_warn(&adapter->pdev->dev, + "Link down - cable problem ?\n"); + adapter->boot_coma = 0; + + if (phydev->speed == SPEED_10) { + /* NOTE - Is there a way to query this without + * TruePHY? + * && TRU_QueryCoreType(adapter->hTruePhy, 0) == + * EMI_TRUEPHY_A13O) + */ + u16 register18; + + et131x_mii_read(adapter, PHY_MPHY_CONTROL_REG, + ®ister18); + et131x_mii_write(adapter, PHY_MPHY_CONTROL_REG, + register18 | 0x4); + et131x_mii_write(adapter, PHY_INDEX_REG, + register18 | 0x8402); + et131x_mii_write(adapter, PHY_DATA_REG, + register18 | 511); + et131x_mii_write(adapter, PHY_MPHY_CONTROL_REG, + register18); + } + + /* Free the packets being actively sent & stopped */ + et131x_free_busy_send_packets(adapter); + + /* Re-initialize the send structures */ + et131x_init_send(adapter); + + /* + * Bring the device back to the state it was during + * init prior to autonegotiation being complete. This + * way, when we get the auto-neg complete interrupt, + * we can complete init by calling config_mac_regs2. + */ + et131x_soft_reset(adapter); + + /* Setup ET1310 as per the documentation */ + et131x_adapter_setup(adapter); + + /* perform reset of tx/rx */ + et131x_disable_txrx(netdev); + et131x_enable_txrx(netdev); + } + + adapter->link = phydev->link; + + phy_print_status(phydev); + } +} + +static int et131x_mii_probe(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + struct phy_device *phydev = NULL; + + phydev = phy_find_first(adapter->mii_bus); + if (!phydev) { + dev_err(&adapter->pdev->dev, "no PHY found\n"); + return -ENODEV; + } + + phydev = phy_connect(netdev, dev_name(&phydev->dev), + &et131x_adjust_link, 0, PHY_INTERFACE_MODE_MII); + + if (IS_ERR(phydev)) { + dev_err(&adapter->pdev->dev, "Could not attach to PHY\n"); + return PTR_ERR(phydev); + } + + phydev->supported &= (SUPPORTED_10baseT_Half + | SUPPORTED_10baseT_Full + | SUPPORTED_100baseT_Half + | SUPPORTED_100baseT_Full + | SUPPORTED_Autoneg + | SUPPORTED_MII + | SUPPORTED_TP); + + if (adapter->pdev->device != ET131X_PCI_DEVICE_ID_FAST) + phydev->supported |= SUPPORTED_1000baseT_Full; + + phydev->advertising = phydev->supported; + adapter->phydev = phydev; + + dev_info(&adapter->pdev->dev, "attached PHY driver [%s] " + "(mii_bus:phy_addr=%s)\n", + phydev->drv->name, dev_name(&phydev->dev)); + + return 0; +} + +/** + * et131x_adapter_init + * @adapter: pointer to the private adapter struct + * @pdev: pointer to the PCI device + * + * Initialize the data structures for the et131x_adapter object and link + * them together with the platform provided device structures. + */ +static struct et131x_adapter *et131x_adapter_init(struct net_device *netdev, + struct pci_dev *pdev) +{ + static const u8 default_mac[] = { 0x00, 0x05, 0x3d, 0x00, 0x02, 0x00 }; + + struct et131x_adapter *adapter; + + /* Allocate private adapter struct and copy in relevant information */ + adapter = netdev_priv(netdev); + adapter->pdev = pci_dev_get(pdev); + adapter->netdev = netdev; + + /* Initialize spinlocks here */ + spin_lock_init(&adapter->lock); + spin_lock_init(&adapter->tcb_send_qlock); + spin_lock_init(&adapter->tcb_ready_qlock); + spin_lock_init(&adapter->send_hw_lock); + spin_lock_init(&adapter->rcv_lock); + spin_lock_init(&adapter->rcv_pend_lock); + spin_lock_init(&adapter->fbr_lock); + spin_lock_init(&adapter->phy_lock); + + adapter->registry_jumbo_packet = 1514; /* 1514-9216 */ + + /* Set the MAC address to a default */ + memcpy(adapter->addr, default_mac, ETH_ALEN); + + return adapter; +} + +/** + * et131x_pci_remove + * @pdev: a pointer to the device's pci_dev structure + * + * Registered in the pci_driver structure, this function is called when the + * PCI subsystem detects that a PCI device which matches the information + * contained in the pci_device_id table has been removed. + */ +static void __devexit et131x_pci_remove(struct pci_dev *pdev) +{ + struct net_device *netdev = pci_get_drvdata(pdev); + struct et131x_adapter *adapter = netdev_priv(netdev); + + unregister_netdev(netdev); + phy_disconnect(adapter->phydev); + mdiobus_unregister(adapter->mii_bus); + kfree(adapter->mii_bus->irq); + mdiobus_free(adapter->mii_bus); + + et131x_adapter_memory_free(adapter); + iounmap(adapter->regs); + pci_dev_put(pdev); + + free_netdev(netdev); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +/** + * et131x_up - Bring up a device for use. + * @netdev: device to be opened + */ +static void et131x_up(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + + et131x_enable_txrx(netdev); + phy_start(adapter->phydev); +} + +/** + * et131x_down - Bring down the device + * @netdev: device to be brought down + */ +static void et131x_down(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + + /* Save the timestamp for the TX watchdog, prevent a timeout */ + netdev->trans_start = jiffies; + + phy_stop(adapter->phydev); + et131x_disable_txrx(netdev); +} + +#ifdef CONFIG_PM_SLEEP +static int et131x_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct net_device *netdev = pci_get_drvdata(pdev); + + if (netif_running(netdev)) { + netif_device_detach(netdev); + et131x_down(netdev); + pci_save_state(pdev); + } + + return 0; +} + +static int et131x_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct net_device *netdev = pci_get_drvdata(pdev); + + if (netif_running(netdev)) { + pci_restore_state(pdev); + et131x_up(netdev); + netif_device_attach(netdev); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(et131x_pm_ops, et131x_suspend, et131x_resume); +#define ET131X_PM_OPS (&et131x_pm_ops) +#else +#define ET131X_PM_OPS NULL +#endif + +/** + * et131x_isr - The Interrupt Service Routine for the driver. + * @irq: the IRQ on which the interrupt was received. + * @dev_id: device-specific info (here a pointer to a net_device struct) + * + * Returns a value indicating if the interrupt was handled. + */ +irqreturn_t et131x_isr(int irq, void *dev_id) +{ + bool handled = true; + struct net_device *netdev = (struct net_device *)dev_id; + struct et131x_adapter *adapter = NULL; + u32 status; + + if (!netif_device_present(netdev)) { + handled = false; + goto out; + } + + adapter = netdev_priv(netdev); + + /* If the adapter is in low power state, then it should not + * recognize any interrupt + */ + + /* Disable Device Interrupts */ + et131x_disable_interrupts(adapter); + + /* Get a copy of the value in the interrupt status register + * so we can process the interrupting section + */ + status = readl(&adapter->regs->global.int_status); + + if (adapter->flowcontrol == FLOW_TXONLY || + adapter->flowcontrol == FLOW_BOTH) { + status &= ~INT_MASK_ENABLE; + } else { + status &= ~INT_MASK_ENABLE_NO_FLOW; + } + + /* Make sure this is our interrupt */ + if (!status) { + handled = false; + et131x_enable_interrupts(adapter); + goto out; + } + + /* This is our interrupt, so process accordingly */ + + if (status & ET_INTR_WATCHDOG) { + struct tcb *tcb = adapter->tx_ring.send_head; + + if (tcb) + if (++tcb->stale > 1) + status |= ET_INTR_TXDMA_ISR; + + if (adapter->rx_ring.unfinished_receives) + status |= ET_INTR_RXDMA_XFR_DONE; + else if (tcb == NULL) + writel(0, &adapter->regs->global.watchdog_timer); + + status &= ~ET_INTR_WATCHDOG; + } + + if (status == 0) { + /* This interrupt has in some way been "handled" by + * the ISR. Either it was a spurious Rx interrupt, or + * it was a Tx interrupt that has been filtered by + * the ISR. + */ + et131x_enable_interrupts(adapter); + goto out; + } + + /* We need to save the interrupt status value for use in our + * DPC. We will clear the software copy of that in that + * routine. + */ + adapter->stats.interrupt_status = status; + + /* Schedule the ISR handler as a bottom-half task in the + * kernel's tq_immediate queue, and mark the queue for + * execution + */ + schedule_work(&adapter->task); +out: + return IRQ_RETVAL(handled); +} + +/** + * et131x_isr_handler - The ISR handler + * @p_adapter, a pointer to the device's private adapter structure + * + * scheduled to run in a deferred context by the ISR. This is where the ISR's + * work actually gets done. + */ +static void et131x_isr_handler(struct work_struct *work) +{ + struct et131x_adapter *adapter = + container_of(work, struct et131x_adapter, task); + u32 status = adapter->stats.interrupt_status; + struct address_map __iomem *iomem = adapter->regs; + + /* + * These first two are by far the most common. Once handled, we clear + * their two bits in the status word. If the word is now zero, we + * exit. + */ + /* Handle all the completed Transmit interrupts */ + if (status & ET_INTR_TXDMA_ISR) + et131x_handle_send_interrupt(adapter); + + /* Handle all the completed Receives interrupts */ + if (status & ET_INTR_RXDMA_XFR_DONE) + et131x_handle_recv_interrupt(adapter); + + status &= 0xffffffd7; + + if (status) { + /* Handle the TXDMA Error interrupt */ + if (status & ET_INTR_TXDMA_ERR) { + u32 txdma_err; + + /* Following read also clears the register (COR) */ + txdma_err = readl(&iomem->txdma.tx_dma_error); + + dev_warn(&adapter->pdev->dev, + "TXDMA_ERR interrupt, error = %d\n", + txdma_err); + } + + /* Handle Free Buffer Ring 0 and 1 Low interrupt */ + if (status & + (ET_INTR_RXDMA_FB_R0_LOW | ET_INTR_RXDMA_FB_R1_LOW)) { + /* + * This indicates the number of unused buffers in + * RXDMA free buffer ring 0 is <= the limit you + * programmed. Free buffer resources need to be + * returned. Free buffers are consumed as packets + * are passed from the network to the host. The host + * becomes aware of the packets from the contents of + * the packet status ring. This ring is queried when + * the packet done interrupt occurs. Packets are then + * passed to the OS. When the OS is done with the + * packets the resources can be returned to the + * ET1310 for re-use. This interrupt is one method of + * returning resources. + */ + + /* If the user has flow control on, then we will + * send a pause packet, otherwise just exit + */ + if (adapter->flowcontrol == FLOW_TXONLY || + adapter->flowcontrol == FLOW_BOTH) { + u32 pm_csr; + + /* Tell the device to send a pause packet via + * the back pressure register (bp req and + * bp xon/xoff) + */ + pm_csr = readl(&iomem->global.pm_csr); + if (!et1310_in_phy_coma(adapter)) + writel(3, &iomem->txmac.bp_ctrl); + } + } + + /* Handle Packet Status Ring Low Interrupt */ + if (status & ET_INTR_RXDMA_STAT_LOW) { + + /* + * Same idea as with the two Free Buffer Rings. + * Packets going from the network to the host each + * consume a free buffer resource and a packet status + * resource. These resoures are passed to the OS. + * When the OS is done with the resources, they need + * to be returned to the ET1310. This is one method + * of returning the resources. + */ + } + + /* Handle RXDMA Error Interrupt */ + if (status & ET_INTR_RXDMA_ERR) { + /* + * The rxdma_error interrupt is sent when a time-out + * on a request issued by the JAGCore has occurred or + * a completion is returned with an un-successful + * status. In both cases the request is considered + * complete. The JAGCore will automatically re-try the + * request in question. Normally information on events + * like these are sent to the host using the "Advanced + * Error Reporting" capability. This interrupt is + * another way of getting similar information. The + * only thing required is to clear the interrupt by + * reading the ISR in the global resources. The + * JAGCore will do a re-try on the request. Normally + * you should never see this interrupt. If you start + * to see this interrupt occurring frequently then + * something bad has occurred. A reset might be the + * thing to do. + */ + /* TRAP();*/ + + dev_warn(&adapter->pdev->dev, + "RxDMA_ERR interrupt, error %x\n", + readl(&iomem->txmac.tx_test)); + } + + /* Handle the Wake on LAN Event */ + if (status & ET_INTR_WOL) { + /* + * This is a secondary interrupt for wake on LAN. + * The driver should never see this, if it does, + * something serious is wrong. We will TRAP the + * message when we are in DBG mode, otherwise we + * will ignore it. + */ + dev_err(&adapter->pdev->dev, "WAKE_ON_LAN interrupt\n"); + } + + /* Let's move on to the TxMac */ + if (status & ET_INTR_TXMAC) { + u32 err = readl(&iomem->txmac.err); + + /* + * When any of the errors occur and TXMAC generates + * an interrupt to report these errors, it usually + * means that TXMAC has detected an error in the data + * stream retrieved from the on-chip Tx Q. All of + * these errors are catastrophic and TXMAC won't be + * able to recover data when these errors occur. In + * a nutshell, the whole Tx path will have to be reset + * and re-configured afterwards. + */ + dev_warn(&adapter->pdev->dev, + "TXMAC interrupt, error 0x%08x\n", + err); + + /* If we are debugging, we want to see this error, + * otherwise we just want the device to be reset and + * continue + */ + } + + /* Handle RXMAC Interrupt */ + if (status & ET_INTR_RXMAC) { + /* + * These interrupts are catastrophic to the device, + * what we need to do is disable the interrupts and + * set the flag to cause us to reset so we can solve + * this issue. + */ + /* MP_SET_FLAG( adapter, + fMP_ADAPTER_HARDWARE_ERROR); */ + + dev_warn(&adapter->pdev->dev, + "RXMAC interrupt, error 0x%08x. Requesting reset\n", + readl(&iomem->rxmac.err_reg)); + + dev_warn(&adapter->pdev->dev, + "Enable 0x%08x, Diag 0x%08x\n", + readl(&iomem->rxmac.ctrl), + readl(&iomem->rxmac.rxq_diag)); + + /* + * If we are debugging, we want to see this error, + * otherwise we just want the device to be reset and + * continue + */ + } + + /* Handle MAC_STAT Interrupt */ + if (status & ET_INTR_MAC_STAT) { + /* + * This means at least one of the un-masked counters + * in the MAC_STAT block has rolled over. Use this + * to maintain the top, software managed bits of the + * counter(s). + */ + et1310_handle_macstat_interrupt(adapter); + } + + /* Handle SLV Timeout Interrupt */ + if (status & ET_INTR_SLV_TIMEOUT) { + /* + * This means a timeout has occurred on a read or + * write request to one of the JAGCore registers. The + * Global Resources block has terminated the request + * and on a read request, returned a "fake" value. + * The most likely reasons are: Bad Address or the + * addressed module is in a power-down state and + * can't respond. + */ + } + } + et131x_enable_interrupts(adapter); +} + +/** + * et131x_stats - Return the current device statistics. + * @netdev: device whose stats are being queried + * + * Returns 0 on success, errno on failure (as defined in errno.h) + */ +static struct net_device_stats *et131x_stats(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + struct net_device_stats *stats = &adapter->net_stats; + struct ce_stats *devstat = &adapter->stats; + + stats->rx_errors = devstat->rx_length_errs + + devstat->rx_align_errs + + devstat->rx_crc_errs + + devstat->rx_code_violations + + devstat->rx_other_errs; + stats->tx_errors = devstat->tx_max_pkt_errs; + stats->multicast = devstat->multicast_pkts_rcvd; + stats->collisions = devstat->tx_collisions; + + stats->rx_length_errors = devstat->rx_length_errs; + stats->rx_over_errors = devstat->rx_overflows; + stats->rx_crc_errors = devstat->rx_crc_errs; + + /* NOTE: These stats don't have corresponding values in CE_STATS, + * so we're going to have to update these directly from within the + * TX/RX code + */ + /* stats->rx_bytes = 20; devstat->; */ + /* stats->tx_bytes = 20; devstat->; */ + /* stats->rx_dropped = devstat->; */ + /* stats->tx_dropped = devstat->; */ + + /* NOTE: Not used, can't find analogous statistics */ + /* stats->rx_frame_errors = devstat->; */ + /* stats->rx_fifo_errors = devstat->; */ + /* stats->rx_missed_errors = devstat->; */ + + /* stats->tx_aborted_errors = devstat->; */ + /* stats->tx_carrier_errors = devstat->; */ + /* stats->tx_fifo_errors = devstat->; */ + /* stats->tx_heartbeat_errors = devstat->; */ + /* stats->tx_window_errors = devstat->; */ + return stats; +} + +/** + * et131x_open - Open the device for use. + * @netdev: device to be opened + * + * Returns 0 on success, errno on failure (as defined in errno.h) + */ +static int et131x_open(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + struct pci_dev *pdev = adapter->pdev; + unsigned int irq = pdev->irq; + int result; + + /* Start the timer to track NIC errors */ + init_timer(&adapter->error_timer); + adapter->error_timer.expires = jiffies + TX_ERROR_PERIOD * HZ / 1000; + adapter->error_timer.function = et131x_error_timer_handler; + adapter->error_timer.data = (unsigned long)adapter; + add_timer(&adapter->error_timer); + + result = request_irq(irq, et131x_isr, IRQF_SHARED, netdev->name, netdev); + if (result) { + dev_err(&pdev->dev, "could not register IRQ %d\n", irq); + return result; + } + + adapter->flags |= fMP_ADAPTER_INTERRUPT_IN_USE; + + et131x_up(netdev); + + return result; +} + +/** + * et131x_close - Close the device + * @netdev: device to be closed + * + * Returns 0 on success, errno on failure (as defined in errno.h) + */ +static int et131x_close(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + + et131x_down(netdev); + + adapter->flags &= ~fMP_ADAPTER_INTERRUPT_IN_USE; + free_irq(adapter->pdev->irq, netdev); + + /* Stop the error timer */ + return del_timer_sync(&adapter->error_timer); +} + +/** + * et131x_ioctl - The I/O Control handler for the driver + * @netdev: device on which the control request is being made + * @reqbuf: a pointer to the IOCTL request buffer + * @cmd: the IOCTL command code + * + * Returns 0 on success, errno on failure (as defined in errno.h) + */ +static int et131x_ioctl(struct net_device *netdev, struct ifreq *reqbuf, + int cmd) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + + if (!adapter->phydev) + return -EINVAL; + + return phy_mii_ioctl(adapter->phydev, reqbuf, cmd); +} + +/** + * et131x_set_packet_filter - Configures the Rx Packet filtering on the device + * @adapter: pointer to our private adapter structure + * + * FIXME: lot of dups with MAC code + * + * Returns 0 on success, errno on failure + */ +static int et131x_set_packet_filter(struct et131x_adapter *adapter) +{ + int filter = adapter->packet_filter; + int status = 0; + u32 ctrl; + u32 pf_ctrl; + + ctrl = readl(&adapter->regs->rxmac.ctrl); + pf_ctrl = readl(&adapter->regs->rxmac.pf_ctrl); + + /* Default to disabled packet filtering. Enable it in the individual + * case statements that require the device to filter something + */ + ctrl |= 0x04; + + /* Set us to be in promiscuous mode so we receive everything, this + * is also true when we get a packet filter of 0 + */ + if ((filter & ET131X_PACKET_TYPE_PROMISCUOUS) || filter == 0) + pf_ctrl &= ~7; /* Clear filter bits */ + else { + /* + * Set us up with Multicast packet filtering. Three cases are + * possible - (1) we have a multi-cast list, (2) we receive ALL + * multicast entries or (3) we receive none. + */ + if (filter & ET131X_PACKET_TYPE_ALL_MULTICAST) + pf_ctrl &= ~2; /* Multicast filter bit */ + else { + et1310_setup_device_for_multicast(adapter); + pf_ctrl |= 2; + ctrl &= ~0x04; + } + + /* Set us up with Unicast packet filtering */ + if (filter & ET131X_PACKET_TYPE_DIRECTED) { + et1310_setup_device_for_unicast(adapter); + pf_ctrl |= 4; + ctrl &= ~0x04; + } + + /* Set us up with Broadcast packet filtering */ + if (filter & ET131X_PACKET_TYPE_BROADCAST) { + pf_ctrl |= 1; /* Broadcast filter bit */ + ctrl &= ~0x04; + } else + pf_ctrl &= ~1; + + /* Setup the receive mac configuration registers - Packet + * Filter control + the enable / disable for packet filter + * in the control reg. + */ + writel(pf_ctrl, &adapter->regs->rxmac.pf_ctrl); + writel(ctrl, &adapter->regs->rxmac.ctrl); + } + return status; +} + +/** + * et131x_multicast - The handler to configure multicasting on the interface + * @netdev: a pointer to a net_device struct representing the device + */ +static void et131x_multicast(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + int packet_filter; + unsigned long flags; + struct netdev_hw_addr *ha; + int i; + + spin_lock_irqsave(&adapter->lock, flags); + + /* Before we modify the platform-independent filter flags, store them + * locally. This allows us to determine if anything's changed and if + * we even need to bother the hardware + */ + packet_filter = adapter->packet_filter; + + /* Clear the 'multicast' flag locally; because we only have a single + * flag to check multicast, and multiple multicast addresses can be + * set, this is the easiest way to determine if more than one + * multicast address is being set. + */ + packet_filter &= ~ET131X_PACKET_TYPE_MULTICAST; + + /* Check the net_device flags and set the device independent flags + * accordingly + */ + + if (netdev->flags & IFF_PROMISC) + adapter->packet_filter |= ET131X_PACKET_TYPE_PROMISCUOUS; + else + adapter->packet_filter &= ~ET131X_PACKET_TYPE_PROMISCUOUS; + + if (netdev->flags & IFF_ALLMULTI) + adapter->packet_filter |= ET131X_PACKET_TYPE_ALL_MULTICAST; + + if (netdev_mc_count(netdev) > NIC_MAX_MCAST_LIST) + adapter->packet_filter |= ET131X_PACKET_TYPE_ALL_MULTICAST; + + if (netdev_mc_count(netdev) < 1) { + adapter->packet_filter &= ~ET131X_PACKET_TYPE_ALL_MULTICAST; + adapter->packet_filter &= ~ET131X_PACKET_TYPE_MULTICAST; + } else + adapter->packet_filter |= ET131X_PACKET_TYPE_MULTICAST; + + /* Set values in the private adapter struct */ + i = 0; + netdev_for_each_mc_addr(ha, netdev) { + if (i == NIC_MAX_MCAST_LIST) + break; + memcpy(adapter->multicast_list[i++], ha->addr, ETH_ALEN); + } + adapter->multicast_addr_count = i; + + /* Are the new flags different from the previous ones? If not, then no + * action is required + * + * NOTE - This block will always update the multicast_list with the + * hardware, even if the addresses aren't the same. + */ + if (packet_filter != adapter->packet_filter) { + /* Call the device's filter function */ + et131x_set_packet_filter(adapter); + } + spin_unlock_irqrestore(&adapter->lock, flags); +} + +/** + * et131x_tx - The handler to tx a packet on the device + * @skb: data to be Tx'd + * @netdev: device on which data is to be Tx'd + * + * Returns 0 on success, errno on failure (as defined in errno.h) + */ +static int et131x_tx(struct sk_buff *skb, struct net_device *netdev) +{ + int status = 0; + struct et131x_adapter *adapter = netdev_priv(netdev); + + /* stop the queue if it's getting full */ + if (adapter->tx_ring.used >= NUM_TCB - 1 && + !netif_queue_stopped(netdev)) + netif_stop_queue(netdev); + + /* Save the timestamp for the TX timeout watchdog */ + netdev->trans_start = jiffies; + + /* Call the device-specific data Tx routine */ + status = et131x_send_packets(skb, netdev); + + /* Check status and manage the netif queue if necessary */ + if (status != 0) { + if (status == -ENOMEM) + status = NETDEV_TX_BUSY; + else + status = NETDEV_TX_OK; + } + return status; +} + +/** + * et131x_tx_timeout - Timeout handler + * @netdev: a pointer to a net_device struct representing the device + * + * The handler called when a Tx request times out. The timeout period is + * specified by the 'tx_timeo" element in the net_device structure (see + * et131x_alloc_device() to see how this value is set). + */ +static void et131x_tx_timeout(struct net_device *netdev) +{ + struct et131x_adapter *adapter = netdev_priv(netdev); + struct tcb *tcb; + unsigned long flags; + + /* If the device is closed, ignore the timeout */ + if (~(adapter->flags & fMP_ADAPTER_INTERRUPT_IN_USE)) + return; + + /* Any nonrecoverable hardware error? + * Checks adapter->flags for any failure in phy reading + */ + if (adapter->flags & fMP_ADAPTER_NON_RECOVER_ERROR) + return; + + /* Hardware failure? */ + if (adapter->flags & fMP_ADAPTER_HARDWARE_ERROR) { + dev_err(&adapter->pdev->dev, "hardware error - reset\n"); + return; + } + + /* Is send stuck? */ + spin_lock_irqsave(&adapter->tcb_send_qlock, flags); + + tcb = adapter->tx_ring.send_head; + + if (tcb != NULL) { + tcb->count++; + + if (tcb->count > NIC_SEND_HANG_THRESHOLD) { + spin_unlock_irqrestore(&adapter->tcb_send_qlock, + flags); + + dev_warn(&adapter->pdev->dev, + "Send stuck - reset. tcb->WrIndex %x, flags 0x%08x\n", + tcb->index, + tcb->flags); + + adapter->net_stats.tx_errors++; + + /* perform reset of tx/rx */ + et131x_disable_txrx(netdev); + et131x_enable_txrx(netdev); + return; + } + } + + spin_unlock_irqrestore(&adapter->tcb_send_qlock, flags); +} + +/** + * et131x_change_mtu - The handler called to change the MTU for the device + * @netdev: device whose MTU is to be changed + * @new_mtu: the desired MTU + * + * Returns 0 on success, errno on failure (as defined in errno.h) + */ +static int et131x_change_mtu(struct net_device *netdev, int new_mtu) +{ + int result = 0; + struct et131x_adapter *adapter = netdev_priv(netdev); + + /* Make sure the requested MTU is valid */ + if (new_mtu < 64 || new_mtu > 9216) + return -EINVAL; + + et131x_disable_txrx(netdev); + et131x_handle_send_interrupt(adapter); + et131x_handle_recv_interrupt(adapter); + + /* Set the new MTU */ + netdev->mtu = new_mtu; + + /* Free Rx DMA memory */ + et131x_adapter_memory_free(adapter); + + /* Set the config parameter for Jumbo Packet support */ + adapter->registry_jumbo_packet = new_mtu + 14; + et131x_soft_reset(adapter); + + /* Alloc and init Rx DMA memory */ + result = et131x_adapter_memory_alloc(adapter); + if (result != 0) { + dev_warn(&adapter->pdev->dev, + "Change MTU failed; couldn't re-alloc DMA memory\n"); + return result; + } + + et131x_init_send(adapter); + + et131x_hwaddr_init(adapter); + memcpy(netdev->dev_addr, adapter->addr, ETH_ALEN); + + /* Init the device with the new settings */ + et131x_adapter_setup(adapter); + + et131x_enable_txrx(netdev); + + return result; +} + +/** + * et131x_set_mac_addr - handler to change the MAC address for the device + * @netdev: device whose MAC is to be changed + * @new_mac: the desired MAC address + * + * Returns 0 on success, errno on failure (as defined in errno.h) + * + * IMPLEMENTED BY : blux http://berndlux.de 22.01.2007 21:14 + */ +static int et131x_set_mac_addr(struct net_device *netdev, void *new_mac) +{ + int result = 0; + struct et131x_adapter *adapter = netdev_priv(netdev); + struct sockaddr *address = new_mac; + + /* begin blux */ + + if (adapter == NULL) + return -ENODEV; + + /* Make sure the requested MAC is valid */ + if (!is_valid_ether_addr(address->sa_data)) + return -EADDRNOTAVAIL; + + et131x_disable_txrx(netdev); + et131x_handle_send_interrupt(adapter); + et131x_handle_recv_interrupt(adapter); + + /* Set the new MAC */ + /* netdev->set_mac_address = &new_mac; */ + + memcpy(netdev->dev_addr, address->sa_data, netdev->addr_len); + + printk(KERN_INFO "%s: Setting MAC address to %pM\n", + netdev->name, netdev->dev_addr); + + /* Free Rx DMA memory */ + et131x_adapter_memory_free(adapter); + + et131x_soft_reset(adapter); + + /* Alloc and init Rx DMA memory */ + result = et131x_adapter_memory_alloc(adapter); + if (result != 0) { + dev_err(&adapter->pdev->dev, + "Change MAC failed; couldn't re-alloc DMA memory\n"); + return result; + } + + et131x_init_send(adapter); + + et131x_hwaddr_init(adapter); + + /* Init the device with the new settings */ + et131x_adapter_setup(adapter); + + et131x_enable_txrx(netdev); + + return result; +} + +static const struct net_device_ops et131x_netdev_ops = { + .ndo_open = et131x_open, + .ndo_stop = et131x_close, + .ndo_start_xmit = et131x_tx, + .ndo_set_rx_mode = et131x_multicast, + .ndo_tx_timeout = et131x_tx_timeout, + .ndo_change_mtu = et131x_change_mtu, + .ndo_set_mac_address = et131x_set_mac_addr, + .ndo_validate_addr = eth_validate_addr, + .ndo_get_stats = et131x_stats, + .ndo_do_ioctl = et131x_ioctl, +}; + +/** + * et131x_pci_setup - Perform device initialization + * @pdev: a pointer to the device's pci_dev structure + * @ent: this device's entry in the pci_device_id table + * + * Returns 0 on success, errno on failure (as defined in errno.h) + * + * Registered in the pci_driver structure, this function is called when the + * PCI subsystem finds a new PCI device which matches the information + * contained in the pci_device_id table. This routine is the equivalent to + * a device insertion routine. + */ +static int __devinit et131x_pci_setup(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct net_device *netdev; + struct et131x_adapter *adapter; + int rc; + int ii; + + rc = pci_enable_device(pdev); + if (rc < 0) { + dev_err(&pdev->dev, "pci_enable_device() failed\n"); + goto out; + } + + /* Perform some basic PCI checks */ + if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) { + dev_err(&pdev->dev, "Can't find PCI device's base address\n"); + rc = -ENODEV; + goto err_disable; + } + + rc = pci_request_regions(pdev, DRIVER_NAME); + if (rc < 0) { + dev_err(&pdev->dev, "Can't get PCI resources\n"); + goto err_disable; + } + + pci_set_master(pdev); + + /* Check the DMA addressing support of this device */ + if (!dma_set_mask(&pdev->dev, DMA_BIT_MASK(64))) { + rc = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)); + if (rc < 0) { + dev_err(&pdev->dev, + "Unable to obtain 64 bit DMA for consistent allocations\n"); + goto err_release_res; + } + } else if (!dma_set_mask(&pdev->dev, DMA_BIT_MASK(32))) { + rc = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (rc < 0) { + dev_err(&pdev->dev, + "Unable to obtain 32 bit DMA for consistent allocations\n"); + goto err_release_res; + } + } else { + dev_err(&pdev->dev, "No usable DMA addressing method\n"); + rc = -EIO; + goto err_release_res; + } + + /* Allocate netdev and private adapter structs */ + netdev = alloc_etherdev(sizeof(struct et131x_adapter)); + if (!netdev) { + dev_err(&pdev->dev, "Couldn't alloc netdev struct\n"); + rc = -ENOMEM; + goto err_release_res; + } + + netdev->watchdog_timeo = ET131X_TX_TIMEOUT; + netdev->netdev_ops = &et131x_netdev_ops; + + SET_NETDEV_DEV(netdev, &pdev->dev); + et131x_set_ethtool_ops(netdev); + + adapter = et131x_adapter_init(netdev, pdev); + + rc = et131x_pci_init(adapter, pdev); + if (rc < 0) + goto err_free_dev; + + /* Map the bus-relative registers to system virtual memory */ + adapter->regs = pci_ioremap_bar(pdev, 0); + if (!adapter->regs) { + dev_err(&pdev->dev, "Cannot map device registers\n"); + rc = -ENOMEM; + goto err_free_dev; + } + + /* If Phy COMA mode was enabled when we went down, disable it here. */ + writel(ET_PMCSR_INIT, &adapter->regs->global.pm_csr); + + /* Issue a global reset to the et1310 */ + et131x_soft_reset(adapter); + + /* Disable all interrupts (paranoid) */ + et131x_disable_interrupts(adapter); + + /* Allocate DMA memory */ + rc = et131x_adapter_memory_alloc(adapter); + if (rc < 0) { + dev_err(&pdev->dev, "Could not alloc adapater memory (DMA)\n"); + goto err_iounmap; + } + + /* Init send data structures */ + et131x_init_send(adapter); + + /* Set up the task structure for the ISR's deferred handler */ + INIT_WORK(&adapter->task, et131x_isr_handler); + + /* Copy address into the net_device struct */ + memcpy(netdev->dev_addr, adapter->addr, ETH_ALEN); + + /* Init variable for counting how long we do not have link status */ + adapter->boot_coma = 0; + et1310_disable_phy_coma(adapter); + + rc = -ENOMEM; + + /* Setup the mii_bus struct */ + adapter->mii_bus = mdiobus_alloc(); + if (!adapter->mii_bus) { + dev_err(&pdev->dev, "Alloc of mii_bus struct failed\n"); + goto err_mem_free; + } + + adapter->mii_bus->name = "et131x_eth_mii"; + snprintf(adapter->mii_bus->id, MII_BUS_ID_SIZE, "%x", + (adapter->pdev->bus->number << 8) | adapter->pdev->devfn); + adapter->mii_bus->priv = netdev; + adapter->mii_bus->read = et131x_mdio_read; + adapter->mii_bus->write = et131x_mdio_write; + adapter->mii_bus->reset = et131x_mdio_reset; + adapter->mii_bus->irq = kmalloc(sizeof(int)*PHY_MAX_ADDR, GFP_KERNEL); + if (!adapter->mii_bus->irq) { + dev_err(&pdev->dev, "mii_bus irq allocation failed\n"); + goto err_mdio_free; + } + + for (ii = 0; ii < PHY_MAX_ADDR; ii++) + adapter->mii_bus->irq[ii] = PHY_POLL; + + rc = mdiobus_register(adapter->mii_bus); + if (rc < 0) { + dev_err(&pdev->dev, "failed to register MII bus\n"); + goto err_mdio_free_irq; + } + + rc = et131x_mii_probe(netdev); + if (rc < 0) { + dev_err(&pdev->dev, "failed to probe MII bus\n"); + goto err_mdio_unregister; + } + + /* Setup et1310 as per the documentation */ + et131x_adapter_setup(adapter); + + /* We can enable interrupts now + * + * NOTE - Because registration of interrupt handler is done in the + * device's open(), defer enabling device interrupts to that + * point + */ + + /* Register the net_device struct with the Linux network layer */ + rc = register_netdev(netdev); + if (rc < 0) { + dev_err(&pdev->dev, "register_netdev() failed\n"); + goto err_phy_disconnect; + } + + /* Register the net_device struct with the PCI subsystem. Save a copy + * of the PCI config space for this device now that the device has + * been initialized, just in case it needs to be quickly restored. + */ + pci_set_drvdata(pdev, netdev); +out: + return rc; + +err_phy_disconnect: + phy_disconnect(adapter->phydev); +err_mdio_unregister: + mdiobus_unregister(adapter->mii_bus); +err_mdio_free_irq: + kfree(adapter->mii_bus->irq); +err_mdio_free: + mdiobus_free(adapter->mii_bus); +err_mem_free: + et131x_adapter_memory_free(adapter); +err_iounmap: + iounmap(adapter->regs); +err_free_dev: + pci_dev_put(pdev); + free_netdev(netdev); +err_release_res: + pci_release_regions(pdev); +err_disable: + pci_disable_device(pdev); + goto out; +} + +static DEFINE_PCI_DEVICE_TABLE(et131x_pci_table) = { + { PCI_VDEVICE(ATT, ET131X_PCI_DEVICE_ID_GIG), 0UL}, + { PCI_VDEVICE(ATT, ET131X_PCI_DEVICE_ID_FAST), 0UL}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, et131x_pci_table); + +static struct pci_driver et131x_driver = { + .name = DRIVER_NAME, + .id_table = et131x_pci_table, + .probe = et131x_pci_setup, + .remove = __devexit_p(et131x_pci_remove), + .driver.pm = ET131X_PM_OPS, +}; + +/** + * et131x_init_module - The "main" entry point called on driver initialization + * + * Returns 0 on success, errno on failure (as defined in errno.h) + */ +static int __init et131x_init_module(void) +{ + return pci_register_driver(&et131x_driver); +} + +/** + * et131x_cleanup_module - The entry point called on driver cleanup + */ +static void __exit et131x_cleanup_module(void) +{ + pci_unregister_driver(&et131x_driver); +} + +module_init(et131x_init_module); +module_exit(et131x_cleanup_module); + diff --git a/drivers/staging/et131x/et131x.h b/drivers/staging/et131x/et131x.h new file mode 100644 index 00000000..864379b4 --- /dev/null +++ b/drivers/staging/et131x/et131x.h @@ -0,0 +1,1666 @@ +/* + * Copyright © 2005 Agere Systems Inc. + * All rights reserved. + * http://www.agere.com + * + * SOFTWARE LICENSE + * + * This software is provided subject to the following terms and conditions, + * which you should read carefully before using the software. Using this + * software indicates your acceptance of these terms and conditions. If you do + * not agree with these terms and conditions, do not use the software. + * + * Copyright © 2005 Agere Systems Inc. + * All rights reserved. + * + * Redistribution and use in source or binary forms, with or without + * modifications, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following Disclaimer as comments in the code as + * well as in the documentation and/or other materials provided with the + * distribution. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following Disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name of Agere Systems Inc. nor the names of the contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * Disclaimer + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, INFRINGEMENT AND THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ANY + * USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE IS SOLELY AT THE USERS OWN + * RISK. IN NO EVENT SHALL AGERE SYSTEMS INC. OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, INCLUDING, BUT NOT LIMITED TO, CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + */ + +#define DRIVER_NAME "et131x" +#define DRIVER_VERSION "v2.0" + +/* EEPROM registers */ + +/* LBCIF Register Groups (addressed via 32-bit offsets) */ +#define LBCIF_DWORD0_GROUP 0xAC +#define LBCIF_DWORD1_GROUP 0xB0 + +/* LBCIF Registers (addressed via 8-bit offsets) */ +#define LBCIF_ADDRESS_REGISTER 0xAC +#define LBCIF_DATA_REGISTER 0xB0 +#define LBCIF_CONTROL_REGISTER 0xB1 +#define LBCIF_STATUS_REGISTER 0xB2 + +/* LBCIF Control Register Bits */ +#define LBCIF_CONTROL_SEQUENTIAL_READ 0x01 +#define LBCIF_CONTROL_PAGE_WRITE 0x02 +#define LBCIF_CONTROL_EEPROM_RELOAD 0x08 +#define LBCIF_CONTROL_TWO_BYTE_ADDR 0x20 +#define LBCIF_CONTROL_I2C_WRITE 0x40 +#define LBCIF_CONTROL_LBCIF_ENABLE 0x80 + +/* LBCIF Status Register Bits */ +#define LBCIF_STATUS_PHY_QUEUE_AVAIL 0x01 +#define LBCIF_STATUS_I2C_IDLE 0x02 +#define LBCIF_STATUS_ACK_ERROR 0x04 +#define LBCIF_STATUS_GENERAL_ERROR 0x08 +#define LBCIF_STATUS_CHECKSUM_ERROR 0x40 +#define LBCIF_STATUS_EEPROM_PRESENT 0x80 + +/* START OF GLOBAL REGISTER ADDRESS MAP */ + +/* + * 10bit registers + * + * Tx queue start address reg in global address map at address 0x0000 + * tx queue end address reg in global address map at address 0x0004 + * rx queue start address reg in global address map at address 0x0008 + * rx queue end address reg in global address map at address 0x000C + */ + +/* + * structure for power management control status reg in global address map + * located at address 0x0010 + * jagcore_rx_rdy bit 9 + * jagcore_tx_rdy bit 8 + * phy_lped_en bit 7 + * phy_sw_coma bit 6 + * rxclk_gate bit 5 + * txclk_gate bit 4 + * sysclk_gate bit 3 + * jagcore_rx_en bit 2 + * jagcore_tx_en bit 1 + * gigephy_en bit 0 + */ + +#define ET_PM_PHY_SW_COMA 0x40 +#define ET_PMCSR_INIT 0x38 + +/* + * Interrupt status reg at address 0x0018 + */ + +#define ET_INTR_TXDMA_ISR 0x00000008 +#define ET_INTR_TXDMA_ERR 0x00000010 +#define ET_INTR_RXDMA_XFR_DONE 0x00000020 +#define ET_INTR_RXDMA_FB_R0_LOW 0x00000040 +#define ET_INTR_RXDMA_FB_R1_LOW 0x00000080 +#define ET_INTR_RXDMA_STAT_LOW 0x00000100 +#define ET_INTR_RXDMA_ERR 0x00000200 +#define ET_INTR_WATCHDOG 0x00004000 +#define ET_INTR_WOL 0x00008000 +#define ET_INTR_PHY 0x00010000 +#define ET_INTR_TXMAC 0x00020000 +#define ET_INTR_RXMAC 0x00040000 +#define ET_INTR_MAC_STAT 0x00080000 +#define ET_INTR_SLV_TIMEOUT 0x00100000 + +/* + * Interrupt mask register at address 0x001C + * Interrupt alias clear mask reg at address 0x0020 + * Interrupt status alias reg at address 0x0024 + * + * Same masks as above + */ + +/* + * Software reset reg at address 0x0028 + * 0: txdma_sw_reset + * 1: rxdma_sw_reset + * 2: txmac_sw_reset + * 3: rxmac_sw_reset + * 4: mac_sw_reset + * 5: mac_stat_sw_reset + * 6: mmc_sw_reset + *31: selfclr_disable + */ + +/* + * SLV Timer reg at address 0x002C (low 24 bits) + */ + +/* + * MSI Configuration reg at address 0x0030 + */ + +#define ET_MSI_VECTOR 0x0000001F +#define ET_MSI_TC 0x00070000 + +/* + * Loopback reg located at address 0x0034 + */ + +#define ET_LOOP_MAC 0x00000001 +#define ET_LOOP_DMA 0x00000002 + +/* + * GLOBAL Module of JAGCore Address Mapping + * Located at address 0x0000 + */ +struct global_regs { /* Location: */ + u32 txq_start_addr; /* 0x0000 */ + u32 txq_end_addr; /* 0x0004 */ + u32 rxq_start_addr; /* 0x0008 */ + u32 rxq_end_addr; /* 0x000C */ + u32 pm_csr; /* 0x0010 */ + u32 unused; /* 0x0014 */ + u32 int_status; /* 0x0018 */ + u32 int_mask; /* 0x001C */ + u32 int_alias_clr_en; /* 0x0020 */ + u32 int_status_alias; /* 0x0024 */ + u32 sw_reset; /* 0x0028 */ + u32 slv_timer; /* 0x002C */ + u32 msi_config; /* 0x0030 */ + u32 loopback; /* 0x0034 */ + u32 watchdog_timer; /* 0x0038 */ +}; + + +/* START OF TXDMA REGISTER ADDRESS MAP */ + +/* + * txdma control status reg at address 0x1000 + */ + +#define ET_TXDMA_CSR_HALT 0x00000001 +#define ET_TXDMA_DROP_TLP 0x00000002 +#define ET_TXDMA_CACHE_THRS 0x000000F0 +#define ET_TXDMA_CACHE_SHIFT 4 +#define ET_TXDMA_SNGL_EPKT 0x00000100 +#define ET_TXDMA_CLASS 0x00001E00 + +/* + * structure for txdma packet ring base address hi reg in txdma address map + * located at address 0x1004 + * Defined earlier (u32) + */ + +/* + * structure for txdma packet ring base address low reg in txdma address map + * located at address 0x1008 + * Defined earlier (u32) + */ + +/* + * structure for txdma packet ring number of descriptor reg in txdma address + * map. Located at address 0x100C + * + * 31-10: unused + * 9-0: pr ndes + */ + +#define ET_DMA12_MASK 0x0FFF /* 12 bit mask for DMA12W types */ +#define ET_DMA12_WRAP 0x1000 +#define ET_DMA10_MASK 0x03FF /* 10 bit mask for DMA10W types */ +#define ET_DMA10_WRAP 0x0400 +#define ET_DMA4_MASK 0x000F /* 4 bit mask for DMA4W types */ +#define ET_DMA4_WRAP 0x0010 + +#define INDEX12(x) ((x) & ET_DMA12_MASK) +#define INDEX10(x) ((x) & ET_DMA10_MASK) +#define INDEX4(x) ((x) & ET_DMA4_MASK) + +/* + * 10bit DMA with wrap + * txdma tx queue write address reg in txdma address map at 0x1010 + * txdma tx queue write address external reg in txdma address map at 0x1014 + * txdma tx queue read address reg in txdma address map at 0x1018 + * + * u32 + * txdma status writeback address hi reg in txdma address map at0x101C + * txdma status writeback address lo reg in txdma address map at 0x1020 + * + * 10bit DMA with wrap + * txdma service request reg in txdma address map at 0x1024 + * structure for txdma service complete reg in txdma address map at 0x1028 + * + * 4bit DMA with wrap + * txdma tx descriptor cache read index reg in txdma address map at 0x102C + * txdma tx descriptor cache write index reg in txdma address map at 0x1030 + * + * txdma error reg in txdma address map at address 0x1034 + * 0: PyldResend + * 1: PyldRewind + * 4: DescrResend + * 5: DescrRewind + * 8: WrbkResend + * 9: WrbkRewind + */ + +/* + * Tx DMA Module of JAGCore Address Mapping + * Located at address 0x1000 + */ +struct txdma_regs { /* Location: */ + u32 csr; /* 0x1000 */ + u32 pr_base_hi; /* 0x1004 */ + u32 pr_base_lo; /* 0x1008 */ + u32 pr_num_des; /* 0x100C */ + u32 txq_wr_addr; /* 0x1010 */ + u32 txq_wr_addr_ext; /* 0x1014 */ + u32 txq_rd_addr; /* 0x1018 */ + u32 dma_wb_base_hi; /* 0x101C */ + u32 dma_wb_base_lo; /* 0x1020 */ + u32 service_request; /* 0x1024 */ + u32 service_complete; /* 0x1028 */ + u32 cache_rd_index; /* 0x102C */ + u32 cache_wr_index; /* 0x1030 */ + u32 tx_dma_error; /* 0x1034 */ + u32 desc_abort_cnt; /* 0x1038 */ + u32 payload_abort_cnt; /* 0x103c */ + u32 writeback_abort_cnt; /* 0x1040 */ + u32 desc_timeout_cnt; /* 0x1044 */ + u32 payload_timeout_cnt; /* 0x1048 */ + u32 writeback_timeout_cnt; /* 0x104c */ + u32 desc_error_cnt; /* 0x1050 */ + u32 payload_error_cnt; /* 0x1054 */ + u32 writeback_error_cnt; /* 0x1058 */ + u32 dropped_tlp_cnt; /* 0x105c */ + u32 new_service_complete; /* 0x1060 */ + u32 ethernet_packet_cnt; /* 0x1064 */ +}; + +/* END OF TXDMA REGISTER ADDRESS MAP */ + + +/* START OF RXDMA REGISTER ADDRESS MAP */ + +/* + * structure for control status reg in rxdma address map + * Located at address 0x2000 + * + * CSR + * 0: halt + * 1-3: tc + * 4: fbr_big_endian + * 5: psr_big_endian + * 6: pkt_big_endian + * 7: dma_big_endian + * 8-9: fbr0_size + * 10: fbr0_enable + * 11-12: fbr1_size + * 13: fbr1_enable + * 14: unused + * 15: pkt_drop_disable + * 16: pkt_done_flush + * 17: halt_status + * 18-31: unused + */ + + +/* + * structure for dma writeback lo reg in rxdma address map + * located at address 0x2004 + * Defined earlier (u32) + */ + +/* + * structure for dma writeback hi reg in rxdma address map + * located at address 0x2008 + * Defined earlier (u32) + */ + +/* + * structure for number of packets done reg in rxdma address map + * located at address 0x200C + * + * 31-8: unused + * 7-0: num done + */ + +/* + * structure for max packet time reg in rxdma address map + * located at address 0x2010 + * + * 31-18: unused + * 17-0: time done + */ + +/* + * structure for rx queue read address reg in rxdma address map + * located at address 0x2014 + * Defined earlier (u32) + */ + +/* + * structure for rx queue read address external reg in rxdma address map + * located at address 0x2018 + * Defined earlier (u32) + */ + +/* + * structure for rx queue write address reg in rxdma address map + * located at address 0x201C + * Defined earlier (u32) + */ + +/* + * structure for packet status ring base address lo reg in rxdma address map + * located at address 0x2020 + * Defined earlier (u32) + */ + +/* + * structure for packet status ring base address hi reg in rxdma address map + * located at address 0x2024 + * Defined earlier (u32) + */ + +/* + * structure for packet status ring number of descriptors reg in rxdma address + * map. Located at address 0x2028 + * + * 31-12: unused + * 11-0: psr ndes + */ + +/* + * structure for packet status ring available offset reg in rxdma address map + * located at address 0x202C + * + * 31-13: unused + * 12: psr avail wrap + * 11-0: psr avail + */ + +/* + * structure for packet status ring full offset reg in rxdma address map + * located at address 0x2030 + * + * 31-13: unused + * 12: psr full wrap + * 11-0: psr full + */ + +/* + * structure for packet status ring access index reg in rxdma address map + * located at address 0x2034 + * + * 31-5: unused + * 4-0: psr_ai + */ + +/* + * structure for packet status ring minimum descriptors reg in rxdma address + * map. Located at address 0x2038 + * + * 31-12: unused + * 11-0: psr_min + */ + +/* + * structure for free buffer ring base lo address reg in rxdma address map + * located at address 0x203C + * Defined earlier (u32) + */ + +/* + * structure for free buffer ring base hi address reg in rxdma address map + * located at address 0x2040 + * Defined earlier (u32) + */ + +/* + * structure for free buffer ring number of descriptors reg in rxdma address + * map. Located at address 0x2044 + * + * 31-10: unused + * 9-0: fbr ndesc + */ + +/* + * structure for free buffer ring 0 available offset reg in rxdma address map + * located at address 0x2048 + * Defined earlier (u32) + */ + +/* + * structure for free buffer ring 0 full offset reg in rxdma address map + * located at address 0x204C + * Defined earlier (u32) + */ + +/* + * structure for free buffer cache 0 full offset reg in rxdma address map + * located at address 0x2050 + * + * 31-5: unused + * 4-0: fbc rdi + */ + +/* + * structure for free buffer ring 0 minimum descriptor reg in rxdma address map + * located at address 0x2054 + * + * 31-10: unused + * 9-0: fbr min + */ + +/* + * structure for free buffer ring 1 base address lo reg in rxdma address map + * located at address 0x2058 - 0x205C + * Defined earlier (RXDMA_FBR_BASE_LO_t and RXDMA_FBR_BASE_HI_t) + */ + +/* + * structure for free buffer ring 1 number of descriptors reg in rxdma address + * map. Located at address 0x2060 + * Defined earlier (RXDMA_FBR_NUM_DES_t) + */ + +/* + * structure for free buffer ring 1 available offset reg in rxdma address map + * located at address 0x2064 + * Defined Earlier (RXDMA_FBR_AVAIL_OFFSET_t) + */ + +/* + * structure for free buffer ring 1 full offset reg in rxdma address map + * located at address 0x2068 + * Defined Earlier (RXDMA_FBR_FULL_OFFSET_t) + */ + +/* + * structure for free buffer cache 1 read index reg in rxdma address map + * located at address 0x206C + * Defined Earlier (RXDMA_FBC_RD_INDEX_t) + */ + +/* + * structure for free buffer ring 1 minimum descriptor reg in rxdma address map + * located at address 0x2070 + * Defined Earlier (RXDMA_FBR_MIN_DES_t) + */ + +/* + * Rx DMA Module of JAGCore Address Mapping + * Located at address 0x2000 + */ +struct rxdma_regs { /* Location: */ + u32 csr; /* 0x2000 */ + u32 dma_wb_base_lo; /* 0x2004 */ + u32 dma_wb_base_hi; /* 0x2008 */ + u32 num_pkt_done; /* 0x200C */ + u32 max_pkt_time; /* 0x2010 */ + u32 rxq_rd_addr; /* 0x2014 */ + u32 rxq_rd_addr_ext; /* 0x2018 */ + u32 rxq_wr_addr; /* 0x201C */ + u32 psr_base_lo; /* 0x2020 */ + u32 psr_base_hi; /* 0x2024 */ + u32 psr_num_des; /* 0x2028 */ + u32 psr_avail_offset; /* 0x202C */ + u32 psr_full_offset; /* 0x2030 */ + u32 psr_access_index; /* 0x2034 */ + u32 psr_min_des; /* 0x2038 */ + u32 fbr0_base_lo; /* 0x203C */ + u32 fbr0_base_hi; /* 0x2040 */ + u32 fbr0_num_des; /* 0x2044 */ + u32 fbr0_avail_offset; /* 0x2048 */ + u32 fbr0_full_offset; /* 0x204C */ + u32 fbr0_rd_index; /* 0x2050 */ + u32 fbr0_min_des; /* 0x2054 */ + u32 fbr1_base_lo; /* 0x2058 */ + u32 fbr1_base_hi; /* 0x205C */ + u32 fbr1_num_des; /* 0x2060 */ + u32 fbr1_avail_offset; /* 0x2064 */ + u32 fbr1_full_offset; /* 0x2068 */ + u32 fbr1_rd_index; /* 0x206C */ + u32 fbr1_min_des; /* 0x2070 */ +}; + +/* END OF RXDMA REGISTER ADDRESS MAP */ + + +/* START OF TXMAC REGISTER ADDRESS MAP */ + +/* + * structure for control reg in txmac address map + * located at address 0x3000 + * + * bits + * 31-8: unused + * 7: cklseg_disable + * 6: ckbcnt_disable + * 5: cksegnum + * 4: async_disable + * 3: fc_disable + * 2: mcif_disable + * 1: mif_disable + * 0: txmac_en + */ + +/* + * structure for shadow pointer reg in txmac address map + * located at address 0x3004 + * 31-27: reserved + * 26-16: txq rd ptr + * 15-11: reserved + * 10-0: txq wr ptr + */ + +/* + * structure for error count reg in txmac address map + * located at address 0x3008 + * + * 31-12: unused + * 11-8: reserved + * 7-4: txq_underrun + * 3-0: fifo_underrun + */ + +/* + * structure for max fill reg in txmac address map + * located at address 0x300C + * 31-12: unused + * 11-0: max fill + */ + +/* + * structure for cf parameter reg in txmac address map + * located at address 0x3010 + * 31-16: cfep + * 15-0: cfpt + */ + +/* + * structure for tx test reg in txmac address map + * located at address 0x3014 + * 31-17: unused + * 16: reserved + * 15: txtest_en + * 14-11: unused + * 10-0: txq test pointer + */ + +/* + * structure for error reg in txmac address map + * located at address 0x3018 + * + * 31-9: unused + * 8: fifo_underrun + * 7-6: unused + * 5: ctrl2_err + * 4: txq_underrun + * 3: bcnt_err + * 2: lseg_err + * 1: segnum_err + * 0: seg0_err + */ + +/* + * structure for error interrupt reg in txmac address map + * located at address 0x301C + * + * 31-9: unused + * 8: fifo_underrun + * 7-6: unused + * 5: ctrl2_err + * 4: txq_underrun + * 3: bcnt_err + * 2: lseg_err + * 1: segnum_err + * 0: seg0_err + */ + +/* + * structure for error interrupt reg in txmac address map + * located at address 0x3020 + * + * 31-2: unused + * 1: bp_req + * 0: bp_xonxoff + */ + +/* + * Tx MAC Module of JAGCore Address Mapping + */ +struct txmac_regs { /* Location: */ + u32 ctl; /* 0x3000 */ + u32 shadow_ptr; /* 0x3004 */ + u32 err_cnt; /* 0x3008 */ + u32 max_fill; /* 0x300C */ + u32 cf_param; /* 0x3010 */ + u32 tx_test; /* 0x3014 */ + u32 err; /* 0x3018 */ + u32 err_int; /* 0x301C */ + u32 bp_ctrl; /* 0x3020 */ +}; + +/* END OF TXMAC REGISTER ADDRESS MAP */ + +/* START OF RXMAC REGISTER ADDRESS MAP */ + +/* + * structure for rxmac control reg in rxmac address map + * located at address 0x4000 + * + * 31-7: reserved + * 6: rxmac_int_disable + * 5: async_disable + * 4: mif_disable + * 3: wol_disable + * 2: pkt_filter_disable + * 1: mcif_disable + * 0: rxmac_en + */ + +/* + * structure for Wake On Lan Control and CRC 0 reg in rxmac address map + * located at address 0x4004 + * 31-16: crc + * 15-12: reserved + * 11: ignore_pp + * 10: ignore_mp + * 9: clr_intr + * 8: ignore_link_chg + * 7: ignore_uni + * 6: ignore_multi + * 5: ignore_broad + * 4-0: valid_crc 4-0 + */ + +/* + * structure for CRC 1 and CRC 2 reg in rxmac address map + * located at address 0x4008 + * + * 31-16: crc2 + * 15-0: crc1 + */ + +/* + * structure for CRC 3 and CRC 4 reg in rxmac address map + * located at address 0x400C + * + * 31-16: crc4 + * 15-0: crc3 + */ + +/* + * structure for Wake On Lan Source Address Lo reg in rxmac address map + * located at address 0x4010 + * + * 31-24: sa3 + * 23-16: sa4 + * 15-8: sa5 + * 7-0: sa6 + */ + +#define ET_WOL_LO_SA3_SHIFT 24 +#define ET_WOL_LO_SA4_SHIFT 16 +#define ET_WOL_LO_SA5_SHIFT 8 + +/* + * structure for Wake On Lan Source Address Hi reg in rxmac address map + * located at address 0x4014 + * + * 31-16: reserved + * 15-8: sa1 + * 7-0: sa2 + */ + +#define ET_WOL_HI_SA1_SHIFT 8 + +/* + * structure for Wake On Lan mask reg in rxmac address map + * located at address 0x4018 - 0x4064 + * Defined earlier (u32) + */ + +/* + * structure for Unicast Paket Filter Address 1 reg in rxmac address map + * located at address 0x4068 + * + * 31-24: addr1_3 + * 23-16: addr1_4 + * 15-8: addr1_5 + * 7-0: addr1_6 + */ + +#define ET_UNI_PF_ADDR1_3_SHIFT 24 +#define ET_UNI_PF_ADDR1_4_SHIFT 16 +#define ET_UNI_PF_ADDR1_5_SHIFT 8 + +/* + * structure for Unicast Paket Filter Address 2 reg in rxmac address map + * located at address 0x406C + * + * 31-24: addr2_3 + * 23-16: addr2_4 + * 15-8: addr2_5 + * 7-0: addr2_6 + */ + +#define ET_UNI_PF_ADDR2_3_SHIFT 24 +#define ET_UNI_PF_ADDR2_4_SHIFT 16 +#define ET_UNI_PF_ADDR2_5_SHIFT 8 + +/* + * structure for Unicast Paket Filter Address 1 & 2 reg in rxmac address map + * located at address 0x4070 + * + * 31-24: addr2_1 + * 23-16: addr2_2 + * 15-8: addr1_1 + * 7-0: addr1_2 + */ + +#define ET_UNI_PF_ADDR2_1_SHIFT 24 +#define ET_UNI_PF_ADDR2_2_SHIFT 16 +#define ET_UNI_PF_ADDR1_1_SHIFT 8 + + +/* + * structure for Multicast Hash reg in rxmac address map + * located at address 0x4074 - 0x4080 + * Defined earlier (u32) + */ + +/* + * structure for Packet Filter Control reg in rxmac address map + * located at address 0x4084 + * + * 31-23: unused + * 22-16: min_pkt_size + * 15-4: unused + * 3: filter_frag_en + * 2: filter_uni_en + * 1: filter_multi_en + * 0: filter_broad_en + */ + +/* + * structure for Memory Controller Interface Control Max Segment reg in rxmac + * address map. Located at address 0x4088 + * + * 31-10: reserved + * 9-2: max_size + * 1: fc_en + * 0: seg_en + */ + +/* + * structure for Memory Controller Interface Water Mark reg in rxmac address + * map. Located at address 0x408C + * + * 31-26: unused + * 25-16: mark_hi + * 15-10: unused + * 9-0: mark_lo + */ + +/* + * structure for Rx Queue Dialog reg in rxmac address map. + * located at address 0x4090 + * + * 31-26: reserved + * 25-16: rd_ptr + * 15-10: reserved + * 9-0: wr_ptr + */ + +/* + * structure for space available reg in rxmac address map. + * located at address 0x4094 + * + * 31-17: reserved + * 16: space_avail_en + * 15-10: reserved + * 9-0: space_avail + */ + +/* + * structure for management interface reg in rxmac address map. + * located at address 0x4098 + * + * 31-18: reserved + * 17: drop_pkt_en + * 16-0: drop_pkt_mask + */ + +/* + * structure for Error reg in rxmac address map. + * located at address 0x409C + * + * 31-4: unused + * 3: mif + * 2: async + * 1: pkt_filter + * 0: mcif + */ + +/* + * Rx MAC Module of JAGCore Address Mapping + */ +struct rxmac_regs { /* Location: */ + u32 ctrl; /* 0x4000 */ + u32 crc0; /* 0x4004 */ + u32 crc12; /* 0x4008 */ + u32 crc34; /* 0x400C */ + u32 sa_lo; /* 0x4010 */ + u32 sa_hi; /* 0x4014 */ + u32 mask0_word0; /* 0x4018 */ + u32 mask0_word1; /* 0x401C */ + u32 mask0_word2; /* 0x4020 */ + u32 mask0_word3; /* 0x4024 */ + u32 mask1_word0; /* 0x4028 */ + u32 mask1_word1; /* 0x402C */ + u32 mask1_word2; /* 0x4030 */ + u32 mask1_word3; /* 0x4034 */ + u32 mask2_word0; /* 0x4038 */ + u32 mask2_word1; /* 0x403C */ + u32 mask2_word2; /* 0x4040 */ + u32 mask2_word3; /* 0x4044 */ + u32 mask3_word0; /* 0x4048 */ + u32 mask3_word1; /* 0x404C */ + u32 mask3_word2; /* 0x4050 */ + u32 mask3_word3; /* 0x4054 */ + u32 mask4_word0; /* 0x4058 */ + u32 mask4_word1; /* 0x405C */ + u32 mask4_word2; /* 0x4060 */ + u32 mask4_word3; /* 0x4064 */ + u32 uni_pf_addr1; /* 0x4068 */ + u32 uni_pf_addr2; /* 0x406C */ + u32 uni_pf_addr3; /* 0x4070 */ + u32 multi_hash1; /* 0x4074 */ + u32 multi_hash2; /* 0x4078 */ + u32 multi_hash3; /* 0x407C */ + u32 multi_hash4; /* 0x4080 */ + u32 pf_ctrl; /* 0x4084 */ + u32 mcif_ctrl_max_seg; /* 0x4088 */ + u32 mcif_water_mark; /* 0x408C */ + u32 rxq_diag; /* 0x4090 */ + u32 space_avail; /* 0x4094 */ + + u32 mif_ctrl; /* 0x4098 */ + u32 err_reg; /* 0x409C */ +}; + +/* END OF RXMAC REGISTER ADDRESS MAP */ + + +/* START OF MAC REGISTER ADDRESS MAP */ + +/* + * structure for configuration #1 reg in mac address map. + * located at address 0x5000 + * + * 31: soft reset + * 30: sim reset + * 29-20: reserved + * 19: reset rx mc + * 18: reset tx mc + * 17: reset rx func + * 16: reset tx fnc + * 15-9: reserved + * 8: loopback + * 7-6: reserved + * 5: rx flow + * 4: tx flow + * 3: syncd rx en + * 2: rx enable + * 1: syncd tx en + * 0: tx enable + */ + +#define CFG1_LOOPBACK 0x00000100 +#define CFG1_RX_FLOW 0x00000020 +#define CFG1_TX_FLOW 0x00000010 +#define CFG1_RX_ENABLE 0x00000004 +#define CFG1_TX_ENABLE 0x00000001 +#define CFG1_WAIT 0x0000000A /* RX & TX syncd */ + +/* + * structure for configuration #2 reg in mac address map. + * located at address 0x5004 + * 31-16: reserved + * 15-12: preamble + * 11-10: reserved + * 9-8: if mode + * 7-6: reserved + * 5: huge frame + * 4: length check + * 3: undefined + * 2: pad crc + * 1: crc enable + * 0: full duplex + */ + + +/* + * structure for Interpacket gap reg in mac address map. + * located at address 0x5008 + * + * 31: reserved + * 30-24: non B2B ipg 1 + * 23: undefined + * 22-16: non B2B ipg 2 + * 15-8: Min ifg enforce + * 7-0: B2B ipg + * + * structure for half duplex reg in mac address map. + * located at address 0x500C + * 31-24: reserved + * 23-20: Alt BEB trunc + * 19: Alt BEB enable + * 18: BP no backoff + * 17: no backoff + * 16: excess defer + * 15-12: re-xmit max + * 11-10: reserved + * 9-0: collision window + */ + +/* + * structure for Maximum Frame Length reg in mac address map. + * located at address 0x5010: bits 0-15 hold the length. + */ + +/* + * structure for Reserve 1 reg in mac address map. + * located at address 0x5014 - 0x5018 + * Defined earlier (u32) + */ + +/* + * structure for Test reg in mac address map. + * located at address 0x501C + * test: bits 0-2, rest unused + */ + +/* + * structure for MII Management Configuration reg in mac address map. + * located at address 0x5020 + * + * 31: reset MII mgmt + * 30-6: unused + * 5: scan auto increment + * 4: preamble suppress + * 3: undefined + * 2-0: mgmt clock reset + */ + +/* + * structure for MII Management Command reg in mac address map. + * located at address 0x5024 + * bit 1: scan cycle + * bit 0: read cycle + */ + +/* + * structure for MII Management Address reg in mac address map. + * located at address 0x5028 + * 31-13: reserved + * 12-8: phy addr + * 7-5: reserved + * 4-0: register + */ + +#define MII_ADDR(phy, reg) ((phy) << 8 | (reg)) + +/* + * structure for MII Management Control reg in mac address map. + * located at address 0x502C + * 31-16: reserved + * 15-0: phy control + */ + +/* + * structure for MII Management Status reg in mac address map. + * located at address 0x5030 + * 31-16: reserved + * 15-0: phy control + */ + +/* + * structure for MII Management Indicators reg in mac address map. + * located at address 0x5034 + * 31-3: reserved + * 2: not valid + * 1: scanning + * 0: busy + */ + +#define MGMT_BUSY 0x00000001 /* busy */ +#define MGMT_WAIT 0x00000005 /* busy | not valid */ + +/* + * structure for Interface Control reg in mac address map. + * located at address 0x5038 + * + * 31: reset if module + * 30-28: reserved + * 27: tbi mode + * 26: ghd mode + * 25: lhd mode + * 24: phy mode + * 23: reset per mii + * 22-17: reserved + * 16: speed + * 15: reset pe100x + * 14-11: reserved + * 10: force quiet + * 9: no cipher + * 8: disable link fail + * 7: reset gpsi + * 6-1: reserved + * 0: enable jabber protection + */ + +/* + * structure for Interface Status reg in mac address map. + * located at address 0x503C + * + * 31-10: reserved + * 9: excess_defer + * 8: clash + * 7: phy_jabber + * 6: phy_link_ok + * 5: phy_full_duplex + * 4: phy_speed + * 3: pe100x_link_fail + * 2: pe10t_loss_carrier + * 1: pe10t_sqe_error + * 0: pe10t_jabber + */ + +/* + * structure for Mac Station Address, Part 1 reg in mac address map. + * located at address 0x5040 + * + * 31-24: Octet6 + * 23-16: Octet5 + * 15-8: Octet4 + * 7-0: Octet3 + */ + +#define ET_MAC_STATION_ADDR1_OC6_SHIFT 24 +#define ET_MAC_STATION_ADDR1_OC5_SHIFT 16 +#define ET_MAC_STATION_ADDR1_OC4_SHIFT 8 + +/* + * structure for Mac Station Address, Part 2 reg in mac address map. + * located at address 0x5044 + * + * 31-24: Octet2 + * 23-16: Octet1 + * 15-0: reserved + */ + +#define ET_MAC_STATION_ADDR2_OC2_SHIFT 24 +#define ET_MAC_STATION_ADDR2_OC1_SHIFT 16 + +/* + * MAC Module of JAGCore Address Mapping + */ +struct mac_regs { /* Location: */ + u32 cfg1; /* 0x5000 */ + u32 cfg2; /* 0x5004 */ + u32 ipg; /* 0x5008 */ + u32 hfdp; /* 0x500C */ + u32 max_fm_len; /* 0x5010 */ + u32 rsv1; /* 0x5014 */ + u32 rsv2; /* 0x5018 */ + u32 mac_test; /* 0x501C */ + u32 mii_mgmt_cfg; /* 0x5020 */ + u32 mii_mgmt_cmd; /* 0x5024 */ + u32 mii_mgmt_addr; /* 0x5028 */ + u32 mii_mgmt_ctrl; /* 0x502C */ + u32 mii_mgmt_stat; /* 0x5030 */ + u32 mii_mgmt_indicator; /* 0x5034 */ + u32 if_ctrl; /* 0x5038 */ + u32 if_stat; /* 0x503C */ + u32 station_addr_1; /* 0x5040 */ + u32 station_addr_2; /* 0x5044 */ +}; + +/* END OF MAC REGISTER ADDRESS MAP */ + +/* START OF MAC STAT REGISTER ADDRESS MAP */ + +/* + * structure for Carry Register One and it's Mask Register reg located in mac + * stat address map address 0x6130 and 0x6138. + * + * 31: tr64 + * 30: tr127 + * 29: tr255 + * 28: tr511 + * 27: tr1k + * 26: trmax + * 25: trmgv + * 24-17: unused + * 16: rbyt + * 15: rpkt + * 14: rfcs + * 13: rmca + * 12: rbca + * 11: rxcf + * 10: rxpf + * 9: rxuo + * 8: raln + * 7: rflr + * 6: rcde + * 5: rcse + * 4: rund + * 3: rovr + * 2: rfrg + * 1: rjbr + * 0: rdrp + */ + +/* + * structure for Carry Register Two Mask Register reg in mac stat address map. + * located at address 0x613C + * + * 31-20: unused + * 19: tjbr + * 18: tfcs + * 17: txcf + * 16: tovr + * 15: tund + * 14: trfg + * 13: tbyt + * 12: tpkt + * 11: tmca + * 10: tbca + * 9: txpf + * 8: tdfr + * 7: tedf + * 6: tscl + * 5: tmcl + * 4: tlcl + * 3: txcl + * 2: tncl + * 1: tpfh + * 0: tdrp + */ + +/* + * MAC STATS Module of JAGCore Address Mapping + */ +struct macstat_regs { /* Location: */ + u32 pad[32]; /* 0x6000 - 607C */ + + /* Tx/Rx 0-64 Byte Frame Counter */ + u32 txrx_0_64_byte_frames; /* 0x6080 */ + + /* Tx/Rx 65-127 Byte Frame Counter */ + u32 txrx_65_127_byte_frames; /* 0x6084 */ + + /* Tx/Rx 128-255 Byte Frame Counter */ + u32 txrx_128_255_byte_frames; /* 0x6088 */ + + /* Tx/Rx 256-511 Byte Frame Counter */ + u32 txrx_256_511_byte_frames; /* 0x608C */ + + /* Tx/Rx 512-1023 Byte Frame Counter */ + u32 txrx_512_1023_byte_frames; /* 0x6090 */ + + /* Tx/Rx 1024-1518 Byte Frame Counter */ + u32 txrx_1024_1518_byte_frames; /* 0x6094 */ + + /* Tx/Rx 1519-1522 Byte Good VLAN Frame Count */ + u32 txrx_1519_1522_gvln_frames; /* 0x6098 */ + + /* Rx Byte Counter */ + u32 rx_bytes; /* 0x609C */ + + /* Rx Packet Counter */ + u32 rx_packets; /* 0x60A0 */ + + /* Rx FCS Error Counter */ + u32 rx_fcs_errs; /* 0x60A4 */ + + /* Rx Multicast Packet Counter */ + u32 rx_multicast_packets; /* 0x60A8 */ + + /* Rx Broadcast Packet Counter */ + u32 rx_broadcast_packets; /* 0x60AC */ + + /* Rx Control Frame Packet Counter */ + u32 rx_control_frames; /* 0x60B0 */ + + /* Rx Pause Frame Packet Counter */ + u32 rx_pause_frames; /* 0x60B4 */ + + /* Rx Unknown OP Code Counter */ + u32 rx_unknown_opcodes; /* 0x60B8 */ + + /* Rx Alignment Error Counter */ + u32 rx_align_errs; /* 0x60BC */ + + /* Rx Frame Length Error Counter */ + u32 rx_frame_len_errs; /* 0x60C0 */ + + /* Rx Code Error Counter */ + u32 rx_code_errs; /* 0x60C4 */ + + /* Rx Carrier Sense Error Counter */ + u32 rx_carrier_sense_errs; /* 0x60C8 */ + + /* Rx Undersize Packet Counter */ + u32 rx_undersize_packets; /* 0x60CC */ + + /* Rx Oversize Packet Counter */ + u32 rx_oversize_packets; /* 0x60D0 */ + + /* Rx Fragment Counter */ + u32 rx_fragment_packets; /* 0x60D4 */ + + /* Rx Jabber Counter */ + u32 rx_jabbers; /* 0x60D8 */ + + /* Rx Drop */ + u32 rx_drops; /* 0x60DC */ + + /* Tx Byte Counter */ + u32 tx_bytes; /* 0x60E0 */ + + /* Tx Packet Counter */ + u32 tx_packets; /* 0x60E4 */ + + /* Tx Multicast Packet Counter */ + u32 tx_multicast_packets; /* 0x60E8 */ + + /* Tx Broadcast Packet Counter */ + u32 tx_broadcast_packets; /* 0x60EC */ + + /* Tx Pause Control Frame Counter */ + u32 tx_pause_frames; /* 0x60F0 */ + + /* Tx Deferral Packet Counter */ + u32 tx_deferred; /* 0x60F4 */ + + /* Tx Excessive Deferral Packet Counter */ + u32 tx_excessive_deferred; /* 0x60F8 */ + + /* Tx Single Collision Packet Counter */ + u32 tx_single_collisions; /* 0x60FC */ + + /* Tx Multiple Collision Packet Counter */ + u32 tx_multiple_collisions; /* 0x6100 */ + + /* Tx Late Collision Packet Counter */ + u32 tx_late_collisions; /* 0x6104 */ + + /* Tx Excessive Collision Packet Counter */ + u32 tx_excessive_collisions; /* 0x6108 */ + + /* Tx Total Collision Packet Counter */ + u32 tx_total_collisions; /* 0x610C */ + + /* Tx Pause Frame Honored Counter */ + u32 tx_pause_honored_frames; /* 0x6110 */ + + /* Tx Drop Frame Counter */ + u32 tx_drops; /* 0x6114 */ + + /* Tx Jabber Frame Counter */ + u32 tx_jabbers; /* 0x6118 */ + + /* Tx FCS Error Counter */ + u32 tx_fcs_errs; /* 0x611C */ + + /* Tx Control Frame Counter */ + u32 tx_control_frames; /* 0x6120 */ + + /* Tx Oversize Frame Counter */ + u32 tx_oversize_frames; /* 0x6124 */ + + /* Tx Undersize Frame Counter */ + u32 tx_undersize_frames; /* 0x6128 */ + + /* Tx Fragments Frame Counter */ + u32 tx_fragments; /* 0x612C */ + + /* Carry Register One Register */ + u32 carry_reg1; /* 0x6130 */ + + /* Carry Register Two Register */ + u32 carry_reg2; /* 0x6134 */ + + /* Carry Register One Mask Register */ + u32 carry_reg1_mask; /* 0x6138 */ + + /* Carry Register Two Mask Register */ + u32 carry_reg2_mask; /* 0x613C */ +}; + +/* END OF MAC STAT REGISTER ADDRESS MAP */ + +/* START OF MMC REGISTER ADDRESS MAP */ + +/* + * Main Memory Controller Control reg in mmc address map. + * located at address 0x7000 + */ + +#define ET_MMC_ENABLE 1 +#define ET_MMC_ARB_DISABLE 2 +#define ET_MMC_RXMAC_DISABLE 4 +#define ET_MMC_TXMAC_DISABLE 8 +#define ET_MMC_TXDMA_DISABLE 16 +#define ET_MMC_RXDMA_DISABLE 32 +#define ET_MMC_FORCE_CE 64 + +/* + * Main Memory Controller Host Memory Access Address reg in mmc + * address map. Located at address 0x7004. Top 16 bits hold the address bits + */ + +#define ET_SRAM_REQ_ACCESS 1 +#define ET_SRAM_WR_ACCESS 2 +#define ET_SRAM_IS_CTRL 4 + +/* + * structure for Main Memory Controller Host Memory Access Data reg in mmc + * address map. Located at address 0x7008 - 0x7014 + * Defined earlier (u32) + */ + +/* + * Memory Control Module of JAGCore Address Mapping + */ +struct mmc_regs { /* Location: */ + u32 mmc_ctrl; /* 0x7000 */ + u32 sram_access; /* 0x7004 */ + u32 sram_word1; /* 0x7008 */ + u32 sram_word2; /* 0x700C */ + u32 sram_word3; /* 0x7010 */ + u32 sram_word4; /* 0x7014 */ +}; + +/* END OF MMC REGISTER ADDRESS MAP */ + + +/* + * JAGCore Address Mapping + */ +struct address_map { + struct global_regs global; + /* unused section of global address map */ + u8 unused_global[4096 - sizeof(struct global_regs)]; + struct txdma_regs txdma; + /* unused section of txdma address map */ + u8 unused_txdma[4096 - sizeof(struct txdma_regs)]; + struct rxdma_regs rxdma; + /* unused section of rxdma address map */ + u8 unused_rxdma[4096 - sizeof(struct rxdma_regs)]; + struct txmac_regs txmac; + /* unused section of txmac address map */ + u8 unused_txmac[4096 - sizeof(struct txmac_regs)]; + struct rxmac_regs rxmac; + /* unused section of rxmac address map */ + u8 unused_rxmac[4096 - sizeof(struct rxmac_regs)]; + struct mac_regs mac; + /* unused section of mac address map */ + u8 unused_mac[4096 - sizeof(struct mac_regs)]; + struct macstat_regs macstat; + /* unused section of mac stat address map */ + u8 unused_mac_stat[4096 - sizeof(struct macstat_regs)]; + struct mmc_regs mmc; + /* unused section of mmc address map */ + u8 unused_mmc[4096 - sizeof(struct mmc_regs)]; + /* unused section of address map */ + u8 unused_[1015808]; + + u8 unused_exp_rom[4096]; /* MGS-size TBD */ + u8 unused__[524288]; /* unused section of address map */ +}; + +/* + * Defines for generic MII registers 0x00 -> 0x0F can be found in + * include/linux/mii.h + */ + +/* some defines for modem registers that seem to be 'reserved' */ +#define PHY_INDEX_REG 0x10 +#define PHY_DATA_REG 0x11 +#define PHY_MPHY_CONTROL_REG 0x12 + +/* defines for specified registers */ +#define PHY_LOOPBACK_CONTROL 0x13 /* TRU_VMI_LOOPBACK_CONTROL_1_REG 19 */ + /* TRU_VMI_LOOPBACK_CONTROL_2_REG 20 */ +#define PHY_REGISTER_MGMT_CONTROL 0x15 /* TRU_VMI_MI_SEQ_CONTROL_REG 21 */ +#define PHY_CONFIG 0x16 /* TRU_VMI_CONFIGURATION_REG 22 */ +#define PHY_PHY_CONTROL 0x17 /* TRU_VMI_PHY_CONTROL_REG 23 */ +#define PHY_INTERRUPT_MASK 0x18 /* TRU_VMI_INTERRUPT_MASK_REG 24 */ +#define PHY_INTERRUPT_STATUS 0x19 /* TRU_VMI_INTERRUPT_STATUS_REG 25 */ +#define PHY_PHY_STATUS 0x1A /* TRU_VMI_PHY_STATUS_REG 26 */ +#define PHY_LED_1 0x1B /* TRU_VMI_LED_CONTROL_1_REG 27 */ +#define PHY_LED_2 0x1C /* TRU_VMI_LED_CONTROL_2_REG 28 */ + /* TRU_VMI_LINK_CONTROL_REG 29 */ + /* TRU_VMI_TIMING_CONTROL_REG */ + +/* MI Register 10: Gigabit basic mode status reg(Reg 0x0A) */ +#define ET_1000BT_MSTR_SLV 0x4000 + +/* MI Register 16 - 18: Reserved Reg(0x10-0x12) */ + +/* MI Register 19: Loopback Control Reg(0x13) + * 15: mii_en + * 14: pcs_en + * 13: pmd_en + * 12: all_digital_en + * 11: replica_en + * 10: line_driver_en + * 9-0: reserved + */ + +/* MI Register 20: Reserved Reg(0x14) */ + +/* MI Register 21: Management Interface Control Reg(0x15) + * 15-11: reserved + * 10-4: mi_error_count + * 3: reserved + * 2: ignore_10g_fr + * 1: reserved + * 0: preamble_suppress_en + */ + +/* MI Register 22: PHY Configuration Reg(0x16) + * 15: crs_tx_en + * 14: reserved + * 13-12: tx_fifo_depth + * 11-10: speed_downshift + * 9: pbi_detect + * 8: tbi_rate + * 7: alternate_np + * 6: group_mdio_en + * 5: tx_clock_en + * 4: sys_clock_en + * 3: reserved + * 2-0: mac_if_mode + */ + +#define ET_PHY_CONFIG_TX_FIFO_DEPTH 0x3000 + +#define ET_PHY_CONFIG_FIFO_DEPTH_8 0x0000 +#define ET_PHY_CONFIG_FIFO_DEPTH_16 0x1000 +#define ET_PHY_CONFIG_FIFO_DEPTH_32 0x2000 +#define ET_PHY_CONFIG_FIFO_DEPTH_64 0x3000 + +/* MI Register 23: PHY CONTROL Reg(0x17) + * 15: reserved + * 14: tdr_en + * 13: reserved + * 12-11: downshift_attempts + * 10-6: reserved + * 5: jabber_10baseT + * 4: sqe_10baseT + * 3: tp_loopback_10baseT + * 2: preamble_gen_en + * 1: reserved + * 0: force_int + */ + +/* MI Register 24: Interrupt Mask Reg(0x18) + * 15-10: reserved + * 9: mdio_sync_lost + * 8: autoneg_status + * 7: hi_bit_err + * 6: np_rx + * 5: err_counter_full + * 4: fifo_over_underflow + * 3: rx_status + * 2: link_status + * 1: automatic_speed + * 0: int_en + */ + +#define ET_PHY_INT_MASK_AUTONEGSTAT 0x0100 +#define ET_PHY_INT_MASK_LINKSTAT 0x0004 +#define ET_PHY_INT_MASK_ENABLE 0x0001 + +/* MI Register 25: Interrupt Status Reg(0x19) + * 15-10: reserved + * 9: mdio_sync_lost + * 8: autoneg_status + * 7: hi_bit_err + * 6: np_rx + * 5: err_counter_full + * 4: fifo_over_underflow + * 3: rx_status + * 2: link_status + * 1: automatic_speed + * 0: int_en + */ + +/* MI Register 26: PHY Status Reg(0x1A) + * 15: reserved + * 14-13: autoneg_fault + * 12: autoneg_status + * 11: mdi_x_status + * 10: polarity_status + * 9-8: speed_status + * 7: duplex_status + * 6: link_status + * 5: tx_status + * 4: rx_status + * 3: collision_status + * 2: autoneg_en + * 1: pause_en + * 0: asymmetric_dir + */ +#define ET_PHY_AUTONEG_STATUS 0x1000 +#define ET_PHY_POLARITY_STATUS 0x0400 +#define ET_PHY_SPEED_STATUS 0x0300 +#define ET_PHY_DUPLEX_STATUS 0x0080 +#define ET_PHY_LSTATUS 0x0040 +#define ET_PHY_AUTONEG_ENABLE 0x0020 + +/* MI Register 27: LED Control Reg 1(0x1B) + * 15-14: reserved + * 13-12: led_dup_indicate + * 11-10: led_10baseT + * 9-8: led_collision + * 7-4: reserved + * 3-2: pulse_dur + * 1: pulse_stretch1 + * 0: pulse_stretch0 + */ + +/* MI Register 28: LED Control Reg 2(0x1C) + * 15-12: led_link + * 11-8: led_tx_rx + * 7-4: led_100BaseTX + * 3-0: led_1000BaseT + */ +#define ET_LED2_LED_LINK 0xF000 +#define ET_LED2_LED_TXRX 0x0F00 +#define ET_LED2_LED_100TX 0x00F0 +#define ET_LED2_LED_1000T 0x000F + +/* defines for LED control reg 2 values */ +#define LED_VAL_1000BT 0x0 +#define LED_VAL_100BTX 0x1 +#define LED_VAL_10BT 0x2 +#define LED_VAL_1000BT_100BTX 0x3 /* 1000BT on, 100BTX blink */ +#define LED_VAL_LINKON 0x4 +#define LED_VAL_TX 0x5 +#define LED_VAL_RX 0x6 +#define LED_VAL_TXRX 0x7 /* TX or RX */ +#define LED_VAL_DUPLEXFULL 0x8 +#define LED_VAL_COLLISION 0x9 +#define LED_VAL_LINKON_ACTIVE 0xA /* Link on, activity blink */ +#define LED_VAL_LINKON_RECV 0xB /* Link on, receive blink */ +#define LED_VAL_DUPLEXFULL_COLLISION 0xC /* Duplex on, collision blink */ +#define LED_VAL_BLINK 0xD +#define LED_VAL_ON 0xE +#define LED_VAL_OFF 0xF + +#define LED_LINK_SHIFT 12 +#define LED_TXRX_SHIFT 8 +#define LED_100TX_SHIFT 4 + +/* MI Register 29 - 31: Reserved Reg(0x1D - 0x1E) */ + +/* Defines for PHY access routines */ + +/* Define bit operation flags */ +#define TRUEPHY_BIT_CLEAR 0 +#define TRUEPHY_BIT_SET 1 +#define TRUEPHY_BIT_READ 2 + +/* Define read/write operation flags */ +#ifndef TRUEPHY_READ +#define TRUEPHY_READ 0 +#define TRUEPHY_WRITE 1 +#define TRUEPHY_MASK 2 +#endif + +/* Define master/slave configuration values */ +#define TRUEPHY_CFG_SLAVE 0 +#define TRUEPHY_CFG_MASTER 1 + +/* Define MDI/MDI-X settings */ +#define TRUEPHY_MDI 0 +#define TRUEPHY_MDIX 1 +#define TRUEPHY_AUTO_MDI_MDIX 2 + +/* Define 10Base-T link polarities */ +#define TRUEPHY_POLARITY_NORMAL 0 +#define TRUEPHY_POLARITY_INVERTED 1 + +/* Define auto-negotiation results */ +#define TRUEPHY_ANEG_NOT_COMPLETE 0 +#define TRUEPHY_ANEG_COMPLETE 1 +#define TRUEPHY_ANEG_DISABLED 2 + +/* Define duplex advertisement flags */ +#define TRUEPHY_ADV_DUPLEX_NONE 0x00 +#define TRUEPHY_ADV_DUPLEX_FULL 0x01 +#define TRUEPHY_ADV_DUPLEX_HALF 0x02 +#define TRUEPHY_ADV_DUPLEX_BOTH \ + (TRUEPHY_ADV_DUPLEX_FULL | TRUEPHY_ADV_DUPLEX_HALF) + diff --git a/drivers/staging/frontier/Kconfig b/drivers/staging/frontier/Kconfig new file mode 100644 index 00000000..7121853b --- /dev/null +++ b/drivers/staging/frontier/Kconfig @@ -0,0 +1,6 @@ +config TRANZPORT + tristate "Frontier Tranzport and Alphatrack support" + depends on USB + default N + ---help--- + Enable support for the Frontier Tranzport and Alphatrack devices. diff --git a/drivers/staging/frontier/Makefile b/drivers/staging/frontier/Makefile new file mode 100644 index 00000000..2d2ac974 --- /dev/null +++ b/drivers/staging/frontier/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_TRANZPORT) += tranzport.o +obj-$(CONFIG_TRANZPORT) += alphatrack.o diff --git a/drivers/staging/frontier/README b/drivers/staging/frontier/README new file mode 100644 index 00000000..cd07af22 --- /dev/null +++ b/drivers/staging/frontier/README @@ -0,0 +1,47 @@ +This directory contains the Linux USB Tranzport and Alphatrack Kernel drivers. + +See http://www.frontierdesign.com for details on these devices. + +Userspace test code is available from + +git://toutatis.isc.org/home/d/src/git/frontier.git + +At present the tranzport does reads/writes of 8 byte cmds to +/dev/tranzport0 to control the lights, screen, and wheel. + +At present the alphatrack accepts reads/writes of 12 byte cmds to +/dev/tranzport0 to control the lights, screen, fader and touchpad. + +The tranzport driver provides a rudimentary sysfs interface for the status of +the device and a writable parameter for turning wheel compression on and off. + +The API is nothing more than the USB commands issued to the device. Why? + +The control wheel/fader can generate events far too quickly for +a typical userspace application to keep up with them via libusb. Input +needs to be 100% accurate and fast in order for the alphatrack or tranzport +to be useful. + +UIO would be useful except that usb disconnect events need +to be handled correctly. + +A sysfs interface is perfect for simple userspace apps to do fun things with +the lights and screen. But it's fairly lousy for handling input events and +very lousy for watching the state of the shuttle wheel. + +A linux input events interface is great for the input events and shuttle wheel. +* It's theoretically OK on LEDs. +* A fader can be mapped to an absolute mouse device. +* But there is no LCD support at all, or fader feedback support in that API + +So, thus, these stubby drivers exist. + +In the end this could be driven by a midi layer, which handles all those +cases via a well defined API, but - among other things - is slow, doesn't do +flow control, and is a LOT of extra work, none of which is required at +the kernel level (probably). Frankly, I'd like to keep the +core driver simple because the only realtime work really required is +the bottom half interrupt handler and the output overlapping. + +Exposing some sort of clean api to userspace would be perfect. What that +API looks like? Gah. beats me. diff --git a/drivers/staging/frontier/TODO b/drivers/staging/frontier/TODO new file mode 100644 index 00000000..3620ad2d --- /dev/null +++ b/drivers/staging/frontier/TODO @@ -0,0 +1,9 @@ +TODO: + - checkpatch.pl clean + - sparse clean + - fix userspace interface to be sane + - possibly just port to userspace with libusb + - review by the USB developer community + +Please send any patches for this driver to Greg Kroah-Hartman +and David Taht . diff --git a/drivers/staging/frontier/alphatrack.c b/drivers/staging/frontier/alphatrack.c new file mode 100644 index 00000000..3bf0f40e --- /dev/null +++ b/drivers/staging/frontier/alphatrack.c @@ -0,0 +1,870 @@ +/* + * Frontier Designs Alphatrack driver + * + * Copyright (C) 2007 Michael Taht (m@taht.net) + * + * Based on the usbled driver and ldusb drivers by + * + * Copyright (C) 2004 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2005 Michael Hund + * + * The ldusb driver was, in turn, derived from Lego USB Tower driver + * Copyright (C) 2003 David Glance + * 2001-2004 Juergen Stuber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +/** + * This driver uses a ring buffer for time critical reading of + * interrupt in reports and provides read and write methods for + * raw interrupt reports. + */ + +/* Note: this currently uses a dumb ringbuffer for reads and writes. + * A more optimal driver would cache and kill off outstanding urbs that are + * now invalid, and ignore ones that already were in the queue but valid + * as we only have 30 commands for the alphatrack. In particular this is + * key for getting lights to flash in time as otherwise many commands + * can be buffered up before the light change makes it to the interface. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "alphatrack.h" + +#define VENDOR_ID 0x165b +#define PRODUCT_ID 0xfad1 + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define USB_ALPHATRACK_MINOR_BASE 0 +#else +/* FIXME 176 - is another driver's minor - apply for that */ +#define USB_ALPHATRACK_MINOR_BASE 176 +#endif + +/* table of devices that work with this driver */ +static const struct usb_device_id usb_alphatrack_table[] = { + {USB_DEVICE(VENDOR_ID, PRODUCT_ID)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_alphatrack_table); +MODULE_VERSION("0.41"); +MODULE_AUTHOR("Mike Taht "); +MODULE_DESCRIPTION("Alphatrack USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("Frontier Designs Alphatrack Control Surface"); + +/* These aren't done yet */ + +#define SUPPRESS_EXTRA_ONLINE_EVENTS 0 +#define BUFFERED_WRITES 0 +#define SUPPRESS_EXTRA_OFFLINE_EVENTS 0 +#define COMPRESS_FADER_EVENTS 0 + +#define BUFFERED_READS 1 +#define RING_BUFFER_SIZE 512 +#define WRITE_BUFFER_SIZE 34 +#define ALPHATRACK_USB_TIMEOUT 10 +#define OUTPUT_CMD_SIZE 8 +#define INPUT_CMD_SIZE 12 +#define ALPHATRACK_DEBUG 0 + +static int debug = ALPHATRACK_DEBUG; + +/* Use our own dbg macro */ +#define dbg_info(dev, format, arg...) do \ + { if (debug) dev_info(dev , format , ## arg); } while (0) + +#define alphatrack_ocmd_info(dev, cmd, format, arg...) + +#define alphatrack_icmd_info(dev, cmd, format, arg...) + +/* Module parameters */ + +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +/* All interrupt in transfers are collected in a ring buffer to + * avoid racing conditions and get better performance of the driver. + */ + +static int ring_buffer_size = RING_BUFFER_SIZE; + +module_param(ring_buffer_size, int, S_IRUGO); +MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size"); + +/* The write_buffer can one day contain more than one interrupt out transfer. + */ + +static int write_buffer_size = WRITE_BUFFER_SIZE; +module_param(write_buffer_size, int, S_IRUGO); +MODULE_PARM_DESC(write_buffer_size, "Write buffer size"); + +/* + * Increase the interval for debugging purposes. + * or set to 1 to use the standard interval from the endpoint descriptors. + */ + +static int min_interrupt_in_interval = ALPHATRACK_USB_TIMEOUT; +module_param(min_interrupt_in_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_in_interval, + "Minimum interrupt in interval in ms"); + +static int min_interrupt_out_interval = ALPHATRACK_USB_TIMEOUT; +module_param(min_interrupt_out_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_out_interval, + "Minimum interrupt out interval in ms"); + +/* Structure to hold all of our device specific stuff */ + +struct usb_alphatrack { + struct mutex mtx; /* locks this structure */ + struct usb_interface *intf; /* save off the usb interface pointer */ + int open_count; /* number of times this port has been opened */ + + /* make gcc happy */ + struct alphatrack_icmd (*ring_buffer)[RING_BUFFER_SIZE]; + struct alphatrack_ocmd (*write_buffer)[WRITE_BUFFER_SIZE]; + unsigned int ring_head; + unsigned int ring_tail; + + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + unsigned char *interrupt_in_buffer; + unsigned char *oldi_buffer; + struct usb_endpoint_descriptor *interrupt_in_endpoint; + struct urb *interrupt_in_urb; + int interrupt_in_interval; + size_t interrupt_in_endpoint_size; + int interrupt_in_running; + int interrupt_in_done; + + char *interrupt_out_buffer; + struct usb_endpoint_descriptor *interrupt_out_endpoint; + struct urb *interrupt_out_urb; + int interrupt_out_interval; + size_t interrupt_out_endpoint_size; + int interrupt_out_busy; + + atomic_t writes_pending; + int event; /* alternate interface to events */ + int fader; /* 10 bits */ + int lights; /* 23 bits */ + unsigned char dump_state; /* 0 if disabled 1 if enabled */ + unsigned char enable; /* 0 if disabled 1 if enabled */ + unsigned char offline; /* if the device is out of range or asleep */ + unsigned char verbose; /* be verbose in error reporting */ + unsigned char last_cmd[OUTPUT_CMD_SIZE]; + unsigned char screen[32]; +}; + +/* prevent races between open() and disconnect() */ +static DEFINE_MUTEX(disconnect_mutex); + +/* forward declaration */ + +static struct usb_driver usb_alphatrack_driver; + +/** + * usb_alphatrack_abort_transfers + * aborts transfers and frees associated data structures + */ +static void usb_alphatrack_abort_transfers(struct usb_alphatrack *dev) +{ + /* shutdown transfer */ + if (dev->interrupt_in_running) { + dev->interrupt_in_running = 0; + if (dev->intf) + usb_kill_urb(dev->interrupt_in_urb); + } + if (dev->interrupt_out_busy) + if (dev->intf) + usb_kill_urb(dev->interrupt_out_urb); +} + +/** + * usb_alphatrack_delete + */ +static void usb_alphatrack_delete(struct usb_alphatrack *dev) +{ + usb_alphatrack_abort_transfers(dev); + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree(dev->ring_buffer); + kfree(dev->interrupt_in_buffer); + kfree(dev->interrupt_out_buffer); + kfree(dev); /* fixme oldi_buffer */ +} + +/** + * usb_alphatrack_interrupt_in_callback + */ + +static void usb_alphatrack_interrupt_in_callback(struct urb *urb) +{ + struct usb_alphatrack *dev = urb->context; + unsigned int next_ring_head; + int retval = -1; + + if (urb->status) { + if (urb->status == -ENOENT || + urb->status == -ECONNRESET || urb->status == -ESHUTDOWN) { + goto exit; + } else { + dbg_info(&dev->intf->dev, + "%s: nonzero status received: %d\n", __func__, + urb->status); + goto resubmit; /* maybe we can recover */ + } + } + + if (urb->actual_length != INPUT_CMD_SIZE) { + dev_warn(&dev->intf->dev, + "Urb length was %d bytes!!" + "Do something intelligent\n", urb->actual_length); + } else { + alphatrack_ocmd_info(&dev->intf->dev, + &(*dev->ring_buffer)[dev->ring_tail].cmd, + "%s", "bla"); + if (memcmp + (dev->interrupt_in_buffer, dev->oldi_buffer, + INPUT_CMD_SIZE) == 0) { + goto resubmit; + } + memcpy(dev->oldi_buffer, dev->interrupt_in_buffer, + INPUT_CMD_SIZE); + +#if SUPPRESS_EXTRA_OFFLINE_EVENTS + if (dev->offline == 2 && dev->interrupt_in_buffer[1] == 0xff) + goto resubmit; + if (dev->offline == 1 && dev->interrupt_in_buffer[1] == 0xff) { + dev->offline = 2; + goto resubmit; + } +/* Always pass one offline event up the stack */ + if (dev->offline > 0 && dev->interrupt_in_buffer[1] != 0xff) + dev->offline = 0; + if (dev->offline == 0 && dev->interrupt_in_buffer[1] == 0xff) + dev->offline = 1; +#endif + dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", + __func__, dev->ring_head, dev->ring_tail); + next_ring_head = (dev->ring_head + 1) % ring_buffer_size; + + if (next_ring_head != dev->ring_tail) { + memcpy(&((*dev->ring_buffer)[dev->ring_head]), + dev->interrupt_in_buffer, urb->actual_length); + dev->ring_head = next_ring_head; + retval = 0; + memset(dev->interrupt_in_buffer, 0, urb->actual_length); + } else { + dev_warn(&dev->intf->dev, + "Ring buffer overflow, %d bytes dropped\n", + urb->actual_length); + memset(dev->interrupt_in_buffer, 0, urb->actual_length); + } + } + +resubmit: + /* resubmit if we're still running */ + if (dev->interrupt_in_running && dev->intf) { + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->intf->dev, + "usb_submit_urb failed (%d)\n", retval); + } + +exit: + dev->interrupt_in_done = 1; + wake_up_interruptible(&dev->read_wait); +} + +/** + * usb_alphatrack_interrupt_out_callback + */ +static void usb_alphatrack_interrupt_out_callback(struct urb *urb) +{ + struct usb_alphatrack *dev = urb->context; + + /* sync/async unlink faults aren't errors */ + if (urb->status && !(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dbg_info(&dev->intf->dev, + "%s - nonzero write interrupt status received: %d\n", + __func__, urb->status); + atomic_dec(&dev->writes_pending); + dev->interrupt_out_busy = 0; + wake_up_interruptible(&dev->write_wait); +} + +/** + * usb_alphatrack_open + */ +static int usb_alphatrack_open(struct inode *inode, struct file *file) +{ + struct usb_alphatrack *dev; + int subminor; + int retval = 0; + struct usb_interface *interface; + + nonseekable_open(inode, file); + subminor = iminor(inode); + + mutex_lock(&disconnect_mutex); + + interface = usb_find_interface(&usb_alphatrack_driver, subminor); + + if (!interface) { + err("%s - error, can't find device for minor %d\n", + __func__, subminor); + retval = -ENODEV; + goto unlock_disconnect_exit; + } + + dev = usb_get_intfdata(interface); + + if (!dev) { + retval = -ENODEV; + goto unlock_disconnect_exit; + } + + /* lock this device */ + if (mutex_lock_interruptible(&dev->mtx)) { + retval = -ERESTARTSYS; + goto unlock_disconnect_exit; + } + + /* allow opening only once */ + if (dev->open_count) { + retval = -EBUSY; + goto unlock_exit; + } + dev->open_count = 1; + + /* initialize in direction */ + dev->ring_head = 0; + dev->ring_tail = 0; + usb_fill_int_urb(dev->interrupt_in_urb, + interface_to_usbdev(interface), + usb_rcvintpipe(interface_to_usbdev(interface), + dev->interrupt_in_endpoint-> + bEndpointAddress), + dev->interrupt_in_buffer, + dev->interrupt_in_endpoint_size, + usb_alphatrack_interrupt_in_callback, dev, + dev->interrupt_in_interval); + + dev->interrupt_in_running = 1; + dev->interrupt_in_done = 0; + dev->enable = 1; + dev->offline = 0; + + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&interface->dev, + "Couldn't submit interrupt_in_urb %d\n", retval); + dev->interrupt_in_running = 0; + dev->open_count = 0; + goto unlock_exit; + } + + /* save device in the file's private structure */ + file->private_data = dev; + +unlock_exit: + mutex_unlock(&dev->mtx); + +unlock_disconnect_exit: + mutex_unlock(&disconnect_mutex); + + return retval; +} + +/** + * usb_alphatrack_release + */ +static int usb_alphatrack_release(struct inode *inode, struct file *file) +{ + struct usb_alphatrack *dev; + int retval = 0; + + dev = file->private_data; + + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + if (mutex_lock_interruptible(&dev->mtx)) { + retval = -ERESTARTSYS; + goto exit; + } + + if (dev->open_count != 1) { + retval = -ENODEV; + goto unlock_exit; + } + + if (dev->intf == NULL) { + /* the device was unplugged before the file was released */ + mutex_unlock(&dev->mtx); + /* unlock here as usb_alphatrack_delete frees dev */ + usb_alphatrack_delete(dev); + retval = -ENODEV; + goto exit; + } + + /* wait until write transfer is finished */ + if (dev->interrupt_out_busy) + wait_event_interruptible_timeout(dev->write_wait, + !dev->interrupt_out_busy, + 2 * HZ); + usb_alphatrack_abort_transfers(dev); + dev->open_count = 0; + +unlock_exit: + mutex_unlock(&dev->mtx); + +exit: + return retval; +} + +/** + * usb_alphatrack_poll + */ +static unsigned int usb_alphatrack_poll(struct file *file, poll_table *wait) +{ + struct usb_alphatrack *dev; + unsigned int mask = 0; + + dev = file->private_data; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + if (dev->ring_head != dev->ring_tail) + mask |= POLLIN | POLLRDNORM; + if (!dev->interrupt_out_busy) + mask |= POLLOUT | POLLWRNORM; + + return mask; +} + +/** + * usb_alphatrack_read + */ +static ssize_t usb_alphatrack_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct usb_alphatrack *dev; + int retval = 0; + + int c = 0; + + dev = file->private_data; + + /* verify that we actually have some data to read */ + if (count == 0) + goto exit; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->mtx)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->intf == NULL) { + retval = -ENODEV; + err("No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + while (dev->ring_head == dev->ring_tail) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + dev->interrupt_in_done = 0; + retval = + wait_event_interruptible(dev->read_wait, + dev->interrupt_in_done); + if (retval < 0) + goto unlock_exit; + } + + alphatrack_ocmd_info(&dev->intf->dev, + &(*dev->ring_buffer)[dev->ring_tail].cmd, "%s", + ": copying to userspace"); + + c = 0; + while ((c < count) && (dev->ring_tail != dev->ring_head)) { + if (copy_to_user + (&buffer[c], &(*dev->ring_buffer)[dev->ring_tail], + INPUT_CMD_SIZE)) { + retval = -EFAULT; + goto unlock_exit; + } + dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size; + c += INPUT_CMD_SIZE; + dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", + __func__, dev->ring_head, dev->ring_tail); + } + retval = c; + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->mtx); + +exit: + return retval; +} + +/** + * usb_alphatrack_write + */ +static ssize_t usb_alphatrack_write(struct file *file, + const char __user *buffer, size_t count, + loff_t *ppos) +{ + struct usb_alphatrack *dev; + size_t bytes_to_write; + int retval = 0; + + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->mtx)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->intf == NULL) { + retval = -ENODEV; + err("No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* wait until previous transfer is finished */ + if (dev->interrupt_out_busy) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = + wait_event_interruptible(dev->write_wait, + !dev->interrupt_out_busy); + if (retval < 0) + goto unlock_exit; + } + + /* write the data into interrupt_out_buffer from userspace */ + /* FIXME - if you write more than 12 bytes this breaks */ + bytes_to_write = + min(count, write_buffer_size * dev->interrupt_out_endpoint_size); + if (bytes_to_write < count) + dev_warn(&dev->intf->dev, + "Write buffer overflow, %zd bytes dropped\n", + count - bytes_to_write); + + dbg_info(&dev->intf->dev, "%s: count = %zd, bytes_to_write = %zd\n", + __func__, count, bytes_to_write); + + if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) { + retval = -EFAULT; + goto unlock_exit; + } + + if (dev->interrupt_out_endpoint == NULL) { + err("Endpoint should not be be null!\n"); + goto unlock_exit; + } + + /* send off the urb */ + usb_fill_int_urb(dev->interrupt_out_urb, + interface_to_usbdev(dev->intf), + usb_sndintpipe(interface_to_usbdev(dev->intf), + dev->interrupt_out_endpoint-> + bEndpointAddress), + dev->interrupt_out_buffer, bytes_to_write, + usb_alphatrack_interrupt_out_callback, dev, + dev->interrupt_out_interval); + dev->interrupt_out_busy = 1; + atomic_inc(&dev->writes_pending); + wmb(); + + retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); + if (retval) { + dev->interrupt_out_busy = 0; + err("Couldn't submit interrupt_out_urb %d\n", retval); + atomic_dec(&dev->writes_pending); + goto unlock_exit; + } + retval = bytes_to_write; + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->mtx); + +exit: + return retval; +} + +/* file operations needed when we register this driver */ +static const struct file_operations usb_alphatrack_fops = { + .owner = THIS_MODULE, + .read = usb_alphatrack_read, + .write = usb_alphatrack_write, + .open = usb_alphatrack_open, + .release = usb_alphatrack_release, + .poll = usb_alphatrack_poll, + .llseek = no_llseek, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ + +static struct usb_class_driver usb_alphatrack_class = { + .name = "alphatrack%d", + .fops = &usb_alphatrack_fops, + .minor_base = USB_ALPHATRACK_MINOR_BASE, +}; + +/** + * usb_alphatrack_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int usb_alphatrack_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_alphatrack *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int i; + int true_size; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&intf->dev, "Out of memory\n"); + goto exit; + } + mutex_init(&dev->mtx); + dev->intf = intf; + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + iface_desc = intf->cur_altsetting; + + /* set up the endpoint information */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) + dev->interrupt_in_endpoint = endpoint; + + if (usb_endpoint_is_int_out(endpoint)) + dev->interrupt_out_endpoint = endpoint; + } + if (dev->interrupt_in_endpoint == NULL) { + dev_err(&intf->dev, "Interrupt in endpoint not found\n"); + goto error; + } + if (dev->interrupt_out_endpoint == NULL) + dev_warn(&intf->dev, + "Interrupt out endpoint not found" + "(using control endpoint instead)\n"); + + dev->interrupt_in_endpoint_size = + le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize); + + if (dev->interrupt_in_endpoint_size != 64) + dev_warn(&intf->dev, "Interrupt in endpoint size is not 64!\n"); + + if (ring_buffer_size == 0) + ring_buffer_size = RING_BUFFER_SIZE; + + true_size = min(ring_buffer_size, RING_BUFFER_SIZE); + + /* FIXME - there are more usb_alloc routines for dma correctness. + Needed? */ + dev->ring_buffer = + kmalloc((true_size * sizeof(struct alphatrack_icmd)), GFP_KERNEL); + + if (!dev->ring_buffer) { + dev_err(&intf->dev, + "Couldn't allocate input ring_buffer of size %d\n", + true_size); + goto error; + } + + dev->interrupt_in_buffer = + kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); + + if (!dev->interrupt_in_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_buffer\n"); + goto error; + } + dev->oldi_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); + if (!dev->oldi_buffer) { + dev_err(&intf->dev, "Couldn't allocate old buffer\n"); + goto error; + } + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_urb\n"); + goto error; + } + + dev->interrupt_out_endpoint_size = + dev->interrupt_out_endpoint ? le16_to_cpu(dev-> + interrupt_out_endpoint-> + wMaxPacketSize) : udev-> + descriptor.bMaxPacketSize0; + + if (dev->interrupt_out_endpoint_size != 64) + dev_warn(&intf->dev, + "Interrupt out endpoint size is not 64!)\n"); + + if (write_buffer_size == 0) + write_buffer_size = WRITE_BUFFER_SIZE; + true_size = min(write_buffer_size, WRITE_BUFFER_SIZE); + + dev->interrupt_out_buffer = + kmalloc(true_size * dev->interrupt_out_endpoint_size, GFP_KERNEL); + + if (!dev->interrupt_out_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_buffer\n"); + goto error; + } + + dev->write_buffer = + kmalloc(true_size * sizeof(struct alphatrack_ocmd), GFP_KERNEL); + + if (!dev->write_buffer) { + dev_err(&intf->dev, "Couldn't allocate write_buffer\n"); + goto error; + } + + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_urb\n"); + goto error; + } + dev->interrupt_in_interval = + min_interrupt_in_interval > + dev->interrupt_in_endpoint-> + bInterval ? min_interrupt_in_interval : dev->interrupt_in_endpoint-> + bInterval; + if (dev->interrupt_out_endpoint) + dev->interrupt_out_interval = + min_interrupt_out_interval > + dev->interrupt_out_endpoint-> + bInterval ? min_interrupt_out_interval : dev-> + interrupt_out_endpoint->bInterval; + + /* we can register the device now, as it is ready */ + usb_set_intfdata(intf, dev); + + atomic_set(&dev->writes_pending, 0); + retval = usb_register_dev(intf, &usb_alphatrack_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&intf->dev, + "Not able to get a minor for this device.\n"); + usb_set_intfdata(intf, NULL); + goto error; + } + + /* let the user know what node this device is now attached to */ + dev_info(&intf->dev, + "Alphatrack Device #%d now attached to major %d minor %d\n", + (intf->minor - USB_ALPHATRACK_MINOR_BASE), USB_MAJOR, + intf->minor); + +exit: + return retval; + +error: + usb_alphatrack_delete(dev); + + return retval; +} + +/** + * usb_alphatrack_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void usb_alphatrack_disconnect(struct usb_interface *intf) +{ + struct usb_alphatrack *dev; + int minor; + + mutex_lock(&disconnect_mutex); + + dev = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + + mutex_lock(&dev->mtx); + + minor = intf->minor; + + /* give back our minor */ + usb_deregister_dev(intf, &usb_alphatrack_class); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) { + mutex_unlock(&dev->mtx); + usb_alphatrack_delete(dev); + } else { + dev->intf = NULL; + mutex_unlock(&dev->mtx); + } + + atomic_set(&dev->writes_pending, 0); + mutex_unlock(&disconnect_mutex); + + dev_info(&intf->dev, "Alphatrack Surface #%d now disconnected\n", + (minor - USB_ALPHATRACK_MINOR_BASE)); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver usb_alphatrack_driver = { + .name = "alphatrack", + .probe = usb_alphatrack_probe, + .disconnect = usb_alphatrack_disconnect, + .id_table = usb_alphatrack_table, +}; + +module_usb_driver(usb_alphatrack_driver); diff --git a/drivers/staging/frontier/alphatrack.h b/drivers/staging/frontier/alphatrack.h new file mode 100644 index 00000000..10a79726 --- /dev/null +++ b/drivers/staging/frontier/alphatrack.h @@ -0,0 +1,76 @@ +struct alphatrack_icmd { + unsigned char cmd[12]; +}; + +struct alphatrack_ocmd { + unsigned char cmd[8]; +}; + +/* These are unused by the present driver but provide documentation for the + * userspace API. + */ +enum LightID { + LIGHT_EQ = 0, + LIGHT_OUT, + LIGHT_F2, + LIGHT_SEND, + LIGHT_IN, + LIGHT_F1, + LIGHT_PAN, + LIGHT_UNDEF1, + LIGHT_UNDEF2, + LIGHT_SHIFT, + LIGHT_TRACKMUTE, + LIGHT_TRACKSOLO, + LIGHT_TRACKREC, + LIGHT_READ, + LIGHT_WRITE, + LIGHT_ANYSOLO, + LIGHT_AUTO, + LIGHT_F4, + LIGHT_RECORD, + LIGHT_WINDOW, + LIGHT_PLUGIN, + LIGHT_F3, + LIGHT_LOOP +}; + +#define BUTTONMASK_BATTERY 0x00004000 +#define BUTTONMASK_BACKLIGHT 0x00008000 +#define BUTTONMASK_FASTFORWARD 0x04000000 +#define BUTTONMASK_TRACKMUTE 0x00040000 +#define BUTTONMASK_TRACKSOLO 0x00800000 +#define BUTTONMASK_TRACKLEFT 0x80000000 +#define BUTTONMASK_RECORD 0x02000000 +#define BUTTONMASK_SHIFT 0x20000000 +#define BUTTONMASK_PUNCH 0x00800000 +#define BUTTONMASK_TRACKRIGHT 0x00020000 +#define BUTTONMASK_REWIND 0x01000000 +#define BUTTONMASK_STOP 0x10000000 +#define BUTTONMASK_LOOP 0x00010000 +#define BUTTONMASK_TRACKREC 0x00001000 +#define BUTTONMASK_PLAY 0x08000000 +#define BUTTONMASK_TOUCH1 0x00000008 +#define BUTTONMASK_TOUCH2 0x00000010 +#define BUTTONMASK_TOUCH3 0x00000020 + +#define BUTTONMASK_PRESS1 0x00000009 +#define BUTTONMASK_PRESS2 0x00008010 +#define BUTTONMASK_PRESS3 0x00002020 + +/* last 3 bytes are the slider position + * 40 is the actual slider moving, the most sig bits, and 3 lsb + */ + +#define BUTTONMASK_FLIP 0x40000000 +#define BUTTONMASK_F1 0x00100000 +#define BUTTONMASK_F2 0x00400000 +#define BUTTONMASK_F3 0x00200000 +#define BUTTONMASK_F4 0x00080000 +#define BUTTONMASK_PAN 0x00000200 +#define BUTTONMASK_SEND 0x00000800 +#define BUTTONMASK_EQ 0x00004000 +#define BUTTONMASK_PLUGIN 0x00000400 +#define BUTTONMASK_AUTO 0x00000100 + +/* #define BUTTONMASK_FOOTSWITCH FIXME */ diff --git a/drivers/staging/frontier/tranzport.c b/drivers/staging/frontier/tranzport.c new file mode 100644 index 00000000..29e99bbc --- /dev/null +++ b/drivers/staging/frontier/tranzport.c @@ -0,0 +1,974 @@ +/* + * Frontier Designs Tranzport driver + * + * Copyright (C) 2007 Michael Taht (m@taht.net) + * + * Based on the usbled driver and ldusb drivers by + * + * Copyright (C) 2004 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2005 Michael Hund + * + * The ldusb driver was, in turn, derived from Lego USB Tower driver + * Copyright (C) 2003 David Glance + * 2001-2004 Juergen Stuber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +/* + * This driver uses a ring buffer for time critical reading of + * interrupt in reports and provides read and write methods for + * raw interrupt reports. + */ + +/* Note: this currently uses a dumb ringbuffer for reads and writes. + * A more optimal driver would cache and kill off outstanding urbs that are + * now invalid, and ignore ones that already were in the queue but valid + * as we only have 17 commands for the tranzport. In particular this is + * key for getting lights to flash in time as otherwise many commands + * can be buffered up before the light change makes it to the interface. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Define these values to match your devices */ +#define VENDOR_ID 0x165b +#define PRODUCT_ID 0x8101 + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define USB_TRANZPORT_MINOR_BASE 0 +#else /* FIXME 177- is the another driver's minor - apply for a minor soon */ +#define USB_TRANZPORT_MINOR_BASE 177 +#endif + +/* table of devices that work with this driver */ +static const struct usb_device_id usb_tranzport_table[] = { + {USB_DEVICE(VENDOR_ID, PRODUCT_ID)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usb_tranzport_table); +MODULE_VERSION("0.35"); +MODULE_AUTHOR("Mike Taht "); +MODULE_DESCRIPTION("Tranzport USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("Frontier Designs Tranzport Control Surface"); + +#define SUPPRESS_EXTRA_OFFLINE_EVENTS 1 +#define COMPRESS_WHEEL_EVENTS 1 +#define BUFFERED_READS 1 +#define RING_BUFFER_SIZE 1000 +#define WRITE_BUFFER_SIZE 34 +#define TRANZPORT_USB_TIMEOUT 10 +#define TRANZPORT_DEBUG 0 + +static int debug = TRANZPORT_DEBUG; + +/* Use our own dbg macro */ +#define dbg_info(dev, format, arg...) do \ + { if (debug) dev_info(dev , format , ## arg); } while (0) + +/* Module parameters */ + +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +/* All interrupt in transfers are collected in a ring buffer to + * avoid racing conditions and get better performance of the driver. + */ + +static int ring_buffer_size = RING_BUFFER_SIZE; + +module_param(ring_buffer_size, int, S_IRUGO); +MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size in reports"); + +/* The write_buffer can one day contain more than one interrupt out transfer. + */ +static int write_buffer_size = WRITE_BUFFER_SIZE; +module_param(write_buffer_size, int, S_IRUGO); +MODULE_PARM_DESC(write_buffer_size, "Write buffer size"); + +/* + * Increase the interval for debugging purposes. + * or set to 1 to use the standard interval from the endpoint descriptors. + */ + +static int min_interrupt_in_interval = TRANZPORT_USB_TIMEOUT; +module_param(min_interrupt_in_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_in_interval, + "Minimum interrupt in interval in ms"); + +static int min_interrupt_out_interval = TRANZPORT_USB_TIMEOUT; +module_param(min_interrupt_out_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_out_interval, + "Minimum interrupt out interval in ms"); + +struct tranzport_cmd { + unsigned char cmd[8]; +}; + +/* Structure to hold all of our device specific stuff */ + +struct usb_tranzport { + struct mutex mtx; /* locks this structure */ + struct usb_interface *intf; /* save off the usb interface pointer */ + int open_count; /* number of times this port opened */ + struct tranzport_cmd (*ring_buffer)[RING_BUFFER_SIZE]; + unsigned int ring_head; + unsigned int ring_tail; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + unsigned char *interrupt_in_buffer; + struct usb_endpoint_descriptor *interrupt_in_endpoint; + struct urb *interrupt_in_urb; + int interrupt_in_interval; + size_t interrupt_in_endpoint_size; + int interrupt_in_running; + int interrupt_in_done; + char *interrupt_out_buffer; + struct usb_endpoint_descriptor *interrupt_out_endpoint; + struct urb *interrupt_out_urb; + int interrupt_out_interval; + size_t interrupt_out_endpoint_size; + int interrupt_out_busy; + + /* Sysfs support */ + + unsigned char enable; /* 0 if disabled 1 if enabled */ + unsigned char offline; /* if the device is out of range or asleep */ + unsigned char compress_wheel; /* flag to compress wheel events */ +}; + +/* prevent races between open() and disconnect() */ +static DEFINE_MUTEX(disconnect_mutex); + +static struct usb_driver usb_tranzport_driver; + +/** + * usb_tranzport_abort_transfers + * aborts transfers and frees associated data structures + */ +static void usb_tranzport_abort_transfers(struct usb_tranzport *dev) +{ + /* shutdown transfer */ + if (dev->interrupt_in_running) { + dev->interrupt_in_running = 0; + if (dev->intf) + usb_kill_urb(dev->interrupt_in_urb); + } + if (dev->interrupt_out_busy) + if (dev->intf) + usb_kill_urb(dev->interrupt_out_urb); +} + +#define show_int(value) \ + static ssize_t show_##value(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_tranzport *t = usb_get_intfdata(intf); \ + return sprintf(buf, "%d\n", t->value); \ + } \ + static DEVICE_ATTR(value, S_IRUGO, show_##value, NULL); + +#define show_set_int(value) \ + static ssize_t show_##value(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_tranzport *t = usb_get_intfdata(intf); \ + return sprintf(buf, "%d\n", t->value); \ + } \ + static ssize_t set_##value(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_tranzport *t = usb_get_intfdata(intf); \ + unsigned long temp; \ + if (kstrtoul(buf, 10, &temp)) \ + return -EINVAL; \ + t->value = temp; \ + return count; \ + } \ + static DEVICE_ATTR(value, S_IWUSR | S_IRUGO, show_##value, set_##value); + +show_int(enable); +show_int(offline); +show_set_int(compress_wheel); + +/** + * usb_tranzport_delete + */ +static void usb_tranzport_delete(struct usb_tranzport *dev) +{ + usb_tranzport_abort_transfers(dev); + if (dev->intf != NULL) { + device_remove_file(&dev->intf->dev, &dev_attr_enable); + device_remove_file(&dev->intf->dev, &dev_attr_offline); + device_remove_file(&dev->intf->dev, &dev_attr_compress_wheel); + } + + /* free data structures */ + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree(dev->ring_buffer); + kfree(dev->interrupt_in_buffer); + kfree(dev->interrupt_out_buffer); + kfree(dev); +} + +/** + * usb_tranzport_interrupt_in_callback + */ + +static void usb_tranzport_interrupt_in_callback(struct urb *urb) +{ + struct usb_tranzport *dev = urb->context; + unsigned int next_ring_head; + int retval = -1; + + if (urb->status) { + if (urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN) { + goto exit; + } else { + dbg_info(&dev->intf->dev, + "%s: nonzero status received: %d\n", + __func__, urb->status); + goto resubmit; /* maybe we can recover */ + } + } + + if (urb->actual_length != 8) { + dev_warn(&dev->intf->dev, + "Urb length was %d bytes!!" + "Do something intelligent\n", + urb->actual_length); + } else { + dbg_info(&dev->intf->dev, + "%s: received: %02x%02x%02x%02x%02x%02x%02x%02x\n", + __func__, dev->interrupt_in_buffer[0], + dev->interrupt_in_buffer[1], + dev->interrupt_in_buffer[2], + dev->interrupt_in_buffer[3], + dev->interrupt_in_buffer[4], + dev->interrupt_in_buffer[5], + dev->interrupt_in_buffer[6], + dev->interrupt_in_buffer[7]); +#if SUPPRESS_EXTRA_OFFLINE_EVENTS + if (dev->offline == 2 && dev->interrupt_in_buffer[1] == 0xff) + goto resubmit; + if (dev->offline == 1 && dev->interrupt_in_buffer[1] == 0xff) { + dev->offline = 2; + goto resubmit; + } + + /* Always pass one offline event up the stack */ + if (dev->offline > 0 && dev->interrupt_in_buffer[1] != 0xff) + dev->offline = 0; + if (dev->offline == 0 && dev->interrupt_in_buffer[1] == 0xff) + dev->offline = 1; + +#endif /* SUPPRESS_EXTRA_OFFLINE_EVENTS */ + dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", + __func__, dev->ring_head, dev->ring_tail); + + next_ring_head = (dev->ring_head + 1) % ring_buffer_size; + + if (next_ring_head != dev->ring_tail) { + memcpy(&((*dev->ring_buffer)[dev->ring_head]), + dev->interrupt_in_buffer, urb->actual_length); + dev->ring_head = next_ring_head; + retval = 0; + memset(dev->interrupt_in_buffer, 0, urb->actual_length); + } else { + dev_warn(&dev->intf->dev, + "Ring buffer overflow, %d bytes dropped\n", + urb->actual_length); + memset(dev->interrupt_in_buffer, 0, urb->actual_length); + } + } + +resubmit: +/* resubmit if we're still running */ + if (dev->interrupt_in_running && dev->intf) { + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->intf->dev, + "usb_submit_urb failed (%d)\n", retval); + } + +exit: + dev->interrupt_in_done = 1; + wake_up_interruptible(&dev->read_wait); +} + +/** + * usb_tranzport_interrupt_out_callback + */ +static void usb_tranzport_interrupt_out_callback(struct urb *urb) +{ + struct usb_tranzport *dev = urb->context; + /* sync/async unlink faults aren't errors */ + if (urb->status && !(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dbg_info(&dev->intf->dev, + "%s - nonzero write interrupt status received: %d\n", + __func__, urb->status); + + dev->interrupt_out_busy = 0; + wake_up_interruptible(&dev->write_wait); +} +/** + * usb_tranzport_open + */ +static int usb_tranzport_open(struct inode *inode, struct file *file) +{ + struct usb_tranzport *dev; + int subminor; + int retval = 0; + struct usb_interface *interface; + + nonseekable_open(inode, file); + subminor = iminor(inode); + + mutex_lock(&disconnect_mutex); + + interface = usb_find_interface(&usb_tranzport_driver, subminor); + + if (!interface) { + err("%s - error, can't find device for minor %d\n", + __func__, subminor); + retval = -ENODEV; + goto unlock_disconnect_exit; + } + + dev = usb_get_intfdata(interface); + + if (!dev) { + retval = -ENODEV; + goto unlock_disconnect_exit; + } + + /* lock this device */ + if (mutex_lock_interruptible(&dev->mtx)) { + retval = -ERESTARTSYS; + goto unlock_disconnect_exit; + } + + /* allow opening only once */ + if (dev->open_count) { + retval = -EBUSY; + goto unlock_exit; + } + dev->open_count = 1; + + /* initialize in direction */ + dev->ring_head = 0; + dev->ring_tail = 0; + usb_fill_int_urb(dev->interrupt_in_urb, + interface_to_usbdev(interface), + usb_rcvintpipe(interface_to_usbdev(interface), + dev->interrupt_in_endpoint-> + bEndpointAddress), + dev->interrupt_in_buffer, + dev->interrupt_in_endpoint_size, + usb_tranzport_interrupt_in_callback, dev, + dev->interrupt_in_interval); + + dev->interrupt_in_running = 1; + dev->interrupt_in_done = 0; + dev->enable = 1; + dev->offline = 0; + dev->compress_wheel = 1; + + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&interface->dev, + "Couldn't submit interrupt_in_urb %d\n", retval); + dev->interrupt_in_running = 0; + dev->open_count = 0; + goto unlock_exit; + } + + /* save device in the file's private structure */ + file->private_data = dev; + +unlock_exit: + mutex_unlock(&dev->mtx); + +unlock_disconnect_exit: + mutex_unlock(&disconnect_mutex); + + return retval; +} + +/** + * usb_tranzport_release + */ +static int usb_tranzport_release(struct inode *inode, struct file *file) +{ + struct usb_tranzport *dev; + int retval = 0; + + dev = file->private_data; + + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + if (mutex_lock_interruptible(&dev->mtx)) { + retval = -ERESTARTSYS; + goto exit; + } + + if (dev->open_count != 1) { + retval = -ENODEV; + goto unlock_exit; + } + + if (dev->intf == NULL) { + /* the device was unplugged before the file was released */ + mutex_unlock(&dev->mtx); + /* unlock here as usb_tranzport_delete frees dev */ + usb_tranzport_delete(dev); + retval = -ENODEV; + goto exit; + } + + /* wait until write transfer is finished */ + if (dev->interrupt_out_busy) + wait_event_interruptible_timeout(dev->write_wait, + !dev->interrupt_out_busy, + 2 * HZ); + usb_tranzport_abort_transfers(dev); + dev->open_count = 0; + +unlock_exit: + mutex_unlock(&dev->mtx); + +exit: + return retval; +} + +/** + * usb_tranzport_poll + */ +static unsigned int usb_tranzport_poll(struct file *file, poll_table *wait) +{ + struct usb_tranzport *dev; + unsigned int mask = 0; + dev = file->private_data; + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + if (dev->ring_head != dev->ring_tail) + mask |= POLLIN | POLLRDNORM; + if (!dev->interrupt_out_busy) + mask |= POLLOUT | POLLWRNORM; + return mask; +} +/** + * usb_tranzport_read + */ + +static ssize_t usb_tranzport_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct usb_tranzport *dev; + int retval = 0; +#if BUFFERED_READS + int c = 0; +#endif +#if COMPRESS_WHEEL_EVENTS + signed char oldwheel; + signed char newwheel; + int cancompress = 1; + int next_tail; +#endif + + /* do I have such a thing as a null event? */ + + dev = file->private_data; + + /* verify that we actually have some data to read */ + if (count == 0) + goto exit; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->mtx)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ if (dev->intf == NULL) { + retval = -ENODEV; + err("No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + while (dev->ring_head == dev->ring_tail) { + + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + /* tiny race - FIXME: make atomic? */ + /* atomic_cmp_exchange(&dev->interrupt_in_done,0,0); */ + dev->interrupt_in_done = 0; + retval = wait_event_interruptible(dev->read_wait, + dev->interrupt_in_done); + if (retval < 0) + goto unlock_exit; + } + + dbg_info(&dev->intf->dev, + "%s: copying to userspace: " + "%02x%02x%02x%02x%02x%02x%02x%02x\n", + __func__, + (*dev->ring_buffer)[dev->ring_tail].cmd[0], + (*dev->ring_buffer)[dev->ring_tail].cmd[1], + (*dev->ring_buffer)[dev->ring_tail].cmd[2], + (*dev->ring_buffer)[dev->ring_tail].cmd[3], + (*dev->ring_buffer)[dev->ring_tail].cmd[4], + (*dev->ring_buffer)[dev->ring_tail].cmd[5], + (*dev->ring_buffer)[dev->ring_tail].cmd[6], + (*dev->ring_buffer)[dev->ring_tail].cmd[7]); + +#if BUFFERED_READS + c = 0; + while ((c < count) && (dev->ring_tail != dev->ring_head)) { + +#if COMPRESS_WHEEL_EVENTS + next_tail = (dev->ring_tail+1) % ring_buffer_size; + if (dev->compress_wheel) + cancompress = 1; + while (dev->ring_head != next_tail && cancompress == 1) { + newwheel = (*dev->ring_buffer)[next_tail].cmd[6]; + oldwheel = (*dev->ring_buffer)[dev->ring_tail].cmd[6]; + /* if both are wheel events, and + no buttons have changes (FIXME, do I have to check?), + and we are the same sign, we can compress +- 7F + */ + dbg_info(&dev->intf->dev, + "%s: trying to compress: " + "%02x%02x%02x%02x%02x%02x%02x%02x\n", + __func__, + (*dev->ring_buffer)[dev->ring_tail].cmd[0], + (*dev->ring_buffer)[dev->ring_tail].cmd[1], + (*dev->ring_buffer)[dev->ring_tail].cmd[2], + (*dev->ring_buffer)[dev->ring_tail].cmd[3], + (*dev->ring_buffer)[dev->ring_tail].cmd[4], + (*dev->ring_buffer)[dev->ring_tail].cmd[5], + (*dev->ring_buffer)[dev->ring_tail].cmd[6], + (*dev->ring_buffer)[dev->ring_tail].cmd[7]); + + if (((*dev->ring_buffer)[dev->ring_tail].cmd[6] != 0 && + (*dev->ring_buffer)[next_tail].cmd[6] != 0) && + ((newwheel > 0 && oldwheel > 0) || + (newwheel < 0 && oldwheel < 0)) && + ((*dev->ring_buffer)[dev->ring_tail].cmd[2] == + (*dev->ring_buffer)[next_tail].cmd[2]) && + ((*dev->ring_buffer)[dev->ring_tail].cmd[3] == + (*dev->ring_buffer)[next_tail].cmd[3]) && + ((*dev->ring_buffer)[dev->ring_tail].cmd[4] == + (*dev->ring_buffer)[next_tail].cmd[4]) && + ((*dev->ring_buffer)[dev->ring_tail].cmd[5] == + (*dev->ring_buffer)[next_tail].cmd[5])) { + dbg_info(&dev->intf->dev, + "%s: should compress: " + "%02x%02x%02x%02x%02x%02x%02x%02x\n", + __func__, + (*dev->ring_buffer)[dev->ring_tail]. + cmd[0], + (*dev->ring_buffer)[dev->ring_tail]. + cmd[1], + (*dev->ring_buffer)[dev->ring_tail]. + cmd[2], + (*dev->ring_buffer)[dev->ring_tail]. + cmd[3], + (*dev->ring_buffer)[dev->ring_tail]. + cmd[4], + (*dev->ring_buffer)[dev->ring_tail]. + cmd[5], + (*dev->ring_buffer)[dev->ring_tail]. + cmd[6], + (*dev->ring_buffer)[dev->ring_tail]. + cmd[7]); + newwheel += oldwheel; + if (oldwheel > 0 && !(newwheel > 0)) { + newwheel = 0x7f; + cancompress = 0; + } + if (oldwheel < 0 && !(newwheel < 0)) { + newwheel = 0x80; + cancompress = 0; + } + + (*dev->ring_buffer)[next_tail].cmd[6] = + newwheel; + dev->ring_tail = next_tail; + next_tail = + (dev->ring_tail + 1) % ring_buffer_size; + } else { + cancompress = 0; + } + } +#endif /* COMPRESS_WHEEL_EVENTS */ + if (copy_to_user( + &buffer[c], + &(*dev->ring_buffer)[dev->ring_tail], 8)) { + retval = -EFAULT; + goto unlock_exit; + } + dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size; + c += 8; + dbg_info(&dev->intf->dev, + "%s: head, tail are %x, %x\n", + __func__, dev->ring_head, dev->ring_tail); + } + retval = c; + +#else +/* if (copy_to_user(buffer, &(*dev->ring_buffer)[dev->ring_tail], 8)) { */ + retval = -EFAULT; + goto unlock_exit; +} + +dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size; +dbg_info(&dev->intf->dev, "%s: head, tail are %x, %x\n", + __func__, dev->ring_head, dev->ring_tail); + +retval = 8; +#endif /* BUFFERED_READS */ + +unlock_exit: +/* unlock the device */ +mutex_unlock(&dev->mtx); + +exit: +return retval; +} + +/** + * usb_tranzport_write + */ +static ssize_t usb_tranzport_write(struct file *file, + const char __user *buffer, size_t count, + loff_t *ppos) +{ + struct usb_tranzport *dev; + size_t bytes_to_write; + int retval = 0; + + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->mtx)) { + retval = -ERESTARTSYS; + goto exit; + } + /* verify that the device wasn't unplugged */ + if (dev->intf == NULL) { + retval = -ENODEV; + err("No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* wait until previous transfer is finished */ + if (dev->interrupt_out_busy) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible(dev->write_wait, + !dev->interrupt_out_busy); + if (retval < 0) + goto unlock_exit; + } + + /* write the data into interrupt_out_buffer from userspace */ + bytes_to_write = min(count, + write_buffer_size * + dev->interrupt_out_endpoint_size); + if (bytes_to_write < count) + dev_warn(&dev->intf->dev, + "Write buffer overflow, %zd bytes dropped\n", + count - bytes_to_write); + + dbg_info(&dev->intf->dev, + "%s: count = %zd, bytes_to_write = %zd\n", __func__, + count, bytes_to_write); + + if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) { + retval = -EFAULT; + goto unlock_exit; + } + + if (dev->interrupt_out_endpoint == NULL) { + err("Endpoint should not be be null!\n"); + goto unlock_exit; + } + + /* send off the urb */ + usb_fill_int_urb(dev->interrupt_out_urb, + interface_to_usbdev(dev->intf), + usb_sndintpipe(interface_to_usbdev(dev->intf), + dev->interrupt_out_endpoint-> + bEndpointAddress), + dev->interrupt_out_buffer, bytes_to_write, + usb_tranzport_interrupt_out_callback, dev, + dev->interrupt_out_interval); + + dev->interrupt_out_busy = 1; + wmb(); + + retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); + if (retval) { + dev->interrupt_out_busy = 0; + err("Couldn't submit interrupt_out_urb %d\n", retval); + goto unlock_exit; + } + retval = bytes_to_write; + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->mtx); + +exit: + return retval; +} + +/* file operations needed when we register this driver */ +static const struct file_operations usb_tranzport_fops = { + .owner = THIS_MODULE, + .read = usb_tranzport_read, + .write = usb_tranzport_write, + .open = usb_tranzport_open, + .release = usb_tranzport_release, + .poll = usb_tranzport_poll, + .llseek = no_llseek, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver usb_tranzport_class = { + .name = "tranzport%d", + .fops = &usb_tranzport_fops, + .minor_base = USB_TRANZPORT_MINOR_BASE, +}; + +/** + * usb_tranzport_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int usb_tranzport_probe(struct usb_interface *intf, + const struct usb_device_id *id) { + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_tranzport *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int i; + int true_size; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&intf->dev, "Out of memory\n"); + goto exit; + } + mutex_init(&dev->mtx); + dev->intf = intf; + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + iface_desc = intf->cur_altsetting; + + /* set up the endpoint information */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) + dev->interrupt_in_endpoint = endpoint; + + if (usb_endpoint_is_int_out(endpoint)) + dev->interrupt_out_endpoint = endpoint; + } + if (dev->interrupt_in_endpoint == NULL) { + dev_err(&intf->dev, "Interrupt in endpoint not found\n"); + goto error; + } + if (dev->interrupt_out_endpoint == NULL) + dev_warn(&intf->dev, + "Interrupt out endpoint not found" + "(using control endpoint instead)\n"); + + dev->interrupt_in_endpoint_size = + le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize); + + if (dev->interrupt_in_endpoint_size != 8) + dev_warn(&intf->dev, "Interrupt in endpoint size is not 8!\n"); + + if (ring_buffer_size == 0) + ring_buffer_size = RING_BUFFER_SIZE; + true_size = min(ring_buffer_size, RING_BUFFER_SIZE); + + /* FIXME - there are more usb_alloc routines for dma correctness. + Needed? */ + + dev->ring_buffer = + kmalloc((true_size * sizeof(struct tranzport_cmd)) + 8, GFP_KERNEL); + + if (!dev->ring_buffer) { + dev_err(&intf->dev, + "Couldn't allocate ring_buffer size %d\n", true_size); + goto error; + } + dev->interrupt_in_buffer = + kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_in_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_buffer\n"); + goto error; + } + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_urb\n"); + goto error; + } + dev->interrupt_out_endpoint_size = + dev->interrupt_out_endpoint ? + le16_to_cpu(dev->interrupt_out_endpoint->wMaxPacketSize) : + udev->descriptor.bMaxPacketSize0; + + if (dev->interrupt_out_endpoint_size != 8) + dev_warn(&intf->dev, + "Interrupt out endpoint size is not 8!)\n"); + + dev->interrupt_out_buffer = + kmalloc(write_buffer_size * dev->interrupt_out_endpoint_size, + GFP_KERNEL); + if (!dev->interrupt_out_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_buffer\n"); + goto error; + } + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_urb\n"); + goto error; + } + dev->interrupt_in_interval = + min_interrupt_in_interval > + dev->interrupt_in_endpoint->bInterval ? min_interrupt_in_interval + : dev->interrupt_in_endpoint->bInterval; + + if (dev->interrupt_out_endpoint) { + dev->interrupt_out_interval = + min_interrupt_out_interval > + dev->interrupt_out_endpoint->bInterval ? + min_interrupt_out_interval : + dev->interrupt_out_endpoint->bInterval; + } + + /* we can register the device now, as it is ready */ + usb_set_intfdata(intf, dev); + + retval = usb_register_dev(intf, &usb_tranzport_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&intf->dev, + "Not able to get a minor for this device.\n"); + usb_set_intfdata(intf, NULL); + goto error; + } + + retval = device_create_file(&intf->dev, &dev_attr_compress_wheel); + if (retval) + goto error; + retval = device_create_file(&intf->dev, &dev_attr_enable); + if (retval) + goto error; + retval = device_create_file(&intf->dev, &dev_attr_offline); + if (retval) + goto error; + + /* let the user know what node this device is now attached to */ + dev_info(&intf->dev, + "Tranzport Device #%d now attached to major %d minor %d\n", + (intf->minor - USB_TRANZPORT_MINOR_BASE), USB_MAJOR, + intf->minor); + +exit: + return retval; + +error: + usb_tranzport_delete(dev); + return retval; +} + +/** + * usb_tranzport_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void usb_tranzport_disconnect(struct usb_interface *intf) +{ + struct usb_tranzport *dev; + int minor; + mutex_lock(&disconnect_mutex); + dev = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + mutex_lock(&dev->mtx); + minor = intf->minor; + /* give back our minor */ + usb_deregister_dev(intf, &usb_tranzport_class); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) { + mutex_unlock(&dev->mtx); + usb_tranzport_delete(dev); + } else { + dev->intf = NULL; + mutex_unlock(&dev->mtx); + } + + mutex_unlock(&disconnect_mutex); + + dev_info(&intf->dev, "Tranzport Surface #%d now disconnected\n", + (minor - USB_TRANZPORT_MINOR_BASE)); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver usb_tranzport_driver = { + .name = "tranzport", + .probe = usb_tranzport_probe, + .disconnect = usb_tranzport_disconnect, + .id_table = usb_tranzport_table, +}; + +module_usb_driver(usb_tranzport_driver); diff --git a/drivers/staging/ft1000/Kconfig b/drivers/staging/ft1000/Kconfig new file mode 100644 index 00000000..c54b4e83 --- /dev/null +++ b/drivers/staging/ft1000/Kconfig @@ -0,0 +1,22 @@ +config FT1000 + tristate "Drivers for Flarion ft1000 devices" + +if FT1000 + +config FT1000_USB + tristate "Driver for ft1000 usb devices." + depends on USB + depends on NET + help + Say Y if you want to have support for Qleadtek FLASH-OFDM USB Modem [LR7F04], + Qleadtek Express Card or Leadtek Multi-band modem HSDPA. + +config FT1000_PCMCIA + tristate "Driver for ft1000 pcmcia device." + depends on PCMCIA + depends on NET + help + Say Y if you want to have support for Flarion card also called + Multimedia Net Card. + +endif diff --git a/drivers/staging/ft1000/Makefile b/drivers/staging/ft1000/Makefile new file mode 100644 index 00000000..3e987770 --- /dev/null +++ b/drivers/staging/ft1000/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_FT1000_USB) += ft1000-usb/ +obj-$(CONFIG_FT1000_PCMCIA) += ft1000-pcmcia/ + diff --git a/drivers/staging/ft1000/TODO b/drivers/staging/ft1000/TODO new file mode 100644 index 00000000..1d346bc4 --- /dev/null +++ b/drivers/staging/ft1000/TODO @@ -0,0 +1,9 @@ +TODO: + - checkpatch.pl cleanups + - coding style + - sparse fixes + - adapt to latest usb and pcmcia api changes + - change firmware loading for usb driver to proper kernel method (request_firmware) + +Please send patches to Greg Kroah-Hartman and +Cc: Marek Belisko diff --git a/drivers/staging/ft1000/ft1000-pcmcia/Makefile b/drivers/staging/ft1000/ft1000-pcmcia/Makefile new file mode 100644 index 00000000..660b7a50 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-pcmcia/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_FT1000_PCMCIA) = ft1000_pcmcia.o +ft1000_pcmcia-y := ft1000_hw.o ft1000_dnld.o ft1000_proc.o ft1000_cs.o + diff --git a/drivers/staging/ft1000/ft1000-pcmcia/boot.h b/drivers/staging/ft1000/ft1000-pcmcia/boot.h new file mode 100644 index 00000000..1fc4ac12 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-pcmcia/boot.h @@ -0,0 +1,158 @@ +//--------------------------------------------------------------------------- +// FT1000 driver for Flarion Flash OFDM NIC Device +// +// Copyright (C) 2002 Flarion Technologies, All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) any +// later version. This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. You should have received a copy of the GNU General Public +// License along with this program; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place - +// Suite 330, Boston, MA 02111-1307, USA. +//--------------------------------------------------------------------------- +// +// File: boot.h +// +// Description: boatloader +// +// History: +// 1/11/05 Whc Ported to Linux. +// +//--------------------------------------------------------------------------- +#ifndef _BOOTH_ +#define _BOOTH_ + +// Official bootloader +unsigned char bootimage [] = { +0x00,0x00,0x01,0x5E,0x00,0x00 +,0x00,0x00,0x00,0x00,0x02,0xD7 +,0x00,0x00,0x01,0x5E,0x46,0xB3 +,0xE6,0x02,0x00,0x98,0xE6,0x8C +,0x00,0x98,0xFB,0x92,0xFF,0xFF +,0x98,0xFB,0x94,0xFF,0xFF,0x98 +,0xFB,0x06,0x08,0x00,0x98,0xFB +,0x96,0x84,0x00,0x98,0xFB,0x08 +,0x1C,0x00,0x98,0xFB,0x51,0x25 +,0x10,0x1C,0x00,0xE6,0x51,0x01 +,0x07,0xFD,0x4C,0xFF,0x20,0xF5 +,0x51,0x02,0x20,0x08,0x00,0x4C +,0xFF,0x20,0x3C,0x00,0xC0,0x64 +,0x98,0xC0,0x66,0x98,0xC0,0x68 +,0x98,0xC0,0x6A,0x98,0xC0,0x6C +,0x98,0x90,0x08,0x90,0x09,0x90 +,0x0A,0x90,0x0B,0x90,0x0C,0x90 +,0x0D,0x90,0x0E,0x90,0x0F,0x90 +,0x04,0x90,0x06,0xFB,0x51,0x22 +,0x16,0x08,0x03,0xFB,0x51,0x52 +,0x16,0x08,0x04,0xFB,0x51,0x24 +,0x2B,0x08,0x06,0xFB,0x51,0x54 +,0x2B,0x08,0x07,0xFB,0x51,0x24 +,0x2B,0x08,0x09,0xFB,0x51,0x54 +,0x2B,0x08,0x0A,0xFB,0x51,0x12 +,0x16,0x08,0x0C,0xFB,0x51,0x52 +,0x16,0x08,0x0D,0x78,0x00,0x00 +,0x00,0x16,0x00,0x00,0xEC,0x31 +,0xAE,0x00,0x00,0x81,0x4C,0x0F +,0xE6,0x43,0xFF,0xEC,0x31,0x4E +,0x00,0x00,0x91,0xEC,0x31,0xAE +,0x00,0x00,0x91,0x4C,0x0F,0xE6 +,0x43,0xFF,0xEC,0x31,0x5E,0x00 +,0x00,0xA1,0xEB,0x31,0x08,0x00 +,0x00,0xA6,0xEB,0x31,0x08,0x00 +,0x00,0xAC,0x3C,0x00,0xEB,0x31 +,0x08,0x00,0x00,0xA8,0x76,0xFE +,0xFE,0x08,0xEB,0x31,0x08,0x20 +,0x00,0x00,0x76,0xFF,0xFF,0x18 +,0xED,0x31,0x08,0x20,0x00,0x00 +,0x26,0x10,0x04,0x10,0xF5,0x3C +,0x01,0x3C,0x00,0x08,0x01,0x12 +,0x3C,0x11,0x3C,0x00,0x08,0x01 +,0x0B,0x08,0x00,0x6D,0xEC,0x31 +,0xAE,0x20,0x00,0x06,0xED,0x4D +,0x08,0x00,0x00,0x67,0x80,0x6F +,0x00,0x01,0x0B,0x6F,0x00,0x02 +,0x2E,0x76,0xEE,0x01,0x48,0x06 +,0x01,0x39,0xED,0x4D,0x18,0x00 +,0x02,0xED,0x4D,0x08,0x00,0x04 +,0x14,0x06,0xA4,0xED,0x31,0x22 +,0x00,0x00,0xAC,0x76,0xEE,0x07 +,0x48,0x6D,0x22,0x01,0x1E,0x08 +,0x01,0x58,0xEB,0x31,0x08,0x00 +,0x00,0xAC,0x06,0xFF,0xBA,0x3C +,0x00,0xEB,0x31,0x08,0x20,0x00 +,0x04,0x3C,0x30,0xEB,0x31,0x08 +,0x20,0x00,0x02,0x3C,0x10,0xEB +,0x31,0x08,0x20,0x00,0x00,0xED +,0x31,0x08,0x20,0x00,0x00,0x04 +,0x10,0xF7,0xED,0x31,0x08,0x00 +,0x00,0xA2,0x91,0x00,0x9C,0x3C +,0x80,0xEB,0x31,0x08,0x20,0x00 +,0x04,0x3C,0x20,0xEB,0x31,0x08 +,0x20,0x00,0x02,0x3C,0x10,0xEB +,0x31,0x08,0x20,0x00,0x00,0xED +,0x31,0x08,0x20,0x00,0x00,0x04 +,0x10,0xF7,0xED,0x31,0x08,0x20 +,0x00,0x04,0x42,0x10,0x90,0x08 +,0xEC,0x31,0xAE,0x20,0x00,0x06 +,0xA4,0x41,0x08,0x00,0xB6,0xED +,0x41,0x28,0x7D,0xFF,0xFF,0x22 +,0xB3,0x40,0x98,0x2A,0x32,0xEB +,0x41,0x28,0xB4,0x43,0xFC,0x05 +,0xFF,0xE6,0xA0,0x31,0x20,0x00 +,0x06,0xEB,0x31,0x08,0x20,0x00 +,0x04,0x3C,0x20,0xEB,0x31,0x08 +,0x20,0x00,0x02,0x3C,0x10,0xEB +,0x31,0x08,0x20,0x00,0x00,0xED +,0x31,0x08,0x20,0x00,0x00,0x04 +,0x10,0xF7,0xED,0x31,0x08,0x20 +,0x00,0x04,0x42,0x10,0x90,0x08 +,0xEC,0x31,0xAE,0x20,0x00,0x06 +,0xA4,0x41,0x08,0x00,0x68,0xED +,0x41,0x28,0x7D,0xFF,0xFF,0x22 +,0xB3,0x40,0x98,0x2A,0x32,0xEB +,0x41,0x28,0xB4,0x43,0xFC,0x05 +,0xFF,0xE6,0x48,0x04,0xEB,0x31 +,0x08,0x20,0x00,0x04,0xEB,0x31 +,0x18,0x20,0x00,0x02,0x3C,0x11 +,0xEB,0x31,0x18,0x20,0x00,0x00 +,0xED,0x31,0x08,0x20,0x00,0x00 +,0x04,0x10,0xF7,0xED,0x31,0x08 +,0x20,0x00,0x02,0x66,0x00,0x6F +,0x00,0x01,0x16,0x76,0xEE,0x06 +,0x48,0x4A,0x1E,0x48,0x04,0xED +,0x31,0x08,0x20,0x00,0x04,0xEB +,0x31,0x08,0x00,0x00,0xA4,0x48 +,0x04,0xED,0x31,0x08,0x20,0x00 +,0x04,0xEB,0x31,0x08,0x00,0x00 +,0xA2,0x48,0x04,0x20,0x20,0x4A +,0x7C,0x46,0x82,0x50,0x05,0x50 +,0x15,0xB5,0x1E,0x98,0xED,0x31 +,0x08,0x00,0x00,0xA8,0x10,0x47 +,0x3B,0x2C,0x01,0xDB,0x40,0x11 +,0x98,0xC1,0x1E,0x98,0x10,0x07 +,0x30,0xF9,0x40,0x07,0x18,0x98 +,0x2A,0x10,0xEB,0x31,0x08,0x00 +,0x00,0xA8,0xA4,0x1E,0x98,0xBB +,0x1E,0x98,0x50,0x14,0x50,0x04 +,0x46,0x83,0x48,0x04,0x02,0x01 +,0x00,0x50,0x05,0x50,0x15,0x10 +,0x87,0x3F,0x90,0x2B,0x18,0x01 +,0x00,0xC0,0x31,0x00,0x00,0xAE +,0xDF,0x41,0x00,0x08,0x00,0x1A +,0x42,0x11,0x67,0x01,0xDF,0x41 +,0x02,0x08,0x00,0x10,0x42,0x11 +,0x62,0x01,0xB4,0x43,0x4A,0x68 +,0x50,0x14,0x50,0x04,0x24,0x10 +,0x48,0x04,0xF2,0x31,0x00,0x01 +,0x00,0x00,0xAE,0xF6,0x31,0x00 +,0x01,0x00,0x00,0xAE,0x62,0xE4 +,0xE5,0x61,0x04,0x48,0x04,0xE5 +,0x63,0x05,0x48,0x04,0x20,0x20 +,0x00,0x00,0x00,0x00 +}; + +#endif diff --git a/drivers/staging/ft1000/ft1000-pcmcia/ft1000.h b/drivers/staging/ft1000/ft1000-pcmcia/ft1000.h new file mode 100644 index 00000000..adb436ed --- /dev/null +++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000.h @@ -0,0 +1,99 @@ +/*--------------------------------------------------------------------------- + FT1000 driver for Flarion Flash OFDM NIC Device + + Copyright (C) 2002 Flarion Technologies, All rights reserved. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) any + later version. This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. You should have received a copy of the GNU General Public + License along with this program; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. +--------------------------------------------------------------------------- + Description: Common structures and defines +---------------------------------------------------------------------------*/ +#ifndef _FT1000H_ +#define _FT1000H_ + +#include "../ft1000.h" + +#define FT1000_DRV_VER 0x01010300 + +#define FT1000_DPRAM_BASE 0x0000 /* Dual Port RAM starting offset */ + +/* Maximum number of occurrence of pseudo header errors before resetting PC Card. */ +#define MAX_PH_ERR 300 + +#define SUCCESS 0x00 +#define FAILURE 0x01 + +struct ft1000_info { + struct net_device_stats stats; + u16 DrvErrNum; + u16 AsicID; + int PktIntfErr; + int CardReady; + int registered; + int mediastate; + u16 packetseqnum; + u8 squeseqnum; /* sequence number on slow queue */ + spinlock_t dpram_lock; + u16 fifo_cnt; + u8 DspVer[DSPVERSZ]; /* DSP version number */ + u8 HwSerNum[HWSERNUMSZ]; /* Hardware Serial Number */ + u8 Sku[SKUSZ]; /* SKU */ + u8 eui64[EUISZ]; /* EUI64 */ + time_t ConTm; /* Connection Time */ + u16 LedStat; + u16 ConStat; + u16 ProgConStat; + u8 ProductMode[MODESZ]; + u8 RfCalVer[CALVERSZ]; + u8 RfCalDate[CALDATESZ]; + u16 DSP_TIME[4]; + struct list_head prov_list; + u16 DSPInfoBlklen; + int (*ft1000_reset)(void *); + void *link; + u16 DSPInfoBlk[MAX_DSP_SESS_REC]; + union { + u16 Rec[MAX_DSP_SESS_REC]; + u32 MagRec[MAX_DSP_SESS_REC/2]; + } DSPSess; + struct proc_dir_entry *proc_ft1000; + char netdevname[IFNAMSIZ]; +}; + +struct pcmcia_device; +struct net_device; +extern struct net_device *init_ft1000_card(struct pcmcia_device *link, + void *ft1000_reset); +extern void stop_ft1000_card(struct net_device *dev); +extern int card_download(struct net_device *dev, const u8 *pFileStart, + size_t FileLength); +extern void ft1000InitProc(struct net_device *dev); +extern void ft1000CleanupProc(struct net_device *dev); + +extern u16 ft1000_read_dpram(struct net_device *dev, int offset); +extern void card_bootload(struct net_device *dev); +extern u16 ft1000_read_dpram_mag_16(struct net_device *dev, int offset, int Index); +extern u32 ft1000_read_dpram_mag_32(struct net_device *dev, int offset); +void ft1000_write_dpram_mag_32(struct net_device *dev, int offset, u32 value); + +/* Read the value of a given ASIC register. */ +static inline u16 ft1000_read_reg(struct net_device *dev, u16 offset) +{ + return inw(dev->base_addr + offset); +} + +/* Set the value of a given ASIC register. */ +static inline void ft1000_write_reg(struct net_device *dev, u16 offset, u16 value) +{ + outw(value, dev->base_addr + offset); +} + +#endif diff --git a/drivers/staging/ft1000/ft1000-pcmcia/ft1000.img b/drivers/staging/ft1000/ft1000-pcmcia/ft1000.img new file mode 100644 index 00000000..aad3c80d Binary files /dev/null and b/drivers/staging/ft1000/ft1000-pcmcia/ft1000.img differ diff --git a/drivers/staging/ft1000/ft1000-pcmcia/ft1000_cs.c b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_cs.c new file mode 100644 index 00000000..f376ca43 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_cs.c @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------- + FT1000 driver for Flarion Flash OFDM NIC Device + + Copyright (C) 1999 David A. Hinds. All Rights Reserved. + Copyright (C) 2002 Flarion Technologies, All rights reserved. + Copyright (C) 2006 Patrik Ostrihon, All rights reserved. + Copyright (C) 2006 ProWeb Consulting, a.s, All rights reserved. + + The initial developer of the original code is David A. Hinds + . Portions created by David A. Hinds. + + This file was modified to support the Flarion Flash OFDM NIC Device + by Wai Chan (w.chan@flarion.com). + + Port for kernel 2.6 created by Patrik Ostrihon (patrik.ostrihon@pwc.sk) + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) any + later version. This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. You should have received a copy of the GNU General Public + License along with this program; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. +-----------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include + +/*====================================================================*/ + +MODULE_AUTHOR("Wai Chan"); +MODULE_DESCRIPTION("FT1000 PCMCIA driver"); +MODULE_LICENSE("GPL"); + +/*====================================================================*/ + +static int ft1000_config(struct pcmcia_device *link); +static void ft1000_detach(struct pcmcia_device *link); +static int ft1000_attach(struct pcmcia_device *link); + +#include "ft1000.h" + +/*====================================================================*/ + +static void ft1000_reset(struct pcmcia_device *link) +{ + pcmcia_reset_card(link->socket); +} + +static int ft1000_attach(struct pcmcia_device *link) +{ + link->priv = NULL; + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + return ft1000_config(link); +} + +static void ft1000_detach(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + + if (dev) + stop_ft1000_card(dev); + + pcmcia_disable_device(link); + free_netdev(dev); +} + +static int ft1000_confcheck(struct pcmcia_device *link, void *priv_data) +{ + return pcmcia_request_io(link); +} + +/*====================================================================== + + ft1000_config() is scheduled to run after a CARD_INSERTION event + is received, to configure the PCMCIA socket, and to make the + device available to the system. + +======================================================================*/ + +static int ft1000_config(struct pcmcia_device *link) +{ + int ret; + + dev_dbg(&link->dev, "ft1000_cs: ft1000_config(0x%p)\n", link); + + /* setup IO window */ + ret = pcmcia_loop_config(link, ft1000_confcheck, NULL); + if (ret) { + printk(KERN_INFO "ft1000: Could not configure pcmcia\n"); + return -ENODEV; + } + + /* configure device */ + ret = pcmcia_enable_device(link); + if (ret) { + printk(KERN_INFO "ft1000: could not enable pcmcia\n"); + goto failed; + } + + link->priv = init_ft1000_card(link, &ft1000_reset); + if (!link->priv) { + printk(KERN_INFO "ft1000: Could not register as network device\n"); + goto failed; + } + + /* Finally, report what we've done */ + + return 0; +failed: + pcmcia_disable_device(link); + return -ENODEV; +} + +static int ft1000_suspend(struct pcmcia_device *link) +{ + struct net_device *dev = link->priv; + + if (link->open) + netif_device_detach(dev); + return 0; +} + +static int ft1000_resume(struct pcmcia_device *link) +{ + return 0; +} + +/*====================================================================*/ + +static const struct pcmcia_device_id ft1000_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x02cc, 0x0100), + PCMCIA_DEVICE_MANF_CARD(0x02cc, 0x1000), + PCMCIA_DEVICE_MANF_CARD(0x02cc, 0x1300), + PCMCIA_DEVICE_NULL, +}; + +MODULE_DEVICE_TABLE(pcmcia, ft1000_ids); + +static struct pcmcia_driver ft1000_cs_driver = { + .owner = THIS_MODULE, + .name = "ft1000_cs", + .probe = ft1000_attach, + .remove = ft1000_detach, + .id_table = ft1000_ids, + .suspend = ft1000_suspend, + .resume = ft1000_resume, +}; + +static int __init init_ft1000_cs(void) +{ + return pcmcia_register_driver(&ft1000_cs_driver); +} + +static void __exit exit_ft1000_cs(void) +{ + pcmcia_unregister_driver(&ft1000_cs_driver); +} + +module_init(init_ft1000_cs); +module_exit(exit_ft1000_cs); diff --git a/drivers/staging/ft1000/ft1000-pcmcia/ft1000_dnld.c b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_dnld.c new file mode 100644 index 00000000..f8b8e712 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_dnld.c @@ -0,0 +1,775 @@ +/*--------------------------------------------------------------------------- + FT1000 driver for Flarion Flash OFDM NIC Device + + Copyright (C) 2002 Flarion Technologies, All rights reserved. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) any + later version. This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. You should have received a copy of the GNU General Public + License along with this program; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. + -------------------------------------------------------------------------- + + Description: This module will handshake with the DSP bootloader to + download the DSP runtime image. + +---------------------------------------------------------------------------*/ + +#define __KERNEL_SYSCALLS__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ft1000.h" +#include "boot.h" + +#ifdef FT_DEBUG +#define DEBUG(n, args...) printk(KERN_DEBUG args); +#else +#define DEBUG(n, args...) +#endif + +#define MAX_DSP_WAIT_LOOPS 100 +#define DSP_WAIT_SLEEP_TIME 1 /* 1 millisecond */ + +#define MAX_LENGTH 0x7f0 + +#define DWNLD_MAG_HANDSHAKE_LOC 0x00 +#define DWNLD_MAG_TYPE_LOC 0x01 +#define DWNLD_MAG_SIZE_LOC 0x02 +#define DWNLD_MAG_PS_HDR_LOC 0x03 + +#define DWNLD_HANDSHAKE_LOC 0x02 +#define DWNLD_TYPE_LOC 0x04 +#define DWNLD_SIZE_MSW_LOC 0x06 +#define DWNLD_SIZE_LSW_LOC 0x08 +#define DWNLD_PS_HDR_LOC 0x0A + +#define HANDSHAKE_TIMEOUT_VALUE 0xF1F1 +#define HANDSHAKE_RESET_VALUE 0xFEFE /* When DSP requests startover */ +#define HANDSHAKE_DSP_BL_READY 0xFEFE /* At start DSP writes this when bootloader ready */ +#define HANDSHAKE_DRIVER_READY 0xFFFF /* Driver writes after receiving 0xFEFE */ +#define HANDSHAKE_SEND_DATA 0x0000 /* DSP writes this when ready for more data */ + +#define HANDSHAKE_REQUEST 0x0001 /* Request from DSP */ +#define HANDSHAKE_RESPONSE 0x0000 /* Satisfied DSP request */ + +#define REQUEST_CODE_LENGTH 0x0000 +#define REQUEST_RUN_ADDRESS 0x0001 +#define REQUEST_CODE_SEGMENT 0x0002 /* In WORD count */ +#define REQUEST_DONE_BL 0x0003 +#define REQUEST_DONE_CL 0x0004 +#define REQUEST_VERSION_INFO 0x0005 +#define REQUEST_CODE_BY_VERSION 0x0006 +#define REQUEST_MAILBOX_DATA 0x0007 +#define REQUEST_FILE_CHECKSUM 0x0008 + +#define STATE_START_DWNLD 0x01 +#define STATE_BOOT_DWNLD 0x02 +#define STATE_CODE_DWNLD 0x03 +#define STATE_DONE_DWNLD 0x04 +#define STATE_SECTION_PROV 0x05 +#define STATE_DONE_PROV 0x06 +#define STATE_DONE_FILE 0x07 + +u16 get_handshake(struct net_device *dev, u16 expected_value); +void put_handshake(struct net_device *dev, u16 handshake_value); +u16 get_request_type(struct net_device *dev); +long get_request_value(struct net_device *dev); +void put_request_value(struct net_device *dev, long lvalue); +u16 hdr_checksum(struct pseudo_hdr *pHdr); + +struct dsp_file_hdr { + u32 version_id; // Version ID of this image format. + u32 package_id; // Package ID of code release. + u32 build_date; // Date/time stamp when file was built. + u32 commands_offset; // Offset to attached commands in Pseudo Hdr format. + u32 loader_offset; // Offset to bootloader code. + u32 loader_code_address; // Start address of bootloader. + u32 loader_code_end; // Where bootloader code ends. + u32 loader_code_size; + u32 version_data_offset; // Offset were scrambled version data begins. + u32 version_data_size; // Size, in words, of scrambled version data. + u32 nDspImages; // Number of DSP images in file. +} __attribute__ ((packed)); + +struct dsp_image_info { + u32 coff_date; // Date/time when DSP Coff image was built. + u32 begin_offset; // Offset in file where image begins. + u32 end_offset; // Offset in file where image begins. + u32 run_address; // On chip Start address of DSP code. + u32 image_size; // Size of image. + u32 version; // Embedded version # of DSP code. + unsigned short checksum; // Dsp File checksum + unsigned short pad1; +} __attribute__ ((packed)); + +void card_bootload(struct net_device *dev) +{ + struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); + unsigned long flags; + u32 *pdata; + u32 size; + u32 i; + u32 templong; + + DEBUG(0, "card_bootload is called\n"); + + pdata = (u32 *) bootimage; + size = sizeof(bootimage); + + // check for odd word + if (size & 0x0003) { + size += 4; + } + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, flags); + + // need to set i/o base address initially and hardware will autoincrement + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, FT1000_DPRAM_BASE); + // write bytes + for (i = 0; i < (size >> 2); i++) { + templong = *pdata++; + outl(templong, dev->base_addr + FT1000_REG_MAG_DPDATA); + } + + spin_unlock_irqrestore(&info->dpram_lock, flags); +} + +u16 get_handshake(struct net_device *dev, u16 expected_value) +{ + struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); + u16 handshake; + u32 tempx; + int loopcnt; + + loopcnt = 0; + while (loopcnt < MAX_DSP_WAIT_LOOPS) { + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + DWNLD_HANDSHAKE_LOC); + + handshake = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); + } else { + tempx = + ntohl(ft1000_read_dpram_mag_32 + (dev, DWNLD_MAG_HANDSHAKE_LOC)); + handshake = (u16) tempx; + } + + if ((handshake == expected_value) + || (handshake == HANDSHAKE_RESET_VALUE)) { + return handshake; + } else { + loopcnt++; + mdelay(DSP_WAIT_SLEEP_TIME); + } + + } + + return HANDSHAKE_TIMEOUT_VALUE; + +} + +void put_handshake(struct net_device *dev, u16 handshake_value) +{ + struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); + u32 tempx; + + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + DWNLD_HANDSHAKE_LOC); + ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, handshake_value); /* Handshake */ + } else { + tempx = (u32) handshake_value; + tempx = ntohl(tempx); + ft1000_write_dpram_mag_32(dev, DWNLD_MAG_HANDSHAKE_LOC, tempx); /* Handshake */ + } +} + +u16 get_request_type(struct net_device *dev) +{ + struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); + u16 request_type; + u32 tempx; + + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, DWNLD_TYPE_LOC); + request_type = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); + } else { + tempx = ft1000_read_dpram_mag_32(dev, DWNLD_MAG_TYPE_LOC); + tempx = ntohl(tempx); + request_type = (u16) tempx; + } + + return request_type; + +} + +long get_request_value(struct net_device *dev) +{ + struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); + long value; + u16 w_val; + + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + DWNLD_SIZE_MSW_LOC); + + w_val = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); + + value = (long)(w_val << 16); + + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + DWNLD_SIZE_LSW_LOC); + + w_val = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); + + value = (long)(value | w_val); + } else { + value = ft1000_read_dpram_mag_32(dev, DWNLD_MAG_SIZE_LOC); + value = ntohl(value); + } + + return value; + +} + +void put_request_value(struct net_device *dev, long lvalue) +{ + struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); + u16 size; + u32 tempx; + + if (info->AsicID == ELECTRABUZZ_ID) { + size = (u16) (lvalue >> 16); + + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + DWNLD_SIZE_MSW_LOC); + + ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, size); + + size = (u16) (lvalue); + + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + DWNLD_SIZE_LSW_LOC); + + ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, size); + } else { + tempx = ntohl(lvalue); + ft1000_write_dpram_mag_32(dev, DWNLD_MAG_SIZE_LOC, tempx); /* Handshake */ + } + +} + +u16 hdr_checksum(struct pseudo_hdr *pHdr) +{ + u16 *usPtr = (u16 *) pHdr; + u16 chksum; + + chksum = ((((((usPtr[0] ^ usPtr[1]) ^ usPtr[2]) ^ usPtr[3]) ^ + usPtr[4]) ^ usPtr[5]) ^ usPtr[6]); + + return chksum; +} + +int card_download(struct net_device *dev, const u8 *pFileStart, + size_t FileLength) +{ + struct ft1000_info *info = (struct ft1000_info *) netdev_priv(dev); + int Status = SUCCESS; + u32 uiState; + u16 handshake; + struct pseudo_hdr *pHdr; + u16 usHdrLength; + long word_length; + u16 request; + u16 temp; + struct prov_record *pprov_record; + u8 *pbuffer; + struct dsp_file_hdr *pFileHdr5; + struct dsp_image_info *pDspImageInfoV6 = NULL; + long requested_version; + bool bGoodVersion = 0; + struct drv_msg *pMailBoxData; + u16 *pUsData = NULL; + u16 *pUsFile = NULL; + u8 *pUcFile = NULL; + u8 *pBootEnd = NULL; + u8 *pCodeEnd = NULL; + int imageN; + long file_version; + long loader_code_address = 0; + long loader_code_size = 0; + long run_address = 0; + long run_size = 0; + unsigned long flags; + unsigned long templong; + unsigned long image_chksum = 0; + + file_version = *(long *)pFileStart; + if (file_version != 6) { + printk(KERN_ERR "ft1000: unsupported firmware version %ld\n", file_version); + Status = FAILURE; + } + + uiState = STATE_START_DWNLD; + + pFileHdr5 = (struct dsp_file_hdr *) pFileStart; + + pUsFile = (u16 *) ((long)pFileStart + pFileHdr5->loader_offset); + pUcFile = (u8 *) ((long)pFileStart + pFileHdr5->loader_offset); + pBootEnd = (u8 *) ((long)pFileStart + pFileHdr5->loader_code_end); + loader_code_address = pFileHdr5->loader_code_address; + loader_code_size = pFileHdr5->loader_code_size; + bGoodVersion = false; + + while ((Status == SUCCESS) && (uiState != STATE_DONE_FILE)) { + + switch (uiState) { + case STATE_START_DWNLD: + + handshake = get_handshake(dev, HANDSHAKE_DSP_BL_READY); + + if (handshake == HANDSHAKE_DSP_BL_READY) { + put_handshake(dev, HANDSHAKE_DRIVER_READY); + } else { + Status = FAILURE; + } + + uiState = STATE_BOOT_DWNLD; + + break; + + case STATE_BOOT_DWNLD: + handshake = get_handshake(dev, HANDSHAKE_REQUEST); + if (handshake == HANDSHAKE_REQUEST) { + /* + * Get type associated with the request. + */ + request = get_request_type(dev); + switch (request) { + case REQUEST_RUN_ADDRESS: + put_request_value(dev, + loader_code_address); + break; + case REQUEST_CODE_LENGTH: + put_request_value(dev, + loader_code_size); + break; + case REQUEST_DONE_BL: + /* Reposition ptrs to beginning of code section */ + pUsFile = (u16 *) ((long)pBootEnd); + pUcFile = (u8 *) ((long)pBootEnd); + uiState = STATE_CODE_DWNLD; + break; + case REQUEST_CODE_SEGMENT: + word_length = get_request_value(dev); + if (word_length > MAX_LENGTH) { + Status = FAILURE; + break; + } + if ((word_length * 2 + (long)pUcFile) > + (long)pBootEnd) { + /* + * Error, beyond boot code range. + */ + Status = FAILURE; + break; + } + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, + flags); + /* + * Position ASIC DPRAM auto-increment pointer. + */ + outw(DWNLD_MAG_PS_HDR_LOC, + dev->base_addr + + FT1000_REG_DPRAM_ADDR); + if (word_length & 0x01) + word_length++; + word_length = word_length / 2; + + for (; word_length > 0; word_length--) { /* In words */ + templong = *pUsFile++; + templong |= + (*pUsFile++ << 16); + pUcFile += 4; + outl(templong, + dev->base_addr + + FT1000_REG_MAG_DPDATAL); + } + spin_unlock_irqrestore(&info-> + dpram_lock, + flags); + break; + default: + Status = FAILURE; + break; + } + put_handshake(dev, HANDSHAKE_RESPONSE); + } else { + Status = FAILURE; + } + + break; + + case STATE_CODE_DWNLD: + handshake = get_handshake(dev, HANDSHAKE_REQUEST); + if (handshake == HANDSHAKE_REQUEST) { + /* + * Get type associated with the request. + */ + request = get_request_type(dev); + switch (request) { + case REQUEST_FILE_CHECKSUM: + DEBUG(0, + "ft1000_dnld: REQUEST_FOR_CHECKSUM\n"); + put_request_value(dev, image_chksum); + break; + case REQUEST_RUN_ADDRESS: + if (bGoodVersion) { + put_request_value(dev, + run_address); + } else { + Status = FAILURE; + break; + } + break; + case REQUEST_CODE_LENGTH: + if (bGoodVersion) { + put_request_value(dev, + run_size); + } else { + Status = FAILURE; + break; + } + break; + case REQUEST_DONE_CL: + /* Reposition ptrs to beginning of provisioning section */ + pUsFile = (u16 *) ((long)pFileStart + pFileHdr5->commands_offset); + pUcFile = (u8 *) ((long)pFileStart + pFileHdr5->commands_offset); + uiState = STATE_DONE_DWNLD; + break; + case REQUEST_CODE_SEGMENT: + if (!bGoodVersion) { + Status = FAILURE; + break; + } + word_length = get_request_value(dev); + if (word_length > MAX_LENGTH) { + Status = FAILURE; + break; + } + if ((word_length * 2 + (long)pUcFile) > + (long)pCodeEnd) { + /* + * Error, beyond boot code range. + */ + Status = FAILURE; + break; + } + /* + * Position ASIC DPRAM auto-increment pointer. + */ + outw(DWNLD_MAG_PS_HDR_LOC, + dev->base_addr + + FT1000_REG_DPRAM_ADDR); + if (word_length & 0x01) + word_length++; + word_length = word_length / 2; + + for (; word_length > 0; word_length--) { /* In words */ + templong = *pUsFile++; + templong |= + (*pUsFile++ << 16); + pUcFile += 4; + outl(templong, + dev->base_addr + + FT1000_REG_MAG_DPDATAL); + } + break; + + case REQUEST_MAILBOX_DATA: + // Convert length from byte count to word count. Make sure we round up. + word_length = + (long)(info->DSPInfoBlklen + 1) / 2; + put_request_value(dev, word_length); + pMailBoxData = + (struct drv_msg *) & info->DSPInfoBlk[0]; + pUsData = + (u16 *) & pMailBoxData->data[0]; + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, + flags); + if (file_version == 5) { + /* + * Position ASIC DPRAM auto-increment pointer. + */ + ft1000_write_reg(dev, + FT1000_REG_DPRAM_ADDR, + DWNLD_PS_HDR_LOC); + + for (; word_length > 0; word_length--) { /* In words */ + temp = ntohs(*pUsData); + ft1000_write_reg(dev, + FT1000_REG_DPRAM_DATA, + temp); + pUsData++; + } + } else { + /* + * Position ASIC DPRAM auto-increment pointer. + */ + outw(DWNLD_MAG_PS_HDR_LOC, + dev->base_addr + + FT1000_REG_DPRAM_ADDR); + if (word_length & 0x01) { + word_length++; + } + word_length = word_length / 2; + + for (; word_length > 0; word_length--) { /* In words */ + templong = *pUsData++; + templong |= + (*pUsData++ << 16); + outl(templong, + dev->base_addr + + FT1000_REG_MAG_DPDATAL); + } + } + spin_unlock_irqrestore(&info-> + dpram_lock, + flags); + break; + + case REQUEST_VERSION_INFO: + word_length = + pFileHdr5->version_data_size; + put_request_value(dev, word_length); + pUsFile = + (u16 *) ((long)pFileStart + + pFileHdr5-> + version_data_offset); + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, + flags); + /* + * Position ASIC DPRAM auto-increment pointer. + */ + outw(DWNLD_MAG_PS_HDR_LOC, + dev->base_addr + + FT1000_REG_DPRAM_ADDR); + if (word_length & 0x01) + word_length++; + word_length = word_length / 2; + + for (; word_length > 0; word_length--) { /* In words */ + templong = + ntohs(*pUsFile++); + temp = + ntohs(*pUsFile++); + templong |= + (temp << 16); + outl(templong, + dev->base_addr + + FT1000_REG_MAG_DPDATAL); + } + spin_unlock_irqrestore(&info-> + dpram_lock, + flags); + break; + + case REQUEST_CODE_BY_VERSION: + bGoodVersion = false; + requested_version = + get_request_value(dev); + pDspImageInfoV6 = + (struct dsp_image_info *) ((long) + pFileStart + + + sizeof + (struct dsp_file_hdr)); + for (imageN = 0; + imageN < + pFileHdr5->nDspImages; + imageN++) { + temp = (u16) + (pDspImageInfoV6-> + version); + templong = temp; + temp = (u16) + (pDspImageInfoV6-> + version >> 16); + templong |= + (temp << 16); + if (templong == + requested_version) { + bGoodVersion = + true; + pUsFile = + (u16 + *) ((long) + pFileStart + + + pDspImageInfoV6-> + begin_offset); + pUcFile = + (u8 + *) ((long) + pFileStart + + + pDspImageInfoV6-> + begin_offset); + pCodeEnd = + (u8 + *) ((long) + pFileStart + + + pDspImageInfoV6-> + end_offset); + run_address = + pDspImageInfoV6-> + run_address; + run_size = + pDspImageInfoV6-> + image_size; + image_chksum = + (u32) + pDspImageInfoV6-> + checksum; + DEBUG(0, + "ft1000_dnld: image_chksum = 0x%8x\n", + (unsigned + int) + image_chksum); + break; + } + pDspImageInfoV6++; + } + if (!bGoodVersion) { + /* + * Error, beyond boot code range. + */ + Status = FAILURE; + break; + } + break; + + default: + Status = FAILURE; + break; + } + put_handshake(dev, HANDSHAKE_RESPONSE); + } else { + Status = FAILURE; + } + + break; + + case STATE_DONE_DWNLD: + if (((unsigned long) (pUcFile) - (unsigned long) pFileStart) >= + (unsigned long) FileLength) { + uiState = STATE_DONE_FILE; + break; + } + + pHdr = (struct pseudo_hdr *) pUsFile; + + if (pHdr->portdest == 0x80 /* DspOAM */ + && (pHdr->portsrc == 0x00 /* Driver */ + || pHdr->portsrc == 0x10 /* FMM */ )) { + uiState = STATE_SECTION_PROV; + } else { + DEBUG(1, + "FT1000:download:Download error: Bad Port IDs in Pseudo Record\n"); + DEBUG(1, "\t Port Source = 0x%2.2x\n", + pHdr->portsrc); + DEBUG(1, "\t Port Destination = 0x%2.2x\n", + pHdr->portdest); + Status = FAILURE; + } + + break; + + case STATE_SECTION_PROV: + + pHdr = (struct pseudo_hdr *) pUcFile; + + if (pHdr->checksum == hdr_checksum(pHdr)) { + if (pHdr->portdest != 0x80 /* Dsp OAM */ ) { + uiState = STATE_DONE_PROV; + break; + } + usHdrLength = ntohs(pHdr->length); /* Byte length for PROV records */ + + // Get buffer for provisioning data + pbuffer = + kmalloc((usHdrLength + sizeof(struct pseudo_hdr)), + GFP_ATOMIC); + if (pbuffer) { + memcpy(pbuffer, (void *)pUcFile, + (u32) (usHdrLength + + sizeof(struct pseudo_hdr))); + // link provisioning data + pprov_record = + kmalloc(sizeof(struct prov_record), + GFP_ATOMIC); + if (pprov_record) { + pprov_record->pprov_data = + pbuffer; + list_add_tail(&pprov_record-> + list, + &info->prov_list); + // Move to next entry if available + pUcFile = + (u8 *) ((unsigned long) pUcFile + + (unsigned long) ((usHdrLength + 1) & 0xFFFFFFFE) + sizeof(struct pseudo_hdr)); + if ((unsigned long) (pUcFile) - + (unsigned long) (pFileStart) >= + (unsigned long) FileLength) { + uiState = + STATE_DONE_FILE; + } + } else { + kfree(pbuffer); + Status = FAILURE; + } + } else { + Status = FAILURE; + } + } else { + /* Checksum did not compute */ + Status = FAILURE; + } + + break; + + case STATE_DONE_PROV: + uiState = STATE_DONE_FILE; + break; + + default: + Status = FAILURE; + break; + } /* End Switch */ + + } /* End while */ + + return Status; + +} diff --git a/drivers/staging/ft1000/ft1000-pcmcia/ft1000_hw.c b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_hw.c new file mode 100644 index 00000000..c4a8a0a2 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_hw.c @@ -0,0 +1,2226 @@ +/*--------------------------------------------------------------------------- + FT1000 driver for Flarion Flash OFDM NIC Device + + Copyright (C) 2002 Flarion Technologies, All rights reserved. + Copyright (C) 2006 Patrik Ostrihon, All rights reserved. + Copyright (C) 2006 ProWeb Consulting, a.s, All rights reserved. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) any + later version. This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. You should have received a copy of the GNU General Public + License along with this program; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. +-----------------------------------------------------------------------------*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#ifdef FT_DEBUG +#define DEBUG(n, args...) printk(KERN_DEBUG args); +#else +#define DEBUG(n, args...) +#endif + +#include +#include "ft1000.h" + +static const struct firmware *fw_entry; + +static void ft1000_hbchk(u_long data); +static struct timer_list poll_timer = { + .function = ft1000_hbchk +}; + +static u16 cmdbuffer[1024]; +static u8 tempbuffer[1600]; +static u8 ft1000_card_present = 0; +static u8 flarion_ft1000_cnt = 0; + +static irqreturn_t ft1000_interrupt(int irq, void *dev_id); +static void ft1000_enable_interrupts(struct net_device *dev); +static void ft1000_disable_interrupts(struct net_device *dev); + +/* new kernel */ +MODULE_AUTHOR(""); +MODULE_DESCRIPTION + ("Support for Flarion Flash OFDM NIC Device. Support for PCMCIA when used with ft1000_cs."); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("FT1000"); + +#define MAX_RCV_LOOP 100 + +//--------------------------------------------------------------------------- +// +// Function: ft1000_read_fifo_len +// Description: This function will read the ASIC Uplink FIFO status register +// which will return the number of bytes remaining in the Uplink FIFO. +// Sixteen bytes are subtracted to make sure that the ASIC does not +// reach its threshold. +// Input: +// dev - network device structure +// Output: +// value - number of bytes available in the ASIC Uplink FIFO. +// +//--------------------------------------------------------------------------- +static inline u16 ft1000_read_fifo_len(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + + if (info->AsicID == ELECTRABUZZ_ID) { + return (ft1000_read_reg(dev, FT1000_REG_UFIFO_STAT) - 16); + } else { + return (ft1000_read_reg(dev, FT1000_REG_MAG_UFSR) - 16); + } +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_read_dpram +// Description: This function will read the specific area of dpram +// (Electrabuzz ASIC only) +// Input: +// dev - device structure +// offset - index of dpram +// Output: +// value - value of dpram +// +//--------------------------------------------------------------------------- +u16 ft1000_read_dpram(struct net_device * dev, int offset) +{ + struct ft1000_info *info = netdev_priv(dev); + unsigned long flags; + u16 data; + + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, flags); + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, offset); + data = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); + spin_unlock_irqrestore(&info->dpram_lock, flags); + + return (data); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_write_dpram +// Description: This function will write to a specific area of dpram +// (Electrabuzz ASIC only) +// Input: +// dev - device structure +// offset - index of dpram +// value - value to write +// Output: +// none. +// +//--------------------------------------------------------------------------- +static inline void ft1000_write_dpram(struct net_device *dev, + int offset, u16 value) +{ + struct ft1000_info *info = netdev_priv(dev); + unsigned long flags; + + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, flags); + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, offset); + ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, value); + spin_unlock_irqrestore(&info->dpram_lock, flags); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_read_dpram_mag_16 +// Description: This function will read the specific area of dpram +// (Magnemite ASIC only) +// Input: +// dev - device structure +// offset - index of dpram +// Output: +// value - value of dpram +// +//--------------------------------------------------------------------------- +u16 ft1000_read_dpram_mag_16(struct net_device *dev, int offset, int Index) +{ + struct ft1000_info *info = netdev_priv(dev); + unsigned long flags; + u16 data; + + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, flags); + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, offset); + // check if we want to read upper or lower 32-bit word + if (Index) { + data = ft1000_read_reg(dev, FT1000_REG_MAG_DPDATAL); + } else { + data = ft1000_read_reg(dev, FT1000_REG_MAG_DPDATAH); + } + spin_unlock_irqrestore(&info->dpram_lock, flags); + + return (data); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_write_dpram_mag_16 +// Description: This function will write to a specific area of dpram +// (Magnemite ASIC only) +// Input: +// dev - device structure +// offset - index of dpram +// value - value to write +// Output: +// none. +// +//--------------------------------------------------------------------------- +static inline void ft1000_write_dpram_mag_16(struct net_device *dev, + int offset, u16 value, int Index) +{ + struct ft1000_info *info = netdev_priv(dev); + unsigned long flags; + + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, flags); + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, offset); + if (Index) { + ft1000_write_reg(dev, FT1000_REG_MAG_DPDATAL, value); + } else { + ft1000_write_reg(dev, FT1000_REG_MAG_DPDATAH, value); + } + spin_unlock_irqrestore(&info->dpram_lock, flags); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_read_dpram_mag_32 +// Description: This function will read the specific area of dpram +// (Magnemite ASIC only) +// Input: +// dev - device structure +// offset - index of dpram +// Output: +// value - value of dpram +// +//--------------------------------------------------------------------------- +u32 ft1000_read_dpram_mag_32(struct net_device *dev, int offset) +{ + struct ft1000_info *info = netdev_priv(dev); + unsigned long flags; + u32 data; + + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, flags); + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, offset); + data = inl(dev->base_addr + FT1000_REG_MAG_DPDATAL); + spin_unlock_irqrestore(&info->dpram_lock, flags); + + return (data); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_write_dpram_mag_32 +// Description: This function will write to a specific area of dpram +// (Magnemite ASIC only) +// Input: +// dev - device structure +// offset - index of dpram +// value - value to write +// Output: +// none. +// +//--------------------------------------------------------------------------- +void ft1000_write_dpram_mag_32(struct net_device *dev, int offset, u32 value) +{ + struct ft1000_info *info = netdev_priv(dev); + unsigned long flags; + + // Provide mutual exclusive access while reading ASIC registers. + spin_lock_irqsave(&info->dpram_lock, flags); + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, offset); + outl(value, dev->base_addr + FT1000_REG_MAG_DPDATAL); + spin_unlock_irqrestore(&info->dpram_lock, flags); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_enable_interrupts +// Description: This function will enable interrupts base on the current interrupt mask. +// Input: +// dev - device structure +// Output: +// None. +// +//--------------------------------------------------------------------------- +static void ft1000_enable_interrupts(struct net_device *dev) +{ + u16 tempword; + + DEBUG(1, "ft1000_hw:ft1000_enable_interrupts()\n"); + ft1000_write_reg(dev, FT1000_REG_SUP_IMASK, ISR_DEFAULT_MASK); + tempword = ft1000_read_reg(dev, FT1000_REG_SUP_IMASK); + DEBUG(1, + "ft1000_hw:ft1000_enable_interrupts:current interrupt enable mask = 0x%x\n", + tempword); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_disable_interrupts +// Description: This function will disable all interrupts. +// Input: +// dev - device structure +// Output: +// None. +// +//--------------------------------------------------------------------------- +static void ft1000_disable_interrupts(struct net_device *dev) +{ + u16 tempword; + + DEBUG(1, "ft1000_hw: ft1000_disable_interrupts()\n"); + ft1000_write_reg(dev, FT1000_REG_SUP_IMASK, ISR_MASK_ALL); + tempword = ft1000_read_reg(dev, FT1000_REG_SUP_IMASK); + DEBUG(1, + "ft1000_hw:ft1000_disable_interrupts:current interrupt enable mask = 0x%x\n", + tempword); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_reset_asic +// Description: This function will call the Card Service function to reset the +// ASIC. +// Input: +// dev - device structure +// Output: +// none +// +//--------------------------------------------------------------------------- +static void ft1000_reset_asic(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + u16 tempword; + + DEBUG(1, "ft1000_hw:ft1000_reset_asic called\n"); + + (*info->ft1000_reset) (info->link); + + // Let's use the register provided by the Magnemite ASIC to reset the + // ASIC and DSP. + if (info->AsicID == MAGNEMITE_ID) { + ft1000_write_reg(dev, FT1000_REG_RESET, + (DSP_RESET_BIT | ASIC_RESET_BIT)); + } + mdelay(1); + if (info->AsicID == ELECTRABUZZ_ID) { + // set watermark to -1 in order to not generate an interrupt + ft1000_write_reg(dev, FT1000_REG_WATERMARK, 0xffff); + } else { + // set watermark to -1 in order to not generate an interrupt + ft1000_write_reg(dev, FT1000_REG_MAG_WATERMARK, 0xffff); + } + // clear interrupts + tempword = ft1000_read_reg(dev, FT1000_REG_SUP_ISR); + DEBUG(1, "ft1000_hw: interrupt status register = 0x%x\n", tempword); + ft1000_write_reg(dev, FT1000_REG_SUP_ISR, tempword); + tempword = ft1000_read_reg(dev, FT1000_REG_SUP_ISR); + DEBUG(1, "ft1000_hw: interrupt status register = 0x%x\n", tempword); + +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_reset_card +// Description: This function will reset the card +// Input: +// dev - device structure +// Output: +// status - false (card reset fail) +// true (card reset successful) +// +//--------------------------------------------------------------------------- +static int ft1000_reset_card(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + u16 tempword; + int i; + unsigned long flags; + struct prov_record *ptr; + + DEBUG(1, "ft1000_hw:ft1000_reset_card called.....\n"); + + info->CardReady = 0; + info->ProgConStat = 0; + info->squeseqnum = 0; + ft1000_disable_interrupts(dev); + +// del_timer(&poll_timer); + + // Make sure we free any memory reserve for provisioning + while (list_empty(&info->prov_list) == 0) { + DEBUG(0, + "ft1000_hw:ft1000_reset_card:deleting provisioning record\n"); + ptr = list_entry(info->prov_list.next, struct prov_record, list); + list_del(&ptr->list); + kfree(ptr->pprov_data); + kfree(ptr); + } + + if (info->AsicID == ELECTRABUZZ_ID) { + DEBUG(1, "ft1000_hw:ft1000_reset_card:resetting DSP\n"); + ft1000_write_reg(dev, FT1000_REG_RESET, DSP_RESET_BIT); + } else { + DEBUG(1, + "ft1000_hw:ft1000_reset_card:resetting ASIC and DSP\n"); + ft1000_write_reg(dev, FT1000_REG_RESET, + (DSP_RESET_BIT | ASIC_RESET_BIT)); + } + + // Copy DSP session record into info block if this is not a coldstart + if (ft1000_card_present == 1) { + spin_lock_irqsave(&info->dpram_lock, flags); + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_RX_BASE); + for (i = 0; i < MAX_DSP_SESS_REC; i++) { + info->DSPSess.Rec[i] = + ft1000_read_reg(dev, + FT1000_REG_DPRAM_DATA); + } + } else { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_MAG_RX_BASE); + for (i = 0; i < MAX_DSP_SESS_REC / 2; i++) { + info->DSPSess.MagRec[i] = + inl(dev->base_addr + FT1000_REG_MAG_DPDATA); + } + } + spin_unlock_irqrestore(&info->dpram_lock, flags); + } + + DEBUG(1, "ft1000_hw:ft1000_reset_card:resetting ASIC\n"); + mdelay(10); + //reset ASIC + ft1000_reset_asic(dev); + + DEBUG(1, "ft1000_hw:ft1000_reset_card:downloading dsp image\n"); + + if (info->AsicID == MAGNEMITE_ID) { + // Put dsp in reset and take ASIC out of reset + DEBUG(0, + "ft1000_hw:ft1000_reset_card:Put DSP in reset and take ASIC out of reset\n"); + ft1000_write_reg(dev, FT1000_REG_RESET, DSP_RESET_BIT); + + // Setting MAGNEMITE ASIC to big endian mode + ft1000_write_reg(dev, FT1000_REG_SUP_CTRL, HOST_INTF_BE); + // Download bootloader + card_bootload(dev); + + // Take DSP out of reset + ft1000_write_reg(dev, FT1000_REG_RESET, 0); + // FLARION_DSP_ACTIVE; + mdelay(10); + DEBUG(0, "ft1000_hw:ft1000_reset_card:Take DSP out of reset\n"); + + // Wait for 0xfefe indicating dsp ready before starting download + for (i = 0; i < 50; i++) { + tempword = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_DPRAM_FEFE, + FT1000_MAG_DPRAM_FEFE_INDX); + if (tempword == 0xfefe) { + break; + } + mdelay(20); + } + + if (i == 50) { + DEBUG(0, + "ft1000_hw:ft1000_reset_card:No FEFE detected from DSP\n"); + return false; + } + + } else { + // Take DSP out of reset + ft1000_write_reg(dev, FT1000_REG_RESET, ~DSP_RESET_BIT); + mdelay(10); + } + + if (card_download(dev, fw_entry->data, fw_entry->size)) { + DEBUG(1, "card download unsuccessful\n"); + return false; + } else { + DEBUG(1, "card download successful\n"); + } + + mdelay(10); + + if (info->AsicID == ELECTRABUZZ_ID) { + // Need to initialize the FIFO length counter to zero in order to sync up + // with the DSP + info->fifo_cnt = 0; + ft1000_write_dpram(dev, FT1000_FIFO_LEN, info->fifo_cnt); + // Initialize DSP heartbeat area to ho + ft1000_write_dpram(dev, FT1000_HI_HO, ho); + tempword = ft1000_read_dpram(dev, FT1000_HI_HO); + DEBUG(1, "ft1000_hw:ft1000_reset_asic:hi_ho value = 0x%x\n", + tempword); + } else { + // Initialize DSP heartbeat area to ho + ft1000_write_dpram_mag_16(dev, FT1000_MAG_HI_HO, ho_mag, + FT1000_MAG_HI_HO_INDX); + tempword = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_HI_HO, + FT1000_MAG_HI_HO_INDX); + DEBUG(1, "ft1000_hw:ft1000_reset_card:hi_ho value = 0x%x\n", + tempword); + } + + info->CardReady = 1; + ft1000_enable_interrupts(dev); + + /* Schedule heartbeat process to run every 2 seconds */ +// poll_timer.expires = jiffies + (2*HZ); +// poll_timer.data = (u_long)dev; +// add_timer(&poll_timer); + + return true; + +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_chkcard +// Description: This function will check if the device is presently available on +// the system. +// Input: +// dev - device structure +// Output: +// status - false (device is not present) +// true (device is present) +// +//--------------------------------------------------------------------------- +static int ft1000_chkcard(struct net_device *dev) +{ + u16 tempword; + + // Mask register is used to check for device presence since it is never + // set to zero. + tempword = ft1000_read_reg(dev, FT1000_REG_SUP_IMASK); + if (tempword == 0) { + DEBUG(1, + "ft1000_hw:ft1000_chkcard: IMASK = 0 Card not detected\n"); + return false; + } + // The system will return the value of 0xffff for the version register + // if the device is not present. + tempword = ft1000_read_reg(dev, FT1000_REG_ASIC_ID); + if (tempword == 0xffff) { + DEBUG(1, + "ft1000_hw:ft1000_chkcard: Version = 0xffff Card not detected\n"); + return false; + } + return true; +} + + +//--------------------------------------------------------------------------- +// +// Function: ft1000_hbchk +// Description: This function will perform the heart beat check of the DSP as +// well as the ASIC. +// Input: +// dev - device structure +// Output: +// none +// +//--------------------------------------------------------------------------- +static void ft1000_hbchk(u_long data) +{ + struct net_device *dev = (struct net_device *)data; + + struct ft1000_info *info; + u16 tempword; + + info = netdev_priv(dev); + + if (info->CardReady == 1) { + // Perform dsp heartbeat check + if (info->AsicID == ELECTRABUZZ_ID) { + tempword = ft1000_read_dpram(dev, FT1000_HI_HO); + } else { + tempword = + ntohs(ft1000_read_dpram_mag_16 + (dev, FT1000_MAG_HI_HO, + FT1000_MAG_HI_HO_INDX)); + } + DEBUG(1, "ft1000_hw:ft1000_hbchk:hi_ho value = 0x%x\n", + tempword); + // Let's perform another check if ho is not detected + if (tempword != ho) { + if (info->AsicID == ELECTRABUZZ_ID) { + tempword = ft1000_read_dpram(dev, FT1000_HI_HO); + } + else { + tempword = ntohs(ft1000_read_dpram_mag_16(dev, FT1000_MAG_HI_HO, FT1000_MAG_HI_HO_INDX)); + } + } + if (tempword != ho) { + printk(KERN_INFO + "ft1000: heartbeat failed - no ho detected\n"); + if (info->AsicID == ELECTRABUZZ_ID) { + info->DSP_TIME[0] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER0); + info->DSP_TIME[1] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER1); + info->DSP_TIME[2] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER2); + info->DSP_TIME[3] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER3); + } else { + info->DSP_TIME[0] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER0, + FT1000_MAG_DSP_TIMER0_INDX); + info->DSP_TIME[1] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER1, + FT1000_MAG_DSP_TIMER1_INDX); + info->DSP_TIME[2] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER2, + FT1000_MAG_DSP_TIMER2_INDX); + info->DSP_TIME[3] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER3, + FT1000_MAG_DSP_TIMER3_INDX); + } + info->DrvErrNum = DSP_HB_INFO; + if (ft1000_reset_card(dev) == 0) { + printk(KERN_INFO + "ft1000: Hardware Failure Detected - PC Card disabled\n"); + info->ProgConStat = 0xff; + return; + } + /* Schedule this module to run every 2 seconds */ + poll_timer.expires = jiffies + (2*HZ); + poll_timer.data = (u_long)dev; + add_timer(&poll_timer); + return; + } + + tempword = ft1000_read_reg(dev, FT1000_REG_DOORBELL); + // Let's check doorbell again if fail + if (tempword & FT1000_DB_HB) { + tempword = ft1000_read_reg(dev, FT1000_REG_DOORBELL); + } + if (tempword & FT1000_DB_HB) { + printk(KERN_INFO + "ft1000: heartbeat doorbell not clear by firmware\n"); + if (info->AsicID == ELECTRABUZZ_ID) { + info->DSP_TIME[0] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER0); + info->DSP_TIME[1] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER1); + info->DSP_TIME[2] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER2); + info->DSP_TIME[3] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER3); + } else { + info->DSP_TIME[0] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER0, + FT1000_MAG_DSP_TIMER0_INDX); + info->DSP_TIME[1] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER1, + FT1000_MAG_DSP_TIMER1_INDX); + info->DSP_TIME[2] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER2, + FT1000_MAG_DSP_TIMER2_INDX); + info->DSP_TIME[3] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER3, + FT1000_MAG_DSP_TIMER3_INDX); + } + info->DrvErrNum = DSP_HB_INFO; + if (ft1000_reset_card(dev) == 0) { + printk(KERN_INFO + "ft1000: Hardware Failure Detected - PC Card disabled\n"); + info->ProgConStat = 0xff; + return; + } + /* Schedule this module to run every 2 seconds */ + poll_timer.expires = jiffies + (2*HZ); + poll_timer.data = (u_long)dev; + add_timer(&poll_timer); + return; + } + // Set dedicated area to hi and ring appropriate doorbell according + // to hi/ho heartbeat protocol + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_dpram(dev, FT1000_HI_HO, hi); + } else { + ft1000_write_dpram_mag_16(dev, FT1000_MAG_HI_HO, hi_mag, + FT1000_MAG_HI_HO_INDX); + } + + if (info->AsicID == ELECTRABUZZ_ID) { + tempword = ft1000_read_dpram(dev, FT1000_HI_HO); + } else { + tempword = + ntohs(ft1000_read_dpram_mag_16 + (dev, FT1000_MAG_HI_HO, + FT1000_MAG_HI_HO_INDX)); + } + // Let's write hi again if fail + if (tempword != hi) { + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_dpram(dev, FT1000_HI_HO, hi); + } + else { + ft1000_write_dpram_mag_16(dev, FT1000_MAG_HI_HO, hi_mag, FT1000_MAG_HI_HO_INDX); + } + + if (info->AsicID == ELECTRABUZZ_ID) { + tempword = ft1000_read_dpram(dev, FT1000_HI_HO); + } + else { + tempword = ntohs(ft1000_read_dpram_mag_16(dev, FT1000_MAG_HI_HO, FT1000_MAG_HI_HO_INDX)); + } + + } + + if (tempword != hi) { + printk(KERN_INFO + "ft1000: heartbeat failed - cannot write hi into DPRAM\n"); + if (info->AsicID == ELECTRABUZZ_ID) { + info->DSP_TIME[0] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER0); + info->DSP_TIME[1] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER1); + info->DSP_TIME[2] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER2); + info->DSP_TIME[3] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER3); + } else { + info->DSP_TIME[0] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER0, + FT1000_MAG_DSP_TIMER0_INDX); + info->DSP_TIME[1] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER1, + FT1000_MAG_DSP_TIMER1_INDX); + info->DSP_TIME[2] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER2, + FT1000_MAG_DSP_TIMER2_INDX); + info->DSP_TIME[3] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER3, + FT1000_MAG_DSP_TIMER3_INDX); + } + info->DrvErrNum = DSP_HB_INFO; + if (ft1000_reset_card(dev) == 0) { + printk(KERN_INFO + "ft1000: Hardware Failure Detected - PC Card disabled\n"); + info->ProgConStat = 0xff; + return; + } + /* Schedule this module to run every 2 seconds */ + poll_timer.expires = jiffies + (2*HZ); + poll_timer.data = (u_long)dev; + add_timer(&poll_timer); + return; + } + ft1000_write_reg(dev, FT1000_REG_DOORBELL, FT1000_DB_HB); + + } + + /* Schedule this module to run every 2 seconds */ + poll_timer.expires = jiffies + (2 * HZ); + poll_timer.data = (u_long) dev; + add_timer(&poll_timer); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_send_cmd +// Description: +// Input: +// Output: +// +//--------------------------------------------------------------------------- +static void ft1000_send_cmd (struct net_device *dev, u16 *ptempbuffer, int size, u16 qtype) +{ + struct ft1000_info *info = netdev_priv(dev); + int i; + u16 tempword; + unsigned long flags; + + size += sizeof(struct pseudo_hdr); + // check for odd byte and increment to 16-bit word align value + if ((size & 0x0001)) { + size++; + } + DEBUG(1, "FT1000:ft1000_send_cmd:total length = %d\n", size); + DEBUG(1, "FT1000:ft1000_send_cmd:length = %d\n", ntohs(*ptempbuffer)); + // put message into slow queue area + // All messages are in the form total_len + pseudo header + message body + spin_lock_irqsave(&info->dpram_lock, flags); + + // Make sure SLOWQ doorbell is clear + tempword = ft1000_read_reg(dev, FT1000_REG_DOORBELL); + i=0; + while (tempword & FT1000_DB_DPRAM_TX) { + mdelay(10); + i++; + if (i==10) { + spin_unlock_irqrestore(&info->dpram_lock, flags); + return; + } + tempword = ft1000_read_reg(dev, FT1000_REG_DOORBELL); + } + + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_TX_BASE); + // Write total length to dpram + ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, size); + // Write pseudo header and messgae body + for (i = 0; i < (size >> 1); i++) { + DEBUG(1, "FT1000:ft1000_send_cmd:data %d = 0x%x\n", i, + *ptempbuffer); + tempword = htons(*ptempbuffer++); + ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, tempword); + } + } else { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_MAG_TX_BASE); + // Write total length to dpram + ft1000_write_reg(dev, FT1000_REG_MAG_DPDATAH, htons(size)); + // Write pseudo header and messgae body + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_MAG_TX_BASE + 1); + for (i = 0; i < (size >> 2); i++) { + DEBUG(1, "FT1000:ft1000_send_cmd:data = 0x%x\n", + *ptempbuffer); + outw(*ptempbuffer++, + dev->base_addr + FT1000_REG_MAG_DPDATAL); + DEBUG(1, "FT1000:ft1000_send_cmd:data = 0x%x\n", + *ptempbuffer); + outw(*ptempbuffer++, + dev->base_addr + FT1000_REG_MAG_DPDATAH); + } + DEBUG(1, "FT1000:ft1000_send_cmd:data = 0x%x\n", *ptempbuffer); + outw(*ptempbuffer++, dev->base_addr + FT1000_REG_MAG_DPDATAL); + DEBUG(1, "FT1000:ft1000_send_cmd:data = 0x%x\n", *ptempbuffer); + outw(*ptempbuffer++, dev->base_addr + FT1000_REG_MAG_DPDATAH); + } + spin_unlock_irqrestore(&info->dpram_lock, flags); + + // ring doorbell to notify DSP that we have a message ready + ft1000_write_reg(dev, FT1000_REG_DOORBELL, FT1000_DB_DPRAM_TX); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_receive_cmd +// Description: This function will read a message from the dpram area. +// Input: +// dev - network device structure +// pbuffer - caller supply address to buffer +// pnxtph - pointer to next pseudo header +// Output: +// Status = 0 (unsuccessful) +// = 1 (successful) +// +//--------------------------------------------------------------------------- +static bool ft1000_receive_cmd(struct net_device *dev, u16 *pbuffer, + int maxsz, u16 *pnxtph) +{ + struct ft1000_info *info = netdev_priv(dev); + u16 size; + u16 *ppseudohdr; + int i; + u16 tempword; + unsigned long flags; + + if (info->AsicID == ELECTRABUZZ_ID) { + size = ( ft1000_read_dpram(dev, *pnxtph) ) + sizeof(struct pseudo_hdr); + } else { + size = + ntohs(ft1000_read_dpram_mag_16 + (dev, FT1000_MAG_PH_LEN, + FT1000_MAG_PH_LEN_INDX)) + sizeof(struct pseudo_hdr); + } + if (size > maxsz) { + DEBUG(1, + "FT1000:ft1000_receive_cmd:Invalid command length = %d\n", + size); + return false; + } else { + ppseudohdr = (u16 *) pbuffer; + spin_lock_irqsave(&info->dpram_lock, flags); + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_RX_BASE + 2); + for (i = 0; i <= (size >> 1); i++) { + tempword = + ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); + *pbuffer++ = ntohs(tempword); + } + } else { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_MAG_RX_BASE); + *pbuffer = inw(dev->base_addr + FT1000_REG_MAG_DPDATAH); + DEBUG(1, "ft1000_hw:received data = 0x%x\n", *pbuffer); + pbuffer++; + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_MAG_RX_BASE + 1); + for (i = 0; i <= (size >> 2); i++) { + *pbuffer = + inw(dev->base_addr + + FT1000_REG_MAG_DPDATAL); + pbuffer++; + *pbuffer = + inw(dev->base_addr + + FT1000_REG_MAG_DPDATAH); + pbuffer++; + } + //copy odd aligned word + *pbuffer = inw(dev->base_addr + FT1000_REG_MAG_DPDATAL); + DEBUG(1, "ft1000_hw:received data = 0x%x\n", *pbuffer); + pbuffer++; + *pbuffer = inw(dev->base_addr + FT1000_REG_MAG_DPDATAH); + DEBUG(1, "ft1000_hw:received data = 0x%x\n", *pbuffer); + pbuffer++; + } + if (size & 0x0001) { + //copy odd byte from fifo + tempword = ft1000_read_reg(dev, FT1000_REG_DPRAM_DATA); + *pbuffer = ntohs(tempword); + } + spin_unlock_irqrestore(&info->dpram_lock, flags); + + // Check if pseudo header checksum is good + // Calculate pseudo header checksum + tempword = *ppseudohdr++; + for (i = 1; i < 7; i++) { + tempword ^= *ppseudohdr++; + } + if ((tempword != *ppseudohdr)) { + DEBUG(1, + "FT1000:ft1000_receive_cmd:Pseudo header checksum mismatch\n"); + // Drop this message + return false; + } + return true; + } +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_proc_drvmsg +// Description: This function will process the various driver messages. +// Input: +// dev - device structure +// pnxtph - pointer to next pseudo header +// Output: +// none +// +//--------------------------------------------------------------------------- +static void ft1000_proc_drvmsg(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + u16 msgtype; + u16 tempword; + struct media_msg *pmediamsg; + struct dsp_init_msg *pdspinitmsg; + struct drv_msg *pdrvmsg; + u16 len; + u16 i; + struct prov_record *ptr; + struct pseudo_hdr *ppseudo_hdr; + u16 *pmsg; + struct timeval tv; + union { + u8 byte[2]; + u16 wrd; + } convert; + + if (info->AsicID == ELECTRABUZZ_ID) { + tempword = FT1000_DPRAM_RX_BASE+2; + } + else { + tempword = FT1000_DPRAM_MAG_RX_BASE; + } + if ( ft1000_receive_cmd(dev, &cmdbuffer[0], MAX_CMD_SQSIZE, &tempword) ) { + + // Get the message type which is total_len + PSEUDO header + msgtype + message body + pdrvmsg = (struct drv_msg *) & cmdbuffer[0]; + msgtype = ntohs(pdrvmsg->type); + DEBUG(1, "Command message type = 0x%x\n", msgtype); + switch (msgtype) { + case DSP_PROVISION: + DEBUG(0, + "Got a provisioning request message from DSP\n"); + mdelay(25); + while (list_empty(&info->prov_list) == 0) { + DEBUG(0, "Sending a provisioning message\n"); + // Make sure SLOWQ doorbell is clear + tempword = + ft1000_read_reg(dev, FT1000_REG_DOORBELL); + i = 0; + while (tempword & FT1000_DB_DPRAM_TX) { + mdelay(5); + i++; + if (i == 10) { + break; + } + } + ptr = + list_entry(info->prov_list.next, + struct prov_record, list); + len = *(u16 *) ptr->pprov_data; + len = htons(len); + + pmsg = (u16 *) ptr->pprov_data; + ppseudo_hdr = (struct pseudo_hdr *) pmsg; + // Insert slow queue sequence number + ppseudo_hdr->seq_num = info->squeseqnum++; + ppseudo_hdr->portsrc = 0; + // Calculate new checksum + ppseudo_hdr->checksum = *pmsg++; + DEBUG(1, "checksum = 0x%x\n", + ppseudo_hdr->checksum); + for (i = 1; i < 7; i++) { + ppseudo_hdr->checksum ^= *pmsg++; + DEBUG(1, "checksum = 0x%x\n", + ppseudo_hdr->checksum); + } + + ft1000_send_cmd (dev, (u16 *)ptr->pprov_data, len, SLOWQ_TYPE); + list_del(&ptr->list); + kfree(ptr->pprov_data); + kfree(ptr); + } + // Indicate adapter is ready to take application messages after all + // provisioning messages are sent + info->CardReady = 1; + break; + case MEDIA_STATE: + pmediamsg = (struct media_msg *) & cmdbuffer[0]; + if (info->ProgConStat != 0xFF) { + if (pmediamsg->state) { + DEBUG(1, "Media is up\n"); + if (info->mediastate == 0) { + netif_carrier_on(dev); + netif_wake_queue(dev); + info->mediastate = 1; + do_gettimeofday(&tv); + info->ConTm = tv.tv_sec; + } + } else { + DEBUG(1, "Media is down\n"); + if (info->mediastate == 1) { + info->mediastate = 0; + netif_carrier_off(dev); + netif_stop_queue(dev); + info->ConTm = 0; + } + } + } + else { + DEBUG(1,"Media is down\n"); + if (info->mediastate == 1) { + info->mediastate = 0; + netif_carrier_off(dev); + netif_stop_queue(dev); + info->ConTm = 0; + } + } + break; + case DSP_INIT_MSG: + pdspinitmsg = (struct dsp_init_msg *) & cmdbuffer[0]; + memcpy(info->DspVer, pdspinitmsg->DspVer, DSPVERSZ); + DEBUG(1, "DSPVER = 0x%2x 0x%2x 0x%2x 0x%2x\n", + info->DspVer[0], info->DspVer[1], info->DspVer[2], + info->DspVer[3]); + memcpy(info->HwSerNum, pdspinitmsg->HwSerNum, + HWSERNUMSZ); + memcpy(info->Sku, pdspinitmsg->Sku, SKUSZ); + memcpy(info->eui64, pdspinitmsg->eui64, EUISZ); + dev->dev_addr[0] = info->eui64[0]; + dev->dev_addr[1] = info->eui64[1]; + dev->dev_addr[2] = info->eui64[2]; + dev->dev_addr[3] = info->eui64[5]; + dev->dev_addr[4] = info->eui64[6]; + dev->dev_addr[5] = info->eui64[7]; + + if (ntohs(pdspinitmsg->length) == + (sizeof(struct dsp_init_msg) - 20)) { + memcpy(info->ProductMode, + pdspinitmsg->ProductMode, MODESZ); + memcpy(info->RfCalVer, pdspinitmsg->RfCalVer, + CALVERSZ); + memcpy(info->RfCalDate, pdspinitmsg->RfCalDate, + CALDATESZ); + DEBUG(1, "RFCalVer = 0x%2x 0x%2x\n", + info->RfCalVer[0], info->RfCalVer[1]); + } + + break ; + case DSP_STORE_INFO: + DEBUG(1, "FT1000:drivermsg:Got DSP_STORE_INFO\n"); + tempword = ntohs(pdrvmsg->length); + info->DSPInfoBlklen = tempword; + if (tempword < (MAX_DSP_SESS_REC - 4)) { + pmsg = (u16 *) & pdrvmsg->data[0]; + for (i = 0; i < ((tempword + 1) / 2); i++) { + DEBUG(1, + "FT1000:drivermsg:dsp info data = 0x%x\n", + *pmsg); + info->DSPInfoBlk[i + 10] = *pmsg++; + } + } + break; + case DSP_GET_INFO: + DEBUG(1, "FT1000:drivermsg:Got DSP_GET_INFO\n"); + // copy dsp info block to dsp + // allow any outstanding ioctl to finish + mdelay(10); + tempword = ft1000_read_reg(dev, FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + mdelay(10); + tempword = + ft1000_read_reg(dev, FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + mdelay(10); + } + } + + if ((tempword & FT1000_DB_DPRAM_TX) == 0) { + // Put message into Slow Queue + // Form Pseudo header + pmsg = (u16 *) info->DSPInfoBlk; + ppseudo_hdr = (struct pseudo_hdr *) pmsg; + ppseudo_hdr->length = + htons(info->DSPInfoBlklen + 4); + ppseudo_hdr->source = 0x10; + ppseudo_hdr->destination = 0x20; + ppseudo_hdr->portdest = 0; + ppseudo_hdr->portsrc = 0; + ppseudo_hdr->sh_str_id = 0; + ppseudo_hdr->control = 0; + ppseudo_hdr->rsvd1 = 0; + ppseudo_hdr->rsvd2 = 0; + ppseudo_hdr->qos_class = 0; + // Insert slow queue sequence number + ppseudo_hdr->seq_num = info->squeseqnum++; + // Insert application id + ppseudo_hdr->portsrc = 0; + // Calculate new checksum + ppseudo_hdr->checksum = *pmsg++; + for (i = 1; i < 7; i++) { + ppseudo_hdr->checksum ^= *pmsg++; + } + info->DSPInfoBlk[8] = 0x7200; + info->DSPInfoBlk[9] = + htons(info->DSPInfoBlklen); + ft1000_send_cmd (dev, (u16 *)info->DSPInfoBlk, (u16)(info->DSPInfoBlklen+4), 0); + } + + break; + case GET_DRV_ERR_RPT_MSG: + DEBUG(1, "FT1000:drivermsg:Got GET_DRV_ERR_RPT_MSG\n"); + // copy driver error message to dsp + // allow any outstanding ioctl to finish + mdelay(10); + tempword = ft1000_read_reg(dev, FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + mdelay(10); + tempword = + ft1000_read_reg(dev, FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + mdelay(10); + } + } + + if ((tempword & FT1000_DB_DPRAM_TX) == 0) { + // Put message into Slow Queue + // Form Pseudo header + pmsg = (u16 *) & tempbuffer[0]; + ppseudo_hdr = (struct pseudo_hdr *) pmsg; + ppseudo_hdr->length = htons(0x0012); + ppseudo_hdr->source = 0x10; + ppseudo_hdr->destination = 0x20; + ppseudo_hdr->portdest = 0; + ppseudo_hdr->portsrc = 0; + ppseudo_hdr->sh_str_id = 0; + ppseudo_hdr->control = 0; + ppseudo_hdr->rsvd1 = 0; + ppseudo_hdr->rsvd2 = 0; + ppseudo_hdr->qos_class = 0; + // Insert slow queue sequence number + ppseudo_hdr->seq_num = info->squeseqnum++; + // Insert application id + ppseudo_hdr->portsrc = 0; + // Calculate new checksum + ppseudo_hdr->checksum = *pmsg++; + for (i=1; i<7; i++) { + ppseudo_hdr->checksum ^= *pmsg++; + } + pmsg = (u16 *) & tempbuffer[16]; + *pmsg++ = htons(RSP_DRV_ERR_RPT_MSG); + *pmsg++ = htons(0x000e); + *pmsg++ = htons(info->DSP_TIME[0]); + *pmsg++ = htons(info->DSP_TIME[1]); + *pmsg++ = htons(info->DSP_TIME[2]); + *pmsg++ = htons(info->DSP_TIME[3]); + convert.byte[0] = info->DspVer[0]; + convert.byte[1] = info->DspVer[1]; + *pmsg++ = convert.wrd; + convert.byte[0] = info->DspVer[2]; + convert.byte[1] = info->DspVer[3]; + *pmsg++ = convert.wrd; + *pmsg++ = htons(info->DrvErrNum); + + ft1000_send_cmd (dev, (u16 *)&tempbuffer[0], (u16)(0x0012), 0); + info->DrvErrNum = 0; + } + + break; + default: + break; + } + } +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_parse_dpram_msg +// Description: This function will parse the message received from the DSP +// via the DPRAM interface. +// Input: +// dev - device structure +// Output: +// status - FAILURE +// SUCCESS +// +//--------------------------------------------------------------------------- +static int ft1000_parse_dpram_msg(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + u16 doorbell; + u16 portid; + u16 nxtph; + u16 total_len; + int i = 0; + int cnt; + unsigned long flags; + + doorbell = ft1000_read_reg(dev, FT1000_REG_DOORBELL); + DEBUG(1, "Doorbell = 0x%x\n", doorbell); + + if (doorbell & FT1000_ASIC_RESET_REQ) { + // Copy DSP session record from info block + spin_lock_irqsave(&info->dpram_lock, flags); + if (info->AsicID == ELECTRABUZZ_ID) { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_RX_BASE); + for (i = 0; i < MAX_DSP_SESS_REC; i++) { + ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, + info->DSPSess.Rec[i]); + } + } else { + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, + FT1000_DPRAM_MAG_RX_BASE); + for (i = 0; i < MAX_DSP_SESS_REC / 2; i++) { + outl(info->DSPSess.MagRec[i], + dev->base_addr + FT1000_REG_MAG_DPDATA); + } + } + spin_unlock_irqrestore(&info->dpram_lock, flags); + + // clear ASIC RESET request + ft1000_write_reg(dev, FT1000_REG_DOORBELL, + FT1000_ASIC_RESET_REQ); + DEBUG(1, "Got an ASIC RESET Request\n"); + ft1000_write_reg(dev, FT1000_REG_DOORBELL, + FT1000_ASIC_RESET_DSP); + + if (info->AsicID == MAGNEMITE_ID) { + // Setting MAGNEMITE ASIC to big endian mode + ft1000_write_reg(dev, FT1000_REG_SUP_CTRL, + HOST_INTF_BE); + } + } + + if (doorbell & FT1000_DSP_ASIC_RESET) { + DEBUG(0, + "FT1000:ft1000_parse_dpram_msg: Got a dsp ASIC reset message\n"); + ft1000_write_reg(dev, FT1000_REG_DOORBELL, + FT1000_DSP_ASIC_RESET); + udelay(200); + return SUCCESS; + } + + if (doorbell & FT1000_DB_DPRAM_RX) { + DEBUG(1, + "FT1000:ft1000_parse_dpram_msg: Got a slow queue message\n"); + nxtph = FT1000_DPRAM_RX_BASE + 2; + if (info->AsicID == ELECTRABUZZ_ID) { + total_len = + ft1000_read_dpram(dev, FT1000_DPRAM_RX_BASE); + } else { + total_len = + ntohs(ft1000_read_dpram_mag_16 + (dev, FT1000_MAG_TOTAL_LEN, + FT1000_MAG_TOTAL_LEN_INDX)); + } + DEBUG(1, "FT1000:ft1000_parse_dpram_msg:total length = %d\n", + total_len); + if ((total_len < MAX_CMD_SQSIZE) && (total_len > sizeof(struct pseudo_hdr))) { + total_len += nxtph; + cnt = 0; + // ft1000_read_reg will return a value that needs to be byteswap + // in order to get DSP_QID_OFFSET. + if (info->AsicID == ELECTRABUZZ_ID) { + portid = + (ft1000_read_dpram + (dev, + DSP_QID_OFFSET + FT1000_DPRAM_RX_BASE + + 2) >> 8) & 0xff; + } else { + portid = + (ft1000_read_dpram_mag_16 + (dev, FT1000_MAG_PORT_ID, + FT1000_MAG_PORT_ID_INDX) & 0xff); + } + DEBUG(1, "DSP_QID = 0x%x\n", portid); + + if (portid == DRIVERID) { + // We are assumming one driver message from the DSP at a time. + ft1000_proc_drvmsg(dev); + } + } + ft1000_write_reg(dev, FT1000_REG_DOORBELL, FT1000_DB_DPRAM_RX); + } + + if (doorbell & FT1000_DB_COND_RESET) { + // Reset ASIC and DSP + if (info->AsicID == ELECTRABUZZ_ID) { + info->DSP_TIME[0] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER0); + info->DSP_TIME[1] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER1); + info->DSP_TIME[2] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER2); + info->DSP_TIME[3] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER3); + } else { + info->DSP_TIME[0] = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_DSP_TIMER0, + FT1000_MAG_DSP_TIMER0_INDX); + info->DSP_TIME[1] = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_DSP_TIMER1, + FT1000_MAG_DSP_TIMER1_INDX); + info->DSP_TIME[2] = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_DSP_TIMER2, + FT1000_MAG_DSP_TIMER2_INDX); + info->DSP_TIME[3] = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_DSP_TIMER3, + FT1000_MAG_DSP_TIMER3_INDX); + } + info->DrvErrNum = DSP_CONDRESET_INFO; + DEBUG(1, "ft1000_hw:DSP conditional reset requested\n"); + ft1000_reset_card(dev); + ft1000_write_reg(dev, FT1000_REG_DOORBELL, + FT1000_DB_COND_RESET); + } + // let's clear any unexpected doorbells from DSP + doorbell = + doorbell & ~(FT1000_DB_DPRAM_RX | FT1000_ASIC_RESET_REQ | + FT1000_DB_COND_RESET | 0xff00); + if (doorbell) { + DEBUG(1, "Clearing unexpected doorbell = 0x%x\n", doorbell); + ft1000_write_reg(dev, FT1000_REG_DOORBELL, doorbell); + } + + return SUCCESS; + +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_flush_fifo +// Description: This function will flush one packet from the downlink +// FIFO. +// Input: +// dev - device structure +// drv_err - driver error causing the flush fifo +// Output: +// None. +// +//--------------------------------------------------------------------------- +static void ft1000_flush_fifo(struct net_device *dev, u16 DrvErrNum) +{ + struct ft1000_info *info = netdev_priv(dev); + u16 i; + u32 templong; + u16 tempword; + + DEBUG(1, "ft1000:ft1000_hw:ft1000_flush_fifo called\n"); + if (info->PktIntfErr > MAX_PH_ERR) { + if (info->AsicID == ELECTRABUZZ_ID) { + info->DSP_TIME[0] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER0); + info->DSP_TIME[1] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER1); + info->DSP_TIME[2] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER2); + info->DSP_TIME[3] = + ft1000_read_dpram(dev, FT1000_DSP_TIMER3); + } else { + info->DSP_TIME[0] = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_DSP_TIMER0, + FT1000_MAG_DSP_TIMER0_INDX); + info->DSP_TIME[1] = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_DSP_TIMER1, + FT1000_MAG_DSP_TIMER1_INDX); + info->DSP_TIME[2] = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_DSP_TIMER2, + FT1000_MAG_DSP_TIMER2_INDX); + info->DSP_TIME[3] = + ft1000_read_dpram_mag_16(dev, FT1000_MAG_DSP_TIMER3, + FT1000_MAG_DSP_TIMER3_INDX); + } + info->DrvErrNum = DrvErrNum; + ft1000_reset_card(dev); + return; + } else { + // Flush corrupted pkt from FIFO + i = 0; + do { + if (info->AsicID == ELECTRABUZZ_ID) { + tempword = + ft1000_read_reg(dev, FT1000_REG_DFIFO); + tempword = + ft1000_read_reg(dev, FT1000_REG_DFIFO_STAT); + } else { + templong = + inl(dev->base_addr + FT1000_REG_MAG_DFR); + tempword = + inw(dev->base_addr + FT1000_REG_MAG_DFSR); + } + i++; + // This should never happen unless the ASIC is broken. + // We must reset to recover. + if ((i > 2048) || (tempword == 0)) { + if (info->AsicID == ELECTRABUZZ_ID) { + info->DSP_TIME[0] = + ft1000_read_dpram(dev, + FT1000_DSP_TIMER0); + info->DSP_TIME[1] = + ft1000_read_dpram(dev, + FT1000_DSP_TIMER1); + info->DSP_TIME[2] = + ft1000_read_dpram(dev, + FT1000_DSP_TIMER2); + info->DSP_TIME[3] = + ft1000_read_dpram(dev, + FT1000_DSP_TIMER3); + } else { + info->DSP_TIME[0] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER0, + FT1000_MAG_DSP_TIMER0_INDX); + info->DSP_TIME[1] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER1, + FT1000_MAG_DSP_TIMER1_INDX); + info->DSP_TIME[2] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER2, + FT1000_MAG_DSP_TIMER2_INDX); + info->DSP_TIME[3] = + ft1000_read_dpram_mag_16(dev, + FT1000_MAG_DSP_TIMER3, + FT1000_MAG_DSP_TIMER3_INDX); + } + if (tempword == 0) { + // Let's check if ASIC reads are still ok by reading the Mask register + // which is never zero at this point of the code. + tempword = + inw(dev->base_addr + + FT1000_REG_SUP_IMASK); + if (tempword == 0) { + // This indicates that we can not communicate with the ASIC + info->DrvErrNum = + FIFO_FLUSH_BADCNT; + } else { + // Let's assume that we really flush the FIFO + info->PktIntfErr++; + return; + } + } else { + info->DrvErrNum = FIFO_FLUSH_MAXLIMIT; + } + return; + } + tempword = inw(dev->base_addr + FT1000_REG_SUP_STAT); + } while ((tempword & 0x03) != 0x03); + if (info->AsicID == ELECTRABUZZ_ID) { + i++; + DEBUG(0, "Flushing FIFO complete = %x\n", tempword); + // Flush last word in FIFO. + tempword = ft1000_read_reg(dev, FT1000_REG_DFIFO); + // Update FIFO counter for DSP + i = i * 2; + DEBUG(0, "Flush Data byte count to dsp = %d\n", i); + info->fifo_cnt += i; + ft1000_write_dpram(dev, FT1000_FIFO_LEN, + info->fifo_cnt); + } else { + DEBUG(0, "Flushing FIFO complete = %x\n", tempword); + // Flush last word in FIFO + templong = inl(dev->base_addr + FT1000_REG_MAG_DFR); + tempword = inw(dev->base_addr + FT1000_REG_SUP_STAT); + DEBUG(0, "FT1000_REG_SUP_STAT = 0x%x\n", tempword); + tempword = inw(dev->base_addr + FT1000_REG_MAG_DFSR); + DEBUG(0, "FT1000_REG_MAG_DFSR = 0x%x\n", tempword); + } + if (DrvErrNum) { + info->PktIntfErr++; + } + } +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_copy_up_pkt +// Description: This function will pull Flarion packets out of the Downlink +// FIFO and convert it to an ethernet packet. The ethernet packet will +// then be deliver to the TCP/IP stack. +// Input: +// dev - device structure +// Output: +// status - FAILURE +// SUCCESS +// +//--------------------------------------------------------------------------- +static int ft1000_copy_up_pkt(struct net_device *dev) +{ + u16 tempword; + struct ft1000_info *info = netdev_priv(dev); + u16 len; + struct sk_buff *skb; + u16 i; + u8 *pbuffer = NULL; + u8 *ptemp = NULL; + u16 chksum; + u32 *ptemplong; + u32 templong; + + DEBUG(1, "ft1000_copy_up_pkt\n"); + // Read length + if (info->AsicID == ELECTRABUZZ_ID) { + tempword = ft1000_read_reg(dev, FT1000_REG_DFIFO); + len = tempword; + } else { + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRL); + len = ntohs(tempword); + } + chksum = tempword; + DEBUG(1, "Number of Bytes in FIFO = %d\n", len); + + if (len > ENET_MAX_SIZE) { + DEBUG(0, "size of ethernet packet invalid\n"); + if (info->AsicID == MAGNEMITE_ID) { + // Read High word to complete 32 bit access + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRH); + } + ft1000_flush_fifo(dev, DSP_PKTLEN_INFO); + info->stats.rx_errors++; + return FAILURE; + } + + skb = dev_alloc_skb(len + 12 + 2); + + if (skb == NULL) { + DEBUG(0, "No Network buffers available\n"); + // Read High word to complete 32 bit access + if (info->AsicID == MAGNEMITE_ID) { + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRH); + } + ft1000_flush_fifo(dev, 0); + info->stats.rx_errors++; + return FAILURE; + } + pbuffer = (u8 *) skb_put(skb, len + 12); + + // Pseudo header + if (info->AsicID == ELECTRABUZZ_ID) { + for (i = 1; i < 7; i++) { + tempword = ft1000_read_reg(dev, FT1000_REG_DFIFO); + chksum ^= tempword; + } + // read checksum value + tempword = ft1000_read_reg(dev, FT1000_REG_DFIFO); + } else { + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRH); + DEBUG(1, "Pseudo = 0x%x\n", tempword); + chksum ^= tempword; + + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRL); + DEBUG(1, "Pseudo = 0x%x\n", tempword); + chksum ^= tempword; + + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRH); + DEBUG(1, "Pseudo = 0x%x\n", tempword); + chksum ^= tempword; + + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRL); + DEBUG(1, "Pseudo = 0x%x\n", tempword); + chksum ^= tempword; + + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRH); + DEBUG(1, "Pseudo = 0x%x\n", tempword); + chksum ^= tempword; + + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRL); + DEBUG(1, "Pseudo = 0x%x\n", tempword); + chksum ^= tempword; + + // read checksum value + tempword = ft1000_read_reg(dev, FT1000_REG_MAG_DFRH); + DEBUG(1, "Pseudo = 0x%x\n", tempword); + } + + if (chksum != tempword) { + DEBUG(0, "Packet checksum mismatch 0x%x 0x%x\n", chksum, + tempword); + ft1000_flush_fifo(dev, DSP_PKTPHCKSUM_INFO); + info->stats.rx_errors++; + kfree_skb(skb); + return FAILURE; + } + //subtract the number of bytes read already + ptemp = pbuffer; + + // fake MAC address + *pbuffer++ = dev->dev_addr[0]; + *pbuffer++ = dev->dev_addr[1]; + *pbuffer++ = dev->dev_addr[2]; + *pbuffer++ = dev->dev_addr[3]; + *pbuffer++ = dev->dev_addr[4]; + *pbuffer++ = dev->dev_addr[5]; + *pbuffer++ = 0x00; + *pbuffer++ = 0x07; + *pbuffer++ = 0x35; + *pbuffer++ = 0xff; + *pbuffer++ = 0xff; + *pbuffer++ = 0xfe; + + if (info->AsicID == ELECTRABUZZ_ID) { + for (i = 0; i < len / 2; i++) { + tempword = ft1000_read_reg(dev, FT1000_REG_DFIFO); + *pbuffer++ = (u8) (tempword >> 8); + *pbuffer++ = (u8) tempword; + if (ft1000_chkcard(dev) == false) { + kfree_skb(skb); + return FAILURE; + } + } + + // Need to read one more word if odd byte + if (len & 0x0001) { + tempword = ft1000_read_reg(dev, FT1000_REG_DFIFO); + *pbuffer++ = (u8) (tempword >> 8); + } + } else { + ptemplong = (u32 *) pbuffer; + for (i = 0; i < len / 4; i++) { + templong = inl(dev->base_addr + FT1000_REG_MAG_DFR); + DEBUG(1, "Data = 0x%8x\n", templong); + *ptemplong++ = templong; + } + + // Need to read one more word if odd align. + if (len & 0x0003) { + templong = inl(dev->base_addr + FT1000_REG_MAG_DFR); + DEBUG(1, "Data = 0x%8x\n", templong); + *ptemplong++ = templong; + } + + } + + DEBUG(1, "Data passed to Protocol layer:\n"); + for (i = 0; i < len + 12; i++) { + DEBUG(1, "Protocol Data: 0x%x\n ", *ptemp++); + } + + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); + + info->stats.rx_packets++; + // Add on 12 bytes for MAC address which was removed + info->stats.rx_bytes += (len + 12); + + if (info->AsicID == ELECTRABUZZ_ID) { + // track how many bytes have been read from FIFO - round up to 16 bit word + tempword = len + 16; + if (tempword & 0x01) + tempword++; + info->fifo_cnt += tempword; + ft1000_write_reg(dev, FT1000_REG_DPRAM_ADDR, FT1000_FIFO_LEN); + ft1000_write_reg(dev, FT1000_REG_DPRAM_DATA, info->fifo_cnt); + } + + return SUCCESS; +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_copy_down_pkt +// Description: This function will take an ethernet packet and convert it to +// a Flarion packet prior to sending it to the ASIC Downlink +// FIFO. +// Input: +// dev - device structure +// packet - address of ethernet packet +// len - length of IP packet +// Output: +// status - FAILURE +// SUCCESS +// +//--------------------------------------------------------------------------- +static int ft1000_copy_down_pkt(struct net_device *dev, u16 * packet, u16 len) +{ + struct ft1000_info *info = netdev_priv(dev); + union { + struct pseudo_hdr blk; + u16 buff[sizeof(struct pseudo_hdr) >> 1]; + u8 buffc[sizeof(struct pseudo_hdr)]; + } pseudo; + int i; + u32 *plong; + + DEBUG(1, "ft1000_hw: copy_down_pkt()\n"); + + // Check if there is room on the FIFO + if (len > ft1000_read_fifo_len(dev)) { + udelay(10); + if (len > ft1000_read_fifo_len(dev)) { + udelay(20); + } + if (len > ft1000_read_fifo_len(dev)) { + udelay(20); + } + if (len > ft1000_read_fifo_len(dev)) { + udelay(20); + } + if (len > ft1000_read_fifo_len(dev)) { + udelay(20); + } + if (len > ft1000_read_fifo_len(dev)) { + udelay(20); + } + if (len > ft1000_read_fifo_len(dev)) { + DEBUG(1, + "ft1000_hw:ft1000_copy_down_pkt:Transmit FIFO is fulli - pkt drop\n"); + info->stats.tx_errors++; + return SUCCESS; + } + } + // Create pseudo header and send pseudo/ip to hardware + if (info->AsicID == ELECTRABUZZ_ID) { + pseudo.blk.length = len; + } else { + pseudo.blk.length = ntohs(len); + } + pseudo.blk.source = DSPID; // Need to swap to get in correct order + pseudo.blk.destination = HOSTID; + pseudo.blk.portdest = NETWORKID; // Need to swap to get in correct order + pseudo.blk.portsrc = DSPAIRID; + pseudo.blk.sh_str_id = 0; + pseudo.blk.control = 0; + pseudo.blk.rsvd1 = 0; + pseudo.blk.seq_num = 0; + pseudo.blk.rsvd2 = info->packetseqnum++; + pseudo.blk.qos_class = 0; + /* Calculate pseudo header checksum */ + pseudo.blk.checksum = pseudo.buff[0]; + for (i = 1; i < 7; i++) { + pseudo.blk.checksum ^= pseudo.buff[i]; + } + + // Production Mode + if (info->AsicID == ELECTRABUZZ_ID) { + // copy first word to UFIFO_BEG reg + ft1000_write_reg(dev, FT1000_REG_UFIFO_BEG, pseudo.buff[0]); + DEBUG(1, "ft1000_hw:ft1000_copy_down_pkt:data 0 BEG = 0x%04x\n", + pseudo.buff[0]); + + // copy subsequent words to UFIFO_MID reg + ft1000_write_reg(dev, FT1000_REG_UFIFO_MID, pseudo.buff[1]); + DEBUG(1, "ft1000_hw:ft1000_copy_down_pkt:data 1 MID = 0x%04x\n", + pseudo.buff[1]); + ft1000_write_reg(dev, FT1000_REG_UFIFO_MID, pseudo.buff[2]); + DEBUG(1, "ft1000_hw:ft1000_copy_down_pkt:data 2 MID = 0x%04x\n", + pseudo.buff[2]); + ft1000_write_reg(dev, FT1000_REG_UFIFO_MID, pseudo.buff[3]); + DEBUG(1, "ft1000_hw:ft1000_copy_down_pkt:data 3 MID = 0x%04x\n", + pseudo.buff[3]); + ft1000_write_reg(dev, FT1000_REG_UFIFO_MID, pseudo.buff[4]); + DEBUG(1, "ft1000_hw:ft1000_copy_down_pkt:data 4 MID = 0x%04x\n", + pseudo.buff[4]); + ft1000_write_reg(dev, FT1000_REG_UFIFO_MID, pseudo.buff[5]); + DEBUG(1, "ft1000_hw:ft1000_copy_down_pkt:data 5 MID = 0x%04x\n", + pseudo.buff[5]); + ft1000_write_reg(dev, FT1000_REG_UFIFO_MID, pseudo.buff[6]); + DEBUG(1, "ft1000_hw:ft1000_copy_down_pkt:data 6 MID = 0x%04x\n", + pseudo.buff[6]); + ft1000_write_reg(dev, FT1000_REG_UFIFO_MID, pseudo.buff[7]); + DEBUG(1, "ft1000_hw:ft1000_copy_down_pkt:data 7 MID = 0x%04x\n", + pseudo.buff[7]); + + // Write PPP type + IP Packet into Downlink FIFO + for (i = 0; i < (len >> 1) - 1; i++) { + ft1000_write_reg(dev, FT1000_REG_UFIFO_MID, + htons(*packet)); + DEBUG(1, + "ft1000_hw:ft1000_copy_down_pkt:data %d MID = 0x%04x\n", + i + 8, htons(*packet)); + packet++; + } + + // Check for odd byte + if (len & 0x0001) { + ft1000_write_reg(dev, FT1000_REG_UFIFO_MID, + htons(*packet)); + DEBUG(1, + "ft1000_hw:ft1000_copy_down_pkt:data MID = 0x%04x\n", + htons(*packet)); + packet++; + ft1000_write_reg(dev, FT1000_REG_UFIFO_END, + htons(*packet)); + DEBUG(1, + "ft1000_hw:ft1000_copy_down_pkt:data %d MID = 0x%04x\n", + i + 8, htons(*packet)); + } else { + ft1000_write_reg(dev, FT1000_REG_UFIFO_END, + htons(*packet)); + DEBUG(1, + "ft1000_hw:ft1000_copy_down_pkt:data %d MID = 0x%04x\n", + i + 8, htons(*packet)); + } + } else { + outl(*(u32 *) & pseudo.buff[0], + dev->base_addr + FT1000_REG_MAG_UFDR); + DEBUG(1, "ft1000_copy_down_pkt: Pseudo = 0x%8x\n", + *(u32 *) & pseudo.buff[0]); + outl(*(u32 *) & pseudo.buff[2], + dev->base_addr + FT1000_REG_MAG_UFDR); + DEBUG(1, "ft1000_copy_down_pkt: Pseudo = 0x%8x\n", + *(u32 *) & pseudo.buff[2]); + outl(*(u32 *) & pseudo.buff[4], + dev->base_addr + FT1000_REG_MAG_UFDR); + DEBUG(1, "ft1000_copy_down_pkt: Pseudo = 0x%8x\n", + *(u32 *) & pseudo.buff[4]); + outl(*(u32 *) & pseudo.buff[6], + dev->base_addr + FT1000_REG_MAG_UFDR); + DEBUG(1, "ft1000_copy_down_pkt: Pseudo = 0x%8x\n", + *(u32 *) & pseudo.buff[6]); + + plong = (u32 *) packet; + // Write PPP type + IP Packet into Downlink FIFO + for (i = 0; i < (len >> 2); i++) { + outl(*plong++, dev->base_addr + FT1000_REG_MAG_UFDR); + } + + // Check for odd alignment + if (len & 0x0003) { + DEBUG(1, + "ft1000_hw:ft1000_copy_down_pkt:data = 0x%8x\n", + *plong); + outl(*plong++, dev->base_addr + FT1000_REG_MAG_UFDR); + } + outl(1, dev->base_addr + FT1000_REG_MAG_UFER); + } + + info->stats.tx_packets++; + // Add 14 bytes for MAC address plus ethernet type + info->stats.tx_bytes += (len + 14); + return SUCCESS; +} + +static struct net_device_stats *ft1000_stats(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + return (&info->stats); +} + +static int ft1000_open(struct net_device *dev) +{ + + DEBUG(0, "ft1000_hw: ft1000_open is called\n"); + + ft1000_reset_card(dev); + DEBUG(0, "ft1000_hw: ft1000_open is ended\n"); + + /* schedule ft1000_hbchk to perform periodic heartbeat checks on DSP and ASIC */ + init_timer(&poll_timer); + poll_timer.expires = jiffies + (2 * HZ); + poll_timer.data = (u_long) dev; + add_timer(&poll_timer); + + DEBUG(0, "ft1000_hw: ft1000_open is ended2\n"); + return 0; +} + +static int ft1000_close(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + + DEBUG(0, "ft1000_hw: ft1000_close()\n"); + + info->CardReady = 0; + del_timer(&poll_timer); + + if (ft1000_card_present == 1) { + DEBUG(0, "Media is down\n"); + netif_stop_queue(dev); + + ft1000_disable_interrupts(dev); + ft1000_write_reg(dev, FT1000_REG_RESET, DSP_RESET_BIT); + + //reset ASIC + ft1000_reset_asic(dev); + } + return 0; +} + +static int ft1000_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + u8 *pdata; + + DEBUG(1, "ft1000_hw: ft1000_start_xmit()\n"); + if (skb == NULL) { + DEBUG(1, "ft1000_hw: ft1000_start_xmit:skb == NULL!!!\n"); + return 0; + } + + DEBUG(1, "ft1000_hw: ft1000_start_xmit:length of packet = %d\n", + skb->len); + + pdata = (u8 *) skb->data; + + if (info->mediastate == 0) { + /* Drop packet is mediastate is down */ + DEBUG(1, "ft1000_hw:ft1000_copy_down_pkt:mediastate is down\n"); + return SUCCESS; + } + + if ((skb->len < ENET_HEADER_SIZE) || (skb->len > ENET_MAX_SIZE)) { + /* Drop packet which has invalid size */ + DEBUG(1, + "ft1000_hw:ft1000_copy_down_pkt:invalid ethernet length\n"); + return SUCCESS; + } + ft1000_copy_down_pkt(dev, (u16 *) (pdata + ENET_HEADER_SIZE - 2), + skb->len - ENET_HEADER_SIZE + 2); + + dev_kfree_skb(skb); + + return 0; +} + +static irqreturn_t ft1000_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct ft1000_info *info = netdev_priv(dev); + u16 tempword; + u16 inttype; + int cnt; + + DEBUG(1, "ft1000_hw: ft1000_interrupt()\n"); + + if (info->CardReady == 0) { + ft1000_disable_interrupts(dev); + return IRQ_HANDLED; + } + + if (ft1000_chkcard(dev) == false) { + ft1000_disable_interrupts(dev); + return IRQ_HANDLED; + } + + ft1000_disable_interrupts(dev); + + // Read interrupt type + inttype = ft1000_read_reg(dev, FT1000_REG_SUP_ISR); + + // Make sure we process all interrupt before leaving the ISR due to the edge trigger interrupt type + while (inttype) { + if (inttype & ISR_DOORBELL_PEND) { + ft1000_parse_dpram_msg(dev); + } + + if (inttype & ISR_RCV) { + DEBUG(1, "Data in FIFO\n"); + + cnt = 0; + do { + // Check if we have packets in the Downlink FIFO + if (info->AsicID == ELECTRABUZZ_ID) { + tempword = + ft1000_read_reg(dev, FT1000_REG_DFIFO_STAT); + } else { + tempword = + ft1000_read_reg(dev, FT1000_REG_MAG_DFSR); + } + if (tempword & 0x1f) { + ft1000_copy_up_pkt(dev); + } else { + break; + } + cnt++; + } while (cnt < MAX_RCV_LOOP); + + } + // clear interrupts + tempword = ft1000_read_reg(dev, FT1000_REG_SUP_ISR); + DEBUG(1, "ft1000_hw: interrupt status register = 0x%x\n", tempword); + ft1000_write_reg(dev, FT1000_REG_SUP_ISR, tempword); + + // Read interrupt type + inttype = ft1000_read_reg (dev, FT1000_REG_SUP_ISR); + DEBUG(1,"ft1000_hw: interrupt status register after clear = 0x%x\n",inttype); + } + ft1000_enable_interrupts(dev); + return IRQ_HANDLED; +} + +void stop_ft1000_card(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + struct prov_record *ptr; +// int cnt; + + DEBUG(0, "ft1000_hw: stop_ft1000_card()\n"); + + info->CardReady = 0; + ft1000_card_present = 0; + netif_stop_queue(dev); + ft1000_disable_interrupts(dev); + + // Make sure we free any memory reserve for provisioning + while (list_empty(&info->prov_list) == 0) { + ptr = list_entry(info->prov_list.next, struct prov_record, list); + list_del(&ptr->list); + kfree(ptr->pprov_data); + kfree(ptr); + } + + if (info->registered) { + unregister_netdev(dev); + info->registered = 0; + } + + free_irq(dev->irq, dev); + release_region(dev->base_addr,256); + release_firmware(fw_entry); + flarion_ft1000_cnt--; + ft1000CleanupProc(dev); + +} + +static void ft1000_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + struct ft1000_info *ft_info; + ft_info = netdev_priv(dev); + + snprintf(info->driver, 32, "ft1000"); + snprintf(info->bus_info, ETHTOOL_BUSINFO_LEN, "PCMCIA 0x%lx", + dev->base_addr); + snprintf(info->fw_version, 32, "%d.%d.%d.%d", ft_info->DspVer[0], + ft_info->DspVer[1], ft_info->DspVer[2], ft_info->DspVer[3]); +} + +static u32 ft1000_get_link(struct net_device *dev) +{ + struct ft1000_info *info; + info = netdev_priv(dev); + return info->mediastate; +} + +static const struct ethtool_ops ops = { + .get_drvinfo = ft1000_get_drvinfo, + .get_link = ft1000_get_link +}; + +struct net_device *init_ft1000_card(struct pcmcia_device *link, + void *ft1000_reset) +{ + struct ft1000_info *info; + struct net_device *dev; + + static const struct net_device_ops ft1000ops = // Slavius 21.10.2009 due to kernel changes + { + .ndo_open = &ft1000_open, + .ndo_stop = &ft1000_close, + .ndo_start_xmit = &ft1000_start_xmit, + .ndo_get_stats = &ft1000_stats, + }; + + DEBUG(1, "ft1000_hw: init_ft1000_card()\n"); + DEBUG(1, "ft1000_hw: irq = %d\n", link->irq); + DEBUG(1, "ft1000_hw: port = 0x%04x\n", link->resource[0]->start); + + flarion_ft1000_cnt++; + + if (flarion_ft1000_cnt > 1) { + flarion_ft1000_cnt--; + + printk(KERN_INFO + "ft1000: This driver can not support more than one instance\n"); + return NULL; + } + + dev = alloc_etherdev(sizeof(struct ft1000_info)); + if (!dev) { + printk(KERN_ERR "ft1000: failed to allocate etherdev\n"); + return NULL; + } + + SET_NETDEV_DEV(dev, &link->dev); + info = netdev_priv(dev); + + memset(info, 0, sizeof(struct ft1000_info)); + + DEBUG(1, "address of dev = 0x%8x\n", (u32) dev); + DEBUG(1, "address of dev info = 0x%8x\n", (u32) info); + DEBUG(0, "device name = %s\n", dev->name); + + memset(&info->stats, 0, sizeof(struct net_device_stats)); + + spin_lock_init(&info->dpram_lock); + info->DrvErrNum = 0; + info->registered = 1; + info->link = link; + info->ft1000_reset = ft1000_reset; + info->mediastate = 0; + info->fifo_cnt = 0; + info->CardReady = 0; + info->DSP_TIME[0] = 0; + info->DSP_TIME[1] = 0; + info->DSP_TIME[2] = 0; + info->DSP_TIME[3] = 0; + flarion_ft1000_cnt = 0; + + INIT_LIST_HEAD(&info->prov_list); + + info->squeseqnum = 0; + +// dev->hard_start_xmit = &ft1000_start_xmit; +// dev->get_stats = &ft1000_stats; +// dev->open = &ft1000_open; +// dev->stop = &ft1000_close; + + dev->netdev_ops = &ft1000ops; // Slavius 21.10.2009 due to kernel changes + + DEBUG(0, "device name = %s\n", dev->name); + + dev->irq = link->irq; + dev->base_addr = link->resource[0]->start; + if (pcmcia_get_mac_from_cis(link, dev)) { + printk(KERN_ERR "ft1000: Could not read mac address\n"); + goto err_dev; + } + + if (request_irq(dev->irq, ft1000_interrupt, IRQF_SHARED, dev->name, dev)) { + printk(KERN_ERR "ft1000: Could not request_irq\n"); + goto err_dev; + } + + if (request_region(dev->base_addr, 256, dev->name) == NULL) { + printk(KERN_ERR "ft1000: Could not request_region\n"); + goto err_irq; + } + + if (register_netdev(dev) != 0) { + DEBUG(0, "ft1000: Could not register netdev"); + goto err_reg; + } + + info->AsicID = ft1000_read_reg(dev, FT1000_REG_ASIC_ID); + if (info->AsicID == ELECTRABUZZ_ID) { + DEBUG(0, "ft1000_hw: ELECTRABUZZ ASIC\n"); + if (request_firmware(&fw_entry, "ft1000.img", &link->dev) != 0) { + printk(KERN_INFO "ft1000: Could not open ft1000.img\n"); + goto err_unreg; + } + } else { + DEBUG(0, "ft1000_hw: MAGNEMITE ASIC\n"); + if (request_firmware(&fw_entry, "ft2000.img", &link->dev) != 0) { + printk(KERN_INFO "ft1000: Could not open ft2000.img\n"); + goto err_unreg; + } + } + + ft1000_enable_interrupts(dev); + + ft1000InitProc(dev); + ft1000_card_present = 1; + SET_ETHTOOL_OPS(dev, &ops); + printk(KERN_INFO "ft1000: %s: addr 0x%04lx irq %d, MAC addr %pM\n", + dev->name, dev->base_addr, dev->irq, dev->dev_addr); + return dev; + +err_unreg: + unregister_netdev(dev); +err_reg: + release_region(dev->base_addr, 256); +err_irq: + free_irq(dev->irq, dev); +err_dev: + free_netdev(dev); + return NULL; +} diff --git a/drivers/staging/ft1000/ft1000-pcmcia/ft1000_proc.c b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_proc.c new file mode 100644 index 00000000..71aaad31 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-pcmcia/ft1000_proc.c @@ -0,0 +1,215 @@ +/*--------------------------------------------------------------------------- + FT1000 driver for Flarion Flash OFDM NIC Device + + Copyright (C) 2006 Patrik Ostrihon, All rights reserved. + Copyright (C) 2006 ProWeb Consulting, a.s, All rights reserved. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) any + later version. This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. You should have received a copy of the GNU General Public + License along with this program; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - + Suite 330, Boston, MA 02111-1307, USA. +-----------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ft1000.h" + +#define FT1000_PROC "ft1000" +#define MAX_FILE_LEN 255 + +#define PUTM_TO_PAGE(len, page, args...) \ + len += snprintf(page+len, PAGE_SIZE - len, args) + +#define PUTX_TO_PAGE(len, page, message, size, var) \ + len += snprintf(page+len, PAGE_SIZE - len, message); \ + for(i = 0; i < (size - 1); i++) \ + { \ + len += snprintf(page+len, PAGE_SIZE - len, "%02x:", var[i]); \ + } \ + len += snprintf(page+len, PAGE_SIZE - len, "%02x\n", var[i]) + +#define PUTD_TO_PAGE(len, page, message, size, var) \ + len += snprintf(page+len, PAGE_SIZE - len, message); \ + for(i = 0; i < (size - 1); i++) \ + { \ + len += snprintf(page+len, PAGE_SIZE - len, "%d.", var[i]); \ + } \ + len += snprintf(page+len, PAGE_SIZE - len, "%d\n", var[i]) + +static int ft1000ReadProc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct net_device *dev; + int len; + int i; + struct ft1000_info *info; + char *status[] = + { "Idle (Disconnect)", "Searching", "Active (Connected)", + "Waiting for L2", "Sleep", "No Coverage", "", "" + }; + char *signal[] = { "", "*", "**", "***", "****" }; + int strength; + int quality; + struct timeval tv; + time_t delta; + + dev = (struct net_device *)data; + info = netdev_priv(dev); + + if (off > 0) { + *eof = 1; + return 0; + } + + /* Wrap-around */ + + if (info->AsicID == ELECTRABUZZ_ID) { + if (info->ProgConStat != 0xFF) { + info->LedStat = + ft1000_read_dpram(dev, FT1000_DSP_LED); + info->ConStat = + ft1000_read_dpram(dev, + FT1000_DSP_CON_STATE); + } else { + info->ConStat = 0xf; + } + } else { + if (info->ProgConStat != 0xFF) { + info->LedStat = + ntohs(ft1000_read_dpram_mag_16 + (dev, FT1000_MAG_DSP_LED, + FT1000_MAG_DSP_LED_INDX)); + info->ConStat = + ntohs(ft1000_read_dpram_mag_16 + (dev, FT1000_MAG_DSP_CON_STATE, + FT1000_MAG_DSP_CON_STATE_INDX)); + } else { + info->ConStat = 0xf; + } + } + + i = (info->LedStat) & 0xf; + switch (i) { + case 0x1: + strength = 1; + break; + case 0x3: + strength = 2; + break; + case 0x7: + strength = 3; + break; + case 0xf: + strength = 4; + break; + default: + strength = 0; + } + + i = (info->LedStat >> 8) & 0xf; + switch (i) { + case 0x1: + quality = 1; + break; + case 0x3: + quality = 2; + break; + case 0x7: + quality = 3; + break; + case 0xf: + quality = 4; + break; + default: + quality = 0; + } + + do_gettimeofday(&tv); + delta = (tv.tv_sec - info->ConTm); + len = 0; + PUTM_TO_PAGE(len, page, "Connection Time: %02ld:%02ld:%02ld\n", + ((delta / 3600) % 24), ((delta / 60) % 60), (delta % 60)); + PUTM_TO_PAGE(len, page, "Connection Time[s]: %ld\n", delta); + PUTM_TO_PAGE(len, page, "Asic ID: %s\n", + (info->AsicID) == + ELECTRABUZZ_ID ? "ELECTRABUZZ ASIC" : "MAGNEMITE ASIC"); + PUTX_TO_PAGE(len, page, "SKU: ", SKUSZ, info->Sku); + PUTX_TO_PAGE(len, page, "EUI64: ", EUISZ, info->eui64); + PUTD_TO_PAGE(len, page, "DSP version number: ", DSPVERSZ, info->DspVer); + PUTX_TO_PAGE(len, page, "Hardware Serial Number: ", HWSERNUMSZ, + info->HwSerNum); + PUTX_TO_PAGE(len, page, "Caliberation Version: ", CALVERSZ, + info->RfCalVer); + PUTD_TO_PAGE(len, page, "Caliberation Date: ", CALDATESZ, + info->RfCalDate); + PUTM_TO_PAGE(len, page, "Media State: %s\n", + (info->mediastate) ? "link" : "no link"); + PUTM_TO_PAGE(len, page, "Connection Status: %s\n", + status[((info->ConStat) & 0x7)]); + PUTM_TO_PAGE(len, page, "RX packets: %ld\n", info->stats.rx_packets); + PUTM_TO_PAGE(len, page, "TX packets: %ld\n", info->stats.tx_packets); + PUTM_TO_PAGE(len, page, "RX bytes: %ld\n", info->stats.rx_bytes); + PUTM_TO_PAGE(len, page, "TX bytes: %ld\n", info->stats.tx_bytes); + PUTM_TO_PAGE(len, page, "Signal Strength: %s\n", signal[strength]); + PUTM_TO_PAGE(len, page, "Signal Quality: %s\n", signal[quality]); + return len; +} + +static int ft1000NotifyProc(struct notifier_block *this, unsigned long event, + void *ptr) +{ + struct net_device *dev = ptr; + struct ft1000_info *info; + + info = netdev_priv(dev); + + switch (event) { + case NETDEV_CHANGENAME: + remove_proc_entry(info->netdevname, info->proc_ft1000); + create_proc_read_entry(dev->name, 0644, info->proc_ft1000, + ft1000ReadProc, dev); + snprintf(info->netdevname, IFNAMSIZ, "%s", dev->name); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block ft1000_netdev_notifier = { + .notifier_call = ft1000NotifyProc +}; + +void ft1000InitProc(struct net_device *dev) +{ + struct ft1000_info *info; + + info = netdev_priv(dev); + + info->proc_ft1000 = proc_mkdir(FT1000_PROC, init_net.proc_net); + create_proc_read_entry(dev->name, 0644, info->proc_ft1000, + ft1000ReadProc, dev); + snprintf(info->netdevname, IFNAMSIZ, "%s", dev->name); + register_netdevice_notifier(&ft1000_netdev_notifier); +} + +void ft1000CleanupProc(struct net_device *dev) +{ + struct ft1000_info *info; + + info = netdev_priv(dev); + + remove_proc_entry(dev->name, info->proc_ft1000); + remove_proc_entry(FT1000_PROC, init_net.proc_net); + unregister_netdevice_notifier(&ft1000_netdev_notifier); +} diff --git a/drivers/staging/ft1000/ft1000-usb/Makefile b/drivers/staging/ft1000/ft1000-usb/Makefile new file mode 100644 index 00000000..f0f52401 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-usb/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_FT1000_USB) += ft1000.o + +ft1000-y := ft1000_debug.o ft1000_download.o ft1000_hw.o ft1000_proc.o ft1000_usb.o diff --git a/drivers/staging/ft1000/ft1000-usb/ft1000_debug.c b/drivers/staging/ft1000/ft1000-usb/ft1000_debug.c new file mode 100644 index 00000000..19db23fe --- /dev/null +++ b/drivers/staging/ft1000/ft1000-usb/ft1000_debug.c @@ -0,0 +1,782 @@ +//--------------------------------------------------------------------------- +// FT1000 driver for Flarion Flash OFDM NIC Device +// +// Copyright (C) 2006 Flarion Technologies, All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) any +// later version. This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. You should have received a copy of the GNU General Public +// License along with this program; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place - +// Suite 330, Boston, MA 02111-1307, USA. +//--------------------------------------------------------------------------- +// +// File: ft1000_chdev.c +// +// Description: Custom character device dispatch routines. +// +// History: +// 8/29/02 Whc Ported to Linux. +// 6/05/06 Whc Porting to Linux 2.6.9 +// +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "ft1000_usb.h" + +static int ft1000_flarion_cnt = 0; + +static int ft1000_open (struct inode *inode, struct file *file); +static unsigned int ft1000_poll_dev(struct file *file, poll_table *wait); +static long ft1000_ioctl(struct file *file, unsigned int command, + unsigned long argument); +static int ft1000_release (struct inode *inode, struct file *file); + +// List to free receive command buffer pool +struct list_head freercvpool; + +// lock to arbitrate free buffer list for receive command data +spinlock_t free_buff_lock; + +int numofmsgbuf = 0; + +// +// Table of entry-point routines for char device +// +static struct file_operations ft1000fops = +{ + .unlocked_ioctl = ft1000_ioctl, + .poll = ft1000_poll_dev, + .open = ft1000_open, + .release = ft1000_release, + .llseek = no_llseek, +}; + +//--------------------------------------------------------------------------- +// Function: ft1000_get_buffer +// +// Parameters: +// +// Returns: +// +// Description: +// +// Notes: +// +//--------------------------------------------------------------------------- +struct dpram_blk *ft1000_get_buffer(struct list_head *bufflist) +{ + unsigned long flags; + struct dpram_blk *ptr; + + spin_lock_irqsave(&free_buff_lock, flags); + // Check if buffer is available + if ( list_empty(bufflist) ) { + DEBUG("ft1000_get_buffer: No more buffer - %d\n", numofmsgbuf); + ptr = NULL; + } + else { + numofmsgbuf--; + ptr = list_entry(bufflist->next, struct dpram_blk, list); + list_del(&ptr->list); + //DEBUG("ft1000_get_buffer: number of free msg buffers = %d\n", numofmsgbuf); + } + spin_unlock_irqrestore(&free_buff_lock, flags); + + return ptr; +} + + + + +//--------------------------------------------------------------------------- +// Function: ft1000_free_buffer +// +// Parameters: +// +// Returns: +// +// Description: +// +// Notes: +// +//--------------------------------------------------------------------------- +void ft1000_free_buffer(struct dpram_blk *pdpram_blk, struct list_head *plist) +{ + unsigned long flags; + + spin_lock_irqsave(&free_buff_lock, flags); + // Put memory back to list + list_add_tail(&pdpram_blk->list, plist); + numofmsgbuf++; + //DEBUG("ft1000_free_buffer: number of free msg buffers = %d\n", numofmsgbuf); + spin_unlock_irqrestore(&free_buff_lock, flags); +} + +//--------------------------------------------------------------------------- +// Function: ft1000_CreateDevice +// +// Parameters: dev - pointer to adapter object +// +// Returns: 0 if successful +// +// Description: Creates a private char device. +// +// Notes: Only called by init_module(). +// +//--------------------------------------------------------------------------- +int ft1000_create_dev(struct ft1000_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev->net); + int result; + int i; + struct dentry *dir, *file; + struct ft1000_debug_dirs *tmp; + + // make a new device name + sprintf(info->DeviceName, "%s%d", "FT1000_", info->CardNumber); + + DEBUG("%s: number of instance = %d\n", __func__, ft1000_flarion_cnt); + DEBUG("DeviceCreated = %x\n", info->DeviceCreated); + + if (info->DeviceCreated) + { + DEBUG("%s: \"%s\" already registered\n", __func__, info->DeviceName); + return -EIO; + } + + + // register the device + DEBUG("%s: \"%s\" debugfs device registration\n", __func__, info->DeviceName); + + tmp = kmalloc(sizeof(struct ft1000_debug_dirs), GFP_KERNEL); + if (tmp == NULL) { + result = -1; + goto fail; + } + + dir = debugfs_create_dir(info->DeviceName, 0); + if (IS_ERR(dir)) { + result = PTR_ERR(dir); + goto debug_dir_fail; + } + + file = debugfs_create_file("device", S_IRUGO | S_IWUSR, dir, + dev, &ft1000fops); + if (IS_ERR(file)) { + result = PTR_ERR(file); + goto debug_file_fail; + } + + tmp->dent = dir; + tmp->file = file; + tmp->int_number = info->CardNumber; + list_add(&(tmp->list), &(info->nodes.list)); + + DEBUG("%s: registered debugfs directory \"%s\"\n", __func__, info->DeviceName); + + // initialize application information + info->appcnt = 0; + for (i=0; iapp_info[i].nTxMsg = 0; + info->app_info[i].nRxMsg = 0; + info->app_info[i].nTxMsgReject = 0; + info->app_info[i].nRxMsgMiss = 0; + info->app_info[i].fileobject = NULL; + info->app_info[i].app_id = i+1; + info->app_info[i].DspBCMsgFlag = 0; + info->app_info[i].NumOfMsg = 0; + init_waitqueue_head(&info->app_info[i].wait_dpram_msg); + INIT_LIST_HEAD (&info->app_info[i].app_sqlist); + } + + info->DeviceCreated = TRUE; + ft1000_flarion_cnt++; + + return 0; + +debug_file_fail: + debugfs_remove(dir); +debug_dir_fail: + kfree(tmp); +fail: + return result; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_DestroyDeviceDEBUG +// +// Parameters: dev - pointer to adapter object +// +// Description: Destroys a private char device. +// +// Notes: Only called by cleanup_module(). +// +//--------------------------------------------------------------------------- +void ft1000_destroy_dev(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + int i; + struct dpram_blk *pdpram_blk; + struct dpram_blk *ptr; + struct list_head *pos, *q; + struct ft1000_debug_dirs *dir; + + DEBUG("%s called\n", __func__); + + + + if (info->DeviceCreated) + { + ft1000_flarion_cnt--; + list_for_each_safe(pos, q, &info->nodes.list) { + dir = list_entry(pos, struct ft1000_debug_dirs, list); + if (dir->int_number == info->CardNumber) { + debugfs_remove(dir->file); + debugfs_remove(dir->dent); + list_del(pos); + kfree(dir); + } + } + DEBUG("%s: unregistered device \"%s\"\n", __func__, + info->DeviceName); + + // Make sure we free any memory reserve for slow Queue + for (i=0; iapp_info[i].app_sqlist) == 0) { + pdpram_blk = list_entry(info->app_info[i].app_sqlist.next, struct dpram_blk, list); + list_del(&pdpram_blk->list); + ft1000_free_buffer(pdpram_blk, &freercvpool); + + } + wake_up_interruptible(&info->app_info[i].wait_dpram_msg); + } + + // Remove buffer allocated for receive command data + if (ft1000_flarion_cnt == 0) { + while (list_empty(&freercvpool) == 0) { + ptr = list_entry(freercvpool.next, struct dpram_blk, list); + list_del(&ptr->list); + kfree(ptr->pbuffer); + kfree(ptr); + } + } + info->DeviceCreated = FALSE; + } + + +} + +//--------------------------------------------------------------------------- +// Function: ft1000_open +// +// Parameters: +// +// Description: +// +// Notes: +// +//--------------------------------------------------------------------------- +static int ft1000_open (struct inode *inode, struct file *file) +{ + struct ft1000_info *info; + struct ft1000_device *dev = (struct ft1000_device *)inode->i_private; + int i,num; + + DEBUG("%s called\n", __func__); + num = (MINOR(inode->i_rdev) & 0xf); + DEBUG("ft1000_open: minor number=%d\n", num); + + info = file->private_data = netdev_priv(dev->net); + + DEBUG("f_owner = %p number of application = %d\n", (&file->f_owner), info->appcnt ); + + // Check if maximum number of application exceeded + if (info->appcnt > MAX_NUM_APP) { + DEBUG("Maximum number of application exceeded\n"); + return -EACCES; + } + + // Search for available application info block + for (i=0; iapp_info[i].fileobject == NULL) ) { + break; + } + } + + // Fail due to lack of application info block + if (i == MAX_NUM_APP) { + DEBUG("Could not find an application info block\n"); + return -EACCES; + } + + info->appcnt++; + info->app_info[i].fileobject = &file->f_owner; + info->app_info[i].nTxMsg = 0; + info->app_info[i].nRxMsg = 0; + info->app_info[i].nTxMsgReject = 0; + info->app_info[i].nRxMsgMiss = 0; + + nonseekable_open(inode, file); + return 0; +} + + +//--------------------------------------------------------------------------- +// Function: ft1000_poll_dev +// +// Parameters: +// +// Description: +// +// Notes: +// +//--------------------------------------------------------------------------- + +static unsigned int ft1000_poll_dev(struct file *file, poll_table *wait) +{ + struct net_device *dev = file->private_data; + struct ft1000_info *info; + int i; + + //DEBUG("ft1000_poll_dev called\n"); + if (ft1000_flarion_cnt == 0) { + DEBUG("FT1000:ft1000_poll_dev called when ft1000_flarion_cnt is zero\n"); + return (-EBADF); + } + + info = netdev_priv(dev); + + // Search for matching file object + for (i=0; iapp_info[i].fileobject == &file->f_owner) { + //DEBUG("FT1000:ft1000_ioctl: Message is for AppId = %d\n", info->app_info[i].app_id); + break; + } + } + + // Could not find application info block + if (i == MAX_NUM_APP) { + DEBUG("FT1000:ft1000_ioctl:Could not find application info block\n"); + return ( -EACCES ); + } + + if (list_empty(&info->app_info[i].app_sqlist) == 0) { + DEBUG("FT1000:ft1000_poll_dev:Message detected in slow queue\n"); + return(POLLIN | POLLRDNORM | POLLPRI); + } + + poll_wait (file, &info->app_info[i].wait_dpram_msg, wait); + //DEBUG("FT1000:ft1000_poll_dev:Polling for data from DSP\n"); + + return (0); +} + +//--------------------------------------------------------------------------- +// Function: ft1000_ioctl +// +// Parameters: +// +// Description: +// +// Notes: +// +//--------------------------------------------------------------------------- +static long ft1000_ioctl (struct file *file, unsigned int command, + unsigned long argument) +{ + void __user *argp = (void __user *)argument; + struct ft1000_info *info; + struct ft1000_device *ft1000dev; + int result=0; + int cmd; + int i; + u16 tempword; + unsigned long flags; + struct timeval tv; + IOCTL_GET_VER get_ver_data; + IOCTL_GET_DSP_STAT get_stat_data; + u8 ConnectionMsg[] = {0x00,0x44,0x10,0x20,0x80,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x93,0x64, + 0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0a, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x02,0x37,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x01,0x00,0x01,0x7f,0x00, + 0x00,0x01,0x00,0x00}; + + unsigned short ledStat=0; + unsigned short conStat=0; + + //DEBUG("ft1000_ioctl called\n"); + + if (ft1000_flarion_cnt == 0) { + DEBUG("FT1000:ft1000_ioctl called when ft1000_flarion_cnt is zero\n"); + return (-EBADF); + } + + //DEBUG("FT1000:ft1000_ioctl:command = 0x%x argument = 0x%8x\n", command, (u32)argument); + + info = file->private_data; + ft1000dev = info->pFt1000Dev; + cmd = _IOC_NR(command); + //DEBUG("FT1000:ft1000_ioctl:cmd = 0x%x\n", cmd); + + // process the command + switch (cmd) { + case IOCTL_REGISTER_CMD: + DEBUG("FT1000:ft1000_ioctl: IOCTL_FT1000_REGISTER called\n"); + result = get_user(tempword, (__u16 __user*)argp); + if (result) { + DEBUG("result = %d failed to get_user\n", result); + break; + } + if (tempword == DSPBCMSGID) { + // Search for matching file object + for (i=0; iapp_info[i].fileobject == &file->f_owner) { + info->app_info[i].DspBCMsgFlag = 1; + DEBUG("FT1000:ft1000_ioctl:Registered for broadcast messages\n"); + break; + } + } + } + break; + + case IOCTL_GET_VER_CMD: + DEBUG("FT1000:ft1000_ioctl: IOCTL_FT1000_GET_VER called\n"); + + get_ver_data.drv_ver = FT1000_DRV_VER; + + if (copy_to_user(argp, &get_ver_data, sizeof(get_ver_data)) ) { + DEBUG("FT1000:ft1000_ioctl: copy fault occurred\n"); + result = -EFAULT; + break; + } + + DEBUG("FT1000:ft1000_ioctl:driver version = 0x%x\n",(unsigned int)get_ver_data.drv_ver); + + break; + case IOCTL_CONNECT: + // Connect Message + DEBUG("FT1000:ft1000_ioctl: IOCTL_FT1000_CONNECT\n"); + ConnectionMsg[79] = 0xfc; + card_send_command(ft1000dev, (unsigned short *)ConnectionMsg, 0x4c); + + break; + case IOCTL_DISCONNECT: + // Disconnect Message + DEBUG("FT1000:ft1000_ioctl: IOCTL_FT1000_DISCONNECT\n"); + ConnectionMsg[79] = 0xfd; + card_send_command(ft1000dev, (unsigned short *)ConnectionMsg, 0x4c); + break; + case IOCTL_GET_DSP_STAT_CMD: + //DEBUG("FT1000:ft1000_ioctl: IOCTL_FT1000_GET_DSP_STAT called\n"); + memset(&get_stat_data, 0, sizeof(get_stat_data)); + memcpy(get_stat_data.DspVer, info->DspVer, DSPVERSZ); + memcpy(get_stat_data.HwSerNum, info->HwSerNum, HWSERNUMSZ); + memcpy(get_stat_data.Sku, info->Sku, SKUSZ); + memcpy(get_stat_data.eui64, info->eui64, EUISZ); + + if (info->ProgConStat != 0xFF) { + ft1000_read_dpram16(ft1000dev, FT1000_MAG_DSP_LED, (u8 *)&ledStat, FT1000_MAG_DSP_LED_INDX); + get_stat_data.LedStat = ntohs(ledStat); + DEBUG("FT1000:ft1000_ioctl: LedStat = 0x%x\n", get_stat_data.LedStat); + ft1000_read_dpram16(ft1000dev, FT1000_MAG_DSP_CON_STATE, (u8 *)&conStat, FT1000_MAG_DSP_CON_STATE_INDX); + get_stat_data.ConStat = ntohs(conStat); + DEBUG("FT1000:ft1000_ioctl: ConStat = 0x%x\n", get_stat_data.ConStat); + } + else { + get_stat_data.ConStat = 0x0f; + } + + + get_stat_data.nTxPkts = info->stats.tx_packets; + get_stat_data.nRxPkts = info->stats.rx_packets; + get_stat_data.nTxBytes = info->stats.tx_bytes; + get_stat_data.nRxBytes = info->stats.rx_bytes; + do_gettimeofday ( &tv ); + get_stat_data.ConTm = (u32)(tv.tv_sec - info->ConTm); + DEBUG("Connection Time = %d\n", (int)get_stat_data.ConTm); + if (copy_to_user(argp, &get_stat_data, sizeof(get_stat_data)) ) { + DEBUG("FT1000:ft1000_ioctl: copy fault occurred\n"); + result = -EFAULT; + break; + } + DEBUG("ft1000_chioctl: GET_DSP_STAT succeed\n"); + break; + case IOCTL_SET_DPRAM_CMD: + { + IOCTL_DPRAM_BLK *dpram_data = NULL; + //IOCTL_DPRAM_COMMAND dpram_command; + u16 qtype; + u16 msgsz; + struct pseudo_hdr *ppseudo_hdr; + u16 *pmsg; + u16 total_len; + u16 app_index; + u16 status; + + //DEBUG("FT1000:ft1000_ioctl: IOCTL_FT1000_SET_DPRAM called\n"); + + + if (ft1000_flarion_cnt == 0) { + return (-EBADF); + } + + if (info->DrvMsgPend) { + return (-ENOTTY); + } + + if ( (info->DspAsicReset) || (info->fProvComplete == 0) ) { + return (-EACCES); + } + + info->fAppMsgPend = 1; + + if (info->CardReady) { + + //DEBUG("FT1000:ft1000_ioctl: try to SET_DPRAM \n"); + + // Get the length field to see how many bytes to copy + result = get_user(msgsz, (__u16 __user *)argp); + msgsz = ntohs (msgsz); + //DEBUG("FT1000:ft1000_ioctl: length of message = %d\n", msgsz); + + if (msgsz > MAX_CMD_SQSIZE) { + DEBUG("FT1000:ft1000_ioctl: bad message length = %d\n", msgsz); + result = -EINVAL; + break; + } + + result = -ENOMEM; + dpram_data = kmalloc(msgsz + 2, GFP_KERNEL); + if (!dpram_data) + break; + + if ( copy_from_user(dpram_data, argp, msgsz+2) ) { + DEBUG("FT1000:ft1000_ChIoctl: copy fault occurred\n"); + result = -EFAULT; + } + else { + // Check if this message came from a registered application + for (i=0; iapp_info[i].fileobject == &file->f_owner) { + break; + } + } + if (i==MAX_NUM_APP) { + DEBUG("FT1000:No matching application fileobject\n"); + result = -EINVAL; + kfree(dpram_data); + break; + } + app_index = i; + + // Check message qtype type which is the lower byte within qos_class + qtype = ntohs(dpram_data->pseudohdr.qos_class) & 0xff; + //DEBUG("FT1000_ft1000_ioctl: qtype = %d\n", qtype); + if (qtype) { + } + else { + // Put message into Slow Queue + // Only put a message into the DPRAM if msg doorbell is available + status = ft1000_read_register(ft1000dev, &tempword, FT1000_REG_DOORBELL); + //DEBUG("FT1000_ft1000_ioctl: READ REGISTER tempword=%x\n", tempword); + if (tempword & FT1000_DB_DPRAM_TX) { + // Suspend for 2ms and try again due to DSP doorbell busy + mdelay(2); + status = ft1000_read_register(ft1000dev, &tempword, FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + // Suspend for 1ms and try again due to DSP doorbell busy + mdelay(1); + status = ft1000_read_register(ft1000dev, &tempword, FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + status = ft1000_read_register(ft1000dev, &tempword, FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + // Suspend for 3ms and try again due to DSP doorbell busy + mdelay(3); + status = ft1000_read_register(ft1000dev, &tempword, FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + DEBUG("FT1000:ft1000_ioctl:Doorbell not available\n"); + result = -ENOTTY; + kfree(dpram_data); + break; + } + } + } + } + } + + //DEBUG("FT1000_ft1000_ioctl: finished reading register\n"); + + // Make sure we are within the limits of the slow queue memory limitation + if ( (msgsz < MAX_CMD_SQSIZE) && (msgsz > PSEUDOSZ) ) { + // Need to put sequence number plus new checksum for message + pmsg = (u16 *)&dpram_data->pseudohdr; + ppseudo_hdr = (struct pseudo_hdr *)pmsg; + total_len = msgsz+2; + if (total_len & 0x1) { + total_len++; + } + + // Insert slow queue sequence number + ppseudo_hdr->seq_num = info->squeseqnum++; + ppseudo_hdr->portsrc = info->app_info[app_index].app_id; + // Calculate new checksum + ppseudo_hdr->checksum = *pmsg++; + //DEBUG("checksum = 0x%x\n", ppseudo_hdr->checksum); + for (i=1; i<7; i++) { + ppseudo_hdr->checksum ^= *pmsg++; + //DEBUG("checksum = 0x%x\n", ppseudo_hdr->checksum); + } + pmsg++; + ppseudo_hdr = (struct pseudo_hdr *)pmsg; + card_send_command(ft1000dev,(unsigned short*)dpram_data,total_len+2); + + + info->app_info[app_index].nTxMsg++; + } + else { + result = -EINVAL; + } + } + } + } + else { + DEBUG("FT1000:ft1000_ioctl: Card not ready take messages\n"); + result = -EACCES; + } + kfree(dpram_data); + + } + break; + case IOCTL_GET_DPRAM_CMD: + { + struct dpram_blk *pdpram_blk; + IOCTL_DPRAM_BLK __user *pioctl_dpram; + int msglen; + + //DEBUG("FT1000:ft1000_ioctl: IOCTL_FT1000_GET_DPRAM called\n"); + + if (ft1000_flarion_cnt == 0) { + return (-EBADF); + } + + // Search for matching file object + for (i=0; iapp_info[i].fileobject == &file->f_owner) { + //DEBUG("FT1000:ft1000_ioctl: Message is for AppId = %d\n", info->app_info[i].app_id); + break; + } + } + + // Could not find application info block + if (i == MAX_NUM_APP) { + DEBUG("FT1000:ft1000_ioctl:Could not find application info block\n"); + result = -EBADF; + break; + } + + result = 0; + pioctl_dpram = argp; + if (list_empty(&info->app_info[i].app_sqlist) == 0) { + //DEBUG("FT1000:ft1000_ioctl:Message detected in slow queue\n"); + spin_lock_irqsave(&free_buff_lock, flags); + pdpram_blk = list_entry(info->app_info[i].app_sqlist.next, struct dpram_blk, list); + list_del(&pdpram_blk->list); + info->app_info[i].NumOfMsg--; + //DEBUG("FT1000:ft1000_ioctl:NumOfMsg for app %d = %d\n", i, info->app_info[i].NumOfMsg); + spin_unlock_irqrestore(&free_buff_lock, flags); + msglen = ntohs(*(u16 *)pdpram_blk->pbuffer) + PSEUDOSZ; + result = get_user(msglen, &pioctl_dpram->total_len); + if (result) + break; + msglen = htons(msglen); + //DEBUG("FT1000:ft1000_ioctl:msg length = %x\n", msglen); + if(copy_to_user (&pioctl_dpram->pseudohdr, pdpram_blk->pbuffer, msglen)) + { + DEBUG("FT1000:ft1000_ioctl: copy fault occurred\n"); + result = -EFAULT; + break; + } + + ft1000_free_buffer(pdpram_blk, &freercvpool); + result = msglen; + } + //DEBUG("FT1000:ft1000_ioctl: IOCTL_FT1000_GET_DPRAM no message\n"); + } + break; + + default: + DEBUG("FT1000:ft1000_ioctl:unknown command: 0x%x\n", command); + result = -ENOTTY; + break; + } + info->fAppMsgPend = 0; + return result; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_release +// +// Parameters: +// +// Description: +// +// Notes: +// +//--------------------------------------------------------------------------- +static int ft1000_release (struct inode *inode, struct file *file) +{ + struct ft1000_info *info; + struct net_device *dev; + int i; + struct dpram_blk *pdpram_blk; + + DEBUG("ft1000_release called\n"); + + dev = file->private_data; + info = netdev_priv(dev); + + if (ft1000_flarion_cnt == 0) { + info->appcnt--; + return (-EBADF); + } + + // Search for matching file object + for (i=0; iapp_info[i].fileobject == &file->f_owner) { + //DEBUG("FT1000:ft1000_ioctl: Message is for AppId = %d\n", info->app_info[i].app_id); + break; + } + } + + if (i==MAX_NUM_APP) + return 0; + + while (list_empty(&info->app_info[i].app_sqlist) == 0) { + DEBUG("Remove and free memory queue up on slow queue\n"); + pdpram_blk = list_entry(info->app_info[i].app_sqlist.next, struct dpram_blk, list); + list_del(&pdpram_blk->list); + ft1000_free_buffer(pdpram_blk, &freercvpool); + } + + // initialize application information + info->appcnt--; + DEBUG("ft1000_chdev:%s:appcnt = %d\n", __FUNCTION__, info->appcnt); + info->app_info[i].fileobject = NULL; + + return 0; +} + diff --git a/drivers/staging/ft1000/ft1000-usb/ft1000_download.c b/drivers/staging/ft1000/ft1000-usb/ft1000_download.c new file mode 100644 index 00000000..1972b724 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-usb/ft1000_download.c @@ -0,0 +1,1235 @@ +//===================================================== +// CopyRight (C) 2007 Qualcomm Inc. All Rights Reserved. +// +// +// This file is part of Express Card USB Driver +// +// $Id: +//==================================================== +// 20090926; aelias; removed compiler warnings; ubuntu 9.04; 2.6.28-15-generic + +#include +#include +#include +#include +#include +#include +#include +#include "ft1000_usb.h" + + +#define DWNLD_HANDSHAKE_LOC 0x02 +#define DWNLD_TYPE_LOC 0x04 +#define DWNLD_SIZE_MSW_LOC 0x06 +#define DWNLD_SIZE_LSW_LOC 0x08 +#define DWNLD_PS_HDR_LOC 0x0A + +#define MAX_DSP_WAIT_LOOPS 40 +#define DSP_WAIT_SLEEP_TIME 1000 /* 1 millisecond */ +#define DSP_WAIT_DISPATCH_LVL 50 /* 50 usec */ + +#define HANDSHAKE_TIMEOUT_VALUE 0xF1F1 +#define HANDSHAKE_RESET_VALUE 0xFEFE /* When DSP requests startover */ +#define HANDSHAKE_RESET_VALUE_USB 0xFE7E /* When DSP requests startover */ +#define HANDSHAKE_DSP_BL_READY 0xFEFE /* At start DSP writes this when bootloader ready */ +#define HANDSHAKE_DSP_BL_READY_USB 0xFE7E /* At start DSP writes this when bootloader ready */ +#define HANDSHAKE_DRIVER_READY 0xFFFF /* Driver writes after receiving 0xFEFE */ +#define HANDSHAKE_SEND_DATA 0x0000 /* DSP writes this when ready for more data */ + +#define HANDSHAKE_REQUEST 0x0001 /* Request from DSP */ +#define HANDSHAKE_RESPONSE 0x0000 /* Satisfied DSP request */ + +#define REQUEST_CODE_LENGTH 0x0000 +#define REQUEST_RUN_ADDRESS 0x0001 +#define REQUEST_CODE_SEGMENT 0x0002 /* In WORD count */ +#define REQUEST_DONE_BL 0x0003 +#define REQUEST_DONE_CL 0x0004 +#define REQUEST_VERSION_INFO 0x0005 +#define REQUEST_CODE_BY_VERSION 0x0006 +#define REQUEST_MAILBOX_DATA 0x0007 +#define REQUEST_FILE_CHECKSUM 0x0008 + +#define STATE_START_DWNLD 0x01 +#define STATE_BOOT_DWNLD 0x02 +#define STATE_CODE_DWNLD 0x03 +#define STATE_DONE_DWNLD 0x04 +#define STATE_SECTION_PROV 0x05 +#define STATE_DONE_PROV 0x06 +#define STATE_DONE_FILE 0x07 + +#define MAX_LENGTH 0x7f0 + +// Temporary download mechanism for Magnemite +#define DWNLD_MAG_TYPE_LOC 0x00 +#define DWNLD_MAG_LEN_LOC 0x01 +#define DWNLD_MAG_ADDR_LOC 0x02 +#define DWNLD_MAG_CHKSUM_LOC 0x03 +#define DWNLD_MAG_VAL_LOC 0x04 + +#define HANDSHAKE_MAG_DSP_BL_READY 0xFEFE0000 /* At start DSP writes this when bootloader ready */ +#define HANDSHAKE_MAG_DSP_ENTRY 0x01000000 /* Dsp writes this to request for entry address */ +#define HANDSHAKE_MAG_DSP_DATA 0x02000000 /* Dsp writes this to request for data block */ +#define HANDSHAKE_MAG_DSP_DONE 0x03000000 /* Dsp writes this to indicate download done */ + +#define HANDSHAKE_MAG_DRV_READY 0xFFFF0000 /* Driver writes this to indicate ready to download */ +#define HANDSHAKE_MAG_DRV_DATA 0x02FECDAB /* Driver writes this to indicate data available to DSP */ +#define HANDSHAKE_MAG_DRV_ENTRY 0x01FECDAB /* Driver writes this to indicate entry point to DSP */ + +#define HANDSHAKE_MAG_TIMEOUT_VALUE 0xF1F1 + + +// New Magnemite downloader +#define DWNLD_MAG1_HANDSHAKE_LOC 0x00 +#define DWNLD_MAG1_TYPE_LOC 0x01 +#define DWNLD_MAG1_SIZE_LOC 0x02 +#define DWNLD_MAG1_PS_HDR_LOC 0x03 + +struct dsp_file_hdr { + long version_id; // Version ID of this image format. + long package_id; // Package ID of code release. + long build_date; // Date/time stamp when file was built. + long commands_offset; // Offset to attached commands in Pseudo Hdr format. + long loader_offset; // Offset to bootloader code. + long loader_code_address; // Start address of bootloader. + long loader_code_end; // Where bootloader code ends. + long loader_code_size; + long version_data_offset; // Offset were scrambled version data begins. + long version_data_size; // Size, in words, of scrambled version data. + long nDspImages; // Number of DSP images in file. +}; + +#pragma pack(1) +struct dsp_image_info { + long coff_date; // Date/time when DSP Coff image was built. + long begin_offset; // Offset in file where image begins. + long end_offset; // Offset in file where image begins. + long run_address; // On chip Start address of DSP code. + long image_size; // Size of image. + long version; // Embedded version # of DSP code. + unsigned short checksum; // DSP File checksum + unsigned short pad1; +}; + + +//--------------------------------------------------------------------------- +// Function: check_usb_db +// +// Parameters: struct ft1000_device - device structure +// +// Returns: 0 - success +// +// Description: This function checks if the doorbell register is cleared +// +// Notes: +// +//--------------------------------------------------------------------------- +static u32 check_usb_db (struct ft1000_device *ft1000dev) +{ + int loopcnt; + u16 temp; + u32 status; + + loopcnt = 0; + + while (loopcnt < 10) { + status = ft1000_read_register(ft1000dev, &temp, + FT1000_REG_DOORBELL); + DEBUG("check_usb_db: read FT1000_REG_DOORBELL value is %x\n", + temp); + if (temp & 0x0080) { + DEBUG("FT1000:Got checkusb doorbell\n"); + status = ft1000_write_register(ft1000dev, 0x0080, + FT1000_REG_DOORBELL); + status = ft1000_write_register(ft1000dev, 0x0100, + FT1000_REG_DOORBELL); + status = ft1000_write_register(ft1000dev, 0x8000, + FT1000_REG_DOORBELL); + break; + } else { + loopcnt++; + msleep(10); + } + + } + + loopcnt = 0; + while (loopcnt < 20) { + status = ft1000_read_register(ft1000dev, &temp, + FT1000_REG_DOORBELL); + DEBUG("FT1000:check_usb_db:Doorbell = 0x%x\n", temp); + if (temp & 0x8000) { + loopcnt++; + msleep(10); + } else { + DEBUG("check_usb_db: door bell is cleared, return 0\n"); + return 0; + } + } + + return HANDSHAKE_MAG_TIMEOUT_VALUE; +} + +//--------------------------------------------------------------------------- +// Function: get_handshake +// +// Parameters: struct ft1000_device - device structure +// u16 expected_value - the handshake value expected +// +// Returns: handshakevalue - success +// HANDSHAKE_TIMEOUT_VALUE - failure +// +// Description: This function gets the handshake and compare with the expected value +// +// Notes: +// +//--------------------------------------------------------------------------- +static u16 get_handshake(struct ft1000_device *ft1000dev, u16 expected_value) +{ + u16 handshake; + int loopcnt; + u32 status = 0; + struct ft1000_info *pft1000info = netdev_priv(ft1000dev->net); + + loopcnt = 0; + + while (loopcnt < 100) { + /* Need to clear downloader doorbell if Hartley ASIC */ + status = ft1000_write_register(ft1000dev, FT1000_DB_DNLD_RX, + FT1000_REG_DOORBELL); + if (pft1000info->fcodeldr) { + DEBUG(" get_handshake: fcodeldr is %d\n", + pft1000info->fcodeldr); + pft1000info->fcodeldr = 0; + status = check_usb_db(ft1000dev); + if (status != STATUS_SUCCESS) { + DEBUG("get_handshake: check_usb_db failed\n"); + status = STATUS_FAILURE; + break; + } + status = ft1000_write_register(ft1000dev, + FT1000_DB_DNLD_RX, + FT1000_REG_DOORBELL); + } + + status = ft1000_read_dpram16(ft1000dev, + DWNLD_MAG1_HANDSHAKE_LOC, (u8 *)&handshake, 1); + handshake = ntohs(handshake); + + if (status) + return HANDSHAKE_TIMEOUT_VALUE; + + if ((handshake == expected_value) || + (handshake == HANDSHAKE_RESET_VALUE_USB)) { + return handshake; + } else { + loopcnt++; + msleep(10); + } + } + + return HANDSHAKE_TIMEOUT_VALUE; +} + +//--------------------------------------------------------------------------- +// Function: put_handshake +// +// Parameters: struct ft1000_device - device structure +// u16 handshake_value - handshake to be written +// +// Returns: none +// +// Description: This function write the handshake value to the handshake location +// in DPRAM +// +// Notes: +// +//--------------------------------------------------------------------------- +static void put_handshake(struct ft1000_device *ft1000dev,u16 handshake_value) +{ + u32 tempx; + u16 tempword; + u32 status; + + tempx = (u32)handshake_value; + tempx = ntohl(tempx); + + tempword = (u16)(tempx & 0xffff); + status = ft1000_write_dpram16(ft1000dev, DWNLD_MAG1_HANDSHAKE_LOC, + tempword, 0); + tempword = (u16)(tempx >> 16); + status = ft1000_write_dpram16(ft1000dev, DWNLD_MAG1_HANDSHAKE_LOC, + tempword, 1); + status = ft1000_write_register(ft1000dev, FT1000_DB_DNLD_TX, + FT1000_REG_DOORBELL); +} + +static u16 get_handshake_usb(struct ft1000_device *ft1000dev, u16 expected_value) +{ + u16 handshake; + int loopcnt; + u16 temp; + u32 status = 0; + + struct ft1000_info *pft1000info = netdev_priv(ft1000dev->net); + loopcnt = 0; + handshake = 0; + + while (loopcnt < 100) { + if (pft1000info->usbboot == 2) { + status = ft1000_read_dpram32(ft1000dev, 0, + (u8 *)&(pft1000info->tempbuf[0]), 64); + for (temp = 0; temp < 16; temp++) { + DEBUG("tempbuf %d = 0x%x\n", temp, + pft1000info->tempbuf[temp]); + } + status = ft1000_read_dpram16(ft1000dev, + DWNLD_MAG1_HANDSHAKE_LOC, + (u8 *)&handshake, 1); + DEBUG("handshake from read_dpram16 = 0x%x\n", + handshake); + if (pft1000info->dspalive == pft1000info->tempbuf[6]) { + handshake = 0; + } else { + handshake = pft1000info->tempbuf[1]; + pft1000info->dspalive = + pft1000info->tempbuf[6]; + } + } else { + status = ft1000_read_dpram16(ft1000dev, + DWNLD_MAG1_HANDSHAKE_LOC, + (u8 *)&handshake, 1); + } + + loopcnt++; + msleep(10); + handshake = ntohs(handshake); + if ((handshake == expected_value) || + (handshake == HANDSHAKE_RESET_VALUE_USB)) + return handshake; + } + + return HANDSHAKE_TIMEOUT_VALUE; +} + +static void put_handshake_usb(struct ft1000_device *ft1000dev,u16 handshake_value) +{ + int i; + + for (i=0; i<1000; i++); +} + +//--------------------------------------------------------------------------- +// Function: get_request_type +// +// Parameters: struct ft1000_device - device structure +// +// Returns: request type - success +// +// Description: This function returns the request type +// +// Notes: +// +//--------------------------------------------------------------------------- +static u16 get_request_type(struct ft1000_device *ft1000dev) +{ + u16 request_type; + u32 status; + u16 tempword; + u32 tempx; + struct ft1000_info *pft1000info = netdev_priv(ft1000dev->net); + + if (pft1000info->bootmode == 1) { + status = fix_ft1000_read_dpram32(ft1000dev, + DWNLD_MAG1_TYPE_LOC, (u8 *)&tempx); + tempx = ntohl(tempx); + } else { + tempx = 0; + status = ft1000_read_dpram16(ft1000dev, + DWNLD_MAG1_TYPE_LOC, (u8 *)&tempword, 1); + tempx |= (tempword << 16); + tempx = ntohl(tempx); + } + request_type = (u16)tempx; + + return request_type; +} + +static u16 get_request_type_usb(struct ft1000_device *ft1000dev) +{ + u16 request_type; + u32 status; + u16 tempword; + u32 tempx; + struct ft1000_info *pft1000info = netdev_priv(ft1000dev->net); + + if (pft1000info->bootmode == 1) { + status = fix_ft1000_read_dpram32(ft1000dev, + DWNLD_MAG1_TYPE_LOC, (u8 *)&tempx); + tempx = ntohl(tempx); + } else { + if (pft1000info->usbboot == 2) { + tempx = pft1000info->tempbuf[2]; + tempword = pft1000info->tempbuf[3]; + } else { + tempx = 0; + status = ft1000_read_dpram16(ft1000dev, + DWNLD_MAG1_TYPE_LOC, + (u8 *)&tempword, 1); + } + tempx |= (tempword << 16); + tempx = ntohl(tempx); + } + request_type = (u16)tempx; + + return request_type; +} + +//--------------------------------------------------------------------------- +// Function: get_request_value +// +// Parameters: struct ft1000_device - device structure +// +// Returns: request value - success +// +// Description: This function returns the request value +// +// Notes: +// +//--------------------------------------------------------------------------- +static long get_request_value(struct ft1000_device *ft1000dev) +{ + u32 value; + u16 tempword; + u32 status; + struct ft1000_info *pft1000info = netdev_priv(ft1000dev->net); + + if (pft1000info->bootmode == 1) { + status = fix_ft1000_read_dpram32(ft1000dev, + DWNLD_MAG1_SIZE_LOC, (u8 *)&value); + value = ntohl(value); + } else { + status = ft1000_read_dpram16(ft1000dev, + DWNLD_MAG1_SIZE_LOC, (u8 *)&tempword, 0); + value = tempword; + status = ft1000_read_dpram16(ft1000dev, + DWNLD_MAG1_SIZE_LOC, (u8 *)&tempword, 1); + value |= (tempword << 16); + value = ntohl(value); + } + + return value; +} + + +//--------------------------------------------------------------------------- +// Function: put_request_value +// +// Parameters: struct ft1000_device - device structure +// long lvalue - value to be put into DPRAM location DWNLD_MAG1_SIZE_LOC +// +// Returns: none +// +// Description: This function writes a value to DWNLD_MAG1_SIZE_LOC +// +// Notes: +// +//--------------------------------------------------------------------------- +static void put_request_value(struct ft1000_device *ft1000dev, long lvalue) +{ + u32 tempx; + u32 status; + + tempx = ntohl(lvalue); + status = fix_ft1000_write_dpram32(ft1000dev, DWNLD_MAG1_SIZE_LOC, + (u8 *)&tempx); +} + + + +//--------------------------------------------------------------------------- +// Function: hdr_checksum +// +// Parameters: struct pseudo_hdr *pHdr - Pseudo header pointer +// +// Returns: checksum - success +// +// Description: This function returns the checksum of the pseudo header +// +// Notes: +// +//--------------------------------------------------------------------------- +static u16 hdr_checksum(struct pseudo_hdr *pHdr) +{ + u16 *usPtr = (u16 *)pHdr; + u16 chksum; + + + chksum = ((((((usPtr[0] ^ usPtr[1]) ^ usPtr[2]) ^ usPtr[3]) ^ + usPtr[4]) ^ usPtr[5]) ^ usPtr[6]); + + return chksum; +} + +static int check_buffers(u16 *buff_w, u16 *buff_r, int len, int offset) +{ + int i; + + for (i = 0; i < len; i++) { + if (buff_w[i] != buff_r[i + offset]) + return -1; + } + + return 0; +} + +//--------------------------------------------------------------------------- +// Function: write_blk +// +// Parameters: struct ft1000_device - device structure +// u16 **pUsFile - DSP image file pointer in u16 +// u8 **pUcFile - DSP image file pointer in u8 +// long word_length - length of the buffer to be written +// to DPRAM +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function writes a block of DSP image to DPRAM +// +// Notes: +// +//--------------------------------------------------------------------------- +static u32 write_blk (struct ft1000_device *ft1000dev, u16 **pUsFile, u8 **pUcFile, long word_length) +{ + u32 Status = STATUS_SUCCESS; + u16 dpram; + int loopcnt, i, j; + u16 tempword; + u16 tempbuffer[64]; + u16 resultbuffer[64]; + struct ft1000_info *pft1000info = netdev_priv(ft1000dev->net); + + //DEBUG("FT1000:download:start word_length = %d\n",(int)word_length); + dpram = (u16)DWNLD_MAG1_PS_HDR_LOC; + tempword = *(*pUsFile); + (*pUsFile)++; + Status = ft1000_write_dpram16(ft1000dev, dpram, tempword, 0); + tempword = *(*pUsFile); + (*pUsFile)++; + Status = ft1000_write_dpram16(ft1000dev, dpram++, tempword, 1); + + *pUcFile = *pUcFile + 4; + word_length--; + tempword = (u16)word_length; + word_length = (word_length / 16) + 1; + for (; word_length > 0; word_length--) /* In words */ + { + loopcnt = 0; + + for (i=0; i<32; i++) + { + if (tempword != 0) + { + tempbuffer[i++] = *(*pUsFile); + (*pUsFile)++; + tempbuffer[i] = *(*pUsFile); + (*pUsFile)++; + *pUcFile = *pUcFile + 4; + loopcnt++; + tempword--; + } + else + { + tempbuffer[i++] = 0; + tempbuffer[i] = 0; + } + } + + //DEBUG("write_blk: loopcnt is %d\n", loopcnt); + //DEBUG("write_blk: bootmode = %d\n", bootmode); + //DEBUG("write_blk: dpram = %x\n", dpram); + if (pft1000info->bootmode == 0) + { + if (dpram >= 0x3F4) + Status = ft1000_write_dpram32 (ft1000dev, dpram, (u8 *)&tempbuffer[0], 8); + else + Status = ft1000_write_dpram32 (ft1000dev, dpram, (u8 *)&tempbuffer[0], 64); + } + else + { + for (j=0; j<10; j++) + { + Status = ft1000_write_dpram32 (ft1000dev, dpram, (u8 *)&tempbuffer[0], 64); + if (Status == STATUS_SUCCESS) + { + // Work around for ASIC bit stuffing problem. + if ( (tempbuffer[31] & 0xfe00) == 0xfe00) + { + Status = ft1000_write_dpram32(ft1000dev, dpram+12, (u8 *)&tempbuffer[24], 64); + } + // Let's check the data written + Status = ft1000_read_dpram32 (ft1000dev, dpram, (u8 *)&resultbuffer[0], 64); + if ( (tempbuffer[31] & 0xfe00) == 0xfe00) + { + if (check_buffers(tempbuffer, resultbuffer, 28, 0)) { + DEBUG("FT1000:download:DPRAM write failed 1 during bootloading\n"); + msleep(10); + Status = STATUS_FAILURE; + break; + } + Status = ft1000_read_dpram32 (ft1000dev, dpram+12, (u8 *)&resultbuffer[0], 64); + + if (check_buffers(tempbuffer, resultbuffer, 16, 24)) { + DEBUG("FT1000:download:DPRAM write failed 2 during bootloading\n"); + msleep(10); + Status = STATUS_FAILURE; + break; + } + + } + else + { + if (check_buffers(tempbuffer, resultbuffer, 32, 0)) { + DEBUG("FT1000:download:DPRAM write failed 3 during bootloading\n"); + msleep(10); + Status = STATUS_FAILURE; + break; + } + + } + + if (Status == STATUS_SUCCESS) + break; + + } + } + + if (Status != STATUS_SUCCESS) + { + DEBUG("FT1000:download:Write failed tempbuffer[31] = 0x%x\n", tempbuffer[31]); + break; + } + + } + dpram = dpram + loopcnt; + } + + return Status; +} + +static void usb_dnld_complete (struct urb *urb) +{ + //DEBUG("****** usb_dnld_complete\n"); +} + +//--------------------------------------------------------------------------- +// Function: write_blk_fifo +// +// Parameters: struct ft1000_device - device structure +// u16 **pUsFile - DSP image file pointer in u16 +// u8 **pUcFile - DSP image file pointer in u8 +// long word_length - length of the buffer to be written +// to DPRAM +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function writes a block of DSP image to DPRAM +// +// Notes: +// +//--------------------------------------------------------------------------- +static u32 write_blk_fifo(struct ft1000_device *ft1000dev, u16 **pUsFile, + u8 **pUcFile, long word_length) +{ + u32 Status = STATUS_SUCCESS; + int byte_length; + + byte_length = word_length * 4; + + if (byte_length && ((byte_length % 64) == 0)) + byte_length += 4; + + if (byte_length < 64) + byte_length = 68; + + usb_init_urb(ft1000dev->tx_urb); + memcpy(ft1000dev->tx_buf, *pUcFile, byte_length); + usb_fill_bulk_urb(ft1000dev->tx_urb, + ft1000dev->dev, + usb_sndbulkpipe(ft1000dev->dev, + ft1000dev->bulk_out_endpointAddr), + ft1000dev->tx_buf, byte_length, usb_dnld_complete, + (void *)ft1000dev); + + usb_submit_urb(ft1000dev->tx_urb, GFP_ATOMIC); + + *pUsFile = *pUsFile + (word_length << 1); + *pUcFile = *pUcFile + (word_length << 2); + + return Status; +} + +//--------------------------------------------------------------------------- +// +// Function: scram_dnldr +// +// Synopsis: Scramble downloader for Harley based ASIC via USB interface +// +// Arguments: pFileStart - pointer to start of file +// FileLength - file length +// +// Returns: status - return code +//--------------------------------------------------------------------------- + +u16 scram_dnldr(struct ft1000_device *ft1000dev, void *pFileStart, + u32 FileLength) +{ + u16 status = STATUS_SUCCESS; + u32 state; + u16 handshake; + struct pseudo_hdr *pseudo_header; + u16 pseudo_header_len; + long word_length; + u16 request; + u16 temp; + u16 tempword; + + struct dsp_file_hdr *file_hdr; + struct dsp_image_info *dsp_img_info = NULL; + long requested_version; + bool correct_version; + struct drv_msg *mailbox_data; + u16 *data = NULL; + u16 *s_file = NULL; + u8 *c_file = NULL; + u8 *boot_end = NULL, *code_end = NULL; + int image; + long loader_code_address, loader_code_size = 0; + long run_address = 0, run_size = 0; + + u32 templong; + u32 image_chksum = 0; + + u16 dpram = 0; + u8 *pbuffer; + struct prov_record *pprov_record; + struct ft1000_info *pft1000info = netdev_priv(ft1000dev->net); + + DEBUG("Entered scram_dnldr...\n"); + + pft1000info->fcodeldr = 0; + pft1000info->usbboot = 0; + pft1000info->dspalive = 0xffff; + + // + // Get version id of file, at first 4 bytes of file, for newer files. + // + + state = STATE_START_DWNLD; + + file_hdr = (struct dsp_file_hdr *)pFileStart; + + ft1000_write_register(ft1000dev, 0x800, FT1000_REG_MAG_WATERMARK); + + s_file = (u16 *) (pFileStart + file_hdr->loader_offset); + c_file = (u8 *) (pFileStart + file_hdr->loader_offset); + + boot_end = (u8 *) (pFileStart + file_hdr->loader_code_end); + + loader_code_address = file_hdr->loader_code_address; + loader_code_size = file_hdr->loader_code_size; + correct_version = FALSE; + + while ((status == STATUS_SUCCESS) && (state != STATE_DONE_FILE)) { + switch (state) { + case STATE_START_DWNLD: + DEBUG("FT1000:STATE_START_DWNLD\n"); + if (pft1000info->usbboot) + handshake = + get_handshake_usb(ft1000dev, + HANDSHAKE_DSP_BL_READY); + else + handshake = + get_handshake(ft1000dev, + HANDSHAKE_DSP_BL_READY); + + if (handshake == HANDSHAKE_DSP_BL_READY) { + DEBUG + ("scram_dnldr: handshake is HANDSHAKE_DSP_BL_READY, call put_handshake(HANDSHAKE_DRIVER_READY)\n"); + put_handshake(ft1000dev, + HANDSHAKE_DRIVER_READY); + } else { + DEBUG + ("FT1000:download:Download error: Handshake failed\n"); + status = STATUS_FAILURE; + } + + state = STATE_BOOT_DWNLD; + + break; + + case STATE_BOOT_DWNLD: + DEBUG("FT1000:STATE_BOOT_DWNLD\n"); + pft1000info->bootmode = 1; + handshake = get_handshake(ft1000dev, HANDSHAKE_REQUEST); + if (handshake == HANDSHAKE_REQUEST) { + /* + * Get type associated with the request. + */ + request = get_request_type(ft1000dev); + switch (request) { + case REQUEST_RUN_ADDRESS: + DEBUG("FT1000:REQUEST_RUN_ADDRESS\n"); + put_request_value(ft1000dev, + loader_code_address); + break; + case REQUEST_CODE_LENGTH: + DEBUG("FT1000:REQUEST_CODE_LENGTH\n"); + put_request_value(ft1000dev, + loader_code_size); + break; + case REQUEST_DONE_BL: + DEBUG("FT1000:REQUEST_DONE_BL\n"); + /* Reposition ptrs to beginning of code section */ + s_file = (u16 *) (boot_end); + c_file = (u8 *) (boot_end); + //DEBUG("FT1000:download:s_file = 0x%8x\n", (int)s_file); + //DEBUG("FT1000:download:c_file = 0x%8x\n", (int)c_file); + state = STATE_CODE_DWNLD; + pft1000info->fcodeldr = 1; + break; + case REQUEST_CODE_SEGMENT: + //DEBUG("FT1000:REQUEST_CODE_SEGMENT\n"); + word_length = + get_request_value(ft1000dev); + //DEBUG("FT1000:word_length = 0x%x\n", (int)word_length); + //NdisMSleep (100); + if (word_length > MAX_LENGTH) { + DEBUG + ("FT1000:download:Download error: Max length exceeded\n"); + status = STATUS_FAILURE; + break; + } + if ((word_length * 2 + c_file) > + boot_end) { + /* + * Error, beyond boot code range. + */ + DEBUG + ("FT1000:download:Download error: Requested len=%d exceeds BOOT code boundary.\n", + (int)word_length); + status = STATUS_FAILURE; + break; + } + /* + * Position ASIC DPRAM auto-increment pointer. + */ + dpram = (u16) DWNLD_MAG1_PS_HDR_LOC; + if (word_length & 0x1) + word_length++; + word_length = word_length / 2; + + status = + write_blk(ft1000dev, &s_file, + &c_file, word_length); + //DEBUG("write_blk returned %d\n", status); + break; + default: + DEBUG + ("FT1000:download:Download error: Bad request type=%d in BOOT download state.\n", + request); + status = STATUS_FAILURE; + break; + } + if (pft1000info->usbboot) + put_handshake_usb(ft1000dev, + HANDSHAKE_RESPONSE); + else + put_handshake(ft1000dev, + HANDSHAKE_RESPONSE); + } else { + DEBUG + ("FT1000:download:Download error: Handshake failed\n"); + status = STATUS_FAILURE; + } + + break; + + case STATE_CODE_DWNLD: + //DEBUG("FT1000:STATE_CODE_DWNLD\n"); + pft1000info->bootmode = 0; + if (pft1000info->usbboot) + handshake = + get_handshake_usb(ft1000dev, + HANDSHAKE_REQUEST); + else + handshake = + get_handshake(ft1000dev, HANDSHAKE_REQUEST); + if (handshake == HANDSHAKE_REQUEST) { + /* + * Get type associated with the request. + */ + if (pft1000info->usbboot) + request = + get_request_type_usb(ft1000dev); + else + request = get_request_type(ft1000dev); + switch (request) { + case REQUEST_FILE_CHECKSUM: + DEBUG + ("FT1000:download:image_chksum = 0x%8x\n", + image_chksum); + put_request_value(ft1000dev, + image_chksum); + break; + case REQUEST_RUN_ADDRESS: + DEBUG + ("FT1000:download: REQUEST_RUN_ADDRESS\n"); + if (correct_version) { + DEBUG + ("FT1000:download:run_address = 0x%8x\n", + (int)run_address); + put_request_value(ft1000dev, + run_address); + } else { + DEBUG + ("FT1000:download:Download error: Got Run address request before image offset request.\n"); + status = STATUS_FAILURE; + break; + } + break; + case REQUEST_CODE_LENGTH: + DEBUG + ("FT1000:download:REQUEST_CODE_LENGTH\n"); + if (correct_version) { + DEBUG + ("FT1000:download:run_size = 0x%8x\n", + (int)run_size); + put_request_value(ft1000dev, + run_size); + } else { + DEBUG + ("FT1000:download:Download error: Got Size request before image offset request.\n"); + status = STATUS_FAILURE; + break; + } + break; + case REQUEST_DONE_CL: + pft1000info->usbboot = 3; + /* Reposition ptrs to beginning of provisioning section */ + s_file = + (u16 *) (pFileStart + + file_hdr->commands_offset); + c_file = + (u8 *) (pFileStart + + file_hdr->commands_offset); + state = STATE_DONE_DWNLD; + break; + case REQUEST_CODE_SEGMENT: + //DEBUG("FT1000:download: REQUEST_CODE_SEGMENT - CODELOADER\n"); + if (!correct_version) { + DEBUG + ("FT1000:download:Download error: Got Code Segment request before image offset request.\n"); + status = STATUS_FAILURE; + break; + } + + word_length = + get_request_value(ft1000dev); + //DEBUG("FT1000:download:word_length = %d\n", (int)word_length); + if (word_length > MAX_LENGTH) { + DEBUG + ("FT1000:download:Download error: Max length exceeded\n"); + status = STATUS_FAILURE; + break; + } + if ((word_length * 2 + c_file) > + code_end) { + /* + * Error, beyond boot code range. + */ + DEBUG + ("FT1000:download:Download error: Requested len=%d exceeds DSP code boundary.\n", + (int)word_length); + status = STATUS_FAILURE; + break; + } + /* + * Position ASIC DPRAM auto-increment pointer. + */ + dpram = (u16) DWNLD_MAG1_PS_HDR_LOC; + if (word_length & 0x1) + word_length++; + word_length = word_length / 2; + + write_blk_fifo(ft1000dev, &s_file, + &c_file, word_length); + if (pft1000info->usbboot == 0) + pft1000info->usbboot++; + if (pft1000info->usbboot == 1) { + tempword = 0; + ft1000_write_dpram16(ft1000dev, + DWNLD_MAG1_PS_HDR_LOC, + tempword, + 0); + } + + break; + + case REQUEST_MAILBOX_DATA: + DEBUG + ("FT1000:download: REQUEST_MAILBOX_DATA\n"); + // Convert length from byte count to word count. Make sure we round up. + word_length = + (long)(pft1000info->DSPInfoBlklen + + 1) / 2; + put_request_value(ft1000dev, + word_length); + mailbox_data = + (struct drv_msg *)&(pft1000info-> + DSPInfoBlk[0]); + /* + * Position ASIC DPRAM auto-increment pointer. + */ + + data = (u16 *) & mailbox_data->data[0]; + dpram = (u16) DWNLD_MAG1_PS_HDR_LOC; + if (word_length & 0x1) + word_length++; + + word_length = (word_length / 2); + + for (; word_length > 0; word_length--) { /* In words */ + + templong = *data++; + templong |= (*data++ << 16); + status = + fix_ft1000_write_dpram32 + (ft1000dev, dpram++, + (u8 *) & templong); + + } + break; + + case REQUEST_VERSION_INFO: + DEBUG + ("FT1000:download:REQUEST_VERSION_INFO\n"); + word_length = + file_hdr->version_data_size; + put_request_value(ft1000dev, + word_length); + /* + * Position ASIC DPRAM auto-increment pointer. + */ + + s_file = + (u16 *) (pFileStart + + file_hdr-> + version_data_offset); + + dpram = (u16) DWNLD_MAG1_PS_HDR_LOC; + if (word_length & 0x1) + word_length++; + + word_length = (word_length / 2); + + for (; word_length > 0; word_length--) { /* In words */ + + templong = ntohs(*s_file++); + temp = ntohs(*s_file++); + templong |= (temp << 16); + status = + fix_ft1000_write_dpram32 + (ft1000dev, dpram++, + (u8 *) & templong); + + } + break; + + case REQUEST_CODE_BY_VERSION: + DEBUG + ("FT1000:download:REQUEST_CODE_BY_VERSION\n"); + correct_version = FALSE; + requested_version = + get_request_value(ft1000dev); + + dsp_img_info = + (struct dsp_image_info *)(pFileStart + + + sizeof + (struct + dsp_file_hdr)); + + for (image = 0; + image < file_hdr->nDspImages; + image++) { + + if (dsp_img_info->version == + requested_version) { + correct_version = TRUE; + DEBUG + ("FT1000:download: correct_version is TRUE\n"); + s_file = + (u16 *) (pFileStart + + + dsp_img_info-> + begin_offset); + c_file = + (u8 *) (pFileStart + + dsp_img_info-> + begin_offset); + code_end = + (u8 *) (pFileStart + + dsp_img_info-> + end_offset); + run_address = + dsp_img_info-> + run_address; + run_size = + dsp_img_info-> + image_size; + image_chksum = + (u32) dsp_img_info-> + checksum; + break; + } + dsp_img_info++; + + } //end of for + + if (!correct_version) { + /* + * Error, beyond boot code range. + */ + DEBUG + ("FT1000:download:Download error: Bad Version Request = 0x%x.\n", + (int)requested_version); + status = STATUS_FAILURE; + break; + } + break; + + default: + DEBUG + ("FT1000:download:Download error: Bad request type=%d in CODE download state.\n", + request); + status = STATUS_FAILURE; + break; + } + if (pft1000info->usbboot) + put_handshake_usb(ft1000dev, + HANDSHAKE_RESPONSE); + else + put_handshake(ft1000dev, + HANDSHAKE_RESPONSE); + } else { + DEBUG + ("FT1000:download:Download error: Handshake failed\n"); + status = STATUS_FAILURE; + } + + break; + + case STATE_DONE_DWNLD: + DEBUG("FT1000:download:Code loader is done...\n"); + state = STATE_SECTION_PROV; + break; + + case STATE_SECTION_PROV: + DEBUG("FT1000:download:STATE_SECTION_PROV\n"); + pseudo_header = (struct pseudo_hdr *)c_file; + + if (pseudo_header->checksum == + hdr_checksum(pseudo_header)) { + if (pseudo_header->portdest != + 0x80 /* Dsp OAM */ ) { + state = STATE_DONE_PROV; + break; + } + pseudo_header_len = ntohs(pseudo_header->length); /* Byte length for PROV records */ + + // Get buffer for provisioning data + pbuffer = + kmalloc((pseudo_header_len + + sizeof(struct pseudo_hdr)), + GFP_ATOMIC); + if (pbuffer) { + memcpy(pbuffer, (void *)c_file, + (u32) (pseudo_header_len + + sizeof(struct + pseudo_hdr))); + // link provisioning data + pprov_record = + kmalloc(sizeof(struct prov_record), + GFP_ATOMIC); + if (pprov_record) { + pprov_record->pprov_data = + pbuffer; + list_add_tail(&pprov_record-> + list, + &pft1000info-> + prov_list); + // Move to next entry if available + c_file = + (u8 *) ((unsigned long) + c_file + + (u32) ((pseudo_header_len + 1) & 0xFFFFFFFE) + sizeof(struct pseudo_hdr)); + if ((unsigned long)(c_file) - + (unsigned long)(pFileStart) + >= + (unsigned long)FileLength) { + state = STATE_DONE_FILE; + } + } else { + kfree(pbuffer); + status = STATUS_FAILURE; + } + } else { + status = STATUS_FAILURE; + } + } else { + /* Checksum did not compute */ + status = STATUS_FAILURE; + } + DEBUG + ("ft1000:download: after STATE_SECTION_PROV, state = %d, status= %d\n", + state, status); + break; + + case STATE_DONE_PROV: + DEBUG("FT1000:download:STATE_DONE_PROV\n"); + state = STATE_DONE_FILE; + break; + + default: + status = STATUS_FAILURE; + break; + } /* End Switch */ + + if (status != STATUS_SUCCESS) { + break; + } + +/**** + // Check if Card is present + status = Harley_Read_Register(&temp, FT1000_REG_SUP_IMASK); + if ( (status != NDIS_STATUS_SUCCESS) || (temp == 0x0000) ) { + break; + } + + status = Harley_Read_Register(&temp, FT1000_REG_ASIC_ID); + if ( (status != NDIS_STATUS_SUCCESS) || (temp == 0xffff) ) { + break; + } +****/ + + } /* End while */ + + DEBUG("Download exiting with status = 0x%8x\n", status); + ft1000_write_register(ft1000dev, FT1000_DB_DNLD_TX, + FT1000_REG_DOORBELL); + + return status; +} + diff --git a/drivers/staging/ft1000/ft1000-usb/ft1000_hw.c b/drivers/staging/ft1000/ft1000-usb/ft1000_hw.c new file mode 100644 index 00000000..43b1d363 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-usb/ft1000_hw.c @@ -0,0 +1,1951 @@ +//===================================================== +// CopyRight (C) 2007 Qualcomm Inc. All Rights Reserved. +// +// +// This file is part of Express Card USB Driver +// +// $Id: +//==================================================== +#include +#include +#include +#include +#include +#include +#include "ft1000_usb.h" +#include + +#define HARLEY_READ_REGISTER 0x0 +#define HARLEY_WRITE_REGISTER 0x01 +#define HARLEY_READ_DPRAM_32 0x02 +#define HARLEY_READ_DPRAM_LOW 0x03 +#define HARLEY_READ_DPRAM_HIGH 0x04 +#define HARLEY_WRITE_DPRAM_32 0x05 +#define HARLEY_WRITE_DPRAM_LOW 0x06 +#define HARLEY_WRITE_DPRAM_HIGH 0x07 + +#define HARLEY_READ_OPERATION 0xc1 +#define HARLEY_WRITE_OPERATION 0x41 + +//#define JDEBUG + +static int ft1000_reset(struct net_device *ft1000dev); +static int ft1000_submit_rx_urb(struct ft1000_info *info); +static int ft1000_start_xmit(struct sk_buff *skb, struct net_device *dev); +static int ft1000_open (struct net_device *dev); +static struct net_device_stats *ft1000_netdev_stats(struct net_device *dev); +static int ft1000_chkcard (struct ft1000_device *dev); + +static u8 tempbuffer[1600]; + +#define MAX_RCV_LOOP 100 + +//--------------------------------------------------------------------------- +// Function: ft1000_control +// +// Parameters: ft1000_device - device structure +// pipe - usb control message pipe +// request - control request +// requesttype - control message request type +// value - value to be written or 0 +// index - register index +// data - data buffer to hold the read/write values +// size - data size +// timeout - control message time out value +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function sends a control message via USB interface synchronously +// +// Notes: +// +//--------------------------------------------------------------------------- +static int ft1000_control(struct ft1000_device *ft1000dev, unsigned int pipe, + u8 request, u8 requesttype, u16 value, u16 index, + void *data, u16 size, int timeout) +{ + u16 ret; + + if ((ft1000dev == NULL) || (ft1000dev->dev == NULL)) { + DEBUG("ft1000dev or ft1000dev->dev == NULL, failure\n"); + return -ENODEV; + } + + ret = usb_control_msg(ft1000dev->dev, pipe, request, requesttype, + value, index, data, size, LARGE_TIMEOUT); + + if (ret > 0) + ret = 0; + + return ret; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_read_register +// +// Parameters: ft1000_device - device structure +// Data - data buffer to hold the value read +// nRegIndex - register index +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function returns the value in a register +// +// Notes: +// +//--------------------------------------------------------------------------- + +int ft1000_read_register(struct ft1000_device *ft1000dev, u16* Data, + u16 nRegIndx) +{ + int ret = STATUS_SUCCESS; + + ret = ft1000_control(ft1000dev, + usb_rcvctrlpipe(ft1000dev->dev, 0), + HARLEY_READ_REGISTER, + HARLEY_READ_OPERATION, + 0, + nRegIndx, + Data, + 2, + LARGE_TIMEOUT); + + return ret; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_write_register +// +// Parameters: ft1000_device - device structure +// value - value to write into a register +// nRegIndex - register index +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function writes the value in a register +// +// Notes: +// +//--------------------------------------------------------------------------- +int ft1000_write_register(struct ft1000_device *ft1000dev, u16 value, + u16 nRegIndx) +{ + int ret = STATUS_SUCCESS; + + ret = ft1000_control(ft1000dev, + usb_sndctrlpipe(ft1000dev->dev, 0), + HARLEY_WRITE_REGISTER, + HARLEY_WRITE_OPERATION, + value, + nRegIndx, + NULL, + 0, + LARGE_TIMEOUT); + + return ret; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_read_dpram32 +// +// Parameters: ft1000_device - device structure +// indx - starting address to read +// buffer - data buffer to hold the data read +// cnt - number of byte read from DPRAM +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function read a number of bytes from DPRAM +// +// Notes: +// +//--------------------------------------------------------------------------- + +int ft1000_read_dpram32(struct ft1000_device *ft1000dev, u16 indx, u8 *buffer, + u16 cnt) +{ + int ret = STATUS_SUCCESS; + + ret = ft1000_control(ft1000dev, + usb_rcvctrlpipe(ft1000dev->dev, 0), + HARLEY_READ_DPRAM_32, + HARLEY_READ_OPERATION, + 0, + indx, + buffer, + cnt, + LARGE_TIMEOUT); + + return ret; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_write_dpram32 +// +// Parameters: ft1000_device - device structure +// indx - starting address to write the data +// buffer - data buffer to write into DPRAM +// cnt - number of bytes to write +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function writes into DPRAM a number of bytes +// +// Notes: +// +//--------------------------------------------------------------------------- +int ft1000_write_dpram32(struct ft1000_device *ft1000dev, u16 indx, u8 *buffer, + u16 cnt) +{ + int ret = STATUS_SUCCESS; + + if (cnt % 4) + cnt += cnt - (cnt % 4); + + ret = ft1000_control(ft1000dev, + usb_sndctrlpipe(ft1000dev->dev, 0), + HARLEY_WRITE_DPRAM_32, + HARLEY_WRITE_OPERATION, + 0, + indx, + buffer, + cnt, + LARGE_TIMEOUT); + + return ret; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_read_dpram16 +// +// Parameters: ft1000_device - device structure +// indx - starting address to read +// buffer - data buffer to hold the data read +// hightlow - high or low 16 bit word +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function read 16 bits from DPRAM +// +// Notes: +// +//--------------------------------------------------------------------------- +int ft1000_read_dpram16(struct ft1000_device *ft1000dev, u16 indx, u8 *buffer, + u8 highlow) +{ + int ret = STATUS_SUCCESS; + u8 request; + + if (highlow == 0) + request = HARLEY_READ_DPRAM_LOW; + else + request = HARLEY_READ_DPRAM_HIGH; + + ret = ft1000_control(ft1000dev, + usb_rcvctrlpipe(ft1000dev->dev, 0), + request, + HARLEY_READ_OPERATION, + 0, + indx, + buffer, + 2, + LARGE_TIMEOUT); + + return ret; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_write_dpram16 +// +// Parameters: ft1000_device - device structure +// indx - starting address to write the data +// value - 16bits value to write +// hightlow - high or low 16 bit word +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function writes into DPRAM a number of bytes +// +// Notes: +// +//--------------------------------------------------------------------------- +int ft1000_write_dpram16(struct ft1000_device *ft1000dev, u16 indx, u16 value, u8 highlow) +{ + int ret = STATUS_SUCCESS; + u8 request; + + if (highlow == 0) + request = HARLEY_WRITE_DPRAM_LOW; + else + request = HARLEY_WRITE_DPRAM_HIGH; + + ret = ft1000_control(ft1000dev, + usb_sndctrlpipe(ft1000dev->dev, 0), + request, + HARLEY_WRITE_OPERATION, + value, + indx, + NULL, + 0, + LARGE_TIMEOUT); + + return ret; +} + +//--------------------------------------------------------------------------- +// Function: fix_ft1000_read_dpram32 +// +// Parameters: ft1000_device - device structure +// indx - starting address to read +// buffer - data buffer to hold the data read +// +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function read DPRAM 4 words at a time +// +// Notes: +// +//--------------------------------------------------------------------------- +int fix_ft1000_read_dpram32(struct ft1000_device *ft1000dev, u16 indx, + u8 *buffer) +{ + u8 buf[16]; + u16 pos; + int ret = STATUS_SUCCESS; + + pos = (indx / 4) * 4; + ret = ft1000_read_dpram32(ft1000dev, pos, buf, 16); + + if (ret == STATUS_SUCCESS) { + pos = (indx % 4) * 4; + *buffer++ = buf[pos++]; + *buffer++ = buf[pos++]; + *buffer++ = buf[pos++]; + *buffer++ = buf[pos++]; + } else { + DEBUG("fix_ft1000_read_dpram32: DPRAM32 Read failed\n"); + *buffer++ = 0; + *buffer++ = 0; + *buffer++ = 0; + *buffer++ = 0; + } + + return ret; +} + + +//--------------------------------------------------------------------------- +// Function: fix_ft1000_write_dpram32 +// +// Parameters: ft1000_device - device structure +// indx - starting address to write +// buffer - data buffer to write +// +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function write to DPRAM 4 words at a time +// +// Notes: +// +//--------------------------------------------------------------------------- +int fix_ft1000_write_dpram32(struct ft1000_device *ft1000dev, u16 indx, u8 *buffer) +{ + u16 pos1; + u16 pos2; + u16 i; + u8 buf[32]; + u8 resultbuffer[32]; + u8 *pdata; + int ret = STATUS_SUCCESS; + + pos1 = (indx / 4) * 4; + pdata = buffer; + ret = ft1000_read_dpram32(ft1000dev, pos1, buf, 16); + + if (ret == STATUS_SUCCESS) { + pos2 = (indx % 4)*4; + buf[pos2++] = *buffer++; + buf[pos2++] = *buffer++; + buf[pos2++] = *buffer++; + buf[pos2++] = *buffer++; + ret = ft1000_write_dpram32(ft1000dev, pos1, buf, 16); + } else { + DEBUG("fix_ft1000_write_dpram32: DPRAM32 Read failed\n"); + return ret; + } + + ret = ft1000_read_dpram32(ft1000dev, pos1, (u8 *)&resultbuffer[0], 16); + + if (ret == STATUS_SUCCESS) { + buffer = pdata; + for (i = 0; i < 16; i++) { + if (buf[i] != resultbuffer[i]) + ret = STATUS_FAILURE; + } + } + + if (ret == STATUS_FAILURE) { + ret = ft1000_write_dpram32(ft1000dev, pos1, + (u8 *)&tempbuffer[0], 16); + ret = ft1000_read_dpram32(ft1000dev, pos1, + (u8 *)&resultbuffer[0], 16); + if (ret == STATUS_SUCCESS) { + buffer = pdata; + for (i = 0; i < 16; i++) { + if (tempbuffer[i] != resultbuffer[i]) { + ret = STATUS_FAILURE; + DEBUG("%s Failed to write\n", + __func__); + } + } + } + } + + return ret; +} + + +//------------------------------------------------------------------------ +// +// Function: card_reset_dsp +// +// Synopsis: This function is called to reset or activate the DSP +// +// Arguments: value - reset or activate +// +// Returns: None +//----------------------------------------------------------------------- +static void card_reset_dsp(struct ft1000_device *ft1000dev, bool value) +{ + u16 status = STATUS_SUCCESS; + u16 tempword; + + status = ft1000_write_register(ft1000dev, HOST_INTF_BE, + FT1000_REG_SUP_CTRL); + status = ft1000_read_register(ft1000dev, &tempword, + FT1000_REG_SUP_CTRL); + + if (value) { + DEBUG("Reset DSP\n"); + status = ft1000_read_register(ft1000dev, &tempword, + FT1000_REG_RESET); + tempword |= DSP_RESET_BIT; + status = ft1000_write_register(ft1000dev, tempword, + FT1000_REG_RESET); + } else { + DEBUG("Activate DSP\n"); + status = ft1000_read_register(ft1000dev, &tempword, + FT1000_REG_RESET); + tempword |= DSP_ENCRYPTED; + tempword &= ~DSP_UNENCRYPTED; + status = ft1000_write_register(ft1000dev, tempword, + FT1000_REG_RESET); + status = ft1000_read_register(ft1000dev, &tempword, + FT1000_REG_RESET); + tempword &= ~EFUSE_MEM_DISABLE; + tempword &= ~DSP_RESET_BIT; + status = ft1000_write_register(ft1000dev, tempword, + FT1000_REG_RESET); + status = ft1000_read_register(ft1000dev, &tempword, + FT1000_REG_RESET); + } +} + +//--------------------------------------------------------------------------- +// Function: card_send_command +// +// Parameters: ft1000_device - device structure +// ptempbuffer - command buffer +// size - command buffer size +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function sends a command to ASIC +// +// Notes: +// +//--------------------------------------------------------------------------- +void card_send_command(struct ft1000_device *ft1000dev, void *ptempbuffer, + int size) +{ + unsigned short temp; + unsigned char *commandbuf; + + DEBUG("card_send_command: enter card_send_command... size=%d\n", size); + + commandbuf = kmalloc(size + 2, GFP_KERNEL); + memcpy((void *)commandbuf + 2, (void *)ptempbuffer, size); + + ft1000_read_register(ft1000dev, &temp, FT1000_REG_DOORBELL); + + if (temp & 0x0100) + msleep(10); + + /* check for odd word */ + size = size + 2; + + /* Must force to be 32 bit aligned */ + if (size % 4) + size += 4 - (size % 4); + + ft1000_write_dpram32(ft1000dev, 0, commandbuf, size); + msleep(1); + ft1000_write_register(ft1000dev, FT1000_DB_DPRAM_TX, + FT1000_REG_DOORBELL); + msleep(1); + + ft1000_read_register(ft1000dev, &temp, FT1000_REG_DOORBELL); + + if ((temp & 0x0100) == 0) { + //DEBUG("card_send_command: Message sent\n"); + } + +} + +//-------------------------------------------------------------------------- +// +// Function: dsp_reload +// +// Synopsis: This function is called to load or reload the DSP +// +// Arguments: ft1000dev - device structure +// +// Returns: None +//----------------------------------------------------------------------- +int dsp_reload(struct ft1000_device *ft1000dev) +{ + u16 status; + u16 tempword; + u32 templong; + + struct ft1000_info *pft1000info; + + pft1000info = netdev_priv(ft1000dev->net); + + pft1000info->CardReady = 0; + + /* Program Interrupt Mask register */ + status = ft1000_write_register(ft1000dev, 0xffff, FT1000_REG_SUP_IMASK); + + status = ft1000_read_register(ft1000dev, &tempword, FT1000_REG_RESET); + tempword |= ASIC_RESET_BIT; + status = ft1000_write_register(ft1000dev, tempword, FT1000_REG_RESET); + msleep(1000); + status = ft1000_read_register(ft1000dev, &tempword, FT1000_REG_RESET); + DEBUG("Reset Register = 0x%x\n", tempword); + + /* Toggle DSP reset */ + card_reset_dsp(ft1000dev, 1); + msleep(1000); + card_reset_dsp(ft1000dev, 0); + msleep(1000); + + status = + ft1000_write_register(ft1000dev, HOST_INTF_BE, FT1000_REG_SUP_CTRL); + + /* Let's check for FEFE */ + status = + ft1000_read_dpram32(ft1000dev, FT1000_MAG_DPRAM_FEFE_INDX, + (u8 *) &templong, 4); + DEBUG("templong (fefe) = 0x%8x\n", templong); + + /* call codeloader */ + status = scram_dnldr(ft1000dev, pFileStart, FileLength); + + if (status != STATUS_SUCCESS) + return -EIO; + + msleep(1000); + + DEBUG("dsp_reload returned\n"); + + return 0; +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_reset_asic +// Description: This function will call the Card Service function to reset the +// ASIC. +// Input: +// dev - device structure +// Output: +// none +// +//--------------------------------------------------------------------------- +static void ft1000_reset_asic(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + struct ft1000_device *ft1000dev = info->pFt1000Dev; + u16 tempword; + + DEBUG("ft1000_hw:ft1000_reset_asic called\n"); + + /* Let's use the register provided by the Magnemite ASIC to reset the + * ASIC and DSP. + */ + ft1000_write_register(ft1000dev, (DSP_RESET_BIT | ASIC_RESET_BIT), + FT1000_REG_RESET); + + mdelay(1); + + /* set watermark to -1 in order to not generate an interrupt */ + ft1000_write_register(ft1000dev, 0xffff, FT1000_REG_MAG_WATERMARK); + + /* clear interrupts */ + ft1000_read_register(ft1000dev, &tempword, FT1000_REG_SUP_ISR); + DEBUG("ft1000_hw: interrupt status register = 0x%x\n", tempword); + ft1000_write_register(ft1000dev, tempword, FT1000_REG_SUP_ISR); + ft1000_read_register(ft1000dev, &tempword, FT1000_REG_SUP_ISR); + DEBUG("ft1000_hw: interrupt status register = 0x%x\n", tempword); +} + + +//--------------------------------------------------------------------------- +// +// Function: ft1000_reset_card +// Description: This function will reset the card +// Input: +// dev - device structure +// Output: +// status - FALSE (card reset fail) +// TRUE (card reset successful) +// +//--------------------------------------------------------------------------- +static int ft1000_reset_card(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + struct ft1000_device *ft1000dev = info->pFt1000Dev; + u16 tempword; + struct prov_record *ptr; + + DEBUG("ft1000_hw:ft1000_reset_card called.....\n"); + + info->fCondResetPend = 1; + info->CardReady = 0; + info->fProvComplete = 0; + + /* Make sure we free any memory reserve for provisioning */ + while (list_empty(&info->prov_list) == 0) { + DEBUG("ft1000_reset_card:deleting provisioning record\n"); + ptr = + list_entry(info->prov_list.next, struct prov_record, list); + list_del(&ptr->list); + kfree(ptr->pprov_data); + kfree(ptr); + } + + DEBUG("ft1000_hw:ft1000_reset_card: reset asic\n"); + ft1000_reset_asic(dev); + + DEBUG("ft1000_hw:ft1000_reset_card: call dsp_reload\n"); + dsp_reload(ft1000dev); + + DEBUG("dsp reload successful\n"); + + mdelay(10); + + /* Initialize DSP heartbeat area */ + ft1000_write_dpram16(ft1000dev, FT1000_MAG_HI_HO, ho_mag, + FT1000_MAG_HI_HO_INDX); + ft1000_read_dpram16(ft1000dev, FT1000_MAG_HI_HO, (u8 *) &tempword, + FT1000_MAG_HI_HO_INDX); + DEBUG("ft1000_hw:ft1000_reset_card:hi_ho value = 0x%x\n", tempword); + + info->CardReady = 1; + + info->fCondResetPend = 0; + + return TRUE; +} + +static const struct net_device_ops ftnet_ops = +{ + .ndo_open = &ft1000_open, + .ndo_stop = &ft1000_close, + .ndo_start_xmit = &ft1000_start_xmit, + .ndo_get_stats = &ft1000_netdev_stats, +}; + + +//--------------------------------------------------------------------------- +// Function: init_ft1000_netdev +// +// Parameters: ft1000dev - device structure +// +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function initialize the network device +// +// Notes: +// +//--------------------------------------------------------------------------- +int init_ft1000_netdev(struct ft1000_device *ft1000dev) +{ + struct net_device *netdev; + struct ft1000_info *pInfo = NULL; + struct dpram_blk *pdpram_blk; + int i, ret_val; + struct list_head *cur, *tmp; + char card_nr[2]; + unsigned long gCardIndex = 0; + + DEBUG("Enter init_ft1000_netdev...\n"); + + netdev = alloc_etherdev(sizeof(struct ft1000_info)); + if (!netdev) { + DEBUG("init_ft1000_netdev: can not allocate network device\n"); + return -ENOMEM; + } + + pInfo = netdev_priv(netdev); + + memset(pInfo, 0, sizeof(struct ft1000_info)); + + dev_alloc_name(netdev, netdev->name); + + DEBUG("init_ft1000_netdev: network device name is %s\n", netdev->name); + + if (strncmp(netdev->name, "eth", 3) == 0) { + card_nr[0] = netdev->name[3]; + card_nr[1] = '\0'; + ret_val = strict_strtoul(card_nr, 10, &gCardIndex); + if (ret_val) { + printk(KERN_ERR "Can't parse netdev\n"); + goto err_net; + } + + pInfo->CardNumber = gCardIndex; + DEBUG("card number = %d\n", pInfo->CardNumber); + } else { + printk(KERN_ERR "ft1000: Invalid device name\n"); + ret_val = -ENXIO; + goto err_net; + } + + memset(&pInfo->stats, 0, sizeof(struct net_device_stats)); + + spin_lock_init(&pInfo->dpram_lock); + pInfo->pFt1000Dev = ft1000dev; + pInfo->DrvErrNum = 0; + pInfo->registered = 1; + pInfo->ft1000_reset = ft1000_reset; + pInfo->mediastate = 0; + pInfo->fifo_cnt = 0; + pInfo->DeviceCreated = FALSE; + pInfo->CardReady = 0; + pInfo->DSP_TIME[0] = 0; + pInfo->DSP_TIME[1] = 0; + pInfo->DSP_TIME[2] = 0; + pInfo->DSP_TIME[3] = 0; + pInfo->fAppMsgPend = 0; + pInfo->fCondResetPend = 0; + pInfo->usbboot = 0; + pInfo->dspalive = 0; + memset(&pInfo->tempbuf[0], 0, sizeof(pInfo->tempbuf)); + + INIT_LIST_HEAD(&pInfo->prov_list); + + INIT_LIST_HEAD(&pInfo->nodes.list); + + netdev->netdev_ops = &ftnet_ops; + + ft1000dev->net = netdev; + + DEBUG("Initialize free_buff_lock and freercvpool\n"); + spin_lock_init(&free_buff_lock); + + /* initialize a list of buffers to be use for queuing + * up receive command data + */ + INIT_LIST_HEAD(&freercvpool); + + /* create list of free buffers */ + for (i = 0; i < NUM_OF_FREE_BUFFERS; i++) { + /* Get memory for DPRAM_DATA link list */ + pdpram_blk = kmalloc(sizeof(struct dpram_blk), GFP_KERNEL); + if (pdpram_blk == NULL) { + ret_val = -ENOMEM; + goto err_free; + } + /* Get a block of memory to store command data */ + pdpram_blk->pbuffer = kmalloc(MAX_CMD_SQSIZE, GFP_KERNEL); + if (pdpram_blk->pbuffer == NULL) { + ret_val = -ENOMEM; + kfree(pdpram_blk); + goto err_free; + } + /* link provisioning data */ + list_add_tail(&pdpram_blk->list, &freercvpool); + } + numofmsgbuf = NUM_OF_FREE_BUFFERS; + + return 0; + +err_free: + list_for_each_safe(cur, tmp, &freercvpool) { + pdpram_blk = list_entry(cur, struct dpram_blk, list); + list_del(&pdpram_blk->list); + kfree(pdpram_blk->pbuffer); + kfree(pdpram_blk); + } +err_net: + free_netdev(netdev); + return ret_val; +} + +//--------------------------------------------------------------------------- +// Function: reg_ft1000_netdev +// +// Parameters: ft1000dev - device structure +// +// +// Returns: STATUS_SUCCESS - success +// STATUS_FAILURE - failure +// +// Description: This function register the network driver +// +// Notes: +// +//--------------------------------------------------------------------------- +int reg_ft1000_netdev(struct ft1000_device *ft1000dev, + struct usb_interface *intf) +{ + struct net_device *netdev; + struct ft1000_info *pInfo; + int rc; + + netdev = ft1000dev->net; + pInfo = netdev_priv(ft1000dev->net); + DEBUG("Enter reg_ft1000_netdev...\n"); + + ft1000_read_register(ft1000dev, &pInfo->AsicID, FT1000_REG_ASIC_ID); + + usb_set_intfdata(intf, pInfo); + SET_NETDEV_DEV(netdev, &intf->dev); + + rc = register_netdev(netdev); + if (rc) { + DEBUG("reg_ft1000_netdev: could not register network device\n"); + free_netdev(netdev); + return rc; + } + + ft1000_create_dev(ft1000dev); + + DEBUG("reg_ft1000_netdev returned\n"); + + pInfo->CardReady = 1; + + return 0; +} + +static int ft1000_reset(struct net_device *dev) +{ + ft1000_reset_card(dev); + return 0; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_usb_transmit_complete +// +// Parameters: urb - transmitted usb urb +// +// +// Returns: none +// +// Description: This is the callback function when a urb is transmitted +// +// Notes: +// +//--------------------------------------------------------------------------- +static void ft1000_usb_transmit_complete(struct urb *urb) +{ + + struct ft1000_device *ft1000dev = urb->context; + + if (urb->status) + pr_err("%s: TX status %d\n", ft1000dev->net->name, urb->status); + + netif_wake_queue(ft1000dev->net); +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_copy_down_pkt +// Description: This function will take an ethernet packet and convert it to +// a Flarion packet prior to sending it to the ASIC Downlink +// FIFO. +// Input: +// dev - device structure +// packet - address of ethernet packet +// len - length of IP packet +// Output: +// status - FAILURE +// SUCCESS +// +//--------------------------------------------------------------------------- +static int ft1000_copy_down_pkt(struct net_device *netdev, u8 * packet, u16 len) +{ + struct ft1000_info *pInfo = netdev_priv(netdev); + struct ft1000_device *pFt1000Dev = pInfo->pFt1000Dev; + + int count, ret; + u8 *t; + struct pseudo_hdr hdr; + + if (!pInfo->CardReady) { + DEBUG("ft1000_copy_down_pkt::Card Not Ready\n"); + return -ENODEV; + } + + count = sizeof(struct pseudo_hdr) + len; + if (count > MAX_BUF_SIZE) { + DEBUG("Error:ft1000_copy_down_pkt:Message Size Overflow!\n"); + DEBUG("size = %d\n", count); + return -EINVAL; + } + + if (count % 4) + count = count + (4 - (count % 4)); + + memset(&hdr, 0, sizeof(struct pseudo_hdr)); + + hdr.length = ntohs(count); + hdr.source = 0x10; + hdr.destination = 0x20; + hdr.portdest = 0x20; + hdr.portsrc = 0x10; + hdr.sh_str_id = 0x91; + hdr.control = 0x00; + + hdr.checksum = hdr.length ^ hdr.source ^ hdr.destination ^ + hdr.portdest ^ hdr.portsrc ^ hdr.sh_str_id ^ hdr.control; + + memcpy(&pFt1000Dev->tx_buf[0], &hdr, sizeof(hdr)); + memcpy(&(pFt1000Dev->tx_buf[sizeof(struct pseudo_hdr)]), packet, len); + + netif_stop_queue(netdev); + + usb_fill_bulk_urb(pFt1000Dev->tx_urb, + pFt1000Dev->dev, + usb_sndbulkpipe(pFt1000Dev->dev, + pFt1000Dev->bulk_out_endpointAddr), + pFt1000Dev->tx_buf, count, + ft1000_usb_transmit_complete, (void *)pFt1000Dev); + + t = (u8 *) pFt1000Dev->tx_urb->transfer_buffer; + + ret = usb_submit_urb(pFt1000Dev->tx_urb, GFP_ATOMIC); + + if (ret) { + DEBUG("ft1000 failed tx_urb %d\n", ret); + return ret; + } else { + pInfo->stats.tx_packets++; + pInfo->stats.tx_bytes += (len + 14); + } + + return 0; +} + + +//--------------------------------------------------------------------------- +// Function: ft1000_start_xmit +// +// Parameters: skb - socket buffer to be sent +// dev - network device +// +// +// Returns: none +// +// Description: transmit a ethernet packet +// +// Notes: +// +//--------------------------------------------------------------------------- +static int ft1000_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ft1000_info *pInfo = netdev_priv(dev); + struct ft1000_device *pFt1000Dev = pInfo->pFt1000Dev; + u8 *pdata; + int maxlen, pipe; + + if (skb == NULL) { + DEBUG("ft1000_hw: ft1000_start_xmit:skb == NULL!!!\n"); + return NETDEV_TX_OK; + } + + if (pFt1000Dev->status & FT1000_STATUS_CLOSING) { + DEBUG("network driver is closed, return\n"); + goto err; + } + + pipe = + usb_sndbulkpipe(pFt1000Dev->dev, pFt1000Dev->bulk_out_endpointAddr); + maxlen = usb_maxpacket(pFt1000Dev->dev, pipe, usb_pipeout(pipe)); + + pdata = (u8 *) skb->data; + + if (pInfo->mediastate == 0) { + /* Drop packet is mediastate is down */ + DEBUG("ft1000_hw:ft1000_start_xmit:mediastate is down\n"); + goto err; + } + + if ((skb->len < ENET_HEADER_SIZE) || (skb->len > ENET_MAX_SIZE)) { + /* Drop packet which has invalid size */ + DEBUG("ft1000_hw:ft1000_start_xmit:invalid ethernet length\n"); + goto err; + } + + ft1000_copy_down_pkt(dev, (pdata + ENET_HEADER_SIZE - 2), + skb->len - ENET_HEADER_SIZE + 2); + +err: + dev_kfree_skb(skb); + + return NETDEV_TX_OK; +} + + +//--------------------------------------------------------------------------- +// +// Function: ft1000_copy_up_pkt +// Description: This function will take a packet from the FIFO up link and +// convert it into an ethernet packet and deliver it to the IP stack +// Input: +// urb - the receiving usb urb +// +// Output: +// status - FAILURE +// SUCCESS +// +//--------------------------------------------------------------------------- +static int ft1000_copy_up_pkt(struct urb *urb) +{ + struct ft1000_info *info = urb->context; + struct ft1000_device *ft1000dev = info->pFt1000Dev; + struct net_device *net = ft1000dev->net; + + u16 tempword; + u16 len; + u16 lena; + struct sk_buff *skb; + u16 i; + u8 *pbuffer = NULL; + u8 *ptemp = NULL; + u16 *chksum; + + if (ft1000dev->status & FT1000_STATUS_CLOSING) { + DEBUG("network driver is closed, return\n"); + return STATUS_SUCCESS; + } + // Read length + len = urb->transfer_buffer_length; + lena = urb->actual_length; + + chksum = (u16 *) ft1000dev->rx_buf; + + tempword = *chksum++; + for (i = 1; i < 7; i++) + tempword ^= *chksum++; + + if (tempword != *chksum) { + info->stats.rx_errors++; + ft1000_submit_rx_urb(info); + return STATUS_FAILURE; + } + + skb = dev_alloc_skb(len + 12 + 2); + + if (skb == NULL) { + DEBUG("ft1000_copy_up_pkt: No Network buffers available\n"); + info->stats.rx_errors++; + ft1000_submit_rx_urb(info); + return STATUS_FAILURE; + } + + pbuffer = (u8 *) skb_put(skb, len + 12); + + /* subtract the number of bytes read already */ + ptemp = pbuffer; + + /* fake MAC address */ + *pbuffer++ = net->dev_addr[0]; + *pbuffer++ = net->dev_addr[1]; + *pbuffer++ = net->dev_addr[2]; + *pbuffer++ = net->dev_addr[3]; + *pbuffer++ = net->dev_addr[4]; + *pbuffer++ = net->dev_addr[5]; + *pbuffer++ = 0x00; + *pbuffer++ = 0x07; + *pbuffer++ = 0x35; + *pbuffer++ = 0xff; + *pbuffer++ = 0xff; + *pbuffer++ = 0xfe; + + memcpy(pbuffer, ft1000dev->rx_buf + sizeof(struct pseudo_hdr), + len - sizeof(struct pseudo_hdr)); + + skb->dev = net; + + skb->protocol = eth_type_trans(skb, net); + skb->ip_summed = CHECKSUM_UNNECESSARY; + netif_rx(skb); + + info->stats.rx_packets++; + /* Add on 12 bytes for MAC address which was removed */ + info->stats.rx_bytes += (lena + 12); + + ft1000_submit_rx_urb(info); + + return SUCCESS; +} + + +//--------------------------------------------------------------------------- +// +// Function: ft1000_submit_rx_urb +// Description: the receiving function of the network driver +// +// Input: +// info - a private structure contains the device information +// +// Output: +// status - FAILURE +// SUCCESS +// +//--------------------------------------------------------------------------- +static int ft1000_submit_rx_urb(struct ft1000_info *info) +{ + int result; + struct ft1000_device *pFt1000Dev = info->pFt1000Dev; + + if (pFt1000Dev->status & FT1000_STATUS_CLOSING) { + DEBUG("network driver is closed, return\n"); + return -ENODEV; + } + + usb_fill_bulk_urb(pFt1000Dev->rx_urb, + pFt1000Dev->dev, + usb_rcvbulkpipe(pFt1000Dev->dev, + pFt1000Dev->bulk_in_endpointAddr), + pFt1000Dev->rx_buf, MAX_BUF_SIZE, + (usb_complete_t) ft1000_copy_up_pkt, info); + + result = usb_submit_urb(pFt1000Dev->rx_urb, GFP_ATOMIC); + + if (result) { + pr_err("ft1000_submit_rx_urb: submitting rx_urb %d failed\n", + result); + return result; + } + + return 0; +} + + +//--------------------------------------------------------------------------- +// Function: ft1000_open +// +// Parameters: +// dev - network device +// +// +// Returns: none +// +// Description: open the network driver +// +// Notes: +// +//--------------------------------------------------------------------------- +static int ft1000_open(struct net_device *dev) +{ + struct ft1000_info *pInfo = netdev_priv(dev); + struct timeval tv; + int ret; + + DEBUG("ft1000_open is called for card %d\n", pInfo->CardNumber); + + pInfo->stats.rx_bytes = 0; + pInfo->stats.tx_bytes = 0; + pInfo->stats.rx_packets = 0; + pInfo->stats.tx_packets = 0; + do_gettimeofday(&tv); + pInfo->ConTm = tv.tv_sec; + pInfo->ProgConStat = 0; + + netif_start_queue(dev); + + netif_carrier_on(dev); + + ret = ft1000_submit_rx_urb(pInfo); + + return ret; +} + +//--------------------------------------------------------------------------- +// Function: ft1000_close +// +// Parameters: +// net - network device +// +// +// Returns: none +// +// Description: close the network driver +// +// Notes: +// +//--------------------------------------------------------------------------- +int ft1000_close(struct net_device *net) +{ + struct ft1000_info *pInfo = netdev_priv(net); + struct ft1000_device *ft1000dev = pInfo->pFt1000Dev; + + ft1000dev->status |= FT1000_STATUS_CLOSING; + + DEBUG("ft1000_close: pInfo=%p, ft1000dev=%p\n", pInfo, ft1000dev); + netif_carrier_off(net); + netif_stop_queue(net); + ft1000dev->status &= ~FT1000_STATUS_CLOSING; + + pInfo->ProgConStat = 0xff; + + return 0; +} + +static struct net_device_stats *ft1000_netdev_stats(struct net_device *dev) +{ + struct ft1000_info *info = netdev_priv(dev); + + return &(info->stats); +} + + +//--------------------------------------------------------------------------- +// +// Function: ft1000_chkcard +// Description: This function will check if the device is presently available on +// the system. +// Input: +// dev - device structure +// Output: +// status - FALSE (device is not present) +// TRUE (device is present) +// +//--------------------------------------------------------------------------- +static int ft1000_chkcard(struct ft1000_device *dev) +{ + u16 tempword; + u16 status; + struct ft1000_info *info = netdev_priv(dev->net); + + if (info->fCondResetPend) { + DEBUG + ("ft1000_hw:ft1000_chkcard:Card is being reset, return FALSE\n"); + return TRUE; + } + /* Mask register is used to check for device presence since it is never + * set to zero. + */ + status = ft1000_read_register(dev, &tempword, FT1000_REG_SUP_IMASK); + if (tempword == 0) { + DEBUG + ("ft1000_hw:ft1000_chkcard: IMASK = 0 Card not detected\n"); + return FALSE; + } + /* The system will return the value of 0xffff for the version register + * if the device is not present. + */ + status = ft1000_read_register(dev, &tempword, FT1000_REG_ASIC_ID); + if (tempword != 0x1b01) { + dev->status |= FT1000_STATUS_CLOSING; + DEBUG + ("ft1000_hw:ft1000_chkcard: Version = 0xffff Card not detected\n"); + return FALSE; + } + return TRUE; +} + +//--------------------------------------------------------------------------- +// +// Function: ft1000_receive_cmd +// Description: This function will read a message from the dpram area. +// Input: +// dev - network device structure +// pbuffer - caller supply address to buffer +// pnxtph - pointer to next pseudo header +// Output: +// Status = 0 (unsuccessful) +// = 1 (successful) +// +//--------------------------------------------------------------------------- +static bool ft1000_receive_cmd(struct ft1000_device *dev, u16 *pbuffer, + int maxsz, u16 *pnxtph) +{ + u16 size, ret; + u16 *ppseudohdr; + int i; + u16 tempword; + + ret = + ft1000_read_dpram16(dev, FT1000_MAG_PH_LEN, (u8 *) &size, + FT1000_MAG_PH_LEN_INDX); + size = ntohs(size) + PSEUDOSZ; + if (size > maxsz) { + DEBUG("FT1000:ft1000_receive_cmd:Invalid command length = %d\n", + size); + return FALSE; + } else { + ppseudohdr = (u16 *) pbuffer; + ft1000_write_register(dev, FT1000_DPRAM_MAG_RX_BASE, + FT1000_REG_DPRAM_ADDR); + ret = + ft1000_read_register(dev, pbuffer, FT1000_REG_MAG_DPDATAH); + pbuffer++; + ft1000_write_register(dev, FT1000_DPRAM_MAG_RX_BASE + 1, + FT1000_REG_DPRAM_ADDR); + for (i = 0; i <= (size >> 2); i++) { + ret = + ft1000_read_register(dev, pbuffer, + FT1000_REG_MAG_DPDATAL); + pbuffer++; + ret = + ft1000_read_register(dev, pbuffer, + FT1000_REG_MAG_DPDATAH); + pbuffer++; + } + /* copy odd aligned word */ + ret = + ft1000_read_register(dev, pbuffer, FT1000_REG_MAG_DPDATAL); + + pbuffer++; + ret = + ft1000_read_register(dev, pbuffer, FT1000_REG_MAG_DPDATAH); + + pbuffer++; + if (size & 0x0001) { + /* copy odd byte from fifo */ + ret = + ft1000_read_register(dev, &tempword, + FT1000_REG_DPRAM_DATA); + *pbuffer = ntohs(tempword); + } + /* Check if pseudo header checksum is good + * Calculate pseudo header checksum + */ + tempword = *ppseudohdr++; + for (i = 1; i < 7; i++) + tempword ^= *ppseudohdr++; + + if ((tempword != *ppseudohdr)) + return FALSE; + + return TRUE; + } +} + +static int ft1000_dsp_prov(void *arg) +{ + struct ft1000_device *dev = (struct ft1000_device *)arg; + struct ft1000_info *info = netdev_priv(dev->net); + u16 tempword; + u16 len; + u16 i = 0; + struct prov_record *ptr; + struct pseudo_hdr *ppseudo_hdr; + u16 *pmsg; + u16 status; + u16 TempShortBuf[256]; + + DEBUG("*** DspProv Entered\n"); + + while (list_empty(&info->prov_list) == 0) { + DEBUG("DSP Provisioning List Entry\n"); + + /* Check if doorbell is available */ + DEBUG("check if doorbell is cleared\n"); + status = + ft1000_read_register(dev, &tempword, FT1000_REG_DOORBELL); + if (status) { + DEBUG("ft1000_dsp_prov::ft1000_read_register error\n"); + break; + } + + while (tempword & FT1000_DB_DPRAM_TX) { + mdelay(10); + i++; + if (i == 10) { + DEBUG("FT1000:ft1000_dsp_prov:message drop\n"); + return STATUS_FAILURE; + } + ft1000_read_register(dev, &tempword, + FT1000_REG_DOORBELL); + } + + if (!(tempword & FT1000_DB_DPRAM_TX)) { + DEBUG("*** Provision Data Sent to DSP\n"); + + /* Send provisioning data */ + ptr = + list_entry(info->prov_list.next, struct prov_record, + list); + len = *(u16 *) ptr->pprov_data; + len = htons(len); + len += PSEUDOSZ; + + pmsg = (u16 *) ptr->pprov_data; + ppseudo_hdr = (struct pseudo_hdr *)pmsg; + /* Insert slow queue sequence number */ + ppseudo_hdr->seq_num = info->squeseqnum++; + ppseudo_hdr->portsrc = 0; + /* Calculate new checksum */ + ppseudo_hdr->checksum = *pmsg++; + for (i = 1; i < 7; i++) { + ppseudo_hdr->checksum ^= *pmsg++; + } + + TempShortBuf[0] = 0; + TempShortBuf[1] = htons(len); + memcpy(&TempShortBuf[2], ppseudo_hdr, len); + + status = + ft1000_write_dpram32(dev, 0, + (u8 *) &TempShortBuf[0], + (unsigned short)(len + 2)); + status = + ft1000_write_register(dev, FT1000_DB_DPRAM_TX, + FT1000_REG_DOORBELL); + + list_del(&ptr->list); + kfree(ptr->pprov_data); + kfree(ptr); + } + msleep(10); + } + + DEBUG("DSP Provisioning List Entry finished\n"); + + msleep(100); + + info->fProvComplete = 1; + info->CardReady = 1; + + return STATUS_SUCCESS; +} + +static int ft1000_proc_drvmsg(struct ft1000_device *dev, u16 size) +{ + struct ft1000_info *info = netdev_priv(dev->net); + u16 msgtype; + u16 tempword; + struct media_msg *pmediamsg; + struct dsp_init_msg *pdspinitmsg; + struct drv_msg *pdrvmsg; + u16 i; + struct pseudo_hdr *ppseudo_hdr; + u16 *pmsg; + u16 status; + union { + u8 byte[2]; + u16 wrd; + } convert; + + char *cmdbuffer = kmalloc(1600, GFP_KERNEL); + if (!cmdbuffer) + return STATUS_FAILURE; + + status = ft1000_read_dpram32(dev, 0x200, cmdbuffer, size); + +#ifdef JDEBUG + DEBUG("ft1000_proc_drvmsg:cmdbuffer\n"); + for (i = 0; i < size; i += 5) { + if ((i + 5) < size) + DEBUG("0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", cmdbuffer[i], + cmdbuffer[i + 1], cmdbuffer[i + 2], + cmdbuffer[i + 3], cmdbuffer[i + 4]); + else { + for (j = i; j < size; j++) + DEBUG("0x%x ", cmdbuffer[j]); + DEBUG("\n"); + break; + } + } +#endif + pdrvmsg = (struct drv_msg *)&cmdbuffer[2]; + msgtype = ntohs(pdrvmsg->type); + DEBUG("ft1000_proc_drvmsg:Command message type = 0x%x\n", msgtype); + switch (msgtype) { + case MEDIA_STATE:{ + DEBUG + ("ft1000_proc_drvmsg:Command message type = MEDIA_STATE"); + + pmediamsg = (struct media_msg *)&cmdbuffer[0]; + if (info->ProgConStat != 0xFF) { + if (pmediamsg->state) { + DEBUG("Media is up\n"); + if (info->mediastate == 0) { + if (info->NetDevRegDone) { + netif_wake_queue(dev-> + net); + } + info->mediastate = 1; + } + } else { + DEBUG("Media is down\n"); + if (info->mediastate == 1) { + info->mediastate = 0; + if (info->NetDevRegDone) { + } + info->ConTm = 0; + } + } + } else { + DEBUG("Media is down\n"); + if (info->mediastate == 1) { + info->mediastate = 0; + info->ConTm = 0; + } + } + break; + } + case DSP_INIT_MSG:{ + DEBUG + ("ft1000_proc_drvmsg:Command message type = DSP_INIT_MSG"); + + pdspinitmsg = (struct dsp_init_msg *)&cmdbuffer[2]; + memcpy(info->DspVer, pdspinitmsg->DspVer, DSPVERSZ); + DEBUG("DSPVER = 0x%2x 0x%2x 0x%2x 0x%2x\n", + info->DspVer[0], info->DspVer[1], info->DspVer[2], + info->DspVer[3]); + memcpy(info->HwSerNum, pdspinitmsg->HwSerNum, + HWSERNUMSZ); + memcpy(info->Sku, pdspinitmsg->Sku, SKUSZ); + memcpy(info->eui64, pdspinitmsg->eui64, EUISZ); + DEBUG("EUI64=%2x.%2x.%2x.%2x.%2x.%2x.%2x.%2x\n", + info->eui64[0], info->eui64[1], info->eui64[2], + info->eui64[3], info->eui64[4], info->eui64[5], + info->eui64[6], info->eui64[7]); + dev->net->dev_addr[0] = info->eui64[0]; + dev->net->dev_addr[1] = info->eui64[1]; + dev->net->dev_addr[2] = info->eui64[2]; + dev->net->dev_addr[3] = info->eui64[5]; + dev->net->dev_addr[4] = info->eui64[6]; + dev->net->dev_addr[5] = info->eui64[7]; + + if (ntohs(pdspinitmsg->length) == + (sizeof(struct dsp_init_msg) - 20)) { + memcpy(info->ProductMode, + pdspinitmsg->ProductMode, MODESZ); + memcpy(info->RfCalVer, pdspinitmsg->RfCalVer, + CALVERSZ); + memcpy(info->RfCalDate, pdspinitmsg->RfCalDate, + CALDATESZ); + DEBUG("RFCalVer = 0x%2x 0x%2x\n", + info->RfCalVer[0], info->RfCalVer[1]); + } + break; + } + case DSP_PROVISION:{ + DEBUG + ("ft1000_proc_drvmsg:Command message type = DSP_PROVISION\n"); + + /* kick off dspprov routine to start provisioning + * Send provisioning data to DSP + */ + if (list_empty(&info->prov_list) == 0) { + info->fProvComplete = 0; + status = ft1000_dsp_prov(dev); + if (status != STATUS_SUCCESS) + goto out; + } else { + info->fProvComplete = 1; + status = + ft1000_write_register(dev, FT1000_DB_HB, + FT1000_REG_DOORBELL); + DEBUG + ("FT1000:drivermsg:No more DSP provisioning data in dsp image\n"); + } + DEBUG("ft1000_proc_drvmsg:DSP PROVISION is done\n"); + break; + } + case DSP_STORE_INFO:{ + DEBUG + ("ft1000_proc_drvmsg:Command message type = DSP_STORE_INFO"); + + DEBUG("FT1000:drivermsg:Got DSP_STORE_INFO\n"); + tempword = ntohs(pdrvmsg->length); + info->DSPInfoBlklen = tempword; + if (tempword < (MAX_DSP_SESS_REC - 4)) { + pmsg = (u16 *) &pdrvmsg->data[0]; + for (i = 0; i < ((tempword + 1) / 2); i++) { + DEBUG + ("FT1000:drivermsg:dsp info data = 0x%x\n", + *pmsg); + info->DSPInfoBlk[i + 10] = *pmsg++; + } + } else { + info->DSPInfoBlklen = 0; + } + break; + } + case DSP_GET_INFO:{ + DEBUG("FT1000:drivermsg:Got DSP_GET_INFO\n"); + /* copy dsp info block to dsp */ + info->DrvMsgPend = 1; + /* allow any outstanding ioctl to finish */ + mdelay(10); + status = + ft1000_read_register(dev, &tempword, + FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + mdelay(10); + status = + ft1000_read_register(dev, &tempword, + FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + mdelay(10); + status = + ft1000_read_register(dev, &tempword, + FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) + break; + } + } + /* Put message into Slow Queue + * Form Pseudo header + */ + pmsg = (u16 *) info->DSPInfoBlk; + *pmsg++ = 0; + *pmsg++ = + htons(info->DSPInfoBlklen + 20 + + info->DSPInfoBlklen); + ppseudo_hdr = + (struct pseudo_hdr *)(u16 *) &info->DSPInfoBlk[2]; + ppseudo_hdr->length = + htons(info->DSPInfoBlklen + 4 + + info->DSPInfoBlklen); + ppseudo_hdr->source = 0x10; + ppseudo_hdr->destination = 0x20; + ppseudo_hdr->portdest = 0; + ppseudo_hdr->portsrc = 0; + ppseudo_hdr->sh_str_id = 0; + ppseudo_hdr->control = 0; + ppseudo_hdr->rsvd1 = 0; + ppseudo_hdr->rsvd2 = 0; + ppseudo_hdr->qos_class = 0; + /* Insert slow queue sequence number */ + ppseudo_hdr->seq_num = info->squeseqnum++; + /* Insert application id */ + ppseudo_hdr->portsrc = 0; + /* Calculate new checksum */ + ppseudo_hdr->checksum = *pmsg++; + for (i = 1; i < 7; i++) + ppseudo_hdr->checksum ^= *pmsg++; + + info->DSPInfoBlk[10] = 0x7200; + info->DSPInfoBlk[11] = htons(info->DSPInfoBlklen); + status = + ft1000_write_dpram32(dev, 0, + (u8 *) &info->DSPInfoBlk[0], + (unsigned short)(info-> + DSPInfoBlklen + + 22)); + status = + ft1000_write_register(dev, FT1000_DB_DPRAM_TX, + FT1000_REG_DOORBELL); + info->DrvMsgPend = 0; + + break; + } + + case GET_DRV_ERR_RPT_MSG:{ + DEBUG("FT1000:drivermsg:Got GET_DRV_ERR_RPT_MSG\n"); + /* copy driver error message to dsp */ + info->DrvMsgPend = 1; + /* allow any outstanding ioctl to finish */ + mdelay(10); + status = + ft1000_read_register(dev, &tempword, + FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) { + mdelay(10); + status = + ft1000_read_register(dev, &tempword, + FT1000_REG_DOORBELL); + if (tempword & FT1000_DB_DPRAM_TX) + mdelay(10); + } + + if ((tempword & FT1000_DB_DPRAM_TX) == 0) { + /* Put message into Slow Queue + * Form Pseudo header + */ + pmsg = (u16 *) &tempbuffer[0]; + ppseudo_hdr = (struct pseudo_hdr *)pmsg; + ppseudo_hdr->length = htons(0x0012); + ppseudo_hdr->source = 0x10; + ppseudo_hdr->destination = 0x20; + ppseudo_hdr->portdest = 0; + ppseudo_hdr->portsrc = 0; + ppseudo_hdr->sh_str_id = 0; + ppseudo_hdr->control = 0; + ppseudo_hdr->rsvd1 = 0; + ppseudo_hdr->rsvd2 = 0; + ppseudo_hdr->qos_class = 0; + /* Insert slow queue sequence number */ + ppseudo_hdr->seq_num = info->squeseqnum++; + /* Insert application id */ + ppseudo_hdr->portsrc = 0; + /* Calculate new checksum */ + ppseudo_hdr->checksum = *pmsg++; + for (i = 1; i < 7; i++) + ppseudo_hdr->checksum ^= *pmsg++; + + pmsg = (u16 *) &tempbuffer[16]; + *pmsg++ = htons(RSP_DRV_ERR_RPT_MSG); + *pmsg++ = htons(0x000e); + *pmsg++ = htons(info->DSP_TIME[0]); + *pmsg++ = htons(info->DSP_TIME[1]); + *pmsg++ = htons(info->DSP_TIME[2]); + *pmsg++ = htons(info->DSP_TIME[3]); + convert.byte[0] = info->DspVer[0]; + convert.byte[1] = info->DspVer[1]; + *pmsg++ = convert.wrd; + convert.byte[0] = info->DspVer[2]; + convert.byte[1] = info->DspVer[3]; + *pmsg++ = convert.wrd; + *pmsg++ = htons(info->DrvErrNum); + + card_send_command(dev, + (unsigned char *)&tempbuffer[0], + (u16) (0x0012 + PSEUDOSZ)); + info->DrvErrNum = 0; + } + info->DrvMsgPend = 0; + + break; + } + + default: + break; + } + + status = STATUS_SUCCESS; +out: + kfree(cmdbuffer); + DEBUG("return from ft1000_proc_drvmsg\n"); + return status; +} + +int ft1000_poll(void* dev_id) { + + struct ft1000_device *dev = (struct ft1000_device *)dev_id; + struct ft1000_info *info = netdev_priv(dev->net); + + u16 tempword; + u16 status; + u16 size; + int i; + u16 data; + u16 modulo; + u16 portid; + u16 nxtph; + struct dpram_blk *pdpram_blk; + struct pseudo_hdr *ppseudo_hdr; + unsigned long flags; + + if (ft1000_chkcard(dev) == FALSE) { + DEBUG("ft1000_poll::ft1000_chkcard: failed\n"); + return STATUS_FAILURE; + } + + status = ft1000_read_register (dev, &tempword, FT1000_REG_DOORBELL); + + if ( !status ) + { + + if (tempword & FT1000_DB_DPRAM_RX) { + + status = ft1000_read_dpram16(dev, 0x200, (u8 *)&data, 0); + size = ntohs(data) + 16 + 2; + if (size % 4) { + modulo = 4 - (size % 4); + size = size + modulo; + } + status = ft1000_read_dpram16(dev, 0x201, (u8 *)&portid, 1); + portid &= 0xff; + + if (size < MAX_CMD_SQSIZE) { + switch (portid) + { + case DRIVERID: + DEBUG("ft1000_poll: FT1000_REG_DOORBELL message type: FT1000_DB_DPRAM_RX : portid DRIVERID\n"); + + status = ft1000_proc_drvmsg (dev, size); + if (status != STATUS_SUCCESS ) + return status; + break; + case DSPBCMSGID: + // This is a dsp broadcast message + // Check which application has registered for dsp broadcast messages + + for (i=0; iapp_info[i].DspBCMsgFlag) && (info->app_info[i].fileobject) && + (info->app_info[i].NumOfMsg < MAX_MSG_LIMIT) ) + { + nxtph = FT1000_DPRAM_RX_BASE + 2; + pdpram_blk = ft1000_get_buffer (&freercvpool); + if (pdpram_blk != NULL) { + if ( ft1000_receive_cmd(dev, pdpram_blk->pbuffer, MAX_CMD_SQSIZE, &nxtph) ) { + ppseudo_hdr = (struct pseudo_hdr *)pdpram_blk->pbuffer; + // Put message into the appropriate application block + info->app_info[i].nRxMsg++; + spin_lock_irqsave(&free_buff_lock, flags); + list_add_tail(&pdpram_blk->list, &info->app_info[i].app_sqlist); + info->app_info[i].NumOfMsg++; + spin_unlock_irqrestore(&free_buff_lock, flags); + wake_up_interruptible(&info->app_info[i].wait_dpram_msg); + } + else { + info->app_info[i].nRxMsgMiss++; + // Put memory back to free pool + ft1000_free_buffer(pdpram_blk, &freercvpool); + DEBUG("pdpram_blk::ft1000_get_buffer NULL\n"); + } + } + else { + DEBUG("Out of memory in free receive command pool\n"); + info->app_info[i].nRxMsgMiss++; + } + } + } + break; + default: + pdpram_blk = ft1000_get_buffer (&freercvpool); + + if (pdpram_blk != NULL) { + if ( ft1000_receive_cmd(dev, pdpram_blk->pbuffer, MAX_CMD_SQSIZE, &nxtph) ) { + ppseudo_hdr = (struct pseudo_hdr *)pdpram_blk->pbuffer; + // Search for correct application block + for (i=0; iapp_info[i].app_id == ppseudo_hdr->portdest) { + break; + } + } + + if (i == MAX_NUM_APP) { + DEBUG("FT1000:ft1000_parse_dpram_msg: No application matching id = %d\n", ppseudo_hdr->portdest); + // Put memory back to free pool + ft1000_free_buffer(pdpram_blk, &freercvpool); + } + else { + if (info->app_info[i].NumOfMsg > MAX_MSG_LIMIT) { + // Put memory back to free pool + ft1000_free_buffer(pdpram_blk, &freercvpool); + } + else { + info->app_info[i].nRxMsg++; + // Put message into the appropriate application block + list_add_tail(&pdpram_blk->list, &info->app_info[i].app_sqlist); + info->app_info[i].NumOfMsg++; + } + } + } + else { + // Put memory back to free pool + ft1000_free_buffer(pdpram_blk, &freercvpool); + } + } + else { + DEBUG("Out of memory in free receive command pool\n"); + } + break; + } + } + else { + DEBUG("FT1000:dpc:Invalid total length for SlowQ = %d\n", size); + } + status = ft1000_write_register (dev, FT1000_DB_DPRAM_RX, FT1000_REG_DOORBELL); + } + else if (tempword & FT1000_DSP_ASIC_RESET) { + + // Let's reset the ASIC from the Host side as well + status = ft1000_write_register (dev, ASIC_RESET_BIT, FT1000_REG_RESET); + status = ft1000_read_register (dev, &tempword, FT1000_REG_RESET); + i = 0; + while (tempword & ASIC_RESET_BIT) { + status = ft1000_read_register (dev, &tempword, FT1000_REG_RESET); + msleep(10); + i++; + if (i==100) + break; + } + if (i==100) { + DEBUG("Unable to reset ASIC\n"); + return STATUS_SUCCESS; + } + msleep(10); + // Program WMARK register + status = ft1000_write_register (dev, 0x600, FT1000_REG_MAG_WATERMARK); + // clear ASIC reset doorbell + status = ft1000_write_register (dev, FT1000_DSP_ASIC_RESET, FT1000_REG_DOORBELL); + msleep(10); + } + else if (tempword & FT1000_ASIC_RESET_REQ) { + DEBUG("ft1000_poll: FT1000_REG_DOORBELL message type: FT1000_ASIC_RESET_REQ\n"); + + // clear ASIC reset request from DSP + status = ft1000_write_register (dev, FT1000_ASIC_RESET_REQ, FT1000_REG_DOORBELL); + status = ft1000_write_register (dev, HOST_INTF_BE, FT1000_REG_SUP_CTRL); + // copy dsp session record from Adapter block + status = ft1000_write_dpram32 (dev, 0, (u8 *)&info->DSPSess.Rec[0], 1024); + // Program WMARK register + status = ft1000_write_register (dev, 0x600, FT1000_REG_MAG_WATERMARK); + // ring doorbell to tell DSP that ASIC is out of reset + status = ft1000_write_register (dev, FT1000_ASIC_RESET_DSP, FT1000_REG_DOORBELL); + } + else if (tempword & FT1000_DB_COND_RESET) { + DEBUG("ft1000_poll: FT1000_REG_DOORBELL message type: FT1000_DB_COND_RESET\n"); + + if (info->fAppMsgPend == 0) { + // Reset ASIC and DSP + + status = ft1000_read_dpram16(dev, FT1000_MAG_DSP_TIMER0, (u8 *)&(info->DSP_TIME[0]), FT1000_MAG_DSP_TIMER0_INDX); + status = ft1000_read_dpram16(dev, FT1000_MAG_DSP_TIMER1, (u8 *)&(info->DSP_TIME[1]), FT1000_MAG_DSP_TIMER1_INDX); + status = ft1000_read_dpram16(dev, FT1000_MAG_DSP_TIMER2, (u8 *)&(info->DSP_TIME[2]), FT1000_MAG_DSP_TIMER2_INDX); + status = ft1000_read_dpram16(dev, FT1000_MAG_DSP_TIMER3, (u8 *)&(info->DSP_TIME[3]), FT1000_MAG_DSP_TIMER3_INDX); + info->CardReady = 0; + info->DrvErrNum = DSP_CONDRESET_INFO; + DEBUG("ft1000_hw:DSP conditional reset requested\n"); + info->ft1000_reset(dev->net); + } + else { + info->fProvComplete = 0; + info->fCondResetPend = 1; + } + + ft1000_write_register(dev, FT1000_DB_COND_RESET, FT1000_REG_DOORBELL); + } + + } + + return STATUS_SUCCESS; + +} diff --git a/drivers/staging/ft1000/ft1000-usb/ft1000_ioctl.h b/drivers/staging/ft1000/ft1000-usb/ft1000_ioctl.h new file mode 100644 index 00000000..3f4207fd --- /dev/null +++ b/drivers/staging/ft1000/ft1000-usb/ft1000_ioctl.h @@ -0,0 +1,107 @@ +//--------------------------------------------------------------------------- +// FT1000 driver for Flarion Flash OFDM NIC Device +// +// Copyright (C) 2002 Flarion Technologies, All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) any +// later version. This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. You should have received a copy of the GNU General Public +// License along with this program; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place - +// Suite 330, Boston, MA 02111-1307, USA. +//--------------------------------------------------------------------------- +// +// File: ft1000_ioctl.h +// +// Description: Common structures and defines relating to IOCTL +// +// History: +// 11/5/02 Whc Created. +// +//---------------------------------------------------------------------------//--------------------------------------------------------------------------- +#ifndef _FT1000IOCTLH_ +#define _FT1000IOCTLH_ + +typedef struct _IOCTL_GET_VER +{ + unsigned long drv_ver; +} __attribute__ ((packed)) IOCTL_GET_VER, *PIOCTL_GET_VER; + +//Data structure for Dsp statistics +typedef struct _IOCTL_GET_DSP_STAT +{ + unsigned char DspVer[DSPVERSZ]; // DSP version number + unsigned char HwSerNum[HWSERNUMSZ]; // Hardware Serial Number + unsigned char Sku[SKUSZ]; // SKU + unsigned char eui64[EUISZ]; // EUI64 + unsigned short ConStat; // Connection Status + // Bits 0-3 = Connection Status Field + // 0000=Idle (Disconnect) + // 0001=Searching + // 0010=Active (Connected) + // 0011=Waiting for L2 down + // 0100=Sleep + unsigned short LedStat; // Led Status + // Bits 0-3 = Signal Strength Field + // 0000 = -105dBm to -92dBm + // 0001 = -92dBm to -85dBm + // 0011 = -85dBm to -75dBm + // 0111 = -75dBm to -50dBm + // 1111 = -50dBm to 0dBm + // Bits 4-7 = Reserved + // Bits 8-11 = SNR Field + // 0000 = <2dB + // 0001 = 2dB to 8dB + // 0011 = 8dB to 15dB + // 0111 = 15dB to 22dB + // 1111 = >22dB + // Bits 12-15 = Reserved + unsigned long nTxPkts; // Number of packets transmitted from host to dsp + unsigned long nRxPkts; // Number of packets received from dsp to host + unsigned long nTxBytes; // Number of bytes transmitted from host to dsp + unsigned long nRxBytes; // Number of bytes received from dsp to host + unsigned long ConTm; // Current session connection time in seconds + unsigned char CalVer[CALVERSZ]; // Proprietary Calibration Version + unsigned char CalDate[CALDATESZ]; // Proprietary Calibration Date +} __attribute__ ((packed)) IOCTL_GET_DSP_STAT, *PIOCTL_GET_DSP_STAT; + +//Data structure for Dual Ported RAM messaging between Host and Dsp +typedef struct _IOCTL_DPRAM_BLK +{ + unsigned short total_len; + struct pseudo_hdr pseudohdr; + unsigned char buffer[1780]; +} __attribute__ ((packed)) IOCTL_DPRAM_BLK, *PIOCTL_DPRAM_BLK; + +typedef struct _IOCTL_DPRAM_COMMAND +{ + unsigned short extra; + IOCTL_DPRAM_BLK dpram_blk; +} __attribute__ ((packed)) IOCTL_DPRAM_COMMAND, *PIOCTL_DPRAM_COMMAND; + +// +// Custom IOCTL command codes +// +#define FT1000_MAGIC_CODE 'F' + +#define IOCTL_REGISTER_CMD 0 +#define IOCTL_SET_DPRAM_CMD 3 +#define IOCTL_GET_DPRAM_CMD 4 +#define IOCTL_GET_DSP_STAT_CMD 6 +#define IOCTL_GET_VER_CMD 7 +#define IOCTL_CONNECT 10 +#define IOCTL_DISCONNECT 11 + +#define IOCTL_FT1000_GET_DSP_STAT _IOR (FT1000_MAGIC_CODE, IOCTL_GET_DSP_STAT_CMD, sizeof(IOCTL_GET_DSP_STAT) ) +#define IOCTL_FT1000_GET_VER _IOR (FT1000_MAGIC_CODE, IOCTL_GET_VER_CMD, sizeof(IOCTL_GET_VER) ) +#define IOCTL_FT1000_CONNECT _IOW (FT1000_MAGIC_CODE, IOCTL_CONNECT, 0 ) +#define IOCTL_FT1000_DISCONNECT _IOW (FT1000_MAGIC_CODE, IOCTL_DISCONNECT, 0 ) +#define IOCTL_FT1000_SET_DPRAM _IOW (FT1000_MAGIC_CODE, IOCTL_SET_DPRAM_CMD, sizeof(IOCTL_DPRAM_BLK) ) +#define IOCTL_FT1000_GET_DPRAM _IOR (FT1000_MAGIC_CODE, IOCTL_GET_DPRAM_CMD, sizeof(IOCTL_DPRAM_BLK) ) +#define IOCTL_FT1000_REGISTER _IOW (FT1000_MAGIC_CODE, IOCTL_REGISTER_CMD, sizeof(unsigned short *) ) +#endif // _FT1000IOCTLH_ + diff --git a/drivers/staging/ft1000/ft1000-usb/ft1000_proc.c b/drivers/staging/ft1000/ft1000-usb/ft1000_proc.c new file mode 100644 index 00000000..5ae39671 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-usb/ft1000_proc.c @@ -0,0 +1,251 @@ +/* + * ft1000_proc.c - ft1000 proc interface + * + * Copyright (C) 2009-2010 Quintec + * (C) 2010 Open-nandra + * + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + + +#include "ft1000_usb.h" + +#define FT1000_PROC_DIR "ft1000" + + +#define PUTM_TO_PAGE(len,page,args...) \ + len += snprintf(page+len, PAGE_SIZE - len, args) + +#define PUTX_TO_PAGE(len,page,message,size,var) \ + len += snprintf(page+len, PAGE_SIZE - len, message); \ + for (i = 0; i < (size - 1); i++) {\ + len += snprintf(page+len, PAGE_SIZE - len, "%02x:", var[i]); \ + } \ + len += snprintf(page+len, PAGE_SIZE - len, "%02x\n", var[i]) + +#define PUTD_TO_PAGE(len,page,message,size,var) \ + len += snprintf(page+len, PAGE_SIZE - len, message); \ + for (i = 0; i < (size - 1); i++) {\ + len += snprintf(page+len, PAGE_SIZE - len, "%d.", var[i]); \ + } \ + len += snprintf(page+len, PAGE_SIZE - len, "%d\n", var[i]) + + +#define FTNET_PROC init_net.proc_net + + +int ft1000_read_dpram16 (struct ft1000_device *ft1000dev, u16 indx, + u8 *buffer, u8 highlow); + + +static int +ft1000ReadProc(char *page, char **start, off_t off, int count, int *eof, + void *data) +{ + struct net_device *dev; + int len; + int i; + unsigned short ledStat; + unsigned short conStat; + + struct ft1000_info *info; + + char *status[] = { + "Idle (Disconnect)", + "Searching", + "Active (Connected)", + "Waiting for L2", + "Sleep", + "No Coverage", + "", + "", + }; + + char *signal[] = { "", "*", "**", "***", "****" }; + int strength; + int quality; + struct timeval tv; + time_t delta; + + dev = (struct net_device *) data; + info = netdev_priv(dev); + + if (off > 0) { + *eof = 1; + return 0; + } + + + if (info->ProgConStat != 0xFF) { + ft1000_read_dpram16(info->pFt1000Dev, FT1000_MAG_DSP_LED, + (u8 *)&ledStat, FT1000_MAG_DSP_LED_INDX); + info->LedStat = ntohs(ledStat); + + ft1000_read_dpram16(info->pFt1000Dev, FT1000_MAG_DSP_CON_STATE, + (u8 *)&conStat, FT1000_MAG_DSP_CON_STATE_INDX); + info->ConStat = ntohs(conStat); + do_gettimeofday(&tv); + delta = (tv.tv_sec - info->ConTm); + } else { + info->ConStat = 0xf; + delta = 0; + } + + i = (info->LedStat) & 0xf; + switch (i) { + case 0x1: + strength = 1; + break; + case 0x3: + strength = 2; + break; + case 0x7: + strength = 3; + break; + case 0xf: + strength = 4; + break; + default: + strength = 0; + } + + i = (info->LedStat >> 8) & 0xf; + switch (i) { + case 0x1: + quality = 1; + break; + case 0x3: + quality = 2; + break; + case 0x7: + quality = 3; + break; + case 0xf: + quality = 4; + break; + default: + quality = 0; + } + + len = 0; + PUTM_TO_PAGE(len, page, "Connection Time: %02ld:%02ld:%02ld\n", + ((delta / 3600) % 24), ((delta / 60) % 60), (delta % 60)); + PUTM_TO_PAGE(len, page, "Connection Time[s]: %ld\n", delta); + PUTM_TO_PAGE(len, page, "Asic ID: %s\n", + (info->AsicID) == + ELECTRABUZZ_ID ? "ELECTRABUZZ ASIC" : "MAGNEMITE ASIC"); + PUTX_TO_PAGE(len, page, "SKU: ", SKUSZ, info->Sku); + PUTX_TO_PAGE(len, page, "EUI64: ", EUISZ, info->eui64); + PUTD_TO_PAGE(len, page, "DSP version number: ", DSPVERSZ, info->DspVer); + PUTX_TO_PAGE(len, page, "Hardware Serial Number: ", HWSERNUMSZ, + info->HwSerNum); + PUTX_TO_PAGE(len, page, "Caliberation Version: ", CALVERSZ, + info->RfCalVer); + PUTD_TO_PAGE(len, page, "Caliberation Date: ", CALDATESZ, + info->RfCalDate); + PUTM_TO_PAGE(len, page, "Media State: %s\n", + (info->mediastate) ? "link" : "no link"); + PUTM_TO_PAGE(len, page, "Connection Status: %s\n", + status[((info->ConStat) & 0x7)]); + PUTM_TO_PAGE(len, page, "RX packets: %ld\n", info->stats.rx_packets); + PUTM_TO_PAGE(len, page, "TX packets: %ld\n", info->stats.tx_packets); + PUTM_TO_PAGE(len, page, "RX bytes: %ld\n", info->stats.rx_bytes); + PUTM_TO_PAGE(len, page, "TX bytes: %ld\n", info->stats.tx_bytes); + PUTM_TO_PAGE(len, page, "Signal Strength: %s\n", signal[strength]); + PUTM_TO_PAGE(len, page, "Signal Quality: %s\n", signal[quality]); + + return len; +} + +static int +ft1000NotifyProc(struct notifier_block *this, unsigned long event, void *ptr) +{ + struct net_device *dev = ptr; + struct ft1000_info *info; + struct proc_dir_entry *ft1000_proc_file; + + info = netdev_priv(dev); + + switch (event) { + case NETDEV_CHANGENAME: + remove_proc_entry(info->netdevname, info->ft1000_proc_dir); + ft1000_proc_file = create_proc_read_entry(dev->name, 0644, + info->ft1000_proc_dir, + ft1000ReadProc, dev); + snprintf(info->netdevname, IFNAMSIZ, "%s", dev->name); + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block ft1000_netdev_notifier = { + .notifier_call = ft1000NotifyProc, +}; + + +int ft1000_init_proc(struct net_device *dev) +{ + struct ft1000_info *info; + struct proc_dir_entry *ft1000_proc_file; + int ret = 0; + + info = netdev_priv(dev); + + info->ft1000_proc_dir = proc_mkdir(FT1000_PROC_DIR, FTNET_PROC); + if (info->ft1000_proc_dir == NULL) { + printk(KERN_WARNING "Unable to create %s dir.\n", + FT1000_PROC_DIR); + ret = -EINVAL; + goto fail; + } + + ft1000_proc_file = + create_proc_read_entry(dev->name, 0644, + info->ft1000_proc_dir, ft1000ReadProc, dev); + + if (ft1000_proc_file == NULL) { + printk(KERN_WARNING "Unable to create /proc entry.\n"); + ret = -EINVAL; + goto fail_entry; + } + + snprintf(info->netdevname, IFNAMSIZ, "%s", dev->name); + + ret = register_netdevice_notifier(&ft1000_netdev_notifier); + if (ret) + goto fail_notif; + + return 0; + +fail_notif: + remove_proc_entry(info->netdevname, info->ft1000_proc_dir); +fail_entry: + remove_proc_entry(FT1000_PROC_DIR, FTNET_PROC); +fail: + return ret; +} + +void ft1000_cleanup_proc(struct ft1000_info *info) +{ + remove_proc_entry(info->netdevname, info->ft1000_proc_dir); + remove_proc_entry(FT1000_PROC_DIR, FTNET_PROC); + unregister_netdevice_notifier(&ft1000_netdev_notifier); +} diff --git a/drivers/staging/ft1000/ft1000-usb/ft1000_usb.c b/drivers/staging/ft1000/ft1000-usb/ft1000_usb.c new file mode 100644 index 00000000..84c38d5c --- /dev/null +++ b/drivers/staging/ft1000/ft1000-usb/ft1000_usb.c @@ -0,0 +1,266 @@ +/*===================================================== + * CopyRight (C) 2007 Qualcomm Inc. All Rights Reserved. + * + * + * This file is part of Express Card USB Driver + * + * $Id: + *==================================================== + */ +#include +#include +#include +#include +#include +#include +#include +#include "ft1000_usb.h" + +#include + +MODULE_DESCRIPTION("FT1000 EXPRESS CARD DRIVER"); +MODULE_LICENSE("Dual MPL/GPL"); +MODULE_SUPPORTED_DEVICE("QFT FT1000 Express Cards"); + +void *pFileStart; +size_t FileLength; + +#define VENDOR_ID 0x1291 /* Qualcomm vendor id */ +#define PRODUCT_ID 0x11 /* fake product id */ + +/* table of devices that work with this driver */ +static struct usb_device_id id_table[] = { + {USB_DEVICE(VENDOR_ID, PRODUCT_ID)}, + {}, +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static bool gPollingfailed = FALSE; +int ft1000_poll_thread(void *arg) +{ + int ret = STATUS_SUCCESS; + + while (!kthread_should_stop()) { + msleep(10); + if (!gPollingfailed) { + ret = ft1000_poll(arg); + if (ret != STATUS_SUCCESS) { + DEBUG("ft1000_poll_thread: polling failed\n"); + gPollingfailed = TRUE; + } + } + } + return STATUS_SUCCESS; +} + +static int ft1000_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + struct usb_device *dev; + unsigned numaltsetting; + int i, ret = 0, size; + + struct ft1000_device *ft1000dev; + struct ft1000_info *pft1000info = NULL; + const struct firmware *dsp_fw; + + ft1000dev = kmalloc(sizeof(struct ft1000_device), GFP_KERNEL); + + if (!ft1000dev) { + printk(KERN_ERR "out of memory allocating device structure\n"); + return 0; + } + + memset(ft1000dev, 0, sizeof(*ft1000dev)); + + dev = interface_to_usbdev(interface); + DEBUG("ft1000_probe: usb device descriptor info:\n"); + DEBUG("ft1000_probe: number of configuration is %d\n", + dev->descriptor.bNumConfigurations); + + ft1000dev->dev = dev; + ft1000dev->status = 0; + ft1000dev->net = NULL; + ft1000dev->tx_urb = usb_alloc_urb(0, GFP_ATOMIC); + ft1000dev->rx_urb = usb_alloc_urb(0, GFP_ATOMIC); + + DEBUG("ft1000_probe is called\n"); + numaltsetting = interface->num_altsetting; + DEBUG("ft1000_probe: number of alt settings is :%d\n", numaltsetting); + iface_desc = interface->cur_altsetting; + DEBUG("ft1000_probe: number of endpoints is %d\n", + iface_desc->desc.bNumEndpoints); + DEBUG("ft1000_probe: descriptor type is %d\n", + iface_desc->desc.bDescriptorType); + DEBUG("ft1000_probe: interface number is %d\n", + iface_desc->desc.bInterfaceNumber); + DEBUG("ft1000_probe: alternatesetting is %d\n", + iface_desc->desc.bAlternateSetting); + DEBUG("ft1000_probe: interface class is %d\n", + iface_desc->desc.bInterfaceClass); + DEBUG("ft1000_probe: control endpoint info:\n"); + DEBUG("ft1000_probe: descriptor0 type -- %d\n", + iface_desc->endpoint[0].desc.bmAttributes); + DEBUG("ft1000_probe: descriptor1 type -- %d\n", + iface_desc->endpoint[1].desc.bmAttributes); + DEBUG("ft1000_probe: descriptor2 type -- %d\n", + iface_desc->endpoint[2].desc.bmAttributes); + + for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { + endpoint = + (struct usb_endpoint_descriptor *)&iface_desc-> + endpoint[i].desc; + DEBUG("endpoint %d\n", i); + DEBUG("bEndpointAddress=%x, bmAttributes=%x\n", + endpoint->bEndpointAddress, endpoint->bmAttributes); + if ((endpoint->bEndpointAddress & USB_DIR_IN) + && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_BULK)) { + ft1000dev->bulk_in_endpointAddr = + endpoint->bEndpointAddress; + DEBUG("ft1000_probe: in: %d\n", + endpoint->bEndpointAddress); + } + + if (!(endpoint->bEndpointAddress & USB_DIR_IN) + && ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_BULK)) { + ft1000dev->bulk_out_endpointAddr = + endpoint->bEndpointAddress; + DEBUG("ft1000_probe: out: %d\n", + endpoint->bEndpointAddress); + } + } + + DEBUG("bulk_in=%d, bulk_out=%d\n", ft1000dev->bulk_in_endpointAddr, + ft1000dev->bulk_out_endpointAddr); + + ret = request_firmware(&dsp_fw, "ft3000.img", &dev->dev); + if (ret < 0) { + printk(KERN_ERR "Error request_firmware().\n"); + goto err_fw; + } + + size = max_t(uint, dsp_fw->size, 4096); + pFileStart = kmalloc(size, GFP_KERNEL); + + if (!pFileStart) { + release_firmware(dsp_fw); + ret = -ENOMEM; + goto err_fw; + } + + memcpy(pFileStart, dsp_fw->data, dsp_fw->size); + FileLength = dsp_fw->size; + release_firmware(dsp_fw); + + DEBUG("ft1000_probe: start downloading dsp image...\n"); + + ret = init_ft1000_netdev(ft1000dev); + if (ret) + goto err_load; + + pft1000info = netdev_priv(ft1000dev->net); + + DEBUG("In probe: pft1000info=%p\n", pft1000info); + ret = dsp_reload(ft1000dev); + if (ret) { + printk(KERN_ERR "Problem with DSP image loading\n"); + goto err_load; + } + + gPollingfailed = FALSE; + pft1000info->pPollThread = + kthread_run(ft1000_poll_thread, ft1000dev, "ft1000_poll"); + + if (IS_ERR(pft1000info->pPollThread)) { + ret = PTR_ERR(pft1000info->pPollThread); + goto err_load; + } + + msleep(500); + + while (!pft1000info->CardReady) { + if (gPollingfailed) { + ret = -EIO; + goto err_thread; + } + msleep(100); + DEBUG("ft1000_probe::Waiting for Card Ready\n"); + } + + DEBUG("ft1000_probe::Card Ready!!!! Registering network device\n"); + + ret = reg_ft1000_netdev(ft1000dev, interface); + if (ret) + goto err_thread; + + ret = ft1000_init_proc(ft1000dev->net); + if (ret) + goto err_proc; + + pft1000info->NetDevRegDone = 1; + + return 0; + +err_proc: + unregister_netdev(ft1000dev->net); + free_netdev(ft1000dev->net); +err_thread: + kthread_stop(pft1000info->pPollThread); +err_load: + kfree(pFileStart); +err_fw: + kfree(ft1000dev); + return ret; +} + +static void ft1000_disconnect(struct usb_interface *interface) +{ + struct ft1000_info *pft1000info; + + DEBUG("ft1000_disconnect is called\n"); + + pft1000info = (struct ft1000_info *) usb_get_intfdata(interface); + DEBUG("In disconnect pft1000info=%p\n", pft1000info); + + if (pft1000info) { + ft1000_cleanup_proc(pft1000info); + if (pft1000info->pPollThread) + kthread_stop(pft1000info->pPollThread); + + DEBUG("ft1000_disconnect: threads are terminated\n"); + + if (pft1000info->pFt1000Dev->net) { + DEBUG("ft1000_disconnect: destroy char driver\n"); + ft1000_destroy_dev(pft1000info->pFt1000Dev->net); + unregister_netdev(pft1000info->pFt1000Dev->net); + DEBUG + ("ft1000_disconnect: network device unregisterd\n"); + free_netdev(pft1000info->pFt1000Dev->net); + + } + + usb_free_urb(pft1000info->pFt1000Dev->rx_urb); + usb_free_urb(pft1000info->pFt1000Dev->tx_urb); + + DEBUG("ft1000_disconnect: urb freed\n"); + + kfree(pft1000info->pFt1000Dev); + } + kfree(pFileStart); + + return; +} + +static struct usb_driver ft1000_usb_driver = { + .name = "ft1000usb", + .probe = ft1000_probe, + .disconnect = ft1000_disconnect, + .id_table = id_table, +}; + +module_usb_driver(ft1000_usb_driver); diff --git a/drivers/staging/ft1000/ft1000-usb/ft1000_usb.h b/drivers/staging/ft1000/ft1000-usb/ft1000_usb.h new file mode 100644 index 00000000..51c08475 --- /dev/null +++ b/drivers/staging/ft1000/ft1000-usb/ft1000_usb.h @@ -0,0 +1,190 @@ +#ifndef _FT1000_USB_H_ +#define _FT1000_USB_H_ + +/*Jim*/ +#include "../ft1000.h" +#include "ft1000_ioctl.h" +#define FT1000_DRV_VER 0x01010403 + +#define MAX_NUM_APP 6 +#define MAX_MSG_LIMIT 200 +#define NUM_OF_FREE_BUFFERS 1500 + +#define PSEUDOSZ 16 + +#define SUCCESS 0x00 + +struct app_info_block { + u32 nTxMsg; // DPRAM msg sent to DSP with app_id + u32 nRxMsg; // DPRAM msg rcv from dsp with app_id + u32 nTxMsgReject; // DPRAM msg rejected due to DSP doorbell set + u32 nRxMsgMiss; // DPRAM msg dropped due to overflow + struct fown_struct *fileobject;// Application's file object + u16 app_id; // Application id + int DspBCMsgFlag; + int NumOfMsg; // number of messages queued up + wait_queue_head_t wait_dpram_msg; + struct list_head app_sqlist; // link list of msgs for applicaton on slow queue +} __attribute__((packed)); + +/*end of Jim*/ +#define DEBUG(args...) printk(KERN_INFO args) + +#define FALSE 0 +#define TRUE 1 + +#define STATUS_SUCCESS 0 +#define STATUS_FAILURE 0x1001 + +#define FT1000_STATUS_CLOSING 0x01 + +#define LARGE_TIMEOUT 5000 + +#define DSPBCMSGID 0x10 + +/* Electrabuzz specific DPRAM mapping */ +/* this is used by ft1000_usb driver - isn't that a bug? */ +#undef FT1000_DPRAM_RX_BASE +#define FT1000_DPRAM_RX_BASE 0x1800 /* RX AREA (SlowQ) */ + +// MEMORY MAP FOR MAGNEMITE +/* the indexes are swapped comparing to PCMCIA - is it OK or a bug? */ +#undef FT1000_MAG_DSP_LED_INDX +#define FT1000_MAG_DSP_LED_INDX 0x1 /* dsp led status for PAD device */ +#undef FT1000_MAG_DSP_CON_STATE_INDX +#define FT1000_MAG_DSP_CON_STATE_INDX 0x0 /* DSP Connection Status Info */ + +// Maximum times trying to get ASIC out of reset +#define MAX_ASIC_RESET_CNT 20 + +#define MAX_BUF_SIZE 4096 + +struct ft1000_device +{ + struct usb_device *dev; + struct net_device *net; + + u32 status; + + struct urb *rx_urb; + struct urb *tx_urb; + + u8 tx_buf[MAX_BUF_SIZE]; + u8 rx_buf[MAX_BUF_SIZE]; + + u8 bulk_in_endpointAddr; + u8 bulk_out_endpointAddr; + + //struct ft1000_ethernet_configuration configuration; + +// struct net_device_stats stats; //mbelian +} __attribute__ ((packed)); + +struct ft1000_debug_dirs { + struct list_head list; + struct dentry *dent; + struct dentry *file; + int int_number; +}; + +struct ft1000_info { + struct ft1000_device *pFt1000Dev; + struct net_device_stats stats; + + struct task_struct *pPollThread; + + unsigned char fcodeldr; + unsigned char bootmode; + unsigned char usbboot; + unsigned short dspalive; + u16 ASIC_ID; + bool fProvComplete; + bool fCondResetPend; + bool fAppMsgPend; + u16 DrvErrNum; + u16 AsicID; + int DspAsicReset; + int DeviceCreated; + int CardReady; + int NetDevRegDone; + u8 CardNumber; + u8 DeviceName[15]; + struct ft1000_debug_dirs nodes; + int registered; + int mediastate; + u8 squeseqnum; // sequence number on slow queue + spinlock_t dpram_lock; + spinlock_t fifo_lock; + u16 fifo_cnt; + u8 DspVer[DSPVERSZ]; // DSP version number + u8 HwSerNum[HWSERNUMSZ]; // Hardware Serial Number + u8 Sku[SKUSZ]; // SKU + u8 eui64[EUISZ]; // EUI64 + time_t ConTm; // Connection Time + u8 ProductMode[MODESZ]; + u8 RfCalVer[CALVERSZ]; + u8 RfCalDate[CALDATESZ]; + u16 DSP_TIME[4]; + u16 LedStat; //mbelian + u16 ConStat; //mbelian + u16 ProgConStat; + struct list_head prov_list; + int appcnt; + struct app_info_block app_info[MAX_NUM_APP]; + u16 DSPInfoBlklen; + u16 DrvMsgPend; + int (*ft1000_reset)(struct net_device *dev); + u16 DSPInfoBlk[MAX_DSP_SESS_REC]; + union { + u16 Rec[MAX_DSP_SESS_REC]; + u32 MagRec[MAX_DSP_SESS_REC/2]; + } DSPSess; + unsigned short tempbuf[32]; + char netdevname[IFNAMSIZ]; + struct proc_dir_entry *ft1000_proc_dir; //mbelian +}; + + +struct dpram_blk { + struct list_head list; + u16 *pbuffer; +} __attribute__ ((packed)); + +int ft1000_read_register(struct ft1000_device *ft1000dev, u16* Data, u16 nRegIndx); +int ft1000_write_register(struct ft1000_device *ft1000dev, u16 value, u16 nRegIndx); +int ft1000_read_dpram32(struct ft1000_device *ft1000dev, u16 indx, u8 *buffer, u16 cnt); +int ft1000_write_dpram32(struct ft1000_device *ft1000dev, u16 indx, u8 *buffer, u16 cnt); +int ft1000_read_dpram16(struct ft1000_device *ft1000dev, u16 indx, u8 *buffer, u8 highlow); +int ft1000_write_dpram16(struct ft1000_device *ft1000dev, u16 indx, u16 value, u8 highlow); +int fix_ft1000_read_dpram32(struct ft1000_device *ft1000dev, u16 indx, u8 *buffer); +int fix_ft1000_write_dpram32(struct ft1000_device *ft1000dev, u16 indx, u8 *buffer); + +extern void *pFileStart; +extern size_t FileLength; +extern int numofmsgbuf; + +int ft1000_close (struct net_device *dev); +u16 scram_dnldr(struct ft1000_device *ft1000dev, void *pFileStart, u32 FileLength); + +extern struct list_head freercvpool; +extern spinlock_t free_buff_lock; // lock to arbitrate free buffer list for receive command data + +int ft1000_create_dev(struct ft1000_device *dev); +void ft1000_destroy_dev(struct net_device *dev); +extern void card_send_command(struct ft1000_device *ft1000dev, void *ptempbuffer, int size); + +struct dpram_blk *ft1000_get_buffer(struct list_head *bufflist); +void ft1000_free_buffer(struct dpram_blk *pdpram_blk, struct list_head *plist); + +int dsp_reload(struct ft1000_device *ft1000dev); +int init_ft1000_netdev(struct ft1000_device *ft1000dev); +struct usb_interface; +int reg_ft1000_netdev(struct ft1000_device *ft1000dev, struct usb_interface *intf); +int ft1000_poll(void* dev_id); + +int ft1000_init_proc(struct net_device *dev); +void ft1000_cleanup_proc(struct ft1000_info *info); + + + +#endif diff --git a/drivers/staging/ft1000/ft1000-usb/ft3000.img b/drivers/staging/ft1000/ft1000-usb/ft3000.img new file mode 100644 index 00000000..7bef6bd3 Binary files /dev/null and b/drivers/staging/ft1000/ft1000-usb/ft3000.img differ diff --git a/drivers/staging/ft1000/ft1000.h b/drivers/staging/ft1000/ft1000.h new file mode 100644 index 00000000..03baa577 --- /dev/null +++ b/drivers/staging/ft1000/ft1000.h @@ -0,0 +1,252 @@ +/* + * Common structures and definitions for FT1000 Flarion Flash OFDM PCMCIA and USB devices + * + * Originally copyright (c) 2002 Flarion Technologies + * + */ + +#define DSPVERSZ 4 +#define HWSERNUMSZ 16 +#define SKUSZ 20 +#define EUISZ 8 +#define MODESZ 2 +#define CALVERSZ 2 +#define CALDATESZ 6 + +#define ELECTRABUZZ_ID 0 /* ASIC ID for Electrabuzz */ +#define MAGNEMITE_ID 0x1a01 /* ASIC ID for Magnemite */ + +/* MEMORY MAP common to both ELECTRABUZZ and MAGNEMITE */ +#define FT1000_REG_DPRAM_ADDR 0x000E /* DPADR - Dual Port Ram Indirect Address Register */ +#define FT1000_REG_SUP_CTRL 0x0020 /* HCTR - Host Control Register */ +#define FT1000_REG_SUP_STAT 0x0022 /* HSTAT - Host Status Register */ +#define FT1000_REG_RESET 0x0024 /* HCTR - Host Control Register */ +#define FT1000_REG_SUP_ISR 0x0026 /* HISR - Host Interrupt Status Register */ +#define FT1000_REG_SUP_IMASK 0x0028 /* HIMASK - Host Interrupt Mask */ +#define FT1000_REG_DOORBELL 0x002a /* DBELL - Door Bell Register */ +#define FT1000_REG_ASIC_ID 0x002e /* ASICID - ASIC Identification Number */ + +/* MEMORY MAP FOR ELECTRABUZZ ASIC */ +#define FT1000_REG_UFIFO_STAT 0x0000 /* UFSR - Uplink FIFO status register */ +#define FT1000_REG_UFIFO_BEG 0x0002 /* UFBR - Uplink FIFO beginning register */ +#define FT1000_REG_UFIFO_MID 0x0004 /* UFMR - Uplink FIFO middle register */ +#define FT1000_REG_UFIFO_END 0x0006 /* UFER - Uplink FIFO end register */ +#define FT1000_REG_DFIFO_STAT 0x0008 /* DFSR - Downlink FIFO status register */ +#define FT1000_REG_DFIFO 0x000A /* DFR - Downlink FIFO Register */ +#define FT1000_REG_DPRAM_DATA 0x000C /* DPRAM - Dual Port Indirect Data Register */ +#define FT1000_REG_WATERMARK 0x0010 /* WMARK - Watermark Register */ + +/* MEMORY MAP FOR MAGNEMITE */ +#define FT1000_REG_MAG_UFDR 0x0000 /* UFDR - Uplink FIFO Data Register (32-bits) */ +#define FT1000_REG_MAG_UFDRL 0x0000 /* UFDRL - Uplink FIFO Data Register low-word (16-bits) */ +#define FT1000_REG_MAG_UFDRH 0x0002 /* UFDRH - Uplink FIFO Data Register high-word (16-bits) */ +#define FT1000_REG_MAG_UFER 0x0004 /* UFER - Uplink FIFO End Register */ +#define FT1000_REG_MAG_UFSR 0x0006 /* UFSR - Uplink FIFO Status Register */ +#define FT1000_REG_MAG_DFR 0x0008 /* DFR - Downlink FIFO Register (32-bits) */ +#define FT1000_REG_MAG_DFRL 0x0008 /* DFRL - Downlink FIFO Register low-word (16-bits) */ +#define FT1000_REG_MAG_DFRH 0x000a /* DFRH - Downlink FIFO Register high-word (16-bits) */ +#define FT1000_REG_MAG_DFSR 0x000c /* DFSR - Downlink FIFO Status Register */ +#define FT1000_REG_MAG_DPDATA 0x0010 /* DPDATA - Dual Port RAM Indirect Data Register (32-bits) */ +#define FT1000_REG_MAG_DPDATAL 0x0010 /* DPDATAL - Dual Port RAM Indirect Data Register low-word (16-bits) */ +#define FT1000_REG_MAG_DPDATAH 0x0012 /* DPDATAH - Dual Port RAM Indirect Data Register high-word (16-bits) */ +#define FT1000_REG_MAG_WATERMARK 0x002c /* WMARK - Watermark Register */ +#define FT1000_REG_MAG_VERSION 0x0030 /* LLC Version */ + +/* Reserved Dual Port RAM offsets for Electrabuzz */ +#define FT1000_DPRAM_TX_BASE 0x0002 /* Host to PC Card Messaging Area */ +#define FT1000_DPRAM_RX_BASE 0x0800 /* PC Card to Host Messaging Area */ +#define FT1000_FIFO_LEN 0x07FC /* total length for DSP FIFO tracking */ +#define FT1000_HI_HO 0x07FE /* heartbeat with HI/HO */ +#define FT1000_DSP_STATUS 0x0FFE /* dsp status - non-zero is a request to reset dsp */ +#define FT1000_DSP_LED 0x0FFA /* dsp led status for PAD device */ +#define FT1000_DSP_CON_STATE 0x0FF8 /* DSP Connection Status Info */ +#define FT1000_DPRAM_FEFE 0x0002 /* location for dsp ready indicator */ +#define FT1000_DSP_TIMER0 0x1FF0 /* Timer Field from Basestation */ +#define FT1000_DSP_TIMER1 0x1FF2 /* Timer Field from Basestation */ +#define FT1000_DSP_TIMER2 0x1FF4 /* Timer Field from Basestation */ +#define FT1000_DSP_TIMER3 0x1FF6 /* Timer Field from Basestation */ + +/* Reserved Dual Port RAM offsets for Magnemite */ +#define FT1000_DPRAM_MAG_TX_BASE 0x0000 /* Host to PC Card Messaging Area */ +#define FT1000_DPRAM_MAG_RX_BASE 0x0200 /* PC Card to Host Messaging Area */ + +#define FT1000_MAG_FIFO_LEN 0x1FF /* total length for DSP FIFO tracking */ +#define FT1000_MAG_FIFO_LEN_INDX 0x1 /* low-word index */ +#define FT1000_MAG_HI_HO 0x1FF /* heartbeat with HI/HO */ +#define FT1000_MAG_HI_HO_INDX 0x0 /* high-word index */ +#define FT1000_MAG_DSP_LED 0x3FE /* dsp led status for PAD device */ +#define FT1000_MAG_DSP_LED_INDX 0x0 /* dsp led status for PAD device */ +#define FT1000_MAG_DSP_CON_STATE 0x3FE /* DSP Connection Status Info */ +#define FT1000_MAG_DSP_CON_STATE_INDX 0x1 /* DSP Connection Status Info */ +#define FT1000_MAG_DPRAM_FEFE 0x000 /* location for dsp ready indicator */ +#define FT1000_MAG_DPRAM_FEFE_INDX 0x0 /* location for dsp ready indicator */ +#define FT1000_MAG_DSP_TIMER0 0x3FC /* Timer Field from Basestation */ +#define FT1000_MAG_DSP_TIMER0_INDX 0x1 +#define FT1000_MAG_DSP_TIMER1 0x3FC /* Timer Field from Basestation */ +#define FT1000_MAG_DSP_TIMER1_INDX 0x0 +#define FT1000_MAG_DSP_TIMER2 0x3FD /* Timer Field from Basestation */ +#define FT1000_MAG_DSP_TIMER2_INDX 0x1 +#define FT1000_MAG_DSP_TIMER3 0x3FD /* Timer Field from Basestation */ +#define FT1000_MAG_DSP_TIMER3_INDX 0x0 +#define FT1000_MAG_TOTAL_LEN 0x200 +#define FT1000_MAG_TOTAL_LEN_INDX 0x1 +#define FT1000_MAG_PH_LEN 0x200 +#define FT1000_MAG_PH_LEN_INDX 0x0 +#define FT1000_MAG_PORT_ID 0x201 +#define FT1000_MAG_PORT_ID_INDX 0x0 + +#define HOST_INTF_LE 0x0 /* Host interface little endian mode */ +#define HOST_INTF_BE 0x1 /* Host interface big endian mode */ + +/* FT1000 to Host Doorbell assignments */ +#define FT1000_DB_DPRAM_RX 0x0001 /* this value indicates that DSP has data for host in DPRAM */ +#define FT1000_DB_DNLD_RX 0x0002 /* Downloader handshake doorbell */ +#define FT1000_ASIC_RESET_REQ 0x0004 /* DSP requesting host to reset the ASIC */ +#define FT1000_DSP_ASIC_RESET 0x0008 /* DSP indicating host that it will reset the ASIC */ +#define FT1000_DB_COND_RESET 0x0010 /* DSP request for a card reset. */ + +/* Host to FT1000 Doorbell assignments */ +#define FT1000_DB_DPRAM_TX 0x0100 /* this value indicates that host has data for DSP in DPRAM. */ +#define FT1000_DB_DNLD_TX 0x0200 /* Downloader handshake doorbell */ +#define FT1000_ASIC_RESET_DSP 0x0400 /* Responds to FT1000_ASIC_RESET_REQ */ +#define FT1000_DB_HB 0x1000 /* Indicates that supervisor has a heartbeat message for DSP. */ + +#define hi 0x6869 /* PC Card heartbeat values */ +#define ho 0x686f /* PC Card heartbeat values */ + +/* Magnemite specific defines */ +#define hi_mag 0x6968 /* Byte swap hi to avoid additional system call */ +#define ho_mag 0x6f68 /* Byte swap ho to avoid additional system call */ + +/* Bit field definitions for Host Interrupt Status Register */ +/* Indicate the cause of an interrupt. */ +#define ISR_EMPTY 0x00 /* no bits set */ +#define ISR_DOORBELL_ACK 0x01 /* Doorbell acknowledge from DSP */ +#define ISR_DOORBELL_PEND 0x02 /* Doorbell pending from DSP */ +#define ISR_RCV 0x04 /* Packet available in Downlink FIFO */ +#define ISR_WATERMARK 0x08 /* Watermark requirements satisfied */ + +/* Bit field definition for Host Interrupt Mask */ +#define ISR_MASK_NONE 0x0000 /* no bits set */ +#define ISR_MASK_DOORBELL_ACK 0x0001 /* Doorbell acknowledge mask */ +#define ISR_MASK_DOORBELL_PEND 0x0002 /* Doorbell pending mask */ +#define ISR_MASK_RCV 0x0004 /* Downlink Packet available mask */ +#define ISR_MASK_WATERMARK 0x0008 /* Watermark interrupt mask */ +#define ISR_MASK_ALL 0xffff /* Mask all interrupts */ +/* Default interrupt mask (Enable Doorbell pending and Packet available interrupts) */ +#define ISR_DEFAULT_MASK 0x7ff9 + +/* Bit field definition for Host Control Register */ +#define DSP_RESET_BIT 0x0001 /* Bit field to control dsp reset state */ + /* (0 = out of reset 1 = reset) */ +#define ASIC_RESET_BIT 0x0002 /* Bit field to control ASIC reset state */ + /* (0 = out of reset 1 = reset) */ +#define DSP_UNENCRYPTED 0x0004 +#define DSP_ENCRYPTED 0x0008 +#define EFUSE_MEM_DISABLE 0x0040 + +/* Application specific IDs */ +#define DSPID 0x20 +#define HOSTID 0x10 +#define DSPAIRID 0x90 +#define DRIVERID 0x00 +#define NETWORKID 0x20 + +/* Size of DPRAM Message */ +#define MAX_CMD_SQSIZE 1780 + +#define ENET_MAX_SIZE 1514 +#define ENET_HEADER_SIZE 14 + +#define SLOWQ_TYPE 0 +#define FASTQ_TYPE 1 + +#define MAX_DSP_SESS_REC 1024 + +#define DSP_QID_OFFSET 4 + +/* Driver message types */ +#define MEDIA_STATE 0x0010 +#define TIME_UPDATE 0x0020 +#define DSP_PROVISION 0x0030 +#define DSP_INIT_MSG 0x0050 +#define DSP_HIBERNATE 0x0060 +#define DSP_STORE_INFO 0x0070 +#define DSP_GET_INFO 0x0071 +#define GET_DRV_ERR_RPT_MSG 0x0073 +#define RSP_DRV_ERR_RPT_MSG 0x0074 + +/* Driver Error Messages for DSP */ +#define DSP_HB_INFO 0x7ef0 +#define DSP_FIFO_INFO 0x7ef1 +#define DSP_CONDRESET_INFO 0x7ef2 +#define DSP_CMDLEN_INFO 0x7ef3 +#define DSP_CMDPHCKSUM_INFO 0x7ef4 +#define DSP_PKTPHCKSUM_INFO 0x7ef5 +#define DSP_PKTLEN_INFO 0x7ef6 +#define DSP_USER_RESET 0x7ef7 +#define FIFO_FLUSH_MAXLIMIT 0x7ef8 +#define FIFO_FLUSH_BADCNT 0x7ef9 +#define FIFO_ZERO_LEN 0x7efa + +/* Pseudo Header structure */ +struct pseudo_hdr { + unsigned short length; /* length of msg body */ + unsigned char source; /* hardware source id */ + /* Host = 0x10 */ + /* Dsp = 0x20 */ + unsigned char destination; /* hardware destination id (refer to source) */ + unsigned char portdest; /* software destination port id */ + /* Host = 0x00 */ + /* Applicaton Broadcast = 0x10 */ + /* Network Stack = 0x20 */ + /* Dsp OAM = 0x80 */ + /* Dsp Airlink = 0x90 */ + /* Dsp Loader = 0xa0 */ + /* Dsp MIP = 0xb0 */ + unsigned char portsrc; /* software source port id (refer to portdest) */ + unsigned short sh_str_id; /* not used */ + unsigned char control; /* not used */ + unsigned char rsvd1; + unsigned char seq_num; /* message sequence number */ + unsigned char rsvd2; + unsigned short qos_class; /* not used */ + unsigned short checksum; /* pseudo header checksum */ +} __packed; + +struct drv_msg { + struct pseudo_hdr pseudo; + u16 type; + u16 length; + u8 data[0]; +} __packed; + +struct media_msg { + struct pseudo_hdr pseudo; + u16 type; + u16 length; + u16 state; + u32 ip_addr; + u32 net_mask; + u32 gateway; + u32 dns_1; + u32 dns_2; +} __packed; + +struct dsp_init_msg { + struct pseudo_hdr pseudo; + u16 type; + u16 length; + u8 DspVer[DSPVERSZ]; /* DSP version number */ + u8 HwSerNum[HWSERNUMSZ]; /* Hardware Serial Number */ + u8 Sku[SKUSZ]; /* SKU */ + u8 eui64[EUISZ]; /* EUI64 */ + u8 ProductMode[MODESZ]; /* Product Mode (Market/Production) */ + u8 RfCalVer[CALVERSZ]; /* Rf Calibration version */ + u8 RfCalDate[CALDATESZ]; /* Rf Calibration date */ +} __packed; + +struct prov_record { + struct list_head list; + u8 *pprov_data; +}; diff --git a/drivers/staging/iio/Documentation/dac/max517 b/drivers/staging/iio/Documentation/dac/max517 new file mode 100644 index 00000000..e60ec2f9 --- /dev/null +++ b/drivers/staging/iio/Documentation/dac/max517 @@ -0,0 +1,41 @@ +Kernel driver max517 +==================== + +Supported chips: + * Maxim MAX517, MAX518, MAX519 + Prefix: 'max517' + Datasheet: Publicly available at the Maxim website + http://www.maxim-ic.com/ + +Author: + Roland Stigge + +Description +----------- + +The Maxim MAX517/518/519 is an 8-bit DAC on the I2C bus. The following table +shows the different feature sets of the variants MAX517, MAX518 and MAX519: + +Feature MAX517 MAX518 MAX519 +-------------------------------------------------------------------------- +One output channel X +Two output channels X X +Simultaneous output updates X X +Supply voltage as reference X +Separate reference input X +Reference input for each DAC X + +Via the iio sysfs interface, there are three attributes available: out1_raw, +out2_raw and out12_raw. With out1_raw and out2_raw, the current output values +(0..255) of the DACs can be written to the device. out12_raw can be used to set +both output channel values simultaneously. + +With MAX517, only out1_raw is available. + +Via out1_scale (and where appropriate, out2_scale), the current scaling factor +in mV can be read. + +When the operating system goes to a power down state, the Power Down function +of the chip is activated, reducing the supply current to 4uA. + +On power-up, the device is in 0V-output state. diff --git a/drivers/staging/iio/Documentation/device.txt b/drivers/staging/iio/Documentation/device.txt new file mode 100644 index 00000000..8926f244 --- /dev/null +++ b/drivers/staging/iio/Documentation/device.txt @@ -0,0 +1,81 @@ +IIO Device drivers + +This is not intended to provide a comprehensive guide to writing an +IIO device driver. For further information see the drivers within the +subsystem. + +The crucial structure for device drivers in iio is iio_dev. + +First allocate one using: + +struct iio_dev *indio_dev = iio_allocate_device(sizeof(struct chip_state)); +where chip_state is a structure of local state data for this instance of +the chip. + +That data can be accessed using iio_priv(struct iio_dev *) + +Then fill in the following: + +- indio_dev->dev.parent + Struct device associated with the underlying hardware. +- indio_dev->name + Name of the device being driven - made available as the name + attribute in sysfs. + +- indio_dev->info + pointer to a structure with elements that tend to be fixed for + large sets of different parts supported by a given driver. + This contains: + * info->driver_module: + Set to THIS_MODULE. Used to ensure correct ownership + of various resources allocate by the core. + * info->num_interrupt_lines: + Number of event triggering hardware lines the device has. + * info->event_attrs: + Attributes used to enable / disable hardware events. + * info->attrs: + General device attributes. Typically used for the weird + and the wonderful bits not covered by the channel specification. + * info->read_raw: + Raw data reading function. Used for both raw channel access + and for associate parameters such as offsets and scales. + * info->write_raw: + Raw value writing function. Used for writable device values such + as DAC values and caliboffset. + * info->read_event_config: + Typically only set if there are some interrupt lines. This + is used to read if an on sensor event detector is enabled. + * info->write_event_config: + Enable / disable an on sensor event detector. + * info->read_event_value: + Read value associated with on sensor event detectors. Note that + the meaning of the returned value is dependent on the event + type. + * info->write_event_value: + Write the value associated with on sensor event detectors. E.g. + a threshold above which an interrupt occurs. Note that the + meaning of the value to be set is event type dependant. + +- indio_dev->modes: + Specify whether direct access and / or ring buffer access is supported. +- indio_dev->ring: + An optional associated buffer. +- indio_dev->pollfunc: + Poll function related elements. This controls what occurs when a trigger + to which this device is attached sends an event. +- indio_dev->channels: + Specification of device channels. Most attributes etc are built + form this spec. +- indio_dev->num_channels: + How many channels are there? + +Once these are set up, a call to iio_device_register(indio_dev), +will register the device with the iio core. + +Worth noting here is that, if a ring buffer is to be used, it can be +allocated prior to registering the device with the iio-core, but must +be registered afterwards (otherwise the whole parentage of devices +gets confused) + +On remove, iio_device_unregister(indio_dev) will remove the device from +the core, and iio_free_device will clean up. diff --git a/drivers/staging/iio/Documentation/generic_buffer.c b/drivers/staging/iio/Documentation/generic_buffer.c new file mode 100644 index 00000000..69a05b94 --- /dev/null +++ b/drivers/staging/iio/Documentation/generic_buffer.c @@ -0,0 +1,329 @@ +/* Industrialio buffer test code. + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is primarily intended as an example application. + * Reads the current buffer setup from sysfs and starts a short capture + * from the specified device, pretty printing the result after appropriate + * conversion. + * + * Command line parameters + * generic_buffer -n -t + * If trigger name is not specified the program assumes you want a dataready + * trigger associated with the device and goes looking for it. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iio_utils.h" + +/** + * size_from_channelarray() - calculate the storage size of a scan + * @channels: the channel info array + * @num_channels: size of the channel info array + * + * Has the side effect of filling the channels[i].location values used + * in processing the buffer output. + **/ +int size_from_channelarray(struct iio_channel_info *channels, int num_channels) +{ + int bytes = 0; + int i = 0; + while (i < num_channels) { + if (bytes % channels[i].bytes == 0) + channels[i].location = bytes; + else + channels[i].location = bytes - bytes%channels[i].bytes + + channels[i].bytes; + bytes = channels[i].location + channels[i].bytes; + i++; + } + return bytes; +} + +void print2byte(int input, struct iio_channel_info *info) +{ + /* First swap if incorrect endian */ + + if (info->be) + input = be16toh((uint_16t)input); + else + input = le16toh((uint_16t)input); + + /* shift before conversion to avoid sign extension + of left aligned data */ + input = input >> info->shift; + if (info->is_signed) { + int16_t val = input; + val &= (1 << info->bits_used) - 1; + val = (int16_t)(val << (16 - info->bits_used)) >> + (16 - info->bits_used); + printf("%05f ", val, + (float)(val + info->offset)*info->scale); + } else { + uint16_t val = input; + val &= (1 << info->bits_used) - 1; + printf("%05f ", ((float)val + info->offset)*info->scale); + } +} +/** + * process_scan() - print out the values in SI units + * @data: pointer to the start of the scan + * @infoarray: information about the channels. Note + * size_from_channelarray must have been called first to fill the + * location offsets. + * @num_channels: the number of active channels + **/ +void process_scan(char *data, + struct iio_channel_info *infoarray, + int num_channels) +{ + int k; + for (k = 0; k < num_channels; k++) + switch (infoarray[k].bytes) { + /* only a few cases implemented so far */ + case 2: + print2byte(*(uint16_t *)(data + infoarray[k].location), + &infoarray[k]); + break; + case 8: + if (infoarray[k].is_signed) { + int64_t val = *(int64_t *) + (data + + infoarray[k].location); + if ((val >> infoarray[k].bits_used) & 1) + val = (val & infoarray[k].mask) | + ~infoarray[k].mask; + /* special case for timestamp */ + if (infoarray[k].scale == 1.0f && + infoarray[k].offset == 0.0f) + printf(" %lld", val); + else + printf("%05f ", ((float)val + + infoarray[k].offset)* + infoarray[k].scale); + } + break; + default: + break; + } + printf("\n"); +} + +int main(int argc, char **argv) +{ + unsigned long num_loops = 2; + unsigned long timedelay = 1000000; + unsigned long buf_len = 128; + + + int ret, c, i, j, toread; + + FILE *fp_ev; + int fp; + + int num_channels; + char *trigger_name = NULL, *device_name = NULL; + char *dev_dir_name, *buf_dir_name; + + int datardytrigger = 1; + char *data; + ssize_t read_size; + int dev_num, trig_num; + char *buffer_access; + int scan_size; + int noevents = 0; + char *dummy; + + struct iio_channel_info *infoarray; + + while ((c = getopt(argc, argv, "l:w:c:et:n:")) != -1) { + switch (c) { + case 'n': + device_name = optarg; + break; + case 't': + trigger_name = optarg; + datardytrigger = 0; + break; + case 'e': + noevents = 1; + break; + case 'c': + num_loops = strtoul(optarg, &dummy, 10); + break; + case 'w': + timedelay = strtoul(optarg, &dummy, 10); + break; + case 'l': + buf_len = strtoul(optarg, &dummy, 10); + break; + case '?': + return -1; + } + } + + if (device_name == NULL) + return -1; + + /* Find the device requested */ + dev_num = find_type_by_name(device_name, "iio:device"); + if (dev_num < 0) { + printf("Failed to find the %s\n", device_name); + ret = -ENODEV; + goto error_ret; + } + printf("iio device number being used is %d\n", dev_num); + + asprintf(&dev_dir_name, "%siio:device%d", iio_dir, dev_num); + if (trigger_name == NULL) { + /* + * Build the trigger name. If it is device associated it's + * name is _dev[n] where n matches the device + * number found above + */ + ret = asprintf(&trigger_name, + "%s-dev%d", device_name, dev_num); + if (ret < 0) { + ret = -ENOMEM; + goto error_ret; + } + } + + /* Verify the trigger exists */ + trig_num = find_type_by_name(trigger_name, "trigger"); + if (trig_num < 0) { + printf("Failed to find the trigger %s\n", trigger_name); + ret = -ENODEV; + goto error_free_triggername; + } + printf("iio trigger number being used is %d\n", trig_num); + + /* + * Parse the files in scan_elements to identify what channels are + * present + */ + ret = build_channel_array(dev_dir_name, &infoarray, &num_channels); + if (ret) { + printf("Problem reading scan element information\n"); + printf("diag %s\n", dev_dir_name); + goto error_free_triggername; + } + + /* + * Construct the directory name for the associated buffer. + * As we know that the lis3l02dq has only one buffer this may + * be built rather than found. + */ + ret = asprintf(&buf_dir_name, + "%siio:device%d/buffer", iio_dir, dev_num); + if (ret < 0) { + ret = -ENOMEM; + goto error_free_triggername; + } + printf("%s %s\n", dev_dir_name, trigger_name); + /* Set the device trigger to be the data rdy trigger found above */ + ret = write_sysfs_string_and_verify("trigger/current_trigger", + dev_dir_name, + trigger_name); + if (ret < 0) { + printf("Failed to write current_trigger file\n"); + goto error_free_buf_dir_name; + } + + /* Setup ring buffer parameters */ + ret = write_sysfs_int("length", buf_dir_name, buf_len); + if (ret < 0) + goto error_free_buf_dir_name; + + /* Enable the buffer */ + ret = write_sysfs_int("enable", buf_dir_name, 1); + if (ret < 0) + goto error_free_buf_dir_name; + scan_size = size_from_channelarray(infoarray, num_channels); + data = malloc(scan_size*buf_len); + if (!data) { + ret = -ENOMEM; + goto error_free_buf_dir_name; + } + + ret = asprintf(&buffer_access, "/dev/iio:device%d", dev_num); + if (ret < 0) { + ret = -ENOMEM; + goto error_free_data; + } + + /* Attempt to open non blocking the access dev */ + fp = open(buffer_access, O_RDONLY | O_NONBLOCK); + if (fp == -1) { /*If it isn't there make the node */ + printf("Failed to open %s\n", buffer_access); + ret = -errno; + goto error_free_buffer_access; + } + + /* Wait for events 10 times */ + for (j = 0; j < num_loops; j++) { + if (!noevents) { + struct pollfd pfd = { + .fd = fp, + .events = POLLIN, + }; + + poll(&pfd, 1, -1); + toread = buf_len; + + } else { + usleep(timedelay); + toread = 64; + } + + read_size = read(fp, + data, + toread*scan_size); + if (read_size == -EAGAIN) { + printf("nothing available\n"); + continue; + } + for (i = 0; i < read_size/scan_size; i++) + process_scan(data + scan_size*i, + infoarray, + num_channels); + } + + /* Stop the ring buffer */ + ret = write_sysfs_int("enable", buf_dir_name, 0); + if (ret < 0) + goto error_close_buffer_access; + + /* Disconnect from the trigger - just write a dummy name.*/ + write_sysfs_string("trigger/current_trigger", + dev_dir_name, "NULL"); + +error_close_buffer_access: + close(fp); +error_free_data: + free(data); +error_free_buffer_access: + free(buffer_access); +error_free_buf_dir_name: + free(buf_dir_name); +error_free_triggername: + if (datardytrigger) + free(trigger_name); +error_ret: + return ret; +} diff --git a/drivers/staging/iio/Documentation/iio_event_monitor.c b/drivers/staging/iio/Documentation/iio_event_monitor.c new file mode 100644 index 00000000..0d21a277 --- /dev/null +++ b/drivers/staging/iio/Documentation/iio_event_monitor.c @@ -0,0 +1,241 @@ +/* Industrialio event test code. + * + * Copyright (c) 2011-2012 Lars-Peter Clausen + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is primarily intended as an example application. + * Reads the current buffer setup from sysfs and starts a short capture + * from the specified device, pretty printing the result after appropriate + * conversion. + * + * Usage: + * iio_event_monitor + * + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include "iio_utils.h" +#include "../events.h" + +static const char * const iio_chan_type_name_spec[] = { + [IIO_VOLTAGE] = "voltage", + [IIO_CURRENT] = "current", + [IIO_POWER] = "power", + [IIO_ACCEL] = "accel", + [IIO_ANGL_VEL] = "anglvel", + [IIO_MAGN] = "magn", + [IIO_LIGHT] = "illuminance", + [IIO_INTENSITY] = "intensity", + [IIO_PROXIMITY] = "proximity", + [IIO_TEMP] = "temp", + [IIO_INCLI] = "incli", + [IIO_ROT] = "rot", + [IIO_ANGL] = "angl", + [IIO_TIMESTAMP] = "timestamp", + [IIO_CAPACITANCE] = "capacitance", +}; + +static const char * const iio_ev_type_text[] = { + [IIO_EV_TYPE_THRESH] = "thresh", + [IIO_EV_TYPE_MAG] = "mag", + [IIO_EV_TYPE_ROC] = "roc", + [IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive", + [IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive", +}; + +static const char * const iio_ev_dir_text[] = { + [IIO_EV_DIR_EITHER] = "either", + [IIO_EV_DIR_RISING] = "rising", + [IIO_EV_DIR_FALLING] = "falling" +}; + +static const char * const iio_modifier_names[] = { + [IIO_MOD_X] = "x", + [IIO_MOD_Y] = "y", + [IIO_MOD_Z] = "z", + [IIO_MOD_LIGHT_BOTH] = "both", + [IIO_MOD_LIGHT_IR] = "ir", +}; + +static bool event_is_known(struct iio_event_data *event) +{ + enum iio_chan_type type = IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event->id); + enum iio_modifier mod = IIO_EVENT_CODE_EXTRACT_MODIFIER(event->id); + enum iio_event_type ev_type = IIO_EVENT_CODE_EXTRACT_TYPE(event->id); + enum iio_event_direction dir = IIO_EVENT_CODE_EXTRACT_DIR(event->id); + + switch (type) { + case IIO_VOLTAGE: + case IIO_CURRENT: + case IIO_POWER: + case IIO_ACCEL: + case IIO_ANGL_VEL: + case IIO_MAGN: + case IIO_LIGHT: + case IIO_INTENSITY: + case IIO_PROXIMITY: + case IIO_TEMP: + case IIO_INCLI: + case IIO_ROT: + case IIO_ANGL: + case IIO_TIMESTAMP: + case IIO_CAPACITANCE: + break; + default: + return false; + } + + switch (mod) { + case IIO_NO_MOD: + case IIO_MOD_X: + case IIO_MOD_Y: + case IIO_MOD_Z: + case IIO_MOD_LIGHT_BOTH: + case IIO_MOD_LIGHT_IR: + break; + default: + return false; + } + + switch (ev_type) { + case IIO_EV_TYPE_THRESH: + case IIO_EV_TYPE_MAG: + case IIO_EV_TYPE_ROC: + case IIO_EV_TYPE_THRESH_ADAPTIVE: + case IIO_EV_TYPE_MAG_ADAPTIVE: + break; + default: + return false; + } + + switch (dir) { + case IIO_EV_DIR_EITHER: + case IIO_EV_DIR_RISING: + case IIO_EV_DIR_FALLING: + break; + default: + return false; + } + + return true; +} + +static void print_event(struct iio_event_data *event) +{ + enum iio_chan_type type = IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event->id); + enum iio_modifier mod = IIO_EVENT_CODE_EXTRACT_MODIFIER(event->id); + enum iio_event_type ev_type = IIO_EVENT_CODE_EXTRACT_TYPE(event->id); + enum iio_event_direction dir = IIO_EVENT_CODE_EXTRACT_DIR(event->id); + int chan = IIO_EVENT_CODE_EXTRACT_CHAN(event->id); + int chan2 = IIO_EVENT_CODE_EXTRACT_CHAN2(event->id); + bool diff = IIO_EVENT_CODE_EXTRACT_DIFF(event->id); + + if (!event_is_known(event)) { + printf("Unknown event: time: %lld, id: %llx\n", + event->timestamp, event->id); + return; + } + + printf("Event: time: %lld, ", event->timestamp); + + if (mod != IIO_NO_MOD) { + printf("type: %s(%s), ", + iio_chan_type_name_spec[type], + iio_modifier_names[mod]); + } else { + printf("type: %s, ", + iio_chan_type_name_spec[type]); + } + + if (diff && chan >= 0 && chan2 >= 0) + printf("channel: %d-%d, ", chan, chan2); + else if (chan >= 0) + printf("channel: %d, ", chan); + + printf("evtype: %s, direction: %s\n", + iio_ev_type_text[ev_type], + iio_ev_dir_text[dir]); +} + +int main(int argc, char **argv) +{ + struct iio_event_data event; + const char *device_name; + char *chrdev_name; + int ret; + int dev_num; + int fd, event_fd; + + if (argc <= 1) { + printf("Usage: %s \n", argv[0]); + return -1; + } + + device_name = argv[1]; + + dev_num = find_type_by_name(device_name, "iio:device"); + if (dev_num >= 0) { + printf("Found IIO device with name %s with device number %d\n", + device_name, dev_num); + ret = asprintf(&chrdev_name, "/dev/iio:device%d", dev_num); + if (ret < 0) { + ret = -ENOMEM; + goto error_ret; + } + } else { + /* If we can't find a IIO device by name assume device_name is a + IIO chrdev */ + chrdev_name = strdup(device_name); + } + + fd = open(chrdev_name, 0); + if (fd == -1) { + fprintf(stdout, "Failed to open %s\n", chrdev_name); + ret = -errno; + goto error_free_chrdev_name; + } + + ret = ioctl(fd, IIO_GET_EVENT_FD_IOCTL, &event_fd); + + close(fd); + + if (ret == -1 || event_fd == -1) { + fprintf(stdout, "Failed to retrieve event fd\n"); + ret = -errno; + goto error_free_chrdev_name; + } + + while (true) { + ret = read(event_fd, &event, sizeof(event)); + if (ret == -1) { + if (errno == EAGAIN) { + printf("nothing available\n"); + continue; + } else { + perror("Failed to read event from device"); + ret = -errno; + break; + } + } + + print_event(&event); + } + + close(event_fd); +error_free_chrdev_name: + free(chrdev_name); +error_ret: + return ret; +} diff --git a/drivers/staging/iio/Documentation/iio_utils.h b/drivers/staging/iio/Documentation/iio_utils.h new file mode 100644 index 00000000..6f3a3922 --- /dev/null +++ b/drivers/staging/iio/Documentation/iio_utils.h @@ -0,0 +1,639 @@ +/* IIO - useful set of util functionality + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* Made up value to limit allocation sizes */ +#include +#include +#include +#include +#include +#include + +#define IIO_MAX_NAME_LENGTH 30 + +#define FORMAT_SCAN_ELEMENTS_DIR "%s/scan_elements" +#define FORMAT_TYPE_FILE "%s_type" + +const char *iio_dir = "/sys/bus/iio/devices/"; + +/** + * iioutils_break_up_name() - extract generic name from full channel name + * @full_name: the full channel name + * @generic_name: the output generic channel name + **/ +static int iioutils_break_up_name(const char *full_name, + char **generic_name) +{ + char *current; + char *w, *r; + char *working; + current = strdup(full_name); + working = strtok(current, "_\0"); + w = working; + r = working; + + while (*r != '\0') { + if (!isdigit(*r)) { + *w = *r; + w++; + } + r++; + } + *w = '\0'; + *generic_name = strdup(working); + free(current); + + return 0; +} + +/** + * struct iio_channel_info - information about a given channel + * @name: channel name + * @generic_name: general name for channel type + * @scale: scale factor to be applied for conversion to si units + * @offset: offset to be applied for conversion to si units + * @index: the channel index in the buffer output + * @bytes: number of bytes occupied in buffer output + * @mask: a bit mask for the raw output + * @is_signed: is the raw value stored signed + * @enabled: is this channel enabled + **/ +struct iio_channel_info { + char *name; + char *generic_name; + float scale; + float offset; + unsigned index; + unsigned bytes; + unsigned bits_used; + unsigned shift; + uint64_t mask; + unsigned be; + unsigned is_signed; + unsigned enabled; + unsigned location; +}; + +/** + * iioutils_get_type() - find and process _type attribute data + * @is_signed: output whether channel is signed + * @bytes: output how many bytes the channel storage occupies + * @mask: output a bit mask for the raw data + * @be: big endian + * @device_dir: the iio device directory + * @name: the channel name + * @generic_name: the channel type name + **/ +inline int iioutils_get_type(unsigned *is_signed, + unsigned *bytes, + unsigned *bits_used, + unsigned *shift, + uint64_t *mask, + unsigned *be, + const char *device_dir, + const char *name, + const char *generic_name) +{ + FILE *sysfsfp; + int ret; + DIR *dp; + char *scan_el_dir, *builtname, *builtname_generic, *filename = 0; + char signchar, endianchar; + unsigned padint; + const struct dirent *ent; + + ret = asprintf(&scan_el_dir, FORMAT_SCAN_ELEMENTS_DIR, device_dir); + if (ret < 0) { + ret = -ENOMEM; + goto error_ret; + } + ret = asprintf(&builtname, FORMAT_TYPE_FILE, name); + if (ret < 0) { + ret = -ENOMEM; + goto error_free_scan_el_dir; + } + ret = asprintf(&builtname_generic, FORMAT_TYPE_FILE, generic_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_free_builtname; + } + + dp = opendir(scan_el_dir); + if (dp == NULL) { + ret = -errno; + goto error_free_builtname_generic; + } + while (ent = readdir(dp), ent != NULL) + /* + * Do we allow devices to override a generic name with + * a specific one? + */ + if ((strcmp(builtname, ent->d_name) == 0) || + (strcmp(builtname_generic, ent->d_name) == 0)) { + ret = asprintf(&filename, + "%s/%s", scan_el_dir, ent->d_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_closedir; + } + sysfsfp = fopen(filename, "r"); + if (sysfsfp == NULL) { + printf("failed to open %s\n", filename); + ret = -errno; + goto error_free_filename; + } + + ret = fscanf(sysfsfp, + "%ce:%c%u/%u>>%u", + &endianchar, + &signchar, + bits_used, + &padint, shift); + if (ret < 0) { + printf("failed to pass scan type description\n"); + return ret; + } + *be = (endianchar == 'b'); + *bytes = padint / 8; + if (*bits_used == 64) + *mask = ~0; + else + *mask = (1 << *bits_used) - 1; + if (signchar == 's') + *is_signed = 1; + else + *is_signed = 0; + fclose(sysfsfp); + free(filename); + + filename = 0; + } +error_free_filename: + if (filename) + free(filename); +error_closedir: + closedir(dp); +error_free_builtname_generic: + free(builtname_generic); +error_free_builtname: + free(builtname); +error_free_scan_el_dir: + free(scan_el_dir); +error_ret: + return ret; +} + +inline int iioutils_get_param_float(float *output, + const char *param_name, + const char *device_dir, + const char *name, + const char *generic_name) +{ + FILE *sysfsfp; + int ret; + DIR *dp; + char *builtname, *builtname_generic; + char *filename = NULL; + const struct dirent *ent; + + ret = asprintf(&builtname, "%s_%s", name, param_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_ret; + } + ret = asprintf(&builtname_generic, + "%s_%s", generic_name, param_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_free_builtname; + } + dp = opendir(device_dir); + if (dp == NULL) { + ret = -errno; + goto error_free_builtname_generic; + } + while (ent = readdir(dp), ent != NULL) + if ((strcmp(builtname, ent->d_name) == 0) || + (strcmp(builtname_generic, ent->d_name) == 0)) { + ret = asprintf(&filename, + "%s/%s", device_dir, ent->d_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_closedir; + } + sysfsfp = fopen(filename, "r"); + if (!sysfsfp) { + ret = -errno; + goto error_free_filename; + } + fscanf(sysfsfp, "%f", output); + break; + } +error_free_filename: + if (filename) + free(filename); +error_closedir: + closedir(dp); +error_free_builtname_generic: + free(builtname_generic); +error_free_builtname: + free(builtname); +error_ret: + return ret; +} + +/** + * bsort_channel_array_by_index() - reorder so that the array is in index order + * + **/ + +inline void bsort_channel_array_by_index(struct iio_channel_info **ci_array, + int cnt) +{ + + struct iio_channel_info temp; + int x, y; + + for (x = 0; x < cnt; x++) + for (y = 0; y < (cnt - 1); y++) + if ((*ci_array)[y].index > (*ci_array)[y+1].index) { + temp = (*ci_array)[y + 1]; + (*ci_array)[y + 1] = (*ci_array)[y]; + (*ci_array)[y] = temp; + } +} + +/** + * build_channel_array() - function to figure out what channels are present + * @device_dir: the IIO device directory in sysfs + * @ + **/ +inline int build_channel_array(const char *device_dir, + struct iio_channel_info **ci_array, + int *counter) +{ + DIR *dp; + FILE *sysfsfp; + int count, temp, i; + struct iio_channel_info *current; + int ret; + const struct dirent *ent; + char *scan_el_dir; + char *filename; + + *counter = 0; + ret = asprintf(&scan_el_dir, FORMAT_SCAN_ELEMENTS_DIR, device_dir); + if (ret < 0) { + ret = -ENOMEM; + goto error_ret; + } + dp = opendir(scan_el_dir); + if (dp == NULL) { + ret = -errno; + goto error_free_name; + } + while (ent = readdir(dp), ent != NULL) + if (strcmp(ent->d_name + strlen(ent->d_name) - strlen("_en"), + "_en") == 0) { + ret = asprintf(&filename, + "%s/%s", scan_el_dir, ent->d_name); + if (ret < 0) { + ret = -ENOMEM; + goto error_close_dir; + } + sysfsfp = fopen(filename, "r"); + if (sysfsfp == NULL) { + ret = -errno; + free(filename); + goto error_close_dir; + } + fscanf(sysfsfp, "%u", &ret); + if (ret == 1) + (*counter)++; + fclose(sysfsfp); + free(filename); + } + *ci_array = malloc(sizeof(**ci_array) * (*counter)); + if (*ci_array == NULL) { + ret = -ENOMEM; + goto error_close_dir; + } + seekdir(dp, 0); + count = 0; + while (ent = readdir(dp), ent != NULL) { + if (strcmp(ent->d_name + strlen(ent->d_name) - strlen("_en"), + "_en") == 0) { + current = &(*ci_array)[count++]; + ret = asprintf(&filename, + "%s/%s", scan_el_dir, ent->d_name); + if (ret < 0) { + ret = -ENOMEM; + /* decrement count to avoid freeing name */ + count--; + goto error_cleanup_array; + } + sysfsfp = fopen(filename, "r"); + if (sysfsfp == NULL) { + free(filename); + ret = -errno; + goto error_cleanup_array; + } + fscanf(sysfsfp, "%u", ¤t->enabled); + fclose(sysfsfp); + + if (!current->enabled) { + free(filename); + count--; + continue; + } + + current->scale = 1.0; + current->offset = 0; + current->name = strndup(ent->d_name, + strlen(ent->d_name) - + strlen("_en")); + if (current->name == NULL) { + free(filename); + ret = -ENOMEM; + goto error_cleanup_array; + } + /* Get the generic and specific name elements */ + ret = iioutils_break_up_name(current->name, + ¤t->generic_name); + if (ret) { + free(filename); + goto error_cleanup_array; + } + ret = asprintf(&filename, + "%s/%s_index", + scan_el_dir, + current->name); + if (ret < 0) { + free(filename); + ret = -ENOMEM; + goto error_cleanup_array; + } + sysfsfp = fopen(filename, "r"); + fscanf(sysfsfp, "%u", ¤t->index); + fclose(sysfsfp); + free(filename); + /* Find the scale */ + ret = iioutils_get_param_float(¤t->scale, + "scale", + device_dir, + current->name, + current->generic_name); + if (ret < 0) + goto error_cleanup_array; + ret = iioutils_get_param_float(¤t->offset, + "offset", + device_dir, + current->name, + current->generic_name); + if (ret < 0) + goto error_cleanup_array; + ret = iioutils_get_type(¤t->is_signed, + ¤t->bytes, + ¤t->bits_used, + ¤t->shift, + ¤t->mask, + ¤t->be, + device_dir, + current->name, + current->generic_name); + } + } + + closedir(dp); + /* reorder so that the array is in index order */ + bsort_channel_array_by_index(ci_array, *counter); + + return 0; + +error_cleanup_array: + for (i = count - 1; i >= 0; i--) + free((*ci_array)[i].name); + free(*ci_array); +error_close_dir: + closedir(dp); +error_free_name: + free(scan_el_dir); +error_ret: + return ret; +} + +/** + * find_type_by_name() - function to match top level types by name + * @name: top level type instance name + * @type: the type of top level instance being sort + * + * Typical types this is used for are device and trigger. + **/ +inline int find_type_by_name(const char *name, const char *type) +{ + const struct dirent *ent; + int number, numstrlen; + + FILE *nameFile; + DIR *dp; + char thisname[IIO_MAX_NAME_LENGTH]; + char *filename; + + dp = opendir(iio_dir); + if (dp == NULL) { + printf("No industrialio devices available"); + return -ENODEV; + } + + while (ent = readdir(dp), ent != NULL) { + if (strcmp(ent->d_name, ".") != 0 && + strcmp(ent->d_name, "..") != 0 && + strlen(ent->d_name) > strlen(type) && + strncmp(ent->d_name, type, strlen(type)) == 0) { + numstrlen = sscanf(ent->d_name + strlen(type), + "%d", + &number); + /* verify the next character is not a colon */ + if (strncmp(ent->d_name + strlen(type) + numstrlen, + ":", + 1) != 0) { + filename = malloc(strlen(iio_dir) + + strlen(type) + + numstrlen + + 6); + if (filename == NULL) + return -ENOMEM; + sprintf(filename, "%s%s%d/name", + iio_dir, + type, + number); + nameFile = fopen(filename, "r"); + if (!nameFile) + continue; + free(filename); + fscanf(nameFile, "%s", thisname); + if (strcmp(name, thisname) == 0) + return number; + fclose(nameFile); + } + } + } + return -ENODEV; +} + +inline int _write_sysfs_int(char *filename, char *basedir, int val, int verify) +{ + int ret; + FILE *sysfsfp; + int test; + char *temp = malloc(strlen(basedir) + strlen(filename) + 2); + if (temp == NULL) + return -ENOMEM; + sprintf(temp, "%s/%s", basedir, filename); + sysfsfp = fopen(temp, "w"); + if (sysfsfp == NULL) { + printf("failed to open %s\n", temp); + ret = -errno; + goto error_free; + } + fprintf(sysfsfp, "%d", val); + fclose(sysfsfp); + if (verify) { + sysfsfp = fopen(temp, "r"); + if (sysfsfp == NULL) { + printf("failed to open %s\n", temp); + ret = -errno; + goto error_free; + } + fscanf(sysfsfp, "%d", &test); + if (test != val) { + printf("Possible failure in int write %d to %s%s\n", + val, + basedir, + filename); + ret = -1; + } + } +error_free: + free(temp); + return ret; +} + +int write_sysfs_int(char *filename, char *basedir, int val) +{ + return _write_sysfs_int(filename, basedir, val, 0); +} + +int write_sysfs_int_and_verify(char *filename, char *basedir, int val) +{ + return _write_sysfs_int(filename, basedir, val, 1); +} + +int _write_sysfs_string(char *filename, char *basedir, char *val, int verify) +{ + int ret = 0; + FILE *sysfsfp; + char *temp = malloc(strlen(basedir) + strlen(filename) + 2); + if (temp == NULL) { + printf("Memory allocation failed\n"); + return -ENOMEM; + } + sprintf(temp, "%s/%s", basedir, filename); + sysfsfp = fopen(temp, "w"); + if (sysfsfp == NULL) { + printf("Could not open %s\n", temp); + ret = -errno; + goto error_free; + } + fprintf(sysfsfp, "%s", val); + fclose(sysfsfp); + if (verify) { + sysfsfp = fopen(temp, "r"); + if (sysfsfp == NULL) { + printf("could not open file to verify\n"); + ret = -errno; + goto error_free; + } + fscanf(sysfsfp, "%s", temp); + if (strcmp(temp, val) != 0) { + printf("Possible failure in string write of %s " + "Should be %s " + "written to %s\%s\n", + temp, + val, + basedir, + filename); + ret = -1; + } + } +error_free: + free(temp); + + return ret; +} + +/** + * write_sysfs_string_and_verify() - string write, readback and verify + * @filename: name of file to write to + * @basedir: the sysfs directory in which the file is to be found + * @val: the string to write + **/ +int write_sysfs_string_and_verify(char *filename, char *basedir, char *val) +{ + return _write_sysfs_string(filename, basedir, val, 1); +} + +int write_sysfs_string(char *filename, char *basedir, char *val) +{ + return _write_sysfs_string(filename, basedir, val, 0); +} + +int read_sysfs_posint(char *filename, char *basedir) +{ + int ret; + FILE *sysfsfp; + char *temp = malloc(strlen(basedir) + strlen(filename) + 2); + if (temp == NULL) { + printf("Memory allocation failed"); + return -ENOMEM; + } + sprintf(temp, "%s/%s", basedir, filename); + sysfsfp = fopen(temp, "r"); + if (sysfsfp == NULL) { + ret = -errno; + goto error_free; + } + fscanf(sysfsfp, "%d\n", &ret); + fclose(sysfsfp); +error_free: + free(temp); + return ret; +} + +int read_sysfs_float(char *filename, char *basedir, float *val) +{ + float ret = 0; + FILE *sysfsfp; + char *temp = malloc(strlen(basedir) + strlen(filename) + 2); + if (temp == NULL) { + printf("Memory allocation failed"); + return -ENOMEM; + } + sprintf(temp, "%s/%s", basedir, filename); + sysfsfp = fopen(temp, "r"); + if (sysfsfp == NULL) { + ret = -errno; + goto error_free; + } + fscanf(sysfsfp, "%f\n", val); + fclose(sysfsfp); +error_free: + free(temp); + return ret; +} diff --git a/drivers/staging/iio/Documentation/inkernel.txt b/drivers/staging/iio/Documentation/inkernel.txt new file mode 100644 index 00000000..a05823e9 --- /dev/null +++ b/drivers/staging/iio/Documentation/inkernel.txt @@ -0,0 +1,58 @@ +Industrial I/O Subsystem in kernel consumers. + +The IIO subsystem can act as a layer under other elements of the kernel +providing a means of obtaining ADC type readings or of driving DAC type +signals. The functionality supported will grow as use cases arise. + +Describing the channel mapping (iio/machine.h) + +Channel associations are described using: + +struct iio_map { + const char *adc_channel_label; + const char *consumer_dev_name; + const char *consumer_channel; +}; + +adc_channel_label identifies the channel on the IIO device by being +matched against the datasheet_name field of the iio_chan_spec. + +consumer_dev_name allows identification of the consumer device. +This are then used to find the channel mapping from the consumer device (see +below). + +Finally consumer_channel is a string identifying the channel to the consumer. +(Perhaps 'battery_voltage' or similar). + +An array of these structures is then passed to the IIO driver. + +Supporting in kernel interfaces in the driver (driver.h) + +The driver must provide datasheet_name values for its channels and +must pass the iio_map structures and a pointer to its own iio_dev structure + on to the core via a call to iio_map_array_register. On removal, +iio_map_array_unregister reverses this process. + +The result of this is that the IIO core now has all the information needed +to associate a given channel with the consumer requesting it. + +Acting as an IIO consumer (consumer.h) + +The consumer first has to obtain an iio_channel structure from the core +by calling iio_channel_get(). The correct channel is identified by: + +* matching dev or dev_name against consumer_dev and consumer_dev_name +* matching consumer_channel against consumer_channel in the map + +There are then a number of functions that can be used to get information +about this channel such as it's current reading. + +e.g. +iio_st_read_channel_raw() - get a reading +iio_st_read_channel_type() - get the type of channel + +There is also provision for retrieving all of the channels associated +with a given consumer. This is useful for generic drivers such as +iio_hwmon where the number and naming of channels is not known by the +consumer driver. To do this, use iio_st_channel_get_all. + diff --git a/drivers/staging/iio/Documentation/overview.txt b/drivers/staging/iio/Documentation/overview.txt new file mode 100644 index 00000000..afc39ecd --- /dev/null +++ b/drivers/staging/iio/Documentation/overview.txt @@ -0,0 +1,57 @@ +Overview of IIO + +The Industrial I/O subsystem is intended to provide support for devices +that in some sense are analog to digital converters (ADCs). As many +actual devices combine some ADCs with digital to analog converters +(DACs) that functionality is also supported. + +The aim is to fill the gap between the somewhat similar hwmon and +input subsystems. Hwmon is very much directed at low sample rate +sensors used in applications such as fan speed control and temperature +measurement. Input is, as it's name suggests focused on input +devices. In some cases there is considerable overlap between these and +IIO. + +A typical device falling into this category would be connected via SPI +or I2C. + +Functionality of IIO + +* Basic device registration and handling. This is very similar to +hwmon with simple polled access to device channels via sysfs. + +* Event chrdevs. These are similar to input in that they provide a +route to user space for hardware triggered events. Such events include +threshold detectors, free-fall detectors and more complex action +detection. The events themselves are currently very simple with +merely an event code and a timestamp. Any data associated with the +event must be accessed via polling. + +Note: A given device may have one or more event channel. These events are +turned on or off (if possible) via sysfs interfaces. + +* Hardware buffer support. Some recent sensors have included +fifo / ring buffers on the sensor chip. These greatly reduce the load +on the host CPU by buffering relatively large numbers of data samples +based on an internal sampling clock. Examples include VTI SCA3000 +series and Analog Device ADXL345 accelerometers. Each buffer supports +polling to establish when data is available. + +* Trigger and software buffer support. In many data analysis +applications it it useful to be able to capture data based on some +external signal (trigger). These triggers might be a data ready +signal, a gpio line connected to some external system or an on +processor periodic interrupt. A single trigger may initialize data +capture or reading from a number of sensors. These triggers are +used in IIO to fill software buffers acting in a very similar +fashion to the hardware buffers described above. + +Other documentation: + +device.txt - elements of a typical device driver. + +trigger.txt - elements of a typical trigger driver. + +ring.txt - additional elements required for buffer support. + +sysfs-bus-iio - abi documentation file. diff --git a/drivers/staging/iio/Documentation/ring.txt b/drivers/staging/iio/Documentation/ring.txt new file mode 100644 index 00000000..e3380776 --- /dev/null +++ b/drivers/staging/iio/Documentation/ring.txt @@ -0,0 +1,47 @@ +Buffer support within IIO + +This document is intended as a general overview of the functionality +a buffer may supply and how it is specified within IIO. For more +specific information on a given buffer implementation, see the +comments in the source code. Note that some drivers allow buffer +implementation to be selected at compile time via Kconfig options. + +A given buffer implementation typically embeds a struct +iio_ring_buffer and it is a pointer to this that is provided to the +IIO core. Access to the embedding structure is typically done via +container_of functions. + +struct iio_ring_buffer contains a struct iio_ring_setup_ops *setup_ops +which in turn contains the 4 function pointers +(preenable, postenable, predisable and postdisable). +These are used to perform device specific steps on either side +of the core changing it's current mode to indicate that the buffer +is enabled or disabled (along with enabling triggering etc as appropriate). + +Also in struct iio_ring_buffer is a struct iio_ring_access_funcs. +The function pointers within here are used to allow the core to handle +as much buffer functionality as possible. Note almost all of these +are optional. + +store_to + If possible, push data to the buffer. + +read_last + If possible, get the most recent scan from the buffer (without removal). + This provides polling like functionality whilst the ring buffering is in + use without a separate read from the device. + +rip_first_n + The primary buffer reading function. Note that it may well not return + as much data as requested. + +request_update + If parameters have changed that require reinitialization or configuration of + the buffer this will trigger it. + +get_bytes_per_datum, set_bytes_per_datum + Get/set the number of bytes for a complete scan. (All samples + timestamp) + +get_length / set_length + Get/set the number of complete scans that may be held by the buffer. + diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio b/drivers/staging/iio/Documentation/sysfs-bus-iio new file mode 100644 index 00000000..46a995d6 --- /dev/null +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio @@ -0,0 +1,741 @@ +What: /sys/bus/iio/devices/iio:deviceX +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Hardware chip or device accessed by one communication port. + Corresponds to a grouping of sensor channels. X is the IIO + index of the device. + +What: /sys/bus/iio/devices/triggerX +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + An event driven driver of data capture to an in kernel buffer. + May be provided by a device driver that also has an IIO device + based on hardware generated events (e.g. data ready) or + provided by a separate driver for other hardware (e.g. + periodic timer, GPIO or high resolution timer). + Contains trigger type specific elements. These do not + generalize well and hence are not documented in this file. + X is the IIO index of the trigger. + +What: /sys/bus/iio/devices/iio:deviceX/buffer +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Directory of attributes relating to the buffer for the device. + +What: /sys/bus/iio/devices/iio:deviceX/name +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Description of the physical chip / device for device X. + Typically a part number. + +What: /sys/bus/iio/devices/iio:deviceX/sampling_frequency +What: /sys/bus/iio/devices/iio:deviceX/buffer/sampling_frequency +What: /sys/bus/iio/devices/triggerX/sampling_frequency +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Some devices have internal clocks. This parameter sets the + resulting sampling frequency. In many devices this + parameter has an effect on input filters etc rather than + simply controlling when the input is sampled. As this + effects datardy triggers, hardware buffers and the sysfs + direct access interfaces, it may be found in any of the + relevant directories. If it effects all of the above + then it is to be found in the base device directory. + +What: /sys/bus/iio/devices/iio:deviceX/sampling_frequency_available +What: /sys/.../iio:deviceX/buffer/sampling_frequency_available +What: /sys/bus/iio/devices/triggerX/sampling_frequency_available +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + When the internal sampling clock can only take a small + discrete set of values, this file lists those available. + +What: /sys/bus/iio/devices/iio:deviceX/oversampling_ratio +KernelVersion: 2.6.38 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent ADC oversampling. Controls the sampling ratio + of the digital filter if available. + +What: /sys/bus/iio/devices/iio:deviceX/oversampling_ratio_available +KernelVersion: 2.6.38 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent values supported by the oversampling filter. + +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_raw +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_supply_raw +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled no bias removal etc) voltage measurement from + channel Y. In special cases where the channel does not + correspond to externally available input one of the named + versions may be used. The number must always be specified and + unique to allow association with event codes. Units after + application of scale and offset are microvolts. + +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY-voltageZ_raw +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled) differential voltage measurement equivalent to + channel Y - channel Z where these channel numbers apply to the + physically equivalent inputs when non differential readings are + separately available. In differential only parts, then all that + is required is a consistent labeling. Units after application + of scale and offset are microvolts. + +What: /sys/bus/iio/devices/iio:deviceX/in_capacitanceY_raw +KernelVersion: 3.2 +Contact: linux-iio@vger.kernel.org +Description: + Raw capacitance measurement from channel Y. Units after + application of scale and offset are nanofarads. + +What: /sys/.../iio:deviceX/in_capacitanceY-in_capacitanceZ_raw +KernelVersion: 3.2 +Contact: linux-iio@vger.kernel.org +Description: + Raw differential capacitance measurement equivalent to + channel Y - channel Z where these channel numbers apply to the + physically equivalent inputs when non differential readings are + separately available. In differential only parts, then all that + is required is a consistent labeling. Units after application + of scale and offset are nanofarads.. + +What: /sys/bus/iio/devices/iio:deviceX/in_temp_raw +What: /sys/bus/iio/devices/iio:deviceX/in_tempX_raw +What: /sys/bus/iio/devices/iio:deviceX/in_temp_x_raw +What: /sys/bus/iio/devices/iio:deviceX/in_temp_y_raw +What: /sys/bus/iio/devices/iio:deviceX/in_temp_z_raw +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled no bias removal etc) temperature measurement. + It an axis is specified it generally means that the temperature + sensor is associated with one part of a compound device (e.g. + a gyroscope axis). Units after application of scale and offset + are milli degrees Celsuis. + +What: /sys/bus/iio/devices/iio:deviceX/in_tempX_input +KernelVersion: 2.6.38 +Contact: linux-iio@vger.kernel.org +Description: + Scaled temperature measurement in milli degrees Celsius. + +What: /sys/bus/iio/devices/iio:deviceX/in_accel_x_raw +What: /sys/bus/iio/devices/iio:deviceX/in_accel_y_raw +What: /sys/bus/iio/devices/iio:deviceX/in_accel_z_raw +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Acceleration in direction x, y or z (may be arbitrarily assigned + but should match other such assignments on device). + Has all of the equivalent parameters as per voltageY. Units + after application of scale and offset are m/s^2. + +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_x_raw +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_y_raw +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_z_raw +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Angular velocity about axis x, y or z (may be arbitrarily + assigned) Data converted by application of offset then scale to + radians per second. Has all the equivalent parameters as + per voltageY. Units after application of scale and offset are + radians per second. + +What: /sys/bus/iio/devices/iio:deviceX/in_incli_x_raw +What: /sys/bus/iio/devices/iio:deviceX/in_incli_y_raw +What: /sys/bus/iio/devices/iio:deviceX/in_incli_z_raw +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Inclination raw reading about axis x, y or z (may be + arbitrarily assigned). Data converted by application of offset + and scale to Degrees. + +What: /sys/bus/iio/devices/iio:deviceX/in_magn_x_raw +What: /sys/bus/iio/devices/iio:deviceX/in_magn_y_raw +What: /sys/bus/iio/devices/iio:deviceX/in_magn_z_raw +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Magnetic field along axis x, y or z (may be arbitrarily + assigned). Data converted by application of offset + then scale to Gauss. + +What: /sys/bus/iio/devices/iio:deviceX/in_accel_x_peak_raw +What: /sys/bus/iio/devices/iio:deviceX/in_accel_y_peak_raw +What: /sys/bus/iio/devices/iio:deviceX/in_accel_z_peak_raw +KernelVersion: 2.6.36 +Contact: linux-iio@vger.kernel.org +Description: + Highest value since some reset condition. These + attributes allow access to this and are otherwise + the direct equivalent of the Y[_name]_raw attributes. + +What: /sys/bus/iio/devices/iio:deviceX/in_accel_xyz_squared_peak_raw +KernelVersion: 2.6.36 +Contact: linux-iio@vger.kernel.org +Description: + A computed peak value based on the sum squared magnitude of + the underlying value in the specified directions. + +What: /sys/bus/iio/devices/iio:deviceX/in_accel_offset +What: /sys/bus/iio/devices/iio:deviceX/in_accel_x_offset +What: /sys/bus/iio/devices/iio:deviceX/in_accel_y_offset +What: /sys/bus/iio/devices/iio:deviceX/in_accel_z_offset +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_offset +What: /sys/bus/iio/devices/iio:deviceX/in_voltage_offset +What: /sys/bus/iio/devices/iio:deviceX/in_tempY_offset +What: /sys/bus/iio/devices/iio:deviceX/in_temp_offset +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + If known for a device, offset to be added to [Y]_raw prior + to scaling by [Y]_scale in order to obtain value in the + units as specified in [y]_raw documentation. + Not present if the offset is always 0 or unknown. If Y or + axis is not present, then the offset applies to all + in channels of . + May be writable if a variable offset can be applied on the + device. Note that this is different to calibbias which + is for devices (or drivers) that apply offsets to compensate + for variation between different instances of the part, typically + adjusted by using some hardware supported calibration procedure. + Calibbias is applied internally, offset is applied in userspace + to the _raw output. + +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_scale +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_supply_scale +What: /sys/bus/iio/devices/iio:deviceX/in_voltage_scale +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_scale +What: /sys/bus/iio/devices/iio:deviceX/in_accel_scale +What: /sys/bus/iio/devices/iio:deviceX/in_accel_peak_scale +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_scale +What: /sys/bus/iio/devices/iio:deviceX/in_magn_scale +What: /sys/bus/iio/devices/iio:deviceX/in_magn_x_scale +What: /sys/bus/iio/devices/iio:deviceX/in_magn_y_scale +What: /sys/bus/iio/devices/iio:deviceX/in_magn_z_scale +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + If known for a device, scale to be applied to Y[_name]_raw + post addition of [Y][_name]_offset in order to obtain the + measured value in units as specified in + [Y][_name]_raw documentation.. If shared across all in + channels then Y and are not present and the value is + called [Y][_name]_scale. The peak modifier means this + value is applied to Y[_name]_peak_raw values. + +What: /sys/bus/iio/devices/iio:deviceX/in_accel_x_calibbias +What: /sys/bus/iio/devices/iio:deviceX/in_accel_y_calibbias +What: /sys/bus/iio/devices/iio:deviceX/in_accel_z_calibbias +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_x_calibbias +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_y_calibbias +What: /sys/bus/iio/devices/iio:deviceX/in_anglvel_z_calibbias +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Hardware applied calibration offset. (assumed to fix production + inaccuracies). + +What /sys/bus/iio/devices/iio:deviceX/in_voltageY_calibscale +What /sys/bus/iio/devices/iio:deviceX/in_voltageY_supply_calibscale +What /sys/bus/iio/devices/iio:deviceX/in_voltage_calibscale +What /sys/bus/iio/devices/iio:deviceX/in_accel_x_calibscale +What /sys/bus/iio/devices/iio:deviceX/in_accel_y_calibscale +What /sys/bus/iio/devices/iio:deviceX/in_accel_z_calibscale +What /sys/bus/iio/devices/iio:deviceX/in_anglvel_x_calibscale +What /sys/bus/iio/devices/iio:deviceX/in_anglvel_y_calibscale +What /sys/bus/iio/devices/iio:deviceX/in_anglvel_z_calibscale +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Hardware applied calibration scale factor. (assumed to fix + production inaccuracies). If shared across all channels, + _calibscale is used. + +What: /sys/bus/iio/devices/iio:deviceX/in_accel_scale_available +What: /sys/.../iio:deviceX/in_voltageX_scale_available +What: /sys/.../iio:deviceX/in_voltage-voltage_scale_available +What: /sys/.../iio:deviceX/out_voltageX_scale_available +What: /sys/.../iio:deviceX/in_capacitance_scale_available +KernelVersion: 2.635 +Contact: linux-iio@vger.kernel.org +Description: + If a discrete set of scale values are available, they + are listed in this attribute. + +What: /sys/.../in_accel_filter_low_pass_3db_frequency +What: /sys/.../in_magn_filter_low_pass_3db_frequency +What: /sys/.../in_anglvel_filter_low_pass_3db_frequency +KernelVersion: 3.2 +Contact: linux-iio@vger.kernel.org +Description: + If a known or controllable low pass filter is applied + to the underlying data channel, then this parameter + gives the 3dB frequency of the filter in Hz. + +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_raw +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled, no bias etc.) output voltage for + channel Y. The number must always be specified and + unique if the output corresponds to a single channel. + +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY&Z_raw +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled, no bias etc.) output voltage for an aggregate of + channel Y, channel Z, etc. This interface is available in cases + where a single output sets the value for multiple channels + simultaneously. + +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_powerdown_mode +What: /sys/bus/iio/devices/iio:deviceX/out_voltage_powerdown_mode +KernelVersion: 2.6.38 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the output powerdown mode. + DAC output stage is disconnected from the amplifier and + 1kohm_to_gnd: connected to ground via an 1kOhm resistor + 100kohm_to_gnd: connected to ground via an 100kOhm resistor + three_state: left floating + For a list of available output power down options read + outX_powerdown_mode_available. If Y is not present the + mode is shared across all outputs. + +What: /sys/.../iio:deviceX/out_votlageY_powerdown_mode_available +What: /sys/.../iio:deviceX/out_voltage_powerdown_mode_available +KernelVersion: 2.6.38 +Contact: linux-iio@vger.kernel.org +Description: + Lists all available output power down modes. + If Y is not present the mode is shared across all outputs. + +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_powerdown +What: /sys/bus/iio/devices/iio:deviceX/out_voltage_powerdown +KernelVersion: 2.6.38 +Contact: linux-iio@vger.kernel.org +Description: + Writing 1 causes output Y to enter the power down mode specified + by the corresponding outY_powerdown_mode. Clearing returns to + normal operation. Y may be suppressed if all outputs are + controlled together. + +What: /sys/bus/iio/devices/iio:deviceX/events +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Configuration of which hardware generated events are passed up + to user-space. + +What: /sys/.../iio:deviceX/events/in_accel_x_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_accel_x_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_accel_y_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_accel_y_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_accel_z_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_accel_z_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_anglvel_x_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_anglvel_x_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_anglvel_y_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_anglvel_y_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_anglvel_z_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_anglvel_z_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_magn_x_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_magn_x_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_magn_y_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_magn_y_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_magn_z_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_magn_z_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_voltageY_supply_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_voltageY_supply_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_voltageY_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_voltageY_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_tempY_thresh_rising_en +What: /sys/.../iio:deviceX/events/in_tempY_thresh_falling_en +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Event generated when channel passes a threshold in the specified + (_rising|_falling) direction. If the direction is not specified, + then either the device will report an event which ever direction + a single threshold value is passed in (e.g. + [Y][_name]__thresh_value) or + [Y][_name]__thresh_rising_value and + [Y][_name]__thresh_falling_value may take + different values, but the device can only enable both thresholds + or neither. + Note the driver will assume the last p events requested are + to be enabled where p is however many it supports (which may + vary depending on the exact set requested. So if you want to be + sure you have set what you think you have, check the contents of + these attributes after everything is configured. Drivers may + have to buffer any parameters so that they are consistent when + a given event type is enabled a future point (and not those for + whatever event was previously enabled). + +What: /sys/.../iio:deviceX/events/in_accel_x_roc_rising_en +What: /sys/.../iio:deviceX/events/in_accel_x_roc_falling_en +What: /sys/.../iio:deviceX/events/in_accel_y_roc_rising_en +What: /sys/.../iio:deviceX/events/in_accel_y_roc_falling_en +What: /sys/.../iio:deviceX/events/in_accel_z_roc_rising_en +What: /sys/.../iio:deviceX/events/in_accel_z_roc_falling_en +What: /sys/.../iio:deviceX/events/in_anglvel_x_roc_rising_en +What: /sys/.../iio:deviceX/events/in_anglvel_x_roc_falling_en +What: /sys/.../iio:deviceX/events/in_anglvel_y_roc_rising_en +What: /sys/.../iio:deviceX/events/in_anglvel_y_roc_falling_en +What: /sys/.../iio:deviceX/events/in_anglvel_z_roc_rising_en +What: /sys/.../iio:deviceX/events/in_anglvel_z_roc_falling_en +What: /sys/.../iio:deviceX/events/in_magn_x_roc_rising_en +What: /sys/.../iio:deviceX/events/in_magn_x_roc_falling_en +What: /sys/.../iio:deviceX/events/in_magn_y_roc_rising_en +What: /sys/.../iio:deviceX/events/in_magn_y_roc_falling_en +What: /sys/.../iio:deviceX/events/in_magn_z_roc_rising_en +What: /sys/.../iio:deviceX/events/in_magn_z_roc_falling_en +What: /sys/.../iio:deviceX/events/in_voltageY_supply_roc_rising_en +What: /sys/.../iio:deviceX/events/in_voltageY_supply_roc_falling_en +What: /sys/.../iio:deviceX/events/in_voltageY_roc_rising_en +What: /sys/.../iio:deviceX/events/in_voltageY_roc_falling_en +What: /sys/.../iio:deviceX/events/in_tempY_roc_rising_en +What: /sys/.../iio:deviceX/events/in_tempY_roc_falling_en +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Event generated when channel passes a threshold on the rate of + change (1st differential) in the specified (_rising|_falling) + direction. If the direction is not specified, then either the + device will report an event which ever direction a single + threshold value is passed in (e.g. + [Y][_name]__roc_value) or + [Y][_name]__roc_rising_value and + [Y][_name]__roc_falling_value may take + different values, but the device can only enable both rate of + change thresholds or neither. + Note the driver will assume the last p events requested are + to be enabled where p is however many it supports (which may + vary depending on the exact set requested. So if you want to be + sure you have set what you think you have, check the contents of + these attributes after everything is configured. Drivers may + have to buffer any parameters so that they are consistent when + a given event type is enabled a future point (and not those for + whatever event was previously enabled). + +What: /sys/.../events/in_accel_x_raw_thresh_rising_value +What: /sys/.../events/in_accel_x_raw_thresh_falling_value +What: /sys/.../events/in_accel_y_raw_thresh_rising_value +What: /sys/.../events/in_accel_y_raw_thresh_falling_value +What: /sys/.../events/in_accel_z_raw_thresh_rising_value +What: /sys/.../events/in_accel_z_raw_thresh_falling_value +What: /sys/.../events/in_anglvel_x_raw_thresh_rising_value +What: /sys/.../events/in_anglvel_x_raw_thresh_falling_value +What: /sys/.../events/in_anglvel_y_raw_thresh_rising_value +What: /sys/.../events/in_anglvel_y_raw_thresh_falling_value +What: /sys/.../events/in_anglvel_z_raw_thresh_rising_value +What: /sys/.../events/in_anglvel_z_raw_thresh_falling_value +What: /sys/.../events/in_magn_x_raw_thresh_rising_value +What: /sys/.../events/in_magn_x_raw_thresh_falling_value +What: /sys/.../events/in_magn_y_raw_thresh_rising_value +What: /sys/.../events/in_magn_y_raw_thresh_falling_value +What: /sys/.../events/in_magn_z_raw_thresh_rising_value +What: /sys/.../events/in_magn_z_raw_thresh_falling_value +What: /sys/.../events/in_voltageY_supply_raw_thresh_rising_value +What: /sys/.../events/in_voltageY_supply_raw_thresh_falling_value +What: /sys/.../events/in_voltageY_raw_thresh_falling_value +What: /sys/.../events/in_voltageY_raw_thresh_falling_value +What: /sys/.../events/in_tempY_raw_thresh_falling_value +What: /sys/.../events/in_tempY_raw_thresh_falling_value +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the value of threshold that the device is comparing + against for the events enabled by + Y[_name]_thresh[_rising|falling]_en. + If separate attributes exist for the two directions, but + direction is not specified for this attribute, then a single + threshold value applies to both directions. + The raw or input element of the name indicates whether the + value is in raw device units or in processed units (as _raw + and _input do on sysfs direct channel read attributes). + +What: /sys/.../events/in_accel_x_raw_roc_rising_value +What: /sys/.../events/in_accel_x_raw_roc_falling_value +What: /sys/.../events/in_accel_y_raw_roc_rising_value +What: /sys/.../events/in_accel_y_raw_roc_falling_value +What: /sys/.../events/in_accel_z_raw_roc_rising_value +What: /sys/.../events/in_accel_z_raw_roc_falling_value +What: /sys/.../events/in_anglvel_x_raw_roc_rising_value +What: /sys/.../events/in_anglvel_x_raw_roc_falling_value +What: /sys/.../events/in_anglvel_y_raw_roc_rising_value +What: /sys/.../events/in_anglvel_y_raw_roc_falling_value +What: /sys/.../events/in_anglvel_z_raw_roc_rising_value +What: /sys/.../events/in_anglvel_z_raw_roc_falling_value +What: /sys/.../events/in_magn_x_raw_roc_rising_value +What: /sys/.../events/in_magn_x_raw_roc_falling_value +What: /sys/.../events/in_magn_y_raw_roc_rising_value +What: /sys/.../events/in_magn_y_raw_roc_falling_value +What: /sys/.../events/in_magn_z_raw_roc_rising_value +What: /sys/.../events/in_magn_z_raw_roc_falling_value +What: /sys/.../events/in_voltageY_supply_raw_roc_rising_value +What: /sys/.../events/in_voltageY_supply_raw_roc_falling_value +What: /sys/.../events/in_voltageY_raw_roc_falling_value +What: /sys/.../events/in_voltageY_raw_roc_falling_value +What: /sys/.../events/in_tempY_raw_roc_falling_value +What: /sys/.../events/in_tempY_raw_roc_falling_value +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the value of rate of change threshold that the + device is comparing against for the events enabled by + [Y][_name]_roc[_rising|falling]_en. + If separate attributes exist for the two directions, + but direction is not specified for this attribute, + then a single threshold value applies to both directions. + The raw or input element of the name indicates whether the + value is in raw device units or in processed units (as _raw + and _input do on sysfs direct channel read attributes). + +What: /sys/.../events/in_accel_x_thresh_rising_period +What: /sys/.../events/in_accel_x_thresh_falling_period +hat: /sys/.../events/in_accel_x_roc_rising_period +What: /sys/.../events/in_accel_x_roc_falling_period +What: /sys/.../events/in_accel_y_thresh_rising_period +What: /sys/.../events/in_accel_y_thresh_falling_period +What: /sys/.../events/in_accel_y_roc_rising_period +What: /sys/.../events/in_accel_y_roc_falling_period +What: /sys/.../events/in_accel_z_thresh_rising_period +What: /sys/.../events/in_accel_z_thresh_falling_period +What: /sys/.../events/in_accel_z_roc_rising_period +What: /sys/.../events/in_accel_z_roc_falling_period +What: /sys/.../events/in_anglvel_x_thresh_rising_period +What: /sys/.../events/in_anglvel_x_thresh_falling_period +What: /sys/.../events/in_anglvel_x_roc_rising_period +What: /sys/.../events/in_anglvel_x_roc_falling_period +What: /sys/.../events/in_anglvel_y_thresh_rising_period +What: /sys/.../events/in_anglvel_y_thresh_falling_period +What: /sys/.../events/in_anglvel_y_roc_rising_period +What: /sys/.../events/in_anglvel_y_roc_falling_period +What: /sys/.../events/in_anglvel_z_thresh_rising_period +What: /sys/.../events/in_anglvel_z_thresh_falling_period +What: /sys/.../events/in_anglvel_z_roc_rising_period +What: /sys/.../events/in_anglvel_z_roc_falling_period +What: /sys/.../events/in_magn_x_thresh_rising_period +What: /sys/.../events/in_magn_x_thresh_falling_period +What: /sys/.../events/in_magn_x_roc_rising_period +What: /sys/.../events/in_magn_x_roc_falling_period +What: /sys/.../events/in_magn_y_thresh_rising_period +What: /sys/.../events/in_magn_y_thresh_falling_period +What: /sys/.../events/in_magn_y_roc_rising_period +What: /sys/.../events/in_magn_y_roc_falling_period +What: /sys/.../events/in_magn_z_thresh_rising_period +What: /sys/.../events/in_magn_z_thresh_falling_period +What: /sys/.../events/in_magn_z_roc_rising_period +What: /sys/.../events/in_magn_z_roc_falling_period +What: /sys/.../events/in_voltageY_supply_thresh_rising_period +What: /sys/.../events/in_voltageY_supply_thresh_falling_period +What: /sys/.../events/in_voltageY_supply_roc_rising_period +What: /sys/.../events/in_voltageY_supply_roc_falling_period +What: /sys/.../events/in_voltageY_thresh_rising_period +What: /sys/.../events/in_voltageY_thresh_falling_period +What: /sys/.../events/in_voltageY_roc_rising_period +What: /sys/.../events/in_voltageY_roc_falling_period +What: /sys/.../events/in_tempY_thresh_rising_period +What: /sys/.../events/in_tempY_thresh_falling_period +What: /sys/.../events/in_tempY_roc_rising_period +What: /sys/.../events/in_tempY_roc_falling_period +What: /sys/.../events/in_accel_x&y&z_mag_falling_period +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Period of time (in seconds) for which the condition must be + met before an event is generated. If direction is not + specified then this period applies to both directions. + +What: /sys/.../iio:deviceX/events/in_accel_mag_en +What: /sys/.../iio:deviceX/events/in_accel_mag_rising_en +What: /sys/.../iio:deviceX/events/in_accel_mag_falling_en +What: /sys/.../iio:deviceX/events/in_accel_x_mag_en +What: /sys/.../iio:deviceX/events/in_accel_x_mag_rising_en +What: /sys/.../iio:deviceX/events/in_accel_x_mag_falling_en +What: /sys/.../iio:deviceX/events/in_accel_y_mag_en +What: /sys/.../iio:deviceX/events/in_accel_y_mag_rising_en +What: /sys/.../iio:deviceX/events/in_accel_y_mag_falling_en +What: /sys/.../iio:deviceX/events/in_accel_z_mag_en +What: /sys/.../iio:deviceX/events/in_accel_z_mag_rising_en +What: /sys/.../iio:deviceX/events/in_accel_z_mag_falling_en +What: /sys/.../iio:deviceX/events/in_accel_x&y&z_mag_rising_en +What: /sys/.../iio:deviceX/events/in_accel_x&y&z_mag_falling_en +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Similar to in_accel_x_thresh[_rising|_falling]_en, but here the + magnitude of the channel is compared to the threshold, not its + signed value. + +What: /sys/.../events/in_accel_raw_mag_value +What: /sys/.../events/in_accel_x_raw_mag_rising_value +What: /sys/.../events/in_accel_y_raw_mag_rising_value +What: /sys/.../events/in_accel_z_raw_mag_rising_value +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + The value to which the magnitude of the channel is compared. If + number or direction is not specified, applies to all channels of + this type. + +What: /sys/bus/iio/devices/iio:deviceX/trigger/current_trigger +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + The name of the trigger source being used, as per string given + in /sys/class/iio/triggerY/name. + +What: /sys/bus/iio/devices/iio:deviceX/buffer/length +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Number of scans contained by the buffer. + +What: /sys/bus/iio/devices/iio:deviceX/buffer/bytes_per_datum +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Bytes per scan. Due to alignment fun, the scan may be larger + than implied directly by the scan_element parameters. + +What: /sys/bus/iio/devices/iio:deviceX/buffer/enable +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + Actually start the buffer capture up. Will start trigger + if first device and appropriate. + +What: /sys/bus/iio/devices/iio:deviceX/buffer/scan_elements +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Directory containing interfaces for elements that will be + captured for a single triggered sample set in the buffer. + +What: /sys/.../buffer/scan_elements/in_accel_x_en +What: /sys/.../buffer/scan_elements/in_accel_y_en +What: /sys/.../buffer/scan_elements/in_accel_z_en +What: /sys/.../buffer/scan_elements/in_anglvel_x_en +What: /sys/.../buffer/scan_elements/in_anglvel_y_en +What: /sys/.../buffer/scan_elements/in_anglvel_z_en +What: /sys/.../buffer/scan_elements/in_magn_x_en +What: /sys/.../buffer/scan_elements/in_magn_y_en +What: /sys/.../buffer/scan_elements/in_magn_z_en +What: /sys/.../buffer/scan_elements/in_timestamp_en +What: /sys/.../buffer/scan_elements/in_voltageY_supply_en +What: /sys/.../buffer/scan_elements/in_voltageY_en +What: /sys/.../buffer/scan_elements/in_voltageY-voltageZ_en +What: /sys/.../buffer/scan_elements/in_incli_x_en +What: /sys/.../buffer/scan_elements/in_incli_y_en +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Scan element control for triggered data capture. + +What: /sys/.../buffer/scan_elements/in_accel_type +What: /sys/.../buffer/scan_elements/in_anglvel_type +What: /sys/.../buffer/scan_elements/in_magn_type +What: /sys/.../buffer/scan_elements/in_incli_type +What: /sys/.../buffer/scan_elements/in_voltageY_type +What: /sys/.../buffer/scan_elements/in_voltage-in_type +What: /sys/.../buffer/scan_elements/in_voltageY_supply_type +What: /sys/.../buffer/scan_elements/in_timestamp_type +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Description of the scan element data storage within the buffer + and hence the form in which it is read from user-space. + Form is [be|le]:[s|u]bits/storagebits[>>shift]. + be or le specifies big or little endian. s or u specifies if + signed (2's complement) or unsigned. bits is the number of bits + of data and storagebits is the space (after padding) that it + occupies in the buffer. shift if specified, is the shift that + needs to be applied prior to masking out unused bits. Some + devices put their data in the middle of the transferred elements + with additional information on both sides. Note that some + devices will have additional information in the unused bits + so to get a clean value, the bits value must be used to mask + the buffer output value appropriately. The storagebits value + also specifies the data alignment. So s48/64>>2 will be a + signed 48 bit integer stored in a 64 bit location aligned to + a a64 bit boundary. To obtain the clean value, shift right 2 + and apply a mask to zero the top 16 bits of the result. + For other storage combinations this attribute will be extended + appropriately. + +What: /sys/.../buffer/scan_elements/in_accel_type_available +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + If the type parameter can take one of a small set of values, + this attribute lists them. + +What: /sys/.../buffer/scan_elements/in_voltageY_index +What: /sys/.../buffer/scan_elements/in_voltageY_supply_index +What: /sys/.../buffer/scan_elements/in_accel_x_index +What: /sys/.../buffer/scan_elements/in_accel_y_index +What: /sys/.../buffer/scan_elements/in_accel_z_index +What: /sys/.../buffer/scan_elements/in_anglvel_x_index +What: /sys/.../buffer/scan_elements/in_anglvel_y_index +What: /sys/.../buffer/scan_elements/in_anglvel_z_index +What: /sys/.../buffer/scan_elements/in_magn_x_index +What: /sys/.../buffer/scan_elements/in_magn_y_index +What: /sys/.../buffer/scan_elements/in_magn_z_index +What: /sys/.../buffer/scan_elements/in_incli_x_index +What: /sys/.../buffer/scan_elements/in_incli_y_index +What: /sys/.../buffer/scan_elements/in_timestamp_index +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + A single positive integer specifying the position of this + scan element in the buffer. Note these are not dependent on + what is enabled and may not be contiguous. Thus for user-space + to establish the full layout these must be used in conjunction + with all _en attributes to establish which channels are present, + and the relevant _type attributes to establish the data storage + format. + +What: /sys/.../iio:deviceX/in_anglvel_z_quadrature_correction_raw +KernelVersion: 2.6.38 +Contact: linux-iio@vger.kernel.org +Description: + This attribute is used to read the amount of quadrature error + present in the device at a given time. + +What: /sys/.../iio:deviceX/ac_excitation_en +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + This attribute, if available, is used to enable the AC + excitation mode found on some converters. In ac excitation mode, + the polarity of the excitation voltage is reversed on + alternate cycles, to eliminate DC errors. + +What: /sys/.../iio:deviceX/bridge_switch_en +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + This attribute, if available, is used to close or open the + bridge power down switch found on some converters. + In bridge applications, such as strain gauges and load cells, + the bridge itself consumes the majority of the current in the + system. To minimize the current consumption of the system, + the bridge can be disconnected (when it is not being used + using the bridge_switch_en attribute. diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a b/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a new file mode 100644 index 00000000..863d3856 --- /dev/null +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-adc-ad7280a @@ -0,0 +1,21 @@ +What: /sys/bus/iio/devices/deviceX/inY-inZ_balance_switch_en +KernelVersion: 3.0.0 +Contact: linux-iio@vger.kernel.org +Description: + Writing 1 enables the cell balance output switch corresponding + to input Y. Writing 0 disables it. If the inY-inZ_balance_timer + is set to a none zero value, the corresponding switch will + enable for the programmed amount of time, before it + automatically disables. + +What: /sys/bus/iio/devices/deviceX/inY-inZ_balance_timer +KernelVersion: 3.0.0 +Contact: linux-iio@vger.kernel.org +Description: + The inY-inZ_balance_timer file allows the user to program + individual times for each cell balance output. The AD7280A + allows the user to set the timer to a value from 0 minutes to + 36.9 minutes. The resolution of the timer is 71.5 sec. + The value written is the on-time in milliseconds. When the + timer value is set 0, the timer is disabled. The cell balance + outputs are controlled only by inY-inZ_balance_switch_en. diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-dds b/drivers/staging/iio/Documentation/sysfs-bus-iio-dds new file mode 100644 index 00000000..ffdd5478 --- /dev/null +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-dds @@ -0,0 +1,93 @@ + +What: /sys/bus/iio/devices/.../ddsX_freqY +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Stores frequency into tuning word Y. + There will be more than one ddsX_freqY file, which allows for + pin controlled FSK Frequency Shift Keying + (ddsX_pincontrol_freq_en is active) or the user can control + the desired active tuning word by writing Y to the + ddsX_freqsymbol file. + +What: /sys/bus/iio/devices/.../ddsX_freqY_scale +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Scale to be applied to ddsX_freqY in order to obtain the + desired value in Hz. If shared across all frequency registers + Y is not present. It is also possible X is not present if + shared across all channels. + +What: /sys/bus/iio/devices/.../ddsX_freqsymbol +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the active output frequency tuning word. The value + corresponds to the Y in ddsX_freqY. To exit this mode the user + can write ddsX_pincontrol_freq_en or ddsX_out_enable file. + +What: /sys/bus/iio/devices/.../ddsX_phaseY +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Stores phase into Y. + There will be more than one ddsX_phaseY file, which allows for + pin controlled PSK Phase Shift Keying + (ddsX_pincontrol_phase_en is active) or the user can + control the desired phase Y which is added to the phase + accumulator output by writing Y to the en_phase file. + +What: /sys/bus/iio/devices/.../ddsX_phaseY_scale +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Scale to be applied to ddsX_phaseY in order to obtain the + desired value in rad. If shared across all phase registers + Y is not present. It is also possible X is not present if + shared across all channels. + +What: /sys/bus/iio/devices/.../ddsX_phasesymbol +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the active phase Y which is added to the phase + accumulator output. The value corresponds to the Y in + ddsX_phaseY. To exit this mode the user can write + ddsX_pincontrol_phase_en or disable file. + +What: /sys/bus/iio/devices/.../ddsX_pincontrol_en +What: /sys/bus/iio/devices/.../ddsX_pincontrol_freq_en +What: /sys/bus/iio/devices/.../ddsX_pincontrol_phase_en +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + ddsX_pincontrol_en: Both, the active frequency and phase is + controlled by the respective phase and frequency control inputs. + In case the device in question allows to independent controls, + then there are dedicated files (ddsX_pincontrol_freq_en, + ddsX_pincontrol_phase_en). + +What: /sys/bus/iio/devices/.../ddsX_out_enable +What: /sys/bus/iio/devices/.../ddsX_outY_enable +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + ddsX_outY_enable controls signal generation on output Y of + channel X. Y may be suppressed if all channels are + controlled together. + +What: /sys/bus/iio/devices/.../ddsX_outY_wavetype +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Specifies the output waveform. + (sine, triangle, ramp, square, ...) + For a list of available output waveform options read + available_output_modes. + +What: /sys/bus/iio/devices/.../ddsX_outY_wavetype_available +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Lists all available output waveform options. diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-impedance-analyzer-ad5933 b/drivers/staging/iio/Documentation/sysfs-bus-iio-impedance-analyzer-ad5933 new file mode 100644 index 00000000..79c7e88c --- /dev/null +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-impedance-analyzer-ad5933 @@ -0,0 +1,30 @@ +What: /sys/bus/iio/devices/iio:deviceX/outY_freq_start +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + Frequency sweep start frequency in Hz. + +What: /sys/bus/iio/devices/iio:deviceX/outY_freq_increment +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + Frequency increment in Hz (step size) between consecutive + frequency points along the sweep. + +What: /sys/bus/iio/devices/iio:deviceX/outY_freq_points +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + Number of frequency points (steps) in the frequency sweep. + This value, in conjunction with the outY_freq_start and the + outY_freq_increment, determines the frequency sweep range + for the sweep operation. + +What: /sys/bus/iio/devices/iio:deviceX/outY_settling_cycles +KernelVersion: 3.1.0 +Contact: linux-iio@vger.kernel.org +Description: + Number of output excitation cycles (settling time cycles) + that are allowed to pass through the unknown impedance, + after each frequency increment, and before the ADC is triggered + to perform a conversion sequence of the response signal. diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light b/drivers/staging/iio/Documentation/sysfs-bus-iio-light new file mode 100644 index 00000000..edbf470e --- /dev/null +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light @@ -0,0 +1,85 @@ + +What: /sys/bus/iio/devices/device[n]/range +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent ADC Full Scale Range used for some ambient + light sensors in calculating lux. + +What: /sys/bus/iio/devices/device[n]/range_available +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent supported vales for ADC Full Scale Range. + +What: /sys/bus/iio/devices/device[n]/adc_resolution +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent ADC resolution of the ambient light sensor + used in calculating the lux. + +What: /sys/bus/iio/devices/device[n]/adc_resolution_available +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent list of possible values supported for the + adc_resolution of the given sensor. + +What: /sys/bus/iio/devices/device[n]/illuminance0[_input|_raw] +KernelVersion: 2.6.35 +Contact: linux-iio@vger.kernel.org +Description: + This should return the calculated lux from the light sensor. If + it comes back in SI units, it should also include _input else it + should include _raw to signify it is not in SI units. + +What: /sys/.../device[n]/proximity_on_chip_ambient_infrared_supression +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware dependent mode for an ALS device to calculate the value + in proximity mode. When this is enabled, then the device should + use a infrared sensor reading to remove infrared noise from the + proximity reading. If this is not enabled, the driver can still + do this calculation manually by reading the infrared sensor + value and doing the negation in sw. + +What: /sys/bus/iio/devices/device[n]/proximity[_input|_raw] +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property is supported by proximity sensors and should be + used to return the value of a reading by the sensor. If this + value is returned in SI units, it should also include _input + but if it is not, then it should include _raw. + +What: /sys/bus/iio/devices/device[n]/intensity_infrared[_input|_raw] +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property is supported by sensors that have an infrared + sensing mode. This value should be the output from a reading + and if expressed in SI units, should include _input. If this + value is not in SI units, then it should include _raw. + +What: /sys/bus/iio/devices/device[n]/illuminance0_target +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property gets/sets the last known external + lux measurement used in/for calibration. + +What: /sys/bus/iio/devices/device[n]/illuminance0_integration_time +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property gets/sets the sensors ADC analog integration time. + +What: /sys/bus/iio/devices/device[n]/illuminance0_calibscale +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Hardware or software applied calibration scale factor assumed + to account for attenuation due to industrial design (glass + filters or aperture holes). diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-tsl2583 b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-tsl2583 new file mode 100644 index 00000000..660781df --- /dev/null +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-tsl2583 @@ -0,0 +1,20 @@ +What: /sys/bus/iio/devices/device[n]/lux_table +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property gets/sets the table of coefficients + used in calculating illuminance in lux. + +What: /sys/bus/iio/devices/device[n]/illuminance0_calibrate +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property causes an internal calibration of the als gain trim + value which is later used in calculating illuminance in lux. + +What: /sys/bus/iio/devices/device[n]/illuminance0_input_target +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + This property is the known externally illuminance (in lux). + It is used in the process of calibrating the device accuracy. diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-trigger-sysfs b/drivers/staging/iio/Documentation/sysfs-bus-iio-trigger-sysfs new file mode 100644 index 00000000..5235e6c7 --- /dev/null +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-trigger-sysfs @@ -0,0 +1,11 @@ +What: /sys/bus/iio/devices/triggerX/trigger_now +KernelVersion: 2.6.38 +Contact: linux-iio@vger.kernel.org +Description: + This file is provided by the iio-trig-sysfs stand-alone trigger + driver. Writing this file with any value triggers an event + driven driver, associated with this trigger, to capture data + into an in kernel buffer. This approach can be valuable during + automated testing or in situations, where other trigger methods + are not applicable. For example no RTC or spare GPIOs. + X is the IIO index of the trigger. diff --git a/drivers/staging/iio/Documentation/trigger.txt b/drivers/staging/iio/Documentation/trigger.txt new file mode 100644 index 00000000..fc2012eb --- /dev/null +++ b/drivers/staging/iio/Documentation/trigger.txt @@ -0,0 +1,38 @@ +IIO trigger drivers. + +Many triggers are provided by hardware that will also be registered as +an IIO device. Whilst this can create device specific complexities +such triggers are registered with the core in the same way as +stand-alone triggers. + +struct iio_trig *trig = iio_allocate_trigger("", ...); + +allocates a trigger structure. The key elements to then fill in within +a driver are: + +trig->private_data + Device specific private data. + +trig->owner + Typically set to THIS_MODULE. Used to ensure correct + ownership of core allocated resources. + +trig->set_trigger_state: + Function that enables / disables the underlying source of the trigger. + +There is also a +trig->alloc_list which is useful for drivers that allocate multiple +triggers to keep track of what they have created. + +When these have been set call: + +iio_trigger_register(trig); + +to register the trigger with the core, making it available to trigger +consumers. + +Trigger Consumers + +Currently triggers are only used for the filling of software +buffers and as such any device supporting INDIO_RING_TRIGGERED has the +consumer interface automatically created. diff --git a/drivers/staging/iio/Kconfig b/drivers/staging/iio/Kconfig new file mode 100644 index 00000000..fe158671 --- /dev/null +++ b/drivers/staging/iio/Kconfig @@ -0,0 +1,107 @@ +# +# Industrial I/O subsytem configuration +# + +menuconfig IIO + tristate "Industrial I/O support" + depends on GENERIC_HARDIRQS + help + The industrial I/O subsystem provides a unified framework for + drivers for many different types of embedded sensors using a + number of different physical interfaces (i2c, spi, etc). See + drivers/staging/iio/Documentation for more information. +if IIO +config IIO_ST_HWMON + tristate "Hwmon driver that uses channels specified via iio maps" + depends on HWMON + help + This is a platform driver that in combination with a suitable + map allows IIO devices to provide basic hwmon functionality + for those channels specified in the map. + +config IIO_BUFFER + bool "Enable buffer support within IIO" + help + Provide core support for various buffer based data + acquisition methods. + +if IIO_BUFFER + +config IIO_SW_RING + select IIO_TRIGGER + tristate "Industrial I/O lock free software ring" + help + Example software ring buffer implementation. The design aim + of this particular realization was to minimize write locking + with the intention that some devices would be able to write + in interrupt context. + +config IIO_KFIFO_BUF + select IIO_TRIGGER + tristate "Industrial I/O buffering based on kfifo" + help + A simple fifo based on kfifo. Use this if you want a fifo + rather than a ring buffer. Note that this currently provides + no buffer events so it is up to userspace to work out how + often to read from the buffer. + +endif # IIO_BUFFER + +config IIO_TRIGGER + boolean "Enable triggered sampling support" + help + Provides IIO core support for triggers. Currently these + are used to initialize capture of samples to push into + ring buffers. The triggers are effectively a 'capture + data now' interrupt. + +config IIO_CONSUMERS_PER_TRIGGER + int "Maximum number of consumers per trigger" + depends on IIO_TRIGGER + default "2" + help + This value controls the maximum number of consumers that a + given trigger may handle. Default is 2. + +source "drivers/staging/iio/accel/Kconfig" +source "drivers/staging/iio/adc/Kconfig" +source "drivers/staging/iio/addac/Kconfig" +source "drivers/staging/iio/cdc/Kconfig" +source "drivers/staging/iio/dac/Kconfig" +source "drivers/staging/iio/dds/Kconfig" +source "drivers/staging/iio/gyro/Kconfig" +source "drivers/staging/iio/impedance-analyzer/Kconfig" +source "drivers/staging/iio/imu/Kconfig" +source "drivers/staging/iio/light/Kconfig" +source "drivers/staging/iio/magnetometer/Kconfig" +source "drivers/staging/iio/meter/Kconfig" +source "drivers/staging/iio/resolver/Kconfig" +source "drivers/staging/iio/trigger/Kconfig" + +config IIO_DUMMY_EVGEN + tristate + +config IIO_SIMPLE_DUMMY + tristate "An example driver with no hardware requirements" + help + Driver intended mainly as documentation for how to write + a driver. May also be useful for testing userspace code + without hardware. + +if IIO_SIMPLE_DUMMY + +config IIO_SIMPLE_DUMMY_EVENTS + boolean "Event generation support" + select IIO_DUMMY_EVGEN + help + Add some dummy events to the simple dummy driver. + +config IIO_SIMPLE_DUMMY_BUFFER + boolean "Buffered capture support" + depends on IIO_KFIFO_BUF + help + Add buffered data capture to the simple dummy driver. + +endif # IIO_SIMPLE_DUMMY + +endif # IIO diff --git a/drivers/staging/iio/Makefile b/drivers/staging/iio/Makefile new file mode 100644 index 00000000..5075291d --- /dev/null +++ b/drivers/staging/iio/Makefile @@ -0,0 +1,35 @@ +# +# Makefile for the industrial I/O core. +# + +obj-$(CONFIG_IIO) += industrialio.o +industrialio-y := industrialio-core.o industrialio-event.o inkern.o +industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o +industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o + +obj-$(CONFIG_IIO_SW_RING) += ring_sw.o +obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o + +obj-$(CONFIG_IIO_SIMPLE_DUMMY) += iio_dummy.o +iio_dummy-y := iio_simple_dummy.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_EVENTS) += iio_simple_dummy_events.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_BUFFER) += iio_simple_dummy_buffer.o + +obj-$(CONFIG_IIO_DUMMY_EVGEN) += iio_dummy_evgen.o + +obj-$(CONFIG_IIO_ST_HWMON) += iio_hwmon.o + +obj-y += accel/ +obj-y += adc/ +obj-y += addac/ +obj-y += cdc/ +obj-y += dac/ +obj-y += dds/ +obj-y += gyro/ +obj-y += impedance-analyzer/ +obj-y += imu/ +obj-y += light/ +obj-y += magnetometer/ +obj-y += meter/ +obj-y += resolver/ +obj-y += trigger/ diff --git a/drivers/staging/iio/TODO b/drivers/staging/iio/TODO new file mode 100644 index 00000000..d1ad35e2 --- /dev/null +++ b/drivers/staging/iio/TODO @@ -0,0 +1,73 @@ +2009 8/18 + +Core: +1) Get reviews +2) Additional testing +3) Ensure all desirable features present by adding more devices. + Major changes not expected except in response to comments + +Max1363 core: +1) Possibly add sysfs exports of constant useful to userspace. +Would be nice +2) Support hardware generated interrupts +3) Expand device set. Lots of other maxim adc's have very + similar interfaces. + +TSL2561 +Would be nice +1) Open question of userspace vs kernel space balance when +converting to useful light measurements from device ones. +2) Add sysfs elements necessary to allow device agnostic +unit conversion. + +LIS3L02DQ core + +LIS3L02DQ ring + +KXSD9 +Currently minimal driver, would be nice to add: +1) Support for all chip generated interrupts (events), +basically get support up to level of lis3l02dq driver. + +Ring buffer core + +SCA3000 +Would be nice +1) Testing on devices other than sca3000-e05 + +Trigger core support +1) Discussion of approach. Is it general enough? + +Ring Buffer: +1) Discussion of approach. +There are probably better ways of doing this. The +intention is to allow for more than one software ring +buffer implementation as different users will have +different requirements. This one suits mid range +frequencies (100Hz - 4kHz). +2) Lots of testing + +Periodic Timer trigger +1) Move to a more general hardware periodic timer request +subsystem. Current approach is abusing purpose of RTC. +Initial discussions have taken place, but no actual code +is in place as yet. This topic will be reopened on lkml +shortly. I don't really envision this patch being merged +in anything like its current form. + +GPIO trigger +1) Add control over the type of interrupt etc. This will +necessitate a header that is also visible from arch board +files. (avoided at the moment to keep the driver set +contained in staging). + +ADI Drivers: +CC the device-drivers-devel@blackfin.uclinux.org mailing list when +e-mailing the normal IIO list (see below). + +Documentation +1) Lots of cleanup and expansion. +2) Some device require indvidual docs. + +Contact: Jonathan Cameron . +Mailing list: linux-iio@vger.kernel.org diff --git a/drivers/staging/iio/accel/Kconfig b/drivers/staging/iio/accel/Kconfig new file mode 100644 index 00000000..5ab71670 --- /dev/null +++ b/drivers/staging/iio/accel/Kconfig @@ -0,0 +1,105 @@ +# +# Accelerometer drivers +# +menu "Accelerometers" + +config ADIS16201 + tristate "Analog Devices ADIS16201 Dual-Axis Digital Inclinometer and Accelerometer" + depends on SPI + select IIO_TRIGGER if IIO_BUFFER + select IIO_SW_RING if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16201 dual-axis + digital inclinometer and accelerometer. + +config ADIS16203 + tristate "Analog Devices ADIS16203 Programmable 360 Degrees Inclinometer" + depends on SPI + select IIO_TRIGGER if IIO_BUFFER + select IIO_SW_RING if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16203 Programmable + 360 Degrees Inclinometer. + +config ADIS16204 + tristate "Analog Devices ADIS16204 Programmable High-g Digital Impact Sensor and Recorder" + depends on SPI + select IIO_TRIGGER if IIO_BUFFER + select IIO_SW_RING if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16204 Programmable + High-g Digital Impact Sensor and Recorder. + +config ADIS16209 + tristate "Analog Devices ADIS16209 Dual-Axis Digital Inclinometer and Accelerometer" + depends on SPI + select IIO_TRIGGER if IIO_BUFFER + select IIO_SW_RING if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16209 dual-axis digital inclinometer + and accelerometer. + +config ADIS16220 + tristate "Analog Devices ADIS16220 Programmable Digital Vibration Sensor" + depends on SPI + help + Say yes here to build support for Analog Devices adis16220 programmable + digital vibration sensor. + +config ADIS16240 + tristate "Analog Devices ADIS16240 Programmable Impact Sensor and Recorder" + depends on SPI + select IIO_TRIGGER if IIO_BUFFER + select IIO_SW_RING if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16240 programmable + impact Sensor and recorder. + +config KXSD9 + tristate "Kionix KXSD9 Accelerometer Driver" + depends on SPI + help + Say yes here to build support for the Kionix KXSD9 accelerometer. + Currently this only supports the device via an SPI interface. + +config LIS3L02DQ + tristate "ST Microelectronics LIS3L02DQ Accelerometer Driver" + depends on SPI + select IIO_TRIGGER if IIO_BUFFER + depends on !IIO_BUFFER || IIO_KFIFO_BUF || IIO_SW_RING + depends on GENERIC_GPIO + help + Say yes here to build SPI support for the ST microelectronics + accelerometer. The driver supplies direct access via sysfs files + and an event interface via a character device. + +choice + prompt "Buffer type" + depends on LIS3L02DQ && IIO_BUFFER + +config LIS3L02DQ_BUF_KFIFO + depends on IIO_KFIFO_BUF + bool "Simple FIFO" + help + Kfifo based FIFO. Does not provide any events so it is up + to userspace to ensure it reads often enough that data is not + lost. + +config LIS3L02DQ_BUF_RING_SW + depends on IIO_SW_RING + bool "IIO Software Ring" + help + Original IIO ring buffer implementation. Provides simple + buffer events, half full etc. + +endchoice + +config SCA3000 + depends on IIO_BUFFER + depends on SPI + tristate "VTI SCA3000 series accelerometers" + help + Say yes here to build support for the VTI SCA3000 series of SPI + accelerometers. These devices use a hardware ring buffer. + +endmenu diff --git a/drivers/staging/iio/accel/Makefile b/drivers/staging/iio/accel/Makefile new file mode 100644 index 00000000..95c66661 --- /dev/null +++ b/drivers/staging/iio/accel/Makefile @@ -0,0 +1,35 @@ +# +# Makefile for industrial I/O accelerometer drivers +# + +adis16201-y := adis16201_core.o +adis16201-$(CONFIG_IIO_BUFFER) += adis16201_ring.o adis16201_trigger.o +obj-$(CONFIG_ADIS16201) += adis16201.o + +adis16203-y := adis16203_core.o +adis16203-$(CONFIG_IIO_BUFFER) += adis16203_ring.o adis16203_trigger.o +obj-$(CONFIG_ADIS16203) += adis16203.o + +adis16204-y := adis16204_core.o +adis16204-$(CONFIG_IIO_BUFFER) += adis16204_ring.o adis16204_trigger.o +obj-$(CONFIG_ADIS16204) += adis16204.o + +adis16209-y := adis16209_core.o +adis16209-$(CONFIG_IIO_BUFFER) += adis16209_ring.o adis16209_trigger.o +obj-$(CONFIG_ADIS16209) += adis16209.o + +adis16220-y := adis16220_core.o +obj-$(CONFIG_ADIS16220) += adis16220.o + +adis16240-y := adis16240_core.o +adis16240-$(CONFIG_IIO_BUFFER) += adis16240_ring.o adis16240_trigger.o +obj-$(CONFIG_ADIS16240) += adis16240.o + +obj-$(CONFIG_KXSD9) += kxsd9.o + +lis3l02dq-y := lis3l02dq_core.o +lis3l02dq-$(CONFIG_IIO_BUFFER) += lis3l02dq_ring.o +obj-$(CONFIG_LIS3L02DQ) += lis3l02dq.o + +sca3000-y := sca3000_core.o sca3000_ring.o +obj-$(CONFIG_SCA3000) += sca3000.o diff --git a/drivers/staging/iio/accel/adis16201.h b/drivers/staging/iio/accel/adis16201.h new file mode 100644 index 00000000..72750f7f --- /dev/null +++ b/drivers/staging/iio/accel/adis16201.h @@ -0,0 +1,141 @@ +#ifndef SPI_ADIS16201_H_ +#define SPI_ADIS16201_H_ + +#define ADIS16201_STARTUP_DELAY 220 /* ms */ + +#define ADIS16201_READ_REG(a) a +#define ADIS16201_WRITE_REG(a) ((a) | 0x80) + +#define ADIS16201_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16201_SUPPLY_OUT 0x02 /* Output, power supply */ +#define ADIS16201_XACCL_OUT 0x04 /* Output, x-axis accelerometer */ +#define ADIS16201_YACCL_OUT 0x06 /* Output, y-axis accelerometer */ +#define ADIS16201_AUX_ADC 0x08 /* Output, auxiliary ADC input */ +#define ADIS16201_TEMP_OUT 0x0A /* Output, temperature */ +#define ADIS16201_XINCL_OUT 0x0C /* Output, x-axis inclination */ +#define ADIS16201_YINCL_OUT 0x0E /* Output, y-axis inclination */ +#define ADIS16201_XACCL_OFFS 0x10 /* Calibration, x-axis acceleration offset */ +#define ADIS16201_YACCL_OFFS 0x12 /* Calibration, y-axis acceleration offset */ +#define ADIS16201_XACCL_SCALE 0x14 /* x-axis acceleration scale factor */ +#define ADIS16201_YACCL_SCALE 0x16 /* y-axis acceleration scale factor */ +#define ADIS16201_XINCL_OFFS 0x18 /* Calibration, x-axis inclination offset */ +#define ADIS16201_YINCL_OFFS 0x1A /* Calibration, y-axis inclination offset */ +#define ADIS16201_XINCL_SCALE 0x1C /* x-axis inclination scale factor */ +#define ADIS16201_YINCL_SCALE 0x1E /* y-axis inclination scale factor */ +#define ADIS16201_ALM_MAG1 0x20 /* Alarm 1 amplitude threshold */ +#define ADIS16201_ALM_MAG2 0x22 /* Alarm 2 amplitude threshold */ +#define ADIS16201_ALM_SMPL1 0x24 /* Alarm 1, sample period */ +#define ADIS16201_ALM_SMPL2 0x26 /* Alarm 2, sample period */ +#define ADIS16201_ALM_CTRL 0x28 /* Alarm control */ +#define ADIS16201_AUX_DAC 0x30 /* Auxiliary DAC data */ +#define ADIS16201_GPIO_CTRL 0x32 /* General-purpose digital input/output control */ +#define ADIS16201_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16201_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16201_AVG_CNT 0x38 /* Operation, filter configuration */ +#define ADIS16201_SLP_CNT 0x3A /* Operation, sleep mode control */ +#define ADIS16201_DIAG_STAT 0x3C /* Diagnostics, system status register */ +#define ADIS16201_GLOB_CMD 0x3E /* Operation, system command register */ + +#define ADIS16201_OUTPUTS 7 + +/* MSC_CTRL */ +#define ADIS16201_MSC_CTRL_SELF_TEST_EN (1 << 8) /* Self-test enable */ +#define ADIS16201_MSC_CTRL_DATA_RDY_EN (1 << 2) /* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16201_MSC_CTRL_ACTIVE_HIGH (1 << 1) /* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16201_MSC_CTRL_DATA_RDY_DIO1 (1 << 0) /* Data-ready line selection: 1 = DIO1, 0 = DIO0 */ + +/* DIAG_STAT */ +#define ADIS16201_DIAG_STAT_ALARM2 (1<<9) /* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16201_DIAG_STAT_ALARM1 (1<<8) /* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16201_DIAG_STAT_SPI_FAIL (1<<3) /* SPI communications failure */ +#define ADIS16201_DIAG_STAT_FLASH_UPT (1<<2) /* Flash update failure */ +#define ADIS16201_DIAG_STAT_POWER_HIGH (1<<1) /* Power supply above 3.625 V */ +#define ADIS16201_DIAG_STAT_POWER_LOW (1<<0) /* Power supply below 3.15 V */ + +/* GLOB_CMD */ +#define ADIS16201_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16201_GLOB_CMD_FACTORY_CAL (1<<1) + +#define ADIS16201_MAX_TX 14 +#define ADIS16201_MAX_RX 14 + +#define ADIS16201_ERROR_ACTIVE (1<<14) + +/** + * struct adis16201_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16201_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + u8 tx[14] ____cacheline_aligned; + u8 rx[14]; +}; + +int adis16201_set_irq(struct iio_dev *indio_dev, bool enable); + +enum adis16201_scan { + ADIS16201_SCAN_SUPPLY, + ADIS16201_SCAN_ACC_X, + ADIS16201_SCAN_ACC_Y, + ADIS16201_SCAN_AUX_ADC, + ADIS16201_SCAN_TEMP, + ADIS16201_SCAN_INCLI_X, + ADIS16201_SCAN_INCLI_Y, +}; + +#ifdef CONFIG_IIO_BUFFER +void adis16201_remove_trigger(struct iio_dev *indio_dev); +int adis16201_probe_trigger(struct iio_dev *indio_dev); + +ssize_t adis16201_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + +int adis16201_configure_ring(struct iio_dev *indio_dev); +void adis16201_unconfigure_ring(struct iio_dev *indio_dev); + +#else /* CONFIG_IIO_BUFFER */ + +static inline void adis16201_remove_trigger(struct iio_dev *indio_dev) +{ +} + +static inline int adis16201_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline ssize_t +adis16201_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static int adis16201_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void adis16201_unconfigure_ring(struct iio_dev *indio_dev) +{ +} + +static inline int adis16201_initialize_ring(struct iio_ring_buffer *ring) +{ + return 0; +} + +static inline void adis16201_uninitialize_ring(struct iio_ring_buffer *ring) +{ +} + +#endif /* CONFIG_IIO_BUFFER */ +#endif /* SPI_ADIS16201_H_ */ diff --git a/drivers/staging/iio/accel/adis16201_core.c b/drivers/staging/iio/accel/adis16201_core.c new file mode 100644 index 00000000..d439e45d --- /dev/null +++ b/drivers/staging/iio/accel/adis16201_core.c @@ -0,0 +1,556 @@ +/* + * ADIS16201 Programmable Digital Vibration Sensor driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "adis16201.h" + +enum adis16201_chan { + in_supply, + temp, + accel_x, + accel_y, + incli_x, + incli_y, + in_aux, +}; + +/** + * adis16201_spi_write_reg_8() - write single byte to a register + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the register to be written + * @val: the value to write + **/ +static int adis16201_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct adis16201_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16201_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16201_spi_write_reg_16() - write 2 bytes to a pair of registers + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: value to be written + **/ +static int adis16201_spi_write_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct adis16201_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16201_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = ADIS16201_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16201_spi_read_reg_16() - read 2 bytes from a 16-bit register + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + **/ +static int adis16201_spi_read_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct adis16201_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 20, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .delay_usecs = 20, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16201_READ_REG(lower_reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + lower_reg_address); + goto error_ret; + } + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int adis16201_reset(struct iio_dev *indio_dev) +{ + int ret; + struct adis16201_state *st = iio_priv(indio_dev); + + ret = adis16201_spi_write_reg_8(indio_dev, + ADIS16201_GLOB_CMD, + ADIS16201_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&st->us->dev, "problem resetting device"); + + return ret; +} + +static ssize_t adis16201_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int ret; + bool res; + + if (len < 1) + return -EINVAL; + ret = strtobool(buf, &res); + if (ret || !res) + return ret; + return adis16201_reset(dev_get_drvdata(dev)); +} + +int adis16201_set_irq(struct iio_dev *indio_dev, bool enable) +{ + int ret = 0; + u16 msc; + + ret = adis16201_spi_read_reg_16(indio_dev, ADIS16201_MSC_CTRL, &msc); + if (ret) + goto error_ret; + + msc |= ADIS16201_MSC_CTRL_ACTIVE_HIGH; + msc &= ~ADIS16201_MSC_CTRL_DATA_RDY_DIO1; + if (enable) + msc |= ADIS16201_MSC_CTRL_DATA_RDY_EN; + else + msc &= ~ADIS16201_MSC_CTRL_DATA_RDY_EN; + + ret = adis16201_spi_write_reg_16(indio_dev, ADIS16201_MSC_CTRL, msc); + +error_ret: + return ret; +} + +static int adis16201_check_status(struct iio_dev *indio_dev) +{ + u16 status; + int ret; + + ret = adis16201_spi_read_reg_16(indio_dev, + ADIS16201_DIAG_STAT, &status); + if (ret < 0) { + dev_err(&indio_dev->dev, "Reading status failed\n"); + goto error_ret; + } + ret = status & 0xF; + if (ret) + ret = -EFAULT; + + if (status & ADIS16201_DIAG_STAT_SPI_FAIL) + dev_err(&indio_dev->dev, "SPI failure\n"); + if (status & ADIS16201_DIAG_STAT_FLASH_UPT) + dev_err(&indio_dev->dev, "Flash update failed\n"); + if (status & ADIS16201_DIAG_STAT_POWER_HIGH) + dev_err(&indio_dev->dev, "Power supply above 3.625V\n"); + if (status & ADIS16201_DIAG_STAT_POWER_LOW) + dev_err(&indio_dev->dev, "Power supply below 3.15V\n"); + +error_ret: + return ret; +} + +static int adis16201_self_test(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16201_spi_write_reg_16(indio_dev, + ADIS16201_MSC_CTRL, + ADIS16201_MSC_CTRL_SELF_TEST_EN); + if (ret) { + dev_err(&indio_dev->dev, "problem starting self test"); + goto err_ret; + } + + ret = adis16201_check_status(indio_dev); + +err_ret: + return ret; +} + +static int adis16201_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &indio_dev->dev; + + /* Disable IRQ */ + ret = adis16201_set_irq(indio_dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + /* Do self test */ + ret = adis16201_self_test(indio_dev); + if (ret) { + dev_err(dev, "self test failure"); + goto err_ret; + } + + /* Read status register to check the result */ + ret = adis16201_check_status(indio_dev); + if (ret) { + adis16201_reset(indio_dev); + dev_err(dev, "device not playing ball -> reset"); + msleep(ADIS16201_STARTUP_DELAY); + ret = adis16201_check_status(indio_dev); + if (ret) { + dev_err(dev, "giving up"); + goto err_ret; + } + } + +err_ret: + return ret; +} + +static u8 adis16201_addresses[7][2] = { + [in_supply] = { ADIS16201_SUPPLY_OUT, }, + [temp] = { ADIS16201_TEMP_OUT }, + [accel_x] = { ADIS16201_XACCL_OUT, ADIS16201_XACCL_OFFS }, + [accel_y] = { ADIS16201_YACCL_OUT, ADIS16201_YACCL_OFFS }, + [in_aux] = { ADIS16201_AUX_ADC }, + [incli_x] = { ADIS16201_XINCL_OUT }, + [incli_y] = { ADIS16201_YINCL_OUT }, +}; + +static int adis16201_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case 0: + mutex_lock(&indio_dev->mlock); + addr = adis16201_addresses[chan->address][0]; + ret = adis16201_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + if (val16 & ADIS16201_ERROR_ACTIVE) { + ret = adis16201_check_status(indio_dev); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } + val16 = val16 & ((1 << chan->scan_type.realbits) - 1); + if (chan->scan_type.sign == 's') + val16 = (s16)(val16 << + (16 - chan->scan_type.realbits)) >> + (16 - chan->scan_type.realbits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) + *val2 = 1220; + else + *val2 = 610; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = -470000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = 462500; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_INCLI: + *val = 0; + *val2 = 100000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + *val = 25; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 12; + break; + case IIO_INCLI: + bits = 9; + break; + default: + return -EINVAL; + }; + mutex_lock(&indio_dev->mlock); + addr = adis16201_addresses[chan->address][1]; + ret = adis16201_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int adis16201_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int bits; + s16 val16; + u8 addr; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 12; + break; + case IIO_INCLI: + bits = 9; + break; + default: + return -EINVAL; + }; + val16 = val & ((1 << bits) - 1); + addr = adis16201_addresses[chan->address][1]; + return adis16201_spi_write_reg_16(indio_dev, addr, val16); + } + return -EINVAL; +} + +static struct iio_chan_spec adis16201_channels[] = { + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, "supply", 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + in_supply, ADIS16201_SCAN_SUPPLY, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_TEMP, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT, + temp, ADIS16201_SCAN_TEMP, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_X, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + accel_x, ADIS16201_SCAN_ACC_X, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Y, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + accel_y, ADIS16201_SCAN_ACC_Y, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 1, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + in_aux, ADIS16201_SCAN_AUX_ADC, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_INCLI, 1, 0, 0, NULL, 0, IIO_MOD_X, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + incli_x, ADIS16201_SCAN_INCLI_X, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN(IIO_INCLI, 1, 0, 0, NULL, 0, IIO_MOD_Y, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + incli_y, ADIS16201_SCAN_INCLI_Y, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN_SOFT_TIMESTAMP(7) +}; + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, adis16201_write_reset, 0); + +static struct attribute *adis16201_attributes[] = { + &iio_dev_attr_reset.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16201_attribute_group = { + .attrs = adis16201_attributes, +}; + +static const struct iio_info adis16201_info = { + .attrs = &adis16201_attribute_group, + .read_raw = &adis16201_read_raw, + .write_raw = &adis16201_write_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit adis16201_probe(struct spi_device *spi) +{ + int ret; + struct adis16201_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16201_info; + + indio_dev->channels = adis16201_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16201_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis16201_configure_ring(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_buffer_register(indio_dev, + adis16201_channels, + ARRAY_SIZE(adis16201_channels)); + if (ret) { + printk(KERN_ERR "failed to initialize the ring\n"); + goto error_unreg_ring_funcs; + } + + if (spi->irq) { + ret = adis16201_probe_trigger(indio_dev); + if (ret) + goto error_uninitialize_ring; + } + + /* Get the device into a sane initial state */ + ret = adis16201_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_remove_trigger; + return 0; + +error_remove_trigger: + adis16201_remove_trigger(indio_dev); +error_uninitialize_ring: + iio_buffer_unregister(indio_dev); +error_unreg_ring_funcs: + adis16201_unconfigure_ring(indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int adis16201_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + adis16201_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + adis16201_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static struct spi_driver adis16201_driver = { + .driver = { + .name = "adis16201", + .owner = THIS_MODULE, + }, + .probe = adis16201_probe, + .remove = __devexit_p(adis16201_remove), +}; +module_spi_driver(adis16201_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16201 Programmable Digital Vibration Sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16201"); diff --git a/drivers/staging/iio/accel/adis16201_ring.c b/drivers/staging/iio/accel/adis16201_ring.c new file mode 100644 index 00000000..97f9e6b1 --- /dev/null +++ b/drivers/staging/iio/accel/adis16201_ring.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" +#include "adis16201.h" + + +/** + * adis16201_read_ring_data() read data registers which will be placed into ring + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @rx: somewhere to pass back the value read + **/ +static int adis16201_read_ring_data(struct iio_dev *indio_dev, u8 *rx) +{ + struct spi_message msg; + struct adis16201_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[ADIS16201_OUTPUTS + 1]; + int ret; + int i; + + mutex_lock(&st->buf_lock); + + spi_message_init(&msg); + + memset(xfers, 0, sizeof(xfers)); + for (i = 0; i <= ADIS16201_OUTPUTS; i++) { + xfers[i].bits_per_word = 8; + xfers[i].cs_change = 1; + xfers[i].len = 2; + xfers[i].delay_usecs = 20; + if (i < ADIS16201_OUTPUTS) { + xfers[i].tx_buf = st->tx + 2 * i; + st->tx[2 * i] = ADIS16201_READ_REG(ADIS16201_SUPPLY_OUT + + 2 * i); + st->tx[2 * i + 1] = 0; + } + if (i >= 1) + xfers[i].rx_buf = rx + 2 * (i - 1); + spi_message_add_tail(&xfers[i], &msg); + } + + ret = spi_sync(st->us, &msg); + if (ret) + dev_err(&st->us->dev, "problem when burst reading"); + + mutex_unlock(&st->buf_lock); + + return ret; +} + +/* Whilst this makes a lot of calls to iio_sw_ring functions - it is to device + * specific to be rolled into the core. + */ +static irqreturn_t adis16201_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16201_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + + int i = 0; + s16 *data; + size_t datasize = ring->access->get_bytes_per_datum(ring); + + data = kmalloc(datasize, GFP_KERNEL); + if (data == NULL) { + dev_err(&st->us->dev, "memory alloc failed in ring bh"); + return -ENOMEM; + } + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength) + && adis16201_read_ring_data(indio_dev, st->rx) >= 0) + for (; i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); i++) + data[i] = be16_to_cpup((__be16 *)&(st->rx[i*2])); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + *((s64 *)(data + ((i + 3)/4)*4)) = pf->timestamp; + + ring->access->store_to(ring, (u8 *)data, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + kfree(data); + + return IRQ_HANDLED; +} + +void adis16201_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +static const struct iio_buffer_setup_ops adis16201_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int adis16201_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct iio_buffer *ring; + + ring = iio_sw_rb_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->buffer = ring; + ring->scan_timestamp = true; + indio_dev->setup_ops = &adis16201_ring_setup_ops; + + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &adis16201_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "adis16201_consumer%d", + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; +error_iio_sw_rb_free: + iio_sw_rb_free(indio_dev->buffer); + return ret; +} diff --git a/drivers/staging/iio/accel/adis16201_trigger.c b/drivers/staging/iio/accel/adis16201_trigger.c new file mode 100644 index 00000000..bce505e7 --- /dev/null +++ b/drivers/staging/iio/accel/adis16201_trigger.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" +#include "adis16201.h" + +/** + * adis16201_data_rdy_trigger_set_state() set datardy interrupt state + **/ +static int adis16201_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + + dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state); + return adis16201_set_irq(indio_dev, state); +} + +static const struct iio_trigger_ops adis16201_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &adis16201_data_rdy_trigger_set_state, +}; + +int adis16201_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct adis16201_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("adis16201-dev%d", indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + ret = request_irq(st->us->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "adis16201", + st->trig); + if (ret) + goto error_free_trig; + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &adis16201_trigger_ops; + st->trig->private_data = indio_dev; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->us->irq, st->trig); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void adis16201_remove_trigger(struct iio_dev *indio_dev) +{ + struct adis16201_state *state = iio_priv(indio_dev); + + iio_trigger_unregister(state->trig); + free_irq(state->us->irq, state->trig); + iio_free_trigger(state->trig); +} diff --git a/drivers/staging/iio/accel/adis16203.h b/drivers/staging/iio/accel/adis16203.h new file mode 100644 index 00000000..3f96ad3b --- /dev/null +++ b/drivers/staging/iio/accel/adis16203.h @@ -0,0 +1,125 @@ +#ifndef SPI_ADIS16203_H_ +#define SPI_ADIS16203_H_ + +#define ADIS16203_STARTUP_DELAY 220 /* ms */ + +#define ADIS16203_READ_REG(a) a +#define ADIS16203_WRITE_REG(a) ((a) | 0x80) + +#define ADIS16203_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16203_SUPPLY_OUT 0x02 /* Output, power supply */ +#define ADIS16203_AUX_ADC 0x08 /* Output, auxiliary ADC input */ +#define ADIS16203_TEMP_OUT 0x0A /* Output, temperature */ +#define ADIS16203_XINCL_OUT 0x0C /* Output, x-axis inclination */ +#define ADIS16203_YINCL_OUT 0x0E /* Output, y-axis inclination */ +#define ADIS16203_INCL_NULL 0x18 /* Incline null calibration */ +#define ADIS16203_ALM_MAG1 0x20 /* Alarm 1 amplitude threshold */ +#define ADIS16203_ALM_MAG2 0x22 /* Alarm 2 amplitude threshold */ +#define ADIS16203_ALM_SMPL1 0x24 /* Alarm 1, sample period */ +#define ADIS16203_ALM_SMPL2 0x26 /* Alarm 2, sample period */ +#define ADIS16203_ALM_CTRL 0x28 /* Alarm control */ +#define ADIS16203_AUX_DAC 0x30 /* Auxiliary DAC data */ +#define ADIS16203_GPIO_CTRL 0x32 /* General-purpose digital input/output control */ +#define ADIS16203_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16203_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16203_AVG_CNT 0x38 /* Operation, filter configuration */ +#define ADIS16203_SLP_CNT 0x3A /* Operation, sleep mode control */ +#define ADIS16203_DIAG_STAT 0x3C /* Diagnostics, system status register */ +#define ADIS16203_GLOB_CMD 0x3E /* Operation, system command register */ + +#define ADIS16203_OUTPUTS 5 + +/* MSC_CTRL */ +#define ADIS16203_MSC_CTRL_PWRUP_SELF_TEST (1 << 10) /* Self-test at power-on: 1 = disabled, 0 = enabled */ +#define ADIS16203_MSC_CTRL_REVERSE_ROT_EN (1 << 9) /* Reverses rotation of both inclination outputs */ +#define ADIS16203_MSC_CTRL_SELF_TEST_EN (1 << 8) /* Self-test enable */ +#define ADIS16203_MSC_CTRL_DATA_RDY_EN (1 << 2) /* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16203_MSC_CTRL_ACTIVE_HIGH (1 << 1) /* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16203_MSC_CTRL_DATA_RDY_DIO1 (1 << 0) /* Data-ready line selection: 1 = DIO1, 0 = DIO0 */ + +/* DIAG_STAT */ +#define ADIS16203_DIAG_STAT_ALARM2 (1<<9) /* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16203_DIAG_STAT_ALARM1 (1<<8) /* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16203_DIAG_STAT_SELFTEST_FAIL (1<<5) /* Self-test diagnostic error flag */ +#define ADIS16203_DIAG_STAT_SPI_FAIL (1<<3) /* SPI communications failure */ +#define ADIS16203_DIAG_STAT_FLASH_UPT (1<<2) /* Flash update failure */ +#define ADIS16203_DIAG_STAT_POWER_HIGH (1<<1) /* Power supply above 3.625 V */ +#define ADIS16203_DIAG_STAT_POWER_LOW (1<<0) /* Power supply below 3.15 V */ + +/* GLOB_CMD */ +#define ADIS16203_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16203_GLOB_CMD_CLEAR_STAT (1<<4) +#define ADIS16203_GLOB_CMD_FACTORY_CAL (1<<1) + +#define ADIS16203_MAX_TX 12 +#define ADIS16203_MAX_RX 10 + +#define ADIS16203_ERROR_ACTIVE (1<<14) + +/** + * struct adis16203_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16203_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + u8 tx[ADIS16203_MAX_TX] ____cacheline_aligned; + u8 rx[ADIS16203_MAX_RX]; +}; + +int adis16203_set_irq(struct iio_dev *indio_dev, bool enable); + +enum adis16203_scan { + ADIS16203_SCAN_SUPPLY, + ADIS16203_SCAN_AUX_ADC, + ADIS16203_SCAN_TEMP, + ADIS16203_SCAN_INCLI_X, + ADIS16203_SCAN_INCLI_Y, +}; + +#ifdef CONFIG_IIO_BUFFER +void adis16203_remove_trigger(struct iio_dev *indio_dev); +int adis16203_probe_trigger(struct iio_dev *indio_dev); + +ssize_t adis16203_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + +int adis16203_configure_ring(struct iio_dev *indio_dev); +void adis16203_unconfigure_ring(struct iio_dev *indio_dev); + +#else /* CONFIG_IIO_BUFFER */ + +static inline void adis16203_remove_trigger(struct iio_dev *indio_dev) +{ +} + +static inline int adis16203_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline ssize_t +adis16203_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static int adis16203_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void adis16203_unconfigure_ring(struct iio_dev *indio_dev) +{ +} + +#endif /* CONFIG_IIO_BUFFER */ +#endif /* SPI_ADIS16203_H_ */ diff --git a/drivers/staging/iio/accel/adis16203_core.c b/drivers/staging/iio/accel/adis16203_core.c new file mode 100644 index 00000000..1a5140f9 --- /dev/null +++ b/drivers/staging/iio/accel/adis16203_core.c @@ -0,0 +1,511 @@ +/* + * ADIS16203 Programmable Digital Vibration Sensor driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "adis16203.h" + +#define DRIVER_NAME "adis16203" + +/** + * adis16203_spi_write_reg_8() - write single byte to a register + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the register to be written + * @val: the value to write + **/ +static int adis16203_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct adis16203_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16203_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16203_spi_write_reg_16() - write 2 bytes to a pair of registers + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: value to be written + **/ +static int adis16203_spi_write_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct adis16203_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16203_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = ADIS16203_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16203_spi_read_reg_16() - read 2 bytes from a 16-bit register + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + **/ +static int adis16203_spi_read_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct adis16203_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 20, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .delay_usecs = 20, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16203_READ_REG(lower_reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + lower_reg_address); + goto error_ret; + } + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int adis16203_check_status(struct iio_dev *indio_dev) +{ + u16 status; + int ret; + + ret = adis16203_spi_read_reg_16(indio_dev, + ADIS16203_DIAG_STAT, + &status); + if (ret < 0) { + dev_err(&indio_dev->dev, "Reading status failed\n"); + goto error_ret; + } + ret = status & 0x1F; + + if (status & ADIS16203_DIAG_STAT_SELFTEST_FAIL) + dev_err(&indio_dev->dev, "Self test failure\n"); + if (status & ADIS16203_DIAG_STAT_SPI_FAIL) + dev_err(&indio_dev->dev, "SPI failure\n"); + if (status & ADIS16203_DIAG_STAT_FLASH_UPT) + dev_err(&indio_dev->dev, "Flash update failed\n"); + if (status & ADIS16203_DIAG_STAT_POWER_HIGH) + dev_err(&indio_dev->dev, "Power supply above 3.625V\n"); + if (status & ADIS16203_DIAG_STAT_POWER_LOW) + dev_err(&indio_dev->dev, "Power supply below 3.15V\n"); + +error_ret: + return ret; +} + +static int adis16203_reset(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16203_spi_write_reg_8(indio_dev, + ADIS16203_GLOB_CMD, + ADIS16203_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&indio_dev->dev, "problem resetting device"); + + return ret; +} + +static ssize_t adis16203_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + if (len < 1) + return -EINVAL; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return adis16203_reset(indio_dev); + } + return -EINVAL; +} + +int adis16203_set_irq(struct iio_dev *indio_dev, bool enable) +{ + int ret = 0; + u16 msc; + + ret = adis16203_spi_read_reg_16(indio_dev, ADIS16203_MSC_CTRL, &msc); + if (ret) + goto error_ret; + + msc |= ADIS16203_MSC_CTRL_ACTIVE_HIGH; + msc &= ~ADIS16203_MSC_CTRL_DATA_RDY_DIO1; + if (enable) + msc |= ADIS16203_MSC_CTRL_DATA_RDY_EN; + else + msc &= ~ADIS16203_MSC_CTRL_DATA_RDY_EN; + + ret = adis16203_spi_write_reg_16(indio_dev, ADIS16203_MSC_CTRL, msc); + +error_ret: + return ret; +} + +static int adis16203_self_test(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16203_spi_write_reg_16(indio_dev, + ADIS16203_MSC_CTRL, + ADIS16203_MSC_CTRL_SELF_TEST_EN); + if (ret) { + dev_err(&indio_dev->dev, "problem starting self test"); + goto err_ret; + } + + adis16203_check_status(indio_dev); + +err_ret: + return ret; +} + +static int adis16203_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + + /* Disable IRQ */ + ret = adis16203_set_irq(indio_dev, false); + if (ret) { + dev_err(&indio_dev->dev, "disable irq failed"); + goto err_ret; + } + + /* Do self test */ + ret = adis16203_self_test(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "self test failure"); + goto err_ret; + } + + /* Read status register to check the result */ + ret = adis16203_check_status(indio_dev); + if (ret) { + adis16203_reset(indio_dev); + dev_err(&indio_dev->dev, "device not playing ball -> reset"); + msleep(ADIS16203_STARTUP_DELAY); + ret = adis16203_check_status(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "giving up"); + goto err_ret; + } + } + +err_ret: + return ret; +} + +enum adis16203_chan { + in_supply, + in_aux, + incli_x, + incli_y, + temp, +}; + +static u8 adis16203_addresses[5][2] = { + [in_supply] = { ADIS16203_SUPPLY_OUT }, + [in_aux] = { ADIS16203_AUX_ADC }, + [incli_x] = { ADIS16203_XINCL_OUT, ADIS16203_INCL_NULL}, + [incli_y] = { ADIS16203_YINCL_OUT }, + [temp] = { ADIS16203_TEMP_OUT } +}; + +static int adis16203_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + /* currently only one writable parameter which keeps this simple */ + u8 addr = adis16203_addresses[chan->address][1]; + return adis16203_spi_write_reg_16(indio_dev, addr, val & 0x3FFF); +} + +static int adis16203_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + int ret; + int bits; + u8 addr; + s16 val16; + switch (mask) { + case 0: + mutex_lock(&indio_dev->mlock); + addr = adis16203_addresses[chan->address][0]; + ret = adis16203_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + if (val16 & ADIS16203_ERROR_ACTIVE) { + ret = adis16203_check_status(indio_dev); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } + val16 = val16 & ((1 << chan->scan_type.realbits) - 1); + if (chan->scan_type.sign == 's') + val16 = (s16)(val16 << + (16 - chan->scan_type.realbits)) >> + (16 - chan->scan_type.realbits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) + *val2 = 1220; + else + *val2 = 610; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = -470000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_INCLI: + *val = 0; + *val2 = 25000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OFFSET: + *val = 25; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + bits = 14; + mutex_lock(&indio_dev->mlock); + addr = adis16203_addresses[chan->address][1]; + ret = adis16203_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static struct iio_chan_spec adis16203_channels[] = { + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, "supply", 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + in_supply, ADIS16203_SCAN_SUPPLY, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 1, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + in_aux, ADIS16203_SCAN_AUX_ADC, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_INCLI, 1, 0, 0, NULL, 0, IIO_MOD_X, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + incli_x, ADIS16203_SCAN_INCLI_X, + IIO_ST('s', 14, 16, 0), 0), + /* Fixme: Not what it appears to be - see data sheet */ + IIO_CHAN(IIO_INCLI, 1, 0, 0, NULL, 0, IIO_MOD_Y, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + incli_y, ADIS16203_SCAN_INCLI_Y, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN(IIO_TEMP, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT, + temp, ADIS16203_SCAN_TEMP, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN_SOFT_TIMESTAMP(5), +}; + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, adis16203_write_reset, 0); + +static struct attribute *adis16203_attributes[] = { + &iio_dev_attr_reset.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16203_attribute_group = { + .attrs = adis16203_attributes, +}; + +static const struct iio_info adis16203_info = { + .attrs = &adis16203_attribute_group, + .read_raw = &adis16203_read_raw, + .write_raw = &adis16203_write_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit adis16203_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev; + struct adis16203_state *st; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->channels = adis16203_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16203_channels); + indio_dev->info = &adis16203_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis16203_configure_ring(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_buffer_register(indio_dev, + adis16203_channels, + ARRAY_SIZE(adis16203_channels)); + if (ret) { + printk(KERN_ERR "failed to initialize the ring\n"); + goto error_unreg_ring_funcs; + } + + if (spi->irq) { + ret = adis16203_probe_trigger(indio_dev); + if (ret) + goto error_uninitialize_ring; + } + + /* Get the device into a sane initial state */ + ret = adis16203_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + adis16203_remove_trigger(indio_dev); +error_uninitialize_ring: + iio_buffer_unregister(indio_dev); +error_unreg_ring_funcs: + adis16203_unconfigure_ring(indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int adis16203_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + adis16203_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + adis16203_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static struct spi_driver adis16203_driver = { + .driver = { + .name = "adis16203", + .owner = THIS_MODULE, + }, + .probe = adis16203_probe, + .remove = __devexit_p(adis16203_remove), +}; +module_spi_driver(adis16203_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16203 Programmable Digital Vibration Sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16203"); diff --git a/drivers/staging/iio/accel/adis16203_ring.c b/drivers/staging/iio/accel/adis16203_ring.c new file mode 100644 index 00000000..6a8963db --- /dev/null +++ b/drivers/staging/iio/accel/adis16203_ring.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" +#include "adis16203.h" + +/** + * adis16203_read_ring_data() read data registers which will be placed into ring + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @rx: somewhere to pass back the value read + **/ +static int adis16203_read_ring_data(struct device *dev, u8 *rx) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16203_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[ADIS16203_OUTPUTS + 1]; + int ret; + int i; + + mutex_lock(&st->buf_lock); + + spi_message_init(&msg); + + memset(xfers, 0, sizeof(xfers)); + for (i = 0; i <= ADIS16203_OUTPUTS; i++) { + xfers[i].bits_per_word = 8; + xfers[i].cs_change = 1; + xfers[i].len = 2; + xfers[i].delay_usecs = 20; + xfers[i].tx_buf = st->tx + 2 * i; + if (i < 1) /* SUPPLY_OUT: 0x02, AUX_ADC: 0x08 */ + st->tx[2 * i] = ADIS16203_READ_REG(ADIS16203_SUPPLY_OUT + 2 * i); + else + st->tx[2 * i] = ADIS16203_READ_REG(ADIS16203_SUPPLY_OUT + 2 * i + 6); + st->tx[2 * i + 1] = 0; + if (i >= 1) + xfers[i].rx_buf = rx + 2 * (i - 1); + spi_message_add_tail(&xfers[i], &msg); + } + + ret = spi_sync(st->us, &msg); + if (ret) + dev_err(&st->us->dev, "problem when burst reading"); + + mutex_unlock(&st->buf_lock); + + return ret; +} + +/* Whilst this makes a lot of calls to iio_sw_ring functions - it is to device + * specific to be rolled into the core. + */ +static irqreturn_t adis16203_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16203_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + + int i = 0; + s16 *data; + size_t datasize = ring->access->get_bytes_per_datum(ring); + + data = kmalloc(datasize, GFP_KERNEL); + if (data == NULL) { + dev_err(&st->us->dev, "memory alloc failed in ring bh"); + return -ENOMEM; + } + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength) && + adis16203_read_ring_data(&indio_dev->dev, st->rx) >= 0) + for (; i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); i++) + data[i] = be16_to_cpup((__be16 *)&(st->rx[i*2])); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + *((s64 *)(data + ((i + 3)/4)*4)) = pf->timestamp; + + ring->access->store_to(ring, + (u8 *)data, + pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + kfree(data); + + return IRQ_HANDLED; +} + +void adis16203_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +static const struct iio_buffer_setup_ops adis16203_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int adis16203_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct iio_buffer *ring; + + ring = iio_sw_rb_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->buffer = ring; + ring->scan_timestamp = true; + indio_dev->setup_ops = &adis16203_ring_setup_ops; + + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &adis16203_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "adis16203_consumer%d", + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_iio_sw_rb_free: + iio_sw_rb_free(indio_dev->buffer); + return ret; +} diff --git a/drivers/staging/iio/accel/adis16203_trigger.c b/drivers/staging/iio/accel/adis16203_trigger.c new file mode 100644 index 00000000..24bcb8e1 --- /dev/null +++ b/drivers/staging/iio/accel/adis16203_trigger.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" +#include "adis16203.h" + +/** + * adis16203_data_rdy_trigger_set_state() set datardy interrupt state + **/ +static int adis16203_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + + dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state); + return adis16203_set_irq(indio_dev, state); +} + +static const struct iio_trigger_ops adis16203_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &adis16203_data_rdy_trigger_set_state, +}; + +int adis16203_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct adis16203_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("adis16203-dev%d", indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + ret = request_irq(st->us->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "adis16203", + st->trig); + if (ret) + goto error_free_trig; + + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &adis16203_trigger_ops; + st->trig->private_data = indio_dev; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->us->irq, st->trig); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void adis16203_remove_trigger(struct iio_dev *indio_dev) +{ + struct adis16203_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->us->irq, st->trig); + iio_free_trigger(st->trig); +} diff --git a/drivers/staging/iio/accel/adis16204.h b/drivers/staging/iio/accel/adis16204.h new file mode 100644 index 00000000..7cf4e91f --- /dev/null +++ b/drivers/staging/iio/accel/adis16204.h @@ -0,0 +1,133 @@ +#ifndef SPI_ADIS16204_H_ +#define SPI_ADIS16204_H_ + +#define ADIS16204_STARTUP_DELAY 220 /* ms */ + +#define ADIS16204_READ_REG(a) a +#define ADIS16204_WRITE_REG(a) ((a) | 0x80) + +#define ADIS16204_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16204_SUPPLY_OUT 0x02 /* Output, power supply */ +#define ADIS16204_XACCL_OUT 0x04 /* Output, x-axis accelerometer */ +#define ADIS16204_YACCL_OUT 0x06 /* Output, y-axis accelerometer */ +#define ADIS16204_AUX_ADC 0x08 /* Output, auxiliary ADC input */ +#define ADIS16204_TEMP_OUT 0x0A /* Output, temperature */ +#define ADIS16204_X_PEAK_OUT 0x0C /* Twos complement */ +#define ADIS16204_Y_PEAK_OUT 0x0E /* Twos complement */ +#define ADIS16204_XACCL_NULL 0x10 /* Calibration, x-axis acceleration offset null */ +#define ADIS16204_YACCL_NULL 0x12 /* Calibration, y-axis acceleration offset null */ +#define ADIS16204_XACCL_SCALE 0x14 /* X-axis scale factor calibration register */ +#define ADIS16204_YACCL_SCALE 0x16 /* Y-axis scale factor calibration register */ +#define ADIS16204_XY_RSS_OUT 0x18 /* XY combined acceleration (RSS) */ +#define ADIS16204_XY_PEAK_OUT 0x1A /* Peak, XY combined output (RSS) */ +#define ADIS16204_CAP_BUF_1 0x1C /* Capture buffer output register 1 */ +#define ADIS16204_CAP_BUF_2 0x1E /* Capture buffer output register 2 */ +#define ADIS16204_ALM_MAG1 0x20 /* Alarm 1 amplitude threshold */ +#define ADIS16204_ALM_MAG2 0x22 /* Alarm 2 amplitude threshold */ +#define ADIS16204_ALM_CTRL 0x28 /* Alarm control */ +#define ADIS16204_CAPT_PNTR 0x2A /* Capture register address pointer */ +#define ADIS16204_AUX_DAC 0x30 /* Auxiliary DAC data */ +#define ADIS16204_GPIO_CTRL 0x32 /* General-purpose digital input/output control */ +#define ADIS16204_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16204_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16204_AVG_CNT 0x38 /* Operation, filter configuration */ +#define ADIS16204_SLP_CNT 0x3A /* Operation, sleep mode control */ +#define ADIS16204_DIAG_STAT 0x3C /* Diagnostics, system status register */ +#define ADIS16204_GLOB_CMD 0x3E /* Operation, system command register */ + +#define ADIS16204_OUTPUTS 5 + +/* MSC_CTRL */ +#define ADIS16204_MSC_CTRL_PWRUP_SELF_TEST (1 << 10) /* Self-test at power-on: 1 = disabled, 0 = enabled */ +#define ADIS16204_MSC_CTRL_SELF_TEST_EN (1 << 8) /* Self-test enable */ +#define ADIS16204_MSC_CTRL_DATA_RDY_EN (1 << 2) /* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16204_MSC_CTRL_ACTIVE_HIGH (1 << 1) /* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16204_MSC_CTRL_DATA_RDY_DIO2 (1 << 0) /* Data-ready line selection: 1 = DIO2, 0 = DIO1 */ + +/* DIAG_STAT */ +#define ADIS16204_DIAG_STAT_ALARM2 (1<<9) /* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16204_DIAG_STAT_ALARM1 (1<<8) /* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16204_DIAG_STAT_SELFTEST_FAIL (1<<5) /* Self-test diagnostic error flag: 1 = error condition, + 0 = normal operation */ +#define ADIS16204_DIAG_STAT_SPI_FAIL (1<<3) /* SPI communications failure */ +#define ADIS16204_DIAG_STAT_FLASH_UPT (1<<2) /* Flash update failure */ +#define ADIS16204_DIAG_STAT_POWER_HIGH (1<<1) /* Power supply above 3.625 V */ +#define ADIS16204_DIAG_STAT_POWER_LOW (1<<0) /* Power supply below 2.975 V */ + +/* GLOB_CMD */ +#define ADIS16204_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16204_GLOB_CMD_CLEAR_STAT (1<<4) +#define ADIS16204_GLOB_CMD_FACTORY_CAL (1<<1) + +#define ADIS16204_MAX_TX 24 +#define ADIS16204_MAX_RX 24 + +#define ADIS16204_ERROR_ACTIVE (1<<14) + +/** + * struct adis16204_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16204_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + u8 tx[ADIS16204_MAX_TX] ____cacheline_aligned; + u8 rx[ADIS16204_MAX_RX]; +}; + +int adis16204_set_irq(struct iio_dev *indio_dev, bool enable); + +enum adis16204_scan { + ADIS16204_SCAN_SUPPLY, + ADIS16204_SCAN_ACC_X, + ADIS16204_SCAN_ACC_Y, + ADIS16204_SCAN_AUX_ADC, + ADIS16204_SCAN_TEMP, +}; + +#ifdef CONFIG_IIO_BUFFER +void adis16204_remove_trigger(struct iio_dev *indio_dev); +int adis16204_probe_trigger(struct iio_dev *indio_dev); + +ssize_t adis16204_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + +int adis16204_configure_ring(struct iio_dev *indio_dev); +void adis16204_unconfigure_ring(struct iio_dev *indio_dev); + +#else /* CONFIG_IIO_BUFFER */ + +static inline void adis16204_remove_trigger(struct iio_dev *indio_dev) +{ +} + +static inline int adis16204_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline ssize_t +adis16204_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static int adis16204_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void adis16204_unconfigure_ring(struct iio_dev *indio_dev) +{ +} + +#endif /* CONFIG_IIO_BUFFER */ +#endif /* SPI_ADIS16204_H_ */ diff --git a/drivers/staging/iio/accel/adis16204_core.c b/drivers/staging/iio/accel/adis16204_core.c new file mode 100644 index 00000000..fa89364b --- /dev/null +++ b/drivers/staging/iio/accel/adis16204_core.c @@ -0,0 +1,585 @@ +/* + * ADIS16204 Programmable High-g Digital Impact Sensor and Recorder + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "adis16204.h" + +#define DRIVER_NAME "adis16204" + +/** + * adis16204_spi_write_reg_8() - write single byte to a register + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the register to be written + * @val: the value to write + **/ +static int adis16204_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct adis16204_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16204_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16204_spi_write_reg_16() - write 2 bytes to a pair of registers + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: value to be written + **/ +static int adis16204_spi_write_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct adis16204_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16204_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = ADIS16204_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16204_spi_read_reg_16() - read 2 bytes from a 16-bit register + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + **/ +static int adis16204_spi_read_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct adis16204_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 20, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .delay_usecs = 20, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16204_READ_REG(lower_reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + lower_reg_address); + goto error_ret; + } + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int adis16204_check_status(struct iio_dev *indio_dev) +{ + u16 status; + int ret; + + ret = adis16204_spi_read_reg_16(indio_dev, + ADIS16204_DIAG_STAT, &status); + if (ret < 0) { + dev_err(&indio_dev->dev, "Reading status failed\n"); + goto error_ret; + } + ret = status & 0x1F; + + if (status & ADIS16204_DIAG_STAT_SELFTEST_FAIL) + dev_err(&indio_dev->dev, "Self test failure\n"); + if (status & ADIS16204_DIAG_STAT_SPI_FAIL) + dev_err(&indio_dev->dev, "SPI failure\n"); + if (status & ADIS16204_DIAG_STAT_FLASH_UPT) + dev_err(&indio_dev->dev, "Flash update failed\n"); + if (status & ADIS16204_DIAG_STAT_POWER_HIGH) + dev_err(&indio_dev->dev, "Power supply above 3.625V\n"); + if (status & ADIS16204_DIAG_STAT_POWER_LOW) + dev_err(&indio_dev->dev, "Power supply below 2.975V\n"); + +error_ret: + return ret; +} + +static ssize_t adis16204_read_14bit_signed(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + s16 val = 0; + ssize_t ret; + + mutex_lock(&indio_dev->mlock); + + ret = adis16204_spi_read_reg_16(indio_dev, + this_attr->address, (u16 *)&val); + if (!ret) { + if (val & ADIS16204_ERROR_ACTIVE) + adis16204_check_status(indio_dev); + + val = ((s16)(val << 2) >> 2); + ret = sprintf(buf, "%d\n", val); + } + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int adis16204_reset(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16204_spi_write_reg_8(indio_dev, + ADIS16204_GLOB_CMD, + ADIS16204_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&indio_dev->dev, "problem resetting device"); + + return ret; +} + +static ssize_t adis16204_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + if (len < 1) + return -EINVAL; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return adis16204_reset(indio_dev); + } + return -EINVAL; +} + +int adis16204_set_irq(struct iio_dev *indio_dev, bool enable) +{ + int ret = 0; + u16 msc; + + ret = adis16204_spi_read_reg_16(indio_dev, ADIS16204_MSC_CTRL, &msc); + if (ret) + goto error_ret; + + msc |= ADIS16204_MSC_CTRL_ACTIVE_HIGH; + msc &= ~ADIS16204_MSC_CTRL_DATA_RDY_DIO2; + if (enable) + msc |= ADIS16204_MSC_CTRL_DATA_RDY_EN; + else + msc &= ~ADIS16204_MSC_CTRL_DATA_RDY_EN; + + ret = adis16204_spi_write_reg_16(indio_dev, ADIS16204_MSC_CTRL, msc); + +error_ret: + return ret; +} + +static int adis16204_self_test(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16204_spi_write_reg_16(indio_dev, + ADIS16204_MSC_CTRL, + ADIS16204_MSC_CTRL_SELF_TEST_EN); + if (ret) { + dev_err(&indio_dev->dev, "problem starting self test"); + goto err_ret; + } + + adis16204_check_status(indio_dev); + +err_ret: + return ret; +} + +static int adis16204_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + + /* Disable IRQ */ + ret = adis16204_set_irq(indio_dev, false); + if (ret) { + dev_err(&indio_dev->dev, "disable irq failed"); + goto err_ret; + } + + /* Do self test */ + ret = adis16204_self_test(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "self test failure"); + goto err_ret; + } + + /* Read status register to check the result */ + ret = adis16204_check_status(indio_dev); + if (ret) { + adis16204_reset(indio_dev); + dev_err(&indio_dev->dev, "device not playing ball -> reset"); + msleep(ADIS16204_STARTUP_DELAY); + ret = adis16204_check_status(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "giving up"); + goto err_ret; + } + } + +err_ret: + return ret; +} + +/* Unique to this driver currently */ +#define IIO_DEV_ATTR_ACCEL_XY(_show, _addr) \ + IIO_DEVICE_ATTR(in_accel_xy, S_IRUGO, _show, NULL, _addr) +#define IIO_DEV_ATTR_ACCEL_XYPEAK(_show, _addr) \ + IIO_DEVICE_ATTR(in_accel_xypeak, S_IRUGO, _show, NULL, _addr) + +static IIO_DEV_ATTR_ACCEL_XY(adis16204_read_14bit_signed, + ADIS16204_XY_RSS_OUT); +static IIO_DEV_ATTR_ACCEL_XYPEAK(adis16204_read_14bit_signed, + ADIS16204_XY_PEAK_OUT); +static IIO_CONST_ATTR(in_accel_xy_scale, "0.017125"); + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, adis16204_write_reset, 0); + +enum adis16204_channel { + in_supply, + in_aux, + temp, + accel_x, + accel_y, +}; + +static u8 adis16204_addresses[5][3] = { + [in_supply] = { ADIS16204_SUPPLY_OUT }, + [in_aux] = { ADIS16204_AUX_ADC }, + [temp] = { ADIS16204_TEMP_OUT }, + [accel_x] = { ADIS16204_XACCL_OUT, ADIS16204_XACCL_NULL, + ADIS16204_X_PEAK_OUT }, + [accel_y] = { ADIS16204_XACCL_OUT, ADIS16204_YACCL_NULL, + ADIS16204_Y_PEAK_OUT }, +}; + +static int adis16204_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + int ret; + int bits; + u8 addr; + s16 val16; + int addrind; + + switch (mask) { + case 0: + mutex_lock(&indio_dev->mlock); + addr = adis16204_addresses[chan->address][0]; + ret = adis16204_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + if (val16 & ADIS16204_ERROR_ACTIVE) { + ret = adis16204_check_status(indio_dev); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } + val16 = val16 & ((1 << chan->scan_type.realbits) - 1); + if (chan->scan_type.sign == 's') + val16 = (s16)(val16 << + (16 - chan->scan_type.realbits)) >> + (16 - chan->scan_type.realbits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) + *val2 = 1220; + else + *val2 = 610; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = -470000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + if (chan->channel == 'x') + *val2 = 17125; + else + *val2 = 8407; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + *val = 25; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + case IIO_CHAN_INFO_PEAK: + if (mask == IIO_CHAN_INFO_CALIBBIAS) { + bits = 12; + addrind = 1; + } else { /* PEAK_SEPARATE */ + bits = 14; + addrind = 2; + } + mutex_lock(&indio_dev->mlock); + addr = adis16204_addresses[chan->address][addrind]; + ret = adis16204_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int adis16204_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int bits; + s16 val16; + u8 addr; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 12; + break; + default: + return -EINVAL; + }; + val16 = val & ((1 << bits) - 1); + addr = adis16204_addresses[chan->address][1]; + return adis16204_spi_write_reg_16(indio_dev, addr, val16); + } + return -EINVAL; +} + +static struct iio_chan_spec adis16204_channels[] = { + IIO_CHAN(IIO_VOLTAGE, 0, 0, 0, "supply", 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + in_supply, ADIS16204_SCAN_SUPPLY, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 1, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + in_aux, ADIS16204_SCAN_AUX_ADC, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_TEMP, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT, + temp, ADIS16204_SCAN_TEMP, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_X, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_PEAK_SEPARATE_BIT, + accel_x, ADIS16204_SCAN_ACC_X, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Y, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_PEAK_SEPARATE_BIT, + accel_y, ADIS16204_SCAN_ACC_Y, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN_SOFT_TIMESTAMP(5), +}; + +static struct attribute *adis16204_attributes[] = { + &iio_dev_attr_reset.dev_attr.attr, + &iio_dev_attr_in_accel_xy.dev_attr.attr, + &iio_dev_attr_in_accel_xypeak.dev_attr.attr, + &iio_const_attr_in_accel_xy_scale.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16204_attribute_group = { + .attrs = adis16204_attributes, +}; + +static const struct iio_info adis16204_info = { + .attrs = &adis16204_attribute_group, + .read_raw = &adis16204_read_raw, + .write_raw = &adis16204_write_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit adis16204_probe(struct spi_device *spi) +{ + int ret; + struct adis16204_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16204_info; + indio_dev->channels = adis16204_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16204_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis16204_configure_ring(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_buffer_register(indio_dev, + adis16204_channels, + ARRAY_SIZE(adis16204_channels)); + if (ret) { + printk(KERN_ERR "failed to initialize the ring\n"); + goto error_unreg_ring_funcs; + } + + if (spi->irq) { + ret = adis16204_probe_trigger(indio_dev); + if (ret) + goto error_uninitialize_ring; + } + + /* Get the device into a sane initial state */ + ret = adis16204_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + adis16204_remove_trigger(indio_dev); +error_uninitialize_ring: + iio_buffer_unregister(indio_dev); +error_unreg_ring_funcs: + adis16204_unconfigure_ring(indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int adis16204_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + adis16204_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + adis16204_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static struct spi_driver adis16204_driver = { + .driver = { + .name = "adis16204", + .owner = THIS_MODULE, + }, + .probe = adis16204_probe, + .remove = __devexit_p(adis16204_remove), +}; +module_spi_driver(adis16204_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("ADIS16204 High-g Digital Impact Sensor and Recorder"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16204"); diff --git a/drivers/staging/iio/accel/adis16204_ring.c b/drivers/staging/iio/accel/adis16204_ring.c new file mode 100644 index 00000000..5c8ab733 --- /dev/null +++ b/drivers/staging/iio/accel/adis16204_ring.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" +#include "adis16204.h" + +/** + * adis16204_read_ring_data() read data registers which will be placed into ring + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @rx: somewhere to pass back the value read + **/ +static int adis16204_read_ring_data(struct device *dev, u8 *rx) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16204_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[ADIS16204_OUTPUTS + 1]; + int ret; + int i; + + mutex_lock(&st->buf_lock); + + spi_message_init(&msg); + + memset(xfers, 0, sizeof(xfers)); + for (i = 0; i <= ADIS16204_OUTPUTS; i++) { + xfers[i].bits_per_word = 8; + xfers[i].cs_change = 1; + xfers[i].len = 2; + xfers[i].delay_usecs = 20; + xfers[i].tx_buf = st->tx + 2 * i; + st->tx[2 * i] + = ADIS16204_READ_REG(ADIS16204_SUPPLY_OUT + 2 * i); + st->tx[2 * i + 1] = 0; + if (i >= 1) + xfers[i].rx_buf = rx + 2 * (i - 1); + spi_message_add_tail(&xfers[i], &msg); + } + + ret = spi_sync(st->us, &msg); + if (ret) + dev_err(&st->us->dev, "problem when burst reading"); + + mutex_unlock(&st->buf_lock); + + return ret; +} + +/* Whilst this makes a lot of calls to iio_sw_ring functions - it is to device + * specific to be rolled into the core. + */ +static irqreturn_t adis16204_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16204_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int i = 0; + s16 *data; + size_t datasize = ring->access->get_bytes_per_datum(ring); + + data = kmalloc(datasize, GFP_KERNEL); + if (data == NULL) { + dev_err(&st->us->dev, "memory alloc failed in ring bh"); + return -ENOMEM; + } + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength) && + adis16204_read_ring_data(&indio_dev->dev, st->rx) >= 0) + for (; i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); i++) + data[i] = be16_to_cpup((__be16 *)&(st->rx[i*2])); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + *((s64 *)(data + ((i + 3)/4)*4)) = pf->timestamp; + + ring->access->store_to(ring, (u8 *)data, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + kfree(data); + + return IRQ_HANDLED; +} + +void adis16204_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +static const struct iio_buffer_setup_ops adis16204_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int adis16204_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct iio_buffer *ring; + + ring = iio_sw_rb_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->buffer = ring; + ring->scan_timestamp = true; + indio_dev->setup_ops = &adis16204_ring_setup_ops; + + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &adis16204_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + indio_dev->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_iio_sw_rb_free: + iio_sw_rb_free(indio_dev->buffer); + return ret; +} diff --git a/drivers/staging/iio/accel/adis16204_trigger.c b/drivers/staging/iio/accel/adis16204_trigger.c new file mode 100644 index 00000000..6e542af0 --- /dev/null +++ b/drivers/staging/iio/accel/adis16204_trigger.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" +#include "adis16204.h" + +/** + * adis16204_data_rdy_trigger_set_state() set datardy interrupt state + **/ +static int adis16204_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + + dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state); + return adis16204_set_irq(indio_dev, state); +} + +static const struct iio_trigger_ops adis16204_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &adis16204_data_rdy_trigger_set_state, +}; + +int adis16204_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct adis16204_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("adis16204-dev%d", indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + ret = request_irq(st->us->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "adis16204", + st->trig); + if (ret) + goto error_free_trig; + + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &adis16204_trigger_ops; + st->trig->private_data = indio_dev; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->us->irq, st->trig); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void adis16204_remove_trigger(struct iio_dev *indio_dev) +{ + struct adis16204_state *state = iio_priv(indio_dev); + + iio_trigger_unregister(state->trig); + free_irq(state->us->irq, state->trig); + iio_free_trigger(state->trig); +} diff --git a/drivers/staging/iio/accel/adis16209.h b/drivers/staging/iio/accel/adis16209.h new file mode 100644 index 00000000..3c88b86e --- /dev/null +++ b/drivers/staging/iio/accel/adis16209.h @@ -0,0 +1,172 @@ +#ifndef SPI_ADIS16209_H_ +#define SPI_ADIS16209_H_ + +#define ADIS16209_STARTUP_DELAY 220 /* ms */ + +#define ADIS16209_READ_REG(a) a +#define ADIS16209_WRITE_REG(a) ((a) | 0x80) + +/* Flash memory write count */ +#define ADIS16209_FLASH_CNT 0x00 +/* Output, power supply */ +#define ADIS16209_SUPPLY_OUT 0x02 +/* Output, x-axis accelerometer */ +#define ADIS16209_XACCL_OUT 0x04 +/* Output, y-axis accelerometer */ +#define ADIS16209_YACCL_OUT 0x06 +/* Output, auxiliary ADC input */ +#define ADIS16209_AUX_ADC 0x08 +/* Output, temperature */ +#define ADIS16209_TEMP_OUT 0x0A +/* Output, x-axis inclination */ +#define ADIS16209_XINCL_OUT 0x0C +/* Output, y-axis inclination */ +#define ADIS16209_YINCL_OUT 0x0E +/* Output, +/-180 vertical rotational position */ +#define ADIS16209_ROT_OUT 0x10 +/* Calibration, x-axis acceleration offset null */ +#define ADIS16209_XACCL_NULL 0x12 +/* Calibration, y-axis acceleration offset null */ +#define ADIS16209_YACCL_NULL 0x14 +/* Calibration, x-axis inclination offset null */ +#define ADIS16209_XINCL_NULL 0x16 +/* Calibration, y-axis inclination offset null */ +#define ADIS16209_YINCL_NULL 0x18 +/* Calibration, vertical rotation offset null */ +#define ADIS16209_ROT_NULL 0x1A +/* Alarm 1 amplitude threshold */ +#define ADIS16209_ALM_MAG1 0x20 +/* Alarm 2 amplitude threshold */ +#define ADIS16209_ALM_MAG2 0x22 +/* Alarm 1, sample period */ +#define ADIS16209_ALM_SMPL1 0x24 +/* Alarm 2, sample period */ +#define ADIS16209_ALM_SMPL2 0x26 +/* Alarm control */ +#define ADIS16209_ALM_CTRL 0x28 +/* Auxiliary DAC data */ +#define ADIS16209_AUX_DAC 0x30 +/* General-purpose digital input/output control */ +#define ADIS16209_GPIO_CTRL 0x32 +/* Miscellaneous control */ +#define ADIS16209_MSC_CTRL 0x34 +/* Internal sample period (rate) control */ +#define ADIS16209_SMPL_PRD 0x36 +/* Operation, filter configuration */ +#define ADIS16209_AVG_CNT 0x38 +/* Operation, sleep mode control */ +#define ADIS16209_SLP_CNT 0x3A +/* Diagnostics, system status register */ +#define ADIS16209_DIAG_STAT 0x3C +/* Operation, system command register */ +#define ADIS16209_GLOB_CMD 0x3E + +#define ADIS16209_OUTPUTS 8 + +/* MSC_CTRL */ +/* Self-test at power-on: 1 = disabled, 0 = enabled */ +#define ADIS16209_MSC_CTRL_PWRUP_SELF_TEST (1 << 10) +/* Self-test enable */ +#define ADIS16209_MSC_CTRL_SELF_TEST_EN (1 << 8) +/* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16209_MSC_CTRL_DATA_RDY_EN (1 << 2) +/* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16209_MSC_CTRL_ACTIVE_HIGH (1 << 1) +/* Data-ready line selection: 1 = DIO2, 0 = DIO1 */ +#define ADIS16209_MSC_CTRL_DATA_RDY_DIO2 (1 << 0) + +/* DIAG_STAT */ +/* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16209_DIAG_STAT_ALARM2 (1<<9) +/* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16209_DIAG_STAT_ALARM1 (1<<8) +/* Self-test diagnostic error flag: 1 = error condition, 0 = normal operation */ +#define ADIS16209_DIAG_STAT_SELFTEST_FAIL (1<<5) +/* SPI communications failure */ +#define ADIS16209_DIAG_STAT_SPI_FAIL (1<<3) +/* Flash update failure */ +#define ADIS16209_DIAG_STAT_FLASH_UPT (1<<2) +/* Power supply above 3.625 V */ +#define ADIS16209_DIAG_STAT_POWER_HIGH (1<<1) +/* Power supply below 3.15 V */ +#define ADIS16209_DIAG_STAT_POWER_LOW (1<<0) + +/* GLOB_CMD */ +#define ADIS16209_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16209_GLOB_CMD_CLEAR_STAT (1<<4) +#define ADIS16209_GLOB_CMD_FACTORY_CAL (1<<1) + +#define ADIS16209_MAX_TX 24 +#define ADIS16209_MAX_RX 24 + +#define ADIS16209_ERROR_ACTIVE (1<<14) + +/** + * struct adis16209_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16209_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + u8 tx[ADIS16209_MAX_TX] ____cacheline_aligned; + u8 rx[ADIS16209_MAX_RX]; +}; + +int adis16209_set_irq(struct iio_dev *indio_dev, bool enable); + +#define ADIS16209_SCAN_SUPPLY 0 +#define ADIS16209_SCAN_ACC_X 1 +#define ADIS16209_SCAN_ACC_Y 2 +#define ADIS16209_SCAN_AUX_ADC 3 +#define ADIS16209_SCAN_TEMP 4 +#define ADIS16209_SCAN_INCLI_X 5 +#define ADIS16209_SCAN_INCLI_Y 6 +#define ADIS16209_SCAN_ROT 7 + +#ifdef CONFIG_IIO_BUFFER + +void adis16209_remove_trigger(struct iio_dev *indio_dev); +int adis16209_probe_trigger(struct iio_dev *indio_dev); + +ssize_t adis16209_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + +int adis16209_configure_ring(struct iio_dev *indio_dev); +void adis16209_unconfigure_ring(struct iio_dev *indio_dev); + +#else /* CONFIG_IIO_BUFFER */ + +static inline void adis16209_remove_trigger(struct iio_dev *indio_dev) +{ +} + +static inline int adis16209_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline ssize_t +adis16209_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static int adis16209_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void adis16209_unconfigure_ring(struct iio_dev *indio_dev) +{ +} + +#endif /* CONFIG_IIO_BUFFER */ +#endif /* SPI_ADIS16209_H_ */ diff --git a/drivers/staging/iio/accel/adis16209_core.c b/drivers/staging/iio/accel/adis16209_core.c new file mode 100644 index 00000000..a98715f6 --- /dev/null +++ b/drivers/staging/iio/accel/adis16209_core.c @@ -0,0 +1,560 @@ +/* + * ADIS16209 Programmable Digital Vibration Sensor driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "adis16209.h" + +#define DRIVER_NAME "adis16209" + +/** + * adis16209_spi_write_reg_8() - write single byte to a register + * @indio_dev: iio device associated with actual device + * @reg_address: the address of the register to be written + * @val: the value to write + **/ +static int adis16209_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct adis16209_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16209_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16209_spi_write_reg_16() - write 2 bytes to a pair of registers + * @indio_dev: iio device associated actual device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: value to be written + **/ +static int adis16209_spi_write_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct adis16209_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 30, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + .delay_usecs = 30, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16209_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = ADIS16209_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16209_spi_read_reg_16() - read 2 bytes from a 16-bit register + * @indio_dev: iio device associated with device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + **/ +static int adis16209_spi_read_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct adis16209_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 30, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .delay_usecs = 30, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16209_READ_REG(lower_reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, + "problem when reading 16 bit register 0x%02X", + lower_reg_address); + goto error_ret; + } + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int adis16209_reset(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16209_spi_write_reg_8(indio_dev, + ADIS16209_GLOB_CMD, + ADIS16209_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&indio_dev->dev, "problem resetting device"); + + return ret; +} + +static ssize_t adis16209_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + if (len < 1) + return -EINVAL; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return adis16209_reset(indio_dev); + } + return -EINVAL; +} + +int adis16209_set_irq(struct iio_dev *indio_dev, bool enable) +{ + int ret = 0; + u16 msc; + + ret = adis16209_spi_read_reg_16(indio_dev, ADIS16209_MSC_CTRL, &msc); + if (ret) + goto error_ret; + + msc |= ADIS16209_MSC_CTRL_ACTIVE_HIGH; + msc &= ~ADIS16209_MSC_CTRL_DATA_RDY_DIO2; + if (enable) + msc |= ADIS16209_MSC_CTRL_DATA_RDY_EN; + else + msc &= ~ADIS16209_MSC_CTRL_DATA_RDY_EN; + + ret = adis16209_spi_write_reg_16(indio_dev, ADIS16209_MSC_CTRL, msc); + +error_ret: + return ret; +} + +static int adis16209_check_status(struct iio_dev *indio_dev) +{ + u16 status; + int ret; + + ret = adis16209_spi_read_reg_16(indio_dev, + ADIS16209_DIAG_STAT, &status); + if (ret < 0) { + dev_err(&indio_dev->dev, "Reading status failed\n"); + goto error_ret; + } + ret = status & 0x1F; + + if (status & ADIS16209_DIAG_STAT_SELFTEST_FAIL) + dev_err(&indio_dev->dev, "Self test failure\n"); + if (status & ADIS16209_DIAG_STAT_SPI_FAIL) + dev_err(&indio_dev->dev, "SPI failure\n"); + if (status & ADIS16209_DIAG_STAT_FLASH_UPT) + dev_err(&indio_dev->dev, "Flash update failed\n"); + if (status & ADIS16209_DIAG_STAT_POWER_HIGH) + dev_err(&indio_dev->dev, "Power supply above 3.625V\n"); + if (status & ADIS16209_DIAG_STAT_POWER_LOW) + dev_err(&indio_dev->dev, "Power supply below 3.15V\n"); + +error_ret: + return ret; +} + +static int adis16209_self_test(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16209_spi_write_reg_16(indio_dev, + ADIS16209_MSC_CTRL, + ADIS16209_MSC_CTRL_SELF_TEST_EN); + if (ret) { + dev_err(&indio_dev->dev, "problem starting self test"); + goto err_ret; + } + + adis16209_check_status(indio_dev); + +err_ret: + return ret; +} + +static int adis16209_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + + /* Disable IRQ */ + ret = adis16209_set_irq(indio_dev, false); + if (ret) { + dev_err(&indio_dev->dev, "disable irq failed"); + goto err_ret; + } + + /* Do self test */ + ret = adis16209_self_test(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "self test failure"); + goto err_ret; + } + + /* Read status register to check the result */ + ret = adis16209_check_status(indio_dev); + if (ret) { + adis16209_reset(indio_dev); + dev_err(&indio_dev->dev, "device not playing ball -> reset"); + msleep(ADIS16209_STARTUP_DELAY); + ret = adis16209_check_status(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "giving up"); + goto err_ret; + } + } + +err_ret: + return ret; +} + +enum adis16209_chan { + in_supply, + temp, + accel_x, + accel_y, + incli_x, + incli_y, + in_aux, + rot, +}; + +static const u8 adis16209_addresses[8][2] = { + [in_supply] = { ADIS16209_SUPPLY_OUT }, + [in_aux] = { ADIS16209_AUX_ADC }, + [accel_x] = { ADIS16209_XACCL_OUT, ADIS16209_XACCL_NULL }, + [accel_y] = { ADIS16209_YACCL_OUT, ADIS16209_YACCL_NULL }, + [incli_x] = { ADIS16209_XINCL_OUT, ADIS16209_XINCL_NULL }, + [incli_y] = { ADIS16209_YINCL_OUT, ADIS16209_YINCL_NULL }, + [rot] = { ADIS16209_ROT_OUT }, + [temp] = { ADIS16209_TEMP_OUT }, +}; + +static int adis16209_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int bits; + s16 val16; + u8 addr; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + case IIO_INCLI: + bits = 14; + break; + default: + return -EINVAL; + }; + val16 = val & ((1 << bits) - 1); + addr = adis16209_addresses[chan->address][1]; + return adis16209_spi_write_reg_16(indio_dev, addr, val16); + } + return -EINVAL; +} + +static int adis16209_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case 0: + mutex_lock(&indio_dev->mlock); + addr = adis16209_addresses[chan->address][0]; + ret = adis16209_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + if (val16 & ADIS16209_ERROR_ACTIVE) { + ret = adis16209_check_status(indio_dev); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } + val16 = val16 & ((1 << chan->scan_type.realbits) - 1); + if (chan->scan_type.sign == 's') + val16 = (s16)(val16 << + (16 - chan->scan_type.realbits)) >> + (16 - chan->scan_type.realbits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) + *val2 = 305180; + else + *val2 = 610500; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = -470000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = 2394; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_INCLI: + *val = 0; + *val2 = 436; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + *val = 25; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ACCEL: + bits = 14; + break; + default: + return -EINVAL; + }; + mutex_lock(&indio_dev->mlock); + addr = adis16209_addresses[chan->address][1]; + ret = adis16209_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static struct iio_chan_spec adis16209_channels[] = { + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + in_supply, ADIS16209_SCAN_SUPPLY, + IIO_ST('u', 14, 16, 0), 0), + IIO_CHAN(IIO_TEMP, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT, + temp, ADIS16209_SCAN_TEMP, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_X, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + accel_x, ADIS16209_SCAN_ACC_X, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Y, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + accel_y, ADIS16209_SCAN_ACC_Y, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 1, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + in_aux, ADIS16209_SCAN_AUX_ADC, + IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_INCLI, 1, 0, 0, NULL, 0, IIO_MOD_X, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + incli_x, ADIS16209_SCAN_INCLI_X, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN(IIO_INCLI, 1, 0, 0, NULL, 0, IIO_MOD_Y, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + incli_y, ADIS16209_SCAN_INCLI_Y, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN(IIO_ROT, 0, 1, 0, NULL, 0, IIO_MOD_X, + 0, + rot, ADIS16209_SCAN_ROT, + IIO_ST('s', 14, 16, 0), 0), + IIO_CHAN_SOFT_TIMESTAMP(8) +}; + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, adis16209_write_reset, 0); + +static struct attribute *adis16209_attributes[] = { + &iio_dev_attr_reset.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16209_attribute_group = { + .attrs = adis16209_attributes, +}; + +static const struct iio_info adis16209_info = { + .attrs = &adis16209_attribute_group, + .read_raw = &adis16209_read_raw, + .write_raw = &adis16209_write_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit adis16209_probe(struct spi_device *spi) +{ + int ret; + struct adis16209_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16209_info; + indio_dev->channels = adis16209_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16209_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis16209_configure_ring(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_buffer_register(indio_dev, + adis16209_channels, + ARRAY_SIZE(adis16209_channels)); + if (ret) { + printk(KERN_ERR "failed to initialize the ring\n"); + goto error_unreg_ring_funcs; + } + + if (spi->irq) { + ret = adis16209_probe_trigger(indio_dev); + if (ret) + goto error_uninitialize_ring; + } + + /* Get the device into a sane initial state */ + ret = adis16209_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + adis16209_remove_trigger(indio_dev); +error_uninitialize_ring: + iio_buffer_unregister(indio_dev); +error_unreg_ring_funcs: + adis16209_unconfigure_ring(indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int adis16209_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + flush_scheduled_work(); + + iio_device_unregister(indio_dev); + adis16209_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + adis16209_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static struct spi_driver adis16209_driver = { + .driver = { + .name = "adis16209", + .owner = THIS_MODULE, + }, + .probe = adis16209_probe, + .remove = __devexit_p(adis16209_remove), +}; +module_spi_driver(adis16209_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16209 Digital Vibration Sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16209"); diff --git a/drivers/staging/iio/accel/adis16209_ring.c b/drivers/staging/iio/accel/adis16209_ring.c new file mode 100644 index 00000000..57254b6b --- /dev/null +++ b/drivers/staging/iio/accel/adis16209_ring.c @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" +#include "adis16209.h" + +/** + * adis16209_read_ring_data() read data registers which will be placed into ring + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @rx: somewhere to pass back the value read + **/ +static int adis16209_read_ring_data(struct device *dev, u8 *rx) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16209_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[ADIS16209_OUTPUTS + 1]; + int ret; + int i; + + mutex_lock(&st->buf_lock); + + spi_message_init(&msg); + + memset(xfers, 0, sizeof(xfers)); + for (i = 0; i <= ADIS16209_OUTPUTS; i++) { + xfers[i].bits_per_word = 8; + xfers[i].cs_change = 1; + xfers[i].len = 2; + xfers[i].delay_usecs = 30; + xfers[i].tx_buf = st->tx + 2 * i; + st->tx[2 * i] + = ADIS16209_READ_REG(ADIS16209_SUPPLY_OUT + 2 * i); + st->tx[2 * i + 1] = 0; + if (i >= 1) + xfers[i].rx_buf = rx + 2 * (i - 1); + spi_message_add_tail(&xfers[i], &msg); + } + + ret = spi_sync(st->us, &msg); + if (ret) + dev_err(&st->us->dev, "problem when burst reading"); + + mutex_unlock(&st->buf_lock); + + return ret; +} + +/* Whilst this makes a lot of calls to iio_sw_ring functions - it is to device + * specific to be rolled into the core. + */ +static irqreturn_t adis16209_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16209_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + + int i = 0; + s16 *data; + size_t datasize = ring->access->get_bytes_per_datum(ring); + + data = kmalloc(datasize , GFP_KERNEL); + if (data == NULL) { + dev_err(&st->us->dev, "memory alloc failed in ring bh"); + return -ENOMEM; + } + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength) && + adis16209_read_ring_data(&indio_dev->dev, st->rx) >= 0) + for (; i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); i++) + data[i] = be16_to_cpup((__be16 *)&(st->rx[i*2])); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + *((s64 *)(data + ((i + 3)/4)*4)) = pf->timestamp; + + ring->access->store_to(ring, (u8 *)data, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + kfree(data); + + return IRQ_HANDLED; +} + +void adis16209_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +static const struct iio_buffer_setup_ops adis16209_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int adis16209_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct iio_buffer *ring; + + ring = iio_sw_rb_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->buffer = ring; + ring->scan_timestamp = true; + indio_dev->setup_ops = &adis16209_ring_setup_ops; + + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &adis16209_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + indio_dev->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_iio_sw_rb_free: + iio_sw_rb_free(indio_dev->buffer); + return ret; +} diff --git a/drivers/staging/iio/accel/adis16209_trigger.c b/drivers/staging/iio/accel/adis16209_trigger.c new file mode 100644 index 00000000..c5d82c1a --- /dev/null +++ b/drivers/staging/iio/accel/adis16209_trigger.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" +#include "adis16209.h" + +/** + * adis16209_data_rdy_trig_poll() the event handler for the data rdy trig + **/ +static irqreturn_t adis16209_data_rdy_trig_poll(int irq, void *trig) +{ + iio_trigger_poll(trig, iio_get_time_ns()); + return IRQ_HANDLED; +} + +/** + * adis16209_data_rdy_trigger_set_state() set datardy interrupt state + **/ +static int adis16209_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + + dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state); + return adis16209_set_irq(indio_dev, state); +} + +static const struct iio_trigger_ops adis16209_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &adis16209_data_rdy_trigger_set_state, +}; + +int adis16209_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct adis16209_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("adis16209-dev%d", indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + ret = request_irq(st->us->irq, + adis16209_data_rdy_trig_poll, + IRQF_TRIGGER_RISING, + "adis16209", + st->trig); + if (ret) + goto error_free_trig; + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &adis16209_trigger_ops; + st->trig->private_data = indio_dev; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->us->irq, st->trig); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void adis16209_remove_trigger(struct iio_dev *indio_dev) +{ + struct adis16209_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->us->irq, st->trig); + iio_free_trigger(st->trig); +} diff --git a/drivers/staging/iio/accel/adis16220.h b/drivers/staging/iio/accel/adis16220.h new file mode 100644 index 00000000..024313cf --- /dev/null +++ b/drivers/staging/iio/accel/adis16220.h @@ -0,0 +1,140 @@ +#ifndef SPI_ADIS16220_H_ +#define SPI_ADIS16220_H_ + +#define ADIS16220_STARTUP_DELAY 220 /* ms */ + +#define ADIS16220_READ_REG(a) a +#define ADIS16220_WRITE_REG(a) ((a) | 0x80) + +/* Flash memory write count */ +#define ADIS16220_FLASH_CNT 0x00 +/* Control, acceleration offset adjustment control */ +#define ADIS16220_ACCL_NULL 0x02 +/* Control, AIN1 offset adjustment control */ +#define ADIS16220_AIN1_NULL 0x04 +/* Control, AIN2 offset adjustment control */ +#define ADIS16220_AIN2_NULL 0x06 +/* Output, power supply during capture */ +#define ADIS16220_CAPT_SUPPLY 0x0A +/* Output, temperature during capture */ +#define ADIS16220_CAPT_TEMP 0x0C +/* Output, peak acceleration during capture */ +#define ADIS16220_CAPT_PEAKA 0x0E +/* Output, peak AIN1 level during capture */ +#define ADIS16220_CAPT_PEAK1 0x10 +/* Output, peak AIN2 level during capture */ +#define ADIS16220_CAPT_PEAK2 0x12 +/* Output, capture buffer for acceleration */ +#define ADIS16220_CAPT_BUFA 0x14 +/* Output, capture buffer for AIN1 */ +#define ADIS16220_CAPT_BUF1 0x16 +/* Output, capture buffer for AIN2 */ +#define ADIS16220_CAPT_BUF2 0x18 +/* Control, capture buffer address pointer */ +#define ADIS16220_CAPT_PNTR 0x1A +/* Control, capture control register */ +#define ADIS16220_CAPT_CTRL 0x1C +/* Control, capture period (automatic mode) */ +#define ADIS16220_CAPT_PRD 0x1E +/* Control, Alarm A, acceleration peak threshold */ +#define ADIS16220_ALM_MAGA 0x20 +/* Control, Alarm 1, AIN1 peak threshold */ +#define ADIS16220_ALM_MAG1 0x22 +/* Control, Alarm 2, AIN2 peak threshold */ +#define ADIS16220_ALM_MAG2 0x24 +/* Control, Alarm S, peak threshold */ +#define ADIS16220_ALM_MAGS 0x26 +/* Control, alarm configuration register */ +#define ADIS16220_ALM_CTRL 0x28 +/* Control, general I/O configuration */ +#define ADIS16220_GPIO_CTRL 0x32 +/* Control, self-test control, AIN configuration */ +#define ADIS16220_MSC_CTRL 0x34 +/* Control, digital I/O configuration */ +#define ADIS16220_DIO_CTRL 0x36 +/* Control, filter configuration */ +#define ADIS16220_AVG_CNT 0x38 +/* Status, system status */ +#define ADIS16220_DIAG_STAT 0x3C +/* Control, system commands */ +#define ADIS16220_GLOB_CMD 0x3E +/* Status, self-test response */ +#define ADIS16220_ST_DELTA 0x40 +/* Lot Identification Code 1 */ +#define ADIS16220_LOT_ID1 0x52 +/* Lot Identification Code 2 */ +#define ADIS16220_LOT_ID2 0x54 +/* Product identifier; convert to decimal = 16220 */ +#define ADIS16220_PROD_ID 0x56 +/* Serial number */ +#define ADIS16220_SERIAL_NUM 0x58 + +#define ADIS16220_CAPTURE_SIZE 2048 + +/* MSC_CTRL */ +#define ADIS16220_MSC_CTRL_SELF_TEST_EN (1 << 8) +#define ADIS16220_MSC_CTRL_POWER_SUP_COM_AIN1 (1 << 1) +#define ADIS16220_MSC_CTRL_POWER_SUP_COM_AIN2 (1 << 0) + +/* DIO_CTRL */ +#define ADIS16220_MSC_CTRL_DIO2_BUSY_IND (3<<4) +#define ADIS16220_MSC_CTRL_DIO1_BUSY_IND (3<<2) +#define ADIS16220_MSC_CTRL_DIO2_ACT_HIGH (1<<1) +#define ADIS16220_MSC_CTRL_DIO1_ACT_HIGH (1<<0) + +/* DIAG_STAT */ +/* AIN2 sample > ALM_MAG2 */ +#define ADIS16220_DIAG_STAT_ALM_MAG2 (1<<14) +/* AIN1 sample > ALM_MAG1 */ +#define ADIS16220_DIAG_STAT_ALM_MAG1 (1<<13) +/* Acceleration sample > ALM_MAGA */ +#define ADIS16220_DIAG_STAT_ALM_MAGA (1<<12) +/* Error condition programmed into ALM_MAGS[11:0] and ALM_CTRL[5:4] is true */ +#define ADIS16220_DIAG_STAT_ALM_MAGS (1<<11) +/* |Peak value in AIN2 data capture| > ALM_MAG2 */ +#define ADIS16220_DIAG_STAT_PEAK_AIN2 (1<<10) +/* |Peak value in AIN1 data capture| > ALM_MAG1 */ +#define ADIS16220_DIAG_STAT_PEAK_AIN1 (1<<9) +/* |Peak value in acceleration data capture| > ALM_MAGA */ +#define ADIS16220_DIAG_STAT_PEAK_ACCEL (1<<8) +/* Data ready, capture complete */ +#define ADIS16220_DIAG_STAT_DATA_RDY (1<<7) +#define ADIS16220_DIAG_STAT_FLASH_CHK (1<<6) +#define ADIS16220_DIAG_STAT_SELF_TEST (1<<5) +/* Capture period violation/interruption */ +#define ADIS16220_DIAG_STAT_VIOLATION (1<<4) +/* SPI communications failure */ +#define ADIS16220_DIAG_STAT_SPI_FAIL (1<<3) +/* Flash update failure */ +#define ADIS16220_DIAG_STAT_FLASH_UPT (1<<2) +/* Power supply above 3.625 V */ +#define ADIS16220_DIAG_STAT_POWER_HIGH (1<<1) +/* Power supply below 3.15 V */ +#define ADIS16220_DIAG_STAT_POWER_LOW (1<<0) + +/* GLOB_CMD */ +#define ADIS16220_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16220_GLOB_CMD_SELF_TEST (1<<2) +#define ADIS16220_GLOB_CMD_PWR_DOWN (1<<1) + +#define ADIS16220_MAX_TX 2048 +#define ADIS16220_MAX_RX 2048 + +#define ADIS16220_SPI_BURST (u32)(1000 * 1000) +#define ADIS16220_SPI_FAST (u32)(2000 * 1000) + +/** + * struct adis16220_state - device instance specific data + * @us: actual spi_device + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16220_state { + struct spi_device *us; + struct mutex buf_lock; + u8 tx[ADIS16220_MAX_TX] ____cacheline_aligned; + u8 rx[ADIS16220_MAX_RX]; +}; + +#endif /* SPI_ADIS16220_H_ */ diff --git a/drivers/staging/iio/accel/adis16220_core.c b/drivers/staging/iio/accel/adis16220_core.c new file mode 100644 index 00000000..51a852d4 --- /dev/null +++ b/drivers/staging/iio/accel/adis16220_core.c @@ -0,0 +1,716 @@ +/* + * ADIS16220 Programmable Digital Vibration Sensor driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#include "adis16220.h" + +#define DRIVER_NAME "adis16220" + +/** + * adis16220_spi_write_reg_8() - write single byte to a register + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the register to be written + * @val: the value to write + **/ +static int adis16220_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct adis16220_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16220_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16220_spi_write_reg_16() - write 2 bytes to a pair of registers + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: value to be written + **/ +static int adis16220_spi_write_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct adis16220_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 35, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + .delay_usecs = 35, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16220_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = ADIS16220_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16220_spi_read_reg_16() - read 2 bytes from a 16-bit register + * @indio_dev: iio device associated with child of actual device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + **/ +static int adis16220_spi_read_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct adis16220_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 35, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 35, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16220_READ_REG(lower_reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, + "problem when reading 16 bit register 0x%02X", + lower_reg_address); + goto error_ret; + } + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t adis16220_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + ssize_t ret; + s16 val = 0; + + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + ret = adis16220_spi_read_reg_16(indio_dev, this_attr->address, + (u16 *)&val); + mutex_unlock(&indio_dev->mlock); + if (ret) + return ret; + return sprintf(buf, "%d\n", val); +} + +static ssize_t adis16220_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + u16 val; + + ret = kstrtou16(buf, 10, &val); + if (ret) + goto error_ret; + ret = adis16220_spi_write_reg_16(indio_dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int adis16220_capture(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16220_spi_write_reg_16(indio_dev, + ADIS16220_GLOB_CMD, + 0xBF08); /* initiates a manual data capture */ + if (ret) + dev_err(&indio_dev->dev, "problem beginning capture"); + + msleep(10); /* delay for capture to finish */ + + return ret; +} + +static int adis16220_reset(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16220_spi_write_reg_8(indio_dev, + ADIS16220_GLOB_CMD, + ADIS16220_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&indio_dev->dev, "problem resetting device"); + + return ret; +} + +static ssize_t adis16220_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + bool val; + int ret; + + ret = strtobool(buf, &val); + if (ret) + return ret; + if (!val) + return -EINVAL; + + ret = adis16220_reset(indio_dev); + if (ret) + return ret; + return len; +} + +static ssize_t adis16220_write_capture(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + bool val; + int ret; + + ret = strtobool(buf, &val); + if (ret) + return ret; + if (!val) + return -EINVAL; + ret = adis16220_capture(indio_dev); + if (ret) + return ret; + + return len; +} + +static int adis16220_check_status(struct iio_dev *indio_dev) +{ + u16 status; + int ret; + + ret = adis16220_spi_read_reg_16(indio_dev, ADIS16220_DIAG_STAT, + &status); + + if (ret < 0) { + dev_err(&indio_dev->dev, "Reading status failed\n"); + goto error_ret; + } + ret = status & 0x7F; + + if (status & ADIS16220_DIAG_STAT_VIOLATION) + dev_err(&indio_dev->dev, + "Capture period violation/interruption\n"); + if (status & ADIS16220_DIAG_STAT_SPI_FAIL) + dev_err(&indio_dev->dev, "SPI failure\n"); + if (status & ADIS16220_DIAG_STAT_FLASH_UPT) + dev_err(&indio_dev->dev, "Flash update failed\n"); + if (status & ADIS16220_DIAG_STAT_POWER_HIGH) + dev_err(&indio_dev->dev, "Power supply above 3.625V\n"); + if (status & ADIS16220_DIAG_STAT_POWER_LOW) + dev_err(&indio_dev->dev, "Power supply below 3.15V\n"); + +error_ret: + return ret; +} + +static int adis16220_self_test(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16220_spi_write_reg_16(indio_dev, + ADIS16220_MSC_CTRL, + ADIS16220_MSC_CTRL_SELF_TEST_EN); + if (ret) { + dev_err(&indio_dev->dev, "problem starting self test"); + goto err_ret; + } + + adis16220_check_status(indio_dev); + +err_ret: + return ret; +} + +static int adis16220_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + + /* Do self test */ + ret = adis16220_self_test(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "self test failure"); + goto err_ret; + } + + /* Read status register to check the result */ + ret = adis16220_check_status(indio_dev); + if (ret) { + adis16220_reset(indio_dev); + dev_err(&indio_dev->dev, "device not playing ball -> reset"); + msleep(ADIS16220_STARTUP_DELAY); + ret = adis16220_check_status(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "giving up"); + goto err_ret; + } + } + +err_ret: + return ret; +} + +static ssize_t adis16220_capture_buffer_read(struct iio_dev *indio_dev, + char *buf, + loff_t off, + size_t count, + int addr) +{ + struct adis16220_state *st = iio_priv(indio_dev); + struct spi_message msg; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 25, + }, { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .cs_change = 1, + .delay_usecs = 25, + }, + }; + int ret; + int i; + + if (unlikely(!count)) + return count; + + if ((off >= ADIS16220_CAPTURE_SIZE) || (count & 1) || (off & 1)) + return -EINVAL; + + if (off + count > ADIS16220_CAPTURE_SIZE) + count = ADIS16220_CAPTURE_SIZE - off; + + /* write the begin position of capture buffer */ + ret = adis16220_spi_write_reg_16(indio_dev, + ADIS16220_CAPT_PNTR, + off > 1); + if (ret) + return -EIO; + + /* read count/2 values from capture buffer */ + mutex_lock(&st->buf_lock); + + for (i = 0; i < count; i += 2) { + st->tx[i] = ADIS16220_READ_REG(addr); + st->tx[i + 1] = 0; + } + xfers[1].len = count; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + + mutex_unlock(&st->buf_lock); + return -EIO; + } + + memcpy(buf, st->rx, count); + + mutex_unlock(&st->buf_lock); + return count; +} + +static ssize_t adis16220_accel_bin_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, + loff_t off, + size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return adis16220_capture_buffer_read(indio_dev, buf, + off, count, + ADIS16220_CAPT_BUFA); +} + +static struct bin_attribute accel_bin = { + .attr = { + .name = "accel_bin", + .mode = S_IRUGO, + }, + .read = adis16220_accel_bin_read, + .size = ADIS16220_CAPTURE_SIZE, +}; + +static ssize_t adis16220_adc1_bin_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, + size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return adis16220_capture_buffer_read(indio_dev, buf, + off, count, + ADIS16220_CAPT_BUF1); +} + +static struct bin_attribute adc1_bin = { + .attr = { + .name = "in0_bin", + .mode = S_IRUGO, + }, + .read = adis16220_adc1_bin_read, + .size = ADIS16220_CAPTURE_SIZE, +}; + +static ssize_t adis16220_adc2_bin_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, + size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + return adis16220_capture_buffer_read(indio_dev, buf, + off, count, + ADIS16220_CAPT_BUF2); +} + + +static struct bin_attribute adc2_bin = { + .attr = { + .name = "in1_bin", + .mode = S_IRUGO, + }, + .read = adis16220_adc2_bin_read, + .size = ADIS16220_CAPTURE_SIZE, +}; + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, + adis16220_write_reset, 0); + +#define IIO_DEV_ATTR_CAPTURE(_store) \ + IIO_DEVICE_ATTR(capture, S_IWUSR, NULL, _store, 0) + +static IIO_DEV_ATTR_CAPTURE(adis16220_write_capture); + +#define IIO_DEV_ATTR_CAPTURE_COUNT(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(capture_count, _mode, _show, _store, _addr) + +static IIO_DEV_ATTR_CAPTURE_COUNT(S_IWUSR | S_IRUGO, + adis16220_read_16bit, + adis16220_write_16bit, + ADIS16220_CAPT_PNTR); + +enum adis16220_channel { + in_supply, in_1, in_2, accel, temp +}; + +struct adis16220_address_spec { + u8 addr; + u8 bits; + bool sign; +}; + +/* Address / bits / signed */ +static const struct adis16220_address_spec adis16220_addresses[][3] = { + [in_supply] = { { ADIS16220_CAPT_SUPPLY, 12, 0 }, }, + [in_1] = { { ADIS16220_CAPT_BUF1, 16, 1 }, + { ADIS16220_AIN1_NULL, 16, 1 }, + { ADIS16220_CAPT_PEAK1, 16, 1 }, }, + [in_2] = { { ADIS16220_CAPT_BUF2, 16, 1 }, + { ADIS16220_AIN2_NULL, 16, 1 }, + { ADIS16220_CAPT_PEAK2, 16, 1 }, }, + [accel] = { { ADIS16220_CAPT_BUFA, 16, 1 }, + { ADIS16220_ACCL_NULL, 16, 1 }, + { ADIS16220_CAPT_PEAKA, 16, 1 }, }, + [temp] = { { ADIS16220_CAPT_TEMP, 12, 0 }, } +}; + +static int adis16220_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + int ret = -EINVAL; + int addrind = 0; + u16 uval; + s16 sval; + u8 bits; + + switch (mask) { + case 0: + addrind = 0; + break; + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + *val = 25; + return IIO_VAL_INT; + } + addrind = 1; + break; + case IIO_CHAN_INFO_PEAK: + addrind = 2; + break; + case IIO_CHAN_INFO_SCALE: + *val = 0; + switch (chan->type) { + case IIO_TEMP: + *val2 = -470000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val2 = 1887042; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_VOLTAGE: + if (chan->channel == 0) + *val2 = 0012221; + else /* Should really be dependent on VDD */ + *val2 = 305; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + if (adis16220_addresses[chan->address][addrind].sign) { + ret = adis16220_spi_read_reg_16(indio_dev, + adis16220_addresses[chan + ->address] + [addrind].addr, + &sval); + if (ret) + return ret; + bits = adis16220_addresses[chan->address][addrind].bits; + sval &= (1 << bits) - 1; + sval = (s16)(sval << (16 - bits)) >> (16 - bits); + *val = sval; + return IIO_VAL_INT; + } else { + ret = adis16220_spi_read_reg_16(indio_dev, + adis16220_addresses[chan + ->address] + [addrind].addr, + &uval); + if (ret) + return ret; + bits = adis16220_addresses[chan->address][addrind].bits; + uval &= (1 << bits) - 1; + *val = uval; + return IIO_VAL_INT; + } +} + +static const struct iio_chan_spec adis16220_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "supply", + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in_supply, + }, { + .type = IIO_ACCEL, + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_PEAK_SEPARATE_BIT, + .address = accel, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = temp, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in_1, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .address = in_2, + } +}; + +static struct attribute *adis16220_attributes[] = { + &iio_dev_attr_reset.dev_attr.attr, + &iio_dev_attr_capture.dev_attr.attr, + &iio_dev_attr_capture_count.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16220_attribute_group = { + .attrs = adis16220_attributes, +}; + +static const struct iio_info adis16220_info = { + .attrs = &adis16220_attribute_group, + .driver_module = THIS_MODULE, + .read_raw = &adis16220_read_raw, +}; + +static int __devinit adis16220_probe(struct spi_device *spi) +{ + int ret; + struct adis16220_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16220_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adis16220_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16220_channels); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + ret = sysfs_create_bin_file(&indio_dev->dev.kobj, &accel_bin); + if (ret) + goto error_unregister_dev; + + ret = sysfs_create_bin_file(&indio_dev->dev.kobj, &adc1_bin); + if (ret) + goto error_rm_accel_bin; + + ret = sysfs_create_bin_file(&indio_dev->dev.kobj, &adc2_bin); + if (ret) + goto error_rm_adc1_bin; + + /* Get the device into a sane initial state */ + ret = adis16220_initial_setup(indio_dev); + if (ret) + goto error_rm_adc2_bin; + return 0; + +error_rm_adc2_bin: + sysfs_remove_bin_file(&indio_dev->dev.kobj, &adc2_bin); +error_rm_adc1_bin: + sysfs_remove_bin_file(&indio_dev->dev.kobj, &adc1_bin); +error_rm_accel_bin: + sysfs_remove_bin_file(&indio_dev->dev.kobj, &accel_bin); +error_unregister_dev: + iio_device_unregister(indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int adis16220_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + flush_scheduled_work(); + + sysfs_remove_bin_file(&indio_dev->dev.kobj, &adc2_bin); + sysfs_remove_bin_file(&indio_dev->dev.kobj, &adc1_bin); + sysfs_remove_bin_file(&indio_dev->dev.kobj, &accel_bin); + iio_device_unregister(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static struct spi_driver adis16220_driver = { + .driver = { + .name = "adis16220", + .owner = THIS_MODULE, + }, + .probe = adis16220_probe, + .remove = __devexit_p(adis16220_remove), +}; +module_spi_driver(adis16220_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16220 Digital Vibration Sensor"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16220"); diff --git a/drivers/staging/iio/accel/adis16240.h b/drivers/staging/iio/accel/adis16240.h new file mode 100644 index 00000000..3fabcc0b --- /dev/null +++ b/drivers/staging/iio/accel/adis16240.h @@ -0,0 +1,196 @@ +#ifndef SPI_ADIS16240_H_ +#define SPI_ADIS16240_H_ + +#define ADIS16240_STARTUP_DELAY 220 /* ms */ + +#define ADIS16240_READ_REG(a) a +#define ADIS16240_WRITE_REG(a) ((a) | 0x80) + +/* Flash memory write count */ +#define ADIS16240_FLASH_CNT 0x00 +/* Output, power supply */ +#define ADIS16240_SUPPLY_OUT 0x02 +/* Output, x-axis accelerometer */ +#define ADIS16240_XACCL_OUT 0x04 +/* Output, y-axis accelerometer */ +#define ADIS16240_YACCL_OUT 0x06 +/* Output, z-axis accelerometer */ +#define ADIS16240_ZACCL_OUT 0x08 +/* Output, auxiliary ADC input */ +#define ADIS16240_AUX_ADC 0x0A +/* Output, temperature */ +#define ADIS16240_TEMP_OUT 0x0C +/* Output, x-axis acceleration peak */ +#define ADIS16240_XPEAK_OUT 0x0E +/* Output, y-axis acceleration peak */ +#define ADIS16240_YPEAK_OUT 0x10 +/* Output, z-axis acceleration peak */ +#define ADIS16240_ZPEAK_OUT 0x12 +/* Output, sum-of-squares acceleration peak */ +#define ADIS16240_XYZPEAK_OUT 0x14 +/* Output, Capture Buffer 1, X and Y acceleration */ +#define ADIS16240_CAPT_BUF1 0x16 +/* Output, Capture Buffer 2, Z acceleration */ +#define ADIS16240_CAPT_BUF2 0x18 +/* Diagnostic, error flags */ +#define ADIS16240_DIAG_STAT 0x1A +/* Diagnostic, event counter */ +#define ADIS16240_EVNT_CNTR 0x1C +/* Diagnostic, check sum value from firmware test */ +#define ADIS16240_CHK_SUM 0x1E +/* Calibration, x-axis acceleration offset adjustment */ +#define ADIS16240_XACCL_OFF 0x20 +/* Calibration, y-axis acceleration offset adjustment */ +#define ADIS16240_YACCL_OFF 0x22 +/* Calibration, z-axis acceleration offset adjustment */ +#define ADIS16240_ZACCL_OFF 0x24 +/* Clock, hour and minute */ +#define ADIS16240_CLK_TIME 0x2E +/* Clock, month and day */ +#define ADIS16240_CLK_DATE 0x30 +/* Clock, year */ +#define ADIS16240_CLK_YEAR 0x32 +/* Wake-up setting, hour and minute */ +#define ADIS16240_WAKE_TIME 0x34 +/* Wake-up setting, month and day */ +#define ADIS16240_WAKE_DATE 0x36 +/* Alarm 1 amplitude threshold */ +#define ADIS16240_ALM_MAG1 0x38 +/* Alarm 2 amplitude threshold */ +#define ADIS16240_ALM_MAG2 0x3A +/* Alarm control */ +#define ADIS16240_ALM_CTRL 0x3C +/* Capture, external trigger control */ +#define ADIS16240_XTRIG_CTRL 0x3E +/* Capture, address pointer */ +#define ADIS16240_CAPT_PNTR 0x40 +/* Capture, configuration and control */ +#define ADIS16240_CAPT_CTRL 0x42 +/* General-purpose digital input/output control */ +#define ADIS16240_GPIO_CTRL 0x44 +/* Miscellaneous control */ +#define ADIS16240_MSC_CTRL 0x46 +/* Internal sample period (rate) control */ +#define ADIS16240_SMPL_PRD 0x48 +/* System command */ +#define ADIS16240_GLOB_CMD 0x4A + +#define ADIS16240_OUTPUTS 6 + +/* MSC_CTRL */ +/* Enables sum-of-squares output (XYZPEAK_OUT) */ +#define ADIS16240_MSC_CTRL_XYZPEAK_OUT_EN (1 << 15) +/* Enables peak tracking output (XPEAK_OUT, YPEAK_OUT, and ZPEAK_OUT) */ +#define ADIS16240_MSC_CTRL_X_Y_ZPEAK_OUT_EN (1 << 14) +/* Self-test enable: 1 = apply electrostatic force, 0 = disabled */ +#define ADIS16240_MSC_CTRL_SELF_TEST_EN (1 << 8) +/* Data-ready enable: 1 = enabled, 0 = disabled */ +#define ADIS16240_MSC_CTRL_DATA_RDY_EN (1 << 2) +/* Data-ready polarity: 1 = active high, 0 = active low */ +#define ADIS16240_MSC_CTRL_ACTIVE_HIGH (1 << 1) +/* Data-ready line selection: 1 = DIO2, 0 = DIO1 */ +#define ADIS16240_MSC_CTRL_DATA_RDY_DIO2 (1 << 0) + +/* DIAG_STAT */ +/* Alarm 2 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16240_DIAG_STAT_ALARM2 (1<<9) +/* Alarm 1 status: 1 = alarm active, 0 = alarm inactive */ +#define ADIS16240_DIAG_STAT_ALARM1 (1<<8) +/* Capture buffer full: 1 = capture buffer is full */ +#define ADIS16240_DIAG_STAT_CPT_BUF_FUL (1<<7) +/* Flash test, checksum flag: 1 = mismatch, 0 = match */ +#define ADIS16240_DIAG_STAT_CHKSUM (1<<6) +/* Power-on, self-test flag: 1 = failure, 0 = pass */ +#define ADIS16240_DIAG_STAT_PWRON_FAIL (1<<5) +/* Power-on self-test: 1 = in-progress, 0 = complete */ +#define ADIS16240_DIAG_STAT_PWRON_BUSY (1<<4) +/* SPI communications failure */ +#define ADIS16240_DIAG_STAT_SPI_FAIL (1<<3) +/* Flash update failure */ +#define ADIS16240_DIAG_STAT_FLASH_UPT (1<<2) +/* Power supply above 3.625 V */ +#define ADIS16240_DIAG_STAT_POWER_HIGH (1<<1) + /* Power supply below 3.15 V */ +#define ADIS16240_DIAG_STAT_POWER_LOW (1<<0) + +/* GLOB_CMD */ +#define ADIS16240_GLOB_CMD_RESUME (1<<8) +#define ADIS16240_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16240_GLOB_CMD_STANDBY (1<<2) + +#define ADIS16240_ERROR_ACTIVE (1<<14) + +#define ADIS16240_MAX_TX 24 +#define ADIS16240_MAX_RX 24 + +/** + * struct adis16240_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16240_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + u8 tx[ADIS16240_MAX_TX] ____cacheline_aligned; + u8 rx[ADIS16240_MAX_RX]; +}; + +int adis16240_set_irq(struct iio_dev *indio_dev, bool enable); + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +#define ADIS16240_SCAN_SUPPLY 0 +#define ADIS16240_SCAN_ACC_X 1 +#define ADIS16240_SCAN_ACC_Y 2 +#define ADIS16240_SCAN_ACC_Z 3 +#define ADIS16240_SCAN_AUX_ADC 4 +#define ADIS16240_SCAN_TEMP 5 + +#ifdef CONFIG_IIO_BUFFER +void adis16240_remove_trigger(struct iio_dev *indio_dev); +int adis16240_probe_trigger(struct iio_dev *indio_dev); + +ssize_t adis16240_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + + +int adis16240_configure_ring(struct iio_dev *indio_dev); +void adis16240_unconfigure_ring(struct iio_dev *indio_dev); + +#else /* CONFIG_IIO_BUFFER */ + +static inline void adis16240_remove_trigger(struct iio_dev *indio_dev) +{ +} + +static inline int adis16240_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline ssize_t +adis16240_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static int adis16240_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void adis16240_unconfigure_ring(struct iio_dev *indio_dev) +{ +} + +#endif /* CONFIG_IIO_BUFFER */ +#endif /* SPI_ADIS16240_H_ */ diff --git a/drivers/staging/iio/accel/adis16240_core.c b/drivers/staging/iio/accel/adis16240_core.c new file mode 100644 index 00000000..17f77fef --- /dev/null +++ b/drivers/staging/iio/accel/adis16240_core.c @@ -0,0 +1,613 @@ +/* + * ADIS16240 Programmable Impact Sensor and Recorder driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "adis16240.h" + +#define DRIVER_NAME "adis16240" + +static int adis16240_check_status(struct iio_dev *indio_dev); + +/** + * adis16240_spi_write_reg_8() - write single byte to a register + * @indio_dev: iio_dev associated with device + * @reg_address: the address of the register to be written + * @val: the value to write + **/ +static int adis16240_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct adis16240_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16240_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16240_spi_write_reg_16() - write 2 bytes to a pair of registers + * @indio_dev: iio_dev for this device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: value to be written + **/ +static int adis16240_spi_write_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct adis16240_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 35, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + .delay_usecs = 35, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16240_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = ADIS16240_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16240_spi_read_reg_16() - read 2 bytes from a 16-bit register + * @indio_dev: iio_dev for this device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + **/ +static int adis16240_spi_read_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct adis16240_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 35, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 35, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16240_READ_REG(lower_reg_address); + st->tx[1] = 0; + st->tx[2] = 0; + st->tx[3] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, + "problem when reading 16 bit register 0x%02X", + lower_reg_address); + goto error_ret; + } + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t adis16240_spi_read_signed(struct device *dev, + struct device_attribute *attr, + char *buf, + unsigned bits) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int ret; + s16 val = 0; + unsigned shift = 16 - bits; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = adis16240_spi_read_reg_16(indio_dev, + this_attr->address, (u16 *)&val); + if (ret) + return ret; + + if (val & ADIS16240_ERROR_ACTIVE) + adis16240_check_status(indio_dev); + + val = ((s16)(val << shift) >> shift); + return sprintf(buf, "%d\n", val); +} + +static ssize_t adis16240_read_12bit_signed(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + ret = adis16240_spi_read_signed(dev, attr, buf, 12); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int adis16240_reset(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16240_spi_write_reg_8(indio_dev, + ADIS16240_GLOB_CMD, + ADIS16240_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&indio_dev->dev, "problem resetting device"); + + return ret; +} + +static ssize_t adis16240_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + if (len < 1) + return -EINVAL; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return adis16240_reset(indio_dev); + } + return -EINVAL; +} + +int adis16240_set_irq(struct iio_dev *indio_dev, bool enable) +{ + int ret = 0; + u16 msc; + + ret = adis16240_spi_read_reg_16(indio_dev, + ADIS16240_MSC_CTRL, &msc); + if (ret) + goto error_ret; + + msc |= ADIS16240_MSC_CTRL_ACTIVE_HIGH; + msc &= ~ADIS16240_MSC_CTRL_DATA_RDY_DIO2; + if (enable) + msc |= ADIS16240_MSC_CTRL_DATA_RDY_EN; + else + msc &= ~ADIS16240_MSC_CTRL_DATA_RDY_EN; + + ret = adis16240_spi_write_reg_16(indio_dev, + ADIS16240_MSC_CTRL, msc); + +error_ret: + return ret; +} + +static int adis16240_self_test(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16240_spi_write_reg_16(indio_dev, + ADIS16240_MSC_CTRL, + ADIS16240_MSC_CTRL_SELF_TEST_EN); + if (ret) { + dev_err(&indio_dev->dev, "problem starting self test"); + goto err_ret; + } + + msleep(ADIS16240_STARTUP_DELAY); + + adis16240_check_status(indio_dev); + +err_ret: + return ret; +} + +static int adis16240_check_status(struct iio_dev *indio_dev) +{ + u16 status; + int ret; + struct device *dev = &indio_dev->dev; + + ret = adis16240_spi_read_reg_16(indio_dev, + ADIS16240_DIAG_STAT, &status); + + if (ret < 0) { + dev_err(dev, "Reading status failed\n"); + goto error_ret; + } + + ret = status & 0x2F; + if (status & ADIS16240_DIAG_STAT_PWRON_FAIL) + dev_err(dev, "Power-on, self-test fail\n"); + if (status & ADIS16240_DIAG_STAT_SPI_FAIL) + dev_err(dev, "SPI failure\n"); + if (status & ADIS16240_DIAG_STAT_FLASH_UPT) + dev_err(dev, "Flash update failed\n"); + if (status & ADIS16240_DIAG_STAT_POWER_HIGH) + dev_err(dev, "Power supply above 3.625V\n"); + if (status & ADIS16240_DIAG_STAT_POWER_LOW) + dev_err(dev, "Power supply below 2.225V\n"); + +error_ret: + return ret; +} + +static int adis16240_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &indio_dev->dev; + + /* Disable IRQ */ + ret = adis16240_set_irq(indio_dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + /* Do self test */ + ret = adis16240_self_test(indio_dev); + if (ret) { + dev_err(dev, "self test failure"); + goto err_ret; + } + + /* Read status register to check the result */ + ret = adis16240_check_status(indio_dev); + if (ret) { + adis16240_reset(indio_dev); + dev_err(dev, "device not playing ball -> reset"); + msleep(ADIS16240_STARTUP_DELAY); + ret = adis16240_check_status(indio_dev); + if (ret) { + dev_err(dev, "giving up"); + goto err_ret; + } + } + +err_ret: + return ret; +} + +static IIO_DEVICE_ATTR(in_accel_xyz_squared_peak_raw, S_IRUGO, + adis16240_read_12bit_signed, NULL, + ADIS16240_XYZPEAK_OUT); + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, adis16240_write_reset, 0); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("4096"); + +enum adis16240_chan { + in_supply, + in_aux, + accel_x, + accel_y, + accel_z, + temp, +}; + +static const u8 adis16240_addresses[6][3] = { + [in_supply] = { ADIS16240_SUPPLY_OUT }, + [in_aux] = { ADIS16240_AUX_ADC }, + [accel_x] = { ADIS16240_XACCL_OUT, ADIS16240_XACCL_OFF, + ADIS16240_XPEAK_OUT }, + [accel_y] = { ADIS16240_YACCL_OUT, ADIS16240_YACCL_OFF, + ADIS16240_YPEAK_OUT }, + [accel_z] = { ADIS16240_ZACCL_OUT, ADIS16240_ZACCL_OFF, + ADIS16240_ZPEAK_OUT }, + [temp] = { ADIS16240_TEMP_OUT }, +}; + +static int adis16240_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case 0: + mutex_lock(&indio_dev->mlock); + addr = adis16240_addresses[chan->address][0]; + ret = adis16240_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + if (val16 & ADIS16240_ERROR_ACTIVE) { + ret = adis16240_check_status(indio_dev); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } + val16 = val16 & ((1 << chan->scan_type.realbits) - 1); + if (chan->scan_type.sign == 's') + val16 = (s16)(val16 << + (16 - chan->scan_type.realbits)) >> + (16 - chan->scan_type.realbits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) + *val2 = 4880; + else + return -EINVAL; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = 244000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = 504062; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_PEAK_SCALE: + *val = 6; + *val2 = 629295; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = 25; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + bits = 10; + mutex_lock(&indio_dev->mlock); + addr = adis16240_addresses[chan->address][1]; + ret = adis16240_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_PEAK: + bits = 10; + mutex_lock(&indio_dev->mlock); + addr = adis16240_addresses[chan->address][2]; + ret = adis16240_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int adis16240_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int bits = 10; + s16 val16; + u8 addr; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + val16 = val & ((1 << bits) - 1); + addr = adis16240_addresses[chan->address][1]; + return adis16240_spi_write_reg_16(indio_dev, addr, val16); + } + return -EINVAL; +} + +static struct iio_chan_spec adis16240_channels[] = { + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, "supply", 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + in_supply, ADIS16240_SCAN_SUPPLY, + IIO_ST('u', 10, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 1, 0, + 0, + in_aux, ADIS16240_SCAN_AUX_ADC, + IIO_ST('u', 10, 16, 0), 0), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_X, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + accel_x, ADIS16240_SCAN_ACC_X, + IIO_ST('s', 10, 16, 0), 0), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Y, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + accel_y, ADIS16240_SCAN_ACC_Y, + IIO_ST('s', 10, 16, 0), 0), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Z, + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + accel_z, ADIS16240_SCAN_ACC_Z, + IIO_ST('s', 10, 16, 0), 0), + IIO_CHAN(IIO_TEMP, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + temp, ADIS16240_SCAN_TEMP, + IIO_ST('u', 10, 16, 0), 0), + IIO_CHAN_SOFT_TIMESTAMP(6) +}; + +static struct attribute *adis16240_attributes[] = { + &iio_dev_attr_in_accel_xyz_squared_peak_raw.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16240_attribute_group = { + .attrs = adis16240_attributes, +}; + +static const struct iio_info adis16240_info = { + .attrs = &adis16240_attribute_group, + .read_raw = &adis16240_read_raw, + .write_raw = &adis16240_write_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit adis16240_probe(struct spi_device *spi) +{ + int ret; + struct adis16240_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16240_info; + indio_dev->channels = adis16240_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16240_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis16240_configure_ring(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_buffer_register(indio_dev, + adis16240_channels, + ARRAY_SIZE(adis16240_channels)); + if (ret) { + printk(KERN_ERR "failed to initialize the ring\n"); + goto error_unreg_ring_funcs; + } + + if (spi->irq) { + ret = adis16240_probe_trigger(indio_dev); + if (ret) + goto error_uninitialize_ring; + } + + /* Get the device into a sane initial state */ + ret = adis16240_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + return 0; + +error_remove_trigger: + adis16240_remove_trigger(indio_dev); +error_uninitialize_ring: + iio_buffer_unregister(indio_dev); +error_unreg_ring_funcs: + adis16240_unconfigure_ring(indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int adis16240_remove(struct spi_device *spi) +{ + + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + flush_scheduled_work(); + + iio_device_unregister(indio_dev); + adis16240_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + adis16240_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static struct spi_driver adis16240_driver = { + .driver = { + .name = "adis16240", + .owner = THIS_MODULE, + }, + .probe = adis16240_probe, + .remove = __devexit_p(adis16240_remove), +}; +module_spi_driver(adis16240_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices Programmable Impact Sensor and Recorder"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16240"); diff --git a/drivers/staging/iio/accel/adis16240_ring.c b/drivers/staging/iio/accel/adis16240_ring.c new file mode 100644 index 00000000..43ba84e9 --- /dev/null +++ b/drivers/staging/iio/accel/adis16240_ring.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" +#include "adis16240.h" + +/** + * adis16240_read_ring_data() read data registers which will be placed into ring + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @rx: somewhere to pass back the value read + **/ +static int adis16240_read_ring_data(struct device *dev, u8 *rx) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16240_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[ADIS16240_OUTPUTS + 1]; + int ret; + int i; + + mutex_lock(&st->buf_lock); + + spi_message_init(&msg); + + memset(xfers, 0, sizeof(xfers)); + for (i = 0; i <= ADIS16240_OUTPUTS; i++) { + xfers[i].bits_per_word = 8; + xfers[i].cs_change = 1; + xfers[i].len = 2; + xfers[i].delay_usecs = 30; + xfers[i].tx_buf = st->tx + 2 * i; + st->tx[2 * i] + = ADIS16240_READ_REG(ADIS16240_SUPPLY_OUT + 2 * i); + st->tx[2 * i + 1] = 0; + if (i >= 1) + xfers[i].rx_buf = rx + 2 * (i - 1); + spi_message_add_tail(&xfers[i], &msg); + } + + ret = spi_sync(st->us, &msg); + if (ret) + dev_err(&st->us->dev, "problem when burst reading"); + + mutex_unlock(&st->buf_lock); + + return ret; +} + +static irqreturn_t adis16240_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16240_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + + int i = 0; + s16 *data; + size_t datasize = ring->access->get_bytes_per_datum(ring); + + data = kmalloc(datasize, GFP_KERNEL); + if (data == NULL) { + dev_err(&st->us->dev, "memory alloc failed in ring bh"); + return -ENOMEM; + } + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength) && + adis16240_read_ring_data(&indio_dev->dev, st->rx) >= 0) + for (; i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); i++) + data[i] = be16_to_cpup((__be16 *)&(st->rx[i*2])); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + *((s64 *)(data + ((i + 3)/4)*4)) = pf->timestamp; + + ring->access->store_to(ring, (u8 *)data, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + kfree(data); + + return IRQ_HANDLED; +} + +void adis16240_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +static const struct iio_buffer_setup_ops adis16240_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int adis16240_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct iio_buffer *ring; + + ring = iio_sw_rb_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->buffer = ring; + ring->scan_timestamp = true; + indio_dev->setup_ops = &adis16240_ring_setup_ops; + + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &adis16240_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + indio_dev->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_iio_sw_rb_free: + iio_sw_rb_free(indio_dev->buffer); + return ret; +} diff --git a/drivers/staging/iio/accel/adis16240_trigger.c b/drivers/staging/iio/accel/adis16240_trigger.c new file mode 100644 index 00000000..8e0ce568 --- /dev/null +++ b/drivers/staging/iio/accel/adis16240_trigger.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" +#include "adis16240.h" + +/** + * adis16240_data_rdy_trig_poll() the event handler for the data rdy trig + **/ +static irqreturn_t adis16240_data_rdy_trig_poll(int irq, void *trig) +{ + iio_trigger_poll(trig, iio_get_time_ns()); + return IRQ_HANDLED; +} + +/** + * adis16240_data_rdy_trigger_set_state() set datardy interrupt state + **/ +static int adis16240_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + + dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state); + return adis16240_set_irq(indio_dev, state); +} + +static const struct iio_trigger_ops adis16240_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &adis16240_data_rdy_trigger_set_state, +}; + +int adis16240_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct adis16240_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("adis16240-dev%d", indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + ret = request_irq(st->us->irq, + adis16240_data_rdy_trig_poll, + IRQF_TRIGGER_RISING, + "adis16240", + st->trig); + if (ret) + goto error_free_trig; + + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &adis16240_trigger_ops; + st->trig->private_data = indio_dev; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->us->irq, st->trig); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void adis16240_remove_trigger(struct iio_dev *indio_dev) +{ + struct adis16240_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->us->irq, st->trig); + iio_free_trigger(st->trig); +} diff --git a/drivers/staging/iio/accel/kxsd9.c b/drivers/staging/iio/accel/kxsd9.c new file mode 100644 index 00000000..d13d7215 --- /dev/null +++ b/drivers/staging/iio/accel/kxsd9.c @@ -0,0 +1,289 @@ +/* + * kxsd9.c simple support for the Kionix KXSD9 3D + * accelerometer. + * + * Copyright (c) 2008-2009 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The i2c interface is very similar, so shouldn't be a problem once + * I have a suitable wire made up. + * + * TODO: Support the motion detector + * Uses register address incrementing so could have a + * heavily optimized ring buffer access function. + */ + +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define KXSD9_REG_X 0x00 +#define KXSD9_REG_Y 0x02 +#define KXSD9_REG_Z 0x04 +#define KXSD9_REG_AUX 0x06 +#define KXSD9_REG_RESET 0x0a +#define KXSD9_REG_CTRL_C 0x0c + +#define KXSD9_FS_MASK 0x03 + +#define KXSD9_REG_CTRL_B 0x0d +#define KXSD9_REG_CTRL_A 0x0e + +#define KXSD9_READ(a) (0x80 | (a)) +#define KXSD9_WRITE(a) (a) + +#define KXSD9_STATE_RX_SIZE 2 +#define KXSD9_STATE_TX_SIZE 2 +/** + * struct kxsd9_state - device related storage + * @buf_lock: protect the rx and tx buffers. + * @us: spi device + * @rx: single rx buffer storage + * @tx: single tx buffer storage + **/ +struct kxsd9_state { + struct mutex buf_lock; + struct spi_device *us; + u8 rx[KXSD9_STATE_RX_SIZE] ____cacheline_aligned; + u8 tx[KXSD9_STATE_TX_SIZE]; +}; + +#define KXSD9_SCALE_2G "0.011978" +#define KXSD9_SCALE_4G "0.023927" +#define KXSD9_SCALE_6G "0.035934" +#define KXSD9_SCALE_8G "0.047853" + +/* reverse order */ +static const int kxsd9_micro_scales[4] = { 47853, 35934, 23927, 11978 }; + +static int kxsd9_write_scale(struct iio_dev *indio_dev, int micro) +{ + int ret, i; + struct kxsd9_state *st = iio_priv(indio_dev); + bool foundit = false; + + for (i = 0; i < 4; i++) + if (micro == kxsd9_micro_scales[i]) { + foundit = true; + break; + } + if (!foundit) + return -EINVAL; + + mutex_lock(&st->buf_lock); + ret = spi_w8r8(st->us, KXSD9_READ(KXSD9_REG_CTRL_C)); + if (ret) + goto error_ret; + st->tx[0] = KXSD9_WRITE(KXSD9_REG_CTRL_C); + st->tx[1] = (ret & ~KXSD9_FS_MASK) | i; + + ret = spi_write(st->us, st->tx, 2); +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int kxsd9_read(struct iio_dev *indio_dev, u8 address) +{ + struct spi_message msg; + int ret; + struct kxsd9_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .bits_per_word = 8, + .len = 1, + .delay_usecs = 200, + .tx_buf = st->tx, + }, { + .bits_per_word = 8, + .len = 2, + .rx_buf = st->rx, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = KXSD9_READ(address); + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) + return ret; + return (((u16)(st->rx[0])) << 8) | (st->rx[1] & 0xF0); +} + +static IIO_CONST_ATTR(accel_scale_available, + KXSD9_SCALE_2G " " + KXSD9_SCALE_4G " " + KXSD9_SCALE_6G " " + KXSD9_SCALE_8G); + +static struct attribute *kxsd9_attributes[] = { + &iio_const_attr_accel_scale_available.dev_attr.attr, + NULL, +}; + +static int kxsd9_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret = -EINVAL; + + if (mask == IIO_CHAN_INFO_SCALE) { + /* Check no integer component */ + if (val) + return -EINVAL; + ret = kxsd9_write_scale(indio_dev, val2); + } + + return ret; +} + +static int kxsd9_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret = -EINVAL; + struct kxsd9_state *st = iio_priv(indio_dev); + + switch (mask) { + case 0: + ret = kxsd9_read(indio_dev, chan->address); + if (ret < 0) + goto error_ret; + *val = ret; + break; + case IIO_CHAN_INFO_SCALE: + ret = spi_w8r8(st->us, KXSD9_READ(KXSD9_REG_CTRL_C)); + if (ret) + goto error_ret; + *val2 = kxsd9_micro_scales[ret & KXSD9_FS_MASK]; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + }; + +error_ret: + return ret; +}; +#define KXSD9_ACCEL_CHAN(axis) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .address = KXSD9_REG_##axis, \ + } + +static struct iio_chan_spec kxsd9_channels[] = { + KXSD9_ACCEL_CHAN(X), KXSD9_ACCEL_CHAN(Y), KXSD9_ACCEL_CHAN(Z), + { + .type = IIO_VOLTAGE, + .indexed = 1, + .address = KXSD9_REG_AUX, + } +}; + +static const struct attribute_group kxsd9_attribute_group = { + .attrs = kxsd9_attributes, +}; + +static int __devinit kxsd9_power_up(struct kxsd9_state *st) +{ + int ret; + + st->tx[0] = 0x0d; + st->tx[1] = 0x40; + ret = spi_write(st->us, st->tx, 2); + if (ret) + return ret; + + st->tx[0] = 0x0c; + st->tx[1] = 0x9b; + return spi_write(st->us, st->tx, 2); +}; + +static const struct iio_info kxsd9_info = { + .read_raw = &kxsd9_read_raw, + .write_raw = &kxsd9_write_raw, + .attrs = &kxsd9_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit kxsd9_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct kxsd9_state *st; + int ret = 0; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->us = spi; + mutex_init(&st->buf_lock); + indio_dev->channels = kxsd9_channels; + indio_dev->num_channels = ARRAY_SIZE(kxsd9_channels); + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &kxsd9_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + spi->mode = SPI_MODE_0; + spi_setup(spi); + kxsd9_power_up(st); + + return 0; + +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit kxsd9_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id kxsd9_id[] = { + {"kxsd9", 0}, + { }, +}; +MODULE_DEVICE_TABLE(spi, kxsd9_id); + +static struct spi_driver kxsd9_driver = { + .driver = { + .name = "kxsd9", + .owner = THIS_MODULE, + }, + .probe = kxsd9_probe, + .remove = __devexit_p(kxsd9_remove), + .id_table = kxsd9_id, +}; +module_spi_driver(kxsd9_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Kionix KXSD9 SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/accel/lis3l02dq.h b/drivers/staging/iio/accel/lis3l02dq.h new file mode 100644 index 00000000..ae5f225b --- /dev/null +++ b/drivers/staging/iio/accel/lis3l02dq.h @@ -0,0 +1,217 @@ +/* + * LISL02DQ.h -- support STMicroelectronics LISD02DQ + * 3d 2g Linear Accelerometers via SPI + * + * Copyright (c) 2007 Jonathan Cameron + * + * Loosely based upon tle62x0.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef SPI_LIS3L02DQ_H_ +#define SPI_LIS3L02DQ_H_ +#define LIS3L02DQ_READ_REG(a) ((a) | 0x80) +#define LIS3L02DQ_WRITE_REG(a) a + +/* Calibration parameters */ +#define LIS3L02DQ_REG_OFFSET_X_ADDR 0x16 +#define LIS3L02DQ_REG_OFFSET_Y_ADDR 0x17 +#define LIS3L02DQ_REG_OFFSET_Z_ADDR 0x18 + +#define LIS3L02DQ_REG_GAIN_X_ADDR 0x19 +#define LIS3L02DQ_REG_GAIN_Y_ADDR 0x1A +#define LIS3L02DQ_REG_GAIN_Z_ADDR 0x1B + +/* Control Register (1 of 2) */ +#define LIS3L02DQ_REG_CTRL_1_ADDR 0x20 +/* Power ctrl - either bit set corresponds to on*/ +#define LIS3L02DQ_REG_CTRL_1_PD_ON 0xC0 + +/* Decimation Factor */ +#define LIS3L02DQ_DEC_MASK 0x30 +#define LIS3L02DQ_REG_CTRL_1_DF_128 0x00 +#define LIS3L02DQ_REG_CTRL_1_DF_64 0x10 +#define LIS3L02DQ_REG_CTRL_1_DF_32 0x20 +#define LIS3L02DQ_REG_CTRL_1_DF_8 (0x10 | 0x20) + +/* Self Test Enable */ +#define LIS3L02DQ_REG_CTRL_1_SELF_TEST_ON 0x08 + +/* Axes enable ctrls */ +#define LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE 0x04 +#define LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE 0x02 +#define LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE 0x01 + +/* Control Register (2 of 2) */ +#define LIS3L02DQ_REG_CTRL_2_ADDR 0x21 + +/* Block Data Update only after MSB and LSB read */ +#define LIS3L02DQ_REG_CTRL_2_BLOCK_UPDATE 0x40 + +/* Set to big endian output */ +#define LIS3L02DQ_REG_CTRL_2_BIG_ENDIAN 0x20 + +/* Reboot memory content */ +#define LIS3L02DQ_REG_CTRL_2_REBOOT_MEMORY 0x10 + +/* Interrupt Enable - applies data ready to the RDY pad */ +#define LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT 0x08 + +/* Enable Data Ready Generation - relationship with previous unclear in docs */ +#define LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION 0x04 + +/* SPI 3 wire mode */ +#define LIS3L02DQ_REG_CTRL_2_THREE_WIRE_SPI_MODE 0x02 + +/* Data alignment, default is 12 bit right justified + * - option for 16 bit left justified */ +#define LIS3L02DQ_REG_CTRL_2_DATA_ALIGNMENT_16_BIT_LEFT_JUSTIFIED 0x01 + +/* Interrupt related stuff */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_ADDR 0x23 + +/* Switch from or combination fo conditions to and */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_BOOLEAN_AND 0x80 + +/* Latch interrupt request, + * if on ack must be given by reading the ack register */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC 0x40 + +/* Z Interrupt on High (above threshold)*/ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_HIGH 0x20 +/* Z Interrupt on Low */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Z_LOW 0x10 +/* Y Interrupt on High */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_HIGH 0x08 +/* Y Interrupt on Low */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_Y_LOW 0x04 +/* X Interrupt on High */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_HIGH 0x02 +/* X Interrupt on Low */ +#define LIS3L02DQ_REG_WAKE_UP_CFG_INTERRUPT_X_LOW 0x01 + +/* Register that gives description of what caused interrupt + * - latched if set in CFG_ADDRES */ +#define LIS3L02DQ_REG_WAKE_UP_SRC_ADDR 0x24 +/* top bit ignored */ +/* Interrupt Active */ +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_ACTIVATED 0x40 +/* Interupts that have been triggered */ +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH 0x20 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW 0x10 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH 0x08 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW 0x04 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH 0x02 +#define LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW 0x01 + +#define LIS3L02DQ_REG_WAKE_UP_ACK_ADDR 0x25 + +/* Status register */ +#define LIS3L02DQ_REG_STATUS_ADDR 0x27 +/* XYZ axis data overrun - first is all overrun? */ +#define LIS3L02DQ_REG_STATUS_XYZ_OVERRUN 0x80 +#define LIS3L02DQ_REG_STATUS_Z_OVERRUN 0x40 +#define LIS3L02DQ_REG_STATUS_Y_OVERRUN 0x20 +#define LIS3L02DQ_REG_STATUS_X_OVERRUN 0x10 +/* XYZ new data available - first is all 3 available? */ +#define LIS3L02DQ_REG_STATUS_XYZ_NEW_DATA 0x08 +#define LIS3L02DQ_REG_STATUS_Z_NEW_DATA 0x04 +#define LIS3L02DQ_REG_STATUS_Y_NEW_DATA 0x02 +#define LIS3L02DQ_REG_STATUS_X_NEW_DATA 0x01 + +/* The accelerometer readings - low and high bytes. +Form of high byte dependent on justification set in ctrl reg */ +#define LIS3L02DQ_REG_OUT_X_L_ADDR 0x28 +#define LIS3L02DQ_REG_OUT_X_H_ADDR 0x29 +#define LIS3L02DQ_REG_OUT_Y_L_ADDR 0x2A +#define LIS3L02DQ_REG_OUT_Y_H_ADDR 0x2B +#define LIS3L02DQ_REG_OUT_Z_L_ADDR 0x2C +#define LIS3L02DQ_REG_OUT_Z_H_ADDR 0x2D + +/* Threshold values for all axes and both above and below thresholds + * - i.e. there is only one value */ +#define LIS3L02DQ_REG_THS_L_ADDR 0x2E +#define LIS3L02DQ_REG_THS_H_ADDR 0x2F + +#define LIS3L02DQ_DEFAULT_CTRL1 (LIS3L02DQ_REG_CTRL_1_PD_ON \ + | LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE \ + | LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE \ + | LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE \ + | LIS3L02DQ_REG_CTRL_1_DF_128) + +#define LIS3L02DQ_DEFAULT_CTRL2 0 + +#define LIS3L02DQ_MAX_TX 12 +#define LIS3L02DQ_MAX_RX 12 +/** + * struct lis3l02dq_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct lis3l02dq_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + bool trigger_on; + + u8 tx[LIS3L02DQ_MAX_RX] ____cacheline_aligned; + u8 rx[LIS3L02DQ_MAX_RX] ____cacheline_aligned; +}; + +int lis3l02dq_spi_read_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 *val); + +int lis3l02dq_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val); + +int lis3l02dq_disable_all_events(struct iio_dev *indio_dev); + +#ifdef CONFIG_IIO_BUFFER +/* At the moment triggers are only used for buffer + * filling. This may change! + */ +void lis3l02dq_remove_trigger(struct iio_dev *indio_dev); +int lis3l02dq_probe_trigger(struct iio_dev *indio_dev); + +int lis3l02dq_configure_buffer(struct iio_dev *indio_dev); +void lis3l02dq_unconfigure_buffer(struct iio_dev *indio_dev); + +#ifdef CONFIG_LIS3L02DQ_BUF_RING_SW +#define lis3l02dq_free_buf iio_sw_rb_free +#define lis3l02dq_alloc_buf iio_sw_rb_allocate +#endif +#ifdef CONFIG_LIS3L02DQ_BUF_KFIFO +#define lis3l02dq_free_buf iio_kfifo_free +#define lis3l02dq_alloc_buf iio_kfifo_allocate +#endif +irqreturn_t lis3l02dq_data_rdy_trig_poll(int irq, void *private); +#define lis3l02dq_th lis3l02dq_data_rdy_trig_poll + +#else /* CONFIG_IIO_BUFFER */ +#define lis3l02dq_th lis3l02dq_nobuffer + +static inline void lis3l02dq_remove_trigger(struct iio_dev *indio_dev) +{ +} +static inline int lis3l02dq_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static int lis3l02dq_configure_buffer(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void lis3l02dq_unconfigure_buffer(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_IIO_BUFFER */ +#endif /* SPI_LIS3L02DQ_H_ */ diff --git a/drivers/staging/iio/accel/lis3l02dq_core.c b/drivers/staging/iio/accel/lis3l02dq_core.c new file mode 100644 index 00000000..376da513 --- /dev/null +++ b/drivers/staging/iio/accel/lis3l02dq_core.c @@ -0,0 +1,810 @@ +/* + * lis3l02dq.c support STMicroelectronics LISD02DQ + * 3d 2g Linear Accelerometers via SPI + * + * Copyright (c) 2007 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Settings: + * 16 bit left justified mode used. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" +#include "../buffer.h" + +#include "lis3l02dq.h" + +/* At the moment the spi framework doesn't allow global setting of cs_change. + * It's in the likely to be added comment at the top of spi.h. + * This means that use cannot be made of spi_write etc. + */ +/* direct copy of the irq_default_primary_handler */ +#ifndef CONFIG_IIO_BUFFER +static irqreturn_t lis3l02dq_nobuffer(int irq, void *private) +{ + return IRQ_WAKE_THREAD; +} +#endif + +/** + * lis3l02dq_spi_read_reg_8() - read single byte from a single register + * @indio_dev: iio_dev for this actual device + * @reg_address: the address of the register to be read + * @val: pass back the resulting value + **/ +int lis3l02dq_spi_read_reg_8(struct iio_dev *indio_dev, + u8 reg_address, u8 *val) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + struct spi_message msg; + int ret; + struct spi_transfer xfer = { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_READ_REG(reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->us, &msg); + *val = st->rx[1]; + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * lis3l02dq_spi_write_reg_8() - write single byte to a register + * @indio_dev: iio_dev for this device + * @reg_address: the address of the register to be written + * @val: the value to write + **/ +int lis3l02dq_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct lis3l02dq_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_WRITE_REG(reg_address); + st->tx[1] = val; + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * lisl302dq_spi_write_reg_s16() - write 2 bytes to a pair of registers + * @indio_dev: iio_dev for this device + * @lower_reg_address: the address of the lower of the two registers. + * Second register is assumed to have address one greater. + * @value: value to be written + **/ +static int lis3l02dq_spi_write_reg_s16(struct iio_dev *indio_dev, + u8 lower_reg_address, + s16 value) +{ + int ret; + struct spi_message msg; + struct lis3l02dq_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = LIS3L02DQ_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int lis3l02dq_read_reg_s16(struct iio_dev *indio_dev, + u8 lower_reg_address, + int *val) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + + struct spi_message msg; + int ret; + s16 tempval; + struct spi_transfer xfers[] = { { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .rx_buf = st->rx + 2, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = LIS3L02DQ_READ_REG(lower_reg_address); + st->tx[1] = 0; + st->tx[2] = LIS3L02DQ_READ_REG(lower_reg_address + 1); + st->tx[3] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 16 bit register"); + goto error_ret; + } + tempval = (s16)(st->rx[1]) | ((s16)(st->rx[3]) << 8); + + *val = tempval; +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +enum lis3l02dq_rm_ind { + LIS3L02DQ_ACCEL, + LIS3L02DQ_GAIN, + LIS3L02DQ_BIAS, +}; + +static u8 lis3l02dq_axis_map[3][3] = { + [LIS3L02DQ_ACCEL] = { LIS3L02DQ_REG_OUT_X_L_ADDR, + LIS3L02DQ_REG_OUT_Y_L_ADDR, + LIS3L02DQ_REG_OUT_Z_L_ADDR }, + [LIS3L02DQ_GAIN] = { LIS3L02DQ_REG_GAIN_X_ADDR, + LIS3L02DQ_REG_GAIN_Y_ADDR, + LIS3L02DQ_REG_GAIN_Z_ADDR }, + [LIS3L02DQ_BIAS] = { LIS3L02DQ_REG_OFFSET_X_ADDR, + LIS3L02DQ_REG_OFFSET_Y_ADDR, + LIS3L02DQ_REG_OFFSET_Z_ADDR } +}; + +static int lis3l02dq_read_thresh(struct iio_dev *indio_dev, + u64 e, + int *val) +{ + return lis3l02dq_read_reg_s16(indio_dev, LIS3L02DQ_REG_THS_L_ADDR, val); +} + +static int lis3l02dq_write_thresh(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + u16 value = val; + return lis3l02dq_spi_write_reg_s16(indio_dev, + LIS3L02DQ_REG_THS_L_ADDR, + value); +} + +static int lis3l02dq_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret = -EINVAL, reg; + u8 uval; + s8 sval; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + if (val > 255 || val < -256) + return -EINVAL; + sval = val; + reg = lis3l02dq_axis_map[LIS3L02DQ_BIAS][chan->address]; + ret = lis3l02dq_spi_write_reg_8(indio_dev, reg, sval); + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (val & ~0xFF) + return -EINVAL; + uval = val; + reg = lis3l02dq_axis_map[LIS3L02DQ_GAIN][chan->address]; + ret = lis3l02dq_spi_write_reg_8(indio_dev, reg, uval); + break; + } + return ret; +} + +static int lis3l02dq_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + u8 utemp; + s8 stemp; + ssize_t ret = 0; + u8 reg; + + switch (mask) { + case 0: + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + ret = -EBUSY; + } else { + reg = lis3l02dq_axis_map + [LIS3L02DQ_ACCEL][chan->address]; + ret = lis3l02dq_read_reg_s16(indio_dev, reg, val); + } + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 9580; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBSCALE: + reg = lis3l02dq_axis_map[LIS3L02DQ_GAIN][chan->address]; + ret = lis3l02dq_spi_read_reg_8(indio_dev, reg, &utemp); + if (ret) + goto error_ret; + /* to match with what previous code does */ + *val = utemp; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_CALIBBIAS: + reg = lis3l02dq_axis_map[LIS3L02DQ_BIAS][chan->address]; + ret = lis3l02dq_spi_read_reg_8(indio_dev, reg, (u8 *)&stemp); + /* to match with what previous code does */ + *val = stemp; + return IIO_VAL_INT; + } +error_ret: + return ret; +} + +static ssize_t lis3l02dq_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int ret, len = 0; + s8 t; + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + (u8 *)&t); + if (ret) + return ret; + t &= LIS3L02DQ_DEC_MASK; + switch (t) { + case LIS3L02DQ_REG_CTRL_1_DF_128: + len = sprintf(buf, "280\n"); + break; + case LIS3L02DQ_REG_CTRL_1_DF_64: + len = sprintf(buf, "560\n"); + break; + case LIS3L02DQ_REG_CTRL_1_DF_32: + len = sprintf(buf, "1120\n"); + break; + case LIS3L02DQ_REG_CTRL_1_DF_8: + len = sprintf(buf, "4480\n"); + break; + } + return len; +} + +static ssize_t lis3l02dq_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + unsigned long val; + int ret; + u8 t; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + if (ret) + goto error_ret_mutex; + /* Wipe the bits clean */ + t &= ~LIS3L02DQ_DEC_MASK; + switch (val) { + case 280: + t |= LIS3L02DQ_REG_CTRL_1_DF_128; + break; + case 560: + t |= LIS3L02DQ_REG_CTRL_1_DF_64; + break; + case 1120: + t |= LIS3L02DQ_REG_CTRL_1_DF_32; + break; + case 4480: + t |= LIS3L02DQ_REG_CTRL_1_DF_8; + break; + default: + ret = -EINVAL; + goto error_ret_mutex; + } + + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + t); + +error_ret_mutex: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int lis3l02dq_initial_setup(struct iio_dev *indio_dev) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + int ret; + u8 val, valtest; + + st->us->mode = SPI_MODE_3; + + spi_setup(st->us); + + val = LIS3L02DQ_DEFAULT_CTRL1; + /* Write suitable defaults to ctrl1 */ + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + val); + if (ret) { + dev_err(&st->us->dev, "problem with setup control register 1"); + goto err_ret; + } + /* Repeat as sometimes doesn't work first time?*/ + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + val); + if (ret) { + dev_err(&st->us->dev, "problem with setup control register 1"); + goto err_ret; + } + + /* Read back to check this has worked acts as loose test of correct + * chip */ + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &valtest); + if (ret || (valtest != val)) { + dev_err(&indio_dev->dev, + "device not playing ball %d %d\n", valtest, val); + ret = -EINVAL; + goto err_ret; + } + + val = LIS3L02DQ_DEFAULT_CTRL2; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + val); + if (ret) { + dev_err(&st->us->dev, "problem with setup control register 2"); + goto err_ret; + } + + val = LIS3L02DQ_REG_WAKE_UP_CFG_LATCH_SRC; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + val); + if (ret) + dev_err(&st->us->dev, "problem with interrupt cfg register"); +err_ret: + + return ret; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + lis3l02dq_read_frequency, + lis3l02dq_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("280 560 1120 4480"); + +static irqreturn_t lis3l02dq_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + u8 t; + + s64 timestamp = iio_get_time_ns(); + + lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_SRC_ADDR, + &t); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_HIGH) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Z_LOW) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_HIGH) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_Y_LOW) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_HIGH) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + if (t & LIS3L02DQ_REG_WAKE_UP_SRC_INTERRUPT_X_LOW) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + /* Ack and allow for new interrupts */ + lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_ACK_ADDR, + &t); + + return IRQ_HANDLED; +} + +#define LIS3L02DQ_INFO_MASK \ + (IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT) + +#define LIS3L02DQ_EVENT_MASK \ + (IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) | \ + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING)) + +static struct iio_chan_spec lis3l02dq_channels[] = { + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_X, LIS3L02DQ_INFO_MASK, + 0, 0, IIO_ST('s', 12, 16, 0), LIS3L02DQ_EVENT_MASK), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Y, LIS3L02DQ_INFO_MASK, + 1, 1, IIO_ST('s', 12, 16, 0), LIS3L02DQ_EVENT_MASK), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Z, LIS3L02DQ_INFO_MASK, + 2, 2, IIO_ST('s', 12, 16, 0), LIS3L02DQ_EVENT_MASK), + IIO_CHAN_SOFT_TIMESTAMP(3) +}; + + +static int lis3l02dq_read_event_config(struct iio_dev *indio_dev, + u64 event_code) +{ + + u8 val; + int ret; + u8 mask = (1 << (IIO_EVENT_CODE_EXTRACT_MODIFIER(event_code)*2 + + (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING))); + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + &val); + if (ret < 0) + return ret; + + return !!(val & mask); +} + +int lis3l02dq_disable_all_events(struct iio_dev *indio_dev) +{ + int ret; + u8 control, val; + + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &control); + + control &= ~LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + control); + if (ret) + goto error_ret; + /* Also for consistency clear the mask */ + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + &val); + if (ret) + goto error_ret; + val &= ~0x3f; + + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + val); + if (ret) + goto error_ret; + + ret = control; +error_ret: + return ret; +} + +static int lis3l02dq_write_event_config(struct iio_dev *indio_dev, + u64 event_code, + int state) +{ + int ret = 0; + u8 val, control; + u8 currentlyset; + bool changed = false; + u8 mask = (1 << (IIO_EVENT_CODE_EXTRACT_MODIFIER(event_code)*2 + + (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING))); + + mutex_lock(&indio_dev->mlock); + /* read current control */ + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &control); + if (ret) + goto error_ret; + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + &val); + if (ret < 0) + goto error_ret; + currentlyset = val & mask; + + if (!currentlyset && state) { + changed = true; + val |= mask; + } else if (currentlyset && !state) { + changed = true; + val &= ~mask; + } + + if (changed) { + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_CFG_ADDR, + val); + if (ret) + goto error_ret; + control = val & 0x3f ? + (control | LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT) : + (control & ~LIS3L02DQ_REG_CTRL_2_ENABLE_INTERRUPT); + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + control); + if (ret) + goto error_ret; + } + +error_ret: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static struct attribute *lis3l02dq_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group lis3l02dq_attribute_group = { + .attrs = lis3l02dq_attributes, +}; + +static const struct iio_info lis3l02dq_info = { + .read_raw = &lis3l02dq_read_raw, + .write_raw = &lis3l02dq_write_raw, + .read_event_value = &lis3l02dq_read_thresh, + .write_event_value = &lis3l02dq_write_thresh, + .write_event_config = &lis3l02dq_write_event_config, + .read_event_config = &lis3l02dq_read_event_config, + .driver_module = THIS_MODULE, + .attrs = &lis3l02dq_attribute_group, +}; + +static int __devinit lis3l02dq_probe(struct spi_device *spi) +{ + int ret; + struct lis3l02dq_state *st; + struct iio_dev *indio_dev; + + indio_dev = iio_allocate_device(sizeof *st); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used tor removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st->us = spi; + mutex_init(&st->buf_lock); + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &lis3l02dq_info; + indio_dev->channels = lis3l02dq_channels; + indio_dev->num_channels = ARRAY_SIZE(lis3l02dq_channels); + + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = lis3l02dq_configure_buffer(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_buffer_register(indio_dev, + lis3l02dq_channels, + ARRAY_SIZE(lis3l02dq_channels)); + if (ret) { + printk(KERN_ERR "failed to initialize the buffer\n"); + goto error_unreg_buffer_funcs; + } + + if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) { + ret = request_threaded_irq(st->us->irq, + &lis3l02dq_th, + &lis3l02dq_event_handler, + IRQF_TRIGGER_RISING, + "lis3l02dq", + indio_dev); + if (ret) + goto error_uninitialize_buffer; + + ret = lis3l02dq_probe_trigger(indio_dev); + if (ret) + goto error_free_interrupt; + } + + /* Get the device into a sane initial state */ + ret = lis3l02dq_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + lis3l02dq_remove_trigger(indio_dev); +error_free_interrupt: + if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) + free_irq(st->us->irq, indio_dev); +error_uninitialize_buffer: + iio_buffer_unregister(indio_dev); +error_unreg_buffer_funcs: + lis3l02dq_unconfigure_buffer(indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +/* Power down the device */ +static int lis3l02dq_stop_device(struct iio_dev *indio_dev) +{ + int ret; + struct lis3l02dq_state *st = iio_priv(indio_dev); + u8 val = 0; + + mutex_lock(&indio_dev->mlock); + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + val); + if (ret) { + dev_err(&st->us->dev, "problem with turning device off: ctrl1"); + goto err_ret; + } + + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + val); + if (ret) + dev_err(&st->us->dev, "problem with turning device off: ctrl2"); +err_ret: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +/* fixme, confirm ordering in this function */ +static int lis3l02dq_remove(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct lis3l02dq_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + ret = lis3l02dq_disable_all_events(indio_dev); + if (ret) + goto err_ret; + + ret = lis3l02dq_stop_device(indio_dev); + if (ret) + goto err_ret; + + if (spi->irq && gpio_is_valid(irq_to_gpio(spi->irq)) > 0) + free_irq(st->us->irq, indio_dev); + + lis3l02dq_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + lis3l02dq_unconfigure_buffer(indio_dev); + + iio_free_device(indio_dev); +err_ret: + return ret; +} + +static struct spi_driver lis3l02dq_driver = { + .driver = { + .name = "lis3l02dq", + .owner = THIS_MODULE, + }, + .probe = lis3l02dq_probe, + .remove = __devexit_p(lis3l02dq_remove), +}; +module_spi_driver(lis3l02dq_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("ST LIS3L02DQ Accelerometer SPI driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:lis3l02dq"); diff --git a/drivers/staging/iio/accel/lis3l02dq_ring.c b/drivers/staging/iio/accel/lis3l02dq_ring.c new file mode 100644 index 00000000..0fc3973f --- /dev/null +++ b/drivers/staging/iio/accel/lis3l02dq_ring.c @@ -0,0 +1,432 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../ring_sw.h" +#include "../kfifo_buf.h" +#include "../trigger.h" +#include "../trigger_consumer.h" +#include "lis3l02dq.h" + +/** + * combine_8_to_16() utility function to munge to u8s into u16 + **/ +static inline u16 combine_8_to_16(u8 lower, u8 upper) +{ + u16 _lower = lower; + u16 _upper = upper; + return _lower | (_upper << 8); +} + +/** + * lis3l02dq_data_rdy_trig_poll() the event handler for the data rdy trig + **/ +irqreturn_t lis3l02dq_data_rdy_trig_poll(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct lis3l02dq_state *st = iio_priv(indio_dev); + + if (st->trigger_on) { + iio_trigger_poll(st->trig, iio_get_time_ns()); + return IRQ_HANDLED; + } else + return IRQ_WAKE_THREAD; +} + +static const u8 read_all_tx_array[] = { + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_L_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_X_H_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_L_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Y_H_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_L_ADDR), 0, + LIS3L02DQ_READ_REG(LIS3L02DQ_REG_OUT_Z_H_ADDR), 0, +}; + +/** + * lis3l02dq_read_all() Reads all channels currently selected + * @st: device specific state + * @rx_array: (dma capable) receive array, must be at least + * 4*number of channels + **/ +static int lis3l02dq_read_all(struct iio_dev *indio_dev, u8 *rx_array) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + struct spi_transfer *xfers; + struct spi_message msg; + int ret, i, j = 0; + + xfers = kcalloc(bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * 2, + sizeof(*xfers), GFP_KERNEL); + if (!xfers) + return -ENOMEM; + + mutex_lock(&st->buf_lock); + + for (i = 0; i < ARRAY_SIZE(read_all_tx_array)/4; i++) + if (test_bit(i, indio_dev->active_scan_mask)) { + /* lower byte */ + xfers[j].tx_buf = st->tx + 2*j; + st->tx[2*j] = read_all_tx_array[i*4]; + st->tx[2*j + 1] = 0; + if (rx_array) + xfers[j].rx_buf = rx_array + j*2; + xfers[j].bits_per_word = 8; + xfers[j].len = 2; + xfers[j].cs_change = 1; + j++; + + /* upper byte */ + xfers[j].tx_buf = st->tx + 2*j; + st->tx[2*j] = read_all_tx_array[i*4 + 2]; + st->tx[2*j + 1] = 0; + if (rx_array) + xfers[j].rx_buf = rx_array + j*2; + xfers[j].bits_per_word = 8; + xfers[j].len = 2; + xfers[j].cs_change = 1; + j++; + } + + /* After these are transmitted, the rx_buff should have + * values in alternate bytes + */ + spi_message_init(&msg); + for (j = 0; j < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * 2; j++) + spi_message_add_tail(&xfers[j], &msg); + + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + kfree(xfers); + + return ret; +} + +static int lis3l02dq_get_buffer_element(struct iio_dev *indio_dev, + u8 *buf) +{ + int ret, i; + u8 *rx_array ; + s16 *data = (s16 *)buf; + int scan_count = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + + rx_array = kzalloc(4 * scan_count, GFP_KERNEL); + if (rx_array == NULL) + return -ENOMEM; + ret = lis3l02dq_read_all(indio_dev, rx_array); + if (ret < 0) + return ret; + for (i = 0; i < scan_count; i++) + data[i] = combine_8_to_16(rx_array[i*4+1], + rx_array[i*4+3]); + kfree(rx_array); + + return i*sizeof(data[0]); +} + +static irqreturn_t lis3l02dq_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct iio_buffer *buffer = indio_dev->buffer; + int len = 0; + size_t datasize = buffer->access->get_bytes_per_datum(buffer); + char *data = kmalloc(datasize, GFP_KERNEL); + + if (data == NULL) { + dev_err(indio_dev->dev.parent, + "memory alloc failed in buffer bh"); + return -ENOMEM; + } + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + len = lis3l02dq_get_buffer_element(indio_dev, data); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (buffer->scan_timestamp) + *(s64 *)(((phys_addr_t)data + len + + sizeof(s64) - 1) & ~(sizeof(s64) - 1)) + = pf->timestamp; + buffer->access->store_to(buffer, (u8 *)data, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + kfree(data); + return IRQ_HANDLED; +} + +/* Caller responsible for locking as necessary. */ +static int +__lis3l02dq_write_data_ready_config(struct device *dev, bool state) +{ + int ret; + u8 valold; + bool currentlyset; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct lis3l02dq_state *st = iio_priv(indio_dev); + +/* Get the current event mask register */ + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + &valold); + if (ret) + goto error_ret; +/* Find out if data ready is already on */ + currentlyset + = valold & LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + +/* Disable requested */ + if (!state && currentlyset) { + /* disable the data ready signal */ + valold &= ~LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + + /* The double write is to overcome a hardware bug?*/ + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + valold); + if (ret) + goto error_ret; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + valold); + if (ret) + goto error_ret; + st->trigger_on = false; +/* Enable requested */ + } else if (state && !currentlyset) { + /* if not set, enable requested */ + /* first disable all events */ + ret = lis3l02dq_disable_all_events(indio_dev); + if (ret < 0) + goto error_ret; + + valold = ret | + LIS3L02DQ_REG_CTRL_2_ENABLE_DATA_READY_GENERATION; + + st->trigger_on = true; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_2_ADDR, + valold); + if (ret) + goto error_ret; + } + + return 0; +error_ret: + return ret; +} + +/** + * lis3l02dq_data_rdy_trigger_set_state() set datardy interrupt state + * + * If disabling the interrupt also does a final read to ensure it is clear. + * This is only important in some cases where the scan enable elements are + * switched before the buffer is reenabled. + **/ +static int lis3l02dq_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + int ret = 0; + u8 t; + + __lis3l02dq_write_data_ready_config(&indio_dev->dev, state); + if (state == false) { + /* + * A possible quirk with the handler is currently worked around + * by ensuring outstanding read events are cleared. + */ + ret = lis3l02dq_read_all(indio_dev, NULL); + } + lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_WAKE_UP_SRC_ADDR, + &t); + return ret; +} + +/** + * lis3l02dq_trig_try_reen() try renabling irq for data rdy trigger + * @trig: the datardy trigger + */ +static int lis3l02dq_trig_try_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = trig->private_data; + struct lis3l02dq_state *st = iio_priv(indio_dev); + int i; + + /* If gpio still high (or high again) */ + /* In theory possible we will need to do this several times */ + for (i = 0; i < 5; i++) + if (gpio_get_value(irq_to_gpio(st->us->irq))) + lis3l02dq_read_all(indio_dev, NULL); + else + break; + if (i == 5) + printk(KERN_INFO + "Failed to clear the interrupt for lis3l02dq\n"); + + /* irq reenabled so success! */ + return 0; +} + +static const struct iio_trigger_ops lis3l02dq_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &lis3l02dq_data_rdy_trigger_set_state, + .try_reenable = &lis3l02dq_trig_try_reen, +}; + +int lis3l02dq_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct lis3l02dq_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("lis3l02dq-dev%d", indio_dev->id); + if (!st->trig) { + ret = -ENOMEM; + goto error_ret; + } + + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &lis3l02dq_trigger_ops; + st->trig->private_data = indio_dev; + ret = iio_trigger_register(st->trig); + if (ret) + goto error_free_trig; + + return 0; + +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void lis3l02dq_remove_trigger(struct iio_dev *indio_dev) +{ + struct lis3l02dq_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + iio_free_trigger(st->trig); +} + +void lis3l02dq_unconfigure_buffer(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + lis3l02dq_free_buf(indio_dev->buffer); +} + +static int lis3l02dq_buffer_postenable(struct iio_dev *indio_dev) +{ + /* Disable unwanted channels otherwise the interrupt will not clear */ + u8 t; + int ret; + bool oneenabled = false; + + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + if (ret) + goto error_ret; + + if (test_bit(0, indio_dev->active_scan_mask)) { + t |= LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE; + oneenabled = true; + } else + t &= ~LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE; + if (test_bit(1, indio_dev->active_scan_mask)) { + t |= LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE; + oneenabled = true; + } else + t &= ~LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE; + if (test_bit(2, indio_dev->active_scan_mask)) { + t |= LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE; + oneenabled = true; + } else + t &= ~LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE; + + if (!oneenabled) /* what happens in this case is unknown */ + return -EINVAL; + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + t); + if (ret) + goto error_ret; + + return iio_triggered_buffer_postenable(indio_dev); +error_ret: + return ret; +} + +/* Turn all channels on again */ +static int lis3l02dq_buffer_predisable(struct iio_dev *indio_dev) +{ + u8 t; + int ret; + + ret = iio_triggered_buffer_predisable(indio_dev); + if (ret) + goto error_ret; + + ret = lis3l02dq_spi_read_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + &t); + if (ret) + goto error_ret; + t |= LIS3L02DQ_REG_CTRL_1_AXES_X_ENABLE | + LIS3L02DQ_REG_CTRL_1_AXES_Y_ENABLE | + LIS3L02DQ_REG_CTRL_1_AXES_Z_ENABLE; + + ret = lis3l02dq_spi_write_reg_8(indio_dev, + LIS3L02DQ_REG_CTRL_1_ADDR, + t); + +error_ret: + return ret; +} + +static const struct iio_buffer_setup_ops lis3l02dq_buffer_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &lis3l02dq_buffer_postenable, + .predisable = &lis3l02dq_buffer_predisable, +}; + +int lis3l02dq_configure_buffer(struct iio_dev *indio_dev) +{ + int ret; + struct iio_buffer *buffer; + + buffer = lis3l02dq_alloc_buf(indio_dev); + if (!buffer) + return -ENOMEM; + + indio_dev->buffer = buffer; + + buffer->scan_timestamp = true; + indio_dev->setup_ops = &lis3l02dq_buffer_setup_ops; + + /* Functions are NULL as we set handler below */ + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &lis3l02dq_trigger_handler, + 0, + indio_dev, + "lis3l02dq_consumer%d", + indio_dev->id); + + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_iio_sw_rb_free: + lis3l02dq_free_buf(indio_dev->buffer); + return ret; +} diff --git a/drivers/staging/iio/accel/sca3000.h b/drivers/staging/iio/accel/sca3000.h new file mode 100644 index 00000000..131daac9 --- /dev/null +++ b/drivers/staging/iio/accel/sca3000.h @@ -0,0 +1,269 @@ +/* + * sca3000.c -- support VTI sca3000 series accelerometers + * via SPI + * + * Copyright (c) 2007 Jonathan Cameron + * + * Partly based upon tle62x0.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Initial mode is direct measurement. + * + * Untested things + * + * Temperature reading (the e05 I'm testing with doesn't have a sensor) + * + * Free fall detection mode - supported but untested as I'm not droping my + * dubious wire rig far enough to test it. + * + * Unsupported as yet + * + * Time stamping of data from ring. Various ideas on how to do this but none + * are remotely simple. Suggestions welcome. + * + * Individual enabling disabling of channels going into ring buffer + * + * Overflow handling (this is signaled for all but 8 bit ring buffer mode.) + * + * Motion detector using AND combinations of signals. + * + * Note: Be very careful about not touching an register bytes marked + * as reserved on the data sheet. They really mean it as changing convents of + * some will cause the device to lock up. + * + * Known issues - on rare occasions the interrupts lock up. Not sure why as yet. + * Can probably alleviate this by reading the interrupt register on start, but + * that is really just brushing the problem under the carpet. + */ +#define SCA3000_WRITE_REG(a) (((a) << 2) | 0x02) +#define SCA3000_READ_REG(a) ((a) << 2) + +#define SCA3000_REG_ADDR_REVID 0x00 +#define SCA3000_REVID_MAJOR_MASK 0xf0 +#define SCA3000_REVID_MINOR_MASK 0x0f + +#define SCA3000_REG_ADDR_STATUS 0x02 +#define SCA3000_LOCKED 0x20 +#define SCA3000_EEPROM_CS_ERROR 0x02 +#define SCA3000_SPI_FRAME_ERROR 0x01 + +/* All reads done using register decrement so no need to directly access LSBs */ +#define SCA3000_REG_ADDR_X_MSB 0x05 +#define SCA3000_REG_ADDR_Y_MSB 0x07 +#define SCA3000_REG_ADDR_Z_MSB 0x09 + +#define SCA3000_REG_ADDR_RING_OUT 0x0f + +/* Temp read untested - the e05 doesn't have the sensor */ +#define SCA3000_REG_ADDR_TEMP_MSB 0x13 + +#define SCA3000_REG_ADDR_MODE 0x14 +#define SCA3000_MODE_PROT_MASK 0x28 + +#define SCA3000_RING_BUF_ENABLE 0x80 +#define SCA3000_RING_BUF_8BIT 0x40 +/* Free fall detection triggers an interrupt if the acceleration + * is below a threshold for equivalent of 25cm drop + */ +#define SCA3000_FREE_FALL_DETECT 0x10 +#define SCA3000_MEAS_MODE_NORMAL 0x00 +#define SCA3000_MEAS_MODE_OP_1 0x01 +#define SCA3000_MEAS_MODE_OP_2 0x02 + +/* In motion detection mode the accelerations are band pass filtered + * (aprox 1 - 25Hz) and then a programmable threshold used to trigger + * and interrupt. + */ +#define SCA3000_MEAS_MODE_MOT_DET 0x03 + +#define SCA3000_REG_ADDR_BUF_COUNT 0x15 + +#define SCA3000_REG_ADDR_INT_STATUS 0x16 + +#define SCA3000_INT_STATUS_THREE_QUARTERS 0x80 +#define SCA3000_INT_STATUS_HALF 0x40 + +#define SCA3000_INT_STATUS_FREE_FALL 0x08 +#define SCA3000_INT_STATUS_Y_TRIGGER 0x04 +#define SCA3000_INT_STATUS_X_TRIGGER 0x02 +#define SCA3000_INT_STATUS_Z_TRIGGER 0x01 + +/* Used to allow access to multiplexed registers */ +#define SCA3000_REG_ADDR_CTRL_SEL 0x18 +/* Only available for SCA3000-D03 and SCA3000-D01 */ +#define SCA3000_REG_CTRL_SEL_I2C_DISABLE 0x01 +#define SCA3000_REG_CTRL_SEL_MD_CTRL 0x02 +#define SCA3000_REG_CTRL_SEL_MD_Y_TH 0x03 +#define SCA3000_REG_CTRL_SEL_MD_X_TH 0x04 +#define SCA3000_REG_CTRL_SEL_MD_Z_TH 0x05 +/* BE VERY CAREFUL WITH THIS, IF 3 BITS ARE NOT SET the device + will not function */ +#define SCA3000_REG_CTRL_SEL_OUT_CTRL 0x0B +#define SCA3000_OUT_CTRL_PROT_MASK 0xE0 +#define SCA3000_OUT_CTRL_BUF_X_EN 0x10 +#define SCA3000_OUT_CTRL_BUF_Y_EN 0x08 +#define SCA3000_OUT_CTRL_BUF_Z_EN 0x04 +#define SCA3000_OUT_CTRL_BUF_DIV_4 0x02 +#define SCA3000_OUT_CTRL_BUF_DIV_2 0x01 + +/* Control which motion detector interrupts are on. + * For now only OR combinations are supported.x + */ +#define SCA3000_MD_CTRL_PROT_MASK 0xC0 +#define SCA3000_MD_CTRL_OR_Y 0x01 +#define SCA3000_MD_CTRL_OR_X 0x02 +#define SCA3000_MD_CTRL_OR_Z 0x04 +/* Currently unsupported */ +#define SCA3000_MD_CTRL_AND_Y 0x08 +#define SCA3000_MD_CTRL_AND_X 0x10 +#define SAC3000_MD_CTRL_AND_Z 0x20 + +/* Some control registers of complex access methods requiring this register to + * be used to remove a lock. + */ +#define SCA3000_REG_ADDR_UNLOCK 0x1e + +#define SCA3000_REG_ADDR_INT_MASK 0x21 +#define SCA3000_INT_MASK_PROT_MASK 0x1C + +#define SCA3000_INT_MASK_RING_THREE_QUARTER 0x80 +#define SCA3000_INT_MASK_RING_HALF 0x40 + +#define SCA3000_INT_MASK_ALL_INTS 0x02 +#define SCA3000_INT_MASK_ACTIVE_HIGH 0x01 +#define SCA3000_INT_MASK_ACTIVE_LOW 0x00 + +/* Values of multiplexed registers (write to ctrl_data after select) */ +#define SCA3000_REG_ADDR_CTRL_DATA 0x22 + +/* Measurement modes available on some sca3000 series chips. Code assumes others + * may become available in the future. + * + * Bypass - Bypass the low-pass filter in the signal channel so as to increase + * signal bandwidth. + * + * Narrow - Narrow low-pass filtering of the signal channel and half output + * data rate by decimation. + * + * Wide - Widen low-pass filtering of signal channel to increase bandwidth + */ +#define SCA3000_OP_MODE_BYPASS 0x01 +#define SCA3000_OP_MODE_NARROW 0x02 +#define SCA3000_OP_MODE_WIDE 0x04 +#define SCA3000_MAX_TX 6 +#define SCA3000_MAX_RX 2 + +/** + * struct sca3000_state - device instance state information + * @us: the associated spi device + * @info: chip variant information + * @indio_dev: device information used by the IIO core + * @interrupt_handler_ws: event interrupt handler for all events + * @last_timestamp: the timestamp of the last event + * @mo_det_use_count: reference counter for the motion detection unit + * @lock: lock used to protect elements of sca3000_state + * and the underlying device state. + * @bpse: number of bits per scan element + * @tx: dma-able transmit buffer + * @rx: dma-able receive buffer + **/ +struct sca3000_state { + struct spi_device *us; + const struct sca3000_chip_info *info; + struct work_struct interrupt_handler_ws; + s64 last_timestamp; + int mo_det_use_count; + struct mutex lock; + int bpse; + /* Can these share a cacheline ? */ + u8 rx[2] ____cacheline_aligned; + u8 tx[6] ____cacheline_aligned; +}; + +/** + * struct sca3000_chip_info - model dependent parameters + * @scale: scale * 10^-6 + * @temp_output: some devices have temperature sensors. + * @measurement_mode_freq: normal mode sampling frequency + * @option_mode_1: first optional mode. Not all models have one + * @option_mode_1_freq: option mode 1 sampling frequency + * @option_mode_2: second optional mode. Not all chips have one + * @option_mode_2_freq: option mode 2 sampling frequency + * + * This structure is used to hold information about the functionality of a given + * sca3000 variant. + **/ +struct sca3000_chip_info { + unsigned int scale; + bool temp_output; + int measurement_mode_freq; + int option_mode_1; + int option_mode_1_freq; + int option_mode_2; + int option_mode_2_freq; + int mot_det_mult_xz[6]; + int mot_det_mult_y[7]; +}; + +int sca3000_read_data_short(struct sca3000_state *st, + u8 reg_address_high, + int len); + +/** + * sca3000_write_reg() write a single register + * @address: address of register on chip + * @val: value to be written to register + * + * The main lock must be held. + **/ +int sca3000_write_reg(struct sca3000_state *st, u8 address, u8 val); + +#ifdef CONFIG_IIO_BUFFER +/** + * sca3000_register_ring_funcs() setup the ring state change functions + **/ +void sca3000_register_ring_funcs(struct iio_dev *indio_dev); + +/** + * sca3000_configure_ring() - allocate and configure ring buffer + * @indio_dev: iio-core device whose ring is to be configured + * + * The hardware ring buffer needs far fewer ring buffer functions than + * a software one as a lot of things are handled automatically. + * This function also tells the iio core that our device supports a + * hardware ring buffer mode. + **/ +int sca3000_configure_ring(struct iio_dev *indio_dev); + +/** + * sca3000_unconfigure_ring() - deallocate the ring buffer + * @indio_dev: iio-core device whose ring we are freeing + **/ +void sca3000_unconfigure_ring(struct iio_dev *indio_dev); + +/** + * sca3000_ring_int_process() handles ring related event pushing and escalation + * @val: the event code + **/ +void sca3000_ring_int_process(u8 val, struct iio_buffer *ring); + +#else +static inline void sca3000_register_ring_funcs(struct iio_dev *indio_dev) +{ +} + +static inline +int sca3000_register_ring_access_and_init(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void sca3000_ring_int_process(u8 val, void *ring) +{ +} + +#endif + diff --git a/drivers/staging/iio/accel/sca3000_core.c b/drivers/staging/iio/accel/sca3000_core.c new file mode 100644 index 00000000..49764fb7 --- /dev/null +++ b/drivers/staging/iio/accel/sca3000_core.c @@ -0,0 +1,1263 @@ +/* + * sca3000_core.c -- support VTI sca3000 series accelerometers via SPI + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Copyright (c) 2009 Jonathan Cameron + * + * See industrialio/accels/sca3000.h for comments. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" +#include "../buffer.h" + +#include "sca3000.h" + +enum sca3000_variant { + d01, + e02, + e04, + e05, +}; + +/* Note where option modes are not defined, the chip simply does not + * support any. + * Other chips in the sca3000 series use i2c and are not included here. + * + * Some of these devices are only listed in the family data sheet and + * do not actually appear to be available. + */ +static const struct sca3000_chip_info sca3000_spi_chip_info_tbl[] = { + [d01] = { + .scale = 7357, + .temp_output = true, + .measurement_mode_freq = 250, + .option_mode_1 = SCA3000_OP_MODE_BYPASS, + .option_mode_1_freq = 250, + .mot_det_mult_xz = {50, 100, 200, 350, 650, 1300}, + .mot_det_mult_y = {50, 100, 150, 250, 450, 850, 1750}, + }, + [e02] = { + .scale = 9810, + .measurement_mode_freq = 125, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 63, + .mot_det_mult_xz = {100, 150, 300, 550, 1050, 2050}, + .mot_det_mult_y = {50, 100, 200, 350, 700, 1350, 2700}, + }, + [e04] = { + .scale = 19620, + .measurement_mode_freq = 100, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 50, + .option_mode_2 = SCA3000_OP_MODE_WIDE, + .option_mode_2_freq = 400, + .mot_det_mult_xz = {200, 300, 600, 1100, 2100, 4100}, + .mot_det_mult_y = {100, 200, 400, 7000, 1400, 2700, 54000}, + }, + [e05] = { + .scale = 61313, + .measurement_mode_freq = 200, + .option_mode_1 = SCA3000_OP_MODE_NARROW, + .option_mode_1_freq = 50, + .option_mode_2 = SCA3000_OP_MODE_WIDE, + .option_mode_2_freq = 400, + .mot_det_mult_xz = {600, 900, 1700, 3200, 6100, 11900}, + .mot_det_mult_y = {300, 600, 1200, 2000, 4100, 7800, 15600}, + }, +}; + +int sca3000_write_reg(struct sca3000_state *st, u8 address, u8 val) +{ + st->tx[0] = SCA3000_WRITE_REG(address); + st->tx[1] = val; + return spi_write(st->us, st->tx, 2); +} + +int sca3000_read_data_short(struct sca3000_state *st, + uint8_t reg_address_high, + int len) +{ + struct spi_message msg; + struct spi_transfer xfer[2] = { + { + .len = 1, + .tx_buf = st->tx, + }, { + .len = len, + .rx_buf = st->rx, + } + }; + st->tx[0] = SCA3000_READ_REG(reg_address_high); + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + spi_message_add_tail(&xfer[1], &msg); + + return spi_sync(st->us, &msg); +} + +/** + * sca3000_reg_lock_on() test if the ctrl register lock is on + * + * Lock must be held. + **/ +static int sca3000_reg_lock_on(struct sca3000_state *st) +{ + int ret; + + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_STATUS, 1); + if (ret < 0) + return ret; + + return !(st->rx[0] & SCA3000_LOCKED); +} + +/** + * __sca3000_unlock_reg_lock() unlock the control registers + * + * Note the device does not appear to support doing this in a single transfer. + * This should only ever be used as part of ctrl reg read. + * Lock must be held before calling this + **/ +static int __sca3000_unlock_reg_lock(struct sca3000_state *st) +{ + struct spi_message msg; + struct spi_transfer xfer[3] = { + { + .len = 2, + .cs_change = 1, + .tx_buf = st->tx, + }, { + .len = 2, + .cs_change = 1, + .tx_buf = st->tx + 2, + }, { + .len = 2, + .tx_buf = st->tx + 4, + }, + }; + st->tx[0] = SCA3000_WRITE_REG(SCA3000_REG_ADDR_UNLOCK); + st->tx[1] = 0x00; + st->tx[2] = SCA3000_WRITE_REG(SCA3000_REG_ADDR_UNLOCK); + st->tx[3] = 0x50; + st->tx[4] = SCA3000_WRITE_REG(SCA3000_REG_ADDR_UNLOCK); + st->tx[5] = 0xA0; + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + spi_message_add_tail(&xfer[1], &msg); + spi_message_add_tail(&xfer[2], &msg); + + return spi_sync(st->us, &msg); +} + +/** + * sca3000_write_ctrl_reg() write to a lock protect ctrl register + * @sel: selects which registers we wish to write to + * @val: the value to be written + * + * Certain control registers are protected against overwriting by the lock + * register and use a shared write address. This function allows writing of + * these registers. + * Lock must be held. + **/ +static int sca3000_write_ctrl_reg(struct sca3000_state *st, + uint8_t sel, + uint8_t val) +{ + + int ret; + + ret = sca3000_reg_lock_on(st); + if (ret < 0) + goto error_ret; + if (ret) { + ret = __sca3000_unlock_reg_lock(st); + if (ret) + goto error_ret; + } + + /* Set the control select register */ + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_CTRL_SEL, sel); + if (ret) + goto error_ret; + + /* Write the actual value into the register */ + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_CTRL_DATA, val); + +error_ret: + return ret; +} + +/* Crucial that lock is called before calling this */ +/** + * sca3000_read_ctrl_reg() read from lock protected control register. + * + * Lock must be held. + **/ +static int sca3000_read_ctrl_reg(struct sca3000_state *st, + u8 ctrl_reg) +{ + int ret; + + ret = sca3000_reg_lock_on(st); + if (ret < 0) + goto error_ret; + if (ret) { + ret = __sca3000_unlock_reg_lock(st); + if (ret) + goto error_ret; + } + /* Set the control select register */ + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_CTRL_SEL, ctrl_reg); + if (ret) + goto error_ret; + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_CTRL_DATA, 1); + if (ret) + goto error_ret; + else + return st->rx[0]; +error_ret: + return ret; +} + +#ifdef SCA3000_DEBUG +/** + * sca3000_check_status() check the status register + * + * Only used for debugging purposes + **/ +static int sca3000_check_status(struct device *dev) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_STATUS, 1); + if (ret < 0) + goto error_ret; + if (st->rx[0] & SCA3000_EEPROM_CS_ERROR) + dev_err(dev, "eeprom error\n"); + if (st->rx[0] & SCA3000_SPI_FRAME_ERROR) + dev_err(dev, "Previous SPI Frame was corrupt\n"); + +error_ret: + mutex_unlock(&st->lock); + return ret; +} +#endif /* SCA3000_DEBUG */ + + +/** + * sca3000_show_reg() - sysfs interface to read the chip revision number + **/ +static ssize_t sca3000_show_rev(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len = 0, ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_REVID, 1); + if (ret < 0) + goto error_ret; + len += sprintf(buf + len, + "major=%d, minor=%d\n", + st->rx[0] & SCA3000_REVID_MAJOR_MASK, + st->rx[0] & SCA3000_REVID_MINOR_MASK); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_show_available_measurement_modes() display available modes + * + * This is all read from chip specific data in the driver. Not all + * of the sca3000 series support modes other than normal. + **/ +static ssize_t +sca3000_show_available_measurement_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int len = 0; + + len += sprintf(buf + len, "0 - normal mode"); + switch (st->info->option_mode_1) { + case SCA3000_OP_MODE_NARROW: + len += sprintf(buf + len, ", 1 - narrow mode"); + break; + case SCA3000_OP_MODE_BYPASS: + len += sprintf(buf + len, ", 1 - bypass mode"); + break; + } + switch (st->info->option_mode_2) { + case SCA3000_OP_MODE_WIDE: + len += sprintf(buf + len, ", 2 - wide mode"); + break; + } + /* always supported */ + len += sprintf(buf + len, " 3 - motion detection\n"); + + return len; +} + +/** + * sca3000_show_measurmenet_mode() sysfs read of current mode + **/ +static ssize_t +sca3000_show_measurement_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int len = 0, ret; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + /* mask bottom 2 bits - only ones that are relevant */ + st->rx[0] &= 0x03; + switch (st->rx[0]) { + case SCA3000_MEAS_MODE_NORMAL: + len += sprintf(buf + len, "0 - normal mode\n"); + break; + case SCA3000_MEAS_MODE_MOT_DET: + len += sprintf(buf + len, "3 - motion detection\n"); + break; + case SCA3000_MEAS_MODE_OP_1: + switch (st->info->option_mode_1) { + case SCA3000_OP_MODE_NARROW: + len += sprintf(buf + len, "1 - narrow mode\n"); + break; + case SCA3000_OP_MODE_BYPASS: + len += sprintf(buf + len, "1 - bypass mode\n"); + break; + } + break; + case SCA3000_MEAS_MODE_OP_2: + switch (st->info->option_mode_2) { + case SCA3000_OP_MODE_WIDE: + len += sprintf(buf + len, "2 - wide mode\n"); + break; + } + break; + } + +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_store_measurement_mode() set the current mode + **/ +static ssize_t +sca3000_store_measurement_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + u8 mask = 0x03; + u8 val; + + mutex_lock(&st->lock); + ret = kstrtou8(buf, 10, &val); + if (ret) + goto error_ret; + if (val > 3) { + ret = -EINVAL; + goto error_ret; + } + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + st->rx[0] &= ~mask; + st->rx[0] |= (val & mask); + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, st->rx[0]); + if (ret) + goto error_ret; + mutex_unlock(&st->lock); + + return len; + +error_ret: + mutex_unlock(&st->lock); + + return ret; +} + + +/* Not even vaguely standard attributes so defined here rather than + * in the relevant IIO core headers + */ +static IIO_DEVICE_ATTR(measurement_mode_available, S_IRUGO, + sca3000_show_available_measurement_modes, + NULL, 0); + +static IIO_DEVICE_ATTR(measurement_mode, S_IRUGO | S_IWUSR, + sca3000_show_measurement_mode, + sca3000_store_measurement_mode, + 0); + +/* More standard attributes */ + +static IIO_DEVICE_ATTR(revision, S_IRUGO, sca3000_show_rev, NULL, 0); + +#define SCA3000_INFO_MASK \ + IIO_CHAN_INFO_SCALE_SHARED_BIT +#define SCA3000_EVENT_MASK \ + (IIO_EV_BIT(IIO_EV_TYPE_MAG, IIO_EV_DIR_RISING)) + +static struct iio_chan_spec sca3000_channels[] = { + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_X, SCA3000_INFO_MASK, + 0, 0, IIO_ST('s', 11, 16, 5), SCA3000_EVENT_MASK), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Y, SCA3000_INFO_MASK, + 1, 1, IIO_ST('s', 11, 16, 5), SCA3000_EVENT_MASK), + IIO_CHAN(IIO_ACCEL, 1, 0, 0, NULL, 0, IIO_MOD_Z, SCA3000_INFO_MASK, + 2, 2, IIO_ST('s', 11, 16, 5), SCA3000_EVENT_MASK), +}; + +static u8 sca3000_addresses[3][3] = { + [0] = {SCA3000_REG_ADDR_X_MSB, SCA3000_REG_CTRL_SEL_MD_X_TH, + SCA3000_MD_CTRL_OR_X}, + [1] = {SCA3000_REG_ADDR_Y_MSB, SCA3000_REG_CTRL_SEL_MD_Y_TH, + SCA3000_MD_CTRL_OR_Y}, + [2] = {SCA3000_REG_ADDR_Z_MSB, SCA3000_REG_CTRL_SEL_MD_Z_TH, + SCA3000_MD_CTRL_OR_Z}, +}; + +static int sca3000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + u8 address; + + switch (mask) { + case 0: + mutex_lock(&st->lock); + if (st->mo_det_use_count) { + mutex_unlock(&st->lock); + return -EBUSY; + } + address = sca3000_addresses[chan->address][0]; + ret = sca3000_read_data_short(st, address, 2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + *val = (be16_to_cpup((__be16 *)st->rx) >> 3) & 0x1FFF; + *val = ((*val) << (sizeof(*val)*8 - 13)) >> + (sizeof(*val)*8 - 13); + mutex_unlock(&st->lock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + if (chan->type == IIO_ACCEL) + *val2 = st->info->scale; + else /* temperature */ + *val2 = 555556; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +/** + * sca3000_read_av_freq() sysfs function to get available frequencies + * + * The later modes are only relevant to the ring buffer - and depend on current + * mode. Note that data sheet gives rather wide tolerances for these so integer + * division will give good enough answer and not all chips have them specified + * at all. + **/ +static ssize_t sca3000_read_av_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int len = 0, ret, val; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret) + goto error_ret; + + switch (val & 0x03) { + case SCA3000_MEAS_MODE_NORMAL: + len += sprintf(buf + len, "%d %d %d\n", + st->info->measurement_mode_freq, + st->info->measurement_mode_freq/2, + st->info->measurement_mode_freq/4); + break; + case SCA3000_MEAS_MODE_OP_1: + len += sprintf(buf + len, "%d %d %d\n", + st->info->option_mode_1_freq, + st->info->option_mode_1_freq/2, + st->info->option_mode_1_freq/4); + break; + case SCA3000_MEAS_MODE_OP_2: + len += sprintf(buf + len, "%d %d %d\n", + st->info->option_mode_2_freq, + st->info->option_mode_2_freq/2, + st->info->option_mode_2_freq/4); + break; + } + return len; +error_ret: + return ret; +} +/** + * __sca3000_get_base_frequency() obtain mode specific base frequency + * + * lock must be held + **/ +static inline int __sca3000_get_base_freq(struct sca3000_state *st, + const struct sca3000_chip_info *info, + int *base_freq) +{ + int ret; + + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + switch (0x03 & st->rx[0]) { + case SCA3000_MEAS_MODE_NORMAL: + *base_freq = info->measurement_mode_freq; + break; + case SCA3000_MEAS_MODE_OP_1: + *base_freq = info->option_mode_1_freq; + break; + case SCA3000_MEAS_MODE_OP_2: + *base_freq = info->option_mode_2_freq; + break; + } +error_ret: + return ret; +} + +/** + * sca3000_read_frequency() sysfs interface to get the current frequency + **/ +static ssize_t sca3000_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int ret, len = 0, base_freq = 0, val; + + mutex_lock(&st->lock); + ret = __sca3000_get_base_freq(st, st->info, &base_freq); + if (ret) + goto error_ret_mut; + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL); + mutex_unlock(&st->lock); + if (ret) + goto error_ret; + val = ret; + if (base_freq > 0) + switch (val & 0x03) { + case 0x00: + case 0x03: + len = sprintf(buf, "%d\n", base_freq); + break; + case 0x01: + len = sprintf(buf, "%d\n", base_freq/2); + break; + case 0x02: + len = sprintf(buf, "%d\n", base_freq/4); + break; + } + + return len; +error_ret_mut: + mutex_unlock(&st->lock); +error_ret: + return ret; +} + +/** + * sca3000_set_frequency() sysfs interface to set the current frequency + **/ +static ssize_t sca3000_set_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int ret, base_freq = 0; + int ctrlval; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&st->lock); + /* What mode are we in? */ + ret = __sca3000_get_base_freq(st, st->info, &base_freq); + if (ret) + goto error_free_lock; + + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL); + if (ret < 0) + goto error_free_lock; + ctrlval = ret; + /* clear the bits */ + ctrlval &= ~0x03; + + if (val == base_freq/2) { + ctrlval |= SCA3000_OUT_CTRL_BUF_DIV_2; + } else if (val == base_freq/4) { + ctrlval |= SCA3000_OUT_CTRL_BUF_DIV_4; + } else if (val != base_freq) { + ret = -EINVAL; + goto error_free_lock; + } + ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL, + ctrlval); +error_free_lock: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/* Should only really be registered if ring buffer support is compiled in. + * Does no harm however and doing it right would add a fair bit of complexity + */ +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(sca3000_read_av_freq); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + sca3000_read_frequency, + sca3000_set_frequency); + + +/** + * sca3000_read_temp() sysfs interface to get the temperature when available + * +* The alignment of data in here is downright odd. See data sheet. +* Converting this into a meaningful value is left to inline functions in +* userspace part of header. +**/ +static ssize_t sca3000_read_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + int val; + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_TEMP_MSB, 2); + if (ret < 0) + goto error_ret; + val = ((st->rx[0] & 0x3F) << 3) | ((st->rx[1] & 0xE0) >> 5); + + return sprintf(buf, "%d\n", val); + +error_ret: + return ret; +} +static IIO_DEV_ATTR_TEMP_RAW(sca3000_read_temp); + +static IIO_CONST_ATTR_TEMP_SCALE("0.555556"); +static IIO_CONST_ATTR_TEMP_OFFSET("-214.6"); + +/** + * sca3000_read_thresh() - query of a threshold + **/ +static int sca3000_read_thresh(struct iio_dev *indio_dev, + u64 e, + int *val) +{ + int ret, i; + struct sca3000_state *st = iio_priv(indio_dev); + int num = IIO_EVENT_CODE_EXTRACT_MODIFIER(e); + mutex_lock(&st->lock); + ret = sca3000_read_ctrl_reg(st, sca3000_addresses[num][1]); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + *val = 0; + if (num == 1) + for_each_set_bit(i, (unsigned long *)&ret, + ARRAY_SIZE(st->info->mot_det_mult_y)) + *val += st->info->mot_det_mult_y[i]; + else + for_each_set_bit(i, (unsigned long *)&ret, + ARRAY_SIZE(st->info->mot_det_mult_xz)) + *val += st->info->mot_det_mult_xz[i]; + + return 0; +} + +/** + * sca3000_write_thresh() control of threshold + **/ +static int sca3000_write_thresh(struct iio_dev *indio_dev, + u64 e, + int val) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int num = IIO_EVENT_CODE_EXTRACT_MODIFIER(e); + int ret; + int i; + u8 nonlinear = 0; + + if (num == 1) { + i = ARRAY_SIZE(st->info->mot_det_mult_y); + while (i > 0) + if (val >= st->info->mot_det_mult_y[--i]) { + nonlinear |= (1 << i); + val -= st->info->mot_det_mult_y[i]; + } + } else { + i = ARRAY_SIZE(st->info->mot_det_mult_xz); + while (i > 0) + if (val >= st->info->mot_det_mult_xz[--i]) { + nonlinear |= (1 << i); + val -= st->info->mot_det_mult_xz[i]; + } + } + + mutex_lock(&st->lock); + ret = sca3000_write_ctrl_reg(st, sca3000_addresses[num][1], nonlinear); + mutex_unlock(&st->lock); + + return ret; +} + +static struct attribute *sca3000_attributes[] = { + &iio_dev_attr_revision.dev_attr.attr, + &iio_dev_attr_measurement_mode_available.dev_attr.attr, + &iio_dev_attr_measurement_mode.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + NULL, +}; + +static struct attribute *sca3000_attributes_with_temp[] = { + &iio_dev_attr_revision.dev_attr.attr, + &iio_dev_attr_measurement_mode_available.dev_attr.attr, + &iio_dev_attr_measurement_mode.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + /* Only present if temp sensor is */ + &iio_dev_attr_in_temp_raw.dev_attr.attr, + &iio_const_attr_in_temp_offset.dev_attr.attr, + &iio_const_attr_in_temp_scale.dev_attr.attr, + NULL, +}; + +static const struct attribute_group sca3000_attribute_group = { + .attrs = sca3000_attributes, +}; + +static const struct attribute_group sca3000_attribute_group_with_temp = { + .attrs = sca3000_attributes_with_temp, +}; + +/* RING RELATED interrupt handler */ +/* depending on event, push to the ring buffer event chrdev or the event one */ + +/** + * sca3000_event_handler() - handling ring and non ring events + * + * This function is complicated by the fact that the devices can signify ring + * and non ring events via the same interrupt line and they can only + * be distinguished via a read of the relevant status register. + **/ +static irqreturn_t sca3000_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sca3000_state *st = iio_priv(indio_dev); + int ret, val; + s64 last_timestamp = iio_get_time_ns(); + + /* Could lead if badly timed to an extra read of status reg, + * but ensures no interrupt is missed. + */ + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_STATUS, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret) + goto done; + + sca3000_ring_int_process(val, indio_dev->buffer); + + if (val & SCA3000_INT_STATUS_FREE_FALL) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + last_timestamp); + + if (val & SCA3000_INT_STATUS_Y_TRIGGER) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Y, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + last_timestamp); + + if (val & SCA3000_INT_STATUS_X_TRIGGER) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_X, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + last_timestamp); + + if (val & SCA3000_INT_STATUS_Z_TRIGGER) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, + 0, + IIO_MOD_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + last_timestamp); + +done: + return IRQ_HANDLED; +} + +/** + * sca3000_read_event_config() what events are enabled + **/ +static int sca3000_read_event_config(struct iio_dev *indio_dev, + u64 e) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + u8 protect_mask = 0x03; + int num = IIO_EVENT_CODE_EXTRACT_MODIFIER(e); + + /* read current value of mode register */ + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + + if ((st->rx[0] & protect_mask) != SCA3000_MEAS_MODE_MOT_DET) + ret = 0; + else { + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL); + if (ret < 0) + goto error_ret; + /* only supporting logical or's for now */ + ret = !!(ret & sca3000_addresses[num][2]); + } +error_ret: + mutex_unlock(&st->lock); + + return ret; +} +/** + * sca3000_query_free_fall_mode() is free fall mode enabled + **/ +static ssize_t sca3000_query_free_fall_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret, len; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + int val; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + len = sprintf(buf, "%d\n", + !!(val & SCA3000_FREE_FALL_DETECT)); + return len; +} + +/** + * sca3000_set_free_fall_mode() simple on off control for free fall int + * + * In these chips the free fall detector should send an interrupt if + * the device falls more than 25cm. This has not been tested due + * to fragile wiring. + **/ + +static ssize_t sca3000_set_free_fall_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + long val; + int ret; + u8 protect_mask = SCA3000_FREE_FALL_DETECT; + + mutex_lock(&st->lock); + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + + /* read current value of mode register */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + + /*if off and should be on*/ + if (val && !(st->rx[0] & protect_mask)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] | SCA3000_FREE_FALL_DETECT)); + /* if on and should be off */ + else if (!val && (st->rx[0] & protect_mask)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] & ~protect_mask)); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +/** + * sca3000_set_mo_det() simple on off control for motion detector + * + * This is a per axis control, but enabling any will result in the + * motion detector unit being enabled. + * N.B. enabling motion detector stops normal data acquisition. + * There is a complexity in knowing which mode to return to when + * this mode is disabled. Currently normal mode is assumed. + **/ +static int sca3000_write_event_config(struct iio_dev *indio_dev, + u64 e, + int state) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret, ctrlval; + u8 protect_mask = 0x03; + int num = IIO_EVENT_CODE_EXTRACT_MODIFIER(e); + + mutex_lock(&st->lock); + /* First read the motion detector config to find out if + * this axis is on*/ + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL); + if (ret < 0) + goto exit_point; + ctrlval = ret; + /* Off and should be on */ + if (state && !(ctrlval & sca3000_addresses[num][2])) { + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + ctrlval | + sca3000_addresses[num][2]); + if (ret) + goto exit_point; + st->mo_det_use_count++; + } else if (!state && (ctrlval & sca3000_addresses[num][2])) { + ret = sca3000_write_ctrl_reg(st, + SCA3000_REG_CTRL_SEL_MD_CTRL, + ctrlval & + ~(sca3000_addresses[num][2])); + if (ret) + goto exit_point; + st->mo_det_use_count--; + } + + /* read current value of mode register */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto exit_point; + /*if off and should be on*/ + if ((st->mo_det_use_count) + && ((st->rx[0] & protect_mask) != SCA3000_MEAS_MODE_MOT_DET)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] & ~protect_mask) + | SCA3000_MEAS_MODE_MOT_DET); + /* if on and should be off */ + else if (!(st->mo_det_use_count) + && ((st->rx[0] & protect_mask) == SCA3000_MEAS_MODE_MOT_DET)) + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] & ~protect_mask)); +exit_point: + mutex_unlock(&st->lock); + + return ret; +} + +/* Free fall detector related event attribute */ +static IIO_DEVICE_ATTR_NAMED(accel_xayaz_mag_falling_en, + in_accel_x&y&z_mag_falling_en, + S_IRUGO | S_IWUSR, + sca3000_query_free_fall_mode, + sca3000_set_free_fall_mode, + 0); + +static IIO_CONST_ATTR_NAMED(accel_xayaz_mag_falling_period, + in_accel_x&y&z_mag_falling_period, + "0.226"); + +static struct attribute *sca3000_event_attributes[] = { + &iio_dev_attr_accel_xayaz_mag_falling_en.dev_attr.attr, + &iio_const_attr_accel_xayaz_mag_falling_period.dev_attr.attr, + NULL, +}; + +static struct attribute_group sca3000_event_attribute_group = { + .attrs = sca3000_event_attributes, + .name = "events", +}; + +/** + * sca3000_clean_setup() get the device into a predictable state + * + * Devices use flash memory to store many of the register values + * and hence can come up in somewhat unpredictable states. + * Hence reset everything on driver load. + **/ +static int sca3000_clean_setup(struct sca3000_state *st) +{ + int ret; + + mutex_lock(&st->lock); + /* Ensure all interrupts have been acknowledged */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_STATUS, 1); + if (ret) + goto error_ret; + + /* Turn off all motion detection channels */ + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL); + if (ret < 0) + goto error_ret; + ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_MD_CTRL, + ret & SCA3000_MD_CTRL_PROT_MASK); + if (ret) + goto error_ret; + + /* Disable ring buffer */ + ret = sca3000_read_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL); + ret = sca3000_write_ctrl_reg(st, SCA3000_REG_CTRL_SEL_OUT_CTRL, + (ret & SCA3000_OUT_CTRL_PROT_MASK) + | SCA3000_OUT_CTRL_BUF_X_EN + | SCA3000_OUT_CTRL_BUF_Y_EN + | SCA3000_OUT_CTRL_BUF_Z_EN + | SCA3000_OUT_CTRL_BUF_DIV_4); + if (ret) + goto error_ret; + /* Enable interrupts, relevant to mode and set up as active low */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_MASK, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_INT_MASK, + (ret & SCA3000_INT_MASK_PROT_MASK) + | SCA3000_INT_MASK_ACTIVE_LOW); + if (ret) + goto error_ret; + /* Select normal measurement mode, free fall off, ring off */ + /* Ring in 12 bit mode - it is fine to overwrite reserved bits 3,5 + * as that occurs in one of the example on the datasheet */ + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_MODE, + (st->rx[0] & SCA3000_MODE_PROT_MASK)); + st->bpse = 11; + +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static const struct iio_info sca3000_info = { + .attrs = &sca3000_attribute_group, + .read_raw = &sca3000_read_raw, + .event_attrs = &sca3000_event_attribute_group, + .read_event_value = &sca3000_read_thresh, + .write_event_value = &sca3000_write_thresh, + .read_event_config = &sca3000_read_event_config, + .write_event_config = &sca3000_write_event_config, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info sca3000_info_with_temp = { + .attrs = &sca3000_attribute_group_with_temp, + .read_raw = &sca3000_read_raw, + .read_event_value = &sca3000_read_thresh, + .write_event_value = &sca3000_write_thresh, + .read_event_config = &sca3000_read_event_config, + .write_event_config = &sca3000_write_event_config, + .driver_module = THIS_MODULE, +}; + +static int __devinit sca3000_probe(struct spi_device *spi) +{ + int ret; + struct sca3000_state *st; + struct iio_dev *indio_dev; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->us = spi; + mutex_init(&st->lock); + st->info = &sca3000_spi_chip_info_tbl[spi_get_device_id(spi) + ->driver_data]; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + if (st->info->temp_output) + indio_dev->info = &sca3000_info_with_temp; + else { + indio_dev->info = &sca3000_info; + indio_dev->channels = sca3000_channels; + indio_dev->num_channels = ARRAY_SIZE(sca3000_channels); + } + indio_dev->modes = INDIO_DIRECT_MODE; + + sca3000_configure_ring(indio_dev); + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_free_dev; + + ret = iio_buffer_register(indio_dev, + sca3000_channels, + ARRAY_SIZE(sca3000_channels)); + if (ret < 0) + goto error_unregister_dev; + if (indio_dev->buffer) { + iio_scan_mask_set(indio_dev, indio_dev->buffer, 0); + iio_scan_mask_set(indio_dev, indio_dev->buffer, 1); + iio_scan_mask_set(indio_dev, indio_dev->buffer, 2); + } + + if (spi->irq) { + ret = request_threaded_irq(spi->irq, + NULL, + &sca3000_event_handler, + IRQF_TRIGGER_FALLING, + "sca3000", + indio_dev); + if (ret) + goto error_unregister_ring; + } + sca3000_register_ring_funcs(indio_dev); + ret = sca3000_clean_setup(st); + if (ret) + goto error_free_irq; + return 0; + +error_free_irq: + if (spi->irq) + free_irq(spi->irq, indio_dev); +error_unregister_ring: + iio_buffer_unregister(indio_dev); +error_unregister_dev: + iio_device_unregister(indio_dev); +error_free_dev: + iio_free_device(indio_dev); + +error_ret: + return ret; +} + +static int sca3000_stop_all_interrupts(struct sca3000_state *st) +{ + int ret; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_MASK, 1); + if (ret) + goto error_ret; + ret = sca3000_write_reg(st, SCA3000_REG_ADDR_INT_MASK, + (st->rx[0] & + ~(SCA3000_INT_MASK_RING_THREE_QUARTER | + SCA3000_INT_MASK_RING_HALF | + SCA3000_INT_MASK_ALL_INTS))); +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static int sca3000_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + /* Must ensure no interrupts can be generated after this!*/ + ret = sca3000_stop_all_interrupts(st); + if (ret) + return ret; + if (spi->irq) + free_irq(spi->irq, indio_dev); + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); + sca3000_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id sca3000_id[] = { + {"sca3000_d01", d01}, + {"sca3000_e02", e02}, + {"sca3000_e04", e04}, + {"sca3000_e05", e05}, + {} +}; +MODULE_DEVICE_TABLE(spi, sca3000_id); + +static struct spi_driver sca3000_driver = { + .driver = { + .name = "sca3000", + .owner = THIS_MODULE, + }, + .probe = sca3000_probe, + .remove = __devexit_p(sca3000_remove), + .id_table = sca3000_id, +}; +module_spi_driver(sca3000_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("VTI SCA3000 Series Accelerometers SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/accel/sca3000_ring.c b/drivers/staging/iio/accel/sca3000_ring.c new file mode 100644 index 00000000..6b824a11 --- /dev/null +++ b/drivers/staging/iio/accel/sca3000_ring.c @@ -0,0 +1,364 @@ +/* + * sca3000_ring.c -- support VTI sca3000 series accelerometers via SPI + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Copyright (c) 2009 Jonathan Cameron + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" +#include "../ring_hw.h" +#include "sca3000.h" + +/* RFC / future work + * + * The internal ring buffer doesn't actually change what it holds depending + * on which signals are enabled etc, merely whether you can read them. + * As such the scan mode selection is somewhat different than for a software + * ring buffer and changing it actually covers any data already in the buffer. + * Currently scan elements aren't configured so it doesn't matter. + */ + +static int sca3000_read_data(struct sca3000_state *st, + uint8_t reg_address_high, + u8 **rx_p, + int len) +{ + int ret; + struct spi_message msg; + struct spi_transfer xfer[2] = { + { + .len = 1, + .tx_buf = st->tx, + }, { + .len = len, + } + }; + *rx_p = kmalloc(len, GFP_KERNEL); + if (*rx_p == NULL) { + ret = -ENOMEM; + goto error_ret; + } + xfer[1].rx_buf = *rx_p; + st->tx[0] = SCA3000_READ_REG(reg_address_high); + spi_message_init(&msg); + spi_message_add_tail(&xfer[0], &msg); + spi_message_add_tail(&xfer[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(get_device(&st->us->dev), "problem reading register"); + goto error_free_rx; + } + + return 0; +error_free_rx: + kfree(*rx_p); +error_ret: + return ret; +} + +/** + * sca3000_read_first_n_hw_rb() - main ring access, pulls data from ring + * @r: the ring + * @count: number of samples to try and pull + * @data: output the actual samples pulled from the hw ring + * + * Currently does not provide timestamps. As the hardware doesn't add them they + * can only be inferred approximately from ring buffer events such as 50% full + * and knowledge of when buffer was last emptied. This is left to userspace. + **/ +static int sca3000_read_first_n_hw_rb(struct iio_buffer *r, + size_t count, char __user *buf) +{ + struct iio_hw_buffer *hw_ring = iio_to_hw_buf(r); + struct iio_dev *indio_dev = hw_ring->private; + struct sca3000_state *st = iio_priv(indio_dev); + u8 *rx; + int ret, i, num_available, num_read = 0; + int bytes_per_sample = 1; + + if (st->bpse == 11) + bytes_per_sample = 2; + + mutex_lock(&st->lock); + if (count % bytes_per_sample) { + ret = -EINVAL; + goto error_ret; + } + + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_BUF_COUNT, 1); + if (ret) + goto error_ret; + else + num_available = st->rx[0]; + /* + * num_available is the total number of samples available + * i.e. number of time points * number of channels. + */ + if (count > num_available * bytes_per_sample) + num_read = num_available*bytes_per_sample; + else + num_read = count; + + ret = sca3000_read_data(st, + SCA3000_REG_ADDR_RING_OUT, + &rx, num_read); + if (ret) + goto error_ret; + + for (i = 0; i < num_read; i++) + *(((u16 *)rx) + i) = be16_to_cpup((u16 *)rx + i); + + if (copy_to_user(buf, rx, num_read)) + ret = -EFAULT; + kfree(rx); + r->stufftoread = 0; +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : num_read; +} + +/* This is only valid with all 3 elements enabled */ +static int sca3000_ring_get_length(struct iio_buffer *r) +{ + return 64; +} + +/* only valid if resolution is kept at 11bits */ +static int sca3000_ring_get_bytes_per_datum(struct iio_buffer *r) +{ + return 6; +} + +static IIO_BUFFER_ENABLE_ATTR; +static IIO_BUFFER_LENGTH_ATTR; + +/** + * sca3000_query_ring_int() is the hardware ring status interrupt enabled + **/ +static ssize_t sca3000_query_ring_int(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret, val; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_MASK, 1); + val = st->rx[0]; + mutex_unlock(&st->lock); + if (ret) + return ret; + + return sprintf(buf, "%d\n", !!(val & this_attr->address)); +} + +/** + * sca3000_set_ring_int() set state of ring status interrupt + **/ +static ssize_t sca3000_set_ring_int(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + long val; + int ret; + + mutex_lock(&st->lock); + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_INT_MASK, 1); + if (ret) + goto error_ret; + if (val) + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_INT_MASK, + st->rx[0] | this_attr->address); + else + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_INT_MASK, + st->rx[0] & ~this_attr->address); +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(50_percent, S_IRUGO | S_IWUSR, + sca3000_query_ring_int, + sca3000_set_ring_int, + SCA3000_INT_MASK_RING_HALF); + +static IIO_DEVICE_ATTR(75_percent, S_IRUGO | S_IWUSR, + sca3000_query_ring_int, + sca3000_set_ring_int, + SCA3000_INT_MASK_RING_THREE_QUARTER); + +static ssize_t sca3000_show_buffer_scale(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct sca3000_state *st = iio_priv(indio_dev); + + return sprintf(buf, "0.%06d\n", 4*st->info->scale); +} + +static IIO_DEVICE_ATTR(in_accel_scale, + S_IRUGO, + sca3000_show_buffer_scale, + NULL, + 0); + +/* + * Ring buffer attributes + * This device is a bit unusual in that the sampling frequency and bpse + * only apply to the ring buffer. At all times full rate and accuracy + * is available via direct reading from registers. + */ +static struct attribute *sca3000_ring_attributes[] = { + &dev_attr_length.attr, + &dev_attr_enable.attr, + &iio_dev_attr_50_percent.dev_attr.attr, + &iio_dev_attr_75_percent.dev_attr.attr, + &iio_dev_attr_in_accel_scale.dev_attr.attr, + NULL, +}; + +static struct attribute_group sca3000_ring_attr = { + .attrs = sca3000_ring_attributes, + .name = "buffer", +}; + +static struct iio_buffer *sca3000_rb_allocate(struct iio_dev *indio_dev) +{ + struct iio_buffer *buf; + struct iio_hw_buffer *ring; + + ring = kzalloc(sizeof *ring, GFP_KERNEL); + if (!ring) + return NULL; + + ring->private = indio_dev; + buf = &ring->buf; + buf->stufftoread = 0; + buf->attrs = &sca3000_ring_attr; + iio_buffer_init(buf); + + return buf; +} + +static inline void sca3000_rb_free(struct iio_buffer *r) +{ + kfree(iio_to_hw_buf(r)); +} + +static const struct iio_buffer_access_funcs sca3000_ring_access_funcs = { + .read_first_n = &sca3000_read_first_n_hw_rb, + .get_length = &sca3000_ring_get_length, + .get_bytes_per_datum = &sca3000_ring_get_bytes_per_datum, +}; + +int sca3000_configure_ring(struct iio_dev *indio_dev) +{ + indio_dev->buffer = sca3000_rb_allocate(indio_dev); + if (indio_dev->buffer == NULL) + return -ENOMEM; + indio_dev->modes |= INDIO_BUFFER_HARDWARE; + + indio_dev->buffer->access = &sca3000_ring_access_funcs; + + return 0; +} + +void sca3000_unconfigure_ring(struct iio_dev *indio_dev) +{ + sca3000_rb_free(indio_dev->buffer); +} + +static inline +int __sca3000_hw_ring_state_set(struct iio_dev *indio_dev, bool state) +{ + struct sca3000_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->lock); + ret = sca3000_read_data_short(st, SCA3000_REG_ADDR_MODE, 1); + if (ret) + goto error_ret; + if (state) { + printk(KERN_INFO "supposedly enabling ring buffer\n"); + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_MODE, + (st->rx[0] | SCA3000_RING_BUF_ENABLE)); + } else + ret = sca3000_write_reg(st, + SCA3000_REG_ADDR_MODE, + (st->rx[0] & ~SCA3000_RING_BUF_ENABLE)); +error_ret: + mutex_unlock(&st->lock); + + return ret; +} +/** + * sca3000_hw_ring_preenable() hw ring buffer preenable function + * + * Very simple enable function as the chip will allows normal reads + * during ring buffer operation so as long as it is indeed running + * before we notify the core, the precise ordering does not matter. + **/ +static int sca3000_hw_ring_preenable(struct iio_dev *indio_dev) +{ + return __sca3000_hw_ring_state_set(indio_dev, 1); +} + +static int sca3000_hw_ring_postdisable(struct iio_dev *indio_dev) +{ + return __sca3000_hw_ring_state_set(indio_dev, 0); +} + +static const struct iio_buffer_setup_ops sca3000_ring_setup_ops = { + .preenable = &sca3000_hw_ring_preenable, + .postdisable = &sca3000_hw_ring_postdisable, +}; + +void sca3000_register_ring_funcs(struct iio_dev *indio_dev) +{ + indio_dev->setup_ops = &sca3000_ring_setup_ops; +} + +/** + * sca3000_ring_int_process() ring specific interrupt handling. + * + * This is only split from the main interrupt handler so as to + * reduce the amount of code if the ring buffer is not enabled. + **/ +void sca3000_ring_int_process(u8 val, struct iio_buffer *ring) +{ + if (val & (SCA3000_INT_STATUS_THREE_QUARTERS | + SCA3000_INT_STATUS_HALF)) { + ring->stufftoread = true; + wake_up_interruptible(&ring->pollq); + } +} diff --git a/drivers/staging/iio/adc/Kconfig b/drivers/staging/iio/adc/Kconfig new file mode 100644 index 00000000..592eabd8 --- /dev/null +++ b/drivers/staging/iio/adc/Kconfig @@ -0,0 +1,205 @@ +# +# ADC drivers +# +menu "Analog to digital converters" + +config AD7291 + tristate "Analog Devices AD7291 ADC driver" + depends on I2C + help + Say yes here to build support for Analog Devices AD7291 + 8 Channel ADC with temperature sensor. + +config AD7298 + tristate "Analog Devices AD7298 ADC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD7298 + 8 Channel ADC with temperature sensor. + + To compile this driver as a module, choose M here: the + module will be called ad7298. + +config AD7606 + tristate "Analog Devices AD7606 ADC driver" + depends on GPIOLIB + select IIO_BUFFER + select IIO_TRIGGER + select IIO_SW_RING + help + Say yes here to build support for Analog Devices: + ad7606, ad7606-6, ad7606-4 analog to digital converters (ADC). + + To compile this driver as a module, choose M here: the + module will be called ad7606. + +config AD7606_IFACE_PARALLEL + tristate "parallel interface support" + depends on AD7606 + help + Say yes here to include parallel interface support on the AD7606 + ADC driver. + +config AD7606_IFACE_SPI + tristate "spi interface support" + depends on AD7606 + depends on SPI + help + Say yes here to include parallel interface support on the AD7606 + ADC driver. + +config AD799X + tristate "Analog Devices AD799x ADC driver" + depends on I2C + select IIO_TRIGGER if IIO_BUFFER + select AD799X_RING_BUFFER + help + Say yes here to build support for Analog Devices: + ad7991, ad7995, ad7999, ad7992, ad7993, ad7994, ad7997, ad7998 + i2c analog to digital converters (ADC). Provides direct access + via sysfs. + +config AD799X_RING_BUFFER + bool "Analog Devices AD799x: use ring buffer" + depends on AD799X + select IIO_BUFFER + select IIO_SW_RING + help + Say yes here to include ring buffer support in the AD799X + ADC driver. + +config AD7476 + tristate "Analog Devices AD7475/6/7/8 AD7466/7/8 and AD7495 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_SW_RING + select IIO_TRIGGER + help + Say yes here to build support for Analog Devices + AD7475, AD7476, AD7477, AD7478, AD7466, AD7467, AD7468, AD7495 + SPI analog to digital converters (ADC). + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ad7476. + +config AD7887 + tristate "Analog Devices AD7887 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_SW_RING + select IIO_TRIGGER + help + Say yes here to build support for Analog Devices + AD7887 SPI analog to digital converter (ADC). + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ad7887. + +config AD7780 + tristate "Analog Devices AD7780 AD7781 ADC driver" + depends on SPI + depends on GPIOLIB + help + Say yes here to build support for Analog Devices + AD7780 and AD7781 SPI analog to digital converters (ADC). + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ad7780. + +config AD7793 + tristate "Analog Devices AD7792 AD7793 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_SW_RING + select IIO_TRIGGER + help + Say yes here to build support for Analog Devices + AD7792 and AD7793 SPI analog to digital converters (ADC). + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called AD7793. + +config AD7816 + tristate "Analog Devices AD7816/7/8 temperature sensor and ADC driver" + depends on SPI + depends on GENERIC_GPIO + help + Say yes here to build support for Analog Devices AD7816/7/8 + temperature sensors and ADC. + +config AD7192 + tristate "Analog Devices AD7190 AD7192 AD7195 ADC driver" + depends on SPI + select IIO_BUFFER + select IIO_SW_RING + select IIO_TRIGGER + help + Say yes here to build support for Analog Devices AD7190, + AD7192 or AD7195 SPI analog to digital converters (ADC). + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ad7192. + +config ADT7310 + tristate "Analog Devices ADT7310 temperature sensor driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADT7310 + temperature sensors. + +config ADT7410 + tristate "Analog Devices ADT7410 temperature sensor driver" + depends on I2C + help + Say yes here to build support for Analog Devices ADT7410 + temperature sensors. + +config AD7280 + tristate "Analog Devices AD7280A Lithium Ion Battery Monitoring System" + depends on SPI + help + Say yes here to build support for Analog Devices AD7280A + Lithium Ion Battery Monitoring System. + + To compile this driver as a module, choose M here: the + module will be called ad7280a + +config MAX1363 + tristate "Maxim max1363 ADC driver" + depends on I2C + select IIO_TRIGGER if IIO_BUFFER + select MAX1363_RING_BUFFER + help + Say yes here to build support for many Maxim i2c analog to digital + converters (ADC). (max1361, max1362, max1363, max1364, max1036, + max1037, max1038, max1039, max1136, max1136, max1137, max1138, + max1139, max1236, max1237, max11238, max1239, max11600, max11601, + max11602, max11603, max11604, max11605, max11606, max11607, + max11608, max11609, max11610, max11611, max11612, max11613, + max11614, max11615, max11616, max11617, max11644, max11645, + max11646, max11647) Provides direct access via sysfs. + +config MAX1363_RING_BUFFER + bool "Maxim max1363: use ring buffer" + depends on MAX1363 + select IIO_BUFFER + select IIO_SW_RING + help + Say yes here to include ring buffer support in the MAX1363 + ADC driver. + +config LPC32XX_ADC + tristate "NXP LPC32XX ADC" + depends on ARCH_LPC32XX && !TOUCHSCREEN_LPC32XX + help + Say yes here to build support for the integrated ADC inside the + LPC32XX SoC. Note that this feature uses the same hardware as the + touchscreen driver, so you can only select one of the two drivers + (lpc32xx_adc or lpc32xx_ts). Provides direct access via sysfs. + +endmenu diff --git a/drivers/staging/iio/adc/Makefile b/drivers/staging/iio/adc/Makefile new file mode 100644 index 00000000..f83ab955 --- /dev/null +++ b/drivers/staging/iio/adc/Makefile @@ -0,0 +1,40 @@ +# +# Makefile for industrial I/O ADC drivers +# + +max1363-y := max1363_core.o +max1363-y += max1363_ring.o + +obj-$(CONFIG_MAX1363) += max1363.o + +ad7606-y := ad7606_core.o +ad7606-$(CONFIG_IIO_BUFFER) += ad7606_ring.o +ad7606-$(CONFIG_AD7606_IFACE_PARALLEL) += ad7606_par.o +ad7606-$(CONFIG_AD7606_IFACE_SPI) += ad7606_spi.o +obj-$(CONFIG_AD7606) += ad7606.o + +ad799x-y := ad799x_core.o +ad799x-$(CONFIG_AD799X_RING_BUFFER) += ad799x_ring.o +obj-$(CONFIG_AD799X) += ad799x.o + +ad7476-y := ad7476_core.o +ad7476-$(CONFIG_IIO_BUFFER) += ad7476_ring.o +obj-$(CONFIG_AD7476) += ad7476.o + +ad7887-y := ad7887_core.o +ad7887-$(CONFIG_IIO_BUFFER) += ad7887_ring.o +obj-$(CONFIG_AD7887) += ad7887.o + +ad7298-y := ad7298_core.o +ad7298-$(CONFIG_IIO_BUFFER) += ad7298_ring.o +obj-$(CONFIG_AD7298) += ad7298.o + +obj-$(CONFIG_AD7291) += ad7291.o +obj-$(CONFIG_AD7780) += ad7780.o +obj-$(CONFIG_AD7793) += ad7793.o +obj-$(CONFIG_AD7816) += ad7816.o +obj-$(CONFIG_AD7192) += ad7192.o +obj-$(CONFIG_ADT7310) += adt7310.o +obj-$(CONFIG_ADT7410) += adt7410.o +obj-$(CONFIG_AD7280) += ad7280a.o +obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o diff --git a/drivers/staging/iio/adc/ad7192.c b/drivers/staging/iio/adc/ad7192.c new file mode 100644 index 00000000..9fd6d63d --- /dev/null +++ b/drivers/staging/iio/adc/ad7192.c @@ -0,0 +1,1161 @@ +/* + * AD7190 AD7192 AD7195 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" +#include "../ring_sw.h" +#include "../trigger.h" +#include "../trigger_consumer.h" + +#include "ad7192.h" + +/* Registers */ +#define AD7192_REG_COMM 0 /* Communications Register (WO, 8-bit) */ +#define AD7192_REG_STAT 0 /* Status Register (RO, 8-bit) */ +#define AD7192_REG_MODE 1 /* Mode Register (RW, 24-bit */ +#define AD7192_REG_CONF 2 /* Configuration Register (RW, 24-bit) */ +#define AD7192_REG_DATA 3 /* Data Register (RO, 24/32-bit) */ +#define AD7192_REG_ID 4 /* ID Register (RO, 8-bit) */ +#define AD7192_REG_GPOCON 5 /* GPOCON Register (RO, 8-bit) */ +#define AD7192_REG_OFFSET 6 /* Offset Register (RW, 16-bit + * (AD7792)/24-bit (AD7192)) */ +#define AD7192_REG_FULLSALE 7 /* Full-Scale Register + * (RW, 16-bit (AD7792)/24-bit (AD7192)) */ + +/* Communications Register Bit Designations (AD7192_REG_COMM) */ +#define AD7192_COMM_WEN (1 << 7) /* Write Enable */ +#define AD7192_COMM_WRITE (0 << 6) /* Write Operation */ +#define AD7192_COMM_READ (1 << 6) /* Read Operation */ +#define AD7192_COMM_ADDR(x) (((x) & 0x7) << 3) /* Register Address */ +#define AD7192_COMM_CREAD (1 << 2) /* Continuous Read of Data Register */ + +/* Status Register Bit Designations (AD7192_REG_STAT) */ +#define AD7192_STAT_RDY (1 << 7) /* Ready */ +#define AD7192_STAT_ERR (1 << 6) /* Error (Overrange, Underrange) */ +#define AD7192_STAT_NOREF (1 << 5) /* Error no external reference */ +#define AD7192_STAT_PARITY (1 << 4) /* Parity */ +#define AD7192_STAT_CH3 (1 << 2) /* Channel 3 */ +#define AD7192_STAT_CH2 (1 << 1) /* Channel 2 */ +#define AD7192_STAT_CH1 (1 << 0) /* Channel 1 */ + +/* Mode Register Bit Designations (AD7192_REG_MODE) */ +#define AD7192_MODE_SEL(x) (((x) & 0x7) << 21) /* Operation Mode Select */ +#define AD7192_MODE_DAT_STA (1 << 20) /* Status Register transmission */ +#define AD7192_MODE_CLKSRC(x) (((x) & 0x3) << 18) /* Clock Source Select */ +#define AD7192_MODE_SINC3 (1 << 15) /* SINC3 Filter Select */ +#define AD7192_MODE_ACX (1 << 14) /* AC excitation enable(AD7195 only)*/ +#define AD7192_MODE_ENPAR (1 << 13) /* Parity Enable */ +#define AD7192_MODE_CLKDIV (1 << 12) /* Clock divide by 2 (AD7190/2 only)*/ +#define AD7192_MODE_SCYCLE (1 << 11) /* Single cycle conversion */ +#define AD7192_MODE_REJ60 (1 << 10) /* 50/60Hz notch filter */ +#define AD7192_MODE_RATE(x) ((x) & 0x3FF) /* Filter Update Rate Select */ + +/* Mode Register: AD7192_MODE_SEL options */ +#define AD7192_MODE_CONT 0 /* Continuous Conversion Mode */ +#define AD7192_MODE_SINGLE 1 /* Single Conversion Mode */ +#define AD7192_MODE_IDLE 2 /* Idle Mode */ +#define AD7192_MODE_PWRDN 3 /* Power-Down Mode */ +#define AD7192_MODE_CAL_INT_ZERO 4 /* Internal Zero-Scale Calibration */ +#define AD7192_MODE_CAL_INT_FULL 5 /* Internal Full-Scale Calibration */ +#define AD7192_MODE_CAL_SYS_ZERO 6 /* System Zero-Scale Calibration */ +#define AD7192_MODE_CAL_SYS_FULL 7 /* System Full-Scale Calibration */ + +/* Mode Register: AD7192_MODE_CLKSRC options */ +#define AD7192_CLK_EXT_MCLK1_2 0 /* External 4.92 MHz Clock connected + * from MCLK1 to MCLK2 */ +#define AD7192_CLK_EXT_MCLK2 1 /* External Clock applied to MCLK2 */ +#define AD7192_CLK_INT 2 /* Internal 4.92 MHz Clock not + * available at the MCLK2 pin */ +#define AD7192_CLK_INT_CO 3 /* Internal 4.92 MHz Clock available + * at the MCLK2 pin */ + + +/* Configuration Register Bit Designations (AD7192_REG_CONF) */ + +#define AD7192_CONF_CHOP (1 << 23) /* CHOP enable */ +#define AD7192_CONF_REFSEL (1 << 20) /* REFIN1/REFIN2 Reference Select */ +#define AD7192_CONF_CHAN(x) (((x) & 0xFF) << 8) /* Channel select */ +#define AD7192_CONF_BURN (1 << 7) /* Burnout current enable */ +#define AD7192_CONF_REFDET (1 << 6) /* Reference detect enable */ +#define AD7192_CONF_BUF (1 << 4) /* Buffered Mode Enable */ +#define AD7192_CONF_UNIPOLAR (1 << 3) /* Unipolar/Bipolar Enable */ +#define AD7192_CONF_GAIN(x) ((x) & 0x7) /* Gain Select */ + +#define AD7192_CH_AIN1P_AIN2M 0 /* AIN1(+) - AIN2(-) */ +#define AD7192_CH_AIN3P_AIN4M 1 /* AIN3(+) - AIN4(-) */ +#define AD7192_CH_TEMP 2 /* Temp Sensor */ +#define AD7192_CH_AIN2P_AIN2M 3 /* AIN2(+) - AIN2(-) */ +#define AD7192_CH_AIN1 4 /* AIN1 - AINCOM */ +#define AD7192_CH_AIN2 5 /* AIN2 - AINCOM */ +#define AD7192_CH_AIN3 6 /* AIN3 - AINCOM */ +#define AD7192_CH_AIN4 7 /* AIN4 - AINCOM */ + +/* ID Register Bit Designations (AD7192_REG_ID) */ +#define ID_AD7190 0x4 +#define ID_AD7192 0x0 +#define ID_AD7195 0x6 +#define AD7192_ID_MASK 0x0F + +/* GPOCON Register Bit Designations (AD7192_REG_GPOCON) */ +#define AD7192_GPOCON_BPDSW (1 << 6) /* Bridge power-down switch enable */ +#define AD7192_GPOCON_GP32EN (1 << 5) /* Digital Output P3 and P2 enable */ +#define AD7192_GPOCON_GP10EN (1 << 4) /* Digital Output P1 and P0 enable */ +#define AD7192_GPOCON_P3DAT (1 << 3) /* P3 state */ +#define AD7192_GPOCON_P2DAT (1 << 2) /* P2 state */ +#define AD7192_GPOCON_P1DAT (1 << 1) /* P1 state */ +#define AD7192_GPOCON_P0DAT (1 << 0) /* P0 state */ + +#define AD7192_INT_FREQ_MHz 4915200 + +/* NOTE: + * The AD7190/2/5 features a dual use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use spi bus locking. + * + * The DOUT/RDY output must also be wired to an interrupt capable GPIO. + */ + +struct ad7192_state { + struct spi_device *spi; + struct iio_trigger *trig; + struct regulator *reg; + struct ad7192_platform_data *pdata; + wait_queue_head_t wq_data_avail; + bool done; + bool irq_dis; + u16 int_vref_mv; + u32 mclk; + u32 f_order; + u32 mode; + u32 conf; + u32 scale_avail[8][2]; + long available_scan_masks[9]; + u8 gpocon; + u8 devid; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + u8 data[4] ____cacheline_aligned; +}; + +static int __ad7192_write_reg(struct ad7192_state *st, bool locked, + bool cs_change, unsigned char reg, + unsigned size, unsigned val) +{ + u8 *data = st->data; + struct spi_transfer t = { + .tx_buf = data, + .len = size + 1, + .cs_change = cs_change, + }; + struct spi_message m; + + data[0] = AD7192_COMM_WRITE | AD7192_COMM_ADDR(reg); + + switch (size) { + case 3: + data[1] = val >> 16; + data[2] = val >> 8; + data[3] = val; + break; + case 2: + data[1] = val >> 8; + data[2] = val; + break; + case 1: + data[1] = val; + break; + default: + return -EINVAL; + } + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + if (locked) + return spi_sync_locked(st->spi, &m); + else + return spi_sync(st->spi, &m); +} + +static int ad7192_write_reg(struct ad7192_state *st, + unsigned reg, unsigned size, unsigned val) +{ + return __ad7192_write_reg(st, false, false, reg, size, val); +} + +static int __ad7192_read_reg(struct ad7192_state *st, bool locked, + bool cs_change, unsigned char reg, + int *val, unsigned size) +{ + u8 *data = st->data; + int ret; + struct spi_transfer t[] = { + { + .tx_buf = data, + .len = 1, + }, { + .rx_buf = data, + .len = size, + .cs_change = cs_change, + }, + }; + struct spi_message m; + + data[0] = AD7192_COMM_READ | AD7192_COMM_ADDR(reg); + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + if (locked) + ret = spi_sync_locked(st->spi, &m); + else + ret = spi_sync(st->spi, &m); + + if (ret < 0) + return ret; + + switch (size) { + case 3: + *val = data[0] << 16 | data[1] << 8 | data[2]; + break; + case 2: + *val = data[0] << 8 | data[1]; + break; + case 1: + *val = data[0]; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ad7192_read_reg(struct ad7192_state *st, + unsigned reg, int *val, unsigned size) +{ + return __ad7192_read_reg(st, 0, 0, reg, val, size); +} + +static int ad7192_read(struct ad7192_state *st, unsigned ch, + unsigned len, int *val) +{ + int ret; + st->conf = (st->conf & ~AD7192_CONF_CHAN(-1)) | + AD7192_CONF_CHAN(1 << ch); + st->mode = (st->mode & ~AD7192_MODE_SEL(-1)) | + AD7192_MODE_SEL(AD7192_MODE_SINGLE); + + ad7192_write_reg(st, AD7192_REG_CONF, 3, st->conf); + + spi_bus_lock(st->spi->master); + st->done = false; + + ret = __ad7192_write_reg(st, 1, 1, AD7192_REG_MODE, 3, st->mode); + if (ret < 0) + goto out; + + st->irq_dis = false; + enable_irq(st->spi->irq); + wait_event_interruptible(st->wq_data_avail, st->done); + + ret = __ad7192_read_reg(st, 1, 0, AD7192_REG_DATA, val, len); +out: + spi_bus_unlock(st->spi->master); + + return ret; +} + +static int ad7192_calibrate(struct ad7192_state *st, unsigned mode, unsigned ch) +{ + int ret; + + st->conf = (st->conf & ~AD7192_CONF_CHAN(-1)) | + AD7192_CONF_CHAN(1 << ch); + st->mode = (st->mode & ~AD7192_MODE_SEL(-1)) | AD7192_MODE_SEL(mode); + + ad7192_write_reg(st, AD7192_REG_CONF, 3, st->conf); + + spi_bus_lock(st->spi->master); + st->done = false; + + ret = __ad7192_write_reg(st, 1, 1, AD7192_REG_MODE, 3, + (st->devid != ID_AD7195) ? + st->mode | AD7192_MODE_CLKDIV : + st->mode); + if (ret < 0) + goto out; + + st->irq_dis = false; + enable_irq(st->spi->irq); + wait_event_interruptible(st->wq_data_avail, st->done); + + st->mode = (st->mode & ~AD7192_MODE_SEL(-1)) | + AD7192_MODE_SEL(AD7192_MODE_IDLE); + + ret = __ad7192_write_reg(st, 1, 0, AD7192_REG_MODE, 3, st->mode); +out: + spi_bus_unlock(st->spi->master); + + return ret; +} + +static const u8 ad7192_calib_arr[8][2] = { + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN1}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN1}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN2}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN2}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN3}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN3}, + {AD7192_MODE_CAL_INT_ZERO, AD7192_CH_AIN4}, + {AD7192_MODE_CAL_INT_FULL, AD7192_CH_AIN4} +}; + +static int ad7192_calibrate_all(struct ad7192_state *st) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(ad7192_calib_arr); i++) { + ret = ad7192_calibrate(st, ad7192_calib_arr[i][0], + ad7192_calib_arr[i][1]); + if (ret) + goto out; + } + + return 0; +out: + dev_err(&st->spi->dev, "Calibration failed\n"); + return ret; +} + +static int ad7192_setup(struct ad7192_state *st) +{ + struct iio_dev *indio_dev = spi_get_drvdata(st->spi); + struct ad7192_platform_data *pdata = st->pdata; + unsigned long long scale_uv; + int i, ret, id; + u8 ones[6]; + + /* reset the serial interface */ + memset(&ones, 0xFF, 6); + ret = spi_write(st->spi, &ones, 6); + if (ret < 0) + goto out; + msleep(1); /* Wait for at least 500us */ + + /* write/read test for device presence */ + ret = ad7192_read_reg(st, AD7192_REG_ID, &id, 1); + if (ret) + goto out; + + id &= AD7192_ID_MASK; + + if (id != st->devid) + dev_warn(&st->spi->dev, "device ID query failed (0x%X)\n", id); + + switch (pdata->clock_source_sel) { + case AD7192_CLK_EXT_MCLK1_2: + case AD7192_CLK_EXT_MCLK2: + st->mclk = AD7192_INT_FREQ_MHz; + break; + case AD7192_CLK_INT: + case AD7192_CLK_INT_CO: + if (pdata->ext_clk_Hz) + st->mclk = pdata->ext_clk_Hz; + else + st->mclk = AD7192_INT_FREQ_MHz; + break; + default: + ret = -EINVAL; + goto out; + } + + st->mode = AD7192_MODE_SEL(AD7192_MODE_IDLE) | + AD7192_MODE_CLKSRC(pdata->clock_source_sel) | + AD7192_MODE_RATE(480); + + st->conf = AD7192_CONF_GAIN(0); + + if (pdata->rej60_en) + st->mode |= AD7192_MODE_REJ60; + + if (pdata->sinc3_en) + st->mode |= AD7192_MODE_SINC3; + + if (pdata->refin2_en && (st->devid != ID_AD7195)) + st->conf |= AD7192_CONF_REFSEL; + + if (pdata->chop_en) { + st->conf |= AD7192_CONF_CHOP; + if (pdata->sinc3_en) + st->f_order = 3; /* SINC 3rd order */ + else + st->f_order = 4; /* SINC 4th order */ + } else { + st->f_order = 1; + } + + if (pdata->buf_en) + st->conf |= AD7192_CONF_BUF; + + if (pdata->unipolar_en) + st->conf |= AD7192_CONF_UNIPOLAR; + + if (pdata->burnout_curr_en) + st->conf |= AD7192_CONF_BURN; + + ret = ad7192_write_reg(st, AD7192_REG_MODE, 3, st->mode); + if (ret) + goto out; + + ret = ad7192_write_reg(st, AD7192_REG_CONF, 3, st->conf); + if (ret) + goto out; + + ret = ad7192_calibrate_all(st); + if (ret) + goto out; + + /* Populate available ADC input ranges */ + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) { + scale_uv = ((u64)st->int_vref_mv * 100000000) + >> (indio_dev->channels[0].scan_type.realbits - + ((st->conf & AD7192_CONF_UNIPOLAR) ? 0 : 1)); + scale_uv >>= i; + + st->scale_avail[i][1] = do_div(scale_uv, 100000000) * 10; + st->scale_avail[i][0] = scale_uv; + } + + return 0; +out: + dev_err(&st->spi->dev, "setup failed\n"); + return ret; +} + +static int ad7192_ring_preenable(struct iio_dev *indio_dev) +{ + struct ad7192_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + size_t d_size; + unsigned channel; + + if (bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + return -EINVAL; + + channel = find_first_bit(indio_dev->active_scan_mask, + indio_dev->masklength); + + d_size = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * + indio_dev->channels[0].scan_type.storagebits / 8; + + if (ring->scan_timestamp) { + d_size += sizeof(s64); + + if (d_size % sizeof(s64)) + d_size += sizeof(s64) - (d_size % sizeof(s64)); + } + + if (indio_dev->buffer->access->set_bytes_per_datum) + indio_dev->buffer->access-> + set_bytes_per_datum(indio_dev->buffer, d_size); + + st->mode = (st->mode & ~AD7192_MODE_SEL(-1)) | + AD7192_MODE_SEL(AD7192_MODE_CONT); + st->conf = (st->conf & ~AD7192_CONF_CHAN(-1)) | + AD7192_CONF_CHAN(1 << indio_dev->channels[channel].address); + + ad7192_write_reg(st, AD7192_REG_CONF, 3, st->conf); + + spi_bus_lock(st->spi->master); + __ad7192_write_reg(st, 1, 1, AD7192_REG_MODE, 3, st->mode); + + st->irq_dis = false; + enable_irq(st->spi->irq); + + return 0; +} + +static int ad7192_ring_postdisable(struct iio_dev *indio_dev) +{ + struct ad7192_state *st = iio_priv(indio_dev); + + st->mode = (st->mode & ~AD7192_MODE_SEL(-1)) | + AD7192_MODE_SEL(AD7192_MODE_IDLE); + + st->done = false; + wait_event_interruptible(st->wq_data_avail, st->done); + + if (!st->irq_dis) + disable_irq_nosync(st->spi->irq); + + __ad7192_write_reg(st, 1, 0, AD7192_REG_MODE, 3, st->mode); + + return spi_bus_unlock(st->spi->master); +} + +/** + * ad7192_trigger_handler() bh of trigger launched polling to ring buffer + **/ +static irqreturn_t ad7192_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct iio_buffer *ring = indio_dev->buffer; + struct ad7192_state *st = iio_priv(indio_dev); + s64 dat64[2]; + s32 *dat32 = (s32 *)dat64; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + __ad7192_read_reg(st, 1, 1, AD7192_REG_DATA, + dat32, + indio_dev->channels[0].scan_type.realbits/8); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + dat64[1] = pf->timestamp; + + ring->access->store_to(ring, (u8 *)dat64, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + st->irq_dis = false; + enable_irq(st->spi->irq); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ad7192_ring_setup_ops = { + .preenable = &ad7192_ring_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &ad7192_ring_postdisable, +}; + +static int ad7192_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + int ret; + + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + goto error_ret; + } + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &ad7192_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "ad7192_consumer%d", + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_deallocate_sw_rb; + } + + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &ad7192_ring_setup_ops; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_deallocate_sw_rb: + iio_sw_rb_free(indio_dev->buffer); +error_ret: + return ret; +} + +static void ad7192_ring_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +/** + * ad7192_data_rdy_trig_poll() the event handler for the data rdy trig + **/ +static irqreturn_t ad7192_data_rdy_trig_poll(int irq, void *private) +{ + struct ad7192_state *st = iio_priv(private); + + st->done = true; + wake_up_interruptible(&st->wq_data_avail); + disable_irq_nosync(irq); + st->irq_dis = true; + iio_trigger_poll(st->trig, iio_get_time_ns()); + + return IRQ_HANDLED; +} + +static struct iio_trigger_ops ad7192_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int ad7192_probe_trigger(struct iio_dev *indio_dev) +{ + struct ad7192_state *st = iio_priv(indio_dev); + int ret; + + st->trig = iio_allocate_trigger("%s-dev%d", + spi_get_device_id(st->spi)->name, + indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st->trig->ops = &ad7192_trigger_ops; + ret = request_irq(st->spi->irq, + ad7192_data_rdy_trig_poll, + IRQF_TRIGGER_LOW, + spi_get_device_id(st->spi)->name, + indio_dev); + if (ret) + goto error_free_trig; + + disable_irq_nosync(st->spi->irq); + st->irq_dis = true; + st->trig->dev.parent = &st->spi->dev; + st->trig->private_data = indio_dev; + + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->spi->irq, indio_dev); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +static void ad7192_remove_trigger(struct iio_dev *indio_dev) +{ + struct ad7192_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->spi->irq, indio_dev); + iio_free_trigger(st->trig); +} + +static ssize_t ad7192_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->mclk / + (st->f_order * 1024 * AD7192_MODE_RATE(st->mode))); +} + +static ssize_t ad7192_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7192_state *st = iio_priv(indio_dev); + unsigned long lval; + int div, ret; + + ret = strict_strtoul(buf, 10, &lval); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + div = st->mclk / (lval * st->f_order * 1024); + if (div < 1 || div > 1023) { + ret = -EINVAL; + goto out; + } + + st->mode &= ~AD7192_MODE_RATE(-1); + st->mode |= AD7192_MODE_RATE(div); + ad7192_write_reg(st, AD7192_REG_MODE, 3, st->mode); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ad7192_read_frequency, + ad7192_write_frequency); + + +static ssize_t ad7192_show_scale_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7192_state *st = iio_priv(indio_dev); + int i, len = 0; + + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + len += sprintf(buf + len, "%d.%09u ", st->scale_avail[i][0], + st->scale_avail[i][1]); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR_NAMED(in_v_m_v_scale_available, + in_voltage-voltage_scale_available, + S_IRUGO, ad7192_show_scale_available, NULL, 0); + +static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO, + ad7192_show_scale_available, NULL, 0); + +static ssize_t ad7192_show_ac_excitation(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", !!(st->mode & AD7192_MODE_ACX)); +} + +static ssize_t ad7192_show_bridge_switch(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7192_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", !!(st->gpocon & AD7192_GPOCON_BPDSW)); +} + +static ssize_t ad7192_set(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7192_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + bool val; + + ret = strtobool(buf, &val); + if (ret < 0) + return ret; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + switch ((u32) this_attr->address) { + case AD7192_REG_GPOCON: + if (val) + st->gpocon |= AD7192_GPOCON_BPDSW; + else + st->gpocon &= ~AD7192_GPOCON_BPDSW; + + ad7192_write_reg(st, AD7192_REG_GPOCON, 1, st->gpocon); + break; + case AD7192_REG_MODE: + if (val) + st->mode |= AD7192_MODE_ACX; + else + st->mode &= ~AD7192_MODE_ACX; + + ad7192_write_reg(st, AD7192_REG_GPOCON, 3, st->mode); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(bridge_switch_en, S_IRUGO | S_IWUSR, + ad7192_show_bridge_switch, ad7192_set, + AD7192_REG_GPOCON); + +static IIO_DEVICE_ATTR(ac_excitation_en, S_IRUGO | S_IWUSR, + ad7192_show_ac_excitation, ad7192_set, + AD7192_REG_MODE); + +static struct attribute *ad7192_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_v_m_v_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + &iio_dev_attr_bridge_switch_en.dev_attr.attr, + &iio_dev_attr_ac_excitation_en.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7192_attribute_group = { + .attrs = ad7192_attributes, +}; + +static struct attribute *ad7195_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_v_m_v_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + &iio_dev_attr_bridge_switch_en.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7195_attribute_group = { + .attrs = ad7195_attributes, +}; + +static int ad7192_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7192_state *st = iio_priv(indio_dev); + int ret, smpl = 0; + bool unipolar = !!(st->conf & AD7192_CONF_UNIPOLAR); + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad7192_read(st, chan->address, + chan->scan_type.realbits / 8, &smpl); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + *val = (smpl >> chan->scan_type.shift) & + ((1 << (chan->scan_type.realbits)) - 1); + + switch (chan->type) { + case IIO_VOLTAGE: + if (!unipolar) + *val -= (1 << (chan->scan_type.realbits - 1)); + break; + case IIO_TEMP: + *val -= 0x800000; + *val /= 2815; /* temp Kelvin */ + *val -= 273; /* temp Celsius */ + break; + default: + return -EINVAL; + } + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + mutex_lock(&indio_dev->mlock); + *val = st->scale_avail[AD7192_CONF_GAIN(st->conf)][0]; + *val2 = st->scale_avail[AD7192_CONF_GAIN(st->conf)][1]; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = 1000; + return IIO_VAL_INT; + default: + return -EINVAL; + } + } + + return -EINVAL; +} + +static int ad7192_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7192_state *st = iio_priv(indio_dev); + int ret, i; + unsigned int tmp; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + if (val2 == st->scale_avail[i][1]) { + tmp = st->conf; + st->conf &= ~AD7192_CONF_GAIN(-1); + st->conf |= AD7192_CONF_GAIN(i); + + if (tmp != st->conf) { + ad7192_write_reg(st, AD7192_REG_CONF, + 3, st->conf); + ad7192_calibrate_all(st); + } + ret = 0; + } + + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad7192_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + if (indio_dev->trig != trig) + return -EINVAL; + + return 0; +} + +static int ad7192_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static const struct iio_info ad7192_info = { + .read_raw = &ad7192_read_raw, + .write_raw = &ad7192_write_raw, + .write_raw_get_fmt = &ad7192_write_raw_get_fmt, + .attrs = &ad7192_attribute_group, + .validate_trigger = ad7192_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad7195_info = { + .read_raw = &ad7192_read_raw, + .write_raw = &ad7192_write_raw, + .write_raw_get_fmt = &ad7192_write_raw_get_fmt, + .attrs = &ad7195_attribute_group, + .validate_trigger = ad7192_validate_trigger, + .driver_module = THIS_MODULE, +}; + +#define AD7192_CHAN_DIFF(_chan, _chan2, _name, _address, _si) \ + { .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .extend_name = _name, \ + .channel = _chan, \ + .channel2 = _chan2, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .address = _address, \ + .scan_index = _si, \ + .scan_type = IIO_ST('s', 24, 32, 0)} + +#define AD7192_CHAN(_chan, _address, _si) \ + { .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _chan, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .address = _address, \ + .scan_index = _si, \ + .scan_type = IIO_ST('s', 24, 32, 0)} + +#define AD7192_CHAN_TEMP(_chan, _address, _si) \ + { .type = IIO_TEMP, \ + .indexed = 1, \ + .channel = _chan, \ + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + .address = _address, \ + .scan_index = _si, \ + .scan_type = IIO_ST('s', 24, 32, 0)} + +static struct iio_chan_spec ad7192_channels[] = { + AD7192_CHAN_DIFF(1, 2, NULL, AD7192_CH_AIN1P_AIN2M, 0), + AD7192_CHAN_DIFF(3, 4, NULL, AD7192_CH_AIN3P_AIN4M, 1), + AD7192_CHAN_TEMP(0, AD7192_CH_TEMP, 2), + AD7192_CHAN_DIFF(2, 2, "shorted", AD7192_CH_AIN2P_AIN2M, 3), + AD7192_CHAN(1, AD7192_CH_AIN1, 4), + AD7192_CHAN(2, AD7192_CH_AIN2, 5), + AD7192_CHAN(3, AD7192_CH_AIN3, 6), + AD7192_CHAN(4, AD7192_CH_AIN4, 7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static int __devinit ad7192_probe(struct spi_device *spi) +{ + struct ad7192_platform_data *pdata = spi->dev.platform_data; + struct ad7192_state *st; + struct iio_dev *indio_dev; + int ret, i , voltage_uv = 0; + + if (!pdata) { + dev_err(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + if (!spi->irq) { + dev_err(&spi->dev, "no IRQ?\n"); + return -ENODEV; + } + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(st->reg); + } + + st->pdata = pdata; + + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, "reference voltage undefined\n"); + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + st->devid = spi_get_device_id(spi)->driver_data; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7192_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7192_channels); + indio_dev->available_scan_masks = st->available_scan_masks; + if (st->devid == ID_AD7195) + indio_dev->info = &ad7195_info; + else + indio_dev->info = &ad7192_info; + + for (i = 0; i < indio_dev->num_channels; i++) + st->available_scan_masks[i] = (1 << i) | (1 << + indio_dev->channels[indio_dev->num_channels - 1]. + scan_index); + + init_waitqueue_head(&st->wq_data_avail); + + ret = ad7192_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_disable_reg; + + ret = ad7192_probe_trigger(indio_dev); + if (ret) + goto error_ring_cleanup; + + ret = iio_buffer_register(indio_dev, + indio_dev->channels, + indio_dev->num_channels); + if (ret) + goto error_remove_trigger; + + ret = ad7192_setup(st); + if (ret) + goto error_unreg_ring; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_unreg_ring; + return 0; + +error_unreg_ring: + iio_buffer_unregister(indio_dev); +error_remove_trigger: + ad7192_remove_trigger(indio_dev); +error_ring_cleanup: + ad7192_ring_cleanup(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_free_device(indio_dev); + + return ret; +} + +static int ad7192_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7192_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); + ad7192_remove_trigger(indio_dev); + ad7192_ring_cleanup(indio_dev); + + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + + return 0; +} + +static const struct spi_device_id ad7192_id[] = { + {"ad7190", ID_AD7190}, + {"ad7192", ID_AD7192}, + {"ad7195", ID_AD7195}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7192_id); + +static struct spi_driver ad7192_driver = { + .driver = { + .name = "ad7192", + .owner = THIS_MODULE, + }, + .probe = ad7192_probe, + .remove = __devexit_p(ad7192_remove), + .id_table = ad7192_id, +}; +module_spi_driver(ad7192_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7190, AD7192, AD7195 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7192.h b/drivers/staging/iio/adc/ad7192.h new file mode 100644 index 00000000..a0a5b61a --- /dev/null +++ b/drivers/staging/iio/adc/ad7192.h @@ -0,0 +1,47 @@ +/* + * AD7190 AD7192 AD7195 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef IIO_ADC_AD7192_H_ +#define IIO_ADC_AD7192_H_ + +/* + * TODO: struct ad7192_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad7192_platform_data - platform/board specific information + * @vref_mv: the external reference voltage in millivolt + * @clock_source_sel: [0..3] + * 0 External 4.92 MHz clock connected from MCLK1 to MCLK2 + * 1 External Clock applied to MCLK2 + * 2 Internal 4.92 MHz Clock not available at the MCLK2 pin + * 3 Internal 4.92 MHz Clock available at the MCLK2 pin + * @ext_clk_Hz: the external clock frequency in Hz, if not set + * the driver uses the internal clock (16.776 MHz) + * @refin2_en: REFIN1/REFIN2 Reference Select (AD7190/2 only) + * @rej60_en: 50/60Hz notch filter enable + * @sinc3_en: SINC3 filter enable (default SINC4) + * @chop_en: CHOP mode enable + * @buf_en: buffered input mode enable + * @unipolar_en: unipolar mode enable + * @burnout_curr_en: constant current generators on AIN(+|-) enable + */ + +struct ad7192_platform_data { + u16 vref_mv; + u8 clock_source_sel; + u32 ext_clk_Hz; + bool refin2_en; + bool rej60_en; + bool sinc3_en; + bool chop_en; + bool buf_en; + bool unipolar_en; + bool burnout_curr_en; +}; + +#endif /* IIO_ADC_AD7192_H_ */ diff --git a/drivers/staging/iio/adc/ad7280a.c b/drivers/staging/iio/adc/ad7280a.c new file mode 100644 index 00000000..7dbd6812 --- /dev/null +++ b/drivers/staging/iio/adc/ad7280a.c @@ -0,0 +1,988 @@ +/* + * AD7280A Lithium Ion Battery Monitoring System + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" + +#include "ad7280a.h" + +/* Registers */ +#define AD7280A_CELL_VOLTAGE_1 0x0 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_2 0x1 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_3 0x2 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_4 0x3 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_5 0x4 /* D11 to D0, Read only */ +#define AD7280A_CELL_VOLTAGE_6 0x5 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_1 0x6 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_2 0x7 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_3 0x8 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_4 0x9 /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_5 0xA /* D11 to D0, Read only */ +#define AD7280A_AUX_ADC_6 0xB /* D11 to D0, Read only */ +#define AD7280A_SELF_TEST 0xC /* D11 to D0, Read only */ +#define AD7280A_CONTROL_HB 0xD /* D15 to D8, Read/write */ +#define AD7280A_CONTROL_LB 0xE /* D7 to D0, Read/write */ +#define AD7280A_CELL_OVERVOLTAGE 0xF /* D7 to D0, Read/write */ +#define AD7280A_CELL_UNDERVOLTAGE 0x10 /* D7 to D0, Read/write */ +#define AD7280A_AUX_ADC_OVERVOLTAGE 0x11 /* D7 to D0, Read/write */ +#define AD7280A_AUX_ADC_UNDERVOLTAGE 0x12 /* D7 to D0, Read/write */ +#define AD7280A_ALERT 0x13 /* D7 to D0, Read/write */ +#define AD7280A_CELL_BALANCE 0x14 /* D7 to D0, Read/write */ +#define AD7280A_CB1_TIMER 0x15 /* D7 to D0, Read/write */ +#define AD7280A_CB2_TIMER 0x16 /* D7 to D0, Read/write */ +#define AD7280A_CB3_TIMER 0x17 /* D7 to D0, Read/write */ +#define AD7280A_CB4_TIMER 0x18 /* D7 to D0, Read/write */ +#define AD7280A_CB5_TIMER 0x19 /* D7 to D0, Read/write */ +#define AD7280A_CB6_TIMER 0x1A /* D7 to D0, Read/write */ +#define AD7280A_PD_TIMER 0x1B /* D7 to D0, Read/write */ +#define AD7280A_READ 0x1C /* D7 to D0, Read/write */ +#define AD7280A_CNVST_CONTROL 0x1D /* D7 to D0, Read/write */ + +/* Bits and Masks */ +#define AD7280A_CTRL_HB_CONV_INPUT_ALL (0 << 6) +#define AD7280A_CTRL_HB_CONV_INPUT_6CELL_AUX1_3_4 (1 << 6) +#define AD7280A_CTRL_HB_CONV_INPUT_6CELL (2 << 6) +#define AD7280A_CTRL_HB_CONV_INPUT_SELF_TEST (3 << 6) +#define AD7280A_CTRL_HB_CONV_RES_READ_ALL (0 << 4) +#define AD7280A_CTRL_HB_CONV_RES_READ_6CELL_AUX1_3_4 (1 << 4) +#define AD7280A_CTRL_HB_CONV_RES_READ_6CELL (2 << 4) +#define AD7280A_CTRL_HB_CONV_RES_READ_NO (3 << 4) +#define AD7280A_CTRL_HB_CONV_START_CNVST (0 << 3) +#define AD7280A_CTRL_HB_CONV_START_CS (1 << 3) +#define AD7280A_CTRL_HB_CONV_AVG_DIS (0 << 1) +#define AD7280A_CTRL_HB_CONV_AVG_2 (1 << 1) +#define AD7280A_CTRL_HB_CONV_AVG_4 (2 << 1) +#define AD7280A_CTRL_HB_CONV_AVG_8 (3 << 1) +#define AD7280A_CTRL_HB_CONV_AVG(x) ((x) << 1) +#define AD7280A_CTRL_HB_PWRDN_SW (1 << 0) + +#define AD7280A_CTRL_LB_SWRST (1 << 7) +#define AD7280A_CTRL_LB_ACQ_TIME_400ns (0 << 5) +#define AD7280A_CTRL_LB_ACQ_TIME_800ns (1 << 5) +#define AD7280A_CTRL_LB_ACQ_TIME_1200ns (2 << 5) +#define AD7280A_CTRL_LB_ACQ_TIME_1600ns (3 << 5) +#define AD7280A_CTRL_LB_ACQ_TIME(x) ((x) << 5) +#define AD7280A_CTRL_LB_MUST_SET (1 << 4) +#define AD7280A_CTRL_LB_THERMISTOR_EN (1 << 3) +#define AD7280A_CTRL_LB_LOCK_DEV_ADDR (1 << 2) +#define AD7280A_CTRL_LB_INC_DEV_ADDR (1 << 1) +#define AD7280A_CTRL_LB_DAISY_CHAIN_RB_EN (1 << 0) + +#define AD7280A_ALERT_GEN_STATIC_HIGH (1 << 6) +#define AD7280A_ALERT_RELAY_SIG_CHAIN_DOWN (3 << 6) + +#define AD7280A_ALL_CELLS (0xAD << 16) + +#define AD7280A_MAX_SPI_CLK_Hz 700000 /* < 1MHz */ +#define AD7280A_MAX_CHAIN 8 +#define AD7280A_CELLS_PER_DEV 6 +#define AD7280A_BITS 12 +#define AD7280A_NUM_CH (AD7280A_AUX_ADC_6 - \ + AD7280A_CELL_VOLTAGE_1 + 1) + +#define AD7280A_DEVADDR_MASTER 0 +#define AD7280A_DEVADDR_ALL 0x1F +/* 5-bit device address is sent LSB first */ +#define AD7280A_DEVADDR(addr) (((addr & 0x1) << 4) | ((addr & 0x2) << 3) | \ + (addr & 0x4) | ((addr & 0x8) >> 3) | \ + ((addr & 0x10) >> 4)) + +/* During a read a valid write is mandatory. + * So writing to the highest available address (Address 0x1F) + * and setting the address all parts bit to 0 is recommended + * So the TXVAL is AD7280A_DEVADDR_ALL + CRC + */ +#define AD7280A_READ_TXVAL 0xF800030A + +/* + * AD7280 CRC + * + * P(x) = x^8 + x^5 + x^3 + x^2 + x^1 + x^0 = 0b100101111 => 0x2F + */ +#define POLYNOM 0x2F +#define POLYNOM_ORDER 8 +#define HIGHBIT 1 << (POLYNOM_ORDER - 1); + +struct ad7280_state { + struct spi_device *spi; + struct iio_chan_spec *channels; + struct iio_dev_attr *iio_attr; + int slave_num; + int scan_cnt; + int readback_delay_us; + unsigned char crc_tab[256]; + unsigned char ctrl_hb; + unsigned char ctrl_lb; + unsigned char cell_threshhigh; + unsigned char cell_threshlow; + unsigned char aux_threshhigh; + unsigned char aux_threshlow; + unsigned char cb_mask[AD7280A_MAX_CHAIN]; +}; + +static void ad7280_crc8_build_table(unsigned char *crc_tab) +{ + unsigned char bit, crc; + int cnt, i; + + for (cnt = 0; cnt < 256; cnt++) { + crc = cnt; + for (i = 0; i < 8; i++) { + bit = crc & HIGHBIT; + crc <<= 1; + if (bit) + crc ^= POLYNOM; + } + crc_tab[cnt] = crc; + } +} + +static unsigned char ad7280_calc_crc8(unsigned char *crc_tab, unsigned val) +{ + unsigned char crc; + + crc = crc_tab[val >> 16 & 0xFF]; + crc = crc_tab[crc ^ (val >> 8 & 0xFF)]; + + return crc ^ (val & 0xFF); +} + +static int ad7280_check_crc(struct ad7280_state *st, unsigned val) +{ + unsigned char crc = ad7280_calc_crc8(st->crc_tab, val >> 10); + + if (crc != ((val >> 2) & 0xFF)) + return -EIO; + + return 0; +} + +/* After initiating a conversion sequence we need to wait until the + * conversion is done. The delay is typically in the range of 15..30 us + * however depending an the number of devices in the daisy chain and the + * number of averages taken, conversion delays and acquisition time options + * it may take up to 250us, in this case we better sleep instead of busy + * wait. + */ + +static void ad7280_delay(struct ad7280_state *st) +{ + if (st->readback_delay_us < 50) + udelay(st->readback_delay_us); + else + msleep(1); +} + +static int __ad7280_read32(struct spi_device *spi, unsigned *val) +{ + unsigned rx_buf, tx_buf = cpu_to_be32(AD7280A_READ_TXVAL); + int ret; + + struct spi_transfer t = { + .tx_buf = &tx_buf, + .rx_buf = &rx_buf, + .len = 4, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + ret = spi_sync(spi, &m); + if (ret) + return ret; + + *val = be32_to_cpu(rx_buf); + + return 0; +} + +static int ad7280_write(struct ad7280_state *st, unsigned devaddr, + unsigned addr, bool all, unsigned val) +{ + unsigned reg = (devaddr << 27 | addr << 21 | + (val & 0xFF) << 13 | all << 12); + + reg |= ad7280_calc_crc8(st->crc_tab, reg >> 11) << 3 | 0x2; + reg = cpu_to_be32(reg); + + return spi_write(st->spi, ®, 4); +} + +static int ad7280_read(struct ad7280_state *st, unsigned devaddr, + unsigned addr) +{ + int ret; + unsigned tmp; + + /* turns off the read operation on all parts */ + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_NO | + st->ctrl_hb); + if (ret) + return ret; + + /* turns on the read operation on the addressed part */ + ret = ad7280_write(st, devaddr, AD7280A_CONTROL_HB, 0, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_ALL | + st->ctrl_hb); + if (ret) + return ret; + + /* Set register address on the part to be read from */ + ret = ad7280_write(st, devaddr, AD7280A_READ, 0, addr << 2); + if (ret) + return ret; + + __ad7280_read32(st->spi, &tmp); + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (((tmp >> 27) != devaddr) || (((tmp >> 21) & 0x3F) != addr)) + return -EFAULT; + + return (tmp >> 13) & 0xFF; +} + +static int ad7280_read_channel(struct ad7280_state *st, unsigned devaddr, + unsigned addr) +{ + int ret; + unsigned tmp; + + ret = ad7280_write(st, devaddr, AD7280A_READ, 0, addr << 2); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_NO | + st->ctrl_hb); + if (ret) + return ret; + + ret = ad7280_write(st, devaddr, AD7280A_CONTROL_HB, 0, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_ALL | + AD7280A_CTRL_HB_CONV_START_CS | + st->ctrl_hb); + if (ret) + return ret; + + ad7280_delay(st); + + __ad7280_read32(st->spi, &tmp); + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (((tmp >> 27) != devaddr) || (((tmp >> 23) & 0xF) != addr)) + return -EFAULT; + + return (tmp >> 11) & 0xFFF; +} + +static int ad7280_read_all_channels(struct ad7280_state *st, unsigned cnt, + unsigned *array) +{ + int i, ret; + unsigned tmp, sum = 0; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_READ, 1, + AD7280A_CELL_VOLTAGE_1 << 2); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_CONV_INPUT_ALL | + AD7280A_CTRL_HB_CONV_RES_READ_ALL | + AD7280A_CTRL_HB_CONV_START_CS | + st->ctrl_hb); + if (ret) + return ret; + + ad7280_delay(st); + + for (i = 0; i < cnt; i++) { + __ad7280_read32(st->spi, &tmp); + + if (ad7280_check_crc(st, tmp)) + return -EIO; + + if (array) + array[i] = tmp; + /* only sum cell voltages */ + if (((tmp >> 23) & 0xF) <= AD7280A_CELL_VOLTAGE_6) + sum += ((tmp >> 11) & 0xFFF); + } + + return sum; +} + +static int ad7280_chain_setup(struct ad7280_state *st) +{ + unsigned val, n; + int ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_LB, 1, + AD7280A_CTRL_LB_DAISY_CHAIN_RB_EN | + AD7280A_CTRL_LB_LOCK_DEV_ADDR | + AD7280A_CTRL_LB_MUST_SET | + AD7280A_CTRL_LB_SWRST | + st->ctrl_lb); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_LB, 1, + AD7280A_CTRL_LB_DAISY_CHAIN_RB_EN | + AD7280A_CTRL_LB_LOCK_DEV_ADDR | + AD7280A_CTRL_LB_MUST_SET | + st->ctrl_lb); + if (ret) + return ret; + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_READ, 1, + AD7280A_CONTROL_LB << 2); + if (ret) + return ret; + + for (n = 0; n <= AD7280A_MAX_CHAIN; n++) { + __ad7280_read32(st->spi, &val); + if (val == 0) + return n - 1; + + if (ad7280_check_crc(st, val)) + return -EIO; + + if (n != AD7280A_DEVADDR(val >> 27)) + return -EIO; + } + + return -EFAULT; +} + +static ssize_t ad7280_show_balance_sw(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return sprintf(buf, "%d\n", + !!(st->cb_mask[this_attr->address >> 8] & + (1 << ((this_attr->address & 0xFF) + 2)))); +} + +static ssize_t ad7280_store_balance_sw(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + bool readin; + int ret; + unsigned devaddr, ch; + + ret = strtobool(buf, &readin); + if (ret) + return ret; + + devaddr = this_attr->address >> 8; + ch = this_attr->address & 0xFF; + + mutex_lock(&indio_dev->mlock); + if (readin) + st->cb_mask[devaddr] |= 1 << (ch + 2); + else + st->cb_mask[devaddr] &= ~(1 << (ch + 2)); + + ret = ad7280_write(st, devaddr, AD7280A_CELL_BALANCE, + 0, st->cb_mask[devaddr]); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static ssize_t ad7280_show_balance_timer(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + unsigned msecs; + + mutex_lock(&indio_dev->mlock); + ret = ad7280_read(st, this_attr->address >> 8, + this_attr->address & 0xFF); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + msecs = (ret >> 3) * 71500; + + return sprintf(buf, "%d\n", msecs); +} + +static ssize_t ad7280_store_balance_timer(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + val /= 71500; + + if (val > 31) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad7280_write(st, this_attr->address >> 8, + this_attr->address & 0xFF, + 0, (val & 0x1F) << 3); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static struct attribute *ad7280_attributes[AD7280A_MAX_CHAIN * + AD7280A_CELLS_PER_DEV * 2 + 1]; + +static struct attribute_group ad7280_attrs_group = { + .attrs = ad7280_attributes, +}; + +static int ad7280_channel_init(struct ad7280_state *st) +{ + int dev, ch, cnt; + + st->channels = kcalloc((st->slave_num + 1) * 12 + 2, + sizeof(*st->channels), GFP_KERNEL); + if (st->channels == NULL) + return -ENOMEM; + + for (dev = 0, cnt = 0; dev <= st->slave_num; dev++) + for (ch = AD7280A_CELL_VOLTAGE_1; ch <= AD7280A_AUX_ADC_6; ch++, + cnt++) { + if (ch < AD7280A_AUX_ADC_1) { + st->channels[cnt].type = IIO_VOLTAGE; + st->channels[cnt].differential = 1; + st->channels[cnt].channel = (dev * 6) + ch; + st->channels[cnt].channel2 = + st->channels[cnt].channel + 1; + } else { + st->channels[cnt].type = IIO_TEMP; + st->channels[cnt].channel = (dev * 6) + ch - 6; + } + st->channels[cnt].indexed = 1; + st->channels[cnt].info_mask = + IIO_CHAN_INFO_SCALE_SHARED_BIT; + st->channels[cnt].address = + AD7280A_DEVADDR(dev) << 8 | ch; + st->channels[cnt].scan_index = cnt; + st->channels[cnt].scan_type.sign = 'u'; + st->channels[cnt].scan_type.realbits = 12; + st->channels[cnt].scan_type.storagebits = 32; + st->channels[cnt].scan_type.shift = 0; + } + + st->channels[cnt].type = IIO_VOLTAGE; + st->channels[cnt].differential = 1; + st->channels[cnt].channel = 0; + st->channels[cnt].channel2 = dev * 6; + st->channels[cnt].address = AD7280A_ALL_CELLS; + st->channels[cnt].indexed = 1; + st->channels[cnt].info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT; + st->channels[cnt].scan_index = cnt; + st->channels[cnt].scan_type.sign = 'u'; + st->channels[cnt].scan_type.realbits = 32; + st->channels[cnt].scan_type.storagebits = 32; + st->channels[cnt].scan_type.shift = 0; + cnt++; + st->channels[cnt].type = IIO_TIMESTAMP; + st->channels[cnt].channel = -1; + st->channels[cnt].scan_index = cnt; + st->channels[cnt].scan_type.sign = 's'; + st->channels[cnt].scan_type.realbits = 64; + st->channels[cnt].scan_type.storagebits = 64; + st->channels[cnt].scan_type.shift = 0; + + return cnt + 1; +} + +static int ad7280_attr_init(struct ad7280_state *st) +{ + int dev, ch, cnt; + + st->iio_attr = kzalloc(sizeof(*st->iio_attr) * (st->slave_num + 1) * + AD7280A_CELLS_PER_DEV * 2, GFP_KERNEL); + if (st->iio_attr == NULL) + return -ENOMEM; + + for (dev = 0, cnt = 0; dev <= st->slave_num; dev++) + for (ch = AD7280A_CELL_VOLTAGE_1; ch <= AD7280A_CELL_VOLTAGE_6; + ch++, cnt++) { + st->iio_attr[cnt].address = + AD7280A_DEVADDR(dev) << 8 | ch; + st->iio_attr[cnt].dev_attr.attr.mode = + S_IWUSR | S_IRUGO; + st->iio_attr[cnt].dev_attr.show = + ad7280_show_balance_sw; + st->iio_attr[cnt].dev_attr.store = + ad7280_store_balance_sw; + st->iio_attr[cnt].dev_attr.attr.name = + kasprintf(GFP_KERNEL, + "in%d-in%d_balance_switch_en", + (dev * AD7280A_CELLS_PER_DEV) + ch, + (dev * AD7280A_CELLS_PER_DEV) + ch + 1); + ad7280_attributes[cnt] = + &st->iio_attr[cnt].dev_attr.attr; + cnt++; + st->iio_attr[cnt].address = + AD7280A_DEVADDR(dev) << 8 | + (AD7280A_CB1_TIMER + ch); + st->iio_attr[cnt].dev_attr.attr.mode = + S_IWUSR | S_IRUGO; + st->iio_attr[cnt].dev_attr.show = + ad7280_show_balance_timer; + st->iio_attr[cnt].dev_attr.store = + ad7280_store_balance_timer; + st->iio_attr[cnt].dev_attr.attr.name = + kasprintf(GFP_KERNEL, "in%d-in%d_balance_timer", + (dev * AD7280A_CELLS_PER_DEV) + ch, + (dev * AD7280A_CELLS_PER_DEV) + ch + 1); + ad7280_attributes[cnt] = + &st->iio_attr[cnt].dev_attr.attr; + } + + ad7280_attributes[cnt] = NULL; + + return 0; +} + +static ssize_t ad7280_read_channel_config(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned val; + + switch ((u32) this_attr->address) { + case AD7280A_CELL_OVERVOLTAGE: + val = 1000 + (st->cell_threshhigh * 1568) / 100; + break; + case AD7280A_CELL_UNDERVOLTAGE: + val = 1000 + (st->cell_threshlow * 1568) / 100; + break; + case AD7280A_AUX_ADC_OVERVOLTAGE: + val = (st->aux_threshhigh * 196) / 10; + break; + case AD7280A_AUX_ADC_UNDERVOLTAGE: + val = (st->aux_threshlow * 196) / 10; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%d\n", val); +} + +static ssize_t ad7280_write_channel_config(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7280_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + long val; + int ret; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + switch ((u32) this_attr->address) { + case AD7280A_CELL_OVERVOLTAGE: + case AD7280A_CELL_UNDERVOLTAGE: + val = ((val - 1000) * 100) / 1568; /* LSB 15.68mV */ + break; + case AD7280A_AUX_ADC_OVERVOLTAGE: + case AD7280A_AUX_ADC_UNDERVOLTAGE: + val = (val * 10) / 196; /* LSB 19.6mV */ + break; + default: + return -EFAULT; + } + + val = clamp(val, 0L, 0xFFL); + + mutex_lock(&indio_dev->mlock); + switch ((u32) this_attr->address) { + case AD7280A_CELL_OVERVOLTAGE: + st->cell_threshhigh = val; + break; + case AD7280A_CELL_UNDERVOLTAGE: + st->cell_threshlow = val; + break; + case AD7280A_AUX_ADC_OVERVOLTAGE: + st->aux_threshhigh = val; + break; + case AD7280A_AUX_ADC_UNDERVOLTAGE: + st->aux_threshlow = val; + break; + } + + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, + this_attr->address, 1, val); + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static irqreturn_t ad7280_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad7280_state *st = iio_priv(indio_dev); + unsigned *channels; + int i, ret; + + channels = kcalloc(st->scan_cnt, sizeof(*channels), GFP_KERNEL); + if (channels == NULL) + return IRQ_HANDLED; + + ret = ad7280_read_all_channels(st, st->scan_cnt, channels); + if (ret < 0) + goto out; + + for (i = 0; i < st->scan_cnt; i++) { + if (((channels[i] >> 23) & 0xF) <= AD7280A_CELL_VOLTAGE_6) { + if (((channels[i] >> 11) & 0xFFF) >= + st->cell_threshhigh) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, + 1, + 0, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + iio_get_time_ns()); + else if (((channels[i] >> 11) & 0xFFF) <= + st->cell_threshlow) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, + 1, + 0, + IIO_EV_DIR_FALLING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + iio_get_time_ns()); + } else { + if (((channels[i] >> 11) & 0xFFF) >= st->aux_threshhigh) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + else if (((channels[i] >> 11) & 0xFFF) <= + st->aux_threshlow) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } + } + +out: + kfree(channels); + + return IRQ_HANDLED; +} + +static IIO_DEVICE_ATTR_NAMED(in_thresh_low_value, + in_voltage-voltage_thresh_low_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_CELL_UNDERVOLTAGE); + +static IIO_DEVICE_ATTR_NAMED(in_thresh_high_value, + in_voltage-voltage_thresh_high_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_CELL_OVERVOLTAGE); + +static IIO_DEVICE_ATTR(in_temp_thresh_low_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_AUX_ADC_UNDERVOLTAGE); + +static IIO_DEVICE_ATTR(in_temp_thresh_high_value, + S_IRUGO | S_IWUSR, + ad7280_read_channel_config, + ad7280_write_channel_config, + AD7280A_AUX_ADC_OVERVOLTAGE); + + +static struct attribute *ad7280_event_attributes[] = { + &iio_dev_attr_in_thresh_low_value.dev_attr.attr, + &iio_dev_attr_in_thresh_high_value.dev_attr.attr, + &iio_dev_attr_in_temp_thresh_low_value.dev_attr.attr, + &iio_dev_attr_in_temp_thresh_high_value.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7280_event_attrs_group = { + .attrs = ad7280_event_attributes, +}; + +static int ad7280_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7280_state *st = iio_priv(indio_dev); + unsigned int scale_uv; + int ret; + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + if (chan->address == AD7280A_ALL_CELLS) + ret = ad7280_read_all_channels(st, st->scan_cnt, NULL); + else + ret = ad7280_read_channel(st, chan->address >> 8, + chan->address & 0xFF); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if ((chan->address & 0xFF) <= AD7280A_CELL_VOLTAGE_6) + scale_uv = (4000 * 1000) >> AD7280A_BITS; + else + scale_uv = (5000 * 1000) >> AD7280A_BITS; + + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static const struct iio_info ad7280_info = { + .read_raw = &ad7280_read_raw, + .event_attrs = &ad7280_event_attrs_group, + .attrs = &ad7280_attrs_group, + .driver_module = THIS_MODULE, +}; + +static const struct ad7280_platform_data ad7793_default_pdata = { + .acquisition_time = AD7280A_ACQ_TIME_400ns, + .conversion_averaging = AD7280A_CONV_AVG_DIS, + .thermistor_term_en = true, +}; + +static int __devinit ad7280_probe(struct spi_device *spi) +{ + const struct ad7280_platform_data *pdata = spi->dev.platform_data; + struct ad7280_state *st; + int ret; + const unsigned short tACQ_ns[4] = {465, 1010, 1460, 1890}; + const unsigned short nAVG[4] = {1, 2, 4, 8}; + struct iio_dev *indio_dev = iio_allocate_device(sizeof(*st)); + + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + if (!pdata) + pdata = &ad7793_default_pdata; + + ad7280_crc8_build_table(st->crc_tab); + + st->spi->max_speed_hz = AD7280A_MAX_SPI_CLK_Hz; + st->spi->mode = SPI_MODE_1; + spi_setup(st->spi); + + st->ctrl_lb = AD7280A_CTRL_LB_ACQ_TIME(pdata->acquisition_time & 0x3); + st->ctrl_hb = AD7280A_CTRL_HB_CONV_AVG(pdata->conversion_averaging + & 0x3) | (pdata->thermistor_term_en ? + AD7280A_CTRL_LB_THERMISTOR_EN : 0); + + ret = ad7280_chain_setup(st); + if (ret < 0) + goto error_free_device; + + st->slave_num = ret; + st->scan_cnt = (st->slave_num + 1) * AD7280A_NUM_CH; + st->cell_threshhigh = 0xFF; + st->aux_threshhigh = 0xFF; + + /* + * Total Conversion Time = ((tACQ + tCONV) * + * (Number of Conversions per Part)) − + * tACQ + ((N - 1) * tDELAY) + * + * Readback Delay = Total Conversion Time + tWAIT + */ + + st->readback_delay_us = + ((tACQ_ns[pdata->acquisition_time & 0x3] + 695) * + (AD7280A_NUM_CH * nAVG[pdata->conversion_averaging & 0x3])) + - tACQ_ns[pdata->acquisition_time & 0x3] + + st->slave_num * 250; + + /* Convert to usecs */ + st->readback_delay_us = DIV_ROUND_UP(st->readback_delay_us, 1000); + st->readback_delay_us += 5; /* Add tWAIT */ + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ad7280_channel_init(st); + if (ret < 0) + goto error_free_device; + + indio_dev->num_channels = ret; + indio_dev->channels = st->channels; + indio_dev->info = &ad7280_info; + + ret = ad7280_attr_init(st); + if (ret < 0) + goto error_free_channels; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_attr; + + if (spi->irq > 0) { + ret = ad7280_write(st, AD7280A_DEVADDR_MASTER, + AD7280A_ALERT, 1, + AD7280A_ALERT_RELAY_SIG_CHAIN_DOWN); + if (ret) + goto error_unregister; + + ret = ad7280_write(st, AD7280A_DEVADDR(st->slave_num), + AD7280A_ALERT, 0, + AD7280A_ALERT_GEN_STATIC_HIGH | + (pdata->chain_last_alert_ignore & 0xF)); + if (ret) + goto error_unregister; + + ret = request_threaded_irq(spi->irq, + NULL, + ad7280_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + indio_dev->name, + indio_dev); + if (ret) + goto error_unregister; + } + + return 0; +error_unregister: + iio_device_unregister(indio_dev); + +error_free_attr: + kfree(st->iio_attr); + +error_free_channels: + kfree(st->channels); + +error_free_device: + iio_free_device(indio_dev); + + return ret; +} + +static int __devexit ad7280_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7280_state *st = iio_priv(indio_dev); + + if (spi->irq > 0) + free_irq(spi->irq, indio_dev); + iio_device_unregister(indio_dev); + + ad7280_write(st, AD7280A_DEVADDR_MASTER, AD7280A_CONTROL_HB, 1, + AD7280A_CTRL_HB_PWRDN_SW | st->ctrl_hb); + + kfree(st->channels); + kfree(st->iio_attr); + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad7280_id[] = { + {"ad7280a", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7280_id); + +static struct spi_driver ad7280_driver = { + .driver = { + .name = "ad7280", + .owner = THIS_MODULE, + }, + .probe = ad7280_probe, + .remove = __devexit_p(ad7280_remove), + .id_table = ad7280_id, +}; +module_spi_driver(ad7280_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7280A"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7280a.h b/drivers/staging/iio/adc/ad7280a.h new file mode 100644 index 00000000..20400b00 --- /dev/null +++ b/drivers/staging/iio/adc/ad7280a.h @@ -0,0 +1,38 @@ +/* + * AD7280A Lithium Ion Battery Monitoring System + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_ADC_AD7280_H_ +#define IIO_ADC_AD7280_H_ + +/* + * TODO: struct ad7280_platform_data needs to go into include/linux/iio + */ + +#define AD7280A_ACQ_TIME_400ns 0 +#define AD7280A_ACQ_TIME_800ns 1 +#define AD7280A_ACQ_TIME_1200ns 2 +#define AD7280A_ACQ_TIME_1600ns 3 + +#define AD7280A_CONV_AVG_DIS 0 +#define AD7280A_CONV_AVG_2 1 +#define AD7280A_CONV_AVG_4 2 +#define AD7280A_CONV_AVG_8 3 + +#define AD7280A_ALERT_REMOVE_VIN5 (1 << 2) +#define AD7280A_ALERT_REMOVE_VIN4_VIN5 (2 << 2) +#define AD7280A_ALERT_REMOVE_AUX5 (1 << 0) +#define AD7280A_ALERT_REMOVE_AUX4_AUX5 (2 << 0) + +struct ad7280_platform_data { + unsigned acquisition_time; + unsigned conversion_averaging; + unsigned chain_last_alert_ignore; + bool thermistor_term_en; +}; + +#endif /* IIO_ADC_AD7280_H_ */ diff --git a/drivers/staging/iio/adc/ad7291.c b/drivers/staging/iio/adc/ad7291.c new file mode 100644 index 00000000..81d6b612 --- /dev/null +++ b/drivers/staging/iio/adc/ad7291.c @@ -0,0 +1,714 @@ +/* + * AD7291 8-Channel, I2C, 12-Bit SAR ADC with Temperature Sensor + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" + +/* + * Simplified handling + * + * If no events enabled - single polled channel read + * If event enabled direct reads disable unless channel + * is in the read mask. + * + * The noise-delayed bit as per datasheet suggestion is always enabled. + * + */ + +/* + * AD7291 registers definition + */ +#define AD7291_COMMAND 0x00 +#define AD7291_VOLTAGE 0x01 +#define AD7291_T_SENSE 0x02 +#define AD7291_T_AVERAGE 0x03 +#define AD7291_CH0_DATA_HIGH 0x04 +#define AD7291_CH0_DATA_LOW 0x05 +#define AD7291_CH0_HYST 0x06 +#define AD7291_CH1_DATA_HIGH 0x07 +#define AD7291_CH1_DATA_LOW 0x08 +#define AD7291_CH1_HYST 0x09 +#define AD7291_CH2_DATA_HIGH 0x0A +#define AD7291_CH2_DATA_LOW 0x0B +#define AD7291_CH2_HYST 0x0C +#define AD7291_CH3_DATA_HIGH 0x0D +#define AD7291_CH3_DATA_LOW 0x0E +#define AD7291_CH3_HYST 0x0F +#define AD7291_CH4_DATA_HIGH 0x10 +#define AD7291_CH4_DATA_LOW 0x11 +#define AD7291_CH4_HYST 0x12 +#define AD7291_CH5_DATA_HIGH 0x13 +#define AD7291_CH5_DATA_LOW 0x14 +#define AD7291_CH5_HYST 0x15 +#define AD7291_CH6_DATA_HIGH 0x16 +#define AD7291_CH6_DATA_LOW 0x17 +#define AD7291_CH6_HYST 0x18 +#define AD7291_CH7_DATA_HIGH 0x19 +#define AD7291_CH7_DATA_LOW 0x1A +#define AD7291_CH7_HYST 0x2B +#define AD7291_T_SENSE_HIGH 0x1C +#define AD7291_T_SENSE_LOW 0x1D +#define AD7291_T_SENSE_HYST 0x1E +#define AD7291_VOLTAGE_ALERT_STATUS 0x1F +#define AD7291_T_ALERT_STATUS 0x20 + +#define AD7291_VOLTAGE_LIMIT_COUNT 8 + + +/* + * AD7291 command + */ +#define AD7291_AUTOCYCLE (1 << 0) +#define AD7291_RESET (1 << 1) +#define AD7291_ALERT_CLEAR (1 << 2) +#define AD7291_ALERT_POLARITY (1 << 3) +#define AD7291_EXT_REF (1 << 4) +#define AD7291_NOISE_DELAY (1 << 5) +#define AD7291_T_SENSE_MASK (1 << 7) +#define AD7291_VOLTAGE_MASK 0xFF00 +#define AD7291_VOLTAGE_OFFSET 0x8 + +/* + * AD7291 value masks + */ +#define AD7291_CHANNEL_MASK 0xF000 +#define AD7291_BITS 12 +#define AD7291_VALUE_MASK 0xFFF +#define AD7291_T_VALUE_SIGN 0x400 +#define AD7291_T_VALUE_FLOAT_OFFSET 2 +#define AD7291_T_VALUE_FLOAT_MASK 0x2 + +#define AD7291_BITS 12 + +struct ad7291_chip_info { + struct i2c_client *client; + struct regulator *reg; + u16 int_vref_mv; + u16 command; + u16 c_mask; /* Active voltage channels for events */ + struct mutex state_lock; +}; + +static int ad7291_i2c_read(struct ad7291_chip_info *chip, u8 reg, u16 *data) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + + *data = swab16((u16)ret); + + return 0; +} + +static int ad7291_i2c_write(struct ad7291_chip_info *chip, u8 reg, u16 data) +{ + return i2c_smbus_write_word_data(chip->client, reg, swab16(data)); +} + +static ssize_t ad7291_store_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7291_chip_info *chip = iio_priv(indio_dev); + + return ad7291_i2c_write(chip, AD7291_COMMAND, + chip->command | AD7291_RESET); +} + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, ad7291_store_reset, 0); + +static struct attribute *ad7291_attributes[] = { + &iio_dev_attr_reset.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7291_attribute_group = { + .attrs = ad7291_attributes, +}; + +static irqreturn_t ad7291_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad7291_chip_info *chip = iio_priv(private); + u16 t_status, v_status; + u16 command; + int i; + s64 timestamp = iio_get_time_ns(); + + if (ad7291_i2c_read(chip, AD7291_T_ALERT_STATUS, &t_status)) + return IRQ_HANDLED; + + if (ad7291_i2c_read(chip, AD7291_VOLTAGE_ALERT_STATUS, &v_status)) + return IRQ_HANDLED; + + if (!(t_status || v_status)) + return IRQ_HANDLED; + + command = chip->command | AD7291_ALERT_CLEAR; + ad7291_i2c_write(chip, AD7291_COMMAND, command); + + command = chip->command & ~AD7291_ALERT_CLEAR; + ad7291_i2c_write(chip, AD7291_COMMAND, command); + + /* For now treat t_sense and t_sense_average the same */ + if ((t_status & (1 << 0)) || (t_status & (1 << 2))) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if ((t_status & (1 << 1)) || (t_status & (1 << 3))) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + for (i = 0; i < AD7291_VOLTAGE_LIMIT_COUNT*2; i += 2) { + if (v_status & (1 << i)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + i/2, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if (v_status & (1 << (i + 1))) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + i/2, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + } + + return IRQ_HANDLED; +} + +static inline ssize_t ad7291_show_hyst(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7291_chip_info *chip = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + u16 data; + int ret; + + ret = ad7291_i2c_read(chip, this_attr->address, &data); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", data & AD7291_VALUE_MASK); +} + +static inline ssize_t ad7291_set_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7291_chip_info *chip = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + u16 data; + int ret; + + ret = kstrtou16(buf, 10, &data); + + if (ret < 0) + return ret; + if (data > AD7291_VALUE_MASK) + return -EINVAL; + + ret = ad7291_i2c_write(chip, this_attr->address, data); + if (ret < 0) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR(in_temp0_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad7291_show_hyst, ad7291_set_hyst, + AD7291_T_SENSE_HYST); +static IIO_DEVICE_ATTR(in_voltage0_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad7291_show_hyst, ad7291_set_hyst, AD7291_CH0_HYST); +static IIO_DEVICE_ATTR(in_voltage1_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad7291_show_hyst, ad7291_set_hyst, AD7291_CH1_HYST); +static IIO_DEVICE_ATTR(in_voltage2_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad7291_show_hyst, ad7291_set_hyst, AD7291_CH2_HYST); +static IIO_DEVICE_ATTR(in_voltage3_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad7291_show_hyst, ad7291_set_hyst, AD7291_CH3_HYST); +static IIO_DEVICE_ATTR(in_voltage4_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad7291_show_hyst, ad7291_set_hyst, AD7291_CH4_HYST); +static IIO_DEVICE_ATTR(in_voltage5_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad7291_show_hyst, ad7291_set_hyst, AD7291_CH5_HYST); +static IIO_DEVICE_ATTR(in_voltage6_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad7291_show_hyst, ad7291_set_hyst, AD7291_CH6_HYST); +static IIO_DEVICE_ATTR(in_voltage7_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad7291_show_hyst, ad7291_set_hyst, AD7291_CH7_HYST); + +static struct attribute *ad7291_event_attributes[] = { + &iio_dev_attr_in_temp0_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage0_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage1_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage2_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage3_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage4_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage5_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage6_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage7_thresh_both_hyst_raw.dev_attr.attr, + NULL, +}; + +/* high / low */ +static u8 ad7291_limit_regs[9][2] = { + { AD7291_CH0_DATA_HIGH, AD7291_CH0_DATA_LOW }, + { AD7291_CH1_DATA_HIGH, AD7291_CH1_DATA_LOW }, + { AD7291_CH2_DATA_HIGH, AD7291_CH2_DATA_LOW }, + { AD7291_CH3_DATA_HIGH, AD7291_CH3_DATA_LOW }, /* FIXME: ? */ + { AD7291_CH4_DATA_HIGH, AD7291_CH4_DATA_LOW }, + { AD7291_CH5_DATA_HIGH, AD7291_CH5_DATA_LOW }, + { AD7291_CH6_DATA_HIGH, AD7291_CH6_DATA_LOW }, + { AD7291_CH7_DATA_HIGH, AD7291_CH7_DATA_LOW }, + /* temp */ + { AD7291_T_SENSE_HIGH, AD7291_T_SENSE_LOW }, +}; + +static int ad7291_read_event_value(struct iio_dev *indio_dev, + u64 event_code, + int *val) +{ + struct ad7291_chip_info *chip = iio_priv(indio_dev); + + int ret; + u8 reg; + u16 uval; + s16 signval; + + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + case IIO_VOLTAGE: + reg = ad7291_limit_regs[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)] + [!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING)]; + + ret = ad7291_i2c_read(chip, reg, &uval); + if (ret < 0) + return ret; + *val = uval & AD7291_VALUE_MASK; + return 0; + + case IIO_TEMP: + reg = ad7291_limit_regs[8] + [!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING)]; + + ret = ad7291_i2c_read(chip, reg, &signval); + if (ret < 0) + return ret; + signval = (s16)((signval & AD7291_VALUE_MASK) << 4) >> 4; + *val = signval; + return 0; + default: + return -EINVAL; + }; +} + +static int ad7291_write_event_value(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + struct ad7291_chip_info *chip = iio_priv(indio_dev); + u8 reg; + s16 signval; + + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + case IIO_VOLTAGE: + if (val > AD7291_VALUE_MASK || val < 0) + return -EINVAL; + reg = ad7291_limit_regs[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)] + [!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING)]; + return ad7291_i2c_write(chip, reg, val); + case IIO_TEMP: + if (val > 2047 || val < -2048) + return -EINVAL; + reg = ad7291_limit_regs[8] + [!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING)]; + signval = val; + return ad7291_i2c_write(chip, reg, *(u16 *)&signval); + default: + return -EINVAL; + }; +} + +static int ad7291_read_event_config(struct iio_dev *indio_dev, + u64 event_code) +{ + struct ad7291_chip_info *chip = iio_priv(indio_dev); + /* To be enabled the channel must simply be on. If any are enabled + we are in continuous sampling mode */ + + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + case IIO_VOLTAGE: + if (chip->c_mask & + (1 << (15 - IIO_EVENT_CODE_EXTRACT_CHAN(event_code)))) + return 1; + else + return 0; + case IIO_TEMP: + /* always on */ + return 1; + default: + return -EINVAL; + } + +} + +static int ad7291_write_event_config(struct iio_dev *indio_dev, + u64 event_code, + int state) +{ + int ret = 0; + struct ad7291_chip_info *chip = iio_priv(indio_dev); + u16 regval; + + mutex_lock(&chip->state_lock); + regval = chip->command; + /* + * To be enabled the channel must simply be on. If any are enabled + * use continuous sampling mode. + * Possible to disable temp as well but that makes single read tricky. + */ + + switch (IIO_EVENT_CODE_EXTRACT_TYPE(event_code)) { + case IIO_VOLTAGE: + if ((!state) && (chip->c_mask & (1 << (15 - + IIO_EVENT_CODE_EXTRACT_CHAN(event_code))))) + chip->c_mask &= ~(1 << (15 - IIO_EVENT_CODE_EXTRACT_CHAN + (event_code))); + else if (state && (!(chip->c_mask & (1 << (15 - + IIO_EVENT_CODE_EXTRACT_CHAN(event_code)))))) + chip->c_mask |= (1 << (15 - IIO_EVENT_CODE_EXTRACT_CHAN + (event_code))); + else + break; + + regval &= ~AD7291_AUTOCYCLE; + regval |= chip->c_mask; + if (chip->c_mask) /* Enable autocycle? */ + regval |= AD7291_AUTOCYCLE; + + ret = ad7291_i2c_write(chip, AD7291_COMMAND, regval); + if (ret < 0) + goto error_ret; + + chip->command = regval; + break; + default: + ret = -EINVAL; + } + +error_ret: + mutex_unlock(&chip->state_lock); + return ret; +} + +static int ad7291_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + struct ad7291_chip_info *chip = iio_priv(indio_dev); + unsigned int scale_uv; + u16 regval; + s16 signval; + + switch (mask) { + case 0: + switch (chan->type) { + case IIO_VOLTAGE: + mutex_lock(&chip->state_lock); + /* If in autocycle mode drop through */ + if (chip->command & AD7291_AUTOCYCLE) { + mutex_unlock(&chip->state_lock); + return -EBUSY; + } + /* Enable this channel alone */ + regval = chip->command & (~AD7291_VOLTAGE_MASK); + regval |= 1 << (15 - chan->channel); + ret = ad7291_i2c_write(chip, AD7291_COMMAND, regval); + if (ret < 0) { + mutex_unlock(&chip->state_lock); + return ret; + } + /* Read voltage */ + ret = i2c_smbus_read_word_data(chip->client, + AD7291_VOLTAGE); + if (ret < 0) { + mutex_unlock(&chip->state_lock); + return ret; + } + *val = swab16((u16)ret) & AD7291_VALUE_MASK; + mutex_unlock(&chip->state_lock); + return IIO_VAL_INT; + case IIO_TEMP: + /* Assumes tsense bit of command register always set */ + ret = i2c_smbus_read_word_data(chip->client, + AD7291_T_SENSE); + if (ret < 0) + return ret; + signval = (s16)((swab16((u16)ret) & + AD7291_VALUE_MASK) << 4) >> 4; + *val = signval; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_AVERAGE_RAW: + ret = i2c_smbus_read_word_data(chip->client, + AD7291_T_AVERAGE); + if (ret < 0) + return ret; + signval = (s16)((swab16((u16)ret) & + AD7291_VALUE_MASK) << 4) >> 4; + *val = signval; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + scale_uv = (chip->int_vref_mv * 1000) >> AD7291_BITS; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + /* + * One LSB of the ADC corresponds to 0.25 deg C. + * The temperature reading is in 12-bit twos + * complement format + */ + *val = 250; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +#define AD7291_VOLTAGE_CHAN(_chan) \ +{ \ + .type = IIO_VOLTAGE, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .indexed = 1, \ + .channel = _chan, \ + .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING)|\ + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING) \ +} + +static const struct iio_chan_spec ad7291_channels[] = { + AD7291_VOLTAGE_CHAN(0), + AD7291_VOLTAGE_CHAN(1), + AD7291_VOLTAGE_CHAN(2), + AD7291_VOLTAGE_CHAN(3), + AD7291_VOLTAGE_CHAN(4), + AD7291_VOLTAGE_CHAN(5), + AD7291_VOLTAGE_CHAN(6), + AD7291_VOLTAGE_CHAN(7), + { + .type = IIO_TEMP, + .info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .indexed = 1, + .channel = 0, + .event_mask = + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING)| + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING) + } +}; + +static struct attribute_group ad7291_event_attribute_group = { + .attrs = ad7291_event_attributes, +}; + +static const struct iio_info ad7291_info = { + .attrs = &ad7291_attribute_group, + .read_raw = &ad7291_read_raw, + .read_event_config = &ad7291_read_event_config, + .write_event_config = &ad7291_write_event_config, + .read_event_value = &ad7291_read_event_value, + .write_event_value = &ad7291_write_event_value, + .event_attrs = &ad7291_event_attribute_group, +}; + +static int __devinit ad7291_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ad7291_chip_info *chip; + struct iio_dev *indio_dev; + int ret = 0, voltage_uv = 0; + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + chip = iio_priv(indio_dev); + + chip->reg = regulator_get(&client->dev, "vcc"); + if (!IS_ERR(chip->reg)) { + ret = regulator_enable(chip->reg); + if (ret) + goto error_put_reg; + voltage_uv = regulator_get_voltage(chip->reg); + } + + mutex_init(&chip->state_lock); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + chip->client = client; + + chip->command = AD7291_NOISE_DELAY | + AD7291_T_SENSE_MASK | /* Tsense always enabled */ + AD7291_ALERT_POLARITY; /* set irq polarity low level */ + + if (voltage_uv) { + chip->int_vref_mv = voltage_uv / 1000; + chip->command |= AD7291_EXT_REF; + } else { + chip->int_vref_mv = 2500; /* Build-in ref */ + } + + indio_dev->name = id->name; + indio_dev->channels = ad7291_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7291_channels); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ad7291_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = ad7291_i2c_write(chip, AD7291_COMMAND, AD7291_RESET); + if (ret) { + ret = -EIO; + goto error_disable_reg; + } + + ret = ad7291_i2c_write(chip, AD7291_COMMAND, chip->command); + if (ret) { + ret = -EIO; + goto error_disable_reg; + } + + if (client->irq > 0) { + ret = request_threaded_irq(client->irq, + NULL, + &ad7291_event_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + id->name, + indio_dev); + if (ret) + goto error_disable_reg; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_irq; + + dev_info(&client->dev, "%s ADC registered.\n", + id->name); + + return 0; + +error_unreg_irq: + if (client->irq) + free_irq(client->irq, indio_dev); +error_disable_reg: + if (!IS_ERR(chip->reg)) + regulator_disable(chip->reg); +error_put_reg: + if (!IS_ERR(chip->reg)) + regulator_put(chip->reg); + + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit ad7291_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ad7291_chip_info *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (client->irq) + free_irq(client->irq, indio_dev); + + if (!IS_ERR(chip->reg)) { + regulator_disable(chip->reg); + regulator_put(chip->reg); + } + + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id ad7291_id[] = { + { "ad7291", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad7291_id); + +static struct i2c_driver ad7291_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ad7291_probe, + .remove = __devexit_p(ad7291_remove), + .id_table = ad7291_id, +}; +module_i2c_driver(ad7291_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices AD7291 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7298.h b/drivers/staging/iio/adc/ad7298.h new file mode 100644 index 00000000..a0e5dea4 --- /dev/null +++ b/drivers/staging/iio/adc/ad7298.h @@ -0,0 +1,71 @@ +/* + * AD7298 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_ADC_AD7298_H_ +#define IIO_ADC_AD7298_H_ + +#define AD7298_WRITE (1 << 15) /* write to the control register */ +#define AD7298_REPEAT (1 << 14) /* repeated conversion enable */ +#define AD7298_CH(x) (1 << (13 - (x))) /* channel select */ +#define AD7298_TSENSE (1 << 5) /* temperature conversion enable */ +#define AD7298_EXTREF (1 << 2) /* external reference enable */ +#define AD7298_TAVG (1 << 1) /* temperature sensor averaging enable */ +#define AD7298_PDD (1 << 0) /* partial power down enable */ + +#define AD7298_MAX_CHAN 8 +#define AD7298_BITS 12 +#define AD7298_STORAGE_BITS 16 +#define AD7298_INTREF_mV 2500 + +#define AD7298_CH_TEMP 9 + +#define RES_MASK(bits) ((1 << (bits)) - 1) + +/* + * TODO: struct ad7298_platform_data needs to go into include/linux/iio + */ + +struct ad7298_platform_data { + /* External Vref voltage applied */ + u16 vref_mv; +}; + +struct ad7298_state { + struct spi_device *spi; + struct regulator *reg; + size_t d_size; + u16 int_vref_mv; + unsigned ext_ref; + struct spi_transfer ring_xfer[10]; + struct spi_transfer scan_single_xfer[3]; + struct spi_message ring_msg; + struct spi_message scan_single_msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + unsigned short rx_buf[8] ____cacheline_aligned; + unsigned short tx_buf[2]; +}; + +#ifdef CONFIG_IIO_BUFFER +int ad7298_register_ring_funcs_and_init(struct iio_dev *indio_dev); +void ad7298_ring_cleanup(struct iio_dev *indio_dev); +#else /* CONFIG_IIO_BUFFER */ + +static inline int +ad7298_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void ad7298_ring_cleanup(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_IIO_BUFFER */ +#endif /* IIO_ADC_AD7298_H_ */ diff --git a/drivers/staging/iio/adc/ad7298_core.c b/drivers/staging/iio/adc/ad7298_core.c new file mode 100644 index 00000000..8dd6aa9c --- /dev/null +++ b/drivers/staging/iio/adc/ad7298_core.c @@ -0,0 +1,285 @@ +/* + * AD7298 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "ad7298.h" + +static struct iio_chan_spec ad7298_channels[] = { + IIO_CHAN(IIO_TEMP, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + 9, AD7298_CH_TEMP, IIO_ST('s', 32, 32, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 1, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 1, 1, IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 2, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 2, 2, IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 3, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 3, 3, IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 4, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 4, 4, IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 5, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 5, 5, IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 6, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 6, 6, IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 7, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 7, 7, IIO_ST('u', 12, 16, 0), 0), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static int ad7298_scan_direct(struct ad7298_state *st, unsigned ch) +{ + int ret; + st->tx_buf[0] = cpu_to_be16(AD7298_WRITE | st->ext_ref | + (AD7298_CH(0) >> ch)); + + ret = spi_sync(st->spi, &st->scan_single_msg); + if (ret) + return ret; + + return be16_to_cpu(st->rx_buf[0]); +} + +static int ad7298_scan_temp(struct ad7298_state *st, int *val) +{ + int tmp, ret; + __be16 buf; + + buf = cpu_to_be16(AD7298_WRITE | AD7298_TSENSE | + AD7298_TAVG | st->ext_ref); + + ret = spi_write(st->spi, (u8 *)&buf, 2); + if (ret) + return ret; + + buf = cpu_to_be16(0); + + ret = spi_write(st->spi, (u8 *)&buf, 2); + if (ret) + return ret; + + usleep_range(101, 1000); /* sleep > 100us */ + + ret = spi_read(st->spi, (u8 *)&buf, 2); + if (ret) + return ret; + + tmp = be16_to_cpu(buf) & RES_MASK(AD7298_BITS); + + /* + * One LSB of the ADC corresponds to 0.25 deg C. + * The temperature reading is in 12-bit twos complement format + */ + + if (tmp & (1 << (AD7298_BITS - 1))) { + tmp = (4096 - tmp) * 250; + tmp -= (2 * tmp); + + } else { + tmp *= 250; /* temperature in milli degrees Celsius */ + } + + *val = tmp; + + return 0; +} + +static int ad7298_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7298_state *st = iio_priv(indio_dev); + unsigned int scale_uv; + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + ret = -EBUSY; + } else { + if (chan->address == AD7298_CH_TEMP) + ret = ad7298_scan_temp(st, val); + else + ret = ad7298_scan_direct(st, chan->address); + } + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + if (chan->address != AD7298_CH_TEMP) + *val = ret & RES_MASK(AD7298_BITS); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + scale_uv = (st->int_vref_mv * 1000) >> AD7298_BITS; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 1; + *val2 = 0; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + } + return -EINVAL; +} + +static const struct iio_info ad7298_info = { + .read_raw = &ad7298_read_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad7298_probe(struct spi_device *spi) +{ + struct ad7298_platform_data *pdata = spi->dev.platform_data; + struct ad7298_state *st; + int ret; + struct iio_dev *indio_dev = iio_allocate_device(sizeof(*st)); + + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + } + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad7298_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7298_channels); + indio_dev->info = &ad7298_info; + + /* Setup default message */ + + st->scan_single_xfer[0].tx_buf = &st->tx_buf[0]; + st->scan_single_xfer[0].len = 2; + st->scan_single_xfer[0].cs_change = 1; + st->scan_single_xfer[1].tx_buf = &st->tx_buf[1]; + st->scan_single_xfer[1].len = 2; + st->scan_single_xfer[1].cs_change = 1; + st->scan_single_xfer[2].rx_buf = &st->rx_buf[0]; + st->scan_single_xfer[2].len = 2; + + spi_message_init(&st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[0], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[1], &st->scan_single_msg); + spi_message_add_tail(&st->scan_single_xfer[2], &st->scan_single_msg); + + if (pdata && pdata->vref_mv) { + st->int_vref_mv = pdata->vref_mv; + st->ext_ref = AD7298_EXTREF; + } else { + st->int_vref_mv = AD7298_INTREF_mV; + } + + ret = ad7298_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_disable_reg; + + ret = iio_buffer_register(indio_dev, + &ad7298_channels[1], /* skip temp0 */ + ARRAY_SIZE(ad7298_channels) - 1); + if (ret) + goto error_cleanup_ring; + ret = iio_device_register(indio_dev); + if (ret) + goto error_unregister_ring; + + return 0; + +error_unregister_ring: + iio_buffer_unregister(indio_dev); +error_cleanup_ring: + ad7298_ring_cleanup(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + iio_free_device(indio_dev); + + return ret; +} + +static int __devexit ad7298_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7298_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); + ad7298_ring_cleanup(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad7298_id[] = { + {"ad7298", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7298_id); + +static struct spi_driver ad7298_driver = { + .driver = { + .name = "ad7298", + .owner = THIS_MODULE, + }, + .probe = ad7298_probe, + .remove = __devexit_p(ad7298_remove), + .id_table = ad7298_id, +}; +module_spi_driver(ad7298_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7298 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7298_ring.c b/drivers/staging/iio/adc/ad7298_ring.c new file mode 100644 index 00000000..feeb0eeb --- /dev/null +++ b/drivers/staging/iio/adc/ad7298_ring.c @@ -0,0 +1,164 @@ +/* + * AD7298 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include + +#include "../iio.h" +#include "../buffer.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" + +#include "ad7298.h" + +/** + * ad7298_ring_preenable() setup the parameters of the ring before enabling + * + * The complex nature of the setting of the number of bytes per datum is due + * to this driver currently ensuring that the timestamp is stored at an 8 + * byte boundary. + **/ +static int ad7298_ring_preenable(struct iio_dev *indio_dev) +{ + struct ad7298_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + size_t d_size; + int i, m; + unsigned short command; + int scan_count = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + d_size = scan_count * (AD7298_STORAGE_BITS / 8); + + if (ring->scan_timestamp) { + d_size += sizeof(s64); + + if (d_size % sizeof(s64)) + d_size += sizeof(s64) - (d_size % sizeof(s64)); + } + + if (ring->access->set_bytes_per_datum) + ring->access->set_bytes_per_datum(ring, d_size); + + st->d_size = d_size; + + command = AD7298_WRITE | st->ext_ref; + + for (i = 0, m = AD7298_CH(0); i < AD7298_MAX_CHAN; i++, m >>= 1) + if (test_bit(i, indio_dev->active_scan_mask)) + command |= m; + + st->tx_buf[0] = cpu_to_be16(command); + + /* build spi ring message */ + st->ring_xfer[0].tx_buf = &st->tx_buf[0]; + st->ring_xfer[0].len = 2; + st->ring_xfer[0].cs_change = 1; + st->ring_xfer[1].tx_buf = &st->tx_buf[1]; + st->ring_xfer[1].len = 2; + st->ring_xfer[1].cs_change = 1; + + spi_message_init(&st->ring_msg); + spi_message_add_tail(&st->ring_xfer[0], &st->ring_msg); + spi_message_add_tail(&st->ring_xfer[1], &st->ring_msg); + + for (i = 0; i < scan_count; i++) { + st->ring_xfer[i + 2].rx_buf = &st->rx_buf[i]; + st->ring_xfer[i + 2].len = 2; + st->ring_xfer[i + 2].cs_change = 1; + spi_message_add_tail(&st->ring_xfer[i + 2], &st->ring_msg); + } + /* make sure last transfer cs_change is not set */ + st->ring_xfer[i + 1].cs_change = 0; + + return 0; +} + +/** + * ad7298_trigger_handler() bh of trigger launched polling to ring buffer + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + **/ +static irqreturn_t ad7298_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7298_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + s64 time_ns; + __u16 buf[16]; + int b_sent, i; + + b_sent = spi_sync(st->spi, &st->ring_msg); + if (b_sent) + return b_sent; + + if (ring->scan_timestamp) { + time_ns = iio_get_time_ns(); + memcpy((u8 *)buf + st->d_size - sizeof(s64), + &time_ns, sizeof(time_ns)); + } + + for (i = 0; i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); i++) + buf[i] = be16_to_cpu(st->rx_buf[i]); + + indio_dev->buffer->access->store_to(ring, (u8 *)buf, time_ns); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ad7298_ring_setup_ops = { + .preenable = &ad7298_ring_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int ad7298_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + int ret; + + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + goto error_ret; + } + indio_dev->pollfunc = iio_alloc_pollfunc(NULL, + &ad7298_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "ad7298_consumer%d", + indio_dev->id); + + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_deallocate_sw_rb; + } + + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &ad7298_ring_setup_ops; + indio_dev->buffer->scan_timestamp = true; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_deallocate_sw_rb: + iio_sw_rb_free(indio_dev->buffer); +error_ret: + return ret; +} + +void ad7298_ring_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} diff --git a/drivers/staging/iio/adc/ad7476.h b/drivers/staging/iio/adc/ad7476.h new file mode 100644 index 00000000..27f696c7 --- /dev/null +++ b/drivers/staging/iio/adc/ad7476.h @@ -0,0 +1,67 @@ +/* + * AD7476/5/7/8 (A) SPI ADC driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ +#ifndef IIO_ADC_AD7476_H_ +#define IIO_ADC_AD7476_H_ + +#define RES_MASK(bits) ((1 << (bits)) - 1) + +/* + * TODO: struct ad7476_platform_data needs to go into include/linux/iio + */ + +struct ad7476_platform_data { + u16 vref_mv; +}; + +struct ad7476_chip_info { + u16 int_vref_mv; + struct iio_chan_spec channel[2]; +}; + +struct ad7476_state { + struct spi_device *spi; + const struct ad7476_chip_info *chip_info; + struct regulator *reg; + size_t d_size; + u16 int_vref_mv; + struct spi_transfer xfer; + struct spi_message msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + unsigned char data[2] ____cacheline_aligned; +}; + +enum ad7476_supported_device_ids { + ID_AD7466, + ID_AD7467, + ID_AD7468, + ID_AD7475, + ID_AD7476, + ID_AD7477, + ID_AD7478, + ID_AD7495 +}; + +#ifdef CONFIG_IIO_BUFFER +int ad7476_register_ring_funcs_and_init(struct iio_dev *indio_dev); +void ad7476_ring_cleanup(struct iio_dev *indio_dev); +#else /* CONFIG_IIO_BUFFER */ + +static inline int +ad7476_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void ad7476_ring_cleanup(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_IIO_BUFFER */ +#endif /* IIO_ADC_AD7476_H_ */ diff --git a/drivers/staging/iio/adc/ad7476_core.c b/drivers/staging/iio/adc/ad7476_core.c new file mode 100644 index 00000000..0c064d1c --- /dev/null +++ b/drivers/staging/iio/adc/ad7476_core.c @@ -0,0 +1,255 @@ +/* + * AD7466/7/8 AD7476/5/7/8 (A) SPI ADC driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "ad7476.h" + +static int ad7476_scan_direct(struct ad7476_state *st) +{ + int ret; + + ret = spi_sync(st->spi, &st->msg); + if (ret) + return ret; + + return (st->data[0] << 8) | st->data[1]; +} + +static int ad7476_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7476_state *st = iio_priv(indio_dev); + unsigned int scale_uv; + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad7476_scan_direct(st); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + *val = (ret >> st->chip_info->channel[0].scan_type.shift) & + RES_MASK(st->chip_info->channel[0].scan_type.realbits); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->int_vref_mv * 1000) + >> st->chip_info->channel[0].scan_type.realbits; + *val = scale_uv/1000; + *val2 = (scale_uv%1000)*1000; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static const struct ad7476_chip_info ad7476_chip_info_tbl[] = { + [ID_AD7466] = { + .channel[0] = IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('u', 12, 16, 0), 0), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7467] = { + .channel[0] = IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('u', 10, 16, 2), 0), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7468] = { + .channel[0] = IIO_CHAN(IIO_VOLTAGE, 0, 1 , 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('u', 8, 16, 4), 0), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7475] = { + .channel[0] = IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('u', 12, 16, 0), 0), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7476] = { + .channel[0] = IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('u', 12, 16, 0), 0), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7477] = { + .channel[0] = IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('u', 10, 16, 2), 0), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7478] = { + .channel[0] = IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('u', 8, 16, 4), 0), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + }, + [ID_AD7495] = { + .channel[0] = IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('u', 12, 16, 0), 0), + .channel[1] = IIO_CHAN_SOFT_TIMESTAMP(1), + .int_vref_mv = 2500, + }, +}; + +static const struct iio_info ad7476_info = { + .driver_module = THIS_MODULE, + .read_raw = &ad7476_read_raw, +}; + +static int __devinit ad7476_probe(struct spi_device *spi) +{ + struct ad7476_platform_data *pdata = spi->dev.platform_data; + struct ad7476_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(st->reg); + } + st->chip_info = + &ad7476_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + if (st->chip_info->int_vref_mv) + st->int_vref_mv = st->chip_info->int_vref_mv; + else if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, "reference voltage unspecified\n"); + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + /* Establish that the iio_dev is a child of the spi device */ + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channel; + indio_dev->num_channels = 2; + indio_dev->info = &ad7476_info; + /* Setup default message */ + + st->xfer.rx_buf = &st->data; + st->xfer.len = st->chip_info->channel[0].scan_type.storagebits / 8; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + ret = ad7476_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_disable_reg; + + ret = iio_buffer_register(indio_dev, + st->chip_info->channel, + ARRAY_SIZE(st->chip_info->channel)); + if (ret) + goto error_cleanup_ring; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_ring_unregister; + return 0; + +error_ring_unregister: + iio_buffer_unregister(indio_dev); +error_cleanup_ring: + ad7476_ring_cleanup(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + iio_free_device(indio_dev); + +error_ret: + return ret; +} + +static int ad7476_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7476_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); + ad7476_ring_cleanup(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad7476_id[] = { + {"ad7466", ID_AD7466}, + {"ad7467", ID_AD7467}, + {"ad7468", ID_AD7468}, + {"ad7475", ID_AD7475}, + {"ad7476", ID_AD7476}, + {"ad7476a", ID_AD7476}, + {"ad7477", ID_AD7477}, + {"ad7477a", ID_AD7477}, + {"ad7478", ID_AD7478}, + {"ad7478a", ID_AD7478}, + {"ad7495", ID_AD7495}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7476_id); + +static struct spi_driver ad7476_driver = { + .driver = { + .name = "ad7476", + .owner = THIS_MODULE, + }, + .probe = ad7476_probe, + .remove = __devexit_p(ad7476_remove), + .id_table = ad7476_id, +}; +module_spi_driver(ad7476_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7475/6/7/8(A) AD7466/7/8 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7476_ring.c b/drivers/staging/iio/adc/ad7476_ring.c new file mode 100644 index 00000000..d6af6c05 --- /dev/null +++ b/drivers/staging/iio/adc/ad7476_ring.c @@ -0,0 +1,132 @@ +/* + * Copyright 2010 Analog Devices Inc. + * Copyright (C) 2008 Jonathan Cameron + * + * Licensed under the GPL-2 or later. + * + * ad7476_ring.c + */ + +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../buffer.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" + +#include "ad7476.h" + +/** + * ad7476_ring_preenable() setup the parameters of the ring before enabling + * + * The complex nature of the setting of the number of bytes per datum is due + * to this driver currently ensuring that the timestamp is stored at an 8 + * byte boundary. + **/ +static int ad7476_ring_preenable(struct iio_dev *indio_dev) +{ + struct ad7476_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + + st->d_size = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * + st->chip_info->channel[0].scan_type.storagebits / 8; + + if (ring->scan_timestamp) { + st->d_size += sizeof(s64); + + if (st->d_size % sizeof(s64)) + st->d_size += sizeof(s64) - (st->d_size % sizeof(s64)); + } + + if (indio_dev->buffer->access->set_bytes_per_datum) + indio_dev->buffer->access-> + set_bytes_per_datum(indio_dev->buffer, st->d_size); + + return 0; +} + +static irqreturn_t ad7476_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7476_state *st = iio_priv(indio_dev); + s64 time_ns; + __u8 *rxbuf; + int b_sent; + + rxbuf = kzalloc(st->d_size, GFP_KERNEL); + if (rxbuf == NULL) + return -ENOMEM; + + b_sent = spi_read(st->spi, rxbuf, + st->chip_info->channel[0].scan_type.storagebits / 8); + if (b_sent < 0) + goto done; + + time_ns = iio_get_time_ns(); + + if (indio_dev->buffer->scan_timestamp) + memcpy(rxbuf + st->d_size - sizeof(s64), + &time_ns, sizeof(time_ns)); + + indio_dev->buffer->access->store_to(indio_dev->buffer, rxbuf, time_ns); +done: + iio_trigger_notify_done(indio_dev->trig); + kfree(rxbuf); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ad7476_ring_setup_ops = { + .preenable = &ad7476_ring_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int ad7476_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + struct ad7476_state *st = iio_priv(indio_dev); + int ret = 0; + + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + goto error_ret; + } + indio_dev->pollfunc + = iio_alloc_pollfunc(NULL, + &ad7476_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + spi_get_device_id(st->spi)->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_deallocate_sw_rb; + } + + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &ad7476_ring_setup_ops; + indio_dev->buffer->scan_timestamp = true; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_deallocate_sw_rb: + iio_sw_rb_free(indio_dev->buffer); +error_ret: + return ret; +} + +void ad7476_ring_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} diff --git a/drivers/staging/iio/adc/ad7606.h b/drivers/staging/iio/adc/ad7606.h new file mode 100644 index 00000000..10f59896 --- /dev/null +++ b/drivers/staging/iio/adc/ad7606.h @@ -0,0 +1,104 @@ +/* + * AD7606 ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_ADC_AD7606_H_ +#define IIO_ADC_AD7606_H_ + +/* + * TODO: struct ad7606_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad7606_platform_data - platform/board specifc information + * @default_os: default oversampling value {0, 2, 4, 8, 16, 32, 64} + * @default_range: default range +/-{5000, 10000} mVolt + * @gpio_convst: number of gpio connected to the CONVST pin + * @gpio_reset: gpio connected to the RESET pin, if not used set to -1 + * @gpio_range: gpio connected to the RANGE pin, if not used set to -1 + * @gpio_os0: gpio connected to the OS0 pin, if not used set to -1 + * @gpio_os1: gpio connected to the OS1 pin, if not used set to -1 + * @gpio_os2: gpio connected to the OS2 pin, if not used set to -1 + * @gpio_frstdata: gpio connected to the FRSTDAT pin, if not used set to -1 + * @gpio_stby: gpio connected to the STBY pin, if not used set to -1 + */ + +struct ad7606_platform_data { + unsigned default_os; + unsigned default_range; + unsigned gpio_convst; + unsigned gpio_reset; + unsigned gpio_range; + unsigned gpio_os0; + unsigned gpio_os1; + unsigned gpio_os2; + unsigned gpio_frstdata; + unsigned gpio_stby; +}; + +/** + * struct ad7606_chip_info - chip specifc information + * @name: indentification string for chip + * @int_vref_mv: the internal reference voltage + * @channels: channel specification + * @num_channels: number of channels + */ + +struct ad7606_chip_info { + const char *name; + u16 int_vref_mv; + struct iio_chan_spec *channels; + unsigned num_channels; +}; + +/** + * struct ad7606_state - driver instance specific data + */ + +struct ad7606_state { + struct device *dev; + const struct ad7606_chip_info *chip_info; + struct ad7606_platform_data *pdata; + struct regulator *reg; + struct work_struct poll_work; + wait_queue_head_t wq_data_avail; + const struct ad7606_bus_ops *bops; + unsigned range; + unsigned oversampling; + bool done; + void __iomem *base_address; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + + unsigned short data[8] ____cacheline_aligned; +}; + +struct ad7606_bus_ops { + /* more methods added in future? */ + int (*read_block)(struct device *, int, void *); +}; + +void ad7606_suspend(struct iio_dev *indio_dev); +void ad7606_resume(struct iio_dev *indio_dev); +struct iio_dev *ad7606_probe(struct device *dev, int irq, + void __iomem *base_address, unsigned id, + const struct ad7606_bus_ops *bops); +int ad7606_remove(struct iio_dev *indio_dev, int irq); +int ad7606_reset(struct ad7606_state *st); + +enum ad7606_supported_device_ids { + ID_AD7606_8, + ID_AD7606_6, + ID_AD7606_4 +}; + +int ad7606_register_ring_funcs_and_init(struct iio_dev *indio_dev); +void ad7606_ring_cleanup(struct iio_dev *indio_dev); +#endif /* IIO_ADC_AD7606_H_ */ diff --git a/drivers/staging/iio/adc/ad7606_core.c b/drivers/staging/iio/adc/ad7606_core.c new file mode 100644 index 00000000..7322c168 --- /dev/null +++ b/drivers/staging/iio/adc/ad7606_core.c @@ -0,0 +1,615 @@ +/* + * AD7606 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "ad7606.h" + +int ad7606_reset(struct ad7606_state *st) +{ + if (gpio_is_valid(st->pdata->gpio_reset)) { + gpio_set_value(st->pdata->gpio_reset, 1); + ndelay(100); /* t_reset >= 100ns */ + gpio_set_value(st->pdata->gpio_reset, 0); + return 0; + } + + return -ENODEV; +} + +static int ad7606_scan_direct(struct iio_dev *indio_dev, unsigned ch) +{ + struct ad7606_state *st = iio_priv(indio_dev); + int ret; + + st->done = false; + gpio_set_value(st->pdata->gpio_convst, 1); + + ret = wait_event_interruptible(st->wq_data_avail, st->done); + if (ret) + goto error_ret; + + if (gpio_is_valid(st->pdata->gpio_frstdata)) { + ret = st->bops->read_block(st->dev, 1, st->data); + if (ret) + goto error_ret; + if (!gpio_get_value(st->pdata->gpio_frstdata)) { + /* This should never happen */ + ad7606_reset(st); + ret = -EIO; + goto error_ret; + } + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels - 1, &st->data[1]); + if (ret) + goto error_ret; + } else { + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels, st->data); + if (ret) + goto error_ret; + } + + ret = st->data[ch]; + +error_ret: + gpio_set_value(st->pdata->gpio_convst, 0); + + return ret; +} + +static int ad7606_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7606_state *st = iio_priv(indio_dev); + unsigned int scale_uv; + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad7606_scan_direct(indio_dev, chan->address); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + *val = (short) ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->range * 1000 * 2) + >> st->chip_info->channels[0].scan_type.realbits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static ssize_t ad7606_show_range(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7606_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", st->range); +} + +static ssize_t ad7606_store_range(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7606_state *st = iio_priv(indio_dev); + unsigned long lval; + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + if (!(lval == 5000 || lval == 10000)) { + dev_err(dev, "range is not supported\n"); + return -EINVAL; + } + mutex_lock(&indio_dev->mlock); + gpio_set_value(st->pdata->gpio_range, lval == 10000); + st->range = lval; + mutex_unlock(&indio_dev->mlock); + + return count; +} + +static IIO_DEVICE_ATTR(in_voltage_range, S_IRUGO | S_IWUSR, \ + ad7606_show_range, ad7606_store_range, 0); +static IIO_CONST_ATTR(in_voltage_range_available, "5000 10000"); + +static ssize_t ad7606_show_oversampling_ratio(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7606_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", st->oversampling); +} + +static int ad7606_oversampling_get_index(unsigned val) +{ + unsigned char supported[] = {0, 2, 4, 8, 16, 32, 64}; + int i; + + for (i = 0; i < ARRAY_SIZE(supported); i++) + if (val == supported[i]) + return i; + + return -EINVAL; +} + +static ssize_t ad7606_store_oversampling_ratio(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7606_state *st = iio_priv(indio_dev); + unsigned long lval; + int ret; + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + ret = ad7606_oversampling_get_index(lval); + if (ret < 0) { + dev_err(dev, "oversampling %lu is not supported\n", lval); + return ret; + } + + mutex_lock(&indio_dev->mlock); + gpio_set_value(st->pdata->gpio_os0, (ret >> 0) & 1); + gpio_set_value(st->pdata->gpio_os1, (ret >> 1) & 1); + gpio_set_value(st->pdata->gpio_os1, (ret >> 2) & 1); + st->oversampling = lval; + mutex_unlock(&indio_dev->mlock); + + return count; +} + +static IIO_DEVICE_ATTR(oversampling_ratio, S_IRUGO | S_IWUSR, + ad7606_show_oversampling_ratio, + ad7606_store_oversampling_ratio, 0); +static IIO_CONST_ATTR(oversampling_ratio_available, "0 2 4 8 16 32 64"); + +static struct attribute *ad7606_attributes_os_and_range[] = { + &iio_dev_attr_in_voltage_range.dev_attr.attr, + &iio_const_attr_in_voltage_range_available.dev_attr.attr, + &iio_dev_attr_oversampling_ratio.dev_attr.attr, + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_os_and_range = { + .attrs = ad7606_attributes_os_and_range, +}; + +static struct attribute *ad7606_attributes_os[] = { + &iio_dev_attr_oversampling_ratio.dev_attr.attr, + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_os = { + .attrs = ad7606_attributes_os, +}; + +static struct attribute *ad7606_attributes_range[] = { + &iio_dev_attr_in_voltage_range.dev_attr.attr, + &iio_const_attr_in_voltage_range_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7606_attribute_group_range = { + .attrs = ad7606_attributes_range, +}; + +#define AD7606_CHANNEL(num) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = num, \ + .address = num, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .scan_index = num, \ + .scan_type = IIO_ST('s', 16, 16, 0), \ + } + +static struct iio_chan_spec ad7606_8_channels[] = { + AD7606_CHANNEL(0), + AD7606_CHANNEL(1), + AD7606_CHANNEL(2), + AD7606_CHANNEL(3), + AD7606_CHANNEL(4), + AD7606_CHANNEL(5), + AD7606_CHANNEL(6), + AD7606_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static struct iio_chan_spec ad7606_6_channels[] = { + AD7606_CHANNEL(0), + AD7606_CHANNEL(1), + AD7606_CHANNEL(2), + AD7606_CHANNEL(3), + AD7606_CHANNEL(4), + AD7606_CHANNEL(5), + IIO_CHAN_SOFT_TIMESTAMP(6), +}; + +static struct iio_chan_spec ad7606_4_channels[] = { + AD7606_CHANNEL(0), + AD7606_CHANNEL(1), + AD7606_CHANNEL(2), + AD7606_CHANNEL(3), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static const struct ad7606_chip_info ad7606_chip_info_tbl[] = { + /* + * More devices added in future + */ + [ID_AD7606_8] = { + .name = "ad7606", + .int_vref_mv = 2500, + .channels = ad7606_8_channels, + .num_channels = 8, + }, + [ID_AD7606_6] = { + .name = "ad7606-6", + .int_vref_mv = 2500, + .channels = ad7606_6_channels, + .num_channels = 6, + }, + [ID_AD7606_4] = { + .name = "ad7606-4", + .int_vref_mv = 2500, + .channels = ad7606_4_channels, + .num_channels = 4, + }, +}; + +static int ad7606_request_gpios(struct ad7606_state *st) +{ + struct gpio gpio_array[3] = { + [0] = { + .gpio = st->pdata->gpio_os0, + .flags = GPIOF_DIR_OUT | ((st->oversampling & 1) ? + GPIOF_INIT_HIGH : GPIOF_INIT_LOW), + .label = "AD7606_OS0", + }, + [1] = { + .gpio = st->pdata->gpio_os1, + .flags = GPIOF_DIR_OUT | ((st->oversampling & 2) ? + GPIOF_INIT_HIGH : GPIOF_INIT_LOW), + .label = "AD7606_OS1", + }, + [2] = { + .gpio = st->pdata->gpio_os2, + .flags = GPIOF_DIR_OUT | ((st->oversampling & 4) ? + GPIOF_INIT_HIGH : GPIOF_INIT_LOW), + .label = "AD7606_OS2", + }, + }; + int ret; + + if (gpio_is_valid(st->pdata->gpio_convst)) { + ret = gpio_request_one(st->pdata->gpio_convst, + GPIOF_OUT_INIT_LOW, + "AD7606_CONVST"); + if (ret) { + dev_err(st->dev, "failed to request GPIO CONVST\n"); + goto error_ret; + } + } else { + ret = -EIO; + goto error_ret; + } + + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) { + ret = gpio_request_array(gpio_array, ARRAY_SIZE(gpio_array)); + if (ret < 0) + goto error_free_convst; + } + + if (gpio_is_valid(st->pdata->gpio_reset)) { + ret = gpio_request_one(st->pdata->gpio_reset, + GPIOF_OUT_INIT_LOW, + "AD7606_RESET"); + if (ret < 0) + goto error_free_os; + } + + if (gpio_is_valid(st->pdata->gpio_range)) { + ret = gpio_request_one(st->pdata->gpio_range, GPIOF_DIR_OUT | + ((st->range == 10000) ? GPIOF_INIT_HIGH : + GPIOF_INIT_LOW), "AD7606_RANGE"); + if (ret < 0) + goto error_free_reset; + } + if (gpio_is_valid(st->pdata->gpio_stby)) { + ret = gpio_request_one(st->pdata->gpio_stby, + GPIOF_OUT_INIT_HIGH, + "AD7606_STBY"); + if (ret < 0) + goto error_free_range; + } + + if (gpio_is_valid(st->pdata->gpio_frstdata)) { + ret = gpio_request_one(st->pdata->gpio_frstdata, GPIOF_IN, + "AD7606_FRSTDATA"); + if (ret < 0) + goto error_free_stby; + } + + return 0; + +error_free_stby: + if (gpio_is_valid(st->pdata->gpio_stby)) + gpio_free(st->pdata->gpio_stby); +error_free_range: + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_free(st->pdata->gpio_range); +error_free_reset: + if (gpio_is_valid(st->pdata->gpio_reset)) + gpio_free(st->pdata->gpio_reset); +error_free_os: + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) + gpio_free_array(gpio_array, ARRAY_SIZE(gpio_array)); +error_free_convst: + gpio_free(st->pdata->gpio_convst); +error_ret: + return ret; +} + +static void ad7606_free_gpios(struct ad7606_state *st) +{ + if (gpio_is_valid(st->pdata->gpio_frstdata)) + gpio_free(st->pdata->gpio_frstdata); + if (gpio_is_valid(st->pdata->gpio_stby)) + gpio_free(st->pdata->gpio_stby); + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_free(st->pdata->gpio_range); + if (gpio_is_valid(st->pdata->gpio_reset)) + gpio_free(st->pdata->gpio_reset); + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) { + gpio_free(st->pdata->gpio_os2); + gpio_free(st->pdata->gpio_os1); + gpio_free(st->pdata->gpio_os0); + } + gpio_free(st->pdata->gpio_convst); +} + +/** + * Interrupt handler + */ +static irqreturn_t ad7606_interrupt(int irq, void *dev_id) +{ + struct iio_dev *indio_dev = dev_id; + struct ad7606_state *st = iio_priv(indio_dev); + + if (iio_buffer_enabled(indio_dev)) { + if (!work_pending(&st->poll_work)) + schedule_work(&st->poll_work); + } else { + st->done = true; + wake_up_interruptible(&st->wq_data_avail); + } + + return IRQ_HANDLED; +}; + +static const struct iio_info ad7606_info_no_os_or_range = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, +}; + +static const struct iio_info ad7606_info_os_and_range = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, + .attrs = &ad7606_attribute_group_os_and_range, +}; + +static const struct iio_info ad7606_info_os = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, + .attrs = &ad7606_attribute_group_os, +}; + +static const struct iio_info ad7606_info_range = { + .driver_module = THIS_MODULE, + .read_raw = &ad7606_read_raw, + .attrs = &ad7606_attribute_group_range, +}; + +struct iio_dev *ad7606_probe(struct device *dev, int irq, + void __iomem *base_address, + unsigned id, + const struct ad7606_bus_ops *bops) +{ + struct ad7606_platform_data *pdata = dev->platform_data; + struct ad7606_state *st; + int ret; + struct iio_dev *indio_dev = iio_allocate_device(sizeof(*st)); + + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + st = iio_priv(indio_dev); + + st->dev = dev; + st->bops = bops; + st->base_address = base_address; + st->range = pdata->default_range == 10000 ? 10000 : 5000; + + ret = ad7606_oversampling_get_index(pdata->default_os); + if (ret < 0) { + dev_warn(dev, "oversampling %d is not supported\n", + pdata->default_os); + st->oversampling = 0; + } else { + st->oversampling = pdata->default_os; + } + + st->reg = regulator_get(dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + } + + st->pdata = pdata; + st->chip_info = &ad7606_chip_info_tbl[id]; + + indio_dev->dev.parent = dev; + if (gpio_is_valid(st->pdata->gpio_os0) && + gpio_is_valid(st->pdata->gpio_os1) && + gpio_is_valid(st->pdata->gpio_os2)) { + if (gpio_is_valid(st->pdata->gpio_range)) + indio_dev->info = &ad7606_info_os_and_range; + else + indio_dev->info = &ad7606_info_os; + } else { + if (gpio_is_valid(st->pdata->gpio_range)) + indio_dev->info = &ad7606_info_range; + else + indio_dev->info = &ad7606_info_no_os_or_range; + } + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = st->chip_info->name; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + init_waitqueue_head(&st->wq_data_avail); + + ret = ad7606_request_gpios(st); + if (ret) + goto error_disable_reg; + + ret = ad7606_reset(st); + if (ret) + dev_warn(st->dev, "failed to RESET: no RESET GPIO specified\n"); + + ret = request_irq(irq, ad7606_interrupt, + IRQF_TRIGGER_FALLING, st->chip_info->name, indio_dev); + if (ret) + goto error_free_gpios; + + ret = ad7606_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_free_irq; + + ret = iio_buffer_register(indio_dev, + indio_dev->channels, + indio_dev->num_channels); + if (ret) + goto error_cleanup_ring; + ret = iio_device_register(indio_dev); + if (ret) + goto error_unregister_ring; + + return indio_dev; +error_unregister_ring: + iio_buffer_unregister(indio_dev); + +error_cleanup_ring: + ad7606_ring_cleanup(indio_dev); + +error_free_irq: + free_irq(irq, indio_dev); + +error_free_gpios: + ad7606_free_gpios(st); + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + iio_free_device(indio_dev); +error_ret: + return ERR_PTR(ret); +} + +int ad7606_remove(struct iio_dev *indio_dev, int irq) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); + ad7606_ring_cleanup(indio_dev); + + free_irq(irq, indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + + ad7606_free_gpios(st); + iio_free_device(indio_dev); + + return 0; +} + +void ad7606_suspend(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + if (gpio_is_valid(st->pdata->gpio_stby)) { + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_set_value(st->pdata->gpio_range, 1); + gpio_set_value(st->pdata->gpio_stby, 0); + } +} + +void ad7606_resume(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + + if (gpio_is_valid(st->pdata->gpio_stby)) { + if (gpio_is_valid(st->pdata->gpio_range)) + gpio_set_value(st->pdata->gpio_range, + st->range == 10000); + + gpio_set_value(st->pdata->gpio_stby, 1); + ad7606_reset(st); + } +} + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7606_par.c b/drivers/staging/iio/adc/ad7606_par.c new file mode 100644 index 00000000..bb152a8e --- /dev/null +++ b/drivers/staging/iio/adc/ad7606_par.c @@ -0,0 +1,180 @@ +/* + * AD7606 Parallel Interface ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "ad7606.h" + +static int ad7606_par16_read_block(struct device *dev, + int count, void *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct ad7606_state *st = iio_priv(indio_dev); + + insw((unsigned long) st->base_address, buf, count); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_par16_bops = { + .read_block = ad7606_par16_read_block, +}; + +static int ad7606_par8_read_block(struct device *dev, + int count, void *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct ad7606_state *st = iio_priv(indio_dev); + + insb((unsigned long) st->base_address, buf, count * 2); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_par8_bops = { + .read_block = ad7606_par8_read_block, +}; + +static int __devinit ad7606_par_probe(struct platform_device *pdev) +{ + struct resource *res; + struct iio_dev *indio_dev; + void __iomem *addr; + resource_size_t remap_size; + int ret, irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + remap_size = resource_size(res); + + /* Request the regions */ + if (!request_mem_region(res->start, remap_size, "iio-ad7606")) { + ret = -EBUSY; + goto out1; + } + addr = ioremap(res->start, remap_size); + if (!addr) { + ret = -ENOMEM; + goto out1; + } + + indio_dev = ad7606_probe(&pdev->dev, irq, addr, + platform_get_device_id(pdev)->driver_data, + remap_size > 1 ? &ad7606_par16_bops : + &ad7606_par8_bops); + + if (IS_ERR(indio_dev)) { + ret = PTR_ERR(indio_dev); + goto out2; + } + + platform_set_drvdata(pdev, indio_dev); + + return 0; + +out2: + iounmap(addr); +out1: + release_mem_region(res->start, remap_size); + + return ret; +} + +static int __devexit ad7606_par_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct resource *res; + struct ad7606_state *st = iio_priv(indio_dev); + + ad7606_remove(indio_dev, platform_get_irq(pdev, 0)); + + iounmap(st->base_address); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int ad7606_par_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_suspend(indio_dev); + + return 0; +} + +static int ad7606_par_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_resume(indio_dev); + + return 0; +} + +static const struct dev_pm_ops ad7606_pm_ops = { + .suspend = ad7606_par_suspend, + .resume = ad7606_par_resume, +}; +#define AD7606_PAR_PM_OPS (&ad7606_pm_ops) + +#else +#define AD7606_PAR_PM_OPS NULL +#endif /* CONFIG_PM */ + +static struct platform_device_id ad7606_driver_ids[] = { + { + .name = "ad7606-8", + .driver_data = ID_AD7606_8, + }, { + .name = "ad7606-6", + .driver_data = ID_AD7606_6, + }, { + .name = "ad7606-4", + .driver_data = ID_AD7606_4, + }, + { } +}; + +MODULE_DEVICE_TABLE(platform, ad7606_driver_ids); + +static struct platform_driver ad7606_driver = { + .probe = ad7606_par_probe, + .remove = __devexit_p(ad7606_par_remove), + .id_table = ad7606_driver_ids, + .driver = { + .name = "ad7606", + .owner = THIS_MODULE, + .pm = AD7606_PAR_PM_OPS, + }, +}; + +module_platform_driver(ad7606_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7606_ring.c b/drivers/staging/iio/adc/ad7606_ring.c new file mode 100644 index 00000000..1ef9fbca --- /dev/null +++ b/drivers/staging/iio/adc/ad7606_ring.c @@ -0,0 +1,146 @@ +/* + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../buffer.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" + +#include "ad7606.h" + +/** + * ad7606_trigger_handler_th() th/bh of trigger launched polling to ring buffer + * + **/ +static irqreturn_t ad7606_trigger_handler_th_bh(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct ad7606_state *st = iio_priv(pf->indio_dev); + + gpio_set_value(st->pdata->gpio_convst, 1); + + return IRQ_HANDLED; +} + +/** + * ad7606_poll_bh_to_ring() bh of trigger launched polling to ring buffer + * @work_s: the work struct through which this was scheduled + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + * I think the one copy of this at a time was to avoid problems if the + * trigger was set far too high and the reads then locked up the computer. + **/ +static void ad7606_poll_bh_to_ring(struct work_struct *work_s) +{ + struct ad7606_state *st = container_of(work_s, struct ad7606_state, + poll_work); + struct iio_dev *indio_dev = iio_priv_to_dev(st); + struct iio_buffer *ring = indio_dev->buffer; + s64 time_ns; + __u8 *buf; + int ret; + + buf = kzalloc(ring->access->get_bytes_per_datum(ring), + GFP_KERNEL); + if (buf == NULL) + return; + + if (gpio_is_valid(st->pdata->gpio_frstdata)) { + ret = st->bops->read_block(st->dev, 1, buf); + if (ret) + goto done; + if (!gpio_get_value(st->pdata->gpio_frstdata)) { + /* This should never happen. However + * some signal glitch caused by bad PCB desgin or + * electrostatic discharge, could cause an extra read + * or clock. This allows recovery. + */ + ad7606_reset(st); + goto done; + } + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels - 1, buf + 2); + if (ret) + goto done; + } else { + ret = st->bops->read_block(st->dev, + st->chip_info->num_channels, buf); + if (ret) + goto done; + } + + time_ns = iio_get_time_ns(); + + if (ring->scan_timestamp) + *((s64 *)(buf + ring->access->get_bytes_per_datum(ring) - + sizeof(s64))) = time_ns; + + ring->access->store_to(indio_dev->buffer, buf, time_ns); +done: + gpio_set_value(st->pdata->gpio_convst, 0); + iio_trigger_notify_done(indio_dev->trig); + kfree(buf); +} + +static const struct iio_buffer_setup_ops ad7606_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int ad7606_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + struct ad7606_state *st = iio_priv(indio_dev); + int ret; + + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + goto error_ret; + } + + indio_dev->pollfunc = iio_alloc_pollfunc(&ad7606_trigger_handler_th_bh, + &ad7606_trigger_handler_th_bh, + 0, + indio_dev, + "%s_consumer%d", + indio_dev->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_deallocate_sw_rb; + } + + /* Ring buffer functions - here trigger setup related */ + + indio_dev->setup_ops = &ad7606_ring_setup_ops; + indio_dev->buffer->scan_timestamp = true ; + + INIT_WORK(&st->poll_work, &ad7606_poll_bh_to_ring); + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_deallocate_sw_rb: + iio_sw_rb_free(indio_dev->buffer); +error_ret: + return ret; +} + +void ad7606_ring_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} diff --git a/drivers/staging/iio/adc/ad7606_spi.c b/drivers/staging/iio/adc/ad7606_spi.c new file mode 100644 index 00000000..237f1c44 --- /dev/null +++ b/drivers/staging/iio/adc/ad7606_spi.c @@ -0,0 +1,116 @@ +/* + * AD7606 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include + +#include "../iio.h" +#include "ad7606.h" + +#define MAX_SPI_FREQ_HZ 23500000 /* VDRIVE above 4.75 V */ + +static int ad7606_spi_read_block(struct device *dev, + int count, void *buf) +{ + struct spi_device *spi = to_spi_device(dev); + int i, ret; + unsigned short *data = buf; + + ret = spi_read(spi, (u8 *)buf, count * 2); + if (ret < 0) { + dev_err(&spi->dev, "SPI read error\n"); + return ret; + } + + for (i = 0; i < count; i++) + data[i] = be16_to_cpu(data[i]); + + return 0; +} + +static const struct ad7606_bus_ops ad7606_spi_bops = { + .read_block = ad7606_spi_read_block, +}; + +static int __devinit ad7606_spi_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + + indio_dev = ad7606_probe(&spi->dev, spi->irq, NULL, + spi_get_device_id(spi)->driver_data, + &ad7606_spi_bops); + + if (IS_ERR(indio_dev)) + return PTR_ERR(indio_dev); + + spi_set_drvdata(spi, indio_dev); + + return 0; +} + +static int __devexit ad7606_spi_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&spi->dev); + + return ad7606_remove(indio_dev, spi->irq); +} + +#ifdef CONFIG_PM +static int ad7606_spi_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_suspend(indio_dev); + + return 0; +} + +static int ad7606_spi_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ad7606_resume(indio_dev); + + return 0; +} + +static const struct dev_pm_ops ad7606_pm_ops = { + .suspend = ad7606_spi_suspend, + .resume = ad7606_spi_resume, +}; +#define AD7606_SPI_PM_OPS (&ad7606_pm_ops) + +#else +#define AD7606_SPI_PM_OPS NULL +#endif + +static const struct spi_device_id ad7606_id[] = { + {"ad7606-8", ID_AD7606_8}, + {"ad7606-6", ID_AD7606_6}, + {"ad7606-4", ID_AD7606_4}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7606_id); + +static struct spi_driver ad7606_driver = { + .driver = { + .name = "ad7606", + .owner = THIS_MODULE, + .pm = AD7606_SPI_PM_OPS, + }, + .probe = ad7606_spi_probe, + .remove = __devexit_p(ad7606_spi_remove), + .id_table = ad7606_id, +}; +module_spi_driver(ad7606_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7606 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7780.c b/drivers/staging/iio/adc/ad7780.c new file mode 100644 index 00000000..a13e58c8 --- /dev/null +++ b/drivers/staging/iio/adc/ad7780.c @@ -0,0 +1,290 @@ +/* + * AD7780/AD7781 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#include "ad7780.h" + +#define AD7780_RDY (1 << 7) +#define AD7780_FILTER (1 << 6) +#define AD7780_ERR (1 << 5) +#define AD7780_ID1 (1 << 4) +#define AD7780_ID0 (1 << 3) +#define AD7780_GAIN (1 << 2) +#define AD7780_PAT1 (1 << 1) +#define AD7780_PAT0 (1 << 0) + +struct ad7780_chip_info { + struct iio_chan_spec channel; +}; + +struct ad7780_state { + struct spi_device *spi; + const struct ad7780_chip_info *chip_info; + struct regulator *reg; + struct ad7780_platform_data *pdata; + wait_queue_head_t wq_data_avail; + bool done; + u16 int_vref_mv; + struct spi_transfer xfer; + struct spi_message msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + unsigned int data ____cacheline_aligned; +}; + +enum ad7780_supported_device_ids { + ID_AD7780, + ID_AD7781, +}; + +static int ad7780_read(struct ad7780_state *st, int *val) +{ + int ret; + + spi_bus_lock(st->spi->master); + + enable_irq(st->spi->irq); + st->done = false; + gpio_set_value(st->pdata->gpio_pdrst, 1); + + ret = wait_event_interruptible(st->wq_data_avail, st->done); + disable_irq_nosync(st->spi->irq); + if (ret) + goto out; + + ret = spi_sync_locked(st->spi, &st->msg); + *val = be32_to_cpu(st->data); +out: + gpio_set_value(st->pdata->gpio_pdrst, 0); + spi_bus_unlock(st->spi->master); + + return ret; +} + +static int ad7780_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7780_state *st = iio_priv(indio_dev); + struct iio_chan_spec channel = st->chip_info->channel; + int ret, smpl = 0; + unsigned long scale_uv; + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + ret = ad7780_read(st, &smpl); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + if ((smpl & AD7780_ERR) || + !((smpl & AD7780_PAT0) && !(smpl & AD7780_PAT1))) + return -EIO; + + *val = (smpl >> channel.scan_type.shift) & + ((1 << (channel.scan_type.realbits)) - 1); + *val -= (1 << (channel.scan_type.realbits - 1)); + + if (!(smpl & AD7780_GAIN)) + *val *= 128; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->int_vref_mv * 100000) + >> (channel.scan_type.realbits - 1); + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static const struct ad7780_chip_info ad7780_chip_info_tbl[] = { + [ID_AD7780] = { + .channel = IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('s', 24, 32, 8), 0), + }, + [ID_AD7781] = { + .channel = IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + 0, 0, IIO_ST('s', 20, 32, 12), 0), + }, +}; + +/** + * Interrupt handler + */ +static irqreturn_t ad7780_interrupt(int irq, void *dev_id) +{ + struct ad7780_state *st = dev_id; + + st->done = true; + wake_up_interruptible(&st->wq_data_avail); + + return IRQ_HANDLED; +}; + +static const struct iio_info ad7780_info = { + .read_raw = &ad7780_read_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad7780_probe(struct spi_device *spi) +{ + struct ad7780_platform_data *pdata = spi->dev.platform_data; + struct ad7780_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(st->reg); + } + + st->chip_info = + &ad7780_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + st->pdata = pdata; + + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, "reference voltage unspecified\n"); + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &st->chip_info->channel; + indio_dev->num_channels = 1; + indio_dev->info = &ad7780_info; + + init_waitqueue_head(&st->wq_data_avail); + + /* Setup default message */ + + st->xfer.rx_buf = &st->data; + st->xfer.len = st->chip_info->channel.scan_type.storagebits / 8; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + ret = gpio_request_one(st->pdata->gpio_pdrst, GPIOF_OUT_INIT_LOW, + "AD7780 /PDRST"); + if (ret) { + dev_err(&spi->dev, "failed to request GPIO PDRST\n"); + goto error_disable_reg; + } + + ret = request_irq(spi->irq, ad7780_interrupt, + IRQF_TRIGGER_FALLING, spi_get_device_id(spi)->name, st); + if (ret) + goto error_free_gpio; + + disable_irq(spi->irq); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(spi->irq, st); +error_free_gpio: + gpio_free(st->pdata->gpio_pdrst); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_free_device(indio_dev); + + return ret; +} + +static int ad7780_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7780_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + free_irq(spi->irq, st); + gpio_free(st->pdata->gpio_pdrst); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad7780_id[] = { + {"ad7780", ID_AD7780}, + {"ad7781", ID_AD7781}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7780_id); + +static struct spi_driver ad7780_driver = { + .driver = { + .name = "ad7780", + .owner = THIS_MODULE, + }, + .probe = ad7780_probe, + .remove = __devexit_p(ad7780_remove), + .id_table = ad7780_id, +}; +module_spi_driver(ad7780_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7780/1 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7780.h b/drivers/staging/iio/adc/ad7780.h new file mode 100644 index 00000000..67e511c3 --- /dev/null +++ b/drivers/staging/iio/adc/ad7780.h @@ -0,0 +1,30 @@ +/* + * AD7780/AD7781 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef IIO_ADC_AD7780_H_ +#define IIO_ADC_AD7780_H_ + +/* + * TODO: struct ad7780_platform_data needs to go into include/linux/iio + */ + +/* NOTE: + * The AD7780 doesn't feature a dedicated SPI chip select, in addition it + * features a dual use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use spi bus locking combined with a dedicated GPIO to control the + * power down reset signal of the AD7780. + * + * The DOUT/RDY output must also be wired to an interrupt capable GPIO. + */ + +struct ad7780_platform_data { + u16 vref_mv; + int gpio_pdrst; +}; + +#endif /* IIO_ADC_AD7780_H_ */ diff --git a/drivers/staging/iio/adc/ad7793.c b/drivers/staging/iio/adc/ad7793.c new file mode 100644 index 00000000..84ecde1a --- /dev/null +++ b/drivers/staging/iio/adc/ad7793.c @@ -0,0 +1,1037 @@ +/* + * AD7792/AD7793 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" +#include "../ring_sw.h" +#include "../trigger.h" +#include "../trigger_consumer.h" + +#include "ad7793.h" + +/* NOTE: + * The AD7792/AD7793 features a dual use data out ready DOUT/RDY output. + * In order to avoid contentions on the SPI bus, it's therefore necessary + * to use spi bus locking. + * + * The DOUT/RDY output must also be wired to an interrupt capable GPIO. + */ + +struct ad7793_chip_info { + struct iio_chan_spec channel[7]; +}; + +struct ad7793_state { + struct spi_device *spi; + struct iio_trigger *trig; + const struct ad7793_chip_info *chip_info; + struct regulator *reg; + struct ad7793_platform_data *pdata; + wait_queue_head_t wq_data_avail; + bool done; + bool irq_dis; + u16 int_vref_mv; + u16 mode; + u16 conf; + u32 scale_avail[8][2]; + /* Note this uses fact that 8 the mask always fits in a long */ + unsigned long available_scan_masks[7]; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + u8 data[4] ____cacheline_aligned; +}; + +enum ad7793_supported_device_ids { + ID_AD7792, + ID_AD7793, +}; + +static int __ad7793_write_reg(struct ad7793_state *st, bool locked, + bool cs_change, unsigned char reg, + unsigned size, unsigned val) +{ + u8 *data = st->data; + struct spi_transfer t = { + .tx_buf = data, + .len = size + 1, + .cs_change = cs_change, + }; + struct spi_message m; + + data[0] = AD7793_COMM_WRITE | AD7793_COMM_ADDR(reg); + + switch (size) { + case 3: + data[1] = val >> 16; + data[2] = val >> 8; + data[3] = val; + break; + case 2: + data[1] = val >> 8; + data[2] = val; + break; + case 1: + data[1] = val; + break; + default: + return -EINVAL; + } + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + if (locked) + return spi_sync_locked(st->spi, &m); + else + return spi_sync(st->spi, &m); +} + +static int ad7793_write_reg(struct ad7793_state *st, + unsigned reg, unsigned size, unsigned val) +{ + return __ad7793_write_reg(st, false, false, reg, size, val); +} + +static int __ad7793_read_reg(struct ad7793_state *st, bool locked, + bool cs_change, unsigned char reg, + int *val, unsigned size) +{ + u8 *data = st->data; + int ret; + struct spi_transfer t[] = { + { + .tx_buf = data, + .len = 1, + }, { + .rx_buf = data, + .len = size, + .cs_change = cs_change, + }, + }; + struct spi_message m; + + data[0] = AD7793_COMM_READ | AD7793_COMM_ADDR(reg); + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + if (locked) + ret = spi_sync_locked(st->spi, &m); + else + ret = spi_sync(st->spi, &m); + + if (ret < 0) + return ret; + + switch (size) { + case 3: + *val = data[0] << 16 | data[1] << 8 | data[2]; + break; + case 2: + *val = data[0] << 8 | data[1]; + break; + case 1: + *val = data[0]; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ad7793_read_reg(struct ad7793_state *st, + unsigned reg, int *val, unsigned size) +{ + return __ad7793_read_reg(st, 0, 0, reg, val, size); +} + +static int ad7793_read(struct ad7793_state *st, unsigned ch, + unsigned len, int *val) +{ + int ret; + st->conf = (st->conf & ~AD7793_CONF_CHAN(-1)) | AD7793_CONF_CHAN(ch); + st->mode = (st->mode & ~AD7793_MODE_SEL(-1)) | + AD7793_MODE_SEL(AD7793_MODE_SINGLE); + + ad7793_write_reg(st, AD7793_REG_CONF, sizeof(st->conf), st->conf); + + spi_bus_lock(st->spi->master); + st->done = false; + + ret = __ad7793_write_reg(st, 1, 1, AD7793_REG_MODE, + sizeof(st->mode), st->mode); + if (ret < 0) + goto out; + + st->irq_dis = false; + enable_irq(st->spi->irq); + wait_event_interruptible(st->wq_data_avail, st->done); + + ret = __ad7793_read_reg(st, 1, 0, AD7793_REG_DATA, val, len); +out: + spi_bus_unlock(st->spi->master); + + return ret; +} + +static int ad7793_calibrate(struct ad7793_state *st, unsigned mode, unsigned ch) +{ + int ret; + + st->conf = (st->conf & ~AD7793_CONF_CHAN(-1)) | AD7793_CONF_CHAN(ch); + st->mode = (st->mode & ~AD7793_MODE_SEL(-1)) | AD7793_MODE_SEL(mode); + + ad7793_write_reg(st, AD7793_REG_CONF, sizeof(st->conf), st->conf); + + spi_bus_lock(st->spi->master); + st->done = false; + + ret = __ad7793_write_reg(st, 1, 1, AD7793_REG_MODE, + sizeof(st->mode), st->mode); + if (ret < 0) + goto out; + + st->irq_dis = false; + enable_irq(st->spi->irq); + wait_event_interruptible(st->wq_data_avail, st->done); + + st->mode = (st->mode & ~AD7793_MODE_SEL(-1)) | + AD7793_MODE_SEL(AD7793_MODE_IDLE); + + ret = __ad7793_write_reg(st, 1, 0, AD7793_REG_MODE, + sizeof(st->mode), st->mode); +out: + spi_bus_unlock(st->spi->master); + + return ret; +} + +static const u8 ad7793_calib_arr[6][2] = { + {AD7793_MODE_CAL_INT_ZERO, AD7793_CH_AIN1P_AIN1M}, + {AD7793_MODE_CAL_INT_FULL, AD7793_CH_AIN1P_AIN1M}, + {AD7793_MODE_CAL_INT_ZERO, AD7793_CH_AIN2P_AIN2M}, + {AD7793_MODE_CAL_INT_FULL, AD7793_CH_AIN2P_AIN2M}, + {AD7793_MODE_CAL_INT_ZERO, AD7793_CH_AIN3P_AIN3M}, + {AD7793_MODE_CAL_INT_FULL, AD7793_CH_AIN3P_AIN3M} +}; + +static int ad7793_calibrate_all(struct ad7793_state *st) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(ad7793_calib_arr); i++) { + ret = ad7793_calibrate(st, ad7793_calib_arr[i][0], + ad7793_calib_arr[i][1]); + if (ret) + goto out; + } + + return 0; +out: + dev_err(&st->spi->dev, "Calibration failed\n"); + return ret; +} + +static int ad7793_setup(struct ad7793_state *st) +{ + int i, ret = -1; + unsigned long long scale_uv; + u32 id; + + /* reset the serial interface */ + ret = spi_write(st->spi, (u8 *)&ret, sizeof(ret)); + if (ret < 0) + goto out; + msleep(1); /* Wait for at least 500us */ + + /* write/read test for device presence */ + ret = ad7793_read_reg(st, AD7793_REG_ID, &id, 1); + if (ret) + goto out; + + id &= AD7793_ID_MASK; + + if (!((id == AD7792_ID) || (id == AD7793_ID))) { + dev_err(&st->spi->dev, "device ID query failed\n"); + goto out; + } + + st->mode = (st->pdata->mode & ~AD7793_MODE_SEL(-1)) | + AD7793_MODE_SEL(AD7793_MODE_IDLE); + st->conf = st->pdata->conf & ~AD7793_CONF_CHAN(-1); + + ret = ad7793_write_reg(st, AD7793_REG_MODE, sizeof(st->mode), st->mode); + if (ret) + goto out; + + ret = ad7793_write_reg(st, AD7793_REG_CONF, sizeof(st->conf), st->conf); + if (ret) + goto out; + + ret = ad7793_write_reg(st, AD7793_REG_IO, + sizeof(st->pdata->io), st->pdata->io); + if (ret) + goto out; + + ret = ad7793_calibrate_all(st); + if (ret) + goto out; + + /* Populate available ADC input ranges */ + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) { + scale_uv = ((u64)st->int_vref_mv * 100000000) + >> (st->chip_info->channel[0].scan_type.realbits - + (!!(st->conf & AD7793_CONF_UNIPOLAR) ? 0 : 1)); + scale_uv >>= i; + + st->scale_avail[i][1] = do_div(scale_uv, 100000000) * 10; + st->scale_avail[i][0] = scale_uv; + } + + return 0; +out: + dev_err(&st->spi->dev, "setup failed\n"); + return ret; +} + +static int ad7793_ring_preenable(struct iio_dev *indio_dev) +{ + struct ad7793_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + size_t d_size; + unsigned channel; + + if (bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + return -EINVAL; + + channel = find_first_bit(indio_dev->active_scan_mask, + indio_dev->masklength); + + d_size = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * + indio_dev->channels[0].scan_type.storagebits / 8; + + if (ring->scan_timestamp) { + d_size += sizeof(s64); + + if (d_size % sizeof(s64)) + d_size += sizeof(s64) - (d_size % sizeof(s64)); + } + + if (indio_dev->buffer->access->set_bytes_per_datum) + indio_dev->buffer->access-> + set_bytes_per_datum(indio_dev->buffer, d_size); + + st->mode = (st->mode & ~AD7793_MODE_SEL(-1)) | + AD7793_MODE_SEL(AD7793_MODE_CONT); + st->conf = (st->conf & ~AD7793_CONF_CHAN(-1)) | + AD7793_CONF_CHAN(indio_dev->channels[channel].address); + + ad7793_write_reg(st, AD7793_REG_CONF, sizeof(st->conf), st->conf); + + spi_bus_lock(st->spi->master); + __ad7793_write_reg(st, 1, 1, AD7793_REG_MODE, + sizeof(st->mode), st->mode); + + st->irq_dis = false; + enable_irq(st->spi->irq); + + return 0; +} + +static int ad7793_ring_postdisable(struct iio_dev *indio_dev) +{ + struct ad7793_state *st = iio_priv(indio_dev); + + st->mode = (st->mode & ~AD7793_MODE_SEL(-1)) | + AD7793_MODE_SEL(AD7793_MODE_IDLE); + + st->done = false; + wait_event_interruptible(st->wq_data_avail, st->done); + + if (!st->irq_dis) + disable_irq_nosync(st->spi->irq); + + __ad7793_write_reg(st, 1, 0, AD7793_REG_MODE, + sizeof(st->mode), st->mode); + + return spi_bus_unlock(st->spi->master); +} + +/** + * ad7793_trigger_handler() bh of trigger launched polling to ring buffer + **/ + +static irqreturn_t ad7793_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct iio_buffer *ring = indio_dev->buffer; + struct ad7793_state *st = iio_priv(indio_dev); + s64 dat64[2]; + s32 *dat32 = (s32 *)dat64; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + __ad7793_read_reg(st, 1, 1, AD7793_REG_DATA, + dat32, + indio_dev->channels[0].scan_type.realbits/8); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + dat64[1] = pf->timestamp; + + ring->access->store_to(ring, (u8 *)dat64, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + st->irq_dis = false; + enable_irq(st->spi->irq); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ad7793_ring_setup_ops = { + .preenable = &ad7793_ring_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &ad7793_ring_postdisable, +}; + +static int ad7793_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + int ret; + + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + goto error_ret; + } + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &ad7793_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "ad7793_consumer%d", + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_deallocate_sw_rb; + } + + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &ad7793_ring_setup_ops; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_deallocate_sw_rb: + iio_sw_rb_free(indio_dev->buffer); +error_ret: + return ret; +} + +static void ad7793_ring_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +/** + * ad7793_data_rdy_trig_poll() the event handler for the data rdy trig + **/ +static irqreturn_t ad7793_data_rdy_trig_poll(int irq, void *private) +{ + struct ad7793_state *st = iio_priv(private); + + st->done = true; + wake_up_interruptible(&st->wq_data_avail); + disable_irq_nosync(irq); + st->irq_dis = true; + iio_trigger_poll(st->trig, iio_get_time_ns()); + + return IRQ_HANDLED; +} + +static struct iio_trigger_ops ad7793_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int ad7793_probe_trigger(struct iio_dev *indio_dev) +{ + struct ad7793_state *st = iio_priv(indio_dev); + int ret; + + st->trig = iio_allocate_trigger("%s-dev%d", + spi_get_device_id(st->spi)->name, + indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st->trig->ops = &ad7793_trigger_ops; + + ret = request_irq(st->spi->irq, + ad7793_data_rdy_trig_poll, + IRQF_TRIGGER_LOW, + spi_get_device_id(st->spi)->name, + indio_dev); + if (ret) + goto error_free_trig; + + disable_irq_nosync(st->spi->irq); + st->irq_dis = true; + st->trig->dev.parent = &st->spi->dev; + st->trig->private_data = indio_dev; + + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->spi->irq, indio_dev); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +static void ad7793_remove_trigger(struct iio_dev *indio_dev) +{ + struct ad7793_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->spi->irq, indio_dev); + iio_free_trigger(st->trig); +} + +static const u16 sample_freq_avail[16] = {0, 470, 242, 123, 62, 50, 39, 33, 19, + 17, 16, 12, 10, 8, 6, 4}; + +static ssize_t ad7793_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7793_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", + sample_freq_avail[AD7793_MODE_RATE(st->mode)]); +} + +static ssize_t ad7793_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7793_state *st = iio_priv(indio_dev); + long lval; + int i, ret; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + mutex_unlock(&indio_dev->mlock); + + ret = strict_strtol(buf, 10, &lval); + if (ret) + return ret; + + ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(sample_freq_avail); i++) + if (lval == sample_freq_avail[i]) { + mutex_lock(&indio_dev->mlock); + st->mode &= ~AD7793_MODE_RATE(-1); + st->mode |= AD7793_MODE_RATE(i); + ad7793_write_reg(st, AD7793_REG_MODE, + sizeof(st->mode), st->mode); + mutex_unlock(&indio_dev->mlock); + ret = 0; + } + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ad7793_read_frequency, + ad7793_write_frequency); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "470 242 123 62 50 39 33 19 17 16 12 10 8 6 4"); + +static ssize_t ad7793_show_scale_available(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7793_state *st = iio_priv(indio_dev); + int i, len = 0; + + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + len += sprintf(buf + len, "%d.%09u ", st->scale_avail[i][0], + st->scale_avail[i][1]); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static IIO_DEVICE_ATTR_NAMED(in_m_in_scale_available, in-in_scale_available, + S_IRUGO, ad7793_show_scale_available, NULL, 0); + +static struct attribute *ad7793_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_in_m_in_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad7793_attribute_group = { + .attrs = ad7793_attributes, +}; + +static int ad7793_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad7793_state *st = iio_priv(indio_dev); + int ret, smpl = 0; + unsigned long long scale_uv; + bool unipolar = !!(st->conf & AD7793_CONF_UNIPOLAR); + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad7793_read(st, chan->address, + chan->scan_type.realbits / 8, &smpl); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + + *val = (smpl >> chan->scan_type.shift) & + ((1 << (chan->scan_type.realbits)) - 1); + + if (!unipolar) + *val -= (1 << (chan->scan_type.realbits - 1)); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->differential) { + *val = st-> + scale_avail[(st->conf >> 8) & 0x7][0]; + *val2 = st-> + scale_avail[(st->conf >> 8) & 0x7][1]; + return IIO_VAL_INT_PLUS_NANO; + } else { + /* 1170mV / 2^23 * 6 */ + scale_uv = (1170ULL * 100000000ULL * 6ULL) + >> (chan->scan_type.realbits - + (unipolar ? 0 : 1)); + } + break; + case IIO_TEMP: + /* Always uses unity gain and internal ref */ + scale_uv = (2500ULL * 100000000ULL) + >> (chan->scan_type.realbits - + (unipolar ? 0 : 1)); + break; + default: + return -EINVAL; + } + + *val2 = do_div(scale_uv, 100000000) * 10; + *val = scale_uv; + + return IIO_VAL_INT_PLUS_NANO; + } + return -EINVAL; +} + +static int ad7793_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7793_state *st = iio_priv(indio_dev); + int ret, i; + unsigned int tmp; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = -EINVAL; + for (i = 0; i < ARRAY_SIZE(st->scale_avail); i++) + if (val2 == st->scale_avail[i][1]) { + tmp = st->conf; + st->conf &= ~AD7793_CONF_GAIN(-1); + st->conf |= AD7793_CONF_GAIN(i); + + if (tmp != st->conf) { + ad7793_write_reg(st, AD7793_REG_CONF, + sizeof(st->conf), + st->conf); + ad7793_calibrate_all(st); + } + ret = 0; + } + + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static int ad7793_validate_trigger(struct iio_dev *indio_dev, + struct iio_trigger *trig) +{ + if (indio_dev->trig != trig) + return -EINVAL; + + return 0; +} + +static int ad7793_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static const struct iio_info ad7793_info = { + .read_raw = &ad7793_read_raw, + .write_raw = &ad7793_write_raw, + .write_raw_get_fmt = &ad7793_write_raw_get_fmt, + .attrs = &ad7793_attribute_group, + .validate_trigger = ad7793_validate_trigger, + .driver_module = THIS_MODULE, +}; + +static const struct ad7793_chip_info ad7793_chip_info_tbl[] = { + [ID_AD7793] = { + .channel[0] = { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 0, + .channel2 = 0, + .address = AD7793_CH_AIN1P_AIN1M, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = 0, + .scan_type = IIO_ST('s', 24, 32, 0) + }, + .channel[1] = { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 1, + .channel2 = 1, + .address = AD7793_CH_AIN2P_AIN2M, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = 1, + .scan_type = IIO_ST('s', 24, 32, 0) + }, + .channel[2] = { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 2, + .channel2 = 2, + .address = AD7793_CH_AIN3P_AIN3M, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = 2, + .scan_type = IIO_ST('s', 24, 32, 0) + }, + .channel[3] = { + .type = IIO_VOLTAGE, + .differential = 1, + .extend_name = "shorted", + .indexed = 1, + .channel = 2, + .channel2 = 2, + .address = AD7793_CH_AIN1M_AIN1M, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = 2, + .scan_type = IIO_ST('s', 24, 32, 0) + }, + .channel[4] = { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .address = AD7793_CH_TEMP, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .scan_index = 4, + .scan_type = IIO_ST('s', 24, 32, 0), + }, + .channel[5] = { + .type = IIO_VOLTAGE, + .extend_name = "supply", + .indexed = 1, + .channel = 4, + .address = AD7793_CH_AVDD_MONITOR, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .scan_index = 5, + .scan_type = IIO_ST('s', 24, 32, 0), + }, + .channel[6] = IIO_CHAN_SOFT_TIMESTAMP(6), + }, + [ID_AD7792] = { + .channel[0] = { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 0, + .channel2 = 0, + .address = AD7793_CH_AIN1P_AIN1M, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = 0, + .scan_type = IIO_ST('s', 16, 32, 0) + }, + .channel[1] = { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 1, + .channel2 = 1, + .address = AD7793_CH_AIN2P_AIN2M, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = 1, + .scan_type = IIO_ST('s', 16, 32, 0) + }, + .channel[2] = { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 2, + .channel2 = 2, + .address = AD7793_CH_AIN3P_AIN3M, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = 2, + .scan_type = IIO_ST('s', 16, 32, 0) + }, + .channel[3] = { + .type = IIO_VOLTAGE, + .differential = 1, + .extend_name = "shorted", + .indexed = 1, + .channel = 2, + .channel2 = 2, + .address = AD7793_CH_AIN1M_AIN1M, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = 2, + .scan_type = IIO_ST('s', 16, 32, 0) + }, + .channel[4] = { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .address = AD7793_CH_TEMP, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .scan_index = 4, + .scan_type = IIO_ST('s', 16, 32, 0), + }, + .channel[5] = { + .type = IIO_VOLTAGE, + .extend_name = "supply", + .indexed = 1, + .channel = 4, + .address = AD7793_CH_AVDD_MONITOR, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .scan_index = 5, + .scan_type = IIO_ST('s', 16, 32, 0), + }, + .channel[6] = IIO_CHAN_SOFT_TIMESTAMP(6), + }, +}; + +static int __devinit ad7793_probe(struct spi_device *spi) +{ + struct ad7793_platform_data *pdata = spi->dev.platform_data; + struct ad7793_state *st; + struct iio_dev *indio_dev; + int ret, i, voltage_uv = 0; + + if (!pdata) { + dev_err(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + if (!spi->irq) { + dev_err(&spi->dev, "no IRQ?\n"); + return -ENODEV; + } + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(st->reg); + } + + st->chip_info = + &ad7793_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + st->pdata = pdata; + + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + st->int_vref_mv = 2500; /* Build-in ref */ + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channel; + indio_dev->available_scan_masks = st->available_scan_masks; + indio_dev->num_channels = 7; + indio_dev->info = &ad7793_info; + + for (i = 0; i < indio_dev->num_channels; i++) { + set_bit(i, &st->available_scan_masks[i]); + set_bit(indio_dev-> + channels[indio_dev->num_channels - 1].scan_index, + &st->available_scan_masks[i]); + } + + init_waitqueue_head(&st->wq_data_avail); + + ret = ad7793_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_disable_reg; + + ret = ad7793_probe_trigger(indio_dev); + if (ret) + goto error_unreg_ring; + + ret = iio_buffer_register(indio_dev, + indio_dev->channels, + indio_dev->num_channels); + if (ret) + goto error_remove_trigger; + + ret = ad7793_setup(st); + if (ret) + goto error_uninitialize_ring; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_uninitialize_ring; + + return 0; + +error_uninitialize_ring: + iio_buffer_unregister(indio_dev); +error_remove_trigger: + ad7793_remove_trigger(indio_dev); +error_unreg_ring: + ad7793_ring_cleanup(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_free_device(indio_dev); + + return ret; +} + +static int ad7793_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7793_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); + ad7793_remove_trigger(indio_dev); + ad7793_ring_cleanup(indio_dev); + + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad7793_id[] = { + {"ad7792", ID_AD7792}, + {"ad7793", ID_AD7793}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7793_id); + +static struct spi_driver ad7793_driver = { + .driver = { + .name = "ad7793", + .owner = THIS_MODULE, + }, + .probe = ad7793_probe, + .remove = __devexit_p(ad7793_remove), + .id_table = ad7793_id, +}; +module_spi_driver(ad7793_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7792/3 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7793.h b/drivers/staging/iio/adc/ad7793.h new file mode 100644 index 00000000..64f7d41d --- /dev/null +++ b/drivers/staging/iio/adc/ad7793.h @@ -0,0 +1,107 @@ +/* + * AD7792/AD7793 SPI ADC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef IIO_ADC_AD7793_H_ +#define IIO_ADC_AD7793_H_ + +/* + * TODO: struct ad7793_platform_data needs to go into include/linux/iio + */ + +/* Registers */ +#define AD7793_REG_COMM 0 /* Communications Register (WO, 8-bit) */ +#define AD7793_REG_STAT 0 /* Status Register (RO, 8-bit) */ +#define AD7793_REG_MODE 1 /* Mode Register (RW, 16-bit */ +#define AD7793_REG_CONF 2 /* Configuration Register (RW, 16-bit) */ +#define AD7793_REG_DATA 3 /* Data Register (RO, 16-/24-bit) */ +#define AD7793_REG_ID 4 /* ID Register (RO, 8-bit) */ +#define AD7793_REG_IO 5 /* IO Register (RO, 8-bit) */ +#define AD7793_REG_OFFSET 6 /* Offset Register (RW, 16-bit + * (AD7792)/24-bit (AD7793)) */ +#define AD7793_REG_FULLSALE 7 /* Full-Scale Register + * (RW, 16-bit (AD7792)/24-bit (AD7793)) */ + +/* Communications Register Bit Designations (AD7793_REG_COMM) */ +#define AD7793_COMM_WEN (1 << 7) /* Write Enable */ +#define AD7793_COMM_WRITE (0 << 6) /* Write Operation */ +#define AD7793_COMM_READ (1 << 6) /* Read Operation */ +#define AD7793_COMM_ADDR(x) (((x) & 0x7) << 3) /* Register Address */ +#define AD7793_COMM_CREAD (1 << 2) /* Continuous Read of Data Register */ + +/* Status Register Bit Designations (AD7793_REG_STAT) */ +#define AD7793_STAT_RDY (1 << 7) /* Ready */ +#define AD7793_STAT_ERR (1 << 6) /* Error (Overrange, Underrange) */ +#define AD7793_STAT_CH3 (1 << 2) /* Channel 3 */ +#define AD7793_STAT_CH2 (1 << 1) /* Channel 2 */ +#define AD7793_STAT_CH1 (1 << 0) /* Channel 1 */ + +/* Mode Register Bit Designations (AD7793_REG_MODE) */ +#define AD7793_MODE_SEL(x) (((x) & 0x7) << 13) /* Operation Mode Select */ +#define AD7793_MODE_CLKSRC(x) (((x) & 0x3) << 6) /* ADC Clock Source Select */ +#define AD7793_MODE_RATE(x) ((x) & 0xF) /* Filter Update Rate Select */ + +#define AD7793_MODE_CONT 0 /* Continuous Conversion Mode */ +#define AD7793_MODE_SINGLE 1 /* Single Conversion Mode */ +#define AD7793_MODE_IDLE 2 /* Idle Mode */ +#define AD7793_MODE_PWRDN 3 /* Power-Down Mode */ +#define AD7793_MODE_CAL_INT_ZERO 4 /* Internal Zero-Scale Calibration */ +#define AD7793_MODE_CAL_INT_FULL 5 /* Internal Full-Scale Calibration */ +#define AD7793_MODE_CAL_SYS_ZERO 6 /* System Zero-Scale Calibration */ +#define AD7793_MODE_CAL_SYS_FULL 7 /* System Full-Scale Calibration */ + +#define AD7793_CLK_INT 0 /* Internal 64 kHz Clock not + * available at the CLK pin */ +#define AD7793_CLK_INT_CO 1 /* Internal 64 kHz Clock available + * at the CLK pin */ +#define AD7793_CLK_EXT 2 /* External 64 kHz Clock */ +#define AD7793_CLK_EXT_DIV2 3 /* External Clock divided by 2 */ + +/* Configuration Register Bit Designations (AD7793_REG_CONF) */ +#define AD7793_CONF_VBIAS(x) (((x) & 0x3) << 14) /* Bias Voltage + * Generator Enable */ +#define AD7793_CONF_BO_EN (1 << 13) /* Burnout Current Enable */ +#define AD7793_CONF_UNIPOLAR (1 << 12) /* Unipolar/Bipolar Enable */ +#define AD7793_CONF_BOOST (1 << 11) /* Boost Enable */ +#define AD7793_CONF_GAIN(x) (((x) & 0x7) << 8) /* Gain Select */ +#define AD7793_CONF_REFSEL (1 << 7) /* INT/EXT Reference Select */ +#define AD7793_CONF_BUF (1 << 4) /* Buffered Mode Enable */ +#define AD7793_CONF_CHAN(x) ((x) & 0x7) /* Channel select */ + +#define AD7793_CH_AIN1P_AIN1M 0 /* AIN1(+) - AIN1(-) */ +#define AD7793_CH_AIN2P_AIN2M 1 /* AIN2(+) - AIN2(-) */ +#define AD7793_CH_AIN3P_AIN3M 2 /* AIN3(+) - AIN3(-) */ +#define AD7793_CH_AIN1M_AIN1M 3 /* AIN1(-) - AIN1(-) */ +#define AD7793_CH_TEMP 6 /* Temp Sensor */ +#define AD7793_CH_AVDD_MONITOR 7 /* AVDD Monitor */ + +/* ID Register Bit Designations (AD7793_REG_ID) */ +#define AD7792_ID 0xA +#define AD7793_ID 0xB +#define AD7793_ID_MASK 0xF + +/* IO (Excitation Current Sources) Register Bit Designations (AD7793_REG_IO) */ +#define AD7793_IO_IEXC1_IOUT1_IEXC2_IOUT2 0 /* IEXC1 connect to IOUT1, + * IEXC2 connect to IOUT2 */ +#define AD7793_IO_IEXC1_IOUT2_IEXC2_IOUT1 1 /* IEXC1 connect to IOUT2, + * IEXC2 connect to IOUT1 */ +#define AD7793_IO_IEXC1_IEXC2_IOUT1 2 /* Both current sources + * IEXC1,2 connect to IOUT1 */ +#define AD7793_IO_IEXC1_IEXC2_IOUT2 3 /* Both current sources + * IEXC1,2 connect to IOUT2 */ + +#define AD7793_IO_IXCEN_10uA (1 << 0) /* Excitation Current 10uA */ +#define AD7793_IO_IXCEN_210uA (2 << 0) /* Excitation Current 210uA */ +#define AD7793_IO_IXCEN_1mA (3 << 0) /* Excitation Current 1mA */ + +struct ad7793_platform_data { + u16 vref_mv; + u16 mode; + u16 conf; + u8 io; +}; + +#endif /* IIO_ADC_AD7793_H_ */ diff --git a/drivers/staging/iio/adc/ad7816.c b/drivers/staging/iio/adc/ad7816.c new file mode 100644 index 00000000..52b720e2 --- /dev/null +++ b/drivers/staging/iio/adc/ad7816.c @@ -0,0 +1,474 @@ +/* + * AD7816 digital temperature sensor driver supporting AD7816/7/8 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" + +/* + * AD7816 config masks + */ +#define AD7816_FULL 0x1 +#define AD7816_PD 0x2 +#define AD7816_CS_MASK 0x7 +#define AD7816_CS_MAX 0x4 + +/* + * AD7816 temperature masks + */ +#define AD7816_VALUE_OFFSET 6 +#define AD7816_BOUND_VALUE_BASE 0x8 +#define AD7816_BOUND_VALUE_MIN -95 +#define AD7816_BOUND_VALUE_MAX 152 +#define AD7816_TEMP_FLOAT_OFFSET 2 +#define AD7816_TEMP_FLOAT_MASK 0x3 + + +/* + * struct ad7816_chip_info - chip specifc information + */ + +struct ad7816_chip_info { + struct spi_device *spi_dev; + u16 rdwr_pin; + u16 convert_pin; + u16 busy_pin; + u8 oti_data[AD7816_CS_MAX+1]; + u8 channel_id; /* 0 always be temperature */ + u8 mode; +}; + +/* + * ad7816 data access by SPI + */ +static int ad7816_spi_read(struct ad7816_chip_info *chip, u16 *data) +{ + struct spi_device *spi_dev = chip->spi_dev; + int ret = 0; + + gpio_set_value(chip->rdwr_pin, 1); + gpio_set_value(chip->rdwr_pin, 0); + ret = spi_write(spi_dev, &chip->channel_id, sizeof(chip->channel_id)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI channel setting error\n"); + return ret; + } + gpio_set_value(chip->rdwr_pin, 1); + + + if (chip->mode == AD7816_PD) { /* operating mode 2 */ + gpio_set_value(chip->convert_pin, 1); + gpio_set_value(chip->convert_pin, 0); + } else { /* operating mode 1 */ + gpio_set_value(chip->convert_pin, 0); + gpio_set_value(chip->convert_pin, 1); + } + + while (gpio_get_value(chip->busy_pin)) + cpu_relax(); + + gpio_set_value(chip->rdwr_pin, 0); + gpio_set_value(chip->rdwr_pin, 1); + ret = spi_read(spi_dev, (u8 *)data, sizeof(*data)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI data read error\n"); + return ret; + } + + *data = be16_to_cpu(*data); + + return ret; +} + +static int ad7816_spi_write(struct ad7816_chip_info *chip, u8 data) +{ + struct spi_device *spi_dev = chip->spi_dev; + int ret = 0; + + gpio_set_value(chip->rdwr_pin, 1); + gpio_set_value(chip->rdwr_pin, 0); + ret = spi_write(spi_dev, &data, sizeof(data)); + if (ret < 0) + dev_err(&spi_dev->dev, "SPI oti data write error\n"); + + return ret; +} + +static ssize_t ad7816_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + if (chip->mode) + return sprintf(buf, "power-save\n"); + else + return sprintf(buf, "full\n"); +} + +static ssize_t ad7816_store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + if (strcmp(buf, "full")) { + gpio_set_value(chip->rdwr_pin, 1); + chip->mode = AD7816_FULL; + } else { + gpio_set_value(chip->rdwr_pin, 0); + chip->mode = AD7816_PD; + } + + return len; +} + +static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, + ad7816_show_mode, + ad7816_store_mode, + 0); + +static ssize_t ad7816_show_available_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "full\npower-save\n"); +} + +static IIO_DEVICE_ATTR(available_modes, S_IRUGO, ad7816_show_available_modes, NULL, 0); + +static ssize_t ad7816_show_channel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->channel_id); +} + +static ssize_t ad7816_store_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + unsigned long data; + int ret; + + ret = strict_strtoul(buf, 10, &data); + if (ret) + return -EINVAL; + + if (data > AD7816_CS_MAX && data != AD7816_CS_MASK) { + dev_err(&chip->spi_dev->dev, "Invalid channel id %lu for %s.\n", + data, indio_dev->name); + return -EINVAL; + } else if (strcmp(indio_dev->name, "ad7818") == 0 && data > 1) { + dev_err(&chip->spi_dev->dev, + "Invalid channel id %lu for ad7818.\n", data); + return -EINVAL; + } else if (strcmp(indio_dev->name, "ad7816") == 0 && data > 0) { + dev_err(&chip->spi_dev->dev, + "Invalid channel id %lu for ad7816.\n", data); + return -EINVAL; + } + + chip->channel_id = data; + + return len; +} + +static IIO_DEVICE_ATTR(channel, S_IRUGO | S_IWUSR, + ad7816_show_channel, + ad7816_store_channel, + 0); + + +static ssize_t ad7816_show_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + u16 data; + s8 value; + int ret; + + ret = ad7816_spi_read(chip, &data); + if (ret) + return -EIO; + + data >>= AD7816_VALUE_OFFSET; + + if (chip->channel_id == 0) { + value = (s8)((data >> AD7816_TEMP_FLOAT_OFFSET) - 103); + data &= AD7816_TEMP_FLOAT_MASK; + if (value < 0) + data = (1 << AD7816_TEMP_FLOAT_OFFSET) - data; + return sprintf(buf, "%d.%.2d\n", value, data * 25); + } else + return sprintf(buf, "%u\n", data); +} + +static IIO_DEVICE_ATTR(value, S_IRUGO, ad7816_show_value, NULL, 0); + +static struct attribute *ad7816_attributes[] = { + &iio_dev_attr_available_modes.dev_attr.attr, + &iio_dev_attr_mode.dev_attr.attr, + &iio_dev_attr_channel.dev_attr.attr, + &iio_dev_attr_value.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7816_attribute_group = { + .attrs = ad7816_attributes, +}; + +/* + * temperature bound events + */ + +#define IIO_EVENT_CODE_AD7816_OTI IIO_UNMOD_EVENT_CODE(IIO_TEMP, \ + 0, \ + IIO_EV_TYPE_THRESH, \ + IIO_EV_DIR_FALLING) + +static irqreturn_t ad7816_event_handler(int irq, void *private) +{ + iio_push_event(private, IIO_EVENT_CODE_AD7816_OTI, iio_get_time_ns()); + return IRQ_HANDLED; +} + +static ssize_t ad7816_show_oti(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + int value; + + if (chip->channel_id > AD7816_CS_MAX) { + dev_err(dev, "Invalid oti channel id %d.\n", chip->channel_id); + return -EINVAL; + } else if (chip->channel_id == 0) { + value = AD7816_BOUND_VALUE_MIN + + (chip->oti_data[chip->channel_id] - + AD7816_BOUND_VALUE_BASE); + return sprintf(buf, "%d\n", value); + } else + return sprintf(buf, "%u\n", chip->oti_data[chip->channel_id]); +} + +static inline ssize_t ad7816_set_oti(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + long value; + u8 data; + int ret; + + ret = strict_strtol(buf, 10, &value); + + if (chip->channel_id > AD7816_CS_MAX) { + dev_err(dev, "Invalid oti channel id %d.\n", chip->channel_id); + return -EINVAL; + } else if (chip->channel_id == 0) { + if (ret || value < AD7816_BOUND_VALUE_MIN || + value > AD7816_BOUND_VALUE_MAX) + return -EINVAL; + + data = (u8)(value - AD7816_BOUND_VALUE_MIN + + AD7816_BOUND_VALUE_BASE); + } else { + if (ret || value < AD7816_BOUND_VALUE_BASE || value > 255) + return -EINVAL; + + data = (u8)value; + } + + ret = ad7816_spi_write(chip, data); + if (ret) + return -EIO; + + chip->oti_data[chip->channel_id] = data; + + return len; +} + +static IIO_DEVICE_ATTR(oti, S_IRUGO | S_IWUSR, + ad7816_show_oti, ad7816_set_oti, 0); + +static struct attribute *ad7816_event_attributes[] = { + &iio_dev_attr_oti.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7816_event_attribute_group = { + .attrs = ad7816_event_attributes, + .name = "events", +}; + +static const struct iio_info ad7816_info = { + .attrs = &ad7816_attribute_group, + .event_attrs = &ad7816_event_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* + * device probe and remove + */ + +static int __devinit ad7816_probe(struct spi_device *spi_dev) +{ + struct ad7816_chip_info *chip; + struct iio_dev *indio_dev; + unsigned short *pins = spi_dev->dev.platform_data; + int ret = 0; + int i; + + if (!pins) { + dev_err(&spi_dev->dev, "No necessary GPIO platform data.\n"); + return -EINVAL; + } + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + dev_set_drvdata(&spi_dev->dev, indio_dev); + + chip->spi_dev = spi_dev; + for (i = 0; i <= AD7816_CS_MAX; i++) + chip->oti_data[i] = 203; + chip->rdwr_pin = pins[0]; + chip->convert_pin = pins[1]; + chip->busy_pin = pins[2]; + + ret = gpio_request(chip->rdwr_pin, spi_get_device_id(spi_dev)->name); + if (ret) { + dev_err(&spi_dev->dev, "Fail to request rdwr gpio PIN %d.\n", + chip->rdwr_pin); + goto error_free_device; + } + gpio_direction_input(chip->rdwr_pin); + ret = gpio_request(chip->convert_pin, spi_get_device_id(spi_dev)->name); + if (ret) { + dev_err(&spi_dev->dev, "Fail to request convert gpio PIN %d.\n", + chip->convert_pin); + goto error_free_gpio_rdwr; + } + gpio_direction_input(chip->convert_pin); + ret = gpio_request(chip->busy_pin, spi_get_device_id(spi_dev)->name); + if (ret) { + dev_err(&spi_dev->dev, "Fail to request busy gpio PIN %d.\n", + chip->busy_pin); + goto error_free_gpio_convert; + } + gpio_direction_input(chip->busy_pin); + + indio_dev->name = spi_get_device_id(spi_dev)->name; + indio_dev->dev.parent = &spi_dev->dev; + indio_dev->info = &ad7816_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (spi_dev->irq) { + /* Only low trigger is supported in ad7816/7/8 */ + ret = request_threaded_irq(spi_dev->irq, + NULL, + &ad7816_event_handler, + IRQF_TRIGGER_LOW, + indio_dev->name, + indio_dev); + if (ret) + goto error_free_gpio; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_irq; + + dev_info(&spi_dev->dev, "%s temperature sensor and ADC registered.\n", + indio_dev->name); + + return 0; +error_free_irq: + free_irq(spi_dev->irq, indio_dev); +error_free_gpio: + gpio_free(chip->busy_pin); +error_free_gpio_convert: + gpio_free(chip->convert_pin); +error_free_gpio_rdwr: + gpio_free(chip->rdwr_pin); +error_free_device: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit ad7816_remove(struct spi_device *spi_dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&spi_dev->dev); + struct ad7816_chip_info *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + dev_set_drvdata(&spi_dev->dev, NULL); + if (spi_dev->irq) + free_irq(spi_dev->irq, indio_dev); + gpio_free(chip->busy_pin); + gpio_free(chip->convert_pin); + gpio_free(chip->rdwr_pin); + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad7816_id[] = { + { "ad7816", 0 }, + { "ad7817", 0 }, + { "ad7818", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(spi, ad7816_id); + +static struct spi_driver ad7816_driver = { + .driver = { + .name = "ad7816", + .owner = THIS_MODULE, + }, + .probe = ad7816_probe, + .remove = __devexit_p(ad7816_remove), + .id_table = ad7816_id, +}; +module_spi_driver(ad7816_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices AD7816/7/8 digital" + " temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7887.h b/drivers/staging/iio/adc/ad7887.h new file mode 100644 index 00000000..bc53b653 --- /dev/null +++ b/drivers/staging/iio/adc/ad7887.h @@ -0,0 +1,100 @@ +/* + * AD7887 SPI ADC driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ +#ifndef IIO_ADC_AD7887_H_ +#define IIO_ADC_AD7887_H_ + +#define AD7887_REF_DIS (1 << 5) /* on-chip reference disable */ +#define AD7887_DUAL (1 << 4) /* dual-channel mode */ +#define AD7887_CH_AIN1 (1 << 3) /* convert on channel 1, DUAL=1 */ +#define AD7887_CH_AIN0 (0 << 3) /* convert on channel 0, DUAL=0,1 */ +#define AD7887_PM_MODE1 (0) /* CS based shutdown */ +#define AD7887_PM_MODE2 (1) /* full on */ +#define AD7887_PM_MODE3 (2) /* auto shutdown after conversion */ +#define AD7887_PM_MODE4 (3) /* standby mode */ + +enum ad7887_channels { + AD7887_CH0, + AD7887_CH0_CH1, + AD7887_CH1, +}; + +#define RES_MASK(bits) ((1 << (bits)) - 1) /* TODO: move this into a common header */ + +/* + * TODO: struct ad7887_platform_data needs to go into include/linux/iio + */ + +struct ad7887_platform_data { + /* External Vref voltage applied */ + u16 vref_mv; + /* + * AD7887: + * In single channel mode en_dual = flase, AIN1/Vref pins assumes its + * Vref function. In dual channel mode en_dual = true, AIN1 becomes the + * second input channel, and Vref is internally connected to Vdd. + */ + bool en_dual; + /* + * AD7887: + * use_onchip_ref = true, the Vref is internally connected to the 2.500V + * Voltage reference. If use_onchip_ref = false, the reference voltage + * is supplied by AIN1/Vref + */ + bool use_onchip_ref; +}; + +/** + * struct ad7887_chip_info - chip specifc information + * @int_vref_mv: the internal reference voltage + * @channel: channel specification + */ + +struct ad7887_chip_info { + u16 int_vref_mv; + struct iio_chan_spec channel[3]; +}; + +struct ad7887_state { + struct spi_device *spi; + const struct ad7887_chip_info *chip_info; + struct regulator *reg; + size_t d_size; + u16 int_vref_mv; + struct spi_transfer xfer[4]; + struct spi_message msg[3]; + struct spi_message *ring_msg; + unsigned char tx_cmd_buf[8]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + + unsigned char data[4] ____cacheline_aligned; +}; + +enum ad7887_supported_device_ids { + ID_AD7887 +}; + +#ifdef CONFIG_IIO_BUFFER +int ad7887_register_ring_funcs_and_init(struct iio_dev *indio_dev); +void ad7887_ring_cleanup(struct iio_dev *indio_dev); +#else /* CONFIG_IIO_BUFFER */ + +static inline int +ad7887_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void ad7887_ring_cleanup(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_IIO_BUFFER */ +#endif /* IIO_ADC_AD7887_H_ */ diff --git a/drivers/staging/iio/adc/ad7887_core.c b/drivers/staging/iio/adc/ad7887_core.c new file mode 100644 index 00000000..e9bbc3ee --- /dev/null +++ b/drivers/staging/iio/adc/ad7887_core.c @@ -0,0 +1,264 @@ +/* + * AD7887 SPI ADC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + + +#include "ad7887.h" + +static int ad7887_scan_direct(struct ad7887_state *st, unsigned ch) +{ + int ret = spi_sync(st->spi, &st->msg[ch]); + if (ret) + return ret; + + return (st->data[(ch * 2)] << 8) | st->data[(ch * 2) + 1]; +} + +static int ad7887_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad7887_state *st = iio_priv(indio_dev); + unsigned int scale_uv; + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad7887_scan_direct(st, chan->address); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + *val = (ret >> st->chip_info->channel[0].scan_type.shift) & + RES_MASK(st->chip_info->channel[0].scan_type.realbits); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->int_vref_mv * 1000) + >> st->chip_info->channel[0].scan_type.realbits; + *val = scale_uv/1000; + *val2 = (scale_uv%1000)*1000; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + + +static const struct ad7887_chip_info ad7887_chip_info_tbl[] = { + /* + * More devices added in future + */ + [ID_AD7887] = { + .channel[0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = 1, + .scan_index = 1, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + .channel[1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = 0, + .scan_index = 0, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + .channel[2] = IIO_CHAN_SOFT_TIMESTAMP(2), + .int_vref_mv = 2500, + }, +}; + +static const struct iio_info ad7887_info = { + .read_raw = &ad7887_read_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad7887_probe(struct spi_device *spi) +{ + struct ad7887_platform_data *pdata = spi->dev.platform_data; + struct ad7887_state *st; + int ret, voltage_uv = 0; + struct iio_dev *indio_dev = iio_allocate_device(sizeof(*st)); + + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(st->reg); + } + + st->chip_info = + &ad7887_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + spi_set_drvdata(spi, indio_dev); + st->spi = spi; + + /* Estabilish that the iio_dev is a child of the spi device */ + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad7887_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Setup default message */ + + st->tx_cmd_buf[0] = AD7887_CH_AIN0 | AD7887_PM_MODE4 | + ((pdata && pdata->use_onchip_ref) ? + 0 : AD7887_REF_DIS); + + st->xfer[0].rx_buf = &st->data[0]; + st->xfer[0].tx_buf = &st->tx_cmd_buf[0]; + st->xfer[0].len = 2; + + spi_message_init(&st->msg[AD7887_CH0]); + spi_message_add_tail(&st->xfer[0], &st->msg[AD7887_CH0]); + + if (pdata && pdata->en_dual) { + st->tx_cmd_buf[0] |= AD7887_DUAL | AD7887_REF_DIS; + + st->tx_cmd_buf[2] = AD7887_CH_AIN1 | AD7887_DUAL | + AD7887_REF_DIS | AD7887_PM_MODE4; + st->tx_cmd_buf[4] = AD7887_CH_AIN0 | AD7887_DUAL | + AD7887_REF_DIS | AD7887_PM_MODE4; + st->tx_cmd_buf[6] = AD7887_CH_AIN1 | AD7887_DUAL | + AD7887_REF_DIS | AD7887_PM_MODE4; + + st->xfer[1].rx_buf = &st->data[0]; + st->xfer[1].tx_buf = &st->tx_cmd_buf[2]; + st->xfer[1].len = 2; + + st->xfer[2].rx_buf = &st->data[2]; + st->xfer[2].tx_buf = &st->tx_cmd_buf[4]; + st->xfer[2].len = 2; + + spi_message_init(&st->msg[AD7887_CH0_CH1]); + spi_message_add_tail(&st->xfer[1], &st->msg[AD7887_CH0_CH1]); + spi_message_add_tail(&st->xfer[2], &st->msg[AD7887_CH0_CH1]); + + st->xfer[3].rx_buf = &st->data[0]; + st->xfer[3].tx_buf = &st->tx_cmd_buf[6]; + st->xfer[3].len = 2; + + spi_message_init(&st->msg[AD7887_CH1]); + spi_message_add_tail(&st->xfer[3], &st->msg[AD7887_CH1]); + + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (voltage_uv) + st->int_vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, "reference voltage unspecified\n"); + + indio_dev->channels = st->chip_info->channel; + indio_dev->num_channels = 3; + } else { + if (pdata && pdata->vref_mv) + st->int_vref_mv = pdata->vref_mv; + else if (pdata && pdata->use_onchip_ref) + st->int_vref_mv = st->chip_info->int_vref_mv; + else + dev_warn(&spi->dev, "reference voltage unspecified\n"); + + indio_dev->channels = &st->chip_info->channel[1]; + indio_dev->num_channels = 2; + } + + ret = ad7887_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_disable_reg; + + ret = iio_buffer_register(indio_dev, + indio_dev->channels, + indio_dev->num_channels); + if (ret) + goto error_cleanup_ring; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unregister_ring; + + return 0; +error_unregister_ring: + iio_buffer_unregister(indio_dev); +error_cleanup_ring: + ad7887_ring_cleanup(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + iio_free_device(indio_dev); + + return ret; +} + +static int ad7887_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad7887_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); + ad7887_ring_cleanup(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad7887_id[] = { + {"ad7887", ID_AD7887}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad7887_id); + +static struct spi_driver ad7887_driver = { + .driver = { + .name = "ad7887", + .owner = THIS_MODULE, + }, + .probe = ad7887_probe, + .remove = __devexit_p(ad7887_remove), + .id_table = ad7887_id, +}; +module_spi_driver(ad7887_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7887 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad7887_ring.c b/drivers/staging/iio/adc/ad7887_ring.c new file mode 100644 index 00000000..d1809079 --- /dev/null +++ b/drivers/staging/iio/adc/ad7887_ring.c @@ -0,0 +1,161 @@ +/* + * Copyright 2010-2011 Analog Devices Inc. + * Copyright (C) 2008 Jonathan Cameron + * + * Licensed under the GPL-2. + * + * ad7887_ring.c + */ + +#include +#include +#include +#include + +#include "../iio.h" +#include "../buffer.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" + +#include "ad7887.h" + +/** + * ad7887_ring_preenable() setup the parameters of the ring before enabling + * + * The complex nature of the setting of the nuber of bytes per datum is due + * to this driver currently ensuring that the timestamp is stored at an 8 + * byte boundary. + **/ +static int ad7887_ring_preenable(struct iio_dev *indio_dev) +{ + struct ad7887_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + + st->d_size = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * + st->chip_info->channel[0].scan_type.storagebits / 8; + + if (ring->scan_timestamp) { + st->d_size += sizeof(s64); + + if (st->d_size % sizeof(s64)) + st->d_size += sizeof(s64) - (st->d_size % sizeof(s64)); + } + + if (indio_dev->buffer->access->set_bytes_per_datum) + indio_dev->buffer->access-> + set_bytes_per_datum(indio_dev->buffer, st->d_size); + + /* We know this is a single long so can 'cheat' */ + switch (*indio_dev->active_scan_mask) { + case (1 << 0): + st->ring_msg = &st->msg[AD7887_CH0]; + break; + case (1 << 1): + st->ring_msg = &st->msg[AD7887_CH1]; + /* Dummy read: push CH1 setting down to hardware */ + spi_sync(st->spi, st->ring_msg); + break; + case ((1 << 1) | (1 << 0)): + st->ring_msg = &st->msg[AD7887_CH0_CH1]; + break; + } + + return 0; +} + +static int ad7887_ring_postdisable(struct iio_dev *indio_dev) +{ + struct ad7887_state *st = iio_priv(indio_dev); + + /* dummy read: restore default CH0 settin */ + return spi_sync(st->spi, &st->msg[AD7887_CH0]); +} + +/** + * ad7887_trigger_handler() bh of trigger launched polling to ring buffer + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + **/ +static irqreturn_t ad7887_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad7887_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + s64 time_ns; + __u8 *buf; + int b_sent; + + unsigned int bytes = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * + st->chip_info->channel[0].scan_type.storagebits / 8; + + buf = kzalloc(st->d_size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + b_sent = spi_sync(st->spi, st->ring_msg); + if (b_sent) + goto done; + + time_ns = iio_get_time_ns(); + + memcpy(buf, st->data, bytes); + if (ring->scan_timestamp) + memcpy(buf + st->d_size - sizeof(s64), + &time_ns, sizeof(time_ns)); + + indio_dev->buffer->access->store_to(indio_dev->buffer, buf, time_ns); +done: + kfree(buf); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ad7887_ring_setup_ops = { + .preenable = &ad7887_ring_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &ad7887_ring_postdisable, +}; + +int ad7887_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + int ret; + + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + goto error_ret; + } + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &ad7887_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "ad7887_consumer%d", + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_deallocate_sw_rb; + } + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &ad7887_ring_setup_ops; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_deallocate_sw_rb: + iio_sw_rb_free(indio_dev->buffer); +error_ret: + return ret; +} + +void ad7887_ring_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} diff --git a/drivers/staging/iio/adc/ad799x.h b/drivers/staging/iio/adc/ad799x.h new file mode 100644 index 00000000..356f690a --- /dev/null +++ b/drivers/staging/iio/adc/ad799x.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010-2011 Michael Hennerich, Analog Devices Inc. + * Copyright (C) 2008-2010 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ad799x.h + */ + +#ifndef _AD799X_H_ +#define _AD799X_H_ + +#define AD799X_CHANNEL_SHIFT 4 +#define AD799X_STORAGEBITS 16 +/* + * AD7991, AD7995 and AD7999 defines + */ + +#define AD7991_REF_SEL 0x08 +#define AD7991_FLTR 0x04 +#define AD7991_BIT_TRIAL_DELAY 0x02 +#define AD7991_SAMPLE_DELAY 0x01 + +/* + * AD7992, AD7993, AD7994, AD7997 and AD7998 defines + */ + +#define AD7998_FLTR 0x08 +#define AD7998_ALERT_EN 0x04 +#define AD7998_BUSY_ALERT 0x02 +#define AD7998_BUSY_ALERT_POL 0x01 + +#define AD7998_CONV_RES_REG 0x0 +#define AD7998_ALERT_STAT_REG 0x1 +#define AD7998_CONF_REG 0x2 +#define AD7998_CYCLE_TMR_REG 0x3 +#define AD7998_DATALOW_CH1_REG 0x4 +#define AD7998_DATAHIGH_CH1_REG 0x5 +#define AD7998_HYST_CH1_REG 0x6 +#define AD7998_DATALOW_CH2_REG 0x7 +#define AD7998_DATAHIGH_CH2_REG 0x8 +#define AD7998_HYST_CH2_REG 0x9 +#define AD7998_DATALOW_CH3_REG 0xA +#define AD7998_DATAHIGH_CH3_REG 0xB +#define AD7998_HYST_CH3_REG 0xC +#define AD7998_DATALOW_CH4_REG 0xD +#define AD7998_DATAHIGH_CH4_REG 0xE +#define AD7998_HYST_CH4_REG 0xF + +#define AD7998_CYC_MASK 0x7 +#define AD7998_CYC_DIS 0x0 +#define AD7998_CYC_TCONF_32 0x1 +#define AD7998_CYC_TCONF_64 0x2 +#define AD7998_CYC_TCONF_128 0x3 +#define AD7998_CYC_TCONF_256 0x4 +#define AD7998_CYC_TCONF_512 0x5 +#define AD7998_CYC_TCONF_1024 0x6 +#define AD7998_CYC_TCONF_2048 0x7 + +#define AD7998_ALERT_STAT_CLEAR 0xFF + +/* + * AD7997 and AD7997 defines + */ + +#define AD7997_8_READ_SINGLE 0x80 +#define AD7997_8_READ_SEQUENCE 0x70 +/* TODO: move this into a common header */ +#define RES_MASK(bits) ((1 << (bits)) - 1) + +enum { + ad7991, + ad7995, + ad7999, + ad7992, + ad7993, + ad7994, + ad7997, + ad7998 +}; + +struct ad799x_state; + +/** + * struct ad799x_chip_info - chip specifc information + * @channel: channel specification + * @num_channels: number of channels + * @int_vref_mv: the internal reference voltage + * @monitor_mode: whether the chip supports monitor interrupts + * @default_config: device default configuration + * @event_attrs: pointer to the monitor event attribute group + */ + +struct ad799x_chip_info { + struct iio_chan_spec channel[9]; + int num_channels; + u16 int_vref_mv; + u16 default_config; + const struct iio_info *info; +}; + +struct ad799x_state { + struct i2c_client *client; + const struct ad799x_chip_info *chip_info; + size_t d_size; + struct iio_trigger *trig; + struct regulator *reg; + u16 int_vref_mv; + unsigned id; + char *name; + u16 config; +}; + +/* + * TODO: struct ad799x_platform_data needs to go into include/linux/iio + */ + +struct ad799x_platform_data { + u16 vref_mv; +}; + +int ad7997_8_set_scan_mode(struct ad799x_state *st, unsigned mask); + +#ifdef CONFIG_AD799X_RING_BUFFER +int ad799x_register_ring_funcs_and_init(struct iio_dev *indio_dev); +void ad799x_ring_cleanup(struct iio_dev *indio_dev); +#else /* CONFIG_AD799X_RING_BUFFER */ + +static inline int +ad799x_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void ad799x_ring_cleanup(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_AD799X_RING_BUFFER */ +#endif /* _AD799X_H_ */ diff --git a/drivers/staging/iio/adc/ad799x_core.c b/drivers/staging/iio/adc/ad799x_core.c new file mode 100644 index 00000000..a8458669 --- /dev/null +++ b/drivers/staging/iio/adc/ad799x_core.c @@ -0,0 +1,936 @@ +/* + * iio/adc/ad799x.c + * Copyright (C) 2010-1011 Michael Hennerich, Analog Devices Inc. + * + * based on iio/adc/max1363 + * Copyright (C) 2008-2010 Jonathan Cameron + * + * based on linux/drivers/i2c/chips/max123x + * Copyright (C) 2002-2004 Stefan Eletzhofer + * + * based on linux/drivers/acron/char/pcf8583.c + * Copyright (C) 2000 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ad799x.c + * + * Support for ad7991, ad7995, ad7999, ad7992, ad7993, ad7994, ad7997, + * ad7998 and similar chips. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" +#include "../buffer.h" + +#include "ad799x.h" + +/* + * ad799x register access by I2C + */ +static int ad799x_i2c_read16(struct ad799x_state *st, u8 reg, u16 *data) +{ + struct i2c_client *client = st->client; + int ret = 0; + + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + + *data = swab16((u16)ret); + + return 0; +} + +static int ad799x_i2c_read8(struct ad799x_state *st, u8 reg, u8 *data) +{ + struct i2c_client *client = st->client; + int ret = 0; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + + *data = (u8)ret; + + return 0; +} + +static int ad799x_i2c_write16(struct ad799x_state *st, u8 reg, u16 data) +{ + struct i2c_client *client = st->client; + int ret = 0; + + ret = i2c_smbus_write_word_data(client, reg, swab16(data)); + if (ret < 0) + dev_err(&client->dev, "I2C write error\n"); + + return ret; +} + +static int ad799x_i2c_write8(struct ad799x_state *st, u8 reg, u8 data) +{ + struct i2c_client *client = st->client; + int ret = 0; + + ret = i2c_smbus_write_byte_data(client, reg, data); + if (ret < 0) + dev_err(&client->dev, "I2C write error\n"); + + return ret; +} + +int ad7997_8_set_scan_mode(struct ad799x_state *st, unsigned mask) +{ + return ad799x_i2c_write16(st, AD7998_CONF_REG, + st->config | (mask << AD799X_CHANNEL_SHIFT)); +} + +static int ad799x_scan_direct(struct ad799x_state *st, unsigned ch) +{ + u16 rxbuf; + u8 cmd; + int ret; + + switch (st->id) { + case ad7991: + case ad7995: + case ad7999: + cmd = st->config | ((1 << ch) << AD799X_CHANNEL_SHIFT); + break; + case ad7992: + case ad7993: + case ad7994: + cmd = (1 << ch) << AD799X_CHANNEL_SHIFT; + break; + case ad7997: + case ad7998: + cmd = (ch << AD799X_CHANNEL_SHIFT) | AD7997_8_READ_SINGLE; + break; + default: + return -EINVAL; + } + + ret = ad799x_i2c_read16(st, cmd, &rxbuf); + if (ret < 0) + return ret; + + return rxbuf; +} + +static int ad799x_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad799x_state *st = iio_priv(indio_dev); + unsigned int scale_uv; + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = ad799x_scan_direct(st, chan->scan_index); + mutex_unlock(&indio_dev->mlock); + + if (ret < 0) + return ret; + *val = (ret >> chan->scan_type.shift) & + RES_MASK(chan->scan_type.realbits); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->int_vref_mv * 1000) >> chan->scan_type.realbits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} +static const unsigned int ad7998_frequencies[] = { + [AD7998_CYC_DIS] = 0, + [AD7998_CYC_TCONF_32] = 15625, + [AD7998_CYC_TCONF_64] = 7812, + [AD7998_CYC_TCONF_128] = 3906, + [AD7998_CYC_TCONF_512] = 976, + [AD7998_CYC_TCONF_1024] = 488, + [AD7998_CYC_TCONF_2048] = 244, +}; +static ssize_t ad799x_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad799x_state *st = iio_priv(indio_dev); + + int ret; + u8 val; + ret = ad799x_i2c_read8(st, AD7998_CYCLE_TMR_REG, &val); + if (ret) + return ret; + + val &= AD7998_CYC_MASK; + + return sprintf(buf, "%u\n", ad7998_frequencies[val]); +} + +static ssize_t ad799x_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad799x_state *st = iio_priv(indio_dev); + + long val; + int ret, i; + u8 t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + ret = ad799x_i2c_read8(st, AD7998_CYCLE_TMR_REG, &t); + if (ret) + goto error_ret_mutex; + /* Wipe the bits clean */ + t &= ~AD7998_CYC_MASK; + + for (i = 0; i < ARRAY_SIZE(ad7998_frequencies); i++) + if (val == ad7998_frequencies[i]) + break; + if (i == ARRAY_SIZE(ad7998_frequencies)) { + ret = -EINVAL; + goto error_ret_mutex; + } + t |= i; + ret = ad799x_i2c_write8(st, AD7998_CYCLE_TMR_REG, t); + +error_ret_mutex: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int ad799x_read_event_config(struct iio_dev *indio_dev, + u64 event_code) +{ + return 1; +} + +static const u8 ad799x_threshold_addresses[][2] = { + { AD7998_DATALOW_CH1_REG, AD7998_DATAHIGH_CH1_REG }, + { AD7998_DATALOW_CH2_REG, AD7998_DATAHIGH_CH2_REG }, + { AD7998_DATALOW_CH3_REG, AD7998_DATAHIGH_CH3_REG }, + { AD7998_DATALOW_CH4_REG, AD7998_DATAHIGH_CH4_REG }, +}; + +static int ad799x_write_event_value(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + int ret; + struct ad799x_state *st = iio_priv(indio_dev); + int direction = !!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_FALLING); + int number = IIO_EVENT_CODE_EXTRACT_CHAN(event_code); + + mutex_lock(&indio_dev->mlock); + ret = ad799x_i2c_write16(st, + ad799x_threshold_addresses[number][direction], + val); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad799x_read_event_value(struct iio_dev *indio_dev, + u64 event_code, + int *val) +{ + int ret; + struct ad799x_state *st = iio_priv(indio_dev); + int direction = !!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_FALLING); + int number = IIO_EVENT_CODE_EXTRACT_CHAN(event_code); + u16 valin; + + mutex_lock(&indio_dev->mlock); + ret = ad799x_i2c_read16(st, + ad799x_threshold_addresses[number][direction], + &valin); + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + *val = valin; + + return 0; +} + +static ssize_t ad799x_read_channel_config(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad799x_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + int ret; + u16 val; + ret = ad799x_i2c_read16(st, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t ad799x_write_channel_config(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad799x_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + long val; + int ret; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + ret = ad799x_i2c_write16(st, this_attr->address, val); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static irqreturn_t ad799x_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad799x_state *st = iio_priv(private); + u8 status; + int i, ret; + + ret = ad799x_i2c_read8(st, AD7998_ALERT_STAT_REG, &status); + if (ret) + return ret; + + if (!status) + return -EIO; + + ad799x_i2c_write8(st, AD7998_ALERT_STAT_REG, AD7998_ALERT_STAT_CLEAR); + + for (i = 0; i < 8; i++) { + if (status & (1 << i)) + iio_push_event(indio_dev, + i & 0x1 ? + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + (i >> 1), + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING) : + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + (i >> 1), + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } + + return IRQ_HANDLED; +} + +static IIO_DEVICE_ATTR(in_voltage0_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad799x_read_channel_config, + ad799x_write_channel_config, + AD7998_HYST_CH1_REG); + +static IIO_DEVICE_ATTR(in_voltage1_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad799x_read_channel_config, + ad799x_write_channel_config, + AD7998_HYST_CH2_REG); + +static IIO_DEVICE_ATTR(in_voltage2_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad799x_read_channel_config, + ad799x_write_channel_config, + AD7998_HYST_CH3_REG); + +static IIO_DEVICE_ATTR(in_voltage3_thresh_both_hyst_raw, + S_IRUGO | S_IWUSR, + ad799x_read_channel_config, + ad799x_write_channel_config, + AD7998_HYST_CH4_REG); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ad799x_read_frequency, + ad799x_write_frequency); +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("15625 7812 3906 1953 976 488 244 0"); + +static struct attribute *ad7993_4_7_8_event_attributes[] = { + &iio_dev_attr_in_voltage0_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage1_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage2_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage3_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7993_4_7_8_event_attrs_group = { + .attrs = ad7993_4_7_8_event_attributes, + .name = "events", +}; + +static struct attribute *ad7992_event_attributes[] = { + &iio_dev_attr_in_voltage0_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_in_voltage1_thresh_both_hyst_raw.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7992_event_attrs_group = { + .attrs = ad7992_event_attributes, + .name = "events", +}; + +static const struct iio_info ad7991_info = { + .read_raw = &ad799x_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad7992_info = { + .read_raw = &ad799x_read_raw, + .event_attrs = &ad7992_event_attrs_group, + .read_event_config = &ad799x_read_event_config, + .read_event_value = &ad799x_read_event_value, + .write_event_value = &ad799x_write_event_value, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad7993_4_7_8_info = { + .read_raw = &ad799x_read_raw, + .event_attrs = &ad7993_4_7_8_event_attrs_group, + .read_event_config = &ad799x_read_event_config, + .read_event_value = &ad799x_read_event_value, + .write_event_value = &ad799x_write_event_value, + .driver_module = THIS_MODULE, +}; + +#define AD799X_EV_MASK (IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) | \ + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING)) + +static const struct ad799x_chip_info ad799x_chip_info_tbl[] = { + [ad7991] = { + .channel = { + [0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .scan_index = 0, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + [1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .scan_index = 1, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + [2] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .scan_index = 2, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + [3] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 3, + .scan_index = 3, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + [4] = IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .num_channels = 5, + .int_vref_mv = 4096, + .info = &ad7991_info, + }, + [ad7995] = { + .channel = { + [0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .scan_index = 0, + .scan_type = IIO_ST('u', 10, 16, 2), + }, + [1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .scan_index = 1, + .scan_type = IIO_ST('u', 10, 16, 2), + }, + [2] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .scan_index = 2, + .scan_type = IIO_ST('u', 10, 16, 2), + }, + [3] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 3, + .scan_index = 3, + .scan_type = IIO_ST('u', 10, 16, 2), + }, + [4] = IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .num_channels = 5, + .int_vref_mv = 1024, + .info = &ad7991_info, + }, + [ad7999] = { + .channel = { + [0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .scan_index = 0, + .scan_type = IIO_ST('u', 8, 16, 4), + }, + [1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .scan_index = 1, + .scan_type = IIO_ST('u', 8, 16, 4), + }, + [2] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .scan_index = 2, + .scan_type = IIO_ST('u', 8, 16, 4), + }, + [3] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 3, + .scan_index = 3, + .scan_type = IIO_ST('u', 8, 16, 4), + }, + [4] = IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .num_channels = 5, + .int_vref_mv = 1024, + .info = &ad7991_info, + }, + [ad7992] = { + .channel = { + [0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .scan_index = 0, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .scan_index = 1, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [2] = IIO_CHAN_SOFT_TIMESTAMP(2), + }, + .num_channels = 3, + .int_vref_mv = 4096, + .default_config = AD7998_ALERT_EN, + .info = &ad7992_info, + }, + [ad7993] = { + .channel = { + [0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .scan_index = 0, + .scan_type = IIO_ST('u', 10, 16, 2), + .event_mask = AD799X_EV_MASK, + }, + [1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .scan_index = 1, + .scan_type = IIO_ST('u', 10, 16, 2), + .event_mask = AD799X_EV_MASK, + }, + [2] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .scan_index = 2, + .scan_type = IIO_ST('u', 10, 16, 2), + .event_mask = AD799X_EV_MASK, + }, + [3] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 3, + .scan_index = 3, + .scan_type = IIO_ST('u', 10, 16, 2), + .event_mask = AD799X_EV_MASK, + }, + [4] = IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .num_channels = 5, + .int_vref_mv = 1024, + .default_config = AD7998_ALERT_EN, + .info = &ad7993_4_7_8_info, + }, + [ad7994] = { + .channel = { + [0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .scan_index = 0, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .scan_index = 1, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [2] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .scan_index = 2, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [3] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 3, + .scan_index = 3, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [4] = IIO_CHAN_SOFT_TIMESTAMP(4), + }, + .num_channels = 5, + .int_vref_mv = 4096, + .default_config = AD7998_ALERT_EN, + .info = &ad7993_4_7_8_info, + }, + [ad7997] = { + .channel = { + [0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .scan_index = 0, + .scan_type = IIO_ST('u', 10, 16, 2), + .event_mask = AD799X_EV_MASK, + }, + [1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .scan_index = 1, + .scan_type = IIO_ST('u', 10, 16, 2), + .event_mask = AD799X_EV_MASK, + }, + [2] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .scan_index = 2, + .scan_type = IIO_ST('u', 10, 16, 2), + .event_mask = AD799X_EV_MASK, + }, + [3] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 3, + .scan_index = 3, + .scan_type = IIO_ST('u', 10, 16, 2), + .event_mask = AD799X_EV_MASK, + }, + [4] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 4, + .scan_index = 4, + .scan_type = IIO_ST('u', 10, 16, 2), + }, + [5] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 5, + .scan_index = 5, + .scan_type = IIO_ST('u', 10, 16, 2), + }, + [6] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 6, + .scan_index = 6, + .scan_type = IIO_ST('u', 10, 16, 2), + }, + [7] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 7, + .scan_index = 7, + .scan_type = IIO_ST('u', 10, 16, 2), + }, + [8] = IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .num_channels = 9, + .int_vref_mv = 1024, + .default_config = AD7998_ALERT_EN, + .info = &ad7993_4_7_8_info, + }, + [ad7998] = { + .channel = { + [0] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .scan_index = 0, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [1] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .scan_index = 1, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [2] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 2, + .scan_index = 2, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [3] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 3, + .scan_index = 3, + .scan_type = IIO_ST('u', 12, 16, 0), + .event_mask = AD799X_EV_MASK, + }, + [4] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 4, + .scan_index = 4, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + [5] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 5, + .scan_index = 5, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + [6] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 6, + .scan_index = 6, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + [7] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 7, + .scan_index = 7, + .scan_type = IIO_ST('u', 12, 16, 0), + }, + [8] = IIO_CHAN_SOFT_TIMESTAMP(8), + }, + .num_channels = 9, + .int_vref_mv = 4096, + .default_config = AD7998_ALERT_EN, + .info = &ad7993_4_7_8_info, + }, +}; + +static int __devinit ad799x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct ad799x_platform_data *pdata = client->dev.platform_data; + struct ad799x_state *st; + struct iio_dev *indio_dev = iio_allocate_device(sizeof(*st)); + + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + st->id = id->driver_data; + st->chip_info = &ad799x_chip_info_tbl[st->id]; + st->config = st->chip_info->default_config; + + /* TODO: Add pdata options for filtering and bit delay */ + + if (pdata) + st->int_vref_mv = pdata->vref_mv; + else + st->int_vref_mv = st->chip_info->int_vref_mv; + + st->reg = regulator_get(&client->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + } + st->client = client; + + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->info = st->chip_info->info; + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channel; + indio_dev->num_channels = st->chip_info->num_channels; + + ret = ad799x_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_disable_reg; + + ret = iio_buffer_register(indio_dev, + indio_dev->channels, + indio_dev->num_channels); + if (ret) + goto error_cleanup_ring; + + if (client->irq > 0) { + ret = request_threaded_irq(client->irq, + NULL, + ad799x_event_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + client->name, + indio_dev); + if (ret) + goto error_cleanup_ring; + } + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(client->irq, indio_dev); +error_cleanup_ring: + ad799x_ring_cleanup(indio_dev); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + iio_free_device(indio_dev); + + return ret; +} + +static __devexit int ad799x_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ad799x_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (client->irq > 0) + free_irq(client->irq, indio_dev); + + iio_buffer_unregister(indio_dev); + ad799x_ring_cleanup(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id ad799x_id[] = { + { "ad7991", ad7991 }, + { "ad7995", ad7995 }, + { "ad7999", ad7999 }, + { "ad7992", ad7992 }, + { "ad7993", ad7993 }, + { "ad7994", ad7994 }, + { "ad7997", ad7997 }, + { "ad7998", ad7998 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad799x_id); + +static struct i2c_driver ad799x_driver = { + .driver = { + .name = "ad799x", + }, + .probe = ad799x_probe, + .remove = __devexit_p(ad799x_remove), + .id_table = ad799x_id, +}; +module_i2c_driver(ad799x_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD799x ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/ad799x_ring.c b/drivers/staging/iio/adc/ad799x_ring.c new file mode 100644 index 00000000..069765ca --- /dev/null +++ b/drivers/staging/iio/adc/ad799x_ring.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2010 Michael Hennerich, Analog Devices Inc. + * Copyright (C) 2008-2010 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ad799x_ring.c + */ + +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../buffer.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" + +#include "ad799x.h" + +/** + * ad799x_ring_preenable() setup the parameters of the ring before enabling + * + * The complex nature of the setting of the number of bytes per datum is due + * to this driver currently ensuring that the timestamp is stored at an 8 + * byte boundary. + **/ +static int ad799x_ring_preenable(struct iio_dev *indio_dev) +{ + struct iio_buffer *ring = indio_dev->buffer; + struct ad799x_state *st = iio_priv(indio_dev); + + /* + * Need to figure out the current mode based upon the requested + * scan mask in iio_dev + */ + + if (st->id == ad7997 || st->id == ad7998) + ad7997_8_set_scan_mode(st, *indio_dev->active_scan_mask); + + st->d_size = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * 2; + + if (ring->scan_timestamp) { + st->d_size += sizeof(s64); + + if (st->d_size % sizeof(s64)) + st->d_size += sizeof(s64) - (st->d_size % sizeof(s64)); + } + + if (indio_dev->buffer->access->set_bytes_per_datum) + indio_dev->buffer->access-> + set_bytes_per_datum(indio_dev->buffer, st->d_size); + + return 0; +} + +/** + * ad799x_trigger_handler() bh of trigger launched polling to ring buffer + * + * Currently there is no option in this driver to disable the saving of + * timestamps within the ring. + **/ + +static irqreturn_t ad799x_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ad799x_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + s64 time_ns; + __u8 *rxbuf; + int b_sent; + u8 cmd; + + rxbuf = kmalloc(st->d_size, GFP_KERNEL); + if (rxbuf == NULL) + goto out; + + switch (st->id) { + case ad7991: + case ad7995: + case ad7999: + cmd = st->config | + (*indio_dev->active_scan_mask << AD799X_CHANNEL_SHIFT); + break; + case ad7992: + case ad7993: + case ad7994: + cmd = (*indio_dev->active_scan_mask << AD799X_CHANNEL_SHIFT) | + AD7998_CONV_RES_REG; + break; + case ad7997: + case ad7998: + cmd = AD7997_8_READ_SEQUENCE | AD7998_CONV_RES_REG; + break; + default: + cmd = 0; + } + + b_sent = i2c_smbus_read_i2c_block_data(st->client, + cmd, bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * 2, rxbuf); + if (b_sent < 0) + goto done; + + time_ns = iio_get_time_ns(); + + if (ring->scan_timestamp) + memcpy(rxbuf + st->d_size - sizeof(s64), + &time_ns, sizeof(time_ns)); + + ring->access->store_to(indio_dev->buffer, rxbuf, time_ns); +done: + kfree(rxbuf); + if (b_sent < 0) + return b_sent; +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops ad799x_buf_setup_ops = { + .preenable = &ad799x_ring_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int ad799x_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + int ret = 0; + + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + goto error_ret; + } + indio_dev->pollfunc = iio_alloc_pollfunc(NULL, + &ad799x_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + indio_dev->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_deallocate_sw_rb; + } + + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &ad799x_buf_setup_ops; + indio_dev->buffer->scan_timestamp = true; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_deallocate_sw_rb: + iio_sw_rb_free(indio_dev->buffer); +error_ret: + return ret; +} + +void ad799x_ring_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} diff --git a/drivers/staging/iio/adc/adt7310.c b/drivers/staging/iio/adc/adt7310.c new file mode 100644 index 00000000..caf57c11 --- /dev/null +++ b/drivers/staging/iio/adc/adt7310.c @@ -0,0 +1,878 @@ +/* + * ADT7310 digital temperature sensor driver supporting ADT7310 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" +/* + * ADT7310 registers definition + */ + +#define ADT7310_STATUS 0 +#define ADT7310_CONFIG 1 +#define ADT7310_TEMPERATURE 2 +#define ADT7310_ID 3 +#define ADT7310_T_CRIT 4 +#define ADT7310_T_HYST 5 +#define ADT7310_T_ALARM_HIGH 6 +#define ADT7310_T_ALARM_LOW 7 + +/* + * ADT7310 status + */ +#define ADT7310_STAT_T_LOW 0x10 +#define ADT7310_STAT_T_HIGH 0x20 +#define ADT7310_STAT_T_CRIT 0x40 +#define ADT7310_STAT_NOT_RDY 0x80 + +/* + * ADT7310 config + */ +#define ADT7310_FAULT_QUEUE_MASK 0x3 +#define ADT7310_CT_POLARITY 0x4 +#define ADT7310_INT_POLARITY 0x8 +#define ADT7310_EVENT_MODE 0x10 +#define ADT7310_MODE_MASK 0x60 +#define ADT7310_ONESHOT 0x20 +#define ADT7310_SPS 0x40 +#define ADT7310_PD 0x60 +#define ADT7310_RESOLUTION 0x80 + +/* + * ADT7310 masks + */ +#define ADT7310_T16_VALUE_SIGN 0x8000 +#define ADT7310_T16_VALUE_FLOAT_OFFSET 7 +#define ADT7310_T16_VALUE_FLOAT_MASK 0x7F +#define ADT7310_T13_VALUE_SIGN 0x1000 +#define ADT7310_T13_VALUE_OFFSET 3 +#define ADT7310_T13_VALUE_FLOAT_OFFSET 4 +#define ADT7310_T13_VALUE_FLOAT_MASK 0xF +#define ADT7310_T_HYST_MASK 0xF +#define ADT7310_DEVICE_ID_MASK 0x7 +#define ADT7310_MANUFACTORY_ID_MASK 0xF8 +#define ADT7310_MANUFACTORY_ID_OFFSET 3 + + +#define ADT7310_CMD_REG_MASK 0x28 +#define ADT7310_CMD_REG_OFFSET 3 +#define ADT7310_CMD_READ 0x40 +#define ADT7310_CMD_CON_READ 0x4 + +#define ADT7310_IRQS 2 + +/* + * struct adt7310_chip_info - chip specifc information + */ + +struct adt7310_chip_info { + struct spi_device *spi_dev; + u8 config; +}; + +/* + * adt7310 register access by SPI + */ + +static int adt7310_spi_read_word(struct adt7310_chip_info *chip, u8 reg, u16 *data) +{ + struct spi_device *spi_dev = chip->spi_dev; + u8 command = (reg << ADT7310_CMD_REG_OFFSET) & ADT7310_CMD_REG_MASK; + int ret = 0; + + command |= ADT7310_CMD_READ; + ret = spi_write(spi_dev, &command, sizeof(command)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI write command error\n"); + return ret; + } + + ret = spi_read(spi_dev, (u8 *)data, sizeof(*data)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI read word error\n"); + return ret; + } + + *data = be16_to_cpu(*data); + + return 0; +} + +static int adt7310_spi_write_word(struct adt7310_chip_info *chip, u8 reg, u16 data) +{ + struct spi_device *spi_dev = chip->spi_dev; + u8 buf[3]; + int ret = 0; + + buf[0] = (reg << ADT7310_CMD_REG_OFFSET) & ADT7310_CMD_REG_MASK; + buf[1] = (u8)(data >> 8); + buf[2] = (u8)(data & 0xFF); + + ret = spi_write(spi_dev, buf, 3); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI write word error\n"); + return ret; + } + + return ret; +} + +static int adt7310_spi_read_byte(struct adt7310_chip_info *chip, u8 reg, u8 *data) +{ + struct spi_device *spi_dev = chip->spi_dev; + u8 command = (reg << ADT7310_CMD_REG_OFFSET) & ADT7310_CMD_REG_MASK; + int ret = 0; + + command |= ADT7310_CMD_READ; + ret = spi_write(spi_dev, &command, sizeof(command)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI write command error\n"); + return ret; + } + + ret = spi_read(spi_dev, data, sizeof(*data)); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI read byte error\n"); + return ret; + } + + return 0; +} + +static int adt7310_spi_write_byte(struct adt7310_chip_info *chip, u8 reg, u8 data) +{ + struct spi_device *spi_dev = chip->spi_dev; + u8 buf[2]; + int ret = 0; + + buf[0] = (reg << ADT7310_CMD_REG_OFFSET) & ADT7310_CMD_REG_MASK; + buf[1] = data; + + ret = spi_write(spi_dev, buf, 2); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI write byte error\n"); + return ret; + } + + return ret; +} + +static ssize_t adt7310_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + u8 config; + + config = chip->config & ADT7310_MODE_MASK; + + switch (config) { + case ADT7310_PD: + return sprintf(buf, "power-down\n"); + case ADT7310_ONESHOT: + return sprintf(buf, "one-shot\n"); + case ADT7310_SPS: + return sprintf(buf, "sps\n"); + default: + return sprintf(buf, "full\n"); + } +} + +static ssize_t adt7310_store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + u16 config; + int ret; + + ret = adt7310_spi_read_byte(chip, ADT7310_CONFIG, &chip->config); + if (ret) + return -EIO; + + config = chip->config & (~ADT7310_MODE_MASK); + if (strcmp(buf, "power-down")) + config |= ADT7310_PD; + else if (strcmp(buf, "one-shot")) + config |= ADT7310_ONESHOT; + else if (strcmp(buf, "sps")) + config |= ADT7310_SPS; + + ret = adt7310_spi_write_byte(chip, ADT7310_CONFIG, config); + if (ret) + return -EIO; + + chip->config = config; + + return len; +} + +static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, + adt7310_show_mode, + adt7310_store_mode, + 0); + +static ssize_t adt7310_show_available_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "full\none-shot\nsps\npower-down\n"); +} + +static IIO_DEVICE_ATTR(available_modes, S_IRUGO, adt7310_show_available_modes, NULL, 0); + +static ssize_t adt7310_show_resolution(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + int ret; + int bits; + + ret = adt7310_spi_read_byte(chip, ADT7310_CONFIG, &chip->config); + if (ret) + return -EIO; + + if (chip->config & ADT7310_RESOLUTION) + bits = 16; + else + bits = 13; + + return sprintf(buf, "%d bits\n", bits); +} + +static ssize_t adt7310_store_resolution(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + unsigned long data; + u16 config; + int ret; + + ret = strict_strtoul(buf, 10, &data); + if (ret) + return -EINVAL; + + ret = adt7310_spi_read_byte(chip, ADT7310_CONFIG, &chip->config); + if (ret) + return -EIO; + + config = chip->config & (~ADT7310_RESOLUTION); + if (data) + config |= ADT7310_RESOLUTION; + + ret = adt7310_spi_write_byte(chip, ADT7310_CONFIG, config); + if (ret) + return -EIO; + + chip->config = config; + + return len; +} + +static IIO_DEVICE_ATTR(resolution, S_IRUGO | S_IWUSR, + adt7310_show_resolution, + adt7310_store_resolution, + 0); + +static ssize_t adt7310_show_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + u8 id; + int ret; + + ret = adt7310_spi_read_byte(chip, ADT7310_ID, &id); + if (ret) + return -EIO; + + return sprintf(buf, "device id: 0x%x\nmanufactory id: 0x%x\n", + id & ADT7310_DEVICE_ID_MASK, + (id & ADT7310_MANUFACTORY_ID_MASK) >> ADT7310_MANUFACTORY_ID_OFFSET); +} + +static IIO_DEVICE_ATTR(id, S_IRUGO | S_IWUSR, + adt7310_show_id, + NULL, + 0); + +static ssize_t adt7310_convert_temperature(struct adt7310_chip_info *chip, + u16 data, char *buf) +{ + char sign = ' '; + + if (chip->config & ADT7310_RESOLUTION) { + if (data & ADT7310_T16_VALUE_SIGN) { + /* convert supplement to positive value */ + data = (u16)((ADT7310_T16_VALUE_SIGN << 1) - (u32)data); + sign = '-'; + } + return sprintf(buf, "%c%d.%.7d\n", sign, + (data >> ADT7310_T16_VALUE_FLOAT_OFFSET), + (data & ADT7310_T16_VALUE_FLOAT_MASK) * 78125); + } else { + if (data & ADT7310_T13_VALUE_SIGN) { + /* convert supplement to positive value */ + data >>= ADT7310_T13_VALUE_OFFSET; + data = (ADT7310_T13_VALUE_SIGN << 1) - data; + sign = '-'; + } + return sprintf(buf, "%c%d.%.4d\n", sign, + (data >> ADT7310_T13_VALUE_FLOAT_OFFSET), + (data & ADT7310_T13_VALUE_FLOAT_MASK) * 625); + } +} + +static ssize_t adt7310_show_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + u8 status; + u16 data; + int ret, i = 0; + + do { + ret = adt7310_spi_read_byte(chip, ADT7310_STATUS, &status); + if (ret) + return -EIO; + i++; + if (i == 10000) + return -EIO; + } while (status & ADT7310_STAT_NOT_RDY); + + ret = adt7310_spi_read_word(chip, ADT7310_TEMPERATURE, &data); + if (ret) + return -EIO; + + return adt7310_convert_temperature(chip, data, buf); +} + +static IIO_DEVICE_ATTR(value, S_IRUGO, adt7310_show_value, NULL, 0); + +static struct attribute *adt7310_attributes[] = { + &iio_dev_attr_available_modes.dev_attr.attr, + &iio_dev_attr_mode.dev_attr.attr, + &iio_dev_attr_resolution.dev_attr.attr, + &iio_dev_attr_id.dev_attr.attr, + &iio_dev_attr_value.dev_attr.attr, + NULL, +}; + +static const struct attribute_group adt7310_attribute_group = { + .attrs = adt7310_attributes, +}; + +static irqreturn_t adt7310_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct adt7310_chip_info *chip = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(); + u8 status; + int ret; + + ret = adt7310_spi_read_byte(chip, ADT7310_STATUS, &status); + if (ret) + return ret; + + if (status & ADT7310_STAT_T_HIGH) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + if (status & ADT7310_STAT_T_LOW) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if (status & ADT7310_STAT_T_CRIT) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + return IRQ_HANDLED; +} + +static ssize_t adt7310_show_event_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + int ret; + + ret = adt7310_spi_read_byte(chip, ADT7310_CONFIG, &chip->config); + if (ret) + return -EIO; + + if (chip->config & ADT7310_EVENT_MODE) + return sprintf(buf, "interrupt\n"); + else + return sprintf(buf, "comparator\n"); +} + +static ssize_t adt7310_set_event_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + u16 config; + int ret; + + ret = adt7310_spi_read_byte(chip, ADT7310_CONFIG, &chip->config); + if (ret) + return -EIO; + + config = chip->config &= ~ADT7310_EVENT_MODE; + if (strcmp(buf, "comparator") != 0) + config |= ADT7310_EVENT_MODE; + + ret = adt7310_spi_write_byte(chip, ADT7310_CONFIG, config); + if (ret) + return -EIO; + + chip->config = config; + + return len; +} + +static ssize_t adt7310_show_available_event_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "comparator\ninterrupt\n"); +} + +static ssize_t adt7310_show_fault_queue(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + int ret; + + ret = adt7310_spi_read_byte(chip, ADT7310_CONFIG, &chip->config); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", chip->config & ADT7310_FAULT_QUEUE_MASK); +} + +static ssize_t adt7310_set_fault_queue(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + unsigned long data; + int ret; + u8 config; + + ret = strict_strtoul(buf, 10, &data); + if (ret || data > 3) + return -EINVAL; + + ret = adt7310_spi_read_byte(chip, ADT7310_CONFIG, &chip->config); + if (ret) + return -EIO; + + config = chip->config & ~ADT7310_FAULT_QUEUE_MASK; + config |= data; + ret = adt7310_spi_write_byte(chip, ADT7310_CONFIG, config); + if (ret) + return -EIO; + + chip->config = config; + + return len; +} + +static inline ssize_t adt7310_show_t_bound(struct device *dev, + struct device_attribute *attr, + u8 bound_reg, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + u16 data; + int ret; + + ret = adt7310_spi_read_word(chip, bound_reg, &data); + if (ret) + return -EIO; + + return adt7310_convert_temperature(chip, data, buf); +} + +static inline ssize_t adt7310_set_t_bound(struct device *dev, + struct device_attribute *attr, + u8 bound_reg, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + long tmp1, tmp2; + u16 data; + char *pos; + int ret; + + pos = strchr(buf, '.'); + + ret = strict_strtol(buf, 10, &tmp1); + + if (ret || tmp1 > 127 || tmp1 < -128) + return -EINVAL; + + if (pos) { + len = strlen(pos); + + if (chip->config & ADT7310_RESOLUTION) { + if (len > ADT7310_T16_VALUE_FLOAT_OFFSET) + len = ADT7310_T16_VALUE_FLOAT_OFFSET; + pos[len] = 0; + ret = strict_strtol(pos, 10, &tmp2); + + if (!ret) + tmp2 = (tmp2 / 78125) * 78125; + } else { + if (len > ADT7310_T13_VALUE_FLOAT_OFFSET) + len = ADT7310_T13_VALUE_FLOAT_OFFSET; + pos[len] = 0; + ret = strict_strtol(pos, 10, &tmp2); + + if (!ret) + tmp2 = (tmp2 / 625) * 625; + } + } + + if (tmp1 < 0) + data = (u16)(-tmp1); + else + data = (u16)tmp1; + + if (chip->config & ADT7310_RESOLUTION) { + data = (data << ADT7310_T16_VALUE_FLOAT_OFFSET) | + (tmp2 & ADT7310_T16_VALUE_FLOAT_MASK); + + if (tmp1 < 0) + /* convert positive value to supplyment */ + data = (u16)((ADT7310_T16_VALUE_SIGN << 1) - (u32)data); + } else { + data = (data << ADT7310_T13_VALUE_FLOAT_OFFSET) | + (tmp2 & ADT7310_T13_VALUE_FLOAT_MASK); + + if (tmp1 < 0) + /* convert positive value to supplyment */ + data = (ADT7310_T13_VALUE_SIGN << 1) - data; + data <<= ADT7310_T13_VALUE_OFFSET; + } + + ret = adt7310_spi_write_word(chip, bound_reg, data); + if (ret) + return -EIO; + + return len; +} + +static ssize_t adt7310_show_t_alarm_high(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return adt7310_show_t_bound(dev, attr, + ADT7310_T_ALARM_HIGH, buf); +} + +static inline ssize_t adt7310_set_t_alarm_high(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return adt7310_set_t_bound(dev, attr, + ADT7310_T_ALARM_HIGH, buf, len); +} + +static ssize_t adt7310_show_t_alarm_low(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return adt7310_show_t_bound(dev, attr, + ADT7310_T_ALARM_LOW, buf); +} + +static inline ssize_t adt7310_set_t_alarm_low(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return adt7310_set_t_bound(dev, attr, + ADT7310_T_ALARM_LOW, buf, len); +} + +static ssize_t adt7310_show_t_crit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return adt7310_show_t_bound(dev, attr, + ADT7310_T_CRIT, buf); +} + +static inline ssize_t adt7310_set_t_crit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return adt7310_set_t_bound(dev, attr, + ADT7310_T_CRIT, buf, len); +} + +static ssize_t adt7310_show_t_hyst(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + int ret; + u8 t_hyst; + + ret = adt7310_spi_read_byte(chip, ADT7310_T_HYST, &t_hyst); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", t_hyst & ADT7310_T_HYST_MASK); +} + +static inline ssize_t adt7310_set_t_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7310_chip_info *chip = iio_priv(dev_info); + int ret; + unsigned long data; + u8 t_hyst; + + ret = strict_strtol(buf, 10, &data); + + if (ret || data > ADT7310_T_HYST_MASK) + return -EINVAL; + + t_hyst = (u8)data; + + ret = adt7310_spi_write_byte(chip, ADT7310_T_HYST, t_hyst); + if (ret) + return -EIO; + + return len; +} + +static IIO_DEVICE_ATTR(event_mode, + S_IRUGO | S_IWUSR, + adt7310_show_event_mode, adt7310_set_event_mode, 0); +static IIO_DEVICE_ATTR(available_event_modes, + S_IRUGO | S_IWUSR, + adt7310_show_available_event_modes, NULL, 0); +static IIO_DEVICE_ATTR(fault_queue, + S_IRUGO | S_IWUSR, + adt7310_show_fault_queue, adt7310_set_fault_queue, 0); +static IIO_DEVICE_ATTR(t_alarm_high, + S_IRUGO | S_IWUSR, + adt7310_show_t_alarm_high, adt7310_set_t_alarm_high, 0); +static IIO_DEVICE_ATTR(t_alarm_low, + S_IRUGO | S_IWUSR, + adt7310_show_t_alarm_low, adt7310_set_t_alarm_low, 0); +static IIO_DEVICE_ATTR(t_crit, + S_IRUGO | S_IWUSR, + adt7310_show_t_crit, adt7310_set_t_crit, 0); +static IIO_DEVICE_ATTR(t_hyst, + S_IRUGO | S_IWUSR, + adt7310_show_t_hyst, adt7310_set_t_hyst, 0); + +static struct attribute *adt7310_event_int_attributes[] = { + &iio_dev_attr_event_mode.dev_attr.attr, + &iio_dev_attr_available_event_modes.dev_attr.attr, + &iio_dev_attr_fault_queue.dev_attr.attr, + &iio_dev_attr_t_alarm_high.dev_attr.attr, + &iio_dev_attr_t_alarm_low.dev_attr.attr, + &iio_dev_attr_t_crit.dev_attr.attr, + &iio_dev_attr_t_hyst.dev_attr.attr, + NULL, +}; + +static struct attribute_group adt7310_event_attribute_group = { + .attrs = adt7310_event_int_attributes, + .name = "events", +}; + +static const struct iio_info adt7310_info = { + .attrs = &adt7310_attribute_group, + .event_attrs = &adt7310_event_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* + * device probe and remove + */ + +static int __devinit adt7310_probe(struct spi_device *spi_dev) +{ + struct adt7310_chip_info *chip; + struct iio_dev *indio_dev; + int ret = 0; + unsigned long *adt7310_platform_data = spi_dev->dev.platform_data; + unsigned long irq_flags; + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + dev_set_drvdata(&spi_dev->dev, indio_dev); + + chip->spi_dev = spi_dev; + + indio_dev->dev.parent = &spi_dev->dev; + indio_dev->name = spi_get_device_id(spi_dev)->name; + indio_dev->info = &adt7310_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* CT critcal temperature event. line 0 */ + if (spi_dev->irq) { + if (adt7310_platform_data[2]) + irq_flags = adt7310_platform_data[2]; + else + irq_flags = IRQF_TRIGGER_LOW; + ret = request_threaded_irq(spi_dev->irq, + NULL, + &adt7310_event_handler, + irq_flags, + indio_dev->name, + indio_dev); + if (ret) + goto error_free_dev; + } + + /* INT bound temperature alarm event. line 1 */ + if (adt7310_platform_data[0]) { + ret = request_threaded_irq(adt7310_platform_data[0], + NULL, + &adt7310_event_handler, + adt7310_platform_data[1], + indio_dev->name, + indio_dev); + if (ret) + goto error_unreg_ct_irq; + } + + if (spi_dev->irq && adt7310_platform_data[0]) { + ret = adt7310_spi_read_byte(chip, ADT7310_CONFIG, &chip->config); + if (ret) { + ret = -EIO; + goto error_unreg_int_irq; + } + + /* set irq polarity low level */ + chip->config &= ~ADT7310_CT_POLARITY; + + if (adt7310_platform_data[1] & IRQF_TRIGGER_HIGH) + chip->config |= ADT7310_INT_POLARITY; + else + chip->config &= ~ADT7310_INT_POLARITY; + + ret = adt7310_spi_write_byte(chip, ADT7310_CONFIG, chip->config); + if (ret) { + ret = -EIO; + goto error_unreg_int_irq; + } + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_int_irq; + + dev_info(&spi_dev->dev, "%s temperature sensor registered.\n", + indio_dev->name); + + return 0; + +error_unreg_int_irq: + free_irq(adt7310_platform_data[0], indio_dev); +error_unreg_ct_irq: + free_irq(spi_dev->irq, indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit adt7310_remove(struct spi_device *spi_dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(&spi_dev->dev); + unsigned long *adt7310_platform_data = spi_dev->dev.platform_data; + + iio_device_unregister(indio_dev); + dev_set_drvdata(&spi_dev->dev, NULL); + if (adt7310_platform_data[0]) + free_irq(adt7310_platform_data[0], indio_dev); + if (spi_dev->irq) + free_irq(spi_dev->irq, indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id adt7310_id[] = { + { "adt7310", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(spi, adt7310_id); + +static struct spi_driver adt7310_driver = { + .driver = { + .name = "adt7310", + .owner = THIS_MODULE, + }, + .probe = adt7310_probe, + .remove = __devexit_p(adt7310_remove), + .id_table = adt7310_id, +}; +module_spi_driver(adt7310_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices ADT7310 digital" + " temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/adt7410.c b/drivers/staging/iio/adc/adt7410.c new file mode 100644 index 00000000..dff3e8ca --- /dev/null +++ b/drivers/staging/iio/adc/adt7410.c @@ -0,0 +1,840 @@ +/* + * ADT7410 digital temperature sensor driver supporting ADT7410 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" + +/* + * ADT7410 registers definition + */ + +#define ADT7410_TEMPERATURE 0 +#define ADT7410_STATUS 2 +#define ADT7410_CONFIG 3 +#define ADT7410_T_ALARM_HIGH 4 +#define ADT7410_T_ALARM_LOW 6 +#define ADT7410_T_CRIT 8 +#define ADT7410_T_HYST 0xA +#define ADT7410_ID 0xB +#define ADT7410_RESET 0x2F + +/* + * ADT7410 status + */ +#define ADT7410_STAT_T_LOW 0x10 +#define ADT7410_STAT_T_HIGH 0x20 +#define ADT7410_STAT_T_CRIT 0x40 +#define ADT7410_STAT_NOT_RDY 0x80 + +/* + * ADT7410 config + */ +#define ADT7410_FAULT_QUEUE_MASK 0x3 +#define ADT7410_CT_POLARITY 0x4 +#define ADT7410_INT_POLARITY 0x8 +#define ADT7410_EVENT_MODE 0x10 +#define ADT7410_MODE_MASK 0x60 +#define ADT7410_ONESHOT 0x20 +#define ADT7410_SPS 0x40 +#define ADT7410_PD 0x60 +#define ADT7410_RESOLUTION 0x80 + +/* + * ADT7410 masks + */ +#define ADT7410_T16_VALUE_SIGN 0x8000 +#define ADT7410_T16_VALUE_FLOAT_OFFSET 7 +#define ADT7410_T16_VALUE_FLOAT_MASK 0x7F +#define ADT7410_T13_VALUE_SIGN 0x1000 +#define ADT7410_T13_VALUE_OFFSET 3 +#define ADT7410_T13_VALUE_FLOAT_OFFSET 4 +#define ADT7410_T13_VALUE_FLOAT_MASK 0xF +#define ADT7410_T_HYST_MASK 0xF +#define ADT7410_DEVICE_ID_MASK 0xF +#define ADT7410_MANUFACTORY_ID_MASK 0xF0 +#define ADT7410_MANUFACTORY_ID_OFFSET 4 + +#define ADT7410_IRQS 2 + +/* + * struct adt7410_chip_info - chip specifc information + */ + +struct adt7410_chip_info { + struct i2c_client *client; + u8 config; +}; + +/* + * adt7410 register access by I2C + */ + +static int adt7410_i2c_read_word(struct adt7410_chip_info *chip, u8 reg, u16 *data) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + + *data = swab16((u16)ret); + + return 0; +} + +static int adt7410_i2c_write_word(struct adt7410_chip_info *chip, u8 reg, u16 data) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + ret = i2c_smbus_write_word_data(client, reg, swab16(data)); + if (ret < 0) + dev_err(&client->dev, "I2C write error\n"); + + return ret; +} + +static int adt7410_i2c_read_byte(struct adt7410_chip_info *chip, u8 reg, u8 *data) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + + *data = (u8)ret; + + return 0; +} + +static int adt7410_i2c_write_byte(struct adt7410_chip_info *chip, u8 reg, u8 data) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + ret = i2c_smbus_write_byte_data(client, reg, data); + if (ret < 0) + dev_err(&client->dev, "I2C write error\n"); + + return ret; +} + +static ssize_t adt7410_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + u8 config; + + config = chip->config & ADT7410_MODE_MASK; + + switch (config) { + case ADT7410_PD: + return sprintf(buf, "power-down\n"); + case ADT7410_ONESHOT: + return sprintf(buf, "one-shot\n"); + case ADT7410_SPS: + return sprintf(buf, "sps\n"); + default: + return sprintf(buf, "full\n"); + } +} + +static ssize_t adt7410_store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + u16 config; + int ret; + + ret = adt7410_i2c_read_byte(chip, ADT7410_CONFIG, &chip->config); + if (ret) + return -EIO; + + config = chip->config & (~ADT7410_MODE_MASK); + if (strcmp(buf, "power-down")) + config |= ADT7410_PD; + else if (strcmp(buf, "one-shot")) + config |= ADT7410_ONESHOT; + else if (strcmp(buf, "sps")) + config |= ADT7410_SPS; + + ret = adt7410_i2c_write_byte(chip, ADT7410_CONFIG, config); + if (ret) + return -EIO; + + chip->config = config; + + return ret; +} + +static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, + adt7410_show_mode, + adt7410_store_mode, + 0); + +static ssize_t adt7410_show_available_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "full\none-shot\nsps\npower-down\n"); +} + +static IIO_DEVICE_ATTR(available_modes, S_IRUGO, adt7410_show_available_modes, NULL, 0); + +static ssize_t adt7410_show_resolution(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + int ret; + int bits; + + ret = adt7410_i2c_read_byte(chip, ADT7410_CONFIG, &chip->config); + if (ret) + return -EIO; + + if (chip->config & ADT7410_RESOLUTION) + bits = 16; + else + bits = 13; + + return sprintf(buf, "%d bits\n", bits); +} + +static ssize_t adt7410_store_resolution(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + unsigned long data; + u16 config; + int ret; + + ret = strict_strtoul(buf, 10, &data); + if (ret) + return -EINVAL; + + ret = adt7410_i2c_read_byte(chip, ADT7410_CONFIG, &chip->config); + if (ret) + return -EIO; + + config = chip->config & (~ADT7410_RESOLUTION); + if (data) + config |= ADT7410_RESOLUTION; + + ret = adt7410_i2c_write_byte(chip, ADT7410_CONFIG, config); + if (ret) + return -EIO; + + chip->config = config; + + return ret; +} + +static IIO_DEVICE_ATTR(resolution, S_IRUGO | S_IWUSR, + adt7410_show_resolution, + adt7410_store_resolution, + 0); + +static ssize_t adt7410_show_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + u8 id; + int ret; + + ret = adt7410_i2c_read_byte(chip, ADT7410_ID, &id); + if (ret) + return -EIO; + + return sprintf(buf, "device id: 0x%x\nmanufactory id: 0x%x\n", + id & ADT7410_DEVICE_ID_MASK, + (id & ADT7410_MANUFACTORY_ID_MASK) >> ADT7410_MANUFACTORY_ID_OFFSET); +} + +static IIO_DEVICE_ATTR(id, S_IRUGO | S_IWUSR, + adt7410_show_id, + NULL, + 0); + +static ssize_t adt7410_convert_temperature(struct adt7410_chip_info *chip, + u16 data, char *buf) +{ + char sign = ' '; + + if (chip->config & ADT7410_RESOLUTION) { + if (data & ADT7410_T16_VALUE_SIGN) { + /* convert supplement to positive value */ + data = (u16)((ADT7410_T16_VALUE_SIGN << 1) - (u32)data); + sign = '-'; + } + return sprintf(buf, "%c%d.%.7d\n", sign, + (data >> ADT7410_T16_VALUE_FLOAT_OFFSET), + (data & ADT7410_T16_VALUE_FLOAT_MASK) * 78125); + } else { + if (data & ADT7410_T13_VALUE_SIGN) { + /* convert supplement to positive value */ + data >>= ADT7410_T13_VALUE_OFFSET; + data = (ADT7410_T13_VALUE_SIGN << 1) - data; + sign = '-'; + } + return sprintf(buf, "%c%d.%.4d\n", sign, + (data >> ADT7410_T13_VALUE_FLOAT_OFFSET), + (data & ADT7410_T13_VALUE_FLOAT_MASK) * 625); + } +} + +static ssize_t adt7410_show_value(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + u8 status; + u16 data; + int ret, i = 0; + + do { + ret = adt7410_i2c_read_byte(chip, ADT7410_STATUS, &status); + if (ret) + return -EIO; + i++; + if (i == 10000) + return -EIO; + } while (status & ADT7410_STAT_NOT_RDY); + + ret = adt7410_i2c_read_word(chip, ADT7410_TEMPERATURE, &data); + if (ret) + return -EIO; + + return adt7410_convert_temperature(chip, data, buf); +} + +static IIO_DEVICE_ATTR(value, S_IRUGO, adt7410_show_value, NULL, 0); + +static struct attribute *adt7410_attributes[] = { + &iio_dev_attr_available_modes.dev_attr.attr, + &iio_dev_attr_mode.dev_attr.attr, + &iio_dev_attr_resolution.dev_attr.attr, + &iio_dev_attr_id.dev_attr.attr, + &iio_dev_attr_value.dev_attr.attr, + NULL, +}; + +static const struct attribute_group adt7410_attribute_group = { + .attrs = adt7410_attributes, +}; + +static irqreturn_t adt7410_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct adt7410_chip_info *chip = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(); + u8 status; + + if (adt7410_i2c_read_byte(chip, ADT7410_STATUS, &status)) + return IRQ_HANDLED; + + if (status & ADT7410_STAT_T_HIGH) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + if (status & ADT7410_STAT_T_LOW) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + if (status & ADT7410_STAT_T_CRIT) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + + return IRQ_HANDLED; +} + +static ssize_t adt7410_show_event_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + int ret; + + ret = adt7410_i2c_read_byte(chip, ADT7410_CONFIG, &chip->config); + if (ret) + return -EIO; + + if (chip->config & ADT7410_EVENT_MODE) + return sprintf(buf, "interrupt\n"); + else + return sprintf(buf, "comparator\n"); +} + +static ssize_t adt7410_set_event_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + u16 config; + int ret; + + ret = adt7410_i2c_read_byte(chip, ADT7410_CONFIG, &chip->config); + if (ret) + return -EIO; + + config = chip->config &= ~ADT7410_EVENT_MODE; + if (strcmp(buf, "comparator") != 0) + config |= ADT7410_EVENT_MODE; + + ret = adt7410_i2c_write_byte(chip, ADT7410_CONFIG, config); + if (ret) + return -EIO; + + chip->config = config; + + return ret; +} + +static ssize_t adt7410_show_available_event_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "comparator\ninterrupt\n"); +} + +static ssize_t adt7410_show_fault_queue(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + int ret; + + ret = adt7410_i2c_read_byte(chip, ADT7410_CONFIG, &chip->config); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", chip->config & ADT7410_FAULT_QUEUE_MASK); +} + +static ssize_t adt7410_set_fault_queue(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + unsigned long data; + int ret; + u8 config; + + ret = strict_strtoul(buf, 10, &data); + if (ret || data > 3) + return -EINVAL; + + ret = adt7410_i2c_read_byte(chip, ADT7410_CONFIG, &chip->config); + if (ret) + return -EIO; + + config = chip->config & ~ADT7410_FAULT_QUEUE_MASK; + config |= data; + ret = adt7410_i2c_write_byte(chip, ADT7410_CONFIG, config); + if (ret) + return -EIO; + + chip->config = config; + + return ret; +} + +static inline ssize_t adt7410_show_t_bound(struct device *dev, + struct device_attribute *attr, + u8 bound_reg, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + u16 data; + int ret; + + ret = adt7410_i2c_read_word(chip, bound_reg, &data); + if (ret) + return -EIO; + + return adt7410_convert_temperature(chip, data, buf); +} + +static inline ssize_t adt7410_set_t_bound(struct device *dev, + struct device_attribute *attr, + u8 bound_reg, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + long tmp1, tmp2; + u16 data; + char *pos; + int ret; + + pos = strchr(buf, '.'); + + ret = strict_strtol(buf, 10, &tmp1); + + if (ret || tmp1 > 127 || tmp1 < -128) + return -EINVAL; + + if (pos) { + len = strlen(pos); + + if (chip->config & ADT7410_RESOLUTION) { + if (len > ADT7410_T16_VALUE_FLOAT_OFFSET) + len = ADT7410_T16_VALUE_FLOAT_OFFSET; + pos[len] = 0; + ret = strict_strtol(pos, 10, &tmp2); + + if (!ret) + tmp2 = (tmp2 / 78125) * 78125; + } else { + if (len > ADT7410_T13_VALUE_FLOAT_OFFSET) + len = ADT7410_T13_VALUE_FLOAT_OFFSET; + pos[len] = 0; + ret = strict_strtol(pos, 10, &tmp2); + + if (!ret) + tmp2 = (tmp2 / 625) * 625; + } + } + + if (tmp1 < 0) + data = (u16)(-tmp1); + else + data = (u16)tmp1; + + if (chip->config & ADT7410_RESOLUTION) { + data = (data << ADT7410_T16_VALUE_FLOAT_OFFSET) | + (tmp2 & ADT7410_T16_VALUE_FLOAT_MASK); + + if (tmp1 < 0) + /* convert positive value to supplyment */ + data = (u16)((ADT7410_T16_VALUE_SIGN << 1) - (u32)data); + } else { + data = (data << ADT7410_T13_VALUE_FLOAT_OFFSET) | + (tmp2 & ADT7410_T13_VALUE_FLOAT_MASK); + + if (tmp1 < 0) + /* convert positive value to supplyment */ + data = (ADT7410_T13_VALUE_SIGN << 1) - data; + data <<= ADT7410_T13_VALUE_OFFSET; + } + + ret = adt7410_i2c_write_word(chip, bound_reg, data); + if (ret) + return -EIO; + + return ret; +} + +static ssize_t adt7410_show_t_alarm_high(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return adt7410_show_t_bound(dev, attr, + ADT7410_T_ALARM_HIGH, buf); +} + +static inline ssize_t adt7410_set_t_alarm_high(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return adt7410_set_t_bound(dev, attr, + ADT7410_T_ALARM_HIGH, buf, len); +} + +static ssize_t adt7410_show_t_alarm_low(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return adt7410_show_t_bound(dev, attr, + ADT7410_T_ALARM_LOW, buf); +} + +static inline ssize_t adt7410_set_t_alarm_low(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return adt7410_set_t_bound(dev, attr, + ADT7410_T_ALARM_LOW, buf, len); +} + +static ssize_t adt7410_show_t_crit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return adt7410_show_t_bound(dev, attr, + ADT7410_T_CRIT, buf); +} + +static inline ssize_t adt7410_set_t_crit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return adt7410_set_t_bound(dev, attr, + ADT7410_T_CRIT, buf, len); +} + +static ssize_t adt7410_show_t_hyst(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + int ret; + u8 t_hyst; + + ret = adt7410_i2c_read_byte(chip, ADT7410_T_HYST, &t_hyst); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", t_hyst & ADT7410_T_HYST_MASK); +} + +static inline ssize_t adt7410_set_t_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7410_chip_info *chip = iio_priv(dev_info); + int ret; + unsigned long data; + u8 t_hyst; + + ret = strict_strtol(buf, 10, &data); + + if (ret || data > ADT7410_T_HYST_MASK) + return -EINVAL; + + t_hyst = (u8)data; + + ret = adt7410_i2c_write_byte(chip, ADT7410_T_HYST, t_hyst); + if (ret) + return -EIO; + + return ret; +} + +static IIO_DEVICE_ATTR(event_mode, + S_IRUGO | S_IWUSR, + adt7410_show_event_mode, adt7410_set_event_mode, 0); +static IIO_DEVICE_ATTR(available_event_modes, + S_IRUGO, + adt7410_show_available_event_modes, NULL, 0); +static IIO_DEVICE_ATTR(fault_queue, + S_IRUGO | S_IWUSR, + adt7410_show_fault_queue, adt7410_set_fault_queue, 0); +static IIO_DEVICE_ATTR(t_alarm_high, + S_IRUGO | S_IWUSR, + adt7410_show_t_alarm_high, adt7410_set_t_alarm_high, 0); +static IIO_DEVICE_ATTR(t_alarm_low, + S_IRUGO | S_IWUSR, + adt7410_show_t_alarm_low, adt7410_set_t_alarm_low, 0); +static IIO_DEVICE_ATTR(t_crit, + S_IRUGO | S_IWUSR, + adt7410_show_t_crit, adt7410_set_t_crit, 0); +static IIO_DEVICE_ATTR(t_hyst, + S_IRUGO | S_IWUSR, + adt7410_show_t_hyst, adt7410_set_t_hyst, 0); + +static struct attribute *adt7410_event_int_attributes[] = { + &iio_dev_attr_event_mode.dev_attr.attr, + &iio_dev_attr_available_event_modes.dev_attr.attr, + &iio_dev_attr_fault_queue.dev_attr.attr, + &iio_dev_attr_t_alarm_high.dev_attr.attr, + &iio_dev_attr_t_alarm_low.dev_attr.attr, + &iio_dev_attr_t_crit.dev_attr.attr, + &iio_dev_attr_t_hyst.dev_attr.attr, + NULL, +}; + +static struct attribute_group adt7410_event_attribute_group = { + .attrs = adt7410_event_int_attributes, + .name = "events", +}; + +static const struct iio_info adt7410_info = { + .attrs = &adt7410_attribute_group, + .event_attrs = &adt7410_event_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* + * device probe and remove + */ + +static int __devinit adt7410_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adt7410_chip_info *chip; + struct iio_dev *indio_dev; + int ret = 0; + unsigned long *adt7410_platform_data = client->dev.platform_data; + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + chip->client = client; + + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->info = &adt7410_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* CT critcal temperature event. line 0 */ + if (client->irq) { + ret = request_threaded_irq(client->irq, + NULL, + &adt7410_event_handler, + IRQF_TRIGGER_LOW, + id->name, + indio_dev); + if (ret) + goto error_free_dev; + } + + /* INT bound temperature alarm event. line 1 */ + if (adt7410_platform_data[0]) { + ret = request_threaded_irq(adt7410_platform_data[0], + NULL, + &adt7410_event_handler, + adt7410_platform_data[1], + id->name, + indio_dev); + if (ret) + goto error_unreg_ct_irq; + } + + if (client->irq && adt7410_platform_data[0]) { + + ret = adt7410_i2c_read_byte(chip, ADT7410_CONFIG, &chip->config); + if (ret) { + ret = -EIO; + goto error_unreg_int_irq; + } + + /* set irq polarity low level */ + chip->config &= ~ADT7410_CT_POLARITY; + + if (adt7410_platform_data[1] & IRQF_TRIGGER_HIGH) + chip->config |= ADT7410_INT_POLARITY; + else + chip->config &= ~ADT7410_INT_POLARITY; + + ret = adt7410_i2c_write_byte(chip, ADT7410_CONFIG, chip->config); + if (ret) { + ret = -EIO; + goto error_unreg_int_irq; + } + } + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_int_irq; + + dev_info(&client->dev, "%s temperature sensor registered.\n", + id->name); + + return 0; + +error_unreg_int_irq: + free_irq(adt7410_platform_data[0], indio_dev); +error_unreg_ct_irq: + free_irq(client->irq, indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit adt7410_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + unsigned long *adt7410_platform_data = client->dev.platform_data; + + iio_device_unregister(indio_dev); + if (adt7410_platform_data[0]) + free_irq(adt7410_platform_data[0], indio_dev); + if (client->irq) + free_irq(client->irq, indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id adt7410_id[] = { + { "adt7410", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, adt7410_id); + +static struct i2c_driver adt7410_driver = { + .driver = { + .name = "adt7410", + }, + .probe = adt7410_probe, + .remove = __devexit_p(adt7410_remove), + .id_table = adt7410_id, +}; +module_i2c_driver(adt7410_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices ADT7410 digital" + " temperature sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/lpc32xx_adc.c b/drivers/staging/iio/adc/lpc32xx_adc.c new file mode 100644 index 00000000..dfc90338 --- /dev/null +++ b/drivers/staging/iio/adc/lpc32xx_adc.c @@ -0,0 +1,237 @@ +/* + * lpc32xx_adc.c - Support for ADC in LPC32XX + * + * 3-channel, 10-bit ADC + * + * Copyright (C) 2011, 2012 Roland Stigge + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +/* + * LPC32XX registers definitions + */ +#define LPC32XX_ADC_SELECT(x) ((x) + 0x04) +#define LPC32XX_ADC_CTRL(x) ((x) + 0x08) +#define LPC32XX_ADC_VALUE(x) ((x) + 0x48) + +/* Bit definitions for LPC32XX_ADC_SELECT: */ +#define AD_REFm 0x00000200 /* constant, always write this value! */ +#define AD_REFp 0x00000080 /* constant, always write this value! */ +#define AD_IN 0x00000010 /* multiple of this is the */ + /* channel number: 0, 1, 2 */ +#define AD_INTERNAL 0x00000004 /* constant, always write this value! */ + +/* Bit definitions for LPC32XX_ADC_CTRL: */ +#define AD_STROBE 0x00000002 +#define AD_PDN_CTRL 0x00000004 + +/* Bit definitions for LPC32XX_ADC_VALUE: */ +#define ADC_VALUE_MASK 0x000003FF + +#define MOD_NAME "lpc32xx-adc" + +struct lpc32xx_adc_info { + void __iomem *adc_base; + struct clk *clk; + struct completion completion; + + u32 value; +}; + +static int lpc32xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct lpc32xx_adc_info *info = iio_priv(indio_dev); + + if (mask == 0) { + mutex_lock(&indio_dev->mlock); + clk_enable(info->clk); + /* Measurement setup */ + __raw_writel(AD_INTERNAL | (chan->address) | AD_REFp | AD_REFm, + LPC32XX_ADC_SELECT(info->adc_base)); + /* Trigger conversion */ + __raw_writel(AD_PDN_CTRL | AD_STROBE, + LPC32XX_ADC_CTRL(info->adc_base)); + wait_for_completion(&info->completion); /* set by ISR */ + clk_disable(info->clk); + *val = info->value; + mutex_unlock(&indio_dev->mlock); + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info lpc32xx_adc_iio_info = { + .read_raw = &lpc32xx_read_raw, + .driver_module = THIS_MODULE, +}; + +#define LPC32XX_ADC_CHANNEL(_index) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .address = AD_IN * _index, \ + .scan_index = _index, \ +} + +static struct iio_chan_spec lpc32xx_adc_iio_channels[] = { + LPC32XX_ADC_CHANNEL(0), + LPC32XX_ADC_CHANNEL(1), + LPC32XX_ADC_CHANNEL(2), +}; + +static irqreturn_t lpc32xx_adc_isr(int irq, void *dev_id) +{ + struct lpc32xx_adc_info *info = (struct lpc32xx_adc_info *) dev_id; + + /* Read value and clear irq */ + info->value = __raw_readl(LPC32XX_ADC_VALUE(info->adc_base)) & + ADC_VALUE_MASK; + complete(&info->completion); + + return IRQ_HANDLED; +} + +static int __devinit lpc32xx_adc_probe(struct platform_device *pdev) +{ + struct lpc32xx_adc_info *info = NULL; + struct resource *res; + int retval = -ENODEV; + struct iio_dev *iodev = NULL; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform I/O memory\n"); + retval = -EBUSY; + goto errout1; + } + + iodev = iio_allocate_device(sizeof(struct lpc32xx_adc_info)); + if (!iodev) { + dev_err(&pdev->dev, "failed allocating iio device\n"); + retval = -ENOMEM; + goto errout1; + } + + info = iio_priv(iodev); + + info->adc_base = ioremap(res->start, res->end - res->start + 1); + if (!info->adc_base) { + dev_err(&pdev->dev, "failed mapping memory\n"); + retval = -EBUSY; + goto errout2; + } + + info->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "failed getting clock\n"); + goto errout3; + } + + irq = platform_get_irq(pdev, 0); + if ((irq < 0) || (irq >= NR_IRQS)) { + dev_err(&pdev->dev, "failed getting interrupt resource\n"); + retval = -EINVAL; + goto errout4; + } + + retval = request_irq(irq, lpc32xx_adc_isr, 0, MOD_NAME, info); + if (retval < 0) { + dev_err(&pdev->dev, "failed requesting interrupt\n"); + goto errout4; + } + + platform_set_drvdata(pdev, iodev); + + init_completion(&info->completion); + + iodev->name = MOD_NAME; + iodev->dev.parent = &pdev->dev; + iodev->info = &lpc32xx_adc_iio_info; + iodev->modes = INDIO_DIRECT_MODE; + iodev->channels = lpc32xx_adc_iio_channels; + iodev->num_channels = ARRAY_SIZE(lpc32xx_adc_iio_channels); + + retval = iio_device_register(iodev); + if (retval) + goto errout5; + + dev_info(&pdev->dev, "LPC32XX ADC driver loaded, IRQ %d\n", irq); + + return 0; + +errout5: + free_irq(irq, iodev); +errout4: + clk_put(info->clk); +errout3: + iounmap(info->adc_base); +errout2: + iio_free_device(iodev); +errout1: + return retval; +} + +static int __devexit lpc32xx_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *iodev = platform_get_drvdata(pdev); + struct lpc32xx_adc_info *info = iio_priv(iodev); + int irq = platform_get_irq(pdev, 0); + + iio_device_unregister(iodev); + free_irq(irq, iodev); + platform_set_drvdata(pdev, NULL); + clk_put(info->clk); + iounmap(info->adc_base); + iio_free_device(iodev); + + return 0; +} + +static struct platform_driver lpc32xx_adc_driver = { + .probe = lpc32xx_adc_probe, + .remove = __devexit_p(lpc32xx_adc_remove), + .driver = { + .name = MOD_NAME, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(lpc32xx_adc_driver); + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("LPC32XX ADC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/adc/max1363.h b/drivers/staging/iio/adc/max1363.h new file mode 100644 index 00000000..2cd01120 --- /dev/null +++ b/drivers/staging/iio/adc/max1363.h @@ -0,0 +1,177 @@ +#ifndef _MAX1363_H_ +#define _MAX1363_H_ + +#define MAX1363_SETUP_BYTE(a) ((a) | 0x80) + +/* There is a fair bit more defined here than currently + * used, but the intention is to support everything these + * chips do in the long run */ + +/* see data sheets */ +/* max1363 and max1236, max1237, max1238, max1239 */ +#define MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_VDD 0x00 +#define MAX1363_SETUP_AIN3_IS_REF_EXT_TO_REF 0x20 +#define MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_INT 0x40 +#define MAX1363_SETUP_AIN3_IS_REF_REF_IS_INT 0x60 +#define MAX1363_SETUP_POWER_UP_INT_REF 0x10 +#define MAX1363_SETUP_POWER_DOWN_INT_REF 0x00 + +/* think about includeing max11600 etc - more settings */ +#define MAX1363_SETUP_EXT_CLOCK 0x08 +#define MAX1363_SETUP_INT_CLOCK 0x00 +#define MAX1363_SETUP_UNIPOLAR 0x00 +#define MAX1363_SETUP_BIPOLAR 0x04 +#define MAX1363_SETUP_RESET 0x00 +#define MAX1363_SETUP_NORESET 0x02 +/* max1363 only - though don't care on others. + * For now monitor modes are not implemented as the relevant + * line is not connected on my test board. + * The definitions are here as I intend to add this soon. + */ +#define MAX1363_SETUP_MONITOR_SETUP 0x01 + +/* Specific to the max1363 */ +#define MAX1363_MON_RESET_CHAN(a) (1 << ((a) + 4)) +#define MAX1363_MON_INT_ENABLE 0x01 + +/* defined for readability reasons */ +/* All chips */ +#define MAX1363_CONFIG_BYTE(a) ((a)) + +#define MAX1363_CONFIG_SE 0x01 +#define MAX1363_CONFIG_DE 0x00 +#define MAX1363_CONFIG_SCAN_TO_CS 0x00 +#define MAX1363_CONFIG_SCAN_SINGLE_8 0x20 +#define MAX1363_CONFIG_SCAN_MONITOR_MODE 0x40 +#define MAX1363_CONFIG_SCAN_SINGLE_1 0x60 +/* max123{6-9} only */ +#define MAX1236_SCAN_MID_TO_CHANNEL 0x40 + +/* max1363 only - merely part of channel selects or don't care for others*/ +#define MAX1363_CONFIG_EN_MON_MODE_READ 0x18 + +#define MAX1363_CHANNEL_SEL(a) ((a) << 1) + +/* max1363 strictly 0x06 - but doesn't matter */ +#define MAX1363_CHANNEL_SEL_MASK 0x1E +#define MAX1363_SCAN_MASK 0x60 +#define MAX1363_SE_DE_MASK 0x01 + +#define MAX1363_MAX_CHANNELS 25 +/** + * struct max1363_mode - scan mode information + * @conf: The corresponding value of the configuration register + * @modemask: Bit mask corresponding to channels enabled in this mode + */ +struct max1363_mode { + int8_t conf; + DECLARE_BITMAP(modemask, MAX1363_MAX_CHANNELS); +}; + +/* This must be maintained along side the max1363_mode_table in max1363_core */ +enum max1363_modes { + /* Single read of a single channel */ + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, _s8, _s9, _s10, _s11, + /* Differential single read */ + d0m1, d2m3, d4m5, d6m7, d8m9, d10m11, + d1m0, d3m2, d5m4, d7m6, d9m8, d11m10, + /* Scan to channel and mid to channel where overlapping */ + s0to1, s0to2, s2to3, s0to3, s0to4, s0to5, s0to6, + s6to7, s0to7, s6to8, s0to8, s6to9, + s0to9, s6to10, s0to10, s6to11, s0to11, + /* Differential scan to channel and mid to channel where overlapping */ + d0m1to2m3, d0m1to4m5, d0m1to6m7, d6m7to8m9, + d0m1to8m9, d6m7to10m11, d0m1to10m11, d1m0to3m2, + d1m0to5m4, d1m0to7m6, d7m6to9m8, d1m0to9m8, + d7m6to11m10, d1m0to11m10, +}; + +/** + * struct max1363_chip_info - chip specifc information + * @name: indentification string for chip + * @bits: accuracy of the adc in bits + * @int_vref_mv: the internal reference voltage + * @info: iio core function callbacks structure + * @mode_list: array of available scan modes + * @num_modes: the number of scan modes available + * @default_mode: the scan mode in which the chip starts up + * @channel: channel specification + * @num_channels: number of channels + */ +struct max1363_chip_info { + const struct iio_info *info; + struct iio_chan_spec *channels; + int num_channels; + const enum max1363_modes *mode_list; + enum max1363_modes default_mode; + u16 int_vref_mv; + u8 num_modes; + u8 bits; +}; + +/** + * struct max1363_state - driver instance specific data + * @client: i2c_client + * @setupbyte: cache of current device setup byte + * @configbyte: cache of current device config byte + * @chip_info: chip model specific constants, available modes etc + * @current_mode: the scan mode of this chip + * @requestedmask: a valid requested set of channels + * @reg: supply regulator + * @monitor_on: whether monitor mode is enabled + * @monitor_speed: parameter corresponding to device monitor speed setting + * @mask_high: bitmask for enabled high thresholds + * @mask_low: bitmask for enabled low thresholds + * @thresh_high: high threshold values + * @thresh_low: low threshold values + */ +struct max1363_state { + struct i2c_client *client; + u8 setupbyte; + u8 configbyte; + const struct max1363_chip_info *chip_info; + const struct max1363_mode *current_mode; + u32 requestedmask; + struct regulator *reg; + + /* Using monitor modes and buffer at the same time is + currently not supported */ + bool monitor_on; + unsigned int monitor_speed:3; + u8 mask_high; + u8 mask_low; + /* 4x unipolar first then the fours bipolar ones */ + s16 thresh_high[8]; + s16 thresh_low[8]; +}; + +const struct max1363_mode +*max1363_match_mode(const unsigned long *mask, + const struct max1363_chip_info *ci); + +int max1363_set_scan_mode(struct max1363_state *st); + +#ifdef CONFIG_MAX1363_RING_BUFFER +int max1363_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask); +int max1363_register_ring_funcs_and_init(struct iio_dev *indio_dev); +void max1363_ring_cleanup(struct iio_dev *indio_dev); + +#else /* CONFIG_MAX1363_RING_BUFFER */ +int max1363_update_scan_mode(struct iio_dev *indio_dev, + const long *scan_mask) +{ + return 0; +} + +static inline int +max1363_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void max1363_ring_cleanup(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_MAX1363_RING_BUFFER */ +#endif /* _MAX1363_H_ */ diff --git a/drivers/staging/iio/adc/max1363_core.c b/drivers/staging/iio/adc/max1363_core.c new file mode 100644 index 00000000..cf3e2ca7 --- /dev/null +++ b/drivers/staging/iio/adc/max1363_core.c @@ -0,0 +1,1435 @@ + /* + * iio/adc/max1363.c + * Copyright (C) 2008-2010 Jonathan Cameron + * + * based on linux/drivers/i2c/chips/max123x + * Copyright (C) 2002-2004 Stefan Eletzhofer + * + * based on linux/drivers/acron/char/pcf8583.c + * Copyright (C) 2000 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * max1363.c + * + * Partial support for max1363 and similar chips. + * + * Not currently implemented. + * + * - Control of internal reference. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" +#include "../buffer.h" + +#include "max1363.h" + +#define MAX1363_MODE_SINGLE(_num, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_SINGLE_1 \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask, \ + } + +#define MAX1363_MODE_SCAN_TO_CHANNEL(_num, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_TO_CS \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask, \ + } + +/* note not available for max1363 hence naming */ +#define MAX1236_MODE_SCAN_MID_TO_CHANNEL(_mid, _num, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1236_SCAN_MID_TO_CHANNEL \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask \ +} + +#define MAX1363_MODE_DIFF_SINGLE(_nump, _numm, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_nump) \ + | MAX1363_CONFIG_SCAN_SINGLE_1 \ + | MAX1363_CONFIG_DE, \ + .modemask[0] = _mask \ + } + +/* Can't think how to automate naming so specify for now */ +#define MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(_num, _numvals, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1363_CONFIG_SCAN_TO_CS \ + | MAX1363_CONFIG_DE, \ + .modemask[0] = _mask \ + } + +/* note only available for max1363 hence naming */ +#define MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(_num, _numvals, _mask) { \ + .conf = MAX1363_CHANNEL_SEL(_num) \ + | MAX1236_SCAN_MID_TO_CHANNEL \ + | MAX1363_CONFIG_SE, \ + .modemask[0] = _mask \ +} + +static const struct max1363_mode max1363_mode_table[] = { + /* All of the single channel options first */ + MAX1363_MODE_SINGLE(0, 1 << 0), + MAX1363_MODE_SINGLE(1, 1 << 1), + MAX1363_MODE_SINGLE(2, 1 << 2), + MAX1363_MODE_SINGLE(3, 1 << 3), + MAX1363_MODE_SINGLE(4, 1 << 4), + MAX1363_MODE_SINGLE(5, 1 << 5), + MAX1363_MODE_SINGLE(6, 1 << 6), + MAX1363_MODE_SINGLE(7, 1 << 7), + MAX1363_MODE_SINGLE(8, 1 << 8), + MAX1363_MODE_SINGLE(9, 1 << 9), + MAX1363_MODE_SINGLE(10, 1 << 10), + MAX1363_MODE_SINGLE(11, 1 << 11), + + MAX1363_MODE_DIFF_SINGLE(0, 1, 1 << 12), + MAX1363_MODE_DIFF_SINGLE(2, 3, 1 << 13), + MAX1363_MODE_DIFF_SINGLE(4, 5, 1 << 14), + MAX1363_MODE_DIFF_SINGLE(6, 7, 1 << 15), + MAX1363_MODE_DIFF_SINGLE(8, 9, 1 << 16), + MAX1363_MODE_DIFF_SINGLE(10, 11, 1 << 17), + MAX1363_MODE_DIFF_SINGLE(1, 0, 1 << 18), + MAX1363_MODE_DIFF_SINGLE(3, 2, 1 << 19), + MAX1363_MODE_DIFF_SINGLE(5, 4, 1 << 20), + MAX1363_MODE_DIFF_SINGLE(7, 6, 1 << 21), + MAX1363_MODE_DIFF_SINGLE(9, 8, 1 << 22), + MAX1363_MODE_DIFF_SINGLE(11, 10, 1 << 23), + + /* The multichannel scans next */ + MAX1363_MODE_SCAN_TO_CHANNEL(1, 0x003), + MAX1363_MODE_SCAN_TO_CHANNEL(2, 0x007), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(2, 3, 0x00C), + MAX1363_MODE_SCAN_TO_CHANNEL(3, 0x00F), + MAX1363_MODE_SCAN_TO_CHANNEL(4, 0x01F), + MAX1363_MODE_SCAN_TO_CHANNEL(5, 0x03F), + MAX1363_MODE_SCAN_TO_CHANNEL(6, 0x07F), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 7, 0x0C0), + MAX1363_MODE_SCAN_TO_CHANNEL(7, 0x0FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 8, 0x1C0), + MAX1363_MODE_SCAN_TO_CHANNEL(8, 0x1FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 9, 0x3C0), + MAX1363_MODE_SCAN_TO_CHANNEL(9, 0x3FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 10, 0x7C0), + MAX1363_MODE_SCAN_TO_CHANNEL(10, 0x7FF), + MAX1236_MODE_SCAN_MID_TO_CHANNEL(6, 11, 0xFC0), + MAX1363_MODE_SCAN_TO_CHANNEL(11, 0xFFF), + + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(2, 2, 0x003000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(4, 3, 0x007000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(6, 4, 0x00F000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(8, 2, 0x018000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(8, 5, 0x01F000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(10, 3, 0x038000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(10, 6, 0x3F000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(3, 2, 0x0C0000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(5, 3, 0x1C0000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(7, 4, 0x3C0000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(9, 2, 0x600000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(9, 5, 0x7C0000), + MAX1236_MODE_DIFF_SCAN_MID_TO_CHANNEL(11, 3, 0xE00000), + MAX1363_MODE_DIFF_SCAN_TO_CHANNEL(11, 6, 0xFC0000), +}; + +const struct max1363_mode +*max1363_match_mode(const unsigned long *mask, +const struct max1363_chip_info *ci) +{ + int i; + if (mask) + for (i = 0; i < ci->num_modes; i++) + if (bitmap_subset(mask, + max1363_mode_table[ci->mode_list[i]]. + modemask, + MAX1363_MAX_CHANNELS)) + return &max1363_mode_table[ci->mode_list[i]]; + return NULL; +} + +static int max1363_write_basic_config(struct i2c_client *client, + unsigned char d1, + unsigned char d2) +{ + u8 tx_buf[2] = {d1, d2}; + + return i2c_master_send(client, tx_buf, 2); +} + +int max1363_set_scan_mode(struct max1363_state *st) +{ + st->configbyte &= ~(MAX1363_CHANNEL_SEL_MASK + | MAX1363_SCAN_MASK + | MAX1363_SE_DE_MASK); + st->configbyte |= st->current_mode->conf; + + return max1363_write_basic_config(st->client, + st->setupbyte, + st->configbyte); +} + +static int max1363_read_single_chan(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + long m) +{ + int ret = 0; + s32 data; + char rxbuf[2]; + struct max1363_state *st = iio_priv(indio_dev); + struct i2c_client *client = st->client; + + mutex_lock(&indio_dev->mlock); + /* + * If monitor mode is enabled, the method for reading a single + * channel will have to be rather different and has not yet + * been implemented. + * + * Also, cannot read directly if buffered capture enabled. + */ + if (st->monitor_on || iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto error_ret; + } + + /* Check to see if current scan mode is correct */ + if (st->current_mode != &max1363_mode_table[chan->address]) { + /* Update scan mode if needed */ + st->current_mode = &max1363_mode_table[chan->address]; + ret = max1363_set_scan_mode(st); + if (ret < 0) + goto error_ret; + } + if (st->chip_info->bits != 8) { + /* Get reading */ + data = i2c_master_recv(client, rxbuf, 2); + if (data < 0) { + ret = data; + goto error_ret; + } + data = (s32)(rxbuf[1]) | ((s32)(rxbuf[0] & 0x0F)) << 8; + } else { + /* Get reading */ + data = i2c_master_recv(client, rxbuf, 1); + if (data < 0) { + ret = data; + goto error_ret; + } + data = rxbuf[0]; + } + *val = data; +error_ret: + mutex_unlock(&indio_dev->mlock); + return ret; + +} + +static int max1363_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct max1363_state *st = iio_priv(indio_dev); + int ret; + switch (m) { + case 0: + ret = max1363_read_single_chan(indio_dev, chan, val, m); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + if ((1 << (st->chip_info->bits + 1)) > + st->chip_info->int_vref_mv) { + *val = 0; + *val2 = 500000; + return IIO_VAL_INT_PLUS_MICRO; + } else { + *val = (st->chip_info->int_vref_mv) + >> st->chip_info->bits; + return IIO_VAL_INT; + } + default: + return -EINVAL; + } + return 0; +} + +/* Applies to max1363 */ +static const enum max1363_modes max1363_mode_list[] = { + _s0, _s1, _s2, _s3, + s0to1, s0to2, s0to3, + d0m1, d2m3, d1m0, d3m2, + d0m1to2m3, d1m0to3m2, +}; + +#define MAX1363_EV_M \ + (IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) \ + | IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING)) +#define MAX1363_INFO_MASK IIO_CHAN_INFO_SCALE_SHARED_BIT +#define MAX1363_CHAN_U(num, addr, si, bits, evmask) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = num, \ + .address = addr, \ + .info_mask = MAX1363_INFO_MASK, \ + .datasheet_name = "AIN"#num, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = bits, \ + .storagebits = (bits > 8) ? 16 : 8, \ + .endianness = IIO_BE, \ + }, \ + .scan_index = si, \ + .event_mask = evmask, \ + } + +/* bipolar channel */ +#define MAX1363_CHAN_B(num, num2, addr, si, bits, evmask) \ + { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .channel = num, \ + .channel2 = num2, \ + .address = addr, \ + .info_mask = MAX1363_INFO_MASK, \ + .datasheet_name = "AIN"#num"-AIN"#num2, \ + .scan_type = { \ + .sign = 's', \ + .realbits = bits, \ + .storagebits = (bits > 8) ? 16 : 8, \ + .endianness = IIO_BE, \ + }, \ + .scan_index = si, \ + .event_mask = evmask, \ + } + +#define MAX1363_4X_CHANS(bits, em) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, em), \ + MAX1363_CHAN_U(1, _s1, 1, bits, em), \ + MAX1363_CHAN_U(2, _s2, 2, bits, em), \ + MAX1363_CHAN_U(3, _s3, 3, bits, em), \ + MAX1363_CHAN_B(0, 1, d0m1, 4, bits, em), \ + MAX1363_CHAN_B(2, 3, d2m3, 5, bits, em), \ + MAX1363_CHAN_B(1, 0, d1m0, 6, bits, em), \ + MAX1363_CHAN_B(3, 2, d3m2, 7, bits, em), \ + IIO_CHAN_SOFT_TIMESTAMP(8) \ + } + +static struct iio_chan_spec max1036_channels[] = MAX1363_4X_CHANS(8, 0); +static struct iio_chan_spec max1136_channels[] = MAX1363_4X_CHANS(10, 0); +static struct iio_chan_spec max1236_channels[] = MAX1363_4X_CHANS(12, 0); +static struct iio_chan_spec max1361_channels[] = + MAX1363_4X_CHANS(10, MAX1363_EV_M); +static struct iio_chan_spec max1363_channels[] = + MAX1363_4X_CHANS(12, MAX1363_EV_M); + +/* Applies to max1236, max1237 */ +static const enum max1363_modes max1236_mode_list[] = { + _s0, _s1, _s2, _s3, + s0to1, s0to2, s0to3, + d0m1, d2m3, d1m0, d3m2, + d0m1to2m3, d1m0to3m2, + s2to3, +}; + +/* Applies to max1238, max1239 */ +static const enum max1363_modes max1238_mode_list[] = { + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, _s8, _s9, _s10, _s11, + s0to1, s0to2, s0to3, s0to4, s0to5, s0to6, + s0to7, s0to8, s0to9, s0to10, s0to11, + d0m1, d2m3, d4m5, d6m7, d8m9, d10m11, + d1m0, d3m2, d5m4, d7m6, d9m8, d11m10, + d0m1to2m3, d0m1to4m5, d0m1to6m7, d0m1to8m9, d0m1to10m11, + d1m0to3m2, d1m0to5m4, d1m0to7m6, d1m0to9m8, d1m0to11m10, + s6to7, s6to8, s6to9, s6to10, s6to11, + d6m7to8m9, d6m7to10m11, d7m6to9m8, d7m6to11m10, +}; + +#define MAX1363_12X_CHANS(bits) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, 0), \ + MAX1363_CHAN_U(2, _s2, 2, bits, 0), \ + MAX1363_CHAN_U(3, _s3, 3, bits, 0), \ + MAX1363_CHAN_U(4, _s4, 4, bits, 0), \ + MAX1363_CHAN_U(5, _s5, 5, bits, 0), \ + MAX1363_CHAN_U(6, _s6, 6, bits, 0), \ + MAX1363_CHAN_U(7, _s7, 7, bits, 0), \ + MAX1363_CHAN_U(8, _s8, 8, bits, 0), \ + MAX1363_CHAN_U(9, _s9, 9, bits, 0), \ + MAX1363_CHAN_U(10, _s10, 10, bits, 0), \ + MAX1363_CHAN_U(11, _s11, 11, bits, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 12, bits, 0), \ + MAX1363_CHAN_B(2, 3, d2m3, 13, bits, 0), \ + MAX1363_CHAN_B(4, 5, d4m5, 14, bits, 0), \ + MAX1363_CHAN_B(6, 7, d6m7, 15, bits, 0), \ + MAX1363_CHAN_B(8, 9, d8m9, 16, bits, 0), \ + MAX1363_CHAN_B(10, 11, d10m11, 17, bits, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 18, bits, 0), \ + MAX1363_CHAN_B(3, 2, d3m2, 19, bits, 0), \ + MAX1363_CHAN_B(5, 4, d5m4, 20, bits, 0), \ + MAX1363_CHAN_B(7, 6, d7m6, 21, bits, 0), \ + MAX1363_CHAN_B(9, 8, d9m8, 22, bits, 0), \ + MAX1363_CHAN_B(11, 10, d11m10, 23, bits, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(24) \ + } +static struct iio_chan_spec max1038_channels[] = MAX1363_12X_CHANS(8); +static struct iio_chan_spec max1138_channels[] = MAX1363_12X_CHANS(10); +static struct iio_chan_spec max1238_channels[] = MAX1363_12X_CHANS(12); + +static const enum max1363_modes max11607_mode_list[] = { + _s0, _s1, _s2, _s3, + s0to1, s0to2, s0to3, + s2to3, + d0m1, d2m3, d1m0, d3m2, + d0m1to2m3, d1m0to3m2, +}; + +static const enum max1363_modes max11608_mode_list[] = { + _s0, _s1, _s2, _s3, _s4, _s5, _s6, _s7, + s0to1, s0to2, s0to3, s0to4, s0to5, s0to6, s0to7, + s6to7, + d0m1, d2m3, d4m5, d6m7, + d1m0, d3m2, d5m4, d7m6, + d0m1to2m3, d0m1to4m5, d0m1to6m7, + d1m0to3m2, d1m0to5m4, d1m0to7m6, +}; + +#define MAX1363_8X_CHANS(bits) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, 0), \ + MAX1363_CHAN_U(2, _s2, 2, bits, 0), \ + MAX1363_CHAN_U(3, _s3, 3, bits, 0), \ + MAX1363_CHAN_U(4, _s4, 4, bits, 0), \ + MAX1363_CHAN_U(5, _s5, 5, bits, 0), \ + MAX1363_CHAN_U(6, _s6, 6, bits, 0), \ + MAX1363_CHAN_U(7, _s7, 7, bits, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 8, bits, 0), \ + MAX1363_CHAN_B(2, 3, d2m3, 9, bits, 0), \ + MAX1363_CHAN_B(4, 5, d4m5, 10, bits, 0), \ + MAX1363_CHAN_B(6, 7, d6m7, 11, bits, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 12, bits, 0), \ + MAX1363_CHAN_B(3, 2, d3m2, 13, bits, 0), \ + MAX1363_CHAN_B(5, 4, d5m4, 14, bits, 0), \ + MAX1363_CHAN_B(7, 6, d7m6, 15, bits, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(16) \ +} +static struct iio_chan_spec max11602_channels[] = MAX1363_8X_CHANS(8); +static struct iio_chan_spec max11608_channels[] = MAX1363_8X_CHANS(10); +static struct iio_chan_spec max11614_channels[] = MAX1363_8X_CHANS(12); + +static const enum max1363_modes max11644_mode_list[] = { + _s0, _s1, s0to1, d0m1, d1m0, +}; + +#define MAX1363_2X_CHANS(bits) { \ + MAX1363_CHAN_U(0, _s0, 0, bits, 0), \ + MAX1363_CHAN_U(1, _s1, 1, bits, 0), \ + MAX1363_CHAN_B(0, 1, d0m1, 2, bits, 0), \ + MAX1363_CHAN_B(1, 0, d1m0, 3, bits, 0), \ + IIO_CHAN_SOFT_TIMESTAMP(4) \ + } + +static struct iio_chan_spec max11646_channels[] = MAX1363_2X_CHANS(10); +static struct iio_chan_spec max11644_channels[] = MAX1363_2X_CHANS(12); + +enum { max1361, + max1362, + max1363, + max1364, + max1036, + max1037, + max1038, + max1039, + max1136, + max1137, + max1138, + max1139, + max1236, + max1237, + max1238, + max1239, + max11600, + max11601, + max11602, + max11603, + max11604, + max11605, + max11606, + max11607, + max11608, + max11609, + max11610, + max11611, + max11612, + max11613, + max11614, + max11615, + max11616, + max11617, + max11644, + max11645, + max11646, + max11647 +}; + +static const int max1363_monitor_speeds[] = { 133000, 665000, 33300, 16600, + 8300, 4200, 2000, 1000 }; + +static ssize_t max1363_monitor_show_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max1363_state *st = iio_priv(dev_get_drvdata(dev)); + return sprintf(buf, "%d\n", max1363_monitor_speeds[st->monitor_speed]); +} + +static ssize_t max1363_monitor_store_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct max1363_state *st = iio_priv(indio_dev); + int i, ret; + unsigned long val; + bool found = false; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(max1363_monitor_speeds); i++) + if (val == max1363_monitor_speeds[i]) { + found = true; + break; + } + if (!found) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + st->monitor_speed = i; + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, + max1363_monitor_show_freq, + max1363_monitor_store_freq); + +static IIO_CONST_ATTR(sampling_frequency_available, + "133000 665000 33300 16600 8300 4200 2000 1000"); + +static int max1363_read_thresh(struct iio_dev *indio_dev, + u64 event_code, + int *val) +{ + struct max1363_state *st = iio_priv(indio_dev); + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_FALLING) + *val = st->thresh_low[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)]; + else + *val = st->thresh_high[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)]; + return 0; +} + +static int max1363_write_thresh(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + struct max1363_state *st = iio_priv(indio_dev); + /* make it handle signed correctly as well */ + switch (st->chip_info->bits) { + case 10: + if (val > 0x3FF) + return -EINVAL; + break; + case 12: + if (val > 0xFFF) + return -EINVAL; + break; + } + + switch (IIO_EVENT_CODE_EXTRACT_DIR(event_code)) { + case IIO_EV_DIR_FALLING: + st->thresh_low[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)] = val; + break; + case IIO_EV_DIR_RISING: + st->thresh_high[IIO_EVENT_CODE_EXTRACT_CHAN(event_code)] = val; + break; + } + + return 0; +} + +static const u64 max1363_event_codes[] = { + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 0, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), +}; + +static irqreturn_t max1363_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct max1363_state *st = iio_priv(indio_dev); + s64 timestamp = iio_get_time_ns(); + unsigned long mask, loc; + u8 rx; + u8 tx[2] = { st->setupbyte, + MAX1363_MON_INT_ENABLE | (st->monitor_speed << 1) | 0xF0 }; + + i2c_master_recv(st->client, &rx, 1); + mask = rx; + for_each_set_bit(loc, &mask, 8) + iio_push_event(indio_dev, max1363_event_codes[loc], timestamp); + i2c_master_send(st->client, tx, 2); + + return IRQ_HANDLED; +} + +static int max1363_read_event_config(struct iio_dev *indio_dev, + u64 event_code) +{ + struct max1363_state *st = iio_priv(indio_dev); + + int val; + int number = IIO_EVENT_CODE_EXTRACT_CHAN(event_code); + mutex_lock(&indio_dev->mlock); + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_FALLING) + val = (1 << number) & st->mask_low; + else + val = (1 << number) & st->mask_high; + mutex_unlock(&indio_dev->mlock); + + return val; +} + +static int max1363_monitor_mode_update(struct max1363_state *st, int enabled) +{ + u8 *tx_buf; + int ret, i = 3, j; + unsigned long numelements; + int len; + const long *modemask; + + if (!enabled) { + /* transition to ring capture is not currently supported */ + st->setupbyte &= ~MAX1363_SETUP_MONITOR_SETUP; + st->configbyte &= ~MAX1363_SCAN_MASK; + st->monitor_on = false; + return max1363_write_basic_config(st->client, + st->setupbyte, + st->configbyte); + } + + /* Ensure we are in the relevant mode */ + st->setupbyte |= MAX1363_SETUP_MONITOR_SETUP; + st->configbyte &= ~(MAX1363_CHANNEL_SEL_MASK + | MAX1363_SCAN_MASK + | MAX1363_SE_DE_MASK); + st->configbyte |= MAX1363_CONFIG_SCAN_MONITOR_MODE; + if ((st->mask_low | st->mask_high) & 0x0F) { + st->configbyte |= max1363_mode_table[s0to3].conf; + modemask = max1363_mode_table[s0to3].modemask; + } else if ((st->mask_low | st->mask_high) & 0x30) { + st->configbyte |= max1363_mode_table[d0m1to2m3].conf; + modemask = max1363_mode_table[d0m1to2m3].modemask; + } else { + st->configbyte |= max1363_mode_table[d1m0to3m2].conf; + modemask = max1363_mode_table[d1m0to3m2].modemask; + } + numelements = bitmap_weight(modemask, MAX1363_MAX_CHANNELS); + len = 3 * numelements + 3; + tx_buf = kmalloc(len, GFP_KERNEL); + if (!tx_buf) { + ret = -ENOMEM; + goto error_ret; + } + tx_buf[0] = st->configbyte; + tx_buf[1] = st->setupbyte; + tx_buf[2] = (st->monitor_speed << 1); + + /* + * So we need to do yet another bit of nefarious scan mode + * setup to match what we need. + */ + for (j = 0; j < 8; j++) + if (test_bit(j, modemask)) { + /* Establish the mode is in the scan */ + if (st->mask_low & (1 << j)) { + tx_buf[i] = (st->thresh_low[j] >> 4) & 0xFF; + tx_buf[i + 1] = (st->thresh_low[j] << 4) & 0xF0; + } else if (j < 4) { + tx_buf[i] = 0; + tx_buf[i + 1] = 0; + } else { + tx_buf[i] = 0x80; + tx_buf[i + 1] = 0; + } + if (st->mask_high & (1 << j)) { + tx_buf[i + 1] |= + (st->thresh_high[j] >> 8) & 0x0F; + tx_buf[i + 2] = st->thresh_high[j] & 0xFF; + } else if (j < 4) { + tx_buf[i + 1] |= 0x0F; + tx_buf[i + 2] = 0xFF; + } else { + tx_buf[i + 1] |= 0x07; + tx_buf[i + 2] = 0xFF; + } + i += 3; + } + + + ret = i2c_master_send(st->client, tx_buf, len); + if (ret < 0) + goto error_ret; + if (ret != len) { + ret = -EIO; + goto error_ret; + } + + /* + * Now that we hopefully have sensible thresholds in place it is + * time to turn the interrupts on. + * It is unclear from the data sheet if this should be necessary + * (i.e. whether monitor mode setup is atomic) but it appears to + * be in practice. + */ + tx_buf[0] = st->setupbyte; + tx_buf[1] = MAX1363_MON_INT_ENABLE | (st->monitor_speed << 1) | 0xF0; + ret = i2c_master_send(st->client, tx_buf, 2); + if (ret < 0) + goto error_ret; + if (ret != 2) { + ret = -EIO; + goto error_ret; + } + ret = 0; + st->monitor_on = true; +error_ret: + + kfree(tx_buf); + + return ret; +} + +/* + * To keep this manageable we always use one of 3 scan modes. + * Scan 0...3, 0-1,2-3 and 1-0,3-2 + */ + +static inline int __max1363_check_event_mask(int thismask, int checkmask) +{ + int ret = 0; + /* Is it unipolar */ + if (thismask < 4) { + if (checkmask & ~0x0F) { + ret = -EBUSY; + goto error_ret; + } + } else if (thismask < 6) { + if (checkmask & ~0x30) { + ret = -EBUSY; + goto error_ret; + } + } else if (checkmask & ~0xC0) + ret = -EBUSY; +error_ret: + return ret; +} + +static int max1363_write_event_config(struct iio_dev *indio_dev, + u64 event_code, + int state) +{ + int ret = 0; + struct max1363_state *st = iio_priv(indio_dev); + u16 unifiedmask; + int number = IIO_EVENT_CODE_EXTRACT_CHAN(event_code); + + mutex_lock(&indio_dev->mlock); + unifiedmask = st->mask_low | st->mask_high; + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_FALLING) { + + if (state == 0) + st->mask_low &= ~(1 << number); + else { + ret = __max1363_check_event_mask((1 << number), + unifiedmask); + if (ret) + goto error_ret; + st->mask_low |= (1 << number); + } + } else { + if (state == 0) + st->mask_high &= ~(1 << number); + else { + ret = __max1363_check_event_mask((1 << number), + unifiedmask); + if (ret) + goto error_ret; + st->mask_high |= (1 << number); + } + } + + max1363_monitor_mode_update(st, !!(st->mask_high | st->mask_low)); +error_ret: + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +/* + * As with scan_elements, only certain sets of these can + * be combined. + */ +static struct attribute *max1363_event_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static struct attribute_group max1363_event_attribute_group = { + .attrs = max1363_event_attributes, + .name = "events", +}; + +#define MAX1363_EVENT_FUNCS \ + + +static const struct iio_info max1238_info = { + .read_raw = &max1363_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info max1363_info = { + .read_event_value = &max1363_read_thresh, + .write_event_value = &max1363_write_thresh, + .read_event_config = &max1363_read_event_config, + .write_event_config = &max1363_write_event_config, + .read_raw = &max1363_read_raw, + .update_scan_mode = &max1363_update_scan_mode, + .driver_module = THIS_MODULE, + .event_attrs = &max1363_event_attribute_group, +}; + +/* max1363 and max1368 tested - rest from data sheet */ +static const struct max1363_chip_info max1363_chip_info_tbl[] = { + [max1361] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1361_channels, + .num_channels = ARRAY_SIZE(max1361_channels), + .info = &max1363_info, + }, + [max1362] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1361_channels, + .num_channels = ARRAY_SIZE(max1361_channels), + .info = &max1363_info, + }, + [max1363] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + .info = &max1363_info, + }, + [max1364] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1363_mode_list, + .num_modes = ARRAY_SIZE(max1363_mode_list), + .default_mode = s0to3, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + .info = &max1363_info, + }, + [max1036] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max1037] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max1038] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max1039] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1038_channels, + .num_channels = ARRAY_SIZE(max1038_channels), + }, + [max1136] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max1137] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max1138] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max1139] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1138_channels, + .num_channels = ARRAY_SIZE(max1138_channels), + }, + [max1236] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1236_channels, + .num_channels = ARRAY_SIZE(max1236_channels), + }, + [max1237] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1236_mode_list, + .num_modes = ARRAY_SIZE(max1236_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1236_channels, + .num_channels = ARRAY_SIZE(max1236_channels), + }, + [max1238] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max1239] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11600] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max11601] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1036_channels, + .num_channels = ARRAY_SIZE(max1036_channels), + }, + [max11602] = { + .bits = 8, + .int_vref_mv = 4096, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11602_channels, + .num_channels = ARRAY_SIZE(max11602_channels), + }, + [max11603] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11602_channels, + .num_channels = ARRAY_SIZE(max11602_channels), + }, + [max11604] = { + .bits = 8, + .int_vref_mv = 4098, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11605] = { + .bits = 8, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11606] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max11607] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1136_channels, + .num_channels = ARRAY_SIZE(max1136_channels), + }, + [max11608] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11608_channels, + .num_channels = ARRAY_SIZE(max11608_channels), + }, + [max11609] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11608_channels, + .num_channels = ARRAY_SIZE(max11608_channels), + }, + [max11610] = { + .bits = 10, + .int_vref_mv = 4098, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11611] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11612] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + }, + [max11613] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max11607_mode_list, + .num_modes = ARRAY_SIZE(max11607_mode_list), + .default_mode = s0to3, + .info = &max1238_info, + .channels = max1363_channels, + .num_channels = ARRAY_SIZE(max1363_channels), + }, + [max11614] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11614_channels, + .num_channels = ARRAY_SIZE(max11614_channels), + }, + [max11615] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max11608_mode_list, + .num_modes = ARRAY_SIZE(max11608_mode_list), + .default_mode = s0to7, + .info = &max1238_info, + .channels = max11614_channels, + .num_channels = ARRAY_SIZE(max11614_channels), + }, + [max11616] = { + .bits = 12, + .int_vref_mv = 4098, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11617] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max1238_mode_list, + .num_modes = ARRAY_SIZE(max1238_mode_list), + .default_mode = s0to11, + .info = &max1238_info, + .channels = max1238_channels, + .num_channels = ARRAY_SIZE(max1238_channels), + }, + [max11644] = { + .bits = 12, + .int_vref_mv = 2048, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11644_channels, + .num_channels = ARRAY_SIZE(max11644_channels), + }, + [max11645] = { + .bits = 12, + .int_vref_mv = 4096, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11644_channels, + .num_channels = ARRAY_SIZE(max11644_channels), + }, + [max11646] = { + .bits = 10, + .int_vref_mv = 2048, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11646_channels, + .num_channels = ARRAY_SIZE(max11646_channels), + }, + [max11647] = { + .bits = 10, + .int_vref_mv = 4096, + .mode_list = max11644_mode_list, + .num_modes = ARRAY_SIZE(max11644_mode_list), + .default_mode = s0to1, + .info = &max1238_info, + .channels = max11646_channels, + .num_channels = ARRAY_SIZE(max11646_channels), + }, +}; + + + +static int max1363_initial_setup(struct max1363_state *st) +{ + st->setupbyte = MAX1363_SETUP_AIN3_IS_AIN3_REF_IS_VDD + | MAX1363_SETUP_POWER_UP_INT_REF + | MAX1363_SETUP_INT_CLOCK + | MAX1363_SETUP_UNIPOLAR + | MAX1363_SETUP_NORESET; + + /* Set scan mode writes the config anyway so wait until then*/ + st->setupbyte = MAX1363_SETUP_BYTE(st->setupbyte); + st->current_mode = &max1363_mode_table[st->chip_info->default_mode]; + st->configbyte = MAX1363_CONFIG_BYTE(st->configbyte); + + return max1363_set_scan_mode(st); +} + +static int __devinit max1363_alloc_scan_masks(struct iio_dev *indio_dev) +{ + struct max1363_state *st = iio_priv(indio_dev); + unsigned long *masks; + int i; + + masks = kzalloc(BITS_TO_LONGS(MAX1363_MAX_CHANNELS)*sizeof(long)* + (st->chip_info->num_modes + 1), GFP_KERNEL); + if (!masks) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_modes; i++) + bitmap_copy(masks + BITS_TO_LONGS(MAX1363_MAX_CHANNELS)*i, + max1363_mode_table[st->chip_info->mode_list[i]] + .modemask, MAX1363_MAX_CHANNELS); + + indio_dev->available_scan_masks = masks; + + return 0; +} + +static int __devinit max1363_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct max1363_state *st; + struct iio_dev *indio_dev; + struct regulator *reg; + + reg = regulator_get(&client->dev, "vcc"); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + goto error_out; + } + + ret = regulator_enable(reg); + if (ret) + goto error_put_reg; + + indio_dev = iio_allocate_device(sizeof(struct max1363_state)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_disable_reg; + } + st = iio_priv(indio_dev); + st->reg = reg; + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + st->chip_info = &max1363_chip_info_tbl[id->driver_data]; + st->client = client; + + ret = max1363_alloc_scan_masks(indio_dev); + if (ret) + goto error_free_device; + + /* Estabilish that the iio_dev is a child of the i2c device */ + indio_dev->dev.parent = &client->dev; + indio_dev->name = id->name; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = st->chip_info->info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + ret = max1363_initial_setup(st); + if (ret < 0) + goto error_free_available_scan_masks; + + ret = max1363_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_free_available_scan_masks; + + ret = iio_buffer_register(indio_dev, + st->chip_info->channels, + st->chip_info->num_channels); + if (ret) + goto error_cleanup_ring; + + if (client->irq) { + ret = request_threaded_irq(st->client->irq, + NULL, + &max1363_event_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "max1363_event", + indio_dev); + + if (ret) + goto error_uninit_ring; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_free_irq; + + return 0; +error_free_irq: + free_irq(st->client->irq, indio_dev); +error_uninit_ring: + iio_buffer_unregister(indio_dev); +error_cleanup_ring: + max1363_ring_cleanup(indio_dev); +error_free_available_scan_masks: + kfree(indio_dev->available_scan_masks); +error_free_device: + iio_free_device(indio_dev); +error_disable_reg: + regulator_disable(reg); +error_put_reg: + regulator_put(reg); +error_out: + return ret; +} + +static int max1363_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct max1363_state *st = iio_priv(indio_dev); + struct regulator *reg = st->reg; + + iio_device_unregister(indio_dev); + if (client->irq) + free_irq(st->client->irq, indio_dev); + iio_buffer_unregister(indio_dev); + max1363_ring_cleanup(indio_dev); + kfree(indio_dev->available_scan_masks); + if (!IS_ERR(reg)) { + regulator_disable(reg); + regulator_put(reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id max1363_id[] = { + { "max1361", max1361 }, + { "max1362", max1362 }, + { "max1363", max1363 }, + { "max1364", max1364 }, + { "max1036", max1036 }, + { "max1037", max1037 }, + { "max1038", max1038 }, + { "max1039", max1039 }, + { "max1136", max1136 }, + { "max1137", max1137 }, + { "max1138", max1138 }, + { "max1139", max1139 }, + { "max1236", max1236 }, + { "max1237", max1237 }, + { "max1238", max1238 }, + { "max1239", max1239 }, + { "max11600", max11600 }, + { "max11601", max11601 }, + { "max11602", max11602 }, + { "max11603", max11603 }, + { "max11604", max11604 }, + { "max11605", max11605 }, + { "max11606", max11606 }, + { "max11607", max11607 }, + { "max11608", max11608 }, + { "max11609", max11609 }, + { "max11610", max11610 }, + { "max11611", max11611 }, + { "max11612", max11612 }, + { "max11613", max11613 }, + { "max11614", max11614 }, + { "max11615", max11615 }, + { "max11616", max11616 }, + { "max11617", max11617 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max1363_id); + +static struct i2c_driver max1363_driver = { + .driver = { + .name = "max1363", + }, + .probe = max1363_probe, + .remove = max1363_remove, + .id_table = max1363_id, +}; +module_i2c_driver(max1363_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Maxim 1363 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/adc/max1363_ring.c b/drivers/staging/iio/adc/max1363_ring.c new file mode 100644 index 00000000..d0a60a38 --- /dev/null +++ b/drivers/staging/iio/adc/max1363_ring.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * max1363_ring.c + */ + +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../buffer.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" + +#include "max1363.h" + +int max1363_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct max1363_state *st = iio_priv(indio_dev); + + /* + * Need to figure out the current mode based upon the requested + * scan mask in iio_dev + */ + st->current_mode = max1363_match_mode(scan_mask, st->chip_info); + if (!st->current_mode) + return -EINVAL; + max1363_set_scan_mode(st); + return 0; +} + +static irqreturn_t max1363_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct max1363_state *st = iio_priv(indio_dev); + s64 time_ns; + __u8 *rxbuf; + int b_sent; + size_t d_size; + unsigned long numvals = bitmap_weight(st->current_mode->modemask, + MAX1363_MAX_CHANNELS); + + /* Ensure the timestamp is 8 byte aligned */ + if (st->chip_info->bits != 8) + d_size = numvals*2; + else + d_size = numvals; + if (indio_dev->buffer->scan_timestamp) { + d_size += sizeof(s64); + if (d_size % sizeof(s64)) + d_size += sizeof(s64) - (d_size % sizeof(s64)); + } + /* Monitor mode prevents reading. Whilst not currently implemented + * might as well have this test in here in the meantime as it does + * no harm. + */ + if (numvals == 0) + return IRQ_HANDLED; + + rxbuf = kmalloc(d_size, GFP_KERNEL); + if (rxbuf == NULL) + return -ENOMEM; + if (st->chip_info->bits != 8) + b_sent = i2c_master_recv(st->client, rxbuf, numvals*2); + else + b_sent = i2c_master_recv(st->client, rxbuf, numvals); + if (b_sent < 0) + goto done; + + time_ns = iio_get_time_ns(); + + if (indio_dev->buffer->scan_timestamp) + memcpy(rxbuf + d_size - sizeof(s64), &time_ns, sizeof(time_ns)); + iio_push_to_buffer(indio_dev->buffer, rxbuf, time_ns); + +done: + iio_trigger_notify_done(indio_dev->trig); + kfree(rxbuf); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops max1363_ring_setup_ops = { + .postenable = &iio_triggered_buffer_postenable, + .preenable = &iio_sw_buffer_preenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int max1363_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + struct max1363_state *st = iio_priv(indio_dev); + int ret = 0; + + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + goto error_ret; + } + indio_dev->pollfunc = iio_alloc_pollfunc(NULL, + &max1363_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + st->client->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_deallocate_sw_rb; + } + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &max1363_ring_setup_ops; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + return 0; + +error_deallocate_sw_rb: + iio_sw_rb_free(indio_dev->buffer); +error_ret: + return ret; +} + +void max1363_ring_cleanup(struct iio_dev *indio_dev) +{ + /* ensure that the trigger has been detached */ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} diff --git a/drivers/staging/iio/addac/Kconfig b/drivers/staging/iio/addac/Kconfig new file mode 100644 index 00000000..698a8970 --- /dev/null +++ b/drivers/staging/iio/addac/Kconfig @@ -0,0 +1,28 @@ +# +# ADDAC drivers +# +menu "Analog digital bi-direction converters" + +config ADT7316 + tristate "Analog Devices ADT7316/7/8 ADT7516/7/9 temperature sensor, ADC and DAC driver" + depends on GENERIC_GPIO + help + Say yes here to build support for Analog Devices ADT7316, ADT7317, ADT7318 + and ADT7516, ADT7517, ADT7519 temperature sensors, ADC and DAC. + +config ADT7316_SPI + tristate "support SPI bus connection" + depends on SPI && ADT7316 + default y + help + Say yes here to build SPI bus support for Analog Devices ADT7316/7/8 + and ADT7516/7/9. + +config ADT7316_I2C + tristate "support I2C bus connection" + depends on I2C && ADT7316 + help + Say yes here to build I2C bus support for Analog Devices ADT7316/7/8 + and ADT7516/7/9. + +endmenu diff --git a/drivers/staging/iio/addac/Makefile b/drivers/staging/iio/addac/Makefile new file mode 100644 index 00000000..4c768613 --- /dev/null +++ b/drivers/staging/iio/addac/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for industrial I/O ADDAC drivers +# + +obj-$(CONFIG_ADT7316) += adt7316.o +obj-$(CONFIG_ADT7316_SPI) += adt7316-spi.o +obj-$(CONFIG_ADT7316_I2C) += adt7316-i2c.o diff --git a/drivers/staging/iio/addac/adt7316-i2c.c b/drivers/staging/iio/addac/adt7316-i2c.c new file mode 100644 index 00000000..9e128dd7 --- /dev/null +++ b/drivers/staging/iio/addac/adt7316-i2c.c @@ -0,0 +1,143 @@ +/* + * I2C bus driver for ADT7316/7/8 ADT7516/7/9 digital temperature + * sensor, ADC and DAC + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include "adt7316.h" + +/* + * adt7316 register access by I2C + */ +static int adt7316_i2c_read(void *client, u8 reg, u8 *data) +{ + struct i2c_client *cl = client; + int ret = 0; + + ret = i2c_smbus_write_byte(cl, reg); + if (ret < 0) { + dev_err(&cl->dev, "I2C fail to select reg\n"); + return ret; + } + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(&cl->dev, "I2C read error\n"); + return ret; + } + + return 0; +} + +static int adt7316_i2c_write(void *client, u8 reg, u8 data) +{ + struct i2c_client *cl = client; + int ret = 0; + + ret = i2c_smbus_write_byte_data(cl, reg, data); + if (ret < 0) + dev_err(&cl->dev, "I2C write error\n"); + + return ret; +} + +static int adt7316_i2c_multi_read(void *client, u8 reg, u8 count, u8 *data) +{ + struct i2c_client *cl = client; + int i, ret = 0; + + if (count > ADT7316_REG_MAX_ADDR) + count = ADT7316_REG_MAX_ADDR; + + for (i = 0; i < count; i++) { + ret = adt7316_i2c_read(cl, reg, &data[i]); + if (ret < 0) { + dev_err(&cl->dev, "I2C multi read error\n"); + return ret; + } + } + + return 0; +} + +static int adt7316_i2c_multi_write(void *client, u8 reg, u8 count, u8 *data) +{ + struct i2c_client *cl = client; + int i, ret = 0; + + if (count > ADT7316_REG_MAX_ADDR) + count = ADT7316_REG_MAX_ADDR; + + for (i = 0; i < count; i++) { + ret = adt7316_i2c_write(cl, reg, data[i]); + if (ret < 0) { + dev_err(&cl->dev, "I2C multi write error\n"); + return ret; + } + } + + return 0; +} + +/* + * device probe and remove + */ + +static int __devinit adt7316_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adt7316_bus bus = { + .client = client, + .irq = client->irq, + .irq_flags = IRQF_TRIGGER_LOW, + .read = adt7316_i2c_read, + .write = adt7316_i2c_write, + .multi_read = adt7316_i2c_multi_read, + .multi_write = adt7316_i2c_multi_write, + }; + + return adt7316_probe(&client->dev, &bus, id->name); +} + +static int __devexit adt7316_i2c_remove(struct i2c_client *client) +{ + return adt7316_remove(&client->dev); +} + +static const struct i2c_device_id adt7316_i2c_id[] = { + { "adt7316", 0 }, + { "adt7317", 0 }, + { "adt7318", 0 }, + { "adt7516", 0 }, + { "adt7517", 0 }, + { "adt7519", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, adt7316_i2c_id); + +static struct i2c_driver adt7316_driver = { + .driver = { + .name = "adt7316", + .pm = ADT7316_PM_OPS, + .owner = THIS_MODULE, + }, + .probe = adt7316_i2c_probe, + .remove = __devexit_p(adt7316_i2c_remove), + .id_table = adt7316_i2c_id, +}; +module_i2c_driver(adt7316_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("I2C bus driver for Analog Devices ADT7316/7/9 and" + "ADT7516/7/8 digital temperature sensor, ADC and DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/addac/adt7316-spi.c b/drivers/staging/iio/addac/adt7316-spi.c new file mode 100644 index 00000000..985f7d8a --- /dev/null +++ b/drivers/staging/iio/addac/adt7316-spi.c @@ -0,0 +1,151 @@ +/* + * API bus driver for ADT7316/7/8 ADT7516/7/9 digital temperature + * sensor, ADC and DAC + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include "adt7316.h" + +#define ADT7316_SPI_MAX_FREQ_HZ 5000000 +#define ADT7316_SPI_CMD_READ 0x91 +#define ADT7316_SPI_CMD_WRITE 0x90 + +/* + * adt7316 register access by SPI + */ + +static int adt7316_spi_multi_read(void *client, u8 reg, u8 count, u8 *data) +{ + struct spi_device *spi_dev = client; + u8 cmd[2]; + int ret = 0; + + if (count > ADT7316_REG_MAX_ADDR) + count = ADT7316_REG_MAX_ADDR; + + cmd[0] = ADT7316_SPI_CMD_WRITE; + cmd[1] = reg; + + ret = spi_write(spi_dev, cmd, 2); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI fail to select reg\n"); + return ret; + } + + cmd[0] = ADT7316_SPI_CMD_READ; + + ret = spi_write_then_read(spi_dev, cmd, 1, data, count); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI read data error\n"); + return ret; + } + + return 0; +} + +static int adt7316_spi_multi_write(void *client, u8 reg, u8 count, u8 *data) +{ + struct spi_device *spi_dev = client; + u8 buf[ADT7316_REG_MAX_ADDR + 2]; + int i, ret = 0; + + if (count > ADT7316_REG_MAX_ADDR) + count = ADT7316_REG_MAX_ADDR; + + buf[0] = ADT7316_SPI_CMD_WRITE; + buf[1] = reg; + for (i = 0; i < count; i++) + buf[i + 2] = data[i]; + + ret = spi_write(spi_dev, buf, count + 2); + if (ret < 0) { + dev_err(&spi_dev->dev, "SPI write error\n"); + return ret; + } + + return ret; +} + +static int adt7316_spi_read(void *client, u8 reg, u8 *data) +{ + return adt7316_spi_multi_read(client, reg, 1, data); +} + +static int adt7316_spi_write(void *client, u8 reg, u8 val) +{ + return adt7316_spi_multi_write(client, reg, 1, &val); +} + +/* + * device probe and remove + */ + +static int __devinit adt7316_spi_probe(struct spi_device *spi_dev) +{ + struct adt7316_bus bus = { + .client = spi_dev, + .irq = spi_dev->irq, + .irq_flags = IRQF_TRIGGER_LOW, + .read = adt7316_spi_read, + .write = adt7316_spi_write, + .multi_read = adt7316_spi_multi_read, + .multi_write = adt7316_spi_multi_write, + }; + + /* don't exceed max specified SPI CLK frequency */ + if (spi_dev->max_speed_hz > ADT7316_SPI_MAX_FREQ_HZ) { + dev_err(&spi_dev->dev, "SPI CLK %d Hz?\n", + spi_dev->max_speed_hz); + return -EINVAL; + } + + /* switch from default I2C protocol to SPI protocol */ + adt7316_spi_write(spi_dev, 0, 0); + adt7316_spi_write(spi_dev, 0, 0); + adt7316_spi_write(spi_dev, 0, 0); + + return adt7316_probe(&spi_dev->dev, &bus, spi_dev->modalias); +} + +static int __devexit adt7316_spi_remove(struct spi_device *spi_dev) +{ + return adt7316_remove(&spi_dev->dev); +} + +static const struct spi_device_id adt7316_spi_id[] = { + { "adt7316", 0 }, + { "adt7317", 0 }, + { "adt7318", 0 }, + { "adt7516", 0 }, + { "adt7517", 0 }, + { "adt7519", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(spi, adt7316_spi_id); + +static struct spi_driver adt7316_driver = { + .driver = { + .name = "adt7316", + .pm = ADT7316_PM_OPS, + .owner = THIS_MODULE, + }, + .probe = adt7316_spi_probe, + .remove = __devexit_p(adt7316_spi_remove), + .id_table = adt7316_spi_id, +}; +module_spi_driver(adt7316_driver); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("SPI bus driver for Analog Devices ADT7316/7/8 and" + "ADT7516/7/9 digital temperature sensor, ADC and DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/addac/adt7316.c b/drivers/staging/iio/addac/adt7316.c new file mode 100644 index 00000000..fd6a4544 --- /dev/null +++ b/drivers/staging/iio/addac/adt7316.c @@ -0,0 +1,2236 @@ +/* + * ADT7316 digital temperature sensor driver supporting ADT7316/7/8 ADT7516/7/9 + * + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../events.h" +#include "../sysfs.h" +#include "adt7316.h" + +/* + * ADT7316 registers definition + */ +#define ADT7316_INT_STAT1 0x0 +#define ADT7316_INT_STAT2 0x1 +#define ADT7316_LSB_IN_TEMP_VDD 0x3 +#define ADT7316_LSB_IN_TEMP_MASK 0x3 +#define ADT7316_LSB_VDD_MASK 0xC +#define ADT7316_LSB_VDD_OFFSET 2 +#define ADT7316_LSB_EX_TEMP_AIN 0x4 +#define ADT7316_LSB_EX_TEMP_MASK 0x3 +#define ADT7516_LSB_AIN_SHIFT 2 +#define ADT7316_AD_MSB_DATA_BASE 0x6 +#define ADT7316_AD_MSB_DATA_REGS 3 +#define ADT7516_AD_MSB_DATA_REGS 6 +#define ADT7316_MSB_VDD 0x6 +#define ADT7316_MSB_IN_TEMP 0x7 +#define ADT7316_MSB_EX_TEMP 0x8 +#define ADT7516_MSB_AIN1 0x8 +#define ADT7516_MSB_AIN2 0x9 +#define ADT7516_MSB_AIN3 0xA +#define ADT7516_MSB_AIN4 0xB +#define ADT7316_DA_DATA_BASE 0x10 +#define ADT7316_DA_MSB_DATA_REGS 4 +#define ADT7316_LSB_DAC_A 0x10 +#define ADT7316_MSB_DAC_A 0x11 +#define ADT7316_LSB_DAC_B 0x12 +#define ADT7316_MSB_DAC_B 0x13 +#define ADT7316_LSB_DAC_C 0x14 +#define ADT7316_MSB_DAC_C 0x15 +#define ADT7316_LSB_DAC_D 0x16 +#define ADT7316_MSB_DAC_D 0x17 +#define ADT7316_CONFIG1 0x18 +#define ADT7316_CONFIG2 0x19 +#define ADT7316_CONFIG3 0x1A +#define ADT7316_LDAC_CONFIG 0x1B +#define ADT7316_DAC_CONFIG 0x1C +#define ADT7316_INT_MASK1 0x1D +#define ADT7316_INT_MASK2 0x1E +#define ADT7316_IN_TEMP_OFFSET 0x1F +#define ADT7316_EX_TEMP_OFFSET 0x20 +#define ADT7316_IN_ANALOG_TEMP_OFFSET 0x21 +#define ADT7316_EX_ANALOG_TEMP_OFFSET 0x22 +#define ADT7316_VDD_HIGH 0x23 +#define ADT7316_VDD_LOW 0x24 +#define ADT7316_IN_TEMP_HIGH 0x25 +#define ADT7316_IN_TEMP_LOW 0x26 +#define ADT7316_EX_TEMP_HIGH 0x27 +#define ADT7316_EX_TEMP_LOW 0x28 +#define ADT7516_AIN2_HIGH 0x2B +#define ADT7516_AIN2_LOW 0x2C +#define ADT7516_AIN3_HIGH 0x2D +#define ADT7516_AIN3_LOW 0x2E +#define ADT7516_AIN4_HIGH 0x2F +#define ADT7516_AIN4_LOW 0x30 +#define ADT7316_DEVICE_ID 0x4D +#define ADT7316_MANUFACTURE_ID 0x4E +#define ADT7316_DEVICE_REV 0x4F +#define ADT7316_SPI_LOCK_STAT 0x7F + +/* + * ADT7316 config1 + */ +#define ADT7316_EN 0x1 +#define ADT7516_SEL_EX_TEMP 0x4 +#define ADT7516_SEL_AIN1_2_EX_TEMP_MASK 0x6 +#define ADT7516_SEL_AIN3 0x8 +#define ADT7316_INT_EN 0x20 +#define ADT7316_INT_POLARITY 0x40 +#define ADT7316_PD 0x80 + +/* + * ADT7316 config2 + */ +#define ADT7316_AD_SINGLE_CH_MASK 0x3 +#define ADT7516_AD_SINGLE_CH_MASK 0x7 +#define ADT7316_AD_SINGLE_CH_VDD 0 +#define ADT7316_AD_SINGLE_CH_IN 1 +#define ADT7316_AD_SINGLE_CH_EX 2 +#define ADT7516_AD_SINGLE_CH_AIN1 2 +#define ADT7516_AD_SINGLE_CH_AIN2 3 +#define ADT7516_AD_SINGLE_CH_AIN3 4 +#define ADT7516_AD_SINGLE_CH_AIN4 5 +#define ADT7316_AD_SINGLE_CH_MODE 0x10 +#define ADT7316_DISABLE_AVERAGING 0x20 +#define ADT7316_EN_SMBUS_TIMEOUT 0x40 +#define ADT7316_RESET 0x80 + +/* + * ADT7316 config3 + */ +#define ADT7316_ADCLK_22_5 0x1 +#define ADT7316_DA_HIGH_RESOLUTION 0x2 +#define ADT7316_DA_EN_VIA_DAC_LDCA 0x4 +#define ADT7516_AIN_IN_VREF 0x10 +#define ADT7316_EN_IN_TEMP_PROP_DACA 0x20 +#define ADT7316_EN_EX_TEMP_PROP_DACB 0x40 + +/* + * ADT7316 DAC config + */ +#define ADT7316_DA_2VREF_CH_MASK 0xF +#define ADT7316_DA_EN_MODE_MASK 0x30 +#define ADT7316_DA_EN_MODE_SINGLE 0x00 +#define ADT7316_DA_EN_MODE_AB_CD 0x10 +#define ADT7316_DA_EN_MODE_ABCD 0x20 +#define ADT7316_DA_EN_MODE_LDAC 0x30 +#define ADT7316_VREF_BYPASS_DAC_AB 0x40 +#define ADT7316_VREF_BYPASS_DAC_CD 0x80 + +/* + * ADT7316 LDAC config + */ +#define ADT7316_LDAC_EN_DA_MASK 0xF +#define ADT7316_DAC_IN_VREF 0x10 +#define ADT7516_DAC_AB_IN_VREF 0x10 +#define ADT7516_DAC_CD_IN_VREF 0x20 +#define ADT7516_DAC_IN_VREF_OFFSET 4 +#define ADT7516_DAC_IN_VREF_MASK 0x30 + +/* + * ADT7316 INT_MASK2 + */ +#define ADT7316_INT_MASK2_VDD 0x10 + +/* + * ADT7316 value masks + */ +#define ADT7316_VALUE_MASK 0xfff +#define ADT7316_T_VALUE_SIGN 0x400 +#define ADT7316_T_VALUE_FLOAT_OFFSET 2 +#define ADT7316_T_VALUE_FLOAT_MASK 0x2 + +/* + * Chip ID + */ +#define ID_ADT7316 0x1 +#define ID_ADT7317 0x2 +#define ID_ADT7318 0x3 +#define ID_ADT7516 0x11 +#define ID_ADT7517 0x12 +#define ID_ADT7519 0x14 + +#define ID_FAMILY_MASK 0xF0 +#define ID_ADT73XX 0x0 +#define ID_ADT75XX 0x10 + +/* + * struct adt7316_chip_info - chip specifc information + */ + +struct adt7316_chip_info { + struct adt7316_bus bus; + u16 ldac_pin; + u16 int_mask; /* 0x2f */ + u8 config1; + u8 config2; + u8 config3; + u8 dac_config; /* DAC config */ + u8 ldac_config; /* LDAC config */ + u8 dac_bits; /* 8, 10, 12 */ + u8 id; /* chip id */ +}; + +/* + * Logic interrupt mask for user application to enable + * interrupts. + */ +#define ADT7316_IN_TEMP_HIGH_INT_MASK 0x1 +#define ADT7316_IN_TEMP_LOW_INT_MASK 0x2 +#define ADT7316_EX_TEMP_HIGH_INT_MASK 0x4 +#define ADT7316_EX_TEMP_LOW_INT_MASK 0x8 +#define ADT7316_EX_TEMP_FAULT_INT_MASK 0x10 +#define ADT7516_AIN1_INT_MASK 0x4 +#define ADT7516_AIN2_INT_MASK 0x20 +#define ADT7516_AIN3_INT_MASK 0x40 +#define ADT7516_AIN4_INT_MASK 0x80 +#define ADT7316_VDD_INT_MASK 0x100 +#define ADT7316_TEMP_INT_MASK 0x1F +#define ADT7516_AIN_INT_MASK 0xE0 +#define ADT7316_TEMP_AIN_INT_MASK \ + (ADT7316_TEMP_INT_MASK) + +/* + * struct adt7316_chip_info - chip specifc information + */ + +struct adt7316_limit_regs { + u16 data_high; + u16 data_low; +}; + +static ssize_t adt7316_show_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", !!(chip->config1 & ADT7316_EN)); +} + +static ssize_t _adt7316_store_enabled(struct adt7316_chip_info *chip, + int enable) +{ + u8 config1; + int ret; + + if (enable) + config1 = chip->config1 | ADT7316_EN; + else + config1 = chip->config1 & ~ADT7316_EN; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, config1); + if (ret) + return -EIO; + + chip->config1 = config1; + + return ret; + +} + +static ssize_t adt7316_store_enabled(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + int enable; + + if (!memcmp(buf, "1", 1)) + enable = 1; + else + enable = 0; + + if (_adt7316_store_enabled(chip, enable) < 0) + return -EIO; + else + return len; +} + +static IIO_DEVICE_ATTR(enabled, S_IRUGO | S_IWUSR, + adt7316_show_enabled, + adt7316_store_enabled, + 0); + +static ssize_t adt7316_show_select_ex_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + return -EPERM; + + return sprintf(buf, "%d\n", !!(chip->config1 & ADT7516_SEL_EX_TEMP)); +} + +static ssize_t adt7316_store_select_ex_temp(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config1; + int ret; + + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + return -EPERM; + + config1 = chip->config1 & (~ADT7516_SEL_EX_TEMP); + if (!memcmp(buf, "1", 1)) + config1 |= ADT7516_SEL_EX_TEMP; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, config1); + if (ret) + return -EIO; + + chip->config1 = config1; + + return len; +} + +static IIO_DEVICE_ATTR(select_ex_temp, S_IRUGO | S_IWUSR, + adt7316_show_select_ex_temp, + adt7316_store_select_ex_temp, + 0); + +static ssize_t adt7316_show_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (chip->config2 & ADT7316_AD_SINGLE_CH_MODE) + return sprintf(buf, "single_channel\n"); + else + return sprintf(buf, "round_robin\n"); +} + +static ssize_t adt7316_store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config2; + int ret; + + config2 = chip->config2 & (~ADT7316_AD_SINGLE_CH_MODE); + if (!memcmp(buf, "single_channel", 14)) + config2 |= ADT7316_AD_SINGLE_CH_MODE; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG2, config2); + if (ret) + return -EIO; + + chip->config2 = config2; + + return len; +} + +static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, + adt7316_show_mode, + adt7316_store_mode, + 0); + +static ssize_t adt7316_show_all_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "single_channel\nround_robin\n"); +} + +static IIO_DEVICE_ATTR(all_modes, S_IRUGO, adt7316_show_all_modes, NULL, 0); + +static ssize_t adt7316_show_ad_channel(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (!(chip->config2 & ADT7316_AD_SINGLE_CH_MODE)) + return -EPERM; + + switch (chip->config2 & ADT7516_AD_SINGLE_CH_MASK) { + case ADT7316_AD_SINGLE_CH_VDD: + return sprintf(buf, "0 - VDD\n"); + case ADT7316_AD_SINGLE_CH_IN: + return sprintf(buf, "1 - Internal Temperature\n"); + case ADT7316_AD_SINGLE_CH_EX: + if (((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) && + (chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0) + return sprintf(buf, "2 - AIN1\n"); + else + return sprintf(buf, "2 - External Temperature\n"); + case ADT7516_AD_SINGLE_CH_AIN2: + if ((chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0) + return sprintf(buf, "3 - AIN2\n"); + else + return sprintf(buf, "N/A\n"); + case ADT7516_AD_SINGLE_CH_AIN3: + if (chip->config1 & ADT7516_SEL_AIN3) + return sprintf(buf, "4 - AIN3\n"); + else + return sprintf(buf, "N/A\n"); + case ADT7516_AD_SINGLE_CH_AIN4: + return sprintf(buf, "5 - AIN4\n"); + default: + return sprintf(buf, "N/A\n"); + } +} + +static ssize_t adt7316_store_ad_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config2; + unsigned long data = 0; + int ret; + + if (!(chip->config2 & ADT7316_AD_SINGLE_CH_MODE)) + return -EPERM; + + ret = strict_strtoul(buf, 10, &data); + if (ret) + return -EINVAL; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) { + if (data > 5) + return -EINVAL; + + config2 = chip->config2 & (~ADT7516_AD_SINGLE_CH_MASK); + } else { + if (data > 2) + return -EINVAL; + + config2 = chip->config2 & (~ADT7316_AD_SINGLE_CH_MASK); + } + + + config2 |= data; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG2, config2); + if (ret) + return -EIO; + + chip->config2 = config2; + + return len; +} + +static IIO_DEVICE_ATTR(ad_channel, S_IRUGO | S_IWUSR, + adt7316_show_ad_channel, + adt7316_store_ad_channel, + 0); + +static ssize_t adt7316_show_all_ad_channels(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (!(chip->config2 & ADT7316_AD_SINGLE_CH_MODE)) + return -EPERM; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return sprintf(buf, "0 - VDD\n1 - Internal Temperature\n" + "2 - External Temperature or AIN1\n" + "3 - AIN2\n4 - AIN3\n5 - AIN4\n"); + else + return sprintf(buf, "0 - VDD\n1 - Internal Temperature\n" + "2 - External Temperature\n"); +} + +static IIO_DEVICE_ATTR(all_ad_channels, S_IRUGO, + adt7316_show_all_ad_channels, NULL, 0); + +static ssize_t adt7316_show_disable_averaging(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", + !!(chip->config2 & ADT7316_DISABLE_AVERAGING)); +} + +static ssize_t adt7316_store_disable_averaging(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config2; + int ret; + + config2 = chip->config2 & (~ADT7316_DISABLE_AVERAGING); + if (!memcmp(buf, "1", 1)) + config2 |= ADT7316_DISABLE_AVERAGING; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG2, config2); + if (ret) + return -EIO; + + chip->config2 = config2; + + return len; +} + +static IIO_DEVICE_ATTR(disable_averaging, S_IRUGO | S_IWUSR, + adt7316_show_disable_averaging, + adt7316_store_disable_averaging, + 0); + +static ssize_t adt7316_show_enable_smbus_timeout(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", + !!(chip->config2 & ADT7316_EN_SMBUS_TIMEOUT)); +} + +static ssize_t adt7316_store_enable_smbus_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config2; + int ret; + + config2 = chip->config2 & (~ADT7316_EN_SMBUS_TIMEOUT); + if (!memcmp(buf, "1", 1)) + config2 |= ADT7316_EN_SMBUS_TIMEOUT; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG2, config2); + if (ret) + return -EIO; + + chip->config2 = config2; + + return len; +} + +static IIO_DEVICE_ATTR(enable_smbus_timeout, S_IRUGO | S_IWUSR, + adt7316_show_enable_smbus_timeout, + adt7316_store_enable_smbus_timeout, + 0); + + +static ssize_t adt7316_store_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config2; + int ret; + + config2 = chip->config2 | ADT7316_RESET; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG2, config2); + if (ret) + return -EIO; + + return len; +} + +static IIO_DEVICE_ATTR(reset, S_IWUSR, + NULL, + adt7316_store_reset, + 0); + +static ssize_t adt7316_show_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", !!(chip->config1 & ADT7316_PD)); +} + +static ssize_t adt7316_store_powerdown(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config1; + int ret; + + config1 = chip->config1 & (~ADT7316_PD); + if (!memcmp(buf, "1", 1)) + config1 |= ADT7316_PD; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, config1); + if (ret) + return -EIO; + + chip->config1 = config1; + + return len; +} + +static IIO_DEVICE_ATTR(powerdown, S_IRUGO | S_IWUSR, + adt7316_show_powerdown, + adt7316_store_powerdown, + 0); + +static ssize_t adt7316_show_fast_ad_clock(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", !!(chip->config3 & ADT7316_ADCLK_22_5)); +} + +static ssize_t adt7316_store_fast_ad_clock(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + config3 = chip->config3 & (~ADT7316_ADCLK_22_5); + if (!memcmp(buf, "1", 1)) + config3 |= ADT7316_ADCLK_22_5; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(fast_ad_clock, S_IRUGO | S_IWUSR, + adt7316_show_fast_ad_clock, + adt7316_store_fast_ad_clock, + 0); + +static ssize_t adt7316_show_da_high_resolution(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (chip->config3 & ADT7316_DA_HIGH_RESOLUTION) { + if (chip->id == ID_ADT7316 || chip->id == ID_ADT7516) + return sprintf(buf, "1 (12 bits)\n"); + else if (chip->id == ID_ADT7317 || chip->id == ID_ADT7517) + return sprintf(buf, "1 (10 bits)\n"); + } + + return sprintf(buf, "0 (8 bits)\n"); +} + +static ssize_t adt7316_store_da_high_resolution(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + chip->dac_bits = 8; + + if (!memcmp(buf, "1", 1)) { + config3 = chip->config3 | ADT7316_DA_HIGH_RESOLUTION; + if (chip->id == ID_ADT7316 || chip->id == ID_ADT7516) + chip->dac_bits = 12; + else if (chip->id == ID_ADT7317 || chip->id == ID_ADT7517) + chip->dac_bits = 10; + } else + config3 = chip->config3 & (~ADT7316_DA_HIGH_RESOLUTION); + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(da_high_resolution, S_IRUGO | S_IWUSR, + adt7316_show_da_high_resolution, + adt7316_store_da_high_resolution, + 0); + +static ssize_t adt7316_show_AIN_internal_Vref(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + return -EPERM; + + return sprintf(buf, "%d\n", + !!(chip->config3 & ADT7516_AIN_IN_VREF)); +} + +static ssize_t adt7316_store_AIN_internal_Vref(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + return -EPERM; + + if (memcmp(buf, "1", 1)) + config3 = chip->config3 & (~ADT7516_AIN_IN_VREF); + else + config3 = chip->config3 | ADT7516_AIN_IN_VREF; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(AIN_internal_Vref, S_IRUGO | S_IWUSR, + adt7316_show_AIN_internal_Vref, + adt7316_store_AIN_internal_Vref, + 0); + + +static ssize_t adt7316_show_enable_prop_DACA(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", + !!(chip->config3 & ADT7316_EN_IN_TEMP_PROP_DACA)); +} + +static ssize_t adt7316_store_enable_prop_DACA(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + config3 = chip->config3 & (~ADT7316_EN_IN_TEMP_PROP_DACA); + if (!memcmp(buf, "1", 1)) + config3 |= ADT7316_EN_IN_TEMP_PROP_DACA; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(enable_proportion_DACA, S_IRUGO | S_IWUSR, + adt7316_show_enable_prop_DACA, + adt7316_store_enable_prop_DACA, + 0); + +static ssize_t adt7316_show_enable_prop_DACB(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", + !!(chip->config3 & ADT7316_EN_EX_TEMP_PROP_DACB)); +} + +static ssize_t adt7316_store_enable_prop_DACB(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config3; + int ret; + + config3 = chip->config3 & (~ADT7316_EN_EX_TEMP_PROP_DACB); + if (!memcmp(buf, "1", 1)) + config3 |= ADT7316_EN_EX_TEMP_PROP_DACB; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, config3); + if (ret) + return -EIO; + + chip->config3 = config3; + + return len; +} + +static IIO_DEVICE_ATTR(enable_proportion_DACB, S_IRUGO | S_IWUSR, + adt7316_show_enable_prop_DACB, + adt7316_store_enable_prop_DACB, + 0); + +static ssize_t adt7316_show_DAC_2Vref_ch_mask(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "0x%x\n", + chip->dac_config & ADT7316_DA_2VREF_CH_MASK); +} + +static ssize_t adt7316_store_DAC_2Vref_ch_mask(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 dac_config; + unsigned long data = 0; + int ret; + + ret = strict_strtoul(buf, 16, &data); + if (ret || data > ADT7316_DA_2VREF_CH_MASK) + return -EINVAL; + + dac_config = chip->dac_config & (~ADT7316_DA_2VREF_CH_MASK); + dac_config |= data; + + ret = chip->bus.write(chip->bus.client, ADT7316_DAC_CONFIG, dac_config); + if (ret) + return -EIO; + + chip->dac_config = dac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DAC_2Vref_channels_mask, S_IRUGO | S_IWUSR, + adt7316_show_DAC_2Vref_ch_mask, + adt7316_store_DAC_2Vref_ch_mask, + 0); + +static ssize_t adt7316_show_DAC_update_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (!(chip->config3 & ADT7316_DA_EN_VIA_DAC_LDCA)) + return sprintf(buf, "manual\n"); + else { + switch (chip->dac_config & ADT7316_DA_EN_MODE_MASK) { + case ADT7316_DA_EN_MODE_SINGLE: + return sprintf(buf, "0 - auto at any MSB DAC writing\n"); + case ADT7316_DA_EN_MODE_AB_CD: + return sprintf(buf, "1 - auto at MSB DAC AB and CD writing\n"); + case ADT7316_DA_EN_MODE_ABCD: + return sprintf(buf, "2 - auto at MSB DAC ABCD writing\n"); + default: /* ADT7316_DA_EN_MODE_LDAC */ + return sprintf(buf, "3 - manual\n"); + } + } +} + +static ssize_t adt7316_store_DAC_update_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 dac_config; + unsigned long data; + int ret; + + if (!(chip->config3 & ADT7316_DA_EN_VIA_DAC_LDCA)) + return -EPERM; + + ret = strict_strtoul(buf, 10, &data); + if (ret || data > ADT7316_DA_EN_MODE_MASK) + return -EINVAL; + + dac_config = chip->dac_config & (~ADT7316_DA_EN_MODE_MASK); + dac_config |= data; + + ret = chip->bus.write(chip->bus.client, ADT7316_DAC_CONFIG, dac_config); + if (ret) + return -EIO; + + chip->dac_config = dac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DAC_update_mode, S_IRUGO | S_IWUSR, + adt7316_show_DAC_update_mode, + adt7316_store_DAC_update_mode, + 0); + +static ssize_t adt7316_show_all_DAC_update_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if (chip->config3 & ADT7316_DA_EN_VIA_DAC_LDCA) + return sprintf(buf, "0 - auto at any MSB DAC writing\n" + "1 - auto at MSB DAC AB and CD writing\n" + "2 - auto at MSB DAC ABCD writing\n" + "3 - manual\n"); + else + return sprintf(buf, "manual\n"); +} + +static IIO_DEVICE_ATTR(all_DAC_update_modes, S_IRUGO, + adt7316_show_all_DAC_update_modes, NULL, 0); + + +static ssize_t adt7316_store_update_DAC(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 ldac_config; + unsigned long data; + int ret; + + if (chip->config3 & ADT7316_DA_EN_VIA_DAC_LDCA) { + if ((chip->dac_config & ADT7316_DA_EN_MODE_MASK) != + ADT7316_DA_EN_MODE_LDAC) + return -EPERM; + + ret = strict_strtoul(buf, 16, &data); + if (ret || data > ADT7316_LDAC_EN_DA_MASK) + return -EINVAL; + + ldac_config = chip->ldac_config & (~ADT7316_LDAC_EN_DA_MASK); + ldac_config |= data; + + ret = chip->bus.write(chip->bus.client, ADT7316_LDAC_CONFIG, + ldac_config); + if (ret) + return -EIO; + } else { + gpio_set_value(chip->ldac_pin, 0); + gpio_set_value(chip->ldac_pin, 1); + } + + return len; +} + +static IIO_DEVICE_ATTR(update_DAC, S_IRUGO | S_IWUSR, + NULL, + adt7316_store_update_DAC, + 0); + +static ssize_t adt7316_show_DA_AB_Vref_bypass(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return -EPERM; + + return sprintf(buf, "%d\n", + !!(chip->dac_config & ADT7316_VREF_BYPASS_DAC_AB)); +} + +static ssize_t adt7316_store_DA_AB_Vref_bypass(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 dac_config; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return -EPERM; + + dac_config = chip->dac_config & (~ADT7316_VREF_BYPASS_DAC_AB); + if (!memcmp(buf, "1", 1)) + dac_config |= ADT7316_VREF_BYPASS_DAC_AB; + + ret = chip->bus.write(chip->bus.client, ADT7316_DAC_CONFIG, dac_config); + if (ret) + return -EIO; + + chip->dac_config = dac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DA_AB_Vref_bypass, S_IRUGO | S_IWUSR, + adt7316_show_DA_AB_Vref_bypass, + adt7316_store_DA_AB_Vref_bypass, + 0); + +static ssize_t adt7316_show_DA_CD_Vref_bypass(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return -EPERM; + + return sprintf(buf, "%d\n", + !!(chip->dac_config & ADT7316_VREF_BYPASS_DAC_CD)); +} + +static ssize_t adt7316_store_DA_CD_Vref_bypass(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 dac_config; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return -EPERM; + + dac_config = chip->dac_config & (~ADT7316_VREF_BYPASS_DAC_CD); + if (!memcmp(buf, "1", 1)) + dac_config |= ADT7316_VREF_BYPASS_DAC_CD; + + ret = chip->bus.write(chip->bus.client, ADT7316_DAC_CONFIG, dac_config); + if (ret) + return -EIO; + + chip->dac_config = dac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DA_CD_Vref_bypass, S_IRUGO | S_IWUSR, + adt7316_show_DA_CD_Vref_bypass, + adt7316_store_DA_CD_Vref_bypass, + 0); + +static ssize_t adt7316_show_DAC_internal_Vref(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return sprintf(buf, "0x%x\n", + (chip->dac_config & ADT7516_DAC_IN_VREF_MASK) >> + ADT7516_DAC_IN_VREF_OFFSET); + else + return sprintf(buf, "%d\n", + !!(chip->dac_config & ADT7316_DAC_IN_VREF)); +} + +static ssize_t adt7316_store_DAC_internal_Vref(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 ldac_config; + unsigned long data; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) { + ret = strict_strtoul(buf, 16, &data); + if (ret || data > 3) + return -EINVAL; + + ldac_config = chip->ldac_config & (~ADT7516_DAC_IN_VREF_MASK); + if (data & 0x1) + ldac_config |= ADT7516_DAC_AB_IN_VREF; + else if (data & 0x2) + ldac_config |= ADT7516_DAC_CD_IN_VREF; + } else { + ret = strict_strtoul(buf, 16, &data); + if (ret) + return -EINVAL; + + ldac_config = chip->ldac_config & (~ADT7316_DAC_IN_VREF); + if (data) + ldac_config = chip->ldac_config | ADT7316_DAC_IN_VREF; + } + + ret = chip->bus.write(chip->bus.client, ADT7316_LDAC_CONFIG, ldac_config); + if (ret) + return -EIO; + + chip->ldac_config = ldac_config; + + return len; +} + +static IIO_DEVICE_ATTR(DAC_internal_Vref, S_IRUGO | S_IWUSR, + adt7316_show_DAC_internal_Vref, + adt7316_store_DAC_internal_Vref, + 0); + +static ssize_t adt7316_show_ad(struct adt7316_chip_info *chip, + int channel, char *buf) +{ + u16 data; + u8 msb, lsb; + char sign = ' '; + int ret; + + if ((chip->config2 & ADT7316_AD_SINGLE_CH_MODE) && + channel != (chip->config2 & ADT7516_AD_SINGLE_CH_MASK)) + return -EPERM; + + switch (channel) { + case ADT7316_AD_SINGLE_CH_IN: + ret = chip->bus.read(chip->bus.client, + ADT7316_LSB_IN_TEMP_VDD, &lsb); + if (ret) + return -EIO; + + ret = chip->bus.read(chip->bus.client, + ADT7316_AD_MSB_DATA_BASE + channel, &msb); + if (ret) + return -EIO; + + data = msb << ADT7316_T_VALUE_FLOAT_OFFSET; + data |= lsb & ADT7316_LSB_IN_TEMP_MASK; + break; + case ADT7316_AD_SINGLE_CH_VDD: + ret = chip->bus.read(chip->bus.client, + ADT7316_LSB_IN_TEMP_VDD, &lsb); + if (ret) + return -EIO; + + ret = chip->bus.read(chip->bus.client, + + ADT7316_AD_MSB_DATA_BASE + channel, &msb); + if (ret) + return -EIO; + + data = msb << ADT7316_T_VALUE_FLOAT_OFFSET; + data |= (lsb & ADT7316_LSB_VDD_MASK) >> ADT7316_LSB_VDD_OFFSET; + return sprintf(buf, "%d\n", data); + default: /* ex_temp and ain */ + ret = chip->bus.read(chip->bus.client, + ADT7316_LSB_EX_TEMP_AIN, &lsb); + if (ret) + return -EIO; + + ret = chip->bus.read(chip->bus.client, + ADT7316_AD_MSB_DATA_BASE + channel, &msb); + if (ret) + return -EIO; + + data = msb << ADT7316_T_VALUE_FLOAT_OFFSET; + data |= lsb & (ADT7316_LSB_EX_TEMP_MASK << + (ADT7516_LSB_AIN_SHIFT * (channel - + (ADT7316_MSB_EX_TEMP - ADT7316_AD_MSB_DATA_BASE)))); + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + return sprintf(buf, "%d\n", data); + else + break; + } + + if (data & ADT7316_T_VALUE_SIGN) { + /* convert supplement to positive value */ + data = (ADT7316_T_VALUE_SIGN << 1) - data; + sign = '-'; + } + + return sprintf(buf, "%c%d.%.2d\n", sign, + (data >> ADT7316_T_VALUE_FLOAT_OFFSET), + (data & ADT7316_T_VALUE_FLOAT_MASK) * 25); +} + +static ssize_t adt7316_show_VDD(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7316_AD_SINGLE_CH_VDD, buf); +} +static IIO_DEVICE_ATTR(VDD, S_IRUGO, adt7316_show_VDD, NULL, 0); + +static ssize_t adt7316_show_in_temp(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7316_AD_SINGLE_CH_IN, buf); +} + +static IIO_DEVICE_ATTR(in_temp, S_IRUGO, adt7316_show_in_temp, NULL, 0); + +static ssize_t adt7316_show_ex_temp_AIN1(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7316_AD_SINGLE_CH_EX, buf); +} + +static IIO_DEVICE_ATTR(ex_temp_AIN1, S_IRUGO, adt7316_show_ex_temp_AIN1, NULL, 0); +static IIO_DEVICE_ATTR(ex_temp, S_IRUGO, adt7316_show_ex_temp_AIN1, NULL, 0); + +static ssize_t adt7316_show_AIN2(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7516_AD_SINGLE_CH_AIN2, buf); +} +static IIO_DEVICE_ATTR(AIN2, S_IRUGO, adt7316_show_AIN2, NULL, 0); + +static ssize_t adt7316_show_AIN3(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7516_AD_SINGLE_CH_AIN3, buf); +} +static IIO_DEVICE_ATTR(AIN3, S_IRUGO, adt7316_show_AIN3, NULL, 0); + +static ssize_t adt7316_show_AIN4(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_ad(chip, ADT7516_AD_SINGLE_CH_AIN4, buf); +} +static IIO_DEVICE_ATTR(AIN4, S_IRUGO, adt7316_show_AIN4, NULL, 0); + +static ssize_t adt7316_show_temp_offset(struct adt7316_chip_info *chip, + int offset_addr, char *buf) +{ + int data; + u8 val; + int ret; + + ret = chip->bus.read(chip->bus.client, offset_addr, &val); + if (ret) + return -EIO; + + data = (int)val; + if (val & 0x80) + data -= 256; + + return sprintf(buf, "%d\n", data); +} + +static ssize_t adt7316_store_temp_offset(struct adt7316_chip_info *chip, + int offset_addr, const char *buf, size_t len) +{ + long data; + u8 val; + int ret; + + ret = strict_strtol(buf, 10, &data); + if (ret || data > 127 || data < -128) + return -EINVAL; + + if (data < 0) + data += 256; + + val = (u8)data; + + ret = chip->bus.write(chip->bus.client, offset_addr, val); + if (ret) + return -EIO; + + return len; +} + +static ssize_t adt7316_show_in_temp_offset(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_temp_offset(chip, ADT7316_IN_TEMP_OFFSET, buf); +} + +static ssize_t adt7316_store_in_temp_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_temp_offset(chip, ADT7316_IN_TEMP_OFFSET, buf, len); +} + +static IIO_DEVICE_ATTR(in_temp_offset, S_IRUGO | S_IWUSR, + adt7316_show_in_temp_offset, + adt7316_store_in_temp_offset, 0); + +static ssize_t adt7316_show_ex_temp_offset(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_temp_offset(chip, ADT7316_EX_TEMP_OFFSET, buf); +} + +static ssize_t adt7316_store_ex_temp_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_temp_offset(chip, ADT7316_EX_TEMP_OFFSET, buf, len); +} + +static IIO_DEVICE_ATTR(ex_temp_offset, S_IRUGO | S_IWUSR, + adt7316_show_ex_temp_offset, + adt7316_store_ex_temp_offset, 0); + +static ssize_t adt7316_show_in_analog_temp_offset(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_temp_offset(chip, + ADT7316_IN_ANALOG_TEMP_OFFSET, buf); +} + +static ssize_t adt7316_store_in_analog_temp_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_temp_offset(chip, + ADT7316_IN_ANALOG_TEMP_OFFSET, buf, len); +} + +static IIO_DEVICE_ATTR(in_analog_temp_offset, S_IRUGO | S_IWUSR, + adt7316_show_in_analog_temp_offset, + adt7316_store_in_analog_temp_offset, 0); + +static ssize_t adt7316_show_ex_analog_temp_offset(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_temp_offset(chip, + ADT7316_EX_ANALOG_TEMP_OFFSET, buf); +} + +static ssize_t adt7316_store_ex_analog_temp_offset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_temp_offset(chip, + ADT7316_EX_ANALOG_TEMP_OFFSET, buf, len); +} + +static IIO_DEVICE_ATTR(ex_analog_temp_offset, S_IRUGO | S_IWUSR, + adt7316_show_ex_analog_temp_offset, + adt7316_store_ex_analog_temp_offset, 0); + +static ssize_t adt7316_show_DAC(struct adt7316_chip_info *chip, + int channel, char *buf) +{ + u16 data; + u8 msb, lsb, offset; + int ret; + + if (channel >= ADT7316_DA_MSB_DATA_REGS || + (channel == 0 && + (chip->config3 & ADT7316_EN_IN_TEMP_PROP_DACA)) || + (channel == 1 && + (chip->config3 & ADT7316_EN_EX_TEMP_PROP_DACB))) + return -EPERM; + + offset = chip->dac_bits - 8; + + if (chip->dac_bits > 8) { + ret = chip->bus.read(chip->bus.client, + ADT7316_DA_DATA_BASE + channel * 2, &lsb); + if (ret) + return -EIO; + } + + ret = chip->bus.read(chip->bus.client, + ADT7316_DA_DATA_BASE + 1 + channel * 2, &msb); + if (ret) + return -EIO; + + data = (msb << offset) + (lsb & ((1 << offset) - 1)); + + return sprintf(buf, "%d\n", data); +} + +static ssize_t adt7316_store_DAC(struct adt7316_chip_info *chip, + int channel, const char *buf, size_t len) +{ + u8 msb, lsb, offset; + unsigned long data; + int ret; + + if (channel >= ADT7316_DA_MSB_DATA_REGS || + (channel == 0 && + (chip->config3 & ADT7316_EN_IN_TEMP_PROP_DACA)) || + (channel == 1 && + (chip->config3 & ADT7316_EN_EX_TEMP_PROP_DACB))) + return -EPERM; + + offset = chip->dac_bits - 8; + + ret = strict_strtoul(buf, 10, &data); + if (ret || data >= (1 << chip->dac_bits)) + return -EINVAL; + + if (chip->dac_bits > 8) { + lsb = data & (1 << offset); + ret = chip->bus.write(chip->bus.client, + ADT7316_DA_DATA_BASE + channel * 2, lsb); + if (ret) + return -EIO; + } + + msb = data >> offset; + ret = chip->bus.write(chip->bus.client, + ADT7316_DA_DATA_BASE + 1 + channel * 2, msb); + if (ret) + return -EIO; + + return len; +} + +static ssize_t adt7316_show_DAC_A(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_DAC(chip, 0, buf); +} + +static ssize_t adt7316_store_DAC_A(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_DAC(chip, 0, buf, len); +} + +static IIO_DEVICE_ATTR(DAC_A, S_IRUGO | S_IWUSR, adt7316_show_DAC_A, + adt7316_store_DAC_A, 0); + +static ssize_t adt7316_show_DAC_B(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_DAC(chip, 1, buf); +} + +static ssize_t adt7316_store_DAC_B(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_DAC(chip, 1, buf, len); +} + +static IIO_DEVICE_ATTR(DAC_B, S_IRUGO | S_IWUSR, adt7316_show_DAC_B, + adt7316_store_DAC_B, 0); + +static ssize_t adt7316_show_DAC_C(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_DAC(chip, 2, buf); +} + +static ssize_t adt7316_store_DAC_C(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_DAC(chip, 2, buf, len); +} + +static IIO_DEVICE_ATTR(DAC_C, S_IRUGO | S_IWUSR, adt7316_show_DAC_C, + adt7316_store_DAC_C, 0); + +static ssize_t adt7316_show_DAC_D(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_show_DAC(chip, 3, buf); +} + +static ssize_t adt7316_store_DAC_D(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return adt7316_store_DAC(chip, 3, buf, len); +} + +static IIO_DEVICE_ATTR(DAC_D, S_IRUGO | S_IWUSR, adt7316_show_DAC_D, + adt7316_store_DAC_D, 0); + +static ssize_t adt7316_show_device_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 id; + int ret; + + ret = chip->bus.read(chip->bus.client, ADT7316_DEVICE_ID, &id); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", id); +} + +static IIO_DEVICE_ATTR(device_id, S_IRUGO, adt7316_show_device_id, NULL, 0); + +static ssize_t adt7316_show_manufactorer_id(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 id; + int ret; + + ret = chip->bus.read(chip->bus.client, ADT7316_MANUFACTURE_ID, &id); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", id); +} + +static IIO_DEVICE_ATTR(manufactorer_id, S_IRUGO, + adt7316_show_manufactorer_id, NULL, 0); + +static ssize_t adt7316_show_device_rev(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 rev; + int ret; + + ret = chip->bus.read(chip->bus.client, ADT7316_DEVICE_REV, &rev); + if (ret) + return -EIO; + + return sprintf(buf, "%d\n", rev); +} + +static IIO_DEVICE_ATTR(device_rev, S_IRUGO, adt7316_show_device_rev, NULL, 0); + +static ssize_t adt7316_show_bus_type(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 stat; + int ret; + + ret = chip->bus.read(chip->bus.client, ADT7316_SPI_LOCK_STAT, &stat); + if (ret) + return -EIO; + + if (stat) + return sprintf(buf, "spi\n"); + else + return sprintf(buf, "i2c\n"); +} + +static IIO_DEVICE_ATTR(bus_type, S_IRUGO, adt7316_show_bus_type, NULL, 0); + +static struct attribute *adt7316_attributes[] = { + &iio_dev_attr_all_modes.dev_attr.attr, + &iio_dev_attr_mode.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + &iio_dev_attr_enabled.dev_attr.attr, + &iio_dev_attr_ad_channel.dev_attr.attr, + &iio_dev_attr_all_ad_channels.dev_attr.attr, + &iio_dev_attr_disable_averaging.dev_attr.attr, + &iio_dev_attr_enable_smbus_timeout.dev_attr.attr, + &iio_dev_attr_powerdown.dev_attr.attr, + &iio_dev_attr_fast_ad_clock.dev_attr.attr, + &iio_dev_attr_da_high_resolution.dev_attr.attr, + &iio_dev_attr_enable_proportion_DACA.dev_attr.attr, + &iio_dev_attr_enable_proportion_DACB.dev_attr.attr, + &iio_dev_attr_DAC_2Vref_channels_mask.dev_attr.attr, + &iio_dev_attr_DAC_update_mode.dev_attr.attr, + &iio_dev_attr_all_DAC_update_modes.dev_attr.attr, + &iio_dev_attr_update_DAC.dev_attr.attr, + &iio_dev_attr_DA_AB_Vref_bypass.dev_attr.attr, + &iio_dev_attr_DA_CD_Vref_bypass.dev_attr.attr, + &iio_dev_attr_DAC_internal_Vref.dev_attr.attr, + &iio_dev_attr_VDD.dev_attr.attr, + &iio_dev_attr_in_temp.dev_attr.attr, + &iio_dev_attr_ex_temp.dev_attr.attr, + &iio_dev_attr_in_temp_offset.dev_attr.attr, + &iio_dev_attr_ex_temp_offset.dev_attr.attr, + &iio_dev_attr_in_analog_temp_offset.dev_attr.attr, + &iio_dev_attr_ex_analog_temp_offset.dev_attr.attr, + &iio_dev_attr_DAC_A.dev_attr.attr, + &iio_dev_attr_DAC_B.dev_attr.attr, + &iio_dev_attr_DAC_C.dev_attr.attr, + &iio_dev_attr_DAC_D.dev_attr.attr, + &iio_dev_attr_device_id.dev_attr.attr, + &iio_dev_attr_manufactorer_id.dev_attr.attr, + &iio_dev_attr_device_rev.dev_attr.attr, + &iio_dev_attr_bus_type.dev_attr.attr, + NULL, +}; + +static const struct attribute_group adt7316_attribute_group = { + .attrs = adt7316_attributes, +}; + +static struct attribute *adt7516_attributes[] = { + &iio_dev_attr_all_modes.dev_attr.attr, + &iio_dev_attr_mode.dev_attr.attr, + &iio_dev_attr_select_ex_temp.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + &iio_dev_attr_enabled.dev_attr.attr, + &iio_dev_attr_ad_channel.dev_attr.attr, + &iio_dev_attr_all_ad_channels.dev_attr.attr, + &iio_dev_attr_disable_averaging.dev_attr.attr, + &iio_dev_attr_enable_smbus_timeout.dev_attr.attr, + &iio_dev_attr_powerdown.dev_attr.attr, + &iio_dev_attr_fast_ad_clock.dev_attr.attr, + &iio_dev_attr_AIN_internal_Vref.dev_attr.attr, + &iio_dev_attr_da_high_resolution.dev_attr.attr, + &iio_dev_attr_enable_proportion_DACA.dev_attr.attr, + &iio_dev_attr_enable_proportion_DACB.dev_attr.attr, + &iio_dev_attr_DAC_2Vref_channels_mask.dev_attr.attr, + &iio_dev_attr_DAC_update_mode.dev_attr.attr, + &iio_dev_attr_all_DAC_update_modes.dev_attr.attr, + &iio_dev_attr_update_DAC.dev_attr.attr, + &iio_dev_attr_DA_AB_Vref_bypass.dev_attr.attr, + &iio_dev_attr_DA_CD_Vref_bypass.dev_attr.attr, + &iio_dev_attr_DAC_internal_Vref.dev_attr.attr, + &iio_dev_attr_VDD.dev_attr.attr, + &iio_dev_attr_in_temp.dev_attr.attr, + &iio_dev_attr_ex_temp_AIN1.dev_attr.attr, + &iio_dev_attr_AIN2.dev_attr.attr, + &iio_dev_attr_AIN3.dev_attr.attr, + &iio_dev_attr_AIN4.dev_attr.attr, + &iio_dev_attr_in_temp_offset.dev_attr.attr, + &iio_dev_attr_ex_temp_offset.dev_attr.attr, + &iio_dev_attr_in_analog_temp_offset.dev_attr.attr, + &iio_dev_attr_ex_analog_temp_offset.dev_attr.attr, + &iio_dev_attr_DAC_A.dev_attr.attr, + &iio_dev_attr_DAC_B.dev_attr.attr, + &iio_dev_attr_DAC_C.dev_attr.attr, + &iio_dev_attr_DAC_D.dev_attr.attr, + &iio_dev_attr_device_id.dev_attr.attr, + &iio_dev_attr_manufactorer_id.dev_attr.attr, + &iio_dev_attr_device_rev.dev_attr.attr, + &iio_dev_attr_bus_type.dev_attr.attr, + NULL, +}; + +static const struct attribute_group adt7516_attribute_group = { + .attrs = adt7516_attributes, +}; + +static irqreturn_t adt7316_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct adt7316_chip_info *chip = iio_priv(indio_dev); + u8 stat1, stat2; + int ret; + s64 time; + + ret = chip->bus.read(chip->bus.client, ADT7316_INT_STAT1, &stat1); + if (!ret) { + if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) + stat1 &= 0x1F; + + time = iio_get_time_ns(); + if (stat1 & (1 << 0)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + time); + if (stat1 & (1 << 1)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + time); + if (stat1 & (1 << 2)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + time); + if (stat1 & (1 << 3)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + time); + if (stat1 & (1 << 5)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + time); + if (stat1 & (1 << 6)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 2, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + time); + if (stat1 & (1 << 7)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, 3, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + time); + } + ret = chip->bus.read(chip->bus.client, ADT7316_INT_STAT2, &stat2); + if (!ret) { + if (stat2 & ADT7316_INT_MASK2_VDD) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_VOLTAGE, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + } + + return IRQ_HANDLED; +} + +/* + * Show mask of enabled interrupts in Hex. + */ +static ssize_t adt7316_show_int_mask(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "0x%x\n", chip->int_mask); +} + +/* + * Set 1 to the mask in Hex to enabled interrupts. + */ +static ssize_t adt7316_set_int_mask(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + unsigned long data; + int ret; + u8 mask; + + ret = strict_strtoul(buf, 16, &data); + if (ret || data >= ADT7316_VDD_INT_MASK + 1) + return -EINVAL; + + if (data & ADT7316_VDD_INT_MASK) + mask = 0; /* enable vdd int */ + else + mask = ADT7316_INT_MASK2_VDD; /* disable vdd int */ + + ret = chip->bus.write(chip->bus.client, ADT7316_INT_MASK2, mask); + if (!ret) { + chip->int_mask &= ~ADT7316_VDD_INT_MASK; + chip->int_mask |= data & ADT7316_VDD_INT_MASK; + } + + if (data & ADT7316_TEMP_AIN_INT_MASK) { + if ((chip->id & ID_FAMILY_MASK) == ID_ADT73XX) + /* mask in reg is opposite, set 1 to disable */ + mask = (~data) & ADT7316_TEMP_INT_MASK; + else + /* mask in reg is opposite, set 1 to disable */ + mask = (~data) & ADT7316_TEMP_AIN_INT_MASK; + } + ret = chip->bus.write(chip->bus.client, ADT7316_INT_MASK1, mask); + + chip->int_mask = mask; + + return len; +} +static inline ssize_t adt7316_show_ad_bound(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 val; + int data; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT73XX && + this_attr->address > ADT7316_EX_TEMP_LOW) + return -EPERM; + + ret = chip->bus.read(chip->bus.client, this_attr->address, &val); + if (ret) + return -EIO; + + data = (int)val; + + if (!((chip->id & ID_FAMILY_MASK) == ID_ADT75XX && + (chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0)) { + if (data & 0x80) + data -= 256; + } + + return sprintf(buf, "%d\n", data); +} + +static inline ssize_t adt7316_set_ad_bound(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + long data; + u8 val; + int ret; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT73XX && + this_attr->address > ADT7316_EX_TEMP_LOW) + return -EPERM; + + ret = strict_strtol(buf, 10, &data); + if (ret) + return -EINVAL; + + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX && + (chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0) { + if (data > 255 || data < 0) + return -EINVAL; + } else { + if (data > 127 || data < -128) + return -EINVAL; + + if (data < 0) + data += 256; + } + + val = (u8)data; + + ret = chip->bus.write(chip->bus.client, this_attr->address, val); + if (ret) + return -EIO; + + return len; +} + +static ssize_t adt7316_show_int_enabled(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return sprintf(buf, "%d\n", !!(chip->config1 & ADT7316_INT_EN)); +} + +static ssize_t adt7316_set_int_enabled(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + u8 config1; + int ret; + + config1 = chip->config1 & (~ADT7316_INT_EN); + if (!memcmp(buf, "1", 1)) + config1 |= ADT7316_INT_EN; + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, config1); + if (ret) + return -EIO; + + chip->config1 = config1; + + return len; +} + +static IIO_DEVICE_ATTR(int_mask, + S_IRUGO | S_IWUSR, + adt7316_show_int_mask, adt7316_set_int_mask, + 0); +static IIO_DEVICE_ATTR(in_temp_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_IN_TEMP_HIGH); +static IIO_DEVICE_ATTR(in_temp_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_IN_TEMP_LOW); +static IIO_DEVICE_ATTR(ex_temp_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_EX_TEMP_HIGH); +static IIO_DEVICE_ATTR(ex_temp_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_EX_TEMP_LOW); + +/* NASTY duplication to be fixed */ +static IIO_DEVICE_ATTR(ex_temp_ain1_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_EX_TEMP_HIGH); +static IIO_DEVICE_ATTR(ex_temp_ain1_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7316_EX_TEMP_LOW); +static IIO_DEVICE_ATTR(ain2_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN2_HIGH); +static IIO_DEVICE_ATTR(ain2_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN2_LOW); +static IIO_DEVICE_ATTR(ain3_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN3_HIGH); +static IIO_DEVICE_ATTR(ain3_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN3_LOW); +static IIO_DEVICE_ATTR(ain4_high_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN4_HIGH); +static IIO_DEVICE_ATTR(ain4_low_value, + S_IRUGO | S_IWUSR, + adt7316_show_ad_bound, adt7316_set_ad_bound, + ADT7516_AIN4_LOW); +static IIO_DEVICE_ATTR(int_enabled, + S_IRUGO | S_IWUSR, + adt7316_show_int_enabled, + adt7316_set_int_enabled, 0); + +static struct attribute *adt7316_event_attributes[] = { + &iio_dev_attr_int_mask.dev_attr.attr, + &iio_dev_attr_in_temp_high_value.dev_attr.attr, + &iio_dev_attr_in_temp_low_value.dev_attr.attr, + &iio_dev_attr_ex_temp_high_value.dev_attr.attr, + &iio_dev_attr_ex_temp_low_value.dev_attr.attr, + &iio_dev_attr_int_enabled.dev_attr.attr, + NULL, +}; + +static struct attribute_group adt7316_event_attribute_group = { + .attrs = adt7316_event_attributes, + .name = "events", +}; + +static struct attribute *adt7516_event_attributes[] = { + &iio_dev_attr_int_mask.dev_attr.attr, + &iio_dev_attr_in_temp_high_value.dev_attr.attr, + &iio_dev_attr_in_temp_low_value.dev_attr.attr, + &iio_dev_attr_ex_temp_ain1_high_value.dev_attr.attr, + &iio_dev_attr_ex_temp_ain1_low_value.dev_attr.attr, + &iio_dev_attr_ain2_high_value.dev_attr.attr, + &iio_dev_attr_ain2_low_value.dev_attr.attr, + &iio_dev_attr_ain3_high_value.dev_attr.attr, + &iio_dev_attr_ain3_low_value.dev_attr.attr, + &iio_dev_attr_ain4_high_value.dev_attr.attr, + &iio_dev_attr_ain4_low_value.dev_attr.attr, + &iio_dev_attr_int_enabled.dev_attr.attr, + NULL, +}; + +static struct attribute_group adt7516_event_attribute_group = { + .attrs = adt7516_event_attributes, + .name = "events", +}; + +#ifdef CONFIG_PM_SLEEP +static int adt7316_disable(struct device *dev) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return _adt7316_store_enabled(chip, 0); +} + +static int adt7316_enable(struct device *dev) +{ + struct iio_dev *dev_info = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(dev_info); + + return _adt7316_store_enabled(chip, 1); +} + +SIMPLE_DEV_PM_OPS(adt7316_pm_ops, adt7316_disable, adt7316_enable); +EXPORT_SYMBOL_GPL(adt7316_pm_ops); +#endif + +static const struct iio_info adt7316_info = { + .attrs = &adt7316_attribute_group, + .event_attrs = &adt7316_event_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info adt7516_info = { + .attrs = &adt7516_attribute_group, + .event_attrs = &adt7516_event_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* + * device probe and remove + */ +int __devinit adt7316_probe(struct device *dev, struct adt7316_bus *bus, + const char *name) +{ + struct adt7316_chip_info *chip; + struct iio_dev *indio_dev; + unsigned short *adt7316_platform_data = dev->platform_data; + int ret = 0; + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + dev_set_drvdata(dev, indio_dev); + + chip->bus = *bus; + + if (name[4] == '3') + chip->id = ID_ADT7316 + (name[6] - '6'); + else if (name[4] == '5') + chip->id = ID_ADT7516 + (name[6] - '6'); + else + return -ENODEV; + + chip->ldac_pin = adt7316_platform_data[1]; + if (chip->ldac_pin) { + chip->config3 |= ADT7316_DA_EN_VIA_DAC_LDCA; + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + chip->config1 |= ADT7516_SEL_AIN3; + } + chip->int_mask = ADT7316_TEMP_INT_MASK | ADT7316_VDD_INT_MASK; + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + chip->int_mask |= ADT7516_AIN_INT_MASK; + + indio_dev->dev.parent = dev; + if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) + indio_dev->info = &adt7516_info; + else + indio_dev->info = &adt7316_info; + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (chip->bus.irq > 0) { + if (adt7316_platform_data[0]) + chip->bus.irq_flags = adt7316_platform_data[0]; + + ret = request_threaded_irq(chip->bus.irq, + NULL, + &adt7316_event_handler, + chip->bus.irq_flags | IRQF_ONESHOT, + indio_dev->name, + indio_dev); + if (ret) + goto error_free_dev; + + if (chip->bus.irq_flags & IRQF_TRIGGER_HIGH) + chip->config1 |= ADT7316_INT_POLARITY; + } + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG1, chip->config1); + if (ret) { + ret = -EIO; + goto error_unreg_irq; + } + + ret = chip->bus.write(chip->bus.client, ADT7316_CONFIG3, chip->config3); + if (ret) { + ret = -EIO; + goto error_unreg_irq; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_unreg_irq; + + dev_info(dev, "%s temperature sensor, ADC and DAC registered.\n", + indio_dev->name); + + return 0; + +error_unreg_irq: + free_irq(chip->bus.irq, indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} +EXPORT_SYMBOL(adt7316_probe); + +int __devexit adt7316_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adt7316_chip_info *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (chip->bus.irq) + free_irq(chip->bus.irq, indio_dev); + iio_free_device(indio_dev); + + return 0; +} +EXPORT_SYMBOL(adt7316_remove); + +MODULE_AUTHOR("Sonic Zhang "); +MODULE_DESCRIPTION("Analog Devices ADT7316/7/8 and ADT7516/7/9 digital" + " temperature sensor, ADC and DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/addac/adt7316.h b/drivers/staging/iio/addac/adt7316.h new file mode 100644 index 00000000..4d3efff4 --- /dev/null +++ b/drivers/staging/iio/addac/adt7316.h @@ -0,0 +1,36 @@ +/* + * ADT7316 digital temperature sensor driver supporting ADT7316/7/8 ADT7516/7/9 + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _ADT7316_H_ +#define _ADT7316_H_ + +#include +#include + +#define ADT7316_REG_MAX_ADDR 0x3F + +struct adt7316_bus { + void *client; + int irq; + int irq_flags; + int (*read) (void *client, u8 reg, u8 *data); + int (*write) (void *client, u8 reg, u8 val); + int (*multi_read) (void *client, u8 first_reg, u8 count, u8 *data); + int (*multi_write) (void *client, u8 first_reg, u8 count, u8 *data); +}; + +#ifdef CONFIG_PM_SLEEP +extern const struct dev_pm_ops adt7316_pm_ops; +#define ADT7316_PM_OPS (&adt7316_pm_ops) +#else +#define ADT7316_PM_OPS NULL +#endif +int adt7316_probe(struct device *dev, struct adt7316_bus *bus, const char *name); +int adt7316_remove(struct device *dev); + +#endif diff --git a/drivers/staging/iio/buffer.h b/drivers/staging/iio/buffer.h new file mode 100644 index 00000000..df2046dc --- /dev/null +++ b/drivers/staging/iio/buffer.h @@ -0,0 +1,193 @@ +/* The industrial I/O core - generic buffer interfaces. + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#ifndef _IIO_BUFFER_GENERIC_H_ +#define _IIO_BUFFER_GENERIC_H_ +#include +#include "iio.h" + +#ifdef CONFIG_IIO_BUFFER + +struct iio_buffer; + +/** + * struct iio_buffer_access_funcs - access functions for buffers. + * @store_to: actually store stuff to the buffer + * @read_first_n: try to get a specified number of bytes (must exist) + * @request_update: if a parameter change has been marked, update underlying + * storage. + * @get_bytes_per_datum:get current bytes per datum + * @set_bytes_per_datum:set number of bytes per datum + * @get_length: get number of datums in buffer + * @set_length: set number of datums in buffer + * + * The purpose of this structure is to make the buffer element + * modular as event for a given driver, different usecases may require + * different buffer designs (space efficiency vs speed for example). + * + * It is worth noting that a given buffer implementation may only support a + * small proportion of these functions. The core code 'should' cope fine with + * any of them not existing. + **/ +struct iio_buffer_access_funcs { + int (*store_to)(struct iio_buffer *buffer, u8 *data, s64 timestamp); + int (*read_first_n)(struct iio_buffer *buffer, + size_t n, + char __user *buf); + + int (*request_update)(struct iio_buffer *buffer); + + int (*get_bytes_per_datum)(struct iio_buffer *buffer); + int (*set_bytes_per_datum)(struct iio_buffer *buffer, size_t bpd); + int (*get_length)(struct iio_buffer *buffer); + int (*set_length)(struct iio_buffer *buffer, int length); +}; + +/** + * struct iio_buffer - general buffer structure + * @length: [DEVICE] number of datums in buffer + * @bytes_per_datum: [DEVICE] size of individual datum including timestamp + * @scan_el_attrs: [DRIVER] control of scan elements if that scan mode + * control method is used + * @scan_mask: [INTERN] bitmask used in masking scan mode elements + * @scan_index_timestamp:[INTERN] cache of the index to the timestamp + * @scan_timestamp: [INTERN] does the scan mode include a timestamp + * @access: [DRIVER] buffer access functions associated with the + * implementation. + * @scan_el_dev_attr_list:[INTERN] list of scan element related attributes. + * @scan_el_group: [DRIVER] attribute group for those attributes not + * created from the iio_chan_info array. + * @pollq: [INTERN] wait queue to allow for polling on the buffer. + * @stufftoread: [INTERN] flag to indicate new data. + * @demux_list: [INTERN] list of operations required to demux the scan. + * @demux_bounce: [INTERN] buffer for doing gather from incoming scan. + **/ +struct iio_buffer { + int length; + int bytes_per_datum; + struct attribute_group *scan_el_attrs; + long *scan_mask; + bool scan_timestamp; + unsigned scan_index_timestamp; + const struct iio_buffer_access_funcs *access; + struct list_head scan_el_dev_attr_list; + struct attribute_group scan_el_group; + wait_queue_head_t pollq; + bool stufftoread; + const struct attribute_group *attrs; + struct list_head demux_list; + unsigned char *demux_bounce; +}; + +/** + * iio_buffer_init() - Initialize the buffer structure + * @buffer: buffer to be initialized + **/ +void iio_buffer_init(struct iio_buffer *buffer); + +/** + * __iio_update_buffer() - update common elements of buffers + * @buffer: buffer that is the event source + * @bytes_per_datum: size of individual datum including timestamp + * @length: number of datums in buffer + **/ +static inline void __iio_update_buffer(struct iio_buffer *buffer, + int bytes_per_datum, int length) +{ + buffer->bytes_per_datum = bytes_per_datum; + buffer->length = length; +} + +int iio_scan_mask_query(struct iio_dev *indio_dev, + struct iio_buffer *buffer, int bit); + +/** + * iio_scan_mask_set() - set particular bit in the scan mask + * @buffer: the buffer whose scan mask we are interested in + * @bit: the bit to be set. + **/ +int iio_scan_mask_set(struct iio_dev *indio_dev, + struct iio_buffer *buffer, int bit); + +/** + * iio_push_to_buffer() - push to a registered buffer. + * @buffer: IIO buffer structure for device + * @scan: Full scan. + * @timestamp: + */ +int iio_push_to_buffer(struct iio_buffer *buffer, unsigned char *data, + s64 timestamp); + +int iio_update_demux(struct iio_dev *indio_dev); + +/** + * iio_buffer_register() - register the buffer with IIO core + * @indio_dev: device with the buffer to be registered + **/ +int iio_buffer_register(struct iio_dev *indio_dev, + const struct iio_chan_spec *channels, + int num_channels); + +/** + * iio_buffer_unregister() - unregister the buffer from IIO core + * @indio_dev: the device with the buffer to be unregistered + **/ +void iio_buffer_unregister(struct iio_dev *indio_dev); + +/** + * iio_buffer_read_length() - attr func to get number of datums in the buffer + **/ +ssize_t iio_buffer_read_length(struct device *dev, + struct device_attribute *attr, + char *buf); +/** + * iio_buffer_write_length() - attr func to set number of datums in the buffer + **/ +ssize_t iio_buffer_write_length(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len); +/** + * iio_buffer_store_enable() - attr to turn the buffer on + **/ +ssize_t iio_buffer_store_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len); +/** + * iio_buffer_show_enable() - attr to see if the buffer is on + **/ +ssize_t iio_buffer_show_enable(struct device *dev, + struct device_attribute *attr, + char *buf); +#define IIO_BUFFER_LENGTH_ATTR DEVICE_ATTR(length, S_IRUGO | S_IWUSR, \ + iio_buffer_read_length, \ + iio_buffer_write_length) + +#define IIO_BUFFER_ENABLE_ATTR DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, \ + iio_buffer_show_enable, \ + iio_buffer_store_enable) + +int iio_sw_buffer_preenable(struct iio_dev *indio_dev); + +#else /* CONFIG_IIO_BUFFER */ + +static inline int iio_buffer_register(struct iio_dev *indio_dev, + struct iio_chan_spec *channels, + int num_channels) +{ + return 0; +} + +static inline void iio_buffer_unregister(struct iio_dev *indio_dev) +{}; + +#endif /* CONFIG_IIO_BUFFER */ + +#endif /* _IIO_BUFFER_GENERIC_H_ */ diff --git a/drivers/staging/iio/cdc/Kconfig b/drivers/staging/iio/cdc/Kconfig new file mode 100644 index 00000000..80211df8 --- /dev/null +++ b/drivers/staging/iio/cdc/Kconfig @@ -0,0 +1,36 @@ +# +# CDC drivers +# +menu "Capacitance to digital converters" + +config AD7150 + tristate "Analog Devices ad7150/1/6 capacitive sensor driver" + depends on I2C + help + Say yes here to build support for Analog Devices capacitive sensors. + (ad7150, ad7151, ad7156) Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad7150. + +config AD7152 + tristate "Analog Devices ad7152/3 capacitive sensor driver" + depends on I2C + help + Say yes here to build support for Analog Devices capacitive sensors. + (ad7152, ad7153) Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad7152. + +config AD7746 + tristate "Analog Devices AD7745, AD7746 AD7747 capacitive sensor driver" + depends on I2C + help + Say yes here to build support for Analog Devices capacitive sensors. + (AD7745, AD7746, AD7747) Provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad7746. + +endmenu diff --git a/drivers/staging/iio/cdc/Makefile b/drivers/staging/iio/cdc/Makefile new file mode 100644 index 00000000..a5fbabf5 --- /dev/null +++ b/drivers/staging/iio/cdc/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for industrial I/O DAC drivers +# + +obj-$(CONFIG_AD7150) += ad7150.o +obj-$(CONFIG_AD7152) += ad7152.o +obj-$(CONFIG_AD7746) += ad7746.o diff --git a/drivers/staging/iio/cdc/ad7150.c b/drivers/staging/iio/cdc/ad7150.c new file mode 100644 index 00000000..e4a08dc9 --- /dev/null +++ b/drivers/staging/iio/cdc/ad7150.c @@ -0,0 +1,664 @@ +/* + * AD7150 capacitive sensor driver supporting AD7150/1/6 + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" +/* + * AD7150 registers definition + */ + +#define AD7150_STATUS 0 +#define AD7150_STATUS_OUT1 (1 << 3) +#define AD7150_STATUS_OUT2 (1 << 5) +#define AD7150_CH1_DATA_HIGH 1 +#define AD7150_CH2_DATA_HIGH 3 +#define AD7150_CH1_AVG_HIGH 5 +#define AD7150_CH2_AVG_HIGH 7 +#define AD7150_CH1_SENSITIVITY 9 +#define AD7150_CH1_THR_HOLD_H 9 +#define AD7150_CH1_TIMEOUT 10 +#define AD7150_CH1_SETUP 11 +#define AD7150_CH2_SENSITIVITY 12 +#define AD7150_CH2_THR_HOLD_H 12 +#define AD7150_CH2_TIMEOUT 13 +#define AD7150_CH2_SETUP 14 +#define AD7150_CFG 15 +#define AD7150_CFG_FIX (1 << 7) +#define AD7150_PD_TIMER 16 +#define AD7150_CH1_CAPDAC 17 +#define AD7150_CH2_CAPDAC 18 +#define AD7150_SN3 19 +#define AD7150_SN2 20 +#define AD7150_SN1 21 +#define AD7150_SN0 22 +#define AD7150_ID 23 + +/** + * struct ad7150_chip_info - instance specific chip data + * @client: i2c client for this device + * @current_event: device always has one type of event enabled. + * This element stores the event code of the current one. + * @threshold: thresholds for simple capacitance value events + * @thresh_sensitivity: threshold for simple capacitance offset + * from 'average' value. + * @mag_sensitity: threshold for magnitude of capacitance offset from + * from 'average' value. + * @thresh_timeout: a timeout, in samples from the moment an + * adaptive threshold event occurs to when the average + * value jumps to current value. + * @mag_timeout: a timeout, in sample from the moment an + * adaptive magnitude event occurs to when the average + * value jumps to the current value. + * @old_state: store state from previous event, allowing confirmation + * of new condition. + * @conversion_mode: the current conversion mode. + * @state_lock: ensure consistent state of this structure wrt the + * hardware. + */ +struct ad7150_chip_info { + struct i2c_client *client; + u64 current_event; + u16 threshold[2][2]; + u8 thresh_sensitivity[2][2]; + u8 mag_sensitivity[2][2]; + u8 thresh_timeout[2][2]; + u8 mag_timeout[2][2]; + int old_state; + char *conversion_mode; + struct mutex state_lock; +}; + +/* + * sysfs nodes + */ + +static const u8 ad7150_addresses[][6] = { + { AD7150_CH1_DATA_HIGH, AD7150_CH1_AVG_HIGH, + AD7150_CH1_SETUP, AD7150_CH1_THR_HOLD_H, + AD7150_CH1_SENSITIVITY, AD7150_CH1_TIMEOUT }, + { AD7150_CH2_DATA_HIGH, AD7150_CH2_AVG_HIGH, + AD7150_CH2_SETUP, AD7150_CH2_THR_HOLD_H, + AD7150_CH2_SENSITIVITY, AD7150_CH2_TIMEOUT }, +}; + +static int ad7150_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + + switch (mask) { + case 0: + ret = i2c_smbus_read_word_data(chip->client, + ad7150_addresses[chan->channel][0]); + if (ret < 0) + return ret; + *val = swab16(ret); + return IIO_VAL_INT; + case IIO_CHAN_INFO_AVERAGE_RAW: + ret = i2c_smbus_read_word_data(chip->client, + ad7150_addresses[chan->channel][1]); + if (ret < 0) + return ret; + *val = swab16(ret); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7150_read_event_config(struct iio_dev *indio_dev, u64 event_code) +{ + int ret; + u8 threshtype; + bool adaptive; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = !!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING); + + ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG); + if (ret < 0) + return ret; + + threshtype = (ret >> 5) & 0x03; + adaptive = !!(ret & 0x80); + + switch (IIO_EVENT_CODE_EXTRACT_TYPE(event_code)) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + if (rising) + return adaptive && (threshtype == 0x1); + else + return adaptive && (threshtype == 0x0); + case IIO_EV_TYPE_THRESH_ADAPTIVE: + if (rising) + return adaptive && (threshtype == 0x3); + else + return adaptive && (threshtype == 0x2); + + case IIO_EV_TYPE_THRESH: + if (rising) + return !adaptive && (threshtype == 0x1); + else + return !adaptive && (threshtype == 0x0); + }; + return -EINVAL; +} + +/* lock should be held */ +static int ad7150_write_event_params(struct iio_dev *indio_dev, u64 event_code) +{ + int ret; + u16 value; + u8 sens, timeout; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int chan = IIO_EVENT_CODE_EXTRACT_CHAN(event_code); + int rising = !!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING); + + if (event_code != chip->current_event) + return 0; + + switch (IIO_EVENT_CODE_EXTRACT_TYPE(event_code)) { + /* Note completely different from the adaptive versions */ + case IIO_EV_TYPE_THRESH: + value = chip->threshold[rising][chan]; + ret = i2c_smbus_write_word_data(chip->client, + ad7150_addresses[chan][3], + swab16(value)); + if (ret < 0) + return ret; + return 0; + case IIO_EV_TYPE_MAG_ADAPTIVE: + sens = chip->mag_sensitivity[rising][chan]; + timeout = chip->mag_timeout[rising][chan]; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + sens = chip->thresh_sensitivity[rising][chan]; + timeout = chip->thresh_timeout[rising][chan]; + break; + default: + return -EINVAL; + }; + ret = i2c_smbus_write_byte_data(chip->client, + ad7150_addresses[chan][4], + sens); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(chip->client, + ad7150_addresses[chan][5], + timeout); + if (ret < 0) + return ret; + + return 0; +} + +static int ad7150_write_event_config(struct iio_dev *indio_dev, + u64 event_code, int state) +{ + u8 thresh_type, cfg, adaptive; + int ret; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = !!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING); + + /* Something must always be turned on */ + if (state == 0) + return -EINVAL; + + if (event_code == chip->current_event) + return 0; + mutex_lock(&chip->state_lock); + ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG); + if (ret < 0) + goto error_ret; + + cfg = ret & ~((0x03 << 5) | (0x1 << 7)); + + switch (IIO_EVENT_CODE_EXTRACT_TYPE(event_code)) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + adaptive = 1; + if (rising) + thresh_type = 0x1; + else + thresh_type = 0x0; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + adaptive = 1; + if (rising) + thresh_type = 0x3; + else + thresh_type = 0x2; + break; + case IIO_EV_TYPE_THRESH: + adaptive = 0; + if (rising) + thresh_type = 0x1; + else + thresh_type = 0x0; + break; + default: + ret = -EINVAL; + goto error_ret; + }; + + cfg |= (!adaptive << 7) | (thresh_type << 5); + + ret = i2c_smbus_write_byte_data(chip->client, AD7150_CFG, cfg); + if (ret < 0) + goto error_ret; + + chip->current_event = event_code; + + /* update control attributes */ + ret = ad7150_write_event_params(indio_dev, event_code); +error_ret: + mutex_unlock(&chip->state_lock); + + return 0; +} + +static int ad7150_read_event_value(struct iio_dev *indio_dev, + u64 event_code, + int *val) +{ + int chan = IIO_EVENT_CODE_EXTRACT_CHAN(event_code); + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int rising = !!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING); + + /* Complex register sharing going on here */ + switch (IIO_EVENT_CODE_EXTRACT_TYPE(event_code)) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + *val = chip->mag_sensitivity[rising][chan]; + return 0; + + case IIO_EV_TYPE_THRESH_ADAPTIVE: + *val = chip->thresh_sensitivity[rising][chan]; + return 0; + + case IIO_EV_TYPE_THRESH: + *val = chip->threshold[rising][chan]; + return 0; + + default: + return -EINVAL; + }; +} + +static int ad7150_write_event_value(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + int ret; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + int chan = IIO_EVENT_CODE_EXTRACT_CHAN(event_code); + int rising = !!(IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING); + + mutex_lock(&chip->state_lock); + switch (IIO_EVENT_CODE_EXTRACT_TYPE(event_code)) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + chip->mag_sensitivity[rising][chan] = val; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + chip->thresh_sensitivity[rising][chan] = val; + break; + case IIO_EV_TYPE_THRESH: + chip->threshold[rising][chan] = val; + break; + default: + ret = -EINVAL; + goto error_ret; + }; + + /* write back if active */ + ret = ad7150_write_event_params(indio_dev, event_code); + +error_ret: + mutex_unlock(&chip->state_lock); + return ret; +} + +static ssize_t ad7150_show_timeout(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7150_chip_info *chip = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + u8 value; + + /* use the event code for consistency reasons */ + int chan = IIO_EVENT_CODE_EXTRACT_CHAN(this_attr->address); + int rising = !!(IIO_EVENT_CODE_EXTRACT_DIR(this_attr->address) + == IIO_EV_DIR_RISING); + + switch (IIO_EVENT_CODE_EXTRACT_TYPE(this_attr->address)) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + value = chip->mag_timeout[rising][chan]; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + value = chip->thresh_timeout[rising][chan]; + break; + default: + return -EINVAL; + }; + + return sprintf(buf, "%d\n", value); +} + +static ssize_t ad7150_store_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7150_chip_info *chip = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int chan = IIO_EVENT_CODE_EXTRACT_CHAN(this_attr->address); + int rising = !!(IIO_EVENT_CODE_EXTRACT_DIR(this_attr->address) == + IIO_EV_DIR_RISING); + u8 data; + int ret; + + ret = kstrtou8(buf, 10, &data); + if (ret < 0) + return ret; + + mutex_lock(&chip->state_lock); + switch (IIO_EVENT_CODE_EXTRACT_TYPE(this_attr->address)) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + chip->mag_timeout[rising][chan] = data; + break; + case IIO_EV_TYPE_THRESH_ADAPTIVE: + chip->thresh_timeout[rising][chan] = data; + break; + default: + ret = -EINVAL; + goto error_ret; + }; + + ret = ad7150_write_event_params(indio_dev, this_attr->address); +error_ret: + mutex_unlock(&chip->state_lock); + + if (ret < 0) + return ret; + + return len; +} + +#define AD7150_TIMEOUT(chan, type, dir, ev_type, ev_dir) \ + IIO_DEVICE_ATTR(in_capacitance##chan##_##type##_##dir##_timeout, \ + S_IRUGO | S_IWUSR, \ + &ad7150_show_timeout, \ + &ad7150_store_timeout, \ + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, \ + chan, \ + IIO_EV_TYPE_##ev_type, \ + IIO_EV_DIR_##ev_dir)) +static AD7150_TIMEOUT(0, mag_adaptive, rising, MAG_ADAPTIVE, RISING); +static AD7150_TIMEOUT(0, mag_adaptive, falling, MAG_ADAPTIVE, FALLING); +static AD7150_TIMEOUT(1, mag_adaptive, rising, MAG_ADAPTIVE, RISING); +static AD7150_TIMEOUT(1, mag_adaptive, falling, MAG_ADAPTIVE, FALLING); +static AD7150_TIMEOUT(0, thresh_adaptive, rising, THRESH_ADAPTIVE, RISING); +static AD7150_TIMEOUT(0, thresh_adaptive, falling, THRESH_ADAPTIVE, FALLING); +static AD7150_TIMEOUT(1, thresh_adaptive, rising, THRESH_ADAPTIVE, RISING); +static AD7150_TIMEOUT(1, thresh_adaptive, falling, THRESH_ADAPTIVE, FALLING); + +static const struct iio_chan_spec ad7150_channels[] = { + { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT, + .event_mask = + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH_ADAPTIVE, IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH_ADAPTIVE, IIO_EV_DIR_FALLING) | + IIO_EV_BIT(IIO_EV_TYPE_MAG_ADAPTIVE, IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_MAG_ADAPTIVE, IIO_EV_DIR_FALLING) + }, { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT, + .event_mask = + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH_ADAPTIVE, IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH_ADAPTIVE, IIO_EV_DIR_FALLING) | + IIO_EV_BIT(IIO_EV_TYPE_MAG_ADAPTIVE, IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_MAG_ADAPTIVE, IIO_EV_DIR_FALLING) + }, +}; + +/* + * threshold events + */ + +static irqreturn_t ad7150_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct ad7150_chip_info *chip = iio_priv(indio_dev); + u8 int_status; + s64 timestamp = iio_get_time_ns(); + int ret; + + ret = i2c_smbus_read_byte_data(chip->client, AD7150_STATUS); + if (ret < 0) + return IRQ_HANDLED; + + int_status = ret; + + if ((int_status & AD7150_STATUS_OUT1) && + !(chip->old_state & AD7150_STATUS_OUT1)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if ((!(int_status & AD7150_STATUS_OUT1)) && + (chip->old_state & AD7150_STATUS_OUT1)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + + if ((int_status & AD7150_STATUS_OUT2) && + !(chip->old_state & AD7150_STATUS_OUT2)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, + 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + timestamp); + else if ((!(int_status & AD7150_STATUS_OUT2)) && + (chip->old_state & AD7150_STATUS_OUT2)) + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, + 1, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + timestamp); + /* store the status to avoid repushing same events */ + chip->old_state = int_status; + + return IRQ_HANDLED; +} + +/* Timeouts not currently handled by core */ +static struct attribute *ad7150_event_attributes[] = { + &iio_dev_attr_in_capacitance0_mag_adaptive_rising_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance0_mag_adaptive_falling_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance1_mag_adaptive_rising_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance1_mag_adaptive_falling_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance0_thresh_adaptive_rising_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance0_thresh_adaptive_falling_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance1_thresh_adaptive_rising_timeout + .dev_attr.attr, + &iio_dev_attr_in_capacitance1_thresh_adaptive_falling_timeout + .dev_attr.attr, + NULL, +}; + +static struct attribute_group ad7150_event_attribute_group = { + .attrs = ad7150_event_attributes, + .name = "events", +}; + +static const struct iio_info ad7150_info = { + .event_attrs = &ad7150_event_attribute_group, + .driver_module = THIS_MODULE, + .read_raw = &ad7150_read_raw, + .read_event_config = &ad7150_read_event_config, + .write_event_config = &ad7150_write_event_config, + .read_event_value = &ad7150_read_event_value, + .write_event_value = &ad7150_write_event_value, +}; + +/* + * device probe and remove + */ + +static int __devinit ad7150_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct ad7150_chip_info *chip; + struct iio_dev *indio_dev; + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + chip = iio_priv(indio_dev); + mutex_init(&chip->state_lock); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + chip->client = client; + + indio_dev->name = id->name; + indio_dev->channels = ad7150_channels; + indio_dev->num_channels = ARRAY_SIZE(ad7150_channels); + /* Establish that the iio_dev is a child of the i2c device */ + indio_dev->dev.parent = &client->dev; + + indio_dev->info = &ad7150_info; + + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) { + ret = request_threaded_irq(client->irq, + NULL, + &ad7150_event_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "ad7150_irq1", + indio_dev); + if (ret) + goto error_free_dev; + } + + if (client->dev.platform_data) { + ret = request_threaded_irq(*(unsigned int *) + client->dev.platform_data, + NULL, + &ad7150_event_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "ad7150_irq2", + indio_dev); + if (ret) + goto error_free_irq; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_irq2; + + dev_info(&client->dev, "%s capacitive sensor registered,irq: %d\n", + id->name, client->irq); + + return 0; +error_free_irq2: + if (client->dev.platform_data) + free_irq(*(unsigned int *)client->dev.platform_data, + indio_dev); +error_free_irq: + if (client->irq) + free_irq(client->irq, indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit ad7150_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + if (client->irq) + free_irq(client->irq, indio_dev); + + if (client->dev.platform_data) + free_irq(*(unsigned int *)client->dev.platform_data, indio_dev); + + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id ad7150_id[] = { + { "ad7150", 0 }, + { "ad7151", 0 }, + { "ad7156", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad7150_id); + +static struct i2c_driver ad7150_driver = { + .driver = { + .name = "ad7150", + }, + .probe = ad7150_probe, + .remove = __devexit_p(ad7150_remove), + .id_table = ad7150_id, +}; +module_i2c_driver(ad7150_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices AD7150/1/6 capacitive sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/cdc/ad7152.c b/drivers/staging/iio/cdc/ad7152.c new file mode 100644 index 00000000..fdb83c35 --- /dev/null +++ b/drivers/staging/iio/cdc/ad7152.c @@ -0,0 +1,547 @@ +/* + * AD7152 capacitive sensor driver supporting AD7152/3 + * + * Copyright 2010-2011a Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +/* + * TODO: Check compliance of calibbias with abi (units) + */ +/* + * AD7152 registers definition + */ + +#define AD7152_REG_STATUS 0 +#define AD7152_REG_CH1_DATA_HIGH 1 +#define AD7152_REG_CH2_DATA_HIGH 3 +#define AD7152_REG_CH1_OFFS_HIGH 5 +#define AD7152_REG_CH2_OFFS_HIGH 7 +#define AD7152_REG_CH1_GAIN_HIGH 9 +#define AD7152_REG_CH1_SETUP 11 +#define AD7152_REG_CH2_GAIN_HIGH 12 +#define AD7152_REG_CH2_SETUP 14 +#define AD7152_REG_CFG 15 +#define AD7152_REG_RESEVERD 16 +#define AD7152_REG_CAPDAC_POS 17 +#define AD7152_REG_CAPDAC_NEG 18 +#define AD7152_REG_CFG2 26 + +/* Status Register Bit Designations (AD7152_REG_STATUS) */ +#define AD7152_STATUS_RDY1 (1 << 0) +#define AD7152_STATUS_RDY2 (1 << 1) +#define AD7152_STATUS_C1C2 (1 << 2) +#define AD7152_STATUS_PWDN (1 << 7) + +/* Setup Register Bit Designations (AD7152_REG_CHx_SETUP) */ +#define AD7152_SETUP_CAPDIFF (1 << 5) +#define AD7152_SETUP_RANGE_2pF (0 << 6) +#define AD7152_SETUP_RANGE_0_5pF (1 << 6) +#define AD7152_SETUP_RANGE_1pF (2 << 6) +#define AD7152_SETUP_RANGE_4pF (3 << 6) +#define AD7152_SETUP_RANGE(x) ((x) << 6) + +/* Config Register Bit Designations (AD7152_REG_CFG) */ +#define AD7152_CONF_CH2EN (1 << 3) +#define AD7152_CONF_CH1EN (1 << 4) +#define AD7152_CONF_MODE_IDLE (0 << 0) +#define AD7152_CONF_MODE_CONT_CONV (1 << 0) +#define AD7152_CONF_MODE_SINGLE_CONV (2 << 0) +#define AD7152_CONF_MODE_OFFS_CAL (5 << 0) +#define AD7152_CONF_MODE_GAIN_CAL (6 << 0) + +/* Capdac Register Bit Designations (AD7152_REG_CAPDAC_XXX) */ +#define AD7152_CAPDAC_DACEN (1 << 7) +#define AD7152_CAPDAC_DACP(x) ((x) & 0x1F) + +/* CFG2 Register Bit Designations (AD7152_REG_CFG2) */ +#define AD7152_CFG2_OSR(x) (((x) & 0x3) << 4) + +enum { + AD7152_DATA, + AD7152_OFFS, + AD7152_GAIN, + AD7152_SETUP +}; + +/* + * struct ad7152_chip_info - chip specifc information + */ + +struct ad7152_chip_info { + struct i2c_client *client; + /* + * Capacitive channel digital filter setup; + * conversion time/update rate setup per channel + */ + u8 filter_rate_setup; + u8 setup[2]; +}; + +static inline ssize_t ad7152_start_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len, + u8 regval) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7152_chip_info *chip = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + bool doit; + int ret, timeout = 10; + + ret = strtobool(buf, &doit); + if (ret < 0) + return ret; + + if (!doit) + return 0; + + if (this_attr->address == 0) + regval |= AD7152_CONF_CH1EN; + else + regval |= AD7152_CONF_CH2EN; + + mutex_lock(&indio_dev->mlock); + ret = i2c_smbus_write_byte_data(chip->client, AD7152_REG_CFG, regval); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + do { + mdelay(20); + ret = i2c_smbus_read_byte_data(chip->client, AD7152_REG_CFG); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } while ((ret == regval) && timeout--); + + mutex_unlock(&indio_dev->mlock); + return len; +} +static ssize_t ad7152_start_offset_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return ad7152_start_calib(dev, attr, buf, len, + AD7152_CONF_MODE_OFFS_CAL); +} +static ssize_t ad7152_start_gain_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + return ad7152_start_calib(dev, attr, buf, len, + AD7152_CONF_MODE_GAIN_CAL); +} + +static IIO_DEVICE_ATTR(in_capacitance0_calibbias_calibration, + S_IWUSR, NULL, ad7152_start_offset_calib, 0); +static IIO_DEVICE_ATTR(in_capacitance1_calibbias_calibration, + S_IWUSR, NULL, ad7152_start_offset_calib, 1); +static IIO_DEVICE_ATTR(in_capacitance0_calibscale_calibration, + S_IWUSR, NULL, ad7152_start_gain_calib, 0); +static IIO_DEVICE_ATTR(in_capacitance1_calibscale_calibration, + S_IWUSR, NULL, ad7152_start_gain_calib, 1); + +/* Values are Update Rate (Hz), Conversion Time (ms) + 1*/ +static const unsigned char ad7152_filter_rate_table[][2] = { + {200, 5 + 1}, {50, 20 + 1}, {20, 50 + 1}, {17, 60 + 1}, +}; + +static ssize_t ad7152_show_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7152_chip_info *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", + ad7152_filter_rate_table[chip->filter_rate_setup][0]); +} + +static ssize_t ad7152_store_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7152_chip_info *chip = iio_priv(indio_dev); + u8 data; + int ret, i; + + ret = kstrtou8(buf, 10, &data); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ad7152_filter_rate_table); i++) + if (data >= ad7152_filter_rate_table[i][0]) + break; + + if (i >= ARRAY_SIZE(ad7152_filter_rate_table)) + i = ARRAY_SIZE(ad7152_filter_rate_table) - 1; + + mutex_lock(&indio_dev->mlock); + ret = i2c_smbus_write_byte_data(chip->client, + AD7152_REG_CFG2, AD7152_CFG2_OSR(i)); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + chip->filter_rate_setup = i; + mutex_unlock(&indio_dev->mlock); + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, + ad7152_show_filter_rate_setup, + ad7152_store_filter_rate_setup); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("200 50 20 17"); + +static IIO_CONST_ATTR(in_capacitance_scale_available, + "0.000061050 0.000030525 0.000015263 0.000007631"); + +static struct attribute *ad7152_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_capacitance0_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance0_calibscale_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibscale_calibration.dev_attr.attr, + &iio_const_attr_in_capacitance_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7152_attribute_group = { + .attrs = ad7152_attributes, +}; + +static const u8 ad7152_addresses[][4] = { + { AD7152_REG_CH1_DATA_HIGH, AD7152_REG_CH1_OFFS_HIGH, + AD7152_REG_CH1_GAIN_HIGH, AD7152_REG_CH1_SETUP }, + { AD7152_REG_CH2_DATA_HIGH, AD7152_REG_CH2_OFFS_HIGH, + AD7152_REG_CH2_GAIN_HIGH, AD7152_REG_CH2_SETUP }, +}; + +/* Values are nano relative to pf base. */ +static const int ad7152_scale_table[] = { + 30525, 7631, 15263, 61050 +}; + +static int ad7152_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7152_chip_info *chip = iio_priv(indio_dev); + int ret, i; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (val != 1) { + ret = -EINVAL; + goto out; + } + + val = (val2 * 1024) / 15625; + + ret = i2c_smbus_write_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_GAIN], + swab16(val)); + if (ret < 0) + goto out; + + ret = 0; + break; + + case IIO_CHAN_INFO_CALIBBIAS: + if ((val < 0) | (val > 0xFFFF)) { + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_OFFS], + swab16(val)); + if (ret < 0) + goto out; + + ret = 0; + break; + case IIO_CHAN_INFO_SCALE: + if (val != 0) { + ret = -EINVAL; + goto out; + } + for (i = 0; i < ARRAY_SIZE(ad7152_scale_table); i++) + if (val2 == ad7152_scale_table[i]) + break; + + chip->setup[chan->channel] &= ~AD7152_SETUP_RANGE_4pF; + chip->setup[chan->channel] |= AD7152_SETUP_RANGE(i); + + ret = i2c_smbus_write_byte_data(chip->client, + ad7152_addresses[chan->channel][AD7152_SETUP], + chip->setup[chan->channel]); + if (ret < 0) + goto out; + + ret = 0; + break; + default: + ret = -EINVAL; + } + +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} +static int ad7152_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct ad7152_chip_info *chip = iio_priv(indio_dev); + int ret; + u8 regval = 0; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case 0: + /* First set whether in differential mode */ + + regval = chip->setup[chan->channel]; + + if (chan->differential) + chip->setup[chan->channel] |= AD7152_SETUP_CAPDIFF; + else + chip->setup[chan->channel] &= ~AD7152_SETUP_CAPDIFF; + + if (regval != chip->setup[chan->channel]) { + ret = i2c_smbus_write_byte_data(chip->client, + ad7152_addresses[chan->channel][AD7152_SETUP], + chip->setup[chan->channel]); + if (ret < 0) + goto out; + } + /* Make sure the channel is enabled */ + if (chan->channel == 0) + regval = AD7152_CONF_CH1EN; + else + regval = AD7152_CONF_CH2EN; + + /* Trigger a single read */ + regval |= AD7152_CONF_MODE_SINGLE_CONV; + ret = i2c_smbus_write_byte_data(chip->client, AD7152_REG_CFG, + regval); + if (ret < 0) + goto out; + + msleep(ad7152_filter_rate_table[chip->filter_rate_setup][1]); + /* Now read the actual register */ + ret = i2c_smbus_read_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_DATA]); + if (ret < 0) + goto out; + *val = swab16(ret); + + if (chan->differential) + *val -= 0x8000; + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + + ret = i2c_smbus_read_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_GAIN]); + if (ret < 0) + goto out; + /* 1 + gain_val / 2^16 */ + *val = 1; + *val2 = (15625 * swab16(ret)) / 1024; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_CALIBBIAS: + ret = i2c_smbus_read_word_data(chip->client, + ad7152_addresses[chan->channel][AD7152_OFFS]); + if (ret < 0) + goto out; + *val = swab16(ret); + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + ret = i2c_smbus_read_byte_data(chip->client, + ad7152_addresses[chan->channel][AD7152_SETUP]); + if (ret < 0) + goto out; + *val = 0; + *val2 = ad7152_scale_table[ret >> 6]; + + ret = IIO_VAL_INT_PLUS_NANO; + break; + default: + ret = -EINVAL; + }; +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static int ad7152_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static const struct iio_info ad7152_info = { + .attrs = &ad7152_attribute_group, + .read_raw = &ad7152_read_raw, + .write_raw = &ad7152_write_raw, + .write_raw_get_fmt = &ad7152_write_raw_get_fmt, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec ad7152_channels[] = { + { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + }, { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 0, + .channel2 = 2, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + }, { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + }, { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 1, + .channel2 = 3, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + } +}; +/* + * device probe and remove + */ + +static int __devinit ad7152_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct ad7152_chip_info *chip; + struct iio_dev *indio_dev; + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + chip->client = client; + + /* Establish that the iio_dev is a child of the i2c device */ + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ad7152_info; + indio_dev->channels = ad7152_channels; + if (id->driver_data == 0) + indio_dev->num_channels = ARRAY_SIZE(ad7152_channels); + else + indio_dev->num_channels = 2; + indio_dev->num_channels = ARRAY_SIZE(ad7152_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + dev_err(&client->dev, "%s capacitive sensor registered\n", id->name); + + return 0; + +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit ad7152_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id ad7152_id[] = { + { "ad7152", 0 }, + { "ad7153", 1 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad7152_id); + +static struct i2c_driver ad7152_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ad7152_probe, + .remove = __devexit_p(ad7152_remove), + .id_table = ad7152_id, +}; +module_i2c_driver(ad7152_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices AD7152/3 capacitive sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/cdc/ad7746.c b/drivers/staging/iio/cdc/ad7746.c new file mode 100644 index 00000000..40b8512c --- /dev/null +++ b/drivers/staging/iio/cdc/ad7746.c @@ -0,0 +1,795 @@ +/* + * AD7746 capacitive sensor driver supporting AD7745, AD7746 and AD7747 + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#include "ad7746.h" + +/* + * AD7746 Register Definition + */ + +#define AD7746_REG_STATUS 0 +#define AD7746_REG_CAP_DATA_HIGH 1 +#define AD7746_REG_CAP_DATA_MID 2 +#define AD7746_REG_CAP_DATA_LOW 3 +#define AD7746_REG_VT_DATA_HIGH 4 +#define AD7746_REG_VT_DATA_MID 5 +#define AD7746_REG_VT_DATA_LOW 6 +#define AD7746_REG_CAP_SETUP 7 +#define AD7746_REG_VT_SETUP 8 +#define AD7746_REG_EXC_SETUP 9 +#define AD7746_REG_CFG 10 +#define AD7746_REG_CAPDACA 11 +#define AD7746_REG_CAPDACB 12 +#define AD7746_REG_CAP_OFFH 13 +#define AD7746_REG_CAP_OFFL 14 +#define AD7746_REG_CAP_GAINH 15 +#define AD7746_REG_CAP_GAINL 16 +#define AD7746_REG_VOLT_GAINH 17 +#define AD7746_REG_VOLT_GAINL 18 + +/* Status Register Bit Designations (AD7746_REG_STATUS) */ +#define AD7746_STATUS_EXCERR (1 << 3) +#define AD7746_STATUS_RDY (1 << 2) +#define AD7746_STATUS_RDYVT (1 << 1) +#define AD7746_STATUS_RDYCAP (1 << 0) + +/* Capacitive Channel Setup Register Bit Designations (AD7746_REG_CAP_SETUP) */ +#define AD7746_CAPSETUP_CAPEN (1 << 7) +#define AD7746_CAPSETUP_CIN2 (1 << 6) /* AD7746 only */ +#define AD7746_CAPSETUP_CAPDIFF (1 << 5) +#define AD7746_CAPSETUP_CACHOP (1 << 0) + +/* Voltage/Temperature Setup Register Bit Designations (AD7746_REG_VT_SETUP) */ +#define AD7746_VTSETUP_VTEN (1 << 7) +#define AD7746_VTSETUP_VTMD_INT_TEMP (0 << 5) +#define AD7746_VTSETUP_VTMD_EXT_TEMP (1 << 5) +#define AD7746_VTSETUP_VTMD_VDD_MON (2 << 5) +#define AD7746_VTSETUP_VTMD_EXT_VIN (3 << 5) +#define AD7746_VTSETUP_EXTREF (1 << 4) +#define AD7746_VTSETUP_VTSHORT (1 << 1) +#define AD7746_VTSETUP_VTCHOP (1 << 0) + +/* Excitation Setup Register Bit Designations (AD7746_REG_EXC_SETUP) */ +#define AD7746_EXCSETUP_CLKCTRL (1 << 7) +#define AD7746_EXCSETUP_EXCON (1 << 6) +#define AD7746_EXCSETUP_EXCB (1 << 5) +#define AD7746_EXCSETUP_NEXCB (1 << 4) +#define AD7746_EXCSETUP_EXCA (1 << 3) +#define AD7746_EXCSETUP_NEXCA (1 << 2) +#define AD7746_EXCSETUP_EXCLVL(x) (((x) & 0x3) << 0) + +/* Config Register Bit Designations (AD7746_REG_CFG) */ +#define AD7746_CONF_VTFS(x) ((x) << 6) +#define AD7746_CONF_CAPFS(x) ((x) << 3) +#define AD7746_CONF_MODE_IDLE (0 << 0) +#define AD7746_CONF_MODE_CONT_CONV (1 << 0) +#define AD7746_CONF_MODE_SINGLE_CONV (2 << 0) +#define AD7746_CONF_MODE_PWRDN (3 << 0) +#define AD7746_CONF_MODE_OFFS_CAL (5 << 0) +#define AD7746_CONF_MODE_GAIN_CAL (6 << 0) + +/* CAPDAC Register Bit Designations (AD7746_REG_CAPDACx) */ +#define AD7746_CAPDAC_DACEN (1 << 7) +#define AD7746_CAPDAC_DACP(x) ((x) & 0x7F) + +/* + * struct ad7746_chip_info - chip specifc information + */ + +struct ad7746_chip_info { + struct i2c_client *client; + /* + * Capacitive channel digital filter setup; + * conversion time/update rate setup per channel + */ + u8 config; + u8 cap_setup; + u8 vt_setup; + u8 capdac[2][2]; + s8 capdac_set; +}; + +enum ad7746_chan { + VIN, + VIN_VDD, + TEMP_INT, + TEMP_EXT, + CIN1, + CIN1_DIFF, + CIN2, + CIN2_DIFF, +}; + +static const struct iio_chan_spec ad7746_channels[] = { + [VIN] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = AD7746_REG_VT_DATA_HIGH << 8 | + AD7746_VTSETUP_VTMD_EXT_VIN, + }, + [VIN_VDD] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .extend_name = "supply", + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = AD7746_REG_VT_DATA_HIGH << 8 | + AD7746_VTSETUP_VTMD_VDD_MON, + }, + [TEMP_INT] = { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .processed_val = IIO_PROCESSED, + .address = AD7746_REG_VT_DATA_HIGH << 8 | + AD7746_VTSETUP_VTMD_INT_TEMP, + }, + [TEMP_EXT] = { + .type = IIO_TEMP, + .indexed = 1, + .channel = 1, + .processed_val = IIO_PROCESSED, + .address = AD7746_REG_VT_DATA_HIGH << 8 | + AD7746_VTSETUP_VTMD_EXT_TEMP, + }, + [CIN1] = { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SHARED_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = AD7746_REG_CAP_DATA_HIGH << 8, + }, + [CIN1_DIFF] = { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 0, + .channel2 = 2, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SHARED_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = AD7746_REG_CAP_DATA_HIGH << 8 | + AD7746_CAPSETUP_CAPDIFF + }, + [CIN2] = { + .type = IIO_CAPACITANCE, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SHARED_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = AD7746_REG_CAP_DATA_HIGH << 8 | + AD7746_CAPSETUP_CIN2, + }, + [CIN2_DIFF] = { + .type = IIO_CAPACITANCE, + .differential = 1, + .indexed = 1, + .channel = 1, + .channel2 = 3, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SHARED_BIT | + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = AD7746_REG_CAP_DATA_HIGH << 8 | + AD7746_CAPSETUP_CAPDIFF | AD7746_CAPSETUP_CIN2, + } +}; + +/* Values are Update Rate (Hz), Conversion Time (ms) + 1*/ +static const unsigned char ad7746_vt_filter_rate_table[][2] = { + {50, 20 + 1}, {31, 32 + 1}, {16, 62 + 1}, {8, 122 + 1}, +}; + +static const unsigned char ad7746_cap_filter_rate_table[][2] = { + {91, 11 + 1}, {84, 12 + 1}, {50, 20 + 1}, {26, 38 + 1}, + {16, 62 + 1}, {13, 77 + 1}, {11, 92 + 1}, {9, 110 + 1}, +}; + +static int ad7746_select_channel(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, delay; + u8 vt_setup, cap_setup; + + switch (chan->type) { + case IIO_CAPACITANCE: + cap_setup = (chan->address & 0xFF) | AD7746_CAPSETUP_CAPEN; + vt_setup = chip->vt_setup & ~AD7746_VTSETUP_VTEN; + delay = ad7746_cap_filter_rate_table[(chip->config >> 3) & + 0x7][1]; + + if (chip->capdac_set != chan->channel) { + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACA, + chip->capdac[chan->channel][0]); + if (ret < 0) + return ret; + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACB, + chip->capdac[chan->channel][1]); + if (ret < 0) + return ret; + + chip->capdac_set = chan->channel; + } + break; + case IIO_VOLTAGE: + case IIO_TEMP: + vt_setup = (chan->address & 0xFF) | AD7746_VTSETUP_VTEN; + cap_setup = chip->cap_setup & ~AD7746_CAPSETUP_CAPEN; + delay = ad7746_cap_filter_rate_table[(chip->config >> 6) & + 0x3][1]; + break; + default: + return -EINVAL; + } + + if (chip->cap_setup != cap_setup) { + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAP_SETUP, + cap_setup); + if (ret < 0) + return ret; + + chip->cap_setup = cap_setup; + } + + if (chip->vt_setup != vt_setup) { + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_VT_SETUP, + vt_setup); + if (ret < 0) + return ret; + + chip->vt_setup = vt_setup; + } + + return delay; +} + +static inline ssize_t ad7746_start_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len, + u8 regval) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + bool doit; + int ret, timeout = 10; + + ret = strtobool(buf, &doit); + if (ret < 0) + return ret; + + if (!doit) + return 0; + + mutex_lock(&indio_dev->mlock); + regval |= chip->config; + ret = i2c_smbus_write_byte_data(chip->client, AD7746_REG_CFG, regval); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + do { + msleep(20); + ret = i2c_smbus_read_byte_data(chip->client, AD7746_REG_CFG); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } while ((ret == regval) && timeout--); + + mutex_unlock(&indio_dev->mlock); + + return len; +} + +static ssize_t ad7746_start_offset_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int ret = ad7746_select_channel(indio_dev, + &ad7746_channels[to_iio_dev_attr(attr)->address]); + if (ret < 0) + return ret; + + return ad7746_start_calib(dev, attr, buf, len, + AD7746_CONF_MODE_OFFS_CAL); +} + +static ssize_t ad7746_start_gain_calib(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int ret = ad7746_select_channel(indio_dev, + &ad7746_channels[to_iio_dev_attr(attr)->address]); + if (ret < 0) + return ret; + + return ad7746_start_calib(dev, attr, buf, len, + AD7746_CONF_MODE_GAIN_CAL); +} + +static IIO_DEVICE_ATTR(in_capacitance0_calibbias_calibration, + S_IWUSR, NULL, ad7746_start_offset_calib, CIN1); +static IIO_DEVICE_ATTR(in_capacitance1_calibbias_calibration, + S_IWUSR, NULL, ad7746_start_offset_calib, CIN2); +static IIO_DEVICE_ATTR(in_capacitance0_calibscale_calibration, + S_IWUSR, NULL, ad7746_start_gain_calib, CIN1); +static IIO_DEVICE_ATTR(in_capacitance1_calibscale_calibration, + S_IWUSR, NULL, ad7746_start_gain_calib, CIN2); +static IIO_DEVICE_ATTR(in_voltage0_calibscale_calibration, + S_IWUSR, NULL, ad7746_start_gain_calib, VIN); + +static ssize_t ad7746_show_cap_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", ad7746_cap_filter_rate_table[ + (chip->config >> 3) & 0x7][0]); +} + +static ssize_t ad7746_store_cap_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + u8 data; + int ret, i; + + ret = kstrtou8(buf, 10, &data); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ad7746_cap_filter_rate_table); i++) + if (data >= ad7746_cap_filter_rate_table[i][0]) + break; + + if (i >= ARRAY_SIZE(ad7746_cap_filter_rate_table)) + i = ARRAY_SIZE(ad7746_cap_filter_rate_table) - 1; + + mutex_lock(&indio_dev->mlock); + chip->config &= ~AD7746_CONF_CAPFS(0x7); + chip->config |= AD7746_CONF_CAPFS(i); + mutex_unlock(&indio_dev->mlock); + + return len; +} + +static ssize_t ad7746_show_vt_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", ad7746_vt_filter_rate_table[ + (chip->config >> 6) & 0x3][0]); +} + +static ssize_t ad7746_store_vt_filter_rate_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad7746_chip_info *chip = iio_priv(indio_dev); + u8 data; + int ret, i; + + ret = kstrtou8(buf, 10, &data); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(ad7746_vt_filter_rate_table); i++) + if (data >= ad7746_vt_filter_rate_table[i][0]) + break; + + if (i >= ARRAY_SIZE(ad7746_vt_filter_rate_table)) + i = ARRAY_SIZE(ad7746_vt_filter_rate_table) - 1; + + mutex_lock(&indio_dev->mlock); + chip->config &= ~AD7746_CONF_VTFS(0x3); + chip->config |= AD7746_CONF_VTFS(i); + mutex_unlock(&indio_dev->mlock); + + return len; +} + +static IIO_DEVICE_ATTR(in_capacitance_sampling_frequency, + S_IRUGO | S_IWUSR, ad7746_show_cap_filter_rate_setup, + ad7746_store_cap_filter_rate_setup, 0); + +static IIO_DEVICE_ATTR(in_voltage_sampling_frequency, + S_IRUGO | S_IWUSR, ad7746_show_vt_filter_rate_setup, + ad7746_store_vt_filter_rate_setup, 0); + +static IIO_CONST_ATTR(in_voltage_sampling_frequency_available, "50 31 16 8"); +static IIO_CONST_ATTR(in_capacitance_sampling_frequency_available, + "91 84 50 26 16 13 11 9"); + +static struct attribute *ad7746_attributes[] = { + &iio_dev_attr_in_capacitance_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_voltage_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_capacitance0_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance0_calibscale_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibscale_calibration.dev_attr.attr, + &iio_dev_attr_in_capacitance1_calibbias_calibration.dev_attr.attr, + &iio_dev_attr_in_voltage0_calibscale_calibration.dev_attr.attr, + &iio_const_attr_in_voltage_sampling_frequency_available.dev_attr.attr, + &iio_const_attr_in_capacitance_sampling_frequency_available. + dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad7746_attribute_group = { + .attrs = ad7746_attributes, +}; + +static int ad7746_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, reg; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case IIO_CHAN_INFO_CALIBSCALE: + if (val != 1) { + ret = -EINVAL; + goto out; + } + + val = (val2 * 1024) / 15625; + + switch (chan->type) { + case IIO_CAPACITANCE: + reg = AD7746_REG_CAP_GAINH; + break; + case IIO_VOLTAGE: + reg = AD7746_REG_VOLT_GAINH; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = i2c_smbus_write_word_data(chip->client, reg, swab16(val)); + if (ret < 0) + goto out; + + ret = 0; + break; + case IIO_CHAN_INFO_CALIBBIAS: + if ((val < 0) | (val > 0xFFFF)) { + ret = -EINVAL; + goto out; + } + ret = i2c_smbus_write_word_data(chip->client, + AD7746_REG_CAP_OFFH, swab16(val)); + if (ret < 0) + goto out; + + ret = 0; + break; + case IIO_CHAN_INFO_OFFSET: + if ((val < 0) | (val > 43008000)) { /* 21pF */ + ret = -EINVAL; + goto out; + } + + /* CAPDAC Scale = 21pF_typ / 127 + * CIN Scale = 8.192pF / 2^24 + * Offset Scale = CAPDAC Scale / CIN Scale = 338646 + * */ + + val /= 338646; + + chip->capdac[chan->channel][chan->differential] = (val > 0 ? + AD7746_CAPDAC_DACP(val) | AD7746_CAPDAC_DACEN : 0); + + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACA, + chip->capdac[chan->channel][0]); + if (ret < 0) + goto out; + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_CAPDACB, + chip->capdac[chan->channel][1]); + if (ret < 0) + goto out; + + chip->capdac_set = chan->channel; + + ret = 0; + break; + default: + ret = -EINVAL; + } + +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static int ad7746_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct ad7746_chip_info *chip = iio_priv(indio_dev); + int ret, delay; + u8 regval, reg; + + union { + u32 d32; + u8 d8[4]; + } data; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case 0: + ret = ad7746_select_channel(indio_dev, chan); + if (ret < 0) + goto out; + delay = ret; + + regval = chip->config | AD7746_CONF_MODE_SINGLE_CONV; + ret = i2c_smbus_write_byte_data(chip->client, AD7746_REG_CFG, + regval); + if (ret < 0) + goto out; + + msleep(delay); + /* Now read the actual register */ + + ret = i2c_smbus_read_i2c_block_data(chip->client, + chan->address >> 8, 3, &data.d8[1]); + + if (ret < 0) + goto out; + + *val = (be32_to_cpu(data.d32) & 0xFFFFFF) - 0x800000; + + switch (chan->type) { + case IIO_TEMP: + /* temperature in milli degrees Celsius + * T = ((*val / 2048) - 4096) * 1000 + */ + *val = (*val * 125) / 256; + break; + case IIO_VOLTAGE: + if (chan->channel == 1) /* supply_raw*/ + *val = *val * 6; + break; + default: + break; + } + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + switch (chan->type) { + case IIO_CAPACITANCE: + reg = AD7746_REG_CAP_GAINH; + break; + case IIO_VOLTAGE: + reg = AD7746_REG_VOLT_GAINH; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = i2c_smbus_read_word_data(chip->client, reg); + if (ret < 0) + goto out; + /* 1 + gain_val / 2^16 */ + *val = 1; + *val2 = (15625 * swab16(ret)) / 1024; + + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_CALIBBIAS: + ret = i2c_smbus_read_word_data(chip->client, + AD7746_REG_CAP_OFFH); + if (ret < 0) + goto out; + *val = swab16(ret); + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_OFFSET: + *val = AD7746_CAPDAC_DACP(chip->capdac[chan->channel] + [chan->differential]) * 338646; + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CAPACITANCE: + /* 8.192pf / 2^24 */ + *val2 = 488; + *val = 0; + break; + case IIO_VOLTAGE: + /* 1170mV / 2^23 */ + *val2 = 139475; + *val = 0; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = IIO_VAL_INT_PLUS_NANO; + break; + default: + ret = -EINVAL; + }; +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static const struct iio_info ad7746_info = { + .attrs = &ad7746_attribute_group, + .read_raw = &ad7746_read_raw, + .write_raw = &ad7746_write_raw, + .driver_module = THIS_MODULE, +}; + +/* + * device probe and remove + */ + +static int __devinit ad7746_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ad7746_platform_data *pdata = client->dev.platform_data; + struct ad7746_chip_info *chip; + struct iio_dev *indio_dev; + int ret = 0; + unsigned char regval = 0; + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + chip = iio_priv(indio_dev); + /* this is only used for device removal purposes */ + i2c_set_clientdata(client, indio_dev); + + chip->client = client; + chip->capdac_set = -1; + + /* Establish that the iio_dev is a child of the i2c device */ + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ad7746_info; + indio_dev->channels = ad7746_channels; + if (id->driver_data == 7746) + indio_dev->num_channels = ARRAY_SIZE(ad7746_channels); + else + indio_dev->num_channels = ARRAY_SIZE(ad7746_channels) - 2; + indio_dev->num_channels = ARRAY_SIZE(ad7746_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + if (pdata) { + if (pdata->exca_en) { + if (pdata->exca_inv_en) + regval |= AD7746_EXCSETUP_NEXCA; + else + regval |= AD7746_EXCSETUP_EXCA; + } + + if (pdata->excb_en) { + if (pdata->excb_inv_en) + regval |= AD7746_EXCSETUP_NEXCB; + else + regval |= AD7746_EXCSETUP_EXCB; + } + + regval |= AD7746_EXCSETUP_EXCLVL(pdata->exclvl); + } else { + dev_warn(&client->dev, "No platform data? using default\n"); + regval = AD7746_EXCSETUP_EXCA | AD7746_EXCSETUP_EXCB | + AD7746_EXCSETUP_EXCLVL(3); + } + + ret = i2c_smbus_write_byte_data(chip->client, + AD7746_REG_EXC_SETUP, regval); + if (ret < 0) + goto error_free_dev; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + dev_info(&client->dev, "%s capacitive sensor registered\n", id->name); + + return 0; + +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit ad7746_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id ad7746_id[] = { + { "ad7745", 7745 }, + { "ad7746", 7746 }, + { "ad7747", 7747 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad7746_id); + +static struct i2c_driver ad7746_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ad7746_probe, + .remove = __devexit_p(ad7746_remove), + .id_table = ad7746_id, +}; +module_i2c_driver(ad7746_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD7746/5/7 capacitive sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/cdc/ad7746.h b/drivers/staging/iio/cdc/ad7746.h new file mode 100644 index 00000000..ea8572d1 --- /dev/null +++ b/drivers/staging/iio/cdc/ad7746.h @@ -0,0 +1,29 @@ +/* + * AD7746 capacitive sensor driver supporting AD7745, AD7746 and AD7747 + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_CDC_AD7746_H_ +#define IIO_CDC_AD7746_H_ + +/* + * TODO: struct ad7746_platform_data needs to go into include/linux/iio + */ + +#define AD7466_EXCLVL_0 0 /* +-VDD/8 */ +#define AD7466_EXCLVL_1 1 /* +-VDD/4 */ +#define AD7466_EXCLVL_2 2 /* +-VDD * 3/8 */ +#define AD7466_EXCLVL_3 3 /* +-VDD/2 */ + +struct ad7746_platform_data { + unsigned char exclvl; /*Excitation Voltage Level */ + bool exca_en; /* enables EXCA pin as the excitation output */ + bool exca_inv_en; /* enables /EXCA pin as the excitation output */ + bool excb_en; /* enables EXCB pin as the excitation output */ + bool excb_inv_en; /* enables /EXCB pin as the excitation output */ +}; + +#endif /* IIO_CDC_AD7746_H_ */ diff --git a/drivers/staging/iio/consumer.h b/drivers/staging/iio/consumer.h new file mode 100644 index 00000000..36a060cd --- /dev/null +++ b/drivers/staging/iio/consumer.h @@ -0,0 +1,96 @@ +/* + * Industrial I/O in kernel consumer interface + * + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#ifndef _IIO_INKERN_CONSUMER_H_ +#define _IIO_INKERN_CONSUMER_H +#include "types.h" + +struct iio_dev; +struct iio_chan_spec; + +/** + * struct iio_channel - everything needed for a consumer to use a channel + * @indio_dev: Device on which the channel exists. + * @channel: Full description of the channel. + */ +struct iio_channel { + struct iio_dev *indio_dev; + const struct iio_chan_spec *channel; +}; + +/** + * iio_channel_get() - get description of all that is needed to access channel. + * @name: Unique name of the device as provided in the iio_map + * with which the desired provider to consumer mapping + * was registered. + * @consumer_channel: Unique name to identify the channel on the consumer + * side. This typically describes the channels use within + * the consumer. E.g. 'battery_voltage' + */ +struct iio_channel *iio_st_channel_get(const char *name, + const char *consumer_channel); + +/** + * iio_st_channel_release() - release channels obtained via iio_st_channel_get + * @chan: The channel to be released. + */ +void iio_st_channel_release(struct iio_channel *chan); + +/** + * iio_st_channel_get_all() - get all channels associated with a client + * @name: name of consumer device. + * + * Returns an array of iio_channel structures terminated with one with + * null iio_dev pointer. + * This function is used by fairly generic consumers to get all the + * channels registered as having this consumer. + */ +struct iio_channel *iio_st_channel_get_all(const char *name); + +/** + * iio_st_channel_release_all() - reverse iio_st_get_all + * @chan: Array of channels to be released. + */ +void iio_st_channel_release_all(struct iio_channel *chan); + +/** + * iio_st_read_channel_raw() - read from a given channel + * @channel: The channel being queried. + * @val: Value read back. + * + * Note raw reads from iio channels are in adc counts and hence + * scale will need to be applied if standard units required. + */ +int iio_st_read_channel_raw(struct iio_channel *chan, + int *val); + +/** + * iio_st_get_channel_type() - get the type of a channel + * @channel: The channel being queried. + * @type: The type of the channel. + * + * returns the enum iio_chan_type of the channel + */ +int iio_st_get_channel_type(struct iio_channel *channel, + enum iio_chan_type *type); + +/** + * iio_st_read_channel_scale() - read the scale value for a channel + * @channel: The channel being queried. + * @val: First part of value read back. + * @val2: Second part of value read back. + * + * Note returns a description of what is in val and val2, such + * as IIO_VAL_INT_PLUS_MICRO telling us we have a value of val + * + val2/1e6 + */ +int iio_st_read_channel_scale(struct iio_channel *chan, int *val, + int *val2); + +#endif diff --git a/drivers/staging/iio/dac/Kconfig b/drivers/staging/iio/dac/Kconfig new file mode 100644 index 00000000..a57803a5 --- /dev/null +++ b/drivers/staging/iio/dac/Kconfig @@ -0,0 +1,121 @@ +# +# DAC drivers +# +menu "Digital to analog converters" + +config AD5064 + tristate "Analog Devices AD5064/64-1/65/44/45/24/25, AD5628/48/66/68 DAC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5024, AD5025, AD5044, + AD5045, AD5064, AD5064-1, AD5065, AD5628, AD5648, AD5666, AD5668 Digital + to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5064. + +config AD5360 + tristate "Analog Devices Analog Devices AD5360/61/62/63/70/71/73 DAC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5360, AD5361, + AD5362, AD5363, AD5370, AD5371, AD5373 multi-channel + Digital to Analog Converters (DAC). + + To compile this driver as module choose M here: the module will be called + ad5360. + +config AD5380 + tristate "Analog Devices AD5380/81/82/83/84/90/91/92 DAC driver" + depends on (SPI_MASTER || I2C) + select REGMAP_I2C if I2C + select REGMAP_SPI if SPI_MASTER + help + Say yes here to build support for Analog Devices AD5380, AD5381, + AD5382, AD5383, AD5384, AD5390, AD5391, AD5392 multi-channel + Digital to Analog Converters (DAC). + + To compile this driver as module choose M here: the module will be called + ad5380. + +config AD5421 + tristate "Analog Devices AD5421 DAC driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5421 loop-powered + digital-to-analog convertors (DAC). + + To compile this driver as module choose M here: the module will be called + ad5421. + +config AD5624R_SPI + tristate "Analog Devices AD5624/44/64R DAC spi driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5624R, AD5644R and + AD5664R converters (DAC). This driver uses the common SPI interface. + +config AD5446 + tristate "Analog Devices AD5444/6, AD5620/40/60 and AD5542A/12A DAC SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5444, AD5446, + AD5512A, AD5542A, AD5543, AD5553, AD5601, AD5611, AD5620, AD5621, + AD5640, AD5660, AD5662 DACs. + + To compile this driver as a module, choose M here: the + module will be called ad5446. + +config AD5504 + tristate "Analog Devices AD5504/AD5501 DAC SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5504, AD5501, + High Voltage Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5504. + +config AD5764 + tristate "Analog Devices AD5764/64R/44/44R DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5764, AD5764R, AD5744, + AD5744R Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5764. + +config AD5791 + tristate "Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5760, AD5780, + AD5781, AD5790, AD5791 High Resolution Voltage Output Digital to + Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5791. + +config AD5686 + tristate "Analog Devices AD5686R/AD5685R/AD5684R DAC SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices AD5686R, AD5685R, + AD5684R, AD5791 Voltage Output Digital to + Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5686. + +config MAX517 + tristate "Maxim MAX517/518/519 DAC driver" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the Maxim chips MAX517, + MAX518 and MAX519 (I2C 8-Bit DACs with rail-to-rail outputs). + + This driver can also be built as a module. If so, the module + will be called max517. + +endmenu diff --git a/drivers/staging/iio/dac/Makefile b/drivers/staging/iio/dac/Makefile new file mode 100644 index 00000000..8ab1d264 --- /dev/null +++ b/drivers/staging/iio/dac/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for industrial I/O DAC drivers +# + +obj-$(CONFIG_AD5360) += ad5360.o +obj-$(CONFIG_AD5380) += ad5380.o +obj-$(CONFIG_AD5421) += ad5421.o +obj-$(CONFIG_AD5624R_SPI) += ad5624r_spi.o +obj-$(CONFIG_AD5064) += ad5064.o +obj-$(CONFIG_AD5504) += ad5504.o +obj-$(CONFIG_AD5446) += ad5446.o +obj-$(CONFIG_AD5764) += ad5764.o +obj-$(CONFIG_AD5791) += ad5791.o +obj-$(CONFIG_AD5686) += ad5686.o +obj-$(CONFIG_MAX517) += max517.o diff --git a/drivers/staging/iio/dac/ad5064.c b/drivers/staging/iio/dac/ad5064.c new file mode 100644 index 00000000..06b16274 --- /dev/null +++ b/drivers/staging/iio/dac/ad5064.c @@ -0,0 +1,559 @@ +/* + * AD5024, AD5025, AD5044, AD5045, AD5064, AD5064-1, AD5065, AD5628, AD5648, + * AD5666, AD5668 Digital to analog converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dac.h" + +#define AD5064_MAX_DAC_CHANNELS 8 +#define AD5064_MAX_VREFS 4 + +#define AD5064_ADDR(x) ((x) << 20) +#define AD5064_CMD(x) ((x) << 24) + +#define AD5064_ADDR_DAC(chan) (chan) +#define AD5064_ADDR_ALL_DAC 0xF + +#define AD5064_CMD_WRITE_INPUT_N 0x0 +#define AD5064_CMD_UPDATE_DAC_N 0x1 +#define AD5064_CMD_WRITE_INPUT_N_UPDATE_ALL 0x2 +#define AD5064_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define AD5064_CMD_POWERDOWN_DAC 0x4 +#define AD5064_CMD_CLEAR 0x5 +#define AD5064_CMD_LDAC_MASK 0x6 +#define AD5064_CMD_RESET 0x7 +#define AD5064_CMD_CONFIG 0x8 + +#define AD5064_CONFIG_DAISY_CHAIN_ENABLE BIT(1) +#define AD5064_CONFIG_INT_VREF_ENABLE BIT(0) + +#define AD5064_LDAC_PWRDN_NONE 0x0 +#define AD5064_LDAC_PWRDN_1K 0x1 +#define AD5064_LDAC_PWRDN_100K 0x2 +#define AD5064_LDAC_PWRDN_3STATE 0x3 + +/** + * struct ad5064_chip_info - chip specific information + * @shared_vref: whether the vref supply is shared between channels + * @internal_vref: internal reference voltage. 0 if the chip has no internal + * vref. + * @channel: channel specification + * @num_channels: number of channels + */ + +struct ad5064_chip_info { + bool shared_vref; + unsigned long internal_vref; + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +/** + * struct ad5064_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulators + * @pwr_down: whether channel is powered down + * @pwr_down_mode: channel's current power down mode + * @dac_cache: current DAC raw value (chip does not support readback) + * @use_internal_vref: set to true if the internal reference voltage should be + * used. + * @data: spi transfer buffers + */ + +struct ad5064_state { + struct spi_device *spi; + const struct ad5064_chip_info *chip_info; + struct regulator_bulk_data vref_reg[AD5064_MAX_VREFS]; + bool pwr_down[AD5064_MAX_DAC_CHANNELS]; + u8 pwr_down_mode[AD5064_MAX_DAC_CHANNELS]; + unsigned int dac_cache[AD5064_MAX_DAC_CHANNELS]; + bool use_internal_vref; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + __be32 data ____cacheline_aligned; +}; + +enum ad5064_type { + ID_AD5024, + ID_AD5025, + ID_AD5044, + ID_AD5045, + ID_AD5064, + ID_AD5064_1, + ID_AD5065, + ID_AD5628_1, + ID_AD5628_2, + ID_AD5648_1, + ID_AD5648_2, + ID_AD5666_1, + ID_AD5666_2, + ID_AD5668_1, + ID_AD5668_2, +}; + +static int ad5064_spi_write(struct ad5064_state *st, unsigned int cmd, + unsigned int addr, unsigned int val, unsigned int shift) +{ + val <<= shift; + + st->data = cpu_to_be32(AD5064_CMD(cmd) | AD5064_ADDR(addr) | val); + + return spi_write(st->spi, &st->data, sizeof(st->data)); +} + +static int ad5064_sync_powerdown_mode(struct ad5064_state *st, + unsigned int channel) +{ + unsigned int val; + int ret; + + val = (0x1 << channel); + + if (st->pwr_down[channel]) + val |= st->pwr_down_mode[channel] << 8; + + ret = ad5064_spi_write(st, AD5064_CMD_POWERDOWN_DAC, 0, val, 0); + + return ret; +} + +static const char ad5064_powerdown_modes[][15] = { + [AD5064_LDAC_PWRDN_NONE] = "", + [AD5064_LDAC_PWRDN_1K] = "1kohm_to_gnd", + [AD5064_LDAC_PWRDN_100K] = "100kohm_to_gnd", + [AD5064_LDAC_PWRDN_3STATE] = "three_state", +}; + +static ssize_t ad5064_read_powerdown_mode_available(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, char *buf) +{ + return sprintf(buf, "%s %s %s\n", ad5064_powerdown_modes[1], + ad5064_powerdown_modes[2], ad5064_powerdown_modes[3]); +} + +static ssize_t ad5064_read_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, char *buf) +{ + struct ad5064_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%s\n", + ad5064_powerdown_modes[st->pwr_down_mode[chan->channel]]); +} + +static ssize_t ad5064_write_powerdown_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, const char *buf, size_t len) +{ + struct ad5064_state *st = iio_priv(indio_dev); + unsigned int mode, i; + int ret; + + mode = 0; + + for (i = 1; i < ARRAY_SIZE(ad5064_powerdown_modes); ++i) { + if (sysfs_streq(buf, ad5064_powerdown_modes[i])) { + mode = i; + break; + } + } + if (mode == 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + st->pwr_down_mode[chan->channel] = mode; + + ret = ad5064_sync_powerdown_mode(st, chan->channel); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static ssize_t ad5064_read_dac_powerdown(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, char *buf) +{ + struct ad5064_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->pwr_down[chan->channel]); +} + +static ssize_t ad5064_write_dac_powerdown(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, const char *buf, size_t len) +{ + struct ad5064_state *st = iio_priv(indio_dev); + bool pwr_down; + int ret; + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + st->pwr_down[chan->channel] = pwr_down; + + ret = ad5064_sync_powerdown_mode(st, chan->channel); + mutex_unlock(&indio_dev->mlock); + return ret ? ret : len; +} + +static int ad5064_get_vref(struct ad5064_state *st, + struct iio_chan_spec const *chan) +{ + unsigned int i; + + if (st->use_internal_vref) + return st->chip_info->internal_vref; + + i = st->chip_info->shared_vref ? 0 : chan->channel; + return regulator_get_voltage(st->vref_reg[i].consumer); +} + +static int ad5064_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5064_state *st = iio_priv(indio_dev); + int scale_uv; + + switch (m) { + case 0: + *val = st->dac_cache[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = ad5064_get_vref(st, chan); + if (scale_uv < 0) + return scale_uv; + + scale_uv = (scale_uv * 100) >> chan->scan_type.realbits; + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + return -EINVAL; +} + +static int ad5064_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct ad5064_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case 0: + if (val > (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad5064_spi_write(st, AD5064_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, val, chan->scan_type.shift); + if (ret == 0) + st->dac_cache[chan->channel] = val; + mutex_unlock(&indio_dev->mlock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad5064_info = { + .read_raw = ad5064_read_raw, + .write_raw = ad5064_write_raw, + .driver_module = THIS_MODULE, +}; + +static struct iio_chan_spec_ext_info ad5064_ext_info[] = { + { + .name = "powerdown", + .read = ad5064_read_dac_powerdown, + .write = ad5064_write_dac_powerdown, + }, + { + .name = "powerdown_mode", + .read = ad5064_read_powerdown_mode, + .write = ad5064_write_powerdown_mode, + }, + { + .name = "powerdown_mode_available", + .shared = true, + .read = ad5064_read_powerdown_mode_available, + }, + { }, +}; + +#define AD5064_CHANNEL(chan, bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (chan), \ + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + .address = AD5064_ADDR_DAC(chan), \ + .scan_type = IIO_ST('u', (bits), 16, 20 - (bits)), \ + .ext_info = ad5064_ext_info, \ +} + +#define DECLARE_AD5064_CHANNELS(name, bits) \ +const struct iio_chan_spec name[] = { \ + AD5064_CHANNEL(0, bits), \ + AD5064_CHANNEL(1, bits), \ + AD5064_CHANNEL(2, bits), \ + AD5064_CHANNEL(3, bits), \ + AD5064_CHANNEL(4, bits), \ + AD5064_CHANNEL(5, bits), \ + AD5064_CHANNEL(6, bits), \ + AD5064_CHANNEL(7, bits), \ +} + +static DECLARE_AD5064_CHANNELS(ad5024_channels, 12); +static DECLARE_AD5064_CHANNELS(ad5044_channels, 14); +static DECLARE_AD5064_CHANNELS(ad5064_channels, 16); + +static const struct ad5064_chip_info ad5064_chip_info_tbl[] = { + [ID_AD5024] = { + .shared_vref = false, + .channels = ad5024_channels, + .num_channels = 4, + }, + [ID_AD5025] = { + .shared_vref = false, + .channels = ad5024_channels, + .num_channels = 2, + }, + [ID_AD5044] = { + .shared_vref = false, + .channels = ad5044_channels, + .num_channels = 4, + }, + [ID_AD5045] = { + .shared_vref = false, + .channels = ad5044_channels, + .num_channels = 2, + }, + [ID_AD5064] = { + .shared_vref = false, + .channels = ad5064_channels, + .num_channels = 4, + }, + [ID_AD5064_1] = { + .shared_vref = true, + .channels = ad5064_channels, + .num_channels = 4, + }, + [ID_AD5065] = { + .shared_vref = false, + .channels = ad5064_channels, + .num_channels = 2, + }, + [ID_AD5628_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5024_channels, + .num_channels = 8, + }, + [ID_AD5628_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5024_channels, + .num_channels = 8, + }, + [ID_AD5648_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5044_channels, + .num_channels = 8, + }, + [ID_AD5648_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5044_channels, + .num_channels = 8, + }, + [ID_AD5666_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5064_channels, + .num_channels = 4, + }, + [ID_AD5666_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5064_channels, + .num_channels = 4, + }, + [ID_AD5668_1] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5064_channels, + .num_channels = 8, + }, + [ID_AD5668_2] = { + .shared_vref = true, + .internal_vref = 5000000, + .channels = ad5064_channels, + .num_channels = 8, + }, +}; + +static inline unsigned int ad5064_num_vref(struct ad5064_state *st) +{ + return st->chip_info->shared_vref ? 1 : st->chip_info->num_channels; +} + +static const char * const ad5064_vref_names[] = { + "vrefA", + "vrefB", + "vrefC", + "vrefD", +}; + +static const char * const ad5064_vref_name(struct ad5064_state *st, + unsigned int vref) +{ + return st->chip_info->shared_vref ? "vref" : ad5064_vref_names[vref]; +} + +static int __devinit ad5064_probe(struct spi_device *spi) +{ + enum ad5064_type type = spi_get_device_id(spi)->driver_data; + struct iio_dev *indio_dev; + struct ad5064_state *st; + unsigned int i; + int ret; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->chip_info = &ad5064_chip_info_tbl[type]; + st->spi = spi; + + for (i = 0; i < ad5064_num_vref(st); ++i) + st->vref_reg[i].supply = ad5064_vref_name(st, i); + + ret = regulator_bulk_get(&st->spi->dev, ad5064_num_vref(st), + st->vref_reg); + if (ret) { + if (!st->chip_info->internal_vref) + goto error_free; + st->use_internal_vref = true; + ret = ad5064_spi_write(st, AD5064_CMD_CONFIG, 0, + AD5064_CONFIG_INT_VREF_ENABLE, 0); + if (ret) { + dev_err(&spi->dev, "Failed to enable internal vref: %d\n", + ret); + goto error_free; + } + } else { + ret = regulator_bulk_enable(ad5064_num_vref(st), st->vref_reg); + if (ret) + goto error_free_reg; + } + + for (i = 0; i < st->chip_info->num_channels; ++i) { + st->pwr_down_mode[i] = AD5064_LDAC_PWRDN_1K; + st->dac_cache[i] = 0x8000; + } + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5064_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!st->use_internal_vref) + regulator_bulk_disable(ad5064_num_vref(st), st->vref_reg); +error_free_reg: + if (!st->use_internal_vref) + regulator_bulk_free(ad5064_num_vref(st), st->vref_reg); +error_free: + iio_free_device(indio_dev); + + return ret; +} + + +static int __devexit ad5064_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5064_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (!st->use_internal_vref) { + regulator_bulk_disable(ad5064_num_vref(st), st->vref_reg); + regulator_bulk_free(ad5064_num_vref(st), st->vref_reg); + } + + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad5064_id[] = { + {"ad5024", ID_AD5024}, + {"ad5025", ID_AD5025}, + {"ad5044", ID_AD5044}, + {"ad5045", ID_AD5045}, + {"ad5064", ID_AD5064}, + {"ad5064-1", ID_AD5064_1}, + {"ad5065", ID_AD5065}, + {"ad5628-1", ID_AD5628_1}, + {"ad5628-2", ID_AD5628_2}, + {"ad5648-1", ID_AD5648_1}, + {"ad5648-2", ID_AD5648_2}, + {"ad5666-1", ID_AD5666_1}, + {"ad5666-2", ID_AD5666_2}, + {"ad5668-1", ID_AD5668_1}, + {"ad5668-2", ID_AD5668_2}, + {"ad5668-3", ID_AD5668_2}, /* similar enough to ad5668-2 */ + {} +}; +MODULE_DEVICE_TABLE(spi, ad5064_id); + +static struct spi_driver ad5064_driver = { + .driver = { + .name = "ad5064", + .owner = THIS_MODULE, + }, + .probe = ad5064_probe, + .remove = __devexit_p(ad5064_remove), + .id_table = ad5064_id, +}; +module_spi_driver(ad5064_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5024/25/44/45/64/64-1/65, AD5628/48/66/68 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dac/ad5360.c b/drivers/staging/iio/dac/ad5360.c new file mode 100644 index 00000000..cec3693b --- /dev/null +++ b/drivers/staging/iio/dac/ad5360.c @@ -0,0 +1,570 @@ +/* + * Analog devices AD5360, AD5361, AD5362, AD5363, AD5370, AD5371, AD5373 + * multi-channel Digital to Analog Converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dac.h" + +#define AD5360_CMD(x) ((x) << 22) +#define AD5360_ADDR(x) ((x) << 16) + +#define AD5360_READBACK_TYPE(x) ((x) << 13) +#define AD5360_READBACK_ADDR(x) ((x) << 7) + +#define AD5360_CHAN_ADDR(chan) ((chan) + 0x8) + +#define AD5360_CMD_WRITE_DATA 0x3 +#define AD5360_CMD_WRITE_OFFSET 0x2 +#define AD5360_CMD_WRITE_GAIN 0x1 +#define AD5360_CMD_SPECIAL_FUNCTION 0x0 + +/* Special function register addresses */ +#define AD5360_REG_SF_NOP 0x0 +#define AD5360_REG_SF_CTRL 0x1 +#define AD5360_REG_SF_OFS(x) (0x2 + (x)) +#define AD5360_REG_SF_READBACK 0x5 + +#define AD5360_SF_CTRL_PWR_DOWN BIT(0) + +#define AD5360_READBACK_X1A 0x0 +#define AD5360_READBACK_X1B 0x1 +#define AD5360_READBACK_OFFSET 0x2 +#define AD5360_READBACK_GAIN 0x3 +#define AD5360_READBACK_SF 0x4 + + +/** + * struct ad5360_chip_info - chip specific information + * @channel_template: channel specification template + * @num_channels: number of channels + * @channels_per_group: number of channels per group + * @num_vrefs: number of vref supplies for the chip +*/ + +struct ad5360_chip_info { + struct iio_chan_spec channel_template; + unsigned int num_channels; + unsigned int channels_per_group; + unsigned int num_vrefs; +}; + +/** + * struct ad5360_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulators + * @ctrl: control register cache + * @data: spi transfer buffers + */ + +struct ad5360_state { + struct spi_device *spi; + const struct ad5360_chip_info *chip_info; + struct regulator_bulk_data vref_reg[3]; + unsigned int ctrl; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +enum ad5360_type { + ID_AD5360, + ID_AD5361, + ID_AD5362, + ID_AD5363, + ID_AD5370, + ID_AD5371, + ID_AD5372, + ID_AD5373, +}; + +#define AD5360_CHANNEL(bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT | \ + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, \ + .scan_type = IIO_ST('u', (bits), 16, 16 - (bits)) \ +} + +static const struct ad5360_chip_info ad5360_chip_info_tbl[] = { + [ID_AD5360] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 16, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5361] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 16, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5362] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 8, + .channels_per_group = 4, + .num_vrefs = 2, + }, + [ID_AD5363] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 8, + .channels_per_group = 4, + .num_vrefs = 2, + }, + [ID_AD5370] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 40, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5371] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 40, + .channels_per_group = 8, + .num_vrefs = 3, + }, + [ID_AD5372] = { + .channel_template = AD5360_CHANNEL(16), + .num_channels = 32, + .channels_per_group = 8, + .num_vrefs = 2, + }, + [ID_AD5373] = { + .channel_template = AD5360_CHANNEL(14), + .num_channels = 32, + .channels_per_group = 8, + .num_vrefs = 2, + }, +}; + +static unsigned int ad5360_get_channel_vref_index(struct ad5360_state *st, + unsigned int channel) +{ + unsigned int i; + + /* The first groups have their own vref, while the remaining groups + * share the last vref */ + i = channel / st->chip_info->channels_per_group; + if (i >= st->chip_info->num_vrefs) + i = st->chip_info->num_vrefs - 1; + + return i; +} + +static int ad5360_get_channel_vref(struct ad5360_state *st, + unsigned int channel) +{ + unsigned int i = ad5360_get_channel_vref_index(st, channel); + + return regulator_get_voltage(st->vref_reg[i].consumer); +} + + +static int ad5360_write_unlocked(struct iio_dev *indio_dev, + unsigned int cmd, unsigned int addr, unsigned int val, + unsigned int shift) +{ + struct ad5360_state *st = iio_priv(indio_dev); + + val <<= shift; + val |= AD5360_CMD(cmd) | AD5360_ADDR(addr); + st->data[0].d32 = cpu_to_be32(val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5360_write(struct iio_dev *indio_dev, unsigned int cmd, + unsigned int addr, unsigned int val, unsigned int shift) +{ + int ret; + + mutex_lock(&indio_dev->mlock); + ret = ad5360_write_unlocked(indio_dev, cmd, addr, val, shift); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5360_read(struct iio_dev *indio_dev, unsigned int type, + unsigned int addr) +{ + struct ad5360_state *st = iio_priv(indio_dev); + struct spi_message m; + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + mutex_lock(&indio_dev->mlock); + + st->data[0].d32 = cpu_to_be32(AD5360_CMD(AD5360_CMD_SPECIAL_FUNCTION) | + AD5360_ADDR(AD5360_REG_SF_READBACK) | + AD5360_READBACK_TYPE(type) | + AD5360_READBACK_ADDR(addr)); + + ret = spi_sync(st->spi, &m); + if (ret >= 0) + ret = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static ssize_t ad5360_read_dac_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5360_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", (bool)(st->ctrl & AD5360_SF_CTRL_PWR_DOWN)); +} + +static int ad5360_update_ctrl(struct iio_dev *indio_dev, unsigned int set, + unsigned int clr) +{ + struct ad5360_state *st = iio_priv(indio_dev); + unsigned int ret; + + mutex_lock(&indio_dev->mlock); + + st->ctrl |= set; + st->ctrl &= ~clr; + + ret = ad5360_write_unlocked(indio_dev, AD5360_CMD_SPECIAL_FUNCTION, + AD5360_REG_SF_CTRL, st->ctrl, 0); + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static ssize_t ad5360_write_dac_powerdown(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + bool pwr_down; + int ret; + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + if (pwr_down) + ret = ad5360_update_ctrl(indio_dev, AD5360_SF_CTRL_PWR_DOWN, 0); + else + ret = ad5360_update_ctrl(indio_dev, 0, AD5360_SF_CTRL_PWR_DOWN); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage_powerdown, + S_IRUGO | S_IWUSR, + ad5360_read_dac_powerdown, + ad5360_write_dac_powerdown, 0); + +static struct attribute *ad5360_attributes[] = { + &iio_dev_attr_out_voltage_powerdown.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5360_attribute_group = { + .attrs = ad5360_attributes, +}; + +static int ad5360_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5360_state *st = iio_priv(indio_dev); + int max_val = (1 << chan->scan_type.realbits); + unsigned int ofs_index; + + switch (mask) { + case 0: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5360_write(indio_dev, AD5360_CMD_WRITE_DATA, + chan->address, val, chan->scan_type.shift); + + case IIO_CHAN_INFO_CALIBBIAS: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5360_write(indio_dev, AD5360_CMD_WRITE_OFFSET, + chan->address, val, chan->scan_type.shift); + + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5360_write(indio_dev, AD5360_CMD_WRITE_GAIN, + chan->address, val, chan->scan_type.shift); + + case IIO_CHAN_INFO_OFFSET: + if (val <= -max_val || val > 0) + return -EINVAL; + + val = -val; + + /* offset is supposed to have the same scale as raw, but it + * is always 14bits wide, so on a chip where the raw value has + * more bits, we need to shift offset. */ + val >>= (chan->scan_type.realbits - 14); + + /* There is one DAC offset register per vref. Changing one + * channels offset will also change the offset for all other + * channels which share the same vref supply. */ + ofs_index = ad5360_get_channel_vref_index(st, chan->channel); + return ad5360_write(indio_dev, AD5360_CMD_SPECIAL_FUNCTION, + AD5360_REG_SF_OFS(ofs_index), val, 0); + default: + break; + } + + return -EINVAL; +} + +static int ad5360_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5360_state *st = iio_priv(indio_dev); + unsigned int ofs_index; + int scale_uv; + int ret; + + switch (m) { + case 0: + ret = ad5360_read(indio_dev, AD5360_READBACK_X1A, + chan->address); + if (ret < 0) + return ret; + *val = ret >> chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* vout = 4 * vref * dac_code */ + scale_uv = ad5360_get_channel_vref(st, chan->channel) * 4 * 100; + if (scale_uv < 0) + return scale_uv; + + scale_uv >>= (chan->scan_type.realbits); + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + ret = ad5360_read(indio_dev, AD5360_READBACK_OFFSET, + chan->address); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ret = ad5360_read(indio_dev, AD5360_READBACK_GAIN, + chan->address); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + ofs_index = ad5360_get_channel_vref_index(st, chan->channel); + ret = ad5360_read(indio_dev, AD5360_READBACK_SF, + AD5360_REG_SF_OFS(ofs_index)); + if (ret < 0) + return ret; + + ret <<= (chan->scan_type.realbits - 14); + *val = -ret; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info ad5360_info = { + .read_raw = ad5360_read_raw, + .write_raw = ad5360_write_raw, + .attrs = &ad5360_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const char * const ad5360_vref_name[] = { + "vref0", "vref1", "vref2" +}; + +static int __devinit ad5360_alloc_channels(struct iio_dev *indio_dev) +{ + struct ad5360_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels; + unsigned int i; + + channels = kcalloc(st->chip_info->num_channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + + if (!channels) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_channels; ++i) { + channels[i] = st->chip_info->channel_template; + channels[i].channel = i; + channels[i].address = AD5360_CHAN_ADDR(i); + } + + indio_dev->channels = channels; + + return 0; +} + +static int __devinit ad5360_probe(struct spi_device *spi) +{ + enum ad5360_type type = spi_get_device_id(spi)->driver_data; + struct iio_dev *indio_dev; + struct ad5360_state *st; + unsigned int i; + int ret; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->chip_info = &ad5360_chip_info_tbl[type]; + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5360_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = st->chip_info->num_channels; + + ret = ad5360_alloc_channels(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to allocate channel spec: %d\n", ret); + goto error_free; + } + + for (i = 0; i < st->chip_info->num_vrefs; ++i) + st->vref_reg[i].supply = ad5360_vref_name[i]; + + ret = regulator_bulk_get(&st->spi->dev, st->chip_info->num_vrefs, + st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to request vref regulators: %d\n", ret); + goto error_free_channels; + } + + ret = regulator_bulk_enable(st->chip_info->num_vrefs, st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to enable vref regulators: %d\n", ret); + goto error_free_reg; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + regulator_bulk_disable(st->chip_info->num_vrefs, st->vref_reg); +error_free_reg: + regulator_bulk_free(st->chip_info->num_vrefs, st->vref_reg); +error_free_channels: + kfree(indio_dev->channels); +error_free: + iio_free_device(indio_dev); + + return ret; +} + +static int __devexit ad5360_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5360_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + kfree(indio_dev->channels); + + regulator_bulk_disable(st->chip_info->num_vrefs, st->vref_reg); + regulator_bulk_free(st->chip_info->num_vrefs, st->vref_reg); + + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad5360_ids[] = { + { "ad5360", ID_AD5360 }, + { "ad5361", ID_AD5361 }, + { "ad5362", ID_AD5362 }, + { "ad5363", ID_AD5363 }, + { "ad5370", ID_AD5370 }, + { "ad5371", ID_AD5371 }, + { "ad5372", ID_AD5372 }, + { "ad5373", ID_AD5373 }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5360_ids); + +static struct spi_driver ad5360_driver = { + .driver = { + .name = "ad5360", + .owner = THIS_MODULE, + }, + .probe = ad5360_probe, + .remove = __devexit_p(ad5360_remove), + .id_table = ad5360_ids, +}; +module_spi_driver(ad5360_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5360/61/62/63/70/71/72/73 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dac/ad5380.c b/drivers/staging/iio/dac/ad5380.c new file mode 100644 index 00000000..4c50716f --- /dev/null +++ b/drivers/staging/iio/dac/ad5380.c @@ -0,0 +1,676 @@ +/* + * Analog devices AD5380, AD5381, AD5382, AD5383, AD5390, AD5391, AD5392 + * multi-channel Digital to Analog Converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dac.h" + + +#define AD5380_REG_DATA(x) (((x) << 2) | 3) +#define AD5380_REG_OFFSET(x) (((x) << 2) | 2) +#define AD5380_REG_GAIN(x) (((x) << 2) | 1) +#define AD5380_REG_SF_PWR_DOWN (8 << 2) +#define AD5380_REG_SF_PWR_UP (9 << 2) +#define AD5380_REG_SF_CTRL (12 << 2) + +#define AD5380_CTRL_PWR_DOWN_MODE_OFFSET 13 +#define AD5380_CTRL_INT_VREF_2V5 BIT(12) +#define AD5380_CTRL_INT_VREF_EN BIT(10) + +/** + * struct ad5380_chip_info - chip specific information + * @channel_template: channel specification template + * @num_channels: number of channels + * @int_vref: internal vref in uV +*/ + +struct ad5380_chip_info { + struct iio_chan_spec channel_template; + unsigned int num_channels; + unsigned int int_vref; +}; + +/** + * struct ad5380_state - driver instance specific data + * @regmap: regmap instance used by the device + * @chip_info: chip model specific constants, available modes etc + * @vref_reg: vref supply regulator + * @vref: actual reference voltage used in uA + * @pwr_down: whether the chip is currently in power down mode + */ + +struct ad5380_state { + struct regmap *regmap; + const struct ad5380_chip_info *chip_info; + struct regulator *vref_reg; + int vref; + bool pwr_down; +}; + +enum ad5380_type { + ID_AD5380_3, + ID_AD5380_5, + ID_AD5381_3, + ID_AD5381_5, + ID_AD5382_3, + ID_AD5382_5, + ID_AD5383_3, + ID_AD5383_5, + ID_AD5390_3, + ID_AD5390_5, + ID_AD5391_3, + ID_AD5391_5, + ID_AD5392_3, + ID_AD5392_5, +}; + +#define AD5380_CHANNEL(_bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, \ + .scan_type = IIO_ST('u', (_bits), 16, 14 - (_bits)) \ +} + +static const struct ad5380_chip_info ad5380_chip_info_tbl[] = { + [ID_AD5380_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 40, + .int_vref = 1250000, + }, + [ID_AD5380_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 40, + .int_vref = 2500000, + }, + [ID_AD5381_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 1250000, + }, + [ID_AD5381_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 2500000, + }, + [ID_AD5382_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 32, + .int_vref = 1250000, + }, + [ID_AD5382_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 32, + .int_vref = 2500000, + }, + [ID_AD5383_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 32, + .int_vref = 1250000, + }, + [ID_AD5383_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 32, + .int_vref = 2500000, + }, + [ID_AD5390_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 16, + .int_vref = 1250000, + }, + [ID_AD5390_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 16, + .int_vref = 2500000, + }, + [ID_AD5391_3] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 1250000, + }, + [ID_AD5391_5] = { + .channel_template = AD5380_CHANNEL(12), + .num_channels = 16, + .int_vref = 2500000, + }, + [ID_AD5392_3] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 8, + .int_vref = 1250000, + }, + [ID_AD5392_5] = { + .channel_template = AD5380_CHANNEL(14), + .num_channels = 8, + .int_vref = 2500000, + }, +}; + +static ssize_t ad5380_read_dac_powerdown(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5380_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5380_write_dac_powerdown(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5380_state *st = iio_priv(indio_dev); + bool pwr_down; + int ret; + + ret = strtobool(buf, &pwr_down); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + if (pwr_down) + ret = regmap_write(st->regmap, AD5380_REG_SF_PWR_DOWN, 0); + else + ret = regmap_write(st->regmap, AD5380_REG_SF_PWR_UP, 0); + + st->pwr_down = pwr_down; + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage_powerdown, + S_IRUGO | S_IWUSR, + ad5380_read_dac_powerdown, + ad5380_write_dac_powerdown, 0); + +static const char ad5380_powerdown_modes[][15] = { + [0] = "100kohm_to_gnd", + [1] = "three_state", +}; + +static ssize_t ad5380_read_powerdown_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5380_state *st = iio_priv(indio_dev); + unsigned int mode; + int ret; + + ret = regmap_read(st->regmap, AD5380_REG_SF_CTRL, &mode); + if (ret) + return ret; + + mode = (mode >> AD5380_CTRL_PWR_DOWN_MODE_OFFSET) & 1; + + return sprintf(buf, "%s\n", ad5380_powerdown_modes[mode]); +} + +static ssize_t ad5380_write_powerdown_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5380_state *st = iio_priv(indio_dev); + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(ad5380_powerdown_modes); ++i) { + if (sysfs_streq(buf, ad5380_powerdown_modes[i])) + break; + } + + if (i == ARRAY_SIZE(ad5380_powerdown_modes)) + return -EINVAL; + + ret = regmap_update_bits(st->regmap, AD5380_REG_SF_CTRL, + 1 << AD5380_CTRL_PWR_DOWN_MODE_OFFSET, + i << AD5380_CTRL_PWR_DOWN_MODE_OFFSET); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage_powerdown_mode, + S_IRUGO | S_IWUSR, + ad5380_read_powerdown_mode, + ad5380_write_powerdown_mode, 0); + +static IIO_CONST_ATTR(out_voltage_powerdown_mode_available, + "100kohm_to_gnd three_state"); + +static struct attribute *ad5380_attributes[] = { + &iio_dev_attr_out_voltage_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage_powerdown_mode.dev_attr.attr, + &iio_const_attr_out_voltage_powerdown_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5380_attribute_group = { + .attrs = ad5380_attributes, +}; + +static unsigned int ad5380_info_to_reg(struct iio_chan_spec const *chan, + long info) +{ + switch (info) { + case 0: + return AD5380_REG_DATA(chan->address); + case IIO_CHAN_INFO_CALIBBIAS: + return AD5380_REG_OFFSET(chan->address); + case IIO_CHAN_INFO_CALIBSCALE: + return AD5380_REG_GAIN(chan->address); + default: + break; + } + + return 0; +} + +static int ad5380_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + const unsigned int max_val = (1 << chan->scan_type.realbits); + struct ad5380_state *st = iio_priv(indio_dev); + + switch (info) { + case 0: + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= max_val || val < 0) + return -EINVAL; + + return regmap_write(st->regmap, + ad5380_info_to_reg(chan, info), + val << chan->scan_type.shift); + case IIO_CHAN_INFO_CALIBBIAS: + val += (1 << chan->scan_type.realbits) / 2; + if (val >= max_val || val < 0) + return -EINVAL; + + return regmap_write(st->regmap, + AD5380_REG_OFFSET(chan->address), + val << chan->scan_type.shift); + default: + break; + } + return -EINVAL; +} + +static int ad5380_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad5380_state *st = iio_priv(indio_dev); + unsigned long scale_uv; + int ret; + + switch (info) { + case 0: + case IIO_CHAN_INFO_CALIBSCALE: + ret = regmap_read(st->regmap, ad5380_info_to_reg(chan, info), + val); + if (ret) + return ret; + *val >>= chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + ret = regmap_read(st->regmap, AD5380_REG_OFFSET(chan->address), + val); + if (ret) + return ret; + *val >>= chan->scan_type.shift; + val -= (1 << chan->scan_type.realbits) / 2; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = ((2 * st->vref) >> chan->scan_type.realbits) * 100; + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info ad5380_info = { + .read_raw = ad5380_read_raw, + .write_raw = ad5380_write_raw, + .attrs = &ad5380_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad5380_alloc_channels(struct iio_dev *indio_dev) +{ + struct ad5380_state *st = iio_priv(indio_dev); + struct iio_chan_spec *channels; + unsigned int i; + + channels = kcalloc(st->chip_info->num_channels, + sizeof(struct iio_chan_spec), GFP_KERNEL); + + if (!channels) + return -ENOMEM; + + for (i = 0; i < st->chip_info->num_channels; ++i) { + channels[i] = st->chip_info->channel_template; + channels[i].channel = i; + channels[i].address = i; + } + + indio_dev->channels = channels; + + return 0; +} + +static int __devinit ad5380_probe(struct device *dev, struct regmap *regmap, + enum ad5380_type type, const char *name) +{ + struct iio_dev *indio_dev; + struct ad5380_state *st; + unsigned int ctrl = 0; + int ret; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + dev_err(dev, "Failed to allocate iio device\n"); + ret = -ENOMEM; + goto error_regmap_exit; + } + + st = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + + st->chip_info = &ad5380_chip_info_tbl[type]; + st->regmap = regmap; + + indio_dev->dev.parent = dev; + indio_dev->name = name; + indio_dev->info = &ad5380_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = st->chip_info->num_channels; + + ret = ad5380_alloc_channels(indio_dev); + if (ret) { + dev_err(dev, "Failed to allocate channel spec: %d\n", ret); + goto error_free; + } + + if (st->chip_info->int_vref == 2500000) + ctrl |= AD5380_CTRL_INT_VREF_2V5; + + st->vref_reg = regulator_get(dev, "vref"); + if (!IS_ERR(st->vref_reg)) { + ret = regulator_enable(st->vref_reg); + if (ret) { + dev_err(dev, "Failed to enable vref regulators: %d\n", + ret); + goto error_free_reg; + } + + st->vref = regulator_get_voltage(st->vref_reg); + } else { + st->vref = st->chip_info->int_vref; + ctrl |= AD5380_CTRL_INT_VREF_EN; + } + + ret = regmap_write(st->regmap, AD5380_REG_SF_CTRL, ctrl); + if (ret) { + dev_err(dev, "Failed to write to device: %d\n", ret); + goto error_disable_reg; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "Failed to register iio device: %d\n", ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + if (!IS_ERR(st->vref_reg)) + regulator_disable(st->vref_reg); +error_free_reg: + if (!IS_ERR(st->vref_reg)) + regulator_put(st->vref_reg); + + kfree(indio_dev->channels); +error_free: + iio_free_device(indio_dev); +error_regmap_exit: + regmap_exit(regmap); + + return ret; +} + +static int __devexit ad5380_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5380_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + kfree(indio_dev->channels); + + if (!IS_ERR(st->vref_reg)) { + regulator_disable(st->vref_reg); + regulator_put(st->vref_reg); + } + + regmap_exit(st->regmap); + iio_free_device(indio_dev); + + return 0; +} + +static bool ad5380_reg_false(struct device *dev, unsigned int reg) +{ + return false; +} + +static const struct regmap_config ad5380_regmap_config = { + .reg_bits = 10, + .val_bits = 14, + + .max_register = AD5380_REG_DATA(40), + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = ad5380_reg_false, + .readable_reg = ad5380_reg_false, +}; + +#if IS_ENABLED(CONFIG_SPI_MASTER) + +static int __devinit ad5380_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + + regmap = regmap_init_spi(spi, &ad5380_regmap_config); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return ad5380_probe(&spi->dev, regmap, id->driver_data, id->name); +} + +static int __devexit ad5380_spi_remove(struct spi_device *spi) +{ + return ad5380_remove(&spi->dev); +} + +static const struct spi_device_id ad5380_spi_ids[] = { + { "ad5380-3", ID_AD5380_3 }, + { "ad5380-5", ID_AD5380_5 }, + { "ad5381-3", ID_AD5381_3 }, + { "ad5381-5", ID_AD5381_5 }, + { "ad5382-3", ID_AD5382_3 }, + { "ad5382-5", ID_AD5382_5 }, + { "ad5383-3", ID_AD5383_3 }, + { "ad5383-5", ID_AD5383_5 }, + { "ad5384-3", ID_AD5380_3 }, + { "ad5384-5", ID_AD5380_5 }, + { "ad5390-3", ID_AD5390_3 }, + { "ad5390-5", ID_AD5390_5 }, + { "ad5391-3", ID_AD5391_3 }, + { "ad5391-5", ID_AD5391_5 }, + { "ad5392-3", ID_AD5392_3 }, + { "ad5392-5", ID_AD5392_5 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5380_spi_ids); + +static struct spi_driver ad5380_spi_driver = { + .driver = { + .name = "ad5380", + .owner = THIS_MODULE, + }, + .probe = ad5380_spi_probe, + .remove = __devexit_p(ad5380_spi_remove), + .id_table = ad5380_spi_ids, +}; + +static inline int ad5380_spi_register_driver(void) +{ + return spi_register_driver(&ad5380_spi_driver); +} + +static inline void ad5380_spi_unregister_driver(void) +{ + spi_unregister_driver(&ad5380_spi_driver); +} + +#else + +static inline int ad5380_spi_register_driver(void) +{ + return 0; +} + +static inline void ad5380_spi_unregister_driver(void) +{ +} + +#endif + +#if IS_ENABLED(CONFIG_I2C) + +static int __devinit ad5380_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + + regmap = regmap_init_i2c(i2c, &ad5380_regmap_config); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return ad5380_probe(&i2c->dev, regmap, id->driver_data, id->name); +} + +static int __devexit ad5380_i2c_remove(struct i2c_client *i2c) +{ + return ad5380_remove(&i2c->dev); +} + +static const struct i2c_device_id ad5380_i2c_ids[] = { + { "ad5380-3", ID_AD5380_3 }, + { "ad5380-5", ID_AD5380_5 }, + { "ad5381-3", ID_AD5381_3 }, + { "ad5381-5", ID_AD5381_5 }, + { "ad5382-3", ID_AD5382_3 }, + { "ad5382-5", ID_AD5382_5 }, + { "ad5383-3", ID_AD5383_3 }, + { "ad5383-5", ID_AD5383_5 }, + { "ad5384-3", ID_AD5380_3 }, + { "ad5384-5", ID_AD5380_5 }, + { "ad5390-3", ID_AD5390_3 }, + { "ad5390-5", ID_AD5390_5 }, + { "ad5391-3", ID_AD5391_3 }, + { "ad5391-5", ID_AD5391_5 }, + { "ad5392-3", ID_AD5392_3 }, + { "ad5392-5", ID_AD5392_5 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5380_i2c_ids); + +static struct i2c_driver ad5380_i2c_driver = { + .driver = { + .name = "ad5380", + .owner = THIS_MODULE, + }, + .probe = ad5380_i2c_probe, + .remove = __devexit_p(ad5380_i2c_remove), + .id_table = ad5380_i2c_ids, +}; + +static inline int ad5380_i2c_register_driver(void) +{ + return i2c_add_driver(&ad5380_i2c_driver); +} + +static inline void ad5380_i2c_unregister_driver(void) +{ + i2c_del_driver(&ad5380_i2c_driver); +} + +#else + +static inline int ad5380_i2c_register_driver(void) +{ + return 0; +} + +static inline void ad5380_i2c_unregister_driver(void) +{ +} + +#endif + +static int __init ad5380_spi_init(void) +{ + int ret; + + ret = ad5380_spi_register_driver(); + if (ret) + return ret; + + ret = ad5380_i2c_register_driver(); + if (ret) { + ad5380_spi_unregister_driver(); + return ret; + } + + return 0; +} +module_init(ad5380_spi_init); + +static void __exit ad5380_spi_exit(void) +{ + ad5380_i2c_unregister_driver(); + ad5380_spi_unregister_driver(); + +} +module_exit(ad5380_spi_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5380/81/82/83/84/90/91/92 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dac/ad5421.c b/drivers/staging/iio/dac/ad5421.c new file mode 100644 index 00000000..0b040b20 --- /dev/null +++ b/drivers/staging/iio/dac/ad5421.c @@ -0,0 +1,544 @@ +/* + * AD5421 Digital to analog converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" +#include "dac.h" +#include "ad5421.h" + + +#define AD5421_REG_DAC_DATA 0x1 +#define AD5421_REG_CTRL 0x2 +#define AD5421_REG_OFFSET 0x3 +#define AD5421_REG_GAIN 0x4 +/* load dac and fault shared the same register number. Writing to it will cause + * a dac load command, reading from it will return the fault status register */ +#define AD5421_REG_LOAD_DAC 0x5 +#define AD5421_REG_FAULT 0x5 +#define AD5421_REG_FORCE_ALARM_CURRENT 0x6 +#define AD5421_REG_RESET 0x7 +#define AD5421_REG_START_CONVERSION 0x8 +#define AD5421_REG_NOOP 0x9 + +#define AD5421_CTRL_WATCHDOG_DISABLE BIT(12) +#define AD5421_CTRL_AUTO_FAULT_READBACK BIT(11) +#define AD5421_CTRL_MIN_CURRENT BIT(9) +#define AD5421_CTRL_ADC_SOURCE_TEMP BIT(8) +#define AD5421_CTRL_ADC_ENABLE BIT(7) +#define AD5421_CTRL_PWR_DOWN_INT_VREF BIT(6) + +#define AD5421_FAULT_SPI BIT(15) +#define AD5421_FAULT_PEC BIT(14) +#define AD5421_FAULT_OVER_CURRENT BIT(13) +#define AD5421_FAULT_UNDER_CURRENT BIT(12) +#define AD5421_FAULT_TEMP_OVER_140 BIT(11) +#define AD5421_FAULT_TEMP_OVER_100 BIT(10) +#define AD5421_FAULT_UNDER_VOLTAGE_6V BIT(9) +#define AD5421_FAULT_UNDER_VOLTAGE_12V BIT(8) + +/* These bits will cause the fault pin to go high */ +#define AD5421_FAULT_TRIGGER_IRQ \ + (AD5421_FAULT_SPI | AD5421_FAULT_PEC | AD5421_FAULT_OVER_CURRENT | \ + AD5421_FAULT_UNDER_CURRENT | AD5421_FAULT_TEMP_OVER_140) + +/** + * struct ad5421_state - driver instance specific data + * @spi: spi_device + * @ctrl: control register cache + * @current_range: current range which the device is configured for + * @data: spi transfer buffers + * @fault_mask: software masking of events + */ +struct ad5421_state { + struct spi_device *spi; + unsigned int ctrl; + enum ad5421_current_range current_range; + unsigned int fault_mask; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + u32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +static const struct iio_chan_spec ad5421_channels[] = { + { + .type = IIO_CURRENT, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_OFFSET_SHARED_BIT | + IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + .scan_type = IIO_ST('u', 16, 16, 0), + .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_FALLING), + }, + { + .type = IIO_TEMP, + .channel = -1, + .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + }, +}; + +static int ad5421_write_unlocked(struct iio_dev *indio_dev, + unsigned int reg, unsigned int val) +{ + struct ad5421_state *st = iio_priv(indio_dev); + + st->data[0].d32 = cpu_to_be32((reg << 16) | val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5421_write(struct iio_dev *indio_dev, unsigned int reg, + unsigned int val) +{ + int ret; + + mutex_lock(&indio_dev->mlock); + ret = ad5421_write_unlocked(indio_dev, reg, val); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5421_read(struct iio_dev *indio_dev, unsigned int reg) +{ + struct ad5421_state *st = iio_priv(indio_dev); + struct spi_message m; + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + mutex_lock(&indio_dev->mlock); + + st->data[0].d32 = cpu_to_be32((1 << 23) | (reg << 16)); + + ret = spi_sync(st->spi, &m); + if (ret >= 0) + ret = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5421_update_ctrl(struct iio_dev *indio_dev, unsigned int set, + unsigned int clr) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int ret; + + mutex_lock(&indio_dev->mlock); + + st->ctrl &= ~clr; + st->ctrl |= set; + + ret = ad5421_write_unlocked(indio_dev, AD5421_REG_CTRL, st->ctrl); + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static irqreturn_t ad5421_fault_handler(int irq, void *data) +{ + struct iio_dev *indio_dev = data; + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int fault; + unsigned int old_fault = 0; + unsigned int events; + + fault = ad5421_read(indio_dev, AD5421_REG_FAULT); + if (!fault) + return IRQ_NONE; + + /* If we had a fault, this might mean that the DAC has lost its state + * and has been reset. Make sure that the control register actually + * contains what we expect it to contain. Otherwise the watchdog might + * be enabled and we get watchdog timeout faults, which will render the + * DAC unusable. */ + ad5421_update_ctrl(indio_dev, 0, 0); + + + /* The fault pin stays high as long as a fault condition is present and + * it is not possible to mask fault conditions. For certain fault + * conditions for example like over-temperature it takes some time + * until the fault condition disappears. If we would exit the interrupt + * handler immediately after handling the event it would be entered + * again instantly. Thus we fall back to polling in case we detect that + * a interrupt condition is still present. + */ + do { + /* 0xffff is a invalid value for the register and will only be + * read if there has been a communication error */ + if (fault == 0xffff) + fault = 0; + + /* we are only interested in new events */ + events = (old_fault ^ fault) & fault; + events &= st->fault_mask; + + if (events & AD5421_FAULT_OVER_CURRENT) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + } + + if (events & AD5421_FAULT_UNDER_CURRENT) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_CURRENT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING), + iio_get_time_ns()); + } + + if (events & AD5421_FAULT_TEMP_OVER_140) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + } + + old_fault = fault; + fault = ad5421_read(indio_dev, AD5421_REG_FAULT); + + /* still active? go to sleep for some time */ + if (fault & AD5421_FAULT_TRIGGER_IRQ) + msleep(1000); + + } while (fault & AD5421_FAULT_TRIGGER_IRQ); + + + return IRQ_HANDLED; +} + +static void ad5421_get_current_min_max(struct ad5421_state *st, + unsigned int *min, unsigned int *max) +{ + /* The current range is configured using external pins, which are + * usually hard-wired and not run-time switchable. */ + switch (st->current_range) { + case AD5421_CURRENT_RANGE_4mA_20mA: + *min = 4000; + *max = 20000; + break; + case AD5421_CURRENT_RANGE_3mA8_21mA: + *min = 3800; + *max = 21000; + break; + case AD5421_CURRENT_RANGE_3mA2_24mA: + *min = 3200; + *max = 24000; + break; + default: + *min = 0; + *max = 1; + break; + } +} + +static inline unsigned int ad5421_get_offset(struct ad5421_state *st) +{ + unsigned int min, max; + + ad5421_get_current_min_max(st, &min, &max); + return (min * (1 << 16)) / (max - min); +} + +static inline unsigned int ad5421_get_scale(struct ad5421_state *st) +{ + unsigned int min, max; + + ad5421_get_current_min_max(st, &min, &max); + return ((max - min) * 1000) / (1 << 16); +} + +static int ad5421_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long m) +{ + struct ad5421_state *st = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_CURRENT) + return -EINVAL; + + switch (m) { + case 0: + ret = ad5421_read(indio_dev, AD5421_REG_DAC_DATA); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = ad5421_get_scale(st); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = ad5421_get_offset(st); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + ret = ad5421_read(indio_dev, AD5421_REG_OFFSET); + if (ret < 0) + return ret; + *val = ret - 32768; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + ret = ad5421_read(indio_dev, AD5421_REG_GAIN); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int ad5421_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + const unsigned int max_val = 1 << 16; + + switch (mask) { + case 0: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5421_write(indio_dev, AD5421_REG_DAC_DATA, val); + case IIO_CHAN_INFO_CALIBBIAS: + val += 32768; + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5421_write(indio_dev, AD5421_REG_OFFSET, val); + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= max_val || val < 0) + return -EINVAL; + + return ad5421_write(indio_dev, AD5421_REG_GAIN, val); + default: + break; + } + + return -EINVAL; +} + +static int ad5421_write_event_config(struct iio_dev *indio_dev, + u64 event_code, int state) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int mask; + + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + case IIO_CURRENT: + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING) + mask = AD5421_FAULT_OVER_CURRENT; + else + mask = AD5421_FAULT_UNDER_CURRENT; + break; + case IIO_TEMP: + mask = AD5421_FAULT_TEMP_OVER_140; + break; + default: + return -EINVAL; + } + + mutex_lock(&indio_dev->mlock); + if (state) + st->fault_mask |= mask; + else + st->fault_mask &= ~mask; + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +static int ad5421_read_event_config(struct iio_dev *indio_dev, + u64 event_code) +{ + struct ad5421_state *st = iio_priv(indio_dev); + unsigned int mask; + + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + case IIO_CURRENT: + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING) + mask = AD5421_FAULT_OVER_CURRENT; + else + mask = AD5421_FAULT_UNDER_CURRENT; + break; + case IIO_TEMP: + mask = AD5421_FAULT_TEMP_OVER_140; + break; + default: + return -EINVAL; + } + + return (bool)(st->fault_mask & mask); +} + +static int ad5421_read_event_value(struct iio_dev *indio_dev, u64 event_code, + int *val) +{ + int ret; + + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + case IIO_CURRENT: + ret = ad5421_read(indio_dev, AD5421_REG_DAC_DATA); + if (ret < 0) + return ret; + *val = ret; + break; + case IIO_TEMP: + *val = 140000; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct iio_info ad5421_info = { + .read_raw = ad5421_read_raw, + .write_raw = ad5421_write_raw, + .read_event_config = ad5421_read_event_config, + .write_event_config = ad5421_write_event_config, + .read_event_value = ad5421_read_event_value, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad5421_probe(struct spi_device *spi) +{ + struct ad5421_platform_data *pdata = dev_get_platdata(&spi->dev); + struct iio_dev *indio_dev; + struct ad5421_state *st; + int ret; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = "ad5421"; + indio_dev->info = &ad5421_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad5421_channels; + indio_dev->num_channels = ARRAY_SIZE(ad5421_channels); + + st->ctrl = AD5421_CTRL_WATCHDOG_DISABLE | + AD5421_CTRL_AUTO_FAULT_READBACK; + + if (pdata) { + st->current_range = pdata->current_range; + if (pdata->external_vref) + st->ctrl |= AD5421_CTRL_PWR_DOWN_INT_VREF; + } else { + st->current_range = AD5421_CURRENT_RANGE_4mA_20mA; + } + + /* write initial ctrl register value */ + ad5421_update_ctrl(indio_dev, 0, 0); + + if (spi->irq) { + ret = request_threaded_irq(spi->irq, + NULL, + ad5421_fault_handler, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ad5421 fault", + indio_dev); + if (ret) + goto error_free; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", ret); + goto error_free_irq; + } + + return 0; + +error_free_irq: + if (spi->irq) + free_irq(spi->irq, indio_dev); +error_free: + iio_free_device(indio_dev); + + return ret; +} + +static int __devexit ad5421_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + if (spi->irq) + free_irq(spi->irq, indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static struct spi_driver ad5421_driver = { + .driver = { + .name = "ad5421", + .owner = THIS_MODULE, + }, + .probe = ad5421_probe, + .remove = __devexit_p(ad5421_remove), +}; +module_spi_driver(ad5421_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5421 DAC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ad5421"); diff --git a/drivers/staging/iio/dac/ad5421.h b/drivers/staging/iio/dac/ad5421.h new file mode 100644 index 00000000..cd2bb84f --- /dev/null +++ b/drivers/staging/iio/dac/ad5421.h @@ -0,0 +1,32 @@ +#ifndef __IIO_DAC_AD5421_H__ +#define __IIO_DAC_AD5421_H__ + +/* + * TODO: This file needs to go into include/linux/iio + */ + +/** + * enum ad5421_current_range - Current range the AD5421 is configured for. + * @AD5421_CURRENT_RANGE_4mA_20mA: 4 mA to 20 mA (RANGE1,0 pins = 00) + * @AD5421_CURRENT_RANGE_3mA8_21mA: 3.8 mA to 21 mA (RANGE1,0 pins = x1) + * @AD5421_CURRENT_RANGE_3mA2_24mA: 3.2 mA to 24 mA (RANGE1,0 pins = 10) + */ + +enum ad5421_current_range { + AD5421_CURRENT_RANGE_4mA_20mA, + AD5421_CURRENT_RANGE_3mA8_21mA, + AD5421_CURRENT_RANGE_3mA2_24mA, +}; + +/** + * struct ad5421_platform_data - AD5421 DAC driver platform data + * @external_vref: whether an external reference voltage is used or not + * @current_range: Current range the AD5421 is configured for + */ + +struct ad5421_platform_data { + bool external_vref; + enum ad5421_current_range current_range; +}; + +#endif diff --git a/drivers/staging/iio/dac/ad5446.c b/drivers/staging/iio/dac/ad5446.c new file mode 100644 index 00000000..633ffbb2 --- /dev/null +++ b/drivers/staging/iio/dac/ad5446.c @@ -0,0 +1,440 @@ +/* + * AD5446 SPI DAC driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dac.h" + +#include "ad5446.h" + +static void ad5446_store_sample(struct ad5446_state *st, unsigned val) +{ + st->data.d16 = cpu_to_be16(AD5446_LOAD | val); +} + +static void ad5542_store_sample(struct ad5446_state *st, unsigned val) +{ + st->data.d16 = cpu_to_be16(val); +} + +static void ad5620_store_sample(struct ad5446_state *st, unsigned val) +{ + st->data.d16 = cpu_to_be16(AD5620_LOAD | val); +} + +static void ad5660_store_sample(struct ad5446_state *st, unsigned val) +{ + val |= AD5660_LOAD; + st->data.d24[0] = (val >> 16) & 0xFF; + st->data.d24[1] = (val >> 8) & 0xFF; + st->data.d24[2] = val & 0xFF; +} + +static void ad5620_store_pwr_down(struct ad5446_state *st, unsigned mode) +{ + st->data.d16 = cpu_to_be16(mode << 14); +} + +static void ad5660_store_pwr_down(struct ad5446_state *st, unsigned mode) +{ + unsigned val = mode << 16; + + st->data.d24[0] = (val >> 16) & 0xFF; + st->data.d24[1] = (val >> 8) & 0xFF; + st->data.d24[2] = val & 0xFF; +} + +static ssize_t ad5446_write_powerdown_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5446_state *st = iio_priv(indio_dev); + + if (sysfs_streq(buf, "1kohm_to_gnd")) + st->pwr_down_mode = MODE_PWRDWN_1k; + else if (sysfs_streq(buf, "100kohm_to_gnd")) + st->pwr_down_mode = MODE_PWRDWN_100k; + else if (sysfs_streq(buf, "three_state")) + st->pwr_down_mode = MODE_PWRDWN_TRISTATE; + else + return -EINVAL; + + return len; +} + +static ssize_t ad5446_read_powerdown_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5446_state *st = iio_priv(indio_dev); + + char mode[][15] = {"", "1kohm_to_gnd", "100kohm_to_gnd", "three_state"}; + + return sprintf(buf, "%s\n", mode[st->pwr_down_mode]); +} + +static ssize_t ad5446_read_dac_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5446_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5446_write_dac_powerdown(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5446_state *st = iio_priv(indio_dev); + unsigned long readin; + int ret; + + ret = strict_strtol(buf, 10, &readin); + if (ret) + return ret; + + if (readin > 1) + ret = -EINVAL; + + mutex_lock(&indio_dev->mlock); + st->pwr_down = readin; + + if (st->pwr_down) + st->chip_info->store_pwr_down(st, st->pwr_down_mode); + else + st->chip_info->store_sample(st, st->cached_val); + + ret = spi_sync(st->spi, &st->msg); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage_powerdown_mode, S_IRUGO | S_IWUSR, + ad5446_read_powerdown_mode, + ad5446_write_powerdown_mode, 0); + +static IIO_CONST_ATTR(out_voltage_powerdown_mode_available, + "1kohm_to_gnd 100kohm_to_gnd three_state"); + +static IIO_DEVICE_ATTR(out_voltage0_powerdown, S_IRUGO | S_IWUSR, + ad5446_read_dac_powerdown, + ad5446_write_dac_powerdown, 0); + +static struct attribute *ad5446_attributes[] = { + &iio_dev_attr_out_voltage0_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage_powerdown_mode.dev_attr.attr, + &iio_const_attr_out_voltage_powerdown_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5446_attribute_group = { + .attrs = ad5446_attributes, +}; + +#define AD5446_CHANNEL(bits, storage, shift) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = 0, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .scan_type = IIO_ST('u', (bits), (storage), (shift)) \ +} + +static const struct ad5446_chip_info ad5446_chip_info_tbl[] = { + [ID_AD5444] = { + .channel = AD5446_CHANNEL(12, 16, 2), + .store_sample = ad5446_store_sample, + }, + [ID_AD5446] = { + .channel = AD5446_CHANNEL(14, 16, 0), + .store_sample = ad5446_store_sample, + }, + [ID_AD5541A] = { + .channel = AD5446_CHANNEL(16, 16, 0), + .store_sample = ad5542_store_sample, + }, + [ID_AD5542A] = { + .channel = AD5446_CHANNEL(16, 16, 0), + .store_sample = ad5542_store_sample, + }, + [ID_AD5543] = { + .channel = AD5446_CHANNEL(16, 16, 0), + .store_sample = ad5542_store_sample, + }, + [ID_AD5512A] = { + .channel = AD5446_CHANNEL(12, 16, 4), + .store_sample = ad5542_store_sample, + }, + [ID_AD5553] = { + .channel = AD5446_CHANNEL(14, 16, 0), + .store_sample = ad5542_store_sample, + }, + [ID_AD5601] = { + .channel = AD5446_CHANNEL(8, 16, 6), + .store_sample = ad5542_store_sample, + .store_pwr_down = ad5620_store_pwr_down, + }, + [ID_AD5611] = { + .channel = AD5446_CHANNEL(10, 16, 4), + .store_sample = ad5542_store_sample, + .store_pwr_down = ad5620_store_pwr_down, + }, + [ID_AD5621] = { + .channel = AD5446_CHANNEL(12, 16, 2), + .store_sample = ad5542_store_sample, + .store_pwr_down = ad5620_store_pwr_down, + }, + [ID_AD5620_2500] = { + .channel = AD5446_CHANNEL(12, 16, 2), + .int_vref_mv = 2500, + .store_sample = ad5620_store_sample, + .store_pwr_down = ad5620_store_pwr_down, + }, + [ID_AD5620_1250] = { + .channel = AD5446_CHANNEL(12, 16, 2), + .int_vref_mv = 1250, + .store_sample = ad5620_store_sample, + .store_pwr_down = ad5620_store_pwr_down, + }, + [ID_AD5640_2500] = { + .channel = AD5446_CHANNEL(14, 16, 0), + .int_vref_mv = 2500, + .store_sample = ad5620_store_sample, + .store_pwr_down = ad5620_store_pwr_down, + }, + [ID_AD5640_1250] = { + .channel = AD5446_CHANNEL(14, 16, 0), + .int_vref_mv = 1250, + .store_sample = ad5620_store_sample, + .store_pwr_down = ad5620_store_pwr_down, + }, + [ID_AD5660_2500] = { + .channel = AD5446_CHANNEL(16, 16, 0), + .int_vref_mv = 2500, + .store_sample = ad5660_store_sample, + .store_pwr_down = ad5660_store_pwr_down, + }, + [ID_AD5660_1250] = { + .channel = AD5446_CHANNEL(16, 16, 0), + .int_vref_mv = 1250, + .store_sample = ad5660_store_sample, + .store_pwr_down = ad5660_store_pwr_down, + }, +}; + +static int ad5446_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5446_state *st = iio_priv(indio_dev); + unsigned long scale_uv; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + + } + return -EINVAL; +} + +static int ad5446_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5446_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case 0: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + val <<= chan->scan_type.shift; + mutex_lock(&indio_dev->mlock); + st->cached_val = val; + st->chip_info->store_sample(st, val); + ret = spi_sync(st->spi, &st->msg); + mutex_unlock(&indio_dev->mlock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad5446_info = { + .read_raw = ad5446_read_raw, + .write_raw = ad5446_write_raw, + .attrs = &ad5446_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad5446_info_no_pwr_down = { + .read_raw = ad5446_read_raw, + .write_raw = ad5446_write_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad5446_probe(struct spi_device *spi) +{ + struct ad5446_state *st; + struct iio_dev *indio_dev; + struct regulator *reg; + int ret, voltage_uv = 0; + + reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(reg); + } + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_disable_reg; + } + st = iio_priv(indio_dev); + st->chip_info = + &ad5446_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + spi_set_drvdata(spi, indio_dev); + st->reg = reg; + st->spi = spi; + + /* Establish that the iio_dev is a child of the spi device */ + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + if (st->chip_info->store_pwr_down) + indio_dev->info = &ad5446_info; + else + indio_dev->info = &ad5446_info_no_pwr_down; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &st->chip_info->channel; + indio_dev->num_channels = 1; + + /* Setup default message */ + + st->xfer.tx_buf = &st->data; + st->xfer.len = st->chip_info->channel.scan_type.storagebits / 8; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + switch (spi_get_device_id(spi)->driver_data) { + case ID_AD5620_2500: + case ID_AD5620_1250: + case ID_AD5640_2500: + case ID_AD5640_1250: + case ID_AD5660_2500: + case ID_AD5660_1250: + st->vref_mv = st->chip_info->int_vref_mv; + break; + default: + if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else + dev_warn(&spi->dev, + "reference voltage unspecified\n"); + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_device; + + return 0; + +error_free_device: + iio_free_device(indio_dev); +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); +error_put_reg: + if (!IS_ERR(reg)) + regulator_put(reg); + + return ret; +} + +static int ad5446_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5446_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad5446_id[] = { + {"ad5444", ID_AD5444}, + {"ad5446", ID_AD5446}, + {"ad5512a", ID_AD5512A}, + {"ad5541a", ID_AD5541A}, + {"ad5542a", ID_AD5542A}, + {"ad5543", ID_AD5543}, + {"ad5553", ID_AD5553}, + {"ad5601", ID_AD5601}, + {"ad5611", ID_AD5611}, + {"ad5621", ID_AD5621}, + {"ad5620-2500", ID_AD5620_2500}, /* AD5620/40/60: */ + {"ad5620-1250", ID_AD5620_1250}, /* part numbers may look differently */ + {"ad5640-2500", ID_AD5640_2500}, + {"ad5640-1250", ID_AD5640_1250}, + {"ad5660-2500", ID_AD5660_2500}, + {"ad5660-1250", ID_AD5660_1250}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5446_id); + +static struct spi_driver ad5446_driver = { + .driver = { + .name = "ad5446", + .owner = THIS_MODULE, + }, + .probe = ad5446_probe, + .remove = __devexit_p(ad5446_remove), + .id_table = ad5446_id, +}; +module_spi_driver(ad5446_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD5444/AD5446 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dac/ad5446.h b/drivers/staging/iio/dac/ad5446.h new file mode 100644 index 00000000..4ea3476f --- /dev/null +++ b/drivers/staging/iio/dac/ad5446.h @@ -0,0 +1,103 @@ +/* + * AD5446 SPI DAC driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ +#ifndef IIO_DAC_AD5446_H_ +#define IIO_DAC_AD5446_H_ + +/* DAC Control Bits */ + +#define AD5446_LOAD (0x0 << 14) /* Load and update */ +#define AD5446_SDO_DIS (0x1 << 14) /* Disable SDO */ +#define AD5446_NOP (0x2 << 14) /* No operation */ +#define AD5446_CLK_RISING (0x3 << 14) /* Clock data on rising edge */ + +#define AD5620_LOAD (0x0 << 14) /* Load and update Norm Operation*/ +#define AD5620_PWRDWN_1k (0x1 << 14) /* Power-down: 1kOhm to GND */ +#define AD5620_PWRDWN_100k (0x2 << 14) /* Power-down: 100kOhm to GND */ +#define AD5620_PWRDWN_TRISTATE (0x3 << 14) /* Power-down: Three-state */ + +#define AD5660_LOAD (0x0 << 16) /* Load and update Norm Operation*/ +#define AD5660_PWRDWN_1k (0x1 << 16) /* Power-down: 1kOhm to GND */ +#define AD5660_PWRDWN_100k (0x2 << 16) /* Power-down: 100kOhm to GND */ +#define AD5660_PWRDWN_TRISTATE (0x3 << 16) /* Power-down: Three-state */ + +#define MODE_PWRDWN_1k 0x1 +#define MODE_PWRDWN_100k 0x2 +#define MODE_PWRDWN_TRISTATE 0x3 + +/** + * struct ad5446_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip model specific constants, available modes etc + * @reg: supply regulator + * @poll_work: bottom half of polling interrupt handler + * @vref_mv: actual reference voltage used + * @xfer: default spi transfer + * @msg: default spi message + * @data: spi transmit buffer + */ + +struct ad5446_state { + struct spi_device *spi; + const struct ad5446_chip_info *chip_info; + struct regulator *reg; + struct work_struct poll_work; + unsigned short vref_mv; + unsigned cached_val; + unsigned pwr_down_mode; + unsigned pwr_down; + struct spi_transfer xfer; + struct spi_message msg; + union { + unsigned short d16; + unsigned char d24[3]; + } data; +}; + +/** + * struct ad5446_chip_info - chip specific information + * @channel: channel spec for the DAC + * @int_vref_mv: AD5620/40/60: the internal reference voltage + * @store_sample: chip specific helper function to store the datum + * @store_sample: chip specific helper function to store the powerpown cmd + */ + +struct ad5446_chip_info { + struct iio_chan_spec channel; + u16 int_vref_mv; + void (*store_sample) (struct ad5446_state *st, unsigned val); + void (*store_pwr_down) (struct ad5446_state *st, unsigned mode); +}; + +/** + * ad5446_supported_device_ids: + * The AD5620/40/60 parts are available in different fixed internal reference + * voltage options. The actual part numbers may look differently + * (and a bit cryptic), however this style is used to make clear which + * parts are supported here. + */ + +enum ad5446_supported_device_ids { + ID_AD5444, + ID_AD5446, + ID_AD5541A, + ID_AD5542A, + ID_AD5543, + ID_AD5512A, + ID_AD5553, + ID_AD5601, + ID_AD5611, + ID_AD5621, + ID_AD5620_2500, + ID_AD5620_1250, + ID_AD5640_2500, + ID_AD5640_1250, + ID_AD5660_2500, + ID_AD5660_1250, +}; + +#endif /* IIO_DAC_AD5446_H_ */ diff --git a/drivers/staging/iio/dac/ad5504.c b/drivers/staging/iio/dac/ad5504.c new file mode 100644 index 00000000..bc17205f --- /dev/null +++ b/drivers/staging/iio/dac/ad5504.c @@ -0,0 +1,396 @@ +/* + * AD5504, AD5501 High Voltage Digital to Analog Converter + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" +#include "dac.h" +#include "ad5504.h" + +#define AD5504_CHANNEL(_chan) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .address = AD5504_ADDR_DAC(_chan), \ + .scan_type = IIO_ST('u', 12, 16, 0), \ +} + +static const struct iio_chan_spec ad5504_channels[] = { + AD5504_CHANNEL(0), + AD5504_CHANNEL(1), + AD5504_CHANNEL(2), + AD5504_CHANNEL(3), +}; + +static int ad5504_spi_write(struct spi_device *spi, u8 addr, u16 val) +{ + u16 tmp = cpu_to_be16(AD5504_CMD_WRITE | + AD5504_ADDR(addr) | + (val & AD5504_RES_MASK)); + + return spi_write(spi, (u8 *)&tmp, 2); +} + +static int ad5504_spi_read(struct spi_device *spi, u8 addr) +{ + u16 tmp = cpu_to_be16(AD5504_CMD_READ | AD5504_ADDR(addr)); + u16 val; + int ret; + struct spi_transfer t = { + .tx_buf = &tmp, + .rx_buf = &val, + .len = 2, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(spi, &m); + + if (ret < 0) + return ret; + + return be16_to_cpu(val) & AD5504_RES_MASK; +} + +static int ad5504_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5504_state *st = iio_priv(indio_dev); + unsigned long scale_uv; + int ret; + + switch (m) { + case 0: + ret = ad5504_spi_read(st->spi, chan->address); + if (ret < 0) + return ret; + + *val = ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + + } + return -EINVAL; +} + +static int ad5504_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5504_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case 0: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + return ad5504_spi_write(st->spi, chan->address, val); + default: + ret = -EINVAL; + } + + return -EINVAL; +} + +static ssize_t ad5504_read_powerdown_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5504_state *st = iio_priv(indio_dev); + + const char mode[][14] = {"20kohm_to_gnd", "three_state"}; + + return sprintf(buf, "%s\n", mode[st->pwr_down_mode]); +} + +static ssize_t ad5504_write_powerdown_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5504_state *st = iio_priv(indio_dev); + int ret; + + if (sysfs_streq(buf, "20kohm_to_gnd")) + st->pwr_down_mode = AD5504_DAC_PWRDN_20K; + else if (sysfs_streq(buf, "three_state")) + st->pwr_down_mode = AD5504_DAC_PWRDN_3STATE; + else + ret = -EINVAL; + + return ret ? ret : len; +} + +static ssize_t ad5504_read_dac_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5504_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return sprintf(buf, "%d\n", + !(st->pwr_down_mask & (1 << this_attr->address))); +} + +static ssize_t ad5504_write_dac_powerdown(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + long readin; + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5504_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = strict_strtol(buf, 10, &readin); + if (ret) + return ret; + + if (readin == 0) + st->pwr_down_mask |= (1 << this_attr->address); + else if (readin == 1) + st->pwr_down_mask &= ~(1 << this_attr->address); + else + ret = -EINVAL; + + ret = ad5504_spi_write(st->spi, AD5504_ADDR_CTRL, + AD5504_DAC_PWRDWN_MODE(st->pwr_down_mode) | + AD5504_DAC_PWR(st->pwr_down_mask)); + + /* writes to the CTRL register must be followed by a NOOP */ + ad5504_spi_write(st->spi, AD5504_ADDR_NOOP, 0); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage_powerdown_mode, S_IRUGO | + S_IWUSR, ad5504_read_powerdown_mode, + ad5504_write_powerdown_mode, 0); + +static IIO_CONST_ATTR(out_voltage_powerdown_mode_available, + "20kohm_to_gnd three_state"); + +#define IIO_DEV_ATTR_DAC_POWERDOWN(_num, _show, _store, _addr) \ + IIO_DEVICE_ATTR(out_voltage##_num##_powerdown, \ + S_IRUGO | S_IWUSR, _show, _store, _addr) +static IIO_DEV_ATTR_DAC_POWERDOWN(0, ad5504_read_dac_powerdown, + ad5504_write_dac_powerdown, 0); +static IIO_DEV_ATTR_DAC_POWERDOWN(1, ad5504_read_dac_powerdown, + ad5504_write_dac_powerdown, 1); +static IIO_DEV_ATTR_DAC_POWERDOWN(2, ad5504_read_dac_powerdown, + ad5504_write_dac_powerdown, 2); +static IIO_DEV_ATTR_DAC_POWERDOWN(3, ad5504_read_dac_powerdown, + ad5504_write_dac_powerdown, 3); + +static struct attribute *ad5504_attributes[] = { + &iio_dev_attr_out_voltage0_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage1_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage2_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage3_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage_powerdown_mode.dev_attr.attr, + &iio_const_attr_out_voltage_powerdown_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5504_attribute_group = { + .attrs = ad5504_attributes, +}; + +static struct attribute *ad5501_attributes[] = { + &iio_dev_attr_out_voltage0_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage_powerdown_mode.dev_attr.attr, + &iio_const_attr_out_voltage_powerdown_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5501_attribute_group = { + .attrs = ad5501_attributes, +}; + +static IIO_CONST_ATTR(temp0_thresh_rising_value, "110000"); +static IIO_CONST_ATTR(temp0_thresh_rising_en, "1"); + +static struct attribute *ad5504_ev_attributes[] = { + &iio_const_attr_temp0_thresh_rising_value.dev_attr.attr, + &iio_const_attr_temp0_thresh_rising_en.dev_attr.attr, + NULL, +}; + +static struct attribute_group ad5504_ev_attribute_group = { + .attrs = ad5504_ev_attributes, + .name = "events", +}; + +static irqreturn_t ad5504_event_handler(int irq, void *private) +{ + iio_push_event(private, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns()); + + return IRQ_HANDLED; +} + +static const struct iio_info ad5504_info = { + .write_raw = ad5504_write_raw, + .read_raw = ad5504_read_raw, + .attrs = &ad5504_attribute_group, + .event_attrs = &ad5504_ev_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad5501_info = { + .write_raw = ad5504_write_raw, + .read_raw = ad5504_read_raw, + .attrs = &ad5501_attribute_group, + .event_attrs = &ad5504_ev_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad5504_probe(struct spi_device *spi) +{ + struct ad5504_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct ad5504_state *st; + struct regulator *reg; + int ret, voltage_uv = 0; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(reg); + } + + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else if (pdata) + st->vref_mv = pdata->vref_mv; + else + dev_warn(&spi->dev, "reference voltage unspecified\n"); + + st->reg = reg; + st->spi = spi; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(st->spi)->name; + if (spi_get_device_id(st->spi)->driver_data == ID_AD5501) { + indio_dev->info = &ad5501_info; + indio_dev->num_channels = 1; + } else { + indio_dev->info = &ad5504_info; + indio_dev->num_channels = 4; + } + indio_dev->channels = ad5504_channels; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (spi->irq) { + ret = request_threaded_irq(spi->irq, + NULL, + &ad5504_event_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + spi_get_device_id(st->spi)->name, + indio_dev); + if (ret) + goto error_disable_reg; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(spi->irq, indio_dev); +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); +error_put_reg: + if (!IS_ERR(reg)) + regulator_put(reg); + + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit ad5504_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5504_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (spi->irq) + free_irq(spi->irq, indio_dev); + + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad5504_id[] = { + {"ad5504", ID_AD5504}, + {"ad5501", ID_AD5501}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5504_id); + +static struct spi_driver ad5504_driver = { + .driver = { + .name = "ad5504", + .owner = THIS_MODULE, + }, + .probe = ad5504_probe, + .remove = __devexit_p(ad5504_remove), + .id_table = ad5504_id, +}; +module_spi_driver(ad5504_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD5501/AD5501 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dac/ad5504.h b/drivers/staging/iio/dac/ad5504.h new file mode 100644 index 00000000..afe09522 --- /dev/null +++ b/drivers/staging/iio/dac/ad5504.h @@ -0,0 +1,65 @@ +/* + * AD5504 SPI DAC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef SPI_AD5504_H_ +#define SPI_AD5504_H_ + +#define AD5505_BITS 12 +#define AD5504_RES_MASK ((1 << (AD5505_BITS)) - 1) + +#define AD5504_CMD_READ (1 << 15) +#define AD5504_CMD_WRITE (0 << 15) +#define AD5504_ADDR(addr) ((addr) << 12) + +/* Registers */ +#define AD5504_ADDR_NOOP 0 +#define AD5504_ADDR_DAC(x) ((x) + 1) +#define AD5504_ADDR_ALL_DAC 5 +#define AD5504_ADDR_CTRL 7 + +/* Control Register */ +#define AD5504_DAC_PWR(ch) ((ch) << 2) +#define AD5504_DAC_PWRDWN_MODE(mode) ((mode) << 6) +#define AD5504_DAC_PWRDN_20K 0 +#define AD5504_DAC_PWRDN_3STATE 1 + +/* + * TODO: struct ad5504_platform_data needs to go into include/linux/iio + */ + +struct ad5504_platform_data { + u16 vref_mv; +}; + +/** + * struct ad5446_state - driver instance specific data + * @us: spi_device + * @reg: supply regulator + * @vref_mv: actual reference voltage used + * @pwr_down_mask power down mask + * @pwr_down_mode current power down mode + */ + +struct ad5504_state { + struct spi_device *spi; + struct regulator *reg; + unsigned short vref_mv; + unsigned pwr_down_mask; + unsigned pwr_down_mode; +}; + +/** + * ad5504_supported_device_ids: + */ + +enum ad5504_supported_device_ids { + ID_AD5504, + ID_AD5501, +}; + +#endif /* SPI_AD5504_H_ */ diff --git a/drivers/staging/iio/dac/ad5624r.h b/drivers/staging/iio/dac/ad5624r.h new file mode 100644 index 00000000..5dca3028 --- /dev/null +++ b/drivers/staging/iio/dac/ad5624r.h @@ -0,0 +1,79 @@ +/* + * AD5624R SPI DAC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef SPI_AD5624R_H_ +#define SPI_AD5624R_H_ + +#define AD5624R_DAC_CHANNELS 4 + +#define AD5624R_ADDR_DAC0 0x0 +#define AD5624R_ADDR_DAC1 0x1 +#define AD5624R_ADDR_DAC2 0x2 +#define AD5624R_ADDR_DAC3 0x3 +#define AD5624R_ADDR_ALL_DAC 0x7 + +#define AD5624R_CMD_WRITE_INPUT_N 0x0 +#define AD5624R_CMD_UPDATE_DAC_N 0x1 +#define AD5624R_CMD_WRITE_INPUT_N_UPDATE_ALL 0x2 +#define AD5624R_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define AD5624R_CMD_POWERDOWN_DAC 0x4 +#define AD5624R_CMD_RESET 0x5 +#define AD5624R_CMD_LDAC_SETUP 0x6 +#define AD5624R_CMD_INTERNAL_REFER_SETUP 0x7 + +#define AD5624R_LDAC_PWRDN_NONE 0x0 +#define AD5624R_LDAC_PWRDN_1K 0x1 +#define AD5624R_LDAC_PWRDN_100K 0x2 +#define AD5624R_LDAC_PWRDN_3STATE 0x3 + +/** + * struct ad5624r_chip_info - chip specific information + * @channels: channel spec for the DAC + * @int_vref_mv: AD5620/40/60: the internal reference voltage + */ + +struct ad5624r_chip_info { + const struct iio_chan_spec *channels; + u16 int_vref_mv; +}; + +/** + * struct ad5446_state - driver instance specific data + * @indio_dev: the industrial I/O device + * @us: spi_device + * @chip_info: chip model specific constants, available modes etc + * @reg: supply regulator + * @vref_mv: actual reference voltage used + * @pwr_down_mask power down mask + * @pwr_down_mode current power down mode + */ + +struct ad5624r_state { + struct spi_device *us; + const struct ad5624r_chip_info *chip_info; + struct regulator *reg; + unsigned short vref_mv; + unsigned pwr_down_mask; + unsigned pwr_down_mode; +}; + +/** + * ad5624r_supported_device_ids: + * The AD5624/44/64 parts are available in different + * fixed internal reference voltage options. + */ + +enum ad5624r_supported_device_ids { + ID_AD5624R3, + ID_AD5644R3, + ID_AD5664R3, + ID_AD5624R5, + ID_AD5644R5, + ID_AD5664R5, +}; + +#endif /* SPI_AD5624R_H_ */ diff --git a/drivers/staging/iio/dac/ad5624r_spi.c b/drivers/staging/iio/dac/ad5624r_spi.c new file mode 100644 index 00000000..10c74843 --- /dev/null +++ b/drivers/staging/iio/dac/ad5624r_spi.c @@ -0,0 +1,353 @@ +/* + * AD5624R, AD5644R, AD5664R Digital to analog convertors spi driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dac.h" +#include "ad5624r.h" + +#define AD5624R_CHANNEL(_chan, _bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .address = (_chan), \ + .scan_type = IIO_ST('u', (_bits), 16, 16 - (_bits)), \ +} + +#define DECLARE_AD5624R_CHANNELS(_name, _bits) \ + const struct iio_chan_spec _name##_channels[] = { \ + AD5624R_CHANNEL(0, _bits), \ + AD5624R_CHANNEL(1, _bits), \ + AD5624R_CHANNEL(2, _bits), \ + AD5624R_CHANNEL(3, _bits), \ +} + +static DECLARE_AD5624R_CHANNELS(ad5624r, 12); +static DECLARE_AD5624R_CHANNELS(ad5644r, 14); +static DECLARE_AD5624R_CHANNELS(ad5664r, 16); + +static const struct ad5624r_chip_info ad5624r_chip_info_tbl[] = { + [ID_AD5624R3] = { + .channels = ad5624r_channels, + .int_vref_mv = 1250, + }, + [ID_AD5624R5] = { + .channels = ad5624r_channels, + .int_vref_mv = 2500, + }, + [ID_AD5644R3] = { + .channels = ad5644r_channels, + .int_vref_mv = 1250, + }, + [ID_AD5644R5] = { + .channels = ad5644r_channels, + .int_vref_mv = 2500, + }, + [ID_AD5664R3] = { + .channels = ad5664r_channels, + .int_vref_mv = 1250, + }, + [ID_AD5664R5] = { + .channels = ad5664r_channels, + .int_vref_mv = 2500, + }, +}; + +static int ad5624r_spi_write(struct spi_device *spi, + u8 cmd, u8 addr, u16 val, u8 len) +{ + u32 data; + u8 msg[3]; + + /* + * The input shift register is 24 bits wide. The first two bits are + * don't care bits. The next three are the command bits, C2 to C0, + * followed by the 3-bit DAC address, A2 to A0, and then the + * 16-, 14-, 12-bit data-word. The data-word comprises the 16-, + * 14-, 12-bit input code followed by 0, 2, or 4 don't care bits, + * for the AD5664R, AD5644R, and AD5624R, respectively. + */ + data = (0 << 22) | (cmd << 19) | (addr << 16) | (val << (16 - len)); + msg[0] = data >> 16; + msg[1] = data >> 8; + msg[2] = data; + + return spi_write(spi, msg, 3); +} + +static int ad5624r_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + unsigned long scale_uv; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->vref_mv * 1000) >> chan->scan_type.realbits; + *val = scale_uv / 1000; + *val2 = (scale_uv % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + + } + return -EINVAL; +} + +static int ad5624r_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5624r_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case 0: + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + return ad5624r_spi_write(st->us, + AD5624R_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, val, + chan->scan_type.shift); + default: + ret = -EINVAL; + } + + return -EINVAL; +} + +static ssize_t ad5624r_read_powerdown_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5624r_state *st = iio_priv(indio_dev); + + char mode[][15] = {"", "1kohm_to_gnd", "100kohm_to_gnd", "three_state"}; + + return sprintf(buf, "%s\n", mode[st->pwr_down_mode]); +} + +static ssize_t ad5624r_write_powerdown_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5624r_state *st = iio_priv(indio_dev); + int ret; + + if (sysfs_streq(buf, "1kohm_to_gnd")) + st->pwr_down_mode = AD5624R_LDAC_PWRDN_1K; + else if (sysfs_streq(buf, "100kohm_to_gnd")) + st->pwr_down_mode = AD5624R_LDAC_PWRDN_100K; + else if (sysfs_streq(buf, "three_state")) + st->pwr_down_mode = AD5624R_LDAC_PWRDN_3STATE; + else + ret = -EINVAL; + + return ret ? ret : len; +} + +static ssize_t ad5624r_read_dac_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5624r_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return sprintf(buf, "%d\n", + !!(st->pwr_down_mask & (1 << this_attr->address))); +} + +static ssize_t ad5624r_write_dac_powerdown(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + long readin; + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5624r_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = strict_strtol(buf, 10, &readin); + if (ret) + return ret; + + if (readin == 1) + st->pwr_down_mask |= (1 << this_attr->address); + else if (!readin) + st->pwr_down_mask &= ~(1 << this_attr->address); + else + ret = -EINVAL; + + ret = ad5624r_spi_write(st->us, AD5624R_CMD_POWERDOWN_DAC, 0, + (st->pwr_down_mode << 4) | + st->pwr_down_mask, 16); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage_powerdown_mode, S_IRUGO | + S_IWUSR, ad5624r_read_powerdown_mode, + ad5624r_write_powerdown_mode, 0); + +static IIO_CONST_ATTR(out_voltage_powerdown_mode_available, + "1kohm_to_gnd 100kohm_to_gnd three_state"); + +#define IIO_DEV_ATTR_DAC_POWERDOWN(_num, _show, _store, _addr) \ + IIO_DEVICE_ATTR(out_voltage##_num##_powerdown, \ + S_IRUGO | S_IWUSR, _show, _store, _addr) + +static IIO_DEV_ATTR_DAC_POWERDOWN(0, ad5624r_read_dac_powerdown, + ad5624r_write_dac_powerdown, 0); +static IIO_DEV_ATTR_DAC_POWERDOWN(1, ad5624r_read_dac_powerdown, + ad5624r_write_dac_powerdown, 1); +static IIO_DEV_ATTR_DAC_POWERDOWN(2, ad5624r_read_dac_powerdown, + ad5624r_write_dac_powerdown, 2); +static IIO_DEV_ATTR_DAC_POWERDOWN(3, ad5624r_read_dac_powerdown, + ad5624r_write_dac_powerdown, 3); + +static struct attribute *ad5624r_attributes[] = { + &iio_dev_attr_out_voltage0_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage1_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage2_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage3_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage_powerdown_mode.dev_attr.attr, + &iio_const_attr_out_voltage_powerdown_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5624r_attribute_group = { + .attrs = ad5624r_attributes, +}; + +static const struct iio_info ad5624r_info = { + .write_raw = ad5624r_write_raw, + .read_raw = ad5624r_read_raw, + .attrs = &ad5624r_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad5624r_probe(struct spi_device *spi) +{ + struct ad5624r_state *st; + struct iio_dev *indio_dev; + int ret, voltage_uv = 0; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(st->reg); + } + + spi_set_drvdata(spi, indio_dev); + st->chip_info = + &ad5624r_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else + st->vref_mv = st->chip_info->int_vref_mv; + + st->us = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5624r_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = AD5624R_DAC_CHANNELS; + + ret = ad5624r_spi_write(spi, AD5624R_CMD_INTERNAL_REFER_SETUP, 0, + !!voltage_uv, 16); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + iio_free_device(indio_dev); +error_ret: + + return ret; +} + +static int __devexit ad5624r_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5624r_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad5624r_id[] = { + {"ad5624r3", ID_AD5624R3}, + {"ad5644r3", ID_AD5644R3}, + {"ad5664r3", ID_AD5664R3}, + {"ad5624r5", ID_AD5624R5}, + {"ad5644r5", ID_AD5644R5}, + {"ad5664r5", ID_AD5664R5}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5624r_id); + +static struct spi_driver ad5624r_driver = { + .driver = { + .name = "ad5624r", + .owner = THIS_MODULE, + }, + .probe = ad5624r_probe, + .remove = __devexit_p(ad5624r_remove), + .id_table = ad5624r_id, +}; +module_spi_driver(ad5624r_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices AD5624/44/64R DAC spi driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dac/ad5686.c b/drivers/staging/iio/dac/ad5686.c new file mode 100644 index 00000000..2415a6e6 --- /dev/null +++ b/drivers/staging/iio/dac/ad5686.c @@ -0,0 +1,454 @@ +/* + * AD5686R, AD5685R, AD5684R Digital to analog converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dac.h" + +#define AD5686_DAC_CHANNELS 4 + +#define AD5686_ADDR(x) ((x) << 16) +#define AD5686_CMD(x) ((x) << 20) + +#define AD5686_ADDR_DAC(chan) (0x1 << (chan)) +#define AD5686_ADDR_ALL_DAC 0xF + +#define AD5686_CMD_NOOP 0x0 +#define AD5686_CMD_WRITE_INPUT_N 0x1 +#define AD5686_CMD_UPDATE_DAC_N 0x2 +#define AD5686_CMD_WRITE_INPUT_N_UPDATE_N 0x3 +#define AD5686_CMD_POWERDOWN_DAC 0x4 +#define AD5686_CMD_LDAC_MASK 0x5 +#define AD5686_CMD_RESET 0x6 +#define AD5686_CMD_INTERNAL_REFER_SETUP 0x7 +#define AD5686_CMD_DAISY_CHAIN_ENABLE 0x8 +#define AD5686_CMD_READBACK_ENABLE 0x9 + +#define AD5686_LDAC_PWRDN_NONE 0x0 +#define AD5686_LDAC_PWRDN_1K 0x1 +#define AD5686_LDAC_PWRDN_100K 0x2 +#define AD5686_LDAC_PWRDN_3STATE 0x3 + +/** + * struct ad5686_chip_info - chip specific information + * @int_vref_mv: AD5620/40/60: the internal reference voltage + * @channel: channel specification +*/ + +struct ad5686_chip_info { + u16 int_vref_mv; + struct iio_chan_spec channel[AD5686_DAC_CHANNELS]; +}; + +/** + * struct ad5446_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip model specific constants, available modes etc + * @reg: supply regulator + * @vref_mv: actual reference voltage used + * @pwr_down_mask: power down mask + * @pwr_down_mode: current power down mode + * @data: spi transfer buffers + */ + +struct ad5686_state { + struct spi_device *spi; + const struct ad5686_chip_info *chip_info; + struct regulator *reg; + unsigned short vref_mv; + unsigned pwr_down_mask; + unsigned pwr_down_mode; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + + union { + u32 d32; + u8 d8[4]; + } data[3] ____cacheline_aligned; +}; + +/** + * ad5686_supported_device_ids: + */ + +enum ad5686_supported_device_ids { + ID_AD5684, + ID_AD5685, + ID_AD5686, +}; +#define AD5868_CHANNEL(chan, bits, shift) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = chan, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .address = AD5686_ADDR_DAC(chan), \ + .scan_type = IIO_ST('u', bits, 16, shift) \ +} +static const struct ad5686_chip_info ad5686_chip_info_tbl[] = { + [ID_AD5684] = { + .channel[0] = AD5868_CHANNEL(0, 12, 4), + .channel[1] = AD5868_CHANNEL(1, 12, 4), + .channel[2] = AD5868_CHANNEL(2, 12, 4), + .channel[3] = AD5868_CHANNEL(3, 12, 4), + .int_vref_mv = 2500, + }, + [ID_AD5685] = { + .channel[0] = AD5868_CHANNEL(0, 14, 2), + .channel[1] = AD5868_CHANNEL(1, 14, 2), + .channel[2] = AD5868_CHANNEL(2, 14, 2), + .channel[3] = AD5868_CHANNEL(3, 14, 2), + .int_vref_mv = 2500, + }, + [ID_AD5686] = { + .channel[0] = AD5868_CHANNEL(0, 16, 0), + .channel[1] = AD5868_CHANNEL(1, 16, 0), + .channel[2] = AD5868_CHANNEL(2, 16, 0), + .channel[3] = AD5868_CHANNEL(3, 16, 0), + .int_vref_mv = 2500, + }, +}; + +static int ad5686_spi_write(struct ad5686_state *st, + u8 cmd, u8 addr, u16 val, u8 shift) +{ + val <<= shift; + + st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) | + AD5686_ADDR(addr) | + val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5686_spi_read(struct ad5686_state *st, u8 addr) +{ + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d8[1], + .rx_buf = &st->data[2].d8[1], + .len = 3, + }, + }; + struct spi_message m; + int ret; + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + st->data[0].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_READBACK_ENABLE) | + AD5686_ADDR(addr)); + st->data[1].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_NOOP)); + + ret = spi_sync(st->spi, &m); + if (ret < 0) + return ret; + + return be32_to_cpu(st->data[2].d32); +} + +static ssize_t ad5686_read_powerdown_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5686_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + char mode[][15] = {"", "1kohm_to_gnd", "100kohm_to_gnd", "three_state"}; + + return sprintf(buf, "%s\n", mode[(st->pwr_down_mode >> + (this_attr->address * 2)) & 0x3]); +} + +static ssize_t ad5686_write_powerdown_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5686_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned mode; + + if (sysfs_streq(buf, "1kohm_to_gnd")) + mode = AD5686_LDAC_PWRDN_1K; + else if (sysfs_streq(buf, "100kohm_to_gnd")) + mode = AD5686_LDAC_PWRDN_100K; + else if (sysfs_streq(buf, "three_state")) + mode = AD5686_LDAC_PWRDN_3STATE; + else + return -EINVAL; + + st->pwr_down_mode &= ~(0x3 << (this_attr->address * 2)); + st->pwr_down_mode |= (mode << (this_attr->address * 2)); + + return len; +} + +static ssize_t ad5686_read_dac_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5686_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return sprintf(buf, "%d\n", !!(st->pwr_down_mask & + (0x3 << (this_attr->address * 2)))); +} + +static ssize_t ad5686_write_dac_powerdown(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + bool readin; + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5686_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = strtobool(buf, &readin); + if (ret) + return ret; + + if (readin == true) + st->pwr_down_mask |= (0x3 << (this_attr->address * 2)); + else + st->pwr_down_mask &= ~(0x3 << (this_attr->address * 2)); + + ret = ad5686_spi_write(st, AD5686_CMD_POWERDOWN_DAC, 0, + st->pwr_down_mask & st->pwr_down_mode, 0); + + return ret ? ret : len; +} + +static IIO_CONST_ATTR(out_voltage_powerdown_mode_available, + "1kohm_to_gnd 100kohm_to_gnd three_state"); + +#define IIO_DEV_ATTR_DAC_POWERDOWN_MODE(_num) \ + IIO_DEVICE_ATTR(out_voltage##_num##_powerdown_mode, \ + S_IRUGO | S_IWUSR, \ + ad5686_read_powerdown_mode, \ + ad5686_write_powerdown_mode, _num) + +static IIO_DEV_ATTR_DAC_POWERDOWN_MODE(0); +static IIO_DEV_ATTR_DAC_POWERDOWN_MODE(1); +static IIO_DEV_ATTR_DAC_POWERDOWN_MODE(2); +static IIO_DEV_ATTR_DAC_POWERDOWN_MODE(3); + +#define IIO_DEV_ATTR_DAC_POWERDOWN(_num) \ + IIO_DEVICE_ATTR(out_voltage##_num##_powerdown, \ + S_IRUGO | S_IWUSR, \ + ad5686_read_dac_powerdown, \ + ad5686_write_dac_powerdown, _num) + +static IIO_DEV_ATTR_DAC_POWERDOWN(0); +static IIO_DEV_ATTR_DAC_POWERDOWN(1); +static IIO_DEV_ATTR_DAC_POWERDOWN(2); +static IIO_DEV_ATTR_DAC_POWERDOWN(3); + +static struct attribute *ad5686_attributes[] = { + &iio_dev_attr_out_voltage0_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage1_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage2_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage3_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage0_powerdown_mode.dev_attr.attr, + &iio_dev_attr_out_voltage1_powerdown_mode.dev_attr.attr, + &iio_dev_attr_out_voltage2_powerdown_mode.dev_attr.attr, + &iio_dev_attr_out_voltage3_powerdown_mode.dev_attr.attr, + &iio_const_attr_out_voltage_powerdown_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5686_attribute_group = { + .attrs = ad5686_attributes, +}; + +static int ad5686_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5686_state *st = iio_priv(indio_dev); + unsigned long scale_uv; + int ret; + + switch (m) { + case 0: + mutex_lock(&indio_dev->mlock); + ret = ad5686_spi_read(st, chan->address); + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + scale_uv = (st->vref_mv * 100000) + >> (chan->scan_type.realbits); + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + + } + return -EINVAL; +} + +static int ad5686_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5686_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case 0: + if (val > (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad5686_spi_write(st, + AD5686_CMD_WRITE_INPUT_N_UPDATE_N, + chan->address, + val, + chan->scan_type.shift); + mutex_unlock(&indio_dev->mlock); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ad5686_info = { + .read_raw = ad5686_read_raw, + .write_raw = ad5686_write_raw, + .attrs = &ad5686_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad5686_probe(struct spi_device *spi) +{ + struct ad5686_state *st; + struct iio_dev *indio_dev; + int ret, regdone = 0, voltage_uv = 0; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + + voltage_uv = regulator_get_voltage(st->reg); + } + + st->chip_info = + &ad5686_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else + st->vref_mv = st->chip_info->int_vref_mv; + + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5686_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channel; + indio_dev->num_channels = AD5686_DAC_CHANNELS; + + regdone = 1; + ret = ad5686_spi_write(st, AD5686_CMD_INTERNAL_REFER_SETUP, 0, + !!voltage_uv, 0); + if (ret) + goto error_disable_reg; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg; + + return 0; + +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_free_device(indio_dev); + + return ret; +} + +static int __devexit ad5686_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5686_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad5686_id[] = { + {"ad5684", ID_AD5684}, + {"ad5685", ID_AD5685}, + {"ad5686", ID_AD5686}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5686_id); + +static struct spi_driver ad5686_driver = { + .driver = { + .name = "ad5686", + .owner = THIS_MODULE, + }, + .probe = ad5686_probe, + .remove = __devexit_p(ad5686_remove), + .id_table = ad5686_id, +}; +module_spi_driver(ad5686_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD5686/85/84 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dac/ad5764.c b/drivers/staging/iio/dac/ad5764.c new file mode 100644 index 00000000..f73a7307 --- /dev/null +++ b/drivers/staging/iio/dac/ad5764.c @@ -0,0 +1,382 @@ +/* + * Analog devices AD5764, AD5764R, AD5744, AD5744R quad-channel + * Digital to Analog Converters driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dac.h" + +#define AD5764_REG_SF_NOP 0x0 +#define AD5764_REG_SF_CONFIG 0x1 +#define AD5764_REG_SF_CLEAR 0x4 +#define AD5764_REG_SF_LOAD 0x5 +#define AD5764_REG_DATA(x) ((2 << 3) | (x)) +#define AD5764_REG_COARSE_GAIN(x) ((3 << 3) | (x)) +#define AD5764_REG_FINE_GAIN(x) ((4 << 3) | (x)) +#define AD5764_REG_OFFSET(x) ((5 << 3) | (x)) + +#define AD5764_NUM_CHANNELS 4 + +/** + * struct ad5764_chip_info - chip specific information + * @int_vref: Value of the internal reference voltage in uV - 0 if external + * reference voltage is used + * @channel channel specification +*/ + +struct ad5764_chip_info { + unsigned long int_vref; + const struct iio_chan_spec *channels; +}; + +/** + * struct ad5764_state - driver instance specific data + * @spi: spi_device + * @chip_info: chip info + * @vref_reg: vref supply regulators + * @data: spi transfer buffers + */ + +struct ad5764_state { + struct spi_device *spi; + const struct ad5764_chip_info *chip_info; + struct regulator_bulk_data vref_reg[2]; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +enum ad5764_type { + ID_AD5744, + ID_AD5744R, + ID_AD5764, + ID_AD5764R, +}; + +#define AD5764_CHANNEL(_chan, _bits) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (_chan), \ + .address = (_chan), \ + .info_mask = IIO_CHAN_INFO_OFFSET_SHARED_BIT | \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, \ + .scan_type = IIO_ST('u', (_bits), 16, 16 - (_bits)) \ +} + +#define DECLARE_AD5764_CHANNELS(_name, _bits) \ +const struct iio_chan_spec _name##_channels[] = { \ + AD5764_CHANNEL(0, (_bits)), \ + AD5764_CHANNEL(1, (_bits)), \ + AD5764_CHANNEL(2, (_bits)), \ + AD5764_CHANNEL(3, (_bits)), \ +}; + +static DECLARE_AD5764_CHANNELS(ad5764, 16); +static DECLARE_AD5764_CHANNELS(ad5744, 14); + +static const struct ad5764_chip_info ad5764_chip_infos[] = { + [ID_AD5744] = { + .int_vref = 0, + .channels = ad5744_channels, + }, + [ID_AD5744R] = { + .int_vref = 5000000, + .channels = ad5744_channels, + }, + [ID_AD5764] = { + .int_vref = 0, + .channels = ad5764_channels, + }, + [ID_AD5764R] = { + .int_vref = 5000000, + .channels = ad5764_channels, + }, +}; + +static int ad5764_write(struct iio_dev *indio_dev, unsigned int reg, + unsigned int val) +{ + struct ad5764_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&indio_dev->mlock); + st->data[0].d32 = cpu_to_be32((reg << 16) | val); + + ret = spi_write(st->spi, &st->data[0].d8[1], 3); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5764_read(struct iio_dev *indio_dev, unsigned int reg, + unsigned int *val) +{ + struct ad5764_state *st = iio_priv(indio_dev); + struct spi_message m; + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[1], + .len = 3, + .cs_change = 1, + }, { + .rx_buf = &st->data[1].d8[1], + .len = 3, + }, + }; + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + mutex_lock(&indio_dev->mlock); + + st->data[0].d32 = cpu_to_be32((1 << 23) | (reg << 16)); + + ret = spi_sync(st->spi, &m); + if (ret >= 0) + *val = be32_to_cpu(st->data[1].d32) & 0xffff; + + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5764_chan_info_to_reg(struct iio_chan_spec const *chan, long info) +{ + switch (info) { + case 0: + return AD5764_REG_DATA(chan->address); + case IIO_CHAN_INFO_CALIBBIAS: + return AD5764_REG_OFFSET(chan->address); + case IIO_CHAN_INFO_CALIBSCALE: + return AD5764_REG_FINE_GAIN(chan->address); + default: + break; + } + + return 0; +} + +static int ad5764_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long info) +{ + const int max_val = (1 << chan->scan_type.realbits); + unsigned int reg; + + switch (info) { + case 0: + if (val >= max_val || val < 0) + return -EINVAL; + val <<= chan->scan_type.shift; + break; + case IIO_CHAN_INFO_CALIBBIAS: + if (val >= 128 || val < -128) + return -EINVAL; + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (val >= 32 || val < -32) + return -EINVAL; + break; + default: + return -EINVAL; + } + + reg = ad5764_chan_info_to_reg(chan, info); + return ad5764_write(indio_dev, reg, (u16)val); +} + +static int ad5764_get_channel_vref(struct ad5764_state *st, + unsigned int channel) +{ + if (st->chip_info->int_vref) + return st->chip_info->int_vref; + else + return regulator_get_voltage(st->vref_reg[channel / 2].consumer); +} + +static int ad5764_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long info) +{ + struct ad5764_state *st = iio_priv(indio_dev); + unsigned long scale_uv; + unsigned int reg; + int vref; + int ret; + + switch (info) { + case 0: + reg = AD5764_REG_DATA(chan->address); + ret = ad5764_read(indio_dev, reg, val); + if (ret < 0) + return ret; + *val >>= chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + reg = AD5764_REG_OFFSET(chan->address); + ret = ad5764_read(indio_dev, reg, val); + if (ret < 0) + return ret; + *val = sign_extend32(*val, 7); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + reg = AD5764_REG_FINE_GAIN(chan->address); + ret = ad5764_read(indio_dev, reg, val); + if (ret < 0) + return ret; + *val = sign_extend32(*val, 5); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* vout = 4 * vref + ((dac_code / 65535) - 0.5) */ + vref = ad5764_get_channel_vref(st, chan->channel); + if (vref < 0) + return vref; + + scale_uv = (vref * 4 * 100) >> chan->scan_type.realbits; + *val = scale_uv / 100000; + *val2 = (scale_uv % 100000) * 10; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = -(1 << chan->scan_type.realbits) / 2; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info ad5764_info = { + .read_raw = ad5764_read_raw, + .write_raw = ad5764_write_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad5764_probe(struct spi_device *spi) +{ + enum ad5764_type type = spi_get_device_id(spi)->driver_data; + struct iio_dev *indio_dev; + struct ad5764_state *st; + int ret; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + dev_err(&spi->dev, "Failed to allocate iio device\n"); + return -ENOMEM; + } + + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + st->chip_info = &ad5764_chip_infos[type]; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad5764_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = AD5764_NUM_CHANNELS; + indio_dev->channels = st->chip_info->channels; + + if (st->chip_info->int_vref == 0) { + st->vref_reg[0].supply = "vrefAB"; + st->vref_reg[1].supply = "vrefCD"; + + ret = regulator_bulk_get(&st->spi->dev, + ARRAY_SIZE(st->vref_reg), st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to request vref regulators: %d\n", + ret); + goto error_free; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(st->vref_reg), + st->vref_reg); + if (ret) { + dev_err(&spi->dev, "Failed to enable vref regulators: %d\n", + ret); + goto error_free_reg; + } + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&spi->dev, "Failed to register iio device: %d\n", ret); + goto error_disable_reg; + } + + return 0; + +error_disable_reg: + if (st->chip_info->int_vref == 0) + regulator_bulk_disable(ARRAY_SIZE(st->vref_reg), st->vref_reg); +error_free_reg: + if (st->chip_info->int_vref == 0) + regulator_bulk_free(ARRAY_SIZE(st->vref_reg), st->vref_reg); +error_free: + iio_free_device(indio_dev); + + return ret; +} + +static int __devexit ad5764_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5764_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (st->chip_info->int_vref == 0) { + regulator_bulk_disable(ARRAY_SIZE(st->vref_reg), st->vref_reg); + regulator_bulk_free(ARRAY_SIZE(st->vref_reg), st->vref_reg); + } + + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad5764_ids[] = { + { "ad5744", ID_AD5744 }, + { "ad5744r", ID_AD5744R }, + { "ad5764", ID_AD5764 }, + { "ad5764r", ID_AD5764R }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5764_ids); + +static struct spi_driver ad5764_driver = { + .driver = { + .name = "ad5764", + .owner = THIS_MODULE, + }, + .probe = ad5764_probe, + .remove = __devexit_p(ad5764_remove), + .id_table = ad5764_ids, +}; +module_spi_driver(ad5764_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Analog Devices AD5744/AD5744R/AD5764/AD5764R DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dac/ad5791.c b/drivers/staging/iio/dac/ad5791.c new file mode 100644 index 00000000..ac45636a --- /dev/null +++ b/drivers/staging/iio/dac/ad5791.c @@ -0,0 +1,420 @@ +/* + * AD5760, AD5780, AD5781, AD5790, AD5791 Voltage Output Digital to Analog + * Converter + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dac.h" +#include "ad5791.h" + +static int ad5791_spi_write(struct spi_device *spi, u8 addr, u32 val) +{ + union { + u32 d32; + u8 d8[4]; + } data; + + data.d32 = cpu_to_be32(AD5791_CMD_WRITE | + AD5791_ADDR(addr) | + (val & AD5791_DAC_MASK)); + + return spi_write(spi, &data.d8[1], 3); +} + +static int ad5791_spi_read(struct spi_device *spi, u8 addr, u32 *val) +{ + union { + u32 d32; + u8 d8[4]; + } data[3]; + int ret; + struct spi_message msg; + struct spi_transfer xfers[] = { + { + .tx_buf = &data[0].d8[1], + .bits_per_word = 8, + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &data[1].d8[1], + .rx_buf = &data[2].d8[1], + .bits_per_word = 8, + .len = 3, + }, + }; + + data[0].d32 = cpu_to_be32(AD5791_CMD_READ | + AD5791_ADDR(addr)); + data[1].d32 = cpu_to_be32(AD5791_ADDR(AD5791_ADDR_NOOP)); + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(spi, &msg); + + *val = be32_to_cpu(data[2].d32); + + return ret; +} + +#define AD5791_CHAN(bits, shift) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .indexed = 1, \ + .address = AD5791_ADDR_DAC0, \ + .channel = 0, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT | \ + IIO_CHAN_INFO_OFFSET_SHARED_BIT, \ + .scan_type = IIO_ST('u', bits, 24, shift) \ +} + +static const struct iio_chan_spec ad5791_channels[] = { + [ID_AD5760] = AD5791_CHAN(16, 4), + [ID_AD5780] = AD5791_CHAN(18, 2), + [ID_AD5781] = AD5791_CHAN(18, 2), + [ID_AD5791] = AD5791_CHAN(20, 0) +}; + +static ssize_t ad5791_read_powerdown_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5791_state *st = iio_priv(indio_dev); + + const char mode[][14] = {"6kohm_to_gnd", "three_state"}; + + return sprintf(buf, "%s\n", mode[st->pwr_down_mode]); +} + +static ssize_t ad5791_write_powerdown_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5791_state *st = iio_priv(indio_dev); + int ret; + + if (sysfs_streq(buf, "6kohm_to_gnd")) + st->pwr_down_mode = AD5791_DAC_PWRDN_6K; + else if (sysfs_streq(buf, "three_state")) + st->pwr_down_mode = AD5791_DAC_PWRDN_3STATE; + else + ret = -EINVAL; + + return ret ? ret : len; +} + +static ssize_t ad5791_read_dac_powerdown(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5791_state *st = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", st->pwr_down); +} + +static ssize_t ad5791_write_dac_powerdown(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + long readin; + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5791_state *st = iio_priv(indio_dev); + + ret = strict_strtol(buf, 10, &readin); + if (ret) + return ret; + + if (readin == 0) { + st->pwr_down = false; + st->ctrl &= ~(AD5791_CTRL_OPGND | AD5791_CTRL_DACTRI); + } else if (readin == 1) { + st->pwr_down = true; + if (st->pwr_down_mode == AD5791_DAC_PWRDN_6K) + st->ctrl |= AD5791_CTRL_OPGND; + else if (st->pwr_down_mode == AD5791_DAC_PWRDN_3STATE) + st->ctrl |= AD5791_CTRL_DACTRI; + } else + ret = -EINVAL; + + ret = ad5791_spi_write(st->spi, AD5791_ADDR_CTRL, st->ctrl); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage_powerdown_mode, S_IRUGO | + S_IWUSR, ad5791_read_powerdown_mode, + ad5791_write_powerdown_mode, 0); + +static IIO_CONST_ATTR(out_voltage_powerdown_mode_available, + "6kohm_to_gnd three_state"); + +#define IIO_DEV_ATTR_DAC_POWERDOWN(_num, _show, _store, _addr) \ + IIO_DEVICE_ATTR(out_voltage##_num##_powerdown, \ + S_IRUGO | S_IWUSR, _show, _store, _addr) + +static IIO_DEV_ATTR_DAC_POWERDOWN(0, ad5791_read_dac_powerdown, + ad5791_write_dac_powerdown, 0); + +static struct attribute *ad5791_attributes[] = { + &iio_dev_attr_out_voltage0_powerdown.dev_attr.attr, + &iio_dev_attr_out_voltage_powerdown_mode.dev_attr.attr, + &iio_const_attr_out_voltage_powerdown_mode_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5791_attribute_group = { + .attrs = ad5791_attributes, +}; + +static int ad5791_get_lin_comp(unsigned int span) +{ + if (span <= 10000) + return AD5791_LINCOMP_0_10; + else if (span <= 12000) + return AD5791_LINCOMP_10_12; + else if (span <= 16000) + return AD5791_LINCOMP_12_16; + else if (span <= 19000) + return AD5791_LINCOMP_16_19; + else + return AD5791_LINCOMP_19_20; +} + +static int ad5780_get_lin_comp(unsigned int span) +{ + if (span <= 10000) + return AD5780_LINCOMP_0_10; + else + return AD5780_LINCOMP_10_20; +} +static const struct ad5791_chip_info ad5791_chip_info_tbl[] = { + [ID_AD5760] = { + .get_lin_comp = ad5780_get_lin_comp, + }, + [ID_AD5780] = { + .get_lin_comp = ad5780_get_lin_comp, + }, + [ID_AD5781] = { + .get_lin_comp = ad5791_get_lin_comp, + }, + [ID_AD5791] = { + .get_lin_comp = ad5791_get_lin_comp, + }, +}; + +static int ad5791_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5791_state *st = iio_priv(indio_dev); + u64 val64; + int ret; + + switch (m) { + case 0: + ret = ad5791_spi_read(st->spi, chan->address, val); + if (ret) + return ret; + *val &= AD5791_DAC_MASK; + *val >>= chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = (((u64)st->vref_mv) * 1000000ULL) >> chan->scan_type.realbits; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + val64 = (((u64)st->vref_neg_mv) << chan->scan_type.realbits); + do_div(val64, st->vref_mv); + *val = -val64; + return IIO_VAL_INT; + default: + return -EINVAL; + } + +}; + + +static int ad5791_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct ad5791_state *st = iio_priv(indio_dev); + + switch (mask) { + case 0: + val &= AD5791_RES_MASK(chan->scan_type.realbits); + val <<= chan->scan_type.shift; + + return ad5791_spi_write(st->spi, chan->address, val); + + default: + return -EINVAL; + } +} + +static const struct iio_info ad5791_info = { + .read_raw = &ad5791_read_raw, + .write_raw = &ad5791_write_raw, + .attrs = &ad5791_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad5791_probe(struct spi_device *spi) +{ + struct ad5791_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct ad5791_state *st; + int ret, pos_voltage_uv = 0, neg_voltage_uv = 0; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + st->reg_vdd = regulator_get(&spi->dev, "vdd"); + if (!IS_ERR(st->reg_vdd)) { + ret = regulator_enable(st->reg_vdd); + if (ret) + goto error_put_reg_pos; + + pos_voltage_uv = regulator_get_voltage(st->reg_vdd); + } + + st->reg_vss = regulator_get(&spi->dev, "vss"); + if (!IS_ERR(st->reg_vss)) { + ret = regulator_enable(st->reg_vss); + if (ret) + goto error_put_reg_neg; + + neg_voltage_uv = regulator_get_voltage(st->reg_vss); + } + + st->pwr_down = true; + st->spi = spi; + + if (!IS_ERR(st->reg_vss) && !IS_ERR(st->reg_vdd)) { + st->vref_mv = (pos_voltage_uv + neg_voltage_uv) / 1000; + st->vref_neg_mv = neg_voltage_uv / 1000; + } else if (pdata) { + st->vref_mv = pdata->vref_pos_mv + pdata->vref_neg_mv; + st->vref_neg_mv = pdata->vref_neg_mv; + } else { + dev_warn(&spi->dev, "reference voltage unspecified\n"); + } + + ret = ad5791_spi_write(spi, AD5791_ADDR_SW_CTRL, AD5791_SWCTRL_RESET); + if (ret) + goto error_disable_reg_neg; + + st->chip_info = &ad5791_chip_info_tbl[spi_get_device_id(spi) + ->driver_data]; + + + st->ctrl = AD5761_CTRL_LINCOMP(st->chip_info->get_lin_comp(st->vref_mv)) + | ((pdata && pdata->use_rbuf_gain2) ? 0 : AD5791_CTRL_RBUF) | + AD5791_CTRL_BIN2SC; + + ret = ad5791_spi_write(spi, AD5791_ADDR_CTRL, st->ctrl | + AD5791_CTRL_OPGND | AD5791_CTRL_DACTRI); + if (ret) + goto error_disable_reg_neg; + + spi_set_drvdata(spi, indio_dev); + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ad5791_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels + = &ad5791_channels[spi_get_device_id(spi)->driver_data]; + indio_dev->num_channels = 1; + indio_dev->name = spi_get_device_id(st->spi)->name; + ret = iio_device_register(indio_dev); + if (ret) + goto error_disable_reg_neg; + + return 0; + +error_disable_reg_neg: + if (!IS_ERR(st->reg_vss)) + regulator_disable(st->reg_vss); +error_put_reg_neg: + if (!IS_ERR(st->reg_vss)) + regulator_put(st->reg_vss); + + if (!IS_ERR(st->reg_vdd)) + regulator_disable(st->reg_vdd); +error_put_reg_pos: + if (!IS_ERR(st->reg_vdd)) + regulator_put(st->reg_vdd); + iio_free_device(indio_dev); +error_ret: + + return ret; +} + +static int __devexit ad5791_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad5791_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg_vdd)) { + regulator_disable(st->reg_vdd); + regulator_put(st->reg_vdd); + } + + if (!IS_ERR(st->reg_vss)) { + regulator_disable(st->reg_vss); + regulator_put(st->reg_vss); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad5791_id[] = { + {"ad5760", ID_AD5760}, + {"ad5780", ID_AD5780}, + {"ad5781", ID_AD5781}, + {"ad5790", ID_AD5791}, + {"ad5791", ID_AD5791}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5791_id); + +static struct spi_driver ad5791_driver = { + .driver = { + .name = "ad5791", + .owner = THIS_MODULE, + }, + .probe = ad5791_probe, + .remove = __devexit_p(ad5791_remove), + .id_table = ad5791_id, +}; +module_spi_driver(ad5791_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD5760/AD5780/AD5781/AD5790/AD5791 DAC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dac/ad5791.h b/drivers/staging/iio/dac/ad5791.h new file mode 100644 index 00000000..fd7edbdb --- /dev/null +++ b/drivers/staging/iio/dac/ad5791.h @@ -0,0 +1,112 @@ +/* + * AD5791 SPI DAC driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef SPI_AD5791_H_ +#define SPI_AD5791_H_ + +#define AD5791_RES_MASK(x) ((1 << (x)) - 1) +#define AD5791_DAC_MASK AD5791_RES_MASK(20) +#define AD5791_DAC_MSB (1 << 19) + +#define AD5791_CMD_READ (1 << 23) +#define AD5791_CMD_WRITE (0 << 23) +#define AD5791_ADDR(addr) ((addr) << 20) + +/* Registers */ +#define AD5791_ADDR_NOOP 0 +#define AD5791_ADDR_DAC0 1 +#define AD5791_ADDR_CTRL 2 +#define AD5791_ADDR_CLRCODE 3 +#define AD5791_ADDR_SW_CTRL 4 + +/* Control Register */ +#define AD5791_CTRL_RBUF (1 << 1) +#define AD5791_CTRL_OPGND (1 << 2) +#define AD5791_CTRL_DACTRI (1 << 3) +#define AD5791_CTRL_BIN2SC (1 << 4) +#define AD5791_CTRL_SDODIS (1 << 5) +#define AD5761_CTRL_LINCOMP(x) ((x) << 6) + +#define AD5791_LINCOMP_0_10 0 +#define AD5791_LINCOMP_10_12 1 +#define AD5791_LINCOMP_12_16 2 +#define AD5791_LINCOMP_16_19 3 +#define AD5791_LINCOMP_19_20 12 + +#define AD5780_LINCOMP_0_10 0 +#define AD5780_LINCOMP_10_20 12 + +/* Software Control Register */ +#define AD5791_SWCTRL_LDAC (1 << 0) +#define AD5791_SWCTRL_CLR (1 << 1) +#define AD5791_SWCTRL_RESET (1 << 2) + +#define AD5791_DAC_PWRDN_6K 0 +#define AD5791_DAC_PWRDN_3STATE 1 + +/* + * TODO: struct ad5791_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad5791_platform_data - platform specific information + * @vref_pos_mv: Vdd Positive Analog Supply Volatge (mV) + * @vref_neg_mv: Vdd Negative Analog Supply Volatge (mV) + * @use_rbuf_gain2: ext. amplifier connected in gain of two configuration + */ + +struct ad5791_platform_data { + u16 vref_pos_mv; + u16 vref_neg_mv; + bool use_rbuf_gain2; +}; + +/** + * struct ad5791_chip_info - chip specific information + * @get_lin_comp: function pointer to the device specific function + */ + +struct ad5791_chip_info { + int (*get_lin_comp) (unsigned int span); +}; + +/** + * struct ad5791_state - driver instance specific data + * @us: spi_device + * @reg_vdd: positive supply regulator + * @reg_vss: negative supply regulator + * @chip_info: chip model specific constants + * @vref_mv: actual reference voltage used + * @vref_neg_mv: voltage of the negative supply + * @pwr_down_mode current power down mode + */ + +struct ad5791_state { + struct spi_device *spi; + struct regulator *reg_vdd; + struct regulator *reg_vss; + const struct ad5791_chip_info *chip_info; + unsigned short vref_mv; + unsigned int vref_neg_mv; + unsigned ctrl; + unsigned pwr_down_mode; + bool pwr_down; +}; + +/** + * ad5791_supported_device_ids: + */ + +enum ad5791_supported_device_ids { + ID_AD5760, + ID_AD5780, + ID_AD5781, + ID_AD5791, +}; + +#endif /* SPI_AD5791_H_ */ diff --git a/drivers/staging/iio/dac/dac.h b/drivers/staging/iio/dac/dac.h new file mode 100644 index 00000000..0754d715 --- /dev/null +++ b/drivers/staging/iio/dac/dac.h @@ -0,0 +1,6 @@ +/* + * dac.h - sysfs attributes associated with DACs + */ + +#define IIO_DEV_ATTR_OUT_RAW(_num, _store, _addr) \ + IIO_DEVICE_ATTR(out_voltage##_num##_raw, S_IWUSR, NULL, _store, _addr) diff --git a/drivers/staging/iio/dac/max517.c b/drivers/staging/iio/dac/max517.c new file mode 100644 index 00000000..41483c72 --- /dev/null +++ b/drivers/staging/iio/dac/max517.c @@ -0,0 +1,293 @@ +/* + * max517.c - Support for Maxim MAX517, MAX518 and MAX519 + * + * Copyright (C) 2010, 2011 Roland Stigge + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dac.h" + +#include "max517.h" + +#define MAX517_DRV_NAME "max517" + +/* Commands */ +#define COMMAND_CHANNEL0 0x00 +#define COMMAND_CHANNEL1 0x01 /* for MAX518 and MAX519 */ +#define COMMAND_PD 0x08 /* Power Down */ + +enum max517_device_ids { + ID_MAX517, + ID_MAX518, + ID_MAX519, +}; + +struct max517_data { + struct iio_dev *indio_dev; + struct i2c_client *client; + unsigned short vref_mv[2]; +}; + +/* + * channel: bit 0: channel 1 + * bit 1: channel 2 + * (this way, it's possible to set both channels at once) + */ +static ssize_t max517_set_value(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, int channel) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct max517_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + u8 outbuf[4]; /* 1x or 2x command + value */ + int outbuf_size = 0; + int res; + long val; + + res = strict_strtol(buf, 10, &val); + + if (res) + return res; + + if (val < 0 || val > 255) + return -EINVAL; + + if (channel & 1) { + outbuf[outbuf_size++] = COMMAND_CHANNEL0; + outbuf[outbuf_size++] = val; + } + if (channel & 2) { + outbuf[outbuf_size++] = COMMAND_CHANNEL1; + outbuf[outbuf_size++] = val; + } + + /* + * At this point, there are always 1 or 2 two-byte commands in + * outbuf. With 2 commands, the device can set two outputs + * simultaneously, latching the values upon the end of the I2C + * transfer. + */ + + res = i2c_master_send(client, outbuf, outbuf_size); + if (res < 0) + return res; + + return count; +} + +static ssize_t max517_set_value_1(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return max517_set_value(dev, attr, buf, count, 1); +} +static IIO_DEV_ATTR_OUT_RAW(1, max517_set_value_1, 0); + +static ssize_t max517_set_value_2(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return max517_set_value(dev, attr, buf, count, 2); +} +static IIO_DEV_ATTR_OUT_RAW(2, max517_set_value_2, 1); + +static ssize_t max517_set_value_both(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return max517_set_value(dev, attr, buf, count, 3); +} +static IIO_DEVICE_ATTR_NAMED(out_voltage1and2_raw, + out_voltage1&2_raw, S_IWUSR, NULL, + max517_set_value_both, -1); + +static ssize_t max517_show_scale(struct device *dev, + struct device_attribute *attr, + char *buf, int channel) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct max517_data *data = iio_priv(indio_dev); + /* Corresponds to Vref / 2^(bits) */ + unsigned int scale_uv = (data->vref_mv[channel - 1] * 1000) >> 8; + + return sprintf(buf, "%d.%03d\n", scale_uv / 1000, scale_uv % 1000); +} + +static ssize_t max517_show_scale1(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return max517_show_scale(dev, attr, buf, 1); +} +static IIO_DEVICE_ATTR(out_voltage1_scale, S_IRUGO, + max517_show_scale1, NULL, 0); + +static ssize_t max517_show_scale2(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return max517_show_scale(dev, attr, buf, 2); +} +static IIO_DEVICE_ATTR(out_voltage2_scale, S_IRUGO, + max517_show_scale2, NULL, 0); + +/* On MAX517 variant, we have one output */ +static struct attribute *max517_attributes[] = { + &iio_dev_attr_out_voltage1_raw.dev_attr.attr, + &iio_dev_attr_out_voltage1_scale.dev_attr.attr, + NULL +}; + +static struct attribute_group max517_attribute_group = { + .attrs = max517_attributes, +}; + +/* On MAX518 and MAX519 variant, we have two outputs */ +static struct attribute *max518_attributes[] = { + &iio_dev_attr_out_voltage1_raw.dev_attr.attr, + &iio_dev_attr_out_voltage1_scale.dev_attr.attr, + &iio_dev_attr_out_voltage2_raw.dev_attr.attr, + &iio_dev_attr_out_voltage2_scale.dev_attr.attr, + &iio_dev_attr_out_voltage1and2_raw.dev_attr.attr, + NULL +}; + +static struct attribute_group max518_attribute_group = { + .attrs = max518_attributes, +}; + +#ifdef CONFIG_PM_SLEEP +static int max517_suspend(struct device *dev) +{ + u8 outbuf = COMMAND_PD; + + return i2c_master_send(to_i2c_client(dev), &outbuf, 1); +} + +static int max517_resume(struct device *dev) +{ + u8 outbuf = 0; + + return i2c_master_send(to_i2c_client(dev), &outbuf, 1); +} + +static SIMPLE_DEV_PM_OPS(max517_pm_ops, max517_suspend, max517_resume); +#define MAX517_PM_OPS (&max517_pm_ops) +#else +#define MAX517_PM_OPS NULL +#endif + +static const struct iio_info max517_info = { + .attrs = &max517_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info max518_info = { + .attrs = &max518_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int max517_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max517_data *data; + struct iio_dev *indio_dev; + struct max517_platform_data *platform_data = client->dev.platform_data; + int err; + + indio_dev = iio_allocate_device(sizeof(*data)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto exit; + } + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + /* establish that the iio_dev is a child of the i2c device */ + indio_dev->dev.parent = &client->dev; + + /* reduced attribute set for MAX517 */ + if (id->driver_data == ID_MAX517) + indio_dev->info = &max517_info; + else + indio_dev->info = &max518_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* + * Reference voltage on MAX518 and default is 5V, else take vref_mv + * from platform_data + */ + if (id->driver_data == ID_MAX518 || !platform_data) { + data->vref_mv[0] = data->vref_mv[1] = 5000; /* mV */ + } else { + data->vref_mv[0] = platform_data->vref_mv[0]; + data->vref_mv[1] = platform_data->vref_mv[1]; + } + + err = iio_device_register(indio_dev); + if (err) + goto exit_free_device; + + dev_info(&client->dev, "DAC registered\n"); + + return 0; + +exit_free_device: + iio_free_device(indio_dev); +exit: + return err; +} + +static int max517_remove(struct i2c_client *client) +{ + iio_free_device(i2c_get_clientdata(client)); + + return 0; +} + +static const struct i2c_device_id max517_id[] = { + { "max517", ID_MAX517 }, + { "max518", ID_MAX518 }, + { "max519", ID_MAX519 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max517_id); + +static struct i2c_driver max517_driver = { + .driver = { + .name = MAX517_DRV_NAME, + .pm = MAX517_PM_OPS, + }, + .probe = max517_probe, + .remove = max517_remove, + .id_table = max517_id, +}; +module_i2c_driver(max517_driver); + +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("MAX517/MAX518/MAX519 8-bit DAC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/dac/max517.h b/drivers/staging/iio/dac/max517.h new file mode 100644 index 00000000..8106cf24 --- /dev/null +++ b/drivers/staging/iio/dac/max517.h @@ -0,0 +1,19 @@ +/* + * MAX517 DAC driver + * + * Copyright 2011 Roland Stigge + * + * Licensed under the GPL-2 or later. + */ +#ifndef IIO_DAC_MAX517_H_ +#define IIO_DAC_MAX517_H_ + +/* + * TODO: struct max517_platform_data needs to go into include/linux/iio + */ + +struct max517_platform_data { + u16 vref_mv[2]; +}; + +#endif /* IIO_DAC_MAX517_H_ */ diff --git a/drivers/staging/iio/dds/Kconfig b/drivers/staging/iio/dds/Kconfig new file mode 100644 index 00000000..93b7141b --- /dev/null +++ b/drivers/staging/iio/dds/Kconfig @@ -0,0 +1,61 @@ +# +# Direct Digital Synthesis drivers +# +menu "Direct Digital Synthesis" + +config AD5930 + tristate "Analog Devices ad5930/5932 driver" + depends on SPI + help + Say yes here to build support for Analog Devices DDS chip + ad5930/ad5932, provides direct access via sysfs. + +config AD9832 + tristate "Analog Devices ad9832/5 driver" + depends on SPI + help + Say yes here to build support for Analog Devices DDS chip + AD9832 and AD9835, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad9832. + +config AD9834 + tristate "Analog Devices AD9833/4/7/8 driver" + depends on SPI + help + Say yes here to build support for Analog Devices DDS chip + AD9833, AD9834, AD9837 and AD9838, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad9834. + +config AD9850 + tristate "Analog Devices ad9850/1 driver" + depends on SPI + help + Say yes here to build support for Analog Devices DDS chip + ad9850/1, provides direct access via sysfs. + +config AD9852 + tristate "Analog Devices ad9852/4 driver" + depends on SPI + help + Say yes here to build support for Analog Devices DDS chip + ad9852/4, provides direct access via sysfs. + +config AD9910 + tristate "Analog Devices ad9910 driver" + depends on SPI + help + Say yes here to build support for Analog Devices DDS chip + ad9910, provides direct access via sysfs. + +config AD9951 + tristate "Analog Devices ad9951 driver" + depends on SPI + help + Say yes here to build support for Analog Devices DDS chip + ad9951, provides direct access via sysfs. + +endmenu diff --git a/drivers/staging/iio/dds/Makefile b/drivers/staging/iio/dds/Makefile new file mode 100644 index 00000000..14774617 --- /dev/null +++ b/drivers/staging/iio/dds/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for Direct Digital Synthesis drivers +# + +obj-$(CONFIG_AD5930) += ad5930.o +obj-$(CONFIG_AD9832) += ad9832.o +obj-$(CONFIG_AD9834) += ad9834.o +obj-$(CONFIG_AD9850) += ad9850.o +obj-$(CONFIG_AD9852) += ad9852.o +obj-$(CONFIG_AD9910) += ad9910.o +obj-$(CONFIG_AD9951) += ad9951.o diff --git a/drivers/staging/iio/dds/ad5930.c b/drivers/staging/iio/dds/ad5930.c new file mode 100644 index 00000000..9c32d1be --- /dev/null +++ b/drivers/staging/iio/dds/ad5930.c @@ -0,0 +1,151 @@ +/* + * Driver for ADI Direct Digital Synthesis ad5930 + * + * Copyright (c) 2010-2010 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define DRV_NAME "ad5930" + +#define value_mask (u16)0xf000 +#define addr_shift 12 + +/* Register format: 4 bits addr + 12 bits value */ +struct ad5903_config { + u16 control; + u16 incnum; + u16 frqdelt[2]; + u16 incitvl; + u16 buritvl; + u16 strtfrq[2]; +}; + +struct ad5930_state { + struct mutex lock; + struct spi_device *sdev; +}; + +static ssize_t ad5930_set_parameter(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct spi_message msg; + struct spi_transfer xfer; + int ret; + struct ad5903_config *config = (struct ad5903_config *)buf; + struct iio_dev *idev = dev_get_drvdata(dev); + struct ad5930_state *st = iio_priv(idev); + + config->control = (config->control & ~value_mask); + config->incnum = (config->control & ~value_mask) | (1 << addr_shift); + config->frqdelt[0] = (config->control & ~value_mask) | (2 << addr_shift); + config->frqdelt[1] = (config->control & ~value_mask) | 3 << addr_shift; + config->incitvl = (config->control & ~value_mask) | 4 << addr_shift; + config->buritvl = (config->control & ~value_mask) | 8 << addr_shift; + config->strtfrq[0] = (config->control & ~value_mask) | 0xc << addr_shift; + config->strtfrq[1] = (config->control & ~value_mask) | 0xd << addr_shift; + + xfer.len = len; + xfer.tx_buf = config; + mutex_lock(&st->lock); + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(dds, S_IWUSR, NULL, ad5930_set_parameter, 0); + +static struct attribute *ad5930_attributes[] = { + &iio_dev_attr_dds.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad5930_attribute_group = { + .attrs = ad5930_attributes, +}; + +static const struct iio_info ad5930_info = { + .attrs = &ad5930_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad5930_probe(struct spi_device *spi) +{ + struct ad5930_state *st; + struct iio_dev *idev; + int ret = 0; + + idev = iio_allocate_device(sizeof(*st)); + if (idev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + spi_set_drvdata(spi, idev); + st = iio_priv(idev); + + mutex_init(&st->lock); + st->sdev = spi; + idev->dev.parent = &spi->dev; + idev->info = &ad5930_info; + idev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(idev); + if (ret) + goto error_free_dev; + spi->max_speed_hz = 2000000; + spi->mode = SPI_MODE_3; + spi->bits_per_word = 16; + spi_setup(spi); + + return 0; + +error_free_dev: + iio_free_device(idev); +error_ret: + return ret; +} + +static int __devexit ad5930_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static struct spi_driver ad5930_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ad5930_probe, + .remove = __devexit_p(ad5930_remove), +}; +module_spi_driver(ad5930_driver); + +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("Analog Devices ad5930 driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:" DRV_NAME); diff --git a/drivers/staging/iio/dds/ad9832.c b/drivers/staging/iio/dds/ad9832.c new file mode 100644 index 00000000..2ccf25dd --- /dev/null +++ b/drivers/staging/iio/dds/ad9832.c @@ -0,0 +1,362 @@ +/* + * AD9832 SPI DDS driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dds.h" + +#include "ad9832.h" + +static unsigned long ad9832_calc_freqreg(unsigned long mclk, unsigned long fout) +{ + unsigned long long freqreg = (u64) fout * + (u64) ((u64) 1L << AD9832_FREQ_BITS); + do_div(freqreg, mclk); + return freqreg; +} + +static int ad9832_write_frequency(struct ad9832_state *st, + unsigned addr, unsigned long fout) +{ + unsigned long regval; + + if (fout > (st->mclk / 2)) + return -EINVAL; + + regval = ad9832_calc_freqreg(st->mclk, fout); + + st->freq_data[0] = cpu_to_be16((AD9832_CMD_FRE8BITSW << CMD_SHIFT) | + (addr << ADD_SHIFT) | + ((regval >> 24) & 0xFF)); + st->freq_data[1] = cpu_to_be16((AD9832_CMD_FRE16BITSW << CMD_SHIFT) | + ((addr - 1) << ADD_SHIFT) | + ((regval >> 16) & 0xFF)); + st->freq_data[2] = cpu_to_be16((AD9832_CMD_FRE8BITSW << CMD_SHIFT) | + ((addr - 2) << ADD_SHIFT) | + ((regval >> 8) & 0xFF)); + st->freq_data[3] = cpu_to_be16((AD9832_CMD_FRE16BITSW << CMD_SHIFT) | + ((addr - 3) << ADD_SHIFT) | + ((regval >> 0) & 0xFF)); + + return spi_sync(st->spi, &st->freq_msg); +} + +static int ad9832_write_phase(struct ad9832_state *st, + unsigned long addr, unsigned long phase) +{ + if (phase > (1 << AD9832_PHASE_BITS)) + return -EINVAL; + + st->phase_data[0] = cpu_to_be16((AD9832_CMD_PHA8BITSW << CMD_SHIFT) | + (addr << ADD_SHIFT) | + ((phase >> 8) & 0xFF)); + st->phase_data[1] = cpu_to_be16((AD9832_CMD_PHA16BITSW << CMD_SHIFT) | + ((addr - 1) << ADD_SHIFT) | + (phase & 0xFF)); + + return spi_sync(st->spi, &st->phase_msg); +} + +static ssize_t ad9832_write(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad9832_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + goto error_ret; + + mutex_lock(&indio_dev->mlock); + switch ((u32) this_attr->address) { + case AD9832_FREQ0HM: + case AD9832_FREQ1HM: + ret = ad9832_write_frequency(st, this_attr->address, val); + break; + case AD9832_PHASE0H: + case AD9832_PHASE1H: + case AD9832_PHASE2H: + case AD9832_PHASE3H: + ret = ad9832_write_phase(st, this_attr->address, val); + break; + case AD9832_PINCTRL_EN: + if (val) + st->ctrl_ss &= ~AD9832_SELSRC; + else + st->ctrl_ss |= AD9832_SELSRC; + st->data = cpu_to_be16((AD9832_CMD_SYNCSELSRC << CMD_SHIFT) | + st->ctrl_ss); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9832_FREQ_SYM: + if (val == 1) + st->ctrl_fp |= AD9832_FREQ; + else if (val == 0) + st->ctrl_fp &= ~AD9832_FREQ; + else { + ret = -EINVAL; + break; + } + st->data = cpu_to_be16((AD9832_CMD_FPSELECT << CMD_SHIFT) | + st->ctrl_fp); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9832_PHASE_SYM: + if (val < 0 || val > 3) { + ret = -EINVAL; + break; + } + + st->ctrl_fp &= ~AD9832_PHASE(3); + st->ctrl_fp |= AD9832_PHASE(val); + + st->data = cpu_to_be16((AD9832_CMD_FPSELECT << CMD_SHIFT) | + st->ctrl_fp); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9832_OUTPUT_EN: + if (val) + st->ctrl_src &= ~(AD9832_RESET | AD9832_SLEEP | + AD9832_CLR); + else + st->ctrl_src |= AD9832_RESET; + + st->data = cpu_to_be16((AD9832_CMD_SLEEPRESCLR << CMD_SHIFT) | + st->ctrl_src); + ret = spi_sync(st->spi, &st->msg); + break; + default: + ret = -ENODEV; + } + mutex_unlock(&indio_dev->mlock); + +error_ret: + return ret ? ret : len; +} + +/** + * see dds.h for further information + */ + +static IIO_DEV_ATTR_FREQ(0, 0, S_IWUSR, NULL, ad9832_write, AD9832_FREQ0HM); +static IIO_DEV_ATTR_FREQ(0, 1, S_IWUSR, NULL, ad9832_write, AD9832_FREQ1HM); +static IIO_DEV_ATTR_FREQSYMBOL(0, S_IWUSR, NULL, ad9832_write, AD9832_FREQ_SYM); +static IIO_CONST_ATTR_FREQ_SCALE(0, "1"); /* 1Hz */ + +static IIO_DEV_ATTR_PHASE(0, 0, S_IWUSR, NULL, ad9832_write, AD9832_PHASE0H); +static IIO_DEV_ATTR_PHASE(0, 1, S_IWUSR, NULL, ad9832_write, AD9832_PHASE1H); +static IIO_DEV_ATTR_PHASE(0, 2, S_IWUSR, NULL, ad9832_write, AD9832_PHASE2H); +static IIO_DEV_ATTR_PHASE(0, 3, S_IWUSR, NULL, ad9832_write, AD9832_PHASE3H); +static IIO_DEV_ATTR_PHASESYMBOL(0, S_IWUSR, NULL, + ad9832_write, AD9832_PHASE_SYM); +static IIO_CONST_ATTR_PHASE_SCALE(0, "0.0015339808"); /* 2PI/2^12 rad*/ + +static IIO_DEV_ATTR_PINCONTROL_EN(0, S_IWUSR, NULL, + ad9832_write, AD9832_PINCTRL_EN); +static IIO_DEV_ATTR_OUT_ENABLE(0, S_IWUSR, NULL, + ad9832_write, AD9832_OUTPUT_EN); + +static struct attribute *ad9832_attributes[] = { + &iio_dev_attr_dds0_freq0.dev_attr.attr, + &iio_dev_attr_dds0_freq1.dev_attr.attr, + &iio_const_attr_dds0_freq_scale.dev_attr.attr, + &iio_dev_attr_dds0_phase0.dev_attr.attr, + &iio_dev_attr_dds0_phase1.dev_attr.attr, + &iio_dev_attr_dds0_phase2.dev_attr.attr, + &iio_dev_attr_dds0_phase3.dev_attr.attr, + &iio_const_attr_dds0_phase_scale.dev_attr.attr, + &iio_dev_attr_dds0_pincontrol_en.dev_attr.attr, + &iio_dev_attr_dds0_freqsymbol.dev_attr.attr, + &iio_dev_attr_dds0_phasesymbol.dev_attr.attr, + &iio_dev_attr_dds0_out_enable.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9832_attribute_group = { + .attrs = ad9832_attributes, +}; + +static const struct iio_info ad9832_info = { + .attrs = &ad9832_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad9832_probe(struct spi_device *spi) +{ + struct ad9832_platform_data *pdata = spi->dev.platform_data; + struct iio_dev *indio_dev; + struct ad9832_state *st; + struct regulator *reg; + int ret; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + goto error_put_reg; + } + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_disable_reg; + } + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + st->reg = reg; + st->mclk = pdata->mclk; + st->spi = spi; + + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->info = &ad9832_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Setup default messages */ + + st->xfer.tx_buf = &st->data; + st->xfer.len = 2; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + st->freq_xfer[0].tx_buf = &st->freq_data[0]; + st->freq_xfer[0].len = 2; + st->freq_xfer[0].cs_change = 1; + st->freq_xfer[1].tx_buf = &st->freq_data[1]; + st->freq_xfer[1].len = 2; + st->freq_xfer[1].cs_change = 1; + st->freq_xfer[2].tx_buf = &st->freq_data[2]; + st->freq_xfer[2].len = 2; + st->freq_xfer[2].cs_change = 1; + st->freq_xfer[3].tx_buf = &st->freq_data[3]; + st->freq_xfer[3].len = 2; + + spi_message_init(&st->freq_msg); + spi_message_add_tail(&st->freq_xfer[0], &st->freq_msg); + spi_message_add_tail(&st->freq_xfer[1], &st->freq_msg); + spi_message_add_tail(&st->freq_xfer[2], &st->freq_msg); + spi_message_add_tail(&st->freq_xfer[3], &st->freq_msg); + + st->phase_xfer[0].tx_buf = &st->phase_data[0]; + st->phase_xfer[0].len = 2; + st->phase_xfer[0].cs_change = 1; + st->phase_xfer[1].tx_buf = &st->phase_data[1]; + st->phase_xfer[1].len = 2; + + spi_message_init(&st->phase_msg); + spi_message_add_tail(&st->phase_xfer[0], &st->phase_msg); + spi_message_add_tail(&st->phase_xfer[1], &st->phase_msg); + + st->ctrl_src = AD9832_SLEEP | AD9832_RESET | AD9832_CLR; + st->data = cpu_to_be16((AD9832_CMD_SLEEPRESCLR << CMD_SHIFT) | + st->ctrl_src); + ret = spi_sync(st->spi, &st->msg); + if (ret) { + dev_err(&spi->dev, "device init failed\n"); + goto error_free_device; + } + + ret = ad9832_write_frequency(st, AD9832_FREQ0HM, pdata->freq0); + if (ret) + goto error_free_device; + + ret = ad9832_write_frequency(st, AD9832_FREQ1HM, pdata->freq1); + if (ret) + goto error_free_device; + + ret = ad9832_write_phase(st, AD9832_PHASE0H, pdata->phase0); + if (ret) + goto error_free_device; + + ret = ad9832_write_phase(st, AD9832_PHASE1H, pdata->phase1); + if (ret) + goto error_free_device; + + ret = ad9832_write_phase(st, AD9832_PHASE2H, pdata->phase2); + if (ret) + goto error_free_device; + + ret = ad9832_write_phase(st, AD9832_PHASE3H, pdata->phase3); + if (ret) + goto error_free_device; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_device; + + return 0; + +error_free_device: + iio_free_device(indio_dev); +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); +error_put_reg: + if (!IS_ERR(reg)) + regulator_put(reg); + + return ret; +} + +static int __devexit ad9832_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad9832_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad9832_id[] = { + {"ad9832", 0}, + {"ad9835", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad9832_id); + +static struct spi_driver ad9832_driver = { + .driver = { + .name = "ad9832", + .owner = THIS_MODULE, + }, + .probe = ad9832_probe, + .remove = __devexit_p(ad9832_remove), + .id_table = ad9832_id, +}; +module_spi_driver(ad9832_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD9832/AD9835 DDS"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dds/ad9832.h b/drivers/staging/iio/dds/ad9832.h new file mode 100644 index 00000000..c5b701f8 --- /dev/null +++ b/drivers/staging/iio/dds/ad9832.h @@ -0,0 +1,126 @@ +/* + * AD9832 SPI DDS driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ +#ifndef IIO_DDS_AD9832_H_ +#define IIO_DDS_AD9832_H_ + +/* Registers */ + +#define AD9832_FREQ0LL 0x0 +#define AD9832_FREQ0HL 0x1 +#define AD9832_FREQ0LM 0x2 +#define AD9832_FREQ0HM 0x3 +#define AD9832_FREQ1LL 0x4 +#define AD9832_FREQ1HL 0x5 +#define AD9832_FREQ1LM 0x6 +#define AD9832_FREQ1HM 0x7 +#define AD9832_PHASE0L 0x8 +#define AD9832_PHASE0H 0x9 +#define AD9832_PHASE1L 0xA +#define AD9832_PHASE1H 0xB +#define AD9832_PHASE2L 0xC +#define AD9832_PHASE2H 0xD +#define AD9832_PHASE3L 0xE +#define AD9832_PHASE3H 0xF + +#define AD9832_PHASE_SYM 0x10 +#define AD9832_FREQ_SYM 0x11 +#define AD9832_PINCTRL_EN 0x12 +#define AD9832_OUTPUT_EN 0x13 + +/* Command Control Bits */ + +#define AD9832_CMD_PHA8BITSW 0x1 +#define AD9832_CMD_PHA16BITSW 0x0 +#define AD9832_CMD_FRE8BITSW 0x3 +#define AD9832_CMD_FRE16BITSW 0x2 +#define AD9832_CMD_FPSELECT 0x6 +#define AD9832_CMD_SYNCSELSRC 0x8 +#define AD9832_CMD_SLEEPRESCLR 0xC + +#define AD9832_FREQ (1 << 11) +#define AD9832_PHASE(x) (((x) & 3) << 9) +#define AD9832_SYNC (1 << 13) +#define AD9832_SELSRC (1 << 12) +#define AD9832_SLEEP (1 << 13) +#define AD9832_RESET (1 << 12) +#define AD9832_CLR (1 << 11) +#define CMD_SHIFT 12 +#define ADD_SHIFT 8 +#define AD9832_FREQ_BITS 32 +#define AD9832_PHASE_BITS 12 +#define RES_MASK(bits) ((1 << (bits)) - 1) + +/** + * struct ad9832_state - driver instance specific data + * @spi: spi_device + * @reg: supply regulator + * @mclk: external master clock + * @ctrl_fp: cached frequency/phase control word + * @ctrl_ss: cached sync/selsrc control word + * @ctrl_src: cached sleep/reset/clr word + * @xfer: default spi transfer + * @msg: default spi message + * @freq_xfer: tuning word spi transfer + * @freq_msg: tuning word spi message + * @phase_xfer: tuning word spi transfer + * @phase_msg: tuning word spi message + * @data: spi transmit buffer + * @phase_data: tuning word spi transmit buffer + * @freq_data: tuning word spi transmit buffer + */ + +struct ad9832_state { + struct spi_device *spi; + struct regulator *reg; + unsigned long mclk; + unsigned short ctrl_fp; + unsigned short ctrl_ss; + unsigned short ctrl_src; + struct spi_transfer xfer; + struct spi_message msg; + struct spi_transfer freq_xfer[4]; + struct spi_message freq_msg; + struct spi_transfer phase_xfer[2]; + struct spi_message phase_msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + unsigned short freq_data[4]____cacheline_aligned; + unsigned short phase_data[2]; + unsigned short data; + }; +}; + +/* + * TODO: struct ad9832_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad9832_platform_data - platform specific information + * @mclk: master clock in Hz + * @freq0: power up freq0 tuning word in Hz + * @freq1: power up freq1 tuning word in Hz + * @phase0: power up phase0 value [0..4095] correlates with 0..2PI + * @phase1: power up phase1 value [0..4095] correlates with 0..2PI + * @phase2: power up phase2 value [0..4095] correlates with 0..2PI + * @phase3: power up phase3 value [0..4095] correlates with 0..2PI + */ + +struct ad9832_platform_data { + unsigned long mclk; + unsigned long freq0; + unsigned long freq1; + unsigned short phase0; + unsigned short phase1; + unsigned short phase2; + unsigned short phase3; +}; + +#endif /* IIO_DDS_AD9832_H_ */ diff --git a/drivers/staging/iio/dds/ad9834.c b/drivers/staging/iio/dds/ad9834.c new file mode 100644 index 00000000..38a2de08 --- /dev/null +++ b/drivers/staging/iio/dds/ad9834.c @@ -0,0 +1,464 @@ +/* + * AD9833/AD9834/AD9837/AD9838 SPI DDS driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "dds.h" + +#include "ad9834.h" + +static unsigned int ad9834_calc_freqreg(unsigned long mclk, unsigned long fout) +{ + unsigned long long freqreg = (u64) fout * (u64) (1 << AD9834_FREQ_BITS); + do_div(freqreg, mclk); + return freqreg; +} + +static int ad9834_write_frequency(struct ad9834_state *st, + unsigned long addr, unsigned long fout) +{ + unsigned long regval; + + if (fout > (st->mclk / 2)) + return -EINVAL; + + regval = ad9834_calc_freqreg(st->mclk, fout); + + st->freq_data[0] = cpu_to_be16(addr | (regval & + RES_MASK(AD9834_FREQ_BITS / 2))); + st->freq_data[1] = cpu_to_be16(addr | ((regval >> + (AD9834_FREQ_BITS / 2)) & + RES_MASK(AD9834_FREQ_BITS / 2))); + + return spi_sync(st->spi, &st->freq_msg); +} + +static int ad9834_write_phase(struct ad9834_state *st, + unsigned long addr, unsigned long phase) +{ + if (phase > (1 << AD9834_PHASE_BITS)) + return -EINVAL; + st->data = cpu_to_be16(addr | phase); + + return spi_sync(st->spi, &st->msg); +} + +static ssize_t ad9834_write(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad9834_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + goto error_ret; + + mutex_lock(&indio_dev->mlock); + switch ((u32) this_attr->address) { + case AD9834_REG_FREQ0: + case AD9834_REG_FREQ1: + ret = ad9834_write_frequency(st, this_attr->address, val); + break; + case AD9834_REG_PHASE0: + case AD9834_REG_PHASE1: + ret = ad9834_write_phase(st, this_attr->address, val); + break; + case AD9834_OPBITEN: + if (st->control & AD9834_MODE) { + ret = -EINVAL; /* AD9843 reserved mode */ + break; + } + + if (val) + st->control |= AD9834_OPBITEN; + else + st->control &= ~AD9834_OPBITEN; + + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9834_PIN_SW: + if (val) + st->control |= AD9834_PIN_SW; + else + st->control &= ~AD9834_PIN_SW; + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9834_FSEL: + case AD9834_PSEL: + if (val == 0) + st->control &= ~(this_attr->address | AD9834_PIN_SW); + else if (val == 1) { + st->control |= this_attr->address; + st->control &= ~AD9834_PIN_SW; + } else { + ret = -EINVAL; + break; + } + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + break; + case AD9834_RESET: + if (val) + st->control &= ~AD9834_RESET; + else + st->control |= AD9834_RESET; + + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + break; + default: + ret = -ENODEV; + } + mutex_unlock(&indio_dev->mlock); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ad9834_store_wavetype(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad9834_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret = 0; + bool is_ad9833_7 = (st->devid == ID_AD9833) || (st->devid == ID_AD9837); + + mutex_lock(&indio_dev->mlock); + + switch ((u32) this_attr->address) { + case 0: + if (sysfs_streq(buf, "sine")) { + st->control &= ~AD9834_MODE; + if (is_ad9833_7) + st->control &= ~AD9834_OPBITEN; + } else if (sysfs_streq(buf, "triangle")) { + if (is_ad9833_7) { + st->control &= ~AD9834_OPBITEN; + st->control |= AD9834_MODE; + } else if (st->control & AD9834_OPBITEN) { + ret = -EINVAL; /* AD9843 reserved mode */ + } else { + st->control |= AD9834_MODE; + } + } else if (is_ad9833_7 && sysfs_streq(buf, "square")) { + st->control &= ~AD9834_MODE; + st->control |= AD9834_OPBITEN; + } else { + ret = -EINVAL; + } + + break; + case 1: + if (sysfs_streq(buf, "square") && + !(st->control & AD9834_MODE)) { + st->control &= ~AD9834_MODE; + st->control |= AD9834_OPBITEN; + } else { + ret = -EINVAL; + } + break; + default: + ret = -EINVAL; + break; + } + + if (!ret) { + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + } + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static ssize_t ad9834_show_out0_wavetype_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad9834_state *st = iio_priv(indio_dev); + char *str; + + if ((st->devid == ID_AD9833) || (st->devid == ID_AD9837)) + str = "sine triangle square"; + else if (st->control & AD9834_OPBITEN) + str = "sine"; + else + str = "sine triangle"; + + return sprintf(buf, "%s\n", str); +} + + +static IIO_DEVICE_ATTR(dds0_out0_wavetype_available, S_IRUGO, + ad9834_show_out0_wavetype_available, NULL, 0); + +static ssize_t ad9834_show_out1_wavetype_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad9834_state *st = iio_priv(indio_dev); + char *str; + + if (st->control & AD9834_MODE) + str = ""; + else + str = "square"; + + return sprintf(buf, "%s\n", str); +} + +static IIO_DEVICE_ATTR(dds0_out1_wavetype_available, S_IRUGO, + ad9834_show_out1_wavetype_available, NULL, 0); + +/** + * see dds.h for further information + */ + +static IIO_DEV_ATTR_FREQ(0, 0, S_IWUSR, NULL, ad9834_write, AD9834_REG_FREQ0); +static IIO_DEV_ATTR_FREQ(0, 1, S_IWUSR, NULL, ad9834_write, AD9834_REG_FREQ1); +static IIO_DEV_ATTR_FREQSYMBOL(0, S_IWUSR, NULL, ad9834_write, AD9834_FSEL); +static IIO_CONST_ATTR_FREQ_SCALE(0, "1"); /* 1Hz */ + +static IIO_DEV_ATTR_PHASE(0, 0, S_IWUSR, NULL, ad9834_write, AD9834_REG_PHASE0); +static IIO_DEV_ATTR_PHASE(0, 1, S_IWUSR, NULL, ad9834_write, AD9834_REG_PHASE1); +static IIO_DEV_ATTR_PHASESYMBOL(0, S_IWUSR, NULL, ad9834_write, AD9834_PSEL); +static IIO_CONST_ATTR_PHASE_SCALE(0, "0.0015339808"); /* 2PI/2^12 rad*/ + +static IIO_DEV_ATTR_PINCONTROL_EN(0, S_IWUSR, NULL, + ad9834_write, AD9834_PIN_SW); +static IIO_DEV_ATTR_OUT_ENABLE(0, S_IWUSR, NULL, ad9834_write, AD9834_RESET); +static IIO_DEV_ATTR_OUTY_ENABLE(0, 1, S_IWUSR, NULL, + ad9834_write, AD9834_OPBITEN); +static IIO_DEV_ATTR_OUT_WAVETYPE(0, 0, ad9834_store_wavetype, 0); +static IIO_DEV_ATTR_OUT_WAVETYPE(0, 1, ad9834_store_wavetype, 1); + +static struct attribute *ad9834_attributes[] = { + &iio_dev_attr_dds0_freq0.dev_attr.attr, + &iio_dev_attr_dds0_freq1.dev_attr.attr, + &iio_const_attr_dds0_freq_scale.dev_attr.attr, + &iio_dev_attr_dds0_phase0.dev_attr.attr, + &iio_dev_attr_dds0_phase1.dev_attr.attr, + &iio_const_attr_dds0_phase_scale.dev_attr.attr, + &iio_dev_attr_dds0_pincontrol_en.dev_attr.attr, + &iio_dev_attr_dds0_freqsymbol.dev_attr.attr, + &iio_dev_attr_dds0_phasesymbol.dev_attr.attr, + &iio_dev_attr_dds0_out_enable.dev_attr.attr, + &iio_dev_attr_dds0_out1_enable.dev_attr.attr, + &iio_dev_attr_dds0_out0_wavetype.dev_attr.attr, + &iio_dev_attr_dds0_out1_wavetype.dev_attr.attr, + &iio_dev_attr_dds0_out0_wavetype_available.dev_attr.attr, + &iio_dev_attr_dds0_out1_wavetype_available.dev_attr.attr, + NULL, +}; + +static struct attribute *ad9833_attributes[] = { + &iio_dev_attr_dds0_freq0.dev_attr.attr, + &iio_dev_attr_dds0_freq1.dev_attr.attr, + &iio_const_attr_dds0_freq_scale.dev_attr.attr, + &iio_dev_attr_dds0_phase0.dev_attr.attr, + &iio_dev_attr_dds0_phase1.dev_attr.attr, + &iio_const_attr_dds0_phase_scale.dev_attr.attr, + &iio_dev_attr_dds0_freqsymbol.dev_attr.attr, + &iio_dev_attr_dds0_phasesymbol.dev_attr.attr, + &iio_dev_attr_dds0_out_enable.dev_attr.attr, + &iio_dev_attr_dds0_out0_wavetype.dev_attr.attr, + &iio_dev_attr_dds0_out0_wavetype_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9834_attribute_group = { + .attrs = ad9834_attributes, +}; + +static const struct attribute_group ad9833_attribute_group = { + .attrs = ad9833_attributes, +}; + +static const struct iio_info ad9834_info = { + .attrs = &ad9834_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct iio_info ad9833_info = { + .attrs = &ad9833_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad9834_probe(struct spi_device *spi) +{ + struct ad9834_platform_data *pdata = spi->dev.platform_data; + struct ad9834_state *st; + struct iio_dev *indio_dev; + struct regulator *reg; + int ret; + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + reg = regulator_get(&spi->dev, "vcc"); + if (!IS_ERR(reg)) { + ret = regulator_enable(reg); + if (ret) + goto error_put_reg; + } + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_disable_reg; + } + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + st->mclk = pdata->mclk; + st->spi = spi; + st->devid = spi_get_device_id(spi)->driver_data; + st->reg = reg; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + switch (st->devid) { + case ID_AD9833: + case ID_AD9837: + indio_dev->info = &ad9833_info; + break; + default: + indio_dev->info = &ad9834_info; + break; + } + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Setup default messages */ + + st->xfer.tx_buf = &st->data; + st->xfer.len = 2; + + spi_message_init(&st->msg); + spi_message_add_tail(&st->xfer, &st->msg); + + st->freq_xfer[0].tx_buf = &st->freq_data[0]; + st->freq_xfer[0].len = 2; + st->freq_xfer[0].cs_change = 1; + st->freq_xfer[1].tx_buf = &st->freq_data[1]; + st->freq_xfer[1].len = 2; + + spi_message_init(&st->freq_msg); + spi_message_add_tail(&st->freq_xfer[0], &st->freq_msg); + spi_message_add_tail(&st->freq_xfer[1], &st->freq_msg); + + st->control = AD9834_B28 | AD9834_RESET; + + if (!pdata->en_div2) + st->control |= AD9834_DIV2; + + if (!pdata->en_signbit_msb_out && (st->devid == ID_AD9834)) + st->control |= AD9834_SIGN_PIB; + + st->data = cpu_to_be16(AD9834_REG_CMD | st->control); + ret = spi_sync(st->spi, &st->msg); + if (ret) { + dev_err(&spi->dev, "device init failed\n"); + goto error_free_device; + } + + ret = ad9834_write_frequency(st, AD9834_REG_FREQ0, pdata->freq0); + if (ret) + goto error_free_device; + + ret = ad9834_write_frequency(st, AD9834_REG_FREQ1, pdata->freq1); + if (ret) + goto error_free_device; + + ret = ad9834_write_phase(st, AD9834_REG_PHASE0, pdata->phase0); + if (ret) + goto error_free_device; + + ret = ad9834_write_phase(st, AD9834_REG_PHASE1, pdata->phase1); + if (ret) + goto error_free_device; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_device; + + return 0; + +error_free_device: + iio_free_device(indio_dev); +error_disable_reg: + if (!IS_ERR(reg)) + regulator_disable(reg); +error_put_reg: + if (!IS_ERR(reg)) + regulator_put(reg); + return ret; +} + +static int __devexit ad9834_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ad9834_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad9834_id[] = { + {"ad9833", ID_AD9833}, + {"ad9834", ID_AD9834}, + {"ad9837", ID_AD9837}, + {"ad9838", ID_AD9838}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad9834_id); + +static struct spi_driver ad9834_driver = { + .driver = { + .name = "ad9834", + .owner = THIS_MODULE, + }, + .probe = ad9834_probe, + .remove = __devexit_p(ad9834_remove), + .id_table = ad9834_id, +}; +module_spi_driver(ad9834_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD9833/AD9834/AD9837/AD9838 DDS"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/dds/ad9834.h b/drivers/staging/iio/dds/ad9834.h new file mode 100644 index 00000000..ed5ed8d0 --- /dev/null +++ b/drivers/staging/iio/dds/ad9834.h @@ -0,0 +1,112 @@ +/* + * AD9833/AD9834/AD9837/AD9838 SPI DDS driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#ifndef IIO_DDS_AD9834_H_ +#define IIO_DDS_AD9834_H_ + +/* Registers */ + +#define AD9834_REG_CMD (0 << 14) +#define AD9834_REG_FREQ0 (1 << 14) +#define AD9834_REG_FREQ1 (2 << 14) +#define AD9834_REG_PHASE0 (6 << 13) +#define AD9834_REG_PHASE1 (7 << 13) + +/* Command Control Bits */ + +#define AD9834_B28 (1 << 13) +#define AD9834_HLB (1 << 12) +#define AD9834_FSEL (1 << 11) +#define AD9834_PSEL (1 << 10) +#define AD9834_PIN_SW (1 << 9) +#define AD9834_RESET (1 << 8) +#define AD9834_SLEEP1 (1 << 7) +#define AD9834_SLEEP12 (1 << 6) +#define AD9834_OPBITEN (1 << 5) +#define AD9834_SIGN_PIB (1 << 4) +#define AD9834_DIV2 (1 << 3) +#define AD9834_MODE (1 << 1) + +#define AD9834_FREQ_BITS 28 +#define AD9834_PHASE_BITS 12 + +#define RES_MASK(bits) ((1 << (bits)) - 1) + +/** + * struct ad9834_state - driver instance specific data + * @spi: spi_device + * @reg: supply regulator + * @mclk: external master clock + * @control: cached control word + * @xfer: default spi transfer + * @msg: default spi message + * @freq_xfer: tuning word spi transfer + * @freq_msg: tuning word spi message + * @data: spi transmit buffer + * @freq_data: tuning word spi transmit buffer + */ + +struct ad9834_state { + struct spi_device *spi; + struct regulator *reg; + unsigned int mclk; + unsigned short control; + unsigned short devid; + struct spi_transfer xfer; + struct spi_message msg; + struct spi_transfer freq_xfer[2]; + struct spi_message freq_msg; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + unsigned short data ____cacheline_aligned; + unsigned short freq_data[2] ; +}; + + +/* + * TODO: struct ad7887_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad9834_platform_data - platform specific information + * @mclk: master clock in Hz + * @freq0: power up freq0 tuning word in Hz + * @freq1: power up freq1 tuning word in Hz + * @phase0: power up phase0 value [0..4095] correlates with 0..2PI + * @phase1: power up phase1 value [0..4095] correlates with 0..2PI + * @en_div2: digital output/2 is passed to the SIGN BIT OUT pin + * @en_signbit_msb_out: the MSB (or MSB/2) of the DAC data is connected to the + * SIGN BIT OUT pin. en_div2 controls whether it is the MSB + * or MSB/2 that is output. if en_signbit_msb_out=false, + * the on-board comparator is connected to SIGN BIT OUT + */ + +struct ad9834_platform_data { + unsigned int mclk; + unsigned int freq0; + unsigned int freq1; + unsigned short phase0; + unsigned short phase1; + bool en_div2; + bool en_signbit_msb_out; +}; + +/** + * ad9834_supported_device_ids: + */ + +enum ad9834_supported_device_ids { + ID_AD9833, + ID_AD9834, + ID_AD9837, + ID_AD9838, +}; + +#endif /* IIO_DDS_AD9834_H_ */ diff --git a/drivers/staging/iio/dds/ad9850.c b/drivers/staging/iio/dds/ad9850.c new file mode 100644 index 00000000..f4f731bb --- /dev/null +++ b/drivers/staging/iio/dds/ad9850.c @@ -0,0 +1,137 @@ +/* + * Driver for ADI Direct Digital Synthesis ad9850 + * + * Copyright (c) 2010-2010 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define DRV_NAME "ad9850" + +#define value_mask (u16)0xf000 +#define addr_shift 12 + +/* Register format: 4 bits addr + 12 bits value */ +struct ad9850_config { + u8 control[5]; +}; + +struct ad9850_state { + struct mutex lock; + struct spi_device *sdev; +}; + +static ssize_t ad9850_set_parameter(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct spi_message msg; + struct spi_transfer xfer; + int ret; + struct ad9850_config *config = (struct ad9850_config *)buf; + struct iio_dev *idev = dev_get_drvdata(dev); + struct ad9850_state *st = iio_priv(idev); + + xfer.len = len; + xfer.tx_buf = config; + mutex_lock(&st->lock); + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(dds, S_IWUSR, NULL, ad9850_set_parameter, 0); + +static struct attribute *ad9850_attributes[] = { + &iio_dev_attr_dds.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9850_attribute_group = { + .attrs = ad9850_attributes, +}; + +static const struct iio_info ad9850_info = { + .attrs = &ad9850_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad9850_probe(struct spi_device *spi) +{ + struct ad9850_state *st; + struct iio_dev *idev; + int ret = 0; + + idev = iio_allocate_device(sizeof(*st)); + if (idev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + spi_set_drvdata(spi, idev); + st = iio_priv(idev); + mutex_init(&st->lock); + st->sdev = spi; + + idev->dev.parent = &spi->dev; + idev->info = &ad9850_info; + idev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(idev); + if (ret) + goto error_free_dev; + spi->max_speed_hz = 2000000; + spi->mode = SPI_MODE_3; + spi->bits_per_word = 16; + spi_setup(spi); + + return 0; + +error_free_dev: + iio_free_device(idev); +error_ret: + return ret; +} + +static int __devexit ad9850_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static struct spi_driver ad9850_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ad9850_probe, + .remove = __devexit_p(ad9850_remove), +}; +module_spi_driver(ad9850_driver); + +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("Analog Devices ad9850 driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:" DRV_NAME); diff --git a/drivers/staging/iio/dds/ad9852.c b/drivers/staging/iio/dds/ad9852.c new file mode 100644 index 00000000..554266c6 --- /dev/null +++ b/drivers/staging/iio/dds/ad9852.c @@ -0,0 +1,288 @@ +/* + * Driver for ADI Direct Digital Synthesis ad9852 + * + * Copyright (c) 2010 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define DRV_NAME "ad9852" + +#define addr_phaad1 0x0 +#define addr_phaad2 0x1 +#define addr_fretu1 0x2 +#define addr_fretu2 0x3 +#define addr_delfre 0x4 +#define addr_updclk 0x5 +#define addr_ramclk 0x6 +#define addr_contrl 0x7 +#define addr_optskm 0x8 +#define addr_optskr 0xa +#define addr_dacctl 0xb + +#define COMPPD (1 << 4) +#define REFMULT2 (1 << 2) +#define BYPPLL (1 << 5) +#define PLLRANG (1 << 6) +#define IEUPCLK (1) +#define OSKEN (1 << 5) + +#define read_bit (1 << 7) + +/* Register format: 1 byte addr + value */ +struct ad9852_config { + u8 phajst0[3]; + u8 phajst1[3]; + u8 fretun1[6]; + u8 fretun2[6]; + u8 dltafre[6]; + u8 updtclk[5]; + u8 ramprat[4]; + u8 control[5]; + u8 outpskm[3]; + u8 outpskr[2]; + u8 daccntl[3]; +}; + +struct ad9852_state { + struct mutex lock; + struct spi_device *sdev; +}; + +static ssize_t ad9852_set_parameter(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct spi_message msg; + struct spi_transfer xfer; + int ret; + struct ad9852_config *config = (struct ad9852_config *)buf; + struct iio_dev *idev = dev_get_drvdata(dev); + struct ad9852_state *st = iio_priv(idev); + + xfer.len = 3; + xfer.tx_buf = &config->phajst0[0]; + mutex_lock(&st->lock); + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 3; + xfer.tx_buf = &config->phajst1[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 6; + xfer.tx_buf = &config->fretun1[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 6; + xfer.tx_buf = &config->fretun2[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 6; + xfer.tx_buf = &config->dltafre[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 5; + xfer.tx_buf = &config->updtclk[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 4; + xfer.tx_buf = &config->ramprat[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 5; + xfer.tx_buf = &config->control[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 3; + xfer.tx_buf = &config->outpskm[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 2; + xfer.tx_buf = &config->outpskr[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 3; + xfer.tx_buf = &config->daccntl[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(dds, S_IWUSR, NULL, ad9852_set_parameter, 0); + +static void ad9852_init(struct ad9852_state *st) +{ + struct spi_message msg; + struct spi_transfer xfer; + int ret; + u8 config[5]; + + config[0] = addr_contrl; + config[1] = COMPPD; + config[2] = REFMULT2 | BYPPLL | PLLRANG; + config[3] = IEUPCLK; + config[4] = OSKEN; + + mutex_lock(&st->lock); + + xfer.len = 5; + xfer.tx_buf = &config; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + +error_ret: + mutex_unlock(&st->lock); + + + +} + +static struct attribute *ad9852_attributes[] = { + &iio_dev_attr_dds.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9852_attribute_group = { + .attrs = ad9852_attributes, +}; + +static const struct iio_info ad9852_info = { + .attrs = &ad9852_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad9852_probe(struct spi_device *spi) +{ + struct ad9852_state *st; + struct iio_dev *idev; + int ret = 0; + + idev = iio_allocate_device(sizeof(*st)); + if (idev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(idev); + spi_set_drvdata(spi, idev); + mutex_init(&st->lock); + st->sdev = spi; + + idev->dev.parent = &spi->dev; + idev->info = &ad9852_info; + idev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(idev); + if (ret) + goto error_free_dev; + spi->max_speed_hz = 2000000; + spi->mode = SPI_MODE_3; + spi->bits_per_word = 8; + spi_setup(spi); + ad9852_init(st); + + return 0; + +error_free_dev: + iio_free_device(idev); + +error_ret: + return ret; +} + +static int __devexit ad9852_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static struct spi_driver ad9852_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ad9852_probe, + .remove = __devexit_p(ad9852_remove), +}; +module_spi_driver(ad9852_driver); + +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("Analog Devices ad9852 driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:" DRV_NAME); diff --git a/drivers/staging/iio/dds/ad9910.c b/drivers/staging/iio/dds/ad9910.c new file mode 100644 index 00000000..3985766d --- /dev/null +++ b/drivers/staging/iio/dds/ad9910.c @@ -0,0 +1,421 @@ +/* + * Driver for ADI Direct Digital Synthesis ad9910 + * + * Copyright (c) 2010 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define DRV_NAME "ad9910" + +#define CFR1 0x0 +#define CFR2 0x1 +#define CFR3 0x2 + +#define AUXDAC 0x3 +#define IOUPD 0x4 +#define FTW 0x7 +#define POW 0x8 +#define ASF 0x9 +#define MULTC 0x0A +#define DIG_RAMPL 0x0B +#define DIG_RAMPS 0x0C +#define DIG_RAMPR 0x0D +#define SIN_TONEP0 0x0E +#define SIN_TONEP1 0x0F +#define SIN_TONEP2 0x10 +#define SIN_TONEP3 0x11 +#define SIN_TONEP4 0x12 +#define SIN_TONEP5 0x13 +#define SIN_TONEP6 0x14 +#define SIN_TONEP7 0x15 + +#define RAM_ENABLE (1 << 7) + +#define MANUAL_OSK (1 << 7) +#define INVSIC (1 << 6) +#define DDS_SINEOP (1) + +#define AUTO_OSK (1) +#define OSKEN (1 << 1) +#define LOAD_ARR (1 << 2) +#define CLR_PHA (1 << 3) +#define CLR_DIG (1 << 4) +#define ACLR_PHA (1 << 5) +#define ACLR_DIG (1 << 6) +#define LOAD_LRR (1 << 7) + +#define LSB_FST (1) +#define SDIO_IPT (1 << 1) +#define EXT_PWD (1 << 3) +#define ADAC_PWD (1 << 4) +#define REFCLK_PWD (1 << 5) +#define DAC_PWD (1 << 6) +#define DIG_PWD (1 << 7) + +#define ENA_AMP (1) +#define READ_FTW (1) +#define DIGR_LOW (1 << 1) +#define DIGR_HIGH (1 << 2) +#define DIGR_ENA (1 << 3) +#define SYNCCLK_ENA (1 << 6) +#define ITER_IOUPD (1 << 7) + +#define TX_ENA (1 << 1) +#define PDCLK_INV (1 << 2) +#define PDCLK_ENB (1 << 3) + +#define PARA_ENA (1 << 4) +#define SYNC_DIS (1 << 5) +#define DATA_ASS (1 << 6) +#define MATCH_ENA (1 << 7) + +#define PLL_ENA (1) +#define PFD_RST (1 << 2) +#define REFCLK_RST (1 << 6) +#define REFCLK_BYP (1 << 7) + +/* Register format: 1 byte addr + value */ +struct ad9910_config { + u8 auxdac[5]; + u8 ioupd[5]; + u8 ftw[5]; + u8 pow[3]; + u8 asf[5]; + u8 multc[5]; + u8 dig_rampl[9]; + u8 dig_ramps[9]; + u8 dig_rampr[5]; + u8 sin_tonep0[9]; + u8 sin_tonep1[9]; + u8 sin_tonep2[9]; + u8 sin_tonep3[9]; + u8 sin_tonep4[9]; + u8 sin_tonep5[9]; + u8 sin_tonep6[9]; + u8 sin_tonep7[9]; +}; + +struct ad9910_state { + struct mutex lock; + struct spi_device *sdev; +}; + +static ssize_t ad9910_set_parameter(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct spi_message msg; + struct spi_transfer xfer; + int ret; + struct ad9910_config *config = (struct ad9910_config *)buf; + struct iio_dev *idev = dev_get_drvdata(dev); + struct ad9910_state *st = iio_priv(idev); + + xfer.len = 5; + xfer.tx_buf = &config->auxdac[0]; + mutex_lock(&st->lock); + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 5; + xfer.tx_buf = &config->ioupd[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 5; + xfer.tx_buf = &config->ftw[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 3; + xfer.tx_buf = &config->pow[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 5; + xfer.tx_buf = &config->asf[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 5; + xfer.tx_buf = &config->multc[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 9; + xfer.tx_buf = &config->dig_rampl[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 9; + xfer.tx_buf = &config->dig_ramps[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 5; + xfer.tx_buf = &config->dig_rampr[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 9; + xfer.tx_buf = &config->sin_tonep0[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 9; + xfer.tx_buf = &config->sin_tonep1[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 9; + xfer.tx_buf = &config->sin_tonep2[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + xfer.len = 9; + xfer.tx_buf = &config->sin_tonep3[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 9; + xfer.tx_buf = &config->sin_tonep4[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 9; + xfer.tx_buf = &config->sin_tonep5[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 9; + xfer.tx_buf = &config->sin_tonep6[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 9; + xfer.tx_buf = &config->sin_tonep7[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(dds, S_IWUSR, NULL, ad9910_set_parameter, 0); + +static void ad9910_init(struct ad9910_state *st) +{ + struct spi_message msg; + struct spi_transfer xfer; + int ret; + u8 cfr[5]; + + cfr[0] = CFR1; + cfr[1] = 0; + cfr[2] = MANUAL_OSK | INVSIC | DDS_SINEOP; + cfr[3] = AUTO_OSK | OSKEN | ACLR_PHA | ACLR_DIG | LOAD_LRR; + cfr[4] = 0; + + mutex_lock(&st->lock); + + xfer.len = 5; + xfer.tx_buf = 𝔠 + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + cfr[0] = CFR2; + cfr[1] = ENA_AMP; + cfr[2] = READ_FTW | DIGR_ENA | ITER_IOUPD; + cfr[3] = TX_ENA | PDCLK_INV | PDCLK_ENB; + cfr[4] = PARA_ENA; + + xfer.len = 5; + xfer.tx_buf = 𝔠 + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + cfr[0] = CFR3; + cfr[1] = PLL_ENA; + cfr[2] = 0; + cfr[3] = REFCLK_RST | REFCLK_BYP; + cfr[4] = 0; + + xfer.len = 5; + xfer.tx_buf = 𝔠 + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + +error_ret: + mutex_unlock(&st->lock); + + + +} + +static struct attribute *ad9910_attributes[] = { + &iio_dev_attr_dds.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9910_attribute_group = { + .attrs = ad9910_attributes, +}; + +static const struct iio_info ad9910_info = { + .attrs = &ad9910_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad9910_probe(struct spi_device *spi) +{ + struct ad9910_state *st; + struct iio_dev *idev; + int ret = 0; + + idev = iio_allocate_device(sizeof(*st)); + if (idev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + spi_set_drvdata(spi, idev); + st = iio_priv(idev); + mutex_init(&st->lock); + st->sdev = spi; + + idev->dev.parent = &spi->dev; + idev->info = &ad9910_info; + idev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(idev); + if (ret) + goto error_free_dev; + spi->max_speed_hz = 2000000; + spi->mode = SPI_MODE_3; + spi->bits_per_word = 8; + spi_setup(spi); + ad9910_init(st); + return 0; + +error_free_dev: + iio_free_device(idev); +error_ret: + return ret; +} + +static int __devexit ad9910_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static struct spi_driver ad9910_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ad9910_probe, + .remove = __devexit_p(ad9910_remove), +}; +module_spi_driver(ad9910_driver); + +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("Analog Devices ad9910 driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:" DRV_NAME); diff --git a/drivers/staging/iio/dds/ad9951.c b/drivers/staging/iio/dds/ad9951.c new file mode 100644 index 00000000..4d150048 --- /dev/null +++ b/drivers/staging/iio/dds/ad9951.c @@ -0,0 +1,232 @@ +/* + * Driver for ADI Direct Digital Synthesis ad9951 + * + * Copyright (c) 2010 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define DRV_NAME "ad9951" + +#define CFR1 0x0 +#define CFR2 0x1 + +#define AUTO_OSK (1) +#define OSKEN (1 << 1) +#define LOAD_ARR (1 << 2) + +#define AUTO_SYNC (1 << 7) + +#define LSB_FST (1) +#define SDIO_IPT (1 << 1) +#define CLR_PHA (1 << 2) +#define SINE_OPT (1 << 4) +#define ACLR_PHA (1 << 5) + +#define VCO_RANGE (1 << 2) + +#define CRS_OPT (1 << 1) +#define HMANU_SYNC (1 << 2) +#define HSPD_SYNC (1 << 3) + +/* Register format: 1 byte addr + value */ +struct ad9951_config { + u8 asf[3]; + u8 arr[2]; + u8 ftw0[5]; + u8 ftw1[3]; +}; + +struct ad9951_state { + struct mutex lock; + struct spi_device *sdev; +}; + +static ssize_t ad9951_set_parameter(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct spi_message msg; + struct spi_transfer xfer; + int ret; + struct ad9951_config *config = (struct ad9951_config *)buf; + struct iio_dev *idev = dev_get_drvdata(dev); + struct ad9951_state *st = iio_priv(idev); + + xfer.len = 3; + xfer.tx_buf = &config->asf[0]; + mutex_lock(&st->lock); + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 2; + xfer.tx_buf = &config->arr[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 5; + xfer.tx_buf = &config->ftw0[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + xfer.len = 3; + xfer.tx_buf = &config->ftw1[0]; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; +error_ret: + mutex_unlock(&st->lock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(dds, S_IWUSR, NULL, ad9951_set_parameter, 0); + +static void ad9951_init(struct ad9951_state *st) +{ + struct spi_message msg; + struct spi_transfer xfer; + int ret; + u8 cfr[5]; + + cfr[0] = CFR1; + cfr[1] = 0; + cfr[2] = LSB_FST | CLR_PHA | SINE_OPT | ACLR_PHA; + cfr[3] = AUTO_OSK | OSKEN | LOAD_ARR; + cfr[4] = 0; + + mutex_lock(&st->lock); + + xfer.len = 5; + xfer.tx_buf = 𝔠 + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + + cfr[0] = CFR2; + cfr[1] = VCO_RANGE; + cfr[2] = HSPD_SYNC; + cfr[3] = 0; + + xfer.len = 4; + xfer.tx_buf = 𝔠 + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->sdev, &msg); + if (ret) + goto error_ret; + +error_ret: + mutex_unlock(&st->lock); + + + +} + +static struct attribute *ad9951_attributes[] = { + &iio_dev_attr_dds.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad9951_attribute_group = { + .attrs = ad9951_attributes, +}; + +static const struct iio_info ad9951_info = { + .attrs = &ad9951_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad9951_probe(struct spi_device *spi) +{ + struct ad9951_state *st; + struct iio_dev *idev; + int ret = 0; + + idev = iio_allocate_device(sizeof(*st)); + if (idev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + spi_set_drvdata(spi, idev); + st = iio_priv(idev); + mutex_init(&st->lock); + st->sdev = spi; + + idev->dev.parent = &spi->dev; + + idev->info = &ad9951_info; + idev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(idev); + if (ret) + goto error_free_dev; + spi->max_speed_hz = 2000000; + spi->mode = SPI_MODE_3; + spi->bits_per_word = 8; + spi_setup(spi); + ad9951_init(st); + return 0; + +error_free_dev: + iio_free_device(idev); + +error_ret: + return ret; +} + +static int __devexit ad9951_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static struct spi_driver ad9951_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ad9951_probe, + .remove = __devexit_p(ad9951_remove), +}; +module_spi_driver(ad9951_driver); + +MODULE_AUTHOR("Cliff Cai"); +MODULE_DESCRIPTION("Analog Devices ad9951 driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:" DRV_NAME); diff --git a/drivers/staging/iio/dds/dds.h b/drivers/staging/iio/dds/dds.h new file mode 100644 index 00000000..d8ac3a93 --- /dev/null +++ b/drivers/staging/iio/dds/dds.h @@ -0,0 +1,110 @@ +/* + * dds.h - sysfs attributes associated with DDS devices + * + * Copyright (c) 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +/** + * /sys/bus/iio/devices/.../ddsX_freqY + */ + +#define IIO_DEV_ATTR_FREQ(_channel, _num, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(dds##_channel##_freq##_num, \ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../ddsX_freqY_scale + */ + +#define IIO_CONST_ATTR_FREQ_SCALE(_channel, _string) \ + IIO_CONST_ATTR(dds##_channel##_freq_scale, _string) + +/** + * /sys/bus/iio/devices/.../ddsX_freqsymbol + */ + +#define IIO_DEV_ATTR_FREQSYMBOL(_channel, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(dds##_channel##_freqsymbol, \ + _mode, _show, _store, _addr); + +/** + * /sys/bus/iio/devices/.../ddsX_phaseY + */ + +#define IIO_DEV_ATTR_PHASE(_channel, _num, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(dds##_channel##_phase##_num, \ + _mode, _show, _store, _addr) + +/** + * /sys/bus/iio/devices/.../ddsX_phaseY_scale + */ + +#define IIO_CONST_ATTR_PHASE_SCALE(_channel, _string) \ + IIO_CONST_ATTR(dds##_channel##_phase_scale, _string) + +/** + * /sys/bus/iio/devices/.../ddsX_phasesymbol + */ + +#define IIO_DEV_ATTR_PHASESYMBOL(_channel, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(dds##_channel##_phasesymbol, \ + _mode, _show, _store, _addr); + +/** + * /sys/bus/iio/devices/.../ddsX_pincontrol_en + */ + +#define IIO_DEV_ATTR_PINCONTROL_EN(_channel, _mode, _show, _store, _addr)\ + IIO_DEVICE_ATTR(dds##_channel##_pincontrol_en, \ + _mode, _show, _store, _addr); + +/** + * /sys/bus/iio/devices/.../ddsX_pincontrol_freq_en + */ + +#define IIO_DEV_ATTR_PINCONTROL_FREQ_EN(_channel, _mode, _show, _store, _addr)\ + IIO_DEVICE_ATTR(dds##_channel##_pincontrol_freq_en, \ + _mode, _show, _store, _addr); + +/** + * /sys/bus/iio/devices/.../ddsX_pincontrol_phase_en + */ + +#define IIO_DEV_ATTR_PINCONTROL_PHASE_EN(_channel, _mode, _show, _store, _addr)\ + IIO_DEVICE_ATTR(dds##_channel##_pincontrol_phase_en, \ + _mode, _show, _store, _addr); + +/** + * /sys/bus/iio/devices/.../ddsX_out_enable + */ + +#define IIO_DEV_ATTR_OUT_ENABLE(_channel, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(dds##_channel##_out_enable, \ + _mode, _show, _store, _addr); + +/** + * /sys/bus/iio/devices/.../ddsX_outY_enable + */ + +#define IIO_DEV_ATTR_OUTY_ENABLE(_channel, _output, \ + _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(dds##_channel##_out##_output##_enable, \ + _mode, _show, _store, _addr); + +/** + * /sys/bus/iio/devices/.../ddsX_outY_wavetype + */ + +#define IIO_DEV_ATTR_OUT_WAVETYPE(_channel, _output, _store, _addr) \ + IIO_DEVICE_ATTR(dds##_channel##_out##_output##_wavetype, \ + S_IWUSR, NULL, _store, _addr); + +/** + * /sys/bus/iio/devices/.../ddsX_outY_wavetype_available + */ + +#define IIO_CONST_ATTR_OUT_WAVETYPES_AVAILABLE(_channel, _output, _modes)\ + IIO_CONST_ATTR(dds##_channel##_out##_output##_wavetype_available,\ + _modes); diff --git a/drivers/staging/iio/driver.h b/drivers/staging/iio/driver.h new file mode 100644 index 00000000..a4f8b2e0 --- /dev/null +++ b/drivers/staging/iio/driver.h @@ -0,0 +1,34 @@ +/* + * Industrial I/O in kernel access map interface. + * + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#ifndef _IIO_INKERN_H_ +#define _IIO_INKERN_H_ + +struct iio_map; + +/** + * iio_map_array_register() - tell the core about inkernel consumers + * @indio_dev: provider device + * @map: array of mappings specifying association of channel with client + */ +int iio_map_array_register(struct iio_dev *indio_dev, + struct iio_map *map); + +/** + * iio_map_array_unregister() - tell the core to remove consumer mappings + * @indio_dev: provider device + * @map: array of mappings to remove. Note these must have same memory + * addresses as those originally added not just equal parameter + * values. + */ +int iio_map_array_unregister(struct iio_dev *indio_dev, + struct iio_map *map); + +#endif diff --git a/drivers/staging/iio/events.h b/drivers/staging/iio/events.h new file mode 100644 index 00000000..c25f0e3c --- /dev/null +++ b/drivers/staging/iio/events.h @@ -0,0 +1,105 @@ +/* The industrial I/O - event passing to userspace + * + * Copyright (c) 2008-2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#ifndef _IIO_EVENTS_H_ +#define _IIO_EVENTS_H_ + +#include +#include +#include "types.h" + +/** + * struct iio_event_data - The actual event being pushed to userspace + * @id: event identifier + * @timestamp: best estimate of time of event occurrence (often from + * the interrupt handler) + */ +struct iio_event_data { + __u64 id; + __s64 timestamp; +}; + +#define IIO_GET_EVENT_FD_IOCTL _IOR('i', 0x90, int) + +enum iio_event_type { + IIO_EV_TYPE_THRESH, + IIO_EV_TYPE_MAG, + IIO_EV_TYPE_ROC, + IIO_EV_TYPE_THRESH_ADAPTIVE, + IIO_EV_TYPE_MAG_ADAPTIVE, +}; + +enum iio_event_direction { + IIO_EV_DIR_EITHER, + IIO_EV_DIR_RISING, + IIO_EV_DIR_FALLING, +}; + +/** + * IIO_EVENT_CODE() - create event identifier + * @chan_type: Type of the channel. Should be one of enum iio_chan_type. + * @diff: Whether the event is for an differential channel or not. + * @modifier: Modifier for the channel. Should be one of enum iio_modifier. + * @direction: Direction of the event. One of enum iio_event_direction. + * @type: Type of the event. Should be one enum iio_event_type. + * @chan: Channel number for non-differential channels. + * @chan1: First channel number for differential channels. + * @chan2: Second channel number for differential channels. + */ + +#define IIO_EVENT_CODE(chan_type, diff, modifier, direction, \ + type, chan, chan1, chan2) \ + (((u64)type << 56) | ((u64)diff << 55) | \ + ((u64)direction << 48) | ((u64)modifier << 40) | \ + ((u64)chan_type << 32) | (((u16)chan2) << 16) | ((u16)chan1) | \ + ((u16)chan)) + + +#define IIO_EV_DIR_MAX 4 +#define IIO_EV_BIT(type, direction) \ + (1 << (type*IIO_EV_DIR_MAX + direction)) + +/** + * IIO_MOD_EVENT_CODE() - create event identifier for modified channels + * @chan_type: Type of the channel. Should be one of enum iio_chan_type. + * @number: Channel number. + * @modifier: Modifier for the channel. Should be one of enum iio_modifier. + * @type: Type of the event. Should be one enum iio_event_type. + * @direction: Direction of the event. One of enum iio_event_direction. + */ + +#define IIO_MOD_EVENT_CODE(chan_type, number, modifier, \ + type, direction) \ + IIO_EVENT_CODE(chan_type, 0, modifier, direction, type, number, 0, 0) + +/** + * IIO_UNMOD_EVENT_CODE() - create event identifier for unmodified channels + * @chan_type: Type of the channel. Should be one of enum iio_chan_type. + * @number: Channel number. + * @type: Type of the event. Should be one enum iio_event_type. + * @direction: Direction of the event. One of enum iio_event_direction. + */ + +#define IIO_UNMOD_EVENT_CODE(chan_type, number, type, direction) \ + IIO_EVENT_CODE(chan_type, 0, 0, direction, type, number, 0, 0) + +#define IIO_EVENT_CODE_EXTRACT_TYPE(mask) ((mask >> 56) & 0xFF) + +#define IIO_EVENT_CODE_EXTRACT_DIR(mask) ((mask >> 48) & 0xCF) + +#define IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(mask) ((mask >> 32) & 0xFF) + +/* Event code number extraction depends on which type of event we have. + * Perhaps review this function in the future*/ +#define IIO_EVENT_CODE_EXTRACT_CHAN(mask) ((__s16)(mask & 0xFFFF)) +#define IIO_EVENT_CODE_EXTRACT_CHAN2(mask) ((__s16)(((mask) >> 16) & 0xFFFF)) + +#define IIO_EVENT_CODE_EXTRACT_MODIFIER(mask) ((mask >> 40) & 0xFF) +#define IIO_EVENT_CODE_EXTRACT_DIFF(mask) (((mask) >> 55) & 0x1) + +#endif diff --git a/drivers/staging/iio/gyro/Kconfig b/drivers/staging/iio/gyro/Kconfig new file mode 100644 index 00000000..ea295b25 --- /dev/null +++ b/drivers/staging/iio/gyro/Kconfig @@ -0,0 +1,49 @@ +# +# IIO Digital Gyroscope Sensor drivers configuration +# +menu "Digital gyroscope sensors" + +config ADIS16060 + tristate "Analog Devices ADIS16060 Yaw Rate Gyroscope with SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices adis16060 wide bandwidth + yaw rate gyroscope with SPI. + +config ADIS16080 + tristate "Analog Devices ADIS16080/100 Yaw Rate Gyroscope with SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices adis16080/100 Yaw Rate + Gyroscope with SPI. + +config ADIS16130 + tristate "Analog Devices ADIS16130 High Precision Angular Rate Sensor driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADIS16130 High Precision + Angular Rate Sensor driver. + +config ADIS16260 + tristate "Analog Devices ADIS16260 Digital Gyroscope Sensor SPI driver" + depends on SPI + select IIO_TRIGGER if IIO_BUFFER + select IIO_SW_RING if IIO_BUFFER + help + Say yes here to build support for Analog Devices ADIS16260 ADIS16265 + ADIS16250 ADIS16255 and ADIS16251 programmable digital gyroscope sensors. + + This driver can also be built as a module. If so, the module + will be called adis16260. + +config ADXRS450 + tristate "Analog Devices ADXRS450/3 Digital Output Gyroscope SPI driver" + depends on SPI + help + Say yes here to build support for Analog Devices ADXRS450 and ADXRS453 + programmable digital output gyroscope. + + This driver can also be built as a module. If so, the module + will be called adxrs450. + +endmenu diff --git a/drivers/staging/iio/gyro/Makefile b/drivers/staging/iio/gyro/Makefile new file mode 100644 index 00000000..9ba5ec15 --- /dev/null +++ b/drivers/staging/iio/gyro/Makefile @@ -0,0 +1,22 @@ +# +# Makefile for digital gyroscope sensor drivers +# + +adis16060-y := adis16060_core.o +obj-$(CONFIG_ADIS16060) += adis16060.o + +adis16080-y := adis16080_core.o +obj-$(CONFIG_ADIS16080) += adis16080.o + +adis16130-y := adis16130_core.o +obj-$(CONFIG_ADIS16130) += adis16130.o + +adis16260-y := adis16260_core.o +adis16260-$(CONFIG_IIO_BUFFER) += adis16260_ring.o adis16260_trigger.o +obj-$(CONFIG_ADIS16260) += adis16260.o + +adis16251-y := adis16251_core.o +obj-$(CONFIG_ADIS16251) += adis16251.o + +adxrs450-y := adxrs450_core.o +obj-$(CONFIG_ADXRS450) += adxrs450.o diff --git a/drivers/staging/iio/gyro/adis16060_core.c b/drivers/staging/iio/gyro/adis16060_core.c new file mode 100644 index 00000000..02cc2342 --- /dev/null +++ b/drivers/staging/iio/gyro/adis16060_core.c @@ -0,0 +1,259 @@ +/* + * ADIS16060 Wide Bandwidth Yaw Rate Gyroscope with SPI driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define ADIS16060_GYRO 0x20 /* Measure Angular Rate (Gyro) */ +#define ADIS16060_TEMP_OUT 0x10 /* Measure Temperature */ +#define ADIS16060_AIN2 0x80 /* Measure AIN2 */ +#define ADIS16060_AIN1 0x40 /* Measure AIN1 */ + +/** + * struct adis16060_state - device instance specific data + * @us_w: actual spi_device to write config + * @us_r: actual spi_device to read back data + * @buf: transmit or receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16060_state { + struct spi_device *us_w; + struct spi_device *us_r; + struct mutex buf_lock; + + u8 buf[3] ____cacheline_aligned; +}; + +static struct iio_dev *adis16060_iio_dev; + +static int adis16060_spi_write(struct iio_dev *indio_dev, u8 val) +{ + int ret; + struct adis16060_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->buf[2] = val; /* The last 8 bits clocked in are latched */ + ret = spi_write(st->us_w, st->buf, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int adis16060_spi_read(struct iio_dev *indio_dev, u16 *val) +{ + int ret; + struct adis16060_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + + ret = spi_read(st->us_r, st->buf, 3); + + /* The internal successive approximation ADC begins the + * conversion process on the falling edge of MSEL1 and + * starts to place data MSB first on the DOUT line at + * the 6th falling edge of SCLK + */ + if (ret == 0) + *val = ((st->buf[0] & 0x3) << 12) | + (st->buf[1] << 4) | + ((st->buf[2] >> 4) & 0xF); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int adis16060_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + u16 tval = 0; + int ret; + + switch (mask) { + case 0: + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + ret = adis16060_spi_write(indio_dev, chan->address); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + ret = adis16060_spi_read(indio_dev, &tval); + mutex_unlock(&indio_dev->mlock); + *val = tval; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = -7; + *val2 = 461117; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 34000; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static const struct iio_info adis16060_info = { + .read_raw = &adis16060_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec adis16060_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .address = ADIS16060_GYRO, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .address = ADIS16060_AIN1, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .address = ADIS16060_AIN2, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = ADIS16060_TEMP_OUT, + } +}; + +static int __devinit adis16060_r_probe(struct spi_device *spi) +{ + int ret; + struct adis16060_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + st->us_r = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16060_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adis16060_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16060_channels); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + adis16060_iio_dev = indio_dev; + return 0; + +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +/* fixme, confirm ordering in this function */ +static int adis16060_r_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static int __devinit adis16060_w_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev = adis16060_iio_dev; + struct adis16060_state *st; + if (!indio_dev) { + ret = -ENODEV; + goto error_ret; + } + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->us_w = spi; + return 0; + +error_ret: + return ret; +} + +static int adis16060_w_remove(struct spi_device *spi) +{ + return 0; +} + +static struct spi_driver adis16060_r_driver = { + .driver = { + .name = "adis16060_r", + .owner = THIS_MODULE, + }, + .probe = adis16060_r_probe, + .remove = __devexit_p(adis16060_r_remove), +}; + +static struct spi_driver adis16060_w_driver = { + .driver = { + .name = "adis16060_w", + .owner = THIS_MODULE, + }, + .probe = adis16060_w_probe, + .remove = __devexit_p(adis16060_w_remove), +}; + +static __init int adis16060_init(void) +{ + int ret; + + ret = spi_register_driver(&adis16060_r_driver); + if (ret < 0) + return ret; + + ret = spi_register_driver(&adis16060_w_driver); + if (ret < 0) { + spi_unregister_driver(&adis16060_r_driver); + return ret; + } + + return 0; +} +module_init(adis16060_init); + +static __exit void adis16060_exit(void) +{ + spi_unregister_driver(&adis16060_w_driver); + spi_unregister_driver(&adis16060_r_driver); +} +module_exit(adis16060_exit); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16060 Yaw Rate Gyroscope Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/gyro/adis16080_core.c b/drivers/staging/iio/gyro/adis16080_core.c new file mode 100644 index 00000000..1815490d --- /dev/null +++ b/drivers/staging/iio/gyro/adis16080_core.c @@ -0,0 +1,197 @@ +/* + * ADIS16080/100 Yaw Rate Gyroscope with SPI driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define ADIS16080_DIN_GYRO (0 << 10) /* Gyroscope output */ +#define ADIS16080_DIN_TEMP (1 << 10) /* Temperature output */ +#define ADIS16080_DIN_AIN1 (2 << 10) +#define ADIS16080_DIN_AIN2 (3 << 10) + +/* + * 1: Write contents on DIN to control register. + * 0: No changes to control register. + */ + +#define ADIS16080_DIN_WRITE (1 << 15) + +/** + * struct adis16080_state - device instance specific data + * @us: actual spi_device to write data + * @buf: transmit or receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct adis16080_state { + struct spi_device *us; + struct mutex buf_lock; + + u8 buf[2] ____cacheline_aligned; +}; + +static int adis16080_spi_write(struct iio_dev *indio_dev, + u16 val) +{ + int ret; + struct adis16080_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->buf[0] = val >> 8; + st->buf[1] = val; + + ret = spi_write(st->us, st->buf, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int adis16080_spi_read(struct iio_dev *indio_dev, + u16 *val) +{ + int ret; + struct adis16080_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + + ret = spi_read(st->us, st->buf, 2); + + if (ret == 0) + *val = ((st->buf[0] & 0xF) << 8) | st->buf[1]; + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int adis16080_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret = -EINVAL; + u16 ut; + /* Take the iio_dev status lock */ + + mutex_lock(&indio_dev->mlock); + switch (mask) { + case 0: + ret = adis16080_spi_write(indio_dev, + chan->address | + ADIS16080_DIN_WRITE); + if (ret < 0) + break; + ret = adis16080_spi_read(indio_dev, &ut); + if (ret < 0) + break; + *val = ut; + ret = IIO_VAL_INT; + break; + } + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static const struct iio_chan_spec adis16080_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .address = ADIS16080_DIN_GYRO, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .address = ADIS16080_DIN_AIN1, + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .address = ADIS16080_DIN_AIN2, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .address = ADIS16080_DIN_TEMP, + } +}; + +static const struct iio_info adis16080_info = { + .read_raw = &adis16080_read_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit adis16080_probe(struct spi_device *spi) +{ + int ret; + struct adis16080_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + /* Allocate the comms buffers */ + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->channels = adis16080_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16080_channels); + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16080_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + return 0; + +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +/* fixme, confirm ordering in this function */ +static int adis16080_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static struct spi_driver adis16080_driver = { + .driver = { + .name = "adis16080", + .owner = THIS_MODULE, + }, + .probe = adis16080_probe, + .remove = __devexit_p(adis16080_remove), +}; +module_spi_driver(adis16080_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16080/100 Yaw Rate Gyroscope Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16080"); diff --git a/drivers/staging/iio/gyro/adis16130_core.c b/drivers/staging/iio/gyro/adis16130_core.c new file mode 100644 index 00000000..947eb86f --- /dev/null +++ b/drivers/staging/iio/gyro/adis16130_core.c @@ -0,0 +1,176 @@ +/* + * ADIS16130 Digital Output, High Precision Angular Rate Sensor driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define ADIS16130_CON 0x0 +#define ADIS16130_CON_RD (1 << 6) +#define ADIS16130_IOP 0x1 + +/* 1 = data-ready signal low when unread data on all channels; */ +#define ADIS16130_IOP_ALL_RDY (1 << 3) +#define ADIS16130_IOP_SYNC (1 << 0) /* 1 = synchronization enabled */ +#define ADIS16130_RATEDATA 0x8 /* Gyroscope output, rate of rotation */ +#define ADIS16130_TEMPDATA 0xA /* Temperature output */ +#define ADIS16130_RATECS 0x28 /* Gyroscope channel setup */ +#define ADIS16130_RATECS_EN (1 << 3) /* 1 = channel enable; */ +#define ADIS16130_TEMPCS 0x2A /* Temperature channel setup */ +#define ADIS16130_TEMPCS_EN (1 << 3) +#define ADIS16130_RATECONV 0x30 +#define ADIS16130_TEMPCONV 0x32 +#define ADIS16130_MODE 0x38 +#define ADIS16130_MODE_24BIT (1 << 1) /* 1 = 24-bit resolution; */ + +/** + * struct adis16130_state - device instance specific data + * @us: actual spi_device to write data + * @buf_lock: mutex to protect tx and rx + * @buf: unified tx/rx buffer + **/ +struct adis16130_state { + struct spi_device *us; + struct mutex buf_lock; + u8 buf[4] ____cacheline_aligned; +}; + +static int adis16130_spi_read(struct iio_dev *indio_dev, u8 reg_addr, u32 *val) +{ + int ret; + struct adis16130_state *st = iio_priv(indio_dev); + struct spi_message msg; + struct spi_transfer xfer = { + .tx_buf = st->buf, + .rx_buf = st->buf, + .len = 4, + }; + + mutex_lock(&st->buf_lock); + + st->buf[0] = ADIS16130_CON_RD | reg_addr; + st->buf[1] = st->buf[2] = st->buf[3] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->us, &msg); + ret = spi_read(st->us, st->buf, 4); + + if (ret == 0) + *val = (st->buf[1] << 16) | (st->buf[2] << 8) | st->buf[3]; + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int adis16130_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + int ret; + u32 temp; + + /* Take the iio_dev status lock */ + mutex_lock(&indio_dev->mlock); + ret = adis16130_spi_read(indio_dev, chan->address, &temp); + mutex_unlock(&indio_dev->mlock); + if (ret) + return ret; + *val = temp; + return IIO_VAL_INT; +} + +static const struct iio_chan_spec adis16130_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .address = ADIS16130_RATEDATA, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .address = ADIS16130_TEMPDATA, + } +}; + +static const struct iio_info adis16130_info = { + .read_raw = &adis16130_read_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit adis16130_probe(struct spi_device *spi) +{ + int ret; + struct adis16130_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + indio_dev->name = spi->dev.driver->name; + indio_dev->channels = adis16130_channels; + indio_dev->num_channels = ARRAY_SIZE(adis16130_channels); + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16130_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + return 0; + +error_free_dev: + iio_free_device(indio_dev); + +error_ret: + return ret; +} + +/* fixme, confirm ordering in this function */ +static int adis16130_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static struct spi_driver adis16130_driver = { + .driver = { + .name = "adis16130", + .owner = THIS_MODULE, + }, + .probe = adis16130_probe, + .remove = __devexit_p(adis16130_remove), +}; +module_spi_driver(adis16130_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16130 High Precision Angular Rate"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:adis16130"); diff --git a/drivers/staging/iio/gyro/adis16260.h b/drivers/staging/iio/gyro/adis16260.h new file mode 100644 index 00000000..4c4b2512 --- /dev/null +++ b/drivers/staging/iio/gyro/adis16260.h @@ -0,0 +1,156 @@ +#ifndef SPI_ADIS16260_H_ +#define SPI_ADIS16260_H_ +#include "adis16260_platform_data.h" + +#define ADIS16260_STARTUP_DELAY 220 /* ms */ + +#define ADIS16260_READ_REG(a) a +#define ADIS16260_WRITE_REG(a) ((a) | 0x80) + +#define ADIS16260_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16260_SUPPLY_OUT 0x02 /* Power supply measurement */ +#define ADIS16260_GYRO_OUT 0x04 /* X-axis gyroscope output */ +#define ADIS16260_AUX_ADC 0x0A /* analog input channel measurement */ +#define ADIS16260_TEMP_OUT 0x0C /* internal temperature measurement */ +#define ADIS16260_ANGL_OUT 0x0E /* angle displacement */ +#define ADIS16260_GYRO_OFF 0x14 /* Calibration, offset/bias adjustment */ +#define ADIS16260_GYRO_SCALE 0x16 /* Calibration, scale adjustment */ +#define ADIS16260_ALM_MAG1 0x20 /* Alarm 1 magnitude/polarity setting */ +#define ADIS16260_ALM_MAG2 0x22 /* Alarm 2 magnitude/polarity setting */ +#define ADIS16260_ALM_SMPL1 0x24 /* Alarm 1 dynamic rate of change setting */ +#define ADIS16260_ALM_SMPL2 0x26 /* Alarm 2 dynamic rate of change setting */ +#define ADIS16260_ALM_CTRL 0x28 /* Alarm control */ +#define ADIS16260_AUX_DAC 0x30 /* Auxiliary DAC data */ +#define ADIS16260_GPIO_CTRL 0x32 /* Control, digital I/O line */ +#define ADIS16260_MSC_CTRL 0x34 /* Control, data ready, self-test settings */ +#define ADIS16260_SMPL_PRD 0x36 /* Control, internal sample rate */ +#define ADIS16260_SENS_AVG 0x38 /* Control, dynamic range, filtering */ +#define ADIS16260_SLP_CNT 0x3A /* Control, sleep mode initiation */ +#define ADIS16260_DIAG_STAT 0x3C /* Diagnostic, error flags */ +#define ADIS16260_GLOB_CMD 0x3E /* Control, global commands */ +#define ADIS16260_LOT_ID1 0x52 /* Lot Identification Code 1 */ +#define ADIS16260_LOT_ID2 0x54 /* Lot Identification Code 2 */ +#define ADIS16260_PROD_ID 0x56 /* Product identifier; + * convert to decimal = 16,265/16,260 */ +#define ADIS16260_SERIAL_NUM 0x58 /* Serial number */ + +#define ADIS16260_OUTPUTS 5 + +#define ADIS16260_ERROR_ACTIVE (1<<14) +#define ADIS16260_NEW_DATA (1<<15) + +/* MSC_CTRL */ +#define ADIS16260_MSC_CTRL_MEM_TEST (1<<11) +/* Internal self-test enable */ +#define ADIS16260_MSC_CTRL_INT_SELF_TEST (1<<10) +#define ADIS16260_MSC_CTRL_NEG_SELF_TEST (1<<9) +#define ADIS16260_MSC_CTRL_POS_SELF_TEST (1<<8) +#define ADIS16260_MSC_CTRL_DATA_RDY_EN (1<<2) +#define ADIS16260_MSC_CTRL_DATA_RDY_POL_HIGH (1<<1) +#define ADIS16260_MSC_CTRL_DATA_RDY_DIO2 (1<<0) + +/* SMPL_PRD */ +/* Time base (tB): 0 = 1.953 ms, 1 = 60.54 ms */ +#define ADIS16260_SMPL_PRD_TIME_BASE (1<<7) +#define ADIS16260_SMPL_PRD_DIV_MASK 0x7F + +/* SLP_CNT */ +#define ADIS16260_SLP_CNT_POWER_OFF 0x80 + +/* DIAG_STAT */ +#define ADIS16260_DIAG_STAT_ALARM2 (1<<9) +#define ADIS16260_DIAG_STAT_ALARM1 (1<<8) +#define ADIS16260_DIAG_STAT_FLASH_CHK (1<<6) +#define ADIS16260_DIAG_STAT_SELF_TEST (1<<5) +#define ADIS16260_DIAG_STAT_OVERFLOW (1<<4) +#define ADIS16260_DIAG_STAT_SPI_FAIL (1<<3) +#define ADIS16260_DIAG_STAT_FLASH_UPT (1<<2) +#define ADIS16260_DIAG_STAT_POWER_HIGH (1<<1) +#define ADIS16260_DIAG_STAT_POWER_LOW (1<<0) + +/* GLOB_CMD */ +#define ADIS16260_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16260_GLOB_CMD_FLASH_UPD (1<<3) +#define ADIS16260_GLOB_CMD_DAC_LATCH (1<<2) +#define ADIS16260_GLOB_CMD_FAC_CALIB (1<<1) +#define ADIS16260_GLOB_CMD_AUTO_NULL (1<<0) + +#define ADIS16260_MAX_TX 24 +#define ADIS16260_MAX_RX 24 + +#define ADIS16260_SPI_SLOW (u32)(300 * 1000) +#define ADIS16260_SPI_BURST (u32)(1000 * 1000) +#define ADIS16260_SPI_FAST (u32)(2000 * 1000) + +/** + * struct adis16260_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @buf_lock: mutex to protect tx and rx + * @negate: negate the scale parameter + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct adis16260_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + unsigned negate:1; + u8 tx[ADIS16260_MAX_TX] ____cacheline_aligned; + u8 rx[ADIS16260_MAX_RX]; +}; + +int adis16260_set_irq(struct iio_dev *indio_dev, bool enable); + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +#define ADIS16260_SCAN_SUPPLY 0 +#define ADIS16260_SCAN_GYRO 1 +#define ADIS16260_SCAN_AUX_ADC 2 +#define ADIS16260_SCAN_TEMP 3 +#define ADIS16260_SCAN_ANGL 4 + +#ifdef CONFIG_IIO_BUFFER +void adis16260_remove_trigger(struct iio_dev *indio_dev); +int adis16260_probe_trigger(struct iio_dev *indio_dev); + +ssize_t adis16260_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + + +int adis16260_configure_ring(struct iio_dev *indio_dev); +void adis16260_unconfigure_ring(struct iio_dev *indio_dev); + +#else /* CONFIG_IIO_BUFFER */ + +static inline void adis16260_remove_trigger(struct iio_dev *indio_dev) +{ +} + +static inline int adis16260_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline ssize_t +adis16260_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static int adis16260_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void adis16260_unconfigure_ring(struct iio_dev *indio_dev) +{ +} + +#endif /* CONFIG_IIO_BUFFER */ +#endif /* SPI_ADIS16260_H_ */ diff --git a/drivers/staging/iio/gyro/adis16260_core.c b/drivers/staging/iio/gyro/adis16260_core.c new file mode 100644 index 00000000..8f6af47e --- /dev/null +++ b/drivers/staging/iio/gyro/adis16260_core.c @@ -0,0 +1,723 @@ +/* + * ADIS16260/ADIS16265 Programmable Digital Gyroscope Sensor Driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" + +#include "adis16260.h" + +#define DRIVER_NAME "adis16260" + +static int adis16260_check_status(struct iio_dev *indio_dev); + +/** + * adis16260_spi_write_reg_8() - write single byte to a register + * @indio_dev: iio_dev for the device + * @reg_address: the address of the register to be written + * @val: the value to write + **/ +static int adis16260_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct adis16260_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16260_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16260_spi_write_reg_16() - write 2 bytes to a pair of registers + * @indio_dev: iio_dev for the device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: value to be written + **/ +static int adis16260_spi_write_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct adis16260_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 20, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + .delay_usecs = 20, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16260_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = ADIS16260_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16260_spi_read_reg_16() - read 2 bytes from a 16-bit register + * @indio_dev: iio_dev for the device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + **/ +static int adis16260_spi_read_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct adis16260_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + .delay_usecs = 30, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + .delay_usecs = 30, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16260_READ_REG(lower_reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, + "problem when reading 16 bit register 0x%02X", + lower_reg_address); + goto error_ret; + } + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t adis16260_read_frequency_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16260_state *st = iio_priv(indio_dev); + if (spi_get_device_id(st->us)->driver_data) + return sprintf(buf, "%s\n", "0.129 ~ 256"); + else + return sprintf(buf, "%s\n", "256 2048"); +} + +static ssize_t adis16260_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16260_state *st = iio_priv(indio_dev); + int ret, len = 0; + u16 t; + int sps; + ret = adis16260_spi_read_reg_16(indio_dev, + ADIS16260_SMPL_PRD, + &t); + if (ret) + return ret; + + if (spi_get_device_id(st->us)->driver_data) /* If an adis16251 */ + sps = (t & ADIS16260_SMPL_PRD_TIME_BASE) ? 8 : 256; + else + sps = (t & ADIS16260_SMPL_PRD_TIME_BASE) ? 66 : 2048; + sps /= (t & ADIS16260_SMPL_PRD_DIV_MASK) + 1; + len = sprintf(buf, "%d SPS\n", sps); + return len; +} + +static ssize_t adis16260_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16260_state *st = iio_priv(indio_dev); + long val; + int ret; + u8 t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + if (spi_get_device_id(st->us)) { + t = (256 / val); + if (t > 0) + t--; + t &= ADIS16260_SMPL_PRD_DIV_MASK; + } else { + t = (2048 / val); + if (t > 0) + t--; + t &= ADIS16260_SMPL_PRD_DIV_MASK; + } + if ((t & ADIS16260_SMPL_PRD_DIV_MASK) >= 0x0A) + st->us->max_speed_hz = ADIS16260_SPI_SLOW; + else + st->us->max_speed_hz = ADIS16260_SPI_FAST; + ret = adis16260_spi_write_reg_8(indio_dev, + ADIS16260_SMPL_PRD, + t); + + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int adis16260_reset(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16260_spi_write_reg_8(indio_dev, + ADIS16260_GLOB_CMD, + ADIS16260_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&indio_dev->dev, "problem resetting device"); + + return ret; +} + +static ssize_t adis16260_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + if (len < 1) + return -EINVAL; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return adis16260_reset(indio_dev); + } + return -EINVAL; +} + +int adis16260_set_irq(struct iio_dev *indio_dev, bool enable) +{ + int ret; + u16 msc; + ret = adis16260_spi_read_reg_16(indio_dev, ADIS16260_MSC_CTRL, &msc); + if (ret) + goto error_ret; + + msc |= ADIS16260_MSC_CTRL_DATA_RDY_POL_HIGH; + if (enable) + msc |= ADIS16260_MSC_CTRL_DATA_RDY_EN; + else + msc &= ~ADIS16260_MSC_CTRL_DATA_RDY_EN; + + ret = adis16260_spi_write_reg_16(indio_dev, ADIS16260_MSC_CTRL, msc); + if (ret) + goto error_ret; + +error_ret: + return ret; +} + +/* Power down the device */ +static int adis16260_stop_device(struct iio_dev *indio_dev) +{ + int ret; + u16 val = ADIS16260_SLP_CNT_POWER_OFF; + + ret = adis16260_spi_write_reg_16(indio_dev, ADIS16260_SLP_CNT, val); + if (ret) + dev_err(&indio_dev->dev, "problem with turning device off: SLP_CNT"); + + return ret; +} + +static int adis16260_self_test(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16260_spi_write_reg_16(indio_dev, + ADIS16260_MSC_CTRL, + ADIS16260_MSC_CTRL_MEM_TEST); + if (ret) { + dev_err(&indio_dev->dev, "problem starting self test"); + goto err_ret; + } + + adis16260_check_status(indio_dev); + +err_ret: + return ret; +} + +static int adis16260_check_status(struct iio_dev *indio_dev) +{ + u16 status; + int ret; + struct device *dev = &indio_dev->dev; + + ret = adis16260_spi_read_reg_16(indio_dev, + ADIS16260_DIAG_STAT, + &status); + + if (ret < 0) { + dev_err(dev, "Reading status failed\n"); + goto error_ret; + } + ret = status & 0x7F; + if (status & ADIS16260_DIAG_STAT_FLASH_CHK) + dev_err(dev, "Flash checksum error\n"); + if (status & ADIS16260_DIAG_STAT_SELF_TEST) + dev_err(dev, "Self test error\n"); + if (status & ADIS16260_DIAG_STAT_OVERFLOW) + dev_err(dev, "Sensor overrange\n"); + if (status & ADIS16260_DIAG_STAT_SPI_FAIL) + dev_err(dev, "SPI failure\n"); + if (status & ADIS16260_DIAG_STAT_FLASH_UPT) + dev_err(dev, "Flash update failed\n"); + if (status & ADIS16260_DIAG_STAT_POWER_HIGH) + dev_err(dev, "Power supply above 5.25V\n"); + if (status & ADIS16260_DIAG_STAT_POWER_LOW) + dev_err(dev, "Power supply below 4.75V\n"); + +error_ret: + return ret; +} + +static int adis16260_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &indio_dev->dev; + + /* Disable IRQ */ + ret = adis16260_set_irq(indio_dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + /* Do self test */ + ret = adis16260_self_test(indio_dev); + if (ret) { + dev_err(dev, "self test failure"); + goto err_ret; + } + + /* Read status register to check the result */ + ret = adis16260_check_status(indio_dev); + if (ret) { + adis16260_reset(indio_dev); + dev_err(dev, "device not playing ball -> reset"); + msleep(ADIS16260_STARTUP_DELAY); + ret = adis16260_check_status(indio_dev); + if (ret) { + dev_err(dev, "giving up"); + goto err_ret; + } + } + +err_ret: + return ret; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + adis16260_read_frequency, + adis16260_write_frequency); + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, adis16260_write_reset, 0); + +static IIO_DEVICE_ATTR(sampling_frequency_available, + S_IRUGO, adis16260_read_frequency_available, NULL, 0); + +enum adis16260_channel { + gyro, + temp, + in_supply, + in_aux, + angle, +}; +#define ADIS16260_GYRO_CHANNEL_SET(axis, mod) \ + struct iio_chan_spec adis16260_channels_##axis[] = { \ + IIO_CHAN(IIO_ANGL_VEL, 1, 0, 0, NULL, 0, mod, \ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | \ + IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + gyro, ADIS16260_SCAN_GYRO, \ + IIO_ST('s', 14, 16, 0), 0), \ + IIO_CHAN(IIO_ANGL, 1, 0, 0, NULL, 0, mod, \ + 0, \ + angle, ADIS16260_SCAN_ANGL, \ + IIO_ST('u', 14, 16, 0), 0), \ + IIO_CHAN(IIO_TEMP, 0, 1, 0, NULL, 0, 0, \ + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + temp, ADIS16260_SCAN_TEMP, \ + IIO_ST('u', 12, 16, 0), 0), \ + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, "supply", 0, 0, \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + in_supply, ADIS16260_SCAN_SUPPLY, \ + IIO_ST('u', 12, 16, 0), 0), \ + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, NULL, 1, 0, \ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + in_aux, ADIS16260_SCAN_AUX_ADC, \ + IIO_ST('u', 12, 16, 0), 0), \ + IIO_CHAN_SOFT_TIMESTAMP(5) \ + } + +static const ADIS16260_GYRO_CHANNEL_SET(x, IIO_MOD_X); +static const ADIS16260_GYRO_CHANNEL_SET(y, IIO_MOD_Y); +static const ADIS16260_GYRO_CHANNEL_SET(z, IIO_MOD_Z); + +static const u8 adis16260_addresses[5][3] = { + [gyro] = { ADIS16260_GYRO_OUT, + ADIS16260_GYRO_OFF, + ADIS16260_GYRO_SCALE }, + [angle] = { ADIS16260_ANGL_OUT }, + [in_supply] = { ADIS16260_SUPPLY_OUT }, + [in_aux] = { ADIS16260_AUX_ADC }, + [temp] = { ADIS16260_TEMP_OUT }, +}; +static int adis16260_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct adis16260_state *st = iio_priv(indio_dev); + int ret; + int bits; + u8 addr; + s16 val16; + + switch (mask) { + case 0: + mutex_lock(&indio_dev->mlock); + addr = adis16260_addresses[chan->address][0]; + ret = adis16260_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + if (val16 & ADIS16260_ERROR_ACTIVE) { + ret = adis16260_check_status(indio_dev); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + } + val16 = val16 & ((1 << chan->scan_type.realbits) - 1); + if (chan->scan_type.sign == 's') + val16 = (s16)(val16 << + (16 - chan->scan_type.realbits)) >> + (16 - chan->scan_type.realbits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + if (spi_get_device_id(st->us)->driver_data) + *val2 = 320; + else + *val2 = 1278; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) + *val2 = 18315; + else + *val2 = 610500; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = 145300; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_OFFSET: + *val = 25; + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ANGL_VEL: + bits = 12; + break; + default: + return -EINVAL; + }; + mutex_lock(&indio_dev->mlock); + addr = adis16260_addresses[chan->address][1]; + ret = adis16260_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << bits) - 1; + val16 = (s16)(val16 << (16 - bits)) >> (16 - bits); + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_CALIBSCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + bits = 12; + break; + default: + return -EINVAL; + }; + mutex_lock(&indio_dev->mlock); + addr = adis16260_addresses[chan->address][2]; + ret = adis16260_spi_read_reg_16(indio_dev, addr, &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + *val = (1 << bits) - 1; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + } + return -EINVAL; +} + +static int adis16260_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int bits = 12; + s16 val16; + u8 addr; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + val16 = val & ((1 << bits) - 1); + addr = adis16260_addresses[chan->address][1]; + return adis16260_spi_write_reg_16(indio_dev, addr, val16); + case IIO_CHAN_INFO_CALIBSCALE: + val16 = val & ((1 << bits) - 1); + addr = adis16260_addresses[chan->address][2]; + return adis16260_spi_write_reg_16(indio_dev, addr, val16); + } + return -EINVAL; +} + +static struct attribute *adis16260_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16260_attribute_group = { + .attrs = adis16260_attributes, +}; + +static const struct iio_info adis16260_info = { + .attrs = &adis16260_attribute_group, + .read_raw = &adis16260_read_raw, + .write_raw = &adis16260_write_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit adis16260_probe(struct spi_device *spi) +{ + int ret; + struct adis16260_platform_data *pd = spi->dev.platform_data; + struct adis16260_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + if (pd) + st->negate = pd->negate; + /* this is only used for removal purposes */ + spi_set_drvdata(spi, st); + + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi_get_device_id(st->us)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adis16260_info; + indio_dev->num_channels + = ARRAY_SIZE(adis16260_channels_x); + if (pd && pd->direction) + switch (pd->direction) { + case 'x': + indio_dev->channels = adis16260_channels_x; + break; + case 'y': + indio_dev->channels = adis16260_channels_y; + break; + case 'z': + indio_dev->channels = adis16260_channels_z; + break; + default: + return -EINVAL; + } + else + indio_dev->channels = adis16260_channels_x; + indio_dev->num_channels = ARRAY_SIZE(adis16260_channels_x); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis16260_configure_ring(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_buffer_register(indio_dev, + indio_dev->channels, + ARRAY_SIZE(adis16260_channels_x)); + if (ret) { + printk(KERN_ERR "failed to initialize the ring\n"); + goto error_unreg_ring_funcs; + } + if (indio_dev->buffer) { + /* Set default scan mode */ + iio_scan_mask_set(indio_dev, indio_dev->buffer, + ADIS16260_SCAN_SUPPLY); + iio_scan_mask_set(indio_dev, indio_dev->buffer, + ADIS16260_SCAN_GYRO); + iio_scan_mask_set(indio_dev, indio_dev->buffer, + ADIS16260_SCAN_AUX_ADC); + iio_scan_mask_set(indio_dev, indio_dev->buffer, + ADIS16260_SCAN_TEMP); + iio_scan_mask_set(indio_dev, indio_dev->buffer, + ADIS16260_SCAN_ANGL); + } + if (spi->irq) { + ret = adis16260_probe_trigger(indio_dev); + if (ret) + goto error_uninitialize_ring; + } + + /* Get the device into a sane initial state */ + ret = adis16260_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + adis16260_remove_trigger(indio_dev); +error_uninitialize_ring: + iio_buffer_unregister(indio_dev); +error_unreg_ring_funcs: + adis16260_unconfigure_ring(indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int adis16260_remove(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + + ret = adis16260_stop_device(indio_dev); + if (ret) + goto err_ret; + + flush_scheduled_work(); + + adis16260_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + adis16260_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + +err_ret: + return ret; +} + +/* + * These parts do not need to be differentiated until someone adds + * support for the on chip filtering. + */ +static const struct spi_device_id adis16260_id[] = { + {"adis16260", 0}, + {"adis16265", 0}, + {"adis16250", 0}, + {"adis16255", 0}, + {"adis16251", 1}, + {} +}; +MODULE_DEVICE_TABLE(spi, adis16260_id); + +static struct spi_driver adis16260_driver = { + .driver = { + .name = "adis16260", + .owner = THIS_MODULE, + }, + .probe = adis16260_probe, + .remove = __devexit_p(adis16260_remove), + .id_table = adis16260_id, +}; +module_spi_driver(adis16260_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADIS16260/5 Digital Gyroscope Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/gyro/adis16260_platform_data.h b/drivers/staging/iio/gyro/adis16260_platform_data.h new file mode 100644 index 00000000..12802e97 --- /dev/null +++ b/drivers/staging/iio/gyro/adis16260_platform_data.h @@ -0,0 +1,19 @@ +/* + * ADIS16260 Programmable Digital Gyroscope Sensor Driver Platform Data + * + * Based on adis16255.h Matthia Brugger + * + * Copyright (C) 2010 Fraunhofer Institute for Integrated Circuits + * + * Licensed under the GPL-2 or later. + */ + +/** + * struct adis16260_platform_data - instance specific data + * @direction: x y or z + * @negate: flag to indicate value should be inverted. + **/ +struct adis16260_platform_data { + char direction; + unsigned negate:1; +}; diff --git a/drivers/staging/iio/gyro/adis16260_ring.c b/drivers/staging/iio/gyro/adis16260_ring.c new file mode 100644 index 00000000..711f1512 --- /dev/null +++ b/drivers/staging/iio/gyro/adis16260_ring.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" +#include "adis16260.h" + +/** + * adis16260_read_ring_data() read data registers which will be placed into ring + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @rx: somewhere to pass back the value read + **/ +static int adis16260_read_ring_data(struct device *dev, u8 *rx) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16260_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[ADIS16260_OUTPUTS + 1]; + int ret; + int i; + + mutex_lock(&st->buf_lock); + + spi_message_init(&msg); + + memset(xfers, 0, sizeof(xfers)); + for (i = 0; i <= ADIS16260_OUTPUTS; i++) { + xfers[i].bits_per_word = 8; + xfers[i].cs_change = 1; + xfers[i].len = 2; + xfers[i].delay_usecs = 30; + xfers[i].tx_buf = st->tx + 2 * i; + if (i < 2) /* SUPPLY_OUT:0x02 GYRO_OUT:0x04 */ + st->tx[2 * i] + = ADIS16260_READ_REG(ADIS16260_SUPPLY_OUT + + 2 * i); + else /* 0x06 to 0x09 is reserved */ + st->tx[2 * i] + = ADIS16260_READ_REG(ADIS16260_SUPPLY_OUT + + 2 * i + 4); + st->tx[2 * i + 1] = 0; + if (i >= 1) + xfers[i].rx_buf = rx + 2 * (i - 1); + spi_message_add_tail(&xfers[i], &msg); + } + + ret = spi_sync(st->us, &msg); + if (ret) + dev_err(&st->us->dev, "problem when burst reading"); + + mutex_unlock(&st->buf_lock); + + return ret; +} + +static irqreturn_t adis16260_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16260_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int i = 0; + s16 *data; + size_t datasize = ring->access->get_bytes_per_datum(ring); + + data = kmalloc(datasize , GFP_KERNEL); + if (data == NULL) { + dev_err(&st->us->dev, "memory alloc failed in ring bh"); + return -ENOMEM; + } + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength) && + adis16260_read_ring_data(&indio_dev->dev, st->rx) >= 0) + for (; i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); i++) + data[i] = be16_to_cpup((__be16 *)&(st->rx[i*2])); + + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + *((s64 *)(data + ((i + 3)/4)*4)) = pf->timestamp; + + ring->access->store_to(ring, (u8 *)data, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + kfree(data); + + return IRQ_HANDLED; +} + +void adis16260_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +static const struct iio_buffer_setup_ops adis16260_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int adis16260_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct iio_buffer *ring; + + ring = iio_sw_rb_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->buffer = ring; + ring->scan_timestamp = true; + indio_dev->setup_ops = &adis16260_ring_setup_ops; + + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &adis16260_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "adis16260_consumer%d", + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_iio_sw_rb_free: + iio_sw_rb_free(indio_dev->buffer); + return ret; +} diff --git a/drivers/staging/iio/gyro/adis16260_trigger.c b/drivers/staging/iio/gyro/adis16260_trigger.c new file mode 100644 index 00000000..8299cd18 --- /dev/null +++ b/drivers/staging/iio/gyro/adis16260_trigger.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" +#include "adis16260.h" + +/** + * adis16260_data_rdy_trigger_set_state() set datardy interrupt state + **/ +static int adis16260_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + + dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state); + return adis16260_set_irq(indio_dev, state); +} + +static const struct iio_trigger_ops adis16260_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &adis16260_data_rdy_trigger_set_state, +}; + +int adis16260_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct adis16260_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("%s-dev%d", + spi_get_device_id(st->us)->name, + indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + ret = request_irq(st->us->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "adis16260", + st->trig); + if (ret) + goto error_free_trig; + + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &adis16260_trigger_ops; + st->trig->private_data = indio_dev; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->us->irq, st->trig); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void adis16260_remove_trigger(struct iio_dev *indio_dev) +{ + struct adis16260_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->us->irq, st->trig); + iio_free_trigger(st->trig); +} diff --git a/drivers/staging/iio/gyro/adxrs450.h b/drivers/staging/iio/gyro/adxrs450.h new file mode 100644 index 00000000..af0c8701 --- /dev/null +++ b/drivers/staging/iio/gyro/adxrs450.h @@ -0,0 +1,62 @@ +#ifndef SPI_ADXRS450_H_ +#define SPI_ADXRS450_H_ + +#define ADXRS450_STARTUP_DELAY 50 /* ms */ + +/* The MSB for the spi commands */ +#define ADXRS450_SENSOR_DATA 0x20 +#define ADXRS450_WRITE_DATA 0x40 +#define ADXRS450_READ_DATA 0x80 + +#define ADXRS450_RATE1 0x00 /* Rate Registers */ +#define ADXRS450_TEMP1 0x02 /* Temperature Registers */ +#define ADXRS450_LOCST1 0x04 /* Low CST Memory Registers */ +#define ADXRS450_HICST1 0x06 /* High CST Memory Registers */ +#define ADXRS450_QUAD1 0x08 /* Quad Memory Registers */ +#define ADXRS450_FAULT1 0x0A /* Fault Registers */ +#define ADXRS450_PID1 0x0C /* Part ID Register 1 */ +#define ADXRS450_SNH 0x0E /* Serial Number Registers, 4 bytes */ +#define ADXRS450_SNL 0x10 +#define ADXRS450_DNC1 0x12 /* Dynamic Null Correction Registers */ +/* Check bits */ +#define ADXRS450_P 0x01 +#define ADXRS450_CHK 0x02 +#define ADXRS450_CST 0x04 +#define ADXRS450_PWR 0x08 +#define ADXRS450_POR 0x10 +#define ADXRS450_NVM 0x20 +#define ADXRS450_Q 0x40 +#define ADXRS450_PLL 0x80 +#define ADXRS450_UV 0x100 +#define ADXRS450_OV 0x200 +#define ADXRS450_AMP 0x400 +#define ADXRS450_FAIL 0x800 + +#define ADXRS450_WRERR_MASK (0x7 << 29) + +#define ADXRS450_MAX_RX 4 +#define ADXRS450_MAX_TX 4 + +#define ADXRS450_GET_ST(a) ((a >> 26) & 0x3) + +enum { + ID_ADXRS450, + ID_ADXRS453, +}; + +/** + * struct adxrs450_state - device instance specific data + * @us: actual spi_device + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: recieve buffer + **/ +struct adxrs450_state { + struct spi_device *us; + struct mutex buf_lock; + u8 tx[ADXRS450_MAX_RX] ____cacheline_aligned; + u8 rx[ADXRS450_MAX_TX]; + +}; + +#endif /* SPI_ADXRS450_H_ */ diff --git a/drivers/staging/iio/gyro/adxrs450_core.c b/drivers/staging/iio/gyro/adxrs450_core.c new file mode 100644 index 00000000..15e2496f --- /dev/null +++ b/drivers/staging/iio/gyro/adxrs450_core.c @@ -0,0 +1,436 @@ +/* + * ADXRS450/ADXRS453 Digital Output Gyroscope Driver + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#include "adxrs450.h" + +/** + * adxrs450_spi_read_reg_16() - read 2 bytes from a register pair + * @dev: device associated with child of actual iio_dev + * @reg_address: the address of the lower of the two registers,which should be an even address, + * Second register's address is reg_address + 1. + * @val: somewhere to pass back the value read + **/ +static int adxrs450_spi_read_reg_16(struct iio_dev *indio_dev, + u8 reg_address, + u16 *val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADXRS450_READ_DATA | (reg_address >> 7); + st->tx[1] = reg_address << 1; + st->tx[2] = 0; + st->tx[3] = 0; + + if (!(hweight32(be32_to_cpu(*(u32 *)st->tx)) & 1)) + st->tx[3] |= ADXRS450_P; + + ret = spi_write(st->us, st->tx, 4); + if (ret) { + dev_err(&st->us->dev, "problem while reading 16 bit register 0x%02x\n", + reg_address); + goto error_ret; + } + ret = spi_read(st->us, st->rx, 4); + if (ret) { + dev_err(&st->us->dev, "problem while reading 16 bit register 0x%02x\n", + reg_address); + goto error_ret; + } + + *val = (be32_to_cpu(*(u32 *)st->rx) >> 5) & 0xFFFF; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_write_reg_16() - write 2 bytes data to a register pair + * @dev: device associated with child of actual actual iio_dev + * @reg_address: the address of the lower of the two registers,which should be an even address, + * Second register's address is reg_address + 1. + * @val: value to be written. + **/ +static int adxrs450_spi_write_reg_16(struct iio_dev *indio_dev, + u8 reg_address, + u16 val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADXRS450_WRITE_DATA | reg_address >> 7; + st->tx[1] = reg_address << 1 | val >> 15; + st->tx[2] = val >> 7; + st->tx[3] = val << 1; + + if (!(hweight32(be32_to_cpu(*(u32 *)st->tx)) & 1)) + st->tx[3] |= ADXRS450_P; + + ret = spi_write(st->us, st->tx, 4); + if (ret) + dev_err(&st->us->dev, "problem while writing 16 bit register 0x%02x\n", + reg_address); + msleep(1); /* enforce sequential transfer delay 0.1ms */ + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_sensor_data() - read 2 bytes sensor data + * @dev: device associated with child of actual iio_dev + * @val: somewhere to pass back the value read + **/ +static int adxrs450_spi_sensor_data(struct iio_dev *indio_dev, s16 *val) +{ + struct adxrs450_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADXRS450_SENSOR_DATA; + st->tx[1] = 0; + st->tx[2] = 0; + st->tx[3] = 0; + + ret = spi_write(st->us, st->tx, 4); + if (ret) { + dev_err(&st->us->dev, "Problem while reading sensor data\n"); + goto error_ret; + } + + ret = spi_read(st->us, st->rx, 4); + if (ret) { + dev_err(&st->us->dev, "Problem while reading sensor data\n"); + goto error_ret; + } + + *val = (be32_to_cpu(*(u32 *)st->rx) >> 10) & 0xFFFF; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/** + * adxrs450_spi_initial() - use for initializing procedure. + * @st: device instance specific data + * @val: somewhere to pass back the value read + **/ +static int adxrs450_spi_initial(struct adxrs450_state *st, + u32 *val, char chk) +{ + struct spi_message msg; + int ret; + struct spi_transfer xfers = { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 4, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADXRS450_SENSOR_DATA; + st->tx[1] = 0; + st->tx[2] = 0; + st->tx[3] = 0; + if (chk) + st->tx[3] |= (ADXRS450_CHK | ADXRS450_P); + spi_message_init(&msg); + spi_message_add_tail(&xfers, &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "Problem while reading initializing data\n"); + goto error_ret; + } + + *val = be32_to_cpu(*(u32 *)st->rx); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +/* Recommended Startup Sequence by spec */ +static int adxrs450_initial_setup(struct iio_dev *indio_dev) +{ + u32 t; + u16 data; + int ret; + struct adxrs450_state *st = iio_priv(indio_dev); + + msleep(ADXRS450_STARTUP_DELAY*2); + ret = adxrs450_spi_initial(st, &t, 1); + if (ret) + return ret; + if (t != 0x01) + dev_warn(&st->us->dev, "The initial power on response " + "is not correct! Restart without reset?\n"); + + msleep(ADXRS450_STARTUP_DELAY); + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + + msleep(ADXRS450_STARTUP_DELAY); + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + if (((t & 0xff) | 0x01) != 0xff || ADXRS450_GET_ST(t) != 2) { + dev_err(&st->us->dev, "The second response is not correct!\n"); + return -EIO; + + } + ret = adxrs450_spi_initial(st, &t, 0); + if (ret) + return ret; + if (((t & 0xff) | 0x01) != 0xff || ADXRS450_GET_ST(t) != 2) { + dev_err(&st->us->dev, "The third response is not correct!\n"); + return -EIO; + + } + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_FAULT1, &data); + if (ret) + return ret; + if (data & 0x0fff) { + dev_err(&st->us->dev, "The device is not in normal status!\n"); + return -EINVAL; + } + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_PID1, &data); + if (ret) + return ret; + dev_info(&st->us->dev, "The Part ID is 0x%x\n", data); + + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_SNL, &data); + if (ret) + return ret; + t = data; + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_SNH, &data); + if (ret) + return ret; + t |= data << 16; + dev_info(&st->us->dev, "The Serial Number is 0x%x\n", t); + + return 0; +} + +static int adxrs450_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int ret; + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + ret = adxrs450_spi_write_reg_16(indio_dev, + ADXRS450_DNC1, + val & 0x3FF); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int adxrs450_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret; + s16 t; + + switch (mask) { + case 0: + switch (chan->type) { + case IIO_ANGL_VEL: + ret = adxrs450_spi_sensor_data(indio_dev, &t); + if (ret) + break; + *val = t; + ret = IIO_VAL_INT; + break; + case IIO_TEMP: + ret = adxrs450_spi_read_reg_16(indio_dev, + ADXRS450_TEMP1, &t); + if (ret) + break; + *val = (t >> 6) + 225; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = 218166; + return IIO_VAL_INT_PLUS_NANO; + case IIO_TEMP: + *val = 200; + *val2 = 0; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW: + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_QUAD1, &t); + if (ret) + break; + *val = t; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBBIAS: + ret = adxrs450_spi_read_reg_16(indio_dev, ADXRS450_DNC1, &t); + if (ret) + break; + *val = t; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct iio_chan_spec adxrs450_channels[2][2] = { + [ID_ADXRS450] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + } + }, + [ID_ADXRS453] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW_SEPARATE_BIT, + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + } + }, +}; + +static const struct iio_info adxrs450_info = { + .driver_module = THIS_MODULE, + .read_raw = &adxrs450_read_raw, + .write_raw = &adxrs450_write_raw, +}; + +static int __devinit adxrs450_probe(struct spi_device *spi) +{ + int ret; + struct adxrs450_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + /* This is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adxrs450_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = + adxrs450_channels[spi_get_device_id(spi)->driver_data]; + indio_dev->num_channels = ARRAY_SIZE(adxrs450_channels); + indio_dev->name = spi->dev.driver->name; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + /* Get the device into a sane initial state */ + ret = adxrs450_initial_setup(indio_dev); + if (ret) + goto error_initial; + return 0; +error_initial: + iio_device_unregister(indio_dev); +error_free_dev: + iio_free_device(indio_dev); + +error_ret: + return ret; +} + +static int adxrs450_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id adxrs450_id[] = { + {"adxrs450", ID_ADXRS450}, + {"adxrs453", ID_ADXRS453}, + {} +}; +MODULE_DEVICE_TABLE(spi, adxrs450_id); + +static struct spi_driver adxrs450_driver = { + .driver = { + .name = "adxrs450", + .owner = THIS_MODULE, + }, + .probe = adxrs450_probe, + .remove = __devexit_p(adxrs450_remove), + .id_table = adxrs450_id, +}; +module_spi_driver(adxrs450_driver); + +MODULE_AUTHOR("Cliff Cai "); +MODULE_DESCRIPTION("Analog Devices ADXRS450/ADXRS453 Gyroscope SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/iio.h b/drivers/staging/iio/iio.h new file mode 100644 index 00000000..b9cd454f --- /dev/null +++ b/drivers/staging/iio/iio.h @@ -0,0 +1,471 @@ + +/* The industrial I/O core + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#ifndef _INDUSTRIAL_IO_H_ +#define _INDUSTRIAL_IO_H_ + +#include +#include +#include "types.h" +/* IIO TODO LIST */ +/* + * Provide means of adjusting timer accuracy. + * Currently assumes nano seconds. + */ + +enum iio_data_type { + IIO_RAW, + IIO_PROCESSED, +}; + +/* Could add the raw attributes as well - allowing buffer only devices */ +enum iio_chan_info_enum { + /* 0 is reserved for raw attributes */ + IIO_CHAN_INFO_SCALE = 1, + IIO_CHAN_INFO_OFFSET, + IIO_CHAN_INFO_CALIBSCALE, + IIO_CHAN_INFO_CALIBBIAS, + IIO_CHAN_INFO_PEAK, + IIO_CHAN_INFO_PEAK_SCALE, + IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW, + IIO_CHAN_INFO_AVERAGE_RAW, + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY, +}; + +#define IIO_CHAN_INFO_SHARED_BIT(type) BIT(type*2) +#define IIO_CHAN_INFO_SEPARATE_BIT(type) BIT(type*2 + 1) + +#define IIO_CHAN_INFO_SCALE_SEPARATE_BIT \ + IIO_CHAN_INFO_SEPARATE_BIT(IIO_CHAN_INFO_SCALE) +#define IIO_CHAN_INFO_SCALE_SHARED_BIT \ + IIO_CHAN_INFO_SHARED_BIT(IIO_CHAN_INFO_SCALE) +#define IIO_CHAN_INFO_OFFSET_SEPARATE_BIT \ + IIO_CHAN_INFO_SEPARATE_BIT(IIO_CHAN_INFO_OFFSET) +#define IIO_CHAN_INFO_OFFSET_SHARED_BIT \ + IIO_CHAN_INFO_SHARED_BIT(IIO_CHAN_INFO_OFFSET) +#define IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT \ + IIO_CHAN_INFO_SEPARATE_BIT(IIO_CHAN_INFO_CALIBSCALE) +#define IIO_CHAN_INFO_CALIBSCALE_SHARED_BIT \ + IIO_CHAN_INFO_SHARED_BIT(IIO_CHAN_INFO_CALIBSCALE) +#define IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT \ + IIO_CHAN_INFO_SEPARATE_BIT(IIO_CHAN_INFO_CALIBBIAS) +#define IIO_CHAN_INFO_CALIBBIAS_SHARED_BIT \ + IIO_CHAN_INFO_SHARED_BIT(IIO_CHAN_INFO_CALIBBIAS) +#define IIO_CHAN_INFO_PEAK_SEPARATE_BIT \ + IIO_CHAN_INFO_SEPARATE_BIT(IIO_CHAN_INFO_PEAK) +#define IIO_CHAN_INFO_PEAK_SHARED_BIT \ + IIO_CHAN_INFO_SHARED_BIT(IIO_CHAN_INFO_PEAK) +#define IIO_CHAN_INFO_PEAKSCALE_SEPARATE_BIT \ + IIO_CHAN_INFO_SEPARATE_BIT(IIO_CHAN_INFO_PEAKSCALE) +#define IIO_CHAN_INFO_PEAKSCALE_SHARED_BIT \ + IIO_CHAN_INFO_SHARED_BIT(IIO_CHAN_INFO_PEAKSCALE) +#define IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW_SEPARATE_BIT \ + IIO_CHAN_INFO_SEPARATE_BIT( \ + IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW) +#define IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW_SHARED_BIT \ + IIO_CHAN_INFO_SHARED_BIT( \ + IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW) +#define IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT \ + IIO_CHAN_INFO_SEPARATE_BIT(IIO_CHAN_INFO_AVERAGE_RAW) +#define IIO_CHAN_INFO_AVERAGE_RAW_SHARED_BIT \ + IIO_CHAN_INFO_SHARED_BIT(IIO_CHAN_INFO_AVERAGE_RAW) +#define IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT \ + IIO_CHAN_INFO_SHARED_BIT( \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) +#define IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SEPARATE_BIT \ + IIO_CHAN_INFO_SEPARATE_BIT( \ + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) + +enum iio_endian { + IIO_CPU, + IIO_BE, + IIO_LE, +}; + +struct iio_chan_spec; +struct iio_dev; + +/** + * struct iio_chan_spec_ext_info - Extended channel info attribute + * @name: Info attribute name + * @shared: Whether this attribute is shared between all channels. + * @read: Read callback for this info attribute, may be NULL. + * @write: Write callback for this info attribute, may be NULL. + */ +struct iio_chan_spec_ext_info { + const char *name; + bool shared; + ssize_t (*read)(struct iio_dev *, struct iio_chan_spec const *, + char *buf); + ssize_t (*write)(struct iio_dev *, struct iio_chan_spec const *, + const char *buf, size_t len); +}; + +/** + * struct iio_chan_spec - specification of a single channel + * @type: What type of measurement is the channel making. + * @channel: What number or name do we wish to assign the channel. + * @channel2: If there is a second number for a differential + * channel then this is it. If modified is set then the + * value here specifies the modifier. + * @address: Driver specific identifier. + * @scan_index: Monotonic index to give ordering in scans when read + * from a buffer. + * @scan_type: Sign: 's' or 'u' to specify signed or unsigned + * realbits: Number of valid bits of data + * storage_bits: Realbits + padding + * shift: Shift right by this before masking out + * realbits. + * endianness: little or big endian + * @info_mask: What information is to be exported about this channel. + * This includes calibbias, scale etc. + * @event_mask: What events can this channel produce. + * @ext_info: Array of extended info attributes for this channel. + * The array is NULL terminated, the last element should + * have it's name field set to NULL. + * @extend_name: Allows labeling of channel attributes with an + * informative name. Note this has no effect codes etc, + * unlike modifiers. + * @datasheet_name: A name used in in kernel mapping of channels. It should + * correspond to the first name that the channel is referred + * to by in the datasheet (e.g. IND), or the nearest + * possible compound name (e.g. IND-INC). + * @processed_val: Flag to specify the data access attribute should be + * *_input rather than *_raw. + * @modified: Does a modifier apply to this channel. What these are + * depends on the channel type. Modifier is set in + * channel2. Examples are IIO_MOD_X for axial sensors about + * the 'x' axis. + * @indexed: Specify the channel has a numerical index. If not, + * the value in channel will be suppressed for attribute + * but not for event codes. Typically set it to 0 when + * the index is false. + * @differential: Channel is differential. + */ +struct iio_chan_spec { + enum iio_chan_type type; + int channel; + int channel2; + unsigned long address; + int scan_index; + struct { + char sign; + u8 realbits; + u8 storagebits; + u8 shift; + enum iio_endian endianness; + } scan_type; + long info_mask; + long event_mask; + const struct iio_chan_spec_ext_info *ext_info; + char *extend_name; + const char *datasheet_name; + unsigned processed_val:1; + unsigned modified:1; + unsigned indexed:1; + unsigned output:1; + unsigned differential:1; +}; + +#define IIO_ST(si, rb, sb, sh) \ + { .sign = si, .realbits = rb, .storagebits = sb, .shift = sh } + +/* Macro assumes input channels */ +#define IIO_CHAN(_type, _mod, _indexed, _proc, _name, _chan, _chan2, \ + _inf_mask, _address, _si, _stype, _event_mask) \ + { .type = _type, \ + .output = 0, \ + .modified = _mod, \ + .indexed = _indexed, \ + .processed_val = _proc, \ + .extend_name = _name, \ + .channel = _chan, \ + .channel2 = _chan2, \ + .info_mask = _inf_mask, \ + .address = _address, \ + .scan_index = _si, \ + .scan_type = _stype, \ + .event_mask = _event_mask } + +#define IIO_CHAN_SOFT_TIMESTAMP(_si) \ + { .type = IIO_TIMESTAMP, .channel = -1, \ + .scan_index = _si, .scan_type = IIO_ST('s', 64, 64, 0) } + +/** + * iio_get_time_ns() - utility function to get a time stamp for events etc + **/ +static inline s64 iio_get_time_ns(void) +{ + struct timespec ts; + /* + * calls getnstimeofday. + * If hrtimers then up to ns accurate, if not microsecond. + */ + ktime_get_real_ts(&ts); + + return timespec_to_ns(&ts); +} + +/* Device operating modes */ +#define INDIO_DIRECT_MODE 0x01 +#define INDIO_BUFFER_TRIGGERED 0x02 +#define INDIO_BUFFER_HARDWARE 0x08 + +#define INDIO_ALL_BUFFER_MODES \ + (INDIO_BUFFER_TRIGGERED | INDIO_BUFFER_HARDWARE) + +struct iio_trigger; /* forward declaration */ +struct iio_dev; + +/** + * struct iio_info - constant information about device + * @driver_module: module structure used to ensure correct + * ownership of chrdevs etc + * @event_attrs: event control attributes + * @attrs: general purpose device attributes + * @read_raw: function to request a value from the device. + * mask specifies which value. Note 0 means a reading of + * the channel in question. Return value will specify the + * type of value returned by the device. val and val2 will + * contain the elements making up the returned value. + * @write_raw: function to write a value to the device. + * Parameters are the same as for read_raw. + * @write_raw_get_fmt: callback function to query the expected + * format/precision. If not set by the driver, write_raw + * returns IIO_VAL_INT_PLUS_MICRO. + * @read_event_config: find out if the event is enabled. + * @write_event_config: set if the event is enabled. + * @read_event_value: read a value associated with the event. Meaning + * is event dependant. event_code specifies which event. + * @write_event_value: write the value associated with the event. + * Meaning is event dependent. + * @validate_trigger: function to validate the trigger when the + * current trigger gets changed. + **/ +struct iio_info { + struct module *driver_module; + struct attribute_group *event_attrs; + const struct attribute_group *attrs; + + int (*read_raw)(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask); + + int (*write_raw)(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask); + + int (*write_raw_get_fmt)(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask); + + int (*read_event_config)(struct iio_dev *indio_dev, + u64 event_code); + + int (*write_event_config)(struct iio_dev *indio_dev, + u64 event_code, + int state); + + int (*read_event_value)(struct iio_dev *indio_dev, + u64 event_code, + int *val); + int (*write_event_value)(struct iio_dev *indio_dev, + u64 event_code, + int val); + int (*validate_trigger)(struct iio_dev *indio_dev, + struct iio_trigger *trig); + int (*update_scan_mode)(struct iio_dev *indio_dev, + const unsigned long *scan_mask); + int (*debugfs_reg_access)(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval); +}; + +/** + * struct iio_buffer_setup_ops - buffer setup related callbacks + * @preenable: [DRIVER] function to run prior to marking buffer enabled + * @postenable: [DRIVER] function to run after marking buffer enabled + * @predisable: [DRIVER] function to run prior to marking buffer + * disabled + * @postdisable: [DRIVER] function to run after marking buffer disabled + */ +struct iio_buffer_setup_ops { + int (*preenable)(struct iio_dev *); + int (*postenable)(struct iio_dev *); + int (*predisable)(struct iio_dev *); + int (*postdisable)(struct iio_dev *); +}; + +/** + * struct iio_dev - industrial I/O device + * @id: [INTERN] used to identify device internally + * @modes: [DRIVER] operating modes supported by device + * @currentmode: [DRIVER] current operating mode + * @dev: [DRIVER] device structure, should be assigned a parent + * and owner + * @event_interface: [INTERN] event chrdevs associated with interrupt lines + * @buffer: [DRIVER] any buffer present + * @mlock: [INTERN] lock used to prevent simultaneous device state + * changes + * @available_scan_masks: [DRIVER] optional array of allowed bitmasks + * @masklength: [INTERN] the length of the mask established from + * channels + * @active_scan_mask: [INTERN] union of all scan masks requested by buffers + * @trig: [INTERN] current device trigger (buffer modes) + * @pollfunc: [DRIVER] function run on trigger being received + * @channels: [DRIVER] channel specification structure table + * @num_channels: [DRIVER] number of chanels specified in @channels. + * @channel_attr_list: [INTERN] keep track of automatically created channel + * attributes + * @chan_attr_group: [INTERN] group for all attrs in base directory + * @name: [DRIVER] name of the device. + * @info: [DRIVER] callbacks and constant info from driver + * @info_exist_lock: [INTERN] lock to prevent use during removal + * @chrdev: [INTERN] associated character device + * @groups: [INTERN] attribute groups + * @groupcounter: [INTERN] index of next attribute group + * @flags: [INTERN] file ops related flags including busy flag. + * @debugfs_dentry: [INTERN] device specific debugfs dentry. + * @cached_reg_addr: [INTERN] cached register address for debugfs reads. + */ +struct iio_dev { + int id; + + int modes; + int currentmode; + struct device dev; + + struct iio_event_interface *event_interface; + + struct iio_buffer *buffer; + struct mutex mlock; + + const unsigned long *available_scan_masks; + unsigned masklength; + const unsigned long *active_scan_mask; + struct iio_trigger *trig; + struct iio_poll_func *pollfunc; + + struct iio_chan_spec const *channels; + int num_channels; + + struct list_head channel_attr_list; + struct attribute_group chan_attr_group; + const char *name; + const struct iio_info *info; + struct mutex info_exist_lock; + const struct iio_buffer_setup_ops *setup_ops; + struct cdev chrdev; +#define IIO_MAX_GROUPS 6 + const struct attribute_group *groups[IIO_MAX_GROUPS + 1]; + int groupcounter; + + unsigned long flags; +#if defined(CONFIG_DEBUG_FS) + struct dentry *debugfs_dentry; + unsigned cached_reg_addr; +#endif +}; + +/** + * iio_find_channel_from_si() - get channel from its scan index + * @indio_dev: device + * @si: scan index to match + */ +const struct iio_chan_spec +*iio_find_channel_from_si(struct iio_dev *indio_dev, int si); + +/** + * iio_device_register() - register a device with the IIO subsystem + * @indio_dev: Device structure filled by the device driver + **/ +int iio_device_register(struct iio_dev *indio_dev); + +/** + * iio_device_unregister() - unregister a device from the IIO subsystem + * @indio_dev: Device structure representing the device. + **/ +void iio_device_unregister(struct iio_dev *indio_dev); + +/** + * iio_push_event() - try to add event to the list for userspace reading + * @indio_dev: IIO device structure + * @ev_code: What event + * @timestamp: When the event occurred + **/ +int iio_push_event(struct iio_dev *indio_dev, u64 ev_code, s64 timestamp); + +extern struct bus_type iio_bus_type; + +/** + * iio_put_device() - reference counted deallocation of struct device + * @dev: the iio_device containing the device + **/ +static inline void iio_put_device(struct iio_dev *indio_dev) +{ + if (indio_dev) + put_device(&indio_dev->dev); +}; + +/* Can we make this smaller? */ +#define IIO_ALIGN L1_CACHE_BYTES +/** + * iio_allocate_device() - allocate an iio_dev from a driver + * @sizeof_priv: Space to allocate for private structure. + **/ +struct iio_dev *iio_allocate_device(int sizeof_priv); + +static inline void *iio_priv(const struct iio_dev *indio_dev) +{ + return (char *)indio_dev + ALIGN(sizeof(struct iio_dev), IIO_ALIGN); +} + +static inline struct iio_dev *iio_priv_to_dev(void *priv) +{ + return (struct iio_dev *)((char *)priv - + ALIGN(sizeof(struct iio_dev), IIO_ALIGN)); +} + +/** + * iio_free_device() - free an iio_dev from a driver + * @dev: the iio_dev associated with the device + **/ +void iio_free_device(struct iio_dev *indio_dev); + +/** + * iio_buffer_enabled() - helper function to test if the buffer is enabled + * @indio_dev: IIO device info structure for device + **/ +static inline bool iio_buffer_enabled(struct iio_dev *indio_dev) +{ + return indio_dev->currentmode + & (INDIO_BUFFER_TRIGGERED | INDIO_BUFFER_HARDWARE); +}; + +/** + * iio_get_debugfs_dentry() - helper function to get the debugfs_dentry + * @indio_dev: IIO device info structure for device + **/ +#if defined(CONFIG_DEBUG_FS) +static inline struct dentry *iio_get_debugfs_dentry(struct iio_dev *indio_dev) +{ + return indio_dev->debugfs_dentry; +}; +#else +static inline struct dentry *iio_get_debugfs_dentry(struct iio_dev *indio_dev) +{ + return NULL; +}; +#endif + +#endif /* _INDUSTRIAL_IO_H_ */ diff --git a/drivers/staging/iio/iio_core.h b/drivers/staging/iio/iio_core.h new file mode 100644 index 00000000..c9dfcba0 --- /dev/null +++ b/drivers/staging/iio/iio_core.h @@ -0,0 +1,56 @@ +/* The industrial I/O core function defs. + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * These definitions are meant for use only within the IIO core, not individual + * drivers. + */ + +#ifndef _IIO_CORE_H_ +#define _IIO_CORE_H_ + +int __iio_add_chan_devattr(const char *postfix, + struct iio_chan_spec const *chan, + ssize_t (*func)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*writefunc)(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len), + u64 mask, + bool generic, + struct device *dev, + struct list_head *attr_list); + +/* Event interface flags */ +#define IIO_BUSY_BIT_POS 1 + +#ifdef CONFIG_IIO_BUFFER +struct poll_table_struct; + +unsigned int iio_buffer_poll(struct file *filp, + struct poll_table_struct *wait); +ssize_t iio_buffer_read_first_n_outer(struct file *filp, char __user *buf, + size_t n, loff_t *f_ps); + + +#define iio_buffer_poll_addr (&iio_buffer_poll) +#define iio_buffer_read_first_n_outer_addr (&iio_buffer_read_first_n_outer) + +#else + +#define iio_buffer_poll_addr NULL +#define iio_buffer_read_first_n_outer_addr NULL + +#endif + +int iio_device_register_eventset(struct iio_dev *indio_dev); +void iio_device_unregister_eventset(struct iio_dev *indio_dev); +int iio_event_getfd(struct iio_dev *indio_dev); + +#endif diff --git a/drivers/staging/iio/iio_core_trigger.h b/drivers/staging/iio/iio_core_trigger.h new file mode 100644 index 00000000..6f7c56fc --- /dev/null +++ b/drivers/staging/iio/iio_core_trigger.h @@ -0,0 +1,46 @@ + +/* The industrial I/O core, trigger consumer handling functions + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#ifdef CONFIG_IIO_TRIGGER +/** + * iio_device_register_trigger_consumer() - set up an iio_dev to use triggers + * @indio_dev: iio_dev associated with the device that will consume the trigger + **/ +void iio_device_register_trigger_consumer(struct iio_dev *indio_dev); + +/** + * iio_device_unregister_trigger_consumer() - reverse the registration process + * @indio_dev: iio_dev associated with the device that consumed the trigger + **/ +void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev); + +#else + +/** + * iio_device_register_trigger_consumer() - set up an iio_dev to use triggers + * @indio_dev: iio_dev associated with the device that will consume the trigger + **/ +static int iio_device_register_trigger_consumer(struct iio_dev *indio_dev) +{ + return 0; +}; + +/** + * iio_device_unregister_trigger_consumer() - reverse the registration process + * @indio_dev: iio_dev associated with the device that consumed the trigger + **/ +static void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev) +{ +}; + +#endif /* CONFIG_TRIGGER_CONSUMER */ + + + diff --git a/drivers/staging/iio/iio_dummy_evgen.c b/drivers/staging/iio/iio_dummy_evgen.c new file mode 100644 index 00000000..f39f346b --- /dev/null +++ b/drivers/staging/iio/iio_dummy_evgen.c @@ -0,0 +1,221 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Companion module to the iio simple dummy example driver. + * The purpose of this is to generate 'fake' event interrupts thus + * allowing that driver's code to be as close as possible to that of + * a normal driver talking to hardware. The approach used here + * is not intended to be general and just happens to work for this + * particular use case. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "iio_dummy_evgen.h" +#include "iio.h" +#include "sysfs.h" + +/* Fiddly bit of faking and irq without hardware */ +#define IIO_EVENTGEN_NO 10 +/** + * struct iio_dummy_evgen - evgen state + * @chip: irq chip we are faking + * @base: base of irq range + * @enabled: mask of which irqs are enabled + * @inuse: mask of which irqs are connected + * @lock: protect the evgen state + */ +struct iio_dummy_eventgen { + struct irq_chip chip; + int base; + bool enabled[IIO_EVENTGEN_NO]; + bool inuse[IIO_EVENTGEN_NO]; + struct mutex lock; +}; + +/* We can only ever have one instance of this 'device' */ +static struct iio_dummy_eventgen *iio_evgen; +static const char *iio_evgen_name = "iio_dummy_evgen"; + +static void iio_dummy_event_irqmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_dummy_eventgen *evgen = + container_of(chip, struct iio_dummy_eventgen, chip); + + evgen->enabled[d->irq - evgen->base] = false; +} + +static void iio_dummy_event_irqunmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_dummy_eventgen *evgen = + container_of(chip, struct iio_dummy_eventgen, chip); + + evgen->enabled[d->irq - evgen->base] = true; +} + +static int iio_dummy_evgen_create(void) +{ + int ret, i; + + iio_evgen = kzalloc(sizeof(*iio_evgen), GFP_KERNEL); + if (iio_evgen == NULL) + return -ENOMEM; + + iio_evgen->base = irq_alloc_descs(-1, 0, IIO_EVENTGEN_NO, 0); + if (iio_evgen->base < 0) { + ret = iio_evgen->base; + kfree(iio_evgen); + return ret; + } + iio_evgen->chip.name = iio_evgen_name; + iio_evgen->chip.irq_mask = &iio_dummy_event_irqmask; + iio_evgen->chip.irq_unmask = &iio_dummy_event_irqunmask; + for (i = 0; i < IIO_EVENTGEN_NO; i++) { + irq_set_chip(iio_evgen->base + i, &iio_evgen->chip); + irq_set_handler(iio_evgen->base + i, &handle_simple_irq); + irq_modify_status(iio_evgen->base + i, + IRQ_NOREQUEST | IRQ_NOAUTOEN, + IRQ_NOPROBE); + } + mutex_init(&iio_evgen->lock); + return 0; +} + +/** + * iio_dummy_evgen_get_irq() - get an evgen provided irq for a device + * + * This function will give a free allocated irq to a client device. + * That irq can then be caused to 'fire' by using the associated sysfs file. + */ +int iio_dummy_evgen_get_irq(void) +{ + int i, ret = 0; + + if (iio_evgen == NULL) + return -ENODEV; + + mutex_lock(&iio_evgen->lock); + for (i = 0; i < IIO_EVENTGEN_NO; i++) + if (iio_evgen->inuse[i] == false) { + ret = iio_evgen->base + i; + iio_evgen->inuse[i] = true; + break; + } + mutex_unlock(&iio_evgen->lock); + if (i == IIO_EVENTGEN_NO) + return -ENOMEM; + return ret; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_get_irq); + +/** + * iio_dummy_evgen_release_irq() - give the irq back. + * @irq: irq being returned to the pool + * + * Used by client driver instances to give the irqs back when they disconnect + */ +int iio_dummy_evgen_release_irq(int irq) +{ + mutex_lock(&iio_evgen->lock); + iio_evgen->inuse[irq - iio_evgen->base] = false; + mutex_unlock(&iio_evgen->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_release_irq); + +static void iio_dummy_evgen_free(void) +{ + irq_free_descs(iio_evgen->base, IIO_EVENTGEN_NO); + kfree(iio_evgen); +} + +static void iio_evgen_release(struct device *dev) +{ + iio_dummy_evgen_free(); +} + +static ssize_t iio_evgen_poke(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + if (iio_evgen->enabled[this_attr->address]) + handle_nested_irq(iio_evgen->base + this_attr->address); + + return len; +} + +static IIO_DEVICE_ATTR(poke_ev0, S_IWUSR, NULL, &iio_evgen_poke, 0); +static IIO_DEVICE_ATTR(poke_ev1, S_IWUSR, NULL, &iio_evgen_poke, 1); +static IIO_DEVICE_ATTR(poke_ev2, S_IWUSR, NULL, &iio_evgen_poke, 2); +static IIO_DEVICE_ATTR(poke_ev3, S_IWUSR, NULL, &iio_evgen_poke, 3); +static IIO_DEVICE_ATTR(poke_ev4, S_IWUSR, NULL, &iio_evgen_poke, 4); +static IIO_DEVICE_ATTR(poke_ev5, S_IWUSR, NULL, &iio_evgen_poke, 5); +static IIO_DEVICE_ATTR(poke_ev6, S_IWUSR, NULL, &iio_evgen_poke, 6); +static IIO_DEVICE_ATTR(poke_ev7, S_IWUSR, NULL, &iio_evgen_poke, 7); +static IIO_DEVICE_ATTR(poke_ev8, S_IWUSR, NULL, &iio_evgen_poke, 8); +static IIO_DEVICE_ATTR(poke_ev9, S_IWUSR, NULL, &iio_evgen_poke, 9); + +static struct attribute *iio_evgen_attrs[] = { + &iio_dev_attr_poke_ev0.dev_attr.attr, + &iio_dev_attr_poke_ev1.dev_attr.attr, + &iio_dev_attr_poke_ev2.dev_attr.attr, + &iio_dev_attr_poke_ev3.dev_attr.attr, + &iio_dev_attr_poke_ev4.dev_attr.attr, + &iio_dev_attr_poke_ev5.dev_attr.attr, + &iio_dev_attr_poke_ev6.dev_attr.attr, + &iio_dev_attr_poke_ev7.dev_attr.attr, + &iio_dev_attr_poke_ev8.dev_attr.attr, + &iio_dev_attr_poke_ev9.dev_attr.attr, + NULL, +}; + +static const struct attribute_group iio_evgen_group = { + .attrs = iio_evgen_attrs, +}; + +static const struct attribute_group *iio_evgen_groups[] = { + &iio_evgen_group, + NULL +}; + +static struct device iio_evgen_dev = { + .bus = &iio_bus_type, + .groups = iio_evgen_groups, + .release = &iio_evgen_release, +}; +static __init int iio_dummy_evgen_init(void) +{ + int ret = iio_dummy_evgen_create(); + if (ret < 0) + return ret; + device_initialize(&iio_evgen_dev); + dev_set_name(&iio_evgen_dev, "iio_evgen"); + return device_add(&iio_evgen_dev); +} +module_init(iio_dummy_evgen_init); + +static __exit void iio_dummy_evgen_exit(void) +{ + device_unregister(&iio_evgen_dev); +} +module_exit(iio_dummy_evgen_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/iio_dummy_evgen.h b/drivers/staging/iio/iio_dummy_evgen.h new file mode 100644 index 00000000..d8845e22 --- /dev/null +++ b/drivers/staging/iio/iio_dummy_evgen.h @@ -0,0 +1,2 @@ +int iio_dummy_evgen_get_irq(void); +int iio_dummy_evgen_release_irq(int irq); diff --git a/drivers/staging/iio/iio_hwmon.c b/drivers/staging/iio/iio_hwmon.c new file mode 100644 index 00000000..a603a5f5 --- /dev/null +++ b/drivers/staging/iio/iio_hwmon.c @@ -0,0 +1,232 @@ +/* Hwmon client for industrial I/O devices + * + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "consumer.h" +#include "types.h" + +/** + * struct iio_hwmon_state - device instance state + * @channels: filled with array of channels from iio + * @num_channels: number of channels in channels (saves counting twice) + * @hwmon_dev: associated hwmon device + * @attr_group: the group of attributes + * @attrs: null terminated array of attribute pointers. + */ +struct iio_hwmon_state { + struct iio_channel *channels; + int num_channels; + struct device *hwmon_dev; + struct attribute_group attr_group; + struct attribute **attrs; +}; + +/* + * Assumes that IIO and hwmon operate in the same base units. + * This is supposed to be true, but needs verification for + * new channel types. + */ +static ssize_t iio_hwmon_read_val(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + long result; + int val, ret, scaleint, scalepart; + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct iio_hwmon_state *state = dev_get_drvdata(dev); + + /* + * No locking between this pair, so theoretically possible + * the scale has changed. + */ + ret = iio_st_read_channel_raw(&state->channels[sattr->index], + &val); + if (ret < 0) + return ret; + + ret = iio_st_read_channel_scale(&state->channels[sattr->index], + &scaleint, &scalepart); + if (ret < 0) + return ret; + switch (ret) { + case IIO_VAL_INT: + result = val * scaleint; + break; + case IIO_VAL_INT_PLUS_MICRO: + result = (s64)val * (s64)scaleint + + div_s64((s64)val * (s64)scalepart, 1000000LL); + break; + case IIO_VAL_INT_PLUS_NANO: + result = (s64)val * (s64)scaleint + + div_s64((s64)val * (s64)scalepart, 1000000000LL); + break; + default: + return -EINVAL; + } + return sprintf(buf, "%ld\n", result); +} + +static void iio_hwmon_free_attrs(struct iio_hwmon_state *st) +{ + int i; + struct sensor_device_attribute *a; + for (i = 0; i < st->num_channels; i++) + if (st->attrs[i]) { + a = to_sensor_dev_attr( + container_of(st->attrs[i], + struct device_attribute, + attr)); + kfree(a); + } +} + +static int __devinit iio_hwmon_probe(struct platform_device *pdev) +{ + struct iio_hwmon_state *st; + struct sensor_device_attribute *a; + int ret, i; + int in_i = 1, temp_i = 1, curr_i = 1; + enum iio_chan_type type; + + st = kzalloc(sizeof(*st), GFP_KERNEL); + if (st == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + st->channels = iio_st_channel_get_all(dev_name(&pdev->dev)); + if (IS_ERR(st->channels)) { + ret = PTR_ERR(st->channels); + goto error_free_state; + } + + /* count how many attributes we have */ + while (st->channels[st->num_channels].indio_dev) + st->num_channels++; + + st->attrs = kzalloc(sizeof(st->attrs) * (st->num_channels + 1), + GFP_KERNEL); + if (st->attrs == NULL) { + ret = -ENOMEM; + goto error_release_channels; + } + for (i = 0; i < st->num_channels; i++) { + a = kzalloc(sizeof(*a), GFP_KERNEL); + if (a == NULL) { + ret = -ENOMEM; + goto error_free_attrs; + } + + sysfs_attr_init(&a->dev_attr.attr); + ret = iio_st_get_channel_type(&st->channels[i], &type); + if (ret < 0) { + kfree(a); + goto error_free_attrs; + } + switch (type) { + case IIO_VOLTAGE: + a->dev_attr.attr.name = kasprintf(GFP_KERNEL, + "in%d_input", + in_i++); + break; + case IIO_TEMP: + a->dev_attr.attr.name = kasprintf(GFP_KERNEL, + "temp%d_input", + temp_i++); + break; + case IIO_CURRENT: + a->dev_attr.attr.name = kasprintf(GFP_KERNEL, + "curr%d_input", + curr_i++); + break; + default: + ret = -EINVAL; + kfree(a); + goto error_free_attrs; + } + if (a->dev_attr.attr.name == NULL) { + kfree(a); + ret = -ENOMEM; + goto error_free_attrs; + } + a->dev_attr.show = iio_hwmon_read_val; + a->dev_attr.attr.mode = S_IRUGO; + a->index = i; + st->attrs[i] = &a->dev_attr.attr; + } + + st->attr_group.attrs = st->attrs; + platform_set_drvdata(pdev, st); + ret = sysfs_create_group(&pdev->dev.kobj, &st->attr_group); + if (ret < 0) + goto error_free_attrs; + + st->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(st->hwmon_dev)) { + ret = PTR_ERR(st->hwmon_dev); + goto error_remove_group; + } + return 0; + +error_remove_group: + sysfs_remove_group(&pdev->dev.kobj, &st->attr_group); +error_free_attrs: + iio_hwmon_free_attrs(st); + kfree(st->attrs); +error_release_channels: + iio_st_channel_release_all(st->channels); +error_free_state: + kfree(st); +error_ret: + return ret; +} + +static int __devexit iio_hwmon_remove(struct platform_device *pdev) +{ + struct iio_hwmon_state *st = platform_get_drvdata(pdev); + + hwmon_device_unregister(st->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &st->attr_group); + iio_hwmon_free_attrs(st); + kfree(st->attrs); + iio_st_channel_release_all(st->channels); + + return 0; +} + +static struct platform_driver __refdata iio_hwmon_driver = { + .driver = { + .name = "iio_hwmon", + .owner = THIS_MODULE, + }, + .probe = iio_hwmon_probe, + .remove = __devexit_p(iio_hwmon_remove), +}; + +static int iio_inkern_init(void) +{ + return platform_driver_register(&iio_hwmon_driver); +} +module_init(iio_inkern_init); + +static void iio_inkern_exit(void) +{ + platform_driver_unregister(&iio_hwmon_driver); +} +module_exit(iio_inkern_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO to hwmon driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/iio_simple_dummy.c b/drivers/staging/iio/iio_simple_dummy.c new file mode 100644 index 00000000..e3a94572 --- /dev/null +++ b/drivers/staging/iio/iio_simple_dummy.c @@ -0,0 +1,550 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * A reference industrial I/O driver to illustrate the functionality available. + * + * There are numerous real drivers to illustrate the finer points. + * The purpose of this driver is to provide a driver with far more comments + * and explanatory notes than any 'real' driver would have. + * Anyone starting out writing an IIO driver should first make sure they + * understand all of this driver except those bits specifically marked + * as being present to allow us to 'fake' the presence of hardware. + */ +#include +#include +#include +#include + +#include "iio.h" +#include "sysfs.h" +#include "events.h" +#include "buffer.h" +#include "iio_simple_dummy.h" + +/* + * A few elements needed to fake a bus for this driver + * Note instances parmeter controls how many of these + * dummy devices are registered. + */ +static unsigned instances = 1; +module_param(instances, int, 0); + +/* Pointer array used to fake bus elements */ +static struct iio_dev **iio_dummy_devs; + +/* Fake a name for the part number, usually obtained from the id table */ +static const char *iio_dummy_part_number = "iio_dummy_part_no"; + +/** + * struct iio_dummy_accel_calibscale - realworld to register mapping + * @val: first value in read_raw - here integer part. + * @val2: second value in read_raw etc - here micro part. + * @regval: register value - magic device specific numbers. + */ +struct iio_dummy_accel_calibscale { + int val; + int val2; + int regval; /* what would be written to hardware */ +}; + +static const struct iio_dummy_accel_calibscale dummy_scales[] = { + { 0, 100, 0x8 }, /* 0.000100 */ + { 0, 133, 0x7 }, /* 0.000133 */ + { 733, 13, 0x9 }, /* 733.00013 */ +}; + +/* + * iio_dummy_channels - Description of available channels + * + * This array of structures tells the IIO core about what the device + * actually provides for a given channel. + */ +static struct iio_chan_spec iio_dummy_channels[] = { + /* indexed ADC channel in_voltage0_raw etc */ + { + .type = IIO_VOLTAGE, + /* Channel has a numeric index of 0 */ + .indexed = 1, + .channel = 0, + /* What other information is available? */ + .info_mask = + /* + * in_voltage0_offset + * Offset for userspace to apply prior to scale + * when converting to standard units (microvolts) + */ + IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + /* + * in_voltage0_scale + * Multipler for userspace to apply post offset + * when converting to standard units (microvolts) + */ + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + /* The ordering of elements in the buffer via an enum */ + .scan_index = voltage0, + .scan_type = { /* Description of storage in buffer */ + .sign = 'u', /* unsigned */ + .realbits = 13, /* 13 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + /* + * simple event - triggered when value rises above + * a threshold + */ + .event_mask = IIO_EV_BIT(IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + /* Differential ADC channel in_voltage1-voltage2_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + /* + * Indexing for differential channels uses channel + * for the positive part, channel2 for the negative. + */ + .indexed = 1, + .channel = 1, + .channel2 = 2, + .info_mask = + /* + * in_voltage-voltage_scale + * Shared version of scale - shared by differential + * input channels of type IIO_VOLTAGE. + */ + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = diffvoltage1m2, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 12, /* 12 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* Differential ADC channel in_voltage3-voltage4_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 3, + .channel2 = 4, + .info_mask = + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .scan_index = diffvoltage3m4, + .scan_type = { + .sign = 's', + .realbits = 11, + .storagebits = 16, + .shift = 0, + }, + }, + /* + * 'modified' (i.e. axis specified) acceleration channel + * in_accel_z_raw + */ + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_X, + .info_mask = + /* + * Internal bias correction value. Applied + * by the hardware or driver prior to userspace + * seeing the readings. Typically part of hardware + * calibration. + */ + IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT, + .scan_index = accelx, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 12 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* + * Convenience macro for timestamps. 4 is the index in + * the buffer. + */ + IIO_CHAN_SOFT_TIMESTAMP(4), + /* DAC channel out_voltage0_raw */ + { + .type = IIO_VOLTAGE, + .output = 1, + .indexed = 1, + .channel = 0, + }, +}; + +/** + * iio_dummy_read_raw() - data read function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be read + * @val: first element of returned value (typically INT) + * @val2: second element of returned value (typically MICRO) + * @mask: what we actually want to read. 0 is the channel, everything else + * is as per the info_mask in iio_chan_spec. + */ +static int iio_dummy_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&st->lock); + switch (mask) { + case 0: /* magic value - channel value read */ + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) { + /* Set integer part to cached value */ + *val = st->dac_val; + ret = IIO_VAL_INT; + } else if (chan->differential) { + if (chan->channel == 1) + *val = st->differential_adc_val[0]; + else + *val = st->differential_adc_val[1]; + ret = IIO_VAL_INT; + } else { + *val = st->single_ended_adc_val; + ret = IIO_VAL_INT; + } + break; + case IIO_ACCEL: + *val = st->accel_val; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* only single ended adc -> 7 */ + *val = 7; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->differential) { + case 0: + /* only single ended adc -> 0.001333 */ + *val = 0; + *val2 = 1333; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case 1: + /* all differential adc channels -> 0.000001344 */ + *val = 0; + *val2 = 1344; + ret = IIO_VAL_INT_PLUS_NANO; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + /* only the acceleration axis - read from cache */ + *val = st->accel_calibbias; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + *val = st->accel_calibscale->val; + *val2 = st->accel_calibscale->val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + break; + } + mutex_unlock(&st->lock); + return ret; +} + +/** + * iio_dummy_write_raw() - data write function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be read + * @val: first element of returned value (typically INT) + * @val2: second element of returned value (typically MICRO) + * @mask: what we actually want to read. 0 is the channel, everything else + * is as per the info_mask in iio_chan_spec. + * + * Note that all raw writes are assumed IIO_VAL_INT and info mask elements + * are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt + * in struct iio_info is provided by the driver. + */ +static int iio_dummy_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int i; + int ret = 0; + struct iio_dummy_state *st = iio_priv(indio_dev); + + switch (mask) { + case 0: + if (chan->output == 0) + return -EINVAL; + + /* Locking not required as writing single value */ + mutex_lock(&st->lock); + st->dac_val = val; + mutex_unlock(&st->lock); + return 0; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&st->lock); + /* Compare against table - hard matching here */ + for (i = 0; i < ARRAY_SIZE(dummy_scales); i++) + if (val == dummy_scales[i].val && + val2 == dummy_scales[i].val2) + break; + if (i == ARRAY_SIZE(dummy_scales)) + ret = -EINVAL; + else + st->accel_calibscale = &dummy_scales[i]; + mutex_unlock(&st->lock); + return ret; + default: + return -EINVAL; + } +} + +/* + * Device type specific information. + */ +static const struct iio_info iio_dummy_info = { + .driver_module = THIS_MODULE, + .read_raw = &iio_dummy_read_raw, + .write_raw = &iio_dummy_write_raw, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .read_event_config = &iio_simple_dummy_read_event_config, + .write_event_config = &iio_simple_dummy_write_event_config, + .read_event_value = &iio_simple_dummy_read_event_value, + .write_event_value = &iio_simple_dummy_write_event_value, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +/** + * iio_dummy_init_device() - device instance specific init + * @indio_dev: the iio device structure + * + * Most drivers have one of these to set up default values, + * reset the device to known state etc. + */ +static int iio_dummy_init_device(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->dac_val = 0; + st->single_ended_adc_val = 73; + st->differential_adc_val[0] = 33; + st->differential_adc_val[1] = -34; + st->accel_val = 34; + st->accel_calibbias = -7; + st->accel_calibscale = &dummy_scales[0]; + + return 0; +} + +/** + * iio_dummy_probe() - device instance probe + * @index: an id number for this instance. + * + * Arguments are bus type specific. + * I2C: iio_dummy_probe(struct i2c_client *client, + * const struct i2c_device_id *id) + * SPI: iio_dummy_probe(struct spi_device *spi) + */ +static int __devinit iio_dummy_probe(int index) +{ + int ret; + struct iio_dev *indio_dev; + struct iio_dummy_state *st; + + /* + * Allocate an IIO device. + * + * This structure contains all generic state + * information about the device instance. + * It also has a region (accessed by iio_priv() + * for chip specific state information. + */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + + iio_dummy_init_device(indio_dev); + /* + * With hardware: Set the parent device. + * indio_dev->dev.parent = &spi->dev; + * indio_dev->dev.parent = &client->dev; + */ + + /* + * Make the iio_dev struct available to remove function. + * Bus equivalents + * i2c_set_clientdata(client, indio_dev); + * spi_set_drvdata(spi, indio_dev); + */ + iio_dummy_devs[index] = indio_dev; + + + /* + * Set the device name. + * + * This is typically a part number and obtained from the module + * id table. + * e.g. for i2c and spi: + * indio_dev->name = id->name; + * indio_dev->name = spi_get_device_id(spi)->name; + */ + indio_dev->name = iio_dummy_part_number; + + /* Provide description of available channels */ + indio_dev->channels = iio_dummy_channels; + indio_dev->num_channels = ARRAY_SIZE(iio_dummy_channels); + + /* + * Provide device type specific interface functions and + * constant data. + */ + indio_dev->info = &iio_dummy_info; + + /* Specify that device provides sysfs type interfaces */ + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_simple_dummy_events_register(indio_dev); + if (ret < 0) + goto error_free_device; + + /* Configure buffered capture support. */ + ret = iio_simple_dummy_configure_buffer(indio_dev); + if (ret < 0) + goto error_unregister_events; + + /* + * Register the channels with the buffer, but avoid the output + * channel being registered by reducing the number of channels by 1. + */ + ret = iio_buffer_register(indio_dev, iio_dummy_channels, 5); + if (ret < 0) + goto error_unconfigure_buffer; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_unregister_buffer; + + return 0; +error_unregister_buffer: + iio_buffer_unregister(indio_dev); +error_unconfigure_buffer: + iio_simple_dummy_unconfigure_buffer(indio_dev); +error_unregister_events: + iio_simple_dummy_events_unregister(indio_dev); +error_free_device: + /* Note free device should only be called, before registration + * has succeeded. */ + iio_free_device(indio_dev); +error_ret: + return ret; +} + +/** + * iio_dummy_remove() - device instance removal function + * @index: device index. + * + * Parameters follow those of iio_dummy_probe for buses. + */ +static int iio_dummy_remove(int index) +{ + int ret; + /* + * Get a pointer to the device instance iio_dev structure + * from the bus subsystem. E.g. + * struct iio_dev *indio_dev = i2c_get_clientdata(client); + * struct iio_dev *indio_dev = spi_get_drvdata(spi); + */ + struct iio_dev *indio_dev = iio_dummy_devs[index]; + + + /* Unregister the device */ + iio_device_unregister(indio_dev); + + /* Device specific code to power down etc */ + + /* Buffered capture related cleanup */ + iio_buffer_unregister(indio_dev); + iio_simple_dummy_unconfigure_buffer(indio_dev); + + ret = iio_simple_dummy_events_unregister(indio_dev); + if (ret) + goto error_ret; + + /* Free all structures */ + iio_free_device(indio_dev); + +error_ret: + return ret; +} + +/** + * iio_dummy_init() - device driver registration + * + * Varies depending on bus type of the device. As there is no device + * here, call probe directly. For information on device registration + * i2c: + * Documentation/i2c/writing-clients + * spi: + * Documentation/spi/spi-summary + */ +static __init int iio_dummy_init(void) +{ + int i, ret; + if (instances > 10) { + instances = 1; + return -EINVAL; + } + /* Fake a bus */ + iio_dummy_devs = kcalloc(instances, sizeof(*iio_dummy_devs), + GFP_KERNEL); + /* Here we have no actual device so call probe */ + for (i = 0; i < instances; i++) { + ret = iio_dummy_probe(i); + if (ret < 0) + return ret; + } + return 0; +} +module_init(iio_dummy_init); + +/** + * iio_dummy_exit() - device driver removal + * + * Varies depending on bus type of the device. + * As there is no device here, call remove directly. + */ +static __exit void iio_dummy_exit(void) +{ + int i; + for (i = 0; i < instances; i++) + iio_dummy_remove(i); + kfree(iio_dummy_devs); +} +module_exit(iio_dummy_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/iio_simple_dummy.h b/drivers/staging/iio/iio_simple_dummy.h new file mode 100644 index 00000000..53975d91 --- /dev/null +++ b/drivers/staging/iio/iio_simple_dummy.h @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Join together the various functionality of iio_simple_dummy driver + */ + +#include + +struct iio_dummy_accel_calibscale; + +/** + * struct iio_dummy_state - device instance specific state. + * @dac_val: cache for dac value + * @single_ended_adc_val: cache for single ended adc value + * @differential_adc_val: cache for differential adc value + * @accel_val: cache for acceleration value + * @accel_calibbias: cache for acceleration calibbias + * @accel_calibscale: cache for acceleration calibscale + * @lock: lock to ensure state is consistent + * @event_irq: irq number for event line (faked) + * @event_val: cache for event theshold value + * @event_en: cache of whether event is enabled + */ +struct iio_dummy_state { + int dac_val; + int single_ended_adc_val; + int differential_adc_val[2]; + int accel_val; + int accel_calibbias; + const struct iio_dummy_accel_calibscale *accel_calibscale; + struct mutex lock; +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + int event_irq; + int event_val; + bool event_en; +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + +struct iio_dev; + +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + u64 event_code); + +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + u64 event_code, + int state); + +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + u64 event_code, + int *val); + +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + u64 event_code, + int val); + +int iio_simple_dummy_events_register(struct iio_dev *indio_dev); +int iio_simple_dummy_events_unregister(struct iio_dev *indio_dev); + +#else /* Stubs for when events are disabled at compile time */ + +static inline int +iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + return 0; +}; + +static inline int +iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{ + return 0; +}; + +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS*/ + +/** + * enum iio_simple_dummy_scan_elements - scan index enum + * @voltage0: the single ended voltage channel + * @diffvoltage1m2: first differential channel + * @diffvoltage3m4: second differenial channel + * @accelx: acceleration channel + * + * Enum provides convenient numbering for the scan index. + */ +enum iio_simple_dummy_scan_elements { + voltage0, + diffvoltage1m2, + diffvoltage3m4, + accelx, +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_BUFFER +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev); +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev); +#else +static inline int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + return 0; +}; +static inline +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{}; +#endif /* CONFIG_IIO_SIMPLE_DUMMY_BUFFER */ diff --git a/drivers/staging/iio/iio_simple_dummy_buffer.c b/drivers/staging/iio/iio_simple_dummy_buffer.c new file mode 100644 index 00000000..bb4daf74 --- /dev/null +++ b/drivers/staging/iio/iio_simple_dummy_buffer.c @@ -0,0 +1,205 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Buffer handling elements of industrial I/O reference driver. + * Uses the kfifo buffer. + * + * To test without hardware use the sysfs trigger. + */ + +#include +#include +#include +#include +#include +#include + +#include "iio.h" +#include "trigger_consumer.h" +#include "kfifo_buf.h" + +#include "iio_simple_dummy.h" + +/* Some fake data */ + +static const s16 fakedata[] = { + [voltage0] = 7, + [diffvoltage1m2] = -33, + [diffvoltage3m4] = -2, + [accelx] = 344, +}; +/** + * iio_simple_dummy_trigger_h() - the trigger handler function + * @irq: the interrupt number + * @p: private data - always a pointer to the poll func. + * + * This is the guts of buffered capture. On a trigger event occuring, + * if the pollfunc is attached then this handler is called as a threaded + * interrupt (and hence may sleep). It is responsible for grabbing data + * from the device and pushing it into the associated buffer. + */ +static irqreturn_t iio_simple_dummy_trigger_h(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct iio_buffer *buffer = indio_dev->buffer; + int len = 0; + /* + * The datasize is obtained from the buffer. It was stored when + * the preenable setup function was called. + */ + size_t datasize = buffer->access->get_bytes_per_datum(buffer); + u16 *data = kmalloc(datasize, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) { + /* + * Three common options here: + * hardware scans: certain combinations of channels make + * up a fast read. The capture will consist of all of them. + * Hence we just call the grab data function and fill the + * buffer without processing. + * sofware scans: can be considered to be random access + * so efficient reading is just a case of minimal bus + * transactions. + * software culled hardware scans: + * occasionally a driver may process the nearest hardware + * scan to avoid storing elements that are not desired. This + * is the fidliest option by far. + * Here lets pretend we have random access. And the values are + * in the constant table fakedata. + */ + int i, j; + for (i = 0, j = 0; + i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + i++) { + j = find_next_bit(buffer->scan_mask, + indio_dev->masklength, j + 1); + /* random access read form the 'device' */ + data[i] = fakedata[j]; + len += 2; + } + } + /* Store a timestampe at an 8 byte boundary */ + if (buffer->scan_timestamp) + *(s64 *)(((phys_addr_t)data + len + + sizeof(s64) - 1) & ~(sizeof(s64) - 1)) + = iio_get_time_ns(); + buffer->access->store_to(buffer, (u8 *)data, pf->timestamp); + + kfree(data); + + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops iio_simple_dummy_buffer_setup_ops = { + /* + * iio_sw_buffer_preenable: + * Generic function for equal sized ring elements + 64 bit timestamp + * Assumes that any combination of channels can be enabled. + * Typically replaced to implement restrictions on what combinations + * can be captured (hardware scan modes). + */ + .preenable = &iio_sw_buffer_preenable, + /* + * iio_triggered_buffer_postenable: + * Generic function that simply attaches the pollfunc to the trigger. + * Replace this to mess with hardware state before we attach the + * trigger. + */ + .postenable = &iio_triggered_buffer_postenable, + /* + * iio_triggered_buffer_predisable: + * Generic function that simple detaches the pollfunc from the trigger. + * Replace this to put hardware state back again after the trigger is + * detached but before userspace knows we have disabled the ring. + */ + .predisable = &iio_triggered_buffer_predisable, +}; + +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + int ret; + struct iio_buffer *buffer; + + /* Allocate a buffer to use - here a kfifo */ + buffer = iio_kfifo_allocate(indio_dev); + if (buffer == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + indio_dev->buffer = buffer; + + /* Enable timestamps by default */ + buffer->scan_timestamp = true; + + /* + * Tell the core what device type specific functions should + * be run on either side of buffer capture enable / disable. + */ + indio_dev->setup_ops = &iio_simple_dummy_buffer_setup_ops; + + /* + * Configure a polling function. + * When a trigger event with this polling function connected + * occurs, this function is run. Typically this grabs data + * from the device. + * + * NULL for the top half. This is normally implemented only if we + * either want to ping a capture now pin (no sleeping) or grab + * a timestamp as close as possible to a data ready trigger firing. + * + * IRQF_ONESHOT ensures irqs are masked such that only one instance + * of the handler can run at a time. + * + * "iio_simple_dummy_consumer%d" formatting string for the irq 'name' + * as seen under /proc/interrupts. Remaining parameters as per printk. + */ + indio_dev->pollfunc = iio_alloc_pollfunc(NULL, + &iio_simple_dummy_trigger_h, + IRQF_ONESHOT, + indio_dev, + "iio_simple_dummy_consumer%d", + indio_dev->id); + + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_free_buffer; + } + + /* + * Notify the core that this device is capable of buffered capture + * driven by a trigger. + */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; + +error_free_buffer: + iio_kfifo_free(indio_dev->buffer); +error_ret: + return ret; + +} + +/** + * iio_simple_dummy_unconfigure_buffer() - release buffer resources + * @indo_dev: device instance state + */ +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_kfifo_free(indio_dev->buffer); +} diff --git a/drivers/staging/iio/iio_simple_dummy_events.c b/drivers/staging/iio/iio_simple_dummy_events.c new file mode 100644 index 00000000..449c7a5e --- /dev/null +++ b/drivers/staging/iio/iio_simple_dummy_events.c @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Event handling elements of industrial I/O reference driver. + */ +#include +#include +#include +#include + +#include "iio.h" +#include "sysfs.h" +#include "events.h" +#include "iio_simple_dummy.h" + +/* Evgen 'fakes' interrupt events for this example */ +#include "iio_dummy_evgen.h" + +/** + * iio_simple_dummy_read_event_config() - is event enabled? + * @indio_dev: the device instance data + * @event_code: event code of the event being queried + * + * This function would normally query the relevant registers or a cache to + * discover if the event generation is enabled on the device. + */ +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + u64 event_code) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + return st->event_en; +} + +/** + * iio_simple_dummy_write_event_config() - set whether event is enabled + * @indio_dev: the device instance data + * @event_code: event code of event being enabled/disabled + * @state: whether to enable or disable the device. + * + * This function would normally set the relevant registers on the devices + * so that it generates the specified event. Here it just sets up a cached + * value. + */ +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + u64 event_code, + int state) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + /* + * Deliberately over the top code splitting to illustrate + * how this is done when multiple events exist. + */ + switch (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(event_code)) { + case IIO_VOLTAGE: + switch (IIO_EVENT_CODE_EXTRACT_TYPE(event_code)) { + case IIO_EV_TYPE_THRESH: + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == + IIO_EV_DIR_RISING) + st->event_en = state; + else + return -EINVAL; + break; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + return 0; +} + +/** + * iio_simple_dummy_read_event_value() - get value associated with event + * @indio_dev: device instance specific data + * @event_code: event code for the event whose value is being queried + * @val: value for the event code. + * + * Many devices provide a large set of events of which only a subset may + * be enabled at a time, with value registers whose meaning changes depending + * on the event enabled. This often means that the driver must cache the values + * associated with each possible events so that the right value is in place when + * the enabled event is changed. + */ +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + u64 event_code, + int *val) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + *val = st->event_val; + + return 0; +} + +/** + * iio_simple_dummy_write_event_value() - set value associate with event + * @indio_dev: device instance specific data + * @event_code: event code for the event whose value is being set + * @val: the value to be set. + */ +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->event_val = val; + + return 0; +} + +/** + * iio_simple_dummy_event_handler() - identify and pass on event + * @irq: irq of event line + * @private: pointer to device instance state. + * + * This handler is responsible for querying the device to find out what + * event occured and for then pushing that event towards userspace. + * Here only one event occurs so we push that directly on with locally + * grabbed timestamp. + */ +static irqreturn_t iio_simple_dummy_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, 0, 0, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, 0, 0, 0), + iio_get_time_ns()); + return IRQ_HANDLED; +} + +/** + * iio_simple_dummy_events_register() - setup interrupt handling for events + * @indio_dev: device instance data + * + * This function requests the threaded interrupt to handle the events. + * Normally the irq is a hardware interrupt and the number comes + * from board configuration files. Here we get it from a companion + * module that fakes the interrupt for us. Note that module in + * no way forms part of this example. Just assume that events magically + * appear via the provided interrupt. + */ +int iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret; + + /* Fire up event source - normally not present */ + st->event_irq = iio_dummy_evgen_get_irq(); + if (st->event_irq < 0) { + ret = st->event_irq; + goto error_ret; + } + ret = request_threaded_irq(st->event_irq, + NULL, + &iio_simple_dummy_event_handler, + IRQF_ONESHOT, + "iio_simple_event", + indio_dev); + if (ret < 0) + goto error_free_evgen; + return 0; + +error_free_evgen: + iio_dummy_evgen_release_irq(st->event_irq); +error_ret: + return ret; +} + +/** + * iio_simple_dummy_events_unregister() - tidy up interrupt handling on remove + * @indio_dev: device instance data + */ +int iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + free_irq(st->event_irq, indio_dev); + /* Not part of normal driver */ + iio_dummy_evgen_release_irq(st->event_irq); + + return 0; +} diff --git a/drivers/staging/iio/impedance-analyzer/Kconfig b/drivers/staging/iio/impedance-analyzer/Kconfig new file mode 100644 index 00000000..ad0ff765 --- /dev/null +++ b/drivers/staging/iio/impedance-analyzer/Kconfig @@ -0,0 +1,18 @@ +# +# Impedance Converter, Network Analyzer drivers +# +menu "Network Analyzer, Impedance Converters" + +config AD5933 + tristate "Analog Devices AD5933, AD5934 driver" + depends on I2C + select IIO_BUFFER + select IIO_SW_RING + help + Say yes here to build support for Analog Devices Impedance Converter, + Network Analyzer, AD5933/4, provides direct access via sysfs. + + To compile this driver as a module, choose M here: the + module will be called ad5933. + +endmenu diff --git a/drivers/staging/iio/impedance-analyzer/Makefile b/drivers/staging/iio/impedance-analyzer/Makefile new file mode 100644 index 00000000..7604d786 --- /dev/null +++ b/drivers/staging/iio/impedance-analyzer/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for Impedance Converter, Network Analyzer drivers +# + +obj-$(CONFIG_AD5933) += ad5933.o diff --git a/drivers/staging/iio/impedance-analyzer/ad5933.c b/drivers/staging/iio/impedance-analyzer/ad5933.c new file mode 100644 index 00000000..cd82b56d --- /dev/null +++ b/drivers/staging/iio/impedance-analyzer/ad5933.c @@ -0,0 +1,802 @@ +/* + * AD5933 AD5934 Impedance Converter, Network Analyzer + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" +#include "../ring_sw.h" + +#include "ad5933.h" + +/* AD5933/AD5934 Registers */ +#define AD5933_REG_CONTROL_HB 0x80 /* R/W, 2 bytes */ +#define AD5933_REG_CONTROL_LB 0x81 /* R/W, 2 bytes */ +#define AD5933_REG_FREQ_START 0x82 /* R/W, 3 bytes */ +#define AD5933_REG_FREQ_INC 0x85 /* R/W, 3 bytes */ +#define AD5933_REG_INC_NUM 0x88 /* R/W, 2 bytes, 9 bit */ +#define AD5933_REG_SETTLING_CYCLES 0x8A /* R/W, 2 bytes */ +#define AD5933_REG_STATUS 0x8F /* R, 1 byte */ +#define AD5933_REG_TEMP_DATA 0x92 /* R, 2 bytes*/ +#define AD5933_REG_REAL_DATA 0x94 /* R, 2 bytes*/ +#define AD5933_REG_IMAG_DATA 0x96 /* R, 2 bytes*/ + +/* AD5933_REG_CONTROL_HB Bits */ +#define AD5933_CTRL_INIT_START_FREQ (0x1 << 4) +#define AD5933_CTRL_START_SWEEP (0x2 << 4) +#define AD5933_CTRL_INC_FREQ (0x3 << 4) +#define AD5933_CTRL_REPEAT_FREQ (0x4 << 4) +#define AD5933_CTRL_MEASURE_TEMP (0x9 << 4) +#define AD5933_CTRL_POWER_DOWN (0xA << 4) +#define AD5933_CTRL_STANDBY (0xB << 4) + +#define AD5933_CTRL_RANGE_2000mVpp (0x0 << 1) +#define AD5933_CTRL_RANGE_200mVpp (0x1 << 1) +#define AD5933_CTRL_RANGE_400mVpp (0x2 << 1) +#define AD5933_CTRL_RANGE_1000mVpp (0x3 << 1) +#define AD5933_CTRL_RANGE(x) ((x) << 1) + +#define AD5933_CTRL_PGA_GAIN_1 (0x1 << 0) +#define AD5933_CTRL_PGA_GAIN_5 (0x0 << 0) + +/* AD5933_REG_CONTROL_LB Bits */ +#define AD5933_CTRL_RESET (0x1 << 4) +#define AD5933_CTRL_INT_SYSCLK (0x0 << 3) +#define AD5933_CTRL_EXT_SYSCLK (0x1 << 3) + +/* AD5933_REG_STATUS Bits */ +#define AD5933_STAT_TEMP_VALID (0x1 << 0) +#define AD5933_STAT_DATA_VALID (0x1 << 1) +#define AD5933_STAT_SWEEP_DONE (0x1 << 2) + +/* I2C Block Commands */ +#define AD5933_I2C_BLOCK_WRITE 0xA0 +#define AD5933_I2C_BLOCK_READ 0xA1 +#define AD5933_I2C_ADDR_POINTER 0xB0 + +/* Device Specs */ +#define AD5933_INT_OSC_FREQ_Hz 16776000 +#define AD5933_MAX_OUTPUT_FREQ_Hz 100000 +#define AD5933_MAX_RETRIES 100 + +#define AD5933_OUT_RANGE 1 +#define AD5933_OUT_RANGE_AVAIL 2 +#define AD5933_OUT_SETTLING_CYCLES 3 +#define AD5933_IN_PGA_GAIN 4 +#define AD5933_IN_PGA_GAIN_AVAIL 5 +#define AD5933_FREQ_POINTS 6 + +#define AD5933_POLL_TIME_ms 10 +#define AD5933_INIT_EXCITATION_TIME_ms 100 + +struct ad5933_state { + struct i2c_client *client; + struct regulator *reg; + struct ad5933_platform_data *pdata; + struct delayed_work work; + unsigned long mclk_hz; + unsigned char ctrl_hb; + unsigned char ctrl_lb; + unsigned range_avail[4]; + unsigned short vref_mv; + unsigned short settling_cycles; + unsigned short freq_points; + unsigned freq_start; + unsigned freq_inc; + unsigned state; + unsigned poll_time_jiffies; +}; + +static struct ad5933_platform_data ad5933_default_pdata = { + .vref_mv = 3300, +}; + +static struct iio_chan_spec ad5933_channels[] = { + IIO_CHAN(IIO_TEMP, 0, 1, 1, NULL, 0, 0, 0, + 0, AD5933_REG_TEMP_DATA, IIO_ST('s', 14, 16, 0), 0), + /* Ring Channels */ + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, "real_raw", 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + AD5933_REG_REAL_DATA, 0, IIO_ST('s', 16, 16, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, "imag_raw", 0, 0, + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + AD5933_REG_IMAG_DATA, 1, IIO_ST('s', 16, 16, 0), 0), +}; + +static int ad5933_i2c_write(struct i2c_client *client, + u8 reg, u8 len, u8 *data) +{ + int ret; + + while (len--) { + ret = i2c_smbus_write_byte_data(client, reg++, *data++); + if (ret < 0) { + dev_err(&client->dev, "I2C write error\n"); + return ret; + } + } + return 0; +} + +static int ad5933_i2c_read(struct i2c_client *client, + u8 reg, u8 len, u8 *data) +{ + int ret; + + while (len--) { + ret = i2c_smbus_read_byte_data(client, reg++); + if (ret < 0) { + dev_err(&client->dev, "I2C read error\n"); + return ret; + } + *data++ = ret; + } + return 0; +} + +static int ad5933_cmd(struct ad5933_state *st, unsigned char cmd) +{ + unsigned char dat = st->ctrl_hb | cmd; + + return ad5933_i2c_write(st->client, + AD5933_REG_CONTROL_HB, 1, &dat); +} + +static int ad5933_reset(struct ad5933_state *st) +{ + unsigned char dat = st->ctrl_lb | AD5933_CTRL_RESET; + return ad5933_i2c_write(st->client, + AD5933_REG_CONTROL_LB, 1, &dat); +} + +static int ad5933_wait_busy(struct ad5933_state *st, unsigned char event) +{ + unsigned char val, timeout = AD5933_MAX_RETRIES; + int ret; + + while (timeout--) { + ret = ad5933_i2c_read(st->client, AD5933_REG_STATUS, 1, &val); + if (ret < 0) + return ret; + if (val & event) + return val; + cpu_relax(); + mdelay(1); + } + + return -EAGAIN; +} + +static int ad5933_set_freq(struct ad5933_state *st, + unsigned reg, unsigned long freq) +{ + unsigned long long freqreg; + union { + u32 d32; + u8 d8[4]; + } dat; + + freqreg = (u64) freq * (u64) (1 << 27); + do_div(freqreg, st->mclk_hz / 4); + + switch (reg) { + case AD5933_REG_FREQ_START: + st->freq_start = freq; + break; + case AD5933_REG_FREQ_INC: + st->freq_inc = freq; + break; + default: + return -EINVAL; + } + + dat.d32 = cpu_to_be32(freqreg); + return ad5933_i2c_write(st->client, reg, 3, &dat.d8[1]); +} + +static int ad5933_setup(struct ad5933_state *st) +{ + unsigned short dat; + int ret; + + ret = ad5933_reset(st); + if (ret < 0) + return ret; + + ret = ad5933_set_freq(st, AD5933_REG_FREQ_START, 10000); + if (ret < 0) + return ret; + + ret = ad5933_set_freq(st, AD5933_REG_FREQ_INC, 200); + if (ret < 0) + return ret; + + st->settling_cycles = 10; + dat = cpu_to_be16(st->settling_cycles); + + ret = ad5933_i2c_write(st->client, + AD5933_REG_SETTLING_CYCLES, 2, (u8 *)&dat); + if (ret < 0) + return ret; + + st->freq_points = 100; + dat = cpu_to_be16(st->freq_points); + + return ad5933_i2c_write(st->client, AD5933_REG_INC_NUM, 2, (u8 *)&dat); +} + +static void ad5933_calc_out_ranges(struct ad5933_state *st) +{ + int i; + unsigned normalized_3v3[4] = {1980, 198, 383, 970}; + + for (i = 0; i < 4; i++) + st->range_avail[i] = normalized_3v3[i] * st->vref_mv / 3300; + +} + +/* + * handles: AD5933_REG_FREQ_START and AD5933_REG_FREQ_INC + */ + +static ssize_t ad5933_show_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5933_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + unsigned long long freqreg; + union { + u32 d32; + u8 d8[4]; + } dat; + + mutex_lock(&indio_dev->mlock); + ret = ad5933_i2c_read(st->client, this_attr->address, 3, &dat.d8[1]); + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + + freqreg = be32_to_cpu(dat.d32) & 0xFFFFFF; + + freqreg = (u64) freqreg * (u64) (st->mclk_hz / 4); + do_div(freqreg, 1 << 27); + + return sprintf(buf, "%d\n", (int) freqreg); +} + +static ssize_t ad5933_store_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5933_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + long val; + int ret; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + return ret; + + if (val > AD5933_MAX_OUTPUT_FREQ_Hz) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + ret = ad5933_set_freq(st, this_attr->address, val); + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage0_freq_start, S_IRUGO | S_IWUSR, + ad5933_show_frequency, + ad5933_store_frequency, + AD5933_REG_FREQ_START); + +static IIO_DEVICE_ATTR(out_voltage0_freq_increment, S_IRUGO | S_IWUSR, + ad5933_show_frequency, + ad5933_store_frequency, + AD5933_REG_FREQ_INC); + +static ssize_t ad5933_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5933_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret = 0, len = 0; + + mutex_lock(&indio_dev->mlock); + switch ((u32) this_attr->address) { + case AD5933_OUT_RANGE: + len = sprintf(buf, "%d\n", + st->range_avail[(st->ctrl_hb >> 1) & 0x3]); + break; + case AD5933_OUT_RANGE_AVAIL: + len = sprintf(buf, "%d %d %d %d\n", st->range_avail[0], + st->range_avail[3], st->range_avail[2], + st->range_avail[1]); + break; + case AD5933_OUT_SETTLING_CYCLES: + len = sprintf(buf, "%d\n", st->settling_cycles); + break; + case AD5933_IN_PGA_GAIN: + len = sprintf(buf, "%s\n", + (st->ctrl_hb & AD5933_CTRL_PGA_GAIN_1) ? + "1" : "0.2"); + break; + case AD5933_IN_PGA_GAIN_AVAIL: + len = sprintf(buf, "1 0.2\n"); + break; + case AD5933_FREQ_POINTS: + len = sprintf(buf, "%d\n", st->freq_points); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + return ret ? ret : len; +} + +static ssize_t ad5933_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ad5933_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + long val; + int i, ret = 0; + unsigned short dat; + + if (this_attr->address != AD5933_IN_PGA_GAIN) { + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + } + + mutex_lock(&indio_dev->mlock); + switch ((u32) this_attr->address) { + case AD5933_OUT_RANGE: + for (i = 0; i < 4; i++) + if (val == st->range_avail[i]) { + st->ctrl_hb &= ~AD5933_CTRL_RANGE(0x3); + st->ctrl_hb |= AD5933_CTRL_RANGE(i); + ret = ad5933_cmd(st, 0); + break; + } + ret = -EINVAL; + break; + case AD5933_IN_PGA_GAIN: + if (sysfs_streq(buf, "1")) { + st->ctrl_hb |= AD5933_CTRL_PGA_GAIN_1; + } else if (sysfs_streq(buf, "0.2")) { + st->ctrl_hb &= ~AD5933_CTRL_PGA_GAIN_1; + } else { + ret = -EINVAL; + break; + } + ret = ad5933_cmd(st, 0); + break; + case AD5933_OUT_SETTLING_CYCLES: + val = clamp(val, 0L, 0x7FFL); + st->settling_cycles = val; + + /* 2x, 4x handling, see datasheet */ + if (val > 511) + val = (val >> 1) | (1 << 9); + else if (val > 1022) + val = (val >> 2) | (3 << 9); + + dat = cpu_to_be16(val); + ret = ad5933_i2c_write(st->client, + AD5933_REG_SETTLING_CYCLES, 2, (u8 *)&dat); + break; + case AD5933_FREQ_POINTS: + val = clamp(val, 0L, 511L); + st->freq_points = val; + + dat = cpu_to_be16(val); + ret = ad5933_i2c_write(st->client, AD5933_REG_INC_NUM, 2, + (u8 *)&dat); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&indio_dev->mlock); + return ret ? ret : len; +} + +static IIO_DEVICE_ATTR(out_voltage0_scale, S_IRUGO | S_IWUSR, + ad5933_show, + ad5933_store, + AD5933_OUT_RANGE); + +static IIO_DEVICE_ATTR(out_voltage0_scale_available, S_IRUGO, + ad5933_show, + NULL, + AD5933_OUT_RANGE_AVAIL); + +static IIO_DEVICE_ATTR(in_voltage0_scale, S_IRUGO | S_IWUSR, + ad5933_show, + ad5933_store, + AD5933_IN_PGA_GAIN); + +static IIO_DEVICE_ATTR(in_voltage0_scale_available, S_IRUGO, + ad5933_show, + NULL, + AD5933_IN_PGA_GAIN_AVAIL); + +static IIO_DEVICE_ATTR(out_voltage0_freq_points, S_IRUGO | S_IWUSR, + ad5933_show, + ad5933_store, + AD5933_FREQ_POINTS); + +static IIO_DEVICE_ATTR(out_voltage0_settling_cycles, S_IRUGO | S_IWUSR, + ad5933_show, + ad5933_store, + AD5933_OUT_SETTLING_CYCLES); + +/* note: + * ideally we would handle the scale attributes via the iio_info + * (read|write)_raw methods, however this part is a untypical since we + * don't create dedicated sysfs channel attributes for out0 and in0. + */ +static struct attribute *ad5933_attributes[] = { + &iio_dev_attr_out_voltage0_scale.dev_attr.attr, + &iio_dev_attr_out_voltage0_scale_available.dev_attr.attr, + &iio_dev_attr_out_voltage0_freq_start.dev_attr.attr, + &iio_dev_attr_out_voltage0_freq_increment.dev_attr.attr, + &iio_dev_attr_out_voltage0_freq_points.dev_attr.attr, + &iio_dev_attr_out_voltage0_settling_cycles.dev_attr.attr, + &iio_dev_attr_in_voltage0_scale.dev_attr.attr, + &iio_dev_attr_in_voltage0_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group ad5933_attribute_group = { + .attrs = ad5933_attributes, +}; + +static int ad5933_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad5933_state *st = iio_priv(indio_dev); + unsigned short dat; + int ret = -EINVAL; + + mutex_lock(&indio_dev->mlock); + switch (m) { + case 0: + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto out; + } + ret = ad5933_cmd(st, AD5933_CTRL_MEASURE_TEMP); + if (ret < 0) + goto out; + ret = ad5933_wait_busy(st, AD5933_STAT_TEMP_VALID); + if (ret < 0) + goto out; + + ret = ad5933_i2c_read(st->client, + AD5933_REG_TEMP_DATA, 2, + (u8 *)&dat); + if (ret < 0) + goto out; + mutex_unlock(&indio_dev->mlock); + ret = be16_to_cpu(dat); + /* Temp in Milli degrees Celsius */ + if (ret < 8192) + *val = ret * 1000 / 32; + else + *val = (ret - 16384) * 1000 / 32; + + return IIO_VAL_INT; + } + +out: + mutex_unlock(&indio_dev->mlock); + return ret; +} + +static const struct iio_info ad5933_info = { + .read_raw = &ad5933_read_raw, + .attrs = &ad5933_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ad5933_ring_preenable(struct iio_dev *indio_dev) +{ + struct ad5933_state *st = iio_priv(indio_dev); + size_t d_size; + int ret; + + if (bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + return -EINVAL; + + d_size = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength) * + ad5933_channels[1].scan_type.storagebits / 8; + + if (indio_dev->buffer->access->set_bytes_per_datum) + indio_dev->buffer->access-> + set_bytes_per_datum(indio_dev->buffer, d_size); + + ret = ad5933_reset(st); + if (ret < 0) + return ret; + + ret = ad5933_cmd(st, AD5933_CTRL_STANDBY); + if (ret < 0) + return ret; + + ret = ad5933_cmd(st, AD5933_CTRL_INIT_START_FREQ); + if (ret < 0) + return ret; + + st->state = AD5933_CTRL_INIT_START_FREQ; + + return 0; +} + +static int ad5933_ring_postenable(struct iio_dev *indio_dev) +{ + struct ad5933_state *st = iio_priv(indio_dev); + + /* AD5933_CTRL_INIT_START_FREQ: + * High Q complex circuits require a long time to reach steady state. + * To facilitate the measurement of such impedances, this mode allows + * the user full control of the settling time requirement before + * entering start frequency sweep mode where the impedance measurement + * takes place. In this mode the impedance is excited with the + * programmed start frequency (ad5933_ring_preenable), + * but no measurement takes place. + */ + + schedule_delayed_work(&st->work, + msecs_to_jiffies(AD5933_INIT_EXCITATION_TIME_ms)); + return 0; +} + +static int ad5933_ring_postdisable(struct iio_dev *indio_dev) +{ + struct ad5933_state *st = iio_priv(indio_dev); + + cancel_delayed_work_sync(&st->work); + return ad5933_cmd(st, AD5933_CTRL_POWER_DOWN); +} + +static const struct iio_buffer_setup_ops ad5933_ring_setup_ops = { + .preenable = &ad5933_ring_preenable, + .postenable = &ad5933_ring_postenable, + .postdisable = &ad5933_ring_postdisable, +}; + +static int ad5933_register_ring_funcs_and_init(struct iio_dev *indio_dev) +{ + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) + return -ENOMEM; + + /* Ring buffer functions - here trigger setup related */ + indio_dev->setup_ops = &ad5933_ring_setup_ops; + + indio_dev->modes |= INDIO_BUFFER_HARDWARE; + + return 0; +} + +static void ad5933_work(struct work_struct *work) +{ + struct ad5933_state *st = container_of(work, + struct ad5933_state, work.work); + struct iio_dev *indio_dev = i2c_get_clientdata(st->client); + struct iio_buffer *ring = indio_dev->buffer; + signed short buf[2]; + unsigned char status; + + mutex_lock(&indio_dev->mlock); + if (st->state == AD5933_CTRL_INIT_START_FREQ) { + /* start sweep */ + ad5933_cmd(st, AD5933_CTRL_START_SWEEP); + st->state = AD5933_CTRL_START_SWEEP; + schedule_delayed_work(&st->work, st->poll_time_jiffies); + mutex_unlock(&indio_dev->mlock); + return; + } + + ad5933_i2c_read(st->client, AD5933_REG_STATUS, 1, &status); + + if (status & AD5933_STAT_DATA_VALID) { + int scan_count = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + ad5933_i2c_read(st->client, + test_bit(1, indio_dev->active_scan_mask) ? + AD5933_REG_REAL_DATA : AD5933_REG_IMAG_DATA, + scan_count * 2, (u8 *)buf); + + if (scan_count == 2) { + buf[0] = be16_to_cpu(buf[0]); + buf[1] = be16_to_cpu(buf[1]); + } else { + buf[0] = be16_to_cpu(buf[0]); + } + /* save datum to the ring */ + ring->access->store_to(ring, (u8 *)buf, iio_get_time_ns()); + } else { + /* no data available - try again later */ + schedule_delayed_work(&st->work, st->poll_time_jiffies); + mutex_unlock(&indio_dev->mlock); + return; + } + + if (status & AD5933_STAT_SWEEP_DONE) { + /* last sample received - power down do nothing until + * the ring enable is toggled */ + ad5933_cmd(st, AD5933_CTRL_POWER_DOWN); + } else { + /* we just received a valid datum, move on to the next */ + ad5933_cmd(st, AD5933_CTRL_INC_FREQ); + schedule_delayed_work(&st->work, st->poll_time_jiffies); + } + + mutex_unlock(&indio_dev->mlock); +} + +static int __devinit ad5933_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret, voltage_uv = 0; + struct ad5933_platform_data *pdata = client->dev.platform_data; + struct ad5933_state *st; + struct iio_dev *indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + st->client = client; + + if (!pdata) + st->pdata = &ad5933_default_pdata; + else + st->pdata = pdata; + + st->reg = regulator_get(&client->dev, "vcc"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + goto error_put_reg; + voltage_uv = regulator_get_voltage(st->reg); + } + + if (voltage_uv) + st->vref_mv = voltage_uv / 1000; + else + st->vref_mv = st->pdata->vref_mv; + + if (st->pdata->ext_clk_Hz) { + st->mclk_hz = st->pdata->ext_clk_Hz; + st->ctrl_lb = AD5933_CTRL_EXT_SYSCLK; + } else { + st->mclk_hz = AD5933_INT_OSC_FREQ_Hz; + st->ctrl_lb = AD5933_CTRL_INT_SYSCLK; + } + + ad5933_calc_out_ranges(st); + INIT_DELAYED_WORK(&st->work, ad5933_work); + st->poll_time_jiffies = msecs_to_jiffies(AD5933_POLL_TIME_ms); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ad5933_info; + indio_dev->name = id->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad5933_channels; + indio_dev->num_channels = 1; /* only register temp0_input */ + + ret = ad5933_register_ring_funcs_and_init(indio_dev); + if (ret) + goto error_disable_reg; + + /* skip temp0_input, register in0_(real|imag)_raw */ + ret = iio_buffer_register(indio_dev, &ad5933_channels[1], 2); + if (ret) + goto error_unreg_ring; + + /* enable both REAL and IMAG channels by default */ + iio_scan_mask_set(indio_dev, indio_dev->buffer, 0); + iio_scan_mask_set(indio_dev, indio_dev->buffer, 1); + + ret = ad5933_setup(st); + if (ret) + goto error_uninitialize_ring; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_uninitialize_ring; + + return 0; + +error_uninitialize_ring: + iio_buffer_unregister(indio_dev); +error_unreg_ring: + iio_sw_rb_free(indio_dev->buffer); +error_disable_reg: + if (!IS_ERR(st->reg)) + regulator_disable(st->reg); +error_put_reg: + if (!IS_ERR(st->reg)) + regulator_put(st->reg); + + iio_free_device(indio_dev); + + return ret; +} + +static __devexit int ad5933_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ad5933_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_buffer_unregister(indio_dev); + iio_sw_rb_free(indio_dev->buffer); + if (!IS_ERR(st->reg)) { + regulator_disable(st->reg); + regulator_put(st->reg); + } + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id ad5933_id[] = { + { "ad5933", 0 }, + { "ad5934", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ad5933_id); + +static struct i2c_driver ad5933_driver = { + .driver = { + .name = "ad5933", + }, + .probe = ad5933_probe, + .remove = __devexit_p(ad5933_remove), + .id_table = ad5933_id, +}; +module_i2c_driver(ad5933_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Analog Devices AD5933 Impedance Conv. Network Analyzer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/impedance-analyzer/ad5933.h b/drivers/staging/iio/impedance-analyzer/ad5933.h new file mode 100644 index 00000000..b140e42d --- /dev/null +++ b/drivers/staging/iio/impedance-analyzer/ad5933.h @@ -0,0 +1,28 @@ +/* + * AD5933 AD5934 Impedance Converter, Network Analyzer + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef IIO_ADC_AD5933_H_ +#define IIO_ADC_AD5933_H_ + +/* + * TODO: struct ad5933_platform_data needs to go into include/linux/iio + */ + +/** + * struct ad5933_platform_data - platform specific data + * @ext_clk_Hz: the external clock frequency in Hz, if not set + * the driver uses the internal clock (16.776 MHz) + * @vref_mv: the external reference voltage in millivolt + */ + +struct ad5933_platform_data { + unsigned long ext_clk_Hz; + unsigned short vref_mv; +}; + +#endif /* IIO_ADC_AD5933_H_ */ diff --git a/drivers/staging/iio/imu/Kconfig b/drivers/staging/iio/imu/Kconfig new file mode 100644 index 00000000..2c2f47de --- /dev/null +++ b/drivers/staging/iio/imu/Kconfig @@ -0,0 +1,17 @@ +# +# IIO imu drivers configuration +# +menu "Inertial measurement units" + +config ADIS16400 + tristate "Analog Devices ADIS16400 and similar IMU SPI driver" + depends on SPI + select IIO_SW_RING if IIO_BUFFER + select IIO_TRIGGER if IIO_BUFFER + help + Say yes here to build support for Analog Devices adis16300, adis16344, + adis16350, adis16354, adis16355, adis16360, adis16362, adis16364, + adis16365, adis16400 and adis16405 triaxial inertial sensors + (adis16400 series also have magnetometers). + +endmenu diff --git a/drivers/staging/iio/imu/Makefile b/drivers/staging/iio/imu/Makefile new file mode 100644 index 00000000..3400a13d --- /dev/null +++ b/drivers/staging/iio/imu/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Inertial Measurement Units +# + +adis16400-y := adis16400_core.o +adis16400-$(CONFIG_IIO_BUFFER) += adis16400_ring.o adis16400_trigger.o +obj-$(CONFIG_ADIS16400) += adis16400.o diff --git a/drivers/staging/iio/imu/adis16400.h b/drivers/staging/iio/imu/adis16400.h new file mode 100644 index 00000000..83d133ef --- /dev/null +++ b/drivers/staging/iio/imu/adis16400.h @@ -0,0 +1,230 @@ +/* + * adis16400.h support Analog Devices ADIS16400 + * 3d 18g accelerometers, + * 3d gyroscopes, + * 3d 2.5gauss magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl + * Copyright (c) 2007 Jonathan Cameron + * + * Loosely based upon lis3l02dq.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef SPI_ADIS16400_H_ +#define SPI_ADIS16400_H_ + +#define ADIS16400_STARTUP_DELAY 290 /* ms */ +#define ADIS16400_MTEST_DELAY 90 /* ms */ + +#define ADIS16400_READ_REG(a) a +#define ADIS16400_WRITE_REG(a) ((a) | 0x80) + +#define ADIS16400_FLASH_CNT 0x00 /* Flash memory write count */ +#define ADIS16400_SUPPLY_OUT 0x02 /* Power supply measurement */ +#define ADIS16400_XGYRO_OUT 0x04 /* X-axis gyroscope output */ +#define ADIS16400_YGYRO_OUT 0x06 /* Y-axis gyroscope output */ +#define ADIS16400_ZGYRO_OUT 0x08 /* Z-axis gyroscope output */ +#define ADIS16400_XACCL_OUT 0x0A /* X-axis accelerometer output */ +#define ADIS16400_YACCL_OUT 0x0C /* Y-axis accelerometer output */ +#define ADIS16400_ZACCL_OUT 0x0E /* Z-axis accelerometer output */ +#define ADIS16400_XMAGN_OUT 0x10 /* X-axis magnetometer measurement */ +#define ADIS16400_YMAGN_OUT 0x12 /* Y-axis magnetometer measurement */ +#define ADIS16400_ZMAGN_OUT 0x14 /* Z-axis magnetometer measurement */ +#define ADIS16400_TEMP_OUT 0x16 /* Temperature output */ +#define ADIS16400_AUX_ADC 0x18 /* Auxiliary ADC measurement */ + +#define ADIS16350_XTEMP_OUT 0x10 /* X-axis gyroscope temperature measurement */ +#define ADIS16350_YTEMP_OUT 0x12 /* Y-axis gyroscope temperature measurement */ +#define ADIS16350_ZTEMP_OUT 0x14 /* Z-axis gyroscope temperature measurement */ + +#define ADIS16300_PITCH_OUT 0x12 /* X axis inclinometer output measurement */ +#define ADIS16300_ROLL_OUT 0x12 /* Y axis inclinometer output measurement */ + +/* Calibration parameters */ +#define ADIS16400_XGYRO_OFF 0x1A /* X-axis gyroscope bias offset factor */ +#define ADIS16400_YGYRO_OFF 0x1C /* Y-axis gyroscope bias offset factor */ +#define ADIS16400_ZGYRO_OFF 0x1E /* Z-axis gyroscope bias offset factor */ +#define ADIS16400_XACCL_OFF 0x20 /* X-axis acceleration bias offset factor */ +#define ADIS16400_YACCL_OFF 0x22 /* Y-axis acceleration bias offset factor */ +#define ADIS16400_ZACCL_OFF 0x24 /* Z-axis acceleration bias offset factor */ +#define ADIS16400_XMAGN_HIF 0x26 /* X-axis magnetometer, hard-iron factor */ +#define ADIS16400_YMAGN_HIF 0x28 /* Y-axis magnetometer, hard-iron factor */ +#define ADIS16400_ZMAGN_HIF 0x2A /* Z-axis magnetometer, hard-iron factor */ +#define ADIS16400_XMAGN_SIF 0x2C /* X-axis magnetometer, soft-iron factor */ +#define ADIS16400_YMAGN_SIF 0x2E /* Y-axis magnetometer, soft-iron factor */ +#define ADIS16400_ZMAGN_SIF 0x30 /* Z-axis magnetometer, soft-iron factor */ + +#define ADIS16400_GPIO_CTRL 0x32 /* Auxiliary digital input/output control */ +#define ADIS16400_MSC_CTRL 0x34 /* Miscellaneous control */ +#define ADIS16400_SMPL_PRD 0x36 /* Internal sample period (rate) control */ +#define ADIS16400_SENS_AVG 0x38 /* Dynamic range and digital filter control */ +#define ADIS16400_SLP_CNT 0x3A /* Sleep mode control */ +#define ADIS16400_DIAG_STAT 0x3C /* System status */ + +/* Alarm functions */ +#define ADIS16400_GLOB_CMD 0x3E /* System command */ +#define ADIS16400_ALM_MAG1 0x40 /* Alarm 1 amplitude threshold */ +#define ADIS16400_ALM_MAG2 0x42 /* Alarm 2 amplitude threshold */ +#define ADIS16400_ALM_SMPL1 0x44 /* Alarm 1 sample size */ +#define ADIS16400_ALM_SMPL2 0x46 /* Alarm 2 sample size */ +#define ADIS16400_ALM_CTRL 0x48 /* Alarm control */ +#define ADIS16400_AUX_DAC 0x4A /* Auxiliary DAC data */ + +#define ADIS16400_PRODUCT_ID 0x56 /* Product identifier */ + +#define ADIS16400_ERROR_ACTIVE (1<<14) +#define ADIS16400_NEW_DATA (1<<14) + +/* MSC_CTRL */ +#define ADIS16400_MSC_CTRL_MEM_TEST (1<<11) +#define ADIS16400_MSC_CTRL_INT_SELF_TEST (1<<10) +#define ADIS16400_MSC_CTRL_NEG_SELF_TEST (1<<9) +#define ADIS16400_MSC_CTRL_POS_SELF_TEST (1<<8) +#define ADIS16400_MSC_CTRL_GYRO_BIAS (1<<7) +#define ADIS16400_MSC_CTRL_ACCL_ALIGN (1<<6) +#define ADIS16400_MSC_CTRL_DATA_RDY_EN (1<<2) +#define ADIS16400_MSC_CTRL_DATA_RDY_POL_HIGH (1<<1) +#define ADIS16400_MSC_CTRL_DATA_RDY_DIO2 (1<<0) + +/* SMPL_PRD */ +#define ADIS16400_SMPL_PRD_TIME_BASE (1<<7) +#define ADIS16400_SMPL_PRD_DIV_MASK 0x7F + +/* DIAG_STAT */ +#define ADIS16400_DIAG_STAT_ZACCL_FAIL (1<<15) +#define ADIS16400_DIAG_STAT_YACCL_FAIL (1<<14) +#define ADIS16400_DIAG_STAT_XACCL_FAIL (1<<13) +#define ADIS16400_DIAG_STAT_XGYRO_FAIL (1<<12) +#define ADIS16400_DIAG_STAT_YGYRO_FAIL (1<<11) +#define ADIS16400_DIAG_STAT_ZGYRO_FAIL (1<<10) +#define ADIS16400_DIAG_STAT_ALARM2 (1<<9) +#define ADIS16400_DIAG_STAT_ALARM1 (1<<8) +#define ADIS16400_DIAG_STAT_FLASH_CHK (1<<6) +#define ADIS16400_DIAG_STAT_SELF_TEST (1<<5) +#define ADIS16400_DIAG_STAT_OVERFLOW (1<<4) +#define ADIS16400_DIAG_STAT_SPI_FAIL (1<<3) +#define ADIS16400_DIAG_STAT_FLASH_UPT (1<<2) +#define ADIS16400_DIAG_STAT_POWER_HIGH (1<<1) +#define ADIS16400_DIAG_STAT_POWER_LOW (1<<0) + +/* GLOB_CMD */ +#define ADIS16400_GLOB_CMD_SW_RESET (1<<7) +#define ADIS16400_GLOB_CMD_P_AUTO_NULL (1<<4) +#define ADIS16400_GLOB_CMD_FLASH_UPD (1<<3) +#define ADIS16400_GLOB_CMD_DAC_LATCH (1<<2) +#define ADIS16400_GLOB_CMD_FAC_CALIB (1<<1) +#define ADIS16400_GLOB_CMD_AUTO_NULL (1<<0) + +/* SLP_CNT */ +#define ADIS16400_SLP_CNT_POWER_OFF (1<<8) + +#define ADIS16400_MAX_TX 24 +#define ADIS16400_MAX_RX 24 + +#define ADIS16400_SPI_SLOW (u32)(300 * 1000) +#define ADIS16400_SPI_BURST (u32)(1000 * 1000) +#define ADIS16400_SPI_FAST (u32)(2000 * 1000) + +#define ADIS16400_HAS_PROD_ID 1 +#define ADIS16400_NO_BURST 2 +struct adis16400_chip_info { + const struct iio_chan_spec *channels; + const int num_channels; + const int product_id; + const long flags; + unsigned int gyro_scale_micro; + unsigned int accel_scale_micro; + unsigned long default_scan_mask; +}; + +/** + * struct adis16400_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + * @filt_int: integer part of requested filter frequency + **/ +struct adis16400_state { + struct spi_device *us; + struct iio_trigger *trig; + struct mutex buf_lock; + struct adis16400_chip_info *variant; + int filt_int; + + u8 tx[ADIS16400_MAX_TX] ____cacheline_aligned; + u8 rx[ADIS16400_MAX_RX] ____cacheline_aligned; +}; + +int adis16400_set_irq(struct iio_dev *indio_dev, bool enable); + +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +#define ADIS16400_SCAN_SUPPLY 0 +#define ADIS16400_SCAN_GYRO_X 1 +#define ADIS16400_SCAN_GYRO_Y 2 +#define ADIS16400_SCAN_GYRO_Z 3 +#define ADIS16400_SCAN_ACC_X 4 +#define ADIS16400_SCAN_ACC_Y 5 +#define ADIS16400_SCAN_ACC_Z 6 +#define ADIS16400_SCAN_MAGN_X 7 +#define ADIS16350_SCAN_TEMP_X 7 +#define ADIS16400_SCAN_MAGN_Y 8 +#define ADIS16350_SCAN_TEMP_Y 8 +#define ADIS16400_SCAN_MAGN_Z 9 +#define ADIS16350_SCAN_TEMP_Z 9 +#define ADIS16400_SCAN_TEMP 10 +#define ADIS16350_SCAN_ADC_0 10 +#define ADIS16400_SCAN_ADC_0 11 +#define ADIS16300_SCAN_INCLI_X 12 +#define ADIS16300_SCAN_INCLI_Y 13 + +#ifdef CONFIG_IIO_BUFFER +void adis16400_remove_trigger(struct iio_dev *indio_dev); +int adis16400_probe_trigger(struct iio_dev *indio_dev); + +ssize_t adis16400_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + + +int adis16400_configure_ring(struct iio_dev *indio_dev); +void adis16400_unconfigure_ring(struct iio_dev *indio_dev); + +#else /* CONFIG_IIO_BUFFER */ + +static inline void adis16400_remove_trigger(struct iio_dev *indio_dev) +{ +} + +static inline int adis16400_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline ssize_t +adis16400_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static int adis16400_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +} + +static inline void adis16400_unconfigure_ring(struct iio_dev *indio_dev) +{ +} + +#endif /* CONFIG_IIO_BUFFER */ +#endif /* SPI_ADIS16400_H_ */ diff --git a/drivers/staging/iio/imu/adis16400_core.c b/drivers/staging/iio/imu/adis16400_core.c new file mode 100644 index 00000000..e73ad781 --- /dev/null +++ b/drivers/staging/iio/imu/adis16400_core.c @@ -0,0 +1,1238 @@ +/* + * adis16400.c support Analog Devices ADIS16400/5 + * 3d 2g Linear Accelerometers, + * 3d Gyroscopes, + * 3d Magnetometers via SPI + * + * Copyright (c) 2009 Manuel Stahl + * Copyright (c) 2007 Jonathan Cameron + * Copyright (c) 2011 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" +#include "adis16400.h" + +enum adis16400_chip_variant { + ADIS16300, + ADIS16334, + ADIS16350, + ADIS16360, + ADIS16362, + ADIS16364, + ADIS16365, + ADIS16400, +}; + +/** + * adis16400_spi_write_reg_8() - write single byte to a register + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the register to be written + * @val: the value to write + */ +static int adis16400_spi_write_reg_8(struct iio_dev *indio_dev, + u8 reg_address, + u8 val) +{ + int ret; + struct adis16400_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16400_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16400_spi_write_reg_16() - write 2 bytes to a pair of registers + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: value to be written + * + * At the moment the spi framework doesn't allow global setting of cs_change. + * This means that use cannot be made of spi_write. + */ +static int adis16400_spi_write_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct adis16400_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .tx_buf = st->tx + 2, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16400_WRITE_REG(lower_reg_address); + st->tx[1] = value & 0xFF; + st->tx[2] = ADIS16400_WRITE_REG(lower_reg_address + 1); + st->tx[3] = (value >> 8) & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +/** + * adis16400_spi_read_reg_16() - read 2 bytes from a 16-bit register + * @indio_dev: iio device + * @reg_address: the address of the lower of the two registers. Second register + * is assumed to have address one greater. + * @val: somewhere to pass back the value read + * + * At the moment the spi framework doesn't allow global setting of cs_change. + * This means that use cannot be made of spi_read. + **/ +static int adis16400_spi_read_reg_16(struct iio_dev *indio_dev, + u8 lower_reg_address, + u16 *val) +{ + struct spi_message msg; + struct adis16400_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + .cs_change = 1, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16400_READ_REG(lower_reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, + "problem when reading 16 bit register 0x%02X", + lower_reg_address); + goto error_ret; + } + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int adis16400_get_freq(struct iio_dev *indio_dev) +{ + u16 t; + int sps, ret; + + ret = adis16400_spi_read_reg_16(indio_dev, ADIS16400_SMPL_PRD, &t); + if (ret < 0) + return ret; + sps = (t & ADIS16400_SMPL_PRD_TIME_BASE) ? 53 : 1638; + sps /= (t & ADIS16400_SMPL_PRD_DIV_MASK) + 1; + + return sps; +} + +static ssize_t adis16400_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int ret, len = 0; + ret = adis16400_get_freq(indio_dev); + if (ret < 0) + return ret; + len = sprintf(buf, "%d SPS\n", ret); + return len; +} + +static const unsigned adis16400_3db_divisors[] = { + [0] = 2, /* Special case */ + [1] = 5, + [2] = 10, + [3] = 50, + [4] = 200, +}; + +static int adis16400_set_filter(struct iio_dev *indio_dev, int sps, int val) +{ + int i, ret; + u16 val16; + for (i = ARRAY_SIZE(adis16400_3db_divisors) - 1; i >= 0; i--) + if (sps/adis16400_3db_divisors[i] > val) + break; + if (i == -1) + ret = -EINVAL; + else { + ret = adis16400_spi_read_reg_16(indio_dev, + ADIS16400_SENS_AVG, + &val16); + if (ret < 0) + goto error_ret; + + ret = adis16400_spi_write_reg_16(indio_dev, + ADIS16400_SENS_AVG, + (val16 & ~0x03) | i); + } +error_ret: + return ret; +} + +static ssize_t adis16400_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16400_state *st = iio_priv(indio_dev); + long val; + int ret; + u8 t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + t = (1638 / val); + if (t > 0) + t--; + t &= ADIS16400_SMPL_PRD_DIV_MASK; + if ((t & ADIS16400_SMPL_PRD_DIV_MASK) >= 0x0A) + st->us->max_speed_hz = ADIS16400_SPI_SLOW; + else + st->us->max_speed_hz = ADIS16400_SPI_FAST; + + ret = adis16400_spi_write_reg_8(indio_dev, + ADIS16400_SMPL_PRD, + t); + + /* Also update the filter */ + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int adis16400_reset(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16400_spi_write_reg_8(indio_dev, + ADIS16400_GLOB_CMD, + ADIS16400_GLOB_CMD_SW_RESET); + if (ret) + dev_err(&indio_dev->dev, "problem resetting device"); + + return ret; +} + +static ssize_t adis16400_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + bool val; + int ret; + + ret = strtobool(buf, &val); + if (ret < 0) + return ret; + if (val) { + ret = adis16400_reset(dev_get_drvdata(dev)); + if (ret < 0) + return ret; + } + + return len; +} + +int adis16400_set_irq(struct iio_dev *indio_dev, bool enable) +{ + int ret; + u16 msc; + + ret = adis16400_spi_read_reg_16(indio_dev, ADIS16400_MSC_CTRL, &msc); + if (ret) + goto error_ret; + + msc |= ADIS16400_MSC_CTRL_DATA_RDY_POL_HIGH; + if (enable) + msc |= ADIS16400_MSC_CTRL_DATA_RDY_EN; + else + msc &= ~ADIS16400_MSC_CTRL_DATA_RDY_EN; + + ret = adis16400_spi_write_reg_16(indio_dev, ADIS16400_MSC_CTRL, msc); + if (ret) + goto error_ret; + +error_ret: + return ret; +} + +/* Power down the device */ +static int adis16400_stop_device(struct iio_dev *indio_dev) +{ + int ret; + u16 val = ADIS16400_SLP_CNT_POWER_OFF; + + ret = adis16400_spi_write_reg_16(indio_dev, ADIS16400_SLP_CNT, val); + if (ret) + dev_err(&indio_dev->dev, + "problem with turning device off: SLP_CNT"); + + return ret; +} + +static int adis16400_check_status(struct iio_dev *indio_dev) +{ + u16 status; + int ret; + struct device *dev = &indio_dev->dev; + + ret = adis16400_spi_read_reg_16(indio_dev, + ADIS16400_DIAG_STAT, &status); + + if (ret < 0) { + dev_err(dev, "Reading status failed\n"); + goto error_ret; + } + ret = status; + if (status & ADIS16400_DIAG_STAT_ZACCL_FAIL) + dev_err(dev, "Z-axis accelerometer self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_YACCL_FAIL) + dev_err(dev, "Y-axis accelerometer self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_XACCL_FAIL) + dev_err(dev, "X-axis accelerometer self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_XGYRO_FAIL) + dev_err(dev, "X-axis gyroscope self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_YGYRO_FAIL) + dev_err(dev, "Y-axis gyroscope self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_ZGYRO_FAIL) + dev_err(dev, "Z-axis gyroscope self-test failure\n"); + if (status & ADIS16400_DIAG_STAT_ALARM2) + dev_err(dev, "Alarm 2 active\n"); + if (status & ADIS16400_DIAG_STAT_ALARM1) + dev_err(dev, "Alarm 1 active\n"); + if (status & ADIS16400_DIAG_STAT_FLASH_CHK) + dev_err(dev, "Flash checksum error\n"); + if (status & ADIS16400_DIAG_STAT_SELF_TEST) + dev_err(dev, "Self test error\n"); + if (status & ADIS16400_DIAG_STAT_OVERFLOW) + dev_err(dev, "Sensor overrange\n"); + if (status & ADIS16400_DIAG_STAT_SPI_FAIL) + dev_err(dev, "SPI failure\n"); + if (status & ADIS16400_DIAG_STAT_FLASH_UPT) + dev_err(dev, "Flash update failed\n"); + if (status & ADIS16400_DIAG_STAT_POWER_HIGH) + dev_err(dev, "Power supply above 5.25V\n"); + if (status & ADIS16400_DIAG_STAT_POWER_LOW) + dev_err(dev, "Power supply below 4.75V\n"); + +error_ret: + return ret; +} + +static int adis16400_self_test(struct iio_dev *indio_dev) +{ + int ret; + ret = adis16400_spi_write_reg_16(indio_dev, + ADIS16400_MSC_CTRL, + ADIS16400_MSC_CTRL_MEM_TEST); + if (ret) { + dev_err(&indio_dev->dev, "problem starting self test"); + goto err_ret; + } + + msleep(ADIS16400_MTEST_DELAY); + adis16400_check_status(indio_dev); + +err_ret: + return ret; +} + +static int adis16400_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + u16 prod_id, smp_prd; + struct adis16400_state *st = iio_priv(indio_dev); + + /* use low spi speed for init */ + st->us->max_speed_hz = ADIS16400_SPI_SLOW; + st->us->mode = SPI_MODE_3; + spi_setup(st->us); + + ret = adis16400_set_irq(indio_dev, false); + if (ret) { + dev_err(&indio_dev->dev, "disable irq failed"); + goto err_ret; + } + + ret = adis16400_self_test(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "self test failure"); + goto err_ret; + } + + ret = adis16400_check_status(indio_dev); + if (ret) { + adis16400_reset(indio_dev); + dev_err(&indio_dev->dev, "device not playing ball -> reset"); + msleep(ADIS16400_STARTUP_DELAY); + ret = adis16400_check_status(indio_dev); + if (ret) { + dev_err(&indio_dev->dev, "giving up"); + goto err_ret; + } + } + if (st->variant->flags & ADIS16400_HAS_PROD_ID) { + ret = adis16400_spi_read_reg_16(indio_dev, + ADIS16400_PRODUCT_ID, &prod_id); + if (ret) + goto err_ret; + + if ((prod_id & 0xF000) != st->variant->product_id) + dev_warn(&indio_dev->dev, "incorrect id"); + + dev_info(&indio_dev->dev, "%s: prod_id 0x%04x at CS%d (irq %d)\n", + indio_dev->name, prod_id, + st->us->chip_select, st->us->irq); + } + /* use high spi speed if possible */ + ret = adis16400_spi_read_reg_16(indio_dev, + ADIS16400_SMPL_PRD, &smp_prd); + if (!ret && (smp_prd & ADIS16400_SMPL_PRD_DIV_MASK) < 0x0A) { + st->us->max_speed_hz = ADIS16400_SPI_SLOW; + spi_setup(st->us); + } + +err_ret: + return ret; +} + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + adis16400_read_frequency, + adis16400_write_frequency); + +static IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, adis16400_write_reset, 0); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("409 546 819 1638"); + +enum adis16400_chan { + in_supply, + gyro_x, + gyro_y, + gyro_z, + accel_x, + accel_y, + accel_z, + magn_x, + magn_y, + magn_z, + temp, + temp0, temp1, temp2, + in1, + incli_x, + incli_y, +}; + +static u8 adis16400_addresses[17][2] = { + [in_supply] = { ADIS16400_SUPPLY_OUT }, + [gyro_x] = { ADIS16400_XGYRO_OUT, ADIS16400_XGYRO_OFF }, + [gyro_y] = { ADIS16400_YGYRO_OUT, ADIS16400_YGYRO_OFF }, + [gyro_z] = { ADIS16400_ZGYRO_OUT, ADIS16400_ZGYRO_OFF }, + [accel_x] = { ADIS16400_XACCL_OUT, ADIS16400_XACCL_OFF }, + [accel_y] = { ADIS16400_YACCL_OUT, ADIS16400_YACCL_OFF }, + [accel_z] = { ADIS16400_ZACCL_OUT, ADIS16400_ZACCL_OFF }, + [magn_x] = { ADIS16400_XMAGN_OUT }, + [magn_y] = { ADIS16400_YMAGN_OUT }, + [magn_z] = { ADIS16400_ZMAGN_OUT }, + [temp] = { ADIS16400_TEMP_OUT }, + [temp0] = { ADIS16350_XTEMP_OUT }, + [temp1] = { ADIS16350_YTEMP_OUT }, + [temp2] = { ADIS16350_ZTEMP_OUT }, + [in1] = { ADIS16400_AUX_ADC }, + [incli_x] = { ADIS16300_PITCH_OUT }, + [incli_y] = { ADIS16300_ROLL_OUT } +}; + + +static int adis16400_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret, sps; + + switch (mask) { + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis16400_spi_write_reg_16(indio_dev, + adis16400_addresses[chan->address][1], + val); + mutex_unlock(&indio_dev->mlock); + return ret; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + /* Need to cache values so we can update if the frequency + changes */ + mutex_lock(&indio_dev->mlock); + st->filt_int = val; + /* Work out update to current value */ + sps = adis16400_get_freq(indio_dev); + if (sps < 0) { + mutex_unlock(&indio_dev->mlock); + return sps; + } + + ret = adis16400_set_filter(indio_dev, sps, val); + mutex_unlock(&indio_dev->mlock); + return ret; + default: + return -EINVAL; + } +} + +static int adis16400_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct adis16400_state *st = iio_priv(indio_dev); + int ret, shift; + s16 val16; + + switch (mask) { + case 0: + mutex_lock(&indio_dev->mlock); + ret = adis16400_spi_read_reg_16(indio_dev, + adis16400_addresses[chan->address][0], + &val16); + if (ret) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + val16 &= (1 << chan->scan_type.realbits) - 1; + if (chan->scan_type.sign == 's') { + shift = 16 - chan->scan_type.realbits; + val16 = (s16)(val16 << shift) >> shift; + } + *val = val16; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ANGL_VEL: + *val = 0; + *val2 = st->variant->gyro_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_VOLTAGE: + *val = 0; + if (chan->channel == 0) + *val2 = 2418; + else + *val2 = 806; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_ACCEL: + *val = 0; + *val2 = st->variant->accel_scale_micro; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_MAGN: + *val = 0; + *val2 = 500; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 0; + *val2 = 140000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&indio_dev->mlock); + ret = adis16400_spi_read_reg_16(indio_dev, + adis16400_addresses[chan->address][1], + &val16); + mutex_unlock(&indio_dev->mlock); + if (ret) + return ret; + val16 = ((val16 & 0xFFF) << 4) >> 4; + *val = val16; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + /* currently only temperature */ + *val = 198; + *val2 = 160000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + mutex_lock(&indio_dev->mlock); + /* Need both the number of taps and the sampling frequency */ + ret = adis16400_spi_read_reg_16(indio_dev, + ADIS16400_SENS_AVG, + &val16); + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + ret = adis16400_get_freq(indio_dev); + if (ret > 0) + *val = ret/adis16400_3db_divisors[val16 & 0x03]; + *val2 = 0; + mutex_unlock(&indio_dev->mlock); + if (ret < 0) + return ret; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static struct iio_chan_spec adis16400_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "supply", + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in_supply, + .scan_index = ADIS16400_SCAN_SUPPLY, + .scan_type = IIO_ST('u', 14, 16, 0) + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_x, + .scan_index = ADIS16400_SCAN_GYRO_X, + .scan_type = IIO_ST('s', 14, 16, 0) + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_y, + .scan_index = ADIS16400_SCAN_GYRO_Y, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_z, + .scan_index = ADIS16400_SCAN_GYRO_Z, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_x, + .scan_index = ADIS16400_SCAN_ACC_X, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_y, + .scan_index = ADIS16400_SCAN_ACC_Y, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_z, + .scan_index = ADIS16400_SCAN_ACC_Z, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = magn_x, + .scan_index = ADIS16400_SCAN_MAGN_X, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = magn_y, + .scan_index = ADIS16400_SCAN_MAGN_Y, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_MAGN, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = magn_z, + .scan_index = ADIS16400_SCAN_MAGN_Z, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = temp, + .scan_index = ADIS16400_SCAN_TEMP, + .scan_type = IIO_ST('s', 12, 16, 0), + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in1, + .scan_index = ADIS16400_SCAN_ADC_0, + .scan_type = IIO_ST('s', 12, 16, 0), + }, + IIO_CHAN_SOFT_TIMESTAMP(12) +}; + +static struct iio_chan_spec adis16350_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "supply", + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in_supply, + .scan_index = ADIS16400_SCAN_SUPPLY, + .scan_type = IIO_ST('u', 12, 16, 0) + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_x, + .scan_index = ADIS16400_SCAN_GYRO_X, + .scan_type = IIO_ST('s', 14, 16, 0) + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_y, + .scan_index = ADIS16400_SCAN_GYRO_Y, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_z, + .scan_index = ADIS16400_SCAN_GYRO_Z, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_x, + .scan_index = ADIS16400_SCAN_ACC_X, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_y, + .scan_index = ADIS16400_SCAN_ACC_Y, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_z, + .scan_index = ADIS16400_SCAN_ACC_Z, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .extend_name = "x", + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = temp0, + .scan_index = ADIS16350_SCAN_TEMP_X, + .scan_type = IIO_ST('s', 12, 16, 0), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 1, + .extend_name = "y", + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = temp1, + .scan_index = ADIS16350_SCAN_TEMP_Y, + .scan_type = IIO_ST('s', 12, 16, 0), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 2, + .extend_name = "z", + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = temp2, + .scan_index = ADIS16350_SCAN_TEMP_Z, + .scan_type = IIO_ST('s', 12, 16, 0), + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in1, + .scan_index = ADIS16350_SCAN_ADC_0, + .scan_type = IIO_ST('s', 12, 16, 0), + }, + IIO_CHAN_SOFT_TIMESTAMP(11) +}; + +static struct iio_chan_spec adis16300_channels[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .extend_name = "supply", + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in_supply, + .scan_index = ADIS16400_SCAN_SUPPLY, + .scan_type = IIO_ST('u', 12, 16, 0) + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_x, + .scan_index = ADIS16400_SCAN_GYRO_X, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_x, + .scan_index = ADIS16400_SCAN_ACC_X, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_y, + .scan_index = ADIS16400_SCAN_ACC_Y, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_z, + .scan_index = ADIS16400_SCAN_ACC_Z, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_OFFSET_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = temp, + .scan_index = ADIS16400_SCAN_TEMP, + .scan_type = IIO_ST('s', 12, 16, 0), + }, { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 1, + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, + .address = in1, + .scan_index = ADIS16350_SCAN_ADC_0, + .scan_type = IIO_ST('s', 12, 16, 0), + }, { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = incli_x, + .scan_index = ADIS16300_SCAN_INCLI_X, + .scan_type = IIO_ST('s', 13, 16, 0), + }, { + .type = IIO_INCLI, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = incli_y, + .scan_index = ADIS16300_SCAN_INCLI_Y, + .scan_type = IIO_ST('s', 13, 16, 0), + }, + IIO_CHAN_SOFT_TIMESTAMP(14) +}; + +static const struct iio_chan_spec adis16334_channels[] = { + { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_x, + .scan_index = ADIS16400_SCAN_GYRO_X, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_y, + .scan_index = ADIS16400_SCAN_GYRO_Y, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ANGL_VEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = gyro_z, + .scan_index = ADIS16400_SCAN_GYRO_Z, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_x, + .scan_index = ADIS16400_SCAN_ACC_X, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Y, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_y, + .scan_index = ADIS16400_SCAN_ACC_Y, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_Z, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT | + IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY_SHARED_BIT, + .address = accel_z, + .scan_index = ADIS16400_SCAN_ACC_Z, + .scan_type = IIO_ST('s', 14, 16, 0), + }, { + .type = IIO_TEMP, + .indexed = 1, + .channel = 0, + .info_mask = IIO_CHAN_INFO_CALIBBIAS_SEPARATE_BIT | + IIO_CHAN_INFO_SCALE_SHARED_BIT, + .address = accel_z, + .scan_index = ADIS16400_SCAN_ACC_Z, + .scan_type = IIO_ST('s', 14, 16, 0), + }, + IIO_CHAN_SOFT_TIMESTAMP(12) +}; + +static struct attribute *adis16400_attributes[] = { + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + NULL +}; + +static const struct attribute_group adis16400_attribute_group = { + .attrs = adis16400_attributes, +}; + +static struct adis16400_chip_info adis16400_chips[] = { + [ADIS16300] = { + .channels = adis16300_channels, + .num_channels = ARRAY_SIZE(adis16300_channels), + .gyro_scale_micro = 873, + .accel_scale_micro = 5884, + .default_scan_mask = (1 << ADIS16400_SCAN_SUPPLY) | + (1 << ADIS16400_SCAN_GYRO_X) | (1 << ADIS16400_SCAN_ACC_X) | + (1 << ADIS16400_SCAN_ACC_Y) | (1 << ADIS16400_SCAN_ACC_Z) | + (1 << ADIS16400_SCAN_TEMP) | (1 << ADIS16400_SCAN_ADC_0) | + (1 << ADIS16300_SCAN_INCLI_X) | (1 << ADIS16300_SCAN_INCLI_Y) | + (1 << 14), + }, + [ADIS16334] = { + .channels = adis16334_channels, + .num_channels = ARRAY_SIZE(adis16334_channels), + .gyro_scale_micro = 873, + .accel_scale_micro = 981, + .default_scan_mask = (1 << ADIS16400_SCAN_GYRO_X) | + (1 << ADIS16400_SCAN_GYRO_Y) | (1 << ADIS16400_SCAN_GYRO_Z) | + (1 << ADIS16400_SCAN_ACC_X) | (1 << ADIS16400_SCAN_ACC_Y) | + (1 << ADIS16400_SCAN_ACC_Z), + }, + [ADIS16350] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .gyro_scale_micro = 872664, + .accel_scale_micro = 24732, + .default_scan_mask = 0x7FF, + .flags = ADIS16400_NO_BURST, + }, + [ADIS16360] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x3FE8, + .gyro_scale_micro = 1279, + .accel_scale_micro = 24732, + .default_scan_mask = 0x7FF, + }, + [ADIS16362] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x3FEA, + .gyro_scale_micro = 1279, + .accel_scale_micro = 24732, + .default_scan_mask = 0x7FF, + }, + [ADIS16364] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x3FEC, + .gyro_scale_micro = 1279, + .accel_scale_micro = 24732, + .default_scan_mask = 0x7FF, + }, + [ADIS16365] = { + .channels = adis16350_channels, + .num_channels = ARRAY_SIZE(adis16350_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x3FED, + .gyro_scale_micro = 1279, + .accel_scale_micro = 24732, + .default_scan_mask = 0x7FF, + }, + [ADIS16400] = { + .channels = adis16400_channels, + .num_channels = ARRAY_SIZE(adis16400_channels), + .flags = ADIS16400_HAS_PROD_ID, + .product_id = 0x4015, + .gyro_scale_micro = 873, + .accel_scale_micro = 32656, + .default_scan_mask = 0xFFF, + } +}; + +static const struct iio_info adis16400_info = { + .driver_module = THIS_MODULE, + .read_raw = &adis16400_read_raw, + .write_raw = &adis16400_write_raw, + .attrs = &adis16400_attribute_group, +}; + +static int __devinit adis16400_probe(struct spi_device *spi) +{ + int ret; + struct adis16400_state *st; + struct iio_dev *indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st->us = spi; + mutex_init(&st->buf_lock); + + /* setup the industrialio driver allocated elements */ + st->variant = &adis16400_chips[spi_get_device_id(spi)->driver_data]; + indio_dev->dev.parent = &spi->dev; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->channels = st->variant->channels; + indio_dev->num_channels = st->variant->num_channels; + indio_dev->info = &adis16400_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = adis16400_configure_ring(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_buffer_register(indio_dev, + st->variant->channels, + st->variant->num_channels); + if (ret) { + dev_err(&spi->dev, "failed to initialize the ring\n"); + goto error_unreg_ring_funcs; + } + + if (spi->irq) { + ret = adis16400_probe_trigger(indio_dev); + if (ret) + goto error_uninitialize_ring; + } + + /* Get the device into a sane initial state */ + ret = adis16400_initial_setup(indio_dev); + if (ret) + goto error_remove_trigger; + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + adis16400_remove_trigger(indio_dev); +error_uninitialize_ring: + iio_buffer_unregister(indio_dev); +error_unreg_ring_funcs: + adis16400_unconfigure_ring(indio_dev); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +/* fixme, confirm ordering in this function */ +static int adis16400_remove(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + ret = adis16400_stop_device(indio_dev); + if (ret) + goto err_ret; + + adis16400_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + adis16400_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + return 0; + +err_ret: + return ret; +} + +static const struct spi_device_id adis16400_id[] = { + {"adis16300", ADIS16300}, + {"adis16334", ADIS16334}, + {"adis16350", ADIS16350}, + {"adis16354", ADIS16350}, + {"adis16355", ADIS16350}, + {"adis16360", ADIS16360}, + {"adis16362", ADIS16362}, + {"adis16364", ADIS16364}, + {"adis16365", ADIS16365}, + {"adis16400", ADIS16400}, + {"adis16405", ADIS16400}, + {} +}; +MODULE_DEVICE_TABLE(spi, adis16400_id); + +static struct spi_driver adis16400_driver = { + .driver = { + .name = "adis16400", + .owner = THIS_MODULE, + }, + .id_table = adis16400_id, + .probe = adis16400_probe, + .remove = __devexit_p(adis16400_remove), +}; +module_spi_driver(adis16400_driver); + +MODULE_AUTHOR("Manuel Stahl "); +MODULE_DESCRIPTION("Analog Devices ADIS16400/5 IMU SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/imu/adis16400_ring.c b/drivers/staging/iio/imu/adis16400_ring.c new file mode 100644 index 00000000..8daa038b --- /dev/null +++ b/drivers/staging/iio/imu/adis16400_ring.c @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" +#include "adis16400.h" + +/** + * adis16400_spi_read_burst() - read all data registers + * @dev: device associated with child of actual device (iio_dev or iio_trig) + * @rx: somewhere to pass back the value read (min size is 24 bytes) + **/ +static int adis16400_spi_read_burst(struct device *dev, u8 *rx) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16400_state *st = iio_priv(indio_dev); + u32 old_speed_hz = st->us->max_speed_hz; + int ret; + + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 2, + }, { + .rx_buf = rx, + .bits_per_word = 8, + .len = 24, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADIS16400_READ_REG(ADIS16400_GLOB_CMD); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + + st->us->max_speed_hz = min(ADIS16400_SPI_BURST, old_speed_hz); + spi_setup(st->us); + + ret = spi_sync(st->us, &msg); + if (ret) + dev_err(&st->us->dev, "problem when burst reading"); + + st->us->max_speed_hz = old_speed_hz; + spi_setup(st->us); + mutex_unlock(&st->buf_lock); + return ret; +} + +static const u16 read_all_tx_array[] = { + cpu_to_be16(ADIS16400_READ_REG(ADIS16400_SUPPLY_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16400_XGYRO_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16400_YGYRO_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16400_ZGYRO_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16400_XACCL_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16400_YACCL_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16400_ZACCL_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16350_XTEMP_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16350_YTEMP_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16350_ZTEMP_OUT)), + cpu_to_be16(ADIS16400_READ_REG(ADIS16400_AUX_ADC)), +}; + +static int adis16350_spi_read_all(struct device *dev, u8 *rx) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct adis16400_state *st = iio_priv(indio_dev); + + struct spi_message msg; + int i, j = 0, ret; + struct spi_transfer *xfers; + int scan_count = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + + xfers = kzalloc(sizeof(*xfers)*(scan_count + 1), + GFP_KERNEL); + if (xfers == NULL) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(read_all_tx_array); i++) + if (test_bit(i, indio_dev->active_scan_mask)) { + xfers[j].tx_buf = &read_all_tx_array[i]; + xfers[j].bits_per_word = 16; + xfers[j].len = 2; + xfers[j + 1].rx_buf = rx + j*2; + j++; + } + xfers[j].bits_per_word = 16; + xfers[j].len = 2; + + spi_message_init(&msg); + for (j = 0; j < scan_count + 1; j++) + spi_message_add_tail(&xfers[j], &msg); + + ret = spi_sync(st->us, &msg); + kfree(xfers); + + return ret; +} + +/* Whilst this makes a lot of calls to iio_sw_ring functions - it is to device + * specific to be rolled into the core. + */ +static irqreturn_t adis16400_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adis16400_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + int i = 0, j, ret = 0; + s16 *data; + size_t datasize = ring->access->get_bytes_per_datum(ring); + /* Asumption that long is enough for maximum channels */ + unsigned long mask = *indio_dev->active_scan_mask; + int scan_count = bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + data = kmalloc(datasize , GFP_KERNEL); + if (data == NULL) { + dev_err(&st->us->dev, "memory alloc failed in ring bh"); + return -ENOMEM; + } + + if (scan_count) { + if (st->variant->flags & ADIS16400_NO_BURST) { + ret = adis16350_spi_read_all(&indio_dev->dev, st->rx); + if (ret < 0) + goto err; + for (; i < scan_count; i++) + data[i] = *(s16 *)(st->rx + i*2); + } else { + ret = adis16400_spi_read_burst(&indio_dev->dev, st->rx); + if (ret < 0) + goto err; + for (; i < scan_count; i++) { + j = __ffs(mask); + mask &= ~(1 << j); + data[i] = be16_to_cpup( + (__be16 *)&(st->rx[j*2])); + } + } + } + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + *((s64 *)(data + ((i + 3)/4)*4)) = pf->timestamp; + ring->access->store_to(indio_dev->buffer, (u8 *) data, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + kfree(data); + return IRQ_HANDLED; + +err: + kfree(data); + return ret; +} + +void adis16400_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +static const struct iio_buffer_setup_ops adis16400_ring_setup_ops = { + .preenable = &iio_sw_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +int adis16400_configure_ring(struct iio_dev *indio_dev) +{ + int ret = 0; + struct iio_buffer *ring; + + ring = iio_sw_rb_allocate(indio_dev); + if (!ring) { + ret = -ENOMEM; + return ret; + } + indio_dev->buffer = ring; + ring->scan_timestamp = true; + indio_dev->setup_ops = &adis16400_ring_setup_ops; + + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &adis16400_trigger_handler, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + indio_dev->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + return 0; +error_iio_sw_rb_free: + iio_sw_rb_free(indio_dev->buffer); + return ret; +} diff --git a/drivers/staging/iio/imu/adis16400_trigger.c b/drivers/staging/iio/imu/adis16400_trigger.c new file mode 100644 index 00000000..5bf00075 --- /dev/null +++ b/drivers/staging/iio/imu/adis16400_trigger.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" +#include "adis16400.h" + +/** + * adis16400_data_rdy_trigger_set_state() set datardy interrupt state + **/ +static int adis16400_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + + dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state); + return adis16400_set_irq(indio_dev, state); +} + +static const struct iio_trigger_ops adis16400_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &adis16400_data_rdy_trigger_set_state, +}; + +int adis16400_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct adis16400_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("%s-dev%d", + indio_dev->name, + indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + ret = request_irq(st->us->irq, + &iio_trigger_generic_data_rdy_poll, + IRQF_TRIGGER_RISING, + "adis16400", + st->trig); + if (ret) + goto error_free_trig; + st->trig->dev.parent = &st->us->dev; + st->trig->private_data = indio_dev; + st->trig->ops = &adis16400_trigger_ops; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->us->irq, st->trig); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void adis16400_remove_trigger(struct iio_dev *indio_dev) +{ + struct adis16400_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->us->irq, st->trig); + iio_free_trigger(st->trig); +} diff --git a/drivers/staging/iio/industrialio-buffer.c b/drivers/staging/iio/industrialio-buffer.c new file mode 100644 index 00000000..386ba760 --- /dev/null +++ b/drivers/staging/iio/industrialio-buffer.c @@ -0,0 +1,734 @@ +/* The industrial I/O core + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Handling of buffer allocation / resizing. + * + * + * Things to look at here. + * - Better memory allocation techniques? + * - Alternative access techniques? + */ +#include +#include +#include +#include +#include +#include +#include + +#include "iio.h" +#include "iio_core.h" +#include "sysfs.h" +#include "buffer.h" + +static const char * const iio_endian_prefix[] = { + [IIO_BE] = "be", + [IIO_LE] = "le", +}; + +/** + * iio_buffer_read_first_n_outer() - chrdev read for buffer access + * + * This function relies on all buffer implementations having an + * iio_buffer as their first element. + **/ +ssize_t iio_buffer_read_first_n_outer(struct file *filp, char __user *buf, + size_t n, loff_t *f_ps) +{ + struct iio_dev *indio_dev = filp->private_data; + struct iio_buffer *rb = indio_dev->buffer; + + if (!rb || !rb->access->read_first_n) + return -EINVAL; + return rb->access->read_first_n(rb, n, buf); +} + +/** + * iio_buffer_poll() - poll the buffer to find out if it has data + */ +unsigned int iio_buffer_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct iio_dev *indio_dev = filp->private_data; + struct iio_buffer *rb = indio_dev->buffer; + + poll_wait(filp, &rb->pollq, wait); + if (rb->stufftoread) + return POLLIN | POLLRDNORM; + /* need a way of knowing if there may be enough data... */ + return 0; +} + +void iio_buffer_init(struct iio_buffer *buffer) +{ + INIT_LIST_HEAD(&buffer->demux_list); + init_waitqueue_head(&buffer->pollq); +} +EXPORT_SYMBOL(iio_buffer_init); + +static ssize_t iio_show_scan_index(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", to_iio_dev_attr(attr)->c->scan_index); +} + +static ssize_t iio_show_fixed_type(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + u8 type = this_attr->c->scan_type.endianness; + + if (type == IIO_CPU) { +#ifdef __LITTLE_ENDIAN + type = IIO_LE; +#else + type = IIO_BE; +#endif + } + return sprintf(buf, "%s:%c%d/%d>>%u\n", + iio_endian_prefix[type], + this_attr->c->scan_type.sign, + this_attr->c->scan_type.realbits, + this_attr->c->scan_type.storagebits, + this_attr->c->scan_type.shift); +} + +static ssize_t iio_scan_el_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + ret = test_bit(to_iio_dev_attr(attr)->address, + indio_dev->buffer->scan_mask); + + return sprintf(buf, "%d\n", ret); +} + +static int iio_scan_mask_clear(struct iio_buffer *buffer, int bit) +{ + clear_bit(bit, buffer->scan_mask); + return 0; +} + +static ssize_t iio_scan_el_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret = 0; + bool state; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_buffer *buffer = indio_dev->buffer; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + state = !(buf[0] == '0'); + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto error_ret; + } + ret = iio_scan_mask_query(indio_dev, buffer, this_attr->address); + if (ret < 0) + goto error_ret; + if (!state && ret) { + ret = iio_scan_mask_clear(buffer, this_attr->address); + if (ret) + goto error_ret; + } else if (state && !ret) { + ret = iio_scan_mask_set(indio_dev, buffer, this_attr->address); + if (ret) + goto error_ret; + } + +error_ret: + mutex_unlock(&indio_dev->mlock); + + return ret < 0 ? ret : len; + +} + +static ssize_t iio_scan_el_ts_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", indio_dev->buffer->scan_timestamp); +} + +static ssize_t iio_scan_el_ts_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + bool state; + + state = !(buf[0] == '0'); + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + goto error_ret; + } + indio_dev->buffer->scan_timestamp = state; +error_ret: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static int iio_buffer_add_channel_sysfs(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + int ret, attrcount = 0; + struct iio_buffer *buffer = indio_dev->buffer; + + ret = __iio_add_chan_devattr("index", + chan, + &iio_show_scan_index, + NULL, + 0, + 0, + &indio_dev->dev, + &buffer->scan_el_dev_attr_list); + if (ret) + goto error_ret; + attrcount++; + ret = __iio_add_chan_devattr("type", + chan, + &iio_show_fixed_type, + NULL, + 0, + 0, + &indio_dev->dev, + &buffer->scan_el_dev_attr_list); + if (ret) + goto error_ret; + attrcount++; + if (chan->type != IIO_TIMESTAMP) + ret = __iio_add_chan_devattr("en", + chan, + &iio_scan_el_show, + &iio_scan_el_store, + chan->scan_index, + 0, + &indio_dev->dev, + &buffer->scan_el_dev_attr_list); + else + ret = __iio_add_chan_devattr("en", + chan, + &iio_scan_el_ts_show, + &iio_scan_el_ts_store, + chan->scan_index, + 0, + &indio_dev->dev, + &buffer->scan_el_dev_attr_list); + attrcount++; + ret = attrcount; +error_ret: + return ret; +} + +static void iio_buffer_remove_and_free_scan_dev_attr(struct iio_dev *indio_dev, + struct iio_dev_attr *p) +{ + kfree(p->dev_attr.attr.name); + kfree(p); +} + +static void __iio_buffer_attr_cleanup(struct iio_dev *indio_dev) +{ + struct iio_dev_attr *p, *n; + struct iio_buffer *buffer = indio_dev->buffer; + + list_for_each_entry_safe(p, n, + &buffer->scan_el_dev_attr_list, l) + iio_buffer_remove_and_free_scan_dev_attr(indio_dev, p); +} + +static const char * const iio_scan_elements_group_name = "scan_elements"; + +int iio_buffer_register(struct iio_dev *indio_dev, + const struct iio_chan_spec *channels, + int num_channels) +{ + struct iio_dev_attr *p; + struct attribute **attr; + struct iio_buffer *buffer = indio_dev->buffer; + int ret, i, attrn, attrcount, attrcount_orig = 0; + + if (buffer->attrs) + indio_dev->groups[indio_dev->groupcounter++] = buffer->attrs; + + if (buffer->scan_el_attrs != NULL) { + attr = buffer->scan_el_attrs->attrs; + while (*attr++ != NULL) + attrcount_orig++; + } + attrcount = attrcount_orig; + INIT_LIST_HEAD(&buffer->scan_el_dev_attr_list); + if (channels) { + /* new magic */ + for (i = 0; i < num_channels; i++) { + /* Establish necessary mask length */ + if (channels[i].scan_index > + (int)indio_dev->masklength - 1) + indio_dev->masklength + = indio_dev->channels[i].scan_index + 1; + + ret = iio_buffer_add_channel_sysfs(indio_dev, + &channels[i]); + if (ret < 0) + goto error_cleanup_dynamic; + attrcount += ret; + if (channels[i].type == IIO_TIMESTAMP) + buffer->scan_index_timestamp = + channels[i].scan_index; + } + if (indio_dev->masklength && buffer->scan_mask == NULL) { + buffer->scan_mask = kcalloc(BITS_TO_LONGS(indio_dev->masklength), + sizeof(*buffer->scan_mask), + GFP_KERNEL); + if (buffer->scan_mask == NULL) { + ret = -ENOMEM; + goto error_cleanup_dynamic; + } + } + } + + buffer->scan_el_group.name = iio_scan_elements_group_name; + + buffer->scan_el_group.attrs = kcalloc(attrcount + 1, + sizeof(buffer->scan_el_group.attrs[0]), + GFP_KERNEL); + if (buffer->scan_el_group.attrs == NULL) { + ret = -ENOMEM; + goto error_free_scan_mask; + } + if (buffer->scan_el_attrs) + memcpy(buffer->scan_el_group.attrs, buffer->scan_el_attrs, + sizeof(buffer->scan_el_group.attrs[0])*attrcount_orig); + attrn = attrcount_orig; + + list_for_each_entry(p, &buffer->scan_el_dev_attr_list, l) + buffer->scan_el_group.attrs[attrn++] = &p->dev_attr.attr; + indio_dev->groups[indio_dev->groupcounter++] = &buffer->scan_el_group; + + return 0; + +error_free_scan_mask: + kfree(buffer->scan_mask); +error_cleanup_dynamic: + __iio_buffer_attr_cleanup(indio_dev); + + return ret; +} +EXPORT_SYMBOL(iio_buffer_register); + +void iio_buffer_unregister(struct iio_dev *indio_dev) +{ + kfree(indio_dev->buffer->scan_mask); + kfree(indio_dev->buffer->scan_el_group.attrs); + __iio_buffer_attr_cleanup(indio_dev); +} +EXPORT_SYMBOL(iio_buffer_unregister); + +ssize_t iio_buffer_read_length(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_buffer *buffer = indio_dev->buffer; + + if (buffer->access->get_length) + return sprintf(buf, "%d\n", + buffer->access->get_length(buffer)); + + return 0; +} +EXPORT_SYMBOL(iio_buffer_read_length); + +ssize_t iio_buffer_write_length(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + ulong val; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_buffer *buffer = indio_dev->buffer; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + return ret; + + if (buffer->access->get_length) + if (val == buffer->access->get_length(buffer)) + return len; + + mutex_lock(&indio_dev->mlock); + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + } else { + if (buffer->access->set_length) + buffer->access->set_length(buffer, val); + ret = 0; + } + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} +EXPORT_SYMBOL(iio_buffer_write_length); + +ssize_t iio_buffer_store_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + bool requested_state, current_state; + int previous_mode; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_buffer *buffer = indio_dev->buffer; + + mutex_lock(&indio_dev->mlock); + previous_mode = indio_dev->currentmode; + requested_state = !(buf[0] == '0'); + current_state = iio_buffer_enabled(indio_dev); + if (current_state == requested_state) { + printk(KERN_INFO "iio-buffer, current state requested again\n"); + goto done; + } + if (requested_state) { + if (indio_dev->setup_ops->preenable) { + ret = indio_dev->setup_ops->preenable(indio_dev); + if (ret) { + printk(KERN_ERR + "Buffer not started:" + "buffer preenable failed\n"); + goto error_ret; + } + } + if (buffer->access->request_update) { + ret = buffer->access->request_update(buffer); + if (ret) { + printk(KERN_INFO + "Buffer not started:" + "buffer parameter update failed\n"); + goto error_ret; + } + } + /* Definitely possible for devices to support both of these.*/ + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) { + if (!indio_dev->trig) { + printk(KERN_INFO + "Buffer not started: no trigger\n"); + ret = -EINVAL; + goto error_ret; + } + indio_dev->currentmode = INDIO_BUFFER_TRIGGERED; + } else if (indio_dev->modes & INDIO_BUFFER_HARDWARE) + indio_dev->currentmode = INDIO_BUFFER_HARDWARE; + else { /* should never be reached */ + ret = -EINVAL; + goto error_ret; + } + + if (indio_dev->setup_ops->postenable) { + ret = indio_dev->setup_ops->postenable(indio_dev); + if (ret) { + printk(KERN_INFO + "Buffer not started:" + "postenable failed\n"); + indio_dev->currentmode = previous_mode; + if (indio_dev->setup_ops->postdisable) + indio_dev->setup_ops-> + postdisable(indio_dev); + goto error_ret; + } + } + } else { + if (indio_dev->setup_ops->predisable) { + ret = indio_dev->setup_ops->predisable(indio_dev); + if (ret) + goto error_ret; + } + indio_dev->currentmode = INDIO_DIRECT_MODE; + if (indio_dev->setup_ops->postdisable) { + ret = indio_dev->setup_ops->postdisable(indio_dev); + if (ret) + goto error_ret; + } + } +done: + mutex_unlock(&indio_dev->mlock); + return len; + +error_ret: + mutex_unlock(&indio_dev->mlock); + return ret; +} +EXPORT_SYMBOL(iio_buffer_store_enable); + +ssize_t iio_buffer_show_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", iio_buffer_enabled(indio_dev)); +} +EXPORT_SYMBOL(iio_buffer_show_enable); + +/* note NULL used as error indicator as it doesn't make sense. */ +static const unsigned long *iio_scan_mask_match(const unsigned long *av_masks, + unsigned int masklength, + const unsigned long *mask) +{ + if (bitmap_empty(mask, masklength)) + return NULL; + while (*av_masks) { + if (bitmap_subset(mask, av_masks, masklength)) + return av_masks; + av_masks += BITS_TO_LONGS(masklength); + } + return NULL; +} + +int iio_sw_buffer_preenable(struct iio_dev *indio_dev) +{ + struct iio_buffer *buffer = indio_dev->buffer; + const struct iio_chan_spec *ch; + unsigned bytes = 0; + int length, i; + dev_dbg(&indio_dev->dev, "%s\n", __func__); + + /* How much space will the demuxed element take? */ + for_each_set_bit(i, buffer->scan_mask, + indio_dev->masklength) { + ch = iio_find_channel_from_si(indio_dev, i); + length = ch->scan_type.storagebits/8; + bytes = ALIGN(bytes, length); + bytes += length; + } + if (buffer->scan_timestamp) { + ch = iio_find_channel_from_si(indio_dev, + buffer->scan_index_timestamp); + length = ch->scan_type.storagebits/8; + bytes = ALIGN(bytes, length); + bytes += length; + } + buffer->access->set_bytes_per_datum(buffer, bytes); + + /* What scan mask do we actually have ?*/ + if (indio_dev->available_scan_masks) + indio_dev->active_scan_mask = + iio_scan_mask_match(indio_dev->available_scan_masks, + indio_dev->masklength, + buffer->scan_mask); + else + indio_dev->active_scan_mask = buffer->scan_mask; + iio_update_demux(indio_dev); + + if (indio_dev->info->update_scan_mode) + return indio_dev->info + ->update_scan_mode(indio_dev, + indio_dev->active_scan_mask); + return 0; +} +EXPORT_SYMBOL(iio_sw_buffer_preenable); + +/** + * iio_scan_mask_set() - set particular bit in the scan mask + * @buffer: the buffer whose scan mask we are interested in + * @bit: the bit to be set. + **/ +int iio_scan_mask_set(struct iio_dev *indio_dev, + struct iio_buffer *buffer, int bit) +{ + const unsigned long *mask; + unsigned long *trialmask; + + trialmask = kmalloc(sizeof(*trialmask)* + BITS_TO_LONGS(indio_dev->masklength), + GFP_KERNEL); + + if (trialmask == NULL) + return -ENOMEM; + if (!indio_dev->masklength) { + WARN_ON("trying to set scanmask prior to registering buffer\n"); + kfree(trialmask); + return -EINVAL; + } + bitmap_copy(trialmask, buffer->scan_mask, indio_dev->masklength); + set_bit(bit, trialmask); + + if (indio_dev->available_scan_masks) { + mask = iio_scan_mask_match(indio_dev->available_scan_masks, + indio_dev->masklength, + trialmask); + if (!mask) { + kfree(trialmask); + return -EINVAL; + } + } + bitmap_copy(buffer->scan_mask, trialmask, indio_dev->masklength); + + kfree(trialmask); + + return 0; +}; +EXPORT_SYMBOL_GPL(iio_scan_mask_set); + +int iio_scan_mask_query(struct iio_dev *indio_dev, + struct iio_buffer *buffer, int bit) +{ + if (bit > indio_dev->masklength) + return -EINVAL; + + if (!buffer->scan_mask) + return 0; + + return test_bit(bit, buffer->scan_mask); +}; +EXPORT_SYMBOL_GPL(iio_scan_mask_query); + +/** + * struct iio_demux_table() - table describing demux memcpy ops + * @from: index to copy from + * @to: index to copy to + * @length: how many bytes to copy + * @l: list head used for management + */ +struct iio_demux_table { + unsigned from; + unsigned to; + unsigned length; + struct list_head l; +}; + +static unsigned char *iio_demux(struct iio_buffer *buffer, + unsigned char *datain) +{ + struct iio_demux_table *t; + + if (list_empty(&buffer->demux_list)) + return datain; + list_for_each_entry(t, &buffer->demux_list, l) + memcpy(buffer->demux_bounce + t->to, + datain + t->from, t->length); + + return buffer->demux_bounce; +} + +int iio_push_to_buffer(struct iio_buffer *buffer, unsigned char *data, + s64 timestamp) +{ + unsigned char *dataout = iio_demux(buffer, data); + + return buffer->access->store_to(buffer, dataout, timestamp); +} +EXPORT_SYMBOL_GPL(iio_push_to_buffer); + +int iio_update_demux(struct iio_dev *indio_dev) +{ + const struct iio_chan_spec *ch; + struct iio_buffer *buffer = indio_dev->buffer; + int ret, in_ind = -1, out_ind, length; + unsigned in_loc = 0, out_loc = 0; + struct iio_demux_table *p, *q; + + /* Clear out any old demux */ + list_for_each_entry_safe(p, q, &buffer->demux_list, l) { + list_del(&p->l); + kfree(p); + } + kfree(buffer->demux_bounce); + buffer->demux_bounce = NULL; + + /* First work out which scan mode we will actually have */ + if (bitmap_equal(indio_dev->active_scan_mask, + buffer->scan_mask, + indio_dev->masklength)) + return 0; + + /* Now we have the two masks, work from least sig and build up sizes */ + for_each_set_bit(out_ind, + indio_dev->active_scan_mask, + indio_dev->masklength) { + in_ind = find_next_bit(indio_dev->active_scan_mask, + indio_dev->masklength, + in_ind + 1); + while (in_ind != out_ind) { + in_ind = find_next_bit(indio_dev->active_scan_mask, + indio_dev->masklength, + in_ind + 1); + ch = iio_find_channel_from_si(indio_dev, in_ind); + length = ch->scan_type.storagebits/8; + /* Make sure we are aligned */ + in_loc += length; + if (in_loc % length) + in_loc += length - in_loc % length; + } + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) { + ret = -ENOMEM; + goto error_clear_mux_table; + } + ch = iio_find_channel_from_si(indio_dev, in_ind); + length = ch->scan_type.storagebits/8; + if (out_loc % length) + out_loc += length - out_loc % length; + if (in_loc % length) + in_loc += length - in_loc % length; + p->from = in_loc; + p->to = out_loc; + p->length = length; + list_add_tail(&p->l, &buffer->demux_list); + out_loc += length; + in_loc += length; + } + /* Relies on scan_timestamp being last */ + if (buffer->scan_timestamp) { + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) { + ret = -ENOMEM; + goto error_clear_mux_table; + } + ch = iio_find_channel_from_si(indio_dev, + buffer->scan_index_timestamp); + length = ch->scan_type.storagebits/8; + if (out_loc % length) + out_loc += length - out_loc % length; + if (in_loc % length) + in_loc += length - in_loc % length; + p->from = in_loc; + p->to = out_loc; + p->length = length; + list_add_tail(&p->l, &buffer->demux_list); + out_loc += length; + in_loc += length; + } + buffer->demux_bounce = kzalloc(out_loc, GFP_KERNEL); + if (buffer->demux_bounce == NULL) { + ret = -ENOMEM; + goto error_clear_mux_table; + } + return 0; + +error_clear_mux_table: + list_for_each_entry_safe(p, q, &buffer->demux_list, l) { + list_del(&p->l); + kfree(p); + } + return ret; +} +EXPORT_SYMBOL_GPL(iio_update_demux); diff --git a/drivers/staging/iio/industrialio-core.c b/drivers/staging/iio/industrialio-core.c new file mode 100644 index 00000000..d303bfbf --- /dev/null +++ b/drivers/staging/iio/industrialio-core.c @@ -0,0 +1,927 @@ +/* The industrial I/O core + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Based on elements of hwmon and input subsystems. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iio.h" +#include "iio_core.h" +#include "iio_core_trigger.h" +#include "sysfs.h" +#include "events.h" + +/* IDA to assign each registered device a unique id*/ +static DEFINE_IDA(iio_ida); + +static dev_t iio_devt; + +#define IIO_DEV_MAX 256 +struct bus_type iio_bus_type = { + .name = "iio", +}; +EXPORT_SYMBOL(iio_bus_type); + +static struct dentry *iio_debugfs_dentry; + +static const char * const iio_data_type_name[] = { + [IIO_RAW] = "raw", + [IIO_PROCESSED] = "input", +}; + +static const char * const iio_direction[] = { + [0] = "in", + [1] = "out", +}; + +static const char * const iio_chan_type_name_spec[] = { + [IIO_VOLTAGE] = "voltage", + [IIO_CURRENT] = "current", + [IIO_POWER] = "power", + [IIO_ACCEL] = "accel", + [IIO_ANGL_VEL] = "anglvel", + [IIO_MAGN] = "magn", + [IIO_LIGHT] = "illuminance", + [IIO_INTENSITY] = "intensity", + [IIO_PROXIMITY] = "proximity", + [IIO_TEMP] = "temp", + [IIO_INCLI] = "incli", + [IIO_ROT] = "rot", + [IIO_ANGL] = "angl", + [IIO_TIMESTAMP] = "timestamp", + [IIO_CAPACITANCE] = "capacitance", +}; + +static const char * const iio_modifier_names[] = { + [IIO_MOD_X] = "x", + [IIO_MOD_Y] = "y", + [IIO_MOD_Z] = "z", + [IIO_MOD_LIGHT_BOTH] = "both", + [IIO_MOD_LIGHT_IR] = "ir", +}; + +/* relies on pairs of these shared then separate */ +static const char * const iio_chan_info_postfix[] = { + [IIO_CHAN_INFO_SCALE] = "scale", + [IIO_CHAN_INFO_OFFSET] = "offset", + [IIO_CHAN_INFO_CALIBSCALE] = "calibscale", + [IIO_CHAN_INFO_CALIBBIAS] = "calibbias", + [IIO_CHAN_INFO_PEAK] = "peak_raw", + [IIO_CHAN_INFO_PEAK_SCALE] = "peak_scale", + [IIO_CHAN_INFO_QUADRATURE_CORRECTION_RAW] = "quadrature_correction_raw", + [IIO_CHAN_INFO_AVERAGE_RAW] = "mean_raw", + [IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY] + = "filter_low_pass_3db_frequency", +}; + +const struct iio_chan_spec +*iio_find_channel_from_si(struct iio_dev *indio_dev, int si) +{ + int i; + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].scan_index == si) + return &indio_dev->channels[i]; + return NULL; +} + +/* This turns up an awful lot */ +ssize_t iio_read_const_attr(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", to_iio_const_attr(attr)->string); +} +EXPORT_SYMBOL(iio_read_const_attr); + +static int __init iio_init(void) +{ + int ret; + + /* Register sysfs bus */ + ret = bus_register(&iio_bus_type); + if (ret < 0) { + printk(KERN_ERR + "%s could not register bus type\n", + __FILE__); + goto error_nothing; + } + + ret = alloc_chrdev_region(&iio_devt, 0, IIO_DEV_MAX, "iio"); + if (ret < 0) { + printk(KERN_ERR "%s: failed to allocate char dev region\n", + __FILE__); + goto error_unregister_bus_type; + } + + iio_debugfs_dentry = debugfs_create_dir("iio", NULL); + + return 0; + +error_unregister_bus_type: + bus_unregister(&iio_bus_type); +error_nothing: + return ret; +} + +static void __exit iio_exit(void) +{ + if (iio_devt) + unregister_chrdev_region(iio_devt, IIO_DEV_MAX); + bus_unregister(&iio_bus_type); + debugfs_remove(iio_debugfs_dentry); +} + +#if defined(CONFIG_DEBUG_FS) +static int iio_debugfs_open(struct inode *inode, struct file *file) +{ + if (inode->i_private) + file->private_data = inode->i_private; + + return 0; +} + +static ssize_t iio_debugfs_read_reg(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct iio_dev *indio_dev = file->private_data; + char buf[20]; + unsigned val = 0; + ssize_t len; + int ret; + + ret = indio_dev->info->debugfs_reg_access(indio_dev, + indio_dev->cached_reg_addr, + 0, &val); + if (ret) + dev_err(indio_dev->dev.parent, "%s: read failed\n", __func__); + + len = snprintf(buf, sizeof(buf), "0x%X\n", val); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static ssize_t iio_debugfs_write_reg(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + struct iio_dev *indio_dev = file->private_data; + unsigned reg, val; + char buf[80]; + int ret; + + count = min_t(size_t, count, (sizeof(buf)-1)); + if (copy_from_user(buf, userbuf, count)) + return -EFAULT; + + buf[count] = 0; + + ret = sscanf(buf, "%i %i", ®, &val); + + switch (ret) { + case 1: + indio_dev->cached_reg_addr = reg; + break; + case 2: + indio_dev->cached_reg_addr = reg; + ret = indio_dev->info->debugfs_reg_access(indio_dev, reg, + val, NULL); + if (ret) { + dev_err(indio_dev->dev.parent, "%s: write failed\n", + __func__); + return ret; + } + break; + default: + return -EINVAL; + } + + return count; +} + +static const struct file_operations iio_debugfs_reg_fops = { + .open = iio_debugfs_open, + .read = iio_debugfs_read_reg, + .write = iio_debugfs_write_reg, +}; + +static void iio_device_unregister_debugfs(struct iio_dev *indio_dev) +{ + debugfs_remove_recursive(indio_dev->debugfs_dentry); +} + +static int iio_device_register_debugfs(struct iio_dev *indio_dev) +{ + struct dentry *d; + + if (indio_dev->info->debugfs_reg_access == NULL) + return 0; + + if (IS_ERR(iio_debugfs_dentry)) + return 0; + + indio_dev->debugfs_dentry = + debugfs_create_dir(dev_name(&indio_dev->dev), + iio_debugfs_dentry); + if (IS_ERR(indio_dev->debugfs_dentry)) + return PTR_ERR(indio_dev->debugfs_dentry); + + if (indio_dev->debugfs_dentry == NULL) { + dev_warn(indio_dev->dev.parent, + "Failed to create debugfs directory\n"); + return -EFAULT; + } + + d = debugfs_create_file("direct_reg_access", 0644, + indio_dev->debugfs_dentry, + indio_dev, &iio_debugfs_reg_fops); + if (!d) { + iio_device_unregister_debugfs(indio_dev); + return -ENOMEM; + } + + return 0; +} +#else +static int iio_device_register_debugfs(struct iio_dev *indio_dev) +{ + return 0; +} + +static void iio_device_unregister_debugfs(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +static ssize_t iio_read_channel_ext_info(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + const struct iio_chan_spec_ext_info *ext_info; + + ext_info = &this_attr->c->ext_info[this_attr->address]; + + return ext_info->read(indio_dev, this_attr->c, buf); +} + +static ssize_t iio_write_channel_ext_info(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + const struct iio_chan_spec_ext_info *ext_info; + + ext_info = &this_attr->c->ext_info[this_attr->address]; + + return ext_info->write(indio_dev, this_attr->c, buf, len); +} + +static ssize_t iio_read_channel_info(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int val, val2; + int ret = indio_dev->info->read_raw(indio_dev, this_attr->c, + &val, &val2, this_attr->address); + + if (ret < 0) + return ret; + + if (ret == IIO_VAL_INT) + return sprintf(buf, "%d\n", val); + else if (ret == IIO_VAL_INT_PLUS_MICRO) { + if (val2 < 0) + return sprintf(buf, "-%d.%06u\n", val, -val2); + else + return sprintf(buf, "%d.%06u\n", val, val2); + } else if (ret == IIO_VAL_INT_PLUS_NANO) { + if (val2 < 0) + return sprintf(buf, "-%d.%09u\n", val, -val2); + else + return sprintf(buf, "%d.%09u\n", val, val2); + } else + return 0; +} + +static ssize_t iio_write_channel_info(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret, integer = 0, fract = 0, fract_mult = 100000; + bool integer_part = true, negative = false; + + /* Assumes decimal - precision based on number of digits */ + if (!indio_dev->info->write_raw) + return -EINVAL; + + if (indio_dev->info->write_raw_get_fmt) + switch (indio_dev->info->write_raw_get_fmt(indio_dev, + this_attr->c, this_attr->address)) { + case IIO_VAL_INT_PLUS_MICRO: + fract_mult = 100000; + break; + case IIO_VAL_INT_PLUS_NANO: + fract_mult = 100000000; + break; + default: + return -EINVAL; + } + + if (buf[0] == '-') { + negative = true; + buf++; + } + + while (*buf) { + if ('0' <= *buf && *buf <= '9') { + if (integer_part) + integer = integer*10 + *buf - '0'; + else { + fract += fract_mult*(*buf - '0'); + if (fract_mult == 1) + break; + fract_mult /= 10; + } + } else if (*buf == '\n') { + if (*(buf + 1) == '\0') + break; + else + return -EINVAL; + } else if (*buf == '.') { + integer_part = false; + } else { + return -EINVAL; + } + buf++; + } + if (negative) { + if (integer) + integer = -integer; + else + fract = -fract; + } + + ret = indio_dev->info->write_raw(indio_dev, this_attr->c, + integer, fract, this_attr->address); + if (ret) + return ret; + + return len; +} + +static +int __iio_device_attr_init(struct device_attribute *dev_attr, + const char *postfix, + struct iio_chan_spec const *chan, + ssize_t (*readfunc)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*writefunc)(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len), + bool generic) +{ + int ret; + char *name_format, *full_postfix; + sysfs_attr_init(&dev_attr->attr); + + /* Build up postfix of __postfix */ + if (chan->modified && !generic) { + if (chan->extend_name) + full_postfix = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_modifier_names[chan + ->channel2], + chan->extend_name, + postfix); + else + full_postfix = kasprintf(GFP_KERNEL, "%s_%s", + iio_modifier_names[chan + ->channel2], + postfix); + } else { + if (chan->extend_name == NULL) + full_postfix = kstrdup(postfix, GFP_KERNEL); + else + full_postfix = kasprintf(GFP_KERNEL, + "%s_%s", + chan->extend_name, + postfix); + } + if (full_postfix == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + if (chan->differential) { /* Differential can not have modifier */ + if (generic) + name_format + = kasprintf(GFP_KERNEL, "%s_%s-%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + iio_chan_type_name_spec[chan->type], + full_postfix); + else if (chan->indexed) + name_format + = kasprintf(GFP_KERNEL, "%s_%s%d-%s%d_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + chan->channel, + iio_chan_type_name_spec[chan->type], + chan->channel2, + full_postfix); + else { + WARN_ON("Differential channels must be indexed\n"); + ret = -EINVAL; + goto error_free_full_postfix; + } + } else { /* Single ended */ + if (generic) + name_format + = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + full_postfix); + else if (chan->indexed) + name_format + = kasprintf(GFP_KERNEL, "%s_%s%d_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + chan->channel, + full_postfix); + else + name_format + = kasprintf(GFP_KERNEL, "%s_%s_%s", + iio_direction[chan->output], + iio_chan_type_name_spec[chan->type], + full_postfix); + } + if (name_format == NULL) { + ret = -ENOMEM; + goto error_free_full_postfix; + } + dev_attr->attr.name = kasprintf(GFP_KERNEL, + name_format, + chan->channel, + chan->channel2); + if (dev_attr->attr.name == NULL) { + ret = -ENOMEM; + goto error_free_name_format; + } + + if (readfunc) { + dev_attr->attr.mode |= S_IRUGO; + dev_attr->show = readfunc; + } + + if (writefunc) { + dev_attr->attr.mode |= S_IWUSR; + dev_attr->store = writefunc; + } + kfree(name_format); + kfree(full_postfix); + + return 0; + +error_free_name_format: + kfree(name_format); +error_free_full_postfix: + kfree(full_postfix); +error_ret: + return ret; +} + +static void __iio_device_attr_deinit(struct device_attribute *dev_attr) +{ + kfree(dev_attr->attr.name); +} + +int __iio_add_chan_devattr(const char *postfix, + struct iio_chan_spec const *chan, + ssize_t (*readfunc)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*writefunc)(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len), + u64 mask, + bool generic, + struct device *dev, + struct list_head *attr_list) +{ + int ret; + struct iio_dev_attr *iio_attr, *t; + + iio_attr = kzalloc(sizeof *iio_attr, GFP_KERNEL); + if (iio_attr == NULL) { + ret = -ENOMEM; + goto error_ret; + } + ret = __iio_device_attr_init(&iio_attr->dev_attr, + postfix, chan, + readfunc, writefunc, generic); + if (ret) + goto error_iio_dev_attr_free; + iio_attr->c = chan; + iio_attr->address = mask; + list_for_each_entry(t, attr_list, l) + if (strcmp(t->dev_attr.attr.name, + iio_attr->dev_attr.attr.name) == 0) { + if (!generic) + dev_err(dev, "tried to double register : %s\n", + t->dev_attr.attr.name); + ret = -EBUSY; + goto error_device_attr_deinit; + } + list_add(&iio_attr->l, attr_list); + + return 0; + +error_device_attr_deinit: + __iio_device_attr_deinit(&iio_attr->dev_attr); +error_iio_dev_attr_free: + kfree(iio_attr); +error_ret: + return ret; +} + +static int iio_device_add_channel_sysfs(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + int ret, i, attrcount = 0; + const struct iio_chan_spec_ext_info *ext_info; + + if (chan->channel < 0) + return 0; + + ret = __iio_add_chan_devattr(iio_data_type_name[chan->processed_val], + chan, + &iio_read_channel_info, + (chan->output ? + &iio_write_channel_info : NULL), + 0, + 0, + &indio_dev->dev, + &indio_dev->channel_attr_list); + if (ret) + goto error_ret; + attrcount++; + + for_each_set_bit(i, &chan->info_mask, sizeof(long)*8) { + ret = __iio_add_chan_devattr(iio_chan_info_postfix[i/2], + chan, + &iio_read_channel_info, + &iio_write_channel_info, + i/2, + !(i%2), + &indio_dev->dev, + &indio_dev->channel_attr_list); + if (ret == -EBUSY && (i%2 == 0)) { + ret = 0; + continue; + } + if (ret < 0) + goto error_ret; + attrcount++; + } + + if (chan->ext_info) { + unsigned int i = 0; + for (ext_info = chan->ext_info; ext_info->name; ext_info++) { + ret = __iio_add_chan_devattr(ext_info->name, + chan, + ext_info->read ? + &iio_read_channel_ext_info : NULL, + ext_info->write ? + &iio_write_channel_ext_info : NULL, + i, + ext_info->shared, + &indio_dev->dev, + &indio_dev->channel_attr_list); + i++; + if (ret == -EBUSY && ext_info->shared) + continue; + + if (ret) + goto error_ret; + + attrcount++; + } + } + + ret = attrcount; +error_ret: + return ret; +} + +static void iio_device_remove_and_free_read_attr(struct iio_dev *indio_dev, + struct iio_dev_attr *p) +{ + kfree(p->dev_attr.attr.name); + kfree(p); +} + +static ssize_t iio_show_dev_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", indio_dev->name); +} + +static DEVICE_ATTR(name, S_IRUGO, iio_show_dev_name, NULL); + +static int iio_device_register_sysfs(struct iio_dev *indio_dev) +{ + int i, ret = 0, attrcount, attrn, attrcount_orig = 0; + struct iio_dev_attr *p, *n; + struct attribute **attr; + + /* First count elements in any existing group */ + if (indio_dev->info->attrs) { + attr = indio_dev->info->attrs->attrs; + while (*attr++ != NULL) + attrcount_orig++; + } + attrcount = attrcount_orig; + /* + * New channel registration method - relies on the fact a group does + * not need to be initialized if it is name is NULL. + */ + INIT_LIST_HEAD(&indio_dev->channel_attr_list); + if (indio_dev->channels) + for (i = 0; i < indio_dev->num_channels; i++) { + ret = iio_device_add_channel_sysfs(indio_dev, + &indio_dev + ->channels[i]); + if (ret < 0) + goto error_clear_attrs; + attrcount += ret; + } + + if (indio_dev->name) + attrcount++; + + indio_dev->chan_attr_group.attrs = kcalloc(attrcount + 1, + sizeof(indio_dev->chan_attr_group.attrs[0]), + GFP_KERNEL); + if (indio_dev->chan_attr_group.attrs == NULL) { + ret = -ENOMEM; + goto error_clear_attrs; + } + /* Copy across original attributes */ + if (indio_dev->info->attrs) + memcpy(indio_dev->chan_attr_group.attrs, + indio_dev->info->attrs->attrs, + sizeof(indio_dev->chan_attr_group.attrs[0]) + *attrcount_orig); + attrn = attrcount_orig; + /* Add all elements from the list. */ + list_for_each_entry(p, &indio_dev->channel_attr_list, l) + indio_dev->chan_attr_group.attrs[attrn++] = &p->dev_attr.attr; + if (indio_dev->name) + indio_dev->chan_attr_group.attrs[attrn++] = &dev_attr_name.attr; + + indio_dev->groups[indio_dev->groupcounter++] = + &indio_dev->chan_attr_group; + + return 0; + +error_clear_attrs: + list_for_each_entry_safe(p, n, + &indio_dev->channel_attr_list, l) { + list_del(&p->l); + iio_device_remove_and_free_read_attr(indio_dev, p); + } + + return ret; +} + +static void iio_device_unregister_sysfs(struct iio_dev *indio_dev) +{ + + struct iio_dev_attr *p, *n; + + list_for_each_entry_safe(p, n, &indio_dev->channel_attr_list, l) { + list_del(&p->l); + iio_device_remove_and_free_read_attr(indio_dev, p); + } + kfree(indio_dev->chan_attr_group.attrs); +} + +static void iio_dev_release(struct device *device) +{ + struct iio_dev *indio_dev = container_of(device, struct iio_dev, dev); + cdev_del(&indio_dev->chrdev); + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + iio_device_unregister_trigger_consumer(indio_dev); + iio_device_unregister_eventset(indio_dev); + iio_device_unregister_sysfs(indio_dev); + iio_device_unregister_debugfs(indio_dev); +} + +static struct device_type iio_dev_type = { + .name = "iio_device", + .release = iio_dev_release, +}; + +struct iio_dev *iio_allocate_device(int sizeof_priv) +{ + struct iio_dev *dev; + size_t alloc_size; + + alloc_size = sizeof(struct iio_dev); + if (sizeof_priv) { + alloc_size = ALIGN(alloc_size, IIO_ALIGN); + alloc_size += sizeof_priv; + } + /* ensure 32-byte alignment of whole construct ? */ + alloc_size += IIO_ALIGN - 1; + + dev = kzalloc(alloc_size, GFP_KERNEL); + + if (dev) { + dev->dev.groups = dev->groups; + dev->dev.type = &iio_dev_type; + dev->dev.bus = &iio_bus_type; + device_initialize(&dev->dev); + dev_set_drvdata(&dev->dev, (void *)dev); + mutex_init(&dev->mlock); + mutex_init(&dev->info_exist_lock); + + dev->id = ida_simple_get(&iio_ida, 0, 0, GFP_KERNEL); + if (dev->id < 0) { + /* cannot use a dev_err as the name isn't available */ + printk(KERN_ERR "Failed to get id\n"); + kfree(dev); + return NULL; + } + dev_set_name(&dev->dev, "iio:device%d", dev->id); + } + + return dev; +} +EXPORT_SYMBOL(iio_allocate_device); + +void iio_free_device(struct iio_dev *dev) +{ + if (dev) { + ida_simple_remove(&iio_ida, dev->id); + kfree(dev); + } +} +EXPORT_SYMBOL(iio_free_device); + +/** + * iio_chrdev_open() - chrdev file open for buffer access and ioctls + **/ +static int iio_chrdev_open(struct inode *inode, struct file *filp) +{ + struct iio_dev *indio_dev = container_of(inode->i_cdev, + struct iio_dev, chrdev); + + if (test_and_set_bit(IIO_BUSY_BIT_POS, &indio_dev->flags)) + return -EBUSY; + + filp->private_data = indio_dev; + + return 0; +} + +/** + * iio_chrdev_release() - chrdev file close buffer access and ioctls + **/ +static int iio_chrdev_release(struct inode *inode, struct file *filp) +{ + struct iio_dev *indio_dev = container_of(inode->i_cdev, + struct iio_dev, chrdev); + clear_bit(IIO_BUSY_BIT_POS, &indio_dev->flags); + return 0; +} + +/* Somewhat of a cross file organization violation - ioctls here are actually + * event related */ +static long iio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct iio_dev *indio_dev = filp->private_data; + int __user *ip = (int __user *)arg; + int fd; + + if (cmd == IIO_GET_EVENT_FD_IOCTL) { + fd = iio_event_getfd(indio_dev); + if (copy_to_user(ip, &fd, sizeof(fd))) + return -EFAULT; + return 0; + } + return -EINVAL; +} + +static const struct file_operations iio_buffer_fileops = { + .read = iio_buffer_read_first_n_outer_addr, + .release = iio_chrdev_release, + .open = iio_chrdev_open, + .poll = iio_buffer_poll_addr, + .owner = THIS_MODULE, + .llseek = noop_llseek, + .unlocked_ioctl = iio_ioctl, + .compat_ioctl = iio_ioctl, +}; + +static const struct iio_buffer_setup_ops noop_ring_setup_ops; + +int iio_device_register(struct iio_dev *indio_dev) +{ + int ret; + + /* configure elements for the chrdev */ + indio_dev->dev.devt = MKDEV(MAJOR(iio_devt), indio_dev->id); + + ret = iio_device_register_debugfs(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to register debugfs interfaces\n"); + goto error_ret; + } + ret = iio_device_register_sysfs(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to register sysfs interfaces\n"); + goto error_unreg_debugfs; + } + ret = iio_device_register_eventset(indio_dev); + if (ret) { + dev_err(indio_dev->dev.parent, + "Failed to register event set\n"); + goto error_free_sysfs; + } + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + iio_device_register_trigger_consumer(indio_dev); + + if ((indio_dev->modes & INDIO_ALL_BUFFER_MODES) && + indio_dev->setup_ops == NULL) + indio_dev->setup_ops = &noop_ring_setup_ops; + + ret = device_add(&indio_dev->dev); + if (ret < 0) + goto error_unreg_eventset; + cdev_init(&indio_dev->chrdev, &iio_buffer_fileops); + indio_dev->chrdev.owner = indio_dev->info->driver_module; + ret = cdev_add(&indio_dev->chrdev, indio_dev->dev.devt, 1); + if (ret < 0) + goto error_del_device; + return 0; + +error_del_device: + device_del(&indio_dev->dev); +error_unreg_eventset: + iio_device_unregister_eventset(indio_dev); +error_free_sysfs: + iio_device_unregister_sysfs(indio_dev); +error_unreg_debugfs: + iio_device_unregister_debugfs(indio_dev); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_device_register); + +void iio_device_unregister(struct iio_dev *indio_dev) +{ + mutex_lock(&indio_dev->info_exist_lock); + indio_dev->info = NULL; + mutex_unlock(&indio_dev->info_exist_lock); + device_unregister(&indio_dev->dev); +} +EXPORT_SYMBOL(iio_device_unregister); +subsys_initcall(iio_init); +module_exit(iio_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Industrial I/O core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/industrialio-event.c b/drivers/staging/iio/industrialio-event.c new file mode 100644 index 00000000..68ef8264 --- /dev/null +++ b/drivers/staging/iio/industrialio-event.c @@ -0,0 +1,459 @@ +/* Industrial I/O event handling + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Based on elements of hwmon and input subsystems. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "iio.h" +#include "iio_core.h" +#include "sysfs.h" +#include "events.h" + +/** + * struct iio_event_interface - chrdev interface for an event line + * @wait: wait queue to allow blocking reads of events + * @det_events: list of detected events + * @dev_attr_list: list of event interface sysfs attribute + * @flags: file operations related flags including busy flag. + * @group: event interface sysfs attribute group + */ +struct iio_event_interface { + wait_queue_head_t wait; + struct mutex read_lock; + DECLARE_KFIFO(det_events, struct iio_event_data, 16); + + struct list_head dev_attr_list; + unsigned long flags; + struct attribute_group group; +}; + +int iio_push_event(struct iio_dev *indio_dev, u64 ev_code, s64 timestamp) +{ + struct iio_event_interface *ev_int = indio_dev->event_interface; + struct iio_event_data ev; + int copied; + + /* Does anyone care? */ + spin_lock(&ev_int->wait.lock); + if (test_bit(IIO_BUSY_BIT_POS, &ev_int->flags)) { + + ev.id = ev_code; + ev.timestamp = timestamp; + + copied = kfifo_put(&ev_int->det_events, &ev); + if (copied != 0) + wake_up_locked_poll(&ev_int->wait, POLLIN); + } + spin_unlock(&ev_int->wait.lock); + + return 0; +} +EXPORT_SYMBOL(iio_push_event); + +/** + * iio_event_poll() - poll the event queue to find out if it has data + */ +static unsigned int iio_event_poll(struct file *filep, + struct poll_table_struct *wait) +{ + struct iio_event_interface *ev_int = filep->private_data; + unsigned int events = 0; + + poll_wait(filep, &ev_int->wait, wait); + + spin_lock(&ev_int->wait.lock); + if (!kfifo_is_empty(&ev_int->det_events)) + events = POLLIN | POLLRDNORM; + spin_unlock(&ev_int->wait.lock); + + return events; +} + +static ssize_t iio_event_chrdev_read(struct file *filep, + char __user *buf, + size_t count, + loff_t *f_ps) +{ + struct iio_event_interface *ev_int = filep->private_data; + unsigned int copied; + int ret; + + if (count < sizeof(struct iio_event_data)) + return -EINVAL; + + if (mutex_lock_interruptible(&ev_int->read_lock)) + return -ERESTARTSYS; + + if (kfifo_is_empty(&ev_int->det_events)) { + if (filep->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + goto error_unlock; + } + /* Blocking on device; waiting for something to be there */ + ret = wait_event_interruptible(ev_int->wait, + !kfifo_is_empty(&ev_int->det_events)); + if (ret) + goto error_unlock; + /* Single access device so no one else can get the data */ + } + + ret = kfifo_to_user(&ev_int->det_events, buf, count, &copied); + +error_unlock: + mutex_unlock(&ev_int->read_lock); + + return ret ? ret : copied; +} + +static int iio_event_chrdev_release(struct inode *inode, struct file *filep) +{ + struct iio_event_interface *ev_int = filep->private_data; + + spin_lock(&ev_int->wait.lock); + __clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags); + /* + * In order to maintain a clean state for reopening, + * clear out any awaiting events. The mask will prevent + * any new __iio_push_event calls running. + */ + kfifo_reset_out(&ev_int->det_events); + spin_unlock(&ev_int->wait.lock); + + return 0; +} + +static const struct file_operations iio_event_chrdev_fileops = { + .read = iio_event_chrdev_read, + .poll = iio_event_poll, + .release = iio_event_chrdev_release, + .owner = THIS_MODULE, + .llseek = noop_llseek, +}; + +int iio_event_getfd(struct iio_dev *indio_dev) +{ + struct iio_event_interface *ev_int = indio_dev->event_interface; + int fd; + + if (ev_int == NULL) + return -ENODEV; + + spin_lock(&ev_int->wait.lock); + if (__test_and_set_bit(IIO_BUSY_BIT_POS, &ev_int->flags)) { + spin_unlock(&ev_int->wait.lock); + return -EBUSY; + } + spin_unlock(&ev_int->wait.lock); + fd = anon_inode_getfd("iio:event", + &iio_event_chrdev_fileops, ev_int, O_RDONLY); + if (fd < 0) { + spin_lock(&ev_int->wait.lock); + __clear_bit(IIO_BUSY_BIT_POS, &ev_int->flags); + spin_unlock(&ev_int->wait.lock); + } + return fd; +} + +static const char * const iio_ev_type_text[] = { + [IIO_EV_TYPE_THRESH] = "thresh", + [IIO_EV_TYPE_MAG] = "mag", + [IIO_EV_TYPE_ROC] = "roc", + [IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive", + [IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive", +}; + +static const char * const iio_ev_dir_text[] = { + [IIO_EV_DIR_EITHER] = "either", + [IIO_EV_DIR_RISING] = "rising", + [IIO_EV_DIR_FALLING] = "falling" +}; + +static ssize_t iio_ev_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + bool val; + + ret = strtobool(buf, &val); + if (ret < 0) + return ret; + + ret = indio_dev->info->write_event_config(indio_dev, + this_attr->address, + val); + return (ret < 0) ? ret : len; +} + +static ssize_t iio_ev_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int val = indio_dev->info->read_event_config(indio_dev, + this_attr->address); + + if (val < 0) + return val; + else + return sprintf(buf, "%d\n", val); +} + +static ssize_t iio_ev_value_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int val, ret; + + ret = indio_dev->info->read_event_value(indio_dev, + this_attr->address, &val); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t iio_ev_value_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long val; + int ret; + + if (!indio_dev->info->write_event_value) + return -EINVAL; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + return ret; + + ret = indio_dev->info->write_event_value(indio_dev, this_attr->address, + val); + if (ret < 0) + return ret; + + return len; +} + +static int iio_device_add_event_sysfs(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + int ret = 0, i, attrcount = 0; + u64 mask = 0; + char *postfix; + if (!chan->event_mask) + return 0; + + for_each_set_bit(i, &chan->event_mask, sizeof(chan->event_mask)*8) { + postfix = kasprintf(GFP_KERNEL, "%s_%s_en", + iio_ev_type_text[i/IIO_EV_DIR_MAX], + iio_ev_dir_text[i%IIO_EV_DIR_MAX]); + if (postfix == NULL) { + ret = -ENOMEM; + goto error_ret; + } + if (chan->modified) + mask = IIO_MOD_EVENT_CODE(chan->type, 0, chan->channel, + i/IIO_EV_DIR_MAX, + i%IIO_EV_DIR_MAX); + else if (chan->differential) + mask = IIO_EVENT_CODE(chan->type, + 0, 0, + i%IIO_EV_DIR_MAX, + i/IIO_EV_DIR_MAX, + 0, + chan->channel, + chan->channel2); + else + mask = IIO_UNMOD_EVENT_CODE(chan->type, + chan->channel, + i/IIO_EV_DIR_MAX, + i%IIO_EV_DIR_MAX); + + ret = __iio_add_chan_devattr(postfix, + chan, + &iio_ev_state_show, + iio_ev_state_store, + mask, + 0, + &indio_dev->dev, + &indio_dev->event_interface-> + dev_attr_list); + kfree(postfix); + if (ret) + goto error_ret; + attrcount++; + postfix = kasprintf(GFP_KERNEL, "%s_%s_value", + iio_ev_type_text[i/IIO_EV_DIR_MAX], + iio_ev_dir_text[i%IIO_EV_DIR_MAX]); + if (postfix == NULL) { + ret = -ENOMEM; + goto error_ret; + } + ret = __iio_add_chan_devattr(postfix, chan, + iio_ev_value_show, + iio_ev_value_store, + mask, + 0, + &indio_dev->dev, + &indio_dev->event_interface-> + dev_attr_list); + kfree(postfix); + if (ret) + goto error_ret; + attrcount++; + } + ret = attrcount; +error_ret: + return ret; +} + +static inline void __iio_remove_event_config_attrs(struct iio_dev *indio_dev) +{ + struct iio_dev_attr *p, *n; + list_for_each_entry_safe(p, n, + &indio_dev->event_interface-> + dev_attr_list, l) { + kfree(p->dev_attr.attr.name); + kfree(p); + } +} + +static inline int __iio_add_event_config_attrs(struct iio_dev *indio_dev) +{ + int j, ret, attrcount = 0; + + INIT_LIST_HEAD(&indio_dev->event_interface->dev_attr_list); + /* Dynically created from the channels array */ + for (j = 0; j < indio_dev->num_channels; j++) { + ret = iio_device_add_event_sysfs(indio_dev, + &indio_dev->channels[j]); + if (ret < 0) + goto error_clear_attrs; + attrcount += ret; + } + return attrcount; + +error_clear_attrs: + __iio_remove_event_config_attrs(indio_dev); + + return ret; +} + +static bool iio_check_for_dynamic_events(struct iio_dev *indio_dev) +{ + int j; + + for (j = 0; j < indio_dev->num_channels; j++) + if (indio_dev->channels[j].event_mask != 0) + return true; + return false; +} + +static void iio_setup_ev_int(struct iio_event_interface *ev_int) +{ + INIT_KFIFO(ev_int->det_events); + init_waitqueue_head(&ev_int->wait); + mutex_init(&ev_int->read_lock); +} + +static const char *iio_event_group_name = "events"; +int iio_device_register_eventset(struct iio_dev *indio_dev) +{ + struct iio_dev_attr *p; + int ret = 0, attrcount_orig = 0, attrcount, attrn; + struct attribute **attr; + + if (!(indio_dev->info->event_attrs || + iio_check_for_dynamic_events(indio_dev))) + return 0; + + indio_dev->event_interface = + kzalloc(sizeof(struct iio_event_interface), GFP_KERNEL); + if (indio_dev->event_interface == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + iio_setup_ev_int(indio_dev->event_interface); + if (indio_dev->info->event_attrs != NULL) { + attr = indio_dev->info->event_attrs->attrs; + while (*attr++ != NULL) + attrcount_orig++; + } + attrcount = attrcount_orig; + if (indio_dev->channels) { + ret = __iio_add_event_config_attrs(indio_dev); + if (ret < 0) + goto error_free_setup_event_lines; + attrcount += ret; + } + + indio_dev->event_interface->group.name = iio_event_group_name; + indio_dev->event_interface->group.attrs = kcalloc(attrcount + 1, + sizeof(indio_dev->event_interface->group.attrs[0]), + GFP_KERNEL); + if (indio_dev->event_interface->group.attrs == NULL) { + ret = -ENOMEM; + goto error_free_setup_event_lines; + } + if (indio_dev->info->event_attrs) + memcpy(indio_dev->event_interface->group.attrs, + indio_dev->info->event_attrs->attrs, + sizeof(indio_dev->event_interface->group.attrs[0]) + *attrcount_orig); + attrn = attrcount_orig; + /* Add all elements from the list. */ + list_for_each_entry(p, + &indio_dev->event_interface->dev_attr_list, + l) + indio_dev->event_interface->group.attrs[attrn++] = + &p->dev_attr.attr; + indio_dev->groups[indio_dev->groupcounter++] = + &indio_dev->event_interface->group; + + return 0; + +error_free_setup_event_lines: + __iio_remove_event_config_attrs(indio_dev); + mutex_destroy(&indio_dev->event_interface->read_lock); + kfree(indio_dev->event_interface); +error_ret: + + return ret; +} + +void iio_device_unregister_eventset(struct iio_dev *indio_dev) +{ + if (indio_dev->event_interface == NULL) + return; + __iio_remove_event_config_attrs(indio_dev); + kfree(indio_dev->event_interface->group.attrs); + mutex_destroy(&indio_dev->event_interface->read_lock); + kfree(indio_dev->event_interface); +} diff --git a/drivers/staging/iio/industrialio-trigger.c b/drivers/staging/iio/industrialio-trigger.c new file mode 100644 index 00000000..47ecadd4 --- /dev/null +++ b/drivers/staging/iio/industrialio-trigger.c @@ -0,0 +1,509 @@ +/* The industrial I/O core, trigger handling functions + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "iio.h" +#include "trigger.h" +#include "iio_core.h" +#include "iio_core_trigger.h" +#include "trigger_consumer.h" + +/* RFC - Question of approach + * Make the common case (single sensor single trigger) + * simple by starting trigger capture from when first sensors + * is added. + * + * Complex simultaneous start requires use of 'hold' functionality + * of the trigger. (not implemented) + * + * Any other suggestions? + */ + +static DEFINE_IDA(iio_trigger_ida); + +/* Single list of all available triggers */ +static LIST_HEAD(iio_trigger_list); +static DEFINE_MUTEX(iio_trigger_list_lock); + +/** + * iio_trigger_read_name() - retrieve useful identifying name + **/ +static ssize_t iio_trigger_read_name(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", trig->name); +} + +static DEVICE_ATTR(name, S_IRUGO, iio_trigger_read_name, NULL); + +/** + * iio_trigger_register_sysfs() - create a device for this trigger + * @trig_info: the trigger + * + * Also adds any control attribute registered by the trigger driver + **/ +static int iio_trigger_register_sysfs(struct iio_trigger *trig_info) +{ + return sysfs_add_file_to_group(&trig_info->dev.kobj, + &dev_attr_name.attr, + NULL); +} + +static void iio_trigger_unregister_sysfs(struct iio_trigger *trig_info) +{ + sysfs_remove_file_from_group(&trig_info->dev.kobj, + &dev_attr_name.attr, + NULL); +} + +int iio_trigger_register(struct iio_trigger *trig_info) +{ + int ret; + + trig_info->id = ida_simple_get(&iio_trigger_ida, 0, 0, GFP_KERNEL); + if (trig_info->id < 0) { + ret = trig_info->id; + goto error_ret; + } + /* Set the name used for the sysfs directory etc */ + dev_set_name(&trig_info->dev, "trigger%ld", + (unsigned long) trig_info->id); + + ret = device_add(&trig_info->dev); + if (ret) + goto error_unregister_id; + + ret = iio_trigger_register_sysfs(trig_info); + if (ret) + goto error_device_del; + + /* Add to list of available triggers held by the IIO core */ + mutex_lock(&iio_trigger_list_lock); + list_add_tail(&trig_info->list, &iio_trigger_list); + mutex_unlock(&iio_trigger_list_lock); + + return 0; + +error_device_del: + device_del(&trig_info->dev); +error_unregister_id: + ida_simple_remove(&iio_trigger_ida, trig_info->id); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_trigger_register); + +void iio_trigger_unregister(struct iio_trigger *trig_info) +{ + mutex_lock(&iio_trigger_list_lock); + list_del(&trig_info->list); + mutex_unlock(&iio_trigger_list_lock); + + iio_trigger_unregister_sysfs(trig_info); + ida_simple_remove(&iio_trigger_ida, trig_info->id); + /* Possible issue in here */ + device_unregister(&trig_info->dev); +} +EXPORT_SYMBOL(iio_trigger_unregister); + +static struct iio_trigger *iio_trigger_find_by_name(const char *name, + size_t len) +{ + struct iio_trigger *trig = NULL, *iter; + + mutex_lock(&iio_trigger_list_lock); + list_for_each_entry(iter, &iio_trigger_list, list) + if (sysfs_streq(iter->name, name)) { + trig = iter; + break; + } + mutex_unlock(&iio_trigger_list_lock); + + return trig; +} + +void iio_trigger_poll(struct iio_trigger *trig, s64 time) +{ + int i; + if (!trig->use_count) + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) + if (trig->subirqs[i].enabled) { + trig->use_count++; + generic_handle_irq(trig->subirq_base + i); + } +} +EXPORT_SYMBOL(iio_trigger_poll); + +irqreturn_t iio_trigger_generic_data_rdy_poll(int irq, void *private) +{ + iio_trigger_poll(private, iio_get_time_ns()); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(iio_trigger_generic_data_rdy_poll); + +void iio_trigger_poll_chained(struct iio_trigger *trig, s64 time) +{ + int i; + if (!trig->use_count) + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) + if (trig->subirqs[i].enabled) { + trig->use_count++; + handle_nested_irq(trig->subirq_base + i); + } +} +EXPORT_SYMBOL(iio_trigger_poll_chained); + +void iio_trigger_notify_done(struct iio_trigger *trig) +{ + trig->use_count--; + if (trig->use_count == 0 && trig->ops && trig->ops->try_reenable) + if (trig->ops->try_reenable(trig)) + /* Missed and interrupt so launch new poll now */ + iio_trigger_poll(trig, 0); +} +EXPORT_SYMBOL(iio_trigger_notify_done); + +/* Trigger Consumer related functions */ +static int iio_trigger_get_irq(struct iio_trigger *trig) +{ + int ret; + mutex_lock(&trig->pool_lock); + ret = bitmap_find_free_region(trig->pool, + CONFIG_IIO_CONSUMERS_PER_TRIGGER, + ilog2(1)); + mutex_unlock(&trig->pool_lock); + if (ret >= 0) + ret += trig->subirq_base; + + return ret; +} + +static void iio_trigger_put_irq(struct iio_trigger *trig, int irq) +{ + mutex_lock(&trig->pool_lock); + clear_bit(irq - trig->subirq_base, trig->pool); + mutex_unlock(&trig->pool_lock); +} + +/* Complexity in here. With certain triggers (datardy) an acknowledgement + * may be needed if the pollfuncs do not include the data read for the + * triggering device. + * This is not currently handled. Alternative of not enabling trigger unless + * the relevant function is in there may be the best option. + */ +/* Worth protecting against double additions?*/ +static int iio_trigger_attach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + int ret = 0; + bool notinuse + = bitmap_empty(trig->pool, CONFIG_IIO_CONSUMERS_PER_TRIGGER); + + /* Prevent the module being removed whilst attached to a trigger */ + __module_get(pf->indio_dev->info->driver_module); + pf->irq = iio_trigger_get_irq(trig); + ret = request_threaded_irq(pf->irq, pf->h, pf->thread, + pf->type, pf->name, + pf); + if (ret < 0) { + module_put(pf->indio_dev->info->driver_module); + return ret; + } + + if (trig->ops && trig->ops->set_trigger_state && notinuse) { + ret = trig->ops->set_trigger_state(trig, true); + if (ret < 0) + module_put(pf->indio_dev->info->driver_module); + } + + return ret; +} + +static int iio_trigger_dettach_poll_func(struct iio_trigger *trig, + struct iio_poll_func *pf) +{ + int ret = 0; + bool no_other_users + = (bitmap_weight(trig->pool, + CONFIG_IIO_CONSUMERS_PER_TRIGGER) + == 1); + if (trig->ops && trig->ops->set_trigger_state && no_other_users) { + ret = trig->ops->set_trigger_state(trig, false); + if (ret) + goto error_ret; + } + iio_trigger_put_irq(trig, pf->irq); + free_irq(pf->irq, pf); + module_put(pf->indio_dev->info->driver_module); + +error_ret: + return ret; +} + +irqreturn_t iio_pollfunc_store_time(int irq, void *p) +{ + struct iio_poll_func *pf = p; + pf->timestamp = iio_get_time_ns(); + return IRQ_WAKE_THREAD; +} +EXPORT_SYMBOL(iio_pollfunc_store_time); + +struct iio_poll_func +*iio_alloc_pollfunc(irqreturn_t (*h)(int irq, void *p), + irqreturn_t (*thread)(int irq, void *p), + int type, + struct iio_dev *indio_dev, + const char *fmt, + ...) +{ + va_list vargs; + struct iio_poll_func *pf; + + pf = kmalloc(sizeof *pf, GFP_KERNEL); + if (pf == NULL) + return NULL; + va_start(vargs, fmt); + pf->name = kvasprintf(GFP_KERNEL, fmt, vargs); + va_end(vargs); + if (pf->name == NULL) { + kfree(pf); + return NULL; + } + pf->h = h; + pf->thread = thread; + pf->type = type; + pf->indio_dev = indio_dev; + + return pf; +} +EXPORT_SYMBOL_GPL(iio_alloc_pollfunc); + +void iio_dealloc_pollfunc(struct iio_poll_func *pf) +{ + kfree(pf->name); + kfree(pf); +} +EXPORT_SYMBOL_GPL(iio_dealloc_pollfunc); + +/** + * iio_trigger_read_current() - trigger consumer sysfs query which trigger + * + * For trigger consumers the current_trigger interface allows the trigger + * used by the device to be queried. + **/ +static ssize_t iio_trigger_read_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + if (indio_dev->trig) + return sprintf(buf, "%s\n", indio_dev->trig->name); + return 0; +} + +/** + * iio_trigger_write_current() trigger consumer sysfs set current trigger + * + * For trigger consumers the current_trigger interface allows the trigger + * used for this device to be specified at run time based on the triggers + * name. + **/ +static ssize_t iio_trigger_write_current(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct iio_trigger *oldtrig = indio_dev->trig; + struct iio_trigger *trig; + int ret; + + mutex_lock(&indio_dev->mlock); + if (indio_dev->currentmode == INDIO_BUFFER_TRIGGERED) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + mutex_unlock(&indio_dev->mlock); + + trig = iio_trigger_find_by_name(buf, len); + if (oldtrig == trig) + return len; + + if (trig && indio_dev->info->validate_trigger) { + ret = indio_dev->info->validate_trigger(indio_dev, trig); + if (ret) + return ret; + } + + if (trig && trig->ops && trig->ops->validate_device) { + ret = trig->ops->validate_device(trig, indio_dev); + if (ret) + return ret; + } + + indio_dev->trig = trig; + + if (oldtrig && indio_dev->trig != oldtrig) + iio_put_trigger(oldtrig); + if (indio_dev->trig) + iio_get_trigger(indio_dev->trig); + + return len; +} + +static DEVICE_ATTR(current_trigger, S_IRUGO | S_IWUSR, + iio_trigger_read_current, + iio_trigger_write_current); + +static struct attribute *iio_trigger_consumer_attrs[] = { + &dev_attr_current_trigger.attr, + NULL, +}; + +static const struct attribute_group iio_trigger_consumer_attr_group = { + .name = "trigger", + .attrs = iio_trigger_consumer_attrs, +}; + +static void iio_trig_release(struct device *device) +{ + struct iio_trigger *trig = to_iio_trigger(device); + int i; + + if (trig->subirq_base) { + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + irq_modify_status(trig->subirq_base + i, + IRQ_NOAUTOEN, + IRQ_NOREQUEST | IRQ_NOPROBE); + irq_set_chip(trig->subirq_base + i, + NULL); + irq_set_handler(trig->subirq_base + i, + NULL); + } + + irq_free_descs(trig->subirq_base, + CONFIG_IIO_CONSUMERS_PER_TRIGGER); + } + kfree(trig->name); + kfree(trig); +} + +static struct device_type iio_trig_type = { + .release = iio_trig_release, +}; + +static void iio_trig_subirqmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_trigger *trig + = container_of(chip, + struct iio_trigger, subirq_chip); + trig->subirqs[d->irq - trig->subirq_base].enabled = false; +} + +static void iio_trig_subirqunmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_trigger *trig + = container_of(chip, + struct iio_trigger, subirq_chip); + trig->subirqs[d->irq - trig->subirq_base].enabled = true; +} + +struct iio_trigger *iio_allocate_trigger(const char *fmt, ...) +{ + va_list vargs; + struct iio_trigger *trig; + trig = kzalloc(sizeof *trig, GFP_KERNEL); + if (trig) { + int i; + trig->dev.type = &iio_trig_type; + trig->dev.bus = &iio_bus_type; + device_initialize(&trig->dev); + dev_set_drvdata(&trig->dev, (void *)trig); + + mutex_init(&trig->pool_lock); + trig->subirq_base + = irq_alloc_descs(-1, 0, + CONFIG_IIO_CONSUMERS_PER_TRIGGER, + 0); + if (trig->subirq_base < 0) { + kfree(trig); + return NULL; + } + va_start(vargs, fmt); + trig->name = kvasprintf(GFP_KERNEL, fmt, vargs); + va_end(vargs); + if (trig->name == NULL) { + irq_free_descs(trig->subirq_base, + CONFIG_IIO_CONSUMERS_PER_TRIGGER); + kfree(trig); + return NULL; + } + trig->subirq_chip.name = trig->name; + trig->subirq_chip.irq_mask = &iio_trig_subirqmask; + trig->subirq_chip.irq_unmask = &iio_trig_subirqunmask; + for (i = 0; i < CONFIG_IIO_CONSUMERS_PER_TRIGGER; i++) { + irq_set_chip(trig->subirq_base + i, + &trig->subirq_chip); + irq_set_handler(trig->subirq_base + i, + &handle_simple_irq); + irq_modify_status(trig->subirq_base + i, + IRQ_NOREQUEST | IRQ_NOAUTOEN, + IRQ_NOPROBE); + } + get_device(&trig->dev); + } + return trig; +} +EXPORT_SYMBOL(iio_allocate_trigger); + +void iio_free_trigger(struct iio_trigger *trig) +{ + if (trig) + put_device(&trig->dev); +} +EXPORT_SYMBOL(iio_free_trigger); + +void iio_device_register_trigger_consumer(struct iio_dev *indio_dev) +{ + indio_dev->groups[indio_dev->groupcounter++] = + &iio_trigger_consumer_attr_group; +} + +void iio_device_unregister_trigger_consumer(struct iio_dev *indio_dev) +{ + /* Clean up and associated but not attached triggers references */ + if (indio_dev->trig) + iio_put_trigger(indio_dev->trig); +} + +int iio_triggered_buffer_postenable(struct iio_dev *indio_dev) +{ + return iio_trigger_attach_poll_func(indio_dev->trig, + indio_dev->pollfunc); +} +EXPORT_SYMBOL(iio_triggered_buffer_postenable); + +int iio_triggered_buffer_predisable(struct iio_dev *indio_dev) +{ + return iio_trigger_dettach_poll_func(indio_dev->trig, + indio_dev->pollfunc); +} +EXPORT_SYMBOL(iio_triggered_buffer_predisable); diff --git a/drivers/staging/iio/inkern.c b/drivers/staging/iio/inkern.c new file mode 100644 index 00000000..ef07a02b --- /dev/null +++ b/drivers/staging/iio/inkern.c @@ -0,0 +1,293 @@ +/* The industrial I/O core in kernel channel mapping + * + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#include +#include +#include +#include + +#include "iio.h" +#include "iio_core.h" +#include "machine.h" +#include "driver.h" +#include "consumer.h" + +struct iio_map_internal { + struct iio_dev *indio_dev; + struct iio_map *map; + struct list_head l; +}; + +static LIST_HEAD(iio_map_list); +static DEFINE_MUTEX(iio_map_list_lock); + +int iio_map_array_register(struct iio_dev *indio_dev, struct iio_map *maps) +{ + int i = 0, ret = 0; + struct iio_map_internal *mapi; + + if (maps == NULL) + return 0; + + mutex_lock(&iio_map_list_lock); + while (maps[i].consumer_dev_name != NULL) { + mapi = kzalloc(sizeof(*mapi), GFP_KERNEL); + if (mapi == NULL) { + ret = -ENOMEM; + goto error_ret; + } + mapi->map = &maps[i]; + mapi->indio_dev = indio_dev; + list_add(&mapi->l, &iio_map_list); + i++; + } +error_ret: + mutex_unlock(&iio_map_list_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_map_array_register); + + +/* Assumes the exact same array (e.g. memory locations) + * used at unregistration as used at registration rather than + * more complex checking of contents. + */ +int iio_map_array_unregister(struct iio_dev *indio_dev, + struct iio_map *maps) +{ + int i = 0, ret = 0; + bool found_it; + struct iio_map_internal *mapi; + + if (maps == NULL) + return 0; + + mutex_lock(&iio_map_list_lock); + while (maps[i].consumer_dev_name != NULL) { + found_it = false; + list_for_each_entry(mapi, &iio_map_list, l) + if (&maps[i] == mapi->map) { + list_del(&mapi->l); + kfree(mapi); + found_it = true; + break; + } + if (found_it == false) { + ret = -ENODEV; + goto error_ret; + } + i++; + } +error_ret: + mutex_unlock(&iio_map_list_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_map_array_unregister); + +static const struct iio_chan_spec +*iio_chan_spec_from_name(const struct iio_dev *indio_dev, + const char *name) +{ + int i; + const struct iio_chan_spec *chan = NULL; + + for (i = 0; i < indio_dev->num_channels; i++) + if (indio_dev->channels[i].datasheet_name && + strcmp(name, indio_dev->channels[i].datasheet_name) == 0) { + chan = &indio_dev->channels[i]; + break; + } + return chan; +} + + +struct iio_channel *iio_st_channel_get(const char *name, + const char *channel_name) +{ + struct iio_map_internal *c_i = NULL, *c = NULL; + struct iio_channel *channel; + + if (name == NULL && channel_name == NULL) + return ERR_PTR(-ENODEV); + + /* first find matching entry the channel map */ + mutex_lock(&iio_map_list_lock); + list_for_each_entry(c_i, &iio_map_list, l) { + if ((name && strcmp(name, c_i->map->consumer_dev_name) != 0) || + (channel_name && + strcmp(channel_name, c_i->map->consumer_channel) != 0)) + continue; + c = c_i; + get_device(&c->indio_dev->dev); + break; + } + mutex_unlock(&iio_map_list_lock); + if (c == NULL) + return ERR_PTR(-ENODEV); + + channel = kmalloc(sizeof(*channel), GFP_KERNEL); + if (channel == NULL) + return ERR_PTR(-ENOMEM); + + channel->indio_dev = c->indio_dev; + + if (c->map->adc_channel_label) + channel->channel = + iio_chan_spec_from_name(channel->indio_dev, + c->map->adc_channel_label); + + return channel; +} +EXPORT_SYMBOL_GPL(iio_st_channel_get); + +void iio_st_channel_release(struct iio_channel *channel) +{ + put_device(&channel->indio_dev->dev); + kfree(channel); +} +EXPORT_SYMBOL_GPL(iio_st_channel_release); + +struct iio_channel *iio_st_channel_get_all(const char *name) +{ + struct iio_channel *chans; + struct iio_map_internal *c = NULL; + int nummaps = 0; + int mapind = 0; + int i, ret; + + if (name == NULL) + return ERR_PTR(-EINVAL); + + mutex_lock(&iio_map_list_lock); + /* first count the matching maps */ + list_for_each_entry(c, &iio_map_list, l) + if (name && strcmp(name, c->map->consumer_dev_name) != 0) + continue; + else + nummaps++; + + if (nummaps == 0) { + ret = -ENODEV; + goto error_ret; + } + + /* NULL terminated array to save passing size */ + chans = kzalloc(sizeof(*chans)*(nummaps + 1), GFP_KERNEL); + if (chans == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + /* for each map fill in the chans element */ + list_for_each_entry(c, &iio_map_list, l) { + if (name && strcmp(name, c->map->consumer_dev_name) != 0) + continue; + chans[mapind].indio_dev = c->indio_dev; + chans[mapind].channel = + iio_chan_spec_from_name(chans[mapind].indio_dev, + c->map->adc_channel_label); + if (chans[mapind].channel == NULL) { + ret = -EINVAL; + put_device(&chans[mapind].indio_dev->dev); + goto error_free_chans; + } + get_device(&chans[mapind].indio_dev->dev); + mapind++; + } + mutex_unlock(&iio_map_list_lock); + if (mapind == 0) { + ret = -ENODEV; + goto error_free_chans; + } + return chans; + +error_free_chans: + for (i = 0; i < nummaps; i++) + if (chans[i].indio_dev) + put_device(&chans[i].indio_dev->dev); + kfree(chans); +error_ret: + mutex_unlock(&iio_map_list_lock); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(iio_st_channel_get_all); + +void iio_st_channel_release_all(struct iio_channel *channels) +{ + struct iio_channel *chan = &channels[0]; + + while (chan->indio_dev) { + put_device(&chan->indio_dev->dev); + chan++; + } + kfree(channels); +} +EXPORT_SYMBOL_GPL(iio_st_channel_release_all); + +int iio_st_read_channel_raw(struct iio_channel *chan, int *val) +{ + int val2, ret; + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + ret = chan->indio_dev->info->read_raw(chan->indio_dev, chan->channel, + val, &val2, 0); +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_st_read_channel_raw); + +int iio_st_read_channel_scale(struct iio_channel *chan, int *val, int *val2) +{ + int ret; + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + ret = chan->indio_dev->info->read_raw(chan->indio_dev, + chan->channel, + val, val2, + IIO_CHAN_INFO_SCALE); +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_st_read_channel_scale); + +int iio_st_get_channel_type(struct iio_channel *chan, + enum iio_chan_type *type) +{ + int ret = 0; + /* Need to verify underlying driver has not gone away */ + + mutex_lock(&chan->indio_dev->info_exist_lock); + if (chan->indio_dev->info == NULL) { + ret = -ENODEV; + goto err_unlock; + } + + *type = chan->channel->type; +err_unlock: + mutex_unlock(&chan->indio_dev->info_exist_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_st_get_channel_type); diff --git a/drivers/staging/iio/kfifo_buf.c b/drivers/staging/iio/kfifo_buf.c new file mode 100644 index 00000000..9f3bd59c --- /dev/null +++ b/drivers/staging/iio/kfifo_buf.c @@ -0,0 +1,151 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "kfifo_buf.h" + +struct iio_kfifo { + struct iio_buffer buffer; + struct kfifo kf; + int update_needed; +}; + +#define iio_to_kfifo(r) container_of(r, struct iio_kfifo, buffer) + +static inline int __iio_allocate_kfifo(struct iio_kfifo *buf, + int bytes_per_datum, int length) +{ + if ((length == 0) || (bytes_per_datum == 0)) + return -EINVAL; + + __iio_update_buffer(&buf->buffer, bytes_per_datum, length); + return kfifo_alloc(&buf->kf, bytes_per_datum*length, GFP_KERNEL); +} + +static int iio_request_update_kfifo(struct iio_buffer *r) +{ + int ret = 0; + struct iio_kfifo *buf = iio_to_kfifo(r); + + if (!buf->update_needed) + goto error_ret; + kfifo_free(&buf->kf); + ret = __iio_allocate_kfifo(buf, buf->buffer.bytes_per_datum, + buf->buffer.length); +error_ret: + return ret; +} + +static int iio_get_length_kfifo(struct iio_buffer *r) +{ + return r->length; +} + +static IIO_BUFFER_ENABLE_ATTR; +static IIO_BUFFER_LENGTH_ATTR; + +static struct attribute *iio_kfifo_attributes[] = { + &dev_attr_length.attr, + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group iio_kfifo_attribute_group = { + .attrs = iio_kfifo_attributes, + .name = "buffer", +}; + +static int iio_get_bytes_per_datum_kfifo(struct iio_buffer *r) +{ + return r->bytes_per_datum; +} + +static int iio_mark_update_needed_kfifo(struct iio_buffer *r) +{ + struct iio_kfifo *kf = iio_to_kfifo(r); + kf->update_needed = true; + return 0; +} + +static int iio_set_bytes_per_datum_kfifo(struct iio_buffer *r, size_t bpd) +{ + if (r->bytes_per_datum != bpd) { + r->bytes_per_datum = bpd; + iio_mark_update_needed_kfifo(r); + } + return 0; +} + +static int iio_set_length_kfifo(struct iio_buffer *r, int length) +{ + if (r->length != length) { + r->length = length; + iio_mark_update_needed_kfifo(r); + } + return 0; +} + +static int iio_store_to_kfifo(struct iio_buffer *r, + u8 *data, + s64 timestamp) +{ + int ret; + struct iio_kfifo *kf = iio_to_kfifo(r); + ret = kfifo_in(&kf->kf, data, r->bytes_per_datum); + if (ret != r->bytes_per_datum) + return -EBUSY; + return 0; +} + +static int iio_read_first_n_kfifo(struct iio_buffer *r, + size_t n, char __user *buf) +{ + int ret, copied; + struct iio_kfifo *kf = iio_to_kfifo(r); + + if (n < r->bytes_per_datum) + return -EINVAL; + + n = rounddown(n, r->bytes_per_datum); + ret = kfifo_to_user(&kf->kf, buf, n, &copied); + + return copied; +} + +static const struct iio_buffer_access_funcs kfifo_access_funcs = { + .store_to = &iio_store_to_kfifo, + .read_first_n = &iio_read_first_n_kfifo, + .request_update = &iio_request_update_kfifo, + .get_bytes_per_datum = &iio_get_bytes_per_datum_kfifo, + .set_bytes_per_datum = &iio_set_bytes_per_datum_kfifo, + .get_length = &iio_get_length_kfifo, + .set_length = &iio_set_length_kfifo, +}; + +struct iio_buffer *iio_kfifo_allocate(struct iio_dev *indio_dev) +{ + struct iio_kfifo *kf; + + kf = kzalloc(sizeof *kf, GFP_KERNEL); + if (!kf) + return NULL; + kf->update_needed = true; + iio_buffer_init(&kf->buffer); + kf->buffer.attrs = &iio_kfifo_attribute_group; + kf->buffer.access = &kfifo_access_funcs; + + return &kf->buffer; +} +EXPORT_SYMBOL(iio_kfifo_allocate); + +void iio_kfifo_free(struct iio_buffer *r) +{ + kfree(iio_to_kfifo(r)); +} +EXPORT_SYMBOL(iio_kfifo_free); + +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/kfifo_buf.h b/drivers/staging/iio/kfifo_buf.h new file mode 100644 index 00000000..9f7da016 --- /dev/null +++ b/drivers/staging/iio/kfifo_buf.h @@ -0,0 +1,8 @@ + +#include +#include "iio.h" +#include "buffer.h" + +struct iio_buffer *iio_kfifo_allocate(struct iio_dev *indio_dev); +void iio_kfifo_free(struct iio_buffer *r); + diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig new file mode 100644 index 00000000..e7e9159d --- /dev/null +++ b/drivers/staging/iio/light/Kconfig @@ -0,0 +1,34 @@ +# +# Light sensors +# +menu "Light sensors" + +config SENSORS_ISL29018 + tristate "ISL 29018 light and proximity sensor" + depends on I2C + default n + help + If you say yes here you get support for ambient light sensing and + proximity infrared sensing from Intersil ISL29018. + This driver will provide the measurements of ambient light intensity + in lux, proximity infrared sensing and normal infrared sensing. + Data from sensor is accessible via sysfs. + +config SENSORS_TSL2563 + tristate "TAOS TSL2560, TSL2561, TSL2562 and TSL2563 ambient light sensors" + depends on I2C + help + If you say yes here you get support for the Taos TSL2560, + TSL2561, TSL2562 and TSL2563 ambient light sensors. + + This driver can also be built as a module. If so, the module + will be called tsl2563. + +config TSL2583 + tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters" + depends on I2C + help + Provides support for the TAOS tsl2580, tsl2581 and tsl2583 devices. + Access ALS data via iio, sysfs. + +endmenu diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile new file mode 100644 index 00000000..3011fbfa --- /dev/null +++ b/drivers/staging/iio/light/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for industrial I/O Light sensors +# + +obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o +obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o +obj-$(CONFIG_TSL2583) += tsl2583.o diff --git a/drivers/staging/iio/light/isl29018.c b/drivers/staging/iio/light/isl29018.c new file mode 100644 index 00000000..38ec52b6 --- /dev/null +++ b/drivers/staging/iio/light/isl29018.c @@ -0,0 +1,615 @@ +/* + * A iio driver for the light sensor ISL 29018. + * + * IIO driver for monitoring ambient light intensity in luxi, proximity + * sensing and infrared sensing. + * + * Copyright (c) 2010, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include "../iio.h" +#include "../sysfs.h" +#define CONVERSION_TIME_MS 100 + +#define ISL29018_REG_ADD_COMMAND1 0x00 +#define COMMMAND1_OPMODE_SHIFT 5 +#define COMMMAND1_OPMODE_MASK (7 << COMMMAND1_OPMODE_SHIFT) +#define COMMMAND1_OPMODE_POWER_DOWN 0 +#define COMMMAND1_OPMODE_ALS_ONCE 1 +#define COMMMAND1_OPMODE_IR_ONCE 2 +#define COMMMAND1_OPMODE_PROX_ONCE 3 + +#define ISL29018_REG_ADD_COMMANDII 0x01 +#define COMMANDII_RESOLUTION_SHIFT 2 +#define COMMANDII_RESOLUTION_MASK (0x3 << COMMANDII_RESOLUTION_SHIFT) + +#define COMMANDII_RANGE_SHIFT 0 +#define COMMANDII_RANGE_MASK (0x3 << COMMANDII_RANGE_SHIFT) + +#define COMMANDII_SCHEME_SHIFT 7 +#define COMMANDII_SCHEME_MASK (0x1 << COMMANDII_SCHEME_SHIFT) + +#define ISL29018_REG_ADD_DATA_LSB 0x02 +#define ISL29018_REG_ADD_DATA_MSB 0x03 +#define ISL29018_MAX_REGS (ISL29018_REG_ADD_DATA_MSB+1) + +#define ISL29018_REG_TEST 0x08 +#define ISL29018_TEST_SHIFT 0 +#define ISL29018_TEST_MASK (0xFF << ISL29018_TEST_SHIFT) + +struct isl29018_chip { + struct i2c_client *client; + struct mutex lock; + unsigned int lux_scale; + unsigned int range; + unsigned int adc_bit; + int prox_scheme; + u8 reg_cache[ISL29018_MAX_REGS]; +}; + +static int isl29018_write_data(struct i2c_client *client, u8 reg, + u8 val, u8 mask, u8 shift) +{ + u8 regval = val; + int ret; + struct isl29018_chip *chip = iio_priv(i2c_get_clientdata(client)); + + /* don't cache or mask REG_TEST */ + if (reg < ISL29018_MAX_REGS) { + regval = chip->reg_cache[reg]; + regval &= ~mask; + regval |= val << shift; + } + + ret = i2c_smbus_write_byte_data(client, reg, regval); + if (ret) { + dev_err(&client->dev, "Write to device fails status %x\n", ret); + } else { + /* don't update cache on err */ + if (reg < ISL29018_MAX_REGS) + chip->reg_cache[reg] = regval; + } + + return ret; +} + +static int isl29018_set_range(struct i2c_client *client, unsigned long range, + unsigned int *new_range) +{ + static const unsigned long supp_ranges[] = {1000, 4000, 16000, 64000}; + int i; + + for (i = 0; i < ARRAY_SIZE(supp_ranges); ++i) { + if (range <= supp_ranges[i]) { + *new_range = (unsigned int)supp_ranges[i]; + break; + } + } + + if (i >= ARRAY_SIZE(supp_ranges)) + return -EINVAL; + + return isl29018_write_data(client, ISL29018_REG_ADD_COMMANDII, + i, COMMANDII_RANGE_MASK, COMMANDII_RANGE_SHIFT); +} + +static int isl29018_set_resolution(struct i2c_client *client, + unsigned long adcbit, unsigned int *conf_adc_bit) +{ + static const unsigned long supp_adcbit[] = {16, 12, 8, 4}; + int i; + + for (i = 0; i < ARRAY_SIZE(supp_adcbit); ++i) { + if (adcbit >= supp_adcbit[i]) { + *conf_adc_bit = (unsigned int)supp_adcbit[i]; + break; + } + } + + if (i >= ARRAY_SIZE(supp_adcbit)) + return -EINVAL; + + return isl29018_write_data(client, ISL29018_REG_ADD_COMMANDII, + i, COMMANDII_RESOLUTION_MASK, + COMMANDII_RESOLUTION_SHIFT); +} + +static int isl29018_read_sensor_input(struct i2c_client *client, int mode) +{ + int status; + int lsb; + int msb; + + /* Set mode */ + status = isl29018_write_data(client, ISL29018_REG_ADD_COMMAND1, + mode, COMMMAND1_OPMODE_MASK, COMMMAND1_OPMODE_SHIFT); + if (status) { + dev_err(&client->dev, "Error in setting operating mode\n"); + return status; + } + msleep(CONVERSION_TIME_MS); + lsb = i2c_smbus_read_byte_data(client, ISL29018_REG_ADD_DATA_LSB); + if (lsb < 0) { + dev_err(&client->dev, "Error in reading LSB DATA\n"); + return lsb; + } + + msb = i2c_smbus_read_byte_data(client, ISL29018_REG_ADD_DATA_MSB); + if (msb < 0) { + dev_err(&client->dev, "Error in reading MSB DATA\n"); + return msb; + } + dev_vdbg(&client->dev, "MSB 0x%x and LSB 0x%x\n", msb, lsb); + + return (msb << 8) | lsb; +} + +static int isl29018_read_lux(struct i2c_client *client, int *lux) +{ + int lux_data; + struct isl29018_chip *chip = iio_priv(i2c_get_clientdata(client)); + + lux_data = isl29018_read_sensor_input(client, + COMMMAND1_OPMODE_ALS_ONCE); + + if (lux_data < 0) + return lux_data; + + *lux = (lux_data * chip->range * chip->lux_scale) >> chip->adc_bit; + + return 0; +} + +static int isl29018_read_ir(struct i2c_client *client, int *ir) +{ + int ir_data; + + ir_data = isl29018_read_sensor_input(client, COMMMAND1_OPMODE_IR_ONCE); + + if (ir_data < 0) + return ir_data; + + *ir = ir_data; + + return 0; +} + +static int isl29018_read_proximity_ir(struct i2c_client *client, int scheme, + int *near_ir) +{ + int status; + int prox_data = -1; + int ir_data = -1; + + /* Do proximity sensing with required scheme */ + status = isl29018_write_data(client, ISL29018_REG_ADD_COMMANDII, + scheme, COMMANDII_SCHEME_MASK, COMMANDII_SCHEME_SHIFT); + if (status) { + dev_err(&client->dev, "Error in setting operating mode\n"); + return status; + } + + prox_data = isl29018_read_sensor_input(client, + COMMMAND1_OPMODE_PROX_ONCE); + if (prox_data < 0) + return prox_data; + + if (scheme == 1) { + *near_ir = prox_data; + return 0; + } + + ir_data = isl29018_read_sensor_input(client, + COMMMAND1_OPMODE_IR_ONCE); + + if (ir_data < 0) + return ir_data; + + if (prox_data >= ir_data) + *near_ir = prox_data - ir_data; + else + *near_ir = 0; + + return 0; +} + +/* Sysfs interface */ +/* range */ +static ssize_t show_range(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", chip->range); +} + +static ssize_t store_range(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int status; + unsigned long lval; + unsigned int new_range; + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + if (!(lval == 1000UL || lval == 4000UL || + lval == 16000UL || lval == 64000UL)) { + dev_err(dev, "The range is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + status = isl29018_set_range(client, lval, &new_range); + if (status < 0) { + mutex_unlock(&chip->lock); + dev_err(dev, "Error in setting max range\n"); + return status; + } + chip->range = new_range; + mutex_unlock(&chip->lock); + + return count; +} + +/* resolution */ +static ssize_t show_resolution(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", chip->adc_bit); +} + +static ssize_t store_resolution(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + int status; + unsigned long lval; + unsigned int new_adc_bit; + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + if (!(lval == 4 || lval == 8 || lval == 12 || lval == 16)) { + dev_err(dev, "The resolution is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + status = isl29018_set_resolution(client, lval, &new_adc_bit); + if (status < 0) { + mutex_unlock(&chip->lock); + dev_err(dev, "Error in setting resolution\n"); + return status; + } + chip->adc_bit = new_adc_bit; + mutex_unlock(&chip->lock); + + return count; +} + +/* proximity scheme */ +static ssize_t show_prox_infrared_supression(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + + /* return the "proximity scheme" i.e. if the chip does on chip + infrared supression (1 means perform on chip supression) */ + return sprintf(buf, "%d\n", chip->prox_scheme); +} + +static ssize_t store_prox_infrared_supression(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29018_chip *chip = iio_priv(indio_dev); + unsigned long lval; + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + if (!(lval == 0UL || lval == 1UL)) { + dev_err(dev, "The mode is not supported\n"); + return -EINVAL; + } + + /* get the "proximity scheme" i.e. if the chip does on chip + infrared supression (1 means perform on chip supression) */ + mutex_lock(&chip->lock); + chip->prox_scheme = (int)lval; + mutex_unlock(&chip->lock); + + return count; +} + +/* Channel IO */ +static int isl29018_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct isl29018_chip *chip = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&chip->lock); + if (mask == IIO_CHAN_INFO_CALIBSCALE && chan->type == IIO_LIGHT) { + chip->lux_scale = val; + ret = 0; + } + mutex_unlock(&chip->lock); + + return 0; +} + +static int isl29018_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + int ret = -EINVAL; + struct isl29018_chip *chip = iio_priv(indio_dev); + struct i2c_client *client = chip->client; + + mutex_lock(&chip->lock); + switch (mask) { + case 0: + switch (chan->type) { + case IIO_LIGHT: + ret = isl29018_read_lux(client, val); + break; + case IIO_INTENSITY: + ret = isl29018_read_ir(client, val); + break; + case IIO_PROXIMITY: + ret = isl29018_read_proximity_ir(client, + chip->prox_scheme, val); + break; + default: + break; + } + if (!ret) + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->type == IIO_LIGHT) { + *val = chip->lux_scale; + ret = IIO_VAL_INT; + } + break; + default: + break; + } + mutex_unlock(&chip->lock); + return ret; +} + +static const struct iio_chan_spec isl29018_channels[] = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + .processed_val = IIO_PROCESSED, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT, + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + }, { + /* Unindexed in current ABI. But perhaps it should be. */ + .type = IIO_PROXIMITY, + } +}; + +static IIO_DEVICE_ATTR(range, S_IRUGO | S_IWUSR, show_range, store_range, 0); +static IIO_CONST_ATTR(range_available, "1000 4000 16000 64000"); +static IIO_CONST_ATTR(adc_resolution_available, "4 8 12 16"); +static IIO_DEVICE_ATTR(adc_resolution, S_IRUGO | S_IWUSR, + show_resolution, store_resolution, 0); +static IIO_DEVICE_ATTR(proximity_on_chip_ambient_infrared_supression, + S_IRUGO | S_IWUSR, + show_prox_infrared_supression, + store_prox_infrared_supression, 0); + +#define ISL29018_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr) +#define ISL29018_CONST_ATTR(name) (&iio_const_attr_##name.dev_attr.attr) +static struct attribute *isl29018_attributes[] = { + ISL29018_DEV_ATTR(range), + ISL29018_CONST_ATTR(range_available), + ISL29018_DEV_ATTR(adc_resolution), + ISL29018_CONST_ATTR(adc_resolution_available), + ISL29018_DEV_ATTR(proximity_on_chip_ambient_infrared_supression), + NULL +}; + +static const struct attribute_group isl29108_group = { + .attrs = isl29018_attributes, +}; + +static int isl29018_chip_init(struct i2c_client *client) +{ + struct isl29018_chip *chip = iio_priv(i2c_get_clientdata(client)); + int status; + int new_adc_bit; + unsigned int new_range; + + memset(chip->reg_cache, 0, sizeof(chip->reg_cache)); + + /* Code added per Intersil Application Note 1534: + * When VDD sinks to approximately 1.8V or below, some of + * the part's registers may change their state. When VDD + * recovers to 2.25V (or greater), the part may thus be in an + * unknown mode of operation. The user can return the part to + * a known mode of operation either by (a) setting VDD = 0V for + * 1 second or more and then powering back up with a slew rate + * of 0.5V/ms or greater, or (b) via I2C disable all ALS/PROX + * conversions, clear the test registers, and then rewrite all + * registers to the desired values. + * ... + * FOR ISL29011, ISL29018, ISL29021, ISL29023 + * 1. Write 0x00 to register 0x08 (TEST) + * 2. Write 0x00 to register 0x00 (CMD1) + * 3. Rewrite all registers to the desired values + * + * ISL29018 Data Sheet (FN6619.1, Feb 11, 2010) essentially says + * the same thing EXCEPT the data sheet asks for a 1ms delay after + * writing the CMD1 register. + */ + status = isl29018_write_data(client, ISL29018_REG_TEST, 0, + ISL29018_TEST_MASK, ISL29018_TEST_SHIFT); + if (status < 0) { + dev_err(&client->dev, "Failed to clear isl29018 TEST reg." + "(%d)\n", status); + return status; + } + + /* See Intersil AN1534 comments above. + * "Operating Mode" (COMMAND1) register is reprogrammed when + * data is read from the device. + */ + status = isl29018_write_data(client, ISL29018_REG_ADD_COMMAND1, 0, + 0xff, 0); + if (status < 0) { + dev_err(&client->dev, "Failed to clear isl29018 CMD1 reg." + "(%d)\n", status); + return status; + } + + msleep(1); /* per data sheet, page 10 */ + + /* set defaults */ + status = isl29018_set_range(client, chip->range, &new_range); + if (status < 0) { + dev_err(&client->dev, "Init of isl29018 fails\n"); + return status; + } + + status = isl29018_set_resolution(client, chip->adc_bit, + &new_adc_bit); + + return 0; +} + +static const struct iio_info isl29108_info = { + .attrs = &isl29108_group, + .driver_module = THIS_MODULE, + .read_raw = &isl29018_read_raw, + .write_raw = &isl29018_write_raw, +}; + +static int __devinit isl29018_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isl29018_chip *chip; + struct iio_dev *indio_dev; + int err; + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + dev_err(&client->dev, "iio allocation fails\n"); + err = -ENOMEM; + goto exit; + } + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, indio_dev); + chip->client = client; + + mutex_init(&chip->lock); + + chip->lux_scale = 1; + chip->range = 1000; + chip->adc_bit = 16; + + err = isl29018_chip_init(client); + if (err) + goto exit_iio_free; + + indio_dev->info = &isl29108_info; + indio_dev->channels = isl29018_channels; + indio_dev->num_channels = ARRAY_SIZE(isl29018_channels); + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + err = iio_device_register(indio_dev); + if (err) { + dev_err(&client->dev, "iio registration fails\n"); + goto exit_iio_free; + } + + return 0; +exit_iio_free: + iio_free_device(indio_dev); +exit: + return err; +} + +static int __devexit isl29018_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "%s()\n", __func__); + iio_device_unregister(indio_dev); + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id isl29018_id[] = { + {"isl29018", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, isl29018_id); + +static const struct of_device_id isl29018_of_match[] = { + { .compatible = "invn,isl29018", }, + { }, +}; +MODULE_DEVICE_TABLE(of, isl29018_of_match); + +static struct i2c_driver isl29018_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "isl29018", + .owner = THIS_MODULE, + .of_match_table = isl29018_of_match, + }, + .probe = isl29018_probe, + .remove = __devexit_p(isl29018_remove), + .id_table = isl29018_id, +}; +module_i2c_driver(isl29018_driver); + +MODULE_DESCRIPTION("ISL29018 Ambient Light Sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/light/tsl2563.c b/drivers/staging/iio/light/tsl2563.c new file mode 100644 index 00000000..546c95a4 --- /dev/null +++ b/drivers/staging/iio/light/tsl2563.c @@ -0,0 +1,895 @@ +/* + * drivers/i2c/chips/tsl2563.c + * + * Copyright (C) 2008 Nokia Corporation + * + * Written by Timo O. Karjalainen + * Contact: Amit Kucheria + * + * Converted to IIO driver + * Amit Kucheria + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../events.h" +#include "tsl2563.h" + +/* Use this many bits for fraction part. */ +#define ADC_FRAC_BITS (14) + +/* Given number of 1/10000's in ADC_FRAC_BITS precision. */ +#define FRAC10K(f) (((f) * (1L << (ADC_FRAC_BITS))) / (10000)) + +/* Bits used for fraction in calibration coefficients.*/ +#define CALIB_FRAC_BITS (10) +/* 0.5 in CALIB_FRAC_BITS precision */ +#define CALIB_FRAC_HALF (1 << (CALIB_FRAC_BITS - 1)) +/* Make a fraction from a number n that was multiplied with b. */ +#define CALIB_FRAC(n, b) (((n) << CALIB_FRAC_BITS) / (b)) +/* Decimal 10^(digits in sysfs presentation) */ +#define CALIB_BASE_SYSFS (1000) + +#define TSL2563_CMD (0x80) +#define TSL2563_CLEARINT (0x40) + +#define TSL2563_REG_CTRL (0x00) +#define TSL2563_REG_TIMING (0x01) +#define TSL2563_REG_LOWLOW (0x02) /* data0 low threshold, 2 bytes */ +#define TSL2563_REG_LOWHIGH (0x03) +#define TSL2563_REG_HIGHLOW (0x04) /* data0 high threshold, 2 bytes */ +#define TSL2563_REG_HIGHHIGH (0x05) +#define TSL2563_REG_INT (0x06) +#define TSL2563_REG_ID (0x0a) +#define TSL2563_REG_DATA0LOW (0x0c) /* broadband sensor value, 2 bytes */ +#define TSL2563_REG_DATA0HIGH (0x0d) +#define TSL2563_REG_DATA1LOW (0x0e) /* infrared sensor value, 2 bytes */ +#define TSL2563_REG_DATA1HIGH (0x0f) + +#define TSL2563_CMD_POWER_ON (0x03) +#define TSL2563_CMD_POWER_OFF (0x00) +#define TSL2563_CTRL_POWER_MASK (0x03) + +#define TSL2563_TIMING_13MS (0x00) +#define TSL2563_TIMING_100MS (0x01) +#define TSL2563_TIMING_400MS (0x02) +#define TSL2563_TIMING_MASK (0x03) +#define TSL2563_TIMING_GAIN16 (0x10) +#define TSL2563_TIMING_GAIN1 (0x00) + +#define TSL2563_INT_DISBLED (0x00) +#define TSL2563_INT_LEVEL (0x10) +#define TSL2563_INT_PERSIST(n) ((n) & 0x0F) + +struct tsl2563_gainlevel_coeff { + u8 gaintime; + u16 min; + u16 max; +}; + +static const struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = { + { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16, + .min = 0, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1, + .min = 2048, + .max = 65534, + }, { + .gaintime = TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1, + .min = 4095, + .max = 37177, + }, { + .gaintime = TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1, + .min = 3000, + .max = 65535, + }, +}; + +struct tsl2563_chip { + struct mutex lock; + struct i2c_client *client; + struct delayed_work poweroff_work; + + /* Remember state for suspend and resume functions */ + bool suspended; + + struct tsl2563_gainlevel_coeff const *gainlevel; + + u16 low_thres; + u16 high_thres; + u8 intr; + bool int_enabled; + + /* Calibration coefficients */ + u32 calib0; + u32 calib1; + int cover_comp_gain; + + /* Cache current values, to be returned while suspended */ + u32 data0; + u32 data1; +}; + +static int tsl2563_set_power(struct tsl2563_chip *chip, int on) +{ + struct i2c_client *client = chip->client; + u8 cmd; + + cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF; + return i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_CTRL, cmd); +} + +/* + * Return value is 0 for off, 1 for on, or a negative error + * code if reading failed. + */ +static int tsl2563_get_power(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_CTRL); + if (ret < 0) + return ret; + + return (ret & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON; +} + +static int tsl2563_configure(struct tsl2563_chip *chip) +{ + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGHLOW, + chip->high_thres & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_HIGHHIGH, + (chip->high_thres >> 8) & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOWLOW, + chip->low_thres & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_LOWHIGH, + (chip->low_thres >> 8) & 0xFF); +/* Interrupt register is automatically written anyway if it is relevant + so is not here */ +error_ret: + return ret; +} + +static void tsl2563_poweroff_work(struct work_struct *work) +{ + struct tsl2563_chip *chip = + container_of(work, struct tsl2563_chip, poweroff_work.work); + tsl2563_set_power(chip, 0); +} + +static int tsl2563_detect(struct tsl2563_chip *chip) +{ + int ret; + + ret = tsl2563_set_power(chip, 1); + if (ret) + return ret; + + ret = tsl2563_get_power(chip); + if (ret < 0) + return ret; + + return ret ? 0 : -ENODEV; +} + +static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id) +{ + struct i2c_client *client = chip->client; + int ret; + + ret = i2c_smbus_read_byte_data(client, TSL2563_CMD | TSL2563_REG_ID); + if (ret < 0) + return ret; + + *id = ret; + + return 0; +} + +/* + * "Normalized" ADC value is one obtained with 400ms of integration time and + * 16x gain. This function returns the number of bits of shift needed to + * convert between normalized values and HW values obtained using given + * timing and gain settings. + */ +static int adc_shiftbits(u8 timing) +{ + int shift = 0; + + switch (timing & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + shift += 5; + break; + case TSL2563_TIMING_100MS: + shift += 2; + break; + case TSL2563_TIMING_400MS: + /* no-op */ + break; + } + + if (!(timing & TSL2563_TIMING_GAIN16)) + shift += 4; + + return shift; +} + +/* Convert a HW ADC value to normalized scale. */ +static u32 normalize_adc(u16 adc, u8 timing) +{ + return adc << adc_shiftbits(timing); +} + +static void tsl2563_wait_adc(struct tsl2563_chip *chip) +{ + unsigned int delay; + + switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) { + case TSL2563_TIMING_13MS: + delay = 14; + break; + case TSL2563_TIMING_100MS: + delay = 101; + break; + default: + delay = 402; + } + /* + * TODO: Make sure that we wait at least required delay but why we + * have to extend it one tick more? + */ + schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2); +} + +static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc) +{ + struct i2c_client *client = chip->client; + + if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) { + + (adc > chip->gainlevel->max) ? + chip->gainlevel++ : chip->gainlevel--; + + i2c_smbus_write_byte_data(client, + TSL2563_CMD | TSL2563_REG_TIMING, + chip->gainlevel->gaintime); + + tsl2563_wait_adc(chip); + tsl2563_wait_adc(chip); + + return 1; + } else + return 0; +} + +static int tsl2563_get_adc(struct tsl2563_chip *chip) +{ + struct i2c_client *client = chip->client; + u16 adc0, adc1; + int retry = 1; + int ret = 0; + + if (chip->suspended) + goto out; + + if (!chip->int_enabled) { + cancel_delayed_work(&chip->poweroff_work); + + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + tsl2563_wait_adc(chip); + } + } + + while (retry) { + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA0LOW); + if (ret < 0) + goto out; + adc0 = ret; + + ret = i2c_smbus_read_word_data(client, + TSL2563_CMD | TSL2563_REG_DATA1LOW); + if (ret < 0) + goto out; + adc1 = ret; + + retry = tsl2563_adjust_gainlevel(chip, adc0); + } + + chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime); + chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime); + + if (!chip->int_enabled) + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + ret = 0; +out: + return ret; +} + +static inline int calib_to_sysfs(u32 calib) +{ + return (int) (((calib * CALIB_BASE_SYSFS) + + CALIB_FRAC_HALF) >> CALIB_FRAC_BITS); +} + +static inline u32 calib_from_sysfs(int value) +{ + return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS; +} + +/* + * Conversions between lux and ADC values. + * + * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are + * appropriate constants. Different constants are needed for different + * kinds of light, determined by the ratio adc1/adc0 (basically the ratio + * of the intensities in infrared and visible wavelengths). lux_table below + * lists the upper threshold of the adc1/adc0 ratio and the corresponding + * constants. + */ + +struct tsl2563_lux_coeff { + unsigned long ch_ratio; + unsigned long ch0_coeff; + unsigned long ch1_coeff; +}; + +static const struct tsl2563_lux_coeff lux_table[] = { + { + .ch_ratio = FRAC10K(1300), + .ch0_coeff = FRAC10K(315), + .ch1_coeff = FRAC10K(262), + }, { + .ch_ratio = FRAC10K(2600), + .ch0_coeff = FRAC10K(337), + .ch1_coeff = FRAC10K(430), + }, { + .ch_ratio = FRAC10K(3900), + .ch0_coeff = FRAC10K(363), + .ch1_coeff = FRAC10K(529), + }, { + .ch_ratio = FRAC10K(5200), + .ch0_coeff = FRAC10K(392), + .ch1_coeff = FRAC10K(605), + }, { + .ch_ratio = FRAC10K(6500), + .ch0_coeff = FRAC10K(229), + .ch1_coeff = FRAC10K(291), + }, { + .ch_ratio = FRAC10K(8000), + .ch0_coeff = FRAC10K(157), + .ch1_coeff = FRAC10K(180), + }, { + .ch_ratio = FRAC10K(13000), + .ch0_coeff = FRAC10K(34), + .ch1_coeff = FRAC10K(26), + }, { + .ch_ratio = ULONG_MAX, + .ch0_coeff = 0, + .ch1_coeff = 0, + }, +}; + +/* + * Convert normalized, scaled ADC values to lux. + */ +static unsigned int adc_to_lux(u32 adc0, u32 adc1) +{ + const struct tsl2563_lux_coeff *lp = lux_table; + unsigned long ratio, lux, ch0 = adc0, ch1 = adc1; + + ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX; + + while (lp->ch_ratio < ratio) + lp++; + + lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff; + + return (unsigned int) (lux >> ADC_FRAC_BITS); +} + +/*--------------------------------------------------------------*/ +/* Sysfs interface */ +/*--------------------------------------------------------------*/ + + +/* Apply calibration coefficient to ADC count. */ +static u32 calib_adc(u32 adc, u32 calib) +{ + unsigned long scaled = adc; + + scaled *= calib; + scaled >>= CALIB_FRAC_BITS; + + return (u32) scaled; +} + +static int tsl2563_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + if (chan->channel == 0) + chip->calib0 = calib_from_sysfs(val); + else + chip->calib1 = calib_from_sysfs(val); + + return 0; +} + +static int tsl2563_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret = -EINVAL; + u32 calib0, calib1; + struct tsl2563_chip *chip = iio_priv(indio_dev); + + mutex_lock(&chip->lock); + switch (m) { + case 0: + switch (chan->type) { + case IIO_LIGHT: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + calib0 = calib_adc(chip->data0, chip->calib0) * + chip->cover_comp_gain; + calib1 = calib_adc(chip->data1, chip->calib1) * + chip->cover_comp_gain; + *val = adc_to_lux(calib0, calib1); + ret = IIO_VAL_INT; + break; + case IIO_INTENSITY: + ret = tsl2563_get_adc(chip); + if (ret) + goto error_ret; + if (chan->channel == 0) + *val = chip->data0; + else + *val = chip->data1; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + case IIO_CHAN_INFO_CALIBSCALE: + if (chan->channel == 0) + *val = calib_to_sysfs(chip->calib0); + else + *val = calib_to_sysfs(chip->calib1); + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + goto error_ret; + } + +error_ret: + mutex_unlock(&chip->lock); + return ret; +} + +static const struct iio_chan_spec tsl2563_channels[] = { + { + .type = IIO_LIGHT, + .indexed = 1, + .channel = 0, + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT, + .event_mask = (IIO_EV_BIT(IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING) | + IIO_EV_BIT(IIO_EV_TYPE_THRESH, + IIO_EV_DIR_FALLING)), + }, { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask = IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT, + } +}; + +static int tsl2563_read_thresh(struct iio_dev *indio_dev, + u64 event_code, + int *val) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + + switch (IIO_EVENT_CODE_EXTRACT_DIR(event_code)) { + case IIO_EV_DIR_RISING: + *val = chip->high_thres; + break; + case IIO_EV_DIR_FALLING: + *val = chip->low_thres; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tsl2563_write_thresh(struct iio_dev *indio_dev, + u64 event_code, + int val) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + u8 address; + + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_RISING) + address = TSL2563_REG_HIGHLOW; + else + address = TSL2563_REG_LOWLOW; + mutex_lock(&chip->lock); + ret = i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | address, + val & 0xFF); + if (ret) + goto error_ret; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | (address + 1), + (val >> 8) & 0xFF); + if (IIO_EVENT_CODE_EXTRACT_DIR(event_code) == IIO_EV_DIR_RISING) + chip->high_thres = val; + else + chip->low_thres = val; + +error_ret: + mutex_unlock(&chip->lock); + + return ret; +} + +static irqreturn_t tsl2563_event_handler(int irq, void *private) +{ + struct iio_dev *dev_info = private; + struct tsl2563_chip *chip = iio_priv(dev_info); + + iio_push_event(dev_info, + IIO_UNMOD_EVENT_CODE(IIO_LIGHT, + 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_EITHER), + iio_get_time_ns()); + + /* clear the interrupt and push the event */ + i2c_smbus_write_byte(chip->client, TSL2563_CMD | TSL2563_CLEARINT); + return IRQ_HANDLED; +} + +static int tsl2563_write_interrupt_config(struct iio_dev *indio_dev, + u64 event_code, + int state) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&chip->lock); + if (state && !(chip->intr & 0x30)) { + chip->intr &= ~0x30; + chip->intr |= 0x10; + /* ensure the chip is actually on */ + cancel_delayed_work(&chip->poweroff_work); + if (!tsl2563_get_power(chip)) { + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + ret = tsl2563_configure(chip); + if (ret) + goto out; + } + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + chip->int_enabled = true; + } + + if (!state && (chip->intr & 0x30)) { + chip->intr |= ~0x30; + ret = i2c_smbus_write_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + chip->int_enabled = false; + /* now the interrupt is not enabled, we can go to sleep */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + } +out: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tsl2563_read_interrupt_config(struct iio_dev *indio_dev, + u64 event_code) +{ + struct tsl2563_chip *chip = iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + ret = i2c_smbus_read_byte_data(chip->client, + TSL2563_CMD | TSL2563_REG_INT); + mutex_unlock(&chip->lock); + if (ret < 0) + goto error_ret; + ret = !!(ret & 0x30); +error_ret: + + return ret; +} + +/*--------------------------------------------------------------*/ +/* Probe, Attach, Remove */ +/*--------------------------------------------------------------*/ +static struct i2c_driver tsl2563_i2c_driver; + +static const struct iio_info tsl2563_info_no_irq = { + .driver_module = THIS_MODULE, + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, +}; + +static const struct iio_info tsl2563_info = { + .driver_module = THIS_MODULE, + .read_raw = &tsl2563_read_raw, + .write_raw = &tsl2563_write_raw, + .read_event_value = &tsl2563_read_thresh, + .write_event_value = &tsl2563_write_thresh, + .read_event_config = &tsl2563_read_interrupt_config, + .write_event_config = &tsl2563_write_interrupt_config, +}; + +static int __devinit tsl2563_probe(struct i2c_client *client, + const struct i2c_device_id *device_id) +{ + struct iio_dev *indio_dev; + struct tsl2563_chip *chip; + struct tsl2563_platform_data *pdata = client->dev.platform_data; + int err = 0; + u8 id = 0; + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + i2c_set_clientdata(client, chip); + chip->client = client; + + err = tsl2563_detect(chip); + if (err) { + dev_err(&client->dev, "detect error %d\n", -err); + goto fail1; + } + + err = tsl2563_read_id(chip, &id); + if (err) { + dev_err(&client->dev, "read id error %d\n", -err); + goto fail1; + } + + mutex_init(&chip->lock); + + /* Default values used until userspace says otherwise */ + chip->low_thres = 0x0; + chip->high_thres = 0xffff; + chip->gainlevel = tsl2563_gainlevel_table; + chip->intr = TSL2563_INT_PERSIST(4); + chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS); + chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS); + + if (pdata) + chip->cover_comp_gain = pdata->cover_comp_gain; + else + chip->cover_comp_gain = 1; + + dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f); + indio_dev->name = client->name; + indio_dev->channels = tsl2563_channels; + indio_dev->num_channels = ARRAY_SIZE(tsl2563_channels); + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + + if (client->irq) + indio_dev->info = &tsl2563_info; + else + indio_dev->info = &tsl2563_info_no_irq; + + if (client->irq) { + err = request_threaded_irq(client->irq, + NULL, + &tsl2563_event_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "tsl2563_event", + indio_dev); + if (err) { + dev_err(&client->dev, "irq request error %d\n", -err); + goto fail1; + } + } + + err = tsl2563_configure(chip); + if (err) { + dev_err(&client->dev, "configure error %d\n", -err); + goto fail2; + } + + INIT_DELAYED_WORK(&chip->poweroff_work, tsl2563_poweroff_work); + + /* The interrupt cannot yet be enabled so this is fine without lock */ + schedule_delayed_work(&chip->poweroff_work, 5 * HZ); + + err = iio_device_register(indio_dev); + if (err) { + dev_err(&client->dev, "iio registration error %d\n", -err); + goto fail3; + } + + return 0; + +fail3: + cancel_delayed_work(&chip->poweroff_work); + flush_scheduled_work(); +fail2: + if (client->irq) + free_irq(client->irq, indio_dev); +fail1: + iio_free_device(indio_dev); + return err; +} + +static int tsl2563_remove(struct i2c_client *client) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(client); + struct iio_dev *indio_dev = iio_priv_to_dev(chip); + + iio_device_unregister(indio_dev); + if (!chip->int_enabled) + cancel_delayed_work(&chip->poweroff_work); + /* Ensure that interrupts are disabled - then flush any bottom halves */ + chip->intr |= ~0x30; + i2c_smbus_write_byte_data(chip->client, TSL2563_CMD | TSL2563_REG_INT, + chip->intr); + flush_scheduled_work(); + tsl2563_set_power(chip, 0); + if (client->irq) + free_irq(client->irq, indio_dev); + + iio_free_device(indio_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tsl2563_suspend(struct device *dev) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 0); + if (ret) + goto out; + + chip->suspended = true; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static int tsl2563_resume(struct device *dev) +{ + struct tsl2563_chip *chip = i2c_get_clientdata(to_i2c_client(dev)); + int ret; + + mutex_lock(&chip->lock); + + ret = tsl2563_set_power(chip, 1); + if (ret) + goto out; + + ret = tsl2563_configure(chip); + if (ret) + goto out; + + chip->suspended = false; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static SIMPLE_DEV_PM_OPS(tsl2563_pm_ops, tsl2563_suspend, tsl2563_resume); +#define TSL2563_PM_OPS (&tsl2563_pm_ops) +#else +#define TSL2563_PM_OPS NULL +#endif + +static const struct i2c_device_id tsl2563_id[] = { + { "tsl2560", 0 }, + { "tsl2561", 1 }, + { "tsl2562", 2 }, + { "tsl2563", 3 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, tsl2563_id); + +static struct i2c_driver tsl2563_i2c_driver = { + .driver = { + .name = "tsl2563", + .pm = TSL2563_PM_OPS, + }, + .probe = tsl2563_probe, + .remove = __devexit_p(tsl2563_remove), + .id_table = tsl2563_id, +}; +module_i2c_driver(tsl2563_i2c_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("tsl2563 light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/light/tsl2563.h b/drivers/staging/iio/light/tsl2563.h new file mode 100644 index 00000000..b97368bd --- /dev/null +++ b/drivers/staging/iio/light/tsl2563.h @@ -0,0 +1,9 @@ +#ifndef __LINUX_TSL2563_H +#define __LINUX_TSL2563_H + +struct tsl2563_platform_data { + int cover_comp_gain; +}; + +#endif /* __LINUX_TSL2563_H */ + diff --git a/drivers/staging/iio/light/tsl2583.c b/drivers/staging/iio/light/tsl2583.c new file mode 100644 index 00000000..8671d98e --- /dev/null +++ b/drivers/staging/iio/light/tsl2583.c @@ -0,0 +1,956 @@ +/* + * Device driver for monitoring ambient light intensity (lux) + * within the TAOS tsl258x family of devices (tsl2580, tsl2581). + * + * Copyright (c) 2011, TAOS Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../iio.h" + +#define TSL258X_MAX_DEVICE_REGS 32 + +/* Triton register offsets */ +#define TSL258X_REG_MAX 8 + +/* Device Registers and Masks */ +#define TSL258X_CNTRL 0x00 +#define TSL258X_ALS_TIME 0X01 +#define TSL258X_INTERRUPT 0x02 +#define TSL258X_GAIN 0x07 +#define TSL258X_REVID 0x11 +#define TSL258X_CHIPID 0x12 +#define TSL258X_ALS_CHAN0LO 0x14 +#define TSL258X_ALS_CHAN0HI 0x15 +#define TSL258X_ALS_CHAN1LO 0x16 +#define TSL258X_ALS_CHAN1HI 0x17 +#define TSL258X_TMR_LO 0x18 +#define TSL258X_TMR_HI 0x19 + +/* tsl2583 cmd reg masks */ +#define TSL258X_CMD_REG 0x80 +#define TSL258X_CMD_SPL_FN 0x60 +#define TSL258X_CMD_ALS_INT_CLR 0X01 + +/* tsl2583 cntrl reg masks */ +#define TSL258X_CNTL_ADC_ENBL 0x02 +#define TSL258X_CNTL_PWR_ON 0x01 + +/* tsl2583 status reg masks */ +#define TSL258X_STA_ADC_VALID 0x01 +#define TSL258X_STA_ADC_INTR 0x10 + +/* Lux calculation constants */ +#define TSL258X_LUX_CALC_OVER_FLOW 65535 + +enum { + TSL258X_CHIP_UNKNOWN = 0, + TSL258X_CHIP_WORKING = 1, + TSL258X_CHIP_SUSPENDED = 2 +}; + +/* Per-device data */ +struct taos_als_info { + u16 als_ch0; + u16 als_ch1; + u16 lux; +}; + +struct taos_settings { + int als_time; + int als_gain; + int als_gain_trim; + int als_cal_target; +}; + +struct tsl2583_chip { + struct mutex als_mutex; + struct i2c_client *client; + struct taos_als_info als_cur_info; + struct taos_settings taos_settings; + int als_time_scale; + int als_saturation; + int taos_chip_status; + u8 taos_config[8]; +}; + +/* + * Initial values for device - this values can/will be changed by driver. + * and applications as needed. + * These values are dynamic. + */ +static const u8 taos_config[8] = { + 0x00, 0xee, 0x00, 0x03, 0x00, 0xFF, 0xFF, 0x00 +}; /* cntrl atime intC Athl0 Athl1 Athh0 Athh1 gain */ + +struct taos_lux { + unsigned int ratio; + unsigned int ch0; + unsigned int ch1; +}; + +/* This structure is intentionally large to accommodate updates via sysfs. */ +/* Sized to 11 = max 10 segments + 1 termination segment */ +/* Assumption is one and only one type of glass used */ +static struct taos_lux taos_device_lux[11] = { + { 9830, 8520, 15729 }, + { 12452, 10807, 23344 }, + { 14746, 6383, 11705 }, + { 17695, 4063, 6554 }, +}; + +struct gainadj { + s16 ch0; + s16 ch1; +}; + +/* Index = (0 - 3) Used to validate the gain selection index */ +static const struct gainadj gainadj[] = { + { 1, 1 }, + { 8, 8 }, + { 16, 16 }, + { 107, 115 } +}; + +/* + * Provides initial operational parameter defaults. + * These defaults may be changed through the device's sysfs files. + */ +static void taos_defaults(struct tsl2583_chip *chip) +{ + /* Operational parameters */ + chip->taos_settings.als_time = 100; + /* must be a multiple of 50mS */ + chip->taos_settings.als_gain = 0; + /* this is actually an index into the gain table */ + /* assume clear glass as default */ + chip->taos_settings.als_gain_trim = 1000; + /* default gain trim to account for aperture effects */ + chip->taos_settings.als_cal_target = 130; + /* Known external ALS reading used for calibration */ +} + +/* + * Read a number of bytes starting at register (reg) location. + * Return 0, or i2c_smbus_write_byte ERROR code. + */ +static int +taos_i2c_read(struct i2c_client *client, u8 reg, u8 *val, unsigned int len) +{ + int i, ret; + + for (i = 0; i < len; i++) { + /* select register to write */ + ret = i2c_smbus_write_byte(client, (TSL258X_CMD_REG | reg)); + if (ret < 0) { + dev_err(&client->dev, "taos_i2c_read failed to write" + " register %x\n", reg); + return ret; + } + /* read the data */ + *val = i2c_smbus_read_byte(client); + val++; + reg++; + } + return 0; +} + +/* + * Reads and calculates current lux value. + * The raw ch0 and ch1 values of the ambient light sensed in the last + * integration cycle are read from the device. + * Time scale factor array values are adjusted based on the integration time. + * The raw values are multiplied by a scale factor, and device gain is obtained + * using gain index. Limit checks are done next, then the ratio of a multiple + * of ch1 value, to the ch0 value, is calculated. The array taos_device_lux[] + * declared above is then scanned to find the first ratio value that is just + * above the ratio we just calculated. The ch0 and ch1 multiplier constants in + * the array are then used along with the time scale factor array values, to + * calculate the lux. + */ +static int taos_get_lux(struct iio_dev *indio_dev) +{ + u16 ch0, ch1; /* separated ch0/ch1 data from device */ + u32 lux; /* raw lux calculated from device data */ + u64 lux64; + u32 ratio; + u8 buf[5]; + struct taos_lux *p; + struct tsl2583_chip *chip = iio_priv(indio_dev); + int i, ret; + u32 ch0lux = 0; + u32 ch1lux = 0; + + if (mutex_trylock(&chip->als_mutex) == 0) { + dev_info(&chip->client->dev, "taos_get_lux device is busy\n"); + return chip->als_cur_info.lux; /* busy, so return LAST VALUE */ + } + + if (chip->taos_chip_status != TSL258X_CHIP_WORKING) { + /* device is not enabled */ + dev_err(&chip->client->dev, "taos_get_lux device is not enabled\n"); + ret = -EBUSY ; + goto out_unlock; + } + + ret = taos_i2c_read(chip->client, (TSL258X_CMD_REG), &buf[0], 1); + if (ret < 0) { + dev_err(&chip->client->dev, "taos_get_lux failed to read CMD_REG\n"); + goto out_unlock; + } + /* is data new & valid */ + if (!(buf[0] & TSL258X_STA_ADC_INTR)) { + dev_err(&chip->client->dev, "taos_get_lux data not valid\n"); + ret = chip->als_cur_info.lux; /* return LAST VALUE */ + goto out_unlock; + } + + for (i = 0; i < 4; i++) { + int reg = TSL258X_CMD_REG | (TSL258X_ALS_CHAN0LO + i); + ret = taos_i2c_read(chip->client, reg, &buf[i], 1); + if (ret < 0) { + dev_err(&chip->client->dev, "taos_get_lux failed to read" + " register %x\n", reg); + goto out_unlock; + } + } + + /* clear status, really interrupt status (interrupts are off), but + * we use the bit anyway - don't forget 0x80 - this is a command*/ + ret = i2c_smbus_write_byte(chip->client, + (TSL258X_CMD_REG | TSL258X_CMD_SPL_FN | + TSL258X_CMD_ALS_INT_CLR)); + + if (ret < 0) { + dev_err(&chip->client->dev, + "taos_i2c_write_command failed in taos_get_lux, err = %d\n", + ret); + goto out_unlock; /* have no data, so return failure */ + } + + /* extract ALS/lux data */ + ch0 = le16_to_cpup((const __le16 *)&buf[0]); + ch1 = le16_to_cpup((const __le16 *)&buf[2]); + + chip->als_cur_info.als_ch0 = ch0; + chip->als_cur_info.als_ch1 = ch1; + + if ((ch0 >= chip->als_saturation) || (ch1 >= chip->als_saturation)) + goto return_max; + + if (ch0 == 0) { + /* have no data, so return LAST VALUE */ + ret = chip->als_cur_info.lux = 0; + goto out_unlock; + } + /* calculate ratio */ + ratio = (ch1 << 15) / ch0; + /* convert to unscaled lux using the pointer to the table */ + for (p = (struct taos_lux *) taos_device_lux; + p->ratio != 0 && p->ratio < ratio; p++) + ; + + if (p->ratio == 0) { + lux = 0; + } else { + ch0lux = ((ch0 * p->ch0) + + (gainadj[chip->taos_settings.als_gain].ch0 >> 1)) + / gainadj[chip->taos_settings.als_gain].ch0; + ch1lux = ((ch1 * p->ch1) + + (gainadj[chip->taos_settings.als_gain].ch1 >> 1)) + / gainadj[chip->taos_settings.als_gain].ch1; + lux = ch0lux - ch1lux; + } + + /* note: lux is 31 bit max at this point */ + if (ch1lux > ch0lux) { + dev_dbg(&chip->client->dev, "No Data - Return last value\n"); + ret = chip->als_cur_info.lux = 0; + goto out_unlock; + } + + /* adjust for active time scale */ + if (chip->als_time_scale == 0) + lux = 0; + else + lux = (lux + (chip->als_time_scale >> 1)) / + chip->als_time_scale; + + /* Adjust for active gain scale. + * The taos_device_lux tables above have a factor of 8192 built in, + * so we need to shift right. + * User-specified gain provides a multiplier. + * Apply user-specified gain before shifting right to retain precision. + * Use 64 bits to avoid overflow on multiplication. + * Then go back to 32 bits before division to avoid using div_u64(). + */ + lux64 = lux; + lux64 = lux64 * chip->taos_settings.als_gain_trim; + lux64 >>= 13; + lux = lux64; + lux = (lux + 500) / 1000; + if (lux > TSL258X_LUX_CALC_OVER_FLOW) { /* check for overflow */ +return_max: + lux = TSL258X_LUX_CALC_OVER_FLOW; + } + + /* Update the structure with the latest VALID lux. */ + chip->als_cur_info.lux = lux; + ret = lux; + +out_unlock: + mutex_unlock(&chip->als_mutex); + return ret; +} + +/* + * Obtain single reading and calculate the als_gain_trim (later used + * to derive actual lux). + * Return updated gain_trim value. + */ +static int taos_als_calibrate(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + u8 reg_val; + unsigned int gain_trim_val; + int ret; + int lux_val; + + ret = i2c_smbus_write_byte(chip->client, + (TSL258X_CMD_REG | TSL258X_CNTRL)); + if (ret < 0) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed to reach the CNTRL register, ret=%d\n", + ret); + return ret; + } + + reg_val = i2c_smbus_read_byte(chip->client); + if ((reg_val & (TSL258X_CNTL_ADC_ENBL | TSL258X_CNTL_PWR_ON)) + != (TSL258X_CNTL_ADC_ENBL | TSL258X_CNTL_PWR_ON)) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed: device not powered on with ADC enabled\n"); + return -1; + } + + ret = i2c_smbus_write_byte(chip->client, + (TSL258X_CMD_REG | TSL258X_CNTRL)); + if (ret < 0) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed to reach the STATUS register, ret=%d\n", + ret); + return ret; + } + reg_val = i2c_smbus_read_byte(chip->client); + + if ((reg_val & TSL258X_STA_ADC_VALID) != TSL258X_STA_ADC_VALID) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed: STATUS - ADC not valid.\n"); + return -ENODATA; + } + lux_val = taos_get_lux(indio_dev); + if (lux_val < 0) { + dev_err(&chip->client->dev, "taos_als_calibrate failed to get lux\n"); + return lux_val; + } + gain_trim_val = (unsigned int) (((chip->taos_settings.als_cal_target) + * chip->taos_settings.als_gain_trim) / lux_val); + + if ((gain_trim_val < 250) || (gain_trim_val > 4000)) { + dev_err(&chip->client->dev, + "taos_als_calibrate failed: trim_val of %d is out of range\n", + gain_trim_val); + return -ENODATA; + } + chip->taos_settings.als_gain_trim = (int) gain_trim_val; + + return (int) gain_trim_val; +} + +/* + * Turn the device on. + * Configuration must be set before calling this function. + */ +static int taos_chip_on(struct iio_dev *indio_dev) +{ + int i; + int ret; + u8 *uP; + u8 utmp; + int als_count; + int als_time; + struct tsl2583_chip *chip = iio_priv(indio_dev); + + /* and make sure we're not already on */ + if (chip->taos_chip_status == TSL258X_CHIP_WORKING) { + /* if forcing a register update - turn off, then on */ + dev_info(&chip->client->dev, "device is already enabled\n"); + return -EINVAL; + } + + /* determine als integration regster */ + als_count = (chip->taos_settings.als_time * 100 + 135) / 270; + if (als_count == 0) + als_count = 1; /* ensure at least one cycle */ + + /* convert back to time (encompasses overrides) */ + als_time = (als_count * 27 + 5) / 10; + chip->taos_config[TSL258X_ALS_TIME] = 256 - als_count; + + /* Set the gain based on taos_settings struct */ + chip->taos_config[TSL258X_GAIN] = chip->taos_settings.als_gain; + + /* set chip struct re scaling and saturation */ + chip->als_saturation = als_count * 922; /* 90% of full scale */ + chip->als_time_scale = (als_time + 25) / 50; + + /* TSL258x Specific power-on / adc enable sequence + * Power on the device 1st. */ + utmp = TSL258X_CNTL_PWR_ON; + ret = i2c_smbus_write_byte_data(chip->client, + TSL258X_CMD_REG | TSL258X_CNTRL, utmp); + if (ret < 0) { + dev_err(&chip->client->dev, "taos_chip_on failed on CNTRL reg.\n"); + return -1; + } + + /* Use the following shadow copy for our delay before enabling ADC. + * Write all the registers. */ + for (i = 0, uP = chip->taos_config; i < TSL258X_REG_MAX; i++) { + ret = i2c_smbus_write_byte_data(chip->client, + TSL258X_CMD_REG + i, + *uP++); + if (ret < 0) { + dev_err(&chip->client->dev, + "taos_chip_on failed on reg %d.\n", i); + return -1; + } + } + + msleep(3); + /* NOW enable the ADC + * initialize the desired mode of operation */ + utmp = TSL258X_CNTL_PWR_ON | TSL258X_CNTL_ADC_ENBL; + ret = i2c_smbus_write_byte_data(chip->client, + TSL258X_CMD_REG | TSL258X_CNTRL, + utmp); + if (ret < 0) { + dev_err(&chip->client->dev, "taos_chip_on failed on 2nd CTRL reg.\n"); + return -1; + } + chip->taos_chip_status = TSL258X_CHIP_WORKING; + + return ret; +} + +static int taos_chip_off(struct iio_dev *indio_dev) +{ + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret; + + /* turn device off */ + chip->taos_chip_status = TSL258X_CHIP_SUSPENDED; + ret = i2c_smbus_write_byte_data(chip->client, + TSL258X_CMD_REG | TSL258X_CNTRL, + 0x00); + return ret; +} + +/* Sysfs Interface Functions */ + +static ssize_t taos_power_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->taos_chip_status); +} + +static ssize_t taos_power_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + if (value == 0) + taos_chip_off(indio_dev); + else + taos_chip_on(indio_dev); + + return len; +} + +static ssize_t taos_gain_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + char gain[4] = {0}; + + switch (chip->taos_settings.als_gain) { + case 0: + strcpy(gain, "001"); + break; + case 1: + strcpy(gain, "008"); + break; + case 2: + strcpy(gain, "016"); + break; + case 3: + strcpy(gain, "111"); + break; + } + + return sprintf(buf, "%s\n", gain); +} + +static ssize_t taos_gain_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + switch (value) { + case 1: + chip->taos_settings.als_gain = 0; + break; + case 8: + chip->taos_settings.als_gain = 1; + break; + case 16: + chip->taos_settings.als_gain = 2; + break; + case 111: + chip->taos_settings.als_gain = 3; + break; + default: + dev_err(dev, "Invalid Gain Index (must be 1,8,16,111)\n"); + return -1; + } + + return len; +} + +static ssize_t taos_gain_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", "1 8 16 111"); +} + +static ssize_t taos_als_time_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->taos_settings.als_time); +} + +static ssize_t taos_als_time_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + if ((value < 50) || (value > 650)) + return -EINVAL; + + if (value % 50) + return -EINVAL; + + chip->taos_settings.als_time = value; + + return len; +} + +static ssize_t taos_als_time_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", + "50 100 150 200 250 300 350 400 450 500 550 600 650"); +} + +static ssize_t taos_als_trim_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->taos_settings.als_gain_trim); +} + +static ssize_t taos_als_trim_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + if (value) + chip->taos_settings.als_gain_trim = value; + + return len; +} + +static ssize_t taos_als_cal_target_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + + return sprintf(buf, "%d\n", chip->taos_settings.als_cal_target); +} + +static ssize_t taos_als_cal_target_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + if (value) + chip->taos_settings.als_cal_target = value; + + return len; +} + +static ssize_t taos_lux_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + + ret = taos_get_lux(dev_get_drvdata(dev)); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t taos_do_calibrate(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + unsigned long value; + + if (strict_strtoul(buf, 0, &value)) + return -EINVAL; + + if (value == 1) + taos_als_calibrate(indio_dev); + + return len; +} + +static ssize_t taos_luxtable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + int offset = 0; + + for (i = 0; i < ARRAY_SIZE(taos_device_lux); i++) { + offset += sprintf(buf + offset, "%d,%d,%d,", + taos_device_lux[i].ratio, + taos_device_lux[i].ch0, + taos_device_lux[i].ch1); + if (taos_device_lux[i].ratio == 0) { + /* We just printed the first "0" entry. + * Now get rid of the extra "," and break. */ + offset--; + break; + } + } + + offset += sprintf(buf + offset, "\n"); + return offset; +} + +static ssize_t taos_luxtable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int value[ARRAY_SIZE(taos_device_lux)*3 + 1]; + int n; + + get_options(buf, ARRAY_SIZE(value), value); + + /* We now have an array of ints starting at value[1], and + * enumerated by value[0]. + * We expect each group of three ints is one table entry, + * and the last table entry is all 0. + */ + n = value[0]; + if ((n % 3) || n < 6 || n > ((ARRAY_SIZE(taos_device_lux) - 1) * 3)) { + dev_info(dev, "LUX TABLE INPUT ERROR 1 Value[0]=%d\n", n); + return -EINVAL; + } + if ((value[(n - 2)] | value[(n - 1)] | value[n]) != 0) { + dev_info(dev, "LUX TABLE INPUT ERROR 2 Value[0]=%d\n", n); + return -EINVAL; + } + + if (chip->taos_chip_status == TSL258X_CHIP_WORKING) + taos_chip_off(indio_dev); + + /* Zero out the table */ + memset(taos_device_lux, 0, sizeof(taos_device_lux)); + memcpy(taos_device_lux, &value[1], (value[0] * 4)); + + taos_chip_on(indio_dev); + + return len; +} + +static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, + taos_power_state_show, taos_power_state_store); + +static DEVICE_ATTR(illuminance0_calibscale, S_IRUGO | S_IWUSR, + taos_gain_show, taos_gain_store); +static DEVICE_ATTR(illuminance0_calibscale_available, S_IRUGO, + taos_gain_available_show, NULL); + +static DEVICE_ATTR(illuminance0_integration_time, S_IRUGO | S_IWUSR, + taos_als_time_show, taos_als_time_store); +static DEVICE_ATTR(illuminance0_integration_time_available, S_IRUGO, + taos_als_time_available_show, NULL); + +static DEVICE_ATTR(illuminance0_calibbias, S_IRUGO | S_IWUSR, + taos_als_trim_show, taos_als_trim_store); + +static DEVICE_ATTR(illuminance0_input_target, S_IRUGO | S_IWUSR, + taos_als_cal_target_show, taos_als_cal_target_store); + +static DEVICE_ATTR(illuminance0_input, S_IRUGO, taos_lux_show, NULL); +static DEVICE_ATTR(illuminance0_calibrate, S_IWUSR, NULL, taos_do_calibrate); +static DEVICE_ATTR(illuminance0_lux_table, S_IRUGO | S_IWUSR, + taos_luxtable_show, taos_luxtable_store); + +static struct attribute *sysfs_attrs_ctrl[] = { + &dev_attr_power_state.attr, + &dev_attr_illuminance0_calibscale.attr, /* Gain */ + &dev_attr_illuminance0_calibscale_available.attr, + &dev_attr_illuminance0_integration_time.attr, /* I time*/ + &dev_attr_illuminance0_integration_time_available.attr, + &dev_attr_illuminance0_calibbias.attr, /* trim */ + &dev_attr_illuminance0_input_target.attr, + &dev_attr_illuminance0_input.attr, + &dev_attr_illuminance0_calibrate.attr, + &dev_attr_illuminance0_lux_table.attr, + NULL +}; + +static struct attribute_group tsl2583_attribute_group = { + .attrs = sysfs_attrs_ctrl, +}; + +/* Use the default register values to identify the Taos device */ +static int taos_tsl258x_device(unsigned char *bufp) +{ + return ((bufp[TSL258X_CHIPID] & 0xf0) == 0x90); +} + +static const struct iio_info tsl2583_info = { + .attrs = &tsl2583_attribute_group, + .driver_module = THIS_MODULE, +}; + +/* + * Client probe function - When a valid device is found, the driver's device + * data structure is updated, and initialization completes successfully. + */ +static int __devinit taos_probe(struct i2c_client *clientp, + const struct i2c_device_id *idp) +{ + int i, ret; + unsigned char buf[TSL258X_MAX_DEVICE_REGS]; + struct tsl2583_chip *chip; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(clientp->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&clientp->dev, + "taos_probe() - i2c smbus byte data " + "functions unsupported\n"); + return -EOPNOTSUPP; + } + + indio_dev = iio_allocate_device(sizeof(*chip)); + if (indio_dev == NULL) { + ret = -ENOMEM; + dev_err(&clientp->dev, "iio allocation failed\n"); + goto fail1; + } + chip = iio_priv(indio_dev); + chip->client = clientp; + i2c_set_clientdata(clientp, indio_dev); + + mutex_init(&chip->als_mutex); + chip->taos_chip_status = TSL258X_CHIP_UNKNOWN; + memcpy(chip->taos_config, taos_config, sizeof(chip->taos_config)); + + for (i = 0; i < TSL258X_MAX_DEVICE_REGS; i++) { + ret = i2c_smbus_write_byte(clientp, + (TSL258X_CMD_REG | (TSL258X_CNTRL + i))); + if (ret < 0) { + dev_err(&clientp->dev, "i2c_smbus_write_bytes() to cmd " + "reg failed in taos_probe(), err = %d\n", ret); + goto fail2; + } + ret = i2c_smbus_read_byte(clientp); + if (ret < 0) { + dev_err(&clientp->dev, "i2c_smbus_read_byte from " + "reg failed in taos_probe(), err = %d\n", ret); + + goto fail2; + } + buf[i] = ret; + } + + if (!taos_tsl258x_device(buf)) { + dev_info(&clientp->dev, "i2c device found but does not match " + "expected id in taos_probe()\n"); + goto fail2; + } + + ret = i2c_smbus_write_byte(clientp, (TSL258X_CMD_REG | TSL258X_CNTRL)); + if (ret < 0) { + dev_err(&clientp->dev, "i2c_smbus_write_byte() to cmd reg " + "failed in taos_probe(), err = %d\n", ret); + goto fail2; + } + + indio_dev->info = &tsl2583_info; + indio_dev->dev.parent = &clientp->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->client->name; + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&clientp->dev, "iio registration failed\n"); + goto fail2; + } + + /* Load up the V2 defaults (these are hard coded defaults for now) */ + taos_defaults(chip); + + /* Make sure the chip is on */ + taos_chip_on(indio_dev); + + dev_info(&clientp->dev, "Light sensor found.\n"); + return 0; +fail1: + iio_free_device(indio_dev); +fail2: + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int taos_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&chip->als_mutex); + + if (chip->taos_chip_status == TSL258X_CHIP_WORKING) { + ret = taos_chip_off(indio_dev); + chip->taos_chip_status = TSL258X_CHIP_SUSPENDED; + } + + mutex_unlock(&chip->als_mutex); + return ret; +} + +static int taos_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct tsl2583_chip *chip = iio_priv(indio_dev); + int ret = 0; + + mutex_lock(&chip->als_mutex); + + if (chip->taos_chip_status == TSL258X_CHIP_SUSPENDED) + ret = taos_chip_on(indio_dev); + + mutex_unlock(&chip->als_mutex); + return ret; +} + +static SIMPLE_DEV_PM_OPS(taos_pm_ops, taos_suspend, taos_resume); +#define TAOS_PM_OPS (&taos_pm_ops) +#else +#define TAOS_PM_OPS NULL +#endif + +static int __devexit taos_remove(struct i2c_client *client) +{ + iio_device_unregister(i2c_get_clientdata(client)); + iio_free_device(i2c_get_clientdata(client)); + + return 0; +} + +static struct i2c_device_id taos_idtable[] = { + { "tsl2580", 0 }, + { "tsl2581", 1 }, + { "tsl2583", 2 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, taos_idtable); + +/* Driver definition */ +static struct i2c_driver taos_driver = { + .driver = { + .name = "tsl2583", + .pm = TAOS_PM_OPS, + }, + .id_table = taos_idtable, + .probe = taos_probe, + .remove = __devexit_p(taos_remove), +}; +module_i2c_driver(taos_driver); + +MODULE_AUTHOR("J. August Brenner"); +MODULE_DESCRIPTION("TAOS tsl2583 ambient light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/machine.h b/drivers/staging/iio/machine.h new file mode 100644 index 00000000..0b1f19bf --- /dev/null +++ b/drivers/staging/iio/machine.h @@ -0,0 +1,24 @@ +/* + * Industrial I/O in kernel access map definitions for board files. + * + * Copyright (c) 2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/** + * struct iio_map - description of link between consumer and device channels + * @adc_channel_label: Label used to identify the channel on the provider. + * This is matched against the datasheet_name element + * of struct iio_chan_spec. + * @consumer_dev_name: Name to uniquely identify the consumer device. + * @consumer_channel: Unique name used to idenitify the channel on the + * consumer side. + */ +struct iio_map { + const char *adc_channel_label; + const char *consumer_dev_name; + const char *consumer_channel; +}; diff --git a/drivers/staging/iio/magnetometer/Kconfig b/drivers/staging/iio/magnetometer/Kconfig new file mode 100644 index 00000000..722c4e13 --- /dev/null +++ b/drivers/staging/iio/magnetometer/Kconfig @@ -0,0 +1,27 @@ +# +# Magnetometer sensors +# +menu "Magnetometer sensors" + +config SENSORS_AK8975 + tristate "Asahi Kasei AK8975 3-Axis Magnetometer" + depends on I2C + depends on GENERIC_GPIO + help + Say yes here to build support for Asahi Kasei AK8975 3-Axis + Magnetometer. + + To compile this driver as a module, choose M here: the module + will be called ak8975. + +config SENSORS_HMC5843 + tristate "Honeywell HMC5843 3-Axis Magnetometer" + depends on I2C + help + Say Y here to add support for the Honeywell HMC 5843 3-Axis + Magnetometer (digital compass). + + To compile this driver as a module, choose M here: the module + will be called hmc5843 + +endmenu diff --git a/drivers/staging/iio/magnetometer/Makefile b/drivers/staging/iio/magnetometer/Makefile new file mode 100644 index 00000000..f2a753f8 --- /dev/null +++ b/drivers/staging/iio/magnetometer/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for industrial I/O Magnetometer sensors +# + +obj-$(CONFIG_SENSORS_AK8975) += ak8975.o +obj-$(CONFIG_SENSORS_HMC5843) += hmc5843.o diff --git a/drivers/staging/iio/magnetometer/ak8975.c b/drivers/staging/iio/magnetometer/ak8975.c new file mode 100644 index 00000000..ebc2d084 --- /dev/null +++ b/drivers/staging/iio/magnetometer/ak8975.c @@ -0,0 +1,589 @@ +/* + * A sensor driver for the magnetometer AK8975. + * + * Magnetic compass sensor driver for monitoring magnetic flux information. + * + * Copyright (c) 2010, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../iio.h" +#include "../sysfs.h" +/* + * Register definitions, as well as various shifts and masks to get at the + * individual fields of the registers. + */ +#define AK8975_REG_WIA 0x00 +#define AK8975_DEVICE_ID 0x48 + +#define AK8975_REG_INFO 0x01 + +#define AK8975_REG_ST1 0x02 +#define AK8975_REG_ST1_DRDY_SHIFT 0 +#define AK8975_REG_ST1_DRDY_MASK (1 << AK8975_REG_ST1_DRDY_SHIFT) + +#define AK8975_REG_HXL 0x03 +#define AK8975_REG_HXH 0x04 +#define AK8975_REG_HYL 0x05 +#define AK8975_REG_HYH 0x06 +#define AK8975_REG_HZL 0x07 +#define AK8975_REG_HZH 0x08 +#define AK8975_REG_ST2 0x09 +#define AK8975_REG_ST2_DERR_SHIFT 2 +#define AK8975_REG_ST2_DERR_MASK (1 << AK8975_REG_ST2_DERR_SHIFT) + +#define AK8975_REG_ST2_HOFL_SHIFT 3 +#define AK8975_REG_ST2_HOFL_MASK (1 << AK8975_REG_ST2_HOFL_SHIFT) + +#define AK8975_REG_CNTL 0x0A +#define AK8975_REG_CNTL_MODE_SHIFT 0 +#define AK8975_REG_CNTL_MODE_MASK (0xF << AK8975_REG_CNTL_MODE_SHIFT) +#define AK8975_REG_CNTL_MODE_POWER_DOWN 0 +#define AK8975_REG_CNTL_MODE_ONCE 1 +#define AK8975_REG_CNTL_MODE_SELF_TEST 8 +#define AK8975_REG_CNTL_MODE_FUSE_ROM 0xF + +#define AK8975_REG_RSVC 0x0B +#define AK8975_REG_ASTC 0x0C +#define AK8975_REG_TS1 0x0D +#define AK8975_REG_TS2 0x0E +#define AK8975_REG_I2CDIS 0x0F +#define AK8975_REG_ASAX 0x10 +#define AK8975_REG_ASAY 0x11 +#define AK8975_REG_ASAZ 0x12 + +#define AK8975_MAX_REGS AK8975_REG_ASAZ + +/* + * Miscellaneous values. + */ +#define AK8975_MAX_CONVERSION_TIMEOUT 500 +#define AK8975_CONVERSION_DONE_POLL_TIME 10 + +/* + * Per-instance context data for the device. + */ +struct ak8975_data { + struct i2c_client *client; + struct attribute_group attrs; + struct mutex lock; + u8 asa[3]; + long raw_to_gauss[3]; + bool mode; + u8 reg_cache[AK8975_MAX_REGS]; + int eoc_gpio; + int eoc_irq; +}; + +static const int ak8975_index_to_reg[] = { + AK8975_REG_HXL, AK8975_REG_HYL, AK8975_REG_HZL, +}; + +/* + * Helper function to write to the I2C device's registers. + */ +static int ak8975_write_data(struct i2c_client *client, + u8 reg, u8 val, u8 mask, u8 shift) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ak8975_data *data = iio_priv(indio_dev); + u8 regval; + int ret; + + regval = (data->reg_cache[reg] & ~mask) | (val << shift); + ret = i2c_smbus_write_byte_data(client, reg, regval); + if (ret < 0) { + dev_err(&client->dev, "Write to device fails status %x\n", ret); + return ret; + } + data->reg_cache[reg] = regval; + + return 0; +} + +/* + * Helper function to read a contiguous set of the I2C device's registers. + */ +static int ak8975_read_data(struct i2c_client *client, + u8 reg, u8 length, u8 *buffer) +{ + int ret; + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = I2C_M_NOSTART, + .len = 1, + .buf = ®, + }, { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + .buf = buffer, + } + }; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_err(&client->dev, "Read from device fails\n"); + return ret; + } + + return 0; +} + +/* + * Perform some start-of-day setup, including reading the asa calibration + * values and caching them. + */ +static int ak8975_setup(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ak8975_data *data = iio_priv(indio_dev); + u8 device_id; + int ret; + + /* Confirm that the device we're talking to is really an AK8975. */ + ret = ak8975_read_data(client, AK8975_REG_WIA, 1, &device_id); + if (ret < 0) { + dev_err(&client->dev, "Error reading WIA\n"); + return ret; + } + if (device_id != AK8975_DEVICE_ID) { + dev_err(&client->dev, "Device ak8975 not found\n"); + return -ENODEV; + } + + /* Write the fused rom access mode. */ + ret = ak8975_write_data(client, + AK8975_REG_CNTL, + AK8975_REG_CNTL_MODE_FUSE_ROM, + AK8975_REG_CNTL_MODE_MASK, + AK8975_REG_CNTL_MODE_SHIFT); + if (ret < 0) { + dev_err(&client->dev, "Error in setting fuse access mode\n"); + return ret; + } + + /* Get asa data and store in the device data. */ + ret = ak8975_read_data(client, AK8975_REG_ASAX, 3, data->asa); + if (ret < 0) { + dev_err(&client->dev, "Not able to read asa data\n"); + return ret; + } + +/* + * Precalculate scale factor (in Gauss units) for each axis and + * store in the device data. + * + * This scale factor is axis-dependent, and is derived from 3 calibration + * factors ASA(x), ASA(y), and ASA(z). + * + * These ASA values are read from the sensor device at start of day, and + * cached in the device context struct. + * + * Adjusting the flux value with the sensitivity adjustment value should be + * done via the following formula: + * + * Hadj = H * ( ( ( (ASA-128)*0.5 ) / 128 ) + 1 ) + * + * where H is the raw value, ASA is the sensitivity adjustment, and Hadj + * is the resultant adjusted value. + * + * We reduce the formula to: + * + * Hadj = H * (ASA + 128) / 256 + * + * H is in the range of -4096 to 4095. The magnetometer has a range of + * +-1229uT. To go from the raw value to uT is: + * + * HuT = H * 1229/4096, or roughly, 3/10. + * + * Since 1uT = 100 gauss, our final scale factor becomes: + * + * Hadj = H * ((ASA + 128) / 256) * 3/10 * 100 + * Hadj = H * ((ASA + 128) * 30 / 256 + * + * Since ASA doesn't change, we cache the resultant scale factor into the + * device context in ak8975_setup(). + */ + data->raw_to_gauss[0] = ((data->asa[0] + 128) * 30) >> 8; + data->raw_to_gauss[1] = ((data->asa[1] + 128) * 30) >> 8; + data->raw_to_gauss[2] = ((data->asa[2] + 128) * 30) >> 8; + + return 0; +} + +/* + * Shows the device's mode. 0 = off, 1 = on. + */ +static ssize_t show_mode(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ak8975_data *data = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", data->mode); +} + +/* + * Sets the device's mode. 0 = off, 1 = on. The device's mode must be on + * for the magn raw attributes to be available. + */ +static ssize_t store_mode(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ak8975_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + bool value; + int ret; + + /* Convert mode string and do some basic sanity checking on it. + only 0 or 1 are valid. */ + ret = strtobool(buf, &value); + if (ret < 0) + return ret; + + mutex_lock(&data->lock); + + /* Write the mode to the device. */ + if (data->mode != value) { + ret = ak8975_write_data(client, + AK8975_REG_CNTL, + (u8)value, + AK8975_REG_CNTL_MODE_MASK, + AK8975_REG_CNTL_MODE_SHIFT); + + if (ret < 0) { + dev_err(&client->dev, "Error in setting mode\n"); + mutex_unlock(&data->lock); + return ret; + } + data->mode = value; + } + + mutex_unlock(&data->lock); + + return count; +} + +static int wait_conversion_complete_gpio(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + u8 read_status; + u32 timeout_ms = AK8975_MAX_CONVERSION_TIMEOUT; + int ret; + + /* Wait for the conversion to complete. */ + while (timeout_ms) { + msleep(AK8975_CONVERSION_DONE_POLL_TIME); + if (gpio_get_value(data->eoc_gpio)) + break; + timeout_ms -= AK8975_CONVERSION_DONE_POLL_TIME; + } + if (!timeout_ms) { + dev_err(&client->dev, "Conversion timeout happened\n"); + return -EINVAL; + } + + ret = ak8975_read_data(client, AK8975_REG_ST1, 1, &read_status); + if (ret < 0) { + dev_err(&client->dev, "Error in reading ST1\n"); + return ret; + } + return read_status; +} + +static int wait_conversion_complete_polled(struct ak8975_data *data) +{ + struct i2c_client *client = data->client; + u8 read_status; + u32 timeout_ms = AK8975_MAX_CONVERSION_TIMEOUT; + int ret; + + /* Wait for the conversion to complete. */ + while (timeout_ms) { + msleep(AK8975_CONVERSION_DONE_POLL_TIME); + ret = ak8975_read_data(client, AK8975_REG_ST1, 1, &read_status); + if (ret < 0) { + dev_err(&client->dev, "Error in reading ST1\n"); + return ret; + } + if (read_status) + break; + timeout_ms -= AK8975_CONVERSION_DONE_POLL_TIME; + } + if (!timeout_ms) { + dev_err(&client->dev, "Conversion timeout happened\n"); + return -EINVAL; + } + return read_status; +} + +/* + * Emits the raw flux value for the x, y, or z axis. + */ +static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) +{ + struct ak8975_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + u16 meas_reg; + s16 raw; + u8 read_status; + int ret; + + mutex_lock(&data->lock); + + if (data->mode == 0) { + dev_err(&client->dev, "Operating mode is in power down mode\n"); + ret = -EBUSY; + goto exit; + } + + /* Set up the device for taking a sample. */ + ret = ak8975_write_data(client, + AK8975_REG_CNTL, + AK8975_REG_CNTL_MODE_ONCE, + AK8975_REG_CNTL_MODE_MASK, + AK8975_REG_CNTL_MODE_SHIFT); + if (ret < 0) { + dev_err(&client->dev, "Error in setting operating mode\n"); + goto exit; + } + + /* Wait for the conversion to complete. */ + if (gpio_is_valid(data->eoc_gpio)) + ret = wait_conversion_complete_gpio(data); + else + ret = wait_conversion_complete_polled(data); + if (ret < 0) + goto exit; + + read_status = ret; + + if (read_status & AK8975_REG_ST1_DRDY_MASK) { + ret = ak8975_read_data(client, AK8975_REG_ST2, 1, &read_status); + if (ret < 0) { + dev_err(&client->dev, "Error in reading ST2\n"); + goto exit; + } + if (read_status & (AK8975_REG_ST2_DERR_MASK | + AK8975_REG_ST2_HOFL_MASK)) { + dev_err(&client->dev, "ST2 status error 0x%x\n", + read_status); + ret = -EINVAL; + goto exit; + } + } + + /* Read the flux value from the appropriate register + (the register is specified in the iio device attributes). */ + ret = ak8975_read_data(client, ak8975_index_to_reg[index], + 2, (u8 *)&meas_reg); + if (ret < 0) { + dev_err(&client->dev, "Read axis data fails\n"); + goto exit; + } + + mutex_unlock(&data->lock); + + /* Endian conversion of the measured values. */ + raw = (s16) (le16_to_cpu(meas_reg)); + + /* Clamp to valid range. */ + raw = clamp_t(s16, raw, -4096, 4095); + *val = raw; + return IIO_VAL_INT; + +exit: + mutex_unlock(&data->lock); + return ret; +} + +static int ak8975_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct ak8975_data *data = iio_priv(indio_dev); + + switch (mask) { + case 0: + return ak8975_read_axis(indio_dev, chan->address, val); + case IIO_CHAN_INFO_SCALE: + *val = data->raw_to_gauss[chan->address]; + return IIO_VAL_INT; + } + return -EINVAL; +} + +#define AK8975_CHANNEL(axis, index) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask = IIO_CHAN_INFO_SCALE_SEPARATE_BIT, \ + .address = index, \ + } + +static const struct iio_chan_spec ak8975_channels[] = { + AK8975_CHANNEL(X, 0), AK8975_CHANNEL(Y, 1), AK8975_CHANNEL(Z, 2), +}; + +static IIO_DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, show_mode, store_mode, 0); + +static struct attribute *ak8975_attr[] = { + &iio_dev_attr_mode.dev_attr.attr, + NULL +}; + +static struct attribute_group ak8975_attr_group = { + .attrs = ak8975_attr, +}; + +static const struct iio_info ak8975_info = { + .attrs = &ak8975_attr_group, + .read_raw = &ak8975_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ak8975_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ak8975_data *data; + struct iio_dev *indio_dev; + int eoc_gpio; + int err; + + /* Grab and set up the supplied GPIO. */ + if (client->dev.platform_data == NULL) + eoc_gpio = -1; + else + eoc_gpio = *(int *)(client->dev.platform_data); + + /* We may not have a GPIO based IRQ to scan, that is fine, we will + poll if so */ + if (gpio_is_valid(eoc_gpio)) { + err = gpio_request(eoc_gpio, "ak_8975"); + if (err < 0) { + dev_err(&client->dev, + "failed to request GPIO %d, error %d\n", + eoc_gpio, err); + goto exit; + } + + err = gpio_direction_input(eoc_gpio); + if (err < 0) { + dev_err(&client->dev, + "Failed to configure input direction for GPIO %d, error %d\n", + eoc_gpio, err); + goto exit_gpio; + } + } + + /* Register with IIO */ + indio_dev = iio_allocate_device(sizeof(*data)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto exit_gpio; + } + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + /* Perform some basic start-of-day setup of the device. */ + err = ak8975_setup(client); + if (err < 0) { + dev_err(&client->dev, "AK8975 initialization fails\n"); + goto exit_free_iio; + } + + data->client = client; + mutex_init(&data->lock); + data->eoc_irq = client->irq; + data->eoc_gpio = eoc_gpio; + indio_dev->dev.parent = &client->dev; + indio_dev->channels = ak8975_channels; + indio_dev->num_channels = ARRAY_SIZE(ak8975_channels); + indio_dev->info = &ak8975_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + err = iio_device_register(indio_dev); + if (err < 0) + goto exit_free_iio; + + return 0; + +exit_free_iio: + iio_free_device(indio_dev); +exit_gpio: + if (gpio_is_valid(eoc_gpio)) + gpio_free(eoc_gpio); +exit: + return err; +} + +static int ak8975_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ak8975_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (gpio_is_valid(data->eoc_gpio)) + gpio_free(data->eoc_gpio); + + iio_free_device(indio_dev); + + return 0; +} + +static const struct i2c_device_id ak8975_id[] = { + {"ak8975", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, ak8975_id); + +static const struct of_device_id ak8975_of_match[] = { + { .compatible = "asahi-kasei,ak8975", }, + { .compatible = "ak8975", }, + { } +}; +MODULE_DEVICE_TABLE(of, ak8975_of_match); + +static struct i2c_driver ak8975_driver = { + .driver = { + .name = "ak8975", + .of_match_table = ak8975_of_match, + }, + .probe = ak8975_probe, + .remove = __devexit_p(ak8975_remove), + .id_table = ak8975_id, +}; +module_i2c_driver(ak8975_driver); + +MODULE_AUTHOR("Laxman Dewangan "); +MODULE_DESCRIPTION("AK8975 magnetometer driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/magnetometer/hmc5843.c b/drivers/staging/iio/magnetometer/hmc5843.c new file mode 100644 index 00000000..e00b416c --- /dev/null +++ b/drivers/staging/iio/magnetometer/hmc5843.c @@ -0,0 +1,634 @@ +/* Copyright (C) 2010 Texas Instruments + Author: Shubhrajyoti Datta + Acknowledgement: Jonathan Cameron for valuable inputs. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include +#include +#include +#include +#include "../iio.h" +#include "../sysfs.h" + +#define HMC5843_I2C_ADDRESS 0x1E + +#define HMC5843_CONFIG_REG_A 0x00 +#define HMC5843_CONFIG_REG_B 0x01 +#define HMC5843_MODE_REG 0x02 +#define HMC5843_DATA_OUT_X_MSB_REG 0x03 +#define HMC5843_DATA_OUT_X_LSB_REG 0x04 +#define HMC5843_DATA_OUT_Y_MSB_REG 0x05 +#define HMC5843_DATA_OUT_Y_LSB_REG 0x06 +#define HMC5843_DATA_OUT_Z_MSB_REG 0x07 +#define HMC5843_DATA_OUT_Z_LSB_REG 0x08 +#define HMC5843_STATUS_REG 0x09 +#define HMC5843_ID_REG_A 0x0A +#define HMC5843_ID_REG_B 0x0B +#define HMC5843_ID_REG_C 0x0C + +#define HMC5843_ID_REG_LENGTH 0x03 +#define HMC5843_ID_STRING "H43" + +/* + * Range settings in (+-)Ga + * */ +#define RANGE_GAIN_OFFSET 0x05 + +#define RANGE_0_7 0x00 +#define RANGE_1_0 0x01 /* default */ +#define RANGE_1_5 0x02 +#define RANGE_2_0 0x03 +#define RANGE_3_2 0x04 +#define RANGE_3_8 0x05 +#define RANGE_4_5 0x06 +#define RANGE_6_5 0x07 /* Not recommended */ + +/* + * Device status + */ +#define DATA_READY 0x01 +#define DATA_OUTPUT_LOCK 0x02 +#define VOLTAGE_REGULATOR_ENABLED 0x04 + +/* + * Mode register configuration + */ +#define MODE_CONVERSION_CONTINUOUS 0x00 +#define MODE_CONVERSION_SINGLE 0x01 +#define MODE_IDLE 0x02 +#define MODE_SLEEP 0x03 + +/* Minimum Data Output Rate in 1/10 Hz */ +#define RATE_OFFSET 0x02 +#define RATE_BITMASK 0x1C +#define RATE_5 0x00 +#define RATE_10 0x01 +#define RATE_20 0x02 +#define RATE_50 0x03 +#define RATE_100 0x04 +#define RATE_200 0x05 +#define RATE_500 0x06 +#define RATE_NOT_USED 0x07 + +/* + * Device Configuration + */ +#define CONF_NORMAL 0x00 +#define CONF_POSITIVE_BIAS 0x01 +#define CONF_NEGATIVE_BIAS 0x02 +#define CONF_NOT_USED 0x03 +#define MEAS_CONF_MASK 0x03 + +static int hmc5843_regval_to_nanoscale[] = { + 6173, 7692, 10309, 12821, 18868, 21739, 25641, 35714 +}; + +static const int regval_to_input_field_mg[] = { + 700, + 1000, + 1500, + 2000, + 3200, + 3800, + 4500, + 6500 +}; +static const char * const regval_to_samp_freq[] = { + "0.5", + "1", + "2", + "5", + "10", + "20", + "50", +}; + +/* Addresses to scan: 0x1E */ +static const unsigned short normal_i2c[] = { HMC5843_I2C_ADDRESS, + I2C_CLIENT_END }; + +/* Each client has this additional data */ +struct hmc5843_data { + struct mutex lock; + u8 rate; + u8 meas_conf; + u8 operating_mode; + u8 range; +}; + +static void hmc5843_init_client(struct i2c_client *client); + +static s32 hmc5843_configure(struct i2c_client *client, + u8 operating_mode) +{ + /* The lower two bits contain the current conversion mode */ + return i2c_smbus_write_byte_data(client, + HMC5843_MODE_REG, + (operating_mode & 0x03)); +} + +/* Return the measurement value from the specified channel */ +static int hmc5843_read_measurement(struct iio_dev *indio_dev, + int address, + int *val) +{ + struct i2c_client *client = to_i2c_client(indio_dev->dev.parent); + struct hmc5843_data *data = iio_priv(indio_dev); + s32 result; + + mutex_lock(&data->lock); + result = i2c_smbus_read_byte_data(client, HMC5843_STATUS_REG); + while (!(result & DATA_READY)) + result = i2c_smbus_read_byte_data(client, HMC5843_STATUS_REG); + + result = i2c_smbus_read_word_data(client, address); + mutex_unlock(&data->lock); + if (result < 0) + return -EINVAL; + + *val = (s16)swab16((u16)result); + return IIO_VAL_INT; +} + + +/* + * From the datasheet + * 0 - Continuous-Conversion Mode: In continuous-conversion mode, the + * device continuously performs conversions and places the result in the + * data register. + * + * 1 - Single-Conversion Mode : device performs a single measurement, + * sets RDY high and returned to sleep mode + * + * 2 - Idle Mode : Device is placed in idle mode. + * + * 3 - Sleep Mode. Device is placed in sleep mode. + * + */ +static ssize_t hmc5843_show_operating_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct hmc5843_data *data = iio_priv(indio_dev); + return sprintf(buf, "%d\n", data->operating_mode); +} + +static ssize_t hmc5843_set_operating_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(indio_dev->dev.parent); + struct hmc5843_data *data = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long operating_mode = 0; + s32 status; + int error; + mutex_lock(&data->lock); + error = strict_strtoul(buf, 10, &operating_mode); + if (error) { + count = error; + goto exit; + } + dev_dbg(dev, "set Conversion mode to %lu\n", operating_mode); + if (operating_mode > MODE_SLEEP) { + count = -EINVAL; + goto exit; + } + + status = i2c_smbus_write_byte_data(client, this_attr->address, + operating_mode); + if (status) { + count = -EINVAL; + goto exit; + } + data->operating_mode = operating_mode; + +exit: + mutex_unlock(&data->lock); + return count; +} +static IIO_DEVICE_ATTR(operating_mode, + S_IWUSR | S_IRUGO, + hmc5843_show_operating_mode, + hmc5843_set_operating_mode, + HMC5843_MODE_REG); + +/* + * API for setting the measurement configuration to + * Normal, Positive bias and Negative bias + * From the datasheet + * + * Normal measurement configuration (default): In normal measurement + * configuration the device follows normal measurement flow. Pins BP and BN + * are left floating and high impedance. + * + * Positive bias configuration: In positive bias configuration, a positive + * current is forced across the resistive load on pins BP and BN. + * + * Negative bias configuration. In negative bias configuration, a negative + * current is forced across the resistive load on pins BP and BN. + * + */ +static s32 hmc5843_set_meas_conf(struct i2c_client *client, + u8 meas_conf) +{ + struct hmc5843_data *data = i2c_get_clientdata(client); + u8 reg_val; + reg_val = (meas_conf & MEAS_CONF_MASK) | (data->rate << RATE_OFFSET); + return i2c_smbus_write_byte_data(client, HMC5843_CONFIG_REG_A, reg_val); +} + +static ssize_t hmc5843_show_measurement_configuration(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct hmc5843_data *data = iio_priv(indio_dev); + return sprintf(buf, "%d\n", data->meas_conf); +} + +static ssize_t hmc5843_set_measurement_configuration(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(indio_dev->dev.parent); + struct hmc5843_data *data = i2c_get_clientdata(client); + unsigned long meas_conf = 0; + int error = strict_strtoul(buf, 10, &meas_conf); + if (error) + return error; + mutex_lock(&data->lock); + + dev_dbg(dev, "set mode to %lu\n", meas_conf); + if (hmc5843_set_meas_conf(client, meas_conf)) { + count = -EINVAL; + goto exit; + } + data->meas_conf = meas_conf; + +exit: + mutex_unlock(&data->lock); + return count; +} +static IIO_DEVICE_ATTR(meas_conf, + S_IWUSR | S_IRUGO, + hmc5843_show_measurement_configuration, + hmc5843_set_measurement_configuration, + 0); + +/* + * From Datasheet + * The table shows the minimum data output + * Value | Minimum data output rate(Hz) + * 0 | 0.5 + * 1 | 1 + * 2 | 2 + * 3 | 5 + * 4 | 10 (default) + * 5 | 20 + * 6 | 50 + * 7 | Not used + */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("0.5 1 2 5 10 20 50"); + +static s32 hmc5843_set_rate(struct i2c_client *client, + u8 rate) +{ + struct hmc5843_data *data = i2c_get_clientdata(client); + u8 reg_val; + + reg_val = (data->meas_conf) | (rate << RATE_OFFSET); + if (rate >= RATE_NOT_USED) { + dev_err(&client->dev, + "This data output rate is not supported\n"); + return -EINVAL; + } + return i2c_smbus_write_byte_data(client, HMC5843_CONFIG_REG_A, reg_val); +} + +static ssize_t set_sampling_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(indio_dev->dev.parent); + struct hmc5843_data *data = iio_priv(indio_dev); + unsigned long rate = 0; + + if (strncmp(buf, "0.5" , 3) == 0) + rate = RATE_5; + else if (strncmp(buf, "1" , 1) == 0) + rate = RATE_10; + else if (strncmp(buf, "2", 1) == 0) + rate = RATE_20; + else if (strncmp(buf, "5", 1) == 0) + rate = RATE_50; + else if (strncmp(buf, "10", 2) == 0) + rate = RATE_100; + else if (strncmp(buf, "20" , 2) == 0) + rate = RATE_200; + else if (strncmp(buf, "50" , 2) == 0) + rate = RATE_500; + else + return -EINVAL; + + mutex_lock(&data->lock); + dev_dbg(dev, "set rate to %lu\n", rate); + if (hmc5843_set_rate(client, rate)) { + count = -EINVAL; + goto exit; + } + data->rate = rate; + +exit: + mutex_unlock(&data->lock); + return count; +} + +static ssize_t show_sampling_frequency(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(indio_dev->dev.parent); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + s32 rate; + + rate = i2c_smbus_read_byte_data(client, this_attr->address); + if (rate < 0) + return rate; + rate = (rate & RATE_BITMASK) >> RATE_OFFSET; + return sprintf(buf, "%s\n", regval_to_samp_freq[rate]); +} +static IIO_DEVICE_ATTR(sampling_frequency, + S_IWUSR | S_IRUGO, + show_sampling_frequency, + set_sampling_frequency, + HMC5843_CONFIG_REG_A); + +/* + * From Datasheet + * Nominal gain settings + * Value | Sensor Input Field Range(Ga) | Gain(counts/ milli-gauss) + *0 |(+-)0.7 |1620 + *1 |(+-)1.0 |1300 + *2 |(+-)1.5 |970 + *3 |(+-)2.0 |780 + *4 |(+-)3.2 |530 + *5 |(+-)3.8 |460 + *6 |(+-)4.5 |390 + *7 |(+-)6.5 |280 + */ +static ssize_t show_range(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 range; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct hmc5843_data *data = iio_priv(indio_dev); + + range = data->range; + return sprintf(buf, "%d\n", regval_to_input_field_mg[range]); +} + +static ssize_t set_range(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct i2c_client *client = to_i2c_client(indio_dev->dev.parent); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct hmc5843_data *data = iio_priv(indio_dev); + unsigned long range = 0; + int error; + mutex_lock(&data->lock); + error = strict_strtoul(buf, 10, &range); + if (error) { + count = error; + goto exit; + } + dev_dbg(dev, "set range to %lu\n", range); + + if (range > RANGE_6_5) { + count = -EINVAL; + goto exit; + } + + data->range = range; + range = range << RANGE_GAIN_OFFSET; + if (i2c_smbus_write_byte_data(client, this_attr->address, range)) + count = -EINVAL; + +exit: + mutex_unlock(&data->lock); + return count; + +} +static IIO_DEVICE_ATTR(in_magn_range, + S_IWUSR | S_IRUGO, + show_range, + set_range, + HMC5843_CONFIG_REG_B); + +static int hmc5843_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + + switch (mask) { + case 0: + return hmc5843_read_measurement(indio_dev, + chan->address, + val); + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = hmc5843_regval_to_nanoscale[data->range]; + return IIO_VAL_INT_PLUS_NANO; + }; + return -EINVAL; +} + +#define HMC5843_CHANNEL(axis, add) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask = IIO_CHAN_INFO_SCALE_SHARED_BIT, \ + .address = add \ + } + +static const struct iio_chan_spec hmc5843_channels[] = { + HMC5843_CHANNEL(X, HMC5843_DATA_OUT_X_MSB_REG), + HMC5843_CHANNEL(Y, HMC5843_DATA_OUT_Y_MSB_REG), + HMC5843_CHANNEL(Z, HMC5843_DATA_OUT_Z_MSB_REG), +}; + +static struct attribute *hmc5843_attributes[] = { + &iio_dev_attr_meas_conf.dev_attr.attr, + &iio_dev_attr_operating_mode.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_in_magn_range.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group hmc5843_group = { + .attrs = hmc5843_attributes, +}; + +static int hmc5843_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + unsigned char id_str[HMC5843_ID_REG_LENGTH]; + + if (client->addr != HMC5843_I2C_ADDRESS) + return -ENODEV; + + if (i2c_smbus_read_i2c_block_data(client, HMC5843_ID_REG_A, + HMC5843_ID_REG_LENGTH, id_str) + != HMC5843_ID_REG_LENGTH) + return -ENODEV; + + if (0 != strncmp(id_str, HMC5843_ID_STRING, HMC5843_ID_REG_LENGTH)) + return -ENODEV; + + return 0; +} + +/* Called when we have found a new HMC5843. */ +static void hmc5843_init_client(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct hmc5843_data *data = iio_priv(indio_dev); + + hmc5843_set_meas_conf(client, data->meas_conf); + hmc5843_set_rate(client, data->rate); + hmc5843_configure(client, data->operating_mode); + i2c_smbus_write_byte_data(client, HMC5843_CONFIG_REG_B, data->range); + mutex_init(&data->lock); + pr_info("HMC5843 initialized\n"); +} + +static const struct iio_info hmc5843_info = { + .attrs = &hmc5843_group, + .read_raw = &hmc5843_read_raw, + .driver_module = THIS_MODULE, +}; + +static int hmc5843_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct hmc5843_data *data; + struct iio_dev *indio_dev; + int err = 0; + + indio_dev = iio_allocate_device(sizeof(*data)); + if (indio_dev == NULL) { + err = -ENOMEM; + goto exit; + } + data = iio_priv(indio_dev); + /* default settings at probe */ + + data->meas_conf = CONF_NORMAL; + data->range = RANGE_1_0; + data->operating_mode = MODE_CONVERSION_CONTINUOUS; + + i2c_set_clientdata(client, indio_dev); + + /* Initialize the HMC5843 chip */ + hmc5843_init_client(client); + + indio_dev->info = &hmc5843_info; + indio_dev->name = id->name; + indio_dev->channels = hmc5843_channels; + indio_dev->num_channels = ARRAY_SIZE(hmc5843_channels); + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + err = iio_device_register(indio_dev); + if (err) + goto exit_free2; + return 0; +exit_free2: + iio_free_device(indio_dev); +exit: + return err; +} + +static int hmc5843_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + /* sleep mode to save power */ + hmc5843_configure(client, MODE_SLEEP); + iio_free_device(indio_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int hmc5843_suspend(struct device *dev) +{ + hmc5843_configure(to_i2c_client(dev), MODE_SLEEP); + return 0; +} + +static int hmc5843_resume(struct device *dev) +{ + struct hmc5843_data *data = i2c_get_clientdata(to_i2c_client(dev)); + hmc5843_configure(to_i2c_client(dev), data->operating_mode); + return 0; +} + +static SIMPLE_DEV_PM_OPS(hmc5843_pm_ops, hmc5843_suspend, hmc5843_resume); +#define HMC5843_PM_OPS (&hmc5843_pm_ops) +#else +#define HMC5843_PM_OPS NULL +#endif + +static const struct i2c_device_id hmc5843_id[] = { + { "hmc5843", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hmc5843_id); + +static struct i2c_driver hmc5843_driver = { + .driver = { + .name = "hmc5843", + .pm = HMC5843_PM_OPS, + }, + .id_table = hmc5843_id, + .probe = hmc5843_probe, + .remove = hmc5843_remove, + .detect = hmc5843_detect, + .address_list = normal_i2c, +}; +module_i2c_driver(hmc5843_driver); + +MODULE_AUTHOR("Shubhrajyoti Datta +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "meter.h" +#include "ade7753.h" + +static int ade7753_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 val) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7753_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7753_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7753_spi_write_reg_16(struct device *dev, + u8 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7753_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7753_WRITE_REG(reg_address); + st->tx[1] = (value >> 8) & 0xFF; + st->tx[2] = value & 0xFF; + ret = spi_write(st->us, st->tx, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7753_spi_read_reg_8(struct device *dev, + u8 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7753_state *st = iio_priv(indio_dev); + ssize_t ret; + + ret = spi_w8r8(st->us, ADE7753_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + return ret; + } + *val = ret; + + return 0; +} + +static int ade7753_spi_read_reg_16(struct device *dev, + u8 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7753_state *st = iio_priv(indio_dev); + ssize_t ret; + + ret = spi_w8r16(st->us, ADE7753_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + return ret; + } + + *val = ret; + *val = be16_to_cpup(val); + + return 0; +} + +static int ade7753_spi_read_reg_24(struct device *dev, + u8 reg_address, + u32 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7753_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 1, + }, { + .rx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + } + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7753_READ_REG(reg_address); + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 24 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = (st->rx[0] << 16) | (st->rx[1] << 8) | st->rx[2]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t ade7753_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7753_spi_read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7753_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7753_spi_read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7753_read_24bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7753_spi_read_reg_24(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7753_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7753_spi_write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7753_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7753_spi_write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7753_reset(struct device *dev) +{ + u16 val; + + ade7753_spi_read_reg_16(dev, ADE7753_MODE, &val); + val |= 1 << 6; /* Software Chip Reset */ + + return ade7753_spi_write_reg_16(dev, ADE7753_MODE, val); +} + +static ssize_t ade7753_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + if (len < 1) + return -1; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return ade7753_reset(dev); + } + return -1; +} + +static IIO_DEV_ATTR_AENERGY(ade7753_read_24bit, ADE7753_AENERGY); +static IIO_DEV_ATTR_LAENERGY(ade7753_read_24bit, ADE7753_LAENERGY); +static IIO_DEV_ATTR_VAENERGY(ade7753_read_24bit, ADE7753_VAENERGY); +static IIO_DEV_ATTR_LVAENERGY(ade7753_read_24bit, ADE7753_LVAENERGY); +static IIO_DEV_ATTR_CFDEN(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_CFDEN); +static IIO_DEV_ATTR_CFNUM(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_CFNUM); +static IIO_DEV_ATTR_CHKSUM(ade7753_read_8bit, ADE7753_CHKSUM); +static IIO_DEV_ATTR_PHCAL(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_PHCAL); +static IIO_DEV_ATTR_APOS(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_APOS); +static IIO_DEV_ATTR_SAGCYC(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_SAGCYC); +static IIO_DEV_ATTR_SAGLVL(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_SAGLVL); +static IIO_DEV_ATTR_LINECYC(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_LINECYC); +static IIO_DEV_ATTR_WDIV(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_WDIV); +static IIO_DEV_ATTR_IRMS(S_IWUSR | S_IRUGO, + ade7753_read_24bit, + NULL, + ADE7753_IRMS); +static IIO_DEV_ATTR_VRMS(S_IRUGO, + ade7753_read_24bit, + NULL, + ADE7753_VRMS); +static IIO_DEV_ATTR_IRMSOS(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_IRMSOS); +static IIO_DEV_ATTR_VRMSOS(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_VRMSOS); +static IIO_DEV_ATTR_WGAIN(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_WGAIN); +static IIO_DEV_ATTR_VAGAIN(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_VAGAIN); +static IIO_DEV_ATTR_PGA_GAIN(S_IWUSR | S_IRUGO, + ade7753_read_16bit, + ade7753_write_16bit, + ADE7753_GAIN); +static IIO_DEV_ATTR_IPKLVL(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_IPKLVL); +static IIO_DEV_ATTR_VPKLVL(S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_VPKLVL); +static IIO_DEV_ATTR_IPEAK(S_IRUGO, + ade7753_read_24bit, + NULL, + ADE7753_IPEAK); +static IIO_DEV_ATTR_VPEAK(S_IRUGO, + ade7753_read_24bit, + NULL, + ADE7753_VPEAK); +static IIO_DEV_ATTR_VPERIOD(S_IRUGO, + ade7753_read_16bit, + NULL, + ADE7753_PERIOD); +static IIO_DEV_ATTR_CH_OFF(1, S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_CH1OS); +static IIO_DEV_ATTR_CH_OFF(2, S_IWUSR | S_IRUGO, + ade7753_read_8bit, + ade7753_write_8bit, + ADE7753_CH2OS); + +static int ade7753_set_irq(struct device *dev, bool enable) +{ + int ret; + u8 irqen; + ret = ade7753_spi_read_reg_8(dev, ADE7753_IRQEN, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 3; /* Enables an interrupt when a data is + present in the waveform register */ + else + irqen &= ~(1 << 3); + + ret = ade7753_spi_write_reg_8(dev, ADE7753_IRQEN, irqen); + +error_ret: + return ret; +} + +/* Power down the device */ +static int ade7753_stop_device(struct device *dev) +{ + u16 val; + + ade7753_spi_read_reg_16(dev, ADE7753_MODE, &val); + val |= 1 << 4; /* AD converters can be turned off */ + + return ade7753_spi_write_reg_16(dev, ADE7753_MODE, val); +} + +static int ade7753_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &indio_dev->dev; + struct ade7753_state *st = iio_priv(indio_dev); + + /* use low spi speed for init */ + st->us->mode = SPI_MODE_3; + spi_setup(st->us); + + /* Disable IRQ */ + ret = ade7753_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7753_reset(dev); + msleep(ADE7753_STARTUP_DELAY); + +err_ret: + return ret; +} + +static ssize_t ade7753_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret, len = 0; + u16 t; + int sps; + ret = ade7753_spi_read_reg_16(dev, ADE7753_MODE, &t); + if (ret) + return ret; + + t = (t >> 11) & 0x3; + sps = 27900 / (1 + t); + + len = sprintf(buf, "%d\n", sps); + return len; +} + +static ssize_t ade7753_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7753_state *st = iio_priv(indio_dev); + unsigned long val; + int ret; + u16 reg, t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + t = (27900 / val); + if (t > 0) + t--; + + if (t > 1) + st->us->max_speed_hz = ADE7753_SPI_SLOW; + else + st->us->max_speed_hz = ADE7753_SPI_FAST; + + ret = ade7753_spi_read_reg_16(dev, ADE7753_MODE, ®); + if (ret) + goto out; + + reg &= ~(3 << 11); + reg |= t << 11; + + ret = ade7753_spi_write_reg_16(dev, ADE7753_MODE, reg); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_TEMP_RAW(ade7753_read_8bit); +static IIO_CONST_ATTR(in_temp_offset, "-25 C"); +static IIO_CONST_ATTR(in_temp_scale, "0.67 C"); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ade7753_read_frequency, + ade7753_write_frequency); + +static IIO_DEV_ATTR_RESET(ade7753_write_reset); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("27900 14000 7000 3500"); + +static struct attribute *ade7753_attributes[] = { + &iio_dev_attr_in_temp_raw.dev_attr.attr, + &iio_const_attr_in_temp_offset.dev_attr.attr, + &iio_const_attr_in_temp_scale.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + &iio_dev_attr_phcal.dev_attr.attr, + &iio_dev_attr_cfden.dev_attr.attr, + &iio_dev_attr_aenergy.dev_attr.attr, + &iio_dev_attr_laenergy.dev_attr.attr, + &iio_dev_attr_vaenergy.dev_attr.attr, + &iio_dev_attr_lvaenergy.dev_attr.attr, + &iio_dev_attr_cfnum.dev_attr.attr, + &iio_dev_attr_apos.dev_attr.attr, + &iio_dev_attr_sagcyc.dev_attr.attr, + &iio_dev_attr_saglvl.dev_attr.attr, + &iio_dev_attr_linecyc.dev_attr.attr, + &iio_dev_attr_chksum.dev_attr.attr, + &iio_dev_attr_pga_gain.dev_attr.attr, + &iio_dev_attr_wgain.dev_attr.attr, + &iio_dev_attr_choff_1.dev_attr.attr, + &iio_dev_attr_choff_2.dev_attr.attr, + &iio_dev_attr_wdiv.dev_attr.attr, + &iio_dev_attr_irms.dev_attr.attr, + &iio_dev_attr_vrms.dev_attr.attr, + &iio_dev_attr_irmsos.dev_attr.attr, + &iio_dev_attr_vrmsos.dev_attr.attr, + &iio_dev_attr_vagain.dev_attr.attr, + &iio_dev_attr_ipklvl.dev_attr.attr, + &iio_dev_attr_vpklvl.dev_attr.attr, + &iio_dev_attr_ipeak.dev_attr.attr, + &iio_dev_attr_vpeak.dev_attr.attr, + &iio_dev_attr_vperiod.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7753_attribute_group = { + .attrs = ade7753_attributes, +}; + +static const struct iio_info ade7753_info = { + .attrs = &ade7753_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ade7753_probe(struct spi_device *spi) +{ + int ret; + struct ade7753_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ade7753_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Get the device into a sane initial state */ + ret = ade7753_initial_setup(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + return 0; + +error_free_dev: + iio_free_device(indio_dev); + +error_ret: + return ret; +} + +/* fixme, confirm ordering in this function */ +static int ade7753_remove(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + + ret = ade7753_stop_device(&(indio_dev->dev)); + if (ret) + goto err_ret; + + iio_free_device(indio_dev); +err_ret: + return ret; +} + +static struct spi_driver ade7753_driver = { + .driver = { + .name = "ade7753", + .owner = THIS_MODULE, + }, + .probe = ade7753_probe, + .remove = __devexit_p(ade7753_remove), +}; +module_spi_driver(ade7753_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7753/6 Single-Phase Multifunction Meter"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ade7753"); diff --git a/drivers/staging/iio/meter/ade7753.h b/drivers/staging/iio/meter/ade7753.h new file mode 100644 index 00000000..3f059d3d --- /dev/null +++ b/drivers/staging/iio/meter/ade7753.h @@ -0,0 +1,74 @@ +#ifndef _ADE7753_H +#define _ADE7753_H + +#define ADE7753_WAVEFORM 0x01 +#define ADE7753_AENERGY 0x02 +#define ADE7753_RAENERGY 0x03 +#define ADE7753_LAENERGY 0x04 +#define ADE7753_VAENERGY 0x05 +#define ADE7753_RVAENERGY 0x06 +#define ADE7753_LVAENERGY 0x07 +#define ADE7753_LVARENERGY 0x08 +#define ADE7753_MODE 0x09 +#define ADE7753_IRQEN 0x0A +#define ADE7753_STATUS 0x0B +#define ADE7753_RSTSTATUS 0x0C +#define ADE7753_CH1OS 0x0D +#define ADE7753_CH2OS 0x0E +#define ADE7753_GAIN 0x0F +#define ADE7753_PHCAL 0x10 +#define ADE7753_APOS 0x11 +#define ADE7753_WGAIN 0x12 +#define ADE7753_WDIV 0x13 +#define ADE7753_CFNUM 0x14 +#define ADE7753_CFDEN 0x15 +#define ADE7753_IRMS 0x16 +#define ADE7753_VRMS 0x17 +#define ADE7753_IRMSOS 0x18 +#define ADE7753_VRMSOS 0x19 +#define ADE7753_VAGAIN 0x1A +#define ADE7753_VADIV 0x1B +#define ADE7753_LINECYC 0x1C +#define ADE7753_ZXTOUT 0x1D +#define ADE7753_SAGCYC 0x1E +#define ADE7753_SAGLVL 0x1F +#define ADE7753_IPKLVL 0x20 +#define ADE7753_VPKLVL 0x21 +#define ADE7753_IPEAK 0x22 +#define ADE7753_RSTIPEAK 0x23 +#define ADE7753_VPEAK 0x24 +#define ADE7753_RSTVPEAK 0x25 +#define ADE7753_TEMP 0x26 +#define ADE7753_PERIOD 0x27 +#define ADE7753_TMODE 0x3D +#define ADE7753_CHKSUM 0x3E +#define ADE7753_DIEREV 0x3F + +#define ADE7753_READ_REG(a) a +#define ADE7753_WRITE_REG(a) ((a) | 0x80) + +#define ADE7753_MAX_TX 4 +#define ADE7753_MAX_RX 4 +#define ADE7753_STARTUP_DELAY 1 + +#define ADE7753_SPI_SLOW (u32)(300 * 1000) +#define ADE7753_SPI_BURST (u32)(1000 * 1000) +#define ADE7753_SPI_FAST (u32)(2000 * 1000) + +#define DRIVER_NAME "ade7753" + +/** + * struct ade7753_state - device instance specific data + * @us: actual spi_device + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct ade7753_state { + struct spi_device *us; + struct mutex buf_lock; + u8 tx[ADE7753_MAX_TX] ____cacheline_aligned; + u8 rx[ADE7753_MAX_RX]; +}; + +#endif diff --git a/drivers/staging/iio/meter/ade7754.c b/drivers/staging/iio/meter/ade7754.c new file mode 100644 index 00000000..8d81c920 --- /dev/null +++ b/drivers/staging/iio/meter/ade7754.c @@ -0,0 +1,608 @@ +/* + * ADE7754 Polyphase Multifunction Energy Metering IC Driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "meter.h" +#include "ade7754.h" + +static int ade7754_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 val) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7754_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7754_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7754_spi_write_reg_16(struct device *dev, + u8 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7754_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7754_WRITE_REG(reg_address); + st->tx[1] = (value >> 8) & 0xFF; + st->tx[2] = value & 0xFF; + ret = spi_write(st->us, st->tx, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7754_spi_read_reg_8(struct device *dev, + u8 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7754_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_w8r8(st->us, ADE7754_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + return ret; + } + *val = ret; + + return 0; +} + +static int ade7754_spi_read_reg_16(struct device *dev, + u8 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7754_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_w8r16(st->us, ADE7754_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + return ret; + } + + *val = ret; + *val = be16_to_cpup(val); + + return 0; +} + +static int ade7754_spi_read_reg_24(struct device *dev, + u8 reg_address, + u32 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7754_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 4, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7754_READ_REG(reg_address); + st->tx[1] = 0; + st->tx[2] = 0; + st->tx[3] = 0; + + spi_message_init(&msg); + spi_message_add_tail(xfers, &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 24 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = (st->rx[1] << 16) | (st->rx[2] << 8) | st->rx[3]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t ade7754_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7754_spi_read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7754_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7754_spi_read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7754_read_24bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7754_spi_read_reg_24(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val & 0xFFFFFF); +} + +static ssize_t ade7754_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7754_spi_write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7754_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7754_spi_write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7754_reset(struct device *dev) +{ + u8 val; + + ade7754_spi_read_reg_8(dev, ADE7754_OPMODE, &val); + val |= 1 << 6; /* Software Chip Reset */ + return ade7754_spi_write_reg_8(dev, ADE7754_OPMODE, val); +} + + +static ssize_t ade7754_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + if (len < 1) + return -1; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return ade7754_reset(dev); + } + return -1; +} + +static IIO_DEV_ATTR_AENERGY(ade7754_read_24bit, ADE7754_AENERGY); +static IIO_DEV_ATTR_LAENERGY(ade7754_read_24bit, ADE7754_LAENERGY); +static IIO_DEV_ATTR_VAENERGY(ade7754_read_24bit, ADE7754_VAENERGY); +static IIO_DEV_ATTR_LVAENERGY(ade7754_read_24bit, ADE7754_LVAENERGY); +static IIO_DEV_ATTR_VPEAK(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_VPEAK); +static IIO_DEV_ATTR_IPEAK(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_VPEAK); +static IIO_DEV_ATTR_APHCAL(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_APHCAL); +static IIO_DEV_ATTR_BPHCAL(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_BPHCAL); +static IIO_DEV_ATTR_CPHCAL(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_CPHCAL); +static IIO_DEV_ATTR_AAPOS(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_AAPOS); +static IIO_DEV_ATTR_BAPOS(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_BAPOS); +static IIO_DEV_ATTR_CAPOS(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CAPOS); +static IIO_DEV_ATTR_WDIV(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_WDIV); +static IIO_DEV_ATTR_VADIV(S_IWUSR | S_IRUGO, + ade7754_read_8bit, + ade7754_write_8bit, + ADE7754_VADIV); +static IIO_DEV_ATTR_CFNUM(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CFNUM); +static IIO_DEV_ATTR_CFDEN(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CFDEN); +static IIO_DEV_ATTR_ACTIVE_POWER_A_GAIN(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_AAPGAIN); +static IIO_DEV_ATTR_ACTIVE_POWER_B_GAIN(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_BAPGAIN); +static IIO_DEV_ATTR_ACTIVE_POWER_C_GAIN(S_IWUSR | S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CAPGAIN); +static IIO_DEV_ATTR_AIRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_AIRMS); +static IIO_DEV_ATTR_BIRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_BIRMS); +static IIO_DEV_ATTR_CIRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_CIRMS); +static IIO_DEV_ATTR_AVRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_AVRMS); +static IIO_DEV_ATTR_BVRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_BVRMS); +static IIO_DEV_ATTR_CVRMS(S_IRUGO, + ade7754_read_24bit, + NULL, + ADE7754_CVRMS); +static IIO_DEV_ATTR_AIRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_AIRMSOS); +static IIO_DEV_ATTR_BIRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_BIRMSOS); +static IIO_DEV_ATTR_CIRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CIRMSOS); +static IIO_DEV_ATTR_AVRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_AVRMSOS); +static IIO_DEV_ATTR_BVRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_BVRMSOS); +static IIO_DEV_ATTR_CVRMSOS(S_IRUGO, + ade7754_read_16bit, + ade7754_write_16bit, + ADE7754_CVRMSOS); + +static int ade7754_set_irq(struct device *dev, bool enable) +{ + int ret; + u16 irqen; + ret = ade7754_spi_read_reg_16(dev, ADE7754_IRQEN, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 14; /* Enables an interrupt when a data is + present in the waveform register */ + else + irqen &= ~(1 << 14); + + ret = ade7754_spi_write_reg_16(dev, ADE7754_IRQEN, irqen); + if (ret) + goto error_ret; + +error_ret: + return ret; +} + +/* Power down the device */ +static int ade7754_stop_device(struct device *dev) +{ + u8 val; + + ade7754_spi_read_reg_8(dev, ADE7754_OPMODE, &val); + val |= 7 << 3; /* ADE7754 powered down */ + return ade7754_spi_write_reg_8(dev, ADE7754_OPMODE, val); +} + +static int ade7754_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct ade7754_state *st = iio_priv(indio_dev); + struct device *dev = &indio_dev->dev; + + /* use low spi speed for init */ + st->us->mode = SPI_MODE_3; + spi_setup(st->us); + + /* Disable IRQ */ + ret = ade7754_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7754_reset(dev); + msleep(ADE7754_STARTUP_DELAY); + +err_ret: + return ret; +} + +static ssize_t ade7754_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 t; + int sps; + ret = ade7754_spi_read_reg_8(dev, + ADE7754_WAVMODE, + &t); + if (ret) + return ret; + + t = (t >> 3) & 0x3; + sps = 26000 / (1 + t); + + return sprintf(buf, "%d\n", sps); +} + +static ssize_t ade7754_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7754_state *st = iio_priv(indio_dev); + unsigned long val; + int ret; + u8 reg, t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + t = (26000 / val); + if (t > 0) + t--; + + if (t > 1) + st->us->max_speed_hz = ADE7754_SPI_SLOW; + else + st->us->max_speed_hz = ADE7754_SPI_FAST; + + ret = ade7754_spi_read_reg_8(dev, ADE7754_WAVMODE, ®); + if (ret) + goto out; + + reg &= ~(3 << 3); + reg |= t << 3; + + ret = ade7754_spi_write_reg_8(dev, ADE7754_WAVMODE, reg); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} +static IIO_DEV_ATTR_TEMP_RAW(ade7754_read_8bit); +static IIO_CONST_ATTR(in_temp_offset, "129 C"); +static IIO_CONST_ATTR(in_temp_scale, "4 C"); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ade7754_read_frequency, + ade7754_write_frequency); + +static IIO_DEV_ATTR_RESET(ade7754_write_reset); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("26000 13000 65000 33000"); + +static struct attribute *ade7754_attributes[] = { + &iio_dev_attr_in_temp_raw.dev_attr.attr, + &iio_const_attr_in_temp_offset.dev_attr.attr, + &iio_const_attr_in_temp_scale.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + &iio_dev_attr_aenergy.dev_attr.attr, + &iio_dev_attr_laenergy.dev_attr.attr, + &iio_dev_attr_vaenergy.dev_attr.attr, + &iio_dev_attr_lvaenergy.dev_attr.attr, + &iio_dev_attr_vpeak.dev_attr.attr, + &iio_dev_attr_ipeak.dev_attr.attr, + &iio_dev_attr_aphcal.dev_attr.attr, + &iio_dev_attr_bphcal.dev_attr.attr, + &iio_dev_attr_cphcal.dev_attr.attr, + &iio_dev_attr_aapos.dev_attr.attr, + &iio_dev_attr_bapos.dev_attr.attr, + &iio_dev_attr_capos.dev_attr.attr, + &iio_dev_attr_wdiv.dev_attr.attr, + &iio_dev_attr_vadiv.dev_attr.attr, + &iio_dev_attr_cfnum.dev_attr.attr, + &iio_dev_attr_cfden.dev_attr.attr, + &iio_dev_attr_active_power_a_gain.dev_attr.attr, + &iio_dev_attr_active_power_b_gain.dev_attr.attr, + &iio_dev_attr_active_power_c_gain.dev_attr.attr, + &iio_dev_attr_airms.dev_attr.attr, + &iio_dev_attr_birms.dev_attr.attr, + &iio_dev_attr_cirms.dev_attr.attr, + &iio_dev_attr_avrms.dev_attr.attr, + &iio_dev_attr_bvrms.dev_attr.attr, + &iio_dev_attr_cvrms.dev_attr.attr, + &iio_dev_attr_airmsos.dev_attr.attr, + &iio_dev_attr_birmsos.dev_attr.attr, + &iio_dev_attr_cirmsos.dev_attr.attr, + &iio_dev_attr_avrmsos.dev_attr.attr, + &iio_dev_attr_bvrmsos.dev_attr.attr, + &iio_dev_attr_cvrmsos.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7754_attribute_group = { + .attrs = ade7754_attributes, +}; + +static const struct iio_info ade7754_info = { + .attrs = &ade7754_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ade7754_probe(struct spi_device *spi) +{ + int ret; + struct ade7754_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ade7754_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Get the device into a sane initial state */ + ret = ade7754_initial_setup(indio_dev); + if (ret) + goto error_free_dev; + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + return 0; + +error_free_dev: + iio_free_device(indio_dev); + +error_ret: + return ret; +} + +/* fixme, confirm ordering in this function */ +static int ade7754_remove(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + ret = ade7754_stop_device(&(indio_dev->dev)); + if (ret) + goto err_ret; + + iio_free_device(indio_dev); + +err_ret: + return ret; + +} + +static struct spi_driver ade7754_driver = { + .driver = { + .name = "ade7754", + .owner = THIS_MODULE, + }, + .probe = ade7754_probe, + .remove = __devexit_p(ade7754_remove), +}; +module_spi_driver(ade7754_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7754 Polyphase Multifunction Energy Metering IC Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ad7754"); diff --git a/drivers/staging/iio/meter/ade7754.h b/drivers/staging/iio/meter/ade7754.h new file mode 100644 index 00000000..61211255 --- /dev/null +++ b/drivers/staging/iio/meter/ade7754.h @@ -0,0 +1,92 @@ +#ifndef _ADE7754_H +#define _ADE7754_H + +#define ADE7754_AENERGY 0x01 +#define ADE7754_RAENERGY 0x02 +#define ADE7754_LAENERGY 0x03 +#define ADE7754_VAENERGY 0x04 +#define ADE7754_RVAENERGY 0x05 +#define ADE7754_LVAENERGY 0x06 +#define ADE7754_PERIOD 0x07 +#define ADE7754_TEMP 0x08 +#define ADE7754_WFORM 0x09 +#define ADE7754_OPMODE 0x0A +#define ADE7754_MMODE 0x0B +#define ADE7754_WAVMODE 0x0C +#define ADE7754_WATMODE 0x0D +#define ADE7754_VAMODE 0x0E +#define ADE7754_IRQEN 0x0F +#define ADE7754_STATUS 0x10 +#define ADE7754_RSTATUS 0x11 +#define ADE7754_ZXTOUT 0x12 +#define ADE7754_LINCYC 0x13 +#define ADE7754_SAGCYC 0x14 +#define ADE7754_SAGLVL 0x15 +#define ADE7754_VPEAK 0x16 +#define ADE7754_IPEAK 0x17 +#define ADE7754_GAIN 0x18 +#define ADE7754_AWG 0x19 +#define ADE7754_BWG 0x1A +#define ADE7754_CWG 0x1B +#define ADE7754_AVAG 0x1C +#define ADE7754_BVAG 0x1D +#define ADE7754_CVAG 0x1E +#define ADE7754_APHCAL 0x1F +#define ADE7754_BPHCAL 0x20 +#define ADE7754_CPHCAL 0x21 +#define ADE7754_AAPOS 0x22 +#define ADE7754_BAPOS 0x23 +#define ADE7754_CAPOS 0x24 +#define ADE7754_CFNUM 0x25 +#define ADE7754_CFDEN 0x26 +#define ADE7754_WDIV 0x27 +#define ADE7754_VADIV 0x28 +#define ADE7754_AIRMS 0x29 +#define ADE7754_BIRMS 0x2A +#define ADE7754_CIRMS 0x2B +#define ADE7754_AVRMS 0x2C +#define ADE7754_BVRMS 0x2D +#define ADE7754_CVRMS 0x2E +#define ADE7754_AIRMSOS 0x2F +#define ADE7754_BIRMSOS 0x30 +#define ADE7754_CIRMSOS 0x31 +#define ADE7754_AVRMSOS 0x32 +#define ADE7754_BVRMSOS 0x33 +#define ADE7754_CVRMSOS 0x34 +#define ADE7754_AAPGAIN 0x35 +#define ADE7754_BAPGAIN 0x36 +#define ADE7754_CAPGAIN 0x37 +#define ADE7754_AVGAIN 0x38 +#define ADE7754_BVGAIN 0x39 +#define ADE7754_CVGAIN 0x3A +#define ADE7754_CHKSUM 0x3E +#define ADE7754_VERSION 0x3F + +#define ADE7754_READ_REG(a) a +#define ADE7754_WRITE_REG(a) ((a) | 0x80) + +#define ADE7754_MAX_TX 4 +#define ADE7754_MAX_RX 4 +#define ADE7754_STARTUP_DELAY 1 + +#define ADE7754_SPI_SLOW (u32)(300 * 1000) +#define ADE7754_SPI_BURST (u32)(1000 * 1000) +#define ADE7754_SPI_FAST (u32)(2000 * 1000) + +#define DRIVER_NAME "ade7754" + +/** + * struct ade7754_state - device instance specific data + * @us: actual spi_device + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct ade7754_state { + struct spi_device *us; + struct mutex buf_lock; + u8 tx[ADE7754_MAX_TX] ____cacheline_aligned; + u8 rx[ADE7754_MAX_RX]; +}; + +#endif diff --git a/drivers/staging/iio/meter/ade7758.h b/drivers/staging/iio/meter/ade7758.h new file mode 100644 index 00000000..bdd1b05b --- /dev/null +++ b/drivers/staging/iio/meter/ade7758.h @@ -0,0 +1,187 @@ +/* + * ADE7758 Poly Phase Multifunction Energy Metering IC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef _ADE7758_H +#define _ADE7758_H + +#define ADE7758_AWATTHR 0x01 +#define ADE7758_BWATTHR 0x02 +#define ADE7758_CWATTHR 0x03 +#define ADE7758_AVARHR 0x04 +#define ADE7758_BVARHR 0x05 +#define ADE7758_CVARHR 0x06 +#define ADE7758_AVAHR 0x07 +#define ADE7758_BVAHR 0x08 +#define ADE7758_CVAHR 0x09 +#define ADE7758_AIRMS 0x0A +#define ADE7758_BIRMS 0x0B +#define ADE7758_CIRMS 0x0C +#define ADE7758_AVRMS 0x0D +#define ADE7758_BVRMS 0x0E +#define ADE7758_CVRMS 0x0F +#define ADE7758_FREQ 0x10 +#define ADE7758_TEMP 0x11 +#define ADE7758_WFORM 0x12 +#define ADE7758_OPMODE 0x13 +#define ADE7758_MMODE 0x14 +#define ADE7758_WAVMODE 0x15 +#define ADE7758_COMPMODE 0x16 +#define ADE7758_LCYCMODE 0x17 +#define ADE7758_MASK 0x18 +#define ADE7758_STATUS 0x19 +#define ADE7758_RSTATUS 0x1A +#define ADE7758_ZXTOUT 0x1B +#define ADE7758_LINECYC 0x1C +#define ADE7758_SAGCYC 0x1D +#define ADE7758_SAGLVL 0x1E +#define ADE7758_VPINTLVL 0x1F +#define ADE7758_IPINTLVL 0x20 +#define ADE7758_VPEAK 0x21 +#define ADE7758_IPEAK 0x22 +#define ADE7758_GAIN 0x23 +#define ADE7758_AVRMSGAIN 0x24 +#define ADE7758_BVRMSGAIN 0x25 +#define ADE7758_CVRMSGAIN 0x26 +#define ADE7758_AIGAIN 0x27 +#define ADE7758_BIGAIN 0x28 +#define ADE7758_CIGAIN 0x29 +#define ADE7758_AWG 0x2A +#define ADE7758_BWG 0x2B +#define ADE7758_CWG 0x2C +#define ADE7758_AVARG 0x2D +#define ADE7758_BVARG 0x2E +#define ADE7758_CVARG 0x2F +#define ADE7758_AVAG 0x30 +#define ADE7758_BVAG 0x31 +#define ADE7758_CVAG 0x32 +#define ADE7758_AVRMSOS 0x33 +#define ADE7758_BVRMSOS 0x34 +#define ADE7758_CVRMSOS 0x35 +#define ADE7758_AIRMSOS 0x36 +#define ADE7758_BIRMSOS 0x37 +#define ADE7758_CIRMSOS 0x38 +#define ADE7758_AWAITOS 0x39 +#define ADE7758_BWAITOS 0x3A +#define ADE7758_CWAITOS 0x3B +#define ADE7758_AVAROS 0x3C +#define ADE7758_BVAROS 0x3D +#define ADE7758_CVAROS 0x3E +#define ADE7758_APHCAL 0x3F +#define ADE7758_BPHCAL 0x40 +#define ADE7758_CPHCAL 0x41 +#define ADE7758_WDIV 0x42 +#define ADE7758_VADIV 0x44 +#define ADE7758_VARDIV 0x43 +#define ADE7758_APCFNUM 0x45 +#define ADE7758_APCFDEN 0x46 +#define ADE7758_VARCFNUM 0x47 +#define ADE7758_VARCFDEN 0x48 +#define ADE7758_CHKSUM 0x7E +#define ADE7758_VERSION 0x7F + +#define ADE7758_READ_REG(a) a +#define ADE7758_WRITE_REG(a) ((a) | 0x80) + +#define ADE7758_MAX_TX 8 +#define ADE7758_MAX_RX 4 +#define ADE7758_STARTUP_DELAY 1 + +#define AD7758_NUM_WAVSEL 5 +#define AD7758_NUM_PHSEL 3 +#define AD7758_NUM_WAVESRC (AD7758_NUM_WAVSEL * AD7758_NUM_PHSEL) + +#define AD7758_PHASE_A 0 +#define AD7758_PHASE_B 1 +#define AD7758_PHASE_C 2 +#define AD7758_CURRENT 0 +#define AD7758_VOLTAGE 1 +#define AD7758_ACT_PWR 2 +#define AD7758_REACT_PWR 3 +#define AD7758_APP_PWR 4 +#define AD7758_WT(p, w) (((w) << 2) | (p)) + +#define DRIVER_NAME "ade7758" + + +/** + * struct ade7758_state - device instance specific data + * @us: actual spi_device + * @trig: data ready trigger registered with iio + * @tx: transmit buffer + * @rx: receive buffer + * @buf_lock: mutex to protect tx and rx + **/ +struct ade7758_state { + struct spi_device *us; + struct iio_trigger *trig; + u8 *tx; + u8 *rx; + struct mutex buf_lock; + unsigned long available_scan_masks[AD7758_NUM_WAVESRC]; + struct iio_chan_spec *ade7758_ring_channels; + struct spi_transfer ring_xfer[4]; + struct spi_message ring_msg; + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + unsigned char rx_buf[8] ____cacheline_aligned; + unsigned char tx_buf[8]; + +}; +#ifdef CONFIG_IIO_BUFFER +/* At the moment triggers are only used for ring buffer + * filling. This may change! + */ + +void ade7758_remove_trigger(struct iio_dev *indio_dev); +int ade7758_probe_trigger(struct iio_dev *indio_dev); + +ssize_t ade7758_read_data_from_ring(struct device *dev, + struct device_attribute *attr, + char *buf); + + +int ade7758_configure_ring(struct iio_dev *indio_dev); +void ade7758_unconfigure_ring(struct iio_dev *indio_dev); + +void ade7758_uninitialize_ring(struct iio_dev *indio_dev); +int ade7758_set_irq(struct device *dev, bool enable); + +int ade7758_spi_write_reg_8(struct device *dev, + u8 reg_address, u8 val); +int ade7758_spi_read_reg_8(struct device *dev, + u8 reg_address, u8 *val); + +#else /* CONFIG_IIO_BUFFER */ + +static inline void ade7758_remove_trigger(struct iio_dev *indio_dev) +{ +} +static inline int ade7758_probe_trigger(struct iio_dev *indio_dev) +{ + return 0; +} + +static int ade7758_configure_ring(struct iio_dev *indio_dev) +{ + return 0; +} +static inline void ade7758_unconfigure_ring(struct iio_dev *indio_dev) +{ +} +static inline int ade7758_initialize_ring(struct iio_ring_buffer *ring) +{ + return 0; +} +static inline void ade7758_uninitialize_ring(struct iio_dev *indio_dev) +{ +} +#endif /* CONFIG_IIO_BUFFER */ + +#endif diff --git a/drivers/staging/iio/meter/ade7758_core.c b/drivers/staging/iio/meter/ade7758_core.c new file mode 100644 index 00000000..dcb20294 --- /dev/null +++ b/drivers/staging/iio/meter/ade7758_core.c @@ -0,0 +1,861 @@ +/* + * ADE7758 Poly Phase Multifunction Energy Metering IC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "../buffer.h" +#include "meter.h" +#include "ade7758.h" + +int ade7758_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 val) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7758_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7758_spi_write_reg_16(struct device *dev, + u8 reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7758_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + } + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_WRITE_REG(reg_address); + st->tx[1] = (value >> 8) & 0xFF; + st->tx[2] = value & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(xfers, &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7758_spi_write_reg_24(struct device *dev, + u8 reg_address, + u32 value) +{ + int ret; + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7758_state *st = iio_priv(indio_dev); + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 4, + } + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_WRITE_REG(reg_address); + st->tx[1] = (value >> 16) & 0xFF; + st->tx[2] = (value >> 8) & 0xFF; + st->tx[3] = value & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(xfers, &msg); + ret = spi_sync(st->us, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +int ade7758_spi_read_reg_8(struct device *dev, + u8 reg_address, + u8 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 1, + .delay_usecs = 4, + }, + { + .tx_buf = &st->tx[1], + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 1, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_READ_REG(reg_address); + st->tx[1] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = st->rx[0]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7758_spi_read_reg_16(struct device *dev, + u8 reg_address, + u16 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 1, + .delay_usecs = 4, + }, + { + .tx_buf = &st->tx[1], + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + }, + }; + + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_READ_REG(reg_address); + st->tx[1] = 0; + st->tx[2] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + goto error_ret; + } + + *val = (st->rx[0] << 8) | st->rx[1]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7758_spi_read_reg_24(struct device *dev, + u8 reg_address, + u32 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 1, + .delay_usecs = 4, + }, + { + .tx_buf = &st->tx[1], + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 3, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7758_READ_REG(reg_address); + st->tx[1] = 0; + st->tx[2] = 0; + st->tx[3] = 0; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 24 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = (st->rx[0] << 16) | (st->rx[1] << 8) | st->rx[2]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t ade7758_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7758_spi_read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7758_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7758_spi_read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7758_read_24bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7758_spi_read_reg_24(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val & 0xFFFFFF); +} + +static ssize_t ade7758_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7758_spi_write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7758_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7758_spi_write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7758_reset(struct device *dev) +{ + int ret; + u8 val; + ade7758_spi_read_reg_8(dev, + ADE7758_OPMODE, + &val); + val |= 1 << 6; /* Software Chip Reset */ + ret = ade7758_spi_write_reg_8(dev, + ADE7758_OPMODE, + val); + + return ret; +} + +static ssize_t ade7758_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + if (len < 1) + return -1; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return ade7758_reset(dev); + } + return len; +} + +static IIO_DEV_ATTR_VPEAK(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_VPEAK); +static IIO_DEV_ATTR_IPEAK(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_VPEAK); +static IIO_DEV_ATTR_APHCAL(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_APHCAL); +static IIO_DEV_ATTR_BPHCAL(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_BPHCAL); +static IIO_DEV_ATTR_CPHCAL(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_CPHCAL); +static IIO_DEV_ATTR_WDIV(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_WDIV); +static IIO_DEV_ATTR_VADIV(S_IWUSR | S_IRUGO, + ade7758_read_8bit, + ade7758_write_8bit, + ADE7758_VADIV); +static IIO_DEV_ATTR_AIRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_AIRMS); +static IIO_DEV_ATTR_BIRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_BIRMS); +static IIO_DEV_ATTR_CIRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_CIRMS); +static IIO_DEV_ATTR_AVRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_AVRMS); +static IIO_DEV_ATTR_BVRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_BVRMS); +static IIO_DEV_ATTR_CVRMS(S_IRUGO, + ade7758_read_24bit, + NULL, + ADE7758_CVRMS); +static IIO_DEV_ATTR_AIRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_AIRMSOS); +static IIO_DEV_ATTR_BIRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_BIRMSOS); +static IIO_DEV_ATTR_CIRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_CIRMSOS); +static IIO_DEV_ATTR_AVRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_AVRMSOS); +static IIO_DEV_ATTR_BVRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_BVRMSOS); +static IIO_DEV_ATTR_CVRMSOS(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_CVRMSOS); +static IIO_DEV_ATTR_AIGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_AIGAIN); +static IIO_DEV_ATTR_BIGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_BIGAIN); +static IIO_DEV_ATTR_CIGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_CIGAIN); +static IIO_DEV_ATTR_AVRMSGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_AVRMSGAIN); +static IIO_DEV_ATTR_BVRMSGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_BVRMSGAIN); +static IIO_DEV_ATTR_CVRMSGAIN(S_IWUSR | S_IRUGO, + ade7758_read_16bit, + ade7758_write_16bit, + ADE7758_CVRMSGAIN); + +int ade7758_set_irq(struct device *dev, bool enable) +{ + int ret; + u32 irqen; + ret = ade7758_spi_read_reg_24(dev, ADE7758_MASK, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 16; /* Enables an interrupt when a data is + present in the waveform register */ + else + irqen &= ~(1 << 16); + + ret = ade7758_spi_write_reg_24(dev, ADE7758_MASK, irqen); + if (ret) + goto error_ret; + +error_ret: + return ret; +} + +/* Power down the device */ +static int ade7758_stop_device(struct device *dev) +{ + int ret; + u8 val; + ade7758_spi_read_reg_8(dev, + ADE7758_OPMODE, + &val); + val |= 7 << 3; /* ADE7758 powered down */ + ret = ade7758_spi_write_reg_8(dev, + ADE7758_OPMODE, + val); + + return ret; +} + +static int ade7758_initial_setup(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + struct device *dev = &indio_dev->dev; + int ret; + + /* use low spi speed for init */ + st->us->mode = SPI_MODE_1; + spi_setup(st->us); + + /* Disable IRQ */ + ret = ade7758_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7758_reset(dev); + msleep(ADE7758_STARTUP_DELAY); + +err_ret: + return ret; +} + +static ssize_t ade7758_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret, len = 0; + u8 t; + int sps; + ret = ade7758_spi_read_reg_8(dev, + ADE7758_WAVMODE, + &t); + if (ret) + return ret; + + t = (t >> 5) & 0x3; + sps = 26040 / (1 << t); + + len = sprintf(buf, "%d SPS\n", sps); + return len; +} + +static ssize_t ade7758_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + unsigned long val; + int ret; + u8 reg, t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + switch (val) { + case 26040: + t = 0; + break; + case 13020: + t = 1; + break; + case 6510: + t = 2; + break; + case 3255: + t = 3; + break; + default: + ret = -EINVAL; + goto out; + } + + ret = ade7758_spi_read_reg_8(dev, + ADE7758_WAVMODE, + ®); + if (ret) + goto out; + + reg &= ~(5 << 3); + reg |= t << 5; + + ret = ade7758_spi_write_reg_8(dev, + ADE7758_WAVMODE, + reg); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} + +static IIO_DEV_ATTR_TEMP_RAW(ade7758_read_8bit); +static IIO_CONST_ATTR(in_temp_offset, "129 C"); +static IIO_CONST_ATTR(in_temp_scale, "4 C"); + +static IIO_DEV_ATTR_AWATTHR(ade7758_read_16bit, + ADE7758_AWATTHR); +static IIO_DEV_ATTR_BWATTHR(ade7758_read_16bit, + ADE7758_BWATTHR); +static IIO_DEV_ATTR_CWATTHR(ade7758_read_16bit, + ADE7758_CWATTHR); +static IIO_DEV_ATTR_AVARHR(ade7758_read_16bit, + ADE7758_AVARHR); +static IIO_DEV_ATTR_BVARHR(ade7758_read_16bit, + ADE7758_BVARHR); +static IIO_DEV_ATTR_CVARHR(ade7758_read_16bit, + ADE7758_CVARHR); +static IIO_DEV_ATTR_AVAHR(ade7758_read_16bit, + ADE7758_AVAHR); +static IIO_DEV_ATTR_BVAHR(ade7758_read_16bit, + ADE7758_BVAHR); +static IIO_DEV_ATTR_CVAHR(ade7758_read_16bit, + ADE7758_CVAHR); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ade7758_read_frequency, + ade7758_write_frequency); + +static IIO_DEV_ATTR_RESET(ade7758_write_reset); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("26040 13020 6510 3255"); + +static struct attribute *ade7758_attributes[] = { + &iio_dev_attr_in_temp_raw.dev_attr.attr, + &iio_const_attr_in_temp_offset.dev_attr.attr, + &iio_const_attr_in_temp_scale.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + &iio_dev_attr_awatthr.dev_attr.attr, + &iio_dev_attr_bwatthr.dev_attr.attr, + &iio_dev_attr_cwatthr.dev_attr.attr, + &iio_dev_attr_avarhr.dev_attr.attr, + &iio_dev_attr_bvarhr.dev_attr.attr, + &iio_dev_attr_cvarhr.dev_attr.attr, + &iio_dev_attr_avahr.dev_attr.attr, + &iio_dev_attr_bvahr.dev_attr.attr, + &iio_dev_attr_cvahr.dev_attr.attr, + &iio_dev_attr_vpeak.dev_attr.attr, + &iio_dev_attr_ipeak.dev_attr.attr, + &iio_dev_attr_aphcal.dev_attr.attr, + &iio_dev_attr_bphcal.dev_attr.attr, + &iio_dev_attr_cphcal.dev_attr.attr, + &iio_dev_attr_wdiv.dev_attr.attr, + &iio_dev_attr_vadiv.dev_attr.attr, + &iio_dev_attr_airms.dev_attr.attr, + &iio_dev_attr_birms.dev_attr.attr, + &iio_dev_attr_cirms.dev_attr.attr, + &iio_dev_attr_avrms.dev_attr.attr, + &iio_dev_attr_bvrms.dev_attr.attr, + &iio_dev_attr_cvrms.dev_attr.attr, + &iio_dev_attr_aigain.dev_attr.attr, + &iio_dev_attr_bigain.dev_attr.attr, + &iio_dev_attr_cigain.dev_attr.attr, + &iio_dev_attr_avrmsgain.dev_attr.attr, + &iio_dev_attr_bvrmsgain.dev_attr.attr, + &iio_dev_attr_cvrmsgain.dev_attr.attr, + &iio_dev_attr_airmsos.dev_attr.attr, + &iio_dev_attr_birmsos.dev_attr.attr, + &iio_dev_attr_cirmsos.dev_attr.attr, + &iio_dev_attr_avrmsos.dev_attr.attr, + &iio_dev_attr_bvrmsos.dev_attr.attr, + &iio_dev_attr_cvrmsos.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7758_attribute_group = { + .attrs = ade7758_attributes, +}; + +static struct iio_chan_spec ade7758_channels[] = { + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, "raw", 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_A, AD7758_VOLTAGE), + 0, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_CURRENT, 0, 1, 0, "raw", 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_A, AD7758_CURRENT), + 1, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_POWER, 0, 1, 0, "apparent_raw", 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_A, AD7758_APP_PWR), + 2, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_POWER, 0, 1, 0, "active_raw", 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_A, AD7758_ACT_PWR), + 3, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_POWER, 0, 1, 0, "reactive_raw", 0, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_A, AD7758_REACT_PWR), + 4, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, "raw", 1, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_B, AD7758_VOLTAGE), + 5, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_CURRENT, 0, 1, 0, "raw", 1, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_B, AD7758_CURRENT), + 6, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_POWER, 0, 1, 0, "apparent_raw", 1, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_B, AD7758_APP_PWR), + 7, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_POWER, 0, 1, 0, "active_raw", 1, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_B, AD7758_ACT_PWR), + 8, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_POWER, 0, 1, 0, "reactive_raw", 1, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_B, AD7758_REACT_PWR), + 9, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_VOLTAGE, 0, 1, 0, "raw", 2, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_C, AD7758_VOLTAGE), + 10, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_CURRENT, 0, 1, 0, "raw", 2, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_C, AD7758_CURRENT), + 11, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_POWER, 0, 1, 0, "apparent_raw", 2, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_C, AD7758_APP_PWR), + 12, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_POWER, 0, 1, 0, "active_raw", 2, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_C, AD7758_ACT_PWR), + 13, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN(IIO_POWER, 0, 1, 0, "reactive_raw", 2, 0, + IIO_CHAN_INFO_SCALE_SHARED_BIT, + AD7758_WT(AD7758_PHASE_C, AD7758_REACT_PWR), + 14, IIO_ST('s', 24, 32, 0), 0), + IIO_CHAN_SOFT_TIMESTAMP(15), +}; + +static const struct iio_info ade7758_info = { + .attrs = &ade7758_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ade7758_probe(struct spi_device *spi) +{ + int i, ret; + struct ade7758_state *st; + struct iio_dev *indio_dev = iio_allocate_device(sizeof(*st)); + + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + st = iio_priv(indio_dev); + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + /* Allocate the comms buffers */ + st->rx = kcalloc(ADE7758_MAX_RX, sizeof(*st->rx), GFP_KERNEL); + if (st->rx == NULL) { + ret = -ENOMEM; + goto error_free_dev; + } + st->tx = kcalloc(ADE7758_MAX_TX, sizeof(*st->tx), GFP_KERNEL); + if (st->tx == NULL) { + ret = -ENOMEM; + goto error_free_rx; + } + st->us = spi; + st->ade7758_ring_channels = &ade7758_channels[0]; + mutex_init(&st->buf_lock); + + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ade7758_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + for (i = 0; i < AD7758_NUM_WAVESRC; i++) + set_bit(i, &st->available_scan_masks[i]); + + indio_dev->available_scan_masks = st->available_scan_masks; + + ret = ade7758_configure_ring(indio_dev); + if (ret) + goto error_free_tx; + + ret = iio_buffer_register(indio_dev, + &ade7758_channels[0], + ARRAY_SIZE(ade7758_channels)); + if (ret) { + dev_err(&spi->dev, "failed to initialize the ring\n"); + goto error_unreg_ring_funcs; + } + + /* Get the device into a sane initial state */ + ret = ade7758_initial_setup(indio_dev); + if (ret) + goto error_uninitialize_ring; + + if (spi->irq) { + ret = ade7758_probe_trigger(indio_dev); + if (ret) + goto error_uninitialize_ring; + } + + ret = iio_device_register(indio_dev); + if (ret) + goto error_remove_trigger; + + return 0; + +error_remove_trigger: + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + ade7758_remove_trigger(indio_dev); +error_uninitialize_ring: + ade7758_uninitialize_ring(indio_dev); +error_unreg_ring_funcs: + ade7758_unconfigure_ring(indio_dev); +error_free_tx: + kfree(st->tx); +error_free_rx: + kfree(st->rx); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int ade7758_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + + iio_device_unregister(indio_dev); + ret = ade7758_stop_device(&indio_dev->dev); + if (ret) + goto err_ret; + + ade7758_remove_trigger(indio_dev); + ade7758_uninitialize_ring(indio_dev); + ade7758_unconfigure_ring(indio_dev); + kfree(st->tx); + kfree(st->rx); + + iio_free_device(indio_dev); + +err_ret: + return ret; +} + +static const struct spi_device_id ade7758_id[] = { + {"ade7758", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, ade7758_id); + +static struct spi_driver ade7758_driver = { + .driver = { + .name = "ade7758", + .owner = THIS_MODULE, + }, + .probe = ade7758_probe, + .remove = __devexit_p(ade7758_remove), + .id_table = ade7758_id, +}; +module_spi_driver(ade7758_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7758 Polyphase Multifunction Energy Metering IC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/meter/ade7758_ring.c b/drivers/staging/iio/meter/ade7758_ring.c new file mode 100644 index 00000000..c45b23bb --- /dev/null +++ b/drivers/staging/iio/meter/ade7758_ring.c @@ -0,0 +1,205 @@ +/* + * ADE7758 Poly Phase Multifunction Energy Metering IC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../ring_sw.h" +#include "../trigger_consumer.h" +#include "ade7758.h" + +/** + * ade7758_spi_read_burst() - read data registers + * @dev: device associated with child of actual device (iio_dev or iio_trig) + **/ +static int ade7758_spi_read_burst(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_sync(st->us, &st->ring_msg); + if (ret) + dev_err(&st->us->dev, "problem when reading WFORM value\n"); + + return ret; +} + +static int ade7758_write_waveform_type(struct device *dev, unsigned type) +{ + int ret; + u8 reg; + + ret = ade7758_spi_read_reg_8(dev, + ADE7758_WAVMODE, + ®); + if (ret) + goto out; + + reg &= ~0x1F; + reg |= type & 0x1F; + + ret = ade7758_spi_write_reg_8(dev, + ADE7758_WAVMODE, + reg); +out: + return ret; +} + +/* Whilst this makes a lot of calls to iio_sw_ring functions - it is to device + * specific to be rolled into the core. + */ +static irqreturn_t ade7758_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct iio_buffer *ring = indio_dev->buffer; + struct ade7758_state *st = iio_priv(indio_dev); + s64 dat64[2]; + u32 *dat32 = (u32 *)dat64; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + if (ade7758_spi_read_burst(&indio_dev->dev) >= 0) + *dat32 = get_unaligned_be32(&st->rx_buf[5]) & 0xFFFFFF; + + /* Guaranteed to be aligned with 8 byte boundary */ + if (ring->scan_timestamp) + dat64[1] = pf->timestamp; + + ring->access->store_to(ring, (u8 *)dat64, pf->timestamp); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +/** + * ade7758_ring_preenable() setup the parameters of the ring before enabling + * + * The complex nature of the setting of the number of bytes per datum is due + * to this driver currently ensuring that the timestamp is stored at an 8 + * byte boundary. + **/ +static int ade7758_ring_preenable(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + struct iio_buffer *ring = indio_dev->buffer; + size_t d_size; + unsigned channel; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) + return -EINVAL; + + channel = find_first_bit(indio_dev->active_scan_mask, + indio_dev->masklength); + + d_size = st->ade7758_ring_channels[channel].scan_type.storagebits / 8; + + if (ring->scan_timestamp) { + d_size += sizeof(s64); + + if (d_size % sizeof(s64)) + d_size += sizeof(s64) - (d_size % sizeof(s64)); + } + + if (indio_dev->buffer->access->set_bytes_per_datum) + indio_dev->buffer->access-> + set_bytes_per_datum(indio_dev->buffer, d_size); + + ade7758_write_waveform_type(&indio_dev->dev, + st->ade7758_ring_channels[channel].address); + + return 0; +} + +static const struct iio_buffer_setup_ops ade7758_ring_setup_ops = { + .preenable = &ade7758_ring_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +void ade7758_unconfigure_ring(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_sw_rb_free(indio_dev->buffer); +} + +int ade7758_configure_ring(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + int ret = 0; + + indio_dev->buffer = iio_sw_rb_allocate(indio_dev); + if (!indio_dev->buffer) { + ret = -ENOMEM; + return ret; + } + + indio_dev->setup_ops = &ade7758_ring_setup_ops; + + indio_dev->pollfunc = iio_alloc_pollfunc(&iio_pollfunc_store_time, + &ade7758_trigger_handler, + 0, + indio_dev, + "ade7759_consumer%d", + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_iio_sw_rb_free; + } + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + st->tx_buf[0] = ADE7758_READ_REG(ADE7758_RSTATUS); + st->tx_buf[1] = 0; + st->tx_buf[2] = 0; + st->tx_buf[3] = 0; + st->tx_buf[4] = ADE7758_READ_REG(ADE7758_WFORM); + st->tx_buf[5] = 0; + st->tx_buf[6] = 0; + st->tx_buf[7] = 0; + + /* build spi ring message */ + st->ring_xfer[0].tx_buf = &st->tx_buf[0]; + st->ring_xfer[0].len = 1; + st->ring_xfer[0].bits_per_word = 8; + st->ring_xfer[0].delay_usecs = 4; + st->ring_xfer[1].rx_buf = &st->rx_buf[1]; + st->ring_xfer[1].len = 3; + st->ring_xfer[1].bits_per_word = 8; + st->ring_xfer[1].cs_change = 1; + + st->ring_xfer[2].tx_buf = &st->tx_buf[4]; + st->ring_xfer[2].len = 1; + st->ring_xfer[2].bits_per_word = 8; + st->ring_xfer[2].delay_usecs = 1; + st->ring_xfer[3].rx_buf = &st->rx_buf[5]; + st->ring_xfer[3].len = 3; + st->ring_xfer[3].bits_per_word = 8; + + spi_message_init(&st->ring_msg); + spi_message_add_tail(&st->ring_xfer[0], &st->ring_msg); + spi_message_add_tail(&st->ring_xfer[1], &st->ring_msg); + spi_message_add_tail(&st->ring_xfer[2], &st->ring_msg); + spi_message_add_tail(&st->ring_xfer[3], &st->ring_msg); + + return 0; + +error_iio_sw_rb_free: + iio_sw_rb_free(indio_dev->buffer); + return ret; +} + +void ade7758_uninitialize_ring(struct iio_dev *indio_dev) +{ + iio_buffer_unregister(indio_dev); +} diff --git a/drivers/staging/iio/meter/ade7758_trigger.c b/drivers/staging/iio/meter/ade7758_trigger.c new file mode 100644 index 00000000..b6569c70 --- /dev/null +++ b/drivers/staging/iio/meter/ade7758_trigger.c @@ -0,0 +1,109 @@ +/* + * ADE7758 Poly Phase Multifunction Energy Metering IC driver + * + * Copyright 2010-2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" +#include "ade7758.h" + +/** + * ade7758_data_rdy_trig_poll() the event handler for the data rdy trig + **/ +static irqreturn_t ade7758_data_rdy_trig_poll(int irq, void *private) +{ + disable_irq_nosync(irq); + iio_trigger_poll(private, iio_get_time_ns()); + + return IRQ_HANDLED; +} + +/** + * ade7758_data_rdy_trigger_set_state() set datardy interrupt state + **/ +static int ade7758_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = trig->private_data; + + dev_dbg(&indio_dev->dev, "%s (%d)\n", __func__, state); + return ade7758_set_irq(&indio_dev->dev, state); +} + +/** + * ade7758_trig_try_reen() try renabling irq for data rdy trigger + * @trig: the datardy trigger + **/ +static int ade7758_trig_try_reen(struct iio_trigger *trig) +{ + struct iio_dev *indio_dev = trig->private_data; + struct ade7758_state *st = iio_priv(indio_dev); + + enable_irq(st->us->irq); + /* irq reenabled so success! */ + return 0; +} + +static const struct iio_trigger_ops ade7758_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &ade7758_data_rdy_trigger_set_state, + .try_reenable = &ade7758_trig_try_reen, +}; + +int ade7758_probe_trigger(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + int ret; + + st->trig = iio_allocate_trigger("%s-dev%d", + spi_get_device_id(st->us)->name, + indio_dev->id); + if (st->trig == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + ret = request_irq(st->us->irq, + ade7758_data_rdy_trig_poll, + IRQF_TRIGGER_LOW, + spi_get_device_id(st->us)->name, + st->trig); + if (ret) + goto error_free_trig; + + st->trig->dev.parent = &st->us->dev; + st->trig->ops = &ade7758_trigger_ops; + st->trig->private_data = indio_dev; + ret = iio_trigger_register(st->trig); + + /* select default trigger */ + indio_dev->trig = st->trig; + if (ret) + goto error_free_irq; + + return 0; + +error_free_irq: + free_irq(st->us->irq, st->trig); +error_free_trig: + iio_free_trigger(st->trig); +error_ret: + return ret; +} + +void ade7758_remove_trigger(struct iio_dev *indio_dev) +{ + struct ade7758_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + free_irq(st->us->irq, st->trig); + iio_free_trigger(st->trig); +} diff --git a/drivers/staging/iio/meter/ade7759.c b/drivers/staging/iio/meter/ade7759.c new file mode 100644 index 00000000..0beab478 --- /dev/null +++ b/drivers/staging/iio/meter/ade7759.c @@ -0,0 +1,529 @@ +/* + * ADE7759 Active Energy Metering IC with di/dt Sensor Interface Driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "meter.h" +#include "ade7759.h" + +static int ade7759_spi_write_reg_8(struct device *dev, + u8 reg_address, + u8 val) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7759_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7759_WRITE_REG(reg_address); + st->tx[1] = val; + + ret = spi_write(st->us, st->tx, 2); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7759_spi_write_reg_16(struct device *dev, + u8 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7759_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7759_WRITE_REG(reg_address); + st->tx[1] = (value >> 8) & 0xFF; + st->tx[2] = value & 0xFF; + ret = spi_write(st->us, st->tx, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7759_spi_read_reg_8(struct device *dev, + u8 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7759_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_w8r8(st->us, ADE7759_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + return ret; + } + *val = ret; + + return 0; +} + +static int ade7759_spi_read_reg_16(struct device *dev, + u8 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7759_state *st = iio_priv(indio_dev); + int ret; + + ret = spi_w8r16(st->us, ADE7759_READ_REG(reg_address)); + if (ret < 0) { + dev_err(&st->us->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + return ret; + } + + *val = ret; + *val = be16_to_cpup(val); + + return 0; +} + +static int ade7759_spi_read_reg_40(struct device *dev, + u8 reg_address, + u64 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7759_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 6, + }, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7759_READ_REG(reg_address); + memset(&st->tx[1], 0 , 5); + + spi_message_init(&msg); + spi_message_add_tail(xfers, &msg); + ret = spi_sync(st->us, &msg); + if (ret) { + dev_err(&st->us->dev, "problem when reading 40 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = ((u64)st->rx[1] << 32) | (st->rx[2] << 24) | + (st->rx[3] << 16) | (st->rx[4] << 8) | st->rx[5]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static ssize_t ade7759_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7759_spi_read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7759_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7759_spi_read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7759_read_40bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u64 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = ade7759_spi_read_reg_40(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%llu\n", val); +} + +static ssize_t ade7759_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7759_spi_write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7759_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = ade7759_spi_write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7759_reset(struct device *dev) +{ + int ret; + u16 val; + ade7759_spi_read_reg_16(dev, + ADE7759_MODE, + &val); + val |= 1 << 6; /* Software Chip Reset */ + ret = ade7759_spi_write_reg_16(dev, + ADE7759_MODE, + val); + + return ret; +} + +static ssize_t ade7759_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + if (len < 1) + return -1; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return ade7759_reset(dev); + } + return -1; +} + +static IIO_DEV_ATTR_AENERGY(ade7759_read_40bit, ADE7759_AENERGY); +static IIO_DEV_ATTR_CFDEN(S_IWUSR | S_IRUGO, + ade7759_read_16bit, + ade7759_write_16bit, + ADE7759_CFDEN); +static IIO_DEV_ATTR_CFNUM(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_CFNUM); +static IIO_DEV_ATTR_CHKSUM(ade7759_read_8bit, ADE7759_CHKSUM); +static IIO_DEV_ATTR_PHCAL(S_IWUSR | S_IRUGO, + ade7759_read_16bit, + ade7759_write_16bit, + ADE7759_PHCAL); +static IIO_DEV_ATTR_APOS(S_IWUSR | S_IRUGO, + ade7759_read_16bit, + ade7759_write_16bit, + ADE7759_APOS); +static IIO_DEV_ATTR_SAGCYC(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_SAGCYC); +static IIO_DEV_ATTR_SAGLVL(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_SAGLVL); +static IIO_DEV_ATTR_LINECYC(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_LINECYC); +static IIO_DEV_ATTR_LENERGY(ade7759_read_40bit, ADE7759_LENERGY); +static IIO_DEV_ATTR_PGA_GAIN(S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_GAIN); +static IIO_DEV_ATTR_ACTIVE_POWER_GAIN(S_IWUSR | S_IRUGO, + ade7759_read_16bit, + ade7759_write_16bit, + ADE7759_APGAIN); +static IIO_DEV_ATTR_CH_OFF(1, S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_CH1OS); +static IIO_DEV_ATTR_CH_OFF(2, S_IWUSR | S_IRUGO, + ade7759_read_8bit, + ade7759_write_8bit, + ADE7759_CH2OS); + +static int ade7759_set_irq(struct device *dev, bool enable) +{ + int ret; + u8 irqen; + ret = ade7759_spi_read_reg_8(dev, ADE7759_IRQEN, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 3; /* Enables an interrupt when a data is + present in the waveform register */ + else + irqen &= ~(1 << 3); + + ret = ade7759_spi_write_reg_8(dev, ADE7759_IRQEN, irqen); + +error_ret: + return ret; +} + +/* Power down the device */ +static int ade7759_stop_device(struct device *dev) +{ + u16 val; + + ade7759_spi_read_reg_16(dev, + ADE7759_MODE, + &val); + val |= 1 << 4; /* AD converters can be turned off */ + + return ade7759_spi_write_reg_16(dev, ADE7759_MODE, val); +} + +static int ade7759_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct ade7759_state *st = iio_priv(indio_dev); + struct device *dev = &indio_dev->dev; + + /* use low spi speed for init */ + st->us->mode = SPI_MODE_3; + spi_setup(st->us); + + /* Disable IRQ */ + ret = ade7759_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7759_reset(dev); + msleep(ADE7759_STARTUP_DELAY); + +err_ret: + return ret; +} + +static ssize_t ade7759_read_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 t; + int sps; + ret = ade7759_spi_read_reg_16(dev, + ADE7759_MODE, + &t); + if (ret) + return ret; + + t = (t >> 3) & 0x3; + sps = 27900 / (1 + t); + + return sprintf(buf, "%d\n", sps); +} + +static ssize_t ade7759_write_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7759_state *st = iio_priv(indio_dev); + unsigned long val; + int ret; + u16 reg, t; + + ret = strict_strtol(buf, 10, &val); + if (ret) + return ret; + + mutex_lock(&indio_dev->mlock); + + t = (27900 / val); + if (t > 0) + t--; + + if (t > 1) + st->us->max_speed_hz = ADE7759_SPI_SLOW; + else + st->us->max_speed_hz = ADE7759_SPI_FAST; + + ret = ade7759_spi_read_reg_16(dev, ADE7759_MODE, ®); + if (ret) + goto out; + + reg &= ~(3 << 13); + reg |= t << 13; + + ret = ade7759_spi_write_reg_16(dev, ADE7759_MODE, reg); + +out: + mutex_unlock(&indio_dev->mlock); + + return ret ? ret : len; +} +static IIO_DEV_ATTR_TEMP_RAW(ade7759_read_8bit); +static IIO_CONST_ATTR(in_temp_offset, "70 C"); +static IIO_CONST_ATTR(in_temp_scale, "1 C"); + +static IIO_DEV_ATTR_SAMP_FREQ(S_IWUSR | S_IRUGO, + ade7759_read_frequency, + ade7759_write_frequency); + +static IIO_DEV_ATTR_RESET(ade7759_write_reset); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("27900 14000 7000 3500"); + +static struct attribute *ade7759_attributes[] = { + &iio_dev_attr_in_temp_raw.dev_attr.attr, + &iio_const_attr_in_temp_offset.dev_attr.attr, + &iio_const_attr_in_temp_scale.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + &iio_dev_attr_phcal.dev_attr.attr, + &iio_dev_attr_cfden.dev_attr.attr, + &iio_dev_attr_aenergy.dev_attr.attr, + &iio_dev_attr_cfnum.dev_attr.attr, + &iio_dev_attr_apos.dev_attr.attr, + &iio_dev_attr_sagcyc.dev_attr.attr, + &iio_dev_attr_saglvl.dev_attr.attr, + &iio_dev_attr_linecyc.dev_attr.attr, + &iio_dev_attr_lenergy.dev_attr.attr, + &iio_dev_attr_chksum.dev_attr.attr, + &iio_dev_attr_pga_gain.dev_attr.attr, + &iio_dev_attr_active_power_gain.dev_attr.attr, + &iio_dev_attr_choff_1.dev_attr.attr, + &iio_dev_attr_choff_2.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7759_attribute_group = { + .attrs = ade7759_attributes, +}; + +static const struct iio_info ade7759_info = { + .attrs = &ade7759_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int __devinit ade7759_probe(struct spi_device *spi) +{ + int ret; + struct ade7759_state *st; + struct iio_dev *indio_dev; + + /* setup the industrialio driver allocated elements */ + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + /* this is only used for removal purposes */ + spi_set_drvdata(spi, indio_dev); + + st = iio_priv(indio_dev); + st->us = spi; + mutex_init(&st->buf_lock); + indio_dev->name = spi->dev.driver->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ade7759_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + /* Get the device into a sane initial state */ + ret = ade7759_initial_setup(indio_dev); + if (ret) + goto error_free_dev; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + return 0; + +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +/* fixme, confirm ordering in this function */ +static int ade7759_remove(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + ret = ade7759_stop_device(&(indio_dev->dev)); + if (ret) + goto err_ret; + + iio_free_device(indio_dev); + +err_ret: + return ret; +} + +static struct spi_driver ade7759_driver = { + .driver = { + .name = "ade7759", + .owner = THIS_MODULE, + }, + .probe = ade7759_probe, + .remove = __devexit_p(ade7759_remove), +}; +module_spi_driver(ade7759_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7759 Active Energy Metering IC Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("spi:ad7759"); diff --git a/drivers/staging/iio/meter/ade7759.h b/drivers/staging/iio/meter/ade7759.h new file mode 100644 index 00000000..c81d23d7 --- /dev/null +++ b/drivers/staging/iio/meter/ade7759.h @@ -0,0 +1,55 @@ +#ifndef _ADE7759_H +#define _ADE7759_H + +#define ADE7759_WAVEFORM 0x01 +#define ADE7759_AENERGY 0x02 +#define ADE7759_RSTENERGY 0x03 +#define ADE7759_STATUS 0x04 +#define ADE7759_RSTSTATUS 0x05 +#define ADE7759_MODE 0x06 +#define ADE7759_CFDEN 0x07 +#define ADE7759_CH1OS 0x08 +#define ADE7759_CH2OS 0x09 +#define ADE7759_GAIN 0x0A +#define ADE7759_APGAIN 0x0B +#define ADE7759_PHCAL 0x0C +#define ADE7759_APOS 0x0D +#define ADE7759_ZXTOUT 0x0E +#define ADE7759_SAGCYC 0x0F +#define ADE7759_IRQEN 0x10 +#define ADE7759_SAGLVL 0x11 +#define ADE7759_TEMP 0x12 +#define ADE7759_LINECYC 0x13 +#define ADE7759_LENERGY 0x14 +#define ADE7759_CFNUM 0x15 +#define ADE7759_CHKSUM 0x1E +#define ADE7759_DIEREV 0x1F + +#define ADE7759_READ_REG(a) a +#define ADE7759_WRITE_REG(a) ((a) | 0x80) + +#define ADE7759_MAX_TX 6 +#define ADE7759_MAX_RX 6 +#define ADE7759_STARTUP_DELAY 1 + +#define ADE7759_SPI_SLOW (u32)(300 * 1000) +#define ADE7759_SPI_BURST (u32)(1000 * 1000) +#define ADE7759_SPI_FAST (u32)(2000 * 1000) + +#define DRIVER_NAME "ade7759" + +/** + * struct ade7759_state - device instance specific data + * @us: actual spi_device + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct ade7759_state { + struct spi_device *us; + struct mutex buf_lock; + u8 tx[ADE7759_MAX_TX] ____cacheline_aligned; + u8 rx[ADE7759_MAX_RX]; +}; + +#endif diff --git a/drivers/staging/iio/meter/ade7854-i2c.c b/drivers/staging/iio/meter/ade7854-i2c.c new file mode 100644 index 00000000..1e1faa04 --- /dev/null +++ b/drivers/staging/iio/meter/ade7854-i2c.c @@ -0,0 +1,260 @@ +/* + * ADE7854/58/68/78 Polyphase Multifunction Energy Metering IC Driver (I2C Bus) + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "ade7854.h" + +static int ade7854_i2c_write_reg_8(struct device *dev, + u16 reg_address, + u8 value) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + st->tx[2] = value; + + ret = i2c_master_send(st->i2c, st->tx, 3); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_i2c_write_reg_16(struct device *dev, + u16 reg_address, + u16 value) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + st->tx[2] = (value >> 8) & 0xFF; + st->tx[3] = value & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 4); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_i2c_write_reg_24(struct device *dev, + u16 reg_address, + u32 value) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + st->tx[2] = (value >> 16) & 0xFF; + st->tx[3] = (value >> 8) & 0xFF; + st->tx[4] = value & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 5); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_i2c_write_reg_32(struct device *dev, + u16 reg_address, + u32 value) +{ + int ret; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + st->tx[2] = (value >> 24) & 0xFF; + st->tx[3] = (value >> 16) & 0xFF; + st->tx[4] = (value >> 8) & 0xFF; + st->tx[5] = value & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 6); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_i2c_read_reg_8(struct device *dev, + u16 reg_address, + u8 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 2); + if (ret) + goto out; + + ret = i2c_master_recv(st->i2c, st->rx, 1); + if (ret) + goto out; + + *val = st->rx[0]; +out: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_i2c_read_reg_16(struct device *dev, + u16 reg_address, + u16 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 2); + if (ret) + goto out; + + ret = i2c_master_recv(st->i2c, st->rx, 2); + if (ret) + goto out; + + *val = (st->rx[0] << 8) | st->rx[1]; +out: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_i2c_read_reg_24(struct device *dev, + u16 reg_address, + u32 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 2); + if (ret) + goto out; + + ret = i2c_master_recv(st->i2c, st->rx, 3); + if (ret) + goto out; + + *val = (st->rx[0] << 16) | (st->rx[1] << 8) | st->rx[2]; +out: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_i2c_read_reg_32(struct device *dev, + u16 reg_address, + u32 *val) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&st->buf_lock); + st->tx[0] = (reg_address >> 8) & 0xFF; + st->tx[1] = reg_address & 0xFF; + + ret = i2c_master_send(st->i2c, st->tx, 2); + if (ret) + goto out; + + ret = i2c_master_recv(st->i2c, st->rx, 3); + if (ret) + goto out; + + *val = (st->rx[0] << 24) | (st->rx[1] << 16) | (st->rx[2] << 8) | st->rx[3]; +out: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int __devinit ade7854_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct ade7854_state *st; + struct iio_dev *indio_dev; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + st = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + st->read_reg_8 = ade7854_i2c_read_reg_8; + st->read_reg_16 = ade7854_i2c_read_reg_16; + st->read_reg_24 = ade7854_i2c_read_reg_24; + st->read_reg_32 = ade7854_i2c_read_reg_32; + st->write_reg_8 = ade7854_i2c_write_reg_8; + st->write_reg_16 = ade7854_i2c_write_reg_16; + st->write_reg_24 = ade7854_i2c_write_reg_24; + st->write_reg_32 = ade7854_i2c_write_reg_32; + st->i2c = client; + st->irq = client->irq; + + ret = ade7854_probe(indio_dev, &client->dev); + if (ret) + iio_free_device(indio_dev); + + return ret; +} + +static int __devexit ade7854_i2c_remove(struct i2c_client *client) +{ + return ade7854_remove(i2c_get_clientdata(client)); +} + +static const struct i2c_device_id ade7854_id[] = { + { "ade7854", 0 }, + { "ade7858", 0 }, + { "ade7868", 0 }, + { "ade7878", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ade7854_id); + +static struct i2c_driver ade7854_i2c_driver = { + .driver = { + .name = "ade7854", + }, + .probe = ade7854_i2c_probe, + .remove = __devexit_p(ade7854_i2c_remove), + .id_table = ade7854_id, +}; +module_i2c_driver(ade7854_i2c_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7854/58/68/78 Polyphase Multifunction Energy Metering IC I2C Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/meter/ade7854-spi.c b/drivers/staging/iio/meter/ade7854-spi.c new file mode 100644 index 00000000..81121862 --- /dev/null +++ b/drivers/staging/iio/meter/ade7854-spi.c @@ -0,0 +1,361 @@ +/* + * ADE7854/58/68/78 Polyphase Multifunction Energy Metering IC Driver (SPI Bus) + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "ade7854.h" + +static int ade7854_spi_write_reg_8(struct device *dev, + u16 reg_address, + u8 value) +{ + int ret; + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 4, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_WRITE_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + st->tx[3] = value & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->spi, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_spi_write_reg_16(struct device *dev, + u16 reg_address, + u16 value) +{ + int ret; + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 5, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_WRITE_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + st->tx[3] = (value >> 8) & 0xFF; + st->tx[4] = value & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->spi, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_spi_write_reg_24(struct device *dev, + u16 reg_address, + u32 value) +{ + int ret; + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 6, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_WRITE_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + st->tx[3] = (value >> 16) & 0xFF; + st->tx[4] = (value >> 8) & 0xFF; + st->tx[5] = value & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->spi, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_spi_write_reg_32(struct device *dev, + u16 reg_address, + u32 value) +{ + int ret; + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct spi_transfer xfer = { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 7, + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_WRITE_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + st->tx[3] = (value >> 24) & 0xFF; + st->tx[4] = (value >> 16) & 0xFF; + st->tx[5] = (value >> 8) & 0xFF; + st->tx[6] = value & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + ret = spi_sync(st->spi, &msg); + mutex_unlock(&st->buf_lock); + + return ret; +} + +static int ade7854_spi_read_reg_8(struct device *dev, + u16 reg_address, + u8 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 1, + } + }; + + mutex_lock(&st->buf_lock); + + st->tx[0] = ADE7854_READ_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->spi, &msg); + if (ret) { + dev_err(&st->spi->dev, "problem when reading 8 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = st->rx[0]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_spi_read_reg_16(struct device *dev, + u16 reg_address, + u16 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 2, + } + }; + + mutex_lock(&st->buf_lock); + st->tx[0] = ADE7854_READ_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->spi, &msg); + if (ret) { + dev_err(&st->spi->dev, "problem when reading 16 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = be16_to_cpup((const __be16 *)st->rx); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_spi_read_reg_24(struct device *dev, + u16 reg_address, + u32 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 3, + } + }; + + mutex_lock(&st->buf_lock); + + st->tx[0] = ADE7854_READ_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->spi, &msg); + if (ret) { + dev_err(&st->spi->dev, "problem when reading 24 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = (st->rx[0] << 16) | (st->rx[1] << 8) | st->rx[2]; + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int ade7854_spi_read_reg_32(struct device *dev, + u16 reg_address, + u32 *val) +{ + struct spi_message msg; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = st->tx, + .bits_per_word = 8, + .len = 3, + }, { + .rx_buf = st->rx, + .bits_per_word = 8, + .len = 4, + } + }; + + mutex_lock(&st->buf_lock); + + st->tx[0] = ADE7854_READ_REG; + st->tx[1] = (reg_address >> 8) & 0xFF; + st->tx[2] = reg_address & 0xFF; + + spi_message_init(&msg); + spi_message_add_tail(&xfers[0], &msg); + spi_message_add_tail(&xfers[1], &msg); + ret = spi_sync(st->spi, &msg); + if (ret) { + dev_err(&st->spi->dev, "problem when reading 32 bit register 0x%02X", + reg_address); + goto error_ret; + } + *val = be32_to_cpup((const __be32 *)st->rx); + +error_ret: + mutex_unlock(&st->buf_lock); + return ret; +} + +static int __devinit ade7854_spi_probe(struct spi_device *spi) +{ + int ret; + struct ade7854_state *st; + struct iio_dev *indio_dev; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + st->read_reg_8 = ade7854_spi_read_reg_8; + st->read_reg_16 = ade7854_spi_read_reg_16; + st->read_reg_24 = ade7854_spi_read_reg_24; + st->read_reg_32 = ade7854_spi_read_reg_32; + st->write_reg_8 = ade7854_spi_write_reg_8; + st->write_reg_16 = ade7854_spi_write_reg_16; + st->write_reg_24 = ade7854_spi_write_reg_24; + st->write_reg_32 = ade7854_spi_write_reg_32; + st->irq = spi->irq; + st->spi = spi; + + + ret = ade7854_probe(indio_dev, &spi->dev); + if (ret) + iio_free_device(indio_dev); + + return 0; +} + +static int ade7854_spi_remove(struct spi_device *spi) +{ + ade7854_remove(spi_get_drvdata(spi)); + + return 0; +} +static const struct spi_device_id ade7854_id[] = { + { "ade7854", 0 }, + { "ade7858", 0 }, + { "ade7868", 0 }, + { "ade7878", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, ade7854_id); + +static struct spi_driver ade7854_driver = { + .driver = { + .name = "ade7854", + .owner = THIS_MODULE, + }, + .probe = ade7854_spi_probe, + .remove = __devexit_p(ade7854_spi_remove), + .id_table = ade7854_id, +}; +module_spi_driver(ade7854_driver); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7854/58/68/78 SPI Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/meter/ade7854.c b/drivers/staging/iio/meter/ade7854.c new file mode 100644 index 00000000..49c01c5c --- /dev/null +++ b/drivers/staging/iio/meter/ade7854.c @@ -0,0 +1,601 @@ +/* + * ADE7854/58/68/78 Polyphase Multifunction Energy Metering IC Driver + * + * Copyright 2010 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "meter.h" +#include "ade7854.h" + +static ssize_t ade7854_read_8bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 val = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = st->read_reg_8(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7854_read_16bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u16 val = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = st->read_reg_16(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7854_read_24bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + ret = st->read_reg_24(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7854_read_32bit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 val = 0; + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + ret = st->read_reg_32(dev, this_attr->address, &val); + if (ret) + return ret; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t ade7854_write_8bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = st->write_reg_8(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7854_write_16bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = st->write_reg_16(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7854_write_24bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = st->write_reg_24(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static ssize_t ade7854_write_32bit(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + long val; + + ret = strict_strtol(buf, 10, &val); + if (ret) + goto error_ret; + ret = st->write_reg_32(dev, this_attr->address, val); + +error_ret: + return ret ? ret : len; +} + +static int ade7854_reset(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + u16 val; + + st->read_reg_16(dev, ADE7854_CONFIG, &val); + val |= 1 << 7; /* Software Chip Reset */ + + return st->write_reg_16(dev, ADE7854_CONFIG, val); +} + + +static ssize_t ade7854_write_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + if (len < 1) + return -1; + switch (buf[0]) { + case '1': + case 'y': + case 'Y': + return ade7854_reset(dev); + } + return -1; +} + +static IIO_DEV_ATTR_AIGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AIGAIN); +static IIO_DEV_ATTR_BIGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BIGAIN); +static IIO_DEV_ATTR_CIGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CIGAIN); +static IIO_DEV_ATTR_NIGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_NIGAIN); +static IIO_DEV_ATTR_AVGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AVGAIN); +static IIO_DEV_ATTR_BVGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BVGAIN); +static IIO_DEV_ATTR_CVGAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CVGAIN); +static IIO_DEV_ATTR_APPARENT_POWER_A_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AVAGAIN); +static IIO_DEV_ATTR_APPARENT_POWER_B_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BVAGAIN); +static IIO_DEV_ATTR_APPARENT_POWER_C_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CVAGAIN); +static IIO_DEV_ATTR_ACTIVE_POWER_A_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AWATTOS); +static IIO_DEV_ATTR_ACTIVE_POWER_B_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BWATTOS); +static IIO_DEV_ATTR_ACTIVE_POWER_C_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CWATTOS); +static IIO_DEV_ATTR_REACTIVE_POWER_A_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AVARGAIN); +static IIO_DEV_ATTR_REACTIVE_POWER_B_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BVARGAIN); +static IIO_DEV_ATTR_REACTIVE_POWER_C_GAIN(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CVARGAIN); +static IIO_DEV_ATTR_REACTIVE_POWER_A_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_AVAROS); +static IIO_DEV_ATTR_REACTIVE_POWER_B_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_BVAROS); +static IIO_DEV_ATTR_REACTIVE_POWER_C_OFFSET(S_IWUSR | S_IRUGO, + ade7854_read_24bit, + ade7854_write_24bit, + ADE7854_CVAROS); +static IIO_DEV_ATTR_VPEAK(S_IWUSR | S_IRUGO, + ade7854_read_32bit, + ade7854_write_32bit, + ADE7854_VPEAK); +static IIO_DEV_ATTR_IPEAK(S_IWUSR | S_IRUGO, + ade7854_read_32bit, + ade7854_write_32bit, + ADE7854_VPEAK); +static IIO_DEV_ATTR_APHCAL(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_APHCAL); +static IIO_DEV_ATTR_BPHCAL(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_BPHCAL); +static IIO_DEV_ATTR_CPHCAL(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CPHCAL); +static IIO_DEV_ATTR_CF1DEN(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CF1DEN); +static IIO_DEV_ATTR_CF2DEN(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CF2DEN); +static IIO_DEV_ATTR_CF3DEN(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CF3DEN); +static IIO_DEV_ATTR_LINECYC(S_IWUSR | S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_LINECYC); +static IIO_DEV_ATTR_SAGCYC(S_IWUSR | S_IRUGO, + ade7854_read_8bit, + ade7854_write_8bit, + ADE7854_SAGCYC); +static IIO_DEV_ATTR_CFCYC(S_IWUSR | S_IRUGO, + ade7854_read_8bit, + ade7854_write_8bit, + ADE7854_CFCYC); +static IIO_DEV_ATTR_PEAKCYC(S_IWUSR | S_IRUGO, + ade7854_read_8bit, + ade7854_write_8bit, + ADE7854_PEAKCYC); +static IIO_DEV_ATTR_CHKSUM(ade7854_read_24bit, + ADE7854_CHECKSUM); +static IIO_DEV_ATTR_ANGLE0(ade7854_read_24bit, + ADE7854_ANGLE0); +static IIO_DEV_ATTR_ANGLE1(ade7854_read_24bit, + ADE7854_ANGLE1); +static IIO_DEV_ATTR_ANGLE2(ade7854_read_24bit, + ADE7854_ANGLE2); +static IIO_DEV_ATTR_AIRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_AIRMS); +static IIO_DEV_ATTR_BIRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_BIRMS); +static IIO_DEV_ATTR_CIRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_CIRMS); +static IIO_DEV_ATTR_NIRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_NIRMS); +static IIO_DEV_ATTR_AVRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_AVRMS); +static IIO_DEV_ATTR_BVRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_BVRMS); +static IIO_DEV_ATTR_CVRMS(S_IRUGO, + ade7854_read_24bit, + NULL, + ADE7854_CVRMS); +static IIO_DEV_ATTR_AIRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_AIRMSOS); +static IIO_DEV_ATTR_BIRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_BIRMSOS); +static IIO_DEV_ATTR_CIRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CIRMSOS); +static IIO_DEV_ATTR_AVRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_AVRMSOS); +static IIO_DEV_ATTR_BVRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_BVRMSOS); +static IIO_DEV_ATTR_CVRMSOS(S_IRUGO, + ade7854_read_16bit, + ade7854_write_16bit, + ADE7854_CVRMSOS); +static IIO_DEV_ATTR_VOLT_A(ade7854_read_24bit, + ADE7854_VAWV); +static IIO_DEV_ATTR_VOLT_B(ade7854_read_24bit, + ADE7854_VBWV); +static IIO_DEV_ATTR_VOLT_C(ade7854_read_24bit, + ADE7854_VCWV); +static IIO_DEV_ATTR_CURRENT_A(ade7854_read_24bit, + ADE7854_IAWV); +static IIO_DEV_ATTR_CURRENT_B(ade7854_read_24bit, + ADE7854_IBWV); +static IIO_DEV_ATTR_CURRENT_C(ade7854_read_24bit, + ADE7854_ICWV); +static IIO_DEV_ATTR_AWATTHR(ade7854_read_32bit, + ADE7854_AWATTHR); +static IIO_DEV_ATTR_BWATTHR(ade7854_read_32bit, + ADE7854_BWATTHR); +static IIO_DEV_ATTR_CWATTHR(ade7854_read_32bit, + ADE7854_CWATTHR); +static IIO_DEV_ATTR_AFWATTHR(ade7854_read_32bit, + ADE7854_AFWATTHR); +static IIO_DEV_ATTR_BFWATTHR(ade7854_read_32bit, + ADE7854_BFWATTHR); +static IIO_DEV_ATTR_CFWATTHR(ade7854_read_32bit, + ADE7854_CFWATTHR); +static IIO_DEV_ATTR_AVARHR(ade7854_read_32bit, + ADE7854_AVARHR); +static IIO_DEV_ATTR_BVARHR(ade7854_read_32bit, + ADE7854_BVARHR); +static IIO_DEV_ATTR_CVARHR(ade7854_read_32bit, + ADE7854_CVARHR); +static IIO_DEV_ATTR_AVAHR(ade7854_read_32bit, + ADE7854_AVAHR); +static IIO_DEV_ATTR_BVAHR(ade7854_read_32bit, + ADE7854_BVAHR); +static IIO_DEV_ATTR_CVAHR(ade7854_read_32bit, + ADE7854_CVAHR); + +static int ade7854_set_irq(struct device *dev, bool enable) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct ade7854_state *st = iio_priv(indio_dev); + + int ret; + u32 irqen; + + ret = st->read_reg_32(dev, ADE7854_MASK0, &irqen); + if (ret) + goto error_ret; + + if (enable) + irqen |= 1 << 17; /* 1: interrupt enabled when all periodical + (at 8 kHz rate) DSP computations finish. */ + else + irqen &= ~(1 << 17); + + ret = st->write_reg_32(dev, ADE7854_MASK0, irqen); + if (ret) + goto error_ret; + +error_ret: + return ret; +} + +static int ade7854_initial_setup(struct iio_dev *indio_dev) +{ + int ret; + struct device *dev = &indio_dev->dev; + + /* Disable IRQ */ + ret = ade7854_set_irq(dev, false); + if (ret) { + dev_err(dev, "disable irq failed"); + goto err_ret; + } + + ade7854_reset(dev); + msleep(ADE7854_STARTUP_DELAY); + +err_ret: + return ret; +} + +static IIO_DEV_ATTR_RESET(ade7854_write_reset); + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("8000"); + +static IIO_CONST_ATTR(name, "ade7854"); + +static struct attribute *ade7854_attributes[] = { + &iio_dev_attr_aigain.dev_attr.attr, + &iio_dev_attr_bigain.dev_attr.attr, + &iio_dev_attr_cigain.dev_attr.attr, + &iio_dev_attr_nigain.dev_attr.attr, + &iio_dev_attr_avgain.dev_attr.attr, + &iio_dev_attr_bvgain.dev_attr.attr, + &iio_dev_attr_cvgain.dev_attr.attr, + &iio_dev_attr_linecyc.dev_attr.attr, + &iio_dev_attr_sagcyc.dev_attr.attr, + &iio_dev_attr_cfcyc.dev_attr.attr, + &iio_dev_attr_peakcyc.dev_attr.attr, + &iio_dev_attr_chksum.dev_attr.attr, + &iio_dev_attr_apparent_power_a_gain.dev_attr.attr, + &iio_dev_attr_apparent_power_b_gain.dev_attr.attr, + &iio_dev_attr_apparent_power_c_gain.dev_attr.attr, + &iio_dev_attr_active_power_a_offset.dev_attr.attr, + &iio_dev_attr_active_power_b_offset.dev_attr.attr, + &iio_dev_attr_active_power_c_offset.dev_attr.attr, + &iio_dev_attr_reactive_power_a_gain.dev_attr.attr, + &iio_dev_attr_reactive_power_b_gain.dev_attr.attr, + &iio_dev_attr_reactive_power_c_gain.dev_attr.attr, + &iio_dev_attr_reactive_power_a_offset.dev_attr.attr, + &iio_dev_attr_reactive_power_b_offset.dev_attr.attr, + &iio_dev_attr_reactive_power_c_offset.dev_attr.attr, + &iio_dev_attr_awatthr.dev_attr.attr, + &iio_dev_attr_bwatthr.dev_attr.attr, + &iio_dev_attr_cwatthr.dev_attr.attr, + &iio_dev_attr_afwatthr.dev_attr.attr, + &iio_dev_attr_bfwatthr.dev_attr.attr, + &iio_dev_attr_cfwatthr.dev_attr.attr, + &iio_dev_attr_avarhr.dev_attr.attr, + &iio_dev_attr_bvarhr.dev_attr.attr, + &iio_dev_attr_cvarhr.dev_attr.attr, + &iio_dev_attr_angle0.dev_attr.attr, + &iio_dev_attr_angle1.dev_attr.attr, + &iio_dev_attr_angle2.dev_attr.attr, + &iio_dev_attr_avahr.dev_attr.attr, + &iio_dev_attr_bvahr.dev_attr.attr, + &iio_dev_attr_cvahr.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_reset.dev_attr.attr, + &iio_const_attr_name.dev_attr.attr, + &iio_dev_attr_vpeak.dev_attr.attr, + &iio_dev_attr_ipeak.dev_attr.attr, + &iio_dev_attr_aphcal.dev_attr.attr, + &iio_dev_attr_bphcal.dev_attr.attr, + &iio_dev_attr_cphcal.dev_attr.attr, + &iio_dev_attr_cf1den.dev_attr.attr, + &iio_dev_attr_cf2den.dev_attr.attr, + &iio_dev_attr_cf3den.dev_attr.attr, + &iio_dev_attr_airms.dev_attr.attr, + &iio_dev_attr_birms.dev_attr.attr, + &iio_dev_attr_cirms.dev_attr.attr, + &iio_dev_attr_nirms.dev_attr.attr, + &iio_dev_attr_avrms.dev_attr.attr, + &iio_dev_attr_bvrms.dev_attr.attr, + &iio_dev_attr_cvrms.dev_attr.attr, + &iio_dev_attr_airmsos.dev_attr.attr, + &iio_dev_attr_birmsos.dev_attr.attr, + &iio_dev_attr_cirmsos.dev_attr.attr, + &iio_dev_attr_avrmsos.dev_attr.attr, + &iio_dev_attr_bvrmsos.dev_attr.attr, + &iio_dev_attr_cvrmsos.dev_attr.attr, + &iio_dev_attr_volt_a.dev_attr.attr, + &iio_dev_attr_volt_b.dev_attr.attr, + &iio_dev_attr_volt_c.dev_attr.attr, + &iio_dev_attr_current_a.dev_attr.attr, + &iio_dev_attr_current_b.dev_attr.attr, + &iio_dev_attr_current_c.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ade7854_attribute_group = { + .attrs = ade7854_attributes, +}; + +static const struct iio_info ade7854_info = { + .attrs = &ade7854_attribute_group, + .driver_module = THIS_MODULE, +}; + +int ade7854_probe(struct iio_dev *indio_dev, struct device *dev) +{ + int ret; + struct ade7854_state *st = iio_priv(indio_dev); + /* setup the industrialio driver allocated elements */ + mutex_init(&st->buf_lock); + + indio_dev->dev.parent = dev; + indio_dev->info = &ade7854_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + /* Get the device into a sane initial state */ + ret = ade7854_initial_setup(indio_dev); + if (ret) + goto error_unreg_dev; + + return 0; + +error_unreg_dev: + iio_device_unregister(indio_dev); +error_free_dev: + iio_free_device(indio_dev); + + return ret; +} +EXPORT_SYMBOL(ade7854_probe); + +int ade7854_remove(struct iio_dev *indio_dev) +{ + iio_device_unregister(indio_dev); + iio_free_device(indio_dev); + + return 0; +} +EXPORT_SYMBOL(ade7854_remove); + +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_DESCRIPTION("Analog Devices ADE7854/58/68/78 Polyphase Energy Meter"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/meter/ade7854.h b/drivers/staging/iio/meter/ade7854.h new file mode 100644 index 00000000..2c96e869 --- /dev/null +++ b/drivers/staging/iio/meter/ade7854.h @@ -0,0 +1,176 @@ +#ifndef _ADE7854_H +#define _ADE7854_H + +#define ADE7854_AIGAIN 0x4380 +#define ADE7854_AVGAIN 0x4381 +#define ADE7854_BIGAIN 0x4382 +#define ADE7854_BVGAIN 0x4383 +#define ADE7854_CIGAIN 0x4384 +#define ADE7854_CVGAIN 0x4385 +#define ADE7854_NIGAIN 0x4386 +#define ADE7854_AIRMSOS 0x4387 +#define ADE7854_AVRMSOS 0x4388 +#define ADE7854_BIRMSOS 0x4389 +#define ADE7854_BVRMSOS 0x438A +#define ADE7854_CIRMSOS 0x438B +#define ADE7854_CVRMSOS 0x438C +#define ADE7854_NIRMSOS 0x438D +#define ADE7854_AVAGAIN 0x438E +#define ADE7854_BVAGAIN 0x438F +#define ADE7854_CVAGAIN 0x4390 +#define ADE7854_AWGAIN 0x4391 +#define ADE7854_AWATTOS 0x4392 +#define ADE7854_BWGAIN 0x4393 +#define ADE7854_BWATTOS 0x4394 +#define ADE7854_CWGAIN 0x4395 +#define ADE7854_CWATTOS 0x4396 +#define ADE7854_AVARGAIN 0x4397 +#define ADE7854_AVAROS 0x4398 +#define ADE7854_BVARGAIN 0x4399 +#define ADE7854_BVAROS 0x439A +#define ADE7854_CVARGAIN 0x439B +#define ADE7854_CVAROS 0x439C +#define ADE7854_AFWGAIN 0x439D +#define ADE7854_AFWATTOS 0x439E +#define ADE7854_BFWGAIN 0x439F +#define ADE7854_BFWATTOS 0x43A0 +#define ADE7854_CFWGAIN 0x43A1 +#define ADE7854_CFWATTOS 0x43A2 +#define ADE7854_AFVARGAIN 0x43A3 +#define ADE7854_AFVAROS 0x43A4 +#define ADE7854_BFVARGAIN 0x43A5 +#define ADE7854_BFVAROS 0x43A6 +#define ADE7854_CFVARGAIN 0x43A7 +#define ADE7854_CFVAROS 0x43A8 +#define ADE7854_VATHR1 0x43A9 +#define ADE7854_VATHR0 0x43AA +#define ADE7854_WTHR1 0x43AB +#define ADE7854_WTHR0 0x43AC +#define ADE7854_VARTHR1 0x43AD +#define ADE7854_VARTHR0 0x43AE +#define ADE7854_RSV 0x43AF +#define ADE7854_VANOLOAD 0x43B0 +#define ADE7854_APNOLOAD 0x43B1 +#define ADE7854_VARNOLOAD 0x43B2 +#define ADE7854_VLEVEL 0x43B3 +#define ADE7854_DICOEFF 0x43B5 +#define ADE7854_HPFDIS 0x43B6 +#define ADE7854_ISUMLVL 0x43B8 +#define ADE7854_ISUM 0x43BF +#define ADE7854_AIRMS 0x43C0 +#define ADE7854_AVRMS 0x43C1 +#define ADE7854_BIRMS 0x43C2 +#define ADE7854_BVRMS 0x43C3 +#define ADE7854_CIRMS 0x43C4 +#define ADE7854_CVRMS 0x43C5 +#define ADE7854_NIRMS 0x43C6 +#define ADE7854_RUN 0xE228 +#define ADE7854_AWATTHR 0xE400 +#define ADE7854_BWATTHR 0xE401 +#define ADE7854_CWATTHR 0xE402 +#define ADE7854_AFWATTHR 0xE403 +#define ADE7854_BFWATTHR 0xE404 +#define ADE7854_CFWATTHR 0xE405 +#define ADE7854_AVARHR 0xE406 +#define ADE7854_BVARHR 0xE407 +#define ADE7854_CVARHR 0xE408 +#define ADE7854_AFVARHR 0xE409 +#define ADE7854_BFVARHR 0xE40A +#define ADE7854_CFVARHR 0xE40B +#define ADE7854_AVAHR 0xE40C +#define ADE7854_BVAHR 0xE40D +#define ADE7854_CVAHR 0xE40E +#define ADE7854_IPEAK 0xE500 +#define ADE7854_VPEAK 0xE501 +#define ADE7854_STATUS0 0xE502 +#define ADE7854_STATUS1 0xE503 +#define ADE7854_OILVL 0xE507 +#define ADE7854_OVLVL 0xE508 +#define ADE7854_SAGLVL 0xE509 +#define ADE7854_MASK0 0xE50A +#define ADE7854_MASK1 0xE50B +#define ADE7854_IAWV 0xE50C +#define ADE7854_IBWV 0xE50D +#define ADE7854_ICWV 0xE50E +#define ADE7854_VAWV 0xE510 +#define ADE7854_VBWV 0xE511 +#define ADE7854_VCWV 0xE512 +#define ADE7854_AWATT 0xE513 +#define ADE7854_BWATT 0xE514 +#define ADE7854_CWATT 0xE515 +#define ADE7854_AVA 0xE519 +#define ADE7854_BVA 0xE51A +#define ADE7854_CVA 0xE51B +#define ADE7854_CHECKSUM 0xE51F +#define ADE7854_VNOM 0xE520 +#define ADE7854_PHSTATUS 0xE600 +#define ADE7854_ANGLE0 0xE601 +#define ADE7854_ANGLE1 0xE602 +#define ADE7854_ANGLE2 0xE603 +#define ADE7854_PERIOD 0xE607 +#define ADE7854_PHNOLOAD 0xE608 +#define ADE7854_LINECYC 0xE60C +#define ADE7854_ZXTOUT 0xE60D +#define ADE7854_COMPMODE 0xE60E +#define ADE7854_GAIN 0xE60F +#define ADE7854_CFMODE 0xE610 +#define ADE7854_CF1DEN 0xE611 +#define ADE7854_CF2DEN 0xE612 +#define ADE7854_CF3DEN 0xE613 +#define ADE7854_APHCAL 0xE614 +#define ADE7854_BPHCAL 0xE615 +#define ADE7854_CPHCAL 0xE616 +#define ADE7854_PHSIGN 0xE617 +#define ADE7854_CONFIG 0xE618 +#define ADE7854_MMODE 0xE700 +#define ADE7854_ACCMODE 0xE701 +#define ADE7854_LCYCMODE 0xE702 +#define ADE7854_PEAKCYC 0xE703 +#define ADE7854_SAGCYC 0xE704 +#define ADE7854_CFCYC 0xE705 +#define ADE7854_HSDC_CFG 0xE706 +#define ADE7854_CONFIG2 0xEC01 + +#define ADE7854_READ_REG 0x1 +#define ADE7854_WRITE_REG 0x0 + +#define ADE7854_MAX_TX 7 +#define ADE7854_MAX_RX 7 +#define ADE7854_STARTUP_DELAY 1 + +#define ADE7854_SPI_SLOW (u32)(300 * 1000) +#define ADE7854_SPI_BURST (u32)(1000 * 1000) +#define ADE7854_SPI_FAST (u32)(2000 * 1000) + +#define DRIVER_NAME "ade7854" + +/** + * struct ade7854_state - device instance specific data + * @spi: actual spi_device + * @indio_dev: industrial I/O device structure + * @buf_lock: mutex to protect tx and rx + * @tx: transmit buffer + * @rx: receive buffer + **/ +struct ade7854_state { + struct spi_device *spi; + struct i2c_client *i2c; + int (*read_reg_8) (struct device *, u16, u8 *); + int (*read_reg_16) (struct device *, u16, u16 *); + int (*read_reg_24) (struct device *, u16, u32 *); + int (*read_reg_32) (struct device *, u16, u32 *); + int (*write_reg_8) (struct device *, u16, u8); + int (*write_reg_16) (struct device *, u16, u16); + int (*write_reg_24) (struct device *, u16, u32); + int (*write_reg_32) (struct device *, u16, u32); + int irq; + struct mutex buf_lock; + u8 tx[ADE7854_MAX_TX] ____cacheline_aligned; + u8 rx[ADE7854_MAX_RX]; + +}; + +extern int ade7854_probe(struct iio_dev *indio_dev, struct device *dev); +extern int ade7854_remove(struct iio_dev *indio_dev); + +#endif diff --git a/drivers/staging/iio/meter/meter.h b/drivers/staging/iio/meter/meter.h new file mode 100644 index 00000000..6a3db142 --- /dev/null +++ b/drivers/staging/iio/meter/meter.h @@ -0,0 +1,396 @@ +#include "../sysfs.h" + +/* metering ic types of attribute */ + +#define IIO_DEV_ATTR_CURRENT_A_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_a_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_B_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_b_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_C_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_c_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VOLT_A_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(volt_a_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VOLT_B_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(volt_b_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VOLT_C_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(volt_c_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_A_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_a_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_B_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_b_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_C_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_c_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_A_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_a_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_B_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_b_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_C_OFFSET(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_c_offset, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_A_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_a_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_B_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_b_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_C_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(current_c_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APPARENT_POWER_A_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(apparent_power_a_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APPARENT_POWER_B_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(apparent_power_b_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APPARENT_POWER_C_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(apparent_power_c_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_A_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_a_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_B_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_b_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_ACTIVE_POWER_C_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(active_power_c_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_A_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_a_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_B_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_b_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_REACTIVE_POWER_C_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(reactive_power_c_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CURRENT_A(_show, _addr) \ + IIO_DEVICE_ATTR(current_a, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CURRENT_B(_show, _addr) \ + IIO_DEVICE_ATTR(current_b, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CURRENT_C(_show, _addr) \ + IIO_DEVICE_ATTR(current_c, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_VOLT_A(_show, _addr) \ + IIO_DEVICE_ATTR(volt_a, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_VOLT_B(_show, _addr) \ + IIO_DEVICE_ATTR(volt_b, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_VOLT_C(_show, _addr) \ + IIO_DEVICE_ATTR(volt_c, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(aenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(lenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_RAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(raenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(laenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_VAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(vaenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LVAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(lvaenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_RVAENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(rvaenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_LVARENERGY(_show, _addr) \ + IIO_DEVICE_ATTR(lvarenergy, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CHKSUM(_show, _addr) \ + IIO_DEVICE_ATTR(chksum, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_ANGLE0(_show, _addr) \ + IIO_DEVICE_ATTR(angle0, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_ANGLE1(_show, _addr) \ + IIO_DEVICE_ATTR(angle1, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_ANGLE2(_show, _addr) \ + IIO_DEVICE_ATTR(angle2, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(awatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_BWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(bwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(cwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AFWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(afwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_BFWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(bfwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CFWATTHR(_show, _addr) \ + IIO_DEVICE_ATTR(cfwatthr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AVARHR(_show, _addr) \ + IIO_DEVICE_ATTR(avarhr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_BVARHR(_show, _addr) \ + IIO_DEVICE_ATTR(bvarhr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CVARHR(_show, _addr) \ + IIO_DEVICE_ATTR(cvarhr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_AVAHR(_show, _addr) \ + IIO_DEVICE_ATTR(avahr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_BVAHR(_show, _addr) \ + IIO_DEVICE_ATTR(bvahr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_CVAHR(_show, _addr) \ + IIO_DEVICE_ATTR(cvahr, S_IRUGO, _show, NULL, _addr) + +#define IIO_DEV_ATTR_IOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(ios, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_PHCAL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(phcal, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APHCAL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(aphcal, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BPHCAL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bphcal, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CPHCAL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cphcal, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_APOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(apos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AAPOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(aapos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BAPOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bapos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CAPOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(capos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AVRMSGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(avrmsgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BVRMSGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bvrmsgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CVRMSGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cvrmsgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AIGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(aigain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BIGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bigain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CIGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cigain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_NIGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(nigain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AVGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(avgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BVGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bvgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CVGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cvgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_WGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(wgain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_WDIV(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(wdiv, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CFNUM(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cfnum, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CFDEN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cfden, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CF1DEN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cf1den, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CF2DEN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cf2den, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CF3DEN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cf3den, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_IRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(irms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vrms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AIRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(airms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BIRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(birms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CIRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cirms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_NIRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(nirms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AVRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(avrms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BVRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bvrms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CVRMS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cvrms, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_IRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(irmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vrmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AIRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(airmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BIRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(birmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CIRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cirmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_AVRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(avrmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_BVRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(bvrmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CVRMSOS(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cvrmsos, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VAGAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vagain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_PGA_GAIN(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(pga_gain, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VADIV(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vadiv, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_LINECYC(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(linecyc, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_SAGCYC(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(sagcyc, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CFCYC(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(cfcyc, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_PEAKCYC(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(peakcyc, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_SAGLVL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(saglvl, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_IPKLVL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(ipklvl, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VPKLVL(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vpklvl, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_IPEAK(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(ipeak, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_RIPEAK(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(ripeak, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VPEAK(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vpeak, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_RVPEAK(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(rvpeak, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_VPERIOD(_mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(vperiod, _mode, _show, _store, _addr) + +#define IIO_DEV_ATTR_CH_OFF(_num, _mode, _show, _store, _addr) \ + IIO_DEVICE_ATTR(choff_##_num, _mode, _show, _store, _addr) + +/* active energy register, AENERGY, is more than half full */ +#define IIO_EVENT_ATTR_AENERGY_HALF_FULL(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(aenergy_half_full, _evlist, _show, _store, _mask) + +/* a SAG on the line voltage */ +#define IIO_EVENT_ATTR_LINE_VOLT_SAG(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(line_volt_sag, _evlist, _show, _store, _mask) + +/* + * Indicates the end of energy accumulation over an integer number + * of half line cycles + */ +#define IIO_EVENT_ATTR_CYCEND(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(cycend, _evlist, _show, _store, _mask) + +/* on the rising and falling edge of the voltage waveform */ +#define IIO_EVENT_ATTR_ZERO_CROSS(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(zero_cross, _evlist, _show, _store, _mask) + +/* the active energy register has overflowed */ +#define IIO_EVENT_ATTR_AENERGY_OVERFLOW(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(aenergy_overflow, _evlist, _show, _store, _mask) + +/* the apparent energy register has overflowed */ +#define IIO_EVENT_ATTR_VAENERGY_OVERFLOW(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(vaenergy_overflow, _evlist, _show, _store, _mask) + +/* the active energy register, VAENERGY, is more than half full */ +#define IIO_EVENT_ATTR_VAENERGY_HALF_FULL(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(vaenergy_half_full, _evlist, _show, _store, _mask) + +/* the power has gone from negative to positive */ +#define IIO_EVENT_ATTR_PPOS(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(ppos, _evlist, _show, _store, _mask) + +/* the power has gone from positive to negative */ +#define IIO_EVENT_ATTR_PNEG(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(pneg, _evlist, _show, _store, _mask) + +/* waveform sample from Channel 1 has exceeded the IPKLVL value */ +#define IIO_EVENT_ATTR_IPKLVL_EXC(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(ipklvl_exc, _evlist, _show, _store, _mask) + +/* waveform sample from Channel 2 has exceeded the VPKLVL value */ +#define IIO_EVENT_ATTR_VPKLVL_EXC(_evlist, _show, _store, _mask) \ + IIO_EVENT_ATTR_SH(vpklvl_exc, _evlist, _show, _store, _mask) + diff --git a/drivers/staging/iio/resolver/Kconfig b/drivers/staging/iio/resolver/Kconfig new file mode 100644 index 00000000..49f69ef9 --- /dev/null +++ b/drivers/staging/iio/resolver/Kconfig @@ -0,0 +1,30 @@ +# +# Resolver/Synchro drivers +# +menu "Resolver to digital converters" + +config AD2S90 + tristate "Analog Devices ad2s90 driver" + depends on SPI + help + Say yes here to build support for Analog Devices spi resolver + to digital converters, ad2s90, provides direct access via sysfs. + +config AD2S1200 + tristate "Analog Devices ad2s1200/ad2s1205 driver" + depends on SPI + depends on GENERIC_GPIO + help + Say yes here to build support for Analog Devices spi resolver + to digital converters, ad2s1200 and ad2s1205, provides direct access + via sysfs. + +config AD2S1210 + tristate "Analog Devices ad2s1210 driver" + depends on SPI + depends on GENERIC_GPIO + help + Say yes here to build support for Analog Devices spi resolver + to digital converters, ad2s1210, provides direct access via sysfs. + +endmenu diff --git a/drivers/staging/iio/resolver/Makefile b/drivers/staging/iio/resolver/Makefile new file mode 100644 index 00000000..14375e44 --- /dev/null +++ b/drivers/staging/iio/resolver/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Resolver/Synchro drivers +# + +obj-$(CONFIG_AD2S90) += ad2s90.o +obj-$(CONFIG_AD2S1200) += ad2s1200.o +obj-$(CONFIG_AD2S1210) += ad2s1210.o diff --git a/drivers/staging/iio/resolver/ad2s1200.c b/drivers/staging/iio/resolver/ad2s1200.c new file mode 100644 index 00000000..d8ce854c --- /dev/null +++ b/drivers/staging/iio/resolver/ad2s1200.c @@ -0,0 +1,178 @@ +/* + * ad2s1200.c simple support for the ADI Resolver to Digital Converters: + * AD2S1200/1205 + * + * Copyright (c) 2010-2010 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +#define DRV_NAME "ad2s1200" + +/* input pin sample and rdvel is controlled by driver */ +#define AD2S1200_PN 2 + +/* input clock on serial interface */ +#define AD2S1200_HZ 8192000 +/* clock period in nano second */ +#define AD2S1200_TSCLK (1000000000/AD2S1200_HZ) + +struct ad2s1200_state { + struct mutex lock; + struct spi_device *sdev; + int sample; + int rdvel; + u8 rx[2] ____cacheline_aligned; +}; + +static int ad2s1200_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret = 0; + s16 vel; + struct ad2s1200_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + gpio_set_value(st->sample, 0); + /* delay (6 * AD2S1200_TSCLK + 20) nano seconds */ + udelay(1); + gpio_set_value(st->sample, 1); + gpio_set_value(st->rdvel, !!(chan->type == IIO_ANGL)); + ret = spi_read(st->sdev, st->rx, 2); + if (ret < 0) { + mutex_unlock(&st->lock); + return ret; + } + + switch (chan->type) { + case IIO_ANGL: + *val = (((u16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4); + break; + case IIO_ANGL_VEL: + vel = (((s16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4); + vel = (vel << 4) >> 4; + *val = vel; + default: + mutex_unlock(&st->lock); + return -EINVAL; + } + /* delay (2 * AD2S1200_TSCLK + 20) ns for sample pulse */ + udelay(1); + mutex_unlock(&st->lock); + return IIO_VAL_INT; +} + +static const struct iio_chan_spec ad2s1200_channels[] = { + { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, + }, { + .type = IIO_ANGL_VEL, + .indexed = 1, + .channel = 0, + } +}; + +static const struct iio_info ad2s1200_info = { + .read_raw = &ad2s1200_read_raw, + .driver_module = THIS_MODULE, +}; + +static int __devinit ad2s1200_probe(struct spi_device *spi) +{ + struct ad2s1200_state *st; + struct iio_dev *indio_dev; + int pn, ret = 0; + unsigned short *pins = spi->dev.platform_data; + + for (pn = 0; pn < AD2S1200_PN; pn++) + if (gpio_request_one(pins[pn], GPIOF_DIR_OUT, DRV_NAME)) { + pr_err("%s: request gpio pin %d failed\n", + DRV_NAME, pins[pn]); + goto error_ret; + } + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + spi_set_drvdata(spi, indio_dev); + st = iio_priv(indio_dev); + mutex_init(&st->lock); + st->sdev = spi; + st->sample = pins[0]; + st->rdvel = pins[1]; + + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ad2s1200_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad2s1200_channels; + indio_dev->num_channels = ARRAY_SIZE(ad2s1200_channels); + indio_dev->name = spi_get_device_id(spi)->name; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + spi->max_speed_hz = AD2S1200_HZ; + spi->mode = SPI_MODE_3; + spi_setup(spi); + + return 0; + +error_free_dev: + iio_free_device(indio_dev); +error_ret: + for (--pn; pn >= 0; pn--) + gpio_free(pins[pn]); + return ret; +} + +static int __devexit ad2s1200_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id ad2s1200_id[] = { + { "ad2s1200" }, + { "ad2s1205" }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad2s1200_id); + +static struct spi_driver ad2s1200_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ad2s1200_probe, + .remove = __devexit_p(ad2s1200_remove), + .id_table = ad2s1200_id, +}; +module_spi_driver(ad2s1200_driver); + +MODULE_AUTHOR("Graff Yang "); +MODULE_DESCRIPTION("Analog Devices AD2S1200/1205 Resolver to Digital SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/resolver/ad2s1210.c b/drivers/staging/iio/resolver/ad2s1210.c new file mode 100644 index 00000000..c439fcf7 --- /dev/null +++ b/drivers/staging/iio/resolver/ad2s1210.c @@ -0,0 +1,767 @@ +/* + * ad2s1210.c support for the ADI Resolver to Digital Converters: AD2S1210 + * + * Copyright (c) 2010-2010 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" +#include "ad2s1210.h" + +#define DRV_NAME "ad2s1210" + +#define AD2S1210_DEF_CONTROL 0x7E + +#define AD2S1210_MSB_IS_HIGH 0x80 +#define AD2S1210_MSB_IS_LOW 0x7F +#define AD2S1210_PHASE_LOCK_RANGE_44 0x20 +#define AD2S1210_ENABLE_HYSTERESIS 0x10 +#define AD2S1210_SET_ENRES1 0x08 +#define AD2S1210_SET_ENRES0 0x04 +#define AD2S1210_SET_RES1 0x02 +#define AD2S1210_SET_RES0 0x01 + +#define AD2S1210_SET_ENRESOLUTION (AD2S1210_SET_ENRES1 | \ + AD2S1210_SET_ENRES0) +#define AD2S1210_SET_RESOLUTION (AD2S1210_SET_RES1 | AD2S1210_SET_RES0) + +#define AD2S1210_REG_POSITION 0x80 +#define AD2S1210_REG_VELOCITY 0x82 +#define AD2S1210_REG_LOS_THRD 0x88 +#define AD2S1210_REG_DOS_OVR_THRD 0x89 +#define AD2S1210_REG_DOS_MIS_THRD 0x8A +#define AD2S1210_REG_DOS_RST_MAX_THRD 0x8B +#define AD2S1210_REG_DOS_RST_MIN_THRD 0x8C +#define AD2S1210_REG_LOT_HIGH_THRD 0x8D +#define AD2S1210_REG_LOT_LOW_THRD 0x8E +#define AD2S1210_REG_EXCIT_FREQ 0x91 +#define AD2S1210_REG_CONTROL 0x92 +#define AD2S1210_REG_SOFT_RESET 0xF0 +#define AD2S1210_REG_FAULT 0xFF + +/* pin SAMPLE, A0, A1, RES0, RES1, is controlled by driver */ +#define AD2S1210_SAA 3 +#define AD2S1210_PN (AD2S1210_SAA + AD2S1210_RES) + +#define AD2S1210_MIN_CLKIN 6144000 +#define AD2S1210_MAX_CLKIN 10240000 +#define AD2S1210_MIN_EXCIT 2000 +#define AD2S1210_MAX_EXCIT 20000 +#define AD2S1210_MIN_FCW 0x4 +#define AD2S1210_MAX_FCW 0x50 + +/* default input clock on serial interface */ +#define AD2S1210_DEF_CLKIN 8192000 +/* clock period in nano second */ +#define AD2S1210_DEF_TCK (1000000000/AD2S1210_DEF_CLKIN) +#define AD2S1210_DEF_EXCIT 10000 + +enum ad2s1210_mode { + MOD_POS = 0, + MOD_VEL, + MOD_CONFIG, + MOD_RESERVED, +}; + +static const unsigned int ad2s1210_resolution_value[] = { 10, 12, 14, 16 }; + +struct ad2s1210_state { + const struct ad2s1210_platform_data *pdata; + struct mutex lock; + struct spi_device *sdev; + unsigned int fclkin; + unsigned int fexcit; + bool hysteresis; + bool old_data; + u8 resolution; + enum ad2s1210_mode mode; + u8 rx[2] ____cacheline_aligned; + u8 tx[2] ____cacheline_aligned; +}; + +static const int ad2s1210_mode_vals[4][2] = { + [MOD_POS] = { 0, 0 }, + [MOD_VEL] = { 0, 1 }, + [MOD_CONFIG] = { 1, 0 }, +}; +static inline void ad2s1210_set_mode(enum ad2s1210_mode mode, + struct ad2s1210_state *st) +{ + gpio_set_value(st->pdata->a[0], ad2s1210_mode_vals[mode][0]); + gpio_set_value(st->pdata->a[1], ad2s1210_mode_vals[mode][1]); + st->mode = mode; +} + +/* write 1 bytes (address or data) to the chip */ +static int ad2s1210_config_write(struct ad2s1210_state *st, u8 data) +{ + int ret; + + ad2s1210_set_mode(MOD_CONFIG, st); + st->tx[0] = data; + ret = spi_write(st->sdev, st->tx, 1); + if (ret < 0) + return ret; + st->old_data = true; + + return 0; +} + +/* read value from one of the registers */ +static int ad2s1210_config_read(struct ad2s1210_state *st, + unsigned char address) +{ + struct spi_transfer xfer = { + .len = 2, + .rx_buf = st->rx, + .tx_buf = st->tx, + }; + struct spi_message msg; + int ret = 0; + + ad2s1210_set_mode(MOD_CONFIG, st); + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + st->tx[0] = address | AD2S1210_MSB_IS_HIGH; + st->tx[1] = AD2S1210_REG_FAULT; + ret = spi_sync(st->sdev, &msg); + if (ret < 0) + return ret; + st->old_data = true; + + return st->rx[1]; +} + +static inline +int ad2s1210_update_frequency_control_word(struct ad2s1210_state *st) +{ + int ret; + unsigned char fcw; + + fcw = (unsigned char)(st->fexcit * (1 << 15) / st->fclkin); + if (fcw < AD2S1210_MIN_FCW || fcw > AD2S1210_MAX_FCW) { + pr_err("ad2s1210: FCW out of range\n"); + return -ERANGE; + } + + ret = ad2s1210_config_write(st, AD2S1210_REG_EXCIT_FREQ); + if (ret < 0) + return ret; + + return ad2s1210_config_write(st, fcw); +} + +static unsigned char ad2s1210_read_resolution_pin(struct ad2s1210_state *st) +{ + return ad2s1210_resolution_value[ + (gpio_get_value(st->pdata->res[0]) << 1) | + gpio_get_value(st->pdata->res[1])]; +} + +static const int ad2s1210_res_pins[4][2] = { + { 0, 0 }, {0, 1}, {1, 0}, {1, 1} +}; + +static inline void ad2s1210_set_resolution_pin(struct ad2s1210_state *st) +{ + gpio_set_value(st->pdata->res[0], + ad2s1210_res_pins[(st->resolution - 10)/2][0]); + gpio_set_value(st->pdata->res[1], + ad2s1210_res_pins[(st->resolution - 10)/2][1]); +} + +static inline int ad2s1210_soft_reset(struct ad2s1210_state *st) +{ + int ret; + + ret = ad2s1210_config_write(st, AD2S1210_REG_SOFT_RESET); + if (ret < 0) + return ret; + + return ad2s1210_config_write(st, 0x0); +} + +static ssize_t ad2s1210_store_softreset(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + + mutex_lock(&st->lock); + ret = ad2s1210_soft_reset(st); + mutex_unlock(&st->lock); + + return ret < 0 ? ret : len; +} + +static ssize_t ad2s1210_show_fclkin(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + return sprintf(buf, "%d\n", st->fclkin); +} + +static ssize_t ad2s1210_store_fclkin(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + unsigned long fclkin; + int ret; + + ret = strict_strtoul(buf, 10, &fclkin); + if (ret) + return ret; + if (fclkin < AD2S1210_MIN_CLKIN || fclkin > AD2S1210_MAX_CLKIN) { + pr_err("ad2s1210: fclkin out of range\n"); + return -EINVAL; + } + + mutex_lock(&st->lock); + st->fclkin = fclkin; + + ret = ad2s1210_update_frequency_control_word(st); + if (ret < 0) + goto error_ret; + ret = ad2s1210_soft_reset(st); +error_ret: + mutex_unlock(&st->lock); + + return ret < 0 ? ret : len; +} + +static ssize_t ad2s1210_show_fexcit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + return sprintf(buf, "%d\n", st->fexcit); +} + +static ssize_t ad2s1210_store_fexcit(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + unsigned long fexcit; + int ret; + + ret = strict_strtoul(buf, 10, &fexcit); + if (ret < 0) + return ret; + if (fexcit < AD2S1210_MIN_EXCIT || fexcit > AD2S1210_MAX_EXCIT) { + pr_err("ad2s1210: excitation frequency out of range\n"); + return -EINVAL; + } + mutex_lock(&st->lock); + st->fexcit = fexcit; + ret = ad2s1210_update_frequency_control_word(st); + if (ret < 0) + goto error_ret; + ret = ad2s1210_soft_reset(st); +error_ret: + mutex_unlock(&st->lock); + + return ret < 0 ? ret : len; +} + +static ssize_t ad2s1210_show_control(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + mutex_lock(&st->lock); + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + mutex_unlock(&st->lock); + return ret < 0 ? ret : sprintf(buf, "0x%x\n", ret); +} + +static ssize_t ad2s1210_store_control(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + unsigned long udata; + unsigned char data; + int ret; + + ret = strict_strtoul(buf, 16, &udata); + if (ret) + return -EINVAL; + + mutex_lock(&st->lock); + ret = ad2s1210_config_write(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + data = udata & AD2S1210_MSB_IS_LOW; + ret = ad2s1210_config_write(st, data); + if (ret < 0) + goto error_ret; + + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + if (ret & AD2S1210_MSB_IS_HIGH) { + ret = -EIO; + pr_err("ad2s1210: write control register fail\n"); + goto error_ret; + } + st->resolution + = ad2s1210_resolution_value[data & AD2S1210_SET_RESOLUTION]; + if (st->pdata->gpioin) { + data = ad2s1210_read_resolution_pin(st); + if (data != st->resolution) + pr_warning("ad2s1210: resolution settings not match\n"); + } else + ad2s1210_set_resolution_pin(st); + + ret = len; + st->hysteresis = !!(data & AD2S1210_ENABLE_HYSTERESIS); + +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static ssize_t ad2s1210_show_resolution(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + return sprintf(buf, "%d\n", st->resolution); +} + +static ssize_t ad2s1210_store_resolution(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + unsigned char data; + unsigned long udata; + int ret; + + ret = strict_strtoul(buf, 10, &udata); + if (ret || udata < 10 || udata > 16) { + pr_err("ad2s1210: resolution out of range\n"); + return -EINVAL; + } + mutex_lock(&st->lock); + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + data = ret; + data &= ~AD2S1210_SET_RESOLUTION; + data |= (udata - 10) >> 1; + ret = ad2s1210_config_write(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + ret = ad2s1210_config_write(st, data & AD2S1210_MSB_IS_LOW); + if (ret < 0) + goto error_ret; + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + data = ret; + if (data & AD2S1210_MSB_IS_HIGH) { + ret = -EIO; + pr_err("ad2s1210: setting resolution fail\n"); + goto error_ret; + } + st->resolution + = ad2s1210_resolution_value[data & AD2S1210_SET_RESOLUTION]; + if (st->pdata->gpioin) { + data = ad2s1210_read_resolution_pin(st); + if (data != st->resolution) + pr_warning("ad2s1210: resolution settings not match\n"); + } else + ad2s1210_set_resolution_pin(st); + ret = len; +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +/* read the fault register since last sample */ +static ssize_t ad2s1210_show_fault(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + + mutex_lock(&st->lock); + ret = ad2s1210_config_read(st, AD2S1210_REG_FAULT); + mutex_unlock(&st->lock); + + return ret ? ret : sprintf(buf, "0x%x\n", ret); +} + +static ssize_t ad2s1210_clear_fault(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + int ret; + + mutex_lock(&st->lock); + gpio_set_value(st->pdata->sample, 0); + /* delay (2 * tck + 20) nano seconds */ + udelay(1); + gpio_set_value(st->pdata->sample, 1); + ret = ad2s1210_config_read(st, AD2S1210_REG_FAULT); + if (ret < 0) + goto error_ret; + gpio_set_value(st->pdata->sample, 0); + gpio_set_value(st->pdata->sample, 1); +error_ret: + mutex_unlock(&st->lock); + + return ret < 0 ? ret : len; +} + +static ssize_t ad2s1210_show_reg(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + struct iio_dev_attr *iattr = to_iio_dev_attr(attr); + int ret; + + mutex_lock(&st->lock); + ret = ad2s1210_config_read(st, iattr->address); + mutex_unlock(&st->lock); + + return ret < 0 ? ret : sprintf(buf, "%d\n", ret); +} + +static ssize_t ad2s1210_store_reg(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct ad2s1210_state *st = iio_priv(dev_get_drvdata(dev)); + unsigned long data; + int ret; + struct iio_dev_attr *iattr = to_iio_dev_attr(attr); + + ret = strict_strtoul(buf, 10, &data); + if (ret) + return -EINVAL; + mutex_lock(&st->lock); + ret = ad2s1210_config_write(st, iattr->address); + if (ret < 0) + goto error_ret; + ret = ad2s1210_config_write(st, data & AD2S1210_MSB_IS_LOW); +error_ret: + mutex_unlock(&st->lock); + return ret < 0 ? ret : len; +} + +static int ad2s1210_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + struct ad2s1210_state *st = iio_priv(indio_dev); + bool negative; + int ret = 0; + u16 pos; + s16 vel; + + mutex_lock(&st->lock); + gpio_set_value(st->pdata->sample, 0); + /* delay (6 * tck + 20) nano seconds */ + udelay(1); + + switch (chan->type) { + case IIO_ANGL: + ad2s1210_set_mode(MOD_POS, st); + break; + case IIO_ANGL_VEL: + ad2s1210_set_mode(MOD_VEL, st); + break; + default: + ret = -EINVAL; + break; + } + if (ret < 0) + goto error_ret; + ret = spi_read(st->sdev, st->rx, 2); + if (ret < 0) + goto error_ret; + + switch (chan->type) { + case IIO_ANGL: + pos = be16_to_cpup((u16 *)st->rx); + if (st->hysteresis) + pos >>= 16 - st->resolution; + *val = pos; + ret = IIO_VAL_INT; + break; + case IIO_ANGL_VEL: + negative = st->rx[0] & 0x80; + vel = be16_to_cpup((s16 *)st->rx); + vel >>= 16 - st->resolution; + if (vel & 0x8000) { + negative = (0xffff >> st->resolution) << st->resolution; + vel |= negative; + } + *val = vel; + ret = IIO_VAL_INT; + break; + default: + mutex_unlock(&st->lock); + return -EINVAL; + } + +error_ret: + gpio_set_value(st->pdata->sample, 1); + /* delay (2 * tck + 20) nano seconds */ + udelay(1); + mutex_unlock(&st->lock); + return ret; +} + +static IIO_DEVICE_ATTR(reset, S_IWUSR, + NULL, ad2s1210_store_softreset, 0); +static IIO_DEVICE_ATTR(fclkin, S_IRUGO | S_IWUSR, + ad2s1210_show_fclkin, ad2s1210_store_fclkin, 0); +static IIO_DEVICE_ATTR(fexcit, S_IRUGO | S_IWUSR, + ad2s1210_show_fexcit, ad2s1210_store_fexcit, 0); +static IIO_DEVICE_ATTR(control, S_IRUGO | S_IWUSR, + ad2s1210_show_control, ad2s1210_store_control, 0); +static IIO_DEVICE_ATTR(bits, S_IRUGO | S_IWUSR, + ad2s1210_show_resolution, ad2s1210_store_resolution, 0); +static IIO_DEVICE_ATTR(fault, S_IRUGO | S_IWUSR, + ad2s1210_show_fault, ad2s1210_clear_fault, 0); + +static IIO_DEVICE_ATTR(los_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_LOS_THRD); +static IIO_DEVICE_ATTR(dos_ovr_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_DOS_OVR_THRD); +static IIO_DEVICE_ATTR(dos_mis_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_DOS_MIS_THRD); +static IIO_DEVICE_ATTR(dos_rst_max_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_DOS_RST_MAX_THRD); +static IIO_DEVICE_ATTR(dos_rst_min_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_DOS_RST_MIN_THRD); +static IIO_DEVICE_ATTR(lot_high_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_LOT_HIGH_THRD); +static IIO_DEVICE_ATTR(lot_low_thrd, S_IRUGO | S_IWUSR, + ad2s1210_show_reg, ad2s1210_store_reg, + AD2S1210_REG_LOT_LOW_THRD); + + +static struct iio_chan_spec ad2s1210_channels[] = { + { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, + }, { + .type = IIO_ANGL_VEL, + .indexed = 1, + .channel = 0, + } +}; + +static struct attribute *ad2s1210_attributes[] = { + &iio_dev_attr_reset.dev_attr.attr, + &iio_dev_attr_fclkin.dev_attr.attr, + &iio_dev_attr_fexcit.dev_attr.attr, + &iio_dev_attr_control.dev_attr.attr, + &iio_dev_attr_bits.dev_attr.attr, + &iio_dev_attr_fault.dev_attr.attr, + &iio_dev_attr_los_thrd.dev_attr.attr, + &iio_dev_attr_dos_ovr_thrd.dev_attr.attr, + &iio_dev_attr_dos_mis_thrd.dev_attr.attr, + &iio_dev_attr_dos_rst_max_thrd.dev_attr.attr, + &iio_dev_attr_dos_rst_min_thrd.dev_attr.attr, + &iio_dev_attr_lot_high_thrd.dev_attr.attr, + &iio_dev_attr_lot_low_thrd.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ad2s1210_attribute_group = { + .attrs = ad2s1210_attributes, +}; + +static int __devinit ad2s1210_initial(struct ad2s1210_state *st) +{ + unsigned char data; + int ret; + + mutex_lock(&st->lock); + if (st->pdata->gpioin) + st->resolution = ad2s1210_read_resolution_pin(st); + else + ad2s1210_set_resolution_pin(st); + + ret = ad2s1210_config_write(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + data = AD2S1210_DEF_CONTROL & ~(AD2S1210_SET_RESOLUTION); + data |= (st->resolution - 10) >> 1; + ret = ad2s1210_config_write(st, data); + if (ret < 0) + goto error_ret; + ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL); + if (ret < 0) + goto error_ret; + + if (ret & AD2S1210_MSB_IS_HIGH) { + ret = -EIO; + goto error_ret; + } + + ret = ad2s1210_update_frequency_control_word(st); + if (ret < 0) + goto error_ret; + ret = ad2s1210_soft_reset(st); +error_ret: + mutex_unlock(&st->lock); + return ret; +} + +static const struct iio_info ad2s1210_info = { + .read_raw = &ad2s1210_read_raw, + .attrs = &ad2s1210_attribute_group, + .driver_module = THIS_MODULE, +}; + +static int ad2s1210_setup_gpios(struct ad2s1210_state *st) +{ + unsigned long flags = st->pdata->gpioin ? GPIOF_DIR_IN : GPIOF_DIR_OUT; + struct gpio ad2s1210_gpios[] = { + { st->pdata->sample, GPIOF_DIR_IN, "sample" }, + { st->pdata->a[0], flags, "a0" }, + { st->pdata->a[1], flags, "a1" }, + { st->pdata->res[0], flags, "res0" }, + { st->pdata->res[0], flags, "res1" }, + }; + + return gpio_request_array(ad2s1210_gpios, ARRAY_SIZE(ad2s1210_gpios)); +} + +static void ad2s1210_free_gpios(struct ad2s1210_state *st) +{ + unsigned long flags = st->pdata->gpioin ? GPIOF_DIR_IN : GPIOF_DIR_OUT; + struct gpio ad2s1210_gpios[] = { + { st->pdata->sample, GPIOF_DIR_IN, "sample" }, + { st->pdata->a[0], flags, "a0" }, + { st->pdata->a[1], flags, "a1" }, + { st->pdata->res[0], flags, "res0" }, + { st->pdata->res[0], flags, "res1" }, + }; + + gpio_free_array(ad2s1210_gpios, ARRAY_SIZE(ad2s1210_gpios)); +} + +static int __devinit ad2s1210_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad2s1210_state *st; + int ret; + + if (spi->dev.platform_data == NULL) + return -EINVAL; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + st->pdata = spi->dev.platform_data; + ret = ad2s1210_setup_gpios(st); + if (ret < 0) + goto error_free_dev; + + spi_set_drvdata(spi, indio_dev); + + mutex_init(&st->lock); + st->sdev = spi; + st->hysteresis = true; + st->mode = MOD_CONFIG; + st->resolution = 12; + st->fexcit = AD2S1210_DEF_EXCIT; + + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ad2s1210_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = ad2s1210_channels; + indio_dev->num_channels = ARRAY_SIZE(ad2s1210_channels); + indio_dev->name = spi_get_device_id(spi)->name; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_gpios; + + st->fclkin = spi->max_speed_hz; + spi->mode = SPI_MODE_3; + spi_setup(spi); + ad2s1210_initial(st); + + return 0; + +error_free_gpios: + ad2s1210_free_gpios(st); +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit ad2s1210_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + + iio_device_unregister(indio_dev); + ad2s1210_free_gpios(iio_priv(indio_dev)); + iio_free_device(indio_dev); + + return 0; +} + +static const struct spi_device_id ad2s1210_id[] = { + { "ad2s1210" }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad2s1210_id); + +static struct spi_driver ad2s1210_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ad2s1210_probe, + .remove = __devexit_p(ad2s1210_remove), + .id_table = ad2s1210_id, +}; +module_spi_driver(ad2s1210_driver); + +MODULE_AUTHOR("Graff Yang "); +MODULE_DESCRIPTION("Analog Devices AD2S1210 Resolver to Digital SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/resolver/ad2s1210.h b/drivers/staging/iio/resolver/ad2s1210.h new file mode 100644 index 00000000..aec0bdca --- /dev/null +++ b/drivers/staging/iio/resolver/ad2s1210.h @@ -0,0 +1,17 @@ +/* + * ad2s1210.h plaform data for the ADI Resolver to Digital Converters: + * AD2S1210 + * + * Copyright (c) 2010-2010 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +struct ad2s1210_platform_data { + unsigned sample; + unsigned a[2]; + unsigned res[2]; + bool gpioin; +}; diff --git a/drivers/staging/iio/resolver/ad2s90.c b/drivers/staging/iio/resolver/ad2s90.c new file mode 100644 index 00000000..2a86f582 --- /dev/null +++ b/drivers/staging/iio/resolver/ad2s90.c @@ -0,0 +1,127 @@ +/* + * ad2s90.c simple support for the ADI Resolver to Digital Converters: AD2S90 + * + * Copyright (c) 2010-2010 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../sysfs.h" + +struct ad2s90_state { + struct mutex lock; + struct spi_device *sdev; + u8 rx[2] ____cacheline_aligned; +}; + +static int ad2s90_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long m) +{ + int ret; + struct ad2s90_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + ret = spi_read(st->sdev, st->rx, 2); + if (ret) + goto error_ret; + *val = (((u16)(st->rx[0])) << 4) | ((st->rx[1] & 0xF0) >> 4); + +error_ret: + mutex_unlock(&st->lock); + + return IIO_VAL_INT; +} + +static const struct iio_info ad2s90_info = { + .read_raw = &ad2s90_read_raw, + .driver_module = THIS_MODULE, +}; + +static const struct iio_chan_spec ad2s90_chan = { + .type = IIO_ANGL, + .indexed = 1, + .channel = 0, +}; + +static int __devinit ad2s90_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct ad2s90_state *st; + int ret = 0; + + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + ret = -ENOMEM; + goto error_ret; + } + st = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + mutex_init(&st->lock); + st->sdev = spi; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &ad2s90_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = &ad2s90_chan; + indio_dev->num_channels = 1; + indio_dev->name = spi_get_device_id(spi)->name; + + ret = iio_device_register(indio_dev); + if (ret) + goto error_free_dev; + + /* need 600ns between CS and the first falling edge of SCLK */ + spi->max_speed_hz = 830000; + spi->mode = SPI_MODE_3; + spi_setup(spi); + + return 0; + +error_free_dev: + iio_free_device(indio_dev); +error_ret: + return ret; +} + +static int __devexit ad2s90_remove(struct spi_device *spi) +{ + iio_device_unregister(spi_get_drvdata(spi)); + iio_free_device(spi_get_drvdata(spi)); + + return 0; +} + +static const struct spi_device_id ad2s90_id[] = { + { "ad2s90" }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad2s90_id); + +static struct spi_driver ad2s90_driver = { + .driver = { + .name = "ad2s90", + .owner = THIS_MODULE, + }, + .probe = ad2s90_probe, + .remove = __devexit_p(ad2s90_remove), + .id_table = ad2s90_id, +}; +module_spi_driver(ad2s90_driver); + +MODULE_AUTHOR("Graff Yang "); +MODULE_DESCRIPTION("Analog Devices AD2S90 Resolver to Digital SPI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/ring_hw.h b/drivers/staging/iio/ring_hw.h new file mode 100644 index 00000000..cad8a2ed --- /dev/null +++ b/drivers/staging/iio/ring_hw.h @@ -0,0 +1,22 @@ +/* + * ring_hw.h - common functionality for iio hardware ring buffers + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Copyright (c) 2009 Jonathan Cameron + * + */ + +/** + * struct iio_hw_ring_buffer- hardware ring buffer + * @buf: generic ring buffer elements + * @private: device specific data + */ +struct iio_hw_buffer { + struct iio_buffer buf; + void *private; +}; + +#define iio_to_hw_buf(r) container_of(r, struct iio_hw_buffer, buf) diff --git a/drivers/staging/iio/ring_sw.c b/drivers/staging/iio/ring_sw.c new file mode 100644 index 00000000..b9945ec4 --- /dev/null +++ b/drivers/staging/iio/ring_sw.c @@ -0,0 +1,367 @@ +/* The industrial I/O simple minimally locked ring buffer. + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "ring_sw.h" +#include "trigger.h" + +/** + * struct iio_sw_ring_buffer - software ring buffer + * @buf: generic ring buffer elements + * @data: the ring buffer memory + * @read_p: read pointer (oldest available) + * @write_p: write pointer + * @half_p: half buffer length behind write_p (event generation) + * @update_needed: flag to indicated change in size requested + * + * Note that the first element of all ring buffers must be a + * struct iio_buffer. +**/ +struct iio_sw_ring_buffer { + struct iio_buffer buf; + unsigned char *data; + unsigned char *read_p; + unsigned char *write_p; + /* used to act as a point at which to signal an event */ + unsigned char *half_p; + int update_needed; +}; + +#define iio_to_sw_ring(r) container_of(r, struct iio_sw_ring_buffer, buf) + +static inline int __iio_allocate_sw_ring_buffer(struct iio_sw_ring_buffer *ring, + int bytes_per_datum, int length) +{ + if ((length == 0) || (bytes_per_datum == 0)) + return -EINVAL; + __iio_update_buffer(&ring->buf, bytes_per_datum, length); + ring->data = kmalloc(length*ring->buf.bytes_per_datum, GFP_ATOMIC); + ring->read_p = NULL; + ring->write_p = NULL; + ring->half_p = NULL; + return ring->data ? 0 : -ENOMEM; +} + +static inline void __iio_free_sw_ring_buffer(struct iio_sw_ring_buffer *ring) +{ + kfree(ring->data); +} + +/* Ring buffer related functionality */ +/* Store to ring is typically called in the bh of a data ready interrupt handler + * in the device driver */ +/* Lock always held if their is a chance this may be called */ +/* Only one of these per ring may run concurrently - enforced by drivers */ +static int iio_store_to_sw_ring(struct iio_sw_ring_buffer *ring, + unsigned char *data, s64 timestamp) +{ + int ret = 0; + unsigned char *temp_ptr, *change_test_ptr; + + /* initial store */ + if (unlikely(ring->write_p == NULL)) { + ring->write_p = ring->data; + /* Doesn't actually matter if this is out of the set + * as long as the read pointer is valid before this + * passes it - guaranteed as set later in this function. + */ + ring->half_p = ring->data - ring->buf.length*ring->buf.bytes_per_datum/2; + } + /* Copy data to where ever the current write pointer says */ + memcpy(ring->write_p, data, ring->buf.bytes_per_datum); + barrier(); + /* Update the pointer used to get most recent value. + * Always valid as either points to latest or second latest value. + * Before this runs it is null and read attempts fail with -EAGAIN. + */ + barrier(); + /* temp_ptr used to ensure we never have an invalid pointer + * it may be slightly lagging, but never invalid + */ + temp_ptr = ring->write_p + ring->buf.bytes_per_datum; + /* End of ring, back to the beginning */ + if (temp_ptr == ring->data + ring->buf.length*ring->buf.bytes_per_datum) + temp_ptr = ring->data; + /* Update the write pointer + * always valid as long as this is the only function able to write. + * Care needed with smp systems to ensure more than one ring fill + * is never scheduled. + */ + ring->write_p = temp_ptr; + + if (ring->read_p == NULL) + ring->read_p = ring->data; + /* Buffer full - move the read pointer and create / escalate + * ring event */ + /* Tricky case - if the read pointer moves before we adjust it. + * Handle by not pushing if it has moved - may result in occasional + * unnecessary buffer full events when it wasn't quite true. + */ + else if (ring->write_p == ring->read_p) { + change_test_ptr = ring->read_p; + temp_ptr = change_test_ptr + ring->buf.bytes_per_datum; + if (temp_ptr + == ring->data + ring->buf.length*ring->buf.bytes_per_datum) { + temp_ptr = ring->data; + } + /* We are moving pointer on one because the ring is full. Any + * change to the read pointer will be this or greater. + */ + if (change_test_ptr == ring->read_p) + ring->read_p = temp_ptr; + } + /* investigate if our event barrier has been passed */ + /* There are definite 'issues' with this and chances of + * simultaneous read */ + /* Also need to use loop count to ensure this only happens once */ + ring->half_p += ring->buf.bytes_per_datum; + if (ring->half_p == ring->data + ring->buf.length*ring->buf.bytes_per_datum) + ring->half_p = ring->data; + if (ring->half_p == ring->read_p) { + ring->buf.stufftoread = true; + wake_up_interruptible(&ring->buf.pollq); + } + return ret; +} + +static int iio_read_first_n_sw_rb(struct iio_buffer *r, + size_t n, char __user *buf) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + + u8 *initial_read_p, *initial_write_p, *current_read_p, *end_read_p; + u8 *data; + int ret, max_copied, bytes_to_rip, dead_offset; + size_t data_available, buffer_size; + + /* A userspace program has probably made an error if it tries to + * read something that is not a whole number of bpds. + * Return an error. + */ + if (n % ring->buf.bytes_per_datum) { + ret = -EINVAL; + printk(KERN_INFO "Ring buffer read request not whole number of" + "samples: Request bytes %zd, Current bytes per datum %d\n", + n, ring->buf.bytes_per_datum); + goto error_ret; + } + + buffer_size = ring->buf.bytes_per_datum*ring->buf.length; + + /* Limit size to whole of ring buffer */ + bytes_to_rip = min_t(size_t, buffer_size, n); + + data = kmalloc(bytes_to_rip, GFP_KERNEL); + if (data == NULL) { + ret = -ENOMEM; + goto error_ret; + } + + /* build local copy */ + initial_read_p = ring->read_p; + if (unlikely(initial_read_p == NULL)) { /* No data here as yet */ + ret = 0; + goto error_free_data_cpy; + } + + initial_write_p = ring->write_p; + + /* Need a consistent pair */ + while ((initial_read_p != ring->read_p) + || (initial_write_p != ring->write_p)) { + initial_read_p = ring->read_p; + initial_write_p = ring->write_p; + } + if (initial_write_p == initial_read_p) { + /* No new data available.*/ + ret = 0; + goto error_free_data_cpy; + } + + if (initial_write_p >= initial_read_p) + data_available = initial_write_p - initial_read_p; + else + data_available = buffer_size - (initial_read_p - initial_write_p); + + if (data_available < bytes_to_rip) + bytes_to_rip = data_available; + + if (initial_read_p + bytes_to_rip >= ring->data + buffer_size) { + max_copied = ring->data + buffer_size - initial_read_p; + memcpy(data, initial_read_p, max_copied); + memcpy(data + max_copied, ring->data, bytes_to_rip - max_copied); + end_read_p = ring->data + bytes_to_rip - max_copied; + } else { + memcpy(data, initial_read_p, bytes_to_rip); + end_read_p = initial_read_p + bytes_to_rip; + } + + /* Now to verify which section was cleanly copied - i.e. how far + * read pointer has been pushed */ + current_read_p = ring->read_p; + + if (initial_read_p <= current_read_p) + dead_offset = current_read_p - initial_read_p; + else + dead_offset = buffer_size - (initial_read_p - current_read_p); + + /* possible issue if the initial write has been lapped or indeed + * the point we were reading to has been passed */ + /* No valid data read. + * In this case the read pointer is already correct having been + * pushed further than we would look. */ + if (bytes_to_rip - dead_offset < 0) { + ret = 0; + goto error_free_data_cpy; + } + + /* setup the next read position */ + /* Beware, this may fail due to concurrency fun and games. + * Possible that sufficient fill commands have run to push the read + * pointer past where we would be after the rip. If this occurs, leave + * it be. + */ + /* Tricky - deal with loops */ + + while (ring->read_p != end_read_p) + ring->read_p = end_read_p; + + ret = bytes_to_rip - dead_offset; + + if (copy_to_user(buf, data + dead_offset, ret)) { + ret = -EFAULT; + goto error_free_data_cpy; + } + + if (bytes_to_rip >= ring->buf.length*ring->buf.bytes_per_datum/2) + ring->buf.stufftoread = 0; + +error_free_data_cpy: + kfree(data); +error_ret: + + return ret; +} + +static int iio_store_to_sw_rb(struct iio_buffer *r, + u8 *data, + s64 timestamp) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + return iio_store_to_sw_ring(ring, data, timestamp); +} + +static int iio_request_update_sw_rb(struct iio_buffer *r) +{ + int ret = 0; + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + + r->stufftoread = false; + if (!ring->update_needed) + goto error_ret; + __iio_free_sw_ring_buffer(ring); + ret = __iio_allocate_sw_ring_buffer(ring, ring->buf.bytes_per_datum, + ring->buf.length); +error_ret: + return ret; +} + +static int iio_get_bytes_per_datum_sw_rb(struct iio_buffer *r) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + return ring->buf.bytes_per_datum; +} + +static int iio_mark_update_needed_sw_rb(struct iio_buffer *r) +{ + struct iio_sw_ring_buffer *ring = iio_to_sw_ring(r); + ring->update_needed = true; + return 0; +} + +static int iio_set_bytes_per_datum_sw_rb(struct iio_buffer *r, size_t bpd) +{ + if (r->bytes_per_datum != bpd) { + r->bytes_per_datum = bpd; + iio_mark_update_needed_sw_rb(r); + } + return 0; +} + +static int iio_get_length_sw_rb(struct iio_buffer *r) +{ + return r->length; +} + +static int iio_set_length_sw_rb(struct iio_buffer *r, int length) +{ + if (r->length != length) { + r->length = length; + iio_mark_update_needed_sw_rb(r); + } + return 0; +} + +static IIO_BUFFER_ENABLE_ATTR; +static IIO_BUFFER_LENGTH_ATTR; + +/* Standard set of ring buffer attributes */ +static struct attribute *iio_ring_attributes[] = { + &dev_attr_length.attr, + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group iio_ring_attribute_group = { + .attrs = iio_ring_attributes, + .name = "buffer", +}; + +static const struct iio_buffer_access_funcs ring_sw_access_funcs = { + .store_to = &iio_store_to_sw_rb, + .read_first_n = &iio_read_first_n_sw_rb, + .request_update = &iio_request_update_sw_rb, + .get_bytes_per_datum = &iio_get_bytes_per_datum_sw_rb, + .set_bytes_per_datum = &iio_set_bytes_per_datum_sw_rb, + .get_length = &iio_get_length_sw_rb, + .set_length = &iio_set_length_sw_rb, +}; + +struct iio_buffer *iio_sw_rb_allocate(struct iio_dev *indio_dev) +{ + struct iio_buffer *buf; + struct iio_sw_ring_buffer *ring; + + ring = kzalloc(sizeof *ring, GFP_KERNEL); + if (!ring) + return NULL; + ring->update_needed = true; + buf = &ring->buf; + iio_buffer_init(buf); + buf->attrs = &iio_ring_attribute_group; + buf->access = &ring_sw_access_funcs; + + return buf; +} +EXPORT_SYMBOL(iio_sw_rb_allocate); + +void iio_sw_rb_free(struct iio_buffer *r) +{ + kfree(iio_to_sw_ring(r)); +} +EXPORT_SYMBOL(iio_sw_rb_free); + +MODULE_DESCRIPTION("Industrialio I/O software ring buffer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/iio/ring_sw.h b/drivers/staging/iio/ring_sw.h new file mode 100644 index 00000000..7556e212 --- /dev/null +++ b/drivers/staging/iio/ring_sw.h @@ -0,0 +1,30 @@ +/* The industrial I/O simple minimally locked ring buffer. + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This code is deliberately kept separate from the main industrialio I/O core + * as it is intended that in the future a number of different software ring + * buffer implementations will exist with different characteristics to suit + * different applications. + * + * This particular one was designed for a data capture application where it was + * particularly important that no userspace reads would interrupt the capture + * process. To this end the ring is not locked during a read. + * + * Comments on this buffer design welcomed. It's far from efficient and some of + * my understanding of the effects of scheduling on this are somewhat limited. + * Frankly, to my mind, this is the current weak point in the industrial I/O + * patch set. + */ + +#ifndef _IIO_RING_SW_H_ +#define _IIO_RING_SW_H_ +#include "buffer.h" + +struct iio_buffer *iio_sw_rb_allocate(struct iio_dev *indio_dev); +void iio_sw_rb_free(struct iio_buffer *ring); +#endif /* _IIO_RING_SW_H_ */ diff --git a/drivers/staging/iio/sysfs.h b/drivers/staging/iio/sysfs.h new file mode 100644 index 00000000..bfedb73b --- /dev/null +++ b/drivers/staging/iio/sysfs.h @@ -0,0 +1,117 @@ +/* The industrial I/O core + * + *Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * General attributes + */ + +#ifndef _INDUSTRIAL_IO_SYSFS_H_ +#define _INDUSTRIAL_IO_SYSFS_H_ + +struct iio_chan_spec; + +/** + * struct iio_dev_attr - iio specific device attribute + * @dev_attr: underlying device attribute + * @address: associated register address + * @l: list head for maintaining list of dynamically created attrs. + */ +struct iio_dev_attr { + struct device_attribute dev_attr; + u64 address; + struct list_head l; + struct iio_chan_spec const *c; +}; + +#define to_iio_dev_attr(_dev_attr) \ + container_of(_dev_attr, struct iio_dev_attr, dev_attr) + +ssize_t iio_read_const_attr(struct device *dev, + struct device_attribute *attr, + char *len); + +/** + * struct iio_const_attr - constant device specific attribute + * often used for things like available modes + * @string: attribute string + * @dev_attr: underlying device attribute + */ +struct iio_const_attr { + const char *string; + struct device_attribute dev_attr; +}; + +#define to_iio_const_attr(_dev_attr) \ + container_of(_dev_attr, struct iio_const_attr, dev_attr) + +/* Some attributes will be hard coded (device dependent) and not require an + address, in these cases pass a negative */ +#define IIO_ATTR(_name, _mode, _show, _store, _addr) \ + { .dev_attr = __ATTR(_name, _mode, _show, _store), \ + .address = _addr } + +#define IIO_DEVICE_ATTR(_name, _mode, _show, _store, _addr) \ + struct iio_dev_attr iio_dev_attr_##_name \ + = IIO_ATTR(_name, _mode, _show, _store, _addr) + +#define IIO_DEVICE_ATTR_NAMED(_vname, _name, _mode, _show, _store, _addr) \ + struct iio_dev_attr iio_dev_attr_##_vname \ + = IIO_ATTR(_name, _mode, _show, _store, _addr) + +#define IIO_CONST_ATTR(_name, _string) \ + struct iio_const_attr iio_const_attr_##_name \ + = { .string = _string, \ + .dev_attr = __ATTR(_name, S_IRUGO, iio_read_const_attr, NULL)} + +#define IIO_CONST_ATTR_NAMED(_vname, _name, _string) \ + struct iio_const_attr iio_const_attr_##_vname \ + = { .string = _string, \ + .dev_attr = __ATTR(_name, S_IRUGO, iio_read_const_attr, NULL)} + +/* Generic attributes of onetype or another */ +/** + * IIO_DEV_ATTR_RESET: resets the device + **/ +#define IIO_DEV_ATTR_RESET(_store) \ + IIO_DEVICE_ATTR(reset, S_IWUSR, NULL, _store, 0) + +/** + * IIO_DEV_ATTR_SAMP_FREQ - sets any internal clock frequency + * @_mode: sysfs file mode/permissions + * @_show: output method for the attribute + * @_store: input method for the attribute + **/ +#define IIO_DEV_ATTR_SAMP_FREQ(_mode, _show, _store) \ + IIO_DEVICE_ATTR(sampling_frequency, _mode, _show, _store, 0) + +/** + * IIO_DEV_ATTR_SAMP_FREQ_AVAIL - list available sampling frequencies + * @_show: output method for the attribute + * + * May be mode dependent on some devices + **/ +#define IIO_DEV_ATTR_SAMP_FREQ_AVAIL(_show) \ + IIO_DEVICE_ATTR(sampling_frequency_available, S_IRUGO, _show, NULL, 0) +/** + * IIO_CONST_ATTR_AVAIL_SAMP_FREQ - list available sampling frequencies + * @_string: frequency string for the attribute + * + * Constant version + **/ +#define IIO_CONST_ATTR_SAMP_FREQ_AVAIL(_string) \ + IIO_CONST_ATTR(sampling_frequency_available, _string) + +#define IIO_DEV_ATTR_TEMP_RAW(_show) \ + IIO_DEVICE_ATTR(in_temp_raw, S_IRUGO, _show, NULL, 0) + +#define IIO_CONST_ATTR_TEMP_OFFSET(_string) \ + IIO_CONST_ATTR(in_temp_offset, _string) + +#define IIO_CONST_ATTR_TEMP_SCALE(_string) \ + IIO_CONST_ATTR(in_temp_scale, _string) + +#endif /* _INDUSTRIAL_IO_SYSFS_H_ */ diff --git a/drivers/staging/iio/trigger.h b/drivers/staging/iio/trigger.h new file mode 100644 index 00000000..1cfca231 --- /dev/null +++ b/drivers/staging/iio/trigger.h @@ -0,0 +1,119 @@ +/* The industrial I/O core, trigger handling functions + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#include +#include + +#ifndef _IIO_TRIGGER_H_ +#define _IIO_TRIGGER_H_ + +struct iio_subirq { + bool enabled; +}; + +/** + * struct iio_trigger_ops - operations structure for an iio_trigger. + * @owner: used to monitor usage count of the trigger. + * @set_trigger_state: switch on/off the trigger on demand + * @try_reenable: function to reenable the trigger when the + * use count is zero (may be NULL) + * @validate_device: function to validate the device when the + * current trigger gets changed. + * + * This is typically static const within a driver and shared by + * instances of a given device. + **/ +struct iio_trigger_ops { + struct module *owner; + int (*set_trigger_state)(struct iio_trigger *trig, bool state); + int (*try_reenable)(struct iio_trigger *trig); + int (*validate_device)(struct iio_trigger *trig, + struct iio_dev *indio_dev); +}; + + +/** + * struct iio_trigger - industrial I/O trigger device + * + * @id: [INTERN] unique id number + * @name: [DRIVER] unique name + * @dev: [DRIVER] associated device (if relevant) + * @private_data: [DRIVER] device specific data + * @list: [INTERN] used in maintenance of global trigger list + * @alloc_list: [DRIVER] used for driver specific trigger list + * @use_count: use count for the trigger + * @subirq_chip: [INTERN] associate 'virtual' irq chip. + * @subirq_base: [INTERN] base number for irqs provided by trigger. + * @subirqs: [INTERN] information about the 'child' irqs. + * @pool: [INTERN] bitmap of irqs currently in use. + * @pool_lock: [INTERN] protection of the irq pool. + **/ +struct iio_trigger { + const struct iio_trigger_ops *ops; + int id; + const char *name; + struct device dev; + + void *private_data; + struct list_head list; + struct list_head alloc_list; + int use_count; + + struct irq_chip subirq_chip; + int subirq_base; + + struct iio_subirq subirqs[CONFIG_IIO_CONSUMERS_PER_TRIGGER]; + unsigned long pool[BITS_TO_LONGS(CONFIG_IIO_CONSUMERS_PER_TRIGGER)]; + struct mutex pool_lock; +}; + + +static inline struct iio_trigger *to_iio_trigger(struct device *d) +{ + return container_of(d, struct iio_trigger, dev); +}; + +static inline void iio_put_trigger(struct iio_trigger *trig) +{ + module_put(trig->ops->owner); + put_device(&trig->dev); +}; + +static inline void iio_get_trigger(struct iio_trigger *trig) +{ + get_device(&trig->dev); + __module_get(trig->ops->owner); +}; + +/** + * iio_trigger_register() - register a trigger with the IIO core + * @trig_info: trigger to be registered + **/ +int iio_trigger_register(struct iio_trigger *trig_info); + +/** + * iio_trigger_unregister() - unregister a trigger from the core + * @trig_info: trigger to be unregistered + **/ +void iio_trigger_unregister(struct iio_trigger *trig_info); + +/** + * iio_trigger_poll() - called on a trigger occurring + * @trig: trigger which occurred + * + * Typically called in relevant hardware interrupt handler. + **/ +void iio_trigger_poll(struct iio_trigger *trig, s64 time); +void iio_trigger_poll_chained(struct iio_trigger *trig, s64 time); + +irqreturn_t iio_trigger_generic_data_rdy_poll(int irq, void *private); + +__printf(1, 2) struct iio_trigger *iio_allocate_trigger(const char *fmt, ...); +void iio_free_trigger(struct iio_trigger *trig); + +#endif /* _IIO_TRIGGER_H_ */ diff --git a/drivers/staging/iio/trigger/Kconfig b/drivers/staging/iio/trigger/Kconfig new file mode 100644 index 00000000..b8abf547 --- /dev/null +++ b/drivers/staging/iio/trigger/Kconfig @@ -0,0 +1,42 @@ +# +# Industrial I/O standalone triggers +# +comment "Triggers - standalone" + +if IIO_TRIGGER + +config IIO_PERIODIC_RTC_TRIGGER + tristate "Periodic RTC triggers" + depends on RTC_CLASS + help + Provides support for using periodic capable real time + clocks as IIO triggers. + +config IIO_GPIO_TRIGGER + tristate "GPIO trigger" + depends on GENERIC_GPIO + help + Provides support for using GPIO pins as IIO triggers. + +config IIO_SYSFS_TRIGGER + tristate "SYSFS trigger" + depends on SYSFS + help + Provides support for using SYSFS entry as IIO triggers. + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called iio-trig-sysfs. + +config IIO_BFIN_TMR_TRIGGER + tristate "Blackfin TIMER trigger" + depends on BLACKFIN + select BFIN_GPTIMERS + help + Provides support for using a Blackfin timer as IIO triggers. + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called iio-trig-bfin-timer. + +endif # IIO_TRIGGER diff --git a/drivers/staging/iio/trigger/Makefile b/drivers/staging/iio/trigger/Makefile new file mode 100644 index 00000000..b088b57d --- /dev/null +++ b/drivers/staging/iio/trigger/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for triggers not associated with iio-devices +# + +obj-$(CONFIG_IIO_PERIODIC_RTC_TRIGGER) += iio-trig-periodic-rtc.o +obj-$(CONFIG_IIO_GPIO_TRIGGER) += iio-trig-gpio.o +obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o +obj-$(CONFIG_IIO_BFIN_TMR_TRIGGER) += iio-trig-bfin-timer.o diff --git a/drivers/staging/iio/trigger/iio-trig-bfin-timer.c b/drivers/staging/iio/trigger/iio-trig-bfin-timer.c new file mode 100644 index 00000000..665653d7 --- /dev/null +++ b/drivers/staging/iio/trigger/iio-trig-bfin-timer.c @@ -0,0 +1,240 @@ +/* + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../iio.h" +#include "../trigger.h" + +struct bfin_timer { + unsigned short id, bit; + unsigned long irqbit; + int irq; +}; + +/* + * this covers all hardware timer configurations on + * all Blackfin derivatives out there today + */ + +static struct bfin_timer iio_bfin_timer_code[MAX_BLACKFIN_GPTIMERS] = { + {TIMER0_id, TIMER0bit, TIMER_STATUS_TIMIL0, IRQ_TIMER0}, + {TIMER1_id, TIMER1bit, TIMER_STATUS_TIMIL1, IRQ_TIMER1}, + {TIMER2_id, TIMER2bit, TIMER_STATUS_TIMIL2, IRQ_TIMER2}, +#if (MAX_BLACKFIN_GPTIMERS > 3) + {TIMER3_id, TIMER3bit, TIMER_STATUS_TIMIL3, IRQ_TIMER3}, + {TIMER4_id, TIMER4bit, TIMER_STATUS_TIMIL4, IRQ_TIMER4}, + {TIMER5_id, TIMER5bit, TIMER_STATUS_TIMIL5, IRQ_TIMER5}, + {TIMER6_id, TIMER6bit, TIMER_STATUS_TIMIL6, IRQ_TIMER6}, + {TIMER7_id, TIMER7bit, TIMER_STATUS_TIMIL7, IRQ_TIMER7}, +#endif +#if (MAX_BLACKFIN_GPTIMERS > 8) + {TIMER8_id, TIMER8bit, TIMER_STATUS_TIMIL8, IRQ_TIMER8}, + {TIMER9_id, TIMER9bit, TIMER_STATUS_TIMIL9, IRQ_TIMER9}, + {TIMER10_id, TIMER10bit, TIMER_STATUS_TIMIL10, IRQ_TIMER10}, +#if (MAX_BLACKFIN_GPTIMERS > 11) + {TIMER11_id, TIMER11bit, TIMER_STATUS_TIMIL11, IRQ_TIMER11}, +#endif +#endif +}; + +struct bfin_tmr_state { + struct iio_trigger *trig; + struct bfin_timer *t; + unsigned timer_num; + int irq; +}; + +static ssize_t iio_bfin_tmr_frequency_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_trigger *trig = dev_get_drvdata(dev); + struct bfin_tmr_state *st = trig->private_data; + long val; + int ret; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + goto error_ret; + + if (val > 100000) { + ret = -EINVAL; + goto error_ret; + } + + disable_gptimers(st->t->bit); + + if (!val) + goto error_ret; + + val = get_sclk() / val; + if (val <= 4) { + ret = -EINVAL; + goto error_ret; + } + + set_gptimer_period(st->t->id, val); + set_gptimer_pwidth(st->t->id, 1); + enable_gptimers(st->t->bit); + +error_ret: + return ret ? ret : count; +} + +static ssize_t iio_bfin_tmr_frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = dev_get_drvdata(dev); + struct bfin_tmr_state *st = trig->private_data; + + return sprintf(buf, "%lu\n", + get_sclk() / get_gptimer_period(st->t->id)); +} + +static DEVICE_ATTR(frequency, S_IRUGO | S_IWUSR, iio_bfin_tmr_frequency_show, + iio_bfin_tmr_frequency_store); + +static struct attribute *iio_bfin_tmr_trigger_attrs[] = { + &dev_attr_frequency.attr, + NULL, +}; + +static const struct attribute_group iio_bfin_tmr_trigger_attr_group = { + .attrs = iio_bfin_tmr_trigger_attrs, +}; + +static const struct attribute_group *iio_bfin_tmr_trigger_attr_groups[] = { + &iio_bfin_tmr_trigger_attr_group, + NULL +}; + + +static irqreturn_t iio_bfin_tmr_trigger_isr(int irq, void *devid) +{ + struct bfin_tmr_state *st = devid; + + clear_gptimer_intr(st->t->id); + iio_trigger_poll(st->trig, 0); + + return IRQ_HANDLED; +} + +static int iio_bfin_tmr_get_number(int irq) +{ + int i; + + for (i = 0; i < MAX_BLACKFIN_GPTIMERS; i++) + if (iio_bfin_timer_code[i].irq == irq) + return i; + + return -ENODEV; +} + +static const struct iio_trigger_ops iio_bfin_tmr_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int __devinit iio_bfin_tmr_trigger_probe(struct platform_device *pdev) +{ + struct bfin_tmr_state *st; + int ret; + + st = kzalloc(sizeof(*st), GFP_KERNEL); + if (st == NULL) { + ret = -ENOMEM; + goto out; + } + + st->irq = platform_get_irq(pdev, 0); + if (!st->irq) { + dev_err(&pdev->dev, "No IRQs specified"); + ret = -ENODEV; + goto out1; + } + + ret = iio_bfin_tmr_get_number(st->irq); + if (ret < 0) + goto out1; + + st->timer_num = ret; + st->t = &iio_bfin_timer_code[st->timer_num]; + + st->trig = iio_allocate_trigger("bfintmr%d", st->timer_num); + if (!st->trig) { + ret = -ENOMEM; + goto out1; + } + + st->trig->private_data = st; + st->trig->ops = &iio_bfin_tmr_trigger_ops; + st->trig->dev.groups = iio_bfin_tmr_trigger_attr_groups; + ret = iio_trigger_register(st->trig); + if (ret) + goto out2; + + ret = request_irq(st->irq, iio_bfin_tmr_trigger_isr, + 0, st->trig->name, st); + if (ret) { + dev_err(&pdev->dev, + "request IRQ-%d failed", st->irq); + goto out4; + } + + set_gptimer_config(st->t->id, OUT_DIS | PWM_OUT | PERIOD_CNT | IRQ_ENA); + + dev_info(&pdev->dev, "iio trigger Blackfin TMR%d, IRQ-%d", + st->timer_num, st->irq); + platform_set_drvdata(pdev, st); + + return 0; +out4: + iio_trigger_unregister(st->trig); +out2: + iio_put_trigger(st->trig); +out1: + kfree(st); +out: + return ret; +} + +static int __devexit iio_bfin_tmr_trigger_remove(struct platform_device *pdev) +{ + struct bfin_tmr_state *st = platform_get_drvdata(pdev); + + disable_gptimers(st->t->bit); + free_irq(st->irq, st); + iio_trigger_unregister(st->trig); + iio_put_trigger(st->trig); + kfree(st); + + return 0; +} + +static struct platform_driver iio_bfin_tmr_trigger_driver = { + .driver = { + .name = "iio_bfin_tmr_trigger", + .owner = THIS_MODULE, + }, + .probe = iio_bfin_tmr_trigger_probe, + .remove = __devexit_p(iio_bfin_tmr_trigger_remove), +}; + +module_platform_driver(iio_bfin_tmr_trigger_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Blackfin system timer based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-bfin-timer"); diff --git a/drivers/staging/iio/trigger/iio-trig-gpio.c b/drivers/staging/iio/trigger/iio-trig-gpio.c new file mode 100644 index 00000000..a3465947 --- /dev/null +++ b/drivers/staging/iio/trigger/iio-trig-gpio.c @@ -0,0 +1,167 @@ +/* + * Industrial I/O - gpio based trigger support + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Currently this is more of a functioning proof of concept than a full + * fledged trigger driver. + * + * TODO: + * + * Add board config elements to allow specification of startup settings. + */ + +#include +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" + +static LIST_HEAD(iio_gpio_trigger_list); +static DEFINE_MUTEX(iio_gpio_trigger_list_lock); + +struct iio_gpio_trigger_info { + struct mutex in_use; + unsigned int irq; +}; +/* + * Need to reference count these triggers and only enable gpio interrupts + * as appropriate. + */ + +/* So what functionality do we want in here?... */ +/* set high / low as interrupt type? */ + +static irqreturn_t iio_gpio_trigger_poll(int irq, void *private) +{ + /* Timestamp not currently provided */ + iio_trigger_poll(private, 0); + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops iio_gpio_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int iio_gpio_trigger_probe(struct platform_device *pdev) +{ + struct iio_gpio_trigger_info *trig_info; + struct iio_trigger *trig, *trig2; + unsigned long irqflags; + struct resource *irq_res; + int irq, ret = 0, irq_res_cnt = 0; + + do { + irq_res = platform_get_resource(pdev, + IORESOURCE_IRQ, irq_res_cnt); + + if (irq_res == NULL) { + if (irq_res_cnt == 0) + dev_err(&pdev->dev, "No GPIO IRQs specified"); + break; + } + irqflags = (irq_res->flags & IRQF_TRIGGER_MASK) | IRQF_SHARED; + + for (irq = irq_res->start; irq <= irq_res->end; irq++) { + + trig = iio_allocate_trigger("irqtrig%d", irq); + if (!trig) { + ret = -ENOMEM; + goto error_free_completed_registrations; + } + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) { + ret = -ENOMEM; + goto error_put_trigger; + } + trig->private_data = trig_info; + trig_info->irq = irq; + trig->ops = &iio_gpio_trigger_ops; + ret = request_irq(irq, iio_gpio_trigger_poll, + irqflags, trig->name, trig); + if (ret) { + dev_err(&pdev->dev, + "request IRQ-%d failed", irq); + goto error_free_trig_info; + } + + ret = iio_trigger_register(trig); + if (ret) + goto error_release_irq; + + list_add_tail(&trig->alloc_list, + &iio_gpio_trigger_list); + } + + irq_res_cnt++; + } while (irq_res != NULL); + + + return 0; + +/* First clean up the partly allocated trigger */ +error_release_irq: + free_irq(irq, trig); +error_free_trig_info: + kfree(trig_info); +error_put_trigger: + iio_put_trigger(trig); +error_free_completed_registrations: + /* The rest should have been added to the iio_gpio_trigger_list */ + list_for_each_entry_safe(trig, + trig2, + &iio_gpio_trigger_list, + alloc_list) { + trig_info = trig->private_data; + free_irq(gpio_to_irq(trig_info->irq), trig); + kfree(trig_info); + iio_trigger_unregister(trig); + } + + return ret; +} + +static int iio_gpio_trigger_remove(struct platform_device *pdev) +{ + struct iio_trigger *trig, *trig2; + struct iio_gpio_trigger_info *trig_info; + + mutex_lock(&iio_gpio_trigger_list_lock); + list_for_each_entry_safe(trig, + trig2, + &iio_gpio_trigger_list, + alloc_list) { + trig_info = trig->private_data; + iio_trigger_unregister(trig); + free_irq(trig_info->irq, trig); + kfree(trig_info); + iio_put_trigger(trig); + } + mutex_unlock(&iio_gpio_trigger_list_lock); + + return 0; +} + +static struct platform_driver iio_gpio_trigger_driver = { + .probe = iio_gpio_trigger_probe, + .remove = iio_gpio_trigger_remove, + .driver = { + .name = "iio_gpio_trigger", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(iio_gpio_trigger_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Example gpio trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c b/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c new file mode 100644 index 00000000..a80cf67b --- /dev/null +++ b/drivers/staging/iio/trigger/iio-trig-periodic-rtc.c @@ -0,0 +1,202 @@ +/* The industrial I/O periodic RTC trigger driver + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This is a heavily rewritten version of the periodic timer system in + * earlier version of industrialio. It supplies the same functionality + * but via a trigger rather than a specific periodic timer system. + */ + +#include +#include +#include +#include +#include +#include "../iio.h" +#include "../trigger.h" + +static LIST_HEAD(iio_prtc_trigger_list); +static DEFINE_MUTEX(iio_prtc_trigger_list_lock); + +struct iio_prtc_trigger_info { + struct rtc_device *rtc; + int frequency; + struct rtc_task task; +}; + +static int iio_trig_periodic_rtc_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_prtc_trigger_info *trig_info = trig->private_data; + if (trig_info->frequency == 0) + return -EINVAL; + printk(KERN_INFO "trigger frequency is %d\n", trig_info->frequency); + return rtc_irq_set_state(trig_info->rtc, &trig_info->task, state); +} + +static ssize_t iio_trig_periodic_read_freq(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = dev_get_drvdata(dev); + struct iio_prtc_trigger_info *trig_info = trig->private_data; + return sprintf(buf, "%u\n", trig_info->frequency); +} + +static ssize_t iio_trig_periodic_write_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_trigger *trig = dev_get_drvdata(dev); + struct iio_prtc_trigger_info *trig_info = trig->private_data; + unsigned long val; + int ret; + + ret = strict_strtoul(buf, 10, &val); + if (ret) + goto error_ret; + + ret = rtc_irq_set_freq(trig_info->rtc, &trig_info->task, val); + if (ret) + goto error_ret; + + trig_info->frequency = val; + + return len; + +error_ret: + return ret; +} + +static DEVICE_ATTR(frequency, S_IRUGO | S_IWUSR, + iio_trig_periodic_read_freq, + iio_trig_periodic_write_freq); + +static struct attribute *iio_trig_prtc_attrs[] = { + &dev_attr_frequency.attr, + NULL, +}; + +static const struct attribute_group iio_trig_prtc_attr_group = { + .attrs = iio_trig_prtc_attrs, +}; + +static const struct attribute_group *iio_trig_prtc_attr_groups[] = { + &iio_trig_prtc_attr_group, + NULL +}; + +static void iio_prtc_trigger_poll(void *private_data) +{ + /* Timestamp is not provided currently */ + iio_trigger_poll(private_data, 0); +} + +static const struct iio_trigger_ops iio_prtc_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &iio_trig_periodic_rtc_set_state, +}; + +static int iio_trig_periodic_rtc_probe(struct platform_device *dev) +{ + char **pdata = dev->dev.platform_data; + struct iio_prtc_trigger_info *trig_info; + struct iio_trigger *trig, *trig2; + + int i, ret; + + for (i = 0;; i++) { + if (pdata[i] == NULL) + break; + trig = iio_allocate_trigger("periodic%s", pdata[i]); + if (!trig) { + ret = -ENOMEM; + goto error_free_completed_registrations; + } + list_add(&trig->alloc_list, &iio_prtc_trigger_list); + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) { + ret = -ENOMEM; + goto error_put_trigger_and_remove_from_list; + } + trig->private_data = trig_info; + trig->ops = &iio_prtc_trigger_ops; + /* RTC access */ + trig_info->rtc + = rtc_class_open(pdata[i]); + if (trig_info->rtc == NULL) { + ret = -EINVAL; + goto error_free_trig_info; + } + trig_info->task.func = iio_prtc_trigger_poll; + trig_info->task.private_data = trig; + ret = rtc_irq_register(trig_info->rtc, &trig_info->task); + if (ret) + goto error_close_rtc; + trig->dev.groups = iio_trig_prtc_attr_groups; + ret = iio_trigger_register(trig); + if (ret) + goto error_unregister_rtc_irq; + } + return 0; +error_unregister_rtc_irq: + rtc_irq_unregister(trig_info->rtc, &trig_info->task); +error_close_rtc: + rtc_class_close(trig_info->rtc); +error_free_trig_info: + kfree(trig_info); +error_put_trigger_and_remove_from_list: + list_del(&trig->alloc_list); + iio_put_trigger(trig); +error_free_completed_registrations: + list_for_each_entry_safe(trig, + trig2, + &iio_prtc_trigger_list, + alloc_list) { + trig_info = trig->private_data; + rtc_irq_unregister(trig_info->rtc, &trig_info->task); + rtc_class_close(trig_info->rtc); + kfree(trig_info); + iio_trigger_unregister(trig); + } + return ret; +} + +static int iio_trig_periodic_rtc_remove(struct platform_device *dev) +{ + struct iio_trigger *trig, *trig2; + struct iio_prtc_trigger_info *trig_info; + mutex_lock(&iio_prtc_trigger_list_lock); + list_for_each_entry_safe(trig, + trig2, + &iio_prtc_trigger_list, + alloc_list) { + trig_info = trig->private_data; + rtc_irq_unregister(trig_info->rtc, &trig_info->task); + rtc_class_close(trig_info->rtc); + kfree(trig_info); + iio_trigger_unregister(trig); + } + mutex_unlock(&iio_prtc_trigger_list_lock); + return 0; +} + +static struct platform_driver iio_trig_periodic_rtc_driver = { + .probe = iio_trig_periodic_rtc_probe, + .remove = iio_trig_periodic_rtc_remove, + .driver = { + .name = "iio_prtc_trigger", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(iio_trig_periodic_rtc_driver); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("Periodic realtime clock trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/iio/trigger/iio-trig-sysfs.c b/drivers/staging/iio/trigger/iio-trig-sysfs.c new file mode 100644 index 00000000..174dc657 --- /dev/null +++ b/drivers/staging/iio/trigger/iio-trig-sysfs.c @@ -0,0 +1,212 @@ +/* + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + */ + +#include +#include +#include +#include +#include + +#include "../iio.h" +#include "../trigger.h" + +struct iio_sysfs_trig { + struct iio_trigger *trig; + int id; + struct list_head l; +}; + +static LIST_HEAD(iio_sysfs_trig_list); +static DEFINE_MUTEX(iio_syfs_trig_list_mut); + +static int iio_sysfs_trigger_probe(int id); +static ssize_t iio_sysfs_trig_add(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = strict_strtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_probe(input); + if (ret) + return ret; + return len; +} +static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add); + +static int iio_sysfs_trigger_remove(int id); +static ssize_t iio_sysfs_trig_remove(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int ret; + unsigned long input; + + ret = strict_strtoul(buf, 10, &input); + if (ret) + return ret; + ret = iio_sysfs_trigger_remove(input); + if (ret) + return ret; + return len; +} + +static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove); + +static struct attribute *iio_sysfs_trig_attrs[] = { + &dev_attr_add_trigger.attr, + &dev_attr_remove_trigger.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trig_group = { + .attrs = iio_sysfs_trig_attrs, +}; + +static const struct attribute_group *iio_sysfs_trig_groups[] = { + &iio_sysfs_trig_group, + NULL +}; + + +/* Nothing to actually do upon release */ +static void iio_trigger_sysfs_release(struct device *dev) +{ +} + +static struct device iio_sysfs_trig_dev = { + .bus = &iio_bus_type, + .groups = iio_sysfs_trig_groups, + .release = &iio_trigger_sysfs_release, +}; + +static ssize_t iio_sysfs_trigger_poll(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_trigger *trig = dev_get_drvdata(dev); + iio_trigger_poll_chained(trig, 0); + + return count; +} + +static DEVICE_ATTR(trigger_now, S_IWUSR, NULL, iio_sysfs_trigger_poll); + +static struct attribute *iio_sysfs_trigger_attrs[] = { + &dev_attr_trigger_now.attr, + NULL, +}; + +static const struct attribute_group iio_sysfs_trigger_attr_group = { + .attrs = iio_sysfs_trigger_attrs, +}; + +static const struct attribute_group *iio_sysfs_trigger_attr_groups[] = { + &iio_sysfs_trigger_attr_group, + NULL +}; + +static const struct iio_trigger_ops iio_sysfs_trigger_ops = { + .owner = THIS_MODULE, +}; + +static int iio_sysfs_trigger_probe(int id) +{ + struct iio_sysfs_trig *t; + int ret; + bool foundit = false; + mutex_lock(&iio_syfs_trig_list_mut); + list_for_each_entry(t, &iio_sysfs_trig_list, l) + if (id == t->id) { + foundit = true; + break; + } + if (foundit) { + ret = -EINVAL; + goto out1; + } + t = kmalloc(sizeof(*t), GFP_KERNEL); + if (t == NULL) { + ret = -ENOMEM; + goto out1; + } + t->id = id; + t->trig = iio_allocate_trigger("sysfstrig%d", id); + if (!t->trig) { + ret = -ENOMEM; + goto free_t; + } + + t->trig->dev.groups = iio_sysfs_trigger_attr_groups; + t->trig->ops = &iio_sysfs_trigger_ops; + t->trig->dev.parent = &iio_sysfs_trig_dev; + + ret = iio_trigger_register(t->trig); + if (ret) + goto out2; + list_add(&t->l, &iio_sysfs_trig_list); + __module_get(THIS_MODULE); + mutex_unlock(&iio_syfs_trig_list_mut); + return 0; + +out2: + iio_put_trigger(t->trig); +free_t: + kfree(t); +out1: + mutex_unlock(&iio_syfs_trig_list_mut); + return ret; +} + +static int iio_sysfs_trigger_remove(int id) +{ + bool foundit = false; + struct iio_sysfs_trig *t; + mutex_lock(&iio_syfs_trig_list_mut); + list_for_each_entry(t, &iio_sysfs_trig_list, l) + if (id == t->id) { + foundit = true; + break; + } + if (!foundit) { + mutex_unlock(&iio_syfs_trig_list_mut); + return -EINVAL; + } + + iio_trigger_unregister(t->trig); + iio_free_trigger(t->trig); + + list_del(&t->l); + kfree(t); + module_put(THIS_MODULE); + mutex_unlock(&iio_syfs_trig_list_mut); + return 0; +} + + +static int __init iio_sysfs_trig_init(void) +{ + device_initialize(&iio_sysfs_trig_dev); + dev_set_name(&iio_sysfs_trig_dev, "iio_sysfs_trigger"); + return device_add(&iio_sysfs_trig_dev); +} +module_init(iio_sysfs_trig_init); + +static void __exit iio_sysfs_trig_exit(void) +{ + device_unregister(&iio_sysfs_trig_dev); +} +module_exit(iio_sysfs_trig_exit); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Sysfs based trigger for the iio subsystem"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:iio-trig-sysfs"); diff --git a/drivers/staging/iio/trigger_consumer.h b/drivers/staging/iio/trigger_consumer.h new file mode 100644 index 00000000..60d64b35 --- /dev/null +++ b/drivers/staging/iio/trigger_consumer.h @@ -0,0 +1,52 @@ +/* The industrial I/O core, trigger consumer functions + * + * Copyright (c) 2008-2011 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/** + * struct iio_poll_func - poll function pair + * + * @indio_dev: data specific to device (passed into poll func) + * @h: the function that is actually run on trigger + * @thread: threaded interrupt part + * @type: the type of interrupt (basically if oneshot) + * @name: name used to identify the trigger consumer. + * @irq: the corresponding irq as allocated from the + * trigger pool + * @timestamp: some devices need a timestamp grabbed as soon + * as possible after the trigger - hence handler + * passes it via here. + **/ +struct iio_poll_func { + struct iio_dev *indio_dev; + irqreturn_t (*h)(int irq, void *p); + irqreturn_t (*thread)(int irq, void *p); + int type; + char *name; + int irq; + s64 timestamp; +}; + + +struct iio_poll_func +*iio_alloc_pollfunc(irqreturn_t (*h)(int irq, void *p), + irqreturn_t (*thread)(int irq, void *p), + int type, + struct iio_dev *indio_dev, + const char *fmt, + ...); +void iio_dealloc_pollfunc(struct iio_poll_func *pf); +irqreturn_t iio_pollfunc_store_time(int irq, void *p); + +void iio_trigger_notify_done(struct iio_trigger *trig); + +/* + * Two functions for common case where all that happens is a pollfunc + * is attached and detached from a trigger + */ +int iio_triggered_buffer_postenable(struct iio_dev *indio_dev); +int iio_triggered_buffer_predisable(struct iio_dev *indio_dev); diff --git a/drivers/staging/iio/types.h b/drivers/staging/iio/types.h new file mode 100644 index 00000000..0c321366 --- /dev/null +++ b/drivers/staging/iio/types.h @@ -0,0 +1,53 @@ +/* industrial I/O data types needed both in and out of kernel + * + * Copyright (c) 2008 Jonathan Cameron + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#ifndef _IIO_TYPES_H_ +#define _IIO_TYPES_H_ + +enum iio_chan_type { + /* real channel types */ + IIO_VOLTAGE, + IIO_CURRENT, + IIO_POWER, + IIO_ACCEL, + IIO_ANGL_VEL, + IIO_MAGN, + IIO_LIGHT, + IIO_INTENSITY, + IIO_PROXIMITY, + IIO_TEMP, + IIO_INCLI, + IIO_ROT, + IIO_ANGL, + IIO_TIMESTAMP, + IIO_CAPACITANCE, +}; + +enum iio_modifier { + IIO_NO_MOD, + IIO_MOD_X, + IIO_MOD_Y, + IIO_MOD_Z, + IIO_MOD_X_AND_Y, + IIO_MOD_X_AND_Z, + IIO_MOD_Y_AND_Z, + IIO_MOD_X_AND_Y_AND_Z, + IIO_MOD_X_OR_Y, + IIO_MOD_X_OR_Z, + IIO_MOD_Y_OR_Z, + IIO_MOD_X_OR_Y_OR_Z, + IIO_MOD_LIGHT_BOTH, + IIO_MOD_LIGHT_IR, +}; + +#define IIO_VAL_INT 1 +#define IIO_VAL_INT_PLUS_MICRO 2 +#define IIO_VAL_INT_PLUS_NANO 3 + +#endif /* _IIO_TYPES_H_ */ diff --git a/drivers/staging/keucr/Kconfig b/drivers/staging/keucr/Kconfig new file mode 100644 index 00000000..ba756bf2 --- /dev/null +++ b/drivers/staging/keucr/Kconfig @@ -0,0 +1,14 @@ +config USB_ENESTORAGE + tristate "USB ENE SM card reader support" + depends on USB && SCSI && m + ---help--- + Say Y here if you wish to control a ENE SM Card reader. + To use SD/MS card, please build driver/usb/storage/ums-eneub6250.ko + + This option depends on 'SCSI' support being enabled, but you + probably also need 'SCSI device support: SCSI disk support' + (BLK_DEV_SD) for most USB storage devices. + + To compile this driver as a module, choose M here: the + module will be called keucr. + diff --git a/drivers/staging/keucr/Makefile b/drivers/staging/keucr/Makefile new file mode 100644 index 00000000..c180bf4f --- /dev/null +++ b/drivers/staging/keucr/Makefile @@ -0,0 +1,13 @@ +ccflags-y := -Idrivers/scsi + +obj-$(CONFIG_USB_ENESTORAGE) += keucr.o + +keucr-y := \ + usb.o \ + scsiglue.o \ + transport.o \ + init.o \ + smscsi.o \ + smilmain.o \ + smilsub.o \ + smilecc.o diff --git a/drivers/staging/keucr/TODO b/drivers/staging/keucr/TODO new file mode 100644 index 00000000..d6da656e --- /dev/null +++ b/drivers/staging/keucr/TODO @@ -0,0 +1,12 @@ +TODO: + - checkpatch.pl clean + - sparse clean + - determine if the driver should not be using a duplicate + version of the usb-storage scsi interface code, but should + be merged into the drivers/usb/storage/ directory and + infrastructure instead. + - review by the USB developer community + - smcommon.h & smilsub.c: use kernel hweight8(), hweight16() + +Please send any patches for this driver to Al Cho and +Greg Kroah-Hartman . diff --git a/drivers/staging/keucr/common.h b/drivers/staging/keucr/common.h new file mode 100644 index 00000000..cf347ccd --- /dev/null +++ b/drivers/staging/keucr/common.h @@ -0,0 +1,15 @@ +#ifndef COMMON_INCD +#define COMMON_INCD + +typedef u8 BOOLEAN; +typedef u8 BYTE; +typedef u8 *PBYTE; +typedef u16 WORD; +typedef u16 *PWORD; +typedef u32 DWORD; +typedef u32 *PDWORD; + +#define BYTE_MASK 0xff + +#endif + diff --git a/drivers/staging/keucr/init.c b/drivers/staging/keucr/init.c new file mode 100644 index 00000000..071bdc23 --- /dev/null +++ b/drivers/staging/keucr/init.c @@ -0,0 +1,373 @@ +#include +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "scsiglue.h" +#include "transport.h" +#include "init.h" + +/* + * ENE_InitMedia(): + */ +int ENE_InitMedia(struct us_data *us) +{ + int result; + BYTE MiscReg03 = 0; + + printk(KERN_INFO "--- Init Media ---\n"); + result = ENE_Read_BYTE(us, REG_CARD_STATUS, &MiscReg03); + if (result != USB_STOR_XFER_GOOD) { + printk(KERN_ERR "Read register fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + printk(KERN_INFO "MiscReg03 = %x\n", MiscReg03); + + if (MiscReg03 & 0x02) { + if (!us->SM_Status.Ready && !us->MS_Status.Ready) { + result = ENE_SMInit(us); + if (result != USB_STOR_XFER_GOOD) { + return USB_STOR_TRANSPORT_ERROR; + } + } + + } + return result; +} + +/* + * ENE_Read_BYTE() : + */ +int ENE_Read_BYTE(struct us_data *us, WORD index, void *buf) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x01; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xED; + bcb->CDB[2] = (BYTE)(index>>8); + bcb->CDB[3] = (BYTE)index; + + result = ENE_SendScsiCmd(us, FDIR_READ, buf, 0); + return result; +} + +/* + *ENE_SMInit() + */ +int ENE_SMInit(struct us_data *us) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + BYTE buf[0x200]; + + printk(KERN_INFO "transport --- ENE_SMInit\n"); + + result = ENE_LoadBinCode(us, SM_INIT_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + printk(KERN_INFO "Load SM Init Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x01; + + result = ENE_SendScsiCmd(us, FDIR_READ, &buf, 0); + if (result != USB_STOR_XFER_GOOD) { + printk(KERN_ERR + "Execution SM Init Code Fail !! result = %x\n", result); + return USB_STOR_TRANSPORT_ERROR; + } + + us->SM_Status = *(PSM_STATUS)&buf[0]; + + us->SM_DeviceID = buf[1]; + us->SM_CardID = buf[2]; + + if (us->SM_Status.Insert && us->SM_Status.Ready) { + printk(KERN_INFO "Insert = %x\n", us->SM_Status.Insert); + printk(KERN_INFO "Ready = %x\n", us->SM_Status.Ready); + printk(KERN_INFO "WtP = %x\n", us->SM_Status.WtP); + printk(KERN_INFO "DeviceID = %x\n", us->SM_DeviceID); + printk(KERN_INFO "CardID = %x\n", us->SM_CardID); + MediaChange = 1; + Check_D_MediaFmt(us); + } else { + printk(KERN_ERR "SM Card Not Ready --- %x\n", buf[0]); + return USB_STOR_TRANSPORT_ERROR; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * ENE_LoadBinCode() + */ +int ENE_LoadBinCode(struct us_data *us, BYTE flag) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + /* void *buf; */ + PBYTE buf; + + /* printk(KERN_INFO "transport --- ENE_LoadBinCode\n"); */ + if (us->BIN_FLAG == flag) + return USB_STOR_TRANSPORT_GOOD; + + buf = kmalloc(0x800, GFP_KERNEL); + if (buf == NULL) + return USB_STOR_TRANSPORT_ERROR; + switch (flag) { + /* For SS */ + case SM_INIT_PATTERN: + printk(KERN_INFO "SM_INIT_PATTERN\n"); + memcpy(buf, SM_Init, 0x800); + break; + case SM_RW_PATTERN: + printk(KERN_INFO "SM_RW_PATTERN\n"); + memcpy(buf, SM_Rdwr, 0x800); + break; + } + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x800; + bcb->Flags = 0x00; + bcb->CDB[0] = 0xEF; + + result = ENE_SendScsiCmd(us, FDIR_WRITE, buf, 0); + + kfree(buf); + us->BIN_FLAG = flag; + return result; +} + +/* + * ENE_SendScsiCmd(): + */ +int ENE_SendScsiCmd(struct us_data *us, BYTE fDir, void *buf, int use_sg) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *) us->iobuf; + + int result; + unsigned int transfer_length = bcb->DataTransferLength, + cswlen = 0, partial = 0; + unsigned int residue; + + /* printk(KERN_INFO "transport --- ENE_SendScsiCmd\n"); */ + /* send cmd to out endpoint */ + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + bcb, US_BULK_CB_WRAP_LEN, NULL); + if (result != USB_STOR_XFER_GOOD) { + printk(KERN_ERR "send cmd to out endpoint fail ---\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + if (buf) { + unsigned int pipe = fDir; + + if (fDir == FDIR_READ) + pipe = us->recv_bulk_pipe; + else + pipe = us->send_bulk_pipe; + + /* Bulk */ + if (use_sg) + result = usb_stor_bulk_srb(us, pipe, us->srb); + else + result = usb_stor_bulk_transfer_sg(us, pipe, buf, + transfer_length, 0, &partial); + if (result != USB_STOR_XFER_GOOD) { + printk(KERN_ERR "data transfer fail ---\n"); + return USB_STOR_TRANSPORT_ERROR; + } + } + + /* Get CSW for device status */ + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, bcs, + US_BULK_CS_WRAP_LEN, &cswlen); + + if (result == USB_STOR_XFER_SHORT && cswlen == 0) { + printk(KERN_WARNING "Received 0-length CSW; retrying...\n"); + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + bcs, US_BULK_CS_WRAP_LEN, &cswlen); + } + + if (result == USB_STOR_XFER_STALLED) { + /* get the status again */ + printk(KERN_WARNING "Attempting to get CSW (2nd try)...\n"); + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + bcs, US_BULK_CS_WRAP_LEN, NULL); + } + + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* check bulk status */ + residue = le32_to_cpu(bcs->Residue); + + /* + * try to compute the actual residue, based on how much data + * was really transferred and what the device tells us + */ + if (residue && !(us->fflags & US_FL_IGNORE_RESIDUE)) { + residue = min(residue, transfer_length); + if (us->srb) + scsi_set_resid(us->srb, max(scsi_get_resid(us->srb), + (int) residue)); + } + + if (bcs->Status != US_BULK_STAT_OK) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * ENE_Read_Data() + */ +int ENE_Read_Data(struct us_data *us, void *buf, unsigned int length) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *) us->iobuf; + int result; + + /* printk(KERN_INFO "transport --- ENE_Read_Data\n"); */ + /* set up the command wrapper */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = length; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xED; + bcb->CDB[2] = 0xFF; + bcb->CDB[3] = 0x81; + + /* send cmd to out endpoint */ + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, bcb, + US_BULK_CB_WRAP_LEN, NULL); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* R/W data */ + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, + buf, length, NULL); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* Get CSW for device status */ + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, bcs, + US_BULK_CS_WRAP_LEN, NULL); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + if (bcs->Status != US_BULK_STAT_OK) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * ENE_Write_Data(): + */ +int ENE_Write_Data(struct us_data *us, void *buf, unsigned int length) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *) us->iobuf; + int result; + + /* printk("transport --- ENE_Write_Data\n"); */ + /* set up the command wrapper */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = length; + bcb->Flags = 0x00; + bcb->CDB[0] = 0xEE; + bcb->CDB[2] = 0xFF; + bcb->CDB[3] = 0x81; + + /* send cmd to out endpoint */ + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, bcb, + US_BULK_CB_WRAP_LEN, NULL); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* R/W data */ + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + buf, length, NULL); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* Get CSW for device status */ + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, bcs, + US_BULK_CS_WRAP_LEN, NULL); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + if (bcs->Status != US_BULK_STAT_OK) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* + * usb_stor_print_cmd(): + */ +void usb_stor_print_cmd(struct scsi_cmnd *srb) +{ + PBYTE Cdb = srb->cmnd; + DWORD cmd = Cdb[0]; + DWORD bn = ((Cdb[2] << 24) & 0xff000000) | + ((Cdb[3] << 16) & 0x00ff0000) | + ((Cdb[4] << 8) & 0x0000ff00) | + ((Cdb[5] << 0) & 0x000000ff); + WORD blen = ((Cdb[7] << 8) & 0xff00) | ((Cdb[8] << 0) & 0x00ff); + + switch (cmd) { + case TEST_UNIT_READY: + /* printk(KERN_INFO + "scsi cmd %X --- SCSIOP_TEST_UNIT_READY\n", cmd); */ + break; + case INQUIRY: + printk(KERN_INFO "scsi cmd %X --- SCSIOP_INQUIRY\n", cmd); + break; + case MODE_SENSE: + printk(KERN_INFO "scsi cmd %X --- SCSIOP_MODE_SENSE\n", cmd); + break; + case START_STOP: + printk(KERN_INFO "scsi cmd %X --- SCSIOP_START_STOP\n", cmd); + break; + case READ_CAPACITY: + printk(KERN_INFO "scsi cmd %X --- SCSIOP_READ_CAPACITY\n", cmd); + break; + case READ_10: + /* printk(KERN_INFO + "scsi cmd %X --- SCSIOP_READ,bn = %X, blen = %X\n" + ,cmd, bn, blen); */ + break; + case WRITE_10: + /* printk(KERN_INFO + "scsi cmd %X --- SCSIOP_WRITE, + bn = %X, blen = %X\n" , cmd, bn, blen); */ + break; + case ALLOW_MEDIUM_REMOVAL: + printk(KERN_INFO + "scsi cmd %X --- SCSIOP_ALLOW_MEDIUM_REMOVAL\n", cmd); + break; + default: + printk(KERN_INFO "scsi cmd %X --- Other cmd\n", cmd); + break; + } + bn = 0; + blen = 0; +} + diff --git a/drivers/staging/keucr/init.h b/drivers/staging/keucr/init.h new file mode 100644 index 00000000..c8b2cd60 --- /dev/null +++ b/drivers/staging/keucr/init.h @@ -0,0 +1,523 @@ +#include "common.h" + +extern DWORD MediaChange; +extern int Check_D_MediaFmt(struct us_data *); + + + +static BYTE SM_Init[] = { +0x7B, 0x09, 0x7C, 0xF0, 0x7D, 0x10, 0x7E, 0xE9, +0x7F, 0xCC, 0x12, 0x2F, 0x71, 0x90, 0xE9, 0xCC, +0xE0, 0xB4, 0x07, 0x12, 0x90, 0xFF, 0x09, 0xE0, +0x30, 0xE1, 0x06, 0x90, 0xFF, 0x23, 0x74, 0x80, +0xF0, 0x12, 0x2F, 0x5C, 0xD3, 0x22, 0x78, 0x00, +0x90, 0xFF, 0x83, 0xE0, 0xA2, 0xE1, 0x92, 0x0A, +0x20, 0x0A, 0x03, 0x02, 0xE0, 0xD0, 0x7F, 0x00, +0x12, 0x2F, 0xCB, 0x20, 0x01, 0x05, 0xC2, 0x25, +0x02, 0xE0, 0xEB, 0xC3, 0xE8, 0x94, 0x02, 0x40, +0x03, 0x02, 0xE0, 0xD0, 0xC0, 0x00, 0x90, 0xFE, +0x66, 0x74, 0x90, 0xF0, 0x12, 0xE1, 0x40, 0x90, +0xFF, 0x95, 0xE0, 0xC2, 0xE4, 0xF0, 0x90, 0xFF, +0x97, 0x74, 0x01, 0xF0, 0x7E, 0x01, 0x7F, 0x90, +0x12, 0x2F, 0x74, 0x90, 0xFF, 0x97, 0x74, 0x03, +0xF0, 0x90, 0xFE, 0xC5, 0xE4, 0xF0, 0x74, 0x00, +0x90, 0xFE, 0x6A, 0xF0, 0xA3, 0xF0, 0xA3, 0xF0, +0xA3, 0xF0, 0x7E, 0x23, 0x7F, 0xDC, 0x12, 0x2F, +0x74, 0x12, 0x2F, 0x5C, 0x90, 0xFE, 0x64, 0xE0, +0x54, 0x01, 0x60, 0x04, 0xD2, 0x02, 0x80, 0x02, +0xC2, 0x02, 0x90, 0xFF, 0x95, 0xE0, 0xD2, 0xE4, +0xF0, 0x78, 0x10, 0x79, 0x04, 0x12, 0xE1, 0x71, +0x50, 0x3A, 0x90, 0xE9, 0xC6, 0xE0, 0x90, 0xE9, +0xC3, 0xF0, 0x78, 0x9A, 0x79, 0x04, 0x12, 0xE1, +0x71, 0x50, 0x29, 0x90, 0xE9, 0xC7, 0xE0, 0xB4, +0xB5, 0x22, 0x90, 0xE9, 0xC4, 0xF0, 0xD0, 0x00, +0xD2, 0x00, 0xC2, 0x01, 0xC2, 0x25, 0x80, 0x1B, +0xC2, 0x00, 0xD2, 0x01, 0x74, 0xFF, 0x90, 0xE9, +0xC3, 0xF0, 0xA3, 0xF0, 0x51, 0x01, 0xC2, 0x0A, +0xC2, 0x02, 0x80, 0x07, 0xD0, 0x00, 0x05, 0x00, +0x02, 0xE0, 0x43, 0x90, 0xFF, 0x09, 0xE0, 0x30, +0xE1, 0x06, 0x90, 0xFF, 0x23, 0x74, 0x80, 0xF0, +0x90, 0xFF, 0x09, 0xE0, 0x30, 0xE5, 0xFC, 0xE4, +0xA2, 0x0A, 0x92, 0xE0, 0xA2, 0x00, 0x92, 0xE1, +0xA2, 0x01, 0x92, 0xE2, 0xA2, 0x02, 0x92, 0xE6, +0xA2, 0x25, 0x92, 0xE7, 0x90, 0xF4, 0x00, 0xF0, +0x90, 0xE9, 0xC3, 0xE0, 0x90, 0xF4, 0x01, 0xF0, +0x90, 0xE9, 0xC4, 0xE0, 0x90, 0xF4, 0x02, 0xF0, +0x90, 0xFF, 0x2A, 0x74, 0x02, 0xF0, 0xA3, 0x74, +0x00, 0xF0, 0x75, 0x3C, 0x00, 0x75, 0x3D, 0x00, +0x75, 0x3E, 0x00, 0x75, 0x3F, 0x00, 0xD3, 0x22, +0x90, 0xFE, 0x71, 0xE4, 0xF0, 0x90, 0xFE, 0x72, +0x74, 0x01, 0xF0, 0x90, 0xFE, 0x64, 0x74, 0x0C, +0xF0, 0x90, 0xFE, 0x64, 0x74, 0x00, 0x45, 0x4E, +0xF0, 0x90, 0xFE, 0x64, 0xE0, 0x54, 0x10, 0x60, +0x08, 0x90, 0xFE, 0x72, 0x74, 0x81, 0xF0, 0xD3, +0x22, 0x90, 0xFE, 0x64, 0x74, 0x08, 0xF0, 0xC3, +0x22, 0x90, 0xFE, 0x6F, 0xE9, 0x14, 0xF0, 0x90, +0xFE, 0x70, 0xE0, 0x54, 0xFC, 0xF0, 0x90, 0xFE, +0x68, 0x74, 0x00, 0xF0, 0xB8, 0x9A, 0x2A, 0x74, +0x15, 0x90, 0xFE, 0x64, 0xF0, 0x74, 0x9A, 0x90, +0xFE, 0x60, 0xF0, 0x74, 0x16, 0x90, 0xFE, 0x64, +0xF0, 0x74, 0x00, 0x90, 0xFE, 0x60, 0xF0, 0x30, +0x0A, 0x5D, 0x90, 0xFE, 0x64, 0xE0, 0x20, 0xE7, +0xF6, 0x74, 0x14, 0x90, 0xFE, 0x64, 0xF0, 0x80, +0x20, 0x90, 0xFE, 0x6E, 0xE8, 0x44, 0x01, 0xF0, +0xC2, 0x09, 0x12, 0xE3, 0x26, 0x20, 0x08, 0x0E, +0x12, 0xE3, 0x32, 0x30, 0x3E, 0xF7, 0x90, 0xFE, +0xD8, 0x74, 0x01, 0xF0, 0xD2, 0x09, 0x20, 0x09, +0x2E, 0x7A, 0xE9, 0x7B, 0xC5, 0x7C, 0xFE, 0x7D, +0x60, 0xB8, 0x10, 0x07, 0x90, 0xFE, 0x69, 0xE0, +0x20, 0xE6, 0xFC, 0x8C, 0x83, 0x8D, 0x82, 0xE0, +0x8A, 0x83, 0x8B, 0x82, 0xF0, 0xA3, 0xAA, 0x83, +0xAB, 0x82, 0xD9, 0xE5, 0xB8, 0x9A, 0x06, 0x74, +0x10, 0x90, 0xFE, 0x64, 0xF0, 0xD3, 0x22, 0xC3, +0x22, 0x90, 0xFF, 0x83, 0xE0, 0xA2, 0xE1, 0x92, +0x25, 0x20, 0x25, 0x06, 0xC2, 0x1F, 0xD2, 0x19, +0xC3, 0x22, 0x7F, 0x02, 0x12, 0x2F, 0xCB, 0x20, +0x19, 0x05, 0x30, 0x1F, 0x02, 0xD3, 0x22, 0x90, +0xEA, 0x44, 0x74, 0x80, 0xF0, 0x7F, 0x10, 0x12, +0x2F, 0xC5, 0x90, 0xFE, 0x47, 0xE0, 0x44, 0x80, +0xF0, 0x78, 0x00, 0xE8, 0xC3, 0x94, 0x04, 0x50, +0x0A, 0x7F, 0x88, 0x7E, 0x13, 0x12, 0xE3, 0x4D, +0x08, 0x80, 0xF0, 0x90, 0xFE, 0x45, 0xE0, 0x54, +0xFB, 0xF0, 0x90, 0xFE, 0x47, 0xE0, 0x54, 0xBF, +0xF0, 0x90, 0xFE, 0x45, 0xE0, 0x54, 0xFE, 0xF0, +0x90, 0xFE, 0x45, 0xE0, 0x54, 0x7F, 0xF0, 0x90, +0xFE, 0x46, 0xE0, 0x44, 0x40, 0xF0, 0x90, 0xFE, +0x45, 0xE0, 0x54, 0xC7, 0x44, 0x18, 0xF0, 0x90, +0xFE, 0x47, 0xE0, 0x44, 0x08, 0xF0, 0x90, 0xFE, +0x45, 0xE0, 0x44, 0x40, 0xF0, 0x7F, 0x32, 0x7E, +0x00, 0x12, 0xE3, 0x4D, 0x90, 0xFE, 0x51, 0xE0, +0x54, 0x33, 0xF0, 0x90, 0xFE, 0x44, 0x74, 0x02, +0xF0, 0x30, 0x25, 0x04, 0xE0, 0x20, 0xE1, 0xF9, +0x90, 0xFE, 0x51, 0xE0, 0x54, 0x0F, 0xF0, 0x90, +0xFE, 0x44, 0x74, 0x02, 0xF0, 0x30, 0x25, 0x04, +0xE0, 0x20, 0xE1, 0xF9, 0x90, 0xFE, 0x44, 0x74, +0x04, 0xF0, 0x30, 0x25, 0x04, 0xE0, 0x20, 0xE2, +0xF9, 0x90, 0xFE, 0x4C, 0xE0, 0xF0, 0x90, 0xFE, +0x4D, 0xE0, 0xF0, 0x90, 0xFE, 0x48, 0x74, 0x7F, +0xF0, 0x90, 0xFE, 0x49, 0x74, 0x9F, 0xF0, 0x90, +0xFE, 0x51, 0xE0, 0x54, 0x3C, 0x44, 0x02, 0xF0, +0x90, 0xFE, 0x44, 0x74, 0x02, 0xF0, 0x30, 0x25, +0x04, 0xE0, 0x20, 0xE1, 0xF9, 0x90, 0xFE, 0x46, +0xE0, 0x44, 0x20, 0xF0, 0x79, 0x02, 0x7A, 0x06, +0x7B, 0x00, 0x7C, 0x00, 0x7D, 0x06, 0x7E, 0xEB, +0x7F, 0xC9, 0x12, 0x2F, 0xA7, 0x40, 0x03, 0x02, +0xE3, 0x04, 0xD3, 0x22, 0xE4, 0x90, 0xFE, 0x48, +0xF0, 0x90, 0xFE, 0x49, 0xF0, 0x90, 0xFE, 0x4C, +0xE0, 0xF0, 0x90, 0xFE, 0x4D, 0xE0, 0xF0, 0x90, +0xFE, 0x47, 0xE0, 0x54, 0x7F, 0xF0, 0xC2, 0x25, +0xC2, 0x1F, 0xD2, 0x19, 0xC3, 0x22, 0xC2, 0x3E, +0x75, 0x7C, 0x00, 0x75, 0x7D, 0x00, 0x75, 0x7E, +0x00, 0x22, 0x05, 0x7C, 0xE5, 0x7C, 0x70, 0x14, +0x05, 0x7D, 0xE5, 0x7D, 0x70, 0x04, 0x05, 0x7E, +0x80, 0x0A, 0xB4, 0x17, 0x07, 0xE5, 0x7E, 0xB4, +0x06, 0x02, 0xD2, 0x3E, 0x22, 0x75, 0x8A, 0x00, +0x75, 0x8C, 0xCE, 0xC2, 0x8D, 0x90, 0xEA, 0x65, +0xE4, 0xF0, 0xA3, 0xF0, 0xD2, 0x8C, 0x90, 0xEA, +0x65, 0xE0, 0xFC, 0xA3, 0xE0, 0xFD, 0xEC, 0xC3, +0x9E, 0x40, 0xF3, 0x70, 0x05, 0xED, 0xC3, 0x9F, +0x40, 0xEC, 0xC2, 0x8C, 0x22, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, +0x58, 0x44, 0x2D, 0x49, 0x6E, 0x69, 0x74, 0x20, +0x20, 0x20, 0x20, 0x31, 0x30, 0x30, 0x30, 0x31 }; + +static BYTE SM_Rdwr[] = { +0x7B, 0x0C, 0x7C, 0xF0, 0x7D, 0x10, 0x7E, 0xE9, +0x7F, 0xCC, 0x12, 0x2F, 0x71, 0x90, 0xE9, 0xC3, +0xE0, 0xB4, 0x73, 0x04, 0x74, 0x40, 0x80, 0x09, +0xB4, 0x75, 0x04, 0x74, 0x40, 0x80, 0x02, 0x74, +0xC0, 0x90, 0xFE, 0x70, 0xF0, 0x90, 0xFF, 0x09, +0xE0, 0x30, 0xE1, 0x06, 0x90, 0xFF, 0x23, 0x74, +0x80, 0xF0, 0x90, 0xFF, 0x83, 0xE0, 0xA2, 0xE1, +0x92, 0x0A, 0x40, 0x01, 0x22, 0x90, 0xFE, 0x6A, +0xE4, 0xF0, 0x90, 0xE9, 0xCC, 0xE0, 0xB4, 0x02, +0x05, 0xD2, 0x06, 0x02, 0xE0, 0x78, 0xB4, 0x03, +0x03, 0x02, 0xE3, 0xD0, 0xB4, 0x04, 0x03, 0x02, +0xE1, 0xC6, 0xB4, 0x05, 0x03, 0x02, 0xE5, 0x20, +0xB4, 0x06, 0x03, 0x02, 0xE5, 0xE0, 0xB4, 0x07, +0x05, 0x12, 0x2F, 0x5C, 0xD3, 0x22, 0xB4, 0x08, +0x05, 0xC2, 0x06, 0x02, 0xE6, 0x3B, 0xC3, 0x22, +0xE5, 0x3E, 0xC3, 0x13, 0x90, 0xE9, 0xCA, 0xF0, +0xC0, 0xE0, 0x75, 0xF0, 0x02, 0xC0, 0xF0, 0x12, +0xE0, 0xD8, 0xEF, 0x70, 0x21, 0x20, 0x37, 0x07, +0x20, 0x09, 0x04, 0xD0, 0xF0, 0x80, 0x05, 0xD0, +0xF0, 0xD5, 0xF0, 0xE9, 0xD0, 0xE0, 0x90, 0xFF, +0x28, 0xE0, 0x30, 0xE7, 0xFC, 0x90, 0xFF, 0x28, +0xE0, 0x44, 0x01, 0xF0, 0xC3, 0x22, 0xD0, 0xF0, +0x90, 0xE9, 0xCF, 0xE0, 0x24, 0x01, 0xF0, 0x90, +0xE9, 0xCE, 0xE0, 0x34, 0x00, 0xF0, 0x90, 0xE9, +0xCD, 0xE0, 0x34, 0x00, 0xF0, 0xD0, 0xE0, 0x14, +0x70, 0xB6, 0x75, 0x3C, 0x00, 0x75, 0x3D, 0x00, +0x75, 0x3E, 0x00, 0x75, 0x3F, 0x00, 0xD3, 0x22, +0xC2, 0x08, 0xC2, 0x36, 0xC2, 0x37, 0xE4, 0x90, +0xEB, 0xC2, 0xF0, 0x90, 0xE9, 0xCD, 0xE0, 0xF8, +0xA3, 0xE0, 0xF9, 0xA3, 0xE0, 0x90, 0xFE, 0x6B, +0xF0, 0xA3, 0xE9, 0xF0, 0xA3, 0xE8, 0xF0, 0x90, +0xFE, 0x6F, 0x74, 0x0F, 0xF0, 0x90, 0xFE, 0x70, +0xE0, 0x54, 0xFC, 0x44, 0x02, 0xF0, 0x90, 0xFE, +0xC6, 0x74, 0x02, 0xF0, 0xA3, 0x74, 0x0F, 0xF0, +0x90, 0xFE, 0xC0, 0x74, 0xF4, 0xF0, 0x74, 0x00, +0xA3, 0xF0, 0x90, 0xFE, 0x68, 0x74, 0x21, 0xF0, +0x90, 0xFE, 0x64, 0x74, 0x70, 0x45, 0x4E, 0xF0, +0x90, 0xFE, 0x64, 0x74, 0x30, 0x45, 0x4E, 0xF0, +0x30, 0x06, 0x07, 0x90, 0xFF, 0x09, 0xE0, 0x30, +0xE5, 0xFC, 0x90, 0xFE, 0x6E, 0x74, 0x51, 0xF0, +0x90, 0xFE, 0xC4, 0x74, 0x21, 0xF0, 0xC2, 0x09, +0x12, 0xE7, 0xB0, 0x20, 0x08, 0x0E, 0x12, 0xE7, +0xBC, 0x30, 0x3E, 0xF7, 0x90, 0xFE, 0xD8, 0x74, +0x01, 0xF0, 0xD2, 0x09, 0x30, 0x09, 0x03, 0x7F, +0x00, 0x22, 0x12, 0xE7, 0xB0, 0x20, 0x36, 0x11, +0x20, 0x37, 0x0E, 0x12, 0xE7, 0xBC, 0x30, 0x3E, +0xF4, 0x90, 0xFE, 0xD8, 0x74, 0x01, 0xF0, 0xD2, +0x37, 0x30, 0x37, 0x03, 0x7F, 0x00, 0x22, 0x90, +0xFE, 0x64, 0x74, 0x10, 0x45, 0x4E, 0xF0, 0x90, +0xFE, 0x64, 0x74, 0x00, 0x45, 0x4E, 0xF0, 0x12, +0x2F, 0x65, 0x12, 0x2F, 0x68, 0xBF, 0x00, 0x09, +0x74, 0x02, 0x90, 0xEB, 0xC2, 0xF0, 0x7F, 0x00, +0x22, 0x12, 0x2F, 0x6B, 0xBF, 0x00, 0x0F, 0x12, +0x2F, 0x6E, 0xBF, 0x00, 0x09, 0x74, 0x01, 0x90, +0xEB, 0xC2, 0xF0, 0x7F, 0x00, 0x22, 0x30, 0x06, +0x0A, 0x90, 0xFF, 0x2A, 0x74, 0x02, 0xF0, 0xA3, +0x74, 0x00, 0xF0, 0x7F, 0x01, 0x22, 0x12, 0xE3, +0xAA, 0x74, 0x01, 0x90, 0xE9, 0xCB, 0xF0, 0xE5, +0x3E, 0xC3, 0x13, 0x90, 0xE9, 0xCA, 0xF0, 0xC0, +0xE0, 0x75, 0xF0, 0x02, 0xC0, 0xF0, 0x12, 0xE2, +0x2F, 0xEF, 0x70, 0x21, 0x20, 0x37, 0x07, 0x20, +0x09, 0x04, 0xD0, 0xF0, 0x80, 0x05, 0xD0, 0xF0, +0xD5, 0xF0, 0xE9, 0xD0, 0xE0, 0x90, 0xFF, 0x28, +0xE0, 0x30, 0xE7, 0xFC, 0x90, 0xFF, 0x28, 0xE0, +0x44, 0x01, 0xF0, 0xC3, 0x22, 0xD0, 0xF0, 0x90, +0xE9, 0xD2, 0xE0, 0x24, 0x01, 0xF0, 0x90, 0xE9, +0xD1, 0xE0, 0x34, 0x00, 0xF0, 0x90, 0xE9, 0xD0, +0xE0, 0x34, 0x00, 0xF0, 0xD0, 0xE0, 0x14, 0x70, +0xB6, 0x75, 0x3C, 0x00, 0x75, 0x3D, 0x00, 0x75, +0x3E, 0x00, 0x75, 0x3F, 0x00, 0xD3, 0x22, 0xC2, +0x08, 0xC2, 0x36, 0xC2, 0x37, 0x90, 0xFE, 0x68, +0x74, 0x31, 0xF0, 0x90, 0xE9, 0xD0, 0xE0, 0xF8, +0xA3, 0xE0, 0xF9, 0xA3, 0xE0, 0x90, 0xFE, 0x6B, +0xF0, 0xA3, 0xE9, 0xF0, 0xA3, 0xE8, 0xF0, 0x90, +0xFE, 0x6F, 0x74, 0x0F, 0xF0, 0x90, 0xFE, 0x70, +0xE0, 0x54, 0xFC, 0x44, 0x22, 0xF0, 0x90, 0xE9, +0xCB, 0xE0, 0x70, 0x0C, 0x90, 0xFE, 0xC0, 0x74, +0xF4, 0xF0, 0xA3, 0x74, 0x00, 0xF0, 0x80, 0x0A, +0x90, 0xFE, 0xC0, 0x74, 0xF0, 0xF0, 0xA3, 0x74, +0x00, 0xF0, 0x90, 0xFE, 0x64, 0x74, 0xF0, 0x45, +0x4E, 0xF0, 0x90, 0xFE, 0x64, 0x74, 0xB0, 0x45, +0x4E, 0xF0, 0x90, 0xFE, 0x6E, 0x74, 0x81, 0xF0, +0x90, 0xE9, 0xCB, 0xE0, 0x70, 0x0D, 0x90, 0xFE, +0xC6, 0x74, 0x02, 0xF0, 0xA3, 0x74, 0x0F, 0xF0, +0x02, 0xE3, 0x56, 0x20, 0x2D, 0x03, 0x02, 0xE2, +0xEF, 0x90, 0xFE, 0xC6, 0x74, 0x01, 0xF0, 0xA3, +0x74, 0xFF, 0xF0, 0x90, 0xFF, 0x09, 0x30, 0x0A, +0x04, 0xE0, 0x30, 0xE1, 0xF9, 0x90, 0xFE, 0xC4, +0x74, 0x23, 0xF0, 0x12, 0xE7, 0xB0, 0x20, 0x36, +0x11, 0x20, 0x37, 0x0E, 0x12, 0xE7, 0xBC, 0x30, +0x3E, 0xF4, 0x90, 0xFE, 0xD8, 0x74, 0x01, 0xF0, +0xD2, 0x37, 0x30, 0x37, 0x02, 0x61, 0xA7, 0x90, +0xFF, 0x09, 0xE0, 0x30, 0xE1, 0x06, 0x90, 0xFF, +0x23, 0x74, 0x80, 0xF0, 0x02, 0xE3, 0x3F, 0x90, +0xFE, 0xC6, 0xE4, 0xF0, 0xA3, 0x74, 0x3F, 0xF0, +0x78, 0x08, 0xC0, 0x00, 0xC2, 0x36, 0xC2, 0x37, +0x90, 0xFF, 0x09, 0x30, 0x0A, 0x04, 0xE0, 0x30, +0xE1, 0xF9, 0x90, 0xFE, 0xC4, 0x74, 0x23, 0xF0, +0x12, 0xE7, 0xB0, 0x20, 0x36, 0x11, 0x20, 0x37, +0x0E, 0x12, 0xE7, 0xBC, 0x30, 0x3E, 0xF4, 0x90, +0xFE, 0xD8, 0x74, 0x01, 0xF0, 0xD2, 0x37, 0x90, +0xFF, 0x09, 0xE0, 0x30, 0xE1, 0x06, 0x90, 0xFF, +0x23, 0x74, 0x80, 0xF0, 0x30, 0x37, 0x04, 0xD0, +0x00, 0x80, 0x6C, 0xD0, 0x00, 0xD8, 0xBB, 0xC2, +0x36, 0xC2, 0x37, 0x90, 0xFE, 0xC6, 0xE4, 0xF0, +0xA3, 0x74, 0x0F, 0xF0, 0x90, 0xFE, 0xC0, 0x74, +0xF6, 0xF0, 0xA3, 0x74, 0x00, 0xF0, 0x90, 0xFE, +0xC4, 0x74, 0x23, 0xF0, 0x12, 0xE7, 0xB0, 0x20, +0x36, 0x11, 0x20, 0x37, 0x0E, 0x12, 0xE7, 0xBC, +0x30, 0x3E, 0xF4, 0x90, 0xFE, 0xD8, 0x74, 0x01, +0xF0, 0xD2, 0x37, 0x30, 0x37, 0x02, 0x80, 0x2F, +0xC2, 0x09, 0x12, 0xE7, 0xB0, 0x20, 0x08, 0x0E, +0x12, 0xE7, 0xBC, 0x30, 0x3E, 0xF7, 0x90, 0xFE, +0xD8, 0x74, 0x01, 0xF0, 0xD2, 0x09, 0x30, 0x09, +0x02, 0x80, 0x14, 0x90, 0xFE, 0x64, 0x74, 0x90, +0x45, 0x4E, 0xF0, 0x90, 0xFE, 0x64, 0x74, 0x80, +0x45, 0x4E, 0xF0, 0x12, 0x2F, 0x59, 0x22, 0x7F, +0x00, 0x22, 0x90, 0xF6, 0x00, 0x7F, 0x06, 0x74, +0xFF, 0xF0, 0xA3, 0xDF, 0xFC, 0x7B, 0x02, 0x7C, +0xE9, 0x7D, 0xD3, 0x7E, 0xF6, 0x7F, 0x06, 0x12, +0x2F, 0x71, 0x7B, 0x02, 0x7C, 0xE9, 0x7D, 0xD3, +0x7E, 0xF6, 0x7F, 0x0B, 0x12, 0x2F, 0x71, 0x22, +0x90, 0xFE, 0xC6, 0xE4, 0xF0, 0xA3, 0x74, 0x0F, +0xF0, 0x90, 0xFE, 0x6F, 0xF0, 0x90, 0xFE, 0x70, +0xE0, 0x54, 0xFC, 0xF0, 0x90, 0xFE, 0xC0, 0x74, +0xF6, 0xF0, 0xA3, 0x74, 0x00, 0xF0, 0x90, 0xFE, +0x68, 0x74, 0x21, 0xF0, 0x90, 0xFE, 0x66, 0xE0, +0x54, 0xEF, 0xF0, 0x90, 0xE9, 0xD3, 0xE0, 0xF5, +0x08, 0xA3, 0xE0, 0xF5, 0x09, 0x90, 0xFF, 0x09, +0xE0, 0x30, 0xE5, 0xFC, 0xE4, 0xF5, 0x10, 0x7E, +0xF4, 0x7F, 0x00, 0xC0, 0x06, 0xC0, 0x07, 0xC2, +0x36, 0xC2, 0x37, 0xC2, 0x09, 0x90, 0xE9, 0xCD, +0xE0, 0xF8, 0xA3, 0xE0, 0xF9, 0xA3, 0xE0, 0x90, +0xFE, 0x6B, 0xF0, 0xA3, 0xE9, 0xF0, 0xA3, 0xE8, +0xF0, 0x90, 0xFE, 0x6E, 0x74, 0x71, 0xF0, 0x90, +0xFE, 0xC4, 0x74, 0x21, 0xF0, 0x90, 0xFE, 0x65, +0x12, 0xE7, 0xB0, 0xE0, 0x20, 0xE4, 0x11, 0x12, +0xE7, 0xBC, 0x30, 0x3E, 0xF6, 0x90, 0xFE, 0xD8, +0x74, 0x01, 0xF0, 0xD2, 0x09, 0x02, 0xE4, 0x72, +0x74, 0x10, 0xF0, 0x12, 0xE7, 0xB0, 0x20, 0x36, +0x11, 0x20, 0x37, 0x0E, 0x12, 0xE7, 0xBC, 0x30, +0x3E, 0xF4, 0x90, 0xFE, 0xD8, 0x74, 0x01, 0xF0, +0xD2, 0x37, 0x20, 0x09, 0x05, 0x20, 0x37, 0x02, +0x80, 0x10, 0x90, 0xFE, 0x66, 0xE0, 0x44, 0x10, +0xF0, 0x12, 0x2F, 0x5C, 0xD0, 0x07, 0xD0, 0x06, +0xC3, 0x22, 0xD0, 0x07, 0xD0, 0x06, 0x7B, 0x10, +0x7C, 0xF6, 0x7D, 0x00, 0x12, 0x2F, 0x71, 0x05, +0x10, 0xC3, 0xE5, 0x09, 0x94, 0x01, 0xF5, 0x09, +0xE5, 0x08, 0x94, 0x00, 0xF5, 0x08, 0x45, 0x09, +0x70, 0x03, 0x02, 0xE4, 0xEF, 0x90, 0xE9, 0xCF, +0xE0, 0x24, 0x20, 0xF0, 0x90, 0xE9, 0xCE, 0xE0, +0x34, 0x00, 0xF0, 0x90, 0xE9, 0xCD, 0xE0, 0x34, +0x00, 0xF0, 0xC3, 0xEF, 0x24, 0x10, 0xFF, 0xEE, +0x34, 0x00, 0xFE, 0xE5, 0x10, 0x64, 0x20, 0x60, +0x03, 0x02, 0xE4, 0x13, 0x90, 0xFF, 0x2A, 0x74, +0x02, 0xF0, 0xA3, 0x74, 0x00, 0xF0, 0x75, 0x10, +0x00, 0x7E, 0xF4, 0x7F, 0x00, 0x90, 0xFF, 0x09, +0xE0, 0x30, 0xE5, 0xFC, 0x02, 0xE4, 0x13, 0xE5, +0x10, 0x60, 0x17, 0x7E, 0x00, 0x7F, 0x00, 0x78, +0x04, 0xC3, 0x33, 0xFF, 0xEE, 0x33, 0xFE, 0xEF, +0xD8, 0xF8, 0x90, 0xFF, 0x2A, 0xEE, 0xF0, 0xA3, +0xEF, 0xF0, 0x90, 0xFE, 0x66, 0xE0, 0x44, 0x10, +0xF0, 0x12, 0x2F, 0x5C, 0x78, 0x00, 0x88, 0x3C, +0x88, 0x3D, 0x88, 0x3E, 0x88, 0x3F, 0xD3, 0x22, +0x12, 0x2F, 0x5F, 0x12, 0x2F, 0x62, 0x90, 0xFE, +0xC6, 0xE4, 0xF0, 0xA3, 0x74, 0x0F, 0xF0, 0x90, +0xFE, 0x6F, 0xF0, 0x90, 0xFE, 0x70, 0xE0, 0x54, +0xFC, 0xF0, 0x90, 0xFE, 0xC0, 0x74, 0xF6, 0xF0, +0xA3, 0x74, 0x00, 0xF0, 0x90, 0xFE, 0x68, 0x74, +0x31, 0xF0, 0x90, 0xE9, 0xD3, 0xE0, 0xF8, 0xC0, +0x00, 0xC2, 0x08, 0xC2, 0x36, 0xC2, 0x37, 0x90, +0xE9, 0xD0, 0xE0, 0xF8, 0xA3, 0xE0, 0xF9, 0xA3, +0xE0, 0x90, 0xFE, 0x6B, 0xF0, 0xA3, 0xE9, 0xF0, +0xA3, 0xE8, 0xF0, 0x90, 0xFE, 0x6E, 0x74, 0x81, +0xF0, 0x90, 0xFE, 0xC4, 0x74, 0x23, 0xF0, 0x12, +0xE7, 0xB0, 0x20, 0x36, 0x11, 0x20, 0x37, 0x0E, +0x12, 0xE7, 0xBC, 0x30, 0x3E, 0xF4, 0x90, 0xFE, +0xD8, 0x74, 0x01, 0xF0, 0xD2, 0x37, 0x30, 0x37, +0x07, 0xD0, 0x00, 0x12, 0x2F, 0x5C, 0xC3, 0x22, +0xC2, 0x09, 0x12, 0xE7, 0xB0, 0x20, 0x08, 0x0E, +0x12, 0xE7, 0xBC, 0x30, 0x3E, 0xF7, 0x90, 0xFE, +0xD8, 0x74, 0x01, 0xF0, 0xD2, 0x09, 0x20, 0x09, +0xE0, 0x90, 0xE9, 0xD2, 0xE0, 0x24, 0x01, 0xF0, +0x90, 0xE9, 0xD1, 0xE0, 0x34, 0x00, 0xF0, 0x90, +0xE9, 0xD0, 0xE0, 0x34, 0x00, 0xF0, 0xD0, 0x00, +0x18, 0xE8, 0x60, 0x03, 0x02, 0xE5, 0x4F, 0x12, +0x2F, 0x5C, 0x75, 0x3C, 0x00, 0x75, 0x3D, 0x00, +0x75, 0x3E, 0x00, 0x75, 0x3F, 0x00, 0xD3, 0x22, +0x90, 0xE9, 0xD0, 0xE0, 0xF8, 0xA3, 0xE0, 0xF9, +0xA3, 0xE0, 0x90, 0xFE, 0x6B, 0xF0, 0xA3, 0xE9, +0xF0, 0xA3, 0xE8, 0xF0, 0x90, 0xFE, 0x68, 0x74, +0x00, 0xF0, 0xC2, 0x08, 0x90, 0xFE, 0x6E, 0x74, +0xB1, 0xF0, 0xC2, 0x09, 0x12, 0xE7, 0xB0, 0x20, +0x08, 0x0E, 0x12, 0xE7, 0xBC, 0x30, 0x3E, 0xF7, +0x90, 0xFE, 0xD8, 0x74, 0x01, 0xF0, 0xD2, 0x09, +0x20, 0x09, 0x1E, 0x90, 0xFE, 0x70, 0xE0, 0x44, +0x10, 0xF0, 0x54, 0xEF, 0xF0, 0x12, 0x2F, 0x59, +0xEF, 0x60, 0x0E, 0x75, 0x3C, 0x00, 0x75, 0x3D, +0x00, 0x75, 0x3E, 0x00, 0x75, 0x3F, 0x00, 0xD3, +0x22, 0xC3, 0x22, 0x7B, 0x03, 0x7C, 0xE9, 0x7D, +0xCD, 0x7E, 0xE9, 0x7F, 0xD7, 0x12, 0x2F, 0x71, +0x12, 0xE3, 0xAA, 0x90, 0xE9, 0xD5, 0xE0, 0x60, +0x12, 0xF9, 0x12, 0xE7, 0x17, 0x40, 0x01, 0x22, +0x90, 0xF6, 0x00, 0x78, 0x06, 0x74, 0xFF, 0xF0, +0xA3, 0xD8, 0xFC, 0x74, 0x01, 0x90, 0xE9, 0xCB, +0xF0, 0xE5, 0x3E, 0xC3, 0x13, 0x90, 0xE9, 0xCA, +0xF0, 0xC0, 0xE0, 0x75, 0xF0, 0x02, 0xC0, 0xF0, +0x12, 0xE2, 0x2F, 0xEF, 0x70, 0x21, 0x20, 0x37, +0x07, 0x20, 0x09, 0x04, 0xD0, 0xF0, 0x80, 0x05, +0xD0, 0xF0, 0xD5, 0xF0, 0xE9, 0xD0, 0xE0, 0x90, +0xFF, 0x28, 0xE0, 0x30, 0xE7, 0xFC, 0x90, 0xFF, +0x28, 0xE0, 0x44, 0x01, 0xF0, 0xC3, 0x22, 0xD0, +0xF0, 0x90, 0xE9, 0xD2, 0xE0, 0x24, 0x01, 0xF0, +0x90, 0xE9, 0xD1, 0xE0, 0x34, 0x00, 0xF0, 0x90, +0xE9, 0xD0, 0xE0, 0x34, 0x00, 0xF0, 0xD0, 0xE0, +0x14, 0x70, 0xB6, 0x90, 0xE9, 0xD5, 0xE0, 0xF8, +0x90, 0xE9, 0xCA, 0xE0, 0x28, 0xF5, 0xF0, 0xC3, +0x74, 0x20, 0x95, 0xF0, 0x60, 0x22, 0xF9, 0x90, +0xE9, 0xCA, 0xE0, 0xF5, 0xF0, 0x90, 0xE9, 0xCF, +0xE0, 0x25, 0xF0, 0xF0, 0x90, 0xE9, 0xCE, 0xE0, +0x34, 0x00, 0xF0, 0x90, 0xE9, 0xCD, 0xE0, 0x34, +0x00, 0xF0, 0x12, 0xE7, 0x17, 0x40, 0x01, 0x22, +0x90, 0xE9, 0xD6, 0xE0, 0x70, 0x13, 0x7B, 0x03, +0x7C, 0xE9, 0x7D, 0xD7, 0x7E, 0xE9, 0x7F, 0xD0, +0x12, 0x2F, 0x71, 0x12, 0xE5, 0xE0, 0x40, 0x01, +0x22, 0x75, 0x3C, 0x00, 0x75, 0x3D, 0x00, 0x75, +0x3E, 0x00, 0x75, 0x3F, 0x00, 0xD3, 0x22, 0x90, +0xE9, 0xD6, 0xE0, 0x60, 0x18, 0x74, 0xFF, 0x90, +0xF4, 0x00, 0x78, 0xFF, 0xF0, 0xA3, 0x18, 0xB8, +0x00, 0xFA, 0x78, 0xFF, 0xF0, 0xA3, 0x18, 0xB8, +0x00, 0xFA, 0xF0, 0xA3, 0xF0, 0xC0, 0x01, 0x12, +0xE7, 0x70, 0x40, 0x04, 0xD0, 0x01, 0xC3, 0x22, +0x90, 0xE9, 0xCF, 0xE0, 0x24, 0x01, 0xF0, 0x90, +0xE9, 0xCE, 0xE0, 0x34, 0x00, 0xF0, 0x90, 0xE9, +0xCD, 0xE0, 0x34, 0x00, 0xF0, 0x90, 0xE9, 0xD2, +0xE0, 0x24, 0x01, 0xF0, 0x90, 0xE9, 0xD1, 0xE0, +0x34, 0x00, 0xF0, 0x90, 0xE9, 0xD0, 0xE0, 0x34, +0x00, 0xF0, 0xD0, 0x01, 0xD9, 0xC7, 0xD3, 0x22, +0xC2, 0x06, 0x90, 0xE9, 0xD6, 0xE0, 0x70, 0x28, +0x12, 0xE0, 0xD8, 0xEF, 0x60, 0x03, 0x02, 0xE7, +0xA0, 0x90, 0xEB, 0xC2, 0xE0, 0x60, 0x17, 0x64, +0x02, 0x60, 0x15, 0x90, 0xF6, 0x00, 0x78, 0x06, +0x74, 0xFF, 0xF0, 0xA3, 0xD8, 0xFC, 0x74, 0xF0, +0x90, 0xF6, 0x04, 0xF0, 0x80, 0x02, 0xC3, 0x22, +0xE4, 0x90, 0xE9, 0xCB, 0xF0, 0x12, 0xE2, 0x2F, +0xEF, 0x70, 0x03, 0x02, 0xE7, 0x81, 0xD3, 0x22, +0xC2, 0x3E, 0x75, 0x7C, 0x00, 0x75, 0x7D, 0x00, +0x75, 0x7E, 0x00, 0x22, 0x05, 0x7C, 0xE5, 0x7C, +0x70, 0x14, 0x05, 0x7D, 0xE5, 0x7D, 0x70, 0x04, +0x05, 0x7E, 0x80, 0x0A, 0xB4, 0x17, 0x07, 0xE5, +0x7E, 0xB4, 0x06, 0x02, 0xD2, 0x3E, 0x22, 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, +0x58, 0x44, 0x2D, 0x52, 0x57, 0x20, 0x20, 0x20, +0x20, 0x20, 0x20, 0x31, 0x30, 0x30, 0x30, 0x30 }; + diff --git a/drivers/staging/keucr/scsiglue.c b/drivers/staging/keucr/scsiglue.c new file mode 100644 index 00000000..e1f3931d --- /dev/null +++ b/drivers/staging/keucr/scsiglue.c @@ -0,0 +1,483 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "usb.h" +#include "scsiglue.h" +#include "transport.h" + +/* Host functions */ +/* + * host_info() + */ +static const char *host_info(struct Scsi_Host *host) +{ + /* pr_info("scsiglue --- host_info\n"); */ + return "SCSI emulation for USB Mass Storage devices"; +} + +/* + * slave_alloc() + */ +static int slave_alloc(struct scsi_device *sdev) +{ + struct us_data *us = host_to_us(sdev->host); + + /* pr_info("scsiglue --- slave_alloc\n"); */ + sdev->inquiry_len = 36; + + blk_queue_update_dma_alignment(sdev->request_queue, (512 - 1)); + + if (us->subclass == USB_SC_UFI) + sdev->sdev_target->pdt_1f_for_no_lun = 1; + + return 0; +} + +/* + * slave_configure() + */ +static int slave_configure(struct scsi_device *sdev) +{ + struct us_data *us = host_to_us(sdev->host); + + /* pr_info("scsiglue --- slave_configure\n"); */ + if (us->fflags & (US_FL_MAX_SECTORS_64 | US_FL_MAX_SECTORS_MIN)) { + unsigned int max_sectors = 64; + + if (us->fflags & US_FL_MAX_SECTORS_MIN) + max_sectors = PAGE_CACHE_SIZE >> 9; + if (queue_max_sectors(sdev->request_queue) > max_sectors) + blk_queue_max_hw_sectors(sdev->request_queue, + max_sectors); + } + + if (sdev->type == TYPE_DISK) { + if (us->subclass != USB_SC_SCSI && + us->subclass != USB_SC_CYP_ATACB) + sdev->use_10_for_ms = 1; + sdev->use_192_bytes_for_3f = 1; + if (us->fflags & US_FL_NO_WP_DETECT) + sdev->skip_ms_page_3f = 1; + sdev->skip_ms_page_8 = 1; + if (us->fflags & US_FL_FIX_CAPACITY) + sdev->fix_capacity = 1; + if (us->fflags & US_FL_CAPACITY_HEURISTICS) + sdev->guess_capacity = 1; + if (sdev->scsi_level > SCSI_2) + sdev->sdev_target->scsi_level = sdev->scsi_level = SCSI_2; + sdev->retry_hwerror = 1; + sdev->allow_restart = 1; + sdev->last_sector_bug = 1; + } else { + sdev->use_10_for_ms = 1; + } + + if ((us->protocol == USB_PR_CB || us->protocol == USB_PR_CBI) && + sdev->scsi_level == SCSI_UNKNOWN) + us->max_lun = 0; + + if (us->fflags & US_FL_NOT_LOCKABLE) + sdev->lockable = 0; + + return 0; +} + +/* This is always called with scsi_lock(host) held */ +/* + * queuecommand() + */ +static int queuecommand_lck(struct scsi_cmnd *srb, + void (*done)(struct scsi_cmnd *)) +{ + struct us_data *us = host_to_us(srb->device->host); + + /* pr_info("scsiglue --- queuecommand\n"); */ + + /* check for state-transition errors */ + if (us->srb != NULL) { + /* pr_info("Error in %s: us->srb = %p\n" + __FUNCTION__, us->srb); */ + return SCSI_MLQUEUE_HOST_BUSY; + } + + /* fail the command if we are disconnecting */ + if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) { + pr_info("Fail command during disconnect\n"); + srb->result = DID_NO_CONNECT << 16; + done(srb); + return 0; + } + + /* enqueue the command and wake up the control thread */ + srb->scsi_done = done; + us->srb = srb; + complete(&us->cmnd_ready); + + return 0; +} + +static DEF_SCSI_QCMD(queuecommand) + +/*********************************************************************** + * Error handling functions + ***********************************************************************/ + +/* Command timeout and abort */ +/* + * command_abort() + */ +static int command_abort(struct scsi_cmnd *srb) +{ + struct us_data *us = host_to_us(srb->device->host); + + /* pr_info("scsiglue --- command_abort\n"); */ + + scsi_lock(us_to_host(us)); + if (us->srb != srb) { + scsi_unlock(us_to_host(us)); + printk("-- nothing to abort\n"); + return FAILED; + } + + set_bit(US_FLIDX_TIMED_OUT, &us->dflags); + if (!test_bit(US_FLIDX_RESETTING, &us->dflags)) { + set_bit(US_FLIDX_ABORTING, &us->dflags); + usb_stor_stop_transport(us); + } + scsi_unlock(us_to_host(us)); + + /* Wait for the aborted command to finish */ + wait_for_completion(&us->notify); + return SUCCESS; +} + +/* This invokes the transport reset mechanism to reset the state of the + * device. + */ +/* + * device_reset() + */ +static int device_reset(struct scsi_cmnd *srb) +{ + struct us_data *us = host_to_us(srb->device->host); + int result; + + /* pr_info("scsiglue --- device_reset\n"); */ + + /* lock the device pointers and do the reset */ + mutex_lock(&(us->dev_mutex)); + result = us->transport_reset(us); + mutex_unlock(&us->dev_mutex); + + return result < 0 ? FAILED : SUCCESS; +} + +/* + * bus_reset() + */ +static int bus_reset(struct scsi_cmnd *srb) +{ + struct us_data *us = host_to_us(srb->device->host); + int result; + + /* pr_info("scsiglue --- bus_reset\n"); */ + result = usb_stor_port_reset(us); + return result < 0 ? FAILED : SUCCESS; +} + +/* + * usb_stor_report_device_reset() + */ +void usb_stor_report_device_reset(struct us_data *us) +{ + int i; + struct Scsi_Host *host = us_to_host(us); + + /* pr_info("scsiglue --- usb_stor_report_device_reset\n"); */ + scsi_report_device_reset(host, 0, 0); + if (us->fflags & US_FL_SCM_MULT_TARG) { + for (i = 1; i < host->max_id; ++i) + scsi_report_device_reset(host, 0, i); + } +} + +/* + * usb_stor_report_bus_reset() + */ +void usb_stor_report_bus_reset(struct us_data *us) +{ + struct Scsi_Host *host = us_to_host(us); + + /* pr_info("scsiglue --- usb_stor_report_bus_reset\n"); */ + scsi_lock(host); + scsi_report_bus_reset(host, 0); + scsi_unlock(host); +} + +/*********************************************************************** + * /proc/scsi/ functions + ***********************************************************************/ + +/* we use this macro to help us write into the buffer */ +#undef SPRINTF +#define SPRINTF(args...) \ + do { if (pos < buffer+length) pos += sprintf(pos, ## args); } while (0) + +/* + * proc_info() + */ +static int proc_info(struct Scsi_Host *host, char *buffer, char **start, + off_t offset, int length, int inout) +{ + struct us_data *us = host_to_us(host); + char *pos = buffer; + const char *string; + + /* pr_info("scsiglue --- proc_info\n"); */ + if (inout) + return length; + + /* print the controller name */ + SPRINTF(" Host scsi%d: usb-storage\n", host->host_no); + + /* print product, vendor, and serial number strings */ + if (us->pusb_dev->manufacturer) + string = us->pusb_dev->manufacturer; + else if (us->unusual_dev->vendorName) + string = us->unusual_dev->vendorName; + else + string = "Unknown"; + SPRINTF(" Vendor: %s\n", string); + if (us->pusb_dev->product) + string = us->pusb_dev->product; + else if (us->unusual_dev->productName) + string = us->unusual_dev->productName; + else + string = "Unknown"; + SPRINTF(" Product: %s\n", string); + if (us->pusb_dev->serial) + string = us->pusb_dev->serial; + else + string = "None"; + SPRINTF("Serial Number: %s\n", string); + + /* show the protocol and transport */ + SPRINTF(" Protocol: %s\n", us->protocol_name); + SPRINTF(" Transport: %s\n", us->transport_name); + + /* show the device flags */ + if (pos < buffer + length) { + pos += sprintf(pos, " Quirks:"); + +#define US_FLAG(name, value) \ + if (us->fflags & value)\ + pos += sprintf(pos, " " #name); +US_DO_ALL_FLAGS +#undef US_FLAG + + *(pos++) = '\n'; + } + + /* Calculate start of next buffer, and return value. */ + *start = buffer + offset; + + if ((pos - buffer) < offset) + return 0; + else if ((pos - buffer - offset) < length) + return pos - buffer - offset; + else + return length; +} + +/*********************************************************************** + * Sysfs interface + ***********************************************************************/ + +/* Output routine for the sysfs max_sectors file */ +/* + * show_max_sectors() + */ +static ssize_t show_max_sectors(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct scsi_device *sdev = to_scsi_device(dev); + + /* pr_info("scsiglue --- ssize_t show_max_sectors\n"); */ + return sprintf(buf, "%u\n", queue_max_sectors(sdev->request_queue)); +} + +/* Input routine for the sysfs max_sectors file */ +/* + * store_max_sectors() + */ +static ssize_t store_max_sectors(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct scsi_device *sdev = to_scsi_device(dev); + unsigned short ms; + + /* pr_info("scsiglue --- ssize_t store_max_sectors\n"); */ + if (sscanf(buf, "%hu", &ms) > 0 && ms <= SCSI_DEFAULT_MAX_SECTORS) { + blk_queue_max_hw_sectors(sdev->request_queue, ms); + return strlen(buf); + } + return -EINVAL; +} + +static DEVICE_ATTR(max_sectors, S_IRUGO | S_IWUSR, show_max_sectors, store_max_sectors); +static struct device_attribute *sysfs_device_attr_list[] = {&dev_attr_max_sectors, NULL, }; + +/* this defines our host template, with which we'll allocate hosts */ + +/* + * usb_stor_host_template() + */ +struct scsi_host_template usb_stor_host_template = { + /* basic userland interface stuff */ + .name = "eucr-storage", + .proc_name = "eucr-storage", + .proc_info = proc_info, + .info = host_info, + + /* command interface -- queued only */ + .queuecommand = queuecommand, + + /* error and abort handlers */ + .eh_abort_handler = command_abort, + .eh_device_reset_handler = device_reset, + .eh_bus_reset_handler = bus_reset, + + /* queue commands only, only one command per LUN */ + .can_queue = 1, + .cmd_per_lun = 1, + + /* unknown initiator id */ + .this_id = -1, + + .slave_alloc = slave_alloc, + .slave_configure = slave_configure, + + /* lots of sg segments can be handled */ + .sg_tablesize = SG_ALL, + + /* limit the total size of a transfer to 120 KB */ + .max_sectors = 240, + + /* merge commands... this seems to help performance, but + * periodically someone should test to see which setting is more + * optimal. + */ + .use_clustering = 1, + + /* emulated HBA */ + .emulated = 1, + + /* we do our own delay after a device or bus reset */ + .skip_settle_delay = 1, + + /* sysfs device attributes */ + .sdev_attrs = sysfs_device_attr_list, + + /* module management */ + .module = THIS_MODULE +}; + +/* To Report "Illegal Request: Invalid Field in CDB */ +unsigned char usb_stor_sense_invalidCDB[18] = { + [0] = 0x70, /* current error */ + [2] = ILLEGAL_REQUEST, /* Illegal Request = 0x05 */ + [7] = 0x0a, /* additional length */ + [12] = 0x24 /* Invalid Field in CDB */ +}; + +/*********************************************************************** + * Scatter-gather transfer buffer access routines + ***********************************************************************/ + +/* + * usb_stor_access_xfer_buf() + */ +unsigned int usb_stor_access_xfer_buf(struct us_data *us, unsigned char *buffer, + unsigned int buflen, struct scsi_cmnd *srb, struct scatterlist **sgptr, + unsigned int *offset, enum xfer_buf_dir dir) +{ + unsigned int cnt; + + /* pr_info("transport --- usb_stor_access_xfer_buf\n"); */ + struct scatterlist *sg = *sgptr; + + if (!sg) + sg = scsi_sglist(srb); + + cnt = 0; + while (cnt < buflen && sg) { + struct page *page = sg_page(sg) + + ((sg->offset + *offset) >> PAGE_SHIFT); + unsigned int poff = (sg->offset + *offset) & (PAGE_SIZE-1); + unsigned int sglen = sg->length - *offset; + + if (sglen > buflen - cnt) { + /* Transfer ends within this s-g entry */ + sglen = buflen - cnt; + *offset += sglen; + } else { + /* Transfer continues to next s-g entry */ + *offset = 0; + sg = sg_next(sg); + } + + while (sglen > 0) { + unsigned int plen = min(sglen, + (unsigned int)PAGE_SIZE - poff); + unsigned char *ptr = kmap(page); + + if (dir == TO_XFER_BUF) + memcpy(ptr + poff, buffer + cnt, plen); + else + memcpy(buffer + cnt, ptr + poff, plen); + kunmap(page); + + /* Start at the beginning of the next page */ + poff = 0; + ++page; + cnt += plen; + sglen -= plen; + } + } + *sgptr = sg; + + /* Return the amount actually transferred */ + return cnt; +} + +/* + * Store the contents of buffer into srb's transfer + * buffer and set the SCSI residue. + */ +/* + * usb_stor_set_xfer_buf() + */ +void usb_stor_set_xfer_buf(struct us_data *us, unsigned char *buffer, + unsigned int buflen, struct scsi_cmnd *srb, unsigned int dir) +{ + unsigned int offset = 0; + struct scatterlist *sg = NULL; + + /* pr_info("transport --- usb_stor_set_xfer_buf\n"); */ + /* TO_XFER_BUF = 0, FROM_XFER_BUF = 1 */ + buflen = min(buflen, scsi_bufflen(srb)); + buflen = usb_stor_access_xfer_buf(us, buffer, buflen, srb, + &sg, &offset, dir); + if (buflen < scsi_bufflen(srb)) + scsi_set_resid(srb, scsi_bufflen(srb) - buflen); +} diff --git a/drivers/staging/keucr/scsiglue.h b/drivers/staging/keucr/scsiglue.h new file mode 100644 index 00000000..c7e59f0f --- /dev/null +++ b/drivers/staging/keucr/scsiglue.h @@ -0,0 +1,10 @@ +#ifndef _SCSIGLUE_H_ +#define _SCSIGLUE_H_ + +extern void usb_stor_report_device_reset(struct us_data *us); +extern void usb_stor_report_bus_reset(struct us_data *us); + +extern unsigned char usb_stor_sense_invalidCDB[18]; +extern struct scsi_host_template usb_stor_host_template; + +#endif diff --git a/drivers/staging/keucr/smcommon.h b/drivers/staging/keucr/smcommon.h new file mode 100644 index 00000000..278bdb87 --- /dev/null +++ b/drivers/staging/keucr/smcommon.h @@ -0,0 +1,31 @@ +/*----- < SMCommon.h> --------------------------------------------------*/ +#ifndef SMCOMMON_INCD +#define SMCOMMON_INCD + + +/*************************************************************************** +Define Difinetion +***************************************************************************/ +#define SMSUCCESS 0x0000 /* SUCCESS */ +#define ERROR 0xFFFF /* ERROR */ +#define CORRECT 0x0001 /* CORRECTABLE */ + +/***************************************************************************/ +#define NO_ERROR 0x0000 /* NO ERROR */ +#define ERR_WriteFault 0x0003 /* Peripheral Device Write Fault */ +#define ERR_HwError 0x0004 /* Hardware Error */ +#define ERR_DataStatus 0x0010 /* DataStatus Error */ +#define ERR_EccReadErr 0x0011 /* Unrecovered Read Error */ +#define ERR_CorReadErr 0x0018 /* Recovered Read Data with ECC */ +#define ERR_OutOfLBA 0x0021 /* Illegal Logical Block Address */ +#define ERR_WrtProtect 0x0027 /* Write Protected */ +#define ERR_ChangedMedia 0x0028 /* Medium Changed */ +#define ERR_UnknownMedia 0x0030 /* Incompatible Medium Installed */ +#define ERR_IllegalFmt 0x0031 /* Medium Format Corrupted */ +#define ERR_NoSmartMedia 0x003A /* Medium Not Present */ + +/***************************************************************************/ +void StringCopy(char *, char *, int); +int StringCmp(char *, char *, int); + +#endif diff --git a/drivers/staging/keucr/smil.h b/drivers/staging/keucr/smil.h new file mode 100644 index 00000000..24a636a4 --- /dev/null +++ b/drivers/staging/keucr/smil.h @@ -0,0 +1,311 @@ +/*----- < smil.h> ----------------------------------------------------*/ +#ifndef SMIL_INCD +#define SMIL_INCD + +/*************************************************************************** +Define Definition +***************************************************************************/ +#define K_BYTE 1024 /* Kilo Byte */ +#define SECTSIZE 512 /* Sector buffer size */ +#define REDTSIZE 16 /* Redundant buffer size */ + +/***************************************************************************/ +#define DUMMY_DATA 0xFF /* No Assign Sector Read Data */ + +/*************************************************************************** +Max Zone/Block/Sectors Data Definition +***************************************************************************/ +#define MAX_ZONENUM 128 /* Max Zone Numbers in a SmartMedia */ +#define MAX_BLOCKNUM 0x0400 /* Max Block Numbers in a Zone */ +#define MAX_SECTNUM 0x20 /* Max Sector Numbers in a Block */ +#define MAX_LOGBLOCK 1000 /* Max Logical Block Numbers in a Zone */ + +/***************************************************************************/ +#define CIS_SEARCH_SECT 0x08 /* Max CIS Search Sector Number */ + +/*************************************************************************** +Logical to Physical Block Table Data Definition +***************************************************************************/ +#define NO_ASSIGN 0xFFFF /* No Assign Logical Block Address */ + +/*************************************************************************** +'SectCopyMode' Data +***************************************************************************/ +#define COMPLETED 0 /* Sector Copy Completed */ +#define REQ_ERASE 1 /* Request Read Block Erase */ +#define REQ_FAIL 2 /* Request Read Block Failed */ + +/*************************************************************************** +Retry Counter Definition +***************************************************************************/ +#define RDERR_REASSIGN 1 /* Reassign with Read Error */ +#define L2P_ERR_ERASE 1 /* BlockErase for Contradicted L2P Table */ + +/*************************************************************************** +Hardware ECC Definition +***************************************************************************/ +#define HW_ECC_SUPPORTED 1 /* Hardware ECC Supported */ +/* No difinition for Software ECC */ + +/*************************************************************************** +SmartMedia Command & Status Definition +***************************************************************************/ +/* SmartMedia Command */ +#define WRDATA 0x80 +/* #define READ 0x00 */ +#define READ_REDT 0x50 +/* #define WRITE 0x10 */ +#define RDSTATUS 0x70 + +#define READ1 0x00 /* NO */ +#define READ2 0x01 /* NO */ +#define READ3 0x50 /* NO */ +#define RST_CHIP 0xFF +#define ERASE1 0x60 +#define ERASE2 0xD0 +#define READ_ID_1 0x90 +#define READ_ID_2 0x91 +#define READ_ID_3 0x9A + +/* 712 SmartMedia Command */ +#define SM_CMD_RESET 0x00 /* 0xFF */ +#define SM_CMD_READ_ID_1 0x10 /* 0x90 */ +#define SM_CMD_READ_ID_2 0x20 /* 0x91 */ +#define SM_CMD_READ_STAT 0x30 /* 0x70 */ +#define SM_CMD_RDMULTPL_STAT 0x40 /* 0x71 */ +#define SM_CMD_READ_1 0x50 /* 0x00 */ +#define SM_CMD_READ_2 0x60 /* 0x01 */ +#define SM_CMD_READ_3 0x70 /* 0x50 */ +#define SM_CMD_PAGPRGM_TRUE 0x80 /* {0x80, 0x10} */ +#define SM_CMD_PAGPRGM_DUMY 0x90 /* {0x80, 0x11} */ +#define SM_CMD_PAGPRGM_MBLK 0xA0 /* {0x80, 0x15} */ +#define SM_CMD_BLKERASE 0xB0 /* {0x60, 0xD0} */ +#define SM_CMD_BLKERASE_MULTPL 0xC0 /* {0x60-0x60, 0xD0} */ + +#define SM_CRADDTCT_DEBNCETIMER_EN 0x02 +#define SM_CMD_START_BIT 0x01 + +#define SM_WaitCmdDone { while (!SM_CmdDone); } +#define SM_WaitDmaDone { while (!SM_DmaDone); } + +/* SmartMedia Status */ +#define WR_FAIL 0x01 /* 0:Pass, 1:Fail */ +#define SUSPENDED 0x20 /* 0:Not Suspended, 1:Suspended */ +#define READY 0x40 /* 0:Busy, 1:Ready */ +#define WR_PRTCT 0x80 /* 0:Protect, 1:Not Protect */ + +/* SmartMedia Busy Time (1bit:0.1ms) */ +#define BUSY_PROG 200 /* tPROG : 20ms ----- Program Time old : 200 */ +#define BUSY_ERASE 4000 /* tBERASE : 400ms ----- Block Erase Time old : 4000 */ + +/*for 712 Test */ +/* #define BUSY_READ 1 *//* tR : 100us ----- Data transfer Time old : 1 */ +/* #define BUSY_READ 10 *//* tR : 100us ----- Data transfer Time old : 1 */ + +#define BUSY_READ 200 /* tR : 20ms ----- Data transfer Time old : 1 */ + +/* #define BUSY_RESET 60 *//* tRST : 6ms ----- Device Resetting Time old : 60 */ + +#define BUSY_RESET 600 /* tRST : 60ms ----- Device Resetting Time old : 60 */ + +/* Hardware Timer (1bit:0.1ms) */ +#define TIME_PON 3000 /* 300ms ------ Power On Wait Time */ +#define TIME_CDCHK 200 /* 20ms ------ Card Check Interval Timer */ +#define TIME_WPCHK 50 /* 5ms ------ WP Check Interval Timer */ +#define TIME_5VCHK 10 /* 1ms ------ 5V Check Interval Timer */ + +/*************************************************************************** +Redundant Data +***************************************************************************/ +#define REDT_DATA 0x04 +#define REDT_BLOCK 0x05 +#define REDT_ADDR1H 0x06 +#define REDT_ADDR1L 0x07 +#define REDT_ADDR2H 0x0B +#define REDT_ADDR2L 0x0C +#define REDT_ECC10 0x0D +#define REDT_ECC11 0x0E +#define REDT_ECC12 0x0F +#define REDT_ECC20 0x08 +#define REDT_ECC21 0x09 +#define REDT_ECC22 0x0A + +/*************************************************************************** +SmartMedia Model & Attribute +***************************************************************************/ +/* SmartMedia Attribute */ +#define NOWP 0x00 /* 0... .... No Write Protect */ +#define WP 0x80 /* 1... .... Write Protected */ +#define MASK 0x00 /* .00. .... NAND MASK ROM Model */ +#define FLASH 0x20 /* .01. .... NAND Flash ROM Model */ +#define AD3CYC 0x00 /* ...0 .... Address 3-cycle */ +#define AD4CYC 0x10 /* ...1 .... Address 4-cycle */ +#define BS16 0x00 /* .... 00.. 16page/block */ +#define BS32 0x04 /* .... 01.. 32page/block */ +#define PS256 0x00 /* .... ..00 256byte/page */ +#define PS512 0x01 /* .... ..01 512byte/page */ +#define MWP 0x80 /* WriteProtect mask */ +#define MFLASH 0x60 /* Flash Rom mask */ +#define MADC 0x10 /* Address Cycle */ +#define MBS 0x0C /* BlockSize mask */ +#define MPS 0x03 /* PageSize mask */ + +/* SmartMedia Model */ +#define NOSSFDC 0x00 /* NO SmartMedia */ +#define SSFDC1MB 0x01 /* 1MB SmartMedia */ +#define SSFDC2MB 0x02 /* 2MB SmartMedia */ +#define SSFDC4MB 0x03 /* 4MB SmartMedia */ +#define SSFDC8MB 0x04 /* 8MB SmartMedia */ +#define SSFDC16MB 0x05 /* 16MB SmartMedia */ +#define SSFDC32MB 0x06 /* 32MB SmartMedia */ +#define SSFDC64MB 0x07 /* 64MB SmartMedia */ +#define SSFDC128MB 0x08 /*128MB SmartMedia */ +#define SSFDC256MB 0x09 +#define SSFDC512MB 0x0A +#define SSFDC1GB 0x0B +#define SSFDC2GB 0x0C + +/*************************************************************************** +Struct Definition +***************************************************************************/ +struct SSFDCTYPE { + BYTE Model; + BYTE Attribute; + BYTE MaxZones; + BYTE MaxSectors; + WORD MaxBlocks; + WORD MaxLogBlocks; +}; + +typedef struct SSFDCTYPE_T { + BYTE Model; + BYTE Attribute; + BYTE MaxZones; + BYTE MaxSectors; + WORD MaxBlocks; + WORD MaxLogBlocks; +} *SSFDCTYPE_T; + +struct ADDRESS { + BYTE Zone; /* Zone Number */ + BYTE Sector; /* Sector(512byte) Number on Block */ + WORD PhyBlock; /* Physical Block Number on Zone */ + WORD LogBlock; /* Logical Block Number of Zone */ +}; + +typedef struct ADDRESS_T { + BYTE Zone; /* Zone Number */ + BYTE Sector; /* Sector(512byte) Number on Block */ + WORD PhyBlock; /* Physical Block Number on Zone */ + WORD LogBlock; /* Logical Block Number of Zone */ +} *ADDRESS_T; + +struct CIS_AREA { + BYTE Sector; /* Sector(512byte) Number on Block */ + WORD PhyBlock; /* Physical Block Number on Zone 0 */ +}; + + +extern BYTE IsSSFDCCompliance; +extern BYTE IsXDCompliance; + +extern DWORD ErrXDCode; +extern DWORD ErrCode; +extern WORD ReadBlock; +extern WORD WriteBlock; +extern DWORD MediaChange; + +extern struct SSFDCTYPE Ssfdc; +extern struct ADDRESS Media; +extern struct CIS_AREA CisArea; + +/* + * SMILMain.c + */ +/******************************************/ +int Init_D_SmartMedia(void); +int Pwoff_D_SmartMedia(void); +int Check_D_SmartMedia(void); +int Check_D_Parameter(struct us_data *, WORD *, BYTE *, BYTE *); +int Media_D_ReadSector(struct us_data *, DWORD, WORD, BYTE *); +int Media_D_WriteSector(struct us_data *, DWORD, WORD, BYTE *); +int Media_D_CopySector(struct us_data *, DWORD, WORD, BYTE *); +int Media_D_EraseBlock(struct us_data *, DWORD, WORD); +int Media_D_EraseAll(struct us_data *); +/******************************************/ +int Media_D_OneSectWriteStart(struct us_data *, DWORD, BYTE *); +int Media_D_OneSectWriteNext(struct us_data *, BYTE *); +int Media_D_OneSectWriteFlush(struct us_data *); + +/******************************************/ +extern int SM_FreeMem(void); /* ENE SM function */ +void SM_EnableLED(struct us_data *, BOOLEAN); +void Led_D_TernOn(void); +void Led_D_TernOff(void); + +int Media_D_EraseAllRedtData(DWORD Index, BOOLEAN CheckBlock); +/*DWORD Media_D_GetMediaInfo(struct us_data * fdoExt, + PIOCTL_MEDIA_INFO_IN pParamIn, PIOCTL_MEDIA_INFO_OUT pParamOut); */ + +/* + * SMILSub.c + */ +/******************************************/ +int Check_D_DataBlank(BYTE *); +int Check_D_FailBlock(BYTE *); +int Check_D_DataStatus(BYTE *); +int Load_D_LogBlockAddr(BYTE *); +void Clr_D_RedundantData(BYTE *); +void Set_D_LogBlockAddr(BYTE *); +void Set_D_FailBlock(BYTE *); +void Set_D_DataStaus(BYTE *); + +/******************************************/ +void Ssfdc_D_Reset(struct us_data *); +int Ssfdc_D_ReadCisSect(struct us_data *, BYTE *, BYTE *); +void Ssfdc_D_WriteRedtMode(void); +void Ssfdc_D_ReadID(BYTE *, BYTE); +int Ssfdc_D_ReadSect(struct us_data *, BYTE *, BYTE *); +int Ssfdc_D_ReadBlock(struct us_data *, WORD, BYTE *, BYTE *); +int Ssfdc_D_WriteSect(struct us_data *, BYTE *, BYTE *); +int Ssfdc_D_WriteBlock(struct us_data *, WORD, BYTE *, BYTE *); +int Ssfdc_D_CopyBlock(struct us_data *, WORD, BYTE *, BYTE *); +int Ssfdc_D_WriteSectForCopy(struct us_data *, BYTE *, BYTE *); +int Ssfdc_D_EraseBlock(struct us_data *); +int Ssfdc_D_ReadRedtData(struct us_data *, BYTE *); +int Ssfdc_D_WriteRedtData(struct us_data *, BYTE *); +int Ssfdc_D_CheckStatus(void); +int Set_D_SsfdcModel(BYTE); +void Cnt_D_Reset(void); +int Cnt_D_PowerOn(void); +void Cnt_D_PowerOff(void); +void Cnt_D_LedOn(void); +void Cnt_D_LedOff(void); +int Check_D_CntPower(void); +int Check_D_CardExist(void); +int Check_D_CardStsChg(void); +int Check_D_SsfdcWP(void); +int SM_ReadBlock(struct us_data *, BYTE *, BYTE *); + +int Ssfdc_D_ReadSect_DMA(struct us_data *, BYTE *, BYTE *); +int Ssfdc_D_ReadSect_PIO(struct us_data *, BYTE *, BYTE *); +int Ssfdc_D_WriteSect_DMA(struct us_data *, BYTE *, BYTE *); +int Ssfdc_D_WriteSect_PIO(struct us_data *, BYTE *, BYTE *); + +/******************************************/ +int Check_D_ReadError(BYTE *); +int Check_D_Correct(BYTE *, BYTE *); +int Check_D_CISdata(BYTE *, BYTE *); +void Set_D_RightECC(BYTE *); + +/* + * SMILECC.c + */ +void calculate_ecc(BYTE *, BYTE *, BYTE *, BYTE *, BYTE *); +BYTE correct_data(BYTE *, BYTE *, BYTE, BYTE, BYTE); +int _Correct_D_SwECC(BYTE *, BYTE *, BYTE *); +void _Calculate_D_SwECC(BYTE *, BYTE *); + +void SM_Init(void); + +#endif /* already included */ diff --git a/drivers/staging/keucr/smilecc.c b/drivers/staging/keucr/smilecc.c new file mode 100644 index 00000000..3085f1d4 --- /dev/null +++ b/drivers/staging/keucr/smilecc.c @@ -0,0 +1,211 @@ +#include "usb.h" +#include "scsiglue.h" +#include "transport.h" +/* #include "stdlib.h" */ +/* #include "EUCR6SK.h" */ +#include "smcommon.h" +#include "smil.h" + +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include "EMCRIOS.h" */ + +/* CP0-CP5 code table */ +static BYTE ecctable[256] = { +0x00, 0x55, 0x56, 0x03, 0x59, 0x0C, 0x0F, 0x5A, 0x5A, 0x0F, 0x0C, 0x59, 0x03, +0x56, 0x55, 0x00, 0x65, 0x30, 0x33, 0x66, 0x3C, 0x69, 0x6A, 0x3F, 0x3F, 0x6A, +0x69, 0x3C, 0x66, 0x33, 0x30, 0x65, 0x66, 0x33, 0x30, 0x65, 0x3F, 0x6A, 0x69, +0x3C, 0x3C, 0x69, 0x6A, 0x3F, 0x65, 0x30, 0x33, 0x66, 0x03, 0x56, 0x55, 0x00, +0x5A, 0x0F, 0x0C, 0x59, 0x59, 0x0C, 0x0F, 0x5A, 0x00, 0x55, 0x56, 0x03, 0x69, +0x3C, 0x3F, 0x6A, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6A, 0x3F, +0x3C, 0x69, 0x0C, 0x59, 0x5A, 0x0F, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, +0x55, 0x0F, 0x5A, 0x59, 0x0C, 0x0F, 0x5A, 0x59, 0x0C, 0x56, 0x03, 0x00, 0x55, +0x55, 0x00, 0x03, 0x56, 0x0C, 0x59, 0x5A, 0x0F, 0x6A, 0x3F, 0x3C, 0x69, 0x33, +0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3C, 0x3F, 0x6A, 0x6A, 0x3F, +0x3C, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3C, 0x3F, +0x6A, 0x0F, 0x5A, 0x59, 0x0C, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, +0x0C, 0x59, 0x5A, 0x0F, 0x0C, 0x59, 0x5A, 0x0F, 0x55, 0x00, 0x03, 0x56, 0x56, +0x03, 0x00, 0x55, 0x0F, 0x5A, 0x59, 0x0C, 0x69, 0x3C, 0x3F, 0x6A, 0x30, 0x65, +0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6A, 0x3F, 0x3C, 0x69, 0x03, 0x56, 0x55, +0x00, 0x5A, 0x0F, 0x0C, 0x59, 0x59, 0x0C, 0x0F, 0x5A, 0x00, 0x55, 0x56, 0x03, +0x66, 0x33, 0x30, 0x65, 0x3F, 0x6A, 0x69, 0x3C, 0x3C, 0x69, 0x6A, 0x3F, 0x65, +0x30, 0x33, 0x66, 0x65, 0x30, 0x33, 0x66, 0x3C, 0x69, 0x6A, 0x3F, 0x3F, 0x6A, +0x69, 0x3C, 0x66, 0x33, 0x30, 0x65, 0x00, 0x55, 0x56, 0x03, 0x59, 0x0C, 0x0F, +0x5A, 0x5A, 0x0F, 0x0C, 0x59, 0x03, 0x56, 0x55, 0x00 +}; + +static void trans_result(BYTE, BYTE, BYTE *, BYTE *); + +#define BIT7 0x80 +#define BIT6 0x40 +#define BIT5 0x20 +#define BIT4 0x10 +#define BIT3 0x08 +#define BIT2 0x04 +#define BIT1 0x02 +#define BIT0 0x01 +#define BIT1BIT0 0x03 +#define BIT23 0x00800000L +#define MASK_CPS 0x3f +#define CORRECTABLE 0x00555554L + +/* + * reg2; * LP14,LP12,LP10,... + * reg3; * LP15,LP13,LP11,... + * *ecc1; * LP15,LP14,LP13,... + * *ecc2; * LP07,LP06,LP05,... + */ +static void trans_result(BYTE reg2, BYTE reg3, BYTE *ecc1, BYTE *ecc2) +{ + BYTE a; /* Working for reg2,reg3 */ + BYTE b; /* Working for ecc1,ecc2 */ + BYTE i; /* For counting */ + + a = BIT7; b = BIT7; /* 80h=10000000b */ + *ecc1 = *ecc2 = 0; /* Clear ecc1,ecc2 */ + for (i = 0; i < 4; ++i) { + if ((reg3&a) != 0) + *ecc1 |= b; /* LP15,13,11,9 -> ecc1 */ + b = b>>1; /* Right shift */ + if ((reg2&a) != 0) + *ecc1 |= b; /* LP14,12,10,8 -> ecc1 */ + b = b>>1; /* Right shift */ + a = a>>1; /* Right shift */ + } + + b = BIT7; /* 80h=10000000b */ + for (i = 0; i < 4; ++i) { + if ((reg3&a) != 0) + *ecc2 |= b; /* LP7,5,3,1 -> ecc2 */ + b = b>>1; /* Right shift */ + if ((reg2&a) != 0) + *ecc2 |= b; /* LP6,4,2,0 -> ecc2 */ + b = b>>1; /* Right shift */ + a = a>>1; /* Right shift */ + } +} + +/*static void calculate_ecc(table,data,ecc1,ecc2,ecc3) */ +/* + * *table; * CP0-CP5 code table + * *data; * DATA + * *ecc1; * LP15,LP14,LP13,... + * *ecc2; * LP07,LP06,LP05,... + * *ecc3; * CP5,CP4,CP3,...,"1","1" + */ +void calculate_ecc(BYTE *table, BYTE *data, BYTE *ecc1, BYTE *ecc2, BYTE *ecc3) +{ + DWORD i; /* For counting */ + BYTE a; /* Working for table */ + BYTE reg1; /* D-all,CP5,CP4,CP3,... */ + BYTE reg2; /* LP14,LP12,L10,... */ + BYTE reg3; /* LP15,LP13,L11,... */ + + reg1 = reg2 = reg3 = 0; /* Clear parameter */ + for (i = 0; i < 256; ++i) { + a = table[data[i]]; /* Get CP0-CP5 code from table */ + reg1 ^= (a&MASK_CPS); /* XOR with a */ + if ((a&BIT6) != 0) { /* If D_all(all bit XOR) = 1 */ + reg3 ^= (BYTE)i; /* XOR with counter */ + reg2 ^= ~((BYTE)i); /* XOR with inv. of counter */ + } + } + + /* Trans LP14,12,10,... & LP15,13,11,... -> + LP15,14,13,... & LP7,6,5,.. */ + trans_result(reg2, reg3, ecc1, ecc2); + *ecc1 = ~(*ecc1); *ecc2 = ~(*ecc2); /* Inv. ecc2 & ecc3 */ + *ecc3 = ((~reg1)<<2)|BIT1BIT0; /* Make TEL format */ +} + +/* + * *data; * DATA + * *eccdata; * ECC DATA + * ecc1; * LP15,LP14,LP13,... + * ecc2; * LP07,LP06,LP05,... + * ecc3; * CP5,CP4,CP3,...,"1","1" + */ +BYTE correct_data(BYTE *data, BYTE *eccdata, BYTE ecc1, BYTE ecc2, BYTE ecc3) +{ + DWORD l; /* Working to check d */ + DWORD d; /* Result of comparison */ + DWORD i; /* For counting */ + BYTE d1, d2, d3; /* Result of comparison */ + BYTE a; /* Working for add */ + BYTE add; /* Byte address of cor. DATA */ + BYTE b; /* Working for bit */ + BYTE bit; /* Bit address of cor. DATA */ + + d1 = ecc1^eccdata[1]; d2 = ecc2^eccdata[0]; /* Compare LP's */ + d3 = ecc3^eccdata[2]; /* Comapre CP's */ + d = ((DWORD)d1<<16) /* Result of comparison */ + +((DWORD)d2<<8) + +(DWORD)d3; + + if (d == 0) + return 0; /* If No error, return */ + + if (((d^(d>>1))&CORRECTABLE) == CORRECTABLE) { /* If correctable */ + l = BIT23; + add = 0; /* Clear parameter */ + a = BIT7; + + for (i = 0; i < 8; ++i) { /* Checking 8 bit */ + if ((d&l) != 0) + add |= a; /* Make byte address from LP's */ + l >>= 2; a >>= 1; /* Right Shift */ + } + + bit = 0; /* Clear parameter */ + b = BIT2; + for (i = 0; i < 3; ++i) { /* Checking 3 bit */ + if ((d&l) != 0) + bit |= b; /* Make bit address from CP's */ + l >>= 2; b >>= 1; /* Right shift */ + } + + b = BIT0; + data[add] ^= (b<>= 1; /* Right shift */ + } + + if (i == 1) { /* If ECC error */ + eccdata[1] = ecc1; eccdata[0] = ecc2; /* Put right ECC code */ + eccdata[2] = ecc3; + return 2; + } + return 3; /* Uncorrectable error */ +} + +int _Correct_D_SwECC(BYTE *buf, BYTE *redundant_ecc, BYTE *calculate_ecc) +{ + DWORD err; + + err = correct_data(buf, redundant_ecc, *(calculate_ecc + 1), + *(calculate_ecc), *(calculate_ecc + 2)); + if (err == 1) + memcpy(calculate_ecc, redundant_ecc, 3); + + if (err == 0 || err == 1 || err == 2) + return 0; + + return -1; +} + +void _Calculate_D_SwECC(BYTE *buf, BYTE *ecc) +{ + calculate_ecc(ecctable, buf, ecc+1, ecc+0, ecc+2); +} + + diff --git a/drivers/staging/keucr/smilmain.c b/drivers/staging/keucr/smilmain.c new file mode 100644 index 00000000..cc49038e --- /dev/null +++ b/drivers/staging/keucr/smilmain.c @@ -0,0 +1,1850 @@ +#include +#include "usb.h" +#include "scsiglue.h" +#include "smcommon.h" +#include "smil.h" + +int Check_D_LogCHS (WORD *,BYTE *,BYTE *); +void Initialize_D_Media (void); +void PowerOff_D_Media (void); +int Check_D_MediaPower (void); +int Check_D_MediaExist (void); +int Check_D_MediaWP (void); +int Check_D_MediaFmt (struct us_data *); +int Check_D_MediaFmtForEraseAll (struct us_data *); +int Conv_D_MediaAddr (struct us_data *, DWORD); +int Inc_D_MediaAddr (struct us_data *); +int Check_D_FirstSect (void); +int Check_D_LastSect (void); +int Media_D_ReadOneSect (struct us_data *, WORD, BYTE *); +int Media_D_WriteOneSect (struct us_data *, WORD, BYTE *); +int Media_D_CopyBlockHead (struct us_data *); +int Media_D_CopyBlockTail (struct us_data *); +int Media_D_EraseOneBlock (void); +int Media_D_EraseAllBlock (void); + +int Copy_D_BlockAll (struct us_data *, DWORD); +int Copy_D_BlockHead (struct us_data *); +int Copy_D_BlockTail (struct us_data *); +int Reassign_D_BlockHead (struct us_data *); + +int Assign_D_WriteBlock (void); +int Release_D_ReadBlock (struct us_data *); +int Release_D_WriteBlock (struct us_data *); +int Release_D_CopySector (struct us_data *); + +int Copy_D_PhyOneSect (struct us_data *); +int Read_D_PhyOneSect (struct us_data *, WORD, BYTE *); +int Write_D_PhyOneSect (struct us_data *, WORD, BYTE *); +int Erase_D_PhyOneBlock (struct us_data *); + +int Set_D_PhyFmtValue (struct us_data *); +int Search_D_CIS (struct us_data *); +int Make_D_LogTable (struct us_data *); +void Check_D_BlockIsFull (void); + +int MarkFail_D_PhyOneBlock (struct us_data *); + +DWORD ErrXDCode; +DWORD ErrCode; +//BYTE SectBuf[SECTSIZE]; +static BYTE WorkBuf[SECTSIZE]; +static BYTE Redundant[REDTSIZE]; +static BYTE WorkRedund[REDTSIZE]; +//WORD Log2Phy[MAX_ZONENUM][MAX_LOGBLOCK]; +static WORD *Log2Phy[MAX_ZONENUM]; // 128 x 1000, Log2Phy[MAX_ZONENUM][MAX_LOGBLOCK]; +static BYTE Assign[MAX_ZONENUM][MAX_BLOCKNUM/8]; +static WORD AssignStart[MAX_ZONENUM]; +WORD ReadBlock; +WORD WriteBlock; +DWORD MediaChange; +static DWORD SectCopyMode; + +//BIT Control Macro +static BYTE BitData[] = { 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80 } ; +#define Set_D_Bit(a,b) (a[(BYTE)((b)/8)]|= BitData[(b)%8]) +#define Clr_D_Bit(a,b) (a[(BYTE)((b)/8)]&=~BitData[(b)%8]) +#define Chk_D_Bit(a,b) (a[(BYTE)((b)/8)] & BitData[(b)%8]) + +//extern PBYTE SMHostAddr; +BYTE IsSSFDCCompliance; +BYTE IsXDCompliance; + + +// +////Power Control & Media Exist Check Function +////----- Init_D_SmartMedia() -------------------------------------------- +//int Init_D_SmartMedia(void) +//{ +// int i; +// +// EMCR_Print("Init_D_SmartMedia start\n"); +// for (i=0; i len) + bn = len; + else + bn = count; + //if (Media_D_ReadOneSect(fdoExt, SectBuf)) + //if (Media_D_ReadOneSect(fdoExt, count, buf)) + if (Media_D_ReadOneSect(us, bn, buf)) + { + ErrCode = ERR_EccReadErr; + return(ErrCode); + } + + Media.Sector += bn; + count -= bn; + + if (count<=0) + break; + + buf += bn * SECTSIZE; + + if (Inc_D_MediaAddr(us)) + return(ErrCode); + } + + return(NO_ERROR); +} +// here +//----- Media_D_CopySector() ------------------------------------------ +int Media_D_CopySector(struct us_data *us, DWORD start,WORD count,BYTE *buf) +{ + //DWORD mode; + //int i; + WORD len, bn; + //SSFDCTYPE_T aa = (SSFDCTYPE_T ) &Ssfdc; + //ADDRESS_T bb = (ADDRESS_T) &Media; + + /* pr_info("Media_D_CopySector !!!\n"); */ + if (Conv_D_MediaAddr(us, start)) + return(ErrCode); + + while(1) + { + if (Assign_D_WriteBlock()) + return(ERROR); + + len = Ssfdc.MaxSectors - Media.Sector; + if (count > len) + bn = len; + else + bn = count; + + //if (Ssfdc_D_CopyBlock(fdoExt,count,buf,Redundant)) + if (Ssfdc_D_CopyBlock(us,bn,buf,Redundant)) + { + ErrCode = ERR_WriteFault; + return(ErrCode); + } + + Media.Sector = 0x1F; + //if (Release_D_ReadBlock(fdoExt)) + if (Release_D_CopySector(us)) + { + if (ErrCode==ERR_HwError) + { + ErrCode = ERR_WriteFault; + return(ErrCode); + } + } + count -= bn; + + if (count<=0) + break; + + buf += bn * SECTSIZE; + + if (Inc_D_MediaAddr(us)) + return(ErrCode); + + } + return(NO_ERROR); +} + +//----- Release_D_CopySector() ------------------------------------------ +int Release_D_CopySector(struct us_data *us) +{ + //SSFDCTYPE_T aa = (SSFDCTYPE_T ) &Ssfdc; + //ADDRESS_T bb = (ADDRESS_T) &Media; + + Log2Phy[Media.Zone][Media.LogBlock]=WriteBlock; + Media.PhyBlock=ReadBlock; + + if (Media.PhyBlock==NO_ASSIGN) + { + Media.PhyBlock=WriteBlock; + return(SMSUCCESS); + } + + Clr_D_Bit(Assign[Media.Zone],Media.PhyBlock); + Media.PhyBlock=WriteBlock; + + return(SMSUCCESS); +} +/* +//----- Media_D_WriteSector() ------------------------------------------ +int Media_D_WriteSector(PFDO_DEVICE_EXTENSION fdoExt, DWORD start,WORD count,BYTE *buf) +{ + int i; + WORD len, bn; + SSFDCTYPE_T aa = (SSFDCTYPE_T ) &Ssfdc; + ADDRESS_T bb = (ADDRESS_T) &Media; + + //if (Check_D_MediaPower()) + // return(ErrCode); + // + //if (Check_D_MediaFmt(fdoExt)) + // return(ErrCode); + // + //if (Check_D_MediaWP()) + // return(ErrCode); + + if (Conv_D_MediaAddr(fdoExt, start)) + return(ErrCode); + + //ENE_Print("Media_D_WriteSector --- Sector = %x\n", Media.Sector); + if (Check_D_FirstSect()) + { + if (Media_D_CopyBlockHead(fdoExt)) + { + ErrCode = ERR_WriteFault; + return(ErrCode); + } + } + + while(1) + { + if (!Check_D_FirstSect()) + { + if (Assign_D_WriteBlock()) + return(ErrCode); + } + + len = Ssfdc.MaxSectors - Media.Sector; + if (count > len) + bn = len; + else + bn = count; + //for(i=0;iDrive_IsSWLED) +// { +// if (enable) +// Led_D_TernOn(); +// else +// Led_D_TernOff(); +// } +//} +// +////----- Led_D_TernOn() ------------------------------------------------- +//void Led_D_TernOn(void) +//{ +// if (Check_D_CardStsChg()) +// MediaChange=ERROR; +// +// Cnt_D_LedOn(); +//} +// +////----- Led_D_TernOff() ------------------------------------------------ +//void Led_D_TernOff(void) +//{ +// if (Check_D_CardStsChg()) +// MediaChange=ERROR; +// +// Cnt_D_LedOff(); +//} +// +////SmartMedia Logical Format Subroutine +////----- Check_D_LogCHS() ----------------------------------------------- +//int Check_D_LogCHS(WORD *c,BYTE *h,BYTE *s) +//{ +// switch(Ssfdc.Model) { +// case SSFDC1MB: *c=125; *h= 4; *s= 4; break; +// case SSFDC2MB: *c=125; *h= 4; *s= 8; break; +// case SSFDC4MB: *c=250; *h= 4; *s= 8; break; +// case SSFDC8MB: *c=250; *h= 4; *s=16; break; +// case SSFDC16MB: *c=500; *h= 4; *s=16; break; +// case SSFDC32MB: *c=500; *h= 8; *s=16; break; +// case SSFDC64MB: *c=500; *h= 8; *s=32; break; +// case SSFDC128MB: *c=500; *h=16; *s=32; break; +// default: *c= 0; *h= 0; *s= 0; ErrCode = ERR_NoSmartMedia; return(ERROR); +// } +// +// return(SMSUCCESS); +//} +// +////Power Control & Media Exist Check Subroutine +////----- Initialize_D_Media() ------------------------------------------- +//void Initialize_D_Media(void) +//{ +// ErrCode = NO_ERROR; +// MediaChange = ERROR; +// SectCopyMode = COMPLETED; +// Cnt_D_Reset(); +//} +// +////----- PowerOff_D_Media() --------------------------------------------- +//void PowerOff_D_Media(void) +//{ +// Cnt_D_PowerOff(); +//} +// +////----- Check_D_MediaPower() ------------------------------------------- +//int Check_D_MediaPower(void) +//{ +// //usleep(56*1024); +// if (Check_D_CardStsChg()) +// MediaChange = ERROR; +// //usleep(56*1024); +// if ((!Check_D_CntPower())&&(!MediaChange)) // ¦³ power & Media ¨S³Q change, «h return success +// return(SMSUCCESS); +// //usleep(56*1024); +// +// if (Check_D_CardExist()) // Check if card is not exist, return err +// { +// ErrCode = ERR_NoSmartMedia; +// MediaChange = ERROR; +// return(ERROR); +// } +// //usleep(56*1024); +// if (Cnt_D_PowerOn()) +// { +// ErrCode = ERR_NoSmartMedia; +// MediaChange = ERROR; +// return(ERROR); +// } +// //usleep(56*1024); +// Ssfdc_D_Reset(fdoExt); +// //usleep(56*1024); +// return(SMSUCCESS); +//} +// +////-----Check_D_MediaExist() -------------------------------------------- +//int Check_D_MediaExist(void) +//{ +// if (Check_D_CardStsChg()) +// MediaChange = ERROR; +// +// if (!Check_D_CardExist()) +// { +// if (!MediaChange) +// return(SMSUCCESS); +// +// ErrCode = ERR_ChangedMedia; +// return(ERROR); +// } +// +// ErrCode = ERR_NoSmartMedia; +// +// return(ERROR); +//} +// +////----- Check_D_MediaWP() ---------------------------------------------- +//int Check_D_MediaWP(void) +//{ +// if (Ssfdc.Attribute &MWP) +// { +// ErrCode = ERR_WrtProtect; +// return(ERROR); +// } +// +// return(SMSUCCESS); +//} +*/ +//SmartMedia Physical Format Test Subroutine +//----- Check_D_MediaFmt() --------------------------------------------- +int Check_D_MediaFmt(struct us_data *us) +{ + pr_info("Check_D_MediaFmt\n"); + //ULONG i,j, result=FALSE, zone,block; + + //usleep(56*1024); + if (!MediaChange) + return(SMSUCCESS); + + MediaChange = ERROR; + SectCopyMode = COMPLETED; + + //usleep(56*1024); + if (Set_D_PhyFmtValue(us)) + { + ErrCode = ERR_UnknownMedia; + return(ERROR); + } + + //usleep(56*1024); + if (Search_D_CIS(us)) + { + ErrCode = ERR_IllegalFmt; + return(ERROR); + } + + + MediaChange = SMSUCCESS; + return(SMSUCCESS); +} +/* +////----- Check_D_BlockIsFull() ---------------------------------- +//void Check_D_BlockIsFull() +//{ +// ULONG i, block; +// +// if (IsXDCompliance || IsSSFDCCompliance) +// { +// // If the blocks are full then return write-protect. +// block = Ssfdc.MaxBlocks/8; +// for (Media.Zone=0; Media.ZoneChildDeviceObject) return(ERROR); +// //pdoExt = fdoExt->ChildDeviceObject->DeviceExtension; +// +// Ssfdc_D_ReadID(idcode, READ_ID_1); +// + //if (Set_D_SsfdcModel(idcode[1])) + if (Set_D_SsfdcModel(us->SM_DeviceID)) + return(ERROR); + +// //Use Multi-function pin to differentiate SM and xD. +// UserDefData_1 = ReadPCIReg(fdoExt->BusID, fdoExt->DevID, fdoExt->FuncID, PCI_REG_USER_DEF) & 0x80; +// if (UserDefData_1) +// { +// if ( READ_PORT_BYTE(SM_REG_INT_STATUS) & 0x80 ) fdoExt->DiskType = DISKTYPE_XD; +// if ( READ_PORT_BYTE(SM_REG_INT_STATUS) & 0x40 ) fdoExt->DiskType = DISKTYPE_SM; +// +// if ( IsXDCompliance && (fdoExt->DiskType == DISKTYPE_XD) ) +// { +// Ssfdc_D_ReadID(idcode, READ_ID_3); +// if (idcode[2] != 0xB5) +// return(ERROR); +// } +// } +// +// //Use GPIO to differentiate SM and xD. +// UserDefData_2 = ReadPCIReg(fdoExt->BusID, fdoExt->DevID, fdoExt->FuncID, PCI_REG_USER_DEF) >> 8; +// if ( UserDefData_2 ) +// { +// Data = ReadPCIReg(fdoExt->BusID, fdoExt->DevID, 0, 0xAC); +// +// mask = 1 << (UserDefData_2-1); +// // 1 : xD , 0 : SM +// if ( Data & mask) +// fdoExt->DiskType = DISKTYPE_XD; +// else +// fdoExt->DiskType = DISKTYPE_SM; +// +// if ( IsXDCompliance && (fdoExt->DiskType == DISKTYPE_XD) ) +// { +// Ssfdc_D_ReadID(idcode, READ_ID_3); +// if (idcode[2] != 0xB5) +// return(ERROR); +// } +// } +// +// if ( !(UserDefData_1 | UserDefData_2) ) +// { +// // Use UserDefine Register to differentiate SM and xD. +// Ssfdc_D_ReadID(idcode, READ_ID_3); +// +// if (idcode[2] == 0xB5) +// fdoExt->DiskType = DISKTYPE_XD; +// else +// { +// if (!IsXDCompliance) +// fdoExt->DiskType = DISKTYPE_SM; +// else +// return(ERROR); +// } +// +// if (fdoExt->UserDef_DiskType == 0x04) fdoExt->DiskType = DISKTYPE_XD; +// if (fdoExt->UserDef_DiskType == 0x08) fdoExt->DiskType = DISKTYPE_SM; +// } +// +// if (!fdoExt->UserDef_DisableWP) +// { +// if (fdoExt->DiskType == DISKTYPE_SM) +// { +// if (Check_D_SsfdcWP()) +// Ssfdc.Attribute|=WP; +// } +// } + + return(SMSUCCESS); +} + +//----- Search_D_CIS() ------------------------------------------------- +int Search_D_CIS(struct us_data *us) +{ + //SSFDCTYPE_T aa = (SSFDCTYPE_T ) &Ssfdc; + //ADDRESS_T bb = (ADDRESS_T) &Media; + + Media.Zone=0; Media.Sector=0; + + for (Media.PhyBlock=0; Media.PhyBlock<(Ssfdc.MaxBlocks-Ssfdc.MaxLogBlocks-1); Media.PhyBlock++) + { + if (Ssfdc_D_ReadRedtData(us, Redundant)) + { + Ssfdc_D_Reset(us); + return(ERROR); + } + + if (!Check_D_FailBlock(Redundant)) + break; + } + + if (Media.PhyBlock==(Ssfdc.MaxBlocks-Ssfdc.MaxLogBlocks-1)) + { + Ssfdc_D_Reset(us); + return(ERROR); + } + + while (Media.Sector=Ssfdc.MaxLogBlocks) + continue; + + if (Log2Phy[Media.Zone][Media.LogBlock]==NO_ASSIGN) + { + Log2Phy[Media.Zone][Media.LogBlock]=Media.PhyBlock; + continue; + } + + phyblock = Media.PhyBlock; + logblock = Media.LogBlock; + Media.Sector = (BYTE)(Ssfdc.MaxSectors-1); + + if (Ssfdc_D_ReadRedtData(us, Redundant)) + { Ssfdc_D_Reset(us); return(ERROR); } + + if (!Load_D_LogBlockAddr(Redundant)) + { + if (Media.LogBlock==logblock) + { + Media.PhyBlock=Log2Phy[Media.Zone][logblock]; + + if (Ssfdc_D_ReadRedtData(us, Redundant)) + { Ssfdc_D_Reset(us); return(ERROR); } + + Media.PhyBlock=phyblock; + + if (!Load_D_LogBlockAddr(Redundant)) + { + if (Media.LogBlock!=logblock) + { + Media.PhyBlock=Log2Phy[Media.Zone][logblock]; + Log2Phy[Media.Zone][logblock]=phyblock; + } + } + else + { + Media.PhyBlock=Log2Phy[Media.Zone][logblock]; + Log2Phy[Media.Zone][logblock]=phyblock; + } + } + } + + Media.Sector=0; + +// here Not yet +//#ifdef L2P_ERR_ERASE +// if (!(Ssfdc.Attribute &MWP)) +// { +// Ssfdc_D_Reset(fdoExt); +// if (Ssfdc_D_EraseBlock(fdoExt)) +// return(ERROR); +// +// if (Ssfdc_D_CheckStatus()) +// { +// if (MarkFail_D_PhyOneBlock()) +// return(ERROR); +// } +// else +// Clr_D_Bit(Assign[Media.Zone],Media.PhyBlock); +// } +//#else +// Ssfdc.Attribute|=MWP; +//#endif + Media.PhyBlock=phyblock; + + } // End for (Media.PhyBlockErrCode = STATUS_CMD_FAIL; +// +// Init_D_SmartMedia(); +// +// if (Check_D_MediaPower()) +// return (ErrCode==ERR_NoSmartMedia) ? STATUS_CMD_NO_MEDIA : STATUS_CMD_FAIL; +// +// if (Set_D_PhyFmtValue(fdoExt)) +// return STATUS_CMD_FAIL; +// +// //usleep(56*1024); +// if (Search_D_CIS(fdoExt)) +// return STATUS_CMD_FAIL; +// +// if (Check_D_MediaWP()) +// return STATUS_CMD_MEDIA_WP; +// +// pParamOut->PageSize = Ssfdc.MaxSectors; +// pParamOut->BlockSize = Ssfdc.MaxBlocks; +// pParamOut->ZoneSize = Ssfdc.MaxZones; +// +// return STATUS_CMD_SUCCESS; +//}*/ diff --git a/drivers/staging/keucr/smilsub.c b/drivers/staging/keucr/smilsub.c new file mode 100644 index 00000000..d4dd5ed5 --- /dev/null +++ b/drivers/staging/keucr/smilsub.c @@ -0,0 +1,693 @@ +#include +#include "usb.h" +#include "scsiglue.h" +#include "transport.h" + +#include "smcommon.h" +#include "smil.h" + +void _Set_D_SsfdcRdCmd(BYTE); +void _Set_D_SsfdcRdAddr(BYTE); +void _Set_D_SsfdcRdChip(void); +void _Set_D_SsfdcRdStandby(void); +void _Start_D_SsfdcRdHwECC(void); +void _Stop_D_SsfdcRdHwECC(void); +void _Load_D_SsfdcRdHwECC(BYTE); +void _Set_D_SsfdcWrCmd(BYTE); +void _Set_D_SsfdcWrAddr(BYTE); +void _Set_D_SsfdcWrBlock(void); +void _Set_D_SsfdcWrStandby(void); +void _Start_D_SsfdcWrHwECC(void); +void _Load_D_SsfdcWrHwECC(BYTE); +int _Check_D_SsfdcBusy(WORD); +int _Check_D_SsfdcStatus(void); +void _Reset_D_SsfdcErr(void); +void _Read_D_SsfdcBuf(BYTE *); +void _Write_D_SsfdcBuf(BYTE *); +void _Read_D_SsfdcByte(BYTE *); +void _ReadRedt_D_SsfdcBuf(BYTE *); +void _WriteRedt_D_SsfdcBuf(BYTE *); +BYTE _Check_D_DevCode(BYTE); + +void _Set_D_ECCdata(BYTE, BYTE *); +void _Calc_D_ECCdata(BYTE *); + + +struct SSFDCTYPE Ssfdc; +struct ADDRESS Media; +struct CIS_AREA CisArea; + +static BYTE EccBuf[6]; +extern PBYTE SMHostAddr; +extern DWORD ErrXDCode; + +extern WORD ReadBlock; +extern WORD WriteBlock; + + + +#define EVEN 0 /* Even Page for 256byte/page */ +#define ODD 1 /* Odd Page for 256byte/page */ + + +/* SmartMedia Redundant buffer data Control Subroutine + *----- Check_D_DataBlank() -------------------------------------------- + */ +int Check_D_DataBlank(BYTE *redundant) +{ + char i; + + for (i = 0; i < REDTSIZE; i++) + if (*redundant++ != 0xFF) + return ERROR; + + return SMSUCCESS; +} + +/* ----- Check_D_FailBlock() -------------------------------------------- */ +int Check_D_FailBlock(BYTE *redundant) +{ + redundant += REDT_BLOCK; + + if (*redundant == 0xFF) + return SMSUCCESS; + if (!*redundant) + return ERROR; + if (hweight8(*redundant) < 7) + return ERROR; + + return SMSUCCESS; +} + +/* ----- Check_D_DataStatus() ------------------------------------------- */ +int Check_D_DataStatus(BYTE *redundant) +{ + redundant += REDT_DATA; + + if (*redundant == 0xFF) + return SMSUCCESS; + if (!*redundant) { + ErrXDCode = ERR_DataStatus; + return ERROR; + } else + ErrXDCode = NO_ERROR; + + if (hweight8(*redundant) < 5) + return ERROR; + + return SMSUCCESS; +} + +/* ----- Load_D_LogBlockAddr() ------------------------------------------ */ +int Load_D_LogBlockAddr(BYTE *redundant) +{ + WORD addr1, addr2; + + addr1 = (WORD)*(redundant + REDT_ADDR1H)*0x0100 + (WORD)*(redundant + REDT_ADDR1L); + addr2 = (WORD)*(redundant + REDT_ADDR2H)*0x0100 + (WORD)*(redundant + REDT_ADDR2L); + + if (addr1 == addr2) + if ((addr1 & 0xF000) == 0x1000) { + Media.LogBlock = (addr1 & 0x0FFF) / 2; + return SMSUCCESS; + } + + if (hweight16((WORD)(addr1^addr2)) != 0x01) + return ERROR; + + if ((addr1 & 0xF000) == 0x1000) + if (!(hweight16(addr1) & 0x01)) { + Media.LogBlock = (addr1 & 0x0FFF) / 2; + return SMSUCCESS; + } + + if ((addr2 & 0xF000) == 0x1000) + if (!(hweight16(addr2) & 0x01)) { + Media.LogBlock = (addr2 & 0x0FFF) / 2; + return SMSUCCESS; + } + + return ERROR; +} + +/* ----- Clr_D_RedundantData() ------------------------------------------ */ +void Clr_D_RedundantData(BYTE *redundant) +{ + char i; + + for (i = 0; i < REDTSIZE; i++) + *(redundant + i) = 0xFF; +} + +/* ----- Set_D_LogBlockAddr() ------------------------------------------- */ +void Set_D_LogBlockAddr(BYTE *redundant) +{ + WORD addr; + + *(redundant + REDT_BLOCK) = 0xFF; + *(redundant + REDT_DATA) = 0xFF; + addr = Media.LogBlock*2 + 0x1000; + + if ((hweight16(addr) % 2)) + addr++; + + *(redundant + REDT_ADDR1H) = *(redundant + REDT_ADDR2H) = (BYTE)(addr / 0x0100); + *(redundant + REDT_ADDR1L) = *(redundant + REDT_ADDR2L) = (BYTE)addr; +} + +/*----- Set_D_FailBlock() ---------------------------------------------- */ +void Set_D_FailBlock(BYTE *redundant) +{ + char i; + for (i = 0; i < REDTSIZE; i++) + *redundant++ = (BYTE)((i == REDT_BLOCK) ? 0xF0 : 0xFF); +} + +/* ----- Set_D_DataStaus() ---------------------------------------------- */ +void Set_D_DataStaus(BYTE *redundant) +{ + redundant += REDT_DATA; + *redundant = 0x00; +} + +/* SmartMedia Function Command Subroutine + * 6250 CMD 6 + */ +/* ----- Ssfdc_D_Reset() ------------------------------------------------ */ +void Ssfdc_D_Reset(struct us_data *us) +{ + return; +} + +/* ----- Ssfdc_D_ReadCisSect() ------------------------------------------ */ +int Ssfdc_D_ReadCisSect(struct us_data *us, BYTE *buf, BYTE *redundant) +{ + BYTE zone, sector; + WORD block; + + zone = Media.Zone; block = Media.PhyBlock; sector = Media.Sector; + Media.Zone = 0; + Media.PhyBlock = CisArea.PhyBlock; + Media.Sector = CisArea.Sector; + + if (Ssfdc_D_ReadSect(us, buf, redundant)) { + Media.Zone = zone; Media.PhyBlock = block; Media.Sector = sector; + return ERROR; + } + + Media.Zone = zone; Media.PhyBlock = block; Media.Sector = sector; + return SMSUCCESS; +} + +/* 6250 CMD 1 */ +/* ----- Ssfdc_D_ReadSect() --------------------------------------------- */ +int Ssfdc_D_ReadSect(struct us_data *us, BYTE *buf, BYTE *redundant) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + WORD addr; + + result = ENE_LoadBinCode(us, SM_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + printk("Load SM RW Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + addr = (WORD)Media.Zone*Ssfdc.MaxBlocks + Media.PhyBlock; + addr = addr*(WORD)Ssfdc.MaxSectors + Media.Sector; + + /* Read sect data */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x02; + bcb->CDB[4] = (BYTE)addr; + bcb->CDB[3] = (BYTE)(addr / 0x0100); + bcb->CDB[2] = Media.Zone / 2; + + result = ENE_SendScsiCmd(us, FDIR_READ, buf, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* Read redundant */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x10; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x03; + bcb->CDB[4] = (BYTE)addr; + bcb->CDB[3] = (BYTE)(addr / 0x0100); + bcb->CDB[2] = Media.Zone / 2; + bcb->CDB[8] = 0; + bcb->CDB[9] = 1; + + result = ENE_SendScsiCmd(us, FDIR_READ, redundant, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* ----- Ssfdc_D_ReadBlock() --------------------------------------------- */ +int Ssfdc_D_ReadBlock(struct us_data *us, WORD count, BYTE *buf, BYTE *redundant) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + WORD addr; + + result = ENE_LoadBinCode(us, SM_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + printk("Load SM RW Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + addr = (WORD)Media.Zone*Ssfdc.MaxBlocks + Media.PhyBlock; + addr = addr*(WORD)Ssfdc.MaxSectors + Media.Sector; + + /* Read sect data */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200*count; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x02; + bcb->CDB[4] = (BYTE)addr; + bcb->CDB[3] = (BYTE)(addr / 0x0100); + bcb->CDB[2] = Media.Zone / 2; + + result = ENE_SendScsiCmd(us, FDIR_READ, buf, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* Read redundant */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x10; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x03; + bcb->CDB[4] = (BYTE)addr; + bcb->CDB[3] = (BYTE)(addr / 0x0100); + bcb->CDB[2] = Media.Zone / 2; + bcb->CDB[8] = 0; + bcb->CDB[9] = 1; + + result = ENE_SendScsiCmd(us, FDIR_READ, redundant, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + + +/* ----- Ssfdc_D_CopyBlock() -------------------------------------------- */ +int Ssfdc_D_CopyBlock(struct us_data *us, WORD count, BYTE *buf, BYTE *redundant) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + WORD ReadAddr, WriteAddr; + + result = ENE_LoadBinCode(us, SM_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + printk("Load SM RW Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + ReadAddr = (WORD)Media.Zone*Ssfdc.MaxBlocks + ReadBlock; + ReadAddr = ReadAddr*(WORD)Ssfdc.MaxSectors; + WriteAddr = (WORD)Media.Zone*Ssfdc.MaxBlocks + WriteBlock; + WriteAddr = WriteAddr*(WORD)Ssfdc.MaxSectors; + + /* Write sect data */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200*count; + bcb->Flags = 0x00; + bcb->CDB[0] = 0xF0; + bcb->CDB[1] = 0x08; + bcb->CDB[7] = (BYTE)WriteAddr; + bcb->CDB[6] = (BYTE)(WriteAddr / 0x0100); + bcb->CDB[5] = Media.Zone / 2; + bcb->CDB[8] = *(redundant + REDT_ADDR1H); + bcb->CDB[9] = *(redundant + REDT_ADDR1L); + bcb->CDB[10] = Media.Sector; + + if (ReadBlock != NO_ASSIGN) { + bcb->CDB[4] = (BYTE)ReadAddr; + bcb->CDB[3] = (BYTE)(ReadAddr / 0x0100); + bcb->CDB[2] = Media.Zone / 2; + } else + bcb->CDB[11] = 1; + + result = ENE_SendScsiCmd(us, FDIR_WRITE, buf, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* ----- Ssfdc_D_WriteSectForCopy() ------------------------------------- */ +int Ssfdc_D_WriteSectForCopy(struct us_data *us, BYTE *buf, BYTE *redundant) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + WORD addr; + + result = ENE_LoadBinCode(us, SM_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + printk("Load SM RW Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + + addr = (WORD)Media.Zone*Ssfdc.MaxBlocks + Media.PhyBlock; + addr = addr*(WORD)Ssfdc.MaxSectors + Media.Sector; + + /* Write sect data */ + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200; + bcb->Flags = 0x00; + bcb->CDB[0] = 0xF0; + bcb->CDB[1] = 0x04; + bcb->CDB[7] = (BYTE)addr; + bcb->CDB[6] = (BYTE)(addr / 0x0100); + bcb->CDB[5] = Media.Zone / 2; + bcb->CDB[8] = *(redundant + REDT_ADDR1H); + bcb->CDB[9] = *(redundant + REDT_ADDR1L); + + result = ENE_SendScsiCmd(us, FDIR_WRITE, buf, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* 6250 CMD 5 */ +/* ----- Ssfdc_D_EraseBlock() ------------------------------------------- */ +int Ssfdc_D_EraseBlock(struct us_data *us) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + WORD addr; + + result = ENE_LoadBinCode(us, SM_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + printk("Load SM RW Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + addr = (WORD)Media.Zone*Ssfdc.MaxBlocks + Media.PhyBlock; + addr = addr*(WORD)Ssfdc.MaxSectors; + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x200; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xF2; + bcb->CDB[1] = 0x06; + bcb->CDB[7] = (BYTE)addr; + bcb->CDB[6] = (BYTE)(addr / 0x0100); + bcb->CDB[5] = Media.Zone / 2; + + result = ENE_SendScsiCmd(us, FDIR_READ, NULL, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* 6250 CMD 2 */ +/*----- Ssfdc_D_ReadRedtData() ----------------------------------------- */ +int Ssfdc_D_ReadRedtData(struct us_data *us, BYTE *redundant) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + WORD addr; + BYTE *buf; + + result = ENE_LoadBinCode(us, SM_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + printk("Load SM RW Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + addr = (WORD)Media.Zone*Ssfdc.MaxBlocks + Media.PhyBlock; + addr = addr*(WORD)Ssfdc.MaxSectors + Media.Sector; + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x10; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xF1; + bcb->CDB[1] = 0x03; + bcb->CDB[4] = (BYTE)addr; + bcb->CDB[3] = (BYTE)(addr / 0x0100); + bcb->CDB[2] = Media.Zone / 2; + bcb->CDB[8] = 0; + bcb->CDB[9] = 1; + + buf = kmalloc(0x10, GFP_KERNEL); + result = ENE_SendScsiCmd(us, FDIR_READ, buf, 0); + memcpy(redundant, buf, 0x10); + kfree(buf); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* 6250 CMD 4 */ +/* ----- Ssfdc_D_WriteRedtData() ---------------------------------------- */ +int Ssfdc_D_WriteRedtData(struct us_data *us, BYTE *redundant) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + int result; + WORD addr; + + result = ENE_LoadBinCode(us, SM_RW_PATTERN); + if (result != USB_STOR_XFER_GOOD) { + printk("Load SM RW Code Fail !!\n"); + return USB_STOR_TRANSPORT_ERROR; + } + + addr = (WORD)Media.Zone*Ssfdc.MaxBlocks + Media.PhyBlock; + addr = addr*(WORD)Ssfdc.MaxSectors + Media.Sector; + + memset(bcb, 0, sizeof(struct bulk_cb_wrap)); + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = 0x10; + bcb->Flags = 0x80; + bcb->CDB[0] = 0xF2; + bcb->CDB[1] = 0x05; + bcb->CDB[7] = (BYTE)addr; + bcb->CDB[6] = (BYTE)(addr / 0x0100); + bcb->CDB[5] = Media.Zone / 2; + bcb->CDB[8] = *(redundant + REDT_ADDR1H); + bcb->CDB[9] = *(redundant + REDT_ADDR1L); + + result = ENE_SendScsiCmd(us, FDIR_READ, NULL, 0); + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* ----- Ssfdc_D_CheckStatus() ------------------------------------------ */ +int Ssfdc_D_CheckStatus(void) +{ + return SMSUCCESS; +} + + + +/* SmartMedia ID Code Check & Mode Set Subroutine + * ----- Set_D_SsfdcModel() --------------------------------------------- + */ +int Set_D_SsfdcModel(BYTE dcode) +{ + switch (_Check_D_DevCode(dcode)) { + case SSFDC1MB: + Ssfdc.Model = SSFDC1MB; + Ssfdc.Attribute = FLASH | AD3CYC | BS16 | PS256; + Ssfdc.MaxZones = 1; + Ssfdc.MaxBlocks = 256; + Ssfdc.MaxLogBlocks = 250; + Ssfdc.MaxSectors = 8; + break; + case SSFDC2MB: + Ssfdc.Model = SSFDC2MB; + Ssfdc.Attribute = FLASH | AD3CYC | BS16 | PS256; + Ssfdc.MaxZones = 1; + Ssfdc.MaxBlocks = 512; + Ssfdc.MaxLogBlocks = 500; + Ssfdc.MaxSectors = 8; + break; + case SSFDC4MB: + Ssfdc.Model = SSFDC4MB; + Ssfdc.Attribute = FLASH | AD3CYC | BS16 | PS512; + Ssfdc.MaxZones = 1; + Ssfdc.MaxBlocks = 512; + Ssfdc.MaxLogBlocks = 500; + Ssfdc.MaxSectors = 16; + break; + case SSFDC8MB: + Ssfdc.Model = SSFDC8MB; + Ssfdc.Attribute = FLASH | AD3CYC | BS16 | PS512; + Ssfdc.MaxZones = 1; + Ssfdc.MaxBlocks = 1024; + Ssfdc.MaxLogBlocks = 1000; + Ssfdc.MaxSectors = 16; + break; + case SSFDC16MB: + Ssfdc.Model = SSFDC16MB; + Ssfdc.Attribute = FLASH | AD3CYC | BS32 | PS512; + Ssfdc.MaxZones = 1; + Ssfdc.MaxBlocks = 1024; + Ssfdc.MaxLogBlocks = 1000; + Ssfdc.MaxSectors = 32; + break; + case SSFDC32MB: + Ssfdc.Model = SSFDC32MB; + Ssfdc.Attribute = FLASH | AD3CYC | BS32 | PS512; + Ssfdc.MaxZones = 2; + Ssfdc.MaxBlocks = 1024; + Ssfdc.MaxLogBlocks = 1000; + Ssfdc.MaxSectors = 32; + break; + case SSFDC64MB: + Ssfdc.Model = SSFDC64MB; + Ssfdc.Attribute = FLASH | AD4CYC | BS32 | PS512; + Ssfdc.MaxZones = 4; + Ssfdc.MaxBlocks = 1024; + Ssfdc.MaxLogBlocks = 1000; + Ssfdc.MaxSectors = 32; + break; + case SSFDC128MB: + Ssfdc.Model = SSFDC128MB; + Ssfdc.Attribute = FLASH | AD4CYC | BS32 | PS512; + Ssfdc.MaxZones = 8; + Ssfdc.MaxBlocks = 1024; + Ssfdc.MaxLogBlocks = 1000; + Ssfdc.MaxSectors = 32; + break; + case SSFDC256MB: + Ssfdc.Model = SSFDC256MB; + Ssfdc.Attribute = FLASH | AD4CYC | BS32 | PS512; + Ssfdc.MaxZones = 16; + Ssfdc.MaxBlocks = 1024; + Ssfdc.MaxLogBlocks = 1000; + Ssfdc.MaxSectors = 32; + break; + case SSFDC512MB: + Ssfdc.Model = SSFDC512MB; + Ssfdc.Attribute = FLASH | AD4CYC | BS32 | PS512; + Ssfdc.MaxZones = 32; + Ssfdc.MaxBlocks = 1024; + Ssfdc.MaxLogBlocks = 1000; + Ssfdc.MaxSectors = 32; + break; + case SSFDC1GB: + Ssfdc.Model = SSFDC1GB; + Ssfdc.Attribute = FLASH | AD4CYC | BS32 | PS512; + Ssfdc.MaxZones = 64; + Ssfdc.MaxBlocks = 1024; + Ssfdc.MaxLogBlocks = 1000; + Ssfdc.MaxSectors = 32; + break; + case SSFDC2GB: + Ssfdc.Model = SSFDC2GB; + Ssfdc.Attribute = FLASH | AD4CYC | BS32 | PS512; + Ssfdc.MaxZones = 128; + Ssfdc.MaxBlocks = 1024; + Ssfdc.MaxLogBlocks = 1000; + Ssfdc.MaxSectors = 32; + break; + default: + Ssfdc.Model = NOSSFDC; + return ERROR; + } + + return SMSUCCESS; +} + +/* ----- _Check_D_DevCode() --------------------------------------------- */ +BYTE _Check_D_DevCode(BYTE dcode) +{ + switch (dcode) { + case 0x6E: + case 0xE8: + case 0xEC: return SSFDC1MB; /* 8Mbit (1M) NAND */ + case 0x64: + case 0xEA: return SSFDC2MB; /* 16Mbit (2M) NAND */ + case 0x6B: + case 0xE3: + case 0xE5: return SSFDC4MB; /* 32Mbit (4M) NAND */ + case 0xE6: return SSFDC8MB; /* 64Mbit (8M) NAND */ + case 0x73: return SSFDC16MB; /* 128Mbit (16M)NAND */ + case 0x75: return SSFDC32MB; /* 256Mbit (32M)NAND */ + case 0x76: return SSFDC64MB; /* 512Mbit (64M)NAND */ + case 0x79: return SSFDC128MB; /* 1Gbit(128M)NAND */ + case 0x71: return SSFDC256MB; + case 0xDC: return SSFDC512MB; + case 0xD3: return SSFDC1GB; + case 0xD5: return SSFDC2GB; + default: return NOSSFDC; + } +} + + + + +/* SmartMedia ECC Control Subroutine + * ----- Check_D_ReadError() ---------------------------------------------- + */ +int Check_D_ReadError(BYTE *redundant) +{ + return SMSUCCESS; +} + +/* ----- Check_D_Correct() ---------------------------------------------- */ +int Check_D_Correct(BYTE *buf, BYTE *redundant) +{ + return SMSUCCESS; +} + +/* ----- Check_D_CISdata() ---------------------------------------------- */ +int Check_D_CISdata(BYTE *buf, BYTE *redundant) +{ + BYTE cis[] = {0x01, 0x03, 0xD9, 0x01, 0xFF, 0x18, 0x02, + 0xDF, 0x01, 0x20}; + + int cis_len = sizeof(cis); + + if (!IsSSFDCCompliance && !IsXDCompliance) + return SMSUCCESS; + + if (!memcmp(redundant + 0x0D, EccBuf, 3)) + return memcmp(buf, cis, cis_len); + + if (!_Correct_D_SwECC(buf, redundant + 0x0D, EccBuf)) + return memcmp(buf, cis, cis_len); + + buf += 0x100; + if (!memcmp(redundant + 0x08, EccBuf + 0x03, 3)) + return memcmp(buf, cis, cis_len); + + if (!_Correct_D_SwECC(buf, redundant + 0x08, EccBuf + 0x03)) + return memcmp(buf, cis, cis_len); + + return ERROR; +} + +/* ----- Set_D_RightECC() ---------------------------------------------- */ +void Set_D_RightECC(BYTE *redundant) +{ + /* Driver ECC Check */ + return; +} + + diff --git a/drivers/staging/keucr/smscsi.c b/drivers/staging/keucr/smscsi.c new file mode 100644 index 00000000..58b55557 --- /dev/null +++ b/drivers/staging/keucr/smscsi.c @@ -0,0 +1,188 @@ +#include +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "scsiglue.h" +#include "transport.h" +#include "smil.h" + +int SM_SCSI_Test_Unit_Ready(struct us_data *us, struct scsi_cmnd *srb); +int SM_SCSI_Inquiry(struct us_data *us, struct scsi_cmnd *srb); +int SM_SCSI_Mode_Sense(struct us_data *us, struct scsi_cmnd *srb); +int SM_SCSI_Start_Stop(struct us_data *us, struct scsi_cmnd *srb); +int SM_SCSI_Read_Capacity(struct us_data *us, struct scsi_cmnd *srb); +int SM_SCSI_Read(struct us_data *us, struct scsi_cmnd *srb); +int SM_SCSI_Write(struct us_data *us, struct scsi_cmnd *srb); + +extern PBYTE SMHostAddr; +extern DWORD ErrXDCode; + +/* ----- SM_SCSIIrp() -------------------------------------------------- */ +int SM_SCSIIrp(struct us_data *us, struct scsi_cmnd *srb) +{ + int result; + + us->SrbStatus = SS_SUCCESS; + switch (srb->cmnd[0]) { + case TEST_UNIT_READY: + result = SM_SCSI_Test_Unit_Ready(us, srb); + break; /* 0x00 */ + case INQUIRY: + result = SM_SCSI_Inquiry(us, srb); + break; /* 0x12 */ + case MODE_SENSE: + result = SM_SCSI_Mode_Sense(us, srb); + break; /* 0x1A */ + case READ_CAPACITY: + result = SM_SCSI_Read_Capacity(us, srb); + break; /* 0x25 */ + case READ_10: + result = SM_SCSI_Read(us, srb); + break; /* 0x28 */ + case WRITE_10: + result = SM_SCSI_Write(us, srb); + break; /* 0x2A */ + + default: + us->SrbStatus = SS_ILLEGAL_REQUEST; + result = USB_STOR_TRANSPORT_FAILED; + break; + } + return result; +} + +/* ----- SM_SCSI_Test_Unit_Ready() -------------------------------------------------- */ +int SM_SCSI_Test_Unit_Ready(struct us_data *us, struct scsi_cmnd *srb) +{ + if (us->SM_Status.Insert && us->SM_Status.Ready) + return USB_STOR_TRANSPORT_GOOD; + else { + ENE_SMInit(us); + return USB_STOR_TRANSPORT_GOOD; + } + + return USB_STOR_TRANSPORT_GOOD; +} + +/* ----- SM_SCSI_Inquiry() -------------------------------------------------- */ +int SM_SCSI_Inquiry(struct us_data *us, struct scsi_cmnd *srb) +{ + BYTE data_ptr[36] = {0x00, 0x80, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, 0x53, 0x42, 0x32, 0x2E, 0x30, 0x20, 0x20, 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x31, 0x30, 0x30}; + + usb_stor_set_xfer_buf(us, data_ptr, 36, srb, TO_XFER_BUF); + return USB_STOR_TRANSPORT_GOOD; +} + + +/* ----- SM_SCSI_Mode_Sense() -------------------------------------------------- */ +int SM_SCSI_Mode_Sense(struct us_data *us, struct scsi_cmnd *srb) +{ + BYTE mediaNoWP[12] = {0x0b, 0x00, 0x00, 0x08, 0x00, 0x00, 0x71, 0xc0, 0x00, 0x00, 0x02, 0x00}; + BYTE mediaWP[12] = {0x0b, 0x00, 0x80, 0x08, 0x00, 0x00, 0x71, 0xc0, 0x00, 0x00, 0x02, 0x00}; + + if (us->SM_Status.WtP) + usb_stor_set_xfer_buf(us, mediaWP, 12, srb, TO_XFER_BUF); + else + usb_stor_set_xfer_buf(us, mediaNoWP, 12, srb, TO_XFER_BUF); + + + return USB_STOR_TRANSPORT_GOOD; +} + +/* ----- SM_SCSI_Read_Capacity() -------------------------------------------------- */ +int SM_SCSI_Read_Capacity(struct us_data *us, struct scsi_cmnd *srb) +{ + unsigned int offset = 0; + struct scatterlist *sg = NULL; + DWORD bl_num; + WORD bl_len; + BYTE buf[8]; + + printk("SM_SCSI_Read_Capacity\n"); + + bl_len = 0x200; + bl_num = Ssfdc.MaxLogBlocks * Ssfdc.MaxSectors * Ssfdc.MaxZones - 1; + + us->bl_num = bl_num; + printk("bl_len = %x\n", bl_len); + printk("bl_num = %x\n", bl_num); + + buf[0] = (bl_num >> 24) & 0xff; + buf[1] = (bl_num >> 16) & 0xff; + buf[2] = (bl_num >> 8) & 0xff; + buf[3] = (bl_num >> 0) & 0xff; + buf[4] = (bl_len >> 24) & 0xff; + buf[5] = (bl_len >> 16) & 0xff; + buf[6] = (bl_len >> 8) & 0xff; + buf[7] = (bl_len >> 0) & 0xff; + + usb_stor_access_xfer_buf(us, buf, 8, srb, &sg, &offset, TO_XFER_BUF); + + return USB_STOR_TRANSPORT_GOOD; +} + +/* ----- SM_SCSI_Read() -------------------------------------------------- */ +int SM_SCSI_Read(struct us_data *us, struct scsi_cmnd *srb) +{ + int result = 0; + PBYTE Cdb = srb->cmnd; + DWORD bn = ((Cdb[2] << 24) & 0xff000000) | ((Cdb[3] << 16) & 0x00ff0000) | + ((Cdb[4] << 8) & 0x0000ff00) | ((Cdb[5] << 0) & 0x000000ff); + WORD blen = ((Cdb[7] << 8) & 0xff00) | ((Cdb[8] << 0) & 0x00ff); + DWORD blenByte = blen * 0x200; + void *buf; + + + if (bn > us->bl_num) + return USB_STOR_TRANSPORT_ERROR; + + buf = kmalloc(blenByte, GFP_KERNEL); + if (buf == NULL) + return USB_STOR_TRANSPORT_ERROR; + result = Media_D_ReadSector(us, bn, blen, buf); + usb_stor_set_xfer_buf(us, buf, blenByte, srb, TO_XFER_BUF); + kfree(buf); + + if (!result) + return USB_STOR_TRANSPORT_GOOD; + else + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + +/* ----- SM_SCSI_Write() -------------------------------------------------- */ +int SM_SCSI_Write(struct us_data *us, struct scsi_cmnd *srb) +{ + int result = 0; + PBYTE Cdb = srb->cmnd; + DWORD bn = ((Cdb[2] << 24) & 0xff000000) | ((Cdb[3] << 16) & 0x00ff0000) | + ((Cdb[4] << 8) & 0x0000ff00) | ((Cdb[5] << 0) & 0x000000ff); + WORD blen = ((Cdb[7] << 8) & 0xff00) | ((Cdb[8] << 0) & 0x00ff); + DWORD blenByte = blen * 0x200; + void *buf; + + + if (bn > us->bl_num) + return USB_STOR_TRANSPORT_ERROR; + + buf = kmalloc(blenByte, GFP_KERNEL); + if (buf == NULL) + return USB_STOR_TRANSPORT_ERROR; + usb_stor_set_xfer_buf(us, buf, blenByte, srb, FROM_XFER_BUF); + result = Media_D_CopySector(us, bn, blen, buf); + kfree(buf); + + if (!result) + return USB_STOR_TRANSPORT_GOOD; + else + return USB_STOR_TRANSPORT_ERROR; + + return USB_STOR_TRANSPORT_GOOD; +} + diff --git a/drivers/staging/keucr/transport.c b/drivers/staging/keucr/transport.c new file mode 100644 index 00000000..1a8837df --- /dev/null +++ b/drivers/staging/keucr/transport.c @@ -0,0 +1,823 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "scsiglue.h" +#include "transport.h" + +/*********************************************************************** + * Data transfer routines + ***********************************************************************/ +/* + * usb_stor_blocking_completion() + */ +static void usb_stor_blocking_completion(struct urb *urb) +{ + struct completion *urb_done_ptr = urb->context; + + /* pr_info("transport --- usb_stor_blocking_completion\n"); */ + complete(urb_done_ptr); +} + +/* + * usb_stor_msg_common() + */ +static int usb_stor_msg_common(struct us_data *us, int timeout) +{ + struct completion urb_done; + long timeleft; + int status; + + /* pr_info("transport --- usb_stor_msg_common\n"); */ + if (test_bit(US_FLIDX_ABORTING, &us->dflags)) + return -EIO; + + init_completion(&urb_done); + + us->current_urb->context = &urb_done; + us->current_urb->actual_length = 0; + us->current_urb->error_count = 0; + us->current_urb->status = 0; + + us->current_urb->transfer_flags = 0; + if (us->current_urb->transfer_buffer == us->iobuf) + us->current_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + us->current_urb->transfer_dma = us->iobuf_dma; + us->current_urb->setup_dma = us->cr_dma; + + status = usb_submit_urb(us->current_urb, GFP_NOIO); + if (status) + return status; + + set_bit(US_FLIDX_URB_ACTIVE, &us->dflags); + + if (test_bit(US_FLIDX_ABORTING, &us->dflags)) { + if (test_and_clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags)) { + /* pr_info("-- cancelling URB\n"); */ + usb_unlink_urb(us->current_urb); + } + } + + timeleft = wait_for_completion_interruptible_timeout(&urb_done, + timeout ? : MAX_SCHEDULE_TIMEOUT); + clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags); + + if (timeleft <= 0) { + /* pr_info("%s -- cancelling URB\n", + timeleft == 0 ? "Timeout" : "Signal"); */ + usb_kill_urb(us->current_urb); + } + + return us->current_urb->status; +} + +/* + * usb_stor_control_msg() + */ +int usb_stor_control_msg(struct us_data *us, unsigned int pipe, + u8 request, u8 requesttype, u16 value, u16 index, + void *data, u16 size, int timeout) +{ + int status; + + /* pr_info("transport --- usb_stor_control_msg\n"); */ + + /* fill in the devrequest structure */ + us->cr->bRequestType = requesttype; + us->cr->bRequest = request; + us->cr->wValue = cpu_to_le16(value); + us->cr->wIndex = cpu_to_le16(index); + us->cr->wLength = cpu_to_le16(size); + + /* fill and submit the URB */ + usb_fill_control_urb(us->current_urb, us->pusb_dev, pipe, + (unsigned char *) us->cr, data, size, + usb_stor_blocking_completion, NULL); + status = usb_stor_msg_common(us, timeout); + + /* return the actual length of the data transferred if no error */ + if (status == 0) + status = us->current_urb->actual_length; + return status; +} + +/* + * usb_stor_clear_halt() + */ +int usb_stor_clear_halt(struct us_data *us, unsigned int pipe) +{ + int result; + int endp = usb_pipeendpoint(pipe); + + /* pr_info("transport --- usb_stor_clear_halt\n"); */ + if (usb_pipein(pipe)) + endp |= USB_DIR_IN; + + result = usb_stor_control_msg(us, us->send_ctrl_pipe, + USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, + USB_ENDPOINT_HALT, endp, + NULL, 0, 3*HZ); + + /* reset the endpoint toggle */ + if (result >= 0) + /* usb_settoggle(us->pusb_dev, usb_pipeendpoint(pipe), + usb_pipeout(pipe), 0); */ + usb_reset_endpoint(us->pusb_dev, endp); + + return result; +} + +/* + * interpret_urb_result() + */ +static int interpret_urb_result(struct us_data *us, unsigned int pipe, + unsigned int length, int result, unsigned int partial) +{ + /* pr_info("transport --- interpret_urb_result\n"); */ + switch (result) { + /* no error code; did we send all the data? */ + case 0: + if (partial != length) { + /* pr_info("-- short transfer\n"); */ + return USB_STOR_XFER_SHORT; + } + /* pr_info("-- transfer complete\n"); */ + return USB_STOR_XFER_GOOD; + case -EPIPE: + if (usb_pipecontrol(pipe)) { + /* pr_info("-- stall on control pipe\n"); */ + return USB_STOR_XFER_STALLED; + } + /* pr_info("clearing endpoint halt for pipe 0x%x\n", pipe); */ + if (usb_stor_clear_halt(us, pipe) < 0) + return USB_STOR_XFER_ERROR; + return USB_STOR_XFER_STALLED; + case -EOVERFLOW: + /* pr_info("-- babble\n"); */ + return USB_STOR_XFER_LONG; + case -ECONNRESET: + /* pr_info("-- transfer cancelled\n"); */ + return USB_STOR_XFER_ERROR; + case -EREMOTEIO: + /* pr_info("-- short read transfer\n"); */ + return USB_STOR_XFER_SHORT; + case -EIO: + /* pr_info("-- abort or disconnect in progress\n"); */ + return USB_STOR_XFER_ERROR; + default: + /* pr_info("-- unknown error\n"); */ + return USB_STOR_XFER_ERROR; + } +} + +/* + * usb_stor_bulk_transfer_buf() + */ +int usb_stor_bulk_transfer_buf(struct us_data *us, unsigned int pipe, + void *buf, unsigned int length, unsigned int *act_len) +{ + int result; + + /* pr_info("transport --- usb_stor_bulk_transfer_buf\n"); */ + + /* fill and submit the URB */ + usb_fill_bulk_urb(us->current_urb, us->pusb_dev, pipe, buf, + length, usb_stor_blocking_completion, NULL); + result = usb_stor_msg_common(us, 0); + + /* store the actual length of the data transferred */ + if (act_len) + *act_len = us->current_urb->actual_length; + + return interpret_urb_result(us, pipe, length, result, + us->current_urb->actual_length); +} + +/* + * usb_stor_bulk_transfer_sglist() + */ +static int usb_stor_bulk_transfer_sglist(struct us_data *us, unsigned int pipe, + struct scatterlist *sg, int num_sg, unsigned int length, + unsigned int *act_len) +{ + int result; + + /* pr_info("transport --- usb_stor_bulk_transfer_sglist\n"); */ + if (test_bit(US_FLIDX_ABORTING, &us->dflags)) + return USB_STOR_XFER_ERROR; + + /* initialize the scatter-gather request block */ + result = usb_sg_init(&us->current_sg, us->pusb_dev, pipe, 0, + sg, num_sg, length, GFP_NOIO); + if (result) { + /* pr_info("usb_sg_init returned %d\n", result); */ + return USB_STOR_XFER_ERROR; + } + + /* since the block has been initialized successfully, + it's now okay to cancel it */ + set_bit(US_FLIDX_SG_ACTIVE, &us->dflags); + + /* did an abort/disconnect occur during the submission? */ + if (test_bit(US_FLIDX_ABORTING, &us->dflags)) { + /* cancel the request, if it hasn't been cancelled already */ + if (test_and_clear_bit(US_FLIDX_SG_ACTIVE, &us->dflags)) { + /* pr_info("-- cancelling sg request\n"); */ + usb_sg_cancel(&us->current_sg); + } + } + + /* wait for the completion of the transfer */ + usb_sg_wait(&us->current_sg); + clear_bit(US_FLIDX_SG_ACTIVE, &us->dflags); + + result = us->current_sg.status; + if (act_len) + *act_len = us->current_sg.bytes; + + return interpret_urb_result(us, pipe, length, + result, us->current_sg.bytes); +} + +/* + * usb_stor_bulk_srb() + */ +int usb_stor_bulk_srb(struct us_data *us, unsigned int pipe, + struct scsi_cmnd *srb) +{ + unsigned int partial; + int result = usb_stor_bulk_transfer_sglist(us, pipe, scsi_sglist(srb), + scsi_sg_count(srb), scsi_bufflen(srb), + &partial); + + scsi_set_resid(srb, scsi_bufflen(srb) - partial); + return result; +} + +/* + * usb_stor_bulk_transfer_sg() + */ +int usb_stor_bulk_transfer_sg(struct us_data *us, unsigned int pipe, + void *buf, unsigned int length_left, int use_sg, int *residual) +{ + int result; + unsigned int partial; + + /* pr_info("transport --- usb_stor_bulk_transfer_sg\n"); */ + /* are we scatter-gathering? */ + if (use_sg) { + /* use the usb core scatter-gather primitives */ + result = usb_stor_bulk_transfer_sglist(us, pipe, + (struct scatterlist *) buf, use_sg, + length_left, &partial); + length_left -= partial; + } else { + /* no scatter-gather, just make the request */ + result = usb_stor_bulk_transfer_buf(us, pipe, buf, + length_left, &partial); + length_left -= partial; + } + + /* store the residual and return the error code */ + if (residual) + *residual = length_left; + return result; +} + +/*********************************************************************** + * Transport routines + ***********************************************************************/ +/* + * usb_stor_invoke_transport() + */ +void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int need_auto_sense; + int result; + + /* pr_info("transport --- usb_stor_invoke_transport\n"); */ + usb_stor_print_cmd(srb); + /* send the command to the transport layer */ + scsi_set_resid(srb, 0); + result = us->transport(srb, us); /* usb_stor_Bulk_transport; */ + + /* if the command gets aborted by the higher layers, + we need to short-circuit all other processing */ + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { + /* pr_info("-- command was aborted\n"); */ + srb->result = DID_ABORT << 16; + goto Handle_Errors; + } + + /* if there is a transport error, reset and don't auto-sense */ + if (result == USB_STOR_TRANSPORT_ERROR) { + /* pr_info("-- transport indicates error, resetting\n"); */ + srb->result = DID_ERROR << 16; + goto Handle_Errors; + } + + /* if the transport provided its own sense data, don't auto-sense */ + if (result == USB_STOR_TRANSPORT_NO_SENSE) { + srb->result = SAM_STAT_CHECK_CONDITION; + return; + } + + srb->result = SAM_STAT_GOOD; + + /* Determine if we need to auto-sense */ + need_auto_sense = 0; + + if ((us->protocol == USB_PR_CB || us->protocol == USB_PR_DPCM_USB) && + srb->sc_data_direction != DMA_FROM_DEVICE) { + /* pr_info("-- CB transport device requiring auto-sense\n"); */ + need_auto_sense = 1; + } + + if (result == USB_STOR_TRANSPORT_FAILED) { + /* pr_info("-- transport indicates command failure\n"); */ + need_auto_sense = 1; + } + + /* Now, if we need to do the auto-sense, let's do it */ + if (need_auto_sense) { + int temp_result; + struct scsi_eh_save ses; + + pr_info("Issuing auto-REQUEST_SENSE\n"); + + scsi_eh_prep_cmnd(srb, &ses, NULL, 0, US_SENSE_SIZE); + + /* we must do the protocol translation here */ + if (us->subclass == USB_SC_RBC || + us->subclass == USB_SC_SCSI || + us->subclass == USB_SC_CYP_ATACB) { + srb->cmd_len = 6; + } else { + srb->cmd_len = 12; + } + /* issue the auto-sense command */ + scsi_set_resid(srb, 0); + temp_result = us->transport(us->srb, us); + + /* let's clean up right away */ + scsi_eh_restore_cmnd(srb, &ses); + + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { + /* pr_info("-- auto-sense aborted\n"); */ + srb->result = DID_ABORT << 16; + goto Handle_Errors; + } + if (temp_result != USB_STOR_TRANSPORT_GOOD) { + /* pr_info("-- auto-sense failure\n"); */ + srb->result = DID_ERROR << 16; + if (!(us->fflags & US_FL_SCM_MULT_TARG)) + goto Handle_Errors; + return; + } + + /* set the result so the higher layers expect this data */ + srb->result = SAM_STAT_CHECK_CONDITION; + + if (result == USB_STOR_TRANSPORT_GOOD && + (srb->sense_buffer[2] & 0xaf) == 0 && + srb->sense_buffer[12] == 0 && + srb->sense_buffer[13] == 0) { + srb->result = SAM_STAT_GOOD; + srb->sense_buffer[0] = 0x0; + } + } + + /* Did we transfer less than the minimum amount required? */ + if (srb->result == SAM_STAT_GOOD && scsi_bufflen(srb) - + scsi_get_resid(srb) < srb->underflow) + srb->result = (DID_ERROR << 16); + /* v02 | (SUGGEST_RETRY << 24); */ + + return; + +Handle_Errors: + scsi_lock(us_to_host(us)); + set_bit(US_FLIDX_RESETTING, &us->dflags); + clear_bit(US_FLIDX_ABORTING, &us->dflags); + scsi_unlock(us_to_host(us)); + + mutex_unlock(&us->dev_mutex); + result = usb_stor_port_reset(us); + mutex_lock(&us->dev_mutex); + + if (result < 0) { + scsi_lock(us_to_host(us)); + usb_stor_report_device_reset(us); + scsi_unlock(us_to_host(us)); + us->transport_reset(us); + } + clear_bit(US_FLIDX_RESETTING, &us->dflags); +} + +/* + * ENE_stor_invoke_transport() + */ +void ENE_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int result = 0; + + /* pr_info("transport --- ENE_stor_invoke_transport\n"); */ + usb_stor_print_cmd(srb); + /* send the command to the transport layer */ + scsi_set_resid(srb, 0); + if (!(us->SM_Status.Ready)) + result = ENE_InitMedia(us); + + if (us->Power_IsResum == true) { + result = ENE_InitMedia(us); + us->Power_IsResum = false; + } + + if (us->SM_Status.Ready) + result = SM_SCSIIrp(us, srb); + + /* if the command gets aborted by the higher layers, + we need to short-circuit all other processing */ + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) { + /* pr_info("-- command was aborted\n"); */ + srb->result = DID_ABORT << 16; + goto Handle_Errors; + } + + /* if there is a transport error, reset and don't auto-sense */ + if (result == USB_STOR_TRANSPORT_ERROR) { + /* pr_info("-- transport indicates error, resetting\n"); */ + srb->result = DID_ERROR << 16; + goto Handle_Errors; + } + + /* if the transport provided its own sense data, don't auto-sense */ + if (result == USB_STOR_TRANSPORT_NO_SENSE) { + srb->result = SAM_STAT_CHECK_CONDITION; + return; + } + + srb->result = SAM_STAT_GOOD; + if (result == USB_STOR_TRANSPORT_FAILED) { + /* pr_info("-- transport indicates command failure\n"); */ + /* need_auto_sense = 1; */ + BuildSenseBuffer(srb, us->SrbStatus); + srb->result = SAM_STAT_CHECK_CONDITION; + } + + /* Did we transfer less than the minimum amount required? */ + if (srb->result == SAM_STAT_GOOD && scsi_bufflen(srb) - + scsi_get_resid(srb) < srb->underflow) + srb->result = (DID_ERROR << 16); + /* v02 | (SUGGEST_RETRY << 24); */ + + return; + +Handle_Errors: + scsi_lock(us_to_host(us)); + set_bit(US_FLIDX_RESETTING, &us->dflags); + clear_bit(US_FLIDX_ABORTING, &us->dflags); + scsi_unlock(us_to_host(us)); + + mutex_unlock(&us->dev_mutex); + result = usb_stor_port_reset(us); + mutex_lock(&us->dev_mutex); + + if (result < 0) { + scsi_lock(us_to_host(us)); + usb_stor_report_device_reset(us); + scsi_unlock(us_to_host(us)); + us->transport_reset(us); + } + clear_bit(US_FLIDX_RESETTING, &us->dflags); +} + +/* + * BuildSenseBuffer() + */ +void BuildSenseBuffer(struct scsi_cmnd *srb, int SrbStatus) +{ + BYTE *buf = srb->sense_buffer; + BYTE asc; + + pr_info("transport --- BuildSenseBuffer\n"); + switch (SrbStatus) { + case SS_NOT_READY: + asc = 0x3a; + break; /* sense key = 0x02 */ + case SS_MEDIUM_ERR: + asc = 0x0c; + break; /* sense key = 0x03 */ + case SS_ILLEGAL_REQUEST: + asc = 0x20; + break; /* sense key = 0x05 */ + default: + asc = 0x00; + break; /* ?? */ + } + + memset(buf, 0, 18); + buf[0x00] = 0xf0; + buf[0x02] = SrbStatus; + buf[0x07] = 0x0b; + buf[0x0c] = asc; +} + +/* + * usb_stor_stop_transport() + */ +void usb_stor_stop_transport(struct us_data *us) +{ + /* pr_info("transport --- usb_stor_stop_transport\n"); */ + + if (test_and_clear_bit(US_FLIDX_URB_ACTIVE, &us->dflags)) { + /* pr_info("-- cancelling URB\n"); */ + usb_unlink_urb(us->current_urb); + } + + if (test_and_clear_bit(US_FLIDX_SG_ACTIVE, &us->dflags)) { + /* pr_info("-- cancelling sg request\n"); */ + usb_sg_cancel(&us->current_sg); + } +} + +/* + * usb_stor_Bulk_max_lun() + */ +int usb_stor_Bulk_max_lun(struct us_data *us) +{ + int result; + + /* pr_info("transport --- usb_stor_Bulk_max_lun\n"); */ + /* issue the command */ + us->iobuf[0] = 0; + result = usb_stor_control_msg(us, us->recv_ctrl_pipe, + US_BULK_GET_MAX_LUN, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + 0, us->ifnum, us->iobuf, 1, HZ); + + /* pr_info("GetMaxLUN command result is %d, data is %d\n", + result, us->iobuf[0]); */ + + /* if we have a successful request, return the result */ + if (result > 0) + return us->iobuf[0]; + + return 0; +} + +/* + * usb_stor_Bulk_transport() + */ +int usb_stor_Bulk_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + struct bulk_cb_wrap *bcb = (struct bulk_cb_wrap *) us->iobuf; + struct bulk_cs_wrap *bcs = (struct bulk_cs_wrap *) us->iobuf; + unsigned int transfer_length = scsi_bufflen(srb); + unsigned int residue; + int result; + int fake_sense = 0; + unsigned int cswlen; + unsigned int cbwlen = US_BULK_CB_WRAP_LEN; + + /* pr_info("transport --- usb_stor_Bulk_transport\n"); */ + /* Take care of BULK32 devices; set extra byte to 0 */ + if (unlikely(us->fflags & US_FL_BULK32)) { + cbwlen = 32; + us->iobuf[31] = 0; + } + + /* set up the command wrapper */ + bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); + bcb->DataTransferLength = cpu_to_le32(transfer_length); + bcb->Flags = srb->sc_data_direction == DMA_FROM_DEVICE ? 1 << 7 : 0; + bcb->Tag = ++us->tag; + bcb->Lun = srb->device->lun; + if (us->fflags & US_FL_SCM_MULT_TARG) + bcb->Lun |= srb->device->id << 4; + bcb->Length = srb->cmd_len; + + /* copy the command payload */ + memset(bcb->CDB, 0, sizeof(bcb->CDB)); + memcpy(bcb->CDB, srb->cmnd, bcb->Length); + + /* send command */ + /* send it to out endpoint */ + /* pr_info("Bulk Command S 0x%x T 0x%x L %d F %d Trg %d LUN %d CL %d\n", + le32_to_cpu(bcb->Signature), bcb->Tag, + le32_to_cpu(bcb->DataTransferLength), bcb->Flags, + (bcb->Lun >> 4), (bcb->Lun & 0x0F), + bcb->Length); */ + result = usb_stor_bulk_transfer_buf(us, us->send_bulk_pipe, + bcb, cbwlen, NULL); + /* pr_info("Bulk command transfer result=%d\n", result); */ + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + if (unlikely(us->fflags & US_FL_GO_SLOW)) + udelay(125); + + /* R/W data */ + if (transfer_length) { + unsigned int pipe; + if (srb->sc_data_direction == DMA_FROM_DEVICE) + pipe = us->recv_bulk_pipe; + else + pipe = us->send_bulk_pipe; + + result = usb_stor_bulk_srb(us, pipe, srb); + /* pr_info("Bulk data transfer result 0x%x\n", result); */ + if (result == USB_STOR_XFER_ERROR) + return USB_STOR_TRANSPORT_ERROR; + + if (result == USB_STOR_XFER_LONG) + fake_sense = 1; + } + + /* get CSW for device status */ + /* pr_info("Attempting to get CSW...\n"); */ + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, bcs, + US_BULK_CS_WRAP_LEN, &cswlen); + + if (result == USB_STOR_XFER_SHORT && cswlen == 0) { + /* pr_info("Received 0-length CSW; retrying...\n"); */ + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, bcs, + US_BULK_CS_WRAP_LEN, &cswlen); + } + + /* did the attempt to read the CSW fail? */ + if (result == USB_STOR_XFER_STALLED) { + /* get the status again */ + /* pr_info("Attempting to get CSW (2nd try)...\n"); */ + result = usb_stor_bulk_transfer_buf(us, us->recv_bulk_pipe, bcs, + US_BULK_CS_WRAP_LEN, NULL); + } + + /* if we still have a failure at this point, we're in trouble */ + /* pr_info("Bulk status result = %d\n", result); */ + if (result != USB_STOR_XFER_GOOD) + return USB_STOR_TRANSPORT_ERROR; + + /* check bulk status */ + residue = le32_to_cpu(bcs->Residue); + /* pr_info("Bulk Status S 0x%x T 0x%x R %u Stat 0x%x\n", + le32_to_cpu(bcs->Signature), + bcs->Tag, residue, bcs->Status); */ + if (!(bcs->Tag == us->tag || + (us->fflags & US_FL_BULK_IGNORE_TAG)) || + bcs->Status > US_BULK_STAT_PHASE) { + /* pr_info("Bulk logical error\n"); */ + return USB_STOR_TRANSPORT_ERROR; + } + + if (!us->bcs_signature) { + us->bcs_signature = bcs->Signature; + /* if (us->bcs_signature != cpu_to_le32(US_BULK_CS_SIGN)) */ + /* pr_info("Learnt BCS signature 0x%08X\n", + le32_to_cpu(us->bcs_signature)); */ + } else if (bcs->Signature != us->bcs_signature) { + /* pr_info("Signature mismatch: got %08X, expecting %08X\n", + le32_to_cpu(bcs->Signature), + le32_to_cpu(us->bcs_signature)); */ + return USB_STOR_TRANSPORT_ERROR; + } + + /* try to compute the actual residue, based on how much data + * was really transferred and what the device tells us */ + if (residue && !(us->fflags & US_FL_IGNORE_RESIDUE)) { + + /* Heuristically detect devices that generate bogus residues + * by seeing what happens with INQUIRY and READ CAPACITY + * commands. + */ + if (bcs->Status == US_BULK_STAT_OK && + scsi_get_resid(srb) == 0 && + ((srb->cmnd[0] == INQUIRY && + transfer_length == 36) || + (srb->cmnd[0] == READ_CAPACITY && + transfer_length == 8))) { + us->fflags |= US_FL_IGNORE_RESIDUE; + + } else { + residue = min(residue, transfer_length); + scsi_set_resid(srb, max(scsi_get_resid(srb), + (int) residue)); + } + } + + /* based on the status code, we report good or bad */ + switch (bcs->Status) { + case US_BULK_STAT_OK: + if (fake_sense) { + memcpy(srb->sense_buffer, usb_stor_sense_invalidCDB, + sizeof(usb_stor_sense_invalidCDB)); + return USB_STOR_TRANSPORT_NO_SENSE; + } + return USB_STOR_TRANSPORT_GOOD; + + case US_BULK_STAT_FAIL: + return USB_STOR_TRANSPORT_FAILED; + + case US_BULK_STAT_PHASE: + return USB_STOR_TRANSPORT_ERROR; + } + return USB_STOR_TRANSPORT_ERROR; +} + +/*********************************************************************** + * Reset routines + ***********************************************************************/ +/* + * usb_stor_reset_common() + */ +static int usb_stor_reset_common(struct us_data *us, + u8 request, u8 requesttype, + u16 value, u16 index, void *data, u16 size) +{ + int result; + int result2; + + /* pr_info("transport --- usb_stor_reset_common\n"); */ + if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) { + /* pr_info("No reset during disconnect\n"); */ + return -EIO; + } + + result = usb_stor_control_msg(us, us->send_ctrl_pipe, + request, requesttype, value, index, data, size, 5*HZ); + + if (result < 0) { + /* pr_info("Soft reset failed: %d\n", result); */ + return result; + } + + wait_event_interruptible_timeout(us->delay_wait, + test_bit(US_FLIDX_DISCONNECTING, &us->dflags), HZ*6); + + if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) { + /* pr_info("Reset interrupted by disconnect\n"); */ + return -EIO; + } + + /* pr_info("Soft reset: clearing bulk-in endpoint halt\n"); */ + result = usb_stor_clear_halt(us, us->recv_bulk_pipe); + + /* pr_info("Soft reset: clearing bulk-out endpoint halt\n"); */ + result2 = usb_stor_clear_halt(us, us->send_bulk_pipe); + + /* return a result code based on the result of the clear-halts */ + if (result >= 0) + result = result2; + /* if (result < 0) */ + /* pr_info("Soft reset failed\n"); */ + /* else */ + /* pr_info("Soft reset done\n"); */ + return result; +} + +/* + * usb_stor_Bulk_reset() + */ +int usb_stor_Bulk_reset(struct us_data *us) +{ + /* pr_info("transport --- usb_stor_Bulk_reset\n"); */ + return usb_stor_reset_common(us, US_BULK_RESET_REQUEST, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, us->ifnum, NULL, 0); +} + +/* + * usb_stor_port_reset() + */ +int usb_stor_port_reset(struct us_data *us) +{ + int result; + + /* pr_info("transport --- usb_stor_port_reset\n"); */ + result = usb_lock_device_for_reset(us->pusb_dev, us->pusb_intf); + if (result < 0) + pr_info("unable to lock device for reset: %d\n", result); + else { + /* Were we disconnected while waiting for the lock? */ + if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) { + result = -EIO; + /* pr_info("No reset during disconnect\n"); */ + } else { + result = usb_reset_device(us->pusb_dev); + /* pr_info("usb_reset_composite_device returns %d\n", + result); */ + } + usb_unlock_device(us->pusb_dev); + } + return result; +} + + diff --git a/drivers/staging/keucr/transport.h b/drivers/staging/keucr/transport.h new file mode 100644 index 00000000..2a11a983 --- /dev/null +++ b/drivers/staging/keucr/transport.h @@ -0,0 +1,74 @@ +#ifndef _TRANSPORT_H_ +#define _TRANSPORT_H_ + +#include + +/* usb_stor_bulk_transfer_xxx() return codes, in order of severity */ +#define USB_STOR_XFER_GOOD 0 /* good transfer */ +#define USB_STOR_XFER_SHORT 1 /* transferred less than expected */ +#define USB_STOR_XFER_STALLED 2 /* endpoint stalled */ +#define USB_STOR_XFER_LONG 3 /* device tried to send too much */ +#define USB_STOR_XFER_ERROR 4 /* transfer died in the middle */ + +/* Transport return codes */ +#define USB_STOR_TRANSPORT_GOOD 0 /* Transport good, command good */ +#define USB_STOR_TRANSPORT_FAILED 1 /* Transport good, command failed */ +#define USB_STOR_TRANSPORT_NO_SENSE 2 /* Command failed, no auto-sense */ +#define USB_STOR_TRANSPORT_ERROR 3 /* Transport bad (i.e. device dead) */ + +/* + * We used to have USB_STOR_XFER_ABORTED and USB_STOR_TRANSPORT_ABORTED + * return codes. But now the transport and low-level transfer routines + * treat an abort as just another error (-ENOENT for a cancelled URB). + * It is up to the invoke_transport() function to test for aborts and + * distinguish them from genuine communication errors. + */ + +/* CBI accept device specific command */ +#define US_CBI_ADSC 0 +extern int usb_stor_Bulk_transport(struct scsi_cmnd *, struct us_data*); +extern int usb_stor_Bulk_max_lun(struct us_data *); +extern int usb_stor_Bulk_reset(struct us_data *); +extern void usb_stor_print_cmd(struct scsi_cmnd *); +extern void usb_stor_invoke_transport(struct scsi_cmnd *, struct us_data*); +extern void usb_stor_stop_transport(struct us_data *); +extern int usb_stor_control_msg(struct us_data *us, unsigned int pipe, + u8 request, u8 requesttype, u16 value, u16 index, + void *data, u16 size, int timeout); +extern int usb_stor_clear_halt(struct us_data *us, unsigned int pipe); +extern int usb_stor_bulk_transfer_buf(struct us_data *us, unsigned int pipe, + void *buf, unsigned int length, unsigned int *act_len); +extern int usb_stor_bulk_transfer_sg(struct us_data *us, unsigned int pipe, + void *buf, unsigned int length, int use_sg, int *residual); +extern int usb_stor_bulk_srb(struct us_data *us, unsigned int pipe, + struct scsi_cmnd *srb); +extern int usb_stor_port_reset(struct us_data *us); + +/* Protocol handling routines */ +enum xfer_buf_dir {TO_XFER_BUF, FROM_XFER_BUF}; +extern unsigned int usb_stor_access_xfer_buf(struct us_data*, + unsigned char *buffer, unsigned int buflen, struct scsi_cmnd *srb, + struct scatterlist **, unsigned int *offset, enum xfer_buf_dir dir); +extern void usb_stor_set_xfer_buf(struct us_data*, unsigned char *buffer, + unsigned int buflen, struct scsi_cmnd *srb, + unsigned int dir); + +/* + * ENE scsi function + */ +extern void ENE_stor_invoke_transport(struct scsi_cmnd *, struct us_data *); +extern int ENE_InitMedia(struct us_data *); +extern int ENE_SMInit(struct us_data *); +extern int ENE_SendScsiCmd(struct us_data*, BYTE, void*, int); +extern int ENE_LoadBinCode(struct us_data*, BYTE); +extern int ENE_Read_BYTE(struct us_data*, WORD index, void *buf); +extern int ENE_Read_Data(struct us_data*, void *buf, unsigned int length); +extern int ENE_Write_Data(struct us_data*, void *buf, unsigned int length); +extern void BuildSenseBuffer(struct scsi_cmnd *, int); + +/* + * ENE scsi function + */ +extern int SM_SCSIIrp(struct us_data *us, struct scsi_cmnd *srb); + +#endif diff --git a/drivers/staging/keucr/usb.c b/drivers/staging/keucr/usb.c new file mode 100644 index 00000000..48330340 --- /dev/null +++ b/drivers/staging/keucr/usb.c @@ -0,0 +1,704 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "usb.h" +#include "scsiglue.h" +#include "smil.h" +#include "transport.h" + +/* Some informational data */ +MODULE_AUTHOR("Domao"); +MODULE_DESCRIPTION("ENE USB Mass Storage driver for Linux"); +MODULE_LICENSE("GPL"); + +static unsigned int delay_use = 1; + +static struct usb_device_id eucr_usb_ids [] = { + { USB_DEVICE(0x058f, 0x6366) }, + { USB_DEVICE(0x0cf2, 0x6230) }, + { USB_DEVICE(0x0cf2, 0x6250) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE (usb, eucr_usb_ids); + + +#ifdef CONFIG_PM + +static int eucr_suspend(struct usb_interface *iface, pm_message_t message) +{ + struct us_data *us = usb_get_intfdata(iface); + pr_info("--- eucr_suspend ---\n"); + /* Wait until no command is running */ + mutex_lock(&us->dev_mutex); + + //US_DEBUGP("%s\n", __func__); + if (us->suspend_resume_hook) + (us->suspend_resume_hook)(us, US_SUSPEND); + + /* When runtime PM is working, we'll set a flag to indicate + * whether we should autoresume when a SCSI request arrives. */ + // us->Power_IsResum = true; + //us->SD_Status.Ready = 0; + + mutex_unlock(&us->dev_mutex); + return 0; +} +//EXPORT_SYMBOL_GPL(eucr_suspend); + +static int eucr_resume(struct usb_interface *iface) +{ + BYTE tmp = 0; + + struct us_data *us = usb_get_intfdata(iface); + pr_info("--- eucr_resume---\n"); + mutex_lock(&us->dev_mutex); + + //US_DEBUGP("%s\n", __func__); + if (us->suspend_resume_hook) + (us->suspend_resume_hook)(us, US_RESUME); + + + mutex_unlock(&us->dev_mutex); + + + us->Power_IsResum = true; + // + //us->SD_Status.Ready = 0; //?? + us->SM_Status = *(PSM_STATUS)&tmp; + + return 0; +} +//EXPORT_SYMBOL_GPL(eucr_resume); +static int eucr_reset_resume(struct usb_interface *iface) +{ + BYTE tmp = 0; + struct us_data *us = usb_get_intfdata(iface); + + pr_info("--- eucr_reset_resume---\n"); + //US_DEBUGP("%s\n", __func__); + + /* Report the reset to the SCSI core */ + usb_stor_report_bus_reset(us); + + /* FIXME: Notify the subdrivers that they need to reinitialize + * the device */ + //ENE_InitMedia(us); + us->Power_IsResum = true; + // + //us->SD_Status.Ready = 0; //?? + us->SM_Status = *(PSM_STATUS)&tmp; + return 0; +} +//EXPORT_SYMBOL_GPL(usb_stor_reset_resume); + +#else + +#define eucr_suspend NULL +#define eucr_resume NULL +#define eucr_reset_resume NULL + +#endif + +//----- eucr_pre_reset() --------------------- +static int eucr_pre_reset(struct usb_interface *iface) +{ + struct us_data *us = usb_get_intfdata(iface); + + pr_info("usb --- eucr_pre_reset\n"); + + /* Make sure no command runs during the reset */ + mutex_lock(&us->dev_mutex); + return 0; +} + +//----- eucr_post_reset() --------------------- +static int eucr_post_reset(struct usb_interface *iface) +{ + struct us_data *us = usb_get_intfdata(iface); + + pr_info("usb --- eucr_post_reset\n"); + + /* Report the reset to the SCSI core */ + usb_stor_report_bus_reset(us); + + mutex_unlock(&us->dev_mutex); + return 0; +} + +//----- fill_inquiry_response() --------------------- +void fill_inquiry_response(struct us_data *us, unsigned char *data, unsigned int data_len) +{ + pr_info("usb --- fill_inquiry_response\n"); + if (data_len<36) // You lose. + return; + + if (data[0]&0x20) + { + memset(data+8,0,28); + } + else + { + u16 bcdDevice = le16_to_cpu(us->pusb_dev->descriptor.bcdDevice); + memcpy(data+8, us->unusual_dev->vendorName, + strlen(us->unusual_dev->vendorName) > 8 ? 8 : + strlen(us->unusual_dev->vendorName)); + memcpy(data+16, us->unusual_dev->productName, + strlen(us->unusual_dev->productName) > 16 ? 16 : + strlen(us->unusual_dev->productName)); + data[32] = 0x30 + ((bcdDevice>>12) & 0x0F); + data[33] = 0x30 + ((bcdDevice>>8) & 0x0F); + data[34] = 0x30 + ((bcdDevice>>4) & 0x0F); + data[35] = 0x30 + ((bcdDevice) & 0x0F); + } + usb_stor_set_xfer_buf(us, data, data_len, us->srb, TO_XFER_BUF); +} + +//----- usb_stor_control_thread() --------------------- +static int usb_stor_control_thread(void * __us) +{ + struct us_data *us = (struct us_data *)__us; + struct Scsi_Host *host = us_to_host(us); + + pr_info("usb --- usb_stor_control_thread\n"); + for(;;) + { + if (wait_for_completion_interruptible(&us->cmnd_ready)) + break; + + /* lock the device pointers */ + mutex_lock(&(us->dev_mutex)); + + /* if the device has disconnected, we are free to exit */ + if (test_bit(US_FLIDX_DISCONNECTING, &us->dflags)) { + mutex_unlock(&us->dev_mutex); + break; + } + + /* lock access to the state */ + scsi_lock(host); + + /* When we are called with no command pending, we're done */ + if (us->srb == NULL) + { + scsi_unlock(host); + mutex_unlock(&us->dev_mutex); + //US_DEBUGP("-- exiting\n"); + break; + } + + /* has the command timed out *already* ? */ + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) + { + us->srb->result = DID_ABORT << 16; + goto SkipForAbort; + } + + scsi_unlock(host); + + if (us->srb->sc_data_direction == DMA_BIDIRECTIONAL) + { + us->srb->result = DID_ERROR << 16; + } + else if (us->srb->device->id && !(us->fflags & US_FL_SCM_MULT_TARG)) + { + us->srb->result = DID_BAD_TARGET << 16; + } + else if (us->srb->device->lun > us->max_lun) + { + us->srb->result = DID_BAD_TARGET << 16; + } + else if ((us->srb->cmnd[0] == INQUIRY) && (us->fflags & US_FL_FIX_INQUIRY)) + { + unsigned char data_ptr[36] = {0x00, 0x80, 0x02, 0x02, 0x1F, 0x00, 0x00, 0x00}; + + fill_inquiry_response(us, data_ptr, 36); + us->srb->result = SAM_STAT_GOOD; + } + else + { + us->proto_handler(us->srb, us); + } + + /* lock access to the state */ + scsi_lock(host); + + /* indicate that the command is done */ + if (us->srb->result != DID_ABORT << 16) + { + us->srb->scsi_done(us->srb); + } + else + { +SkipForAbort: + pr_info("scsi command aborted\n"); + } + + if (test_bit(US_FLIDX_TIMED_OUT, &us->dflags)) + { + complete(&(us->notify)); + + /* Allow USB transfers to resume */ + clear_bit(US_FLIDX_ABORTING, &us->dflags); + clear_bit(US_FLIDX_TIMED_OUT, &us->dflags); + } + + /* finished working on this command */ + us->srb = NULL; + scsi_unlock(host); + + /* unlock the device pointers */ + mutex_unlock(&us->dev_mutex); + } /* for (;;) */ + + /* Wait until we are told to stop */ + for (;;) + { + set_current_state(TASK_INTERRUPTIBLE); + if (kthread_should_stop()) + break; + schedule(); + } + __set_current_state(TASK_RUNNING); + return 0; +} + +//----- associate_dev() --------------------- +static int associate_dev(struct us_data *us, struct usb_interface *intf) +{ + pr_info("usb --- associate_dev\n"); + + /* Fill in the device-related fields */ + us->pusb_dev = interface_to_usbdev(intf); + us->pusb_intf = intf; + us->ifnum = intf->cur_altsetting->desc.bInterfaceNumber; + + /* Store our private data in the interface */ + usb_set_intfdata(intf, us); + + /* Allocate the device-related DMA-mapped buffers */ + us->cr = usb_alloc_coherent(us->pusb_dev, sizeof(*us->cr), GFP_KERNEL, &us->cr_dma); + if (!us->cr) + { + pr_info("usb_ctrlrequest allocation failed\n"); + return -ENOMEM; + } + + us->iobuf = usb_alloc_coherent(us->pusb_dev, US_IOBUF_SIZE, GFP_KERNEL, &us->iobuf_dma); + if (!us->iobuf) + { + pr_info("I/O buffer allocation failed\n"); + return -ENOMEM; + } + + us->sensebuf = kmalloc(US_SENSE_SIZE, GFP_KERNEL); + if (!us->sensebuf) + { + pr_info("Sense buffer allocation failed\n"); + return -ENOMEM; + } + return 0; +} + +//----- get_device_info() --------------------- +static int get_device_info(struct us_data *us, const struct usb_device_id *id) +{ + struct usb_device *dev = us->pusb_dev; + struct usb_interface_descriptor *idesc = &us->pusb_intf->cur_altsetting->desc; + + pr_info("usb --- get_device_info\n"); + + us->subclass = idesc->bInterfaceSubClass; + us->protocol = idesc->bInterfaceProtocol; + us->fflags = USB_US_ORIG_FLAGS(id->driver_info); + us->Power_IsResum = false; + + if (us->fflags & US_FL_IGNORE_DEVICE) + { + pr_info("device ignored\n"); + return -ENODEV; + } + + if (dev->speed != USB_SPEED_HIGH) + us->fflags &= ~US_FL_GO_SLOW; + + return 0; +} + +//----- get_transport() --------------------- +static int get_transport(struct us_data *us) +{ + pr_info("usb --- get_transport\n"); + switch (us->protocol) { + case USB_PR_BULK: + us->transport_name = "Bulk"; + us->transport = usb_stor_Bulk_transport; + us->transport_reset = usb_stor_Bulk_reset; + break; + + default: + return -EIO; + } + /* pr_info("Transport: %s\n", us->transport_name); */ + + /* fix for single-lun devices */ + if (us->fflags & US_FL_SINGLE_LUN) + us->max_lun = 0; + return 0; +} + +//----- get_protocol() --------------------- +static int get_protocol(struct us_data *us) +{ + pr_info("usb --- get_protocol\n"); + pr_info("us->pusb_dev->descriptor.idVendor = %x\n", + us->pusb_dev->descriptor.idVendor); + pr_info("us->pusb_dev->descriptor.idProduct = %x\n", + us->pusb_dev->descriptor.idProduct); + switch (us->subclass) { + case USB_SC_SCSI: + us->protocol_name = "Transparent SCSI"; + if( (us->pusb_dev->descriptor.idVendor == 0x0CF2) && (us->pusb_dev->descriptor.idProduct == 0x6250) ) + us->proto_handler = ENE_stor_invoke_transport; + else + us->proto_handler = usb_stor_invoke_transport; + break; + + default: + return -EIO; + } + /* pr_info("Protocol: %s\n", us->protocol_name); */ + return 0; +} + +//----- get_pipes() --------------------- +static int get_pipes(struct us_data *us) +{ + struct usb_host_interface *altsetting = us->pusb_intf->cur_altsetting; + int i; + struct usb_endpoint_descriptor *ep; + struct usb_endpoint_descriptor *ep_in = NULL; + struct usb_endpoint_descriptor *ep_out = NULL; + struct usb_endpoint_descriptor *ep_int = NULL; + + pr_info("usb --- get_pipes\n"); + + for (i = 0; i < altsetting->desc.bNumEndpoints; i++) + { + ep = &altsetting->endpoint[i].desc; + + if (usb_endpoint_xfer_bulk(ep)) + { + if (usb_endpoint_dir_in(ep)) + { + if (!ep_in) + ep_in = ep; + } + else + { + if (!ep_out) + ep_out = ep; + } + } + else if (usb_endpoint_is_int_in(ep)) + { + if (!ep_int) + ep_int = ep; + } + } + + if (!ep_in || !ep_out || (us->protocol == USB_PR_CBI && !ep_int)) + { + pr_info("Endpoint sanity check failed! Rejecting dev.\n"); + return -EIO; + } + + /* Calculate and store the pipe values */ + us->send_ctrl_pipe = usb_sndctrlpipe(us->pusb_dev, 0); + us->recv_ctrl_pipe = usb_rcvctrlpipe(us->pusb_dev, 0); + us->send_bulk_pipe = usb_sndbulkpipe(us->pusb_dev, ep_out->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + us->recv_bulk_pipe = usb_rcvbulkpipe(us->pusb_dev, ep_in->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + if (ep_int) + { + us->recv_intr_pipe = usb_rcvintpipe(us->pusb_dev, ep_int->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + us->ep_bInterval = ep_int->bInterval; + } + return 0; +} + +//----- usb_stor_acquire_resources() --------------------- +static int usb_stor_acquire_resources(struct us_data *us) +{ + struct task_struct *th; + + pr_info("usb --- usb_stor_acquire_resources\n"); + us->current_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!us->current_urb) + { + pr_info("URB allocation failed\n"); + return -ENOMEM; + } + + /* Start up our control thread */ + th = kthread_run(usb_stor_control_thread, us, "eucr-storage"); + if (IS_ERR(th)) + { + pr_info("Unable to start control thread\n"); + return PTR_ERR(th); + } + us->ctl_thread = th; + + return 0; +} + +//----- usb_stor_release_resources() --------------------- +static void usb_stor_release_resources(struct us_data *us) +{ + pr_info("usb --- usb_stor_release_resources\n"); + + SM_FreeMem(); + + complete(&us->cmnd_ready); + if (us->ctl_thread) + kthread_stop(us->ctl_thread); + + /* Call the destructor routine, if it exists */ + if (us->extra_destructor) + { + pr_info("-- calling extra_destructor()\n"); + us->extra_destructor(us->extra); + } + + /* Free the extra data and the URB */ + kfree(us->extra); + usb_free_urb(us->current_urb); +} + +//----- dissociate_dev() --------------------- +static void dissociate_dev(struct us_data *us) +{ + pr_info("usb --- dissociate_dev\n"); + + kfree(us->sensebuf); + + /* Free the device-related DMA-mapped buffers */ + if (us->cr) + usb_free_coherent(us->pusb_dev, sizeof(*us->cr), us->cr, us->cr_dma); + if (us->iobuf) + usb_free_coherent(us->pusb_dev, US_IOBUF_SIZE, us->iobuf, us->iobuf_dma); + + /* Remove our private data from the interface */ + usb_set_intfdata(us->pusb_intf, NULL); +} + +//----- quiesce_and_remove_host() --------------------- +static void quiesce_and_remove_host(struct us_data *us) +{ + struct Scsi_Host *host = us_to_host(us); + + pr_info("usb --- quiesce_and_remove_host\n"); + + /* If the device is really gone, cut short reset delays */ + if (us->pusb_dev->state == USB_STATE_NOTATTACHED) + set_bit(US_FLIDX_DISCONNECTING, &us->dflags); + + /* Prevent SCSI-scanning (if it hasn't started yet) + * and wait for the SCSI-scanning thread to stop. + */ + set_bit(US_FLIDX_DONT_SCAN, &us->dflags); + wake_up(&us->delay_wait); + wait_for_completion(&us->scanning_done); + + /* Removing the host will perform an orderly shutdown: caches + * synchronized, disks spun down, etc. + */ + scsi_remove_host(host); + + /* Prevent any new commands from being accepted and cut short + * reset delays. + */ + scsi_lock(host); + set_bit(US_FLIDX_DISCONNECTING, &us->dflags); + scsi_unlock(host); + wake_up(&us->delay_wait); +} + +//----- release_everything() --------------------- +static void release_everything(struct us_data *us) +{ + pr_info("usb --- release_everything\n"); + + usb_stor_release_resources(us); + dissociate_dev(us); + scsi_host_put(us_to_host(us)); +} + +//----- usb_stor_scan_thread() --------------------- +static int usb_stor_scan_thread(void * __us) +{ + struct us_data *us = (struct us_data *)__us; + + pr_info("usb --- usb_stor_scan_thread\n"); + pr_info("EUCR : device found at %d\n", us->pusb_dev->devnum); + + set_freezable(); + /* Wait for the timeout to expire or for a disconnect */ + if (delay_use > 0) { + wait_event_freezable_timeout(us->delay_wait, + test_bit(US_FLIDX_DONT_SCAN, &us->dflags), + delay_use * HZ); + } + + /* If the device is still connected, perform the scanning */ + if (!test_bit(US_FLIDX_DONT_SCAN, &us->dflags)) + { + /* For bulk-only devices, determine the max LUN value */ + if (us->protocol == USB_PR_BULK && !(us->fflags & US_FL_SINGLE_LUN)) + { + mutex_lock(&us->dev_mutex); + us->max_lun = usb_stor_Bulk_max_lun(us); + mutex_unlock(&us->dev_mutex); + } + scsi_scan_host(us_to_host(us)); + pr_info("EUCR : device scan complete\n"); + } + complete_and_exit(&us->scanning_done, 0); +} + +//----- eucr_probe() --------------------- +static int eucr_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct Scsi_Host *host; + struct us_data *us; + int result; + BYTE MiscReg03 = 0; + struct task_struct *th; + + pr_info("usb --- eucr_probe\n"); + + host = scsi_host_alloc(&usb_stor_host_template, sizeof(*us)); + if (!host) + { + pr_info("Unable to allocate the scsi host\n"); + return -ENOMEM; + } + + /* Allow 16-byte CDBs and thus > 2TB */ + host->max_cmd_len = 16; + us = host_to_us(host); + memset(us, 0, sizeof(struct us_data)); + mutex_init(&(us->dev_mutex)); + init_completion(&us->cmnd_ready); + init_completion(&(us->notify)); + init_waitqueue_head(&us->delay_wait); + init_completion(&us->scanning_done); + + /* Associate the us_data structure with the USB device */ + result = associate_dev(us, intf); + if (result) + goto BadDevice; + + /* Get Device info */ + result = get_device_info(us, id); + if (result) + goto BadDevice; + + /* Get the transport, protocol, and pipe settings */ + result = get_transport(us); + if (result) + goto BadDevice; + result = get_protocol(us); + if (result) + goto BadDevice; + result = get_pipes(us); + if (result) + goto BadDevice; + + /* Acquire all the other resources and add the host */ + result = usb_stor_acquire_resources(us); + if (result) + goto BadDevice; + + result = scsi_add_host(host, &intf->dev); + if (result) + { + pr_info("Unable to add the scsi host\n"); + goto BadDevice; + } + + /* Start up the thread for delayed SCSI-device scanning */ + th = kthread_create(usb_stor_scan_thread, us, "eucr-stor-scan"); + if (IS_ERR(th)) { + pr_info("Unable to start the device-scanning thread\n"); + complete(&us->scanning_done); + quiesce_and_remove_host(us); + result = PTR_ERR(th); + goto BadDevice; + } + wake_up_process(th); + + /* probe card type */ + result = ENE_Read_BYTE(us, REG_CARD_STATUS, &MiscReg03); + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_TRANSPORT_ERROR; + quiesce_and_remove_host(us); + goto BadDevice; + } + + if (!(MiscReg03 & 0x02)) { + result = -ENODEV; + quiesce_and_remove_host(us); + pr_info("keucr: The driver only supports SM/MS card.\ + To use SD card, \ + please build driver/usb/storage/ums-eneub6250.ko\n"); + goto BadDevice; + } + + return 0; + + /* We come here if there are any problems */ +BadDevice: + pr_info("usb --- eucr_probe failed\n"); + release_everything(us); + return result; +} + +//----- eucr_disconnect() --------------------- +static void eucr_disconnect(struct usb_interface *intf) +{ + struct us_data *us = usb_get_intfdata(intf); + + pr_info("usb --- eucr_disconnect\n"); + quiesce_and_remove_host(us); + release_everything(us); +} + +/*********************************************************************** + * Initialization and registration + ***********************************************************************/ + +//----- usb_storage_driver() --------------------- +static struct usb_driver usb_storage_driver = { + .name = "eucr", + .probe = eucr_probe, + .suspend = eucr_suspend, + .resume = eucr_resume, + .reset_resume = eucr_reset_resume, + .disconnect = eucr_disconnect, + .pre_reset = eucr_pre_reset, + .post_reset = eucr_post_reset, + .id_table = eucr_usb_ids, + .soft_unbind = 1, +}; + +module_usb_driver(usb_storage_driver); diff --git a/drivers/staging/keucr/usb.h b/drivers/staging/keucr/usb.h new file mode 100644 index 00000000..a5f7a16c --- /dev/null +++ b/drivers/staging/keucr/usb.h @@ -0,0 +1,237 @@ +// Driver for USB Mass Storage compliant devices + +#ifndef _USB_H_ +#define _USB_H_ + +#include +#include +#include +#include +#include +#include +#include "common.h" + +struct us_data; +struct scsi_cmnd; + +/* + * Unusual device list definitions + */ + +struct us_unusual_dev { + const char* vendorName; + const char* productName; + __u8 useProtocol; + __u8 useTransport; + int (*initFunction)(struct us_data *); +}; + +//EnE HW Register +#define REG_CARD_STATUS 0xFF83 +#define REG_HW_TRAP1 0xFF89 + +// SRB Status. Refers /usr/include/wine/wine/wnaspi32.h & SCSI sense key +#define SS_SUCCESS 0x00 // No Sense +#define SS_NOT_READY 0x02 +#define SS_MEDIUM_ERR 0x03 +#define SS_HW_ERR 0x04 +#define SS_ILLEGAL_REQUEST 0x05 +#define SS_UNIT_ATTENTION 0x06 + +//ENE Load FW Pattern +#define SD_INIT1_PATTERN 1 +#define SD_INIT2_PATTERN 2 +#define SD_RW_PATTERN 3 +#define MS_INIT_PATTERN 4 +#define MSP_RW_PATTERN 5 +#define MS_RW_PATTERN 6 +#define SM_INIT_PATTERN 7 +#define SM_RW_PATTERN 8 + +#define FDIR_WRITE 0 +#define FDIR_READ 1 + +typedef struct _SD_STATUS { + BYTE Insert:1; + BYTE Ready:1; + BYTE MediaChange:1; + BYTE IsMMC:1; + BYTE HiCapacity:1; + BYTE HiSpeed:1; + BYTE WtP:1; + BYTE Reserved:1; +} SD_STATUS, *PSD_STATUS; + +typedef struct _MS_STATUS { + BYTE Insert:1; + BYTE Ready:1; + BYTE MediaChange:1; + BYTE IsMSPro:1; + BYTE IsMSPHG:1; + BYTE Reserved1:1; + BYTE WtP:1; + BYTE Reserved2:1; +} MS_STATUS, *PMS_STATUS; + +typedef struct _SM_STATUS { + BYTE Insert:1; + BYTE Ready:1; + BYTE MediaChange:1; + BYTE Reserved:3; + BYTE WtP:1; + BYTE IsMS:1; +} SM_STATUS, *PSM_STATUS; + +// SD Block Length +#define SD_BLOCK_LEN 9 // 2^9 = 512 Bytes, The HW maximum read/write data length + +/* Dynamic bitflag definitions (us->dflags): used in set_bit() etc. */ +#define US_FLIDX_URB_ACTIVE 0 /* current_urb is in use */ +#define US_FLIDX_SG_ACTIVE 1 /* current_sg is in use */ +#define US_FLIDX_ABORTING 2 /* abort is in progress */ +#define US_FLIDX_DISCONNECTING 3 /* disconnect in progress */ +#define US_FLIDX_RESETTING 4 /* device reset in progress */ +#define US_FLIDX_TIMED_OUT 5 /* SCSI midlayer timed out */ +#define US_FLIDX_DONT_SCAN 6 /* don't scan (disconnect) */ + + +#define USB_STOR_STRING_LEN 32 + +/* + * We provide a DMA-mapped I/O buffer for use with small USB transfers. + * It turns out that CB[I] needs a 12-byte buffer and Bulk-only needs a + * 31-byte buffer. But Freecom needs a 64-byte buffer, so that's the + * size we'll allocate. + */ + +#define US_IOBUF_SIZE 64 /* Size of the DMA-mapped I/O buffer */ +#define US_SENSE_SIZE 18 /* Size of the autosense data buffer */ + +typedef int (*trans_cmnd)(struct scsi_cmnd *, struct us_data*); +typedef int (*trans_reset)(struct us_data*); +typedef void (*proto_cmnd)(struct scsi_cmnd*, struct us_data*); +typedef void (*extra_data_destructor)(void *); /* extra data destructor */ +typedef void (*pm_hook)(struct us_data *, int); /* power management hook */ + +#define US_SUSPEND 0 +#define US_RESUME 1 + +/* we allocate one of these for every device that we remember */ +struct us_data { + /* The device we're working with + * It's important to note: + * (o) you must hold dev_mutex to change pusb_dev + */ + struct mutex dev_mutex; /* protect pusb_dev */ + struct usb_device *pusb_dev; /* this usb_device */ + struct usb_interface *pusb_intf; /* this interface */ + struct us_unusual_dev *unusual_dev; /* device-filter entry */ + unsigned long fflags; /* fixed flags from filter */ + unsigned long dflags; /* dynamic atomic bitflags */ + unsigned int send_bulk_pipe; /* cached pipe values */ + unsigned int recv_bulk_pipe; + unsigned int send_ctrl_pipe; + unsigned int recv_ctrl_pipe; + unsigned int recv_intr_pipe; + + /* information about the device */ + char *transport_name; + char *protocol_name; + __le32 bcs_signature; + u8 subclass; + u8 protocol; + u8 max_lun; + + u8 ifnum; /* interface number */ + u8 ep_bInterval; /* interrupt interval */ + + /* function pointers for this device */ + trans_cmnd transport; /* transport function */ + trans_reset transport_reset; /* transport device reset */ + proto_cmnd proto_handler; /* protocol handler */ + + /* SCSI interfaces */ + struct scsi_cmnd *srb; /* current srb */ + unsigned int tag; /* current dCBWTag */ + + /* control and bulk communications data */ + struct urb *current_urb; /* USB requests */ + struct usb_ctrlrequest *cr; /* control requests */ + struct usb_sg_request current_sg; /* scatter-gather req. */ + unsigned char *iobuf; /* I/O buffer */ + unsigned char *sensebuf; /* sense data buffer */ + dma_addr_t cr_dma; /* buffer DMA addresses */ + dma_addr_t iobuf_dma; + struct task_struct *ctl_thread; /* the control thread */ + + /* mutual exclusion and synchronization structures */ + struct completion cmnd_ready; /* to sleep thread on */ + struct completion notify; /* thread begin/end */ + wait_queue_head_t delay_wait; /* wait during scan, reset */ + struct completion scanning_done; /* wait for scan thread */ + + /* subdriver information */ + void *extra; /* Any extra data */ + extra_data_destructor extra_destructor;/* extra data destructor */ +#ifdef CONFIG_PM + pm_hook suspend_resume_hook; +#endif + // for 6250 code + SD_STATUS SD_Status; + MS_STATUS MS_Status; + SM_STATUS SM_Status; + + //----- SD Control Data ---------------- + //SD_REGISTER SD_Regs; + WORD SD_Block_Mult; + BYTE SD_READ_BL_LEN; + WORD SD_C_SIZE; + BYTE SD_C_SIZE_MULT; + + // SD/MMC New spec. + BYTE SD_SPEC_VER; + BYTE SD_CSD_VER; + BYTE SD20_HIGH_CAPACITY; + DWORD HC_C_SIZE; + BYTE MMC_SPEC_VER; + BYTE MMC_BusWidth; + BYTE MMC_HIGH_CAPACITY; + + //----- MS Control Data ---------------- + BOOLEAN MS_SWWP; + DWORD MSP_TotalBlock; + /* MS_LibControl MS_Lib; */ + BOOLEAN MS_IsRWPage; + WORD MS_Model; + + //----- SM Control Data ---------------- + BYTE SM_DeviceID; + BYTE SM_CardID; + + PBYTE testbuf; + BYTE BIN_FLAG; + DWORD bl_num; + int SrbStatus; + + //------Power Managerment --------------- + BOOLEAN Power_IsResum; +}; + +/* Convert between us_data and the corresponding Scsi_Host */ +static inline struct Scsi_Host *us_to_host(struct us_data *us) { + return container_of((void *) us, struct Scsi_Host, hostdata); +} +static inline struct us_data *host_to_us(struct Scsi_Host *host) { + return (struct us_data *) host->hostdata; +} + +/* Function to fill an inquiry response. See usb.c for details */ +extern void fill_inquiry_response(struct us_data *us, + unsigned char *data, unsigned int data_len); + +/* The scsi_lock() and scsi_unlock() macros protect the sm_state and the + * single queue element srb for write access */ +#define scsi_unlock(host) spin_unlock_irq(host->host_lock) +#define scsi_lock(host) spin_lock_irq(host->host_lock) + +#endif diff --git a/drivers/staging/line6/Kconfig b/drivers/staging/line6/Kconfig new file mode 100644 index 00000000..43120ff2 --- /dev/null +++ b/drivers/staging/line6/Kconfig @@ -0,0 +1,85 @@ +menuconfig LINE6_USB + tristate "Line6 USB support" + depends on USB && SND + select SND_RAWMIDI + select SND_PCM + help + This is a driver for the guitar amp, cab, and effects modeller + PODxt Pro by Line6 (and similar devices), supporting the + following features: + * Reading/writing individual parameters + * Reading/writing complete channel, effects setup, and amp + setup data + * Channel switching + * Virtual MIDI interface + * Tuner access + * Playback/capture/mixer device for any ALSA-compatible PCM + audio application + * Signal routing (record clean/processed guitar signal, + re-amping) + + Preliminary support for the Variax Workbench and TonePort + devices is included. + +if LINE6_USB + +config LINE6_USB_DEBUG + bool "print debug messages" + default n + help + Say Y here to write debug messages to the syslog. + + If unsure, say N. + +config LINE6_USB_DUMP_CTRL + bool "dump control messages" + default n + help + Say Y here to write control messages sent to and received from + Line6 devices to the syslog. + + If unsure, say N. + +config LINE6_USB_DUMP_MIDI + bool "dump MIDI messages" + default n + help + Say Y here to write MIDI messages sent to and received from + Line6 devices to the syslog. + + If unsure, say N. + +config LINE6_USB_DUMP_PCM + bool "dump PCM data" + default n + help + Say Y here to write PCM data sent to and received from Line6 + devices to the syslog. This will produce a huge amount of + syslog data during playback and capture. + + If unsure, say N. + +config LINE6_USB_RAW + bool "raw data communication" + default n + help + Say Y here to create special files which allow to send raw data + to the device. This bypasses any sanity checks, so if you discover + the code to erase the firmware, feel free to render your device + useless, but only after reading the GPL section "NO WARRANTY". + + If unsure, say N. + +config LINE6_USB_IMPULSE_RESPONSE + bool "measure impulse response" + default n + help + Say Y here to add code to measure the impulse response of a Line6 + device. This is more accurate than user-space methods since it + bypasses any PCM data buffering (e.g., by ALSA or jack). This is + useful for assessing the performance of new devices, but is not + required for normal operation. + + If unsure, say N. + +endif # LINE6_USB diff --git a/drivers/staging/line6/Makefile b/drivers/staging/line6/Makefile new file mode 100644 index 00000000..34a2ddac --- /dev/null +++ b/drivers/staging/line6/Makefile @@ -0,0 +1,16 @@ +obj-$(CONFIG_LINE6_USB) += line6usb.o + +line6usb-y := \ + audio.o \ + capture.o \ + control.o \ + driver.o \ + dumprequest.o \ + midi.o \ + midibuf.o \ + pcm.o \ + playback.o \ + pod.o \ + toneport.o \ + variax.o \ + podhd.o diff --git a/drivers/staging/line6/audio.c b/drivers/staging/line6/audio.c new file mode 100644 index 00000000..8e739839 --- /dev/null +++ b/drivers/staging/line6/audio.c @@ -0,0 +1,74 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include + +#include "driver.h" +#include "audio.h" + +static int line6_index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *line6_id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; + +/* + Initialize the Line6 USB audio system. +*/ +int line6_init_audio(struct usb_line6 *line6) +{ + static int dev; + struct snd_card *card; + int err; + + err = snd_card_create(line6_index[dev], line6_id[dev], THIS_MODULE, 0, + &card); + if (err < 0) + return err; + + line6->card = card; + + strcpy(card->id, line6->properties->id); + strcpy(card->driver, DRIVER_NAME); + strcpy(card->shortname, line6->properties->name); + /* longname is 80 chars - see asound.h */ + sprintf(card->longname, "Line6 %s at USB %s", line6->properties->name, + dev_name(line6->ifcdev)); + return 0; +} + +/* + Register the Line6 USB audio system. +*/ +int line6_register_audio(struct usb_line6 *line6) +{ + int err; + + err = snd_card_register(line6->card); + if (err < 0) + return err; + + return 0; +} + +/* + Cleanup the Line6 USB audio system. +*/ +void line6_cleanup_audio(struct usb_line6 *line6) +{ + struct snd_card *card = line6->card; + + if (card == NULL) + return; + + snd_card_disconnect(card); + snd_card_free(card); + line6->card = NULL; +} diff --git a/drivers/staging/line6/audio.h b/drivers/staging/line6/audio.h new file mode 100644 index 00000000..5f8a09a0 --- /dev/null +++ b/drivers/staging/line6/audio.h @@ -0,0 +1,21 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef AUDIO_H +#define AUDIO_H + +#include "driver.h" + +extern void line6_cleanup_audio(struct usb_line6 *); +extern int line6_init_audio(struct usb_line6 *); +extern int line6_register_audio(struct usb_line6 *); + +#endif diff --git a/drivers/staging/line6/capture.c b/drivers/staging/line6/capture.c new file mode 100644 index 00000000..c85c5b6b --- /dev/null +++ b/drivers/staging/line6/capture.c @@ -0,0 +1,435 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include + +#include "audio.h" +#include "capture.h" +#include "driver.h" +#include "pcm.h" +#include "pod.h" + +/* + Find a free URB and submit it. +*/ +static int submit_audio_in_urb(struct snd_line6_pcm *line6pcm) +{ + int index; + unsigned long flags; + int i, urb_size; + int ret; + struct urb *urb_in; + + spin_lock_irqsave(&line6pcm->lock_audio_in, flags); + index = + find_first_zero_bit(&line6pcm->active_urb_in, LINE6_ISO_BUFFERS); + + if (index < 0 || index >= LINE6_ISO_BUFFERS) { + spin_unlock_irqrestore(&line6pcm->lock_audio_in, flags); + dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); + return -EINVAL; + } + + urb_in = line6pcm->urb_audio_in[index]; + urb_size = 0; + + for (i = 0; i < LINE6_ISO_PACKETS; ++i) { + struct usb_iso_packet_descriptor *fin = + &urb_in->iso_frame_desc[i]; + fin->offset = urb_size; + fin->length = line6pcm->max_packet_size; + urb_size += line6pcm->max_packet_size; + } + + urb_in->transfer_buffer = + line6pcm->buffer_in + + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + urb_in->transfer_buffer_length = urb_size; + urb_in->context = line6pcm; + + ret = usb_submit_urb(urb_in, GFP_ATOMIC); + + if (ret == 0) + set_bit(index, &line6pcm->active_urb_in); + else + dev_err(line6pcm->line6->ifcdev, + "URB in #%d submission failed (%d)\n", index, ret); + + spin_unlock_irqrestore(&line6pcm->lock_audio_in, flags); + return 0; +} + +/* + Submit all currently available capture URBs. +*/ +int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm) +{ + int ret, i; + + for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + ret = submit_audio_in_urb(line6pcm); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + Unlink all currently active capture URBs. +*/ +void line6_unlink_audio_in_urbs(struct snd_line6_pcm *line6pcm) +{ + unsigned int i; + + for (i = LINE6_ISO_BUFFERS; i--;) { + if (test_bit(i, &line6pcm->active_urb_in)) { + if (!test_and_set_bit(i, &line6pcm->unlink_urb_in)) { + struct urb *u = line6pcm->urb_audio_in[i]; + usb_unlink_urb(u); + } + } + } +} + +/* + Wait until unlinking of all currently active capture URBs has been + finished. +*/ +void line6_wait_clear_audio_in_urbs(struct snd_line6_pcm *line6pcm) +{ + int timeout = HZ; + unsigned int i; + int alive; + + do { + alive = 0; + for (i = LINE6_ISO_BUFFERS; i--;) { + if (test_bit(i, &line6pcm->active_urb_in)) + alive++; + } + if (!alive) + break; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } while (--timeout > 0); + if (alive) + snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive); +} + +/* + Unlink all currently active capture URBs, and wait for finishing. +*/ +void line6_unlink_wait_clear_audio_in_urbs(struct snd_line6_pcm *line6pcm) +{ + line6_unlink_audio_in_urbs(line6pcm); + line6_wait_clear_audio_in_urbs(line6pcm); +} + +/* + Copy data into ALSA capture buffer. +*/ +void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) +{ + struct snd_pcm_substream *substream = + get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); + struct snd_pcm_runtime *runtime = substream->runtime; + const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + int frames = fsize / bytes_per_frame; + + if (runtime == NULL) + return; + + if (line6pcm->pos_in_done + frames > runtime->buffer_size) { + /* + The transferred area goes over buffer boundary, + copy two separate chunks. + */ + int len; + len = runtime->buffer_size - line6pcm->pos_in_done; + + if (len > 0) { + memcpy(runtime->dma_area + + line6pcm->pos_in_done * bytes_per_frame, fbuf, + len * bytes_per_frame); + memcpy(runtime->dma_area, fbuf + len * bytes_per_frame, + (frames - len) * bytes_per_frame); + } else { + /* this is somewhat paranoid */ + dev_err(line6pcm->line6->ifcdev, + "driver bug: len = %d\n", len); + } + } else { + /* copy single chunk */ + memcpy(runtime->dma_area + + line6pcm->pos_in_done * bytes_per_frame, fbuf, fsize); + } + + line6pcm->pos_in_done += frames; + if (line6pcm->pos_in_done >= runtime->buffer_size) + line6pcm->pos_in_done -= runtime->buffer_size; +} + +void line6_capture_check_period(struct snd_line6_pcm *line6pcm, int length) +{ + struct snd_pcm_substream *substream = + get_substream(line6pcm, SNDRV_PCM_STREAM_CAPTURE); + + line6pcm->bytes_in += length; + if (line6pcm->bytes_in >= line6pcm->period_in) { + line6pcm->bytes_in %= line6pcm->period_in; + snd_pcm_period_elapsed(substream); + } +} + +void line6_free_capture_buffer(struct snd_line6_pcm *line6pcm) +{ + kfree(line6pcm->buffer_in); + line6pcm->buffer_in = NULL; +} + +/* + * Callback for completed capture URB. + */ +static void audio_in_callback(struct urb *urb) +{ + int i, index, length = 0, shutdown = 0; + unsigned long flags; + + struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; + + line6pcm->last_frame_in = urb->start_frame; + + /* find index of URB */ + for (index = 0; index < LINE6_ISO_BUFFERS; ++index) + if (urb == line6pcm->urb_audio_in[index]) + break; + +#ifdef CONFIG_LINE6_USB_DUMP_PCM + for (i = 0; i < LINE6_ISO_PACKETS; ++i) { + struct usb_iso_packet_descriptor *fout = + &urb->iso_frame_desc[i]; + line6_write_hexdump(line6pcm->line6, 'C', + urb->transfer_buffer + fout->offset, + fout->length); + } +#endif + + spin_lock_irqsave(&line6pcm->lock_audio_in, flags); + + for (i = 0; i < LINE6_ISO_PACKETS; ++i) { + char *fbuf; + int fsize; + struct usb_iso_packet_descriptor *fin = &urb->iso_frame_desc[i]; + + if (fin->status == -EXDEV) { + shutdown = 1; + break; + } + + fbuf = urb->transfer_buffer + fin->offset; + fsize = fin->actual_length; + + if (fsize > line6pcm->max_packet_size) { + dev_err(line6pcm->line6->ifcdev, + "driver and/or device bug: packet too large (%d > %d)\n", + fsize, line6pcm->max_packet_size); + } + + length += fsize; + + /* the following assumes LINE6_ISO_PACKETS == 1: */ + line6pcm->prev_fbuf = fbuf; + line6pcm->prev_fsize = fsize; + +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + if (!(line6pcm->flags & LINE6_BITS_PCM_IMPULSE)) +#endif + if (test_bit(LINE6_INDEX_PCM_ALSA_CAPTURE_STREAM, &line6pcm->flags) + && (fsize > 0)) + line6_capture_copy(line6pcm, fbuf, fsize); + } + + clear_bit(index, &line6pcm->active_urb_in); + + if (test_and_clear_bit(index, &line6pcm->unlink_urb_in)) + shutdown = 1; + + spin_unlock_irqrestore(&line6pcm->lock_audio_in, flags); + + if (!shutdown) { + submit_audio_in_urb(line6pcm); + +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + if (!(line6pcm->flags & LINE6_BITS_PCM_IMPULSE)) +#endif + if (test_bit(LINE6_INDEX_PCM_ALSA_CAPTURE_STREAM, &line6pcm->flags)) + line6_capture_check_period(line6pcm, length); + } +} + +/* open capture callback */ +static int snd_line6_capture_open(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + err = snd_pcm_hw_constraint_ratdens(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + (&line6pcm-> + properties->snd_line6_rates)); + if (err < 0) + return err; + + runtime->hw = line6pcm->properties->snd_line6_capture_hw; + return 0; +} + +/* close capture callback */ +static int snd_line6_capture_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* hw_params capture callback */ +static int snd_line6_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int ret; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + /* -- Florian Demski [FD] */ + /* don't ask me why, but this fixes the bug on my machine */ + if (line6pcm == NULL) { + if (substream->pcm == NULL) + return -ENOMEM; + if (substream->pcm->private_data == NULL) + return -ENOMEM; + substream->private_data = substream->pcm->private_data; + line6pcm = snd_pcm_substream_chip(substream); + } + /* -- [FD] end */ + + ret = line6_pcm_acquire(line6pcm, LINE6_BIT_PCM_ALSA_CAPTURE_BUFFER); + + if (ret < 0) + return ret; + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) { + line6_pcm_release(line6pcm, LINE6_BIT_PCM_ALSA_CAPTURE_BUFFER); + return ret; + } + + line6pcm->period_in = params_period_bytes(hw_params); + return 0; +} + +/* hw_free capture callback */ +static int snd_line6_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + line6_pcm_release(line6pcm, LINE6_BIT_PCM_ALSA_CAPTURE_BUFFER); + return snd_pcm_lib_free_pages(substream); +} + +/* trigger callback */ +int snd_line6_capture_trigger(struct snd_line6_pcm *line6pcm, int cmd) +{ + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +#ifdef CONFIG_PM + case SNDRV_PCM_TRIGGER_RESUME: +#endif + err = line6_pcm_acquire(line6pcm, LINE6_BIT_PCM_ALSA_CAPTURE_STREAM); + + if (err < 0) + return err; + + break; + + case SNDRV_PCM_TRIGGER_STOP: +#ifdef CONFIG_PM + case SNDRV_PCM_TRIGGER_SUSPEND: +#endif + err = line6_pcm_release(line6pcm, LINE6_BIT_PCM_ALSA_CAPTURE_STREAM); + + if (err < 0) + return err; + + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* capture pointer callback */ +static snd_pcm_uframes_t +snd_line6_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + return line6pcm->pos_in_done; +} + +/* capture operators */ +struct snd_pcm_ops snd_line6_capture_ops = { + .open = snd_line6_capture_open, + .close = snd_line6_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_line6_capture_hw_params, + .hw_free = snd_line6_capture_hw_free, + .prepare = snd_line6_prepare, + .trigger = snd_line6_trigger, + .pointer = snd_line6_capture_pointer, +}; + +int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm) +{ + int i; + + /* create audio URBs and fill in constant values: */ + for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + struct urb *urb; + + /* URB for audio in: */ + urb = line6pcm->urb_audio_in[i] = + usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL); + + if (urb == NULL) { + dev_err(line6pcm->line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + urb->dev = line6pcm->line6->usbdev; + urb->pipe = + usb_rcvisocpipe(line6pcm->line6->usbdev, + line6pcm->ep_audio_read & + USB_ENDPOINT_NUMBER_MASK); + urb->transfer_flags = URB_ISO_ASAP; + urb->start_frame = -1; + urb->number_of_packets = LINE6_ISO_PACKETS; + urb->interval = LINE6_ISO_INTERVAL; + urb->error_count = 0; + urb->complete = audio_in_callback; + } + + return 0; +} diff --git a/drivers/staging/line6/capture.h b/drivers/staging/line6/capture.h new file mode 100644 index 00000000..4157bcb5 --- /dev/null +++ b/drivers/staging/line6/capture.h @@ -0,0 +1,35 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef CAPTURE_H +#define CAPTURE_H + +#include + +#include "driver.h" +#include "pcm.h" + +extern struct snd_pcm_ops snd_line6_capture_ops; + +extern void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, + int fsize); +extern void line6_capture_check_period(struct snd_line6_pcm *line6pcm, + int length); +extern int line6_create_audio_in_urbs(struct snd_line6_pcm *line6pcm); +extern void line6_free_capture_buffer(struct snd_line6_pcm *line6pcm); +extern int line6_submit_audio_in_all_urbs(struct snd_line6_pcm *line6pcm); +extern void line6_unlink_audio_in_urbs(struct snd_line6_pcm *line6pcm); +extern void line6_unlink_wait_clear_audio_in_urbs(struct snd_line6_pcm + *line6pcm); +extern void line6_wait_clear_audio_in_urbs(struct snd_line6_pcm *line6pcm); +extern int snd_line6_capture_trigger(struct snd_line6_pcm *line6pcm, int cmd); + +#endif diff --git a/drivers/staging/line6/config.h b/drivers/staging/line6/config.h new file mode 100644 index 00000000..f8a5149e --- /dev/null +++ b/drivers/staging/line6/config.h @@ -0,0 +1,48 @@ +/* + * Line6 Linux USB driver - 0.8.0 + * + * Copyright (C) 2004-2009 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef CONFIG_H +#define CONFIG_H + + +#ifdef CONFIG_USB_DEBUG +#define DEBUG 1 +#endif + + +/* + * Development tools. + */ +#define DO_DEBUG_MESSAGES 0 +#define DO_DUMP_URB_SEND DO_DEBUG_MESSAGES +#define DO_DUMP_URB_RECEIVE DO_DEBUG_MESSAGES +#define DO_DUMP_PCM_SEND 0 +#define DO_DUMP_PCM_RECEIVE 0 +#define DO_DUMP_MIDI_SEND DO_DEBUG_MESSAGES +#define DO_DUMP_MIDI_RECEIVE DO_DEBUG_MESSAGES +#define DO_DUMP_ANY (DO_DUMP_URB_SEND || DO_DUMP_URB_RECEIVE || \ + DO_DUMP_PCM_SEND || DO_DUMP_PCM_RECEIVE || \ + DO_DUMP_MIDI_SEND || DO_DUMP_MIDI_RECEIVE) +#define CREATE_RAW_FILE 0 + +#if DO_DEBUG_MESSAGES +#define CHECKPOINT printk(KERN_INFO "line6usb: %s (%s:%d)\n", \ + __func__, __FILE__, __LINE__) +#endif + +#if DO_DEBUG_MESSAGES +#define DEBUG_MESSAGES(x) (x) +#else +#define DEBUG_MESSAGES(x) +#endif + + +#endif diff --git a/drivers/staging/line6/control.c b/drivers/staging/line6/control.c new file mode 100644 index 00000000..67e23b6e --- /dev/null +++ b/drivers/staging/line6/control.c @@ -0,0 +1,995 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include + +#include "control.h" +#include "driver.h" +#include "pod.h" +#include "usbdefs.h" +#include "variax.h" + +#define DEVICE_ATTR2(_name1, _name2, _mode, _show, _store) \ +struct device_attribute dev_attr_##_name1 = __ATTR(_name2, _mode, _show, _store) + +#define LINE6_PARAM_R(PREFIX, prefix, type, param) \ +static ssize_t prefix##_get_##param(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + return prefix##_get_param_##type(dev, buf, PREFIX##_##param); \ +} + +#define LINE6_PARAM_RW(PREFIX, prefix, type, param) \ +LINE6_PARAM_R(PREFIX, prefix, type, param); \ +static ssize_t prefix##_set_##param(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + return prefix##_set_param_##type(dev, buf, count, PREFIX##_##param); \ +} + +#define POD_PARAM_R(type, param) LINE6_PARAM_R(POD, pod, type, param) +#define POD_PARAM_RW(type, param) LINE6_PARAM_RW(POD, pod, type, param) +#define VARIAX_PARAM_R(type, param) LINE6_PARAM_R(VARIAX, variax, type, param) +#define VARIAX_PARAM_RW(type, param) LINE6_PARAM_RW(VARIAX, variax, type, param) + +static ssize_t pod_get_param_int(struct device *dev, char *buf, int param) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int retval = line6_dump_wait_interruptible(&pod->dumpreq); + if (retval < 0) + return retval; + return sprintf(buf, "%d\n", pod->prog_data.control[param]); +} + +static ssize_t pod_set_param_int(struct device *dev, const char *buf, + size_t count, int param) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + unsigned long value; + int retval; + + retval = strict_strtoul(buf, 10, &value); + if (retval) + return retval; + + line6_pod_transmit_parameter(pod, param, value); + return count; +} + +static ssize_t variax_get_param_int(struct device *dev, char *buf, int param) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_variax *variax = usb_get_intfdata(interface); + int retval = line6_dump_wait_interruptible(&variax->dumpreq); + if (retval < 0) + return retval; + return sprintf(buf, "%d\n", variax->model_data.control[param]); +} + +static ssize_t variax_get_param_float(struct device *dev, char *buf, int param) +{ + /* + We do our own floating point handling here since at the time + this code was written (Jan 2006) it was highly discouraged to + use floating point arithmetic in the kernel. If you think that + this no longer applies, feel free to replace this by generic + floating point code. + */ + + static const int BIAS = 0x7f; + static const int OFFSET = 0xf; + static const int PRECISION = 1000; + + int len = 0; + unsigned part_int, part_frac; + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_variax *variax = usb_get_intfdata(interface); + const unsigned char *p = variax->model_data.control + param; + int retval = line6_dump_wait_interruptible(&variax->dumpreq); + if (retval < 0) + return retval; + + if ((p[0] == 0) && (p[1] == 0) && (p[2] == 0)) + part_int = part_frac = 0; + else { + int exponent = (((p[0] & 0x7f) << 1) | (p[1] >> 7)) - BIAS; + unsigned mantissa = (p[1] << 8) | p[2] | 0x8000; + exponent -= OFFSET; + + if (exponent >= 0) { + part_int = mantissa << exponent; + part_frac = 0; + } else { + part_int = mantissa >> -exponent; + part_frac = (mantissa << (32 + exponent)) & 0xffffffff; + } + + part_frac = + (part_frac / ((1UL << 31) / (PRECISION / 2 * 10)) + 5) / 10; + } + + len += + sprintf(buf + len, "%s%d.%03d\n", ((p[0] & 0x80) ? "-" : ""), + part_int, part_frac); + return len; +} + +POD_PARAM_RW(int, tweak); +POD_PARAM_RW(int, wah_position); +POD_PARAM_RW(int, compression_gain); +POD_PARAM_RW(int, vol_pedal_position); +POD_PARAM_RW(int, compression_threshold); +POD_PARAM_RW(int, pan); +POD_PARAM_RW(int, amp_model_setup); +POD_PARAM_RW(int, amp_model); +POD_PARAM_RW(int, drive); +POD_PARAM_RW(int, bass); +POD_PARAM_RW(int, mid); +POD_PARAM_RW(int, lowmid); +POD_PARAM_RW(int, treble); +POD_PARAM_RW(int, highmid); +POD_PARAM_RW(int, chan_vol); +POD_PARAM_RW(int, reverb_mix); +POD_PARAM_RW(int, effect_setup); +POD_PARAM_RW(int, band_1_frequency); +POD_PARAM_RW(int, presence); +POD_PARAM_RW(int, treble__bass); +POD_PARAM_RW(int, noise_gate_enable); +POD_PARAM_RW(int, gate_threshold); +POD_PARAM_RW(int, gate_decay_time); +POD_PARAM_RW(int, stomp_enable); +POD_PARAM_RW(int, comp_enable); +POD_PARAM_RW(int, stomp_time); +POD_PARAM_RW(int, delay_enable); +POD_PARAM_RW(int, mod_param_1); +POD_PARAM_RW(int, delay_param_1); +POD_PARAM_RW(int, delay_param_1_note_value); +POD_PARAM_RW(int, band_2_frequency__bass); +POD_PARAM_RW(int, delay_param_2); +POD_PARAM_RW(int, delay_volume_mix); +POD_PARAM_RW(int, delay_param_3); +POD_PARAM_RW(int, reverb_enable); +POD_PARAM_RW(int, reverb_type); +POD_PARAM_RW(int, reverb_decay); +POD_PARAM_RW(int, reverb_tone); +POD_PARAM_RW(int, reverb_pre_delay); +POD_PARAM_RW(int, reverb_pre_post); +POD_PARAM_RW(int, band_2_frequency); +POD_PARAM_RW(int, band_3_frequency__bass); +POD_PARAM_RW(int, wah_enable); +POD_PARAM_RW(int, modulation_lo_cut); +POD_PARAM_RW(int, delay_reverb_lo_cut); +POD_PARAM_RW(int, volume_pedal_minimum); +POD_PARAM_RW(int, eq_pre_post); +POD_PARAM_RW(int, volume_pre_post); +POD_PARAM_RW(int, di_model); +POD_PARAM_RW(int, di_delay); +POD_PARAM_RW(int, mod_enable); +POD_PARAM_RW(int, mod_param_1_note_value); +POD_PARAM_RW(int, mod_param_2); +POD_PARAM_RW(int, mod_param_3); +POD_PARAM_RW(int, mod_param_4); +POD_PARAM_RW(int, mod_param_5); +POD_PARAM_RW(int, mod_volume_mix); +POD_PARAM_RW(int, mod_pre_post); +POD_PARAM_RW(int, modulation_model); +POD_PARAM_RW(int, band_3_frequency); +POD_PARAM_RW(int, band_4_frequency__bass); +POD_PARAM_RW(int, mod_param_1_double_precision); +POD_PARAM_RW(int, delay_param_1_double_precision); +POD_PARAM_RW(int, eq_enable); +POD_PARAM_RW(int, tap); +POD_PARAM_RW(int, volume_tweak_pedal_assign); +POD_PARAM_RW(int, band_5_frequency); +POD_PARAM_RW(int, tuner); +POD_PARAM_RW(int, mic_selection); +POD_PARAM_RW(int, cabinet_model); +POD_PARAM_RW(int, stomp_model); +POD_PARAM_RW(int, roomlevel); +POD_PARAM_RW(int, band_4_frequency); +POD_PARAM_RW(int, band_6_frequency); +POD_PARAM_RW(int, stomp_param_1_note_value); +POD_PARAM_RW(int, stomp_param_2); +POD_PARAM_RW(int, stomp_param_3); +POD_PARAM_RW(int, stomp_param_4); +POD_PARAM_RW(int, stomp_param_5); +POD_PARAM_RW(int, stomp_param_6); +POD_PARAM_RW(int, amp_switch_select); +POD_PARAM_RW(int, delay_param_4); +POD_PARAM_RW(int, delay_param_5); +POD_PARAM_RW(int, delay_pre_post); +POD_PARAM_RW(int, delay_model); +POD_PARAM_RW(int, delay_verb_model); +POD_PARAM_RW(int, tempo_msb); +POD_PARAM_RW(int, tempo_lsb); +POD_PARAM_RW(int, wah_model); +POD_PARAM_RW(int, bypass_volume); +POD_PARAM_RW(int, fx_loop_on_off); +POD_PARAM_RW(int, tweak_param_select); +POD_PARAM_RW(int, amp1_engage); +POD_PARAM_RW(int, band_1_gain); +POD_PARAM_RW(int, band_2_gain__bass); +POD_PARAM_RW(int, band_2_gain); +POD_PARAM_RW(int, band_3_gain__bass); +POD_PARAM_RW(int, band_3_gain); +POD_PARAM_RW(int, band_4_gain__bass); +POD_PARAM_RW(int, band_5_gain__bass); +POD_PARAM_RW(int, band_4_gain); +POD_PARAM_RW(int, band_6_gain__bass); +VARIAX_PARAM_R(int, body); +VARIAX_PARAM_R(int, pickup1_enable); +VARIAX_PARAM_R(int, pickup1_type); +VARIAX_PARAM_R(float, pickup1_position); +VARIAX_PARAM_R(float, pickup1_angle); +VARIAX_PARAM_R(float, pickup1_level); +VARIAX_PARAM_R(int, pickup2_enable); +VARIAX_PARAM_R(int, pickup2_type); +VARIAX_PARAM_R(float, pickup2_position); +VARIAX_PARAM_R(float, pickup2_angle); +VARIAX_PARAM_R(float, pickup2_level); +VARIAX_PARAM_R(int, pickup_phase); +VARIAX_PARAM_R(float, capacitance); +VARIAX_PARAM_R(float, tone_resistance); +VARIAX_PARAM_R(float, volume_resistance); +VARIAX_PARAM_R(int, taper); +VARIAX_PARAM_R(float, tone_dump); +VARIAX_PARAM_R(int, save_tone); +VARIAX_PARAM_R(float, volume_dump); +VARIAX_PARAM_R(int, tuning_enable); +VARIAX_PARAM_R(int, tuning6); +VARIAX_PARAM_R(int, tuning5); +VARIAX_PARAM_R(int, tuning4); +VARIAX_PARAM_R(int, tuning3); +VARIAX_PARAM_R(int, tuning2); +VARIAX_PARAM_R(int, tuning1); +VARIAX_PARAM_R(float, detune6); +VARIAX_PARAM_R(float, detune5); +VARIAX_PARAM_R(float, detune4); +VARIAX_PARAM_R(float, detune3); +VARIAX_PARAM_R(float, detune2); +VARIAX_PARAM_R(float, detune1); +VARIAX_PARAM_R(float, mix6); +VARIAX_PARAM_R(float, mix5); +VARIAX_PARAM_R(float, mix4); +VARIAX_PARAM_R(float, mix3); +VARIAX_PARAM_R(float, mix2); +VARIAX_PARAM_R(float, mix1); +VARIAX_PARAM_R(int, pickup_wiring); + +static DEVICE_ATTR(tweak, S_IWUSR | S_IRUGO, pod_get_tweak, pod_set_tweak); +static DEVICE_ATTR(wah_position, S_IWUSR | S_IRUGO, pod_get_wah_position, + pod_set_wah_position); +static DEVICE_ATTR(compression_gain, S_IWUSR | S_IRUGO, + pod_get_compression_gain, pod_set_compression_gain); +static DEVICE_ATTR(vol_pedal_position, S_IWUSR | S_IRUGO, + pod_get_vol_pedal_position, pod_set_vol_pedal_position); +static DEVICE_ATTR(compression_threshold, S_IWUSR | S_IRUGO, + pod_get_compression_threshold, + pod_set_compression_threshold); +static DEVICE_ATTR(pan, S_IWUSR | S_IRUGO, pod_get_pan, pod_set_pan); +static DEVICE_ATTR(amp_model_setup, S_IWUSR | S_IRUGO, pod_get_amp_model_setup, + pod_set_amp_model_setup); +static DEVICE_ATTR(amp_model, S_IWUSR | S_IRUGO, pod_get_amp_model, + pod_set_amp_model); +static DEVICE_ATTR(drive, S_IWUSR | S_IRUGO, pod_get_drive, pod_set_drive); +static DEVICE_ATTR(bass, S_IWUSR | S_IRUGO, pod_get_bass, pod_set_bass); +static DEVICE_ATTR(mid, S_IWUSR | S_IRUGO, pod_get_mid, pod_set_mid); +static DEVICE_ATTR(lowmid, S_IWUSR | S_IRUGO, pod_get_lowmid, pod_set_lowmid); +static DEVICE_ATTR(treble, S_IWUSR | S_IRUGO, pod_get_treble, pod_set_treble); +static DEVICE_ATTR(highmid, S_IWUSR | S_IRUGO, pod_get_highmid, + pod_set_highmid); +static DEVICE_ATTR(chan_vol, S_IWUSR | S_IRUGO, pod_get_chan_vol, + pod_set_chan_vol); +static DEVICE_ATTR(reverb_mix, S_IWUSR | S_IRUGO, pod_get_reverb_mix, + pod_set_reverb_mix); +static DEVICE_ATTR(effect_setup, S_IWUSR | S_IRUGO, pod_get_effect_setup, + pod_set_effect_setup); +static DEVICE_ATTR(band_1_frequency, S_IWUSR | S_IRUGO, + pod_get_band_1_frequency, pod_set_band_1_frequency); +static DEVICE_ATTR(presence, S_IWUSR | S_IRUGO, pod_get_presence, + pod_set_presence); +static DEVICE_ATTR2(treble__bass, treble, S_IWUSR | S_IRUGO, + pod_get_treble__bass, pod_set_treble__bass); +static DEVICE_ATTR(noise_gate_enable, S_IWUSR | S_IRUGO, + pod_get_noise_gate_enable, pod_set_noise_gate_enable); +static DEVICE_ATTR(gate_threshold, S_IWUSR | S_IRUGO, pod_get_gate_threshold, + pod_set_gate_threshold); +static DEVICE_ATTR(gate_decay_time, S_IWUSR | S_IRUGO, pod_get_gate_decay_time, + pod_set_gate_decay_time); +static DEVICE_ATTR(stomp_enable, S_IWUSR | S_IRUGO, pod_get_stomp_enable, + pod_set_stomp_enable); +static DEVICE_ATTR(comp_enable, S_IWUSR | S_IRUGO, pod_get_comp_enable, + pod_set_comp_enable); +static DEVICE_ATTR(stomp_time, S_IWUSR | S_IRUGO, pod_get_stomp_time, + pod_set_stomp_time); +static DEVICE_ATTR(delay_enable, S_IWUSR | S_IRUGO, pod_get_delay_enable, + pod_set_delay_enable); +static DEVICE_ATTR(mod_param_1, S_IWUSR | S_IRUGO, pod_get_mod_param_1, + pod_set_mod_param_1); +static DEVICE_ATTR(delay_param_1, S_IWUSR | S_IRUGO, pod_get_delay_param_1, + pod_set_delay_param_1); +static DEVICE_ATTR(delay_param_1_note_value, S_IWUSR | S_IRUGO, + pod_get_delay_param_1_note_value, + pod_set_delay_param_1_note_value); +static DEVICE_ATTR2(band_2_frequency__bass, band_2_frequency, S_IWUSR | S_IRUGO, + pod_get_band_2_frequency__bass, + pod_set_band_2_frequency__bass); +static DEVICE_ATTR(delay_param_2, S_IWUSR | S_IRUGO, pod_get_delay_param_2, + pod_set_delay_param_2); +static DEVICE_ATTR(delay_volume_mix, S_IWUSR | S_IRUGO, + pod_get_delay_volume_mix, pod_set_delay_volume_mix); +static DEVICE_ATTR(delay_param_3, S_IWUSR | S_IRUGO, pod_get_delay_param_3, + pod_set_delay_param_3); +static DEVICE_ATTR(reverb_enable, S_IWUSR | S_IRUGO, pod_get_reverb_enable, + pod_set_reverb_enable); +static DEVICE_ATTR(reverb_type, S_IWUSR | S_IRUGO, pod_get_reverb_type, + pod_set_reverb_type); +static DEVICE_ATTR(reverb_decay, S_IWUSR | S_IRUGO, pod_get_reverb_decay, + pod_set_reverb_decay); +static DEVICE_ATTR(reverb_tone, S_IWUSR | S_IRUGO, pod_get_reverb_tone, + pod_set_reverb_tone); +static DEVICE_ATTR(reverb_pre_delay, S_IWUSR | S_IRUGO, + pod_get_reverb_pre_delay, pod_set_reverb_pre_delay); +static DEVICE_ATTR(reverb_pre_post, S_IWUSR | S_IRUGO, pod_get_reverb_pre_post, + pod_set_reverb_pre_post); +static DEVICE_ATTR(band_2_frequency, S_IWUSR | S_IRUGO, + pod_get_band_2_frequency, pod_set_band_2_frequency); +static DEVICE_ATTR2(band_3_frequency__bass, band_3_frequency, S_IWUSR | S_IRUGO, + pod_get_band_3_frequency__bass, + pod_set_band_3_frequency__bass); +static DEVICE_ATTR(wah_enable, S_IWUSR | S_IRUGO, pod_get_wah_enable, + pod_set_wah_enable); +static DEVICE_ATTR(modulation_lo_cut, S_IWUSR | S_IRUGO, + pod_get_modulation_lo_cut, pod_set_modulation_lo_cut); +static DEVICE_ATTR(delay_reverb_lo_cut, S_IWUSR | S_IRUGO, + pod_get_delay_reverb_lo_cut, pod_set_delay_reverb_lo_cut); +static DEVICE_ATTR(volume_pedal_minimum, S_IWUSR | S_IRUGO, + pod_get_volume_pedal_minimum, pod_set_volume_pedal_minimum); +static DEVICE_ATTR(eq_pre_post, S_IWUSR | S_IRUGO, pod_get_eq_pre_post, + pod_set_eq_pre_post); +static DEVICE_ATTR(volume_pre_post, S_IWUSR | S_IRUGO, pod_get_volume_pre_post, + pod_set_volume_pre_post); +static DEVICE_ATTR(di_model, S_IWUSR | S_IRUGO, pod_get_di_model, + pod_set_di_model); +static DEVICE_ATTR(di_delay, S_IWUSR | S_IRUGO, pod_get_di_delay, + pod_set_di_delay); +static DEVICE_ATTR(mod_enable, S_IWUSR | S_IRUGO, pod_get_mod_enable, + pod_set_mod_enable); +static DEVICE_ATTR(mod_param_1_note_value, S_IWUSR | S_IRUGO, + pod_get_mod_param_1_note_value, + pod_set_mod_param_1_note_value); +static DEVICE_ATTR(mod_param_2, S_IWUSR | S_IRUGO, pod_get_mod_param_2, + pod_set_mod_param_2); +static DEVICE_ATTR(mod_param_3, S_IWUSR | S_IRUGO, pod_get_mod_param_3, + pod_set_mod_param_3); +static DEVICE_ATTR(mod_param_4, S_IWUSR | S_IRUGO, pod_get_mod_param_4, + pod_set_mod_param_4); +static DEVICE_ATTR(mod_param_5, S_IWUSR | S_IRUGO, pod_get_mod_param_5, + pod_set_mod_param_5); +static DEVICE_ATTR(mod_volume_mix, S_IWUSR | S_IRUGO, pod_get_mod_volume_mix, + pod_set_mod_volume_mix); +static DEVICE_ATTR(mod_pre_post, S_IWUSR | S_IRUGO, pod_get_mod_pre_post, + pod_set_mod_pre_post); +static DEVICE_ATTR(modulation_model, S_IWUSR | S_IRUGO, + pod_get_modulation_model, pod_set_modulation_model); +static DEVICE_ATTR(band_3_frequency, S_IWUSR | S_IRUGO, + pod_get_band_3_frequency, pod_set_band_3_frequency); +static DEVICE_ATTR2(band_4_frequency__bass, band_4_frequency, S_IWUSR | S_IRUGO, + pod_get_band_4_frequency__bass, + pod_set_band_4_frequency__bass); +static DEVICE_ATTR(mod_param_1_double_precision, S_IWUSR | S_IRUGO, + pod_get_mod_param_1_double_precision, + pod_set_mod_param_1_double_precision); +static DEVICE_ATTR(delay_param_1_double_precision, S_IWUSR | S_IRUGO, + pod_get_delay_param_1_double_precision, + pod_set_delay_param_1_double_precision); +static DEVICE_ATTR(eq_enable, S_IWUSR | S_IRUGO, pod_get_eq_enable, + pod_set_eq_enable); +static DEVICE_ATTR(tap, S_IWUSR | S_IRUGO, pod_get_tap, pod_set_tap); +static DEVICE_ATTR(volume_tweak_pedal_assign, S_IWUSR | S_IRUGO, + pod_get_volume_tweak_pedal_assign, + pod_set_volume_tweak_pedal_assign); +static DEVICE_ATTR(band_5_frequency, S_IWUSR | S_IRUGO, + pod_get_band_5_frequency, pod_set_band_5_frequency); +static DEVICE_ATTR(tuner, S_IWUSR | S_IRUGO, pod_get_tuner, pod_set_tuner); +static DEVICE_ATTR(mic_selection, S_IWUSR | S_IRUGO, pod_get_mic_selection, + pod_set_mic_selection); +static DEVICE_ATTR(cabinet_model, S_IWUSR | S_IRUGO, pod_get_cabinet_model, + pod_set_cabinet_model); +static DEVICE_ATTR(stomp_model, S_IWUSR | S_IRUGO, pod_get_stomp_model, + pod_set_stomp_model); +static DEVICE_ATTR(roomlevel, S_IWUSR | S_IRUGO, pod_get_roomlevel, + pod_set_roomlevel); +static DEVICE_ATTR(band_4_frequency, S_IWUSR | S_IRUGO, + pod_get_band_4_frequency, pod_set_band_4_frequency); +static DEVICE_ATTR(band_6_frequency, S_IWUSR | S_IRUGO, + pod_get_band_6_frequency, pod_set_band_6_frequency); +static DEVICE_ATTR(stomp_param_1_note_value, S_IWUSR | S_IRUGO, + pod_get_stomp_param_1_note_value, + pod_set_stomp_param_1_note_value); +static DEVICE_ATTR(stomp_param_2, S_IWUSR | S_IRUGO, pod_get_stomp_param_2, + pod_set_stomp_param_2); +static DEVICE_ATTR(stomp_param_3, S_IWUSR | S_IRUGO, pod_get_stomp_param_3, + pod_set_stomp_param_3); +static DEVICE_ATTR(stomp_param_4, S_IWUSR | S_IRUGO, pod_get_stomp_param_4, + pod_set_stomp_param_4); +static DEVICE_ATTR(stomp_param_5, S_IWUSR | S_IRUGO, pod_get_stomp_param_5, + pod_set_stomp_param_5); +static DEVICE_ATTR(stomp_param_6, S_IWUSR | S_IRUGO, pod_get_stomp_param_6, + pod_set_stomp_param_6); +static DEVICE_ATTR(amp_switch_select, S_IWUSR | S_IRUGO, + pod_get_amp_switch_select, pod_set_amp_switch_select); +static DEVICE_ATTR(delay_param_4, S_IWUSR | S_IRUGO, pod_get_delay_param_4, + pod_set_delay_param_4); +static DEVICE_ATTR(delay_param_5, S_IWUSR | S_IRUGO, pod_get_delay_param_5, + pod_set_delay_param_5); +static DEVICE_ATTR(delay_pre_post, S_IWUSR | S_IRUGO, pod_get_delay_pre_post, + pod_set_delay_pre_post); +static DEVICE_ATTR(delay_model, S_IWUSR | S_IRUGO, pod_get_delay_model, + pod_set_delay_model); +static DEVICE_ATTR(delay_verb_model, S_IWUSR | S_IRUGO, + pod_get_delay_verb_model, pod_set_delay_verb_model); +static DEVICE_ATTR(tempo_msb, S_IWUSR | S_IRUGO, pod_get_tempo_msb, + pod_set_tempo_msb); +static DEVICE_ATTR(tempo_lsb, S_IWUSR | S_IRUGO, pod_get_tempo_lsb, + pod_set_tempo_lsb); +static DEVICE_ATTR(wah_model, S_IWUSR | S_IRUGO, pod_get_wah_model, + pod_set_wah_model); +static DEVICE_ATTR(bypass_volume, S_IWUSR | S_IRUGO, pod_get_bypass_volume, + pod_set_bypass_volume); +static DEVICE_ATTR(fx_loop_on_off, S_IWUSR | S_IRUGO, pod_get_fx_loop_on_off, + pod_set_fx_loop_on_off); +static DEVICE_ATTR(tweak_param_select, S_IWUSR | S_IRUGO, + pod_get_tweak_param_select, pod_set_tweak_param_select); +static DEVICE_ATTR(amp1_engage, S_IWUSR | S_IRUGO, pod_get_amp1_engage, + pod_set_amp1_engage); +static DEVICE_ATTR(band_1_gain, S_IWUSR | S_IRUGO, pod_get_band_1_gain, + pod_set_band_1_gain); +static DEVICE_ATTR2(band_2_gain__bass, band_2_gain, S_IWUSR | S_IRUGO, + pod_get_band_2_gain__bass, pod_set_band_2_gain__bass); +static DEVICE_ATTR(band_2_gain, S_IWUSR | S_IRUGO, pod_get_band_2_gain, + pod_set_band_2_gain); +static DEVICE_ATTR2(band_3_gain__bass, band_3_gain, S_IWUSR | S_IRUGO, + pod_get_band_3_gain__bass, pod_set_band_3_gain__bass); +static DEVICE_ATTR(band_3_gain, S_IWUSR | S_IRUGO, pod_get_band_3_gain, + pod_set_band_3_gain); +static DEVICE_ATTR2(band_4_gain__bass, band_4_gain, S_IWUSR | S_IRUGO, + pod_get_band_4_gain__bass, pod_set_band_4_gain__bass); +static DEVICE_ATTR2(band_5_gain__bass, band_5_gain, S_IWUSR | S_IRUGO, + pod_get_band_5_gain__bass, pod_set_band_5_gain__bass); +static DEVICE_ATTR(band_4_gain, S_IWUSR | S_IRUGO, pod_get_band_4_gain, + pod_set_band_4_gain); +static DEVICE_ATTR2(band_6_gain__bass, band_6_gain, S_IWUSR | S_IRUGO, + pod_get_band_6_gain__bass, pod_set_band_6_gain__bass); +static DEVICE_ATTR(body, S_IRUGO, variax_get_body, line6_nop_write); +static DEVICE_ATTR(pickup1_enable, S_IRUGO, variax_get_pickup1_enable, + line6_nop_write); +static DEVICE_ATTR(pickup1_type, S_IRUGO, variax_get_pickup1_type, + line6_nop_write); +static DEVICE_ATTR(pickup1_position, S_IRUGO, variax_get_pickup1_position, + line6_nop_write); +static DEVICE_ATTR(pickup1_angle, S_IRUGO, variax_get_pickup1_angle, + line6_nop_write); +static DEVICE_ATTR(pickup1_level, S_IRUGO, variax_get_pickup1_level, + line6_nop_write); +static DEVICE_ATTR(pickup2_enable, S_IRUGO, variax_get_pickup2_enable, + line6_nop_write); +static DEVICE_ATTR(pickup2_type, S_IRUGO, variax_get_pickup2_type, + line6_nop_write); +static DEVICE_ATTR(pickup2_position, S_IRUGO, variax_get_pickup2_position, + line6_nop_write); +static DEVICE_ATTR(pickup2_angle, S_IRUGO, variax_get_pickup2_angle, + line6_nop_write); +static DEVICE_ATTR(pickup2_level, S_IRUGO, variax_get_pickup2_level, + line6_nop_write); +static DEVICE_ATTR(pickup_phase, S_IRUGO, variax_get_pickup_phase, + line6_nop_write); +static DEVICE_ATTR(capacitance, S_IRUGO, variax_get_capacitance, + line6_nop_write); +static DEVICE_ATTR(tone_resistance, S_IRUGO, variax_get_tone_resistance, + line6_nop_write); +static DEVICE_ATTR(volume_resistance, S_IRUGO, variax_get_volume_resistance, + line6_nop_write); +static DEVICE_ATTR(taper, S_IRUGO, variax_get_taper, line6_nop_write); +static DEVICE_ATTR(tone_dump, S_IRUGO, variax_get_tone_dump, line6_nop_write); +static DEVICE_ATTR(save_tone, S_IRUGO, variax_get_save_tone, line6_nop_write); +static DEVICE_ATTR(volume_dump, S_IRUGO, variax_get_volume_dump, + line6_nop_write); +static DEVICE_ATTR(tuning_enable, S_IRUGO, variax_get_tuning_enable, + line6_nop_write); +static DEVICE_ATTR(tuning6, S_IRUGO, variax_get_tuning6, line6_nop_write); +static DEVICE_ATTR(tuning5, S_IRUGO, variax_get_tuning5, line6_nop_write); +static DEVICE_ATTR(tuning4, S_IRUGO, variax_get_tuning4, line6_nop_write); +static DEVICE_ATTR(tuning3, S_IRUGO, variax_get_tuning3, line6_nop_write); +static DEVICE_ATTR(tuning2, S_IRUGO, variax_get_tuning2, line6_nop_write); +static DEVICE_ATTR(tuning1, S_IRUGO, variax_get_tuning1, line6_nop_write); +static DEVICE_ATTR(detune6, S_IRUGO, variax_get_detune6, line6_nop_write); +static DEVICE_ATTR(detune5, S_IRUGO, variax_get_detune5, line6_nop_write); +static DEVICE_ATTR(detune4, S_IRUGO, variax_get_detune4, line6_nop_write); +static DEVICE_ATTR(detune3, S_IRUGO, variax_get_detune3, line6_nop_write); +static DEVICE_ATTR(detune2, S_IRUGO, variax_get_detune2, line6_nop_write); +static DEVICE_ATTR(detune1, S_IRUGO, variax_get_detune1, line6_nop_write); +static DEVICE_ATTR(mix6, S_IRUGO, variax_get_mix6, line6_nop_write); +static DEVICE_ATTR(mix5, S_IRUGO, variax_get_mix5, line6_nop_write); +static DEVICE_ATTR(mix4, S_IRUGO, variax_get_mix4, line6_nop_write); +static DEVICE_ATTR(mix3, S_IRUGO, variax_get_mix3, line6_nop_write); +static DEVICE_ATTR(mix2, S_IRUGO, variax_get_mix2, line6_nop_write); +static DEVICE_ATTR(mix1, S_IRUGO, variax_get_mix1, line6_nop_write); +static DEVICE_ATTR(pickup_wiring, S_IRUGO, variax_get_pickup_wiring, + line6_nop_write); + +int line6_pod_create_files(int firmware, int type, struct device *dev) +{ + int err; + CHECK_RETURN(device_create_file(dev, &dev_attr_tweak)); + CHECK_RETURN(device_create_file(dev, &dev_attr_wah_position)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file + (dev, &dev_attr_compression_gain)); + CHECK_RETURN(device_create_file(dev, &dev_attr_vol_pedal_position)); + CHECK_RETURN(device_create_file(dev, &dev_attr_compression_threshold)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pan)); + CHECK_RETURN(device_create_file(dev, &dev_attr_amp_model_setup)); + if (firmware >= 200) + CHECK_RETURN(device_create_file(dev, &dev_attr_amp_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_drive)); + CHECK_RETURN(device_create_file(dev, &dev_attr_bass)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_mid)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_lowmid)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_treble)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_highmid)); + CHECK_RETURN(device_create_file(dev, &dev_attr_chan_vol)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_mix)); + CHECK_RETURN(device_create_file(dev, &dev_attr_effect_setup)); + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_1_frequency)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_presence)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_treble__bass)); + CHECK_RETURN(device_create_file(dev, &dev_attr_noise_gate_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_gate_threshold)); + CHECK_RETURN(device_create_file(dev, &dev_attr_gate_decay_time)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_comp_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_time)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_1)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_1)); + CHECK_RETURN(device_create_file + (dev, &dev_attr_delay_param_1_note_value)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_2_frequency__bass)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_volume_mix)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_3)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_enable)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_type)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_decay)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_reverb_tone)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file + (dev, &dev_attr_reverb_pre_delay)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file + (dev, &dev_attr_reverb_pre_post)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_2_frequency)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_3_frequency__bass)); + CHECK_RETURN(device_create_file(dev, &dev_attr_wah_enable)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + CHECK_RETURN(device_create_file + (dev, &dev_attr_modulation_lo_cut)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + CHECK_RETURN(device_create_file + (dev, &dev_attr_delay_reverb_lo_cut)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_volume_pedal_minimum)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_eq_pre_post)); + CHECK_RETURN(device_create_file(dev, &dev_attr_volume_pre_post)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_di_model)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_di_delay)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_1_note_value)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_4)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_param_5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_volume_mix)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mod_pre_post)); + CHECK_RETURN(device_create_file(dev, &dev_attr_modulation_model)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_3_frequency)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_4_frequency__bass)); + CHECK_RETURN(device_create_file + (dev, &dev_attr_mod_param_1_double_precision)); + CHECK_RETURN(device_create_file + (dev, &dev_attr_delay_param_1_double_precision)); + if (firmware >= 200) + CHECK_RETURN(device_create_file(dev, &dev_attr_eq_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tap)); + CHECK_RETURN(device_create_file + (dev, &dev_attr_volume_tweak_pedal_assign)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_5_frequency)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mic_selection)); + CHECK_RETURN(device_create_file(dev, &dev_attr_cabinet_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_roomlevel)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_4_frequency)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_6_frequency)); + CHECK_RETURN(device_create_file + (dev, &dev_attr_stomp_param_1_note_value)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_stomp_param_6)); + if ((type & (LINE6_BITS_LIVE)) != 0) + CHECK_RETURN(device_create_file + (dev, &dev_attr_amp_switch_select)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_param_5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_pre_post)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_delay_model)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + CHECK_RETURN(device_create_file + (dev, &dev_attr_delay_verb_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tempo_msb)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tempo_lsb)); + if (firmware >= 300) + CHECK_RETURN(device_create_file(dev, &dev_attr_wah_model)); + if (firmware >= 214) + CHECK_RETURN(device_create_file(dev, &dev_attr_bypass_volume)); + if ((type & (LINE6_BITS_PRO)) != 0) + CHECK_RETURN(device_create_file(dev, &dev_attr_fx_loop_on_off)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tweak_param_select)); + CHECK_RETURN(device_create_file(dev, &dev_attr_amp1_engage)); + if (firmware >= 200) + CHECK_RETURN(device_create_file(dev, &dev_attr_band_1_gain)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_2_gain__bass)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_2_gain)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_3_gain__bass)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_3_gain)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_4_gain__bass)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_5_gain__bass)); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_4_gain)); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + CHECK_RETURN(device_create_file + (dev, &dev_attr_band_6_gain__bass)); + return 0; +} + +void line6_pod_remove_files(int firmware, int type, struct device *dev) +{ + device_remove_file(dev, &dev_attr_tweak); + device_remove_file(dev, &dev_attr_wah_position); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_compression_gain); + device_remove_file(dev, &dev_attr_vol_pedal_position); + device_remove_file(dev, &dev_attr_compression_threshold); + device_remove_file(dev, &dev_attr_pan); + device_remove_file(dev, &dev_attr_amp_model_setup); + if (firmware >= 200) + device_remove_file(dev, &dev_attr_amp_model); + device_remove_file(dev, &dev_attr_drive); + device_remove_file(dev, &dev_attr_bass); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_mid); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + device_remove_file(dev, &dev_attr_lowmid); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_treble); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + device_remove_file(dev, &dev_attr_highmid); + device_remove_file(dev, &dev_attr_chan_vol); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_reverb_mix); + device_remove_file(dev, &dev_attr_effect_setup); + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_1_frequency); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_presence); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + device_remove_file(dev, &dev_attr_treble__bass); + device_remove_file(dev, &dev_attr_noise_gate_enable); + device_remove_file(dev, &dev_attr_gate_threshold); + device_remove_file(dev, &dev_attr_gate_decay_time); + device_remove_file(dev, &dev_attr_stomp_enable); + device_remove_file(dev, &dev_attr_comp_enable); + device_remove_file(dev, &dev_attr_stomp_time); + device_remove_file(dev, &dev_attr_delay_enable); + device_remove_file(dev, &dev_attr_mod_param_1); + device_remove_file(dev, &dev_attr_delay_param_1); + device_remove_file(dev, &dev_attr_delay_param_1_note_value); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, + &dev_attr_band_2_frequency__bass); + device_remove_file(dev, &dev_attr_delay_param_2); + device_remove_file(dev, &dev_attr_delay_volume_mix); + device_remove_file(dev, &dev_attr_delay_param_3); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_reverb_enable); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_reverb_type); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_reverb_decay); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_reverb_tone); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_reverb_pre_delay); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_reverb_pre_post); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_2_frequency); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, + &dev_attr_band_3_frequency__bass); + device_remove_file(dev, &dev_attr_wah_enable); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + device_remove_file(dev, &dev_attr_modulation_lo_cut); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + device_remove_file(dev, &dev_attr_delay_reverb_lo_cut); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_volume_pedal_minimum); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_eq_pre_post); + device_remove_file(dev, &dev_attr_volume_pre_post); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + device_remove_file(dev, &dev_attr_di_model); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + device_remove_file(dev, &dev_attr_di_delay); + device_remove_file(dev, &dev_attr_mod_enable); + device_remove_file(dev, &dev_attr_mod_param_1_note_value); + device_remove_file(dev, &dev_attr_mod_param_2); + device_remove_file(dev, &dev_attr_mod_param_3); + device_remove_file(dev, &dev_attr_mod_param_4); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + device_remove_file(dev, &dev_attr_mod_param_5); + device_remove_file(dev, &dev_attr_mod_volume_mix); + device_remove_file(dev, &dev_attr_mod_pre_post); + device_remove_file(dev, &dev_attr_modulation_model); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_3_frequency); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, + &dev_attr_band_4_frequency__bass); + device_remove_file(dev, &dev_attr_mod_param_1_double_precision); + device_remove_file(dev, &dev_attr_delay_param_1_double_precision); + if (firmware >= 200) + device_remove_file(dev, &dev_attr_eq_enable); + device_remove_file(dev, &dev_attr_tap); + device_remove_file(dev, &dev_attr_volume_tweak_pedal_assign); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_5_frequency); + device_remove_file(dev, &dev_attr_tuner); + device_remove_file(dev, &dev_attr_mic_selection); + device_remove_file(dev, &dev_attr_cabinet_model); + device_remove_file(dev, &dev_attr_stomp_model); + device_remove_file(dev, &dev_attr_roomlevel); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_4_frequency); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_6_frequency); + device_remove_file(dev, &dev_attr_stomp_param_1_note_value); + device_remove_file(dev, &dev_attr_stomp_param_2); + device_remove_file(dev, &dev_attr_stomp_param_3); + device_remove_file(dev, &dev_attr_stomp_param_4); + device_remove_file(dev, &dev_attr_stomp_param_5); + device_remove_file(dev, &dev_attr_stomp_param_6); + if ((type & (LINE6_BITS_LIVE)) != 0) + device_remove_file(dev, &dev_attr_amp_switch_select); + device_remove_file(dev, &dev_attr_delay_param_4); + device_remove_file(dev, &dev_attr_delay_param_5); + device_remove_file(dev, &dev_attr_delay_pre_post); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + device_remove_file(dev, &dev_attr_delay_model); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + device_remove_file(dev, &dev_attr_delay_verb_model); + device_remove_file(dev, &dev_attr_tempo_msb); + device_remove_file(dev, &dev_attr_tempo_lsb); + if (firmware >= 300) + device_remove_file(dev, &dev_attr_wah_model); + if (firmware >= 214) + device_remove_file(dev, &dev_attr_bypass_volume); + if ((type & (LINE6_BITS_PRO)) != 0) + device_remove_file(dev, &dev_attr_fx_loop_on_off); + device_remove_file(dev, &dev_attr_tweak_param_select); + device_remove_file(dev, &dev_attr_amp1_engage); + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_1_gain); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_2_gain__bass); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_2_gain); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_3_gain__bass); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_3_gain); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_4_gain__bass); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_5_gain__bass); + if ((type & (LINE6_BITS_PODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_4_gain); + if ((type & (LINE6_BITS_BASSPODXTALL)) != 0) + if (firmware >= 200) + device_remove_file(dev, &dev_attr_band_6_gain__bass); +} + +int line6_variax_create_files(int firmware, int type, struct device *dev) +{ + int err; + CHECK_RETURN(device_create_file(dev, &dev_attr_body)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_type)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_position)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_angle)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup1_level)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_type)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_position)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_angle)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup2_level)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup_phase)); + CHECK_RETURN(device_create_file(dev, &dev_attr_capacitance)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tone_resistance)); + CHECK_RETURN(device_create_file(dev, &dev_attr_volume_resistance)); + CHECK_RETURN(device_create_file(dev, &dev_attr_taper)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tone_dump)); + CHECK_RETURN(device_create_file(dev, &dev_attr_save_tone)); + CHECK_RETURN(device_create_file(dev, &dev_attr_volume_dump)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning_enable)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning6)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuning1)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune6)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_detune1)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix6)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix5)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix4)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix3)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix2)); + CHECK_RETURN(device_create_file(dev, &dev_attr_mix1)); + CHECK_RETURN(device_create_file(dev, &dev_attr_pickup_wiring)); + return 0; +} + +void line6_variax_remove_files(int firmware, int type, struct device *dev) +{ + device_remove_file(dev, &dev_attr_body); + device_remove_file(dev, &dev_attr_pickup1_enable); + device_remove_file(dev, &dev_attr_pickup1_type); + device_remove_file(dev, &dev_attr_pickup1_position); + device_remove_file(dev, &dev_attr_pickup1_angle); + device_remove_file(dev, &dev_attr_pickup1_level); + device_remove_file(dev, &dev_attr_pickup2_enable); + device_remove_file(dev, &dev_attr_pickup2_type); + device_remove_file(dev, &dev_attr_pickup2_position); + device_remove_file(dev, &dev_attr_pickup2_angle); + device_remove_file(dev, &dev_attr_pickup2_level); + device_remove_file(dev, &dev_attr_pickup_phase); + device_remove_file(dev, &dev_attr_capacitance); + device_remove_file(dev, &dev_attr_tone_resistance); + device_remove_file(dev, &dev_attr_volume_resistance); + device_remove_file(dev, &dev_attr_taper); + device_remove_file(dev, &dev_attr_tone_dump); + device_remove_file(dev, &dev_attr_save_tone); + device_remove_file(dev, &dev_attr_volume_dump); + device_remove_file(dev, &dev_attr_tuning_enable); + device_remove_file(dev, &dev_attr_tuning6); + device_remove_file(dev, &dev_attr_tuning5); + device_remove_file(dev, &dev_attr_tuning4); + device_remove_file(dev, &dev_attr_tuning3); + device_remove_file(dev, &dev_attr_tuning2); + device_remove_file(dev, &dev_attr_tuning1); + device_remove_file(dev, &dev_attr_detune6); + device_remove_file(dev, &dev_attr_detune5); + device_remove_file(dev, &dev_attr_detune4); + device_remove_file(dev, &dev_attr_detune3); + device_remove_file(dev, &dev_attr_detune2); + device_remove_file(dev, &dev_attr_detune1); + device_remove_file(dev, &dev_attr_mix6); + device_remove_file(dev, &dev_attr_mix5); + device_remove_file(dev, &dev_attr_mix4); + device_remove_file(dev, &dev_attr_mix3); + device_remove_file(dev, &dev_attr_mix2); + device_remove_file(dev, &dev_attr_mix1); + device_remove_file(dev, &dev_attr_pickup_wiring); +} diff --git a/drivers/staging/line6/control.h b/drivers/staging/line6/control.h new file mode 100644 index 00000000..e4c5d2ce --- /dev/null +++ b/drivers/staging/line6/control.h @@ -0,0 +1,195 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef LINE6_CONTROL_H +#define LINE6_CONTROL_H + +/** + List of PODxt Pro controls. + See Appendix C of the "PODxt (Pro) Pilot's Handbook" by Line6. + Comments after the number refer to the PODxt Pro firmware version required + for this feature. + + Please *don't* reformat this file since "control.c" is created automatically + from "control.h", and this process depends on the exact formatting of the + code and the comments below! +*/ + +/* *INDENT-OFF* */ + +enum { + POD_tweak = 1, + POD_wah_position = 4, + POD_compression_gain = 5, /* device: LINE6_BITS_PODXTALL */ + POD_vol_pedal_position = 7, + POD_compression_threshold = 9, + POD_pan = 10, + POD_amp_model_setup = 11, + POD_amp_model = 12, /* firmware: 2.0 */ + POD_drive = 13, + POD_bass = 14, + POD_mid = 15, /* device: LINE6_BITS_PODXTALL */ + POD_lowmid = 15, /* device: LINE6_BITS_BASSPODXTALL */ + POD_treble = 16, /* device: LINE6_BITS_PODXTALL */ + POD_highmid = 16, /* device: LINE6_BITS_BASSPODXTALL */ + POD_chan_vol = 17, + POD_reverb_mix = 18, /* device: LINE6_BITS_PODXTALL */ + POD_effect_setup = 19, + POD_band_1_frequency = 20, /* firmware: 2.0 */ + POD_presence = 21, /* device: LINE6_BITS_PODXTALL */ + POD_treble__bass = 21, /* device: LINE6_BITS_BASSPODXTALL */ + POD_noise_gate_enable = 22, + POD_gate_threshold = 23, + POD_gate_decay_time = 24, + POD_stomp_enable = 25, + POD_comp_enable = 26, + POD_stomp_time = 27, + POD_delay_enable = 28, + POD_mod_param_1 = 29, + POD_delay_param_1 = 30, + POD_delay_param_1_note_value = 31, + POD_band_2_frequency__bass = 32, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_delay_param_2 = 33, + POD_delay_volume_mix = 34, + POD_delay_param_3 = 35, + POD_reverb_enable = 36, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_type = 37, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_decay = 38, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_tone = 39, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_pre_delay = 40, /* device: LINE6_BITS_PODXTALL */ + POD_reverb_pre_post = 41, /* device: LINE6_BITS_PODXTALL */ + POD_band_2_frequency = 42, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_3_frequency__bass = 42, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_wah_enable = 43, + POD_modulation_lo_cut = 44, /* device: LINE6_BITS_BASSPODXTALL */ + POD_delay_reverb_lo_cut = 45, /* device: LINE6_BITS_BASSPODXTALL */ + POD_volume_pedal_minimum = 46, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_eq_pre_post = 46, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_volume_pre_post = 47, + POD_di_model = 48, /* device: LINE6_BITS_BASSPODXTALL */ + POD_di_delay = 49, /* device: LINE6_BITS_BASSPODXTALL */ + POD_mod_enable = 50, + POD_mod_param_1_note_value = 51, + POD_mod_param_2 = 52, + POD_mod_param_3 = 53, + POD_mod_param_4 = 54, + POD_mod_param_5 = 55, /* device: LINE6_BITS_BASSPODXTALL */ + POD_mod_volume_mix = 56, + POD_mod_pre_post = 57, + POD_modulation_model = 58, + POD_band_3_frequency = 60, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_4_frequency__bass = 60, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_mod_param_1_double_precision = 61, + POD_delay_param_1_double_precision = 62, + POD_eq_enable = 63, /* firmware: 2.0 */ + POD_tap = 64, + POD_volume_tweak_pedal_assign = 65, + POD_band_5_frequency = 68, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_tuner = 69, + POD_mic_selection = 70, + POD_cabinet_model = 71, + POD_stomp_model = 75, + POD_roomlevel = 76, + POD_band_4_frequency = 77, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_6_frequency = 77, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_stomp_param_1_note_value = 78, + POD_stomp_param_2 = 79, + POD_stomp_param_3 = 80, + POD_stomp_param_4 = 81, + POD_stomp_param_5 = 82, + POD_stomp_param_6 = 83, + POD_amp_switch_select = 84, /* device: LINE6_BITS_LIVE */ + POD_delay_param_4 = 85, + POD_delay_param_5 = 86, + POD_delay_pre_post = 87, + POD_delay_model = 88, /* device: LINE6_BITS_PODXTALL */ + POD_delay_verb_model = 88, /* device: LINE6_BITS_BASSPODXTALL */ + POD_tempo_msb = 89, + POD_tempo_lsb = 90, + POD_wah_model = 91, /* firmware: 3.0 */ + POD_bypass_volume = 105, /* firmware: 2.14 */ + POD_fx_loop_on_off = 107, /* device: LINE6_BITS_PRO */ + POD_tweak_param_select = 108, + POD_amp1_engage = 111, + POD_band_1_gain = 114, /* firmware: 2.0 */ + POD_band_2_gain__bass = 115, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_band_2_gain = 116, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_3_gain__bass = 116, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_band_3_gain = 117, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_4_gain__bass = 117, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_band_5_gain__bass = 118, /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ + POD_band_4_gain = 119, /* device: LINE6_BITS_PODXTALL */ /* firmware: 2.0 */ + POD_band_6_gain__bass = 119 /* device: LINE6_BITS_BASSPODXTALL */ /* firmware: 2.0 */ +}; + +/** + List of Variax workbench controls (dump). +*/ +enum { + VARIAX_body = 3, + VARIAX_pickup1_enable = 4, /* 0: enabled, 1: disabled */ + VARIAX_pickup1_type = 8, + VARIAX_pickup1_position = 9, /* type: 24 bit float */ + VARIAX_pickup1_angle = 12, /* type: 24 bit float */ + VARIAX_pickup1_level = 15, /* type: 24 bit float */ + VARIAX_pickup2_enable = 18, /* 0: enabled, 1: disabled */ + VARIAX_pickup2_type = 22, + VARIAX_pickup2_position = 23, /* type: 24 bit float */ + VARIAX_pickup2_angle = 26, /* type: 24 bit float */ + VARIAX_pickup2_level = 29, /* type: 24 bit float */ + VARIAX_pickup_phase = 32, /* 0: in phase, 1: out of phase */ + VARIAX_capacitance = 33, /* type: 24 bit float */ + VARIAX_tone_resistance = 36, /* type: 24 bit float */ + VARIAX_volume_resistance = 39, /* type: 24 bit float */ + VARIAX_taper = 42, /* 0: Linear, 1: Audio */ + VARIAX_tone_dump = 43, /* type: 24 bit float */ + VARIAX_save_tone = 46, + VARIAX_volume_dump = 47, /* type: 24 bit float */ + VARIAX_tuning_enable = 50, + VARIAX_tuning6 = 51, + VARIAX_tuning5 = 52, + VARIAX_tuning4 = 53, + VARIAX_tuning3 = 54, + VARIAX_tuning2 = 55, + VARIAX_tuning1 = 56, + VARIAX_detune6 = 57, /* type: 24 bit float */ + VARIAX_detune5 = 60, /* type: 24 bit float */ + VARIAX_detune4 = 63, /* type: 24 bit float */ + VARIAX_detune3 = 66, /* type: 24 bit float */ + VARIAX_detune2 = 69, /* type: 24 bit float */ + VARIAX_detune1 = 72, /* type: 24 bit float */ + VARIAX_mix6 = 75, /* type: 24 bit float */ + VARIAX_mix5 = 78, /* type: 24 bit float */ + VARIAX_mix4 = 81, /* type: 24 bit float */ + VARIAX_mix3 = 84, /* type: 24 bit float */ + VARIAX_mix2 = 87, /* type: 24 bit float */ + VARIAX_mix1 = 90, /* type: 24 bit float */ + VARIAX_pickup_wiring = 96 /* 0: parallel, 1: series */ +}; + +/** + List of Variax workbench controls (MIDI). +*/ +enum { + VARIAXMIDI_volume = 7, + VARIAXMIDI_tone = 79, +}; + +/* *INDENT-ON* */ + +extern int line6_pod_create_files(int firmware, int type, struct device *dev); +extern void line6_pod_remove_files(int firmware, int type, struct device *dev); +extern int line6_variax_create_files(int firmware, int type, + struct device *dev); +extern void line6_variax_remove_files(int firmware, int type, + struct device *dev); + +#endif diff --git a/drivers/staging/line6/driver.c b/drivers/staging/line6/driver.c new file mode 100644 index 00000000..e8023afd --- /dev/null +++ b/drivers/staging/line6/driver.c @@ -0,0 +1,1362 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include + +#include "audio.h" +#include "capture.h" +#include "control.h" +#include "driver.h" +#include "midi.h" +#include "playback.h" +#include "pod.h" +#include "podhd.h" +#include "revision.h" +#include "toneport.h" +#include "usbdefs.h" +#include "variax.h" + +#define DRIVER_AUTHOR "Markus Grabner " +#define DRIVER_DESC "Line6 USB Driver" +#define DRIVER_VERSION "0.9.1beta" DRIVER_REVISION + +/* table of devices that work with this driver */ +static const struct usb_device_id line6_id_table[] = { + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_BASSPODXT)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_BASSPODXTLIVE)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_BASSPODXTPRO)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_GUITARPORT)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_POCKETPOD)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODHD300)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODHD500)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODSTUDIO_GX)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODSTUDIO_UX1)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODSTUDIO_UX2)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODX3)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODX3LIVE)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODXT)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODXTLIVE)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_PODXTPRO)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_TONEPORT_GX)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_TONEPORT_UX1)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_TONEPORT_UX2)}, + {USB_DEVICE(LINE6_VENDOR_ID, LINE6_DEVID_VARIAX)}, + {}, +}; + +MODULE_DEVICE_TABLE(usb, line6_id_table); + +/* *INDENT-OFF* */ +static struct line6_properties line6_properties_table[] = { + { LINE6_BIT_BASSPODXT, "BassPODxt", "BassPODxt", LINE6_BIT_CONTROL_PCM_HWMON }, + { LINE6_BIT_BASSPODXTLIVE, "BassPODxtLive", "BassPODxt Live", LINE6_BIT_CONTROL_PCM_HWMON }, + { LINE6_BIT_BASSPODXTPRO, "BassPODxtPro", "BassPODxt Pro", LINE6_BIT_CONTROL_PCM_HWMON }, + { LINE6_BIT_GUITARPORT, "GuitarPort", "GuitarPort", LINE6_BIT_PCM }, + { LINE6_BIT_POCKETPOD, "PocketPOD", "Pocket POD", LINE6_BIT_CONTROL }, + { LINE6_BIT_PODHD300, "PODHD300", "POD HD300", LINE6_BIT_CONTROL_PCM_HWMON }, + { LINE6_BIT_PODHD500, "PODHD500", "POD HD500", LINE6_BIT_CONTROL_PCM_HWMON }, + { LINE6_BIT_PODSTUDIO_GX, "PODStudioGX", "POD Studio GX", LINE6_BIT_PCM }, + { LINE6_BIT_PODSTUDIO_UX1, "PODStudioUX1", "POD Studio UX1", LINE6_BIT_PCM }, + { LINE6_BIT_PODSTUDIO_UX2, "PODStudioUX2", "POD Studio UX2", LINE6_BIT_PCM }, + { LINE6_BIT_PODX3, "PODX3", "POD X3", LINE6_BIT_PCM }, + { LINE6_BIT_PODX3LIVE, "PODX3Live", "POD X3 Live", LINE6_BIT_PCM }, + { LINE6_BIT_PODXT, "PODxt", "PODxt", LINE6_BIT_CONTROL_PCM_HWMON }, + { LINE6_BIT_PODXTLIVE, "PODxtLive", "PODxt Live", LINE6_BIT_CONTROL_PCM_HWMON }, + { LINE6_BIT_PODXTPRO, "PODxtPro", "PODxt Pro", LINE6_BIT_CONTROL_PCM_HWMON }, + { LINE6_BIT_TONEPORT_GX, "TonePortGX", "TonePort GX", LINE6_BIT_PCM }, + { LINE6_BIT_TONEPORT_UX1, "TonePortUX1", "TonePort UX1", LINE6_BIT_PCM }, + { LINE6_BIT_TONEPORT_UX2, "TonePortUX2", "TonePort UX2", LINE6_BIT_PCM }, + { LINE6_BIT_VARIAX, "Variax", "Variax Workbench", LINE6_BIT_CONTROL }, +}; +/* *INDENT-ON* */ + +/* + This is Line6's MIDI manufacturer ID. +*/ +const unsigned char line6_midi_id[] = { + 0x00, 0x01, 0x0c +}; + +/* + Code to request version of POD, Variax interface + (and maybe other devices). +*/ +static const char line6_request_version0[] = { + 0xf0, 0x7e, 0x7f, 0x06, 0x01, 0xf7 +}; + +/* + Copy of version request code with GFP_KERNEL flag for use in URB. +*/ +static const char *line6_request_version; + +struct usb_line6 *line6_devices[LINE6_MAX_DEVICES]; + +/** + Class for asynchronous messages. +*/ +struct message { + struct usb_line6 *line6; + const char *buffer; + int size; + int done; +}; + +/* + Forward declarations. +*/ +static void line6_data_received(struct urb *urb); +static int line6_send_raw_message_async_part(struct message *msg, + struct urb *urb); + +/* + Start to listen on endpoint. +*/ +static int line6_start_listen(struct usb_line6 *line6) +{ + int err; + usb_fill_int_urb(line6->urb_listen, line6->usbdev, + usb_rcvintpipe(line6->usbdev, line6->ep_control_read), + line6->buffer_listen, LINE6_BUFSIZE_LISTEN, + line6_data_received, line6, line6->interval); + line6->urb_listen->actual_length = 0; + err = usb_submit_urb(line6->urb_listen, GFP_ATOMIC); + return err; +} + +/* + Stop listening on endpoint. +*/ +static void line6_stop_listen(struct usb_line6 *line6) +{ + usb_kill_urb(line6->urb_listen); +} + +#ifdef CONFIG_LINE6_USB_DUMP_ANY +/* + Write hexdump to syslog. +*/ +void line6_write_hexdump(struct usb_line6 *line6, char dir, + const unsigned char *buffer, int size) +{ + static const int BYTES_PER_LINE = 8; + char hexdump[100]; + char asc[BYTES_PER_LINE + 1]; + int i, j; + + for (i = 0; i < size; i += BYTES_PER_LINE) { + int hexdumpsize = sizeof(hexdump); + char *p = hexdump; + int n = min(size - i, BYTES_PER_LINE); + asc[n] = 0; + + for (j = 0; j < BYTES_PER_LINE; ++j) { + int bytes; + + if (j < n) { + unsigned char val = buffer[i + j]; + bytes = snprintf(p, hexdumpsize, " %02X", val); + asc[j] = ((val >= 0x20) + && (val < 0x7f)) ? val : '.'; + } else + bytes = snprintf(p, hexdumpsize, " "); + + if (bytes > hexdumpsize) + break; /* buffer overflow */ + + p += bytes; + hexdumpsize -= bytes; + } + + dev_info(line6->ifcdev, "%c%04X:%s %s\n", dir, i, hexdump, asc); + } +} +#endif + +#ifdef CONFIG_LINE6_USB_DUMP_CTRL +/* + Dump URB data to syslog. +*/ +static void line6_dump_urb(struct urb *urb) +{ + struct usb_line6 *line6 = (struct usb_line6 *)urb->context; + + if (urb->status < 0) + return; + + line6_write_hexdump(line6, 'R', (unsigned char *)urb->transfer_buffer, + urb->actual_length); +} +#endif + +/* + Send raw message in pieces of wMaxPacketSize bytes. +*/ +int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, + int size) +{ + int i, done = 0; + +#ifdef CONFIG_LINE6_USB_DUMP_CTRL + line6_write_hexdump(line6, 'S', buffer, size); +#endif + + for (i = 0; i < size; i += line6->max_packet_size) { + int partial; + const char *frag_buf = buffer + i; + int frag_size = min(line6->max_packet_size, size - i); + int retval; + + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, + line6->ep_control_write), + (char *)frag_buf, frag_size, + &partial, LINE6_TIMEOUT * HZ); + + if (retval) { + dev_err(line6->ifcdev, + "usb_interrupt_msg failed (%d)\n", retval); + break; + } + + done += frag_size; + } + + return done; +} + +/* + Notification of completion of asynchronous request transmission. +*/ +static void line6_async_request_sent(struct urb *urb) +{ + struct message *msg = (struct message *)urb->context; + + if (msg->done >= msg->size) { + usb_free_urb(urb); + kfree(msg); + } else + line6_send_raw_message_async_part(msg, urb); +} + +/* + Asynchronously send part of a raw message. +*/ +static int line6_send_raw_message_async_part(struct message *msg, + struct urb *urb) +{ + int retval; + struct usb_line6 *line6 = msg->line6; + int done = msg->done; + int bytes = min(msg->size - done, line6->max_packet_size); + + usb_fill_int_urb(urb, line6->usbdev, + usb_sndintpipe(line6->usbdev, line6->ep_control_write), + (char *)msg->buffer + done, bytes, + line6_async_request_sent, msg, line6->interval); + +#ifdef CONFIG_LINE6_USB_DUMP_CTRL + line6_write_hexdump(line6, 'S', (char *)msg->buffer + done, bytes); +#endif + + msg->done += bytes; + retval = usb_submit_urb(urb, GFP_ATOMIC); + + if (retval < 0) { + dev_err(line6->ifcdev, "%s: usb_submit_urb failed (%d)\n", + __func__, retval); + usb_free_urb(urb); + kfree(msg); + return -EINVAL; + } + + return 0; +} + +/* + Setup and start timer. +*/ +void line6_start_timer(struct timer_list *timer, unsigned int msecs, + void (*function) (unsigned long), unsigned long data) +{ + setup_timer(timer, function, data); + timer->expires = jiffies + msecs * HZ / 1000; + add_timer(timer); +} + +/* + Asynchronously send raw message. +*/ +int line6_send_raw_message_async(struct usb_line6 *line6, const char *buffer, + int size) +{ + struct message *msg; + struct urb *urb; + + /* create message: */ + msg = kmalloc(sizeof(struct message), GFP_ATOMIC); + + if (msg == NULL) { + dev_err(line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + /* create URB: */ + urb = usb_alloc_urb(0, GFP_ATOMIC); + + if (urb == NULL) { + kfree(msg); + dev_err(line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + /* set message data: */ + msg->line6 = line6; + msg->buffer = buffer; + msg->size = size; + msg->done = 0; + + /* start sending: */ + return line6_send_raw_message_async_part(msg, urb); +} + +/* + Send asynchronous device version request. +*/ +int line6_version_request_async(struct usb_line6 *line6) +{ + return line6_send_raw_message_async(line6, line6_request_version, + sizeof(line6_request_version0)); +} + +/* + Send sysex message in pieces of wMaxPacketSize bytes. +*/ +int line6_send_sysex_message(struct usb_line6 *line6, const char *buffer, + int size) +{ + return line6_send_raw_message(line6, buffer, + size + SYSEX_EXTRA_SIZE) - + SYSEX_EXTRA_SIZE; +} + +/* + Send sysex message in pieces of wMaxPacketSize bytes. +*/ +int line6_send_sysex_message_async(struct usb_line6 *line6, const char *buffer, + int size) +{ + return line6_send_raw_message_async(line6, buffer, + size + SYSEX_EXTRA_SIZE) - + SYSEX_EXTRA_SIZE; +} + +/* + Allocate buffer for sysex message and prepare header. + @param code sysex message code + @param size number of bytes between code and sysex end +*/ +char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1, int code2, + int size) +{ + char *buffer = kmalloc(size + SYSEX_EXTRA_SIZE, GFP_ATOMIC); + + if (!buffer) { + dev_err(line6->ifcdev, "out of memory\n"); + return NULL; + } + + buffer[0] = LINE6_SYSEX_BEGIN; + memcpy(buffer + 1, line6_midi_id, sizeof(line6_midi_id)); + buffer[sizeof(line6_midi_id) + 1] = code1; + buffer[sizeof(line6_midi_id) + 2] = code2; + buffer[sizeof(line6_midi_id) + 3 + size] = LINE6_SYSEX_END; + return buffer; +} + +/* + Notification of data received from the Line6 device. +*/ +static void line6_data_received(struct urb *urb) +{ + struct usb_line6 *line6 = (struct usb_line6 *)urb->context; + struct MidiBuffer *mb = &line6->line6midi->midibuf_in; + int done; + + if (urb->status == -ESHUTDOWN) + return; + +#ifdef CONFIG_LINE6_USB_DUMP_CTRL + line6_dump_urb(urb); +#endif + + done = + line6_midibuf_write(mb, urb->transfer_buffer, urb->actual_length); + + if (done < urb->actual_length) { + line6_midibuf_ignore(mb, done); + DEBUG_MESSAGES(dev_err + (line6->ifcdev, + "%d %d buffer overflow - message skipped\n", + done, urb->actual_length)); + } + + for (;;) { + done = + line6_midibuf_read(mb, line6->buffer_message, + LINE6_MESSAGE_MAXLEN); + + if (done == 0) + break; + + /* MIDI input filter */ + if (line6_midibuf_skip_message + (mb, line6->line6midi->midi_mask_receive)) + continue; + + line6->message_length = done; +#ifdef CONFIG_LINE6_USB_DUMP_MIDI + line6_write_hexdump(line6, 'r', line6->buffer_message, done); +#endif + line6_midi_receive(line6, line6->buffer_message, done); + + switch (line6->usbdev->descriptor.idProduct) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + case LINE6_DEVID_POCKETPOD: + line6_pod_process_message((struct usb_line6_pod *) + line6); + break; + + case LINE6_DEVID_PODHD300: + case LINE6_DEVID_PODHD500: + break; /* let userspace handle MIDI */ + + case LINE6_DEVID_PODXTLIVE: + switch (line6->interface_number) { + case PODXTLIVE_INTERFACE_POD: + line6_pod_process_message((struct usb_line6_pod + *)line6); + break; + + case PODXTLIVE_INTERFACE_VARIAX: + line6_variax_process_message((struct + usb_line6_variax + *)line6); + break; + + default: + dev_err(line6->ifcdev, + "PODxt Live interface %d not supported\n", + line6->interface_number); + } + break; + + case LINE6_DEVID_VARIAX: + line6_variax_process_message((struct usb_line6_variax *) + line6); + break; + + default: + MISSING_CASE; + } + } + + line6_start_listen(line6); +} + +/* + Send channel number (i.e., switch to a different sound). +*/ +int line6_send_program(struct usb_line6 *line6, int value) +{ + int retval; + unsigned char *buffer; + int partial; + + buffer = kmalloc(2, GFP_KERNEL); + + if (!buffer) { + dev_err(line6->ifcdev, "out of memory\n"); + return -ENOMEM; + } + + buffer[0] = LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST; + buffer[1] = value; + +#ifdef CONFIG_LINE6_USB_DUMP_CTRL + line6_write_hexdump(line6, 'S', buffer, 2); +#endif + + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, + line6->ep_control_write), + buffer, 2, &partial, LINE6_TIMEOUT * HZ); + + if (retval) + dev_err(line6->ifcdev, "usb_interrupt_msg failed (%d)\n", + retval); + + kfree(buffer); + return retval; +} + +/* + Transmit Line6 control parameter. +*/ +int line6_transmit_parameter(struct usb_line6 *line6, int param, int value) +{ + int retval; + unsigned char *buffer; + int partial; + + buffer = kmalloc(3, GFP_KERNEL); + + if (!buffer) { + dev_err(line6->ifcdev, "out of memory\n"); + return -ENOMEM; + } + + buffer[0] = LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST; + buffer[1] = param; + buffer[2] = value; + +#ifdef CONFIG_LINE6_USB_DUMP_CTRL + line6_write_hexdump(line6, 'S', buffer, 3); +#endif + + retval = usb_interrupt_msg(line6->usbdev, + usb_sndintpipe(line6->usbdev, + line6->ep_control_write), + buffer, 3, &partial, LINE6_TIMEOUT * HZ); + + if (retval) + dev_err(line6->ifcdev, "usb_interrupt_msg failed (%d)\n", + retval); + + kfree(buffer); + return retval; +} + +/* + Read data from device. +*/ +int line6_read_data(struct usb_line6 *line6, int address, void *data, + size_t datalen) +{ + struct usb_device *usbdev = line6->usbdev; + int ret; + unsigned char len; + + /* query the serial number: */ + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + (datalen << 8) | 0x21, address, + NULL, 0, LINE6_TIMEOUT * HZ); + + if (ret < 0) { + dev_err(line6->ifcdev, "read request failed (error %d)\n", ret); + return ret; + } + + /* Wait for data length. We'll get a couple of 0xff until length arrives. */ + do { + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | + USB_DIR_IN, + 0x0012, 0x0000, &len, 1, + LINE6_TIMEOUT * HZ); + if (ret < 0) { + dev_err(line6->ifcdev, + "receive length failed (error %d)\n", ret); + return ret; + } + } while (len == 0xff); + + if (len != datalen) { + /* should be equal or something went wrong */ + dev_err(line6->ifcdev, + "length mismatch (expected %d, got %d)\n", + (int)datalen, (int)len); + return -EINVAL; + } + + /* receive the result: */ + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x0013, 0x0000, data, datalen, + LINE6_TIMEOUT * HZ); + + if (ret < 0) { + dev_err(line6->ifcdev, "read failed (error %d)\n", ret); + return ret; + } + + return 0; +} + +/* + Write data to device. +*/ +int line6_write_data(struct usb_line6 *line6, int address, void *data, + size_t datalen) +{ + struct usb_device *usbdev = line6->usbdev; + int ret; + unsigned char status; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + 0x0022, address, data, datalen, + LINE6_TIMEOUT * HZ); + + if (ret < 0) { + dev_err(line6->ifcdev, + "write request failed (error %d)\n", ret); + return ret; + } + + do { + ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), + 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | + USB_DIR_IN, + 0x0012, 0x0000, + &status, 1, LINE6_TIMEOUT * HZ); + + if (ret < 0) { + dev_err(line6->ifcdev, + "receiving status failed (error %d)\n", ret); + return ret; + } + } while (status == 0xff); + + if (status != 0) { + dev_err(line6->ifcdev, "write failed (error %d)\n", ret); + return -EINVAL; + } + + return 0; +} + +/* + Read Line6 device serial number. + (POD, TonePort, GuitarPort) +*/ +int line6_read_serial_number(struct usb_line6 *line6, int *serial_number) +{ + return line6_read_data(line6, 0x80d0, serial_number, + sizeof(*serial_number)); +} + +/* + No operation (i.e., unsupported). +*/ +ssize_t line6_nop_read(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +/* + No operation (i.e., unsupported). +*/ +ssize_t line6_nop_write(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +/* + "write" request on "raw" special file. +*/ +#ifdef CONFIG_LINE6_USB_RAW +ssize_t line6_set_raw(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + line6_send_raw_message(line6, buf, count); + return count; +} +#endif + +/* + Generic destructor. +*/ +static void line6_destruct(struct usb_interface *interface) +{ + struct usb_line6 *line6; + + if (interface == NULL) + return; + line6 = usb_get_intfdata(interface); + if (line6 == NULL) + return; + + /* free buffer memory first: */ + kfree(line6->buffer_message); + kfree(line6->buffer_listen); + + /* then free URBs: */ + usb_free_urb(line6->urb_listen); + + /* make sure the device isn't destructed twice: */ + usb_set_intfdata(interface, NULL); + + /* free interface data: */ + kfree(line6); +} + +/* + Probe USB device. +*/ +static int line6_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int devtype; + struct usb_device *usbdev; + struct usb_line6 *line6; + const struct line6_properties *properties; + int devnum; + int interface_number, alternate = 0; + int product; + int size = 0; + int ep_read = 0, ep_write = 0; + int ret; + + if (interface == NULL) + return -ENODEV; + usbdev = interface_to_usbdev(interface); + if (usbdev == NULL) + return -ENODEV; + + /* we don't handle multiple configurations */ + if (usbdev->descriptor.bNumConfigurations != 1) { + ret = -ENODEV; + goto err_put; + } + + /* check vendor and product id */ + for (devtype = ARRAY_SIZE(line6_id_table) - 1; devtype--;) { + u16 idVendor = le16_to_cpu(usbdev->descriptor.idVendor); + u16 idProduct = le16_to_cpu(usbdev->descriptor.idProduct); + + if (idVendor == line6_id_table[devtype].idVendor && + idProduct == line6_id_table[devtype].idProduct) + break; + } + + if (devtype < 0) { + ret = -ENODEV; + goto err_put; + } + + /* find free slot in device table: */ + for (devnum = 0; devnum < LINE6_MAX_DEVICES; ++devnum) + if (line6_devices[devnum] == NULL) + break; + + if (devnum == LINE6_MAX_DEVICES) { + ret = -ENODEV; + goto err_put; + } + + /* initialize device info: */ + properties = &line6_properties_table[devtype]; + dev_info(&interface->dev, "Line6 %s found\n", properties->name); + product = le16_to_cpu(usbdev->descriptor.idProduct); + + /* query interface number */ + interface_number = interface->cur_altsetting->desc.bInterfaceNumber; + + switch (product) { + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_PODXTLIVE: + case LINE6_DEVID_VARIAX: + alternate = 1; + break; + + case LINE6_DEVID_POCKETPOD: + switch (interface_number) { + case 0: + return 0; /* this interface has no endpoints */ + case 1: + alternate = 0; + break; + default: + MISSING_CASE; + } + break; + + case LINE6_DEVID_PODHD500: + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + switch (interface_number) { + case 0: + alternate = 1; + break; + case 1: + alternate = 0; + break; + default: + MISSING_CASE; + } + break; + + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + case LINE6_DEVID_PODHD300: + alternate = 5; + break; + + case LINE6_DEVID_GUITARPORT: + case LINE6_DEVID_PODSTUDIO_GX: + case LINE6_DEVID_PODSTUDIO_UX1: + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_TONEPORT_UX1: + alternate = 2; /* 1..4 seem to be ok */ + break; + + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_PODSTUDIO_UX2: + switch (interface_number) { + case 0: + /* defaults to 44.1kHz, 16-bit */ + alternate = 2; + break; + case 1: + /* don't know yet what this is ... + alternate = 1; + break; + */ + return -ENODEV; + default: + MISSING_CASE; + } + break; + + default: + MISSING_CASE; + ret = -ENODEV; + goto err_put; + } + + ret = usb_set_interface(usbdev, interface_number, alternate); + if (ret < 0) { + dev_err(&interface->dev, "set_interface failed\n"); + goto err_put; + } + + /* initialize device data based on product id: */ + switch (product) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + size = sizeof(struct usb_line6_pod); + ep_read = 0x84; + ep_write = 0x03; + break; + + case LINE6_DEVID_PODHD300: + size = sizeof(struct usb_line6_podhd); + ep_read = 0x84; + ep_write = 0x03; + break; + + case LINE6_DEVID_PODHD500: + size = sizeof(struct usb_line6_podhd); + ep_read = 0x81; + ep_write = 0x01; + break; + + case LINE6_DEVID_POCKETPOD: + size = sizeof(struct usb_line6_pod); + ep_read = 0x82; + ep_write = 0x02; + break; + + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + /* currently unused! */ + size = sizeof(struct usb_line6_pod); + ep_read = 0x81; + ep_write = 0x01; + break; + + case LINE6_DEVID_PODSTUDIO_GX: + case LINE6_DEVID_PODSTUDIO_UX1: + case LINE6_DEVID_PODSTUDIO_UX2: + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_GUITARPORT: + size = sizeof(struct usb_line6_toneport); + /* these don't have a control channel */ + break; + + case LINE6_DEVID_PODXTLIVE: + switch (interface_number) { + case PODXTLIVE_INTERFACE_POD: + size = sizeof(struct usb_line6_pod); + ep_read = 0x84; + ep_write = 0x03; + break; + + case PODXTLIVE_INTERFACE_VARIAX: + size = sizeof(struct usb_line6_variax); + ep_read = 0x86; + ep_write = 0x05; + break; + + default: + ret = -ENODEV; + goto err_put; + } + break; + + case LINE6_DEVID_VARIAX: + size = sizeof(struct usb_line6_variax); + ep_read = 0x82; + ep_write = 0x01; + break; + + default: + MISSING_CASE; + ret = -ENODEV; + goto err_put; + } + + if (size == 0) { + dev_err(&interface->dev, + "driver bug: interface data size not set\n"); + ret = -ENODEV; + goto err_put; + } + + line6 = kzalloc(size, GFP_KERNEL); + + if (line6 == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + ret = -ENODEV; + goto err_put; + } + + /* store basic data: */ + line6->interface_number = interface_number; + line6->properties = properties; + line6->usbdev = usbdev; + line6->ifcdev = &interface->dev; + line6->ep_control_read = ep_read; + line6->ep_control_write = ep_write; + line6->product = product; + + /* get data from endpoint descriptor (see usb_maxpacket): */ + { + struct usb_host_endpoint *ep; + unsigned epnum = + usb_pipeendpoint(usb_rcvintpipe(usbdev, ep_read)); + ep = usbdev->ep_in[epnum]; + + if (ep != NULL) { + line6->interval = ep->desc.bInterval; + line6->max_packet_size = + le16_to_cpu(ep->desc.wMaxPacketSize); + } else { + line6->interval = LINE6_FALLBACK_INTERVAL; + line6->max_packet_size = LINE6_FALLBACK_MAXPACKETSIZE; + dev_err(line6->ifcdev, + "endpoint not available, using fallback values"); + } + } + + usb_set_intfdata(interface, line6); + + if (properties->capabilities & LINE6_BIT_CONTROL) { + /* initialize USB buffers: */ + line6->buffer_listen = + kmalloc(LINE6_BUFSIZE_LISTEN, GFP_KERNEL); + + if (line6->buffer_listen == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + ret = -ENOMEM; + goto err_destruct; + } + + line6->buffer_message = + kmalloc(LINE6_MESSAGE_MAXLEN, GFP_KERNEL); + + if (line6->buffer_message == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + ret = -ENOMEM; + goto err_destruct; + } + + line6->urb_listen = usb_alloc_urb(0, GFP_KERNEL); + + if (line6->urb_listen == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + line6_destruct(interface); + ret = -ENOMEM; + goto err_destruct; + } + + ret = line6_start_listen(line6); + if (ret < 0) { + dev_err(&interface->dev, "%s: usb_submit_urb failed\n", + __func__); + goto err_destruct; + } + } + + /* initialize device data based on product id: */ + switch (product) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_POCKETPOD: + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + ret = line6_pod_init(interface, (struct usb_line6_pod *)line6); + break; + + case LINE6_DEVID_PODHD300: + case LINE6_DEVID_PODHD500: + ret = line6_podhd_init(interface, + (struct usb_line6_podhd *)line6); + break; + + case LINE6_DEVID_PODXTLIVE: + switch (interface_number) { + case PODXTLIVE_INTERFACE_POD: + ret = + line6_pod_init(interface, + (struct usb_line6_pod *)line6); + break; + + case PODXTLIVE_INTERFACE_VARIAX: + ret = + line6_variax_init(interface, + (struct usb_line6_variax *)line6); + break; + + default: + dev_err(&interface->dev, + "PODxt Live interface %d not supported\n", + interface_number); + ret = -ENODEV; + } + + break; + + case LINE6_DEVID_VARIAX: + ret = + line6_variax_init(interface, + (struct usb_line6_variax *)line6); + break; + + case LINE6_DEVID_PODSTUDIO_GX: + case LINE6_DEVID_PODSTUDIO_UX1: + case LINE6_DEVID_PODSTUDIO_UX2: + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_GUITARPORT: + ret = + line6_toneport_init(interface, + (struct usb_line6_toneport *)line6); + break; + + default: + MISSING_CASE; + ret = -ENODEV; + } + + if (ret < 0) + goto err_destruct; + + ret = sysfs_create_link(&interface->dev.kobj, &usbdev->dev.kobj, + "usb_device"); + if (ret < 0) + goto err_destruct; + + /* creation of additional special files should go here */ + + dev_info(&interface->dev, "Line6 %s now attached\n", + line6->properties->name); + line6_devices[devnum] = line6; + + switch (product) { + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + dev_info(&interface->dev, + "NOTE: the Line6 %s is detected, but not yet supported\n", + line6->properties->name); + } + + /* increment reference counters: */ + usb_get_intf(interface); + usb_get_dev(usbdev); + + return 0; + +err_destruct: + line6_destruct(interface); +err_put: + return ret; +} + +/* + Line6 device disconnected. +*/ +static void line6_disconnect(struct usb_interface *interface) +{ + struct usb_line6 *line6; + struct usb_device *usbdev; + int interface_number, i; + + if (interface == NULL) + return; + usbdev = interface_to_usbdev(interface); + if (usbdev == NULL) + return; + + /* removal of additional special files should go here */ + + sysfs_remove_link(&interface->dev.kobj, "usb_device"); + + interface_number = interface->cur_altsetting->desc.bInterfaceNumber; + line6 = usb_get_intfdata(interface); + + if (line6 != NULL) { + if (line6->urb_listen != NULL) + line6_stop_listen(line6); + + if (usbdev != line6->usbdev) + dev_err(line6->ifcdev, + "driver bug: inconsistent usb device\n"); + + switch (line6->usbdev->descriptor.idProduct) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_POCKETPOD: + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTPRO: + line6_pod_disconnect(interface); + break; + + case LINE6_DEVID_PODHD300: + case LINE6_DEVID_PODHD500: + line6_podhd_disconnect(interface); + break; + + case LINE6_DEVID_PODXTLIVE: + switch (interface_number) { + case PODXTLIVE_INTERFACE_POD: + line6_pod_disconnect(interface); + break; + + case PODXTLIVE_INTERFACE_VARIAX: + line6_variax_disconnect(interface); + break; + } + + break; + + case LINE6_DEVID_VARIAX: + line6_variax_disconnect(interface); + break; + + case LINE6_DEVID_PODSTUDIO_GX: + case LINE6_DEVID_PODSTUDIO_UX1: + case LINE6_DEVID_PODSTUDIO_UX2: + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_GUITARPORT: + line6_toneport_disconnect(interface); + break; + + default: + MISSING_CASE; + } + + dev_info(&interface->dev, "Line6 %s now disconnected\n", + line6->properties->name); + + for (i = LINE6_MAX_DEVICES; i--;) + if (line6_devices[i] == line6) + line6_devices[i] = NULL; + } + + line6_destruct(interface); + + /* decrement reference counters: */ + usb_put_intf(interface); + usb_put_dev(usbdev); +} + +#ifdef CONFIG_PM + +/* + Suspend Line6 device. +*/ +static int line6_suspend(struct usb_interface *interface, pm_message_t message) +{ + struct usb_line6 *line6 = usb_get_intfdata(interface); + struct snd_line6_pcm *line6pcm = line6->line6pcm; + + snd_power_change_state(line6->card, SNDRV_CTL_POWER_D3hot); + + if (line6->properties->capabilities & LINE6_BIT_CONTROL) + line6_stop_listen(line6); + + if (line6pcm != NULL) { + snd_pcm_suspend_all(line6pcm->pcm); + line6_pcm_disconnect(line6pcm); + line6pcm->flags = 0; + } + + return 0; +} + +/* + Resume Line6 device. +*/ +static int line6_resume(struct usb_interface *interface) +{ + struct usb_line6 *line6 = usb_get_intfdata(interface); + + if (line6->properties->capabilities & LINE6_BIT_CONTROL) + line6_start_listen(line6); + + snd_power_change_state(line6->card, SNDRV_CTL_POWER_D0); + return 0; +} + +/* + Resume Line6 device after reset. +*/ +static int line6_reset_resume(struct usb_interface *interface) +{ + struct usb_line6 *line6 = usb_get_intfdata(interface); + + switch (line6->usbdev->descriptor.idProduct) { + case LINE6_DEVID_PODSTUDIO_GX: + case LINE6_DEVID_PODSTUDIO_UX1: + case LINE6_DEVID_PODSTUDIO_UX2: + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_GUITARPORT: + line6_toneport_reset_resume((struct usb_line6_toneport *)line6); + } + + return line6_resume(interface); +} + +#endif /* CONFIG_PM */ + +static struct usb_driver line6_driver = { + .name = DRIVER_NAME, + .probe = line6_probe, + .disconnect = line6_disconnect, +#ifdef CONFIG_PM + .suspend = line6_suspend, + .resume = line6_resume, + .reset_resume = line6_reset_resume, +#endif + .id_table = line6_id_table, +}; + +/* + Module initialization. +*/ +static int __init line6_init(void) +{ + int i, retval; + + printk(KERN_INFO "%s driver version %s\n", DRIVER_NAME, DRIVER_VERSION); + + for (i = LINE6_MAX_DEVICES; i--;) + line6_devices[i] = NULL; + + retval = usb_register(&line6_driver); + + if (retval) { + err("usb_register failed. Error number %d", retval); + return retval; + } + + line6_request_version = kmalloc(sizeof(line6_request_version0), + GFP_KERNEL); + + if (line6_request_version == NULL) { + err("Out of memory"); + return -ENOMEM; + } + + memcpy((char *)line6_request_version, line6_request_version0, + sizeof(line6_request_version0)); + + return retval; +} + +/* + Module cleanup. +*/ +static void __exit line6_exit(void) +{ + int i; + struct usb_line6 *line6; + struct snd_line6_pcm *line6pcm; + + /* stop all PCM channels */ + for (i = LINE6_MAX_DEVICES; i--;) { + line6 = line6_devices[i]; + + if (line6 == NULL) + continue; + + line6pcm = line6->line6pcm; + + if (line6pcm == NULL) + continue; + + line6_pcm_release(line6pcm, ~0); + } + + usb_deregister(&line6_driver); + kfree(line6_request_version); +} + +module_init(line6_init); +module_exit(line6_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); diff --git a/drivers/staging/line6/driver.h b/drivers/staging/line6/driver.h new file mode 100644 index 00000000..117bf994 --- /dev/null +++ b/drivers/staging/line6/driver.h @@ -0,0 +1,237 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef DRIVER_H +#define DRIVER_H + +#include +#include +#include + +#include "midi.h" + +#define DRIVER_NAME "line6usb" + +#if defined(CONFIG_LINE6_USB_DUMP_CTRL) || defined(CONFIG_LINE6_USB_DUMP_MIDI) || defined(CONFIG_LINE6_USB_DUMP_PCM) +#define CONFIG_LINE6_USB_DUMP_ANY +#endif + +#define LINE6_TIMEOUT 1 +#define LINE6_MAX_DEVICES 8 +#define LINE6_BUFSIZE_LISTEN 32 +#define LINE6_MESSAGE_MAXLEN 256 + +/* + Line6 MIDI control commands +*/ +#define LINE6_PARAM_CHANGE 0xb0 +#define LINE6_PROGRAM_CHANGE 0xc0 +#define LINE6_SYSEX_BEGIN 0xf0 +#define LINE6_SYSEX_END 0xf7 +#define LINE6_RESET 0xff + +/* + MIDI channel for messages initiated by the host + (and eventually echoed back by the device) +*/ +#define LINE6_CHANNEL_HOST 0x00 + +/* + MIDI channel for messages initiated by the device +*/ +#define LINE6_CHANNEL_DEVICE 0x02 + +#define LINE6_CHANNEL_UNKNOWN 5 /* don't know yet what this is good for */ + +#define LINE6_CHANNEL_MASK 0x0f + +#ifdef CONFIG_LINE6_USB_DEBUG +#define DEBUG_MESSAGES(x) (x) +#else +#define DEBUG_MESSAGES(x) +#endif + +#define MISSING_CASE \ + printk(KERN_ERR "line6usb driver bug: missing case in %s:%d\n", \ + __FILE__, __LINE__) + +#define CHECK_RETURN(x) \ +do { \ + err = x; \ + if (err < 0) \ + return err; \ +} while (0) + +#define CHECK_STARTUP_PROGRESS(x, n) \ +do { \ + if ((x) >= (n)) \ + return; \ + x = (n); \ +} while (0) + +extern const unsigned char line6_midi_id[3]; +extern struct usb_line6 *line6_devices[LINE6_MAX_DEVICES]; + +static const int SYSEX_DATA_OFS = sizeof(line6_midi_id) + 3; +static const int SYSEX_EXTRA_SIZE = sizeof(line6_midi_id) + 4; + +/** + Common properties of Line6 devices. +*/ +struct line6_properties { + /** + Bit identifying this device in the line6usb driver. + */ + int device_bit; + + /** + Card id string (maximum 16 characters). + This can be used to address the device in ALSA programs as + "default:CARD=" + */ + const char *id; + + /** + Card short name (maximum 32 characters). + */ + const char *name; + + /** + Bit vector defining this device's capabilities in the + line6usb driver. + */ + int capabilities; +}; + +/** + Common data shared by all Line6 devices. + Corresponds to a pair of USB endpoints. +*/ +struct usb_line6 { + /** + USB device. + */ + struct usb_device *usbdev; + + /** + Product id. + */ + int product; + + /** + Properties. + */ + const struct line6_properties *properties; + + /** + Interface number. + */ + int interface_number; + + /** + Interval (ms). + */ + int interval; + + /** + Maximum size of USB packet. + */ + int max_packet_size; + + /** + Device representing the USB interface. + */ + struct device *ifcdev; + + /** + Line6 sound card data structure. + Each device has at least MIDI or PCM. + */ + struct snd_card *card; + + /** + Line6 PCM device data structure. + */ + struct snd_line6_pcm *line6pcm; + + /** + Line6 MIDI device data structure. + */ + struct snd_line6_midi *line6midi; + + /** + USB endpoint for listening to control commands. + */ + int ep_control_read; + + /** + USB endpoint for writing control commands. + */ + int ep_control_write; + + /** + URB for listening to PODxt Pro control endpoint. + */ + struct urb *urb_listen; + + /** + Buffer for listening to PODxt Pro control endpoint. + */ + unsigned char *buffer_listen; + + /** + Buffer for message to be processed. + */ + unsigned char *buffer_message; + + /** + Length of message to be processed. + */ + int message_length; +}; + +extern char *line6_alloc_sysex_buffer(struct usb_line6 *line6, int code1, + int code2, int size); +extern ssize_t line6_nop_read(struct device *dev, + struct device_attribute *attr, char *buf); +extern ssize_t line6_nop_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); +extern int line6_read_data(struct usb_line6 *line6, int address, void *data, + size_t datalen); +extern int line6_read_serial_number(struct usb_line6 *line6, + int *serial_number); +extern int line6_send_program(struct usb_line6 *line6, int value); +extern int line6_send_raw_message(struct usb_line6 *line6, const char *buffer, + int size); +extern int line6_send_raw_message_async(struct usb_line6 *line6, + const char *buffer, int size); +extern int line6_send_sysex_message(struct usb_line6 *line6, + const char *buffer, int size); +extern int line6_send_sysex_message_async(struct usb_line6 *line6, + const char *buffer, int size); +extern ssize_t line6_set_raw(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +extern void line6_start_timer(struct timer_list *timer, unsigned int msecs, + void (*function) (unsigned long), + unsigned long data); +extern int line6_transmit_parameter(struct usb_line6 *line6, int param, + int value); +extern int line6_version_request_async(struct usb_line6 *line6); +extern int line6_write_data(struct usb_line6 *line6, int address, void *data, + size_t datalen); + +#ifdef CONFIG_LINE6_USB_DUMP_ANY +extern void line6_write_hexdump(struct usb_line6 *line6, char dir, + const unsigned char *buffer, int size); +#endif + +#endif diff --git a/drivers/staging/line6/dumprequest.c b/drivers/staging/line6/dumprequest.c new file mode 100644 index 00000000..60c7bae3 --- /dev/null +++ b/drivers/staging/line6/dumprequest.c @@ -0,0 +1,135 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include + +#include "driver.h" +#include "dumprequest.h" + +/* + Set "dump in progress" flag. +*/ +void line6_dump_started(struct line6_dump_request *l6dr, int dest) +{ + l6dr->in_progress = dest; +} + +/* + Invalidate current channel, i.e., set "dump in progress" flag. + Reading from the "dump" special file blocks until dump is completed. +*/ +void line6_invalidate_current(struct line6_dump_request *l6dr) +{ + line6_dump_started(l6dr, LINE6_DUMP_CURRENT); +} + +/* + Clear "dump in progress" flag and notify waiting processes. +*/ +void line6_dump_finished(struct line6_dump_request *l6dr) +{ + l6dr->in_progress = LINE6_DUMP_NONE; + wake_up(&l6dr->wait); +} + +/* + Send an asynchronous channel dump request. +*/ +int line6_dump_request_async(struct line6_dump_request *l6dr, + struct usb_line6 *line6, int num, int dest) +{ + int ret; + line6_dump_started(l6dr, dest); + ret = line6_send_raw_message_async(line6, l6dr->reqbufs[num].buffer, + l6dr->reqbufs[num].length); + + if (ret < 0) + line6_dump_finished(l6dr); + + return ret; +} + +/* + Wait for completion (interruptible). +*/ +int line6_dump_wait_interruptible(struct line6_dump_request *l6dr) +{ + return wait_event_interruptible(l6dr->wait, + l6dr->in_progress == LINE6_DUMP_NONE); +} + +/* + Wait for completion. +*/ +void line6_dump_wait(struct line6_dump_request *l6dr) +{ + wait_event(l6dr->wait, l6dr->in_progress == LINE6_DUMP_NONE); +} + +/* + Wait for completion (with timeout). +*/ +int line6_dump_wait_timeout(struct line6_dump_request *l6dr, long timeout) +{ + return wait_event_timeout(l6dr->wait, + l6dr->in_progress == LINE6_DUMP_NONE, + timeout); +} + +/* + Initialize dump request buffer. +*/ +int line6_dumpreq_initbuf(struct line6_dump_request *l6dr, const void *buf, + size_t len, int num) +{ + l6dr->reqbufs[num].buffer = kmemdup(buf, len, GFP_KERNEL); + if (l6dr->reqbufs[num].buffer == NULL) + return -ENOMEM; + l6dr->reqbufs[num].length = len; + return 0; +} + +/* + Initialize dump request data structure (including one buffer). +*/ +int line6_dumpreq_init(struct line6_dump_request *l6dr, const void *buf, + size_t len) +{ + int ret; + ret = line6_dumpreq_initbuf(l6dr, buf, len, 0); + if (ret < 0) + return ret; + init_waitqueue_head(&l6dr->wait); + return 0; +} + +/* + Destruct dump request data structure. +*/ +void line6_dumpreq_destructbuf(struct line6_dump_request *l6dr, int num) +{ + if (l6dr == NULL) + return; + if (l6dr->reqbufs[num].buffer == NULL) + return; + kfree(l6dr->reqbufs[num].buffer); + l6dr->reqbufs[num].buffer = NULL; +} + +/* + Destruct dump request data structure. +*/ +void line6_dumpreq_destruct(struct line6_dump_request *l6dr) +{ + if (l6dr->reqbufs[0].buffer == NULL) + return; + line6_dumpreq_destructbuf(l6dr, 0); +} diff --git a/drivers/staging/line6/dumprequest.h b/drivers/staging/line6/dumprequest.h new file mode 100644 index 00000000..c17a262f --- /dev/null +++ b/drivers/staging/line6/dumprequest.h @@ -0,0 +1,76 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef DUMPREQUEST_H +#define DUMPREQUEST_H + +#include +#include +#include + +enum { + LINE6_DUMP_NONE, + LINE6_DUMP_CURRENT +}; + +struct line6_dump_reqbuf { + /** + Buffer for dump requests. + */ + unsigned char *buffer; + + /** + Size of dump request. + */ + size_t length; +}; + +/** + Provides the functionality to request channel/model/... dump data from a + Line6 device. +*/ +struct line6_dump_request { + /** + Wait queue for access to program dump data. + */ + wait_queue_head_t wait; + + /** + Indicates an unfinished program dump request. + 0: no dump + 1: dump current settings + Other device-specific values are also allowed. + */ + int in_progress; + + /** + Dump request buffers + */ + struct line6_dump_reqbuf reqbufs[1]; +}; + +extern void line6_dump_finished(struct line6_dump_request *l6dr); +extern int line6_dump_request_async(struct line6_dump_request *l6dr, + struct usb_line6 *line6, int num, int dest); +extern void line6_dump_started(struct line6_dump_request *l6dr, int dest); +extern void line6_dumpreq_destruct(struct line6_dump_request *l6dr); +extern void line6_dumpreq_destructbuf(struct line6_dump_request *l6dr, int num); +extern int line6_dumpreq_init(struct line6_dump_request *l6dr, const void *buf, + size_t len); +extern int line6_dumpreq_initbuf(struct line6_dump_request *l6dr, + const void *buf, size_t len, int num); +extern void line6_invalidate_current(struct line6_dump_request *l6dr); +extern void line6_dump_wait(struct line6_dump_request *l6dr); +extern int line6_dump_wait_interruptible(struct line6_dump_request *l6dr); +extern int line6_dump_wait_timeout(struct line6_dump_request *l6dr, + long timeout); + +#endif diff --git a/drivers/staging/line6/midi.c b/drivers/staging/line6/midi.c new file mode 100644 index 00000000..13d02939 --- /dev/null +++ b/drivers/staging/line6/midi.c @@ -0,0 +1,446 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include + +#include "audio.h" +#include "driver.h" +#include "midi.h" +#include "pod.h" +#include "usbdefs.h" + +#define line6_rawmidi_substream_midi(substream) \ + ((struct snd_line6_midi *)((substream)->rmidi->private_data)) + +static int send_midi_async(struct usb_line6 *line6, unsigned char *data, + int length); + +/* + Pass data received via USB to MIDI. +*/ +void line6_midi_receive(struct usb_line6 *line6, unsigned char *data, + int length) +{ + if (line6->line6midi->substream_receive) + snd_rawmidi_receive(line6->line6midi->substream_receive, + data, length); +} + +/* + Read data from MIDI buffer and transmit them via USB. +*/ +static void line6_midi_transmit(struct snd_rawmidi_substream *substream) +{ + struct usb_line6 *line6 = + line6_rawmidi_substream_midi(substream)->line6; + struct snd_line6_midi *line6midi = line6->line6midi; + struct MidiBuffer *mb = &line6midi->midibuf_out; + unsigned long flags; + unsigned char chunk[line6->max_packet_size]; + int req, done; + + spin_lock_irqsave(&line6->line6midi->midi_transmit_lock, flags); + + for (;;) { + req = min(line6_midibuf_bytes_free(mb), line6->max_packet_size); + done = snd_rawmidi_transmit_peek(substream, chunk, req); + + if (done == 0) + break; + +#ifdef CONFIG_LINE6_USB_DUMP_MIDI + line6_write_hexdump(line6, 's', chunk, done); +#endif + line6_midibuf_write(mb, chunk, done); + snd_rawmidi_transmit_ack(substream, done); + } + + for (;;) { + done = line6_midibuf_read(mb, chunk, line6->max_packet_size); + + if (done == 0) + break; + + if (line6_midibuf_skip_message + (mb, line6midi->midi_mask_transmit)) + continue; + + send_midi_async(line6, chunk, done); + } + + spin_unlock_irqrestore(&line6->line6midi->midi_transmit_lock, flags); +} + +/* + Notification of completion of MIDI transmission. +*/ +static void midi_sent(struct urb *urb) +{ + unsigned long flags; + int status; + int num; + struct usb_line6 *line6 = (struct usb_line6 *)urb->context; + + status = urb->status; + kfree(urb->transfer_buffer); + usb_free_urb(urb); + + if (status == -ESHUTDOWN) + return; + + spin_lock_irqsave(&line6->line6midi->send_urb_lock, flags); + num = --line6->line6midi->num_active_send_urbs; + + if (num == 0) { + line6_midi_transmit(line6->line6midi->substream_transmit); + num = line6->line6midi->num_active_send_urbs; + } + + if (num == 0) + wake_up(&line6->line6midi->send_wait); + + spin_unlock_irqrestore(&line6->line6midi->send_urb_lock, flags); +} + +/* + Send an asynchronous MIDI message. + Assumes that line6->line6midi->send_urb_lock is held + (i.e., this function is serialized). +*/ +static int send_midi_async(struct usb_line6 *line6, unsigned char *data, + int length) +{ + struct urb *urb; + int retval; + unsigned char *transfer_buffer; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + + if (urb == NULL) { + dev_err(line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } +#ifdef CONFIG_LINE6_USB_DUMP_CTRL + line6_write_hexdump(line6, 'S', data, length); +#endif + + transfer_buffer = kmemdup(data, length, GFP_ATOMIC); + + if (transfer_buffer == NULL) { + usb_free_urb(urb); + dev_err(line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + usb_fill_int_urb(urb, line6->usbdev, + usb_sndbulkpipe(line6->usbdev, + line6->ep_control_write), + transfer_buffer, length, midi_sent, line6, + line6->interval); + urb->actual_length = 0; + retval = usb_submit_urb(urb, GFP_ATOMIC); + + if (retval < 0) { + dev_err(line6->ifcdev, "usb_submit_urb failed\n"); + usb_free_urb(urb); + return -EINVAL; + } + + ++line6->line6midi->num_active_send_urbs; + + switch (line6->usbdev->descriptor.idProduct) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTLIVE: + case LINE6_DEVID_PODXTPRO: + case LINE6_DEVID_POCKETPOD: + line6_pod_midi_postprocess((struct usb_line6_pod *)line6, data, + length); + break; + + case LINE6_DEVID_VARIAX: + case LINE6_DEVID_PODHD300: + case LINE6_DEVID_PODHD500: + break; + + default: + MISSING_CASE; + } + + return 0; +} + +static int line6_midi_output_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int line6_midi_output_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void line6_midi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + unsigned long flags; + struct usb_line6 *line6 = + line6_rawmidi_substream_midi(substream)->line6; + + line6->line6midi->substream_transmit = substream; + spin_lock_irqsave(&line6->line6midi->send_urb_lock, flags); + + if (line6->line6midi->num_active_send_urbs == 0) + line6_midi_transmit(substream); + + spin_unlock_irqrestore(&line6->line6midi->send_urb_lock, flags); +} + +static void line6_midi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct usb_line6 *line6 = + line6_rawmidi_substream_midi(substream)->line6; + struct snd_line6_midi *midi = line6->line6midi; + wait_event_interruptible(midi->send_wait, + midi->num_active_send_urbs == 0); +} + +static int line6_midi_input_open(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static int line6_midi_input_close(struct snd_rawmidi_substream *substream) +{ + return 0; +} + +static void line6_midi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct usb_line6 *line6 = + line6_rawmidi_substream_midi(substream)->line6; + + if (up) + line6->line6midi->substream_receive = substream; + else + line6->line6midi->substream_receive = 0; +} + +static struct snd_rawmidi_ops line6_midi_output_ops = { + .open = line6_midi_output_open, + .close = line6_midi_output_close, + .trigger = line6_midi_output_trigger, + .drain = line6_midi_output_drain, +}; + +static struct snd_rawmidi_ops line6_midi_input_ops = { + .open = line6_midi_input_open, + .close = line6_midi_input_close, + .trigger = line6_midi_input_trigger, +}; + +/* + Cleanup the Line6 MIDI device. +*/ +static void line6_cleanup_midi(struct snd_rawmidi *rmidi) +{ +} + +/* Create a MIDI device */ +static int snd_line6_new_midi(struct snd_line6_midi *line6midi) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(line6midi->line6->card, "Line6 MIDI", 0, 1, 1, + &rmidi); + if (err < 0) + return err; + + rmidi->private_data = line6midi; + rmidi->private_free = line6_cleanup_midi; + strcpy(rmidi->id, line6midi->line6->properties->id); + strcpy(rmidi->name, line6midi->line6->properties->name); + + rmidi->info_flags = + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &line6_midi_output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &line6_midi_input_ops); + return 0; +} + +/* + "read" request on "midi_mask_transmit" special file. +*/ +static ssize_t midi_get_midi_mask_transmit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", line6->line6midi->midi_mask_transmit); +} + +/* + "write" request on "midi_mask" special file. +*/ +static ssize_t midi_set_midi_mask_transmit(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + unsigned short value; + int ret; + + ret = kstrtou16(buf, 10, &value); + if (ret) + return ret; + + line6->line6midi->midi_mask_transmit = value; + return count; +} + +/* + "read" request on "midi_mask_receive" special file. +*/ +static ssize_t midi_get_midi_mask_receive(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", line6->line6midi->midi_mask_receive); +} + +/* + "write" request on "midi_mask" special file. +*/ +static ssize_t midi_set_midi_mask_receive(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + unsigned short value; + int ret; + + ret = kstrtou16(buf, 10, &value); + if (ret) + return ret; + + line6->line6midi->midi_mask_receive = value; + return count; +} + +static DEVICE_ATTR(midi_mask_transmit, S_IWUSR | S_IRUGO, + midi_get_midi_mask_transmit, midi_set_midi_mask_transmit); +static DEVICE_ATTR(midi_mask_receive, S_IWUSR | S_IRUGO, + midi_get_midi_mask_receive, midi_set_midi_mask_receive); + +/* MIDI device destructor */ +static int snd_line6_midi_free(struct snd_device *device) +{ + struct snd_line6_midi *line6midi = device->device_data; + device_remove_file(line6midi->line6->ifcdev, + &dev_attr_midi_mask_transmit); + device_remove_file(line6midi->line6->ifcdev, + &dev_attr_midi_mask_receive); + line6_midibuf_destroy(&line6midi->midibuf_in); + line6_midibuf_destroy(&line6midi->midibuf_out); + return 0; +} + +/* + Initialize the Line6 MIDI subsystem. +*/ +int line6_init_midi(struct usb_line6 *line6) +{ + static struct snd_device_ops midi_ops = { + .dev_free = snd_line6_midi_free, + }; + + int err; + struct snd_line6_midi *line6midi; + + if (!(line6->properties->capabilities & LINE6_BIT_CONTROL)) { + /* skip MIDI initialization and report success */ + return 0; + } + + line6midi = kzalloc(sizeof(struct snd_line6_midi), GFP_KERNEL); + + if (line6midi == NULL) + return -ENOMEM; + + err = line6_midibuf_init(&line6midi->midibuf_in, MIDI_BUFFER_SIZE, 0); + if (err < 0) { + kfree(line6midi); + return err; + } + + err = line6_midibuf_init(&line6midi->midibuf_out, MIDI_BUFFER_SIZE, 1); + if (err < 0) { + kfree(line6midi->midibuf_in.buf); + kfree(line6midi); + return err; + } + + line6midi->line6 = line6; + + switch(line6->product) { + case LINE6_DEVID_PODHD300: + case LINE6_DEVID_PODHD500: + line6midi->midi_mask_transmit = 1; + line6midi->midi_mask_receive = 1; + break; + + default: + line6midi->midi_mask_transmit = 1; + line6midi->midi_mask_receive = 4; + } + + line6->line6midi = line6midi; + + err = snd_device_new(line6->card, SNDRV_DEV_RAWMIDI, line6midi, + &midi_ops); + if (err < 0) + return err; + + snd_card_set_dev(line6->card, line6->ifcdev); + + err = snd_line6_new_midi(line6midi); + if (err < 0) + return err; + + err = device_create_file(line6->ifcdev, &dev_attr_midi_mask_transmit); + if (err < 0) + return err; + + err = device_create_file(line6->ifcdev, &dev_attr_midi_mask_receive); + if (err < 0) + return err; + + init_waitqueue_head(&line6midi->send_wait); + spin_lock_init(&line6midi->send_urb_lock); + spin_lock_init(&line6midi->midi_transmit_lock); + return 0; +} diff --git a/drivers/staging/line6/midi.h b/drivers/staging/line6/midi.h new file mode 100644 index 00000000..4a9e9f94 --- /dev/null +++ b/drivers/staging/line6/midi.h @@ -0,0 +1,82 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef MIDI_H +#define MIDI_H + +#include + +#include "midibuf.h" + +#define MIDI_BUFFER_SIZE 1024 + +struct snd_line6_midi { + /** + Pointer back to the Line6 driver data structure. + */ + struct usb_line6 *line6; + + /** + MIDI substream for receiving (or NULL if not active). + */ + struct snd_rawmidi_substream *substream_receive; + + /** + MIDI substream for transmitting (or NULL if not active). + */ + struct snd_rawmidi_substream *substream_transmit; + + /** + Number of currently active MIDI send URBs. + */ + int num_active_send_urbs; + + /** + Spin lock to protect updates of send_urb. + */ + spinlock_t send_urb_lock; + + /** + Spin lock to protect MIDI buffer handling. + */ + spinlock_t midi_transmit_lock; + + /** + Wait queue for MIDI transmission. + */ + wait_queue_head_t send_wait; + + /** + Bit mask for output MIDI channels. + */ + unsigned short midi_mask_transmit; + + /** + Bit mask for input MIDI channels. + */ + unsigned short midi_mask_receive; + + /** + Buffer for incoming MIDI stream. + */ + struct MidiBuffer midibuf_in; + + /** + Buffer for outgoing MIDI stream. + */ + struct MidiBuffer midibuf_out; +}; + +extern int line6_init_midi(struct usb_line6 *line6); +extern void line6_midi_receive(struct usb_line6 *line6, unsigned char *data, + int length); + +#endif diff --git a/drivers/staging/line6/midibuf.c b/drivers/staging/line6/midibuf.c new file mode 100644 index 00000000..7b532e5c --- /dev/null +++ b/drivers/staging/line6/midibuf.c @@ -0,0 +1,264 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include + +#include "midibuf.h" + +static int midibuf_message_length(unsigned char code) +{ + if (code < 0x80) + return -1; + else if (code < 0xf0) { + static const int length[] = { 3, 3, 3, 3, 2, 2, 3 }; + return length[(code >> 4) - 8]; + } else { + /* + Note that according to the MIDI specification 0xf2 is + the "Song Position Pointer", but this is used by Line6 + to send sysex messages to the host. + */ + static const int length[] = { -1, 2, -1, 2, -1, -1, 1, 1, 1, 1, + 1, 1, 1, -1, 1, 1 + }; + return length[code & 0x0f]; + } +} + +static int midibuf_is_empty(struct MidiBuffer *this) +{ + return (this->pos_read == this->pos_write) && !this->full; +} + +static int midibuf_is_full(struct MidiBuffer *this) +{ + return this->full; +} + +void line6_midibuf_reset(struct MidiBuffer *this) +{ + this->pos_read = this->pos_write = this->full = 0; + this->command_prev = -1; +} + +int line6_midibuf_init(struct MidiBuffer *this, int size, int split) +{ + this->buf = kmalloc(size, GFP_KERNEL); + + if (this->buf == NULL) + return -ENOMEM; + + this->size = size; + this->split = split; + line6_midibuf_reset(this); + return 0; +} + +void line6_midibuf_status(struct MidiBuffer *this) +{ + printk(KERN_DEBUG "midibuf size=%d split=%d pos_read=%d pos_write=%d " + "full=%d command_prev=%02x\n", this->size, this->split, + this->pos_read, this->pos_write, this->full, this->command_prev); +} + +int line6_midibuf_bytes_free(struct MidiBuffer *this) +{ + return + midibuf_is_full(this) ? + 0 : + (this->pos_read - this->pos_write + this->size - 1) % this->size + + 1; +} + +int line6_midibuf_bytes_used(struct MidiBuffer *this) +{ + return + midibuf_is_empty(this) ? + 0 : + (this->pos_write - this->pos_read + this->size - 1) % this->size + + 1; +} + +int line6_midibuf_write(struct MidiBuffer *this, unsigned char *data, + int length) +{ + int bytes_free; + int length1, length2; + int skip_active_sense = 0; + + if (midibuf_is_full(this) || (length <= 0)) + return 0; + + /* skip trailing active sense */ + if (data[length - 1] == 0xfe) { + --length; + skip_active_sense = 1; + } + + bytes_free = line6_midibuf_bytes_free(this); + + if (length > bytes_free) + length = bytes_free; + + if (length > 0) { + length1 = this->size - this->pos_write; + + if (length < length1) { + /* no buffer wraparound */ + memcpy(this->buf + this->pos_write, data, length); + this->pos_write += length; + } else { + /* buffer wraparound */ + length2 = length - length1; + memcpy(this->buf + this->pos_write, data, length1); + memcpy(this->buf, data + length1, length2); + this->pos_write = length2; + } + + if (this->pos_write == this->pos_read) + this->full = 1; + } + + return length + skip_active_sense; +} + +int line6_midibuf_read(struct MidiBuffer *this, unsigned char *data, int length) +{ + int bytes_used; + int length1, length2; + int command; + int midi_length; + int repeat = 0; + int i; + + /* we need to be able to store at least a 3 byte MIDI message */ + if (length < 3) + return -EINVAL; + + if (midibuf_is_empty(this)) + return 0; + + bytes_used = line6_midibuf_bytes_used(this); + + if (length > bytes_used) + length = bytes_used; + + length1 = this->size - this->pos_read; + + /* check MIDI command length */ + command = this->buf[this->pos_read]; + + if (command & 0x80) { + midi_length = midibuf_message_length(command); + this->command_prev = command; + } else { + if (this->command_prev > 0) { + int midi_length_prev = + midibuf_message_length(this->command_prev); + + if (midi_length_prev > 0) { + midi_length = midi_length_prev - 1; + repeat = 1; + } else + midi_length = -1; + } else + midi_length = -1; + } + + if (midi_length < 0) { + /* search for end of message */ + if (length < length1) { + /* no buffer wraparound */ + for (i = 1; i < length; ++i) + if (this->buf[this->pos_read + i] & 0x80) + break; + + midi_length = i; + } else { + /* buffer wraparound */ + length2 = length - length1; + + for (i = 1; i < length1; ++i) + if (this->buf[this->pos_read + i] & 0x80) + break; + + if (i < length1) + midi_length = i; + else { + for (i = 0; i < length2; ++i) + if (this->buf[i] & 0x80) + break; + + midi_length = length1 + i; + } + } + + if (midi_length == length) + midi_length = -1; /* end of message not found */ + } + + if (midi_length < 0) { + if (!this->split) + return 0; /* command is not yet complete */ + } else { + if (length < midi_length) + return 0; /* command is not yet complete */ + + length = midi_length; + } + + if (length < length1) { + /* no buffer wraparound */ + memcpy(data + repeat, this->buf + this->pos_read, length); + this->pos_read += length; + } else { + /* buffer wraparound */ + length2 = length - length1; + memcpy(data + repeat, this->buf + this->pos_read, length1); + memcpy(data + repeat + length1, this->buf, length2); + this->pos_read = length2; + } + + if (repeat) + data[0] = this->command_prev; + + this->full = 0; + return length + repeat; +} + +int line6_midibuf_ignore(struct MidiBuffer *this, int length) +{ + int bytes_used = line6_midibuf_bytes_used(this); + + if (length > bytes_used) + length = bytes_used; + + this->pos_read = (this->pos_read + length) % this->size; + this->full = 0; + return length; +} + +int line6_midibuf_skip_message(struct MidiBuffer *this, unsigned short mask) +{ + int cmd = this->command_prev; + + if ((cmd >= 0x80) && (cmd < 0xf0)) + if ((mask & (1 << (cmd & 0x0f))) == 0) + return 1; + + return 0; +} + +void line6_midibuf_destroy(struct MidiBuffer *this) +{ + kfree(this->buf); + this->buf = NULL; +} diff --git a/drivers/staging/line6/midibuf.h b/drivers/staging/line6/midibuf.h new file mode 100644 index 00000000..444cb3a1 --- /dev/null +++ b/drivers/staging/line6/midibuf.h @@ -0,0 +1,38 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef MIDIBUF_H +#define MIDIBUF_H + +struct MidiBuffer { + unsigned char *buf; + int size; + int split; + int pos_read, pos_write; + int full; + int command_prev; +}; + +extern int line6_midibuf_bytes_used(struct MidiBuffer *mb); +extern int line6_midibuf_bytes_free(struct MidiBuffer *mb); +extern void line6_midibuf_destroy(struct MidiBuffer *mb); +extern int line6_midibuf_ignore(struct MidiBuffer *mb, int length); +extern int line6_midibuf_init(struct MidiBuffer *mb, int size, int split); +extern int line6_midibuf_read(struct MidiBuffer *mb, unsigned char *data, + int length); +extern void line6_midibuf_reset(struct MidiBuffer *mb); +extern int line6_midibuf_skip_message(struct MidiBuffer *mb, + unsigned short mask); +extern void line6_midibuf_status(struct MidiBuffer *mb); +extern int line6_midibuf_write(struct MidiBuffer *mb, unsigned char *data, + int length); + +#endif diff --git a/drivers/staging/line6/pcm.c b/drivers/staging/line6/pcm.c new file mode 100644 index 00000000..90d2d447 --- /dev/null +++ b/drivers/staging/line6/pcm.c @@ -0,0 +1,564 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include +#include + +#include "audio.h" +#include "capture.h" +#include "driver.h" +#include "playback.h" +#include "pod.h" + +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + +static struct snd_line6_pcm *dev2pcm(struct device *dev) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6 *line6 = usb_get_intfdata(interface); + struct snd_line6_pcm *line6pcm = line6->line6pcm; + return line6pcm; +} + +/* + "read" request on "impulse_volume" special file. +*/ +static ssize_t pcm_get_impulse_volume(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", dev2pcm(dev)->impulse_volume); +} + +/* + "write" request on "impulse_volume" special file. +*/ +static ssize_t pcm_set_impulse_volume(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct snd_line6_pcm *line6pcm = dev2pcm(dev); + int value = simple_strtoul(buf, NULL, 10); + line6pcm->impulse_volume = value; + + if (value > 0) + line6_pcm_acquire(line6pcm, LINE6_BITS_PCM_IMPULSE); + else + line6_pcm_release(line6pcm, LINE6_BITS_PCM_IMPULSE); + + return count; +} + +/* + "read" request on "impulse_period" special file. +*/ +static ssize_t pcm_get_impulse_period(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", dev2pcm(dev)->impulse_period); +} + +/* + "write" request on "impulse_period" special file. +*/ +static ssize_t pcm_set_impulse_period(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + dev2pcm(dev)->impulse_period = simple_strtoul(buf, NULL, 10); + return count; +} + +static DEVICE_ATTR(impulse_volume, S_IWUSR | S_IRUGO, pcm_get_impulse_volume, + pcm_set_impulse_volume); +static DEVICE_ATTR(impulse_period, S_IWUSR | S_IRUGO, pcm_get_impulse_period, + pcm_set_impulse_period); + +#endif + +static bool test_flags(unsigned long flags0, unsigned long flags1, + unsigned long mask) +{ + return ((flags0 & mask) == 0) && ((flags1 & mask) != 0); +} + +int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int channels) +{ + unsigned long flags_old = + __sync_fetch_and_or(&line6pcm->flags, channels); + unsigned long flags_new = flags_old | channels; + unsigned long flags_final = flags_old; + int err = 0; + + line6pcm->prev_fbuf = NULL; + + if (test_flags(flags_old, flags_new, LINE6_BITS_CAPTURE_BUFFER)) { + /* We may be invoked multiple times in a row so allocate once only */ + if (!line6pcm->buffer_in) { + line6pcm->buffer_in = + kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * + line6pcm->max_packet_size, GFP_KERNEL); + + if (!line6pcm->buffer_in) { + dev_err(line6pcm->line6->ifcdev, + "cannot malloc capture buffer\n"); + err = -ENOMEM; + goto pcm_acquire_error; + } + + flags_final |= channels & LINE6_BITS_CAPTURE_BUFFER; + } + } + + if (test_flags(flags_old, flags_new, LINE6_BITS_CAPTURE_STREAM)) { + /* + Waiting for completion of active URBs in the stop handler is + a bug, we therefore report an error if capturing is restarted + too soon. + */ + if (line6pcm->active_urb_in | line6pcm->unlink_urb_in) { + dev_err(line6pcm->line6->ifcdev, "Device not yet ready\n"); + return -EBUSY; + } + + line6pcm->count_in = 0; + line6pcm->prev_fsize = 0; + err = line6_submit_audio_in_all_urbs(line6pcm); + + if (err < 0) + goto pcm_acquire_error; + + flags_final |= channels & LINE6_BITS_CAPTURE_STREAM; + } + + if (test_flags(flags_old, flags_new, LINE6_BITS_PLAYBACK_BUFFER)) { + /* We may be invoked multiple times in a row so allocate once only */ + if (!line6pcm->buffer_out) { + line6pcm->buffer_out = + kmalloc(LINE6_ISO_BUFFERS * LINE6_ISO_PACKETS * + line6pcm->max_packet_size, GFP_KERNEL); + + if (!line6pcm->buffer_out) { + dev_err(line6pcm->line6->ifcdev, + "cannot malloc playback buffer\n"); + err = -ENOMEM; + goto pcm_acquire_error; + } + + flags_final |= channels & LINE6_BITS_PLAYBACK_BUFFER; + } + } + + if (test_flags(flags_old, flags_new, LINE6_BITS_PLAYBACK_STREAM)) { + /* + See comment above regarding PCM restart. + */ + if (line6pcm->active_urb_out | line6pcm->unlink_urb_out) { + dev_err(line6pcm->line6->ifcdev, "Device not yet ready\n"); + return -EBUSY; + } + + line6pcm->count_out = 0; + err = line6_submit_audio_out_all_urbs(line6pcm); + + if (err < 0) + goto pcm_acquire_error; + + flags_final |= channels & LINE6_BITS_PLAYBACK_STREAM; + } + + return 0; + +pcm_acquire_error: + /* + If not all requested resources/streams could be obtained, release + those which were successfully obtained (if any). + */ + line6_pcm_release(line6pcm, flags_final & channels); + return err; +} + +int line6_pcm_release(struct snd_line6_pcm *line6pcm, int channels) +{ + unsigned long flags_old = + __sync_fetch_and_and(&line6pcm->flags, ~channels); + unsigned long flags_new = flags_old & ~channels; + + if (test_flags(flags_new, flags_old, LINE6_BITS_CAPTURE_STREAM)) + line6_unlink_audio_in_urbs(line6pcm); + + if (test_flags(flags_new, flags_old, LINE6_BITS_CAPTURE_BUFFER)) { + line6_wait_clear_audio_in_urbs(line6pcm); + line6_free_capture_buffer(line6pcm); + } + + if (test_flags(flags_new, flags_old, LINE6_BITS_PLAYBACK_STREAM)) + line6_unlink_audio_out_urbs(line6pcm); + + if (test_flags(flags_new, flags_old, LINE6_BITS_PLAYBACK_BUFFER)) { + line6_wait_clear_audio_out_urbs(line6pcm); + line6_free_playback_buffer(line6pcm); + } + + return 0; +} + +/* trigger callback */ +int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + struct snd_pcm_substream *s; + int err; + unsigned long flags; + + spin_lock_irqsave(&line6pcm->lock_trigger, flags); + clear_bit(LINE6_INDEX_PREPARED, &line6pcm->flags); + + snd_pcm_group_for_each_entry(s, substream) { + switch (s->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + err = snd_line6_playback_trigger(line6pcm, cmd); + + if (err < 0) { + spin_unlock_irqrestore(&line6pcm->lock_trigger, + flags); + return err; + } + + break; + + case SNDRV_PCM_STREAM_CAPTURE: + err = snd_line6_capture_trigger(line6pcm, cmd); + + if (err < 0) { + spin_unlock_irqrestore(&line6pcm->lock_trigger, + flags); + return err; + } + + break; + + default: + dev_err(line6pcm->line6->ifcdev, + "Unknown stream direction %d\n", s->stream); + } + } + + spin_unlock_irqrestore(&line6pcm->lock_trigger, flags); + return 0; +} + +/* control info callback */ +static int snd_line6_control_playback_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 = 256; + return 0; +} + +/* control get callback */ +static int snd_line6_control_playback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i; + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + for (i = 2; i--;) + ucontrol->value.integer.value[i] = line6pcm->volume_playback[i]; + + return 0; +} + +/* control put callback */ +static int snd_line6_control_playback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int i, changed = 0; + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + for (i = 2; i--;) + if (line6pcm->volume_playback[i] != + ucontrol->value.integer.value[i]) { + line6pcm->volume_playback[i] = + ucontrol->value.integer.value[i]; + changed = 1; + } + + return changed; +} + +/* control definition */ +static struct snd_kcontrol_new line6_control_playback = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_line6_control_playback_info, + .get = snd_line6_control_playback_get, + .put = snd_line6_control_playback_put +}; + +/* + Cleanup the PCM device. +*/ +static void line6_cleanup_pcm(struct snd_pcm *pcm) +{ + int i; + struct snd_line6_pcm *line6pcm = snd_pcm_chip(pcm); + +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + device_remove_file(line6pcm->line6->ifcdev, &dev_attr_impulse_volume); + device_remove_file(line6pcm->line6->ifcdev, &dev_attr_impulse_period); +#endif + + for (i = LINE6_ISO_BUFFERS; i--;) { + if (line6pcm->urb_audio_out[i]) { + usb_kill_urb(line6pcm->urb_audio_out[i]); + usb_free_urb(line6pcm->urb_audio_out[i]); + } + if (line6pcm->urb_audio_in[i]) { + usb_kill_urb(line6pcm->urb_audio_in[i]); + usb_free_urb(line6pcm->urb_audio_in[i]); + } + } +} + +/* create a PCM device */ +static int snd_line6_new_pcm(struct snd_line6_pcm *line6pcm) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(line6pcm->line6->card, + (char *)line6pcm->line6->properties->name, + 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = line6pcm; + pcm->private_free = line6_cleanup_pcm; + line6pcm->pcm = pcm; + strcpy(pcm->name, line6pcm->line6->properties->name); + + /* set operators */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_line6_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_line6_capture_ops); + + /* pre-allocation of buffers */ + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data + (GFP_KERNEL), 64 * 1024, + 128 * 1024); + + return 0; +} + +/* PCM device destructor */ +static int snd_line6_pcm_free(struct snd_device *device) +{ + return 0; +} + +/* + Stop substream if still running. +*/ +static void pcm_disconnect_substream(struct snd_pcm_substream *substream) +{ + if (substream->runtime && snd_pcm_running(substream)) + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); +} + +/* + Stop PCM stream. +*/ +void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm) +{ + pcm_disconnect_substream(get_substream + (line6pcm, SNDRV_PCM_STREAM_CAPTURE)); + pcm_disconnect_substream(get_substream + (line6pcm, SNDRV_PCM_STREAM_PLAYBACK)); + line6_unlink_wait_clear_audio_out_urbs(line6pcm); + line6_unlink_wait_clear_audio_in_urbs(line6pcm); +} + +/* + Create and register the PCM device and mixer entries. + Create URBs for playback and capture. +*/ +int line6_init_pcm(struct usb_line6 *line6, + struct line6_pcm_properties *properties) +{ + static struct snd_device_ops pcm_ops = { + .dev_free = snd_line6_pcm_free, + }; + + int err; + int ep_read = 0, ep_write = 0; + struct snd_line6_pcm *line6pcm; + + if (!(line6->properties->capabilities & LINE6_BIT_PCM)) + return 0; /* skip PCM initialization and report success */ + + /* initialize PCM subsystem based on product id: */ + switch (line6->product) { + case LINE6_DEVID_BASSPODXT: + case LINE6_DEVID_BASSPODXTLIVE: + case LINE6_DEVID_BASSPODXTPRO: + case LINE6_DEVID_PODXT: + case LINE6_DEVID_PODXTLIVE: + case LINE6_DEVID_PODXTPRO: + case LINE6_DEVID_PODHD300: + ep_read = 0x82; + ep_write = 0x01; + break; + + case LINE6_DEVID_PODHD500: + case LINE6_DEVID_PODX3: + case LINE6_DEVID_PODX3LIVE: + ep_read = 0x86; + ep_write = 0x02; + break; + + case LINE6_DEVID_POCKETPOD: + ep_read = 0x82; + ep_write = 0x02; + break; + + case LINE6_DEVID_GUITARPORT: + case LINE6_DEVID_PODSTUDIO_GX: + case LINE6_DEVID_PODSTUDIO_UX1: + case LINE6_DEVID_PODSTUDIO_UX2: + case LINE6_DEVID_TONEPORT_GX: + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + ep_read = 0x82; + ep_write = 0x01; + break; + + /* this is for interface_number == 1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_PODSTUDIO_UX2: + ep_read = 0x87; + ep_write = 0x00; + break; + */ + + default: + MISSING_CASE; + } + + line6pcm = kzalloc(sizeof(struct snd_line6_pcm), GFP_KERNEL); + + if (line6pcm == NULL) + return -ENOMEM; + + line6pcm->volume_playback[0] = line6pcm->volume_playback[1] = 255; + line6pcm->volume_monitor = 255; + line6pcm->line6 = line6; + line6pcm->ep_audio_read = ep_read; + line6pcm->ep_audio_write = ep_write; + + /* Read and write buffers are sized identically, so choose minimum */ + line6pcm->max_packet_size = min( + usb_maxpacket(line6->usbdev, + usb_rcvisocpipe(line6->usbdev, ep_read), 0), + usb_maxpacket(line6->usbdev, + usb_sndisocpipe(line6->usbdev, ep_write), 1)); + + line6pcm->properties = properties; + line6->line6pcm = line6pcm; + + /* PCM device: */ + err = snd_device_new(line6->card, SNDRV_DEV_PCM, line6, &pcm_ops); + if (err < 0) + return err; + + snd_card_set_dev(line6->card, line6->ifcdev); + + err = snd_line6_new_pcm(line6pcm); + if (err < 0) + return err; + + spin_lock_init(&line6pcm->lock_audio_out); + spin_lock_init(&line6pcm->lock_audio_in); + spin_lock_init(&line6pcm->lock_trigger); + + err = line6_create_audio_out_urbs(line6pcm); + if (err < 0) + return err; + + err = line6_create_audio_in_urbs(line6pcm); + if (err < 0) + return err; + + /* mixer: */ + err = + snd_ctl_add(line6->card, + snd_ctl_new1(&line6_control_playback, line6pcm)); + if (err < 0) + return err; + +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + /* impulse response test: */ + err = device_create_file(line6->ifcdev, &dev_attr_impulse_volume); + if (err < 0) + return err; + + err = device_create_file(line6->ifcdev, &dev_attr_impulse_period); + if (err < 0) + return err; + + line6pcm->impulse_period = LINE6_IMPULSE_DEFAULT_PERIOD; +#endif + + return 0; +} + +/* prepare pcm callback */ +int snd_line6_prepare(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + switch (substream->stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + if ((line6pcm->flags & LINE6_BITS_PLAYBACK_STREAM) == 0) + line6_unlink_wait_clear_audio_out_urbs(line6pcm); + + break; + + case SNDRV_PCM_STREAM_CAPTURE: + if ((line6pcm->flags & LINE6_BITS_CAPTURE_STREAM) == 0) + line6_unlink_wait_clear_audio_in_urbs(line6pcm); + + break; + + default: + MISSING_CASE; + } + + if (!test_and_set_bit(LINE6_INDEX_PREPARED, &line6pcm->flags)) { + line6pcm->count_out = 0; + line6pcm->pos_out = 0; + line6pcm->pos_out_done = 0; + line6pcm->bytes_out = 0; + line6pcm->count_in = 0; + line6pcm->pos_in_done = 0; + line6pcm->bytes_in = 0; + } + + return 0; +} diff --git a/drivers/staging/line6/pcm.h b/drivers/staging/line6/pcm.h new file mode 100644 index 00000000..5210ec8d --- /dev/null +++ b/drivers/staging/line6/pcm.h @@ -0,0 +1,382 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +/* + PCM interface to POD series devices. +*/ + +#ifndef PCM_H +#define PCM_H + +#include + +#include "driver.h" +#include "usbdefs.h" + +/* number of URBs */ +#define LINE6_ISO_BUFFERS 2 + +/* + number of USB frames per URB + The Line6 Windows driver always transmits two frames per packet, but + the Linux driver performs significantly better (i.e., lower latency) + with only one frame per packet. +*/ +#define LINE6_ISO_PACKETS 1 + +/* in a "full speed" device (such as the PODxt Pro) this means 1ms */ +#define LINE6_ISO_INTERVAL 1 + +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE +#define LINE6_IMPULSE_DEFAULT_PERIOD 100 +#endif + +/* + Get substream from Line6 PCM data structure +*/ +#define get_substream(line6pcm, stream) \ + (line6pcm->pcm->streams[stream].substream) + +/* + PCM mode bits. + + There are several features of the Line6 USB driver which require PCM + data to be exchanged with the device: + *) PCM playback and capture via ALSA + *) software monitoring (for devices without hardware monitoring) + *) optional impulse response measurement + However, from the device's point of view, there is just a single + capture and playback stream, which must be shared between these + subsystems. It is therefore necessary to maintain the state of the + subsystems with respect to PCM usage. We define several constants of + the form LINE6_BIT_PCM___ with the + following meanings: + *) is one of + -) ALSA: PCM playback and capture via ALSA + -) MONITOR: software monitoring + -) IMPULSE: optional impulse response measurement + *) is one of + -) PLAYBACK: audio output (from host to device) + -) CAPTURE: audio input (from device to host) + *) is one of + -) BUFFER: buffer required by PCM data stream + -) STREAM: actual PCM data stream + + The subsystems call line6_pcm_acquire() to acquire the (shared) + resources needed for a particular operation (e.g., allocate the buffer + for ALSA playback or start the capture stream for software monitoring). + When a resource is no longer needed, it is released by calling + line6_pcm_release(). Buffer allocation and stream startup are handled + separately to allow the ALSA kernel driver to perform them at + appropriate places (since the callback which starts a PCM stream is not + allowed to sleep). +*/ +enum { + /* individual bit indices: */ + LINE6_INDEX_PCM_ALSA_PLAYBACK_BUFFER, + LINE6_INDEX_PCM_ALSA_PLAYBACK_STREAM, + LINE6_INDEX_PCM_ALSA_CAPTURE_BUFFER, + LINE6_INDEX_PCM_ALSA_CAPTURE_STREAM, + LINE6_INDEX_PCM_MONITOR_PLAYBACK_BUFFER, + LINE6_INDEX_PCM_MONITOR_PLAYBACK_STREAM, + LINE6_INDEX_PCM_MONITOR_CAPTURE_BUFFER, + LINE6_INDEX_PCM_MONITOR_CAPTURE_STREAM, +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + LINE6_INDEX_PCM_IMPULSE_PLAYBACK_BUFFER, + LINE6_INDEX_PCM_IMPULSE_PLAYBACK_STREAM, + LINE6_INDEX_PCM_IMPULSE_CAPTURE_BUFFER, + LINE6_INDEX_PCM_IMPULSE_CAPTURE_STREAM, +#endif + LINE6_INDEX_PAUSE_PLAYBACK, + LINE6_INDEX_PREPARED, + + /* individual bit masks: */ + LINE6_BIT(PCM_ALSA_PLAYBACK_BUFFER), + LINE6_BIT(PCM_ALSA_PLAYBACK_STREAM), + LINE6_BIT(PCM_ALSA_CAPTURE_BUFFER), + LINE6_BIT(PCM_ALSA_CAPTURE_STREAM), + LINE6_BIT(PCM_MONITOR_PLAYBACK_BUFFER), + LINE6_BIT(PCM_MONITOR_PLAYBACK_STREAM), + LINE6_BIT(PCM_MONITOR_CAPTURE_BUFFER), + LINE6_BIT(PCM_MONITOR_CAPTURE_STREAM), +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + LINE6_BIT(PCM_IMPULSE_PLAYBACK_BUFFER), + LINE6_BIT(PCM_IMPULSE_PLAYBACK_STREAM), + LINE6_BIT(PCM_IMPULSE_CAPTURE_BUFFER), + LINE6_BIT(PCM_IMPULSE_CAPTURE_STREAM), +#endif + LINE6_BIT(PAUSE_PLAYBACK), + LINE6_BIT(PREPARED), + + /* combined bit masks (by operation): */ + LINE6_BITS_PCM_ALSA_BUFFER = + LINE6_BIT_PCM_ALSA_PLAYBACK_BUFFER | + LINE6_BIT_PCM_ALSA_CAPTURE_BUFFER, + + LINE6_BITS_PCM_ALSA_STREAM = + LINE6_BIT_PCM_ALSA_PLAYBACK_STREAM | + LINE6_BIT_PCM_ALSA_CAPTURE_STREAM, + + LINE6_BITS_PCM_MONITOR = + LINE6_BIT_PCM_MONITOR_PLAYBACK_BUFFER | + LINE6_BIT_PCM_MONITOR_PLAYBACK_STREAM | + LINE6_BIT_PCM_MONITOR_CAPTURE_BUFFER | + LINE6_BIT_PCM_MONITOR_CAPTURE_STREAM, + +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + LINE6_BITS_PCM_IMPULSE = + LINE6_BIT_PCM_IMPULSE_PLAYBACK_BUFFER | + LINE6_BIT_PCM_IMPULSE_PLAYBACK_STREAM | + LINE6_BIT_PCM_IMPULSE_CAPTURE_BUFFER | + LINE6_BIT_PCM_IMPULSE_CAPTURE_STREAM, +#endif + + /* combined bit masks (by direction): */ + LINE6_BITS_PLAYBACK_BUFFER = +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + LINE6_BIT_PCM_IMPULSE_PLAYBACK_BUFFER | +#endif + LINE6_BIT_PCM_ALSA_PLAYBACK_BUFFER | + LINE6_BIT_PCM_MONITOR_PLAYBACK_BUFFER , + + LINE6_BITS_PLAYBACK_STREAM = +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + LINE6_BIT_PCM_IMPULSE_PLAYBACK_STREAM | +#endif + LINE6_BIT_PCM_ALSA_PLAYBACK_STREAM | + LINE6_BIT_PCM_MONITOR_PLAYBACK_STREAM , + + LINE6_BITS_CAPTURE_BUFFER = +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + LINE6_BIT_PCM_IMPULSE_CAPTURE_BUFFER | +#endif + LINE6_BIT_PCM_ALSA_CAPTURE_BUFFER | + LINE6_BIT_PCM_MONITOR_CAPTURE_BUFFER , + + LINE6_BITS_CAPTURE_STREAM = +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + LINE6_BIT_PCM_IMPULSE_CAPTURE_STREAM | +#endif + LINE6_BIT_PCM_ALSA_CAPTURE_STREAM | + LINE6_BIT_PCM_MONITOR_CAPTURE_STREAM, + + LINE6_BITS_STREAM = + LINE6_BITS_PLAYBACK_STREAM | + LINE6_BITS_CAPTURE_STREAM +}; + +struct line6_pcm_properties { + struct snd_pcm_hardware snd_line6_playback_hw, snd_line6_capture_hw; + struct snd_pcm_hw_constraint_ratdens snd_line6_rates; + int bytes_per_frame; +}; + +struct snd_line6_pcm { + /** + Pointer back to the Line6 driver data structure. + */ + struct usb_line6 *line6; + + /** + Properties. + */ + struct line6_pcm_properties *properties; + + /** + ALSA pcm stream + */ + struct snd_pcm *pcm; + + /** + URBs for audio playback. + */ + struct urb *urb_audio_out[LINE6_ISO_BUFFERS]; + + /** + URBs for audio capture. + */ + struct urb *urb_audio_in[LINE6_ISO_BUFFERS]; + + /** + Temporary buffer for playback. + Since the packet size is not known in advance, this buffer is + large enough to store maximum size packets. + */ + unsigned char *buffer_out; + + /** + Temporary buffer for capture. + Since the packet size is not known in advance, this buffer is + large enough to store maximum size packets. + */ + unsigned char *buffer_in; + + /** + Previously captured frame (for software monitoring). + */ + unsigned char *prev_fbuf; + + /** + Size of previously captured frame (for software monitoring). + */ + int prev_fsize; + + /** + Free frame position in the playback buffer. + */ + snd_pcm_uframes_t pos_out; + + /** + Count processed bytes for playback. + This is modulo period size (to determine when a period is + finished). + */ + unsigned bytes_out; + + /** + Counter to create desired playback sample rate. + */ + unsigned count_out; + + /** + Playback period size in bytes + */ + unsigned period_out; + + /** + Processed frame position in the playback buffer. + The contents of the output ring buffer have been consumed by + the USB subsystem (i.e., sent to the USB device) up to this + position. + */ + snd_pcm_uframes_t pos_out_done; + + /** + Count processed bytes for capture. + This is modulo period size (to determine when a period is + finished). + */ + unsigned bytes_in; + + /** + Counter to create desired capture sample rate. + */ + unsigned count_in; + + /** + Capture period size in bytes + */ + unsigned period_in; + + /** + Processed frame position in the capture buffer. + The contents of the output ring buffer have been consumed by + the USB subsystem (i.e., sent to the USB device) up to this + position. + */ + snd_pcm_uframes_t pos_in_done; + + /** + Bit mask of active playback URBs. + */ + unsigned long active_urb_out; + + /** + Maximum size of USB packet. + */ + int max_packet_size; + + /** + USB endpoint for listening to audio data. + */ + int ep_audio_read; + + /** + USB endpoint for writing audio data. + */ + int ep_audio_write; + + /** + Bit mask of active capture URBs. + */ + unsigned long active_urb_in; + + /** + Bit mask of playback URBs currently being unlinked. + */ + unsigned long unlink_urb_out; + + /** + Bit mask of capture URBs currently being unlinked. + */ + unsigned long unlink_urb_in; + + /** + Spin lock to protect updates of the playback buffer positions (not + contents!) + */ + spinlock_t lock_audio_out; + + /** + Spin lock to protect updates of the capture buffer positions (not + contents!) + */ + spinlock_t lock_audio_in; + + /** + Spin lock to protect trigger. + */ + spinlock_t lock_trigger; + + /** + PCM playback volume (left and right). + */ + int volume_playback[2]; + + /** + PCM monitor volume. + */ + int volume_monitor; + +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + /** + Volume of impulse response test signal (if zero, test is disabled). + */ + int impulse_volume; + + /** + Period of impulse response test signal. + */ + int impulse_period; + + /** + Counter for impulse response test signal. + */ + int impulse_count; +#endif + + /** + Several status bits (see LINE6_BIT_*). + */ + unsigned long flags; + + int last_frame_in, last_frame_out; +}; + +extern int line6_init_pcm(struct usb_line6 *line6, + struct line6_pcm_properties *properties); +extern int snd_line6_trigger(struct snd_pcm_substream *substream, int cmd); +extern int snd_line6_prepare(struct snd_pcm_substream *substream); +extern void line6_pcm_disconnect(struct snd_line6_pcm *line6pcm); +extern int line6_pcm_acquire(struct snd_line6_pcm *line6pcm, int channels); +extern int line6_pcm_release(struct snd_line6_pcm *line6pcm, int channels); + +#endif diff --git a/drivers/staging/line6/playback.c b/drivers/staging/line6/playback.c new file mode 100644 index 00000000..a0ab9d04 --- /dev/null +++ b/drivers/staging/line6/playback.c @@ -0,0 +1,586 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include +#include + +#include "audio.h" +#include "capture.h" +#include "driver.h" +#include "pcm.h" +#include "pod.h" +#include "playback.h" + +/* + Software stereo volume control. +*/ +static void change_volume(struct urb *urb_out, int volume[], + int bytes_per_frame) +{ + int chn = 0; + + if (volume[0] == 256 && volume[1] == 256) + return; /* maximum volume - no change */ + + if (bytes_per_frame == 4) { + short *p, *buf_end; + p = (short *)urb_out->transfer_buffer; + buf_end = p + urb_out->transfer_buffer_length / sizeof(*p); + + for (; p < buf_end; ++p) { + *p = (*p * volume[chn & 1]) >> 8; + ++chn; + } + } else if (bytes_per_frame == 6) { + unsigned char *p, *buf_end; + p = (unsigned char *)urb_out->transfer_buffer; + buf_end = p + urb_out->transfer_buffer_length; + + for (; p < buf_end; p += 3) { + int val; + val = p[0] + (p[1] << 8) + ((signed char)p[2] << 16); + val = (val * volume[chn & 1]) >> 8; + p[0] = val; + p[1] = val >> 8; + p[2] = val >> 16; + ++chn; + } + } +} + +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + +/* + Create signal for impulse response test. +*/ +static void create_impulse_test_signal(struct snd_line6_pcm *line6pcm, + struct urb *urb_out, int bytes_per_frame) +{ + int frames = urb_out->transfer_buffer_length / bytes_per_frame; + + if (bytes_per_frame == 4) { + int i; + short *pi = (short *)line6pcm->prev_fbuf; + short *po = (short *)urb_out->transfer_buffer; + + for (i = 0; i < frames; ++i) { + po[0] = pi[0]; + po[1] = 0; + pi += 2; + po += 2; + } + } else if (bytes_per_frame == 6) { + int i, j; + unsigned char *pi = line6pcm->prev_fbuf; + unsigned char *po = urb_out->transfer_buffer; + + for (i = 0; i < frames; ++i) { + for (j = 0; j < bytes_per_frame / 2; ++j) + po[j] = pi[j]; + + for (; j < bytes_per_frame; ++j) + po[j] = 0; + + pi += bytes_per_frame; + po += bytes_per_frame; + } + } + if (--line6pcm->impulse_count <= 0) { + ((unsigned char *)(urb_out->transfer_buffer))[bytes_per_frame - + 1] = + line6pcm->impulse_volume; + line6pcm->impulse_count = line6pcm->impulse_period; + } +} + +#endif + +/* + Add signal to buffer for software monitoring. +*/ +static void add_monitor_signal(struct urb *urb_out, unsigned char *signal, + int volume, int bytes_per_frame) +{ + if (volume == 0) + return; /* zero volume - no change */ + + if (bytes_per_frame == 4) { + short *pi, *po, *buf_end; + pi = (short *)signal; + po = (short *)urb_out->transfer_buffer; + buf_end = po + urb_out->transfer_buffer_length / sizeof(*po); + + for (; po < buf_end; ++pi, ++po) + *po += (*pi * volume) >> 8; + } + + /* + We don't need to handle devices with 6 bytes per frame here + since they all support hardware monitoring. + */ +} + +/* + Find a free URB, prepare audio data, and submit URB. +*/ +static int submit_audio_out_urb(struct snd_line6_pcm *line6pcm) +{ + int index; + unsigned long flags; + int i, urb_size, urb_frames; + int ret; + const int bytes_per_frame = line6pcm->properties->bytes_per_frame; + const int frame_increment = + line6pcm->properties->snd_line6_rates.rats[0].num_min; + const int frame_factor = + line6pcm->properties->snd_line6_rates.rats[0].den * + (USB_INTERVALS_PER_SECOND / LINE6_ISO_INTERVAL); + struct urb *urb_out; + + spin_lock_irqsave(&line6pcm->lock_audio_out, flags); + index = + find_first_zero_bit(&line6pcm->active_urb_out, LINE6_ISO_BUFFERS); + + if (index < 0 || index >= LINE6_ISO_BUFFERS) { + spin_unlock_irqrestore(&line6pcm->lock_audio_out, flags); + dev_err(line6pcm->line6->ifcdev, "no free URB found\n"); + return -EINVAL; + } + + urb_out = line6pcm->urb_audio_out[index]; + urb_size = 0; + + for (i = 0; i < LINE6_ISO_PACKETS; ++i) { + /* compute frame size for given sampling rate */ + int fsize = 0; + struct usb_iso_packet_descriptor *fout = + &urb_out->iso_frame_desc[i]; + + if (line6pcm->flags & LINE6_BITS_CAPTURE_STREAM) + fsize = line6pcm->prev_fsize; + + if (fsize == 0) { + int n; + line6pcm->count_out += frame_increment; + n = line6pcm->count_out / frame_factor; + line6pcm->count_out -= n * frame_factor; + fsize = n * bytes_per_frame; + } + + fout->offset = urb_size; + fout->length = fsize; + urb_size += fsize; + } + + if (urb_size == 0) { + /* can't determine URB size */ + spin_unlock_irqrestore(&line6pcm->lock_audio_out, flags); + dev_err(line6pcm->line6->ifcdev, "driver bug: urb_size = 0\n"); /* this is somewhat paranoid */ + return -EINVAL; + } + + urb_frames = urb_size / bytes_per_frame; + urb_out->transfer_buffer = + line6pcm->buffer_out + + index * LINE6_ISO_PACKETS * line6pcm->max_packet_size; + urb_out->transfer_buffer_length = urb_size; + urb_out->context = line6pcm; + + if (test_bit(LINE6_INDEX_PCM_ALSA_PLAYBACK_STREAM, &line6pcm->flags) && + !test_bit(LINE6_INDEX_PAUSE_PLAYBACK, &line6pcm->flags)) { + struct snd_pcm_runtime *runtime = + get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK)->runtime; + + if (line6pcm->pos_out + urb_frames > runtime->buffer_size) { + /* + The transferred area goes over buffer boundary, + copy the data to the temp buffer. + */ + int len; + len = runtime->buffer_size - line6pcm->pos_out; + + if (len > 0) { + memcpy(urb_out->transfer_buffer, + runtime->dma_area + + line6pcm->pos_out * bytes_per_frame, + len * bytes_per_frame); + memcpy(urb_out->transfer_buffer + + len * bytes_per_frame, runtime->dma_area, + (urb_frames - len) * bytes_per_frame); + } else + dev_err(line6pcm->line6->ifcdev, "driver bug: len = %d\n", len); /* this is somewhat paranoid */ + } else { + memcpy(urb_out->transfer_buffer, + runtime->dma_area + + line6pcm->pos_out * bytes_per_frame, + urb_out->transfer_buffer_length); + } + + line6pcm->pos_out += urb_frames; + if (line6pcm->pos_out >= runtime->buffer_size) + line6pcm->pos_out -= runtime->buffer_size; + } else { + memset(urb_out->transfer_buffer, 0, + urb_out->transfer_buffer_length); + } + + change_volume(urb_out, line6pcm->volume_playback, bytes_per_frame); + + if (line6pcm->prev_fbuf != NULL) { +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + if (line6pcm->flags & LINE6_BITS_PCM_IMPULSE) { + create_impulse_test_signal(line6pcm, urb_out, + bytes_per_frame); + if (line6pcm->flags & LINE6_BIT_PCM_ALSA_CAPTURE_STREAM) { + line6_capture_copy(line6pcm, + urb_out->transfer_buffer, + urb_out-> + transfer_buffer_length); + line6_capture_check_period(line6pcm, + urb_out->transfer_buffer_length); + } + } else { +#endif + if (! + (line6pcm->line6-> + properties->capabilities & LINE6_BIT_HWMON) + && (line6pcm->flags & LINE6_BITS_PLAYBACK_STREAM) + && (line6pcm->flags & LINE6_BITS_CAPTURE_STREAM)) + add_monitor_signal(urb_out, line6pcm->prev_fbuf, + line6pcm->volume_monitor, + bytes_per_frame); +#ifdef CONFIG_LINE6_USB_IMPULSE_RESPONSE + } +#endif + } +#ifdef CONFIG_LINE6_USB_DUMP_PCM + for (i = 0; i < LINE6_ISO_PACKETS; ++i) { + struct usb_iso_packet_descriptor *fout = + &urb_out->iso_frame_desc[i]; + line6_write_hexdump(line6pcm->line6, 'P', + urb_out->transfer_buffer + fout->offset, + fout->length); + } +#endif + + ret = usb_submit_urb(urb_out, GFP_ATOMIC); + + if (ret == 0) + set_bit(index, &line6pcm->active_urb_out); + else + dev_err(line6pcm->line6->ifcdev, + "URB out #%d submission failed (%d)\n", index, ret); + + spin_unlock_irqrestore(&line6pcm->lock_audio_out, flags); + return 0; +} + +/* + Submit all currently available playback URBs. +*/ +int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm) +{ + int ret, i; + + for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + ret = submit_audio_out_urb(line6pcm); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + Unlink all currently active playback URBs. +*/ +void line6_unlink_audio_out_urbs(struct snd_line6_pcm *line6pcm) +{ + unsigned int i; + + for (i = LINE6_ISO_BUFFERS; i--;) { + if (test_bit(i, &line6pcm->active_urb_out)) { + if (!test_and_set_bit(i, &line6pcm->unlink_urb_out)) { + struct urb *u = line6pcm->urb_audio_out[i]; + usb_unlink_urb(u); + } + } + } +} + +/* + Wait until unlinking of all currently active playback URBs has been finished. +*/ +void line6_wait_clear_audio_out_urbs(struct snd_line6_pcm *line6pcm) +{ + int timeout = HZ; + unsigned int i; + int alive; + + do { + alive = 0; + for (i = LINE6_ISO_BUFFERS; i--;) { + if (test_bit(i, &line6pcm->active_urb_out)) + alive++; + } + if (!alive) + break; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } while (--timeout > 0); + if (alive) + snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive); +} + +/* + Unlink all currently active playback URBs, and wait for finishing. +*/ +void line6_unlink_wait_clear_audio_out_urbs(struct snd_line6_pcm *line6pcm) +{ + line6_unlink_audio_out_urbs(line6pcm); + line6_wait_clear_audio_out_urbs(line6pcm); +} + +void line6_free_playback_buffer(struct snd_line6_pcm *line6pcm) +{ + kfree(line6pcm->buffer_out); + line6pcm->buffer_out = NULL; +} + +/* + Callback for completed playback URB. +*/ +static void audio_out_callback(struct urb *urb) +{ + int i, index, length = 0, shutdown = 0; + unsigned long flags; + + struct snd_line6_pcm *line6pcm = (struct snd_line6_pcm *)urb->context; + struct snd_pcm_substream *substream = + get_substream(line6pcm, SNDRV_PCM_STREAM_PLAYBACK); + +#if USE_CLEAR_BUFFER_WORKAROUND + memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); +#endif + + line6pcm->last_frame_out = urb->start_frame; + + /* find index of URB */ + for (index = LINE6_ISO_BUFFERS; index--;) + if (urb == line6pcm->urb_audio_out[index]) + break; + + if (index < 0) + return; /* URB has been unlinked asynchronously */ + + for (i = LINE6_ISO_PACKETS; i--;) + length += urb->iso_frame_desc[i].length; + + spin_lock_irqsave(&line6pcm->lock_audio_out, flags); + + if (test_bit(LINE6_INDEX_PCM_ALSA_PLAYBACK_STREAM, &line6pcm->flags)) { + struct snd_pcm_runtime *runtime = substream->runtime; + line6pcm->pos_out_done += + length / line6pcm->properties->bytes_per_frame; + + if (line6pcm->pos_out_done >= runtime->buffer_size) + line6pcm->pos_out_done -= runtime->buffer_size; + } + + clear_bit(index, &line6pcm->active_urb_out); + + for (i = LINE6_ISO_PACKETS; i--;) + if (urb->iso_frame_desc[i].status == -EXDEV) { + shutdown = 1; + break; + } + + if (test_and_clear_bit(index, &line6pcm->unlink_urb_out)) + shutdown = 1; + + spin_unlock_irqrestore(&line6pcm->lock_audio_out, flags); + + if (!shutdown) { + submit_audio_out_urb(line6pcm); + + if (test_bit(LINE6_INDEX_PCM_ALSA_PLAYBACK_STREAM, &line6pcm->flags)) { + line6pcm->bytes_out += length; + if (line6pcm->bytes_out >= line6pcm->period_out) { + line6pcm->bytes_out %= line6pcm->period_out; + snd_pcm_period_elapsed(substream); + } + } + } +} + +/* open playback callback */ +static int snd_line6_playback_open(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + err = snd_pcm_hw_constraint_ratdens(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + (&line6pcm-> + properties->snd_line6_rates)); + if (err < 0) + return err; + + runtime->hw = line6pcm->properties->snd_line6_playback_hw; + return 0; +} + +/* close playback callback */ +static int snd_line6_playback_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* hw_params playback callback */ +static int snd_line6_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int ret; + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + + /* -- Florian Demski [FD] */ + /* don't ask me why, but this fixes the bug on my machine */ + if (line6pcm == NULL) { + if (substream->pcm == NULL) + return -ENOMEM; + if (substream->pcm->private_data == NULL) + return -ENOMEM; + substream->private_data = substream->pcm->private_data; + line6pcm = snd_pcm_substream_chip(substream); + } + /* -- [FD] end */ + + ret = line6_pcm_acquire(line6pcm, LINE6_BIT_PCM_ALSA_PLAYBACK_BUFFER); + + if (ret < 0) + return ret; + + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) { + line6_pcm_release(line6pcm, LINE6_BIT_PCM_ALSA_PLAYBACK_BUFFER); + return ret; + } + + line6pcm->period_out = params_period_bytes(hw_params); + return 0; +} + +/* hw_free playback callback */ +static int snd_line6_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + line6_pcm_release(line6pcm, LINE6_BIT_PCM_ALSA_PLAYBACK_BUFFER); + return snd_pcm_lib_free_pages(substream); +} + +/* trigger playback callback */ +int snd_line6_playback_trigger(struct snd_line6_pcm *line6pcm, int cmd) +{ + int err; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: +#ifdef CONFIG_PM + case SNDRV_PCM_TRIGGER_RESUME: +#endif + err = line6_pcm_acquire(line6pcm, LINE6_BIT_PCM_ALSA_PLAYBACK_STREAM); + + if (err < 0) + return err; + + break; + + case SNDRV_PCM_TRIGGER_STOP: +#ifdef CONFIG_PM + case SNDRV_PCM_TRIGGER_SUSPEND: +#endif + err = line6_pcm_release(line6pcm, LINE6_BIT_PCM_ALSA_PLAYBACK_STREAM); + + if (err < 0) + return err; + + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + set_bit(LINE6_INDEX_PAUSE_PLAYBACK, &line6pcm->flags); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + clear_bit(LINE6_INDEX_PAUSE_PLAYBACK, &line6pcm->flags); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* playback pointer callback */ +static snd_pcm_uframes_t +snd_line6_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_line6_pcm *line6pcm = snd_pcm_substream_chip(substream); + return line6pcm->pos_out_done; +} + +/* playback operators */ +struct snd_pcm_ops snd_line6_playback_ops = { + .open = snd_line6_playback_open, + .close = snd_line6_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_line6_playback_hw_params, + .hw_free = snd_line6_playback_hw_free, + .prepare = snd_line6_prepare, + .trigger = snd_line6_trigger, + .pointer = snd_line6_playback_pointer, +}; + +int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm) +{ + int i; + + /* create audio URBs and fill in constant values: */ + for (i = 0; i < LINE6_ISO_BUFFERS; ++i) { + struct urb *urb; + + /* URB for audio out: */ + urb = line6pcm->urb_audio_out[i] = + usb_alloc_urb(LINE6_ISO_PACKETS, GFP_KERNEL); + + if (urb == NULL) { + dev_err(line6pcm->line6->ifcdev, "Out of memory\n"); + return -ENOMEM; + } + + urb->dev = line6pcm->line6->usbdev; + urb->pipe = + usb_sndisocpipe(line6pcm->line6->usbdev, + line6pcm->ep_audio_write & + USB_ENDPOINT_NUMBER_MASK); + urb->transfer_flags = URB_ISO_ASAP; + urb->start_frame = -1; + urb->number_of_packets = LINE6_ISO_PACKETS; + urb->interval = LINE6_ISO_INTERVAL; + urb->error_count = 0; + urb->complete = audio_out_callback; + } + + return 0; +} diff --git a/drivers/staging/line6/playback.h b/drivers/staging/line6/playback.h new file mode 100644 index 00000000..743bd6f7 --- /dev/null +++ b/drivers/staging/line6/playback.h @@ -0,0 +1,41 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef PLAYBACK_H +#define PLAYBACK_H + +#include + +#include "driver.h" + +/* + * When the TonePort is used with jack in full duplex mode and the outputs are + * not connected, the software monitor produces an ugly noise since everything + * written to the output buffer (i.e., the input signal) will be repeated in + * the next period (sounds like a delay effect). As a workaround, the output + * buffer is cleared after the data have been read, but there must be a better + * solution. Until one is found, this workaround can be used to fix the + * problem. + */ +#define USE_CLEAR_BUFFER_WORKAROUND 1 + +extern struct snd_pcm_ops snd_line6_playback_ops; + +extern int line6_create_audio_out_urbs(struct snd_line6_pcm *line6pcm); +extern void line6_free_playback_buffer(struct snd_line6_pcm *line6pcm); +extern int line6_submit_audio_out_all_urbs(struct snd_line6_pcm *line6pcm); +extern void line6_unlink_audio_out_urbs(struct snd_line6_pcm *line6pcm); +extern void line6_unlink_wait_clear_audio_out_urbs(struct snd_line6_pcm + *line6pcm); +extern void line6_wait_clear_audio_out_urbs(struct snd_line6_pcm *line6pcm); +extern int snd_line6_playback_trigger(struct snd_line6_pcm *line6pcm, int cmd); + +#endif diff --git a/drivers/staging/line6/pod.c b/drivers/staging/line6/pod.c new file mode 100644 index 00000000..4dadc571 --- /dev/null +++ b/drivers/staging/line6/pod.c @@ -0,0 +1,1351 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include +#include + +#include "audio.h" +#include "capture.h" +#include "control.h" +#include "driver.h" +#include "playback.h" +#include "pod.h" + +#define POD_SYSEX_CODE 3 +#define POD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */ + +/* *INDENT-OFF* */ + +enum { + POD_SYSEX_CLIP = 0x0f, + POD_SYSEX_SAVE = 0x24, + POD_SYSEX_SYSTEM = 0x56, + POD_SYSEX_SYSTEMREQ = 0x57, + /* POD_SYSEX_UPDATE = 0x6c, */ /* software update! */ + POD_SYSEX_STORE = 0x71, + POD_SYSEX_FINISH = 0x72, + POD_SYSEX_DUMPMEM = 0x73, + POD_SYSEX_DUMP = 0x74, + POD_SYSEX_DUMPREQ = 0x75 + /* POD_SYSEX_DUMPMEM2 = 0x76 */ /* dumps entire internal memory of PODxt Pro */ +}; + +enum { + POD_monitor_level = 0x04, + POD_routing = 0x05, + POD_tuner_mute = 0x13, + POD_tuner_freq = 0x15, + POD_tuner_note = 0x16, + POD_tuner_pitch = 0x17, + POD_system_invalid = 0x10000 +}; + +/* *INDENT-ON* */ + +enum { + POD_DUMP_MEMORY = 2 +}; + +enum { + POD_BUSY_READ, + POD_BUSY_WRITE, + POD_CHANNEL_DIRTY, + POD_SAVE_PRESSED, + POD_BUSY_MIDISEND +}; + +static struct snd_ratden pod_ratden = { + .num_min = 78125, + .num_max = 78125, + .num_step = 1, + .den = 2 +}; + +static struct line6_pcm_properties pod_pcm_properties = { + .snd_line6_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | +#ifdef CONFIG_PM + SNDRV_PCM_INFO_RESUME | +#endif + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 39062, + .rate_max = 39063, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .snd_line6_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | +#ifdef CONFIG_PM + SNDRV_PCM_INFO_RESUME | +#endif + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 39062, + .rate_max = 39063, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .snd_line6_rates = { + .nrats = 1, + .rats = &pod_ratden}, + .bytes_per_frame = POD_BYTES_PER_FRAME +}; + +static const char pod_request_channel[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x03, 0x75, 0xf7 +}; + +static const char pod_version_header[] = { + 0xf2, 0x7e, 0x7f, 0x06, 0x02 +}; + +/* forward declarations: */ +static void pod_startup2(unsigned long data); +static void pod_startup3(struct usb_line6_pod *pod); +static void pod_startup4(struct usb_line6_pod *pod); + +/* + Mark all parameters as dirty and notify waiting processes. +*/ +static void pod_mark_batch_all_dirty(struct usb_line6_pod *pod) +{ + int i; + + for (i = 0; i < POD_CONTROL_SIZE; i++) + set_bit(i, pod->param_dirty); +} + +static char *pod_alloc_sysex_buffer(struct usb_line6_pod *pod, int code, + int size) +{ + return line6_alloc_sysex_buffer(&pod->line6, POD_SYSEX_CODE, code, + size); +} + +/* + Send channel dump data to the PODxt Pro. +*/ +static void pod_dump(struct usb_line6_pod *pod, const unsigned char *data) +{ + int size = 1 + sizeof(pod->prog_data); + char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_DUMP, size); + if (!sysex) + return; + /* Don't know what this is good for, but PODxt Pro transmits it, so we + * also do... */ + sysex[SYSEX_DATA_OFS] = 5; + memcpy(sysex + SYSEX_DATA_OFS + 1, data, sizeof(pod->prog_data)); + line6_send_sysex_message(&pod->line6, sysex, size); + memcpy(&pod->prog_data, data, sizeof(pod->prog_data)); + pod_mark_batch_all_dirty(pod); + kfree(sysex); +} + +/* + Store parameter value in driver memory and mark it as dirty. +*/ +static void pod_store_parameter(struct usb_line6_pod *pod, int param, int value) +{ + pod->prog_data.control[param] = value; + set_bit(param, pod->param_dirty); + pod->dirty = 1; +} + +/* + Handle SAVE button. +*/ +static void pod_save_button_pressed(struct usb_line6_pod *pod, int type, + int index) +{ + pod->dirty = 0; + set_bit(POD_SAVE_PRESSED, &pod->atomic_flags); +} + +/* + Process a completely received message. +*/ +void line6_pod_process_message(struct usb_line6_pod *pod) +{ + const unsigned char *buf = pod->line6.buffer_message; + + /* filter messages by type */ + switch (buf[0] & 0xf0) { + case LINE6_PARAM_CHANGE: + case LINE6_PROGRAM_CHANGE: + case LINE6_SYSEX_BEGIN: + break; /* handle these further down */ + + default: + return; /* ignore all others */ + } + + /* process all remaining messages */ + switch (buf[0]) { + case LINE6_PARAM_CHANGE | LINE6_CHANNEL_DEVICE: + pod_store_parameter(pod, buf[1], buf[2]); + /* intentionally no break here! */ + + case LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST: + if ((buf[1] == POD_amp_model_setup) || + (buf[1] == POD_effect_setup)) + /* these also affect other settings */ + line6_dump_request_async(&pod->dumpreq, &pod->line6, 0, + LINE6_DUMP_CURRENT); + + break; + + case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_DEVICE: + case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST: + pod->channel_num = buf[1]; + pod->dirty = 0; + set_bit(POD_CHANNEL_DIRTY, &pod->atomic_flags); + line6_dump_request_async(&pod->dumpreq, &pod->line6, 0, + LINE6_DUMP_CURRENT); + break; + + case LINE6_SYSEX_BEGIN | LINE6_CHANNEL_DEVICE: + case LINE6_SYSEX_BEGIN | LINE6_CHANNEL_UNKNOWN: + if (memcmp(buf + 1, line6_midi_id, sizeof(line6_midi_id)) == 0) { + switch (buf[5]) { + case POD_SYSEX_DUMP: + if (pod->line6.message_length == + sizeof(pod->prog_data) + 7) { + switch (pod->dumpreq.in_progress) { + case LINE6_DUMP_CURRENT: + memcpy(&pod->prog_data, buf + 7, + sizeof(pod->prog_data)); + pod_mark_batch_all_dirty(pod); + break; + + case POD_DUMP_MEMORY: + memcpy(&pod->prog_data_buf, + buf + 7, + sizeof + (pod->prog_data_buf)); + break; + + default: + DEBUG_MESSAGES(dev_err + (pod-> + line6.ifcdev, + "unknown dump code %02X\n", + pod-> + dumpreq.in_progress)); + } + + line6_dump_finished(&pod->dumpreq); + pod_startup3(pod); + } else + DEBUG_MESSAGES(dev_err + (pod->line6.ifcdev, + "wrong size of channel dump message (%d instead of %d)\n", + pod-> + line6.message_length, + (int) + sizeof(pod->prog_data) + + 7)); + + break; + + case POD_SYSEX_SYSTEM:{ + short value = + ((int)buf[7] << 12) | ((int)buf[8] + << 8) | + ((int)buf[9] << 4) | (int)buf[10]; + +#define PROCESS_SYSTEM_PARAM(x) \ + case POD_ ## x: \ + pod->x.value = value; \ + wake_up(&pod->x.wait); \ + break; + + switch (buf[6]) { + PROCESS_SYSTEM_PARAM + (monitor_level); + PROCESS_SYSTEM_PARAM(routing); + PROCESS_SYSTEM_PARAM + (tuner_mute); + PROCESS_SYSTEM_PARAM + (tuner_freq); + PROCESS_SYSTEM_PARAM + (tuner_note); + PROCESS_SYSTEM_PARAM + (tuner_pitch); + +#undef PROCESS_SYSTEM_PARAM + + default: + DEBUG_MESSAGES(dev_err + (pod-> + line6.ifcdev, + "unknown tuner/system response %02X\n", + buf[6])); + } + + break; + } + + case POD_SYSEX_FINISH: + /* do we need to respond to this? */ + break; + + case POD_SYSEX_SAVE: + pod_save_button_pressed(pod, buf[6], buf[7]); + break; + + case POD_SYSEX_CLIP: + DEBUG_MESSAGES(dev_err + (pod->line6.ifcdev, + "audio clipped\n")); + pod->clipping.value = 1; + wake_up(&pod->clipping.wait); + break; + + case POD_SYSEX_STORE: + DEBUG_MESSAGES(dev_err + (pod->line6.ifcdev, + "message %02X not yet implemented\n", + buf[5])); + break; + + default: + DEBUG_MESSAGES(dev_err + (pod->line6.ifcdev, + "unknown sysex message %02X\n", + buf[5])); + } + } else + if (memcmp + (buf, pod_version_header, + sizeof(pod_version_header)) == 0) { + pod->firmware_version = + buf[13] * 100 + buf[14] * 10 + buf[15]; + pod->device_id = + ((int)buf[8] << 16) | ((int)buf[9] << 8) | (int) + buf[10]; + pod_startup4(pod); + } else + DEBUG_MESSAGES(dev_err + (pod->line6.ifcdev, + "unknown sysex header\n")); + + break; + + case LINE6_SYSEX_END: + break; + + default: + DEBUG_MESSAGES(dev_err + (pod->line6.ifcdev, + "POD: unknown message %02X\n", buf[0])); + } +} + +/* + Detect some cases that require a channel dump after sending a command to the + device. Important notes: + *) The actual dump request can not be sent here since we are not allowed to + wait for the completion of the first message in this context, and sending + the dump request before completion of the previous message leaves the POD + in an undefined state. The dump request will be sent when the echoed + commands are received. + *) This method fails if a param change message is "chopped" after the first + byte. +*/ +void line6_pod_midi_postprocess(struct usb_line6_pod *pod, unsigned char *data, + int length) +{ + int i; + + if (!pod->midi_postprocess) + return; + + for (i = 0; i < length; ++i) { + if (data[i] == (LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST)) { + line6_invalidate_current(&pod->dumpreq); + break; + } else + if ((data[i] == (LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST)) + && (i < length - 1)) + if ((data[i + 1] == POD_amp_model_setup) + || (data[i + 1] == POD_effect_setup)) { + line6_invalidate_current(&pod->dumpreq); + break; + } + } +} + +/* + Send channel number (i.e., switch to a different sound). +*/ +static void pod_send_channel(struct usb_line6_pod *pod, int value) +{ + line6_invalidate_current(&pod->dumpreq); + + if (line6_send_program(&pod->line6, value) == 0) + pod->channel_num = value; + else + line6_dump_finished(&pod->dumpreq); +} + +/* + Transmit PODxt Pro control parameter. +*/ +void line6_pod_transmit_parameter(struct usb_line6_pod *pod, int param, + int value) +{ + if (line6_transmit_parameter(&pod->line6, param, value) == 0) + pod_store_parameter(pod, param, value); + + if ((param == POD_amp_model_setup) || (param == POD_effect_setup)) /* these also affect other settings */ + line6_invalidate_current(&pod->dumpreq); +} + +/* + Resolve value to memory location. +*/ +static int pod_resolve(const char *buf, short block0, short block1, + unsigned char *location) +{ + unsigned long value; + short block; + int ret; + + ret = strict_strtoul(buf, 10, &value); + if (ret) + return ret; + + block = (value < 0x40) ? block0 : block1; + value &= 0x3f; + location[0] = block >> 7; + location[1] = value | (block & 0x7f); + return 0; +} + +/* + Send command to store channel/effects setup/amp setup to PODxt Pro. +*/ +static ssize_t pod_send_store_command(struct device *dev, const char *buf, + size_t count, short block0, short block1) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int ret; + int size = 3 + sizeof(pod->prog_data_buf); + char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_STORE, size); + + if (!sysex) + return 0; + + sysex[SYSEX_DATA_OFS] = 5; /* see pod_dump() */ + ret = pod_resolve(buf, block0, block1, sysex + SYSEX_DATA_OFS + 1); + if (ret) { + kfree(sysex); + return ret; + } + + memcpy(sysex + SYSEX_DATA_OFS + 3, &pod->prog_data_buf, + sizeof(pod->prog_data_buf)); + + line6_send_sysex_message(&pod->line6, sysex, size); + kfree(sysex); + /* needs some delay here on AMD64 platform */ + return count; +} + +/* + Send command to retrieve channel/effects setup/amp setup to PODxt Pro. +*/ +static ssize_t pod_send_retrieve_command(struct device *dev, const char *buf, + size_t count, short block0, + short block1) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int ret; + int size = 4; + char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_DUMPMEM, size); + + if (!sysex) + return 0; + + ret = pod_resolve(buf, block0, block1, sysex + SYSEX_DATA_OFS); + if (ret) { + kfree(sysex); + return ret; + } + sysex[SYSEX_DATA_OFS + 2] = 0; + sysex[SYSEX_DATA_OFS + 3] = 0; + line6_dump_started(&pod->dumpreq, POD_DUMP_MEMORY); + + if (line6_send_sysex_message(&pod->line6, sysex, size) < size) + line6_dump_finished(&pod->dumpreq); + + kfree(sysex); + /* needs some delay here on AMD64 platform */ + return count; +} + +/* + Generic get name function. +*/ +static ssize_t get_name_generic(struct usb_line6_pod *pod, const char *str, + char *buf) +{ + int length = 0; + const char *p1; + char *p2; + char *last_non_space = buf; + + int retval = line6_dump_wait_interruptible(&pod->dumpreq); + if (retval < 0) + return retval; + + for (p1 = str, p2 = buf; *p1; ++p1, ++p2) { + *p2 = *p1; + if (*p2 != ' ') + last_non_space = p2; + if (++length == POD_NAME_LENGTH) + break; + } + + *(last_non_space + 1) = '\n'; + return last_non_space - buf + 2; +} + +/* + "read" request on "channel" special file. +*/ +static ssize_t pod_get_channel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", pod->channel_num); +} + +/* + "write" request on "channel" special file. +*/ +static ssize_t pod_set_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + unsigned long value; + int ret; + + ret = strict_strtoul(buf, 10, &value); + if (ret) + return ret; + + pod_send_channel(pod, value); + return count; +} + +/* + "read" request on "name" special file. +*/ +static ssize_t pod_get_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return get_name_generic(pod, pod->prog_data.header + POD_NAME_OFFSET, + buf); +} + +/* + "read" request on "name" special file. +*/ +static ssize_t pod_get_name_buf(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return get_name_generic(pod, + pod->prog_data_buf.header + POD_NAME_OFFSET, + buf); +} + +/* + "read" request on "dump" special file. +*/ +static ssize_t pod_get_dump(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int retval = line6_dump_wait_interruptible(&pod->dumpreq); + if (retval < 0) + return retval; + memcpy(buf, &pod->prog_data, sizeof(pod->prog_data)); + return sizeof(pod->prog_data); +} + +/* + "write" request on "dump" special file. +*/ +static ssize_t pod_set_dump(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + + if (count != sizeof(pod->prog_data)) { + dev_err(pod->line6.ifcdev, + "data block must be exactly %d bytes\n", + (int)sizeof(pod->prog_data)); + return -EINVAL; + } + + pod_dump(pod, buf); + return sizeof(pod->prog_data); +} + +/* + Identify system parameters related to the tuner. +*/ +static bool pod_is_tuner(int code) +{ + return + (code == POD_tuner_mute) || + (code == POD_tuner_freq) || + (code == POD_tuner_note) || (code == POD_tuner_pitch); +} + +/* + Get system parameter (as integer). + @param tuner non-zero, if code refers to a tuner parameter +*/ +static int pod_get_system_param_int(struct usb_line6_pod *pod, int *value, + int code, struct ValueWait *param, int sign) +{ + char *sysex; + static const int size = 1; + int retval = 0; + + if (((pod->prog_data.control[POD_tuner] & 0x40) == 0) + && pod_is_tuner(code)) + return -ENODEV; + + /* send value request to device: */ + param->value = POD_system_invalid; + sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEMREQ, size); + + if (!sysex) + return -ENOMEM; + + sysex[SYSEX_DATA_OFS] = code; + line6_send_sysex_message(&pod->line6, sysex, size); + kfree(sysex); + + /* wait for device to respond: */ + retval = + wait_event_interruptible(param->wait, + param->value != POD_system_invalid); + + if (retval < 0) + return retval; + + *value = sign ? (int)(signed short)param->value : (int)(unsigned short) + param->value; + + if (*value == POD_system_invalid) + *value = 0; /* don't report uninitialized values */ + + return 0; +} + +/* + Get system parameter (as string). + @param tuner non-zero, if code refers to a tuner parameter +*/ +static ssize_t pod_get_system_param_string(struct usb_line6_pod *pod, char *buf, + int code, struct ValueWait *param, + int sign) +{ + int retval, value = 0; + retval = pod_get_system_param_int(pod, &value, code, param, sign); + + if (retval < 0) + return retval; + + return sprintf(buf, "%d\n", value); +} + +/* + Send system parameter (from integer). + @param tuner non-zero, if code refers to a tuner parameter +*/ +static int pod_set_system_param_int(struct usb_line6_pod *pod, int value, + int code) +{ + char *sysex; + static const int size = 5; + + if (((pod->prog_data.control[POD_tuner] & 0x40) == 0) + && pod_is_tuner(code)) + return -EINVAL; + + /* send value to tuner: */ + sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_SYSTEM, size); + if (!sysex) + return -ENOMEM; + sysex[SYSEX_DATA_OFS] = code; + sysex[SYSEX_DATA_OFS + 1] = (value >> 12) & 0x0f; + sysex[SYSEX_DATA_OFS + 2] = (value >> 8) & 0x0f; + sysex[SYSEX_DATA_OFS + 3] = (value >> 4) & 0x0f; + sysex[SYSEX_DATA_OFS + 4] = (value) & 0x0f; + line6_send_sysex_message(&pod->line6, sysex, size); + kfree(sysex); + return 0; +} + +/* + Send system parameter (from string). + @param tuner non-zero, if code refers to a tuner parameter +*/ +static ssize_t pod_set_system_param_string(struct usb_line6_pod *pod, + const char *buf, int count, int code, + unsigned short mask) +{ + int retval; + unsigned short value = simple_strtoul(buf, NULL, 10) & mask; + retval = pod_set_system_param_int(pod, value, code); + return (retval < 0) ? retval : count; +} + +/* + "read" request on "dump_buf" special file. +*/ +static ssize_t pod_get_dump_buf(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int retval = line6_dump_wait_interruptible(&pod->dumpreq); + if (retval < 0) + return retval; + memcpy(buf, &pod->prog_data_buf, sizeof(pod->prog_data_buf)); + return sizeof(pod->prog_data_buf); +} + +/* + "write" request on "dump_buf" special file. +*/ +static ssize_t pod_set_dump_buf(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + + if (count != sizeof(pod->prog_data)) { + dev_err(pod->line6.ifcdev, + "data block must be exactly %d bytes\n", + (int)sizeof(pod->prog_data)); + return -EINVAL; + } + + memcpy(&pod->prog_data_buf, buf, sizeof(pod->prog_data)); + return sizeof(pod->prog_data); +} + +/* + "write" request on "finish" special file. +*/ +static ssize_t pod_set_finish(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + int size = 0; + char *sysex = pod_alloc_sysex_buffer(pod, POD_SYSEX_FINISH, size); + if (!sysex) + return 0; + line6_send_sysex_message(&pod->line6, sysex, size); + kfree(sysex); + return count; +} + +/* + "write" request on "store_channel" special file. +*/ +static ssize_t pod_set_store_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return pod_send_store_command(dev, buf, count, 0x0000, 0x00c0); +} + +/* + "write" request on "store_effects_setup" special file. +*/ +static ssize_t pod_set_store_effects_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return pod_send_store_command(dev, buf, count, 0x0080, 0x0080); +} + +/* + "write" request on "store_amp_setup" special file. +*/ +static ssize_t pod_set_store_amp_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return pod_send_store_command(dev, buf, count, 0x0040, 0x0100); +} + +/* + "write" request on "retrieve_channel" special file. +*/ +static ssize_t pod_set_retrieve_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return pod_send_retrieve_command(dev, buf, count, 0x0000, 0x00c0); +} + +/* + "write" request on "retrieve_effects_setup" special file. +*/ +static ssize_t pod_set_retrieve_effects_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return pod_send_retrieve_command(dev, buf, count, 0x0080, 0x0080); +} + +/* + "write" request on "retrieve_amp_setup" special file. +*/ +static ssize_t pod_set_retrieve_amp_setup(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return pod_send_retrieve_command(dev, buf, count, 0x0040, 0x0100); +} + +/* + "read" request on "dirty" special file. +*/ +static ssize_t pod_get_dirty(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + buf[0] = pod->dirty ? '1' : '0'; + buf[1] = '\n'; + return 2; +} + +/* + "read" request on "midi_postprocess" special file. +*/ +static ssize_t pod_get_midi_postprocess(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", pod->midi_postprocess); +} + +/* + "write" request on "midi_postprocess" special file. +*/ +static ssize_t pod_set_midi_postprocess(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + unsigned long value; + int ret; + + ret = strict_strtoul(buf, 10, &value); + if (ret) + return ret; + + pod->midi_postprocess = value ? 1 : 0; + return count; +} + +/* + "read" request on "serial_number" special file. +*/ +static ssize_t pod_get_serial_number(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", pod->serial_number); +} + +/* + "read" request on "firmware_version" special file. +*/ +static ssize_t pod_get_firmware_version(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100, + pod->firmware_version % 100); +} + +/* + "read" request on "device_id" special file. +*/ +static ssize_t pod_get_device_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return sprintf(buf, "%d\n", pod->device_id); +} + +/* + "read" request on "clip" special file. +*/ +static ssize_t pod_wait_for_clip(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_pod *pod = usb_get_intfdata(interface); + return wait_event_interruptible(pod->clipping.wait, + pod->clipping.value != 0); +} + +/* + POD startup procedure. + This is a sequence of functions with special requirements (e.g., must + not run immediately after initialization, must not run in interrupt + context). After the last one has finished, the device is ready to use. +*/ + +static void pod_startup1(struct usb_line6_pod *pod) +{ + CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_INIT); + + /* delay startup procedure: */ + line6_start_timer(&pod->startup_timer, POD_STARTUP_DELAY, pod_startup2, + (unsigned long)pod); +} + +static void pod_startup2(unsigned long data) +{ + struct usb_line6_pod *pod = (struct usb_line6_pod *)data; + + /* schedule another startup procedure until startup is complete: */ + if (pod->startup_progress >= POD_STARTUP_LAST) + return; + + pod->startup_progress = POD_STARTUP_DUMPREQ; + line6_start_timer(&pod->startup_timer, POD_STARTUP_DELAY, pod_startup2, + (unsigned long)pod); + + /* current channel dump: */ + line6_dump_request_async(&pod->dumpreq, &pod->line6, 0, + LINE6_DUMP_CURRENT); +} + +static void pod_startup3(struct usb_line6_pod *pod) +{ + struct usb_line6 *line6 = &pod->line6; + CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_VERSIONREQ); + + /* request firmware version: */ + line6_version_request_async(line6); +} + +static void pod_startup4(struct usb_line6_pod *pod) +{ + CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_WORKQUEUE); + + /* schedule work for global work queue: */ + schedule_work(&pod->startup_work); +} + +static void pod_startup5(struct work_struct *work) +{ + struct usb_line6_pod *pod = + container_of(work, struct usb_line6_pod, startup_work); + struct usb_line6 *line6 = &pod->line6; + + CHECK_STARTUP_PROGRESS(pod->startup_progress, POD_STARTUP_SETUP); + + /* serial number: */ + line6_read_serial_number(&pod->line6, &pod->serial_number); + + /* ALSA audio interface: */ + line6_register_audio(line6); + + /* device files: */ + line6_pod_create_files(pod->firmware_version, + line6->properties->device_bit, line6->ifcdev); +} + +#define POD_GET_SYSTEM_PARAM(code, sign) \ +static ssize_t pod_get_ ## code(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *interface = to_usb_interface(dev); \ + struct usb_line6_pod *pod = usb_get_intfdata(interface); \ + return pod_get_system_param_string(pod, buf, POD_ ## code, \ + &pod->code, sign); \ +} + +#define POD_GET_SET_SYSTEM_PARAM(code, mask, sign) \ +POD_GET_SYSTEM_PARAM(code, sign) \ +static ssize_t pod_set_ ## code(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct usb_interface *interface = to_usb_interface(dev); \ + struct usb_line6_pod *pod = usb_get_intfdata(interface); \ + return pod_set_system_param_string(pod, buf, count, POD_ ## code, mask); \ +} + +POD_GET_SET_SYSTEM_PARAM(monitor_level, 0xffff, 0); +POD_GET_SET_SYSTEM_PARAM(routing, 0x0003, 0); +POD_GET_SET_SYSTEM_PARAM(tuner_mute, 0x0001, 0); +POD_GET_SET_SYSTEM_PARAM(tuner_freq, 0xffff, 0); +POD_GET_SYSTEM_PARAM(tuner_note, 1); +POD_GET_SYSTEM_PARAM(tuner_pitch, 1); + +#undef GET_SET_SYSTEM_PARAM +#undef GET_SYSTEM_PARAM + +/* POD special files: */ +static DEVICE_ATTR(channel, S_IWUSR | S_IRUGO, pod_get_channel, + pod_set_channel); +static DEVICE_ATTR(clip, S_IRUGO, pod_wait_for_clip, line6_nop_write); +static DEVICE_ATTR(device_id, S_IRUGO, pod_get_device_id, line6_nop_write); +static DEVICE_ATTR(dirty, S_IRUGO, pod_get_dirty, line6_nop_write); +static DEVICE_ATTR(dump, S_IWUSR | S_IRUGO, pod_get_dump, pod_set_dump); +static DEVICE_ATTR(dump_buf, S_IWUSR | S_IRUGO, pod_get_dump_buf, + pod_set_dump_buf); +static DEVICE_ATTR(finish, S_IWUSR, line6_nop_read, pod_set_finish); +static DEVICE_ATTR(firmware_version, S_IRUGO, pod_get_firmware_version, + line6_nop_write); +static DEVICE_ATTR(midi_postprocess, S_IWUSR | S_IRUGO, + pod_get_midi_postprocess, pod_set_midi_postprocess); +static DEVICE_ATTR(monitor_level, S_IWUSR | S_IRUGO, pod_get_monitor_level, + pod_set_monitor_level); +static DEVICE_ATTR(name, S_IRUGO, pod_get_name, line6_nop_write); +static DEVICE_ATTR(name_buf, S_IRUGO, pod_get_name_buf, line6_nop_write); +static DEVICE_ATTR(retrieve_amp_setup, S_IWUSR, line6_nop_read, + pod_set_retrieve_amp_setup); +static DEVICE_ATTR(retrieve_channel, S_IWUSR, line6_nop_read, + pod_set_retrieve_channel); +static DEVICE_ATTR(retrieve_effects_setup, S_IWUSR, line6_nop_read, + pod_set_retrieve_effects_setup); +static DEVICE_ATTR(routing, S_IWUSR | S_IRUGO, pod_get_routing, + pod_set_routing); +static DEVICE_ATTR(serial_number, S_IRUGO, pod_get_serial_number, + line6_nop_write); +static DEVICE_ATTR(store_amp_setup, S_IWUSR, line6_nop_read, + pod_set_store_amp_setup); +static DEVICE_ATTR(store_channel, S_IWUSR, line6_nop_read, + pod_set_store_channel); +static DEVICE_ATTR(store_effects_setup, S_IWUSR, line6_nop_read, + pod_set_store_effects_setup); +static DEVICE_ATTR(tuner_freq, S_IWUSR | S_IRUGO, pod_get_tuner_freq, + pod_set_tuner_freq); +static DEVICE_ATTR(tuner_mute, S_IWUSR | S_IRUGO, pod_get_tuner_mute, + pod_set_tuner_mute); +static DEVICE_ATTR(tuner_note, S_IRUGO, pod_get_tuner_note, line6_nop_write); +static DEVICE_ATTR(tuner_pitch, S_IRUGO, pod_get_tuner_pitch, line6_nop_write); + +#ifdef CONFIG_LINE6_USB_RAW +static DEVICE_ATTR(raw, S_IWUSR, line6_nop_read, line6_set_raw); +#endif + +/* control info callback */ +static int snd_pod_control_monitor_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 65535; + return 0; +} + +/* control get callback */ +static int snd_pod_control_monitor_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6; + ucontrol->value.integer.value[0] = pod->monitor_level.value; + return 0; +} + +/* control put callback */ +static int snd_pod_control_monitor_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_pod *pod = (struct usb_line6_pod *)line6pcm->line6; + + if (ucontrol->value.integer.value[0] == pod->monitor_level.value) + return 0; + + pod->monitor_level.value = ucontrol->value.integer.value[0]; + pod_set_system_param_int(pod, ucontrol->value.integer.value[0], + POD_monitor_level); + return 1; +} + +/* control definition */ +static struct snd_kcontrol_new pod_control_monitor = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_pod_control_monitor_info, + .get = snd_pod_control_monitor_get, + .put = snd_pod_control_monitor_put +}; + +/* + POD destructor. +*/ +static void pod_destruct(struct usb_interface *interface) +{ + struct usb_line6_pod *pod = usb_get_intfdata(interface); + + if (pod == NULL) + return; + line6_cleanup_audio(&pod->line6); + + del_timer(&pod->startup_timer); + cancel_work_sync(&pod->startup_work); + + /* free dump request data: */ + line6_dumpreq_destruct(&pod->dumpreq); +} + +/* + Create sysfs entries. +*/ +static int pod_create_files2(struct device *dev) +{ + int err; + + CHECK_RETURN(device_create_file(dev, &dev_attr_channel)); + CHECK_RETURN(device_create_file(dev, &dev_attr_clip)); + CHECK_RETURN(device_create_file(dev, &dev_attr_device_id)); + CHECK_RETURN(device_create_file(dev, &dev_attr_dirty)); + CHECK_RETURN(device_create_file(dev, &dev_attr_dump)); + CHECK_RETURN(device_create_file(dev, &dev_attr_dump_buf)); + CHECK_RETURN(device_create_file(dev, &dev_attr_finish)); + CHECK_RETURN(device_create_file(dev, &dev_attr_firmware_version)); + CHECK_RETURN(device_create_file(dev, &dev_attr_midi_postprocess)); + CHECK_RETURN(device_create_file(dev, &dev_attr_monitor_level)); + CHECK_RETURN(device_create_file(dev, &dev_attr_name)); + CHECK_RETURN(device_create_file(dev, &dev_attr_name_buf)); + CHECK_RETURN(device_create_file(dev, &dev_attr_retrieve_amp_setup)); + CHECK_RETURN(device_create_file(dev, &dev_attr_retrieve_channel)); + CHECK_RETURN(device_create_file(dev, &dev_attr_retrieve_effects_setup)); + CHECK_RETURN(device_create_file(dev, &dev_attr_routing)); + CHECK_RETURN(device_create_file(dev, &dev_attr_serial_number)); + CHECK_RETURN(device_create_file(dev, &dev_attr_store_amp_setup)); + CHECK_RETURN(device_create_file(dev, &dev_attr_store_channel)); + CHECK_RETURN(device_create_file(dev, &dev_attr_store_effects_setup)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_freq)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_mute)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_note)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tuner_pitch)); + +#ifdef CONFIG_LINE6_USB_RAW + CHECK_RETURN(device_create_file(dev, &dev_attr_raw)); +#endif + + return 0; +} + +/* + Try to init POD device. +*/ +static int pod_try_init(struct usb_interface *interface, + struct usb_line6_pod *pod) +{ + int err; + struct usb_line6 *line6 = &pod->line6; + + init_timer(&pod->startup_timer); + INIT_WORK(&pod->startup_work, pod_startup5); + + if ((interface == NULL) || (pod == NULL)) + return -ENODEV; + + pod->channel_num = 255; + + /* initialize wait queues: */ + init_waitqueue_head(&pod->monitor_level.wait); + init_waitqueue_head(&pod->routing.wait); + init_waitqueue_head(&pod->tuner_mute.wait); + init_waitqueue_head(&pod->tuner_freq.wait); + init_waitqueue_head(&pod->tuner_note.wait); + init_waitqueue_head(&pod->tuner_pitch.wait); + init_waitqueue_head(&pod->clipping.wait); + + memset(pod->param_dirty, 0xff, sizeof(pod->param_dirty)); + + /* initialize USB buffers: */ + err = line6_dumpreq_init(&pod->dumpreq, pod_request_channel, + sizeof(pod_request_channel)); + if (err < 0) { + dev_err(&interface->dev, "Out of memory\n"); + return -ENOMEM; + } + + /* create sysfs entries: */ + err = pod_create_files2(&interface->dev); + if (err < 0) + return err; + + /* initialize audio system: */ + err = line6_init_audio(line6); + if (err < 0) + return err; + + /* initialize MIDI subsystem: */ + err = line6_init_midi(line6); + if (err < 0) + return err; + + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, &pod_pcm_properties); + if (err < 0) + return err; + + /* register monitor control: */ + err = snd_ctl_add(line6->card, + snd_ctl_new1(&pod_control_monitor, line6->line6pcm)); + if (err < 0) + return err; + + /* + When the sound card is registered at this point, the PODxt Live + displays "Invalid Code Error 07", so we do it later in the event + handler. + */ + + if (pod->line6.properties->capabilities & LINE6_BIT_CONTROL) { + pod->monitor_level.value = POD_system_invalid; + + /* initiate startup procedure: */ + pod_startup1(pod); + } + + return 0; +} + +/* + Init POD device (and clean up in case of failure). +*/ +int line6_pod_init(struct usb_interface *interface, struct usb_line6_pod *pod) +{ + int err = pod_try_init(interface, pod); + + if (err < 0) + pod_destruct(interface); + + return err; +} + +/* + POD device disconnected. +*/ +void line6_pod_disconnect(struct usb_interface *interface) +{ + struct usb_line6_pod *pod; + + if (interface == NULL) + return; + pod = usb_get_intfdata(interface); + + if (pod != NULL) { + struct snd_line6_pcm *line6pcm = pod->line6.line6pcm; + struct device *dev = &interface->dev; + + if (line6pcm != NULL) + line6_pcm_disconnect(line6pcm); + + if (dev != NULL) { + /* remove sysfs entries: */ + line6_pod_remove_files(pod->firmware_version, + pod->line6. + properties->device_bit, dev); + + device_remove_file(dev, &dev_attr_channel); + device_remove_file(dev, &dev_attr_clip); + device_remove_file(dev, &dev_attr_device_id); + device_remove_file(dev, &dev_attr_dirty); + device_remove_file(dev, &dev_attr_dump); + device_remove_file(dev, &dev_attr_dump_buf); + device_remove_file(dev, &dev_attr_finish); + device_remove_file(dev, &dev_attr_firmware_version); + device_remove_file(dev, &dev_attr_midi_postprocess); + device_remove_file(dev, &dev_attr_monitor_level); + device_remove_file(dev, &dev_attr_name); + device_remove_file(dev, &dev_attr_name_buf); + device_remove_file(dev, &dev_attr_retrieve_amp_setup); + device_remove_file(dev, &dev_attr_retrieve_channel); + device_remove_file(dev, + &dev_attr_retrieve_effects_setup); + device_remove_file(dev, &dev_attr_routing); + device_remove_file(dev, &dev_attr_serial_number); + device_remove_file(dev, &dev_attr_store_amp_setup); + device_remove_file(dev, &dev_attr_store_channel); + device_remove_file(dev, &dev_attr_store_effects_setup); + device_remove_file(dev, &dev_attr_tuner_freq); + device_remove_file(dev, &dev_attr_tuner_mute); + device_remove_file(dev, &dev_attr_tuner_note); + device_remove_file(dev, &dev_attr_tuner_pitch); + +#ifdef CONFIG_LINE6_USB_RAW + device_remove_file(dev, &dev_attr_raw); +#endif + } + } + + pod_destruct(interface); +} diff --git a/drivers/staging/line6/pod.h b/drivers/staging/line6/pod.h new file mode 100644 index 00000000..18b9d08c --- /dev/null +++ b/drivers/staging/line6/pod.h @@ -0,0 +1,205 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef POD_H +#define POD_H + +#include +#include +#include +#include + +#include + +#include "driver.h" +#include "dumprequest.h" + +/* + PODxt Live interfaces +*/ +#define PODXTLIVE_INTERFACE_POD 0 +#define PODXTLIVE_INTERFACE_VARIAX 1 + +/* + Locate name in binary program dump +*/ +#define POD_NAME_OFFSET 0 +#define POD_NAME_LENGTH 16 + +/* + Other constants +*/ +#define POD_CONTROL_SIZE 0x80 +#define POD_BUFSIZE_DUMPREQ 7 +#define POD_STARTUP_DELAY 1000 + +/* + Stages of POD startup procedure +*/ +enum { + POD_STARTUP_INIT = 1, + POD_STARTUP_DUMPREQ, + POD_STARTUP_VERSIONREQ, + POD_STARTUP_WORKQUEUE, + POD_STARTUP_SETUP, + POD_STARTUP_LAST = POD_STARTUP_SETUP - 1 +}; + +/** + Data structure for values that need to be requested explicitly. + This is the case for system and tuner settings. +*/ +struct ValueWait { + int value; + wait_queue_head_t wait; +}; + +/** + Binary PODxt Pro program dump +*/ +struct pod_program { + /** + Header information (including program name). + */ + unsigned char header[0x20]; + + /** + Program parameters. + */ + unsigned char control[POD_CONTROL_SIZE]; +}; + +struct usb_line6_pod { + /** + Generic Line6 USB data. + */ + struct usb_line6 line6; + + /** + Dump request structure. + */ + struct line6_dump_request dumpreq; + + /** + Current program number. + */ + unsigned char channel_num; + + /** + Current program settings. + */ + struct pod_program prog_data; + + /** + Buffer for data retrieved from or to be stored on PODxt Pro. + */ + struct pod_program prog_data_buf; + + /** + Tuner mute mode. + */ + struct ValueWait tuner_mute; + + /** + Tuner base frequency (typically 440Hz). + */ + struct ValueWait tuner_freq; + + /** + Note received from tuner. + */ + struct ValueWait tuner_note; + + /** + Pitch value received from tuner. + */ + struct ValueWait tuner_pitch; + + /** + Instrument monitor level. + */ + struct ValueWait monitor_level; + + /** + Audio routing mode. + 0: send processed guitar + 1: send clean guitar + 2: send clean guitar re-amp playback + 3: send re-amp playback + */ + struct ValueWait routing; + + /** + Wait for audio clipping event. + */ + struct ValueWait clipping; + + /** + Timer for device initializaton. + */ + struct timer_list startup_timer; + + /** + Work handler for device initializaton. + */ + struct work_struct startup_work; + + /** + Current progress in startup procedure. + */ + int startup_progress; + + /** + Dirty flags for access to parameter data. + */ + unsigned long param_dirty[POD_CONTROL_SIZE / sizeof(unsigned long)]; + + /** + Some atomic flags. + */ + unsigned long atomic_flags; + + /** + Serial number of device. + */ + int serial_number; + + /** + Firmware version (x 100). + */ + int firmware_version; + + /** + Device ID. + */ + int device_id; + + /** + Flag to indicate modification of current program settings. + */ + char dirty; + + /** + Flag to enable MIDI postprocessing. + */ + char midi_postprocess; +}; + +extern void line6_pod_disconnect(struct usb_interface *interface); +extern int line6_pod_init(struct usb_interface *interface, + struct usb_line6_pod *pod); +extern void line6_pod_midi_postprocess(struct usb_line6_pod *pod, + unsigned char *data, int length); +extern void line6_pod_process_message(struct usb_line6_pod *pod); +extern void line6_pod_transmit_parameter(struct usb_line6_pod *pod, int param, + int value); + +#endif diff --git a/drivers/staging/line6/podhd.c b/drivers/staging/line6/podhd.c new file mode 100644 index 00000000..7ef45437 --- /dev/null +++ b/drivers/staging/line6/podhd.c @@ -0,0 +1,154 @@ +/* + * Line6 Pod HD + * + * Copyright (C) 2011 Stefan Hajnoczi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include + +#include "audio.h" +#include "driver.h" +#include "pcm.h" +#include "podhd.h" + +#define PODHD_BYTES_PER_FRAME 6 /* 24bit audio (stereo) */ + +static struct snd_ratden podhd_ratden = { + .num_min = 48000, + .num_max = 48000, + .num_step = 1, + .den = 1, +}; + +static struct line6_pcm_properties podhd_pcm_properties = { + .snd_line6_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | +#ifdef CONFIG_PM + SNDRV_PCM_INFO_RESUME | +#endif + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .snd_line6_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | +#ifdef CONFIG_PM + SNDRV_PCM_INFO_RESUME | +#endif + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .snd_line6_rates = { + .nrats = 1, + .rats = &podhd_ratden}, + .bytes_per_frame = PODHD_BYTES_PER_FRAME +}; + +/* + POD HD destructor. +*/ +static void podhd_destruct(struct usb_interface *interface) +{ + struct usb_line6_podhd *podhd = usb_get_intfdata(interface); + + if (podhd == NULL) + return; + line6_cleanup_audio(&podhd->line6); +} + +/* + Try to init POD HD device. +*/ +static int podhd_try_init(struct usb_interface *interface, + struct usb_line6_podhd *podhd) +{ + int err; + struct usb_line6 *line6 = &podhd->line6; + + if ((interface == NULL) || (podhd == NULL)) + return -ENODEV; + + /* initialize audio system: */ + err = line6_init_audio(line6); + if (err < 0) + return err; + + /* initialize MIDI subsystem: */ + err = line6_init_midi(line6); + if (err < 0) + return err; + + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, &podhd_pcm_properties); + if (err < 0) + return err; + + /* register USB audio system: */ + err = line6_register_audio(line6); + return err; +} + +/* + Init POD HD device (and clean up in case of failure). +*/ +int line6_podhd_init(struct usb_interface *interface, + struct usb_line6_podhd *podhd) +{ + int err = podhd_try_init(interface, podhd); + + if (err < 0) + podhd_destruct(interface); + + return err; +} + +/* + POD HD device disconnected. +*/ +void line6_podhd_disconnect(struct usb_interface *interface) +{ + struct usb_line6_podhd *podhd; + + if (interface == NULL) + return; + podhd = usb_get_intfdata(interface); + + if (podhd != NULL) { + struct snd_line6_pcm *line6pcm = podhd->line6.line6pcm; + + if (line6pcm != NULL) + line6_pcm_disconnect(line6pcm); + } + + podhd_destruct(interface); +} diff --git a/drivers/staging/line6/podhd.h b/drivers/staging/line6/podhd.h new file mode 100644 index 00000000..652f7405 --- /dev/null +++ b/drivers/staging/line6/podhd.h @@ -0,0 +1,30 @@ +/* + * Line6 Pod HD + * + * Copyright (C) 2011 Stefan Hajnoczi + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef PODHD_H +#define PODHD_H + +#include + +#include "driver.h" + +struct usb_line6_podhd { + /** + Generic Line6 USB data. + */ + struct usb_line6 line6; +}; + +extern void line6_podhd_disconnect(struct usb_interface *interface); +extern int line6_podhd_init(struct usb_interface *interface, + struct usb_line6_podhd *podhd); + +#endif /* PODHD_H */ diff --git a/drivers/staging/line6/revision.h b/drivers/staging/line6/revision.h new file mode 100644 index 00000000..b4eee2b7 --- /dev/null +++ b/drivers/staging/line6/revision.h @@ -0,0 +1,4 @@ +#ifndef DRIVER_REVISION +/* current subversion revision */ +#define DRIVER_REVISION " (904)" +#endif diff --git a/drivers/staging/line6/toneport.c b/drivers/staging/line6/toneport.c new file mode 100644 index 00000000..b754f69a --- /dev/null +++ b/drivers/staging/line6/toneport.c @@ -0,0 +1,455 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * Emil Myhrman (emil.myhrman@gmail.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include +#include + +#include "audio.h" +#include "capture.h" +#include "driver.h" +#include "playback.h" +#include "toneport.h" + +static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2); + +#define TONEPORT_PCM_DELAY 1 + +static struct snd_ratden toneport_ratden = { + .num_min = 44100, + .num_max = 44100, + .num_step = 1, + .den = 1 +}; + +static struct line6_pcm_properties toneport_pcm_properties = { + .snd_line6_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | +#ifdef CONFIG_PM + SNDRV_PCM_INFO_RESUME | +#endif + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .snd_line6_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | +#ifdef CONFIG_PM + SNDRV_PCM_INFO_RESUME | +#endif + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 60000, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 1, + .periods_max = 1024}, + .snd_line6_rates = { + .nrats = 1, + .rats = &toneport_ratden}, + .bytes_per_frame = 4 +}; + +/* + For the led on Guitarport. + Brightness goes from 0x00 to 0x26. Set a value above this to have led + blink. + (void cmd_0x02(byte red, byte green) +*/ +static int led_red = 0x00; +static int led_green = 0x26; + +struct ToneportSourceInfo { + const char *name; + int code; +}; + +static const struct ToneportSourceInfo toneport_source_info[] = { + {"Microphone", 0x0a01}, + {"Line", 0x0801}, + {"Instrument", 0x0b01}, + {"Inst & Mic", 0x0901} +}; + +static bool toneport_has_led(short product) +{ + return + (product == LINE6_DEVID_GUITARPORT) || + (product == LINE6_DEVID_TONEPORT_GX); + /* add your device here if you are missing support for the LEDs */ +} + +static void toneport_update_led(struct device *dev) +{ + struct usb_interface *interface = to_usb_interface(dev); + struct usb_line6_toneport *tp = usb_get_intfdata(interface); + struct usb_line6 *line6; + + if (!tp) + return; + + line6 = &tp->line6; + if (line6) + toneport_send_cmd(line6->usbdev, (led_red << 8) | 0x0002, + led_green); +} + +static ssize_t toneport_set_led_red(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int retval; + long value; + + retval = strict_strtol(buf, 10, &value); + if (retval) + return retval; + + led_red = value; + toneport_update_led(dev); + return count; +} + +static ssize_t toneport_set_led_green(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int retval; + long value; + + retval = strict_strtol(buf, 10, &value); + if (retval) + return retval; + + led_green = value; + toneport_update_led(dev); + return count; +} + +static DEVICE_ATTR(led_red, S_IWUSR | S_IRUGO, line6_nop_read, + toneport_set_led_red); +static DEVICE_ATTR(led_green, S_IWUSR | S_IRUGO, line6_nop_read, + toneport_set_led_green); + +static int toneport_send_cmd(struct usb_device *usbdev, int cmd1, int cmd2) +{ + int ret; + + ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0), 0x67, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT, + cmd1, cmd2, NULL, 0, LINE6_TIMEOUT * HZ); + + if (ret < 0) { + err("send failed (error %d)\n", ret); + return ret; + } + + return 0; +} + +/* monitor info callback */ +static int snd_toneport_monitor_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 256; + return 0; +} + +/* monitor get callback */ +static int snd_toneport_monitor_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = line6pcm->volume_monitor; + return 0; +} + +/* monitor put callback */ +static int snd_toneport_monitor_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.integer.value[0] == line6pcm->volume_monitor) + return 0; + + line6pcm->volume_monitor = ucontrol->value.integer.value[0]; + + if (line6pcm->volume_monitor > 0) + line6_pcm_acquire(line6pcm, LINE6_BITS_PCM_MONITOR); + else + line6_pcm_release(line6pcm, LINE6_BITS_PCM_MONITOR); + + return 1; +} + +/* source info callback */ +static int snd_toneport_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + const int size = ARRAY_SIZE(toneport_source_info); + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = size; + + if (uinfo->value.enumerated.item >= size) + uinfo->value.enumerated.item = size - 1; + + strcpy(uinfo->value.enumerated.name, + toneport_source_info[uinfo->value.enumerated.item].name); + + return 0; +} + +/* source get callback */ +static int snd_toneport_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_toneport *toneport = + (struct usb_line6_toneport *)line6pcm->line6; + ucontrol->value.enumerated.item[0] = toneport->source; + return 0; +} + +/* source put callback */ +static int snd_toneport_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_line6_pcm *line6pcm = snd_kcontrol_chip(kcontrol); + struct usb_line6_toneport *toneport = + (struct usb_line6_toneport *)line6pcm->line6; + + if (ucontrol->value.enumerated.item[0] == toneport->source) + return 0; + + toneport->source = ucontrol->value.enumerated.item[0]; + toneport_send_cmd(toneport->line6.usbdev, + toneport_source_info[toneport->source].code, 0x0000); + return 1; +} + +static void toneport_start_pcm(unsigned long arg) +{ + struct usb_line6_toneport *toneport = (struct usb_line6_toneport *)arg; + struct usb_line6 *line6 = &toneport->line6; + line6_pcm_acquire(line6->line6pcm, LINE6_BITS_PCM_MONITOR); +} + +/* control definition */ +static struct snd_kcontrol_new toneport_control_monitor = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitor Playback Volume", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_toneport_monitor_info, + .get = snd_toneport_monitor_get, + .put = snd_toneport_monitor_put +}; + +/* source selector definition */ +static struct snd_kcontrol_new toneport_control_source = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Capture Source", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_toneport_source_info, + .get = snd_toneport_source_get, + .put = snd_toneport_source_put +}; + +/* + Toneport destructor. +*/ +static void toneport_destruct(struct usb_interface *interface) +{ + struct usb_line6_toneport *toneport = usb_get_intfdata(interface); + + if (toneport == NULL) + return; + line6_cleanup_audio(&toneport->line6); +} + +/* + Setup Toneport device. +*/ +static void toneport_setup(struct usb_line6_toneport *toneport) +{ + int ticks; + struct usb_line6 *line6 = &toneport->line6; + struct usb_device *usbdev = line6->usbdev; + + /* sync time on device with host: */ + ticks = (int)get_seconds(); + line6_write_data(line6, 0x80c6, &ticks, 4); + + /* enable device: */ + toneport_send_cmd(usbdev, 0x0301, 0x0000); + + /* initialize source select: */ + switch (usbdev->descriptor.idProduct) { + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_PODSTUDIO_UX1: + case LINE6_DEVID_PODSTUDIO_UX2: + toneport_send_cmd(usbdev, + toneport_source_info[toneport->source].code, + 0x0000); + } + + if (toneport_has_led(usbdev->descriptor.idProduct)) + toneport_update_led(&usbdev->dev); +} + +/* + Try to init Toneport device. +*/ +static int toneport_try_init(struct usb_interface *interface, + struct usb_line6_toneport *toneport) +{ + int err; + struct usb_line6 *line6 = &toneport->line6; + struct usb_device *usbdev = line6->usbdev; + + if ((interface == NULL) || (toneport == NULL)) + return -ENODEV; + + /* initialize audio system: */ + err = line6_init_audio(line6); + if (err < 0) + return err; + + /* initialize PCM subsystem: */ + err = line6_init_pcm(line6, &toneport_pcm_properties); + if (err < 0) + return err; + + /* register monitor control: */ + err = snd_ctl_add(line6->card, + snd_ctl_new1(&toneport_control_monitor, + line6->line6pcm)); + if (err < 0) + return err; + + /* register source select control: */ + switch (usbdev->descriptor.idProduct) { + case LINE6_DEVID_TONEPORT_UX1: + case LINE6_DEVID_TONEPORT_UX2: + case LINE6_DEVID_PODSTUDIO_UX1: + case LINE6_DEVID_PODSTUDIO_UX2: + err = + snd_ctl_add(line6->card, + snd_ctl_new1(&toneport_control_source, + line6->line6pcm)); + if (err < 0) + return err; + } + + /* register audio system: */ + err = line6_register_audio(line6); + if (err < 0) + return err; + + line6_read_serial_number(line6, &toneport->serial_number); + line6_read_data(line6, 0x80c2, &toneport->firmware_version, 1); + + if (toneport_has_led(usbdev->descriptor.idProduct)) { + CHECK_RETURN(device_create_file + (&interface->dev, &dev_attr_led_red)); + CHECK_RETURN(device_create_file + (&interface->dev, &dev_attr_led_green)); + } + + toneport_setup(toneport); + + init_timer(&toneport->timer); + toneport->timer.expires = jiffies + TONEPORT_PCM_DELAY * HZ; + toneport->timer.function = toneport_start_pcm; + toneport->timer.data = (unsigned long)toneport; + add_timer(&toneport->timer); + + return 0; +} + +/* + Init Toneport device (and clean up in case of failure). +*/ +int line6_toneport_init(struct usb_interface *interface, + struct usb_line6_toneport *toneport) +{ + int err = toneport_try_init(interface, toneport); + + if (err < 0) + toneport_destruct(interface); + + return err; +} + +/* + Resume Toneport device after reset. +*/ +void line6_toneport_reset_resume(struct usb_line6_toneport *toneport) +{ + toneport_setup(toneport); +} + +/* + Toneport device disconnected. +*/ +void line6_toneport_disconnect(struct usb_interface *interface) +{ + struct usb_line6_toneport *toneport; + + if (interface == NULL) + return; + + toneport = usb_get_intfdata(interface); + del_timer_sync(&toneport->timer); + + if (toneport_has_led(toneport->line6.usbdev->descriptor.idProduct)) { + device_remove_file(&interface->dev, &dev_attr_led_red); + device_remove_file(&interface->dev, &dev_attr_led_green); + } + + if (toneport != NULL) { + struct snd_line6_pcm *line6pcm = toneport->line6.line6pcm; + + if (line6pcm != NULL) { + line6_pcm_release(line6pcm, LINE6_BITS_PCM_MONITOR); + line6_pcm_disconnect(line6pcm); + } + } + + toneport_destruct(interface); +} diff --git a/drivers/staging/line6/toneport.h b/drivers/staging/line6/toneport.h new file mode 100644 index 00000000..8576b726 --- /dev/null +++ b/drivers/staging/line6/toneport.h @@ -0,0 +1,52 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef TONEPORT_H +#define TONEPORT_H + +#include +#include + +#include "driver.h" + +struct usb_line6_toneport { + /** + Generic Line6 USB data. + */ + struct usb_line6 line6; + + /** + Source selector. + */ + int source; + + /** + Serial number of device. + */ + int serial_number; + + /** + Firmware version (x 100). + */ + int firmware_version; + + /** + Timer for delayed PCM startup. + */ + struct timer_list timer; +}; + +extern void line6_toneport_disconnect(struct usb_interface *interface); +extern int line6_toneport_init(struct usb_interface *interface, + struct usb_line6_toneport *toneport); +extern void line6_toneport_reset_resume(struct usb_line6_toneport *toneport); + +#endif diff --git a/drivers/staging/line6/usbdefs.h b/drivers/staging/line6/usbdefs.h new file mode 100644 index 00000000..353d59d7 --- /dev/null +++ b/drivers/staging/line6/usbdefs.h @@ -0,0 +1,107 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2005-2008 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef USBDEFS_H +#define USBDEFS_H + +#define LINE6_VENDOR_ID 0x0e41 + +#define USB_INTERVALS_PER_SECOND 1000 + +/* + Device ids. +*/ +#define LINE6_DEVID_BASSPODXT 0x4250 +#define LINE6_DEVID_BASSPODXTLIVE 0x4642 +#define LINE6_DEVID_BASSPODXTPRO 0x4252 +#define LINE6_DEVID_GUITARPORT 0x4750 +#define LINE6_DEVID_POCKETPOD 0x5051 +#define LINE6_DEVID_PODHD300 0x5057 +#define LINE6_DEVID_PODHD500 0x414D +#define LINE6_DEVID_PODSTUDIO_GX 0x4153 +#define LINE6_DEVID_PODSTUDIO_UX1 0x4150 +#define LINE6_DEVID_PODSTUDIO_UX2 0x4151 +#define LINE6_DEVID_PODX3 0x414a +#define LINE6_DEVID_PODX3LIVE 0x414b +#define LINE6_DEVID_PODXT 0x5044 +#define LINE6_DEVID_PODXTLIVE 0x4650 +#define LINE6_DEVID_PODXTPRO 0x5050 +#define LINE6_DEVID_TONEPORT_GX 0x4147 +#define LINE6_DEVID_TONEPORT_UX1 0x4141 +#define LINE6_DEVID_TONEPORT_UX2 0x4142 +#define LINE6_DEVID_VARIAX 0x534d + +#define LINE6_BIT(x) LINE6_BIT_ ## x = 1 << LINE6_INDEX_ ## x + +enum { + LINE6_INDEX_BASSPODXT, + LINE6_INDEX_BASSPODXTLIVE, + LINE6_INDEX_BASSPODXTPRO, + LINE6_INDEX_GUITARPORT, + LINE6_INDEX_POCKETPOD, + LINE6_INDEX_PODHD300, + LINE6_INDEX_PODHD500, + LINE6_INDEX_PODSTUDIO_GX, + LINE6_INDEX_PODSTUDIO_UX1, + LINE6_INDEX_PODSTUDIO_UX2, + LINE6_INDEX_PODX3, + LINE6_INDEX_PODX3LIVE, + LINE6_INDEX_PODXT, + LINE6_INDEX_PODXTLIVE, + LINE6_INDEX_PODXTPRO, + LINE6_INDEX_TONEPORT_GX, + LINE6_INDEX_TONEPORT_UX1, + LINE6_INDEX_TONEPORT_UX2, + LINE6_INDEX_VARIAX, + + LINE6_BIT(BASSPODXT), + LINE6_BIT(BASSPODXTLIVE), + LINE6_BIT(BASSPODXTPRO), + LINE6_BIT(GUITARPORT), + LINE6_BIT(POCKETPOD), + LINE6_BIT(PODHD300), + LINE6_BIT(PODHD500), + LINE6_BIT(PODSTUDIO_GX), + LINE6_BIT(PODSTUDIO_UX1), + LINE6_BIT(PODSTUDIO_UX2), + LINE6_BIT(PODX3), + LINE6_BIT(PODX3LIVE), + LINE6_BIT(PODXT), + LINE6_BIT(PODXTLIVE), + LINE6_BIT(PODXTPRO), + LINE6_BIT(TONEPORT_GX), + LINE6_BIT(TONEPORT_UX1), + LINE6_BIT(TONEPORT_UX2), + LINE6_BIT(VARIAX), + + LINE6_BITS_PRO = LINE6_BIT_BASSPODXTPRO | LINE6_BIT_PODXTPRO, + LINE6_BITS_LIVE = LINE6_BIT_BASSPODXTLIVE | LINE6_BIT_PODXTLIVE | LINE6_BIT_PODX3LIVE, + LINE6_BITS_PODXTALL = LINE6_BIT_PODXT | LINE6_BIT_PODXTLIVE | LINE6_BIT_PODXTPRO, + LINE6_BITS_PODX3ALL = LINE6_BIT_PODX3 | LINE6_BIT_PODX3LIVE, + LINE6_BITS_PODHDALL = LINE6_BIT_PODHD300 | LINE6_BIT_PODHD500, + LINE6_BITS_BASSPODXTALL = LINE6_BIT_BASSPODXT | LINE6_BIT_BASSPODXTLIVE | LINE6_BIT_BASSPODXTPRO +}; + +/* device supports settings parameter via USB */ +#define LINE6_BIT_CONTROL (1 << 0) +/* device supports PCM input/output via USB */ +#define LINE6_BIT_PCM (1 << 1) +/* device support hardware monitoring */ +#define LINE6_BIT_HWMON (1 << 2) + +#define LINE6_BIT_CONTROL_PCM_HWMON (LINE6_BIT_CONTROL | \ + LINE6_BIT_PCM | \ + LINE6_BIT_HWMON) + +#define LINE6_FALLBACK_INTERVAL 10 +#define LINE6_FALLBACK_MAXPACKETSIZE 16 + +#endif diff --git a/drivers/staging/line6/variax.c b/drivers/staging/line6/variax.c new file mode 100644 index 00000000..d3662222 --- /dev/null +++ b/drivers/staging/line6/variax.c @@ -0,0 +1,719 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#include + +#include "audio.h" +#include "control.h" +#include "driver.h" +#include "variax.h" + +#define VARIAX_SYSEX_CODE 7 +#define VARIAX_SYSEX_PARAM 0x3b +#define VARIAX_SYSEX_ACTIVATE 0x2a +#define VARIAX_MODEL_HEADER_LENGTH 7 +#define VARIAX_MODEL_MESSAGE_LENGTH 199 +#define VARIAX_OFFSET_ACTIVATE 7 + +/* + This message is sent by the device during initialization and identifies + the connected guitar model. +*/ +static const char variax_init_model[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x69, 0x02, + 0x00 +}; + +/* + This message is sent by the device during initialization and identifies + the connected guitar version. +*/ +static const char variax_init_version[] = { + 0xf0, 0x7e, 0x7f, 0x06, 0x02, 0x00, 0x01, 0x0c, + 0x07, 0x00, 0x00, 0x00 +}; + +/* + This message is the last one sent by the device during initialization. +*/ +static const char variax_init_done[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6b +}; + +static const char variax_activate[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x2a, 0x01, + 0xf7 +}; + +static const char variax_request_bank[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x6d, 0xf7 +}; + +static const char variax_request_model1[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x3c, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0x03, + 0x00, 0x00, 0x00, 0xf7 +}; + +static const char variax_request_model2[] = { + 0xf0, 0x00, 0x01, 0x0c, 0x07, 0x00, 0x3c, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x03, + 0x00, 0x00, 0x00, 0xf7 +}; + +/* forward declarations: */ +static int variax_create_files2(struct device *dev); +static void variax_startup2(unsigned long data); +static void variax_startup4(unsigned long data); +static void variax_startup5(unsigned long data); + +/* + Decode data transmitted by workbench. +*/ +static void variax_decode(const unsigned char *raw_data, unsigned char *data, + int raw_size) +{ + for (; raw_size > 0; raw_size -= 6) { + data[2] = raw_data[0] | (raw_data[1] << 4); + data[1] = raw_data[2] | (raw_data[3] << 4); + data[0] = raw_data[4] | (raw_data[5] << 4); + raw_data += 6; + data += 3; + } +} + +static void variax_activate_async(struct usb_line6_variax *variax, int a) +{ + variax->buffer_activate[VARIAX_OFFSET_ACTIVATE] = a; + line6_send_raw_message_async(&variax->line6, variax->buffer_activate, + sizeof(variax_activate)); +} + +/* + Variax startup procedure. + This is a sequence of functions with special requirements (e.g., must + not run immediately after initialization, must not run in interrupt + context). After the last one has finished, the device is ready to use. +*/ + +static void variax_startup1(struct usb_line6_variax *variax) +{ + CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_INIT); + + /* delay startup procedure: */ + line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1, + variax_startup2, (unsigned long)variax); +} + +static void variax_startup2(unsigned long data) +{ + struct usb_line6_variax *variax = (struct usb_line6_variax *)data; + struct usb_line6 *line6 = &variax->line6; + + /* schedule another startup procedure until startup is complete: */ + if (variax->startup_progress >= VARIAX_STARTUP_LAST) + return; + + variax->startup_progress = VARIAX_STARTUP_VERSIONREQ; + line6_start_timer(&variax->startup_timer1, VARIAX_STARTUP_DELAY1, + variax_startup2, (unsigned long)variax); + + /* request firmware version: */ + line6_version_request_async(line6); +} + +static void variax_startup3(struct usb_line6_variax *variax) +{ + CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_WAIT); + + /* delay startup procedure: */ + line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY3, + variax_startup4, (unsigned long)variax); +} + +static void variax_startup4(unsigned long data) +{ + struct usb_line6_variax *variax = (struct usb_line6_variax *)data; + CHECK_STARTUP_PROGRESS(variax->startup_progress, + VARIAX_STARTUP_ACTIVATE); + + /* activate device: */ + variax_activate_async(variax, 1); + line6_start_timer(&variax->startup_timer2, VARIAX_STARTUP_DELAY4, + variax_startup5, (unsigned long)variax); +} + +static void variax_startup5(unsigned long data) +{ + struct usb_line6_variax *variax = (struct usb_line6_variax *)data; + CHECK_STARTUP_PROGRESS(variax->startup_progress, + VARIAX_STARTUP_DUMPREQ); + + /* current model dump: */ + line6_dump_request_async(&variax->dumpreq, &variax->line6, 0, + VARIAX_DUMP_PASS1); + /* passes 2 and 3 are performed implicitly before entering variax_startup6 */ +} + +static void variax_startup6(struct usb_line6_variax *variax) +{ + CHECK_STARTUP_PROGRESS(variax->startup_progress, + VARIAX_STARTUP_WORKQUEUE); + + /* schedule work for global work queue: */ + schedule_work(&variax->startup_work); +} + +static void variax_startup7(struct work_struct *work) +{ + struct usb_line6_variax *variax = + container_of(work, struct usb_line6_variax, startup_work); + struct usb_line6 *line6 = &variax->line6; + + CHECK_STARTUP_PROGRESS(variax->startup_progress, VARIAX_STARTUP_SETUP); + + /* ALSA audio interface: */ + line6_register_audio(&variax->line6); + + /* device files: */ + line6_variax_create_files(0, 0, line6->ifcdev); + variax_create_files2(line6->ifcdev); +} + +/* + Process a completely received message. +*/ +void line6_variax_process_message(struct usb_line6_variax *variax) +{ + const unsigned char *buf = variax->line6.buffer_message; + + switch (buf[0]) { + case LINE6_PARAM_CHANGE | LINE6_CHANNEL_HOST: + switch (buf[1]) { + case VARIAXMIDI_volume: + variax->volume = buf[2]; + break; + + case VARIAXMIDI_tone: + variax->tone = buf[2]; + } + + break; + + case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_DEVICE: + case LINE6_PROGRAM_CHANGE | LINE6_CHANNEL_HOST: + variax->model = buf[1]; + line6_dump_request_async(&variax->dumpreq, &variax->line6, 0, + VARIAX_DUMP_PASS1); + break; + + case LINE6_RESET: + dev_info(variax->line6.ifcdev, "VARIAX reset\n"); + break; + + case LINE6_SYSEX_BEGIN: + if (memcmp(buf + 1, variax_request_model1 + 1, + VARIAX_MODEL_HEADER_LENGTH - 1) == 0) { + if (variax->line6.message_length == + VARIAX_MODEL_MESSAGE_LENGTH) { + switch (variax->dumpreq.in_progress) { + case VARIAX_DUMP_PASS1: + variax_decode(buf + + VARIAX_MODEL_HEADER_LENGTH, + (unsigned char *) + &variax->model_data, + (sizeof + (variax->model_data. + name) + + sizeof(variax-> + model_data. + control) + / 2) * 2); + line6_dump_request_async + (&variax->dumpreq, &variax->line6, + 1, VARIAX_DUMP_PASS2); + break; + + case VARIAX_DUMP_PASS2: + /* model name is transmitted twice, so skip it here: */ + variax_decode(buf + + VARIAX_MODEL_HEADER_LENGTH, + (unsigned char *) + &variax-> + model_data.control + + sizeof(variax->model_data. + control) + / 2, + sizeof(variax->model_data. + control) + / 2 * 2); + line6_dump_request_async + (&variax->dumpreq, &variax->line6, + 2, VARIAX_DUMP_PASS3); + } + } else { + DEBUG_MESSAGES(dev_err + (variax->line6.ifcdev, + "illegal length %d of model data\n", + variax->line6.message_length)); + line6_dump_finished(&variax->dumpreq); + } + } else if (memcmp(buf + 1, variax_request_bank + 1, + sizeof(variax_request_bank) - 2) == 0) { + memcpy(variax->bank, + buf + sizeof(variax_request_bank) - 1, + sizeof(variax->bank)); + line6_dump_finished(&variax->dumpreq); + variax_startup6(variax); + } else if (memcmp(buf + 1, variax_init_model + 1, + sizeof(variax_init_model) - 1) == 0) { + memcpy(variax->guitar, + buf + sizeof(variax_init_model), + sizeof(variax->guitar)); + } else if (memcmp(buf + 1, variax_init_version + 1, + sizeof(variax_init_version) - 1) == 0) { + variax_startup3(variax); + } else if (memcmp(buf + 1, variax_init_done + 1, + sizeof(variax_init_done) - 1) == 0) { + /* notify of complete initialization: */ + variax_startup4((unsigned long)variax); + } + + break; + + case LINE6_SYSEX_END: + break; + + default: + DEBUG_MESSAGES(dev_err + (variax->line6.ifcdev, + "Variax: unknown message %02X\n", buf[0])); + } +} + +/* + "read" request on "volume" special file. +*/ +static ssize_t variax_get_volume(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + return sprintf(buf, "%d\n", variax->volume); +} + +/* + "write" request on "volume" special file. +*/ +static ssize_t variax_set_volume(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + unsigned long value; + int ret; + + ret = strict_strtoul(buf, 10, &value); + if (ret) + return ret; + + if (line6_transmit_parameter(&variax->line6, VARIAXMIDI_volume, + value) == 0) + variax->volume = value; + + return count; +} + +/* + "read" request on "model" special file. +*/ +static ssize_t variax_get_model(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + return sprintf(buf, "%d\n", variax->model); +} + +/* + "write" request on "model" special file. +*/ +static ssize_t variax_set_model(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + unsigned long value; + int ret; + + ret = strict_strtoul(buf, 10, &value); + if (ret) + return ret; + + if (line6_send_program(&variax->line6, value) == 0) + variax->model = value; + + return count; +} + +/* + "read" request on "active" special file. +*/ +static ssize_t variax_get_active(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + return sprintf(buf, "%d\n", + variax->buffer_activate[VARIAX_OFFSET_ACTIVATE]); +} + +/* + "write" request on "active" special file. +*/ +static ssize_t variax_set_active(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + unsigned long value; + int ret; + + ret = strict_strtoul(buf, 10, &value); + if (ret) + return ret; + + variax_activate_async(variax, value ? 1 : 0); + return count; +} + +/* + "read" request on "tone" special file. +*/ +static ssize_t variax_get_tone(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + return sprintf(buf, "%d\n", variax->tone); +} + +/* + "write" request on "tone" special file. +*/ +static ssize_t variax_set_tone(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + unsigned long value; + int ret; + + ret = strict_strtoul(buf, 10, &value); + if (ret) + return ret; + + if (line6_transmit_parameter(&variax->line6, VARIAXMIDI_tone, + value) == 0) + variax->tone = value; + + return count; +} + +static ssize_t get_string(char *buf, const char *data, int length) +{ + int i; + memcpy(buf, data, length); + + for (i = length; i--;) { + char c = buf[i]; + + if ((c != 0) && (c != ' ')) + break; + } + + buf[i + 1] = '\n'; + return i + 2; +} + +/* + "read" request on "name" special file. +*/ +static ssize_t variax_get_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + line6_dump_wait_interruptible(&variax->dumpreq); + return get_string(buf, variax->model_data.name, + sizeof(variax->model_data.name)); +} + +/* + "read" request on "bank" special file. +*/ +static ssize_t variax_get_bank(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + line6_dump_wait_interruptible(&variax->dumpreq); + return get_string(buf, variax->bank, sizeof(variax->bank)); +} + +/* + "read" request on "dump" special file. +*/ +static ssize_t variax_get_dump(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + int retval; + retval = line6_dump_wait_interruptible(&variax->dumpreq); + if (retval < 0) + return retval; + memcpy(buf, &variax->model_data.control, + sizeof(variax->model_data.control)); + return sizeof(variax->model_data.control); +} + +/* + "read" request on "guitar" special file. +*/ +static ssize_t variax_get_guitar(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + return sprintf(buf, "%s\n", variax->guitar); +} + +#ifdef CONFIG_LINE6_USB_RAW + +static char *variax_alloc_sysex_buffer(struct usb_line6_variax *variax, + int code, int size) +{ + return line6_alloc_sysex_buffer(&variax->line6, VARIAX_SYSEX_CODE, code, + size); +} + +/* + "write" request on "raw" special file. +*/ +static ssize_t variax_set_raw2(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_line6_variax *variax = + usb_get_intfdata(to_usb_interface(dev)); + int size; + int i; + char *sysex; + + count -= count % 3; + size = count * 2; + sysex = variax_alloc_sysex_buffer(variax, VARIAX_SYSEX_PARAM, size); + + if (!sysex) + return 0; + + for (i = 0; i < count; i += 3) { + const unsigned char *p1 = buf + i; + char *p2 = sysex + SYSEX_DATA_OFS + i * 2; + p2[0] = p1[2] & 0x0f; + p2[1] = p1[2] >> 4; + p2[2] = p1[1] & 0x0f; + p2[3] = p1[1] >> 4; + p2[4] = p1[0] & 0x0f; + p2[5] = p1[0] >> 4; + } + + line6_send_sysex_message(&variax->line6, sysex, size); + kfree(sysex); + return count; +} + +#endif + +/* Variax workbench special files: */ +static DEVICE_ATTR(model, S_IWUSR | S_IRUGO, variax_get_model, + variax_set_model); +static DEVICE_ATTR(volume, S_IWUSR | S_IRUGO, variax_get_volume, + variax_set_volume); +static DEVICE_ATTR(tone, S_IWUSR | S_IRUGO, variax_get_tone, variax_set_tone); +static DEVICE_ATTR(name, S_IRUGO, variax_get_name, line6_nop_write); +static DEVICE_ATTR(bank, S_IRUGO, variax_get_bank, line6_nop_write); +static DEVICE_ATTR(dump, S_IRUGO, variax_get_dump, line6_nop_write); +static DEVICE_ATTR(active, S_IWUSR | S_IRUGO, variax_get_active, + variax_set_active); +static DEVICE_ATTR(guitar, S_IRUGO, variax_get_guitar, line6_nop_write); + +#ifdef CONFIG_LINE6_USB_RAW +static DEVICE_ATTR(raw, S_IWUSR, line6_nop_read, line6_set_raw); +static DEVICE_ATTR(raw2, S_IWUSR, line6_nop_read, variax_set_raw2); +#endif + +/* + Variax destructor. +*/ +static void variax_destruct(struct usb_interface *interface) +{ + struct usb_line6_variax *variax = usb_get_intfdata(interface); + + if (variax == NULL) + return; + line6_cleanup_audio(&variax->line6); + + del_timer(&variax->startup_timer1); + del_timer(&variax->startup_timer2); + cancel_work_sync(&variax->startup_work); + + /* free dump request data: */ + line6_dumpreq_destructbuf(&variax->dumpreq, 2); + line6_dumpreq_destructbuf(&variax->dumpreq, 1); + line6_dumpreq_destruct(&variax->dumpreq); + + kfree(variax->buffer_activate); +} + +/* + Create sysfs entries. +*/ +static int variax_create_files2(struct device *dev) +{ + int err; + CHECK_RETURN(device_create_file(dev, &dev_attr_model)); + CHECK_RETURN(device_create_file(dev, &dev_attr_volume)); + CHECK_RETURN(device_create_file(dev, &dev_attr_tone)); + CHECK_RETURN(device_create_file(dev, &dev_attr_name)); + CHECK_RETURN(device_create_file(dev, &dev_attr_bank)); + CHECK_RETURN(device_create_file(dev, &dev_attr_dump)); + CHECK_RETURN(device_create_file(dev, &dev_attr_active)); + CHECK_RETURN(device_create_file(dev, &dev_attr_guitar)); +#ifdef CONFIG_LINE6_USB_RAW + CHECK_RETURN(device_create_file(dev, &dev_attr_raw)); + CHECK_RETURN(device_create_file(dev, &dev_attr_raw2)); +#endif + return 0; +} + +/* + Try to init workbench device. +*/ +static int variax_try_init(struct usb_interface *interface, + struct usb_line6_variax *variax) +{ + int err; + + init_timer(&variax->startup_timer1); + init_timer(&variax->startup_timer2); + INIT_WORK(&variax->startup_work, variax_startup7); + + if ((interface == NULL) || (variax == NULL)) + return -ENODEV; + + /* initialize USB buffers: */ + err = line6_dumpreq_init(&variax->dumpreq, variax_request_model1, + sizeof(variax_request_model1)); + + if (err < 0) { + dev_err(&interface->dev, "Out of memory\n"); + return err; + } + + err = line6_dumpreq_initbuf(&variax->dumpreq, variax_request_model2, + sizeof(variax_request_model2), 1); + + if (err < 0) { + dev_err(&interface->dev, "Out of memory\n"); + return err; + } + + err = line6_dumpreq_initbuf(&variax->dumpreq, variax_request_bank, + sizeof(variax_request_bank), 2); + + if (err < 0) { + dev_err(&interface->dev, "Out of memory\n"); + return err; + } + + variax->buffer_activate = kmemdup(variax_activate, + sizeof(variax_activate), GFP_KERNEL); + + if (variax->buffer_activate == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + return -ENOMEM; + } + + /* initialize audio system: */ + err = line6_init_audio(&variax->line6); + if (err < 0) + return err; + + /* initialize MIDI subsystem: */ + err = line6_init_midi(&variax->line6); + if (err < 0) + return err; + + /* initiate startup procedure: */ + variax_startup1(variax); + return 0; +} + +/* + Init workbench device (and clean up in case of failure). +*/ +int line6_variax_init(struct usb_interface *interface, + struct usb_line6_variax *variax) +{ + int err = variax_try_init(interface, variax); + + if (err < 0) + variax_destruct(interface); + + return err; +} + +/* + Workbench device disconnected. +*/ +void line6_variax_disconnect(struct usb_interface *interface) +{ + struct device *dev; + + if (interface == NULL) + return; + dev = &interface->dev; + + if (dev != NULL) { + /* remove sysfs entries: */ + line6_variax_remove_files(0, 0, dev); + device_remove_file(dev, &dev_attr_model); + device_remove_file(dev, &dev_attr_volume); + device_remove_file(dev, &dev_attr_tone); + device_remove_file(dev, &dev_attr_name); + device_remove_file(dev, &dev_attr_bank); + device_remove_file(dev, &dev_attr_dump); + device_remove_file(dev, &dev_attr_active); + device_remove_file(dev, &dev_attr_guitar); +#ifdef CONFIG_LINE6_USB_RAW + device_remove_file(dev, &dev_attr_raw); + device_remove_file(dev, &dev_attr_raw2); +#endif + } + + variax_destruct(interface); +} diff --git a/drivers/staging/line6/variax.h b/drivers/staging/line6/variax.h new file mode 100644 index 00000000..e2999ab4 --- /dev/null +++ b/drivers/staging/line6/variax.h @@ -0,0 +1,132 @@ +/* + * Line6 Linux USB driver - 0.9.1beta + * + * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + */ + +#ifndef VARIAX_H +#define VARIAX_H + +#include +#include +#include +#include + +#include "driver.h" +#include "dumprequest.h" + +#define VARIAX_STARTUP_DELAY1 1000 +#define VARIAX_STARTUP_DELAY3 100 +#define VARIAX_STARTUP_DELAY4 100 + +/* + Stages of Variax startup procedure +*/ +enum { + VARIAX_STARTUP_INIT = 1, + VARIAX_STARTUP_VERSIONREQ, + VARIAX_STARTUP_WAIT, + VARIAX_STARTUP_ACTIVATE, + VARIAX_STARTUP_DUMPREQ, + VARIAX_STARTUP_WORKQUEUE, + VARIAX_STARTUP_SETUP, + VARIAX_STARTUP_LAST = VARIAX_STARTUP_SETUP - 1 +}; + +enum { + VARIAX_DUMP_PASS1 = LINE6_DUMP_CURRENT, + VARIAX_DUMP_PASS2, + VARIAX_DUMP_PASS3 +}; + +/** + Binary Variax model dump +*/ +struct variax_model { + /** + Header information (including program name). + */ + unsigned char name[18]; + + /** + Model parameters. + */ + unsigned char control[78 * 2]; +}; + +struct usb_line6_variax { + /** + Generic Line6 USB data. + */ + struct usb_line6 line6; + + /** + Dump request structure. + Append two extra buffers for 3-pass data query. + */ + struct line6_dump_request dumpreq; + struct line6_dump_reqbuf extrabuf[2]; + + /** + Buffer for activation code. + */ + unsigned char *buffer_activate; + + /** + Model number. + */ + int model; + + /** + Current model settings. + */ + struct variax_model model_data; + + /** + Name of connected guitar. + */ + unsigned char guitar[18]; + + /** + Name of current model bank. + */ + unsigned char bank[18]; + + /** + Position of volume dial. + */ + int volume; + + /** + Position of tone control dial. + */ + int tone; + + /** + Handler for device initializaton. + */ + struct work_struct startup_work; + + /** + Timers for device initializaton. + */ + struct timer_list startup_timer1; + struct timer_list startup_timer2; + + /** + Current progress in startup procedure. + */ + int startup_progress; +}; + +extern void line6_variax_disconnect(struct usb_interface *interface); +extern int line6_variax_init(struct usb_interface *interface, + struct usb_line6_variax *variax); +extern void line6_variax_process_message(struct usb_line6_variax *variax); + +#endif diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig new file mode 100644 index 00000000..4f4b7d62 --- /dev/null +++ b/drivers/staging/media/Kconfig @@ -0,0 +1,37 @@ +menuconfig STAGING_MEDIA + bool "Media staging drivers" + default n + ---help--- + This option allows you to select a number of media drivers that + don't have the "normal" Linux kernel quality level. + Most of them don't follow properly the V4L, DVB and/or RC API's, + so, they won't likely work fine with the existing applications. + That also means that, once fixed, their API's will change to match + the existing ones. + + If you wish to work on these drivers, to help improve them, or + to report problems you have with them, please use the + linux-media@vger.kernel.org mailing list. + + If in doubt, say N here. + + +if STAGING_MEDIA + +# Please keep them in alphabetic order +source "drivers/staging/media/as102/Kconfig" + +source "drivers/staging/media/cxd2099/Kconfig" + +source "drivers/staging/media/dt3155v4l/Kconfig" + +source "drivers/staging/media/easycap/Kconfig" + +source "drivers/staging/media/go7007/Kconfig" + +source "drivers/staging/media/solo6x10/Kconfig" + +# Keep LIRC at the end, as it has sub-menus +source "drivers/staging/media/lirc/Kconfig" + +endif diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile new file mode 100644 index 00000000..c69124cd --- /dev/null +++ b/drivers/staging/media/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_DVB_AS102) += as102/ +obj-$(CONFIG_DVB_CXD2099) += cxd2099/ +obj-$(CONFIG_EASYCAP) += easycap/ +obj-$(CONFIG_LIRC_STAGING) += lirc/ +obj-$(CONFIG_SOLO6X10) += solo6x10/ +obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l/ +obj-$(CONFIG_VIDEO_GO7007) += go7007/ diff --git a/drivers/staging/media/as102/Kconfig b/drivers/staging/media/as102/Kconfig new file mode 100644 index 00000000..28aba00d --- /dev/null +++ b/drivers/staging/media/as102/Kconfig @@ -0,0 +1,8 @@ +config DVB_AS102 + tristate "Abilis AS102 DVB receiver" + depends on DVB_CORE && USB && I2C && INPUT + select FW_LOADER + help + Choose Y or M here if you have a device containing an AS102 + + To compile this driver as a module, choose M here diff --git a/drivers/staging/media/as102/Makefile b/drivers/staging/media/as102/Makefile new file mode 100644 index 00000000..1bca43e8 --- /dev/null +++ b/drivers/staging/media/as102/Makefile @@ -0,0 +1,6 @@ +dvb-as102-objs := as102_drv.o as102_fw.o as10x_cmd.o as10x_cmd_stream.o \ + as102_fe.o as102_usb_drv.o as10x_cmd_cfg.o + +obj-$(CONFIG_DVB_AS102) += dvb-as102.o + +EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core diff --git a/drivers/staging/media/as102/as102_drv.c b/drivers/staging/media/as102/as102_drv.c new file mode 100644 index 00000000..ea4f992d --- /dev/null +++ b/drivers/staging/media/as102/as102_drv.c @@ -0,0 +1,319 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * Copyright (C) 2010 Devin Heitmueller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* header file for usb device driver*/ +#include "as102_drv.h" +#include "as102_fw.h" +#include "dvbdev.h" + +int as102_debug; +module_param_named(debug, as102_debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off debugging (default: off)"); + +int dual_tuner; +module_param_named(dual_tuner, dual_tuner, int, 0644); +MODULE_PARM_DESC(dual_tuner, "Activate Dual-Tuner config (default: off)"); + +static int fw_upload = 1; +module_param_named(fw_upload, fw_upload, int, 0644); +MODULE_PARM_DESC(fw_upload, "Turn on/off default FW upload (default: on)"); + +static int pid_filtering; +module_param_named(pid_filtering, pid_filtering, int, 0644); +MODULE_PARM_DESC(pid_filtering, "Activate HW PID filtering (default: off)"); + +static int ts_auto_disable; +module_param_named(ts_auto_disable, ts_auto_disable, int, 0644); +MODULE_PARM_DESC(ts_auto_disable, "Stream Auto Enable on FW (default: off)"); + +int elna_enable = 1; +module_param_named(elna_enable, elna_enable, int, 0644); +MODULE_PARM_DESC(elna_enable, "Activate eLNA (default: on)"); + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static void as102_stop_stream(struct as102_dev_t *dev) +{ + struct as10x_bus_adapter_t *bus_adap; + + if (dev != NULL) + bus_adap = &dev->bus_adap; + else + return; + + if (bus_adap->ops->stop_stream != NULL) + bus_adap->ops->stop_stream(dev); + + if (ts_auto_disable) { + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return; + + if (as10x_cmd_stop_streaming(bus_adap) < 0) + dprintk(debug, "as10x_cmd_stop_streaming failed\n"); + + mutex_unlock(&dev->bus_adap.lock); + } +} + +static int as102_start_stream(struct as102_dev_t *dev) +{ + struct as10x_bus_adapter_t *bus_adap; + int ret = -EFAULT; + + if (dev != NULL) + bus_adap = &dev->bus_adap; + else + return ret; + + if (bus_adap->ops->start_stream != NULL) + ret = bus_adap->ops->start_stream(dev); + + if (ts_auto_disable) { + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EFAULT; + + ret = as10x_cmd_start_streaming(bus_adap); + + mutex_unlock(&dev->bus_adap.lock); + } + + return ret; +} + +static int as10x_pid_filter(struct as102_dev_t *dev, + int index, u16 pid, int onoff) { + + struct as10x_bus_adapter_t *bus_adap = &dev->bus_adap; + int ret = -EFAULT; + + ENTER(); + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) { + dprintk(debug, "mutex_lock_interruptible(lock) failed !\n"); + return -EBUSY; + } + + switch (onoff) { + case 0: + ret = as10x_cmd_del_PID_filter(bus_adap, (uint16_t) pid); + dprintk(debug, "DEL_PID_FILTER([%02d] 0x%04x) ret = %d\n", + index, pid, ret); + break; + case 1: + { + struct as10x_ts_filter filter; + + filter.type = TS_PID_TYPE_TS; + filter.idx = 0xFF; + filter.pid = pid; + + ret = as10x_cmd_add_PID_filter(bus_adap, &filter); + dprintk(debug, "ADD_PID_FILTER([%02d -> %02d], 0x%04x) ret = %d\n", + index, filter.idx, filter.pid, ret); + break; + } + } + + mutex_unlock(&dev->bus_adap.lock); + + LEAVE(); + return ret; +} + +static int as102_dvb_dmx_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + int ret = 0; + struct dvb_demux *demux = dvbdmxfeed->demux; + struct as102_dev_t *as102_dev = demux->priv; + + ENTER(); + + if (mutex_lock_interruptible(&as102_dev->sem)) + return -ERESTARTSYS; + + if (pid_filtering) + as10x_pid_filter(as102_dev, dvbdmxfeed->index, + dvbdmxfeed->pid, 1); + + if (as102_dev->streaming++ == 0) + ret = as102_start_stream(as102_dev); + + mutex_unlock(&as102_dev->sem); + LEAVE(); + return ret; +} + +static int as102_dvb_dmx_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *demux = dvbdmxfeed->demux; + struct as102_dev_t *as102_dev = demux->priv; + + ENTER(); + + if (mutex_lock_interruptible(&as102_dev->sem)) + return -ERESTARTSYS; + + if (--as102_dev->streaming == 0) + as102_stop_stream(as102_dev); + + if (pid_filtering) + as10x_pid_filter(as102_dev, dvbdmxfeed->index, + dvbdmxfeed->pid, 0); + + mutex_unlock(&as102_dev->sem); + LEAVE(); + return 0; +} + +int as102_dvb_register(struct as102_dev_t *as102_dev) +{ + struct device *dev = &as102_dev->bus_adap.usb_dev->dev; + int ret; + + ret = dvb_register_adapter(&as102_dev->dvb_adap, + as102_dev->name, THIS_MODULE, + dev, adapter_nr); + if (ret < 0) { + dev_err(dev, "%s: dvb_register_adapter() failed: %d\n", + __func__, ret); + return ret; + } + + as102_dev->dvb_dmx.priv = as102_dev; + as102_dev->dvb_dmx.filternum = pid_filtering ? 16 : 256; + as102_dev->dvb_dmx.feednum = 256; + as102_dev->dvb_dmx.start_feed = as102_dvb_dmx_start_feed; + as102_dev->dvb_dmx.stop_feed = as102_dvb_dmx_stop_feed; + + as102_dev->dvb_dmx.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING; + + as102_dev->dvb_dmxdev.filternum = as102_dev->dvb_dmx.filternum; + as102_dev->dvb_dmxdev.demux = &as102_dev->dvb_dmx.dmx; + as102_dev->dvb_dmxdev.capabilities = 0; + + ret = dvb_dmx_init(&as102_dev->dvb_dmx); + if (ret < 0) { + dev_err(dev, "%s: dvb_dmx_init() failed: %d\n", __func__, ret); + goto edmxinit; + } + + ret = dvb_dmxdev_init(&as102_dev->dvb_dmxdev, &as102_dev->dvb_adap); + if (ret < 0) { + dev_err(dev, "%s: dvb_dmxdev_init() failed: %d\n", + __func__, ret); + goto edmxdinit; + } + + ret = as102_dvb_register_fe(as102_dev, &as102_dev->dvb_fe); + if (ret < 0) { + dev_err(dev, "%s: as102_dvb_register_frontend() failed: %d", + __func__, ret); + goto efereg; + } + + /* init bus mutex for token locking */ + mutex_init(&as102_dev->bus_adap.lock); + + /* init start / stop stream mutex */ + mutex_init(&as102_dev->sem); + + /* + * try to load as102 firmware. If firmware upload failed, we'll be + * able to upload it later. + */ + if (fw_upload) + try_then_request_module(as102_fw_upload(&as102_dev->bus_adap), + "firmware_class"); + + pr_info("Registered device %s", as102_dev->name); + return 0; + +efereg: + dvb_dmxdev_release(&as102_dev->dvb_dmxdev); +edmxdinit: + dvb_dmx_release(&as102_dev->dvb_dmx); +edmxinit: + dvb_unregister_adapter(&as102_dev->dvb_adap); + return ret; +} + +void as102_dvb_unregister(struct as102_dev_t *as102_dev) +{ + /* unregister as102 frontend */ + as102_dvb_unregister_fe(&as102_dev->dvb_fe); + + /* unregister demux device */ + dvb_dmxdev_release(&as102_dev->dvb_dmxdev); + dvb_dmx_release(&as102_dev->dvb_dmx); + + /* unregister dvb adapter */ + dvb_unregister_adapter(&as102_dev->dvb_adap); + + pr_info("Unregistered device %s", as102_dev->name); +} + +static int __init as102_driver_init(void) +{ + int ret; + + /* register this driver with the low level subsystem */ + ret = usb_register(&as102_usb_driver); + if (ret) + err("usb_register failed (ret = %d)", ret); + + return ret; +} + +/* + * Mandatory function : Adds a special section to the module indicating + * where initialisation function is defined + */ +module_init(as102_driver_init); + +/** + * as102_driver_exit - as102 driver exit point + * + * This function is called when device has to be removed. + */ +static void __exit as102_driver_exit(void) +{ + /* deregister this driver with the low level bus subsystem */ + usb_deregister(&as102_usb_driver); +} + +/* + * required function for unload: Adds a special section to the module + * indicating where unload function is defined + */ +module_exit(as102_driver_exit); +/* modinfo details */ +MODULE_DESCRIPTION(DRIVER_FULL_NAME); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Pierrick Hascoet "); diff --git a/drivers/staging/media/as102/as102_drv.h b/drivers/staging/media/as102/as102_drv.h new file mode 100644 index 00000000..b0e5a23b --- /dev/null +++ b/drivers/staging/media/as102/as102_drv.h @@ -0,0 +1,107 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include "as10x_cmd.h" +#include "as102_usb_drv.h" + +#define DRIVER_FULL_NAME "Abilis Systems as10x usb driver" +#define DRIVER_NAME "as10x_usb" + +extern int as102_debug; +#define debug as102_debug +extern struct usb_driver as102_usb_driver; +extern int elna_enable; + +#define dprintk(debug, args...) \ + do { if (debug) { \ + pr_debug("%s: ", __func__); \ + printk(args); \ + } } while (0) + +#ifdef TRACE +#define ENTER() pr_debug(">> enter %s\n", __func__) +#define LEAVE() pr_debug("<< leave %s\n", __func__) +#else +#define ENTER() +#define LEAVE() +#endif + +#define AS102_DEVICE_MAJOR 192 + +#define AS102_USB_BUF_SIZE 512 +#define MAX_STREAM_URB 32 + +struct as10x_bus_adapter_t { + struct usb_device *usb_dev; + /* bus token lock */ + struct mutex lock; + /* low level interface for bus adapter */ + union as10x_bus_token_t { + /* usb token */ + struct as10x_usb_token_cmd_t usb; + } token; + + /* token cmd xfer id */ + uint16_t cmd_xid; + + /* as10x command and response for dvb interface*/ + struct as10x_cmd_t *cmd, *rsp; + + /* bus adapter private ops callback */ + struct as102_priv_ops_t *ops; +}; + +struct as102_dev_t { + const char *name; + struct as10x_bus_adapter_t bus_adap; + struct list_head device_entry; + struct kref kref; + uint8_t elna_cfg; + + struct dvb_adapter dvb_adap; + struct dvb_frontend dvb_fe; + struct dvb_demux dvb_dmx; + struct dmxdev dvb_dmxdev; + + /* demodulator stats */ + struct as10x_demod_stats demod_stats; + /* signal strength */ + uint16_t signal_strength; + /* bit error rate */ + uint32_t ber; + + /* timer handle to trig ts stream download */ + struct timer_list timer_handle; + + struct mutex sem; + dma_addr_t dma_addr; + void *stream; + int streaming; + struct urb *stream_urb[MAX_STREAM_URB]; +}; + +int as102_dvb_register(struct as102_dev_t *dev); +void as102_dvb_unregister(struct as102_dev_t *dev); + +int as102_dvb_register_fe(struct as102_dev_t *dev, struct dvb_frontend *fe); +int as102_dvb_unregister_fe(struct dvb_frontend *dev); diff --git a/drivers/staging/media/as102/as102_fe.c b/drivers/staging/media/as102/as102_fe.c new file mode 100644 index 00000000..5917657b --- /dev/null +++ b/drivers/staging/media/as102/as102_fe.c @@ -0,0 +1,598 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * Copyright (C) 2010 Devin Heitmueller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include + +#include "as102_drv.h" +#include "as10x_types.h" +#include "as10x_cmd.h" + +static void as10x_fe_copy_tps_parameters(struct dtv_frontend_properties *dst, + struct as10x_tps *src); + +static void as102_fe_copy_tune_parameters(struct as10x_tune_args *dst, + struct dtv_frontend_properties *src); + +static int as102_fe_set_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + int ret = 0; + struct as102_dev_t *dev; + struct as10x_tune_args tune_args = { 0 }; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + as102_fe_copy_tune_parameters(&tune_args, p); + + /* send abilis command: SET_TUNE */ + ret = as10x_cmd_set_tune(&dev->bus_adap, &tune_args); + if (ret != 0) + dprintk(debug, "as10x_cmd_set_tune failed. (err = %d)\n", ret); + + mutex_unlock(&dev->bus_adap.lock); + + LEAVE(); + return (ret < 0) ? -EINVAL : 0; +} + +static int as102_fe_get_frontend(struct dvb_frontend *fe) +{ + struct dtv_frontend_properties *p = &fe->dtv_property_cache; + int ret = 0; + struct as102_dev_t *dev; + struct as10x_tps tps = { 0 }; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -EINVAL; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + /* send abilis command: GET_TPS */ + ret = as10x_cmd_get_tps(&dev->bus_adap, &tps); + + if (ret == 0) + as10x_fe_copy_tps_parameters(p, &tps); + + mutex_unlock(&dev->bus_adap.lock); + + LEAVE(); + return (ret < 0) ? -EINVAL : 0; +} + +static int as102_fe_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *settings) { + ENTER(); + +#if 0 + dprintk(debug, "step_size = %d\n", settings->step_size); + dprintk(debug, "max_drift = %d\n", settings->max_drift); + dprintk(debug, "min_delay_ms = %d -> %d\n", settings->min_delay_ms, + 1000); +#endif + + settings->min_delay_ms = 1000; + + LEAVE(); + return 0; +} + + +static int as102_fe_read_status(struct dvb_frontend *fe, fe_status_t *status) +{ + int ret = 0; + struct as102_dev_t *dev; + struct as10x_tune_status tstate = { 0 }; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + /* send abilis command: GET_TUNE_STATUS */ + ret = as10x_cmd_get_tune_status(&dev->bus_adap, &tstate); + if (ret < 0) { + dprintk(debug, "as10x_cmd_get_tune_status failed (err = %d)\n", + ret); + goto out; + } + + dev->signal_strength = tstate.signal_strength; + dev->ber = tstate.BER; + + switch (tstate.tune_state) { + case TUNE_STATUS_SIGNAL_DVB_OK: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER; + break; + case TUNE_STATUS_STREAM_DETECTED: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC; + break; + case TUNE_STATUS_STREAM_TUNED: + *status = FE_HAS_SIGNAL | FE_HAS_CARRIER | FE_HAS_SYNC | + FE_HAS_LOCK; + break; + default: + *status = TUNE_STATUS_NOT_TUNED; + } + + dprintk(debug, "tuner status: 0x%02x, strength %d, per: %d, ber: %d\n", + tstate.tune_state, tstate.signal_strength, + tstate.PER, tstate.BER); + + if (*status & FE_HAS_LOCK) { + if (as10x_cmd_get_demod_stats(&dev->bus_adap, + (struct as10x_demod_stats *) &dev->demod_stats) < 0) { + memset(&dev->demod_stats, 0, sizeof(dev->demod_stats)); + dprintk(debug, "as10x_cmd_get_demod_stats failed " + "(probably not tuned)\n"); + } else { + dprintk(debug, + "demod status: fc: 0x%08x, bad fc: 0x%08x, " + "bytes corrected: 0x%08x , MER: 0x%04x\n", + dev->demod_stats.frame_count, + dev->demod_stats.bad_frame_count, + dev->demod_stats.bytes_fixed_by_rs, + dev->demod_stats.mer); + } + } else { + memset(&dev->demod_stats, 0, sizeof(dev->demod_stats)); + } + +out: + mutex_unlock(&dev->bus_adap.lock); + LEAVE(); + return ret; +} + +/* + * Note: + * - in AS102 SNR=MER + * - the SNR will be returned in linear terms, i.e. not in dB + * - the accuracy equals ±2dB for a SNR range from 4dB to 30dB + * - the accuracy is >2dB for SNR values outside this range + */ +static int as102_fe_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + struct as102_dev_t *dev; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + *snr = dev->demod_stats.mer; + + LEAVE(); + return 0; +} + +static int as102_fe_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct as102_dev_t *dev; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + *ber = dev->ber; + + LEAVE(); + return 0; +} + +static int as102_fe_read_signal_strength(struct dvb_frontend *fe, + u16 *strength) +{ + struct as102_dev_t *dev; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + *strength = (((0xffff * 400) * dev->signal_strength + 41000) * 2); + + LEAVE(); + return 0; +} + +static int as102_fe_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks) +{ + struct as102_dev_t *dev; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (dev->demod_stats.has_started) + *ucblocks = dev->demod_stats.bad_frame_count; + else + *ucblocks = 0; + + LEAVE(); + return 0; +} + +static int as102_fe_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) +{ + struct as102_dev_t *dev; + int ret; + + ENTER(); + + dev = (struct as102_dev_t *) fe->tuner_priv; + if (dev == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&dev->bus_adap.lock)) + return -EBUSY; + + if (acquire) { + if (elna_enable) + as10x_cmd_set_context(&dev->bus_adap, CONTEXT_LNA, dev->elna_cfg); + + ret = as10x_cmd_turn_on(&dev->bus_adap); + } else { + ret = as10x_cmd_turn_off(&dev->bus_adap); + } + + mutex_unlock(&dev->bus_adap.lock); + + LEAVE(); + return ret; +} + +static struct dvb_frontend_ops as102_fe_ops = { + .delsys = { SYS_DVBT }, + .info = { + .name = "Unknown AS102 device", + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 166667, + .caps = FE_CAN_INVERSION_AUTO + | FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 + | FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO + | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QPSK + | FE_CAN_QAM_AUTO + | FE_CAN_TRANSMISSION_MODE_AUTO + | FE_CAN_GUARD_INTERVAL_AUTO + | FE_CAN_HIERARCHY_AUTO + | FE_CAN_RECOVER + | FE_CAN_MUTE_TS + }, + + .set_frontend = as102_fe_set_frontend, + .get_frontend = as102_fe_get_frontend, + .get_tune_settings = as102_fe_get_tune_settings, + + .read_status = as102_fe_read_status, + .read_snr = as102_fe_read_snr, + .read_ber = as102_fe_read_ber, + .read_signal_strength = as102_fe_read_signal_strength, + .read_ucblocks = as102_fe_read_ucblocks, + .ts_bus_ctrl = as102_fe_ts_bus_ctrl, +}; + +int as102_dvb_unregister_fe(struct dvb_frontend *fe) +{ + /* unregister frontend */ + dvb_unregister_frontend(fe); + + /* detach frontend */ + dvb_frontend_detach(fe); + + return 0; +} + +int as102_dvb_register_fe(struct as102_dev_t *as102_dev, + struct dvb_frontend *dvb_fe) +{ + int errno; + struct dvb_adapter *dvb_adap; + + if (as102_dev == NULL) + return -EINVAL; + + /* extract dvb_adapter */ + dvb_adap = &as102_dev->dvb_adap; + + /* init frontend callback ops */ + memcpy(&dvb_fe->ops, &as102_fe_ops, sizeof(struct dvb_frontend_ops)); + strncpy(dvb_fe->ops.info.name, as102_dev->name, + sizeof(dvb_fe->ops.info.name)); + + /* register dvb frontend */ + errno = dvb_register_frontend(dvb_adap, dvb_fe); + if (errno == 0) + dvb_fe->tuner_priv = as102_dev; + + return errno; +} + +static void as10x_fe_copy_tps_parameters(struct dtv_frontend_properties *fe_tps, + struct as10x_tps *as10x_tps) +{ + + /* extract constellation */ + switch (as10x_tps->modulation) { + case CONST_QPSK: + fe_tps->modulation = QPSK; + break; + case CONST_QAM16: + fe_tps->modulation = QAM_16; + break; + case CONST_QAM64: + fe_tps->modulation = QAM_64; + break; + } + + /* extract hierarchy */ + switch (as10x_tps->hierarchy) { + case HIER_NONE: + fe_tps->hierarchy = HIERARCHY_NONE; + break; + case HIER_ALPHA_1: + fe_tps->hierarchy = HIERARCHY_1; + break; + case HIER_ALPHA_2: + fe_tps->hierarchy = HIERARCHY_2; + break; + case HIER_ALPHA_4: + fe_tps->hierarchy = HIERARCHY_4; + break; + } + + /* extract code rate HP */ + switch (as10x_tps->code_rate_HP) { + case CODE_RATE_1_2: + fe_tps->code_rate_HP = FEC_1_2; + break; + case CODE_RATE_2_3: + fe_tps->code_rate_HP = FEC_2_3; + break; + case CODE_RATE_3_4: + fe_tps->code_rate_HP = FEC_3_4; + break; + case CODE_RATE_5_6: + fe_tps->code_rate_HP = FEC_5_6; + break; + case CODE_RATE_7_8: + fe_tps->code_rate_HP = FEC_7_8; + break; + } + + /* extract code rate LP */ + switch (as10x_tps->code_rate_LP) { + case CODE_RATE_1_2: + fe_tps->code_rate_LP = FEC_1_2; + break; + case CODE_RATE_2_3: + fe_tps->code_rate_LP = FEC_2_3; + break; + case CODE_RATE_3_4: + fe_tps->code_rate_LP = FEC_3_4; + break; + case CODE_RATE_5_6: + fe_tps->code_rate_LP = FEC_5_6; + break; + case CODE_RATE_7_8: + fe_tps->code_rate_LP = FEC_7_8; + break; + } + + /* extract guard interval */ + switch (as10x_tps->guard_interval) { + case GUARD_INT_1_32: + fe_tps->guard_interval = GUARD_INTERVAL_1_32; + break; + case GUARD_INT_1_16: + fe_tps->guard_interval = GUARD_INTERVAL_1_16; + break; + case GUARD_INT_1_8: + fe_tps->guard_interval = GUARD_INTERVAL_1_8; + break; + case GUARD_INT_1_4: + fe_tps->guard_interval = GUARD_INTERVAL_1_4; + break; + } + + /* extract transmission mode */ + switch (as10x_tps->transmission_mode) { + case TRANS_MODE_2K: + fe_tps->transmission_mode = TRANSMISSION_MODE_2K; + break; + case TRANS_MODE_8K: + fe_tps->transmission_mode = TRANSMISSION_MODE_8K; + break; + } +} + +static uint8_t as102_fe_get_code_rate(fe_code_rate_t arg) +{ + uint8_t c; + + switch (arg) { + case FEC_1_2: + c = CODE_RATE_1_2; + break; + case FEC_2_3: + c = CODE_RATE_2_3; + break; + case FEC_3_4: + c = CODE_RATE_3_4; + break; + case FEC_5_6: + c = CODE_RATE_5_6; + break; + case FEC_7_8: + c = CODE_RATE_7_8; + break; + default: + c = CODE_RATE_UNKNOWN; + break; + } + + return c; +} + +static void as102_fe_copy_tune_parameters(struct as10x_tune_args *tune_args, + struct dtv_frontend_properties *params) +{ + + /* set frequency */ + tune_args->freq = params->frequency / 1000; + + /* fix interleaving_mode */ + tune_args->interleaving_mode = INTLV_NATIVE; + + switch (params->bandwidth_hz) { + case 8000000: + tune_args->bandwidth = BW_8_MHZ; + break; + case 7000000: + tune_args->bandwidth = BW_7_MHZ; + break; + case 6000000: + tune_args->bandwidth = BW_6_MHZ; + break; + default: + tune_args->bandwidth = BW_8_MHZ; + } + + switch (params->guard_interval) { + case GUARD_INTERVAL_1_32: + tune_args->guard_interval = GUARD_INT_1_32; + break; + case GUARD_INTERVAL_1_16: + tune_args->guard_interval = GUARD_INT_1_16; + break; + case GUARD_INTERVAL_1_8: + tune_args->guard_interval = GUARD_INT_1_8; + break; + case GUARD_INTERVAL_1_4: + tune_args->guard_interval = GUARD_INT_1_4; + break; + case GUARD_INTERVAL_AUTO: + default: + tune_args->guard_interval = GUARD_UNKNOWN; + break; + } + + switch (params->modulation) { + case QPSK: + tune_args->modulation = CONST_QPSK; + break; + case QAM_16: + tune_args->modulation = CONST_QAM16; + break; + case QAM_64: + tune_args->modulation = CONST_QAM64; + break; + default: + tune_args->modulation = CONST_UNKNOWN; + break; + } + + switch (params->transmission_mode) { + case TRANSMISSION_MODE_2K: + tune_args->transmission_mode = TRANS_MODE_2K; + break; + case TRANSMISSION_MODE_8K: + tune_args->transmission_mode = TRANS_MODE_8K; + break; + default: + tune_args->transmission_mode = TRANS_MODE_UNKNOWN; + } + + switch (params->hierarchy) { + case HIERARCHY_NONE: + tune_args->hierarchy = HIER_NONE; + break; + case HIERARCHY_1: + tune_args->hierarchy = HIER_ALPHA_1; + break; + case HIERARCHY_2: + tune_args->hierarchy = HIER_ALPHA_2; + break; + case HIERARCHY_4: + tune_args->hierarchy = HIER_ALPHA_4; + break; + case HIERARCHY_AUTO: + tune_args->hierarchy = HIER_UNKNOWN; + break; + } + + dprintk(debug, "tuner parameters: freq: %d bw: 0x%02x gi: 0x%02x\n", + params->frequency, + tune_args->bandwidth, + tune_args->guard_interval); + + /* + * Detect a hierarchy selection + * if HP/LP are both set to FEC_NONE, HP will be selected. + */ + if ((tune_args->hierarchy != HIER_NONE) && + ((params->code_rate_LP == FEC_NONE) || + (params->code_rate_HP == FEC_NONE))) { + + if (params->code_rate_LP == FEC_NONE) { + tune_args->hier_select = HIER_HIGH_PRIORITY; + tune_args->code_rate = + as102_fe_get_code_rate(params->code_rate_HP); + } + + if (params->code_rate_HP == FEC_NONE) { + tune_args->hier_select = HIER_LOW_PRIORITY; + tune_args->code_rate = + as102_fe_get_code_rate(params->code_rate_LP); + } + + dprintk(debug, "\thierarchy: 0x%02x " + "selected: %s code_rate_%s: 0x%02x\n", + tune_args->hierarchy, + tune_args->hier_select == HIER_HIGH_PRIORITY ? + "HP" : "LP", + tune_args->hier_select == HIER_HIGH_PRIORITY ? + "HP" : "LP", + tune_args->code_rate); + } else { + tune_args->code_rate = + as102_fe_get_code_rate(params->code_rate_HP); + } +} diff --git a/drivers/staging/media/as102/as102_fw.c b/drivers/staging/media/as102/as102_fw.c new file mode 100644 index 00000000..1075fb1d --- /dev/null +++ b/drivers/staging/media/as102/as102_fw.c @@ -0,0 +1,241 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * Copyright (C) 2010 Devin Heitmueller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include +#include + +#include "as102_drv.h" +#include "as102_fw.h" + +char as102_st_fw1[] = "as102_data1_st.hex"; +char as102_st_fw2[] = "as102_data2_st.hex"; +char as102_dt_fw1[] = "as102_data1_dt.hex"; +char as102_dt_fw2[] = "as102_data2_dt.hex"; + +static unsigned char atohx(unsigned char *dst, char *src) +{ + unsigned char value = 0; + + char msb = tolower(*src) - '0'; + char lsb = tolower(*(src + 1)) - '0'; + + if (msb > 9) + msb -= 7; + if (lsb > 9) + lsb -= 7; + + *dst = value = ((msb & 0xF) << 4) | (lsb & 0xF); + return value; +} + +/* + * Parse INTEL HEX firmware file to extract address and data. + */ +static int parse_hex_line(unsigned char *fw_data, unsigned char *addr, + unsigned char *data, int *dataLength, + unsigned char *addr_has_changed) { + + int count = 0; + unsigned char *src, dst; + + if (*fw_data++ != ':') { + pr_err("invalid firmware file\n"); + return -EFAULT; + } + + /* locate end of line */ + for (src = fw_data; *src != '\n'; src += 2) { + atohx(&dst, src); + /* parse line to split addr / data */ + switch (count) { + case 0: + *dataLength = dst; + break; + case 1: + addr[2] = dst; + break; + case 2: + addr[3] = dst; + break; + case 3: + /* check if data is an address */ + if (dst == 0x04) + *addr_has_changed = 1; + else + *addr_has_changed = 0; + break; + case 4: + case 5: + if (*addr_has_changed) + addr[(count - 4)] = dst; + else + data[(count - 4)] = dst; + break; + default: + data[(count - 4)] = dst; + break; + } + count++; + } + + /* return read value + ':' + '\n' */ + return (count * 2) + 2; +} + +static int as102_firmware_upload(struct as10x_bus_adapter_t *bus_adap, + unsigned char *cmd, + const struct firmware *firmware) { + + struct as10x_fw_pkt_t fw_pkt; + int total_read_bytes = 0, errno = 0; + unsigned char addr_has_changed = 0; + + ENTER(); + + for (total_read_bytes = 0; total_read_bytes < firmware->size; ) { + int read_bytes = 0, data_len = 0; + + /* parse intel hex line */ + read_bytes = parse_hex_line( + (u8 *) (firmware->data + total_read_bytes), + fw_pkt.raw.address, + fw_pkt.raw.data, + &data_len, + &addr_has_changed); + + if (read_bytes <= 0) + goto error; + + /* detect the end of file */ + total_read_bytes += read_bytes; + if (total_read_bytes == firmware->size) { + fw_pkt.u.request[0] = 0x00; + fw_pkt.u.request[1] = 0x03; + + /* send EOF command */ + errno = bus_adap->ops->upload_fw_pkt(bus_adap, + (uint8_t *) + &fw_pkt, 2, 0); + if (errno < 0) + goto error; + } else { + if (!addr_has_changed) { + /* prepare command to send */ + fw_pkt.u.request[0] = 0x00; + fw_pkt.u.request[1] = 0x01; + + data_len += sizeof(fw_pkt.u.request); + data_len += sizeof(fw_pkt.raw.address); + + /* send cmd to device */ + errno = bus_adap->ops->upload_fw_pkt(bus_adap, + (uint8_t *) + &fw_pkt, + data_len, + 0); + if (errno < 0) + goto error; + } + } + } +error: + LEAVE(); + return (errno == 0) ? total_read_bytes : errno; +} + +int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap) +{ + int errno = -EFAULT; + const struct firmware *firmware = NULL; + unsigned char *cmd_buf = NULL; + char *fw1, *fw2; + struct usb_device *dev = bus_adap->usb_dev; + + ENTER(); + + /* select fw file to upload */ + if (dual_tuner) { + fw1 = as102_dt_fw1; + fw2 = as102_dt_fw2; + } else { + fw1 = as102_st_fw1; + fw2 = as102_st_fw2; + } + + /* allocate buffer to store firmware upload command and data */ + cmd_buf = kzalloc(MAX_FW_PKT_SIZE, GFP_KERNEL); + if (cmd_buf == NULL) { + errno = -ENOMEM; + goto error; + } + + /* request kernel to locate firmware file: part1 */ + errno = request_firmware(&firmware, fw1, &dev->dev); + if (errno < 0) { + pr_err("%s: unable to locate firmware file: %s\n", + DRIVER_NAME, fw1); + goto error; + } + + /* initiate firmware upload */ + errno = as102_firmware_upload(bus_adap, cmd_buf, firmware); + if (errno < 0) { + pr_err("%s: error during firmware upload part1\n", + DRIVER_NAME); + goto error; + } + + pr_info("%s: firmware: %s loaded with success\n", + DRIVER_NAME, fw1); + release_firmware(firmware); + + /* wait for boot to complete */ + mdelay(100); + + /* request kernel to locate firmware file: part2 */ + errno = request_firmware(&firmware, fw2, &dev->dev); + if (errno < 0) { + pr_err("%s: unable to locate firmware file: %s\n", + DRIVER_NAME, fw2); + goto error; + } + + /* initiate firmware upload */ + errno = as102_firmware_upload(bus_adap, cmd_buf, firmware); + if (errno < 0) { + pr_err("%s: error during firmware upload part2\n", + DRIVER_NAME); + goto error; + } + + pr_info("%s: firmware: %s loaded with success\n", + DRIVER_NAME, fw2); +error: + /* free data buffer */ + kfree(cmd_buf); + /* release firmware if needed */ + if (firmware != NULL) + release_firmware(firmware); + + LEAVE(); + return errno; +} diff --git a/drivers/staging/media/as102/as102_fw.h b/drivers/staging/media/as102/as102_fw.h new file mode 100644 index 00000000..4bfc6849 --- /dev/null +++ b/drivers/staging/media/as102/as102_fw.h @@ -0,0 +1,38 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#define MAX_FW_PKT_SIZE 64 + +extern int dual_tuner; + +struct as10x_raw_fw_pkt { + unsigned char address[4]; + unsigned char data[MAX_FW_PKT_SIZE - 6]; +} __packed; + +struct as10x_fw_pkt_t { + union { + unsigned char request[2]; + unsigned char length[2]; + } __packed u; + struct as10x_raw_fw_pkt raw; +} __packed; + +#ifdef __KERNEL__ +int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap); +#endif diff --git a/drivers/staging/media/as102/as102_usb_drv.c b/drivers/staging/media/as102/as102_usb_drv.c new file mode 100644 index 00000000..0f6bfe7e --- /dev/null +++ b/drivers/staging/media/as102/as102_usb_drv.c @@ -0,0 +1,493 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * Copyright (C) 2010 Devin Heitmueller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include +#include + +#include "as102_drv.h" +#include "as102_usb_drv.h" +#include "as102_fw.h" + +static void as102_usb_disconnect(struct usb_interface *interface); +static int as102_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id); + +static int as102_usb_start_stream(struct as102_dev_t *dev); +static void as102_usb_stop_stream(struct as102_dev_t *dev); + +static int as102_open(struct inode *inode, struct file *file); +static int as102_release(struct inode *inode, struct file *file); + +static struct usb_device_id as102_usb_id_table[] = { + { USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001) }, + { USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID) }, + { USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID) }, + { USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID) }, + { USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID) }, + { } /* Terminating entry */ +}; + +/* Note that this table must always have the same number of entries as the + as102_usb_id_table struct */ +static const char * const as102_device_names[] = { + AS102_REFERENCE_DESIGN, + AS102_PCTV_74E, + AS102_ELGATO_EYETV_DTT_NAME, + AS102_NBOX_DVBT_DONGLE_NAME, + AS102_SKY_IT_DIGITAL_KEY_NAME, + NULL /* Terminating entry */ +}; + +/* eLNA configuration: devices built on the reference design work best + with 0xA0, while custom designs seem to require 0xC0 */ +static uint8_t const as102_elna_cfg[] = { + 0xA0, + 0xC0, + 0xC0, + 0xA0, + 0xA0, + 0x00 /* Terminating entry */ +}; + +struct usb_driver as102_usb_driver = { + .name = DRIVER_FULL_NAME, + .probe = as102_usb_probe, + .disconnect = as102_usb_disconnect, + .id_table = as102_usb_id_table +}; + +static const struct file_operations as102_dev_fops = { + .owner = THIS_MODULE, + .open = as102_open, + .release = as102_release, +}; + +static struct usb_class_driver as102_usb_class_driver = { + .name = "aton2-%d", + .fops = &as102_dev_fops, + .minor_base = AS102_DEVICE_MAJOR, +}; + +static int as102_usb_xfer_cmd(struct as10x_bus_adapter_t *bus_adap, + unsigned char *send_buf, int send_buf_len, + unsigned char *recv_buf, int recv_buf_len) +{ + int ret = 0; + ENTER(); + + if (send_buf != NULL) { + ret = usb_control_msg(bus_adap->usb_dev, + usb_sndctrlpipe(bus_adap->usb_dev, 0), + AS102_USB_DEVICE_TX_CTRL_CMD, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + bus_adap->cmd_xid, /* value */ + 0, /* index */ + send_buf, send_buf_len, + USB_CTRL_SET_TIMEOUT /* 200 */); + if (ret < 0) { + dprintk(debug, "usb_control_msg(send) failed, err %i\n", + ret); + return ret; + } + + if (ret != send_buf_len) { + dprintk(debug, "only wrote %d of %d bytes\n", + ret, send_buf_len); + return -1; + } + } + + if (recv_buf != NULL) { +#ifdef TRACE + dprintk(debug, "want to read: %d bytes\n", recv_buf_len); +#endif + ret = usb_control_msg(bus_adap->usb_dev, + usb_rcvctrlpipe(bus_adap->usb_dev, 0), + AS102_USB_DEVICE_RX_CTRL_CMD, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + bus_adap->cmd_xid, /* value */ + 0, /* index */ + recv_buf, recv_buf_len, + USB_CTRL_GET_TIMEOUT /* 200 */); + if (ret < 0) { + dprintk(debug, "usb_control_msg(recv) failed, err %i\n", + ret); + return ret; + } +#ifdef TRACE + dprintk(debug, "read %d bytes\n", recv_buf_len); +#endif + } + + LEAVE(); + return ret; +} + +static int as102_send_ep1(struct as10x_bus_adapter_t *bus_adap, + unsigned char *send_buf, + int send_buf_len, + int swap32) +{ + int ret = 0, actual_len; + + ret = usb_bulk_msg(bus_adap->usb_dev, + usb_sndbulkpipe(bus_adap->usb_dev, 1), + send_buf, send_buf_len, &actual_len, 200); + if (ret) { + dprintk(debug, "usb_bulk_msg(send) failed, err %i\n", ret); + return ret; + } + + if (actual_len != send_buf_len) { + dprintk(debug, "only wrote %d of %d bytes\n", + actual_len, send_buf_len); + return -1; + } + return ret ? ret : actual_len; +} + +static int as102_read_ep2(struct as10x_bus_adapter_t *bus_adap, + unsigned char *recv_buf, int recv_buf_len) +{ + int ret = 0, actual_len; + + if (recv_buf == NULL) + return -EINVAL; + + ret = usb_bulk_msg(bus_adap->usb_dev, + usb_rcvbulkpipe(bus_adap->usb_dev, 2), + recv_buf, recv_buf_len, &actual_len, 200); + if (ret) { + dprintk(debug, "usb_bulk_msg(recv) failed, err %i\n", ret); + return ret; + } + + if (actual_len != recv_buf_len) { + dprintk(debug, "only read %d of %d bytes\n", + actual_len, recv_buf_len); + return -1; + } + return ret ? ret : actual_len; +} + +struct as102_priv_ops_t as102_priv_ops = { + .upload_fw_pkt = as102_send_ep1, + .xfer_cmd = as102_usb_xfer_cmd, + .as102_read_ep2 = as102_read_ep2, + .start_stream = as102_usb_start_stream, + .stop_stream = as102_usb_stop_stream, +}; + +static int as102_submit_urb_stream(struct as102_dev_t *dev, struct urb *urb) +{ + int err; + + usb_fill_bulk_urb(urb, + dev->bus_adap.usb_dev, + usb_rcvbulkpipe(dev->bus_adap.usb_dev, 0x2), + urb->transfer_buffer, + AS102_USB_BUF_SIZE, + as102_urb_stream_irq, + dev); + + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err) + dprintk(debug, "%s: usb_submit_urb failed\n", __func__); + + return err; +} + +void as102_urb_stream_irq(struct urb *urb) +{ + struct as102_dev_t *as102_dev = urb->context; + + if (urb->actual_length > 0) { + dvb_dmx_swfilter(&as102_dev->dvb_dmx, + urb->transfer_buffer, + urb->actual_length); + } else { + if (urb->actual_length == 0) + memset(urb->transfer_buffer, 0, AS102_USB_BUF_SIZE); + } + + /* is not stopped, re-submit urb */ + if (as102_dev->streaming) + as102_submit_urb_stream(as102_dev, urb); +} + +static void as102_free_usb_stream_buffer(struct as102_dev_t *dev) +{ + int i; + + ENTER(); + + for (i = 0; i < MAX_STREAM_URB; i++) + usb_free_urb(dev->stream_urb[i]); + + usb_free_coherent(dev->bus_adap.usb_dev, + MAX_STREAM_URB * AS102_USB_BUF_SIZE, + dev->stream, + dev->dma_addr); + LEAVE(); +} + +static int as102_alloc_usb_stream_buffer(struct as102_dev_t *dev) +{ + int i, ret = 0; + + ENTER(); + + dev->stream = usb_alloc_coherent(dev->bus_adap.usb_dev, + MAX_STREAM_URB * AS102_USB_BUF_SIZE, + GFP_KERNEL, + &dev->dma_addr); + if (!dev->stream) { + dprintk(debug, "%s: usb_buffer_alloc failed\n", __func__); + return -ENOMEM; + } + + memset(dev->stream, 0, MAX_STREAM_URB * AS102_USB_BUF_SIZE); + + /* init urb buffers */ + for (i = 0; i < MAX_STREAM_URB; i++) { + struct urb *urb; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (urb == NULL) { + dprintk(debug, "%s: usb_alloc_urb failed\n", __func__); + as102_free_usb_stream_buffer(dev); + return -ENOMEM; + } + + urb->transfer_buffer = dev->stream + (i * AS102_USB_BUF_SIZE); + urb->transfer_dma = dev->dma_addr + (i * AS102_USB_BUF_SIZE); + urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer_length = AS102_USB_BUF_SIZE; + + dev->stream_urb[i] = urb; + } + LEAVE(); + return ret; +} + +static void as102_usb_stop_stream(struct as102_dev_t *dev) +{ + int i; + + for (i = 0; i < MAX_STREAM_URB; i++) + usb_kill_urb(dev->stream_urb[i]); +} + +static int as102_usb_start_stream(struct as102_dev_t *dev) +{ + int i, ret = 0; + + for (i = 0; i < MAX_STREAM_URB; i++) { + ret = as102_submit_urb_stream(dev, dev->stream_urb[i]); + if (ret) { + as102_usb_stop_stream(dev); + return ret; + } + } + + return 0; +} + +static void as102_usb_release(struct kref *kref) +{ + struct as102_dev_t *as102_dev; + + ENTER(); + + as102_dev = container_of(kref, struct as102_dev_t, kref); + if (as102_dev != NULL) { + usb_put_dev(as102_dev->bus_adap.usb_dev); + kfree(as102_dev); + } + + LEAVE(); +} + +static void as102_usb_disconnect(struct usb_interface *intf) +{ + struct as102_dev_t *as102_dev; + + ENTER(); + + /* extract as102_dev_t from usb_device private data */ + as102_dev = usb_get_intfdata(intf); + + /* unregister dvb layer */ + as102_dvb_unregister(as102_dev); + + /* free usb buffers */ + as102_free_usb_stream_buffer(as102_dev); + + usb_set_intfdata(intf, NULL); + + /* usb unregister device */ + usb_deregister_dev(intf, &as102_usb_class_driver); + + /* decrement usage counter */ + kref_put(&as102_dev->kref, as102_usb_release); + + pr_info("%s: device has been disconnected\n", DRIVER_NAME); + + LEAVE(); +} + +static int as102_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret; + struct as102_dev_t *as102_dev; + int i; + + ENTER(); + + /* This should never actually happen */ + if ((sizeof(as102_usb_id_table) / sizeof(struct usb_device_id)) != + (sizeof(as102_device_names) / sizeof(const char *))) { + pr_err("Device names table invalid size"); + return -EINVAL; + } + + as102_dev = kzalloc(sizeof(struct as102_dev_t), GFP_KERNEL); + if (as102_dev == NULL) { + err("%s: kzalloc failed", __func__); + return -ENOMEM; + } + + /* Assign the user-friendly device name */ + for (i = 0; i < (sizeof(as102_usb_id_table) / + sizeof(struct usb_device_id)); i++) { + if (id == &as102_usb_id_table[i]) { + as102_dev->name = as102_device_names[i]; + as102_dev->elna_cfg = as102_elna_cfg[i]; + } + } + + if (as102_dev->name == NULL) + as102_dev->name = "Unknown AS102 device"; + + /* set private callback functions */ + as102_dev->bus_adap.ops = &as102_priv_ops; + + /* init cmd token for usb bus */ + as102_dev->bus_adap.cmd = &as102_dev->bus_adap.token.usb.c; + as102_dev->bus_adap.rsp = &as102_dev->bus_adap.token.usb.r; + + /* init kernel device reference */ + kref_init(&as102_dev->kref); + + /* store as102 device to usb_device private data */ + usb_set_intfdata(intf, (void *) as102_dev); + + /* store in as102 device the usb_device pointer */ + as102_dev->bus_adap.usb_dev = usb_get_dev(interface_to_usbdev(intf)); + + /* we can register the device now, as it is ready */ + ret = usb_register_dev(intf, &as102_usb_class_driver); + if (ret < 0) { + /* something prevented us from registering this driver */ + err("%s: usb_register_dev() failed (errno = %d)", + __func__, ret); + goto failed; + } + + pr_info("%s: device has been detected\n", DRIVER_NAME); + + /* request buffer allocation for streaming */ + ret = as102_alloc_usb_stream_buffer(as102_dev); + if (ret != 0) + goto failed; + + /* register dvb layer */ + ret = as102_dvb_register(as102_dev); + + LEAVE(); + return ret; + +failed: + usb_set_intfdata(intf, NULL); + kfree(as102_dev); + return ret; +} + +static int as102_open(struct inode *inode, struct file *file) +{ + int ret = 0, minor = 0; + struct usb_interface *intf = NULL; + struct as102_dev_t *dev = NULL; + + ENTER(); + + /* read minor from inode */ + minor = iminor(inode); + + /* fetch device from usb interface */ + intf = usb_find_interface(&as102_usb_driver, minor); + if (intf == NULL) { + pr_err("%s: can't find device for minor %d\n", + __func__, minor); + ret = -ENODEV; + goto exit; + } + + /* get our device */ + dev = usb_get_intfdata(intf); + if (dev == NULL) { + ret = -EFAULT; + goto exit; + } + + /* save our device object in the file's private structure */ + file->private_data = dev; + + /* increment our usage count for the device */ + kref_get(&dev->kref); + +exit: + LEAVE(); + return ret; +} + +static int as102_release(struct inode *inode, struct file *file) +{ + int ret = 0; + struct as102_dev_t *dev = NULL; + + ENTER(); + + dev = file->private_data; + if (dev != NULL) { + /* decrement the count on our device */ + kref_put(&dev->kref, as102_usb_release); + } + + LEAVE(); + return ret; +} + +MODULE_DEVICE_TABLE(usb, as102_usb_id_table); diff --git a/drivers/staging/media/as102/as102_usb_drv.h b/drivers/staging/media/as102/as102_usb_drv.h new file mode 100644 index 00000000..fc2884ab --- /dev/null +++ b/drivers/staging/media/as102/as102_usb_drv.h @@ -0,0 +1,63 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * Copyright (C) 2010 Devin Heitmueller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include + +#ifndef _AS102_USB_DRV_H_ +#define _AS102_USB_DRV_H_ + +#define AS102_USB_DEVICE_TX_CTRL_CMD 0xF1 +#define AS102_USB_DEVICE_RX_CTRL_CMD 0xF2 + +/* define these values to match the supported devices */ + +/* Abilis system: "TITAN" */ +#define AS102_REFERENCE_DESIGN "Abilis Systems DVB-Titan" +#define AS102_USB_DEVICE_VENDOR_ID 0x1BA6 +#define AS102_USB_DEVICE_PID_0001 0x0001 + +/* PCTV Systems: PCTV picoStick (74e) */ +#define AS102_PCTV_74E "PCTV Systems picoStick (74e)" +#define PCTV_74E_USB_VID 0x2013 +#define PCTV_74E_USB_PID 0x0246 + +/* Elgato: EyeTV DTT Deluxe */ +#define AS102_ELGATO_EYETV_DTT_NAME "Elgato EyeTV DTT Deluxe" +#define ELGATO_EYETV_DTT_USB_VID 0x0fd9 +#define ELGATO_EYETV_DTT_USB_PID 0x002c + +/* nBox: nBox DVB-T Dongle */ +#define AS102_NBOX_DVBT_DONGLE_NAME "nBox DVB-T Dongle" +#define NBOX_DVBT_DONGLE_USB_VID 0x0b89 +#define NBOX_DVBT_DONGLE_USB_PID 0x0007 + +/* Sky Italia: Digital Key (green led) */ +#define AS102_SKY_IT_DIGITAL_KEY_NAME "Sky IT Digital Key (green led)" +#define SKY_IT_DIGITAL_KEY_USB_VID 0x2137 +#define SKY_IT_DIGITAL_KEY_USB_PID 0x0001 + +void as102_urb_stream_irq(struct urb *urb); + +struct as10x_usb_token_cmd_t { + /* token cmd */ + struct as10x_cmd_t c; + /* token response */ + struct as10x_cmd_t r; +}; +#endif diff --git a/drivers/staging/media/as102/as10x_cmd.c b/drivers/staging/media/as102/as10x_cmd.c new file mode 100644 index 00000000..262bb94a --- /dev/null +++ b/drivers/staging/media/as102/as10x_cmd.c @@ -0,0 +1,453 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * Copyright (C) 2010 Devin Heitmueller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include "as102_drv.h" +#include "as10x_types.h" +#include "as10x_cmd.h" + +/** + * as10x_cmd_turn_on - send turn on command to AS10x + * @adap: pointer to AS10x bus adapter + * + * Return 0 when no error, < 0 in case of error. + */ +int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.turn_on.req)); + + /* fill command */ + pcmd->body.turn_on.req.proc_id = cpu_to_le16(CONTROL_PROC_TURNON); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.turn_on.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.turn_on.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNON_RSP); + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_turn_off - send turn off command to AS10x + * @adap: pointer to AS10x bus adapter + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.turn_off.req)); + + /* fill command */ + pcmd->body.turn_off.req.proc_id = cpu_to_le16(CONTROL_PROC_TURNOFF); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd( + adap, (uint8_t *) pcmd, + sizeof(pcmd->body.turn_off.req) + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.turn_off.rsp) + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNOFF_RSP); + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_set_tune - send set tune command to AS10x + * @adap: pointer to AS10x bus adapter + * @ptune: tune parameters + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap, + struct as10x_tune_args *ptune) +{ + int error; + struct as10x_cmd_t *preq, *prsp; + + ENTER(); + + preq = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(preq, (++adap->cmd_xid), + sizeof(preq->body.set_tune.req)); + + /* fill command */ + preq->body.set_tune.req.proc_id = cpu_to_le16(CONTROL_PROC_SETTUNE); + preq->body.set_tune.req.args.freq = cpu_to_le32(ptune->freq); + preq->body.set_tune.req.args.bandwidth = ptune->bandwidth; + preq->body.set_tune.req.args.hier_select = ptune->hier_select; + preq->body.set_tune.req.args.modulation = ptune->modulation; + preq->body.set_tune.req.args.hierarchy = ptune->hierarchy; + preq->body.set_tune.req.args.interleaving_mode = + ptune->interleaving_mode; + preq->body.set_tune.req.args.code_rate = ptune->code_rate; + preq->body.set_tune.req.args.guard_interval = ptune->guard_interval; + preq->body.set_tune.req.args.transmission_mode = + ptune->transmission_mode; + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) preq, + sizeof(preq->body.set_tune.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.set_tune.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_SETTUNE_RSP); + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_get_tune_status - send get tune status command to AS10x + * @adap: pointer to AS10x bus adapter + * @pstatus: pointer to updated status structure of the current tune + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap, + struct as10x_tune_status *pstatus) +{ + int error; + struct as10x_cmd_t *preq, *prsp; + + ENTER(); + + preq = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(preq, (++adap->cmd_xid), + sizeof(preq->body.get_tune_status.req)); + + /* fill command */ + preq->body.get_tune_status.req.proc_id = + cpu_to_le16(CONTROL_PROC_GETTUNESTAT); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd( + adap, + (uint8_t *) preq, + sizeof(preq->body.get_tune_status.req) + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.get_tune_status.rsp) + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_GETTUNESTAT_RSP); + if (error < 0) + goto out; + + /* Response OK -> get response data */ + pstatus->tune_state = prsp->body.get_tune_status.rsp.sts.tune_state; + pstatus->signal_strength = + le16_to_cpu(prsp->body.get_tune_status.rsp.sts.signal_strength); + pstatus->PER = le16_to_cpu(prsp->body.get_tune_status.rsp.sts.PER); + pstatus->BER = le16_to_cpu(prsp->body.get_tune_status.rsp.sts.BER); + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_get_tps - send get TPS command to AS10x + * @adap: pointer to AS10x handle + * @ptps: pointer to TPS parameters structure + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap, struct as10x_tps *ptps) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.get_tps.req)); + + /* fill command */ + pcmd->body.get_tune_status.req.proc_id = + cpu_to_le16(CONTROL_PROC_GETTPS); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.get_tps.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.get_tps.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_GETTPS_RSP); + if (error < 0) + goto out; + + /* Response OK -> get response data */ + ptps->modulation = prsp->body.get_tps.rsp.tps.modulation; + ptps->hierarchy = prsp->body.get_tps.rsp.tps.hierarchy; + ptps->interleaving_mode = prsp->body.get_tps.rsp.tps.interleaving_mode; + ptps->code_rate_HP = prsp->body.get_tps.rsp.tps.code_rate_HP; + ptps->code_rate_LP = prsp->body.get_tps.rsp.tps.code_rate_LP; + ptps->guard_interval = prsp->body.get_tps.rsp.tps.guard_interval; + ptps->transmission_mode = prsp->body.get_tps.rsp.tps.transmission_mode; + ptps->DVBH_mask_HP = prsp->body.get_tps.rsp.tps.DVBH_mask_HP; + ptps->DVBH_mask_LP = prsp->body.get_tps.rsp.tps.DVBH_mask_LP; + ptps->cell_ID = le16_to_cpu(prsp->body.get_tps.rsp.tps.cell_ID); + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_get_demod_stats - send get demod stats command to AS10x + * @adap: pointer to AS10x bus adapter + * @pdemod_stats: pointer to demod stats parameters structure + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t *adap, + struct as10x_demod_stats *pdemod_stats) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.get_demod_stats.req)); + + /* fill command */ + pcmd->body.get_demod_stats.req.proc_id = + cpu_to_le16(CONTROL_PROC_GET_DEMOD_STATS); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.get_demod_stats.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.get_demod_stats.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_GET_DEMOD_STATS_RSP); + if (error < 0) + goto out; + + /* Response OK -> get response data */ + pdemod_stats->frame_count = + le32_to_cpu(prsp->body.get_demod_stats.rsp.stats.frame_count); + pdemod_stats->bad_frame_count = + le32_to_cpu(prsp->body.get_demod_stats.rsp.stats.bad_frame_count); + pdemod_stats->bytes_fixed_by_rs = + le32_to_cpu(prsp->body.get_demod_stats.rsp.stats.bytes_fixed_by_rs); + pdemod_stats->mer = + le16_to_cpu(prsp->body.get_demod_stats.rsp.stats.mer); + pdemod_stats->has_started = + prsp->body.get_demod_stats.rsp.stats.has_started; + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_get_impulse_resp - send get impulse response command to AS10x + * @adap: pointer to AS10x bus adapter + * @is_ready: pointer to value indicating when impulse + * response data is ready + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap, + uint8_t *is_ready) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.get_impulse_rsp.req)); + + /* fill command */ + pcmd->body.get_impulse_rsp.req.proc_id = + cpu_to_le16(CONTROL_PROC_GET_IMPULSE_RESP); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.get_impulse_rsp.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.get_impulse_rsp.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_GET_IMPULSE_RESP_RSP); + if (error < 0) + goto out; + + /* Response OK -> get response data */ + *is_ready = prsp->body.get_impulse_rsp.rsp.is_ready; + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_build - build AS10x command header + * @pcmd: pointer to AS10x command buffer + * @xid: sequence id of the command + * @cmd_len: length of the command + */ +void as10x_cmd_build(struct as10x_cmd_t *pcmd, + uint16_t xid, uint16_t cmd_len) +{ + pcmd->header.req_id = cpu_to_le16(xid); + pcmd->header.prog = cpu_to_le16(SERVICE_PROG_ID); + pcmd->header.version = cpu_to_le16(SERVICE_PROG_VERSION); + pcmd->header.data_len = cpu_to_le16(cmd_len); +} + +/** + * as10x_rsp_parse - Parse command response + * @prsp: pointer to AS10x command buffer + * @proc_id: id of the command + * + * Return 0 on success or negative value in case of error. + */ +int as10x_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id) +{ + int error; + + /* extract command error code */ + error = prsp->body.common.rsp.error; + + if ((error == 0) && + (le16_to_cpu(prsp->body.common.rsp.proc_id) == proc_id)) { + return 0; + } + + return AS10X_CMD_ERROR; +} diff --git a/drivers/staging/media/as102/as10x_cmd.h b/drivers/staging/media/as102/as10x_cmd.h new file mode 100644 index 00000000..e21ec6c7 --- /dev/null +++ b/drivers/staging/media/as102/as10x_cmd.h @@ -0,0 +1,529 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef _AS10X_CMD_H_ +#define _AS10X_CMD_H_ + +#ifdef __KERNEL__ +#include +#endif + +#include "as10x_types.h" + +/*********************************/ +/* MACRO DEFINITIONS */ +/*********************************/ +#define AS10X_CMD_ERROR -1 + +#define SERVICE_PROG_ID 0x0002 +#define SERVICE_PROG_VERSION 0x0001 + +#define HIER_NONE 0x00 +#define HIER_LOW_PRIORITY 0x01 + +#define HEADER_SIZE (sizeof(struct as10x_cmd_header_t)) + +/* context request types */ +#define GET_CONTEXT_DATA 1 +#define SET_CONTEXT_DATA 2 + +/* ODSP suspend modes */ +#define CFG_MODE_ODSP_RESUME 0 +#define CFG_MODE_ODSP_SUSPEND 1 + +/* Dump memory size */ +#define DUMP_BLOCK_SIZE_MAX 0x20 + +/*********************************/ +/* TYPE DEFINITION */ +/*********************************/ +enum control_proc { + CONTROL_PROC_TURNON = 0x0001, + CONTROL_PROC_TURNON_RSP = 0x0100, + CONTROL_PROC_SET_REGISTER = 0x0002, + CONTROL_PROC_SET_REGISTER_RSP = 0x0200, + CONTROL_PROC_GET_REGISTER = 0x0003, + CONTROL_PROC_GET_REGISTER_RSP = 0x0300, + CONTROL_PROC_SETTUNE = 0x000A, + CONTROL_PROC_SETTUNE_RSP = 0x0A00, + CONTROL_PROC_GETTUNESTAT = 0x000B, + CONTROL_PROC_GETTUNESTAT_RSP = 0x0B00, + CONTROL_PROC_GETTPS = 0x000D, + CONTROL_PROC_GETTPS_RSP = 0x0D00, + CONTROL_PROC_SETFILTER = 0x000E, + CONTROL_PROC_SETFILTER_RSP = 0x0E00, + CONTROL_PROC_REMOVEFILTER = 0x000F, + CONTROL_PROC_REMOVEFILTER_RSP = 0x0F00, + CONTROL_PROC_GET_IMPULSE_RESP = 0x0012, + CONTROL_PROC_GET_IMPULSE_RESP_RSP = 0x1200, + CONTROL_PROC_START_STREAMING = 0x0013, + CONTROL_PROC_START_STREAMING_RSP = 0x1300, + CONTROL_PROC_STOP_STREAMING = 0x0014, + CONTROL_PROC_STOP_STREAMING_RSP = 0x1400, + CONTROL_PROC_GET_DEMOD_STATS = 0x0015, + CONTROL_PROC_GET_DEMOD_STATS_RSP = 0x1500, + CONTROL_PROC_ELNA_CHANGE_MODE = 0x0016, + CONTROL_PROC_ELNA_CHANGE_MODE_RSP = 0x1600, + CONTROL_PROC_ODSP_CHANGE_MODE = 0x0017, + CONTROL_PROC_ODSP_CHANGE_MODE_RSP = 0x1700, + CONTROL_PROC_AGC_CHANGE_MODE = 0x0018, + CONTROL_PROC_AGC_CHANGE_MODE_RSP = 0x1800, + + CONTROL_PROC_CONTEXT = 0x00FC, + CONTROL_PROC_CONTEXT_RSP = 0xFC00, + CONTROL_PROC_DUMP_MEMORY = 0x00FD, + CONTROL_PROC_DUMP_MEMORY_RSP = 0xFD00, + CONTROL_PROC_DUMPLOG_MEMORY = 0x00FE, + CONTROL_PROC_DUMPLOG_MEMORY_RSP = 0xFE00, + CONTROL_PROC_TURNOFF = 0x00FF, + CONTROL_PROC_TURNOFF_RSP = 0xFF00 +}; + +union as10x_turn_on { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_turn_off { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t err; + } __packed rsp; +} __packed; + +union as10x_set_tune { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* tune params */ + struct as10x_tune_args args; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_get_tune_status { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + /* tune status */ + struct as10x_tune_status sts; + } __packed rsp; +} __packed; + +union as10x_get_tps { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + /* tps details */ + struct as10x_tps tps; + } __packed rsp; +} __packed; + +union as10x_common { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_add_pid_filter { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* PID to filter */ + uint16_t pid; + /* stream type (MPE, PSI/SI or PES )*/ + uint8_t stream_type; + /* PID index in filter table */ + uint8_t idx; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + /* Filter id */ + uint8_t filter_id; + } __packed rsp; +} __packed; + +union as10x_del_pid_filter { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* PID to remove */ + uint16_t pid; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* response error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_start_streaming { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_stop_streaming { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_get_demod_stats { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* demod stats */ + struct as10x_demod_stats stats; + } __packed rsp; +} __packed; + +union as10x_get_impulse_resp { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* impulse response ready */ + uint8_t is_ready; + } __packed rsp; +} __packed; + +union as10x_fw_context { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* value to write (for set context)*/ + struct as10x_register_value reg_val; + /* context tag */ + uint16_t tag; + /* context request type */ + uint16_t type; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* value read (for get context) */ + struct as10x_register_value reg_val; + /* context request type */ + uint16_t type; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_set_register { + /* request */ + struct { + /* response identifier */ + uint16_t proc_id; + /* register description */ + struct as10x_register_addr reg_addr; + /* register content */ + struct as10x_register_value reg_val; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +union as10x_get_register { + /* request */ + struct { + /* response identifier */ + uint16_t proc_id; + /* register description */ + struct as10x_register_addr reg_addr; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* register content */ + struct as10x_register_value reg_val; + } __packed rsp; +} __packed; + +union as10x_cfg_change_mode { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* mode */ + uint8_t mode; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + } __packed rsp; +} __packed; + +struct as10x_cmd_header_t { + uint16_t req_id; + uint16_t prog; + uint16_t version; + uint16_t data_len; +} __packed; + +#define DUMP_BLOCK_SIZE 16 + +union as10x_dump_memory { + /* request */ + struct { + /* request identifier */ + uint16_t proc_id; + /* dump memory type request */ + uint8_t dump_req; + /* register description */ + struct as10x_register_addr reg_addr; + /* nb blocks to read */ + uint16_t num_blocks; + } __packed req; + /* response */ + struct { + /* response identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* dump response */ + uint8_t dump_rsp; + /* data */ + union { + uint8_t data8[DUMP_BLOCK_SIZE]; + uint16_t data16[DUMP_BLOCK_SIZE / sizeof(uint16_t)]; + uint32_t data32[DUMP_BLOCK_SIZE / sizeof(uint32_t)]; + } __packed u; + } __packed rsp; +} __packed; + +union as10x_dumplog_memory { + struct { + /* request identifier */ + uint16_t proc_id; + /* dump memory type request */ + uint8_t dump_req; + } __packed req; + struct { + /* request identifier */ + uint16_t proc_id; + /* error */ + uint8_t error; + /* dump response */ + uint8_t dump_rsp; + /* dump data */ + uint8_t data[DUMP_BLOCK_SIZE]; + } __packed rsp; +} __packed; + +union as10x_raw_data { + /* request */ + struct { + uint16_t proc_id; + uint8_t data[64 - sizeof(struct as10x_cmd_header_t) + - 2 /* proc_id */]; + } __packed req; + /* response */ + struct { + uint16_t proc_id; + uint8_t error; + uint8_t data[64 - sizeof(struct as10x_cmd_header_t) + - 2 /* proc_id */ - 1 /* rc */]; + } __packed rsp; +} __packed; + +struct as10x_cmd_t { + struct as10x_cmd_header_t header; + union { + union as10x_turn_on turn_on; + union as10x_turn_off turn_off; + union as10x_set_tune set_tune; + union as10x_get_tune_status get_tune_status; + union as10x_get_tps get_tps; + union as10x_common common; + union as10x_add_pid_filter add_pid_filter; + union as10x_del_pid_filter del_pid_filter; + union as10x_start_streaming start_streaming; + union as10x_stop_streaming stop_streaming; + union as10x_get_demod_stats get_demod_stats; + union as10x_get_impulse_resp get_impulse_rsp; + union as10x_fw_context context; + union as10x_set_register set_register; + union as10x_get_register get_register; + union as10x_cfg_change_mode cfg_change_mode; + union as10x_dump_memory dump_memory; + union as10x_dumplog_memory dumplog_memory; + union as10x_raw_data raw_data; + } __packed body; +} __packed; + +struct as10x_token_cmd_t { + /* token cmd */ + struct as10x_cmd_t c; + /* token response */ + struct as10x_cmd_t r; +} __packed; + + +/**************************/ +/* FUNCTION DECLARATION */ +/**************************/ + +void as10x_cmd_build(struct as10x_cmd_t *pcmd, uint16_t proc_id, + uint16_t cmd_len); +int as10x_rsp_parse(struct as10x_cmd_t *r, uint16_t proc_id); + +/* as10x cmd */ +int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap); +int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap); + +int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap, + struct as10x_tune_args *ptune); + +int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap, + struct as10x_tune_status *pstatus); + +int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap, + struct as10x_tps *ptps); + +int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t *adap, + struct as10x_demod_stats *pdemod_stats); + +int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap, + uint8_t *is_ready); + +/* as10x cmd stream */ +int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap, + struct as10x_ts_filter *filter); +int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap, + uint16_t pid_value); + +int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap); +int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap); + +/* as10x cmd cfg */ +int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap, + uint16_t tag, + uint32_t value); +int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap, + uint16_t tag, + uint32_t *pvalue); + +int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode); +int as10x_context_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id); +#endif diff --git a/drivers/staging/media/as102/as10x_cmd_cfg.c b/drivers/staging/media/as102/as10x_cmd_cfg.c new file mode 100644 index 00000000..d2a4bce8 --- /dev/null +++ b/drivers/staging/media/as102/as10x_cmd_cfg.c @@ -0,0 +1,215 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include "as102_drv.h" +#include "as10x_types.h" +#include "as10x_cmd.h" + +/***************************/ +/* FUNCTION DEFINITION */ +/***************************/ + +/** + * as10x_cmd_get_context - Send get context command to AS10x + * @adap: pointer to AS10x bus adapter + * @tag: context tag + * @pvalue: pointer where to store context value read + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap, uint16_t tag, + uint32_t *pvalue) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.context.req)); + + /* fill command */ + pcmd->body.context.req.proc_id = cpu_to_le16(CONTROL_PROC_CONTEXT); + pcmd->body.context.req.tag = cpu_to_le16(tag); + pcmd->body.context.req.type = cpu_to_le16(GET_CONTEXT_DATA); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.context.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.context.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response: context command do not follow the common response */ + /* structure -> specific handling response parse required */ + error = as10x_context_rsp_parse(prsp, CONTROL_PROC_CONTEXT_RSP); + + if (error == 0) { + /* Response OK -> get response data */ + *pvalue = le32_to_cpu(prsp->body.context.rsp.reg_val.u.value32); + /* value returned is always a 32-bit value */ + } + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_set_context - send set context command to AS10x + * @adap: pointer to AS10x bus adapter + * @tag: context tag + * @value: value to set in context + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap, uint16_t tag, + uint32_t value) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.context.req)); + + /* fill command */ + pcmd->body.context.req.proc_id = cpu_to_le16(CONTROL_PROC_CONTEXT); + /* pcmd->body.context.req.reg_val.mode initialization is not required */ + pcmd->body.context.req.reg_val.u.value32 = cpu_to_le32(value); + pcmd->body.context.req.tag = cpu_to_le16(tag); + pcmd->body.context.req.type = cpu_to_le16(SET_CONTEXT_DATA); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, + (uint8_t *) pcmd, + sizeof(pcmd->body.context.req) + + HEADER_SIZE, + (uint8_t *) prsp, + sizeof(prsp->body.context.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response: context command do not follow the common response */ + /* structure -> specific handling response parse required */ + error = as10x_context_rsp_parse(prsp, CONTROL_PROC_CONTEXT_RSP); + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_eLNA_change_mode - send eLNA change mode command to AS10x + * @adap: pointer to AS10x bus adapter + * @mode: mode selected: + * - ON : 0x0 => eLNA always ON + * - OFF : 0x1 => eLNA always OFF + * - AUTO : 0x2 => eLNA follow hysteresis parameters + * to be ON or OFF + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.cfg_change_mode.req)); + + /* fill command */ + pcmd->body.cfg_change_mode.req.proc_id = + cpu_to_le16(CONTROL_PROC_ELNA_CHANGE_MODE); + pcmd->body.cfg_change_mode.req.mode = mode; + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.cfg_change_mode.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.cfg_change_mode.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_ELNA_CHANGE_MODE_RSP); + +out: + LEAVE(); + return error; +} + +/** + * as10x_context_rsp_parse - Parse context command response + * @prsp: pointer to AS10x command response buffer + * @proc_id: id of the command + * + * Since the contex command reponse does not follow the common + * response, a specific parse function is required. + * Return 0 on success or negative value in case of error. + */ +int as10x_context_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id) +{ + int err; + + err = prsp->body.context.rsp.error; + + if ((err == 0) && + (le16_to_cpu(prsp->body.context.rsp.proc_id) == proc_id)) { + return 0; + } + return AS10X_CMD_ERROR; +} diff --git a/drivers/staging/media/as102/as10x_cmd_stream.c b/drivers/staging/media/as102/as10x_cmd_stream.c new file mode 100644 index 00000000..6d000f60 --- /dev/null +++ b/drivers/staging/media/as102/as10x_cmd_stream.c @@ -0,0 +1,223 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include "as102_drv.h" +#include "as10x_cmd.h" + +/** + * as10x_cmd_add_PID_filter - send add filter command to AS10x + * @adap: pointer to AS10x bus adapter + * @filter: TSFilter filter for DVB-T + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap, + struct as10x_ts_filter *filter) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.add_pid_filter.req)); + + /* fill command */ + pcmd->body.add_pid_filter.req.proc_id = + cpu_to_le16(CONTROL_PROC_SETFILTER); + pcmd->body.add_pid_filter.req.pid = cpu_to_le16(filter->pid); + pcmd->body.add_pid_filter.req.stream_type = filter->type; + + if (filter->idx < 16) + pcmd->body.add_pid_filter.req.idx = filter->idx; + else + pcmd->body.add_pid_filter.req.idx = 0xFF; + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.add_pid_filter.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.add_pid_filter.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_SETFILTER_RSP); + + if (error == 0) { + /* Response OK -> get response data */ + filter->idx = prsp->body.add_pid_filter.rsp.filter_id; + } + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_del_PID_filter - Send delete filter command to AS10x + * @adap: pointer to AS10x bus adapte + * @pid_value: PID to delete + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap, + uint16_t pid_value) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.del_pid_filter.req)); + + /* fill command */ + pcmd->body.del_pid_filter.req.proc_id = + cpu_to_le16(CONTROL_PROC_REMOVEFILTER); + pcmd->body.del_pid_filter.req.pid = cpu_to_le16(pid_value); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.del_pid_filter.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.del_pid_filter.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_REMOVEFILTER_RSP); + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_start_streaming - Send start streaming command to AS10x + * @adap: pointer to AS10x bus adapter + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap) +{ + int error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.start_streaming.req)); + + /* fill command */ + pcmd->body.start_streaming.req.proc_id = + cpu_to_le16(CONTROL_PROC_START_STREAMING); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.start_streaming.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.start_streaming.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_START_STREAMING_RSP); + +out: + LEAVE(); + return error; +} + +/** + * as10x_cmd_stop_streaming - Send stop streaming command to AS10x + * @adap: pointer to AS10x bus adapter + * + * Return 0 on success or negative value in case of error. + */ +int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap) +{ + int8_t error; + struct as10x_cmd_t *pcmd, *prsp; + + ENTER(); + + pcmd = adap->cmd; + prsp = adap->rsp; + + /* prepare command */ + as10x_cmd_build(pcmd, (++adap->cmd_xid), + sizeof(pcmd->body.stop_streaming.req)); + + /* fill command */ + pcmd->body.stop_streaming.req.proc_id = + cpu_to_le16(CONTROL_PROC_STOP_STREAMING); + + /* send command */ + if (adap->ops->xfer_cmd) { + error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd, + sizeof(pcmd->body.stop_streaming.req) + + HEADER_SIZE, (uint8_t *) prsp, + sizeof(prsp->body.stop_streaming.rsp) + + HEADER_SIZE); + } else { + error = AS10X_CMD_ERROR; + } + + if (error < 0) + goto out; + + /* parse response */ + error = as10x_rsp_parse(prsp, CONTROL_PROC_STOP_STREAMING_RSP); + +out: + LEAVE(); + return error; +} diff --git a/drivers/staging/media/as102/as10x_handle.h b/drivers/staging/media/as102/as10x_handle.h new file mode 100644 index 00000000..62b9795e --- /dev/null +++ b/drivers/staging/media/as102/as10x_handle.h @@ -0,0 +1,54 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifdef __KERNEL__ +struct as10x_bus_adapter_t; +struct as102_dev_t; + +#include "as10x_cmd.h" + +/* values for "mode" field */ +#define REGMODE8 8 +#define REGMODE16 16 +#define REGMODE32 32 + +struct as102_priv_ops_t { + int (*upload_fw_pkt) (struct as10x_bus_adapter_t *bus_adap, + unsigned char *buf, int buflen, int swap32); + + int (*send_cmd) (struct as10x_bus_adapter_t *bus_adap, + unsigned char *buf, int buflen); + + int (*xfer_cmd) (struct as10x_bus_adapter_t *bus_adap, + unsigned char *send_buf, int send_buf_len, + unsigned char *recv_buf, int recv_buf_len); + + int (*start_stream) (struct as102_dev_t *dev); + void (*stop_stream) (struct as102_dev_t *dev); + + int (*reset_target) (struct as10x_bus_adapter_t *bus_adap); + + int (*read_write)(struct as10x_bus_adapter_t *bus_adap, uint8_t mode, + uint32_t rd_addr, uint16_t rd_len, + uint32_t wr_addr, uint16_t wr_len); + + int (*as102_read_ep2) (struct as10x_bus_adapter_t *bus_adap, + unsigned char *recv_buf, + int recv_buf_len); +}; +#endif diff --git a/drivers/staging/media/as102/as10x_types.h b/drivers/staging/media/as102/as10x_types.h new file mode 100644 index 00000000..af26e057 --- /dev/null +++ b/drivers/staging/media/as102/as10x_types.h @@ -0,0 +1,194 @@ +/* + * Abilis Systems Single DVB-T Receiver + * Copyright (C) 2008 Pierrick Hascoet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef _AS10X_TYPES_H_ +#define _AS10X_TYPES_H_ + +#include "as10x_handle.h" + +/*********************************/ +/* MACRO DEFINITIONS */ +/*********************************/ + +/* bandwidth constant values */ +#define BW_5_MHZ 0x00 +#define BW_6_MHZ 0x01 +#define BW_7_MHZ 0x02 +#define BW_8_MHZ 0x03 + +/* hierarchy priority selection values */ +#define HIER_NO_PRIORITY 0x00 +#define HIER_LOW_PRIORITY 0x01 +#define HIER_HIGH_PRIORITY 0x02 + +/* constellation available values */ +#define CONST_QPSK 0x00 +#define CONST_QAM16 0x01 +#define CONST_QAM64 0x02 +#define CONST_UNKNOWN 0xFF + +/* hierarchy available values */ +#define HIER_NONE 0x00 +#define HIER_ALPHA_1 0x01 +#define HIER_ALPHA_2 0x02 +#define HIER_ALPHA_4 0x03 +#define HIER_UNKNOWN 0xFF + +/* interleaving available values */ +#define INTLV_NATIVE 0x00 +#define INTLV_IN_DEPTH 0x01 +#define INTLV_UNKNOWN 0xFF + +/* code rate available values */ +#define CODE_RATE_1_2 0x00 +#define CODE_RATE_2_3 0x01 +#define CODE_RATE_3_4 0x02 +#define CODE_RATE_5_6 0x03 +#define CODE_RATE_7_8 0x04 +#define CODE_RATE_UNKNOWN 0xFF + +/* guard interval available values */ +#define GUARD_INT_1_32 0x00 +#define GUARD_INT_1_16 0x01 +#define GUARD_INT_1_8 0x02 +#define GUARD_INT_1_4 0x03 +#define GUARD_UNKNOWN 0xFF + +/* transmission mode available values */ +#define TRANS_MODE_2K 0x00 +#define TRANS_MODE_8K 0x01 +#define TRANS_MODE_4K 0x02 +#define TRANS_MODE_UNKNOWN 0xFF + +/* DVBH signalling available values */ +#define TIMESLICING_PRESENT 0x01 +#define MPE_FEC_PRESENT 0x02 + +/* tune state available */ +#define TUNE_STATUS_NOT_TUNED 0x00 +#define TUNE_STATUS_IDLE 0x01 +#define TUNE_STATUS_LOCKING 0x02 +#define TUNE_STATUS_SIGNAL_DVB_OK 0x03 +#define TUNE_STATUS_STREAM_DETECTED 0x04 +#define TUNE_STATUS_STREAM_TUNED 0x05 +#define TUNE_STATUS_ERROR 0xFF + +/* available TS FID filter types */ +#define TS_PID_TYPE_TS 0 +#define TS_PID_TYPE_PSI_SI 1 +#define TS_PID_TYPE_MPE 2 + +/* number of echos available */ +#define MAX_ECHOS 15 + +/* Context types */ +#define CONTEXT_LNA 1010 +#define CONTEXT_ELNA_HYSTERESIS 4003 +#define CONTEXT_ELNA_GAIN 4004 +#define CONTEXT_MER_THRESHOLD 5005 +#define CONTEXT_MER_OFFSET 5006 +#define CONTEXT_IR_STATE 7000 +#define CONTEXT_TSOUT_MSB_FIRST 7004 +#define CONTEXT_TSOUT_FALLING_EDGE 7005 + +/* Configuration modes */ +#define CFG_MODE_ON 0 +#define CFG_MODE_OFF 1 +#define CFG_MODE_AUTO 2 + +struct as10x_tps { + uint8_t modulation; + uint8_t hierarchy; + uint8_t interleaving_mode; + uint8_t code_rate_HP; + uint8_t code_rate_LP; + uint8_t guard_interval; + uint8_t transmission_mode; + uint8_t DVBH_mask_HP; + uint8_t DVBH_mask_LP; + uint16_t cell_ID; +} __packed; + +struct as10x_tune_args { + /* frequency */ + uint32_t freq; + /* bandwidth */ + uint8_t bandwidth; + /* hierarchy selection */ + uint8_t hier_select; + /* constellation */ + uint8_t modulation; + /* hierarchy */ + uint8_t hierarchy; + /* interleaving mode */ + uint8_t interleaving_mode; + /* code rate */ + uint8_t code_rate; + /* guard interval */ + uint8_t guard_interval; + /* transmission mode */ + uint8_t transmission_mode; +} __packed; + +struct as10x_tune_status { + /* tune status */ + uint8_t tune_state; + /* signal strength */ + int16_t signal_strength; + /* packet error rate 10^-4 */ + uint16_t PER; + /* bit error rate 10^-4 */ + uint16_t BER; +} __packed; + +struct as10x_demod_stats { + /* frame counter */ + uint32_t frame_count; + /* Bad frame counter */ + uint32_t bad_frame_count; + /* Number of wrong bytes fixed by Reed-Solomon */ + uint32_t bytes_fixed_by_rs; + /* Averaged MER */ + uint16_t mer; + /* statistics calculation state indicator (started or not) */ + uint8_t has_started; +} __packed; + +struct as10x_ts_filter { + uint16_t pid; /* valid PID value 0x00 : 0x2000 */ + uint8_t type; /* Red TS_PID_TYPE_ values */ + uint8_t idx; /* index in filtering table */ +} __packed; + +struct as10x_register_value { + uint8_t mode; + union { + uint8_t value8; /* 8 bit value */ + uint16_t value16; /* 16 bit value */ + uint32_t value32; /* 32 bit value */ + } __packed u; +} __packed; + +struct as10x_register_addr { + /* register addr */ + uint32_t addr; + /* register mode access */ + uint8_t mode; +}; + +#endif diff --git a/drivers/staging/media/cxd2099/Kconfig b/drivers/staging/media/cxd2099/Kconfig new file mode 100644 index 00000000..b48aefdd --- /dev/null +++ b/drivers/staging/media/cxd2099/Kconfig @@ -0,0 +1,12 @@ +config DVB_CXD2099 + tristate "CXD2099AR Common Interface driver" + depends on DVB_CORE && PCI && I2C + ---help--- + Support for the CI module found on cards based on + - Micronas ngene PCIe bridge: cineS2 etc. + - Digital Devices PCIe bridge: Octopus series + + For now, data is passed through '/dev/dvb/adapterX/sec0': + - Encrypted data must be written to 'sec0'. + - Decrypted data can be read from 'sec0'. + - Setup the CAM using device 'ca0'. diff --git a/drivers/staging/media/cxd2099/Makefile b/drivers/staging/media/cxd2099/Makefile new file mode 100644 index 00000000..64cfc77b --- /dev/null +++ b/drivers/staging/media/cxd2099/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_DVB_CXD2099) += cxd2099.o + +ccflags-y += -Idrivers/media/dvb/dvb-core/ +ccflags-y += -Idrivers/media/dvb/frontends/ +ccflags-y += -Idrivers/media/common/tuners/ diff --git a/drivers/staging/media/cxd2099/TODO b/drivers/staging/media/cxd2099/TODO new file mode 100644 index 00000000..375bb6f8 --- /dev/null +++ b/drivers/staging/media/cxd2099/TODO @@ -0,0 +1,12 @@ +For now, data is passed through '/dev/dvb/adapterX/sec0': + - Encrypted data must be written to 'sec0'. + - Decrypted data can be read from 'sec0'. + - Setup the CAM using device 'ca0'. + +But this is wrong. There are some discussions about the proper way for +doing it, as seen at: + http://www.mail-archive.com/linux-media@vger.kernel.org/msg22196.html + +While there's no proper fix for it, the driver should be kept in staging. + +Patches should be submitted to: linux-media@vger.kernel.org. diff --git a/drivers/staging/media/cxd2099/cxd2099.c b/drivers/staging/media/cxd2099/cxd2099.c new file mode 100644 index 00000000..1c04185b --- /dev/null +++ b/drivers/staging/media/cxd2099/cxd2099.c @@ -0,0 +1,716 @@ +/* + * cxd2099.c: Driver for the CXD2099AR Common Interface Controller + * + * Copyright (C) 2010-2011 Digital Devices GmbH + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cxd2099.h" + +#define MAX_BUFFER_SIZE 248 + +struct cxd { + struct dvb_ca_en50221 en; + + struct i2c_adapter *i2c; + struct cxd2099_cfg cfg; + + u8 regs[0x23]; + u8 lastaddress; + u8 clk_reg_f; + u8 clk_reg_b; + int mode; + int ready; + int dr; + int slot_stat; + + u8 amem[1024]; + int amem_read; + + int cammode; + struct mutex lock; +}; + +static int i2c_write_reg(struct i2c_adapter *adapter, u8 adr, + u8 reg, u8 data) +{ + u8 m[2] = {reg, data}; + struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = m, .len = 2}; + + if (i2c_transfer(adapter, &msg, 1) != 1) { + printk(KERN_ERR "Failed to write to I2C register %02x@%02x!\n", + reg, adr); + return -1; + } + return 0; +} + +static int i2c_write(struct i2c_adapter *adapter, u8 adr, + u8 *data, u8 len) +{ + struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = data, .len = len}; + + if (i2c_transfer(adapter, &msg, 1) != 1) { + printk(KERN_ERR "Failed to write to I2C!\n"); + return -1; + } + return 0; +} + +static int i2c_read_reg(struct i2c_adapter *adapter, u8 adr, + u8 reg, u8 *val) +{ + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = ®, .len = 1}, + {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1} }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + printk(KERN_ERR "error in i2c_read_reg\n"); + return -1; + } + return 0; +} + +static int i2c_read(struct i2c_adapter *adapter, u8 adr, + u8 reg, u8 *data, u8 n) +{ + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = ®, .len = 1}, + {.addr = adr, .flags = I2C_M_RD, + .buf = data, .len = n} }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + printk(KERN_ERR "error in i2c_read\n"); + return -1; + } + return 0; +} + +static int read_block(struct cxd *ci, u8 adr, u8 *data, u8 n) +{ + int status; + + status = i2c_write_reg(ci->i2c, ci->cfg.adr, 0, adr); + if (!status) { + ci->lastaddress = adr; + status = i2c_read(ci->i2c, ci->cfg.adr, 1, data, n); + } + return status; +} + +static int read_reg(struct cxd *ci, u8 reg, u8 *val) +{ + return read_block(ci, reg, val, 1); +} + + +static int read_pccard(struct cxd *ci, u16 address, u8 *data, u8 n) +{ + int status; + u8 addr[3] = {2, address & 0xff, address >> 8}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) + status = i2c_read(ci->i2c, ci->cfg.adr, 3, data, n); + return status; +} + +static int write_pccard(struct cxd *ci, u16 address, u8 *data, u8 n) +{ + int status; + u8 addr[3] = {2, address & 0xff, address >> 8}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) { + u8 buf[256] = {3}; + memcpy(buf+1, data, n); + status = i2c_write(ci->i2c, ci->cfg.adr, buf, n+1); + } + return status; +} + +static int read_io(struct cxd *ci, u16 address, u8 *val) +{ + int status; + u8 addr[3] = {2, address & 0xff, address >> 8}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) + status = i2c_read(ci->i2c, ci->cfg.adr, 3, val, 1); + return status; +} + +static int write_io(struct cxd *ci, u16 address, u8 val) +{ + int status; + u8 addr[3] = {2, address & 0xff, address >> 8}; + u8 buf[2] = {3, val}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) + status = i2c_write(ci->i2c, ci->cfg.adr, buf, 2); + return status; +} + +#if 0 +static int read_io_data(struct cxd *ci, u8 *data, u8 n) +{ + int status; + u8 addr[3] = { 2, 0, 0 }; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) + status = i2c_read(ci->i2c, ci->cfg.adr, 3, data, n); + return 0; +} + +static int write_io_data(struct cxd *ci, u8 *data, u8 n) +{ + int status; + u8 addr[3] = {2, 0, 0}; + + status = i2c_write(ci->i2c, ci->cfg.adr, addr, 3); + if (!status) { + u8 buf[256] = {3}; + memcpy(buf+1, data, n); + status = i2c_write(ci->i2c, ci->cfg.adr, buf, n + 1); + } + return 0; +} +#endif + +static int write_regm(struct cxd *ci, u8 reg, u8 val, u8 mask) +{ + int status; + + status = i2c_write_reg(ci->i2c, ci->cfg.adr, 0, reg); + if (!status && reg >= 6 && reg <= 8 && mask != 0xff) + status = i2c_read_reg(ci->i2c, ci->cfg.adr, 1, &ci->regs[reg]); + ci->regs[reg] = (ci->regs[reg] & (~mask)) | val; + if (!status) { + ci->lastaddress = reg; + status = i2c_write_reg(ci->i2c, ci->cfg.adr, 1, ci->regs[reg]); + } + if (reg == 0x20) + ci->regs[reg] &= 0x7f; + return status; +} + +static int write_reg(struct cxd *ci, u8 reg, u8 val) +{ + return write_regm(ci, reg, val, 0xff); +} + +#ifdef BUFFER_MODE +static int write_block(struct cxd *ci, u8 adr, u8 *data, int n) +{ + int status; + u8 buf[256] = {1}; + + status = i2c_write_reg(ci->i2c, ci->cfg.adr, 0, adr); + if (!status) { + ci->lastaddress = adr; + memcpy(buf + 1, data, n); + status = i2c_write(ci->i2c, ci->cfg.adr, buf, n + 1); + } + return status; +} +#endif + +static void set_mode(struct cxd *ci, int mode) +{ + if (mode == ci->mode) + return; + + switch (mode) { + case 0x00: /* IO mem */ + write_regm(ci, 0x06, 0x00, 0x07); + break; + case 0x01: /* ATT mem */ + write_regm(ci, 0x06, 0x02, 0x07); + break; + default: + break; + } + ci->mode = mode; +} + +static void cam_mode(struct cxd *ci, int mode) +{ + if (mode == ci->cammode) + return; + + switch (mode) { + case 0x00: + write_regm(ci, 0x20, 0x80, 0x80); + break; + case 0x01: +#ifdef BUFFER_MODE + if (!ci->en.read_data) + return; + printk(KERN_INFO "enable cam buffer mode\n"); + /* write_reg(ci, 0x0d, 0x00); */ + /* write_reg(ci, 0x0e, 0x01); */ + write_regm(ci, 0x08, 0x40, 0x40); + /* read_reg(ci, 0x12, &dummy); */ + write_regm(ci, 0x08, 0x80, 0x80); +#endif + break; + default: + break; + } + ci->cammode = mode; +} + + + +static int init(struct cxd *ci) +{ + int status; + + mutex_lock(&ci->lock); + ci->mode = -1; + do { + status = write_reg(ci, 0x00, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x01, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x02, 0x10); + if (status < 0) + break; + status = write_reg(ci, 0x03, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x05, 0xFF); + if (status < 0) + break; + status = write_reg(ci, 0x06, 0x1F); + if (status < 0) + break; + status = write_reg(ci, 0x07, 0x1F); + if (status < 0) + break; + status = write_reg(ci, 0x08, 0x28); + if (status < 0) + break; + status = write_reg(ci, 0x14, 0x20); + if (status < 0) + break; + +#if 0 + status = write_reg(ci, 0x09, 0x4D); /* Input Mode C, BYPass Serial, TIVAL = low, MSB */ + if (status < 0) + break; +#endif + status = write_reg(ci, 0x0A, 0xA7); /* TOSTRT = 8, Mode B (gated clock), falling Edge, Serial, POL=HIGH, MSB */ + if (status < 0) + break; + + status = write_reg(ci, 0x0B, 0x33); + if (status < 0) + break; + status = write_reg(ci, 0x0C, 0x33); + if (status < 0) + break; + + status = write_regm(ci, 0x14, 0x00, 0x0F); + if (status < 0) + break; + status = write_reg(ci, 0x15, ci->clk_reg_b); + if (status < 0) + break; + status = write_regm(ci, 0x16, 0x00, 0x0F); + if (status < 0) + break; + status = write_reg(ci, 0x17, ci->clk_reg_f); + if (status < 0) + break; + + if (ci->cfg.clock_mode) { + if (ci->cfg.polarity) { + status = write_reg(ci, 0x09, 0x6f); + if (status < 0) + break; + } else { + status = write_reg(ci, 0x09, 0x6d); + if (status < 0) + break; + } + status = write_reg(ci, 0x20, 0x68); + if (status < 0) + break; + status = write_reg(ci, 0x21, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x22, 0x02); + if (status < 0) + break; + } else { + if (ci->cfg.polarity) { + status = write_reg(ci, 0x09, 0x4f); + if (status < 0) + break; + } else { + status = write_reg(ci, 0x09, 0x4d); + if (status < 0) + break; + } + + status = write_reg(ci, 0x20, 0x28); + if (status < 0) + break; + status = write_reg(ci, 0x21, 0x00); + if (status < 0) + break; + status = write_reg(ci, 0x22, 0x07); + if (status < 0) + break; + } + + status = write_regm(ci, 0x20, 0x80, 0x80); + if (status < 0) + break; + status = write_regm(ci, 0x03, 0x02, 0x02); + if (status < 0) + break; + status = write_reg(ci, 0x01, 0x04); + if (status < 0) + break; + status = write_reg(ci, 0x00, 0x31); + if (status < 0) + break; + + /* Put TS in bypass */ + status = write_regm(ci, 0x09, 0x08, 0x08); + if (status < 0) + break; + ci->cammode = -1; + cam_mode(ci, 0); + } while (0); + mutex_unlock(&ci->lock); + + return 0; +} + +static int read_attribute_mem(struct dvb_ca_en50221 *ca, + int slot, int address) +{ + struct cxd *ci = ca->data; +#if 0 + if (ci->amem_read) { + if (address <= 0 || address > 1024) + return -EIO; + return ci->amem[address]; + } + + mutex_lock(&ci->lock); + write_regm(ci, 0x06, 0x00, 0x05); + read_pccard(ci, 0, &ci->amem[0], 128); + read_pccard(ci, 128, &ci->amem[0], 128); + read_pccard(ci, 256, &ci->amem[0], 128); + read_pccard(ci, 384, &ci->amem[0], 128); + write_regm(ci, 0x06, 0x05, 0x05); + mutex_unlock(&ci->lock); + return ci->amem[address]; +#else + u8 val; + mutex_lock(&ci->lock); + set_mode(ci, 1); + read_pccard(ci, address, &val, 1); + mutex_unlock(&ci->lock); + /* printk(KERN_INFO "%02x:%02x\n", address,val); */ + return val; +#endif +} + +static int write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, + int address, u8 value) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + set_mode(ci, 1); + write_pccard(ci, address, &value, 1); + mutex_unlock(&ci->lock); + return 0; +} + +static int read_cam_control(struct dvb_ca_en50221 *ca, + int slot, u8 address) +{ + struct cxd *ci = ca->data; + u8 val; + + mutex_lock(&ci->lock); + set_mode(ci, 0); + read_io(ci, address, &val); + mutex_unlock(&ci->lock); + return val; +} + +static int write_cam_control(struct dvb_ca_en50221 *ca, int slot, + u8 address, u8 value) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + set_mode(ci, 0); + write_io(ci, address, value); + mutex_unlock(&ci->lock); + return 0; +} + +static int slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); +#if 0 + write_reg(ci, 0x00, 0x21); + write_reg(ci, 0x06, 0x1F); + write_reg(ci, 0x00, 0x31); +#else +#if 0 + write_reg(ci, 0x06, 0x1F); + write_reg(ci, 0x06, 0x2F); +#else + cam_mode(ci, 0); + write_reg(ci, 0x00, 0x21); + write_reg(ci, 0x06, 0x1F); + write_reg(ci, 0x00, 0x31); + write_regm(ci, 0x20, 0x80, 0x80); + write_reg(ci, 0x03, 0x02); + ci->ready = 0; +#endif +#endif + ci->mode = -1; + { + int i; +#if 0 + u8 val; +#endif + for (i = 0; i < 100; i++) { + msleep(10); +#if 0 + read_reg(ci, 0x06, &val); + printk(KERN_INFO "%d:%02x\n", i, val); + if (!(val&0x10)) + break; +#else + if (ci->ready) + break; +#endif + } + } + mutex_unlock(&ci->lock); + /* msleep(500); */ + return 0; +} + +static int slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct cxd *ci = ca->data; + + printk(KERN_INFO "slot_shutdown\n"); + mutex_lock(&ci->lock); + write_regm(ci, 0x09, 0x08, 0x08); + write_regm(ci, 0x20, 0x80, 0x80); /* Reset CAM Mode */ + write_regm(ci, 0x06, 0x07, 0x07); /* Clear IO Mode */ + ci->mode = -1; + mutex_unlock(&ci->lock); + return 0; +} + +static int slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + write_regm(ci, 0x09, 0x00, 0x08); + set_mode(ci, 0); +#ifdef BUFFER_MODE + cam_mode(ci, 1); +#endif + mutex_unlock(&ci->lock); + return 0; +} + + +static int campoll(struct cxd *ci) +{ + u8 istat; + + read_reg(ci, 0x04, &istat); + if (!istat) + return 0; + write_reg(ci, 0x05, istat); + + if (istat&0x40) { + ci->dr = 1; + printk(KERN_INFO "DR\n"); + } + if (istat&0x20) + printk(KERN_INFO "WC\n"); + + if (istat&2) { + u8 slotstat; + + read_reg(ci, 0x01, &slotstat); + if (!(2&slotstat)) { + if (!ci->slot_stat) { + ci->slot_stat |= DVB_CA_EN50221_POLL_CAM_PRESENT; + write_regm(ci, 0x03, 0x08, 0x08); + } + + } else { + if (ci->slot_stat) { + ci->slot_stat = 0; + write_regm(ci, 0x03, 0x00, 0x08); + printk(KERN_INFO "NO CAM\n"); + ci->ready = 0; + } + } + if (istat&8 && ci->slot_stat == DVB_CA_EN50221_POLL_CAM_PRESENT) { + ci->ready = 1; + ci->slot_stat |= DVB_CA_EN50221_POLL_CAM_READY; + } + } + return 0; +} + + +static int poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct cxd *ci = ca->data; + u8 slotstat; + + mutex_lock(&ci->lock); + campoll(ci); + read_reg(ci, 0x01, &slotstat); + mutex_unlock(&ci->lock); + + return ci->slot_stat; +} + +#ifdef BUFFER_MODE +static int read_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount) +{ + struct cxd *ci = ca->data; + u8 msb, lsb; + u16 len; + + mutex_lock(&ci->lock); + campoll(ci); + mutex_unlock(&ci->lock); + + printk(KERN_INFO "read_data\n"); + if (!ci->dr) + return 0; + + mutex_lock(&ci->lock); + read_reg(ci, 0x0f, &msb); + read_reg(ci, 0x10, &lsb); + len = (msb<<8)|lsb; + read_block(ci, 0x12, ebuf, len); + ci->dr = 0; + mutex_unlock(&ci->lock); + + return len; +} + +static int write_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + printk(kern_INFO "write_data %d\n", ecount); + write_reg(ci, 0x0d, ecount>>8); + write_reg(ci, 0x0e, ecount&0xff); + write_block(ci, 0x11, ebuf, ecount); + mutex_unlock(&ci->lock); + return ecount; +} +#endif + +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, +#ifdef BUFFER_MODE + .read_data = read_data, + .write_data = write_data, +#endif + +}; + +struct dvb_ca_en50221 *cxd2099_attach(struct cxd2099_cfg *cfg, + void *priv, + struct i2c_adapter *i2c) +{ + struct cxd *ci = 0; + u8 val; + + if (i2c_read_reg(i2c, cfg->adr, 0, &val) < 0) { + printk(KERN_INFO "No CXD2099 detected at %02x\n", cfg->adr); + return 0; + } + + ci = kmalloc(sizeof(struct cxd), GFP_KERNEL); + if (!ci) + return 0; + memset(ci, 0, sizeof(*ci)); + + mutex_init(&ci->lock); + memcpy(&ci->cfg, cfg, sizeof(struct cxd2099_cfg)); + ci->i2c = i2c; + ci->lastaddress = 0xff; + ci->clk_reg_b = 0x4a; + ci->clk_reg_f = 0x1b; + + memcpy(&ci->en, &en_templ, sizeof(en_templ)); + ci->en.data = ci; + init(ci); + printk(KERN_INFO "Attached CXD2099AR at %02x\n", ci->cfg.adr); + return &ci->en; +} +EXPORT_SYMBOL(cxd2099_attach); + +MODULE_DESCRIPTION("cxd2099"); +MODULE_AUTHOR("Ralph Metzler"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/cxd2099/cxd2099.h b/drivers/staging/media/cxd2099/cxd2099.h new file mode 100644 index 00000000..19c588a5 --- /dev/null +++ b/drivers/staging/media/cxd2099/cxd2099.h @@ -0,0 +1,51 @@ +/* + * cxd2099.h: Driver for the CXD2099AR Common Interface Controller + * + * Copyright (C) 2010-2011 Digital Devices GmbH + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _CXD2099_H_ +#define _CXD2099_H_ + +#include + +struct cxd2099_cfg { + u32 bitrate; + u8 adr; + u8 polarity:1; + u8 clock_mode:1; +}; + +#if defined(CONFIG_DVB_CXD2099) || \ + (defined(CONFIG_DVB_CXD2099_MODULE) && defined(MODULE)) +struct dvb_ca_en50221 *cxd2099_attach(struct cxd2099_cfg *cfg, + void *priv, struct i2c_adapter *i2c); +#else + +static inline struct dvb_ca_en50221 *cxd2099_attach(struct cxd2099_cfg *cfg, + void *priv, struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif diff --git a/drivers/staging/media/dt3155v4l/Kconfig b/drivers/staging/media/dt3155v4l/Kconfig new file mode 100644 index 00000000..226a1ca9 --- /dev/null +++ b/drivers/staging/media/dt3155v4l/Kconfig @@ -0,0 +1,28 @@ +config VIDEO_DT3155 + tristate "DT3155 frame grabber, Video4Linux interface" + depends on PCI && VIDEO_DEV && VIDEO_V4L2 + select VIDEOBUF2_DMA_CONTIG + default n + ---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 dt3155v4l. + +config DT3155_CCIR + bool "Selects CCIR/50Hz vertical refresh" + depends on VIDEO_DT3155 + default y + ---help--- + Select it for CCIR/50Hz (European region), + or leave it unselected for RS-170/60Hz (North America). + +config DT3155_STREAMING + bool "Selects streaming capture method" + depends on VIDEO_DT3155 + default y + ---help--- + Select it if you want to use streaming of memory mapped buffers + or leave it unselected if you want to use read method (one copy more). diff --git a/drivers/staging/media/dt3155v4l/Makefile b/drivers/staging/media/dt3155v4l/Makefile new file mode 100644 index 00000000..ce7a3ec2 --- /dev/null +++ b/drivers/staging/media/dt3155v4l/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_VIDEO_DT3155) += dt3155v4l.o diff --git a/drivers/staging/media/dt3155v4l/dt3155v4l.c b/drivers/staging/media/dt3155v4l/dt3155v4l.c new file mode 100644 index 00000000..280c84ec --- /dev/null +++ b/drivers/staging/media/dt3155v4l/dt3155v4l.c @@ -0,0 +1,988 @@ +/*************************************************************************** + * Copyright (C) 2006-2010 by Marin Mitov * + * mitov@issp.bas.bg * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dt3155v4l.h" + +#define DT3155_VENDOR_ID 0x8086 +#define DT3155_DEVICE_ID 0x1223 + +/* DT3155_CHUNK_SIZE is 4M (2^22) 8 full size buffers */ +#define DT3155_CHUNK_SIZE (1U << 22) + +#define DT3155_COH_FLAGS (GFP_KERNEL | GFP_DMA32 | __GFP_COLD | __GFP_NOWARN) + +#define DT3155_BUF_SIZE (768 * 576) + +#ifdef CONFIG_DT3155_STREAMING +#define DT3155_CAPTURE_METHOD V4L2_CAP_STREAMING +#else +#define DT3155_CAPTURE_METHOD V4L2_CAP_READWRITE +#endif + +/* global initializers (for all boards) */ +#ifdef CONFIG_DT3155_CCIR +static const u8 csr2_init = VT_50HZ; +#define DT3155_CURRENT_NORM V4L2_STD_625_50 +static const unsigned int img_width = 768; +static const unsigned int img_height = 576; +static const unsigned int frames_per_sec = 25; +static const struct v4l2_fmtdesc frame_std[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = 0, + .description = "CCIR/50Hz 8 bits gray", + .pixelformat = V4L2_PIX_FMT_GREY, + }, +}; +#else +static const u8 csr2_init = VT_60HZ; +#define DT3155_CURRENT_NORM V4L2_STD_525_60 +static const unsigned int img_width = 640; +static const unsigned int img_height = 480; +static const unsigned int frames_per_sec = 30; +static const struct v4l2_fmtdesc frame_std[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = 0, + .description = "RS-170/60Hz 8 bits gray", + .pixelformat = V4L2_PIX_FMT_GREY, + }, +}; +#endif + +#define NUM_OF_FORMATS ARRAY_SIZE(frame_std) + +static u8 config_init = ACQ_MODE_EVEN; + +/** + * 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); + mmiowb(); + 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 writting 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); + mmiowb(); + 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 writting 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); + mmiowb(); +} + +/** + * 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/writting 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_start_acq(struct dt3155_priv *pd) +{ + struct vb2_buffer *vb = pd->curr_buf; + dma_addr_t dma_addr; + + dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + iowrite32(dma_addr, pd->regs + EVEN_DMA_START); + iowrite32(dma_addr + img_width, pd->regs + ODD_DMA_START); + iowrite32(img_width, pd->regs + EVEN_DMA_STRIDE); + iowrite32(img_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; /* success */ +} + +/* + * driver-specific callbacks (vb2_ops) + */ +static int +dt3155_queue_setup(struct vb2_queue *q, const struct v4l2_format *fmt, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], void *alloc_ctxs[]) + +{ + struct dt3155_priv *pd = vb2_get_drv_priv(q); + void *ret; + + if (*num_buffers == 0) + *num_buffers = 1; + *num_planes = 1; + sizes[0] = img_width * img_height; + if (pd->q->alloc_ctx[0]) + return 0; + ret = vb2_dma_contig_init_ctx(&pd->pdev->dev); + if (IS_ERR(ret)) + return PTR_ERR(ret); + pd->q->alloc_ctx[0] = ret; + return 0; +} + +static void +dt3155_wait_prepare(struct vb2_queue *q) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(q); + + mutex_unlock(pd->vdev->lock); +} + +static void +dt3155_wait_finish(struct vb2_queue *q) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(q); + + mutex_lock(pd->vdev->lock); +} + +static int +dt3155_buf_prepare(struct vb2_buffer *vb) +{ + vb2_set_plane_payload(vb, 0, img_width * img_height); + return 0; +} + +static int +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); + 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); + msleep(45); /* irq hendler will stop the hardware */ + return 0; +} + +static void +dt3155_buf_queue(struct vb2_buffer *vb) +{ + struct dt3155_priv *pd = vb2_get_drv_priv(vb->vb2_queue); + + /* pd->q->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 = vb; + dt3155_start_acq(pd); + } + spin_unlock_irq(&pd->lock); +} +/* + * end driver-specific callbacks + */ + +const struct vb2_ops q_ops = { + .queue_setup = dt3155_queue_setup, + .wait_prepare = dt3155_wait_prepare, + .wait_finish = dt3155_wait_finish, + .buf_prepare = dt3155_buf_prepare, + .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); + ipd->field_count++; + return IRQ_HANDLED; /* start of field irq */ + } + if ((tmp & FLD_START) && (tmp & FLD_END_ODD)) + ipd->stats.start_before_end++; + /* check for corrupted fields */ +/* write_i2c_reg(ipd->regs, EVEN_CSR, CSR_ERROR | CSR_DONE); */ +/* write_i2c_reg(ipd->regs, ODD_CSR, CSR_ERROR | CSR_DONE); */ + tmp = ioread32(ipd->regs + CSR1) & (FLD_CRPT_EVEN | FLD_CRPT_ODD); + if (tmp) { + ipd->stats.corrupted_fields++; + 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); + mmiowb(); + } + + spin_lock(&ipd->lock); + if (ipd->curr_buf) { + do_gettimeofday(&ipd->curr_buf->v4l2_buf.timestamp); + ipd->curr_buf->v4l2_buf.sequence = (ipd->field_count) >> 1; + vb2_buffer_done(ipd->curr_buf, VB2_BUF_STATE_DONE); + } + + if (!ipd->q->streaming || list_empty(&ipd->dmaq)) + goto stop_dma; + ivb = list_first_entry(&ipd->dmaq, typeof(*ivb), done_entry); + list_del(&ivb->done_entry); + ipd->curr_buf = ivb; + dma_addr = vb2_dma_contig_plane_dma_addr(ivb, 0); + iowrite32(dma_addr, ipd->regs + EVEN_DMA_START); + iowrite32(dma_addr + img_width, ipd->regs + ODD_DMA_START); + iowrite32(img_width, ipd->regs + EVEN_DMA_STRIDE); + iowrite32(img_width, ipd->regs + ODD_DMA_STRIDE); + mmiowb(); + /* 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; + +stop_dma: + ipd->curr_buf = NULL; + /* stop the board */ + write_i2c_reg_nowait(ipd->regs, CSR2, ipd->csr2); + iowrite32(FIFO_EN | SRST | FLD_CRPT_ODD | FLD_CRPT_EVEN | + FLD_DN_ODD | FLD_DN_EVEN, ipd->regs + CSR1); + /* disable interrupts, clear all irq flags */ + iowrite32(FLD_START | FLD_END_EVEN | FLD_END_ODD, ipd->regs + INT_CSR); + spin_unlock(&ipd->lock); + return IRQ_HANDLED; +} + +static int +dt3155_open(struct file *filp) +{ + int ret = 0; + struct dt3155_priv *pd = video_drvdata(filp); + + if (!pd->users) { + pd->q = kzalloc(sizeof(*pd->q), GFP_KERNEL); + if (!pd->q) { + ret = -ENOMEM; + goto err_alloc_queue; + } + pd->q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pd->q->io_modes = VB2_READ | VB2_MMAP; + pd->q->ops = &q_ops; + pd->q->mem_ops = &vb2_dma_contig_memops; + pd->q->drv_priv = pd; + pd->curr_buf = NULL; + pd->field_count = 0; + vb2_queue_init(pd->q); /* cannot fail */ + INIT_LIST_HEAD(&pd->dmaq); + spin_lock_init(&pd->lock); + /* disable all irqs, clear all irq flags */ + iowrite32(FLD_START | FLD_END_EVEN | FLD_END_ODD, + pd->regs + INT_CSR); + ret = request_irq(pd->pdev->irq, dt3155_irq_handler_even, + IRQF_SHARED, DT3155_NAME, pd); + if (ret) + goto err_request_irq; + } + pd->users++; + return 0; /* success */ +err_request_irq: + kfree(pd->q); + pd->q = NULL; +err_alloc_queue: + return ret; +} + +static int +dt3155_release(struct file *filp) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + pd->users--; + BUG_ON(pd->users < 0); + if (!pd->users) { + vb2_queue_release(pd->q); + free_irq(pd->pdev->irq, pd); + if (pd->q->alloc_ctx[0]) + vb2_dma_contig_cleanup_ctx(pd->q->alloc_ctx[0]); + kfree(pd->q); + pd->q = NULL; + } + return 0; +} + +static ssize_t +dt3155_read(struct file *filp, char __user *user, size_t size, loff_t *loff) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_read(pd->q, user, size, loff, filp->f_flags & O_NONBLOCK); +} + +static unsigned int +dt3155_poll(struct file *filp, struct poll_table_struct *polltbl) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_poll(pd->q, filp, polltbl); +} + +static int +dt3155_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_mmap(pd->q, vma); +} + +static const struct v4l2_file_operations dt3155_fops = { + .owner = THIS_MODULE, + .open = dt3155_open, + .release = dt3155_release, + .read = dt3155_read, + .poll = dt3155_poll, + .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */ + .mmap = dt3155_mmap, +}; + +static int +dt3155_ioc_streamon(struct file *filp, void *p, enum v4l2_buf_type type) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_streamon(pd->q, type); +} + +static int +dt3155_ioc_streamoff(struct file *filp, void *p, enum v4l2_buf_type type) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_streamoff(pd->q, type); +} + +static int +dt3155_ioc_querycap(struct file *filp, void *p, struct v4l2_capability *cap) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + strcpy(cap->driver, DT3155_NAME); + strcpy(cap->card, DT3155_NAME " frame grabber"); + sprintf(cap->bus_info, "PCI:%s", pci_name(pd->pdev)); + cap->version = + KERNEL_VERSION(DT3155_VER_MAJ, DT3155_VER_MIN, DT3155_VER_EXT); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + DT3155_CAPTURE_METHOD; + return 0; +} + +static int +dt3155_ioc_enum_fmt_vid_cap(struct file *filp, void *p, struct v4l2_fmtdesc *f) +{ + if (f->index >= NUM_OF_FORMATS) + return -EINVAL; + *f = frame_std[f->index]; + return 0; +} + +static int +dt3155_ioc_g_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + f->fmt.pix.width = img_width; + f->fmt.pix.height = img_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 = 0; + f->fmt.pix.priv = 0; + return 0; +} + +static int +dt3155_ioc_try_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (f->fmt.pix.width == img_width && + f->fmt.pix.height == img_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) + return 0; + else + return -EINVAL; +} + +static int +dt3155_ioc_s_fmt_vid_cap(struct file *filp, void *p, struct v4l2_format *f) +{ + return dt3155_ioc_g_fmt_vid_cap(filp, p, f); +} + +static int +dt3155_ioc_reqbufs(struct file *filp, void *p, struct v4l2_requestbuffers *b) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_reqbufs(pd->q, b); +} + +static int +dt3155_ioc_querybuf(struct file *filp, void *p, struct v4l2_buffer *b) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_querybuf(pd->q, b); +} + +static int +dt3155_ioc_qbuf(struct file *filp, void *p, struct v4l2_buffer *b) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_qbuf(pd->q, b); +} + +static int +dt3155_ioc_dqbuf(struct file *filp, void *p, struct v4l2_buffer *b) +{ + struct dt3155_priv *pd = video_drvdata(filp); + + return vb2_dqbuf(pd->q, b, filp->f_flags & O_NONBLOCK); +} + +static int +dt3155_ioc_querystd(struct file *filp, void *p, v4l2_std_id *norm) +{ + *norm = DT3155_CURRENT_NORM; + return 0; +} + +static int +dt3155_ioc_g_std(struct file *filp, void *p, v4l2_std_id *norm) +{ + *norm = DT3155_CURRENT_NORM; + return 0; +} + +static int +dt3155_ioc_s_std(struct file *filp, void *p, v4l2_std_id *norm) +{ + if (*norm & DT3155_CURRENT_NORM) + return 0; + return -EINVAL; +} + +static int +dt3155_ioc_enum_input(struct file *filp, void *p, struct v4l2_input *input) +{ + if (input->index) + return -EINVAL; + strcpy(input->name, "Coax in"); + input->type = V4L2_INPUT_TYPE_CAMERA; + /* + * FIXME: input->std = 0 according to v4l2 API + * VIDIOC_G_STD, VIDIOC_S_STD, VIDIOC_QUERYSTD and VIDIOC_ENUMSTD + * should return -EINVAL + */ + input->std = DT3155_CURRENT_NORM; + input->status = 0;/* FIXME: add sync detection & V4L2_IN_ST_NO_H_LOCK */ + return 0; +} + +static int +dt3155_ioc_g_input(struct file *filp, void *p, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int +dt3155_ioc_s_input(struct file *filp, void *p, unsigned int i) +{ + if (i) + return -EINVAL; + return 0; +} + +static int +dt3155_ioc_g_parm(struct file *filp, void *p, struct v4l2_streamparm *parms) +{ + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + parms->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + parms->parm.capture.capturemode = 0; + parms->parm.capture.timeperframe.numerator = 1001; + parms->parm.capture.timeperframe.denominator = frames_per_sec * 1000; + parms->parm.capture.extendedmode = 0; + parms->parm.capture.readbuffers = 1; /* FIXME: 2 buffers? */ + return 0; +} + +static int +dt3155_ioc_s_parm(struct file *filp, void *p, struct v4l2_streamparm *parms) +{ + if (parms->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + parms->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + parms->parm.capture.capturemode = 0; + parms->parm.capture.timeperframe.numerator = 1001; + parms->parm.capture.timeperframe.denominator = frames_per_sec * 1000; + parms->parm.capture.extendedmode = 0; + parms->parm.capture.readbuffers = 1; /* FIXME: 2 buffers? */ + return 0; +} + +static const struct v4l2_ioctl_ops dt3155_ioctl_ops = { + .vidioc_streamon = dt3155_ioc_streamon, + .vidioc_streamoff = dt3155_ioc_streamoff, + .vidioc_querycap = dt3155_ioc_querycap, +/* + .vidioc_g_priority = dt3155_ioc_g_priority, + .vidioc_s_priority = dt3155_ioc_s_priority, +*/ + .vidioc_enum_fmt_vid_cap = dt3155_ioc_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = dt3155_ioc_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = dt3155_ioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = dt3155_ioc_s_fmt_vid_cap, + .vidioc_reqbufs = dt3155_ioc_reqbufs, + .vidioc_querybuf = dt3155_ioc_querybuf, + .vidioc_qbuf = dt3155_ioc_qbuf, + .vidioc_dqbuf = dt3155_ioc_dqbuf, + .vidioc_querystd = dt3155_ioc_querystd, + .vidioc_g_std = dt3155_ioc_g_std, + .vidioc_s_std = dt3155_ioc_s_std, + .vidioc_enum_input = dt3155_ioc_enum_input, + .vidioc_g_input = dt3155_ioc_g_input, + .vidioc_s_input = dt3155_ioc_s_input, +/* + .vidioc_queryctrl = dt3155_ioc_queryctrl, + .vidioc_g_ctrl = dt3155_ioc_g_ctrl, + .vidioc_s_ctrl = dt3155_ioc_s_ctrl, + .vidioc_querymenu = dt3155_ioc_querymenu, + .vidioc_g_ext_ctrls = dt3155_ioc_g_ext_ctrls, + .vidioc_s_ext_ctrls = dt3155_ioc_s_ext_ctrls, +*/ + .vidioc_g_parm = dt3155_ioc_g_parm, + .vidioc_s_parm = dt3155_ioc_s_parm, +/* + .vidioc_cropcap = dt3155_ioc_cropcap, + .vidioc_g_crop = dt3155_ioc_g_crop, + .vidioc_s_crop = dt3155_ioc_s_crop, + .vidioc_enum_framesizes = dt3155_ioc_enum_framesizes, + .vidioc_enum_frameintervals = dt3155_ioc_enum_frameintervals, +*/ +}; + +static int __devinit +dt3155_init_board(struct pci_dev *pdev) +{ + struct dt3155_priv *pd = pci_get_drvdata(pdev); + void *buf_cpu; + dma_addr_t buf_dma; + int i; + u8 tmp; + + pci_set_master(pdev); /* dt3155 needs it */ + + /* resetting the adapter */ + iowrite32(FLD_CRPT_ODD | FLD_CRPT_EVEN | FLD_DN_ODD | FLD_DN_EVEN, + pd->regs + CSR1); + mmiowb(); + msleep(20); + + /* initializing adaper registers */ + iowrite32(FIFO_EN | SRST, pd->regs + CSR1); + mmiowb(); + iowrite32(0xEEEEEE01, pd->regs + EVEN_PIXEL_FMT); + iowrite32(0xEEEEEE01, pd->regs + ODD_PIXEL_FMT); + iowrite32(0x00000020, pd->regs + FIFO_TRIGER); + 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); + mmiowb(); + + /* 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 chanel 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); + + /* allocate memory, and initialize the DMA machine */ + buf_cpu = dma_alloc_coherent(&pdev->dev, DT3155_BUF_SIZE, &buf_dma, + GFP_KERNEL); + if (!buf_cpu) + return -ENOMEM; + iowrite32(buf_dma, pd->regs + EVEN_DMA_START); + iowrite32(buf_dma, pd->regs + ODD_DMA_START); + iowrite32(0, pd->regs + EVEN_DMA_STRIDE); + iowrite32(0, pd->regs + ODD_DMA_STRIDE); + + /* Perform a pseudo even field acquire */ + iowrite32(FIFO_EN | SRST | CAP_CONT_ODD, pd->regs + CSR1); + write_i2c_reg(pd->regs, CSR2, pd->csr2 | SYNC_SNTL); + write_i2c_reg(pd->regs, CONFIG, pd->config); + write_i2c_reg(pd->regs, EVEN_CSR, CSR_SNGL); + write_i2c_reg(pd->regs, CSR2, pd->csr2 | BUSY_EVEN | SYNC_SNTL); + msleep(100); + read_i2c_reg(pd->regs, CSR2, &tmp); + write_i2c_reg(pd->regs, EVEN_CSR, CSR_ERROR | CSR_SNGL | CSR_DONE); + write_i2c_reg(pd->regs, ODD_CSR, CSR_ERROR | CSR_SNGL | CSR_DONE); + write_i2c_reg(pd->regs, CSR2, pd->csr2); + iowrite32(FIFO_EN | SRST | FLD_DN_EVEN | FLD_DN_ODD, pd->regs + CSR1); + + /* deallocate memory */ + dma_free_coherent(&pdev->dev, DT3155_BUF_SIZE, buf_cpu, buf_dma); + if (tmp & BUSY_EVEN) + return -EIO; + return 0; +} + +static struct video_device dt3155_vdev = { + .name = DT3155_NAME, + .fops = &dt3155_fops, + .ioctl_ops = &dt3155_ioctl_ops, + .minor = -1, + .release = video_device_release, + .tvnorms = DT3155_CURRENT_NORM, + .current_norm = DT3155_CURRENT_NORM, +}; + +/* same as in drivers/base/dma-coherent.c */ +struct dma_coherent_mem { + void *virt_base; + dma_addr_t device_base; + int size; + int flags; + unsigned long *bitmap; +}; + +static int __devinit +dt3155_alloc_coherent(struct device *dev, size_t size, int flags) +{ + struct dma_coherent_mem *mem; + dma_addr_t dev_base; + int pages = size >> PAGE_SHIFT; + int bitmap_size = BITS_TO_LONGS(pages) * sizeof(long); + + if ((flags & DMA_MEMORY_MAP) == 0) + goto out; + if (!size) + goto out; + if (dev->dma_mem) + goto out; + + mem = kzalloc(sizeof(*mem), GFP_KERNEL); + if (!mem) + goto out; + mem->virt_base = dma_alloc_coherent(dev, size, &dev_base, + DT3155_COH_FLAGS); + if (!mem->virt_base) + goto err_alloc_coherent; + mem->bitmap = kzalloc(bitmap_size, GFP_KERNEL); + if (!mem->bitmap) + goto err_bitmap; + + /* coherent_dma_mask is already set to 32 bits */ + mem->device_base = dev_base; + mem->size = pages; + mem->flags = flags; + dev->dma_mem = mem; + return DMA_MEMORY_MAP; + +err_bitmap: + dma_free_coherent(dev, size, mem->virt_base, dev_base); +err_alloc_coherent: + kfree(mem); +out: + return 0; +} + +static void __devexit +dt3155_free_coherent(struct device *dev) +{ + struct dma_coherent_mem *mem = dev->dma_mem; + + if (!mem) + return; + dev->dma_mem = NULL; + dma_free_coherent(dev, mem->size << PAGE_SHIFT, + mem->virt_base, mem->device_base); + kfree(mem->bitmap); + kfree(mem); +} + +static int __devinit +dt3155_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + int err; + struct dt3155_priv *pd; + + err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return -ENODEV; + err = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return -ENODEV; + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + pd->vdev = video_device_alloc(); + if (!pd->vdev) + goto err_video_device_alloc; + *pd->vdev = dt3155_vdev; + pci_set_drvdata(pdev, pd); /* for use in dt3155_remove() */ + video_set_drvdata(pd->vdev, pd); /* for use in video_fops */ + pd->users = 0; + pd->pdev = pdev; + INIT_LIST_HEAD(&pd->dmaq); + mutex_init(&pd->mux); + pd->vdev->lock = &pd->mux; /* for locking v4l2_file_operations */ + spin_lock_init(&pd->lock); + pd->csr2 = csr2_init; + pd->config = config_init; + err = pci_enable_device(pdev); + if (err) + goto err_enable_dev; + err = pci_request_region(pdev, 0, pci_name(pdev)); + if (err) + goto err_req_region; + pd->regs = pci_iomap(pdev, 0, pci_resource_len(pd->pdev, 0)); + if (!pd->regs) { + err = -ENOMEM; + goto err_pci_iomap; + } + err = dt3155_init_board(pdev); + if (err) + goto err_init_board; + err = video_register_device(pd->vdev, VFL_TYPE_GRABBER, -1); + if (err) + goto err_init_board; + if (dt3155_alloc_coherent(&pdev->dev, DT3155_CHUNK_SIZE, + DMA_MEMORY_MAP)) + dev_info(&pdev->dev, "preallocated 8 buffers\n"); + dev_info(&pdev->dev, "/dev/video%i is ready\n", pd->vdev->minor); + return 0; /* success */ + +err_init_board: + pci_iounmap(pdev, pd->regs); +err_pci_iomap: + pci_release_region(pdev, 0); +err_req_region: + pci_disable_device(pdev); +err_enable_dev: + video_device_release(pd->vdev); +err_video_device_alloc: + kfree(pd); + return err; +} + +static void __devexit +dt3155_remove(struct pci_dev *pdev) +{ + struct dt3155_priv *pd = pci_get_drvdata(pdev); + + dt3155_free_coherent(&pdev->dev); + video_unregister_device(pd->vdev); + pci_iounmap(pdev, pd->regs); + pci_release_region(pdev, 0); + pci_disable_device(pdev); + /* + * video_device_release() is invoked automatically + * see: struct video_device dt3155_vdev + */ + kfree(pd); +} + +static DEFINE_PCI_DEVICE_TABLE(pci_ids) = { + { PCI_DEVICE(DT3155_VENDOR_ID, DT3155_DEVICE_ID) }, + { 0, /* 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 = __devexit_p(dt3155_remove), +}; + +static int __init +dt3155_init_module(void) +{ + return pci_register_driver(&pci_driver); +} + +static void __exit +dt3155_exit_module(void) +{ + pci_unregister_driver(&pci_driver); +} + +module_init(dt3155_init_module); +module_exit(dt3155_exit_module); + +MODULE_DESCRIPTION("video4linux pci-driver for dt3155 frame grabber"); +MODULE_AUTHOR("Marin Mitov "); +MODULE_VERSION(DT3155_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/media/dt3155v4l/dt3155v4l.h b/drivers/staging/media/dt3155v4l/dt3155v4l.h new file mode 100644 index 00000000..2e4f89d4 --- /dev/null +++ b/drivers/staging/media/dt3155v4l/dt3155v4l.h @@ -0,0 +1,212 @@ +/*************************************************************************** + * Copyright (C) 2006-2010 by Marin Mitov * + * mitov@issp.bas.bg * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/* DT3155 header file */ +#ifndef _DT3155_H_ +#define _DT3155_H_ + +#ifdef __KERNEL__ + +#include +#include + +#define DT3155_NAME "dt3155" +#define DT3155_VER_MAJ 1 +#define DT3155_VER_MIN 1 +#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_TRIGER 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 CRPT_DIS 0x00004000 +#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 + +#ifdef CONFIG_DT3155_CCIR +#define DMA_STRIDE 768 +#else +#define DMA_STRIDE 640 +#endif + +/** + * struct dt3155_stats - statistics structure + * + * @free_bufs_empty: no free image buffers + * @corrupted_fields: corrupted fields + * @dma_map_failed: dma mapping failed + * @start_before_end: new started before old ended + */ +struct dt3155_stats { + int free_bufs_empty; + int corrupted_fields; + int dma_map_failed; + int start_before_end; +}; + +/* per board private data structure */ +/** + * struct dt3155_priv - private data structure + * + * @vdev: pointer to video_device structure + * @pdev: pointer to pci_dev structure + * @q pointer to 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 + * @field_count fields counter + * @stats: statistics structure + * @users open count + * @regs: local copy of mmio base register + * @csr2: local copy of csr2 register + * @config: local copy of config register + */ +struct dt3155_priv { + struct video_device *vdev; + struct pci_dev *pdev; + struct vb2_queue *q; + struct vb2_buffer *curr_buf; + struct mutex mux; + struct list_head dmaq; + spinlock_t lock; + unsigned int field_count; + struct dt3155_stats stats; + void __iomem *regs; + int users; + u8 csr2, config; +}; + +#endif /* __KERNEL__ */ + +#endif /* _DT3155_H_ */ diff --git a/drivers/staging/media/easycap/Kconfig b/drivers/staging/media/easycap/Kconfig new file mode 100644 index 00000000..a425a6f9 --- /dev/null +++ b/drivers/staging/media/easycap/Kconfig @@ -0,0 +1,30 @@ +config EASYCAP + tristate "EasyCAP USB ID 05e1:0408 support" + depends on USB && VIDEO_DEV && SND + select SND_PCM + + ---help--- + This is an integrated audio/video driver for EasyCAP cards with + USB ID 05e1:0408. It supports two hardware variants: + + * EasyCAP USB 2.0 Video Adapter with Audio, Model DC60, + having input cables labelled CVBS, S-VIDEO, AUDIO(L), AUDIO(R) + + * EasyCAP002 4-Channel USB 2.0 DVR, having input cables labelled + 1, 2, 3, 4 and an unlabelled input cable for a microphone. + + To compile this driver as a module, choose M here: the + module will be called easycap + +config EASYCAP_DEBUG + bool "Enable EasyCAP driver debugging" + depends on EASYCAP + + ---help--- + This option enables debug printouts + + To enable debug, pass the debug level to the debug module + parameter: + + modprobe easycap debug=[0..9] + diff --git a/drivers/staging/media/easycap/Makefile b/drivers/staging/media/easycap/Makefile new file mode 100644 index 00000000..a34e75f5 --- /dev/null +++ b/drivers/staging/media/easycap/Makefile @@ -0,0 +1,10 @@ +easycap-objs := easycap_main.o +easycap-objs += easycap_low.o +easycap-objs += easycap_ioctl.o +easycap-objs += easycap_settings.o +easycap-objs += easycap_testcard.o +easycap-objs += easycap_sound.o +obj-$(CONFIG_EASYCAP) += easycap.o + +ccflags-y := -Wall + diff --git a/drivers/staging/media/easycap/README b/drivers/staging/media/easycap/README new file mode 100644 index 00000000..796b0323 --- /dev/null +++ b/drivers/staging/media/easycap/README @@ -0,0 +1,141 @@ + + *********************************************************** + * EasyCAP USB 2.0 Video Adapter with Audio, Model DC60 * + * and * + * EasyCAP002 4-Channel USB 2.0 DVR * + *********************************************************** + Mike Thomas + + + +SUPPORTED HARDWARE +------------------ + +This driver is intended for use with hardware having USB ID 05e1:0408. +Two kinds of EasyCAP have this USB ID, namely: + + * EasyCAP USB 2.0 Video Adapter with Audio, Model DC60, + having input cables labelled CVBS, S-VIDEO, AUDIO(L), AUDIO(R) + + * EasyCAP002 4-Channel USB 2.0 DVR, having input cables labelled + 1, 2, 3, 4 and an unlabelled input cable for a microphone. + + +BUILD OPTIONS AND DEPENDENCIES +------------------------------ + +Unless EASYCAP_DEBUG is defined during compilation it will not be possible +to select a debug level at the time of module installation. + + +KNOWN RUNTIME ISSUES +-------------------- + +(1) Intentionally, this driver will not stream material which is unambiguously +identified by the hardware as copy-protected. Normal video output will be +present for about a minute but will then freeze when this situation arises. + +(2) The controls for luminance, contrast, saturation, hue and volume may not +always work properly. + +(3) Reduced-resolution S-Video seems to suffer from moire artefacts. + + +INPUT NUMBERING +--------------- + +For the EasyCAP with S-VIDEO input cable the driver regards a request for +inputs numbered 0 or 1 as referring to CVBS and a request for input +numbered 5 as referring to S-VIDEO. + +For the EasyCAP with four CVBS inputs the driver expects to be asked for +any one of inputs numbered 1,2,3,4. If input 0 is asked for, it is +interpreted as input 1. + + +MODULE PARAMETERS +----------------- + +Three module parameters are defined: + +debug the easycap module is configured at diagnostic level n (0 to 9) +gain audio gain level n (0 to 31, default is 16) +bars whether to display testcard bars when incoming video signal is lost + 0 => no, 1 => yes (default) + + +SUPPORTED TV STANDARDS AND RESOLUTIONS +-------------------------------------- + +The following TV standards are natively supported by the hardware and are +usable as (for example) the "norm=" parameter in the mplayer command: + + PAL_BGHIN, NTSC_N_443, + PAL_Nc, NTSC_N, + SECAM, NTSC_M, NTSC_M_JP, + PAL_60, NTSC_443, + PAL_M. + +In addition, the driver offers "custom" pseudo-standards with a framerate +which is 20% of the usual framerate. These pseudo-standards are named: + + PAL_BGHIN_SLOW, NTSC_N_443_SLOW, + PAL_Nc_SLOW, NTSC_N_SLOW, + SECAM_SLOW, NTSC_M_SLOW, NTSC_M_JP_SLOW, + PAL_60_SLOW, NTSC_443_SLOW, + PAL_M_SLOW. + + +The available picture sizes are: + + at 25 frames per second: 720x576, 704x576, 640x480, 360x288, 320x240; + at 30 frames per second: 720x480, 640x480, 360x240, 320x240. + + +WHAT'S TESTED AND WHAT'S NOT +---------------------------- + +This driver is known to work with mplayer, mencoder, tvtime, zoneminder, +xawtv, gstreamer and sufficiently recent versions of vlc. An interface +to ffmpeg is implemented, but serious audio-video synchronization problems +remain. + +The driver is designed to support all the TV standards accepted by the +hardware, but as yet it has actually been tested on only a few of these. + +I have been unable to test and calibrate the S-video input myself because I +do not possess any equipment with S-video output. + + +UDEV RULES +---------- + +In order that the special files /dev/easycap0 and /dev/easysnd1 are created +with conveniently relaxed permissions when the EasyCAP is plugged in, a file +is preferably to be provided in directory /etc/udev/rules.d with content: + +ACTION!="add|change", GOTO="easycap_rules_end" +ATTRS{idVendor}=="05e1", ATTRS{idProduct}=="0408", \ + MODE="0666", OWNER="root", GROUP="root" +LABEL="easycap_rules_end" + + +MODPROBE CONFIGURATION +---------------------- + +The easycap module is in competition with the module snd-usb-audio for the +EasyCAP's audio channel, and its installation can be aided by providing a +file in directory /etc/modprobe.d with content: + +options easycap gain=16 bars=1 +install easycap /sbin/rmmod snd-usb-audio; /sbin/modprobe --ignore-install easycap + + +ACKNOWLEGEMENTS AND REFERENCES +------------------------------ +This driver makes use of information contained in the Syntek Semicon DC-1125 +Driver, presently maintained at http://sourceforge.net/projects/syntekdriver/ +by Nicolas Vivien. Particularly useful has been a patch to the latter driver +provided by Ivor Hewitt in January 2009. The NTSC implementation is taken +from the work of Ben Trask. + diff --git a/drivers/staging/media/easycap/easycap.h b/drivers/staging/media/easycap/easycap.h new file mode 100644 index 00000000..a007e744 --- /dev/null +++ b/drivers/staging/media/easycap/easycap.h @@ -0,0 +1,567 @@ +/***************************************************************************** +* * +* easycap.h * +* * +*****************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas + * + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * THE FOLLOWING PARAMETERS ARE UNDEFINED: + * + * EASYCAP_DEBUG + * + * IF REQUIRED THEY MUST BE EXTERNALLY DEFINED, FOR EXAMPLE AS COMPILER + * OPTIONS. + */ +/*---------------------------------------------------------------------------*/ + +#ifndef __EASYCAP_H__ +#define __EASYCAP_H__ + +/*---------------------------------------------------------------------------*/ +/* + * THESE ARE NORMALLY DEFINED + */ +/*---------------------------------------------------------------------------*/ +#define PATIENCE 500 +#define PERSEVERE +/*---------------------------------------------------------------------------*/ +/* + * THESE ARE FOR MAINTENANCE ONLY - NORMALLY UNDEFINED: + */ +/*---------------------------------------------------------------------------*/ +#undef EASYCAP_TESTCARD +/*---------------------------------------------------------------------------*/ +#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 + +/*---------------------------------------------------------------------------*/ +/* VENDOR, PRODUCT: Syntek Semiconductor Co., Ltd + * + * EITHER EasyCAP USB 2.0 Video Adapter with Audio, Model No. DC60 + * with input cabling: AUDIO(L), AUDIO(R), CVBS, S-VIDEO. + * + * OR EasyCAP 4CHANNEL USB 2.0 DVR, Model No. EasyCAP002 + * with input cabling: MICROPHONE, CVBS1, CVBS2, CVBS3, CVBS4. + */ +/*---------------------------------------------------------------------------*/ +#define USB_EASYCAP_VENDOR_ID 0x05e1 +#define USB_EASYCAP_PRODUCT_ID 0x0408 + +#define EASYCAP_DRIVER_VERSION "0.9.01" +#define EASYCAP_DRIVER_DESCRIPTION "easycapdc60" + +#define DONGLE_MANY 8 +#define INPUT_MANY 6 +/*---------------------------------------------------------------------------*/ +/* + * DEFAULT LUMINANCE, CONTRAST, SATURATION AND HUE + */ +/*---------------------------------------------------------------------------*/ +#define SAA_0A_DEFAULT 0x7F +#define SAA_0B_DEFAULT 0x3F +#define SAA_0C_DEFAULT 0x2F +#define SAA_0D_DEFAULT 0x00 +/*---------------------------------------------------------------------------*/ +/* + * VIDEO STREAMING PARAMETERS: + * USB 2.0 PROVIDES FOR HIGH-BANDWIDTH ENDPOINTS WITH AN UPPER LIMIT + * OF 3072 BYTES PER MICROFRAME for wMaxPacketSize. + */ +/*---------------------------------------------------------------------------*/ +#define VIDEO_ISOC_BUFFER_MANY 16 +#define VIDEO_ISOC_ORDER 3 +#define VIDEO_ISOC_FRAMESPERDESC ((unsigned int) 1 << VIDEO_ISOC_ORDER) +#define USB_2_0_MAXPACKETSIZE 3072 +#if (USB_2_0_MAXPACKETSIZE > PAGE_SIZE) +#error video_isoc_buffer[.] will not be big enough +#endif +#define VIDEO_JUNK_TOLERATE VIDEO_ISOC_BUFFER_MANY +#define VIDEO_LOST_TOLERATE 50 +/*---------------------------------------------------------------------------*/ +/* + * VIDEO BUFFERS + */ +/*---------------------------------------------------------------------------*/ +#define FIELD_BUFFER_SIZE (203 * PAGE_SIZE) +#define FRAME_BUFFER_SIZE (405 * PAGE_SIZE) +#define FIELD_BUFFER_MANY 4 +#define FRAME_BUFFER_MANY 6 +/*---------------------------------------------------------------------------*/ +/* + * AUDIO STREAMING PARAMETERS + */ +/*---------------------------------------------------------------------------*/ +#define AUDIO_ISOC_BUFFER_MANY 16 +#define AUDIO_ISOC_ORDER 1 +#define AUDIO_ISOC_FRAMESPERDESC 32 +#define AUDIO_ISOC_BUFFER_SIZE (PAGE_SIZE << AUDIO_ISOC_ORDER) +/*---------------------------------------------------------------------------*/ +/* + * AUDIO BUFFERS + */ +/*---------------------------------------------------------------------------*/ +#define AUDIO_FRAGMENT_MANY 32 +#define PAGES_PER_AUDIO_FRAGMENT 4 +/*---------------------------------------------------------------------------*/ +/* + * IT IS ESSENTIAL THAT EVEN-NUMBERED STANDARDS ARE 25 FRAMES PER SECOND, + * ODD-NUMBERED STANDARDS ARE 30 FRAMES PER SECOND. + * THE NUMBERING OF STANDARDS MUST NOT BE CHANGED WITHOUT DUE CARE. NOT + * ONLY MUST THE PARAMETER + * STANDARD_MANY + * BE CHANGED TO CORRESPOND TO THE NEW NUMBER OF STANDARDS, BUT ALSO THE + * NUMBERING MUST REMAIN AN UNBROKEN ASCENDING SEQUENCE: DUMMY STANDARDS + * MAY NEED TO BE ADDED. APPROPRIATE CHANGES WILL ALWAYS BE REQUIRED IN + * ROUTINE fillin_formats() AND POSSIBLY ELSEWHERE. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +#define PAL_BGHIN 0 +#define PAL_Nc 2 +#define SECAM 4 +#define NTSC_N 6 +#define NTSC_N_443 8 +#define NTSC_M 1 +#define NTSC_443 3 +#define NTSC_M_JP 5 +#define PAL_60 7 +#define PAL_M 9 +#define PAL_BGHIN_SLOW 10 +#define PAL_Nc_SLOW 12 +#define SECAM_SLOW 14 +#define NTSC_N_SLOW 16 +#define NTSC_N_443_SLOW 18 +#define NTSC_M_SLOW 11 +#define NTSC_443_SLOW 13 +#define NTSC_M_JP_SLOW 15 +#define PAL_60_SLOW 17 +#define PAL_M_SLOW 19 +#define STANDARD_MANY 20 +/*---------------------------------------------------------------------------*/ +/* + * ENUMS + */ +/*---------------------------------------------------------------------------*/ +enum { + AT_720x576, + AT_704x576, + AT_640x480, + AT_720x480, + AT_360x288, + AT_320x240, + AT_360x240, + RESOLUTION_MANY +}; +enum { + FMT_UYVY, + FMT_YUY2, + FMT_RGB24, + FMT_RGB32, + FMT_BGR24, + FMT_BGR32, + PIXELFORMAT_MANY +}; +enum { + FIELD_NONE, + FIELD_INTERLACED, + INTERLACE_MANY +}; +#define SETTINGS_MANY (STANDARD_MANY * \ + RESOLUTION_MANY * \ + 2 * \ + PIXELFORMAT_MANY * \ + INTERLACE_MANY) +/*---------------------------------------------------------------------------*/ +/* + * STRUCTURE DEFINITIONS + */ +/*---------------------------------------------------------------------------*/ +struct easycap_dongle { + struct easycap *peasycap; + struct mutex mutex_video; + struct mutex mutex_audio; +}; +/*---------------------------------------------------------------------------*/ +struct data_buffer { + struct list_head list_head; + void *pgo; + void *pto; + u16 kount; + u16 input; +}; +/*---------------------------------------------------------------------------*/ +struct data_urb { + struct list_head list_head; + struct urb *purb; + int isbuf; + int length; +}; +/*---------------------------------------------------------------------------*/ +struct easycap_standard { + u16 mask; +struct v4l2_standard v4l2_standard; +}; +struct easycap_format { + u16 mask; + char name[128]; +struct v4l2_format v4l2_format; +}; +struct inputset { + int input; + int input_ok; + int standard_offset; + int standard_offset_ok; + int format_offset; + int format_offset_ok; + int brightness; + int brightness_ok; + int contrast; + int contrast_ok; + int saturation; + int saturation_ok; + int hue; + int hue_ok; +}; +/*---------------------------------------------------------------------------*/ +/* + * easycap.ilk == 0 => CVBS+S-VIDEO HARDWARE, AUDIO wMaxPacketSize=256 + * easycap.ilk == 2 => CVBS+S-VIDEO HARDWARE, AUDIO wMaxPacketSize=9 + * easycap.ilk == 3 => FOUR-CVBS HARDWARE, AUDIO wMaxPacketSize=9 + */ +/*---------------------------------------------------------------------------*/ +struct easycap { + int isdongle; + int minor; + + struct video_device video_device; + struct v4l2_device v4l2_device; + + int status; + unsigned int audio_pages_per_fragment; + unsigned int audio_bytes_per_fragment; + unsigned int audio_buffer_page_many; + +#define UPSAMPLE +#ifdef UPSAMPLE + s16 oldaudio; +#endif /*UPSAMPLE*/ + + int ilk; + bool microphone; + + struct usb_device *pusb_device; + struct usb_interface *pusb_interface; + + struct kref kref; + + int queued[FRAME_BUFFER_MANY]; + int done[FRAME_BUFFER_MANY]; + + wait_queue_head_t wq_video; + wait_queue_head_t wq_audio; + wait_queue_head_t wq_trigger; + + int input; + int polled; + int standard_offset; + int format_offset; + struct inputset inputset[INPUT_MANY]; + + bool ntsc; + int fps; + int usec; + int tolerate; + int skip; + int skipped; + int lost[INPUT_MANY]; + int merit[180]; + + int video_interface; + int video_altsetting_on; + int video_altsetting_off; + int video_endpointnumber; + int video_isoc_maxframesize; + int video_isoc_buffer_size; + int video_isoc_framesperdesc; + + int video_isoc_streaming; + int video_isoc_sequence; + int video_idle; + int video_eof; + int video_junk; + + struct data_buffer video_isoc_buffer[VIDEO_ISOC_BUFFER_MANY]; + struct data_buffer field_buffer[FIELD_BUFFER_MANY] + [(FIELD_BUFFER_SIZE/PAGE_SIZE)]; + struct data_buffer frame_buffer[FRAME_BUFFER_MANY] + [(FRAME_BUFFER_SIZE/PAGE_SIZE)]; + + struct list_head urb_video_head; + struct list_head *purb_video_head; + + u8 cache[8]; + u8 *pcache; + int video_mt; + int audio_mt; + u32 isequence; + + int vma_many; +/*---------------------------------------------------------------------------*/ +/* + * BUFFER INDICATORS + */ +/*---------------------------------------------------------------------------*/ + int field_fill; /* Field buffer being filled by easycap_complete(). */ + /* Bumped only by easycap_complete(). */ + int field_page; /* Page of field buffer page being filled by */ + /* easycap_complete(). */ + int field_read; /* Field buffer to be read by field2frame(). */ + /* Bumped only by easycap_complete(). */ + int frame_fill; /* Frame buffer being filled by field2frame(). */ + /* Bumped only by easycap_dqbuf() when */ + /* field2frame() has created a complete frame. */ + int frame_read; /* Frame buffer offered to user by DQBUF. */ + /* Set only by easycap_dqbuf() to trail frame_fill.*/ + int frame_lock; /* Flag set to 1 by DQBUF and cleared by QBUF */ +/*---------------------------------------------------------------------------*/ +/* + * IMAGE PROPERTIES + */ +/*---------------------------------------------------------------------------*/ + u32 pixelformat; + int width; + int height; + int bytesperpixel; + bool byteswaporder; + bool decimatepixel; + bool offerfields; + int frame_buffer_used; + int frame_buffer_many; + int videofieldamount; + + int brightness; + int contrast; + int saturation; + int hue; + + int allocation_video_urb; + int allocation_video_page; + int allocation_video_struct; + int registered_video; +/*---------------------------------------------------------------------------*/ +/* + * ALSA + */ +/*---------------------------------------------------------------------------*/ + struct snd_pcm_hardware alsa_hardware; + struct snd_card *psnd_card; + struct snd_pcm *psnd_pcm; + struct snd_pcm_substream *psubstream; + int dma_fill; + int dma_next; + int dma_read; +/*---------------------------------------------------------------------------*/ +/* + * SOUND PROPERTIES + */ +/*---------------------------------------------------------------------------*/ + int audio_interface; + int audio_altsetting_on; + int audio_altsetting_off; + int audio_endpointnumber; + int audio_isoc_maxframesize; + int audio_isoc_buffer_size; + int audio_isoc_framesperdesc; + + int audio_isoc_streaming; + int audio_idle; + int audio_eof; + int volume; + int mute; + s8 gain; + + struct data_buffer audio_isoc_buffer[AUDIO_ISOC_BUFFER_MANY]; + + struct list_head urb_audio_head; + struct list_head *purb_audio_head; +/*---------------------------------------------------------------------------*/ +/* + * BUFFER INDICATORS + */ +/*---------------------------------------------------------------------------*/ + int audio_fill; /* Audio buffer being filled by easycap_complete(). */ + /* Bumped only by easycap_complete(). */ + int audio_read; /* Audio buffer page being read by easycap_read(). */ + /* Set by easycap_read() to trail audio_fill by */ + /* one fragment. */ +/*---------------------------------------------------------------------------*/ +/* + * SOUND PROPERTIES + */ +/*---------------------------------------------------------------------------*/ + int allocation_audio_urb; + int allocation_audio_page; + int allocation_audio_struct; + int registered_audio; + + long long int audio_sample; + long long int audio_niveau; + long long int audio_square; + + struct data_buffer audio_buffer[]; +}; +/*---------------------------------------------------------------------------*/ +/* + * VIDEO FUNCTION PROTOTYPES + */ +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +int easycap_newinput(struct easycap *, int); +void easycap_testcard(struct easycap *, int); +int easycap_isdongle(struct easycap *); + +long easycap_unlocked_ioctl(struct file *, unsigned int, unsigned long); + +int easycap_video_dqbuf(struct easycap *, int); +int easycap_video_submit_urbs(struct easycap *); +int easycap_video_kill_urbs(struct easycap *); +int easycap_video_fillin_formats(void); + +int adjust_standard(struct easycap *, v4l2_std_id); +int adjust_format(struct easycap *, u32, u32, u32, int, bool); +int adjust_brightness(struct easycap *, int); +int adjust_contrast(struct easycap *, int); +int adjust_saturation(struct easycap *, int); +int adjust_hue(struct easycap *, int); +/*---------------------------------------------------------------------------*/ +/* + * AUDIO FUNCTION PROTOTYPES + */ +/*---------------------------------------------------------------------------*/ +int easycap_alsa_probe(struct easycap *); +int easycap_audio_kill_urbs(struct easycap *); +void easycap_alsa_complete(struct urb *); +/*---------------------------------------------------------------------------*/ +/* + * LOW-LEVEL FUNCTION PROTOTYPES + */ +/*---------------------------------------------------------------------------*/ +int easycap_audio_gainset(struct usb_device *, s8); +int easycap_audio_setup(struct easycap *); + +int easycap_wakeup_device(struct usb_device *); + +int setup_stk(struct usb_device *, bool); +int setup_saa(struct usb_device *, bool); +int ready_saa(struct usb_device *); +int merit_saa(struct usb_device *); +int check_vt(struct usb_device *); +int select_input(struct usb_device *, int, int); +int set_resolution(struct usb_device *, u16, u16, u16, u16); + +int read_saa(struct usb_device *, u16); +int write_saa(struct usb_device *, u16, u16); +int start_100(struct usb_device *); +int stop_100(struct usb_device *); +/*---------------------------------------------------------------------------*/ + + +/*---------------------------------------------------------------------------*/ +/* + * MACROS SAM(...) AND JOM(...) ALLOW DIAGNOSTIC OUTPUT TO BE TAGGED WITH + * THE IDENTITY OF THE DONGLE TO WHICH IT APPLIES, BUT IF INVOKED WHEN THE + * POINTER peasycap IS INVALID AN Oops IS LIKELY, AND ITS CAUSE MAY NOT BE + * IMMEDIATELY OBVIOUS FROM A CASUAL READING OF THE SOURCE CODE. BEWARE. +*/ +/*---------------------------------------------------------------------------*/ +const char *strerror(int err); + +#define SAY(format, args...) do { \ + printk(KERN_DEBUG "easycap:: %s: " \ + format, __func__, ##args); \ +} while (0) +#define SAM(format, args...) do { \ + printk(KERN_DEBUG "easycap::%i%s: " \ + format, peasycap->isdongle, __func__, ##args);\ +} while (0) + +#ifdef CONFIG_EASYCAP_DEBUG +extern int easycap_debug; +#define JOT(n, format, args...) do { \ + if (n <= easycap_debug) { \ + printk(KERN_DEBUG "easycap:: %s: " \ + format, __func__, ##args);\ + } \ +} while (0) +#define JOM(n, format, args...) do { \ + if (n <= easycap_debug) { \ + printk(KERN_DEBUG "easycap::%i%s: " \ + format, peasycap->isdongle, __func__, ##args);\ + } \ +} while (0) + +#else +#define JOT(n, format, args...) do {} while (0) +#define JOM(n, format, args...) do {} while (0) +#endif /* CONFIG_EASYCAP_DEBUG */ + +/*---------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* globals + */ +/*---------------------------------------------------------------------------*/ + +extern bool easycap_readback; +extern const struct easycap_standard easycap_standard[]; +extern struct easycap_format easycap_format[]; +extern struct v4l2_queryctrl easycap_control[]; +extern struct easycap_dongle easycapdc60_dongle[]; + +#endif /* !__EASYCAP_H__ */ diff --git a/drivers/staging/media/easycap/easycap_ioctl.c b/drivers/staging/media/easycap/easycap_ioctl.c new file mode 100644 index 00000000..9413b374 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_ioctl.c @@ -0,0 +1,2442 @@ +/****************************************************************************** +* * +* easycap_ioctl.c * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas + * + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include "easycap.h" + +/*--------------------------------------------------------------------------*/ +/* + * UNLESS THERE IS A PREMATURE ERROR RETURN THIS ROUTINE UPDATES THE + * FOLLOWING: + * peasycap->standard_offset + * peasycap->inputset[peasycap->input].standard_offset + * peasycap->fps + * peasycap->usec + * peasycap->tolerate + * peasycap->skip + */ +/*---------------------------------------------------------------------------*/ +int adjust_standard(struct easycap *peasycap, v4l2_std_id std_id) +{ + struct easycap_standard const *peasycap_standard; + u16 reg, set; + int ir, rc, need, k; + unsigned int itwas, isnow; + bool resubmit; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + peasycap_standard = &easycap_standard[0]; + while (0xFFFF != peasycap_standard->mask) { + if (std_id == peasycap_standard->v4l2_standard.id) + break; + peasycap_standard++; + } + if (0xFFFF == peasycap_standard->mask) { + peasycap_standard = &easycap_standard[0]; + while (0xFFFF != peasycap_standard->mask) { + if (std_id & peasycap_standard->v4l2_standard.id) + break; + peasycap_standard++; + } + } + if (0xFFFF == peasycap_standard->mask) { + SAM("ERROR: 0x%08X=std_id: standard not found\n", + (unsigned int)std_id); + return -EINVAL; + } + SAM("selected standard: %s\n", + &(peasycap_standard->v4l2_standard.name[0])); + if (peasycap->standard_offset == peasycap_standard - easycap_standard) { + SAM("requested standard already in effect\n"); + return 0; + } + peasycap->standard_offset = peasycap_standard - easycap_standard; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].standard_offset_ok) { + peasycap->inputset[k].standard_offset = + peasycap->standard_offset; + } + } + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].standard_offset = + peasycap->standard_offset; + peasycap->inputset[peasycap->input].standard_offset_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + + peasycap->fps = peasycap_standard->v4l2_standard.frameperiod.denominator / + peasycap_standard->v4l2_standard.frameperiod.numerator; + switch (peasycap->fps) { + case 6: + case 30: { + peasycap->ntsc = true; + break; + } + case 5: + case 25: { + peasycap->ntsc = false; + break; + } + default: { + SAM("MISTAKE: %i=frames-per-second\n", peasycap->fps); + return -ENOENT; + } + } + JOM(8, "%i frames-per-second\n", peasycap->fps); + if (0x8000 & peasycap_standard->mask) { + peasycap->skip = 5; + peasycap->usec = 1000000 / (2 * (5 * peasycap->fps)); + peasycap->tolerate = 1000 * (25 / (5 * peasycap->fps)); + } else { + peasycap->skip = 0; + peasycap->usec = 1000000 / (2 * peasycap->fps); + peasycap->tolerate = 1000 * (25 / peasycap->fps); + } + if (peasycap->video_isoc_streaming) { + resubmit = true; + easycap_video_kill_urbs(peasycap); + } else + resubmit = false; +/*--------------------------------------------------------------------------*/ +/* + * SAA7113H DATASHEET PAGE 44, TABLE 42 + */ +/*--------------------------------------------------------------------------*/ + need = 0; + itwas = 0; + reg = 0x00; + set = 0x00; + switch (peasycap_standard->mask & 0x000F) { + case NTSC_M_JP: { + reg = 0x0A; + set = 0x95; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: cannot read SAA register 0x%02X\n", reg); + else + itwas = (unsigned int)ir; + rc = write_saa(peasycap->pusb_device, reg, set); + if (rc) + SAM("ERROR: failed to set SAA register " + "0x%02X to 0x%02X for JP standard\n", reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed " + "to 0x%02X\n", reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + + reg = 0x0B; + set = 0x48; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: cannot read SAA register 0x%02X\n", reg); + else + itwas = (unsigned int)ir; + rc = write_saa(peasycap->pusb_device, reg, set); + if (rc) + SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X " + "for JP standard\n", reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed " + "to 0x%02X\n", reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } +/*--------------------------------------------------------------------------*/ +/* + * NOTE: NO break HERE: RUN ON TO NEXT CASE + */ +/*--------------------------------------------------------------------------*/ + } + case NTSC_M: + case PAL_BGHIN: { + reg = 0x0E; + set = 0x01; + need = 1; + break; + } + case NTSC_N_443: + case PAL_60: { + reg = 0x0E; + set = 0x11; + need = 1; + break; + } + case NTSC_443: + case PAL_Nc: { + reg = 0x0E; + set = 0x21; + need = 1; + break; + } + case NTSC_N: + case PAL_M: { + reg = 0x0E; + set = 0x31; + need = 1; + break; + } + case SECAM: { + reg = 0x0E; + set = 0x51; + need = 1; + break; + } + default: + break; + } +/*--------------------------------------------------------------------------*/ + if (need) { + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: failed to read SAA register 0x%02X\n", reg); + else + itwas = (unsigned int)ir; + rc = write_saa(peasycap->pusb_device, reg, set); + if (0 != write_saa(peasycap->pusb_device, reg, set)) { + SAM("ERROR: failed to set SAA register " + "0x%02X to 0x%02X for table 42\n", reg, set); + } else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed " + "to 0x%02X\n", reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + } +/*--------------------------------------------------------------------------*/ +/* + * SAA7113H DATASHEET PAGE 41 + */ +/*--------------------------------------------------------------------------*/ + reg = 0x08; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: failed to read SAA register 0x%02X " + "so cannot reset\n", reg); + else { + itwas = (unsigned int)ir; + if (peasycap_standard->mask & 0x0001) + set = itwas | 0x40 ; + else + set = itwas & ~0x40 ; + rc = write_saa(peasycap->pusb_device, reg, set); + if (rc) + SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X\n", + reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed to 0x%02X\n", + reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + } +/*--------------------------------------------------------------------------*/ +/* + * SAA7113H DATASHEET PAGE 51, TABLE 57 + */ +/*---------------------------------------------------------------------------*/ + reg = 0x40; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: failed to read SAA register 0x%02X " + "so cannot reset\n", reg); + else { + itwas = (unsigned int)ir; + if (peasycap_standard->mask & 0x0001) + set = itwas | 0x80 ; + else + set = itwas & ~0x80 ; + rc = write_saa(peasycap->pusb_device, reg, set); + if (rc) + SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X\n", + reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed to 0x%02X\n", + reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + } +/*--------------------------------------------------------------------------*/ +/* + * SAA7113H DATASHEET PAGE 53, TABLE 66 + */ +/*--------------------------------------------------------------------------*/ + reg = 0x5A; + ir = read_saa(peasycap->pusb_device, reg); + if (0 > ir) + SAM("ERROR: failed to read SAA register 0x%02X but continuing\n", reg); + itwas = (unsigned int)ir; + if (peasycap_standard->mask & 0x0001) + set = 0x0A ; + else + set = 0x07 ; + if (0 != write_saa(peasycap->pusb_device, reg, set)) + SAM("ERROR: failed to set SAA register 0x%02X to 0x%02X\n", + reg, set); + else { + isnow = (unsigned int)read_saa(peasycap->pusb_device, reg); + if (0 > ir) + JOM(8, "SAA register 0x%02X changed " + "to 0x%02X\n", reg, isnow); + else + JOM(8, "SAA register 0x%02X changed " + "from 0x%02X to 0x%02X\n", reg, itwas, isnow); + } + if (resubmit) + easycap_video_submit_urbs(peasycap); + return 0; +} +/*****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * THE ALGORITHM FOR RESPONDING TO THE VIDIO_S_FMT IOCTL REQUIRES + * A VALID VALUE OF peasycap->standard_offset, OTHERWISE -EBUSY IS RETURNED. + * + * PROVIDED THE ARGUMENT try IS false AND THERE IS NO PREMATURE ERROR RETURN + * THIS ROUTINE UPDATES THE FOLLOWING: + * peasycap->format_offset + * peasycap->inputset[peasycap->input].format_offset + * peasycap->pixelformat + * peasycap->height + * peasycap->width + * peasycap->bytesperpixel + * peasycap->byteswaporder + * peasycap->decimatepixel + * peasycap->frame_buffer_used + * peasycap->videofieldamount + * peasycap->offerfields + * + * IF SUCCESSFUL THE FUNCTION RETURNS THE OFFSET IN easycap_format[] + * IDENTIFYING THE FORMAT WHICH IS TO RETURNED TO THE USER. + * ERRORS RETURN A NEGATIVE NUMBER. + */ +/*--------------------------------------------------------------------------*/ +int adjust_format(struct easycap *peasycap, + u32 width, u32 height, u32 pixelformat, int field, bool try) +{ + struct easycap_format *peasycap_format, *peasycap_best_format; + u16 mask; + struct usb_device *p; + int miss, multiplier, best, k; + char bf[5], fo[32], *pc; + u32 uc; + bool resubmit; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (0 > peasycap->standard_offset) { + JOM(8, "%i=peasycap->standard_offset\n", peasycap->standard_offset); + return -EBUSY; + } + p = peasycap->pusb_device; + if (!p) { + SAM("ERROR: peaycap->pusb_device is NULL\n"); + return -EFAULT; + } + pc = &bf[0]; + uc = pixelformat; + memcpy((void *)pc, (void *)(&uc), 4); + bf[4] = 0; + mask = 0xFF & easycap_standard[peasycap->standard_offset].mask; + SAM("sought: %ix%i,%s(0x%08X),%i=field,0x%02X=std mask\n", + width, height, pc, pixelformat, field, mask); + switch (field) { + case V4L2_FIELD_ANY: { + strcpy(&fo[0], "V4L2_FIELD_ANY "); + break; + } + case V4L2_FIELD_NONE: { + strcpy(&fo[0], "V4L2_FIELD_NONE"); + break; + } + case V4L2_FIELD_TOP: { + strcpy(&fo[0], "V4L2_FIELD_TOP"); + break; + } + case V4L2_FIELD_BOTTOM: { + strcpy(&fo[0], "V4L2_FIELD_BOTTOM"); + break; + } + case V4L2_FIELD_INTERLACED: { + strcpy(&fo[0], "V4L2_FIELD_INTERLACED"); + break; + } + case V4L2_FIELD_SEQ_TB: { + strcpy(&fo[0], "V4L2_FIELD_SEQ_TB"); + break; + } + case V4L2_FIELD_SEQ_BT: { + strcpy(&fo[0], "V4L2_FIELD_SEQ_BT"); + break; + } + case V4L2_FIELD_ALTERNATE: { + strcpy(&fo[0], "V4L2_FIELD_ALTERNATE"); + break; + } + case V4L2_FIELD_INTERLACED_TB: { + strcpy(&fo[0], "V4L2_FIELD_INTERLACED_TB"); + break; + } + case V4L2_FIELD_INTERLACED_BT: { + strcpy(&fo[0], "V4L2_FIELD_INTERLACED_BT"); + break; + } + default: { + strcpy(&fo[0], "V4L2_FIELD_... UNKNOWN "); + break; + } + } + SAM("sought: %s\n", &fo[0]); + if (V4L2_FIELD_ANY == field) { + field = V4L2_FIELD_NONE; + SAM("prefer: V4L2_FIELD_NONE=field, was V4L2_FIELD_ANY\n"); + } + peasycap_best_format = NULL; + peasycap_format = &easycap_format[0]; + while (0 != peasycap_format->v4l2_format.fmt.pix.width) { + JOM(16, ".> %i %i 0x%08X %ix%i\n", + peasycap_format->mask & 0x01, + peasycap_format->v4l2_format.fmt.pix.field, + peasycap_format->v4l2_format.fmt.pix.pixelformat, + peasycap_format->v4l2_format.fmt.pix.width, + peasycap_format->v4l2_format.fmt.pix.height); + + if (((peasycap_format->mask & 0x1F) == (mask & 0x1F)) && + (peasycap_format->v4l2_format.fmt.pix.field == field) && + (peasycap_format->v4l2_format.fmt.pix.pixelformat == pixelformat) && + (peasycap_format->v4l2_format.fmt.pix.width == width) && + (peasycap_format->v4l2_format.fmt.pix.height == height)) { + + peasycap_best_format = peasycap_format; + break; + } + peasycap_format++; + } + if (0 == peasycap_format->v4l2_format.fmt.pix.width) { + SAM("cannot do: %ix%i with standard mask 0x%02X\n", + width, height, mask); + peasycap_format = &easycap_format[0]; + best = -1; + while (0 != peasycap_format->v4l2_format.fmt.pix.width) { + if (((peasycap_format->mask & 0x1F) == (mask & 0x1F)) && + (peasycap_format->v4l2_format.fmt.pix.field == field) && + (peasycap_format->v4l2_format.fmt.pix.pixelformat == pixelformat)) { + + miss = abs(peasycap_format->v4l2_format.fmt.pix.width - width); + if ((best > miss) || (best < 0)) { + best = miss; + peasycap_best_format = peasycap_format; + if (!miss) + break; + } + } + peasycap_format++; + } + if (-1 == best) { + SAM("cannot do %ix... with standard mask 0x%02X\n", + width, mask); + SAM("cannot do ...x%i with standard mask 0x%02X\n", + height, mask); + SAM(" %ix%i unmatched\n", width, height); + return peasycap->format_offset; + } + } + if (!peasycap_best_format) { + SAM("MISTAKE: peasycap_best_format is NULL"); + return -EINVAL; + } + peasycap_format = peasycap_best_format; + +/*...........................................................................*/ + if (try) + return peasycap_best_format - easycap_format; +/*...........................................................................*/ + + if (false != try) { + SAM("MISTAKE: true==try where is should be false\n"); + return -EINVAL; + } + SAM("actioning: %ix%i %s\n", + peasycap_format->v4l2_format.fmt.pix.width, + peasycap_format->v4l2_format.fmt.pix.height, + &peasycap_format->name[0]); + peasycap->height = peasycap_format->v4l2_format.fmt.pix.height; + peasycap->width = peasycap_format->v4l2_format.fmt.pix.width; + peasycap->pixelformat = peasycap_format->v4l2_format.fmt.pix.pixelformat; + peasycap->format_offset = peasycap_format - easycap_format; + + + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].format_offset_ok) { + peasycap->inputset[k].format_offset = + peasycap->format_offset; + } + } + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].format_offset = + peasycap->format_offset; + peasycap->inputset[peasycap->input].format_offset_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + + + + peasycap->bytesperpixel = (0x00E0 & peasycap_format->mask) >> 5 ; + if (0x0100 & peasycap_format->mask) + peasycap->byteswaporder = true; + else + peasycap->byteswaporder = false; + if (0x0200 & peasycap_format->mask) + peasycap->skip = 5; + else + peasycap->skip = 0; + if (0x0800 & peasycap_format->mask) + peasycap->decimatepixel = true; + else + peasycap->decimatepixel = false; + if (0x1000 & peasycap_format->mask) + peasycap->offerfields = true; + else + peasycap->offerfields = false; + if (peasycap->decimatepixel) + multiplier = 2; + else + multiplier = 1; + peasycap->videofieldamount = + multiplier * peasycap->width * multiplier * peasycap->height; + peasycap->frame_buffer_used = + peasycap->bytesperpixel * peasycap->width * peasycap->height; + if (peasycap->video_isoc_streaming) { + resubmit = true; + easycap_video_kill_urbs(peasycap); + } else + resubmit = false; +/*---------------------------------------------------------------------------*/ +/* + * PAL + */ +/*---------------------------------------------------------------------------*/ + if (0 == (0x01 & peasycap_format->mask)) { + if (((720 == peasycap_format->v4l2_format.fmt.pix.width) && + (576 == peasycap_format->v4l2_format.fmt.pix.height)) || + ((360 == peasycap_format->v4l2_format.fmt.pix.width) && + (288 == peasycap_format->v4l2_format.fmt.pix.height))) { + if (set_resolution(p, 0x0000, 0x0001, 0x05A0, 0x0121)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else if ((704 == peasycap_format->v4l2_format.fmt.pix.width) && + (576 == peasycap_format->v4l2_format.fmt.pix.height)) { + if (set_resolution(p, 0x0004, 0x0001, 0x0584, 0x0121)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else if (((640 == peasycap_format->v4l2_format.fmt.pix.width) && + (480 == peasycap_format->v4l2_format.fmt.pix.height)) || + ((320 == peasycap_format->v4l2_format.fmt.pix.width) && + (240 == peasycap_format->v4l2_format.fmt.pix.height))) { + if (set_resolution(p, 0x0014, 0x0020, 0x0514, 0x0110)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else { + SAM("MISTAKE: bad format, cannot set resolution\n"); + return -EINVAL; + } +/*---------------------------------------------------------------------------*/ +/* + * NTSC + */ +/*---------------------------------------------------------------------------*/ + } else { + if (((720 == peasycap_format->v4l2_format.fmt.pix.width) && + (480 == peasycap_format->v4l2_format.fmt.pix.height)) || + ((360 == peasycap_format->v4l2_format.fmt.pix.width) && + (240 == peasycap_format->v4l2_format.fmt.pix.height))) { + if (set_resolution(p, 0x0000, 0x0003, 0x05A0, 0x00F3)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else if (((640 == peasycap_format->v4l2_format.fmt.pix.width) && + (480 == peasycap_format->v4l2_format.fmt.pix.height)) || + ((320 == peasycap_format->v4l2_format.fmt.pix.width) && + (240 == peasycap_format->v4l2_format.fmt.pix.height))) { + if (set_resolution(p, 0x0014, 0x0003, 0x0514, 0x00F3)) { + SAM("ERROR: set_resolution() failed\n"); + return -EINVAL; + } + } else { + SAM("MISTAKE: bad format, cannot set resolution\n"); + return -EINVAL; + } + } +/*---------------------------------------------------------------------------*/ + if (resubmit) + easycap_video_submit_urbs(peasycap); + + return peasycap_best_format - easycap_format; +} +/*****************************************************************************/ +int adjust_brightness(struct easycap *peasycap, int value) +{ + unsigned int mood; + int i1, k; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_BRIGHTNESS == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + if ((easycap_control[i1].minimum <= peasycap->brightness) && + (easycap_control[i1].maximum >= peasycap->brightness)) { + if (peasycap->brightness == value) { + SAM("unchanged brightness at 0x%02X\n", + value); + return 0; + } + } + peasycap->brightness = value; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].brightness_ok) + peasycap->inputset[k].brightness = + peasycap->brightness; + } + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].brightness = + peasycap->brightness; + peasycap->inputset[peasycap->input].brightness_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + + mood = 0x00FF & (unsigned int)peasycap->brightness; + if (write_saa(peasycap->pusb_device, 0x0A, mood)) { + SAM("WARNING: failed to adjust brightness " + "to 0x%02X\n", mood); + return -ENOENT; + } + SAM("adjusting brightness to 0x%02X\n", mood); + return 0; + } + i1++; + } + SAM("WARNING: failed to adjust brightness: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +int adjust_contrast(struct easycap *peasycap, int value) +{ + unsigned int mood; + int i1, k; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_CONTRAST == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + + if ((easycap_control[i1].minimum <= peasycap->contrast) && + (easycap_control[i1].maximum >= peasycap->contrast)) { + if (peasycap->contrast == value) { + SAM("unchanged contrast at 0x%02X\n", value); + return 0; + } + } + peasycap->contrast = value; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].contrast_ok) + peasycap->inputset[k].contrast = peasycap->contrast; + } + + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].contrast = + peasycap->contrast; + peasycap->inputset[peasycap->input].contrast_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + + mood = 0x00FF & (unsigned int) (peasycap->contrast - 128); + if (write_saa(peasycap->pusb_device, 0x0B, mood)) { + SAM("WARNING: failed to adjust contrast to " + "0x%02X\n", mood); + return -ENOENT; + } + SAM("adjusting contrast to 0x%02X\n", mood); + return 0; + } + i1++; + } + SAM("WARNING: failed to adjust contrast: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +int adjust_saturation(struct easycap *peasycap, int value) +{ + unsigned int mood; + int i1, k; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_SATURATION == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + + if ((easycap_control[i1].minimum <= peasycap->saturation) && + (easycap_control[i1].maximum >= peasycap->saturation)) { + if (peasycap->saturation == value) { + SAM("unchanged saturation at 0x%02X\n", + value); + return 0; + } + } + peasycap->saturation = value; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].saturation_ok) + peasycap->inputset[k].saturation = + peasycap->saturation; + } + if ((0 <= peasycap->input) && (INPUT_MANY > peasycap->input)) { + peasycap->inputset[peasycap->input].saturation = + peasycap->saturation; + peasycap->inputset[peasycap->input].saturation_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + mood = 0x00FF & (unsigned int) (peasycap->saturation - 128); + if (write_saa(peasycap->pusb_device, 0x0C, mood)) { + SAM("WARNING: failed to adjust saturation to " + "0x%02X\n", mood); + return -ENOENT; + } + SAM("adjusting saturation to 0x%02X\n", mood); + return 0; + break; + } + i1++; + } + SAM("WARNING: failed to adjust saturation: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +int adjust_hue(struct easycap *peasycap, int value) +{ + unsigned int mood; + int i1, i2, k; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_HUE == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + if ((easycap_control[i1].minimum <= peasycap->hue) && + (easycap_control[i1].maximum >= peasycap->hue)) { + if (peasycap->hue == value) { + SAM("unchanged hue at 0x%02X\n", value); + return 0; + } + } + peasycap->hue = value; + for (k = 0; k < INPUT_MANY; k++) { + if (!peasycap->inputset[k].hue_ok) + peasycap->inputset[k].hue = peasycap->hue; + } + if (0 <= peasycap->input && INPUT_MANY > peasycap->input) { + peasycap->inputset[peasycap->input].hue = peasycap->hue; + peasycap->inputset[peasycap->input].hue_ok = 1; + } else + JOM(8, "%i=peasycap->input\n", peasycap->input); + i2 = peasycap->hue - 128; + mood = 0x00FF & ((int) i2); + if (write_saa(peasycap->pusb_device, 0x0D, mood)) { + SAM("WARNING: failed to adjust hue to 0x%02X\n", mood); + return -ENOENT; + } + SAM("adjusting hue to 0x%02X\n", mood); + return 0; + break; + } + i1++; + } + SAM("WARNING: failed to adjust hue: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +static int adjust_volume(struct easycap *peasycap, int value) +{ + s8 mood; + int i1; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_AUDIO_VOLUME == easycap_control[i1].id) { + if ((easycap_control[i1].minimum > value) || + (easycap_control[i1].maximum < value)) + value = easycap_control[i1].default_value; + + if ((easycap_control[i1].minimum <= peasycap->volume) && + (easycap_control[i1].maximum >= peasycap->volume)) { + if (peasycap->volume == value) { + SAM("unchanged volume at 0x%02X\n", value); + return 0; + } + } + peasycap->volume = value; + mood = (16 > peasycap->volume) ? 16 : + ((31 < peasycap->volume) ? 31 : + (s8) peasycap->volume); + if (!easycap_audio_gainset(peasycap->pusb_device, mood)) { + SAM("WARNING: failed to adjust volume to " + "0x%2X\n", mood); + return -ENOENT; + } + SAM("adjusting volume to 0x%02X\n", mood); + return 0; + } + i1++; + } + SAM("WARNING: failed to adjust volume: control not found\n"); + return -ENOENT; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * AN ALTERNATIVE METHOD OF MUTING MIGHT SEEM TO BE: + * usb_set_interface(peasycap->pusb_device, + * peasycap->audio_interface, + * peasycap->audio_altsetting_off); + * HOWEVER, AFTER THIS COMMAND IS ISSUED ALL SUBSEQUENT URBS RECEIVE STATUS + * -ESHUTDOWN. THE HANDLER ROUTINE easyxxx_complete() DECLINES TO RESUBMIT + * THE URB AND THE PIPELINE COLLAPSES IRRETRIEVABLY. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +static int adjust_mute(struct easycap *peasycap, int value) +{ + int i1; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (V4L2_CID_AUDIO_MUTE == easycap_control[i1].id) { + peasycap->mute = value; + switch (peasycap->mute) { + case 1: { + peasycap->audio_idle = 1; + SAM("adjusting mute: %i=peasycap->audio_idle\n", + peasycap->audio_idle); + return 0; + } + default: { + peasycap->audio_idle = 0; + SAM("adjusting mute: %i=peasycap->audio_idle\n", + peasycap->audio_idle); + return 0; + } + } + break; + } + i1++; + } + SAM("WARNING: failed to adjust mute: control not found\n"); + return -ENOENT; +} +/*---------------------------------------------------------------------------*/ +long easycap_unlocked_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct easycap *peasycap; + struct usb_device *p; + int kd; + + if (!file) { + SAY("ERROR: file is NULL\n"); + return -ERESTARTSYS; + } + peasycap = file->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -1; + } + p = peasycap->pusb_device; + if (!p) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + kd = easycap_isdongle(peasycap); + if (0 <= kd && DONGLE_MANY > kd) { + if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_video)) { + SAY("ERROR: cannot lock " + "easycapdc60_dongle[%i].mutex_video\n", kd); + return -ERESTARTSYS; + } + JOM(4, "locked easycapdc60_dongle[%i].mutex_video\n", kd); +/*---------------------------------------------------------------------------*/ +/* + * MEANWHILE, easycap_usb_disconnect() MAY HAVE FREED POINTER peasycap, + * IN WHICH CASE A REPEAT CALL TO isdongle() WILL FAIL. + * IF NECESSARY, BAIL OUT. + */ +/*---------------------------------------------------------------------------*/ + if (kd != easycap_isdongle(peasycap)) + return -ERESTARTSYS; + if (!file) { + SAY("ERROR: file is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + peasycap = file->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + } else { +/*---------------------------------------------------------------------------*/ +/* + * IF easycap_usb_disconnect() HAS ALREADY FREED POINTER peasycap BEFORE THE + * ATTEMPT TO ACQUIRE THE SEMAPHORE, isdongle() WILL HAVE FAILED. BAIL OUT. + */ +/*---------------------------------------------------------------------------*/ + return -ERESTARTSYS; + } +/*---------------------------------------------------------------------------*/ + switch (cmd) { + case VIDIOC_QUERYCAP: { + struct v4l2_capability v4l2_capability; + char version[16], *p1, *p2; + int i, rc, k[3]; + long lng; + + JOM(8, "VIDIOC_QUERYCAP\n"); + + if (16 <= strlen(EASYCAP_DRIVER_VERSION)) { + SAM("ERROR: bad driver version string\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + strcpy(&version[0], EASYCAP_DRIVER_VERSION); + for (i = 0; i < 3; i++) + k[i] = 0; + p2 = &version[0]; + i = 0; + while (*p2) { + p1 = p2; + while (*p2 && ('.' != *p2)) + p2++; + if (*p2) + *p2++ = 0; + if (3 > i) { + rc = (int) strict_strtol(p1, 10, &lng); + if (rc) { + SAM("ERROR: %i=strict_strtol(%s,.,,)\n", + rc, p1); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + k[i] = (int)lng; + } + i++; + } + + memset(&v4l2_capability, 0, sizeof(struct v4l2_capability)); + strlcpy(&v4l2_capability.driver[0], + "easycap", sizeof(v4l2_capability.driver)); + + v4l2_capability.capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | + V4L2_CAP_AUDIO | + V4L2_CAP_READWRITE; + + v4l2_capability.version = KERNEL_VERSION(k[0], k[1], k[2]); + JOM(8, "v4l2_capability.version=(%i,%i,%i)\n", k[0], k[1], k[2]); + + strlcpy(&v4l2_capability.card[0], + "EasyCAP DC60", sizeof(v4l2_capability.card)); + + if (usb_make_path(peasycap->pusb_device, + &v4l2_capability.bus_info[0], + sizeof(v4l2_capability.bus_info)) < 0) { + + strlcpy(&v4l2_capability.bus_info[0], "EasyCAP bus_info", + sizeof(v4l2_capability.bus_info)); + JOM(8, "%s=v4l2_capability.bus_info\n", + &v4l2_capability.bus_info[0]); + } + if (copy_to_user((void __user *)arg, &v4l2_capability, + sizeof(struct v4l2_capability))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUMINPUT: { + struct v4l2_input v4l2_input; + u32 index; + + JOM(8, "VIDIOC_ENUMINPUT\n"); + + if (copy_from_user(&v4l2_input, (void __user *)arg, + sizeof(struct v4l2_input))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + index = v4l2_input.index; + memset(&v4l2_input, 0, sizeof(struct v4l2_input)); + + switch (index) { + case 0: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS0"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | + V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 1: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS1"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 2: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS2"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 3: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS3"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 4: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "CVBS4"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + case 5: { + v4l2_input.index = index; + strcpy(&v4l2_input.name[0], "S-VIDEO"); + v4l2_input.type = V4L2_INPUT_TYPE_CAMERA; + v4l2_input.audioset = 0x01; + v4l2_input.tuner = 0; + v4l2_input.std = V4L2_STD_PAL | V4L2_STD_SECAM | + V4L2_STD_NTSC ; + v4l2_input.status = 0; + JOM(8, "%i=index: %s\n", index, &v4l2_input.name[0]); + break; + } + default: { + JOM(8, "%i=index: exhausts inputs\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + + if (copy_to_user((void __user *)arg, &v4l2_input, + sizeof(struct v4l2_input))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_INPUT: { + u32 index; + + JOM(8, "VIDIOC_G_INPUT\n"); + index = (u32)peasycap->input; + JOM(8, "user is told: %i\n", index); + if (copy_to_user((void __user *)arg, &index, sizeof(u32))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_INPUT: + { + u32 index; + int rc; + + JOM(8, "VIDIOC_S_INPUT\n"); + + if (0 != copy_from_user(&index, (void __user *)arg, sizeof(u32))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + JOM(8, "user requests input %i\n", index); + + if ((int)index == peasycap->input) { + SAM("requested input already in effect\n"); + break; + } + + if ((0 > index) || (INPUT_MANY <= index)) { + JOM(8, "ERROR: bad requested input: %i\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + + rc = easycap_newinput(peasycap, (int)index); + if (0 == rc) { + JOM(8, "newinput(.,%i) OK\n", (int)index); + } else { + SAM("ERROR: newinput(.,%i) returned %i\n", (int)index, rc); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUMAUDIO: { + JOM(8, "VIDIOC_ENUMAUDIO\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUMAUDOUT: { + struct v4l2_audioout v4l2_audioout; + + JOM(8, "VIDIOC_ENUMAUDOUT\n"); + + if (copy_from_user(&v4l2_audioout, (void __user *)arg, + sizeof(struct v4l2_audioout))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (0 != v4l2_audioout.index) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + memset(&v4l2_audioout, 0, sizeof(struct v4l2_audioout)); + v4l2_audioout.index = 0; + strcpy(&v4l2_audioout.name[0], "Soundtrack"); + + if (copy_to_user((void __user *)arg, &v4l2_audioout, + sizeof(struct v4l2_audioout))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QUERYCTRL: { + int i1; + struct v4l2_queryctrl v4l2_queryctrl; + + JOM(8, "VIDIOC_QUERYCTRL\n"); + + if (0 != copy_from_user(&v4l2_queryctrl, (void __user *)arg, + sizeof(struct v4l2_queryctrl))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + i1 = 0; + while (0xFFFFFFFF != easycap_control[i1].id) { + if (easycap_control[i1].id == v4l2_queryctrl.id) { + JOM(8, "VIDIOC_QUERYCTRL %s=easycap_control[%i]" + ".name\n", &easycap_control[i1].name[0], i1); + memcpy(&v4l2_queryctrl, &easycap_control[i1], + sizeof(struct v4l2_queryctrl)); + break; + } + i1++; + } + if (0xFFFFFFFF == easycap_control[i1].id) { + JOM(8, "%i=index: exhausts controls\n", i1); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + if (copy_to_user((void __user *)arg, &v4l2_queryctrl, + sizeof(struct v4l2_queryctrl))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QUERYMENU: { + JOM(8, "VIDIOC_QUERYMENU unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_CTRL: { + struct v4l2_control *pv4l2_control; + + JOM(8, "VIDIOC_G_CTRL\n"); + pv4l2_control = memdup_user((void __user *)arg, + sizeof(struct v4l2_control)); + if (IS_ERR(pv4l2_control)) { + SAM("ERROR: copy from user failed\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return PTR_ERR(pv4l2_control); + } + + switch (pv4l2_control->id) { + case V4L2_CID_BRIGHTNESS: { + pv4l2_control->value = peasycap->brightness; + JOM(8, "user enquires brightness: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_CONTRAST: { + pv4l2_control->value = peasycap->contrast; + JOM(8, "user enquires contrast: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_SATURATION: { + pv4l2_control->value = peasycap->saturation; + JOM(8, "user enquires saturation: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_HUE: { + pv4l2_control->value = peasycap->hue; + JOM(8, "user enquires hue: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_AUDIO_VOLUME: { + pv4l2_control->value = peasycap->volume; + JOM(8, "user enquires volume: %i\n", pv4l2_control->value); + break; + } + case V4L2_CID_AUDIO_MUTE: { + if (1 == peasycap->mute) + pv4l2_control->value = true; + else + pv4l2_control->value = false; + JOM(8, "user enquires mute: %i\n", pv4l2_control->value); + break; + } + default: { + SAM("ERROR: unknown V4L2 control: 0x%08X=id\n", + pv4l2_control->id); + kfree(pv4l2_control); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + if (copy_to_user((void __user *)arg, pv4l2_control, + sizeof(struct v4l2_control))) { + kfree(pv4l2_control); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + kfree(pv4l2_control); + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_CTRL: { + struct v4l2_control v4l2_control; + + JOM(8, "VIDIOC_S_CTRL\n"); + + if (0 != copy_from_user(&v4l2_control, (void __user *)arg, + sizeof(struct v4l2_control))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + switch (v4l2_control.id) { + case V4L2_CID_BRIGHTNESS: { + JOM(8, "user requests brightness %i\n", v4l2_control.value); + if (0 != adjust_brightness(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_CONTRAST: { + JOM(8, "user requests contrast %i\n", v4l2_control.value); + if (0 != adjust_contrast(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_SATURATION: { + JOM(8, "user requests saturation %i\n", v4l2_control.value); + if (0 != adjust_saturation(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_HUE: { + JOM(8, "user requests hue %i\n", v4l2_control.value); + if (0 != adjust_hue(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_AUDIO_VOLUME: { + JOM(8, "user requests volume %i\n", v4l2_control.value); + if (0 != adjust_volume(peasycap, v4l2_control.value)) + ; + break; + } + case V4L2_CID_AUDIO_MUTE: { + int mute; + + JOM(8, "user requests mute %i\n", v4l2_control.value); + if (v4l2_control.value) + mute = 1; + else + mute = 0; + + if (0 != adjust_mute(peasycap, mute)) + SAM("WARNING: failed to adjust mute to %i\n", mute); + break; + } + default: { + SAM("ERROR: unknown V4L2 control: 0x%08X=id\n", + v4l2_control.id); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_EXT_CTRLS: { + JOM(8, "VIDIOC_S_EXT_CTRLS unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUM_FMT: { + u32 index; + struct v4l2_fmtdesc v4l2_fmtdesc; + + JOM(8, "VIDIOC_ENUM_FMT\n"); + + if (0 != copy_from_user(&v4l2_fmtdesc, (void __user *)arg, + sizeof(struct v4l2_fmtdesc))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + index = v4l2_fmtdesc.index; + memset(&v4l2_fmtdesc, 0, sizeof(struct v4l2_fmtdesc)); + + v4l2_fmtdesc.index = index; + v4l2_fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + switch (index) { + case 0: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "uyvy"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_UYVY; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 1: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "yuy2"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_YUYV; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 2: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "rgb24"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_RGB24; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 3: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "rgb32"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_RGB32; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 4: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "bgr24"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_BGR24; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + case 5: { + v4l2_fmtdesc.flags = 0; + strcpy(&v4l2_fmtdesc.description[0], "bgr32"); + v4l2_fmtdesc.pixelformat = V4L2_PIX_FMT_BGR32; + JOM(8, "%i=index: %s\n", index, &v4l2_fmtdesc.description[0]); + break; + } + default: { + JOM(8, "%i=index: exhausts formats\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + if (copy_to_user((void __user *)arg, &v4l2_fmtdesc, + sizeof(struct v4l2_fmtdesc))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* + * THE RESPONSE TO VIDIOC_ENUM_FRAMESIZES MUST BE CONDITIONED ON THE + * THE CURRENT STANDARD, BECAUSE THAT IS WHAT gstreamer EXPECTS. BEWARE. + */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUM_FRAMESIZES: { + u32 index; + struct v4l2_frmsizeenum v4l2_frmsizeenum; + + JOM(8, "VIDIOC_ENUM_FRAMESIZES\n"); + + if (0 != copy_from_user(&v4l2_frmsizeenum, (void __user *)arg, + sizeof(struct v4l2_frmsizeenum))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + index = v4l2_frmsizeenum.index; + + v4l2_frmsizeenum.type = (u32) V4L2_FRMSIZE_TYPE_DISCRETE; + + if (peasycap->ntsc) { + switch (index) { + case 0: { + v4l2_frmsizeenum.discrete.width = 640; + v4l2_frmsizeenum.discrete.height = 480; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 1: { + v4l2_frmsizeenum.discrete.width = 320; + v4l2_frmsizeenum.discrete.height = 240; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 2: { + v4l2_frmsizeenum.discrete.width = 720; + v4l2_frmsizeenum.discrete.height = 480; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 3: { + v4l2_frmsizeenum.discrete.width = 360; + v4l2_frmsizeenum.discrete.height = 240; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + default: { + JOM(8, "%i=index: exhausts framesizes\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + } else { + switch (index) { + case 0: { + v4l2_frmsizeenum.discrete.width = 640; + v4l2_frmsizeenum.discrete.height = 480; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 1: { + v4l2_frmsizeenum.discrete.width = 320; + v4l2_frmsizeenum.discrete.height = 240; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 2: { + v4l2_frmsizeenum.discrete.width = 704; + v4l2_frmsizeenum.discrete.height = 576; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 3: { + v4l2_frmsizeenum.discrete.width = 720; + v4l2_frmsizeenum.discrete.height = 576; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + case 4: { + v4l2_frmsizeenum.discrete.width = 360; + v4l2_frmsizeenum.discrete.height = 288; + JOM(8, "%i=index: %ix%i\n", index, + (int)(v4l2_frmsizeenum. + discrete.width), + (int)(v4l2_frmsizeenum. + discrete.height)); + break; + } + default: { + JOM(8, "%i=index: exhausts framesizes\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + } + if (copy_to_user((void __user *)arg, &v4l2_frmsizeenum, + sizeof(struct v4l2_frmsizeenum))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* + * THE RESPONSE TO VIDIOC_ENUM_FRAMEINTERVALS MUST BE CONDITIONED ON THE + * THE CURRENT STANDARD, BECAUSE THAT IS WHAT gstreamer EXPECTS. BEWARE. + */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_ENUM_FRAMEINTERVALS: { + u32 index; + int denominator; + struct v4l2_frmivalenum v4l2_frmivalenum; + + JOM(8, "VIDIOC_ENUM_FRAMEINTERVALS\n"); + + if (peasycap->fps) + denominator = peasycap->fps; + else { + if (peasycap->ntsc) + denominator = 30; + else + denominator = 25; + } + + if (0 != copy_from_user(&v4l2_frmivalenum, (void __user *)arg, + sizeof(struct v4l2_frmivalenum))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + index = v4l2_frmivalenum.index; + + v4l2_frmivalenum.type = (u32) V4L2_FRMIVAL_TYPE_DISCRETE; + + switch (index) { + case 0: { + v4l2_frmivalenum.discrete.numerator = 1; + v4l2_frmivalenum.discrete.denominator = denominator; + JOM(8, "%i=index: %i/%i\n", index, + (int)(v4l2_frmivalenum.discrete.numerator), + (int)(v4l2_frmivalenum.discrete.denominator)); + break; + } + case 1: { + v4l2_frmivalenum.discrete.numerator = 1; + v4l2_frmivalenum.discrete.denominator = denominator/5; + JOM(8, "%i=index: %i/%i\n", index, + (int)(v4l2_frmivalenum.discrete.numerator), + (int)(v4l2_frmivalenum.discrete.denominator)); + break; + } + default: { + JOM(8, "%i=index: exhausts frameintervals\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + } + if (copy_to_user((void __user *)arg, &v4l2_frmivalenum, + sizeof(struct v4l2_frmivalenum))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_FMT: { + struct v4l2_format *pv4l2_format; + struct v4l2_pix_format *pv4l2_pix_format; + + JOM(8, "VIDIOC_G_FMT\n"); + pv4l2_format = kzalloc(sizeof(struct v4l2_format), GFP_KERNEL); + if (!pv4l2_format) { + SAM("ERROR: out of memory\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOMEM; + } + pv4l2_pix_format = kzalloc(sizeof(struct v4l2_pix_format), GFP_KERNEL); + if (!pv4l2_pix_format) { + SAM("ERROR: out of memory\n"); + kfree(pv4l2_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOMEM; + } + if (0 != copy_from_user(pv4l2_format, (void __user *)arg, + sizeof(struct v4l2_format))) { + kfree(pv4l2_format); + kfree(pv4l2_pix_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (pv4l2_format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + kfree(pv4l2_format); + kfree(pv4l2_pix_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + + memset(pv4l2_pix_format, 0, sizeof(struct v4l2_pix_format)); + pv4l2_format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + memcpy(&pv4l2_format->fmt.pix, + &easycap_format[peasycap->format_offset] + .v4l2_format.fmt.pix, sizeof(struct v4l2_pix_format)); + JOM(8, "user is told: %s\n", + &easycap_format[peasycap->format_offset].name[0]); + + if (copy_to_user((void __user *)arg, pv4l2_format, + sizeof(struct v4l2_format))) { + kfree(pv4l2_format); + kfree(pv4l2_pix_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + kfree(pv4l2_format); + kfree(pv4l2_pix_format); + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_TRY_FMT: + case VIDIOC_S_FMT: { + struct v4l2_format v4l2_format; + struct v4l2_pix_format v4l2_pix_format; + bool try; + int best_format; + + if (VIDIOC_TRY_FMT == cmd) { + JOM(8, "VIDIOC_TRY_FMT\n"); + try = true; + } else { + JOM(8, "VIDIOC_S_FMT\n"); + try = false; + } + + if (0 != copy_from_user(&v4l2_format, (void __user *)arg, + sizeof(struct v4l2_format))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + best_format = adjust_format(peasycap, + v4l2_format.fmt.pix.width, + v4l2_format.fmt.pix.height, + v4l2_format.fmt.pix.pixelformat, + v4l2_format.fmt.pix.field, + try); + if (0 > best_format) { + if (-EBUSY == best_format) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EBUSY; + } + JOM(8, "WARNING: adjust_format() returned %i\n", best_format); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOENT; + } +/*...........................................................................*/ + memset(&v4l2_pix_format, 0, sizeof(struct v4l2_pix_format)); + v4l2_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + memcpy(&(v4l2_format.fmt.pix), + &(easycap_format[best_format].v4l2_format.fmt.pix), + sizeof(v4l2_pix_format)); + JOM(8, "user is told: %s\n", &easycap_format[best_format].name[0]); + + if (copy_to_user((void __user *)arg, &v4l2_format, + sizeof(struct v4l2_format))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_CROPCAP: { + struct v4l2_cropcap v4l2_cropcap; + + JOM(8, "VIDIOC_CROPCAP\n"); + + if (0 != copy_from_user(&v4l2_cropcap, (void __user *)arg, + sizeof(struct v4l2_cropcap))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_cropcap.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + JOM(8, "v4l2_cropcap.type != V4L2_BUF_TYPE_VIDEO_CAPTURE\n"); + + memset(&v4l2_cropcap, 0, sizeof(struct v4l2_cropcap)); + v4l2_cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_cropcap.bounds.left = 0; + v4l2_cropcap.bounds.top = 0; + v4l2_cropcap.bounds.width = peasycap->width; + v4l2_cropcap.bounds.height = peasycap->height; + v4l2_cropcap.defrect.left = 0; + v4l2_cropcap.defrect.top = 0; + v4l2_cropcap.defrect.width = peasycap->width; + v4l2_cropcap.defrect.height = peasycap->height; + v4l2_cropcap.pixelaspect.numerator = 1; + v4l2_cropcap.pixelaspect.denominator = 1; + + JOM(8, "user is told: %ix%i\n", peasycap->width, peasycap->height); + + if (copy_to_user((void __user *)arg, &v4l2_cropcap, + sizeof(struct v4l2_cropcap))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_CROP: + case VIDIOC_S_CROP: { + JOM(8, "VIDIOC_G_CROP|VIDIOC_S_CROP unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QUERYSTD: { + JOM(8, "VIDIOC_QUERYSTD: " + "EasyCAP is incapable of detecting standard\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + break; + } + /*-------------------------------------------------------------------*/ + /* + * THE MANIPULATIONS INVOLVING last0,last1,last2,last3 + * CONSTITUTE A WORKAROUND * FOR WHAT APPEARS TO BE + * A BUG IN 64-BIT mplayer. + * NOT NEEDED, BUT HOPEFULLY HARMLESS, FOR 32-BIT mplayer. + */ + /*------------------------------------------------------------------*/ + case VIDIOC_ENUMSTD: { + int last0 = -1, last1 = -1, last2 = -1, last3 = -1; + struct v4l2_standard v4l2_standard; + u32 index; + struct easycap_standard const *peasycap_standard; + + JOM(8, "VIDIOC_ENUMSTD\n"); + + if (0 != copy_from_user(&v4l2_standard, (void __user *)arg, + sizeof(struct v4l2_standard))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + index = v4l2_standard.index; + + last3 = last2; + last2 = last1; + last1 = last0; + last0 = index; + if ((index == last3) && (index == last2) && + (index == last1) && (index == last0)) { + index++; + last3 = last2; + last2 = last1; + last1 = last0; + last0 = index; + } + + memset(&v4l2_standard, 0, sizeof(struct v4l2_standard)); + + peasycap_standard = &easycap_standard[0]; + while (0xFFFF != peasycap_standard->mask) { + if ((int)(peasycap_standard - &easycap_standard[0]) == index) + break; + peasycap_standard++; + } + if (0xFFFF == peasycap_standard->mask) { + JOM(8, "%i=index: exhausts standards\n", index); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + JOM(8, "%i=index: %s\n", index, + &(peasycap_standard->v4l2_standard.name[0])); + memcpy(&v4l2_standard, &(peasycap_standard->v4l2_standard), + sizeof(struct v4l2_standard)); + + v4l2_standard.index = index; + + if (copy_to_user((void __user *)arg, &v4l2_standard, + sizeof(struct v4l2_standard))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_STD: { + v4l2_std_id std_id; + struct easycap_standard const *peasycap_standard; + + JOM(8, "VIDIOC_G_STD\n"); + + if (0 > peasycap->standard_offset) { + JOM(8, "%i=peasycap->standard_offset\n", + peasycap->standard_offset); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EBUSY; + } + + if (0 != copy_from_user(&std_id, (void __user *)arg, + sizeof(v4l2_std_id))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + peasycap_standard = &easycap_standard[peasycap->standard_offset]; + std_id = peasycap_standard->v4l2_standard.id; + + JOM(8, "user is told: %s\n", + &peasycap_standard->v4l2_standard.name[0]); + + if (copy_to_user((void __user *)arg, &std_id, + sizeof(v4l2_std_id))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_STD: { + v4l2_std_id std_id; + int rc; + + JOM(8, "VIDIOC_S_STD\n"); + + if (0 != copy_from_user(&std_id, (void __user *)arg, + sizeof(v4l2_std_id))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + JOM(8, "User requests standard: 0x%08X%08X\n", + (int)((std_id & (((v4l2_std_id)0xFFFFFFFF) << 32)) >> 32), + (int)(std_id & ((v4l2_std_id)0xFFFFFFFF))); + + rc = adjust_standard(peasycap, std_id); + if (0 > rc) { + JOM(8, "WARNING: adjust_standard() returned %i\n", rc); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOENT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_REQBUFS: { + int nbuffers; + struct v4l2_requestbuffers v4l2_requestbuffers; + + JOM(8, "VIDIOC_REQBUFS\n"); + + if (0 != copy_from_user(&v4l2_requestbuffers, + (void __user *)arg, + sizeof(struct v4l2_requestbuffers))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_requestbuffers.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + if (v4l2_requestbuffers.memory != V4L2_MEMORY_MMAP) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + nbuffers = v4l2_requestbuffers.count; + JOM(8, " User requests %i buffers ...\n", nbuffers); + if (nbuffers < 2) + nbuffers = 2; + if (nbuffers > FRAME_BUFFER_MANY) + nbuffers = FRAME_BUFFER_MANY; + if (v4l2_requestbuffers.count == nbuffers) { + JOM(8, " ... agree to %i buffers\n", + nbuffers); + } else { + JOM(8, " ... insist on %i buffers\n", + nbuffers); + v4l2_requestbuffers.count = nbuffers; + } + peasycap->frame_buffer_many = nbuffers; + + if (copy_to_user((void __user *)arg, &v4l2_requestbuffers, + sizeof(struct v4l2_requestbuffers))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QUERYBUF: { + u32 index; + struct v4l2_buffer v4l2_buffer; + + JOM(8, "VIDIOC_QUERYBUF\n"); + + if (peasycap->video_eof) { + JOM(8, "returning -EIO because %i=video_eof\n", + peasycap->video_eof); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + + if (0 != copy_from_user(&v4l2_buffer, (void __user *)arg, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_buffer.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + index = v4l2_buffer.index; + if (index < 0 || index >= peasycap->frame_buffer_many) + return -EINVAL; + memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer)); + v4l2_buffer.index = index; + v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_buffer.bytesused = peasycap->frame_buffer_used; + v4l2_buffer.flags = V4L2_BUF_FLAG_MAPPED | + peasycap->done[index] | + peasycap->queued[index]; + v4l2_buffer.field = V4L2_FIELD_NONE; + v4l2_buffer.memory = V4L2_MEMORY_MMAP; + v4l2_buffer.m.offset = index * FRAME_BUFFER_SIZE; + v4l2_buffer.length = FRAME_BUFFER_SIZE; + + JOM(16, " %10i=index\n", v4l2_buffer.index); + JOM(16, " 0x%08X=type\n", v4l2_buffer.type); + JOM(16, " %10i=bytesused\n", v4l2_buffer.bytesused); + JOM(16, " 0x%08X=flags\n", v4l2_buffer.flags); + JOM(16, " %10i=field\n", v4l2_buffer.field); + JOM(16, " %10li=timestamp.tv_usec\n", + (long)v4l2_buffer.timestamp.tv_usec); + JOM(16, " %10i=sequence\n", v4l2_buffer.sequence); + JOM(16, " 0x%08X=memory\n", v4l2_buffer.memory); + JOM(16, " %10i=m.offset\n", v4l2_buffer.m.offset); + JOM(16, " %10i=length\n", v4l2_buffer.length); + + if (copy_to_user((void __user *)arg, &v4l2_buffer, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_QBUF: { + struct v4l2_buffer v4l2_buffer; + + JOM(8, "VIDIOC_QBUF\n"); + + if (0 != copy_from_user(&v4l2_buffer, (void __user *)arg, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_buffer.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + if (v4l2_buffer.memory != V4L2_MEMORY_MMAP) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + if (v4l2_buffer.index < 0 || + v4l2_buffer.index >= peasycap->frame_buffer_many) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + v4l2_buffer.flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED; + + peasycap->done[v4l2_buffer.index] = 0; + peasycap->queued[v4l2_buffer.index] = V4L2_BUF_FLAG_QUEUED; + + if (copy_to_user((void __user *)arg, &v4l2_buffer, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + JOM(8, "..... user queueing frame buffer %i\n", + (int)v4l2_buffer.index); + + peasycap->frame_lock = 0; + + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_DQBUF: + { + struct timeval timeval, timeval2; + int i, j; + struct v4l2_buffer v4l2_buffer; + int rcdq; + u16 input; + + JOM(8, "VIDIOC_DQBUF\n"); + + if ((peasycap->video_idle) || (peasycap->video_eof)) { + JOM(8, "returning -EIO because " + "%i=video_idle %i=video_eof\n", + peasycap->video_idle, peasycap->video_eof); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + + if (copy_from_user(&v4l2_buffer, (void __user *)arg, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + if (v4l2_buffer.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + + if (peasycap->offerfields) { + /*---------------------------------------------------*/ + /* + * IN ITS 50 "fps" MODE tvtime SEEMS ALWAYS TO REQUEST + * V4L2_FIELD_BOTTOM + */ + /*---------------------------------------------------*/ + if (V4L2_FIELD_TOP == v4l2_buffer.field) + JOM(8, "user wants V4L2_FIELD_TOP\n"); + else if (V4L2_FIELD_BOTTOM == v4l2_buffer.field) + JOM(8, "user wants V4L2_FIELD_BOTTOM\n"); + else if (V4L2_FIELD_ANY == v4l2_buffer.field) + JOM(8, "user wants V4L2_FIELD_ANY\n"); + else + JOM(8, "user wants V4L2_FIELD_...UNKNOWN: %i\n", + v4l2_buffer.field); + } + + if (!peasycap->video_isoc_streaming) { + JOM(16, "returning -EIO because video urbs not streaming\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + /*-------------------------------------------------------------------*/ + /* + * IF THE USER HAS PREVIOUSLY CALLED easycap_poll(), + * AS DETERMINED BY FINDING + * THE FLAG peasycap->polled SET, THERE MUST BE + * NO FURTHER WAIT HERE. IN THIS + * CASE, JUST CHOOSE THE FRAME INDICATED BY peasycap->frame_read + */ + /*-------------------------------------------------------------------*/ + + if (!peasycap->polled) { + do { + rcdq = easycap_video_dqbuf(peasycap, 0); + if (-EIO == rcdq) { + JOM(8, "returning -EIO because " + "dqbuf() returned -EIO\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + } while (0 != rcdq); + } else { + if (peasycap->video_eof) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EIO; + } + } + if (V4L2_BUF_FLAG_DONE != peasycap->done[peasycap->frame_read]) { + JOM(8, "V4L2_BUF_FLAG_DONE != 0x%08X\n", + peasycap->done[peasycap->frame_read]); + } + peasycap->polled = 0; + + if (!(peasycap->isequence % 10)) { + for (i = 0; i < 179; i++) + peasycap->merit[i] = peasycap->merit[i+1]; + peasycap->merit[179] = merit_saa(peasycap->pusb_device); + j = 0; + for (i = 0; i < 180; i++) + j += peasycap->merit[i]; + if (90 < j) { + SAM("easycap driver shutting down " + "on condition blue\n"); + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + } + } + + v4l2_buffer.index = peasycap->frame_read; + v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_buffer.bytesused = peasycap->frame_buffer_used; + v4l2_buffer.flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE; + if (peasycap->offerfields) + v4l2_buffer.field = V4L2_FIELD_BOTTOM; + else + v4l2_buffer.field = V4L2_FIELD_NONE; + do_gettimeofday(&timeval); + timeval2 = timeval; + + v4l2_buffer.timestamp = timeval2; + v4l2_buffer.sequence = peasycap->isequence++; + v4l2_buffer.memory = V4L2_MEMORY_MMAP; + v4l2_buffer.m.offset = v4l2_buffer.index * FRAME_BUFFER_SIZE; + v4l2_buffer.length = FRAME_BUFFER_SIZE; + + JOM(16, " %10i=index\n", v4l2_buffer.index); + JOM(16, " 0x%08X=type\n", v4l2_buffer.type); + JOM(16, " %10i=bytesused\n", v4l2_buffer.bytesused); + JOM(16, " 0x%08X=flags\n", v4l2_buffer.flags); + JOM(16, " %10i=field\n", v4l2_buffer.field); + JOM(16, " %10li=timestamp.tv_sec\n", + (long)v4l2_buffer.timestamp.tv_sec); + JOM(16, " %10li=timestamp.tv_usec\n", + (long)v4l2_buffer.timestamp.tv_usec); + JOM(16, " %10i=sequence\n", v4l2_buffer.sequence); + JOM(16, " 0x%08X=memory\n", v4l2_buffer.memory); + JOM(16, " %10i=m.offset\n", v4l2_buffer.m.offset); + JOM(16, " %10i=length\n", v4l2_buffer.length); + + if (copy_to_user((void __user *)arg, &v4l2_buffer, + sizeof(struct v4l2_buffer))) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + input = peasycap->frame_buffer[peasycap->frame_read][0].input; + if (0x08 & input) { + JOM(8, "user is offered frame buffer %i, input %i\n", + peasycap->frame_read, (0x07 & input)); + } else { + JOM(8, "user is offered frame buffer %i\n", + peasycap->frame_read); + } + peasycap->frame_lock = 1; + JOM(8, "%i=peasycap->frame_fill\n", peasycap->frame_fill); + if (peasycap->frame_read == peasycap->frame_fill) { + if (peasycap->frame_lock) { + JOM(8, "WORRY: filling frame buffer " + "while offered to user\n"); + } + } + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_STREAMON: { + int i; + + JOM(8, "VIDIOC_STREAMON\n"); + + peasycap->isequence = 0; + for (i = 0; i < 180; i++) + peasycap->merit[i] = 0; + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + easycap_video_submit_urbs(peasycap); + peasycap->video_idle = 0; + peasycap->audio_idle = 0; + peasycap->video_eof = 0; + peasycap->audio_eof = 0; + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_STREAMOFF: { + JOM(8, "VIDIOC_STREAMOFF\n"); + + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + + peasycap->video_idle = 1; + peasycap->audio_idle = 1; +/*---------------------------------------------------------------------------*/ +/* + * IF THE WAIT QUEUES ARE NOT CLEARED IN RESPONSE TO THE STREAMOFF COMMAND + * THE USERSPACE PROGRAM, E.G. mplayer, MAY HANG ON EXIT. BEWARE. + */ +/*---------------------------------------------------------------------------*/ + JOM(8, "calling wake_up on wq_video and wq_audio\n"); + wake_up_interruptible(&(peasycap->wq_video)); + if (peasycap->psubstream) + snd_pcm_period_elapsed(peasycap->psubstream); + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_PARM: { + struct v4l2_streamparm *pv4l2_streamparm; + + JOM(8, "VIDIOC_G_PARM\n"); + pv4l2_streamparm = memdup_user((void __user *)arg, + sizeof(struct v4l2_streamparm)); + if (IS_ERR(pv4l2_streamparm)) { + SAM("ERROR: copy from user failed\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return PTR_ERR(pv4l2_streamparm); + } + + if (pv4l2_streamparm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + kfree(pv4l2_streamparm); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + pv4l2_streamparm->parm.capture.capability = 0; + pv4l2_streamparm->parm.capture.capturemode = 0; + pv4l2_streamparm->parm.capture.timeperframe.numerator = 1; + + if (peasycap->fps) { + pv4l2_streamparm->parm.capture.timeperframe. + denominator = peasycap->fps; + } else { + if (peasycap->ntsc) { + pv4l2_streamparm->parm.capture.timeperframe. + denominator = 30; + } else { + pv4l2_streamparm->parm.capture.timeperframe. + denominator = 25; + } + } + + pv4l2_streamparm->parm.capture.readbuffers = + peasycap->frame_buffer_many; + pv4l2_streamparm->parm.capture.extendedmode = 0; + if (copy_to_user((void __user *)arg, + pv4l2_streamparm, + sizeof(struct v4l2_streamparm))) { + kfree(pv4l2_streamparm); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EFAULT; + } + kfree(pv4l2_streamparm); + break; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_PARM: { + JOM(8, "VIDIOC_S_PARM unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_AUDIO: { + JOM(8, "VIDIOC_G_AUDIO unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_AUDIO: { + JOM(8, "VIDIOC_S_AUDIO unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_S_TUNER: { + JOM(8, "VIDIOC_S_TUNER unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_FBUF: + case VIDIOC_S_FBUF: + case VIDIOC_OVERLAY: { + JOM(8, "VIDIOC_G_FBUF|VIDIOC_S_FBUF|VIDIOC_OVERLAY unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + case VIDIOC_G_TUNER: { + JOM(8, "VIDIOC_G_TUNER unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: { + JOM(8, "VIDIOC_G_FREQUENCY|VIDIOC_S_FREQUENCY unsupported\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -EINVAL; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + default: { + JOM(8, "ERROR: unrecognized V4L2 IOCTL command: 0x%08X\n", cmd); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ENOIOCTLCMD; + } + } + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + JOM(4, "unlocked easycapdc60_dongle[%i].mutex_video\n", kd); + return 0; +} +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_low.c b/drivers/staging/media/easycap/easycap_low.c new file mode 100644 index 00000000..0380babe --- /dev/null +++ b/drivers/staging/media/easycap/easycap_low.c @@ -0,0 +1,968 @@ +/***************************************************************************** +* * +* * +* easycap_low.c * +* * +* * +*****************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas + * + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ +/* + * ACKNOWLEGEMENTS AND REFERENCES + * ------------------------------ + * This driver makes use of register information contained in the Syntek + * Semicon DC-1125 driver hosted at + * http://sourceforge.net/projects/syntekdriver/. + * Particularly useful has been a patch to the latter driver provided by + * Ivor Hewitt in January 2009. The NTSC implementation is taken from the + * work of Ben Trask. +*/ +/****************************************************************************/ + +#include "easycap.h" + + +#define GET(X, Y, Z) do { \ + int __rc; \ + *(Z) = (u16)0; \ + __rc = regget(X, Y, Z, sizeof(u8)); \ + if (0 > __rc) { \ + JOT(8, ":-(%i\n", __LINE__); return __rc; \ + } \ +} while (0) + +#define SET(X, Y, Z) do { \ + int __rc; \ + __rc = regset(X, Y, Z); \ + if (0 > __rc) { \ + JOT(8, ":-(%i\n", __LINE__); return __rc; \ + } \ +} while (0) + +/*--------------------------------------------------------------------------*/ +static const struct stk1160config { + u16 reg; + u16 set; +} stk1160configPAL[] = { + {0x000, 0x0098}, + {0x002, 0x0093}, + + {0x001, 0x0003}, + {0x003, 0x0080}, + {0x00D, 0x0000}, + {0x00F, 0x0002}, + {0x018, 0x0010}, + {0x019, 0x0000}, + {0x01A, 0x0014}, + {0x01B, 0x000E}, + {0x01C, 0x0046}, + + {0x100, 0x0033}, + {0x103, 0x0000}, + {0x104, 0x0000}, + {0x105, 0x0000}, + {0x106, 0x0000}, + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* + * RESOLUTION 640x480 +*/ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + {0x110, 0x0008}, + {0x111, 0x0000}, + {0x112, 0x0020}, + {0x113, 0x0000}, + {0x114, 0x0508}, + {0x115, 0x0005}, + {0x116, 0x0110}, + {0x117, 0x0001}, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + {0x202, 0x000F}, + {0x203, 0x004A}, + {0x2FF, 0x0000}, + + {0xFFF, 0xFFFF} +}; +/*--------------------------------------------------------------------------*/ +static const struct stk1160config stk1160configNTSC[] = { + {0x000, 0x0098}, + {0x002, 0x0093}, + + {0x001, 0x0003}, + {0x003, 0x0080}, + {0x00D, 0x0000}, + {0x00F, 0x0002}, + {0x018, 0x0010}, + {0x019, 0x0000}, + {0x01A, 0x0014}, + {0x01B, 0x000E}, + {0x01C, 0x0046}, + + {0x100, 0x0033}, + {0x103, 0x0000}, + {0x104, 0x0000}, + {0x105, 0x0000}, + {0x106, 0x0000}, + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* + * RESOLUTION 640x480 +*/ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + {0x110, 0x0008}, + {0x111, 0x0000}, + {0x112, 0x0003}, + {0x113, 0x0000}, + {0x114, 0x0508}, + {0x115, 0x0005}, + {0x116, 0x00F3}, + {0x117, 0x0000}, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + {0x202, 0x000F}, + {0x203, 0x004A}, + {0x2FF, 0x0000}, + + {0xFFF, 0xFFFF} +}; +/*--------------------------------------------------------------------------*/ +static const struct saa7113config { + u8 reg; + u8 set; +} saa7113configPAL[] = { + {0x01, 0x08}, + {0x02, 0x80}, + {0x03, 0x33}, + {0x04, 0x00}, + {0x05, 0x00}, + {0x06, 0xE9}, + {0x07, 0x0D}, + {0x08, 0x38}, + {0x09, 0x00}, + {0x0A, SAA_0A_DEFAULT}, + {0x0B, SAA_0B_DEFAULT}, + {0x0C, SAA_0C_DEFAULT}, + {0x0D, SAA_0D_DEFAULT}, + {0x0E, 0x01}, + {0x0F, 0x36}, + {0x10, 0x00}, + {0x11, 0x0C}, + {0x12, 0xE7}, + {0x13, 0x00}, + {0x15, 0x00}, + {0x16, 0x00}, + {0x40, 0x02}, + {0x41, 0xFF}, + {0x42, 0xFF}, + {0x43, 0xFF}, + {0x44, 0xFF}, + {0x45, 0xFF}, + {0x46, 0xFF}, + {0x47, 0xFF}, + {0x48, 0xFF}, + {0x49, 0xFF}, + {0x4A, 0xFF}, + {0x4B, 0xFF}, + {0x4C, 0xFF}, + {0x4D, 0xFF}, + {0x4E, 0xFF}, + {0x4F, 0xFF}, + {0x50, 0xFF}, + {0x51, 0xFF}, + {0x52, 0xFF}, + {0x53, 0xFF}, + {0x54, 0xFF}, + {0x55, 0xFF}, + {0x56, 0xFF}, + {0x57, 0xFF}, + {0x58, 0x40}, + {0x59, 0x54}, + {0x5A, 0x07}, + {0x5B, 0x83}, + + {0xFF, 0xFF} +}; +/*--------------------------------------------------------------------------*/ +static const struct saa7113config saa7113configNTSC[] = { + {0x01, 0x08}, + {0x02, 0x80}, + {0x03, 0x33}, + {0x04, 0x00}, + {0x05, 0x00}, + {0x06, 0xE9}, + {0x07, 0x0D}, + {0x08, 0x78}, + {0x09, 0x00}, + {0x0A, SAA_0A_DEFAULT}, + {0x0B, SAA_0B_DEFAULT}, + {0x0C, SAA_0C_DEFAULT}, + {0x0D, SAA_0D_DEFAULT}, + {0x0E, 0x01}, + {0x0F, 0x36}, + {0x10, 0x00}, + {0x11, 0x0C}, + {0x12, 0xE7}, + {0x13, 0x00}, + {0x15, 0x00}, + {0x16, 0x00}, + {0x40, 0x82}, + {0x41, 0xFF}, + {0x42, 0xFF}, + {0x43, 0xFF}, + {0x44, 0xFF}, + {0x45, 0xFF}, + {0x46, 0xFF}, + {0x47, 0xFF}, + {0x48, 0xFF}, + {0x49, 0xFF}, + {0x4A, 0xFF}, + {0x4B, 0xFF}, + {0x4C, 0xFF}, + {0x4D, 0xFF}, + {0x4E, 0xFF}, + {0x4F, 0xFF}, + {0x50, 0xFF}, + {0x51, 0xFF}, + {0x52, 0xFF}, + {0x53, 0xFF}, + {0x54, 0xFF}, + {0x55, 0xFF}, + {0x56, 0xFF}, + {0x57, 0xFF}, + {0x58, 0x40}, + {0x59, 0x54}, + {0x5A, 0x0A}, + {0x5B, 0x83}, + + {0xFF, 0xFF} +}; + +static int regget(struct usb_device *pusb_device, + u16 index, void *reg, int reg_size) +{ + int rc; + + if (!pusb_device) + return -ENODEV; + + rc = usb_control_msg(pusb_device, usb_rcvctrlpipe(pusb_device, 0), + 0x00, + (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE), + 0x00, + index, reg, reg_size, 50000); + + return rc; +} + +static int regset(struct usb_device *pusb_device, u16 index, u16 value) +{ + int rc; + + if (!pusb_device) + return -ENODEV; + + rc = usb_control_msg(pusb_device, usb_sndctrlpipe(pusb_device, 0), + 0x01, + (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE), + value, index, NULL, 0, 500); + + if (rc < 0) + return rc; + + if (easycap_readback) { + u16 igot = 0; + rc = regget(pusb_device, index, &igot, sizeof(igot)); + igot = 0xFF & igot; + switch (index) { + case 0x000: + case 0x500: + case 0x502: + case 0x503: + case 0x504: + case 0x506: + case 0x507: + break; + + case 0x204: + case 0x205: + case 0x350: + case 0x351: + if (igot) + JOT(8, "unexpected 0x%02X " + "for STK register 0x%03X\n", + igot, index); + break; + + default: + if ((0xFF & value) != igot) + JOT(8, "unexpected 0x%02X != 0x%02X " + "for STK register 0x%03X\n", + igot, value, index); + break; + } + } + + return rc; +} +/*--------------------------------------------------------------------------*/ +/* + * FUNCTION wait_i2c() RETURNS 0 ON SUCCESS +*/ +/*--------------------------------------------------------------------------*/ +static int wait_i2c(struct usb_device *p) +{ + u16 get0; + u8 igot; + const int max = 2; + int k; + + if (!p) + return -ENODEV; + + for (k = 0; k < max; k++) { + GET(p, 0x0201, &igot); get0 = igot; + switch (get0) { + case 0x04: + case 0x01: + return 0; + case 0x00: + msleep(20); + continue; + default: + return get0 - 1; + } + } + return -1; +} + +/****************************************************************************/ +int write_saa(struct usb_device *p, u16 reg0, u16 set0) +{ + if (!p) + return -ENODEV; + SET(p, 0x200, 0x00); + SET(p, 0x204, reg0); + SET(p, 0x205, set0); + SET(p, 0x200, 0x01); + return wait_i2c(p); +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * REGISTER 500: SETTING VALUE TO 0x008B READS FROM VT1612A (?) + * REGISTER 500: SETTING VALUE TO 0x008C WRITES TO VT1612A + * REGISTER 502: LEAST SIGNIFICANT BYTE OF VALUE TO SET + * REGISTER 503: MOST SIGNIFICANT BYTE OF VALUE TO SET + * REGISTER 504: TARGET ADDRESS ON VT1612A + */ +/*--------------------------------------------------------------------------*/ +static int write_vt(struct usb_device *p, u16 reg0, u16 set0) +{ + u8 igot; + u16 got502, got503; + u16 set502, set503; + + if (!p) + return -ENODEV; + SET(p, 0x0504, reg0); + SET(p, 0x0500, 0x008B); + + GET(p, 0x0502, &igot); got502 = (0xFF & igot); + GET(p, 0x0503, &igot); got503 = (0xFF & igot); + + JOT(16, "write_vt(., 0x%04X, 0x%04X): was 0x%04X\n", + reg0, set0, ((got503 << 8) | got502)); + + set502 = (0x00FF & set0); + set503 = ((0xFF00 & set0) >> 8); + + SET(p, 0x0504, reg0); + SET(p, 0x0502, set502); + SET(p, 0x0503, set503); + SET(p, 0x0500, 0x008C); + + return 0; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * REGISTER 500: SETTING VALUE TO 0x008B READS FROM VT1612A (?) + * REGISTER 500: SETTING VALUE TO 0x008C WRITES TO VT1612A + * REGISTER 502: LEAST SIGNIFICANT BYTE OF VALUE TO GET + * REGISTER 503: MOST SIGNIFICANT BYTE OF VALUE TO GET + * REGISTER 504: TARGET ADDRESS ON VT1612A + */ +/*--------------------------------------------------------------------------*/ +static int read_vt(struct usb_device *p, u16 reg0) +{ + u8 igot; + u16 got502, got503; + + if (!p) + return -ENODEV; + SET(p, 0x0504, reg0); + SET(p, 0x0500, 0x008B); + + GET(p, 0x0502, &igot); got502 = (0xFF & igot); + GET(p, 0x0503, &igot); got503 = (0xFF & igot); + + JOT(16, "read_vt(., 0x%04X): has 0x%04X\n", + reg0, ((got503 << 8) | got502)); + + return (got503 << 8) | got502; +} +/****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * THESE APPEAR TO HAVE NO EFFECT ON EITHER VIDEO OR AUDIO. + */ +/*--------------------------------------------------------------------------*/ +static int write_300(struct usb_device *p) +{ + if (!p) + return -ENODEV; + SET(p, 0x300, 0x0012); + SET(p, 0x350, 0x002D); + SET(p, 0x351, 0x0001); + SET(p, 0x352, 0x0000); + SET(p, 0x353, 0x0000); + SET(p, 0x300, 0x0080); + return 0; +} +/****************************************************************************/ +/****************************************************************************/ +int setup_stk(struct usb_device *p, bool ntsc) +{ + int i; + const struct stk1160config *cfg; + if (!p) + return -ENODEV; + cfg = (ntsc) ? stk1160configNTSC : stk1160configPAL; + for (i = 0; cfg[i].reg != 0xFFF; i++) + SET(p, cfg[i].reg, cfg[i].set); + + write_300(p); + + return 0; +} +/****************************************************************************/ +int setup_saa(struct usb_device *p, bool ntsc) +{ + int i, rc; + const struct saa7113config *cfg; + if (!p) + return -ENODEV; + cfg = (ntsc) ? saa7113configNTSC : saa7113configPAL; + for (i = 0; cfg[i].reg != 0xFF; i++) { + rc = write_saa(p, cfg[i].reg, cfg[i].set); + if (rc) + dev_err(&p->dev, + "Failed to set SAA register %d", cfg[i].reg); + } + return 0; +} +/****************************************************************************/ +int merit_saa(struct usb_device *p) +{ + int rc; + + if (!p) + return -ENODEV; + rc = read_saa(p, 0x1F); + return ((0 > rc) || (0x02 & rc)) ? 1 : 0; +} +/****************************************************************************/ +int ready_saa(struct usb_device *p) +{ + int j, rc, rate; + const int max = 5, marktime = PATIENCE/5; +/*--------------------------------------------------------------------------*/ +/* + * RETURNS 0 FOR INTERLACED 50 Hz + * 1 FOR NON-INTERLACED 50 Hz + * 2 FOR INTERLACED 60 Hz + * 3 FOR NON-INTERLACED 60 Hz +*/ +/*--------------------------------------------------------------------------*/ + if (!p) + return -ENODEV; + j = 0; + while (max > j) { + rc = read_saa(p, 0x1F); + if (0 <= rc) { + if (0 == (0x40 & rc)) + break; + if (1 == (0x01 & rc)) + break; + } + msleep(marktime); + j++; + } + + if (max == j) + return -1; + + if (0x20 & rc) { + rate = 2; + JOT(8, "hardware detects 60 Hz\n"); + } else { + rate = 0; + JOT(8, "hardware detects 50 Hz\n"); + } + if (0x80 & rc) + JOT(8, "hardware detects interlacing\n"); + else { + rate++; + JOT(8, "hardware detects no interlacing\n"); + } + return 0; +} +/****************************************************************************/ +int read_saa(struct usb_device *p, u16 reg0) +{ + u8 igot; + + if (!p) + return -ENODEV; + SET(p, 0x208, reg0); + SET(p, 0x200, 0x20); + if (0 != wait_i2c(p)) + return -1; + igot = 0; + GET(p, 0x0209, &igot); + return igot; +} +/****************************************************************************/ +static int read_stk(struct usb_device *p, u32 reg0) +{ + u8 igot; + + if (!p) + return -ENODEV; + igot = 0; + GET(p, reg0, &igot); + return igot; +} +int select_input(struct usb_device *p, int input, int mode) +{ + int ir; + + if (!p) + return -ENODEV; + stop_100(p); + switch (input) { + case 0: + case 1: { + if (0 != write_saa(p, 0x02, 0x80)) + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + + SET(p, 0x0000, 0x0098); + SET(p, 0x0002, 0x0078); + break; + } + case 2: { + if (0 != write_saa(p, 0x02, 0x80)) + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + + SET(p, 0x0000, 0x0090); + SET(p, 0x0002, 0x0078); + break; + } + case 3: { + if (0 != write_saa(p, 0x02, 0x80)) + SAY("ERROR: failed to set SAA register 0x02 " + " for input %i\n", input); + + SET(p, 0x0000, 0x0088); + SET(p, 0x0002, 0x0078); + break; + } + case 4: { + if (0 != write_saa(p, 0x02, 0x80)) { + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + } + SET(p, 0x0000, 0x0080); + SET(p, 0x0002, 0x0078); + break; + } + case 5: { + if (9 != mode) + mode = 7; + switch (mode) { + case 7: { + if (0 != write_saa(p, 0x02, 0x87)) + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + + if (0 != write_saa(p, 0x05, 0xFF)) + SAY("ERROR: failed to set SAA register 0x05 " + "for input %i\n", input); + + break; + } + case 9: { + if (0 != write_saa(p, 0x02, 0x89)) + SAY("ERROR: failed to set SAA register 0x02 " + "for input %i\n", input); + + if (0 != write_saa(p, 0x05, 0x00)) + SAY("ERROR: failed to set SAA register 0x05 " + "for input %i\n", input); + + break; + } + default: + SAY("MISTAKE: bad mode: %i\n", mode); + return -1; + } + + if (0 != write_saa(p, 0x04, 0x00)) + SAY("ERROR: failed to set SAA register 0x04 " + "for input %i\n", input); + + if (0 != write_saa(p, 0x09, 0x80)) + SAY("ERROR: failed to set SAA register 0x09 " + "for input %i\n", input); + + SET(p, 0x0002, 0x0093); + break; + } + default: + SAY("ERROR: bad input: %i\n", input); + return -1; + } + + ir = read_stk(p, 0x00); + JOT(8, "STK register 0x00 has 0x%02X\n", ir); + ir = read_saa(p, 0x02); + JOT(8, "SAA register 0x02 has 0x%02X\n", ir); + + start_100(p); + + return 0; +} +/****************************************************************************/ +int set_resolution(struct usb_device *p, + u16 set0, u16 set1, u16 set2, u16 set3) +{ + u16 u0x0111, u0x0113, u0x0115, u0x0117; + + if (!p) + return -ENODEV; + u0x0111 = ((0xFF00 & set0) >> 8); + u0x0113 = ((0xFF00 & set1) >> 8); + u0x0115 = ((0xFF00 & set2) >> 8); + u0x0117 = ((0xFF00 & set3) >> 8); + + SET(p, 0x0110, (0x00FF & set0)); + SET(p, 0x0111, u0x0111); + SET(p, 0x0112, (0x00FF & set1)); + SET(p, 0x0113, u0x0113); + SET(p, 0x0114, (0x00FF & set2)); + SET(p, 0x0115, u0x0115); + SET(p, 0x0116, (0x00FF & set3)); + SET(p, 0x0117, u0x0117); + + return 0; +} +/****************************************************************************/ +int start_100(struct usb_device *p) +{ + u16 get116, get117, get0; + u8 igot116, igot117, igot; + + if (!p) + return -ENODEV; + GET(p, 0x0116, &igot116); + get116 = igot116; + GET(p, 0x0117, &igot117); + get117 = igot117; + SET(p, 0x0116, 0x0000); + SET(p, 0x0117, 0x0000); + + GET(p, 0x0100, &igot); + get0 = igot; + SET(p, 0x0100, (0x80 | get0)); + + SET(p, 0x0116, get116); + SET(p, 0x0117, get117); + + return 0; +} +/****************************************************************************/ +int stop_100(struct usb_device *p) +{ + u16 get0; + u8 igot; + + if (!p) + return -ENODEV; + GET(p, 0x0100, &igot); + get0 = igot; + SET(p, 0x0100, (0x7F & get0)); + return 0; +} +/****************************************************************************/ +/****************************************************************************/ +/*****************************************************************************/ +int easycap_wakeup_device(struct usb_device *pusb_device) +{ + if (!pusb_device) + return -ENODEV; + + return usb_control_msg(pusb_device, usb_sndctrlpipe(pusb_device, 0), + USB_REQ_SET_FEATURE, + USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_DEVICE_REMOTE_WAKEUP, + 0, NULL, 0, 50000); +} +/*****************************************************************************/ +int easycap_audio_setup(struct easycap *peasycap) +{ + struct usb_device *pusb_device; + u8 buffer[1]; + int rc, id1, id2; +/*---------------------------------------------------------------------------*/ +/* + * IMPORTANT: + * THE MESSAGE OF TYPE (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) + * CAUSES MUTING IF THE VALUE 0x0100 IS SENT. + * TO ENABLE AUDIO THE VALUE 0x0200 MUST BE SENT. + */ +/*---------------------------------------------------------------------------*/ + const u8 request = 0x01; + const u8 requesttype = USB_DIR_OUT | + USB_TYPE_CLASS | + USB_RECIP_INTERFACE; + const u16 value_unmute = 0x0200; + const u16 index = 0x0301; + const u16 length = 1; + + if (!peasycap) + return -EFAULT; + + pusb_device = peasycap->pusb_device; + if (!pusb_device) + return -ENODEV; + + JOM(8, "%02X %02X %02X %02X %02X %02X %02X %02X\n", + requesttype, request, + (0x00FF & value_unmute), + (0xFF00 & value_unmute) >> 8, + (0x00FF & index), + (0xFF00 & index) >> 8, + (0x00FF & length), + (0xFF00 & length) >> 8); + + buffer[0] = 0x01; + + rc = usb_control_msg(pusb_device, usb_sndctrlpipe(pusb_device, 0), + request, requesttype, value_unmute, + index, &buffer[0], length, 50000); + + JOT(8, "0x%02X=buffer\n", buffer[0]); + if (rc != (int)length) { + switch (rc) { + case -EPIPE: + SAY("usb_control_msg returned -EPIPE\n"); + break; + default: + SAY("ERROR: usb_control_msg returned %i\n", rc); + break; + } + } +/*--------------------------------------------------------------------------*/ +/* + * REGISTER 500: SETTING VALUE TO 0x0094 RESETS AUDIO CONFIGURATION ??? + * REGISTER 506: ANALOGUE AUDIO ATTENTUATOR ??? + * FOR THE CVBS+S-VIDEO HARDWARE: + * SETTING VALUE TO 0x0000 GIVES QUIET SOUND. + * THE UPPER BYTE SEEMS TO HAVE NO EFFECT. + * FOR THE FOUR-CVBS HARDWARE: + * SETTING VALUE TO 0x0000 SEEMS TO HAVE NO EFFECT. + * REGISTER 507: ANALOGUE AUDIO PREAMPLIFIER ON/OFF ??? + * FOR THE CVBS-S-VIDEO HARDWARE: + * SETTING VALUE TO 0x0001 GIVES VERY LOUD, DISTORTED SOUND. + * THE UPPER BYTE SEEMS TO HAVE NO EFFECT. + */ +/*--------------------------------------------------------------------------*/ + SET(pusb_device, 0x0500, 0x0094); + SET(pusb_device, 0x0500, 0x008C); + SET(pusb_device, 0x0506, 0x0001); + SET(pusb_device, 0x0507, 0x0000); + id1 = read_vt(pusb_device, 0x007C); + id2 = read_vt(pusb_device, 0x007E); + SAM("0x%04X:0x%04X is audio vendor id\n", id1, id2); +/*---------------------------------------------------------------------------*/ +/* + * SELECT AUDIO SOURCE "LINE IN" AND SET THE AUDIO GAIN. +*/ +/*---------------------------------------------------------------------------*/ + if (easycap_audio_gainset(pusb_device, peasycap->gain)) + SAY("ERROR: audio_gainset() failed\n"); + check_vt(pusb_device); + return 0; +} +/*****************************************************************************/ +int check_vt(struct usb_device *pusb_device) +{ + int igot; + + if (!pusb_device) + return -ENODEV; + igot = read_vt(pusb_device, 0x0002); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x02\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x02); + + igot = read_vt(pusb_device, 0x000E); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x0E\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x0E); + + igot = read_vt(pusb_device, 0x0010); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x10\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x10); + + igot = read_vt(pusb_device, 0x0012); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x12\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x12); + + igot = read_vt(pusb_device, 0x0014); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x14\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x14); + + igot = read_vt(pusb_device, 0x0016); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x16\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x16); + + igot = read_vt(pusb_device, 0x0018); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x18\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x18); + + igot = read_vt(pusb_device, 0x001C); + if (0 > igot) + SAY("ERROR: failed to read VT1612A register 0x1C\n"); + if (0x8000 & igot) + SAY("register 0x%02X muted\n", 0x1C); + + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* NOTE: THIS DOES INCREASE THE VOLUME DRAMATICALLY: + * audio_gainset(pusb_device, 0x000F); + * + * loud dB register 0x10 dB register 0x1C dB total + * 0 -34.5 0 -34.5 + * .. .... . .... + * 15 10.5 0 10.5 + * 16 12.0 0 12.0 + * 17 12.0 1.5 13.5 + * .. .... .... .... + * 31 12.0 22.5 34.5 +*/ +/*---------------------------------------------------------------------------*/ +int easycap_audio_gainset(struct usb_device *pusb_device, s8 loud) +{ + int igot; + u8 tmp; + u16 mute; + + if (!pusb_device) + return -ENODEV; + if (0 > loud) + loud = 0; + if (31 < loud) + loud = 31; + + write_vt(pusb_device, 0x0002, 0x8000); +/*---------------------------------------------------------------------------*/ + igot = read_vt(pusb_device, 0x000E); + if (0 > igot) { + SAY("ERROR: failed to read VT1612A register 0x0E\n"); + mute = 0x0000; + } else + mute = 0x8000 & ((unsigned int)igot); + mute = 0; + + if (16 > loud) + tmp = 0x01 | (0x001F & (((u8)(15 - loud)) << 1)); + else + tmp = 0; + + JOT(8, "0x%04X=(mute|tmp) for VT1612A register 0x0E\n", mute | tmp); + write_vt(pusb_device, 0x000E, (mute | tmp)); +/*---------------------------------------------------------------------------*/ + igot = read_vt(pusb_device, 0x0010); + if (0 > igot) { + SAY("ERROR: failed to read VT1612A register 0x10\n"); + mute = 0x0000; + } else + mute = 0x8000 & ((unsigned int)igot); + mute = 0; + + JOT(8, "0x%04X=(mute|tmp|(tmp<<8)) for VT1612A register 0x10,...0x18\n", + mute | tmp | (tmp << 8)); + write_vt(pusb_device, 0x0010, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x0012, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x0014, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x0016, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x0018, (mute | tmp | (tmp << 8))); +/*---------------------------------------------------------------------------*/ + igot = read_vt(pusb_device, 0x001C); + if (0 > igot) { + SAY("ERROR: failed to read VT1612A register 0x1C\n"); + mute = 0x0000; + } else + mute = 0x8000 & ((unsigned int)igot); + mute = 0; + + if (16 <= loud) + tmp = 0x000F & (u8)(loud - 16); + else + tmp = 0; + + JOT(8, "0x%04X=(mute|tmp|(tmp<<8)) for VT1612A register 0x1C\n", + mute | tmp | (tmp << 8)); + write_vt(pusb_device, 0x001C, (mute | tmp | (tmp << 8))); + write_vt(pusb_device, 0x001A, 0x0404); + write_vt(pusb_device, 0x0002, 0x0000); + return 0; +} +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_main.c b/drivers/staging/media/easycap/easycap_main.c new file mode 100644 index 00000000..d0fe34af --- /dev/null +++ b/drivers/staging/media/easycap/easycap_main.c @@ -0,0 +1,4077 @@ +/****************************************************************************** +* * +* easycap_main.c * +* * +* Video driver for EasyCAP USB2.0 Video Capture Device DC60 * +* * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas + * + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include "easycap.h" +#include + + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("R.M. Thomas "); +MODULE_DESCRIPTION(EASYCAP_DRIVER_DESCRIPTION); +MODULE_VERSION(EASYCAP_DRIVER_VERSION); + +#ifdef CONFIG_EASYCAP_DEBUG +int easycap_debug; +module_param_named(debug, easycap_debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level: 0(default),1,2,...,9"); +#endif /* CONFIG_EASYCAP_DEBUG */ + +bool easycap_readback; +module_param_named(readback, easycap_readback, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(readback, "read back written registers: (default false)"); + +static int easycap_bars = 1; +module_param_named(bars, easycap_bars, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(bars, + "Testcard bars on input signal failure: 0=>no, 1=>yes(default)"); + +static int easycap_gain = 16; +module_param_named(gain, easycap_gain, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(gain, "Audio gain: 0,...,16(default),...31"); + +static bool easycap_ntsc; +module_param_named(ntsc, easycap_ntsc, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ntsc, "NTSC default encoding (default PAL)"); + + + +struct easycap_dongle easycapdc60_dongle[DONGLE_MANY]; +static struct mutex mutex_dongle; +static void easycap_complete(struct urb *purb); +static int reset(struct easycap *peasycap); +static int field2frame(struct easycap *peasycap); +static int redaub(struct easycap *peasycap, + void *pad, void *pex, int much, int more, + u8 mask, u8 margin, bool isuy); + +const char *strerror(int err) +{ +#define ERRNOSTR(_e) case _e: return # _e + switch (err) { + case 0: return "OK"; + ERRNOSTR(ENOMEM); + ERRNOSTR(ENODEV); + ERRNOSTR(ENXIO); + ERRNOSTR(EINVAL); + ERRNOSTR(EAGAIN); + ERRNOSTR(EFBIG); + ERRNOSTR(EPIPE); + ERRNOSTR(EMSGSIZE); + ERRNOSTR(ENOSPC); + ERRNOSTR(EINPROGRESS); + ERRNOSTR(ENOSR); + ERRNOSTR(EOVERFLOW); + ERRNOSTR(EPROTO); + ERRNOSTR(EILSEQ); + ERRNOSTR(ETIMEDOUT); + ERRNOSTR(EOPNOTSUPP); + ERRNOSTR(EPFNOSUPPORT); + ERRNOSTR(EAFNOSUPPORT); + ERRNOSTR(EADDRINUSE); + ERRNOSTR(EADDRNOTAVAIL); + ERRNOSTR(ENOBUFS); + ERRNOSTR(EISCONN); + ERRNOSTR(ENOTCONN); + ERRNOSTR(ESHUTDOWN); + ERRNOSTR(ENOENT); + ERRNOSTR(ECONNRESET); + ERRNOSTR(ETIME); + ERRNOSTR(ECOMM); + ERRNOSTR(EREMOTEIO); + ERRNOSTR(EXDEV); + ERRNOSTR(EPERM); + default: return "unknown"; + } + +#undef ERRNOSTR +} + +/****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * THIS ROUTINE DOES NOT DETECT DUPLICATE OCCURRENCES OF POINTER peasycap +*/ +/*---------------------------------------------------------------------------*/ +int easycap_isdongle(struct easycap *peasycap) +{ + int k; + if (!peasycap) + return -2; + for (k = 0; k < DONGLE_MANY; k++) { + if (easycapdc60_dongle[k].peasycap == peasycap) { + peasycap->isdongle = k; + return k; + } + } + return -1; +} +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +static int easycap_open(struct inode *inode, struct file *file) +{ + struct video_device *pvideo_device; + struct easycap *peasycap; + int rc; + + JOT(4, "\n"); + SAY("==========OPEN=========\n"); + + pvideo_device = video_devdata(file); + if (!pvideo_device) { + SAY("ERROR: pvideo_device is NULL.\n"); + return -EFAULT; + } + peasycap = (struct easycap *)video_get_drvdata(pvideo_device); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + + JOM(16, "peasycap->pusb_device=%p\n", peasycap->pusb_device); + + file->private_data = peasycap; + rc = easycap_wakeup_device(peasycap->pusb_device); + if (rc) { + SAM("ERROR: wakeup_device() rc = %i\n", rc); + if (-ENODEV == rc) + SAM("ERROR: wakeup_device() returned -ENODEV\n"); + else + SAM("ERROR: wakeup_device() rc = %i\n", rc); + return rc; + } + JOM(8, "wakeup_device() OK\n"); + peasycap->input = 0; + rc = reset(peasycap); + if (rc) { + SAM("ERROR: reset() rc = %i\n", rc); + return -EFAULT; + } + return 0; +} + +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * RESET THE HARDWARE TO ITS REFERENCE STATE. + * + * THIS ROUTINE MAY BE CALLED REPEATEDLY IF easycap_complete() DETECTS + * A BAD VIDEO FRAME SIZE. +*/ +/*---------------------------------------------------------------------------*/ +static int reset(struct easycap *peasycap) +{ + struct easycap_standard const *peasycap_standard; + int fmtidx, input, rate; + bool ntsc, other; + int rc; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + input = peasycap->input; + +/*---------------------------------------------------------------------------*/ +/* + * IF THE SAA7113H HAS ALREADY ACQUIRED SYNC, USE ITS HARDWARE-DETECTED + * FIELD FREQUENCY TO DISTINGUISH NTSC FROM PAL. THIS IS ESSENTIAL FOR + * gstreamer AND OTHER USERSPACE PROGRAMS WHICH MAY NOT ATTEMPT TO INITIATE + * A SWITCH BETWEEN PAL AND NTSC. + * + * FUNCTION ready_saa() MAY REQUIRE A SUBSTANTIAL FRACTION OF A SECOND TO + * COMPLETE, SO SHOULD NOT BE INVOKED WITHOUT GOOD REASON. +*/ +/*---------------------------------------------------------------------------*/ + other = false; + JOM(8, "peasycap->ntsc=%d\n", peasycap->ntsc); + + rate = ready_saa(peasycap->pusb_device); + if (rate < 0) { + JOM(8, "not ready to capture after %i ms ...\n", PATIENCE); + ntsc = !peasycap->ntsc; + JOM(8, "... trying %s ..\n", ntsc ? "NTSC" : "PAL"); + rc = setup_stk(peasycap->pusb_device, ntsc); + if (rc) { + SAM("ERROR: setup_stk() rc = %i\n", rc); + return -EFAULT; + } + rc = setup_saa(peasycap->pusb_device, ntsc); + if (rc) { + SAM("ERROR: setup_saa() rc = %i\n", rc); + return -EFAULT; + } + + rate = ready_saa(peasycap->pusb_device); + if (rate < 0) { + JOM(8, "not ready to capture after %i ms\n", PATIENCE); + JOM(8, "... saa register 0x1F has 0x%02X\n", + read_saa(peasycap->pusb_device, 0x1F)); + ntsc = peasycap->ntsc; + } else { + JOM(8, "... success at second try: %i=rate\n", rate); + ntsc = (0 < (rate/2)) ? true : false ; + other = true; + } + } else { + JOM(8, "... success at first try: %i=rate\n", rate); + ntsc = (0 < rate/2) ? true : false ; + } + JOM(8, "ntsc=%d\n", ntsc); +/*---------------------------------------------------------------------------*/ + + rc = setup_stk(peasycap->pusb_device, ntsc); + if (rc) { + SAM("ERROR: setup_stk() rc = %i\n", rc); + return -EFAULT; + } + rc = setup_saa(peasycap->pusb_device, ntsc); + if (rc) { + SAM("ERROR: setup_saa() rc = %i\n", rc); + return -EFAULT; + } + + memset(peasycap->merit, 0, sizeof(peasycap->merit)); + + peasycap->video_eof = 0; + peasycap->audio_eof = 0; +/*---------------------------------------------------------------------------*/ +/* + * RESTORE INPUT AND FORCE REFRESH OF STANDARD, FORMAT, ETC. + * + * WHILE THIS PROCEDURE IS IN PROGRESS, SOME IOCTL COMMANDS WILL RETURN -EBUSY. +*/ +/*---------------------------------------------------------------------------*/ + peasycap->input = -8192; + peasycap->standard_offset = -8192; + fmtidx = ntsc ? NTSC_M : PAL_BGHIN; + if (other) { + peasycap_standard = &easycap_standard[0]; + while (0xFFFF != peasycap_standard->mask) { + if (fmtidx == peasycap_standard->v4l2_standard.index) { + peasycap->inputset[input].standard_offset = + peasycap_standard - easycap_standard; + break; + } + peasycap_standard++; + } + if (0xFFFF == peasycap_standard->mask) { + SAM("ERROR: standard not found\n"); + return -EINVAL; + } + JOM(8, "%i=peasycap->inputset[%i].standard_offset\n", + peasycap->inputset[input].standard_offset, input); + } + peasycap->format_offset = -8192; + peasycap->brightness = -8192; + peasycap->contrast = -8192; + peasycap->saturation = -8192; + peasycap->hue = -8192; + + rc = easycap_newinput(peasycap, input); + + if (rc) { + SAM("ERROR: newinput(.,%i) rc = %i\n", rc, input); + return -EFAULT; + } + JOM(4, "restored input, standard and format\n"); + + JOM(8, "true=peasycap->ntsc %d\n", peasycap->ntsc); + + if (0 > peasycap->input) { + SAM("MISTAKE: %i=peasycap->input\n", peasycap->input); + return -ENOENT; + } + if (0 > peasycap->standard_offset) { + SAM("MISTAKE: %i=peasycap->standard_offset\n", + peasycap->standard_offset); + return -ENOENT; + } + if (0 > peasycap->format_offset) { + SAM("MISTAKE: %i=peasycap->format_offset\n", + peasycap->format_offset); + return -ENOENT; + } + if (0 > peasycap->brightness) { + SAM("MISTAKE: %i=peasycap->brightness\n", + peasycap->brightness); + return -ENOENT; + } + if (0 > peasycap->contrast) { + SAM("MISTAKE: %i=peasycap->contrast\n", peasycap->contrast); + return -ENOENT; + } + if (0 > peasycap->saturation) { + SAM("MISTAKE: %i=peasycap->saturation\n", + peasycap->saturation); + return -ENOENT; + } + if (0 > peasycap->hue) { + SAM("MISTAKE: %i=peasycap->hue\n", peasycap->hue); + return -ENOENT; + } + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * IF THE REQUESTED INPUT IS THE SAME AS THE EXISTING INPUT, DO NOTHING. + * OTHERWISE: + * KILL URBS, CLEAR FIELD AND FRAME BUFFERS AND RESET THEIR + * _read AND _fill POINTERS. + * SELECT THE NEW INPUT. + * ADJUST THE STANDARD, FORMAT, BRIGHTNESS, CONTRAST, SATURATION AND HUE + * ON THE BASIS OF INFORMATION IN STRUCTURE easycap.inputset[input]. + * RESUBMIT THE URBS IF STREAMING WAS ALREADY IN PROGRESS. + * + * NOTE: + * THIS ROUTINE MAY BE CALLED FREQUENTLY BY ZONEMINDER VIA IOCTL, + * SO IT SHOULD WRITE ONLY SPARINGLY TO THE LOGFILE. +*/ +/*---------------------------------------------------------------------------*/ +int easycap_newinput(struct easycap *peasycap, int input) +{ + int rc, k, m, mood, off; + int inputnow, video_idlenow, audio_idlenow; + bool resubmit; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + JOM(8, "%i=input sought\n", input); + + if (0 > input && INPUT_MANY <= input) + return -ENOENT; + inputnow = peasycap->input; + if (input == inputnow) + return 0; +/*---------------------------------------------------------------------------*/ +/* + * IF STREAMING IS IN PROGRESS THE URBS ARE KILLED AT THIS + * STAGE AND WILL BE RESUBMITTED PRIOR TO EXIT FROM THE ROUTINE. + * IF NO STREAMING IS IN PROGRESS NO URBS WILL BE SUBMITTED BY THE + * ROUTINE. +*/ +/*---------------------------------------------------------------------------*/ + video_idlenow = peasycap->video_idle; + audio_idlenow = peasycap->audio_idle; + + peasycap->video_idle = 1; + peasycap->audio_idle = 1; + if (peasycap->video_isoc_streaming) { + resubmit = true; + easycap_video_kill_urbs(peasycap); + } else { + resubmit = false; + } +/*---------------------------------------------------------------------------*/ + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -ENODEV; + } + rc = usb_set_interface(peasycap->pusb_device, + peasycap->video_interface, + peasycap->video_altsetting_off); + if (rc) { + SAM("ERROR: usb_set_interface() rc = %i\n", rc); + return -EFAULT; + } + rc = stop_100(peasycap->pusb_device); + if (rc) { + SAM("ERROR: stop_100() rc = %i\n", rc); + return -EFAULT; + } + for (k = 0; k < FIELD_BUFFER_MANY; k++) { + for (m = 0; m < FIELD_BUFFER_SIZE/PAGE_SIZE; m++) + memset(peasycap->field_buffer[k][m].pgo, 0, PAGE_SIZE); + } + for (k = 0; k < FRAME_BUFFER_MANY; k++) { + for (m = 0; m < FRAME_BUFFER_SIZE/PAGE_SIZE; m++) + memset(peasycap->frame_buffer[k][m].pgo, 0, PAGE_SIZE); + } + peasycap->field_page = 0; + peasycap->field_read = 0; + peasycap->field_fill = 0; + + peasycap->frame_read = 0; + peasycap->frame_fill = 0; + for (k = 0; k < peasycap->input; k++) { + (peasycap->frame_fill)++; + if (peasycap->frame_buffer_many <= peasycap->frame_fill) + peasycap->frame_fill = 0; + } + peasycap->input = input; + select_input(peasycap->pusb_device, peasycap->input, 9); +/*---------------------------------------------------------------------------*/ + if (input == peasycap->inputset[input].input) { + off = peasycap->inputset[input].standard_offset; + if (off != peasycap->standard_offset) { + rc = adjust_standard(peasycap, + easycap_standard[off].v4l2_standard.id); + if (rc) { + SAM("ERROR: adjust_standard() rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->standard_offset\n", + peasycap->standard_offset); + } else { + JOM(8, "%i=peasycap->standard_offset unchanged\n", + peasycap->standard_offset); + } + off = peasycap->inputset[input].format_offset; + if (off != peasycap->format_offset) { + struct v4l2_pix_format *pix = + &easycap_format[off].v4l2_format.fmt.pix; + rc = adjust_format(peasycap, + pix->width, pix->height, + pix->pixelformat, pix->field, false); + if (0 > rc) { + SAM("ERROR: adjust_format() rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->format_offset\n", + peasycap->format_offset); + } else { + JOM(8, "%i=peasycap->format_offset unchanged\n", + peasycap->format_offset); + } + mood = peasycap->inputset[input].brightness; + if (mood != peasycap->brightness) { + rc = adjust_brightness(peasycap, mood); + if (rc) { + SAM("ERROR: adjust_brightness rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->brightness\n", + peasycap->brightness); + } + mood = peasycap->inputset[input].contrast; + if (mood != peasycap->contrast) { + rc = adjust_contrast(peasycap, mood); + if (rc) { + SAM("ERROR: adjust_contrast rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->contrast\n", peasycap->contrast); + } + mood = peasycap->inputset[input].saturation; + if (mood != peasycap->saturation) { + rc = adjust_saturation(peasycap, mood); + if (rc) { + SAM("ERROR: adjust_saturation rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->saturation\n", + peasycap->saturation); + } + mood = peasycap->inputset[input].hue; + if (mood != peasycap->hue) { + rc = adjust_hue(peasycap, mood); + if (rc) { + SAM("ERROR: adjust_hue rc = %i\n", rc); + return -EFAULT; + } + JOM(8, "%i=peasycap->hue\n", peasycap->hue); + } + } else { + SAM("MISTAKE: easycap.inputset[%i] unpopulated\n", input); + return -ENOENT; + } +/*---------------------------------------------------------------------------*/ + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -ENODEV; + } + rc = usb_set_interface(peasycap->pusb_device, + peasycap->video_interface, + peasycap->video_altsetting_on); + if (rc) { + SAM("ERROR: usb_set_interface() rc = %i\n", rc); + return -EFAULT; + } + rc = start_100(peasycap->pusb_device); + if (rc) { + SAM("ERROR: start_100() rc = %i\n", rc); + return -EFAULT; + } + if (resubmit) + easycap_video_submit_urbs(peasycap); + + peasycap->video_isoc_sequence = VIDEO_ISOC_BUFFER_MANY - 1; + peasycap->video_idle = video_idlenow; + peasycap->audio_idle = audio_idlenow; + peasycap->video_junk = 0; + + return 0; +} +/*****************************************************************************/ +int easycap_video_submit_urbs(struct easycap *peasycap) +{ + struct data_urb *pdata_urb; + struct urb *purb; + struct list_head *plist_head; + int j, isbad, nospc, m, rc; + int isbuf; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + + if (!peasycap->purb_video_head) { + SAY("ERROR: peasycap->urb_video_head uninitialized\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAY("ERROR: peasycap->pusb_device is NULL\n"); + return -ENODEV; + } + if (!peasycap->video_isoc_streaming) { + JOM(4, "submission of all video urbs\n"); + isbad = 0; nospc = 0; m = 0; + list_for_each(plist_head, (peasycap->purb_video_head)) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + purb = pdata_urb->purb; + isbuf = pdata_urb->isbuf; + purb->interval = 1; + purb->dev = peasycap->pusb_device; + purb->pipe = + usb_rcvisocpipe(peasycap->pusb_device, + peasycap->video_endpointnumber); + purb->transfer_flags = URB_ISO_ASAP; + purb->transfer_buffer = + peasycap->video_isoc_buffer[isbuf].pgo; + purb->transfer_buffer_length = + peasycap->video_isoc_buffer_size; + purb->complete = easycap_complete; + purb->context = peasycap; + purb->start_frame = 0; + purb->number_of_packets = + peasycap->video_isoc_framesperdesc; + + for (j = 0; j < peasycap->video_isoc_framesperdesc; j++) { + purb->iso_frame_desc[j]. offset = + j * peasycap->video_isoc_maxframesize; + purb->iso_frame_desc[j]. length = + peasycap->video_isoc_maxframesize; + } + + rc = usb_submit_urb(purb, GFP_KERNEL); + if (rc) { + isbad++; + SAM("ERROR: usb_submit_urb() failed " + "for urb with rc:-%s\n", + strerror(rc)); + if (rc == -ENOSPC) + nospc++; + } else { + m++; + } + } else { + isbad++; + } + } + if (nospc) { + SAM("-ENOSPC=usb_submit_urb() for %i urbs\n", nospc); + SAM("..... possibly inadequate USB bandwidth\n"); + peasycap->video_eof = 1; + } + + if (isbad) + easycap_video_kill_urbs(peasycap); + else + peasycap->video_isoc_streaming = 1; + } else { + JOM(4, "already streaming video urbs\n"); + } + return 0; +} +/*****************************************************************************/ +int easycap_audio_kill_urbs(struct easycap *peasycap) +{ + int m; + struct list_head *plist_head; + struct data_urb *pdata_urb; + + if (!peasycap->audio_isoc_streaming) + return 0; + + if (!peasycap->purb_audio_head) { + SAM("ERROR: peasycap->purb_audio_head is NULL\n"); + return -EFAULT; + } + + peasycap->audio_isoc_streaming = 0; + m = 0; + list_for_each(plist_head, peasycap->purb_audio_head) { + pdata_urb = list_entry(plist_head, struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + usb_kill_urb(pdata_urb->purb); + m++; + } + } + + JOM(4, "%i audio urbs killed\n", m); + + return 0; +} +int easycap_video_kill_urbs(struct easycap *peasycap) +{ + int m; + struct list_head *plist_head; + struct data_urb *pdata_urb; + + if (!peasycap->video_isoc_streaming) + return 0; + + if (!peasycap->purb_video_head) { + SAM("ERROR: peasycap->purb_video_head is NULL\n"); + return -EFAULT; + } + + peasycap->video_isoc_streaming = 0; + JOM(4, "killing video urbs\n"); + m = 0; + list_for_each(plist_head, (peasycap->purb_video_head)) { + pdata_urb = list_entry(plist_head, struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + usb_kill_urb(pdata_urb->purb); + m++; + } + } + JOM(4, "%i video urbs killed\n", m); + + return 0; +} +/****************************************************************************/ +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +/*--------------------------------------------------------------------------*/ +static int easycap_open_noinode(struct file *file) +{ + return easycap_open(NULL, file); +} + +static int videodev_release(struct video_device *pvideo_device) +{ + struct easycap *peasycap; + + peasycap = video_get_drvdata(pvideo_device); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + SAY("ending unsuccessfully\n"); + return -EFAULT; + } + if (easycap_video_kill_urbs(peasycap)) { + SAM("ERROR: easycap_video_kill_urbs() failed\n"); + return -EFAULT; + } + JOM(4, "ending successfully\n"); + return 0; +} +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ +/*****************************************************************************/ +/*--------------------------------------------------------------------------*/ +/* + * THIS FUNCTION IS CALLED FROM WITHIN easycap_usb_disconnect() AND IS + * PROTECTED BY SEMAPHORES SET AND CLEARED BY easycap_usb_disconnect(). + * + * BY THIS STAGE THE DEVICE HAS ALREADY BEEN PHYSICALLY UNPLUGGED, SO + * peasycap->pusb_device IS NO LONGER VALID. + */ +/*---------------------------------------------------------------------------*/ +static void easycap_delete(struct kref *pkref) +{ + struct easycap *peasycap; + struct data_urb *pdata_urb; + struct list_head *plist_head, *plist_next; + int k, m, gone, kd; + int allocation_video_urb; + int allocation_video_page; + int allocation_video_struct; + int allocation_audio_urb; + int allocation_audio_page; + int allocation_audio_struct; + int registered_video, registered_audio; + + peasycap = container_of(pkref, struct easycap, kref); + if (!peasycap) { + SAM("ERROR: peasycap is NULL: cannot perform deletions\n"); + return; + } + kd = easycap_isdongle(peasycap); +/*---------------------------------------------------------------------------*/ +/* + * FREE VIDEO. + */ +/*---------------------------------------------------------------------------*/ + if (peasycap->purb_video_head) { + m = 0; + list_for_each(plist_head, peasycap->purb_video_head) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + usb_free_urb(pdata_urb->purb); + pdata_urb->purb = NULL; + peasycap->allocation_video_urb--; + m++; + } + } + + JOM(4, "%i video urbs freed\n", m); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing video data_urb structures.\n"); + m = 0; + list_for_each_safe(plist_head, plist_next, + peasycap->purb_video_head) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb) { + peasycap->allocation_video_struct -= + sizeof(struct data_urb); + kfree(pdata_urb); + m++; + } + } + JOM(4, "%i video data_urb structures freed\n", m); + JOM(4, "setting peasycap->purb_video_head=NULL\n"); + peasycap->purb_video_head = NULL; + } +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing video isoc buffers.\n"); + m = 0; + for (k = 0; k < VIDEO_ISOC_BUFFER_MANY; k++) { + if (peasycap->video_isoc_buffer[k].pgo) { + free_pages((unsigned long) + peasycap->video_isoc_buffer[k].pgo, + VIDEO_ISOC_ORDER); + peasycap->video_isoc_buffer[k].pgo = NULL; + peasycap->allocation_video_page -= + BIT(VIDEO_ISOC_ORDER); + m++; + } + } + JOM(4, "isoc video buffers freed: %i pages\n", + m * (0x01 << VIDEO_ISOC_ORDER)); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing video field buffers.\n"); + gone = 0; + for (k = 0; k < FIELD_BUFFER_MANY; k++) { + for (m = 0; m < FIELD_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->field_buffer[k][m].pgo) { + free_page((unsigned long) + peasycap->field_buffer[k][m].pgo); + peasycap->field_buffer[k][m].pgo = NULL; + peasycap->allocation_video_page -= 1; + gone++; + } + } + } + JOM(4, "video field buffers freed: %i pages\n", gone); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing video frame buffers.\n"); + gone = 0; + for (k = 0; k < FRAME_BUFFER_MANY; k++) { + for (m = 0; m < FRAME_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->frame_buffer[k][m].pgo) { + free_page((unsigned long) + peasycap->frame_buffer[k][m].pgo); + peasycap->frame_buffer[k][m].pgo = NULL; + peasycap->allocation_video_page -= 1; + gone++; + } + } + } + JOM(4, "video frame buffers freed: %i pages\n", gone); +/*---------------------------------------------------------------------------*/ +/* + * FREE AUDIO. + */ +/*---------------------------------------------------------------------------*/ + if (peasycap->purb_audio_head) { + JOM(4, "freeing audio urbs\n"); + m = 0; + list_for_each(plist_head, (peasycap->purb_audio_head)) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + usb_free_urb(pdata_urb->purb); + pdata_urb->purb = NULL; + peasycap->allocation_audio_urb--; + m++; + } + } + JOM(4, "%i audio urbs freed\n", m); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing audio data_urb structures.\n"); + m = 0; + list_for_each_safe(plist_head, plist_next, + peasycap->purb_audio_head) { + pdata_urb = list_entry(plist_head, + struct data_urb, list_head); + if (pdata_urb) { + peasycap->allocation_audio_struct -= + sizeof(struct data_urb); + kfree(pdata_urb); + m++; + } + } + JOM(4, "%i audio data_urb structures freed\n", m); + JOM(4, "setting peasycap->purb_audio_head=NULL\n"); + peasycap->purb_audio_head = NULL; + } +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing audio isoc buffers.\n"); + m = 0; + for (k = 0; k < AUDIO_ISOC_BUFFER_MANY; k++) { + if (peasycap->audio_isoc_buffer[k].pgo) { + free_pages((unsigned long) + (peasycap->audio_isoc_buffer[k].pgo), + AUDIO_ISOC_ORDER); + peasycap->audio_isoc_buffer[k].pgo = NULL; + peasycap->allocation_audio_page -= + BIT(AUDIO_ISOC_ORDER); + m++; + } + } + JOM(4, "easyoss_delete(): isoc audio buffers freed: %i pages\n", + m * (0x01 << AUDIO_ISOC_ORDER)); +/*---------------------------------------------------------------------------*/ + JOM(4, "freeing easycap structure.\n"); + allocation_video_urb = peasycap->allocation_video_urb; + allocation_video_page = peasycap->allocation_video_page; + allocation_video_struct = peasycap->allocation_video_struct; + registered_video = peasycap->registered_video; + allocation_audio_urb = peasycap->allocation_audio_urb; + allocation_audio_page = peasycap->allocation_audio_page; + allocation_audio_struct = peasycap->allocation_audio_struct; + registered_audio = peasycap->registered_audio; + + if (0 <= kd && DONGLE_MANY > kd) { + if (mutex_lock_interruptible(&mutex_dongle)) { + SAY("ERROR: cannot down mutex_dongle\n"); + } else { + JOM(4, "locked mutex_dongle\n"); + easycapdc60_dongle[kd].peasycap = NULL; + mutex_unlock(&mutex_dongle); + JOM(4, "unlocked mutex_dongle\n"); + JOT(4, " null-->dongle[%i].peasycap\n", kd); + allocation_video_struct -= sizeof(struct easycap); + } + } else { + SAY("ERROR: cannot purge dongle[].peasycap"); + } + + kfree(peasycap); + +/*---------------------------------------------------------------------------*/ + SAY("%8i=video urbs after all deletions\n", allocation_video_urb); + SAY("%8i=video pages after all deletions\n", allocation_video_page); + SAY("%8i=video structs after all deletions\n", allocation_video_struct); + SAY("%8i=video devices after all deletions\n", registered_video); + SAY("%8i=audio urbs after all deletions\n", allocation_audio_urb); + SAY("%8i=audio pages after all deletions\n", allocation_audio_page); + SAY("%8i=audio structs after all deletions\n", allocation_audio_struct); + SAY("%8i=audio devices after all deletions\n", registered_audio); + + JOT(4, "ending.\n"); + return; +} +/*****************************************************************************/ +static unsigned int easycap_poll(struct file *file, poll_table *wait) +{ + struct easycap *peasycap; + int rc, kd; + + JOT(8, "\n"); + + if (NULL == ((poll_table *)wait)) + JOT(8, "WARNING: poll table pointer is NULL ... continuing\n"); + if (!file) { + SAY("ERROR: file pointer is NULL\n"); + return -ERESTARTSYS; + } + peasycap = file->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAY("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } +/*---------------------------------------------------------------------------*/ + kd = easycap_isdongle(peasycap); + if (0 <= kd && DONGLE_MANY > kd) { + if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_video)) { + SAY("ERROR: cannot down dongle[%i].mutex_video\n", kd); + return -ERESTARTSYS; + } + JOM(4, "locked dongle[%i].mutex_video\n", kd); + /* + * MEANWHILE, easycap_usb_disconnect() MAY HAVE FREED POINTER + * peasycap, IN WHICH CASE A REPEAT CALL TO isdongle() WILL FAIL. + * IF NECESSARY, BAIL OUT. + */ + if (kd != easycap_isdongle(peasycap)) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + if (!file) { + SAY("ERROR: file is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + peasycap = file->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return -ERESTARTSYS; + } + } else + /* + * IF easycap_usb_disconnect() HAS ALREADY FREED POINTER peasycap + * BEFORE THE ATTEMPT TO ACQUIRE THE SEMAPHORE, isdongle() WILL + * HAVE FAILED. BAIL OUT. + */ + return -ERESTARTSYS; +/*---------------------------------------------------------------------------*/ + rc = easycap_video_dqbuf(peasycap, 0); + peasycap->polled = 1; + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + if (rc) + return POLLERR; + + return POLLIN | POLLRDNORM; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * IF mode IS NONZERO THIS ROUTINE RETURNS -EAGAIN RATHER THAN BLOCKING. + */ +/*---------------------------------------------------------------------------*/ +int easycap_video_dqbuf(struct easycap *peasycap, int mode) +{ + int input, ifield, miss, rc; + + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAY("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + ifield = 0; + JOM(8, "%i=ifield\n", ifield); +/*---------------------------------------------------------------------------*/ +/* + * CHECK FOR LOST INPUT SIGNAL. + * + * FOR THE FOUR-CVBS EasyCAP, THIS DOES NOT WORK AS EXPECTED. + * IF INPUT 0 IS PRESENT AND SYNC ACQUIRED, UNPLUGGING INPUT 4 DOES NOT + * RESULT IN SETTING BIT 0x40 ON REGISTER 0x1F, PRESUMABLY BECAUSE THERE + * IS FLYWHEELING ON INPUT 0. THE UPSHOT IS: + * + * INPUT 0 PLUGGED, INPUT 4 PLUGGED => SCREEN 0 OK, SCREEN 4 OK + * INPUT 0 PLUGGED, INPUT 4 UNPLUGGED => SCREEN 0 OK, SCREEN 4 BLACK + * INPUT 0 UNPLUGGED, INPUT 4 PLUGGED => SCREEN 0 BARS, SCREEN 4 OK + * INPUT 0 UNPLUGGED, INPUT 4 UNPLUGGED => SCREEN 0 BARS, SCREEN 4 BARS +*/ +/*---------------------------------------------------------------------------*/ + input = peasycap->input; + if (0 <= input && INPUT_MANY > input) { + rc = read_saa(peasycap->pusb_device, 0x1F); + if (0 <= rc) { + if (rc & 0x40) + peasycap->lost[input] += 1; + else + peasycap->lost[input] -= 2; + + if (0 > peasycap->lost[input]) + peasycap->lost[input] = 0; + else if ((2 * VIDEO_LOST_TOLERATE) < peasycap->lost[input]) + peasycap->lost[input] = (2 * VIDEO_LOST_TOLERATE); + } + } +/*---------------------------------------------------------------------------*/ +/* + * WAIT FOR FIELD ifield (0 => TOP, 1 => BOTTOM) + */ +/*---------------------------------------------------------------------------*/ + miss = 0; + while ((peasycap->field_read == peasycap->field_fill) || + (0 != (0xFF00 & peasycap->field_buffer + [peasycap->field_read][0].kount)) || + (ifield != (0x00FF & peasycap->field_buffer + [peasycap->field_read][0].kount))) { + if (mode) + return -EAGAIN; + + JOM(8, "first wait on wq_video, %i=field_read %i=field_fill\n", + peasycap->field_read, peasycap->field_fill); + + if (0 != (wait_event_interruptible(peasycap->wq_video, + (peasycap->video_idle || peasycap->video_eof || + ((peasycap->field_read != peasycap->field_fill) && + (0 == (0xFF00 & peasycap->field_buffer[peasycap->field_read][0].kount)) && + (ifield == (0x00FF & peasycap->field_buffer[peasycap->field_read][0].kount))))))) { + SAM("aborted by signal\n"); + return -EIO; + } + if (peasycap->video_idle) { + JOM(8, "%i=peasycap->video_idle returning -EAGAIN\n", + peasycap->video_idle); + return -EAGAIN; + } + if (peasycap->video_eof) { + JOM(8, "%i=peasycap->video_eof\n", peasycap->video_eof); + #if defined(PERSEVERE) + if (1 == peasycap->status) { + JOM(8, "persevering ...\n"); + peasycap->video_eof = 0; + peasycap->audio_eof = 0; + if (0 != reset(peasycap)) { + JOM(8, " ... failed returning -EIO\n"); + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + easycap_video_kill_urbs(peasycap); + return -EIO; + } + peasycap->status = 0; + JOM(8, " ... OK returning -EAGAIN\n"); + return -EAGAIN; + } + #endif /*PERSEVERE*/ + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + easycap_video_kill_urbs(peasycap); + JOM(8, "returning -EIO\n"); + return -EIO; + } + miss++; + } + JOM(8, "first awakening on wq_video after %i waits\n", miss); + + rc = field2frame(peasycap); + if (rc) + SAM("ERROR: field2frame() rc = %i\n", rc); +/*---------------------------------------------------------------------------*/ +/* + * WAIT FOR THE OTHER FIELD + */ +/*---------------------------------------------------------------------------*/ + if (ifield) + ifield = 0; + else + ifield = 1; + miss = 0; + while ((peasycap->field_read == peasycap->field_fill) || + (0 != (0xFF00 & peasycap->field_buffer[peasycap->field_read][0].kount)) || + (ifield != (0x00FF & peasycap->field_buffer[peasycap->field_read][0].kount))) { + if (mode) + return -EAGAIN; + + JOM(8, "second wait on wq_video %i=field_read %i=field_fill\n", + peasycap->field_read, peasycap->field_fill); + if (0 != (wait_event_interruptible(peasycap->wq_video, + (peasycap->video_idle || peasycap->video_eof || + ((peasycap->field_read != peasycap->field_fill) && + (0 == (0xFF00 & peasycap->field_buffer[peasycap->field_read][0].kount)) && + (ifield == (0x00FF & peasycap->field_buffer[peasycap->field_read][0].kount))))))) { + SAM("aborted by signal\n"); + return -EIO; + } + if (peasycap->video_idle) { + JOM(8, "%i=peasycap->video_idle returning -EAGAIN\n", + peasycap->video_idle); + return -EAGAIN; + } + if (peasycap->video_eof) { + JOM(8, "%i=peasycap->video_eof\n", peasycap->video_eof); +#if defined(PERSEVERE) + if (1 == peasycap->status) { + JOM(8, "persevering ...\n"); + peasycap->video_eof = 0; + peasycap->audio_eof = 0; + if (0 != reset(peasycap)) { + JOM(8, " ... failed returning -EIO\n"); + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + easycap_video_kill_urbs(peasycap); + return -EIO; + } + peasycap->status = 0; + JOM(8, " ... OK ... returning -EAGAIN\n"); + return -EAGAIN; + } +#endif /*PERSEVERE*/ + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + easycap_video_kill_urbs(peasycap); + JOM(8, "returning -EIO\n"); + return -EIO; + } + miss++; + } + JOM(8, "second awakening on wq_video after %i waits\n", miss); + + rc = field2frame(peasycap); + if (rc) + SAM("ERROR: field2frame() rc = %i\n", rc); +/*---------------------------------------------------------------------------*/ +/* + * WASTE THIS FRAME +*/ +/*---------------------------------------------------------------------------*/ + if (peasycap->skip) { + peasycap->skipped++; + if (peasycap->skip != peasycap->skipped) + return peasycap->skip - peasycap->skipped; + else + peasycap->skipped = 0; + } +/*---------------------------------------------------------------------------*/ + peasycap->frame_read = peasycap->frame_fill; + peasycap->queued[peasycap->frame_read] = 0; + peasycap->done[peasycap->frame_read] = V4L2_BUF_FLAG_DONE; + + peasycap->frame_fill++; + if (peasycap->frame_buffer_many <= peasycap->frame_fill) + peasycap->frame_fill = 0; + + if (0x01 & easycap_standard[peasycap->standard_offset].mask) + peasycap->frame_buffer[peasycap->frame_read][0].kount = + V4L2_FIELD_TOP; + else + peasycap->frame_buffer[peasycap->frame_read][0].kount = + V4L2_FIELD_BOTTOM; + + + JOM(8, "setting: %i=peasycap->frame_read\n", peasycap->frame_read); + JOM(8, "bumped to: %i=peasycap->frame_fill\n", peasycap->frame_fill); + + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * BY DEFINITION, odd IS true FOR THE FIELD OCCUPYING LINES 1,3,5,...,479 + * odd IS false FOR THE FIELD OCCUPYING LINES 0,2,4,...,478 + * + * WHEN BOOLEAN PARAMETER decimatepixel IS true, ONLY THE FIELD FOR WHICH + * odd==false IS TRANSFERRED TO THE FRAME BUFFER. + * + */ +/*---------------------------------------------------------------------------*/ +static int field2frame(struct easycap *peasycap) +{ + + void *pex, *pad; + int kex, kad, mex, mad, rex, rad, rad2; + int c2, c3, w2, w3, cz, wz; + int rc, bytesperpixel, multiplier; + int much, more, over, rump, caches, input; + u8 mask, margin; + bool odd, isuy, decimatepixel, badinput; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + + badinput = false; + input = 0x07 & peasycap->field_buffer[peasycap->field_read][0].input; + + JOM(8, "===== parity %i, input 0x%02X, field buffer %i --> " + "frame buffer %i\n", + peasycap->field_buffer[peasycap->field_read][0].kount, + peasycap->field_buffer[peasycap->field_read][0].input, + peasycap->field_read, peasycap->frame_fill); + JOM(8, "===== %i=bytesperpixel\n", peasycap->bytesperpixel); + +/*---------------------------------------------------------------------------*/ +/* + * REJECT OR CLEAN BAD FIELDS + */ +/*---------------------------------------------------------------------------*/ + if (peasycap->field_read == peasycap->field_fill) { + SAM("ERROR: on entry, still filling field buffer %i\n", + peasycap->field_read); + return 0; + } +#ifdef EASYCAP_TESTCARD + easycap_testcard(peasycap, peasycap->field_read); +#else + if (0 <= input && INPUT_MANY > input) { + if (easycap_bars && VIDEO_LOST_TOLERATE <= peasycap->lost[input]) + easycap_testcard(peasycap, peasycap->field_read); + } +#endif /*EASYCAP_TESTCARD*/ +/*---------------------------------------------------------------------------*/ + + bytesperpixel = peasycap->bytesperpixel; + decimatepixel = peasycap->decimatepixel; + + if ((2 != bytesperpixel) && + (3 != bytesperpixel) && + (4 != bytesperpixel)) { + SAM("MISTAKE: %i=bytesperpixel\n", bytesperpixel); + return -EFAULT; + } + if (decimatepixel) + multiplier = 2; + else + multiplier = 1; + + w2 = 2 * multiplier * (peasycap->width); + w3 = bytesperpixel * multiplier * (peasycap->width); + wz = multiplier * (peasycap->height) * + multiplier * (peasycap->width); + + kex = peasycap->field_read; mex = 0; + kad = peasycap->frame_fill; mad = 0; + + pex = peasycap->field_buffer[kex][0].pgo; rex = PAGE_SIZE; + pad = peasycap->frame_buffer[kad][0].pgo; rad = PAGE_SIZE; + odd = !!(peasycap->field_buffer[kex][0].kount); + + if (odd && (!decimatepixel)) { + JOM(8, "initial skipping %4i bytes p.%4i\n", + w3/multiplier, mad); + pad += (w3 / multiplier); rad -= (w3 / multiplier); + } + isuy = true; + mask = 0; rump = 0; caches = 0; + + cz = 0; + while (cz < wz) { + /* + * PROCESS ONE LINE OF FRAME AT FULL RESOLUTION: + * READ w2 BYTES FROM FIELD BUFFER, + * WRITE w3 BYTES TO FRAME BUFFER + */ + if (!decimatepixel) { + over = w2; + do { + much = over; more = 0; + margin = 0; mask = 0x00; + if (rex < much) + much = rex; + rump = 0; + + if (much % 2) { + SAM("MISTAKE: much is odd\n"); + return -EFAULT; + } + + more = (bytesperpixel * + much) / 2; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + if (1 < bytesperpixel) { + if (rad * 2 < much * bytesperpixel) { + /* + * INJUDICIOUS ALTERATION OF + * THIS STATEMENT BLOCK WILL + * CAUSE BREAKAGE. BEWARE. + */ + rad2 = rad + bytesperpixel - 1; + much = ((((2 * rad2)/bytesperpixel)/2) * 2); + rump = ((bytesperpixel * much) / 2) - rad; + more = rad; + } + mask = (u8)rump; + margin = 0; + if (much == rex) { + mask |= 0x04; + if ((mex + 1) < FIELD_BUFFER_SIZE / PAGE_SIZE) + margin = *((u8 *)(peasycap->field_buffer[kex][mex + 1].pgo)); + else + mask |= 0x08; + } + } else { + SAM("MISTAKE: %i=bytesperpixel\n", + bytesperpixel); + return -EFAULT; + } + if (rump) + caches++; + if (badinput) { + JOM(8, "ERROR: 0x%02X=->field_buffer" + "[%i][%i].input, " + "0x%02X=(0x08|->input)\n", + peasycap->field_buffer + [kex][mex].input, kex, mex, + (0x08|peasycap->input)); + } + rc = redaub(peasycap, pad, pex, much, more, + mask, margin, isuy); + if (0 > rc) { + SAM("ERROR: redaub() failed\n"); + return -EFAULT; + } + if (much % 4) + isuy = !isuy; + + over -= much; cz += much; + pex += much; rex -= much; + if (!rex) { + mex++; + pex = peasycap->field_buffer[kex][mex].pgo; + rex = PAGE_SIZE; + if (peasycap->field_buffer[kex][mex].input != (0x08|peasycap->input)) + badinput = true; + } + pad += more; + rad -= more; + if (!rad) { + mad++; + pad = peasycap->frame_buffer[kad][mad].pgo; + rad = PAGE_SIZE; + if (rump) { + pad += rump; + rad -= rump; + } + } + } while (over); +/*---------------------------------------------------------------------------*/ +/* + * SKIP w3 BYTES IN TARGET FRAME BUFFER, + * UNLESS IT IS THE LAST LINE OF AN ODD FRAME + */ +/*---------------------------------------------------------------------------*/ + if (!odd || (cz != wz)) { + over = w3; + do { + if (!rad) { + mad++; + pad = peasycap->frame_buffer + [kad][mad].pgo; + rad = PAGE_SIZE; + } + more = over; + if (rad < more) + more = rad; + over -= more; + pad += more; + rad -= more; + } while (over); + } +/*---------------------------------------------------------------------------*/ +/* + * PROCESS ONE LINE OF FRAME AT REDUCED RESOLUTION: + * ONLY IF false==odd, + * READ w2 BYTES FROM FIELD BUFFER, + * WRITE w3 / 2 BYTES TO FRAME BUFFER + */ +/*---------------------------------------------------------------------------*/ + } else if (!odd) { + over = w2; + do { + much = over; more = 0; margin = 0; mask = 0x00; + if (rex < much) + much = rex; + rump = 0; + + if (much % 2) { + SAM("MISTAKE: much is odd\n"); + return -EFAULT; + } + + more = (bytesperpixel * much) / 4; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + if (1 < bytesperpixel) { + if (rad * 4 < much * bytesperpixel) { + /* + * INJUDICIOUS ALTERATION OF + * THIS STATEMENT BLOCK + * WILL CAUSE BREAKAGE. + * BEWARE. + */ + rad2 = rad + bytesperpixel - 1; + much = ((((2 * rad2) / bytesperpixel) / 2) * 4); + rump = ((bytesperpixel * much) / 4) - rad; + more = rad; + } + mask = (u8)rump; + margin = 0; + if (much == rex) { + mask |= 0x04; + if ((mex + 1) < FIELD_BUFFER_SIZE / PAGE_SIZE) + margin = *((u8 *)(peasycap->field_buffer[kex][mex + 1].pgo)); + else + mask |= 0x08; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + } else { + SAM("MISTAKE: %i=bytesperpixel\n", + bytesperpixel); + return -EFAULT; + } +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + if (rump) + caches++; + + if (badinput) { + JOM(8, "ERROR: 0x%02X=->field_buffer" + "[%i][%i].input, " + "0x%02X=(0x08|->input)\n", + peasycap->field_buffer + [kex][mex].input, kex, mex, + (0x08|peasycap->input)); + } + rc = redaub(peasycap, pad, pex, much, more, + mask, margin, isuy); + if (0 > rc) { + SAM("ERROR: redaub() failed\n"); + return -EFAULT; + } + over -= much; cz += much; + pex += much; rex -= much; + if (!rex) { + mex++; + pex = peasycap->field_buffer[kex][mex].pgo; + rex = PAGE_SIZE; + if (peasycap->field_buffer[kex][mex].input != + (0x08|peasycap->input)) + badinput = true; + } + pad += more; + rad -= more; + if (!rad) { + mad++; + pad = peasycap->frame_buffer[kad][mad].pgo; + rad = PAGE_SIZE; + if (rump) { + pad += rump; + rad -= rump; + } + } + } while (over); +/*---------------------------------------------------------------------------*/ +/* + * OTHERWISE JUST + * READ w2 BYTES FROM FIELD BUFFER AND DISCARD THEM + */ +/*---------------------------------------------------------------------------*/ + } else { + over = w2; + do { + if (!rex) { + mex++; + pex = peasycap->field_buffer[kex][mex].pgo; + rex = PAGE_SIZE; + if (peasycap->field_buffer[kex][mex].input != + (0x08|peasycap->input)) { + JOM(8, "ERROR: 0x%02X=->field_buffer" + "[%i][%i].input, " + "0x%02X=(0x08|->input)\n", + peasycap->field_buffer + [kex][mex].input, kex, mex, + (0x08|peasycap->input)); + badinput = true; + } + } + much = over; + if (rex < much) + much = rex; + over -= much; + cz += much; + pex += much; + rex -= much; + } while (over); + } + } +/*---------------------------------------------------------------------------*/ +/* + * SANITY CHECKS + */ +/*---------------------------------------------------------------------------*/ + c2 = (mex + 1)*PAGE_SIZE - rex; + if (cz != c2) + SAM("ERROR: discrepancy %i in bytes read\n", c2 - cz); + c3 = (mad + 1)*PAGE_SIZE - rad; + + if (!decimatepixel) { + if (bytesperpixel * cz != c3) + SAM("ERROR: discrepancy %i in bytes written\n", + c3 - (bytesperpixel * cz)); + } else { + if (!odd) { + if (bytesperpixel * + cz != (4 * c3)) + SAM("ERROR: discrepancy %i in bytes written\n", + (2*c3)-(bytesperpixel * cz)); + } else { + if (0 != c3) + SAM("ERROR: discrepancy %i " + "in bytes written\n", c3); + } + } + if (rump) + SAM("WORRY: undischarged cache at end of line in frame buffer\n"); + + JOM(8, "===== field2frame(): %i bytes --> %i bytes (incl skip)\n", c2, c3); + JOM(8, "===== field2frame(): %i=mad %i=rad\n", mad, rad); + + if (odd) + JOM(8, "+++++ field2frame(): frame buffer %i is full\n", kad); + + if (peasycap->field_read == peasycap->field_fill) + SAM("WARNING: on exit, filling field buffer %i\n", + peasycap->field_read); + + if (caches) + JOM(8, "%i=caches\n", caches); + return 0; +} +/*---------------------------------------------------------------------------*/ +/* + * DECIMATION AND COLOURSPACE CONVERSION. + * + * THIS ROUTINE REQUIRES THAT ALL THE DATA TO BE READ RESIDES ON ONE PAGE + * AND THAT ALL THE DATA TO BE WRITTEN RESIDES ON ONE (DIFFERENT) PAGE. + * THE CALLING ROUTINE MUST ENSURE THAT THIS REQUIREMENT IS MET, AND MUST + * ALSO ENSURE THAT much IS EVEN. + * + * much BYTES ARE READ, AT LEAST (bytesperpixel * much)/2 BYTES ARE WRITTEN + * IF THERE IS NO DECIMATION, HALF THIS AMOUNT IF THERE IS DECIMATION. + * + * mask IS ZERO WHEN NO SPECIAL BEHAVIOUR REQUIRED. OTHERWISE IT IS SET THUS: + * 0x03 & mask = number of bytes to be written to cache instead of to + * frame buffer + * 0x04 & mask => use argument margin to set the chrominance for last pixel + * 0x08 & mask => do not set the chrominance for last pixel + * + * YUV to RGB CONVERSION IS (OR SHOULD BE) ITU-R BT 601. + * + * THERE IS A LOT OF CODE REPETITION IN THIS ROUTINE IN ORDER TO AVOID + * INEFFICIENT SWITCHING INSIDE INNER LOOPS. REARRANGING THE LOGIC TO + * REDUCE CODE LENGTH WILL GENERALLY IMPAIR RUNTIME PERFORMANCE. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +static int redaub(struct easycap *peasycap, + void *pad, void *pex, int much, int more, + u8 mask, u8 margin, bool isuy) +{ + static s32 ay[256], bu[256], rv[256], gu[256], gv[256]; + u8 *pcache; + u8 r, g, b, y, u, v, c, *p2, *p3, *pz, *pr; + int bytesperpixel; + bool byteswaporder, decimatepixel, last; + int j, rump; + s32 tmp; + + if (much % 2) { + SAM("MISTAKE: much is odd\n"); + return -EFAULT; + } + bytesperpixel = peasycap->bytesperpixel; + byteswaporder = peasycap->byteswaporder; + decimatepixel = peasycap->decimatepixel; + +/*---------------------------------------------------------------------------*/ + if (!bu[255]) { + for (j = 0; j < 112; j++) { + tmp = (0xFF00 & (453 * j)) >> 8; + bu[j + 128] = tmp; bu[127 - j] = -tmp; + tmp = (0xFF00 & (359 * j)) >> 8; + rv[j + 128] = tmp; rv[127 - j] = -tmp; + tmp = (0xFF00 & (88 * j)) >> 8; + gu[j + 128] = tmp; gu[127 - j] = -tmp; + tmp = (0xFF00 & (183 * j)) >> 8; + gv[j + 128] = tmp; gv[127 - j] = -tmp; + } + for (j = 0; j < 16; j++) { + bu[j] = bu[16]; rv[j] = rv[16]; + gu[j] = gu[16]; gv[j] = gv[16]; + } + for (j = 240; j < 256; j++) { + bu[j] = bu[239]; rv[j] = rv[239]; + gu[j] = gu[239]; gv[j] = gv[239]; + } + for (j = 16; j < 236; j++) + ay[j] = j; + for (j = 0; j < 16; j++) + ay[j] = ay[16]; + for (j = 236; j < 256; j++) + ay[j] = ay[235]; + JOM(8, "lookup tables are prepared\n"); + } + pcache = peasycap->pcache; + if (!pcache) + pcache = &peasycap->cache[0]; +/*---------------------------------------------------------------------------*/ +/* + * TRANSFER CONTENTS OF CACHE TO THE FRAME BUFFER + */ +/*---------------------------------------------------------------------------*/ + if (!pcache) { + SAM("MISTAKE: pcache is NULL\n"); + return -EFAULT; + } + + if (pcache != &peasycap->cache[0]) + JOM(16, "cache has %i bytes\n", (int)(pcache - &peasycap->cache[0])); + p2 = &peasycap->cache[0]; + p3 = (u8 *)pad - (int)(pcache - &peasycap->cache[0]); + while (p2 < pcache) { + *p3++ = *p2; p2++; + } + pcache = &peasycap->cache[0]; + if (p3 != pad) { + SAM("MISTAKE: pointer misalignment\n"); + return -EFAULT; + } +/*---------------------------------------------------------------------------*/ + rump = (int)(0x03 & mask); + u = 0; v = 0; + p2 = (u8 *)pex; pz = p2 + much; pr = p3 + more; last = false; + p2++; + + if (isuy) + u = *(p2 - 1); + else + v = *(p2 - 1); + + if (rump) + JOM(16, "%4i=much %4i=more %i=rump\n", much, more, rump); + +/*---------------------------------------------------------------------------*/ + switch (bytesperpixel) { + case 2: { + if (!decimatepixel) { + memcpy(pad, pex, (size_t)much); + if (!byteswaporder) { + /* UYVY */ + return 0; + } else { + /* YUYV */ + p3 = (u8 *)pad; pz = p3 + much; + while (pz > p3) { + c = *p3; + *p3 = *(p3 + 1); + *(p3 + 1) = c; + p3 += 2; + } + return 0; + } + } else { + if (!byteswaporder) { + /* UYVY DECIMATED */ + p2 = (u8 *)pex; p3 = (u8 *)pad; pz = p2 + much; + while (pz > p2) { + *p3 = *p2; + *(p3 + 1) = *(p2 + 1); + *(p3 + 2) = *(p2 + 2); + *(p3 + 3) = *(p2 + 3); + p3 += 4; p2 += 8; + } + return 0; + } else { + /* YUYV DECIMATED */ + p2 = (u8 *)pex; p3 = (u8 *)pad; pz = p2 + much; + while (pz > p2) { + *p3 = *(p2 + 1); + *(p3 + 1) = *p2; + *(p3 + 2) = *(p2 + 3); + *(p3 + 3) = *(p2 + 2); + p3 += 4; p2 += 8; + } + return 0; + } + } + break; + } + case 3: + { + if (!decimatepixel) { + if (!byteswaporder) { + /* RGB */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = r; + *pcache++ = g; + *pcache++ = b; + break; + } + case 2: { + *p3 = r; + *(p3 + 1) = g; + *pcache++ = b; + break; + } + default: { + SAM("MISTAKE: %i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + } + p2 += 2; + if (isuy) + isuy = false; + else + isuy = true; + p3 += bytesperpixel; + } + return 0; + } else { + /* BGR */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } + else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = b; + *pcache++ = g; + *pcache++ = r; + break; + } + case 2: { + *p3 = b; + *(p3 + 1) = g; + *pcache++ = r; + break; + } + default: { + SAM("MISTAKE: %i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + } + p2 += 2; + if (isuy) + isuy = false; + else + isuy = true; + p3 += bytesperpixel; + } + } + return 0; + } else { + if (!byteswaporder) { + /* RGB DECIMATED */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + if (isuy) { + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - + gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = r; + *pcache++ = g; + *pcache++ = b; + break; + } + case 2: { + *p3 = r; + *(p3 + 1) = g; + *pcache++ = b; + break; + } + default: { + SAM("MISTAKE: " + "%i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + } + isuy = false; + p3 += bytesperpixel; + } else { + isuy = true; + } + p2 += 2; + } + return 0; + } else { + /* BGR DECIMATED */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + if (isuy) { + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - + gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = b; + *pcache++ = g; + *pcache++ = r; + break; + } + case 2: { + *p3 = b; + *(p3 + 1) = g; + *pcache++ = r; + break; + } + default: { + SAM("MISTAKE: " + "%i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + } + isuy = false; + p3 += bytesperpixel; + } + else + isuy = true; + p2 += 2; + } + return 0; + } + } + break; + } + case 4: + { + if (!decimatepixel) { + if (!byteswaporder) { + /* RGBA */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = r; + *pcache++ = g; + *pcache++ = b; + *pcache++ = 0; + break; + } + case 2: { + *p3 = r; + *(p3 + 1) = g; + *pcache++ = b; + *pcache++ = 0; + break; + } + case 3: { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + *pcache++ = 0; + break; + } + default: { + SAM("MISTAKE: %i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + *(p3 + 3) = 0; + } + p2 += 2; + if (isuy) + isuy = false; + else + isuy = true; + p3 += bytesperpixel; + } + return 0; + } else { + /* + * BGRA + */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = b; + *pcache++ = g; + *pcache++ = r; + *pcache++ = 0; + break; + } + case 2: { + *p3 = b; + *(p3 + 1) = g; + *pcache++ = r; + *pcache++ = 0; + break; + } + case 3: { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + *pcache++ = 0; + break; + } + default: + SAM("MISTAKE: %i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } else { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + *(p3 + 3) = 0; + } + p2 += 2; + if (isuy) + isuy = false; + else + isuy = true; + p3 += bytesperpixel; + } + } + return 0; + } else { + if (!byteswaporder) { + /* + * RGBA DECIMATED + */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + if (isuy) { + + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - + gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = r; + *pcache++ = g; + *pcache++ = b; + *pcache++ = 0; + break; + } + case 2: { + *p3 = r; + *(p3 + 1) = g; + *pcache++ = b; + *pcache++ = 0; + break; + } + case 3: { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + *pcache++ = 0; + break; + } + default: { + SAM("MISTAKE: " + "%i=rump\n", + bytesperpixel - + rump); + return -EFAULT; + } + } + } else { + *p3 = r; + *(p3 + 1) = g; + *(p3 + 2) = b; + *(p3 + 3) = 0; + } + isuy = false; + p3 += bytesperpixel; + } else + isuy = true; + p2 += 2; + } + return 0; + } else { + /* + * BGRA DECIMATED + */ + while (pz > p2) { + if (pr <= (p3 + bytesperpixel)) + last = true; + else + last = false; + y = *p2; + if (last && (0x0C & mask)) { + if (0x04 & mask) { + if (isuy) + v = margin; + else + u = margin; + } else + if (0x08 & mask) + ; + } else { + if (isuy) + v = *(p2 + 1); + else + u = *(p2 + 1); + } + + if (isuy) { + tmp = ay[(int)y] + rv[(int)v]; + r = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] - gu[(int)u] - + gv[(int)v]; + g = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + tmp = ay[(int)y] + bu[(int)u]; + b = (255 < tmp) ? 255 : ((0 > tmp) ? + 0 : (u8)tmp); + + if (last && rump) { + pcache = &peasycap->cache[0]; + switch (bytesperpixel - rump) { + case 1: { + *p3 = b; + *pcache++ = g; + *pcache++ = r; + *pcache++ = 0; + break; + } + case 2: { + *p3 = b; + *(p3 + 1) = g; + *pcache++ = r; + *pcache++ = 0; + break; + } + case 3: { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + *pcache++ = 0; + break; + } + default: { + SAM("MISTAKE: " + "%i=rump\n", + bytesperpixel - rump); + return -EFAULT; + } + } + } else { + *p3 = b; + *(p3 + 1) = g; + *(p3 + 2) = r; + *(p3 + 3) = 0; + } + isuy = false; + p3 += bytesperpixel; + } else + isuy = true; + p2 += 2; + } + return 0; + } + } + break; + } + default: { + SAM("MISTAKE: %i=bytesperpixel\n", bytesperpixel); + return -EFAULT; + } + } + return 0; +} +/*****************************************************************************/ +/* + * SEE CORBET ET AL. "LINUX DEVICE DRIVERS", 3rd EDITION, PAGES 430-434 + */ +/*****************************************************************************/ +static void easycap_vma_open(struct vm_area_struct *pvma) +{ + struct easycap *peasycap; + + peasycap = pvma->vm_private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } + peasycap->vma_many++; + JOT(8, "%i=peasycap->vma_many\n", peasycap->vma_many); + return; +} +/*****************************************************************************/ +static void easycap_vma_close(struct vm_area_struct *pvma) +{ + struct easycap *peasycap; + + peasycap = pvma->vm_private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } + peasycap->vma_many--; + JOT(8, "%i=peasycap->vma_many\n", peasycap->vma_many); + return; +} +/*****************************************************************************/ +static int easycap_vma_fault(struct vm_area_struct *pvma, struct vm_fault *pvmf) +{ + int k, m, retcode; + void *pbuf; + struct page *page; + struct easycap *peasycap; + + retcode = VM_FAULT_NOPAGE; + + if (!pvma) { + SAY("pvma is NULL\n"); + return retcode; + } + if (!pvmf) { + SAY("pvmf is NULL\n"); + return retcode; + } + + k = (pvmf->pgoff) / (FRAME_BUFFER_SIZE/PAGE_SIZE); + m = (pvmf->pgoff) % (FRAME_BUFFER_SIZE/PAGE_SIZE); + + if (!m) + JOT(4, "%4i=k, %4i=m\n", k, m); + else + JOT(16, "%4i=k, %4i=m\n", k, m); + + if ((0 > k) || (FRAME_BUFFER_MANY <= k)) { + SAY("ERROR: buffer index %i out of range\n", k); + return retcode; + } + if ((0 > m) || (FRAME_BUFFER_SIZE/PAGE_SIZE <= m)) { + SAY("ERROR: page number %i out of range\n", m); + return retcode; + } + peasycap = pvma->vm_private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return retcode; + } +/*---------------------------------------------------------------------------*/ + pbuf = peasycap->frame_buffer[k][m].pgo; + if (!pbuf) { + SAM("ERROR: pbuf is NULL\n"); + return retcode; + } + page = virt_to_page(pbuf); + if (!page) { + SAM("ERROR: page is NULL\n"); + return retcode; + } + get_page(page); +/*---------------------------------------------------------------------------*/ + if (!page) { + SAM("ERROR: page is NULL after get_page(page)\n"); + } else { + pvmf->page = page; + retcode = VM_FAULT_MINOR; + } + return retcode; +} + +static const struct vm_operations_struct easycap_vm_ops = { + .open = easycap_vma_open, + .close = easycap_vma_close, + .fault = easycap_vma_fault, +}; + +static int easycap_mmap(struct file *file, struct vm_area_struct *pvma) +{ + JOT(8, "\n"); + + pvma->vm_ops = &easycap_vm_ops; + pvma->vm_flags |= VM_RESERVED; + if (file) + pvma->vm_private_data = file->private_data; + easycap_vma_open(pvma); + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * ON COMPLETION OF A VIDEO URB ITS DATA IS COPIED TO THE FIELD BUFFERS + * PROVIDED peasycap->video_idle IS ZERO. REGARDLESS OF THIS BEING TRUE, + * IT IS RESUBMITTED PROVIDED peasycap->video_isoc_streaming IS NOT ZERO. + * + * THIS FUNCTION IS AN INTERRUPT SERVICE ROUTINE AND MUST NOT SLEEP. + * + * INFORMATION ABOUT THE VALIDITY OF THE CONTENTS OF THE FIELD BUFFER ARE + * STORED IN THE TWO-BYTE STATUS PARAMETER + * peasycap->field_buffer[peasycap->field_fill][0].kount + * NOTICE THAT THE INFORMATION IS STORED ONLY WITH PAGE 0 OF THE FIELD BUFFER. + * + * THE LOWER BYTE CONTAINS THE FIELD PARITY BYTE FURNISHED BY THE SAA7113H + * CHIP. + * + * THE UPPER BYTE IS ZERO IF NO PROBLEMS, OTHERWISE: + * 0 != (kount & 0x8000) => AT LEAST ONE URB COMPLETED WITH ERRORS + * 0 != (kount & 0x4000) => BUFFER HAS TOO MUCH DATA + * 0 != (kount & 0x2000) => BUFFER HAS NOT ENOUGH DATA + * 0 != (kount & 0x1000) => BUFFER HAS DATA FROM DISPARATE INPUTS + * 0 != (kount & 0x0400) => RESERVED + * 0 != (kount & 0x0200) => FIELD BUFFER NOT YET CHECKED + * 0 != (kount & 0x0100) => BUFFER HAS TWO EXTRA BYTES - WHY? + */ +/*---------------------------------------------------------------------------*/ +static void easycap_complete(struct urb *purb) +{ + struct easycap *peasycap; + struct data_buffer *pfield_buffer; + char errbuf[16]; + int i, more, much, leap, rc, last; + int videofieldamount; + unsigned int override, bad; + int framestatus, framelength, frameactual, frameoffset; + u8 *pu; + + if (!purb) { + SAY("ERROR: easycap_complete(): purb is NULL\n"); + return; + } + peasycap = purb->context; + if (!peasycap) { + SAY("ERROR: easycap_complete(): peasycap is NULL\n"); + return; + } + if (peasycap->video_eof) + return; + for (i = 0; i < VIDEO_ISOC_BUFFER_MANY; i++) + if (purb->transfer_buffer == peasycap->video_isoc_buffer[i].pgo) + break; + JOM(16, "%2i=urb\n", i); + last = peasycap->video_isoc_sequence; + if ((((VIDEO_ISOC_BUFFER_MANY - 1) == last) && (0 != i)) || + (((VIDEO_ISOC_BUFFER_MANY - 1) != last) && ((last + 1) != i))) { + JOM(16, "ERROR: out-of-order urbs %i,%i ... continuing\n", + last, i); + } + peasycap->video_isoc_sequence = i; + + if (peasycap->video_idle) { + JOM(16, "%i=video_idle %i=video_isoc_streaming\n", + peasycap->video_idle, peasycap->video_isoc_streaming); + if (peasycap->video_isoc_streaming) { + rc = usb_submit_urb(purb, GFP_ATOMIC); + if (rc) { + SAM("%s:%d ENOMEM\n", strerror(rc), rc); + if (-ENODEV != rc) + SAM("ERROR: while %i=video_idle, " + "usb_submit_urb() " + "failed with rc:\n", + peasycap->video_idle); + } + } + return; + } + override = 0; +/*---------------------------------------------------------------------------*/ + if (FIELD_BUFFER_MANY <= peasycap->field_fill) { + SAM("ERROR: bad peasycap->field_fill\n"); + return; + } + if (purb->status) { + if ((-ESHUTDOWN == purb->status) || (-ENOENT == purb->status)) { + JOM(8, "urb status -ESHUTDOWN or -ENOENT\n"); + return; + } + + (peasycap->field_buffer[peasycap->field_fill][0].kount) |= 0x8000 ; + SAM("ERROR: bad urb status -%s: %d\n", + strerror(purb->status), purb->status); +/*---------------------------------------------------------------------------*/ + } else { + for (i = 0; i < purb->number_of_packets; i++) { + if (0 != purb->iso_frame_desc[i].status) { + (peasycap->field_buffer + [peasycap->field_fill][0].kount) |= 0x8000 ; + /* FIXME: 1. missing '-' check boundaries */ + strcpy(&errbuf[0], + strerror(purb->iso_frame_desc[i].status)); + } + framestatus = purb->iso_frame_desc[i].status; + framelength = purb->iso_frame_desc[i].length; + frameactual = purb->iso_frame_desc[i].actual_length; + frameoffset = purb->iso_frame_desc[i].offset; + + JOM(16, "frame[%2i]:" + "%4i=status " + "%4i=actual " + "%4i=length " + "%5i=offset\n", + i, framestatus, frameactual, framelength, frameoffset); + if (!purb->iso_frame_desc[i].status) { + more = purb->iso_frame_desc[i].actual_length; + pfield_buffer = &peasycap->field_buffer + [peasycap->field_fill][peasycap->field_page]; + videofieldamount = (peasycap->field_page * + PAGE_SIZE) + + (int)(pfield_buffer->pto - pfield_buffer->pgo); + if (4 == more) + peasycap->video_mt++; + if (4 < more) { + if (peasycap->video_mt) { + JOM(8, "%4i empty video urb frames\n", + peasycap->video_mt); + peasycap->video_mt = 0; + } + if (FIELD_BUFFER_MANY <= peasycap->field_fill) { + SAM("ERROR: bad peasycap->field_fill\n"); + return; + } + if (FIELD_BUFFER_SIZE/PAGE_SIZE <= + peasycap->field_page) { + SAM("ERROR: bad peasycap->field_page\n"); + return; + } + pfield_buffer = &peasycap->field_buffer + [peasycap->field_fill][peasycap->field_page]; + pu = (u8 *)(purb->transfer_buffer + + purb->iso_frame_desc[i].offset); + if (0x80 & *pu) + leap = 8; + else + leap = 4; +/*--------------------------------------------------------------------------*/ +/* + * EIGHT-BYTE END-OF-VIDEOFIELD MARKER. + * NOTE: A SUCCESSION OF URB FRAMES FOLLOWING THIS ARE EMPTY, + * CORRESPONDING TO THE FIELD FLYBACK (VERTICAL BLANKING) PERIOD. + * + * PROVIDED THE FIELD BUFFER CONTAINS GOOD DATA AS INDICATED BY A ZERO UPPER + * BYTE OF + * peasycap->field_buffer[peasycap->field_fill][0].kount + * THE CONTENTS OF THE FIELD BUFFER ARE OFFERED TO dqbuf(), field_read IS + * UPDATED AND field_fill IS BUMPED. IF THE FIELD BUFFER CONTAINS BAD DATA + * NOTHING IS OFFERED TO dqbuf(). + * + * THE DECISION ON WHETHER THE PARITY OF THE OFFERED FIELD BUFFER IS RIGHT + * RESTS WITH dqbuf(). + */ +/*---------------------------------------------------------------------------*/ + if ((8 == more) || override) { + if (videofieldamount > + peasycap->videofieldamount) { + if (2 == videofieldamount - + peasycap-> + videofieldamount) { + (peasycap->field_buffer + [peasycap->field_fill] + [0].kount) |= 0x0100; + peasycap->video_junk += (1 + + VIDEO_JUNK_TOLERATE); + } else + (peasycap->field_buffer + [peasycap->field_fill] + [0].kount) |= 0x4000; + } else if (videofieldamount < + peasycap-> + videofieldamount) { + (peasycap->field_buffer + [peasycap->field_fill] + [0].kount) |= 0x2000; + } + bad = 0xFF00 & peasycap->field_buffer + [peasycap->field_fill] + [0].kount; + if (!bad) { + (peasycap->video_junk)--; + if (-VIDEO_JUNK_TOLERATE > + peasycap->video_junk) + peasycap->video_junk = + -VIDEO_JUNK_TOLERATE; + peasycap->field_read = + (peasycap-> + field_fill)++; + if (FIELD_BUFFER_MANY <= + peasycap-> + field_fill) + peasycap-> + field_fill = 0; + peasycap->field_page = 0; + pfield_buffer = &peasycap-> + field_buffer + [peasycap-> + field_fill] + [peasycap-> + field_page]; + pfield_buffer->pto = + pfield_buffer->pgo; + JOM(8, "bumped to: %i=" + "peasycap->" + "field_fill %i=" + "parity\n", + peasycap->field_fill, + 0x00FF & + pfield_buffer->kount); + JOM(8, "field buffer %i has " + "%i bytes fit to be " + "read\n", + peasycap->field_read, + videofieldamount); + JOM(8, "wakeup call to " + "wq_video, " + "%i=field_read " + "%i=field_fill " + "%i=parity\n", + peasycap->field_read, + peasycap->field_fill, + 0x00FF & peasycap-> + field_buffer + [peasycap-> + field_read][0].kount); + wake_up_interruptible + (&(peasycap-> + wq_video)); + } else { + peasycap->video_junk++; + if (bad & 0x0010) + peasycap->video_junk += + (1 + VIDEO_JUNK_TOLERATE/2); + JOM(8, "field buffer %i had %i " + "bytes, now discarded: " + "0x%04X\n", + peasycap->field_fill, + videofieldamount, + (0xFF00 & + peasycap->field_buffer + [peasycap->field_fill][0]. + kount)); + (peasycap->field_fill)++; + + if (FIELD_BUFFER_MANY <= + peasycap->field_fill) + peasycap->field_fill = 0; + peasycap->field_page = 0; + pfield_buffer = + &peasycap->field_buffer + [peasycap->field_fill] + [peasycap->field_page]; + pfield_buffer->pto = + pfield_buffer->pgo; + + JOM(8, "bumped to: %i=peasycap->" + "field_fill %i=parity\n", + peasycap->field_fill, + 0x00FF & pfield_buffer->kount); + } + if (8 == more) { + JOM(8, "end-of-field: received " + "parity byte 0x%02X\n", + (0xFF & *pu)); + if (0x40 & *pu) + pfield_buffer->kount = 0x0000; + else + pfield_buffer->kount = 0x0001; + pfield_buffer->input = 0x08 | + (0x07 & peasycap->input); + JOM(8, "end-of-field: 0x%02X=kount\n", + 0xFF & pfield_buffer->kount); + } + } +/*---------------------------------------------------------------------------*/ +/* + * COPY more BYTES FROM ISOC BUFFER TO FIELD BUFFER + */ +/*---------------------------------------------------------------------------*/ + pu += leap; + more -= leap; + + if (FIELD_BUFFER_MANY <= peasycap->field_fill) { + SAM("ERROR: bad peasycap->field_fill\n"); + return; + } + if (FIELD_BUFFER_SIZE/PAGE_SIZE <= peasycap->field_page) { + SAM("ERROR: bad peasycap->field_page\n"); + return; + } + pfield_buffer = &peasycap->field_buffer + [peasycap->field_fill][peasycap->field_page]; + while (more) { + pfield_buffer = &peasycap->field_buffer + [peasycap->field_fill] + [peasycap->field_page]; + if (PAGE_SIZE < (pfield_buffer->pto - + pfield_buffer->pgo)) { + SAM("ERROR: bad pfield_buffer->pto\n"); + return; + } + if (PAGE_SIZE == (pfield_buffer->pto - + pfield_buffer->pgo)) { + (peasycap->field_page)++; + if (FIELD_BUFFER_SIZE/PAGE_SIZE <= + peasycap->field_page) { + JOM(16, "wrapping peasycap->" + "field_page\n"); + peasycap->field_page = 0; + } + pfield_buffer = &peasycap-> + field_buffer + [peasycap->field_fill] + [peasycap->field_page]; + pfield_buffer->pto = pfield_buffer->pgo; + pfield_buffer->input = 0x08 | + (0x07 & peasycap->input); + if ((peasycap->field_buffer[peasycap-> + field_fill][0]). + input != + pfield_buffer->input) + (peasycap->field_buffer + [peasycap->field_fill] + [0]).kount |= 0x1000; + } + + much = PAGE_SIZE - + (int)(pfield_buffer->pto - + pfield_buffer->pgo); + + if (much > more) + much = more; + memcpy(pfield_buffer->pto, pu, much); + pu += much; + (pfield_buffer->pto) += much; + more -= much; + } + } + } + } + } +/*---------------------------------------------------------------------------*/ +/* + * RESUBMIT THIS URB, UNLESS A SEVERE PERSISTENT ERROR CONDITION EXISTS. + * + * IF THE WAIT QUEUES ARE NOT CLEARED IN RESPONSE TO AN ERROR CONDITION + * THE USERSPACE PROGRAM, E.G. mplayer, MAY HANG ON EXIT. BEWARE. + */ +/*---------------------------------------------------------------------------*/ + if (VIDEO_ISOC_BUFFER_MANY <= peasycap->video_junk) { + SAM("easycap driver shutting down on condition green\n"); + peasycap->status = 1; + peasycap->video_eof = 1; + peasycap->video_junk = 0; + wake_up_interruptible(&peasycap->wq_video); +#if !defined(PERSEVERE) + peasycap->audio_eof = 1; + wake_up_interruptible(&peasycap->wq_audio); +#endif /*PERSEVERE*/ + return; + } + if (peasycap->video_isoc_streaming) { + rc = usb_submit_urb(purb, GFP_ATOMIC); + if (rc) { + SAM("%s: %d\n", strerror(rc), rc); + if (-ENODEV != rc) + SAM("ERROR: while %i=video_idle, " + "usb_submit_urb() " + "failed with rc:\n", + peasycap->video_idle); + } + } + return; +} + +static const struct v4l2_file_operations v4l2_fops = { + .owner = THIS_MODULE, + .open = easycap_open_noinode, + .unlocked_ioctl = easycap_unlocked_ioctl, + .poll = easycap_poll, + .mmap = easycap_mmap, +}; + +/* + * When the device is plugged, this function is called three times, + * one for each interface. + */ +static int easycap_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usbdev; + struct usb_host_interface *alt; + struct usb_endpoint_descriptor *ep; + struct usb_interface_descriptor *interface; + struct urb *purb; + struct easycap *peasycap; + int ndong; + struct data_urb *pdata_urb; + int i, j, k, m, rc; + u8 bInterfaceNumber; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + void *pbuf; + int okalt[8], isokalt; + int okepn[8]; + int okmps[8]; + int maxpacketsize; + u16 mask; + s32 value; + struct easycap_format *peasycap_format; + int fmtidx; + struct inputset *inputset; + + usbdev = interface_to_usbdev(intf); + + alt = usb_altnum_to_altsetting(intf, 0); + if (!alt) { + SAY("ERROR: usb_host_interface not found\n"); + return -EFAULT; + } + + interface = &alt->desc; + if (!interface) { + SAY("ERROR: intf_descriptor is NULL\n"); + return -EFAULT; + } + + /* Get properties of probed interface */ + bInterfaceNumber = interface->bInterfaceNumber; + bInterfaceClass = interface->bInterfaceClass; + bInterfaceSubClass = interface->bInterfaceSubClass; + + JOT(4, "intf[%i]: num_altsetting=%i\n", + bInterfaceNumber, intf->num_altsetting); + JOT(4, "intf[%i]: cur_altsetting - altsetting=%li\n", + bInterfaceNumber, + (long int)(intf->cur_altsetting - intf->altsetting)); + JOT(4, "intf[%i]: bInterfaceClass=0x%02X bInterfaceSubClass=0x%02X\n", + bInterfaceNumber, bInterfaceClass, bInterfaceSubClass); + + /* + * A new struct easycap is always allocated when interface 0 is probed. + * It is not possible here to free any existing struct easycap. + * This should have been done by easycap_delete() when the device was + * physically unplugged. + * The allocated struct easycap is saved for later usage when + * interfaces 1 and 2 are probed. + */ + if (0 == bInterfaceNumber) { + peasycap = kzalloc(sizeof(struct easycap), GFP_KERNEL); + if (!peasycap) { + SAY("ERROR: Could not allocate peasycap\n"); + return -ENOMEM; + } + + /* Perform urgent initializations */ + peasycap->minor = -1; + kref_init(&peasycap->kref); + JOM(8, "intf[%i]: after kref_init(..._video) " + "%i=peasycap->kref.refcount.counter\n", + bInterfaceNumber, peasycap->kref.refcount.counter); + + /* module params */ + peasycap->gain = (s8)clamp(easycap_gain, 0, 31); + + init_waitqueue_head(&peasycap->wq_video); + init_waitqueue_head(&peasycap->wq_audio); + init_waitqueue_head(&peasycap->wq_trigger); + + if (mutex_lock_interruptible(&mutex_dongle)) { + SAY("ERROR: cannot down mutex_dongle\n"); + return -ERESTARTSYS; + } + + for (ndong = 0; ndong < DONGLE_MANY; ndong++) { + if ((!easycapdc60_dongle[ndong].peasycap) && + (!mutex_is_locked(&easycapdc60_dongle + [ndong].mutex_video)) && + (!mutex_is_locked(&easycapdc60_dongle + [ndong].mutex_audio))) { + easycapdc60_dongle[ndong].peasycap = peasycap; + peasycap->isdongle = ndong; + JOM(8, "intf[%i]: peasycap-->easycap" + "_dongle[%i].peasycap\n", + bInterfaceNumber, ndong); + break; + } + } + + if (DONGLE_MANY <= ndong) { + SAM("ERROR: too many dongles\n"); + mutex_unlock(&mutex_dongle); + return -ENOMEM; + } + mutex_unlock(&mutex_dongle); + + peasycap->allocation_video_struct = sizeof(struct easycap); + + /* and further initialize the structure */ + peasycap->pusb_device = usbdev; + peasycap->pusb_interface = intf; + + peasycap->microphone = false; + + peasycap->video_interface = -1; + peasycap->video_altsetting_on = -1; + peasycap->video_altsetting_off = -1; + peasycap->video_endpointnumber = -1; + peasycap->video_isoc_maxframesize = -1; + peasycap->video_isoc_buffer_size = -1; + + peasycap->audio_interface = -1; + peasycap->audio_altsetting_on = -1; + peasycap->audio_altsetting_off = -1; + peasycap->audio_endpointnumber = -1; + peasycap->audio_isoc_maxframesize = -1; + peasycap->audio_isoc_buffer_size = -1; + + peasycap->frame_buffer_many = FRAME_BUFFER_MANY; + + /* Dynamically fill in the available formats */ + rc = easycap_video_fillin_formats(); + if (0 > rc) { + SAM("ERROR: fillin_formats() rc = %i\n", rc); + return -EFAULT; + } + JOM(4, "%i formats available\n", rc); + + /* Populate easycap.inputset[] */ + inputset = peasycap->inputset; + fmtidx = peasycap->ntsc ? NTSC_M : PAL_BGHIN; + m = 0; + mask = 0; + for (i = 0; 0xFFFF != easycap_standard[i].mask; i++) { + if (fmtidx == easycap_standard[i].v4l2_standard.index) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].standard_offset = i; + + mask = easycap_standard[i].mask; + } + } + if (1 != m) { + SAM("ERROR: " + "inputset->standard_offset unpopulated, %i=m\n", m); + return -ENOENT; + } + + peasycap_format = &easycap_format[0]; + m = 0; + for (i = 0; peasycap_format->v4l2_format.fmt.pix.width; i++) { + struct v4l2_pix_format *pix = + &peasycap_format->v4l2_format.fmt.pix; + if (((peasycap_format->mask & 0x0F) == (mask & 0x0F)) && + pix->field == V4L2_FIELD_NONE && + pix->pixelformat == V4L2_PIX_FMT_UYVY && + pix->width == 640 && pix->height == 480) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].format_offset = i; + break; + } + peasycap_format++; + } + if (1 != m) { + SAM("ERROR: inputset[]->format_offset unpopulated\n"); + return -ENOENT; + } + + m = 0; + for (i = 0; 0xFFFFFFFF != easycap_control[i].id; i++) { + value = easycap_control[i].default_value; + if (V4L2_CID_BRIGHTNESS == easycap_control[i].id) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].brightness = value; + } else if (V4L2_CID_CONTRAST == easycap_control[i].id) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].contrast = value; + } else if (V4L2_CID_SATURATION == easycap_control[i].id) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].saturation = value; + } else if (V4L2_CID_HUE == easycap_control[i].id) { + m++; + for (k = 0; k < INPUT_MANY; k++) + inputset[k].hue = value; + } + } + + if (4 != m) { + SAM("ERROR: inputset[]->brightness underpopulated\n"); + return -ENOENT; + } + for (k = 0; k < INPUT_MANY; k++) + inputset[k].input = k; + JOM(4, "populated inputset[]\n"); + JOM(4, "finished initialization\n"); + } else { + + /* + * FIXME: Identify the appropriate pointer + * peasycap for interfaces 1 and 2. + * The address of peasycap->pusb_device + * is reluctantly used for this purpose. + */ + for (ndong = 0; ndong < DONGLE_MANY; ndong++) { + if (usbdev == easycapdc60_dongle[ndong].peasycap-> + pusb_device) { + peasycap = easycapdc60_dongle[ndong].peasycap; + JOT(8, "intf[%i]: dongle[%i].peasycap\n", + bInterfaceNumber, ndong); + break; + } + } + if (DONGLE_MANY <= ndong) { + SAY("ERROR: peasycap is unknown when probing interface %i\n", + bInterfaceNumber); + return -ENODEV; + } + if (!peasycap) { + SAY("ERROR: peasycap is NULL when probing interface %i\n", + bInterfaceNumber); + return -ENODEV; + } + } + + if ((USB_CLASS_VIDEO == bInterfaceClass) || + (USB_CLASS_VENDOR_SPEC == bInterfaceClass)) { + if (-1 == peasycap->video_interface) { + peasycap->video_interface = bInterfaceNumber; + JOM(4, "setting peasycap->video_interface=%i\n", + peasycap->video_interface); + } else { + if (peasycap->video_interface != bInterfaceNumber) { + SAM("ERROR: attempting to reset " + "peasycap->video_interface\n"); + SAM("...... continuing with " + "%i=peasycap->video_interface\n", + peasycap->video_interface); + } + } + } else if ((USB_CLASS_AUDIO == bInterfaceClass) && + (USB_SUBCLASS_AUDIOSTREAMING == bInterfaceSubClass)) { + if (-1 == peasycap->audio_interface) { + peasycap->audio_interface = bInterfaceNumber; + JOM(4, "setting peasycap->audio_interface=%i\n", + peasycap->audio_interface); + } else { + if (peasycap->audio_interface != bInterfaceNumber) { + SAM("ERROR: attempting to reset " + "peasycap->audio_interface\n"); + SAM("...... continuing with " + "%i=peasycap->audio_interface\n", + peasycap->audio_interface); + } + } + } + + /* + * Investigate all altsettings. This is done in detail + * because USB device 05e1:0408 has disparate incarnations. + */ + isokalt = 0; + for (i = 0; i < intf->num_altsetting; i++) { + alt = usb_altnum_to_altsetting(intf, i); + if (!alt) { + SAM("ERROR: alt is NULL\n"); + return -EFAULT; + } + interface = &alt->desc; + if (!interface) { + SAM("ERROR: intf_descriptor is NULL\n"); + return -EFAULT; + } + + if (0 == interface->bNumEndpoints) + JOM(4, "intf[%i]alt[%i] has no endpoints\n", + bInterfaceNumber, i); + for (j = 0; j < interface->bNumEndpoints; j++) { + ep = &alt->endpoint[j].desc; + if (!ep) { + SAM("ERROR: ep is NULL.\n"); + SAM("...... skipping\n"); + continue; + } + + if (!usb_endpoint_is_isoc_in(ep)) { + JOM(4, "intf[%i]alt[%i]end[%i] is a %d endpoint\n", + bInterfaceNumber, + i, j, ep->bmAttributes); + if (usb_endpoint_dir_out(ep)) { + SAM("ERROR: OUT endpoint unexpected\n"); + SAM("...... continuing\n"); + } + continue; + } + switch (bInterfaceClass) { + case USB_CLASS_VIDEO: + case USB_CLASS_VENDOR_SPEC: { + if (ep->wMaxPacketSize) { + if (8 > isokalt) { + okalt[isokalt] = i; + JOM(4, + "%i=okalt[%i]\n", + okalt[isokalt], + isokalt); + okepn[isokalt] = + ep-> + bEndpointAddress & + 0x0F; + JOM(4, + "%i=okepn[%i]\n", + okepn[isokalt], + isokalt); + okmps[isokalt] = + le16_to_cpu(ep-> + wMaxPacketSize); + JOM(4, + "%i=okmps[%i]\n", + okmps[isokalt], + isokalt); + isokalt++; + } + } else { + if (-1 == peasycap-> + video_altsetting_off) { + peasycap-> + video_altsetting_off = + i; + JOM(4, "%i=video_" + "altsetting_off " + "<====\n", + peasycap-> + video_altsetting_off); + } else { + SAM("ERROR: peasycap" + "->video_altsetting_" + "off already set\n"); + SAM("...... " + "continuing with " + "%i=peasycap->video_" + "altsetting_off\n", + peasycap-> + video_altsetting_off); + } + } + break; + } + case USB_CLASS_AUDIO: { + if (bInterfaceSubClass != + USB_SUBCLASS_AUDIOSTREAMING) + break; + if (!peasycap) { + SAM("MISTAKE: " + "peasycap is NULL\n"); + return -EFAULT; + } + if (ep->wMaxPacketSize) { + if (8 > isokalt) { + okalt[isokalt] = i ; + JOM(4, + "%i=okalt[%i]\n", + okalt[isokalt], + isokalt); + okepn[isokalt] = + ep-> + bEndpointAddress & + 0x0F; + JOM(4, + "%i=okepn[%i]\n", + okepn[isokalt], + isokalt); + okmps[isokalt] = + le16_to_cpu(ep-> + wMaxPacketSize); + JOM(4, + "%i=okmps[%i]\n", + okmps[isokalt], + isokalt); + isokalt++; + } + } else { + if (-1 == peasycap-> + audio_altsetting_off) { + peasycap-> + audio_altsetting_off = + i; + JOM(4, "%i=audio_" + "altsetting_off " + "<====\n", + peasycap-> + audio_altsetting_off); + } else { + SAM("ERROR: peasycap" + "->audio_altsetting_" + "off already set\n"); + SAM("...... " + "continuing with " + "%i=peasycap->" + "audio_altsetting_" + "off\n", + peasycap-> + audio_altsetting_off); + } + } + break; + } + default: + break; + } + if (0 == ep->wMaxPacketSize) { + JOM(4, "intf[%i]alt[%i]end[%i] " + "has zero packet size\n", + bInterfaceNumber, i, j); + } + } + } + + /* Perform initialization of the probed interface */ + JOM(4, "initialization begins for interface %i\n", + interface->bInterfaceNumber); + switch (bInterfaceNumber) { + /* 0: Video interface */ + case 0: { + if (!peasycap) { + SAM("MISTAKE: peasycap is NULL\n"); + return -EFAULT; + } + if (!isokalt) { + SAM("ERROR: no viable video_altsetting_on\n"); + return -ENOENT; + } + peasycap->video_altsetting_on = okalt[isokalt - 1]; + JOM(4, "%i=video_altsetting_on <====\n", + peasycap->video_altsetting_on); + + /* Decide video streaming parameters */ + peasycap->video_endpointnumber = okepn[isokalt - 1]; + JOM(4, "%i=video_endpointnumber\n", peasycap->video_endpointnumber); + maxpacketsize = okmps[isokalt - 1]; + + peasycap->video_isoc_maxframesize = + min(maxpacketsize, USB_2_0_MAXPACKETSIZE); + if (0 >= peasycap->video_isoc_maxframesize) { + SAM("ERROR: bad video_isoc_maxframesize\n"); + SAM(" possibly because port is USB 1.1\n"); + return -ENOENT; + } + JOM(4, "%i=video_isoc_maxframesize\n", + peasycap->video_isoc_maxframesize); + + peasycap->video_isoc_framesperdesc = VIDEO_ISOC_FRAMESPERDESC; + JOM(4, "%i=video_isoc_framesperdesc\n", + peasycap->video_isoc_framesperdesc); + if (0 >= peasycap->video_isoc_framesperdesc) { + SAM("ERROR: bad video_isoc_framesperdesc\n"); + return -ENOENT; + } + peasycap->video_isoc_buffer_size = + peasycap->video_isoc_maxframesize * + peasycap->video_isoc_framesperdesc; + JOM(4, "%i=video_isoc_buffer_size\n", + peasycap->video_isoc_buffer_size); + if ((PAGE_SIZE << VIDEO_ISOC_ORDER) < + peasycap->video_isoc_buffer_size) { + SAM("MISTAKE: peasycap->video_isoc_buffer_size too big\n"); + return -EFAULT; + } + if (-1 == peasycap->video_interface) { + SAM("MISTAKE: video_interface is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_altsetting_on) { + SAM("MISTAKE: video_altsetting_on is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_altsetting_off) { + SAM("MISTAKE: video_interface_off is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_endpointnumber) { + SAM("MISTAKE: video_endpointnumber is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_isoc_maxframesize) { + SAM("MISTAKE: video_isoc_maxframesize is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->video_isoc_buffer_size) { + SAM("MISTAKE: video_isoc_buffer_size is unset\n"); + return -EFAULT; + } + + /* + * Allocate memory for video buffers. + * Lists must be initialized first. + */ + INIT_LIST_HEAD(&(peasycap->urb_video_head)); + peasycap->purb_video_head = &(peasycap->urb_video_head); + JOM(4, "allocating %i frame buffers of size %li\n", + FRAME_BUFFER_MANY, (long int)FRAME_BUFFER_SIZE); + JOM(4, ".... each scattered over %li pages\n", + FRAME_BUFFER_SIZE/PAGE_SIZE); + + for (k = 0; k < FRAME_BUFFER_MANY; k++) { + for (m = 0; m < FRAME_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->frame_buffer[k][m].pgo) + SAM("attempting to reallocate frame " + " buffers\n"); + else { + pbuf = (void *)__get_free_page(GFP_KERNEL); + if (!pbuf) { + SAM("ERROR: Could not allocate frame " + "buffer %i page %i\n", k, m); + return -ENOMEM; + } + + peasycap->allocation_video_page += 1; + peasycap->frame_buffer[k][m].pgo = pbuf; + } + peasycap->frame_buffer[k][m].pto = + peasycap->frame_buffer[k][m].pgo; + } + } + + peasycap->frame_fill = 0; + peasycap->frame_read = 0; + JOM(4, "allocation of frame buffers done: %i pages\n", k * + m); + JOM(4, "allocating %i field buffers of size %li\n", + FIELD_BUFFER_MANY, (long int)FIELD_BUFFER_SIZE); + JOM(4, ".... each scattered over %li pages\n", + FIELD_BUFFER_SIZE/PAGE_SIZE); + + for (k = 0; k < FIELD_BUFFER_MANY; k++) { + for (m = 0; m < FIELD_BUFFER_SIZE/PAGE_SIZE; m++) { + if (peasycap->field_buffer[k][m].pgo) { + SAM("ERROR: attempting to reallocate " + "field buffers\n"); + } else { + pbuf = (void *) __get_free_page(GFP_KERNEL); + if (!pbuf) { + SAM("ERROR: Could not allocate field" + " buffer %i page %i\n", k, m); + return -ENOMEM; + } + + peasycap->allocation_video_page += 1; + peasycap->field_buffer[k][m].pgo = pbuf; + } + peasycap->field_buffer[k][m].pto = + peasycap->field_buffer[k][m].pgo; + } + peasycap->field_buffer[k][0].kount = 0x0200; + } + peasycap->field_fill = 0; + peasycap->field_page = 0; + peasycap->field_read = 0; + JOM(4, "allocation of field buffers done: %i pages\n", k * + m); + JOM(4, "allocating %i isoc video buffers of size %i\n", + VIDEO_ISOC_BUFFER_MANY, + peasycap->video_isoc_buffer_size); + JOM(4, ".... each occupying contiguous memory pages\n"); + + for (k = 0; k < VIDEO_ISOC_BUFFER_MANY; k++) { + pbuf = (void *)__get_free_pages(GFP_KERNEL, + VIDEO_ISOC_ORDER); + if (!pbuf) { + SAM("ERROR: Could not allocate isoc video buffer " + "%i\n", k); + return -ENOMEM; + } + peasycap->allocation_video_page += + BIT(VIDEO_ISOC_ORDER); + + peasycap->video_isoc_buffer[k].pgo = pbuf; + peasycap->video_isoc_buffer[k].pto = + pbuf + peasycap->video_isoc_buffer_size; + peasycap->video_isoc_buffer[k].kount = k; + } + JOM(4, "allocation of isoc video buffers done: %i pages\n", + k * (0x01 << VIDEO_ISOC_ORDER)); + + /* Allocate and initialize multiple struct usb */ + JOM(4, "allocating %i struct urb.\n", VIDEO_ISOC_BUFFER_MANY); + JOM(4, "using %i=peasycap->video_isoc_framesperdesc\n", + peasycap->video_isoc_framesperdesc); + JOM(4, "using %i=peasycap->video_isoc_maxframesize\n", + peasycap->video_isoc_maxframesize); + JOM(4, "using %i=peasycap->video_isoc_buffer_sizen", + peasycap->video_isoc_buffer_size); + + for (k = 0; k < VIDEO_ISOC_BUFFER_MANY; k++) { + purb = usb_alloc_urb(peasycap->video_isoc_framesperdesc, + GFP_KERNEL); + if (!purb) { + SAM("ERROR: usb_alloc_urb returned NULL for buffer " + "%i\n", k); + return -ENOMEM; + } + + peasycap->allocation_video_urb += 1; + pdata_urb = kzalloc(sizeof(struct data_urb), GFP_KERNEL); + if (!pdata_urb) { + SAM("ERROR: Could not allocate struct data_urb.\n"); + return -ENOMEM; + } + + peasycap->allocation_video_struct += + sizeof(struct data_urb); + + pdata_urb->purb = purb; + pdata_urb->isbuf = k; + pdata_urb->length = 0; + list_add_tail(&(pdata_urb->list_head), + peasycap->purb_video_head); + + /* Initialize allocated urbs */ + if (!k) { + JOM(4, "initializing video urbs thus:\n"); + JOM(4, " purb->interval = 1;\n"); + JOM(4, " purb->dev = peasycap->pusb_device;\n"); + JOM(4, " purb->pipe = usb_rcvisocpipe" + "(peasycap->pusb_device,%i);\n", + peasycap->video_endpointnumber); + JOM(4, " purb->transfer_flags = URB_ISO_ASAP;\n"); + JOM(4, " purb->transfer_buffer = peasycap->" + "video_isoc_buffer[.].pgo;\n"); + JOM(4, " purb->transfer_buffer_length = %i;\n", + peasycap->video_isoc_buffer_size); + JOM(4, " purb->complete = easycap_complete;\n"); + JOM(4, " purb->context = peasycap;\n"); + JOM(4, " purb->start_frame = 0;\n"); + JOM(4, " purb->number_of_packets = %i;\n", + peasycap->video_isoc_framesperdesc); + JOM(4, " for (j = 0; j < %i; j++)\n", + peasycap->video_isoc_framesperdesc); + JOM(4, " {\n"); + JOM(4, " purb->iso_frame_desc[j].offset = j*%i;\n", + peasycap->video_isoc_maxframesize); + JOM(4, " purb->iso_frame_desc[j].length = %i;\n", + peasycap->video_isoc_maxframesize); + JOM(4, " }\n"); + } + + purb->interval = 1; + purb->dev = peasycap->pusb_device; + purb->pipe = usb_rcvisocpipe(peasycap->pusb_device, + peasycap->video_endpointnumber); + purb->transfer_flags = URB_ISO_ASAP; + purb->transfer_buffer = peasycap->video_isoc_buffer[k].pgo; + purb->transfer_buffer_length = + peasycap->video_isoc_buffer_size; + purb->complete = easycap_complete; + purb->context = peasycap; + purb->start_frame = 0; + purb->number_of_packets = peasycap->video_isoc_framesperdesc; + for (j = 0; j < peasycap->video_isoc_framesperdesc; j++) { + purb->iso_frame_desc[j].offset = j * + peasycap->video_isoc_maxframesize; + purb->iso_frame_desc[j].length = + peasycap->video_isoc_maxframesize; + } + } + JOM(4, "allocation of %i struct urb done.\n", k); + + /* Save pointer peasycap in this interface */ + usb_set_intfdata(intf, peasycap); + + /* + * It is essential to initialize the hardware before, + * rather than after, the device is registered, + * because some udev rules triggers easycap_open() + * immediately after registration, causing a clash. + */ + peasycap->ntsc = easycap_ntsc; + JOM(8, "defaulting initially to %s\n", + easycap_ntsc ? "NTSC" : "PAL"); + rc = reset(peasycap); + if (rc) { + SAM("ERROR: reset() rc = %i\n", rc); + return -EFAULT; + } + + /* The video device can now be registered */ + if (v4l2_device_register(&intf->dev, &peasycap->v4l2_device)) { + SAM("v4l2_device_register() failed\n"); + return -ENODEV; + } + JOM(4, "registered device instance: %s\n", + peasycap->v4l2_device.name); + + /* + * FIXME: This is believed to be harmless, + * but may well be unnecessary or wrong. + */ + peasycap->video_device.v4l2_dev = NULL; + + + strcpy(&peasycap->video_device.name[0], "easycapdc60"); + peasycap->video_device.fops = &v4l2_fops; + peasycap->video_device.minor = -1; + peasycap->video_device.release = (void *)(&videodev_release); + + video_set_drvdata(&(peasycap->video_device), (void *)peasycap); + + if (0 != (video_register_device(&(peasycap->video_device), + VFL_TYPE_GRABBER, -1))) { + err("Not able to register with videodev"); + videodev_release(&(peasycap->video_device)); + return -ENODEV; + } + + peasycap->registered_video++; + SAM("registered with videodev: %i=minor\n", + peasycap->video_device.minor); + peasycap->minor = peasycap->video_device.minor; + + break; + } + /* 1: Audio control */ + case 1: { + if (!peasycap) { + SAM("MISTAKE: peasycap is NULL\n"); + return -EFAULT; + } + /* Save pointer peasycap in this interface */ + usb_set_intfdata(intf, peasycap); + JOM(4, "no initialization required for interface %i\n", + interface->bInterfaceNumber); + break; + } + /* 2: Audio streaming */ + case 2: { + if (!peasycap) { + SAM("MISTAKE: peasycap is NULL\n"); + return -EFAULT; + } + if (!isokalt) { + SAM("ERROR: no viable audio_altsetting_on\n"); + return -ENOENT; + } + peasycap->audio_altsetting_on = okalt[isokalt - 1]; + JOM(4, "%i=audio_altsetting_on <====\n", + peasycap->audio_altsetting_on); + + peasycap->audio_endpointnumber = okepn[isokalt - 1]; + JOM(4, "%i=audio_endpointnumber\n", peasycap->audio_endpointnumber); + + peasycap->audio_isoc_maxframesize = okmps[isokalt - 1]; + JOM(4, "%i=audio_isoc_maxframesize\n", + peasycap->audio_isoc_maxframesize); + if (0 >= peasycap->audio_isoc_maxframesize) { + SAM("ERROR: bad audio_isoc_maxframesize\n"); + return -ENOENT; + } + if (9 == peasycap->audio_isoc_maxframesize) { + peasycap->ilk |= 0x02; + SAM("audio hardware is microphone\n"); + peasycap->microphone = true; + peasycap->audio_pages_per_fragment = + PAGES_PER_AUDIO_FRAGMENT; + } else if (256 == peasycap->audio_isoc_maxframesize) { + peasycap->ilk &= ~0x02; + SAM("audio hardware is AC'97\n"); + peasycap->microphone = false; + peasycap->audio_pages_per_fragment = + PAGES_PER_AUDIO_FRAGMENT; + } else { + SAM("hardware is unidentified:\n"); + SAM("%i=audio_isoc_maxframesize\n", + peasycap->audio_isoc_maxframesize); + return -ENOENT; + } + + peasycap->audio_bytes_per_fragment = + peasycap->audio_pages_per_fragment * PAGE_SIZE; + peasycap->audio_buffer_page_many = (AUDIO_FRAGMENT_MANY * + peasycap->audio_pages_per_fragment); + + JOM(4, "%6i=AUDIO_FRAGMENT_MANY\n", AUDIO_FRAGMENT_MANY); + JOM(4, "%6i=audio_pages_per_fragment\n", + peasycap->audio_pages_per_fragment); + JOM(4, "%6i=audio_bytes_per_fragment\n", + peasycap->audio_bytes_per_fragment); + JOM(4, "%6i=audio_buffer_page_many\n", + peasycap->audio_buffer_page_many); + + peasycap->audio_isoc_framesperdesc = AUDIO_ISOC_FRAMESPERDESC; + + JOM(4, "%i=audio_isoc_framesperdesc\n", + peasycap->audio_isoc_framesperdesc); + if (0 >= peasycap->audio_isoc_framesperdesc) { + SAM("ERROR: bad audio_isoc_framesperdesc\n"); + return -ENOENT; + } + + peasycap->audio_isoc_buffer_size = + peasycap->audio_isoc_maxframesize * + peasycap->audio_isoc_framesperdesc; + JOM(4, "%i=audio_isoc_buffer_size\n", + peasycap->audio_isoc_buffer_size); + if (AUDIO_ISOC_BUFFER_SIZE < peasycap->audio_isoc_buffer_size) { + SAM("MISTAKE: audio_isoc_buffer_size bigger " + "than %li=AUDIO_ISOC_BUFFER_SIZE\n", + AUDIO_ISOC_BUFFER_SIZE); + return -EFAULT; + } + if (-1 == peasycap->audio_interface) { + SAM("MISTAKE: audio_interface is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_altsetting_on) { + SAM("MISTAKE: audio_altsetting_on is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_altsetting_off) { + SAM("MISTAKE: audio_interface_off is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_endpointnumber) { + SAM("MISTAKE: audio_endpointnumber is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_isoc_maxframesize) { + SAM("MISTAKE: audio_isoc_maxframesize is unset\n"); + return -EFAULT; + } + if (-1 == peasycap->audio_isoc_buffer_size) { + SAM("MISTAKE: audio_isoc_buffer_size is unset\n"); + return -EFAULT; + } + + /* + * Allocate memory for audio buffers. + * Lists must be initialized first. + */ + INIT_LIST_HEAD(&(peasycap->urb_audio_head)); + peasycap->purb_audio_head = &(peasycap->urb_audio_head); + + JOM(4, "allocating %i isoc audio buffers of size %i\n", + AUDIO_ISOC_BUFFER_MANY, + peasycap->audio_isoc_buffer_size); + JOM(4, ".... each occupying contiguous memory pages\n"); + + for (k = 0; k < AUDIO_ISOC_BUFFER_MANY; k++) { + pbuf = (void *)__get_free_pages(GFP_KERNEL, + AUDIO_ISOC_ORDER); + if (!pbuf) { + SAM("ERROR: Could not allocate isoc audio buffer " + "%i\n", k); + return -ENOMEM; + } + peasycap->allocation_audio_page += + BIT(AUDIO_ISOC_ORDER); + + peasycap->audio_isoc_buffer[k].pgo = pbuf; + peasycap->audio_isoc_buffer[k].pto = pbuf + + peasycap->audio_isoc_buffer_size; + peasycap->audio_isoc_buffer[k].kount = k; + } + JOM(4, "allocation of isoc audio buffers done.\n"); + + /* Allocate and initialize urbs */ + JOM(4, "allocating %i struct urb.\n", AUDIO_ISOC_BUFFER_MANY); + JOM(4, "using %i=peasycap->audio_isoc_framesperdesc\n", + peasycap->audio_isoc_framesperdesc); + JOM(4, "using %i=peasycap->audio_isoc_maxframesize\n", + peasycap->audio_isoc_maxframesize); + JOM(4, "using %i=peasycap->audio_isoc_buffer_size\n", + peasycap->audio_isoc_buffer_size); + + for (k = 0; k < AUDIO_ISOC_BUFFER_MANY; k++) { + purb = usb_alloc_urb(peasycap->audio_isoc_framesperdesc, + GFP_KERNEL); + if (!purb) { + SAM("ERROR: usb_alloc_urb returned NULL for buffer " + "%i\n", k); + return -ENOMEM; + } + peasycap->allocation_audio_urb += 1 ; + pdata_urb = kzalloc(sizeof(struct data_urb), GFP_KERNEL); + if (!pdata_urb) { + usb_free_urb(purb); + SAM("ERROR: Could not allocate struct data_urb.\n"); + return -ENOMEM; + } + peasycap->allocation_audio_struct += + sizeof(struct data_urb); + + pdata_urb->purb = purb; + pdata_urb->isbuf = k; + pdata_urb->length = 0; + list_add_tail(&(pdata_urb->list_head), + peasycap->purb_audio_head); + + if (!k) { + JOM(4, "initializing audio urbs thus:\n"); + JOM(4, " purb->interval = 1;\n"); + JOM(4, " purb->dev = peasycap->pusb_device;\n"); + JOM(4, " purb->pipe = usb_rcvisocpipe(peasycap->" + "pusb_device,%i);\n", + peasycap->audio_endpointnumber); + JOM(4, " purb->transfer_flags = URB_ISO_ASAP;\n"); + JOM(4, " purb->transfer_buffer = " + "peasycap->audio_isoc_buffer[.].pgo;\n"); + JOM(4, " purb->transfer_buffer_length = %i;\n", + peasycap->audio_isoc_buffer_size); + JOM(4, " purb->complete = easycap_alsa_complete;\n"); + JOM(4, " purb->context = peasycap;\n"); + JOM(4, " purb->start_frame = 0;\n"); + JOM(4, " purb->number_of_packets = %i;\n", + peasycap->audio_isoc_framesperdesc); + JOM(4, " for (j = 0; j < %i; j++)\n", + peasycap->audio_isoc_framesperdesc); + JOM(4, " {\n"); + JOM(4, " purb->iso_frame_desc[j].offset = j*%i;\n", + peasycap->audio_isoc_maxframesize); + JOM(4, " purb->iso_frame_desc[j].length = %i;\n", + peasycap->audio_isoc_maxframesize); + JOM(4, " }\n"); + } + + purb->interval = 1; + purb->dev = peasycap->pusb_device; + purb->pipe = usb_rcvisocpipe(peasycap->pusb_device, + peasycap->audio_endpointnumber); + purb->transfer_flags = URB_ISO_ASAP; + purb->transfer_buffer = peasycap->audio_isoc_buffer[k].pgo; + purb->transfer_buffer_length = + peasycap->audio_isoc_buffer_size; + purb->complete = easycap_alsa_complete; + purb->context = peasycap; + purb->start_frame = 0; + purb->number_of_packets = peasycap->audio_isoc_framesperdesc; + for (j = 0; j < peasycap->audio_isoc_framesperdesc; j++) { + purb->iso_frame_desc[j].offset = j * + peasycap->audio_isoc_maxframesize; + purb->iso_frame_desc[j].length = + peasycap->audio_isoc_maxframesize; + } + } + JOM(4, "allocation of %i struct urb done.\n", k); + + /* Save pointer peasycap in this interface */ + usb_set_intfdata(intf, peasycap); + + /* The audio device can now be registered */ + JOM(4, "initializing ALSA card\n"); + + rc = easycap_alsa_probe(peasycap); + if (rc) { + err("easycap_alsa_probe() rc = %i\n", rc); + return -ENODEV; + } + + + JOM(8, "kref_get() with %i=kref.refcount.counter\n", + peasycap->kref.refcount.counter); + kref_get(&peasycap->kref); + peasycap->registered_audio++; + break; + } + /* Interfaces other than 0,1,2 are unexpected */ + default: + JOM(4, "ERROR: unexpected interface %i\n", bInterfaceNumber); + return -EINVAL; + } + SAM("ends successfully for interface %i\n", bInterfaceNumber); + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * WHEN THIS FUNCTION IS CALLED THE EasyCAP HAS ALREADY BEEN PHYSICALLY + * UNPLUGGED. HENCE peasycap->pusb_device IS NO LONGER VALID. + * + * THIS FUNCTION AFFECTS ALSA. BEWARE. + */ +/*---------------------------------------------------------------------------*/ +static void easycap_usb_disconnect(struct usb_interface *pusb_interface) +{ + struct usb_host_interface *pusb_host_interface; + struct usb_interface_descriptor *pusb_interface_descriptor; + struct easycap *peasycap; + int minor, kd; + u8 bInterfaceNumber; + + JOT(4, "\n"); + + pusb_host_interface = pusb_interface->cur_altsetting; + if (!pusb_host_interface) { + JOT(4, "ERROR: pusb_host_interface is NULL\n"); + return; + } + pusb_interface_descriptor = &(pusb_host_interface->desc); + if (!pusb_interface_descriptor) { + JOT(4, "ERROR: pusb_interface_descriptor is NULL\n"); + return; + } + bInterfaceNumber = pusb_interface_descriptor->bInterfaceNumber; + minor = pusb_interface->minor; + JOT(4, "intf[%i]: minor=%i\n", bInterfaceNumber, minor); + + if (1 == bInterfaceNumber) + return; + + peasycap = usb_get_intfdata(pusb_interface); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } +/*---------------------------------------------------------------------------*/ +/* + * IF THE WAIT QUEUES ARE NOT CLEARED A DEADLOCK IS POSSIBLE. BEWARE. +*/ +/*---------------------------------------------------------------------------*/ + peasycap->video_eof = 1; + peasycap->audio_eof = 1; + wake_up_interruptible(&(peasycap->wq_video)); + wake_up_interruptible(&(peasycap->wq_audio)); + + switch (bInterfaceNumber) { + case 0: + easycap_video_kill_urbs(peasycap); + break; + case 2: + easycap_audio_kill_urbs(peasycap); + break; + default: + break; + } +/*--------------------------------------------------------------------------*/ +/* + * DEREGISTER + * + * THIS PROCEDURE WILL BLOCK UNTIL easycap_poll(), VIDEO IOCTL AND AUDIO + * IOCTL ARE ALL UNLOCKED. IF THIS IS NOT DONE AN Oops CAN OCCUR WHEN + * AN EasyCAP IS UNPLUGGED WHILE THE URBS ARE RUNNING. BEWARE. + */ +/*--------------------------------------------------------------------------*/ + kd = easycap_isdongle(peasycap); + switch (bInterfaceNumber) { + case 0: { + if (0 <= kd && DONGLE_MANY > kd) { + wake_up_interruptible(&peasycap->wq_video); + JOM(4, "about to lock dongle[%i].mutex_video\n", kd); + if (mutex_lock_interruptible(&easycapdc60_dongle[kd]. + mutex_video)) { + SAY("ERROR: " + "cannot lock dongle[%i].mutex_video\n", kd); + return; + } + JOM(4, "locked dongle[%i].mutex_video\n", kd); + } else { + SAY("ERROR: %i=kd is bad: cannot lock dongle\n", kd); + } +/*---------------------------------------------------------------------------*/ + if (!peasycap->v4l2_device.name[0]) { + SAM("ERROR: peasycap->v4l2_device.name is empty\n"); + if (0 <= kd && DONGLE_MANY > kd) + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + return; + } + v4l2_device_disconnect(&peasycap->v4l2_device); + JOM(4, "v4l2_device_disconnect() OK\n"); + v4l2_device_unregister(&peasycap->v4l2_device); + JOM(4, "v4l2_device_unregister() OK\n"); + + video_unregister_device(&peasycap->video_device); + JOM(4, "intf[%i]: video_unregister_device() minor=%i\n", + bInterfaceNumber, minor); + peasycap->registered_video--; +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + if (0 <= kd && DONGLE_MANY > kd) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + JOM(4, "unlocked dongle[%i].mutex_video\n", kd); + } + break; + } + case 2: { + if (0 <= kd && DONGLE_MANY > kd) { + wake_up_interruptible(&peasycap->wq_audio); + JOM(4, "about to lock dongle[%i].mutex_audio\n", kd); + if (mutex_lock_interruptible(&easycapdc60_dongle[kd]. + mutex_audio)) { + SAY("ERROR: " + "cannot lock dongle[%i].mutex_audio\n", kd); + return; + } + JOM(4, "locked dongle[%i].mutex_audio\n", kd); + } else + SAY("ERROR: %i=kd is bad: cannot lock dongle\n", kd); + if (0 != snd_card_free(peasycap->psnd_card)) { + SAY("ERROR: snd_card_free() failed\n"); + } else { + peasycap->psnd_card = NULL; + (peasycap->registered_audio)--; + } + if (0 <= kd && DONGLE_MANY > kd) { + mutex_unlock(&easycapdc60_dongle[kd].mutex_audio); + JOM(4, "unlocked dongle[%i].mutex_audio\n", kd); + } + break; + } + default: + break; + } +/*---------------------------------------------------------------------------*/ +/* + * CALL easycap_delete() IF NO REMAINING REFERENCES TO peasycap + * (ALSO WHEN ALSA HAS BEEN IN USE) + */ +/*---------------------------------------------------------------------------*/ + if (!peasycap->kref.refcount.counter) { + SAM("ERROR: peasycap->kref.refcount.counter is zero " + "so cannot call kref_put()\n"); + SAM("ending unsuccessfully: may cause memory leak\n"); + return; + } + if (0 <= kd && DONGLE_MANY > kd) { + JOM(4, "about to lock dongle[%i].mutex_video\n", kd); + if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_video)) { + SAY("ERROR: cannot lock dongle[%i].mutex_video\n", kd); + SAM("ending unsuccessfully: may cause memory leak\n"); + return; + } + JOM(4, "locked dongle[%i].mutex_video\n", kd); + JOM(4, "about to lock dongle[%i].mutex_audio\n", kd); + if (mutex_lock_interruptible(&easycapdc60_dongle[kd].mutex_audio)) { + SAY("ERROR: cannot lock dongle[%i].mutex_audio\n", kd); + mutex_unlock(&(easycapdc60_dongle[kd].mutex_video)); + JOM(4, "unlocked dongle[%i].mutex_video\n", kd); + SAM("ending unsuccessfully: may cause memory leak\n"); + return; + } + JOM(4, "locked dongle[%i].mutex_audio\n", kd); + } + JOM(4, "intf[%i]: %i=peasycap->kref.refcount.counter\n", + bInterfaceNumber, (int)peasycap->kref.refcount.counter); + kref_put(&peasycap->kref, easycap_delete); + JOT(4, "intf[%i]: kref_put() done.\n", bInterfaceNumber); + if (0 <= kd && DONGLE_MANY > kd) { + mutex_unlock(&(easycapdc60_dongle[kd].mutex_audio)); + JOT(4, "unlocked dongle[%i].mutex_audio\n", kd); + mutex_unlock(&easycapdc60_dongle[kd].mutex_video); + JOT(4, "unlocked dongle[%i].mutex_video\n", kd); + } +/*---------------------------------------------------------------------------*/ + JOM(4, "ends\n"); + return; +} +/*****************************************************************************/ + +/*---------------------------------------------------------------------------*/ +/* + * PARAMETERS APPLICABLE TO ENTIRE DRIVER, I.E. BOTH VIDEO AND AUDIO + */ +/*---------------------------------------------------------------------------*/ +static struct usb_device_id easycap_usb_device_id_table[] = { + {USB_DEVICE(USB_EASYCAP_VENDOR_ID, USB_EASYCAP_PRODUCT_ID)}, + { } +}; + +MODULE_DEVICE_TABLE(usb, easycap_usb_device_id_table); +static struct usb_driver easycap_usb_driver = { + .name = "easycap", + .id_table = easycap_usb_device_id_table, + .probe = easycap_usb_probe, + .disconnect = easycap_usb_disconnect, +}; + +static int __init easycap_module_init(void) +{ + int k, rc; + + printk(KERN_INFO "Easycap version: "EASYCAP_DRIVER_VERSION "\n"); + + JOT(4, "begins. %i=debug %i=bars %i=gain\n", + easycap_debug, easycap_bars, easycap_gain); + + mutex_init(&mutex_dongle); + for (k = 0; k < DONGLE_MANY; k++) { + easycapdc60_dongle[k].peasycap = NULL; + mutex_init(&easycapdc60_dongle[k].mutex_video); + mutex_init(&easycapdc60_dongle[k].mutex_audio); + } + rc = usb_register(&easycap_usb_driver); + if (rc) + printk(KERN_ERR "Easycap: usb_register failed rc=%d\n", rc); + + return rc; +} +/*****************************************************************************/ +static void __exit easycap_module_exit(void) +{ + usb_deregister(&easycap_usb_driver); +} +/*****************************************************************************/ + +module_init(easycap_module_init); +module_exit(easycap_module_exit); + +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_settings.c b/drivers/staging/media/easycap/easycap_settings.c new file mode 100644 index 00000000..3f5f5b3e --- /dev/null +++ b/drivers/staging/media/easycap/easycap_settings.c @@ -0,0 +1,696 @@ +/****************************************************************************** +* * +* easycap_settings.c * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas + * + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include "easycap.h" + +/*---------------------------------------------------------------------------*/ +/* + * THE LEAST SIGNIFICANT BIT OF easycap_standard.mask HAS MEANING: + * 0 => 25 fps + * 1 => 30 fps + * + * THE MOST SIGNIFICANT BIT OF easycap_standard.mask HAS MEANING: + * 0 => full framerate + * 1 => 20% framerate + */ +/*---------------------------------------------------------------------------*/ +const struct easycap_standard easycap_standard[] = { + { + .mask = 0x00FF & PAL_BGHIN , + .v4l2_standard = { + .index = PAL_BGHIN, + .id = (V4L2_STD_PAL_B | + V4L2_STD_PAL_G | V4L2_STD_PAL_H | + V4L2_STD_PAL_I | V4L2_STD_PAL_N), + .name = "PAL_BGHIN", + .frameperiod = {1, 25}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_N_443 , + .v4l2_standard = { + .index = NTSC_N_443, + .id = V4L2_STD_UNKNOWN, + .name = "NTSC_N_443", + .frameperiod = {1, 25}, + .framelines = 480, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & PAL_Nc , + .v4l2_standard = { + .index = PAL_Nc, + .id = V4L2_STD_PAL_Nc, + .name = "PAL_Nc", + .frameperiod = {1, 25}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_N , + .v4l2_standard = { + .index = NTSC_N, + .id = V4L2_STD_UNKNOWN, + .name = "NTSC_N", + .frameperiod = {1, 25}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & SECAM , + .v4l2_standard = { + .index = SECAM, + .id = V4L2_STD_SECAM, + .name = "SECAM", + .frameperiod = {1, 25}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_M , + .v4l2_standard = { + .index = NTSC_M, + .id = V4L2_STD_NTSC_M, + .name = "NTSC_M", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_M_JP , + .v4l2_standard = { + .index = NTSC_M_JP, + .id = V4L2_STD_NTSC_M_JP, + .name = "NTSC_M_JP", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & PAL_60 , + .v4l2_standard = { + .index = PAL_60, + .id = V4L2_STD_PAL_60, + .name = "PAL_60", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & NTSC_443 , + .v4l2_standard = { + .index = NTSC_443, + .id = V4L2_STD_NTSC_443, + .name = "NTSC_443", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x00FF & PAL_M , + .v4l2_standard = { + .index = PAL_M, + .id = V4L2_STD_PAL_M, + .name = "PAL_M", + .frameperiod = {1, 30}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & PAL_BGHIN_SLOW), + .v4l2_standard = { + .index = PAL_BGHIN_SLOW, + .id = (V4L2_STD_PAL_B | V4L2_STD_PAL_G | + V4L2_STD_PAL_H | + V4L2_STD_PAL_I | V4L2_STD_PAL_N | + (((v4l2_std_id)0x01) << 32)), + .name = "PAL_BGHIN_SLOW", + .frameperiod = {1, 5}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_N_443_SLOW), + .v4l2_standard = { + .index = NTSC_N_443_SLOW, + .id = (V4L2_STD_UNKNOWN | (((v4l2_std_id)0x11) << 32)), + .name = "NTSC_N_443_SLOW", + .frameperiod = {1, 5}, + .framelines = 480, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & PAL_Nc_SLOW), + .v4l2_standard = { + .index = PAL_Nc_SLOW, + .id = (V4L2_STD_PAL_Nc | (((v4l2_std_id)0x01) << 32)), + .name = "PAL_Nc_SLOW", + .frameperiod = {1, 5}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_N_SLOW), + .v4l2_standard = { + .index = NTSC_N_SLOW, + .id = (V4L2_STD_UNKNOWN | (((v4l2_std_id)0x21) << 32)), + .name = "NTSC_N_SLOW", + .frameperiod = {1, 5}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & SECAM_SLOW), + .v4l2_standard = { + .index = SECAM_SLOW, + .id = (V4L2_STD_SECAM | (((v4l2_std_id)0x01) << 32)), + .name = "SECAM_SLOW", + .frameperiod = {1, 5}, + .framelines = 625, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_M_SLOW), + .v4l2_standard = { + .index = NTSC_M_SLOW, + .id = (V4L2_STD_NTSC_M | (((v4l2_std_id)0x01) << 32)), + .name = "NTSC_M_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_M_JP_SLOW), + .v4l2_standard = { + .index = NTSC_M_JP_SLOW, + .id = (V4L2_STD_NTSC_M_JP | + (((v4l2_std_id)0x01) << 32)), + .name = "NTSC_M_JP_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & PAL_60_SLOW), + .v4l2_standard = { + .index = PAL_60_SLOW, + .id = (V4L2_STD_PAL_60 | (((v4l2_std_id)0x01) << 32)), + .name = "PAL_60_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & NTSC_443_SLOW), + .v4l2_standard = { + .index = NTSC_443_SLOW, + .id = (V4L2_STD_NTSC_443 | (((v4l2_std_id)0x01) << 32)), + .name = "NTSC_443_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0x8000 | (0x00FF & PAL_M_SLOW), + .v4l2_standard = { + .index = PAL_M_SLOW, + .id = (V4L2_STD_PAL_M | (((v4l2_std_id)0x01) << 32)), + .name = "PAL_M_SLOW", + .frameperiod = {1, 6}, + .framelines = 525, + .reserved = {0, 0, 0, 0} + } + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .mask = 0xFFFF + } +}; +/*---------------------------------------------------------------------------*/ +/* + * THE 16-BIT easycap_format.mask HAS MEANING: + * (least significant) BIT 0: 0 => PAL, 25 FPS; 1 => NTSC, 30 FPS + * BITS 2-4: RESERVED FOR DIFFERENTIATING STANDARDS + * BITS 5-7: NUMBER OF BYTES PER PIXEL + * BIT 8: 0 => NATIVE BYTE ORDER; 1 => SWAPPED + * BITS 9-10: RESERVED FOR OTHER BYTE PERMUTATIONS + * BIT 11: 0 => UNDECIMATED; 1 => DECIMATED + * BIT 12: 0 => OFFER FRAMES; 1 => OFFER FIELDS + * BIT 13: 0 => FULL FRAMERATE; 1 => REDUCED + * (most significant) BITS 14-15: RESERVED FOR OTHER FIELD/FRAME OPTIONS + * IT FOLLOWS THAT: + * bytesperpixel IS ((0x00E0 & easycap_format.mask) >> 5) + * byteswaporder IS true IF (0 != (0x0100 & easycap_format.mask)) + * + * decimatepixel IS true IF (0 != (0x0800 & easycap_format.mask)) + * + * offerfields IS true IF (0 != (0x1000 & easycap_format.mask)) + */ +/*---------------------------------------------------------------------------*/ + +struct easycap_format easycap_format[1 + SETTINGS_MANY]; + +int easycap_video_fillin_formats(void) +{ + const char *name1, *name2, *name3, *name4; + struct v4l2_format *fmt; + int i, j, k, m, n; + u32 width, height, pixelformat, bytesperline, sizeimage; + u16 mask1, mask2, mask3, mask4; + enum v4l2_field field; + enum v4l2_colorspace colorspace; + + for (i = 0, n = 0; i < STANDARD_MANY; i++) { + mask1 = 0x0000; + switch (i) { + case PAL_BGHIN: { + mask1 = 0x1F & PAL_BGHIN; + name1 = "PAL_BGHIN"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case SECAM: { + mask1 = 0x1F & SECAM; + name1 = "SECAM"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_Nc: { + mask1 = 0x1F & PAL_Nc; + name1 = "PAL_Nc"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_60: { + mask1 = 0x1F & PAL_60; + name1 = "PAL_60"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_M: { + mask1 = 0x1F & PAL_M; + name1 = "PAL_M"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case NTSC_M: { + mask1 = 0x1F & NTSC_M; + name1 = "NTSC_M"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_443: { + mask1 = 0x1F & NTSC_443; + name1 = "NTSC_443"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_M_JP: { + mask1 = 0x1F & NTSC_M_JP; + name1 = "NTSC_M_JP"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_N: { + mask1 = 0x1F & NTSC_M; + name1 = "NTSC_N"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_N_443: { + mask1 = 0x1F & NTSC_N_443; + name1 = "NTSC_N_443"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case PAL_BGHIN_SLOW: { + mask1 = 0x001F & PAL_BGHIN_SLOW; + mask1 |= 0x0200; + name1 = "PAL_BGHIN_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case SECAM_SLOW: { + mask1 = 0x001F & SECAM_SLOW; + mask1 |= 0x0200; + name1 = "SECAM_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_Nc_SLOW: { + mask1 = 0x001F & PAL_Nc_SLOW; + mask1 |= 0x0200; + name1 = "PAL_Nc_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_60_SLOW: { + mask1 = 0x001F & PAL_60_SLOW; + mask1 |= 0x0200; + name1 = "PAL_60_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case PAL_M_SLOW: { + mask1 = 0x001F & PAL_M_SLOW; + mask1 |= 0x0200; + name1 = "PAL_M_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_BG; + break; + } + case NTSC_M_SLOW: { + mask1 = 0x001F & NTSC_M_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_M_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_443_SLOW: { + mask1 = 0x001F & NTSC_443_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_443_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_M_JP_SLOW: { + mask1 = 0x001F & NTSC_M_JP_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_M_JP_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_N_SLOW: { + mask1 = 0x001F & NTSC_N_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_N_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + case NTSC_N_443_SLOW: { + mask1 = 0x001F & NTSC_N_443_SLOW; + mask1 |= 0x0200; + name1 = "NTSC_N_443_SLOW"; + colorspace = V4L2_COLORSPACE_470_SYSTEM_M; + break; + } + default: + return -1; + } + + for (j = 0; j < RESOLUTION_MANY; j++) { + mask2 = 0x0000; + switch (j) { + case AT_720x576: { + if (0x1 & mask1) + continue; + name2 = "_AT_720x576"; + width = 720; + height = 576; + break; + } + case AT_704x576: { + if (0x1 & mask1) + continue; + name2 = "_AT_704x576"; + width = 704; + height = 576; + break; + } + case AT_640x480: { + name2 = "_AT_640x480"; + width = 640; + height = 480; + break; + } + case AT_720x480: { + if (!(0x1 & mask1)) + continue; + name2 = "_AT_720x480"; + width = 720; + height = 480; + break; + } + case AT_360x288: { + if (0x1 & mask1) + continue; + name2 = "_AT_360x288"; + width = 360; + height = 288; + mask2 = 0x0800; + break; + } + case AT_320x240: { + name2 = "_AT_320x240"; + width = 320; + height = 240; + mask2 = 0x0800; + break; + } + case AT_360x240: { + if (!(0x1 & mask1)) + continue; + name2 = "_AT_360x240"; + width = 360; + height = 240; + mask2 = 0x0800; + break; + } + default: + return -2; + } + + for (k = 0; k < PIXELFORMAT_MANY; k++) { + mask3 = 0x0000; + switch (k) { + case FMT_UYVY: { + name3 = __stringify(FMT_UYVY); + pixelformat = V4L2_PIX_FMT_UYVY; + mask3 |= (0x02 << 5); + break; + } + case FMT_YUY2: { + name3 = __stringify(FMT_YUY2); + pixelformat = V4L2_PIX_FMT_YUYV; + mask3 |= (0x02 << 5); + mask3 |= 0x0100; + break; + } + case FMT_RGB24: { + name3 = __stringify(FMT_RGB24); + pixelformat = V4L2_PIX_FMT_RGB24; + mask3 |= (0x03 << 5); + break; + } + case FMT_RGB32: { + name3 = __stringify(FMT_RGB32); + pixelformat = V4L2_PIX_FMT_RGB32; + mask3 |= (0x04 << 5); + break; + } + case FMT_BGR24: { + name3 = __stringify(FMT_BGR24); + pixelformat = V4L2_PIX_FMT_BGR24; + mask3 |= (0x03 << 5); + mask3 |= 0x0100; + break; + } + case FMT_BGR32: { + name3 = __stringify(FMT_BGR32); + pixelformat = V4L2_PIX_FMT_BGR32; + mask3 |= (0x04 << 5); + mask3 |= 0x0100; + break; + } + default: + return -3; + } + bytesperline = width * ((mask3 & 0x00E0) >> 5); + sizeimage = bytesperline * height; + + for (m = 0; m < INTERLACE_MANY; m++) { + mask4 = 0x0000; + switch (m) { + case FIELD_NONE: { + name4 = "-n"; + field = V4L2_FIELD_NONE; + break; + } + case FIELD_INTERLACED: { + name4 = "-i"; + mask4 |= 0x1000; + field = V4L2_FIELD_INTERLACED; + break; + } + default: + return -4; + } + if (SETTINGS_MANY <= n) + return -5; + + strcpy(easycap_format[n].name, name1); + strcat(easycap_format[n].name, name2); + strcat(easycap_format[n].name, "_"); + strcat(easycap_format[n].name, name3); + strcat(easycap_format[n].name, name4); + easycap_format[n].mask = + mask1 | mask2 | mask3 | mask4; + fmt = &easycap_format[n].v4l2_format; + + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt->fmt.pix.width = width; + fmt->fmt.pix.height = height; + fmt->fmt.pix.pixelformat = pixelformat; + fmt->fmt.pix.field = field; + fmt->fmt.pix.bytesperline = bytesperline; + fmt->fmt.pix.sizeimage = sizeimage; + fmt->fmt.pix.colorspace = colorspace; + fmt->fmt.pix.priv = 0; + n++; + } + } + } + } + if ((1 + SETTINGS_MANY) <= n) + return -6; + easycap_format[n].mask = 0xFFFF; + return n; +} +/*---------------------------------------------------------------------------*/ +struct v4l2_queryctrl easycap_control[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = SAA_0A_DEFAULT, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = SAA_0B_DEFAULT + 128, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = SAA_0C_DEFAULT + 128, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Hue", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = SAA_0D_DEFAULT + 128, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_AUDIO_VOLUME, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Volume", + .minimum = 0, + .maximum = 31, + .step = 1, + .default_value = 16, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .default_value = true, + .flags = 0, + .reserved = {0, 0} + }, +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + { + .id = 0xFFFFFFFF + } +}; +/*****************************************************************************/ diff --git a/drivers/staging/media/easycap/easycap_sound.c b/drivers/staging/media/easycap/easycap_sound.c new file mode 100644 index 00000000..8c8bcae8 --- /dev/null +++ b/drivers/staging/media/easycap/easycap_sound.c @@ -0,0 +1,750 @@ +/****************************************************************************** +* * +* easycap_sound.c * +* * +* Audio driver for EasyCAP USB2.0 Video Capture Device DC60 * +* * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas + * + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include "easycap.h" + +/*--------------------------------------------------------------------------*/ +/* + * PARAMETERS USED WHEN REGISTERING THE AUDIO INTERFACE + */ +/*--------------------------------------------------------------------------*/ +static const struct snd_pcm_hardware alsa_hardware = { + .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_32000 | SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = PAGE_SIZE * + PAGES_PER_AUDIO_FRAGMENT * + AUDIO_FRAGMENT_MANY, + .period_bytes_min = PAGE_SIZE * PAGES_PER_AUDIO_FRAGMENT, + .period_bytes_max = PAGE_SIZE * PAGES_PER_AUDIO_FRAGMENT * 2, + .periods_min = AUDIO_FRAGMENT_MANY, + .periods_max = AUDIO_FRAGMENT_MANY * 2, +}; + + +/*---------------------------------------------------------------------------*/ +/* + * SUBMIT ALL AUDIO URBS. + */ +/*---------------------------------------------------------------------------*/ +static int easycap_audio_submit_urbs(struct easycap *peasycap) +{ + struct data_urb *pdata_urb; + struct urb *purb; + struct list_head *plist_head; + int j, isbad, nospc, m, rc; + int isbuf; + + if (!peasycap->purb_audio_head) { + SAM("ERROR: peasycap->urb_audio_head uninitialized\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -EFAULT; + } + + if (peasycap->audio_isoc_streaming) { + JOM(4, "already streaming audio urbs\n"); + return 0; + } + + JOM(4, "initial submission of all audio urbs\n"); + rc = usb_set_interface(peasycap->pusb_device, + peasycap->audio_interface, + peasycap->audio_altsetting_on); + JOM(8, "usb_set_interface(.,%i,%i) returned %i\n", + peasycap->audio_interface, + peasycap->audio_altsetting_on, rc); + + isbad = 0; + nospc = 0; + m = 0; + list_for_each(plist_head, peasycap->purb_audio_head) { + pdata_urb = list_entry(plist_head, struct data_urb, list_head); + if (pdata_urb && pdata_urb->purb) { + purb = pdata_urb->purb; + isbuf = pdata_urb->isbuf; + + purb->interval = 1; + purb->dev = peasycap->pusb_device; + purb->pipe = usb_rcvisocpipe(peasycap->pusb_device, + peasycap->audio_endpointnumber); + purb->transfer_flags = URB_ISO_ASAP; + purb->transfer_buffer = peasycap->audio_isoc_buffer[isbuf].pgo; + purb->transfer_buffer_length = peasycap->audio_isoc_buffer_size; + purb->complete = easycap_alsa_complete; + purb->context = peasycap; + purb->start_frame = 0; + purb->number_of_packets = peasycap->audio_isoc_framesperdesc; + for (j = 0; j < peasycap->audio_isoc_framesperdesc; j++) { + purb->iso_frame_desc[j].offset = j * peasycap->audio_isoc_maxframesize; + purb->iso_frame_desc[j].length = peasycap->audio_isoc_maxframesize; + } + + rc = usb_submit_urb(purb, GFP_KERNEL); + if (rc) { + isbad++; + SAM("ERROR: usb_submit_urb() failed" + " for urb with rc: -%s: %d\n", + strerror(rc), rc); + } else { + m++; + } + } else { + isbad++; + } + } + if (nospc) { + SAM("-ENOSPC=usb_submit_urb() for %i urbs\n", nospc); + SAM("..... possibly inadequate USB bandwidth\n"); + peasycap->audio_eof = 1; + } + + if (isbad) + easycap_audio_kill_urbs(peasycap); + else + peasycap->audio_isoc_streaming = m; + + return 0; +} +/*---------------------------------------------------------------------------*/ +/* + * COMMON AUDIO INITIALIZATION + */ +/*---------------------------------------------------------------------------*/ +static int easycap_sound_setup(struct easycap *peasycap) +{ + int rc; + + JOM(4, "starting initialization\n"); + + if (!peasycap) { + SAY("ERROR: peasycap is NULL.\n"); + return -EFAULT; + } + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device is NULL\n"); + return -ENODEV; + } + JOM(16, "0x%08lX=peasycap->pusb_device\n", (long int)peasycap->pusb_device); + + rc = easycap_audio_setup(peasycap); + JOM(8, "audio_setup() returned %i\n", rc); + + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device has become NULL\n"); + return -ENODEV; + } +/*---------------------------------------------------------------------------*/ + if (!peasycap->pusb_device) { + SAM("ERROR: peasycap->pusb_device has become NULL\n"); + return -ENODEV; + } + rc = usb_set_interface(peasycap->pusb_device, peasycap->audio_interface, + peasycap->audio_altsetting_on); + JOM(8, "usb_set_interface(.,%i,%i) returned %i\n", peasycap->audio_interface, + peasycap->audio_altsetting_on, rc); + + rc = easycap_wakeup_device(peasycap->pusb_device); + JOM(8, "wakeup_device() returned %i\n", rc); + + peasycap->audio_eof = 0; + peasycap->audio_idle = 0; + + easycap_audio_submit_urbs(peasycap); + + JOM(4, "finished initialization\n"); + return 0; +} +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * ON COMPLETION OF AN AUDIO URB ITS DATA IS COPIED TO THE DAM BUFFER + * PROVIDED peasycap->audio_idle IS ZERO. REGARDLESS OF THIS BEING TRUE, + * IT IS RESUBMITTED PROVIDED peasycap->audio_isoc_streaming IS NOT ZERO. + */ +/*---------------------------------------------------------------------------*/ +void easycap_alsa_complete(struct urb *purb) +{ + struct easycap *peasycap; + struct snd_pcm_substream *pss; + struct snd_pcm_runtime *prt; + int dma_bytes, fragment_bytes; + int isfragment; + u8 *p1, *p2; + s16 tmp; + int i, j, more, much, rc; +#ifdef UPSAMPLE + int k; + s16 oldaudio, newaudio, delta; +#endif /*UPSAMPLE*/ + + JOT(16, "\n"); + + if (!purb) { + SAY("ERROR: purb is NULL\n"); + return; + } + peasycap = purb->context; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } + much = 0; + if (peasycap->audio_idle) { + JOM(16, "%i=audio_idle %i=audio_isoc_streaming\n", + peasycap->audio_idle, peasycap->audio_isoc_streaming); + if (peasycap->audio_isoc_streaming) + goto resubmit; + } +/*---------------------------------------------------------------------------*/ + pss = peasycap->psubstream; + if (!pss) + goto resubmit; + prt = pss->runtime; + if (!prt) + goto resubmit; + dma_bytes = (int)prt->dma_bytes; + if (0 == dma_bytes) + goto resubmit; + fragment_bytes = 4 * ((int)prt->period_size); + if (0 == fragment_bytes) + goto resubmit; +/* -------------------------------------------------------------------------*/ + if (purb->status) { + if ((-ESHUTDOWN == purb->status) || (-ENOENT == purb->status)) { + JOM(16, "urb status -ESHUTDOWN or -ENOENT\n"); + return; + } + SAM("ERROR: non-zero urb status: -%s: %d\n", + strerror(purb->status), purb->status); + goto resubmit; + } +/*---------------------------------------------------------------------------*/ +/* + * PROCEED HERE WHEN NO ERROR + */ +/*---------------------------------------------------------------------------*/ + +#ifdef UPSAMPLE + oldaudio = peasycap->oldaudio; +#endif /*UPSAMPLE*/ + + for (i = 0; i < purb->number_of_packets; i++) { + if (purb->iso_frame_desc[i].status < 0) { + SAM("-%s: %d\n", + strerror(purb->iso_frame_desc[i].status), + purb->iso_frame_desc[i].status); + } + if (purb->iso_frame_desc[i].status) { + JOM(12, "discarding audio samples because " + "%i=purb->iso_frame_desc[i].status\n", + purb->iso_frame_desc[i].status); + continue; + } + more = purb->iso_frame_desc[i].actual_length; + if (more == 0) { + peasycap->audio_mt++; + continue; + } + if (0 > more) { + SAM("MISTAKE: more is negative\n"); + return; + } + + if (peasycap->audio_mt) { + JOM(12, "%4i empty audio urb frames\n", + peasycap->audio_mt); + peasycap->audio_mt = 0; + } + + p1 = (u8 *)(purb->transfer_buffer + + purb->iso_frame_desc[i].offset); + + /* + * COPY more BYTES FROM ISOC BUFFER + * TO THE DMA BUFFER, CONVERTING + * 8-BIT MONO TO 16-BIT SIGNED + * LITTLE-ENDIAN SAMPLES IF NECESSARY + */ + while (more) { + much = dma_bytes - peasycap->dma_fill; + if (0 > much) { + SAM("MISTAKE: much is negative\n"); + return; + } + if (0 == much) { + peasycap->dma_fill = 0; + peasycap->dma_next = fragment_bytes; + JOM(8, "wrapped dma buffer\n"); + } + if (!peasycap->microphone) { + if (much > more) + much = more; + memcpy(prt->dma_area + peasycap->dma_fill, + p1, much); + p1 += much; + more -= much; + } else { +#ifdef UPSAMPLE + if (much % 16) + JOM(8, "MISTAKE? much" + " is not divisible by 16\n"); + if (much > (16 * more)) + much = 16 * more; + p2 = (u8 *)(prt->dma_area + peasycap->dma_fill); + + for (j = 0; j < (much / 16); j++) { + newaudio = ((int) *p1) - 128; + newaudio = 128 * newaudio; + + delta = (newaudio - oldaudio) / 4; + tmp = oldaudio + delta; + + for (k = 0; k < 4; k++) { + *p2 = (0x00FF & tmp); + *(p2 + 1) = (0xFF00 & tmp) >> 8; + p2 += 2; + *p2 = (0x00FF & tmp); + *(p2 + 1) = (0xFF00 & tmp) >> 8; + p2 += 2; + tmp += delta; + } + p1++; + more--; + oldaudio = tmp; + } +#else /*!UPSAMPLE*/ + if (much > (2 * more)) + much = 2 * more; + p2 = (u8 *)(prt->dma_area + peasycap->dma_fill); + + for (j = 0; j < (much / 2); j++) { + tmp = ((int) *p1) - 128; + tmp = 128 * tmp; + *p2 = (0x00FF & tmp); + *(p2 + 1) = (0xFF00 & tmp) >> 8; + p1++; + p2 += 2; + more--; + } +#endif /*UPSAMPLE*/ + } + peasycap->dma_fill += much; + if (peasycap->dma_fill >= peasycap->dma_next) { + isfragment = peasycap->dma_fill / fragment_bytes; + if (0 > isfragment) { + SAM("MISTAKE: isfragment is negative\n"); + return; + } + peasycap->dma_read = (isfragment - 1) * fragment_bytes; + peasycap->dma_next = (isfragment + 1) * fragment_bytes; + if (dma_bytes < peasycap->dma_next) + peasycap->dma_next = fragment_bytes; + + if (0 <= peasycap->dma_read) { + JOM(8, "snd_pcm_period_elapsed(), %i=" + "isfragment\n", isfragment); + snd_pcm_period_elapsed(pss); + } + } + } + +#ifdef UPSAMPLE + peasycap->oldaudio = oldaudio; +#endif /*UPSAMPLE*/ + + } +/*---------------------------------------------------------------------------*/ +/* + * RESUBMIT THIS URB + */ +/*---------------------------------------------------------------------------*/ +resubmit: + if (peasycap->audio_isoc_streaming == 0) + return; + + rc = usb_submit_urb(purb, GFP_ATOMIC); + if (rc) { + if ((-ENODEV != rc) && (-ENOENT != rc)) { + SAM("ERROR: while %i=audio_idle, usb_submit_urb failed " + "with rc: -%s :%d\n", + peasycap->audio_idle, strerror(rc), rc); + } + if (0 < peasycap->audio_isoc_streaming) + peasycap->audio_isoc_streaming--; + } + return; +} +/*****************************************************************************/ +static int easycap_alsa_open(struct snd_pcm_substream *pss) +{ + struct snd_pcm *psnd_pcm; + struct snd_card *psnd_card; + struct easycap *peasycap; + + JOT(4, "\n"); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + psnd_pcm = pss->pcm; + if (!psnd_pcm) { + SAY("ERROR: psnd_pcm is NULL\n"); + return -EFAULT; + } + psnd_card = psnd_pcm->card; + if (!psnd_card) { + SAY("ERROR: psnd_card is NULL\n"); + return -EFAULT; + } + + peasycap = psnd_card->private_data; + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if (peasycap->psnd_card != psnd_card) { + SAM("ERROR: bad peasycap->psnd_card\n"); + return -EFAULT; + } + if (peasycap->psubstream) { + SAM("ERROR: bad peasycap->psubstream\n"); + return -EFAULT; + } + pss->private_data = peasycap; + peasycap->psubstream = pss; + pss->runtime->hw = peasycap->alsa_hardware; + pss->runtime->private_data = peasycap; + pss->private_data = peasycap; + + if (0 != easycap_sound_setup(peasycap)) { + JOM(4, "ending unsuccessfully\n"); + return -EFAULT; + } + JOM(4, "ending successfully\n"); + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_close(struct snd_pcm_substream *pss) +{ + struct easycap *peasycap; + + JOT(4, "\n"); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + peasycap = snd_pcm_substream_chip(pss); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + pss->private_data = NULL; + peasycap->psubstream = NULL; + JOT(4, "ending successfully\n"); + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_vmalloc(struct snd_pcm_substream *pss, size_t sz) +{ + struct snd_pcm_runtime *prt; + JOT(4, "\n"); + + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + prt = pss->runtime; + if (!prt) { + SAY("ERROR: substream.runtime is NULL\n"); + return -EFAULT; + } + if (prt->dma_area) { + if (prt->dma_bytes > sz) + return 0; + vfree(prt->dma_area); + } + prt->dma_area = vmalloc(sz); + if (!prt->dma_area) + return -ENOMEM; + prt->dma_bytes = sz; + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_hw_params(struct snd_pcm_substream *pss, + struct snd_pcm_hw_params *phw) +{ + int rc; + + JOT(4, "%i\n", (params_buffer_bytes(phw))); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + rc = easycap_alsa_vmalloc(pss, params_buffer_bytes(phw)); + if (rc) + return rc; + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_hw_free(struct snd_pcm_substream *pss) +{ + struct snd_pcm_runtime *prt; + JOT(4, "\n"); + + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + prt = pss->runtime; + if (!prt) { + SAY("ERROR: substream.runtime is NULL\n"); + return -EFAULT; + } + if (prt->dma_area) { + JOT(8, "prt->dma_area = %p\n", prt->dma_area); + vfree(prt->dma_area); + prt->dma_area = NULL; + } else + JOT(8, "dma_area already freed\n"); + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_prepare(struct snd_pcm_substream *pss) +{ + struct easycap *peasycap; + struct snd_pcm_runtime *prt; + + JOT(4, "\n"); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + prt = pss->runtime; + peasycap = snd_pcm_substream_chip(pss); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + + JOM(16, "ALSA decides %8i Hz=rate\n", pss->runtime->rate); + JOM(16, "ALSA decides %8ld =period_size\n", pss->runtime->period_size); + JOM(16, "ALSA decides %8i =periods\n", pss->runtime->periods); + JOM(16, "ALSA decides %8ld =buffer_size\n", pss->runtime->buffer_size); + JOM(16, "ALSA decides %8zd =dma_bytes\n", pss->runtime->dma_bytes); + JOM(16, "ALSA decides %8ld =boundary\n", pss->runtime->boundary); + JOM(16, "ALSA decides %8i =period_step\n", pss->runtime->period_step); + JOM(16, "ALSA decides %8i =sample_bits\n", pss->runtime->sample_bits); + JOM(16, "ALSA decides %8i =frame_bits\n", pss->runtime->frame_bits); + JOM(16, "ALSA decides %8ld =min_align\n", pss->runtime->min_align); + JOM(12, "ALSA decides %8ld =hw_ptr_base\n", pss->runtime->hw_ptr_base); + JOM(12, "ALSA decides %8ld =hw_ptr_interrupt\n", + pss->runtime->hw_ptr_interrupt); + + if (prt->dma_bytes != 4 * ((int)prt->period_size) * ((int)prt->periods)) { + SAY("MISTAKE: unexpected ALSA parameters\n"); + return -ENOENT; + } + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_ack(struct snd_pcm_substream *pss) +{ + return 0; +} +/*****************************************************************************/ +static int easycap_alsa_trigger(struct snd_pcm_substream *pss, int cmd) +{ + struct easycap *peasycap; + + JOT(4, "%i=cmd cf %i=START %i=STOP\n", cmd, SNDRV_PCM_TRIGGER_START, + SNDRV_PCM_TRIGGER_STOP); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + peasycap = snd_pcm_substream_chip(pss); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: { + peasycap->audio_idle = 0; + break; + } + case SNDRV_PCM_TRIGGER_STOP: { + peasycap->audio_idle = 1; + break; + } + default: + return -EINVAL; + } + return 0; +} +/*****************************************************************************/ +static snd_pcm_uframes_t easycap_alsa_pointer(struct snd_pcm_substream *pss) +{ + struct easycap *peasycap; + snd_pcm_uframes_t offset; + + JOT(16, "\n"); + if (!pss) { + SAY("ERROR: pss is NULL\n"); + return -EFAULT; + } + peasycap = snd_pcm_substream_chip(pss); + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -EFAULT; + } + if ((0 != peasycap->audio_eof) || (0 != peasycap->audio_idle)) { + JOM(8, "returning -EIO because " + "%i=audio_idle %i=audio_eof\n", + peasycap->audio_idle, peasycap->audio_eof); + return -EIO; + } +/*---------------------------------------------------------------------------*/ + if (0 > peasycap->dma_read) { + JOM(8, "returning -EBUSY\n"); + return -EBUSY; + } + offset = ((snd_pcm_uframes_t)peasycap->dma_read)/4; + JOM(8, "ALSA decides %8i =hw_ptr_base\n", (int)pss->runtime->hw_ptr_base); + JOM(8, "ALSA decides %8i =hw_ptr_interrupt\n", + (int)pss->runtime->hw_ptr_interrupt); + JOM(8, "%7i=offset %7i=dma_read %7i=dma_next\n", + (int)offset, peasycap->dma_read, peasycap->dma_next); + return offset; +} +/*****************************************************************************/ +static struct page * +easycap_alsa_page(struct snd_pcm_substream *pss, unsigned long offset) +{ + return vmalloc_to_page(pss->runtime->dma_area + offset); +} +/*****************************************************************************/ + +static struct snd_pcm_ops easycap_alsa_pcm_ops = { + .open = easycap_alsa_open, + .close = easycap_alsa_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = easycap_alsa_hw_params, + .hw_free = easycap_alsa_hw_free, + .prepare = easycap_alsa_prepare, + .ack = easycap_alsa_ack, + .trigger = easycap_alsa_trigger, + .pointer = easycap_alsa_pointer, + .page = easycap_alsa_page, +}; + +/*****************************************************************************/ +/*---------------------------------------------------------------------------*/ +/* + * THE FUNCTION snd_card_create() HAS THIS_MODULE AS AN ARGUMENT. THIS + * MEANS MODULE easycap. BEWARE. +*/ +/*---------------------------------------------------------------------------*/ +int easycap_alsa_probe(struct easycap *peasycap) +{ + int rc; + struct snd_card *psnd_card; + struct snd_pcm *psnd_pcm; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return -ENODEV; + } + if (0 > peasycap->minor) { + SAY("ERROR: no minor\n"); + return -ENODEV; + } + + peasycap->alsa_hardware = alsa_hardware; + if (peasycap->microphone) { + peasycap->alsa_hardware.rates = SNDRV_PCM_RATE_32000; + peasycap->alsa_hardware.rate_min = 32000; + peasycap->alsa_hardware.rate_max = 32000; + } else { + peasycap->alsa_hardware.rates = SNDRV_PCM_RATE_48000; + peasycap->alsa_hardware.rate_min = 48000; + peasycap->alsa_hardware.rate_max = 48000; + } + + if (0 != snd_card_create(SNDRV_DEFAULT_IDX1, "easycap_alsa", + THIS_MODULE, 0, &psnd_card)) { + SAY("ERROR: Cannot do ALSA snd_card_create()\n"); + return -EFAULT; + } + + sprintf(&psnd_card->id[0], "EasyALSA%i", peasycap->minor); + strcpy(&psnd_card->driver[0], EASYCAP_DRIVER_DESCRIPTION); + strcpy(&psnd_card->shortname[0], "easycap_alsa"); + sprintf(&psnd_card->longname[0], "%s", &psnd_card->shortname[0]); + + psnd_card->dev = &peasycap->pusb_device->dev; + psnd_card->private_data = peasycap; + peasycap->psnd_card = psnd_card; + + rc = snd_pcm_new(psnd_card, "easycap_pcm", 0, 0, 1, &psnd_pcm); + if (rc) { + SAM("ERROR: Cannot do ALSA snd_pcm_new()\n"); + snd_card_free(psnd_card); + return -EFAULT; + } + + snd_pcm_set_ops(psnd_pcm, SNDRV_PCM_STREAM_CAPTURE, + &easycap_alsa_pcm_ops); + psnd_pcm->info_flags = 0; + strcpy(&psnd_pcm->name[0], &psnd_card->id[0]); + psnd_pcm->private_data = peasycap; + peasycap->psnd_pcm = psnd_pcm; + peasycap->psubstream = NULL; + + rc = snd_card_register(psnd_card); + if (rc) { + SAM("ERROR: Cannot do ALSA snd_card_register()\n"); + snd_card_free(psnd_card); + return -EFAULT; + } + + SAM("registered %s\n", &psnd_card->id[0]); + return 0; +} + diff --git a/drivers/staging/media/easycap/easycap_testcard.c b/drivers/staging/media/easycap/easycap_testcard.c new file mode 100644 index 00000000..0f71470a --- /dev/null +++ b/drivers/staging/media/easycap/easycap_testcard.c @@ -0,0 +1,155 @@ +/****************************************************************************** +* * +* easycap_testcard.c * +* * +******************************************************************************/ +/* + * + * Copyright (C) 2010 R.M. Thomas + * + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * +*/ +/*****************************************************************************/ + +#include "easycap.h" + +/*****************************************************************************/ +#define TESTCARD_BYTESPERLINE (2 * 720) +void +easycap_testcard(struct easycap *peasycap, int field) +{ + int total; + int y, u, v, r, g, b; + unsigned char uyvy[4]; + int i1, line, k, m, n, more, much, barwidth, barheight; + unsigned char bfbar[TESTCARD_BYTESPERLINE / 8], *p1, *p2; + struct data_buffer *pfield_buffer; + + if (!peasycap) { + SAY("ERROR: peasycap is NULL\n"); + return; + } + JOM(8, "%i=field\n", field); + switch (peasycap->width) { + case 720: + case 360: { + barwidth = (2 * 720) / 8; + break; + } + case 704: + case 352: { + barwidth = (2 * 704) / 8; + break; + } + case 640: + case 320: { + barwidth = (2 * 640) / 8; + break; + } + default: { + SAM("ERROR: cannot set barwidth\n"); + return; + } + } + if (TESTCARD_BYTESPERLINE < barwidth) { + SAM("ERROR: barwidth is too large\n"); + return; + } + switch (peasycap->height) { + case 576: + case 288: { + barheight = 576; + break; + } + case 480: + case 240: { + barheight = 480; + break; + } + default: { + SAM("ERROR: cannot set barheight\n"); + return; + } + } + total = 0; + k = field; + m = 0; + n = 0; + + for (line = 0; line < (barheight / 2); line++) { + for (i1 = 0; i1 < 8; i1++) { + r = (i1 * 256)/8; + g = (i1 * 256)/8; + b = (i1 * 256)/8; + + y = 299*r/1000 + 587*g/1000 + 114*b/1000 ; + u = -147*r/1000 - 289*g/1000 + 436*b/1000 ; + u = u + 128; + v = 615*r/1000 - 515*g/1000 - 100*b/1000 ; + v = v + 128; + + uyvy[0] = 0xFF & u ; + uyvy[1] = 0xFF & y ; + uyvy[2] = 0xFF & v ; + uyvy[3] = 0xFF & y ; + + p1 = &bfbar[0]; + while (p1 < &bfbar[barwidth]) { + *p1++ = uyvy[0] ; + *p1++ = uyvy[1] ; + *p1++ = uyvy[2] ; + *p1++ = uyvy[3] ; + total += 4; + } + + p1 = &bfbar[0]; + more = barwidth; + + while (more) { + if ((FIELD_BUFFER_SIZE/PAGE_SIZE) <= m) { + SAM("ERROR: bad m reached\n"); + return; + } + if (PAGE_SIZE < n) { + SAM("ERROR: bad n reached\n"); + return; + } + + if (0 > more) { + SAM("ERROR: internal fault\n"); + return; + } + + much = PAGE_SIZE - n; + if (much > more) + much = more; + pfield_buffer = &peasycap->field_buffer[k][m]; + p2 = pfield_buffer->pgo + n; + memcpy(p2, p1, much); + + p1 += much; + n += much; + more -= much; + if (PAGE_SIZE == n) { + m++; + n = 0; + } + } + } + } + return; +} diff --git a/drivers/staging/media/go7007/Kconfig b/drivers/staging/media/go7007/Kconfig new file mode 100644 index 00000000..7dfb2815 --- /dev/null +++ b/drivers/staging/media/go7007/Kconfig @@ -0,0 +1,109 @@ +config VIDEO_GO7007 + tristate "WIS GO7007 MPEG encoder support" + depends on VIDEO_DEV && PCI && I2C + depends on SND + select VIDEOBUF_DMA_SG + depends on RC_CORE + select VIDEO_TUNER + select VIDEO_TVEEPROM + select SND_PCM + select CRC32 + default N + ---help--- + This is a video4linux driver for the WIS GO7007 MPEG + encoder chip. + + To compile this driver as a module, choose M here: the + module will be called go7007 + +config VIDEO_GO7007_USB + tristate "WIS GO7007 USB support" + depends on VIDEO_GO7007 && USB + default N + ---help--- + This is a video4linux driver for the WIS GO7007 MPEG + encoder chip over USB. + + To compile this driver as a module, choose M here: the + module will be called go7007-usb + +config VIDEO_GO7007_USB_S2250_BOARD + tristate "Sensoray 2250/2251 support" + depends on VIDEO_GO7007_USB && DVB_USB + default N + ---help--- + This is a video4linux driver for the Sensoray 2250/2251 device. + + To compile this driver as a module, choose M here: the + module will be called s2250 + +config VIDEO_GO7007_OV7640 + tristate "OV7640 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the OV7640 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-ov7640 + +config VIDEO_GO7007_SAA7113 + tristate "SAA7113 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the SAA7113 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-saa7113 + +config VIDEO_GO7007_SAA7115 + tristate "SAA7115 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the SAA7115 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-saa7115 + +config VIDEO_GO7007_TW9903 + tristate "TW9903 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the TW9903 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-tw9903 + +config VIDEO_GO7007_UDA1342 + tristate "UDA1342 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the UDA1342 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-uda1342 + +config VIDEO_GO7007_SONY_TUNER + tristate "Sony tuner subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the Sony Tuner sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-sony-tuner + +config VIDEO_GO7007_TW2804 + tristate "TW2804 subdev support" + depends on VIDEO_GO7007 + default N + ---help--- + This is a video4linux driver for the TW2804 sub-device. + + To compile this driver as a module, choose M here: the + module will be called wis-tw2804 + diff --git a/drivers/staging/media/go7007/Makefile b/drivers/staging/media/go7007/Makefile new file mode 100644 index 00000000..6ee837c5 --- /dev/null +++ b/drivers/staging/media/go7007/Makefile @@ -0,0 +1,30 @@ +#obj-m += go7007.o go7007-usb.o snd-go7007.o wis-saa7115.o wis-tw9903.o \ + wis-uda1342.o wis-sony-tuner.o wis-saa7113.o wis-ov7640.o \ + wis-tw2804.o + + +obj-$(CONFIG_VIDEO_GO7007) += go7007.o +obj-$(CONFIG_VIDEO_GO7007_USB) += go7007-usb.o +obj-$(CONFIG_VIDEO_GO7007_USB_S2250_BOARD) += s2250.o s2250-loader.o +obj-$(CONFIG_VIDEO_GO7007_SAA7113) += wis-saa7113.o +obj-$(CONFIG_VIDEO_GO7007_OV7640) += wis-ov7640.o +obj-$(CONFIG_VIDEO_GO7007_SAA7115) += wis-saa7115.o +obj-$(CONFIG_VIDEO_GO7007_TW9903) += wis-tw9903.o +obj-$(CONFIG_VIDEO_GO7007_UDA1342) += wis-uda1342.o +obj-$(CONFIG_VIDEO_GO7007_SONY_TUNER) += wis-sony-tuner.o +obj-$(CONFIG_VIDEO_GO7007_TW2804) += wis-tw2804.o + +go7007-y := go7007-v4l2.o go7007-driver.o go7007-i2c.o go7007-fw.o \ + snd-go7007.o + +s2250-y := s2250-board.o + +# Uncomment when the saa7134 patches get into upstream +#obj-$(CONFIG_VIDEO_SAA7134) += saa7134-go7007.o +#ccflags-$(CONFIG_VIDEO_SAA7134:m=y) += -Idrivers/media/video/saa7134 -DSAA7134_MPEG_GO7007=3 + +# S2250 needs cypress ezusb loader from dvb-usb +ccflags-$(CONFIG_VIDEO_GO7007_USB_S2250_BOARD:m=y) += -Idrivers/media/dvb/dvb-usb + +ccflags-y += -Idrivers/media/dvb/frontends +ccflags-y += -Idrivers/media/dvb/dvb-core diff --git a/drivers/staging/media/go7007/README b/drivers/staging/media/go7007/README new file mode 100644 index 00000000..48f44763 --- /dev/null +++ b/drivers/staging/media/go7007/README @@ -0,0 +1,11 @@ +Todo: + - checkpatch.pl cleanups + - sparse cleanups + - lots of little modules, should be merged together + and added to the build. + - testing? + - handle churn in v4l layer. + +Please send patchs to Greg Kroah-Hartman and Cc: Ross +Cohen as well. + diff --git a/drivers/staging/media/go7007/go7007-driver.c b/drivers/staging/media/go7007/go7007-driver.c new file mode 100644 index 00000000..ece2dd14 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-driver.c @@ -0,0 +1,658 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "go7007-priv.h" +#include "wis-i2c.h" + +/* + * Wait for an interrupt to be delivered from the GO7007SB and return + * the associated value and data. + * + * Must be called with the hw_lock held. + */ +int go7007_read_interrupt(struct go7007 *go, u16 *value, u16 *data) +{ + go->interrupt_available = 0; + go->hpi_ops->read_interrupt(go); + if (wait_event_timeout(go->interrupt_waitq, + go->interrupt_available, 5*HZ) < 0) { + v4l2_err(&go->v4l2_dev, "timeout waiting for read interrupt\n"); + return -1; + } + if (!go->interrupt_available) + return -1; + go->interrupt_available = 0; + *value = go->interrupt_value & 0xfffe; + *data = go->interrupt_data; + return 0; +} +EXPORT_SYMBOL(go7007_read_interrupt); + +/* + * Read a register/address on the GO7007SB. + * + * Must be called with the hw_lock held. + */ +int go7007_read_addr(struct go7007 *go, u16 addr, u16 *data) +{ + int count = 100; + u16 value; + + if (go7007_write_interrupt(go, 0x0010, addr) < 0) + return -EIO; + while (count-- > 0) { + if (go7007_read_interrupt(go, &value, data) == 0 && + value == 0xa000) + return 0; + } + return -EIO; +} +EXPORT_SYMBOL(go7007_read_addr); + +/* + * Send the boot firmware to the encoder, which just wakes it up and lets + * us talk to the GPIO pins and on-board I2C adapter. + * + * Must be called with the hw_lock held. + */ +static int go7007_load_encoder(struct go7007 *go) +{ + const struct firmware *fw_entry; + char fw_name[] = "go7007fw.bin"; + void *bounce; + int fw_len, rv = 0; + u16 intr_val, intr_data; + + if (request_firmware(&fw_entry, fw_name, go->dev)) { + v4l2_err(go, "unable to load firmware from file " + "\"%s\"\n", fw_name); + return -1; + } + if (fw_entry->size < 16 || memcmp(fw_entry->data, "WISGO7007FW", 11)) { + v4l2_err(go, "file \"%s\" does not appear to be " + "go7007 firmware\n", fw_name); + release_firmware(fw_entry); + return -1; + } + fw_len = fw_entry->size - 16; + bounce = kmalloc(fw_len, GFP_KERNEL); + if (bounce == NULL) { + v4l2_err(go, "unable to allocate %d bytes for " + "firmware transfer\n", fw_len); + release_firmware(fw_entry); + return -1; + } + memcpy(bounce, fw_entry->data + 16, fw_len); + release_firmware(fw_entry); + if (go7007_interface_reset(go) < 0 || + go7007_send_firmware(go, bounce, fw_len) < 0 || + go7007_read_interrupt(go, &intr_val, &intr_data) < 0 || + (intr_val & ~0x1) != 0x5a5a) { + v4l2_err(go, "error transferring firmware\n"); + rv = -1; + } + kfree(bounce); + return rv; +} + +MODULE_FIRMWARE("go7007fw.bin"); + +/* + * Boot the encoder and register the I2C adapter if requested. Do the + * minimum initialization necessary, since the board-specific code may + * still need to probe the board ID. + * + * Must NOT be called with the hw_lock held. + */ +int go7007_boot_encoder(struct go7007 *go, int init_i2c) +{ + int ret; + + mutex_lock(&go->hw_lock); + ret = go7007_load_encoder(go); + mutex_unlock(&go->hw_lock); + if (ret < 0) + return -1; + if (!init_i2c) + return 0; + if (go7007_i2c_init(go) < 0) + return -1; + go->i2c_adapter_online = 1; + return 0; +} +EXPORT_SYMBOL(go7007_boot_encoder); + +/* + * Configure any hardware-related registers in the GO7007, such as GPIO + * pins and bus parameters, which are board-specific. This assumes + * the boot firmware has already been downloaded. + * + * Must be called with the hw_lock held. + */ +static int go7007_init_encoder(struct go7007 *go) +{ + if (go->board_info->audio_flags & GO7007_AUDIO_I2S_MASTER) { + go7007_write_addr(go, 0x1000, 0x0811); + go7007_write_addr(go, 0x1000, 0x0c11); + } + if (go->board_id == GO7007_BOARDID_MATRIX_REV) { + /* Set GPIO pin 0 to be an output (audio clock control) */ + go7007_write_addr(go, 0x3c82, 0x0001); + go7007_write_addr(go, 0x3c80, 0x00fe); + } + return 0; +} + +/* + * Send the boot firmware to the GO7007 and configure the registers. This + * is the only way to stop the encoder once it has started streaming video. + * + * Must be called with the hw_lock held. + */ +int go7007_reset_encoder(struct go7007 *go) +{ + if (go7007_load_encoder(go) < 0) + return -1; + return go7007_init_encoder(go); +} + +/* + * Attempt to instantiate an I2C client by ID, probably loading a module. + */ +static int init_i2c_module(struct i2c_adapter *adapter, const char *type, + int addr) +{ + struct go7007 *go = i2c_get_adapdata(adapter); + struct v4l2_device *v4l2_dev = &go->v4l2_dev; + + if (v4l2_i2c_new_subdev(v4l2_dev, adapter, type, addr, NULL)) + return 0; + + printk(KERN_INFO "go7007: probing for module i2c:%s failed\n", type); + return -1; +} + +/* + * Finalize the GO7007 hardware setup, register the on-board I2C adapter + * (if used on this board), load the I2C client driver for the sensor + * (SAA7115 or whatever) and other devices, and register the ALSA and V4L2 + * interfaces. + * + * Must NOT be called with the hw_lock held. + */ +int go7007_register_encoder(struct go7007 *go) +{ + int i, ret; + + printk(KERN_INFO "go7007: registering new %s\n", go->name); + + mutex_lock(&go->hw_lock); + ret = go7007_init_encoder(go); + mutex_unlock(&go->hw_lock); + if (ret < 0) + return -1; + + /* v4l2 init must happen before i2c subdevs */ + ret = go7007_v4l2_init(go); + if (ret < 0) + return ret; + + if (!go->i2c_adapter_online && + go->board_info->flags & GO7007_BOARD_USE_ONBOARD_I2C) { + if (go7007_i2c_init(go) < 0) + return -1; + go->i2c_adapter_online = 1; + } + if (go->i2c_adapter_online) { + for (i = 0; i < go->board_info->num_i2c_devs; ++i) + init_i2c_module(&go->i2c_adapter, + go->board_info->i2c_devs[i].type, + go->board_info->i2c_devs[i].addr); + if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) + i2c_clients_command(&go->i2c_adapter, + DECODER_SET_CHANNEL, &go->channel_number); + } + if (go->board_info->flags & GO7007_BOARD_HAS_AUDIO) { + go->audio_enabled = 1; + go7007_snd_init(go); + } + return 0; +} +EXPORT_SYMBOL(go7007_register_encoder); + +/* + * Send the encode firmware to the encoder, which will cause it + * to immediately start delivering the video and audio streams. + * + * Must be called with the hw_lock held. + */ +int go7007_start_encoder(struct go7007 *go) +{ + u8 *fw; + int fw_len, rv = 0, i; + u16 intr_val, intr_data; + + go->modet_enable = 0; + if (!go->dvd_mode) + for (i = 0; i < 4; ++i) { + if (go->modet[i].enable) { + go->modet_enable = 1; + continue; + } + go->modet[i].pixel_threshold = 32767; + go->modet[i].motion_threshold = 32767; + go->modet[i].mb_threshold = 32767; + } + + if (go7007_construct_fw_image(go, &fw, &fw_len) < 0) + return -1; + + if (go7007_send_firmware(go, fw, fw_len) < 0 || + go7007_read_interrupt(go, &intr_val, &intr_data) < 0) { + v4l2_err(&go->v4l2_dev, "error transferring firmware\n"); + rv = -1; + goto start_error; + } + + go->state = STATE_DATA; + go->parse_length = 0; + go->seen_frame = 0; + if (go7007_stream_start(go) < 0) { + v4l2_err(&go->v4l2_dev, "error starting stream transfer\n"); + rv = -1; + goto start_error; + } + +start_error: + kfree(fw); + return rv; +} + +/* + * Store a byte in the current video buffer, if there is one. + */ +static inline void store_byte(struct go7007_buffer *gobuf, u8 byte) +{ + if (gobuf != NULL && gobuf->bytesused < GO7007_BUF_SIZE) { + unsigned int pgidx = gobuf->offset >> PAGE_SHIFT; + unsigned int pgoff = gobuf->offset & ~PAGE_MASK; + + *((u8 *)page_address(gobuf->pages[pgidx]) + pgoff) = byte; + ++gobuf->offset; + ++gobuf->bytesused; + } +} + +/* + * Deliver the last video buffer and get a new one to start writing to. + */ +static void frame_boundary(struct go7007 *go) +{ + struct go7007_buffer *gobuf; + int i; + + if (go->active_buf) { + if (go->active_buf->modet_active) { + if (go->active_buf->bytesused + 216 < GO7007_BUF_SIZE) { + for (i = 0; i < 216; ++i) + store_byte(go->active_buf, + go->active_map[i]); + go->active_buf->bytesused -= 216; + } else + go->active_buf->modet_active = 0; + } + go->active_buf->state = BUF_STATE_DONE; + wake_up_interruptible(&go->frame_waitq); + go->active_buf = NULL; + } + list_for_each_entry(gobuf, &go->stream, stream) + if (gobuf->state == BUF_STATE_QUEUED) { + gobuf->seq = go->next_seq; + do_gettimeofday(&gobuf->timestamp); + go->active_buf = gobuf; + break; + } + ++go->next_seq; +} + +static void write_bitmap_word(struct go7007 *go) +{ + int x, y, i, stride = ((go->width >> 4) + 7) >> 3; + + for (i = 0; i < 16; ++i) { + y = (((go->parse_length - 1) << 3) + i) / (go->width >> 4); + x = (((go->parse_length - 1) << 3) + i) % (go->width >> 4); + if (stride * y + (x >> 3) < sizeof(go->active_map)) + go->active_map[stride * y + (x >> 3)] |= + (go->modet_word & 1) << (x & 0x7); + go->modet_word >>= 1; + } +} + +/* + * Parse a chunk of the video stream into frames. The frames are not + * delimited by the hardware, so we have to parse the frame boundaries + * based on the type of video stream we're receiving. + */ +void go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length) +{ + int i, seq_start_code = -1, frame_start_code = -1; + + spin_lock(&go->spinlock); + + switch (go->format) { + case GO7007_FORMAT_MPEG4: + seq_start_code = 0xB0; + frame_start_code = 0xB6; + break; + case GO7007_FORMAT_MPEG1: + case GO7007_FORMAT_MPEG2: + seq_start_code = 0xB3; + frame_start_code = 0x00; + break; + } + + for (i = 0; i < length; ++i) { + if (go->active_buf != NULL && + go->active_buf->bytesused >= GO7007_BUF_SIZE - 3) { + v4l2_info(&go->v4l2_dev, "dropping oversized frame\n"); + go->active_buf->offset -= go->active_buf->bytesused; + go->active_buf->bytesused = 0; + go->active_buf->modet_active = 0; + go->active_buf = NULL; + } + + switch (go->state) { + case STATE_DATA: + switch (buf[i]) { + case 0x00: + go->state = STATE_00; + break; + case 0xFF: + go->state = STATE_FF; + break; + default: + store_byte(go->active_buf, buf[i]); + break; + } + break; + case STATE_00: + switch (buf[i]) { + case 0x00: + go->state = STATE_00_00; + break; + case 0xFF: + store_byte(go->active_buf, 0x00); + go->state = STATE_FF; + break; + default: + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_00_00: + switch (buf[i]) { + case 0x00: + store_byte(go->active_buf, 0x00); + /* go->state remains STATE_00_00 */ + break; + case 0x01: + go->state = STATE_00_00_01; + break; + case 0xFF: + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + go->state = STATE_FF; + break; + default: + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_00_00_01: + if (buf[i] == 0xF8 && go->modet_enable == 0) { + /* MODET start code, but MODET not enabled */ + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x01); + store_byte(go->active_buf, 0xF8); + go->state = STATE_DATA; + break; + } + /* If this is the start of a new MPEG frame, + * get a new buffer */ + if ((go->format == GO7007_FORMAT_MPEG1 || + go->format == GO7007_FORMAT_MPEG2 || + go->format == GO7007_FORMAT_MPEG4) && + (buf[i] == seq_start_code || + buf[i] == 0xB8 || /* GOP code */ + buf[i] == frame_start_code)) { + if (go->active_buf == NULL || go->seen_frame) + frame_boundary(go); + if (buf[i] == frame_start_code) { + if (go->active_buf != NULL) + go->active_buf->frame_offset = + go->active_buf->offset; + go->seen_frame = 1; + } else { + go->seen_frame = 0; + } + } + /* Handle any special chunk types, or just write the + * start code to the (potentially new) buffer */ + switch (buf[i]) { + case 0xF5: /* timestamp */ + go->parse_length = 12; + go->state = STATE_UNPARSED; + break; + case 0xF6: /* vbi */ + go->state = STATE_VBI_LEN_A; + break; + case 0xF8: /* MD map */ + go->parse_length = 0; + memset(go->active_map, 0, + sizeof(go->active_map)); + go->state = STATE_MODET_MAP; + break; + case 0xFF: /* Potential JPEG start code */ + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x01); + go->state = STATE_FF; + break; + default: + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x00); + store_byte(go->active_buf, 0x01); + store_byte(go->active_buf, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_FF: + switch (buf[i]) { + case 0x00: + store_byte(go->active_buf, 0xFF); + go->state = STATE_00; + break; + case 0xFF: + store_byte(go->active_buf, 0xFF); + /* go->state remains STATE_FF */ + break; + case 0xD8: + if (go->format == GO7007_FORMAT_MJPEG) + frame_boundary(go); + /* fall through */ + default: + store_byte(go->active_buf, 0xFF); + store_byte(go->active_buf, buf[i]); + go->state = STATE_DATA; + break; + } + break; + case STATE_VBI_LEN_A: + go->parse_length = buf[i] << 8; + go->state = STATE_VBI_LEN_B; + break; + case STATE_VBI_LEN_B: + go->parse_length |= buf[i]; + if (go->parse_length > 0) + go->state = STATE_UNPARSED; + else + go->state = STATE_DATA; + break; + case STATE_MODET_MAP: + if (go->parse_length < 204) { + if (go->parse_length & 1) { + go->modet_word |= buf[i]; + write_bitmap_word(go); + } else + go->modet_word = buf[i] << 8; + } else if (go->parse_length == 207 && go->active_buf) { + go->active_buf->modet_active = buf[i]; + } + if (++go->parse_length == 208) + go->state = STATE_DATA; + break; + case STATE_UNPARSED: + if (--go->parse_length == 0) + go->state = STATE_DATA; + break; + } + } + + spin_unlock(&go->spinlock); +} +EXPORT_SYMBOL(go7007_parse_video_stream); + +/* + * Allocate a new go7007 struct. Used by the hardware-specific probe. + */ +struct go7007 *go7007_alloc(struct go7007_board_info *board, struct device *dev) +{ + struct go7007 *go; + int i; + + go = kmalloc(sizeof(struct go7007), GFP_KERNEL); + if (go == NULL) + return NULL; + go->dev = dev; + go->board_info = board; + go->board_id = 0; + go->tuner_type = -1; + go->channel_number = 0; + go->name[0] = 0; + mutex_init(&go->hw_lock); + init_waitqueue_head(&go->frame_waitq); + spin_lock_init(&go->spinlock); + go->video_dev = NULL; + go->ref_count = 0; + go->status = STATUS_INIT; + memset(&go->i2c_adapter, 0, sizeof(go->i2c_adapter)); + go->i2c_adapter_online = 0; + go->interrupt_available = 0; + init_waitqueue_head(&go->interrupt_waitq); + go->in_use = 0; + go->input = 0; + if (board->sensor_flags & GO7007_SENSOR_TV) { + go->standard = GO7007_STD_NTSC; + go->width = 720; + go->height = 480; + go->sensor_framerate = 30000; + } else { + go->standard = GO7007_STD_OTHER; + go->width = board->sensor_width; + go->height = board->sensor_height; + go->sensor_framerate = board->sensor_framerate; + } + go->encoder_v_offset = board->sensor_v_offset; + go->encoder_h_offset = board->sensor_h_offset; + go->encoder_h_halve = 0; + go->encoder_v_halve = 0; + go->encoder_subsample = 0; + go->streaming = 0; + go->format = GO7007_FORMAT_MJPEG; + go->bitrate = 1500000; + go->fps_scale = 1; + go->pali = 0; + go->aspect_ratio = GO7007_RATIO_1_1; + go->gop_size = 0; + go->ipb = 0; + go->closed_gop = 0; + go->repeat_seqhead = 0; + go->seq_header_enable = 0; + go->gop_header_enable = 0; + go->dvd_mode = 0; + go->interlace_coding = 0; + for (i = 0; i < 4; ++i) + go->modet[i].enable = 0; + for (i = 0; i < 1624; ++i) + go->modet_map[i] = 0; + go->audio_deliver = NULL; + go->audio_enabled = 0; + INIT_LIST_HEAD(&go->stream); + + return go; +} +EXPORT_SYMBOL(go7007_alloc); + +/* + * Detach and unregister the encoder. The go7007 struct won't be freed + * until v4l2 finishes releasing its resources and all associated fds are + * closed by applications. + */ +void go7007_remove(struct go7007 *go) +{ + if (go->i2c_adapter_online) { + if (i2c_del_adapter(&go->i2c_adapter) == 0) + go->i2c_adapter_online = 0; + else + v4l2_err(&go->v4l2_dev, + "error removing I2C adapter!\n"); + } + + if (go->audio_enabled) + go7007_snd_remove(go); + go7007_v4l2_remove(go); +} +EXPORT_SYMBOL(go7007_remove); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/go7007-fw.c b/drivers/staging/media/go7007/go7007-fw.c new file mode 100644 index 00000000..c9a6409e --- /dev/null +++ b/drivers/staging/media/go7007/go7007-fw.c @@ -0,0 +1,1636 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +/* + * This file contains code to generate a firmware image for the GO7007SB + * encoder. Much of the firmware is read verbatim from a file, but some of + * it concerning bitrate control and other things that can be configured at + * run-time are generated dynamically. Note that the format headers + * generated here do not affect the functioning of the encoder; they are + * merely parroted back to the host at the start of each frame. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "go7007-priv.h" + +/* Constants used in the source firmware image to describe code segments */ + +#define FLAG_MODE_MJPEG (1) +#define FLAG_MODE_MPEG1 (1<<1) +#define FLAG_MODE_MPEG2 (1<<2) +#define FLAG_MODE_MPEG4 (1<<3) +#define FLAG_MODE_H263 (1<<4) +#define FLAG_MODE_ALL (FLAG_MODE_MJPEG | FLAG_MODE_MPEG1 | \ + FLAG_MODE_MPEG2 | FLAG_MODE_MPEG4 | \ + FLAG_MODE_H263) +#define FLAG_SPECIAL (1<<8) + +#define SPECIAL_FRM_HEAD 0 +#define SPECIAL_BRC_CTRL 1 +#define SPECIAL_CONFIG 2 +#define SPECIAL_SEQHEAD 3 +#define SPECIAL_AV_SYNC 4 +#define SPECIAL_FINAL 5 +#define SPECIAL_AUDIO 6 +#define SPECIAL_MODET 7 + +/* Little data class for creating MPEG headers bit-by-bit */ + +struct code_gen { + unsigned char *p; /* destination */ + u32 a; /* collects bits at the top of the variable */ + int b; /* bit position of most recently-written bit */ + int len; /* written out so far */ +}; + +#define CODE_GEN(name, dest) struct code_gen name = { dest, 0, 32, 0 } + +#define CODE_ADD(name, val, length) do { \ + name.b -= (length); \ + name.a |= (val) << name.b; \ + while (name.b <= 24) { \ + *name.p = name.a >> 24; \ + ++name.p; \ + name.a <<= 8; \ + name.b += 8; \ + name.len += 8; \ + } \ +} while (0) + +#define CODE_LENGTH(name) (name.len + (32 - name.b)) + +/* Tables for creating the bitrate control data */ + +static const s16 converge_speed_ip[101] = { + 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, 1, 1, 1, 1, 1, 1, 1, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, + 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, + 5, 5, 5, 6, 6, 6, 7, 7, 8, 8, + 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, + 19, 20, 22, 23, 25, 27, 30, 32, 35, 38, + 41, 45, 49, 53, 58, 63, 69, 76, 83, 91, + 100 +}; + +static const s16 converge_speed_ipb[101] = { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 9, + 9, 9, 10, 10, 11, 12, 12, 13, 14, 14, + 15, 16, 17, 18, 19, 20, 22, 23, 25, 26, + 28, 30, 32, 34, 37, 40, 42, 46, 49, 53, + 57, 61, 66, 71, 77, 83, 90, 97, 106, 115, + 125, 135, 147, 161, 175, 191, 209, 228, 249, 273, + 300 +}; + +static const s16 LAMBDA_table[4][101] = { + { 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, + 19, 19, 19, 20, 20, 20, 21, 21, 22, 22, + 22, 23, 23, 24, 24, 25, 25, 25, 26, 26, + 27, 27, 28, 28, 29, 29, 30, 31, 31, 32, + 32, 33, 33, 34, 35, 35, 36, 37, 37, 38, + 39, 39, 40, 41, 42, 42, 43, 44, 45, 46, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 67, 68, 69, 70, 72, 73, 74, 76, 77, 78, + 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, + 96 + }, + { + 20, 20, 20, 21, 21, 21, 22, 22, 23, 23, + 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, + 28, 29, 29, 30, 30, 31, 31, 32, 33, 33, + 34, 34, 35, 36, 36, 37, 38, 38, 39, 40, + 40, 41, 42, 43, 43, 44, 45, 46, 47, 48, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 64, 65, 66, 67, 68, + 70, 71, 72, 73, 75, 76, 78, 79, 80, 82, + 83, 85, 86, 88, 90, 91, 93, 95, 96, 98, + 100, 102, 103, 105, 107, 109, 111, 113, 115, 117, + 120 + }, + { + 24, 24, 24, 25, 25, 26, 26, 27, 27, 28, + 28, 29, 29, 30, 30, 31, 31, 32, 33, 33, + 34, 34, 35, 36, 36, 37, 38, 38, 39, 40, + 41, 41, 42, 43, 44, 44, 45, 46, 47, 48, + 49, 50, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 62, 63, 64, 65, 66, 67, 69, + 70, 71, 72, 74, 75, 76, 78, 79, 81, 82, + 84, 85, 87, 88, 90, 92, 93, 95, 97, 98, + 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, + 120, 122, 124, 127, 129, 131, 134, 136, 138, 141, + 144 + }, + { + 32, 32, 33, 33, 34, 34, 35, 36, 36, 37, + 38, 38, 39, 40, 41, 41, 42, 43, 44, 44, + 45, 46, 47, 48, 49, 50, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 62, 63, 64, + 65, 66, 67, 69, 70, 71, 72, 74, 75, 76, + 78, 79, 81, 82, 84, 85, 87, 88, 90, 92, + 93, 95, 97, 98, 100, 102, 104, 106, 108, 110, + 112, 114, 116, 118, 120, 122, 124, 127, 129, 131, + 134, 136, 139, 141, 144, 146, 149, 152, 154, 157, + 160, 163, 166, 169, 172, 175, 178, 181, 185, 188, + 192 + } +}; + +/* MPEG blank frame generation tables */ + +enum mpeg_frame_type { + PFRAME, + BFRAME_PRE, + BFRAME_POST, + BFRAME_BIDIR, + BFRAME_EMPTY +}; + +static const u32 addrinctab[33][2] = { + { 0x01, 1 }, { 0x03, 3 }, { 0x02, 3 }, { 0x03, 4 }, + { 0x02, 4 }, { 0x03, 5 }, { 0x02, 5 }, { 0x07, 7 }, + { 0x06, 7 }, { 0x0b, 8 }, { 0x0a, 8 }, { 0x09, 8 }, + { 0x08, 8 }, { 0x07, 8 }, { 0x06, 8 }, { 0x17, 10 }, + { 0x16, 10 }, { 0x15, 10 }, { 0x14, 10 }, { 0x13, 10 }, + { 0x12, 10 }, { 0x23, 11 }, { 0x22, 11 }, { 0x21, 11 }, + { 0x20, 11 }, { 0x1f, 11 }, { 0x1e, 11 }, { 0x1d, 11 }, + { 0x1c, 11 }, { 0x1b, 11 }, { 0x1a, 11 }, { 0x19, 11 }, + { 0x18, 11 } +}; + +/* Standard JPEG tables */ + +static const u8 default_intra_quant_table[] = { + 8, 16, 19, 22, 26, 27, 29, 34, + 16, 16, 22, 24, 27, 29, 34, 37, + 19, 22, 26, 27, 29, 34, 34, 38, + 22, 22, 26, 27, 29, 34, 37, 40, + 22, 26, 27, 29, 32, 35, 40, 48, + 26, 27, 29, 32, 35, 40, 48, 58, + 26, 27, 29, 34, 38, 46, 56, 69, + 27, 29, 35, 38, 46, 56, 69, 83 +}; + +static const u8 bits_dc_luminance[] = { + 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 +}; + +static const u8 val_dc_luminance[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 +}; + +static const u8 bits_dc_chrominance[] = { + 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 +}; + +static const u8 val_dc_chrominance[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 +}; + +static const u8 bits_ac_luminance[] = { + 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d +}; + +static const u8 val_ac_luminance[] = { + 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 +}; + +static const u8 bits_ac_chrominance[] = { + 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 +}; + +static const u8 val_ac_chrominance[] = { + 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 +}; + +/* Zig-zag mapping for quant table + * + * OK, let's do this mapping on the actual table above so it doesn't have + * to be done on the fly. + */ +static const int zz[64] = { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 +}; + +static int copy_packages(__le16 *dest, u16 *src, int pkg_cnt, int space) +{ + int i, cnt = pkg_cnt * 32; + + if (space < cnt) + return -1; + + for (i = 0; i < cnt; ++i) + dest[i] = cpu_to_le16p(src + i); + + return cnt; +} + +static int mjpeg_frame_header(struct go7007 *go, unsigned char *buf, int q) +{ + int i, p = 0; + + buf[p++] = 0xff; + buf[p++] = 0xd8; + buf[p++] = 0xff; + buf[p++] = 0xdb; + buf[p++] = 0; + buf[p++] = 2 + 65; + buf[p++] = 0; + buf[p++] = default_intra_quant_table[0]; + for (i = 1; i < 64; ++i) + /* buf[p++] = (default_intra_quant_table[i] * q) >> 3; */ + buf[p++] = (default_intra_quant_table[zz[i]] * q) >> 3; + buf[p++] = 0xff; + buf[p++] = 0xc0; + buf[p++] = 0; + buf[p++] = 17; + buf[p++] = 8; + buf[p++] = go->height >> 8; + buf[p++] = go->height & 0xff; + buf[p++] = go->width >> 8; + buf[p++] = go->width & 0xff; + buf[p++] = 3; + buf[p++] = 1; + buf[p++] = 0x22; + buf[p++] = 0; + buf[p++] = 2; + buf[p++] = 0x11; + buf[p++] = 0; + buf[p++] = 3; + buf[p++] = 0x11; + buf[p++] = 0; + buf[p++] = 0xff; + buf[p++] = 0xc4; + buf[p++] = 418 >> 8; + buf[p++] = 418 & 0xff; + buf[p++] = 0x00; + memcpy(buf + p, bits_dc_luminance + 1, 16); + p += 16; + memcpy(buf + p, val_dc_luminance, sizeof(val_dc_luminance)); + p += sizeof(val_dc_luminance); + buf[p++] = 0x01; + memcpy(buf + p, bits_dc_chrominance + 1, 16); + p += 16; + memcpy(buf + p, val_dc_chrominance, sizeof(val_dc_chrominance)); + p += sizeof(val_dc_chrominance); + buf[p++] = 0x10; + memcpy(buf + p, bits_ac_luminance + 1, 16); + p += 16; + memcpy(buf + p, val_ac_luminance, sizeof(val_ac_luminance)); + p += sizeof(val_ac_luminance); + buf[p++] = 0x11; + memcpy(buf + p, bits_ac_chrominance + 1, 16); + p += 16; + memcpy(buf + p, val_ac_chrominance, sizeof(val_ac_chrominance)); + p += sizeof(val_ac_chrominance); + buf[p++] = 0xff; + buf[p++] = 0xda; + buf[p++] = 0; + buf[p++] = 12; + buf[p++] = 3; + buf[p++] = 1; + buf[p++] = 0x00; + buf[p++] = 2; + buf[p++] = 0x11; + buf[p++] = 3; + buf[p++] = 0x11; + buf[p++] = 0; + buf[p++] = 63; + buf[p++] = 0; + return p; +} + +static int gen_mjpeghdr_to_package(struct go7007 *go, __le16 *code, int space) +{ + u8 *buf; + u16 mem = 0x3e00; + unsigned int addr = 0x19; + int size = 0, i, off = 0, chunk; + + buf = kzalloc(4096, GFP_KERNEL); + if (buf == NULL) { + printk(KERN_ERR "go7007: unable to allocate 4096 bytes for " + "firmware construction\n"); + return -1; + } + + for (i = 1; i < 32; ++i) { + mjpeg_frame_header(go, buf + size, i); + size += 80; + } + chunk = mjpeg_frame_header(go, buf + size, 1); + memmove(buf + size, buf + size + 80, chunk - 80); + size += chunk - 80; + + for (i = 0; i < size; i += chunk * 2) { + if (space - off < 32) { + off = -1; + goto done; + } + + code[off + 1] = __cpu_to_le16(0x8000 | mem); + + chunk = 28; + if (mem + chunk > 0x4000) + chunk = 0x4000 - mem; + if (i + 2 * chunk > size) + chunk = (size - i) / 2; + + if (chunk < 28) { + code[off] = __cpu_to_le16(0x4000 | chunk); + code[off + 31] = __cpu_to_le16(addr++); + mem = 0x3e00; + } else { + code[off] = __cpu_to_le16(0x1000 | 28); + code[off + 31] = 0; + mem += 28; + } + + memcpy(&code[off + 2], buf + i, chunk * 2); + off += 32; + } +done: + kfree(buf); + return off; +} + +static int mpeg1_frame_header(struct go7007 *go, unsigned char *buf, + int modulo, int pict_struct, enum mpeg_frame_type frame) +{ + int i, j, mb_code, mb_len; + int rows = go->interlace_coding ? go->height / 32 : go->height / 16; + CODE_GEN(c, buf + 6); + + switch (frame) { + case PFRAME: + mb_code = 0x1; + mb_len = 3; + break; + case BFRAME_PRE: + mb_code = 0x2; + mb_len = 4; + break; + case BFRAME_POST: + mb_code = 0x2; + mb_len = 3; + break; + case BFRAME_BIDIR: + mb_code = 0x2; + mb_len = 2; + break; + default: /* keep the compiler happy */ + mb_code = mb_len = 0; + break; + } + + CODE_ADD(c, frame == PFRAME ? 0x2 : 0x3, 13); + CODE_ADD(c, 0xffff, 16); + CODE_ADD(c, go->format == GO7007_FORMAT_MPEG2 ? 0x7 : 0x4, 4); + if (frame != PFRAME) + CODE_ADD(c, go->format == GO7007_FORMAT_MPEG2 ? 0x7 : 0x4, 4); + else + CODE_ADD(c, 0, 4); /* Is this supposed to be here?? */ + CODE_ADD(c, 0, 3); /* What is this?? */ + /* Byte-align with zeros */ + j = 8 - (CODE_LENGTH(c) % 8); + if (j != 8) + CODE_ADD(c, 0, j); + + if (go->format == GO7007_FORMAT_MPEG2) { + CODE_ADD(c, 0x1, 24); + CODE_ADD(c, 0xb5, 8); + CODE_ADD(c, 0x844, 12); + CODE_ADD(c, frame == PFRAME ? 0xff : 0x44, 8); + if (go->interlace_coding) { + CODE_ADD(c, pict_struct, 4); + if (go->dvd_mode) + CODE_ADD(c, 0x000, 11); + else + CODE_ADD(c, 0x200, 11); + } else { + CODE_ADD(c, 0x3, 4); + CODE_ADD(c, 0x20c, 11); + } + /* Byte-align with zeros */ + j = 8 - (CODE_LENGTH(c) % 8); + if (j != 8) + CODE_ADD(c, 0, j); + } + + for (i = 0; i < rows; ++i) { + CODE_ADD(c, 1, 24); + CODE_ADD(c, i + 1, 8); + CODE_ADD(c, 0x2, 6); + CODE_ADD(c, 0x1, 1); + CODE_ADD(c, mb_code, mb_len); + if (go->interlace_coding) { + CODE_ADD(c, 0x1, 2); + CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1); + } + if (frame == BFRAME_BIDIR) { + CODE_ADD(c, 0x3, 2); + if (go->interlace_coding) + CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1); + } + CODE_ADD(c, 0x3, 2); + for (j = (go->width >> 4) - 2; j >= 33; j -= 33) + CODE_ADD(c, 0x8, 11); + CODE_ADD(c, addrinctab[j][0], addrinctab[j][1]); + CODE_ADD(c, mb_code, mb_len); + if (go->interlace_coding) { + CODE_ADD(c, 0x1, 2); + CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1); + } + if (frame == BFRAME_BIDIR) { + CODE_ADD(c, 0x3, 2); + if (go->interlace_coding) + CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1); + } + CODE_ADD(c, 0x3, 2); + + /* Byte-align with zeros */ + j = 8 - (CODE_LENGTH(c) % 8); + if (j != 8) + CODE_ADD(c, 0, j); + } + + i = CODE_LENGTH(c) + 4 * 8; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x01; + buf[5] = 0x00; + return i; +} + +static int mpeg1_sequence_header(struct go7007 *go, unsigned char *buf, int ext) +{ + int i, aspect_ratio, picture_rate; + CODE_GEN(c, buf + 6); + + if (go->format == GO7007_FORMAT_MPEG1) { + switch (go->aspect_ratio) { + case GO7007_RATIO_4_3: + aspect_ratio = go->standard == GO7007_STD_NTSC ? 3 : 2; + break; + case GO7007_RATIO_16_9: + aspect_ratio = go->standard == GO7007_STD_NTSC ? 5 : 4; + break; + default: + aspect_ratio = 1; + break; + } + } else { + switch (go->aspect_ratio) { + case GO7007_RATIO_4_3: + aspect_ratio = 2; + break; + case GO7007_RATIO_16_9: + aspect_ratio = 3; + break; + default: + aspect_ratio = 1; + break; + } + } + switch (go->sensor_framerate) { + case 24000: + picture_rate = 1; + break; + case 24024: + picture_rate = 2; + break; + case 25025: + picture_rate = go->interlace_coding ? 6 : 3; + break; + case 30000: + picture_rate = go->interlace_coding ? 7 : 4; + break; + case 30030: + picture_rate = go->interlace_coding ? 8 : 5; + break; + default: + picture_rate = 5; /* 30 fps seems like a reasonable default */ + break; + } + + CODE_ADD(c, go->width, 12); + CODE_ADD(c, go->height, 12); + CODE_ADD(c, aspect_ratio, 4); + CODE_ADD(c, picture_rate, 4); + CODE_ADD(c, go->format == GO7007_FORMAT_MPEG2 ? 20000 : 0x3ffff, 18); + CODE_ADD(c, 1, 1); + CODE_ADD(c, go->format == GO7007_FORMAT_MPEG2 ? 112 : 20, 10); + CODE_ADD(c, 0, 3); + + /* Byte-align with zeros */ + i = 8 - (CODE_LENGTH(c) % 8); + if (i != 8) + CODE_ADD(c, 0, i); + + if (go->format == GO7007_FORMAT_MPEG2) { + CODE_ADD(c, 0x1, 24); + CODE_ADD(c, 0xb5, 8); + CODE_ADD(c, 0x148, 12); + if (go->interlace_coding) + CODE_ADD(c, 0x20001, 20); + else + CODE_ADD(c, 0xa0001, 20); + CODE_ADD(c, 0, 16); + + /* Byte-align with zeros */ + i = 8 - (CODE_LENGTH(c) % 8); + if (i != 8) + CODE_ADD(c, 0, i); + + if (ext) { + CODE_ADD(c, 0x1, 24); + CODE_ADD(c, 0xb52, 12); + CODE_ADD(c, go->standard == GO7007_STD_NTSC ? 2 : 1, 3); + CODE_ADD(c, 0x105, 9); + CODE_ADD(c, 0x505, 16); + CODE_ADD(c, go->width, 14); + CODE_ADD(c, 1, 1); + CODE_ADD(c, go->height, 14); + + /* Byte-align with zeros */ + i = 8 - (CODE_LENGTH(c) % 8); + if (i != 8) + CODE_ADD(c, 0, i); + } + } + + i = CODE_LENGTH(c) + 4 * 8; + buf[0] = i & 0xff; + buf[1] = i >> 8; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x01; + buf[5] = 0xb3; + return i; +} + +static int gen_mpeg1hdr_to_package(struct go7007 *go, + __le16 *code, int space, int *framelen) +{ + u8 *buf; + u16 mem = 0x3e00; + unsigned int addr = 0x19; + int i, off = 0, chunk; + + buf = kzalloc(5120, GFP_KERNEL); + if (buf == NULL) { + printk(KERN_ERR "go7007: unable to allocate 5120 bytes for " + "firmware construction\n"); + return -1; + } + framelen[0] = mpeg1_frame_header(go, buf, 0, 1, PFRAME); + if (go->interlace_coding) + framelen[0] += mpeg1_frame_header(go, buf + framelen[0] / 8, + 0, 2, PFRAME); + buf[0] = framelen[0] & 0xff; + buf[1] = framelen[0] >> 8; + i = 368; + framelen[1] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_PRE); + if (go->interlace_coding) + framelen[1] += mpeg1_frame_header(go, buf + i + framelen[1] / 8, + 0, 2, BFRAME_PRE); + buf[i] = framelen[1] & 0xff; + buf[i + 1] = framelen[1] >> 8; + i += 1632; + framelen[2] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_POST); + if (go->interlace_coding) + framelen[2] += mpeg1_frame_header(go, buf + i + framelen[2] / 8, + 0, 2, BFRAME_POST); + buf[i] = framelen[2] & 0xff; + buf[i + 1] = framelen[2] >> 8; + i += 1432; + framelen[3] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_BIDIR); + if (go->interlace_coding) + framelen[3] += mpeg1_frame_header(go, buf + i + framelen[3] / 8, + 0, 2, BFRAME_BIDIR); + buf[i] = framelen[3] & 0xff; + buf[i + 1] = framelen[3] >> 8; + i += 1632 + 16; + mpeg1_sequence_header(go, buf + i, 0); + i += 40; + for (i = 0; i < 5120; i += chunk * 2) { + if (space - off < 32) { + off = -1; + goto done; + } + + code[off + 1] = __cpu_to_le16(0x8000 | mem); + + chunk = 28; + if (mem + chunk > 0x4000) + chunk = 0x4000 - mem; + if (i + 2 * chunk > 5120) + chunk = (5120 - i) / 2; + + if (chunk < 28) { + code[off] = __cpu_to_le16(0x4000 | chunk); + code[off + 31] = __cpu_to_le16(addr); + if (mem + chunk == 0x4000) { + mem = 0x3e00; + ++addr; + } + } else { + code[off] = __cpu_to_le16(0x1000 | 28); + code[off + 31] = 0; + mem += 28; + } + + memcpy(&code[off + 2], buf + i, chunk * 2); + off += 32; + } +done: + kfree(buf); + return off; +} + +static int vti_bitlen(struct go7007 *go) +{ + unsigned int i, max_time_incr = go->sensor_framerate / go->fps_scale; + + for (i = 31; (max_time_incr & ((1 << i) - 1)) == max_time_incr; --i); + return i + 1; +} + +static int mpeg4_frame_header(struct go7007 *go, unsigned char *buf, + int modulo, enum mpeg_frame_type frame) +{ + int i; + CODE_GEN(c, buf + 6); + int mb_count = (go->width >> 4) * (go->height >> 4); + + CODE_ADD(c, frame == PFRAME ? 0x1 : 0x2, 2); + if (modulo) + CODE_ADD(c, 0x1, 1); + CODE_ADD(c, 0x1, 2); + CODE_ADD(c, 0, vti_bitlen(go)); + CODE_ADD(c, 0x3, 2); + if (frame == PFRAME) + CODE_ADD(c, 0, 1); + CODE_ADD(c, 0xc, 11); + if (frame != PFRAME) + CODE_ADD(c, 0x4, 3); + if (frame != BFRAME_EMPTY) { + for (i = 0; i < mb_count; ++i) { + switch (frame) { + case PFRAME: + CODE_ADD(c, 0x1, 1); + break; + case BFRAME_PRE: + CODE_ADD(c, 0x47, 8); + break; + case BFRAME_POST: + CODE_ADD(c, 0x27, 7); + break; + case BFRAME_BIDIR: + CODE_ADD(c, 0x5f, 8); + break; + case BFRAME_EMPTY: /* keep compiler quiet */ + break; + } + } + } + + /* Byte-align with a zero followed by ones */ + i = 8 - (CODE_LENGTH(c) % 8); + CODE_ADD(c, 0, 1); + CODE_ADD(c, (1 << (i - 1)) - 1, i - 1); + + i = CODE_LENGTH(c) + 4 * 8; + buf[0] = i & 0xff; + buf[1] = i >> 8; + buf[2] = 0x00; + buf[3] = 0x00; + buf[4] = 0x01; + buf[5] = 0xb6; + return i; +} + +static int mpeg4_sequence_header(struct go7007 *go, unsigned char *buf, int ext) +{ + const unsigned char head[] = { 0x00, 0x00, 0x01, 0xb0, go->pali, + 0x00, 0x00, 0x01, 0xb5, 0x09, + 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x20, }; + int i, aspect_ratio; + int fps = go->sensor_framerate / go->fps_scale; + CODE_GEN(c, buf + 2 + sizeof(head)); + + switch (go->aspect_ratio) { + case GO7007_RATIO_4_3: + aspect_ratio = go->standard == GO7007_STD_NTSC ? 3 : 2; + break; + case GO7007_RATIO_16_9: + aspect_ratio = go->standard == GO7007_STD_NTSC ? 5 : 4; + break; + default: + aspect_ratio = 1; + break; + } + + memcpy(buf + 2, head, sizeof(head)); + CODE_ADD(c, 0x191, 17); + CODE_ADD(c, aspect_ratio, 4); + CODE_ADD(c, 0x1, 4); + CODE_ADD(c, fps, 16); + CODE_ADD(c, 0x3, 2); + CODE_ADD(c, 1001, vti_bitlen(go)); + CODE_ADD(c, 1, 1); + CODE_ADD(c, go->width, 13); + CODE_ADD(c, 1, 1); + CODE_ADD(c, go->height, 13); + CODE_ADD(c, 0x2830, 14); + + /* Byte-align */ + i = 8 - (CODE_LENGTH(c) % 8); + CODE_ADD(c, 0, 1); + CODE_ADD(c, (1 << (i - 1)) - 1, i - 1); + + i = CODE_LENGTH(c) + sizeof(head) * 8; + buf[0] = i & 0xff; + buf[1] = i >> 8; + return i; +} + +static int gen_mpeg4hdr_to_package(struct go7007 *go, + __le16 *code, int space, int *framelen) +{ + u8 *buf; + u16 mem = 0x3e00; + unsigned int addr = 0x19; + int i, off = 0, chunk; + + buf = kzalloc(5120, GFP_KERNEL); + if (buf == NULL) { + printk(KERN_ERR "go7007: unable to allocate 5120 bytes for " + "firmware construction\n"); + return -1; + } + framelen[0] = mpeg4_frame_header(go, buf, 0, PFRAME); + i = 368; + framelen[1] = mpeg4_frame_header(go, buf + i, 0, BFRAME_PRE); + i += 1632; + framelen[2] = mpeg4_frame_header(go, buf + i, 0, BFRAME_POST); + i += 1432; + framelen[3] = mpeg4_frame_header(go, buf + i, 0, BFRAME_BIDIR); + i += 1632; + mpeg4_frame_header(go, buf + i, 0, BFRAME_EMPTY); + i += 16; + mpeg4_sequence_header(go, buf + i, 0); + i += 40; + for (i = 0; i < 5120; i += chunk * 2) { + if (space - off < 32) { + off = -1; + goto done; + } + + code[off + 1] = __cpu_to_le16(0x8000 | mem); + + chunk = 28; + if (mem + chunk > 0x4000) + chunk = 0x4000 - mem; + if (i + 2 * chunk > 5120) + chunk = (5120 - i) / 2; + + if (chunk < 28) { + code[off] = __cpu_to_le16(0x4000 | chunk); + code[off + 31] = __cpu_to_le16(addr); + if (mem + chunk == 0x4000) { + mem = 0x3e00; + ++addr; + } + } else { + code[off] = __cpu_to_le16(0x1000 | 28); + code[off + 31] = 0; + mem += 28; + } + + memcpy(&code[off + 2], buf + i, chunk * 2); + off += 32; + } + mem = 0x3e00; + addr = go->ipb ? 0x14f9 : 0x0af9; + memset(buf, 0, 5120); + framelen[4] = mpeg4_frame_header(go, buf, 1, PFRAME); + i = 368; + framelen[5] = mpeg4_frame_header(go, buf + i, 1, BFRAME_PRE); + i += 1632; + framelen[6] = mpeg4_frame_header(go, buf + i, 1, BFRAME_POST); + i += 1432; + framelen[7] = mpeg4_frame_header(go, buf + i, 1, BFRAME_BIDIR); + i += 1632; + mpeg4_frame_header(go, buf + i, 1, BFRAME_EMPTY); + i += 16; + for (i = 0; i < 5120; i += chunk * 2) { + if (space - off < 32) { + off = -1; + goto done; + } + + code[off + 1] = __cpu_to_le16(0x8000 | mem); + + chunk = 28; + if (mem + chunk > 0x4000) + chunk = 0x4000 - mem; + if (i + 2 * chunk > 5120) + chunk = (5120 - i) / 2; + + if (chunk < 28) { + code[off] = __cpu_to_le16(0x4000 | chunk); + code[off + 31] = __cpu_to_le16(addr); + if (mem + chunk == 0x4000) { + mem = 0x3e00; + ++addr; + } + } else { + code[off] = __cpu_to_le16(0x1000 | 28); + code[off + 31] = 0; + mem += 28; + } + + memcpy(&code[off + 2], buf + i, chunk * 2); + off += 32; + } +done: + kfree(buf); + return off; +} + +static int brctrl_to_package(struct go7007 *go, + __le16 *code, int space, int *framelen) +{ + int converge_speed = 0; + int lambda = (go->format == GO7007_FORMAT_MJPEG || go->dvd_mode) ? + 100 : 0; + int peak_rate = 6 * go->bitrate / 5; + int vbv_buffer = go->format == GO7007_FORMAT_MJPEG ? + go->bitrate : + (go->dvd_mode ? 900000 : peak_rate); + int fps = go->sensor_framerate / go->fps_scale; + int q = 0; + /* Bizarre math below depends on rounding errors in division */ + u32 sgop_expt_addr = go->bitrate / 32 * (go->ipb ? 3 : 1) * 1001 / fps; + u32 sgop_peak_addr = peak_rate / 32 * 1001 / fps; + u32 total_expt_addr = go->bitrate / 32 * 1000 / fps * (fps / 1000); + u32 vbv_alert_addr = vbv_buffer * 3 / (4 * 32); + u32 cplx[] = { + q > 0 ? sgop_expt_addr * q : + 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32, + q > 0 ? sgop_expt_addr * q : + 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32, + q > 0 ? sgop_expt_addr * q : + 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32, + q > 0 ? sgop_expt_addr * q : + 2 * go->width * go->height * (go->ipb ? 6 : 4) / 32, + }; + u32 calc_q = q > 0 ? q : cplx[0] / sgop_expt_addr; + u16 pack[] = { + 0x200e, 0x0000, + 0xBF20, go->ipb ? converge_speed_ipb[converge_speed] + : converge_speed_ip[converge_speed], + 0xBF21, go->ipb ? 2 : 0, + 0xBF22, go->ipb ? LAMBDA_table[0][lambda / 2 + 50] + : 32767, + 0xBF23, go->ipb ? LAMBDA_table[1][lambda] : 32767, + 0xBF24, 32767, + 0xBF25, lambda > 99 ? 32767 : LAMBDA_table[3][lambda], + 0xBF26, sgop_expt_addr & 0x0000FFFF, + 0xBF27, sgop_expt_addr >> 16, + 0xBF28, sgop_peak_addr & 0x0000FFFF, + 0xBF29, sgop_peak_addr >> 16, + 0xBF2A, vbv_alert_addr & 0x0000FFFF, + 0xBF2B, vbv_alert_addr >> 16, + 0xBF2C, 0, + 0xBF2D, 0, + 0, 0, + + 0x200e, 0x0000, + 0xBF2E, vbv_alert_addr & 0x0000FFFF, + 0xBF2F, vbv_alert_addr >> 16, + 0xBF30, cplx[0] & 0x0000FFFF, + 0xBF31, cplx[0] >> 16, + 0xBF32, cplx[1] & 0x0000FFFF, + 0xBF33, cplx[1] >> 16, + 0xBF34, cplx[2] & 0x0000FFFF, + 0xBF35, cplx[2] >> 16, + 0xBF36, cplx[3] & 0x0000FFFF, + 0xBF37, cplx[3] >> 16, + 0xBF38, 0, + 0xBF39, 0, + 0xBF3A, total_expt_addr & 0x0000FFFF, + 0xBF3B, total_expt_addr >> 16, + 0, 0, + + 0x200e, 0x0000, + 0xBF3C, total_expt_addr & 0x0000FFFF, + 0xBF3D, total_expt_addr >> 16, + 0xBF3E, 0, + 0xBF3F, 0, + 0xBF48, 0, + 0xBF49, 0, + 0xBF4A, calc_q < 4 ? 4 : (calc_q > 124 ? 124 : calc_q), + 0xBF4B, 4, + 0xBF4C, 0, + 0xBF4D, 0, + 0xBF4E, 0, + 0xBF4F, 0, + 0xBF50, 0, + 0xBF51, 0, + 0, 0, + + 0x200e, 0x0000, + 0xBF40, sgop_expt_addr & 0x0000FFFF, + 0xBF41, sgop_expt_addr >> 16, + 0xBF42, 0, + 0xBF43, 0, + 0xBF44, 0, + 0xBF45, 0, + 0xBF46, (go->width >> 4) * (go->height >> 4), + 0xBF47, 0, + 0xBF64, 0, + 0xBF65, 0, + 0xBF18, framelen[4], + 0xBF19, framelen[5], + 0xBF1A, framelen[6], + 0xBF1B, framelen[7], + 0, 0, + +#if 0 + /* Remove once we don't care about matching */ + 0x200e, 0x0000, + 0xBF56, 4, + 0xBF57, 0, + 0xBF58, 5, + 0xBF59, 0, + 0xBF5A, 6, + 0xBF5B, 0, + 0xBF5C, 8, + 0xBF5D, 0, + 0xBF5E, 1, + 0xBF5F, 0, + 0xBF60, 1, + 0xBF61, 0, + 0xBF62, 0, + 0xBF63, 0, + 0, 0, +#else + 0x2008, 0x0000, + 0xBF56, 4, + 0xBF57, 0, + 0xBF58, 5, + 0xBF59, 0, + 0xBF5A, 6, + 0xBF5B, 0, + 0xBF5C, 8, + 0xBF5D, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, +#endif + + 0x200e, 0x0000, + 0xBF10, 0, + 0xBF11, 0, + 0xBF12, 0, + 0xBF13, 0, + 0xBF14, 0, + 0xBF15, 0, + 0xBF16, 0, + 0xBF17, 0, + 0xBF7E, 0, + 0xBF7F, 1, + 0xBF52, framelen[0], + 0xBF53, framelen[1], + 0xBF54, framelen[2], + 0xBF55, framelen[3], + 0, 0, + }; + + return copy_packages(code, pack, 6, space); +} + +static int config_package(struct go7007 *go, __le16 *code, int space) +{ + int fps = go->sensor_framerate / go->fps_scale / 1000; + int rows = go->interlace_coding ? go->height / 32 : go->height / 16; + int brc_window_size = fps; + int q_min = 2, q_max = 31; + int THACCoeffSet0 = 0; + u16 pack[] = { + 0x200e, 0x0000, + 0xc002, 0x14b4, + 0xc003, 0x28b4, + 0xc004, 0x3c5a, + 0xdc05, 0x2a77, + 0xc6c3, go->format == GO7007_FORMAT_MPEG4 ? 0 : + (go->format == GO7007_FORMAT_H263 ? 0 : 1), + 0xc680, go->format == GO7007_FORMAT_MPEG4 ? 0xf1 : + (go->format == GO7007_FORMAT_H263 ? 0x61 : + 0xd3), + 0xc780, 0x0140, + 0xe009, 0x0001, + 0xc60f, 0x0008, + 0xd4ff, 0x0002, + 0xe403, 2340, + 0xe406, 75, + 0xd411, 0x0001, + 0xd410, 0xa1d6, + 0x0001, 0x2801, + + 0x200d, 0x0000, + 0xe402, 0x018b, + 0xe401, 0x8b01, + 0xd472, (go->board_info->sensor_flags & + GO7007_SENSOR_TV) && + (!go->interlace_coding) ? + 0x01b0 : 0x0170, + 0xd475, (go->board_info->sensor_flags & + GO7007_SENSOR_TV) && + (!go->interlace_coding) ? + 0x0008 : 0x0009, + 0xc404, go->interlace_coding ? 0x44 : + (go->format == GO7007_FORMAT_MPEG4 ? 0x11 : + (go->format == GO7007_FORMAT_MPEG1 ? 0x02 : + (go->format == GO7007_FORMAT_MPEG2 ? 0x04 : + (go->format == GO7007_FORMAT_H263 ? 0x08 : + 0x20)))), + 0xbf0a, (go->format == GO7007_FORMAT_MPEG4 ? 8 : + (go->format == GO7007_FORMAT_MPEG1 ? 1 : + (go->format == GO7007_FORMAT_MPEG2 ? 2 : + (go->format == GO7007_FORMAT_H263 ? 4 : 16)))) | + ((go->repeat_seqhead ? 1 : 0) << 6) | + ((go->dvd_mode ? 1 : 0) << 9) | + ((go->gop_header_enable ? 1 : 0) << 10), + 0xbf0b, 0, + 0xdd5a, go->ipb ? 0x14 : 0x0a, + 0xbf0c, 0, + 0xbf0d, 0, + 0xc683, THACCoeffSet0, + 0xc40a, (go->width << 4) | rows, + 0xe01a, go->board_info->hpi_buffer_cap, + 0, 0, + 0, 0, + + 0x2008, 0, + 0xe402, 0x88, + 0xe401, 0x8f01, + 0xbf6a, 0, + 0xbf6b, 0, + 0xbf6c, 0, + 0xbf6d, 0, + 0xbf6e, 0, + 0xbf6f, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + + 0x200e, 0, + 0xbf66, brc_window_size, + 0xbf67, 0, + 0xbf68, q_min, + 0xbf69, q_max, + 0xbfe0, 0, + 0xbfe1, 0, + 0xbfe2, 0, + 0xbfe3, go->ipb ? 3 : 1, + 0xc031, go->board_info->sensor_flags & + GO7007_SENSOR_VBI ? 1 : 0, + 0xc01c, 0x1f, + 0xdd8c, 0x15, + 0xdd94, 0x15, + 0xdd88, go->ipb ? 0x1401 : 0x0a01, + 0xdd90, go->ipb ? 0x1401 : 0x0a01, + 0, 0, + + 0x200e, 0, + 0xbfe4, 0, + 0xbfe5, 0, + 0xbfe6, 0, + 0xbfe7, fps << 8, + 0xbfe8, 0x3a00, + 0xbfe9, 0, + 0xbfea, 0, + 0xbfeb, 0, + 0xbfec, (go->interlace_coding ? 1 << 15 : 0) | + (go->modet_enable ? 0xa : 0) | + (go->board_info->sensor_flags & + GO7007_SENSOR_VBI ? 1 : 0), + 0xbfed, 0, + 0xbfee, 0, + 0xbfef, 0, + 0xbff0, go->board_info->sensor_flags & + GO7007_SENSOR_TV ? 0xf060 : 0xb060, + 0xbff1, 0, + 0, 0, + }; + + return copy_packages(code, pack, 5, space); +} + +static int seqhead_to_package(struct go7007 *go, __le16 *code, int space, + int (*sequence_header_func)(struct go7007 *go, + unsigned char *buf, int ext)) +{ + int vop_time_increment_bitlength = vti_bitlen(go); + int fps = go->sensor_framerate / go->fps_scale * + (go->interlace_coding ? 2 : 1); + unsigned char buf[40] = { }; + int len = sequence_header_func(go, buf, 1); + u16 pack[] = { + 0x2006, 0, + 0xbf08, fps, + 0xbf09, 0, + 0xbff2, vop_time_increment_bitlength, + 0xbff3, (1 << vop_time_increment_bitlength) - 1, + 0xbfe6, 0, + 0xbfe7, (fps / 1000) << 8, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + + 0x2007, 0, + 0xc800, buf[2] << 8 | buf[3], + 0xc801, buf[4] << 8 | buf[5], + 0xc802, buf[6] << 8 | buf[7], + 0xc803, buf[8] << 8 | buf[9], + 0xc406, 64, + 0xc407, len - 64, + 0xc61b, 1, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + + 0x200e, 0, + 0xc808, buf[10] << 8 | buf[11], + 0xc809, buf[12] << 8 | buf[13], + 0xc80a, buf[14] << 8 | buf[15], + 0xc80b, buf[16] << 8 | buf[17], + 0xc80c, buf[18] << 8 | buf[19], + 0xc80d, buf[20] << 8 | buf[21], + 0xc80e, buf[22] << 8 | buf[23], + 0xc80f, buf[24] << 8 | buf[25], + 0xc810, buf[26] << 8 | buf[27], + 0xc811, buf[28] << 8 | buf[29], + 0xc812, buf[30] << 8 | buf[31], + 0xc813, buf[32] << 8 | buf[33], + 0xc814, buf[34] << 8 | buf[35], + 0xc815, buf[36] << 8 | buf[37], + 0, 0, + 0, 0, + 0, 0, + }; + + return copy_packages(code, pack, 3, space); +} + +static int relative_prime(int big, int little) +{ + int remainder; + + while (little != 0) { + remainder = big % little; + big = little; + little = remainder; + } + return big; +} + +static int avsync_to_package(struct go7007 *go, __le16 *code, int space) +{ + int arate = go->board_info->audio_rate * 1001 * go->fps_scale; + int ratio = arate / go->sensor_framerate; + int adjratio = ratio * 215 / 100; + int rprime = relative_prime(go->sensor_framerate, + arate % go->sensor_framerate); + int f1 = (arate % go->sensor_framerate) / rprime; + int f2 = (go->sensor_framerate - arate % go->sensor_framerate) / rprime; + u16 pack[] = { + 0x200e, 0, + 0xbf98, (u16)((-adjratio) & 0xffff), + 0xbf99, (u16)((-adjratio) >> 16), + 0xbf92, 0, + 0xbf93, 0, + 0xbff4, f1 > f2 ? f1 : f2, + 0xbff5, f1 < f2 ? f1 : f2, + 0xbff6, f1 < f2 ? ratio : ratio + 1, + 0xbff7, f1 > f2 ? ratio : ratio + 1, + 0xbff8, 0, + 0xbff9, 0, + 0xbffa, adjratio & 0xffff, + 0xbffb, adjratio >> 16, + 0xbf94, 0, + 0xbf95, 0, + 0, 0, + }; + + return copy_packages(code, pack, 1, space); +} + +static int final_package(struct go7007 *go, __le16 *code, int space) +{ + int rows = go->interlace_coding ? go->height / 32 : go->height / 16; + u16 pack[] = { + 0x8000, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + ((go->board_info->sensor_flags & GO7007_SENSOR_TV) && + (!go->interlace_coding) ? + (1 << 14) | (1 << 9) : 0) | + ((go->encoder_subsample ? 1 : 0) << 8) | + (go->board_info->sensor_flags & + GO7007_SENSOR_CONFIG_MASK), + ((go->encoder_v_halve ? 1 : 0) << 14) | + (go->encoder_v_halve ? rows << 9 : rows << 8) | + (go->encoder_h_halve ? 1 << 6 : 0) | + (go->encoder_h_halve ? go->width >> 3 : go->width >> 4), + (1 << 15) | (go->encoder_v_offset << 6) | + (1 << 7) | (go->encoder_h_offset >> 2), + (1 << 6), + 0, + 0, + ((go->fps_scale - 1) << 8) | + (go->board_info->sensor_flags & GO7007_SENSOR_TV ? + (1 << 7) : 0) | + 0x41, + go->ipb ? 0xd4c : 0x36b, + (rows << 8) | (go->width >> 4), + go->format == GO7007_FORMAT_MPEG4 ? 0x0404 : 0, + (1 << 15) | ((go->interlace_coding ? 1 : 0) << 13) | + ((go->closed_gop ? 1 : 0) << 12) | + ((go->format == GO7007_FORMAT_MPEG4 ? 1 : 0) << 11) | + /* (1 << 9) | */ + ((go->ipb ? 3 : 0) << 7) | + ((go->modet_enable ? 1 : 0) << 2) | + ((go->dvd_mode ? 1 : 0) << 1) | 1, + (go->format == GO7007_FORMAT_MPEG1 ? 0x89a0 : + (go->format == GO7007_FORMAT_MPEG2 ? 0x89a0 : + (go->format == GO7007_FORMAT_MJPEG ? 0x89a0 : + (go->format == GO7007_FORMAT_MPEG4 ? 0x8920 : + (go->format == GO7007_FORMAT_H263 ? 0x8920 : 0))))), + go->ipb ? 0x1f15 : 0x1f0b, + go->ipb ? 0x0015 : 0x000b, + go->ipb ? 0xa800 : 0x5800, + 0xffff, + 0x0020 + 0x034b * 0, + 0x0020 + 0x034b * 1, + 0x0020 + 0x034b * 2, + 0x0020 + 0x034b * 3, + 0x0020 + 0x034b * 4, + 0x0020 + 0x034b * 5, + go->ipb ? (go->gop_size / 3) : go->gop_size, + (go->height >> 4) * (go->width >> 4) * 110 / 100, + }; + + return copy_packages(code, pack, 1, space); +} + +static int audio_to_package(struct go7007 *go, __le16 *code, int space) +{ + int clock_config = ((go->board_info->audio_flags & + GO7007_AUDIO_I2S_MASTER ? 1 : 0) << 11) | + ((go->board_info->audio_flags & + GO7007_AUDIO_OKI_MODE ? 1 : 0) << 8) | + (((go->board_info->audio_bclk_div / 4) - 1) << 4) | + (go->board_info->audio_main_div - 1); + u16 pack[] = { + 0x200d, 0, + 0x9002, 0, + 0x9002, 0, + 0x9031, 0, + 0x9032, 0, + 0x9033, 0, + 0x9034, 0, + 0x9035, 0, + 0x9036, 0, + 0x9037, 0, + 0x9040, 0, + 0x9000, clock_config, + 0x9001, (go->board_info->audio_flags & 0xffff) | + (1 << 9), + 0x9000, ((go->board_info->audio_flags & + GO7007_AUDIO_I2S_MASTER ? + 1 : 0) << 10) | + clock_config, + 0, 0, + 0, 0, + 0x2005, 0, + 0x9041, 0, + 0x9042, 256, + 0x9043, 0, + 0x9044, 16, + 0x9045, 16, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }; + + return copy_packages(code, pack, 2, space); +} + +static int modet_to_package(struct go7007 *go, __le16 *code, int space) +{ + int ret, mb, i, addr, cnt = 0; + u16 pack[32]; + u16 thresholds[] = { + 0x200e, 0, + 0xbf82, go->modet[0].pixel_threshold, + 0xbf83, go->modet[1].pixel_threshold, + 0xbf84, go->modet[2].pixel_threshold, + 0xbf85, go->modet[3].pixel_threshold, + 0xbf86, go->modet[0].motion_threshold, + 0xbf87, go->modet[1].motion_threshold, + 0xbf88, go->modet[2].motion_threshold, + 0xbf89, go->modet[3].motion_threshold, + 0xbf8a, go->modet[0].mb_threshold, + 0xbf8b, go->modet[1].mb_threshold, + 0xbf8c, go->modet[2].mb_threshold, + 0xbf8d, go->modet[3].mb_threshold, + 0xbf8e, 0, + 0xbf8f, 0, + 0, 0, + }; + + ret = copy_packages(code, thresholds, 1, space); + if (ret < 0) + return -1; + cnt += ret; + + addr = 0xbac0; + memset(pack, 0, 64); + i = 0; + for (mb = 0; mb < 1624; ++mb) { + pack[i * 2 + 3] <<= 2; + pack[i * 2 + 3] |= go->modet_map[mb]; + if (mb % 8 != 7) + continue; + pack[i * 2 + 2] = addr++; + ++i; + if (i == 10 || mb == 1623) { + pack[0] = 0x2000 | i; + ret = copy_packages(code + cnt, pack, 1, space - cnt); + if (ret < 0) + return -1; + cnt += ret; + i = 0; + memset(pack, 0, 64); + } + pack[i * 2 + 3] = 0; + } + + memset(pack, 0, 64); + i = 0; + for (addr = 0xbb90; addr < 0xbbfa; ++addr) { + pack[i * 2 + 2] = addr; + pack[i * 2 + 3] = 0; + ++i; + if (i == 10 || addr == 0xbbf9) { + pack[0] = 0x2000 | i; + ret = copy_packages(code + cnt, pack, 1, space - cnt); + if (ret < 0) + return -1; + cnt += ret; + i = 0; + memset(pack, 0, 64); + } + } + return cnt; +} + +static int do_special(struct go7007 *go, u16 type, __le16 *code, int space, + int *framelen) +{ + switch (type) { + case SPECIAL_FRM_HEAD: + switch (go->format) { + case GO7007_FORMAT_MJPEG: + return gen_mjpeghdr_to_package(go, code, space); + case GO7007_FORMAT_MPEG1: + case GO7007_FORMAT_MPEG2: + return gen_mpeg1hdr_to_package(go, code, space, + framelen); + case GO7007_FORMAT_MPEG4: + return gen_mpeg4hdr_to_package(go, code, space, + framelen); + } + case SPECIAL_BRC_CTRL: + return brctrl_to_package(go, code, space, framelen); + case SPECIAL_CONFIG: + return config_package(go, code, space); + case SPECIAL_SEQHEAD: + switch (go->format) { + case GO7007_FORMAT_MPEG1: + case GO7007_FORMAT_MPEG2: + return seqhead_to_package(go, code, space, + mpeg1_sequence_header); + case GO7007_FORMAT_MPEG4: + return seqhead_to_package(go, code, space, + mpeg4_sequence_header); + default: + return 0; + } + case SPECIAL_AV_SYNC: + return avsync_to_package(go, code, space); + case SPECIAL_FINAL: + return final_package(go, code, space); + case SPECIAL_AUDIO: + return audio_to_package(go, code, space); + case SPECIAL_MODET: + return modet_to_package(go, code, space); + } + printk(KERN_ERR + "go7007: firmware file contains unsupported feature %04x\n", + type); + return -1; +} + +int go7007_construct_fw_image(struct go7007 *go, u8 **fw, int *fwlen) +{ + const struct firmware *fw_entry; + __le16 *code, *src; + int framelen[8] = { }; /* holds the lengths of empty frame templates */ + int codespace = 64 * 1024, i = 0, srclen, chunk_len, chunk_flags; + int mode_flag; + int ret; + + switch (go->format) { + case GO7007_FORMAT_MJPEG: + mode_flag = FLAG_MODE_MJPEG; + break; + case GO7007_FORMAT_MPEG1: + mode_flag = FLAG_MODE_MPEG1; + break; + case GO7007_FORMAT_MPEG2: + mode_flag = FLAG_MODE_MPEG2; + break; + case GO7007_FORMAT_MPEG4: + mode_flag = FLAG_MODE_MPEG4; + break; + default: + return -1; + } + if (request_firmware(&fw_entry, go->board_info->firmware, go->dev)) { + printk(KERN_ERR + "go7007: unable to load firmware from file \"%s\"\n", + go->board_info->firmware); + return -1; + } + code = kzalloc(codespace * 2, GFP_KERNEL); + if (code == NULL) { + printk(KERN_ERR "go7007: unable to allocate %d bytes for " + "firmware construction\n", codespace * 2); + goto fw_failed; + } + src = (__le16 *)fw_entry->data; + srclen = fw_entry->size / 2; + while (srclen >= 2) { + chunk_flags = __le16_to_cpu(src[0]); + chunk_len = __le16_to_cpu(src[1]); + if (chunk_len + 2 > srclen) { + printk(KERN_ERR "go7007: firmware file \"%s\" " + "appears to be corrupted\n", + go->board_info->firmware); + goto fw_failed; + } + if (chunk_flags & mode_flag) { + if (chunk_flags & FLAG_SPECIAL) { + ret = do_special(go, __le16_to_cpu(src[2]), + &code[i], codespace - i, framelen); + if (ret < 0) { + printk(KERN_ERR "go7007: insufficient " + "memory for firmware " + "construction\n"); + goto fw_failed; + } + i += ret; + } else { + if (codespace - i < chunk_len) { + printk(KERN_ERR "go7007: insufficient " + "memory for firmware " + "construction\n"); + goto fw_failed; + } + memcpy(&code[i], &src[2], chunk_len * 2); + i += chunk_len; + } + } + srclen -= chunk_len + 2; + src += chunk_len + 2; + } + release_firmware(fw_entry); + *fw = (u8 *)code; + *fwlen = i * 2; + return 0; + +fw_failed: + kfree(code); + release_firmware(fw_entry); + return -1; +} diff --git a/drivers/staging/media/go7007/go7007-i2c.c b/drivers/staging/media/go7007/go7007-i2c.c new file mode 100644 index 00000000..6bc82aae --- /dev/null +++ b/drivers/staging/media/go7007/go7007-i2c.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "go7007-priv.h" +#include "wis-i2c.h" + +/********************* Driver for on-board I2C adapter *********************/ + +/* #define GO7007_I2C_DEBUG */ + +#define SPI_I2C_ADDR_BASE 0x1400 +#define STATUS_REG_ADDR (SPI_I2C_ADDR_BASE + 0x2) +#define I2C_CTRL_REG_ADDR (SPI_I2C_ADDR_BASE + 0x6) +#define I2C_DEV_UP_ADDR_REG_ADDR (SPI_I2C_ADDR_BASE + 0x7) +#define I2C_LO_ADDR_REG_ADDR (SPI_I2C_ADDR_BASE + 0x8) +#define I2C_DATA_REG_ADDR (SPI_I2C_ADDR_BASE + 0x9) +#define I2C_CLKFREQ_REG_ADDR (SPI_I2C_ADDR_BASE + 0xa) + +#define I2C_STATE_MASK 0x0007 +#define I2C_READ_READY_MASK 0x0008 + +/* There is only one I2C port on the TW2804 that feeds all four GO7007 VIPs + * on the Adlink PCI-MPG24, so access is shared between all of them. */ +static DEFINE_MUTEX(adlink_mpg24_i2c_lock); + +static int go7007_i2c_xfer(struct go7007 *go, u16 addr, int read, + u16 command, int flags, u8 *data) +{ + int i, ret = -1; + u16 val; + + if (go->status == STATUS_SHUTDOWN) + return -1; + +#ifdef GO7007_I2C_DEBUG + if (read) + printk(KERN_DEBUG "go7007-i2c: reading 0x%02x on 0x%02x\n", + command, addr); + else + printk(KERN_DEBUG + "go7007-i2c: writing 0x%02x to 0x%02x on 0x%02x\n", + *data, command, addr); +#endif + + mutex_lock(&go->hw_lock); + + if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) { + /* Bridge the I2C port on this GO7007 to the shared bus */ + mutex_lock(&adlink_mpg24_i2c_lock); + go7007_write_addr(go, 0x3c82, 0x0020); + } + + /* Wait for I2C adapter to be ready */ + for (i = 0; i < 10; ++i) { + if (go7007_read_addr(go, STATUS_REG_ADDR, &val) < 0) + goto i2c_done; + if (!(val & I2C_STATE_MASK)) + break; + msleep(100); + } + if (i == 10) { + printk(KERN_ERR "go7007-i2c: I2C adapter is hung\n"); + goto i2c_done; + } + + /* Set target register (command) */ + go7007_write_addr(go, I2C_CTRL_REG_ADDR, flags); + go7007_write_addr(go, I2C_LO_ADDR_REG_ADDR, command); + + /* If we're writing, send the data and target address and we're done */ + if (!read) { + go7007_write_addr(go, I2C_DATA_REG_ADDR, *data); + go7007_write_addr(go, I2C_DEV_UP_ADDR_REG_ADDR, + (addr << 9) | (command >> 8)); + ret = 0; + goto i2c_done; + } + + /* Otherwise, we're reading. First clear i2c_rx_data_rdy. */ + if (go7007_read_addr(go, I2C_DATA_REG_ADDR, &val) < 0) + goto i2c_done; + + /* Send the target address plus read flag */ + go7007_write_addr(go, I2C_DEV_UP_ADDR_REG_ADDR, + (addr << 9) | 0x0100 | (command >> 8)); + + /* Wait for i2c_rx_data_rdy */ + for (i = 0; i < 10; ++i) { + if (go7007_read_addr(go, STATUS_REG_ADDR, &val) < 0) + goto i2c_done; + if (val & I2C_READ_READY_MASK) + break; + msleep(100); + } + if (i == 10) { + printk(KERN_ERR "go7007-i2c: I2C adapter is hung\n"); + goto i2c_done; + } + + /* Retrieve the read byte */ + if (go7007_read_addr(go, I2C_DATA_REG_ADDR, &val) < 0) + goto i2c_done; + *data = val; + ret = 0; + +i2c_done: + if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) { + /* Isolate the I2C port on this GO7007 from the shared bus */ + go7007_write_addr(go, 0x3c82, 0x0000); + mutex_unlock(&adlink_mpg24_i2c_lock); + } + mutex_unlock(&go->hw_lock); + return ret; +} + +static int go7007_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + struct go7007 *go = i2c_get_adapdata(adapter); + + if (size != I2C_SMBUS_BYTE_DATA) + return -1; + return go7007_i2c_xfer(go, addr, read_write == I2C_SMBUS_READ, command, + flags & I2C_CLIENT_SCCB ? 0x10 : 0x00, &data->byte); +} + +/* VERY LIMITED I2C master xfer function -- only needed because the + * SMBus functions only support 8-bit commands and the SAA7135 uses + * 16-bit commands. The I2C interface on the GO7007, as limited as + * it is, does support this mode. */ + +static int go7007_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg msgs[], int num) +{ + struct go7007 *go = i2c_get_adapdata(adapter); + int i; + + for (i = 0; i < num; ++i) { + /* We can only do two things here -- write three bytes, or + * write two bytes and read one byte. */ + if (msgs[i].len == 2) { + if (i + 1 == num || msgs[i].addr != msgs[i + 1].addr || + (msgs[i].flags & I2C_M_RD) || + !(msgs[i + 1].flags & I2C_M_RD) || + msgs[i + 1].len != 1) + return -1; + if (go7007_i2c_xfer(go, msgs[i].addr, 1, + (msgs[i].buf[0] << 8) | msgs[i].buf[1], + 0x01, &msgs[i + 1].buf[0]) < 0) + return -1; + ++i; + } else if (msgs[i].len == 3) { + if (msgs[i].flags & I2C_M_RD) + return -1; + if (msgs[i].len != 3) + return -1; + if (go7007_i2c_xfer(go, msgs[i].addr, 0, + (msgs[i].buf[0] << 8) | msgs[i].buf[1], + 0x01, &msgs[i].buf[2]) < 0) + return -1; + } else + return -1; + } + + return 0; +} + +static u32 go7007_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_BYTE_DATA; +} + +static struct i2c_algorithm go7007_algo = { + .smbus_xfer = go7007_smbus_xfer, + .master_xfer = go7007_i2c_master_xfer, + .functionality = go7007_functionality, +}; + +static struct i2c_adapter go7007_adap_templ = { + .owner = THIS_MODULE, + .name = "WIS GO7007SB", + .algo = &go7007_algo, +}; + +int go7007_i2c_init(struct go7007 *go) +{ + memcpy(&go->i2c_adapter, &go7007_adap_templ, + sizeof(go7007_adap_templ)); + go->i2c_adapter.dev.parent = go->dev; + i2c_set_adapdata(&go->i2c_adapter, go); + if (i2c_add_adapter(&go->i2c_adapter) < 0) { + printk(KERN_ERR + "go7007-i2c: error: i2c_add_adapter failed\n"); + return -1; + } + return 0; +} diff --git a/drivers/staging/media/go7007/go7007-priv.h b/drivers/staging/media/go7007/go7007-priv.h new file mode 100644 index 00000000..b58c394c --- /dev/null +++ b/drivers/staging/media/go7007/go7007-priv.h @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +/* + * This is the private include file for the go7007 driver. It should not + * be included by anybody but the driver itself, and especially not by + * user-space applications. + */ + +#include + +struct go7007; + +/* IDs to activate board-specific support code */ +#define GO7007_BOARDID_MATRIX_II 0 +#define GO7007_BOARDID_MATRIX_RELOAD 1 +#define GO7007_BOARDID_STAR_TREK 2 +#define GO7007_BOARDID_PCI_VOYAGER 3 +#define GO7007_BOARDID_XMEN 4 +#define GO7007_BOARDID_XMEN_II 5 +#define GO7007_BOARDID_XMEN_III 6 +#define GO7007_BOARDID_MATRIX_REV 7 +#define GO7007_BOARDID_PX_M402U 16 +#define GO7007_BOARDID_PX_TV402U_ANY 17 /* need to check tuner model */ +#define GO7007_BOARDID_PX_TV402U_NA 18 /* detected NTSC tuner */ +#define GO7007_BOARDID_PX_TV402U_EU 19 /* detected PAL tuner */ +#define GO7007_BOARDID_PX_TV402U_JP 20 /* detected NTSC-J tuner */ +#define GO7007_BOARDID_LIFEVIEW_LR192 21 /* TV Walker Ultra */ +#define GO7007_BOARDID_ENDURA 22 +#define GO7007_BOARDID_ADLINK_MPG24 23 +#define GO7007_BOARDID_SENSORAY_2250 24 /* Sensoray 2250/2251 */ + +/* Various characteristics of each board */ +#define GO7007_BOARD_HAS_AUDIO (1<<0) +#define GO7007_BOARD_USE_ONBOARD_I2C (1<<1) +#define GO7007_BOARD_HAS_TUNER (1<<2) + +/* Characteristics of sensor devices */ +#define GO7007_SENSOR_VALID_POLAR (1<<0) +#define GO7007_SENSOR_HREF_POLAR (1<<1) +#define GO7007_SENSOR_VREF_POLAR (1<<2) +#define GO7007_SENSOR_FIELD_ID_POLAR (1<<3) +#define GO7007_SENSOR_BIT_WIDTH (1<<4) +#define GO7007_SENSOR_VALID_ENABLE (1<<5) +#define GO7007_SENSOR_656 (1<<6) +#define GO7007_SENSOR_CONFIG_MASK 0x7f +#define GO7007_SENSOR_TV (1<<7) +#define GO7007_SENSOR_VBI (1<<8) +#define GO7007_SENSOR_SCALING (1<<9) + +/* Characteristics of audio sensor devices */ +#define GO7007_AUDIO_I2S_MODE_1 (1) +#define GO7007_AUDIO_I2S_MODE_2 (2) +#define GO7007_AUDIO_I2S_MODE_3 (3) +#define GO7007_AUDIO_BCLK_POLAR (1<<2) +#define GO7007_AUDIO_WORD_14 (14<<4) +#define GO7007_AUDIO_WORD_16 (16<<4) +#define GO7007_AUDIO_ONE_CHANNEL (1<<11) +#define GO7007_AUDIO_I2S_MASTER (1<<16) +#define GO7007_AUDIO_OKI_MODE (1<<17) + +struct go7007_board_info { + char *firmware; + unsigned int flags; + int hpi_buffer_cap; + unsigned int sensor_flags; + int sensor_width; + int sensor_height; + int sensor_framerate; + int sensor_h_offset; + int sensor_v_offset; + unsigned int audio_flags; + int audio_rate; + int audio_bclk_div; + int audio_main_div; + int num_i2c_devs; + struct { + const char *type; + int id; + int addr; + } i2c_devs[4]; + int num_inputs; + struct { + int video_input; + int audio_input; + char *name; + } inputs[4]; +}; + +struct go7007_hpi_ops { + int (*interface_reset)(struct go7007 *go); + int (*write_interrupt)(struct go7007 *go, int addr, int data); + int (*read_interrupt)(struct go7007 *go); + int (*stream_start)(struct go7007 *go); + int (*stream_stop)(struct go7007 *go); + int (*send_firmware)(struct go7007 *go, u8 *data, int len); + int (*send_command)(struct go7007 *go, unsigned int cmd, void *arg); +}; + +/* The video buffer size must be a multiple of PAGE_SIZE */ +#define GO7007_BUF_PAGES (128 * 1024 / PAGE_SIZE) +#define GO7007_BUF_SIZE (GO7007_BUF_PAGES << PAGE_SHIFT) + +struct go7007_buffer { + struct go7007 *go; /* Reverse reference for VMA ops */ + int index; /* Reverse reference for DQBUF */ + enum { BUF_STATE_IDLE, BUF_STATE_QUEUED, BUF_STATE_DONE } state; + u32 seq; + struct timeval timestamp; + struct list_head stream; + struct page *pages[GO7007_BUF_PAGES + 1]; /* extra for straddling */ + unsigned long user_addr; + unsigned int page_count; + unsigned int offset; + unsigned int bytesused; + unsigned int frame_offset; + u32 modet_active; + int mapped; +}; + +struct go7007_file { + struct go7007 *go; + struct mutex lock; + int buf_count; + struct go7007_buffer *bufs; +}; + +#define GO7007_FORMAT_MJPEG 0 +#define GO7007_FORMAT_MPEG4 1 +#define GO7007_FORMAT_MPEG1 2 +#define GO7007_FORMAT_MPEG2 3 +#define GO7007_FORMAT_H263 4 + +#define GO7007_RATIO_1_1 0 +#define GO7007_RATIO_4_3 1 +#define GO7007_RATIO_16_9 2 + +enum go7007_parser_state { + STATE_DATA, + STATE_00, + STATE_00_00, + STATE_00_00_01, + STATE_FF, + STATE_VBI_LEN_A, + STATE_VBI_LEN_B, + STATE_MODET_MAP, + STATE_UNPARSED, +}; + +struct go7007 { + struct device *dev; + struct go7007_board_info *board_info; + unsigned int board_id; + int tuner_type; + int channel_number; /* for multi-channel boards like Adlink PCI-MPG24 */ + char name[64]; + struct video_device *video_dev; + struct v4l2_device v4l2_dev; + int ref_count; + enum { STATUS_INIT, STATUS_ONLINE, STATUS_SHUTDOWN } status; + spinlock_t spinlock; + struct mutex hw_lock; + int streaming; + int in_use; + int audio_enabled; + + /* Video input */ + int input; + enum { GO7007_STD_NTSC, GO7007_STD_PAL, GO7007_STD_OTHER } standard; + int sensor_framerate; + int width; + int height; + int encoder_h_offset; + int encoder_v_offset; + unsigned int encoder_h_halve:1; + unsigned int encoder_v_halve:1; + unsigned int encoder_subsample:1; + + /* Encoder config */ + int format; + int bitrate; + int fps_scale; + int pali; + int aspect_ratio; + int gop_size; + unsigned int ipb:1; + unsigned int closed_gop:1; + unsigned int repeat_seqhead:1; + unsigned int seq_header_enable:1; + unsigned int gop_header_enable:1; + unsigned int dvd_mode:1; + unsigned int interlace_coding:1; + + /* Motion detection */ + unsigned int modet_enable:1; + struct { + unsigned int enable:1; + int pixel_threshold; + int motion_threshold; + int mb_threshold; + } modet[4]; + unsigned char modet_map[1624]; + unsigned char active_map[216]; + + /* Video streaming */ + struct go7007_buffer *active_buf; + enum go7007_parser_state state; + int parse_length; + u16 modet_word; + int seen_frame; + u32 next_seq; + struct list_head stream; + wait_queue_head_t frame_waitq; + + /* Audio streaming */ + void (*audio_deliver)(struct go7007 *go, u8 *buf, int length); + void *snd_context; + + /* I2C */ + int i2c_adapter_online; + struct i2c_adapter i2c_adapter; + + /* HPI driver */ + struct go7007_hpi_ops *hpi_ops; + void *hpi_context; + int interrupt_available; + wait_queue_head_t interrupt_waitq; + unsigned short interrupt_value; + unsigned short interrupt_data; +}; + +static inline struct go7007 *to_go7007(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct go7007, v4l2_dev); +} + +/* All of these must be called with the hpi_lock mutex held! */ +#define go7007_interface_reset(go) \ + ((go)->hpi_ops->interface_reset(go)) +#define go7007_write_interrupt(go, x, y) \ + ((go)->hpi_ops->write_interrupt)((go), (x), (y)) +#define go7007_stream_start(go) \ + ((go)->hpi_ops->stream_start(go)) +#define go7007_stream_stop(go) \ + ((go)->hpi_ops->stream_stop(go)) +#define go7007_send_firmware(go, x, y) \ + ((go)->hpi_ops->send_firmware)((go), (x), (y)) +#define go7007_write_addr(go, x, y) \ + ((go)->hpi_ops->write_interrupt)((go), (x)|0x8000, (y)) + +/* go7007-driver.c */ +int go7007_read_addr(struct go7007 *go, u16 addr, u16 *data); +int go7007_read_interrupt(struct go7007 *go, u16 *value, u16 *data); +int go7007_boot_encoder(struct go7007 *go, int init_i2c); +int go7007_reset_encoder(struct go7007 *go); +int go7007_register_encoder(struct go7007 *go); +int go7007_start_encoder(struct go7007 *go); +void go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length); +struct go7007 *go7007_alloc(struct go7007_board_info *board, + struct device *dev); +void go7007_remove(struct go7007 *go); + +/* go7007-fw.c */ +int go7007_construct_fw_image(struct go7007 *go, u8 **fw, int *fwlen); + +/* go7007-i2c.c */ +int go7007_i2c_init(struct go7007 *go); +int go7007_i2c_remove(struct go7007 *go); + +/* go7007-v4l2.c */ +int go7007_v4l2_init(struct go7007 *go); +void go7007_v4l2_remove(struct go7007 *go); + +/* snd-go7007.c */ +int go7007_snd_init(struct go7007 *go); +int go7007_snd_remove(struct go7007 *go); diff --git a/drivers/staging/media/go7007/go7007-usb.c b/drivers/staging/media/go7007/go7007-usb.c new file mode 100644 index 00000000..5443e250 --- /dev/null +++ b/drivers/staging/media/go7007/go7007-usb.c @@ -0,0 +1,1282 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "go7007-priv.h" +#include "wis-i2c.h" + +static unsigned int assume_endura; +module_param(assume_endura, int, 0644); +MODULE_PARM_DESC(assume_endura, "when probing fails, " + "hardware is a Pelco Endura"); + +/* #define GO7007_USB_DEBUG */ +/* #define GO7007_I2C_DEBUG */ /* for debugging the EZ-USB I2C adapter */ + +#define HPI_STATUS_ADDR 0xFFF4 +#define INT_PARAM_ADDR 0xFFF6 +#define INT_INDEX_ADDR 0xFFF8 + +/* + * Pipes on EZ-USB interface: + * 0 snd - Control + * 0 rcv - Control + * 2 snd - Download firmware (control) + * 4 rcv - Read Interrupt (interrupt) + * 6 rcv - Read Video (bulk) + * 8 rcv - Read Audio (bulk) + */ + +#define GO7007_USB_EZUSB (1<<0) +#define GO7007_USB_EZUSB_I2C (1<<1) + +struct go7007_usb_board { + unsigned int flags; + struct go7007_board_info main_info; +}; + +struct go7007_usb { + struct go7007_usb_board *board; + struct mutex i2c_lock; + struct usb_device *usbdev; + struct urb *video_urbs[8]; + struct urb *audio_urbs[8]; + struct urb *intr_urb; +}; + +/*********************** Product specification data ***********************/ + +static struct go7007_usb_board board_matrix_ii = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_USE_ONBOARD_I2C, + .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, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI | + GO7007_SENSOR_SCALING, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "wis_saa7115", + .id = I2C_DRIVERID_WIS_SAA7115, + .addr = 0x20, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 9, + .name = "S-Video", + }, + }, + }, +}; + +static struct go7007_usb_board board_matrix_reload = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_USE_ONBOARD_I2C, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "wis_saa7113", + .id = I2C_DRIVERID_WIS_SAA7113, + .addr = 0x25, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 9, + .name = "S-Video", + }, + }, + }, +}; + +static struct go7007_usb_board board_star_trek = { + .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_HAS_AUDIO, /* | + GO7007_BOARD_HAS_TUNER, */ + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI | + GO7007_SENSOR_SCALING, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_WORD_16, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "wis_saa7115", + .id = I2C_DRIVERID_WIS_SAA7115, + .addr = 0x20, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 1, + /* .audio_input = AUDIO_EXTERN, */ + .name = "Composite", + }, + { + .video_input = 8, + /* .audio_input = AUDIO_EXTERN, */ + .name = "S-Video", + }, + /* { + * .video_input = 3, + * .audio_input = AUDIO_TUNER, + * .name = "Tuner", + * }, + */ + }, + }, +}; + +static struct go7007_usb_board board_px_tv402u = { + .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_HAS_TUNER, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI | + GO7007_SENSOR_SCALING, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_WORD_16, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .num_i2c_devs = 3, + .i2c_devs = { + { + .type = "wis_saa7115", + .id = I2C_DRIVERID_WIS_SAA7115, + .addr = 0x20, + }, + { + .type = "wis_uda1342", + .id = I2C_DRIVERID_WIS_UDA1342, + .addr = 0x1a, + }, + { + .type = "wis_sony_tuner", + .id = I2C_DRIVERID_WIS_SONY_TUNER, + .addr = 0x60, + }, + }, + .num_inputs = 3, + .inputs = { + { + .video_input = 1, + .audio_input = TVAUDIO_INPUT_EXTERN, + .name = "Composite", + }, + { + .video_input = 8, + .audio_input = TVAUDIO_INPUT_EXTERN, + .name = "S-Video", + }, + { + .video_input = 3, + .audio_input = TVAUDIO_INPUT_TUNER, + .name = "Tuner", + }, + }, + }, +}; + +static struct go7007_usb_board board_xmen = { + .flags = 0, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_USE_ONBOARD_I2C, + .hpi_buffer_cap = 0, + .sensor_flags = GO7007_SENSOR_VREF_POLAR, + .sensor_width = 320, + .sensor_height = 240, + .sensor_framerate = 30030, + .audio_flags = GO7007_AUDIO_ONE_CHANNEL | + GO7007_AUDIO_I2S_MODE_3 | + GO7007_AUDIO_WORD_14 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_BCLK_POLAR | + GO7007_AUDIO_OKI_MODE, + .audio_rate = 8000, + .audio_bclk_div = 48, + .audio_main_div = 1, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "wis_ov7640", + .id = I2C_DRIVERID_WIS_OV7640, + .addr = 0x21, + }, + }, + .num_inputs = 1, + .inputs = { + { + .name = "Camera", + }, + }, + }, +}; + +static struct go7007_usb_board board_matrix_revolution = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_USE_ONBOARD_I2C, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "wis_tw9903", + .id = I2C_DRIVERID_WIS_TW9903, + .addr = 0x44, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 2, + .name = "Composite", + }, + { + .video_input = 8, + .name = "S-Video", + }, + }, + }, +}; + +static struct go7007_usb_board board_lifeview_lr192 = { + .flags = GO7007_USB_EZUSB, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_HAS_AUDIO | + GO7007_BOARD_USE_ONBOARD_I2C, + .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, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_VALID_ENABLE | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI | + GO7007_SENSOR_SCALING, + .num_i2c_devs = 0, + .num_inputs = 1, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + }, + }, +}; + +static struct go7007_usb_board board_endura = { + .flags = 0, + .main_info = { + .firmware = "go7007tv.bin", + .flags = 0, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .audio_rate = 8000, + .audio_bclk_div = 48, + .audio_main_div = 8, + .hpi_buffer_cap = 0, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV, + .sensor_h_offset = 8, + .num_i2c_devs = 0, + .num_inputs = 1, + .inputs = { + { + .name = "Camera", + }, + }, + }, +}; + +static struct go7007_usb_board board_adlink_mpg24 = { + .flags = 0, + .main_info = { + .firmware = "go7007tv.bin", + .flags = GO7007_BOARD_USE_ONBOARD_I2C, + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 0, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV | + GO7007_SENSOR_VBI, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "wis_tw2804", + .id = I2C_DRIVERID_WIS_TW2804, + .addr = 0x00, /* yes, really */ + }, + }, + .num_inputs = 1, + .inputs = { + { + .name = "Composite", + }, + }, + }, +}; + +static struct go7007_usb_board board_sensoray_2250 = { + .flags = GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C, + .main_info = { + .firmware = "go7007tv.bin", + .audio_flags = GO7007_AUDIO_I2S_MODE_1 | + GO7007_AUDIO_I2S_MASTER | + GO7007_AUDIO_WORD_16, + .flags = GO7007_BOARD_HAS_AUDIO, + .audio_rate = 48000, + .audio_bclk_div = 8, + .audio_main_div = 2, + .hpi_buffer_cap = 7, + .sensor_flags = GO7007_SENSOR_656 | + GO7007_SENSOR_TV, + .num_i2c_devs = 1, + .i2c_devs = { + { + .type = "s2250", + .id = I2C_DRIVERID_S2250, + .addr = 0x43, + }, + }, + .num_inputs = 2, + .inputs = { + { + .video_input = 0, + .name = "Composite", + }, + { + .video_input = 1, + .name = "S-Video", + }, + }, + }, +}; + +MODULE_FIRMWARE("go7007tv.bin"); + +static const struct usb_device_id go7007_usb_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x200, /* Revision number of XMen */ + .bcdDevice_hi = 0x200, + .bInterfaceClass = 255, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 255, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_XMEN, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x202, /* Revision number of Matrix II */ + .bcdDevice_hi = 0x202, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_MATRIX_II, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x204, /* Revision number of Matrix */ + .bcdDevice_hi = 0x204, /* Reloaded */ + .driver_info = (kernel_ulong_t)GO7007_BOARDID_MATRIX_RELOAD, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x205, /* Revision number of XMen-II */ + .bcdDevice_hi = 0x205, + .bInterfaceClass = 255, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 255, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_XMEN_II, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x208, /* Revision number of Star Trek */ + .bcdDevice_hi = 0x208, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_STAR_TREK, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x209, /* Revision number of XMen-III */ + .bcdDevice_hi = 0x209, + .bInterfaceClass = 255, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 255, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_XMEN_III, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x0eb1, /* Vendor ID of WIS Technologies */ + .idProduct = 0x7007, /* Product ID of GO7007SB chip */ + .bcdDevice_lo = 0x210, /* Revision number of Matrix */ + .bcdDevice_hi = 0x210, /* Revolution */ + .driver_info = (kernel_ulong_t)GO7007_BOARDID_MATRIX_REV, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x093b, /* Vendor ID of Plextor */ + .idProduct = 0xa102, /* Product ID of M402U */ + .bcdDevice_lo = 0x1, /* revision number of Blueberry */ + .bcdDevice_hi = 0x1, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_PX_M402U, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x093b, /* Vendor ID of Plextor */ + .idProduct = 0xa104, /* Product ID of TV402U */ + .bcdDevice_lo = 0x1, + .bcdDevice_hi = 0x1, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_PX_TV402U_ANY, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x10fd, /* Vendor ID of Anubis Electronics */ + .idProduct = 0xde00, /* Product ID of Lifeview LR192 */ + .bcdDevice_lo = 0x1, + .bcdDevice_hi = 0x1, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_LIFEVIEW_LR192, + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION, + .idVendor = 0x1943, /* Vendor ID Sensoray */ + .idProduct = 0x2250, /* Product ID of 2250/2251 */ + .bcdDevice_lo = 0x1, + .bcdDevice_hi = 0x1, + .driver_info = (kernel_ulong_t)GO7007_BOARDID_SENSORAY_2250, + }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, go7007_usb_id_table); + +/********************* Driver for EZ-USB HPI interface *********************/ + +static int go7007_usb_vendor_request(struct go7007 *go, int request, + int value, int index, void *transfer_buffer, int length, int in) +{ + struct go7007_usb *usb = go->hpi_context; + int timeout = 5000; + + if (in) { + return usb_control_msg(usb->usbdev, + usb_rcvctrlpipe(usb->usbdev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, transfer_buffer, length, timeout); + } else { + return usb_control_msg(usb->usbdev, + usb_sndctrlpipe(usb->usbdev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, transfer_buffer, length, timeout); + } +} + +static int go7007_usb_interface_reset(struct go7007 *go) +{ + struct go7007_usb *usb = go->hpi_context; + u16 intr_val, intr_data; + + /* Reset encoder */ + if (go7007_write_interrupt(go, 0x0001, 0x0001) < 0) + return -1; + msleep(100); + + if (usb->board->flags & GO7007_USB_EZUSB) { + /* Reset buffer in EZ-USB */ +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG "go7007-usb: resetting EZ-USB buffers\n"); +#endif + if (go7007_usb_vendor_request(go, 0x10, 0, 0, NULL, 0, 0) < 0 || + go7007_usb_vendor_request(go, 0x10, 0, 0, NULL, 0, 0) < 0) + return -1; + + /* Reset encoder again */ + if (go7007_write_interrupt(go, 0x0001, 0x0001) < 0) + return -1; + msleep(100); + } + + /* Wait for an interrupt to indicate successful hardware reset */ + if (go7007_read_interrupt(go, &intr_val, &intr_data) < 0 || + (intr_val & ~0x1) != 0x55aa) { + printk(KERN_ERR + "go7007-usb: unable to reset the USB interface\n"); + return -1; + } + return 0; +} + +static int go7007_usb_ezusb_write_interrupt(struct go7007 *go, + int addr, int data) +{ + struct go7007_usb *usb = go->hpi_context; + int i, r; + u16 status_reg; + int timeout = 500; + +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG + "go7007-usb: WriteInterrupt: %04x %04x\n", addr, data); +#endif + + for (i = 0; i < 100; ++i) { + r = usb_control_msg(usb->usbdev, + usb_rcvctrlpipe(usb->usbdev, 0), 0x14, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0, HPI_STATUS_ADDR, &status_reg, + sizeof(status_reg), timeout); + if (r < 0) + goto write_int_error; + __le16_to_cpus(&status_reg); + if (!(status_reg & 0x0010)) + break; + msleep(10); + } + if (i == 100) { + printk(KERN_ERR + "go7007-usb: device is hung, status reg = 0x%04x\n", + status_reg); + return -1; + } + r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 0), 0x12, + USB_TYPE_VENDOR | USB_RECIP_DEVICE, data, + INT_PARAM_ADDR, NULL, 0, timeout); + if (r < 0) + goto write_int_error; + r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 0), + 0x12, USB_TYPE_VENDOR | USB_RECIP_DEVICE, addr, + INT_INDEX_ADDR, NULL, 0, timeout); + if (r < 0) + goto write_int_error; + return 0; + +write_int_error: + printk(KERN_ERR "go7007-usb: error in WriteInterrupt: %d\n", r); + return r; +} + +static int go7007_usb_onboard_write_interrupt(struct go7007 *go, + int addr, int data) +{ + struct go7007_usb *usb = go->hpi_context; + u8 *tbuf; + int r; + int timeout = 500; + +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG + "go7007-usb: WriteInterrupt: %04x %04x\n", addr, data); +#endif + + tbuf = kzalloc(8, GFP_KERNEL); + if (tbuf == NULL) + return -ENOMEM; + tbuf[0] = data & 0xff; + tbuf[1] = data >> 8; + tbuf[2] = addr & 0xff; + tbuf[3] = addr >> 8; + r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 2), 0x00, + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, 0x55aa, + 0xf0f0, tbuf, 8, timeout); + kfree(tbuf); + if (r < 0) { + printk(KERN_ERR "go7007-usb: error in WriteInterrupt: %d\n", r); + return r; + } + return 0; +} + +static void go7007_usb_readinterrupt_complete(struct urb *urb) +{ + struct go7007 *go = (struct go7007 *)urb->context; + u16 *regs = (u16 *)urb->transfer_buffer; + int status = urb->status; + + if (status) { + if (status != -ESHUTDOWN && + go->status != STATUS_SHUTDOWN) { + printk(KERN_ERR + "go7007-usb: error in read interrupt: %d\n", + urb->status); + } else { + wake_up(&go->interrupt_waitq); + return; + } + } else if (urb->actual_length != urb->transfer_buffer_length) { + printk(KERN_ERR "go7007-usb: short read in interrupt pipe!\n"); + } else { + go->interrupt_available = 1; + go->interrupt_data = __le16_to_cpu(regs[0]); + go->interrupt_value = __le16_to_cpu(regs[1]); +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG "go7007-usb: ReadInterrupt: %04x %04x\n", + go->interrupt_value, go->interrupt_data); +#endif + } + + wake_up(&go->interrupt_waitq); +} + +static int go7007_usb_read_interrupt(struct go7007 *go) +{ + struct go7007_usb *usb = go->hpi_context; + int r; + + r = usb_submit_urb(usb->intr_urb, GFP_KERNEL); + if (r < 0) { + printk(KERN_ERR + "go7007-usb: unable to submit interrupt urb: %d\n", r); + return r; + } + return 0; +} + +static void go7007_usb_read_video_pipe_complete(struct urb *urb) +{ + struct go7007 *go = (struct go7007 *)urb->context; + int r, status = urb->status; + + if (!go->streaming) { + wake_up_interruptible(&go->frame_waitq); + return; + } + if (status) { + printk(KERN_ERR "go7007-usb: error in video pipe: %d\n", + status); + return; + } + if (urb->actual_length != urb->transfer_buffer_length) { + printk(KERN_ERR "go7007-usb: short read in video pipe!\n"); + return; + } + go7007_parse_video_stream(go, urb->transfer_buffer, urb->actual_length); + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r < 0) + printk(KERN_ERR "go7007-usb: error in video pipe: %d\n", r); +} + +static void go7007_usb_read_audio_pipe_complete(struct urb *urb) +{ + struct go7007 *go = (struct go7007 *)urb->context; + int r, status = urb->status; + + if (!go->streaming) + return; + if (status) { + printk(KERN_ERR "go7007-usb: error in audio pipe: %d\n", + status); + return; + } + if (urb->actual_length != urb->transfer_buffer_length) { + printk(KERN_ERR "go7007-usb: short read in audio pipe!\n"); + return; + } + if (go->audio_deliver != NULL) + go->audio_deliver(go, urb->transfer_buffer, urb->actual_length); + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r < 0) + printk(KERN_ERR "go7007-usb: error in audio pipe: %d\n", r); +} + +static int go7007_usb_stream_start(struct go7007 *go) +{ + struct go7007_usb *usb = go->hpi_context; + int i, r; + + for (i = 0; i < 8; ++i) { + r = usb_submit_urb(usb->video_urbs[i], GFP_KERNEL); + if (r < 0) { + printk(KERN_ERR "go7007-usb: error submitting video " + "urb %d: %d\n", i, r); + goto video_submit_failed; + } + } + if (!go->audio_enabled) + return 0; + + for (i = 0; i < 8; ++i) { + r = usb_submit_urb(usb->audio_urbs[i], GFP_KERNEL); + if (r < 0) { + printk(KERN_ERR "go7007-usb: error submitting audio " + "urb %d: %d\n", i, r); + goto audio_submit_failed; + } + } + return 0; + +audio_submit_failed: + for (i = 0; i < 7; ++i) + usb_kill_urb(usb->audio_urbs[i]); +video_submit_failed: + for (i = 0; i < 8; ++i) + usb_kill_urb(usb->video_urbs[i]); + return -1; +} + +static int go7007_usb_stream_stop(struct go7007 *go) +{ + struct go7007_usb *usb = go->hpi_context; + int i; + + if (go->status == STATUS_SHUTDOWN) + return 0; + for (i = 0; i < 8; ++i) + usb_kill_urb(usb->video_urbs[i]); + if (go->audio_enabled) + for (i = 0; i < 8; ++i) + usb_kill_urb(usb->audio_urbs[i]); + return 0; +} + +static int go7007_usb_send_firmware(struct go7007 *go, u8 *data, int len) +{ + struct go7007_usb *usb = go->hpi_context; + int transferred, pipe; + int timeout = 500; + +#ifdef GO7007_USB_DEBUG + printk(KERN_DEBUG "go7007-usb: DownloadBuffer sending %d bytes\n", len); +#endif + + if (usb->board->flags & GO7007_USB_EZUSB) + pipe = usb_sndbulkpipe(usb->usbdev, 2); + else + pipe = usb_sndbulkpipe(usb->usbdev, 3); + + return usb_bulk_msg(usb->usbdev, pipe, data, len, + &transferred, timeout); +} + +static struct go7007_hpi_ops go7007_usb_ezusb_hpi_ops = { + .interface_reset = go7007_usb_interface_reset, + .write_interrupt = go7007_usb_ezusb_write_interrupt, + .read_interrupt = go7007_usb_read_interrupt, + .stream_start = go7007_usb_stream_start, + .stream_stop = go7007_usb_stream_stop, + .send_firmware = go7007_usb_send_firmware, +}; + +static struct go7007_hpi_ops go7007_usb_onboard_hpi_ops = { + .interface_reset = go7007_usb_interface_reset, + .write_interrupt = go7007_usb_onboard_write_interrupt, + .read_interrupt = go7007_usb_read_interrupt, + .stream_start = go7007_usb_stream_start, + .stream_stop = go7007_usb_stream_stop, + .send_firmware = go7007_usb_send_firmware, +}; + +/********************* Driver for EZ-USB I2C adapter *********************/ + +static int go7007_usb_i2c_master_xfer(struct i2c_adapter *adapter, + struct i2c_msg msgs[], int num) +{ + struct go7007 *go = i2c_get_adapdata(adapter); + struct go7007_usb *usb = go->hpi_context; + u8 buf[16]; + int buf_len, i; + int ret = -1; + + if (go->status == STATUS_SHUTDOWN) + return -1; + + mutex_lock(&usb->i2c_lock); + + for (i = 0; i < num; ++i) { + /* The hardware command is "write some bytes then read some + * bytes", so we try to coalesce a write followed by a read + * into a single USB transaction */ + if (i + 1 < num && msgs[i].addr == msgs[i + 1].addr && + !(msgs[i].flags & I2C_M_RD) && + (msgs[i + 1].flags & I2C_M_RD)) { +#ifdef GO7007_I2C_DEBUG + printk(KERN_DEBUG "go7007-usb: i2c write/read %d/%d " + "bytes on %02x\n", msgs[i].len, + msgs[i + 1].len, msgs[i].addr); +#endif + buf[0] = 0x01; + buf[1] = msgs[i].len + 1; + buf[2] = msgs[i].addr << 1; + memcpy(&buf[3], msgs[i].buf, msgs[i].len); + buf_len = msgs[i].len + 3; + buf[buf_len++] = msgs[++i].len; + } else if (msgs[i].flags & I2C_M_RD) { +#ifdef GO7007_I2C_DEBUG + printk(KERN_DEBUG "go7007-usb: i2c read %d " + "bytes on %02x\n", msgs[i].len, + msgs[i].addr); +#endif + buf[0] = 0x01; + buf[1] = 1; + buf[2] = msgs[i].addr << 1; + buf[3] = msgs[i].len; + buf_len = 4; + } else { +#ifdef GO7007_I2C_DEBUG + printk(KERN_DEBUG "go7007-usb: i2c write %d " + "bytes on %02x\n", msgs[i].len, + msgs[i].addr); +#endif + buf[0] = 0x00; + buf[1] = msgs[i].len + 1; + buf[2] = msgs[i].addr << 1; + memcpy(&buf[3], msgs[i].buf, msgs[i].len); + buf_len = msgs[i].len + 3; + buf[buf_len++] = 0; + } + if (go7007_usb_vendor_request(go, 0x24, 0, 0, + buf, buf_len, 0) < 0) + goto i2c_done; + if (msgs[i].flags & I2C_M_RD) { + memset(buf, 0, sizeof(buf)); + if (go7007_usb_vendor_request(go, 0x25, 0, 0, buf, + msgs[i].len + 1, 1) < 0) + goto i2c_done; + memcpy(msgs[i].buf, buf + 1, msgs[i].len); + } + } + ret = 0; + +i2c_done: + mutex_unlock(&usb->i2c_lock); + return ret; +} + +static u32 go7007_usb_functionality(struct i2c_adapter *adapter) +{ + /* No errors are reported by the hardware, so we don't bother + * supporting quick writes to avoid confusing probing */ + return (I2C_FUNC_SMBUS_EMUL) & ~I2C_FUNC_SMBUS_QUICK; +} + +static struct i2c_algorithm go7007_usb_algo = { + .master_xfer = go7007_usb_i2c_master_xfer, + .functionality = go7007_usb_functionality, +}; + +static struct i2c_adapter go7007_usb_adap_templ = { + .owner = THIS_MODULE, + .name = "WIS GO7007SB EZ-USB", + .algo = &go7007_usb_algo, +}; + +/********************* USB add/remove functions *********************/ + +static int go7007_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct go7007 *go; + struct go7007_usb *usb; + struct go7007_usb_board *board; + struct usb_device *usbdev = interface_to_usbdev(intf); + char *name; + int video_pipe, i, v_urb_len; + + printk(KERN_DEBUG "go7007-usb: probing new GO7007 USB board\n"); + + switch (id->driver_info) { + case GO7007_BOARDID_MATRIX_II: + name = "WIS Matrix II or compatible"; + board = &board_matrix_ii; + break; + case GO7007_BOARDID_MATRIX_RELOAD: + name = "WIS Matrix Reloaded or compatible"; + board = &board_matrix_reload; + break; + case GO7007_BOARDID_MATRIX_REV: + name = "WIS Matrix Revolution or compatible"; + board = &board_matrix_revolution; + break; + case GO7007_BOARDID_STAR_TREK: + name = "WIS Star Trek or compatible"; + board = &board_star_trek; + break; + case GO7007_BOARDID_XMEN: + name = "WIS XMen or compatible"; + board = &board_xmen; + break; + case GO7007_BOARDID_XMEN_II: + name = "WIS XMen II or compatible"; + board = &board_xmen; + break; + case GO7007_BOARDID_XMEN_III: + name = "WIS XMen III or compatible"; + board = &board_xmen; + break; + case GO7007_BOARDID_PX_M402U: + name = "Plextor PX-M402U"; + board = &board_matrix_ii; + break; + case GO7007_BOARDID_PX_TV402U_ANY: + name = "Plextor PX-TV402U (unknown tuner)"; + board = &board_px_tv402u; + break; + case GO7007_BOARDID_LIFEVIEW_LR192: + printk(KERN_ERR "go7007-usb: The Lifeview TV Walker Ultra " + "is not supported. Sorry!\n"); + return 0; + name = "Lifeview TV Walker Ultra"; + board = &board_lifeview_lr192; + break; + case GO7007_BOARDID_SENSORAY_2250: + printk(KERN_INFO "Sensoray 2250 found\n"); + name = "Sensoray 2250/2251"; + board = &board_sensoray_2250; + break; + default: + printk(KERN_ERR "go7007-usb: unknown board ID %d!\n", + (unsigned int)id->driver_info); + return 0; + } + + usb = kzalloc(sizeof(struct go7007_usb), GFP_KERNEL); + if (usb == NULL) + return -ENOMEM; + + /* Allocate the URB and buffer for receiving incoming interrupts */ + usb->intr_urb = usb_alloc_urb(0, GFP_KERNEL); + if (usb->intr_urb == NULL) + goto allocfail; + usb->intr_urb->transfer_buffer = kmalloc(2*sizeof(u16), GFP_KERNEL); + if (usb->intr_urb->transfer_buffer == NULL) + goto allocfail; + + go = go7007_alloc(&board->main_info, &intf->dev); + if (go == NULL) + goto allocfail; + usb->board = board; + usb->usbdev = usbdev; + go->board_id = id->driver_info; + strncpy(go->name, name, sizeof(go->name)); + if (board->flags & GO7007_USB_EZUSB) + go->hpi_ops = &go7007_usb_ezusb_hpi_ops; + else + go->hpi_ops = &go7007_usb_onboard_hpi_ops; + go->hpi_context = usb; + if (go->board_id == GO7007_BOARDID_SENSORAY_2250) + usb_fill_bulk_urb(usb->intr_urb, usb->usbdev, + usb_rcvbulkpipe(usb->usbdev, 4), + usb->intr_urb->transfer_buffer, 2*sizeof(u16), + go7007_usb_readinterrupt_complete, go); + else + usb_fill_int_urb(usb->intr_urb, usb->usbdev, + usb_rcvintpipe(usb->usbdev, 4), + usb->intr_urb->transfer_buffer, 2*sizeof(u16), + go7007_usb_readinterrupt_complete, go, 8); + usb_set_intfdata(intf, &go->v4l2_dev); + + /* Boot the GO7007 */ + if (go7007_boot_encoder(go, go->board_info->flags & + GO7007_BOARD_USE_ONBOARD_I2C) < 0) + goto initfail; + + /* Register the EZ-USB I2C adapter, if we're using it */ + if (board->flags & GO7007_USB_EZUSB_I2C) { + memcpy(&go->i2c_adapter, &go7007_usb_adap_templ, + sizeof(go7007_usb_adap_templ)); + mutex_init(&usb->i2c_lock); + go->i2c_adapter.dev.parent = go->dev; + i2c_set_adapdata(&go->i2c_adapter, go); + if (i2c_add_adapter(&go->i2c_adapter) < 0) { + printk(KERN_ERR + "go7007-usb: error: i2c_add_adapter failed\n"); + goto initfail; + } + go->i2c_adapter_online = 1; + } + + /* Pelco and Adlink reused the XMen and XMen-III vendor and product + * IDs for their own incompatible designs. We can detect XMen boards + * by probing the sensor, but there is no way to probe the sensors on + * the Pelco and Adlink designs so we default to the Adlink. If it + * is actually a Pelco, the user must set the assume_endura module + * parameter. */ + if ((go->board_id == GO7007_BOARDID_XMEN || + go->board_id == GO7007_BOARDID_XMEN_III) && + go->i2c_adapter_online) { + union i2c_smbus_data data; + + /* Check to see if register 0x0A is 0x76 */ + i2c_smbus_xfer(&go->i2c_adapter, 0x21, I2C_CLIENT_SCCB, + I2C_SMBUS_READ, 0x0A, I2C_SMBUS_BYTE_DATA, &data); + if (data.byte != 0x76) { + if (assume_endura) { + go->board_id = GO7007_BOARDID_ENDURA; + usb->board = board = &board_endura; + go->board_info = &board->main_info; + strncpy(go->name, "Pelco Endura", + sizeof(go->name)); + } else { + u16 channel; + + /* set GPIO5 to be an output, currently low */ + go7007_write_addr(go, 0x3c82, 0x0000); + go7007_write_addr(go, 0x3c80, 0x00df); + /* read channel number from GPIO[1:0] */ + go7007_read_addr(go, 0x3c81, &channel); + channel &= 0x3; + go->board_id = GO7007_BOARDID_ADLINK_MPG24; + usb->board = board = &board_adlink_mpg24; + go->board_info = &board->main_info; + go->channel_number = channel; + snprintf(go->name, sizeof(go->name), + "Adlink PCI-MPG24, channel #%d", + channel); + } + } + } + + /* Probe the tuner model on the TV402U */ + if (go->board_id == GO7007_BOARDID_PX_TV402U_ANY) { + u8 data[3]; + + /* Board strapping indicates tuner model */ + if (go7007_usb_vendor_request(go, 0x41, 0, 0, data, 3, 1) < 0) { + printk(KERN_ERR "go7007-usb: GPIO read failed!\n"); + goto initfail; + } + switch (data[0] >> 6) { + case 1: + go->board_id = GO7007_BOARDID_PX_TV402U_EU; + go->tuner_type = TUNER_SONY_BTF_PG472Z; + strncpy(go->name, "Plextor PX-TV402U-EU", + sizeof(go->name)); + break; + case 2: + go->board_id = GO7007_BOARDID_PX_TV402U_JP; + go->tuner_type = TUNER_SONY_BTF_PK467Z; + strncpy(go->name, "Plextor PX-TV402U-JP", + sizeof(go->name)); + break; + case 3: + go->board_id = GO7007_BOARDID_PX_TV402U_NA; + go->tuner_type = TUNER_SONY_BTF_PB463Z; + strncpy(go->name, "Plextor PX-TV402U-NA", + sizeof(go->name)); + break; + default: + printk(KERN_DEBUG "go7007-usb: unable to detect " + "tuner type!\n"); + break; + } + /* Configure tuner mode selection inputs connected + * to the EZ-USB GPIO output pins */ + if (go7007_usb_vendor_request(go, 0x40, 0x7f02, 0, + NULL, 0, 0) < 0) { + printk(KERN_ERR "go7007-usb: GPIO write failed!\n"); + goto initfail; + } + } + + /* Print a nasty message if the user attempts to use a USB2.0 device in + * a USB1.1 port. There will be silent corruption of the stream. */ + if ((board->flags & GO7007_USB_EZUSB) && + usbdev->speed != USB_SPEED_HIGH) + printk(KERN_ERR "go7007-usb: *** WARNING *** This device " + "must be connected to a USB 2.0 port! " + "Attempting to capture video through a USB 1.1 " + "port will result in stream corruption, even " + "at low bitrates!\n"); + + /* Do any final GO7007 initialization, then register the + * V4L2 and ALSA interfaces */ + if (go7007_register_encoder(go) < 0) + goto initfail; + + /* Allocate the URBs and buffers for receiving the video stream */ + if (board->flags & GO7007_USB_EZUSB) { + v_urb_len = 1024; + video_pipe = usb_rcvbulkpipe(usb->usbdev, 6); + } else { + v_urb_len = 512; + video_pipe = usb_rcvbulkpipe(usb->usbdev, 1); + } + for (i = 0; i < 8; ++i) { + usb->video_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (usb->video_urbs[i] == NULL) + goto initfail; + usb->video_urbs[i]->transfer_buffer = + kmalloc(v_urb_len, GFP_KERNEL); + if (usb->video_urbs[i]->transfer_buffer == NULL) + goto initfail; + usb_fill_bulk_urb(usb->video_urbs[i], usb->usbdev, video_pipe, + usb->video_urbs[i]->transfer_buffer, v_urb_len, + go7007_usb_read_video_pipe_complete, go); + } + + /* Allocate the URBs and buffers for receiving the audio stream */ + if ((board->flags & GO7007_USB_EZUSB) && go->audio_enabled) + for (i = 0; i < 8; ++i) { + usb->audio_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (usb->audio_urbs[i] == NULL) + goto initfail; + usb->audio_urbs[i]->transfer_buffer = kmalloc(4096, + GFP_KERNEL); + if (usb->audio_urbs[i]->transfer_buffer == NULL) + goto initfail; + usb_fill_bulk_urb(usb->audio_urbs[i], usb->usbdev, + usb_rcvbulkpipe(usb->usbdev, 8), + usb->audio_urbs[i]->transfer_buffer, 4096, + go7007_usb_read_audio_pipe_complete, go); + } + + + go->status = STATUS_ONLINE; + return 0; + +initfail: + go->status = STATUS_SHUTDOWN; + return 0; + +allocfail: + if (usb->intr_urb) { + kfree(usb->intr_urb->transfer_buffer); + usb_free_urb(usb->intr_urb); + } + kfree(usb); + return -ENOMEM; +} + +static void go7007_usb_disconnect(struct usb_interface *intf) +{ + struct go7007 *go = to_go7007(usb_get_intfdata(intf)); + struct go7007_usb *usb = go->hpi_context; + struct urb *vurb, *aurb; + int i; + + go->status = STATUS_SHUTDOWN; + usb_kill_urb(usb->intr_urb); + + /* Free USB-related structs */ + for (i = 0; i < 8; ++i) { + vurb = usb->video_urbs[i]; + if (vurb) { + usb_kill_urb(vurb); + kfree(vurb->transfer_buffer); + usb_free_urb(vurb); + } + aurb = usb->audio_urbs[i]; + if (aurb) { + usb_kill_urb(aurb); + kfree(aurb->transfer_buffer); + usb_free_urb(aurb); + } + } + kfree(usb->intr_urb->transfer_buffer); + usb_free_urb(usb->intr_urb); + + kfree(go->hpi_context); + + go7007_remove(go); +} + +static struct usb_driver go7007_usb_driver = { + .name = "go7007", + .probe = go7007_usb_probe, + .disconnect = go7007_usb_disconnect, + .id_table = go7007_usb_id_table, +}; + +module_usb_driver(go7007_usb_driver); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/go7007-v4l2.c b/drivers/staging/media/go7007/go7007-v4l2.c new file mode 100644 index 00000000..3ef4cd8b --- /dev/null +++ b/drivers/staging/media/go7007/go7007-v4l2.c @@ -0,0 +1,1838 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "go7007.h" +#include "go7007-priv.h" +#include "wis-i2c.h" + +/* Temporary defines until accepted in v4l-dvb */ +#ifndef V4L2_MPEG_STREAM_TYPE_MPEG_ELEM +#define V4L2_MPEG_STREAM_TYPE_MPEG_ELEM 6 /* MPEG elementary stream */ +#endif +#ifndef V4L2_MPEG_VIDEO_ENCODING_MPEG_4 +#define V4L2_MPEG_VIDEO_ENCODING_MPEG_4 3 +#endif + +#define call_all(dev, o, f, args...) \ + v4l2_device_call_until_err(dev, 0, o, f, ##args) + +static void deactivate_buffer(struct go7007_buffer *gobuf) +{ + int i; + + if (gobuf->state != BUF_STATE_IDLE) { + list_del(&gobuf->stream); + gobuf->state = BUF_STATE_IDLE; + } + if (gobuf->page_count > 0) { + for (i = 0; i < gobuf->page_count; ++i) + page_cache_release(gobuf->pages[i]); + gobuf->page_count = 0; + } +} + +static void abort_queued(struct go7007 *go) +{ + struct go7007_buffer *gobuf, *next; + + list_for_each_entry_safe(gobuf, next, &go->stream, stream) { + deactivate_buffer(gobuf); + } +} + +static int go7007_streamoff(struct go7007 *go) +{ + int retval = -EINVAL; + unsigned long flags; + + mutex_lock(&go->hw_lock); + if (go->streaming) { + go->streaming = 0; + go7007_stream_stop(go); + spin_lock_irqsave(&go->spinlock, flags); + abort_queued(go); + spin_unlock_irqrestore(&go->spinlock, flags); + go7007_reset_encoder(go); + retval = 0; + } + mutex_unlock(&go->hw_lock); + return 0; +} + +static int go7007_open(struct file *file) +{ + struct go7007 *go = video_get_drvdata(video_devdata(file)); + struct go7007_file *gofh; + + if (go->status != STATUS_ONLINE) + return -EBUSY; + gofh = kmalloc(sizeof(struct go7007_file), GFP_KERNEL); + if (gofh == NULL) + return -ENOMEM; + ++go->ref_count; + gofh->go = go; + mutex_init(&gofh->lock); + gofh->buf_count = 0; + file->private_data = gofh; + return 0; +} + +static int go7007_release(struct file *file) +{ + struct go7007_file *gofh = file->private_data; + struct go7007 *go = gofh->go; + + if (gofh->buf_count > 0) { + go7007_streamoff(go); + go->in_use = 0; + kfree(gofh->bufs); + gofh->buf_count = 0; + } + kfree(gofh); + if (--go->ref_count == 0) + kfree(go); + file->private_data = NULL; + return 0; +} + +static u32 get_frame_type_flag(struct go7007_buffer *gobuf, int format) +{ + u8 *f = page_address(gobuf->pages[0]); + + switch (format) { + case GO7007_FORMAT_MJPEG: + return V4L2_BUF_FLAG_KEYFRAME; + case GO7007_FORMAT_MPEG4: + switch ((f[gobuf->frame_offset + 4] >> 6) & 0x3) { + case 0: + return V4L2_BUF_FLAG_KEYFRAME; + case 1: + return V4L2_BUF_FLAG_PFRAME; + case 2: + return V4L2_BUF_FLAG_BFRAME; + default: + return 0; + } + case GO7007_FORMAT_MPEG1: + case GO7007_FORMAT_MPEG2: + switch ((f[gobuf->frame_offset + 5] >> 3) & 0x7) { + case 1: + return V4L2_BUF_FLAG_KEYFRAME; + case 2: + return V4L2_BUF_FLAG_PFRAME; + case 3: + return V4L2_BUF_FLAG_BFRAME; + default: + return 0; + } + } + + return 0; +} + +static int set_capture_size(struct go7007 *go, struct v4l2_format *fmt, int try) +{ + int sensor_height = 0, sensor_width = 0; + int width, height, i; + + if (fmt != NULL && fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG && + fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MPEG && + fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_MPEG4) + return -EINVAL; + + switch (go->standard) { + case GO7007_STD_NTSC: + sensor_width = 720; + sensor_height = 480; + break; + case GO7007_STD_PAL: + sensor_width = 720; + sensor_height = 576; + break; + case GO7007_STD_OTHER: + sensor_width = go->board_info->sensor_width; + sensor_height = go->board_info->sensor_height; + break; + } + + if (fmt == NULL) { + width = sensor_width; + height = sensor_height; + } else if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) { + if (fmt->fmt.pix.width > sensor_width) + width = sensor_width; + else if (fmt->fmt.pix.width < 144) + width = 144; + else + width = fmt->fmt.pix.width & ~0x0f; + + if (fmt->fmt.pix.height > sensor_height) + height = sensor_height; + else if (fmt->fmt.pix.height < 96) + height = 96; + else + height = fmt->fmt.pix.height & ~0x0f; + } else { + int requested_size = fmt->fmt.pix.width * fmt->fmt.pix.height; + int sensor_size = sensor_width * sensor_height; + + if (64 * requested_size < 9 * sensor_size) { + width = sensor_width / 4; + height = sensor_height / 4; + } else if (64 * requested_size < 36 * sensor_size) { + width = sensor_width / 2; + height = sensor_height / 2; + } else { + width = sensor_width; + height = sensor_height; + } + width &= ~0xf; + height &= ~0xf; + } + + if (fmt != NULL) { + u32 pixelformat = fmt->fmt.pix.pixelformat; + + memset(fmt, 0, sizeof(*fmt)); + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt->fmt.pix.width = width; + fmt->fmt.pix.height = height; + fmt->fmt.pix.pixelformat = pixelformat; + fmt->fmt.pix.field = V4L2_FIELD_NONE; + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.sizeimage = GO7007_BUF_SIZE; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; /* ?? */ + } + + if (try) + return 0; + + go->width = width; + go->height = height; + go->encoder_h_offset = go->board_info->sensor_h_offset; + go->encoder_v_offset = go->board_info->sensor_v_offset; + for (i = 0; i < 4; ++i) + go->modet[i].enable = 0; + for (i = 0; i < 1624; ++i) + go->modet_map[i] = 0; + + if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) { + struct v4l2_mbus_framefmt mbus_fmt; + + mbus_fmt.code = V4L2_MBUS_FMT_FIXED; + if (fmt != NULL) + mbus_fmt.width = fmt->fmt.pix.width; + else + mbus_fmt.width = width; + + if (height > sensor_height / 2) { + mbus_fmt.height = height / 2; + go->encoder_v_halve = 0; + } else { + mbus_fmt.height = height; + go->encoder_v_halve = 1; + } + call_all(&go->v4l2_dev, video, s_mbus_fmt, &mbus_fmt); + } else { + if (width <= sensor_width / 4) { + go->encoder_h_halve = 1; + go->encoder_v_halve = 1; + go->encoder_subsample = 1; + } else if (width <= sensor_width / 2) { + go->encoder_h_halve = 1; + go->encoder_v_halve = 1; + go->encoder_subsample = 0; + } else { + go->encoder_h_halve = 0; + go->encoder_v_halve = 0; + go->encoder_subsample = 0; + } + } + + if (fmt == NULL) + return 0; + + switch (fmt->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_MPEG: + if (go->format == GO7007_FORMAT_MPEG1 || + go->format == GO7007_FORMAT_MPEG2 || + go->format == GO7007_FORMAT_MPEG4) + break; + go->format = GO7007_FORMAT_MPEG1; + go->pali = 0; + go->aspect_ratio = GO7007_RATIO_1_1; + go->gop_size = go->sensor_framerate / 1000; + go->ipb = 0; + go->closed_gop = 1; + go->repeat_seqhead = 1; + go->seq_header_enable = 1; + go->gop_header_enable = 1; + go->dvd_mode = 0; + break; + /* Backwards compatibility only! */ + case V4L2_PIX_FMT_MPEG4: + if (go->format == GO7007_FORMAT_MPEG4) + break; + go->format = GO7007_FORMAT_MPEG4; + go->pali = 0xf5; + go->aspect_ratio = GO7007_RATIO_1_1; + go->gop_size = go->sensor_framerate / 1000; + go->ipb = 0; + go->closed_gop = 1; + go->repeat_seqhead = 1; + go->seq_header_enable = 1; + go->gop_header_enable = 1; + go->dvd_mode = 0; + break; + case V4L2_PIX_FMT_MJPEG: + go->format = GO7007_FORMAT_MJPEG; + go->pali = 0; + go->aspect_ratio = GO7007_RATIO_1_1; + go->gop_size = 0; + go->ipb = 0; + go->closed_gop = 0; + go->repeat_seqhead = 0; + go->seq_header_enable = 0; + go->gop_header_enable = 0; + go->dvd_mode = 0; + break; + } + return 0; +} + +#if 0 +static int clip_to_modet_map(struct go7007 *go, int region, + struct v4l2_clip *clip_list) +{ + struct v4l2_clip clip, *clip_ptr; + int x, y, mbnum; + + /* Check if coordinates are OK and if any macroblocks are already + * used by other regions (besides 0) */ + clip_ptr = clip_list; + while (clip_ptr) { + if (copy_from_user(&clip, clip_ptr, sizeof(clip))) + return -EFAULT; + if (clip.c.left < 0 || (clip.c.left & 0xF) || + clip.c.width <= 0 || (clip.c.width & 0xF)) + return -EINVAL; + if (clip.c.left + clip.c.width > go->width) + return -EINVAL; + if (clip.c.top < 0 || (clip.c.top & 0xF) || + clip.c.height <= 0 || (clip.c.height & 0xF)) + return -EINVAL; + if (clip.c.top + clip.c.height > go->height) + return -EINVAL; + for (y = 0; y < clip.c.height; y += 16) + for (x = 0; x < clip.c.width; x += 16) { + mbnum = (go->width >> 4) * + ((clip.c.top + y) >> 4) + + ((clip.c.left + x) >> 4); + if (go->modet_map[mbnum] != 0 && + go->modet_map[mbnum] != region) + return -EBUSY; + } + clip_ptr = clip.next; + } + + /* Clear old region macroblocks */ + for (mbnum = 0; mbnum < 1624; ++mbnum) + if (go->modet_map[mbnum] == region) + go->modet_map[mbnum] = 0; + + /* Claim macroblocks in this list */ + clip_ptr = clip_list; + while (clip_ptr) { + if (copy_from_user(&clip, clip_ptr, sizeof(clip))) + return -EFAULT; + for (y = 0; y < clip.c.height; y += 16) + for (x = 0; x < clip.c.width; x += 16) { + mbnum = (go->width >> 4) * + ((clip.c.top + y) >> 4) + + ((clip.c.left + x) >> 4); + go->modet_map[mbnum] = region; + } + clip_ptr = clip.next; + } + return 0; +} +#endif + +static int mpeg_query_ctrl(struct v4l2_queryctrl *ctrl) +{ + static const u32 mpeg_ctrls[] = { + V4L2_CID_MPEG_CLASS, + V4L2_CID_MPEG_STREAM_TYPE, + V4L2_CID_MPEG_VIDEO_ENCODING, + V4L2_CID_MPEG_VIDEO_ASPECT, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, + V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, + V4L2_CID_MPEG_VIDEO_BITRATE, + 0 + }; + static const u32 *ctrl_classes[] = { + mpeg_ctrls, + NULL + }; + + ctrl->id = v4l2_ctrl_next(ctrl_classes, ctrl->id); + + switch (ctrl->id) { + case V4L2_CID_MPEG_CLASS: + return v4l2_ctrl_query_fill(ctrl, 0, 0, 0, 0); + case V4L2_CID_MPEG_STREAM_TYPE: + return v4l2_ctrl_query_fill(ctrl, + V4L2_MPEG_STREAM_TYPE_MPEG2_DVD, + V4L2_MPEG_STREAM_TYPE_MPEG_ELEM, 1, + V4L2_MPEG_STREAM_TYPE_MPEG_ELEM); + case V4L2_CID_MPEG_VIDEO_ENCODING: + return v4l2_ctrl_query_fill(ctrl, + V4L2_MPEG_VIDEO_ENCODING_MPEG_1, + V4L2_MPEG_VIDEO_ENCODING_MPEG_4, 1, + V4L2_MPEG_VIDEO_ENCODING_MPEG_2); + case V4L2_CID_MPEG_VIDEO_ASPECT: + return v4l2_ctrl_query_fill(ctrl, + V4L2_MPEG_VIDEO_ASPECT_1x1, + V4L2_MPEG_VIDEO_ASPECT_16x9, 1, + V4L2_MPEG_VIDEO_ASPECT_1x1); + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + return v4l2_ctrl_query_fill(ctrl, 0, 34, 1, 15); + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + return v4l2_ctrl_query_fill(ctrl, 0, 1, 1, 0); + case V4L2_CID_MPEG_VIDEO_BITRATE: + return v4l2_ctrl_query_fill(ctrl, + 64000, + 10000000, 1, + 1500000); + default: + return -EINVAL; + } + return 0; +} + +static int mpeg_s_ctrl(struct v4l2_control *ctrl, struct go7007 *go) +{ + /* pretty sure we can't change any of these while streaming */ + if (go->streaming) + return -EBUSY; + + switch (ctrl->id) { + case V4L2_CID_MPEG_STREAM_TYPE: + switch (ctrl->value) { + case V4L2_MPEG_STREAM_TYPE_MPEG2_DVD: + go->format = GO7007_FORMAT_MPEG2; + go->bitrate = 9800000; + go->gop_size = 15; + go->pali = 0x48; + go->closed_gop = 1; + go->repeat_seqhead = 0; + go->seq_header_enable = 1; + go->gop_header_enable = 1; + go->dvd_mode = 1; + break; + case V4L2_MPEG_STREAM_TYPE_MPEG_ELEM: + /* todo: */ + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_ENCODING: + switch (ctrl->value) { + case V4L2_MPEG_VIDEO_ENCODING_MPEG_1: + go->format = GO7007_FORMAT_MPEG1; + go->pali = 0; + break; + case V4L2_MPEG_VIDEO_ENCODING_MPEG_2: + go->format = GO7007_FORMAT_MPEG2; + /*if (mpeg->pali >> 24 == 2) + go->pali = mpeg->pali & 0xff; + else*/ + go->pali = 0x48; + break; + case V4L2_MPEG_VIDEO_ENCODING_MPEG_4: + go->format = GO7007_FORMAT_MPEG4; + /*if (mpeg->pali >> 24 == 4) + go->pali = mpeg->pali & 0xff; + else*/ + go->pali = 0xf5; + break; + default: + return -EINVAL; + } + go->gop_header_enable = + /*mpeg->flags & GO7007_MPEG_OMIT_GOP_HEADER + ? 0 :*/ 1; + /*if (mpeg->flags & GO7007_MPEG_REPEAT_SEQHEADER) + go->repeat_seqhead = 1; + else*/ + go->repeat_seqhead = 0; + go->dvd_mode = 0; + break; + case V4L2_CID_MPEG_VIDEO_ASPECT: + if (go->format == GO7007_FORMAT_MJPEG) + return -EINVAL; + switch (ctrl->value) { + case V4L2_MPEG_VIDEO_ASPECT_1x1: + go->aspect_ratio = GO7007_RATIO_1_1; + break; + case V4L2_MPEG_VIDEO_ASPECT_4x3: + go->aspect_ratio = GO7007_RATIO_4_3; + break; + case V4L2_MPEG_VIDEO_ASPECT_16x9: + go->aspect_ratio = GO7007_RATIO_16_9; + break; + case V4L2_MPEG_VIDEO_ASPECT_221x100: + default: + return -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + if (ctrl->value < 0 || ctrl->value > 34) + return -EINVAL; + go->gop_size = ctrl->value; + break; + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + if (ctrl->value != 0 && ctrl->value != 1) + return -EINVAL; + go->closed_gop = ctrl->value; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + /* Upper bound is kind of arbitrary here */ + if (ctrl->value < 64000 || ctrl->value > 10000000) + return -EINVAL; + go->bitrate = ctrl->value; + break; + default: + return -EINVAL; + } + return 0; +} + +static int mpeg_g_ctrl(struct v4l2_control *ctrl, struct go7007 *go) +{ + switch (ctrl->id) { + case V4L2_CID_MPEG_STREAM_TYPE: + if (go->dvd_mode) + ctrl->value = V4L2_MPEG_STREAM_TYPE_MPEG2_DVD; + else + ctrl->value = V4L2_MPEG_STREAM_TYPE_MPEG_ELEM; + break; + case V4L2_CID_MPEG_VIDEO_ENCODING: + switch (go->format) { + case GO7007_FORMAT_MPEG1: + ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_1; + break; + case GO7007_FORMAT_MPEG2: + ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_2; + break; + case GO7007_FORMAT_MPEG4: + ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_4; + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_ASPECT: + switch (go->aspect_ratio) { + case GO7007_RATIO_1_1: + ctrl->value = V4L2_MPEG_VIDEO_ASPECT_1x1; + break; + case GO7007_RATIO_4_3: + ctrl->value = V4L2_MPEG_VIDEO_ASPECT_4x3; + break; + case GO7007_RATIO_16_9: + ctrl->value = V4L2_MPEG_VIDEO_ASPECT_16x9; + break; + default: + return -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + ctrl->value = go->gop_size; + break; + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + ctrl->value = go->closed_gop; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + ctrl->value = go->bitrate; + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + strlcpy(cap->driver, "go7007", sizeof(cap->driver)); + strlcpy(cap->card, go->name, sizeof(cap->card)); +#if 0 + strlcpy(cap->bus_info, dev_name(&dev->udev->dev), sizeof(cap->bus_info)); +#endif + + cap->version = KERNEL_VERSION(0, 9, 8); + + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING; /* | V4L2_CAP_AUDIO; */ + + if (go->board_info->flags & GO7007_BOARD_HAS_TUNER) + cap->capabilities |= V4L2_CAP_TUNER; + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + char *desc = NULL; + + switch (fmt->index) { + case 0: + fmt->pixelformat = V4L2_PIX_FMT_MJPEG; + desc = "Motion-JPEG"; + break; + case 1: + fmt->pixelformat = V4L2_PIX_FMT_MPEG; + desc = "MPEG1/MPEG2/MPEG4"; + break; + default: + return -EINVAL; + } + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt->flags = V4L2_FMT_FLAG_COMPRESSED; + + strncpy(fmt->description, desc, sizeof(fmt->description)); + + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt->fmt.pix.width = go->width; + fmt->fmt.pix.height = go->height; + fmt->fmt.pix.pixelformat = (go->format == GO7007_FORMAT_MJPEG) ? + V4L2_PIX_FMT_MJPEG : V4L2_PIX_FMT_MPEG; + fmt->fmt.pix.field = V4L2_FIELD_NONE; + fmt->fmt.pix.bytesperline = 0; + fmt->fmt.pix.sizeimage = GO7007_BUF_SIZE; + fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + return set_capture_size(go, fmt, 1); +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (go->streaming) + return -EBUSY; + + return set_capture_size(go, fmt, 0); +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + int retval = -EBUSY; + unsigned int count, i; + + if (go->streaming) + return retval; + + if (req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + req->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + + mutex_lock(&gofh->lock); + for (i = 0; i < gofh->buf_count; ++i) + if (gofh->bufs[i].mapped > 0) + goto unlock_and_return; + + mutex_lock(&go->hw_lock); + if (go->in_use > 0 && gofh->buf_count == 0) { + mutex_unlock(&go->hw_lock); + goto unlock_and_return; + } + + if (gofh->buf_count > 0) + kfree(gofh->bufs); + + retval = -ENOMEM; + count = req->count; + if (count > 0) { + if (count < 2) + count = 2; + if (count > 32) + count = 32; + + gofh->bufs = kcalloc(count, sizeof(struct go7007_buffer), + GFP_KERNEL); + + if (!gofh->bufs) { + mutex_unlock(&go->hw_lock); + goto unlock_and_return; + } + + for (i = 0; i < count; ++i) { + gofh->bufs[i].go = go; + gofh->bufs[i].index = i; + gofh->bufs[i].state = BUF_STATE_IDLE; + gofh->bufs[i].mapped = 0; + } + + go->in_use = 1; + } else { + go->in_use = 0; + } + + gofh->buf_count = count; + mutex_unlock(&go->hw_lock); + mutex_unlock(&gofh->lock); + + memset(req, 0, sizeof(*req)); + + req->count = count; + req->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req->memory = V4L2_MEMORY_MMAP; + + return 0; + +unlock_and_return: + mutex_unlock(&gofh->lock); + return retval; +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct go7007_file *gofh = priv; + int retval = -EINVAL; + unsigned int index; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return retval; + + index = buf->index; + + mutex_lock(&gofh->lock); + if (index >= gofh->buf_count) + goto unlock_and_return; + + memset(buf, 0, sizeof(*buf)); + buf->index = index; + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + switch (gofh->bufs[index].state) { + case BUF_STATE_QUEUED: + buf->flags = V4L2_BUF_FLAG_QUEUED; + break; + case BUF_STATE_DONE: + buf->flags = V4L2_BUF_FLAG_DONE; + break; + default: + buf->flags = 0; + } + + if (gofh->bufs[index].mapped) + buf->flags |= V4L2_BUF_FLAG_MAPPED; + buf->memory = V4L2_MEMORY_MMAP; + buf->m.offset = index * GO7007_BUF_SIZE; + buf->length = GO7007_BUF_SIZE; + mutex_unlock(&gofh->lock); + + return 0; + +unlock_and_return: + mutex_unlock(&gofh->lock); + return retval; +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + struct go7007_buffer *gobuf; + unsigned long flags; + int retval = -EINVAL; + int ret; + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + buf->memory != V4L2_MEMORY_MMAP) + return retval; + + mutex_lock(&gofh->lock); + if (buf->index < 0 || buf->index >= gofh->buf_count) + goto unlock_and_return; + + gobuf = &gofh->bufs[buf->index]; + if (!gobuf->mapped) + goto unlock_and_return; + + retval = -EBUSY; + if (gobuf->state != BUF_STATE_IDLE) + goto unlock_and_return; + + /* offset will be 0 until we really support USERPTR streaming */ + gobuf->offset = gobuf->user_addr & ~PAGE_MASK; + gobuf->bytesused = 0; + gobuf->frame_offset = 0; + gobuf->modet_active = 0; + if (gobuf->offset > 0) + gobuf->page_count = GO7007_BUF_PAGES + 1; + else + gobuf->page_count = GO7007_BUF_PAGES; + + retval = -ENOMEM; + down_read(¤t->mm->mmap_sem); + ret = get_user_pages(current, current->mm, + gobuf->user_addr & PAGE_MASK, gobuf->page_count, + 1, 1, gobuf->pages, NULL); + up_read(¤t->mm->mmap_sem); + + if (ret != gobuf->page_count) { + int i; + for (i = 0; i < ret; ++i) + page_cache_release(gobuf->pages[i]); + gobuf->page_count = 0; + goto unlock_and_return; + } + + gobuf->state = BUF_STATE_QUEUED; + spin_lock_irqsave(&go->spinlock, flags); + list_add_tail(&gobuf->stream, &go->stream); + spin_unlock_irqrestore(&go->spinlock, flags); + mutex_unlock(&gofh->lock); + + return 0; + +unlock_and_return: + mutex_unlock(&gofh->lock); + return retval; +} + + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + struct go7007_buffer *gobuf; + int retval = -EINVAL; + unsigned long flags; + u32 frame_type_flag; + DEFINE_WAIT(wait); + + if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return retval; + if (buf->memory != V4L2_MEMORY_MMAP) + return retval; + + mutex_lock(&gofh->lock); + if (list_empty(&go->stream)) + goto unlock_and_return; + gobuf = list_entry(go->stream.next, + struct go7007_buffer, stream); + + retval = -EAGAIN; + if (gobuf->state != BUF_STATE_DONE && + !(file->f_flags & O_NONBLOCK)) { + for (;;) { + prepare_to_wait(&go->frame_waitq, &wait, + TASK_INTERRUPTIBLE); + if (gobuf->state == BUF_STATE_DONE) + break; + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + } + finish_wait(&go->frame_waitq, &wait); + } + if (gobuf->state != BUF_STATE_DONE) + goto unlock_and_return; + + spin_lock_irqsave(&go->spinlock, flags); + deactivate_buffer(gobuf); + spin_unlock_irqrestore(&go->spinlock, flags); + frame_type_flag = get_frame_type_flag(gobuf, go->format); + gobuf->state = BUF_STATE_IDLE; + + memset(buf, 0, sizeof(*buf)); + buf->index = gobuf->index; + buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf->bytesused = gobuf->bytesused; + buf->flags = V4L2_BUF_FLAG_MAPPED | frame_type_flag; + buf->field = V4L2_FIELD_NONE; + buf->timestamp = gobuf->timestamp; + buf->sequence = gobuf->seq; + buf->memory = V4L2_MEMORY_MMAP; + buf->m.offset = gobuf->index * GO7007_BUF_SIZE; + buf->length = GO7007_BUF_SIZE; + buf->reserved = gobuf->modet_active; + + mutex_unlock(&gofh->lock); + return 0; + +unlock_and_return: + mutex_unlock(&gofh->lock); + return retval; +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + int retval = 0; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + mutex_lock(&gofh->lock); + mutex_lock(&go->hw_lock); + + if (!go->streaming) { + go->streaming = 1; + go->next_seq = 0; + go->active_buf = NULL; + if (go7007_start_encoder(go) < 0) + retval = -EIO; + else + retval = 0; + } + mutex_unlock(&go->hw_lock); + mutex_unlock(&gofh->lock); + + return retval; +} + +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct go7007_file *gofh = priv; + struct go7007 *go = gofh->go; + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + mutex_lock(&gofh->lock); + go7007_streamoff(go); + mutex_unlock(&gofh->lock); + + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *query) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + int id = query->id; + + if (0 == call_all(&go->v4l2_dev, core, queryctrl, query)) + return 0; + + query->id = id; + return mpeg_query_ctrl(query); +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (0 == call_all(&go->v4l2_dev, core, g_ctrl, ctrl)) + return 0; + + return mpeg_g_ctrl(ctrl, go); +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (0 == call_all(&go->v4l2_dev, core, s_ctrl, ctrl)) + return 0; + + return mpeg_s_ctrl(ctrl, go); +} + +static int vidioc_g_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + struct v4l2_fract timeperframe = { + .numerator = 1001 * go->fps_scale, + .denominator = go->sensor_framerate, + }; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + parm->parm.capture.capability |= V4L2_CAP_TIMEPERFRAME; + parm->parm.capture.timeperframe = timeperframe; + + return 0; +} + +static int vidioc_s_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + unsigned int n, d; + + if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (parm->parm.capture.capturemode != 0) + return -EINVAL; + + n = go->sensor_framerate * + parm->parm.capture.timeperframe.numerator; + d = 1001 * parm->parm.capture.timeperframe.denominator; + if (n != 0 && d != 0 && n > d) + go->fps_scale = (n + d/2) / d; + else + go->fps_scale = 1; + + return 0; +} + +/* VIDIOC_ENUMSTD on go7007 were used for enumerating the supported fps and + its resolution, when the device is not connected to TV. + This is were an API abuse, probably used by the lack of specific IOCTL's to + enumerate it, by the time the driver was written. + + However, since kernel 2.6.19, two new ioctls (VIDIOC_ENUM_FRAMEINTERVALS + and VIDIOC_ENUM_FRAMESIZES) were added for this purpose. + + The two functions below implement the newer ioctls +*/ +static int vidioc_enum_framesizes(struct file *filp, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + /* Return -EINVAL, if it is a TV board */ + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) || + (go->board_info->sensor_flags & GO7007_SENSOR_TV)) + return -EINVAL; + + if (fsize->index > 0) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = go->board_info->sensor_width; + fsize->discrete.height = go->board_info->sensor_height; + + return 0; +} + +static int vidioc_enum_frameintervals(struct file *filp, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + /* Return -EINVAL, if it is a TV board */ + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) || + (go->board_info->sensor_flags & GO7007_SENSOR_TV)) + return -EINVAL; + + if (fival->index > 0) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1001; + fival->discrete.denominator = go->board_info->sensor_framerate; + + return 0; +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + switch (go->standard) { + case GO7007_STD_NTSC: + *std = V4L2_STD_NTSC; + break; + case GO7007_STD_PAL: + *std = V4L2_STD_PAL; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (go->streaming) + return -EBUSY; + + if (!(go->board_info->sensor_flags & GO7007_SENSOR_TV) && *std != 0) + return -EINVAL; + + if (*std == 0) + return -EINVAL; + + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) && + go->input == go->board_info->num_inputs - 1) { + if (!go->i2c_adapter_online) + return -EIO; + if (call_all(&go->v4l2_dev, core, s_std, *std) < 0) + return -EINVAL; + } + + if (*std & V4L2_STD_NTSC) { + go->standard = GO7007_STD_NTSC; + go->sensor_framerate = 30000; + } else if (*std & V4L2_STD_PAL) { + go->standard = GO7007_STD_PAL; + go->sensor_framerate = 25025; + } else if (*std & V4L2_STD_SECAM) { + go->standard = GO7007_STD_PAL; + go->sensor_framerate = 25025; + } else + return -EINVAL; + + call_all(&go->v4l2_dev, core, s_std, *std); + set_capture_size(go, NULL, 0); + + return 0; +} + +static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) && + go->input == go->board_info->num_inputs - 1) { + if (!go->i2c_adapter_online) + return -EIO; + return call_all(&go->v4l2_dev, video, querystd, std); + } else if (go->board_info->sensor_flags & GO7007_SENSOR_TV) + *std = V4L2_STD_NTSC | V4L2_STD_PAL | V4L2_STD_SECAM; + else + *std = 0; + + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (inp->index >= go->board_info->num_inputs) + return -EINVAL; + + strncpy(inp->name, go->board_info->inputs[inp->index].name, + sizeof(inp->name)); + + /* If this board has a tuner, it will be the last input */ + if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) && + inp->index == go->board_info->num_inputs - 1) + inp->type = V4L2_INPUT_TYPE_TUNER; + else + inp->type = V4L2_INPUT_TYPE_CAMERA; + + inp->audioset = 0; + inp->tuner = 0; + if (go->board_info->sensor_flags & GO7007_SENSOR_TV) + inp->std = V4L2_STD_NTSC | V4L2_STD_PAL | + V4L2_STD_SECAM; + else + inp->std = 0; + + return 0; +} + + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *input) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + *input = go->input; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int input) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (input >= go->board_info->num_inputs) + return -EINVAL; + if (go->streaming) + return -EBUSY; + + go->input = input; + + return call_all(&go->v4l2_dev, video, s_routing, input, 0, 0); +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) + return -EINVAL; + if (t->index != 0) + return -EINVAL; + if (!go->i2c_adapter_online) + return -EIO; + + return call_all(&go->v4l2_dev, tuner, g_tuner, t); +} + +static int vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) + return -EINVAL; + if (t->index != 0) + return -EINVAL; + if (!go->i2c_adapter_online) + return -EIO; + + switch (go->board_id) { + case GO7007_BOARDID_PX_TV402U_NA: + case GO7007_BOARDID_PX_TV402U_JP: + /* No selectable options currently */ + if (t->audmode != V4L2_TUNER_MODE_STEREO) + return -EINVAL; + break; + } + + return call_all(&go->v4l2_dev, tuner, s_tuner, t); +} + +static int vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) + return -EINVAL; + if (!go->i2c_adapter_online) + return -EIO; + + f->type = V4L2_TUNER_ANALOG_TV; + + return call_all(&go->v4l2_dev, tuner, g_frequency, f); +} + +static int vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) + return -EINVAL; + if (!go->i2c_adapter_online) + return -EIO; + + return call_all(&go->v4l2_dev, tuner, s_frequency, f); +} + +static int vidioc_cropcap(struct file *file, void *priv, + struct v4l2_cropcap *cropcap) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (cropcap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* These specify the raw input of the sensor */ + switch (go->standard) { + case GO7007_STD_NTSC: + cropcap->bounds.top = 0; + cropcap->bounds.left = 0; + cropcap->bounds.width = 720; + cropcap->bounds.height = 480; + cropcap->defrect.top = 0; + cropcap->defrect.left = 0; + cropcap->defrect.width = 720; + cropcap->defrect.height = 480; + break; + case GO7007_STD_PAL: + cropcap->bounds.top = 0; + cropcap->bounds.left = 0; + cropcap->bounds.width = 720; + cropcap->bounds.height = 576; + cropcap->defrect.top = 0; + cropcap->defrect.left = 0; + cropcap->defrect.width = 720; + cropcap->defrect.height = 576; + break; + case GO7007_STD_OTHER: + cropcap->bounds.top = 0; + cropcap->bounds.left = 0; + cropcap->bounds.width = go->board_info->sensor_width; + cropcap->bounds.height = go->board_info->sensor_height; + cropcap->defrect.top = 0; + cropcap->defrect.left = 0; + cropcap->defrect.width = go->board_info->sensor_width; + cropcap->defrect.height = go->board_info->sensor_height; + break; + } + + return 0; +} + +static int vidioc_g_crop(struct file *file, void *priv, struct v4l2_crop *crop) +{ + struct go7007 *go = ((struct go7007_file *) priv)->go; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + crop->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* These specify the raw input of the sensor */ + switch (go->standard) { + case GO7007_STD_NTSC: + crop->c.top = 0; + crop->c.left = 0; + crop->c.width = 720; + crop->c.height = 480; + break; + case GO7007_STD_PAL: + crop->c.top = 0; + crop->c.left = 0; + crop->c.width = 720; + crop->c.height = 576; + break; + case GO7007_STD_OTHER: + crop->c.top = 0; + crop->c.left = 0; + crop->c.width = go->board_info->sensor_width; + crop->c.height = go->board_info->sensor_height; + break; + } + + return 0; +} + +/* FIXME: vidioc_s_crop is not really implemented!!! + */ +static int vidioc_s_crop(struct file *file, void *priv, struct v4l2_crop *crop) +{ + if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return 0; +} + +static int vidioc_g_jpegcomp(struct file *file, void *priv, + struct v4l2_jpegcompression *params) +{ + memset(params, 0, sizeof(*params)); + params->quality = 50; /* ?? */ + params->jpeg_markers = V4L2_JPEG_MARKER_DHT | + V4L2_JPEG_MARKER_DQT; + + return 0; +} + +static int vidioc_s_jpegcomp(struct file *file, void *priv, + struct v4l2_jpegcompression *params) +{ + if (params->quality != 50 || + params->jpeg_markers != (V4L2_JPEG_MARKER_DHT | + V4L2_JPEG_MARKER_DQT)) + return -EINVAL; + + return 0; +} + +/* FIXME: + Those ioctls are private, and not needed, since several standard + extended controls already provide streaming control. + So, those ioctls should be converted into vidioc_g_ext_ctrls() + and vidioc_s_ext_ctrls() + */ + +#if 0 + /* Temporary ioctls for controlling compression characteristics */ + case GO7007IOC_S_BITRATE: + { + int *bitrate = arg; + + if (go->streaming) + return -EINVAL; + /* Upper bound is kind of arbitrary here */ + if (*bitrate < 64000 || *bitrate > 10000000) + return -EINVAL; + go->bitrate = *bitrate; + return 0; + } + case GO7007IOC_G_BITRATE: + { + int *bitrate = arg; + + *bitrate = go->bitrate; + return 0; + } + case GO7007IOC_S_COMP_PARAMS: + { + struct go7007_comp_params *comp = arg; + + if (go->format == GO7007_FORMAT_MJPEG) + return -EINVAL; + if (comp->gop_size > 0) + go->gop_size = comp->gop_size; + else + go->gop_size = go->sensor_framerate / 1000; + if (go->gop_size != 15) + go->dvd_mode = 0; + /*go->ipb = comp->max_b_frames > 0;*/ /* completely untested */ + if (go->board_info->sensor_flags & GO7007_SENSOR_TV) { + switch (comp->aspect_ratio) { + case GO7007_ASPECT_RATIO_4_3_NTSC: + case GO7007_ASPECT_RATIO_4_3_PAL: + go->aspect_ratio = GO7007_RATIO_4_3; + break; + case GO7007_ASPECT_RATIO_16_9_NTSC: + case GO7007_ASPECT_RATIO_16_9_PAL: + go->aspect_ratio = GO7007_RATIO_16_9; + break; + default: + go->aspect_ratio = GO7007_RATIO_1_1; + break; + } + } + if (comp->flags & GO7007_COMP_OMIT_SEQ_HEADER) { + go->dvd_mode = 0; + go->seq_header_enable = 0; + } else { + go->seq_header_enable = 1; + } + /* fall-through */ + } + case GO7007IOC_G_COMP_PARAMS: + { + struct go7007_comp_params *comp = arg; + + if (go->format == GO7007_FORMAT_MJPEG) + return -EINVAL; + memset(comp, 0, sizeof(*comp)); + comp->gop_size = go->gop_size; + comp->max_b_frames = go->ipb ? 2 : 0; + switch (go->aspect_ratio) { + case GO7007_RATIO_4_3: + if (go->standard == GO7007_STD_NTSC) + comp->aspect_ratio = + GO7007_ASPECT_RATIO_4_3_NTSC; + else + comp->aspect_ratio = + GO7007_ASPECT_RATIO_4_3_PAL; + break; + case GO7007_RATIO_16_9: + if (go->standard == GO7007_STD_NTSC) + comp->aspect_ratio = + GO7007_ASPECT_RATIO_16_9_NTSC; + else + comp->aspect_ratio = + GO7007_ASPECT_RATIO_16_9_PAL; + break; + default: + comp->aspect_ratio = GO7007_ASPECT_RATIO_1_1; + break; + } + if (go->closed_gop) + comp->flags |= GO7007_COMP_CLOSED_GOP; + if (!go->seq_header_enable) + comp->flags |= GO7007_COMP_OMIT_SEQ_HEADER; + return 0; + } + case GO7007IOC_S_MPEG_PARAMS: + { + struct go7007_mpeg_params *mpeg = arg; + + if (go->format != GO7007_FORMAT_MPEG1 && + go->format != GO7007_FORMAT_MPEG2 && + go->format != GO7007_FORMAT_MPEG4) + return -EINVAL; + + if (mpeg->flags & GO7007_MPEG_FORCE_DVD_MODE) { + go->format = GO7007_FORMAT_MPEG2; + go->bitrate = 9800000; + go->gop_size = 15; + go->pali = 0x48; + go->closed_gop = 1; + go->repeat_seqhead = 0; + go->seq_header_enable = 1; + go->gop_header_enable = 1; + go->dvd_mode = 1; + } else { + switch (mpeg->mpeg_video_standard) { + case GO7007_MPEG_VIDEO_MPEG1: + go->format = GO7007_FORMAT_MPEG1; + go->pali = 0; + break; + case GO7007_MPEG_VIDEO_MPEG2: + go->format = GO7007_FORMAT_MPEG2; + if (mpeg->pali >> 24 == 2) + go->pali = mpeg->pali & 0xff; + else + go->pali = 0x48; + break; + case GO7007_MPEG_VIDEO_MPEG4: + go->format = GO7007_FORMAT_MPEG4; + if (mpeg->pali >> 24 == 4) + go->pali = mpeg->pali & 0xff; + else + go->pali = 0xf5; + break; + default: + return -EINVAL; + } + go->gop_header_enable = + mpeg->flags & GO7007_MPEG_OMIT_GOP_HEADER + ? 0 : 1; + if (mpeg->flags & GO7007_MPEG_REPEAT_SEQHEADER) + go->repeat_seqhead = 1; + else + go->repeat_seqhead = 0; + go->dvd_mode = 0; + } + /* fall-through */ + } + case GO7007IOC_G_MPEG_PARAMS: + { + struct go7007_mpeg_params *mpeg = arg; + + memset(mpeg, 0, sizeof(*mpeg)); + switch (go->format) { + case GO7007_FORMAT_MPEG1: + mpeg->mpeg_video_standard = GO7007_MPEG_VIDEO_MPEG1; + mpeg->pali = 0; + break; + case GO7007_FORMAT_MPEG2: + mpeg->mpeg_video_standard = GO7007_MPEG_VIDEO_MPEG2; + mpeg->pali = GO7007_MPEG_PROFILE(2, go->pali); + break; + case GO7007_FORMAT_MPEG4: + mpeg->mpeg_video_standard = GO7007_MPEG_VIDEO_MPEG4; + mpeg->pali = GO7007_MPEG_PROFILE(4, go->pali); + break; + default: + return -EINVAL; + } + if (!go->gop_header_enable) + mpeg->flags |= GO7007_MPEG_OMIT_GOP_HEADER; + if (go->repeat_seqhead) + mpeg->flags |= GO7007_MPEG_REPEAT_SEQHEADER; + if (go->dvd_mode) + mpeg->flags |= GO7007_MPEG_FORCE_DVD_MODE; + return 0; + } + case GO7007IOC_S_MD_PARAMS: + { + struct go7007_md_params *mdp = arg; + + if (mdp->region > 3) + return -EINVAL; + if (mdp->trigger > 0) { + go->modet[mdp->region].pixel_threshold = + mdp->pixel_threshold >> 1; + go->modet[mdp->region].motion_threshold = + mdp->motion_threshold >> 1; + go->modet[mdp->region].mb_threshold = + mdp->trigger >> 1; + go->modet[mdp->region].enable = 1; + } else + go->modet[mdp->region].enable = 0; + /* fall-through */ + } + case GO7007IOC_G_MD_PARAMS: + { + struct go7007_md_params *mdp = arg; + int region = mdp->region; + + if (mdp->region > 3) + return -EINVAL; + memset(mdp, 0, sizeof(struct go7007_md_params)); + mdp->region = region; + if (!go->modet[region].enable) + return 0; + mdp->pixel_threshold = + (go->modet[region].pixel_threshold << 1) + 1; + mdp->motion_threshold = + (go->modet[region].motion_threshold << 1) + 1; + mdp->trigger = + (go->modet[region].mb_threshold << 1) + 1; + return 0; + } + case GO7007IOC_S_MD_REGION: + { + struct go7007_md_region *region = arg; + + if (region->region < 1 || region->region > 3) + return -EINVAL; + return clip_to_modet_map(go, region->region, region->clips); + } +#endif + +static ssize_t go7007_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} + +static void go7007_vm_open(struct vm_area_struct *vma) +{ + struct go7007_buffer *gobuf = vma->vm_private_data; + + ++gobuf->mapped; +} + +static void go7007_vm_close(struct vm_area_struct *vma) +{ + struct go7007_buffer *gobuf = vma->vm_private_data; + unsigned long flags; + + if (--gobuf->mapped == 0) { + spin_lock_irqsave(&gobuf->go->spinlock, flags); + deactivate_buffer(gobuf); + spin_unlock_irqrestore(&gobuf->go->spinlock, flags); + } +} + +/* Copied from videobuf-dma-sg.c */ +static int go7007_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct page *page; + + page = alloc_page(GFP_USER | __GFP_DMA32); + if (!page) + return VM_FAULT_OOM; + clear_user_highpage(page, (unsigned long)vmf->virtual_address); + vmf->page = page; + return 0; +} + +static struct vm_operations_struct go7007_vm_ops = { + .open = go7007_vm_open, + .close = go7007_vm_close, + .fault = go7007_vm_fault, +}; + +static int go7007_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct go7007_file *gofh = file->private_data; + unsigned int index; + + if (gofh->go->status != STATUS_ONLINE) + return -EIO; + if (!(vma->vm_flags & VM_SHARED)) + return -EINVAL; /* only support VM_SHARED mapping */ + if (vma->vm_end - vma->vm_start != GO7007_BUF_SIZE) + return -EINVAL; /* must map exactly one full buffer */ + mutex_lock(&gofh->lock); + index = vma->vm_pgoff / GO7007_BUF_PAGES; + if (index >= gofh->buf_count) { + mutex_unlock(&gofh->lock); + return -EINVAL; /* trying to map beyond requested buffers */ + } + if (index * GO7007_BUF_PAGES != vma->vm_pgoff) { + mutex_unlock(&gofh->lock); + return -EINVAL; /* offset is not aligned on buffer boundary */ + } + if (gofh->bufs[index].mapped > 0) { + mutex_unlock(&gofh->lock); + return -EBUSY; + } + gofh->bufs[index].mapped = 1; + gofh->bufs[index].user_addr = vma->vm_start; + vma->vm_ops = &go7007_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_flags &= ~VM_IO; + vma->vm_private_data = &gofh->bufs[index]; + mutex_unlock(&gofh->lock); + return 0; +} + +static unsigned int go7007_poll(struct file *file, poll_table *wait) +{ + struct go7007_file *gofh = file->private_data; + struct go7007_buffer *gobuf; + + if (list_empty(&gofh->go->stream)) + return POLLERR; + gobuf = list_entry(gofh->go->stream.next, struct go7007_buffer, stream); + poll_wait(file, &gofh->go->frame_waitq, wait); + if (gobuf->state == BUF_STATE_DONE) + return POLLIN | POLLRDNORM; + return 0; +} + +static void go7007_vfl_release(struct video_device *vfd) +{ + struct go7007 *go = video_get_drvdata(vfd); + + video_device_release(vfd); + if (--go->ref_count == 0) + kfree(go); +} + +static struct v4l2_file_operations go7007_fops = { + .owner = THIS_MODULE, + .open = go7007_open, + .release = go7007_release, + .ioctl = video_ioctl2, + .read = go7007_read, + .mmap = go7007_mmap, + .poll = go7007_poll, +}; + +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 = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_g_std = vidioc_g_std, + .vidioc_s_std = vidioc_s_std, + .vidioc_querystd = vidioc_querystd, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_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_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + .vidioc_enum_frameintervals = vidioc_enum_frameintervals, + .vidioc_cropcap = vidioc_cropcap, + .vidioc_g_crop = vidioc_g_crop, + .vidioc_s_crop = vidioc_s_crop, + .vidioc_g_jpegcomp = vidioc_g_jpegcomp, + .vidioc_s_jpegcomp = vidioc_s_jpegcomp, +}; + +static struct video_device go7007_template = { + .name = "go7007", + .fops = &go7007_fops, + .release = go7007_vfl_release, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = V4L2_STD_ALL, + .current_norm = V4L2_STD_NTSC, +}; + +int go7007_v4l2_init(struct go7007 *go) +{ + int rv; + + go->video_dev = video_device_alloc(); + if (go->video_dev == NULL) + return -ENOMEM; + *go->video_dev = go7007_template; + go->video_dev->parent = go->dev; + rv = video_register_device(go->video_dev, VFL_TYPE_GRABBER, -1); + if (rv < 0) { + video_device_release(go->video_dev); + go->video_dev = NULL; + return rv; + } + rv = v4l2_device_register(go->dev, &go->v4l2_dev); + if (rv < 0) { + video_device_release(go->video_dev); + go->video_dev = NULL; + return rv; + } + video_set_drvdata(go->video_dev, go); + ++go->ref_count; + printk(KERN_INFO "%s: registered device %s [v4l2]\n", + go->video_dev->name, video_device_node_name(go->video_dev)); + + return 0; +} + +void go7007_v4l2_remove(struct go7007 *go) +{ + unsigned long flags; + + mutex_lock(&go->hw_lock); + if (go->streaming) { + go->streaming = 0; + go7007_stream_stop(go); + spin_lock_irqsave(&go->spinlock, flags); + abort_queued(go); + spin_unlock_irqrestore(&go->spinlock, flags); + } + mutex_unlock(&go->hw_lock); + if (go->video_dev) + video_unregister_device(go->video_dev); + v4l2_device_unregister(&go->v4l2_dev); +} diff --git a/drivers/staging/media/go7007/go7007.h b/drivers/staging/media/go7007/go7007.h new file mode 100644 index 00000000..7399c915 --- /dev/null +++ b/drivers/staging/media/go7007/go7007.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and the associated README documentation file (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* DEPRECATED -- use V4L2_PIX_FMT_MPEG and then call GO7007IOC_S_MPEG_PARAMS + * to select between MPEG1, MPEG2, and MPEG4 */ +#define V4L2_PIX_FMT_MPEG4 v4l2_fourcc('M', 'P', 'G', '4') /* MPEG4 */ + +/* These will be replaced with a better interface + * soon, so don't get too attached to them */ +#define GO7007IOC_S_BITRATE _IOW('V', BASE_VIDIOC_PRIVATE + 0, int) +#define GO7007IOC_G_BITRATE _IOR('V', BASE_VIDIOC_PRIVATE + 1, int) + +enum go7007_aspect_ratio { + GO7007_ASPECT_RATIO_1_1 = 0, + GO7007_ASPECT_RATIO_4_3_NTSC = 1, + GO7007_ASPECT_RATIO_4_3_PAL = 2, + GO7007_ASPECT_RATIO_16_9_NTSC = 3, + GO7007_ASPECT_RATIO_16_9_PAL = 4, +}; + +/* Used to set generic compression parameters */ +struct go7007_comp_params { + __u32 gop_size; + __u32 max_b_frames; + enum go7007_aspect_ratio aspect_ratio; + __u32 flags; + __u32 reserved[8]; +}; + +#define GO7007_COMP_CLOSED_GOP 0x00000001 +#define GO7007_COMP_OMIT_SEQ_HEADER 0x00000002 + +enum go7007_mpeg_video_standard { + GO7007_MPEG_VIDEO_MPEG1 = 0, + GO7007_MPEG_VIDEO_MPEG2 = 1, + GO7007_MPEG_VIDEO_MPEG4 = 2, +}; + +/* Used to set parameters for V4L2_PIX_FMT_MPEG format */ +struct go7007_mpeg_params { + enum go7007_mpeg_video_standard mpeg_video_standard; + __u32 flags; + __u32 pali; + __u32 reserved[8]; +}; + +#define GO7007_MPEG_FORCE_DVD_MODE 0x00000001 +#define GO7007_MPEG_OMIT_GOP_HEADER 0x00000002 +#define GO7007_MPEG_REPEAT_SEQHEADER 0x00000004 + +#define GO7007_MPEG_PROFILE(format, pali) (((format)<<24)|(pali)) + +#define GO7007_MPEG2_PROFILE_MAIN_MAIN GO7007_MPEG_PROFILE(2, 0x48) + +#define GO7007_MPEG4_PROFILE_S_L0 GO7007_MPEG_PROFILE(4, 0x08) +#define GO7007_MPEG4_PROFILE_S_L1 GO7007_MPEG_PROFILE(4, 0x01) +#define GO7007_MPEG4_PROFILE_S_L2 GO7007_MPEG_PROFILE(4, 0x02) +#define GO7007_MPEG4_PROFILE_S_L3 GO7007_MPEG_PROFILE(4, 0x03) +#define GO7007_MPEG4_PROFILE_ARTS_L1 GO7007_MPEG_PROFILE(4, 0x91) +#define GO7007_MPEG4_PROFILE_ARTS_L2 GO7007_MPEG_PROFILE(4, 0x92) +#define GO7007_MPEG4_PROFILE_ARTS_L3 GO7007_MPEG_PROFILE(4, 0x93) +#define GO7007_MPEG4_PROFILE_ARTS_L4 GO7007_MPEG_PROFILE(4, 0x94) +#define GO7007_MPEG4_PROFILE_AS_L0 GO7007_MPEG_PROFILE(4, 0xf0) +#define GO7007_MPEG4_PROFILE_AS_L1 GO7007_MPEG_PROFILE(4, 0xf1) +#define GO7007_MPEG4_PROFILE_AS_L2 GO7007_MPEG_PROFILE(4, 0xf2) +#define GO7007_MPEG4_PROFILE_AS_L3 GO7007_MPEG_PROFILE(4, 0xf3) +#define GO7007_MPEG4_PROFILE_AS_L4 GO7007_MPEG_PROFILE(4, 0xf4) +#define GO7007_MPEG4_PROFILE_AS_L5 GO7007_MPEG_PROFILE(4, 0xf5) + +struct go7007_md_params { + __u16 region; + __u16 trigger; + __u16 pixel_threshold; + __u16 motion_threshold; + __u32 reserved[8]; +}; + +struct go7007_md_region { + __u16 region; + __u16 flags; + struct v4l2_clip *clips; + __u32 reserved[8]; +}; + +#define GO7007IOC_S_MPEG_PARAMS _IOWR('V', BASE_VIDIOC_PRIVATE + 2, \ + struct go7007_mpeg_params) +#define GO7007IOC_G_MPEG_PARAMS _IOR('V', BASE_VIDIOC_PRIVATE + 3, \ + struct go7007_mpeg_params) +#define GO7007IOC_S_COMP_PARAMS _IOWR('V', BASE_VIDIOC_PRIVATE + 4, \ + struct go7007_comp_params) +#define GO7007IOC_G_COMP_PARAMS _IOR('V', BASE_VIDIOC_PRIVATE + 5, \ + struct go7007_comp_params) +#define GO7007IOC_S_MD_PARAMS _IOWR('V', BASE_VIDIOC_PRIVATE + 6, \ + struct go7007_md_params) +#define GO7007IOC_G_MD_PARAMS _IOR('V', BASE_VIDIOC_PRIVATE + 7, \ + struct go7007_md_params) +#define GO7007IOC_S_MD_REGION _IOW('V', BASE_VIDIOC_PRIVATE + 8, \ + struct go7007_md_region) diff --git a/drivers/staging/media/go7007/go7007.txt b/drivers/staging/media/go7007/go7007.txt new file mode 100644 index 00000000..9db1f395 --- /dev/null +++ b/drivers/staging/media/go7007/go7007.txt @@ -0,0 +1,481 @@ +This is a driver for the WIS GO7007SB multi-format video encoder. + +Pete Eberlein + +The driver was originally released under the GPL and is currently hosted at: +http://nikosapi.org/wiki/index.php/WIS_Go7007_Linux_driver +The go7007 firmware can be acquired from the package on the site above. + +I've modified the driver to support the following Video4Linux2 MPEG +controls, with acceptable values: + +V4L2_CID_MPEG_STREAM_TYPE V4L2_MPEG_STREAM_TYPE_MPEG2_DVD + V4L2_MPEG_STREAM_TYPE_MPEG_ELEM +V4L2_CID_MPEG_VIDEO_ENCODING V4L2_MPEG_VIDEO_ENCODING_MPEG_1 + V4L2_MPEG_VIDEO_ENCODING_MPEG_2 + V4L2_MPEG_VIDEO_ENCODING_MPEG_4 +V4L2_CID_MPEG_VIDEO_ASPECT V4L2_MPEG_VIDEO_ASPECT_1x1 + V4L2_MPEG_VIDEO_ASPECT_4x3 + V4L2_MPEG_VIDEO_ASPECT_16x9 +V4L2_CID_MPEG_VIDEO_GOP_SIZE integer +V4L2_CID_MPEG_VIDEO_BITRATE 64000 .. 10000000 + +These should be used instead of the non-standard GO7007 ioctls described +below. + + +The README files from the orignal package appear below: + +--------------------------------------------------------------------------- + WIS GO7007SB Public Linux Driver +--------------------------------------------------------------------------- + + +*** Please see the file RELEASE-NOTES for important last-minute updates *** + + + 0. OVERVIEW AND LICENSING/DISCLAIMER + + +This driver kit contains Linux drivers for the WIS GO7007SB multi-format +video encoder. Only kernel version 2.6.x is supported. The video stream +is available through the Video4Linux2 API and the audio stream is available +through the ALSA API (or the OSS emulation layer of the ALSA system). + +The files in kernel/ and hotplug/ are licensed under the GNU General Public +License Version 2 from the Free Software Foundation. A copy of the license +is included in the file COPYING. + +The example applications in apps/ and C header files in include/ are +licensed under a permissive license included in the source files which +allows copying, modification and redistribution for any purpose without +attribution. + +The firmware files included in the firmware/ directory may be freely +redistributed only in conjunction with this document; but modification, +tampering and reverse engineering are prohibited. + +MICRONAS USA, INC., MAKES NO WARRANTIES TO ANY PERSON OR ENTITY WITH +RESPECT TO THE SOFTWARE OR ANY DERIVATIVES THEREOF OR ANY SERVICES OR +LICENSES AND DISCLAIMS ALL IMPLIED WARRANTIES, INCLUDING WITHOUT LIMITATION +WARRANTIES OF MERCHANTABILITY, SUPPORT, AND FITNESS FOR A PARTICULAR +PURPOSE AND NON-INFRINGEMENT. + + + 1. SYSTEM REQUIREMENTS + + +This driver requires Linux kernel 2.6. Kernel 2.4 is not supported. Using +kernel 2.6.10 or later is recommended, as earlier kernels are known to have +unstable USB 2.0 support. + +A fully built kernel source tree must be available. Typically this will be +linked from "/lib/modules//build" for convenience. If this +link does not exist, an extra parameter will need to be passed to the +`make` command. + +All vendor-built kernels should already be configured properly. However, +for custom-built kernels, the following options need to be enabled in the +kernel as built-in or modules: + + CONFIG_HOTPLUG - Support for hot-pluggable devices + CONFIG_MODULES - Enable loadable module support + CONFIG_KMOD - Automatic kernel module loading + CONFIG_FW_LOADER - Hotplug firmware loading support + CONFIG_I2C - I2C support + CONFIG_VIDEO_DEV - Video For Linux + CONFIG_SOUND - Sound card support + CONFIG_SND - Advanced Linux Sound Architecture + CONFIG_USB - Support for Host-side USB + CONFIG_USB_DEVICEFS - USB device filesystem + CONFIG_USB_EHCI_HCD - EHCI HCD (USB 2.0) support + +Additionally, to use the example application, the following options need to +be enabled in the ALSA section: + + CONFIG_SND_MIXER_OSS - OSS Mixer API + CONFIG_SND_PCM_OSS - OSS PCM (digital audio) API + +The hotplug scripts, along with the fxload utility, must also be installed. +These scripts can be obtained from . +Hotplugging is used for loading firmware into the Cypruss EZ-USB chip using +fxload and for loading firmware into the driver using the firmware agent. + + + 2. COMPILING AND INSTALLING THE DRIVER + + +Most users should be able to compile the driver by simply running: + + $ make + +in the top-level directory of the driver kit. First the kernel modules +will be built, followed by the example applications. + +If the build system is unable to locate the kernel source tree for the +currently-running kernel, or if the module should be built for a kernel +other than the currently-running kernel, an additional parameter will need +to be passed to make to specify the appropriate kernel source directory: + + $ make KERNELSRC=/usr/src/linux-2.6.10-custom3 + +Once the compile completes, the driver and firmware files should be +installed by running: + + $ make install + +The kernel modules will be placed in "/lib/modules//extra" +and the firmware files will be placed in the appropriate hotplug firmware +directory, usually /lib/firmware. In addition, USB maps and scripts will +be placed in /etc/hotplug/usb to enable fxload to initialize the EZ-USB +control chip when the device is connected. + + + 3. PAL/SECAM TUNER CONFIGURATION (TV402U-EU only) + + +The PAL model of the Plextor ConvertX TV402U may require additional +configuration to correctly select the appropriate TV frequency band and +audio subchannel. + +Users with a device other than the Plextor ConvertX TV402U-EU should skip +this section. + +The wide variety of PAL TV systems used in Europe requires that additional +information about the local TV standards be passed to the driver in order +to properly tune TV channels. The two necessary parameters are (a) the PAL +TV band, and (b) the audio subchannel format in use. + +In many cases, the appropriate TV band selection is passed to the driver +from applications. However, in some cases, the application only specifies +that the driver should use PAL but not the specific information about the +appropriate TV band. To work around this issue, the correct TV band may be +specified in the "force_band" parameter to the wis-sony-tuner module: + + TV band force_band + ------- ---------- + PAL B/G B + PAL I I + PAL D/K D + SECAM L L + +If the "force_band" parameter is specified, the driver will ignore any TV +band specified by applications and will always use the band provided in the +module parameter. + +The other parameter that can be specified is the audio subchannel format. +There are several stereo audio carrier systems in use, including NICAM and +three varieties of A2. To receive audio broadcast on one of these stereo +carriers, the "force_mpx_mode" parameter must be specified to the +wis-sony-tuner module. + + TV band Audio subcarrier force_mpx_mode + ------- ---------------- -------------- + PAL B/G Mono (default) 1 + PAL B/G A2 2 + PAL B/G NICAM 3 + PAL I Mono (default) 4 + PAL I NICAM 5 + PAL D/K Mono (default) 6 + PAL D/K A2 (1) 7 + PAL D/K A2 (2) 8 + PAL D/K A2 (3) 9 + PAL D/K NICAM 10 + SECAM L Mono (default) 11 + SECAM L NICAM 12 + +If the "force_mpx_mode" parameter is not specified, the correct mono-only +mode will be chosen based on the TV band. However, the tuner will not +receive stereo audio or bilingual broadcasts correctly. + +To pass the "force_band" or "force_mpx_mode" parameters to the +wis-sony-tuner module, the following line must be added to the modprobe +configuration file, which varies from one Linux distribution to another. + + options wis-sony-tuner force_band=B force_mpx_mode=2 + +The above example would force the tuner to the PAL B/G TV band and receive +stereo audio broadcasts on the A2 carrier. + +To verify that the configuration has been placed in the correct location, +execute: + + $ modprobe -c | grep wis-sony-tuner + +If the configuration line appears, then modprobe will pass the parameters +correctly the next time the wis-sony-tuner module is loaded into the +kernel. + + + 4. TESTING THE DRIVER + + +Because few Linux applications are able to correctly capture from +Video4Linux2 devices with only compressed formats supported, the new driver +should be tested with the "gorecord" application in the apps/ directory. + +First connect a video source to the device, such as a DVD player or VCR. +This will be captured to a file for testing the driver. If an input source +is unavailable, a test file can still be captured, but the video will be +black and the audio will be silent. + +This application will auto-detect the V4L2 and ALSA/OSS device names of the +hardware and will record video and audio to an AVI file for a specified +number of seconds. For example: + + $ apps/gorecord -duration 60 capture.avi + +If this application does not successfully record an AVI file, the error +messages produced by gorecord and recorded in the system log (usually in +/var/log/messages) should provide information to help resolve the problem. + +Supplying no parameters to gorecord will cause it to probe the available +devices and exit. Use the -help flag for usage information. + + + 5. USING THE DRIVER + + +The V4L2 device implemented by the driver provides a standard compressed +format API, within the following criteria: + + * Applications that only support the original Video4Linux1 API will not + be able to communicate with this driver at all. + + * No raw video modes are supported, so applications like xawtv that + expect only uncompressed video will not function. + + * Supported compression formats are: Motion-JPEG, MPEG1, MPEG2 and MPEG4. + + * MPEG video formats are delivered as Video Elementary Streams only. + Program Stream (PS), Transport Stream (TS) and Packetized Elementary + Stream (PES) formats are not supported. + + * Video parameters such as format and input port may not be changed while + the encoder is active. + + * The audio capture device only functions when the video encoder is + actively capturing video. Attempts to read from the audio device when + the encoder is inactive will result in an I/O error. + + * The native format of the audio device is 48Khz 2-channel 16-bit + little-endian PCM, delivered through the ALSA system. No audio + compression is implemented in the hardware. ALSA may convert to other + uncompressed formats on the fly. + +The include/ directory contains a C header file describing non-standard +features of the GO7007SB encoder, which are described below: + + + GO7007IOC_S_COMP_PARAMS, GO7007IOC_G_COMP_PARAMS + + These ioctls are used to negotiate general compression parameters. + + To query the current parameters, call the GO7007IOC_G_COMP_PARAMS ioctl + with a pointer to a struct go7007_comp_params. If the driver is not + set to MPEG format, the EINVAL error code will be returned. + + To change the current parameters, initialize all fields of a struct + go7007_comp_params and call the GO7007_IOC_S_COMP_PARAMS ioctl with a + pointer to this structure. The driver will return the current + parameters with any necessary changes to conform to the limitations of + the hardware or current compression mode. Any or all fields can be set + to zero to request a reasonable default value. If the driver is not + set to MPEG format, the EINVAL error code will be returned. When I/O + is in progress, the EBUSY error code will be returned. + + Fields in struct go7007_comp_params: + + __u32 The maximum number of frames in each + gop_size Group Of Pictures; i.e. the maximum + number of frames minus one between + each key frame. + + __u32 The maximum number of sequential + max_b_frames bidirectionally-predicted frames. + (B-frames are not yet supported.) + + enum go7007_aspect_ratio The aspect ratio to be encoded in the + aspect_ratio meta-data of the compressed format. + + Choices are: + GO7007_ASPECT_RATIO_1_1 + GO7007_ASPECT_RATIO_4_3_NTSC + GO7007_ASPECT_RATIO_4_3_PAL + GO7007_ASPECT_RATIO_16_9_NTSC + GO7007_ASPECT_RATIO_16_9_PAL + + __u32 Bit-wise OR of control flags (below) + flags + + Flags in struct go7007_comp_params: + + GO7007_COMP_CLOSED_GOP Only produce self-contained GOPs, used + to produce streams appropriate for + random seeking. + + GO7007_COMP_OMIT_SEQ_HEADER Omit the stream sequence header. + + + GO7007IOC_S_MPEG_PARAMS, GO7007IOC_G_MPEG_PARAMS + + These ioctls are used to negotiate MPEG-specific stream parameters when + the pixelformat has been set to V4L2_PIX_FMT_MPEG. + + To query the current parameters, call the GO7007IOC_G_MPEG_PARAMS ioctl + with a pointer to a struct go7007_mpeg_params. If the driver is not + set to MPEG format, the EINVAL error code will be returned. + + To change the current parameters, initialize all fields of a struct + go7007_mpeg_params and call the GO7007_IOC_S_MPEG_PARAMS ioctl with a + pointer to this structure. The driver will return the current + parameters with any necessary changes to conform to the limitations of + the hardware or selected MPEG mode. Any or all fields can be set to + zero to request a reasonable default value. If the driver is not set + to MPEG format, the EINVAL error code will be returned. When I/O is in + progress, the EBUSY error code will be returned. + + Fields in struct go7007_mpeg_params: + + enum go7007_mpeg_video_standard + mpeg_video_standard The MPEG video standard in which to + compress the video. + + Choices are: + GO7007_MPEG_VIDEO_MPEG1 + GO7007_MPEG_VIDEO_MPEG2 + GO7007_MPEG_VIDEO_MPEG4 + + __u32 Bit-wise OR of control flags (below) + flags + + __u32 The profile and level indication to be + pali stored in the sequence header. This + is only used as an indicator to the + decoder, and does not affect the MPEG + features used in the video stream. + Not valid for MPEG1. + + Choices for MPEG2 are: + GO7007_MPEG2_PROFILE_MAIN_MAIN + + Choices for MPEG4 are: + GO7007_MPEG4_PROFILE_S_L0 + GO7007_MPEG4_PROFILE_S_L1 + GO7007_MPEG4_PROFILE_S_L2 + GO7007_MPEG4_PROFILE_S_L3 + GO7007_MPEG4_PROFILE_ARTS_L1 + GO7007_MPEG4_PROFILE_ARTS_L2 + GO7007_MPEG4_PROFILE_ARTS_L3 + GO7007_MPEG4_PROFILE_ARTS_L4 + GO7007_MPEG4_PROFILE_AS_L0 + GO7007_MPEG4_PROFILE_AS_L1 + GO7007_MPEG4_PROFILE_AS_L2 + GO7007_MPEG4_PROFILE_AS_L3 + GO7007_MPEG4_PROFILE_AS_L4 + GO7007_MPEG4_PROFILE_AS_L5 + + Flags in struct go7007_mpeg_params: + + GO7007_MPEG_FORCE_DVD_MODE Force all compression parameters and + bitrate control settings to comply + with DVD MPEG2 stream requirements. + This overrides most compression and + bitrate settings! + + GO7007_MPEG_OMIT_GOP_HEADER Omit the GOP header. + + GO7007_MPEG_REPEAT_SEQHEADER Repeat the MPEG sequence header at + the start of each GOP. + + + GO7007IOC_S_BITRATE, GO7007IOC_G_BITRATE + + These ioctls are used to set and query the target bitrate value for the + compressed video stream. The bitrate may be selected by storing the + target bits per second in an int and calling GO7007IOC_S_BITRATE with a + pointer to the int. The bitrate may be queried by calling + GO7007IOC_G_BITRATE with a pointer to an int where the current bitrate + will be stored. + + Note that this is the primary means of controlling the video quality + for all compression modes, including V4L2_PIX_FMT_MJPEG. The + VIDIOC_S_JPEGCOMP ioctl is not supported. + + +---------------------------------------------------------------------------- + Installing the WIS PCI Voyager Driver +--------------------------------------------------------------------------- + +The WIS PCI Voyager driver requires several patches to the Linux 2.6.11.x +kernel source tree before compiling the driver. These patches update the +in-kernel SAA7134 driver to the newest development version and patch bugs +in the TDA8290/TDA8275 tuner driver. + +The following patches must be downloaded from Gerd Knorr's website and +applied in the order listed: + + http://dl.bytesex.org/patches/2.6.11-2/i2c-tuner + http://dl.bytesex.org/patches/2.6.11-2/i2c-tuner2 + http://dl.bytesex.org/patches/2.6.11-2/v4l2-api-mpeg + http://dl.bytesex.org/patches/2.6.11-2/saa7134-update + +The following patches are included with this SDK and can be applied in any +order: + + patches/2.6.11/saa7134-voyager.diff + patches/2.6.11/tda8275-newaddr.diff + patches/2.6.11/tda8290-ntsc.diff + +Check to make sure the CONFIG_VIDEO_SAA7134 option is enabled in the kernel +configuration, and build and install the kernel. + +After rebooting into the new kernel, the GO7007 driver can be compiled and +installed: + + $ make SAA7134_BUILD=y + $ make install + $ modprobe saa7134-go7007 + +There will be two V4L video devices associated with the PCI Voyager. The +first device (most likely /dev/video0) provides access to the raw video +capture mode of the SAA7133 device and is used to configure the source +video parameters and tune the TV tuner. This device can be used with xawtv +or other V4L(2) video software as a standard uncompressed device. + +The second device (most likely /dev/video1) provides access to the +compression functions of the GO7007. It can be tested using the gorecord +application in the apps/ directory of this SDK: + + $ apps/gorecord -vdevice /dev/video1 -noaudio test.avi + +Currently the frame resolution is fixed at 720x480 (NTSC) or 720x576 (PAL), +and the video standard must be specified to both the raw and the compressed +video devices (xawtv and gorecord, for example). + + +-------------------------------------------------------------------------- +RELEASE NOTES FOR WIS GO7007SB LINUX DRIVER +--------------------------------------------------------------------------- + +Last updated: 5 November 2005 + + - Release 0.9.7 includes new support for using udev to run fxload. The + install script should automatically detect whether the old hotplug + scripts or the new udev rules should be used. To force the use of + hotplug, run "make install USE_UDEV=n". To force the use of udev, run + "make install USE_UDEV=y". + + - Motion detection is supported but undocumented. Try the `modet` app + for a demonstration of how to use the facility. + + - Using USB2.0 devices such as the TV402U with USB1.1 HCDs or hubs can + cause buffer overruns and frame drops, even at low framerates, due to + inconsistency in the bitrate control mechanism. + + - On devices with an SAA7115, including the Plextor ConvertX, video height + values of 96, 128, 160, 192, 256, 320, and 384 do not work in NTSC mode. + All valid heights up to 512 work correctly in PAL mode. + + - The WIS Star Trek and PCI Voyager boards have no support yet for audio + or the TV tuner. diff --git a/drivers/staging/media/go7007/s2250-board.c b/drivers/staging/media/go7007/s2250-board.c new file mode 100644 index 00000000..014d3841 --- /dev/null +++ b/drivers/staging/media/go7007/s2250-board.c @@ -0,0 +1,702 @@ +/* + * Copyright (C) 2008 Sensoray Company Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "go7007-priv.h" + +MODULE_DESCRIPTION("Sensoray 2250/2251 i2c v4l2 subdev driver"); +MODULE_LICENSE("GPL v2"); + +#define TLV320_ADDRESS 0x34 +#define VPX322_ADDR_ANALOGCONTROL1 0x02 +#define VPX322_ADDR_BRIGHTNESS0 0x0127 +#define VPX322_ADDR_BRIGHTNESS1 0x0131 +#define VPX322_ADDR_CONTRAST0 0x0128 +#define VPX322_ADDR_CONTRAST1 0x0132 +#define VPX322_ADDR_HUE 0x00dc +#define VPX322_ADDR_SAT 0x0030 + +struct go7007_usb_board { + unsigned int flags; + struct go7007_board_info main_info; +}; + +struct go7007_usb { + struct go7007_usb_board *board; + struct mutex i2c_lock; + struct usb_device *usbdev; + struct urb *video_urbs[8]; + struct urb *audio_urbs[8]; + struct urb *intr_urb; +}; + +static unsigned char aud_regs[] = { + 0x1e, 0x00, + 0x00, 0x17, + 0x02, 0x17, + 0x04, 0xf9, + 0x06, 0xf9, + 0x08, 0x02, + 0x0a, 0x00, + 0x0c, 0x00, + 0x0a, 0x00, + 0x0c, 0x00, + 0x0e, 0x02, + 0x10, 0x00, + 0x12, 0x01, + 0x00, 0x00, +}; + + +static unsigned char vid_regs[] = { + 0xF2, 0x0f, + 0xAA, 0x00, + 0xF8, 0xff, + 0x00, 0x00, +}; + +static u16 vid_regs_fp[] = { + 0x028, 0x067, + 0x120, 0x016, + 0x121, 0xcF2, + 0x122, 0x0F2, + 0x123, 0x00c, + 0x124, 0x2d0, + 0x125, 0x2e0, + 0x126, 0x004, + 0x128, 0x1E0, + 0x12A, 0x016, + 0x12B, 0x0F2, + 0x12C, 0x0F2, + 0x12D, 0x00c, + 0x12E, 0x2d0, + 0x12F, 0x2e0, + 0x130, 0x004, + 0x132, 0x1E0, + 0x140, 0x060, + 0x153, 0x00C, + 0x154, 0x200, + 0x150, 0x801, + 0x000, 0x000 +}; + +/* PAL specific values */ +static u16 vid_regs_fp_pal[] = +{ + 0x120, 0x017, + 0x121, 0xd22, + 0x122, 0x122, + 0x12A, 0x017, + 0x12B, 0x122, + 0x12C, 0x122, + 0x140, 0x060, + 0x000, 0x000, +}; + +struct s2250 { + struct v4l2_subdev sd; + v4l2_std_id std; + int input; + int brightness; + int contrast; + int saturation; + int hue; + int reg12b_val; + int audio_input; + struct i2c_client *audio; +}; + +static inline struct s2250 *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct s2250, sd); +} + +/* from go7007-usb.c which is Copyright (C) 2005-2006 Micronas USA Inc.*/ +static int go7007_usb_vendor_request(struct go7007 *go, u16 request, + u16 value, u16 index, void *transfer_buffer, int length, int in) +{ + struct go7007_usb *usb = go->hpi_context; + int timeout = 5000; + + if (in) { + return usb_control_msg(usb->usbdev, + usb_rcvctrlpipe(usb->usbdev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + value, index, transfer_buffer, length, timeout); + } else { + return usb_control_msg(usb->usbdev, + usb_sndctrlpipe(usb->usbdev, 0), request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, index, transfer_buffer, length, timeout); + } +} +/* end from go7007-usb.c which is Copyright (C) 2005-2006 Micronas USA Inc.*/ + +static int write_reg(struct i2c_client *client, u8 reg, u8 value) +{ + struct go7007 *go = i2c_get_adapdata(client->adapter); + struct go7007_usb *usb; + int rc; + int dev_addr = client->addr << 1; /* firmware wants 8-bit address */ + u8 *buf; + + if (go == NULL) + return -ENODEV; + + if (go->status == STATUS_SHUTDOWN) + return -EBUSY; + + buf = kzalloc(16, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + usb = go->hpi_context; + if (mutex_lock_interruptible(&usb->i2c_lock) != 0) { + printk(KERN_INFO "i2c lock failed\n"); + kfree(buf); + return -EINTR; + } + rc = go7007_usb_vendor_request(go, 0x55, dev_addr, + (reg<<8 | value), + buf, + 16, 1); + + mutex_unlock(&usb->i2c_lock); + kfree(buf); + return rc; +} + +static int write_reg_fp(struct i2c_client *client, u16 addr, u16 val) +{ + struct go7007 *go = i2c_get_adapdata(client->adapter); + struct go7007_usb *usb; + int rc; + u8 *buf; + struct s2250 *dec = i2c_get_clientdata(client); + + if (go == NULL) + return -ENODEV; + + if (go->status == STATUS_SHUTDOWN) + return -EBUSY; + + buf = kzalloc(16, GFP_KERNEL); + + if (buf == NULL) + return -ENOMEM; + + + + memset(buf, 0xcd, 6); + + usb = go->hpi_context; + if (mutex_lock_interruptible(&usb->i2c_lock) != 0) { + printk(KERN_INFO "i2c lock failed\n"); + kfree(buf); + return -EINTR; + } + rc = go7007_usb_vendor_request(go, 0x57, addr, val, buf, 16, 1); + mutex_unlock(&usb->i2c_lock); + if (rc < 0) { + kfree(buf); + return rc; + } + + if (buf[0] == 0) { + unsigned int subaddr, val_read; + + subaddr = (buf[4] << 8) + buf[5]; + val_read = (buf[2] << 8) + buf[3]; + kfree(buf); + if (val_read != val) { + printk(KERN_INFO "invalid fp write %x %x\n", + val_read, val); + return -EFAULT; + } + if (subaddr != addr) { + printk(KERN_INFO "invalid fp write addr %x %x\n", + subaddr, addr); + return -EFAULT; + } + } else { + kfree(buf); + return -EFAULT; + } + + /* save last 12b value */ + if (addr == 0x12b) + dec->reg12b_val = val; + + return 0; +} + +static int read_reg_fp(struct i2c_client *client, u16 addr, u16 *val) +{ + struct go7007 *go = i2c_get_adapdata(client->adapter); + struct go7007_usb *usb; + int rc; + u8 *buf; + + if (go == NULL) + return -ENODEV; + + if (go->status == STATUS_SHUTDOWN) + return -EBUSY; + + buf = kzalloc(16, GFP_KERNEL); + + if (buf == NULL) + return -ENOMEM; + + + + memset(buf, 0xcd, 6); + usb = go->hpi_context; + if (mutex_lock_interruptible(&usb->i2c_lock) != 0) { + printk(KERN_INFO "i2c lock failed\n"); + kfree(buf); + return -EINTR; + } + rc = go7007_usb_vendor_request(go, 0x58, addr, 0, buf, 16, 1); + mutex_unlock(&usb->i2c_lock); + if (rc < 0) { + kfree(buf); + return rc; + } + + *val = (buf[0] << 8) | buf[1]; + kfree(buf); + + return 0; +} + + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; !((regs[i] == 0x00) && (regs[i+1] == 0x00)); i += 2) { + if (write_reg(client, regs[i], regs[i+1]) < 0) { + printk(KERN_INFO "s2250: failed\n"); + return -1; + } + } + return 0; +} + +static int write_regs_fp(struct i2c_client *client, u16 *regs) +{ + int i; + + for (i = 0; !((regs[i] == 0x00) && (regs[i+1] == 0x00)); i += 2) { + if (write_reg_fp(client, regs[i], regs[i+1]) < 0) { + printk(KERN_INFO "s2250: failed fp\n"); + return -1; + } + } + return 0; +} + + +/* ------------------------------------------------------------------------- */ + +static int s2250_s_video_routing(struct v4l2_subdev *sd, u32 input, u32 output, + u32 config) +{ + struct s2250 *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int vidsys; + + vidsys = (state->std == V4L2_STD_NTSC) ? 0x01 : 0x00; + if (input == 0) { + /* composite */ + write_reg_fp(client, 0x20, 0x020 | vidsys); + write_reg_fp(client, 0x21, 0x662); + write_reg_fp(client, 0x140, 0x060); + } else if (input == 1) { + /* S-Video */ + write_reg_fp(client, 0x20, 0x040 | vidsys); + write_reg_fp(client, 0x21, 0x666); + write_reg_fp(client, 0x140, 0x060); + } else { + return -EINVAL; + } + state->input = input; + return 0; +} + +static int s2250_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + struct s2250 *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + u16 vidsource; + + vidsource = (state->input == 1) ? 0x040 : 0x020; + switch (norm) { + case V4L2_STD_NTSC: + write_regs_fp(client, vid_regs_fp); + write_reg_fp(client, 0x20, vidsource | 1); + break; + case V4L2_STD_PAL: + write_regs_fp(client, vid_regs_fp); + write_regs_fp(client, vid_regs_fp_pal); + write_reg_fp(client, 0x20, vidsource); + break; + default: + return -EINVAL; + } + state->std = norm; + return 0; +} + +static int s2250_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *query) +{ + switch (query->id) { + case V4L2_CID_BRIGHTNESS: + return v4l2_ctrl_query_fill(query, 0, 100, 1, 50); + case V4L2_CID_CONTRAST: + return v4l2_ctrl_query_fill(query, 0, 100, 1, 50); + case V4L2_CID_SATURATION: + return v4l2_ctrl_query_fill(query, 0, 100, 1, 50); + case V4L2_CID_HUE: + return v4l2_ctrl_query_fill(query, -50, 50, 1, 0); + default: + return -EINVAL; + } + return 0; +} + +static int s2250_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct s2250 *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int value1; + u16 oldvalue; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 100) + state->brightness = 100; + else if (ctrl->value < 0) + state->brightness = 0; + else + state->brightness = ctrl->value; + value1 = (state->brightness - 50) * 255 / 100; + read_reg_fp(client, VPX322_ADDR_BRIGHTNESS0, &oldvalue); + write_reg_fp(client, VPX322_ADDR_BRIGHTNESS0, + value1 | (oldvalue & ~0xff)); + read_reg_fp(client, VPX322_ADDR_BRIGHTNESS1, &oldvalue); + write_reg_fp(client, VPX322_ADDR_BRIGHTNESS1, + value1 | (oldvalue & ~0xff)); + write_reg_fp(client, 0x140, 0x60); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 100) + state->contrast = 100; + else if (ctrl->value < 0) + state->contrast = 0; + else + state->contrast = ctrl->value; + value1 = state->contrast * 0x40 / 100; + if (value1 > 0x3f) + value1 = 0x3f; /* max */ + read_reg_fp(client, VPX322_ADDR_CONTRAST0, &oldvalue); + write_reg_fp(client, VPX322_ADDR_CONTRAST0, + value1 | (oldvalue & ~0x3f)); + read_reg_fp(client, VPX322_ADDR_CONTRAST1, &oldvalue); + write_reg_fp(client, VPX322_ADDR_CONTRAST1, + value1 | (oldvalue & ~0x3f)); + write_reg_fp(client, 0x140, 0x60); + break; + case V4L2_CID_SATURATION: + if (ctrl->value > 100) + state->saturation = 100; + else if (ctrl->value < 0) + state->saturation = 0; + else + state->saturation = ctrl->value; + value1 = state->saturation * 4140 / 100; + if (value1 > 4094) + value1 = 4094; + write_reg_fp(client, VPX322_ADDR_SAT, value1); + break; + case V4L2_CID_HUE: + if (ctrl->value > 50) + state->hue = 50; + else if (ctrl->value < -50) + state->hue = -50; + else + state->hue = ctrl->value; + /* clamp the hue range */ + value1 = state->hue * 280 / 50; + write_reg_fp(client, VPX322_ADDR_HUE, value1); + break; + default: + return -EINVAL; + } + return 0; +} + +static int s2250_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct s2250 *state = to_state(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = state->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = state->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = state->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = state->hue; + break; + default: + return -EINVAL; + } + return 0; +} + +static int s2250_s_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt) +{ + struct s2250 *state = to_state(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (fmt->height < 640) { + write_reg_fp(client, 0x12b, state->reg12b_val | 0x400); + write_reg_fp(client, 0x140, 0x060); + } else { + write_reg_fp(client, 0x12b, state->reg12b_val & ~0x400); + write_reg_fp(client, 0x140, 0x060); + } + return 0; +} + +static int s2250_s_audio_routing(struct v4l2_subdev *sd, u32 input, u32 output, + u32 config) +{ + struct s2250 *state = to_state(sd); + + switch (input) { + case 0: + write_reg(state->audio, 0x08, 0x02); /* Line In */ + break; + case 1: + write_reg(state->audio, 0x08, 0x04); /* Mic */ + break; + case 2: + write_reg(state->audio, 0x08, 0x05); /* Mic Boost */ + break; + default: + return -EINVAL; + } + state->audio_input = input; + return 0; +} + + +static int s2250_log_status(struct v4l2_subdev *sd) +{ + struct s2250 *state = to_state(sd); + + v4l2_info(sd, "Standard: %s\n", state->std == V4L2_STD_NTSC ? "NTSC" : + state->std == V4L2_STD_PAL ? "PAL" : + state->std == V4L2_STD_SECAM ? "SECAM" : + "unknown"); + v4l2_info(sd, "Input: %s\n", state->input == 0 ? "Composite" : + state->input == 1 ? "S-video" : + "error"); + v4l2_info(sd, "Brightness: %d\n", state->brightness); + v4l2_info(sd, "Contrast: %d\n", state->contrast); + v4l2_info(sd, "Saturation: %d\n", state->saturation); + v4l2_info(sd, "Hue: %d\n", state->hue); return 0; + v4l2_info(sd, "Audio input: %s\n", state->audio_input == 0 ? "Line In" : + state->audio_input == 1 ? "Mic" : + state->audio_input == 2 ? "Mic Boost" : + "error"); + return 0; +} + +/* --------------------------------------------------------------------------*/ + +static const struct v4l2_subdev_core_ops s2250_core_ops = { + .log_status = s2250_log_status, + .g_ctrl = s2250_g_ctrl, + .s_ctrl = s2250_s_ctrl, + .queryctrl = s2250_queryctrl, + .s_std = s2250_s_std, +}; + +static const struct v4l2_subdev_audio_ops s2250_audio_ops = { + .s_routing = s2250_s_audio_routing, +}; + +static const struct v4l2_subdev_video_ops s2250_video_ops = { + .s_routing = s2250_s_video_routing, + .s_mbus_fmt = s2250_s_mbus_fmt, +}; + +static const struct v4l2_subdev_ops s2250_ops = { + .core = &s2250_core_ops, + .audio = &s2250_audio_ops, + .video = &s2250_video_ops, +}; + +/* --------------------------------------------------------------------------*/ + +static int s2250_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_client *audio; + struct i2c_adapter *adapter = client->adapter; + struct s2250 *state; + struct v4l2_subdev *sd; + u8 *data; + struct go7007 *go = i2c_get_adapdata(adapter); + struct go7007_usb *usb = go->hpi_context; + + audio = i2c_new_dummy(adapter, TLV320_ADDRESS >> 1); + if (audio == NULL) + return -ENOMEM; + + state = kmalloc(sizeof(struct s2250), GFP_KERNEL); + if (state == NULL) { + i2c_unregister_device(audio); + return -ENOMEM; + } + + sd = &state->sd; + v4l2_i2c_subdev_init(sd, client, &s2250_ops); + + v4l2_info(sd, "initializing %s at address 0x%x on %s\n", + "Sensoray 2250/2251", client->addr, client->adapter->name); + + state->std = V4L2_STD_NTSC; + state->brightness = 50; + state->contrast = 50; + state->saturation = 50; + state->hue = 0; + state->audio = audio; + + /* initialize the audio */ + if (write_regs(audio, aud_regs) < 0) { + printk(KERN_ERR + "s2250: error initializing audio\n"); + i2c_unregister_device(audio); + kfree(state); + return 0; + } + + if (write_regs(client, vid_regs) < 0) { + printk(KERN_ERR + "s2250: error initializing decoder\n"); + i2c_unregister_device(audio); + kfree(state); + return 0; + } + if (write_regs_fp(client, vid_regs_fp) < 0) { + printk(KERN_ERR + "s2250: error initializing decoder\n"); + i2c_unregister_device(audio); + kfree(state); + return 0; + } + /* set default channel */ + /* composite */ + write_reg_fp(client, 0x20, 0x020 | 1); + write_reg_fp(client, 0x21, 0x662); + write_reg_fp(client, 0x140, 0x060); + + /* set default audio input */ + state->audio_input = 0; + write_reg(client, 0x08, 0x02); /* Line In */ + + if (mutex_lock_interruptible(&usb->i2c_lock) == 0) { + data = kzalloc(16, GFP_KERNEL); + if (data != NULL) { + int rc; + rc = go7007_usb_vendor_request(go, 0x41, 0, 0, + data, 16, 1); + if (rc > 0) { + u8 mask; + data[0] = 0; + mask = 1<<5; + data[0] &= ~mask; + data[1] |= mask; + go7007_usb_vendor_request(go, 0x40, 0, + (data[1]<<8) + + data[1], + data, 16, 0); + } + kfree(data); + } + mutex_unlock(&usb->i2c_lock); + } + + v4l2_info(sd, "initialized successfully\n"); + return 0; +} + +static int s2250_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + + v4l2_device_unregister_subdev(sd); + kfree(to_state(sd)); + return 0; +} + +static const struct i2c_device_id s2250_id[] = { + { "s2250", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, s2250_id); + +static struct i2c_driver s2250_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "s2250", + }, + .probe = s2250_probe, + .remove = s2250_remove, + .id_table = s2250_id, +}; + +static __init int init_s2250(void) +{ + return i2c_add_driver(&s2250_driver); +} + +static __exit void exit_s2250(void) +{ + i2c_del_driver(&s2250_driver); +} + +module_init(init_s2250); +module_exit(exit_s2250); diff --git a/drivers/staging/media/go7007/s2250-loader.c b/drivers/staging/media/go7007/s2250-loader.c new file mode 100644 index 00000000..4e132519 --- /dev/null +++ b/drivers/staging/media/go7007/s2250-loader.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2008 Sensoray Company Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include + +#define S2250_LOADER_FIRMWARE "s2250_loader.fw" +#define S2250_FIRMWARE "s2250.fw" + +typedef struct device_extension_s { + struct kref kref; + int minor; + struct usb_device *usbdev; +} device_extension_t, *pdevice_extension_t; + +#define USB_s2250loader_MAJOR 240 +#define USB_s2250loader_MINOR_BASE 0 +#define MAX_DEVICES 256 + +static pdevice_extension_t s2250_dev_table[MAX_DEVICES]; +static DEFINE_MUTEX(s2250_dev_table_mutex); + +#define to_s2250loader_dev_common(d) container_of(d, device_extension_t, kref) +static void s2250loader_delete(struct kref *kref) +{ + pdevice_extension_t s = to_s2250loader_dev_common(kref); + s2250_dev_table[s->minor] = NULL; + kfree(s); +} + +static int s2250loader_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usbdev; + int minor, ret; + pdevice_extension_t s = NULL; + const struct firmware *fw; + + usbdev = usb_get_dev(interface_to_usbdev(interface)); + if (!usbdev) { + printk(KERN_ERR "Enter s2250loader_probe failed\n"); + return -1; + } + printk(KERN_INFO "Enter s2250loader_probe 2.6 kernel\n"); + printk(KERN_INFO "vendor id 0x%x, device id 0x%x devnum:%d\n", + usbdev->descriptor.idVendor, usbdev->descriptor.idProduct, + usbdev->devnum); + + if (usbdev->descriptor.bNumConfigurations != 1) { + printk(KERN_ERR "can't handle multiple config\n"); + return -1; + } + mutex_lock(&s2250_dev_table_mutex); + + for (minor = 0; minor < MAX_DEVICES; minor++) { + if (s2250_dev_table[minor] == NULL) + break; + } + + if (minor < 0 || minor >= MAX_DEVICES) { + printk(KERN_ERR "Invalid minor: %d\n", minor); + goto failed; + } + + /* Allocate dev data structure */ + s = kmalloc(sizeof(device_extension_t), GFP_KERNEL); + if (s == NULL) { + printk(KERN_ERR "Out of memory\n"); + goto failed; + } + s2250_dev_table[minor] = s; + + printk(KERN_INFO "s2250loader_probe: Device %d on Bus %d Minor %d\n", + usbdev->devnum, usbdev->bus->busnum, minor); + + memset(s, 0, sizeof(device_extension_t)); + s->usbdev = usbdev; + printk(KERN_INFO "loading 2250 loader\n"); + + kref_init(&(s->kref)); + + mutex_unlock(&s2250_dev_table_mutex); + + if (request_firmware(&fw, S2250_LOADER_FIRMWARE, &usbdev->dev)) { + printk(KERN_ERR + "s2250: unable to load firmware from file \"%s\"\n", + S2250_LOADER_FIRMWARE); + goto failed2; + } + ret = usb_cypress_load_firmware(usbdev, fw, CYPRESS_FX2); + release_firmware(fw); + if (0 != ret) { + printk(KERN_ERR "loader download failed\n"); + goto failed2; + } + + if (request_firmware(&fw, S2250_FIRMWARE, &usbdev->dev)) { + printk(KERN_ERR + "s2250: unable to load firmware from file \"%s\"\n", + S2250_FIRMWARE); + goto failed2; + } + ret = usb_cypress_load_firmware(usbdev, fw, CYPRESS_FX2); + release_firmware(fw); + if (0 != ret) { + printk(KERN_ERR "firmware_s2250 download failed\n"); + goto failed2; + } + + usb_set_intfdata(interface, s); + return 0; + +failed: + mutex_unlock(&s2250_dev_table_mutex); +failed2: + if (s) + kref_put(&(s->kref), s2250loader_delete); + + printk(KERN_ERR "probe failed\n"); + return -1; +} + +static void s2250loader_disconnect(struct usb_interface *interface) +{ + pdevice_extension_t s; + printk(KERN_INFO "s2250: disconnect\n"); + s = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + kref_put(&(s->kref), s2250loader_delete); +} + +static const struct usb_device_id s2250loader_ids[] = { + {USB_DEVICE(0x1943, 0xa250)}, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, s2250loader_ids); + +static struct usb_driver s2250loader_driver = { + .name = "s2250-loader", + .probe = s2250loader_probe, + .disconnect = s2250loader_disconnect, + .id_table = s2250loader_ids, +}; + +static int __init s2250loader_init(void) +{ + int r; + unsigned i = 0; + + for (i = 0; i < MAX_DEVICES; i++) + s2250_dev_table[i] = NULL; + + r = usb_register(&s2250loader_driver); + if (r) { + printk(KERN_ERR "usb_register failed. Error number %d\n", r); + return -1; + } + + printk(KERN_INFO "s2250loader_init: driver registered\n"); + return 0; +} +module_init(s2250loader_init); + +static void __exit s2250loader_cleanup(void) +{ + printk(KERN_INFO "s2250loader_cleanup\n"); + usb_deregister(&s2250loader_driver); +} +module_exit(s2250loader_cleanup); + +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("firmware loader for Sensoray 2250/2251"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/s2250-loader.h b/drivers/staging/media/go7007/s2250-loader.h new file mode 100644 index 00000000..b7c301af --- /dev/null +++ b/drivers/staging/media/go7007/s2250-loader.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#ifndef _S2250_LOADER_H_ +#define _S2250_LOADER_H_ + +extern int s2250loader_init(void); +extern void s2250loader_cleanup(void); + +#endif diff --git a/drivers/staging/media/go7007/saa7134-go7007.c b/drivers/staging/media/go7007/saa7134-go7007.c new file mode 100644 index 00000000..cf7c34a9 --- /dev/null +++ b/drivers/staging/media/go7007/saa7134-go7007.c @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "saa7134-reg.h" +#include "saa7134.h" +#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 saa7134_dev *dev; + u8 *top; + u8 *bottom; + dma_addr_t top_dma; + dma_addr_t bottom_dma; +}; + +static struct go7007_board_info board_voyager = { + .firmware = "go7007tv.bin", + .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", + }, + }, +}; +MODULE_FIRMWARE("go7007tv.bin"); + +/********************* 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; + u32 status; + 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); + + status = saa_readb(SAA7134_GPIO_GPSTATUS2); + /*printk(KERN_DEBUG "status is %s\n", status & 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); + status = saa_readb(SAA7134_GPIO_GPSTATUS2); + /*printk(KERN_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) { + printk(KERN_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 + printk(KERN_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) { + printk(KERN_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 + printk(KERN_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 (!go->streaming) + return; + if (0 != (status & 0x000f0000)) + printk(KERN_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), cpu_to_le32(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), cpu_to_le32(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 (!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 (!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); + saa_writeb(SAA7134_TS_DMA1, 0); + saa_writeb(SAA7134_TS_DMA2, 0); + + /* Enable video streaming mode */ + saa_writeb(SAA7134_GPIO_GPSTATUS2, GPIO_COMMAND_VIDEO); + + saa_writel(SAA7134_RS_BA1(5), cpu_to_le32(saa->top_dma)); + saa_writel(SAA7134_RS_BA2(5), cpu_to_le32(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 + printk(KERN_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) { + printk(KERN_ERR "saa7134-go7007: device is hung, " + "status reg = 0x%04x\n", status_reg); + return -1; + } + } + return 0; +} + +static int saa7134_go7007_send_command(struct go7007 *go, unsigned int cmd, + void *arg) +{ + struct saa7134_go7007 *saa = go->hpi_context; + struct saa7134_dev *dev = saa->dev; + + switch (cmd) { + case VIDIOC_S_STD: + { + v4l2_std_id *std = arg; + return saa7134_s_std_internal(dev, NULL, std); + } + case VIDIOC_G_STD: + { + v4l2_std_id *std = arg; + *std = dev->tvnorm->id; + return 0; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + if (V4L2_CTRL_ID2CLASS(ctrl->id) == V4L2_CTRL_CLASS_USER) + return saa7134_queryctrl(NULL, NULL, ctrl); + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + if (V4L2_CTRL_ID2CLASS(ctrl->id) == V4L2_CTRL_CLASS_USER) + return saa7134_g_ctrl_internal(dev, NULL, ctrl); + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + if (V4L2_CTRL_ID2CLASS(ctrl->id) == V4L2_CTRL_CLASS_USER) + return saa7134_s_ctrl_internal(dev, NULL, ctrl); + } + } + return -EINVAL; + +} + +static 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, + .send_command = saa7134_go7007_send_command, +}; + +/********************* Add/remove functions *********************/ + +static int saa7134_go7007_init(struct saa7134_dev *dev) +{ + struct go7007 *go; + struct saa7134_go7007 *saa; + + printk(KERN_DEBUG "saa7134-go7007: probing new SAA713X board\n"); + + saa = kzalloc(sizeof(struct saa7134_go7007), GFP_KERNEL); + if (saa == NULL) + return -ENOMEM; + + /* 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; + + go = go7007_alloc(&board_voyager, &dev->pci->dev); + if (go == NULL) + goto allocfail; + go->board_id = GO7007_BOARDID_PCI_VOYAGER; + strncpy(go->name, saa7134_boards[dev->board].name, sizeof(go->name)); + go->hpi_ops = &saa7134_go7007_hpi_ops; + go->hpi_context = saa; + saa->dev = dev; + + /* Boot the GO7007 */ + if (go7007_boot_encoder(go, go->board_info->flags & + GO7007_BOARD_USE_ONBOARD_I2C) < 0) + goto initfail; + + /* Do any final GO7007 initialization, then register the + * V4L2 and ALSA interfaces */ + if (go7007_register_encoder(go) < 0) + goto initfail; + dev->empress_dev = go->video_dev; + video_set_drvdata(dev->empress_dev, go); + + go->status = STATUS_ONLINE; + return 0; + +initfail: + go->status = STATUS_SHUTDOWN; + return 0; + +allocfail: + if (saa->top) + free_page((unsigned long)saa->top); + if (saa->bottom) + free_page((unsigned long)saa->bottom); + kfree(saa); + 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); + saa = go->hpi_context; + go->status = STATUS_SHUTDOWN; + free_page((unsigned long)saa->top); + free_page((unsigned long)saa->bottom); + kfree(saa); + go7007_remove(go); + 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_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/snd-go7007.c b/drivers/staging/media/go7007/snd-go7007.c new file mode 100644 index 00000000..5af29ff6 --- /dev/null +++ b/drivers/staging/media/go7007/snd-go7007.c @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "go7007-priv.h" + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +module_param_array(id, charp, NULL, 0444); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for the go7007 audio driver"); +MODULE_PARM_DESC(id, "ID string for the go7007 audio driver"); +MODULE_PARM_DESC(enable, "Enable for the go7007 audio driver"); + +struct go7007_snd { + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + spinlock_t lock; + int w_idx; + int hw_ptr; + int avail; + int capturing; +}; + +static struct snd_pcm_hardware go7007_snd_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_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 4096, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 32, +}; + +static void parse_audio_stream_data(struct go7007 *go, u8 *buf, int length) +{ + struct go7007_snd *gosnd = go->snd_context; + struct snd_pcm_runtime *runtime = gosnd->substream->runtime; + int frames = bytes_to_frames(runtime, length); + + spin_lock(&gosnd->lock); + gosnd->hw_ptr += frames; + if (gosnd->hw_ptr >= runtime->buffer_size) + gosnd->hw_ptr -= runtime->buffer_size; + gosnd->avail += frames; + spin_unlock(&gosnd->lock); + if (gosnd->w_idx + length > runtime->dma_bytes) { + int cpy = runtime->dma_bytes - gosnd->w_idx; + + memcpy(runtime->dma_area + gosnd->w_idx, buf, cpy); + length -= cpy; + buf += cpy; + gosnd->w_idx = 0; + } + memcpy(runtime->dma_area + gosnd->w_idx, buf, length); + gosnd->w_idx += length; + spin_lock(&gosnd->lock); + if (gosnd->avail < runtime->period_size) { + spin_unlock(&gosnd->lock); + return; + } + gosnd->avail -= runtime->period_size; + spin_unlock(&gosnd->lock); + if (gosnd->capturing) + snd_pcm_period_elapsed(gosnd->substream); +} + +static int go7007_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + unsigned int bytes; + + bytes = params_buffer_bytes(hw_params); + if (substream->runtime->dma_bytes > 0) + vfree(substream->runtime->dma_area); + substream->runtime->dma_bytes = 0; + substream->runtime->dma_area = vmalloc(bytes); + if (substream->runtime->dma_area == NULL) + return -ENOMEM; + substream->runtime->dma_bytes = bytes; + go->audio_deliver = parse_audio_stream_data; + return 0; +} + +static int go7007_snd_hw_free(struct snd_pcm_substream *substream) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + + go->audio_deliver = NULL; + if (substream->runtime->dma_bytes > 0) + vfree(substream->runtime->dma_area); + substream->runtime->dma_bytes = 0; + return 0; +} + +static int go7007_snd_capture_open(struct snd_pcm_substream *substream) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + struct go7007_snd *gosnd = go->snd_context; + unsigned long flags; + int r; + + spin_lock_irqsave(&gosnd->lock, flags); + if (gosnd->substream == NULL) { + gosnd->substream = substream; + substream->runtime->hw = go7007_snd_capture_hw; + r = 0; + } else + r = -EBUSY; + spin_unlock_irqrestore(&gosnd->lock, flags); + return r; +} + +static int go7007_snd_capture_close(struct snd_pcm_substream *substream) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + struct go7007_snd *gosnd = go->snd_context; + + gosnd->substream = NULL; + return 0; +} + +static int go7007_snd_pcm_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int go7007_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + struct go7007_snd *gosnd = go->snd_context; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* Just set a flag to indicate we should signal ALSA when + * sound comes in */ + gosnd->capturing = 1; + return 0; + case SNDRV_PCM_TRIGGER_STOP: + gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0; + gosnd->capturing = 0; + return 0; + default: + return -EINVAL; + } +} + +static snd_pcm_uframes_t go7007_snd_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct go7007 *go = snd_pcm_substream_chip(substream); + struct go7007_snd *gosnd = go->snd_context; + + return gosnd->hw_ptr; +} + +static struct page *go7007_snd_pcm_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + return vmalloc_to_page(substream->runtime->dma_area + offset); +} + +static struct snd_pcm_ops go7007_snd_capture_ops = { + .open = go7007_snd_capture_open, + .close = go7007_snd_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = go7007_snd_hw_params, + .hw_free = go7007_snd_hw_free, + .prepare = go7007_snd_pcm_prepare, + .trigger = go7007_snd_pcm_trigger, + .pointer = go7007_snd_pcm_pointer, + .page = go7007_snd_pcm_page, +}; + +static int go7007_snd_free(struct snd_device *device) +{ + struct go7007 *go = device->device_data; + + kfree(go->snd_context); + go->snd_context = NULL; + if (--go->ref_count == 0) + kfree(go); + return 0; +} + +static struct snd_device_ops go7007_snd_device_ops = { + .dev_free = go7007_snd_free, +}; + +int go7007_snd_init(struct go7007 *go) +{ + static int dev; + struct go7007_snd *gosnd; + int ret = 0; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + gosnd = kmalloc(sizeof(struct go7007_snd), GFP_KERNEL); + if (gosnd == NULL) + return -ENOMEM; + spin_lock_init(&gosnd->lock); + gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0; + gosnd->capturing = 0; + ret = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, + &gosnd->card); + if (ret < 0) { + kfree(gosnd); + return ret; + } + ret = snd_device_new(gosnd->card, SNDRV_DEV_LOWLEVEL, go, + &go7007_snd_device_ops); + if (ret < 0) { + kfree(gosnd); + return ret; + } + snd_card_set_dev(gosnd->card, go->dev); + ret = snd_pcm_new(gosnd->card, "go7007", 0, 0, 1, &gosnd->pcm); + if (ret < 0) { + snd_card_free(gosnd->card); + kfree(gosnd); + return ret; + } + strncpy(gosnd->card->driver, "go7007", sizeof(gosnd->card->driver)); + strncpy(gosnd->card->shortname, go->name, sizeof(gosnd->card->driver)); + strncpy(gosnd->card->longname, gosnd->card->shortname, + sizeof(gosnd->card->longname)); + + gosnd->pcm->private_data = go; + snd_pcm_set_ops(gosnd->pcm, SNDRV_PCM_STREAM_CAPTURE, + &go7007_snd_capture_ops); + + ret = snd_card_register(gosnd->card); + if (ret < 0) { + snd_card_free(gosnd->card); + kfree(gosnd); + return ret; + } + + gosnd->substream = NULL; + go->snd_context = gosnd; + ++dev; + ++go->ref_count; + + return 0; +} +EXPORT_SYMBOL(go7007_snd_init); + +int go7007_snd_remove(struct go7007 *go) +{ + struct go7007_snd *gosnd = go->snd_context; + + snd_card_disconnect(gosnd->card); + snd_card_free_when_closed(gosnd->card); + return 0; +} +EXPORT_SYMBOL(go7007_snd_remove); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-i2c.h b/drivers/staging/media/go7007/wis-i2c.h new file mode 100644 index 00000000..3c2b9be4 --- /dev/null +++ b/drivers/staging/media/go7007/wis-i2c.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +/* Temporary I2C IDs -- these need to be replaced with real registered IDs */ +#define I2C_DRIVERID_WIS_SAA7115 0xf0f0 +#define I2C_DRIVERID_WIS_UDA1342 0xf0f1 +#define I2C_DRIVERID_WIS_SONY_TUNER 0xf0f2 +#define I2C_DRIVERID_WIS_TW9903 0xf0f3 +#define I2C_DRIVERID_WIS_SAA7113 0xf0f4 +#define I2C_DRIVERID_WIS_OV7640 0xf0f5 +#define I2C_DRIVERID_WIS_TW2804 0xf0f6 +#define I2C_DRIVERID_S2250 0xf0f7 + +/* Flag to indicate that the client needs to be accessed with SCCB semantics */ +/* We re-use the I2C_M_TEN value so the flag passes through the masks in the + * core I2C code. Major kludge, but the I2C layer ain't exactly flexible. */ +#define I2C_CLIENT_SCCB 0x10 + +/* Definitions for new video decoder commands */ + +struct video_decoder_resolution { + unsigned int width; + unsigned int height; +}; + +#define DECODER_SET_RESOLUTION _IOW('d', 200, struct video_decoder_resolution) +#define DECODER_SET_CHANNEL _IOW('d', 201, int) + +/* Sony tuner types */ + +#define TUNER_SONY_BTF_PG472Z 200 +#define TUNER_SONY_BTF_PK467Z 201 +#define TUNER_SONY_BTF_PB463Z 202 diff --git a/drivers/staging/media/go7007/wis-ov7640.c b/drivers/staging/media/go7007/wis-ov7640.c new file mode 100644 index 00000000..6bc9470f --- /dev/null +++ b/drivers/staging/media/go7007/wis-ov7640.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include "wis-i2c.h" + +struct wis_ov7640 { + int brightness; + int contrast; + int saturation; + int hue; +}; + +static u8 initial_registers[] = +{ + 0x12, 0x80, + 0x12, 0x54, + 0x14, 0x24, + 0x15, 0x01, + 0x28, 0x20, + 0x75, 0x82, + 0xFF, 0xFF, /* Terminator (reg 0xFF is unused) */ +}; + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0xFF; i += 2) + if (i2c_smbus_write_byte_data(client, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_ov7640_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + client->flags = I2C_CLIENT_SCCB; + + printk(KERN_DEBUG + "wis-ov7640: initializing OV7640 at address %d on %s\n", + client->addr, adapter->name); + + if (write_regs(client, initial_registers) < 0) { + printk(KERN_ERR "wis-ov7640: error initializing OV7640\n"); + return -ENODEV; + } + + return 0; +} + +static int wis_ov7640_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id wis_ov7640_id[] = { + { "wis_ov7640", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_ov7640_id); + +static struct i2c_driver wis_ov7640_driver = { + .driver = { + .name = "WIS OV7640 I2C driver", + }, + .probe = wis_ov7640_probe, + .remove = wis_ov7640_remove, + .id_table = wis_ov7640_id, +}; + +static int __init wis_ov7640_init(void) +{ + return i2c_add_driver(&wis_ov7640_driver); +} + +static void __exit wis_ov7640_cleanup(void) +{ + i2c_del_driver(&wis_ov7640_driver); +} + +module_init(wis_ov7640_init); +module_exit(wis_ov7640_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-saa7113.c b/drivers/staging/media/go7007/wis-saa7113.c new file mode 100644 index 00000000..05e0e108 --- /dev/null +++ b/drivers/staging/media/go7007/wis-saa7113.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "wis-i2c.h" + +struct wis_saa7113 { + int norm; + int brightness; + int contrast; + int saturation; + int hue; +}; + +static u8 initial_registers[] = +{ + 0x01, 0x08, + 0x02, 0xc0, + 0x03, 0x33, + 0x04, 0x00, + 0x05, 0x00, + 0x06, 0xe9, + 0x07, 0x0d, + 0x08, 0xd8, + 0x09, 0x40, + 0x0a, 0x80, + 0x0b, 0x47, + 0x0c, 0x40, + 0x0d, 0x00, + 0x0e, 0x01, + 0x0f, 0x2a, + 0x10, 0x40, + 0x11, 0x0c, + 0x12, 0xfe, + 0x13, 0x00, + 0x14, 0x00, + 0x15, 0x04, + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0x00, + 0x19, 0x00, + 0x1a, 0x00, + 0x1b, 0x00, + 0x1c, 0x00, + 0x1d, 0x00, + 0x1e, 0x00, + 0x1f, 0xc8, + 0x40, 0x00, + 0x41, 0xff, + 0x42, 0xff, + 0x43, 0xff, + 0x44, 0xff, + 0x45, 0xff, + 0x46, 0xff, + 0x47, 0xff, + 0x48, 0xff, + 0x49, 0xff, + 0x4a, 0xff, + 0x4b, 0xff, + 0x4c, 0xff, + 0x4d, 0xff, + 0x4e, 0xff, + 0x4f, 0xff, + 0x50, 0xff, + 0x51, 0xff, + 0x52, 0xff, + 0x53, 0xff, + 0x54, 0xff, + 0x55, 0xff, + 0x56, 0xff, + 0x57, 0xff, + 0x58, 0x00, + 0x59, 0x54, + 0x5a, 0x07, + 0x5b, 0x83, + 0x5c, 0x00, + 0x5d, 0x00, + 0x5e, 0x00, + 0x5f, 0x00, + 0x60, 0x00, + 0x61, 0x00, + 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */ +}; + +static int write_reg(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0x00; i += 2) + if (i2c_smbus_write_byte_data(client, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_saa7113_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct wis_saa7113 *dec = i2c_get_clientdata(client); + + switch (cmd) { + case VIDIOC_S_INPUT: + { + int *input = arg; + + i2c_smbus_write_byte_data(client, 0x02, 0xC0 | *input); + i2c_smbus_write_byte_data(client, 0x09, + *input < 6 ? 0x40 : 0x80); + break; + } + case VIDIOC_S_STD: + { + v4l2_std_id *input = arg; + dec->norm = *input; + if (dec->norm & V4L2_STD_NTSC) { + write_reg(client, 0x0e, 0x01); + write_reg(client, 0x10, 0x40); + } else if (dec->norm & V4L2_STD_PAL) { + write_reg(client, 0x0e, 0x01); + write_reg(client, 0x10, 0x48); + } else if (dec->norm * V4L2_STD_SECAM) { + write_reg(client, 0x0e, 0x50); + write_reg(client, 0x10, 0x48); + } + break; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Brightness", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_CONTRAST: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Contrast", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 71; + ctrl->flags = 0; + break; + case V4L2_CID_SATURATION: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Saturation", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 64; + ctrl->flags = 0; + break; + case V4L2_CID_HUE: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Hue", sizeof(ctrl->name)); + ctrl->minimum = -128; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 0; + ctrl->flags = 0; + break; + } + break; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 255) + dec->brightness = 255; + else if (ctrl->value < 0) + dec->brightness = 0; + else + dec->brightness = ctrl->value; + write_reg(client, 0x0a, dec->brightness); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 127) + dec->contrast = 127; + else if (ctrl->value < 0) + dec->contrast = 0; + else + dec->contrast = ctrl->value; + write_reg(client, 0x0b, dec->contrast); + break; + case V4L2_CID_SATURATION: + if (ctrl->value > 127) + dec->saturation = 127; + else if (ctrl->value < 0) + dec->saturation = 0; + else + dec->saturation = ctrl->value; + write_reg(client, 0x0c, dec->saturation); + break; + case V4L2_CID_HUE: + if (ctrl->value > 127) + dec->hue = 127; + else if (ctrl->value < -128) + dec->hue = -128; + else + dec->hue = ctrl->value; + write_reg(client, 0x0d, dec->hue); + break; + } + break; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = dec->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = dec->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = dec->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = dec->hue; + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_saa7113_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_saa7113 *dec; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + dec = kmalloc(sizeof(struct wis_saa7113), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + + dec->norm = V4L2_STD_NTSC; + dec->brightness = 128; + dec->contrast = 71; + dec->saturation = 64; + dec->hue = 0; + i2c_set_clientdata(client, dec); + + printk(KERN_DEBUG + "wis-saa7113: initializing SAA7113 at address %d on %s\n", + client->addr, adapter->name); + + if (write_regs(client, initial_registers) < 0) { + printk(KERN_ERR + "wis-saa7113: error initializing SAA7113\n"); + kfree(dec); + return -ENODEV; + } + + return 0; +} + +static int wis_saa7113_remove(struct i2c_client *client) +{ + struct wis_saa7113 *dec = i2c_get_clientdata(client); + + kfree(dec); + return 0; +} + +static const struct i2c_device_id wis_saa7113_id[] = { + { "wis_saa7113", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_saa7113_id); + +static struct i2c_driver wis_saa7113_driver = { + .driver = { + .name = "WIS SAA7113 I2C driver", + }, + .probe = wis_saa7113_probe, + .remove = wis_saa7113_remove, + .command = wis_saa7113_command, + .id_table = wis_saa7113_id, +}; + +static int __init wis_saa7113_init(void) +{ + return i2c_add_driver(&wis_saa7113_driver); +} + +static void __exit wis_saa7113_cleanup(void) +{ + i2c_del_driver(&wis_saa7113_driver); +} + +module_init(wis_saa7113_init); +module_exit(wis_saa7113_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-saa7115.c b/drivers/staging/media/go7007/wis-saa7115.c new file mode 100644 index 00000000..46cff59e --- /dev/null +++ b/drivers/staging/media/go7007/wis-saa7115.c @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "wis-i2c.h" + +struct wis_saa7115 { + int norm; + int brightness; + int contrast; + int saturation; + int hue; +}; + +static u8 initial_registers[] = +{ + 0x01, 0x08, + 0x02, 0xc0, + 0x03, 0x20, + 0x04, 0x80, + 0x05, 0x80, + 0x06, 0xeb, + 0x07, 0xe0, + 0x08, 0xf0, /* always toggle FID */ + 0x09, 0x40, + 0x0a, 0x80, + 0x0b, 0x40, + 0x0c, 0x40, + 0x0d, 0x00, + 0x0e, 0x03, + 0x0f, 0x2a, + 0x10, 0x0e, + 0x11, 0x00, + 0x12, 0x8d, + 0x13, 0x00, + 0x14, 0x00, + 0x15, 0x11, + 0x16, 0x01, + 0x17, 0xda, + 0x18, 0x40, + 0x19, 0x80, + 0x1a, 0x00, + 0x1b, 0x42, + 0x1c, 0xa9, + 0x30, 0x66, + 0x31, 0x90, + 0x32, 0x01, + 0x34, 0x00, + 0x35, 0x00, + 0x36, 0x20, + 0x38, 0x03, + 0x39, 0x20, + 0x3a, 0x88, + 0x40, 0x00, + 0x41, 0xff, + 0x42, 0xff, + 0x43, 0xff, + 0x44, 0xff, + 0x45, 0xff, + 0x46, 0xff, + 0x47, 0xff, + 0x48, 0xff, + 0x49, 0xff, + 0x4a, 0xff, + 0x4b, 0xff, + 0x4c, 0xff, + 0x4d, 0xff, + 0x4e, 0xff, + 0x4f, 0xff, + 0x50, 0xff, + 0x51, 0xff, + 0x52, 0xff, + 0x53, 0xff, + 0x54, 0xf4 /*0xff*/, + 0x55, 0xff, + 0x56, 0xff, + 0x57, 0xff, + 0x58, 0x40, + 0x59, 0x47, + 0x5a, 0x06 /*0x03*/, + 0x5b, 0x83, + 0x5d, 0x06, + 0x5e, 0x00, + 0x80, 0x30, /* window defined scaler operation, task A and B enabled */ + 0x81, 0x03, /* use scaler datapath generated V */ + 0x83, 0x00, + 0x84, 0x00, + 0x85, 0x00, + 0x86, 0x45, + 0x87, 0x31, + 0x88, 0xc0, + 0x90, 0x02, /* task A process top field */ + 0x91, 0x08, + 0x92, 0x09, + 0x93, 0x80, + 0x94, 0x06, + 0x95, 0x00, + 0x96, 0xc0, + 0x97, 0x02, + 0x98, 0x12, + 0x99, 0x00, + 0x9a, 0xf2, + 0x9b, 0x00, + 0x9c, 0xd0, + 0x9d, 0x02, + 0x9e, 0xf2, + 0x9f, 0x00, + 0xa0, 0x01, + 0xa1, 0x01, + 0xa2, 0x01, + 0xa4, 0x80, + 0xa5, 0x40, + 0xa6, 0x40, + 0xa8, 0x00, + 0xa9, 0x04, + 0xaa, 0x00, + 0xac, 0x00, + 0xad, 0x02, + 0xae, 0x00, + 0xb0, 0x00, + 0xb1, 0x04, + 0xb2, 0x00, + 0xb3, 0x04, + 0xb4, 0x00, + 0xb8, 0x00, + 0xbc, 0x00, + 0xc0, 0x03, /* task B process bottom field */ + 0xc1, 0x08, + 0xc2, 0x09, + 0xc3, 0x80, + 0xc4, 0x06, + 0xc5, 0x00, + 0xc6, 0xc0, + 0xc7, 0x02, + 0xc8, 0x12, + 0xc9, 0x00, + 0xca, 0xf2, + 0xcb, 0x00, + 0xcc, 0xd0, + 0xcd, 0x02, + 0xce, 0xf2, + 0xcf, 0x00, + 0xd0, 0x01, + 0xd1, 0x01, + 0xd2, 0x01, + 0xd4, 0x80, + 0xd5, 0x40, + 0xd6, 0x40, + 0xd8, 0x00, + 0xd9, 0x04, + 0xda, 0x00, + 0xdc, 0x00, + 0xdd, 0x02, + 0xde, 0x00, + 0xe0, 0x00, + 0xe1, 0x04, + 0xe2, 0x00, + 0xe3, 0x04, + 0xe4, 0x00, + 0xe8, 0x00, + 0x88, 0xf0, /* End of original static list */ + 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */ +}; + +static int write_reg(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0x00; i += 2) + if (i2c_smbus_write_byte_data(client, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_saa7115_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct wis_saa7115 *dec = i2c_get_clientdata(client); + + switch (cmd) { + case VIDIOC_S_INPUT: + { + int *input = arg; + + i2c_smbus_write_byte_data(client, 0x02, 0xC0 | *input); + i2c_smbus_write_byte_data(client, 0x09, + *input < 6 ? 0x40 : 0xC0); + break; + } + case DECODER_SET_RESOLUTION: + { + struct video_decoder_resolution *res = arg; + /* Course-grained scaler */ + int h_integer_scaler = res->width < 704 ? 704 / res->width : 1; + /* Fine-grained scaler to take care of remainder */ + int h_scaling_increment = (704 / h_integer_scaler) * + 1024 / res->width; + /* Fine-grained scaler only */ + int v_scaling_increment = (dec->norm & V4L2_STD_NTSC ? + 240 : 288) * 1024 / res->height; + u8 regs[] = { + 0x88, 0xc0, + 0x9c, res->width & 0xff, + 0x9d, res->width >> 8, + 0x9e, res->height & 0xff, + 0x9f, res->height >> 8, + 0xa0, h_integer_scaler, + 0xa1, 1, + 0xa2, 1, + 0xa8, h_scaling_increment & 0xff, + 0xa9, h_scaling_increment >> 8, + 0xac, (h_scaling_increment / 2) & 0xff, + 0xad, (h_scaling_increment / 2) >> 8, + 0xb0, v_scaling_increment & 0xff, + 0xb1, v_scaling_increment >> 8, + 0xb2, v_scaling_increment & 0xff, + 0xb3, v_scaling_increment >> 8, + 0xcc, res->width & 0xff, + 0xcd, res->width >> 8, + 0xce, res->height & 0xff, + 0xcf, res->height >> 8, + 0xd0, h_integer_scaler, + 0xd1, 1, + 0xd2, 1, + 0xd8, h_scaling_increment & 0xff, + 0xd9, h_scaling_increment >> 8, + 0xdc, (h_scaling_increment / 2) & 0xff, + 0xdd, (h_scaling_increment / 2) >> 8, + 0xe0, v_scaling_increment & 0xff, + 0xe1, v_scaling_increment >> 8, + 0xe2, v_scaling_increment & 0xff, + 0xe3, v_scaling_increment >> 8, + 0x88, 0xf0, + 0, 0, + }; + write_regs(client, regs); + break; + } + case VIDIOC_S_STD: + { + v4l2_std_id *input = arg; + u8 regs[] = { + 0x88, 0xc0, + 0x98, *input & V4L2_STD_NTSC ? 0x12 : 0x16, + 0x9a, *input & V4L2_STD_NTSC ? 0xf2 : 0x20, + 0x9b, *input & V4L2_STD_NTSC ? 0x00 : 0x01, + 0xc8, *input & V4L2_STD_NTSC ? 0x12 : 0x16, + 0xca, *input & V4L2_STD_NTSC ? 0xf2 : 0x20, + 0xcb, *input & V4L2_STD_NTSC ? 0x00 : 0x01, + 0x88, 0xf0, + 0x30, *input & V4L2_STD_NTSC ? 0x66 : 0x00, + 0x31, *input & V4L2_STD_NTSC ? 0x90 : 0xe0, + 0, 0, + }; + write_regs(client, regs); + dec->norm = *input; + break; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Brightness", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_CONTRAST: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Contrast", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 64; + ctrl->flags = 0; + break; + case V4L2_CID_SATURATION: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Saturation", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 64; + ctrl->flags = 0; + break; + case V4L2_CID_HUE: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Hue", sizeof(ctrl->name)); + ctrl->minimum = -128; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 0; + ctrl->flags = 0; + break; + } + break; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 255) + dec->brightness = 255; + else if (ctrl->value < 0) + dec->brightness = 0; + else + dec->brightness = ctrl->value; + write_reg(client, 0x0a, dec->brightness); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 127) + dec->contrast = 127; + else if (ctrl->value < 0) + dec->contrast = 0; + else + dec->contrast = ctrl->value; + write_reg(client, 0x0b, dec->contrast); + break; + case V4L2_CID_SATURATION: + if (ctrl->value > 127) + dec->saturation = 127; + else if (ctrl->value < 0) + dec->saturation = 0; + else + dec->saturation = ctrl->value; + write_reg(client, 0x0c, dec->saturation); + break; + case V4L2_CID_HUE: + if (ctrl->value > 127) + dec->hue = 127; + else if (ctrl->value < -128) + dec->hue = -128; + else + dec->hue = ctrl->value; + write_reg(client, 0x0d, dec->hue); + break; + } + break; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = dec->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = dec->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = dec->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = dec->hue; + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_saa7115_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_saa7115 *dec; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + dec = kmalloc(sizeof(struct wis_saa7115), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + + dec->norm = V4L2_STD_NTSC; + dec->brightness = 128; + dec->contrast = 64; + dec->saturation = 64; + dec->hue = 0; + i2c_set_clientdata(client, dec); + + printk(KERN_DEBUG + "wis-saa7115: initializing SAA7115 at address %d on %s\n", + client->addr, adapter->name); + + if (write_regs(client, initial_registers) < 0) { + printk(KERN_ERR + "wis-saa7115: error initializing SAA7115\n"); + kfree(dec); + return -ENODEV; + } + + return 0; +} + +static int wis_saa7115_remove(struct i2c_client *client) +{ + struct wis_saa7115 *dec = i2c_get_clientdata(client); + + kfree(dec); + return 0; +} + +static const struct i2c_device_id wis_saa7115_id[] = { + { "wis_saa7115", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_saa7115_id); + +static struct i2c_driver wis_saa7115_driver = { + .driver = { + .name = "WIS SAA7115 I2C driver", + }, + .probe = wis_saa7115_probe, + .remove = wis_saa7115_remove, + .command = wis_saa7115_command, + .id_table = wis_saa7115_id, +}; + +static int __init wis_saa7115_init(void) +{ + return i2c_add_driver(&wis_saa7115_driver); +} + +static void __exit wis_saa7115_cleanup(void) +{ + i2c_del_driver(&wis_saa7115_driver); +} + +module_init(wis_saa7115_init); +module_exit(wis_saa7115_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-sony-tuner.c b/drivers/staging/media/go7007/wis-sony-tuner.c new file mode 100644 index 00000000..8f1b7d4f --- /dev/null +++ b/drivers/staging/media/go7007/wis-sony-tuner.c @@ -0,0 +1,720 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wis-i2c.h" + +/* #define MPX_DEBUG */ + +/* AS(IF/MPX) pin: LOW HIGH/OPEN + * IF/MPX address: 0x42/0x40 0x43/0x44 + */ +#define IF_I2C_ADDR 0x43 +#define MPX_I2C_ADDR 0x44 + +static v4l2_std_id force_band; +static char force_band_str[] = "-"; +module_param_string(force_band, force_band_str, sizeof(force_band_str), 0644); +static int force_mpx_mode = -1; +module_param(force_mpx_mode, int, 0644); + +/* Store tuner info in the same format as tuner.c, so maybe we can put the + * Sony tuner support in there. */ +struct sony_tunertype { + char *name; + unsigned char Vendor; /* unused here */ + unsigned char Type; /* unused here */ + + unsigned short thresh1; /* band switch VHF_LO <=> VHF_HI */ + unsigned short thresh2; /* band switch VHF_HI <=> UHF */ + unsigned char VHF_L; + unsigned char VHF_H; + unsigned char UHF; + unsigned char config; + unsigned short IFPCoff; +}; + +/* This array is indexed by (tuner_type - 200) */ +static struct sony_tunertype sony_tuners[] = { + { "Sony PAL+SECAM (BTF-PG472Z)", 0, 0, + 16*144.25, 16*427.25, 0x01, 0x02, 0x04, 0xc6, 623}, + { "Sony NTSC_JP (BTF-PK467Z)", 0, 0, + 16*220.25, 16*467.25, 0x01, 0x02, 0x04, 0xc6, 940}, + { "Sony NTSC (BTF-PB463Z)", 0, 0, + 16*130.25, 16*364.25, 0x01, 0x02, 0x04, 0xc6, 732}, +}; + +struct wis_sony_tuner { + int type; + v4l2_std_id std; + unsigned int freq; + int mpxmode; + u32 audmode; +}; + +/* Basically the same as default_set_tv_freq() in tuner.c */ +static int set_freq(struct i2c_client *client, int freq) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + char *band_name; + int n; + int band_select; + struct sony_tunertype *tun; + u8 buffer[4]; + + tun = &sony_tuners[t->type - 200]; + if (freq < tun->thresh1) { + band_name = "VHF_L"; + band_select = tun->VHF_L; + } else if (freq < tun->thresh2) { + band_name = "VHF_H"; + band_select = tun->VHF_H; + } else { + band_name = "UHF"; + band_select = tun->UHF; + } + printk(KERN_DEBUG "wis-sony-tuner: tuning to frequency %d.%04d (%s)\n", + freq / 16, (freq % 16) * 625, band_name); + n = freq + tun->IFPCoff; + + buffer[0] = n >> 8; + buffer[1] = n & 0xff; + buffer[2] = tun->config; + buffer[3] = band_select; + i2c_master_send(client, buffer, 4); + + return 0; +} + +static int mpx_write(struct i2c_client *client, int dev, int addr, int val) +{ + u8 buffer[5]; + struct i2c_msg msg; + + buffer[0] = dev; + buffer[1] = addr >> 8; + buffer[2] = addr & 0xff; + buffer[3] = val >> 8; + buffer[4] = val & 0xff; + msg.addr = MPX_I2C_ADDR; + msg.flags = 0; + msg.len = 5; + msg.buf = buffer; + i2c_transfer(client->adapter, &msg, 1); + return 0; +} + +/* + * MPX register values for the BTF-PG472Z: + * + * FM_ NICAM_ SCART_ + * MODUS SOURCE ACB PRESCAL PRESCAL PRESCAL SYSTEM VOLUME + * 10/0030 12/0008 12/0013 12/000E 12/0010 12/0000 10/0020 12/0000 + * --------------------------------------------------------------- + * Auto 1003 0020 0100 2603 5000 XXXX 0001 7500 + * + * B/G + * Mono 1003 0020 0100 2603 5000 XXXX 0003 7500 + * A2 1003 0020 0100 2601 5000 XXXX 0003 7500 + * NICAM 1003 0120 0100 2603 5000 XXXX 0008 7500 + * + * I + * Mono 1003 0020 0100 2603 7900 XXXX 000A 7500 + * NICAM 1003 0120 0100 2603 7900 XXXX 000A 7500 + * + * D/K + * Mono 1003 0020 0100 2603 5000 XXXX 0004 7500 + * A2-1 1003 0020 0100 2601 5000 XXXX 0004 7500 + * A2-2 1003 0020 0100 2601 5000 XXXX 0005 7500 + * A2-3 1003 0020 0100 2601 5000 XXXX 0007 7500 + * NICAM 1003 0120 0100 2603 5000 XXXX 000B 7500 + * + * L/L' + * Mono 0003 0200 0100 7C03 5000 2200 0009 7500 + * NICAM 0003 0120 0100 7C03 5000 XXXX 0009 7500 + * + * M + * Mono 1003 0200 0100 2B03 5000 2B00 0002 7500 + * + * For Asia, replace the 0x26XX in FM_PRESCALE with 0x14XX. + * + * Bilingual selection in A2/NICAM: + * + * High byte of SOURCE Left chan Right chan + * 0x01 MAIN SUB + * 0x03 MAIN MAIN + * 0x04 SUB SUB + * + * Force mono in NICAM by setting the high byte of SOURCE to 0x02 (L/L') or + * 0x00 (all other bands). Force mono in A2 with FMONO_A2: + * + * FMONO_A2 + * 10/0022 + * -------- + * Forced mono ON 07F0 + * Forced mono OFF 0190 + */ + +static struct { + enum { AUD_MONO, AUD_A2, AUD_NICAM, AUD_NICAM_L } audio_mode; + u16 modus; + u16 source; + u16 acb; + u16 fm_prescale; + u16 nicam_prescale; + u16 scart_prescale; + u16 system; + u16 volume; +} mpx_audio_modes[] = { + /* Auto */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0001, 0x7500 }, + /* B/G Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0003, 0x7500 }, + /* B/G A2 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0003, 0x7500 }, + /* B/G NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0008, 0x7500 }, + /* I Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x7900, 0x0000, 0x000A, 0x7500 }, + /* I NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, + 0x7900, 0x0000, 0x000A, 0x7500 }, + /* D/K Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, + 0x5000, 0x0000, 0x0004, 0x7500 }, + /* D/K A2-1 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0004, 0x7500 }, + /* D/K A2-2 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0005, 0x7500 }, + /* D/K A2-3 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, + 0x5000, 0x0000, 0x0007, 0x7500 }, + /* D/K NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, + 0x5000, 0x0000, 0x000B, 0x7500 }, + /* L/L' Mono */ { AUD_MONO, 0x0003, 0x0200, 0x0100, 0x7C03, + 0x5000, 0x2200, 0x0009, 0x7500 }, + /* L/L' NICAM */{ AUD_NICAM_L, 0x0003, 0x0120, 0x0100, 0x7C03, + 0x5000, 0x0000, 0x0009, 0x7500 }, +}; + +#define MPX_NUM_MODES ARRAY_SIZE(mpx_audio_modes) + +static int mpx_setup(struct i2c_client *client) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + u16 source = 0; + u8 buffer[3]; + struct i2c_msg msg; + + /* reset MPX */ + buffer[0] = 0x00; + buffer[1] = 0x80; + buffer[2] = 0x00; + msg.addr = MPX_I2C_ADDR; + msg.flags = 0; + msg.len = 3; + msg.buf = buffer; + i2c_transfer(client->adapter, &msg, 1); + buffer[1] = 0x00; + i2c_transfer(client->adapter, &msg, 1); + + if (mpx_audio_modes[t->mpxmode].audio_mode != AUD_MONO) { + switch (t->audmode) { + case V4L2_TUNER_MODE_MONO: + switch (mpx_audio_modes[t->mpxmode].audio_mode) { + case AUD_A2: + source = mpx_audio_modes[t->mpxmode].source; + break; + case AUD_NICAM: + source = 0x0000; + break; + case AUD_NICAM_L: + source = 0x0200; + break; + default: + break; + } + break; + case V4L2_TUNER_MODE_STEREO: + source = mpx_audio_modes[t->mpxmode].source; + break; + case V4L2_TUNER_MODE_LANG1: + source = 0x0300; + break; + case V4L2_TUNER_MODE_LANG2: + source = 0x0400; + break; + } + source |= mpx_audio_modes[t->mpxmode].source & 0x00ff; + } else + source = mpx_audio_modes[t->mpxmode].source; + + mpx_write(client, 0x10, 0x0030, mpx_audio_modes[t->mpxmode].modus); + mpx_write(client, 0x12, 0x0008, source); + mpx_write(client, 0x12, 0x0013, mpx_audio_modes[t->mpxmode].acb); + mpx_write(client, 0x12, 0x000e, + mpx_audio_modes[t->mpxmode].fm_prescale); + mpx_write(client, 0x12, 0x0010, + mpx_audio_modes[t->mpxmode].nicam_prescale); + mpx_write(client, 0x12, 0x000d, + mpx_audio_modes[t->mpxmode].scart_prescale); + mpx_write(client, 0x10, 0x0020, mpx_audio_modes[t->mpxmode].system); + mpx_write(client, 0x12, 0x0000, mpx_audio_modes[t->mpxmode].volume); + if (mpx_audio_modes[t->mpxmode].audio_mode == AUD_A2) + mpx_write(client, 0x10, 0x0022, + t->audmode == V4L2_TUNER_MODE_MONO ? 0x07f0 : 0x0190); + +#ifdef MPX_DEBUG + { + u8 buf1[3], buf2[2]; + struct i2c_msg msgs[2]; + + printk(KERN_DEBUG "wis-sony-tuner: MPX registers: %04x %04x " + "%04x %04x %04x %04x %04x %04x\n", + mpx_audio_modes[t->mpxmode].modus, + source, + mpx_audio_modes[t->mpxmode].acb, + mpx_audio_modes[t->mpxmode].fm_prescale, + mpx_audio_modes[t->mpxmode].nicam_prescale, + mpx_audio_modes[t->mpxmode].scart_prescale, + mpx_audio_modes[t->mpxmode].system, + mpx_audio_modes[t->mpxmode].volume); + buf1[0] = 0x11; + buf1[1] = 0x00; + buf1[2] = 0x7e; + msgs[0].addr = MPX_I2C_ADDR; + msgs[0].flags = 0; + msgs[0].len = 3; + msgs[0].buf = buf1; + msgs[1].addr = MPX_I2C_ADDR; + msgs[1].flags = I2C_M_RD; + msgs[1].len = 2; + msgs[1].buf = buf2; + i2c_transfer(client->adapter, msgs, 2); + printk(KERN_DEBUG "wis-sony-tuner: MPX system: %02x%02x\n", + buf2[0], buf2[1]); + buf1[0] = 0x11; + buf1[1] = 0x02; + buf1[2] = 0x00; + i2c_transfer(client->adapter, msgs, 2); + printk(KERN_DEBUG "wis-sony-tuner: MPX status: %02x%02x\n", + buf2[0], buf2[1]); + } +#endif + return 0; +} + +/* + * IF configuration values for the BTF-PG472Z: + * + * B/G: 0x94 0x70 0x49 + * I: 0x14 0x70 0x4a + * D/K: 0x14 0x70 0x4b + * L: 0x04 0x70 0x4b + * L': 0x44 0x70 0x53 + * M: 0x50 0x30 0x4c + */ + +static int set_if(struct i2c_client *client) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + u8 buffer[4]; + struct i2c_msg msg; + int default_mpx_mode = 0; + + /* configure IF */ + buffer[0] = 0; + if (t->std & V4L2_STD_PAL_BG) { + buffer[1] = 0x94; + buffer[2] = 0x70; + buffer[3] = 0x49; + default_mpx_mode = 1; + } else if (t->std & V4L2_STD_PAL_I) { + buffer[1] = 0x14; + buffer[2] = 0x70; + buffer[3] = 0x4a; + default_mpx_mode = 4; + } else if (t->std & V4L2_STD_PAL_DK) { + buffer[1] = 0x14; + buffer[2] = 0x70; + buffer[3] = 0x4b; + default_mpx_mode = 6; + } else if (t->std & V4L2_STD_SECAM_L) { + buffer[1] = 0x04; + buffer[2] = 0x70; + buffer[3] = 0x4b; + default_mpx_mode = 11; + } + msg.addr = IF_I2C_ADDR; + msg.flags = 0; + msg.len = 4; + msg.buf = buffer; + i2c_transfer(client->adapter, &msg, 1); + + /* Select MPX mode if not forced by the user */ + if (force_mpx_mode >= 0 && force_mpx_mode < MPX_NUM_MODES) + t->mpxmode = force_mpx_mode; + else + t->mpxmode = default_mpx_mode; + printk(KERN_DEBUG "wis-sony-tuner: setting MPX to mode %d\n", + t->mpxmode); + mpx_setup(client); + + return 0; +} + +static int tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + + switch (cmd) { +#if 0 +#ifdef TUNER_SET_TYPE_ADDR + case TUNER_SET_TYPE_ADDR: + { + struct tuner_setup *tun_setup = arg; + int *type = &tun_setup->type; +#else + case TUNER_SET_TYPE: + { + int *type = arg; +#endif + + if (t->type >= 0) { + if (t->type != *type) + printk(KERN_ERR "wis-sony-tuner: type already " + "set to %d, ignoring request for %d\n", + t->type, *type); + break; + } + t->type = *type; + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + switch (force_band_str[0]) { + case 'b': + case 'B': + case 'g': + case 'G': + printk(KERN_INFO "wis-sony-tuner: forcing " + "tuner to PAL-B/G bands\n"); + force_band = V4L2_STD_PAL_BG; + break; + case 'i': + case 'I': + printk(KERN_INFO "wis-sony-tuner: forcing " + "tuner to PAL-I band\n"); + force_band = V4L2_STD_PAL_I; + break; + case 'd': + case 'D': + case 'k': + case 'K': + printk(KERN_INFO "wis-sony-tuner: forcing " + "tuner to PAL-D/K bands\n"); + force_band = V4L2_STD_PAL_I; + break; + case 'l': + case 'L': + printk(KERN_INFO "wis-sony-tuner: forcing " + "tuner to SECAM-L band\n"); + force_band = V4L2_STD_SECAM_L; + break; + default: + force_band = 0; + break; + } + if (force_band) + t->std = force_band; + else + t->std = V4L2_STD_PAL_BG; + set_if(client); + break; + case TUNER_SONY_BTF_PK467Z: + t->std = V4L2_STD_NTSC_M_JP; + break; + case TUNER_SONY_BTF_PB463Z: + t->std = V4L2_STD_NTSC_M; + break; + default: + printk(KERN_ERR "wis-sony-tuner: tuner type %d is not " + "supported by this module\n", *type); + break; + } + if (type >= 0) + printk(KERN_INFO + "wis-sony-tuner: type set to %d (%s)\n", + t->type, sony_tuners[t->type - 200].name); + break; + } +#endif + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + f->frequency = t->freq; + break; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + t->freq = f->frequency; + set_freq(client, t->freq); + break; + } + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *std = arg; + + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + switch (std->index) { + case 0: + v4l2_video_std_construct(std, + V4L2_STD_PAL_BG, "PAL-B/G"); + break; + case 1: + v4l2_video_std_construct(std, + V4L2_STD_PAL_I, "PAL-I"); + break; + case 2: + v4l2_video_std_construct(std, + V4L2_STD_PAL_DK, "PAL-D/K"); + break; + case 3: + v4l2_video_std_construct(std, + V4L2_STD_SECAM_L, "SECAM-L"); + break; + default: + std->id = 0; /* hack to indicate EINVAL */ + break; + } + break; + case TUNER_SONY_BTF_PK467Z: + if (std->index != 0) { + std->id = 0; /* hack to indicate EINVAL */ + break; + } + v4l2_video_std_construct(std, + V4L2_STD_NTSC_M_JP, "NTSC-J"); + break; + case TUNER_SONY_BTF_PB463Z: + if (std->index != 0) { + std->id = 0; /* hack to indicate EINVAL */ + break; + } + v4l2_video_std_construct(std, V4L2_STD_NTSC_M, "NTSC"); + break; + } + break; + } + case VIDIOC_G_STD: + { + v4l2_std_id *std = arg; + + *std = t->std; + break; + } + case VIDIOC_S_STD: + { + v4l2_std_id *std = arg; + v4l2_std_id old = t->std; + + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + if (force_band && (*std & force_band) != *std && + *std != V4L2_STD_PAL && + *std != V4L2_STD_SECAM) { + printk(KERN_DEBUG "wis-sony-tuner: ignoring " + "requested TV standard in " + "favor of force_band value\n"); + t->std = force_band; + } else if (*std & V4L2_STD_PAL_BG) { /* default */ + t->std = V4L2_STD_PAL_BG; + } else if (*std & V4L2_STD_PAL_I) { + t->std = V4L2_STD_PAL_I; + } else if (*std & V4L2_STD_PAL_DK) { + t->std = V4L2_STD_PAL_DK; + } else if (*std & V4L2_STD_SECAM_L) { + t->std = V4L2_STD_SECAM_L; + } else { + printk(KERN_ERR "wis-sony-tuner: TV standard " + "not supported\n"); + *std = 0; /* hack to indicate EINVAL */ + break; + } + if (old != t->std) + set_if(client); + break; + case TUNER_SONY_BTF_PK467Z: + if (!(*std & V4L2_STD_NTSC_M_JP)) { + printk(KERN_ERR "wis-sony-tuner: TV standard " + "not supported\n"); + *std = 0; /* hack to indicate EINVAL */ + } + break; + case TUNER_SONY_BTF_PB463Z: + if (!(*std & V4L2_STD_NTSC_M)) { + printk(KERN_ERR "wis-sony-tuner: TV standard " + "not supported\n"); + *std = 0; /* hack to indicate EINVAL */ + } + break; + } + break; + } + case VIDIOC_QUERYSTD: + { + v4l2_std_id *std = arg; + + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + if (force_band) + *std = force_band; + else + *std = V4L2_STD_PAL_BG | V4L2_STD_PAL_I | + V4L2_STD_PAL_DK | V4L2_STD_SECAM_L; + break; + case TUNER_SONY_BTF_PK467Z: + *std = V4L2_STD_NTSC_M_JP; + break; + case TUNER_SONY_BTF_PB463Z: + *std = V4L2_STD_NTSC_M; + break; + } + break; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *tun = arg; + + memset(tun, 0, sizeof(*tun)); + strcpy(tun->name, "Television"); + tun->type = V4L2_TUNER_ANALOG_TV; + tun->rangelow = 0UL; /* does anything use these? */ + tun->rangehigh = 0xffffffffUL; + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + tun->capability = V4L2_TUNER_CAP_NORM | + V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2; + tun->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2; + break; + case TUNER_SONY_BTF_PK467Z: + case TUNER_SONY_BTF_PB463Z: + tun->capability = V4L2_TUNER_CAP_STEREO; + tun->rxsubchans = V4L2_TUNER_SUB_MONO | + V4L2_TUNER_SUB_STEREO; + break; + } + tun->audmode = t->audmode; + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *tun = arg; + + switch (t->type) { + case TUNER_SONY_BTF_PG472Z: + if (tun->audmode != t->audmode) { + t->audmode = tun->audmode; + mpx_setup(client); + } + break; + case TUNER_SONY_BTF_PK467Z: + case TUNER_SONY_BTF_PB463Z: + break; + } + return 0; + } + default: + break; + } + return 0; +} + +static int wis_sony_tuner_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_sony_tuner *t; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + t = kmalloc(sizeof(struct wis_sony_tuner), GFP_KERNEL); + if (t == NULL) + return -ENOMEM; + + t->type = -1; + t->freq = 0; + t->mpxmode = 0; + t->audmode = V4L2_TUNER_MODE_STEREO; + i2c_set_clientdata(client, t); + + printk(KERN_DEBUG + "wis-sony-tuner: initializing tuner at address %d on %s\n", + client->addr, adapter->name); + + return 0; +} + +static int wis_sony_tuner_remove(struct i2c_client *client) +{ + struct wis_sony_tuner *t = i2c_get_clientdata(client); + + kfree(t); + return 0; +} + +static const struct i2c_device_id wis_sony_tuner_id[] = { + { "wis_sony_tuner", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_sony_tuner_id); + +static struct i2c_driver wis_sony_tuner_driver = { + .driver = { + .name = "WIS Sony TV Tuner I2C driver", + }, + .probe = wis_sony_tuner_probe, + .remove = wis_sony_tuner_remove, + .command = tuner_command, + .id_table = wis_sony_tuner_id, +}; + +static int __init wis_sony_tuner_init(void) +{ + return i2c_add_driver(&wis_sony_tuner_driver); +} + +static void __exit wis_sony_tuner_cleanup(void) +{ + i2c_del_driver(&wis_sony_tuner_driver); +} + +module_init(wis_sony_tuner_init); +module_exit(wis_sony_tuner_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-tw2804.c b/drivers/staging/media/go7007/wis-tw2804.c new file mode 100644 index 00000000..9134f03e --- /dev/null +++ b/drivers/staging/media/go7007/wis-tw2804.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "wis-i2c.h" + +struct wis_tw2804 { + int channel; + int norm; + int brightness; + int contrast; + int saturation; + int hue; +}; + +static u8 global_registers[] = { + 0x39, 0x00, + 0x3a, 0xff, + 0x3b, 0x84, + 0x3c, 0x80, + 0x3d, 0x80, + 0x3e, 0x82, + 0x3f, 0x82, + 0xff, 0xff, /* Terminator (reg 0xff does not exist) */ +}; + +static u8 channel_registers[] = { + 0x01, 0xc4, + 0x02, 0xa5, + 0x03, 0x20, + 0x04, 0xd0, + 0x05, 0x20, + 0x06, 0xd0, + 0x07, 0x88, + 0x08, 0x20, + 0x09, 0x07, + 0x0a, 0xf0, + 0x0b, 0x07, + 0x0c, 0xf0, + 0x0d, 0x40, + 0x0e, 0xd2, + 0x0f, 0x80, + 0x10, 0x80, + 0x11, 0x80, + 0x12, 0x80, + 0x13, 0x1f, + 0x14, 0x00, + 0x15, 0x00, + 0x16, 0x00, + 0x17, 0x00, + 0x18, 0xff, + 0x19, 0xff, + 0x1a, 0xff, + 0x1b, 0xff, + 0x1c, 0xff, + 0x1d, 0xff, + 0x1e, 0xff, + 0x1f, 0xff, + 0x20, 0x07, + 0x21, 0x07, + 0x22, 0x00, + 0x23, 0x91, + 0x24, 0x51, + 0x25, 0x03, + 0x26, 0x00, + 0x27, 0x00, + 0x28, 0x00, + 0x29, 0x00, + 0x2a, 0x00, + 0x2b, 0x00, + 0x2c, 0x00, + 0x2d, 0x00, + 0x2e, 0x00, + 0x2f, 0x00, + 0x30, 0x00, + 0x31, 0x00, + 0x32, 0x00, + 0x33, 0x00, + 0x34, 0x00, + 0x35, 0x00, + 0x36, 0x00, + 0x37, 0x00, + 0xff, 0xff, /* Terminator (reg 0xff does not exist) */ +}; + +static int write_reg(struct i2c_client *client, u8 reg, u8 value, int channel) +{ + return i2c_smbus_write_byte_data(client, reg | (channel << 6), value); +} + +static int write_regs(struct i2c_client *client, u8 *regs, int channel) +{ + int i; + + for (i = 0; regs[i] != 0xff; i += 2) + if (i2c_smbus_write_byte_data(client, + regs[i] | (channel << 6), regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_tw2804_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct wis_tw2804 *dec = i2c_get_clientdata(client); + + if (cmd == DECODER_SET_CHANNEL) { + int *input = arg; + + if (*input < 0 || *input > 3) { + printk(KERN_ERR "wis-tw2804: channel %d is not " + "between 0 and 3!\n", *input); + return 0; + } + dec->channel = *input; + printk(KERN_DEBUG "wis-tw2804: initializing TW2804 " + "channel %d\n", dec->channel); + if (dec->channel == 0 && + write_regs(client, global_registers, 0) < 0) { + printk(KERN_ERR "wis-tw2804: error initializing " + "TW2804 global registers\n"); + return 0; + } + if (write_regs(client, channel_registers, dec->channel) < 0) { + printk(KERN_ERR "wis-tw2804: error initializing " + "TW2804 channel %d\n", dec->channel); + return 0; + } + return 0; + } + + if (dec->channel < 0) { + printk(KERN_DEBUG "wis-tw2804: ignoring command %08x until " + "channel number is set\n", cmd); + return 0; + } + + switch (cmd) { + case VIDIOC_S_STD: + { + v4l2_std_id *input = arg; + u8 regs[] = { + 0x01, *input & V4L2_STD_NTSC ? 0xc4 : 0x84, + 0x09, *input & V4L2_STD_NTSC ? 0x07 : 0x04, + 0x0a, *input & V4L2_STD_NTSC ? 0xf0 : 0x20, + 0x0b, *input & V4L2_STD_NTSC ? 0x07 : 0x04, + 0x0c, *input & V4L2_STD_NTSC ? 0xf0 : 0x20, + 0x0d, *input & V4L2_STD_NTSC ? 0x40 : 0x4a, + 0x16, *input & V4L2_STD_NTSC ? 0x00 : 0x40, + 0x17, *input & V4L2_STD_NTSC ? 0x00 : 0x40, + 0x20, *input & V4L2_STD_NTSC ? 0x07 : 0x0f, + 0x21, *input & V4L2_STD_NTSC ? 0x07 : 0x0f, + 0xff, 0xff, + }; + write_regs(client, regs, dec->channel); + dec->norm = *input; + break; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Brightness", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_CONTRAST: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Contrast", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_SATURATION: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Saturation", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + case V4L2_CID_HUE: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Hue", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 128; + ctrl->flags = 0; + break; + } + break; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 255) + dec->brightness = 255; + else if (ctrl->value < 0) + dec->brightness = 0; + else + dec->brightness = ctrl->value; + write_reg(client, 0x12, dec->brightness, dec->channel); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 255) + dec->contrast = 255; + else if (ctrl->value < 0) + dec->contrast = 0; + else + dec->contrast = ctrl->value; + write_reg(client, 0x11, dec->contrast, dec->channel); + break; + case V4L2_CID_SATURATION: + if (ctrl->value > 255) + dec->saturation = 255; + else if (ctrl->value < 0) + dec->saturation = 0; + else + dec->saturation = ctrl->value; + write_reg(client, 0x10, dec->saturation, dec->channel); + break; + case V4L2_CID_HUE: + if (ctrl->value > 255) + dec->hue = 255; + else if (ctrl->value < 0) + dec->hue = 0; + else + dec->hue = ctrl->value; + write_reg(client, 0x0f, dec->hue, dec->channel); + break; + } + break; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = dec->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = dec->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = dec->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = dec->hue; + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_tw2804_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_tw2804 *dec; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + dec = kmalloc(sizeof(struct wis_tw2804), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + + dec->channel = -1; + dec->norm = V4L2_STD_NTSC; + dec->brightness = 128; + dec->contrast = 128; + dec->saturation = 128; + dec->hue = 128; + i2c_set_clientdata(client, dec); + + printk(KERN_DEBUG "wis-tw2804: creating TW2804 at address %d on %s\n", + client->addr, adapter->name); + + return 0; +} + +static int wis_tw2804_remove(struct i2c_client *client) +{ + struct wis_tw2804 *dec = i2c_get_clientdata(client); + + kfree(dec); + return 0; +} + +static const struct i2c_device_id wis_tw2804_id[] = { + { "wis_tw2804", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_tw2804_id); + +static struct i2c_driver wis_tw2804_driver = { + .driver = { + .name = "WIS TW2804 I2C driver", + }, + .probe = wis_tw2804_probe, + .remove = wis_tw2804_remove, + .command = wis_tw2804_command, + .id_table = wis_tw2804_id, +}; + +static int __init wis_tw2804_init(void) +{ + return i2c_add_driver(&wis_tw2804_driver); +} + +static void __exit wis_tw2804_cleanup(void) +{ + i2c_del_driver(&wis_tw2804_driver); +} + +module_init(wis_tw2804_init); +module_exit(wis_tw2804_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-tw9903.c b/drivers/staging/media/go7007/wis-tw9903.c new file mode 100644 index 00000000..9230f4a8 --- /dev/null +++ b/drivers/staging/media/go7007/wis-tw9903.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "wis-i2c.h" + +struct wis_tw9903 { + int norm; + int brightness; + int contrast; + int hue; +}; + +static u8 initial_registers[] = +{ + 0x02, 0x44, /* input 1, composite */ + 0x03, 0x92, /* correct digital format */ + 0x04, 0x00, + 0x05, 0x80, /* or 0x00 for PAL */ + 0x06, 0x40, /* second internal current reference */ + 0x07, 0x02, /* window */ + 0x08, 0x14, /* window */ + 0x09, 0xf0, /* window */ + 0x0a, 0x81, /* window */ + 0x0b, 0xd0, /* window */ + 0x0c, 0x8c, + 0x0d, 0x00, /* scaling */ + 0x0e, 0x11, /* scaling */ + 0x0f, 0x00, /* scaling */ + 0x10, 0x00, /* brightness */ + 0x11, 0x60, /* contrast */ + 0x12, 0x01, /* sharpness */ + 0x13, 0x7f, /* U gain */ + 0x14, 0x5a, /* V gain */ + 0x15, 0x00, /* hue */ + 0x16, 0xc3, /* sharpness */ + 0x18, 0x00, + 0x19, 0x58, /* vbi */ + 0x1a, 0x80, + 0x1c, 0x0f, /* video norm */ + 0x1d, 0x7f, /* video norm */ + 0x20, 0xa0, /* clamping gain (working 0x50) */ + 0x21, 0x22, + 0x22, 0xf0, + 0x23, 0xfe, + 0x24, 0x3c, + 0x25, 0x38, + 0x26, 0x44, + 0x27, 0x20, + 0x28, 0x00, + 0x29, 0x15, + 0x2a, 0xa0, + 0x2b, 0x44, + 0x2c, 0x37, + 0x2d, 0x00, + 0x2e, 0xa5, /* burst PLL control (working: a9) */ + 0x2f, 0xe0, /* 0xea is blue test frame -- 0xe0 for normal */ + 0x31, 0x00, + 0x33, 0x22, + 0x34, 0x11, + 0x35, 0x35, + 0x3b, 0x05, + 0x06, 0xc0, /* reset device */ + 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */ +}; + +static int write_reg(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int write_regs(struct i2c_client *client, u8 *regs) +{ + int i; + + for (i = 0; regs[i] != 0x00; i += 2) + if (i2c_smbus_write_byte_data(client, regs[i], regs[i + 1]) < 0) + return -1; + return 0; +} + +static int wis_tw9903_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct wis_tw9903 *dec = i2c_get_clientdata(client); + + switch (cmd) { + case VIDIOC_S_INPUT: + { + int *input = arg; + + i2c_smbus_write_byte_data(client, 0x02, 0x40 | (*input << 1)); + break; + } +#if 0 + /* The scaler on this thing seems to be horribly broken */ + case DECODER_SET_RESOLUTION: + { + struct video_decoder_resolution *res = arg; + /*int hscale = 256 * 720 / res->width;*/ + int hscale = 256 * 720 / (res->width - (res->width > 704 ? 0 : 8)); + int vscale = 256 * (dec->norm & V4L2_STD_NTSC ? 240 : 288) + / res->height; + u8 regs[] = { + 0x0d, vscale & 0xff, + 0x0f, hscale & 0xff, + 0x0e, ((vscale & 0xf00) >> 4) | ((hscale & 0xf00) >> 8), + 0x06, 0xc0, /* reset device */ + 0, 0, + }; + printk(KERN_DEBUG "vscale is %04x, hscale is %04x\n", + vscale, hscale); + /*write_regs(client, regs);*/ + break; + } +#endif + case VIDIOC_S_STD: + { + v4l2_std_id *input = arg; + u8 regs[] = { + 0x05, *input & V4L2_STD_NTSC ? 0x80 : 0x00, + 0x07, *input & V4L2_STD_NTSC ? 0x02 : 0x12, + 0x08, *input & V4L2_STD_NTSC ? 0x14 : 0x18, + 0x09, *input & V4L2_STD_NTSC ? 0xf0 : 0x20, + 0, 0, + }; + write_regs(client, regs); + dec->norm = *input; + break; + } + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Brightness", sizeof(ctrl->name)); + ctrl->minimum = -128; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 0x00; + ctrl->flags = 0; + break; + case V4L2_CID_CONTRAST: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Contrast", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 255; + ctrl->step = 1; + ctrl->default_value = 0x60; + ctrl->flags = 0; + break; +#if 0 + /* I don't understand how the Chroma Gain registers work... */ + case V4L2_CID_SATURATION: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Saturation", sizeof(ctrl->name)); + ctrl->minimum = 0; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 64; + ctrl->flags = 0; + break; +#endif + case V4L2_CID_HUE: + ctrl->type = V4L2_CTRL_TYPE_INTEGER; + strncpy(ctrl->name, "Hue", sizeof(ctrl->name)); + ctrl->minimum = -128; + ctrl->maximum = 127; + ctrl->step = 1; + ctrl->default_value = 0; + ctrl->flags = 0; + break; + } + break; + } + case VIDIOC_S_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value > 127) + dec->brightness = 127; + else if (ctrl->value < -128) + dec->brightness = -128; + else + dec->brightness = ctrl->value; + write_reg(client, 0x10, dec->brightness); + break; + case V4L2_CID_CONTRAST: + if (ctrl->value > 255) + dec->contrast = 255; + else if (ctrl->value < 0) + dec->contrast = 0; + else + dec->contrast = ctrl->value; + write_reg(client, 0x11, dec->contrast); + break; +#if 0 + case V4L2_CID_SATURATION: + if (ctrl->value > 127) + dec->saturation = 127; + else if (ctrl->value < 0) + dec->saturation = 0; + else + dec->saturation = ctrl->value; + /*write_reg(client, 0x0c, dec->saturation);*/ + break; +#endif + case V4L2_CID_HUE: + if (ctrl->value > 127) + dec->hue = 127; + else if (ctrl->value < -128) + dec->hue = -128; + else + dec->hue = ctrl->value; + write_reg(client, 0x15, dec->hue); + break; + } + break; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *ctrl = arg; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = dec->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = dec->contrast; + break; +#if 0 + case V4L2_CID_SATURATION: + ctrl->value = dec->saturation; + break; +#endif + case V4L2_CID_HUE: + ctrl->value = dec->hue; + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_tw9903_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct wis_tw9903 *dec; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + dec = kmalloc(sizeof(struct wis_tw9903), GFP_KERNEL); + if (dec == NULL) + return -ENOMEM; + + dec->norm = V4L2_STD_NTSC; + dec->brightness = 0; + dec->contrast = 0x60; + dec->hue = 0; + i2c_set_clientdata(client, dec); + + printk(KERN_DEBUG + "wis-tw9903: initializing TW9903 at address %d on %s\n", + client->addr, adapter->name); + + if (write_regs(client, initial_registers) < 0) { + printk(KERN_ERR "wis-tw9903: error initializing TW9903\n"); + kfree(dec); + return -ENODEV; + } + + return 0; +} + +static int wis_tw9903_remove(struct i2c_client *client) +{ + struct wis_tw9903 *dec = i2c_get_clientdata(client); + + kfree(dec); + return 0; +} + +static const struct i2c_device_id wis_tw9903_id[] = { + { "wis_tw9903", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_tw9903_id); + +static struct i2c_driver wis_tw9903_driver = { + .driver = { + .name = "WIS TW9903 I2C driver", + }, + .probe = wis_tw9903_probe, + .remove = wis_tw9903_remove, + .command = wis_tw9903_command, + .id_table = wis_tw9903_id, +}; + +static int __init wis_tw9903_init(void) +{ + return i2c_add_driver(&wis_tw9903_driver); +} + +static void __exit wis_tw9903_cleanup(void) +{ + i2c_del_driver(&wis_tw9903_driver); +} + +module_init(wis_tw9903_init); +module_exit(wis_tw9903_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/go7007/wis-uda1342.c b/drivers/staging/media/go7007/wis-uda1342.c new file mode 100644 index 00000000..0127be2f --- /dev/null +++ b/drivers/staging/media/go7007/wis-uda1342.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2005-2006 Micronas USA Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "wis-i2c.h" + +static int write_reg(struct i2c_client *client, int reg, int value) +{ + /* UDA1342 wants MSB first, but SMBus sends LSB first */ + i2c_smbus_write_word_data(client, reg, swab16(value)); + return 0; +} + +static int wis_uda1342_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + switch (cmd) { + case VIDIOC_S_AUDIO: + { + int *inp = arg; + + switch (*inp) { + case TVAUDIO_INPUT_TUNER: + write_reg(client, 0x00, 0x1441); /* select input 2 */ + break; + case TVAUDIO_INPUT_EXTERN: + write_reg(client, 0x00, 0x1241); /* select input 1 */ + break; + default: + printk(KERN_ERR "wis-uda1342: input %d not supported\n", + *inp); + break; + } + break; + } + default: + break; + } + return 0; +} + +static int wis_uda1342_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + printk(KERN_DEBUG + "wis-uda1342: initializing UDA1342 at address %d on %s\n", + client->addr, adapter->name); + + write_reg(client, 0x00, 0x8000); /* reset registers */ + write_reg(client, 0x00, 0x1241); /* select input 1 */ + + return 0; +} + +static int wis_uda1342_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id wis_uda1342_id[] = { + { "wis_uda1342", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wis_uda1342_id); + +static struct i2c_driver wis_uda1342_driver = { + .driver = { + .name = "WIS UDA1342 I2C driver", + }, + .probe = wis_uda1342_probe, + .remove = wis_uda1342_remove, + .command = wis_uda1342_command, + .id_table = wis_uda1342_id, +}; + +static int __init wis_uda1342_init(void) +{ + return i2c_add_driver(&wis_uda1342_driver); +} + +static void __exit wis_uda1342_cleanup(void) +{ + i2c_del_driver(&wis_uda1342_driver); +} + +module_init(wis_uda1342_init); +module_exit(wis_uda1342_cleanup); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/media/lirc/Kconfig b/drivers/staging/media/lirc/Kconfig new file mode 100644 index 00000000..526ec0fc --- /dev/null +++ b/drivers/staging/media/lirc/Kconfig @@ -0,0 +1,78 @@ +# +# LIRC driver(s) configuration +# +menuconfig LIRC_STAGING + bool "Linux Infrared Remote Control IR receiver/transmitter drivers" + depends on LIRC + help + Say Y here, and all supported Linux Infrared Remote Control IR and + RF receiver and transmitter drivers will be displayed. When paired + with a remote control and the lirc daemon, the receiver drivers + allow control of your Linux system via remote control. + +if LIRC_STAGING + +config LIRC_BT829 + tristate "BT829 based hardware" + depends on LIRC && PCI + help + Driver for the IR interface on BT829-based hardware + +config LIRC_IGORPLUGUSB + tristate "Igor Cesko's USB IR Receiver" + depends on LIRC && USB + help + Driver for Igor Cesko's USB IR Receiver + +config LIRC_IMON + tristate "Legacy SoundGraph iMON Receiver and Display" + depends on LIRC && USB + help + Driver for the original SoundGraph iMON IR Receiver and Display + + Current generation iMON devices use the input layer imon driver. + +config LIRC_PARALLEL + tristate "Homebrew Parallel Port Receiver" + depends on LIRC && PARPORT + help + Driver for Homebrew Parallel Port Receivers + +config LIRC_SASEM + tristate "Sasem USB IR Remote" + depends on LIRC && USB + help + Driver for the Sasem OnAir Remocon-V or Dign HV5 HTPC IR/VFD Module + +config LIRC_SERIAL + tristate "Homebrew Serial Port Receiver" + depends on LIRC + help + Driver for Homebrew Serial Port Receivers + +config LIRC_SERIAL_TRANSMITTER + bool "Serial Port Transmitter" + default y + depends on LIRC_SERIAL + help + Serial Port Transmitter support + +config LIRC_SIR + tristate "Built-in SIR IrDA port" + depends on LIRC + help + Driver for the SIR IrDA port + +config LIRC_TTUSBIR + tristate "Technotrend USB IR Receiver" + depends on LIRC && USB + help + Driver for the Technotrend USB IR Receiver + +config LIRC_ZILOG + tristate "Zilog/Hauppauge IR Transmitter" + depends on LIRC && I2C + help + Driver for the Zilog/Hauppauge IR Transmitter, found on + PVR-150/500, HVR-1200/1250/1700/1800, HD-PVR and other cards +endif diff --git a/drivers/staging/media/lirc/Makefile b/drivers/staging/media/lirc/Makefile new file mode 100644 index 00000000..d76b0fa2 --- /dev/null +++ b/drivers/staging/media/lirc/Makefile @@ -0,0 +1,14 @@ +# Makefile for the lirc drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_LIRC_BT829) += lirc_bt829.o +obj-$(CONFIG_LIRC_IGORPLUGUSB) += lirc_igorplugusb.o +obj-$(CONFIG_LIRC_IMON) += lirc_imon.o +obj-$(CONFIG_LIRC_PARALLEL) += lirc_parallel.o +obj-$(CONFIG_LIRC_SASEM) += lirc_sasem.o +obj-$(CONFIG_LIRC_SERIAL) += lirc_serial.o +obj-$(CONFIG_LIRC_SIR) += lirc_sir.o +obj-$(CONFIG_LIRC_TTUSBIR) += lirc_ttusbir.o +obj-$(CONFIG_LIRC_ZILOG) += lirc_zilog.o diff --git a/drivers/staging/media/lirc/TODO b/drivers/staging/media/lirc/TODO new file mode 100644 index 00000000..b6cb593f --- /dev/null +++ b/drivers/staging/media/lirc/TODO @@ -0,0 +1,8 @@ +- All drivers should either be ported to ir-core, or dropped entirely + (see drivers/media/IR/mceusb.c vs. lirc_mceusb.c in lirc cvs for an + example of a previously completed port). + +Please send patches to: +Jarod Wilson +Greg Kroah-Hartman + diff --git a/drivers/staging/media/lirc/TODO.lirc_zilog b/drivers/staging/media/lirc/TODO.lirc_zilog new file mode 100644 index 00000000..a97800a8 --- /dev/null +++ b/drivers/staging/media/lirc/TODO.lirc_zilog @@ -0,0 +1,36 @@ +1. Both ir-kbd-i2c and lirc_zilog provide support for RX events for +the chips supported by lirc_zilog. Before moving lirc_zilog out of staging: + +a. ir-kbd-i2c needs a module parameter added to allow the user to tell + ir-kbd-i2c to ignore Z8 IR units. + +b. lirc_zilog should provide Rx key presses to the rc core like ir-kbd-i2c + does. + + +2. lirc_zilog module ref-counting need examination. It has not been +verified that cdev and lirc_dev will take the proper module references on +lirc_zilog to prevent removal of lirc_zilog when the /dev/lircN device node +is open. + +(The good news is ref-counting of lirc_zilog internal structures appears to be +complete. Testing has shown the cx18 module can be unloaded out from under +irw + lircd + lirc_dev, with the /dev/lirc0 device node open, with no adverse +effects. The cx18 module could then be reloaded and irw properly began +receiving button presses again and ir_send worked without error.) + + +3. Bridge drivers, if able, should provide a chip reset() callback +to lirc_zilog via struct IR_i2c_init_data. cx18 and ivtv already have routines +to perform Z8 chip resets via GPIO manipulations. This would allow lirc_zilog +to bring the chip back to normal when it hangs, in the same places the +original lirc_pvr150 driver code does. This is not strictly needed, so it +is not required to move lirc_zilog out of staging. + +Note: Both lirc_zilog and ir-kbd-i2c support the Zilog Z8 for IR, as programmed +and installed on Hauppauge products. When working on either module, developers +must consider at least the following bridge drivers which mention an IR Rx unit +at address 0x71 (indicative of a Z8): + + ivtv cx18 hdpvr pvrusb2 bt8xx cx88 saa7134 + diff --git a/drivers/staging/media/lirc/lirc_bt829.c b/drivers/staging/media/lirc/lirc_bt829.c new file mode 100644 index 00000000..4d20e9f7 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_bt829.c @@ -0,0 +1,383 @@ +/* + * Remote control driver for the TV-card based on bt829 + * + * by Leonid Froenchenko + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +static int poll_main(void); +static int atir_init_start(void); + +static void write_index(unsigned char index, unsigned int value); +static unsigned int read_index(unsigned char index); + +static void do_i2c_start(void); +static void do_i2c_stop(void); + +static void seems_wr_byte(unsigned char al); +static unsigned char seems_rd_byte(void); + +static unsigned int read_index(unsigned char al); +static void write_index(unsigned char ah, unsigned int edx); + +static void cycle_delay(int cycle); + +static void do_set_bits(unsigned char bl); +static unsigned char do_get_bits(void); + +#define DATA_PCI_OFF 0x7FFC00 +#define WAIT_CYCLE 20 + +#define DRIVER_NAME "lirc_bt829" + +static bool debug; +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG DRIVER_NAME ": "fmt, ## args); \ + } while (0) + +static int atir_minor; +static unsigned long pci_addr_phys; +static unsigned char *pci_addr_lin; + +static struct lirc_driver atir_driver; + +static struct pci_dev *do_pci_probe(void) +{ + struct pci_dev *my_dev; + my_dev = pci_get_device(PCI_VENDOR_ID_ATI, + PCI_DEVICE_ID_ATI_264VT, NULL); + if (my_dev) { + printk(KERN_ERR DRIVER_NAME ": Using device: %s\n", + pci_name(my_dev)); + pci_addr_phys = 0; + if (my_dev->resource[0].flags & IORESOURCE_MEM) { + pci_addr_phys = my_dev->resource[0].start; + printk(KERN_INFO DRIVER_NAME ": memory at 0x%08X\n", + (unsigned int)pci_addr_phys); + } + if (pci_addr_phys == 0) { + printk(KERN_ERR DRIVER_NAME ": no memory resource ?\n"); + return NULL; + } + } else { + printk(KERN_ERR DRIVER_NAME ": pci_probe failed\n"); + return NULL; + } + return my_dev; +} + +static int atir_add_to_buf(void *data, struct lirc_buffer *buf) +{ + unsigned char key; + int status; + status = poll_main(); + key = (status >> 8) & 0xFF; + if (status & 0xFF) { + dprintk("reading key %02X\n", key); + lirc_buffer_write(buf, &key); + return 0; + } + return -ENODATA; +} + +static int atir_set_use_inc(void *data) +{ + dprintk("driver is opened\n"); + return 0; +} + +static void atir_set_use_dec(void *data) +{ + dprintk("driver is closed\n"); +} + +int init_module(void) +{ + struct pci_dev *pdev; + + pdev = do_pci_probe(); + if (pdev == NULL) + return -ENODEV; + + if (!atir_init_start()) + return -ENODEV; + + strcpy(atir_driver.name, "ATIR"); + atir_driver.minor = -1; + atir_driver.code_length = 8; + atir_driver.sample_rate = 10; + atir_driver.data = 0; + atir_driver.add_to_buf = atir_add_to_buf; + atir_driver.set_use_inc = atir_set_use_inc; + atir_driver.set_use_dec = atir_set_use_dec; + atir_driver.dev = &pdev->dev; + atir_driver.owner = THIS_MODULE; + + atir_minor = lirc_register_driver(&atir_driver); + if (atir_minor < 0) { + printk(KERN_ERR DRIVER_NAME ": failed to register driver!\n"); + return atir_minor; + } + dprintk("driver is registered on minor %d\n", atir_minor); + + return 0; +} + + +void cleanup_module(void) +{ + lirc_unregister_driver(atir_minor); +} + + +static int atir_init_start(void) +{ + pci_addr_lin = ioremap(pci_addr_phys + DATA_PCI_OFF, 0x400); + if (pci_addr_lin == 0) { + printk(KERN_INFO DRIVER_NAME ": pci mem must be mapped\n"); + return 0; + } + return 1; +} + +static void cycle_delay(int cycle) +{ + udelay(WAIT_CYCLE*cycle); +} + + +static int poll_main() +{ + unsigned char status_high, status_low; + + do_i2c_start(); + + seems_wr_byte(0xAA); + seems_wr_byte(0x01); + + do_i2c_start(); + + seems_wr_byte(0xAB); + + status_low = seems_rd_byte(); + status_high = seems_rd_byte(); + + do_i2c_stop(); + + return (status_high << 8) | status_low; +} + +static void do_i2c_start(void) +{ + do_set_bits(3); + cycle_delay(4); + + do_set_bits(1); + cycle_delay(7); + + do_set_bits(0); + cycle_delay(2); +} + +static void do_i2c_stop(void) +{ + unsigned char bits; + bits = do_get_bits() & 0xFD; + do_set_bits(bits); + cycle_delay(1); + + bits |= 1; + do_set_bits(bits); + cycle_delay(2); + + bits |= 2; + do_set_bits(bits); + bits = 3; + do_set_bits(bits); + cycle_delay(2); +} + +static void seems_wr_byte(unsigned char value) +{ + int i; + unsigned char reg; + + reg = do_get_bits(); + for (i = 0; i < 8; i++) { + if (value & 0x80) + reg |= 0x02; + else + reg &= 0xFD; + + do_set_bits(reg); + cycle_delay(1); + + reg |= 1; + do_set_bits(reg); + cycle_delay(1); + + reg &= 0xFE; + do_set_bits(reg); + cycle_delay(1); + value <<= 1; + } + cycle_delay(2); + + reg |= 2; + do_set_bits(reg); + + reg |= 1; + do_set_bits(reg); + + cycle_delay(1); + do_get_bits(); + + reg &= 0xFE; + do_set_bits(reg); + cycle_delay(3); +} + +static unsigned char seems_rd_byte(void) +{ + int i; + int rd_byte; + unsigned char bits_2, bits_1; + + bits_1 = do_get_bits() | 2; + do_set_bits(bits_1); + + rd_byte = 0; + for (i = 0; i < 8; i++) { + bits_1 &= 0xFE; + do_set_bits(bits_1); + cycle_delay(2); + + bits_1 |= 1; + do_set_bits(bits_1); + cycle_delay(1); + + bits_2 = do_get_bits(); + if (bits_2 & 2) + rd_byte |= 1; + + rd_byte <<= 1; + } + + bits_1 = 0; + if (bits_2 == 0) + bits_1 |= 2; + + do_set_bits(bits_1); + cycle_delay(2); + + bits_1 |= 1; + do_set_bits(bits_1); + cycle_delay(3); + + bits_1 &= 0xFE; + do_set_bits(bits_1); + cycle_delay(2); + + rd_byte >>= 1; + rd_byte &= 0xFF; + return rd_byte; +} + +static void do_set_bits(unsigned char new_bits) +{ + int reg_val; + reg_val = read_index(0x34); + if (new_bits & 2) { + reg_val &= 0xFFFFFFDF; + reg_val |= 1; + } else { + reg_val &= 0xFFFFFFFE; + reg_val |= 0x20; + } + reg_val |= 0x10; + write_index(0x34, reg_val); + + reg_val = read_index(0x31); + if (new_bits & 1) + reg_val |= 0x1000000; + else + reg_val &= 0xFEFFFFFF; + + reg_val |= 0x8000000; + write_index(0x31, reg_val); +} + +static unsigned char do_get_bits(void) +{ + unsigned char bits; + int reg_val; + + reg_val = read_index(0x34); + reg_val |= 0x10; + reg_val &= 0xFFFFFFDF; + write_index(0x34, reg_val); + + reg_val = read_index(0x34); + bits = 0; + if (reg_val & 8) + bits |= 2; + else + bits &= 0xFD; + + reg_val = read_index(0x31); + if (reg_val & 0x1000000) + bits |= 1; + else + bits &= 0xFE; + + return bits; +} + +static unsigned int read_index(unsigned char index) +{ + unsigned char *addr; + unsigned int value; + /* addr = pci_addr_lin + DATA_PCI_OFF + ((index & 0xFF) << 2); */ + addr = pci_addr_lin + ((index & 0xFF) << 2); + value = readl(addr); + return value; +} + +static void write_index(unsigned char index, unsigned int reg_val) +{ + unsigned char *addr; + addr = pci_addr_lin + ((index & 0xFF) << 2); + writel(reg_val, addr); +} + +MODULE_AUTHOR("Froenchenko Leonid"); +MODULE_DESCRIPTION("IR remote driver for bt829 based TV cards"); +MODULE_LICENSE("GPL"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); diff --git a/drivers/staging/media/lirc/lirc_ene0100.h b/drivers/staging/media/lirc/lirc_ene0100.h new file mode 100644 index 00000000..06bebd6a --- /dev/null +++ b/drivers/staging/media/lirc/lirc_ene0100.h @@ -0,0 +1,169 @@ +/* + * driver for ENE KB3926 B/C/D CIR (also known as ENE0100) + * + * Copyright (C) 2009 Maxim Levitsky + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include +#include + +/* hardware address */ +#define ENE_STATUS 0 /* hardware status - unused */ +#define ENE_ADDR_HI 1 /* hi byte of register address */ +#define ENE_ADDR_LO 2 /* low byte of register address */ +#define ENE_IO 3 /* read/write window */ +#define ENE_MAX_IO 4 + +/* 8 bytes of samples, divided in 2 halfs*/ +#define ENE_SAMPLE_BUFFER 0xF8F0 /* regular sample buffer */ +#define ENE_SAMPLE_SPC_MASK (1 << 7) /* sample is space */ +#define ENE_SAMPLE_VALUE_MASK 0x7F +#define ENE_SAMPLE_OVERFLOW 0x7F +#define ENE_SAMPLES_SIZE 4 + +/* fan input sample buffer */ +#define ENE_SAMPLE_BUFFER_FAN 0xF8FB /* this buffer holds high byte of */ + /* each sample of normal buffer */ + +#define ENE_FAN_SMPL_PULS_MSK 0x8000 /* this bit of combined sample */ + /* if set, says that sample is pulse */ +#define ENE_FAN_VALUE_MASK 0x0FFF /* mask for valid bits of the value */ + +/* first firmware register */ +#define ENE_FW1 0xF8F8 +#define ENE_FW1_ENABLE (1 << 0) /* enable fw processing */ +#define ENE_FW1_TXIRQ (1 << 1) /* TX interrupt pending */ +#define ENE_FW1_WAKE (1 << 6) /* enable wake from S3 */ +#define ENE_FW1_IRQ (1 << 7) /* enable interrupt */ + +/* second firmware register */ +#define ENE_FW2 0xF8F9 +#define ENE_FW2_BUF_HIGH (1 << 0) /* which half of the buffer to read */ +#define ENE_FW2_IRQ_CLR (1 << 2) /* clear this on IRQ */ +#define ENE_FW2_GP40_AS_LEARN (1 << 4) /* normal input is used as */ + /* learning input */ +#define ENE_FW2_FAN_AS_NRML_IN (1 << 6) /* fan is used as normal input */ +#define ENE_FW2_LEARNING (1 << 7) /* hardware supports learning and TX */ + +/* fan as input settings - only if learning capable */ +#define ENE_FAN_AS_IN1 0xFE30 /* fan init reg 1 */ +#define ENE_FAN_AS_IN1_EN 0xCD +#define ENE_FAN_AS_IN2 0xFE31 /* fan init reg 2 */ +#define ENE_FAN_AS_IN2_EN 0x03 +#define ENE_SAMPLE_PERIOD_FAN 61 /* fan input has fixed sample period */ + +/* IRQ registers block (for revision B) */ +#define ENEB_IRQ 0xFD09 /* IRQ number */ +#define ENEB_IRQ_UNK1 0xFD17 /* unknown setting = 1 */ +#define ENEB_IRQ_STATUS 0xFD80 /* irq status */ +#define ENEB_IRQ_STATUS_IR (1 << 5) /* IR irq */ + +/* IRQ registers block (for revision C,D) */ +#define ENEC_IRQ 0xFE9B /* new irq settings register */ +#define ENEC_IRQ_MASK 0x0F /* irq number mask */ +#define ENEC_IRQ_UNK_EN (1 << 4) /* always enabled */ +#define ENEC_IRQ_STATUS (1 << 5) /* irq status and ACK */ + +/* CIR block settings */ +#define ENE_CIR_CONF1 0xFEC0 +#define ENE_CIR_CONF1_ADC_ON 0x7 /* receiver on gpio40 enabled */ +#define ENE_CIR_CONF1_LEARN1 (1 << 3) /* enabled on learning mode */ +#define ENE_CIR_CONF1_TX_ON 0x30 /* enabled on transmit */ +#define ENE_CIR_CONF1_TX_CARR (1 << 7) /* send TX carrier or not */ + +#define ENE_CIR_CONF2 0xFEC1 /* unknown setting = 0 */ +#define ENE_CIR_CONF2_LEARN2 (1 << 4) /* set on enable learning */ +#define ENE_CIR_CONF2_GPIO40DIS (1 << 5) /* disable normal input via gpio40 */ + +#define ENE_CIR_SAMPLE_PERIOD 0xFEC8 /* sample period in us */ +#define ENE_CIR_SAMPLE_OVERFLOW (1 << 7) /* interrupt on overflows if set */ + + +/* transmitter - not implemented yet */ +/* KB3926C and higher */ +/* transmission is very similar to receiving, a byte is written to */ +/* ENE_TX_INPUT, in same manner as it is read from sample buffer */ +/* sample period is fixed*/ + + +/* transmitter ports */ +#define ENE_TX_PORT1 0xFC01 /* this enables one or both */ +#define ENE_TX_PORT1_EN (1 << 5) /* TX ports */ +#define ENE_TX_PORT2 0xFC08 +#define ENE_TX_PORT2_EN (1 << 1) + +#define ENE_TX_INPUT 0xFEC9 /* next byte to transmit */ +#define ENE_TX_SPC_MASK (1 << 7) /* Transmitted sample is space */ +#define ENE_TX_UNK1 0xFECB /* set to 0x63 */ +#define ENE_TX_SMPL_PERIOD 50 /* transmit sample period */ + + +#define ENE_TX_CARRIER 0xFECE /* TX carrier * 2 (khz) */ +#define ENE_TX_CARRIER_UNKBIT 0x80 /* This bit set on transmit */ +#define ENE_TX_CARRIER_LOW 0xFECF /* TX carrier / 2 */ + +/* Hardware versions */ +#define ENE_HW_VERSION 0xFF00 /* hardware revision */ +#define ENE_HW_UNK 0xFF1D +#define ENE_HW_UNK_CLR (1 << 2) +#define ENE_HW_VER_MAJOR 0xFF1E /* chip version */ +#define ENE_HW_VER_MINOR 0xFF1F +#define ENE_HW_VER_OLD 0xFD00 + +#define same_sign(a, b) ((((a) > 0) && (b) > 0) || ((a) < 0 && (b) < 0)) + +#define ENE_DRIVER_NAME "enecir" +#define ENE_MAXGAP 250000 /* this is amount of time we wait + before turning the sampler, chosen + arbitry */ + +#define space(len) (-(len)) /* add a space */ + +/* software defines */ +#define ENE_IRQ_RX 1 +#define ENE_IRQ_TX 2 + +#define ENE_HW_B 1 /* 3926B */ +#define ENE_HW_C 2 /* 3926C */ +#define ENE_HW_D 3 /* 3926D */ + +#define ene_printk(level, text, ...) \ + printk(level ENE_DRIVER_NAME ": " text, ## __VA_ARGS__) + +struct ene_device { + struct pnp_dev *pnp_dev; + struct lirc_driver *lirc_driver; + + /* hw settings */ + unsigned long hw_io; + int irq; + + int hw_revision; /* hardware revision */ + int hw_learning_and_tx_capable; /* learning capable */ + int hw_gpio40_learning; /* gpio40 is learning */ + int hw_fan_as_normal_input; /* fan input is used as regular input */ + + /* device data */ + int idle; + int fan_input_inuse; + + int sample; + int in_use; + + struct timeval gap_start; +}; diff --git a/drivers/staging/media/lirc/lirc_igorplugusb.c b/drivers/staging/media/lirc/lirc_igorplugusb.c new file mode 100644 index 00000000..7a250177 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_igorplugusb.c @@ -0,0 +1,558 @@ +/* + * lirc_igorplugusb - USB remote support for LIRC + * + * Supports the standard homebrew IgorPlugUSB receiver with Igor's firmware. + * See http://www.cesko.host.sk/IgorPlugUSB/IgorPlug-USB%20(AVR)_eng.htm + * + * The device can only record bursts of up to 36 pulses/spaces. + * Works fine with RC5. Longer commands lead to device buffer overrun. + * (Maybe a better firmware or a microcontroller with more ram can help?) + * + * Version 0.1 [beta status] + * + * Copyright (C) 2004 Jan M. Hochstein + * + * + * This driver was derived from: + * Paul Miller + * "lirc_atiusb" module + * Vladimir Dergachev 's 2002 + * "USB ATI Remote support" (input device) + * Adrian Dewhurst 's 2002 + * "USB StreamZap remote driver" (LIRC) + * Artur Lipowski 's 2002 + * "lirc_dev" and "lirc_gpio" LIRC modules + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +/* module identification */ +#define DRIVER_VERSION "0.2" +#define DRIVER_AUTHOR \ + "Jan M. Hochstein " +#define DRIVER_DESC "Igorplug USB remote driver for LIRC" +#define DRIVER_NAME "lirc_igorplugusb" + +/* debugging support */ +#ifdef CONFIG_USB_DEBUG +static bool debug = 1; +#else +static bool debug; +#endif + +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG fmt, ## args); \ + } while (0) + +/* One mode2 pulse/space has 4 bytes. */ +#define CODE_LENGTH sizeof(int) + +/* Igor's firmware cannot record bursts longer than 36. */ +#define DEVICE_BUFLEN 36 + +/* + * Header at the beginning of the device's buffer: + * unsigned char data_length + * unsigned char data_start (!=0 means ring-buffer overrun) + * unsigned char counter (incremented by each burst) + */ +#define DEVICE_HEADERLEN 3 + +/* This is for the gap */ +#define ADDITIONAL_LIRC_BYTES 2 + +/* times to poll per second */ +#define SAMPLE_RATE 100 +static int sample_rate = SAMPLE_RATE; + + +/**** Igor's USB Request Codes */ + +#define SET_INFRABUFFER_EMPTY 1 +/** + * Params: none + * Answer: empty + */ + +#define GET_INFRACODE 2 +/** + * Params: + * wValue: offset to begin reading infra buffer + * + * Answer: infra data + */ + +#define SET_DATAPORT_DIRECTION 3 +/** + * Params: + * wValue: (byte) 1 bit for each data port pin (0=in, 1=out) + * + * Answer: empty + */ + +#define GET_DATAPORT_DIRECTION 4 +/** + * Params: none + * + * Answer: (byte) 1 bit for each data port pin (0=in, 1=out) + */ + +#define SET_OUT_DATAPORT 5 +/** + * Params: + * wValue: byte to write to output data port + * + * Answer: empty + */ + +#define GET_OUT_DATAPORT 6 +/** + * Params: none + * + * Answer: least significant 3 bits read from output data port + */ + +#define GET_IN_DATAPORT 7 +/** + * Params: none + * + * Answer: least significant 3 bits read from input data port + */ + +#define READ_EEPROM 8 +/** + * Params: + * wValue: offset to begin reading EEPROM + * + * Answer: EEPROM bytes + */ + +#define WRITE_EEPROM 9 +/** + * Params: + * wValue: offset to EEPROM byte + * wIndex: byte to write + * + * Answer: empty + */ + +#define SEND_RS232 10 +/** + * Params: + * wValue: byte to send + * + * Answer: empty + */ + +#define RECV_RS232 11 +/** + * Params: none + * + * Answer: byte received + */ + +#define SET_RS232_BAUD 12 +/** + * Params: + * wValue: byte to write to UART bit rate register (UBRR) + * + * Answer: empty + */ + +#define GET_RS232_BAUD 13 +/** + * Params: none + * + * Answer: byte read from UART bit rate register (UBRR) + */ + + +/* data structure for each usb remote */ +struct igorplug { + + /* usb */ + struct usb_device *usbdev; + int devnum; + + unsigned char *buf_in; + unsigned int len_in; + int in_space; + struct timeval last_time; + + dma_addr_t dma_in; + + /* lirc */ + struct lirc_driver *d; + + /* handle sending (init strings) */ + int send_flags; +}; + +static int unregister_from_lirc(struct igorplug *ir) +{ + struct lirc_driver *d; + int devnum; + + if (!ir) { + printk(KERN_ERR "%s: called with NULL device struct!\n", + __func__); + return -EINVAL; + } + + devnum = ir->devnum; + d = ir->d; + + if (!d) { + printk(KERN_ERR "%s: called with NULL lirc driver struct!\n", + __func__); + return -EINVAL; + } + + dprintk(DRIVER_NAME "[%d]: calling lirc_unregister_driver\n", devnum); + lirc_unregister_driver(d->minor); + + kfree(d); + ir->d = NULL; + kfree(ir); + + return devnum; +} + +static int set_use_inc(void *data) +{ + struct igorplug *ir = data; + + if (!ir) { + printk(DRIVER_NAME "[?]: set_use_inc called with no context\n"); + return -EIO; + } + + dprintk(DRIVER_NAME "[%d]: set use inc\n", ir->devnum); + + if (!ir->usbdev) + return -ENODEV; + + return 0; +} + +static void set_use_dec(void *data) +{ + struct igorplug *ir = data; + + if (!ir) { + printk(DRIVER_NAME "[?]: set_use_dec called with no context\n"); + return; + } + + dprintk(DRIVER_NAME "[%d]: set use dec\n", ir->devnum); +} + +static void send_fragment(struct igorplug *ir, struct lirc_buffer *buf, + int i, int max) +{ + int code; + + /* MODE2: pulse/space (PULSE_BIT) in 1us units */ + while (i < max) { + /* 1 Igor-tick = 85.333333 us */ + code = (unsigned int)ir->buf_in[i] * 85 + + (unsigned int)ir->buf_in[i] / 3; + ir->last_time.tv_usec += code; + if (ir->in_space) + code |= PULSE_BIT; + lirc_buffer_write(buf, (unsigned char *)&code); + /* 1 chunk = CODE_LENGTH bytes */ + ir->in_space ^= 1; + ++i; + } +} + +/** + * Called in user context. + * return 0 if data was added to the buffer and + * -ENODATA if none was available. This should add some number of bits + * evenly divisible by code_length to the buffer + */ +static int igorplugusb_remote_poll(void *data, struct lirc_buffer *buf) +{ + int ret; + struct igorplug *ir = (struct igorplug *)data; + + if (!ir || !ir->usbdev) /* Has the device been removed? */ + return -ENODEV; + + memset(ir->buf_in, 0, ir->len_in); + + ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0), + GET_INFRACODE, USB_TYPE_VENDOR | USB_DIR_IN, + 0/* offset */, /*unused*/0, + ir->buf_in, ir->len_in, + /*timeout*/HZ * USB_CTRL_GET_TIMEOUT); + if (ret > 0) { + int code, timediff; + struct timeval now; + + /* ACK packet has 1 byte --> ignore */ + if (ret < DEVICE_HEADERLEN) + return -ENODATA; + + dprintk(DRIVER_NAME ": Got %d bytes. Header: %02x %02x %02x\n", + ret, ir->buf_in[0], ir->buf_in[1], ir->buf_in[2]); + + do_gettimeofday(&now); + timediff = now.tv_sec - ir->last_time.tv_sec; + if (timediff + 1 > PULSE_MASK / 1000000) + timediff = PULSE_MASK; + else { + timediff *= 1000000; + timediff += now.tv_usec - ir->last_time.tv_usec; + } + ir->last_time.tv_sec = now.tv_sec; + ir->last_time.tv_usec = now.tv_usec; + + /* create leading gap */ + code = timediff; + lirc_buffer_write(buf, (unsigned char *)&code); + ir->in_space = 1; /* next comes a pulse */ + + if (ir->buf_in[2] == 0) + send_fragment(ir, buf, DEVICE_HEADERLEN, ret); + else { + printk(KERN_WARNING DRIVER_NAME + "[%d]: Device buffer overrun.\n", ir->devnum); + /* HHHNNNNNNNNNNNOOOOOOOO H = header + <---[2]---> N = newer + <---------ret--------> O = older */ + ir->buf_in[2] %= ret - DEVICE_HEADERLEN; /* sanitize */ + /* keep even-ness to not desync pulse/pause */ + send_fragment(ir, buf, DEVICE_HEADERLEN + + ir->buf_in[2] - (ir->buf_in[2] & 1), ret); + send_fragment(ir, buf, DEVICE_HEADERLEN, + DEVICE_HEADERLEN + ir->buf_in[2]); + } + + ret = usb_control_msg( + ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0), + SET_INFRABUFFER_EMPTY, USB_TYPE_VENDOR|USB_DIR_IN, + /*unused*/0, /*unused*/0, + /*dummy*/ir->buf_in, /*dummy*/ir->len_in, + /*timeout*/HZ * USB_CTRL_GET_TIMEOUT); + if (ret < 0) + printk(DRIVER_NAME "[%d]: SET_INFRABUFFER_EMPTY: " + "error %d\n", ir->devnum, ret); + return 0; + } else if (ret < 0) + printk(DRIVER_NAME "[%d]: GET_INFRACODE: error %d\n", + ir->devnum, ret); + + return -ENODATA; +} + + + +static int igorplugusb_remote_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = NULL; + struct usb_host_interface *idesc = NULL; + struct usb_endpoint_descriptor *ep; + struct igorplug *ir = NULL; + struct lirc_driver *driver = NULL; + int devnum, pipe, maxp; + int minor = 0; + char buf[63], name[128] = ""; + int mem_failure = 0; + int ret; + + dprintk(DRIVER_NAME ": usb probe called.\n"); + + dev = interface_to_usbdev(intf); + + idesc = intf->cur_altsetting; + + if (idesc->desc.bNumEndpoints != 1) + return -ENODEV; + + ep = &idesc->endpoint->desc; + if (((ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + != USB_DIR_IN) + || (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + != USB_ENDPOINT_XFER_CONTROL) + return -ENODEV; + + pipe = usb_rcvctrlpipe(dev, ep->bEndpointAddress); + devnum = dev->devnum; + maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); + + dprintk(DRIVER_NAME "[%d]: bytes_in_key=%zu maxp=%d\n", + devnum, CODE_LENGTH, maxp); + + mem_failure = 0; + ir = kzalloc(sizeof(struct igorplug), GFP_KERNEL); + if (!ir) { + mem_failure = 1; + goto mem_failure_switch; + } + driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); + if (!driver) { + mem_failure = 2; + goto mem_failure_switch; + } + + ir->buf_in = usb_alloc_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN, + GFP_ATOMIC, &ir->dma_in); + if (!ir->buf_in) { + mem_failure = 3; + goto mem_failure_switch; + } + + strcpy(driver->name, DRIVER_NAME " "); + driver->minor = -1; + driver->code_length = CODE_LENGTH * 8; /* in bits */ + driver->features = LIRC_CAN_REC_MODE2; + driver->data = ir; + driver->chunk_size = CODE_LENGTH; + driver->buffer_size = DEVICE_BUFLEN + ADDITIONAL_LIRC_BYTES; + driver->set_use_inc = &set_use_inc; + driver->set_use_dec = &set_use_dec; + driver->sample_rate = sample_rate; /* per second */ + driver->add_to_buf = &igorplugusb_remote_poll; + driver->dev = &intf->dev; + driver->owner = THIS_MODULE; + + minor = lirc_register_driver(driver); + if (minor < 0) + mem_failure = 9; + +mem_failure_switch: + + switch (mem_failure) { + case 9: + usb_free_coherent(dev, DEVICE_BUFLEN + DEVICE_HEADERLEN, + ir->buf_in, ir->dma_in); + case 3: + kfree(driver); + case 2: + kfree(ir); + case 1: + printk(DRIVER_NAME "[%d]: out of memory (code=%d)\n", + devnum, mem_failure); + return -ENOMEM; + } + + driver->minor = minor; + ir->d = driver; + ir->devnum = devnum; + ir->usbdev = dev; + ir->len_in = DEVICE_BUFLEN + DEVICE_HEADERLEN; + ir->in_space = 1; /* First mode2 event is a space. */ + do_gettimeofday(&ir->last_time); + + if (dev->descriptor.iManufacturer + && usb_string(dev, dev->descriptor.iManufacturer, + buf, sizeof(buf)) > 0) + strlcpy(name, buf, sizeof(name)); + if (dev->descriptor.iProduct + && usb_string(dev, dev->descriptor.iProduct, buf, sizeof(buf)) > 0) + snprintf(name + strlen(name), sizeof(name) - strlen(name), + " %s", buf); + printk(DRIVER_NAME "[%d]: %s on usb%d:%d\n", devnum, name, + dev->bus->busnum, devnum); + + /* clear device buffer */ + ret = usb_control_msg(ir->usbdev, usb_rcvctrlpipe(ir->usbdev, 0), + SET_INFRABUFFER_EMPTY, USB_TYPE_VENDOR|USB_DIR_IN, + /*unused*/0, /*unused*/0, + /*dummy*/ir->buf_in, /*dummy*/ir->len_in, + /*timeout*/HZ * USB_CTRL_GET_TIMEOUT); + if (ret < 0) + printk(DRIVER_NAME "[%d]: SET_INFRABUFFER_EMPTY: error %d\n", + devnum, ret); + + usb_set_intfdata(intf, ir); + return 0; +} + + +static void igorplugusb_remote_disconnect(struct usb_interface *intf) +{ + struct usb_device *usbdev = interface_to_usbdev(intf); + struct igorplug *ir = usb_get_intfdata(intf); + struct device *dev = &intf->dev; + int devnum; + + usb_set_intfdata(intf, NULL); + + if (!ir || !ir->d) + return; + + ir->usbdev = NULL; + + usb_free_coherent(usbdev, ir->len_in, ir->buf_in, ir->dma_in); + + devnum = unregister_from_lirc(ir); + + dev_info(dev, DRIVER_NAME "[%d]: %s done\n", devnum, __func__); +} + +static struct usb_device_id igorplugusb_remote_id_table[] = { + /* Igor Plug USB (Atmel's Manufact. ID) */ + { USB_DEVICE(0x03eb, 0x0002) }, + /* Fit PC2 Infrared Adapter */ + { USB_DEVICE(0x03eb, 0x21fe) }, + + /* Terminating entry */ + { } +}; + +static struct usb_driver igorplugusb_remote_driver = { + .name = DRIVER_NAME, + .probe = igorplugusb_remote_probe, + .disconnect = igorplugusb_remote_disconnect, + .id_table = igorplugusb_remote_id_table +}; + +module_usb_driver(igorplugusb_remote_driver); + +#include +MODULE_INFO(vermagic, VERMAGIC_STRING); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(usb, igorplugusb_remote_id_table); + +module_param(sample_rate, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(sample_rate, "Sampling rate in Hz (default: 100)"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); diff --git a/drivers/staging/media/lirc/lirc_imon.c b/drivers/staging/media/lirc/lirc_imon.c new file mode 100644 index 00000000..5f7f8cd3 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_imon.c @@ -0,0 +1,1028 @@ +/* + * lirc_imon.c: LIRC/VFD/LCD driver for SoundGraph iMON IR/VFD/LCD + * including the iMON PAD model + * + * Copyright(C) 2004 Venky Raju(dev@venky.ws) + * Copyright(C) 2009 Jarod Wilson + * + * lirc_imon is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#define MOD_AUTHOR "Venky Raju " +#define MOD_DESC "Driver for SoundGraph iMON MultiMedia IR/Display" +#define MOD_NAME "lirc_imon" +#define MOD_VERSION "0.8" + +#define DISPLAY_MINOR_BASE 144 +#define DEVICE_NAME "lcd%d" + +#define BUF_CHUNK_SIZE 4 +#define BUF_SIZE 128 + +#define BIT_DURATION 250 /* each bit received is 250us */ + +/*** P R O T O T Y P E S ***/ + +/* USB Callback prototypes */ +static int imon_probe(struct usb_interface *interface, + const struct usb_device_id *id); +static void imon_disconnect(struct usb_interface *interface); +static void usb_rx_callback(struct urb *urb); +static void usb_tx_callback(struct urb *urb); + +/* suspend/resume support */ +static int imon_resume(struct usb_interface *intf); +static int imon_suspend(struct usb_interface *intf, pm_message_t message); + +/* Display file_operations function prototypes */ +static int display_open(struct inode *inode, struct file *file); +static int display_close(struct inode *inode, struct file *file); + +/* VFD write operation */ +static ssize_t vfd_write(struct file *file, const char __user *buf, + size_t n_bytes, loff_t *pos); + +/* LIRC driver function prototypes */ +static int ir_open(void *data); +static void ir_close(void *data); + +/* Driver init/exit prototypes */ +static int __init imon_init(void); +static void __exit imon_exit(void); + +/*** G L O B A L S ***/ +#define IMON_DATA_BUF_SZ 35 + +struct imon_context { + struct usb_device *usbdev; + /* Newer devices have two interfaces */ + int display; /* not all controllers do */ + int display_isopen; /* display port has been opened */ + int ir_isopen; /* IR port open */ + int dev_present; /* USB device presence */ + struct mutex ctx_lock; /* to lock this object */ + wait_queue_head_t remove_ok; /* For unexpected USB disconnects */ + + int vfd_proto_6p; /* some VFD require a 6th packet */ + + struct lirc_driver *driver; + struct usb_endpoint_descriptor *rx_endpoint; + struct usb_endpoint_descriptor *tx_endpoint; + struct urb *rx_urb; + struct urb *tx_urb; + unsigned char usb_rx_buf[8]; + unsigned char usb_tx_buf[8]; + + struct rx_data { + int count; /* length of 0 or 1 sequence */ + int prev_bit; /* logic level of sequence */ + int initial_space; /* initial space flag */ + } rx; + + struct tx_t { + unsigned char data_buf[IMON_DATA_BUF_SZ]; /* user data buffer */ + struct completion finished; /* wait for write to finish */ + atomic_t busy; /* write in progress */ + int status; /* status of tx completion */ + } tx; +}; + +static const struct file_operations display_fops = { + .owner = THIS_MODULE, + .open = &display_open, + .write = &vfd_write, + .release = &display_close, + .llseek = noop_llseek, +}; + +/* + * USB Device ID for iMON USB Control Boards + * + * The Windows drivers contain 6 different inf files, more or less one for + * each new device until the 0x0034-0x0046 devices, which all use the same + * driver. Some of the devices in the 34-46 range haven't been definitively + * identified yet. Early devices have either a TriGem Computer, Inc. or a + * Samsung vendor ID (0x0aa8 and 0x04e8 respectively), while all later + * devices use the SoundGraph vendor ID (0x15c2). + */ +static struct usb_device_id imon_usb_id_table[] = { + /* TriGem iMON (IR only) -- TG_iMON.inf */ + { USB_DEVICE(0x0aa8, 0x8001) }, + + /* SoundGraph iMON (IR only) -- sg_imon.inf */ + { USB_DEVICE(0x04e8, 0xff30) }, + + /* SoundGraph iMON VFD (IR & VFD) -- iMON_VFD.inf */ + { USB_DEVICE(0x0aa8, 0xffda) }, + + /* SoundGraph iMON SS (IR & VFD) -- iMON_SS.inf */ + { USB_DEVICE(0x15c2, 0xffda) }, + + {} +}; + +/* Some iMON VFD models requires a 6th packet for VFD writes */ +static struct usb_device_id vfd_proto_6p_list[] = { + { USB_DEVICE(0x15c2, 0xffda) }, + {} +}; + +/* Some iMON devices have no lcd/vfd, don't set one up */ +static struct usb_device_id ir_only_list[] = { + { USB_DEVICE(0x0aa8, 0x8001) }, + { USB_DEVICE(0x04e8, 0xff30) }, + {} +}; + +/* USB Device data */ +static struct usb_driver imon_driver = { + .name = MOD_NAME, + .probe = imon_probe, + .disconnect = imon_disconnect, + .suspend = imon_suspend, + .resume = imon_resume, + .id_table = imon_usb_id_table, +}; + +static struct usb_class_driver imon_class = { + .name = DEVICE_NAME, + .fops = &display_fops, + .minor_base = DISPLAY_MINOR_BASE, +}; + +/* to prevent races between open() and disconnect(), probing, etc */ +static DEFINE_MUTEX(driver_lock); + +static int debug; + +/*** M O D U L E C O D E ***/ + +MODULE_AUTHOR(MOD_AUTHOR); +MODULE_DESCRIPTION(MOD_DESC); +MODULE_VERSION(MOD_VERSION); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(usb, imon_usb_id_table); +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes(default: no)"); + +static void free_imon_context(struct imon_context *context) +{ + struct device *dev = context->driver->dev; + usb_free_urb(context->tx_urb); + usb_free_urb(context->rx_urb); + lirc_buffer_free(context->driver->rbuf); + kfree(context->driver->rbuf); + kfree(context->driver); + kfree(context); + + dev_dbg(dev, "%s: iMON context freed\n", __func__); +} + +static void deregister_from_lirc(struct imon_context *context) +{ + int retval; + int minor = context->driver->minor; + + retval = lirc_unregister_driver(minor); + if (retval) + err("%s: unable to deregister from lirc(%d)", + __func__, retval); + else + printk(KERN_INFO MOD_NAME ": Deregistered iMON driver " + "(minor:%d)\n", minor); + +} + +/** + * Called when the Display device (e.g. /dev/lcd0) + * is opened by the application. + */ +static int display_open(struct inode *inode, struct file *file) +{ + struct usb_interface *interface; + struct imon_context *context = NULL; + int subminor; + int retval = 0; + + /* prevent races with disconnect */ + mutex_lock(&driver_lock); + + subminor = iminor(inode); + interface = usb_find_interface(&imon_driver, subminor); + if (!interface) { + err("%s: could not find interface for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + context = usb_get_intfdata(interface); + + if (!context) { + err("%s: no context found for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + mutex_lock(&context->ctx_lock); + + if (!context->display) { + err("%s: display not supported by device", __func__); + retval = -ENODEV; + } else if (context->display_isopen) { + err("%s: display port is already open", __func__); + retval = -EBUSY; + } else { + context->display_isopen = 1; + file->private_data = context; + dev_info(context->driver->dev, "display port opened\n"); + } + + mutex_unlock(&context->ctx_lock); + +exit: + mutex_unlock(&driver_lock); + return retval; +} + +/** + * Called when the display device (e.g. /dev/lcd0) + * is closed by the application. + */ +static int display_close(struct inode *inode, struct file *file) +{ + struct imon_context *context = NULL; + int retval = 0; + + context = file->private_data; + + if (!context) { + err("%s: no context for device", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->display) { + err("%s: display not supported by device", __func__); + retval = -ENODEV; + } else if (!context->display_isopen) { + err("%s: display is not open", __func__); + retval = -EIO; + } else { + context->display_isopen = 0; + dev_info(context->driver->dev, "display port closed\n"); + if (!context->dev_present && !context->ir_isopen) { + /* + * Device disconnected before close and IR port is not + * open. If IR port is open, context will be deleted by + * ir_close. + */ + mutex_unlock(&context->ctx_lock); + free_imon_context(context); + return retval; + } + } + + mutex_unlock(&context->ctx_lock); + return retval; +} + +/** + * Sends a packet to the device -- this function must be called + * with context->ctx_lock held. + */ +static int send_packet(struct imon_context *context) +{ + unsigned int pipe; + int interval = 0; + int retval = 0; + + /* Check if we need to use control or interrupt urb */ + pipe = usb_sndintpipe(context->usbdev, + context->tx_endpoint->bEndpointAddress); + interval = context->tx_endpoint->bInterval; + + usb_fill_int_urb(context->tx_urb, context->usbdev, pipe, + context->usb_tx_buf, + sizeof(context->usb_tx_buf), + usb_tx_callback, context, interval); + + context->tx_urb->actual_length = 0; + + init_completion(&context->tx.finished); + atomic_set(&(context->tx.busy), 1); + + retval = usb_submit_urb(context->tx_urb, GFP_KERNEL); + if (retval) { + atomic_set(&(context->tx.busy), 0); + err("%s: error submitting urb(%d)", __func__, retval); + } else { + /* Wait for transmission to complete (or abort) */ + mutex_unlock(&context->ctx_lock); + retval = wait_for_completion_interruptible( + &context->tx.finished); + if (retval) + err("%s: task interrupted", __func__); + mutex_lock(&context->ctx_lock); + + retval = context->tx.status; + if (retval) + err("%s: packet tx failed (%d)", __func__, retval); + } + + return retval; +} + +/** + * Writes data to the VFD. The iMON VFD is 2x16 characters + * and requires data in 5 consecutive USB interrupt packets, + * each packet but the last carrying 7 bytes. + * + * I don't know if the VFD board supports features such as + * scrolling, clearing rows, blanking, etc. so at + * the caller must provide a full screen of data. If fewer + * than 32 bytes are provided spaces will be appended to + * generate a full screen. + */ +static ssize_t vfd_write(struct file *file, const char __user *buf, + size_t n_bytes, loff_t *pos) +{ + int i; + int offset; + int seq; + int retval = 0; + struct imon_context *context; + const unsigned char vfd_packet6[] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF }; + int *data_buf = NULL; + + context = file->private_data; + if (!context) { + err("%s: no context for device", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->dev_present) { + err("%s: no iMON device present", __func__); + retval = -ENODEV; + goto exit; + } + + if (n_bytes <= 0 || n_bytes > IMON_DATA_BUF_SZ - 3) { + err("%s: invalid payload size", __func__); + retval = -EINVAL; + goto exit; + } + + data_buf = memdup_user(buf, n_bytes); + if (IS_ERR(data_buf)) { + retval = PTR_ERR(data_buf); + goto exit; + } + + memcpy(context->tx.data_buf, data_buf, n_bytes); + + /* Pad with spaces */ + for (i = n_bytes; i < IMON_DATA_BUF_SZ - 3; ++i) + context->tx.data_buf[i] = ' '; + + for (i = IMON_DATA_BUF_SZ - 3; i < IMON_DATA_BUF_SZ; ++i) + context->tx.data_buf[i] = 0xFF; + + offset = 0; + seq = 0; + + do { + memcpy(context->usb_tx_buf, context->tx.data_buf + offset, 7); + context->usb_tx_buf[7] = (unsigned char) seq; + + retval = send_packet(context); + if (retval) { + err("%s: send packet failed for packet #%d", + __func__, seq/2); + goto exit; + } else { + seq += 2; + offset += 7; + } + + } while (offset < IMON_DATA_BUF_SZ); + + if (context->vfd_proto_6p) { + /* Send packet #6 */ + memcpy(context->usb_tx_buf, &vfd_packet6, sizeof(vfd_packet6)); + context->usb_tx_buf[7] = (unsigned char) seq; + retval = send_packet(context); + if (retval) + err("%s: send packet failed for packet #%d", + __func__, seq/2); + } + +exit: + mutex_unlock(&context->ctx_lock); + kfree(data_buf); + + return (!retval) ? n_bytes : retval; +} + +/** + * Callback function for USB core API: transmit data + */ +static void usb_tx_callback(struct urb *urb) +{ + struct imon_context *context; + + if (!urb) + return; + context = (struct imon_context *)urb->context; + if (!context) + return; + + context->tx.status = urb->status; + + /* notify waiters that write has finished */ + atomic_set(&context->tx.busy, 0); + complete(&context->tx.finished); + + return; +} + +/** + * Called by lirc_dev when the application opens /dev/lirc + */ +static int ir_open(void *data) +{ + int retval = 0; + struct imon_context *context; + + /* prevent races with disconnect */ + mutex_lock(&driver_lock); + + context = (struct imon_context *)data; + + /* initial IR protocol decode variables */ + context->rx.count = 0; + context->rx.initial_space = 1; + context->rx.prev_bit = 0; + + context->ir_isopen = 1; + dev_info(context->driver->dev, "IR port opened\n"); + + mutex_unlock(&driver_lock); + return retval; +} + +/** + * Called by lirc_dev when the application closes /dev/lirc + */ +static void ir_close(void *data) +{ + struct imon_context *context; + + context = (struct imon_context *)data; + if (!context) { + err("%s: no context for device", __func__); + return; + } + + mutex_lock(&context->ctx_lock); + + context->ir_isopen = 0; + dev_info(context->driver->dev, "IR port closed\n"); + + if (!context->dev_present) { + /* + * Device disconnected while IR port was still open. Driver + * was not deregistered at disconnect time, so do it now. + */ + deregister_from_lirc(context); + + if (!context->display_isopen) { + mutex_unlock(&context->ctx_lock); + free_imon_context(context); + return; + } + /* + * If display port is open, context will be deleted by + * display_close + */ + } + + mutex_unlock(&context->ctx_lock); + return; +} + +/** + * Convert bit count to time duration (in us) and submit + * the value to lirc_dev. + */ +static void submit_data(struct imon_context *context) +{ + unsigned char buf[4]; + int value = context->rx.count; + int i; + + dev_dbg(context->driver->dev, "submitting data to LIRC\n"); + + value *= BIT_DURATION; + value &= PULSE_MASK; + if (context->rx.prev_bit) + value |= PULSE_BIT; + + for (i = 0; i < 4; ++i) + buf[i] = value>>(i*8); + + lirc_buffer_write(context->driver->rbuf, buf); + wake_up(&context->driver->rbuf->wait_poll); + return; +} + +static inline int tv2int(const struct timeval *a, const struct timeval *b) +{ + int usecs = 0; + int sec = 0; + + if (b->tv_usec > a->tv_usec) { + usecs = 1000000; + sec--; + } + + usecs += a->tv_usec - b->tv_usec; + + sec += a->tv_sec - b->tv_sec; + sec *= 1000; + usecs /= 1000; + sec += usecs; + + if (sec < 0) + sec = 1000; + + return sec; +} + +/** + * Process the incoming packet + */ +static void imon_incoming_packet(struct imon_context *context, + struct urb *urb, int intf) +{ + int len = urb->actual_length; + unsigned char *buf = urb->transfer_buffer; + struct device *dev = context->driver->dev; + int octet, bit; + unsigned char mask; + int i; + + /* + * just bail out if no listening IR client + */ + if (!context->ir_isopen) + return; + + if (len != 8) { + dev_warn(dev, "imon %s: invalid incoming packet " + "size (len = %d, intf%d)\n", __func__, len, intf); + return; + } + + if (debug) { + printk(KERN_INFO "raw packet: "); + for (i = 0; i < len; ++i) + printk("%02x ", buf[i]); + printk("\n"); + } + + /* + * Translate received data to pulse and space lengths. + * Received data is active low, i.e. pulses are 0 and + * spaces are 1. + * + * My original algorithm was essentially similar to + * Changwoo Ryu's with the exception that he switched + * the incoming bits to active high and also fed an + * initial space to LIRC at the start of a new sequence + * if the previous bit was a pulse. + * + * I've decided to adopt his algorithm. + */ + + if (buf[7] == 1 && context->rx.initial_space) { + /* LIRC requires a leading space */ + context->rx.prev_bit = 0; + context->rx.count = 4; + submit_data(context); + context->rx.count = 0; + } + + for (octet = 0; octet < 5; ++octet) { + mask = 0x80; + for (bit = 0; bit < 8; ++bit) { + int curr_bit = !(buf[octet] & mask); + if (curr_bit != context->rx.prev_bit) { + if (context->rx.count) { + submit_data(context); + context->rx.count = 0; + } + context->rx.prev_bit = curr_bit; + } + ++context->rx.count; + mask >>= 1; + } + } + + if (buf[7] == 10) { + if (context->rx.count) { + submit_data(context); + context->rx.count = 0; + } + context->rx.initial_space = context->rx.prev_bit; + } +} + +/** + * Callback function for USB core API: receive data + */ +static void usb_rx_callback(struct urb *urb) +{ + struct imon_context *context; + int intfnum = 0; + + if (!urb) + return; + + context = (struct imon_context *)urb->context; + if (!context) + return; + + switch (urb->status) { + case -ENOENT: /* usbcore unlink successful! */ + return; + + case 0: + imon_incoming_packet(context, urb, intfnum); + break; + + default: + dev_warn(context->driver->dev, "imon %s: status(%d): ignored\n", + __func__, urb->status); + break; + } + + usb_submit_urb(context->rx_urb, GFP_ATOMIC); + + return; +} + +/** + * Callback function for USB core API: Probe + */ +static int imon_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usbdev = NULL; + struct usb_host_interface *iface_desc = NULL; + struct usb_endpoint_descriptor *rx_endpoint = NULL; + struct usb_endpoint_descriptor *tx_endpoint = NULL; + struct urb *rx_urb = NULL; + struct urb *tx_urb = NULL; + struct lirc_driver *driver = NULL; + struct lirc_buffer *rbuf = NULL; + struct device *dev = &interface->dev; + int ifnum; + int lirc_minor = 0; + int num_endpts; + int retval = 0; + int display_ep_found = 0; + int ir_ep_found = 0; + int alloc_status = 0; + int vfd_proto_6p = 0; + struct imon_context *context = NULL; + int i; + u16 vendor, product; + + /* prevent races probing devices w/multiple interfaces */ + mutex_lock(&driver_lock); + + context = kzalloc(sizeof(struct imon_context), GFP_KERNEL); + if (!context) { + err("%s: kzalloc failed for context", __func__); + alloc_status = 1; + goto alloc_status_switch; + } + + /* + * Try to auto-detect the type of display if the user hasn't set + * it by hand via the display_type modparam. Default is VFD. + */ + if (usb_match_id(interface, ir_only_list)) + context->display = 0; + else + context->display = 1; + + usbdev = usb_get_dev(interface_to_usbdev(interface)); + iface_desc = interface->cur_altsetting; + num_endpts = iface_desc->desc.bNumEndpoints; + ifnum = iface_desc->desc.bInterfaceNumber; + vendor = le16_to_cpu(usbdev->descriptor.idVendor); + product = le16_to_cpu(usbdev->descriptor.idProduct); + + dev_dbg(dev, "%s: found iMON device (%04x:%04x, intf%d)\n", + __func__, vendor, product, ifnum); + + /* + * Scan the endpoint list and set: + * first input endpoint = IR endpoint + * first output endpoint = display endpoint + */ + for (i = 0; i < num_endpts && !(ir_ep_found && display_ep_found); ++i) { + struct usb_endpoint_descriptor *ep; + int ep_dir; + int ep_type; + ep = &iface_desc->endpoint[i].desc; + ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK; + ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + if (!ir_ep_found && + ep_dir == USB_DIR_IN && + ep_type == USB_ENDPOINT_XFER_INT) { + + rx_endpoint = ep; + ir_ep_found = 1; + dev_dbg(dev, "%s: found IR endpoint\n", __func__); + + } else if (!display_ep_found && ep_dir == USB_DIR_OUT && + ep_type == USB_ENDPOINT_XFER_INT) { + tx_endpoint = ep; + display_ep_found = 1; + dev_dbg(dev, "%s: found display endpoint\n", __func__); + } + } + + /* + * Some iMON receivers have no display. Unfortunately, it seems + * that SoundGraph recycles device IDs between devices both with + * and without... :\ + */ + if (context->display == 0) { + display_ep_found = 0; + dev_dbg(dev, "%s: device has no display\n", __func__); + } + + /* Input endpoint is mandatory */ + if (!ir_ep_found) { + err("%s: no valid input (IR) endpoint found.", __func__); + retval = -ENODEV; + alloc_status = 2; + goto alloc_status_switch; + } + + /* Determine if display requires 6 packets */ + if (display_ep_found) { + if (usb_match_id(interface, vfd_proto_6p_list)) + vfd_proto_6p = 1; + + dev_dbg(dev, "%s: vfd_proto_6p: %d\n", + __func__, vfd_proto_6p); + } + + driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); + if (!driver) { + err("%s: kzalloc failed for lirc_driver", __func__); + alloc_status = 2; + goto alloc_status_switch; + } + rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); + if (!rbuf) { + err("%s: kmalloc failed for lirc_buffer", __func__); + alloc_status = 3; + goto alloc_status_switch; + } + if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) { + err("%s: lirc_buffer_init failed", __func__); + alloc_status = 4; + goto alloc_status_switch; + } + rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rx_urb) { + err("%s: usb_alloc_urb failed for IR urb", __func__); + alloc_status = 5; + goto alloc_status_switch; + } + tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tx_urb) { + err("%s: usb_alloc_urb failed for display urb", + __func__); + alloc_status = 6; + goto alloc_status_switch; + } + + mutex_init(&context->ctx_lock); + context->vfd_proto_6p = vfd_proto_6p; + + strcpy(driver->name, MOD_NAME); + driver->minor = -1; + driver->code_length = BUF_CHUNK_SIZE * 8; + driver->sample_rate = 0; + driver->features = LIRC_CAN_REC_MODE2; + driver->data = context; + driver->rbuf = rbuf; + driver->set_use_inc = ir_open; + driver->set_use_dec = ir_close; + driver->dev = &interface->dev; + driver->owner = THIS_MODULE; + + mutex_lock(&context->ctx_lock); + + context->driver = driver; + /* start out in keyboard mode */ + + lirc_minor = lirc_register_driver(driver); + if (lirc_minor < 0) { + err("%s: lirc_register_driver failed", __func__); + alloc_status = 7; + goto unlock; + } else + dev_info(dev, "Registered iMON driver " + "(lirc minor: %d)\n", lirc_minor); + + /* Needed while unregistering! */ + driver->minor = lirc_minor; + + context->usbdev = usbdev; + context->dev_present = 1; + context->rx_endpoint = rx_endpoint; + context->rx_urb = rx_urb; + + /* + * tx is used to send characters to lcd/vfd, associate RF + * remotes, set IR protocol, and maybe more... + */ + context->tx_endpoint = tx_endpoint; + context->tx_urb = tx_urb; + + if (display_ep_found) + context->display = 1; + + usb_fill_int_urb(context->rx_urb, context->usbdev, + usb_rcvintpipe(context->usbdev, + context->rx_endpoint->bEndpointAddress), + context->usb_rx_buf, sizeof(context->usb_rx_buf), + usb_rx_callback, context, + context->rx_endpoint->bInterval); + + retval = usb_submit_urb(context->rx_urb, GFP_KERNEL); + + if (retval) { + err("%s: usb_submit_urb failed for intf0 (%d)", + __func__, retval); + mutex_unlock(&context->ctx_lock); + goto exit; + } + + usb_set_intfdata(interface, context); + + if (context->display && ifnum == 0) { + dev_dbg(dev, "%s: Registering iMON display with sysfs\n", + __func__); + + if (usb_register_dev(interface, &imon_class)) { + /* Not a fatal error, so ignore */ + dev_info(dev, "%s: could not get a minor number for " + "display\n", __func__); + } + } + + dev_info(dev, "iMON device (%04x:%04x, intf%d) on " + "usb<%d:%d> initialized\n", vendor, product, ifnum, + usbdev->bus->busnum, usbdev->devnum); + +unlock: + mutex_unlock(&context->ctx_lock); +alloc_status_switch: + + switch (alloc_status) { + case 7: + usb_free_urb(tx_urb); + case 6: + usb_free_urb(rx_urb); + case 5: + if (rbuf) + lirc_buffer_free(rbuf); + case 4: + kfree(rbuf); + case 3: + kfree(driver); + case 2: + kfree(context); + context = NULL; + case 1: + if (retval != -ENODEV) + retval = -ENOMEM; + break; + case 0: + retval = 0; + } + +exit: + mutex_unlock(&driver_lock); + + return retval; +} + +/** + * Callback function for USB core API: disconnect + */ +static void imon_disconnect(struct usb_interface *interface) +{ + struct imon_context *context; + int ifnum; + + /* prevent races with ir_open()/display_open() */ + mutex_lock(&driver_lock); + + context = usb_get_intfdata(interface); + ifnum = interface->cur_altsetting->desc.bInterfaceNumber; + + mutex_lock(&context->ctx_lock); + + usb_set_intfdata(interface, NULL); + + /* Abort ongoing write */ + if (atomic_read(&context->tx.busy)) { + usb_kill_urb(context->tx_urb); + complete_all(&context->tx.finished); + } + + context->dev_present = 0; + usb_kill_urb(context->rx_urb); + if (context->display) + usb_deregister_dev(interface, &imon_class); + + if (!context->ir_isopen && !context->dev_present) { + deregister_from_lirc(context); + mutex_unlock(&context->ctx_lock); + if (!context->display_isopen) + free_imon_context(context); + } else + mutex_unlock(&context->ctx_lock); + + mutex_unlock(&driver_lock); + + printk(KERN_INFO "%s: iMON device (intf%d) disconnected\n", + __func__, ifnum); +} + +static int imon_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct imon_context *context = usb_get_intfdata(intf); + + usb_kill_urb(context->rx_urb); + + return 0; +} + +static int imon_resume(struct usb_interface *intf) +{ + int rc = 0; + struct imon_context *context = usb_get_intfdata(intf); + + usb_fill_int_urb(context->rx_urb, context->usbdev, + usb_rcvintpipe(context->usbdev, + context->rx_endpoint->bEndpointAddress), + context->usb_rx_buf, sizeof(context->usb_rx_buf), + usb_rx_callback, context, + context->rx_endpoint->bInterval); + + rc = usb_submit_urb(context->rx_urb, GFP_ATOMIC); + + return rc; +} + +module_usb_driver(imon_driver); diff --git a/drivers/staging/media/lirc/lirc_parallel.c b/drivers/staging/media/lirc/lirc_parallel.c new file mode 100644 index 00000000..dd2bca7b --- /dev/null +++ b/drivers/staging/media/lirc/lirc_parallel.c @@ -0,0 +1,755 @@ +/* + * lirc_parallel.c + * + * lirc_parallel - device driver for infra-red signal receiving and + * transmitting unit built by the author + * + * Copyright (C) 1998 Christoph Bartelmus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/*** Includes ***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "lirc_parallel.h" + +#define LIRC_DRIVER_NAME "lirc_parallel" + +#ifndef LIRC_IRQ +#define LIRC_IRQ 7 +#endif +#ifndef LIRC_PORT +#define LIRC_PORT 0x378 +#endif +#ifndef LIRC_TIMER +#define LIRC_TIMER 65536 +#endif + +/*** Global Variables ***/ + +static bool debug; +static bool check_pselecd; + +unsigned int irq = LIRC_IRQ; +unsigned int io = LIRC_PORT; +#ifdef LIRC_TIMER +unsigned int timer; +unsigned int default_timer = LIRC_TIMER; +#endif + +#define RBUF_SIZE (256) /* this must be a power of 2 larger than 1 */ + +static int rbuf[RBUF_SIZE]; + +DECLARE_WAIT_QUEUE_HEAD(lirc_wait); + +unsigned int rptr; +unsigned int wptr; +unsigned int lost_irqs; +int is_open; + +struct parport *pport; +struct pardevice *ppdevice; +int is_claimed; + +unsigned int tx_mask = 1; + +/*** Internal Functions ***/ + +static unsigned int in(int offset) +{ + switch (offset) { + case LIRC_LP_BASE: + return parport_read_data(pport); + case LIRC_LP_STATUS: + return parport_read_status(pport); + case LIRC_LP_CONTROL: + return parport_read_control(pport); + } + return 0; /* make compiler happy */ +} + +static void out(int offset, int value) +{ + switch (offset) { + case LIRC_LP_BASE: + parport_write_data(pport, value); + break; + case LIRC_LP_CONTROL: + parport_write_control(pport, value); + break; + case LIRC_LP_STATUS: + printk(KERN_INFO "%s: attempt to write to status register\n", + LIRC_DRIVER_NAME); + break; + } +} + +static unsigned int lirc_get_timer(void) +{ + return in(LIRC_PORT_TIMER) & LIRC_PORT_TIMER_BIT; +} + +static unsigned int lirc_get_signal(void) +{ + return in(LIRC_PORT_SIGNAL) & LIRC_PORT_SIGNAL_BIT; +} + +static void lirc_on(void) +{ + out(LIRC_PORT_DATA, tx_mask); +} + +static void lirc_off(void) +{ + out(LIRC_PORT_DATA, 0); +} + +static unsigned int init_lirc_timer(void) +{ + struct timeval tv, now; + unsigned int level, newlevel, timeelapsed, newtimer; + int count = 0; + + do_gettimeofday(&tv); + tv.tv_sec++; /* wait max. 1 sec. */ + level = lirc_get_timer(); + do { + newlevel = lirc_get_timer(); + if (level == 0 && newlevel != 0) + count++; + level = newlevel; + do_gettimeofday(&now); + } while (count < 1000 && (now.tv_sec < tv.tv_sec + || (now.tv_sec == tv.tv_sec + && now.tv_usec < tv.tv_usec))); + + timeelapsed = ((now.tv_sec + 1 - tv.tv_sec)*1000000 + + (now.tv_usec - tv.tv_usec)); + if (count >= 1000 && timeelapsed > 0) { + if (default_timer == 0) { + /* autodetect timer */ + newtimer = (1000000*count)/timeelapsed; + printk(KERN_INFO "%s: %u Hz timer detected\n", + LIRC_DRIVER_NAME, newtimer); + return newtimer; + } else { + newtimer = (1000000*count)/timeelapsed; + if (abs(newtimer - default_timer) > default_timer/10) { + /* bad timer */ + printk(KERN_NOTICE "%s: bad timer: %u Hz\n", + LIRC_DRIVER_NAME, newtimer); + printk(KERN_NOTICE "%s: using default timer: " + "%u Hz\n", + LIRC_DRIVER_NAME, default_timer); + return default_timer; + } else { + printk(KERN_INFO "%s: %u Hz timer detected\n", + LIRC_DRIVER_NAME, newtimer); + return newtimer; /* use detected value */ + } + } + } else { + printk(KERN_NOTICE "%s: no timer detected\n", LIRC_DRIVER_NAME); + return 0; + } +} + +static int lirc_claim(void) +{ + if (parport_claim(ppdevice) != 0) { + printk(KERN_WARNING "%s: could not claim port\n", + LIRC_DRIVER_NAME); + printk(KERN_WARNING "%s: waiting for port becoming available" + "\n", LIRC_DRIVER_NAME); + if (parport_claim_or_block(ppdevice) < 0) { + printk(KERN_NOTICE "%s: could not claim port, giving" + " up\n", LIRC_DRIVER_NAME); + return 0; + } + } + out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP); + is_claimed = 1; + return 1; +} + +/*** interrupt handler ***/ + +static void rbuf_write(int signal) +{ + unsigned int nwptr; + + nwptr = (wptr + 1) & (RBUF_SIZE - 1); + if (nwptr == rptr) { + /* no new signals will be accepted */ + lost_irqs++; + printk(KERN_NOTICE "%s: buffer overrun\n", LIRC_DRIVER_NAME); + return; + } + rbuf[wptr] = signal; + wptr = nwptr; +} + +static void irq_handler(void *blah) +{ + struct timeval tv; + static struct timeval lasttv; + static int init; + long signal; + int data; + unsigned int level, newlevel; + unsigned int timeout; + + if (!is_open) + return; + + if (!is_claimed) + return; + +#if 0 + /* disable interrupt */ + disable_irq(irq); + out(LIRC_PORT_IRQ, in(LIRC_PORT_IRQ) & (~LP_PINTEN)); +#endif + if (check_pselecd && (in(1) & LP_PSELECD)) + return; + +#ifdef LIRC_TIMER + if (init) { + do_gettimeofday(&tv); + + signal = tv.tv_sec - lasttv.tv_sec; + if (signal > 15) + /* really long time */ + data = PULSE_MASK; + else + data = (int) (signal*1000000 + + tv.tv_usec - lasttv.tv_usec + + LIRC_SFH506_DELAY); + + rbuf_write(data); /* space */ + } else { + if (timer == 0) { + /* + * wake up; we'll lose this signal, but it will be + * garbage if the device is turned on anyway + */ + timer = init_lirc_timer(); + /* enable_irq(irq); */ + return; + } + init = 1; + } + + timeout = timer/10; /* timeout after 1/10 sec. */ + signal = 1; + level = lirc_get_timer(); + do { + newlevel = lirc_get_timer(); + if (level == 0 && newlevel != 0) + signal++; + level = newlevel; + + /* giving up */ + if (signal > timeout + || (check_pselecd && (in(1) & LP_PSELECD))) { + signal = 0; + printk(KERN_NOTICE "%s: timeout\n", LIRC_DRIVER_NAME); + break; + } + } while (lirc_get_signal()); + + if (signal != 0) { + /* adjust value to usecs */ + __u64 helper; + + helper = ((__u64) signal)*1000000; + do_div(helper, timer); + signal = (long) helper; + + if (signal > LIRC_SFH506_DELAY) + data = signal - LIRC_SFH506_DELAY; + else + data = 1; + rbuf_write(PULSE_BIT|data); /* pulse */ + } + do_gettimeofday(&lasttv); +#else + /* add your code here */ +#endif + + wake_up_interruptible(&lirc_wait); + + /* enable interrupt */ + /* + enable_irq(irq); + out(LIRC_PORT_IRQ, in(LIRC_PORT_IRQ)|LP_PINTEN); + */ +} + +/*** file operations ***/ + +static loff_t lirc_lseek(struct file *filep, loff_t offset, int orig) +{ + return -ESPIPE; +} + +static ssize_t lirc_read(struct file *filep, char *buf, size_t n, loff_t *ppos) +{ + int result = 0; + int count = 0; + DECLARE_WAITQUEUE(wait, current); + + if (n % sizeof(int)) + return -EINVAL; + + add_wait_queue(&lirc_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + while (count < n) { + if (rptr != wptr) { + if (copy_to_user(buf+count, (char *) &rbuf[rptr], + sizeof(int))) { + result = -EFAULT; + break; + } + rptr = (rptr + 1) & (RBUF_SIZE - 1); + count += sizeof(int); + } else { + if (filep->f_flags & O_NONBLOCK) { + result = -EAGAIN; + break; + } + if (signal_pending(current)) { + result = -ERESTARTSYS; + break; + } + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + } + remove_wait_queue(&lirc_wait, &wait); + set_current_state(TASK_RUNNING); + return count ? count : result; +} + +static ssize_t lirc_write(struct file *filep, const char *buf, size_t n, + loff_t *ppos) +{ + int count; + unsigned int i; + unsigned int level, newlevel; + unsigned long flags; + int counttimer; + int *wbuf; + ssize_t ret; + + if (!is_claimed) + return -EBUSY; + + count = n / sizeof(int); + + if (n % sizeof(int) || count % 2 == 0) + return -EINVAL; + + wbuf = memdup_user(buf, n); + if (IS_ERR(wbuf)) + return PTR_ERR(wbuf); + +#ifdef LIRC_TIMER + if (timer == 0) { + /* try again if device is ready */ + timer = init_lirc_timer(); + if (timer == 0) { + ret = -EIO; + goto out; + } + } + + /* adjust values from usecs */ + for (i = 0; i < count; i++) { + __u64 helper; + + helper = ((__u64) wbuf[i])*timer; + do_div(helper, 1000000); + wbuf[i] = (int) helper; + } + + local_irq_save(flags); + i = 0; + while (i < count) { + level = lirc_get_timer(); + counttimer = 0; + lirc_on(); + do { + newlevel = lirc_get_timer(); + if (level == 0 && newlevel != 0) + counttimer++; + level = newlevel; + if (check_pselecd && (in(1) & LP_PSELECD)) { + lirc_off(); + local_irq_restore(flags); + ret = -EIO; + goto out; + } + } while (counttimer < wbuf[i]); + i++; + + lirc_off(); + if (i == count) + break; + counttimer = 0; + do { + newlevel = lirc_get_timer(); + if (level == 0 && newlevel != 0) + counttimer++; + level = newlevel; + if (check_pselecd && (in(1) & LP_PSELECD)) { + local_irq_restore(flags); + ret = -EIO; + goto out; + } + } while (counttimer < wbuf[i]); + i++; + } + local_irq_restore(flags); +#else + /* place code that handles write without external timer here */ +#endif + ret = n; +out: + kfree(wbuf); + + return ret; +} + +static unsigned int lirc_poll(struct file *file, poll_table *wait) +{ + poll_wait(file, &lirc_wait, wait); + if (rptr != wptr) + return POLLIN | POLLRDNORM; + return 0; +} + +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + int result; + __u32 features = LIRC_CAN_SET_TRANSMITTER_MASK | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2; + __u32 mode; + __u32 value; + + switch (cmd) { + case LIRC_GET_FEATURES: + result = put_user(features, (__u32 *) arg); + if (result) + return result; + break; + case LIRC_GET_SEND_MODE: + result = put_user(LIRC_MODE_PULSE, (__u32 *) arg); + if (result) + return result; + break; + case LIRC_GET_REC_MODE: + result = put_user(LIRC_MODE_MODE2, (__u32 *) arg); + if (result) + return result; + break; + case LIRC_SET_SEND_MODE: + result = get_user(mode, (__u32 *) arg); + if (result) + return result; + if (mode != LIRC_MODE_PULSE) + return -EINVAL; + break; + case LIRC_SET_REC_MODE: + result = get_user(mode, (__u32 *) arg); + if (result) + return result; + if (mode != LIRC_MODE_MODE2) + return -ENOSYS; + break; + case LIRC_SET_TRANSMITTER_MASK: + result = get_user(value, (__u32 *) arg); + if (result) + return result; + if ((value & LIRC_PARALLEL_TRANSMITTER_MASK) != value) + return LIRC_PARALLEL_MAX_TRANSMITTERS; + tx_mask = value; + break; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int lirc_open(struct inode *node, struct file *filep) +{ + if (is_open || !lirc_claim()) + return -EBUSY; + + parport_enable_irq(pport); + + /* init read ptr */ + rptr = 0; + wptr = 0; + lost_irqs = 0; + + is_open = 1; + return 0; +} + +static int lirc_close(struct inode *node, struct file *filep) +{ + if (is_claimed) { + is_claimed = 0; + parport_release(ppdevice); + } + is_open = 0; + return 0; +} + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .llseek = lirc_lseek, + .read = lirc_read, + .write = lirc_write, + .poll = lirc_poll, + .unlocked_ioctl = lirc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = lirc_ioctl, +#endif + .open = lirc_open, + .release = lirc_close +}; + +static int set_use_inc(void *data) +{ + return 0; +} + +static void set_use_dec(void *data) +{ +} + +static struct lirc_driver driver = { + .name = LIRC_DRIVER_NAME, + .minor = -1, + .code_length = 1, + .sample_rate = 0, + .data = NULL, + .add_to_buf = NULL, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .dev = NULL, + .owner = THIS_MODULE, +}; + +static struct platform_device *lirc_parallel_dev; + +static int __devinit lirc_parallel_probe(struct platform_device *dev) +{ + return 0; +} + +static int __devexit lirc_parallel_remove(struct platform_device *dev) +{ + return 0; +} + +static int lirc_parallel_suspend(struct platform_device *dev, + pm_message_t state) +{ + return 0; +} + +static int lirc_parallel_resume(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver lirc_parallel_driver = { + .probe = lirc_parallel_probe, + .remove = __devexit_p(lirc_parallel_remove), + .suspend = lirc_parallel_suspend, + .resume = lirc_parallel_resume, + .driver = { + .name = LIRC_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int pf(void *handle) +{ + parport_disable_irq(pport); + is_claimed = 0; + return 0; +} + +static void kf(void *handle) +{ + if (!is_open) + return; + if (!lirc_claim()) + return; + parport_enable_irq(pport); + lirc_off(); + /* this is a bit annoying when you actually print...*/ + /* + printk(KERN_INFO "%s: reclaimed port\n", LIRC_DRIVER_NAME); + */ +} + +/*** module initialization and cleanup ***/ + +static int __init lirc_parallel_init(void) +{ + int result; + + result = platform_driver_register(&lirc_parallel_driver); + if (result) { + printk(KERN_NOTICE "platform_driver_register" + " returned %d\n", result); + return result; + } + + lirc_parallel_dev = platform_device_alloc(LIRC_DRIVER_NAME, 0); + if (!lirc_parallel_dev) { + result = -ENOMEM; + goto exit_driver_unregister; + } + + result = platform_device_add(lirc_parallel_dev); + if (result) + goto exit_device_put; + + pport = parport_find_base(io); + if (pport == NULL) { + printk(KERN_NOTICE "%s: no port at %x found\n", + LIRC_DRIVER_NAME, io); + result = -ENXIO; + goto exit_device_put; + } + ppdevice = parport_register_device(pport, LIRC_DRIVER_NAME, + pf, kf, irq_handler, 0, NULL); + parport_put_port(pport); + if (ppdevice == NULL) { + printk(KERN_NOTICE "%s: parport_register_device() failed\n", + LIRC_DRIVER_NAME); + result = -ENXIO; + goto exit_device_put; + } + if (parport_claim(ppdevice) != 0) + goto skip_init; + is_claimed = 1; + out(LIRC_LP_CONTROL, LP_PSELECP|LP_PINITP); + +#ifdef LIRC_TIMER + if (debug) + out(LIRC_PORT_DATA, tx_mask); + + timer = init_lirc_timer(); + +#if 0 /* continue even if device is offline */ + if (timer == 0) { + is_claimed = 0; + parport_release(pport); + parport_unregister_device(ppdevice); + result = -EIO; + goto exit_device_put; + } + +#endif + if (debug) + out(LIRC_PORT_DATA, 0); +#endif + + is_claimed = 0; + parport_release(ppdevice); + skip_init: + driver.dev = &lirc_parallel_dev->dev; + driver.minor = lirc_register_driver(&driver); + if (driver.minor < 0) { + printk(KERN_NOTICE "%s: register_chrdev() failed\n", + LIRC_DRIVER_NAME); + parport_unregister_device(ppdevice); + result = -EIO; + goto exit_device_put; + } + printk(KERN_INFO "%s: installed using port 0x%04x irq %d\n", + LIRC_DRIVER_NAME, io, irq); + return 0; + +exit_device_put: + platform_device_put(lirc_parallel_dev); +exit_driver_unregister: + platform_driver_unregister(&lirc_parallel_driver); + return result; +} + +static void __exit lirc_parallel_exit(void) +{ + parport_unregister_device(ppdevice); + lirc_unregister_driver(driver.minor); + + platform_device_unregister(lirc_parallel_dev); + platform_driver_unregister(&lirc_parallel_driver); +} + +module_init(lirc_parallel_init); +module_exit(lirc_parallel_exit); + +MODULE_DESCRIPTION("Infrared receiver driver for parallel ports."); +MODULE_AUTHOR("Christoph Bartelmus"); +MODULE_LICENSE("GPL"); + +module_param(io, int, S_IRUGO); +MODULE_PARM_DESC(io, "I/O address base (0x3bc, 0x378 or 0x278)"); + +module_param(irq, int, S_IRUGO); +MODULE_PARM_DESC(irq, "Interrupt (7 or 5)"); + +module_param(tx_mask, int, S_IRUGO); +MODULE_PARM_DESC(tx_maxk, "Transmitter mask (default: 0x01)"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging messages"); + +module_param(check_pselecd, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(check_pselecd, "Check for printer (default: 0)"); diff --git a/drivers/staging/media/lirc/lirc_parallel.h b/drivers/staging/media/lirc/lirc_parallel.h new file mode 100644 index 00000000..4bed6afe --- /dev/null +++ b/drivers/staging/media/lirc/lirc_parallel.h @@ -0,0 +1,26 @@ +/* lirc_parallel.h */ + +#ifndef _LIRC_PARALLEL_H +#define _LIRC_PARALLEL_H + +#include + +#define LIRC_PORT_LEN 3 + +#define LIRC_LP_BASE 0 +#define LIRC_LP_STATUS 1 +#define LIRC_LP_CONTROL 2 + +#define LIRC_PORT_DATA LIRC_LP_BASE /* base */ +#define LIRC_PORT_TIMER LIRC_LP_STATUS /* status port */ +#define LIRC_PORT_TIMER_BIT LP_PBUSY /* busy signal */ +#define LIRC_PORT_SIGNAL LIRC_LP_STATUS /* status port */ +#define LIRC_PORT_SIGNAL_BIT LP_PACK /* ack signal */ +#define LIRC_PORT_IRQ LIRC_LP_CONTROL /* control port */ + +#define LIRC_SFH506_DELAY 0 /* delay t_phl in usecs */ + +#define LIRC_PARALLEL_MAX_TRANSMITTERS 8 +#define LIRC_PARALLEL_TRANSMITTER_MASK ((1< + * Tim Davies + * + * This driver was derived from: + * Venky Raju + * "lirc_imon - "LIRC/VFD driver for Ahanix/Soundgraph IMON IR/VFD" + * Paul Miller 's 2003-2004 + * "lirc_atiusb - USB remote support for LIRC" + * Culver Consulting Services 's 2003 + * "Sasem OnAir VFD/IR USB driver" + * + * + * NOTE - The LCDproc iMon driver should work with this module. More info at + * http://www.frogstorm.info/sasem + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#define MOD_AUTHOR "Oliver Stabel , " \ + "Tim Davies " +#define MOD_DESC "USB Driver for Sasem Remote Controller V1.1" +#define MOD_NAME "lirc_sasem" +#define MOD_VERSION "0.5" + +#define VFD_MINOR_BASE 144 /* Same as LCD */ +#define DEVICE_NAME "lcd%d" + +#define BUF_CHUNK_SIZE 8 +#define BUF_SIZE 128 + +#define IOCTL_LCD_CONTRAST 1 + +/*** P R O T O T Y P E S ***/ + +/* USB Callback prototypes */ +static int sasem_probe(struct usb_interface *interface, + const struct usb_device_id *id); +static void sasem_disconnect(struct usb_interface *interface); +static void usb_rx_callback(struct urb *urb); +static void usb_tx_callback(struct urb *urb); + +/* VFD file_operations function prototypes */ +static int vfd_open(struct inode *inode, struct file *file); +static long vfd_ioctl(struct file *file, unsigned cmd, unsigned long arg); +static int vfd_close(struct inode *inode, struct file *file); +static ssize_t vfd_write(struct file *file, const char *buf, + size_t n_bytes, loff_t *pos); + +/* LIRC driver function prototypes */ +static int ir_open(void *data); +static void ir_close(void *data); + +/* Driver init/exit prototypes */ +static int __init sasem_init(void); +static void __exit sasem_exit(void); + +/*** G L O B A L S ***/ +#define SASEM_DATA_BUF_SZ 32 + +struct sasem_context { + + struct usb_device *dev; + int vfd_isopen; /* VFD port has been opened */ + unsigned int vfd_contrast; /* VFD contrast */ + int ir_isopen; /* IR port has been opened */ + int dev_present; /* USB device presence */ + struct mutex ctx_lock; /* to lock this object */ + wait_queue_head_t remove_ok; /* For unexpected USB disconnects */ + + struct lirc_driver *driver; + struct usb_endpoint_descriptor *rx_endpoint; + struct usb_endpoint_descriptor *tx_endpoint; + struct urb *rx_urb; + struct urb *tx_urb; + unsigned char usb_rx_buf[8]; + unsigned char usb_tx_buf[8]; + + struct tx_t { + unsigned char data_buf[SASEM_DATA_BUF_SZ]; /* user data + * buffer */ + struct completion finished; /* wait for write to finish */ + atomic_t busy; /* write in progress */ + int status; /* status of tx completion */ + } tx; + + /* for dealing with repeat codes (wish there was a toggle bit!) */ + struct timeval presstime; + char lastcode[8]; + int codesaved; +}; + +/* VFD file operations */ +static const struct file_operations vfd_fops = { + .owner = THIS_MODULE, + .open = &vfd_open, + .write = &vfd_write, + .unlocked_ioctl = &vfd_ioctl, + .release = &vfd_close, + .llseek = noop_llseek, +}; + +/* USB Device ID for Sasem USB Control Board */ +static struct usb_device_id sasem_usb_id_table[] = { + /* Sasem USB Control Board */ + { USB_DEVICE(0x11ba, 0x0101) }, + /* Terminating entry */ + {} +}; + +/* USB Device data */ +static struct usb_driver sasem_driver = { + .name = MOD_NAME, + .probe = sasem_probe, + .disconnect = sasem_disconnect, + .id_table = sasem_usb_id_table, +}; + +static struct usb_class_driver sasem_class = { + .name = DEVICE_NAME, + .fops = &vfd_fops, + .minor_base = VFD_MINOR_BASE, +}; + +/* to prevent races between open() and disconnect() */ +static DEFINE_MUTEX(disconnect_lock); + +static int debug; + + +/*** M O D U L E C O D E ***/ + +MODULE_AUTHOR(MOD_AUTHOR); +MODULE_DESCRIPTION(MOD_DESC); +MODULE_LICENSE("GPL"); +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug messages: 0=no, 1=yes (default: no)"); + +static void delete_context(struct sasem_context *context) +{ + usb_free_urb(context->tx_urb); /* VFD */ + usb_free_urb(context->rx_urb); /* IR */ + lirc_buffer_free(context->driver->rbuf); + kfree(context->driver->rbuf); + kfree(context->driver); + kfree(context); + + if (debug) + printk(KERN_INFO "%s: context deleted\n", __func__); +} + +static void deregister_from_lirc(struct sasem_context *context) +{ + int retval; + int minor = context->driver->minor; + + retval = lirc_unregister_driver(minor); + if (retval) + err("%s: unable to deregister from lirc (%d)", + __func__, retval); + else + printk(KERN_INFO "Deregistered Sasem driver (minor:%d)\n", + minor); + +} + +/** + * Called when the VFD device (e.g. /dev/usb/lcd) + * is opened by the application. + */ +static int vfd_open(struct inode *inode, struct file *file) +{ + struct usb_interface *interface; + struct sasem_context *context = NULL; + int subminor; + int retval = 0; + + /* prevent races with disconnect */ + mutex_lock(&disconnect_lock); + + subminor = iminor(inode); + interface = usb_find_interface(&sasem_driver, subminor); + if (!interface) { + err("%s: could not find interface for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + context = usb_get_intfdata(interface); + + if (!context) { + err("%s: no context found for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + mutex_lock(&context->ctx_lock); + + if (context->vfd_isopen) { + err("%s: VFD port is already open", __func__); + retval = -EBUSY; + } else { + context->vfd_isopen = 1; + file->private_data = context; + printk(KERN_INFO "VFD port opened\n"); + } + + mutex_unlock(&context->ctx_lock); + +exit: + mutex_unlock(&disconnect_lock); + return retval; +} + +/** + * Called when the VFD device (e.g. /dev/usb/lcd) + * is closed by the application. + */ +static long vfd_ioctl(struct file *file, unsigned cmd, unsigned long arg) +{ + struct sasem_context *context = NULL; + + context = (struct sasem_context *) file->private_data; + + if (!context) { + err("%s: no context for device", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + switch (cmd) { + case IOCTL_LCD_CONTRAST: + if (arg > 1000) + arg = 1000; + context->vfd_contrast = (unsigned int)arg; + break; + default: + printk(KERN_INFO "Unknown IOCTL command\n"); + mutex_unlock(&context->ctx_lock); + return -ENOIOCTLCMD; /* not supported */ + } + + mutex_unlock(&context->ctx_lock); + return 0; +} + +/** + * Called when the VFD device (e.g. /dev/usb/lcd) + * is closed by the application. + */ +static int vfd_close(struct inode *inode, struct file *file) +{ + struct sasem_context *context = NULL; + int retval = 0; + + context = (struct sasem_context *) file->private_data; + + if (!context) { + err("%s: no context for device", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->vfd_isopen) { + err("%s: VFD is not open", __func__); + retval = -EIO; + } else { + context->vfd_isopen = 0; + printk(KERN_INFO "VFD port closed\n"); + if (!context->dev_present && !context->ir_isopen) { + + /* Device disconnected before close and IR port is + * not open. If IR port is open, context will be + * deleted by ir_close. */ + mutex_unlock(&context->ctx_lock); + delete_context(context); + return retval; + } + } + + mutex_unlock(&context->ctx_lock); + return retval; +} + +/** + * Sends a packet to the VFD. + */ +static int send_packet(struct sasem_context *context) +{ + unsigned int pipe; + int interval = 0; + int retval = 0; + + pipe = usb_sndintpipe(context->dev, + context->tx_endpoint->bEndpointAddress); + interval = context->tx_endpoint->bInterval; + + usb_fill_int_urb(context->tx_urb, context->dev, pipe, + context->usb_tx_buf, sizeof(context->usb_tx_buf), + usb_tx_callback, context, interval); + + context->tx_urb->actual_length = 0; + + init_completion(&context->tx.finished); + atomic_set(&(context->tx.busy), 1); + + retval = usb_submit_urb(context->tx_urb, GFP_KERNEL); + if (retval) { + atomic_set(&(context->tx.busy), 0); + err("%s: error submitting urb (%d)", __func__, retval); + } else { + /* Wait for transmission to complete (or abort) */ + mutex_unlock(&context->ctx_lock); + wait_for_completion(&context->tx.finished); + mutex_lock(&context->ctx_lock); + + retval = context->tx.status; + if (retval) + err("%s: packet tx failed (%d)", __func__, retval); + } + + return retval; +} + +/** + * Writes data to the VFD. The Sasem VFD is 2x16 characters + * and requires data in 9 consecutive USB interrupt packets, + * each packet carrying 8 bytes. + */ +static ssize_t vfd_write(struct file *file, const char *buf, + size_t n_bytes, loff_t *pos) +{ + int i; + int retval = 0; + struct sasem_context *context; + int *data_buf = NULL; + + context = (struct sasem_context *) file->private_data; + if (!context) { + err("%s: no context for device", __func__); + return -ENODEV; + } + + mutex_lock(&context->ctx_lock); + + if (!context->dev_present) { + err("%s: no Sasem device present", __func__); + retval = -ENODEV; + goto exit; + } + + if (n_bytes <= 0 || n_bytes > SASEM_DATA_BUF_SZ) { + err("%s: invalid payload size", __func__); + retval = -EINVAL; + goto exit; + } + + data_buf = memdup_user(buf, n_bytes); + if (IS_ERR(data_buf)) { + retval = PTR_ERR(data_buf); + goto exit; + } + + memcpy(context->tx.data_buf, data_buf, n_bytes); + + /* Pad with spaces */ + for (i = n_bytes; i < SASEM_DATA_BUF_SZ; ++i) + context->tx.data_buf[i] = ' '; + + /* Nine 8 byte packets to be sent */ + /* NOTE: "\x07\x01\0\0\0\0\0\0" or "\x0c\0\0\0\0\0\0\0" + * will clear the VFD */ + for (i = 0; i < 9; i++) { + switch (i) { + case 0: + memcpy(context->usb_tx_buf, "\x07\0\0\0\0\0\0\0", 8); + context->usb_tx_buf[1] = (context->vfd_contrast) ? + (0x2B - (context->vfd_contrast - 1) / 250) + : 0x2B; + break; + case 1: + memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8); + break; + case 2: + memcpy(context->usb_tx_buf, "\x0b\x01\0\0\0\0\0\0", 8); + break; + case 3: + memcpy(context->usb_tx_buf, context->tx.data_buf, 8); + break; + case 4: + memcpy(context->usb_tx_buf, + context->tx.data_buf + 8, 8); + break; + case 5: + memcpy(context->usb_tx_buf, "\x09\x01\0\0\0\0\0\0", 8); + break; + case 6: + memcpy(context->usb_tx_buf, "\x0b\x02\0\0\0\0\0\0", 8); + break; + case 7: + memcpy(context->usb_tx_buf, + context->tx.data_buf + 16, 8); + break; + case 8: + memcpy(context->usb_tx_buf, + context->tx.data_buf + 24, 8); + break; + } + retval = send_packet(context); + if (retval) { + + err("%s: send packet failed for packet #%d", + __func__, i); + goto exit; + } + } +exit: + + mutex_unlock(&context->ctx_lock); + kfree(data_buf); + + return (!retval) ? n_bytes : retval; +} + +/** + * Callback function for USB core API: transmit data + */ +static void usb_tx_callback(struct urb *urb) +{ + struct sasem_context *context; + + if (!urb) + return; + context = (struct sasem_context *) urb->context; + if (!context) + return; + + context->tx.status = urb->status; + + /* notify waiters that write has finished */ + atomic_set(&context->tx.busy, 0); + complete(&context->tx.finished); + + return; +} + +/** + * Called by lirc_dev when the application opens /dev/lirc + */ +static int ir_open(void *data) +{ + int retval = 0; + struct sasem_context *context; + + /* prevent races with disconnect */ + mutex_lock(&disconnect_lock); + + context = (struct sasem_context *) data; + + mutex_lock(&context->ctx_lock); + + if (context->ir_isopen) { + err("%s: IR port is already open", __func__); + retval = -EBUSY; + goto exit; + } + + usb_fill_int_urb(context->rx_urb, context->dev, + usb_rcvintpipe(context->dev, + context->rx_endpoint->bEndpointAddress), + context->usb_rx_buf, sizeof(context->usb_rx_buf), + usb_rx_callback, context, context->rx_endpoint->bInterval); + + retval = usb_submit_urb(context->rx_urb, GFP_KERNEL); + + if (retval) + err("%s: usb_submit_urb failed for ir_open (%d)", + __func__, retval); + else { + context->ir_isopen = 1; + printk(KERN_INFO "IR port opened\n"); + } + +exit: + mutex_unlock(&context->ctx_lock); + + mutex_unlock(&disconnect_lock); + return retval; +} + +/** + * Called by lirc_dev when the application closes /dev/lirc + */ +static void ir_close(void *data) +{ + struct sasem_context *context; + + context = (struct sasem_context *)data; + if (!context) { + err("%s: no context for device", __func__); + return; + } + + mutex_lock(&context->ctx_lock); + + usb_kill_urb(context->rx_urb); + context->ir_isopen = 0; + printk(KERN_INFO "IR port closed\n"); + + if (!context->dev_present) { + + /* + * Device disconnected while IR port was + * still open. Driver was not deregistered + * at disconnect time, so do it now. + */ + deregister_from_lirc(context); + + if (!context->vfd_isopen) { + + mutex_unlock(&context->ctx_lock); + delete_context(context); + return; + } + /* If VFD port is open, context will be deleted by vfd_close */ + } + + mutex_unlock(&context->ctx_lock); + return; +} + +/** + * Process the incoming packet + */ +static void incoming_packet(struct sasem_context *context, + struct urb *urb) +{ + int len = urb->actual_length; + unsigned char *buf = urb->transfer_buffer; + long ms; + struct timeval tv; + int i; + + if (len != 8) { + printk(KERN_WARNING "%s: invalid incoming packet size (%d)\n", + __func__, len); + return; + } + + if (debug) { + printk(KERN_INFO "Incoming data: "); + for (i = 0; i < 8; ++i) + printk(KERN_CONT "%02x ", buf[i]); + printk(KERN_CONT "\n"); + } + + /* + * Lirc could deal with the repeat code, but we really need to block it + * if it arrives too late. Otherwise we could repeat the wrong code. + */ + + /* get the time since the last button press */ + do_gettimeofday(&tv); + ms = (tv.tv_sec - context->presstime.tv_sec) * 1000 + + (tv.tv_usec - context->presstime.tv_usec) / 1000; + + if (memcmp(buf, "\x08\0\0\0\0\0\0\0", 8) == 0) { + /* + * the repeat code is being sent, so we copy + * the old code to LIRC + */ + + /* + * NOTE: Only if the last code was less than 250ms ago + * - no one should be able to push another (undetected) button + * in that time and then get a false repeat of the previous + * press but it is long enough for a genuine repeat + */ + if ((ms < 250) && (context->codesaved != 0)) { + memcpy(buf, &context->lastcode, 8); + context->presstime.tv_sec = tv.tv_sec; + context->presstime.tv_usec = tv.tv_usec; + } + } else { + /* save the current valid code for repeats */ + memcpy(&context->lastcode, buf, 8); + /* + * set flag to signal a valid code was save; + * just for safety reasons + */ + context->codesaved = 1; + context->presstime.tv_sec = tv.tv_sec; + context->presstime.tv_usec = tv.tv_usec; + } + + lirc_buffer_write(context->driver->rbuf, buf); + wake_up(&context->driver->rbuf->wait_poll); +} + +/** + * Callback function for USB core API: receive data + */ +static void usb_rx_callback(struct urb *urb) +{ + struct sasem_context *context; + + if (!urb) + return; + context = (struct sasem_context *) urb->context; + if (!context) + return; + + switch (urb->status) { + + case -ENOENT: /* usbcore unlink successful! */ + return; + + case 0: + if (context->ir_isopen) + incoming_packet(context, urb); + break; + + default: + printk(KERN_WARNING "%s: status (%d): ignored", + __func__, urb->status); + break; + } + + usb_submit_urb(context->rx_urb, GFP_ATOMIC); + return; +} + + + +/** + * Callback function for USB core API: Probe + */ +static int sasem_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *dev = NULL; + struct usb_host_interface *iface_desc = NULL; + struct usb_endpoint_descriptor *rx_endpoint = NULL; + struct usb_endpoint_descriptor *tx_endpoint = NULL; + struct urb *rx_urb = NULL; + struct urb *tx_urb = NULL; + struct lirc_driver *driver = NULL; + struct lirc_buffer *rbuf = NULL; + int lirc_minor = 0; + int num_endpoints; + int retval = 0; + int vfd_ep_found; + int ir_ep_found; + int alloc_status; + struct sasem_context *context = NULL; + int i; + + printk(KERN_INFO "%s: found Sasem device\n", __func__); + + + dev = usb_get_dev(interface_to_usbdev(interface)); + iface_desc = interface->cur_altsetting; + num_endpoints = iface_desc->desc.bNumEndpoints; + + /* + * Scan the endpoint list and set: + * first input endpoint = IR endpoint + * first output endpoint = VFD endpoint + */ + + ir_ep_found = 0; + vfd_ep_found = 0; + + for (i = 0; i < num_endpoints && !(ir_ep_found && vfd_ep_found); ++i) { + + struct usb_endpoint_descriptor *ep; + int ep_dir; + int ep_type; + ep = &iface_desc->endpoint [i].desc; + ep_dir = ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK; + ep_type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + if (!ir_ep_found && + ep_dir == USB_DIR_IN && + ep_type == USB_ENDPOINT_XFER_INT) { + + rx_endpoint = ep; + ir_ep_found = 1; + if (debug) + printk(KERN_INFO "%s: found IR endpoint\n", + __func__); + + } else if (!vfd_ep_found && + ep_dir == USB_DIR_OUT && + ep_type == USB_ENDPOINT_XFER_INT) { + + tx_endpoint = ep; + vfd_ep_found = 1; + if (debug) + printk(KERN_INFO "%s: found VFD endpoint\n", + __func__); + } + } + + /* Input endpoint is mandatory */ + if (!ir_ep_found) { + + err("%s: no valid input (IR) endpoint found.", __func__); + retval = -ENODEV; + goto exit; + } + + if (!vfd_ep_found) + printk(KERN_INFO "%s: no valid output (VFD) endpoint found.\n", + __func__); + + + /* Allocate memory */ + alloc_status = 0; + + context = kzalloc(sizeof(struct sasem_context), GFP_KERNEL); + if (!context) { + err("%s: kzalloc failed for context", __func__); + alloc_status = 1; + goto alloc_status_switch; + } + driver = kzalloc(sizeof(struct lirc_driver), GFP_KERNEL); + if (!driver) { + err("%s: kzalloc failed for lirc_driver", __func__); + alloc_status = 2; + goto alloc_status_switch; + } + rbuf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); + if (!rbuf) { + err("%s: kmalloc failed for lirc_buffer", __func__); + alloc_status = 3; + goto alloc_status_switch; + } + if (lirc_buffer_init(rbuf, BUF_CHUNK_SIZE, BUF_SIZE)) { + err("%s: lirc_buffer_init failed", __func__); + alloc_status = 4; + goto alloc_status_switch; + } + rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rx_urb) { + err("%s: usb_alloc_urb failed for IR urb", __func__); + alloc_status = 5; + goto alloc_status_switch; + } + if (vfd_ep_found) { + tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!tx_urb) { + err("%s: usb_alloc_urb failed for VFD urb", + __func__); + alloc_status = 6; + goto alloc_status_switch; + } + } + + mutex_init(&context->ctx_lock); + + strcpy(driver->name, MOD_NAME); + driver->minor = -1; + driver->code_length = 64; + driver->sample_rate = 0; + driver->features = LIRC_CAN_REC_LIRCCODE; + driver->data = context; + driver->rbuf = rbuf; + driver->set_use_inc = ir_open; + driver->set_use_dec = ir_close; + driver->dev = &interface->dev; + driver->owner = THIS_MODULE; + + mutex_lock(&context->ctx_lock); + + lirc_minor = lirc_register_driver(driver); + if (lirc_minor < 0) { + err("%s: lirc_register_driver failed", __func__); + alloc_status = 7; + retval = lirc_minor; + goto unlock; + } else + printk(KERN_INFO "%s: Registered Sasem driver (minor:%d)\n", + __func__, lirc_minor); + + /* Needed while unregistering! */ + driver->minor = lirc_minor; + + context->dev = dev; + context->dev_present = 1; + context->rx_endpoint = rx_endpoint; + context->rx_urb = rx_urb; + if (vfd_ep_found) { + context->tx_endpoint = tx_endpoint; + context->tx_urb = tx_urb; + context->vfd_contrast = 1000; /* range 0 - 1000 */ + } + context->driver = driver; + + usb_set_intfdata(interface, context); + + if (vfd_ep_found) { + + if (debug) + printk(KERN_INFO "Registering VFD with sysfs\n"); + if (usb_register_dev(interface, &sasem_class)) + /* Not a fatal error, so ignore */ + printk(KERN_INFO "%s: could not get a minor number " + "for VFD\n", __func__); + } + + printk(KERN_INFO "%s: Sasem device on usb<%d:%d> initialized\n", + __func__, dev->bus->busnum, dev->devnum); +unlock: + mutex_unlock(&context->ctx_lock); + +alloc_status_switch: + switch (alloc_status) { + + case 7: + if (vfd_ep_found) + usb_free_urb(tx_urb); + case 6: + usb_free_urb(rx_urb); + case 5: + lirc_buffer_free(rbuf); + case 4: + kfree(rbuf); + case 3: + kfree(driver); + case 2: + kfree(context); + context = NULL; + case 1: + if (retval == 0) + retval = -ENOMEM; + } + +exit: + return retval; +} + +/** + * Callback function for USB core API: disonnect + */ +static void sasem_disconnect(struct usb_interface *interface) +{ + struct sasem_context *context; + + /* prevent races with ir_open()/vfd_open() */ + mutex_lock(&disconnect_lock); + + context = usb_get_intfdata(interface); + mutex_lock(&context->ctx_lock); + + printk(KERN_INFO "%s: Sasem device disconnected\n", __func__); + + usb_set_intfdata(interface, NULL); + context->dev_present = 0; + + /* Stop reception */ + usb_kill_urb(context->rx_urb); + + /* Abort ongoing write */ + if (atomic_read(&context->tx.busy)) { + + usb_kill_urb(context->tx_urb); + wait_for_completion(&context->tx.finished); + } + + /* De-register from lirc_dev if IR port is not open */ + if (!context->ir_isopen) + deregister_from_lirc(context); + + usb_deregister_dev(interface, &sasem_class); + + mutex_unlock(&context->ctx_lock); + + if (!context->ir_isopen && !context->vfd_isopen) + delete_context(context); + + mutex_unlock(&disconnect_lock); +} + +module_usb_driver(sasem_driver); diff --git a/drivers/staging/media/lirc/lirc_serial.c b/drivers/staging/media/lirc/lirc_serial.c new file mode 100644 index 00000000..3295ea63 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_serial.c @@ -0,0 +1,1309 @@ +/* + * lirc_serial.c + * + * lirc_serial - Device driver that records pulse- and pause-lengths + * (space-lengths) between DDCD event on a serial port. + * + * Copyright (C) 1996,97 Ralph Metzler + * Copyright (C) 1998 Trent Piepho + * Copyright (C) 1998 Ben Pfaff + * Copyright (C) 1999 Christoph Bartelmus + * Copyright (C) 2007 Andrei Tanas (suspend/resume support) + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Steve's changes to improve transmission fidelity: + * - for systems with the rdtsc instruction and the clock counter, a + * send_pule that times the pulses directly using the counter. + * This means that the LIRC_SERIAL_TRANSMITTER_LATENCY fudge is + * not needed. Measurement shows very stable waveform, even where + * PCI activity slows the access to the UART, which trips up other + * versions. + * - For other system, non-integer-microsecond pulse/space lengths, + * done using fixed point binary. So, much more accurate carrier + * frequency. + * - fine tuned transmitter latency, taking advantage of fractional + * microseconds in previous change + * - Fixed bug in the way transmitter latency was accounted for by + * tuning the pulse lengths down - the send_pulse routine ignored + * this overhead as it timed the overall pulse length - so the + * pulse frequency was right but overall pulse length was too + * long. Fixed by accounting for latency on each pulse/space + * iteration. + * + * Steve Davies July 2001 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef CONFIG_LIRC_SERIAL_NSLU2 +#include +#endif +/* From Intel IXP42X Developer's Manual (#252480-005): */ +/* ftp://download.intel.com/design/network/manuals/25248005.pdf */ +#define UART_IE_IXP42X_UUE 0x40 /* IXP42X UART Unit enable */ +#define UART_IE_IXP42X_RTOIE 0x10 /* IXP42X Receiver Data Timeout int.enable */ + +#include +#include + +#define LIRC_DRIVER_NAME "lirc_serial" + +struct lirc_serial { + int signal_pin; + int signal_pin_change; + u8 on; + u8 off; + long (*send_pulse)(unsigned long length); + void (*send_space)(long length); + int features; + spinlock_t lock; +}; + +#define LIRC_HOMEBREW 0 +#define LIRC_IRDEO 1 +#define LIRC_IRDEO_REMOTE 2 +#define LIRC_ANIMAX 3 +#define LIRC_IGOR 4 +#define LIRC_NSLU2 5 + +/*** module parameters ***/ +static int type; +static int io; +static int irq; +static bool iommap; +static int ioshift; +static bool softcarrier = 1; +static bool share_irq; +static bool debug; +static int sense = -1; /* -1 = auto, 0 = active high, 1 = active low */ +static bool txsense; /* 0 = active high, 1 = active low */ + +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \ + fmt, ## args); \ + } while (0) + +/* forward declarations */ +static long send_pulse_irdeo(unsigned long length); +static long send_pulse_homebrew(unsigned long length); +static void send_space_irdeo(long length); +static void send_space_homebrew(long length); + +static struct lirc_serial hardware[] = { + [LIRC_HOMEBREW] = { + .signal_pin = UART_MSR_DCD, + .signal_pin_change = UART_MSR_DDCD, + .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR), + .off = (UART_MCR_RTS | UART_MCR_OUT2), + .send_pulse = send_pulse_homebrew, + .send_space = send_space_homebrew, +#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER + .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SET_SEND_CARRIER | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2) +#else + .features = LIRC_CAN_REC_MODE2 +#endif + }, + + [LIRC_IRDEO] = { + .signal_pin = UART_MSR_DSR, + .signal_pin_change = UART_MSR_DDSR, + .on = UART_MCR_OUT2, + .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), + .send_pulse = send_pulse_irdeo, + .send_space = send_space_irdeo, + .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2) + }, + + [LIRC_IRDEO_REMOTE] = { + .signal_pin = UART_MSR_DSR, + .signal_pin_change = UART_MSR_DDSR, + .on = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), + .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), + .send_pulse = send_pulse_irdeo, + .send_space = send_space_irdeo, + .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2) + }, + + [LIRC_ANIMAX] = { + .signal_pin = UART_MSR_DCD, + .signal_pin_change = UART_MSR_DDCD, + .on = 0, + .off = (UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2), + .send_pulse = NULL, + .send_space = NULL, + .features = LIRC_CAN_REC_MODE2 + }, + + [LIRC_IGOR] = { + .signal_pin = UART_MSR_DSR, + .signal_pin_change = UART_MSR_DDSR, + .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR), + .off = (UART_MCR_RTS | UART_MCR_OUT2), + .send_pulse = send_pulse_homebrew, + .send_space = send_space_homebrew, +#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER + .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SET_SEND_CARRIER | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2) +#else + .features = LIRC_CAN_REC_MODE2 +#endif + }, + +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + /* + * Modified Linksys Network Storage Link USB 2.0 (NSLU2): + * We receive on CTS of the 2nd serial port (R142,LHS), we + * transmit with a IR diode between GPIO[1] (green status LED), + * and ground (Matthias Goebl ). + * See also http://www.nslu2-linux.org for this device + */ + [LIRC_NSLU2] = { + .signal_pin = UART_MSR_CTS, + .signal_pin_change = UART_MSR_DCTS, + .on = (UART_MCR_RTS | UART_MCR_OUT2 | UART_MCR_DTR), + .off = (UART_MCR_RTS | UART_MCR_OUT2), + .send_pulse = send_pulse_homebrew, + .send_space = send_space_homebrew, +#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER + .features = (LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SET_SEND_CARRIER | + LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2) +#else + .features = LIRC_CAN_REC_MODE2 +#endif + }, +#endif + +}; + +#define RS_ISR_PASS_LIMIT 256 + +/* + * A long pulse code from a remote might take up to 300 bytes. The + * daemon should read the bytes as soon as they are generated, so take + * the number of keys you think you can push before the daemon runs + * and multiply by 300. The driver will warn you if you overrun this + * buffer. If you have a slow computer or non-busmastering IDE disks, + * maybe you will need to increase this. + */ + +/* This MUST be a power of two! It has to be larger than 1 as well. */ + +#define RBUF_LEN 256 + +static struct timeval lasttv = {0, 0}; + +static struct lirc_buffer rbuf; + +static unsigned int freq = 38000; +static unsigned int duty_cycle = 50; + +/* Initialized in init_timing_params() */ +static unsigned long period; +static unsigned long pulse_width; +static unsigned long space_width; + +#if defined(__i386__) +/* + * From: + * Linux I/O port programming mini-HOWTO + * Author: Riku Saikkonen + * v, 28 December 1997 + * + * [...] + * Actually, a port I/O instruction on most ports in the 0-0x3ff range + * takes almost exactly 1 microsecond, so if you're, for example, using + * the parallel port directly, just do additional inb()s from that port + * to delay. + * [...] + */ +/* transmitter latency 1.5625us 0x1.90 - this figure arrived at from + * comment above plus trimming to match actual measured frequency. + * This will be sensitive to cpu speed, though hopefully most of the 1.5us + * is spent in the uart access. Still - for reference test machine was a + * 1.13GHz Athlon system - Steve + */ + +/* + * changed from 400 to 450 as this works better on slower machines; + * faster machines will use the rdtsc code anyway + */ +#define LIRC_SERIAL_TRANSMITTER_LATENCY 450 + +#else + +/* does anybody have information on other platforms ? */ +/* 256 = 1<<8 */ +#define LIRC_SERIAL_TRANSMITTER_LATENCY 256 + +#endif /* __i386__ */ +/* + * FIXME: should we be using hrtimers instead of this + * LIRC_SERIAL_TRANSMITTER_LATENCY nonsense? + */ + +/* fetch serial input packet (1 byte) from register offset */ +static u8 sinp(int offset) +{ + if (iommap != 0) + /* the register is memory-mapped */ + offset <<= ioshift; + + return inb(io + offset); +} + +/* write serial output packet (1 byte) of value to register offset */ +static void soutp(int offset, u8 value) +{ + if (iommap != 0) + /* the register is memory-mapped */ + offset <<= ioshift; + + outb(value, io + offset); +} + +static void on(void) +{ +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + /* + * On NSLU2, we put the transmit diode between the output of the green + * status LED and ground + */ + if (type == LIRC_NSLU2) { + gpio_line_set(NSLU2_LED_GRN, IXP4XX_GPIO_LOW); + return; + } +#endif + if (txsense) + soutp(UART_MCR, hardware[type].off); + else + soutp(UART_MCR, hardware[type].on); +} + +static void off(void) +{ +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + if (type == LIRC_NSLU2) { + gpio_line_set(NSLU2_LED_GRN, IXP4XX_GPIO_HIGH); + return; + } +#endif + if (txsense) + soutp(UART_MCR, hardware[type].on); + else + soutp(UART_MCR, hardware[type].off); +} + +#ifndef MAX_UDELAY_MS +#define MAX_UDELAY_US 5000 +#else +#define MAX_UDELAY_US (MAX_UDELAY_MS*1000) +#endif + +static void safe_udelay(unsigned long usecs) +{ + while (usecs > MAX_UDELAY_US) { + udelay(MAX_UDELAY_US); + usecs -= MAX_UDELAY_US; + } + udelay(usecs); +} + +#ifdef USE_RDTSC +/* + * This is an overflow/precision juggle, complicated in that we can't + * do long long divide in the kernel + */ + +/* + * When we use the rdtsc instruction to measure clocks, we keep the + * pulse and space widths as clock cycles. As this is CPU speed + * dependent, the widths must be calculated in init_port and ioctl + * time + */ + +/* So send_pulse can quickly convert microseconds to clocks */ +static unsigned long conv_us_to_clocks; + +static int init_timing_params(unsigned int new_duty_cycle, + unsigned int new_freq) +{ + __u64 loops_per_sec, work; + + duty_cycle = new_duty_cycle; + freq = new_freq; + + loops_per_sec = __this_cpu_read(cpu.info.loops_per_jiffy); + loops_per_sec *= HZ; + + /* How many clocks in a microsecond?, avoiding long long divide */ + work = loops_per_sec; + work *= 4295; /* 4295 = 2^32 / 1e6 */ + conv_us_to_clocks = (work >> 32); + + /* + * Carrier period in clocks, approach good up to 32GHz clock, + * gets carrier frequency within 8Hz + */ + period = loops_per_sec >> 3; + period /= (freq >> 3); + + /* Derive pulse and space from the period */ + pulse_width = period * duty_cycle / 100; + space_width = period - pulse_width; + dprintk("in init_timing_params, freq=%d, duty_cycle=%d, " + "clk/jiffy=%ld, pulse=%ld, space=%ld, " + "conv_us_to_clocks=%ld\n", + freq, duty_cycle, __this_cpu_read(cpu_info.loops_per_jiffy), + pulse_width, space_width, conv_us_to_clocks); + return 0; +} +#else /* ! USE_RDTSC */ +static int init_timing_params(unsigned int new_duty_cycle, + unsigned int new_freq) +{ +/* + * period, pulse/space width are kept with 8 binary places - + * IE multiplied by 256. + */ + if (256 * 1000000L / new_freq * new_duty_cycle / 100 <= + LIRC_SERIAL_TRANSMITTER_LATENCY) + return -EINVAL; + if (256 * 1000000L / new_freq * (100 - new_duty_cycle) / 100 <= + LIRC_SERIAL_TRANSMITTER_LATENCY) + return -EINVAL; + duty_cycle = new_duty_cycle; + freq = new_freq; + period = 256 * 1000000L / freq; + pulse_width = period * duty_cycle / 100; + space_width = period - pulse_width; + dprintk("in init_timing_params, freq=%d pulse=%ld, " + "space=%ld\n", freq, pulse_width, space_width); + return 0; +} +#endif /* USE_RDTSC */ + + +/* return value: space length delta */ + +static long send_pulse_irdeo(unsigned long length) +{ + long rawbits, ret; + int i; + unsigned char output; + unsigned char chunk, shifted; + + /* how many bits have to be sent ? */ + rawbits = length * 1152 / 10000; + if (duty_cycle > 50) + chunk = 3; + else + chunk = 1; + for (i = 0, output = 0x7f; rawbits > 0; rawbits -= 3) { + shifted = chunk << (i * 3); + shifted >>= 1; + output &= (~shifted); + i++; + if (i == 3) { + soutp(UART_TX, output); + while (!(sinp(UART_LSR) & UART_LSR_THRE)) + ; + output = 0x7f; + i = 0; + } + } + if (i != 0) { + soutp(UART_TX, output); + while (!(sinp(UART_LSR) & UART_LSR_TEMT)) + ; + } + + if (i == 0) + ret = (-rawbits) * 10000 / 1152; + else + ret = (3 - i) * 3 * 10000 / 1152 + (-rawbits) * 10000 / 1152; + + return ret; +} + +#ifdef USE_RDTSC +/* Version that uses Pentium rdtsc instruction to measure clocks */ + +/* + * This version does sub-microsecond timing using rdtsc instruction, + * and does away with the fudged LIRC_SERIAL_TRANSMITTER_LATENCY + * Implicitly i586 architecture... - Steve + */ + +static long send_pulse_homebrew_softcarrier(unsigned long length) +{ + int flag; + unsigned long target, start, now; + + /* Get going quick as we can */ + rdtscl(start); + on(); + /* Convert length from microseconds to clocks */ + length *= conv_us_to_clocks; + /* And loop till time is up - flipping at right intervals */ + now = start; + target = pulse_width; + flag = 1; + /* + * FIXME: This looks like a hard busy wait, without even an occasional, + * polite, cpu_relax() call. There's got to be a better way? + * + * The i2c code has the result of a lot of bit-banging work, I wonder if + * there's something there which could be helpful here. + */ + while ((now - start) < length) { + /* Delay till flip time */ + do { + rdtscl(now); + } while ((now - start) < target); + + /* flip */ + if (flag) { + rdtscl(now); + off(); + target += space_width; + } else { + rdtscl(now); on(); + target += pulse_width; + } + flag = !flag; + } + rdtscl(now); + return ((now - start) - length) / conv_us_to_clocks; +} +#else /* ! USE_RDTSC */ +/* Version using udelay() */ + +/* + * here we use fixed point arithmetic, with 8 + * fractional bits. that gets us within 0.1% or so of the right average + * frequency, albeit with some jitter in pulse length - Steve + */ + +/* To match 8 fractional bits used for pulse/space length */ + +static long send_pulse_homebrew_softcarrier(unsigned long length) +{ + int flag; + unsigned long actual, target, d; + length <<= 8; + + actual = 0; target = 0; flag = 0; + while (actual < length) { + if (flag) { + off(); + target += space_width; + } else { + on(); + target += pulse_width; + } + d = (target - actual - + LIRC_SERIAL_TRANSMITTER_LATENCY + 128) >> 8; + /* + * Note - we've checked in ioctl that the pulse/space + * widths are big enough so that d is > 0 + */ + udelay(d); + actual += (d << 8) + LIRC_SERIAL_TRANSMITTER_LATENCY; + flag = !flag; + } + return (actual-length) >> 8; +} +#endif /* USE_RDTSC */ + +static long send_pulse_homebrew(unsigned long length) +{ + if (length <= 0) + return 0; + + if (softcarrier) + return send_pulse_homebrew_softcarrier(length); + else { + on(); + safe_udelay(length); + return 0; + } +} + +static void send_space_irdeo(long length) +{ + if (length <= 0) + return; + + safe_udelay(length); +} + +static void send_space_homebrew(long length) +{ + off(); + if (length <= 0) + return; + safe_udelay(length); +} + +static void rbwrite(int l) +{ + if (lirc_buffer_full(&rbuf)) { + /* no new signals will be accepted */ + dprintk("Buffer overrun\n"); + return; + } + lirc_buffer_write(&rbuf, (void *)&l); +} + +static void frbwrite(int l) +{ + /* simple noise filter */ + static int pulse, space; + static unsigned int ptr; + + if (ptr > 0 && (l & PULSE_BIT)) { + pulse += l & PULSE_MASK; + if (pulse > 250) { + rbwrite(space); + rbwrite(pulse | PULSE_BIT); + ptr = 0; + pulse = 0; + } + return; + } + if (!(l & PULSE_BIT)) { + if (ptr == 0) { + if (l > 20000) { + space = l; + ptr++; + return; + } + } else { + if (l > 20000) { + space += pulse; + if (space > PULSE_MASK) + space = PULSE_MASK; + space += l; + if (space > PULSE_MASK) + space = PULSE_MASK; + pulse = 0; + return; + } + rbwrite(space); + rbwrite(pulse | PULSE_BIT); + ptr = 0; + pulse = 0; + } + } + rbwrite(l); +} + +static irqreturn_t irq_handler(int i, void *blah) +{ + struct timeval tv; + int counter, dcd; + u8 status; + long deltv; + int data; + static int last_dcd = -1; + + if ((sinp(UART_IIR) & UART_IIR_NO_INT)) { + /* not our interrupt */ + return IRQ_NONE; + } + + counter = 0; + do { + counter++; + status = sinp(UART_MSR); + if (counter > RS_ISR_PASS_LIMIT) { + printk(KERN_WARNING LIRC_DRIVER_NAME ": AIEEEE: " + "We're caught!\n"); + break; + } + if ((status & hardware[type].signal_pin_change) + && sense != -1) { + /* get current time */ + do_gettimeofday(&tv); + + /* New mode, written by Trent Piepho + . */ + + /* + * The old format was not very portable. + * We now use an int to pass pulses + * and spaces to user space. + * + * If PULSE_BIT is set a pulse has been + * received, otherwise a space has been + * received. The driver needs to know if your + * receiver is active high or active low, or + * the space/pulse sense could be + * inverted. The bits denoted by PULSE_MASK are + * the length in microseconds. Lengths greater + * than or equal to 16 seconds are clamped to + * PULSE_MASK. All other bits are unused. + * This is a much simpler interface for user + * programs, as well as eliminating "out of + * phase" errors with space/pulse + * autodetection. + */ + + /* calc time since last interrupt in microseconds */ + dcd = (status & hardware[type].signal_pin) ? 1 : 0; + + if (dcd == last_dcd) { + printk(KERN_WARNING LIRC_DRIVER_NAME + ": ignoring spike: %d %d %lx %lx %lx %lx\n", + dcd, sense, + tv.tv_sec, lasttv.tv_sec, + tv.tv_usec, lasttv.tv_usec); + continue; + } + + deltv = tv.tv_sec-lasttv.tv_sec; + if (tv.tv_sec < lasttv.tv_sec || + (tv.tv_sec == lasttv.tv_sec && + tv.tv_usec < lasttv.tv_usec)) { + printk(KERN_WARNING LIRC_DRIVER_NAME + ": AIEEEE: your clock just jumped " + "backwards\n"); + printk(KERN_WARNING LIRC_DRIVER_NAME + ": %d %d %lx %lx %lx %lx\n", + dcd, sense, + tv.tv_sec, lasttv.tv_sec, + tv.tv_usec, lasttv.tv_usec); + data = PULSE_MASK; + } else if (deltv > 15) { + data = PULSE_MASK; /* really long time */ + if (!(dcd^sense)) { + /* sanity check */ + printk(KERN_WARNING LIRC_DRIVER_NAME + ": AIEEEE: " + "%d %d %lx %lx %lx %lx\n", + dcd, sense, + tv.tv_sec, lasttv.tv_sec, + tv.tv_usec, lasttv.tv_usec); + /* + * detecting pulse while this + * MUST be a space! + */ + sense = sense ? 0 : 1; + } + } else + data = (int) (deltv*1000000 + + tv.tv_usec - + lasttv.tv_usec); + frbwrite(dcd^sense ? data : (data|PULSE_BIT)); + lasttv = tv; + last_dcd = dcd; + wake_up_interruptible(&rbuf.wait_poll); + } + } while (!(sinp(UART_IIR) & UART_IIR_NO_INT)); /* still pending ? */ + return IRQ_HANDLED; +} + + +static int hardware_init_port(void) +{ + u8 scratch, scratch2, scratch3; + + /* + * This is a simple port existence test, borrowed from the autoconfig + * function in drivers/serial/8250.c + */ + scratch = sinp(UART_IER); + soutp(UART_IER, 0); +#ifdef __i386__ + outb(0xff, 0x080); +#endif + scratch2 = sinp(UART_IER) & 0x0f; + soutp(UART_IER, 0x0f); +#ifdef __i386__ + outb(0x00, 0x080); +#endif + scratch3 = sinp(UART_IER) & 0x0f; + soutp(UART_IER, scratch); + if (scratch2 != 0 || scratch3 != 0x0f) { + /* we fail, there's nothing here */ + printk(KERN_ERR LIRC_DRIVER_NAME ": port existence test " + "failed, cannot continue\n"); + return -ENODEV; + } + + + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* First of all, disable all interrupts */ + soutp(UART_IER, sinp(UART_IER) & + (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI))); + + /* Clear registers. */ + sinp(UART_LSR); + sinp(UART_RX); + sinp(UART_IIR); + sinp(UART_MSR); + +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + if (type == LIRC_NSLU2) { + /* Setup NSLU2 UART */ + + /* Enable UART */ + soutp(UART_IER, sinp(UART_IER) | UART_IE_IXP42X_UUE); + /* Disable Receiver data Time out interrupt */ + soutp(UART_IER, sinp(UART_IER) & ~UART_IE_IXP42X_RTOIE); + /* set out2 = interrupt unmask; off() doesn't set MCR + on NSLU2 */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2); + } +#endif + + /* Set line for power source */ + off(); + + /* Clear registers again to be sure. */ + sinp(UART_LSR); + sinp(UART_RX); + sinp(UART_IIR); + sinp(UART_MSR); + + switch (type) { + case LIRC_IRDEO: + case LIRC_IRDEO_REMOTE: + /* setup port to 7N1 @ 115200 Baud */ + /* 7N1+start = 9 bits at 115200 ~ 3 bits at 38kHz */ + + /* Set DLAB 1. */ + soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB); + /* Set divisor to 1 => 115200 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 1); + /* Set DLAB 0 + 7N1 */ + soutp(UART_LCR, UART_LCR_WLEN7); + /* THR interrupt already disabled at this point */ + break; + default: + break; + } + + return 0; +} + +static int __devinit lirc_serial_probe(struct platform_device *dev) +{ + int i, nlow, nhigh, result; + + result = request_irq(irq, irq_handler, + (share_irq ? IRQF_SHARED : 0), + LIRC_DRIVER_NAME, (void *)&hardware); + if (result < 0) { + if (result == -EBUSY) + printk(KERN_ERR LIRC_DRIVER_NAME ": IRQ %d busy\n", + irq); + else if (result == -EINVAL) + printk(KERN_ERR LIRC_DRIVER_NAME + ": Bad irq number or handler\n"); + return result; + } + + /* Reserve io region. */ + /* + * Future MMAP-Developers: Attention! + * For memory mapped I/O you *might* need to use ioremap() first, + * for the NSLU2 it's done in boot code. + */ + if (((iommap != 0) + && (request_mem_region(iommap, 8 << ioshift, + LIRC_DRIVER_NAME) == NULL)) + || ((iommap == 0) + && (request_region(io, 8, LIRC_DRIVER_NAME) == NULL))) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": port %04x already in use\n", io); + printk(KERN_WARNING LIRC_DRIVER_NAME + ": use 'setserial /dev/ttySX uart none'\n"); + printk(KERN_WARNING LIRC_DRIVER_NAME + ": or compile the serial port driver as module and\n"); + printk(KERN_WARNING LIRC_DRIVER_NAME + ": make sure this module is loaded first\n"); + result = -EBUSY; + goto exit_free_irq; + } + + result = hardware_init_port(); + if (result < 0) + goto exit_release_region; + + /* Initialize pulse/space widths */ + init_timing_params(duty_cycle, freq); + + /* If pin is high, then this must be an active low receiver. */ + if (sense == -1) { + /* wait 1/2 sec for the power supply */ + msleep(500); + + /* + * probe 9 times every 0.04s, collect "votes" for + * active high/low + */ + nlow = 0; + nhigh = 0; + for (i = 0; i < 9; i++) { + if (sinp(UART_MSR) & hardware[type].signal_pin) + nlow++; + else + nhigh++; + msleep(40); + } + sense = (nlow >= nhigh ? 1 : 0); + printk(KERN_INFO LIRC_DRIVER_NAME ": auto-detected active " + "%s receiver\n", sense ? "low" : "high"); + } else + printk(KERN_INFO LIRC_DRIVER_NAME ": Manually using active " + "%s receiver\n", sense ? "low" : "high"); + + dprintk("Interrupt %d, port %04x obtained\n", irq, io); + return 0; + +exit_release_region: + if (iommap != 0) + release_mem_region(iommap, 8 << ioshift); + else + release_region(io, 8); +exit_free_irq: + free_irq(irq, (void *)&hardware); + + return result; +} + +static int __devexit lirc_serial_remove(struct platform_device *dev) +{ + free_irq(irq, (void *)&hardware); + + if (iommap != 0) + release_mem_region(iommap, 8 << ioshift); + else + release_region(io, 8); + + return 0; +} + +static int set_use_inc(void *data) +{ + unsigned long flags; + + /* initialize timestamp */ + do_gettimeofday(&lasttv); + + spin_lock_irqsave(&hardware[type].lock, flags); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + soutp(UART_IER, sinp(UART_IER)|UART_IER_MSI); + + spin_unlock_irqrestore(&hardware[type].lock, flags); + + return 0; +} + +static void set_use_dec(void *data) +{ unsigned long flags; + + spin_lock_irqsave(&hardware[type].lock, flags); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* First of all, disable all interrupts */ + soutp(UART_IER, sinp(UART_IER) & + (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI))); + spin_unlock_irqrestore(&hardware[type].lock, flags); +} + +static ssize_t lirc_write(struct file *file, const char *buf, + size_t n, loff_t *ppos) +{ + int i, count; + unsigned long flags; + long delta = 0; + int *wbuf; + + if (!(hardware[type].features & LIRC_CAN_SEND_PULSE)) + return -EPERM; + + count = n / sizeof(int); + if (n % sizeof(int) || count % 2 == 0) + return -EINVAL; + wbuf = memdup_user(buf, n); + if (IS_ERR(wbuf)) + return PTR_ERR(wbuf); + spin_lock_irqsave(&hardware[type].lock, flags); + if (type == LIRC_IRDEO) { + /* DTR, RTS down */ + on(); + } + for (i = 0; i < count; i++) { + if (i%2) + hardware[type].send_space(wbuf[i] - delta); + else + delta = hardware[type].send_pulse(wbuf[i]); + } + off(); + spin_unlock_irqrestore(&hardware[type].lock, flags); + kfree(wbuf); + return n; +} + +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + int result; + __u32 value; + + switch (cmd) { + case LIRC_GET_SEND_MODE: + if (!(hardware[type].features&LIRC_CAN_SEND_MASK)) + return -ENOIOCTLCMD; + + result = put_user(LIRC_SEND2MODE + (hardware[type].features&LIRC_CAN_SEND_MASK), + (__u32 *) arg); + if (result) + return result; + break; + + case LIRC_SET_SEND_MODE: + if (!(hardware[type].features&LIRC_CAN_SEND_MASK)) + return -ENOIOCTLCMD; + + result = get_user(value, (__u32 *) arg); + if (result) + return result; + /* only LIRC_MODE_PULSE supported */ + if (value != LIRC_MODE_PULSE) + return -EINVAL; + break; + + case LIRC_GET_LENGTH: + return -ENOIOCTLCMD; + break; + + case LIRC_SET_SEND_DUTY_CYCLE: + dprintk("SET_SEND_DUTY_CYCLE\n"); + if (!(hardware[type].features&LIRC_CAN_SET_SEND_DUTY_CYCLE)) + return -ENOIOCTLCMD; + + result = get_user(value, (__u32 *) arg); + if (result) + return result; + if (value <= 0 || value > 100) + return -EINVAL; + return init_timing_params(value, freq); + break; + + case LIRC_SET_SEND_CARRIER: + dprintk("SET_SEND_CARRIER\n"); + if (!(hardware[type].features&LIRC_CAN_SET_SEND_CARRIER)) + return -ENOIOCTLCMD; + + result = get_user(value, (__u32 *) arg); + if (result) + return result; + if (value > 500000 || value < 20000) + return -EINVAL; + return init_timing_params(duty_cycle, value); + break; + + default: + return lirc_dev_fop_ioctl(filep, cmd, arg); + } + return 0; +} + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .write = lirc_write, + .unlocked_ioctl = lirc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = lirc_ioctl, +#endif + .read = lirc_dev_fop_read, + .poll = lirc_dev_fop_poll, + .open = lirc_dev_fop_open, + .release = lirc_dev_fop_close, + .llseek = no_llseek, +}; + +static struct lirc_driver driver = { + .name = LIRC_DRIVER_NAME, + .minor = -1, + .code_length = 1, + .sample_rate = 0, + .data = NULL, + .add_to_buf = NULL, + .rbuf = &rbuf, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .dev = NULL, + .owner = THIS_MODULE, +}; + +static struct platform_device *lirc_serial_dev; + +static int lirc_serial_suspend(struct platform_device *dev, + pm_message_t state) +{ + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* Disable all interrupts */ + soutp(UART_IER, sinp(UART_IER) & + (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI))); + + /* Clear registers. */ + sinp(UART_LSR); + sinp(UART_RX); + sinp(UART_IIR); + sinp(UART_MSR); + + return 0; +} + +/* twisty maze... need a forward-declaration here... */ +static void lirc_serial_exit(void); + +static int lirc_serial_resume(struct platform_device *dev) +{ + unsigned long flags; + int result; + + result = hardware_init_port(); + if (result < 0) + return result; + + spin_lock_irqsave(&hardware[type].lock, flags); + /* Enable Interrupt */ + do_gettimeofday(&lasttv); + soutp(UART_IER, sinp(UART_IER)|UART_IER_MSI); + off(); + + lirc_buffer_clear(&rbuf); + + spin_unlock_irqrestore(&hardware[type].lock, flags); + + return 0; +} + +static struct platform_driver lirc_serial_driver = { + .probe = lirc_serial_probe, + .remove = __devexit_p(lirc_serial_remove), + .suspend = lirc_serial_suspend, + .resume = lirc_serial_resume, + .driver = { + .name = "lirc_serial", + .owner = THIS_MODULE, + }, +}; + +static int __init lirc_serial_init(void) +{ + int result; + + /* Init read buffer. */ + result = lirc_buffer_init(&rbuf, sizeof(int), RBUF_LEN); + if (result < 0) + return result; + + result = platform_driver_register(&lirc_serial_driver); + if (result) { + printk("lirc register returned %d\n", result); + goto exit_buffer_free; + } + + lirc_serial_dev = platform_device_alloc("lirc_serial", 0); + if (!lirc_serial_dev) { + result = -ENOMEM; + goto exit_driver_unregister; + } + + result = platform_device_add(lirc_serial_dev); + if (result) + goto exit_device_put; + + return 0; + +exit_device_put: + platform_device_put(lirc_serial_dev); +exit_driver_unregister: + platform_driver_unregister(&lirc_serial_driver); +exit_buffer_free: + lirc_buffer_free(&rbuf); + return result; +} + +static void lirc_serial_exit(void) +{ + platform_device_unregister(lirc_serial_dev); + platform_driver_unregister(&lirc_serial_driver); + lirc_buffer_free(&rbuf); +} + +static int __init lirc_serial_init_module(void) +{ + int result; + + switch (type) { + case LIRC_HOMEBREW: + case LIRC_IRDEO: + case LIRC_IRDEO_REMOTE: + case LIRC_ANIMAX: + case LIRC_IGOR: + /* if nothing specified, use ttyS0/com1 and irq 4 */ + io = io ? io : 0x3f8; + irq = irq ? irq : 4; + break; +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + case LIRC_NSLU2: + io = io ? io : IRQ_IXP4XX_UART2; + irq = irq ? irq : (IXP4XX_UART2_BASE_VIRT + REG_OFFSET); + iommap = iommap ? iommap : IXP4XX_UART2_BASE_PHYS; + ioshift = ioshift ? ioshift : 2; + break; +#endif + default: + return -EINVAL; + } + if (!softcarrier) { + switch (type) { + case LIRC_HOMEBREW: + case LIRC_IGOR: +#ifdef CONFIG_LIRC_SERIAL_NSLU2 + case LIRC_NSLU2: +#endif + hardware[type].features &= + ~(LIRC_CAN_SET_SEND_DUTY_CYCLE| + LIRC_CAN_SET_SEND_CARRIER); + break; + } + } + + result = lirc_serial_init(); + if (result) + return result; + + driver.features = hardware[type].features; + driver.dev = &lirc_serial_dev->dev; + driver.minor = lirc_register_driver(&driver); + if (driver.minor < 0) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": register_chrdev failed!\n"); + lirc_serial_exit(); + return driver.minor; + } + return 0; +} + +static void __exit lirc_serial_exit_module(void) +{ + lirc_unregister_driver(driver.minor); + lirc_serial_exit(); + dprintk("cleaned up module\n"); +} + + +module_init(lirc_serial_init_module); +module_exit(lirc_serial_exit_module); + +MODULE_DESCRIPTION("Infra-red receiver driver for serial ports."); +MODULE_AUTHOR("Ralph Metzler, Trent Piepho, Ben Pfaff, " + "Christoph Bartelmus, Andrei Tanas"); +MODULE_LICENSE("GPL"); + +module_param(type, int, S_IRUGO); +MODULE_PARM_DESC(type, "Hardware type (0 = home-brew, 1 = IRdeo," + " 2 = IRdeo Remote, 3 = AnimaX, 4 = IgorPlug," + " 5 = NSLU2 RX:CTS2/TX:GreenLED)"); + +module_param(io, int, S_IRUGO); +MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)"); + +/* some architectures (e.g. intel xscale) have memory mapped registers */ +module_param(iommap, bool, S_IRUGO); +MODULE_PARM_DESC(iommap, "physical base for memory mapped I/O" + " (0 = no memory mapped io)"); + +/* + * some architectures (e.g. intel xscale) align the 8bit serial registers + * on 32bit word boundaries. + * See linux-kernel/drivers/tty/serial/8250/8250.c serial_in()/out() + */ +module_param(ioshift, int, S_IRUGO); +MODULE_PARM_DESC(ioshift, "shift I/O register offset (0 = no shift)"); + +module_param(irq, int, S_IRUGO); +MODULE_PARM_DESC(irq, "Interrupt (4 or 3)"); + +module_param(share_irq, bool, S_IRUGO); +MODULE_PARM_DESC(share_irq, "Share interrupts (0 = off, 1 = on)"); + +module_param(sense, bool, S_IRUGO); +MODULE_PARM_DESC(sense, "Override autodetection of IR receiver circuit" + " (0 = active high, 1 = active low )"); + +#ifdef CONFIG_LIRC_SERIAL_TRANSMITTER +module_param(txsense, bool, S_IRUGO); +MODULE_PARM_DESC(txsense, "Sense of transmitter circuit" + " (0 = active high, 1 = active low )"); +#endif + +module_param(softcarrier, bool, S_IRUGO); +MODULE_PARM_DESC(softcarrier, "Software carrier (0 = off, 1 = on, default on)"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging messages"); diff --git a/drivers/staging/media/lirc/lirc_sir.c b/drivers/staging/media/lirc/lirc_sir.c new file mode 100644 index 00000000..945d9623 --- /dev/null +++ b/drivers/staging/media/lirc/lirc_sir.c @@ -0,0 +1,1278 @@ +/* + * LIRC SIR driver, (C) 2000 Milan Pikula + * + * lirc_sir - Device driver for use with SIR (serial infra red) + * mode of IrDA on many notebooks. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * 2000/09/16 Frank Przybylski : + * added timeout and relaxed pulse detection, removed gap bug + * + * 2000/12/15 Christoph Bartelmus : + * added support for Tekram Irmate 210 (sending does not work yet, + * kind of disappointing that nobody was able to implement that + * before), + * major clean-up + * + * 2001/02/27 Christoph Bartelmus : + * added support for StrongARM SA1100 embedded microprocessor + * parts cut'n'pasted from sa1100_ir.c (C) 2000 Russell King + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef LIRC_ON_SA1100 +#include +#ifdef CONFIG_SA1100_COLLIE +#include +#include +#endif +#endif + +#include + +#include +#include + +/* SECTION: Definitions */ + +/*** Tekram dongle ***/ +#ifdef LIRC_SIR_TEKRAM +/* stolen from kernel source */ +/* definitions for Tekram dongle */ +#define TEKRAM_115200 0x00 +#define TEKRAM_57600 0x01 +#define TEKRAM_38400 0x02 +#define TEKRAM_19200 0x03 +#define TEKRAM_9600 0x04 +#define TEKRAM_2400 0x08 + +#define TEKRAM_PW 0x10 /* Pulse select bit */ + +/* 10bit * 1s/115200bit in milliseconds = 87ms*/ +#define TIME_CONST (10000000ul/115200ul) + +#endif + +#ifdef LIRC_SIR_ACTISYS_ACT200L +static void init_act200(void); +#elif defined(LIRC_SIR_ACTISYS_ACT220L) +static void init_act220(void); +#endif + +/*** SA1100 ***/ +#ifdef LIRC_ON_SA1100 +struct sa1100_ser2_registers { + /* HSSP control register */ + unsigned char hscr0; + /* UART registers */ + unsigned char utcr0; + unsigned char utcr1; + unsigned char utcr2; + unsigned char utcr3; + unsigned char utcr4; + unsigned char utdr; + unsigned char utsr0; + unsigned char utsr1; +} sr; + +static int irq = IRQ_Ser2ICP; + +#define LIRC_ON_SA1100_TRANSMITTER_LATENCY 0 + +/* pulse/space ratio of 50/50 */ +static unsigned long pulse_width = (13-LIRC_ON_SA1100_TRANSMITTER_LATENCY); +/* 1000000/freq-pulse_width */ +static unsigned long space_width = (13-LIRC_ON_SA1100_TRANSMITTER_LATENCY); +static unsigned int freq = 38000; /* modulation frequency */ +static unsigned int duty_cycle = 50; /* duty cycle of 50% */ + +#endif + +#define RBUF_LEN 1024 +#define WBUF_LEN 1024 + +#define LIRC_DRIVER_NAME "lirc_sir" + +#define PULSE '[' + +#ifndef LIRC_SIR_TEKRAM +/* 9bit * 1s/115200bit in milli seconds = 78.125ms*/ +#define TIME_CONST (9000000ul/115200ul) +#endif + + +/* timeout for sequences in jiffies (=5/100s), must be longer than TIME_CONST */ +#define SIR_TIMEOUT (HZ*5/100) + +#ifndef LIRC_ON_SA1100 +#ifndef LIRC_IRQ +#define LIRC_IRQ 4 +#endif +#ifndef LIRC_PORT +/* for external dongles, default to com1 */ +#if defined(LIRC_SIR_ACTISYS_ACT200L) || \ + defined(LIRC_SIR_ACTISYS_ACT220L) || \ + defined(LIRC_SIR_TEKRAM) +#define LIRC_PORT 0x3f8 +#else +/* onboard sir ports are typically com3 */ +#define LIRC_PORT 0x3e8 +#endif +#endif + +static int io = LIRC_PORT; +static int irq = LIRC_IRQ; +static int threshold = 3; +#endif + +static DEFINE_SPINLOCK(timer_lock); +static struct timer_list timerlist; +/* time of last signal change detected */ +static struct timeval last_tv = {0, 0}; +/* time of last UART data ready interrupt */ +static struct timeval last_intr_tv = {0, 0}; +static int last_value; + +static DECLARE_WAIT_QUEUE_HEAD(lirc_read_queue); + +static DEFINE_SPINLOCK(hardware_lock); + +static int rx_buf[RBUF_LEN]; +static unsigned int rx_tail, rx_head; + +static bool debug; +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG LIRC_DRIVER_NAME ": " \ + fmt, ## args); \ + } while (0) + +/* SECTION: Prototypes */ + +/* Communication with user-space */ +static unsigned int lirc_poll(struct file *file, poll_table *wait); +static ssize_t lirc_read(struct file *file, char *buf, size_t count, + loff_t *ppos); +static ssize_t lirc_write(struct file *file, const char *buf, size_t n, + loff_t *pos); +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg); +static void add_read_queue(int flag, unsigned long val); +static int init_chrdev(void); +static void drop_chrdev(void); +/* Hardware */ +static irqreturn_t sir_interrupt(int irq, void *dev_id); +static void send_space(unsigned long len); +static void send_pulse(unsigned long len); +static int init_hardware(void); +static void drop_hardware(void); +/* Initialisation */ +static int init_port(void); +static void drop_port(void); + +#ifdef LIRC_ON_SA1100 +static void on(void) +{ + PPSR |= PPC_TXD2; +} + +static void off(void) +{ + PPSR &= ~PPC_TXD2; +} +#else +static inline unsigned int sinp(int offset) +{ + return inb(io + offset); +} + +static inline void soutp(int offset, int value) +{ + outb(value, io + offset); +} +#endif + +#ifndef MAX_UDELAY_MS +#define MAX_UDELAY_US 5000 +#else +#define MAX_UDELAY_US (MAX_UDELAY_MS*1000) +#endif + +static void safe_udelay(unsigned long usecs) +{ + while (usecs > MAX_UDELAY_US) { + udelay(MAX_UDELAY_US); + usecs -= MAX_UDELAY_US; + } + udelay(usecs); +} + +/* SECTION: Communication with user-space */ + +static unsigned int lirc_poll(struct file *file, poll_table *wait) +{ + poll_wait(file, &lirc_read_queue, wait); + if (rx_head != rx_tail) + return POLLIN | POLLRDNORM; + return 0; +} + +static ssize_t lirc_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + int n = 0; + int retval = 0; + DECLARE_WAITQUEUE(wait, current); + + if (count % sizeof(int)) + return -EINVAL; + + add_wait_queue(&lirc_read_queue, &wait); + set_current_state(TASK_INTERRUPTIBLE); + while (n < count) { + if (rx_head != rx_tail) { + if (copy_to_user((void *) buf + n, + (void *) (rx_buf + rx_head), + sizeof(int))) { + retval = -EFAULT; + break; + } + rx_head = (rx_head + 1) & (RBUF_LEN - 1); + n += sizeof(int); + } else { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + } + remove_wait_queue(&lirc_read_queue, &wait); + set_current_state(TASK_RUNNING); + return n ? n : retval; +} +static ssize_t lirc_write(struct file *file, const char *buf, size_t n, + loff_t *pos) +{ + unsigned long flags; + int i, count; + int *tx_buf; + + count = n / sizeof(int); + if (n % sizeof(int) || count % 2 == 0) + return -EINVAL; + tx_buf = memdup_user(buf, n); + if (IS_ERR(tx_buf)) + return PTR_ERR(tx_buf); + i = 0; +#ifdef LIRC_ON_SA1100 + /* disable receiver */ + Ser2UTCR3 = 0; +#endif + local_irq_save(flags); + while (1) { + if (i >= count) + break; + if (tx_buf[i]) + send_pulse(tx_buf[i]); + i++; + if (i >= count) + break; + if (tx_buf[i]) + send_space(tx_buf[i]); + i++; + } + local_irq_restore(flags); +#ifdef LIRC_ON_SA1100 + off(); + udelay(1000); /* wait 1ms for IR diode to recover */ + Ser2UTCR3 = 0; + /* clear status register to prevent unwanted interrupts */ + Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB); + /* enable receiver */ + Ser2UTCR3 = UTCR3_RXE|UTCR3_RIE; +#endif + kfree(tx_buf); + return count; +} + +static long lirc_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + __u32 value = 0; +#ifdef LIRC_ON_SA1100 + + if (cmd == LIRC_GET_FEATURES) + value = LIRC_CAN_SEND_PULSE | + LIRC_CAN_SET_SEND_DUTY_CYCLE | + LIRC_CAN_SET_SEND_CARRIER | + LIRC_CAN_REC_MODE2; + else if (cmd == LIRC_GET_SEND_MODE) + value = LIRC_MODE_PULSE; + else if (cmd == LIRC_GET_REC_MODE) + value = LIRC_MODE_MODE2; +#else + if (cmd == LIRC_GET_FEATURES) + value = LIRC_CAN_SEND_PULSE | LIRC_CAN_REC_MODE2; + else if (cmd == LIRC_GET_SEND_MODE) + value = LIRC_MODE_PULSE; + else if (cmd == LIRC_GET_REC_MODE) + value = LIRC_MODE_MODE2; +#endif + + switch (cmd) { + case LIRC_GET_FEATURES: + case LIRC_GET_SEND_MODE: + case LIRC_GET_REC_MODE: + retval = put_user(value, (__u32 *) arg); + break; + + case LIRC_SET_SEND_MODE: + case LIRC_SET_REC_MODE: + retval = get_user(value, (__u32 *) arg); + break; +#ifdef LIRC_ON_SA1100 + case LIRC_SET_SEND_DUTY_CYCLE: + retval = get_user(value, (__u32 *) arg); + if (retval) + return retval; + if (value <= 0 || value > 100) + return -EINVAL; + /* (value/100)*(1000000/freq) */ + duty_cycle = value; + pulse_width = (unsigned long) duty_cycle*10000/freq; + space_width = (unsigned long) 1000000L/freq-pulse_width; + if (pulse_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY) + pulse_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY; + if (space_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY) + space_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY; + break; + case LIRC_SET_SEND_CARRIER: + retval = get_user(value, (__u32 *) arg); + if (retval) + return retval; + if (value > 500000 || value < 20000) + return -EINVAL; + freq = value; + pulse_width = (unsigned long) duty_cycle*10000/freq; + space_width = (unsigned long) 1000000L/freq-pulse_width; + if (pulse_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY) + pulse_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY; + if (space_width >= LIRC_ON_SA1100_TRANSMITTER_LATENCY) + space_width -= LIRC_ON_SA1100_TRANSMITTER_LATENCY; + break; +#endif + default: + retval = -ENOIOCTLCMD; + + } + + if (retval) + return retval; + if (cmd == LIRC_SET_REC_MODE) { + if (value != LIRC_MODE_MODE2) + retval = -ENOSYS; + } else if (cmd == LIRC_SET_SEND_MODE) { + if (value != LIRC_MODE_PULSE) + retval = -ENOSYS; + } + + return retval; +} + +static void add_read_queue(int flag, unsigned long val) +{ + unsigned int new_rx_tail; + int newval; + + dprintk("add flag %d with val %lu\n", flag, val); + + newval = val & PULSE_MASK; + + /* + * statistically, pulses are ~TIME_CONST/2 too long. we could + * maybe make this more exact, but this is good enough + */ + if (flag) { + /* pulse */ + if (newval > TIME_CONST/2) + newval -= TIME_CONST/2; + else /* should not ever happen */ + newval = 1; + newval |= PULSE_BIT; + } else { + newval += TIME_CONST/2; + } + new_rx_tail = (rx_tail + 1) & (RBUF_LEN - 1); + if (new_rx_tail == rx_head) { + dprintk("Buffer overrun.\n"); + return; + } + rx_buf[rx_tail] = newval; + rx_tail = new_rx_tail; + wake_up_interruptible(&lirc_read_queue); +} + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .read = lirc_read, + .write = lirc_write, + .poll = lirc_poll, + .unlocked_ioctl = lirc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = lirc_ioctl, +#endif + .open = lirc_dev_fop_open, + .release = lirc_dev_fop_close, + .llseek = no_llseek, +}; + +static int set_use_inc(void *data) +{ + return 0; +} + +static void set_use_dec(void *data) +{ +} + +static struct lirc_driver driver = { + .name = LIRC_DRIVER_NAME, + .minor = -1, + .code_length = 1, + .sample_rate = 0, + .data = NULL, + .add_to_buf = NULL, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .dev = NULL, + .owner = THIS_MODULE, +}; + + +static int init_chrdev(void) +{ + driver.minor = lirc_register_driver(&driver); + if (driver.minor < 0) { + printk(KERN_ERR LIRC_DRIVER_NAME ": init_chrdev() failed.\n"); + return -EIO; + } + return 0; +} + +static void drop_chrdev(void) +{ + lirc_unregister_driver(driver.minor); +} + +/* SECTION: Hardware */ +static long delta(struct timeval *tv1, struct timeval *tv2) +{ + unsigned long deltv; + + deltv = tv2->tv_sec - tv1->tv_sec; + if (deltv > 15) + deltv = 0xFFFFFF; + else + deltv = deltv*1000000 + + tv2->tv_usec - + tv1->tv_usec; + return deltv; +} + +static void sir_timeout(unsigned long data) +{ + /* + * if last received signal was a pulse, but receiving stopped + * within the 9 bit frame, we need to finish this pulse and + * simulate a signal change to from pulse to space. Otherwise + * upper layers will receive two sequences next time. + */ + + unsigned long flags; + unsigned long pulse_end; + + /* avoid interference with interrupt */ + spin_lock_irqsave(&timer_lock, flags); + if (last_value) { +#ifndef LIRC_ON_SA1100 + /* clear unread bits in UART and restart */ + outb(UART_FCR_CLEAR_RCVR, io + UART_FCR); +#endif + /* determine 'virtual' pulse end: */ + pulse_end = delta(&last_tv, &last_intr_tv); + dprintk("timeout add %d for %lu usec\n", last_value, pulse_end); + add_read_queue(last_value, pulse_end); + last_value = 0; + last_tv = last_intr_tv; + } + spin_unlock_irqrestore(&timer_lock, flags); +} + +static irqreturn_t sir_interrupt(int irq, void *dev_id) +{ + unsigned char data; + struct timeval curr_tv; + static unsigned long deltv; +#ifdef LIRC_ON_SA1100 + int status; + static int n; + + status = Ser2UTSR0; + /* + * Deal with any receive errors first. The bytes in error may be + * the only bytes in the receive FIFO, so we do this first. + */ + while (status & UTSR0_EIF) { + int bstat; + + if (debug) { + dprintk("EIF\n"); + bstat = Ser2UTSR1; + + if (bstat & UTSR1_FRE) + dprintk("frame error\n"); + if (bstat & UTSR1_ROR) + dprintk("receive fifo overrun\n"); + if (bstat & UTSR1_PRE) + dprintk("parity error\n"); + } + + bstat = Ser2UTDR; + n++; + status = Ser2UTSR0; + } + + if (status & (UTSR0_RFS | UTSR0_RID)) { + do_gettimeofday(&curr_tv); + deltv = delta(&last_tv, &curr_tv); + do { + data = Ser2UTDR; + dprintk("%d data: %u\n", n, (unsigned int) data); + n++; + } while (status & UTSR0_RID && /* do not empty fifo in order to + * get UTSR0_RID in any case */ + Ser2UTSR1 & UTSR1_RNE); /* data ready */ + + if (status&UTSR0_RID) { + add_read_queue(0 , deltv - n * TIME_CONST); /*space*/ + add_read_queue(1, n * TIME_CONST); /*pulse*/ + n = 0; + last_tv = curr_tv; + } + } + + if (status & UTSR0_TFS) + printk(KERN_ERR "transmit fifo not full, shouldn't happen\n"); + + /* We must clear certain bits. */ + status &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB); + if (status) + Ser2UTSR0 = status; +#else + unsigned long deltintrtv; + unsigned long flags; + int iir, lsr; + + while ((iir = inb(io + UART_IIR) & UART_IIR_ID)) { + switch (iir&UART_IIR_ID) { /* FIXME toto treba preriedit */ + case UART_IIR_MSI: + (void) inb(io + UART_MSR); + break; + case UART_IIR_RLSI: + (void) inb(io + UART_LSR); + break; + case UART_IIR_THRI: +#if 0 + if (lsr & UART_LSR_THRE) /* FIFO is empty */ + outb(data, io + UART_TX) +#endif + break; + case UART_IIR_RDI: + /* avoid interference with timer */ + spin_lock_irqsave(&timer_lock, flags); + do { + del_timer(&timerlist); + data = inb(io + UART_RX); + do_gettimeofday(&curr_tv); + deltv = delta(&last_tv, &curr_tv); + deltintrtv = delta(&last_intr_tv, &curr_tv); + dprintk("t %lu, d %d\n", deltintrtv, (int)data); + /* + * if nothing came in last X cycles, + * it was gap + */ + if (deltintrtv > TIME_CONST * threshold) { + if (last_value) { + dprintk("GAP\n"); + /* simulate signal change */ + add_read_queue(last_value, + deltv - + deltintrtv); + last_value = 0; + last_tv.tv_sec = + last_intr_tv.tv_sec; + last_tv.tv_usec = + last_intr_tv.tv_usec; + deltv = deltintrtv; + } + } + data = 1; + if (data ^ last_value) { + /* + * deltintrtv > 2*TIME_CONST, remember? + * the other case is timeout + */ + add_read_queue(last_value, + deltv-TIME_CONST); + last_value = data; + last_tv = curr_tv; + if (last_tv.tv_usec >= TIME_CONST) { + last_tv.tv_usec -= TIME_CONST; + } else { + last_tv.tv_sec--; + last_tv.tv_usec += 1000000 - + TIME_CONST; + } + } + last_intr_tv = curr_tv; + if (data) { + /* + * start timer for end of + * sequence detection + */ + timerlist.expires = jiffies + + SIR_TIMEOUT; + add_timer(&timerlist); + } + + lsr = inb(io + UART_LSR); + } while (lsr & UART_LSR_DR); /* data ready */ + spin_unlock_irqrestore(&timer_lock, flags); + break; + default: + break; + } + } +#endif + return IRQ_RETVAL(IRQ_HANDLED); +} + +#ifdef LIRC_ON_SA1100 +static void send_pulse(unsigned long length) +{ + unsigned long k, delay; + int flag; + + if (length == 0) + return; + /* + * this won't give us the carrier frequency we really want + * due to integer arithmetic, but we can accept this inaccuracy + */ + + for (k = flag = 0; k < length; k += delay, flag = !flag) { + if (flag) { + off(); + delay = space_width; + } else { + on(); + delay = pulse_width; + } + safe_udelay(delay); + } + off(); +} + +static void send_space(unsigned long length) +{ + if (length == 0) + return; + off(); + safe_udelay(length); +} +#else +static void send_space(unsigned long len) +{ + safe_udelay(len); +} + +static void send_pulse(unsigned long len) +{ + long bytes_out = len / TIME_CONST; + + if (bytes_out == 0) + bytes_out++; + + while (bytes_out--) { + outb(PULSE, io + UART_TX); + /* FIXME treba seriozne cakanie z char/serial.c */ + while (!(inb(io + UART_LSR) & UART_LSR_THRE)) + ; + } +} +#endif + +#ifdef CONFIG_SA1100_COLLIE +static int sa1100_irda_set_power_collie(int state) +{ + if (state) { + /* + * 0 - off + * 1 - short range, lowest power + * 2 - medium range, medium power + * 3 - maximum range, high power + */ + ucb1200_set_io_direction(TC35143_GPIO_IR_ON, + TC35143_IODIR_OUTPUT); + ucb1200_set_io(TC35143_GPIO_IR_ON, TC35143_IODAT_LOW); + udelay(100); + } else { + /* OFF */ + ucb1200_set_io_direction(TC35143_GPIO_IR_ON, + TC35143_IODIR_OUTPUT); + ucb1200_set_io(TC35143_GPIO_IR_ON, TC35143_IODAT_HIGH); + } + return 0; +} +#endif + +static int init_hardware(void) +{ + unsigned long flags; + + spin_lock_irqsave(&hardware_lock, flags); + /* reset UART */ +#ifdef LIRC_ON_SA1100 +#ifdef CONFIG_SA1100_BITSY + if (machine_is_bitsy()) { + printk(KERN_INFO "Power on IR module\n"); + set_bitsy_egpio(EGPIO_BITSY_IR_ON); + } +#endif +#ifdef CONFIG_SA1100_COLLIE + sa1100_irda_set_power_collie(3); /* power on */ +#endif + sr.hscr0 = Ser2HSCR0; + + sr.utcr0 = Ser2UTCR0; + sr.utcr1 = Ser2UTCR1; + sr.utcr2 = Ser2UTCR2; + sr.utcr3 = Ser2UTCR3; + sr.utcr4 = Ser2UTCR4; + + sr.utdr = Ser2UTDR; + sr.utsr0 = Ser2UTSR0; + sr.utsr1 = Ser2UTSR1; + + /* configure GPIO */ + /* output */ + PPDR |= PPC_TXD2; + PSDR |= PPC_TXD2; + /* set output to 0 */ + off(); + + /* Enable HP-SIR modulation, and ensure that the port is disabled. */ + Ser2UTCR3 = 0; + Ser2HSCR0 = sr.hscr0 & (~HSCR0_HSSP); + + /* clear status register to prevent unwanted interrupts */ + Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB); + + /* 7N1 */ + Ser2UTCR0 = UTCR0_1StpBit|UTCR0_7BitData; + /* 115200 */ + Ser2UTCR1 = 0; + Ser2UTCR2 = 1; + /* use HPSIR, 1.6 usec pulses */ + Ser2UTCR4 = UTCR4_HPSIR|UTCR4_Z1_6us; + + /* enable receiver, receive fifo interrupt */ + Ser2UTCR3 = UTCR3_RXE|UTCR3_RIE; + + /* clear status register to prevent unwanted interrupts */ + Ser2UTSR0 &= (UTSR0_RID | UTSR0_RBB | UTSR0_REB); + +#elif defined(LIRC_SIR_TEKRAM) + /* disable FIFO */ + soutp(UART_FCR, + UART_FCR_CLEAR_RCVR| + UART_FCR_CLEAR_XMIT| + UART_FCR_TRIGGER_1); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* First of all, disable all interrupts */ + soutp(UART_IER, sinp(UART_IER) & + (~(UART_IER_MSI|UART_IER_RLSI|UART_IER_THRI|UART_IER_RDI))); + + /* Set DLAB 1. */ + soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB); + + /* Set divisor to 12 => 9600 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 12); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* power supply */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + safe_udelay(50*1000); + + /* -DTR low -> reset PIC */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2); + udelay(1*1000); + + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + udelay(100); + + + /* -RTS low -> send control byte */ + soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2); + udelay(7); + soutp(UART_TX, TEKRAM_115200|TEKRAM_PW); + + /* one byte takes ~1042 usec to transmit at 9600,8N1 */ + udelay(1500); + + /* back to normal operation */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + udelay(50); + + udelay(1500); + + /* read previous control byte */ + printk(KERN_INFO LIRC_DRIVER_NAME + ": 0x%02x\n", sinp(UART_RX)); + + /* Set DLAB 1. */ + soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB); + + /* Set divisor to 1 => 115200 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 1); + + /* Set DLAB 0, 8 Bit */ + soutp(UART_LCR, UART_LCR_WLEN8); + /* enable interrupts */ + soutp(UART_IER, sinp(UART_IER)|UART_IER_RDI); +#else + outb(0, io + UART_MCR); + outb(0, io + UART_IER); + /* init UART */ + /* set DLAB, speed = 115200 */ + outb(UART_LCR_DLAB | UART_LCR_WLEN7, io + UART_LCR); + outb(1, io + UART_DLL); outb(0, io + UART_DLM); + /* 7N1+start = 9 bits at 115200 ~ 3 bits at 44000 */ + outb(UART_LCR_WLEN7, io + UART_LCR); + /* FIFO operation */ + outb(UART_FCR_ENABLE_FIFO, io + UART_FCR); + /* interrupts */ + /* outb(UART_IER_RLSI|UART_IER_RDI|UART_IER_THRI, io + UART_IER); */ + outb(UART_IER_RDI, io + UART_IER); + /* turn on UART */ + outb(UART_MCR_DTR|UART_MCR_RTS|UART_MCR_OUT2, io + UART_MCR); +#ifdef LIRC_SIR_ACTISYS_ACT200L + init_act200(); +#elif defined(LIRC_SIR_ACTISYS_ACT220L) + init_act220(); +#endif +#endif + spin_unlock_irqrestore(&hardware_lock, flags); + return 0; +} + +static void drop_hardware(void) +{ + unsigned long flags; + + spin_lock_irqsave(&hardware_lock, flags); + +#ifdef LIRC_ON_SA1100 + Ser2UTCR3 = 0; + + Ser2UTCR0 = sr.utcr0; + Ser2UTCR1 = sr.utcr1; + Ser2UTCR2 = sr.utcr2; + Ser2UTCR4 = sr.utcr4; + Ser2UTCR3 = sr.utcr3; + + Ser2HSCR0 = sr.hscr0; +#ifdef CONFIG_SA1100_BITSY + if (machine_is_bitsy()) + clr_bitsy_egpio(EGPIO_BITSY_IR_ON); +#endif +#ifdef CONFIG_SA1100_COLLIE + sa1100_irda_set_power_collie(0); /* power off */ +#endif +#else + /* turn off interrupts */ + outb(0, io + UART_IER); +#endif + spin_unlock_irqrestore(&hardware_lock, flags); +} + +/* SECTION: Initialisation */ + +static int init_port(void) +{ + int retval; + + /* get I/O port access and IRQ line */ +#ifndef LIRC_ON_SA1100 + if (request_region(io, 8, LIRC_DRIVER_NAME) == NULL) { + printk(KERN_ERR LIRC_DRIVER_NAME + ": i/o port 0x%.4x already in use.\n", io); + return -EBUSY; + } +#endif + retval = request_irq(irq, sir_interrupt, 0, + LIRC_DRIVER_NAME, NULL); + if (retval < 0) { +# ifndef LIRC_ON_SA1100 + release_region(io, 8); +# endif + printk(KERN_ERR LIRC_DRIVER_NAME + ": IRQ %d already in use.\n", + irq); + return retval; + } +#ifndef LIRC_ON_SA1100 + printk(KERN_INFO LIRC_DRIVER_NAME + ": I/O port 0x%.4x, IRQ %d.\n", + io, irq); +#endif + + init_timer(&timerlist); + timerlist.function = sir_timeout; + timerlist.data = 0xabadcafe; + + return 0; +} + +static void drop_port(void) +{ + free_irq(irq, NULL); + del_timer_sync(&timerlist); +#ifndef LIRC_ON_SA1100 + release_region(io, 8); +#endif +} + +#ifdef LIRC_SIR_ACTISYS_ACT200L +/* Crystal/Cirrus CS8130 IR transceiver, used in Actisys Act200L dongle */ +/* some code borrowed from Linux IRDA driver */ + +/* Register 0: Control register #1 */ +#define ACT200L_REG0 0x00 +#define ACT200L_TXEN 0x01 /* Enable transmitter */ +#define ACT200L_RXEN 0x02 /* Enable receiver */ +#define ACT200L_ECHO 0x08 /* Echo control chars */ + +/* Register 1: Control register #2 */ +#define ACT200L_REG1 0x10 +#define ACT200L_LODB 0x01 /* Load new baud rate count value */ +#define ACT200L_WIDE 0x04 /* Expand the maximum allowable pulse */ + +/* Register 3: Transmit mode register #2 */ +#define ACT200L_REG3 0x30 +#define ACT200L_B0 0x01 /* DataBits, 0=6, 1=7, 2=8, 3=9(8P) */ +#define ACT200L_B1 0x02 /* DataBits, 0=6, 1=7, 2=8, 3=9(8P) */ +#define ACT200L_CHSY 0x04 /* StartBit Synced 0=bittime, 1=startbit */ + +/* Register 4: Output Power register */ +#define ACT200L_REG4 0x40 +#define ACT200L_OP0 0x01 /* Enable LED1C output */ +#define ACT200L_OP1 0x02 /* Enable LED2C output */ +#define ACT200L_BLKR 0x04 + +/* Register 5: Receive Mode register */ +#define ACT200L_REG5 0x50 +#define ACT200L_RWIDL 0x01 /* fixed 1.6us pulse mode */ + /*.. other various IRDA bit modes, and TV remote modes..*/ + +/* Register 6: Receive Sensitivity register #1 */ +#define ACT200L_REG6 0x60 +#define ACT200L_RS0 0x01 /* receive threshold bit 0 */ +#define ACT200L_RS1 0x02 /* receive threshold bit 1 */ + +/* Register 7: Receive Sensitivity register #2 */ +#define ACT200L_REG7 0x70 +#define ACT200L_ENPOS 0x04 /* Ignore the falling edge */ + +/* Register 8,9: Baud Rate Divider register #1,#2 */ +#define ACT200L_REG8 0x80 +#define ACT200L_REG9 0x90 + +#define ACT200L_2400 0x5f +#define ACT200L_9600 0x17 +#define ACT200L_19200 0x0b +#define ACT200L_38400 0x05 +#define ACT200L_57600 0x03 +#define ACT200L_115200 0x01 + +/* Register 13: Control register #3 */ +#define ACT200L_REG13 0xd0 +#define ACT200L_SHDW 0x01 /* Enable access to shadow registers */ + +/* Register 15: Status register */ +#define ACT200L_REG15 0xf0 + +/* Register 21: Control register #4 */ +#define ACT200L_REG21 0x50 +#define ACT200L_EXCK 0x02 /* Disable clock output driver */ +#define ACT200L_OSCL 0x04 /* oscillator in low power, medium accuracy mode */ + +static void init_act200(void) +{ + int i; + __u8 control[] = { + ACT200L_REG15, + ACT200L_REG13 | ACT200L_SHDW, + ACT200L_REG21 | ACT200L_EXCK | ACT200L_OSCL, + ACT200L_REG13, + ACT200L_REG7 | ACT200L_ENPOS, + ACT200L_REG6 | ACT200L_RS0 | ACT200L_RS1, + ACT200L_REG5 | ACT200L_RWIDL, + ACT200L_REG4 | ACT200L_OP0 | ACT200L_OP1 | ACT200L_BLKR, + ACT200L_REG3 | ACT200L_B0, + ACT200L_REG0 | ACT200L_TXEN | ACT200L_RXEN, + ACT200L_REG8 | (ACT200L_115200 & 0x0f), + ACT200L_REG9 | ((ACT200L_115200 >> 4) & 0x0f), + ACT200L_REG1 | ACT200L_LODB | ACT200L_WIDE + }; + + /* Set DLAB 1. */ + soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8); + + /* Set divisor to 12 => 9600 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 12); + + /* Set DLAB 0. */ + soutp(UART_LCR, UART_LCR_WLEN8); + /* Set divisor to 12 => 9600 Baud */ + + /* power supply */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + for (i = 0; i < 50; i++) + safe_udelay(1000); + + /* Reset the dongle : set RTS low for 25 ms */ + soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2); + for (i = 0; i < 25; i++) + udelay(1000); + + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + udelay(100); + + /* Clear DTR and set RTS to enter command mode */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2); + udelay(7); + + /* send out the control register settings for 115K 7N1 SIR operation */ + for (i = 0; i < sizeof(control); i++) { + soutp(UART_TX, control[i]); + /* one byte takes ~1042 usec to transmit at 9600,8N1 */ + udelay(1500); + } + + /* back to normal operation */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + udelay(50); + + udelay(1500); + soutp(UART_LCR, sinp(UART_LCR) | UART_LCR_DLAB); + + /* Set DLAB 1. */ + soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN7); + + /* Set divisor to 1 => 115200 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 1); + + /* Set DLAB 0. */ + soutp(UART_LCR, sinp(UART_LCR) & (~UART_LCR_DLAB)); + + /* Set DLAB 0, 7 Bit */ + soutp(UART_LCR, UART_LCR_WLEN7); + + /* enable interrupts */ + soutp(UART_IER, sinp(UART_IER)|UART_IER_RDI); +} +#endif + +#ifdef LIRC_SIR_ACTISYS_ACT220L +/* + * Derived from linux IrDA driver (net/irda/actisys.c) + * Drop me a mail for any kind of comment: maxx@spaceboyz.net + */ + +void init_act220(void) +{ + int i; + + /* DLAB 1 */ + soutp(UART_LCR, UART_LCR_DLAB|UART_LCR_WLEN7); + + /* 9600 baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 12); + + /* DLAB 0 */ + soutp(UART_LCR, UART_LCR_WLEN7); + + /* reset the dongle, set DTR low for 10us */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_OUT2); + udelay(10); + + /* back to normal (still 9600) */ + soutp(UART_MCR, UART_MCR_DTR|UART_MCR_RTS|UART_MCR_OUT2); + + /* + * send RTS pulses until we reach 115200 + * i hope this is really the same for act220l/act220l+ + */ + for (i = 0; i < 3; i++) { + udelay(10); + /* set RTS low for 10 us */ + soutp(UART_MCR, UART_MCR_DTR|UART_MCR_OUT2); + udelay(10); + /* set RTS high for 10 us */ + soutp(UART_MCR, UART_MCR_RTS|UART_MCR_DTR|UART_MCR_OUT2); + } + + /* back to normal operation */ + udelay(1500); /* better safe than sorry ;) */ + + /* Set DLAB 1. */ + soutp(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN7); + + /* Set divisor to 1 => 115200 Baud */ + soutp(UART_DLM, 0); + soutp(UART_DLL, 1); + + /* Set DLAB 0, 7 Bit */ + /* The dongle doesn't seem to have any problems with operation at 7N1 */ + soutp(UART_LCR, UART_LCR_WLEN7); + + /* enable interrupts */ + soutp(UART_IER, UART_IER_RDI); +} +#endif + +static int init_lirc_sir(void) +{ + int retval; + + init_waitqueue_head(&lirc_read_queue); + retval = init_port(); + if (retval < 0) + return retval; + init_hardware(); + printk(KERN_INFO LIRC_DRIVER_NAME + ": Installed.\n"); + return 0; +} + + +static int __init lirc_sir_init(void) +{ + int retval; + + retval = init_chrdev(); + if (retval < 0) + return retval; + retval = init_lirc_sir(); + if (retval) { + drop_chrdev(); + return retval; + } + return 0; +} + +static void __exit lirc_sir_exit(void) +{ + drop_hardware(); + drop_chrdev(); + drop_port(); + printk(KERN_INFO LIRC_DRIVER_NAME ": Uninstalled.\n"); +} + +module_init(lirc_sir_init); +module_exit(lirc_sir_exit); + +#ifdef LIRC_SIR_TEKRAM +MODULE_DESCRIPTION("Infrared receiver driver for Tekram Irmate 210"); +MODULE_AUTHOR("Christoph Bartelmus"); +#elif defined(LIRC_ON_SA1100) +MODULE_DESCRIPTION("LIRC driver for StrongARM SA1100 embedded microprocessor"); +MODULE_AUTHOR("Christoph Bartelmus"); +#elif defined(LIRC_SIR_ACTISYS_ACT200L) +MODULE_DESCRIPTION("LIRC driver for Actisys Act200L"); +MODULE_AUTHOR("Karl Bongers"); +#elif defined(LIRC_SIR_ACTISYS_ACT220L) +MODULE_DESCRIPTION("LIRC driver for Actisys Act220L(+)"); +MODULE_AUTHOR("Jan Roemisch"); +#else +MODULE_DESCRIPTION("Infrared receiver driver for SIR type serial ports"); +MODULE_AUTHOR("Milan Pikula"); +#endif +MODULE_LICENSE("GPL"); + +#ifdef LIRC_ON_SA1100 +module_param(irq, int, S_IRUGO); +MODULE_PARM_DESC(irq, "Interrupt (16)"); +#else +module_param(io, int, S_IRUGO); +MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)"); + +module_param(irq, int, S_IRUGO); +MODULE_PARM_DESC(irq, "Interrupt (4 or 3)"); + +module_param(threshold, int, S_IRUGO); +MODULE_PARM_DESC(threshold, "space detection threshold (3)"); +#endif + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging messages"); diff --git a/drivers/staging/media/lirc/lirc_ttusbir.c b/drivers/staging/media/lirc/lirc_ttusbir.c new file mode 100644 index 00000000..7950887f --- /dev/null +++ b/drivers/staging/media/lirc/lirc_ttusbir.c @@ -0,0 +1,375 @@ +/* + * lirc_ttusbir.c + * + * lirc_ttusbir - LIRC device driver for the TechnoTrend USB IR Receiver + * + * Copyright (C) 2007 Stefan Macher + * + * This LIRC driver provides access to the TechnoTrend USB IR Receiver. + * The receiver delivers the IR signal as raw sampled true/false data in + * isochronous USB packets each of size 128 byte. + * Currently the driver reduces the sampling rate by factor of 8 as this + * is still more than enough to decode RC-5 - others should be analyzed. + * But the driver does not rely on RC-5 it should be able to decode every + * IR signal that is not too fast. + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +MODULE_DESCRIPTION("TechnoTrend USB IR device driver for LIRC"); +MODULE_AUTHOR("Stefan Macher (st_maker-lirc@yahoo.de)"); +MODULE_LICENSE("GPL"); + +/* #define DEBUG */ +#ifdef DEBUG +#define DPRINTK printk +#else +#define DPRINTK(_x_, a...) +#endif + +/* function declarations */ +static int probe(struct usb_interface *intf, const struct usb_device_id *id); +static void disconnect(struct usb_interface *intf); +static void urb_complete(struct urb *urb); +static int set_use_inc(void *data); +static void set_use_dec(void *data); + +static int num_urbs = 2; +module_param(num_urbs, int, S_IRUGO); +MODULE_PARM_DESC(num_urbs, + "Number of URBs in queue. Try to increase to 4 in case " + "of problems (default: 2; minimum: 2)"); + +/* table of devices that work with this driver */ +static struct usb_device_id device_id_table[] = { + /* TechnoTrend USB IR Receiver */ + { USB_DEVICE(0x0B48, 0x2003) }, + /* Terminating entry */ + { } +}; +MODULE_DEVICE_TABLE(usb, device_id_table); + +/* USB driver definition */ +static struct usb_driver usb_driver = { + .name = "TTUSBIR", + .id_table = &(device_id_table[0]), + .probe = probe, + .disconnect = disconnect, +}; + +/* USB device definition */ +struct ttusbir_device { + struct usb_driver *usb_driver; + struct usb_device *udev; + struct usb_interface *interf; + struct usb_class_driver class_driver; + unsigned int ifnum; /* Interface number to use */ + unsigned int alt_setting; /* alternate setting to use */ + unsigned int endpoint; /* Endpoint to use */ + struct urb **urb; /* num_urb URB pointers*/ + char **buffer; /* 128 byte buffer for each URB */ + struct lirc_buffer rbuf; /* Buffer towards LIRC */ + struct lirc_driver driver; + int minor; + int last_pulse; /* remembers if last received byte was pulse or space */ + int last_num; /* remembers how many last bytes appeared */ + int opened; +}; + +/*** LIRC specific functions ***/ +static int set_use_inc(void *data) +{ + int i, retval; + struct ttusbir_device *ttusbir = data; + + DPRINTK("Sending first URBs\n"); + /* @TODO Do I need to check if I am already opened */ + ttusbir->opened = 1; + + for (i = 0; i < num_urbs; i++) { + retval = usb_submit_urb(ttusbir->urb[i], GFP_KERNEL); + if (retval) { + err("%s: usb_submit_urb failed on urb %d", + __func__, i); + return retval; + } + } + return 0; +} + +static void set_use_dec(void *data) +{ + struct ttusbir_device *ttusbir = data; + + DPRINTK("Device closed\n"); + + ttusbir->opened = 0; +} + +/*** USB specific functions ***/ + +/* + * This mapping table is used to do a very simple filtering of the + * input signal. + * For a value with at least 4 bits set it returns 0xFF otherwise + * 0x00. For faster IR signals this can not be used. But for RC-5 we + * still have about 14 samples per pulse/space, i.e. we sample with 14 + * times higher frequency than the signal frequency + */ +const unsigned char map_table[] = { + 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, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +static void urb_complete(struct urb *urb) +{ + struct ttusbir_device *ttusbir; + unsigned char *buf; + int i; + int l; + + ttusbir = urb->context; + + if (!ttusbir->opened) + return; + + buf = (unsigned char *)urb->transfer_buffer; + + for (i = 0; i < 128; i++) { + /* Here we do the filtering and some kind of down sampling */ + buf[i] = ~map_table[buf[i]]; + if (ttusbir->last_pulse == buf[i]) { + if (ttusbir->last_num < PULSE_MASK/63) + ttusbir->last_num++; + /* + * else we are in a idle period and do not need to + * increment any longer + */ + } else { + l = ttusbir->last_num * 62; /* about 62 = us/byte */ + if (ttusbir->last_pulse) /* pulse or space? */ + l |= PULSE_BIT; + if (!lirc_buffer_full(&ttusbir->rbuf)) { + lirc_buffer_write(&ttusbir->rbuf, (void *)&l); + wake_up_interruptible(&ttusbir->rbuf.wait_poll); + } + ttusbir->last_num = 0; + ttusbir->last_pulse = buf[i]; + } + } + usb_submit_urb(urb, GFP_ATOMIC); /* keep data rolling :-) */ +} + +/* + * Called whenever the USB subsystem thinks we could be the right driver + * to handle this device + */ +static int probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + int alt_set, endp; + int found = 0; + int i, j; + int struct_size; + struct usb_host_interface *host_interf; + struct usb_interface_descriptor *interf_desc; + struct usb_host_endpoint *host_endpoint; + struct ttusbir_device *ttusbir; + + DPRINTK("Module ttusbir probe\n"); + + /* To reduce memory fragmentation we use only one allocation */ + struct_size = sizeof(struct ttusbir_device) + + (sizeof(struct urb *) * num_urbs) + + (sizeof(char *) * num_urbs) + + (num_urbs * 128); + ttusbir = kzalloc(struct_size, GFP_KERNEL); + if (!ttusbir) + return -ENOMEM; + + ttusbir->urb = (struct urb **)((char *)ttusbir + + sizeof(struct ttusbir_device)); + ttusbir->buffer = (char **)((char *)ttusbir->urb + + (sizeof(struct urb *) * num_urbs)); + for (i = 0; i < num_urbs; i++) + ttusbir->buffer[i] = (char *)ttusbir->buffer + + (sizeof(char *)*num_urbs) + (i * 128); + + ttusbir->usb_driver = &usb_driver; + ttusbir->alt_setting = -1; + /* @TODO check if error can be returned */ + ttusbir->udev = usb_get_dev(interface_to_usbdev(intf)); + ttusbir->interf = intf; + ttusbir->last_pulse = 0x00; + ttusbir->last_num = 0; + + /* + * Now look for interface setting we can handle + * We are searching for the alt setting where end point + * 0x82 has max packet size 16 + */ + for (alt_set = 0; alt_set < intf->num_altsetting && !found; alt_set++) { + host_interf = &intf->altsetting[alt_set]; + interf_desc = &host_interf->desc; + for (endp = 0; endp < interf_desc->bNumEndpoints; endp++) { + host_endpoint = &host_interf->endpoint[endp]; + if ((host_endpoint->desc.bEndpointAddress == 0x82) && + (host_endpoint->desc.wMaxPacketSize == 0x10)) { + ttusbir->alt_setting = alt_set; + ttusbir->endpoint = endp; + found = 1; + break; + } + } + } + if (ttusbir->alt_setting != -1) + DPRINTK("alt setting: %d\n", ttusbir->alt_setting); + else { + err("Could not find alternate setting\n"); + kfree(ttusbir); + return -EINVAL; + } + + /* OK lets setup this interface setting */ + usb_set_interface(ttusbir->udev, 0, ttusbir->alt_setting); + + /* Store device info in interface structure */ + usb_set_intfdata(intf, ttusbir); + + /* Register as a LIRC driver */ + if (lirc_buffer_init(&ttusbir->rbuf, sizeof(int), 256) < 0) { + err("Could not get memory for LIRC data buffer\n"); + usb_set_intfdata(intf, NULL); + kfree(ttusbir); + return -ENOMEM; + } + strcpy(ttusbir->driver.name, "TTUSBIR"); + ttusbir->driver.minor = -1; + ttusbir->driver.code_length = 1; + ttusbir->driver.sample_rate = 0; + ttusbir->driver.data = ttusbir; + ttusbir->driver.add_to_buf = NULL; + ttusbir->driver.rbuf = &ttusbir->rbuf; + ttusbir->driver.set_use_inc = set_use_inc; + ttusbir->driver.set_use_dec = set_use_dec; + ttusbir->driver.dev = &intf->dev; + ttusbir->driver.owner = THIS_MODULE; + ttusbir->driver.features = LIRC_CAN_REC_MODE2; + ttusbir->minor = lirc_register_driver(&ttusbir->driver); + if (ttusbir->minor < 0) { + err("Error registering as LIRC driver\n"); + usb_set_intfdata(intf, NULL); + lirc_buffer_free(&ttusbir->rbuf); + kfree(ttusbir); + return -EIO; + } + + /* Allocate and setup the URB that we will use to talk to the device */ + for (i = 0; i < num_urbs; i++) { + ttusbir->urb[i] = usb_alloc_urb(8, GFP_KERNEL); + if (!ttusbir->urb[i]) { + err("Could not allocate memory for the URB\n"); + for (j = i - 1; j >= 0; j--) + kfree(ttusbir->urb[j]); + lirc_buffer_free(&ttusbir->rbuf); + lirc_unregister_driver(ttusbir->minor); + kfree(ttusbir); + usb_set_intfdata(intf, NULL); + return -ENOMEM; + } + ttusbir->urb[i]->dev = ttusbir->udev; + ttusbir->urb[i]->context = ttusbir; + ttusbir->urb[i]->pipe = usb_rcvisocpipe(ttusbir->udev, + ttusbir->endpoint); + ttusbir->urb[i]->interval = 1; + ttusbir->urb[i]->transfer_flags = URB_ISO_ASAP; + ttusbir->urb[i]->transfer_buffer = &ttusbir->buffer[i][0]; + ttusbir->urb[i]->complete = urb_complete; + ttusbir->urb[i]->number_of_packets = 8; + ttusbir->urb[i]->transfer_buffer_length = 128; + for (j = 0; j < 8; j++) { + ttusbir->urb[i]->iso_frame_desc[j].offset = j*16; + ttusbir->urb[i]->iso_frame_desc[j].length = 16; + } + } + return 0; +} + +/** + * Called when the driver is unloaded or the device is unplugged + */ +static void disconnect(struct usb_interface *intf) +{ + int i; + struct ttusbir_device *ttusbir; + + DPRINTK("Module ttusbir disconnect\n"); + + ttusbir = (struct ttusbir_device *) usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + lirc_unregister_driver(ttusbir->minor); + DPRINTK("unregistered\n"); + + for (i = 0; i < num_urbs; i++) { + usb_kill_urb(ttusbir->urb[i]); + usb_free_urb(ttusbir->urb[i]); + } + DPRINTK("URBs killed\n"); + lirc_buffer_free(&ttusbir->rbuf); + kfree(ttusbir); +} + +module_usb_driver(usb_driver); diff --git a/drivers/staging/media/lirc/lirc_zilog.c b/drivers/staging/media/lirc/lirc_zilog.c new file mode 100644 index 00000000..76ea4a8f --- /dev/null +++ b/drivers/staging/media/lirc/lirc_zilog.c @@ -0,0 +1,1676 @@ +/* + * i2c IR lirc driver for devices with zilog IR processors + * + * Copyright (c) 2000 Gerd Knorr + * modified for PixelView (BT878P+W/FM) by + * Michal Kochanowicz + * Christoph Bartelmus + * modified for KNC ONE TV Station/Anubis Typhoon TView Tuner by + * Ulrich Mueller + * modified for Asus TV-Box and Creative/VisionTek BreakOut-Box by + * Stefan Jahn + * modified for inclusion into kernel sources by + * Jerome Brock + * modified for Leadtek Winfast PVR2000 by + * Thomas Reitmayr (treitmayr@yahoo.com) + * modified for Hauppauge PVR-150 IR TX device by + * Mark Weaver + * changed name from lirc_pvr150 to lirc_zilog, works on more than pvr-150 + * Jarod Wilson + * + * parts are cut&pasted from the lirc_i2c.c driver + * + * Numerous changes updating lirc_zilog.c in kernel 2.6.38 and later are + * Copyright (C) 2011 Andy Walls + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +struct IR; + +struct IR_rx { + struct kref ref; + struct IR *ir; + + /* RX device */ + struct mutex client_lock; + struct i2c_client *c; + + /* RX polling thread data */ + struct task_struct *task; + + /* RX read data */ + unsigned char b[3]; + bool hdpvr_data_fmt; +}; + +struct IR_tx { + struct kref ref; + struct IR *ir; + + /* TX device */ + struct mutex client_lock; + struct i2c_client *c; + + /* TX additional actions needed */ + int need_boot; + bool post_tx_ready_poll; +}; + +struct IR { + struct kref ref; + struct list_head list; + + /* FIXME spinlock access to l.features */ + struct lirc_driver l; + struct lirc_buffer rbuf; + + struct mutex ir_lock; + atomic_t open_count; + + struct i2c_adapter *adapter; + + spinlock_t rx_ref_lock; /* struct IR_rx kref get()/put() */ + struct IR_rx *rx; + + spinlock_t tx_ref_lock; /* struct IR_tx kref get()/put() */ + struct IR_tx *tx; +}; + +/* IR transceiver instance object list */ +/* + * This lock is used for the following: + * a. ir_devices_list access, insertions, deletions + * b. struct IR kref get()s and put()s + * c. serialization of ir_probe() for the two i2c_clients for a Z8 + */ +static DEFINE_MUTEX(ir_devices_lock); +static LIST_HEAD(ir_devices_list); + +/* Block size for IR transmitter */ +#define TX_BLOCK_SIZE 99 + +/* Hauppauge IR transmitter data */ +struct tx_data_struct { + /* Boot block */ + unsigned char *boot_data; + + /* Start of binary data block */ + unsigned char *datap; + + /* End of binary data block */ + unsigned char *endp; + + /* Number of installed codesets */ + unsigned int num_code_sets; + + /* Pointers to codesets */ + unsigned char **code_sets; + + /* Global fixed data template */ + int fixed[TX_BLOCK_SIZE]; +}; + +static struct tx_data_struct *tx_data; +static struct mutex tx_data_lock; + +#define zilog_notify(s, args...) printk(KERN_NOTICE KBUILD_MODNAME ": " s, \ + ## args) +#define zilog_error(s, args...) printk(KERN_ERR KBUILD_MODNAME ": " s, ## args) +#define zilog_info(s, args...) printk(KERN_INFO KBUILD_MODNAME ": " s, ## args) + +/* module parameters */ +static bool debug; /* debug output */ +static bool tx_only; /* only handle the IR Tx function */ +static int minor = -1; /* minor number */ + +#define dprintk(fmt, args...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG KBUILD_MODNAME ": " fmt, \ + ## args); \ + } while (0) + + +/* struct IR reference counting */ +static struct IR *get_ir_device(struct IR *ir, bool ir_devices_lock_held) +{ + if (ir_devices_lock_held) { + kref_get(&ir->ref); + } else { + mutex_lock(&ir_devices_lock); + kref_get(&ir->ref); + mutex_unlock(&ir_devices_lock); + } + return ir; +} + +static void release_ir_device(struct kref *ref) +{ + struct IR *ir = container_of(ref, struct IR, ref); + + /* + * Things should be in this state by now: + * ir->rx set to NULL and deallocated - happens before ir->rx->ir put() + * ir->rx->task kthread stopped - happens before ir->rx->ir put() + * ir->tx set to NULL and deallocated - happens before ir->tx->ir put() + * ir->open_count == 0 - happens on final close() + * ir_lock, tx_ref_lock, rx_ref_lock, all released + */ + if (ir->l.minor >= 0 && ir->l.minor < MAX_IRCTL_DEVICES) { + lirc_unregister_driver(ir->l.minor); + ir->l.minor = MAX_IRCTL_DEVICES; + } + if (ir->rbuf.fifo_initialized) + lirc_buffer_free(&ir->rbuf); + list_del(&ir->list); + kfree(ir); +} + +static int put_ir_device(struct IR *ir, bool ir_devices_lock_held) +{ + int released; + + if (ir_devices_lock_held) + return kref_put(&ir->ref, release_ir_device); + + mutex_lock(&ir_devices_lock); + released = kref_put(&ir->ref, release_ir_device); + mutex_unlock(&ir_devices_lock); + + return released; +} + +/* struct IR_rx reference counting */ +static struct IR_rx *get_ir_rx(struct IR *ir) +{ + struct IR_rx *rx; + + spin_lock(&ir->rx_ref_lock); + rx = ir->rx; + if (rx != NULL) + kref_get(&rx->ref); + spin_unlock(&ir->rx_ref_lock); + return rx; +} + +static void destroy_rx_kthread(struct IR_rx *rx, bool ir_devices_lock_held) +{ + /* end up polling thread */ + if (!IS_ERR_OR_NULL(rx->task)) { + kthread_stop(rx->task); + rx->task = NULL; + /* Put the ir ptr that ir_probe() gave to the rx poll thread */ + put_ir_device(rx->ir, ir_devices_lock_held); + } +} + +static void release_ir_rx(struct kref *ref) +{ + struct IR_rx *rx = container_of(ref, struct IR_rx, ref); + struct IR *ir = rx->ir; + + /* + * This release function can't do all the work, as we want + * to keep the rx_ref_lock a spinlock, and killing the poll thread + * and releasing the ir reference can cause a sleep. That work is + * performed by put_ir_rx() + */ + ir->l.features &= ~LIRC_CAN_REC_LIRCCODE; + /* Don't put_ir_device(rx->ir) here; lock can't be freed yet */ + ir->rx = NULL; + /* Don't do the kfree(rx) here; we still need to kill the poll thread */ + return; +} + +static int put_ir_rx(struct IR_rx *rx, bool ir_devices_lock_held) +{ + int released; + struct IR *ir = rx->ir; + + spin_lock(&ir->rx_ref_lock); + released = kref_put(&rx->ref, release_ir_rx); + spin_unlock(&ir->rx_ref_lock); + /* Destroy the rx kthread while not holding the spinlock */ + if (released) { + destroy_rx_kthread(rx, ir_devices_lock_held); + kfree(rx); + /* Make sure we're not still in a poll_table somewhere */ + wake_up_interruptible(&ir->rbuf.wait_poll); + } + /* Do a reference put() for the rx->ir reference, if we released rx */ + if (released) + put_ir_device(ir, ir_devices_lock_held); + return released; +} + +/* struct IR_tx reference counting */ +static struct IR_tx *get_ir_tx(struct IR *ir) +{ + struct IR_tx *tx; + + spin_lock(&ir->tx_ref_lock); + tx = ir->tx; + if (tx != NULL) + kref_get(&tx->ref); + spin_unlock(&ir->tx_ref_lock); + return tx; +} + +static void release_ir_tx(struct kref *ref) +{ + struct IR_tx *tx = container_of(ref, struct IR_tx, ref); + struct IR *ir = tx->ir; + + ir->l.features &= ~LIRC_CAN_SEND_PULSE; + /* Don't put_ir_device(tx->ir) here, so our lock doesn't get freed */ + ir->tx = NULL; + kfree(tx); +} + +static int put_ir_tx(struct IR_tx *tx, bool ir_devices_lock_held) +{ + int released; + struct IR *ir = tx->ir; + + spin_lock(&ir->tx_ref_lock); + released = kref_put(&tx->ref, release_ir_tx); + spin_unlock(&ir->tx_ref_lock); + /* Do a reference put() for the tx->ir reference, if we released tx */ + if (released) + put_ir_device(ir, ir_devices_lock_held); + return released; +} + +static int add_to_buf(struct IR *ir) +{ + __u16 code; + unsigned char codes[2]; + unsigned char keybuf[6]; + int got_data = 0; + int ret; + int failures = 0; + unsigned char sendbuf[1] = { 0 }; + struct lirc_buffer *rbuf = ir->l.rbuf; + struct IR_rx *rx; + struct IR_tx *tx; + + if (lirc_buffer_full(rbuf)) { + dprintk("buffer overflow\n"); + return -EOVERFLOW; + } + + rx = get_ir_rx(ir); + if (rx == NULL) + return -ENXIO; + + /* Ensure our rx->c i2c_client remains valid for the duration */ + mutex_lock(&rx->client_lock); + if (rx->c == NULL) { + mutex_unlock(&rx->client_lock); + put_ir_rx(rx, false); + return -ENXIO; + } + + tx = get_ir_tx(ir); + + /* + * service the device as long as it is returning + * data and we have space + */ + do { + if (kthread_should_stop()) { + ret = -ENODATA; + break; + } + + /* + * Lock i2c bus for the duration. RX/TX chips interfere so + * this is worth it + */ + mutex_lock(&ir->ir_lock); + + if (kthread_should_stop()) { + mutex_unlock(&ir->ir_lock); + ret = -ENODATA; + break; + } + + /* + * Send random "poll command" (?) Windows driver does this + * and it is a good point to detect chip failure. + */ + ret = i2c_master_send(rx->c, sendbuf, 1); + if (ret != 1) { + zilog_error("i2c_master_send failed with %d\n", ret); + if (failures >= 3) { + mutex_unlock(&ir->ir_lock); + zilog_error("unable to read from the IR chip " + "after 3 resets, giving up\n"); + break; + } + + /* Looks like the chip crashed, reset it */ + zilog_error("polling the IR receiver chip failed, " + "trying reset\n"); + + set_current_state(TASK_UNINTERRUPTIBLE); + if (kthread_should_stop()) { + mutex_unlock(&ir->ir_lock); + ret = -ENODATA; + break; + } + schedule_timeout((100 * HZ + 999) / 1000); + if (tx != NULL) + tx->need_boot = 1; + + ++failures; + mutex_unlock(&ir->ir_lock); + ret = 0; + continue; + } + + if (kthread_should_stop()) { + mutex_unlock(&ir->ir_lock); + ret = -ENODATA; + break; + } + ret = i2c_master_recv(rx->c, keybuf, sizeof(keybuf)); + mutex_unlock(&ir->ir_lock); + if (ret != sizeof(keybuf)) { + zilog_error("i2c_master_recv failed with %d -- " + "keeping last read buffer\n", ret); + } else { + rx->b[0] = keybuf[3]; + rx->b[1] = keybuf[4]; + rx->b[2] = keybuf[5]; + dprintk("key (0x%02x/0x%02x)\n", rx->b[0], rx->b[1]); + } + + /* key pressed ? */ + if (rx->hdpvr_data_fmt) { + if (got_data && (keybuf[0] == 0x80)) { + ret = 0; + break; + } else if (got_data && (keybuf[0] == 0x00)) { + ret = -ENODATA; + break; + } + } else if ((rx->b[0] & 0x80) == 0) { + ret = got_data ? 0 : -ENODATA; + break; + } + + /* look what we have */ + code = (((__u16)rx->b[0] & 0x7f) << 6) | (rx->b[1] >> 2); + + codes[0] = (code >> 8) & 0xff; + codes[1] = code & 0xff; + + /* return it */ + lirc_buffer_write(rbuf, codes); + ++got_data; + ret = 0; + } while (!lirc_buffer_full(rbuf)); + + mutex_unlock(&rx->client_lock); + if (tx != NULL) + put_ir_tx(tx, false); + put_ir_rx(rx, false); + return ret; +} + +/* + * Main function of the polling thread -- from lirc_dev. + * We don't fit the LIRC model at all anymore. This is horrible, but + * basically we have a single RX/TX device with a nasty failure mode + * that needs to be accounted for across the pair. lirc lets us provide + * fops, but prevents us from using the internal polling, etc. if we do + * so. Hence the replication. Might be neater to extend the LIRC model + * to account for this but I'd think it's a very special case of seriously + * messed up hardware. + */ +static int lirc_thread(void *arg) +{ + struct IR *ir = arg; + struct lirc_buffer *rbuf = ir->l.rbuf; + + dprintk("poll thread started\n"); + + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + + /* if device not opened, we can sleep half a second */ + if (atomic_read(&ir->open_count) == 0) { + schedule_timeout(HZ/2); + continue; + } + + /* + * This is ~113*2 + 24 + jitter (2*repeat gap + code length). + * We use this interval as the chip resets every time you poll + * it (bad!). This is therefore just sufficient to catch all + * of the button presses. It makes the remote much more + * responsive. You can see the difference by running irw and + * holding down a button. With 100ms, the old polling + * interval, you'll notice breaks in the repeat sequence + * corresponding to lost keypresses. + */ + schedule_timeout((260 * HZ) / 1000); + if (kthread_should_stop()) + break; + if (!add_to_buf(ir)) + wake_up_interruptible(&rbuf->wait_poll); + } + + dprintk("poll thread ended\n"); + return 0; +} + +static int set_use_inc(void *data) +{ + return 0; +} + +static void set_use_dec(void *data) +{ + return; +} + +/* safe read of a uint32 (always network byte order) */ +static int read_uint32(unsigned char **data, + unsigned char *endp, unsigned int *val) +{ + if (*data + 4 > endp) + return 0; + *val = ((*data)[0] << 24) | ((*data)[1] << 16) | + ((*data)[2] << 8) | (*data)[3]; + *data += 4; + return 1; +} + +/* safe read of a uint8 */ +static int read_uint8(unsigned char **data, + unsigned char *endp, unsigned char *val) +{ + if (*data + 1 > endp) + return 0; + *val = *((*data)++); + return 1; +} + +/* safe skipping of N bytes */ +static int skip(unsigned char **data, + unsigned char *endp, unsigned int distance) +{ + if (*data + distance > endp) + return 0; + *data += distance; + return 1; +} + +/* decompress key data into the given buffer */ +static int get_key_data(unsigned char *buf, + unsigned int codeset, unsigned int key) +{ + unsigned char *data, *endp, *diffs, *key_block; + unsigned char keys, ndiffs, id; + unsigned int base, lim, pos, i; + + /* Binary search for the codeset */ + for (base = 0, lim = tx_data->num_code_sets; lim; lim >>= 1) { + pos = base + (lim >> 1); + data = tx_data->code_sets[pos]; + + if (!read_uint32(&data, tx_data->endp, &i)) + goto corrupt; + + if (i == codeset) + break; + else if (codeset > i) { + base = pos + 1; + --lim; + } + } + /* Not found? */ + if (!lim) + return -EPROTO; + + /* Set end of data block */ + endp = pos < tx_data->num_code_sets - 1 ? + tx_data->code_sets[pos + 1] : tx_data->endp; + + /* Read the block header */ + if (!read_uint8(&data, endp, &keys) || + !read_uint8(&data, endp, &ndiffs) || + ndiffs > TX_BLOCK_SIZE || keys == 0) + goto corrupt; + + /* Save diffs & skip */ + diffs = data; + if (!skip(&data, endp, ndiffs)) + goto corrupt; + + /* Read the id of the first key */ + if (!read_uint8(&data, endp, &id)) + goto corrupt; + + /* Unpack the first key's data */ + for (i = 0; i < TX_BLOCK_SIZE; ++i) { + if (tx_data->fixed[i] == -1) { + if (!read_uint8(&data, endp, &buf[i])) + goto corrupt; + } else { + buf[i] = (unsigned char)tx_data->fixed[i]; + } + } + + /* Early out key found/not found */ + if (key == id) + return 0; + if (keys == 1) + return -EPROTO; + + /* Sanity check */ + key_block = data; + if (!skip(&data, endp, (keys - 1) * (ndiffs + 1))) + goto corrupt; + + /* Binary search for the key */ + for (base = 0, lim = keys - 1; lim; lim >>= 1) { + /* Seek to block */ + unsigned char *key_data; + pos = base + (lim >> 1); + key_data = key_block + (ndiffs + 1) * pos; + + if (*key_data == key) { + /* skip key id */ + ++key_data; + + /* found, so unpack the diffs */ + for (i = 0; i < ndiffs; ++i) { + unsigned char val; + if (!read_uint8(&key_data, endp, &val) || + diffs[i] >= TX_BLOCK_SIZE) + goto corrupt; + buf[diffs[i]] = val; + } + + return 0; + } else if (key > *key_data) { + base = pos + 1; + --lim; + } + } + /* Key not found */ + return -EPROTO; + +corrupt: + zilog_error("firmware is corrupt\n"); + return -EFAULT; +} + +/* send a block of data to the IR TX device */ +static int send_data_block(struct IR_tx *tx, unsigned char *data_block) +{ + int i, j, ret; + unsigned char buf[5]; + + for (i = 0; i < TX_BLOCK_SIZE;) { + int tosend = TX_BLOCK_SIZE - i; + if (tosend > 4) + tosend = 4; + buf[0] = (unsigned char)(i + 1); + for (j = 0; j < tosend; ++j) + buf[1 + j] = data_block[i + j]; + dprintk("%02x %02x %02x %02x %02x", + buf[0], buf[1], buf[2], buf[3], buf[4]); + ret = i2c_master_send(tx->c, buf, tosend + 1); + if (ret != tosend + 1) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + i += tosend; + } + return 0; +} + +/* send boot data to the IR TX device */ +static int send_boot_data(struct IR_tx *tx) +{ + int ret, i; + unsigned char buf[4]; + + /* send the boot block */ + ret = send_data_block(tx, tx_data->boot_data); + if (ret != 0) + return ret; + + /* Hit the go button to activate the new boot data */ + buf[0] = 0x00; + buf[1] = 0x20; + ret = i2c_master_send(tx->c, buf, 2); + if (ret != 2) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* + * Wait for zilog to settle after hitting go post boot block upload. + * Without this delay, the HD-PVR and HVR-1950 both return an -EIO + * upon attempting to get firmware revision, and tx probe thus fails. + */ + for (i = 0; i < 10; i++) { + ret = i2c_master_send(tx->c, buf, 1); + if (ret == 1) + break; + udelay(100); + } + + if (ret != 1) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* Here comes the firmware version... (hopefully) */ + ret = i2c_master_recv(tx->c, buf, 4); + if (ret != 4) { + zilog_error("i2c_master_recv failed with %d\n", ret); + return 0; + } + if ((buf[0] != 0x80) && (buf[0] != 0xa0)) { + zilog_error("unexpected IR TX init response: %02x\n", buf[0]); + return 0; + } + zilog_notify("Zilog/Hauppauge IR blaster firmware version " + "%d.%d.%d loaded\n", buf[1], buf[2], buf[3]); + + return 0; +} + +/* unload "firmware", lock held */ +static void fw_unload_locked(void) +{ + if (tx_data) { + if (tx_data->code_sets) + vfree(tx_data->code_sets); + + if (tx_data->datap) + vfree(tx_data->datap); + + vfree(tx_data); + tx_data = NULL; + dprintk("successfully unloaded IR blaster firmware\n"); + } +} + +/* unload "firmware" for the IR TX device */ +static void fw_unload(void) +{ + mutex_lock(&tx_data_lock); + fw_unload_locked(); + mutex_unlock(&tx_data_lock); +} + +/* load "firmware" for the IR TX device */ +static int fw_load(struct IR_tx *tx) +{ + int ret; + unsigned int i; + unsigned char *data, version, num_global_fixed; + const struct firmware *fw_entry; + + /* Already loaded? */ + mutex_lock(&tx_data_lock); + if (tx_data) { + ret = 0; + goto out; + } + + /* Request codeset data file */ + ret = request_firmware(&fw_entry, "haup-ir-blaster.bin", tx->ir->l.dev); + if (ret != 0) { + zilog_error("firmware haup-ir-blaster.bin not available " + "(%d)\n", ret); + ret = ret < 0 ? ret : -EFAULT; + goto out; + } + dprintk("firmware of size %zu loaded\n", fw_entry->size); + + /* Parse the file */ + tx_data = vmalloc(sizeof(*tx_data)); + if (tx_data == NULL) { + zilog_error("out of memory\n"); + release_firmware(fw_entry); + ret = -ENOMEM; + goto out; + } + tx_data->code_sets = NULL; + + /* Copy the data so hotplug doesn't get confused and timeout */ + tx_data->datap = vmalloc(fw_entry->size); + if (tx_data->datap == NULL) { + zilog_error("out of memory\n"); + release_firmware(fw_entry); + vfree(tx_data); + ret = -ENOMEM; + goto out; + } + memcpy(tx_data->datap, fw_entry->data, fw_entry->size); + tx_data->endp = tx_data->datap + fw_entry->size; + release_firmware(fw_entry); fw_entry = NULL; + + /* Check version */ + data = tx_data->datap; + if (!read_uint8(&data, tx_data->endp, &version)) + goto corrupt; + if (version != 1) { + zilog_error("unsupported code set file version (%u, expected" + "1) -- please upgrade to a newer driver", + version); + fw_unload_locked(); + ret = -EFAULT; + goto out; + } + + /* Save boot block for later */ + tx_data->boot_data = data; + if (!skip(&data, tx_data->endp, TX_BLOCK_SIZE)) + goto corrupt; + + if (!read_uint32(&data, tx_data->endp, + &tx_data->num_code_sets)) + goto corrupt; + + dprintk("%u IR blaster codesets loaded\n", tx_data->num_code_sets); + + tx_data->code_sets = vmalloc( + tx_data->num_code_sets * sizeof(char *)); + if (tx_data->code_sets == NULL) { + fw_unload_locked(); + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < TX_BLOCK_SIZE; ++i) + tx_data->fixed[i] = -1; + + /* Read global fixed data template */ + if (!read_uint8(&data, tx_data->endp, &num_global_fixed) || + num_global_fixed > TX_BLOCK_SIZE) + goto corrupt; + for (i = 0; i < num_global_fixed; ++i) { + unsigned char pos, val; + if (!read_uint8(&data, tx_data->endp, &pos) || + !read_uint8(&data, tx_data->endp, &val) || + pos >= TX_BLOCK_SIZE) + goto corrupt; + tx_data->fixed[pos] = (int)val; + } + + /* Filch out the position of each code set */ + for (i = 0; i < tx_data->num_code_sets; ++i) { + unsigned int id; + unsigned char keys; + unsigned char ndiffs; + + /* Save the codeset position */ + tx_data->code_sets[i] = data; + + /* Read header */ + if (!read_uint32(&data, tx_data->endp, &id) || + !read_uint8(&data, tx_data->endp, &keys) || + !read_uint8(&data, tx_data->endp, &ndiffs) || + ndiffs > TX_BLOCK_SIZE || keys == 0) + goto corrupt; + + /* skip diff positions */ + if (!skip(&data, tx_data->endp, ndiffs)) + goto corrupt; + + /* + * After the diffs we have the first key id + data - + * global fixed + */ + if (!skip(&data, tx_data->endp, + 1 + TX_BLOCK_SIZE - num_global_fixed)) + goto corrupt; + + /* Then we have keys-1 blocks of key id+diffs */ + if (!skip(&data, tx_data->endp, + (ndiffs + 1) * (keys - 1))) + goto corrupt; + } + ret = 0; + goto out; + +corrupt: + zilog_error("firmware is corrupt\n"); + fw_unload_locked(); + ret = -EFAULT; + +out: + mutex_unlock(&tx_data_lock); + return ret; +} + +/* copied from lirc_dev */ +static ssize_t read(struct file *filep, char *outbuf, size_t n, loff_t *ppos) +{ + struct IR *ir = filep->private_data; + struct IR_rx *rx; + struct lirc_buffer *rbuf = ir->l.rbuf; + int ret = 0, written = 0, retries = 0; + unsigned int m; + DECLARE_WAITQUEUE(wait, current); + + dprintk("read called\n"); + if (n % rbuf->chunk_size) { + dprintk("read result = -EINVAL\n"); + return -EINVAL; + } + + rx = get_ir_rx(ir); + if (rx == NULL) + return -ENXIO; + + /* + * we add ourselves to the task queue before buffer check + * to avoid losing scan code (in case when queue is awaken somewhere + * between while condition checking and scheduling) + */ + add_wait_queue(&rbuf->wait_poll, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + /* + * while we didn't provide 'length' bytes, device is opened in blocking + * mode and 'copy_to_user' is happy, wait for data. + */ + while (written < n && ret == 0) { + if (lirc_buffer_empty(rbuf)) { + /* + * According to the read(2) man page, 'written' can be + * returned as less than 'n', instead of blocking + * again, returning -EWOULDBLOCK, or returning + * -ERESTARTSYS + */ + if (written) + break; + if (filep->f_flags & O_NONBLOCK) { + ret = -EWOULDBLOCK; + break; + } + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } else { + unsigned char buf[rbuf->chunk_size]; + m = lirc_buffer_read(rbuf, buf); + if (m == rbuf->chunk_size) { + ret = copy_to_user((void *)outbuf+written, buf, + rbuf->chunk_size); + written += rbuf->chunk_size; + } else { + retries++; + } + if (retries >= 5) { + zilog_error("Buffer read failed!\n"); + ret = -EIO; + } + } + } + + remove_wait_queue(&rbuf->wait_poll, &wait); + put_ir_rx(rx, false); + set_current_state(TASK_RUNNING); + + dprintk("read result = %d (%s)\n", ret, ret ? "Error" : "OK"); + + return ret ? ret : written; +} + +/* send a keypress to the IR TX device */ +static int send_code(struct IR_tx *tx, unsigned int code, unsigned int key) +{ + unsigned char data_block[TX_BLOCK_SIZE]; + unsigned char buf[2]; + int i, ret; + + /* Get data for the codeset/key */ + ret = get_key_data(data_block, code, key); + + if (ret == -EPROTO) { + zilog_error("failed to get data for code %u, key %u -- check " + "lircd.conf entries\n", code, key); + return ret; + } else if (ret != 0) + return ret; + + /* Send the data block */ + ret = send_data_block(tx, data_block); + if (ret != 0) + return ret; + + /* Send data block length? */ + buf[0] = 0x00; + buf[1] = 0x40; + ret = i2c_master_send(tx->c, buf, 2); + if (ret != 2) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* Give the z8 a moment to process data block */ + for (i = 0; i < 10; i++) { + ret = i2c_master_send(tx->c, buf, 1); + if (ret == 1) + break; + udelay(100); + } + + if (ret != 1) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* Send finished download? */ + ret = i2c_master_recv(tx->c, buf, 1); + if (ret != 1) { + zilog_error("i2c_master_recv failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + if (buf[0] != 0xA0) { + zilog_error("unexpected IR TX response #1: %02x\n", + buf[0]); + return -EFAULT; + } + + /* Send prepare command? */ + buf[0] = 0x00; + buf[1] = 0x80; + ret = i2c_master_send(tx->c, buf, 2); + if (ret != 2) { + zilog_error("i2c_master_send failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* + * The sleep bits aren't necessary on the HD PVR, and in fact, the + * last i2c_master_recv always fails with a -5, so for now, we're + * going to skip this whole mess and say we're done on the HD PVR + */ + if (!tx->post_tx_ready_poll) { + dprintk("sent code %u, key %u\n", code, key); + return 0; + } + + /* + * This bit NAKs until the device is ready, so we retry it + * sleeping a bit each time. This seems to be what the windows + * driver does, approximately. + * Try for up to 1s. + */ + for (i = 0; i < 20; ++i) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout((50 * HZ + 999) / 1000); + ret = i2c_master_send(tx->c, buf, 1); + if (ret == 1) + break; + dprintk("NAK expected: i2c_master_send " + "failed with %d (try %d)\n", ret, i+1); + } + if (ret != 1) { + zilog_error("IR TX chip never got ready: last i2c_master_send " + "failed with %d\n", ret); + return ret < 0 ? ret : -EFAULT; + } + + /* Seems to be an 'ok' response */ + i = i2c_master_recv(tx->c, buf, 1); + if (i != 1) { + zilog_error("i2c_master_recv failed with %d\n", ret); + return -EFAULT; + } + if (buf[0] != 0x80) { + zilog_error("unexpected IR TX response #2: %02x\n", buf[0]); + return -EFAULT; + } + + /* Oh good, it worked */ + dprintk("sent code %u, key %u\n", code, key); + return 0; +} + +/* + * Write a code to the device. We take in a 32-bit number (an int) and then + * decode this to a codeset/key index. The key data is then decompressed and + * sent to the device. We have a spin lock as per i2c documentation to prevent + * multiple concurrent sends which would probably cause the device to explode. + */ +static ssize_t write(struct file *filep, const char *buf, size_t n, + loff_t *ppos) +{ + struct IR *ir = filep->private_data; + struct IR_tx *tx; + size_t i; + int failures = 0; + + /* Validate user parameters */ + if (n % sizeof(int)) + return -EINVAL; + + /* Get a struct IR_tx reference */ + tx = get_ir_tx(ir); + if (tx == NULL) + return -ENXIO; + + /* Ensure our tx->c i2c_client remains valid for the duration */ + mutex_lock(&tx->client_lock); + if (tx->c == NULL) { + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + return -ENXIO; + } + + /* Lock i2c bus for the duration */ + mutex_lock(&ir->ir_lock); + + /* Send each keypress */ + for (i = 0; i < n;) { + int ret = 0; + int command; + + if (copy_from_user(&command, buf + i, sizeof(command))) { + mutex_unlock(&ir->ir_lock); + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + return -EFAULT; + } + + /* Send boot data first if required */ + if (tx->need_boot == 1) { + /* Make sure we have the 'firmware' loaded, first */ + ret = fw_load(tx); + if (ret != 0) { + mutex_unlock(&ir->ir_lock); + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + if (ret != -ENOMEM) + ret = -EIO; + return ret; + } + /* Prep the chip for transmitting codes */ + ret = send_boot_data(tx); + if (ret == 0) + tx->need_boot = 0; + } + + /* Send the code */ + if (ret == 0) { + ret = send_code(tx, (unsigned)command >> 16, + (unsigned)command & 0xFFFF); + if (ret == -EPROTO) { + mutex_unlock(&ir->ir_lock); + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + return ret; + } + } + + /* + * Hmm, a failure. If we've had a few then give up, otherwise + * try a reset + */ + if (ret != 0) { + /* Looks like the chip crashed, reset it */ + zilog_error("sending to the IR transmitter chip " + "failed, trying reset\n"); + + if (failures >= 3) { + zilog_error("unable to send to the IR chip " + "after 3 resets, giving up\n"); + mutex_unlock(&ir->ir_lock); + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + return ret; + } + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout((100 * HZ + 999) / 1000); + tx->need_boot = 1; + ++failures; + } else + i += sizeof(int); + } + + /* Release i2c bus */ + mutex_unlock(&ir->ir_lock); + + mutex_unlock(&tx->client_lock); + + /* Give back our struct IR_tx reference */ + put_ir_tx(tx, false); + + /* All looks good */ + return n; +} + +/* copied from lirc_dev */ +static unsigned int poll(struct file *filep, poll_table *wait) +{ + struct IR *ir = filep->private_data; + struct IR_rx *rx; + struct lirc_buffer *rbuf = ir->l.rbuf; + unsigned int ret; + + dprintk("poll called\n"); + + rx = get_ir_rx(ir); + if (rx == NULL) { + /* + * Revisit this, if our poll function ever reports writeable + * status for Tx + */ + dprintk("poll result = POLLERR\n"); + return POLLERR; + } + + /* + * Add our lirc_buffer's wait_queue to the poll_table. A wake up on + * that buffer's wait queue indicates we may have a new poll status. + */ + poll_wait(filep, &rbuf->wait_poll, wait); + + /* Indicate what ops could happen immediately without blocking */ + ret = lirc_buffer_empty(rbuf) ? 0 : (POLLIN|POLLRDNORM); + + dprintk("poll result = %s\n", ret ? "POLLIN|POLLRDNORM" : "none"); + return ret; +} + +static long ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + struct IR *ir = filep->private_data; + int result; + unsigned long mode, features; + + features = ir->l.features; + + switch (cmd) { + case LIRC_GET_LENGTH: + result = put_user((unsigned long)13, + (unsigned long *)arg); + break; + case LIRC_GET_FEATURES: + result = put_user(features, (unsigned long *) arg); + break; + case LIRC_GET_REC_MODE: + if (!(features&LIRC_CAN_REC_MASK)) + return -ENOSYS; + + result = put_user(LIRC_REC2MODE + (features&LIRC_CAN_REC_MASK), + (unsigned long *)arg); + break; + case LIRC_SET_REC_MODE: + if (!(features&LIRC_CAN_REC_MASK)) + return -ENOSYS; + + result = get_user(mode, (unsigned long *)arg); + if (!result && !(LIRC_MODE2REC(mode) & features)) + result = -EINVAL; + break; + case LIRC_GET_SEND_MODE: + if (!(features&LIRC_CAN_SEND_MASK)) + return -ENOSYS; + + result = put_user(LIRC_MODE_PULSE, (unsigned long *) arg); + break; + case LIRC_SET_SEND_MODE: + if (!(features&LIRC_CAN_SEND_MASK)) + return -ENOSYS; + + result = get_user(mode, (unsigned long *) arg); + if (!result && mode != LIRC_MODE_PULSE) + return -EINVAL; + break; + default: + return -EINVAL; + } + return result; +} + +static struct IR *get_ir_device_by_minor(unsigned int minor) +{ + struct IR *ir; + struct IR *ret = NULL; + + mutex_lock(&ir_devices_lock); + + if (!list_empty(&ir_devices_list)) { + list_for_each_entry(ir, &ir_devices_list, list) { + if (ir->l.minor == minor) { + ret = get_ir_device(ir, true); + break; + } + } + } + + mutex_unlock(&ir_devices_lock); + return ret; +} + +/* + * Open the IR device. Get hold of our IR structure and + * stash it in private_data for the file + */ +static int open(struct inode *node, struct file *filep) +{ + struct IR *ir; + unsigned int minor = MINOR(node->i_rdev); + + /* find our IR struct */ + ir = get_ir_device_by_minor(minor); + + if (ir == NULL) + return -ENODEV; + + atomic_inc(&ir->open_count); + + /* stash our IR struct */ + filep->private_data = ir; + + nonseekable_open(node, filep); + return 0; +} + +/* Close the IR device */ +static int close(struct inode *node, struct file *filep) +{ + /* find our IR struct */ + struct IR *ir = filep->private_data; + if (ir == NULL) { + zilog_error("close: no private_data attached to the file!\n"); + return -ENODEV; + } + + atomic_dec(&ir->open_count); + + put_ir_device(ir, false); + return 0; +} + +static int ir_remove(struct i2c_client *client); +static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id); + +#define ID_FLAG_TX 0x01 +#define ID_FLAG_HDPVR 0x02 + +static const struct i2c_device_id ir_transceiver_id[] = { + { "ir_tx_z8f0811_haup", ID_FLAG_TX }, + { "ir_rx_z8f0811_haup", 0 }, + { "ir_tx_z8f0811_hdpvr", ID_FLAG_HDPVR | ID_FLAG_TX }, + { "ir_rx_z8f0811_hdpvr", ID_FLAG_HDPVR }, + { } +}; + +static struct i2c_driver driver = { + .driver = { + .owner = THIS_MODULE, + .name = "Zilog/Hauppauge i2c IR", + }, + .probe = ir_probe, + .remove = ir_remove, + .id_table = ir_transceiver_id, +}; + +static const struct file_operations lirc_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = read, + .write = write, + .poll = poll, + .unlocked_ioctl = ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ioctl, +#endif + .open = open, + .release = close +}; + +static struct lirc_driver lirc_template = { + .name = "lirc_zilog", + .minor = -1, + .code_length = 13, + .buffer_size = BUFLEN / 2, + .sample_rate = 0, /* tell lirc_dev to not start its own kthread */ + .chunk_size = 2, + .set_use_inc = set_use_inc, + .set_use_dec = set_use_dec, + .fops = &lirc_fops, + .owner = THIS_MODULE, +}; + +static int ir_remove(struct i2c_client *client) +{ + if (strncmp("ir_tx_z8", client->name, 8) == 0) { + struct IR_tx *tx = i2c_get_clientdata(client); + if (tx != NULL) { + mutex_lock(&tx->client_lock); + tx->c = NULL; + mutex_unlock(&tx->client_lock); + put_ir_tx(tx, false); + } + } else if (strncmp("ir_rx_z8", client->name, 8) == 0) { + struct IR_rx *rx = i2c_get_clientdata(client); + if (rx != NULL) { + mutex_lock(&rx->client_lock); + rx->c = NULL; + mutex_unlock(&rx->client_lock); + put_ir_rx(rx, false); + } + } + return 0; +} + + +/* ir_devices_lock must be held */ +static struct IR *get_ir_device_by_adapter(struct i2c_adapter *adapter) +{ + struct IR *ir; + + if (list_empty(&ir_devices_list)) + return NULL; + + list_for_each_entry(ir, &ir_devices_list, list) + if (ir->adapter == adapter) { + get_ir_device(ir, true); + return ir; + } + + return NULL; +} + +static int ir_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct IR *ir; + struct IR_tx *tx; + struct IR_rx *rx; + struct i2c_adapter *adap = client->adapter; + int ret; + bool tx_probe = false; + + dprintk("%s: %s on i2c-%d (%s), client addr=0x%02x\n", + __func__, id->name, adap->nr, adap->name, client->addr); + + /* + * The IR receiver is at i2c address 0x71. + * The IR transmitter is at i2c address 0x70. + */ + + if (id->driver_data & ID_FLAG_TX) + tx_probe = true; + else if (tx_only) /* module option */ + return -ENXIO; + + zilog_info("probing IR %s on %s (i2c-%d)\n", + tx_probe ? "Tx" : "Rx", adap->name, adap->nr); + + mutex_lock(&ir_devices_lock); + + /* Use a single struct IR instance for both the Rx and Tx functions */ + ir = get_ir_device_by_adapter(adap); + if (ir == NULL) { + ir = kzalloc(sizeof(struct IR), GFP_KERNEL); + if (ir == NULL) { + ret = -ENOMEM; + goto out_no_ir; + } + kref_init(&ir->ref); + + /* store for use in ir_probe() again, and open() later on */ + INIT_LIST_HEAD(&ir->list); + list_add_tail(&ir->list, &ir_devices_list); + + ir->adapter = adap; + mutex_init(&ir->ir_lock); + atomic_set(&ir->open_count, 0); + spin_lock_init(&ir->tx_ref_lock); + spin_lock_init(&ir->rx_ref_lock); + + /* set lirc_dev stuff */ + memcpy(&ir->l, &lirc_template, sizeof(struct lirc_driver)); + /* + * FIXME this is a pointer reference to us, but no refcount. + * + * This OK for now, since lirc_dev currently won't touch this + * buffer as we provide our own lirc_fops. + * + * Currently our own lirc_fops rely on this ir->l.rbuf pointer + */ + ir->l.rbuf = &ir->rbuf; + ir->l.dev = &adap->dev; + ret = lirc_buffer_init(ir->l.rbuf, + ir->l.chunk_size, ir->l.buffer_size); + if (ret) + goto out_put_ir; + } + + if (tx_probe) { + /* Get the IR_rx instance for later, if already allocated */ + rx = get_ir_rx(ir); + + /* Set up a struct IR_tx instance */ + tx = kzalloc(sizeof(struct IR_tx), GFP_KERNEL); + if (tx == NULL) { + ret = -ENOMEM; + goto out_put_xx; + } + kref_init(&tx->ref); + ir->tx = tx; + + ir->l.features |= LIRC_CAN_SEND_PULSE; + mutex_init(&tx->client_lock); + tx->c = client; + tx->need_boot = 1; + tx->post_tx_ready_poll = + (id->driver_data & ID_FLAG_HDPVR) ? false : true; + + /* An ir ref goes to the struct IR_tx instance */ + tx->ir = get_ir_device(ir, true); + + /* A tx ref goes to the i2c_client */ + i2c_set_clientdata(client, get_ir_tx(ir)); + + /* + * Load the 'firmware'. We do this before registering with + * lirc_dev, so the first firmware load attempt does not happen + * after a open() or write() call on the device. + * + * Failure here is not deemed catastrophic, so the receiver will + * still be usable. Firmware load will be retried in write(), + * if it is needed. + */ + fw_load(tx); + + /* Proceed only if the Rx client is also ready or not needed */ + if (rx == NULL && !tx_only) { + zilog_info("probe of IR Tx on %s (i2c-%d) done. Waiting" + " on IR Rx.\n", adap->name, adap->nr); + goto out_ok; + } + } else { + /* Get the IR_tx instance for later, if already allocated */ + tx = get_ir_tx(ir); + + /* Set up a struct IR_rx instance */ + rx = kzalloc(sizeof(struct IR_rx), GFP_KERNEL); + if (rx == NULL) { + ret = -ENOMEM; + goto out_put_xx; + } + kref_init(&rx->ref); + ir->rx = rx; + + ir->l.features |= LIRC_CAN_REC_LIRCCODE; + mutex_init(&rx->client_lock); + rx->c = client; + rx->hdpvr_data_fmt = + (id->driver_data & ID_FLAG_HDPVR) ? true : false; + + /* An ir ref goes to the struct IR_rx instance */ + rx->ir = get_ir_device(ir, true); + + /* An rx ref goes to the i2c_client */ + i2c_set_clientdata(client, get_ir_rx(ir)); + + /* + * Start the polling thread. + * It will only perform an empty loop around schedule_timeout() + * until we register with lirc_dev and the first user open() + */ + /* An ir ref goes to the new rx polling kthread */ + rx->task = kthread_run(lirc_thread, get_ir_device(ir, true), + "zilog-rx-i2c-%d", adap->nr); + if (IS_ERR(rx->task)) { + ret = PTR_ERR(rx->task); + zilog_error("%s: could not start IR Rx polling thread" + "\n", __func__); + /* Failed kthread, so put back the ir ref */ + put_ir_device(ir, true); + /* Failure exit, so put back rx ref from i2c_client */ + i2c_set_clientdata(client, NULL); + put_ir_rx(rx, true); + ir->l.features &= ~LIRC_CAN_REC_LIRCCODE; + goto out_put_xx; + } + + /* Proceed only if the Tx client is also ready */ + if (tx == NULL) { + zilog_info("probe of IR Rx on %s (i2c-%d) done. Waiting" + " on IR Tx.\n", adap->name, adap->nr); + goto out_ok; + } + } + + /* register with lirc */ + ir->l.minor = minor; /* module option: user requested minor number */ + ir->l.minor = lirc_register_driver(&ir->l); + if (ir->l.minor < 0 || ir->l.minor >= MAX_IRCTL_DEVICES) { + zilog_error("%s: \"minor\" must be between 0 and %d (%d)!\n", + __func__, MAX_IRCTL_DEVICES-1, ir->l.minor); + ret = -EBADRQC; + goto out_put_xx; + } + zilog_info("IR unit on %s (i2c-%d) registered as lirc%d and ready\n", + adap->name, adap->nr, ir->l.minor); + +out_ok: + if (rx != NULL) + put_ir_rx(rx, true); + if (tx != NULL) + put_ir_tx(tx, true); + put_ir_device(ir, true); + zilog_info("probe of IR %s on %s (i2c-%d) done\n", + tx_probe ? "Tx" : "Rx", adap->name, adap->nr); + mutex_unlock(&ir_devices_lock); + return 0; + +out_put_xx: + if (rx != NULL) + put_ir_rx(rx, true); + if (tx != NULL) + put_ir_tx(tx, true); +out_put_ir: + put_ir_device(ir, true); +out_no_ir: + zilog_error("%s: probing IR %s on %s (i2c-%d) failed with %d\n", + __func__, tx_probe ? "Tx" : "Rx", adap->name, adap->nr, + ret); + mutex_unlock(&ir_devices_lock); + return ret; +} + +static int __init zilog_init(void) +{ + int ret; + + zilog_notify("Zilog/Hauppauge IR driver initializing\n"); + + mutex_init(&tx_data_lock); + + request_module("firmware_class"); + + ret = i2c_add_driver(&driver); + if (ret) + zilog_error("initialization failed\n"); + else + zilog_notify("initialization complete\n"); + + return ret; +} + +static void __exit zilog_exit(void) +{ + i2c_del_driver(&driver); + /* if loaded */ + fw_unload(); + zilog_notify("Zilog/Hauppauge IR driver unloaded\n"); +} + +module_init(zilog_init); +module_exit(zilog_exit); + +MODULE_DESCRIPTION("Zilog/Hauppauge infrared transmitter driver (i2c stack)"); +MODULE_AUTHOR("Gerd Knorr, Michal Kochanowicz, Christoph Bartelmus, " + "Ulrich Mueller, Stefan Jahn, Jerome Brock, Mark Weaver, " + "Andy Walls"); +MODULE_LICENSE("GPL"); +/* for compat with old name, which isn't all that accurate anymore */ +MODULE_ALIAS("lirc_pvr150"); + +module_param(minor, int, 0444); +MODULE_PARM_DESC(minor, "Preferred minor device number"); + +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Enable debugging messages"); + +module_param(tx_only, bool, 0644); +MODULE_PARM_DESC(tx_only, "Only handle the IR transmit function"); diff --git a/drivers/staging/media/solo6x10/Kconfig b/drivers/staging/media/solo6x10/Kconfig new file mode 100644 index 00000000..63352de5 --- /dev/null +++ b/drivers/staging/media/solo6x10/Kconfig @@ -0,0 +1,8 @@ +config SOLO6X10 + tristate "Softlogic 6x10 MPEG codec cards" + depends on PCI && VIDEO_DEV && SND && I2C + select VIDEOBUF_DMA_SG + select SND_PCM + ---help--- + This driver supports the Softlogic based MPEG-4 and h.264 codec + cards. diff --git a/drivers/staging/media/solo6x10/Makefile b/drivers/staging/media/solo6x10/Makefile new file mode 100644 index 00000000..337e38c3 --- /dev/null +++ b/drivers/staging/media/solo6x10/Makefile @@ -0,0 +1,3 @@ +solo6x10-y := core.o i2c.o p2m.o v4l2.o tw28.o gpio.o disp.o enc.o v4l2-enc.o g723.o + +obj-$(CONFIG_SOLO6X10) += solo6x10.o diff --git a/drivers/staging/media/solo6x10/TODO b/drivers/staging/media/solo6x10/TODO new file mode 100644 index 00000000..7e6c4fa1 --- /dev/null +++ b/drivers/staging/media/solo6x10/TODO @@ -0,0 +1,24 @@ +TODO (staging => main): + + * Motion detection flags need to be moved to v4l2 + * Some private CIDs need to be moved to v4l2 + +TODO (general): + + * encoder on/off controls + * mpeg cid bitrate mode (vbr/cbr) + * mpeg cid bitrate/bitrate-peak + * mpeg encode of user data + * mpeg decode of user data + * switch between 4 frames/irq to 1 when using mjpeg (and then back + when not) + * implement a CID control for motion areas/thresholds + * implement CID controls for mozaic areas + * allow for higher level of interval (for < 1 fps) + * sound: + - implement playback via external sound jack + - implement loopback of external sound jack with incoming audio? + - implement pause/resume + +Plase send patches to Greg Kroah-Hartman and Cc Ben Collins + diff --git a/drivers/staging/media/solo6x10/core.c b/drivers/staging/media/solo6x10/core.c new file mode 100644 index 00000000..d2fd842e --- /dev/null +++ b/drivers/staging/media/solo6x10/core.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include "solo6x10.h" +#include "tw28.h" + +MODULE_DESCRIPTION("Softlogic 6x10 MP4/H.264 Encoder/Decoder V4L2/ALSA Driver"); +MODULE_AUTHOR("Ben Collins "); +MODULE_VERSION(SOLO6X10_VERSION); +MODULE_LICENSE("GPL"); + +void solo_irq_on(struct solo_dev *solo_dev, u32 mask) +{ + solo_dev->irq_mask |= mask; + solo_reg_write(solo_dev, SOLO_IRQ_ENABLE, solo_dev->irq_mask); +} + +void solo_irq_off(struct solo_dev *solo_dev, u32 mask) +{ + solo_dev->irq_mask &= ~mask; + solo_reg_write(solo_dev, SOLO_IRQ_ENABLE, solo_dev->irq_mask); +} + +/* XXX We should check the return value of the sub-device ISR's */ +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; + + if (status & ~solo_dev->irq_mask) { + solo_reg_write(solo_dev, SOLO_IRQ_STAT, + status & ~solo_dev->irq_mask); + status &= solo_dev->irq_mask; + } + + if (status & SOLO_IRQ_PCI_ERR) { + u32 err = solo_reg_read(solo_dev, SOLO_PCI_ERR); + solo_p2m_error_isr(solo_dev, err); + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_PCI_ERR); + } + + 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); + + /* Call this first so enc gets detected flag set */ + if (status & SOLO_IRQ_MOTION) + solo_motion_isr(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; + + if (!solo_dev) + return; + + pdev = solo_dev->pdev; + + /* If we never initialized the PCI device, then nothing else + * below here needs cleanup */ + if (!pdev) { + kfree(solo_dev); + return; + } + + /* 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 */ + if (solo_dev->reg_base) { + solo_irq_off(solo_dev, ~0); + pci_iounmap(pdev, solo_dev->reg_base); + free_irq(pdev->irq, solo_dev); + } + + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_set_drvdata(pdev, NULL); + + kfree(solo_dev); +} + +static int __devinit solo_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct solo_dev *solo_dev; + int ret; + int sdram; + u8 chip_id; + u32 reg; + + solo_dev = kzalloc(sizeof(*solo_dev), GFP_KERNEL); + if (solo_dev == NULL) + return -ENOMEM; + + solo_dev->pdev = pdev; + spin_lock_init(&solo_dev->reg_io_lock); + pci_set_drvdata(pdev, solo_dev); + + ret = pci_enable_device(pdev); + if (ret) + goto fail_probe; + + pci_set_master(pdev); + + ret = pci_request_regions(pdev, SOLO6X10_NAME); + if (ret) + goto fail_probe; + + solo_dev->reg_base = pci_ioremap_bar(pdev, 0); + if (solo_dev->reg_base == NULL) { + ret = -ENOMEM; + 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, " + "defaulting to 4 channels\n", + chip_id); + case 5: + solo_dev->nr_chans = 4; + solo_dev->nr_ext = 1; + } + + solo_dev->flags = id->driver_data; + + /* Disable all interrupts to start */ + solo_irq_off(solo_dev, ~0); + + reg = SOLO_SYS_CFG_SDRAM64BIT; + /* Initial global settings */ + if (!(solo_dev->flags & FLAGS_6110)) + reg |= SOLO6010_SYS_CFG_INPUTDIV(25) | + SOLO6010_SYS_CFG_FEEDBACKDIV((SOLO_CLOCK_MHZ * 2) - 2) | + SOLO6010_SYS_CFG_OUTDIV(3); + solo_reg_write(solo_dev, SOLO_SYS_CFG, reg); + + if (solo_dev->flags & FLAGS_6110) { + u32 sys_clock_MHz = SOLO_CLOCK_MHZ; + u32 pll_DIVQ; + u32 pll_DIVF; + + if (sys_clock_MHz < 125) { + pll_DIVQ = 3; + pll_DIVF = (sys_clock_MHz * 4) / 3; + } else { + pll_DIVQ = 2; + pll_DIVF = (sys_clock_MHz * 2) / 3; + } + + solo_reg_write(solo_dev, SOLO6110_PLL_CONFIG, + SOLO6110_PLL_RANGE_5_10MHZ | + SOLO6110_PLL_DIVR(9) | + SOLO6110_PLL_DIVQ_EXP(pll_DIVQ) | + SOLO6110_PLL_DIVF(pll_DIVF) | SOLO6110_PLL_FSEN); + mdelay(1); /* PLL Locking time (1ms) */ + + solo_reg_write(solo_dev, SOLO_DMA_CTRL1, 3 << 8); /* ? */ + } else + solo_reg_write(solo_dev, SOLO_DMA_CTRL1, 1 << 8); /* ? */ + + solo_reg_write(solo_dev, SOLO_TIMER_CLOCK_NUM, SOLO_CLOCK_MHZ - 1); + + /* PLL locking time of 1ms */ + mdelay(1); + + ret = request_irq(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 */ + sdram = (solo_dev->nr_chans >= 8) ? 2 : 1; + solo_reg_write(solo_dev, SOLO_DMA_CTRL, + SOLO_DMA_CTRL_REFRESH_CYCLE(1) | + SOLO_DMA_CTRL_SDRAM_SIZE(sdram) | + SOLO_DMA_CTRL_SDRAM_CLK_INVERT | + SOLO_DMA_CTRL_READ_CLK_SELECT | + SOLO_DMA_CTRL_LATENCY(1)); + + 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); + if (ret) + goto fail_probe; + + ret = solo_enc_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_enc_v4l2_init(solo_dev); + if (ret) + goto fail_probe; + + ret = solo_g723_init(solo_dev); + if (ret) + goto fail_probe; + + return 0; + +fail_probe: + free_solo_dev(solo_dev); + return ret; +} + +static void __devexit solo_pci_remove(struct pci_dev *pdev) +{ + struct solo_dev *solo_dev = pci_get_drvdata(pdev); + + free_solo_dev(solo_dev); +} + +static struct pci_device_id solo_id_table[] = { + /* 6010 based cards */ + {PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6010)}, + {PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6110), + .driver_data = FLAGS_6110}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_9)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_16)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_9)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_16)}, + /* 6110 based cards */ + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_8)}, + {PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_16)}, + {0,} +}; + +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, +}; + +static int __init solo_module_init(void) +{ + return pci_register_driver(&solo_pci_driver); +} + +static void __exit solo_module_exit(void) +{ + pci_unregister_driver(&solo_pci_driver); +} + +module_init(solo_module_init); +module_exit(solo_module_exit); diff --git a/drivers/staging/media/solo6x10/disp.c b/drivers/staging/media/solo6x10/disp.c new file mode 100644 index 00000000..884c0eb7 --- /dev/null +++ b/drivers/staging/media/solo6x10/disp.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#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 512 +#define SOLO_MOT_FLAG_AREA (SOLO_MOT_FLAG_SIZE * 32) + +static unsigned video_type; +module_param(video_type, uint, 0644); +MODULE_PARM_DESC(video_type, "video_type (0 = NTSC/Default, 1 = PAL)"); + +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)); + + solo_reg_write(solo_dev, SOLO_VI_FMT_CFG, 0); + 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_disp_config(struct solo_dev *solo_dev) +{ + solo_dev->vout_hstart = 6; + solo_dev->vout_vstart = 8; + + 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_FMT_ENC, + solo_dev->video_type | + SOLO_VO_USER_COLOR_SET_NAV | + 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)); + + solo_reg_write(solo_dev, SOLO_VI_WIN_SW, 5); + + 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_reg_write(solo_dev, SOLO_VO_DISP_ERASE, SOLO_VO_DISP_ERASE_ON); + + /* Enable channels we support */ + solo_reg_write(solo_dev, SOLO_VI_CH_ENA, (1 << solo_dev->nr_chans) - 1); + + /* Disable the watchdog */ + solo_reg_write(solo_dev, SOLO_WATCHDOG, 0); +} + +static int solo_dma_vin_region(struct solo_dev *solo_dev, u32 off, + u16 val, int reg_size) +{ + u16 buf[64]; + int i; + int ret = 0; + + for (i = 0; i < sizeof(buf) >> 1; i++) + buf[i] = val; + + for (i = 0; i < reg_size; i += sizeof(buf)) + ret |= solo_p2m_dma(solo_dev, SOLO_P2M_DMA_ID_VIN, 1, buf, + SOLO_MOTION_EXT_ADDR(solo_dev) + off + i, + sizeof(buf)); + + return ret; +} + +void solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val) +{ + if (ch > solo_dev->nr_chans) + return; + + solo_dma_vin_region(solo_dev, SOLO_MOT_FLAG_AREA + + (ch * SOLO_MOT_THRESH_SIZE * 2), + val, SOLO_MOT_THRESH_REAL); +} + +/* First 8k is motion flag (512 bytes * 16). Following that is an 8k+8k + * threshold and working table for each channel. Atleast that's what the + * spec says. However, this code (take 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 + + SOLO_MOT_THRESH_SIZE + + (i * SOLO_MOT_THRESH_SIZE * 2), + 0x0000, SOLO_MOT_THRESH_REAL); + + /* 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 (video_type == 0) { + solo_dev->video_type = SOLO_VO_FMT_TYPE_NTSC; + solo_dev->video_vsize = 240; + solo_dev->fps = 30; + } else { + solo_dev->video_type = SOLO_VO_FMT_TYPE_PAL; + solo_dev->video_vsize = 288; + solo_dev->fps = 25; + } + + solo_vin_config(solo_dev); + solo_motion_config(solo_dev); + solo_disp_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_irq_off(solo_dev, SOLO_IRQ_MOTION); + + 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/staging/media/solo6x10/enc.c b/drivers/staging/media/solo6x10/enc.c new file mode 100644 index 00000000..de502599 --- /dev/null +++ b/drivers/staging/media/solo6x10/enc.c @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "solo6x10.h" +#include "osd-font.h" + +#define CAPTURE_MAX_BANDWIDTH 32 /* D1 4channel (D1 == 4) */ +#define OSG_BUFFER_SIZE 1024 + +#define VI_PROG_HSIZE (1280 - 16) +#define VI_PROG_VSIZE (1024 - 16) + +static void solo_capture_config(struct solo_dev *solo_dev) +{ + int i, j; + unsigned long height; + unsigned long width; + unsigned char *buf; + + solo_reg_write(solo_dev, SOLO_CAP_BASE, + SOLO_CAP_MAX_PAGE(SOLO_CAP_EXT_MAX_PAGE * + solo_dev->nr_chans) | + SOLO_CAP_BASE_ADDR(SOLO_CAP_EXT_ADDR(solo_dev) >> 16)); + solo_reg_write(solo_dev, SOLO_CAP_BTW, + (1 << 17) | SOLO_CAP_PROG_BANDWIDTH(2) | + SOLO_CAP_MAX_BANDWIDTH(CAPTURE_MAX_BANDWIDTH)); + + /* 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); + solo_reg_write(solo_dev, SOLO_VE_OSD_OPT, 0); + + /* Clear OSG buffer */ + buf = kzalloc(OSG_BUFFER_SIZE, GFP_KERNEL); + if (!buf) + return; + + for (i = 0; i < solo_dev->nr_chans; i++) { + for (j = 0; j < SOLO_EOSD_EXT_SIZE; j += OSG_BUFFER_SIZE) { + solo_p2m_dma(solo_dev, SOLO_P2M_DMA_ID_MP4E, 1, buf, + SOLO_EOSD_EXT_ADDR + + (i * SOLO_EOSD_EXT_SIZE) + j, + OSG_BUFFER_SIZE); + } + } + kfree(buf); +} + +int solo_osd_print(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + char *str = solo_enc->osd_text; + u8 *buf; + u32 reg = solo_reg_read(solo_dev, SOLO_VE_OSD_CH); + int len = strlen(str); + int i, j; + int x = 1, y = 1; + + if (len == 0) { + reg &= ~(1 << solo_enc->ch); + solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg); + return 0; + } + + buf = kzalloc(SOLO_EOSD_EXT_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < len; i++) { + for (j = 0; j < 16; j++) { + buf[(j*2) + (i%2) + ((x + (i/2)) * 32) + (y * 2048)] = + (solo_osd_font[(str[i] * 4) + (j / 4)] + >> ((3 - (j % 4)) * 8)) & 0xff; + } + } + + solo_p2m_dma(solo_dev, 0, 1, buf, SOLO_EOSD_EXT_ADDR + + (solo_enc->ch * SOLO_EOSD_EXT_SIZE), SOLO_EOSD_EXT_SIZE); + reg |= (1 << solo_enc->ch); + solo_reg_write(solo_dev, SOLO_VE_OSD_CH, reg); + + kfree(buf); + + return 0; +} + +static void solo_jpeg_config(struct solo_dev *solo_dev) +{ + u32 reg; + if (solo_dev->flags & FLAGS_6110) + reg = (4 << 24) | (3 << 16) | (2 << 8) | (1 << 0); + else + reg = (2 << 24) | (2 << 16) | (2 << 8) | (2 << 0); + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_TBL, reg); + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_L, 0); + solo_reg_write(solo_dev, SOLO_VE_JPEG_QP_CH_H, 0); + 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); + /* que limit, samp limit, pos limit */ + solo_reg_write(solo_dev, 0x0688, (0 << 16) | (30 << 8) | 60); +} + +static void solo_mp4e_config(struct solo_dev *solo_dev) +{ + int i; + u32 reg; + + /* We can only use VE_INTR_CTRL(0) if we want to support mjpeg */ + solo_reg_write(solo_dev, SOLO_VE_CFG0, + SOLO_VE_INTR_CTRL(0) | + SOLO_VE_BLOCK_SIZE(SOLO_MP4E_EXT_SIZE(solo_dev) >> 16) | + SOLO_VE_BLOCK_BASE(SOLO_MP4E_EXT_ADDR(solo_dev) >> 16)); + + solo_reg_write(solo_dev, SOLO_VE_CFG1, + SOLO_VE_INSERT_INDEX | SOLO_VE_MOTION_MODE(0)); + + 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); + solo_reg_write(solo_dev, SOLO_VE_ENCRYP_POLY, 0); + solo_reg_write(solo_dev, SOLO_VE_ENCRYP_INIT, 0); + + reg = SOLO_VE_LITTLE_ENDIAN | SOLO_COMP_ATTR_FCODE(1) | + SOLO_COMP_TIME_INC(0) | SOLO_COMP_TIME_WIDTH(15); + if (solo_dev->flags & FLAGS_6110) + reg |= SOLO_DCT_INTERVAL(10); + else + reg |= SOLO_DCT_INTERVAL(36 / 4); + solo_reg_write(solo_dev, SOLO_VE_ATTR, reg); + + 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); + + if (solo_dev->flags & FLAGS_6110) + solo_reg_write(solo_dev, 0x0634, 0x00040008); /* ? */ +} + +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); + } + + solo_irq_on(solo_dev, SOLO_IRQ_ENCODER); + + return 0; +} + +void solo_enc_exit(struct solo_dev *solo_dev) +{ + int i; + + solo_irq_off(solo_dev, SOLO_IRQ_ENCODER); + + 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/staging/media/solo6x10/g723.c b/drivers/staging/media/solo6x10/g723.c new file mode 100644 index 00000000..2cd0de28 --- /dev/null +++ b/drivers/staging/media/solo6x10/g723.c @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "solo6x10.h" +#include "tw28.h" + +#define G723_INTR_ORDER 0 +#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 MAX_BUFFER (G723_PERIOD_BYTES * PERIODS_MAX) +#define IRQ_PAGES 4 /* 0 - 4 */ +#define PERIODS_MIN (1 << IRQ_PAGES) +#define PERIODS_MAX G723_FDMA_PAGES + +struct solo_snd_pcm { + int on; + spinlock_t lock; + struct solo_dev *solo_dev; + unsigned char g723_buf[G723_PERIOD_BYTES]; +}; + +static void solo_g723_config(struct solo_dev *solo_dev) +{ + int clk_div; + + clk_div = SOLO_CLOCK_MHZ / (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(IRQ_PAGES) | + 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; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_G723); + + 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 int snd_solo_hw_params(struct snd_pcm_substream *ss, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(ss, params_buffer_bytes(hw_params)); +} + +static int snd_solo_hw_free(struct snd_pcm_substream *ss) +{ + return snd_pcm_lib_free_pages(ss); +} + +static 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 = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = MAX_BUFFER, + .period_bytes_min = G723_PERIOD_BYTES, + .period_bytes_max = G723_PERIOD_BYTES, + .periods_min = PERIODS_MIN, + .periods_max = PERIODS_MAX, +}; + +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(sizeof(*solo_pcm), GFP_KERNEL); + if (solo_pcm == NULL) + return -ENOMEM; + + 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; +} + +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; + 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, + snd_pcm_uframes_t pos, void __user *dst, + snd_pcm_uframes_t 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(solo_dev, SOLO_P2M_DMA_ID_G723E, 0, + solo_pcm->g723_buf, + SOLO_G723_EXT_ADDR(solo_dev) + + (page * G723_PERIOD_BLOCK) + + (ss->number * G723_PERIOD_BYTES), + G723_PERIOD_BYTES); + if (err) + return err; + + err = copy_to_user(dst + (i * G723_PERIOD_BYTES), + solo_pcm->g723_buf, G723_PERIOD_BYTES); + + if (err) + return -EFAULT; + } + + return 0; +} + +static struct snd_pcm_ops snd_solo_pcm_ops = { + .open = snd_solo_pcm_open, + .close = snd_solo_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_solo_hw_params, + .hw_free = snd_solo_hw_free, + .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 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; + strcpy(pcm->name, card->shortname); + + for (i = 0, ss = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + ss; ss = ss->next, i++) + sprintf(ss->name, "Camera #%d Audio", i); + + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + MAX_BUFFER, MAX_BUFFER); + if (ret < 0) + return ret; + + solo_dev->snd_pcm = pcm; + + return 0; +} + +int solo_g723_init(struct solo_dev *solo_dev) +{ + static struct snd_device_ops ops = { NULL }; + 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_create(SNDRV_DEFAULT_IDX1, name, THIS_MODULE, 0, + &solo_dev->snd_card); + if (ret < 0) + return ret; + + card = solo_dev->snd_card; + + strcpy(card->driver, SOLO6X10_NAME); + strcpy(card->shortname, "SOLO-6x10 Audio"); + sprintf(card->longname, "%s on %s IRQ %d", card->shortname, + pci_name(solo_dev->pdev), solo_dev->pdev->irq); + snd_card_set_dev(card, &solo_dev->pdev->dev); + + ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, solo_dev, &ops); + if (ret < 0) + goto snd_error; + + /* Mixer controls */ + strcpy(card->mixername, "SOLO-6x10"); + 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) + return ret; + + 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) +{ + solo_reg_write(solo_dev, SOLO_AUDIO_CONTROL, 0); + solo_irq_off(solo_dev, SOLO_IRQ_G723); + + snd_card_free(solo_dev->snd_card); +} diff --git a/drivers/staging/media/solo6x10/gpio.c b/drivers/staging/media/solo6x10/gpio.c new file mode 100644 index 00000000..0925e6f3 --- /dev/null +++ b/drivers/staging/media/solo6x10/gpio.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#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 (!((1 << (port + 16)) & port_mask)) + continue; + + if (!mode) + ret &= ~(1 << port); + else + ret |= 1 << port; + } + + 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); +} + +int solo_gpio_init(struct solo_dev *solo_dev) +{ + solo_gpio_config(solo_dev); + return 0; +} + +void solo_gpio_exit(struct solo_dev *solo_dev) +{ + solo_gpio_clear(solo_dev, 0x30); + solo_gpio_config(solo_dev); +} diff --git a/drivers/staging/media/solo6x10/i2c.c b/drivers/staging/media/solo6x10/i2c.c new file mode 100644 index 00000000..ef95a500 --- /dev/null +++ b/drivers/staging/media/solo6x10/i2c.c @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* 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 "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; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_IIC); + + if (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; + 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 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; + } + + dev_info(&solo_dev->pdev->dev, "Enabled %d i2c adapters\n", + SOLO_I2C_ADAPTERS); + + 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/staging/media/solo6x10/offsets.h b/drivers/staging/media/solo6x10/offsets.h new file mode 100644 index 00000000..3d7e569f --- /dev/null +++ b/drivers/staging/media/solo6x10/offsets.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_OFFSETS_H +#define __SOLO6X10_OFFSETS_H + +/* Offsets and sizes of the external address */ +#define SOLO_DISP_EXT_ADDR 0x00000000 +#define SOLO_DISP_EXT_SIZE 0x00480000 + +#define SOLO_DEC2LIVE_EXT_ADDR (SOLO_DISP_EXT_ADDR + SOLO_DISP_EXT_SIZE) +#define SOLO_DEC2LIVE_EXT_SIZE 0x00240000 + +#define SOLO_OSG_EXT_ADDR (SOLO_DEC2LIVE_EXT_ADDR + SOLO_DEC2LIVE_EXT_SIZE) +#define SOLO_OSG_EXT_SIZE 0x00120000 + +#define SOLO_EOSD_EXT_ADDR (SOLO_OSG_EXT_ADDR + SOLO_OSG_EXT_SIZE) +#define SOLO_EOSD_EXT_SIZE 0x00010000 + +#define SOLO_MOTION_EXT_ADDR(__solo) (SOLO_EOSD_EXT_ADDR + \ + (SOLO_EOSD_EXT_SIZE * __solo->nr_chans)) +#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) +#define SOLO_CAP_EXT_MAX_PAGE (18 + 15) +#define SOLO_CAP_EXT_SIZE (SOLO_CAP_EXT_MAX_PAGE * 65536) + +/* This +1 is very important -- Why?! -- BenC */ +#define SOLO_EREF_EXT_ADDR(__solo) \ + (SOLO_CAP_EXT_ADDR(__solo) + \ + (SOLO_CAP_EXT_SIZE * (__solo->nr_chans + 1))) +#define SOLO_EREF_EXT_SIZE 0x00140000 + +#define SOLO_MP4E_EXT_ADDR(__solo) \ + (SOLO_EREF_EXT_ADDR(__solo) + \ + (SOLO_EREF_EXT_SIZE * __solo->nr_chans)) +#define SOLO_MP4E_EXT_SIZE(__solo) (0x00080000 * __solo->nr_chans) + +#define SOLO_DREF_EXT_ADDR(__solo) \ + (SOLO_MP4E_EXT_ADDR(__solo) + SOLO_MP4E_EXT_SIZE(__solo)) +#define SOLO_DREF_EXT_SIZE 0x00140000 + +#define SOLO_MP4D_EXT_ADDR(__solo) \ + (SOLO_DREF_EXT_ADDR(__solo) + \ + (SOLO_DREF_EXT_SIZE * __solo->nr_chans)) +#define SOLO_MP4D_EXT_SIZE 0x00080000 + +#define SOLO_JPEG_EXT_ADDR(__solo) \ + (SOLO_MP4D_EXT_ADDR(__solo) + \ + (SOLO_MP4D_EXT_SIZE * __solo->nr_chans)) +#define SOLO_JPEG_EXT_SIZE(__solo) (0x00080000 * __solo->nr_chans) + +#endif /* __SOLO6X10_OFFSETS_H */ diff --git a/drivers/staging/media/solo6x10/osd-font.h b/drivers/staging/media/solo6x10/osd-font.h new file mode 100644 index 00000000..591e0e82 --- /dev/null +++ b/drivers/staging/media/solo6x10/osd-font.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_OSD_FONT_H +#define __SOLO6X10_OSD_FONT_H + +static const unsigned int solo_osd_font[] = { + 0x00000000, 0x0000c0c8, 0xccfefe0c, 0x08000000, + 0x00000000, 0x10103838, 0x7c7cfefe, 0x00000000, /* 0 */ + 0x00000000, 0xfefe7c7c, 0x38381010, 0x10000000, + 0x00000000, 0x7c82fefe, 0xfefefe7c, 0x00000000, + 0x00000000, 0x00001038, 0x10000000, 0x00000000, + 0x00000000, 0x0010387c, 0xfe7c3810, 0x00000000, + 0x00000000, 0x00384444, 0x44380000, 0x00000000, + 0x00000000, 0x38448282, 0x82443800, 0x00000000, + 0x00000000, 0x007c7c7c, 0x7c7c0000, 0x00000000, + 0x00000000, 0x6c6c6c6c, 0x6c6c6c6c, 0x00000000, + 0x00000000, 0x061e7efe, 0xfe7e1e06, 0x00000000, + 0x00000000, 0xc0f0fcfe, 0xfefcf0c0, 0x00000000, + 0x00000000, 0xc6cedefe, 0xfedecec6, 0x00000000, + 0x00000000, 0xc6e6f6fe, 0xfef6e6c6, 0x00000000, + 0x00000000, 0x12367efe, 0xfe7e3612, 0x00000000, + 0x00000000, 0x90d8fcfe, 0xfefcd890, 0x00000000, + 0x00000038, 0x7cc692ba, 0x92c67c38, 0x00000000, + 0x00000038, 0x7cc6aa92, 0xaac67c38, 0x00000000, + 0x00000038, 0x7830107c, 0xbaa8680c, 0x00000000, + 0x00000038, 0x3c18127c, 0xb8382c60, 0x00000000, + 0x00000044, 0xaa6c8254, 0x38eec67c, 0x00000000, + 0x00000082, 0x44288244, 0x38c6827c, 0x00000000, + 0x00000038, 0x444444fe, 0xfeeec6fe, 0x00000000, + 0x00000018, 0x78187818, 0x3c7e7e3c, 0x00000000, + 0x00000000, 0x3854929a, 0x82443800, 0x00000000, + 0x00000000, 0x00c0c8cc, 0xfefe0c08, 0x00000000, + 0x0000e0a0, 0xe040e00e, 0x8a0ea40e, 0x00000000, + 0x0000e0a0, 0xe040e00e, 0x0a8e440e, 0x00000000, + 0x0000007c, 0x82829292, 0x929282fe, 0x00000000, + 0x000000f8, 0xfc046494, 0x946404fc, 0x00000000, + 0x0000003f, 0x7f404c52, 0x524c407f, 0x00000000, + 0x0000007c, 0x82ba82ba, 0x82ba82fe, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x183c3c3c, 0x18180018, 0x18000000, /* 32 ! */ + 0x00000066, 0x66240000, 0x00000000, 0x00000000, + 0x00000000, 0x6c6cfe6c, 0x6c6cfe6c, 0x6c000000, /* 34 " # */ + 0x00001010, 0x7cd6d616, 0x7cd0d6d6, 0x7c101000, + 0x00000000, 0x0086c660, 0x30180cc6, 0xc2000000, /* 36 $ % */ + 0x00000000, 0x386c6c38, 0xdc766666, 0xdc000000, + 0x0000000c, 0x0c0c0600, 0x00000000, 0x00000000, /* 38 & ' */ + 0x00000000, 0x30180c0c, 0x0c0c0c18, 0x30000000, + 0x00000000, 0x0c183030, 0x30303018, 0x0c000000, /* 40 ( ) */ + 0x00000000, 0x0000663c, 0xff3c6600, 0x00000000, + 0x00000000, 0x00001818, 0x7e181800, 0x00000000, /* 42 * + */ + 0x00000000, 0x00000000, 0x00000e0e, 0x0c060000, + 0x00000000, 0x00000000, 0x7e000000, 0x00000000, /* 44 , - */ + 0x00000000, 0x00000000, 0x00000006, 0x06000000, + 0x00000000, 0x80c06030, 0x180c0602, 0x00000000, /* 46 . / */ + 0x0000007c, 0xc6e6f6de, 0xcec6c67c, 0x00000000, + 0x00000030, 0x383c3030, 0x303030fc, 0x00000000, /* 48 0 1 */ + 0x0000007c, 0xc6c06030, 0x180cc6fe, 0x00000000, + 0x0000007c, 0xc6c0c07c, 0xc0c0c67c, 0x00000000, /* 50 2 3 */ + 0x00000060, 0x70786c66, 0xfe6060f0, 0x00000000, + 0x000000fe, 0x0606067e, 0xc0c0c67c, 0x00000000, /* 52 4 5 */ + 0x00000038, 0x0c06067e, 0xc6c6c67c, 0x00000000, + 0x000000fe, 0xc6c06030, 0x18181818, 0x00000000, /* 54 6 7 */ + 0x0000007c, 0xc6c6c67c, 0xc6c6c67c, 0x00000000, + 0x0000007c, 0xc6c6c6fc, 0xc0c06038, 0x00000000, /* 56 8 9 */ + 0x00000000, 0x18180000, 0x00181800, 0x00000000, + 0x00000000, 0x18180000, 0x0018180c, 0x00000000, /* 58 : ; */ + 0x00000060, 0x30180c06, 0x0c183060, 0x00000000, + 0x00000000, 0x007e0000, 0x007e0000, 0x00000000, + 0x00000006, 0x0c183060, 0x30180c06, 0x00000000, + 0x0000007c, 0xc6c66030, 0x30003030, 0x00000000, + 0x0000007c, 0xc6f6d6d6, 0x7606067c, 0x00000000, + 0x00000010, 0x386cc6c6, 0xfec6c6c6, 0x00000000, /* 64 @ A */ + 0x0000007e, 0xc6c6c67e, 0xc6c6c67e, 0x00000000, + 0x00000078, 0xcc060606, 0x0606cc78, 0x00000000, /* 66 */ + 0x0000003e, 0x66c6c6c6, 0xc6c6663e, 0x00000000, + 0x000000fe, 0x0606063e, 0x060606fe, 0x00000000, /* 68 */ + 0x000000fe, 0x0606063e, 0x06060606, 0x00000000, + 0x00000078, 0xcc060606, 0xf6c6ccb8, 0x00000000, /* 70 */ + 0x000000c6, 0xc6c6c6fe, 0xc6c6c6c6, 0x00000000, + 0x0000003c, 0x18181818, 0x1818183c, 0x00000000, /* 72 */ + 0x00000060, 0x60606060, 0x6066663c, 0x00000000, + 0x000000c6, 0xc666361e, 0x3666c6c6, 0x00000000, /* 74 */ + 0x00000006, 0x06060606, 0x060606fe, 0x00000000, + 0x000000c6, 0xeefed6c6, 0xc6c6c6c6, 0x00000000, /* 76 */ + 0x000000c6, 0xcedefef6, 0xe6c6c6c6, 0x00000000, + 0x00000038, 0x6cc6c6c6, 0xc6c66c38, 0x00000000, /* 78 */ + 0x0000007e, 0xc6c6c67e, 0x06060606, 0x00000000, + 0x00000038, 0x6cc6c6c6, 0xc6d67c38, 0x60000000, /* 80 */ + 0x0000007e, 0xc6c6c67e, 0x66c6c6c6, 0x00000000, + 0x0000007c, 0xc6c60c38, 0x60c6c67c, 0x00000000, /* 82 */ + 0x0000007e, 0x18181818, 0x18181818, 0x00000000, + 0x000000c6, 0xc6c6c6c6, 0xc6c6c67c, 0x00000000, /* 84 */ + 0x000000c6, 0xc6c6c6c6, 0xc66c3810, 0x00000000, + 0x000000c6, 0xc6c6c6c6, 0xd6d6fe6c, 0x00000000, /* 86 */ + 0x000000c6, 0xc6c66c38, 0x6cc6c6c6, 0x00000000, + 0x00000066, 0x66666666, 0x3c181818, 0x00000000, /* 88 */ + 0x000000fe, 0xc0603018, 0x0c0606fe, 0x00000000, + 0x0000003c, 0x0c0c0c0c, 0x0c0c0c3c, 0x00000000, /* 90 */ + 0x00000002, 0x060c1830, 0x60c08000, 0x00000000, + 0x0000003c, 0x30303030, 0x3030303c, 0x00000000, /* 92 */ + 0x00001038, 0x6cc60000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00fe0000, + 0x00001818, 0x30000000, 0x00000000, 0x00000000, + 0x00000000, 0x00003c60, 0x7c66667c, 0x00000000, + 0x0000000c, 0x0c0c7ccc, 0xcccccc7c, 0x00000000, + 0x00000000, 0x00007cc6, 0x0606c67c, 0x00000000, + 0x00000060, 0x60607c66, 0x6666667c, 0x00000000, + 0x00000000, 0x00007cc6, 0xfe06c67c, 0x00000000, + 0x00000078, 0x0c0c0c3e, 0x0c0c0c0c, 0x00000000, + 0x00000000, 0x00007c66, 0x6666667c, 0x60603e00, + 0x0000000c, 0x0c0c7ccc, 0xcccccccc, 0x00000000, + 0x00000030, 0x30003830, 0x30303078, 0x00000000, + 0x00000030, 0x30003c30, 0x30303030, 0x30301f00, + 0x0000000c, 0x0c0ccc6c, 0x3c6ccccc, 0x00000000, + 0x00000030, 0x30303030, 0x30303030, 0x00000000, + 0x00000000, 0x000066fe, 0xd6d6d6d6, 0x00000000, + 0x00000000, 0x000078cc, 0xcccccccc, 0x00000000, + 0x00000000, 0x00007cc6, 0xc6c6c67c, 0x00000000, + 0x00000000, 0x00007ccc, 0xcccccc7c, 0x0c0c0c00, + 0x00000000, 0x00007c66, 0x6666667c, 0x60606000, + 0x00000000, 0x000076dc, 0x0c0c0c0c, 0x00000000, + 0x00000000, 0x00007cc6, 0x1c70c67c, 0x00000000, + 0x00000000, 0x1818fe18, 0x18181870, 0x00000000, + 0x00000000, 0x00006666, 0x6666663c, 0x00000000, + 0x00000000, 0x0000c6c6, 0xc66c3810, 0x00000000, + 0x00000000, 0x0000c6d6, 0xd6d6fe6c, 0x00000000, + 0x00000000, 0x0000c66c, 0x38386cc6, 0x00000000, + 0x00000000, 0x00006666, 0x6666667c, 0x60603e00, + 0x00000000, 0x0000fe60, 0x30180cfe, 0x00000000, + 0x00000070, 0x1818180e, 0x18181870, 0x00000000, + 0x00000018, 0x18181800, 0x18181818, 0x00000000, + 0x0000000e, 0x18181870, 0x1818180e, 0x00000000, + 0x000000dc, 0x76000000, 0x00000000, 0x00000000, + 0x00000000, 0x0010386c, 0xc6c6fe00, 0x00000000 +}; + +#endif /* __SOLO6X10_OSD_FONT_H */ diff --git a/drivers/staging/media/solo6x10/p2m.c b/drivers/staging/media/solo6x10/p2m.c new file mode 100644 index 00000000..56210f0f --- /dev/null +++ b/drivers/staging/media/solo6x10/p2m.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include "solo6x10.h" + +/* #define SOLO_TEST_P2M */ + +int solo_p2m_dma(struct solo_dev *solo_dev, u8 id, int wr, + void *sys_addr, u32 ext_addr, u32 size) +{ + dma_addr_t dma_addr; + int ret; + + WARN_ON(!size); + BUG_ON(id >= SOLO_NR_P2M); + + if (!size) + return -EINVAL; + + dma_addr = pci_map_single(solo_dev->pdev, sys_addr, size, + wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + + ret = solo_p2m_dma_t(solo_dev, id, wr, dma_addr, ext_addr, size); + + pci_unmap_single(solo_dev->pdev, dma_addr, size, + wr ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + + return ret; +} + +int solo_p2m_dma_t(struct solo_dev *solo_dev, u8 id, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size) +{ + struct p2m_desc *desc = kzalloc(sizeof(*desc) * 2, GFP_DMA); + int ret; + + if (desc == NULL) + return -ENOMEM; + + solo_p2m_push_desc(&desc[1], wr, dma_addr, ext_addr, size, 0, 0); + ret = solo_p2m_dma_desc(solo_dev, id, desc, 2); + kfree(desc); + + return ret; +} + +void solo_p2m_push_desc(struct p2m_desc *desc, int wr, dma_addr_t dma_addr, + u32 ext_addr, u32 size, int repeat, u32 ext_size) +{ + desc->ta = cpu_to_le32(dma_addr); + desc->fa = cpu_to_le32(ext_addr); + + desc->ext = cpu_to_le32(SOLO_P2M_COPY_SIZE(size >> 2)); + desc->ctrl = cpu_to_le32(SOLO_P2M_BURST_SIZE(SOLO_P2M_BURST_256) | + (wr ? SOLO_P2M_WRITE : 0) | SOLO_P2M_TRANS_ON); + + /* Ext size only matters when we're repeating */ + if (repeat) { + desc->ext |= cpu_to_le32(SOLO_P2M_EXT_INC(ext_size >> 2)); + desc->ctrl |= cpu_to_le32(SOLO_P2M_PCI_INC(size >> 2) | + SOLO_P2M_REPEAT(repeat)); + } +} + +int solo_p2m_dma_desc(struct solo_dev *solo_dev, u8 id, + struct p2m_desc *desc, int desc_count) +{ + struct solo_p2m_dev *p2m_dev; + unsigned int timeout; + int ret = 0; + u32 config = 0; + dma_addr_t desc_dma = 0; + + BUG_ON(id >= SOLO_NR_P2M); + BUG_ON(!desc_count || desc_count > SOLO_NR_P2M_DESC); + + p2m_dev = &solo_dev->p2m_dev[id]; + + mutex_lock(&p2m_dev->mutex); + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0); + + INIT_COMPLETION(p2m_dev->completion); + p2m_dev->error = 0; + + /* Enable the descriptors */ + config = solo_reg_read(solo_dev, SOLO_P2M_CONFIG(id)); + desc_dma = pci_map_single(solo_dev->pdev, desc, + desc_count * sizeof(*desc), + PCI_DMA_TODEVICE); + solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(id), desc_dma); + solo_reg_write(solo_dev, SOLO_P2M_DESC_ID(id), desc_count - 1); + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(id), config | + SOLO_P2M_DESC_MODE); + + /* Should have all descriptors completed from one interrupt */ + timeout = wait_for_completion_timeout(&p2m_dev->completion, HZ); + + solo_reg_write(solo_dev, SOLO_P2M_CONTROL(id), 0); + + /* Reset back to non-descriptor mode */ + solo_reg_write(solo_dev, SOLO_P2M_CONFIG(id), config); + solo_reg_write(solo_dev, SOLO_P2M_DESC_ID(id), 0); + solo_reg_write(solo_dev, SOLO_P2M_DES_ADR(id), 0); + pci_unmap_single(solo_dev->pdev, desc_dma, + desc_count * sizeof(*desc), + PCI_DMA_TODEVICE); + + if (p2m_dev->error) + ret = -EIO; + else if (timeout == 0) + ret = -EAGAIN; + + mutex_unlock(&p2m_dev->mutex); + + WARN_ON_ONCE(ret); + + return ret; +} + +int solo_p2m_dma_sg(struct solo_dev *solo_dev, u8 id, + struct p2m_desc *pdesc, int wr, + struct scatterlist *sg, u32 sg_off, + u32 ext_addr, u32 size) +{ + int i; + int idx; + + BUG_ON(id >= SOLO_NR_P2M); + + if (WARN_ON_ONCE(!size)) + return -EINVAL; + + memset(pdesc, 0, sizeof(*pdesc)); + + /* Should rewrite this to handle > SOLO_NR_P2M_DESC transactions */ + for (i = 0, idx = 1; idx < SOLO_NR_P2M_DESC && sg && size > 0; + i++, sg = sg_next(sg)) { + struct p2m_desc *desc = &pdesc[idx]; + u32 sg_len = sg_dma_len(sg); + u32 len; + + if (sg_off >= sg_len) { + sg_off -= sg_len; + continue; + } + + sg_len -= sg_off; + len = min(sg_len, size); + + solo_p2m_push_desc(desc, wr, sg_dma_address(sg) + sg_off, + ext_addr, len, 0, 0); + + size -= len; + ext_addr += len; + idx++; + + sg_off = 0; + } + + WARN_ON_ONCE(size || i >= SOLO_NR_P2M_DESC); + + return solo_p2m_dma_desc(solo_dev, id, pdesc, idx); +} + +#ifdef SOLO_TEST_P2M + +#define P2M_TEST_CHAR 0xbe + +static unsigned long long p2m_test(struct solo_dev *solo_dev, u8 id, + u32 base, int size) +{ + u8 *wr_buf; + u8 *rd_buf; + int i; + unsigned long long err_cnt = 0; + + wr_buf = kmalloc(size, GFP_KERNEL); + if (!wr_buf) { + printk(SOLO6X10_NAME ": Failed to malloc for p2m_test\n"); + return size; + } + + rd_buf = kmalloc(size, GFP_KERNEL); + if (!rd_buf) { + printk(SOLO6X10_NAME ": Failed to malloc for p2m_test\n"); + kfree(wr_buf); + return size; + } + + memset(wr_buf, P2M_TEST_CHAR, size); + memset(rd_buf, P2M_TEST_CHAR + 1, size); + + solo_p2m_dma(solo_dev, id, 1, wr_buf, base, size); + solo_p2m_dma(solo_dev, id, 0, rd_buf, base, size); + + for (i = 0; i < size; i++) + if (wr_buf[i] != rd_buf[i]) + err_cnt++; + + kfree(wr_buf); + kfree(rd_buf); + + return err_cnt; +} + +#define TEST_CHUNK_SIZE (8 * 1024) + +static void run_p2m_test(struct solo_dev *solo_dev) +{ + unsigned long long errs = 0; + u32 size = SOLO_JPEG_EXT_ADDR(solo_dev) + SOLO_JPEG_EXT_SIZE(solo_dev); + int i, d; + + printk(KERN_WARNING "%s: Testing %u bytes of external ram\n", + SOLO6X10_NAME, size); + + for (i = 0; i < size; i += TEST_CHUNK_SIZE) + for (d = 0; d < 4; d++) + errs += p2m_test(solo_dev, d, i, TEST_CHUNK_SIZE); + + printk(KERN_WARNING "%s: Found %llu errors during p2m test\n", + SOLO6X10_NAME, errs); + + return; +} +#else +#define run_p2m_test(__solo) do {} while (0) +#endif + +void solo_p2m_isr(struct solo_dev *solo_dev, int id) +{ + struct solo_p2m_dev *p2m_dev = &solo_dev->p2m_dev[id]; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_P2M(id)); + + complete(&p2m_dev->completion); +} + +void solo_p2m_error_isr(struct solo_dev *solo_dev, u32 status) +{ + struct solo_p2m_dev *p2m_dev; + int i; + + if (!(status & SOLO_PCI_ERR_P2M)) + 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)); +} + +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_DMA_INTERVAL(3) | + SOLO_P2M_DESC_INTR_OPT | + SOLO_P2M_PCI_MASTER_MODE); + solo_irq_on(solo_dev, SOLO_IRQ_P2M(i)); + } + + run_p2m_test(solo_dev); + + return 0; +} diff --git a/drivers/staging/media/solo6x10/registers.h b/drivers/staging/media/solo6x10/registers.h new file mode 100644 index 00000000..aca54447 --- /dev/null +++ b/drivers/staging/media/solo6x10/registers.h @@ -0,0 +1,637 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_REGISTERS_H +#define __SOLO6X10_REGISTERS_H + +#include "offsets.h" + +/* Global 6X10 system configuration */ +#define SOLO_SYS_CFG 0x0000 +#define SOLO6010_SYS_CFG_FOUT_EN 0x00000001 /* 6010 only */ +#define SOLO6010_SYS_CFG_PLL_BYPASS 0x00000002 /* 6010 only */ +#define SOLO6010_SYS_CFG_PLL_PWDN 0x00000004 /* 6010 only */ +#define SOLO6010_SYS_CFG_OUTDIV(__n) (((__n) & 0x003) << 3) /* 6010 only */ +#define SOLO6010_SYS_CFG_FEEDBACKDIV(__n) (((__n) & 0x1ff) << 5) /* 6010 only */ +#define SOLO6010_SYS_CFG_INPUTDIV(__n) (((__n) & 0x01f) << 14) /* 6010 only */ +#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 /* 6110: must be set */ +#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 (1<<5) +#define SOLO_DMA_CTRL_STROBE_SELECT (1<<4) +#define SOLO_DMA_CTRL_READ_DATA_SELECT (1<<3) +#define SOLO_DMA_CTRL_READ_CLK_SELECT (1<<2) +#define SOLO_DMA_CTRL_LATENCY(n) ((n)<<0) +#define SOLO_DMA_CTRL1 0x0008 + +#define SOLO_SYS_VCLK 0x000C +#define SOLO_VCLK_INVERT (1<<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_ENABLE 0x0014 +#define SOLO_IRQ_P2M(n) (1<<((n)+17)) +#define SOLO_IRQ_GPIO (1<<16) +#define SOLO_IRQ_VIDEO_LOSS (1<<15) +#define SOLO_IRQ_VIDEO_IN (1<<14) +#define SOLO_IRQ_MOTION (1<<13) +#define SOLO_IRQ_ATA_CMD (1<<12) +#define SOLO_IRQ_ATA_DIR (1<<11) +#define SOLO_IRQ_PCI_ERR (1<<10) +#define SOLO_IRQ_PS2_1 (1<<9) +#define SOLO_IRQ_PS2_0 (1<<8) +#define SOLO_IRQ_SPI (1<<7) +#define SOLO_IRQ_IIC (1<<6) +#define SOLO_IRQ_UART(n) (1<<((n) + 4)) +#define SOLO_IRQ_G723 (1<<3) +#define SOLO_IRQ_DECODER (1<<1) +#define SOLO_IRQ_ENCODER (1<<0) + +#define SOLO_CHIP_OPTION 0x001C +#define SOLO_CHIP_ID_MASK 0x00000007 + +#define SOLO6110_PLL_CONFIG 0x0020 +#define SOLO6110_PLL_RANGE_BYPASS (0 << 20) +#define SOLO6110_PLL_RANGE_5_10MHZ (1 << 20) +#define SOLO6110_PLL_RANGE_8_16MHZ (2 << 20) +#define SOLO6110_PLL_RANGE_13_26MHZ (3 << 20) +#define SOLO6110_PLL_RANGE_21_42MHZ (4 << 20) +#define SOLO6110_PLL_RANGE_34_68MHZ (5 << 20) +#define SOLO6110_PLL_RANGE_54_108MHZ (6 << 20) +#define SOLO6110_PLL_RANGE_88_200MHZ (7 << 20) +#define SOLO6110_PLL_DIVR(x) (((x) - 1) << 15) +#define SOLO6110_PLL_DIVQ_EXP(x) ((x) << 12) +#define SOLO6110_PLL_DIVF(x) (((x) - 1) << 4) +#define SOLO6110_PLL_RESET (1 << 3) +#define SOLO6110_PLL_BYPASS (1 << 2) +#define SOLO6110_PLL_FSEN (1 << 1) +#define SOLO6110_PLL_FB (1 << 0) + +#define SOLO_EEPROM_CTRL 0x0060 +#define SOLO_EEPROM_ACCESS_EN (1<<7) +#define SOLO_EEPROM_CS (1<<3) +#define SOLO_EEPROM_CLK (1<<2) +#define SOLO_EEPROM_DO (1<<1) +#define SOLO_EEPROM_DI (1<<0) +#define SOLO_EEPROM_ENABLE (EEPROM_ACCESS_EN | 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 (1<<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 (1<<4) +#define SOLO_P2M_UV_SWAP (1<<3) +#define SOLO_P2M_PCI_MASTER_MODE (1<<2) +#define SOLO_P2M_DESC_INTR_OPT (1<<1) /* 1:Empty, 0:Each */ +#define SOLO_P2M_DESC_MODE (1<<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 (1<<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 (1<<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 (1<<3) +#define SOLO_P2M_INTERRUPT_REQ (1<<2) +#define SOLO_P2M_WRITE (1<<1) +#define SOLO_P2M_TRANS_ON (1<<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 (1<<31) +#define SOLO_VI_FMT_CHECK_HCOUNT (1<<30) +#define SOLO_VI_FMT_TEST_SIGNAL (1<<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 (1<<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 (1<<1) +#define SOLO_VI_PB_PAL (1<<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 (1<<15) +#define SOLO_VI_MOTION_FREEZE_DATA (1<<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 (1<<29) +#define SOLO_VI_MOTION_Y_ADD (1<<28) +#define SOLO_VI_MOTION_CB_SET (1<<27) +#define SOLO_VI_MOTION_CB_ADD (1<<26) +#define SOLO_VI_MOTION_CR_SET (1<<25) +#define SOLO_VI_MOTION_CR_ADD (1<<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 (1<<31) +#define SOLO_VO_FMT_TYPE_PAL (1<<30) +#define SOLO_VO_FMT_TYPE_NTSC 0 +#define SOLO_VO_USER_SET (1<<29) + +#define SOLO_VO_FI_CHANGE (1<<20) +#define SOLO_VO_USER_COLOR_SET_VSYNC (1<<19) +#define SOLO_VO_USER_COLOR_SET_HSYNC (1<<18) +#define SOLO_VO_USER_COLOR_SET_NAV (1<<17) +#define SOLO_VO_USER_COLOR_SET_NAH (1<<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 (1<<24) +#define SOLO_VO_HSYNC_INVERT (1<<23) +#define SOLO_VO_VSYNC_INVERT (1<<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 (1<<31) +#define SOLO_VO_DISP_ERASE_COUNT(n) ((n&0xf)<<24) +#define SOLO_VO_DISP_DOUBLE_SCAN (1<<22) +#define SOLO_VO_DISP_SINGLE_PAGE (1<<21) +#define SOLO_VO_DISP_BASE(n) (((n)>>16) & 0xffff) + +#define SOLO_VO_DISP_ERASE 0x0314 +#define SOLO_VO_DISP_ERASE_ON (1<<0) + +#define SOLO_VO_ZOOM_CTRL 0x0318 +#define SOLO_VO_ZOOM_VER_ON (1<<24) +#define SOLO_VO_ZOOM_HOR_ON (1<<23) +#define SOLO_VO_ZOOM_V_COMP (1<<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 (1<<1) +#define SOLO_VO_FREEZE_INTERPOLATION (1<<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 (1<<31) +#define SOLO_VO_OSG_COLOR_MUTE (1<<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 (1<<1) +#define SOLO_VO_OSG_BLINK_INTREVAL18 (1<<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 (1<<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 SOLO6110_VE_MPEG_SIZE_H(n) ((n)<<28) /* 6110 only */ +#define SOLO6010_VE_BYTE_ALIGN(n) ((n)<<24) /* 6010 only */ +#define SOLO6110_VE_JPEG_SIZE_H(n) ((n)<<20) /* 6110 only */ +#define SOLO_VE_INSERT_INDEX (1<<18) +#define SOLO_VE_MOTION_MODE(n) ((n)<<16) +#define SOLO_VE_MOTION_BASE(n) ((n)<<0) + +#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 (1<<31) +#define SOLO_COMP_ATTR_RN (1<<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_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_OSD_CH 0x0690 +#define SOLO_VE_OSD_BASE 0x0694 +#define SOLO_VE_OSD_CLR 0x0698 +#define SOLO_VE_OSD_OPT 0x069C + +#define SOLO_VE_CH_INTL(ch) (0x0700+((ch)*4)) +#define SOLO6010_VE_CH_MOT(ch) (0x0740+((ch)*4)) /* 6010 only */ +#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 SOLO6010_VD_CFG_NO_WRITE_NO_WINDOW (1<<24) /* 6010 only */ +#define SOLO_VD_CFG_BUSY_WIAT_CODE (1<<23) +#define SOLO_VD_CFG_BUSY_WIAT_REF (1<<22) +#define SOLO_VD_CFG_BUSY_WIAT_RES (1<<21) +#define SOLO_VD_CFG_BUSY_WIAT_MS (1<<20) +#define SOLO_VD_CFG_SINGLE_MODE (1<<18) +#define SOLO_VD_CFG_SCAL_MANUAL (1<<17) +#define SOLO_VD_CFG_USER_PAGE_CTRL (1<<16) +#define SOLO_VD_CFG_LITTLE_ENDIAN (1<<15) +#define SOLO_VD_CFG_START_FI (1<<14) +#define SOLO_VD_CFG_ERR_LOCK (1<<13) +#define SOLO_VD_CFG_ERR_INT_ENA (1<<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 (1<<31) +#define SOLO_VD_MAX_ITEM(n) ((n)<<0) + +#define SOLO_VD_STATUS0 0x0920 +#define SOLO_VD_STATUS0_INTR_ACK (1<<22) +#define SOLO_VD_STATUS0_INTR_EMPTY (1<<21) +#define SOLO_VD_STATUS0_INTR_ERR (1<<20) + +#define SOLO_VD_STATUS1 0x0924 + +#define SOLO_VD_IDX0 0x0930 +#define SOLO_VD_IDX_INTERLACE (1<<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 (1<<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 (1<<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 (1<<27) +#define SOLO_VD_IDX_INTERPOL (1<<26) +#define SOLO_VD_IDX_HOR2X (1<<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 (1<<8) +#define SOLO_IIC_PRESCALE(n) ((n)<<0) + +#define SOLO_IIC_CTRL 0x0B24 +#define SOLO_IIC_AUTO_CLEAR (1<<20) +#define SOLO_IIC_STATE_RX_ACK (1<<19) +#define SOLO_IIC_STATE_BUSY (1<<18) +#define SOLO_IIC_STATE_SIG_ERR (1<<17) +#define SOLO_IIC_STATE_TRNS (1<<16) +#define SOLO_IIC_CH_SET(n) ((n)<<5) +#define SOLO_IIC_ACK_EN (1<<4) +#define SOLO_IIC_START (1<<3) +#define SOLO_IIC_STOP (1<<2) +#define SOLO_IIC_READ (1<<1) +#define SOLO_IIC_WRITE (1<<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 (1<<20) +#define SOLO_PARITY_ERROR_DROP (1<<18) +#define SOLO_IRQ_ERR_EN (1<<17) +#define SOLO_IRQ_RX_EN (1<<16) +#define SOLO_IRQ_TX_EN (1<<15) +#define SOLO_RX_EN (1<<14) +#define SOLO_TX_EN (1<<13) +#define SOLO_UART_HALF_DUPLEX (1<<12) +#define SOLO_UART_LOOPBACK (1<<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 (1<<15) +#define SOLO_UART_RX_BUSY (1<<14) +#define SOLO_UART_OVERRUN (1<<13) +#define SOLO_UART_FRAME_ERR (1<<12) +#define SOLO_UART_PARITY_ERR (1<<11) +#define SOLO_UART_TX_BUSY (1<<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 (1<<8) +#define SOLO_UART_RX_DATA(n) (0x0BAC + ((n)*0x20)) +#define SOLO_UART_RX_DATA_POP (1<<8) + +#define SOLO_TIMER_CLOCK_NUM 0x0be0 +#define SOLO_TIMER_WATCHDOG 0x0be4 +#define SOLO_TIMER_USEC 0x0be8 +#define SOLO_TIMER_SEC 0x0bec + +#define SOLO_AUDIO_CONTROL 0x0D00 +#define SOLO_AUDIO_ENABLE (1<<31) +#define SOLO_AUDIO_MASTER_MODE (1<<30) +#define SOLO_AUDIO_I2S_MODE (1<<29) +#define SOLO_AUDIO_I2S_LR_SWAP (1<<27) +#define SOLO_AUDIO_I2S_8BIT (1<<26) +#define SOLO_AUDIO_I2S_MULTI(n) ((n)<<24) +#define SOLO_AUDIO_MIX_9TO0 (1<<23) +#define SOLO_AUDIO_DEC_9TO0_VOL(n) ((n)<<20) +#define SOLO_AUDIO_MIX_19TO10 (1<<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 (1<<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 + + +#define SOLO_WATCHDOG 0x0BE4 +#define WATCHDOG_STAT(status) (status<<8) +#define WATCHDOG_TIME(sec) (sec&0xff) + +#endif /* __SOLO6X10_REGISTERS_H */ diff --git a/drivers/staging/media/solo6x10/solo6x10-jpeg.h b/drivers/staging/media/solo6x10/solo6x10-jpeg.h new file mode 100644 index 00000000..50defec3 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10-jpeg.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_JPEG_H +#define __SOLO6X10_JPEG_H + +static unsigned char 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 + +#endif /* __SOLO6X10_JPEG_H */ diff --git a/drivers/staging/media/solo6x10/solo6x10.h b/drivers/staging/media/solo6x10/solo6x10.h new file mode 100644 index 00000000..abee7213 --- /dev/null +++ b/drivers/staging/media/solo6x10/solo6x10.h @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __SOLO6X10_H +#define __SOLO6X10_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "registers.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 */ + +#define SOLO6X10_NAME "solo6x10" + +#define SOLO_MAX_CHANNELS 16 + +/* Make sure these two match */ +#define SOLO6X10_VERSION "2.1.0" +#define SOLO6X10_VER_MAJOR 2 +#define SOLO6X10_VER_MINOR 0 +#define SOLO6X10_VER_SUB 0 +#define SOLO6X10_VER_NUM \ + KERNEL_VERSION(SOLO6X10_VER_MAJOR, SOLO6X10_VER_MINOR, SOLO6X10_VER_SUB) + +#define FLAGS_6110 1 + +/* + * 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 +/* MPEG and JPEG share the same interrupt and locks so they must be together + * in the same dma channel. */ +#define SOLO_P2M_DMA_ID_MP4E 0 +#define SOLO_P2M_DMA_ID_JPEG 0 +#define SOLO_P2M_DMA_ID_MP4D 1 +#define SOLO_P2M_DMA_ID_G723D 1 +#define SOLO_P2M_DMA_ID_DISP 2 +#define SOLO_P2M_DMA_ID_OSG 2 +#define SOLO_P2M_DMA_ID_G723E 3 +#define SOLO_P2M_DMA_ID_VIN 3 + +/* Encoder standard modes */ +#define SOLO_ENC_MODE_CIF 2 +#define SOLO_ENC_MODE_HD1 1 +#define SOLO_ENC_MODE_D1 9 + +#define SOLO_DEFAULT_GOP 30 +#define SOLO_DEFAULT_QP 3 + +/* There is 8MB memory available for solo to buffer MPEG4 frames. + * This gives us 512 * 16kbyte queues. */ +#define SOLO_NR_RING_BUFS 512 + +#define SOLO_CLOCK_MHZ 108 + +#ifndef V4L2_BUF_FLAG_MOTION_ON +#define V4L2_BUF_FLAG_MOTION_ON 0x0400 +#define V4L2_BUF_FLAG_MOTION_DETECTED 0x0800 +#endif +#ifndef V4L2_CID_MOTION_ENABLE +#define PRIVATE_CIDS +#define V4L2_CID_MOTION_ENABLE (V4L2_CID_PRIVATE_BASE+0) +#define V4L2_CID_MOTION_THRESHOLD (V4L2_CID_PRIVATE_BASE+1) +#define V4L2_CID_MOTION_TRACE (V4L2_CID_PRIVATE_BASE+2) +#endif + +enum SOLO_I2C_STATE { + IIC_STATE_IDLE, + IIC_STATE_START, + IIC_STATE_READ, + IIC_STATE_WRITE, + IIC_STATE_STOP +}; + +struct p2m_desc { + u32 ctrl; + u32 ext; + u32 ta; + u32 fa; +}; + +struct solo_p2m_dev { + struct mutex mutex; + struct completion completion; + int error; +}; + +#define OSD_TEXT_MAX 30 + +enum solo_enc_types { + SOLO_ENC_TYPE_STD, + SOLO_ENC_TYPE_EXT, +}; + +struct solo_enc_dev { + struct solo_dev *solo_dev; + /* V4L2 Items */ + struct video_device *vfd; + /* General accounting */ + wait_queue_head_t thread_wait; + spinlock_t lock; + atomic_t readers; + u8 ch; + u8 mode, gop, qp, interlaced, interval; + u8 reset_gop; + u8 bw_weight; + u8 motion_detected; + u16 motion_thresh; + u16 width; + u16 height; + char osd_text[OSD_TEXT_MAX + 1]; +}; + +struct solo_enc_buf { + u8 vop; + u8 ch; + enum solo_enc_types type; + u32 off; + u32 size; + u32 jpeg_off; + u32 jpeg_size; + struct timeval ts; +}; + +/* The SOLO6x10 PCI Device */ +struct solo_dev { + /* General stuff */ + struct pci_dev *pdev; + u8 __iomem *reg_base; + int nr_chans; + int nr_ext; + u32 flags; + u32 irq_mask; + u32 motion_mask; + spinlock_t reg_io_lock; + + /* 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]; + + /* 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; + + /* V4L2 Encoder items */ + struct solo_enc_dev *v4l2_enc[SOLO_MAX_CHANNELS]; + u16 enc_bw_remain; + /* IDX into hw mp4 encoder */ + u8 enc_idx; + /* Our software ring of enc buf references */ + u16 enc_wr_idx; + struct solo_enc_buf enc_buf[SOLO_NR_RING_BUFS]; + + /* Current video settings */ + u32 video_type; + u16 video_hsize, video_vsize; + u16 vout_hstart, vout_vstart; + u16 vin_hstart, vin_vstart; + u8 fps; + + /* Audio components */ + struct snd_card *snd_card; + struct snd_pcm *snd_pcm; + atomic_t snd_users; + int g723_hw_idx; +}; + +static inline u32 solo_reg_read(struct solo_dev *solo_dev, int reg) +{ + unsigned long flags; + u32 ret; + u16 val; + + spin_lock_irqsave(&solo_dev->reg_io_lock, flags); + + ret = readl(solo_dev->reg_base + reg); + rmb(); + pci_read_config_word(solo_dev->pdev, PCI_STATUS, &val); + rmb(); + + spin_unlock_irqrestore(&solo_dev->reg_io_lock, flags); + + return ret; +} + +static inline void solo_reg_write(struct solo_dev *solo_dev, int reg, u32 data) +{ + unsigned long flags; + u16 val; + + spin_lock_irqsave(&solo_dev->reg_io_lock, flags); + + writel(data, solo_dev->reg_base + reg); + wmb(); + pci_read_config_word(solo_dev->pdev, PCI_STATUS, &val); + rmb(); + + spin_unlock_irqrestore(&solo_dev->reg_io_lock, flags); +} + +void solo_irq_on(struct solo_dev *solo_dev, u32 mask); +void solo_irq_off(struct solo_dev *solo_dev, u32 mask); + +/* Init/exit routeines 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); +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); +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, u32 status); +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, u8 id, int wr, + dma_addr_t dma_addr, u32 ext_addr, u32 size); +int solo_p2m_dma(struct solo_dev *solo_dev, u8 id, int wr, + void *sys_addr, u32 ext_addr, u32 size); +int solo_p2m_dma_sg(struct solo_dev *solo_dev, u8 id, + struct p2m_desc *pdesc, int wr, + struct scatterlist *sglist, u32 sg_off, + u32 ext_addr, u32 size); +void solo_p2m_push_desc(struct 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, u8 id, + struct p2m_desc *desc, int desc_count); + +/* Set the threshold for motion detection */ +void solo_set_motion_threshold(struct solo_dev *solo_dev, u8 ch, u16 val); +#define SOLO_DEF_MOT_THRESH 0x0300 + +/* Write text on OSD */ +int solo_osd_print(struct solo_enc_dev *solo_enc); + +#endif /* __SOLO6X10_H */ diff --git a/drivers/staging/media/solo6x10/tw28.c b/drivers/staging/media/solo6x10/tw28.c new file mode 100644 index 00000000..db56b42c --- /dev/null +++ b/drivers/staging/media/solo6x10/tw28.c @@ -0,0 +1,821 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include "solo6x10.h" +#include "tw28.h" + +/* XXX: Some of these values are masked into an 8-bit regs, and shifted + * around for other 8-bit regs. What are the magic bits in these values? */ +#define DEFAULT_HDELAY_NTSC (32 - 4) +#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 u8 tbl_tw2864_template[] = { + 0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, /* 0x00 */ + 0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, /* 0x10 */ + 0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, /* 0x20 */ + 0x12, 0xf5, 0x09, 0xd0, 0x00, 0x00, 0x00, 0x7f, + 0x00, 0x00, 0x80, 0x10, 0x80, 0x80, 0x00, 0x02, /* 0x30 */ + 0x12, 0xf5, 0x09, 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, 0xd1, 0x0f, 0x11, 0x11, 0x81, + 0x10, 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 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 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 & (1 << __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 */ + if (i >= 0xb8 && i <= 0xc1) + continue; + if ((i & ~0x30) == 0x00 || + (i & ~0x30) == 0x0c || + (i & ~0x30) == 0x0d) + continue; + if (i >= 0xc4 && i <= 0xc7) + continue; + if (i == 0xfd) + 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[sizeof(tbl_tw2864_template)]; + int i; + + memcpy(tbl_tw2864_common, tbl_tw2864_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; + } + } + + /* NTSC or PAL */ + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_PAL) { + for (i = 0; i < 4; i++) { + tbl_tw2864_common[0x07 | (i << 4)] |= 0x10; + tbl_tw2864_common[0x08 | (i << 4)] |= 0x06; + tbl_tw2864_common[0x0a | (i << 4)] |= 0x08; + tbl_tw2864_common[0x0b | (i << 4)] |= 0x13; + tbl_tw2864_common[0x0e | (i << 4)] |= 0x01; + } + tbl_tw2864_common[0x9d] = 0x90; + tbl_tw2864_common[0xf3] = 0x00; + tbl_tw2864_common[0xf4] = 0xa0; + } + + for (i = 0; i < 0xff; i++) { + /* Skip read only registers */ + if (i >= 0xb8 && i <= 0xc1) + continue; + if ((i & ~0x30) == 0x00 || + (i & ~0x30) == 0x0c || + (i & ~0x30) == 0x0d) + continue; + if (i == 0x74 || i == 0x77 || i == 0x78 || + i == 0x79 || i == 0x7a) + continue; + if (i == 0xfd) + 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 saa7128_setup(struct solo_dev *solo_dev) +{ + int i; + unsigned char regs[128] = { + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 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, + 0x1C, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x59, 0x1d, 0x75, 0x3f, 0x06, 0x3f, 0x00, 0x00, + 0x1c, 0x33, 0x00, 0x3f, 0x00, 0x00, 0x3f, 0x00, + 0x1a, 0x1a, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x68, 0x10, 0x97, 0x4c, 0x18, + 0x9b, 0x93, 0x9f, 0xff, 0x7c, 0x34, 0x3f, 0x3f, + 0x3f, 0x83, 0x83, 0x80, 0x0d, 0x0f, 0xc3, 0x06, + 0x02, 0x80, 0x71, 0x77, 0xa7, 0x67, 0x66, 0x2e, + 0x7b, 0x11, 0x4f, 0x1f, 0x7c, 0xf0, 0x21, 0x77, + 0x41, 0x88, 0x41, 0x12, 0xed, 0x10, 0x10, 0x00, + 0x41, 0xc3, 0x00, 0x3e, 0xb8, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x08, 0xff, 0x80, 0x00, 0xff, 0xff, + }; + + regs[0x7A] = FIRST_ACTIVE_LINE & 0xff; + regs[0x7B] = LAST_ACTIVE_LINE & 0xff; + regs[0x7C] = ((1 << 7) | + (((LAST_ACTIVE_LINE >> 8) & 1) << 6) | + (((FIRST_ACTIVE_LINE >> 8) & 1) << 4)); + + /* PAL: XXX: We could do a second set of regs to avoid this */ + if (solo_dev->video_type != SOLO_VO_FMT_TYPE_NTSC) { + regs[0x28] = 0xE1; + + regs[0x5A] = 0x0F; + regs[0x61] = 0x02; + regs[0x62] = 0x35; + regs[0x63] = 0xCB; + regs[0x64] = 0x8A; + regs[0x65] = 0x09; + regs[0x66] = 0x2A; + + regs[0x6C] = 0xf1; + regs[0x6E] = 0x20; + + regs[0x7A] = 0x06 + 12; + regs[0x7b] = 0x24 + 12; + regs[0x7c] |= 1 << 6; + } + + /* First 0x25 bytes are read-only? */ + for (i = 0x26; i < 128; i++) { + if (i == 0x60 || i == 0x7D) + continue; + solo_i2c_writebyte(solo_dev, SOLO_I2C_SAA, 0x46, i, regs[i]); + } + + return; +} + +int solo_tw28_init(struct solo_dev *solo_dev) +{ + int i; + u8 value; + + /* Detect techwell chip type */ + for (i = 0; i < TW_NUM_CHIP; 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: + 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) + return -EINVAL; + + saa7128_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)); + } + + dev_info(&solo_dev->pdev->dev, "Initialized %d tw28xx chip%s:", + solo_dev->tw28_cnt, solo_dev->tw28_cnt == 1 ? "" : "s"); + + if (solo_dev->tw2865) + printk(" tw2865[%d]", hweight32(solo_dev->tw2865)); + if (solo_dev->tw2864) + printk(" tw2864[%d]", hweight32(solo_dev->tw2864)); + if (solo_dev->tw2815) + printk(" tw2815[%d]", hweight32(solo_dev->tw2815)); + printk("\n"); + + 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 + +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 (val > 255 || val < 0) + return -ERANGE; + + switch (ctrl) { + case V4L2_CID_SHARPNESS: + /* Only 286x has sharpness */ + if (val > 0x0f || val < 0) + return -ERANGE; + 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 if (val != 0) + return -ERANGE; + 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: + 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; + + 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/staging/media/solo6x10/tw28.h b/drivers/staging/media/solo6x10/tw28.h new file mode 100644 index 00000000..a44a03af --- /dev/null +++ b/drivers/staging/media/solo6x10/tw28.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#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); + +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/staging/media/solo6x10/v4l2-enc.c b/drivers/staging/media/solo6x10/v4l2-enc.c new file mode 100644 index 00000000..f8f0da95 --- /dev/null +++ b/drivers/staging/media/solo6x10/v4l2-enc.c @@ -0,0 +1,1825 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "solo6x10.h" +#include "tw28.h" +#include "solo6x10-jpeg.h" + +#define MIN_VID_BUFFERS 4 +#define FRAME_BUF_SIZE (128 * 1024) +#define MP4_QS 16 + +static int solo_enc_thread(void *data); + +extern unsigned video_nr; + +struct solo_enc_fh { + struct solo_enc_dev *enc; + u32 fmt; + u16 rd_idx; + u8 enc_on; + enum solo_enc_types type; + struct videobuf_queue vidq; + struct list_head vidq_active; + struct task_struct *kthread; + struct p2m_desc desc[SOLO_NR_P2M_DESC]; +}; + +static const u32 solo_user_ctrls[] = { + V4L2_CID_BRIGHTNESS, + V4L2_CID_CONTRAST, + V4L2_CID_SATURATION, + V4L2_CID_HUE, + V4L2_CID_SHARPNESS, + 0 +}; + +static const u32 solo_mpeg_ctrls[] = { + V4L2_CID_MPEG_VIDEO_ENCODING, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, + 0 +}; + +static const u32 solo_private_ctrls[] = { + V4L2_CID_MOTION_ENABLE, + V4L2_CID_MOTION_THRESHOLD, + 0 +}; + +static const u32 solo_fmtx_ctrls[] = { + V4L2_CID_RDS_TX_RADIO_TEXT, + 0 +}; + +static const u32 *solo_ctrl_classes[] = { + solo_user_ctrls, + solo_mpeg_ctrls, + solo_fmtx_ctrls, + solo_private_ctrls, + NULL +}; + +static int solo_is_motion_on(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + u8 ch = solo_enc->ch; + + if (solo_dev->motion_mask & (1 << ch)) + return 1; + return 0; +} + +static void solo_motion_toggle(struct solo_enc_dev *solo_enc, int on) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + u8 ch = solo_enc->ch; + + spin_lock(&solo_enc->lock); + + if (on) + solo_dev->motion_mask |= (1 << ch); + else + solo_dev->motion_mask &= ~(1 << ch); + + /* Do this regardless of if we are turning on or off */ + solo_reg_write(solo_enc->solo_dev, SOLO_VI_MOT_CLEAR, + 1 << solo_enc->ch); + solo_enc->motion_detected = 0; + + solo_reg_write(solo_dev, SOLO_VI_MOT_ADR, + SOLO_VI_MOTION_EN(solo_dev->motion_mask) | + (SOLO_MOTION_EXT_ADDR(solo_dev) >> 16)); + + if (solo_dev->motion_mask) + solo_irq_on(solo_dev, SOLO_IRQ_MOTION); + else + solo_irq_off(solo_dev, SOLO_IRQ_MOTION); + + spin_unlock(&solo_enc->lock); +} + +/* Should be called with solo_enc->lock held */ +static void solo_update_mode(struct solo_enc_dev *solo_enc) +{ + struct solo_dev *solo_dev = solo_enc->solo_dev; + + assert_spin_locked(&solo_enc->lock); + + solo_enc->interlaced = (solo_enc->mode & 0x08) ? 1 : 0; + solo_enc->bw_weight = max(solo_dev->fps / solo_enc->interval, 1); + + switch (solo_enc->mode) { + case SOLO_ENC_MODE_CIF: + solo_enc->width = solo_dev->video_hsize >> 1; + solo_enc->height = solo_dev->video_vsize; + break; + case SOLO_ENC_MODE_D1: + solo_enc->width = solo_dev->video_hsize; + solo_enc->height = solo_dev->video_vsize << 1; + solo_enc->bw_weight <<= 2; + break; + default: + WARN(1, "mode is unknown\n"); + } +} + +/* Should be called with solo_enc->lock held */ +static int solo_enc_on(struct solo_enc_fh *fh) +{ + struct solo_enc_dev *solo_enc = fh->enc; + u8 ch = solo_enc->ch; + struct solo_dev *solo_dev = solo_enc->solo_dev; + u8 interval; + + assert_spin_locked(&solo_enc->lock); + + if (fh->enc_on) + return 0; + + solo_update_mode(solo_enc); + + /* Make sure to bw check on first reader */ + if (!atomic_read(&solo_enc->readers)) { + if (solo_enc->bw_weight > solo_dev->enc_bw_remain) + return -EBUSY; + else + solo_dev->enc_bw_remain -= solo_enc->bw_weight; + } + + fh->enc_on = 1; + fh->rd_idx = solo_enc->solo_dev->enc_wr_idx; + + if (fh->type == SOLO_ENC_TYPE_EXT) + solo_reg_write(solo_dev, SOLO_CAP_CH_COMP_ENA_E(ch), 1); + + if (atomic_inc_return(&solo_enc->readers) > 1) + return 0; + + /* 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); + + /* Settle down Beavis... */ + mdelay(10); + + return 0; +} + +static void solo_enc_off(struct solo_enc_fh *fh) +{ + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + + if (!fh->enc_on) + return; + + if (fh->kthread) { + kthread_stop(fh->kthread); + fh->kthread = NULL; + } + + solo_dev->enc_bw_remain += solo_enc->bw_weight; + fh->enc_on = 0; + + if (atomic_dec_return(&solo_enc->readers) > 0) + return; + + 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 solo_start_fh_thread(struct solo_enc_fh *fh) +{ + struct solo_enc_dev *solo_enc = fh->enc; + + fh->kthread = kthread_run(solo_enc_thread, fh, SOLO6X10_NAME "_enc"); + + /* Oops, we had a problem */ + if (IS_ERR(fh->kthread)) { + spin_lock(&solo_enc->lock); + solo_enc_off(fh); + spin_unlock(&solo_enc->lock); + + return PTR_ERR(fh->kthread); + } + + return 0; +} + +static void enc_reset_gop(struct solo_dev *solo_dev, u8 ch) +{ + BUG_ON(ch >= solo_dev->nr_chans); + solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), 1); + solo_dev->v4l2_enc[ch]->reset_gop = 1; +} + +static int enc_gop_reset(struct solo_dev *solo_dev, u8 ch, u8 vop) +{ + BUG_ON(ch >= solo_dev->nr_chans); + if (!solo_dev->v4l2_enc[ch]->reset_gop) + return 0; + if (vop) + return 1; + solo_dev->v4l2_enc[ch]->reset_gop = 0; + solo_reg_write(solo_dev, SOLO_VE_CH_GOP(ch), + solo_dev->v4l2_enc[ch]->gop); + return 0; +} + +static void enc_write_sg(struct scatterlist *sglist, void *buf, int size) +{ + struct scatterlist *sg; + u8 *src = buf; + + for (sg = sglist; sg && size > 0; sg = sg_next(sg)) { + u8 *p = sg_virt(sg); + size_t len = sg_dma_len(sg); + int i; + + for (i = 0; i < len && size; i++) + p[i] = *(src++); + } +} + +static int enc_get_mpeg_dma_sg(struct solo_dev *solo_dev, + struct p2m_desc *desc, + struct scatterlist *sglist, int skip, + unsigned int off, unsigned int size) +{ + int ret; + + if (off > SOLO_MP4E_EXT_SIZE(solo_dev)) + return -EINVAL; + + if (off + size <= SOLO_MP4E_EXT_SIZE(solo_dev)) { + return solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_MP4E, + desc, 0, sglist, skip, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, size); + } + + /* Buffer wrap */ + ret = solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_MP4E, desc, 0, + sglist, skip, SOLO_MP4E_EXT_ADDR(solo_dev) + off, + SOLO_MP4E_EXT_SIZE(solo_dev) - off); + + ret |= solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_MP4E, desc, 0, + sglist, skip + SOLO_MP4E_EXT_SIZE(solo_dev) - off, + SOLO_MP4E_EXT_ADDR(solo_dev), + size + off - SOLO_MP4E_EXT_SIZE(solo_dev)); + + return ret; +} + +static int enc_get_mpeg_dma_t(struct solo_dev *solo_dev, + dma_addr_t buf, unsigned int off, + unsigned int size) +{ + int ret; + + if (off > SOLO_MP4E_EXT_SIZE(solo_dev)) + return -EINVAL; + + if (off + size <= SOLO_MP4E_EXT_SIZE(solo_dev)) { + return solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_MP4E, 0, buf, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, size); + } + + /* Buffer wrap */ + ret = solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_MP4E, 0, buf, + SOLO_MP4E_EXT_ADDR(solo_dev) + off, + SOLO_MP4E_EXT_SIZE(solo_dev) - off); + + ret |= solo_p2m_dma_t(solo_dev, SOLO_P2M_DMA_ID_MP4E, 0, + buf + SOLO_MP4E_EXT_SIZE(solo_dev) - off, + SOLO_MP4E_EXT_ADDR(solo_dev), + size + off - SOLO_MP4E_EXT_SIZE(solo_dev)); + + return ret; +} + +static int enc_get_mpeg_dma(struct solo_dev *solo_dev, void *buf, + unsigned int off, unsigned int size) +{ + int ret; + + dma_addr_t dma_addr = pci_map_single(solo_dev->pdev, buf, size, + PCI_DMA_FROMDEVICE); + ret = enc_get_mpeg_dma_t(solo_dev, dma_addr, off, size); + pci_unmap_single(solo_dev->pdev, dma_addr, size, PCI_DMA_FROMDEVICE); + + return ret; +} + +static int enc_get_jpeg_dma_sg(struct solo_dev *solo_dev, + struct p2m_desc *desc, + struct scatterlist *sglist, int skip, + unsigned int off, unsigned int size) +{ + int ret; + + if (off > SOLO_JPEG_EXT_SIZE(solo_dev)) + return -EINVAL; + + if (off + size <= SOLO_JPEG_EXT_SIZE(solo_dev)) { + return solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_JPEG, + desc, 0, sglist, skip, + SOLO_JPEG_EXT_ADDR(solo_dev) + off, size); + } + + /* Buffer wrap */ + ret = solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_JPEG, desc, 0, + sglist, skip, SOLO_JPEG_EXT_ADDR(solo_dev) + off, + SOLO_JPEG_EXT_SIZE(solo_dev) - off); + + ret |= solo_p2m_dma_sg(solo_dev, SOLO_P2M_DMA_ID_JPEG, desc, 0, + sglist, skip + SOLO_JPEG_EXT_SIZE(solo_dev) - off, + SOLO_JPEG_EXT_ADDR(solo_dev), + size + off - SOLO_JPEG_EXT_SIZE(solo_dev)); + + return ret; +} + +/* Returns true of __chk is within the first __range bytes of __off */ +#define OFF_IN_RANGE(__off, __range, __chk) \ + ((__off <= __chk) && ((__off + __range) >= __chk)) + +static void solo_jpeg_header(struct solo_enc_dev *solo_enc, + struct videobuf_dmabuf *vbuf) +{ + struct scatterlist *sg; + void *src = jpeg_header; + size_t copied = 0; + size_t to_copy = sizeof(jpeg_header); + + for (sg = vbuf->sglist; sg && copied < to_copy; sg = sg_next(sg)) { + size_t this_copy = min(sg_dma_len(sg), + (unsigned int)(to_copy - copied)); + u8 *p = sg_virt(sg); + + memcpy(p, src + copied, this_copy); + + if (OFF_IN_RANGE(copied, this_copy, SOF0_START + 5)) + p[(SOF0_START + 5) - copied] = + 0xff & (solo_enc->height >> 8); + if (OFF_IN_RANGE(copied, this_copy, SOF0_START + 6)) + p[(SOF0_START + 6) - copied] = 0xff & solo_enc->height; + if (OFF_IN_RANGE(copied, this_copy, SOF0_START + 7)) + p[(SOF0_START + 7) - copied] = + 0xff & (solo_enc->width >> 8); + if (OFF_IN_RANGE(copied, this_copy, SOF0_START + 8)) + p[(SOF0_START + 8) - copied] = 0xff & solo_enc->width; + + copied += this_copy; + } +} + +static int solo_fill_jpeg(struct solo_enc_fh *fh, struct solo_enc_buf *enc_buf, + struct videobuf_buffer *vb, + struct videobuf_dmabuf *vbuf) +{ + struct solo_dev *solo_dev = fh->enc->solo_dev; + int size = enc_buf->jpeg_size; + + /* Copy the header first (direct write) */ + solo_jpeg_header(fh->enc, vbuf); + + vb->size = size + sizeof(jpeg_header); + + /* Grab the jpeg frame */ + return enc_get_jpeg_dma_sg(solo_dev, fh->desc, vbuf->sglist, + sizeof(jpeg_header), + enc_buf->jpeg_off, size); +} + +static inline int vop_interlaced(__le32 *vh) +{ + return (__le32_to_cpu(vh[0]) >> 30) & 1; +} + +static inline u32 vop_size(__le32 *vh) +{ + return __le32_to_cpu(vh[0]) & 0xFFFFF; +} + +static inline u8 vop_hsize(__le32 *vh) +{ + return (__le32_to_cpu(vh[1]) >> 8) & 0xFF; +} + +static inline u8 vop_vsize(__le32 *vh) +{ + return __le32_to_cpu(vh[1]) & 0xFF; +} + +/* must be called with *bits % 8 = 0 */ +static void write_bytes(u8 **out, unsigned *bits, const u8 *src, unsigned count) +{ + memcpy(*out, src, count); + *out += count; + *bits += count * 8; +} + +static void write_bits(u8 **out, unsigned *bits, u32 value, unsigned count) +{ + + value <<= 32 - count; // shift to the right + + while (count--) { + **out <<= 1; + **out |= !!(value & (1 << 31)); /* MSB */ + value <<= 1; + if (++(*bits) % 8 == 0) + (*out)++; + } +} + +static void write_ue(u8 **out, unsigned *bits, unsigned value) /* H.264 only */ +{ + uint32_t max = 0, cnt = 0; + + while (value > max) { + max = (max + 2) * 2 - 2; + cnt++; + } + write_bits(out, bits, 1, cnt + 1); + write_bits(out, bits, ~(max - value), cnt); +} + +static void write_se(u8 **out, unsigned *bits, int value) /* H.264 only */ +{ + if (value <= 0) + write_ue(out, bits, -value * 2); + else + write_ue(out, bits, value * 2 - 1); +} + +static void write_mpeg4_end(u8 **out, unsigned *bits) +{ + write_bits(out, bits, 0, 1); + /* align on 32-bit boundary */ + if (*bits % 32) + write_bits(out, bits, 0xFFFFFFFF, 32 - *bits % 32); +} + +static void write_h264_end(u8 **out, unsigned *bits, int align) +{ + write_bits(out, bits, 1, 1); + while ((*bits) % 8) + write_bits(out, bits, 0, 1); + if (align) + while ((*bits) % 32) + write_bits(out, bits, 0, 1); +} + +static void mpeg4_write_vol(u8 **out, struct solo_dev *solo_dev, + __le32 *vh, unsigned fps, unsigned interval) +{ + static const u8 hdr[] = { + 0, 0, 1, 0x00 /* video_object_start_code */, + 0, 0, 1, 0x20 /* video_object_layer_start_code */ + }; + unsigned bits = 0; + unsigned width = vop_hsize(vh) << 4; + unsigned height = vop_vsize(vh) << 4; + unsigned interlaced = vop_interlaced(vh); + + write_bytes(out, &bits, hdr, sizeof(hdr)); + write_bits(out, &bits, 0, 1); /* random_accessible_vol */ + write_bits(out, &bits, 0x04, 8); /* video_object_type_indication: main */ + write_bits(out, &bits, 1, 1); /* is_object_layer_identifier */ + write_bits(out, &bits, 2, 4); /* video_object_layer_verid: table V2-39 */ + write_bits(out, &bits, 0, 3); /* video_object_layer_priority */ + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + write_bits(out, &bits, 3, 4); /* aspect_ratio_info, assuming 4:3 */ + else + write_bits(out, &bits, 2, 4); + write_bits(out, &bits, 1, 1); /* vol_control_parameters */ + write_bits(out, &bits, 1, 2); /* chroma_format: 4:2:0 */ + write_bits(out, &bits, 1, 1); /* low_delay */ + write_bits(out, &bits, 0, 1); /* vbv_parameters */ + write_bits(out, &bits, 0, 2); /* video_object_layer_shape: rectangular */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, fps, 16); /* vop_time_increment_resolution */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, 1, 1); /* fixed_vop_rate */ + write_bits(out, &bits, interval, 15); /* fixed_vop_time_increment */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, width, 13); /* video_object_layer_width */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, height, 13); /* video_object_layer_height */ + write_bits(out, &bits, 1, 1); /* marker_bit */ + write_bits(out, &bits, interlaced, 1); /* interlaced */ + write_bits(out, &bits, 1, 1); /* obmc_disable */ + write_bits(out, &bits, 0, 2); /* sprite_enable */ + write_bits(out, &bits, 0, 1); /* not_8_bit */ + write_bits(out, &bits, 1, 0); /* quant_type */ + write_bits(out, &bits, 0, 1); /* load_intra_quant_mat */ + write_bits(out, &bits, 0, 1); /* load_nonintra_quant_mat */ + write_bits(out, &bits, 0, 1); /* quarter_sample */ + write_bits(out, &bits, 1, 1); /* complexity_estimation_disable */ + write_bits(out, &bits, 1, 1); /* resync_marker_disable */ + write_bits(out, &bits, 0, 1); /* data_partitioned */ + write_bits(out, &bits, 0, 1); /* newpred_enable */ + write_bits(out, &bits, 0, 1); /* reduced_resolution_vop_enable */ + write_bits(out, &bits, 0, 1); /* scalability */ + write_mpeg4_end(out, &bits); +} + +static void h264_write_vol(u8 **out, struct solo_dev *solo_dev, __le32 *vh) +{ + static const u8 sps[] = { + 0, 0, 0, 1 /* start code */, 0x67, 66 /* profile_idc */, + 0 /* constraints */, 30 /* level_idc */ + }; + static const u8 pps[] = { + 0, 0, 0, 1 /* start code */, 0x68 + }; + + unsigned bits = 0; + unsigned mbs_w = vop_hsize(vh); + unsigned mbs_h = vop_vsize(vh); + + write_bytes(out, &bits, sps, sizeof(sps)); + write_ue(out, &bits, 0); /* seq_parameter_set_id */ + write_ue(out, &bits, 5); /* log2_max_frame_num_minus4 */ + write_ue(out, &bits, 0); /* pic_order_cnt_type */ + write_ue(out, &bits, 6); /* log2_max_pic_order_cnt_lsb_minus4 */ + write_ue(out, &bits, 1); /* max_num_ref_frames */ + write_bits(out, &bits, 0, 1); /* gaps_in_frame_num_value_allowed_flag */ + write_ue(out, &bits, mbs_w - 1); /* pic_width_in_mbs_minus1 */ + write_ue(out, &bits, mbs_h - 1); /* pic_height_in_map_units_minus1 */ + write_bits(out, &bits, 1, 1); /* frame_mbs_only_flag */ + write_bits(out, &bits, 1, 1); /* direct_8x8_frame_field_flag */ + write_bits(out, &bits, 0, 1); /* frame_cropping_flag */ + write_bits(out, &bits, 0, 1); /* vui_parameters_present_flag */ + write_h264_end(out, &bits, 0); + + write_bytes(out, &bits, pps, sizeof(pps)); + write_ue(out, &bits, 0); /* pic_parameter_set_id */ + write_ue(out, &bits, 0); /* seq_parameter_set_id */ + write_bits(out, &bits, 0, 1); /* entropy_coding_mode_flag */ + write_bits(out, &bits, 0, 1); /* bottom_field_pic_order_in_frame_present_flag */ + write_ue(out, &bits, 0); /* num_slice_groups_minus1 */ + write_ue(out, &bits, 0); /* num_ref_idx_l0_default_active_minus1 */ + write_ue(out, &bits, 0); /* num_ref_idx_l1_default_active_minus1 */ + write_bits(out, &bits, 0, 1); /* weighted_pred_flag */ + write_bits(out, &bits, 0, 2); /* weighted_bipred_idc */ + write_se(out, &bits, 0); /* pic_init_qp_minus26 */ + write_se(out, &bits, 0); /* pic_init_qs_minus26 */ + write_se(out, &bits, 2); /* chroma_qp_index_offset */ + write_bits(out, &bits, 0, 1); /* deblocking_filter_control_present_flag */ + write_bits(out, &bits, 1, 1); /* constrained_intra_pred_flag */ + write_bits(out, &bits, 0, 1); /* redundant_pic_cnt_present_flag */ + write_h264_end(out, &bits, 1); +} + +static int solo_fill_mpeg(struct solo_enc_fh *fh, struct solo_enc_buf *enc_buf, + struct videobuf_buffer *vb, + struct videobuf_dmabuf *vbuf) +{ + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + +#define VH_WORDS 16 +#define MAX_VOL_HEADER_LENGTH 64 + + __le32 vh[VH_WORDS]; + int ret; + int frame_size, frame_off; + int skip = 0; + + if (WARN_ON_ONCE(enc_buf->size <= sizeof(vh))) + return -EINVAL; + + /* First get the hardware vop header (not real mpeg) */ + ret = enc_get_mpeg_dma(solo_dev, vh, enc_buf->off, sizeof(vh)); + if (WARN_ON_ONCE(ret)) + return ret; + + if (WARN_ON_ONCE(vop_size(vh) > enc_buf->size)) + return -EINVAL; + + vb->width = vop_hsize(vh) << 4; + vb->height = vop_vsize(vh) << 4; + vb->size = vop_size(vh); + + /* If this is a key frame, add extra m4v header */ + if (!enc_buf->vop) { + u8 header[MAX_VOL_HEADER_LENGTH], *out = header; + + if (solo_dev->flags & FLAGS_6110) + h264_write_vol(&out, solo_dev, vh); + else + mpeg4_write_vol(&out, solo_dev, vh, + solo_dev->fps * 1000, + solo_enc->interval * 1000); + skip = out - header; + enc_write_sg(vbuf->sglist, header, skip); + /* Adjust the dma buffer past this header */ + vb->size += skip; + } + + /* Now get the actual mpeg payload */ + frame_off = (enc_buf->off + sizeof(vh)) % SOLO_MP4E_EXT_SIZE(solo_dev); + frame_size = enc_buf->size - sizeof(vh); + + ret = enc_get_mpeg_dma_sg(solo_dev, fh->desc, vbuf->sglist, + skip, frame_off, frame_size); + WARN_ON_ONCE(ret); + + return ret; +} + +static void solo_enc_fillbuf(struct solo_enc_fh *fh, + struct videobuf_buffer *vb) +{ + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct solo_enc_buf *enc_buf = NULL; + struct videobuf_dmabuf *vbuf; + int ret; + int error = 1; + u16 idx = fh->rd_idx; + + while (idx != solo_dev->enc_wr_idx) { + struct solo_enc_buf *ebuf = &solo_dev->enc_buf[idx]; + + idx = (idx + 1) % SOLO_NR_RING_BUFS; + + if (ebuf->ch != solo_enc->ch) + continue; + + if (fh->fmt == V4L2_PIX_FMT_MPEG) { + if (fh->type == ebuf->type) { + enc_buf = ebuf; + break; + } + } else { + /* For mjpeg, keep reading to the newest frame */ + enc_buf = ebuf; + } + } + + fh->rd_idx = idx; + + if (WARN_ON_ONCE(!enc_buf)) + goto buf_err; + + if ((fh->fmt == V4L2_PIX_FMT_MPEG && + vb->bsize < enc_buf->size) || + (fh->fmt == V4L2_PIX_FMT_MJPEG && + vb->bsize < (enc_buf->jpeg_size + sizeof(jpeg_header)))) { + WARN_ON_ONCE(1); + goto buf_err; + } + + vbuf = videobuf_to_dma(vb); + if (WARN_ON_ONCE(!vbuf)) + goto buf_err; + + if (fh->fmt == V4L2_PIX_FMT_MPEG) + ret = solo_fill_mpeg(fh, enc_buf, vb, vbuf); + else + ret = solo_fill_jpeg(fh, enc_buf, vb, vbuf); + + if (!ret) + error = 0; + +buf_err: + if (error) { + vb->state = VIDEOBUF_ERROR; + } else { + vb->field_count++; + vb->ts = enc_buf->ts; + vb->state = VIDEOBUF_DONE; + } + + wake_up(&vb->done); + + return; +} + +static void solo_enc_thread_try(struct solo_enc_fh *fh) +{ + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct videobuf_buffer *vb; + + for (;;) { + spin_lock(&solo_enc->lock); + + if (fh->rd_idx == solo_dev->enc_wr_idx) + break; + + if (list_empty(&fh->vidq_active)) + break; + + vb = list_first_entry(&fh->vidq_active, + struct videobuf_buffer, queue); + + if (!waitqueue_active(&vb->done)) + break; + + list_del(&vb->queue); + + spin_unlock(&solo_enc->lock); + + solo_enc_fillbuf(fh, vb); + } + + assert_spin_locked(&solo_enc->lock); + spin_unlock(&solo_enc->lock); +} + +static int solo_enc_thread(void *data) +{ + struct solo_enc_fh *fh = data; + struct solo_enc_dev *solo_enc = fh->enc; + DECLARE_WAITQUEUE(wait, current); + + set_freezable(); + add_wait_queue(&solo_enc->thread_wait, &wait); + + for (;;) { + long timeout = schedule_timeout_interruptible(HZ); + if (timeout == -ERESTARTSYS || kthread_should_stop()) + break; + solo_enc_thread_try(fh); + try_to_freeze(); + } + + remove_wait_queue(&solo_enc->thread_wait, &wait); + + return 0; +} + +void solo_motion_isr(struct solo_dev *solo_dev) +{ + u32 status; + int i; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_MOTION); + + status = solo_reg_read(solo_dev, SOLO_VI_MOT_STATUS); + + for (i = 0; i < solo_dev->nr_chans; i++) { + struct solo_enc_dev *solo_enc = solo_dev->v4l2_enc[i]; + + BUG_ON(solo_enc == NULL); + + if (solo_enc->motion_detected) + continue; + if (!(status & (1 << i))) + continue; + + solo_enc->motion_detected = 1; + } +} + +void solo_enc_v4l2_isr(struct solo_dev *solo_dev) +{ + struct solo_enc_buf *enc_buf; + u32 mpeg_current, mpeg_next, mpeg_size; + u32 jpeg_current, jpeg_next, jpeg_size; + u32 reg_mpeg_size; + u8 cur_q, vop_type; + u8 ch; + enum solo_enc_types enc_type; + + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_ENCODER); + + cur_q = ((solo_reg_read(solo_dev, SOLO_VE_STATE(11)) & 0xF) + 1) % MP4_QS; + + reg_mpeg_size = ((solo_reg_read(solo_dev, SOLO_VE_STATE(0)) & 0xFFFFF) + 64 + 8) & ~7; + + while (solo_dev->enc_idx != cur_q) { + mpeg_current = solo_reg_read(solo_dev, + SOLO_VE_MPEG4_QUE(solo_dev->enc_idx)); + jpeg_current = solo_reg_read(solo_dev, + SOLO_VE_JPEG_QUE(solo_dev->enc_idx)); + solo_dev->enc_idx = (solo_dev->enc_idx + 1) % MP4_QS; + mpeg_next = solo_reg_read(solo_dev, + SOLO_VE_MPEG4_QUE(solo_dev->enc_idx)); + jpeg_next = solo_reg_read(solo_dev, + SOLO_VE_JPEG_QUE(solo_dev->enc_idx)); + + ch = (mpeg_current >> 24) & 0x1f; + if (ch >= SOLO_MAX_CHANNELS) { + ch -= SOLO_MAX_CHANNELS; + enc_type = SOLO_ENC_TYPE_EXT; + } else + enc_type = SOLO_ENC_TYPE_STD; + + vop_type = (mpeg_current >> 29) & 3; + + mpeg_current &= 0x00ffffff; + mpeg_next &= 0x00ffffff; + jpeg_current &= 0x00ffffff; + jpeg_next &= 0x00ffffff; + + mpeg_size = (SOLO_MP4E_EXT_SIZE(solo_dev) + + mpeg_next - mpeg_current) % + SOLO_MP4E_EXT_SIZE(solo_dev); + + jpeg_size = (SOLO_JPEG_EXT_SIZE(solo_dev) + + jpeg_next - jpeg_current) % + SOLO_JPEG_EXT_SIZE(solo_dev); + + /* XXX I think this means we had a ring overflow? */ + if (mpeg_current > mpeg_next && mpeg_size != reg_mpeg_size) { + enc_reset_gop(solo_dev, ch); + continue; + } + + /* When resetting the GOP, skip frames until I-frame */ + if (enc_gop_reset(solo_dev, ch, vop_type)) + continue; + + enc_buf = &solo_dev->enc_buf[solo_dev->enc_wr_idx]; + + enc_buf->vop = vop_type; + enc_buf->ch = ch; + enc_buf->off = mpeg_current; + enc_buf->size = mpeg_size; + enc_buf->jpeg_off = jpeg_current; + enc_buf->jpeg_size = jpeg_size; + enc_buf->type = enc_type; + + do_gettimeofday(&enc_buf->ts); + + solo_dev->enc_wr_idx = (solo_dev->enc_wr_idx + 1) % + SOLO_NR_RING_BUFS; + + wake_up_interruptible(&solo_dev->v4l2_enc[ch]->thread_wait); + } + + return; +} + +static int solo_enc_buf_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + *size = FRAME_BUF_SIZE; + + if (*count < MIN_VID_BUFFERS) + *count = MIN_VID_BUFFERS; + + return 0; +} + +static int solo_enc_buf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct solo_enc_fh *fh = vq->priv_data; + struct solo_enc_dev *solo_enc = fh->enc; + + vb->size = FRAME_BUF_SIZE; + if (vb->baddr != 0 && vb->bsize < vb->size) + return -EINVAL; + + /* These properties only change when queue is idle */ + vb->width = solo_enc->width; + vb->height = solo_enc->height; + vb->field = field; + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + int rc = videobuf_iolock(vq, vb, NULL); + if (rc < 0) { + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + videobuf_dma_unmap(vq->dev, dma); + videobuf_dma_free(dma); + vb->state = VIDEOBUF_NEEDS_INIT; + return rc; + } + } + vb->state = VIDEOBUF_PREPARED; + + return 0; +} + +static void solo_enc_buf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct solo_enc_fh *fh = vq->priv_data; + + vb->state = VIDEOBUF_QUEUED; + list_add_tail(&vb->queue, &fh->vidq_active); + wake_up_interruptible(&fh->enc->thread_wait); +} + +static void solo_enc_buf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + videobuf_dma_unmap(vq->dev, dma); + videobuf_dma_free(dma); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static struct videobuf_queue_ops solo_enc_video_qops = { + .buf_setup = solo_enc_buf_setup, + .buf_prepare = solo_enc_buf_prepare, + .buf_queue = solo_enc_buf_queue, + .buf_release = solo_enc_buf_release, +}; + +static unsigned int solo_enc_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct solo_enc_fh *fh = file->private_data; + + return videobuf_poll_stream(file, &fh->vidq, wait); +} + +static int solo_enc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct solo_enc_fh *fh = file->private_data; + + return videobuf_mmap_mapper(&fh->vidq, vma); +} + +static int solo_enc_open(struct file *file) +{ + struct solo_enc_dev *solo_enc = video_drvdata(file); + struct solo_enc_fh *fh; + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (fh == NULL) + return -ENOMEM; + + fh->enc = solo_enc; + file->private_data = fh; + INIT_LIST_HEAD(&fh->vidq_active); + fh->fmt = V4L2_PIX_FMT_MPEG; + fh->type = SOLO_ENC_TYPE_STD; + + videobuf_queue_sg_init(&fh->vidq, &solo_enc_video_qops, + &solo_enc->solo_dev->pdev->dev, + &solo_enc->lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED, + sizeof(struct videobuf_buffer), fh, NULL); + + return 0; +} + +static ssize_t solo_enc_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct solo_enc_fh *fh = file->private_data; + struct solo_enc_dev *solo_enc = fh->enc; + + /* Make sure the encoder is on */ + if (!fh->enc_on) { + int ret; + + spin_lock(&solo_enc->lock); + ret = solo_enc_on(fh); + spin_unlock(&solo_enc->lock); + if (ret) + return ret; + + ret = solo_start_fh_thread(fh); + if (ret) + return ret; + } + + return videobuf_read_stream(&fh->vidq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static int solo_enc_release(struct file *file) +{ + struct solo_enc_fh *fh = file->private_data; + struct solo_enc_dev *solo_enc = fh->enc; + + videobuf_stop(&fh->vidq); + videobuf_mmap_free(&fh->vidq); + + spin_lock(&solo_enc->lock); + solo_enc_off(fh); + spin_unlock(&solo_enc->lock); + + kfree(fh); + + return 0; +} + +static int solo_enc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + + strcpy(cap->driver, SOLO6X10_NAME); + snprintf(cap->card, sizeof(cap->card), "Softlogic 6x10 Enc %d", + solo_enc->ch); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI %s", + pci_name(solo_dev->pdev)); + cap->version = SOLO6X10_VER_NUM; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + return 0; +} + +static int solo_enc_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + 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; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + input->std = V4L2_STD_NTSC_M; + else + input->std = V4L2_STD_PAL_B; + + 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) +{ + switch (f->index) { + case 0: + f->pixelformat = V4L2_PIX_FMT_MPEG; + strcpy(f->description, "MPEG-4 AVC"); + break; + case 1: + f->pixelformat = V4L2_PIX_FMT_MJPEG; + strcpy(f->description, "MJPEG"); + break; + default: + return -EINVAL; + } + + f->flags = V4L2_FMT_FLAG_COMPRESSED; + + return 0; +} + +static int solo_enc_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + + if (pix->pixelformat != V4L2_PIX_FMT_MPEG && + pix->pixelformat != V4L2_PIX_FMT_MJPEG) + return -EINVAL; + + /* We cannot change width/height in mid read */ + if (atomic_read(&solo_enc->readers) > 0) { + if (pix->width != solo_enc->width || + pix->height != solo_enc->height) + return -EBUSY; + } + + 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; + } + + if (pix->field == V4L2_FIELD_ANY) + pix->field = V4L2_FIELD_INTERLACED; + else if (pix->field != V4L2_FIELD_INTERLACED) + pix->field = V4L2_FIELD_INTERLACED; + + /* Just set these */ + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->sizeimage = FRAME_BUF_SIZE; + + return 0; +} + +static int solo_enc_set_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + int ret; + + spin_lock(&solo_enc->lock); + + ret = solo_enc_try_fmt_cap(file, priv, f); + if (ret) { + spin_unlock(&solo_enc->lock); + 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 */ + fh->fmt = pix->pixelformat; + + if (pix->priv) + fh->type = SOLO_ENC_TYPE_EXT; + ret = solo_enc_on(fh); + + spin_unlock(&solo_enc->lock); + + if (ret) + return ret; + + return solo_start_fh_thread(fh); +} + +static int solo_enc_get_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = solo_enc->width; + pix->height = solo_enc->height; + pix->pixelformat = fh->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_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req) +{ + struct solo_enc_fh *fh = priv; + + return videobuf_reqbufs(&fh->vidq, req); +} + +static int solo_enc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct solo_enc_fh *fh = priv; + + return videobuf_querybuf(&fh->vidq, buf); +} + +static int solo_enc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct solo_enc_fh *fh = priv; + + return videobuf_qbuf(&fh->vidq, buf); +} + +static int solo_enc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + int ret; + + /* Make sure the encoder is on */ + if (!fh->enc_on) { + spin_lock(&solo_enc->lock); + ret = solo_enc_on(fh); + spin_unlock(&solo_enc->lock); + if (ret) + return ret; + + ret = solo_start_fh_thread(fh); + if (ret) + return ret; + } + + ret = videobuf_dqbuf(&fh->vidq, buf, file->f_flags & O_NONBLOCK); + if (ret) + return ret; + + /* Signal motion detection */ + if (solo_is_motion_on(solo_enc)) { + buf->flags |= V4L2_BUF_FLAG_MOTION_ON; + if (solo_enc->motion_detected) { + buf->flags |= V4L2_BUF_FLAG_MOTION_DETECTED; + solo_reg_write(solo_enc->solo_dev, SOLO_VI_MOT_CLEAR, + 1 << solo_enc->ch); + solo_enc->motion_detected = 0; + } + } + + /* Check for key frame on mpeg data */ + if (fh->fmt == V4L2_PIX_FMT_MPEG) { + struct videobuf_dmabuf *vbuf = + videobuf_to_dma(fh->vidq.bufs[buf->index]); + + if (vbuf) { + u8 *p = sg_virt(vbuf->sglist); + if (p[3] == 0x00) + buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + else + buf->flags |= V4L2_BUF_FLAG_PFRAME; + } + } + + return 0; +} + +static int solo_enc_streamon(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct solo_enc_fh *fh = priv; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return videobuf_streamon(&fh->vidq); +} + +static int solo_enc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct solo_enc_fh *fh = priv; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return videobuf_streamoff(&fh->vidq); +} + +static int solo_enc_s_std(struct file *file, void *priv, v4l2_std_id *i) +{ + return 0; +} + +static int solo_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct solo_enc_fh *fh = priv; + struct solo_dev *solo_dev = fh->enc->solo_dev; + + if (fsize->pixel_format != V4L2_PIX_FMT_MPEG) + 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_fh *fh = priv; + struct solo_dev *solo_dev = fh->enc->solo_dev; + + if (fintv->pixel_format != V4L2_PIX_FMT_MPEG || fintv->index) + return -EINVAL; + + fintv->type = V4L2_FRMIVAL_TYPE_STEPWISE; + + fintv->stepwise.min.numerator = solo_dev->fps; + fintv->stepwise.min.denominator = 1; + + fintv->stepwise.max.numerator = solo_dev->fps; + fintv->stepwise.max.denominator = 15; + + fintv->stepwise.step.numerator = 1; + fintv->stepwise.step.denominator = 1; + + return 0; +} + +static int solo_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_captureparm *cp = &sp->parm.capture; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + cp->timeperframe.numerator = solo_enc->interval; + cp->timeperframe.denominator = solo_dev->fps; + cp->capturemode = 0; + /* XXX: Shouldn't we be able to get/set this from videobuf? */ + cp->readbuffers = 2; + + return 0; +} + +static int solo_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + struct v4l2_captureparm *cp = &sp->parm.capture; + + spin_lock(&solo_enc->lock); + + if (atomic_read(&solo_enc->readers) > 0) { + spin_unlock(&solo_enc->lock); + return -EBUSY; + } + + if ((cp->timeperframe.numerator == 0) || + (cp->timeperframe.denominator == 0)) { + /* reset framerate */ + cp->timeperframe.numerator = 1; + cp->timeperframe.denominator = solo_dev->fps; + } + + if (cp->timeperframe.denominator != solo_dev->fps) + cp->timeperframe.denominator = solo_dev->fps; + + if (cp->timeperframe.numerator > 15) + cp->timeperframe.numerator = 15; + + solo_enc->interval = cp->timeperframe.numerator; + + cp->capability = V4L2_CAP_TIMEPERFRAME; + + solo_enc->gop = max(solo_dev->fps / solo_enc->interval, 1); + solo_update_mode(solo_enc); + + spin_unlock(&solo_enc->lock); + + return 0; +} + +static int solo_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + + qc->id = v4l2_ctrl_next(solo_ctrl_classes, qc->id); + if (!qc->id) + return -EINVAL; + + switch (qc->id) { + case V4L2_CID_BRIGHTNESS: + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + case V4L2_CID_HUE: + return v4l2_ctrl_query_fill(qc, 0x00, 0xff, 1, 0x80); + case V4L2_CID_SHARPNESS: + return v4l2_ctrl_query_fill(qc, 0x00, 0x0f, 1, 0x00); + case V4L2_CID_MPEG_VIDEO_ENCODING: + return v4l2_ctrl_query_fill( + qc, V4L2_MPEG_VIDEO_ENCODING_MPEG_1, + V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC, 1, + V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC); + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + return v4l2_ctrl_query_fill(qc, 1, 255, 1, solo_dev->fps); +#ifdef PRIVATE_CIDS + case V4L2_CID_MOTION_THRESHOLD: + qc->flags |= V4L2_CTRL_FLAG_SLIDER; + qc->type = V4L2_CTRL_TYPE_INTEGER; + qc->minimum = 0; + qc->maximum = 0xffff; + qc->step = 1; + qc->default_value = SOLO_DEF_MOT_THRESH; + strlcpy(qc->name, "Motion Detection Threshold", + sizeof(qc->name)); + return 0; + case V4L2_CID_MOTION_ENABLE: + qc->type = V4L2_CTRL_TYPE_BOOLEAN; + qc->minimum = 0; + qc->maximum = qc->step = 1; + qc->default_value = 0; + strlcpy(qc->name, "Motion Detection Enable", sizeof(qc->name)); + return 0; +#else + case V4L2_CID_MOTION_THRESHOLD: + return v4l2_ctrl_query_fill(qc, 0, 0xffff, 1, + SOLO_DEF_MOT_THRESH); + case V4L2_CID_MOTION_ENABLE: + return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0); +#endif + case V4L2_CID_RDS_TX_RADIO_TEXT: + qc->type = V4L2_CTRL_TYPE_STRING; + qc->minimum = 0; + qc->maximum = OSD_TEXT_MAX; + qc->step = 1; + qc->default_value = 0; + strlcpy(qc->name, "OSD Text", sizeof(qc->name)); + return 0; + } + + return -EINVAL; +} + +static int solo_querymenu(struct file *file, void *priv, + struct v4l2_querymenu *qmenu) +{ + struct v4l2_queryctrl qctrl; + int err; + + qctrl.id = qmenu->id; + err = solo_queryctrl(file, priv, &qctrl); + if (err) + return err; + + return v4l2_ctrl_query_menu(qmenu, &qctrl, NULL); +} + +static int solo_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + + 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_get_ctrl_val(solo_dev, ctrl->id, solo_enc->ch, + &ctrl->value); + case V4L2_CID_MPEG_VIDEO_ENCODING: + ctrl->value = V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + ctrl->value = solo_enc->gop; + break; + case V4L2_CID_MOTION_THRESHOLD: + ctrl->value = solo_enc->motion_thresh; + break; + case V4L2_CID_MOTION_ENABLE: + ctrl->value = solo_is_motion_on(solo_enc); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int solo_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + struct solo_dev *solo_dev = solo_enc->solo_dev; + + 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->value); + case V4L2_CID_MPEG_VIDEO_ENCODING: + if (ctrl->value != V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC) + return -ERANGE; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + if (ctrl->value < 1 || ctrl->value > 255) + return -ERANGE; + solo_enc->gop = ctrl->value; + 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); + break; + case V4L2_CID_MOTION_THRESHOLD: + /* TODO accept value on lower 16-bits and use high + * 16-bits to assign the value to a specific block */ + if (ctrl->value < 0 || ctrl->value > 0xffff) + return -ERANGE; + solo_enc->motion_thresh = ctrl->value; + solo_set_motion_threshold(solo_dev, solo_enc->ch, ctrl->value); + break; + case V4L2_CID_MOTION_ENABLE: + solo_motion_toggle(solo_enc, ctrl->value); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int solo_s_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrls) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + int i; + + for (i = 0; i < ctrls->count; i++) { + struct v4l2_ext_control *ctrl = (ctrls->controls + i); + int err; + + switch (ctrl->id) { + case V4L2_CID_RDS_TX_RADIO_TEXT: + if (ctrl->size - 1 > OSD_TEXT_MAX) + err = -ERANGE; + else { + err = copy_from_user(solo_enc->osd_text, + ctrl->string, + OSD_TEXT_MAX); + solo_enc->osd_text[OSD_TEXT_MAX] = '\0'; + if (!err) + err = solo_osd_print(solo_enc); + } + break; + default: + err = -EINVAL; + } + + if (err < 0) { + ctrls->error_idx = i; + return err; + } + } + + return 0; +} + +static int solo_g_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrls) +{ + struct solo_enc_fh *fh = priv; + struct solo_enc_dev *solo_enc = fh->enc; + int i; + + for (i = 0; i < ctrls->count; i++) { + struct v4l2_ext_control *ctrl = (ctrls->controls + i); + int err; + + switch (ctrl->id) { + case V4L2_CID_RDS_TX_RADIO_TEXT: + if (ctrl->size < OSD_TEXT_MAX) { + ctrl->size = OSD_TEXT_MAX; + err = -ENOSPC; + } else { + err = copy_to_user(ctrl->string, + solo_enc->osd_text, + OSD_TEXT_MAX); + } + break; + default: + err = -EINVAL; + } + + if (err < 0) { + ctrls->error_idx = i; + return err; + } + } + + return 0; +} + +static const struct v4l2_file_operations solo_enc_fops = { + .owner = THIS_MODULE, + .open = solo_enc_open, + .release = solo_enc_release, + .read = solo_enc_read, + .poll = solo_enc_poll, + .mmap = solo_enc_mmap, + .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, + /* 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 = solo_enc_reqbufs, + .vidioc_querybuf = solo_enc_querybuf, + .vidioc_qbuf = solo_enc_qbuf, + .vidioc_dqbuf = solo_enc_dqbuf, + .vidioc_streamon = solo_enc_streamon, + .vidioc_streamoff = solo_enc_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, + /* Controls */ + .vidioc_queryctrl = solo_queryctrl, + .vidioc_querymenu = solo_querymenu, + .vidioc_g_ctrl = solo_g_ctrl, + .vidioc_s_ctrl = solo_s_ctrl, + .vidioc_g_ext_ctrls = solo_g_ext_ctrls, + .vidioc_s_ext_ctrls = solo_s_ext_ctrls, +}; + +static 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_B, + .current_norm = V4L2_STD_NTSC_M, +}; + +static struct solo_enc_dev *solo_enc_alloc(struct solo_dev *solo_dev, u8 ch) +{ + struct solo_enc_dev *solo_enc; + int ret; + + solo_enc = kzalloc(sizeof(*solo_enc), GFP_KERNEL); + if (!solo_enc) + return ERR_PTR(-ENOMEM); + + solo_enc->vfd = video_device_alloc(); + if (!solo_enc->vfd) { + kfree(solo_enc); + return ERR_PTR(-ENOMEM); + } + + solo_enc->solo_dev = solo_dev; + solo_enc->ch = ch; + + *solo_enc->vfd = solo_enc_template; + solo_enc->vfd->parent = &solo_dev->pdev->dev; + ret = video_register_device(solo_enc->vfd, VFL_TYPE_GRABBER, + video_nr); + if (ret < 0) { + video_device_release(solo_enc->vfd); + kfree(solo_enc); + return ERR_PTR(ret); + } + + video_set_drvdata(solo_enc->vfd, solo_enc); + + snprintf(solo_enc->vfd->name, sizeof(solo_enc->vfd->name), + "%s-enc (%i/%i)", SOLO6X10_NAME, solo_dev->vfd->num, + solo_enc->vfd->num); + + if (video_nr != -1) + video_nr++; + + spin_lock_init(&solo_enc->lock); + init_waitqueue_head(&solo_enc->thread_wait); + atomic_set(&solo_enc->readers, 0); + + 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_thresh = SOLO_DEF_MOT_THRESH; + + spin_lock(&solo_enc->lock); + solo_update_mode(solo_enc); + spin_unlock(&solo_enc->lock); + + return solo_enc; +} + +static void solo_enc_free(struct solo_enc_dev *solo_enc) +{ + if (solo_enc == NULL) + return; + + video_unregister_device(solo_enc->vfd); + kfree(solo_enc); +} + +int solo_enc_v4l2_init(struct solo_dev *solo_dev) +{ + int i; + + for (i = 0; i < solo_dev->nr_chans; i++) { + solo_dev->v4l2_enc[i] = solo_enc_alloc(solo_dev, i); + 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]); + return ret; + } + + /* D1@MAX-FPS * 4 */ + solo_dev->enc_bw_remain = solo_dev->fps * 4 * 4; + + 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 0; +} + +void solo_enc_v4l2_exit(struct solo_dev *solo_dev) +{ + int i; + + solo_irq_off(solo_dev, SOLO_IRQ_MOTION); + + for (i = 0; i < solo_dev->nr_chans; i++) + solo_enc_free(solo_dev->v4l2_enc[i]); +} diff --git a/drivers/staging/media/solo6x10/v4l2.c b/drivers/staging/media/solo6x10/v4l2.c new file mode 100644 index 00000000..571c3a34 --- /dev/null +++ b/drivers/staging/media/solo6x10/v4l2.c @@ -0,0 +1,964 @@ +/* + * Copyright (C) 2010 Bluecherry, LLC www.bluecherrydvr.com + * Copyright (C) 2010 Ben Collins + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "solo6x10.h" +#include "tw28.h" + +#define SOLO_HW_BPL 2048 +#define SOLO_DISP_PIX_FIELD V4L2_FIELD_INTERLACED + +/* Image size is two fields, SOLO_HW_BPL is one horizontal line */ +#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 4 + +/* Simple file handle */ +struct solo_filehandle { + struct solo_dev *solo_dev; + struct videobuf_queue vidq; + struct task_struct *kthread; + spinlock_t slock; + int old_write; + struct list_head vidq_active; + struct p2m_desc desc[SOLO_NR_P2M_DESC]; + int desc_idx; +}; + +unsigned video_nr = -1; +module_param(video_nr, uint, 0644); +MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect (default)"); + +static 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 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) +{ + solo_reg_write(solo_dev, SOLO_IRQ_STAT, SOLO_IRQ_VIDEO_IN); + wake_up_interruptible(&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 disp_reset_desc(struct solo_filehandle *fh) +{ + /* We use desc mode, which ignores desc 0 */ + memset(fh->desc, 0, sizeof(*fh->desc)); + fh->desc_idx = 1; +} + +static int disp_flush_descs(struct solo_filehandle *fh) +{ + int ret; + + if (!fh->desc_idx) + return 0; + + ret = solo_p2m_dma_desc(fh->solo_dev, SOLO_P2M_DMA_ID_DISP, + fh->desc, fh->desc_idx); + disp_reset_desc(fh); + + return ret; +} + +static int disp_push_desc(struct solo_filehandle *fh, dma_addr_t dma_addr, + u32 ext_addr, int size, int repeat, int ext_size) +{ + if (fh->desc_idx >= SOLO_NR_P2M_DESC) { + int ret = disp_flush_descs(fh); + if (ret) + return ret; + } + + solo_p2m_push_desc(&fh->desc[fh->desc_idx], 0, dma_addr, ext_addr, + size, repeat, ext_size); + fh->desc_idx++; + + return 0; +} + +static void solo_fillbuf(struct solo_filehandle *fh, + struct videobuf_buffer *vb) +{ + struct solo_dev *solo_dev = fh->solo_dev; + struct videobuf_dmabuf *vbuf; + unsigned int fdma_addr; + int error = 1; + int i; + struct scatterlist *sg; + dma_addr_t sg_dma; + int sg_size_left; + + vbuf = videobuf_to_dma(vb); + if (!vbuf) + goto finish_buf; + + if (erase_off(solo_dev)) { + int i; + + /* Just blit to the entire sg list, ignoring size */ + for_each_sg(vbuf->sglist, sg, vbuf->sglen, i) { + void *p = sg_virt(sg); + size_t len = sg_dma_len(sg); + + for (i = 0; i < len; i += 2) { + ((u8 *)p)[i] = 0x80; + ((u8 *)p)[i + 1] = 0x00; + } + } + + error = 0; + goto finish_buf; + } + + disp_reset_desc(fh); + sg = vbuf->sglist; + sg_dma = sg_dma_address(sg); + sg_size_left = sg_dma_len(sg); + + fdma_addr = SOLO_DISP_EXT_ADDR + (fh->old_write * + (SOLO_HW_BPL * solo_vlines(solo_dev))); + + for (i = 0; i < solo_vlines(solo_dev); i++) { + int line_len = solo_bytesperline(solo_dev); + int lines; + + if (!sg_size_left) { + sg = sg_next(sg); + if (sg == NULL) + goto finish_buf; + sg_dma = sg_dma_address(sg); + sg_size_left = sg_dma_len(sg); + } + + /* No room for an entire line, so chunk it up */ + if (sg_size_left < line_len) { + int this_addr = fdma_addr; + + while (line_len > 0) { + int this_write; + + if (!sg_size_left) { + sg = sg_next(sg); + if (sg == NULL) + goto finish_buf; + sg_dma = sg_dma_address(sg); + sg_size_left = sg_dma_len(sg); + } + + this_write = min(sg_size_left, line_len); + + if (disp_push_desc(fh, sg_dma, this_addr, + this_write, 0, 0)) + goto finish_buf; + + line_len -= this_write; + sg_size_left -= this_write; + sg_dma += this_write; + this_addr += this_write; + } + + fdma_addr += SOLO_HW_BPL; + continue; + } + + /* Shove as many lines into a repeating descriptor as possible */ + lines = min(sg_size_left / line_len, + solo_vlines(solo_dev) - i); + + if (disp_push_desc(fh, sg_dma, fdma_addr, line_len, + lines - 1, SOLO_HW_BPL)) + goto finish_buf; + + i += lines - 1; + fdma_addr += SOLO_HW_BPL * lines; + sg_dma += lines * line_len; + sg_size_left -= lines * line_len; + } + + error = disp_flush_descs(fh); + +finish_buf: + if (error) { + vb->state = VIDEOBUF_ERROR; + } else { + vb->size = solo_vlines(solo_dev) * solo_bytesperline(solo_dev); + vb->state = VIDEOBUF_DONE; + vb->field_count++; + do_gettimeofday(&vb->ts); + } + + wake_up(&vb->done); + + return; +} + +static void solo_thread_try(struct solo_filehandle *fh) +{ + struct videobuf_buffer *vb; + unsigned int cur_write; + + for (;;) { + spin_lock(&fh->slock); + + if (list_empty(&fh->vidq_active)) + break; + + vb = list_first_entry(&fh->vidq_active, struct videobuf_buffer, + queue); + + if (!waitqueue_active(&vb->done)) + break; + + cur_write = SOLO_VI_STATUS0_PAGE(solo_reg_read(fh->solo_dev, + SOLO_VI_STATUS0)); + if (cur_write == fh->old_write) + break; + + fh->old_write = cur_write; + list_del(&vb->queue); + + spin_unlock(&fh->slock); + + solo_fillbuf(fh, vb); + } + + assert_spin_locked(&fh->slock); + spin_unlock(&fh->slock); +} + +static int solo_thread(void *data) +{ + struct solo_filehandle *fh = data; + struct solo_dev *solo_dev = fh->solo_dev; + 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(fh); + try_to_freeze(); + } + + remove_wait_queue(&solo_dev->disp_thread_wait, &wait); + + return 0; +} + +static int solo_start_thread(struct solo_filehandle *fh) +{ + fh->kthread = kthread_run(solo_thread, fh, SOLO6X10_NAME "_disp"); + + if (IS_ERR(fh->kthread)) + return PTR_ERR(fh->kthread); + + return 0; +} + +static void solo_stop_thread(struct solo_filehandle *fh) +{ + if (fh->kthread) { + kthread_stop(fh->kthread); + fh->kthread = NULL; + } +} + +static int solo_buf_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct solo_filehandle *fh = vq->priv_data; + struct solo_dev *solo_dev = fh->solo_dev; + + *size = solo_image_size(solo_dev); + + if (*count < MIN_VID_BUFFERS) + *count = MIN_VID_BUFFERS; + + return 0; +} + +static int solo_buf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct solo_filehandle *fh = vq->priv_data; + struct solo_dev *solo_dev = fh->solo_dev; + + vb->size = solo_image_size(solo_dev); + if (vb->baddr != 0 && vb->bsize < vb->size) + return -EINVAL; + + /* XXX: These properties only change when queue is idle */ + vb->width = solo_dev->video_hsize; + vb->height = solo_vlines(solo_dev); + vb->bytesperline = solo_bytesperline(solo_dev); + vb->field = field; + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + int rc = videobuf_iolock(vq, vb, NULL); + if (rc < 0) { + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + videobuf_dma_unmap(vq->dev, dma); + videobuf_dma_free(dma); + vb->state = VIDEOBUF_NEEDS_INIT; + return rc; + } + } + vb->state = VIDEOBUF_PREPARED; + + return 0; +} + +static void solo_buf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct solo_filehandle *fh = vq->priv_data; + struct solo_dev *solo_dev = fh->solo_dev; + + vb->state = VIDEOBUF_QUEUED; + list_add_tail(&vb->queue, &fh->vidq_active); + wake_up_interruptible(&solo_dev->disp_thread_wait); +} + +static void solo_buf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + videobuf_dma_unmap(vq->dev, dma); + videobuf_dma_free(dma); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static struct videobuf_queue_ops solo_video_qops = { + .buf_setup = solo_buf_setup, + .buf_prepare = solo_buf_prepare, + .buf_queue = solo_buf_queue, + .buf_release = solo_buf_release, +}; + +static unsigned int solo_v4l2_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct solo_filehandle *fh = file->private_data; + + return videobuf_poll_stream(file, &fh->vidq, wait); +} + +static int solo_v4l2_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct solo_filehandle *fh = file->private_data; + + return videobuf_mmap_mapper(&fh->vidq, vma); +} + +static int solo_v4l2_open(struct file *file) +{ + struct solo_dev *solo_dev = video_drvdata(file); + struct solo_filehandle *fh; + int ret; + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (fh == NULL) + return -ENOMEM; + + spin_lock_init(&fh->slock); + INIT_LIST_HEAD(&fh->vidq_active); + fh->solo_dev = solo_dev; + file->private_data = fh; + + ret = solo_start_thread(fh); + if (ret) { + kfree(fh); + return ret; + } + + videobuf_queue_sg_init(&fh->vidq, &solo_video_qops, + &solo_dev->pdev->dev, &fh->slock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + SOLO_DISP_PIX_FIELD, + sizeof(struct videobuf_buffer), fh, NULL); + + return 0; +} + +static ssize_t solo_v4l2_read(struct file *file, char __user *data, + size_t count, loff_t *ppos) +{ + struct solo_filehandle *fh = file->private_data; + + return videobuf_read_stream(&fh->vidq, data, count, ppos, 0, + file->f_flags & O_NONBLOCK); +} + +static int solo_v4l2_release(struct file *file) +{ + struct solo_filehandle *fh = file->private_data; + + videobuf_stop(&fh->vidq); + videobuf_mmap_free(&fh->vidq); + solo_stop_thread(fh); + kfree(fh); + + return 0; +} + +static int solo_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + + strcpy(cap->driver, SOLO6X10_NAME); + strcpy(cap->card, "Softlogic 6x10"); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI %s", + pci_name(solo_dev->pdev)); + cap->version = SOLO6X10_VER_NUM; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + return 0; +} + +static int solo_enum_ext_input(struct solo_dev *solo_dev, + struct v4l2_input *input) +{ + static const char *dispnames_1[] = { "4UP" }; + static const char *dispnames_2[] = { "4UP-1", "4UP-2" }; + static const char *dispnames_5[] = { + "4UP-1", "4UP-2", "4UP-3", "4UP-4", "16UP" + }; + const char **dispnames; + + if (input->index >= (solo_dev->nr_chans + solo_dev->nr_ext)) + return -EINVAL; + + if (solo_dev->nr_ext == 5) + dispnames = dispnames_5; + else if (solo_dev->nr_ext == 2) + dispnames = dispnames_2; + else + dispnames = dispnames_1; + + snprintf(input->name, sizeof(input->name), "Multi %s", + dispnames[input->index - solo_dev->nr_chans]); + + return 0; +} + +static int solo_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + + 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; + + if (solo_dev->video_type == SOLO_VO_FMT_TYPE_NTSC) + input->std = V4L2_STD_NTSC_M; + else + input->std = V4L2_STD_PAL_B; + + return 0; +} + +static int solo_set_input(struct file *file, void *priv, unsigned int index) +{ + struct solo_filehandle *fh = priv; + + return solo_v4l2_set_ch(fh->solo_dev, index); +} + +static int solo_get_input(struct file *file, void *priv, unsigned int *index) +{ + struct solo_filehandle *fh = priv; + + *index = fh->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; + strlcpy(f->description, "UYUV 4:2:2 Packed", sizeof(f->description)); + + return 0; +} + +static int solo_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + int image_size = solo_image_size(solo_dev); + + /* Check supported sizes */ + if (pix->width != solo_dev->video_hsize) + pix->width = solo_dev->video_hsize; + if (pix->height != solo_vlines(solo_dev)) + pix->height = solo_vlines(solo_dev); + if (pix->sizeimage != image_size) + pix->sizeimage = image_size; + + /* Check formats */ + if (pix->field == V4L2_FIELD_ANY) + pix->field = SOLO_DISP_PIX_FIELD; + + if (pix->pixelformat != V4L2_PIX_FMT_UYVY || + pix->field != SOLO_DISP_PIX_FIELD || + pix->colorspace != V4L2_COLORSPACE_SMPTE170M) + return -EINVAL; + + return 0; +} + +static int solo_set_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct solo_filehandle *fh = priv; + + if (videobuf_queue_is_busy(&fh->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_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + 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 = SOLO_DISP_PIX_FIELD; + pix->sizeimage = solo_image_size(solo_dev); + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->bytesperline = solo_bytesperline(solo_dev); + + return 0; +} + +static int solo_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *req) +{ + struct solo_filehandle *fh = priv; + + return videobuf_reqbufs(&fh->vidq, req); +} + +static int solo_querybuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct solo_filehandle *fh = priv; + + return videobuf_querybuf(&fh->vidq, buf); +} + +static int solo_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct solo_filehandle *fh = priv; + + return videobuf_qbuf(&fh->vidq, buf); +} + +static int solo_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct solo_filehandle *fh = priv; + + return videobuf_dqbuf(&fh->vidq, buf, file->f_flags & O_NONBLOCK); +} + +static int solo_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct solo_filehandle *fh = priv; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return videobuf_streamon(&fh->vidq); +} + +static int solo_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct solo_filehandle *fh = priv; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return videobuf_streamoff(&fh->vidq); +} + +static int solo_s_std(struct file *file, void *priv, v4l2_std_id *i) +{ + return 0; +} + +static const u32 solo_motion_ctrls[] = { + V4L2_CID_MOTION_TRACE, + 0 +}; + +static const u32 *solo_ctrl_classes[] = { + solo_motion_ctrls, + NULL +}; + +static int solo_disp_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + qc->id = v4l2_ctrl_next(solo_ctrl_classes, qc->id); + if (!qc->id) + return -EINVAL; + + switch (qc->id) { +#ifdef PRIVATE_CIDS + case V4L2_CID_MOTION_TRACE: + qc->type = V4L2_CTRL_TYPE_BOOLEAN; + qc->minimum = 0; + qc->maximum = qc->step = 1; + qc->default_value = 0; + strlcpy(qc->name, "Motion Detection Trace", sizeof(qc->name)); + return 0; +#else + case V4L2_CID_MOTION_TRACE: + return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0); +#endif + } + return -EINVAL; +} + +static int solo_disp_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + + switch (ctrl->id) { + case V4L2_CID_MOTION_TRACE: + ctrl->value = solo_reg_read(solo_dev, SOLO_VI_MOTION_BAR) + ? 1 : 0; + return 0; + } + return -EINVAL; +} + +static int solo_disp_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct solo_filehandle *fh = priv; + struct solo_dev *solo_dev = fh->solo_dev; + + switch (ctrl->id) { + case V4L2_CID_MOTION_TRACE: + if (ctrl->value) { + 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; + } + return -EINVAL; +} + +static const struct v4l2_file_operations solo_v4l2_fops = { + .owner = THIS_MODULE, + .open = solo_v4l2_open, + .release = solo_v4l2_release, + .read = solo_v4l2_read, + .poll = solo_v4l2_poll, + .mmap = solo_v4l2_mmap, + .ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops solo_v4l2_ioctl_ops = { + .vidioc_querycap = solo_querycap, + .vidioc_s_std = solo_s_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 = solo_reqbufs, + .vidioc_querybuf = solo_querybuf, + .vidioc_qbuf = solo_qbuf, + .vidioc_dqbuf = solo_dqbuf, + .vidioc_streamon = solo_streamon, + .vidioc_streamoff = solo_streamoff, + /* Controls */ + .vidioc_queryctrl = solo_disp_queryctrl, + .vidioc_g_ctrl = solo_disp_g_ctrl, + .vidioc_s_ctrl = solo_disp_s_ctrl, +}; + +static 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_B, + .current_norm = V4L2_STD_NTSC_M, +}; + +int solo_v4l2_init(struct solo_dev *solo_dev) +{ + int ret; + int i; + + init_waitqueue_head(&solo_dev->disp_thread_wait); + + solo_dev->vfd = video_device_alloc(); + if (!solo_dev->vfd) + return -ENOMEM; + + *solo_dev->vfd = solo_v4l2_template; + solo_dev->vfd->parent = &solo_dev->pdev->dev; + + ret = video_register_device(solo_dev->vfd, VFL_TYPE_GRABBER, video_nr); + if (ret < 0) { + video_device_release(solo_dev->vfd); + solo_dev->vfd = NULL; + return ret; + } + + video_set_drvdata(solo_dev->vfd, solo_dev); + + snprintf(solo_dev->vfd->name, sizeof(solo_dev->vfd->name), "%s (%i)", + SOLO6X10_NAME, solo_dev->vfd->num); + + if (video_nr != -1) + video_nr++; + + 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); + + /* 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 */ + + solo_irq_on(solo_dev, SOLO_IRQ_VIDEO_IN); + + return 0; +} + +void solo_v4l2_exit(struct solo_dev *solo_dev) +{ + solo_irq_off(solo_dev, SOLO_IRQ_VIDEO_IN); + if (solo_dev->vfd) { + video_unregister_device(solo_dev->vfd); + solo_dev->vfd = NULL; + } +} diff --git a/drivers/staging/mei/Kconfig b/drivers/staging/mei/Kconfig new file mode 100644 index 00000000..47d78a72 --- /dev/null +++ b/drivers/staging/mei/Kconfig @@ -0,0 +1,28 @@ +config INTEL_MEI + tristate "Intel Management Engine Interface (Intel MEI)" + depends on X86 && PCI && EXPERIMENTAL && WATCHDOG_CORE + help + The Intel Management Engine (Intel ME) provides Manageability, + Security and Media services for system containing Intel chipsets. + if selected /dev/mei misc device will be created. + + Supported Chipsets are: + 7 Series Chipset Family + 6 Series Chipset Family + 5 Series Chipset Family + 4 Series Chipset Family + Mobile 4 Series Chipset Family + ICH9 + 82946GZ/GL + 82G35 Express + 82Q963/Q965 + 82P965/G965 + Mobile PM965/GM965 + Mobile GME965/GLE960 + 82Q35 Express + 82G33/G31/P35/P31 Express + 82Q33 Express + 82X38/X48 Express + + For more information see + diff --git a/drivers/staging/mei/Makefile b/drivers/staging/mei/Makefile new file mode 100644 index 00000000..57168db6 --- /dev/null +++ b/drivers/staging/mei/Makefile @@ -0,0 +1,11 @@ +# +# Makefile - Intel Management Engine Interface (Intel MEI) Linux driver +# Copyright (c) 2010-2011, Intel Corporation. +# +obj-$(CONFIG_INTEL_MEI) += mei.o +mei-objs := init.o +mei-objs += interrupt.o +mei-objs += interface.o +mei-objs += iorw.o +mei-objs += main.o +mei-objs += wd.o diff --git a/drivers/staging/mei/TODO b/drivers/staging/mei/TODO new file mode 100644 index 00000000..fc266018 --- /dev/null +++ b/drivers/staging/mei/TODO @@ -0,0 +1,10 @@ +TODO: + - Cleanup and split the timer function +Upon Unstaging: + - move mei.h to include/linux/mei.h + - Documentation/ioctl/ioctl-number.txt + - move mei.txt under Documentation/mei/ + - move mei-amt-version.c under Documentation/mei + - add hostprogs-y for mei-amt-version.c + - drop mei_version.h + - Updated MAINTAINERS diff --git a/drivers/staging/mei/hw.h b/drivers/staging/mei/hw.h new file mode 100644 index 00000000..24c4c962 --- /dev/null +++ b/drivers/staging/mei/hw.h @@ -0,0 +1,332 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef _MEI_HW_TYPES_H_ +#define _MEI_HW_TYPES_H_ + +#include + +/* + * Timeouts + */ +#define MEI_INTEROP_TIMEOUT (HZ * 7) +#define MEI_CONNECT_TIMEOUT 3 /* at least 2 seconds */ + +#define CONNECT_TIMEOUT 15 /* HPS definition */ +#define INIT_CLIENTS_TIMEOUT 15 /* HPS definition */ + +#define IAMTHIF_STALL_TIMER 12 /* seconds */ +#define IAMTHIF_READ_TIMER 10000 /* ms */ + +/* + * Internal Clients Number + */ +#define MEI_WD_HOST_CLIENT_ID 1 +#define MEI_IAMTHIF_HOST_CLIENT_ID 2 + +/* + * MEI device IDs + */ +#define MEI_DEV_ID_82946GZ 0x2974 /* 82946GZ/GL */ +#define MEI_DEV_ID_82G35 0x2984 /* 82G35 Express */ +#define MEI_DEV_ID_82Q965 0x2994 /* 82Q963/Q965 */ +#define MEI_DEV_ID_82G965 0x29A4 /* 82P965/G965 */ + +#define MEI_DEV_ID_82GM965 0x2A04 /* Mobile PM965/GM965 */ +#define MEI_DEV_ID_82GME965 0x2A14 /* Mobile GME965/GLE960 */ + +#define MEI_DEV_ID_ICH9_82Q35 0x29B4 /* 82Q35 Express */ +#define MEI_DEV_ID_ICH9_82G33 0x29C4 /* 82G33/G31/P35/P31 Express */ +#define MEI_DEV_ID_ICH9_82Q33 0x29D4 /* 82Q33 Express */ +#define MEI_DEV_ID_ICH9_82X38 0x29E4 /* 82X38/X48 Express */ +#define MEI_DEV_ID_ICH9_3200 0x29F4 /* 3200/3210 Server */ + +#define MEI_DEV_ID_ICH9_6 0x28B4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_7 0x28C4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_8 0x28D4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_9 0x28E4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_10 0x28F4 /* Bearlake */ + +#define MEI_DEV_ID_ICH9M_1 0x2A44 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_2 0x2A54 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_3 0x2A64 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_4 0x2A74 /* Cantiga */ + +#define MEI_DEV_ID_ICH10_1 0x2E04 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_2 0x2E14 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_3 0x2E24 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_4 0x2E34 /* Eaglelake */ + +#define MEI_DEV_ID_IBXPK_1 0x3B64 /* Calpella */ +#define MEI_DEV_ID_IBXPK_2 0x3B65 /* Calpella */ + +#define MEI_DEV_ID_CPT_1 0x1C3A /* Cougerpoint */ +#define MEI_DEV_ID_PBG_1 0x1D3A /* PBG */ + +#define MEI_DEV_ID_PPT_1 0x1E3A /* Pantherpoint PPT */ +#define MEI_DEV_ID_PPT_2 0x1CBA /* Pantherpoint PPT */ +#define MEI_DEV_ID_PPT_3 0x1DBA /* Pantherpoint PPT */ + + +/* + * MEI HW Section + */ + +/* MEI registers */ +/* H_CB_WW - Host Circular Buffer (CB) Write Window register */ +#define H_CB_WW 0 +/* H_CSR - Host Control Status register */ +#define H_CSR 4 +/* ME_CB_RW - ME Circular Buffer Read Window register (read only) */ +#define ME_CB_RW 8 +/* ME_CSR_HA - ME Control Status Host Access register (read only) */ +#define ME_CSR_HA 0xC + + +/* register bits of H_CSR (Host Control Status register) */ +/* Host Circular Buffer Depth - maximum number of 32-bit entries in CB */ +#define H_CBD 0xFF000000 +/* Host Circular Buffer Write Pointer */ +#define H_CBWP 0x00FF0000 +/* Host Circular Buffer Read Pointer */ +#define H_CBRP 0x0000FF00 +/* Host Reset */ +#define H_RST 0x00000010 +/* Host Ready */ +#define H_RDY 0x00000008 +/* Host Interrupt Generate */ +#define H_IG 0x00000004 +/* Host Interrupt Status */ +#define H_IS 0x00000002 +/* Host Interrupt Enable */ +#define H_IE 0x00000001 + + +/* register bits of ME_CSR_HA (ME Control Status Host Access register) */ +/* ME CB (Circular Buffer) Depth HRA (Host Read Access) - host read only +access to ME_CBD */ +#define ME_CBD_HRA 0xFF000000 +/* ME CB Write Pointer HRA - host read only access to ME_CBWP */ +#define ME_CBWP_HRA 0x00FF0000 +/* ME CB Read Pointer HRA - host read only access to ME_CBRP */ +#define ME_CBRP_HRA 0x0000FF00 +/* ME Reset HRA - host read only access to ME_RST */ +#define ME_RST_HRA 0x00000010 +/* ME Ready HRA - host read only access to ME_RDY */ +#define ME_RDY_HRA 0x00000008 +/* ME Interrupt Generate HRA - host read only access to ME_IG */ +#define ME_IG_HRA 0x00000004 +/* ME Interrupt Status HRA - host read only access to ME_IS */ +#define ME_IS_HRA 0x00000002 +/* ME Interrupt Enable HRA - host read only access to ME_IE */ +#define ME_IE_HRA 0x00000001 + +/* + * MEI Version + */ +#define HBM_MINOR_VERSION 0 +#define HBM_MAJOR_VERSION 1 +#define HBM_TIMEOUT 1 /* 1 second */ + +/* Host bus message command opcode */ +#define MEI_HBM_CMD_OP_MSK 0x7f +/* Host bus message command RESPONSE */ +#define MEI_HBM_CMD_RES_MSK 0x80 + +/* + * MEI Bus Message Command IDs + */ +#define HOST_START_REQ_CMD 0x01 +#define HOST_START_RES_CMD 0x81 + +#define HOST_STOP_REQ_CMD 0x02 +#define HOST_STOP_RES_CMD 0x82 + +#define ME_STOP_REQ_CMD 0x03 + +#define HOST_ENUM_REQ_CMD 0x04 +#define HOST_ENUM_RES_CMD 0x84 + +#define HOST_CLIENT_PROPERTIES_REQ_CMD 0x05 +#define HOST_CLIENT_PROPERTIES_RES_CMD 0x85 + +#define CLIENT_CONNECT_REQ_CMD 0x06 +#define CLIENT_CONNECT_RES_CMD 0x86 + +#define CLIENT_DISCONNECT_REQ_CMD 0x07 +#define CLIENT_DISCONNECT_RES_CMD 0x87 + +#define MEI_FLOW_CONTROL_CMD 0x08 + +/* + * MEI Stop Reason + * used by hbm_host_stop_request.reason + */ +enum mei_stop_reason_types { + DRIVER_STOP_REQUEST = 0x00, + DEVICE_D1_ENTRY = 0x01, + DEVICE_D2_ENTRY = 0x02, + DEVICE_D3_ENTRY = 0x03, + SYSTEM_S1_ENTRY = 0x04, + SYSTEM_S2_ENTRY = 0x05, + SYSTEM_S3_ENTRY = 0x06, + SYSTEM_S4_ENTRY = 0x07, + SYSTEM_S5_ENTRY = 0x08 +}; + +/* + * Client Connect Status + * used by hbm_client_connect_response.status + */ +enum client_connect_status_types { + CCS_SUCCESS = 0x00, + CCS_NOT_FOUND = 0x01, + CCS_ALREADY_STARTED = 0x02, + CCS_OUT_OF_RESOURCES = 0x03, + CCS_MESSAGE_SMALL = 0x04 +}; + +/* + * Client Disconnect Status + */ +enum client_disconnect_status_types { + CDS_SUCCESS = 0x00 +}; + +/* + * MEI BUS Interface Section + */ +struct mei_msg_hdr { + u32 me_addr:8; + u32 host_addr:8; + u32 length:9; + u32 reserved:6; + u32 msg_complete:1; +} __packed; + + +struct mei_bus_message { + u8 hbm_cmd; + u8 data[0]; +} __packed; + +struct hbm_version { + u8 minor_version; + u8 major_version; +} __packed; + +struct hbm_host_version_request { + u8 hbm_cmd; + u8 reserved; + struct hbm_version host_version; +} __packed; + +struct hbm_host_version_response { + u8 hbm_cmd; + u8 host_version_supported; + struct hbm_version me_max_version; +} __packed; + +struct hbm_host_stop_request { + u8 hbm_cmd; + u8 reason; + u8 reserved[2]; +} __packed; + +struct hbm_host_stop_response { + u8 hbm_cmd; + u8 reserved[3]; +} __packed; + +struct hbm_me_stop_request { + u8 hbm_cmd; + u8 reason; + u8 reserved[2]; +} __packed; + +struct hbm_host_enum_request { + u8 hbm_cmd; + u8 reserved[3]; +} __packed; + +struct hbm_host_enum_response { + u8 hbm_cmd; + u8 reserved[3]; + u8 valid_addresses[32]; +} __packed; + +struct mei_client_properties { + uuid_le protocol_name; + u8 protocol_version; + u8 max_number_of_connections; + u8 fixed_address; + u8 single_recv_buf; + u32 max_msg_length; +} __packed; + +struct hbm_props_request { + u8 hbm_cmd; + u8 address; + u8 reserved[2]; +} __packed; + + +struct hbm_props_response { + u8 hbm_cmd; + u8 address; + u8 status; + u8 reserved[1]; + struct mei_client_properties client_properties; +} __packed; + +struct hbm_client_connect_request { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 reserved; +} __packed; + +struct hbm_client_connect_response { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 status; +} __packed; + +struct hbm_client_disconnect_request { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 reserved[1]; +} __packed; + +#define MEI_FC_MESSAGE_RESERVED_LENGTH 5 + +struct hbm_flow_control { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 reserved[MEI_FC_MESSAGE_RESERVED_LENGTH]; +} __packed; + +struct mei_me_client { + struct mei_client_properties props; + u8 client_id; + u8 mei_flow_ctrl_creds; +} __packed; + + +#endif diff --git a/drivers/staging/mei/init.c b/drivers/staging/mei/init.c new file mode 100644 index 00000000..eab711fb --- /dev/null +++ b/drivers/staging/mei/init.c @@ -0,0 +1,735 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include + +#include "mei_dev.h" +#include "hw.h" +#include "interface.h" +#include "mei.h" + +const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, + 0xa8, 0x46, 0xe0, 0xff, 0x65, + 0x81, 0x4c); + +/** + * mei_io_list_init - Sets up a queue list. + * + * @list: An instance io list structure + * @dev: the device structure + */ +void mei_io_list_init(struct mei_io_list *list) +{ + /* initialize our queue list */ + INIT_LIST_HEAD(&list->mei_cb.cb_list); +} + +/** + * mei_io_list_flush - removes list entry belonging to cl. + * + * @list: An instance of our list structure + * @cl: private data of the file object + */ +void mei_io_list_flush(struct mei_io_list *list, struct mei_cl *cl) +{ + struct mei_cl_cb *pos; + struct mei_cl_cb *next; + + list_for_each_entry_safe(pos, next, &list->mei_cb.cb_list, cb_list) { + if (pos->file_private) { + struct mei_cl *cl_tmp; + cl_tmp = (struct mei_cl *)pos->file_private; + if (mei_cl_cmp_id(cl, cl_tmp)) + list_del(&pos->cb_list); + } + } +} +/** + * mei_cl_flush_queues - flushes queue lists belonging to cl. + * + * @dev: the device structure + * @cl: private data of the file object + */ +int mei_cl_flush_queues(struct mei_cl *cl) +{ + if (!cl || !cl->dev) + return -EINVAL; + + dev_dbg(&cl->dev->pdev->dev, "remove list entry belonging to cl\n"); + mei_io_list_flush(&cl->dev->read_list, cl); + mei_io_list_flush(&cl->dev->write_list, cl); + mei_io_list_flush(&cl->dev->write_waiting_list, cl); + mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); + mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); + mei_io_list_flush(&cl->dev->amthi_cmd_list, cl); + mei_io_list_flush(&cl->dev->amthi_read_complete_list, cl); + return 0; +} + + + +/** + * mei_reset_iamthif_params - initializes mei device iamthif + * + * @dev: the device structure + */ +static void mei_reset_iamthif_params(struct mei_device *dev) +{ + /* reset iamthif parameters. */ + dev->iamthif_current_cb = NULL; + dev->iamthif_msg_buf_size = 0; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = false; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->iamthif_timer = 0; +} + +/** + * init_mei_device - allocates and initializes the mei device structure + * + * @pdev: The pci device structure + * + * returns The mei_device_device pointer on success, NULL on failure. + */ +struct mei_device *mei_device_init(struct pci_dev *pdev) +{ + struct mei_device *dev; + + dev = kzalloc(sizeof(struct mei_device), GFP_KERNEL); + if (!dev) + return NULL; + + /* setup our list array */ + INIT_LIST_HEAD(&dev->file_list); + INIT_LIST_HEAD(&dev->wd_cl.link); + INIT_LIST_HEAD(&dev->iamthif_cl.link); + mutex_init(&dev->device_lock); + init_waitqueue_head(&dev->wait_recvd_msg); + init_waitqueue_head(&dev->wait_stop_wd); + dev->mei_state = MEI_INITIALIZING; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->wd_interface_reg = false; + + + mei_io_list_init(&dev->read_list); + mei_io_list_init(&dev->write_list); + mei_io_list_init(&dev->write_waiting_list); + mei_io_list_init(&dev->ctrl_wr_list); + mei_io_list_init(&dev->ctrl_rd_list); + mei_io_list_init(&dev->amthi_cmd_list); + mei_io_list_init(&dev->amthi_read_complete_list); + dev->pdev = pdev; + return dev; +} + +/** + * mei_hw_init - initializes host and fw to start work. + * + * @dev: the device structure + * + * returns 0 on success, <0 on failure. + */ +int mei_hw_init(struct mei_device *dev) +{ + int err = 0; + int ret; + + mutex_lock(&dev->device_lock); + + dev->host_hw_state = mei_hcsr_read(dev); + dev->me_hw_state = mei_mecsr_read(dev); + dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, mestate = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + + /* acknowledge interrupt and stop interupts */ + if ((dev->host_hw_state & H_IS) == H_IS) + mei_reg_write(dev, H_CSR, dev->host_hw_state); + + dev->recvd_msg = false; + dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n"); + + mei_reset(dev, 1); + + dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + + /* wait for ME to turn on ME_RDY */ + if (!dev->recvd_msg) { + mutex_unlock(&dev->device_lock); + err = wait_event_interruptible_timeout(dev->wait_recvd_msg, + dev->recvd_msg, MEI_INTEROP_TIMEOUT); + mutex_lock(&dev->device_lock); + } + + if (err <= 0 && !dev->recvd_msg) { + dev->mei_state = MEI_DISABLED; + dev_dbg(&dev->pdev->dev, + "wait_event_interruptible_timeout failed" + "on wait for ME to turn on ME_RDY.\n"); + ret = -ENODEV; + goto out; + } + + if (!(((dev->host_hw_state & H_RDY) == H_RDY) && + ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA))) { + dev->mei_state = MEI_DISABLED; + dev_dbg(&dev->pdev->dev, + "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + + if (!(dev->host_hw_state & H_RDY)) + dev_dbg(&dev->pdev->dev, "host turn off H_RDY.\n"); + + if (!(dev->me_hw_state & ME_RDY_HRA)) + dev_dbg(&dev->pdev->dev, "ME turn off ME_RDY.\n"); + + printk(KERN_ERR "mei: link layer initialization failed.\n"); + ret = -ENODEV; + goto out; + } + + if (dev->version.major_version != HBM_MAJOR_VERSION || + dev->version.minor_version != HBM_MINOR_VERSION) { + dev_dbg(&dev->pdev->dev, "MEI start failed.\n"); + ret = -ENODEV; + goto out; + } + + dev->recvd_msg = false; + dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + dev_dbg(&dev->pdev->dev, "ME turn on ME_RDY and host turn on H_RDY.\n"); + dev_dbg(&dev->pdev->dev, "link layer has been established.\n"); + dev_dbg(&dev->pdev->dev, "MEI start success.\n"); + ret = 0; + +out: + mutex_unlock(&dev->device_lock); + return ret; +} + +/** + * mei_hw_reset - resets fw via mei csr register. + * + * @dev: the device structure + * @interrupts_enabled: if interrupt should be enabled after reset. + */ +static void mei_hw_reset(struct mei_device *dev, int interrupts_enabled) +{ + dev->host_hw_state |= (H_RST | H_IG); + + if (interrupts_enabled) + mei_enable_interrupts(dev); + else + mei_disable_interrupts(dev); +} + +/** + * mei_reset - resets host and fw. + * + * @dev: the device structure + * @interrupts_enabled: if interrupt should be enabled after reset. + */ +void mei_reset(struct mei_device *dev, int interrupts_enabled) +{ + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + struct mei_cl_cb *cb_pos = NULL; + struct mei_cl_cb *cb_next = NULL; + bool unexpected; + + if (dev->mei_state == MEI_RECOVERING_FROM_RESET) { + dev->need_reset = true; + return; + } + + unexpected = (dev->mei_state != MEI_INITIALIZING && + dev->mei_state != MEI_DISABLED && + dev->mei_state != MEI_POWER_DOWN && + dev->mei_state != MEI_POWER_UP); + + dev->host_hw_state = mei_hcsr_read(dev); + + dev_dbg(&dev->pdev->dev, "before reset host_hw_state = 0x%08x.\n", + dev->host_hw_state); + + mei_hw_reset(dev, interrupts_enabled); + + dev->host_hw_state &= ~H_RST; + dev->host_hw_state |= H_IG; + + mei_hcsr_set(dev); + + dev_dbg(&dev->pdev->dev, "currently saved host_hw_state = 0x%08x.\n", + dev->host_hw_state); + + dev->need_reset = false; + + if (dev->mei_state != MEI_INITIALIZING) { + if (dev->mei_state != MEI_DISABLED && + dev->mei_state != MEI_POWER_DOWN) + dev->mei_state = MEI_RESETING; + + list_for_each_entry_safe(cl_pos, + cl_next, &dev->file_list, link) { + cl_pos->state = MEI_FILE_DISCONNECTED; + cl_pos->mei_flow_ctrl_creds = 0; + cl_pos->read_cb = NULL; + cl_pos->timer_count = 0; + } + /* remove entry if already in list */ + dev_dbg(&dev->pdev->dev, "list del iamthif and wd file list.\n"); + mei_remove_client_from_file_list(dev, + dev->wd_cl.host_client_id); + + mei_remove_client_from_file_list(dev, + dev->iamthif_cl.host_client_id); + + mei_reset_iamthif_params(dev); + dev->wd_due_counter = 0; + dev->extra_write_index = 0; + } + + dev->me_clients_num = 0; + dev->rd_msg_hdr = 0; + dev->stop = false; + dev->wd_pending = false; + + /* update the state of the registers after reset */ + dev->host_hw_state = mei_hcsr_read(dev); + dev->me_hw_state = mei_mecsr_read(dev); + + dev_dbg(&dev->pdev->dev, "after reset host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", + dev->host_hw_state, dev->me_hw_state); + + if (unexpected) + dev_warn(&dev->pdev->dev, "unexpected reset.\n"); + + /* Wake up all readings so they can be interrupted */ + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if (waitqueue_active(&cl_pos->rx_wait)) { + dev_dbg(&dev->pdev->dev, "Waking up client!\n"); + wake_up_interruptible(&cl_pos->rx_wait); + } + } + /* remove all waiting requests */ + list_for_each_entry_safe(cb_pos, cb_next, + &dev->write_list.mei_cb.cb_list, cb_list) { + list_del(&cb_pos->cb_list); + mei_free_cb_private(cb_pos); + } +} + + + +/** + * host_start_message - mei host sends start message. + * + * @dev: the device structure + * + * returns none. + */ +void mei_host_start_message(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_host_version_request *host_start_req; + + /* host start message */ + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_host_version_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + host_start_req = + (struct hbm_host_version_request *) &dev->wr_msg_buf[1]; + memset(host_start_req, 0, sizeof(struct hbm_host_version_request)); + host_start_req->hbm_cmd = HOST_START_REQ_CMD; + host_start_req->host_version.major_version = HBM_MAJOR_VERSION; + host_start_req->host_version.minor_version = HBM_MINOR_VERSION; + dev->recvd_msg = false; + if (mei_write_message(dev, mei_hdr, (unsigned char *)host_start_req, + mei_hdr->length)) { + dev_dbg(&dev->pdev->dev, "write send version message to FW fail.\n"); + dev->mei_state = MEI_RESETING; + mei_reset(dev, 1); + } + dev->init_clients_state = MEI_START_MESSAGE; + dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; + return ; +} + +/** + * host_enum_clients_message - host sends enumeration client request message. + * + * @dev: the device structure + * + * returns none. + */ +void mei_host_enum_clients_message(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_host_enum_request *host_enum_req; + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + /* enumerate clients */ + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_host_enum_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + host_enum_req = (struct hbm_host_enum_request *) &dev->wr_msg_buf[1]; + memset(host_enum_req, 0, sizeof(struct hbm_host_enum_request)); + host_enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; + if (mei_write_message(dev, mei_hdr, (unsigned char *)host_enum_req, + mei_hdr->length)) { + dev->mei_state = MEI_RESETING; + dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); + mei_reset(dev, 1); + } + dev->init_clients_state = MEI_ENUM_CLIENTS_MESSAGE; + dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; + return; +} + + +/** + * allocate_me_clients_storage - allocates storage for me clients + * + * @dev: the device structure + * + * returns none. + */ +void mei_allocate_me_clients_storage(struct mei_device *dev) +{ + struct mei_me_client *clients; + int b; + + /* count how many ME clients we have */ + for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) + dev->me_clients_num++; + + if (dev->me_clients_num <= 0) + return ; + + + if (dev->me_clients != NULL) { + kfree(dev->me_clients); + dev->me_clients = NULL; + } + dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%zd.\n", + dev->me_clients_num * sizeof(struct mei_me_client)); + /* allocate storage for ME clients representation */ + clients = kcalloc(dev->me_clients_num, + sizeof(struct mei_me_client), GFP_KERNEL); + if (!clients) { + dev_dbg(&dev->pdev->dev, "memory allocation for ME clients failed.\n"); + dev->mei_state = MEI_RESETING; + mei_reset(dev, 1); + return ; + } + dev->me_clients = clients; + return ; +} +/** + * host_client_properties - reads properties for client + * + * @dev: the device structure + * + * returns: + * < 0 - Error. + * = 0 - no more clients. + * = 1 - still have clients to send properties request. + */ +int mei_host_client_properties(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_header; + struct hbm_props_request *host_cli_req; + int b; + u8 client_num = dev->me_client_presentation_num; + + b = dev->me_client_index; + b = find_next_bit(dev->me_clients_map, MEI_CLIENTS_MAX, b); + if (b < MEI_CLIENTS_MAX) { + dev->me_clients[client_num].client_id = b; + dev->me_clients[client_num].mei_flow_ctrl_creds = 0; + mei_header = (struct mei_msg_hdr *)&dev->wr_msg_buf[0]; + mei_header->host_addr = 0; + mei_header->me_addr = 0; + mei_header->length = sizeof(struct hbm_props_request); + mei_header->msg_complete = 1; + mei_header->reserved = 0; + + host_cli_req = (struct hbm_props_request *)&dev->wr_msg_buf[1]; + + memset(host_cli_req, 0, sizeof(struct hbm_props_request)); + + host_cli_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; + host_cli_req->address = b; + + if (mei_write_message(dev, mei_header, + (unsigned char *)host_cli_req, + mei_header->length)) { + dev->mei_state = MEI_RESETING; + dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); + mei_reset(dev, 1); + return -EIO; + } + + dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; + dev->me_client_index = b; + return 1; + } + + return 0; +} + +/** + * mei_init_file_private - initializes private file structure. + * + * @priv: private file structure to be initialized + * @file: the file structure + */ +void mei_cl_init(struct mei_cl *priv, struct mei_device *dev) +{ + memset(priv, 0, sizeof(struct mei_cl)); + init_waitqueue_head(&priv->wait); + init_waitqueue_head(&priv->rx_wait); + init_waitqueue_head(&priv->tx_wait); + INIT_LIST_HEAD(&priv->link); + priv->reading_state = MEI_IDLE; + priv->writing_state = MEI_IDLE; + priv->dev = dev; +} + +int mei_find_me_client_index(const struct mei_device *dev, uuid_le cuuid) +{ + int i, res = -1; + + for (i = 0; i < dev->me_clients_num; ++i) + if (uuid_le_cmp(cuuid, + dev->me_clients[i].props.protocol_name) == 0) { + res = i; + break; + } + + return res; +} + + +/** + * mei_find_me_client_update_filext - searches for ME client guid + * sets client_id in mei_file_private if found + * @dev: the device structure + * @priv: private file structure to set client_id in + * @cguid: searched guid of ME client + * @client_id: id of host client to be set in file private structure + * + * returns ME client index + */ +u8 mei_find_me_client_update_filext(struct mei_device *dev, struct mei_cl *priv, + const uuid_le *cguid, u8 client_id) +{ + int i; + + if (!dev || !priv || !cguid) + return 0; + + /* check for valid client id */ + i = mei_find_me_client_index(dev, *cguid); + if (i >= 0) { + priv->me_client_id = dev->me_clients[i].client_id; + priv->state = MEI_FILE_CONNECTING; + priv->host_client_id = client_id; + + list_add_tail(&priv->link, &dev->file_list); + return (u8)i; + } + + return 0; +} + +/** + * host_init_iamthif - mei initialization iamthif client. + * + * @dev: the device structure + * + */ +void mei_host_init_iamthif(struct mei_device *dev) +{ + u8 i; + unsigned char *msg_buf; + + mei_cl_init(&dev->iamthif_cl, dev); + dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; + + /* find ME amthi client */ + i = mei_find_me_client_update_filext(dev, &dev->iamthif_cl, + &mei_amthi_guid, MEI_IAMTHIF_HOST_CLIENT_ID); + if (dev->iamthif_cl.state != MEI_FILE_CONNECTING) { + dev_dbg(&dev->pdev->dev, "failed to find iamthif client.\n"); + return; + } + + /* Assign iamthif_mtu to the value received from ME */ + + dev->iamthif_mtu = dev->me_clients[i].props.max_msg_length; + dev_dbg(&dev->pdev->dev, "IAMTHIF_MTU = %d\n", + dev->me_clients[i].props.max_msg_length); + + kfree(dev->iamthif_msg_buf); + dev->iamthif_msg_buf = NULL; + + /* allocate storage for ME message buffer */ + msg_buf = kcalloc(dev->iamthif_mtu, + sizeof(unsigned char), GFP_KERNEL); + if (!msg_buf) { + dev_dbg(&dev->pdev->dev, "memory allocation for ME message buffer failed.\n"); + return; + } + + dev->iamthif_msg_buf = msg_buf; + + if (mei_connect(dev, &dev->iamthif_cl)) { + dev_dbg(&dev->pdev->dev, "Failed to connect to AMTHI client\n"); + dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; + dev->iamthif_cl.host_client_id = 0; + } else { + dev->iamthif_cl.timer_count = CONNECT_TIMEOUT; + } +} + +/** + * mei_alloc_file_private - allocates a private file structure and sets it up. + * @file: the file structure + * + * returns The allocated file or NULL on failure + */ +struct mei_cl *mei_cl_allocate(struct mei_device *dev) +{ + struct mei_cl *cl; + + cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); + if (!cl) + return NULL; + + mei_cl_init(cl, dev); + + return cl; +} + + + +/** + * mei_disconnect_host_client - sends disconnect message to fw from host client. + * + * @dev: the device structure + * @cl: private data of the file object + * + * Locking: called under "dev->device_lock" lock + * + * returns 0 on success, <0 on failure. + */ +int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) +{ + int rets, err; + long timeout = 15; /* 15 seconds */ + struct mei_cl_cb *cb; + + if (!dev || !cl) + return -ENODEV; + + if (cl->state != MEI_FILE_DISCONNECTING) + return 0; + + cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!cb) + return -ENOMEM; + + INIT_LIST_HEAD(&cb->cb_list); + cb->file_private = cl; + cb->major_file_operations = MEI_CLOSE; + if (dev->mei_host_buffer_is_empty) { + dev->mei_host_buffer_is_empty = false; + if (mei_disconnect(dev, cl)) { + rets = -ENODEV; + dev_dbg(&dev->pdev->dev, "failed to call mei_disconnect.\n"); + goto free; + } + mdelay(10); /* Wait for hardware disconnection ready */ + list_add_tail(&cb->cb_list, &dev->ctrl_rd_list.mei_cb.cb_list); + } else { + dev_dbg(&dev->pdev->dev, "add disconnect cb to control write list\n"); + list_add_tail(&cb->cb_list, + &dev->ctrl_wr_list.mei_cb.cb_list); + } + mutex_unlock(&dev->device_lock); + + err = wait_event_timeout(dev->wait_recvd_msg, + (MEI_FILE_DISCONNECTED == cl->state), + timeout * HZ); + + mutex_lock(&dev->device_lock); + if (MEI_FILE_DISCONNECTED == cl->state) { + rets = 0; + dev_dbg(&dev->pdev->dev, "successfully disconnected from FW client.\n"); + } else { + rets = -ENODEV; + if (MEI_FILE_DISCONNECTED != cl->state) + dev_dbg(&dev->pdev->dev, "wrong status client disconnect.\n"); + + if (err) + dev_dbg(&dev->pdev->dev, + "wait failed disconnect err=%08x\n", + err); + + dev_dbg(&dev->pdev->dev, "failed to disconnect from FW client.\n"); + } + + mei_io_list_flush(&dev->ctrl_rd_list, cl); + mei_io_list_flush(&dev->ctrl_wr_list, cl); +free: + mei_free_cb_private(cb); + return rets; +} + +/** + * mei_remove_client_from_file_list - + * removes file private data from device file list + * + * @dev: the device structure + * @host_client_id: host client id to be removed + */ +void mei_remove_client_from_file_list(struct mei_device *dev, + u8 host_client_id) +{ + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if (host_client_id == cl_pos->host_client_id) { + dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", + cl_pos->host_client_id, + cl_pos->me_client_id); + list_del_init(&cl_pos->link); + break; + } + } +} diff --git a/drivers/staging/mei/interface.c b/drivers/staging/mei/interface.c new file mode 100644 index 00000000..9a2cfafc --- /dev/null +++ b/drivers/staging/mei/interface.c @@ -0,0 +1,428 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include "mei_dev.h" +#include "mei.h" +#include "interface.h" + + + +/** + * mei_set_csr_register - writes H_CSR register to the mei device, + * and ignores the H_IS bit for it is write-one-to-zero. + * + * @dev: the device structure + */ +void mei_hcsr_set(struct mei_device *dev) +{ + if ((dev->host_hw_state & H_IS) == H_IS) + dev->host_hw_state &= ~H_IS; + mei_reg_write(dev, H_CSR, dev->host_hw_state); + dev->host_hw_state = mei_hcsr_read(dev); +} + +/** + * mei_csr_enable_interrupts - enables mei device interrupts + * + * @dev: the device structure + */ +void mei_enable_interrupts(struct mei_device *dev) +{ + dev->host_hw_state |= H_IE; + mei_hcsr_set(dev); +} + +/** + * mei_csr_disable_interrupts - disables mei device interrupts + * + * @dev: the device structure + */ +void mei_disable_interrupts(struct mei_device *dev) +{ + dev->host_hw_state &= ~H_IE; + mei_hcsr_set(dev); +} + +/** + * _host_get_filled_slots - gets number of device filled buffer slots + * + * @device: the device structure + * + * returns number of filled slots + */ +static unsigned char _host_get_filled_slots(const struct mei_device *dev) +{ + char read_ptr, write_ptr; + + read_ptr = (char) ((dev->host_hw_state & H_CBRP) >> 8); + write_ptr = (char) ((dev->host_hw_state & H_CBWP) >> 16); + + return (unsigned char) (write_ptr - read_ptr); +} + +/** + * mei_host_buffer_is_empty - checks if host buffer is empty. + * + * @dev: the device structure + * + * returns 1 if empty, 0 - otherwise. + */ +int mei_host_buffer_is_empty(struct mei_device *dev) +{ + unsigned char filled_slots; + + dev->host_hw_state = mei_hcsr_read(dev); + filled_slots = _host_get_filled_slots(dev); + + if (filled_slots == 0) + return 1; + + return 0; +} + +/** + * mei_count_empty_write_slots - counts write empty slots. + * + * @dev: the device structure + * + * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise empty slots count + */ +int mei_count_empty_write_slots(struct mei_device *dev) +{ + unsigned char buffer_depth, filled_slots, empty_slots; + + dev->host_hw_state = mei_hcsr_read(dev); + buffer_depth = (unsigned char) ((dev->host_hw_state & H_CBD) >> 24); + filled_slots = _host_get_filled_slots(dev); + empty_slots = buffer_depth - filled_slots; + + /* check for overflow */ + if (filled_slots > buffer_depth) + return -EOVERFLOW; + + return empty_slots; +} + +/** + * mei_write_message - writes a message to mei device. + * + * @dev: the device structure + * @header: header of message + * @write_buffer: message buffer will be written + * @write_length: message size will be written + * + * This function returns -EIO if write has failed + */ +int mei_write_message(struct mei_device *dev, + struct mei_msg_hdr *header, + unsigned char *write_buffer, + unsigned long write_length) +{ + u32 temp_msg = 0; + unsigned long bytes_written = 0; + unsigned char buffer_depth, filled_slots, empty_slots; + unsigned long dw_to_write; + + dev->host_hw_state = mei_hcsr_read(dev); + + dev_dbg(&dev->pdev->dev, + "host_hw_state = 0x%08x.\n", + dev->host_hw_state); + + dev_dbg(&dev->pdev->dev, + "mei_write_message header=%08x.\n", + *((u32 *) header)); + + buffer_depth = (unsigned char) ((dev->host_hw_state & H_CBD) >> 24); + filled_slots = _host_get_filled_slots(dev); + empty_slots = buffer_depth - filled_slots; + dev_dbg(&dev->pdev->dev, + "filled = %hu, empty = %hu.\n", + filled_slots, empty_slots); + + dw_to_write = ((write_length + 3) / 4); + + if (dw_to_write > empty_slots) + return -EIO; + + mei_reg_write(dev, H_CB_WW, *((u32 *) header)); + + while (write_length >= 4) { + mei_reg_write(dev, H_CB_WW, + *(u32 *) (write_buffer + bytes_written)); + bytes_written += 4; + write_length -= 4; + } + + if (write_length > 0) { + memcpy(&temp_msg, &write_buffer[bytes_written], write_length); + mei_reg_write(dev, H_CB_WW, temp_msg); + } + + dev->host_hw_state |= H_IG; + mei_hcsr_set(dev); + dev->me_hw_state = mei_mecsr_read(dev); + if ((dev->me_hw_state & ME_RDY_HRA) != ME_RDY_HRA) + return -EIO; + + return 0; +} + +/** + * mei_count_full_read_slots - counts read full slots. + * + * @dev: the device structure + * + * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise filled slots count + */ +int mei_count_full_read_slots(struct mei_device *dev) +{ + char read_ptr, write_ptr; + unsigned char buffer_depth, filled_slots; + + dev->me_hw_state = mei_mecsr_read(dev); + buffer_depth = (unsigned char)((dev->me_hw_state & ME_CBD_HRA) >> 24); + read_ptr = (char) ((dev->me_hw_state & ME_CBRP_HRA) >> 8); + write_ptr = (char) ((dev->me_hw_state & ME_CBWP_HRA) >> 16); + filled_slots = (unsigned char) (write_ptr - read_ptr); + + /* check for overflow */ + if (filled_slots > buffer_depth) + return -EOVERFLOW; + + dev_dbg(&dev->pdev->dev, "filled_slots =%08x\n", filled_slots); + return (int)filled_slots; +} + +/** + * mei_read_slots - reads a message from mei device. + * + * @dev: the device structure + * @buffer: message buffer will be written + * @buffer_length: message size will be read + */ +void mei_read_slots(struct mei_device *dev, unsigned char *buffer, + unsigned long buffer_length) +{ + u32 *reg_buf = (u32 *)buffer; + + for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32)) + *reg_buf++ = mei_mecbrw_read(dev); + + if (buffer_length > 0) { + u32 reg = mei_mecbrw_read(dev); + memcpy(reg_buf, ®, buffer_length); + } + + dev->host_hw_state |= H_IG; + mei_hcsr_set(dev); +} + +/** + * mei_flow_ctrl_creds - checks flow_control credentials. + * + * @dev: the device structure + * @cl: private data of the file object + * + * returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise. + * -ENOENT if mei_cl is not present + * -EINVAL if single_recv_buf == 0 + */ +int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl) +{ + int i; + + if (!dev->me_clients_num) + return 0; + + if (cl->mei_flow_ctrl_creds > 0) + return 1; + + for (i = 0; i < dev->me_clients_num; i++) { + struct mei_me_client *me_cl = &dev->me_clients[i]; + if (me_cl->client_id == cl->me_client_id) { + if (me_cl->mei_flow_ctrl_creds) { + if (WARN_ON(me_cl->props.single_recv_buf == 0)) + return -EINVAL; + return 1; + } else { + return 0; + } + } + } + return -ENOENT; +} + +/** + * mei_flow_ctrl_reduce - reduces flow_control. + * + * @dev: the device structure + * @cl: private data of the file object + * @returns + * 0 on success + * -ENOENT when me client is not found + * -EINVAL when ctrl credits are <= 0 + */ +int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl) +{ + int i; + + if (!dev->me_clients_num) + return -ENOENT; + + for (i = 0; i < dev->me_clients_num; i++) { + struct mei_me_client *me_cl = &dev->me_clients[i]; + if (me_cl->client_id == cl->me_client_id) { + if (me_cl->props.single_recv_buf != 0) { + if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) + return -EINVAL; + dev->me_clients[i].mei_flow_ctrl_creds--; + } else { + if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) + return -EINVAL; + cl->mei_flow_ctrl_creds--; + } + return 0; + } + } + return -ENOENT; +} + +/** + * mei_send_flow_control - sends flow control to fw. + * + * @dev: the device structure + * @cl: private data of the file object + * + * This function returns -EIO on write failure + */ +int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_flow_control *mei_flow_control; + + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_flow_control); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + mei_flow_control = (struct hbm_flow_control *) &dev->wr_msg_buf[1]; + memset(mei_flow_control, 0, sizeof(*mei_flow_control)); + mei_flow_control->host_addr = cl->host_client_id; + mei_flow_control->me_addr = cl->me_client_id; + mei_flow_control->hbm_cmd = MEI_FLOW_CONTROL_CMD; + memset(mei_flow_control->reserved, 0, + sizeof(mei_flow_control->reserved)); + dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n", + cl->host_client_id, cl->me_client_id); + + return mei_write_message(dev, mei_hdr, + (unsigned char *) mei_flow_control, + sizeof(struct hbm_flow_control)); +} + +/** + * mei_other_client_is_connecting - checks if other + * client with the same client id is connected. + * + * @dev: the device structure + * @cl: private data of the file object + * + * returns 1 if other client is connected, 0 - otherwise. + */ +int mei_other_client_is_connecting(struct mei_device *dev, + struct mei_cl *cl) +{ + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if ((cl_pos->state == MEI_FILE_CONNECTING) && + (cl_pos != cl) && + cl->me_client_id == cl_pos->me_client_id) + return 1; + + } + return 0; +} + +/** + * mei_disconnect - sends disconnect message to fw. + * + * @dev: the device structure + * @cl: private data of the file object + * + * This function returns -EIO on write failure + */ +int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_client_disconnect_request *mei_cli_disconnect; + + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_client_disconnect_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + mei_cli_disconnect = + (struct hbm_client_disconnect_request *) &dev->wr_msg_buf[1]; + memset(mei_cli_disconnect, 0, sizeof(*mei_cli_disconnect)); + mei_cli_disconnect->host_addr = cl->host_client_id; + mei_cli_disconnect->me_addr = cl->me_client_id; + mei_cli_disconnect->hbm_cmd = CLIENT_DISCONNECT_REQ_CMD; + mei_cli_disconnect->reserved[0] = 0; + + return mei_write_message(dev, mei_hdr, + (unsigned char *) mei_cli_disconnect, + sizeof(struct hbm_client_disconnect_request)); +} + +/** + * mei_connect - sends connect message to fw. + * + * @dev: the device structure + * @cl: private data of the file object + * + * This function returns -EIO on write failure + */ +int mei_connect(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_client_connect_request *mei_cli_connect; + + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_client_connect_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + mei_cli_connect = + (struct hbm_client_connect_request *) &dev->wr_msg_buf[1]; + mei_cli_connect->host_addr = cl->host_client_id; + mei_cli_connect->me_addr = cl->me_client_id; + mei_cli_connect->hbm_cmd = CLIENT_CONNECT_REQ_CMD; + mei_cli_connect->reserved = 0; + + return mei_write_message(dev, mei_hdr, + (unsigned char *) mei_cli_connect, + sizeof(struct hbm_client_connect_request)); +} diff --git a/drivers/staging/mei/interface.h b/drivers/staging/mei/interface.h new file mode 100644 index 00000000..fb90c6f8 --- /dev/null +++ b/drivers/staging/mei/interface.h @@ -0,0 +1,76 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + + + +#ifndef _MEI_INTERFACE_H_ +#define _MEI_INTERFACE_H_ + +#include "mei.h" +#include "mei_dev.h" + + +#define AMT_WD_DEFAULT_TIMEOUT 120 /* seconds */ +#define AMT_WD_MIN_TIMEOUT 120 /* seconds */ +#define AMT_WD_MAX_TIMEOUT 65535 /* seconds */ + +#define MEI_WATCHDOG_DATA_SIZE 16 +#define MEI_START_WD_DATA_SIZE 20 +#define MEI_WD_PARAMS_SIZE 4 + + +void mei_read_slots(struct mei_device *dev, + unsigned char *buffer, + unsigned long buffer_length); + +int mei_write_message(struct mei_device *dev, + struct mei_msg_hdr *header, + unsigned char *write_buffer, + unsigned long write_length); + +int mei_host_buffer_is_empty(struct mei_device *dev); + +int mei_count_full_read_slots(struct mei_device *dev); + +int mei_count_empty_write_slots(struct mei_device *dev); + +int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl); + +int mei_wd_send(struct mei_device *dev); +int mei_wd_stop(struct mei_device *dev, bool preserve); +bool mei_wd_host_init(struct mei_device *dev); +void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout); +/* + * mei_watchdog_register - Registering watchdog interface + * once we got connection to the WD Client + * @dev - mei device + */ +void mei_watchdog_register(struct mei_device *dev); +/* + * mei_watchdog_unregister - Unregistering watchdog interface + * @dev - mei device + */ +void mei_watchdog_unregister(struct mei_device *dev); + +int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl); + +int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl); + +int mei_disconnect(struct mei_device *dev, struct mei_cl *cl); +int mei_other_client_is_connecting(struct mei_device *dev, struct mei_cl *cl); +int mei_connect(struct mei_device *dev, struct mei_cl *cl); + +#endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/staging/mei/interrupt.c b/drivers/staging/mei/interrupt.c new file mode 100644 index 00000000..2007d244 --- /dev/null +++ b/drivers/staging/mei/interrupt.c @@ -0,0 +1,1590 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + + +#include +#include +#include +#include +#include + +#include "mei_dev.h" +#include "mei.h" +#include "hw.h" +#include "interface.h" + + +/** + * mei_interrupt_quick_handler - The ISR of the MEI device + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + */ +irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + u32 csr_reg = mei_hcsr_read(dev); + + if ((csr_reg & H_IS) != H_IS) + return IRQ_NONE; + + /* clear H_IS bit in H_CSR */ + mei_reg_write(dev, H_CSR, csr_reg); + + return IRQ_WAKE_THREAD; +} + +/** + * _mei_cmpl - processes completed operation. + * + * @cl: private data of the file object. + * @cb_pos: callback block. + */ +static void _mei_cmpl(struct mei_cl *cl, struct mei_cl_cb *cb_pos) +{ + if (cb_pos->major_file_operations == MEI_WRITE) { + mei_free_cb_private(cb_pos); + cb_pos = NULL; + cl->writing_state = MEI_WRITE_COMPLETE; + if (waitqueue_active(&cl->tx_wait)) + wake_up_interruptible(&cl->tx_wait); + + } else if (cb_pos->major_file_operations == MEI_READ && + MEI_READING == cl->reading_state) { + cl->reading_state = MEI_READ_COMPLETE; + if (waitqueue_active(&cl->rx_wait)) + wake_up_interruptible(&cl->rx_wait); + + } +} + +/** + * _mei_cmpl_iamthif - processes completed iamthif operation. + * + * @dev: the device structure. + * @cb_pos: callback block. + */ +static void _mei_cmpl_iamthif(struct mei_device *dev, struct mei_cl_cb *cb_pos) +{ + if (dev->iamthif_canceled != 1) { + dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE; + dev->iamthif_stall_timer = 0; + memcpy(cb_pos->response_buffer.data, + dev->iamthif_msg_buf, + dev->iamthif_msg_buf_index); + list_add_tail(&cb_pos->cb_list, + &dev->amthi_read_complete_list.mei_cb.cb_list); + dev_dbg(&dev->pdev->dev, "amthi read completed.\n"); + dev->iamthif_timer = jiffies; + dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", + dev->iamthif_timer); + } else { + mei_run_next_iamthif_cmd(dev); + } + + dev_dbg(&dev->pdev->dev, "completing amthi call back.\n"); + wake_up_interruptible(&dev->iamthif_cl.wait); +} + + +/** + * mei_irq_thread_read_amthi_message - bottom half read routine after ISR to + * handle the read amthi message data processing. + * + * @complete_list: An instance of our list structure + * @dev: the device structure + * @mei_hdr: header of amthi message + * + * returns 0 on success, <0 on failure. + */ +static int mei_irq_thread_read_amthi_message(struct mei_io_list *complete_list, + struct mei_device *dev, + struct mei_msg_hdr *mei_hdr) +{ + struct mei_cl *cl; + struct mei_cl_cb *cb; + unsigned char *buffer; + + BUG_ON(mei_hdr->me_addr != dev->iamthif_cl.me_client_id); + BUG_ON(dev->iamthif_state != MEI_IAMTHIF_READING); + + buffer = dev->iamthif_msg_buf + dev->iamthif_msg_buf_index; + BUG_ON(dev->iamthif_mtu < dev->iamthif_msg_buf_index + mei_hdr->length); + + mei_read_slots(dev, buffer, mei_hdr->length); + + dev->iamthif_msg_buf_index += mei_hdr->length; + + if (!mei_hdr->msg_complete) + return 0; + + dev_dbg(&dev->pdev->dev, + "amthi_message_buffer_index =%d\n", + mei_hdr->length); + + dev_dbg(&dev->pdev->dev, "completed amthi read.\n "); + if (!dev->iamthif_current_cb) + return -ENODEV; + + cb = dev->iamthif_current_cb; + dev->iamthif_current_cb = NULL; + + cl = (struct mei_cl *)cb->file_private; + if (!cl) + return -ENODEV; + + dev->iamthif_stall_timer = 0; + cb->information = dev->iamthif_msg_buf_index; + cb->read_time = jiffies; + if (dev->iamthif_ioctl && cl == &dev->iamthif_cl) { + /* found the iamthif cb */ + dev_dbg(&dev->pdev->dev, "complete the amthi read cb.\n "); + dev_dbg(&dev->pdev->dev, "add the amthi read cb to complete.\n "); + list_add_tail(&cb->cb_list, + &complete_list->mei_cb.cb_list); + } + return 0; +} + +/** + * _mei_irq_thread_state_ok - checks if mei header matches file private data + * + * @cl: private data of the file object + * @mei_hdr: header of mei client message + * + * returns !=0 if matches, 0 if no match. + */ +static int _mei_irq_thread_state_ok(struct mei_cl *cl, + struct mei_msg_hdr *mei_hdr) +{ + return (cl->host_client_id == mei_hdr->host_addr && + cl->me_client_id == mei_hdr->me_addr && + cl->state == MEI_FILE_CONNECTED && + MEI_READ_COMPLETE != cl->reading_state); +} + +/** + * mei_irq_thread_read_client_message - bottom half read routine after ISR to + * handle the read mei client message data processing. + * + * @complete_list: An instance of our list structure + * @dev: the device structure + * @mei_hdr: header of mei client message + * + * returns 0 on success, <0 on failure. + */ +static int mei_irq_thread_read_client_message(struct mei_io_list *complete_list, + struct mei_device *dev, + struct mei_msg_hdr *mei_hdr) +{ + struct mei_cl *cl; + struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + unsigned char *buffer = NULL; + + dev_dbg(&dev->pdev->dev, "start client msg\n"); + if (list_empty(&dev->read_list.mei_cb.cb_list)) + goto quit; + + list_for_each_entry_safe(cb_pos, cb_next, + &dev->read_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)cb_pos->file_private; + if (cl && _mei_irq_thread_state_ok(cl, mei_hdr)) { + cl->reading_state = MEI_READING; + buffer = cb_pos->response_buffer.data + cb_pos->information; + + if (cb_pos->response_buffer.size < + mei_hdr->length + cb_pos->information) { + dev_dbg(&dev->pdev->dev, "message overflow.\n"); + list_del(&cb_pos->cb_list); + return -ENOMEM; + } + if (buffer) + mei_read_slots(dev, buffer, mei_hdr->length); + + cb_pos->information += mei_hdr->length; + if (mei_hdr->msg_complete) { + cl->status = 0; + list_del(&cb_pos->cb_list); + dev_dbg(&dev->pdev->dev, + "completed read host client = %d," + "ME client = %d, " + "data length = %lu\n", + cl->host_client_id, + cl->me_client_id, + cb_pos->information); + + *(cb_pos->response_buffer.data + + cb_pos->information) = '\0'; + dev_dbg(&dev->pdev->dev, "cb_pos->res_buffer - %s\n", + cb_pos->response_buffer.data); + list_add_tail(&cb_pos->cb_list, + &complete_list->mei_cb.cb_list); + } + + break; + } + + } + +quit: + dev_dbg(&dev->pdev->dev, "message read\n"); + if (!buffer) { + mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); + dev_dbg(&dev->pdev->dev, "discarding message, header =%08x.\n", + *(u32 *) dev->rd_msg_buf); + } + + return 0; +} + +/** + * _mei_irq_thread_iamthif_read - prepares to read iamthif data. + * + * @dev: the device structure. + * @slots: free slots. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_iamthif_read(struct mei_device *dev, s32 *slots) +{ + + if (((*slots) * sizeof(u32)) < (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_flow_control))) { + return -EMSGSIZE; + } + *slots -= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_flow_control) + 3) / 4; + if (mei_send_flow_control(dev, &dev->iamthif_cl)) { + dev_dbg(&dev->pdev->dev, "iamthif flow control failed\n"); + return -EIO; + } + + dev_dbg(&dev->pdev->dev, "iamthif flow control success\n"); + dev->iamthif_state = MEI_IAMTHIF_READING; + dev->iamthif_flow_control_pending = false; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_msg_buf_size = 0; + dev->iamthif_stall_timer = IAMTHIF_STALL_TIMER; + dev->mei_host_buffer_is_empty = mei_host_buffer_is_empty(dev); + return 0; +} + +/** + * _mei_irq_thread_close - processes close related operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_close(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_client_disconnect_request))) { + *slots -= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_client_disconnect_request) + 3) / 4; + + if (mei_disconnect(dev, cl)) { + cl->status = 0; + cb_pos->information = 0; + list_move_tail(&cb_pos->cb_list, + &cmpl_list->mei_cb.cb_list); + return -EMSGSIZE; + } else { + cl->state = MEI_FILE_DISCONNECTING; + cl->status = 0; + cb_pos->information = 0; + list_move_tail(&cb_pos->cb_list, + &dev->ctrl_rd_list.mei_cb.cb_list); + cl->timer_count = MEI_CONNECT_TIMEOUT; + } + } else { + /* return the cancel routine */ + return -EBADMSG; + } + + return 0; +} + +/** + * is_treat_specially_client - checks if the message belongs + * to the file private data. + * + * @cl: private data of the file object + * @rs: connect response bus message + * + */ +static bool is_treat_specially_client(struct mei_cl *cl, + struct hbm_client_connect_response *rs) +{ + + if (cl->host_client_id == rs->host_addr && + cl->me_client_id == rs->me_addr) { + if (!rs->status) { + cl->state = MEI_FILE_CONNECTED; + cl->status = 0; + + } else { + cl->state = MEI_FILE_DISCONNECTED; + cl->status = -ENODEV; + } + cl->timer_count = 0; + + return true; + } + return false; +} + +/** + * mei_client_connect_response - connects to response irq routine + * + * @dev: the device structure + * @rs: connect response bus message + */ +static void mei_client_connect_response(struct mei_device *dev, + struct hbm_client_connect_response *rs) +{ + + struct mei_cl *cl; + struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + + dev_dbg(&dev->pdev->dev, + "connect_response:\n" + "ME Client = %d\n" + "Host Client = %d\n" + "Status = %d\n", + rs->me_addr, + rs->host_addr, + rs->status); + + /* if WD or iamthif client treat specially */ + + if (is_treat_specially_client(&(dev->wd_cl), rs)) { + dev_dbg(&dev->pdev->dev, "successfully connected to WD client.\n"); + mei_watchdog_register(dev); + + /* next step in the state maching */ + mei_host_init_iamthif(dev); + return; + } + + if (is_treat_specially_client(&(dev->iamthif_cl), rs)) { + dev->iamthif_state = MEI_IAMTHIF_IDLE; + return; + } + list_for_each_entry_safe(cb_pos, cb_next, + &dev->ctrl_rd_list.mei_cb.cb_list, cb_list) { + + cl = (struct mei_cl *)cb_pos->file_private; + if (!cl) { + list_del(&cb_pos->cb_list); + return; + } + if (MEI_IOCTL == cb_pos->major_file_operations) { + if (is_treat_specially_client(cl, rs)) { + list_del(&cb_pos->cb_list); + cl->status = 0; + cl->timer_count = 0; + break; + } + } + } +} + +/** + * mei_client_disconnect_response - disconnects from response irq routine + * + * @dev: the device structure + * @rs: disconnect response bus message + */ +static void mei_client_disconnect_response(struct mei_device *dev, + struct hbm_client_connect_response *rs) +{ + struct mei_cl *cl; + struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + + dev_dbg(&dev->pdev->dev, + "disconnect_response:\n" + "ME Client = %d\n" + "Host Client = %d\n" + "Status = %d\n", + rs->me_addr, + rs->host_addr, + rs->status); + + list_for_each_entry_safe(cb_pos, cb_next, + &dev->ctrl_rd_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)cb_pos->file_private; + + if (!cl) { + list_del(&cb_pos->cb_list); + return; + } + + dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in ctrl_rd_list.\n"); + if (cl->host_client_id == rs->host_addr && + cl->me_client_id == rs->me_addr) { + + list_del(&cb_pos->cb_list); + if (!rs->status) + cl->state = MEI_FILE_DISCONNECTED; + + cl->status = 0; + cl->timer_count = 0; + break; + } + } +} + +/** + * same_flow_addr - tells if they have the same address. + * + * @file: private data of the file object. + * @flow: flow control. + * + * returns !=0, same; 0,not. + */ +static int same_flow_addr(struct mei_cl *cl, struct hbm_flow_control *flow) +{ + return (cl->host_client_id == flow->host_addr && + cl->me_client_id == flow->me_addr); +} + +/** + * add_single_flow_creds - adds single buffer credentials. + * + * @file: private data ot the file object. + * @flow: flow control. + */ +static void add_single_flow_creds(struct mei_device *dev, + struct hbm_flow_control *flow) +{ + struct mei_me_client *client; + int i; + + for (i = 0; i < dev->me_clients_num; i++) { + client = &dev->me_clients[i]; + if (client && flow->me_addr == client->client_id) { + if (client->props.single_recv_buf) { + client->mei_flow_ctrl_creds++; + dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n", + flow->me_addr); + dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n", + client->mei_flow_ctrl_creds); + } else { + BUG(); /* error in flow control */ + } + } + } +} + +/** + * mei_client_flow_control_response - flow control response irq routine + * + * @dev: the device structure + * @flow_control: flow control response bus message + */ +static void mei_client_flow_control_response(struct mei_device *dev, + struct hbm_flow_control *flow_control) +{ + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + + if (!flow_control->host_addr) { + /* single receive buffer */ + add_single_flow_creds(dev, flow_control); + } else { + /* normal connection */ + list_for_each_entry_safe(cl_pos, cl_next, + &dev->file_list, link) { + dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in file_list\n"); + + dev_dbg(&dev->pdev->dev, "cl of host client %d ME client %d.\n", + cl_pos->host_client_id, + cl_pos->me_client_id); + dev_dbg(&dev->pdev->dev, "flow ctrl msg for host %d ME %d.\n", + flow_control->host_addr, + flow_control->me_addr); + if (same_flow_addr(cl_pos, flow_control)) { + dev_dbg(&dev->pdev->dev, "recv ctrl msg for host %d ME %d.\n", + flow_control->host_addr, + flow_control->me_addr); + cl_pos->mei_flow_ctrl_creds++; + dev_dbg(&dev->pdev->dev, "flow control credentials = %d.\n", + cl_pos->mei_flow_ctrl_creds); + break; + } + } + } +} + +/** + * same_disconn_addr - tells if they have the same address + * + * @file: private data of the file object. + * @disconn: disconnection request. + * + * returns !=0, same; 0,not. + */ +static int same_disconn_addr(struct mei_cl *cl, + struct hbm_client_disconnect_request *disconn) +{ + return (cl->host_client_id == disconn->host_addr && + cl->me_client_id == disconn->me_addr); +} + +/** + * mei_client_disconnect_request - disconnects from request irq routine + * + * @dev: the device structure. + * @disconnect_req: disconnect request bus message. + */ +static void mei_client_disconnect_request(struct mei_device *dev, + struct hbm_client_disconnect_request *disconnect_req) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_client_connect_response *disconnect_res; + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if (same_disconn_addr(cl_pos, disconnect_req)) { + dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n", + disconnect_req->host_addr, + disconnect_req->me_addr); + cl_pos->state = MEI_FILE_DISCONNECTED; + cl_pos->timer_count = 0; + if (cl_pos == &dev->wd_cl) { + dev->wd_due_counter = 0; + dev->wd_pending = false; + } else if (cl_pos == &dev->iamthif_cl) + dev->iamthif_timer = 0; + + /* prepare disconnect response */ + mei_hdr = + (struct mei_msg_hdr *) &dev->ext_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = + sizeof(struct hbm_client_connect_response); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + disconnect_res = + (struct hbm_client_connect_response *) + &dev->ext_msg_buf[1]; + disconnect_res->host_addr = cl_pos->host_client_id; + disconnect_res->me_addr = cl_pos->me_client_id; + disconnect_res->hbm_cmd = CLIENT_DISCONNECT_RES_CMD; + disconnect_res->status = 0; + dev->extra_write_index = 2; + break; + } + } +} + + +/** + * mei_irq_thread_read_bus_message - bottom half read routine after ISR to + * handle the read bus message cmd processing. + * + * @dev: the device structure + * @mei_hdr: header of bus message + */ +static void mei_irq_thread_read_bus_message(struct mei_device *dev, + struct mei_msg_hdr *mei_hdr) +{ + struct mei_bus_message *mei_msg; + struct hbm_host_version_response *version_res; + struct hbm_client_connect_response *connect_res; + struct hbm_client_connect_response *disconnect_res; + struct hbm_flow_control *flow_control; + struct hbm_props_response *props_res; + struct hbm_host_enum_response *enum_res; + struct hbm_client_disconnect_request *disconnect_req; + struct hbm_host_stop_request *host_stop_req; + int res; + + + /* read the message to our buffer */ + BUG_ON(mei_hdr->length >= sizeof(dev->rd_msg_buf)); + mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); + mei_msg = (struct mei_bus_message *)dev->rd_msg_buf; + + switch (mei_msg->hbm_cmd) { + case HOST_START_RES_CMD: + version_res = (struct hbm_host_version_response *) mei_msg; + if (version_res->host_version_supported) { + dev->version.major_version = HBM_MAJOR_VERSION; + dev->version.minor_version = HBM_MINOR_VERSION; + if (dev->mei_state == MEI_INIT_CLIENTS && + dev->init_clients_state == MEI_START_MESSAGE) { + dev->init_clients_timer = 0; + mei_host_enum_clients_message(dev); + } else { + dev->recvd_msg = false; + dev_dbg(&dev->pdev->dev, "IMEI reset due to received host start response bus message.\n"); + mei_reset(dev, 1); + return; + } + } else { + dev->version = version_res->me_max_version; + /* send stop message */ + mei_hdr = (struct mei_msg_hdr *)&dev->wr_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_host_stop_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + host_stop_req = (struct hbm_host_stop_request *) + &dev->wr_msg_buf[1]; + + memset(host_stop_req, + 0, + sizeof(struct hbm_host_stop_request)); + host_stop_req->hbm_cmd = HOST_STOP_REQ_CMD; + host_stop_req->reason = DRIVER_STOP_REQUEST; + mei_write_message(dev, mei_hdr, + (unsigned char *) (host_stop_req), + mei_hdr->length); + dev_dbg(&dev->pdev->dev, "version mismatch.\n"); + return; + } + + dev->recvd_msg = true; + dev_dbg(&dev->pdev->dev, "host start response message received.\n"); + break; + + case CLIENT_CONNECT_RES_CMD: + connect_res = + (struct hbm_client_connect_response *) mei_msg; + mei_client_connect_response(dev, connect_res); + dev_dbg(&dev->pdev->dev, "client connect response message received.\n"); + wake_up(&dev->wait_recvd_msg); + break; + + case CLIENT_DISCONNECT_RES_CMD: + disconnect_res = + (struct hbm_client_connect_response *) mei_msg; + mei_client_disconnect_response(dev, disconnect_res); + dev_dbg(&dev->pdev->dev, "client disconnect response message received.\n"); + wake_up(&dev->wait_recvd_msg); + break; + + case MEI_FLOW_CONTROL_CMD: + flow_control = (struct hbm_flow_control *) mei_msg; + mei_client_flow_control_response(dev, flow_control); + dev_dbg(&dev->pdev->dev, "client flow control response message received.\n"); + break; + + case HOST_CLIENT_PROPERTIES_RES_CMD: + props_res = (struct hbm_props_response *)mei_msg; + if (props_res->status || !dev->me_clients) { + dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message wrong status.\n"); + mei_reset(dev, 1); + return; + } + if (dev->me_clients[dev->me_client_presentation_num] + .client_id == props_res->address) { + + dev->me_clients[dev->me_client_presentation_num].props + = props_res->client_properties; + + if (dev->mei_state == MEI_INIT_CLIENTS && + dev->init_clients_state == + MEI_CLIENT_PROPERTIES_MESSAGE) { + dev->me_client_index++; + dev->me_client_presentation_num++; + + /** Send Client Properties request **/ + res = mei_host_client_properties(dev); + if (res < 0) { + dev_dbg(&dev->pdev->dev, "mei_host_client_properties() failed"); + return; + } else if (!res) { + /* + * No more clients to send to. + * Clear Map for indicating now ME clients + * with associated host client + */ + bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); + dev->open_handle_count = 0; + + /* + * Reserving the first three client IDs + * Client Id 0 - Reserved for MEI Bus Message communications + * Client Id 1 - Reserved for Watchdog + * Client ID 2 - Reserved for AMTHI + */ + bitmap_set(dev->host_clients_map, 0, 3); + dev->mei_state = MEI_ENABLED; + + /* if wd initialization fails, initialization the AMTHI client, + * otherwise the AMTHI client will be initialized after the WD client connect response + * will be received + */ + if (mei_wd_host_init(dev)) + mei_host_init_iamthif(dev); + } + + } else { + dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message"); + mei_reset(dev, 1); + return; + } + } else { + dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message for wrong client ID\n"); + mei_reset(dev, 1); + return; + } + break; + + case HOST_ENUM_RES_CMD: + enum_res = (struct hbm_host_enum_response *) mei_msg; + memcpy(dev->me_clients_map, enum_res->valid_addresses, 32); + if (dev->mei_state == MEI_INIT_CLIENTS && + dev->init_clients_state == MEI_ENUM_CLIENTS_MESSAGE) { + dev->init_clients_timer = 0; + dev->me_client_presentation_num = 0; + dev->me_client_index = 0; + mei_allocate_me_clients_storage(dev); + dev->init_clients_state = + MEI_CLIENT_PROPERTIES_MESSAGE; + mei_host_client_properties(dev); + } else { + dev_dbg(&dev->pdev->dev, "reset due to received host enumeration clients response bus message.\n"); + mei_reset(dev, 1); + return; + } + break; + + case HOST_STOP_RES_CMD: + dev->mei_state = MEI_DISABLED; + dev_dbg(&dev->pdev->dev, "resetting because of FW stop response.\n"); + mei_reset(dev, 1); + break; + + case CLIENT_DISCONNECT_REQ_CMD: + /* search for client */ + disconnect_req = + (struct hbm_client_disconnect_request *) mei_msg; + mei_client_disconnect_request(dev, disconnect_req); + break; + + case ME_STOP_REQ_CMD: + /* prepare stop request */ + mei_hdr = (struct mei_msg_hdr *) &dev->ext_msg_buf[0]; + mei_hdr->host_addr = 0; + mei_hdr->me_addr = 0; + mei_hdr->length = sizeof(struct hbm_host_stop_request); + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + host_stop_req = + (struct hbm_host_stop_request *) &dev->ext_msg_buf[1]; + memset(host_stop_req, 0, sizeof(struct hbm_host_stop_request)); + host_stop_req->hbm_cmd = HOST_STOP_REQ_CMD; + host_stop_req->reason = DRIVER_STOP_REQUEST; + host_stop_req->reserved[0] = 0; + host_stop_req->reserved[1] = 0; + dev->extra_write_index = 2; + break; + + default: + BUG(); + break; + + } +} + + +/** + * _mei_hb_read - processes read related operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_read(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_flow_control))) { + /* return the cancel routine */ + list_del(&cb_pos->cb_list); + return -EBADMSG; + } + + *slots -= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_flow_control) + 3) / 4; + if (mei_send_flow_control(dev, cl)) { + cl->status = -ENODEV; + cb_pos->information = 0; + list_move_tail(&cb_pos->cb_list, &cmpl_list->mei_cb.cb_list); + return -ENODEV; + } + list_move_tail(&cb_pos->cb_list, &dev->read_list.mei_cb.cb_list); + + return 0; +} + + +/** + * _mei_irq_thread_ioctl - processes ioctl related operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_client_connect_request))) { + cl->state = MEI_FILE_CONNECTING; + *slots -= (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_client_connect_request) + 3) / 4; + if (mei_connect(dev, cl)) { + cl->status = -ENODEV; + cb_pos->information = 0; + list_del(&cb_pos->cb_list); + return -ENODEV; + } else { + list_move_tail(&cb_pos->cb_list, + &dev->ctrl_rd_list.mei_cb.cb_list); + cl->timer_count = MEI_CONNECT_TIMEOUT; + } + } else { + /* return the cancel routine */ + list_del(&cb_pos->cb_list); + return -EBADMSG; + } + + return 0; +} + +/** + * _mei_irq_thread_cmpl - processes completed and no-iamthif operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_cmpl(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + struct mei_msg_hdr *mei_hdr; + + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + (cb_pos->request_buffer.size - + cb_pos->information))) { + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = cb_pos->request_buffer.size - + cb_pos->information; + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + dev_dbg(&dev->pdev->dev, "cb_pos->request_buffer.size =%d" + "mei_hdr->msg_complete = %d\n", + cb_pos->request_buffer.size, + mei_hdr->msg_complete); + dev_dbg(&dev->pdev->dev, "cb_pos->information =%lu\n", + cb_pos->information); + dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", + mei_hdr->length); + *slots -= (sizeof(struct mei_msg_hdr) + + mei_hdr->length + 3) / 4; + if (mei_write_message(dev, mei_hdr, + (unsigned char *) + (cb_pos->request_buffer.data + + cb_pos->information), + mei_hdr->length)) { + cl->status = -ENODEV; + list_move_tail(&cb_pos->cb_list, + &cmpl_list->mei_cb.cb_list); + return -ENODEV; + } else { + if (mei_flow_ctrl_reduce(dev, cl)) + return -ENODEV; + cl->status = 0; + cb_pos->information += mei_hdr->length; + list_move_tail(&cb_pos->cb_list, + &dev->write_waiting_list.mei_cb.cb_list); + } + } else if (*slots == ((dev->host_hw_state & H_CBD) >> 24)) { + /* buffer is still empty */ + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = + (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); + mei_hdr->msg_complete = 0; + mei_hdr->reserved = 0; + + (*slots) -= (sizeof(struct mei_msg_hdr) + + mei_hdr->length + 3) / 4; + if (mei_write_message(dev, mei_hdr, + (unsigned char *) + (cb_pos->request_buffer.data + + cb_pos->information), + mei_hdr->length)) { + cl->status = -ENODEV; + list_move_tail(&cb_pos->cb_list, + &cmpl_list->mei_cb.cb_list); + return -ENODEV; + } else { + cb_pos->information += mei_hdr->length; + dev_dbg(&dev->pdev->dev, + "cb_pos->request_buffer.size =%d" + " mei_hdr->msg_complete = %d\n", + cb_pos->request_buffer.size, + mei_hdr->msg_complete); + dev_dbg(&dev->pdev->dev, "cb_pos->information =%lu\n", + cb_pos->information); + dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", + mei_hdr->length); + } + return -EMSGSIZE; + } else { + return -EBADMSG; + } + + return 0; +} + +/** + * _mei_irq_thread_cmpl_iamthif - processes completed iamthif operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +static int _mei_irq_thread_cmpl_iamthif(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_io_list *cmpl_list) +{ + struct mei_msg_hdr *mei_hdr; + + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + dev->iamthif_msg_buf_size - + dev->iamthif_msg_buf_index)) { + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = dev->iamthif_msg_buf_size - + dev->iamthif_msg_buf_index; + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + *slots -= (sizeof(struct mei_msg_hdr) + + mei_hdr->length + 3) / 4; + + if (mei_write_message(dev, mei_hdr, + (dev->iamthif_msg_buf + + dev->iamthif_msg_buf_index), + mei_hdr->length)) { + dev->iamthif_state = MEI_IAMTHIF_IDLE; + cl->status = -ENODEV; + list_del(&cb_pos->cb_list); + return -ENODEV; + } else { + if (mei_flow_ctrl_reduce(dev, cl)) + return -ENODEV; + dev->iamthif_msg_buf_index += mei_hdr->length; + cb_pos->information = dev->iamthif_msg_buf_index; + cl->status = 0; + dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; + dev->iamthif_flow_control_pending = true; + /* save iamthif cb sent to amthi client */ + dev->iamthif_current_cb = cb_pos; + list_move_tail(&cb_pos->cb_list, + &dev->write_waiting_list.mei_cb.cb_list); + + } + } else if (*slots == ((dev->host_hw_state & H_CBD) >> 24)) { + /* buffer is still empty */ + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = + (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); + mei_hdr->msg_complete = 0; + mei_hdr->reserved = 0; + + *slots -= (sizeof(struct mei_msg_hdr) + + mei_hdr->length + 3) / 4; + + if (mei_write_message(dev, mei_hdr, + (dev->iamthif_msg_buf + + dev->iamthif_msg_buf_index), + mei_hdr->length)) { + cl->status = -ENODEV; + list_del(&cb_pos->cb_list); + } else { + dev->iamthif_msg_buf_index += mei_hdr->length; + } + return -EMSGSIZE; + } else { + return -EBADMSG; + } + + return 0; +} + +/** + * mei_irq_thread_read_handler - bottom half read routine after ISR to + * handle the read processing. + * + * @cmpl_list: An instance of our list structure + * @dev: the device structure + * @slots: slots to read. + * + * returns 0 on success, <0 on failure. + */ +static int mei_irq_thread_read_handler(struct mei_io_list *cmpl_list, + struct mei_device *dev, + s32 *slots) +{ + struct mei_msg_hdr *mei_hdr; + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + int ret = 0; + + if (!dev->rd_msg_hdr) { + dev->rd_msg_hdr = mei_mecbrw_read(dev); + dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); + (*slots)--; + dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); + } + mei_hdr = (struct mei_msg_hdr *) &dev->rd_msg_hdr; + dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", mei_hdr->length); + + if (mei_hdr->reserved || !dev->rd_msg_hdr) { + dev_dbg(&dev->pdev->dev, "corrupted message header.\n"); + ret = -EBADMSG; + goto end; + } + + if (mei_hdr->host_addr || mei_hdr->me_addr) { + list_for_each_entry_safe(cl_pos, cl_next, + &dev->file_list, link) { + dev_dbg(&dev->pdev->dev, + "list_for_each_entry_safe read host" + " client = %d, ME client = %d\n", + cl_pos->host_client_id, + cl_pos->me_client_id); + if (cl_pos->host_client_id == mei_hdr->host_addr && + cl_pos->me_client_id == mei_hdr->me_addr) + break; + } + + if (&cl_pos->link == &dev->file_list) { + dev_dbg(&dev->pdev->dev, "corrupted message header\n"); + ret = -EBADMSG; + goto end; + } + } + if (((*slots) * sizeof(u32)) < mei_hdr->length) { + dev_dbg(&dev->pdev->dev, + "we can't read the message slots =%08x.\n", + *slots); + /* we can't read the message */ + ret = -ERANGE; + goto end; + } + + /* decide where to read the message too */ + if (!mei_hdr->host_addr) { + dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_bus_message.\n"); + mei_irq_thread_read_bus_message(dev, mei_hdr); + dev_dbg(&dev->pdev->dev, "end mei_irq_thread_read_bus_message.\n"); + } else if (mei_hdr->host_addr == dev->iamthif_cl.host_client_id && + (MEI_FILE_CONNECTED == dev->iamthif_cl.state) && + (dev->iamthif_state == MEI_IAMTHIF_READING)) { + dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_iamthif_message.\n"); + dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", + mei_hdr->length); + ret = mei_irq_thread_read_amthi_message(cmpl_list, + dev, mei_hdr); + if (ret) + goto end; + + } else { + dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_client_message.\n"); + ret = mei_irq_thread_read_client_message(cmpl_list, + dev, mei_hdr); + if (ret) + goto end; + + } + + /* reset the number of slots and header */ + *slots = mei_count_full_read_slots(dev); + dev->rd_msg_hdr = 0; + + if (*slots == -EOVERFLOW) { + /* overflow - reset */ + dev_dbg(&dev->pdev->dev, "resetting due to slots overflow.\n"); + /* set the event since message has been read */ + ret = -ERANGE; + goto end; + } +end: + return ret; +} + + +/** + * mei_irq_thread_write_handler - bottom half write routine after + * ISR to handle the write processing. + * + * @cmpl_list: An instance of our list structure + * @dev: the device structure + * @slots: slots to write. + * + * returns 0 on success, <0 on failure. + */ +static int mei_irq_thread_write_handler(struct mei_io_list *cmpl_list, + struct mei_device *dev, + s32 *slots) +{ + + struct mei_cl *cl; + struct mei_cl_cb *pos = NULL, *next = NULL; + struct mei_io_list *list; + int ret; + + if (!mei_host_buffer_is_empty(dev)) { + dev_dbg(&dev->pdev->dev, "host buffer is not empty.\n"); + return 0; + } + *slots = mei_count_empty_write_slots(dev); + /* complete all waiting for write CB */ + dev_dbg(&dev->pdev->dev, "complete all waiting for write cb.\n"); + + list = &dev->write_waiting_list; + list_for_each_entry_safe(pos, next, + &list->mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)pos->file_private; + if (cl == NULL) + continue; + + cl->status = 0; + list_del(&pos->cb_list); + if (MEI_WRITING == cl->writing_state && + (pos->major_file_operations == MEI_WRITE) && + (cl != &dev->iamthif_cl)) { + dev_dbg(&dev->pdev->dev, + "MEI WRITE COMPLETE\n"); + cl->writing_state = MEI_WRITE_COMPLETE; + list_add_tail(&pos->cb_list, + &cmpl_list->mei_cb.cb_list); + } + if (cl == &dev->iamthif_cl) { + dev_dbg(&dev->pdev->dev, "check iamthif flow control.\n"); + if (dev->iamthif_flow_control_pending) { + ret = _mei_irq_thread_iamthif_read( + dev, slots); + if (ret) + return ret; + } + } + } + + if (dev->stop && !dev->wd_pending) { + dev->wd_stopped = true; + wake_up_interruptible(&dev->wait_stop_wd); + return 0; + } + + if (dev->extra_write_index) { + dev_dbg(&dev->pdev->dev, "extra_write_index =%d.\n", + dev->extra_write_index); + mei_write_message(dev, + (struct mei_msg_hdr *) &dev->ext_msg_buf[0], + (unsigned char *) &dev->ext_msg_buf[1], + (dev->extra_write_index - 1) * sizeof(u32)); + *slots -= dev->extra_write_index; + dev->extra_write_index = 0; + } + if (dev->mei_state == MEI_ENABLED) { + if (dev->wd_pending && + mei_flow_ctrl_creds(dev, &dev->wd_cl) > 0) { + if (mei_wd_send(dev)) + dev_dbg(&dev->pdev->dev, "wd send failed.\n"); + else + if (mei_flow_ctrl_reduce(dev, &dev->wd_cl)) + return -ENODEV; + + dev->wd_pending = false; + + if (dev->wd_timeout) { + *slots -= (sizeof(struct mei_msg_hdr) + + MEI_START_WD_DATA_SIZE + 3) / 4; + dev->wd_due_counter = 2; + } else { + *slots -= (sizeof(struct mei_msg_hdr) + + MEI_WD_PARAMS_SIZE + 3) / 4; + dev->wd_due_counter = 0; + } + + } + } + if (dev->stop) + return -ENODEV; + + /* complete control write list CB */ + dev_dbg(&dev->pdev->dev, "complete control write list cb.\n"); + list_for_each_entry_safe(pos, next, + &dev->ctrl_wr_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *) pos->file_private; + if (!cl) { + list_del(&pos->cb_list); + return -ENODEV; + } + switch (pos->major_file_operations) { + case MEI_CLOSE: + /* send disconnect message */ + ret = _mei_irq_thread_close(dev, slots, pos, cl, cmpl_list); + if (ret) + return ret; + + break; + case MEI_READ: + /* send flow control message */ + ret = _mei_irq_thread_read(dev, slots, pos, cl, cmpl_list); + if (ret) + return ret; + + break; + case MEI_IOCTL: + /* connect message */ + if (mei_other_client_is_connecting(dev, cl)) + continue; + ret = _mei_irq_thread_ioctl(dev, slots, pos, cl, cmpl_list); + if (ret) + return ret; + + break; + + default: + BUG(); + } + + } + /* complete write list CB */ + dev_dbg(&dev->pdev->dev, "complete write list cb.\n"); + list_for_each_entry_safe(pos, next, + &dev->write_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)pos->file_private; + if (cl == NULL) + continue; + + if (cl != &dev->iamthif_cl) { + if (!mei_flow_ctrl_creds(dev, cl)) { + dev_dbg(&dev->pdev->dev, + "No flow control" + " credentials for client" + " %d, not sending.\n", + cl->host_client_id); + continue; + } + ret = _mei_irq_thread_cmpl(dev, slots, + pos, + cl, cmpl_list); + if (ret) + return ret; + + } else if (cl == &dev->iamthif_cl) { + /* IAMTHIF IOCTL */ + dev_dbg(&dev->pdev->dev, "complete amthi write cb.\n"); + if (!mei_flow_ctrl_creds(dev, cl)) { + dev_dbg(&dev->pdev->dev, + "No flow control" + " credentials for amthi" + " client %d.\n", + cl->host_client_id); + continue; + } + ret = _mei_irq_thread_cmpl_iamthif(dev, + slots, + pos, + cl, + cmpl_list); + if (ret) + return ret; + + } + + } + return 0; +} + + + +/** + * mei_timer - timer function. + * + * @work: pointer to the work_struct structure + * + * NOTE: This function is called by timer interrupt work + */ +void mei_timer(struct work_struct *work) +{ + unsigned long timeout; + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + struct list_head *amthi_complete_list = NULL; + struct mei_cl_cb *cb_pos = NULL; + struct mei_cl_cb *cb_next = NULL; + + struct mei_device *dev = container_of(work, + struct mei_device, timer_work.work); + + + mutex_lock(&dev->device_lock); + if (dev->mei_state != MEI_ENABLED) { + if (dev->mei_state == MEI_INIT_CLIENTS) { + if (dev->init_clients_timer) { + if (--dev->init_clients_timer == 0) { + dev_dbg(&dev->pdev->dev, "IMEI reset due to init clients timeout ,init clients state = %d.\n", + dev->init_clients_state); + mei_reset(dev, 1); + } + } + } + goto out; + } + /*** connect/disconnect timeouts ***/ + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if (cl_pos->timer_count) { + if (--cl_pos->timer_count == 0) { + dev_dbg(&dev->pdev->dev, "HECI reset due to connect/disconnect timeout.\n"); + mei_reset(dev, 1); + goto out; + } + } + } + + if (dev->iamthif_stall_timer) { + if (--dev->iamthif_stall_timer == 0) { + dev_dbg(&dev->pdev->dev, "resetting because of hang to amthi.\n"); + mei_reset(dev, 1); + dev->iamthif_msg_buf_size = 0; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = true; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->iamthif_timer = 0; + + if (dev->iamthif_current_cb) + mei_free_cb_private(dev->iamthif_current_cb); + + dev->iamthif_file_object = NULL; + dev->iamthif_current_cb = NULL; + mei_run_next_iamthif_cmd(dev); + } + } + + if (dev->iamthif_timer) { + + timeout = dev->iamthif_timer + + msecs_to_jiffies(IAMTHIF_READ_TIMER); + + dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", + dev->iamthif_timer); + dev_dbg(&dev->pdev->dev, "timeout = %ld\n", timeout); + dev_dbg(&dev->pdev->dev, "jiffies = %ld\n", jiffies); + if (time_after(jiffies, timeout)) { + /* + * User didn't read the AMTHI data on time (15sec) + * freeing AMTHI for other requests + */ + + dev_dbg(&dev->pdev->dev, "freeing AMTHI for other requests\n"); + + amthi_complete_list = &dev->amthi_read_complete_list. + mei_cb.cb_list; + + list_for_each_entry_safe(cb_pos, cb_next, amthi_complete_list, cb_list) { + + cl_pos = cb_pos->file_object->private_data; + + /* Finding the AMTHI entry. */ + if (cl_pos == &dev->iamthif_cl) + list_del(&cb_pos->cb_list); + } + if (dev->iamthif_current_cb) + mei_free_cb_private(dev->iamthif_current_cb); + + dev->iamthif_file_object->private_data = NULL; + dev->iamthif_file_object = NULL; + dev->iamthif_current_cb = NULL; + dev->iamthif_timer = 0; + mei_run_next_iamthif_cmd(dev); + + } + } +out: + schedule_delayed_work(&dev->timer_work, 2 * HZ); + mutex_unlock(&dev->device_lock); +} + +/** + * mei_interrupt_thread_handler - function called after ISR to handle the interrupt + * processing. + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + * + */ +irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + struct mei_io_list complete_list; + struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + struct mei_cl *cl; + s32 slots; + int rets; + bool bus_message_received; + + + dev_dbg(&dev->pdev->dev, "function called after ISR to handle the interrupt processing.\n"); + /* initialize our complete list */ + mutex_lock(&dev->device_lock); + mei_io_list_init(&complete_list); + dev->host_hw_state = mei_hcsr_read(dev); + + /* Ack the interrupt here + * In case of MSI we don't go through the quick handler */ + if (pci_dev_msi_enabled(dev->pdev)) + mei_reg_write(dev, H_CSR, dev->host_hw_state); + + dev->me_hw_state = mei_mecsr_read(dev); + + /* check if ME wants a reset */ + if ((dev->me_hw_state & ME_RDY_HRA) == 0 && + dev->mei_state != MEI_RESETING && + dev->mei_state != MEI_INITIALIZING) { + dev_dbg(&dev->pdev->dev, "FW not ready.\n"); + mei_reset(dev, 1); + mutex_unlock(&dev->device_lock); + return IRQ_HANDLED; + } + + /* check if we need to start the dev */ + if ((dev->host_hw_state & H_RDY) == 0) { + if ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA) { + dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); + dev->host_hw_state |= (H_IE | H_IG | H_RDY); + mei_hcsr_set(dev); + dev->mei_state = MEI_INIT_CLIENTS; + dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); + /* link is established + * start sending messages. + */ + mei_host_start_message(dev); + mutex_unlock(&dev->device_lock); + return IRQ_HANDLED; + } else { + dev_dbg(&dev->pdev->dev, "FW not ready.\n"); + mutex_unlock(&dev->device_lock); + return IRQ_HANDLED; + } + } + /* check slots available for reading */ + slots = mei_count_full_read_slots(dev); + dev_dbg(&dev->pdev->dev, "slots =%08x extra_write_index =%08x.\n", + slots, dev->extra_write_index); + while (slots > 0 && !dev->extra_write_index) { + dev_dbg(&dev->pdev->dev, "slots =%08x extra_write_index =%08x.\n", + slots, dev->extra_write_index); + dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_handler.\n"); + rets = mei_irq_thread_read_handler(&complete_list, dev, &slots); + if (rets) + goto end; + } + rets = mei_irq_thread_write_handler(&complete_list, dev, &slots); +end: + dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); + dev->host_hw_state = mei_hcsr_read(dev); + dev->mei_host_buffer_is_empty = mei_host_buffer_is_empty(dev); + + bus_message_received = false; + if (dev->recvd_msg && waitqueue_active(&dev->wait_recvd_msg)) { + dev_dbg(&dev->pdev->dev, "received waiting bus message\n"); + bus_message_received = true; + } + mutex_unlock(&dev->device_lock); + if (bus_message_received) { + dev_dbg(&dev->pdev->dev, "wake up dev->wait_recvd_msg\n"); + wake_up_interruptible(&dev->wait_recvd_msg); + bus_message_received = false; + } + if (list_empty(&complete_list.mei_cb.cb_list)) + return IRQ_HANDLED; + + + list_for_each_entry_safe(cb_pos, cb_next, + &complete_list.mei_cb.cb_list, cb_list) { + cl = (struct mei_cl *)cb_pos->file_private; + list_del(&cb_pos->cb_list); + if (cl) { + if (cl != &dev->iamthif_cl) { + dev_dbg(&dev->pdev->dev, "completing call back.\n"); + _mei_cmpl(cl, cb_pos); + cb_pos = NULL; + } else if (cl == &dev->iamthif_cl) { + _mei_cmpl_iamthif(dev, cb_pos); + } + } + } + return IRQ_HANDLED; +} diff --git a/drivers/staging/mei/iorw.c b/drivers/staging/mei/iorw.c new file mode 100644 index 00000000..0a80dc4e --- /dev/null +++ b/drivers/staging/mei/iorw.c @@ -0,0 +1,590 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "mei_dev.h" +#include "hw.h" +#include "mei.h" +#include "interface.h" + + + +/** + * mei_ioctl_connect_client - the connect to fw client IOCTL function + * + * @dev: the device structure + * @data: IOCTL connect data, input and output parameters + * @file: private data of the file object + * + * Locking: called under "dev->device_lock" lock + * + * returns 0 on success, <0 on failure. + */ +int mei_ioctl_connect_client(struct file *file, + struct mei_connect_client_data *data) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + struct mei_client *client; + struct mei_cl *cl; + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + long timeout = CONNECT_TIMEOUT; + int i; + int err; + int rets; + + cl = file->private_data; + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + dev_dbg(&dev->pdev->dev, "mei_ioctl_connect_client() Entry\n"); + + + /* buffered ioctl cb */ + cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!cb) { + rets = -ENOMEM; + goto end; + } + INIT_LIST_HEAD(&cb->cb_list); + + cb->major_file_operations = MEI_IOCTL; + + if (dev->mei_state != MEI_ENABLED) { + rets = -ENODEV; + goto end; + } + if (cl->state != MEI_FILE_INITIALIZING && + cl->state != MEI_FILE_DISCONNECTED) { + rets = -EBUSY; + goto end; + } + + /* find ME client we're trying to connect to */ + i = mei_find_me_client_index(dev, data->in_client_uuid); + if (i >= 0 && !dev->me_clients[i].props.fixed_address) { + cl->me_client_id = dev->me_clients[i].client_id; + cl->state = MEI_FILE_CONNECTING; + } + + dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", + cl->me_client_id); + dev_dbg(&dev->pdev->dev, "FW Client - Protocol Version = %d\n", + dev->me_clients[i].props.protocol_version); + dev_dbg(&dev->pdev->dev, "FW Client - Max Msg Len = %d\n", + dev->me_clients[i].props.max_msg_length); + + /* if we're connecting to amthi client then we will use the + * existing connection + */ + if (uuid_le_cmp(data->in_client_uuid, mei_amthi_guid) == 0) { + dev_dbg(&dev->pdev->dev, "FW Client is amthi\n"); + if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { + rets = -ENODEV; + goto end; + } + clear_bit(cl->host_client_id, dev->host_clients_map); + list_for_each_entry_safe(cl_pos, cl_next, + &dev->file_list, link) { + if (mei_cl_cmp_id(cl, cl_pos)) { + dev_dbg(&dev->pdev->dev, + "remove file private data node host" + " client = %d, ME client = %d.\n", + cl_pos->host_client_id, + cl_pos->me_client_id); + list_del(&cl_pos->link); + } + + } + dev_dbg(&dev->pdev->dev, "free file private data memory.\n"); + kfree(cl); + + cl = NULL; + file->private_data = &dev->iamthif_cl; + + client = &data->out_client_properties; + client->max_msg_length = + dev->me_clients[i].props.max_msg_length; + client->protocol_version = + dev->me_clients[i].props.protocol_version; + rets = dev->iamthif_cl.status; + + goto end; + } + + if (cl->state != MEI_FILE_CONNECTING) { + rets = -ENODEV; + goto end; + } + + + /* prepare the output buffer */ + client = &data->out_client_properties; + client->max_msg_length = dev->me_clients[i].props.max_msg_length; + client->protocol_version = dev->me_clients[i].props.protocol_version; + dev_dbg(&dev->pdev->dev, "Can connect?\n"); + if (dev->mei_host_buffer_is_empty + && !mei_other_client_is_connecting(dev, cl)) { + dev_dbg(&dev->pdev->dev, "Sending Connect Message\n"); + dev->mei_host_buffer_is_empty = false; + if (mei_connect(dev, cl)) { + dev_dbg(&dev->pdev->dev, "Sending connect message - failed\n"); + rets = -ENODEV; + goto end; + } else { + dev_dbg(&dev->pdev->dev, "Sending connect message - succeeded\n"); + cl->timer_count = MEI_CONNECT_TIMEOUT; + cb->file_private = cl; + list_add_tail(&cb->cb_list, + &dev->ctrl_rd_list.mei_cb. + cb_list); + } + + + } else { + dev_dbg(&dev->pdev->dev, "Queuing the connect request due to device busy\n"); + cb->file_private = cl; + dev_dbg(&dev->pdev->dev, "add connect cb to control write list.\n"); + list_add_tail(&cb->cb_list, + &dev->ctrl_wr_list.mei_cb.cb_list); + } + mutex_unlock(&dev->device_lock); + err = wait_event_timeout(dev->wait_recvd_msg, + (MEI_FILE_CONNECTED == cl->state || + MEI_FILE_DISCONNECTED == cl->state), + timeout * HZ); + + mutex_lock(&dev->device_lock); + if (MEI_FILE_CONNECTED == cl->state) { + dev_dbg(&dev->pdev->dev, "successfully connected to FW client.\n"); + rets = cl->status; + goto end; + } else { + dev_dbg(&dev->pdev->dev, "failed to connect to FW client.cl->state = %d.\n", + cl->state); + if (!err) { + dev_dbg(&dev->pdev->dev, + "wait_event_interruptible_timeout failed on client" + " connect message fw response message.\n"); + } + rets = -EFAULT; + + mei_io_list_flush(&dev->ctrl_rd_list, cl); + mei_io_list_flush(&dev->ctrl_wr_list, cl); + goto end; + } + rets = 0; +end: + dev_dbg(&dev->pdev->dev, "free connect cb memory."); + kfree(cb); + return rets; +} + +/** + * find_amthi_read_list_entry - finds a amthilist entry for current file + * + * @dev: the device structure + * @file: pointer to file object + * + * returns returned a list entry on success, NULL on failure. + */ +struct mei_cl_cb *find_amthi_read_list_entry( + struct mei_device *dev, + struct file *file) +{ + struct mei_cl *cl_temp; + struct mei_cl_cb *pos = NULL; + struct mei_cl_cb *next = NULL; + + list_for_each_entry_safe(pos, next, + &dev->amthi_read_complete_list.mei_cb.cb_list, cb_list) { + cl_temp = (struct mei_cl *)pos->file_private; + if (cl_temp && cl_temp == &dev->iamthif_cl && + pos->file_object == file) + return pos; + } + return NULL; +} + +/** + * amthi_read - read data from AMTHI client + * + * @dev: the device structure + * @if_num: minor number + * @file: pointer to file object + * @*ubuf: pointer to user data in user space + * @length: data length to read + * @offset: data read offset + * + * Locking: called under "dev->device_lock" lock + * + * returns + * returned data length on success, + * zero if no data to read, + * negative on failure. + */ +int amthi_read(struct mei_device *dev, struct file *file, + char __user *ubuf, size_t length, loff_t *offset) +{ + int rets; + int wait_ret; + struct mei_cl_cb *cb = NULL; + struct mei_cl *cl = file->private_data; + unsigned long timeout; + int i; + + /* Only Posible if we are in timeout */ + if (!cl || cl != &dev->iamthif_cl) { + dev_dbg(&dev->pdev->dev, "bad file ext.\n"); + return -ETIMEDOUT; + } + + for (i = 0; i < dev->me_clients_num; i++) { + if (dev->me_clients[i].client_id == + dev->iamthif_cl.me_client_id) + break; + } + + if (i == dev->me_clients_num) { + dev_dbg(&dev->pdev->dev, "amthi client not found.\n"); + return -ENODEV; + } + if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id)) + return -ENODEV; + + dev_dbg(&dev->pdev->dev, "checking amthi data\n"); + cb = find_amthi_read_list_entry(dev, file); + + /* Check for if we can block or not*/ + if (cb == NULL && file->f_flags & O_NONBLOCK) + return -EAGAIN; + + + dev_dbg(&dev->pdev->dev, "waiting for amthi data\n"); + while (cb == NULL) { + /* unlock the Mutex */ + mutex_unlock(&dev->device_lock); + + wait_ret = wait_event_interruptible(dev->iamthif_cl.wait, + (cb = find_amthi_read_list_entry(dev, file))); + + if (wait_ret) + return -ERESTARTSYS; + + dev_dbg(&dev->pdev->dev, "woke up from sleep\n"); + + /* Locking again the Mutex */ + mutex_lock(&dev->device_lock); + } + + + dev_dbg(&dev->pdev->dev, "Got amthi data\n"); + dev->iamthif_timer = 0; + + if (cb) { + timeout = cb->read_time + + msecs_to_jiffies(IAMTHIF_READ_TIMER); + dev_dbg(&dev->pdev->dev, "amthi timeout = %lud\n", + timeout); + + if (time_after(jiffies, timeout)) { + dev_dbg(&dev->pdev->dev, "amthi Time out\n"); + /* 15 sec for the message has expired */ + list_del(&cb->cb_list); + rets = -ETIMEDOUT; + goto free; + } + } + /* if the whole message will fit remove it from the list */ + if (cb->information >= *offset && length >= (cb->information - *offset)) + list_del(&cb->cb_list); + else if (cb->information > 0 && cb->information <= *offset) { + /* end of the message has been reached */ + list_del(&cb->cb_list); + rets = 0; + goto free; + } + /* else means that not full buffer will be read and do not + * remove message from deletion list + */ + + dev_dbg(&dev->pdev->dev, "amthi cb->response_buffer size - %d\n", + cb->response_buffer.size); + dev_dbg(&dev->pdev->dev, "amthi cb->information - %lu\n", + cb->information); + + /* length is being turncated to PAGE_SIZE, however, + * the information may be longer */ + length = min_t(size_t, length, (cb->information - *offset)); + + if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) + rets = -EFAULT; + else { + rets = length; + if ((*offset + length) < cb->information) { + *offset += length; + goto out; + } + } +free: + dev_dbg(&dev->pdev->dev, "free amthi cb memory.\n"); + *offset = 0; + mei_free_cb_private(cb); +out: + return rets; +} + +/** + * mei_start_read - the start read client message function. + * + * @dev: the device structure + * @if_num: minor number + * @cl: private data of the file object + * + * returns 0 on success, <0 on failure. + */ +int mei_start_read(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_cl_cb *cb; + int rets = 0; + int i; + + if (cl->state != MEI_FILE_CONNECTED) + return -ENODEV; + + if (dev->mei_state != MEI_ENABLED) + return -ENODEV; + + dev_dbg(&dev->pdev->dev, "check if read is pending.\n"); + if (cl->read_pending || cl->read_cb) { + dev_dbg(&dev->pdev->dev, "read is pending.\n"); + return -EBUSY; + } + + cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!cb) + return -ENOMEM; + + dev_dbg(&dev->pdev->dev, "allocation call back successful. host client = %d, ME client = %d\n", + cl->host_client_id, cl->me_client_id); + + for (i = 0; i < dev->me_clients_num; i++) { + if (dev->me_clients[i].client_id == cl->me_client_id) + break; + + } + + if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id)) { + rets = -ENODEV; + goto unlock; + } + + if (i == dev->me_clients_num) { + rets = -ENODEV; + goto unlock; + } + + cb->response_buffer.size = dev->me_clients[i].props.max_msg_length; + cb->response_buffer.data = + kmalloc(cb->response_buffer.size, GFP_KERNEL); + if (!cb->response_buffer.data) { + rets = -ENOMEM; + goto unlock; + } + dev_dbg(&dev->pdev->dev, "allocation call back data success.\n"); + cb->major_file_operations = MEI_READ; + /* make sure information is zero before we start */ + cb->information = 0; + cb->file_private = (void *) cl; + cl->read_cb = cb; + if (dev->mei_host_buffer_is_empty) { + dev->mei_host_buffer_is_empty = false; + if (mei_send_flow_control(dev, cl)) { + rets = -ENODEV; + goto unlock; + } + list_add_tail(&cb->cb_list, &dev->read_list.mei_cb.cb_list); + } else { + list_add_tail(&cb->cb_list, &dev->ctrl_wr_list.mei_cb.cb_list); + } + return rets; +unlock: + mei_free_cb_private(cb); + return rets; +} + +/** + * amthi_write - write iamthif data to amthi client + * + * @dev: the device structure + * @cb: mei call back struct + * + * returns 0 on success, <0 on failure. + */ +int amthi_write(struct mei_device *dev, struct mei_cl_cb *cb) +{ + struct mei_msg_hdr mei_hdr; + int ret; + + if (!dev || !cb) + return -ENODEV; + + dev_dbg(&dev->pdev->dev, "write data to amthi client.\n"); + + dev->iamthif_state = MEI_IAMTHIF_WRITING; + dev->iamthif_current_cb = cb; + dev->iamthif_file_object = cb->file_object; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = true; + dev->iamthif_msg_buf_size = cb->request_buffer.size; + memcpy(dev->iamthif_msg_buf, cb->request_buffer.data, + cb->request_buffer.size); + + ret = mei_flow_ctrl_creds(dev, &dev->iamthif_cl); + if (ret < 0) + return ret; + + if (ret && dev->mei_host_buffer_is_empty) { + ret = 0; + dev->mei_host_buffer_is_empty = false; + if (cb->request_buffer.size > + (((dev->host_hw_state & H_CBD) >> 24) * sizeof(u32)) + -sizeof(struct mei_msg_hdr)) { + mei_hdr.length = + (((dev->host_hw_state & H_CBD) >> 24) * + sizeof(u32)) - sizeof(struct mei_msg_hdr); + mei_hdr.msg_complete = 0; + } else { + mei_hdr.length = cb->request_buffer.size; + mei_hdr.msg_complete = 1; + } + + mei_hdr.host_addr = dev->iamthif_cl.host_client_id; + mei_hdr.me_addr = dev->iamthif_cl.me_client_id; + mei_hdr.reserved = 0; + dev->iamthif_msg_buf_index += mei_hdr.length; + if (mei_write_message(dev, &mei_hdr, + (unsigned char *)(dev->iamthif_msg_buf), + mei_hdr.length)) + return -ENODEV; + + if (mei_hdr.msg_complete) { + if (mei_flow_ctrl_reduce(dev, &dev->iamthif_cl)) + return -ENODEV; + dev->iamthif_flow_control_pending = true; + dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; + dev_dbg(&dev->pdev->dev, "add amthi cb to write waiting list\n"); + dev->iamthif_current_cb = cb; + dev->iamthif_file_object = cb->file_object; + list_add_tail(&cb->cb_list, + &dev->write_waiting_list.mei_cb.cb_list); + } else { + dev_dbg(&dev->pdev->dev, "message does not complete, " + "so add amthi cb to write list.\n"); + list_add_tail(&cb->cb_list, + &dev->write_list.mei_cb.cb_list); + } + } else { + if (!(dev->mei_host_buffer_is_empty)) + dev_dbg(&dev->pdev->dev, "host buffer is not empty"); + + dev_dbg(&dev->pdev->dev, "No flow control credentials, " + "so add iamthif cb to write list.\n"); + list_add_tail(&cb->cb_list, &dev->write_list.mei_cb.cb_list); + } + return 0; +} + +/** + * iamthif_ioctl_send_msg - send cmd data to amthi client + * + * @dev: the device structure + * + * returns 0 on success, <0 on failure. + */ +void mei_run_next_iamthif_cmd(struct mei_device *dev) +{ + struct mei_cl *cl_tmp; + struct mei_cl_cb *pos = NULL; + struct mei_cl_cb *next = NULL; + int status; + + if (!dev) + return; + + dev->iamthif_msg_buf_size = 0; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = true; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->iamthif_timer = 0; + dev->iamthif_file_object = NULL; + + dev_dbg(&dev->pdev->dev, "complete amthi cmd_list cb.\n"); + + list_for_each_entry_safe(pos, next, + &dev->amthi_cmd_list.mei_cb.cb_list, cb_list) { + list_del(&pos->cb_list); + cl_tmp = (struct mei_cl *)pos->file_private; + + if (cl_tmp && cl_tmp == &dev->iamthif_cl) { + status = amthi_write(dev, pos); + if (status) { + dev_dbg(&dev->pdev->dev, + "amthi write failed status = %d\n", + status); + return; + } + break; + } + } +} + +/** + * mei_free_cb_private - free mei_cb_private related memory + * + * @cb: mei callback struct + */ +void mei_free_cb_private(struct mei_cl_cb *cb) +{ + if (cb == NULL) + return; + + kfree(cb->request_buffer.data); + kfree(cb->response_buffer.data); + kfree(cb); +} diff --git a/drivers/staging/mei/main.c b/drivers/staging/mei/main.c new file mode 100644 index 00000000..7c9321fa --- /dev/null +++ b/drivers/staging/mei/main.c @@ -0,0 +1,1237 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mei_dev.h" +#include "mei.h" +#include "interface.h" + + +#define MEI_READ_TIMEOUT 45 +#define MEI_DRIVER_NAME "mei" +#define MEI_DEV_NAME "mei" + +/* + * mei driver strings + */ +static char mei_driver_name[] = MEI_DRIVER_NAME; +static const char mei_driver_string[] = "Intel(R) Management Engine Interface"; + +/* The device pointer */ +/* Currently this driver works as long as there is only a single AMT device. */ +struct pci_dev *mei_device; + +/* mei_pci_tbl - PCI Device ID Table */ +static DEFINE_PCI_DEVICE_TABLE(mei_pci_tbl) = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GM965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GME965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q35)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82G33)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q33)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82X38)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_3200)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_6)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_7)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_8)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_9)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_10)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_3)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_3)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_CPT_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PBG_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_3)}, + + /* required last entry */ + {0, } +}; + +MODULE_DEVICE_TABLE(pci, mei_pci_tbl); + +static DEFINE_MUTEX(mei_mutex); + + +/** + * mei_clear_list - removes all callbacks associated with file + * from mei_cb_list + * + * @dev: device structure. + * @file: file structure + * @mei_cb_list: callbacks list + * + * mei_clear_list is called to clear resources associated with file + * when application calls close function or Ctrl-C was pressed + * + * returns true if callback removed from the list, false otherwise + */ +static bool mei_clear_list(struct mei_device *dev, + struct file *file, struct list_head *mei_cb_list) +{ + struct mei_cl_cb *cb_pos = NULL; + struct mei_cl_cb *cb_next = NULL; + struct file *file_temp; + bool removed = false; + + /* list all list member */ + list_for_each_entry_safe(cb_pos, cb_next, mei_cb_list, cb_list) { + file_temp = (struct file *)cb_pos->file_object; + /* check if list member associated with a file */ + if (file_temp == file) { + /* remove member from the list */ + list_del(&cb_pos->cb_list); + /* check if cb equal to current iamthif cb */ + if (dev->iamthif_current_cb == cb_pos) { + dev->iamthif_current_cb = NULL; + /* send flow control to iamthif client */ + mei_send_flow_control(dev, &dev->iamthif_cl); + } + /* free all allocated buffers */ + mei_free_cb_private(cb_pos); + cb_pos = NULL; + removed = true; + } + } + return removed; +} + +/** + * mei_clear_lists - removes all callbacks associated with file + * + * @dev: device structure + * @file: file structure + * + * mei_clear_lists is called to clear resources associated with file + * when application calls close function or Ctrl-C was pressed + * + * returns true if callback removed from the list, false otherwise + */ +static bool mei_clear_lists(struct mei_device *dev, struct file *file) +{ + bool removed = false; + + /* remove callbacks associated with a file */ + mei_clear_list(dev, file, &dev->amthi_cmd_list.mei_cb.cb_list); + if (mei_clear_list(dev, file, + &dev->amthi_read_complete_list.mei_cb.cb_list)) + removed = true; + + mei_clear_list(dev, file, &dev->ctrl_rd_list.mei_cb.cb_list); + + if (mei_clear_list(dev, file, &dev->ctrl_wr_list.mei_cb.cb_list)) + removed = true; + + if (mei_clear_list(dev, file, &dev->write_waiting_list.mei_cb.cb_list)) + removed = true; + + if (mei_clear_list(dev, file, &dev->write_list.mei_cb.cb_list)) + removed = true; + + /* check if iamthif_current_cb not NULL */ + if (dev->iamthif_current_cb && !removed) { + /* check file and iamthif current cb association */ + if (dev->iamthif_current_cb->file_object == file) { + /* remove cb */ + mei_free_cb_private(dev->iamthif_current_cb); + dev->iamthif_current_cb = NULL; + removed = true; + } + } + return removed; +} +/** + * find_read_list_entry - find read list entry + * + * @dev: device structure + * @file: pointer to file structure + * + * returns cb on success, NULL on error + */ +static struct mei_cl_cb *find_read_list_entry( + struct mei_device *dev, + struct mei_cl *cl) +{ + struct mei_cl_cb *pos = NULL; + struct mei_cl_cb *next = NULL; + + dev_dbg(&dev->pdev->dev, "remove read_list CB\n"); + list_for_each_entry_safe(pos, next, + &dev->read_list.mei_cb.cb_list, cb_list) { + struct mei_cl *cl_temp; + cl_temp = (struct mei_cl *)pos->file_private; + + if (mei_cl_cmp_id(cl, cl_temp)) + return pos; + } + return NULL; +} + +/** + * mei_open - the open function + * + * @inode: pointer to inode structure + * @file: pointer to file structure + * + * returns 0 on success, <0 on error + */ +static int mei_open(struct inode *inode, struct file *file) +{ + struct mei_cl *cl; + struct mei_device *dev; + unsigned long cl_id; + int err; + + err = -ENODEV; + if (!mei_device) + goto out; + + dev = pci_get_drvdata(mei_device); + if (!dev) + goto out; + + mutex_lock(&dev->device_lock); + err = -ENOMEM; + cl = mei_cl_allocate(dev); + if (!cl) + goto out_unlock; + + err = -ENODEV; + if (dev->mei_state != MEI_ENABLED) { + dev_dbg(&dev->pdev->dev, "mei_state != MEI_ENABLED mei_state= %d\n", + dev->mei_state); + goto out_unlock; + } + err = -EMFILE; + if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) + goto out_unlock; + + cl_id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX); + if (cl_id >= MEI_CLIENTS_MAX) + goto out_unlock; + + cl->host_client_id = cl_id; + + dev_dbg(&dev->pdev->dev, "client_id = %d\n", cl->host_client_id); + + dev->open_handle_count++; + + list_add_tail(&cl->link, &dev->file_list); + + set_bit(cl->host_client_id, dev->host_clients_map); + cl->state = MEI_FILE_INITIALIZING; + cl->sm_state = 0; + + file->private_data = cl; + mutex_unlock(&dev->device_lock); + + return nonseekable_open(inode, file); + +out_unlock: + mutex_unlock(&dev->device_lock); + kfree(cl); +out: + return err; +} + +/** + * mei_release - the release function + * + * @inode: pointer to inode structure + * @file: pointer to file structure + * + * returns 0 on success, <0 on error + */ +static int mei_release(struct inode *inode, struct file *file) +{ + struct mei_cl *cl = file->private_data; + struct mei_cl_cb *cb; + struct mei_device *dev; + int rets = 0; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + if (cl != &dev->iamthif_cl) { + if (cl->state == MEI_FILE_CONNECTED) { + cl->state = MEI_FILE_DISCONNECTING; + dev_dbg(&dev->pdev->dev, + "disconnecting client host client = %d, " + "ME client = %d\n", + cl->host_client_id, + cl->me_client_id); + rets = mei_disconnect_host_client(dev, cl); + } + mei_cl_flush_queues(cl); + dev_dbg(&dev->pdev->dev, "remove client host client = %d, ME client = %d\n", + cl->host_client_id, + cl->me_client_id); + + if (dev->open_handle_count > 0) { + clear_bit(cl->host_client_id, dev->host_clients_map); + dev->open_handle_count--; + } + mei_remove_client_from_file_list(dev, cl->host_client_id); + + /* free read cb */ + cb = NULL; + if (cl->read_cb) { + cb = find_read_list_entry(dev, cl); + /* Remove entry from read list */ + if (cb) + list_del(&cb->cb_list); + + cb = cl->read_cb; + cl->read_cb = NULL; + } + + file->private_data = NULL; + + if (cb) { + mei_free_cb_private(cb); + cb = NULL; + } + + kfree(cl); + } else { + if (dev->open_handle_count > 0) + dev->open_handle_count--; + + if (dev->iamthif_file_object == file && + dev->iamthif_state != MEI_IAMTHIF_IDLE) { + + dev_dbg(&dev->pdev->dev, "amthi canceled iamthif state %d\n", + dev->iamthif_state); + dev->iamthif_canceled = true; + if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE) { + dev_dbg(&dev->pdev->dev, "run next amthi iamthif cb\n"); + mei_run_next_iamthif_cmd(dev); + } + } + + if (mei_clear_lists(dev, file)) + dev->iamthif_state = MEI_IAMTHIF_IDLE; + + } + mutex_unlock(&dev->device_lock); + return rets; +} + + +/** + * mei_read - the read function. + * + * @file: pointer to file structure + * @ubuf: pointer to user buffer + * @length: buffer length + * @offset: data offset in buffer + * + * returns >=0 data length on success , <0 on error + */ +static ssize_t mei_read(struct file *file, char __user *ubuf, + size_t length, loff_t *offset) +{ + struct mei_cl *cl = file->private_data; + struct mei_cl_cb *cb_pos = NULL; + struct mei_cl_cb *cb = NULL; + struct mei_device *dev; + int i; + int rets; + int err; + + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + if (dev->mei_state != MEI_ENABLED) { + rets = -ENODEV; + goto out; + } + + if ((cl->sm_state & MEI_WD_STATE_INDEPENDENCE_MSG_SENT) == 0) { + /* Do not allow to read watchdog client */ + i = mei_find_me_client_index(dev, mei_wd_guid); + if (i >= 0) { + struct mei_me_client *me_client = &dev->me_clients[i]; + + if (cl->me_client_id == me_client->client_id) { + rets = -EBADF; + goto out; + } + } + } else { + cl->sm_state &= ~MEI_WD_STATE_INDEPENDENCE_MSG_SENT; + } + + if (cl == &dev->iamthif_cl) { + rets = amthi_read(dev, file, ubuf, length, offset); + goto out; + } + + if (cl->read_cb && cl->read_cb->information > *offset) { + cb = cl->read_cb; + goto copy_buffer; + } else if (cl->read_cb && cl->read_cb->information > 0 && + cl->read_cb->information <= *offset) { + cb = cl->read_cb; + rets = 0; + goto free; + } else if ((!cl->read_cb || !cl->read_cb->information) && + *offset > 0) { + /*Offset needs to be cleaned for contiguous reads*/ + *offset = 0; + rets = 0; + goto out; + } + + err = mei_start_read(dev, cl); + if (err && err != -EBUSY) { + dev_dbg(&dev->pdev->dev, + "mei start read failure with status = %d\n", err); + rets = err; + goto out; + } + + if (MEI_READ_COMPLETE != cl->reading_state && + !waitqueue_active(&cl->rx_wait)) { + if (file->f_flags & O_NONBLOCK) { + rets = -EAGAIN; + goto out; + } + + mutex_unlock(&dev->device_lock); + + if (wait_event_interruptible(cl->rx_wait, + (MEI_READ_COMPLETE == cl->reading_state || + MEI_FILE_INITIALIZING == cl->state || + MEI_FILE_DISCONNECTED == cl->state || + MEI_FILE_DISCONNECTING == cl->state))) { + if (signal_pending(current)) + return -EINTR; + return -ERESTARTSYS; + } + + mutex_lock(&dev->device_lock); + if (MEI_FILE_INITIALIZING == cl->state || + MEI_FILE_DISCONNECTED == cl->state || + MEI_FILE_DISCONNECTING == cl->state) { + rets = -EBUSY; + goto out; + } + } + + cb = cl->read_cb; + + if (!cb) { + rets = -ENODEV; + goto out; + } + if (cl->reading_state != MEI_READ_COMPLETE) { + rets = 0; + goto out; + } + /* now copy the data to user space */ +copy_buffer: + dev_dbg(&dev->pdev->dev, "cb->response_buffer size - %d\n", + cb->response_buffer.size); + dev_dbg(&dev->pdev->dev, "cb->information - %lu\n", + cb->information); + if (length == 0 || ubuf == NULL || *offset > cb->information) { + rets = -EMSGSIZE; + goto free; + } + + /* length is being truncated to PAGE_SIZE, however, */ + /* information size may be longer */ + length = min_t(size_t, length, (cb->information - *offset)); + + if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) { + rets = -EFAULT; + goto free; + } + + rets = length; + *offset += length; + if ((unsigned long)*offset < cb->information) + goto out; + +free: + cb_pos = find_read_list_entry(dev, cl); + /* Remove entry from read list */ + if (cb_pos) + list_del(&cb_pos->cb_list); + mei_free_cb_private(cb); + cl->reading_state = MEI_IDLE; + cl->read_cb = NULL; + cl->read_pending = 0; +out: + dev_dbg(&dev->pdev->dev, "end mei read rets= %d\n", rets); + mutex_unlock(&dev->device_lock); + return rets; +} + +/** + * mei_write - the write function. + * + * @file: pointer to file structure + * @ubuf: pointer to user buffer + * @length: buffer length + * @offset: data offset in buffer + * + * returns >=0 data length on success , <0 on error + */ +static ssize_t mei_write(struct file *file, const char __user *ubuf, + size_t length, loff_t *offset) +{ + struct mei_cl *cl = file->private_data; + struct mei_cl_cb *write_cb = NULL; + struct mei_msg_hdr mei_hdr; + struct mei_device *dev; + unsigned long timeout = 0; + int rets; + int i; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (dev->mei_state != MEI_ENABLED) { + mutex_unlock(&dev->device_lock); + return -ENODEV; + } + + if (cl == &dev->iamthif_cl) { + write_cb = find_amthi_read_list_entry(dev, file); + + if (write_cb) { + timeout = write_cb->read_time + + msecs_to_jiffies(IAMTHIF_READ_TIMER); + + if (time_after(jiffies, timeout) || + cl->reading_state == MEI_READ_COMPLETE) { + *offset = 0; + list_del(&write_cb->cb_list); + mei_free_cb_private(write_cb); + write_cb = NULL; + } + } + } + + /* free entry used in read */ + if (cl->reading_state == MEI_READ_COMPLETE) { + *offset = 0; + write_cb = find_read_list_entry(dev, cl); + if (write_cb) { + list_del(&write_cb->cb_list); + mei_free_cb_private(write_cb); + write_cb = NULL; + cl->reading_state = MEI_IDLE; + cl->read_cb = NULL; + cl->read_pending = 0; + } + } else if (cl->reading_state == MEI_IDLE && !cl->read_pending) + *offset = 0; + + + write_cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!write_cb) { + mutex_unlock(&dev->device_lock); + return -ENOMEM; + } + + write_cb->file_object = file; + write_cb->file_private = cl; + write_cb->request_buffer.data = kmalloc(length, GFP_KERNEL); + rets = -ENOMEM; + if (!write_cb->request_buffer.data) + goto unlock_dev; + + dev_dbg(&dev->pdev->dev, "length =%d\n", (int) length); + + rets = -EFAULT; + if (copy_from_user(write_cb->request_buffer.data, ubuf, length)) + goto unlock_dev; + + cl->sm_state = 0; + if (length == 4 && + ((memcmp(mei_wd_state_independence_msg[0], + write_cb->request_buffer.data, 4) == 0) || + (memcmp(mei_wd_state_independence_msg[1], + write_cb->request_buffer.data, 4) == 0) || + (memcmp(mei_wd_state_independence_msg[2], + write_cb->request_buffer.data, 4) == 0))) + cl->sm_state |= MEI_WD_STATE_INDEPENDENCE_MSG_SENT; + + INIT_LIST_HEAD(&write_cb->cb_list); + if (cl == &dev->iamthif_cl) { + write_cb->response_buffer.data = + kmalloc(dev->iamthif_mtu, GFP_KERNEL); + if (!write_cb->response_buffer.data) { + rets = -ENOMEM; + goto unlock_dev; + } + if (dev->mei_state != MEI_ENABLED) { + rets = -ENODEV; + goto unlock_dev; + } + for (i = 0; i < dev->me_clients_num; i++) { + if (dev->me_clients[i].client_id == + dev->iamthif_cl.me_client_id) + break; + } + + if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id)) { + rets = -ENODEV; + goto unlock_dev; + } + if (i == dev->me_clients_num || + (dev->me_clients[i].client_id != + dev->iamthif_cl.me_client_id)) { + rets = -ENODEV; + goto unlock_dev; + } else if (length > dev->me_clients[i].props.max_msg_length || + length <= 0) { + rets = -EMSGSIZE; + goto unlock_dev; + } + + write_cb->response_buffer.size = dev->iamthif_mtu; + write_cb->major_file_operations = MEI_IOCTL; + write_cb->information = 0; + write_cb->request_buffer.size = length; + if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { + rets = -ENODEV; + goto unlock_dev; + } + + if (!list_empty(&dev->amthi_cmd_list.mei_cb.cb_list) || + dev->iamthif_state != MEI_IAMTHIF_IDLE) { + dev_dbg(&dev->pdev->dev, "amthi_state = %d\n", + (int) dev->iamthif_state); + dev_dbg(&dev->pdev->dev, "add amthi cb to amthi cmd waiting list\n"); + list_add_tail(&write_cb->cb_list, + &dev->amthi_cmd_list.mei_cb.cb_list); + rets = length; + } else { + dev_dbg(&dev->pdev->dev, "call amthi write\n"); + rets = amthi_write(dev, write_cb); + + if (rets) { + dev_dbg(&dev->pdev->dev, "amthi write failed with status = %d\n", + rets); + goto unlock_dev; + } + rets = length; + } + mutex_unlock(&dev->device_lock); + return rets; + } + + write_cb->major_file_operations = MEI_WRITE; + /* make sure information is zero before we start */ + + write_cb->information = 0; + write_cb->request_buffer.size = length; + + dev_dbg(&dev->pdev->dev, "host client = %d, ME client = %d\n", + cl->host_client_id, cl->me_client_id); + if (cl->state != MEI_FILE_CONNECTED) { + rets = -ENODEV; + dev_dbg(&dev->pdev->dev, "host client = %d, is not connected to ME client = %d", + cl->host_client_id, + cl->me_client_id); + goto unlock_dev; + } + for (i = 0; i < dev->me_clients_num; i++) { + if (dev->me_clients[i].client_id == + cl->me_client_id) + break; + } + if (WARN_ON(dev->me_clients[i].client_id != cl->me_client_id)) { + rets = -ENODEV; + goto unlock_dev; + } + if (i == dev->me_clients_num) { + rets = -ENODEV; + goto unlock_dev; + } + if (length > dev->me_clients[i].props.max_msg_length || length <= 0) { + rets = -EINVAL; + goto unlock_dev; + } + write_cb->file_private = cl; + + rets = mei_flow_ctrl_creds(dev, cl); + if (rets < 0) + goto unlock_dev; + + if (rets && dev->mei_host_buffer_is_empty) { + rets = 0; + dev->mei_host_buffer_is_empty = false; + if (length > ((((dev->host_hw_state & H_CBD) >> 24) * + sizeof(u32)) - sizeof(struct mei_msg_hdr))) { + + mei_hdr.length = + (((dev->host_hw_state & H_CBD) >> 24) * + sizeof(u32)) - + sizeof(struct mei_msg_hdr); + mei_hdr.msg_complete = 0; + } else { + mei_hdr.length = length; + mei_hdr.msg_complete = 1; + } + mei_hdr.host_addr = cl->host_client_id; + mei_hdr.me_addr = cl->me_client_id; + mei_hdr.reserved = 0; + dev_dbg(&dev->pdev->dev, "call mei_write_message header=%08x.\n", + *((u32 *) &mei_hdr)); + if (mei_write_message(dev, &mei_hdr, + (unsigned char *) (write_cb->request_buffer.data), + mei_hdr.length)) { + rets = -ENODEV; + goto unlock_dev; + } + cl->writing_state = MEI_WRITING; + write_cb->information = mei_hdr.length; + if (mei_hdr.msg_complete) { + if (mei_flow_ctrl_reduce(dev, cl)) { + rets = -ENODEV; + goto unlock_dev; + } + list_add_tail(&write_cb->cb_list, + &dev->write_waiting_list.mei_cb.cb_list); + } else { + list_add_tail(&write_cb->cb_list, + &dev->write_list.mei_cb.cb_list); + } + + } else { + + write_cb->information = 0; + cl->writing_state = MEI_WRITING; + list_add_tail(&write_cb->cb_list, + &dev->write_list.mei_cb.cb_list); + } + mutex_unlock(&dev->device_lock); + return length; + +unlock_dev: + mutex_unlock(&dev->device_lock); + mei_free_cb_private(write_cb); + return rets; +} + + +/** + * mei_ioctl - the IOCTL function + * + * @file: pointer to file structure + * @cmd: ioctl command + * @data: pointer to mei message structure + * + * returns 0 on success , <0 on error + */ +static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) +{ + struct mei_device *dev; + struct mei_cl *cl = file->private_data; + struct mei_connect_client_data *connect_data = NULL; + int rets; + + if (cmd != IOCTL_MEI_CONNECT_CLIENT) + return -EINVAL; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + dev_dbg(&dev->pdev->dev, "IOCTL cmd = 0x%x", cmd); + + mutex_lock(&dev->device_lock); + if (dev->mei_state != MEI_ENABLED) { + rets = -ENODEV; + goto out; + } + + dev_dbg(&dev->pdev->dev, ": IOCTL_MEI_CONNECT_CLIENT.\n"); + + connect_data = kzalloc(sizeof(struct mei_connect_client_data), + GFP_KERNEL); + if (!connect_data) { + rets = -ENOMEM; + goto out; + } + dev_dbg(&dev->pdev->dev, "copy connect data from user\n"); + if (copy_from_user(connect_data, (char __user *)data, + sizeof(struct mei_connect_client_data))) { + dev_dbg(&dev->pdev->dev, "failed to copy data from userland\n"); + rets = -EFAULT; + goto out; + } + rets = mei_ioctl_connect_client(file, connect_data); + + /* if all is ok, copying the data back to user. */ + if (rets) + goto out; + + dev_dbg(&dev->pdev->dev, "copy connect data to user\n"); + if (copy_to_user((char __user *)data, connect_data, + sizeof(struct mei_connect_client_data))) { + dev_dbg(&dev->pdev->dev, "failed to copy data to userland\n"); + rets = -EFAULT; + goto out; + } + +out: + kfree(connect_data); + mutex_unlock(&dev->device_lock); + return rets; +} + +/** + * mei_compat_ioctl - the compat IOCTL function + * + * @file: pointer to file structure + * @cmd: ioctl command + * @data: pointer to mei message structure + * + * returns 0 on success , <0 on error + */ +#ifdef CONFIG_COMPAT +static long mei_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long data) +{ + return mei_ioctl(file, cmd, (unsigned long)compat_ptr(data)); +} +#endif + + +/** + * mei_poll - the poll function + * + * @file: pointer to file structure + * @wait: pointer to poll_table structure + * + * returns poll mask + */ +static unsigned int mei_poll(struct file *file, poll_table *wait) +{ + struct mei_cl *cl = file->private_data; + struct mei_device *dev; + unsigned int mask = 0; + + if (WARN_ON(!cl || !cl->dev)) + return mask; + + dev = cl->dev; + + mutex_lock(&dev->device_lock); + + if (dev->mei_state != MEI_ENABLED) + goto out; + + + if (cl == &dev->iamthif_cl) { + mutex_unlock(&dev->device_lock); + poll_wait(file, &dev->iamthif_cl.wait, wait); + mutex_lock(&dev->device_lock); + if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE && + dev->iamthif_file_object == file) { + mask |= (POLLIN | POLLRDNORM); + dev_dbg(&dev->pdev->dev, "run next amthi cb\n"); + mei_run_next_iamthif_cmd(dev); + } + goto out; + } + + mutex_unlock(&dev->device_lock); + poll_wait(file, &cl->tx_wait, wait); + mutex_lock(&dev->device_lock); + if (MEI_WRITE_COMPLETE == cl->writing_state) + mask |= (POLLIN | POLLRDNORM); + +out: + mutex_unlock(&dev->device_lock); + return mask; +} + +/* + * file operations structure will be used for mei char device. + */ +static const struct file_operations mei_fops = { + .owner = THIS_MODULE, + .read = mei_read, + .unlocked_ioctl = mei_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = mei_compat_ioctl, +#endif + .open = mei_open, + .release = mei_release, + .write = mei_write, + .poll = mei_poll, + .llseek = no_llseek +}; + + +/* + * Misc Device Struct + */ +static struct miscdevice mei_misc_device = { + .name = MEI_DRIVER_NAME, + .fops = &mei_fops, + .minor = MISC_DYNAMIC_MINOR, +}; + +/** + * mei_probe - Device Initialization Routine + * + * @pdev: PCI device structure + * @ent: entry in kcs_pci_tbl + * + * returns 0 on success, <0 on failure. + */ +static int __devinit mei_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct mei_device *dev; + int err; + + mutex_lock(&mei_mutex); + if (mei_device) { + err = -EEXIST; + goto end; + } + /* enable pci dev */ + err = pci_enable_device(pdev); + if (err) { + printk(KERN_ERR "mei: Failed to enable pci device.\n"); + goto end; + } + /* set PCI host mastering */ + pci_set_master(pdev); + /* pci request regions for mei driver */ + err = pci_request_regions(pdev, mei_driver_name); + if (err) { + printk(KERN_ERR "mei: Failed to get pci regions.\n"); + goto disable_device; + } + /* allocates and initializes the mei dev structure */ + dev = mei_device_init(pdev); + if (!dev) { + err = -ENOMEM; + goto release_regions; + } + /* mapping IO device memory */ + dev->mem_addr = pci_iomap(pdev, 0, 0); + if (!dev->mem_addr) { + printk(KERN_ERR "mei: mapping I/O device memory failure.\n"); + err = -ENOMEM; + goto free_device; + } + pci_enable_msi(pdev); + + /* request and enable interrupt */ + if (pci_dev_msi_enabled(pdev)) + err = request_threaded_irq(pdev->irq, + NULL, + mei_interrupt_thread_handler, + 0, mei_driver_name, dev); + else + err = request_threaded_irq(pdev->irq, + mei_interrupt_quick_handler, + mei_interrupt_thread_handler, + IRQF_SHARED, mei_driver_name, dev); + + if (err) { + printk(KERN_ERR "mei: request_threaded_irq failure. irq = %d\n", + pdev->irq); + goto unmap_memory; + } + INIT_DELAYED_WORK(&dev->timer_work, mei_timer); + if (mei_hw_init(dev)) { + printk(KERN_ERR "mei: Init hw failure.\n"); + err = -ENODEV; + goto release_irq; + } + + err = misc_register(&mei_misc_device); + if (err) + goto release_irq; + + mei_device = pdev; + pci_set_drvdata(pdev, dev); + + + schedule_delayed_work(&dev->timer_work, HZ); + + mutex_unlock(&mei_mutex); + + pr_debug("mei: Driver initialization successful.\n"); + + return 0; + +release_irq: + /* disable interrupts */ + dev->host_hw_state = mei_hcsr_read(dev); + mei_disable_interrupts(dev); + flush_scheduled_work(); + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); +unmap_memory: + pci_iounmap(pdev, dev->mem_addr); +free_device: + kfree(dev); +release_regions: + pci_release_regions(pdev); +disable_device: + pci_disable_device(pdev); +end: + mutex_unlock(&mei_mutex); + printk(KERN_ERR "mei: Driver initialization failed.\n"); + return err; +} + +/** + * mei_remove - Device Removal Routine + * + * @pdev: PCI device structure + * + * mei_remove is called by the PCI subsystem to alert the driver + * that it should release a PCI device. + */ +static void __devexit mei_remove(struct pci_dev *pdev) +{ + struct mei_device *dev; + + if (mei_device != pdev) + return; + + dev = pci_get_drvdata(pdev); + if (!dev) + return; + + mutex_lock(&dev->device_lock); + + mei_wd_stop(dev, false); + + mei_device = NULL; + + if (dev->iamthif_cl.state == MEI_FILE_CONNECTED) { + dev->iamthif_cl.state = MEI_FILE_DISCONNECTING; + mei_disconnect_host_client(dev, &dev->iamthif_cl); + } + if (dev->wd_cl.state == MEI_FILE_CONNECTED) { + dev->wd_cl.state = MEI_FILE_DISCONNECTING; + mei_disconnect_host_client(dev, &dev->wd_cl); + } + + /* Unregistering watchdog device */ + mei_watchdog_unregister(dev); + + /* remove entry if already in list */ + dev_dbg(&pdev->dev, "list del iamthif and wd file list.\n"); + mei_remove_client_from_file_list(dev, dev->wd_cl.host_client_id); + mei_remove_client_from_file_list(dev, dev->iamthif_cl.host_client_id); + + dev->iamthif_current_cb = NULL; + dev->me_clients_num = 0; + + mutex_unlock(&dev->device_lock); + + flush_scheduled_work(); + + /* disable interrupts */ + mei_disable_interrupts(dev); + + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); + pci_set_drvdata(pdev, NULL); + + if (dev->mem_addr) + pci_iounmap(pdev, dev->mem_addr); + + kfree(dev); + + pci_release_regions(pdev); + pci_disable_device(pdev); +} +#ifdef CONFIG_PM +static int mei_pci_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev = pci_get_drvdata(pdev); + int err; + + if (!dev) + return -ENODEV; + mutex_lock(&dev->device_lock); + /* Stop watchdog if exists */ + err = mei_wd_stop(dev, true); + /* Set new mei state */ + if (dev->mei_state == MEI_ENABLED || + dev->mei_state == MEI_RECOVERING_FROM_RESET) { + dev->mei_state = MEI_POWER_DOWN; + mei_reset(dev, 0); + } + mutex_unlock(&dev->device_lock); + + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); + + return err; +} + +static int mei_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int err; + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + pci_enable_msi(pdev); + + /* request and enable interrupt */ + if (pci_dev_msi_enabled(pdev)) + err = request_threaded_irq(pdev->irq, + NULL, + mei_interrupt_thread_handler, + 0, mei_driver_name, dev); + else + err = request_threaded_irq(pdev->irq, + mei_interrupt_quick_handler, + mei_interrupt_thread_handler, + IRQF_SHARED, mei_driver_name, dev); + + if (err) { + printk(KERN_ERR "mei: Request_irq failure. irq = %d\n", + pdev->irq); + return err; + } + + mutex_lock(&dev->device_lock); + dev->mei_state = MEI_POWER_UP; + mei_reset(dev, 1); + mutex_unlock(&dev->device_lock); + + /* Start timer if stopped in suspend */ + schedule_delayed_work(&dev->timer_work, HZ); + + return err; +} +static SIMPLE_DEV_PM_OPS(mei_pm_ops, mei_pci_suspend, mei_pci_resume); +#define MEI_PM_OPS (&mei_pm_ops) +#else +#define MEI_PM_OPS NULL +#endif /* CONFIG_PM */ +/* + * PCI driver structure + */ +static struct pci_driver mei_driver = { + .name = mei_driver_name, + .id_table = mei_pci_tbl, + .probe = mei_probe, + .remove = __devexit_p(mei_remove), + .shutdown = __devexit_p(mei_remove), + .driver.pm = MEI_PM_OPS, +}; + +/** + * mei_init_module - Driver Registration Routine + * + * mei_init_module is the first routine called when the driver is + * loaded. All it does is to register with the PCI subsystem. + * + * returns 0 on success, <0 on failure. + */ +static int __init mei_init_module(void) +{ + int ret; + + pr_debug("mei: %s\n", mei_driver_string); + /* init pci module */ + ret = pci_register_driver(&mei_driver); + if (ret < 0) + printk(KERN_ERR "mei: Error registering driver.\n"); + + return ret; +} + +module_init(mei_init_module); + +/** + * mei_exit_module - Driver Exit Cleanup Routine + * + * mei_exit_module is called just before the driver is removed + * from memory. + */ +static void __exit mei_exit_module(void) +{ + misc_deregister(&mei_misc_device); + pci_unregister_driver(&mei_driver); + + pr_debug("mei: Driver unloaded successfully.\n"); +} + +module_exit(mei_exit_module); + + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) Management Engine Interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/mei/mei-amt-version.c b/drivers/staging/mei/mei-amt-version.c new file mode 100644 index 00000000..ac2a507b --- /dev/null +++ b/drivers/staging/mei/mei-amt-version.c @@ -0,0 +1,481 @@ +/****************************************************************************** + * Intel Management Engine Interface (Intel MEI) Linux driver + * Intel MEI Interface Header + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Corporation. + * linux-mei@linux.intel.com + * http://www.intel.com + * + * BSD LICENSE + * + * Copyright(c) 2003 - 2012 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mei.h" + +/***************************************************************************** + * Intel Management Engine Interface + *****************************************************************************/ + +#define mei_msg(_me, fmt, ARGS...) do { \ + if (_me->verbose) \ + fprintf(stderr, fmt, ##ARGS); \ +} while (0) + +#define mei_err(_me, fmt, ARGS...) do { \ + fprintf(stderr, "Error: " fmt, ##ARGS); \ +} while (0) + +struct mei { + uuid_le guid; + bool initialized; + bool verbose; + unsigned int buf_size; + unsigned char prot_ver; + int fd; +}; + +static void mei_deinit(struct mei *cl) +{ + if (cl->fd != -1) + close(cl->fd); + cl->fd = -1; + cl->buf_size = 0; + cl->prot_ver = 0; + cl->initialized = false; +} + +static bool mei_init(struct mei *me, const uuid_le *guid, + unsigned char req_protocol_version, bool verbose) +{ + int result; + struct mei_client *cl; + struct mei_connect_client_data data; + + mei_deinit(me); + + me->verbose = verbose; + + me->fd = open("/dev/mei", O_RDWR); + if (me->fd == -1) { + mei_err(me, "Cannot establish a handle to the Intel MEI driver\n"); + goto err; + } + memcpy(&me->guid, guid, sizeof(*guid)); + memset(&data, 0, sizeof(data)); + me->initialized = true; + + memcpy(&data.in_client_uuid, &me->guid, sizeof(me->guid)); + result = ioctl(me->fd, IOCTL_MEI_CONNECT_CLIENT, &data); + if (result) { + mei_err(me, "IOCTL_MEI_CONNECT_CLIENT receive message. err=%d\n", result); + goto err; + } + cl = &data.out_client_properties; + mei_msg(me, "max_message_length %d\n", cl->max_msg_length); + mei_msg(me, "protocol_version %d\n", cl->protocol_version); + + if ((req_protocol_version > 0) && + (cl->protocol_version != req_protocol_version)) { + mei_err(me, "Intel MEI protocol version not supported\n"); + goto err; + } + + me->buf_size = cl->max_msg_length; + me->prot_ver = cl->protocol_version; + + return true; +err: + mei_deinit(me); + return false; +} + +static ssize_t mei_recv_msg(struct mei *me, unsigned char *buffer, + ssize_t len, unsigned long timeout) +{ + ssize_t rc; + + mei_msg(me, "call read length = %zd\n", len); + + rc = read(me->fd, buffer, len); + if (rc < 0) { + mei_err(me, "read failed with status %zd %s\n", + rc, strerror(errno)); + mei_deinit(me); + } else { + mei_msg(me, "read succeeded with result %zd\n", rc); + } + return rc; +} + +static ssize_t mei_send_msg(struct mei *me, const unsigned char *buffer, + ssize_t len, unsigned long timeout) +{ + struct timeval tv; + ssize_t written; + ssize_t rc; + fd_set set; + + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000000; + + mei_msg(me, "call write length = %zd\n", len); + + written = write(me->fd, buffer, len); + if (written < 0) { + rc = -errno; + mei_err(me, "write failed with status %zd %s\n", + written, strerror(errno)); + goto out; + } + + FD_ZERO(&set); + FD_SET(me->fd, &set); + rc = select(me->fd + 1 , &set, NULL, NULL, &tv); + if (rc > 0 && FD_ISSET(me->fd, &set)) { + mei_msg(me, "write success\n"); + } else if (rc == 0) { + mei_err(me, "write failed on timeout with status\n"); + goto out; + } else { /* rc < 0 */ + mei_err(me, "write failed on select with status %zd\n", rc); + goto out; + } + + rc = written; +out: + if (rc < 0) + mei_deinit(me); + + return rc; +} + +/*************************************************************************** + * Intel Advanced Management Technolgy ME Client + ***************************************************************************/ + +#define AMT_MAJOR_VERSION 1 +#define AMT_MINOR_VERSION 1 + +#define AMT_STATUS_SUCCESS 0x0 +#define AMT_STATUS_INTERNAL_ERROR 0x1 +#define AMT_STATUS_NOT_READY 0x2 +#define AMT_STATUS_INVALID_AMT_MODE 0x3 +#define AMT_STATUS_INVALID_MESSAGE_LENGTH 0x4 + +#define AMT_STATUS_HOST_IF_EMPTY_RESPONSE 0x4000 +#define AMT_STATUS_SDK_RESOURCES 0x1004 + + +#define AMT_BIOS_VERSION_LEN 65 +#define AMT_VERSIONS_NUMBER 50 +#define AMT_UNICODE_STRING_LEN 20 + +struct amt_unicode_string { + uint16_t length; + char string[AMT_UNICODE_STRING_LEN]; +} __attribute__((packed)); + +struct amt_version_type { + struct amt_unicode_string description; + struct amt_unicode_string version; +} __attribute__((packed)); + +struct amt_version { + uint8_t major; + uint8_t minor; +} __attribute__((packed)); + +struct amt_code_versions { + uint8_t bios[AMT_BIOS_VERSION_LEN]; + uint32_t count; + struct amt_version_type versions[AMT_VERSIONS_NUMBER]; +} __attribute__((packed)); + +/*************************************************************************** + * Intel Advanced Management Technolgy Host Interface + ***************************************************************************/ + +struct amt_host_if_msg_header { + struct amt_version version; + uint16_t _reserved; + uint32_t command; + uint32_t length; +} __attribute__((packed)); + +struct amt_host_if_resp_header { + struct amt_host_if_msg_header header; + uint32_t status; + unsigned char data[0]; +} __attribute__((packed)); + +const uuid_le MEI_IAMTHIF = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, \ + 0xac, 0xa8, 0x46, 0xe0, 0xff, 0x65, 0x81, 0x4c); + +#define AMT_HOST_IF_CODE_VERSIONS_REQUEST 0x0400001A +#define AMT_HOST_IF_CODE_VERSIONS_RESPONSE 0x0480001A + +const struct amt_host_if_msg_header CODE_VERSION_REQ = { + .version = {AMT_MAJOR_VERSION, AMT_MINOR_VERSION}, + ._reserved = 0, + .command = AMT_HOST_IF_CODE_VERSIONS_REQUEST, + .length = 0 +}; + + +struct amt_host_if { + struct mei mei_cl; + unsigned long send_timeout; + bool initialized; +}; + + +static bool amt_host_if_init(struct amt_host_if *acmd, + unsigned long send_timeout, bool verbose) +{ + acmd->send_timeout = (send_timeout) ? send_timeout : 20000; + acmd->initialized = mei_init(&acmd->mei_cl, &MEI_IAMTHIF, 0, verbose); + return acmd->initialized; +} + +static void amt_host_if_deinit(struct amt_host_if *acmd) +{ + mei_deinit(&acmd->mei_cl); + acmd->initialized = false; +} + +static uint32_t amt_verify_code_versions(const struct amt_host_if_resp_header *resp) +{ + uint32_t status = AMT_STATUS_SUCCESS; + struct amt_code_versions *code_ver; + size_t code_ver_len; + uint32_t ver_type_cnt; + uint32_t len; + uint32_t i; + + code_ver = (struct amt_code_versions *)resp->data; + /* length - sizeof(status) */ + code_ver_len = resp->header.length - sizeof(uint32_t); + ver_type_cnt = code_ver_len - + sizeof(code_ver->bios) - + sizeof(code_ver->count); + if (code_ver->count != ver_type_cnt / sizeof(struct amt_version_type)) { + status = AMT_STATUS_INTERNAL_ERROR; + goto out; + } + + for (i = 0; i < code_ver->count; i++) { + len = code_ver->versions[i].description.length; + + if (len > AMT_UNICODE_STRING_LEN) { + status = AMT_STATUS_INTERNAL_ERROR; + goto out; + } + + len = code_ver->versions[i].version.length; + if (code_ver->versions[i].version.string[len] != '\0' || + len != strlen(code_ver->versions[i].version.string)) { + status = AMT_STATUS_INTERNAL_ERROR; + goto out; + } + } +out: + return status; +} + +static uint32_t amt_verify_response_header(uint32_t command, + const struct amt_host_if_msg_header *resp_hdr, + uint32_t response_size) +{ + if (response_size < sizeof(struct amt_host_if_resp_header)) { + return AMT_STATUS_INTERNAL_ERROR; + } else if (response_size != (resp_hdr->length + + sizeof(struct amt_host_if_msg_header))) { + return AMT_STATUS_INTERNAL_ERROR; + } else if (resp_hdr->command != command) { + return AMT_STATUS_INTERNAL_ERROR; + } else if (resp_hdr->_reserved != 0) { + return AMT_STATUS_INTERNAL_ERROR; + } else if (resp_hdr->version.major != AMT_MAJOR_VERSION || + resp_hdr->version.minor < AMT_MINOR_VERSION) { + return AMT_STATUS_INTERNAL_ERROR; + } + return AMT_STATUS_SUCCESS; +} + +static uint32_t amt_host_if_call(struct amt_host_if *acmd, + const unsigned char *command, ssize_t command_sz, + uint8_t **read_buf, uint32_t rcmd, + unsigned int expected_sz) +{ + uint32_t in_buf_sz; + uint32_t out_buf_sz; + ssize_t written; + uint32_t status; + struct amt_host_if_resp_header *msg_hdr; + + in_buf_sz = acmd->mei_cl.buf_size; + *read_buf = (uint8_t *)malloc(sizeof(uint8_t) * in_buf_sz); + if (*read_buf == NULL) + return AMT_STATUS_SDK_RESOURCES; + memset(*read_buf, 0, in_buf_sz); + msg_hdr = (struct amt_host_if_resp_header *)*read_buf; + + written = mei_send_msg(&acmd->mei_cl, + command, command_sz, acmd->send_timeout); + if (written != command_sz) + return AMT_STATUS_INTERNAL_ERROR; + + out_buf_sz = mei_recv_msg(&acmd->mei_cl, *read_buf, in_buf_sz, 2000); + if (out_buf_sz <= 0) + return AMT_STATUS_HOST_IF_EMPTY_RESPONSE; + + status = msg_hdr->status; + if (status != AMT_STATUS_SUCCESS) + return status; + + status = amt_verify_response_header(rcmd, + &msg_hdr->header, out_buf_sz); + if (status != AMT_STATUS_SUCCESS) + return status; + + if (expected_sz && expected_sz != out_buf_sz) + return AMT_STATUS_INTERNAL_ERROR; + + return AMT_STATUS_SUCCESS; +} + + +static uint32_t amt_get_code_versions(struct amt_host_if *cmd, + struct amt_code_versions *versions) +{ + struct amt_host_if_resp_header *response = NULL; + uint32_t status; + + status = amt_host_if_call(cmd, + (const unsigned char *)&CODE_VERSION_REQ, + sizeof(CODE_VERSION_REQ), + (uint8_t **)&response, + AMT_HOST_IF_CODE_VERSIONS_RESPONSE, 0); + + if (status != AMT_STATUS_SUCCESS) + goto out; + + status = amt_verify_code_versions(response); + if (status != AMT_STATUS_SUCCESS) + goto out; + + memcpy(versions, response->data, sizeof(struct amt_code_versions)); +out: + if (response != NULL) + free(response); + + return status; +} + +/************************** end of amt_host_if_command ***********************/ +int main(int argc, char **argv) +{ + struct amt_code_versions ver; + struct amt_host_if acmd; + unsigned int i; + uint32_t status; + int ret; + bool verbose; + + verbose = (argc > 1 && strcmp(argv[1], "-v") == 0); + + if (!amt_host_if_init(&acmd, 5000, verbose)) { + ret = 1; + goto out; + } + + status = amt_get_code_versions(&acmd, &ver); + + amt_host_if_deinit(&acmd); + + switch (status) { + case AMT_STATUS_HOST_IF_EMPTY_RESPONSE: + printf("Intel AMT: DISABLED\n"); + ret = 0; + break; + case AMT_STATUS_SUCCESS: + printf("Intel AMT: ENABLED\n"); + for (i = 0; i < ver.count; i++) { + printf("%s:\t%s\n", ver.versions[i].description.string, + ver.versions[i].version.string); + } + ret = 0; + break; + default: + printf("An error has occurred\n"); + ret = 1; + break; + } + +out: + return ret; +} diff --git a/drivers/staging/mei/mei.h b/drivers/staging/mei/mei.h new file mode 100644 index 00000000..bc0d8b69 --- /dev/null +++ b/drivers/staging/mei/mei.h @@ -0,0 +1,110 @@ +/****************************************************************************** + * Intel Management Engine Interface (Intel MEI) Linux driver + * Intel MEI Interface Header + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2003 - 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Corporation. + * linux-mei@linux.intel.com + * http://www.intel.com + * + * BSD LICENSE + * + * Copyright(c) 2003 - 2012 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef _LINUX_MEI_H +#define _LINUX_MEI_H + +#include + +/* + * This IOCTL is used to associate the current file descriptor with a + * FW Client (given by UUID). This opens a communication channel + * between a host client and a FW client. From this point every read and write + * will communicate with the associated FW client. + * Only in close() (file_operation release()) the communication between + * the clients is disconnected + * + * The IOCTL argument is a struct with a union that contains + * the input parameter and the output parameter for this IOCTL. + * + * The input parameter is UUID of the FW Client. + * The output parameter is the properties of the FW client + * (FW protocol version and max message size). + * + */ +#define IOCTL_MEI_CONNECT_CLIENT \ + _IOWR('H' , 0x01, struct mei_connect_client_data) + +/* + * Intel MEI client information struct + */ +struct mei_client { + __u32 max_msg_length; + __u8 protocol_version; + __u8 reserved[3]; +}; + +/* + * IOCTL Connect Client Data structure + */ +struct mei_connect_client_data { + union { + uuid_le in_client_uuid; + struct mei_client out_client_properties; + }; +}; + +#endif /* _LINUX_MEI_H */ diff --git a/drivers/staging/mei/mei.txt b/drivers/staging/mei/mei.txt new file mode 100644 index 00000000..2785697d --- /dev/null +++ b/drivers/staging/mei/mei.txt @@ -0,0 +1,215 @@ +Intel(R) Management Engine Interface (Intel(R) MEI) +======================= + +Introduction +======================= + +The Intel Management Engine (Intel ME) is an isolated and protected computing +resource (Co-processor) residing inside certain Intel chipsets. The Intel ME +provides support for computer/IT management features. The feature set +depends on the Intel chipset SKU. + +The Intel Management Engine Interface (Intel MEI, previously known as HECI) +is the interface between the Host and Intel ME. This interface is exposed +to the host as a PCI device. The Intel MEI Driver is in charge of the +communication channel between a host application and the Intel ME feature. + +Each Intel ME feature (Intel ME Client) is addressed by a GUID/UUID and +each client has its own protocol. The protocol is message-based with a +header and payload up to 512 bytes. + +Prominent usage of the Intel ME Interface is to communicate with Intel(R) +Active Management Technology (Intel AMT)implemented in firmware running on +the Intel ME. + +Intel AMT provides the ability to manage a host remotely out-of-band (OOB) +even when the operating system running on the host processor has crashed or +is in a sleep state. + +Some examples of Intel AMT usage are: + - Monitoring hardware state and platform components + - Remote power off/on (useful for green computing or overnight IT + maintenance) + - OS updates + - Storage of useful platform information such as software assets + - Built-in hardware KVM + - Selective network isolation of Ethernet and IP protocol flows based + on policies set by a remote management console + - IDE device redirection from remote management console + +Intel AMT (OOB) communication is based on SOAP (deprecated +starting with Release 6.0) over HTTP/S or WS-Management protocol over +HTTP/S that are received from a remote management console application. + +For more information about Intel AMT: +http://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide + +Intel MEI Driver +======================= + +The driver exposes a misc device called /dev/mei. + +An application maintains communication with an Intel ME feature while +/dev/mei is open. The binding to a specific features is performed by calling +MEI_CONNECT_CLIENT_IOCTL, which passes the desired UUID. +The number of instances of an Intel ME feature that can be opened +at the same time depends on the Intel ME feature, but most of the +features allow only a single instance. + +The Intel AMT Host Interface (Intel AMTHI) feature supports multiple +simultaneous user applications. Therefore, the Intel MEI driver handles +this internally by maintaining request queues for the applications. + +The driver is oblivious to data that is passed between firmware feature +and host application. + +Because some of the Intel ME features can change the system +configuration, the driver by default allows only a privileged +user to access it. + +A code snippet for an application communicating with +Intel AMTHI client: + struct mei_connect_client_data data; + fd = open(MEI_DEVICE); + + data.d.in_client_uuid = AMTHI_UUID; + + ioctl(fd, IOCTL_MEI_CONNECT_CLIENT, &data); + + printf("Ver=%d, MaxLen=%ld\n", + data.d.in_client_uuid.protocol_version, + data.d.in_client_uuid.max_msg_length); + + [...] + + write(fd, amthi_req_data, amthi_req_data_len); + + [...] + + read(fd, &amthi_res_data, amthi_res_data_len); + + [...] + close(fd); + +IOCTL: +====== +The Intel MEI Driver supports the following IOCTL command: + IOCTL_MEI_CONNECT_CLIENT Connect to firmware Feature (client). + + usage: + struct mei_connect_client_data clientData; + ioctl(fd, IOCTL_MEI_CONNECT_CLIENT, &clientData); + + inputs: + mei_connect_client_data struct contain the following + input field: + + in_client_uuid - UUID of the FW Feature that needs + to connect to. + outputs: + out_client_properties - Client Properties: MTU and Protocol Version. + + error returns: + EINVAL Wrong IOCTL Number + ENODEV Device or Connection is not initialized or ready. + (e.g. Wrong UUID) + ENOMEM Unable to allocate memory to client internal data. + EFAULT Fatal Error (e.g. Unable to access user input data) + EBUSY Connection Already Open + + Notes: + max_msg_length (MTU) in client properties describes the maximum + data that can be sent or received. (e.g. if MTU=2K, can send + requests up to bytes 2k and received responses upto 2k bytes). + +Intel ME Applications: +============== + +1) Intel Local Management Service (Intel LMS) + + Applications running locally on the platform communicate with Intel AMT Release + 2.0 and later releases in the same way that network applications do via SOAP + over HTTP (deprecated starting with Release 6.0) or with WS-Management over + SOAP over HTTP. This means that some Intel AMT features can be accessed from a + local application using the same network interface as a remote application + communicating with Intel AMT over the network. + + When a local application sends a message addressed to the local Intel AMT host + name, the Intel LMS, which listens for traffic directed to the host name, + intercepts the message and routes it to the Intel MEI. + For more information: + http://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide + Under "About Intel AMT" => "Local Access" + + For downloading Intel LMS: + http://software.intel.com/en-us/articles/download-the-latest-intel-amt-open-source-drivers/ + + The Intel LMS opens a connection using the Intel MEI driver to the Intel LMS + firmware feature using a defined UUID and then communicates with the feature + using a protocol called Intel AMT Port Forwarding Protocol(Intel APF protocol). + The protocol is used to maintain multiple sessions with Intel AMT from a + single application. + + See the protocol specification in the Intel AMT Software Development Kit(SDK) + http://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide + Under "SDK Resources" => "Intel(R) vPro(TM) Gateway(MPS)" + => "Information for Intel(R) vPro(TM) Gateway Developers" + => "Description of the Intel AMT Port Forwarding (APF)Protocol" + + 2) Intel AMT Remote configuration using a Local Agent + A Local Agent enables IT personnel to configure Intel AMT out-of-the-box + without requiring installing additional data to enable setup. The remote + configuration process may involve an ISV-developed remote configuration + agent that runs on the host. + For more information: + http://software.intel.com/sites/manageability/AMT_Implementation_and_Reference_Guide + Under "Setup and Configuration of Intel AMT" => + "SDK Tools Supporting Setup and Configuration" => + "Using the Local Agent Sample" + + An open source Intel AMT configuration utility, implementing a local agent + that accesses the Intel MEI driver, can be found here: + http://software.intel.com/en-us/articles/download-the-latest-intel-amt-open-source-drivers/ + + +Intel AMT OS Health Watchdog: +============================= +The Intel AMT Watchdog is an OS Health (Hang/Crash) watchdog. +Whenever the OS hangs or crashes, Intel AMT will send an event +to any subscriber to this event. This mechanism means that +IT knows when a platform crashes even when there is a hard failure on the host. + +The Intel AMT Watchdog is composed of two parts: + 1) Firmware feature - receives the heartbeats + and sends an event when the heartbeats stop. + 2) Intel MEI driver - connects to the watchdog feature, configures the + watchdog and sends the heartbeats. + +The Intel MEI driver uses the kernel watchdog to configure the Intel AMT +Watchdog and to send heartbeats to it. The default timeout of the +watchdog is 120 seconds. + +If the Intel AMT Watchdog feature does not exist (i.e. the connection failed), +the Intel MEI driver will disable the sending of heartbeats. + +Supported Chipsets: +================== +7 Series Chipset Family +6 Series Chipset Family +5 Series Chipset Family +4 Series Chipset Family +Mobile 4 Series Chipset Family +ICH9 +82946GZ/GL +82G35 Express +82Q963/Q965 +82P965/G965 +Mobile PM965/GM965 +Mobile GME965/GLE960 +82Q35 Express +82G33/G31/P35/P31 Express +82Q33 Express +82X38/X48 Express + +--- +linux-mei@linux.intel.com diff --git a/drivers/staging/mei/mei_dev.h b/drivers/staging/mei/mei_dev.h new file mode 100644 index 00000000..10b1b4e2 --- /dev/null +++ b/drivers/staging/mei/mei_dev.h @@ -0,0 +1,428 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#ifndef _MEI_DEV_H_ +#define _MEI_DEV_H_ + +#include +#include +#include "mei.h" +#include "hw.h" + +/* + * watch dog definition + */ +#define MEI_WATCHDOG_DATA_SIZE 16 +#define MEI_START_WD_DATA_SIZE 20 +#define MEI_WD_PARAMS_SIZE 4 +#define MEI_WD_STATE_INDEPENDENCE_MSG_SENT (1 << 0) + +#define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32)) + +/* + * MEI PCI Device object + */ +extern struct pci_dev *mei_device; + + +/* + * AMTHI Client UUID + */ +extern const uuid_le mei_amthi_guid; + +/* + * Watchdog Client UUID + */ +extern const uuid_le mei_wd_guid; + +/* + * Watchdog independence state message + */ +extern const u8 mei_wd_state_independence_msg[3][4]; + +/* + * Number of File descriptors/handles + * that can be opened to the driver. + * + * Limit to 253: 255 Total Clients + * minus internal client for AMTHI + * minus internal client for Watchdog + */ +#define MEI_MAX_OPEN_HANDLE_COUNT 253 + +/* + * Number of Maximum MEI Clients + */ +#define MEI_CLIENTS_MAX 255 + +/* File state */ +enum file_state { + MEI_FILE_INITIALIZING = 0, + MEI_FILE_CONNECTING, + MEI_FILE_CONNECTED, + MEI_FILE_DISCONNECTING, + MEI_FILE_DISCONNECTED +}; + +/* MEI device states */ +enum mei_states { + MEI_INITIALIZING = 0, + MEI_INIT_CLIENTS, + MEI_ENABLED, + MEI_RESETING, + MEI_DISABLED, + MEI_RECOVERING_FROM_RESET, + MEI_POWER_DOWN, + MEI_POWER_UP +}; + +/* init clients states*/ +enum mei_init_clients_states { + MEI_START_MESSAGE = 0, + MEI_ENUM_CLIENTS_MESSAGE, + MEI_CLIENT_PROPERTIES_MESSAGE +}; + +enum iamthif_states { + MEI_IAMTHIF_IDLE, + MEI_IAMTHIF_WRITING, + MEI_IAMTHIF_FLOW_CONTROL, + MEI_IAMTHIF_READING, + MEI_IAMTHIF_READ_COMPLETE +}; + +enum mei_file_transaction_states { + MEI_IDLE, + MEI_WRITING, + MEI_WRITE_COMPLETE, + MEI_FLOW_CONTROL, + MEI_READING, + MEI_READ_COMPLETE +}; + +/* MEI CB */ +enum mei_cb_major_types { + MEI_READ = 0, + MEI_WRITE, + MEI_IOCTL, + MEI_OPEN, + MEI_CLOSE +}; + +/* + * Intel MEI message data struct + */ +struct mei_message_data { + u32 size; + unsigned char *data; +} __packed; + + +struct mei_cl_cb { + struct list_head cb_list; + enum mei_cb_major_types major_file_operations; + void *file_private; + struct mei_message_data request_buffer; + struct mei_message_data response_buffer; + unsigned long information; + unsigned long read_time; + struct file *file_object; +}; + +/* MEI client instance carried as file->pirvate_data*/ +struct mei_cl { + struct list_head link; + struct mei_device *dev; + enum file_state state; + wait_queue_head_t tx_wait; + wait_queue_head_t rx_wait; + wait_queue_head_t wait; + int read_pending; + int status; + /* ID of client connected */ + u8 host_client_id; + u8 me_client_id; + u8 mei_flow_ctrl_creds; + u8 timer_count; + enum mei_file_transaction_states reading_state; + enum mei_file_transaction_states writing_state; + int sm_state; + struct mei_cl_cb *read_cb; +}; + +struct mei_io_list { + struct mei_cl_cb mei_cb; +}; + +/* MEI private device struct */ +struct mei_device { + struct pci_dev *pdev; /* pointer to pci device struct */ + /* + * lists of queues + */ + /* array of pointers to aio lists */ + struct mei_io_list read_list; /* driver read queue */ + struct mei_io_list write_list; /* driver write queue */ + struct mei_io_list write_waiting_list; /* write waiting queue */ + struct mei_io_list ctrl_wr_list; /* managed write IOCTL list */ + struct mei_io_list ctrl_rd_list; /* managed read IOCTL list */ + struct mei_io_list amthi_cmd_list; /* amthi list for cmd waiting */ + + /* driver managed amthi list for reading completed amthi cmd data */ + struct mei_io_list amthi_read_complete_list; + /* + * list of files + */ + struct list_head file_list; + long open_handle_count; + /* + * memory of device + */ + unsigned int mem_base; + unsigned int mem_length; + void __iomem *mem_addr; + /* + * lock for the device + */ + struct mutex device_lock; /* device lock */ + struct delayed_work timer_work; /* MEI timer delayed work (timeouts) */ + bool recvd_msg; + /* + * hw states of host and fw(ME) + */ + u32 host_hw_state; + u32 me_hw_state; + /* + * waiting queue for receive message from FW + */ + wait_queue_head_t wait_recvd_msg; + wait_queue_head_t wait_stop_wd; + + /* + * mei device states + */ + enum mei_states mei_state; + enum mei_init_clients_states init_clients_state; + u16 init_clients_timer; + bool stop; + bool need_reset; + + u32 extra_write_index; + unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ + u32 wr_msg_buf[128]; /* used for control messages */ + u32 ext_msg_buf[8]; /* for control responses */ + u32 rd_msg_hdr; + + struct hbm_version version; + + struct mei_me_client *me_clients; /* Note: memory has to be allocated */ + DECLARE_BITMAP(me_clients_map, MEI_CLIENTS_MAX); + DECLARE_BITMAP(host_clients_map, MEI_CLIENTS_MAX); + u8 me_clients_num; + u8 me_client_presentation_num; + u8 me_client_index; + bool mei_host_buffer_is_empty; + + struct mei_cl wd_cl; + bool wd_pending; + bool wd_stopped; + bool wd_bypass; /* if false, don't refresh watchdog ME client */ + u16 wd_timeout; /* seconds ((wd_data[1] << 8) + wd_data[0]) */ + u16 wd_due_counter; + unsigned char wd_data[MEI_START_WD_DATA_SIZE]; + + + + struct file *iamthif_file_object; + struct mei_cl iamthif_cl; + struct mei_cl_cb *iamthif_current_cb; + int iamthif_mtu; + unsigned long iamthif_timer; + u32 iamthif_stall_timer; + unsigned char *iamthif_msg_buf; /* Note: memory has to be allocated */ + u32 iamthif_msg_buf_size; + u32 iamthif_msg_buf_index; + enum iamthif_states iamthif_state; + bool iamthif_flow_control_pending; + bool iamthif_ioctl; + bool iamthif_canceled; + + bool wd_interface_reg; +}; + + +/* + * mei init function prototypes + */ +struct mei_device *mei_device_init(struct pci_dev *pdev); +void mei_reset(struct mei_device *dev, int interrupts); +int mei_hw_init(struct mei_device *dev); +int mei_task_initialize_clients(void *data); +int mei_initialize_clients(struct mei_device *dev); +int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl); +void mei_remove_client_from_file_list(struct mei_device *dev, u8 host_client_id); +void mei_host_init_iamthif(struct mei_device *dev); +void mei_allocate_me_clients_storage(struct mei_device *dev); + + +u8 mei_find_me_client_update_filext(struct mei_device *dev, + struct mei_cl *priv, + const uuid_le *cguid, u8 client_id); + +/* + * MEI IO List Functions + */ +void mei_io_list_init(struct mei_io_list *list); +void mei_io_list_flush(struct mei_io_list *list, struct mei_cl *cl); + +/* + * MEI ME Client Functions + */ + +struct mei_cl *mei_cl_allocate(struct mei_device *dev); +void mei_cl_init(struct mei_cl *cl, struct mei_device *dev); +int mei_cl_flush_queues(struct mei_cl *cl); +/** + * mei_cl_cmp_id - tells if file private data have same id + * + * @fe1: private data of 1. file object + * @fe2: private data of 2. file object + * + * returns true - if ids are the same and not NULL + */ +static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, + const struct mei_cl *cl2) +{ + return cl1 && cl2 && + (cl1->host_client_id == cl2->host_client_id) && + (cl1->me_client_id == cl2->me_client_id); +} + + + +/* + * MEI Host Client Functions + */ +void mei_host_start_message(struct mei_device *dev); +void mei_host_enum_clients_message(struct mei_device *dev); +int mei_host_client_properties(struct mei_device *dev); + +/* + * MEI interrupt functions prototype + */ +irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id); +irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id); +void mei_timer(struct work_struct *work); + +/* + * MEI input output function prototype + */ +int mei_ioctl_connect_client(struct file *file, + struct mei_connect_client_data *data); + +int mei_start_read(struct mei_device *dev, struct mei_cl *cl); + +int amthi_write(struct mei_device *dev, struct mei_cl_cb *priv_cb); + +int amthi_read(struct mei_device *dev, struct file *file, + char __user *ubuf, size_t length, loff_t *offset); + +struct mei_cl_cb *find_amthi_read_list_entry(struct mei_device *dev, + struct file *file); + +void mei_run_next_iamthif_cmd(struct mei_device *dev); + +void mei_free_cb_private(struct mei_cl_cb *priv_cb); + +int mei_find_me_client_index(const struct mei_device *dev, uuid_le cuuid); + +/* + * Register Access Function + */ + +/** + * mei_reg_read - Reads 32bit data from the mei device + * + * @dev: the device structure + * @offset: offset from which to read the data + * + * returns register value (u32) + */ +static inline u32 mei_reg_read(struct mei_device *dev, unsigned long offset) +{ + return ioread32(dev->mem_addr + offset); +} + +/** + * mei_reg_write - Writes 32bit data to the mei device + * + * @dev: the device structure + * @offset: offset from which to write the data + * @value: register value to write (u32) + */ +static inline void mei_reg_write(struct mei_device *dev, + unsigned long offset, u32 value) +{ + iowrite32(value, dev->mem_addr + offset); +} + +/** + * mei_hcsr_read - Reads 32bit data from the host CSR + * + * @dev: the device structure + * + * returns the byte read. + */ +static inline u32 mei_hcsr_read(struct mei_device *dev) +{ + return mei_reg_read(dev, H_CSR); +} + +/** + * mei_mecsr_read - Reads 32bit data from the ME CSR + * + * @dev: the device structure + * + * returns ME_CSR_HA register value (u32) + */ +static inline u32 mei_mecsr_read(struct mei_device *dev) +{ + return mei_reg_read(dev, ME_CSR_HA); +} + +/** + * get_me_cb_rw - Reads 32bit data from the mei ME_CB_RW register + * + * @dev: the device structure + * + * returns ME_CB_RW register value (u32) + */ +static inline u32 mei_mecbrw_read(struct mei_device *dev) +{ + return mei_reg_read(dev, ME_CB_RW); +} + + +/* + * mei interface function prototypes + */ +void mei_hcsr_set(struct mei_device *dev); +void mei_csr_clear_his(struct mei_device *dev); + +void mei_enable_interrupts(struct mei_device *dev); +void mei_disable_interrupts(struct mei_device *dev); + +#endif diff --git a/drivers/staging/mei/wd.c b/drivers/staging/mei/wd.c new file mode 100644 index 00000000..cf4c29d1 --- /dev/null +++ b/drivers/staging/mei/wd.c @@ -0,0 +1,379 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "mei_dev.h" +#include "hw.h" +#include "interface.h" +#include "mei.h" + +static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 }; +static const u8 mei_stop_wd_params[] = { 0x02, 0x02, 0x14, 0x10 }; + +const u8 mei_wd_state_independence_msg[3][4] = { + {0x05, 0x02, 0x51, 0x10}, + {0x05, 0x02, 0x52, 0x10}, + {0x07, 0x02, 0x01, 0x10} +}; + +/* + * AMT Watchdog Device + */ +#define INTEL_AMT_WATCHDOG_ID "INTCAMT" + +/* UUIDs for AMT F/W clients */ +const uuid_le mei_wd_guid = UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, 0x89, + 0x9D, 0xA9, 0x15, 0x14, 0xCB, + 0x32, 0xAB); + +void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout) +{ + dev_dbg(&dev->pdev->dev, "timeout=%d.\n", timeout); + memcpy(dev->wd_data, mei_start_wd_params, MEI_WD_PARAMS_SIZE); + memcpy(dev->wd_data + MEI_WD_PARAMS_SIZE, + &timeout, sizeof(u16)); +} + +/** + * host_init_wd - mei initialization wd. + * + * @dev: the device structure + */ +bool mei_wd_host_init(struct mei_device *dev) +{ + bool ret = false; + + mei_cl_init(&dev->wd_cl, dev); + + /* look for WD client and connect to it */ + dev->wd_cl.state = MEI_FILE_DISCONNECTED; + dev->wd_timeout = AMT_WD_DEFAULT_TIMEOUT; + + /* find ME WD client */ + mei_find_me_client_update_filext(dev, &dev->wd_cl, + &mei_wd_guid, MEI_WD_HOST_CLIENT_ID); + + dev_dbg(&dev->pdev->dev, "check wd_cl\n"); + if (MEI_FILE_CONNECTING == dev->wd_cl.state) { + if (mei_connect(dev, &dev->wd_cl)) { + dev_dbg(&dev->pdev->dev, "Failed to connect to WD client\n"); + dev->wd_cl.state = MEI_FILE_DISCONNECTED; + dev->wd_cl.host_client_id = 0; + ret = false; + goto end; + } else { + dev->wd_cl.timer_count = CONNECT_TIMEOUT; + } + } else { + dev_dbg(&dev->pdev->dev, "Failed to find WD client\n"); + ret = false; + goto end; + } + +end: + return ret; +} + +/** + * mei_wd_send - sends watch dog message to fw. + * + * @dev: the device structure + * + * returns 0 if success, + * -EIO when message send fails + * -EINVAL when invalid message is to be sent + */ +int mei_wd_send(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_hdr; + + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = dev->wd_cl.host_client_id; + mei_hdr->me_addr = dev->wd_cl.me_client_id; + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_PARAMS_SIZE)) + mei_hdr->length = MEI_START_WD_DATA_SIZE; + else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_PARAMS_SIZE)) + mei_hdr->length = MEI_WD_PARAMS_SIZE; + else + return -EINVAL; + + return mei_write_message(dev, mei_hdr, dev->wd_data, mei_hdr->length); +} + +/** + * mei_wd_stop - sends watchdog stop message to fw. + * + * @dev: the device structure + * @preserve: indicate if to keep the timeout value + * + * returns 0 if success, + * -EIO when message send fails + * -EINVAL when invalid message is to be sent + */ +int mei_wd_stop(struct mei_device *dev, bool preserve) +{ + int ret; + u16 wd_timeout = dev->wd_timeout; + + cancel_delayed_work(&dev->timer_work); + if (dev->wd_cl.state != MEI_FILE_CONNECTED || !dev->wd_timeout) + return 0; + + dev->wd_timeout = 0; + dev->wd_due_counter = 0; + memcpy(dev->wd_data, mei_stop_wd_params, MEI_WD_PARAMS_SIZE); + dev->stop = true; + + ret = mei_flow_ctrl_creds(dev, &dev->wd_cl); + if (ret < 0) + goto out; + + if (ret && dev->mei_host_buffer_is_empty) { + ret = 0; + dev->mei_host_buffer_is_empty = false; + + if (!mei_wd_send(dev)) { + ret = mei_flow_ctrl_reduce(dev, &dev->wd_cl); + if (ret) + goto out; + } else { + dev_dbg(&dev->pdev->dev, "send stop WD failed\n"); + } + + dev->wd_pending = false; + } else { + dev->wd_pending = true; + } + dev->wd_stopped = false; + mutex_unlock(&dev->device_lock); + + ret = wait_event_interruptible_timeout(dev->wait_stop_wd, + dev->wd_stopped, 10 * HZ); + mutex_lock(&dev->device_lock); + if (dev->wd_stopped) { + dev_dbg(&dev->pdev->dev, "stop wd complete ret=%d.\n", ret); + ret = 0; + } else { + if (!ret) + ret = -ETIMEDOUT; + dev_warn(&dev->pdev->dev, + "stop wd failed to complete ret=%d.\n", ret); + } + + if (preserve) + dev->wd_timeout = wd_timeout; + +out: + return ret; +} + +/* + * mei_wd_ops_start - wd start command from the watchdog core. + * + * @wd_dev - watchdog device struct + * + * returns 0 if success, negative errno code for failure + */ +static int mei_wd_ops_start(struct watchdog_device *wd_dev) +{ + int err = -ENODEV; + struct mei_device *dev; + + dev = pci_get_drvdata(mei_device); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + if (dev->mei_state != MEI_ENABLED) { + dev_dbg(&dev->pdev->dev, "mei_state != MEI_ENABLED mei_state= %d\n", + dev->mei_state); + goto end_unlock; + } + + if (dev->wd_cl.state != MEI_FILE_CONNECTED) { + dev_dbg(&dev->pdev->dev, "MEI Driver is not connected to Watchdog Client\n"); + goto end_unlock; + } + + mei_wd_set_start_timeout(dev, dev->wd_timeout); + + err = 0; +end_unlock: + mutex_unlock(&dev->device_lock); + return err; +} + +/* + * mei_wd_ops_stop - wd stop command from the watchdog core. + * + * @wd_dev - watchdog device struct + * + * returns 0 if success, negative errno code for failure + */ +static int mei_wd_ops_stop(struct watchdog_device *wd_dev) +{ + struct mei_device *dev; + dev = pci_get_drvdata(mei_device); + + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + mei_wd_stop(dev, false); + mutex_unlock(&dev->device_lock); + + return 0; +} + +/* + * mei_wd_ops_ping - wd ping command from the watchdog core. + * + * @wd_dev - watchdog device struct + * + * returns 0 if success, negative errno code for failure + */ +static int mei_wd_ops_ping(struct watchdog_device *wd_dev) +{ + int ret = 0; + struct mei_device *dev; + dev = pci_get_drvdata(mei_device); + + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + if (dev->wd_cl.state != MEI_FILE_CONNECTED) { + dev_dbg(&dev->pdev->dev, "wd is not connected.\n"); + ret = -ENODEV; + goto end; + } + + /* Check if we can send the ping to HW*/ + if (dev->mei_host_buffer_is_empty && + mei_flow_ctrl_creds(dev, &dev->wd_cl) > 0) { + + dev->mei_host_buffer_is_empty = false; + dev_dbg(&dev->pdev->dev, "sending watchdog ping\n"); + + if (mei_wd_send(dev)) { + dev_dbg(&dev->pdev->dev, "wd send failed.\n"); + ret = -EIO; + goto end; + } + + if (mei_flow_ctrl_reduce(dev, &dev->wd_cl)) { + dev_dbg(&dev->pdev->dev, "mei_flow_ctrl_reduce() failed.\n"); + ret = -EIO; + goto end; + } + + } else { + dev->wd_pending = true; + } + +end: + mutex_unlock(&dev->device_lock); + return ret; +} + +/* + * mei_wd_ops_set_timeout - wd set timeout command from the watchdog core. + * + * @wd_dev - watchdog device struct + * @timeout - timeout value to set + * + * returns 0 if success, negative errno code for failure + */ +static int mei_wd_ops_set_timeout(struct watchdog_device *wd_dev, unsigned int timeout) +{ + struct mei_device *dev; + dev = pci_get_drvdata(mei_device); + + if (!dev) + return -ENODEV; + + /* Check Timeout value */ + if (timeout < AMT_WD_MIN_TIMEOUT || timeout > AMT_WD_MAX_TIMEOUT) + return -EINVAL; + + mutex_lock(&dev->device_lock); + + dev->wd_timeout = timeout; + wd_dev->timeout = timeout; + mei_wd_set_start_timeout(dev, dev->wd_timeout); + + mutex_unlock(&dev->device_lock); + + return 0; +} + +/* + * Watchdog Device structs + */ +static const struct watchdog_ops wd_ops = { + .owner = THIS_MODULE, + .start = mei_wd_ops_start, + .stop = mei_wd_ops_stop, + .ping = mei_wd_ops_ping, + .set_timeout = mei_wd_ops_set_timeout, +}; +static const struct watchdog_info wd_info = { + .identity = INTEL_AMT_WATCHDOG_ID, + .options = WDIOF_KEEPALIVEPING, +}; + +struct watchdog_device amt_wd_dev = { + .info = &wd_info, + .ops = &wd_ops, + .timeout = AMT_WD_DEFAULT_TIMEOUT, + .min_timeout = AMT_WD_MIN_TIMEOUT, + .max_timeout = AMT_WD_MAX_TIMEOUT, +}; + + +void mei_watchdog_register(struct mei_device *dev) +{ + dev_dbg(&dev->pdev->dev, "dev->wd_timeout =%d.\n", dev->wd_timeout); + + dev->wd_due_counter = !!dev->wd_timeout; + + if (watchdog_register_device(&amt_wd_dev)) { + dev_err(&dev->pdev->dev, "unable to register watchdog device.\n"); + dev->wd_interface_reg = false; + } else { + dev_dbg(&dev->pdev->dev, "successfully register watchdog interface.\n"); + dev->wd_interface_reg = true; + } +} + +void mei_watchdog_unregister(struct mei_device *dev) +{ + if (dev->wd_interface_reg) + watchdog_unregister_device(&amt_wd_dev); + dev->wd_interface_reg = false; +} + diff --git a/drivers/staging/nvec/Kconfig b/drivers/staging/nvec/Kconfig new file mode 100644 index 00000000..731301f5 --- /dev/null +++ b/drivers/staging/nvec/Kconfig @@ -0,0 +1,33 @@ +config MFD_NVEC + bool "NV Tegra Embedded Controller SMBus Interface" + depends on I2C && GPIOLIB && ARCH_TEGRA + help + Say Y here to enable support for a nVidia compliant embedded + controller. + +config KEYBOARD_NVEC + bool "Keyboard on nVidia compliant EC" + depends on MFD_NVEC && INPUT + help + Say Y here to enable support for a keyboard connected to + a nVidia compliant embedded controller. + +config SERIO_NVEC_PS2 + bool "PS2 on nVidia EC" + depends on MFD_NVEC && SERIO + help + Say Y here to enable support for a Touchpad / Mouse connected + to a nVidia compliant embedded controller. + +config NVEC_POWER + bool "NVEC charger and battery" + depends on MFD_NVEC && POWER_SUPPLY + help + Say Y to enable support for battery and charger interface for + nVidia compliant embedded controllers. + +config NVEC_LEDS + bool "NVEC leds" + depends on MFD_NVEC && LEDS_CLASS + help + Say Y to enable yellow side leds on AC100 or other nVidia tegra nvec leds diff --git a/drivers/staging/nvec/Makefile b/drivers/staging/nvec/Makefile new file mode 100644 index 00000000..b844d604 --- /dev/null +++ b/drivers/staging/nvec/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_SERIO_NVEC_PS2) += nvec_ps2.o +obj-$(CONFIG_MFD_NVEC) += nvec.o +obj-$(CONFIG_NVEC_POWER) += nvec_power.o +obj-$(CONFIG_KEYBOARD_NVEC) += nvec_kbd.o +obj-$(CONFIG_NVEC_LEDS) += nvec_leds.o diff --git a/drivers/staging/nvec/README b/drivers/staging/nvec/README new file mode 100644 index 00000000..9a320b7f --- /dev/null +++ b/drivers/staging/nvec/README @@ -0,0 +1,14 @@ +NVEC: An NVidia compliant Embedded Controller Protocol Implemenation + +This is an implementation of the NVEC protocol used to communicate with an +embedded controller (EC) via I2C bus. The EC is an I2C master while the host +processor is the I2C slave. Requests from the host processor to the EC are +started by triggering a gpio line. + +There is no written documentation of the protocol available to the public, +but the source code[1] of the published nvec reference drivers can be a guide. +This driver is currently only used by the AC100 project[2], but it is likely, +that other Tegra boards (not yet mainlined, if ever) also use it. + +[1] e.g. http://nv-tegra.nvidia.com/gitweb/?p=linux-2.6.git;a=tree;f=arch/arm/mach-tegra/nvec;hb=android-tegra-2.6.32 +[2] http://gitorious.org/ac100, http://launchpad.net/ac100 diff --git a/drivers/staging/nvec/TODO b/drivers/staging/nvec/TODO new file mode 100644 index 00000000..f950ab89 --- /dev/null +++ b/drivers/staging/nvec/TODO @@ -0,0 +1,12 @@ +ToDo list (incomplete, unordered) + - add compile as module support + - fix clk usage + should not be using clk_get_sys(), but clk_get(&pdev->dev, conn) + where conn is either NULL if the device only has one clock, or + the device specific name if it has multiple clocks. + - move half of the nvec init stuff to i2c-tegra.c + - move event handling to nvec_events + - finish suspend/resume support + - modifiy the sync_write method to return the received + message in a variable (and return the error code). + - add support for more device implementations diff --git a/drivers/staging/nvec/nvec-keytable.h b/drivers/staging/nvec/nvec-keytable.h new file mode 100644 index 00000000..1dc22cb8 --- /dev/null +++ b/drivers/staging/nvec/nvec-keytable.h @@ -0,0 +1,307 @@ +/* + * drivers/input/keyboard/tegra-nvec.c + * + * Keyboard class input driver for keyboards connected to an NvEc compliant + * embedded controller + * + * Copyright (c) 2009, NVIDIA Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +static unsigned short code_tab_102us[] = { + /* 0x00 */ + KEY_GRAVE, + KEY_ESC, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_0, + KEY_MINUS, + KEY_EQUAL, + KEY_BACKSPACE, + KEY_TAB, + /* 0x10 */ + KEY_Q, + KEY_W, + KEY_E, + KEY_R, + KEY_T, + KEY_Y, + KEY_U, + KEY_I, + KEY_O, + KEY_P, + KEY_LEFTBRACE, + KEY_RIGHTBRACE, + KEY_ENTER, + KEY_LEFTCTRL, + KEY_A, + KEY_S, + /* 0x20 */ + KEY_D, + KEY_F, + KEY_G, + KEY_H, + KEY_J, + KEY_K, + KEY_L, + KEY_SEMICOLON, + KEY_APOSTROPHE, + KEY_GRAVE, + KEY_LEFTSHIFT, + KEY_BACKSLASH, + KEY_Z, + KEY_X, + KEY_C, + KEY_V, + /* 0x30 */ + KEY_B, + KEY_N, + KEY_M, + KEY_COMMA, + KEY_DOT, + KEY_SLASH, + KEY_RIGHTSHIFT, + KEY_KPASTERISK, + KEY_LEFTALT, + KEY_SPACE, + KEY_CAPSLOCK, + KEY_F1, + KEY_F2, + KEY_F3, + KEY_F4, + KEY_F5, + /* 0x40 */ + KEY_F6, + KEY_F7, + KEY_F8, + KEY_F9, + KEY_F10, + KEY_FN, + /* VK_SCROLL */ + 0, + KEY_KP7, + KEY_KP8, + KEY_KP9, + KEY_KPMINUS, + KEY_KP4, + KEY_KP5, + KEY_KP6, + KEY_KPPLUS, + KEY_KP1, + /* 0x50 */ + KEY_KP2, + KEY_KP3, + KEY_KP0, + KEY_KPDOT, + /* VK_SNAPSHOT */ + KEY_MENU, + KEY_POWER, + /* VK_OEM_102 */ + KEY_102ND, + KEY_F11, + KEY_F12, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x60 */ + 0, + 0, + 0, + KEY_SEARCH, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x70 */ + 0, + 0, + 0, + KEY_KP5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + KEY_KP9, +}; + +static unsigned short extcode_tab_us102[] = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x10 */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* VK_MEDIA_NEXT_TRACK */ + 0, + 0, + 0, + /* VK_RETURN */ + 0, + KEY_RIGHTCTRL, + 0, + 0, + /* 0x20 */ + KEY_MUTE, + /* VK_LAUNCH_APP1 */ + 0, + /* VK_MEDIA_PLAY_PAUSE */ + 0, + 0, + /* VK_MEDIA_STOP */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x30 */ + KEY_VOLUMEUP, + 0, + /* VK_BROWSER_HOME */ + 0, + 0, + 0, + /* VK_DIVIDE */ + KEY_KPSLASH, + 0, + /* VK_SNAPSHOT */ + KEY_SYSRQ, + /* VK_RMENU */ + KEY_RIGHTALT, + /* VK_OEM_NV_BACKLIGHT_UP */ + 0, + /* VK_OEM_NV_BACKLIGHT_DN */ + 0, + /* VK_OEM_NV_BACKLIGHT_AUTOTOGGLE */ + 0, + /* VK_OEM_NV_POWER_INFO */ + 0, + /* VK_OEM_NV_WIFI_TOGGLE */ + 0, + /* VK_OEM_NV_DISPLAY_SELECT */ + 0, + /* VK_OEM_NV_AIRPLANE_TOGGLE */ + 0, + /* 0x40 */ + 0, + KEY_LEFT, + 0, + 0, + 0, + 0, + KEY_CANCEL, + KEY_HOME, + KEY_UP, + KEY_PAGEUP, + 0, + KEY_LEFT, + 0, + KEY_RIGHT, + 0, + KEY_END, + /* 0x50 */ + KEY_DOWN, + KEY_PAGEDOWN, + KEY_INSERT, + KEY_DELETE, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + KEY_LEFTMETA, + 0, + KEY_ESC, + KEY_KPMINUS, + 0, + 0, + 0, + 0, + 0, + 0, + /* VK_BROWSER_SEARCH */ + 0, + /* VK_BROWSER_FAVORITES */ + 0, + /* VK_BROWSER_REFRESH */ + 0, + /* VK_BROWSER_STOP */ + 0, + /* VK_BROWSER_FORWARD */ + 0, + /* VK_BROWSER_BACK */ + 0, + /* VK_LAUNCH_APP2 */ + 0, + /* VK_LAUNCH_MAIL */ + 0, + /* VK_LAUNCH_MEDIA_SELECT */ + 0, +}; + +static unsigned short *code_tabs[] = { code_tab_102us, extcode_tab_us102 }; diff --git a/drivers/staging/nvec/nvec.c b/drivers/staging/nvec/nvec.c new file mode 100644 index 00000000..3c600888 --- /dev/null +++ b/drivers/staging/nvec/nvec.c @@ -0,0 +1,942 @@ +/* + * NVEC: NVIDIA compliant embedded controller interface + * + * Copyright (C) 2011 The AC100 Kernel Team + * + * Authors: Pierre-Hugues Husson + * Ilya Petrov + * Marc Dietrich + * Julian Andres Klode + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +/* #define DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "nvec.h" + +#define I2C_CNFG 0x00 +#define I2C_CNFG_PACKET_MODE_EN (1<<10) +#define I2C_CNFG_NEW_MASTER_SFM (1<<11) +#define I2C_CNFG_DEBOUNCE_CNT_SHIFT 12 + +#define I2C_SL_CNFG 0x20 +#define I2C_SL_NEWSL (1<<2) +#define I2C_SL_NACK (1<<1) +#define I2C_SL_RESP (1<<0) +#define I2C_SL_IRQ (1<<3) +#define END_TRANS (1<<4) +#define RCVD (1<<2) +#define RNW (1<<1) + +#define I2C_SL_RCVD 0x24 +#define I2C_SL_STATUS 0x28 +#define I2C_SL_ADDR1 0x2c +#define I2C_SL_ADDR2 0x30 +#define I2C_SL_DELAY_COUNT 0x3c + +/** + * enum nvec_msg_category - Message categories for nvec_msg_alloc() + * @NVEC_MSG_RX: The message is an incoming message (from EC) + * @NVEC_MSG_TX: The message is an outgoing message (to EC) + */ +enum nvec_msg_category { + NVEC_MSG_RX, + NVEC_MSG_TX, +}; + +static const unsigned char EC_DISABLE_EVENT_REPORTING[3] = "\x04\x00\x00"; +static const unsigned char EC_ENABLE_EVENT_REPORTING[3] = "\x04\x00\x01"; +static const unsigned char EC_GET_FIRMWARE_VERSION[2] = "\x07\x15"; + +static struct nvec_chip *nvec_power_handle; + +static struct mfd_cell nvec_devices[] = { + { + .name = "nvec-kbd", + .id = 1, + }, + { + .name = "nvec-mouse", + .id = 1, + }, + { + .name = "nvec-power", + .id = 1, + }, + { + .name = "nvec-power", + .id = 2, + }, + { + .name = "nvec-leds", + .id = 1, + }, +}; + +/** + * nvec_register_notifier - Register a notifier with nvec + * @nvec: A &struct nvec_chip + * @nb: The notifier block to register + * + * Registers a notifier with @nvec. The notifier will be added to an atomic + * notifier chain that is called for all received messages except those that + * correspond to a request initiated by nvec_write_sync(). + */ +int nvec_register_notifier(struct nvec_chip *nvec, struct notifier_block *nb, + unsigned int events) +{ + return atomic_notifier_chain_register(&nvec->notifier_list, nb); +} +EXPORT_SYMBOL_GPL(nvec_register_notifier); + +/** + * nvec_status_notifier - The final notifier + * + * Prints a message about control events not handled in the notifier + * chain. + */ +static int nvec_status_notifier(struct notifier_block *nb, + unsigned long event_type, void *data) +{ + unsigned char *msg = (unsigned char *)data; + + if (event_type != NVEC_CNTL) + return NOTIFY_DONE; + + printk(KERN_WARNING "unhandled msg type %ld\n", event_type); + print_hex_dump(KERN_WARNING, "payload: ", DUMP_PREFIX_NONE, 16, 1, + msg, msg[1] + 2, true); + + return NOTIFY_OK; +} + +/** + * nvec_msg_alloc: + * @nvec: A &struct nvec_chip + * @category: Pool category, see &enum nvec_msg_category + * + * Allocate a single &struct nvec_msg object from the message pool of + * @nvec. The result shall be passed to nvec_msg_free() if no longer + * used. + * + * Outgoing messages are placed in the upper 75% of the pool, keeping the + * lower 25% available for RX buffers only. The reason is to prevent a + * situation where all buffers are full and a message is thus endlessly + * retried because the response could never be processed. + */ +static struct nvec_msg *nvec_msg_alloc(struct nvec_chip *nvec, + enum nvec_msg_category category) +{ + int i = (category == NVEC_MSG_TX) ? (NVEC_POOL_SIZE / 4) : 0; + + for (; i < NVEC_POOL_SIZE; i++) { + if (atomic_xchg(&nvec->msg_pool[i].used, 1) == 0) { + dev_vdbg(nvec->dev, "INFO: Allocate %i\n", i); + return &nvec->msg_pool[i]; + } + } + + dev_err(nvec->dev, "could not allocate %s buffer\n", + (category == NVEC_MSG_TX) ? "TX" : "RX"); + + return NULL; +} + +/** + * nvec_msg_free: + * @nvec: A &struct nvec_chip + * @msg: A message (must be allocated by nvec_msg_alloc() and belong to @nvec) + * + * Free the given message + */ +inline void nvec_msg_free(struct nvec_chip *nvec, struct nvec_msg *msg) +{ + if (msg != &nvec->tx_scratch) + dev_vdbg(nvec->dev, "INFO: Free %ti\n", msg - nvec->msg_pool); + atomic_set(&msg->used, 0); +} +EXPORT_SYMBOL_GPL(nvec_msg_free); + +/** + * nvec_msg_is_event - Return %true if @msg is an event + * @msg: A message + */ +static bool nvec_msg_is_event(struct nvec_msg *msg) +{ + return msg->data[0] >> 7; +} + +/** + * nvec_msg_size - Get the size of a message + * @msg: The message to get the size for + * + * This only works for received messages, not for outgoing messages. + */ +static size_t nvec_msg_size(struct nvec_msg *msg) +{ + bool is_event = nvec_msg_is_event(msg); + int event_length = (msg->data[0] & 0x60) >> 5; + + /* for variable size, payload size in byte 1 + count (1) + cmd (1) */ + if (!is_event || event_length == NVEC_VAR_SIZE) + return (msg->pos || msg->size) ? (msg->data[1] + 2) : 0; + else if (event_length == NVEC_2BYTES) + return 2; + else if (event_length == NVEC_3BYTES) + return 3; + else + return 0; +} + +/** + * nvec_gpio_set_value - Set the GPIO value + * @nvec: A &struct nvec_chip + * @value: The value to write (0 or 1) + * + * Like gpio_set_value(), but generating debugging information + */ +static void nvec_gpio_set_value(struct nvec_chip *nvec, int value) +{ + dev_dbg(nvec->dev, "GPIO changed from %u to %u\n", + gpio_get_value(nvec->gpio), value); + gpio_set_value(nvec->gpio, value); +} + +/** + * nvec_write_async - Asynchronously write a message to NVEC + * @nvec: An nvec_chip instance + * @data: The message data, starting with the request type + * @size: The size of @data + * + * Queue a single message to be transferred to the embedded controller + * and return immediately. + * + * Returns: 0 on success, a negative error code on failure. If a failure + * occured, the nvec driver may print an error. + */ +int nvec_write_async(struct nvec_chip *nvec, const unsigned char *data, + short size) +{ + struct nvec_msg *msg; + unsigned long flags; + + msg = nvec_msg_alloc(nvec, NVEC_MSG_TX); + + if (msg == NULL) + return -ENOMEM; + + msg->data[0] = size; + memcpy(msg->data + 1, data, size); + msg->size = size + 1; + + spin_lock_irqsave(&nvec->tx_lock, flags); + list_add_tail(&msg->node, &nvec->tx_data); + spin_unlock_irqrestore(&nvec->tx_lock, flags); + + queue_work(nvec->wq, &nvec->tx_work); + + return 0; +} +EXPORT_SYMBOL(nvec_write_async); + +/** + * nvec_write_sync - Write a message to nvec and read the response + * @nvec: An &struct nvec_chip + * @data: The data to write + * @size: The size of @data + * + * This is similar to nvec_write_async(), but waits for the + * request to be answered before returning. This function + * uses a mutex and can thus not be called from e.g. + * interrupt handlers. + * + * Returns: A pointer to the response message on success, + * %NULL on failure. Free with nvec_msg_free() once no longer + * used. + */ +struct nvec_msg *nvec_write_sync(struct nvec_chip *nvec, + const unsigned char *data, short size) +{ + struct nvec_msg *msg; + + mutex_lock(&nvec->sync_write_mutex); + + nvec->sync_write_pending = (data[1] << 8) + data[0]; + + if (nvec_write_async(nvec, data, size) < 0) + return NULL; + + dev_dbg(nvec->dev, "nvec_sync_write: 0x%04x\n", + nvec->sync_write_pending); + if (!(wait_for_completion_timeout(&nvec->sync_write, + msecs_to_jiffies(2000)))) { + dev_warn(nvec->dev, "timeout waiting for sync write to complete\n"); + mutex_unlock(&nvec->sync_write_mutex); + return NULL; + } + + dev_dbg(nvec->dev, "nvec_sync_write: pong!\n"); + + msg = nvec->last_sync_msg; + + mutex_unlock(&nvec->sync_write_mutex); + + return msg; +} +EXPORT_SYMBOL(nvec_write_sync); + +/** + * nvec_request_master - Process outgoing messages + * @work: A &struct work_struct (the tx_worker member of &struct nvec_chip) + * + * Processes all outgoing requests by sending the request and awaiting the + * response, then continuing with the next request. Once a request has a + * matching response, it will be freed and removed from the list. + */ +static void nvec_request_master(struct work_struct *work) +{ + struct nvec_chip *nvec = container_of(work, struct nvec_chip, tx_work); + unsigned long flags; + long err; + struct nvec_msg *msg; + + spin_lock_irqsave(&nvec->tx_lock, flags); + while (!list_empty(&nvec->tx_data)) { + msg = list_first_entry(&nvec->tx_data, struct nvec_msg, node); + spin_unlock_irqrestore(&nvec->tx_lock, flags); + nvec_gpio_set_value(nvec, 0); + err = wait_for_completion_interruptible_timeout( + &nvec->ec_transfer, msecs_to_jiffies(5000)); + + if (err == 0) { + dev_warn(nvec->dev, "timeout waiting for ec transfer\n"); + nvec_gpio_set_value(nvec, 1); + msg->pos = 0; + } + + spin_lock_irqsave(&nvec->tx_lock, flags); + + if (err > 0) { + list_del_init(&msg->node); + nvec_msg_free(nvec, msg); + } + } + spin_unlock_irqrestore(&nvec->tx_lock, flags); +} + +/** + * parse_msg - Print some information and call the notifiers on an RX message + * @nvec: A &struct nvec_chip + * @msg: A message received by @nvec + * + * Paarse some pieces of the message and then call the chain of notifiers + * registered via nvec_register_notifier. + */ +static int parse_msg(struct nvec_chip *nvec, struct nvec_msg *msg) +{ + if ((msg->data[0] & 1 << 7) == 0 && msg->data[3]) { + dev_err(nvec->dev, "ec responded %02x %02x %02x %02x\n", + msg->data[0], msg->data[1], msg->data[2], msg->data[3]); + return -EINVAL; + } + + if ((msg->data[0] >> 7) == 1 && (msg->data[0] & 0x0f) == 5) + print_hex_dump(KERN_WARNING, "ec system event ", + DUMP_PREFIX_NONE, 16, 1, msg->data, + msg->data[1] + 2, true); + + atomic_notifier_call_chain(&nvec->notifier_list, msg->data[0] & 0x8f, + msg->data); + + return 0; +} + +/** + * nvec_dispatch - Process messages received from the EC + * @work: A &struct work_struct (the tx_worker member of &struct nvec_chip) + * + * Process messages previously received from the EC and put into the RX + * queue of the &struct nvec_chip instance associated with @work. + */ +static void nvec_dispatch(struct work_struct *work) +{ + struct nvec_chip *nvec = container_of(work, struct nvec_chip, rx_work); + unsigned long flags; + struct nvec_msg *msg; + + spin_lock_irqsave(&nvec->rx_lock, flags); + while (!list_empty(&nvec->rx_data)) { + msg = list_first_entry(&nvec->rx_data, struct nvec_msg, node); + list_del_init(&msg->node); + spin_unlock_irqrestore(&nvec->rx_lock, flags); + + if (nvec->sync_write_pending == + (msg->data[2] << 8) + msg->data[0]) { + dev_dbg(nvec->dev, "sync write completed!\n"); + nvec->sync_write_pending = 0; + nvec->last_sync_msg = msg; + complete(&nvec->sync_write); + } else { + parse_msg(nvec, msg); + nvec_msg_free(nvec, msg); + } + spin_lock_irqsave(&nvec->rx_lock, flags); + } + spin_unlock_irqrestore(&nvec->rx_lock, flags); +} + +/** + * nvec_tx_completed - Complete the current transfer + * @nvec: A &struct nvec_chip + * + * This is called when we have received an END_TRANS on a TX transfer. + */ +static void nvec_tx_completed(struct nvec_chip *nvec) +{ + /* We got an END_TRANS, let's skip this, maybe there's an event */ + if (nvec->tx->pos != nvec->tx->size) { + dev_err(nvec->dev, "premature END_TRANS, resending\n"); + nvec->tx->pos = 0; + nvec_gpio_set_value(nvec, 0); + } else { + nvec->state = 0; + } +} + +/** + * nvec_rx_completed - Complete the current transfer + * @nvec: A &struct nvec_chip + * + * This is called when we have received an END_TRANS on a RX transfer. + */ +static void nvec_rx_completed(struct nvec_chip *nvec) +{ + if (nvec->rx->pos != nvec_msg_size(nvec->rx)) { + dev_err(nvec->dev, "RX incomplete: Expected %u bytes, got %u\n", + (uint) nvec_msg_size(nvec->rx), + (uint) nvec->rx->pos); + + nvec_msg_free(nvec, nvec->rx); + nvec->state = 0; + + /* Battery quirk - Often incomplete, and likes to crash */ + if (nvec->rx->data[0] == NVEC_BAT) + complete(&nvec->ec_transfer); + + return; + } + + spin_lock(&nvec->rx_lock); + + /* add the received data to the work list + and move the ring buffer pointer to the next entry */ + list_add_tail(&nvec->rx->node, &nvec->rx_data); + + spin_unlock(&nvec->rx_lock); + + nvec->state = 0; + + if (!nvec_msg_is_event(nvec->rx)) + complete(&nvec->ec_transfer); + + queue_work(nvec->wq, &nvec->rx_work); +} + +/** + * nvec_invalid_flags - Send an error message about invalid flags and jump + * @nvec: The nvec device + * @status: The status flags + * @reset: Whether we shall jump to state 0. + */ +static void nvec_invalid_flags(struct nvec_chip *nvec, unsigned int status, + bool reset) +{ + dev_err(nvec->dev, "unexpected status flags 0x%02x during state %i\n", + status, nvec->state); + if (reset) + nvec->state = 0; +} + +/** + * nvec_tx_set - Set the message to transfer (nvec->tx) + * @nvec: A &struct nvec_chip + * + * Gets the first entry from the tx_data list of @nvec and sets the + * tx member to it. If the tx_data list is empty, this uses the + * tx_scratch message to send a no operation message. + */ +static void nvec_tx_set(struct nvec_chip *nvec) +{ + spin_lock(&nvec->tx_lock); + if (list_empty(&nvec->tx_data)) { + dev_err(nvec->dev, "empty tx - sending no-op\n"); + memcpy(nvec->tx_scratch.data, "\x02\x07\x02", 3); + nvec->tx_scratch.size = 3; + nvec->tx_scratch.pos = 0; + nvec->tx = &nvec->tx_scratch; + list_add_tail(&nvec->tx->node, &nvec->tx_data); + } else { + nvec->tx = list_first_entry(&nvec->tx_data, struct nvec_msg, + node); + nvec->tx->pos = 0; + } + spin_unlock(&nvec->tx_lock); + + dev_dbg(nvec->dev, "Sending message of length %u, command 0x%x\n", + (uint)nvec->tx->size, nvec->tx->data[1]); +} + +/** + * nvec_interrupt - Interrupt handler + * @irq: The IRQ + * @dev: The nvec device + * + * Interrupt handler that fills our RX buffers and empties our TX + * buffers. This uses a finite state machine with ridiculous amounts + * of error checking, in order to be fairly reliable. + */ +static irqreturn_t nvec_interrupt(int irq, void *dev) +{ + unsigned long status; + unsigned int received = 0; + unsigned char to_send = 0xff; + const unsigned long irq_mask = I2C_SL_IRQ | END_TRANS | RCVD | RNW; + struct nvec_chip *nvec = dev; + unsigned int state = nvec->state; + + status = readl(nvec->base + I2C_SL_STATUS); + + /* Filter out some errors */ + if ((status & irq_mask) == 0 && (status & ~irq_mask) != 0) { + dev_err(nvec->dev, "unexpected irq mask %lx\n", status); + return IRQ_HANDLED; + } + if ((status & I2C_SL_IRQ) == 0) { + dev_err(nvec->dev, "Spurious IRQ\n"); + return IRQ_HANDLED; + } + + /* The EC did not request a read, so it send us something, read it */ + if ((status & RNW) == 0) { + received = readl(nvec->base + I2C_SL_RCVD); + if (status & RCVD) + writel(0, nvec->base + I2C_SL_RCVD); + } + + if (status == (I2C_SL_IRQ | RCVD)) + nvec->state = 0; + + switch (nvec->state) { + case 0: /* Verify that its a transfer start, the rest later */ + if (status != (I2C_SL_IRQ | RCVD)) + nvec_invalid_flags(nvec, status, false); + break; + case 1: /* command byte */ + if (status != I2C_SL_IRQ) { + nvec_invalid_flags(nvec, status, true); + } else { + nvec->rx = nvec_msg_alloc(nvec, NVEC_MSG_RX); + /* Should not happen in a normal world */ + if (unlikely(nvec->rx == NULL)) { + nvec->state = 0; + break; + } + nvec->rx->data[0] = received; + nvec->rx->pos = 1; + nvec->state = 2; + } + break; + case 2: /* first byte after command */ + if (status == (I2C_SL_IRQ | RNW | RCVD)) { + udelay(33); + if (nvec->rx->data[0] != 0x01) { + dev_err(nvec->dev, + "Read without prior read command\n"); + nvec->state = 0; + break; + } + nvec_msg_free(nvec, nvec->rx); + nvec->state = 3; + nvec_tx_set(nvec); + BUG_ON(nvec->tx->size < 1); + to_send = nvec->tx->data[0]; + nvec->tx->pos = 1; + } else if (status == (I2C_SL_IRQ)) { + BUG_ON(nvec->rx == NULL); + nvec->rx->data[1] = received; + nvec->rx->pos = 2; + nvec->state = 4; + } else { + nvec_invalid_flags(nvec, status, true); + } + break; + case 3: /* EC does a block read, we transmit data */ + if (status & END_TRANS) { + nvec_tx_completed(nvec); + } else if ((status & RNW) == 0 || (status & RCVD)) { + nvec_invalid_flags(nvec, status, true); + } else if (nvec->tx && nvec->tx->pos < nvec->tx->size) { + to_send = nvec->tx->data[nvec->tx->pos++]; + } else { + dev_err(nvec->dev, "tx buffer underflow on %p (%u > %u)\n", + nvec->tx, + (uint) (nvec->tx ? nvec->tx->pos : 0), + (uint) (nvec->tx ? nvec->tx->size : 0)); + nvec->state = 0; + } + break; + case 4: /* EC does some write, we read the data */ + if ((status & (END_TRANS | RNW)) == END_TRANS) + nvec_rx_completed(nvec); + else if (status & (RNW | RCVD)) + nvec_invalid_flags(nvec, status, true); + else if (nvec->rx && nvec->rx->pos < NVEC_MSG_SIZE) + nvec->rx->data[nvec->rx->pos++] = received; + else + dev_err(nvec->dev, + "RX buffer overflow on %p: " + "Trying to write byte %u of %u\n", + nvec->rx, nvec->rx->pos, NVEC_MSG_SIZE); + break; + default: + nvec->state = 0; + } + + /* If we are told that a new transfer starts, verify it */ + if ((status & (RCVD | RNW)) == RCVD) { + if (received != nvec->i2c_addr) + dev_err(nvec->dev, + "received address 0x%02x, expected 0x%02x\n", + received, nvec->i2c_addr); + nvec->state = 1; + } + + /* Send data if requested, but not on end of transmission */ + if ((status & (RNW | END_TRANS)) == RNW) + writel(to_send, nvec->base + I2C_SL_RCVD); + + /* If we have send the first byte */ + if (status == (I2C_SL_IRQ | RNW | RCVD)) + nvec_gpio_set_value(nvec, 1); + + dev_dbg(nvec->dev, + "Handled: %s 0x%02x, %s 0x%02x in state %u [%s%s%s]\n", + (status & RNW) == 0 ? "received" : "R=", + received, + (status & (RNW | END_TRANS)) ? "sent" : "S=", + to_send, + state, + status & END_TRANS ? " END_TRANS" : "", + status & RCVD ? " RCVD" : "", + status & RNW ? " RNW" : ""); + + + /* + * TODO: A correct fix needs to be found for this. + * + * We experience less incomplete messages with this delay than without + * it, but we don't know why. Help is appreciated. + */ + udelay(100); + + return IRQ_HANDLED; +} + +static void tegra_init_i2c_slave(struct nvec_chip *nvec) +{ + u32 val; + + clk_enable(nvec->i2c_clk); + + tegra_periph_reset_assert(nvec->i2c_clk); + udelay(2); + tegra_periph_reset_deassert(nvec->i2c_clk); + + val = I2C_CNFG_NEW_MASTER_SFM | I2C_CNFG_PACKET_MODE_EN | + (0x2 << I2C_CNFG_DEBOUNCE_CNT_SHIFT); + writel(val, nvec->base + I2C_CNFG); + + clk_set_rate(nvec->i2c_clk, 8 * 80000); + + writel(I2C_SL_NEWSL, nvec->base + I2C_SL_CNFG); + writel(0x1E, nvec->base + I2C_SL_DELAY_COUNT); + + writel(nvec->i2c_addr>>1, nvec->base + I2C_SL_ADDR1); + writel(0, nvec->base + I2C_SL_ADDR2); + + enable_irq(nvec->irq); + + clk_disable(nvec->i2c_clk); +} + +static void nvec_disable_i2c_slave(struct nvec_chip *nvec) +{ + disable_irq(nvec->irq); + writel(I2C_SL_NEWSL | I2C_SL_NACK, nvec->base + I2C_SL_CNFG); + clk_disable(nvec->i2c_clk); +} + +static void nvec_power_off(void) +{ + nvec_write_async(nvec_power_handle, EC_DISABLE_EVENT_REPORTING, 3); + nvec_write_async(nvec_power_handle, "\x04\x01", 2); +} + +static int __devinit tegra_nvec_probe(struct platform_device *pdev) +{ + int err, ret; + struct clk *i2c_clk; + struct nvec_platform_data *pdata = pdev->dev.platform_data; + struct nvec_chip *nvec; + struct nvec_msg *msg; + struct resource *res; + struct resource *iomem; + void __iomem *base; + + nvec = kzalloc(sizeof(struct nvec_chip), GFP_KERNEL); + if (nvec == NULL) { + dev_err(&pdev->dev, "failed to reserve memory\n"); + return -ENOMEM; + } + platform_set_drvdata(pdev, nvec); + nvec->dev = &pdev->dev; + + if (pdata) { + nvec->gpio = pdata->gpio; + nvec->i2c_addr = pdata->i2c_addr; + } else if (nvec->dev->of_node) { + nvec->gpio = of_get_named_gpio(nvec->dev->of_node, "request-gpios", 0); + if (nvec->gpio < 0) { + dev_err(&pdev->dev, "no gpio specified"); + goto failed; + } + if (of_property_read_u32(nvec->dev->of_node, "slave-addr", &nvec->i2c_addr)) { + dev_err(&pdev->dev, "no i2c address specified"); + goto failed; + } + } else { + dev_err(&pdev->dev, "no platform data\n"); + goto failed; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -ENODEV; + } + + iomem = request_mem_region(res->start, resource_size(res), pdev->name); + if (!iomem) { + dev_err(&pdev->dev, "I2C region already claimed\n"); + return -EBUSY; + } + + base = ioremap(iomem->start, resource_size(iomem)); + if (!base) { + dev_err(&pdev->dev, "Can't ioremap I2C region\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "no irq resource?\n"); + ret = -ENODEV; + goto err_iounmap; + } + + i2c_clk = clk_get_sys("tegra-i2c.2", NULL); + if (IS_ERR(i2c_clk)) { + dev_err(nvec->dev, "failed to get controller clock\n"); + goto err_iounmap; + } + + nvec->base = base; + nvec->irq = res->start; + nvec->i2c_clk = i2c_clk; + nvec->rx = &nvec->msg_pool[0]; + + ATOMIC_INIT_NOTIFIER_HEAD(&nvec->notifier_list); + + init_completion(&nvec->sync_write); + init_completion(&nvec->ec_transfer); + mutex_init(&nvec->sync_write_mutex); + spin_lock_init(&nvec->tx_lock); + spin_lock_init(&nvec->rx_lock); + INIT_LIST_HEAD(&nvec->rx_data); + INIT_LIST_HEAD(&nvec->tx_data); + INIT_WORK(&nvec->rx_work, nvec_dispatch); + INIT_WORK(&nvec->tx_work, nvec_request_master); + nvec->wq = alloc_workqueue("nvec", WQ_NON_REENTRANT, 2); + + err = gpio_request_one(nvec->gpio, GPIOF_OUT_INIT_HIGH, "nvec gpio"); + if (err < 0) { + dev_err(nvec->dev, "couldn't request gpio\n"); + goto failed; + } + + err = request_irq(nvec->irq, nvec_interrupt, 0, "nvec", nvec); + if (err) { + dev_err(nvec->dev, "couldn't request irq\n"); + goto failed; + } + disable_irq(nvec->irq); + + tegra_init_i2c_slave(nvec); + + clk_enable(i2c_clk); + + + /* enable event reporting */ + nvec_write_async(nvec, EC_ENABLE_EVENT_REPORTING, + sizeof(EC_ENABLE_EVENT_REPORTING)); + + nvec->nvec_status_notifier.notifier_call = nvec_status_notifier; + nvec_register_notifier(nvec, &nvec->nvec_status_notifier, 0); + + nvec_power_handle = nvec; + pm_power_off = nvec_power_off; + + /* Get Firmware Version */ + msg = nvec_write_sync(nvec, EC_GET_FIRMWARE_VERSION, + sizeof(EC_GET_FIRMWARE_VERSION)); + + if (msg) { + dev_warn(nvec->dev, "ec firmware version %02x.%02x.%02x / %02x\n", + msg->data[4], msg->data[5], msg->data[6], msg->data[7]); + + nvec_msg_free(nvec, msg); + } + + ret = mfd_add_devices(nvec->dev, -1, nvec_devices, + ARRAY_SIZE(nvec_devices), base, 0); + if (ret) + dev_err(nvec->dev, "error adding subdevices\n"); + + /* unmute speakers? */ + nvec_write_async(nvec, "\x0d\x10\x59\x95", 4); + + /* enable lid switch event */ + nvec_write_async(nvec, "\x01\x01\x01\x00\x00\x02\x00", 7); + + /* enable power button event */ + nvec_write_async(nvec, "\x01\x01\x01\x00\x00\x80\x00", 7); + + return 0; + +err_iounmap: + iounmap(base); +failed: + kfree(nvec); + return -ENOMEM; +} + +static int __devexit tegra_nvec_remove(struct platform_device *pdev) +{ + struct nvec_chip *nvec = platform_get_drvdata(pdev); + + nvec_write_async(nvec, EC_DISABLE_EVENT_REPORTING, 3); + mfd_remove_devices(nvec->dev); + free_irq(nvec->irq, &nvec_interrupt); + iounmap(nvec->base); + gpio_free(nvec->gpio); + destroy_workqueue(nvec->wq); + kfree(nvec); + + return 0; +} + +#ifdef CONFIG_PM + +static int tegra_nvec_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct nvec_chip *nvec = platform_get_drvdata(pdev); + struct nvec_msg *msg; + + dev_dbg(nvec->dev, "suspending\n"); + + /* keep these sync or you'll break suspend */ + msg = nvec_write_sync(nvec, EC_DISABLE_EVENT_REPORTING, 3); + nvec_msg_free(nvec, msg); + msg = nvec_write_sync(nvec, "\x04\x02", 2); + nvec_msg_free(nvec, msg); + + nvec_disable_i2c_slave(nvec); + + return 0; +} + +static int tegra_nvec_resume(struct platform_device *pdev) +{ + struct nvec_chip *nvec = platform_get_drvdata(pdev); + + dev_dbg(nvec->dev, "resuming\n"); + tegra_init_i2c_slave(nvec); + nvec_write_async(nvec, EC_ENABLE_EVENT_REPORTING, 3); + + return 0; +} + +#else +#define tegra_nvec_suspend NULL +#define tegra_nvec_resume NULL +#endif + +/* Match table for of_platform binding */ +static const struct of_device_id nvidia_nvec_of_match[] __devinitconst = { + { .compatible = "nvidia,nvec", }, + {}, +}; +MODULE_DEVICE_TABLE(of, nvidia_nvec_of_match); + +static struct platform_driver nvec_device_driver = { + .probe = tegra_nvec_probe, + .remove = __devexit_p(tegra_nvec_remove), + .suspend = tegra_nvec_suspend, + .resume = tegra_nvec_resume, + .driver = { + .name = "nvec", + .owner = THIS_MODULE, + .of_match_table = nvidia_nvec_of_match, + } +}; + +static int __init tegra_nvec_init(void) +{ + return platform_driver_register(&nvec_device_driver); +} + +module_init(tegra_nvec_init); + +MODULE_ALIAS("platform:nvec"); +MODULE_DESCRIPTION("NVIDIA compliant embedded controller interface"); +MODULE_AUTHOR("Marc Dietrich "); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/nvec/nvec.h b/drivers/staging/nvec/nvec.h new file mode 100644 index 00000000..a4c17b0e --- /dev/null +++ b/drivers/staging/nvec/nvec.h @@ -0,0 +1,203 @@ +/* + * NVEC: NVIDIA compliant embedded controller interface + * + * Copyright (C) 2011 The AC100 Kernel Team + * + * Authors: Pierre-Hugues Husson + * Ilya Petrov + * Marc Dietrich + * Julian Andres Klode + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +#ifndef __LINUX_MFD_NVEC +#define __LINUX_MFD_NVEC + +#include +#include +#include +#include +#include +#include +#include +#include + +/* NVEC_POOL_SIZE - Size of the pool in &struct nvec_msg */ +#define NVEC_POOL_SIZE 64 + +/* + * NVEC_MSG_SIZE - Maximum size of the data field of &struct nvec_msg. + * + * A message must store up to a SMBus block operation which consists of + * one command byte, one count byte, and up to 32 payload bytes = 34 + * byte. + */ +#define NVEC_MSG_SIZE 34 + +/** + * enum nvec_event_size - The size of an event message + * @NVEC_2BYTES: The message has one command byte and one data byte + * @NVEC_3BYTES: The message has one command byte and two data bytes + * @NVEC_VAR_SIZE: The message has one command byte, one count byte, and as + * up to as many bytes as the number in the count byte. The + * maximum is 32 + * + * Events can be fixed or variable sized. This is useless on other message + * types, which are always variable sized. + */ +enum nvec_event_size { + NVEC_2BYTES, + NVEC_3BYTES, + NVEC_VAR_SIZE, +}; + +/** + * enum nvec_msg_type - The type of a message + * @NVEC_SYS: A system request/response + * @NVEC_BAT: A battery request/response + * @NVEC_KBD: A keyboard request/response + * @NVEC_PS2: A mouse request/response + * @NVEC_CNTL: A EC control request/response + * @NVEC_KB_EVT: An event from the keyboard + * @NVEC_PS2_EVT: An event from the mouse + * + * Events can be fixed or variable sized. This is useless on other message + * types, which are always variable sized. + */ +enum nvec_msg_type { + NVEC_SYS = 1, + NVEC_BAT, + NVEC_KBD = 5, + NVEC_PS2, + NVEC_CNTL, + NVEC_KB_EVT = 0x80, + NVEC_PS2_EVT, +}; + +/** + * struct nvec_msg - A buffer for a single message + * @node: Messages are part of various lists in a &struct nvec_chip + * @data: The data of the message + * @size: For TX messages, the number of bytes used in @data + * @pos: For RX messages, the current position to write to. For TX messages, + * the position to read from. + * @used: Used for the message pool to mark a message as free/allocated. + * + * This structure is used to hold outgoing and incoming messages. Outgoing + * messages have a different format than incoming messages, and that is not + * documented yet. + */ +struct nvec_msg { + struct list_head node; + unsigned char data[NVEC_MSG_SIZE]; + unsigned short size; + unsigned short pos; + atomic_t used; +}; + +/** + * struct nvec_subdev - A subdevice of nvec, such as nvec_kbd + * @name: The name of the sub device + * @platform_data: Platform data + * @id: Identifier of the sub device + */ +struct nvec_subdev { + const char *name; + void *platform_data; + int id; +}; + +/** + * struct nvec_platform_data - platform data for a tegra slave controller + * @i2c_addr: number of i2c slave adapter the ec is connected to + * @gpio: gpio number for the ec request line + * + * Platform data, to be used in board definitions. For an example, take a + * look at the paz00 board in arch/arm/mach-tegra/board-paz00.c + */ +struct nvec_platform_data { + int i2c_addr; + int gpio; +}; + +/** + * struct nvec_chip - A single connection to an NVIDIA Embedded controller + * @dev: The device + * @gpio: The same as for &struct nvec_platform_data + * @irq: The IRQ of the I2C device + * @i2c_addr: The address of the I2C slave + * @base: The base of the memory mapped region of the I2C device + * @clk: The clock of the I2C device + * @notifier_list: Notifiers to be called on received messages, see + * nvec_register_notifier() + * @rx_data: Received messages that have to be processed + * @tx_data: Messages waiting to be sent to the controller + * @nvec_status_notifier: Internal notifier (see nvec_status_notifier()) + * @rx_work: A work structure for the RX worker nvec_dispatch() + * @tx_work: A work structure for the TX worker nvec_request_master() + * @wq: The work queue in which @rx_work and @tx_work are executed + * @rx: The message currently being retrieved or %NULL + * @msg_pool: A pool of messages for allocation + * @tx: The message currently being transferred + * @tx_scratch: Used for building pseudo messages + * @ec_transfer: A completion that will be completed once a message has been + * received (see nvec_rx_completed()) + * @tx_lock: Spinlock for modifications on @tx_data + * @rx_lock: Spinlock for modifications on @rx_data + * @sync_write_mutex: A mutex for nvec_write_sync() + * @sync_write: A completion to signal that a synchronous message is complete + * @sync_write_pending: The first two bytes of the request (type and subtype) + * @last_sync_msg: The last synchronous message. + * @state: State of our finite state machine used in nvec_interrupt() + */ +struct nvec_chip { + struct device *dev; + int gpio; + int irq; + int i2c_addr; + void __iomem *base; + struct clk *i2c_clk; + struct atomic_notifier_head notifier_list; + struct list_head rx_data, tx_data; + struct notifier_block nvec_status_notifier; + struct work_struct rx_work, tx_work; + struct workqueue_struct *wq; + struct nvec_msg msg_pool[NVEC_POOL_SIZE]; + struct nvec_msg *rx; + + struct nvec_msg *tx; + struct nvec_msg tx_scratch; + struct completion ec_transfer; + + spinlock_t tx_lock, rx_lock; + + /* sync write stuff */ + struct mutex sync_write_mutex; + struct completion sync_write; + u16 sync_write_pending; + struct nvec_msg *last_sync_msg; + + int state; +}; + +extern int nvec_write_async(struct nvec_chip *nvec, const unsigned char *data, + short size); + +extern struct nvec_msg *nvec_write_sync(struct nvec_chip *nvec, + const unsigned char *data, short size); + +extern int nvec_register_notifier(struct nvec_chip *nvec, + struct notifier_block *nb, + unsigned int events); + +extern int nvec_unregister_notifier(struct device *dev, + struct notifier_block *nb, + unsigned int events); + +extern void nvec_msg_free(struct nvec_chip *nvec, struct nvec_msg *msg); + +#endif diff --git a/drivers/staging/nvec/nvec_kbd.c b/drivers/staging/nvec/nvec_kbd.c new file mode 100644 index 00000000..a4ce5a74 --- /dev/null +++ b/drivers/staging/nvec/nvec_kbd.c @@ -0,0 +1,179 @@ +/* + * nvec_kbd: keyboard driver for a NVIDIA compliant embedded controller + * + * Copyright (C) 2011 The AC100 Kernel Team + * + * Authors: Pierre-Hugues Husson + * Marc Dietrich + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "nvec-keytable.h" +#include "nvec.h" + +#define ACK_KBD_EVENT {'\x05', '\xed', '\x01'} + +static const char led_on[3] = "\x05\xed\x07"; +static const char led_off[3] = "\x05\xed\x00"; +static unsigned char keycodes[ARRAY_SIZE(code_tab_102us) + + ARRAY_SIZE(extcode_tab_us102)]; + +struct nvec_keys { + struct input_dev *input; + struct notifier_block notifier; + struct nvec_chip *nvec; + bool caps_lock; +}; + +static struct nvec_keys keys_dev; + +static void nvec_kbd_toggle_led(void) +{ + keys_dev.caps_lock = !keys_dev.caps_lock; + + if (keys_dev.caps_lock) + nvec_write_async(keys_dev.nvec, led_on, sizeof(led_on)); + else + nvec_write_async(keys_dev.nvec, led_off, sizeof(led_off)); +} + +static int nvec_keys_notifier(struct notifier_block *nb, + unsigned long event_type, void *data) +{ + int code, state; + unsigned char *msg = (unsigned char *)data; + + if (event_type == NVEC_KB_EVT) { + int _size = (msg[0] & (3 << 5)) >> 5; + +/* power on/off button */ + if (_size == NVEC_VAR_SIZE) + return NOTIFY_STOP; + + if (_size == NVEC_3BYTES) + msg++; + + code = msg[1] & 0x7f; + state = msg[1] & 0x80; + + if (code_tabs[_size][code] == KEY_CAPSLOCK && state) + nvec_kbd_toggle_led(); + + input_report_key(keys_dev.input, code_tabs[_size][code], + !state); + input_sync(keys_dev.input); + + return NOTIFY_STOP; + } + + return NOTIFY_DONE; +} + +static int nvec_kbd_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + unsigned char buf[] = ACK_KBD_EVENT; + struct nvec_chip *nvec = keys_dev.nvec; + + if (type == EV_REP) + return 0; + + if (type != EV_LED) + return -1; + + if (code != LED_CAPSL) + return -1; + + buf[2] = !!value; + nvec_write_async(nvec, buf, sizeof(buf)); + + return 0; +} + +static int __devinit nvec_kbd_probe(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + int i, j, err; + struct input_dev *idev; + + j = 0; + + for (i = 0; i < ARRAY_SIZE(code_tab_102us); ++i) + keycodes[j++] = code_tab_102us[i]; + + for (i = 0; i < ARRAY_SIZE(extcode_tab_us102); ++i) + keycodes[j++] = extcode_tab_us102[i]; + + idev = input_allocate_device(); + idev->name = "nvec keyboard"; + idev->phys = "nvec"; + idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_LED); + idev->ledbit[0] = BIT_MASK(LED_CAPSL); + idev->event = nvec_kbd_event; + idev->keycode = keycodes; + idev->keycodesize = sizeof(unsigned char); + idev->keycodemax = ARRAY_SIZE(keycodes); + + for (i = 0; i < ARRAY_SIZE(keycodes); ++i) + set_bit(keycodes[i], idev->keybit); + + clear_bit(0, idev->keybit); + err = input_register_device(idev); + if (err) + goto fail; + + keys_dev.input = idev; + keys_dev.notifier.notifier_call = nvec_keys_notifier; + keys_dev.nvec = nvec; + nvec_register_notifier(nvec, &keys_dev.notifier, 0); + + /* Enable keyboard */ + nvec_write_async(nvec, "\x05\xf4", 2); + + /* keyboard reset? */ + nvec_write_async(nvec, "\x05\x03\x01\x01", 4); + nvec_write_async(nvec, "\x05\x04\x01", 3); + nvec_write_async(nvec, "\x06\x01\xff\x03", 4); +/* FIXME + wait until keyboard reset is finished + or until we have a sync write */ + mdelay(1000); + + /* Disable caps lock LED */ + nvec_write_async(nvec, led_off, sizeof(led_off)); + + return 0; + +fail: + input_free_device(idev); + return err; +} + +static struct platform_driver nvec_kbd_driver = { + .probe = nvec_kbd_probe, + .driver = { + .name = "nvec-kbd", + .owner = THIS_MODULE, + }, +}; + +static int __init nvec_kbd_init(void) +{ + return platform_driver_register(&nvec_kbd_driver); +} + +module_init(nvec_kbd_init); + +MODULE_AUTHOR("Marc Dietrich "); +MODULE_DESCRIPTION("NVEC keyboard driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/nvec/nvec_leds.c b/drivers/staging/nvec/nvec_leds.c new file mode 100644 index 00000000..f4cbcd62 --- /dev/null +++ b/drivers/staging/nvec/nvec_leds.c @@ -0,0 +1,114 @@ +/* + * nvec_leds: LED driver for a NVIDIA compliant embedded controller + * + * Copyright (C) 2011 The AC100 Kernel Team + * + * Authors: Ilya Petrov + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +#include +#include +#include +#include +#include +#include "nvec.h" + +#define to_nvec_led(led_cdev) \ + container_of(led_cdev, struct nvec_led, cdev) + +#define NVEC_LED_REQ {'\x0d', '\x10', '\x45', '\x10', '\x00'} + +#define NVEC_LED_MAX 8 + +struct nvec_led { + struct led_classdev cdev; + struct nvec_chip *nvec; +}; + +static void nvec_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct nvec_led *led = to_nvec_led(led_cdev); + unsigned char buf[] = NVEC_LED_REQ; + buf[4] = value; + + nvec_write_async(led->nvec, buf, sizeof(buf)); + + led->cdev.brightness = value; + +} + +static int __devinit nvec_led_probe(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + struct nvec_led *led; + int ret = 0; + + led = kzalloc(sizeof(*led), GFP_KERNEL); + if (led == NULL) + return -ENOMEM; + + led->cdev.max_brightness = NVEC_LED_MAX; + + led->cdev.brightness_set = nvec_led_brightness_set; + led->cdev.name = "nvec-led"; + led->cdev.flags |= LED_CORE_SUSPENDRESUME; + led->nvec = nvec; + + platform_set_drvdata(pdev, led); + + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) + goto err_led; + + /* to expose the default value to userspace */ + led->cdev.brightness = 0; + + return 0; + +err_led: + kfree(led); + return ret; +} + +static int __devexit nvec_led_remove(struct platform_device *pdev) +{ + struct nvec_led *led = platform_get_drvdata(pdev); + + led_classdev_unregister(&led->cdev); + kfree(led); + return 0; +} + +static struct platform_driver nvec_led_driver = { + .probe = nvec_led_probe, + .remove = __devexit_p(nvec_led_remove), + .driver = { + .name = "nvec-leds", + .owner = THIS_MODULE, + }, +}; + +static int __init nvec_led_init(void) +{ + return platform_driver_register(&nvec_led_driver); +} + +module_init(nvec_led_init); + +static void __exit nvec_led_exit(void) +{ + platform_driver_unregister(&nvec_led_driver); +} + +module_exit(nvec_led_exit); + +MODULE_AUTHOR("Ilya Petrov "); +MODULE_DESCRIPTION("Tegra NVEC LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nvec-leds"); diff --git a/drivers/staging/nvec/nvec_power.c b/drivers/staging/nvec/nvec_power.c new file mode 100644 index 00000000..dfa966f6 --- /dev/null +++ b/drivers/staging/nvec/nvec_power.c @@ -0,0 +1,426 @@ +/* + * nvec_power: power supply driver for a NVIDIA compliant embedded controller + * + * Copyright (C) 2011 The AC100 Kernel Team + * + * Authors: Ilya Petrov + * Marc Dietrich + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "nvec.h" + +struct nvec_power { + struct notifier_block notifier; + struct delayed_work poller; + struct nvec_chip *nvec; + int on; + int bat_present; + int bat_status; + int bat_voltage_now; + int bat_current_now; + int bat_current_avg; + int time_remain; + int charge_full_design; + int charge_last_full; + int critical_capacity; + int capacity_remain; + int bat_temperature; + int bat_cap; + int bat_type_enum; + char bat_manu[30]; + char bat_model[30]; + char bat_type[30]; +}; + +enum { + SLOT_STATUS, + VOLTAGE, + TIME_REMAINING, + CURRENT, + AVERAGE_CURRENT, + AVERAGING_TIME_INTERVAL, + CAPACITY_REMAINING, + LAST_FULL_CHARGE_CAPACITY, + DESIGN_CAPACITY, + CRITICAL_CAPACITY, + TEMPERATURE, + MANUFACTURER, + MODEL, + TYPE, +}; + +enum { + AC, + BAT, +}; + +struct bat_response { + u8 event_type; + u8 length; + u8 sub_type; + u8 status; + /* payload */ + union { + char plc[30]; + u16 plu; + s16 pls; + }; +}; + +static struct power_supply nvec_bat_psy; +static struct power_supply nvec_psy; + +static int nvec_power_notifier(struct notifier_block *nb, + unsigned long event_type, void *data) +{ + struct nvec_power *power = + container_of(nb, struct nvec_power, notifier); + struct bat_response *res = (struct bat_response *)data; + + if (event_type != NVEC_SYS) + return NOTIFY_DONE; + + if (res->sub_type == 0) { + if (power->on != res->plu) { + power->on = res->plu; + power_supply_changed(&nvec_psy); + } + return NOTIFY_STOP; + } + return NOTIFY_OK; +} + +static const int bat_init[] = { + LAST_FULL_CHARGE_CAPACITY, DESIGN_CAPACITY, CRITICAL_CAPACITY, + MANUFACTURER, MODEL, TYPE, +}; + +static void get_bat_mfg_data(struct nvec_power *power) +{ + int i; + char buf[] = { '\x02', '\x00' }; + + for (i = 0; i < ARRAY_SIZE(bat_init); i++) { + buf[1] = bat_init[i]; + nvec_write_async(power->nvec, buf, 2); + } +} + +static int nvec_power_bat_notifier(struct notifier_block *nb, + unsigned long event_type, void *data) +{ + struct nvec_power *power = + container_of(nb, struct nvec_power, notifier); + struct bat_response *res = (struct bat_response *)data; + int status_changed = 0; + + if (event_type != NVEC_BAT) + return NOTIFY_DONE; + + switch (res->sub_type) { + case SLOT_STATUS: + if (res->plc[0] & 1) { + if (power->bat_present == 0) { + status_changed = 1; + get_bat_mfg_data(power); + } + + power->bat_present = 1; + + switch ((res->plc[0] >> 1) & 3) { + case 0: + power->bat_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case 1: + power->bat_status = + POWER_SUPPLY_STATUS_CHARGING; + break; + case 2: + power->bat_status = + POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: + power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; + } + } else { + if (power->bat_present == 1) + status_changed = 1; + + power->bat_present = 0; + power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; + } + power->bat_cap = res->plc[1]; + if (status_changed) + power_supply_changed(&nvec_bat_psy); + break; + case VOLTAGE: + power->bat_voltage_now = res->plu * 1000; + break; + case TIME_REMAINING: + power->time_remain = res->plu * 3600; + break; + case CURRENT: + power->bat_current_now = res->pls * 1000; + break; + case AVERAGE_CURRENT: + power->bat_current_avg = res->pls * 1000; + break; + case CAPACITY_REMAINING: + power->capacity_remain = res->plu * 1000; + break; + case LAST_FULL_CHARGE_CAPACITY: + power->charge_last_full = res->plu * 1000; + break; + case DESIGN_CAPACITY: + power->charge_full_design = res->plu * 1000; + break; + case CRITICAL_CAPACITY: + power->critical_capacity = res->plu * 1000; + break; + case TEMPERATURE: + power->bat_temperature = res->plu - 2732; + break; + case MANUFACTURER: + memcpy(power->bat_manu, &res->plc, res->length - 2); + power->bat_model[res->length - 2] = '\0'; + break; + case MODEL: + memcpy(power->bat_model, &res->plc, res->length - 2); + power->bat_model[res->length - 2] = '\0'; + break; + case TYPE: + memcpy(power->bat_type, &res->plc, res->length - 2); + power->bat_type[res->length - 2] = '\0'; + /* this differs a little from the spec + fill in more if you find some */ + if (!strncmp(power->bat_type, "Li", 30)) + power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_LION; + else + power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + break; + default: + return NOTIFY_STOP; + } + + return NOTIFY_STOP; +} + +static int nvec_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct nvec_power *power = dev_get_drvdata(psy->dev->parent); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = power->on; + break; + default: + return -EINVAL; + } + return 0; +} + +static int nvec_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct nvec_power *power = dev_get_drvdata(psy->dev->parent); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = power->bat_status; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = power->bat_cap; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = power->bat_present; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = power->bat_voltage_now; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = power->bat_current_now; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = power->bat_current_avg; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + val->intval = power->time_remain; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = power->charge_full_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = power->charge_last_full; + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + val->intval = power->critical_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = power->capacity_remain; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = power->bat_temperature; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = power->bat_manu; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = power->bat_model; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = power->bat_type_enum; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property nvec_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property nvec_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +#ifdef EC_FULL_DIAG + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, +#endif + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_TECHNOLOGY, +}; + +static char *nvec_power_supplied_to[] = { + "battery", +}; + +static struct power_supply nvec_bat_psy = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = nvec_battery_props, + .num_properties = ARRAY_SIZE(nvec_battery_props), + .get_property = nvec_battery_get_property, +}; + +static struct power_supply nvec_psy = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = nvec_power_supplied_to, + .num_supplicants = ARRAY_SIZE(nvec_power_supplied_to), + .properties = nvec_power_props, + .num_properties = ARRAY_SIZE(nvec_power_props), + .get_property = nvec_power_get_property, +}; + +static int counter; +static int const bat_iter[] = { + SLOT_STATUS, VOLTAGE, CURRENT, CAPACITY_REMAINING, +#ifdef EC_FULL_DIAG + AVERAGE_CURRENT, TEMPERATURE, TIME_REMAINING, +#endif +}; + +static void nvec_power_poll(struct work_struct *work) +{ + char buf[] = { '\x01', '\x00' }; + struct nvec_power *power = container_of(work, struct nvec_power, + poller.work); + + if (counter >= ARRAY_SIZE(bat_iter)) + counter = 0; + +/* AC status via sys req */ + nvec_write_async(power->nvec, buf, 2); + msleep(100); + +/* select a battery request function via round robin + doing it all at once seems to overload the power supply */ + buf[0] = '\x02'; /* battery */ + buf[1] = bat_iter[counter++]; + nvec_write_async(power->nvec, buf, 2); + + schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(5000)); +}; + +static int __devinit nvec_power_probe(struct platform_device *pdev) +{ + struct power_supply *psy; + struct nvec_power *power = + kzalloc(sizeof(struct nvec_power), GFP_NOWAIT); + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + + dev_set_drvdata(&pdev->dev, power); + power->nvec = nvec; + + switch (pdev->id) { + case AC: + psy = &nvec_psy; + + power->notifier.notifier_call = nvec_power_notifier; + + INIT_DELAYED_WORK(&power->poller, nvec_power_poll); + schedule_delayed_work(&power->poller, msecs_to_jiffies(5000)); + break; + case BAT: + psy = &nvec_bat_psy; + + power->notifier.notifier_call = nvec_power_bat_notifier; + break; + default: + kfree(power); + return -ENODEV; + } + + nvec_register_notifier(nvec, &power->notifier, NVEC_SYS); + + if (pdev->id == BAT) + get_bat_mfg_data(power); + + return power_supply_register(&pdev->dev, psy); +} + +static struct platform_driver nvec_power_driver = { + .probe = nvec_power_probe, + .driver = { + .name = "nvec-power", + .owner = THIS_MODULE, + } +}; + +static int __init nvec_power_init(void) +{ + return platform_driver_register(&nvec_power_driver); +} + +module_init(nvec_power_init); + +MODULE_AUTHOR("Ilya Petrov "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("NVEC battery and AC driver"); +MODULE_ALIAS("platform:nvec-power"); diff --git a/drivers/staging/nvec/nvec_ps2.c b/drivers/staging/nvec/nvec_ps2.c new file mode 100644 index 00000000..14a6f687 --- /dev/null +++ b/drivers/staging/nvec/nvec_ps2.c @@ -0,0 +1,166 @@ +/* + * nvec_ps2: mouse driver for a NVIDIA compliant embedded controller + * + * Copyright (C) 2011 The AC100 Kernel Team + * + * Authors: Pierre-Hugues Husson + * Ilya Petrov + * Marc Dietrich + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "nvec.h" + +#define START_STREAMING {'\x06', '\x03', '\x06'} +#define STOP_STREAMING {'\x06', '\x04'} +#define SEND_COMMAND {'\x06', '\x01', '\xf4', '\x01'} + +#ifdef NVEC_PS2_DEBUG +#define NVEC_PHD(str, buf, len) \ + print_hex_dump(KERN_DEBUG, str, DUMP_PREFIX_NONE, \ + 16, 1, buf, len, false) +#else +#define NVEC_PHD(str, buf, len) +#endif + +static const unsigned char MOUSE_RESET[] = {'\x06', '\x01', '\xff', '\x03'}; + +struct nvec_ps2 { + struct serio *ser_dev; + struct notifier_block notifier; + struct nvec_chip *nvec; +}; + +static struct nvec_ps2 ps2_dev; + +static int ps2_startstreaming(struct serio *ser_dev) +{ + unsigned char buf[] = START_STREAMING; + return nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); +} + +static void ps2_stopstreaming(struct serio *ser_dev) +{ + unsigned char buf[] = STOP_STREAMING; + nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); +} + +static int ps2_sendcommand(struct serio *ser_dev, unsigned char cmd) +{ + unsigned char buf[] = SEND_COMMAND; + + buf[2] = cmd & 0xff; + + dev_dbg(&ser_dev->dev, "Sending ps2 cmd %02x\n", cmd); + return nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); +} + +static int nvec_ps2_notifier(struct notifier_block *nb, + unsigned long event_type, void *data) +{ + int i; + unsigned char *msg = (unsigned char *)data; + + switch (event_type) { + case NVEC_PS2_EVT: + for (i = 0; i < msg[1]; i++) + serio_interrupt(ps2_dev.ser_dev, msg[2 + i], 0); + NVEC_PHD("ps/2 mouse event: ", &msg[2], msg[1]); + return NOTIFY_STOP; + + case NVEC_PS2: + if (msg[2] == 1) { + for (i = 0; i < (msg[1] - 2); i++) + serio_interrupt(ps2_dev.ser_dev, msg[i + 4], 0); + NVEC_PHD("ps/2 mouse reply: ", &msg[4], msg[1] - 2); + } + + else if (msg[1] != 2) /* !ack */ + NVEC_PHD("unhandled mouse event: ", msg, msg[1] + 2); + return NOTIFY_STOP; + } + + return NOTIFY_DONE; +} + +static int __devinit nvec_mouse_probe(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + struct serio *ser_dev = kzalloc(sizeof(struct serio), GFP_KERNEL); + + ser_dev->id.type = SERIO_PS_PSTHRU; + ser_dev->write = ps2_sendcommand; + ser_dev->start = ps2_startstreaming; + ser_dev->stop = ps2_stopstreaming; + + strlcpy(ser_dev->name, "nvec mouse", sizeof(ser_dev->name)); + strlcpy(ser_dev->phys, "nvec", sizeof(ser_dev->phys)); + + ps2_dev.ser_dev = ser_dev; + ps2_dev.notifier.notifier_call = nvec_ps2_notifier; + ps2_dev.nvec = nvec; + nvec_register_notifier(nvec, &ps2_dev.notifier, 0); + + serio_register_port(ser_dev); + + /* mouse reset */ + nvec_write_async(nvec, MOUSE_RESET, 4); + + return 0; +} + +static int nvec_mouse_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + + /* disable mouse */ + nvec_write_async(nvec, "\x06\xf4", 2); + + /* send cancel autoreceive */ + nvec_write_async(nvec, "\x06\x04", 2); + + return 0; +} + +static int nvec_mouse_resume(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + + ps2_startstreaming(ps2_dev.ser_dev); + + /* enable mouse */ + nvec_write_async(nvec, "\x06\xf5", 2); + + return 0; +} + +static struct platform_driver nvec_mouse_driver = { + .probe = nvec_mouse_probe, + .suspend = nvec_mouse_suspend, + .resume = nvec_mouse_resume, + .driver = { + .name = "nvec-mouse", + .owner = THIS_MODULE, + }, +}; + +static int __init nvec_mouse_init(void) +{ + return platform_driver_register(&nvec_mouse_driver); +} + +module_init(nvec_mouse_init); + +MODULE_DESCRIPTION("NVEC mouse driver"); +MODULE_AUTHOR("Marc Dietrich "); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/octeon/Kconfig b/drivers/staging/octeon/Kconfig new file mode 100644 index 00000000..9493128e --- /dev/null +++ b/drivers/staging/octeon/Kconfig @@ -0,0 +1,13 @@ +config OCTEON_ETHERNET + tristate "Cavium Networks Octeon Ethernet support" + depends on CPU_CAVIUM_OCTEON && NETDEVICES + select PHYLIB + select MDIO_OCTEON + help + This driver supports the builtin ethernet ports on Cavium + Networks' products in the Octeon family. This driver supports the + CN3XXX and CN5XXX Octeon processors. + + To compile this driver as a module, choose M here. The module + will be called octeon-ethernet. + diff --git a/drivers/staging/octeon/Makefile b/drivers/staging/octeon/Makefile new file mode 100644 index 00000000..9012dee0 --- /dev/null +++ b/drivers/staging/octeon/Makefile @@ -0,0 +1,23 @@ +# This file is subject to the terms and conditions of the GNU General Public +# License. See the file "COPYING" in the main directory of this archive +# for more details. +# +# Copyright (C) 2005-2009 Cavium Networks +# + +# +# Makefile for Cavium OCTEON on-board ethernet driver +# + +obj-${CONFIG_OCTEON_ETHERNET} := octeon-ethernet.o + +octeon-ethernet-y := ethernet.o +octeon-ethernet-y += ethernet-mdio.o +octeon-ethernet-y += ethernet-mem.o +octeon-ethernet-y += ethernet-rgmii.o +octeon-ethernet-y += ethernet-rx.o +octeon-ethernet-y += ethernet-sgmii.o +octeon-ethernet-y += ethernet-spi.o +octeon-ethernet-y += ethernet-tx.o +octeon-ethernet-y += ethernet-xaui.o + diff --git a/drivers/staging/octeon/ethernet-defines.h b/drivers/staging/octeon/ethernet-defines.h new file mode 100644 index 00000000..bdaec8d2 --- /dev/null +++ b/drivers/staging/octeon/ethernet-defines.h @@ -0,0 +1,106 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ + +/* + * A few defines are used to control the operation of this driver: + * CONFIG_CAVIUM_RESERVE32 + * This kernel config options controls the amount of memory configured + * in a wired TLB entry for all processes to share. If this is set, the + * driver will use this memory instead of kernel memory for pools. This + * allows 32bit userspace application to access the buffers, but also + * requires all received packets to be copied. + * CONFIG_CAVIUM_OCTEON_NUM_PACKET_BUFFERS + * This kernel config option allows the user to control the number of + * packet and work queue buffers allocated by the driver. If this is zero, + * the driver uses the default from below. + * USE_SKBUFFS_IN_HW + * Tells the driver to populate the packet buffers with kernel skbuffs. + * This allows the driver to receive packets without copying them. It also + * means that 32bit userspace can't access the packet buffers. + * USE_HW_TCPUDP_CHECKSUM + * Controls if the Octeon TCP/UDP checksum engine is used for packet + * output. If this is zero, the kernel will perform the checksum in + * software. + * USE_ASYNC_IOBDMA + * Use asynchronous IO access to hardware. This uses Octeon's asynchronous + * IOBDMAs to issue IO accesses without stalling. Set this to zero + * to disable this. Note that IOBDMAs require CVMSEG. + * REUSE_SKBUFFS_WITHOUT_FREE + * Allows the TX path to free an skbuff into the FPA hardware pool. This + * can significantly improve performance for forwarding and bridging, but + * may be somewhat dangerous. Checks are made, but if any buffer is reused + * without the proper Linux cleanup, the networking stack may have very + * bizarre bugs. + */ +#ifndef __ETHERNET_DEFINES_H__ +#define __ETHERNET_DEFINES_H__ + +#include + + +#define OCTEON_ETHERNET_VERSION "1.9" + +#ifndef CONFIG_CAVIUM_RESERVE32 +#define CONFIG_CAVIUM_RESERVE32 0 +#endif + +#define USE_SKBUFFS_IN_HW 1 +#ifdef CONFIG_NETFILTER +#define REUSE_SKBUFFS_WITHOUT_FREE 0 +#else +#define REUSE_SKBUFFS_WITHOUT_FREE 1 +#endif + +#define USE_HW_TCPUDP_CHECKSUM 1 + +/* Enable Random Early Dropping under load */ +#define USE_RED 1 +#define USE_ASYNC_IOBDMA (CONFIG_CAVIUM_OCTEON_CVMSEG_SIZE > 0) + +/* + * Allow SW based preamble removal at 10Mbps to workaround PHYs giving + * us bad preambles. + */ +#define USE_10MBPS_PREAMBLE_WORKAROUND 1 +/* + * Use this to have all FPA frees also tell the L2 not to write data + * to memory. + */ +#define DONT_WRITEBACK(x) (x) +/* Use this to not have FPA frees control L2 */ +/*#define DONT_WRITEBACK(x) 0 */ + +/* Maximum number of SKBs to try to free per xmit packet. */ +#define MAX_OUT_QUEUE_DEPTH 1000 + +#define FAU_TOTAL_TX_TO_CLEAN (CVMX_FAU_REG_END - sizeof(uint32_t)) +#define FAU_NUM_PACKET_BUFFERS_TO_FREE (FAU_TOTAL_TX_TO_CLEAN - sizeof(uint32_t)) + +#define TOTAL_NUMBER_OF_PORTS (CVMX_PIP_NUM_INPUT_PORTS+1) + + +#endif /* __ETHERNET_DEFINES_H__ */ diff --git a/drivers/staging/octeon/ethernet-mdio.c b/drivers/staging/octeon/ethernet-mdio.c new file mode 100644 index 00000000..e31949c9 --- /dev/null +++ b/drivers/staging/octeon/ethernet-mdio.c @@ -0,0 +1,182 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ +#include +#include +#include +#include + +#include + +#include + +#include "ethernet-defines.h" +#include "octeon-ethernet.h" +#include "ethernet-mdio.h" +#include "ethernet-util.h" + +#include + +#include + +static void cvm_oct_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, "cavium-ethernet"); + strcpy(info->version, OCTEON_ETHERNET_VERSION); + strcpy(info->bus_info, "Builtin"); +} + +static int cvm_oct_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + + if (priv->phydev) + return phy_ethtool_gset(priv->phydev, cmd); + + return -EINVAL; +} + +static int cvm_oct_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (priv->phydev) + return phy_ethtool_sset(priv->phydev, cmd); + + return -EINVAL; +} + +static int cvm_oct_nway_reset(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (priv->phydev) + return phy_start_aneg(priv->phydev); + + return -EINVAL; +} + +const struct ethtool_ops cvm_oct_ethtool_ops = { + .get_drvinfo = cvm_oct_get_drvinfo, + .get_settings = cvm_oct_get_settings, + .set_settings = cvm_oct_set_settings, + .nway_reset = cvm_oct_nway_reset, + .get_link = ethtool_op_get_link, +}; + +/** + * cvm_oct_ioctl - IOCTL support for PHY control + * @dev: Device to change + * @rq: the request + * @cmd: the command + * + * Returns Zero on success + */ +int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + + if (!netif_running(dev)) + return -EINVAL; + + if (!priv->phydev) + return -EINVAL; + + return phy_mii_ioctl(priv->phydev, rq, cmd); +} + +static void cvm_oct_adjust_link(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + cvmx_helper_link_info_t link_info; + + if (priv->last_link != priv->phydev->link) { + priv->last_link = priv->phydev->link; + link_info.u64 = 0; + link_info.s.link_up = priv->last_link ? 1 : 0; + link_info.s.full_duplex = priv->phydev->duplex ? 1 : 0; + link_info.s.speed = priv->phydev->speed; + cvmx_helper_link_set( priv->port, link_info); + if (priv->last_link) { + netif_carrier_on(dev); + if (priv->queue != -1) + printk_ratelimited("%s: %u Mbps %s duplex, " + "port %2d, queue %2d\n", + dev->name, priv->phydev->speed, + priv->phydev->duplex ? + "Full" : "Half", + priv->port, priv->queue); + else + printk_ratelimited("%s: %u Mbps %s duplex, " + "port %2d, POW\n", + dev->name, priv->phydev->speed, + priv->phydev->duplex ? + "Full" : "Half", + priv->port); + } else { + netif_carrier_off(dev); + printk_ratelimited("%s: Link down\n", dev->name); + } + } +} + + +/** + * cvm_oct_phy_setup_device - setup the PHY + * + * @dev: Device to setup + * + * Returns Zero on success, negative on failure + */ +int cvm_oct_phy_setup_device(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + + int phy_addr = cvmx_helper_board_get_mii_address(priv->port); + if (phy_addr != -1) { + char phy_id[MII_BUS_ID_SIZE + 3]; + + snprintf(phy_id, sizeof(phy_id), PHY_ID_FMT, "mdio-octeon-0", phy_addr); + + priv->phydev = phy_connect(dev, phy_id, cvm_oct_adjust_link, 0, + PHY_INTERFACE_MODE_GMII); + + if (IS_ERR(priv->phydev)) { + priv->phydev = NULL; + return -1; + } + priv->last_link = 0; + phy_start_aneg(priv->phydev); + } + return 0; +} diff --git a/drivers/staging/octeon/ethernet-mdio.h b/drivers/staging/octeon/ethernet-mdio.h new file mode 100644 index 00000000..a417d4fc --- /dev/null +++ b/drivers/staging/octeon/ethernet-mdio.h @@ -0,0 +1,45 @@ +/********************************************************************* + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +*********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_XFRM +#include +#include +#endif /* CONFIG_XFRM */ + +extern const struct ethtool_ops cvm_oct_ethtool_ops; +int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +int cvm_oct_phy_setup_device(struct net_device *dev); diff --git a/drivers/staging/octeon/ethernet-mem.c b/drivers/staging/octeon/ethernet-mem.c new file mode 100644 index 00000000..78b6cb74 --- /dev/null +++ b/drivers/staging/octeon/ethernet-mem.c @@ -0,0 +1,177 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2010 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ +#include +#include +#include + +#include + +#include "ethernet-defines.h" + +#include + +/** + * cvm_oct_fill_hw_skbuff - fill the supplied hardware pool with skbuffs + * @pool: Pool to allocate an skbuff for + * @size: Size of the buffer needed for the pool + * @elements: Number of buffers to allocate + * + * Returns the actual number of buffers allocated. + */ +static int cvm_oct_fill_hw_skbuff(int pool, int size, int elements) +{ + int freed = elements; + while (freed) { + + struct sk_buff *skb = dev_alloc_skb(size + 256); + if (unlikely(skb == NULL)) { + pr_warning + ("Failed to allocate skb for hardware pool %d\n", + pool); + break; + } + + skb_reserve(skb, 256 - (((unsigned long)skb->data) & 0x7f)); + *(struct sk_buff **)(skb->data - sizeof(void *)) = skb; + cvmx_fpa_free(skb->data, pool, DONT_WRITEBACK(size / 128)); + freed--; + } + return elements - freed; +} + +/** + * cvm_oct_free_hw_skbuff- free hardware pool skbuffs + * @pool: Pool to allocate an skbuff for + * @size: Size of the buffer needed for the pool + * @elements: Number of buffers to allocate + */ +static void cvm_oct_free_hw_skbuff(int pool, int size, int elements) +{ + char *memory; + + do { + memory = cvmx_fpa_alloc(pool); + if (memory) { + struct sk_buff *skb = + *(struct sk_buff **)(memory - sizeof(void *)); + elements--; + dev_kfree_skb(skb); + } + } while (memory); + + if (elements < 0) + pr_warning("Freeing of pool %u had too many skbuffs (%d)\n", + pool, elements); + else if (elements > 0) + pr_warning("Freeing of pool %u is missing %d skbuffs\n", + pool, elements); +} + +/** + * cvm_oct_fill_hw_memory - fill a hardware pool with memory. + * @pool: Pool to populate + * @size: Size of each buffer in the pool + * @elements: Number of buffers to allocate + * + * Returns the actual number of buffers allocated. + */ +static int cvm_oct_fill_hw_memory(int pool, int size, int elements) +{ + char *memory; + char *fpa; + int freed = elements; + + while (freed) { + /* + * FPA memory must be 128 byte aligned. Since we are + * aligning we need to save the original pointer so we + * can feed it to kfree when the memory is returned to + * the kernel. + * + * We allocate an extra 256 bytes to allow for + * alignment and space for the original pointer saved + * just before the block. + */ + memory = kmalloc(size + 256, GFP_ATOMIC); + if (unlikely(memory == NULL)) { + pr_warning("Unable to allocate %u bytes for FPA pool %d\n", + elements * size, pool); + break; + } + fpa = (char *)(((unsigned long)memory + 256) & ~0x7fUL); + *((char **)fpa - 1) = memory; + cvmx_fpa_free(fpa, pool, 0); + freed--; + } + return elements - freed; +} + +/** + * cvm_oct_free_hw_memory - Free memory allocated by cvm_oct_fill_hw_memory + * @pool: FPA pool to free + * @size: Size of each buffer in the pool + * @elements: Number of buffers that should be in the pool + */ +static void cvm_oct_free_hw_memory(int pool, int size, int elements) +{ + char *memory; + char *fpa; + do { + fpa = cvmx_fpa_alloc(pool); + if (fpa) { + elements--; + fpa = (char *)phys_to_virt(cvmx_ptr_to_phys(fpa)); + memory = *((char **)fpa - 1); + kfree(memory); + } + } while (fpa); + + if (elements < 0) + pr_warning("Freeing of pool %u had too many buffers (%d)\n", + pool, elements); + else if (elements > 0) + pr_warning("Warning: Freeing of pool %u is missing %d buffers\n", + pool, elements); +} + +int cvm_oct_mem_fill_fpa(int pool, int size, int elements) +{ + int freed; + if (USE_SKBUFFS_IN_HW && pool == CVMX_FPA_PACKET_POOL) + freed = cvm_oct_fill_hw_skbuff(pool, size, elements); + else + freed = cvm_oct_fill_hw_memory(pool, size, elements); + return freed; +} + +void cvm_oct_mem_empty_fpa(int pool, int size, int elements) +{ + if (USE_SKBUFFS_IN_HW && pool == CVMX_FPA_PACKET_POOL) + cvm_oct_free_hw_skbuff(pool, size, elements); + else + cvm_oct_free_hw_memory(pool, size, elements); +} diff --git a/drivers/staging/octeon/ethernet-mem.h b/drivers/staging/octeon/ethernet-mem.h new file mode 100644 index 00000000..713f2edc --- /dev/null +++ b/drivers/staging/octeon/ethernet-mem.h @@ -0,0 +1,29 @@ +/********************************************************************* + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +********************************************************************/ + +int cvm_oct_mem_fill_fpa(int pool, int size, int elements); +void cvm_oct_mem_empty_fpa(int pool, int size, int elements); diff --git a/drivers/staging/octeon/ethernet-rgmii.c b/drivers/staging/octeon/ethernet-rgmii.c new file mode 100644 index 00000000..d8f5f694 --- /dev/null +++ b/drivers/staging/octeon/ethernet-rgmii.c @@ -0,0 +1,430 @@ +/********************************************************************* + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ +#include +#include +#include +#include +#include +#include + +#include + +#include "ethernet-defines.h" +#include "octeon-ethernet.h" +#include "ethernet-util.h" + +#include + +#include +#include +#include + +DEFINE_SPINLOCK(global_register_lock); + +static int number_rgmii_ports; + +static void cvm_oct_rgmii_poll(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + unsigned long flags = 0; + cvmx_helper_link_info_t link_info; + int use_global_register_lock = (priv->phydev == NULL); + + BUG_ON(in_interrupt()); + if (use_global_register_lock) { + /* + * Take the global register lock since we are going to + * touch registers that affect more than one port. + */ + spin_lock_irqsave(&global_register_lock, flags); + } else { + mutex_lock(&priv->phydev->bus->mdio_lock); + } + + link_info = cvmx_helper_link_get(priv->port); + if (link_info.u64 == priv->link_info) { + + /* + * If the 10Mbps preamble workaround is supported and we're + * at 10Mbps we may need to do some special checking. + */ + if (USE_10MBPS_PREAMBLE_WORKAROUND && (link_info.s.speed == 10)) { + + /* + * Read the GMXX_RXX_INT_REG[PCTERR] bit and + * see if we are getting preamble errors. + */ + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg; + gmxx_rxx_int_reg.u64 = + cvmx_read_csr(CVMX_GMXX_RXX_INT_REG + (index, interface)); + if (gmxx_rxx_int_reg.s.pcterr) { + + /* + * We are getting preamble errors at + * 10Mbps. Most likely the PHY is + * giving us packets with mis aligned + * preambles. In order to get these + * packets we need to disable preamble + * checking and do it in software. + */ + union cvmx_gmxx_rxx_frm_ctl gmxx_rxx_frm_ctl; + union cvmx_ipd_sub_port_fcs ipd_sub_port_fcs; + + /* Disable preamble checking */ + gmxx_rxx_frm_ctl.u64 = + cvmx_read_csr(CVMX_GMXX_RXX_FRM_CTL + (index, interface)); + gmxx_rxx_frm_ctl.s.pre_chk = 0; + cvmx_write_csr(CVMX_GMXX_RXX_FRM_CTL + (index, interface), + gmxx_rxx_frm_ctl.u64); + + /* Disable FCS stripping */ + ipd_sub_port_fcs.u64 = + cvmx_read_csr(CVMX_IPD_SUB_PORT_FCS); + ipd_sub_port_fcs.s.port_bit &= + 0xffffffffull ^ (1ull << priv->port); + cvmx_write_csr(CVMX_IPD_SUB_PORT_FCS, + ipd_sub_port_fcs.u64); + + /* Clear any error bits */ + cvmx_write_csr(CVMX_GMXX_RXX_INT_REG + (index, interface), + gmxx_rxx_int_reg.u64); + printk_ratelimited("%s: Using 10Mbps with software " + "preamble removal\n", + dev->name); + } + } + + if (use_global_register_lock) + spin_unlock_irqrestore(&global_register_lock, flags); + else + mutex_unlock(&priv->phydev->bus->mdio_lock); + return; + } + + /* If the 10Mbps preamble workaround is allowed we need to on + preamble checking, FCS stripping, and clear error bits on + every speed change. If errors occur during 10Mbps operation + the above code will change this stuff */ + if (USE_10MBPS_PREAMBLE_WORKAROUND) { + + union cvmx_gmxx_rxx_frm_ctl gmxx_rxx_frm_ctl; + union cvmx_ipd_sub_port_fcs ipd_sub_port_fcs; + union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg; + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + + /* Enable preamble checking */ + gmxx_rxx_frm_ctl.u64 = + cvmx_read_csr(CVMX_GMXX_RXX_FRM_CTL(index, interface)); + gmxx_rxx_frm_ctl.s.pre_chk = 1; + cvmx_write_csr(CVMX_GMXX_RXX_FRM_CTL(index, interface), + gmxx_rxx_frm_ctl.u64); + /* Enable FCS stripping */ + ipd_sub_port_fcs.u64 = cvmx_read_csr(CVMX_IPD_SUB_PORT_FCS); + ipd_sub_port_fcs.s.port_bit |= 1ull << priv->port; + cvmx_write_csr(CVMX_IPD_SUB_PORT_FCS, ipd_sub_port_fcs.u64); + /* Clear any error bits */ + gmxx_rxx_int_reg.u64 = + cvmx_read_csr(CVMX_GMXX_RXX_INT_REG(index, interface)); + cvmx_write_csr(CVMX_GMXX_RXX_INT_REG(index, interface), + gmxx_rxx_int_reg.u64); + } + if (priv->phydev == NULL) { + link_info = cvmx_helper_link_autoconf(priv->port); + priv->link_info = link_info.u64; + } + + if (use_global_register_lock) + spin_unlock_irqrestore(&global_register_lock, flags); + else { + mutex_unlock(&priv->phydev->bus->mdio_lock); + } + + if (priv->phydev == NULL) { + /* Tell core. */ + if (link_info.s.link_up) { + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + if (priv->queue != -1) + printk_ratelimited("%s: %u Mbps %s duplex, " + "port %2d, queue %2d\n", + dev->name, link_info.s.speed, + (link_info.s.full_duplex) ? + "Full" : "Half", + priv->port, priv->queue); + else + printk_ratelimited("%s: %u Mbps %s duplex, " + "port %2d, POW\n", + dev->name, link_info.s.speed, + (link_info.s.full_duplex) ? + "Full" : "Half", + priv->port); + } else { + if (netif_carrier_ok(dev)) + netif_carrier_off(dev); + printk_ratelimited("%s: Link down\n", dev->name); + } + } +} + +static irqreturn_t cvm_oct_rgmii_rml_interrupt(int cpl, void *dev_id) +{ + union cvmx_npi_rsl_int_blocks rsl_int_blocks; + int index; + irqreturn_t return_status = IRQ_NONE; + + rsl_int_blocks.u64 = cvmx_read_csr(CVMX_NPI_RSL_INT_BLOCKS); + + /* Check and see if this interrupt was caused by the GMX0 block */ + if (rsl_int_blocks.s.gmx0) { + + int interface = 0; + /* Loop through every port of this interface */ + for (index = 0; + index < cvmx_helper_ports_on_interface(interface); + index++) { + + /* Read the GMX interrupt status bits */ + union cvmx_gmxx_rxx_int_reg gmx_rx_int_reg; + gmx_rx_int_reg.u64 = + cvmx_read_csr(CVMX_GMXX_RXX_INT_REG + (index, interface)); + gmx_rx_int_reg.u64 &= + cvmx_read_csr(CVMX_GMXX_RXX_INT_EN + (index, interface)); + /* Poll the port if inband status changed */ + if (gmx_rx_int_reg.s.phy_dupx + || gmx_rx_int_reg.s.phy_link + || gmx_rx_int_reg.s.phy_spd) { + + struct net_device *dev = + cvm_oct_device[cvmx_helper_get_ipd_port + (interface, index)]; + struct octeon_ethernet *priv = netdev_priv(dev); + + if (dev && !atomic_read(&cvm_oct_poll_queue_stopping)) + queue_work(cvm_oct_poll_queue, &priv->port_work); + + gmx_rx_int_reg.u64 = 0; + gmx_rx_int_reg.s.phy_dupx = 1; + gmx_rx_int_reg.s.phy_link = 1; + gmx_rx_int_reg.s.phy_spd = 1; + cvmx_write_csr(CVMX_GMXX_RXX_INT_REG + (index, interface), + gmx_rx_int_reg.u64); + return_status = IRQ_HANDLED; + } + } + } + + /* Check and see if this interrupt was caused by the GMX1 block */ + if (rsl_int_blocks.s.gmx1) { + + int interface = 1; + /* Loop through every port of this interface */ + for (index = 0; + index < cvmx_helper_ports_on_interface(interface); + index++) { + + /* Read the GMX interrupt status bits */ + union cvmx_gmxx_rxx_int_reg gmx_rx_int_reg; + gmx_rx_int_reg.u64 = + cvmx_read_csr(CVMX_GMXX_RXX_INT_REG + (index, interface)); + gmx_rx_int_reg.u64 &= + cvmx_read_csr(CVMX_GMXX_RXX_INT_EN + (index, interface)); + /* Poll the port if inband status changed */ + if (gmx_rx_int_reg.s.phy_dupx + || gmx_rx_int_reg.s.phy_link + || gmx_rx_int_reg.s.phy_spd) { + + struct net_device *dev = + cvm_oct_device[cvmx_helper_get_ipd_port + (interface, index)]; + struct octeon_ethernet *priv = netdev_priv(dev); + + if (dev && !atomic_read(&cvm_oct_poll_queue_stopping)) + queue_work(cvm_oct_poll_queue, &priv->port_work); + + gmx_rx_int_reg.u64 = 0; + gmx_rx_int_reg.s.phy_dupx = 1; + gmx_rx_int_reg.s.phy_link = 1; + gmx_rx_int_reg.s.phy_spd = 1; + cvmx_write_csr(CVMX_GMXX_RXX_INT_REG + (index, interface), + gmx_rx_int_reg.u64); + return_status = IRQ_HANDLED; + } + } + } + return return_status; +} + +int cvm_oct_rgmii_open(struct net_device *dev) +{ + union cvmx_gmxx_prtx_cfg gmx_cfg; + struct octeon_ethernet *priv = netdev_priv(dev); + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + cvmx_helper_link_info_t link_info; + + gmx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); + gmx_cfg.s.en = 1; + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmx_cfg.u64); + + if (!octeon_is_simulation()) { + link_info = cvmx_helper_link_get(priv->port); + if (!link_info.s.link_up) + netif_carrier_off(dev); + } + + return 0; +} + +int cvm_oct_rgmii_stop(struct net_device *dev) +{ + union cvmx_gmxx_prtx_cfg gmx_cfg; + struct octeon_ethernet *priv = netdev_priv(dev); + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + + gmx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); + gmx_cfg.s.en = 0; + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmx_cfg.u64); + return 0; +} + +static void cvm_oct_rgmii_immediate_poll(struct work_struct *work) +{ + struct octeon_ethernet *priv = container_of(work, struct octeon_ethernet, port_work); + cvm_oct_rgmii_poll(cvm_oct_device[priv->port]); +} + +int cvm_oct_rgmii_init(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + int r; + + cvm_oct_common_init(dev); + dev->netdev_ops->ndo_stop(dev); + INIT_WORK(&priv->port_work, cvm_oct_rgmii_immediate_poll); + /* + * Due to GMX errata in CN3XXX series chips, it is necessary + * to take the link down immediately when the PHY changes + * state. In order to do this we call the poll function every + * time the RGMII inband status changes. This may cause + * problems if the PHY doesn't implement inband status + * properly. + */ + if (number_rgmii_ports == 0) { + r = request_irq(OCTEON_IRQ_RML, cvm_oct_rgmii_rml_interrupt, + IRQF_SHARED, "RGMII", &number_rgmii_ports); + if (r != 0) + return r; + } + number_rgmii_ports++; + + /* + * Only true RGMII ports need to be polled. In GMII mode, port + * 0 is really a RGMII port. + */ + if (((priv->imode == CVMX_HELPER_INTERFACE_MODE_GMII) + && (priv->port == 0)) + || (priv->imode == CVMX_HELPER_INTERFACE_MODE_RGMII)) { + + if (!octeon_is_simulation()) { + + union cvmx_gmxx_rxx_int_en gmx_rx_int_en; + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + + /* + * Enable interrupts on inband status changes + * for this port. + */ + gmx_rx_int_en.u64 = + cvmx_read_csr(CVMX_GMXX_RXX_INT_EN + (index, interface)); + gmx_rx_int_en.s.phy_dupx = 1; + gmx_rx_int_en.s.phy_link = 1; + gmx_rx_int_en.s.phy_spd = 1; + cvmx_write_csr(CVMX_GMXX_RXX_INT_EN(index, interface), + gmx_rx_int_en.u64); + priv->poll = cvm_oct_rgmii_poll; + } + } + + return 0; +} + +void cvm_oct_rgmii_uninit(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + cvm_oct_common_uninit(dev); + + /* + * Only true RGMII ports need to be polled. In GMII mode, port + * 0 is really a RGMII port. + */ + if (((priv->imode == CVMX_HELPER_INTERFACE_MODE_GMII) + && (priv->port == 0)) + || (priv->imode == CVMX_HELPER_INTERFACE_MODE_RGMII)) { + + if (!octeon_is_simulation()) { + + union cvmx_gmxx_rxx_int_en gmx_rx_int_en; + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + + /* + * Disable interrupts on inband status changes + * for this port. + */ + gmx_rx_int_en.u64 = + cvmx_read_csr(CVMX_GMXX_RXX_INT_EN + (index, interface)); + gmx_rx_int_en.s.phy_dupx = 0; + gmx_rx_int_en.s.phy_link = 0; + gmx_rx_int_en.s.phy_spd = 0; + cvmx_write_csr(CVMX_GMXX_RXX_INT_EN(index, interface), + gmx_rx_int_en.u64); + } + } + + /* Remove the interrupt handler when the last port is removed. */ + number_rgmii_ports--; + if (number_rgmii_ports == 0) + free_irq(OCTEON_IRQ_RML, &number_rgmii_ports); + cancel_work_sync(&priv->port_work); +} diff --git a/drivers/staging/octeon/ethernet-rx.c b/drivers/staging/octeon/ethernet-rx.c new file mode 100644 index 00000000..d91751f9 --- /dev/null +++ b/drivers/staging/octeon/ethernet-rx.c @@ -0,0 +1,563 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2010 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_XFRM +#include +#include +#endif /* CONFIG_XFRM */ + +#include + +#include + +#include "ethernet-defines.h" +#include "ethernet-mem.h" +#include "ethernet-rx.h" +#include "octeon-ethernet.h" +#include "ethernet-util.h" + +#include +#include +#include +#include +#include +#include + +#include + +struct cvm_napi_wrapper { + struct napi_struct napi; +} ____cacheline_aligned_in_smp; + +static struct cvm_napi_wrapper cvm_oct_napi[NR_CPUS] __cacheline_aligned_in_smp; + +struct cvm_oct_core_state { + int baseline_cores; + /* + * The number of additional cores that could be processing + * input packtes. + */ + atomic_t available_cores; + cpumask_t cpu_state; +} ____cacheline_aligned_in_smp; + +static struct cvm_oct_core_state core_state __cacheline_aligned_in_smp; + +static void cvm_oct_enable_napi(void *_) +{ + int cpu = smp_processor_id(); + napi_schedule(&cvm_oct_napi[cpu].napi); +} + +static void cvm_oct_enable_one_cpu(void) +{ + int v; + int cpu; + + /* Check to see if more CPUs are available for receive processing... */ + v = atomic_sub_if_positive(1, &core_state.available_cores); + if (v < 0) + return; + + /* ... if a CPU is available, Turn on NAPI polling for that CPU. */ + for_each_online_cpu(cpu) { + if (!cpu_test_and_set(cpu, core_state.cpu_state)) { + v = smp_call_function_single(cpu, cvm_oct_enable_napi, + NULL, 0); + if (v) + panic("Can't enable NAPI."); + break; + } + } +} + +static void cvm_oct_no_more_work(void) +{ + int cpu = smp_processor_id(); + + /* + * CPU zero is special. It always has the irq enabled when + * waiting for incoming packets. + */ + if (cpu == 0) { + enable_irq(OCTEON_IRQ_WORKQ0 + pow_receive_group); + return; + } + + cpu_clear(cpu, core_state.cpu_state); + atomic_add(1, &core_state.available_cores); +} + +/** + * cvm_oct_do_interrupt - interrupt handler. + * + * The interrupt occurs whenever the POW has packets in our group. + * + */ +static irqreturn_t cvm_oct_do_interrupt(int cpl, void *dev_id) +{ + /* Disable the IRQ and start napi_poll. */ + disable_irq_nosync(OCTEON_IRQ_WORKQ0 + pow_receive_group); + cvm_oct_enable_napi(NULL); + + return IRQ_HANDLED; +} + +/** + * cvm_oct_check_rcv_error - process receive errors + * @work: Work queue entry pointing to the packet. + * + * Returns Non-zero if the packet can be dropped, zero otherwise. + */ +static inline int cvm_oct_check_rcv_error(cvmx_wqe_t *work) +{ + if ((work->word2.snoip.err_code == 10) && (work->len <= 64)) { + /* + * Ignore length errors on min size packets. Some + * equipment incorrectly pads packets to 64+4FCS + * instead of 60+4FCS. Note these packets still get + * counted as frame errors. + */ + } else + if (USE_10MBPS_PREAMBLE_WORKAROUND + && ((work->word2.snoip.err_code == 5) + || (work->word2.snoip.err_code == 7))) { + + /* + * We received a packet with either an alignment error + * or a FCS error. This may be signalling that we are + * running 10Mbps with GMXX_RXX_FRM_CTL[PRE_CHK} + * off. If this is the case we need to parse the + * packet to determine if we can remove a non spec + * preamble and generate a correct packet. + */ + int interface = cvmx_helper_get_interface_num(work->ipprt); + int index = cvmx_helper_get_interface_index_num(work->ipprt); + union cvmx_gmxx_rxx_frm_ctl gmxx_rxx_frm_ctl; + gmxx_rxx_frm_ctl.u64 = + cvmx_read_csr(CVMX_GMXX_RXX_FRM_CTL(index, interface)); + if (gmxx_rxx_frm_ctl.s.pre_chk == 0) { + + uint8_t *ptr = + cvmx_phys_to_ptr(work->packet_ptr.s.addr); + int i = 0; + + while (i < work->len - 1) { + if (*ptr != 0x55) + break; + ptr++; + i++; + } + + if (*ptr == 0xd5) { + /* + printk_ratelimited("Port %d received 0xd5 preamble\n", work->ipprt); + */ + work->packet_ptr.s.addr += i + 1; + work->len -= i + 5; + } else if ((*ptr & 0xf) == 0xd) { + /* + printk_ratelimited("Port %d received 0x?d preamble\n", work->ipprt); + */ + work->packet_ptr.s.addr += i; + work->len -= i + 4; + for (i = 0; i < work->len; i++) { + *ptr = + ((*ptr & 0xf0) >> 4) | + ((*(ptr + 1) & 0xf) << 4); + ptr++; + } + } else { + printk_ratelimited("Port %d unknown preamble, packet " + "dropped\n", + work->ipprt); + /* + cvmx_helper_dump_packet(work); + */ + cvm_oct_free_work(work); + return 1; + } + } + } else { + printk_ratelimited("Port %d receive error code %d, packet dropped\n", + work->ipprt, work->word2.snoip.err_code); + cvm_oct_free_work(work); + return 1; + } + + return 0; +} + +/** + * cvm_oct_napi_poll - the NAPI poll function. + * @napi: The NAPI instance, or null if called from cvm_oct_poll_controller + * @budget: Maximum number of packets to receive. + * + * Returns the number of packets processed. + */ +static int cvm_oct_napi_poll(struct napi_struct *napi, int budget) +{ + const int coreid = cvmx_get_core_num(); + uint64_t old_group_mask; + uint64_t old_scratch; + int rx_count = 0; + int did_work_request = 0; + int packet_not_copied; + + /* Prefetch cvm_oct_device since we know we need it soon */ + prefetch(cvm_oct_device); + + if (USE_ASYNC_IOBDMA) { + /* Save scratch in case userspace is using it */ + CVMX_SYNCIOBDMA; + old_scratch = cvmx_scratch_read64(CVMX_SCR_SCRATCH); + } + + /* Only allow work for our group (and preserve priorities) */ + old_group_mask = cvmx_read_csr(CVMX_POW_PP_GRP_MSKX(coreid)); + cvmx_write_csr(CVMX_POW_PP_GRP_MSKX(coreid), + (old_group_mask & ~0xFFFFull) | 1 << pow_receive_group); + + if (USE_ASYNC_IOBDMA) { + cvmx_pow_work_request_async(CVMX_SCR_SCRATCH, CVMX_POW_NO_WAIT); + did_work_request = 1; + } + + while (rx_count < budget) { + struct sk_buff *skb = NULL; + struct sk_buff **pskb = NULL; + int skb_in_hw; + cvmx_wqe_t *work; + + if (USE_ASYNC_IOBDMA && did_work_request) + work = cvmx_pow_work_response_async(CVMX_SCR_SCRATCH); + else + work = cvmx_pow_work_request_sync(CVMX_POW_NO_WAIT); + + prefetch(work); + did_work_request = 0; + if (work == NULL) { + union cvmx_pow_wq_int wq_int; + wq_int.u64 = 0; + wq_int.s.iq_dis = 1 << pow_receive_group; + wq_int.s.wq_int = 1 << pow_receive_group; + cvmx_write_csr(CVMX_POW_WQ_INT, wq_int.u64); + break; + } + pskb = (struct sk_buff **)(cvm_oct_get_buffer_ptr(work->packet_ptr) - sizeof(void *)); + prefetch(pskb); + + if (USE_ASYNC_IOBDMA && rx_count < (budget - 1)) { + cvmx_pow_work_request_async_nocheck(CVMX_SCR_SCRATCH, CVMX_POW_NO_WAIT); + did_work_request = 1; + } + + if (rx_count == 0) { + /* + * First time through, see if there is enough + * work waiting to merit waking another + * CPU. + */ + union cvmx_pow_wq_int_cntx counts; + int backlog; + int cores_in_use = core_state.baseline_cores - atomic_read(&core_state.available_cores); + counts.u64 = cvmx_read_csr(CVMX_POW_WQ_INT_CNTX(pow_receive_group)); + backlog = counts.s.iq_cnt + counts.s.ds_cnt; + if (backlog > budget * cores_in_use && napi != NULL) + cvm_oct_enable_one_cpu(); + } + + skb_in_hw = USE_SKBUFFS_IN_HW && work->word2.s.bufs == 1; + if (likely(skb_in_hw)) { + skb = *pskb; + prefetch(&skb->head); + prefetch(&skb->len); + } + prefetch(cvm_oct_device[work->ipprt]); + + /* Immediately throw away all packets with receive errors */ + if (unlikely(work->word2.snoip.rcv_error)) { + if (cvm_oct_check_rcv_error(work)) + continue; + } + + /* + * We can only use the zero copy path if skbuffs are + * in the FPA pool and the packet fits in a single + * buffer. + */ + if (likely(skb_in_hw)) { + skb->data = skb->head + work->packet_ptr.s.addr - cvmx_ptr_to_phys(skb->head); + prefetch(skb->data); + skb->len = work->len; + skb_set_tail_pointer(skb, skb->len); + packet_not_copied = 1; + } else { + /* + * We have to copy the packet. First allocate + * an skbuff for it. + */ + skb = dev_alloc_skb(work->len); + if (!skb) { + printk_ratelimited("Port %d failed to allocate " + "skbuff, packet dropped\n", + work->ipprt); + cvm_oct_free_work(work); + continue; + } + + /* + * Check if we've received a packet that was + * entirely stored in the work entry. + */ + if (unlikely(work->word2.s.bufs == 0)) { + uint8_t *ptr = work->packet_data; + + if (likely(!work->word2.s.not_IP)) { + /* + * The beginning of the packet + * moves for IP packets. + */ + if (work->word2.s.is_v6) + ptr += 2; + else + ptr += 6; + } + memcpy(skb_put(skb, work->len), ptr, work->len); + /* No packet buffers to free */ + } else { + int segments = work->word2.s.bufs; + union cvmx_buf_ptr segment_ptr = work->packet_ptr; + int len = work->len; + + while (segments--) { + union cvmx_buf_ptr next_ptr = + *(union cvmx_buf_ptr *)cvmx_phys_to_ptr(segment_ptr.s.addr - 8); + + /* + * Octeon Errata PKI-100: The segment size is + * wrong. Until it is fixed, calculate the + * segment size based on the packet pool + * buffer size. When it is fixed, the + * following line should be replaced with this + * one: int segment_size = + * segment_ptr.s.size; + */ + int segment_size = CVMX_FPA_PACKET_POOL_SIZE - + (segment_ptr.s.addr - (((segment_ptr.s.addr >> 7) - segment_ptr.s.back) << 7)); + /* + * Don't copy more than what + * is left in the packet. + */ + if (segment_size > len) + segment_size = len; + /* Copy the data into the packet */ + memcpy(skb_put(skb, segment_size), + cvmx_phys_to_ptr(segment_ptr.s.addr), + segment_size); + len -= segment_size; + segment_ptr = next_ptr; + } + } + packet_not_copied = 0; + } + + if (likely((work->ipprt < TOTAL_NUMBER_OF_PORTS) && + cvm_oct_device[work->ipprt])) { + struct net_device *dev = cvm_oct_device[work->ipprt]; + struct octeon_ethernet *priv = netdev_priv(dev); + + /* + * Only accept packets for devices that are + * currently up. + */ + if (likely(dev->flags & IFF_UP)) { + skb->protocol = eth_type_trans(skb, dev); + skb->dev = dev; + + if (unlikely(work->word2.s.not_IP || work->word2.s.IP_exc || + work->word2.s.L4_error || !work->word2.s.tcp_or_udp)) + skb->ip_summed = CHECKSUM_NONE; + else + skb->ip_summed = CHECKSUM_UNNECESSARY; + + /* Increment RX stats for virtual ports */ + if (work->ipprt >= CVMX_PIP_NUM_INPUT_PORTS) { +#ifdef CONFIG_64BIT + atomic64_add(1, (atomic64_t *)&priv->stats.rx_packets); + atomic64_add(skb->len, (atomic64_t *)&priv->stats.rx_bytes); +#else + atomic_add(1, (atomic_t *)&priv->stats.rx_packets); + atomic_add(skb->len, (atomic_t *)&priv->stats.rx_bytes); +#endif + } + netif_receive_skb(skb); + rx_count++; + } else { + /* Drop any packet received for a device that isn't up */ + /* + printk_ratelimited("%s: Device not up, packet dropped\n", + dev->name); + */ +#ifdef CONFIG_64BIT + atomic64_add(1, (atomic64_t *)&priv->stats.rx_dropped); +#else + atomic_add(1, (atomic_t *)&priv->stats.rx_dropped); +#endif + dev_kfree_skb_irq(skb); + } + } else { + /* + * Drop any packet received for a device that + * doesn't exist. + */ + printk_ratelimited("Port %d not controlled by Linux, packet dropped\n", + work->ipprt); + dev_kfree_skb_irq(skb); + } + /* + * Check to see if the skbuff and work share the same + * packet buffer. + */ + if (USE_SKBUFFS_IN_HW && likely(packet_not_copied)) { + /* + * This buffer needs to be replaced, increment + * the number of buffers we need to free by + * one. + */ + cvmx_fau_atomic_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, + 1); + + cvmx_fpa_free(work, CVMX_FPA_WQE_POOL, + DONT_WRITEBACK(1)); + } else { + cvm_oct_free_work(work); + } + } + /* Restore the original POW group mask */ + cvmx_write_csr(CVMX_POW_PP_GRP_MSKX(coreid), old_group_mask); + if (USE_ASYNC_IOBDMA) { + /* Restore the scratch area */ + cvmx_scratch_write64(CVMX_SCR_SCRATCH, old_scratch); + } + cvm_oct_rx_refill_pool(0); + + if (rx_count < budget && napi != NULL) { + /* No more work */ + napi_complete(napi); + cvm_oct_no_more_work(); + } + return rx_count; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +/** + * cvm_oct_poll_controller - poll for receive packets + * device. + * + * @dev: Device to poll. Unused + */ +void cvm_oct_poll_controller(struct net_device *dev) +{ + cvm_oct_napi_poll(NULL, 16); +} +#endif + +void cvm_oct_rx_initialize(void) +{ + int i; + struct net_device *dev_for_napi = NULL; + union cvmx_pow_wq_int_thrx int_thr; + union cvmx_pow_wq_int_pc int_pc; + + for (i = 0; i < TOTAL_NUMBER_OF_PORTS; i++) { + if (cvm_oct_device[i]) { + dev_for_napi = cvm_oct_device[i]; + break; + } + } + + if (NULL == dev_for_napi) + panic("No net_devices were allocated."); + + if (max_rx_cpus > 1 && max_rx_cpus < num_online_cpus()) + atomic_set(&core_state.available_cores, max_rx_cpus); + else + atomic_set(&core_state.available_cores, num_online_cpus()); + core_state.baseline_cores = atomic_read(&core_state.available_cores); + + core_state.cpu_state = CPU_MASK_NONE; + for_each_possible_cpu(i) { + netif_napi_add(dev_for_napi, &cvm_oct_napi[i].napi, + cvm_oct_napi_poll, rx_napi_weight); + napi_enable(&cvm_oct_napi[i].napi); + } + /* Register an IRQ hander for to receive POW interrupts */ + i = request_irq(OCTEON_IRQ_WORKQ0 + pow_receive_group, + cvm_oct_do_interrupt, 0, "Ethernet", cvm_oct_device); + + if (i) + panic("Could not acquire Ethernet IRQ %d\n", + OCTEON_IRQ_WORKQ0 + pow_receive_group); + + disable_irq_nosync(OCTEON_IRQ_WORKQ0 + pow_receive_group); + + int_thr.u64 = 0; + int_thr.s.tc_en = 1; + int_thr.s.tc_thr = 1; + /* Enable POW interrupt when our port has at least one packet */ + cvmx_write_csr(CVMX_POW_WQ_INT_THRX(pow_receive_group), int_thr.u64); + + int_pc.u64 = 0; + int_pc.s.pc_thr = 5; + cvmx_write_csr(CVMX_POW_WQ_INT_PC, int_pc.u64); + + + /* Scheduld NAPI now. This will indirectly enable interrupts. */ + cvm_oct_enable_one_cpu(); +} + +void cvm_oct_rx_shutdown(void) +{ + int i; + /* Shutdown all of the NAPIs */ + for_each_possible_cpu(i) + netif_napi_del(&cvm_oct_napi[i].napi); +} diff --git a/drivers/staging/octeon/ethernet-rx.h b/drivers/staging/octeon/ethernet-rx.h new file mode 100644 index 00000000..9240c85c --- /dev/null +++ b/drivers/staging/octeon/ethernet-rx.h @@ -0,0 +1,52 @@ +/********************************************************************* + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +*********************************************************************/ +#include + +void cvm_oct_poll_controller(struct net_device *dev); +void cvm_oct_rx_initialize(void); +void cvm_oct_rx_shutdown(void); + +static inline void cvm_oct_rx_refill_pool(int fill_threshold) +{ + int number_to_free; + int num_freed; + /* Refill the packet buffer pool */ + number_to_free = + cvmx_fau_fetch_and_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, 0); + + if (number_to_free > fill_threshold) { + cvmx_fau_atomic_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, + -number_to_free); + num_freed = cvm_oct_mem_fill_fpa(CVMX_FPA_PACKET_POOL, + CVMX_FPA_PACKET_POOL_SIZE, + number_to_free); + if (num_freed != number_to_free) { + cvmx_fau_atomic_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, + number_to_free - num_freed); + } + } +} diff --git a/drivers/staging/octeon/ethernet-sgmii.c b/drivers/staging/octeon/ethernet-sgmii.c new file mode 100644 index 00000000..d3e82430 --- /dev/null +++ b/drivers/staging/octeon/ethernet-sgmii.c @@ -0,0 +1,127 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ +#include +#include +#include +#include + +#include + +#include "ethernet-defines.h" +#include "octeon-ethernet.h" +#include "ethernet-util.h" + +#include + +#include + +int cvm_oct_sgmii_open(struct net_device *dev) +{ + union cvmx_gmxx_prtx_cfg gmx_cfg; + struct octeon_ethernet *priv = netdev_priv(dev); + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + cvmx_helper_link_info_t link_info; + + gmx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); + gmx_cfg.s.en = 1; + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmx_cfg.u64); + + if (!octeon_is_simulation()) { + link_info = cvmx_helper_link_get(priv->port); + if (!link_info.s.link_up) + netif_carrier_off(dev); + } + + return 0; +} + +int cvm_oct_sgmii_stop(struct net_device *dev) +{ + union cvmx_gmxx_prtx_cfg gmx_cfg; + struct octeon_ethernet *priv = netdev_priv(dev); + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + + gmx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); + gmx_cfg.s.en = 0; + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmx_cfg.u64); + return 0; +} + +static void cvm_oct_sgmii_poll(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + cvmx_helper_link_info_t link_info; + + link_info = cvmx_helper_link_get(priv->port); + if (link_info.u64 == priv->link_info) + return; + + link_info = cvmx_helper_link_autoconf(priv->port); + priv->link_info = link_info.u64; + + /* Tell Linux */ + if (link_info.s.link_up) { + + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + if (priv->queue != -1) + printk_ratelimited + ("%s: %u Mbps %s duplex, port %2d, queue %2d\n", + dev->name, link_info.s.speed, + (link_info.s.full_duplex) ? "Full" : "Half", + priv->port, priv->queue); + else + printk_ratelimited + ("%s: %u Mbps %s duplex, port %2d, POW\n", + dev->name, link_info.s.speed, + (link_info.s.full_duplex) ? "Full" : "Half", + priv->port); + } else { + if (netif_carrier_ok(dev)) + netif_carrier_off(dev); + printk_ratelimited("%s: Link down\n", dev->name); + } +} + +int cvm_oct_sgmii_init(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + cvm_oct_common_init(dev); + dev->netdev_ops->ndo_stop(dev); + if (!octeon_is_simulation() && priv->phydev == NULL) + priv->poll = cvm_oct_sgmii_poll; + + /* FIXME: Need autoneg logic */ + return 0; +} + +void cvm_oct_sgmii_uninit(struct net_device *dev) +{ + cvm_oct_common_uninit(dev); +} diff --git a/drivers/staging/octeon/ethernet-spi.c b/drivers/staging/octeon/ethernet-spi.c new file mode 100644 index 00000000..af8d6281 --- /dev/null +++ b/drivers/staging/octeon/ethernet-spi.c @@ -0,0 +1,324 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ +#include +#include +#include +#include + +#include + +#include "ethernet-defines.h" +#include "octeon-ethernet.h" +#include "ethernet-util.h" + +#include + +#include +#include +#include + +static int number_spi_ports; +static int need_retrain[2] = { 0, 0 }; + +static irqreturn_t cvm_oct_spi_rml_interrupt(int cpl, void *dev_id) +{ + irqreturn_t return_status = IRQ_NONE; + union cvmx_npi_rsl_int_blocks rsl_int_blocks; + + /* Check and see if this interrupt was caused by the GMX block */ + rsl_int_blocks.u64 = cvmx_read_csr(CVMX_NPI_RSL_INT_BLOCKS); + if (rsl_int_blocks.s.spx1) { /* 19 - SPX1_INT_REG & STX1_INT_REG */ + + union cvmx_spxx_int_reg spx_int_reg; + union cvmx_stxx_int_reg stx_int_reg; + + spx_int_reg.u64 = cvmx_read_csr(CVMX_SPXX_INT_REG(1)); + cvmx_write_csr(CVMX_SPXX_INT_REG(1), spx_int_reg.u64); + if (!need_retrain[1]) { + + spx_int_reg.u64 &= cvmx_read_csr(CVMX_SPXX_INT_MSK(1)); + if (spx_int_reg.s.spf) + pr_err("SPI1: SRX Spi4 interface down\n"); + if (spx_int_reg.s.calerr) + pr_err("SPI1: SRX Spi4 Calendar table " + "parity error\n"); + if (spx_int_reg.s.syncerr) + pr_err("SPI1: SRX Consecutive Spi4 DIP4 " + "errors have exceeded " + "SPX_ERR_CTL[ERRCNT]\n"); + if (spx_int_reg.s.diperr) + pr_err("SPI1: SRX Spi4 DIP4 error\n"); + if (spx_int_reg.s.tpaovr) + pr_err("SPI1: SRX Selected port has hit " + "TPA overflow\n"); + if (spx_int_reg.s.rsverr) + pr_err("SPI1: SRX Spi4 reserved control " + "word detected\n"); + if (spx_int_reg.s.drwnng) + pr_err("SPI1: SRX Spi4 receive FIFO " + "drowning/overflow\n"); + if (spx_int_reg.s.clserr) + pr_err("SPI1: SRX Spi4 packet closed on " + "non-16B alignment without EOP\n"); + if (spx_int_reg.s.spiovr) + pr_err("SPI1: SRX Spi4 async FIFO overflow\n"); + if (spx_int_reg.s.abnorm) + pr_err("SPI1: SRX Abnormal packet " + "termination (ERR bit)\n"); + if (spx_int_reg.s.prtnxa) + pr_err("SPI1: SRX Port out of range\n"); + } + + stx_int_reg.u64 = cvmx_read_csr(CVMX_STXX_INT_REG(1)); + cvmx_write_csr(CVMX_STXX_INT_REG(1), stx_int_reg.u64); + if (!need_retrain[1]) { + + stx_int_reg.u64 &= cvmx_read_csr(CVMX_STXX_INT_MSK(1)); + if (stx_int_reg.s.syncerr) + pr_err("SPI1: STX Interface encountered a " + "fatal error\n"); + if (stx_int_reg.s.frmerr) + pr_err("SPI1: STX FRMCNT has exceeded " + "STX_DIP_CNT[MAXFRM]\n"); + if (stx_int_reg.s.unxfrm) + pr_err("SPI1: STX Unexpected framing " + "sequence\n"); + if (stx_int_reg.s.nosync) + pr_err("SPI1: STX ERRCNT has exceeded " + "STX_DIP_CNT[MAXDIP]\n"); + if (stx_int_reg.s.diperr) + pr_err("SPI1: STX DIP2 error on the Spi4 " + "Status channel\n"); + if (stx_int_reg.s.datovr) + pr_err("SPI1: STX Spi4 FIFO overflow error\n"); + if (stx_int_reg.s.ovrbst) + pr_err("SPI1: STX Transmit packet burst " + "too big\n"); + if (stx_int_reg.s.calpar1) + pr_err("SPI1: STX Calendar Table Parity " + "Error Bank1\n"); + if (stx_int_reg.s.calpar0) + pr_err("SPI1: STX Calendar Table Parity " + "Error Bank0\n"); + } + + cvmx_write_csr(CVMX_SPXX_INT_MSK(1), 0); + cvmx_write_csr(CVMX_STXX_INT_MSK(1), 0); + need_retrain[1] = 1; + return_status = IRQ_HANDLED; + } + + if (rsl_int_blocks.s.spx0) { /* 18 - SPX0_INT_REG & STX0_INT_REG */ + union cvmx_spxx_int_reg spx_int_reg; + union cvmx_stxx_int_reg stx_int_reg; + + spx_int_reg.u64 = cvmx_read_csr(CVMX_SPXX_INT_REG(0)); + cvmx_write_csr(CVMX_SPXX_INT_REG(0), spx_int_reg.u64); + if (!need_retrain[0]) { + + spx_int_reg.u64 &= cvmx_read_csr(CVMX_SPXX_INT_MSK(0)); + if (spx_int_reg.s.spf) + pr_err("SPI0: SRX Spi4 interface down\n"); + if (spx_int_reg.s.calerr) + pr_err("SPI0: SRX Spi4 Calendar table " + "parity error\n"); + if (spx_int_reg.s.syncerr) + pr_err("SPI0: SRX Consecutive Spi4 DIP4 " + "errors have exceeded " + "SPX_ERR_CTL[ERRCNT]\n"); + if (spx_int_reg.s.diperr) + pr_err("SPI0: SRX Spi4 DIP4 error\n"); + if (spx_int_reg.s.tpaovr) + pr_err("SPI0: SRX Selected port has hit " + "TPA overflow\n"); + if (spx_int_reg.s.rsverr) + pr_err("SPI0: SRX Spi4 reserved control " + "word detected\n"); + if (spx_int_reg.s.drwnng) + pr_err("SPI0: SRX Spi4 receive FIFO " + "drowning/overflow\n"); + if (spx_int_reg.s.clserr) + pr_err("SPI0: SRX Spi4 packet closed on " + "non-16B alignment without EOP\n"); + if (spx_int_reg.s.spiovr) + pr_err("SPI0: SRX Spi4 async FIFO overflow\n"); + if (spx_int_reg.s.abnorm) + pr_err("SPI0: SRX Abnormal packet " + "termination (ERR bit)\n"); + if (spx_int_reg.s.prtnxa) + pr_err("SPI0: SRX Port out of range\n"); + } + + stx_int_reg.u64 = cvmx_read_csr(CVMX_STXX_INT_REG(0)); + cvmx_write_csr(CVMX_STXX_INT_REG(0), stx_int_reg.u64); + if (!need_retrain[0]) { + + stx_int_reg.u64 &= cvmx_read_csr(CVMX_STXX_INT_MSK(0)); + if (stx_int_reg.s.syncerr) + pr_err("SPI0: STX Interface encountered a " + "fatal error\n"); + if (stx_int_reg.s.frmerr) + pr_err("SPI0: STX FRMCNT has exceeded " + "STX_DIP_CNT[MAXFRM]\n"); + if (stx_int_reg.s.unxfrm) + pr_err("SPI0: STX Unexpected framing " + "sequence\n"); + if (stx_int_reg.s.nosync) + pr_err("SPI0: STX ERRCNT has exceeded " + "STX_DIP_CNT[MAXDIP]\n"); + if (stx_int_reg.s.diperr) + pr_err("SPI0: STX DIP2 error on the Spi4 " + "Status channel\n"); + if (stx_int_reg.s.datovr) + pr_err("SPI0: STX Spi4 FIFO overflow error\n"); + if (stx_int_reg.s.ovrbst) + pr_err("SPI0: STX Transmit packet burst " + "too big\n"); + if (stx_int_reg.s.calpar1) + pr_err("SPI0: STX Calendar Table Parity " + "Error Bank1\n"); + if (stx_int_reg.s.calpar0) + pr_err("SPI0: STX Calendar Table Parity " + "Error Bank0\n"); + } + + cvmx_write_csr(CVMX_SPXX_INT_MSK(0), 0); + cvmx_write_csr(CVMX_STXX_INT_MSK(0), 0); + need_retrain[0] = 1; + return_status = IRQ_HANDLED; + } + + return return_status; +} + +static void cvm_oct_spi_enable_error_reporting(int interface) +{ + union cvmx_spxx_int_msk spxx_int_msk; + union cvmx_stxx_int_msk stxx_int_msk; + + spxx_int_msk.u64 = cvmx_read_csr(CVMX_SPXX_INT_MSK(interface)); + spxx_int_msk.s.calerr = 1; + spxx_int_msk.s.syncerr = 1; + spxx_int_msk.s.diperr = 1; + spxx_int_msk.s.tpaovr = 1; + spxx_int_msk.s.rsverr = 1; + spxx_int_msk.s.drwnng = 1; + spxx_int_msk.s.clserr = 1; + spxx_int_msk.s.spiovr = 1; + spxx_int_msk.s.abnorm = 1; + spxx_int_msk.s.prtnxa = 1; + cvmx_write_csr(CVMX_SPXX_INT_MSK(interface), spxx_int_msk.u64); + + stxx_int_msk.u64 = cvmx_read_csr(CVMX_STXX_INT_MSK(interface)); + stxx_int_msk.s.frmerr = 1; + stxx_int_msk.s.unxfrm = 1; + stxx_int_msk.s.nosync = 1; + stxx_int_msk.s.diperr = 1; + stxx_int_msk.s.datovr = 1; + stxx_int_msk.s.ovrbst = 1; + stxx_int_msk.s.calpar1 = 1; + stxx_int_msk.s.calpar0 = 1; + cvmx_write_csr(CVMX_STXX_INT_MSK(interface), stxx_int_msk.u64); +} + +static void cvm_oct_spi_poll(struct net_device *dev) +{ + static int spi4000_port; + struct octeon_ethernet *priv = netdev_priv(dev); + int interface; + + for (interface = 0; interface < 2; interface++) { + + if ((priv->port == interface * 16) && need_retrain[interface]) { + + if (cvmx_spi_restart_interface + (interface, CVMX_SPI_MODE_DUPLEX, 10) == 0) { + need_retrain[interface] = 0; + cvm_oct_spi_enable_error_reporting(interface); + } + } + + /* + * The SPI4000 TWSI interface is very slow. In order + * not to bring the system to a crawl, we only poll a + * single port every second. This means negotiation + * speed changes take up to 10 seconds, but at least + * we don't waste absurd amounts of time waiting for + * TWSI. + */ + if (priv->port == spi4000_port) { + /* + * This function does nothing if it is called on an + * interface without a SPI4000. + */ + cvmx_spi4000_check_speed(interface, priv->port); + /* + * Normal ordering increments. By decrementing + * we only match once per iteration. + */ + spi4000_port--; + if (spi4000_port < 0) + spi4000_port = 10; + } + } +} + +int cvm_oct_spi_init(struct net_device *dev) +{ + int r; + struct octeon_ethernet *priv = netdev_priv(dev); + + if (number_spi_ports == 0) { + r = request_irq(OCTEON_IRQ_RML, cvm_oct_spi_rml_interrupt, + IRQF_SHARED, "SPI", &number_spi_ports); + if (r) + return r; + } + number_spi_ports++; + + if ((priv->port == 0) || (priv->port == 16)) { + cvm_oct_spi_enable_error_reporting(INTERFACE(priv->port)); + priv->poll = cvm_oct_spi_poll; + } + cvm_oct_common_init(dev); + return 0; +} + +void cvm_oct_spi_uninit(struct net_device *dev) +{ + int interface; + + cvm_oct_common_uninit(dev); + number_spi_ports--; + if (number_spi_ports == 0) { + for (interface = 0; interface < 2; interface++) { + cvmx_write_csr(CVMX_SPXX_INT_MSK(interface), 0); + cvmx_write_csr(CVMX_STXX_INT_MSK(interface), 0); + } + free_irq(OCTEON_IRQ_RML, &number_spi_ports); + } +} diff --git a/drivers/staging/octeon/ethernet-tx.c b/drivers/staging/octeon/ethernet-tx.c new file mode 100644 index 00000000..91a97b3e --- /dev/null +++ b/drivers/staging/octeon/ethernet-tx.c @@ -0,0 +1,725 @@ +/********************************************************************* + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2010 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +*********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_XFRM +#include +#include +#endif /* CONFIG_XFRM */ + +#include + +#include + +#include "ethernet-defines.h" +#include "octeon-ethernet.h" +#include "ethernet-tx.h" +#include "ethernet-util.h" + +#include +#include +#include +#include +#include + +#include + +#define CVM_OCT_SKB_CB(skb) ((u64 *)((skb)->cb)) + +/* + * You can define GET_SKBUFF_QOS() to override how the skbuff output + * function determines which output queue is used. The default + * implementation always uses the base queue for the port. If, for + * example, you wanted to use the skb->priority fieid, define + * GET_SKBUFF_QOS as: #define GET_SKBUFF_QOS(skb) ((skb)->priority) + */ +#ifndef GET_SKBUFF_QOS +#define GET_SKBUFF_QOS(skb) 0 +#endif + +static void cvm_oct_tx_do_cleanup(unsigned long arg); +static DECLARE_TASKLET(cvm_oct_tx_cleanup_tasklet, cvm_oct_tx_do_cleanup, 0); + +/* Maximum number of SKBs to try to free per xmit packet. */ +#define MAX_SKB_TO_FREE (MAX_OUT_QUEUE_DEPTH * 2) + +static inline int32_t cvm_oct_adjust_skb_to_free(int32_t skb_to_free, int fau) +{ + int32_t undo; + undo = skb_to_free > 0 ? MAX_SKB_TO_FREE : skb_to_free + MAX_SKB_TO_FREE; + if (undo > 0) + cvmx_fau_atomic_add32(fau, -undo); + skb_to_free = -skb_to_free > MAX_SKB_TO_FREE ? MAX_SKB_TO_FREE : -skb_to_free; + return skb_to_free; +} + +static void cvm_oct_kick_tx_poll_watchdog(void) +{ + union cvmx_ciu_timx ciu_timx; + ciu_timx.u64 = 0; + ciu_timx.s.one_shot = 1; + ciu_timx.s.len = cvm_oct_tx_poll_interval; + cvmx_write_csr(CVMX_CIU_TIMX(1), ciu_timx.u64); +} + +void cvm_oct_free_tx_skbs(struct net_device *dev) +{ + int32_t skb_to_free; + int qos, queues_per_port; + int total_freed = 0; + int total_remaining = 0; + unsigned long flags; + struct octeon_ethernet *priv = netdev_priv(dev); + + queues_per_port = cvmx_pko_get_num_queues(priv->port); + /* Drain any pending packets in the free list */ + for (qos = 0; qos < queues_per_port; qos++) { + if (skb_queue_len(&priv->tx_free_list[qos]) == 0) + continue; + skb_to_free = cvmx_fau_fetch_and_add32(priv->fau+qos*4, MAX_SKB_TO_FREE); + skb_to_free = cvm_oct_adjust_skb_to_free(skb_to_free, priv->fau+qos*4); + + + total_freed += skb_to_free; + if (skb_to_free > 0) { + struct sk_buff *to_free_list = NULL; + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); + while (skb_to_free > 0) { + struct sk_buff *t = __skb_dequeue(&priv->tx_free_list[qos]); + t->next = to_free_list; + to_free_list = t; + skb_to_free--; + } + spin_unlock_irqrestore(&priv->tx_free_list[qos].lock, flags); + /* Do the actual freeing outside of the lock. */ + while (to_free_list) { + struct sk_buff *t = to_free_list; + to_free_list = to_free_list->next; + dev_kfree_skb_any(t); + } + } + total_remaining += skb_queue_len(&priv->tx_free_list[qos]); + } + if (total_freed >= 0 && netif_queue_stopped(dev)) + netif_wake_queue(dev); + if (total_remaining) + cvm_oct_kick_tx_poll_watchdog(); +} + +/** + * cvm_oct_xmit - transmit a packet + * @skb: Packet to send + * @dev: Device info structure + * + * Returns Always returns NETDEV_TX_OK + */ +int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) +{ + cvmx_pko_command_word0_t pko_command; + union cvmx_buf_ptr hw_buffer; + uint64_t old_scratch; + uint64_t old_scratch2; + int qos; + int i; + enum {QUEUE_CORE, QUEUE_HW, QUEUE_DROP} queue_type; + struct octeon_ethernet *priv = netdev_priv(dev); + struct sk_buff *to_free_list; + int32_t skb_to_free; + int32_t buffers_to_free; + u32 total_to_clean; + unsigned long flags; +#if REUSE_SKBUFFS_WITHOUT_FREE + unsigned char *fpa_head; +#endif + + /* + * Prefetch the private data structure. It is larger that one + * cache line. + */ + prefetch(priv); + + /* + * The check on CVMX_PKO_QUEUES_PER_PORT_* is designed to + * completely remove "qos" in the event neither interface + * supports multiple queues per port. + */ + if ((CVMX_PKO_QUEUES_PER_PORT_INTERFACE0 > 1) || + (CVMX_PKO_QUEUES_PER_PORT_INTERFACE1 > 1)) { + qos = GET_SKBUFF_QOS(skb); + if (qos <= 0) + qos = 0; + else if (qos >= cvmx_pko_get_num_queues(priv->port)) + qos = 0; + } else + qos = 0; + + if (USE_ASYNC_IOBDMA) { + /* Save scratch in case userspace is using it */ + CVMX_SYNCIOBDMA; + old_scratch = cvmx_scratch_read64(CVMX_SCR_SCRATCH); + old_scratch2 = cvmx_scratch_read64(CVMX_SCR_SCRATCH + 8); + + /* + * Fetch and increment the number of packets to be + * freed. + */ + cvmx_fau_async_fetch_and_add32(CVMX_SCR_SCRATCH + 8, + FAU_NUM_PACKET_BUFFERS_TO_FREE, + 0); + cvmx_fau_async_fetch_and_add32(CVMX_SCR_SCRATCH, + priv->fau + qos * 4, + MAX_SKB_TO_FREE); + } + + /* + * We have space for 6 segment pointers, If there will be more + * than that, we must linearize. + */ + if (unlikely(skb_shinfo(skb)->nr_frags > 5)) { + if (unlikely(__skb_linearize(skb))) { + queue_type = QUEUE_DROP; + if (USE_ASYNC_IOBDMA) { + /* Get the number of skbuffs in use by the hardware */ + CVMX_SYNCIOBDMA; + skb_to_free = cvmx_scratch_read64(CVMX_SCR_SCRATCH); + } else { + /* Get the number of skbuffs in use by the hardware */ + skb_to_free = cvmx_fau_fetch_and_add32(priv->fau + qos * 4, + MAX_SKB_TO_FREE); + } + skb_to_free = cvm_oct_adjust_skb_to_free(skb_to_free, priv->fau + qos * 4); + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); + goto skip_xmit; + } + } + + /* + * The CN3XXX series of parts has an errata (GMX-401) which + * causes the GMX block to hang if a collision occurs towards + * the end of a <68 byte packet. As a workaround for this, we + * pad packets to be 68 bytes whenever we are in half duplex + * mode. We don't handle the case of having a small packet but + * no room to add the padding. The kernel should always give + * us at least a cache line + */ + if ((skb->len < 64) && OCTEON_IS_MODEL(OCTEON_CN3XXX)) { + union cvmx_gmxx_prtx_cfg gmx_prt_cfg; + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + + if (interface < 2) { + /* We only need to pad packet in half duplex mode */ + gmx_prt_cfg.u64 = + cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); + if (gmx_prt_cfg.s.duplex == 0) { + int add_bytes = 64 - skb->len; + if ((skb_tail_pointer(skb) + add_bytes) <= + skb_end_pointer(skb)) + memset(__skb_put(skb, add_bytes), 0, + add_bytes); + } + } + } + + /* Build the PKO command */ + pko_command.u64 = 0; + pko_command.s.n2 = 1; /* Don't pollute L2 with the outgoing packet */ + pko_command.s.segs = 1; + pko_command.s.total_bytes = skb->len; + pko_command.s.size0 = CVMX_FAU_OP_SIZE_32; + pko_command.s.subone0 = 1; + + pko_command.s.dontfree = 1; + + /* Build the PKO buffer pointer */ + hw_buffer.u64 = 0; + if (skb_shinfo(skb)->nr_frags == 0) { + hw_buffer.s.addr = XKPHYS_TO_PHYS((u64)skb->data); + hw_buffer.s.pool = 0; + hw_buffer.s.size = skb->len; + } else { + hw_buffer.s.addr = XKPHYS_TO_PHYS((u64)skb->data); + hw_buffer.s.pool = 0; + hw_buffer.s.size = skb_headlen(skb); + CVM_OCT_SKB_CB(skb)[0] = hw_buffer.u64; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + struct skb_frag_struct *fs = skb_shinfo(skb)->frags + i; + hw_buffer.s.addr = XKPHYS_TO_PHYS((u64)(page_address(fs->page.p) + fs->page_offset)); + hw_buffer.s.size = fs->size; + CVM_OCT_SKB_CB(skb)[i + 1] = hw_buffer.u64; + } + hw_buffer.s.addr = XKPHYS_TO_PHYS((u64)CVM_OCT_SKB_CB(skb)); + hw_buffer.s.size = skb_shinfo(skb)->nr_frags + 1; + pko_command.s.segs = skb_shinfo(skb)->nr_frags + 1; + pko_command.s.gather = 1; + goto dont_put_skbuff_in_hw; + } + + /* + * See if we can put this skb in the FPA pool. Any strange + * behavior from the Linux networking stack will most likely + * be caused by a bug in the following code. If some field is + * in use by the network stack and get carried over when a + * buffer is reused, bad thing may happen. If in doubt and + * you dont need the absolute best performance, disable the + * define REUSE_SKBUFFS_WITHOUT_FREE. The reuse of buffers has + * shown a 25% increase in performance under some loads. + */ +#if REUSE_SKBUFFS_WITHOUT_FREE + fpa_head = skb->head + 256 - ((unsigned long)skb->head & 0x7f); + if (unlikely(skb->data < fpa_head)) { + /* + * printk("TX buffer beginning can't meet FPA + * alignment constraints\n"); + */ + goto dont_put_skbuff_in_hw; + } + if (unlikely + ((skb_end_pointer(skb) - fpa_head) < CVMX_FPA_PACKET_POOL_SIZE)) { + /* + printk("TX buffer isn't large enough for the FPA\n"); + */ + goto dont_put_skbuff_in_hw; + } + if (unlikely(skb_shared(skb))) { + /* + printk("TX buffer sharing data with someone else\n"); + */ + goto dont_put_skbuff_in_hw; + } + if (unlikely(skb_cloned(skb))) { + /* + printk("TX buffer has been cloned\n"); + */ + goto dont_put_skbuff_in_hw; + } + if (unlikely(skb_header_cloned(skb))) { + /* + printk("TX buffer header has been cloned\n"); + */ + goto dont_put_skbuff_in_hw; + } + if (unlikely(skb->destructor)) { + /* + printk("TX buffer has a destructor\n"); + */ + goto dont_put_skbuff_in_hw; + } + if (unlikely(skb_shinfo(skb)->nr_frags)) { + /* + printk("TX buffer has fragments\n"); + */ + goto dont_put_skbuff_in_hw; + } + if (unlikely + (skb->truesize != + sizeof(*skb) + skb_end_pointer(skb) - skb->head)) { + /* + printk("TX buffer truesize has been changed\n"); + */ + goto dont_put_skbuff_in_hw; + } + + /* + * We can use this buffer in the FPA. We don't need the FAU + * update anymore + */ + pko_command.s.dontfree = 0; + + hw_buffer.s.back = ((unsigned long)skb->data >> 7) - ((unsigned long)fpa_head >> 7); + *(struct sk_buff **)(fpa_head - sizeof(void *)) = skb; + + /* + * The skbuff will be reused without ever being freed. We must + * cleanup a bunch of core things. + */ + dst_release(skb_dst(skb)); + skb_dst_set(skb, NULL); +#ifdef CONFIG_XFRM + secpath_put(skb->sp); + skb->sp = NULL; +#endif + nf_reset(skb); + +#ifdef CONFIG_NET_SCHED + skb->tc_index = 0; +#ifdef CONFIG_NET_CLS_ACT + skb->tc_verd = 0; +#endif /* CONFIG_NET_CLS_ACT */ +#endif /* CONFIG_NET_SCHED */ +#endif /* REUSE_SKBUFFS_WITHOUT_FREE */ + +dont_put_skbuff_in_hw: + + /* Check if we can use the hardware checksumming */ + if (USE_HW_TCPUDP_CHECKSUM && (skb->protocol == htons(ETH_P_IP)) && + (ip_hdr(skb)->version == 4) && (ip_hdr(skb)->ihl == 5) && + ((ip_hdr(skb)->frag_off == 0) || (ip_hdr(skb)->frag_off == 1 << 14)) + && ((ip_hdr(skb)->protocol == IPPROTO_TCP) + || (ip_hdr(skb)->protocol == IPPROTO_UDP))) { + /* Use hardware checksum calc */ + pko_command.s.ipoffp1 = sizeof(struct ethhdr) + 1; + } + + if (USE_ASYNC_IOBDMA) { + /* Get the number of skbuffs in use by the hardware */ + CVMX_SYNCIOBDMA; + skb_to_free = cvmx_scratch_read64(CVMX_SCR_SCRATCH); + buffers_to_free = cvmx_scratch_read64(CVMX_SCR_SCRATCH + 8); + } else { + /* Get the number of skbuffs in use by the hardware */ + skb_to_free = cvmx_fau_fetch_and_add32(priv->fau + qos * 4, + MAX_SKB_TO_FREE); + buffers_to_free = + cvmx_fau_fetch_and_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, 0); + } + + skb_to_free = cvm_oct_adjust_skb_to_free(skb_to_free, priv->fau+qos*4); + + /* + * If we're sending faster than the receive can free them then + * don't do the HW free. + */ + if ((buffers_to_free < -100) && !pko_command.s.dontfree) + pko_command.s.dontfree = 1; + + if (pko_command.s.dontfree) { + queue_type = QUEUE_CORE; + pko_command.s.reg0 = priv->fau+qos*4; + } else { + queue_type = QUEUE_HW; + } + if (USE_ASYNC_IOBDMA) + cvmx_fau_async_fetch_and_add32(CVMX_SCR_SCRATCH, FAU_TOTAL_TX_TO_CLEAN, 1); + + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); + + /* Drop this packet if we have too many already queued to the HW */ + if (unlikely(skb_queue_len(&priv->tx_free_list[qos]) >= MAX_OUT_QUEUE_DEPTH)) { + if (dev->tx_queue_len != 0) { + /* Drop the lock when notifying the core. */ + spin_unlock_irqrestore(&priv->tx_free_list[qos].lock, flags); + netif_stop_queue(dev); + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); + } else { + /* If not using normal queueing. */ + queue_type = QUEUE_DROP; + goto skip_xmit; + } + } + + cvmx_pko_send_packet_prepare(priv->port, priv->queue + qos, + CVMX_PKO_LOCK_NONE); + + /* Send the packet to the output queue */ + if (unlikely(cvmx_pko_send_packet_finish(priv->port, + priv->queue + qos, + pko_command, hw_buffer, + CVMX_PKO_LOCK_NONE))) { + printk_ratelimited("%s: Failed to send the packet\n", dev->name); + queue_type = QUEUE_DROP; + } +skip_xmit: + to_free_list = NULL; + + switch (queue_type) { + case QUEUE_DROP: + skb->next = to_free_list; + to_free_list = skb; + priv->stats.tx_dropped++; + break; + case QUEUE_HW: + cvmx_fau_atomic_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, -1); + break; + case QUEUE_CORE: + __skb_queue_tail(&priv->tx_free_list[qos], skb); + break; + default: + BUG(); + } + + while (skb_to_free > 0) { + struct sk_buff *t = __skb_dequeue(&priv->tx_free_list[qos]); + t->next = to_free_list; + to_free_list = t; + skb_to_free--; + } + + spin_unlock_irqrestore(&priv->tx_free_list[qos].lock, flags); + + /* Do the actual freeing outside of the lock. */ + while (to_free_list) { + struct sk_buff *t = to_free_list; + to_free_list = to_free_list->next; + dev_kfree_skb_any(t); + } + + if (USE_ASYNC_IOBDMA) { + CVMX_SYNCIOBDMA; + total_to_clean = cvmx_scratch_read64(CVMX_SCR_SCRATCH); + /* Restore the scratch area */ + cvmx_scratch_write64(CVMX_SCR_SCRATCH, old_scratch); + cvmx_scratch_write64(CVMX_SCR_SCRATCH + 8, old_scratch2); + } else { + total_to_clean = cvmx_fau_fetch_and_add32(FAU_TOTAL_TX_TO_CLEAN, 1); + } + + if (total_to_clean & 0x3ff) { + /* + * Schedule the cleanup tasklet every 1024 packets for + * the pathological case of high traffic on one port + * delaying clean up of packets on a different port + * that is blocked waiting for the cleanup. + */ + tasklet_schedule(&cvm_oct_tx_cleanup_tasklet); + } + + cvm_oct_kick_tx_poll_watchdog(); + + return NETDEV_TX_OK; +} + +/** + * cvm_oct_xmit_pow - transmit a packet to the POW + * @skb: Packet to send + * @dev: Device info structure + + * Returns Always returns zero + */ +int cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + void *packet_buffer; + void *copy_location; + + /* Get a work queue entry */ + cvmx_wqe_t *work = cvmx_fpa_alloc(CVMX_FPA_WQE_POOL); + if (unlikely(work == NULL)) { + printk_ratelimited("%s: Failed to allocate a work " + "queue entry\n", dev->name); + priv->stats.tx_dropped++; + dev_kfree_skb(skb); + return 0; + } + + /* Get a packet buffer */ + packet_buffer = cvmx_fpa_alloc(CVMX_FPA_PACKET_POOL); + if (unlikely(packet_buffer == NULL)) { + printk_ratelimited("%s: Failed to allocate a packet buffer\n", + dev->name); + cvmx_fpa_free(work, CVMX_FPA_WQE_POOL, DONT_WRITEBACK(1)); + priv->stats.tx_dropped++; + dev_kfree_skb(skb); + return 0; + } + + /* + * Calculate where we need to copy the data to. We need to + * leave 8 bytes for a next pointer (unused). We also need to + * include any configure skip. Then we need to align the IP + * packet src and dest into the same 64bit word. The below + * calculation may add a little extra, but that doesn't + * hurt. + */ + copy_location = packet_buffer + sizeof(uint64_t); + copy_location += ((CVMX_HELPER_FIRST_MBUFF_SKIP + 7) & 0xfff8) + 6; + + /* + * We have to copy the packet since whoever processes this + * packet will free it to a hardware pool. We can't use the + * trick of counting outstanding packets like in + * cvm_oct_xmit. + */ + memcpy(copy_location, skb->data, skb->len); + + /* + * Fill in some of the work queue fields. We may need to add + * more if the software at the other end needs them. + */ + work->hw_chksum = skb->csum; + work->len = skb->len; + work->ipprt = priv->port; + work->qos = priv->port & 0x7; + work->grp = pow_send_group; + work->tag_type = CVMX_HELPER_INPUT_TAG_TYPE; + work->tag = pow_send_group; /* FIXME */ + /* Default to zero. Sets of zero later are commented out */ + work->word2.u64 = 0; + work->word2.s.bufs = 1; + work->packet_ptr.u64 = 0; + work->packet_ptr.s.addr = cvmx_ptr_to_phys(copy_location); + work->packet_ptr.s.pool = CVMX_FPA_PACKET_POOL; + work->packet_ptr.s.size = CVMX_FPA_PACKET_POOL_SIZE; + work->packet_ptr.s.back = (copy_location - packet_buffer) >> 7; + + if (skb->protocol == htons(ETH_P_IP)) { + work->word2.s.ip_offset = 14; +#if 0 + work->word2.s.vlan_valid = 0; /* FIXME */ + work->word2.s.vlan_cfi = 0; /* FIXME */ + work->word2.s.vlan_id = 0; /* FIXME */ + work->word2.s.dec_ipcomp = 0; /* FIXME */ +#endif + work->word2.s.tcp_or_udp = + (ip_hdr(skb)->protocol == IPPROTO_TCP) + || (ip_hdr(skb)->protocol == IPPROTO_UDP); +#if 0 + /* FIXME */ + work->word2.s.dec_ipsec = 0; + /* We only support IPv4 right now */ + work->word2.s.is_v6 = 0; + /* Hardware would set to zero */ + work->word2.s.software = 0; + /* No error, packet is internal */ + work->word2.s.L4_error = 0; +#endif + work->word2.s.is_frag = !((ip_hdr(skb)->frag_off == 0) + || (ip_hdr(skb)->frag_off == + 1 << 14)); +#if 0 + /* Assume Linux is sending a good packet */ + work->word2.s.IP_exc = 0; +#endif + work->word2.s.is_bcast = (skb->pkt_type == PACKET_BROADCAST); + work->word2.s.is_mcast = (skb->pkt_type == PACKET_MULTICAST); +#if 0 + /* This is an IP packet */ + work->word2.s.not_IP = 0; + /* No error, packet is internal */ + work->word2.s.rcv_error = 0; + /* No error, packet is internal */ + work->word2.s.err_code = 0; +#endif + + /* + * When copying the data, include 4 bytes of the + * ethernet header to align the same way hardware + * does. + */ + memcpy(work->packet_data, skb->data + 10, + sizeof(work->packet_data)); + } else { +#if 0 + work->word2.snoip.vlan_valid = 0; /* FIXME */ + work->word2.snoip.vlan_cfi = 0; /* FIXME */ + work->word2.snoip.vlan_id = 0; /* FIXME */ + work->word2.snoip.software = 0; /* Hardware would set to zero */ +#endif + work->word2.snoip.is_rarp = skb->protocol == htons(ETH_P_RARP); + work->word2.snoip.is_arp = skb->protocol == htons(ETH_P_ARP); + work->word2.snoip.is_bcast = + (skb->pkt_type == PACKET_BROADCAST); + work->word2.snoip.is_mcast = + (skb->pkt_type == PACKET_MULTICAST); + work->word2.snoip.not_IP = 1; /* IP was done up above */ +#if 0 + /* No error, packet is internal */ + work->word2.snoip.rcv_error = 0; + /* No error, packet is internal */ + work->word2.snoip.err_code = 0; +#endif + memcpy(work->packet_data, skb->data, sizeof(work->packet_data)); + } + + /* Submit the packet to the POW */ + cvmx_pow_work_submit(work, work->tag, work->tag_type, work->qos, + work->grp); + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len; + dev_kfree_skb(skb); + return 0; +} + +/** + * cvm_oct_tx_shutdown_dev - free all skb that are currently queued for TX. + * @dev: Device being shutdown + * + */ +void cvm_oct_tx_shutdown_dev(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + unsigned long flags; + int qos; + + for (qos = 0; qos < 16; qos++) { + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); + while (skb_queue_len(&priv->tx_free_list[qos])) + dev_kfree_skb_any(__skb_dequeue + (&priv->tx_free_list[qos])); + spin_unlock_irqrestore(&priv->tx_free_list[qos].lock, flags); + } +} + +static void cvm_oct_tx_do_cleanup(unsigned long arg) +{ + int port; + + for (port = 0; port < TOTAL_NUMBER_OF_PORTS; port++) { + if (cvm_oct_device[port]) { + struct net_device *dev = cvm_oct_device[port]; + cvm_oct_free_tx_skbs(dev); + } + } +} + +static irqreturn_t cvm_oct_tx_cleanup_watchdog(int cpl, void *dev_id) +{ + /* Disable the interrupt. */ + cvmx_write_csr(CVMX_CIU_TIMX(1), 0); + /* Do the work in the tasklet. */ + tasklet_schedule(&cvm_oct_tx_cleanup_tasklet); + return IRQ_HANDLED; +} + +void cvm_oct_tx_initialize(void) +{ + int i; + + /* Disable the interrupt. */ + cvmx_write_csr(CVMX_CIU_TIMX(1), 0); + /* Register an IRQ hander for to receive CIU_TIMX(1) interrupts */ + i = request_irq(OCTEON_IRQ_TIMER1, + cvm_oct_tx_cleanup_watchdog, 0, + "Ethernet", cvm_oct_device); + + if (i) + panic("Could not acquire Ethernet IRQ %d\n", OCTEON_IRQ_TIMER1); +} + +void cvm_oct_tx_shutdown(void) +{ + /* Free the interrupt handler */ + free_irq(OCTEON_IRQ_TIMER1, cvm_oct_device); +} diff --git a/drivers/staging/octeon/ethernet-tx.h b/drivers/staging/octeon/ethernet-tx.h new file mode 100644 index 00000000..547680c6 --- /dev/null +++ b/drivers/staging/octeon/ethernet-tx.h @@ -0,0 +1,34 @@ +/********************************************************************* + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +*********************************************************************/ + +int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev); +int cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev); +int cvm_oct_transmit_qos(struct net_device *dev, void *work_queue_entry, + int do_free, int qos); +void cvm_oct_tx_initialize(void); +void cvm_oct_tx_shutdown(void); +void cvm_oct_tx_shutdown_dev(struct net_device *dev); diff --git a/drivers/staging/octeon/ethernet-util.h b/drivers/staging/octeon/ethernet-util.h new file mode 100644 index 00000000..144fb99b --- /dev/null +++ b/drivers/staging/octeon/ethernet-util.h @@ -0,0 +1,72 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +*********************************************************************/ + +/** + * cvm_oct_get_buffer_ptr - convert packet data address to pointer + * @packet_ptr: Packet data hardware address + * + * Returns Packet buffer pointer + */ +static inline void *cvm_oct_get_buffer_ptr(union cvmx_buf_ptr packet_ptr) +{ + return cvmx_phys_to_ptr(((packet_ptr.s.addr >> 7) - packet_ptr.s.back) + << 7); +} + +/** + * INTERFACE - convert IPD port to locgical interface + * @ipd_port: Port to check + * + * Returns Logical interface + */ +static inline int INTERFACE(int ipd_port) +{ + if (ipd_port < 32) /* Interface 0 or 1 for RGMII,GMII,SPI, etc */ + return ipd_port >> 4; + else if (ipd_port < 36) /* Interface 2 for NPI */ + return 2; + else if (ipd_port < 40) /* Interface 3 for loopback */ + return 3; + else if (ipd_port == 40) /* Non existent interface for POW0 */ + return 4; + else + panic("Illegal ipd_port %d passed to INTERFACE\n", ipd_port); +} + +/** + * INDEX - convert IPD/PKO port number to the port's interface index + * @ipd_port: Port to check + * + * Returns Index into interface port list + */ +static inline int INDEX(int ipd_port) +{ + if (ipd_port < 32) + return ipd_port & 15; + else + return ipd_port & 3; +} diff --git a/drivers/staging/octeon/ethernet-xaui.c b/drivers/staging/octeon/ethernet-xaui.c new file mode 100644 index 00000000..419f8c34 --- /dev/null +++ b/drivers/staging/octeon/ethernet-xaui.c @@ -0,0 +1,125 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ +#include +#include +#include +#include + +#include + +#include "ethernet-defines.h" +#include "octeon-ethernet.h" +#include "ethernet-util.h" + +#include + +#include + +int cvm_oct_xaui_open(struct net_device *dev) +{ + union cvmx_gmxx_prtx_cfg gmx_cfg; + struct octeon_ethernet *priv = netdev_priv(dev); + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + cvmx_helper_link_info_t link_info; + + gmx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); + gmx_cfg.s.en = 1; + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmx_cfg.u64); + + if (!octeon_is_simulation()) { + link_info = cvmx_helper_link_get(priv->port); + if (!link_info.s.link_up) + netif_carrier_off(dev); + } + return 0; +} + +int cvm_oct_xaui_stop(struct net_device *dev) +{ + union cvmx_gmxx_prtx_cfg gmx_cfg; + struct octeon_ethernet *priv = netdev_priv(dev); + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + + gmx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); + gmx_cfg.s.en = 0; + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmx_cfg.u64); + return 0; +} + +static void cvm_oct_xaui_poll(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + cvmx_helper_link_info_t link_info; + + link_info = cvmx_helper_link_get(priv->port); + if (link_info.u64 == priv->link_info) + return; + + link_info = cvmx_helper_link_autoconf(priv->port); + priv->link_info = link_info.u64; + + /* Tell Linux */ + if (link_info.s.link_up) { + + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + if (priv->queue != -1) + printk_ratelimited + ("%s: %u Mbps %s duplex, port %2d, queue %2d\n", + dev->name, link_info.s.speed, + (link_info.s.full_duplex) ? "Full" : "Half", + priv->port, priv->queue); + else + printk_ratelimited + ("%s: %u Mbps %s duplex, port %2d, POW\n", + dev->name, link_info.s.speed, + (link_info.s.full_duplex) ? "Full" : "Half", + priv->port); + } else { + if (netif_carrier_ok(dev)) + netif_carrier_off(dev); + printk_ratelimited("%s: Link down\n", dev->name); + } +} + +int cvm_oct_xaui_init(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + cvm_oct_common_init(dev); + dev->netdev_ops->ndo_stop(dev); + if (!octeon_is_simulation() && priv->phydev == NULL) + priv->poll = cvm_oct_xaui_poll; + + return 0; +} + +void cvm_oct_xaui_uninit(struct net_device *dev) +{ + cvm_oct_common_uninit(dev); +} diff --git a/drivers/staging/octeon/ethernet.c b/drivers/staging/octeon/ethernet.c new file mode 100644 index 00000000..60cba819 --- /dev/null +++ b/drivers/staging/octeon/ethernet.c @@ -0,0 +1,844 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2007 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "ethernet-defines.h" +#include "octeon-ethernet.h" +#include "ethernet-mem.h" +#include "ethernet-rx.h" +#include "ethernet-tx.h" +#include "ethernet-mdio.h" +#include "ethernet-util.h" + +#include +#include +#include +#include +#include + +#include +#include + +#if defined(CONFIG_CAVIUM_OCTEON_NUM_PACKET_BUFFERS) \ + && CONFIG_CAVIUM_OCTEON_NUM_PACKET_BUFFERS +int num_packet_buffers = CONFIG_CAVIUM_OCTEON_NUM_PACKET_BUFFERS; +#else +int num_packet_buffers = 1024; +#endif +module_param(num_packet_buffers, int, 0444); +MODULE_PARM_DESC(num_packet_buffers, "\n" + "\tNumber of packet buffers to allocate and store in the\n" + "\tFPA. By default, 1024 packet buffers are used unless\n" + "\tCONFIG_CAVIUM_OCTEON_NUM_PACKET_BUFFERS is defined."); + +int pow_receive_group = 15; +module_param(pow_receive_group, int, 0444); +MODULE_PARM_DESC(pow_receive_group, "\n" + "\tPOW group to receive packets from. All ethernet hardware\n" + "\twill be configured to send incomming packets to this POW\n" + "\tgroup. Also any other software can submit packets to this\n" + "\tgroup for the kernel to process."); + +int pow_send_group = -1; +module_param(pow_send_group, int, 0644); +MODULE_PARM_DESC(pow_send_group, "\n" + "\tPOW group to send packets to other software on. This\n" + "\tcontrols the creation of the virtual device pow0.\n" + "\talways_use_pow also depends on this value."); + +int always_use_pow; +module_param(always_use_pow, int, 0444); +MODULE_PARM_DESC(always_use_pow, "\n" + "\tWhen set, always send to the pow group. This will cause\n" + "\tpackets sent to real ethernet devices to be sent to the\n" + "\tPOW group instead of the hardware. Unless some other\n" + "\tapplication changes the config, packets will still be\n" + "\treceived from the low level hardware. Use this option\n" + "\tto allow a CVMX app to intercept all packets from the\n" + "\tlinux kernel. You must specify pow_send_group along with\n" + "\tthis option."); + +char pow_send_list[128] = ""; +module_param_string(pow_send_list, pow_send_list, sizeof(pow_send_list), 0444); +MODULE_PARM_DESC(pow_send_list, "\n" + "\tComma separated list of ethernet devices that should use the\n" + "\tPOW for transmit instead of the actual ethernet hardware. This\n" + "\tis a per port version of always_use_pow. always_use_pow takes\n" + "\tprecedence over this list. For example, setting this to\n" + "\t\"eth2,spi3,spi7\" would cause these three devices to transmit\n" + "\tusing the pow_send_group."); + +int max_rx_cpus = -1; +module_param(max_rx_cpus, int, 0444); +MODULE_PARM_DESC(max_rx_cpus, "\n" + "\t\tThe maximum number of CPUs to use for packet reception.\n" + "\t\tUse -1 to use all available CPUs."); + +int rx_napi_weight = 32; +module_param(rx_napi_weight, int, 0444); +MODULE_PARM_DESC(rx_napi_weight, "The NAPI WEIGHT parameter."); + +/* + * The offset from mac_addr_base that should be used for the next port + * that is configured. By convention, if any mgmt ports exist on the + * chip, they get the first mac addresses, The ports controlled by + * this driver are numbered sequencially following any mgmt addresses + * that may exist. + */ +static unsigned int cvm_oct_mac_addr_offset; + +/** + * cvm_oct_poll_queue - Workqueue for polling operations. + */ +struct workqueue_struct *cvm_oct_poll_queue; + +/** + * cvm_oct_poll_queue_stopping - flag to indicate polling should stop. + * + * Set to one right before cvm_oct_poll_queue is destroyed. + */ +atomic_t cvm_oct_poll_queue_stopping = ATOMIC_INIT(0); + +/** + * Array of every ethernet device owned by this driver indexed by + * the ipd input port number. + */ +struct net_device *cvm_oct_device[TOTAL_NUMBER_OF_PORTS]; + +u64 cvm_oct_tx_poll_interval; + +static void cvm_oct_rx_refill_worker(struct work_struct *work); +static DECLARE_DELAYED_WORK(cvm_oct_rx_refill_work, cvm_oct_rx_refill_worker); + +static void cvm_oct_rx_refill_worker(struct work_struct *work) +{ + /* + * FPA 0 may have been drained, try to refill it if we need + * more than num_packet_buffers / 2, otherwise normal receive + * processing will refill it. If it were drained, no packets + * could be received so cvm_oct_napi_poll would never be + * invoked to do the refill. + */ + cvm_oct_rx_refill_pool(num_packet_buffers / 2); + + if (!atomic_read(&cvm_oct_poll_queue_stopping)) + queue_delayed_work(cvm_oct_poll_queue, + &cvm_oct_rx_refill_work, HZ); +} + +static void cvm_oct_periodic_worker(struct work_struct *work) +{ + struct octeon_ethernet *priv = container_of(work, + struct octeon_ethernet, + port_periodic_work.work); + + if (priv->poll) + priv->poll(cvm_oct_device[priv->port]); + + cvm_oct_device[priv->port]->netdev_ops->ndo_get_stats(cvm_oct_device[priv->port]); + + if (!atomic_read(&cvm_oct_poll_queue_stopping)) + queue_delayed_work(cvm_oct_poll_queue, &priv->port_periodic_work, HZ); + } + +static __init void cvm_oct_configure_common_hw(void) +{ + /* Setup the FPA */ + cvmx_fpa_enable(); + cvm_oct_mem_fill_fpa(CVMX_FPA_PACKET_POOL, CVMX_FPA_PACKET_POOL_SIZE, + num_packet_buffers); + cvm_oct_mem_fill_fpa(CVMX_FPA_WQE_POOL, CVMX_FPA_WQE_POOL_SIZE, + num_packet_buffers); + if (CVMX_FPA_OUTPUT_BUFFER_POOL != CVMX_FPA_PACKET_POOL) + cvm_oct_mem_fill_fpa(CVMX_FPA_OUTPUT_BUFFER_POOL, + CVMX_FPA_OUTPUT_BUFFER_POOL_SIZE, 128); + + if (USE_RED) + cvmx_helper_setup_red(num_packet_buffers / 4, + num_packet_buffers / 8); + +} + +/** + * cvm_oct_free_work- Free a work queue entry + * + * @work_queue_entry: Work queue entry to free + * + * Returns Zero on success, Negative on failure. + */ +int cvm_oct_free_work(void *work_queue_entry) +{ + cvmx_wqe_t *work = work_queue_entry; + + int segments = work->word2.s.bufs; + union cvmx_buf_ptr segment_ptr = work->packet_ptr; + + while (segments--) { + union cvmx_buf_ptr next_ptr = *(union cvmx_buf_ptr *) + cvmx_phys_to_ptr(segment_ptr.s.addr - 8); + if (unlikely(!segment_ptr.s.i)) + cvmx_fpa_free(cvm_oct_get_buffer_ptr(segment_ptr), + segment_ptr.s.pool, + DONT_WRITEBACK(CVMX_FPA_PACKET_POOL_SIZE / + 128)); + segment_ptr = next_ptr; + } + cvmx_fpa_free(work, CVMX_FPA_WQE_POOL, DONT_WRITEBACK(1)); + + return 0; +} +EXPORT_SYMBOL(cvm_oct_free_work); + +/** + * cvm_oct_common_get_stats - get the low level ethernet statistics + * @dev: Device to get the statistics from + * + * Returns Pointer to the statistics + */ +static struct net_device_stats *cvm_oct_common_get_stats(struct net_device *dev) +{ + cvmx_pip_port_status_t rx_status; + cvmx_pko_port_status_t tx_status; + struct octeon_ethernet *priv = netdev_priv(dev); + + if (priv->port < CVMX_PIP_NUM_INPUT_PORTS) { + if (octeon_is_simulation()) { + /* The simulator doesn't support statistics */ + memset(&rx_status, 0, sizeof(rx_status)); + memset(&tx_status, 0, sizeof(tx_status)); + } else { + cvmx_pip_get_port_status(priv->port, 1, &rx_status); + cvmx_pko_get_port_status(priv->port, 1, &tx_status); + } + + priv->stats.rx_packets += rx_status.inb_packets; + priv->stats.tx_packets += tx_status.packets; + priv->stats.rx_bytes += rx_status.inb_octets; + priv->stats.tx_bytes += tx_status.octets; + priv->stats.multicast += rx_status.multicast_packets; + priv->stats.rx_crc_errors += rx_status.inb_errors; + priv->stats.rx_frame_errors += rx_status.fcs_align_err_packets; + + /* + * The drop counter must be incremented atomically + * since the RX tasklet also increments it. + */ +#ifdef CONFIG_64BIT + atomic64_add(rx_status.dropped_packets, + (atomic64_t *)&priv->stats.rx_dropped); +#else + atomic_add(rx_status.dropped_packets, + (atomic_t *)&priv->stats.rx_dropped); +#endif + } + + return &priv->stats; +} + +/** + * cvm_oct_common_change_mtu - change the link MTU + * @dev: Device to change + * @new_mtu: The new MTU + * + * Returns Zero on success + */ +static int cvm_oct_common_change_mtu(struct net_device *dev, int new_mtu) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); +#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE) + int vlan_bytes = 4; +#else + int vlan_bytes = 0; +#endif + + /* + * Limit the MTU to make sure the ethernet packets are between + * 64 bytes and 65535 bytes. + */ + if ((new_mtu + 14 + 4 + vlan_bytes < 64) + || (new_mtu + 14 + 4 + vlan_bytes > 65392)) { + pr_err("MTU must be between %d and %d.\n", + 64 - 14 - 4 - vlan_bytes, 65392 - 14 - 4 - vlan_bytes); + return -EINVAL; + } + dev->mtu = new_mtu; + + if ((interface < 2) + && (cvmx_helper_interface_get_mode(interface) != + CVMX_HELPER_INTERFACE_MODE_SPI)) { + /* Add ethernet header and FCS, and VLAN if configured. */ + int max_packet = new_mtu + 14 + 4 + vlan_bytes; + + if (OCTEON_IS_MODEL(OCTEON_CN3XXX) + || OCTEON_IS_MODEL(OCTEON_CN58XX)) { + /* Signal errors on packets larger than the MTU */ + cvmx_write_csr(CVMX_GMXX_RXX_FRM_MAX(index, interface), + max_packet); + } else { + /* + * Set the hardware to truncate packets larger + * than the MTU and smaller the 64 bytes. + */ + union cvmx_pip_frm_len_chkx frm_len_chk; + frm_len_chk.u64 = 0; + frm_len_chk.s.minlen = 64; + frm_len_chk.s.maxlen = max_packet; + cvmx_write_csr(CVMX_PIP_FRM_LEN_CHKX(interface), + frm_len_chk.u64); + } + /* + * Set the hardware to truncate packets larger than + * the MTU. The jabber register must be set to a + * multiple of 8 bytes, so round up. + */ + cvmx_write_csr(CVMX_GMXX_RXX_JABBER(index, interface), + (max_packet + 7) & ~7u); + } + return 0; +} + +/** + * cvm_oct_common_set_multicast_list - set the multicast list + * @dev: Device to work on + */ +static void cvm_oct_common_set_multicast_list(struct net_device *dev) +{ + union cvmx_gmxx_prtx_cfg gmx_cfg; + struct octeon_ethernet *priv = netdev_priv(dev); + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + + if ((interface < 2) + && (cvmx_helper_interface_get_mode(interface) != + CVMX_HELPER_INTERFACE_MODE_SPI)) { + union cvmx_gmxx_rxx_adr_ctl control; + control.u64 = 0; + control.s.bcst = 1; /* Allow broadcast MAC addresses */ + + if (!netdev_mc_empty(dev) || (dev->flags & IFF_ALLMULTI) || + (dev->flags & IFF_PROMISC)) + /* Force accept multicast packets */ + control.s.mcst = 2; + else + /* Force reject multicat packets */ + control.s.mcst = 1; + + if (dev->flags & IFF_PROMISC) + /* + * Reject matches if promisc. Since CAM is + * shut off, should accept everything. + */ + control.s.cam_mode = 0; + else + /* Filter packets based on the CAM */ + control.s.cam_mode = 1; + + gmx_cfg.u64 = + cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), + gmx_cfg.u64 & ~1ull); + + cvmx_write_csr(CVMX_GMXX_RXX_ADR_CTL(index, interface), + control.u64); + if (dev->flags & IFF_PROMISC) + cvmx_write_csr(CVMX_GMXX_RXX_ADR_CAM_EN + (index, interface), 0); + else + cvmx_write_csr(CVMX_GMXX_RXX_ADR_CAM_EN + (index, interface), 1); + + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), + gmx_cfg.u64); + } +} + +/** + * cvm_oct_common_set_mac_address - set the hardware MAC address for a device + * @dev: The device in question. + * @addr: Address structure to change it too. + + * Returns Zero on success + */ +static int cvm_oct_common_set_mac_address(struct net_device *dev, void *addr) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + union cvmx_gmxx_prtx_cfg gmx_cfg; + int interface = INTERFACE(priv->port); + int index = INDEX(priv->port); + + memcpy(dev->dev_addr, addr + 2, 6); + + if ((interface < 2) + && (cvmx_helper_interface_get_mode(interface) != + CVMX_HELPER_INTERFACE_MODE_SPI)) { + int i; + uint8_t *ptr = addr; + uint64_t mac = 0; + for (i = 0; i < 6; i++) + mac = (mac << 8) | (uint64_t) (ptr[i + 2]); + + gmx_cfg.u64 = + cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), + gmx_cfg.u64 & ~1ull); + + cvmx_write_csr(CVMX_GMXX_SMACX(index, interface), mac); + cvmx_write_csr(CVMX_GMXX_RXX_ADR_CAM0(index, interface), + ptr[2]); + cvmx_write_csr(CVMX_GMXX_RXX_ADR_CAM1(index, interface), + ptr[3]); + cvmx_write_csr(CVMX_GMXX_RXX_ADR_CAM2(index, interface), + ptr[4]); + cvmx_write_csr(CVMX_GMXX_RXX_ADR_CAM3(index, interface), + ptr[5]); + cvmx_write_csr(CVMX_GMXX_RXX_ADR_CAM4(index, interface), + ptr[6]); + cvmx_write_csr(CVMX_GMXX_RXX_ADR_CAM5(index, interface), + ptr[7]); + cvm_oct_common_set_multicast_list(dev); + cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), + gmx_cfg.u64); + } + return 0; +} + +/** + * cvm_oct_common_init - per network device initialization + * @dev: Device to initialize + * + * Returns Zero on success + */ +int cvm_oct_common_init(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + struct sockaddr sa; + u64 mac = ((u64)(octeon_bootinfo->mac_addr_base[0] & 0xff) << 40) | + ((u64)(octeon_bootinfo->mac_addr_base[1] & 0xff) << 32) | + ((u64)(octeon_bootinfo->mac_addr_base[2] & 0xff) << 24) | + ((u64)(octeon_bootinfo->mac_addr_base[3] & 0xff) << 16) | + ((u64)(octeon_bootinfo->mac_addr_base[4] & 0xff) << 8) | + (u64)(octeon_bootinfo->mac_addr_base[5] & 0xff); + + mac += cvm_oct_mac_addr_offset; + sa.sa_data[0] = (mac >> 40) & 0xff; + sa.sa_data[1] = (mac >> 32) & 0xff; + sa.sa_data[2] = (mac >> 24) & 0xff; + sa.sa_data[3] = (mac >> 16) & 0xff; + sa.sa_data[4] = (mac >> 8) & 0xff; + sa.sa_data[5] = mac & 0xff; + + if (cvm_oct_mac_addr_offset >= octeon_bootinfo->mac_addr_count) + printk(KERN_DEBUG "%s: Using MAC outside of the assigned range:" + " %pM\n", dev->name, sa.sa_data); + cvm_oct_mac_addr_offset++; + + /* + * Force the interface to use the POW send if always_use_pow + * was specified or it is in the pow send list. + */ + if ((pow_send_group != -1) + && (always_use_pow || strstr(pow_send_list, dev->name))) + priv->queue = -1; + + if (priv->queue != -1) { + dev->features |= NETIF_F_SG; + if (USE_HW_TCPUDP_CHECKSUM) + dev->features |= NETIF_F_IP_CSUM; + } + + /* We do our own locking, Linux doesn't need to */ + dev->features |= NETIF_F_LLTX; + SET_ETHTOOL_OPS(dev, &cvm_oct_ethtool_ops); + + cvm_oct_phy_setup_device(dev); + dev->netdev_ops->ndo_set_mac_address(dev, &sa); + dev->netdev_ops->ndo_change_mtu(dev, dev->mtu); + + /* + * Zero out stats for port so we won't mistakenly show + * counters from the bootloader. + */ + memset(dev->netdev_ops->ndo_get_stats(dev), 0, + sizeof(struct net_device_stats)); + + return 0; +} + +void cvm_oct_common_uninit(struct net_device *dev) +{ + struct octeon_ethernet *priv = netdev_priv(dev); + + if (priv->phydev) + phy_disconnect(priv->phydev); +} + +static const struct net_device_ops cvm_oct_npi_netdev_ops = { + .ndo_init = cvm_oct_common_init, + .ndo_uninit = cvm_oct_common_uninit, + .ndo_start_xmit = cvm_oct_xmit, + .ndo_set_rx_mode = cvm_oct_common_set_multicast_list, + .ndo_set_mac_address = cvm_oct_common_set_mac_address, + .ndo_do_ioctl = cvm_oct_ioctl, + .ndo_change_mtu = cvm_oct_common_change_mtu, + .ndo_get_stats = cvm_oct_common_get_stats, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = cvm_oct_poll_controller, +#endif +}; +static const struct net_device_ops cvm_oct_xaui_netdev_ops = { + .ndo_init = cvm_oct_xaui_init, + .ndo_uninit = cvm_oct_xaui_uninit, + .ndo_open = cvm_oct_xaui_open, + .ndo_stop = cvm_oct_xaui_stop, + .ndo_start_xmit = cvm_oct_xmit, + .ndo_set_rx_mode = cvm_oct_common_set_multicast_list, + .ndo_set_mac_address = cvm_oct_common_set_mac_address, + .ndo_do_ioctl = cvm_oct_ioctl, + .ndo_change_mtu = cvm_oct_common_change_mtu, + .ndo_get_stats = cvm_oct_common_get_stats, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = cvm_oct_poll_controller, +#endif +}; +static const struct net_device_ops cvm_oct_sgmii_netdev_ops = { + .ndo_init = cvm_oct_sgmii_init, + .ndo_uninit = cvm_oct_sgmii_uninit, + .ndo_open = cvm_oct_sgmii_open, + .ndo_stop = cvm_oct_sgmii_stop, + .ndo_start_xmit = cvm_oct_xmit, + .ndo_set_rx_mode = cvm_oct_common_set_multicast_list, + .ndo_set_mac_address = cvm_oct_common_set_mac_address, + .ndo_do_ioctl = cvm_oct_ioctl, + .ndo_change_mtu = cvm_oct_common_change_mtu, + .ndo_get_stats = cvm_oct_common_get_stats, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = cvm_oct_poll_controller, +#endif +}; +static const struct net_device_ops cvm_oct_spi_netdev_ops = { + .ndo_init = cvm_oct_spi_init, + .ndo_uninit = cvm_oct_spi_uninit, + .ndo_start_xmit = cvm_oct_xmit, + .ndo_set_rx_mode = cvm_oct_common_set_multicast_list, + .ndo_set_mac_address = cvm_oct_common_set_mac_address, + .ndo_do_ioctl = cvm_oct_ioctl, + .ndo_change_mtu = cvm_oct_common_change_mtu, + .ndo_get_stats = cvm_oct_common_get_stats, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = cvm_oct_poll_controller, +#endif +}; +static const struct net_device_ops cvm_oct_rgmii_netdev_ops = { + .ndo_init = cvm_oct_rgmii_init, + .ndo_uninit = cvm_oct_rgmii_uninit, + .ndo_open = cvm_oct_rgmii_open, + .ndo_stop = cvm_oct_rgmii_stop, + .ndo_start_xmit = cvm_oct_xmit, + .ndo_set_rx_mode = cvm_oct_common_set_multicast_list, + .ndo_set_mac_address = cvm_oct_common_set_mac_address, + .ndo_do_ioctl = cvm_oct_ioctl, + .ndo_change_mtu = cvm_oct_common_change_mtu, + .ndo_get_stats = cvm_oct_common_get_stats, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = cvm_oct_poll_controller, +#endif +}; +static const struct net_device_ops cvm_oct_pow_netdev_ops = { + .ndo_init = cvm_oct_common_init, + .ndo_start_xmit = cvm_oct_xmit_pow, + .ndo_set_rx_mode = cvm_oct_common_set_multicast_list, + .ndo_set_mac_address = cvm_oct_common_set_mac_address, + .ndo_do_ioctl = cvm_oct_ioctl, + .ndo_change_mtu = cvm_oct_common_change_mtu, + .ndo_get_stats = cvm_oct_common_get_stats, +#ifdef CONFIG_NET_POLL_CONTROLLER + .ndo_poll_controller = cvm_oct_poll_controller, +#endif +}; + +extern void octeon_mdiobus_force_mod_depencency(void); + +static int __init cvm_oct_init_module(void) +{ + int num_interfaces; + int interface; + int fau = FAU_NUM_PACKET_BUFFERS_TO_FREE; + int qos; + + octeon_mdiobus_force_mod_depencency(); + pr_notice("cavium-ethernet %s\n", OCTEON_ETHERNET_VERSION); + + if (OCTEON_IS_MODEL(OCTEON_CN52XX)) + cvm_oct_mac_addr_offset = 2; /* First two are the mgmt ports. */ + else if (OCTEON_IS_MODEL(OCTEON_CN56XX)) + cvm_oct_mac_addr_offset = 1; /* First one is the mgmt port. */ + else + cvm_oct_mac_addr_offset = 0; + + cvm_oct_poll_queue = create_singlethread_workqueue("octeon-ethernet"); + if (cvm_oct_poll_queue == NULL) { + pr_err("octeon-ethernet: Cannot create workqueue"); + return -ENOMEM; + } + + cvm_oct_configure_common_hw(); + + cvmx_helper_initialize_packet_io_global(); + + /* Change the input group for all ports before input is enabled */ + num_interfaces = cvmx_helper_get_number_of_interfaces(); + for (interface = 0; interface < num_interfaces; interface++) { + int num_ports = cvmx_helper_ports_on_interface(interface); + int port; + + for (port = cvmx_helper_get_ipd_port(interface, 0); + port < cvmx_helper_get_ipd_port(interface, num_ports); + port++) { + union cvmx_pip_prt_tagx pip_prt_tagx; + pip_prt_tagx.u64 = + cvmx_read_csr(CVMX_PIP_PRT_TAGX(port)); + pip_prt_tagx.s.grp = pow_receive_group; + cvmx_write_csr(CVMX_PIP_PRT_TAGX(port), + pip_prt_tagx.u64); + } + } + + cvmx_helper_ipd_and_packet_input_enable(); + + memset(cvm_oct_device, 0, sizeof(cvm_oct_device)); + + /* + * Initialize the FAU used for counting packet buffers that + * need to be freed. + */ + cvmx_fau_atomic_write32(FAU_NUM_PACKET_BUFFERS_TO_FREE, 0); + + /* Initialize the FAU used for counting tx SKBs that need to be freed */ + cvmx_fau_atomic_write32(FAU_TOTAL_TX_TO_CLEAN, 0); + + if ((pow_send_group != -1)) { + struct net_device *dev; + pr_info("\tConfiguring device for POW only access\n"); + dev = alloc_etherdev(sizeof(struct octeon_ethernet)); + if (dev) { + /* Initialize the device private structure. */ + struct octeon_ethernet *priv = netdev_priv(dev); + + dev->netdev_ops = &cvm_oct_pow_netdev_ops; + priv->imode = CVMX_HELPER_INTERFACE_MODE_DISABLED; + priv->port = CVMX_PIP_NUM_INPUT_PORTS; + priv->queue = -1; + strcpy(dev->name, "pow%d"); + for (qos = 0; qos < 16; qos++) + skb_queue_head_init(&priv->tx_free_list[qos]); + + if (register_netdev(dev) < 0) { + pr_err("Failed to register ethernet device for POW\n"); + free_netdev(dev); + } else { + cvm_oct_device[CVMX_PIP_NUM_INPUT_PORTS] = dev; + pr_info("%s: POW send group %d, receive group %d\n", + dev->name, pow_send_group, + pow_receive_group); + } + } else { + pr_err("Failed to allocate ethernet device for POW\n"); + } + } + + num_interfaces = cvmx_helper_get_number_of_interfaces(); + for (interface = 0; interface < num_interfaces; interface++) { + cvmx_helper_interface_mode_t imode = + cvmx_helper_interface_get_mode(interface); + int num_ports = cvmx_helper_ports_on_interface(interface); + int port; + + for (port = cvmx_helper_get_ipd_port(interface, 0); + port < cvmx_helper_get_ipd_port(interface, num_ports); + port++) { + struct octeon_ethernet *priv; + struct net_device *dev = + alloc_etherdev(sizeof(struct octeon_ethernet)); + if (!dev) { + pr_err("Failed to allocate ethernet device for port %d\n", port); + continue; + } + + /* Initialize the device private structure. */ + priv = netdev_priv(dev); + + INIT_DELAYED_WORK(&priv->port_periodic_work, + cvm_oct_periodic_worker); + priv->imode = imode; + priv->port = port; + priv->queue = cvmx_pko_get_base_queue(priv->port); + priv->fau = fau - cvmx_pko_get_num_queues(port) * 4; + for (qos = 0; qos < 16; qos++) + skb_queue_head_init(&priv->tx_free_list[qos]); + for (qos = 0; qos < cvmx_pko_get_num_queues(port); + qos++) + cvmx_fau_atomic_write32(priv->fau + qos * 4, 0); + + switch (priv->imode) { + + /* These types don't support ports to IPD/PKO */ + case CVMX_HELPER_INTERFACE_MODE_DISABLED: + case CVMX_HELPER_INTERFACE_MODE_PCIE: + case CVMX_HELPER_INTERFACE_MODE_PICMG: + break; + + case CVMX_HELPER_INTERFACE_MODE_NPI: + dev->netdev_ops = &cvm_oct_npi_netdev_ops; + strcpy(dev->name, "npi%d"); + break; + + case CVMX_HELPER_INTERFACE_MODE_XAUI: + dev->netdev_ops = &cvm_oct_xaui_netdev_ops; + strcpy(dev->name, "xaui%d"); + break; + + case CVMX_HELPER_INTERFACE_MODE_LOOP: + dev->netdev_ops = &cvm_oct_npi_netdev_ops; + strcpy(dev->name, "loop%d"); + break; + + case CVMX_HELPER_INTERFACE_MODE_SGMII: + dev->netdev_ops = &cvm_oct_sgmii_netdev_ops; + strcpy(dev->name, "eth%d"); + break; + + case CVMX_HELPER_INTERFACE_MODE_SPI: + dev->netdev_ops = &cvm_oct_spi_netdev_ops; + strcpy(dev->name, "spi%d"); + break; + + case CVMX_HELPER_INTERFACE_MODE_RGMII: + case CVMX_HELPER_INTERFACE_MODE_GMII: + dev->netdev_ops = &cvm_oct_rgmii_netdev_ops; + strcpy(dev->name, "eth%d"); + break; + } + + if (!dev->netdev_ops) { + free_netdev(dev); + } else if (register_netdev(dev) < 0) { + pr_err("Failed to register ethernet device " + "for interface %d, port %d\n", + interface, priv->port); + free_netdev(dev); + } else { + cvm_oct_device[priv->port] = dev; + fau -= + cvmx_pko_get_num_queues(priv->port) * + sizeof(uint32_t); + queue_delayed_work(cvm_oct_poll_queue, + &priv->port_periodic_work, HZ); + } + } + } + + cvm_oct_tx_initialize(); + cvm_oct_rx_initialize(); + + /* + * 150 uS: about 10 1500-byte packtes at 1GE. + */ + cvm_oct_tx_poll_interval = 150 * (octeon_get_clock_rate() / 1000000); + + queue_delayed_work(cvm_oct_poll_queue, &cvm_oct_rx_refill_work, HZ); + + return 0; +} + +static void __exit cvm_oct_cleanup_module(void) +{ + int port; + + /* Disable POW interrupt */ + cvmx_write_csr(CVMX_POW_WQ_INT_THRX(pow_receive_group), 0); + + cvmx_ipd_disable(); + + /* Free the interrupt handler */ + free_irq(OCTEON_IRQ_WORKQ0 + pow_receive_group, cvm_oct_device); + + atomic_inc_return(&cvm_oct_poll_queue_stopping); + cancel_delayed_work_sync(&cvm_oct_rx_refill_work); + + cvm_oct_rx_shutdown(); + cvm_oct_tx_shutdown(); + + cvmx_pko_disable(); + + /* Free the ethernet devices */ + for (port = 0; port < TOTAL_NUMBER_OF_PORTS; port++) { + if (cvm_oct_device[port]) { + struct net_device *dev = cvm_oct_device[port]; + struct octeon_ethernet *priv = netdev_priv(dev); + cancel_delayed_work_sync(&priv->port_periodic_work); + + cvm_oct_tx_shutdown_dev(dev); + unregister_netdev(dev); + free_netdev(dev); + cvm_oct_device[port] = NULL; + } + } + + destroy_workqueue(cvm_oct_poll_queue); + + cvmx_pko_shutdown(); + + cvmx_ipd_free_ptr(); + + /* Free the HW pools */ + cvm_oct_mem_empty_fpa(CVMX_FPA_PACKET_POOL, CVMX_FPA_PACKET_POOL_SIZE, + num_packet_buffers); + cvm_oct_mem_empty_fpa(CVMX_FPA_WQE_POOL, CVMX_FPA_WQE_POOL_SIZE, + num_packet_buffers); + if (CVMX_FPA_OUTPUT_BUFFER_POOL != CVMX_FPA_PACKET_POOL) + cvm_oct_mem_empty_fpa(CVMX_FPA_OUTPUT_BUFFER_POOL, + CVMX_FPA_OUTPUT_BUFFER_POOL_SIZE, 128); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cavium Networks "); +MODULE_DESCRIPTION("Cavium Networks Octeon ethernet driver."); +module_init(cvm_oct_init_module); +module_exit(cvm_oct_cleanup_module); diff --git a/drivers/staging/octeon/octeon-ethernet.h b/drivers/staging/octeon/octeon-ethernet.h new file mode 100644 index 00000000..d5819256 --- /dev/null +++ b/drivers/staging/octeon/octeon-ethernet.h @@ -0,0 +1,98 @@ +/********************************************************************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2010 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information +**********************************************************************/ + +/* + * External interface for the Cavium Octeon ethernet driver. + */ +#ifndef OCTEON_ETHERNET_H +#define OCTEON_ETHERNET_H + +/** + * This is the definition of the Ethernet driver's private + * driver state stored in netdev_priv(dev). + */ +struct octeon_ethernet { + /* PKO hardware output port */ + int port; + /* PKO hardware queue for the port */ + int queue; + /* Hardware fetch and add to count outstanding tx buffers */ + int fau; + /* + * Type of port. This is one of the enums in + * cvmx_helper_interface_mode_t + */ + int imode; + /* List of outstanding tx buffers per queue */ + struct sk_buff_head tx_free_list[16]; + /* Device statistics */ + struct net_device_stats stats; + struct phy_device *phydev; + unsigned int last_link; + /* Last negotiated link state */ + uint64_t link_info; + /* Called periodically to check link status */ + void (*poll) (struct net_device *dev); + struct delayed_work port_periodic_work; + struct work_struct port_work; /* may be unused. */ +}; + +int cvm_oct_free_work(void *work_queue_entry); + +extern int cvm_oct_rgmii_init(struct net_device *dev); +extern void cvm_oct_rgmii_uninit(struct net_device *dev); +extern int cvm_oct_rgmii_open(struct net_device *dev); +extern int cvm_oct_rgmii_stop(struct net_device *dev); + +extern int cvm_oct_sgmii_init(struct net_device *dev); +extern void cvm_oct_sgmii_uninit(struct net_device *dev); +extern int cvm_oct_sgmii_open(struct net_device *dev); +extern int cvm_oct_sgmii_stop(struct net_device *dev); + +extern int cvm_oct_spi_init(struct net_device *dev); +extern void cvm_oct_spi_uninit(struct net_device *dev); +extern int cvm_oct_xaui_init(struct net_device *dev); +extern void cvm_oct_xaui_uninit(struct net_device *dev); +extern int cvm_oct_xaui_open(struct net_device *dev); +extern int cvm_oct_xaui_stop(struct net_device *dev); + +extern int cvm_oct_common_init(struct net_device *dev); +extern void cvm_oct_common_uninit(struct net_device *dev); + +extern int always_use_pow; +extern int pow_send_group; +extern int pow_receive_group; +extern char pow_send_list[]; +extern struct net_device *cvm_oct_device[]; +extern struct workqueue_struct *cvm_oct_poll_queue; +extern atomic_t cvm_oct_poll_queue_stopping; +extern u64 cvm_oct_tx_poll_interval; + +extern int max_rx_cpus; +extern int rx_napi_weight; + +#endif diff --git a/drivers/staging/olpc_dcon/Kconfig b/drivers/staging/olpc_dcon/Kconfig new file mode 100644 index 00000000..fe40e0b6 --- /dev/null +++ b/drivers/staging/olpc_dcon/Kconfig @@ -0,0 +1,29 @@ +config FB_OLPC_DCON + tristate "One Laptop Per Child Display CONtroller support" + depends on OLPC && FB + select I2C + select BACKLIGHT_CLASS_DEVICE + ---help--- + Add support for the OLPC XO DCON controller. This controller is + only available on OLPC platforms. Unless you have one of these + platforms, you will want to say 'N'. + +config FB_OLPC_DCON_1 + bool "OLPC XO-1 DCON support" + depends on FB_OLPC_DCON && GPIO_CS5535 + default y + ---help--- + Enable support for the DCON in XO-1 model laptops. The kernel + communicates with the DCON using model-specific code. If you + have an XO-1 (or if you're unsure what model you have), you should + say 'Y'. + +config FB_OLPC_DCON_1_5 + bool "OLPC XO-1.5 DCON support" + depends on FB_OLPC_DCON && ACPI + default y + ---help--- + Enable support for the DCON in XO-1.5 model laptops. The kernel + communicates with the DCON using model-specific code. If you + have an XO-1.5 (or if you're unsure what model you have), you + should say 'Y'. diff --git a/drivers/staging/olpc_dcon/Makefile b/drivers/staging/olpc_dcon/Makefile new file mode 100644 index 00000000..36c7e67f --- /dev/null +++ b/drivers/staging/olpc_dcon/Makefile @@ -0,0 +1,6 @@ +olpc-dcon-objs += olpc_dcon.o +olpc-dcon-$(CONFIG_FB_OLPC_DCON_1) += olpc_dcon_xo_1.o +olpc-dcon-$(CONFIG_FB_OLPC_DCON_1_5) += olpc_dcon_xo_1_5.o +obj-$(CONFIG_FB_OLPC_DCON) += olpc-dcon.o + + diff --git a/drivers/staging/olpc_dcon/TODO b/drivers/staging/olpc_dcon/TODO new file mode 100644 index 00000000..35f9cda7 --- /dev/null +++ b/drivers/staging/olpc_dcon/TODO @@ -0,0 +1,16 @@ +TODO: + - checkpatch.pl cleanups + - see if vx855 gpio API can be made similar enough to cs5535 so we can + share more code + - allow simultaneous XO-1 and XO-1.5 support + - console event notifier support + - drop global variables, use a proper olpc_dcon_priv struct + - audit code for unnecessary code; old unsupported prototype + workarounds, ancient variables (noaa?), etc + - verify sane i2c API usage, update to new stuff if necessary + +Please send patches to Greg Kroah-Hartman and +copy: + Andres Salomon + Chris Ball + Jon Nettleton diff --git a/drivers/staging/olpc_dcon/olpc_dcon.c b/drivers/staging/olpc_dcon/olpc_dcon.c new file mode 100644 index 00000000..3d919932 --- /dev/null +++ b/drivers/staging/olpc_dcon/olpc_dcon.c @@ -0,0 +1,842 @@ +/* + * Mainly by David Woodhouse, somewhat modified by Jordan Crouse + * + * Copyright © 2006-2007 Red Hat, Inc. + * Copyright © 2006-2007 Advanced Micro Devices, Inc. + * Copyright © 2009 VIA Technology, Inc. + * Copyright (c) 2010-2011 Andres Salomon + * + * This program is free software. You can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "olpc_dcon.h" + +/* Module definitions */ + +static ushort resumeline = 898; +module_param(resumeline, ushort, 0444); + +/* Default off since it doesn't work on DCON ASIC in B-test OLPC board */ +static int useaa = 1; +module_param(useaa, int, 0444); + +static struct dcon_platform_data *pdata; + +/* I2C structures */ + +/* Platform devices */ +static struct platform_device *dcon_device; + +static DECLARE_WAIT_QUEUE_HEAD(dcon_wait_queue); + +static unsigned short normal_i2c[] = { 0x0d, I2C_CLIENT_END }; + +static s32 dcon_write(struct dcon_priv *dcon, u8 reg, u16 val) +{ + return i2c_smbus_write_word_data(dcon->client, reg, val); +} + +static s32 dcon_read(struct dcon_priv *dcon, u8 reg) +{ + return i2c_smbus_read_word_data(dcon->client, reg); +} + +/* ===== API functions - these are called by a variety of users ==== */ + +static int dcon_hw_init(struct dcon_priv *dcon, int is_init) +{ + uint16_t ver; + int rc = 0; + + ver = dcon_read(dcon, DCON_REG_ID); + if ((ver >> 8) != 0xDC) { + printk(KERN_ERR "olpc-dcon: DCON ID not 0xDCxx: 0x%04x " + "instead.\n", ver); + rc = -ENXIO; + goto err; + } + + if (is_init) { + printk(KERN_INFO "olpc-dcon: Discovered DCON version %x\n", + ver & 0xFF); + rc = pdata->init(dcon); + if (rc != 0) { + printk(KERN_ERR "olpc-dcon: Unable to init.\n"); + goto err; + } + } + + if (ver < 0xdc02) { + dev_err(&dcon->client->dev, + "DCON v1 is unsupported, giving up..\n"); + rc = -ENODEV; + goto err; + } + + /* SDRAM setup/hold time */ + dcon_write(dcon, 0x3a, 0xc040); + dcon_write(dcon, 0x41, 0x0000); + dcon_write(dcon, 0x41, 0x0101); + dcon_write(dcon, 0x42, 0x0101); + + /* Colour swizzle, AA, no passthrough, backlight */ + if (is_init) { + dcon->disp_mode = MODE_PASSTHRU | MODE_BL_ENABLE | + MODE_CSWIZZLE; + if (useaa) + dcon->disp_mode |= MODE_COL_AA; + } + dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode); + + + /* Set the scanline to interrupt on during resume */ + dcon_write(dcon, DCON_REG_SCAN_INT, resumeline); + +err: + return rc; +} + +/* + * The smbus doesn't always come back due to what is believed to be + * hardware (power rail) bugs. For older models where this is known to + * occur, our solution is to attempt to wait for the bus to stabilize; + * if it doesn't happen, cut power to the dcon, repower it, and wait + * for the bus to stabilize. Rinse, repeat until we have a working + * smbus. For newer models, we simply BUG(); we want to know if this + * still happens despite the power fixes that have been made! + */ +static int dcon_bus_stabilize(struct dcon_priv *dcon, int is_powered_down) +{ + unsigned long timeout; + int x; + +power_up: + if (is_powered_down) { + x = 1; + x = olpc_ec_cmd(0x26, (unsigned char *) &x, 1, NULL, 0); + if (x) { + printk(KERN_WARNING "olpc-dcon: unable to force dcon " + "to power up: %d!\n", x); + return x; + } + msleep(10); /* we'll be conservative */ + } + + pdata->bus_stabilize_wiggle(); + + for (x = -1, timeout = 50; timeout && x < 0; timeout--) { + msleep(1); + x = dcon_read(dcon, DCON_REG_ID); + } + if (x < 0) { + printk(KERN_ERR "olpc-dcon: unable to stabilize dcon's " + "smbus, reasserting power and praying.\n"); + BUG_ON(olpc_board_at_least(olpc_board(0xc2))); + x = 0; + olpc_ec_cmd(0x26, (unsigned char *) &x, 1, NULL, 0); + msleep(100); + is_powered_down = 1; + goto power_up; /* argh, stupid hardware.. */ + } + + if (is_powered_down) + return dcon_hw_init(dcon, 0); + return 0; +} + +static void dcon_set_backlight(struct dcon_priv *dcon, u8 level) +{ + dcon->bl_val = level; + dcon_write(dcon, DCON_REG_BRIGHT, dcon->bl_val); + + /* Purposely turn off the backlight when we go to level 0 */ + if (dcon->bl_val == 0) { + dcon->disp_mode &= ~MODE_BL_ENABLE; + dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode); + } else if (!(dcon->disp_mode & MODE_BL_ENABLE)) { + dcon->disp_mode |= MODE_BL_ENABLE; + dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode); + } +} + +/* Set the output type to either color or mono */ +static int dcon_set_mono_mode(struct dcon_priv *dcon, bool enable_mono) +{ + if (dcon->mono == enable_mono) + return 0; + + dcon->mono = enable_mono; + + if (enable_mono) { + dcon->disp_mode &= ~(MODE_CSWIZZLE | MODE_COL_AA); + dcon->disp_mode |= MODE_MONO_LUMA; + } else { + dcon->disp_mode &= ~(MODE_MONO_LUMA); + dcon->disp_mode |= MODE_CSWIZZLE; + if (useaa) + dcon->disp_mode |= MODE_COL_AA; + } + + dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode); + return 0; +} + +/* For now, this will be really stupid - we need to address how + * DCONLOAD works in a sleep and account for it accordingly + */ + +static void dcon_sleep(struct dcon_priv *dcon, bool sleep) +{ + int x; + + /* Turn off the backlight and put the DCON to sleep */ + + if (dcon->asleep == sleep) + return; + + if (!olpc_board_at_least(olpc_board(0xc2))) + return; + + if (sleep) { + x = 0; + x = olpc_ec_cmd(0x26, (unsigned char *) &x, 1, NULL, 0); + if (x) + printk(KERN_WARNING "olpc-dcon: unable to force dcon " + "to power down: %d!\n", x); + else + dcon->asleep = sleep; + } else { + /* Only re-enable the backlight if the backlight value is set */ + if (dcon->bl_val != 0) + dcon->disp_mode |= MODE_BL_ENABLE; + x = dcon_bus_stabilize(dcon, 1); + if (x) + printk(KERN_WARNING "olpc-dcon: unable to reinit dcon" + " hardware: %d!\n", x); + else + dcon->asleep = sleep; + + /* Restore backlight */ + dcon_set_backlight(dcon, dcon->bl_val); + } + + /* We should turn off some stuff in the framebuffer - but what? */ +} + +/* the DCON seems to get confused if we change DCONLOAD too + * frequently -- i.e., approximately faster than frame time. + * normally we don't change it this fast, so in general we won't + * delay here. + */ +static void dcon_load_holdoff(struct dcon_priv *dcon) +{ + struct timespec delta_t, now; + while (1) { + getnstimeofday(&now); + delta_t = timespec_sub(now, dcon->load_time); + if (delta_t.tv_sec != 0 || + delta_t.tv_nsec > NSEC_PER_MSEC * 20) { + break; + } + mdelay(4); + } +} + +static bool dcon_blank_fb(struct dcon_priv *dcon, bool blank) +{ + int err; + + if (!lock_fb_info(dcon->fbinfo)) { + dev_err(&dcon->client->dev, "unable to lock framebuffer\n"); + return false; + } + console_lock(); + dcon->ignore_fb_events = true; + err = fb_blank(dcon->fbinfo, + blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); + dcon->ignore_fb_events = false; + console_unlock(); + unlock_fb_info(dcon->fbinfo); + + if (err) { + dev_err(&dcon->client->dev, "couldn't %sblank framebuffer\n", + blank ? "" : "un"); + return false; + } + return true; +} + +/* Set the source of the display (CPU or DCON) */ +static void dcon_source_switch(struct work_struct *work) +{ + struct dcon_priv *dcon = container_of(work, struct dcon_priv, + switch_source); + DECLARE_WAITQUEUE(wait, current); + int source = dcon->pending_src; + + if (dcon->curr_src == source) + return; + + dcon_load_holdoff(dcon); + + dcon->switched = false; + + switch (source) { + case DCON_SOURCE_CPU: + printk("dcon_source_switch to CPU\n"); + /* Enable the scanline interrupt bit */ + if (dcon_write(dcon, DCON_REG_MODE, + dcon->disp_mode | MODE_SCAN_INT)) + printk(KERN_ERR + "olpc-dcon: couldn't enable scanline interrupt!\n"); + else { + /* Wait up to one second for the scanline interrupt */ + wait_event_timeout(dcon_wait_queue, + dcon->switched == true, HZ); + } + + if (!dcon->switched) + printk(KERN_ERR "olpc-dcon: Timeout entering CPU mode; expect a screen glitch.\n"); + + /* Turn off the scanline interrupt */ + if (dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode)) + printk(KERN_ERR "olpc-dcon: couldn't disable scanline interrupt!\n"); + + /* + * Ideally we'd like to disable interrupts here so that the + * fb unblanking and DCON turn on happen at a known time value; + * however, we can't do that right now with fb_blank + * messing with semaphores. + * + * For now, we just hope.. + */ + if (!dcon_blank_fb(dcon, false)) { + printk(KERN_ERR "olpc-dcon: Failed to enter CPU mode\n"); + dcon->pending_src = DCON_SOURCE_DCON; + return; + } + + /* And turn off the DCON */ + pdata->set_dconload(1); + getnstimeofday(&dcon->load_time); + + printk(KERN_INFO "olpc-dcon: The CPU has control\n"); + break; + case DCON_SOURCE_DCON: + { + int t; + struct timespec delta_t; + + printk(KERN_INFO "dcon_source_switch to DCON\n"); + + add_wait_queue(&dcon_wait_queue, &wait); + set_current_state(TASK_UNINTERRUPTIBLE); + + /* Clear DCONLOAD - this implies that the DCON is in control */ + pdata->set_dconload(0); + getnstimeofday(&dcon->load_time); + + t = schedule_timeout(HZ/2); + remove_wait_queue(&dcon_wait_queue, &wait); + set_current_state(TASK_RUNNING); + + if (!dcon->switched) { + printk(KERN_ERR "olpc-dcon: Timeout entering DCON mode; expect a screen glitch.\n"); + } else { + /* sometimes the DCON doesn't follow its own rules, + * and doesn't wait for two vsync pulses before + * ack'ing the frame load with an IRQ. the result + * is that the display shows the *previously* + * loaded frame. we can detect this by looking at + * the time between asserting DCONLOAD and the IRQ -- + * if it's less than 20msec, then the DCON couldn't + * have seen two VSYNC pulses. in that case we + * deassert and reassert, and hope for the best. + * see http://dev.laptop.org/ticket/9664 + */ + delta_t = timespec_sub(dcon->irq_time, dcon->load_time); + if (dcon->switched && delta_t.tv_sec == 0 && + delta_t.tv_nsec < NSEC_PER_MSEC * 20) { + printk(KERN_ERR "olpc-dcon: missed loading, retrying\n"); + pdata->set_dconload(1); + mdelay(41); + pdata->set_dconload(0); + getnstimeofday(&dcon->load_time); + mdelay(41); + } + } + + dcon_blank_fb(dcon, true); + printk(KERN_INFO "olpc-dcon: The DCON has control\n"); + break; + } + default: + BUG(); + } + + dcon->curr_src = source; +} + +static void dcon_set_source(struct dcon_priv *dcon, int arg) +{ + if (dcon->pending_src == arg) + return; + + dcon->pending_src = arg; + + if ((dcon->curr_src != arg) && !work_pending(&dcon->switch_source)) + schedule_work(&dcon->switch_source); +} + +static void dcon_set_source_sync(struct dcon_priv *dcon, int arg) +{ + dcon_set_source(dcon, arg); + flush_scheduled_work(); +} + +static ssize_t dcon_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dcon_priv *dcon = dev_get_drvdata(dev); + return sprintf(buf, "%4.4X\n", dcon->disp_mode); +} + +static ssize_t dcon_sleep_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + struct dcon_priv *dcon = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", dcon->asleep); +} + +static ssize_t dcon_freeze_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dcon_priv *dcon = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", dcon->curr_src == DCON_SOURCE_DCON ? 1 : 0); +} + +static ssize_t dcon_mono_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dcon_priv *dcon = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", dcon->mono); +} + +static ssize_t dcon_resumeline_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", resumeline); +} + +static ssize_t dcon_mono_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long enable_mono; + int rc; + + rc = kstrtoul(buf, 10, &enable_mono); + if (rc) + return rc; + + dcon_set_mono_mode(dev_get_drvdata(dev), enable_mono ? true : false); + + return count; +} + +static ssize_t dcon_freeze_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct dcon_priv *dcon = dev_get_drvdata(dev); + unsigned long output; + int ret; + + ret = kstrtoul(buf, 10, &output); + if (ret) + return ret; + + printk(KERN_INFO "dcon_freeze_store: %lu\n", output); + + switch (output) { + case 0: + dcon_set_source(dcon, DCON_SOURCE_CPU); + break; + case 1: + dcon_set_source_sync(dcon, DCON_SOURCE_DCON); + break; + case 2: /* normally unused */ + dcon_set_source(dcon, DCON_SOURCE_DCON); + break; + default: + return -EINVAL; + } + + return count; +} + +static ssize_t dcon_resumeline_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned short rl; + int rc; + + rc = kstrtou16(buf, 10, &rl); + if (rc) + return rc; + + resumeline = rl; + dcon_write(dev_get_drvdata(dev), DCON_REG_SCAN_INT, resumeline); + + return count; +} + +static ssize_t dcon_sleep_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long output; + int ret; + + ret = kstrtoul(buf, 10, &output); + if (ret) + return ret; + + dcon_sleep(dev_get_drvdata(dev), output ? true : false); + return count; +} + +static struct device_attribute dcon_device_files[] = { + __ATTR(mode, 0444, dcon_mode_show, NULL), + __ATTR(sleep, 0644, dcon_sleep_show, dcon_sleep_store), + __ATTR(freeze, 0644, dcon_freeze_show, dcon_freeze_store), + __ATTR(monochrome, 0644, dcon_mono_show, dcon_mono_store), + __ATTR(resumeline, 0644, dcon_resumeline_show, dcon_resumeline_store), +}; + +static int dcon_bl_update(struct backlight_device *dev) +{ + struct dcon_priv *dcon = bl_get_data(dev); + u8 level = dev->props.brightness & 0x0F; + + if (dev->props.power != FB_BLANK_UNBLANK) + level = 0; + + if (level != dcon->bl_val) + dcon_set_backlight(dcon, level); + + return 0; +} + +static int dcon_bl_get(struct backlight_device *dev) +{ + struct dcon_priv *dcon = bl_get_data(dev); + return dcon->bl_val; +} + +static const struct backlight_ops dcon_bl_ops = { + .update_status = dcon_bl_update, + .get_brightness = dcon_bl_get, +}; + +static struct backlight_properties dcon_bl_props = { + .max_brightness = 15, + .type = BACKLIGHT_RAW, + .power = FB_BLANK_UNBLANK, +}; + +static int dcon_reboot_notify(struct notifier_block *nb, + unsigned long foo, void *bar) +{ + struct dcon_priv *dcon = container_of(nb, struct dcon_priv, reboot_nb); + + if (!dcon || !dcon->client) + return 0; + + /* Turn off the DCON. Entirely. */ + dcon_write(dcon, DCON_REG_MODE, 0x39); + dcon_write(dcon, DCON_REG_MODE, 0x32); + return 0; +} + +static int unfreeze_on_panic(struct notifier_block *nb, + unsigned long e, void *p) +{ + pdata->set_dconload(1); + return NOTIFY_DONE; +} + +static struct notifier_block dcon_panic_nb = { + .notifier_call = unfreeze_on_panic, +}; + +/* + * When the framebuffer sleeps due to external sources (e.g. user idle), power + * down the DCON as well. Power it back up when the fb comes back to life. + */ +static int dcon_fb_notifier(struct notifier_block *self, + unsigned long event, void *data) +{ + struct fb_event *evdata = data; + struct dcon_priv *dcon = container_of(self, struct dcon_priv, + fbevent_nb); + int *blank = (int *) evdata->data; + if (((event != FB_EVENT_BLANK) && (event != FB_EVENT_CONBLANK)) || + dcon->ignore_fb_events) + return 0; + dcon_sleep(dcon, *blank ? true : false); + return 0; +} + +static int dcon_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + strlcpy(info->type, "olpc_dcon", I2C_NAME_SIZE); + + return 0; +} + +static int dcon_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct dcon_priv *dcon; + int rc, i, j; + + if (!pdata) + return -ENXIO; + + dcon = kzalloc(sizeof(*dcon), GFP_KERNEL); + if (!dcon) + return -ENOMEM; + + dcon->client = client; + INIT_WORK(&dcon->switch_source, dcon_source_switch); + dcon->reboot_nb.notifier_call = dcon_reboot_notify; + dcon->reboot_nb.priority = -1; + dcon->fbevent_nb.notifier_call = dcon_fb_notifier; + + i2c_set_clientdata(client, dcon); + + if (num_registered_fb < 1) { + dev_err(&client->dev, "DCON driver requires a registered fb\n"); + rc = -EIO; + goto einit; + } + dcon->fbinfo = registered_fb[0]; + + rc = dcon_hw_init(dcon, 1); + if (rc) + goto einit; + + /* Add the DCON device */ + + dcon_device = platform_device_alloc("dcon", -1); + + if (dcon_device == NULL) { + printk(KERN_ERR "dcon: Unable to create the DCON device\n"); + rc = -ENOMEM; + goto eirq; + } + rc = platform_device_add(dcon_device); + platform_set_drvdata(dcon_device, dcon); + + if (rc) { + printk(KERN_ERR "dcon: Unable to add the DCON device\n"); + goto edev; + } + + for (i = 0; i < ARRAY_SIZE(dcon_device_files); i++) { + rc = device_create_file(&dcon_device->dev, + &dcon_device_files[i]); + if (rc) { + dev_err(&dcon_device->dev, "Cannot create sysfs file\n"); + goto ecreate; + } + } + + dcon->bl_val = dcon_read(dcon, DCON_REG_BRIGHT) & 0x0F; + + /* Add the backlight device for the DCON */ + dcon_bl_props.brightness = dcon->bl_val; + dcon->bl_dev = backlight_device_register("dcon-bl", &dcon_device->dev, + dcon, &dcon_bl_ops, &dcon_bl_props); + if (IS_ERR(dcon->bl_dev)) { + dev_err(&client->dev, "cannot register backlight dev (%ld)\n", + PTR_ERR(dcon->bl_dev)); + dcon->bl_dev = NULL; + } + + register_reboot_notifier(&dcon->reboot_nb); + atomic_notifier_chain_register(&panic_notifier_list, &dcon_panic_nb); + fb_register_client(&dcon->fbevent_nb); + + return 0; + + ecreate: + for (j = 0; j < i; j++) + device_remove_file(&dcon_device->dev, &dcon_device_files[j]); + edev: + platform_device_unregister(dcon_device); + dcon_device = NULL; + eirq: + free_irq(DCON_IRQ, dcon); + einit: + kfree(dcon); + return rc; +} + +static int dcon_remove(struct i2c_client *client) +{ + struct dcon_priv *dcon = i2c_get_clientdata(client); + + fb_unregister_client(&dcon->fbevent_nb); + unregister_reboot_notifier(&dcon->reboot_nb); + atomic_notifier_chain_unregister(&panic_notifier_list, &dcon_panic_nb); + + free_irq(DCON_IRQ, dcon); + + if (dcon->bl_dev) + backlight_device_unregister(dcon->bl_dev); + + if (dcon_device != NULL) + platform_device_unregister(dcon_device); + cancel_work_sync(&dcon->switch_source); + + kfree(dcon); + + return 0; +} + +#ifdef CONFIG_PM +static int dcon_suspend(struct i2c_client *client, pm_message_t state) +{ + struct dcon_priv *dcon = i2c_get_clientdata(client); + + if (!dcon->asleep) { + /* Set up the DCON to have the source */ + dcon_set_source_sync(dcon, DCON_SOURCE_DCON); + } + + return 0; +} + +static int dcon_resume(struct i2c_client *client) +{ + struct dcon_priv *dcon = i2c_get_clientdata(client); + + if (!dcon->asleep) { + dcon_bus_stabilize(dcon, 0); + dcon_set_source(dcon, DCON_SOURCE_CPU); + } + + return 0; +} + +#endif + + +irqreturn_t dcon_interrupt(int irq, void *id) +{ + struct dcon_priv *dcon = id; + u8 status; + + if (pdata->read_status(&status)) + return IRQ_NONE; + + switch (status & 3) { + case 3: + printk(KERN_DEBUG "olpc-dcon: DCONLOAD_MISSED interrupt\n"); + break; + + case 2: /* switch to DCON mode */ + case 1: /* switch to CPU mode */ + dcon->switched = true; + getnstimeofday(&dcon->irq_time); + wake_up(&dcon_wait_queue); + break; + + case 0: + /* workaround resume case: the DCON (on 1.5) doesn't + * ever assert status 0x01 when switching to CPU mode + * during resume. this is because DCONLOAD is de-asserted + * _immediately_ upon exiting S3, so the actual release + * of the DCON happened long before this point. + * see http://dev.laptop.org/ticket/9869 + */ + if (dcon->curr_src != dcon->pending_src && !dcon->switched) { + dcon->switched = true; + getnstimeofday(&dcon->irq_time); + wake_up(&dcon_wait_queue); + printk(KERN_DEBUG "olpc-dcon: switching w/ status 0/0\n"); + } else { + printk(KERN_DEBUG "olpc-dcon: scanline interrupt w/CPU\n"); + } + } + + return IRQ_HANDLED; +} + +static const struct i2c_device_id dcon_idtable[] = { + { "olpc_dcon", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, dcon_idtable); + +struct i2c_driver dcon_driver = { + .driver = { + .name = "olpc_dcon", + }, + .class = I2C_CLASS_DDC | I2C_CLASS_HWMON, + .id_table = dcon_idtable, + .probe = dcon_probe, + .remove = __devexit_p(dcon_remove), + .detect = dcon_detect, + .address_list = normal_i2c, +#ifdef CONFIG_PM + .suspend = dcon_suspend, + .resume = dcon_resume, +#endif +}; + +static int __init olpc_dcon_init(void) +{ +#ifdef CONFIG_FB_OLPC_DCON_1_5 + /* XO-1.5 */ + if (olpc_board_at_least(olpc_board(0xd0))) + pdata = &dcon_pdata_xo_1_5; +#endif +#ifdef CONFIG_FB_OLPC_DCON_1 + if (!pdata) + pdata = &dcon_pdata_xo_1; +#endif + + return i2c_add_driver(&dcon_driver); +} + +static void __exit olpc_dcon_exit(void) +{ + i2c_del_driver(&dcon_driver); +} + +module_init(olpc_dcon_init); +module_exit(olpc_dcon_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/olpc_dcon/olpc_dcon.h b/drivers/staging/olpc_dcon/olpc_dcon.h new file mode 100644 index 00000000..167a4177 --- /dev/null +++ b/drivers/staging/olpc_dcon/olpc_dcon.h @@ -0,0 +1,102 @@ +#ifndef OLPC_DCON_H_ +#define OLPC_DCON_H_ + +#include +#include + +/* DCON registers */ + +#define DCON_REG_ID 0 +#define DCON_REG_MODE 1 + +#define MODE_PASSTHRU (1<<0) +#define MODE_SLEEP (1<<1) +#define MODE_SLEEP_AUTO (1<<2) +#define MODE_BL_ENABLE (1<<3) +#define MODE_BLANK (1<<4) +#define MODE_CSWIZZLE (1<<5) +#define MODE_COL_AA (1<<6) +#define MODE_MONO_LUMA (1<<7) +#define MODE_SCAN_INT (1<<8) +#define MODE_CLOCKDIV (1<<9) +#define MODE_DEBUG (1<<14) +#define MODE_SELFTEST (1<<15) + +#define DCON_REG_HRES 2 +#define DCON_REG_HTOTAL 3 +#define DCON_REG_HSYNC_WIDTH 4 +#define DCON_REG_VRES 5 +#define DCON_REG_VTOTAL 6 +#define DCON_REG_VSYNC_WIDTH 7 +#define DCON_REG_TIMEOUT 8 +#define DCON_REG_SCAN_INT 9 +#define DCON_REG_BRIGHT 10 + +/* Status values */ + +#define DCONSTAT_SCANINT 0 +#define DCONSTAT_SCANINT_DCON 1 +#define DCONSTAT_DISPLAYLOAD 2 +#define DCONSTAT_MISSED 3 + +/* Source values */ + +#define DCON_SOURCE_DCON 0 +#define DCON_SOURCE_CPU 1 + +/* Interrupt */ +#define DCON_IRQ 6 + +struct dcon_priv { + struct i2c_client *client; + struct fb_info *fbinfo; + struct backlight_device *bl_dev; + + struct work_struct switch_source; + struct notifier_block reboot_nb; + struct notifier_block fbevent_nb; + + /* Shadow register for the DCON_REG_MODE register */ + u8 disp_mode; + + /* The current backlight value - this saves us some smbus traffic */ + u8 bl_val; + + /* Current source, initialized at probe time */ + int curr_src; + + /* Desired source */ + int pending_src; + + /* Variables used during switches */ + bool switched; + struct timespec irq_time; + struct timespec load_time; + + /* Current output type; true == mono, false == color */ + bool mono; + bool asleep; + /* This get set while controlling fb blank state from the driver */ + bool ignore_fb_events; +}; + +struct dcon_platform_data { + int (*init)(struct dcon_priv *); + void (*bus_stabilize_wiggle)(void); + void (*set_dconload)(int); + int (*read_status)(u8 *); +}; + +#include + +extern irqreturn_t dcon_interrupt(int irq, void *id); + +#ifdef CONFIG_FB_OLPC_DCON_1 +extern struct dcon_platform_data dcon_pdata_xo_1; +#endif + +#ifdef CONFIG_FB_OLPC_DCON_1_5 +extern struct dcon_platform_data dcon_pdata_xo_1_5; +#endif + +#endif diff --git a/drivers/staging/olpc_dcon/olpc_dcon_xo_1.c b/drivers/staging/olpc_dcon/olpc_dcon_xo_1.c new file mode 100644 index 00000000..cb6ce0cf --- /dev/null +++ b/drivers/staging/olpc_dcon/olpc_dcon_xo_1.c @@ -0,0 +1,202 @@ +/* + * Mainly by David Woodhouse, somewhat modified by Jordan Crouse + * + * Copyright © 2006-2007 Red Hat, Inc. + * Copyright © 2006-2007 Advanced Micro Devices, Inc. + * Copyright © 2009 VIA Technology, Inc. + * Copyright (c) 2010 Andres Salomon + * + * This program is free software. You can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + */ +#include +#include +#include +#include + +#include "olpc_dcon.h" + +static int dcon_init_xo_1(struct dcon_priv *dcon) +{ + unsigned char lob; + + if (gpio_request(OLPC_GPIO_DCON_STAT0, "OLPC-DCON")) { + printk(KERN_ERR "olpc-dcon: failed to request STAT0 GPIO\n"); + return -EIO; + } + if (gpio_request(OLPC_GPIO_DCON_STAT1, "OLPC-DCON")) { + printk(KERN_ERR "olpc-dcon: failed to request STAT1 GPIO\n"); + goto err_gp_stat1; + } + if (gpio_request(OLPC_GPIO_DCON_IRQ, "OLPC-DCON")) { + printk(KERN_ERR "olpc-dcon: failed to request IRQ GPIO\n"); + goto err_gp_irq; + } + if (gpio_request(OLPC_GPIO_DCON_LOAD, "OLPC-DCON")) { + printk(KERN_ERR "olpc-dcon: failed to request LOAD GPIO\n"); + goto err_gp_load; + } + if (gpio_request(OLPC_GPIO_DCON_BLANK, "OLPC-DCON")) { + printk(KERN_ERR "olpc-dcon: failed to request BLANK GPIO\n"); + goto err_gp_blank; + } + + /* Turn off the event enable for GPIO7 just to be safe */ + cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_EVENTS_ENABLE); + + /* + * Determine the current state by reading the GPIO bit; earlier + * stages of the boot process have established the state. + * + * Note that we read GPIO_OUPUT_VAL rather than GPIO_READ_BACK here; + * this is because OFW will disable input for the pin and set a value.. + * READ_BACK will only contain a valid value if input is enabled and + * then a value is set. So, future readings of the pin can use + * READ_BACK, but the first one cannot. Awesome, huh? + */ + dcon->curr_src = cs5535_gpio_isset(OLPC_GPIO_DCON_LOAD, GPIO_OUTPUT_VAL) + ? DCON_SOURCE_CPU + : DCON_SOURCE_DCON; + dcon->pending_src = dcon->curr_src; + + /* Set the directions for the GPIO pins */ + gpio_direction_input(OLPC_GPIO_DCON_STAT0); + gpio_direction_input(OLPC_GPIO_DCON_STAT1); + gpio_direction_input(OLPC_GPIO_DCON_IRQ); + gpio_direction_input(OLPC_GPIO_DCON_BLANK); + gpio_direction_output(OLPC_GPIO_DCON_LOAD, + dcon->curr_src == DCON_SOURCE_CPU); + + /* Set up the interrupt mappings */ + + /* Set the IRQ to pair 2 */ + cs5535_gpio_setup_event(OLPC_GPIO_DCON_IRQ, 2, 0); + + /* Enable group 2 to trigger the DCON interrupt */ + cs5535_gpio_set_irq(2, DCON_IRQ); + + /* Select edge level for interrupt (in PIC) */ + lob = inb(0x4d0); + lob &= ~(1 << DCON_IRQ); + outb(lob, 0x4d0); + + /* Register the interrupt handler */ + if (request_irq(DCON_IRQ, &dcon_interrupt, 0, "DCON", dcon)) { + printk(KERN_ERR "olpc-dcon: failed to request DCON's irq\n"); + goto err_req_irq; + } + + /* Clear INV_EN for GPIO7 (DCONIRQ) */ + cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_INVERT); + + /* Enable filter for GPIO12 (DCONBLANK) */ + cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_INPUT_FILTER); + + /* Disable filter for GPIO7 */ + cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_FILTER); + + /* Disable event counter for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */ + cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_EVENT_COUNT); + cs5535_gpio_clear(OLPC_GPIO_DCON_BLANK, GPIO_INPUT_EVENT_COUNT); + + /* Add GPIO12 to the Filter Event Pair #7 */ + cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_FE7_SEL); + + /* Turn off negative Edge Enable for GPIO12 */ + cs5535_gpio_clear(OLPC_GPIO_DCON_BLANK, GPIO_NEGATIVE_EDGE_EN); + + /* Enable negative Edge Enable for GPIO7 */ + cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_EN); + + /* Zero the filter amount for Filter Event Pair #7 */ + cs5535_gpio_set(0, GPIO_FLTR7_AMOUNT); + + /* Clear the negative edge status for GPIO7 and GPIO12 */ + cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_STS); + cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_NEGATIVE_EDGE_STS); + + /* FIXME: Clear the posiitive status as well, just to be sure */ + cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_POSITIVE_EDGE_STS); + cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_POSITIVE_EDGE_STS); + + /* Enable events for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */ + cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_EVENTS_ENABLE); + cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_EVENTS_ENABLE); + + return 0; + +err_req_irq: + gpio_free(OLPC_GPIO_DCON_BLANK); +err_gp_blank: + gpio_free(OLPC_GPIO_DCON_LOAD); +err_gp_load: + gpio_free(OLPC_GPIO_DCON_IRQ); +err_gp_irq: + gpio_free(OLPC_GPIO_DCON_STAT1); +err_gp_stat1: + gpio_free(OLPC_GPIO_DCON_STAT0); + return -EIO; +} + +static void dcon_wiggle_xo_1(void) +{ + int x; + + /* + * According to HiMax, when powering the DCON up we should hold + * SMB_DATA high for 8 SMB_CLK cycles. This will force the DCON + * state machine to reset to a (sane) initial state. Mitch Bradley + * did some testing and discovered that holding for 16 SMB_CLK cycles + * worked a lot more reliably, so that's what we do here. + * + * According to the cs5536 spec, to set GPIO14 to SMB_CLK we must + * simultaneously set AUX1 IN/OUT to GPIO14; ditto for SMB_DATA and + * GPIO15. + */ + cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL); + cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_VAL); + cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_ENABLE); + cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_ENABLE); + cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX1); + cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1); + cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX2); + cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX2); + cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_INPUT_AUX1); + cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1); + + for (x = 0; x < 16; x++) { + udelay(5); + cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL); + udelay(5); + cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL); + } + udelay(5); + cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX1); + cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1); + cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_INPUT_AUX1); + cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1); +} + +static void dcon_set_dconload_1(int val) +{ + gpio_set_value(OLPC_GPIO_DCON_LOAD, val); +} + +static int dcon_read_status_xo_1(u8 *status) +{ + *status = gpio_get_value(OLPC_GPIO_DCON_STAT0); + *status |= gpio_get_value(OLPC_GPIO_DCON_STAT1) << 1; + + /* Clear the negative edge status for GPIO7 */ + cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_STS); + + return 0; +} + +struct dcon_platform_data dcon_pdata_xo_1 = { + .init = dcon_init_xo_1, + .bus_stabilize_wiggle = dcon_wiggle_xo_1, + .set_dconload = dcon_set_dconload_1, + .read_status = dcon_read_status_xo_1, +}; diff --git a/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c b/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c new file mode 100644 index 00000000..69415eec --- /dev/null +++ b/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009,2010 One Laptop per Child + * + * This program is free software. You can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +/* TODO: this eventually belongs in linux/vx855.h */ +#define NR_VX855_GPI 14 +#define NR_VX855_GPO 13 +#define NR_VX855_GPIO 15 + +#define VX855_GPI(n) (n) +#define VX855_GPO(n) (NR_VX855_GPI + (n)) +#define VX855_GPIO(n) (NR_VX855_GPI + NR_VX855_GPO + (n)) + +#include "olpc_dcon.h" + +/* Hardware setup on the XO 1.5: + * DCONLOAD connects to VX855_GPIO1 (not SMBCK2) + * DCONBLANK connects to VX855_GPIO8 (not SSPICLK) unused in driver + * DCONSTAT0 connects to VX855_GPI10 (not SSPISDI) + * DCONSTAT1 connects to VX855_GPI11 (not nSSPISS) + * DCONIRQ connects to VX855_GPIO12 + * DCONSMBDATA connects to VX855 graphics CRTSPD + * DCONSMBCLK connects to VX855 graphics CRTSPCLK + */ + +#define VX855_GENL_PURPOSE_OUTPUT 0x44c /* PMIO_Rx4c-4f */ +#define VX855_GPI_STATUS_CHG 0x450 /* PMIO_Rx50 */ +#define VX855_GPI_SCI_SMI 0x452 /* PMIO_Rx52 */ +#define BIT_GPIO12 0x40 + +#define PREFIX "OLPC DCON:" + +static void dcon_clear_irq(void) +{ + /* irq status will appear in PMIO_Rx50[6] (RW1C) on gpio12 */ + outb(BIT_GPIO12, VX855_GPI_STATUS_CHG); +} + +static int dcon_was_irq(void) +{ + u_int8_t tmp; + + /* irq status will appear in PMIO_Rx50[6] on gpio12 */ + tmp = inb(VX855_GPI_STATUS_CHG); + return !!(tmp & BIT_GPIO12); + + return 0; +} + +static int dcon_init_xo_1_5(struct dcon_priv *dcon) +{ + unsigned int irq; + u_int8_t tmp; + struct pci_dev *pdev; + + pdev = pci_get_device(PCI_VENDOR_ID_VIA, + PCI_DEVICE_ID_VIA_VX855, NULL); + if (!pdev) { + printk(KERN_ERR "cannot find VX855 PCI ID\n"); + return 1; + } + + pci_read_config_byte(pdev, 0x95, &tmp); + pci_write_config_byte(pdev, 0x95, tmp|0x0c); + + /* Set GPIO8 to GPIO mode, not SSPICLK */ + pci_read_config_byte(pdev, 0xe3, &tmp); + pci_write_config_byte(pdev, 0xe3, tmp | 0x04); + + /* Set GPI10/GPI11 to GPI mode, not SSPISDI/SSPISS */ + pci_read_config_byte(pdev, 0xe4, &tmp); + pci_write_config_byte(pdev, 0xe4, tmp|0x08); + + /* clear PMU_RxE1[6] to select SCI on GPIO12 */ + /* clear PMU_RxE0[6] to choose falling edge */ + pci_read_config_byte(pdev, 0xe1, &tmp); + pci_write_config_byte(pdev, 0xe1, tmp & ~BIT_GPIO12); + pci_read_config_byte(pdev, 0xe0, &tmp); + pci_write_config_byte(pdev, 0xe0, tmp & ~BIT_GPIO12); + + dcon_clear_irq(); + + /* set PMIO_Rx52[6] to enable SCI/SMI on gpio12 */ + outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI); + + /* Determine the current state of DCONLOAD, likely set by firmware */ + /* GPIO1 */ + dcon->curr_src = (inl(VX855_GENL_PURPOSE_OUTPUT) & 0x1000) ? + DCON_SOURCE_CPU : DCON_SOURCE_DCON; + dcon->pending_src = dcon->curr_src; + + pci_dev_put(pdev); + + /* we're sharing the IRQ with ACPI */ + irq = acpi_gbl_FADT.sci_interrupt; + if (request_irq(irq, &dcon_interrupt, IRQF_SHARED, "DCON", dcon)) { + printk(KERN_ERR PREFIX "DCON (IRQ%d) allocation failed\n", irq); + return 1; + } + + return 0; +} + +static void set_i2c_line(int sda, int scl) +{ + unsigned char tmp; + unsigned int port = 0x26; + + /* FIXME: This directly accesses the CRT GPIO controller !!! */ + outb(port, 0x3c4); + tmp = inb(0x3c5); + + if (scl) + tmp |= 0x20; + else + tmp &= ~0x20; + + if (sda) + tmp |= 0x10; + else + tmp &= ~0x10; + + tmp |= 0x01; + + outb(port, 0x3c4); + outb(tmp, 0x3c5); +} + + +static void dcon_wiggle_xo_1_5(void) +{ + int x; + + /* + * According to HiMax, when powering the DCON up we should hold + * SMB_DATA high for 8 SMB_CLK cycles. This will force the DCON + * state machine to reset to a (sane) initial state. Mitch Bradley + * did some testing and discovered that holding for 16 SMB_CLK cycles + * worked a lot more reliably, so that's what we do here. + */ + set_i2c_line(1, 1); + + for (x = 0; x < 16; x++) { + udelay(5); + set_i2c_line(1, 0); + udelay(5); + set_i2c_line(1, 1); + } + udelay(5); + + /* set PMIO_Rx52[6] to enable SCI/SMI on gpio12 */ + outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI); +} + +static void dcon_set_dconload_xo_1_5(int val) +{ + gpio_set_value(VX855_GPIO(1), val); +} + +static int dcon_read_status_xo_1_5(u8 *status) +{ + if (!dcon_was_irq()) + return -1; + + /* i believe this is the same as "inb(0x44b) & 3" */ + *status = gpio_get_value(VX855_GPI(10)); + *status |= gpio_get_value(VX855_GPI(11)) << 1; + + dcon_clear_irq(); + + return 0; +} + +struct dcon_platform_data dcon_pdata_xo_1_5 = { + .init = dcon_init_xo_1_5, + .bus_stabilize_wiggle = dcon_wiggle_xo_1_5, + .set_dconload = dcon_set_dconload_xo_1_5, + .read_status = dcon_read_status_xo_1_5, +}; diff --git a/drivers/staging/omapdrm/Kconfig b/drivers/staging/omapdrm/Kconfig new file mode 100644 index 00000000..81a7cba4 --- /dev/null +++ b/drivers/staging/omapdrm/Kconfig @@ -0,0 +1,25 @@ + +config DRM_OMAP + tristate "OMAP DRM" + depends on DRM && !CONFIG_FB_OMAP2 + depends on ARCH_OMAP2PLUS + select DRM_KMS_HELPER + select OMAP2_DSS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + default n + help + DRM display driver for OMAP2/3/4 based boards. + +config DRM_OMAP_NUM_CRTCS + int "Number of CRTCs" + range 1 10 + default 1 if ARCH_OMAP2 || ARCH_OMAP3 + default 2 if ARCH_OMAP4 + depends on DRM_OMAP + help + Select the number of video overlays which can be used as framebuffers. + The remaining overlays are reserved for video. + diff --git a/drivers/staging/omapdrm/Makefile b/drivers/staging/omapdrm/Makefile new file mode 100644 index 00000000..d9cdc120 --- /dev/null +++ b/drivers/staging/omapdrm/Makefile @@ -0,0 +1,22 @@ +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) +# + +ccflags-y := -Iinclude/drm -Werror +omapdrm-y := omap_drv.o \ + omap_debugfs.o \ + omap_crtc.o \ + omap_plane.o \ + omap_encoder.o \ + omap_connector.o \ + omap_fb.o \ + omap_fbdev.o \ + omap_gem.o \ + omap_dmm_tiler.o \ + tcm-sita.o + +# temporary: +omapdrm-y += omap_gem_helpers.o + +obj-$(CONFIG_DRM_OMAP) += omapdrm.o diff --git a/drivers/staging/omapdrm/TODO b/drivers/staging/omapdrm/TODO new file mode 100644 index 00000000..55b18377 --- /dev/null +++ b/drivers/staging/omapdrm/TODO @@ -0,0 +1,38 @@ +TODO +. check error handling/cleanup paths +. add drm_plane / overlay support +. add video decode/encode support (via syslink3 + codec-engine) +. still some rough edges with flipping.. event back to userspace should + really come after VSYNC interrupt +. where should we do eviction (detatch_pages())? We aren't necessarily + accessing the pages via a GART, so maybe we need some other threshold + to put a cap on the # of pages that can be pin'd. (It is mostly only + of interest in case you have a swap partition/file.. which a lot of + these devices do not.. but it doesn't hurt for the driver to do the + right thing anyways.) + . Use mm_shrinker to trigger unpinning pages. Need to figure out how + to handle next issue first (I think?) + . Note TTM already has some mm_shrinker stuff.. maybe an argument to + move to TTM? Or maybe something that could be factored out in common? +. GEM/shmem backed pages can have existing mappings (kernel linear map, + etc..), which isn't really ideal. +. Revisit GEM sync object infrastructure.. TTM has some framework for this + already. Possibly this could be refactored out and made more common? + There should be some way to do this with less wheel-reinvention. +. Review DSS vs KMS mismatches. The omap_dss_device is sort of part encoder, + part connector. Which results in a bit of duct tape to fwd calls from + encoder to connector. Possibly this could be done a bit better. +. Solve PM sequencing on resume. DMM/TILER must be reloaded before any + access is made from any component in the system. Which means on suspend + CRTC's should be disabled, and on resume the LUT should be reprogrammed + before CRTC's are re-enabled, to prevent DSS from trying to DMA from a + buffer mapped in DMM/TILER before LUT is reloaded. +. Add debugfs information for DMM/TILER + +Userspace: +. git://github.com/robclark/xf86-video-omap.git + +Currently tested on +. OMAP3530 beagleboard +. OMAP4430 pandaboard +. OMAP4460 pandaboard diff --git a/drivers/staging/omapdrm/omap_connector.c b/drivers/staging/omapdrm/omap_connector.c new file mode 100644 index 00000000..5e2856c0 --- /dev/null +++ b/drivers/staging/omapdrm/omap_connector.c @@ -0,0 +1,371 @@ +/* + * drivers/staging/omapdrm/omap_connector.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +/* + * connector funcs + */ + +#define to_omap_connector(x) container_of(x, struct omap_connector, base) + +struct omap_connector { + struct drm_connector base; + struct omap_dss_device *dssdev; +}; + +static inline void copy_timings_omap_to_drm(struct drm_display_mode *mode, + struct omap_video_timings *timings) +{ + mode->clock = timings->pixel_clock; + + mode->hdisplay = timings->x_res; + mode->hsync_start = mode->hdisplay + timings->hfp; + mode->hsync_end = mode->hsync_start + timings->hsw; + mode->htotal = mode->hsync_end + timings->hbp; + + mode->vdisplay = timings->y_res; + mode->vsync_start = mode->vdisplay + timings->vfp; + mode->vsync_end = mode->vsync_start + timings->vsw; + mode->vtotal = mode->vsync_end + timings->vbp; + + /* note: whether or not it is interlaced, +/- h/vsync, etc, + * which should be set in the mode flags, is not exposed in + * the omap_video_timings struct.. but hdmi driver tracks + * those separately so all we have to have to set the mode + * is the way to recover these timings values, and the + * omap_dss_driver would do the rest. + */ +} + +static inline void copy_timings_drm_to_omap(struct omap_video_timings *timings, + struct drm_display_mode *mode) +{ + timings->pixel_clock = mode->clock; + + timings->x_res = mode->hdisplay; + timings->hfp = mode->hsync_start - mode->hdisplay; + timings->hsw = mode->hsync_end - mode->hsync_start; + timings->hbp = mode->htotal - mode->hsync_end; + + timings->y_res = mode->vdisplay; + timings->vfp = mode->vsync_start - mode->vdisplay; + timings->vsw = mode->vsync_end - mode->vsync_start; + timings->vbp = mode->vtotal - mode->vsync_end; +} + +static void omap_connector_dpms(struct drm_connector *connector, int mode) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + int old_dpms; + + DBG("%s: %d", dssdev->name, mode); + + old_dpms = connector->dpms; + + /* from off to on, do from crtc to connector */ + if (mode < old_dpms) + drm_helper_connector_dpms(connector, mode); + + if (mode == DRM_MODE_DPMS_ON) { + /* store resume info for suspended displays */ + switch (dssdev->state) { + case OMAP_DSS_DISPLAY_SUSPENDED: + dssdev->activate_after_resume = true; + break; + case OMAP_DSS_DISPLAY_DISABLED: { + int ret = dssdev->driver->enable(dssdev); + if (ret) { + DBG("%s: failed to enable: %d", + dssdev->name, ret); + dssdev->driver->disable(dssdev); + } + break; + } + default: + break; + } + } else { + /* TODO */ + } + + /* from on to off, do from connector to crtc */ + if (mode > old_dpms) + drm_helper_connector_dpms(connector, mode); +} + +enum drm_connector_status omap_connector_detect( + struct drm_connector *connector, bool force) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + enum drm_connector_status ret; + + if (dssdrv->detect) { + if (dssdrv->detect(dssdev)) { + ret = connector_status_connected; + } else { + ret = connector_status_disconnected; + } + } else { + ret = connector_status_unknown; + } + + VERB("%s: %d (force=%d)", omap_connector->dssdev->name, ret, force); + + return ret; +} + +static void omap_connector_destroy(struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + + dssdev->driver->disable(dssdev); + + DBG("%s", omap_connector->dssdev->name); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(omap_connector); + + omap_dss_put_device(dssdev); +} + +#define MAX_EDID 512 + +static int omap_connector_get_modes(struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct drm_device *dev = connector->dev; + int n = 0; + + DBG("%s", omap_connector->dssdev->name); + + /* if display exposes EDID, then we parse that in the normal way to + * build table of supported modes.. otherwise (ie. fixed resolution + * LCD panels) we just return a single mode corresponding to the + * currently configured timings: + */ + if (dssdrv->read_edid) { + void *edid = kzalloc(MAX_EDID, GFP_KERNEL); + + if ((dssdrv->read_edid(dssdev, edid, MAX_EDID) > 0) && + drm_edid_is_valid(edid)) { + drm_mode_connector_update_edid_property( + connector, edid); + n = drm_add_edid_modes(connector, edid); + kfree(connector->display_info.raw_edid); + connector->display_info.raw_edid = edid; + } else { + drm_mode_connector_update_edid_property( + connector, NULL); + connector->display_info.raw_edid = NULL; + kfree(edid); + } + } else { + struct drm_display_mode *mode = drm_mode_create(dev); + struct omap_video_timings timings; + + dssdrv->get_timings(dssdev, &timings); + + copy_timings_omap_to_drm(mode, &timings); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + n = 1; + } + + return n; +} + +static int omap_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct omap_video_timings timings = {0}; + struct drm_device *dev = connector->dev; + struct drm_display_mode *new_mode; + int ret = MODE_BAD; + + copy_timings_drm_to_omap(&timings, mode); + mode->vrefresh = drm_mode_vrefresh(mode); + + if (!dssdrv->check_timings(dssdev, &timings)) { + /* check if vrefresh is still valid */ + new_mode = drm_mode_duplicate(dev, mode); + new_mode->clock = timings.pixel_clock; + new_mode->vrefresh = 0; + if (mode->vrefresh == drm_mode_vrefresh(new_mode)) + ret = MODE_OK; + drm_mode_destroy(dev, new_mode); + } + + DBG("connector: mode %s: " + "%d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", + (ret == MODE_OK) ? "valid" : "invalid", + mode->base.id, mode->name, mode->vrefresh, mode->clock, + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, mode->type, mode->flags); + + return ret; +} + +struct drm_encoder *omap_connector_attached_encoder( + struct drm_connector *connector) +{ + int i; + struct omap_connector *omap_connector = to_omap_connector(connector); + + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + struct drm_mode_object *obj; + + if (connector->encoder_ids[i] == 0) + break; + + obj = drm_mode_object_find(connector->dev, + connector->encoder_ids[i], + DRM_MODE_OBJECT_ENCODER); + + if (obj) { + struct drm_encoder *encoder = obj_to_encoder(obj); + struct omap_overlay_manager *mgr = + omap_encoder_get_manager(encoder); + DBG("%s: found %s", omap_connector->dssdev->name, + mgr->name); + return encoder; + } + } + + DBG("%s: no encoder", omap_connector->dssdev->name); + + return NULL; +} + +static const struct drm_connector_funcs omap_connector_funcs = { + .dpms = omap_connector_dpms, + .detect = omap_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = omap_connector_destroy, +}; + +static const struct drm_connector_helper_funcs omap_connector_helper_funcs = { + .get_modes = omap_connector_get_modes, + .mode_valid = omap_connector_mode_valid, + .best_encoder = omap_connector_attached_encoder, +}; + +/* called from encoder when mode is set, to propagate settings to the dssdev */ +void omap_connector_mode_set(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_device *dev = connector->dev; + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct omap_video_timings timings; + + copy_timings_drm_to_omap(&timings, mode); + + DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", + omap_connector->dssdev->name, + mode->base.id, mode->name, mode->vrefresh, mode->clock, + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, mode->type, mode->flags); + + if (dssdrv->check_timings(dssdev, &timings)) { + dev_err(dev->dev, "could not set timings\n"); + return; + } + + dssdrv->set_timings(dssdev, &timings); +} + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +void omap_connector_flush(struct drm_connector *connector, + int x, int y, int w, int h) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + + /* TODO: enable when supported in dss */ + VERB("%s: %d,%d, %dx%d", omap_connector->dssdev->name, x, y, w, h); +} + +/* initialize connector */ +struct drm_connector *omap_connector_init(struct drm_device *dev, + int connector_type, struct omap_dss_device *dssdev) +{ + struct drm_connector *connector = NULL; + struct omap_connector *omap_connector; + + DBG("%s", dssdev->name); + + omap_dss_get_device(dssdev); + + omap_connector = kzalloc(sizeof(struct omap_connector), GFP_KERNEL); + if (!omap_connector) { + dev_err(dev->dev, "could not allocate connector\n"); + goto fail; + } + + omap_connector->dssdev = dssdev; + connector = &omap_connector->base; + + drm_connector_init(dev, connector, &omap_connector_funcs, + connector_type); + drm_connector_helper_add(connector, &omap_connector_helper_funcs); + +#if 0 /* enable when dss2 supports hotplug */ + if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_HPD) + connector->polled = 0; + else +#endif + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 1; + connector->doublescan_allowed = 0; + + drm_sysfs_connector_add(connector); + + return connector; + +fail: + if (connector) { + omap_connector_destroy(connector); + } + + return NULL; +} diff --git a/drivers/staging/omapdrm/omap_crtc.c b/drivers/staging/omapdrm/omap_crtc.c new file mode 100644 index 00000000..490a7f15 --- /dev/null +++ b/drivers/staging/omapdrm/omap_crtc.c @@ -0,0 +1,248 @@ +/* + * drivers/staging/omapdrm/omap_crtc.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "omap_drv.h" + +#include "drm_mode.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +#define to_omap_crtc(x) container_of(x, struct omap_crtc, base) + +struct omap_crtc { + struct drm_crtc base; + struct drm_plane *plane; + const char *name; + int id; + + /* if there is a pending flip, these will be non-null: */ + struct drm_pending_vblank_event *event; + struct drm_framebuffer *old_fb; +}; + +static void omap_crtc_gamma_set(struct drm_crtc *crtc, + u16 *red, u16 *green, u16 *blue, uint32_t start, uint32_t size) +{ + /* not supported.. at least not yet */ +} + +static void omap_crtc_destroy(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + omap_crtc->plane->funcs->destroy(omap_crtc->plane); + drm_crtc_cleanup(crtc); + kfree(omap_crtc); +} + +static void omap_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct omap_drm_private *priv = crtc->dev->dev_private; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + int i; + + WARN_ON(omap_plane_dpms(omap_crtc->plane, mode)); + + for (i = 0; i < priv->num_planes; i++) { + struct drm_plane *plane = priv->planes[i]; + if (plane->crtc == crtc) + WARN_ON(omap_plane_dpms(plane, mode)); + } +} + +static bool omap_crtc_mode_fixup(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int omap_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, + struct drm_framebuffer *old_fb) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct drm_plane *plane = omap_crtc->plane; + + return omap_plane_mode_set(plane, crtc, crtc->fb, + 0, 0, mode->hdisplay, mode->vdisplay, + x << 16, y << 16, + mode->hdisplay << 16, mode->vdisplay << 16); +} + +static void omap_crtc_prepare(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->name); + omap_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static void omap_crtc_commit(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->name); + omap_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static int omap_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct drm_plane *plane = omap_crtc->plane; + struct drm_display_mode *mode = &crtc->mode; + + return plane->funcs->update_plane(plane, crtc, crtc->fb, + 0, 0, mode->hdisplay, mode->vdisplay, + x << 16, y << 16, + mode->hdisplay << 16, mode->vdisplay << 16); +} + +static void omap_crtc_load_lut(struct drm_crtc *crtc) +{ +} + +static void vblank_cb(void *arg) +{ + static uint32_t sequence = 0; + struct drm_crtc *crtc = arg; + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct drm_pending_vblank_event *event = omap_crtc->event; + unsigned long flags; + struct timeval now; + + WARN_ON(!event); + + omap_crtc->event = NULL; + + /* wakeup userspace */ + if (event) { + do_gettimeofday(&now); + + spin_lock_irqsave(&dev->event_lock, flags); + /* TODO: we can't yet use the vblank time accounting, + * because omapdss lower layer is the one that knows + * the irq # and registers the handler, which more or + * less defeats how drm_irq works.. for now just fake + * the sequence number and use gettimeofday.. + * + event->event.sequence = drm_vblank_count_and_time( + dev, omap_crtc->id, &now); + */ + event->event.sequence = sequence++; + event->event.tv_sec = now.tv_sec; + event->event.tv_usec = now.tv_usec; + list_add_tail(&event->base.link, + &event->base.file_priv->event_list); + wake_up_interruptible(&event->base.file_priv->event_wait); + spin_unlock_irqrestore(&dev->event_lock, flags); + } +} + +static void page_flip_cb(void *arg) +{ + struct drm_crtc *crtc = arg; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct drm_framebuffer *old_fb = omap_crtc->old_fb; + + omap_crtc->old_fb = NULL; + + omap_crtc_mode_set_base(crtc, crtc->x, crtc->y, old_fb); + + /* really we'd like to setup the callback atomically w/ setting the + * new scanout buffer to avoid getting stuck waiting an extra vblank + * cycle.. for now go for correctness and later figure out speed.. + */ + omap_plane_on_endwin(omap_crtc->plane, vblank_cb, crtc); +} + +static int omap_crtc_page_flip_locked(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + + DBG("%d -> %d", crtc->fb ? crtc->fb->base.id : -1, fb->base.id); + + if (omap_crtc->event) { + dev_err(dev->dev, "already a pending flip\n"); + return -EINVAL; + } + + omap_crtc->old_fb = crtc->fb; + omap_crtc->event = event; + crtc->fb = fb; + + omap_gem_op_async(omap_framebuffer_bo(fb, 0), OMAP_GEM_READ, + page_flip_cb, crtc); + + return 0; +} + +static const struct drm_crtc_funcs omap_crtc_funcs = { + .gamma_set = omap_crtc_gamma_set, + .set_config = drm_crtc_helper_set_config, + .destroy = omap_crtc_destroy, + .page_flip = omap_crtc_page_flip_locked, +}; + +static const struct drm_crtc_helper_funcs omap_crtc_helper_funcs = { + .dpms = omap_crtc_dpms, + .mode_fixup = omap_crtc_mode_fixup, + .mode_set = omap_crtc_mode_set, + .prepare = omap_crtc_prepare, + .commit = omap_crtc_commit, + .mode_set_base = omap_crtc_mode_set_base, + .load_lut = omap_crtc_load_lut, +}; + +/* initialize crtc */ +struct drm_crtc *omap_crtc_init(struct drm_device *dev, + struct omap_overlay *ovl, int id) +{ + struct drm_crtc *crtc = NULL; + struct omap_crtc *omap_crtc = kzalloc(sizeof(*omap_crtc), GFP_KERNEL); + + DBG("%s", ovl->name); + + if (!omap_crtc) { + dev_err(dev->dev, "could not allocate CRTC\n"); + goto fail; + } + + crtc = &omap_crtc->base; + + omap_crtc->plane = omap_plane_init(dev, ovl, (1 << id), true); + omap_crtc->plane->crtc = crtc; + omap_crtc->name = ovl->name; + omap_crtc->id = id; + + drm_crtc_init(dev, crtc, &omap_crtc_funcs); + drm_crtc_helper_add(crtc, &omap_crtc_helper_funcs); + + return crtc; + +fail: + if (crtc) { + omap_crtc_destroy(crtc); + } + return NULL; +} diff --git a/drivers/staging/omapdrm/omap_debugfs.c b/drivers/staging/omapdrm/omap_debugfs.c new file mode 100644 index 00000000..2f122e00 --- /dev/null +++ b/drivers/staging/omapdrm/omap_debugfs.c @@ -0,0 +1,137 @@ +/* + * drivers/staging/omapdrm/omap_debugfs.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "omap_drv.h" +#include "omap_dmm_tiler.h" + +#include "drm_fb_helper.h" + + +#ifdef CONFIG_DEBUG_FS + +static int gem_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct omap_drm_private *priv = dev->dev_private; + int ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) + return ret; + + seq_printf(m, "All Objects:\n"); + omap_gem_describe_objects(&priv->obj_list, m); + + mutex_unlock(&dev->struct_mutex); + + return 0; +} + +static int mm_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + return drm_mm_dump_table(m, dev->mm_private); +} + +static int fb_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct omap_drm_private *priv = dev->dev_private; + struct drm_framebuffer *fb; + int ret; + + ret = mutex_lock_interruptible(&dev->mode_config.mutex); + if (ret) + return ret; + + ret = mutex_lock_interruptible(&dev->struct_mutex); + if (ret) { + mutex_unlock(&dev->mode_config.mutex); + return ret; + } + + seq_printf(m, "fbcon "); + omap_framebuffer_describe(priv->fbdev->fb, m); + + list_for_each_entry(fb, &dev->mode_config.fb_list, head) { + if (fb == priv->fbdev->fb) + continue; + + seq_printf(m, "user "); + omap_framebuffer_describe(fb, m); + } + + mutex_unlock(&dev->struct_mutex); + mutex_unlock(&dev->mode_config.mutex); + + return 0; +} + +/* list of debufs files that are applicable to all devices */ +static struct drm_info_list omap_debugfs_list[] = { + {"gem", gem_show, 0}, + {"mm", mm_show, 0}, + {"fb", fb_show, 0}, +}; + +/* list of debugfs files that are specific to devices with dmm/tiler */ +static struct drm_info_list omap_dmm_debugfs_list[] = { + {"tiler_map", tiler_map_show, 0}, +}; + +int omap_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *dev = minor->dev; + int ret; + + ret = drm_debugfs_create_files(omap_debugfs_list, + ARRAY_SIZE(omap_debugfs_list), + minor->debugfs_root, minor); + + if (ret) { + dev_err(dev->dev, "could not install omap_debugfs_list\n"); + return ret; + } + + if (dmm_is_available()) + ret = drm_debugfs_create_files(omap_dmm_debugfs_list, + ARRAY_SIZE(omap_dmm_debugfs_list), + minor->debugfs_root, minor); + + if (ret) { + dev_err(dev->dev, "could not install omap_dmm_debugfs_list\n"); + return ret; + } + + return ret; +} + +void omap_debugfs_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(omap_debugfs_list, + ARRAY_SIZE(omap_debugfs_list), minor); + if (dmm_is_available()) + drm_debugfs_remove_files(omap_dmm_debugfs_list, + ARRAY_SIZE(omap_dmm_debugfs_list), minor); +} + +#endif diff --git a/drivers/staging/omapdrm/omap_dmm_priv.h b/drivers/staging/omapdrm/omap_dmm_priv.h new file mode 100644 index 00000000..2f529ab4 --- /dev/null +++ b/drivers/staging/omapdrm/omap_dmm_priv.h @@ -0,0 +1,187 @@ +/* + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Rob Clark + * Andy Gross + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef OMAP_DMM_PRIV_H +#define OMAP_DMM_PRIV_H + +#define DMM_REVISION 0x000 +#define DMM_HWINFO 0x004 +#define DMM_LISA_HWINFO 0x008 +#define DMM_DMM_SYSCONFIG 0x010 +#define DMM_LISA_LOCK 0x01C +#define DMM_LISA_MAP__0 0x040 +#define DMM_LISA_MAP__1 0x044 +#define DMM_TILER_HWINFO 0x208 +#define DMM_TILER_OR__0 0x220 +#define DMM_TILER_OR__1 0x224 +#define DMM_PAT_HWINFO 0x408 +#define DMM_PAT_GEOMETRY 0x40C +#define DMM_PAT_CONFIG 0x410 +#define DMM_PAT_VIEW__0 0x420 +#define DMM_PAT_VIEW__1 0x424 +#define DMM_PAT_VIEW_MAP__0 0x440 +#define DMM_PAT_VIEW_MAP_BASE 0x460 +#define DMM_PAT_IRQ_EOI 0x478 +#define DMM_PAT_IRQSTATUS_RAW 0x480 +#define DMM_PAT_IRQSTATUS 0x490 +#define DMM_PAT_IRQENABLE_SET 0x4A0 +#define DMM_PAT_IRQENABLE_CLR 0x4B0 +#define DMM_PAT_STATUS__0 0x4C0 +#define DMM_PAT_STATUS__1 0x4C4 +#define DMM_PAT_STATUS__2 0x4C8 +#define DMM_PAT_STATUS__3 0x4CC +#define DMM_PAT_DESCR__0 0x500 +#define DMM_PAT_DESCR__1 0x510 +#define DMM_PAT_DESCR__2 0x520 +#define DMM_PAT_DESCR__3 0x530 +#define DMM_PEG_HWINFO 0x608 +#define DMM_PEG_PRIO 0x620 +#define DMM_PEG_PRIO_PAT 0x640 + +#define DMM_IRQSTAT_DST (1<<0) +#define DMM_IRQSTAT_LST (1<<1) +#define DMM_IRQSTAT_ERR_INV_DSC (1<<2) +#define DMM_IRQSTAT_ERR_INV_DATA (1<<3) +#define DMM_IRQSTAT_ERR_UPD_AREA (1<<4) +#define DMM_IRQSTAT_ERR_UPD_CTRL (1<<5) +#define DMM_IRQSTAT_ERR_UPD_DATA (1<<6) +#define DMM_IRQSTAT_ERR_LUT_MISS (1<<7) + +#define DMM_IRQSTAT_ERR_MASK (DMM_IRQ_STAT_ERR_INV_DSC | \ + DMM_IRQ_STAT_ERR_INV_DATA | \ + DMM_IRQ_STAT_ERR_UPD_AREA | \ + DMM_IRQ_STAT_ERR_UPD_CTRL | \ + DMM_IRQ_STAT_ERR_UPD_DATA | \ + DMM_IRQ_STAT_ERR_LUT_MISS) + +#define DMM_PATSTATUS_READY (1<<0) +#define DMM_PATSTATUS_VALID (1<<1) +#define DMM_PATSTATUS_RUN (1<<2) +#define DMM_PATSTATUS_DONE (1<<3) +#define DMM_PATSTATUS_LINKED (1<<4) +#define DMM_PATSTATUS_BYPASSED (1<<7) +#define DMM_PATSTATUS_ERR_INV_DESCR (1<<10) +#define DMM_PATSTATUS_ERR_INV_DATA (1<<11) +#define DMM_PATSTATUS_ERR_UPD_AREA (1<<12) +#define DMM_PATSTATUS_ERR_UPD_CTRL (1<<13) +#define DMM_PATSTATUS_ERR_UPD_DATA (1<<14) +#define DMM_PATSTATUS_ERR_ACCESS (1<<15) + +/* note: don't treat DMM_PATSTATUS_ERR_ACCESS as an error */ +#define DMM_PATSTATUS_ERR (DMM_PATSTATUS_ERR_INV_DESCR | \ + DMM_PATSTATUS_ERR_INV_DATA | \ + DMM_PATSTATUS_ERR_UPD_AREA | \ + DMM_PATSTATUS_ERR_UPD_CTRL | \ + DMM_PATSTATUS_ERR_UPD_DATA) + + + +enum { + PAT_STATUS, + PAT_DESCR +}; + +struct pat_ctrl { + u32 start:4; + u32 dir:4; + u32 lut_id:8; + u32 sync:12; + u32 ini:4; +}; + +struct pat { + uint32_t next_pa; + struct pat_area area; + struct pat_ctrl ctrl; + uint32_t data_pa; +}; + +#define DMM_FIXED_RETRY_COUNT 1000 + +/* create refill buffer big enough to refill all slots, plus 3 descriptors.. + * 3 descriptors is probably the worst-case for # of 2d-slices in a 1d area, + * but I guess you don't hit that worst case at the same time as full area + * refill + */ +#define DESCR_SIZE 128 +#define REFILL_BUFFER_SIZE ((4 * 128 * 256) + (3 * DESCR_SIZE)) + +struct dmm; + +struct dmm_txn { + void *engine_handle; + struct tcm *tcm; + + uint8_t *current_va; + dma_addr_t current_pa; + + struct pat *last_pat; +}; + +struct refill_engine { + int id; + struct dmm *dmm; + struct tcm *tcm; + + uint8_t *refill_va; + dma_addr_t refill_pa; + + /* only one trans per engine for now */ + struct dmm_txn txn; + + /* offset to lut associated with container */ + u32 *lut_offset; + + wait_queue_head_t wait_for_refill; + + struct list_head idle_node; +}; + +struct dmm { + struct device *dev; + void __iomem *base; + int irq; + + struct page *dummy_page; + dma_addr_t dummy_pa; + + void *refill_va; + dma_addr_t refill_pa; + + /* refill engines */ + struct semaphore engine_sem; + struct list_head idle_head; + struct refill_engine *engines; + int num_engines; + + /* container information */ + int container_width; + int container_height; + int lut_width; + int lut_height; + int num_lut; + + /* array of LUT - TCM containers */ + struct tcm **tcm; + + /* LUT table storage */ + u32 *lut; + + /* allocation list and lock */ + struct list_head alloc_head; + spinlock_t list_lock; +}; + +#endif diff --git a/drivers/staging/omapdrm/omap_dmm_tiler.c b/drivers/staging/omapdrm/omap_dmm_tiler.c new file mode 100644 index 00000000..1ecb6a73 --- /dev/null +++ b/drivers/staging/omapdrm/omap_dmm_tiler.c @@ -0,0 +1,873 @@ +/* + * DMM IOMMU driver support functions for TI OMAP processors. + * + * Author: Rob Clark + * Andy Gross + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include /* platform_device() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "omap_dmm_tiler.h" +#include "omap_dmm_priv.h" + +#define DMM_DRIVER_NAME "dmm" + +/* mappings for associating views to luts */ +static struct tcm *containers[TILFMT_NFORMATS]; +static struct dmm *omap_dmm; + +/* Geometry table */ +#define GEOM(xshift, yshift, bytes_per_pixel) { \ + .x_shft = (xshift), \ + .y_shft = (yshift), \ + .cpp = (bytes_per_pixel), \ + .slot_w = 1 << (SLOT_WIDTH_BITS - (xshift)), \ + .slot_h = 1 << (SLOT_HEIGHT_BITS - (yshift)), \ + } + +static const struct { + uint32_t x_shft; /* unused X-bits (as part of bpp) */ + uint32_t y_shft; /* unused Y-bits (as part of bpp) */ + uint32_t cpp; /* bytes/chars per pixel */ + uint32_t slot_w; /* width of each slot (in pixels) */ + uint32_t slot_h; /* height of each slot (in pixels) */ +} geom[TILFMT_NFORMATS] = { + [TILFMT_8BIT] = GEOM(0, 0, 1), + [TILFMT_16BIT] = GEOM(0, 1, 2), + [TILFMT_32BIT] = GEOM(1, 1, 4), + [TILFMT_PAGE] = GEOM(SLOT_WIDTH_BITS, SLOT_HEIGHT_BITS, 1), +}; + + +/* lookup table for registers w/ per-engine instances */ +static const uint32_t reg[][4] = { + [PAT_STATUS] = {DMM_PAT_STATUS__0, DMM_PAT_STATUS__1, + DMM_PAT_STATUS__2, DMM_PAT_STATUS__3}, + [PAT_DESCR] = {DMM_PAT_DESCR__0, DMM_PAT_DESCR__1, + DMM_PAT_DESCR__2, DMM_PAT_DESCR__3}, +}; + +/* simple allocator to grab next 16 byte aligned memory from txn */ +static void *alloc_dma(struct dmm_txn *txn, size_t sz, dma_addr_t *pa) +{ + void *ptr; + struct refill_engine *engine = txn->engine_handle; + + /* dmm programming requires 16 byte aligned addresses */ + txn->current_pa = round_up(txn->current_pa, 16); + txn->current_va = (void *)round_up((long)txn->current_va, 16); + + ptr = txn->current_va; + *pa = txn->current_pa; + + txn->current_pa += sz; + txn->current_va += sz; + + BUG_ON((txn->current_va - engine->refill_va) > REFILL_BUFFER_SIZE); + + return ptr; +} + +/* check status and spin until wait_mask comes true */ +static int wait_status(struct refill_engine *engine, uint32_t wait_mask) +{ + struct dmm *dmm = engine->dmm; + uint32_t r = 0, err, i; + + i = DMM_FIXED_RETRY_COUNT; + while (true) { + r = readl(dmm->base + reg[PAT_STATUS][engine->id]); + err = r & DMM_PATSTATUS_ERR; + if (err) + return -EFAULT; + + if ((r & wait_mask) == wait_mask) + break; + + if (--i == 0) + return -ETIMEDOUT; + + udelay(1); + } + + return 0; +} + +irqreturn_t omap_dmm_irq_handler(int irq, void *arg) +{ + struct dmm *dmm = arg; + uint32_t status = readl(dmm->base + DMM_PAT_IRQSTATUS); + int i; + + /* ack IRQ */ + writel(status, dmm->base + DMM_PAT_IRQSTATUS); + + for (i = 0; i < dmm->num_engines; i++) { + if (status & DMM_IRQSTAT_LST) + wake_up_interruptible(&dmm->engines[i].wait_for_refill); + + status >>= 8; + } + + return IRQ_HANDLED; +} + +/** + * Get a handle for a DMM transaction + */ +static struct dmm_txn *dmm_txn_init(struct dmm *dmm, struct tcm *tcm) +{ + struct dmm_txn *txn = NULL; + struct refill_engine *engine = NULL; + + down(&dmm->engine_sem); + + /* grab an idle engine */ + spin_lock(&dmm->list_lock); + if (!list_empty(&dmm->idle_head)) { + engine = list_entry(dmm->idle_head.next, struct refill_engine, + idle_node); + list_del(&engine->idle_node); + } + spin_unlock(&dmm->list_lock); + + BUG_ON(!engine); + + txn = &engine->txn; + engine->tcm = tcm; + txn->engine_handle = engine; + txn->last_pat = NULL; + txn->current_va = engine->refill_va; + txn->current_pa = engine->refill_pa; + + return txn; +} + +/** + * Add region to DMM transaction. If pages or pages[i] is NULL, then the + * corresponding slot is cleared (ie. dummy_pa is programmed) + */ +static int dmm_txn_append(struct dmm_txn *txn, struct pat_area *area, + struct page **pages, uint32_t npages, uint32_t roll) +{ + dma_addr_t pat_pa = 0; + uint32_t *data; + struct pat *pat; + struct refill_engine *engine = txn->engine_handle; + int columns = (1 + area->x1 - area->x0); + int rows = (1 + area->y1 - area->y0); + int i = columns*rows; + u32 *lut = omap_dmm->lut + (engine->tcm->lut_id * omap_dmm->lut_width * + omap_dmm->lut_height) + + (area->y0 * omap_dmm->lut_width) + area->x0; + + pat = alloc_dma(txn, sizeof(struct pat), &pat_pa); + + if (txn->last_pat) + txn->last_pat->next_pa = (uint32_t)pat_pa; + + pat->area = *area; + pat->ctrl = (struct pat_ctrl){ + .start = 1, + .lut_id = engine->tcm->lut_id, + }; + + data = alloc_dma(txn, 4*i, &pat->data_pa); + + while (i--) { + int n = i + roll; + if (n >= npages) + n -= npages; + data[i] = (pages && pages[n]) ? + page_to_phys(pages[n]) : engine->dmm->dummy_pa; + } + + /* fill in lut with new addresses */ + for (i = 0; i < rows; i++, lut += omap_dmm->lut_width) + memcpy(lut, &data[i*columns], columns * sizeof(u32)); + + txn->last_pat = pat; + + return 0; +} + +/** + * Commit the DMM transaction. + */ +static int dmm_txn_commit(struct dmm_txn *txn, bool wait) +{ + int ret = 0; + struct refill_engine *engine = txn->engine_handle; + struct dmm *dmm = engine->dmm; + + if (!txn->last_pat) { + dev_err(engine->dmm->dev, "need at least one txn\n"); + ret = -EINVAL; + goto cleanup; + } + + txn->last_pat->next_pa = 0; + + /* write to PAT_DESCR to clear out any pending transaction */ + writel(0x0, dmm->base + reg[PAT_DESCR][engine->id]); + + /* wait for engine ready: */ + ret = wait_status(engine, DMM_PATSTATUS_READY); + if (ret) { + ret = -EFAULT; + goto cleanup; + } + + /* kick reload */ + writel(engine->refill_pa, + dmm->base + reg[PAT_DESCR][engine->id]); + + if (wait) { + if (wait_event_interruptible_timeout(engine->wait_for_refill, + wait_status(engine, DMM_PATSTATUS_READY) == 0, + msecs_to_jiffies(1)) <= 0) { + dev_err(dmm->dev, "timed out waiting for done\n"); + ret = -ETIMEDOUT; + } + } + +cleanup: + spin_lock(&dmm->list_lock); + list_add(&engine->idle_node, &dmm->idle_head); + spin_unlock(&dmm->list_lock); + + up(&omap_dmm->engine_sem); + return ret; +} + +/* + * DMM programming + */ +static int fill(struct tcm_area *area, struct page **pages, + uint32_t npages, uint32_t roll, bool wait) +{ + int ret = 0; + struct tcm_area slice, area_s; + struct dmm_txn *txn; + + txn = dmm_txn_init(omap_dmm, area->tcm); + if (IS_ERR_OR_NULL(txn)) + return PTR_ERR(txn); + + tcm_for_each_slice(slice, *area, area_s) { + struct pat_area p_area = { + .x0 = slice.p0.x, .y0 = slice.p0.y, + .x1 = slice.p1.x, .y1 = slice.p1.y, + }; + + ret = dmm_txn_append(txn, &p_area, pages, npages, roll); + if (ret) + goto fail; + + roll += tcm_sizeof(slice); + } + + ret = dmm_txn_commit(txn, wait); + +fail: + return ret; +} + +/* + * Pin/unpin + */ + +/* note: slots for which pages[i] == NULL are filled w/ dummy page + */ +int tiler_pin(struct tiler_block *block, struct page **pages, + uint32_t npages, uint32_t roll, bool wait) +{ + int ret; + + ret = fill(&block->area, pages, npages, roll, wait); + + if (ret) + tiler_unpin(block); + + return ret; +} + +int tiler_unpin(struct tiler_block *block) +{ + return fill(&block->area, NULL, 0, 0, false); +} + +/* + * Reserve/release + */ +struct tiler_block *tiler_reserve_2d(enum tiler_fmt fmt, uint16_t w, + uint16_t h, uint16_t align) +{ + struct tiler_block *block = kzalloc(sizeof(*block), GFP_KERNEL); + u32 min_align = 128; + int ret; + + BUG_ON(!validfmt(fmt)); + + /* convert width/height to slots */ + w = DIV_ROUND_UP(w, geom[fmt].slot_w); + h = DIV_ROUND_UP(h, geom[fmt].slot_h); + + /* convert alignment to slots */ + min_align = max(min_align, (geom[fmt].slot_w * geom[fmt].cpp)); + align = ALIGN(align, min_align); + align /= geom[fmt].slot_w * geom[fmt].cpp; + + block->fmt = fmt; + + ret = tcm_reserve_2d(containers[fmt], w, h, align, &block->area); + if (ret) { + kfree(block); + return 0; + } + + /* add to allocation list */ + spin_lock(&omap_dmm->list_lock); + list_add(&block->alloc_node, &omap_dmm->alloc_head); + spin_unlock(&omap_dmm->list_lock); + + return block; +} + +struct tiler_block *tiler_reserve_1d(size_t size) +{ + struct tiler_block *block = kzalloc(sizeof(*block), GFP_KERNEL); + int num_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + + if (!block) + return 0; + + block->fmt = TILFMT_PAGE; + + if (tcm_reserve_1d(containers[TILFMT_PAGE], num_pages, + &block->area)) { + kfree(block); + return 0; + } + + spin_lock(&omap_dmm->list_lock); + list_add(&block->alloc_node, &omap_dmm->alloc_head); + spin_unlock(&omap_dmm->list_lock); + + return block; +} + +/* note: if you have pin'd pages, you should have already unpin'd first! */ +int tiler_release(struct tiler_block *block) +{ + int ret = tcm_free(&block->area); + + if (block->area.tcm) + dev_err(omap_dmm->dev, "failed to release block\n"); + + spin_lock(&omap_dmm->list_lock); + list_del(&block->alloc_node); + spin_unlock(&omap_dmm->list_lock); + + kfree(block); + return ret; +} + +/* + * Utils + */ + +/* calculate the tiler space address of a pixel in a view orientation */ +static u32 tiler_get_address(u32 orient, enum tiler_fmt fmt, u32 x, u32 y) +{ + u32 x_bits, y_bits, tmp, x_mask, y_mask, alignment; + + x_bits = CONT_WIDTH_BITS - geom[fmt].x_shft; + y_bits = CONT_HEIGHT_BITS - geom[fmt].y_shft; + alignment = geom[fmt].x_shft + geom[fmt].y_shft; + + /* validate coordinate */ + x_mask = MASK(x_bits); + y_mask = MASK(y_bits); + + if (x < 0 || x > x_mask || y < 0 || y > y_mask) + return 0; + + /* account for mirroring */ + if (orient & MASK_X_INVERT) + x ^= x_mask; + if (orient & MASK_Y_INVERT) + y ^= y_mask; + + /* get coordinate address */ + if (orient & MASK_XY_FLIP) + tmp = ((x << y_bits) + y); + else + tmp = ((y << x_bits) + x); + + return TIL_ADDR((tmp << alignment), orient, fmt); +} + +dma_addr_t tiler_ssptr(struct tiler_block *block) +{ + BUG_ON(!validfmt(block->fmt)); + + return TILVIEW_8BIT + tiler_get_address(0, block->fmt, + block->area.p0.x * geom[block->fmt].slot_w, + block->area.p0.y * geom[block->fmt].slot_h); +} + +void tiler_align(enum tiler_fmt fmt, uint16_t *w, uint16_t *h) +{ + BUG_ON(!validfmt(fmt)); + *w = round_up(*w, geom[fmt].slot_w); + *h = round_up(*h, geom[fmt].slot_h); +} + +uint32_t tiler_stride(enum tiler_fmt fmt) +{ + BUG_ON(!validfmt(fmt)); + + return 1 << (CONT_WIDTH_BITS + geom[fmt].y_shft); +} + +size_t tiler_size(enum tiler_fmt fmt, uint16_t w, uint16_t h) +{ + tiler_align(fmt, &w, &h); + return geom[fmt].cpp * w * h; +} + +size_t tiler_vsize(enum tiler_fmt fmt, uint16_t w, uint16_t h) +{ + BUG_ON(!validfmt(fmt)); + return round_up(geom[fmt].cpp * w, PAGE_SIZE) * h; +} + +bool dmm_is_initialized(void) +{ + return omap_dmm ? true : false; +} + +static int omap_dmm_remove(struct platform_device *dev) +{ + struct tiler_block *block, *_block; + int i; + + if (omap_dmm) { + /* free all area regions */ + spin_lock(&omap_dmm->list_lock); + list_for_each_entry_safe(block, _block, &omap_dmm->alloc_head, + alloc_node) { + list_del(&block->alloc_node); + kfree(block); + } + spin_unlock(&omap_dmm->list_lock); + + for (i = 0; i < omap_dmm->num_lut; i++) + if (omap_dmm->tcm && omap_dmm->tcm[i]) + omap_dmm->tcm[i]->deinit(omap_dmm->tcm[i]); + kfree(omap_dmm->tcm); + + kfree(omap_dmm->engines); + if (omap_dmm->refill_va) + dma_free_coherent(omap_dmm->dev, + REFILL_BUFFER_SIZE * omap_dmm->num_engines, + omap_dmm->refill_va, + omap_dmm->refill_pa); + if (omap_dmm->dummy_page) + __free_page(omap_dmm->dummy_page); + + vfree(omap_dmm->lut); + + if (omap_dmm->irq != -1) + free_irq(omap_dmm->irq, omap_dmm); + + iounmap(omap_dmm->base); + kfree(omap_dmm); + omap_dmm = NULL; + } + + return 0; +} + +static int omap_dmm_probe(struct platform_device *dev) +{ + int ret = -EFAULT, i; + struct tcm_area area = {0}; + u32 hwinfo, pat_geom, lut_table_size; + struct resource *mem; + + omap_dmm = kzalloc(sizeof(*omap_dmm), GFP_KERNEL); + if (!omap_dmm) { + dev_err(&dev->dev, "failed to allocate driver data section\n"); + goto fail; + } + + /* lookup hwmod data - base address and irq */ + mem = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&dev->dev, "failed to get base address resource\n"); + goto fail; + } + + omap_dmm->base = ioremap(mem->start, SZ_2K); + + if (!omap_dmm->base) { + dev_err(&dev->dev, "failed to get dmm base address\n"); + goto fail; + } + + omap_dmm->irq = platform_get_irq(dev, 0); + if (omap_dmm->irq < 0) { + dev_err(&dev->dev, "failed to get IRQ resource\n"); + goto fail; + } + + omap_dmm->dev = &dev->dev; + + hwinfo = readl(omap_dmm->base + DMM_PAT_HWINFO); + omap_dmm->num_engines = (hwinfo >> 24) & 0x1F; + omap_dmm->num_lut = (hwinfo >> 16) & 0x1F; + omap_dmm->container_width = 256; + omap_dmm->container_height = 128; + + /* read out actual LUT width and height */ + pat_geom = readl(omap_dmm->base + DMM_PAT_GEOMETRY); + omap_dmm->lut_width = ((pat_geom >> 16) & 0xF) << 5; + omap_dmm->lut_height = ((pat_geom >> 24) & 0xF) << 5; + + /* initialize DMM registers */ + writel(0x88888888, omap_dmm->base + DMM_PAT_VIEW__0); + writel(0x88888888, omap_dmm->base + DMM_PAT_VIEW__1); + writel(0x80808080, omap_dmm->base + DMM_PAT_VIEW_MAP__0); + writel(0x80000000, omap_dmm->base + DMM_PAT_VIEW_MAP_BASE); + writel(0x88888888, omap_dmm->base + DMM_TILER_OR__0); + writel(0x88888888, omap_dmm->base + DMM_TILER_OR__1); + + ret = request_irq(omap_dmm->irq, omap_dmm_irq_handler, IRQF_SHARED, + "omap_dmm_irq_handler", omap_dmm); + + if (ret) { + dev_err(&dev->dev, "couldn't register IRQ %d, error %d\n", + omap_dmm->irq, ret); + omap_dmm->irq = -1; + goto fail; + } + + /* Enable all interrupts for each refill engine except + * ERR_LUT_MISS (which is just advisory, and we don't care + * about because we want to be able to refill live scanout + * buffers for accelerated pan/scroll) and FILL_DSC which + * we just generally don't care about. + */ + writel(0x7e7e7e7e, omap_dmm->base + DMM_PAT_IRQENABLE_SET); + + lut_table_size = omap_dmm->lut_width * omap_dmm->lut_height * + omap_dmm->num_lut; + + omap_dmm->lut = vmalloc(lut_table_size * sizeof(*omap_dmm->lut)); + if (!omap_dmm->lut) { + dev_err(&dev->dev, "could not allocate lut table\n"); + ret = -ENOMEM; + goto fail; + } + + omap_dmm->dummy_page = alloc_page(GFP_KERNEL | __GFP_DMA32); + if (!omap_dmm->dummy_page) { + dev_err(&dev->dev, "could not allocate dummy page\n"); + ret = -ENOMEM; + goto fail; + } + + /* set dma mask for device */ + /* NOTE: this is a workaround for the hwmod not initializing properly */ + dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + omap_dmm->dummy_pa = page_to_phys(omap_dmm->dummy_page); + + /* alloc refill memory */ + omap_dmm->refill_va = dma_alloc_coherent(&dev->dev, + REFILL_BUFFER_SIZE * omap_dmm->num_engines, + &omap_dmm->refill_pa, GFP_KERNEL); + if (!omap_dmm->refill_va) { + dev_err(&dev->dev, "could not allocate refill memory\n"); + goto fail; + } + + /* alloc engines */ + omap_dmm->engines = kzalloc( + omap_dmm->num_engines * sizeof(struct refill_engine), + GFP_KERNEL); + if (!omap_dmm->engines) { + dev_err(&dev->dev, "could not allocate engines\n"); + ret = -ENOMEM; + goto fail; + } + + sema_init(&omap_dmm->engine_sem, omap_dmm->num_engines); + INIT_LIST_HEAD(&omap_dmm->idle_head); + for (i = 0; i < omap_dmm->num_engines; i++) { + omap_dmm->engines[i].id = i; + omap_dmm->engines[i].dmm = omap_dmm; + omap_dmm->engines[i].refill_va = omap_dmm->refill_va + + (REFILL_BUFFER_SIZE * i); + omap_dmm->engines[i].refill_pa = omap_dmm->refill_pa + + (REFILL_BUFFER_SIZE * i); + init_waitqueue_head(&omap_dmm->engines[i].wait_for_refill); + + list_add(&omap_dmm->engines[i].idle_node, &omap_dmm->idle_head); + } + + omap_dmm->tcm = kzalloc(omap_dmm->num_lut * sizeof(*omap_dmm->tcm), + GFP_KERNEL); + if (!omap_dmm->tcm) { + dev_err(&dev->dev, "failed to allocate lut ptrs\n"); + ret = -ENOMEM; + goto fail; + } + + /* init containers */ + for (i = 0; i < omap_dmm->num_lut; i++) { + omap_dmm->tcm[i] = sita_init(omap_dmm->container_width, + omap_dmm->container_height, + NULL); + + if (!omap_dmm->tcm[i]) { + dev_err(&dev->dev, "failed to allocate container\n"); + ret = -ENOMEM; + goto fail; + } + + omap_dmm->tcm[i]->lut_id = i; + } + + /* assign access mode containers to applicable tcm container */ + /* OMAP 4 has 1 container for all 4 views */ + containers[TILFMT_8BIT] = omap_dmm->tcm[0]; + containers[TILFMT_16BIT] = omap_dmm->tcm[0]; + containers[TILFMT_32BIT] = omap_dmm->tcm[0]; + containers[TILFMT_PAGE] = omap_dmm->tcm[0]; + + INIT_LIST_HEAD(&omap_dmm->alloc_head); + spin_lock_init(&omap_dmm->list_lock); + + area = (struct tcm_area) { + .is2d = true, + .tcm = NULL, + .p1.x = omap_dmm->container_width - 1, + .p1.y = omap_dmm->container_height - 1, + }; + + for (i = 0; i < lut_table_size; i++) + omap_dmm->lut[i] = omap_dmm->dummy_pa; + + /* initialize all LUTs to dummy page entries */ + for (i = 0; i < omap_dmm->num_lut; i++) { + area.tcm = omap_dmm->tcm[i]; + if (fill(&area, NULL, 0, 0, true)) + dev_err(omap_dmm->dev, "refill failed"); + } + + dev_info(omap_dmm->dev, "initialized all PAT entries\n"); + + return 0; + +fail: + omap_dmm_remove(dev); + return ret; +} + +/* + * debugfs support + */ + +#ifdef CONFIG_DEBUG_FS + +static const char *alphabet = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; +static const char *special = ".,:;'\"`~!^-+"; + +static void fill_map(char **map, int xdiv, int ydiv, struct tcm_area *a, + char c, bool ovw) +{ + int x, y; + for (y = a->p0.y / ydiv; y <= a->p1.y / ydiv; y++) + for (x = a->p0.x / xdiv; x <= a->p1.x / xdiv; x++) + if (map[y][x] == ' ' || ovw) + map[y][x] = c; +} + +static void fill_map_pt(char **map, int xdiv, int ydiv, struct tcm_pt *p, + char c) +{ + map[p->y / ydiv][p->x / xdiv] = c; +} + +static char read_map_pt(char **map, int xdiv, int ydiv, struct tcm_pt *p) +{ + return map[p->y / ydiv][p->x / xdiv]; +} + +static int map_width(int xdiv, int x0, int x1) +{ + return (x1 / xdiv) - (x0 / xdiv) + 1; +} + +static void text_map(char **map, int xdiv, char *nice, int yd, int x0, int x1) +{ + char *p = map[yd] + (x0 / xdiv); + int w = (map_width(xdiv, x0, x1) - strlen(nice)) / 2; + if (w >= 0) { + p += w; + while (*nice) + *p++ = *nice++; + } +} + +static void map_1d_info(char **map, int xdiv, int ydiv, char *nice, + struct tcm_area *a) +{ + sprintf(nice, "%dK", tcm_sizeof(*a) * 4); + if (a->p0.y + 1 < a->p1.y) { + text_map(map, xdiv, nice, (a->p0.y + a->p1.y) / 2 / ydiv, 0, + 256 - 1); + } else if (a->p0.y < a->p1.y) { + if (strlen(nice) < map_width(xdiv, a->p0.x, 256 - 1)) + text_map(map, xdiv, nice, a->p0.y / ydiv, + a->p0.x + xdiv, 256 - 1); + else if (strlen(nice) < map_width(xdiv, 0, a->p1.x)) + text_map(map, xdiv, nice, a->p1.y / ydiv, + 0, a->p1.y - xdiv); + } else if (strlen(nice) + 1 < map_width(xdiv, a->p0.x, a->p1.x)) { + text_map(map, xdiv, nice, a->p0.y / ydiv, a->p0.x, a->p1.x); + } +} + +static void map_2d_info(char **map, int xdiv, int ydiv, char *nice, + struct tcm_area *a) +{ + sprintf(nice, "(%d*%d)", tcm_awidth(*a), tcm_aheight(*a)); + if (strlen(nice) + 1 < map_width(xdiv, a->p0.x, a->p1.x)) + text_map(map, xdiv, nice, (a->p0.y + a->p1.y) / 2 / ydiv, + a->p0.x, a->p1.x); +} + +int tiler_map_show(struct seq_file *s, void *arg) +{ + int xdiv = 2, ydiv = 1; + char **map = NULL, *global_map; + struct tiler_block *block; + struct tcm_area a, p; + int i; + const char *m2d = alphabet; + const char *a2d = special; + const char *m2dp = m2d, *a2dp = a2d; + char nice[128]; + int h_adj; + int w_adj; + unsigned long flags; + + if (!omap_dmm) { + /* early return if dmm/tiler device is not initialized */ + return 0; + } + + h_adj = omap_dmm->lut_height / ydiv; + w_adj = omap_dmm->lut_width / xdiv; + + map = kzalloc(h_adj * sizeof(*map), GFP_KERNEL); + global_map = kzalloc((w_adj + 1) * h_adj, GFP_KERNEL); + + if (!map || !global_map) + goto error; + + memset(global_map, ' ', (w_adj + 1) * h_adj); + for (i = 0; i < omap_dmm->lut_height; i++) { + map[i] = global_map + i * (w_adj + 1); + map[i][w_adj] = 0; + } + spin_lock_irqsave(&omap_dmm->list_lock, flags); + + list_for_each_entry(block, &omap_dmm->alloc_head, alloc_node) { + if (block->fmt != TILFMT_PAGE) { + fill_map(map, xdiv, ydiv, &block->area, *m2dp, true); + if (!*++a2dp) + a2dp = a2d; + if (!*++m2dp) + m2dp = m2d; + map_2d_info(map, xdiv, ydiv, nice, &block->area); + } else { + bool start = read_map_pt(map, xdiv, ydiv, + &block->area.p0) + == ' '; + bool end = read_map_pt(map, xdiv, ydiv, &block->area.p1) + == ' '; + tcm_for_each_slice(a, block->area, p) + fill_map(map, xdiv, ydiv, &a, '=', true); + fill_map_pt(map, xdiv, ydiv, &block->area.p0, + start ? '<' : 'X'); + fill_map_pt(map, xdiv, ydiv, &block->area.p1, + end ? '>' : 'X'); + map_1d_info(map, xdiv, ydiv, nice, &block->area); + } + } + + spin_unlock_irqrestore(&omap_dmm->list_lock, flags); + + if (s) { + seq_printf(s, "BEGIN DMM TILER MAP\n"); + for (i = 0; i < 128; i++) + seq_printf(s, "%03d:%s\n", i, map[i]); + seq_printf(s, "END TILER MAP\n"); + } else { + dev_dbg(omap_dmm->dev, "BEGIN DMM TILER MAP\n"); + for (i = 0; i < 128; i++) + dev_dbg(omap_dmm->dev, "%03d:%s\n", i, map[i]); + dev_dbg(omap_dmm->dev, "END TILER MAP\n"); + } + +error: + kfree(map); + kfree(global_map); + + return 0; +} +#endif + +struct platform_driver omap_dmm_driver = { + .probe = omap_dmm_probe, + .remove = omap_dmm_remove, + .driver = { + .owner = THIS_MODULE, + .name = DMM_DRIVER_NAME, + }, +}; + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Andy Gross "); +MODULE_DESCRIPTION("OMAP DMM/Tiler Driver"); +MODULE_ALIAS("platform:" DMM_DRIVER_NAME); diff --git a/drivers/staging/omapdrm/omap_dmm_tiler.h b/drivers/staging/omapdrm/omap_dmm_tiler.h new file mode 100644 index 00000000..7b1052a3 --- /dev/null +++ b/drivers/staging/omapdrm/omap_dmm_tiler.h @@ -0,0 +1,134 @@ +/* + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Rob Clark + * Andy Gross + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef OMAP_DMM_TILER_H +#define OMAP_DMM_TILER_H + +#include +#include "omap_drv.h" +#include "tcm.h" + +enum tiler_fmt { + TILFMT_8BIT = 0, + TILFMT_16BIT, + TILFMT_32BIT, + TILFMT_PAGE, + TILFMT_NFORMATS +}; + +struct pat_area { + u32 x0:8; + u32 y0:8; + u32 x1:8; + u32 y1:8; +}; + +struct tiler_block { + struct list_head alloc_node; /* node for global block list */ + struct tcm_area area; /* area */ + enum tiler_fmt fmt; /* format */ +}; + +/* bits representing the same slot in DMM-TILER hw-block */ +#define SLOT_WIDTH_BITS 6 +#define SLOT_HEIGHT_BITS 6 + +/* bits reserved to describe coordinates in DMM-TILER hw-block */ +#define CONT_WIDTH_BITS 14 +#define CONT_HEIGHT_BITS 13 + +/* calculated constants */ +#define TILER_PAGE (1 << (SLOT_WIDTH_BITS + SLOT_HEIGHT_BITS)) +#define TILER_WIDTH (1 << (CONT_WIDTH_BITS - SLOT_WIDTH_BITS)) +#define TILER_HEIGHT (1 << (CONT_HEIGHT_BITS - SLOT_HEIGHT_BITS)) + +/* tiler space addressing bitfields */ +#define MASK_XY_FLIP (1 << 31) +#define MASK_Y_INVERT (1 << 30) +#define MASK_X_INVERT (1 << 29) +#define SHIFT_ACC_MODE 27 +#define MASK_ACC_MODE 3 + +#define MASK(bits) ((1 << (bits)) - 1) + +#define TILVIEW_8BIT 0x60000000u +#define TILVIEW_16BIT (TILVIEW_8BIT + VIEW_SIZE) +#define TILVIEW_32BIT (TILVIEW_16BIT + VIEW_SIZE) +#define TILVIEW_PAGE (TILVIEW_32BIT + VIEW_SIZE) +#define TILVIEW_END (TILVIEW_PAGE + VIEW_SIZE) + +/* create tsptr by adding view orientation and access mode */ +#define TIL_ADDR(x, orient, a)\ + ((u32) (x) | (orient) | ((a) << SHIFT_ACC_MODE)) + +#ifdef CONFIG_DEBUG_FS +int tiler_map_show(struct seq_file *s, void *arg); +#endif + +/* pin/unpin */ +int tiler_pin(struct tiler_block *block, struct page **pages, + uint32_t npages, uint32_t roll, bool wait); +int tiler_unpin(struct tiler_block *block); + +/* reserve/release */ +struct tiler_block *tiler_reserve_2d(enum tiler_fmt fmt, uint16_t w, uint16_t h, + uint16_t align); +struct tiler_block *tiler_reserve_1d(size_t size); +int tiler_release(struct tiler_block *block); + +/* utilities */ +dma_addr_t tiler_ssptr(struct tiler_block *block); +uint32_t tiler_stride(enum tiler_fmt fmt); +size_t tiler_size(enum tiler_fmt fmt, uint16_t w, uint16_t h); +size_t tiler_vsize(enum tiler_fmt fmt, uint16_t w, uint16_t h); +void tiler_align(enum tiler_fmt fmt, uint16_t *w, uint16_t *h); +bool dmm_is_initialized(void); + +extern struct platform_driver omap_dmm_driver; + +/* GEM bo flags -> tiler fmt */ +static inline enum tiler_fmt gem2fmt(uint32_t flags) +{ + switch (flags & OMAP_BO_TILED) { + case OMAP_BO_TILED_8: + return TILFMT_8BIT; + case OMAP_BO_TILED_16: + return TILFMT_16BIT; + case OMAP_BO_TILED_32: + return TILFMT_32BIT; + default: + return TILFMT_PAGE; + } +} + +static inline bool validfmt(enum tiler_fmt fmt) +{ + switch (fmt) { + case TILFMT_8BIT: + case TILFMT_16BIT: + case TILFMT_32BIT: + case TILFMT_PAGE: + return true; + default: + return false; + } +} + +static inline int dmm_is_available(void) +{ + return cpu_is_omap44xx(); +} + +#endif diff --git a/drivers/staging/omapdrm/omap_drm.h b/drivers/staging/omapdrm/omap_drm.h new file mode 100644 index 00000000..f0ac34a8 --- /dev/null +++ b/drivers/staging/omapdrm/omap_drm.h @@ -0,0 +1,123 @@ +/* + * include/drm/omap_drm.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __OMAP_DRM_H__ +#define __OMAP_DRM_H__ + +#include + +/* Please note that modifications to all structs defined here are + * subject to backwards-compatibility constraints. + */ + +#define OMAP_PARAM_CHIPSET_ID 1 /* ie. 0x3430, 0x4430, etc */ + +struct drm_omap_param { + uint64_t param; /* in */ + uint64_t value; /* in (set_param), out (get_param) */ +}; + +#define OMAP_BO_SCANOUT 0x00000001 /* scanout capable (phys contiguous) */ +#define OMAP_BO_CACHE_MASK 0x00000006 /* cache type mask, see cache modes */ +#define OMAP_BO_TILED_MASK 0x00000f00 /* tiled mapping mask, see tiled modes */ + +/* cache modes */ +#define OMAP_BO_CACHED 0x00000000 /* default */ +#define OMAP_BO_WC 0x00000002 /* write-combine */ +#define OMAP_BO_UNCACHED 0x00000004 /* strongly-ordered (uncached) */ + +/* tiled modes */ +#define OMAP_BO_TILED_8 0x00000100 +#define OMAP_BO_TILED_16 0x00000200 +#define OMAP_BO_TILED_32 0x00000300 +#define OMAP_BO_TILED (OMAP_BO_TILED_8 | OMAP_BO_TILED_16 | OMAP_BO_TILED_32) + +union omap_gem_size { + uint32_t bytes; /* (for non-tiled formats) */ + struct { + uint16_t width; + uint16_t height; + } tiled; /* (for tiled formats) */ +}; + +struct drm_omap_gem_new { + union omap_gem_size size; /* in */ + uint32_t flags; /* in */ + uint32_t handle; /* out */ + uint32_t __pad; +}; + +/* mask of operations: */ +enum omap_gem_op { + OMAP_GEM_READ = 0x01, + OMAP_GEM_WRITE = 0x02, +}; + +struct drm_omap_gem_cpu_prep { + uint32_t handle; /* buffer handle (in) */ + uint32_t op; /* mask of omap_gem_op (in) */ +}; + +struct drm_omap_gem_cpu_fini { + uint32_t handle; /* buffer handle (in) */ + uint32_t op; /* mask of omap_gem_op (in) */ + /* TODO maybe here we pass down info about what regions are touched + * by sw so we can be clever about cache ops? For now a placeholder, + * set to zero and we just do full buffer flush.. + */ + uint32_t nregions; + uint32_t __pad; +}; + +struct drm_omap_gem_info { + uint32_t handle; /* buffer handle (in) */ + uint32_t pad; + uint64_t offset; /* mmap offset (out) */ + /* note: in case of tiled buffers, the user virtual size can be + * different from the physical size (ie. how many pages are needed + * to back the object) which is returned in DRM_IOCTL_GEM_OPEN.. + * This size here is the one that should be used if you want to + * mmap() the buffer: + */ + uint32_t size; /* virtual size for mmap'ing (out) */ + uint32_t __pad; +}; + +#define DRM_OMAP_GET_PARAM 0x00 +#define DRM_OMAP_SET_PARAM 0x01 +/* placeholder for plugin-api +#define DRM_OMAP_GET_BASE 0x02 +*/ +#define DRM_OMAP_GEM_NEW 0x03 +#define DRM_OMAP_GEM_CPU_PREP 0x04 +#define DRM_OMAP_GEM_CPU_FINI 0x05 +#define DRM_OMAP_GEM_INFO 0x06 +#define DRM_OMAP_NUM_IOCTLS 0x07 + +#define DRM_IOCTL_OMAP_GET_PARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GET_PARAM, struct drm_omap_param) +#define DRM_IOCTL_OMAP_SET_PARAM DRM_IOW (DRM_COMMAND_BASE + DRM_OMAP_SET_PARAM, struct drm_omap_param) +/* placeholder for plugin-api +#define DRM_IOCTL_OMAP_GET_BASE DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GET_BASE, struct drm_omap_get_base) +*/ +#define DRM_IOCTL_OMAP_GEM_NEW DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GEM_NEW, struct drm_omap_gem_new) +#define DRM_IOCTL_OMAP_GEM_CPU_PREP DRM_IOW (DRM_COMMAND_BASE + DRM_OMAP_GEM_CPU_PREP, struct drm_omap_gem_cpu_prep) +#define DRM_IOCTL_OMAP_GEM_CPU_FINI DRM_IOW (DRM_COMMAND_BASE + DRM_OMAP_GEM_CPU_FINI, struct drm_omap_gem_cpu_fini) +#define DRM_IOCTL_OMAP_GEM_INFO DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GEM_INFO, struct drm_omap_gem_info) + +#endif /* __OMAP_DRM_H__ */ diff --git a/drivers/staging/omapdrm/omap_drv.c b/drivers/staging/omapdrm/omap_drv.c new file mode 100644 index 00000000..620b8d54 --- /dev/null +++ b/drivers/staging/omapdrm/omap_drv.c @@ -0,0 +1,853 @@ +/* + * drivers/staging/omapdrm/omap_drv.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "omap_drv.h" + +#include "drm_crtc_helper.h" +#include "drm_fb_helper.h" +#include "omap_dmm_tiler.h" + +#define DRIVER_NAME MODULE_NAME +#define DRIVER_DESC "OMAP DRM" +#define DRIVER_DATE "20110917" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +struct drm_device *drm_device; + +static int num_crtc = CONFIG_DRM_OMAP_NUM_CRTCS; + +MODULE_PARM_DESC(num_crtc, "Number of overlays to use as CRTCs"); +module_param(num_crtc, int, 0600); + +/* + * mode config funcs + */ + +/* Notes about mapping DSS and DRM entities: + * CRTC: overlay + * encoder: manager.. with some extension to allow one primary CRTC + * and zero or more video CRTC's to be mapped to one encoder? + * connector: dssdev.. manager can be attached/detached from different + * devices + */ + +static void omap_fb_output_poll_changed(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + DBG("dev=%p", dev); + if (priv->fbdev) { + drm_fb_helper_hotplug_event(priv->fbdev); + } +} + +static struct drm_mode_config_funcs omap_mode_config_funcs = { + .fb_create = omap_framebuffer_create, + .output_poll_changed = omap_fb_output_poll_changed, +}; + +static int get_connector_type(struct omap_dss_device *dssdev) +{ + switch (dssdev->type) { + case OMAP_DISPLAY_TYPE_HDMI: + return DRM_MODE_CONNECTOR_HDMIA; + case OMAP_DISPLAY_TYPE_DPI: + if (!strcmp(dssdev->name, "dvi")) + return DRM_MODE_CONNECTOR_DVID; + /* fallthrough */ + default: + return DRM_MODE_CONNECTOR_Unknown; + } +} + +#if 0 /* enable when dss2 supports hotplug */ +static int omap_drm_notifier(struct notifier_block *nb, + unsigned long evt, void *arg) +{ + switch (evt) { + case OMAP_DSS_SIZE_CHANGE: + case OMAP_DSS_HOTPLUG_CONNECT: + case OMAP_DSS_HOTPLUG_DISCONNECT: { + struct drm_device *dev = drm_device; + DBG("hotplug event: evt=%d, dev=%p", evt, dev); + if (dev) { + drm_sysfs_hotplug_event(dev); + } + return NOTIFY_OK; + } + default: /* don't care about other events for now */ + return NOTIFY_DONE; + } +} +#endif + +static void dump_video_chains(void) +{ + int i; + + DBG("dumping video chains: "); + for (i = 0; i < omap_dss_get_num_overlays(); i++) { + struct omap_overlay *ovl = omap_dss_get_overlay(i); + struct omap_overlay_manager *mgr = ovl->manager; + struct omap_dss_device *dssdev = mgr ? mgr->device : NULL; + if (dssdev) { + DBG("%d: %s -> %s -> %s", i, ovl->name, mgr->name, + dssdev->name); + } else if (mgr) { + DBG("%d: %s -> %s", i, ovl->name, mgr->name); + } else { + DBG("%d: %s", i, ovl->name); + } + } +} + +/* create encoders for each manager */ +static int create_encoder(struct drm_device *dev, + struct omap_overlay_manager *mgr) +{ + struct omap_drm_private *priv = dev->dev_private; + struct drm_encoder *encoder = omap_encoder_init(dev, mgr); + + if (!encoder) { + dev_err(dev->dev, "could not create encoder: %s\n", + mgr->name); + return -ENOMEM; + } + + BUG_ON(priv->num_encoders >= ARRAY_SIZE(priv->encoders)); + + priv->encoders[priv->num_encoders++] = encoder; + + return 0; +} + +/* create connectors for each display device */ +static int create_connector(struct drm_device *dev, + struct omap_dss_device *dssdev) +{ + struct omap_drm_private *priv = dev->dev_private; + static struct notifier_block *notifier; + struct drm_connector *connector; + int j; + + if (!dssdev->driver) { + dev_warn(dev->dev, "%s has no driver.. skipping it\n", + dssdev->name); + return 0; + } + + if (!(dssdev->driver->get_timings || + dssdev->driver->read_edid)) { + dev_warn(dev->dev, "%s driver does not support " + "get_timings or read_edid.. skipping it!\n", + dssdev->name); + return 0; + } + + connector = omap_connector_init(dev, + get_connector_type(dssdev), dssdev); + + if (!connector) { + dev_err(dev->dev, "could not create connector: %s\n", + dssdev->name); + return -ENOMEM; + } + + BUG_ON(priv->num_connectors >= ARRAY_SIZE(priv->connectors)); + + priv->connectors[priv->num_connectors++] = connector; + +#if 0 /* enable when dss2 supports hotplug */ + notifier = kzalloc(sizeof(struct notifier_block), GFP_KERNEL); + notifier->notifier_call = omap_drm_notifier; + omap_dss_add_notify(dssdev, notifier); +#else + notifier = NULL; +#endif + + for (j = 0; j < priv->num_encoders; j++) { + struct omap_overlay_manager *mgr = + omap_encoder_get_manager(priv->encoders[j]); + if (mgr->device == dssdev) { + drm_mode_connector_attach_encoder(connector, + priv->encoders[j]); + } + } + + return 0; +} + +/* create up to max_overlays CRTCs mapping to overlays.. by default, + * connect the overlays to different managers/encoders, giving priority + * to encoders connected to connectors with a detected connection + */ +static int create_crtc(struct drm_device *dev, struct omap_overlay *ovl, + int *j, unsigned int connected_connectors) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_overlay_manager *mgr = NULL; + struct drm_crtc *crtc; + + /* find next best connector, ones with detected connection first + */ + while (*j < priv->num_connectors && !mgr) { + if (connected_connectors & (1 << *j)) { + struct drm_encoder *encoder = + omap_connector_attached_encoder( + priv->connectors[*j]); + if (encoder) { + mgr = omap_encoder_get_manager(encoder); + } + } + (*j)++; + } + + /* if we couldn't find another connected connector, lets start + * looking at the unconnected connectors: + * + * note: it might not be immediately apparent, but thanks to + * the !mgr check in both this loop and the one above, the only + * way to enter this loop is with *j == priv->num_connectors, + * so idx can never go negative. + */ + while (*j < 2 * priv->num_connectors && !mgr) { + int idx = *j - priv->num_connectors; + if (!(connected_connectors & (1 << idx))) { + struct drm_encoder *encoder = + omap_connector_attached_encoder( + priv->connectors[idx]); + if (encoder) { + mgr = omap_encoder_get_manager(encoder); + } + } + (*j)++; + } + + crtc = omap_crtc_init(dev, ovl, priv->num_crtcs); + + if (!crtc) { + dev_err(dev->dev, "could not create CRTC: %s\n", + ovl->name); + return -ENOMEM; + } + + BUG_ON(priv->num_crtcs >= ARRAY_SIZE(priv->crtcs)); + + priv->crtcs[priv->num_crtcs++] = crtc; + + return 0; +} + +static int create_plane(struct drm_device *dev, struct omap_overlay *ovl, + unsigned int possible_crtcs) +{ + struct omap_drm_private *priv = dev->dev_private; + struct drm_plane *plane = + omap_plane_init(dev, ovl, possible_crtcs, false); + + if (!plane) { + dev_err(dev->dev, "could not create plane: %s\n", + ovl->name); + return -ENOMEM; + } + + BUG_ON(priv->num_planes >= ARRAY_SIZE(priv->planes)); + + priv->planes[priv->num_planes++] = plane; + + return 0; +} + +static int match_dev_name(struct omap_dss_device *dssdev, void *data) +{ + return !strcmp(dssdev->name, data); +} + +static unsigned int detect_connectors(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + unsigned int connected_connectors = 0; + int i; + + for (i = 0; i < priv->num_connectors; i++) { + struct drm_connector *connector = priv->connectors[i]; + if (omap_connector_detect(connector, true) == + connector_status_connected) { + connected_connectors |= (1 << i); + } + } + + return connected_connectors; +} + +static int omap_modeset_init(struct drm_device *dev) +{ + const struct omap_drm_platform_data *pdata = dev->dev->platform_data; + struct omap_kms_platform_data *kms_pdata = NULL; + struct omap_drm_private *priv = dev->dev_private; + struct omap_dss_device *dssdev = NULL; + int i, j; + unsigned int connected_connectors = 0; + + drm_mode_config_init(dev); + + if (pdata && pdata->kms_pdata) { + kms_pdata = pdata->kms_pdata; + + /* if platform data is provided by the board file, use it to + * control which overlays, managers, and devices we own. + */ + for (i = 0; i < kms_pdata->mgr_cnt; i++) { + struct omap_overlay_manager *mgr = + omap_dss_get_overlay_manager( + kms_pdata->mgr_ids[i]); + create_encoder(dev, mgr); + } + + for (i = 0; i < kms_pdata->dev_cnt; i++) { + struct omap_dss_device *dssdev = + omap_dss_find_device( + (void *)kms_pdata->dev_names[i], + match_dev_name); + if (!dssdev) { + dev_warn(dev->dev, "no such dssdev: %s\n", + kms_pdata->dev_names[i]); + continue; + } + create_connector(dev, dssdev); + } + + connected_connectors = detect_connectors(dev); + + j = 0; + for (i = 0; i < kms_pdata->ovl_cnt; i++) { + struct omap_overlay *ovl = + omap_dss_get_overlay(kms_pdata->ovl_ids[i]); + create_crtc(dev, ovl, &j, connected_connectors); + } + + for (i = 0; i < kms_pdata->pln_cnt; i++) { + struct omap_overlay *ovl = + omap_dss_get_overlay(kms_pdata->pln_ids[i]); + create_plane(dev, ovl, (1 << priv->num_crtcs) - 1); + } + } else { + /* otherwise just grab up to CONFIG_DRM_OMAP_NUM_CRTCS and try + * to make educated guesses about everything else + */ + int max_overlays = min(omap_dss_get_num_overlays(), num_crtc); + + for (i = 0; i < omap_dss_get_num_overlay_managers(); i++) { + create_encoder(dev, omap_dss_get_overlay_manager(i)); + } + + for_each_dss_dev(dssdev) { + create_connector(dev, dssdev); + } + + connected_connectors = detect_connectors(dev); + + j = 0; + for (i = 0; i < max_overlays; i++) { + create_crtc(dev, omap_dss_get_overlay(i), + &j, connected_connectors); + } + + /* use any remaining overlays as drm planes */ + for (; i < omap_dss_get_num_overlays(); i++) { + struct omap_overlay *ovl = omap_dss_get_overlay(i); + create_plane(dev, ovl, (1 << priv->num_crtcs) - 1); + } + } + + /* for now keep the mapping of CRTCs and encoders static.. */ + for (i = 0; i < priv->num_encoders; i++) { + struct drm_encoder *encoder = priv->encoders[i]; + struct omap_overlay_manager *mgr = + omap_encoder_get_manager(encoder); + + encoder->possible_crtcs = (1 << priv->num_crtcs) - 1; + + DBG("%s: possible_crtcs=%08x", mgr->name, + encoder->possible_crtcs); + } + + dump_video_chains(); + + dev->mode_config.min_width = 32; + dev->mode_config.min_height = 32; + + /* note: eventually will need some cpu_is_omapXYZ() type stuff here + * to fill in these limits properly on different OMAP generations.. + */ + dev->mode_config.max_width = 2048; + dev->mode_config.max_height = 2048; + + dev->mode_config.funcs = &omap_mode_config_funcs; + + return 0; +} + +static void omap_modeset_free(struct drm_device *dev) +{ + drm_mode_config_cleanup(dev); +} + +/* + * drm ioctl funcs + */ + + +static int ioctl_get_param(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_param *args = data; + + DBG("%p: param=%llu", dev, args->param); + + switch (args->param) { + case OMAP_PARAM_CHIPSET_ID: + args->value = GET_OMAP_TYPE; + break; + default: + DBG("unknown parameter %lld", args->param); + return -EINVAL; + } + + return 0; +} + +static int ioctl_set_param(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_param *args = data; + + switch (args->param) { + default: + DBG("unknown parameter %lld", args->param); + return -EINVAL; + } + + return 0; +} + +static int ioctl_gem_new(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_new *args = data; + DBG("%p:%p: size=0x%08x, flags=%08x", dev, file_priv, + args->size.bytes, args->flags); + return omap_gem_new_handle(dev, file_priv, args->size, + args->flags, &args->handle); +} + +static int ioctl_gem_cpu_prep(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_cpu_prep *args = data; + struct drm_gem_object *obj; + int ret; + + VERB("%p:%p: handle=%d, op=%x", dev, file_priv, args->handle, args->op); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) { + return -ENOENT; + } + + ret = omap_gem_op_sync(obj, args->op); + + if (!ret) { + ret = omap_gem_op_start(obj, args->op); + } + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int ioctl_gem_cpu_fini(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_cpu_fini *args = data; + struct drm_gem_object *obj; + int ret; + + VERB("%p:%p: handle=%d", dev, file_priv, args->handle); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) { + return -ENOENT; + } + + /* XXX flushy, flushy */ + ret = 0; + + if (!ret) { + ret = omap_gem_op_finish(obj, args->op); + } + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int ioctl_gem_info(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_info *args = data; + struct drm_gem_object *obj; + int ret = 0; + + DBG("%p:%p: handle=%d", dev, file_priv, args->handle); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) { + return -ENOENT; + } + + args->size = omap_gem_mmap_size(obj); + args->offset = omap_gem_mmap_offset(obj); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = { + DRM_IOCTL_DEF_DRV(OMAP_GET_PARAM, ioctl_get_param, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_SET_PARAM, ioctl_set_param, DRM_UNLOCKED|DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), + DRM_IOCTL_DEF_DRV(OMAP_GEM_NEW, ioctl_gem_new, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_PREP, ioctl_gem_cpu_prep, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_FINI, ioctl_gem_cpu_fini, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_INFO, ioctl_gem_info, DRM_UNLOCKED|DRM_AUTH), +}; + +/* + * drm driver funcs + */ + +/** + * load - setup chip and create an initial config + * @dev: DRM device + * @flags: startup flags + * + * The driver load routine has to do several things: + * - initialize the memory manager + * - allocate initial config memory + * - setup the DRM framebuffer with the allocated memory + */ +static int dev_load(struct drm_device *dev, unsigned long flags) +{ + struct omap_drm_private *priv; + int ret; + + DBG("load: dev=%p", dev); + + drm_device = dev; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(dev->dev, "could not allocate priv\n"); + return -ENOMEM; + } + + dev->dev_private = priv; + + priv->wq = alloc_workqueue("omapdrm", + WQ_UNBOUND | WQ_NON_REENTRANT, 1); + + INIT_LIST_HEAD(&priv->obj_list); + + omap_gem_init(dev); + + ret = omap_modeset_init(dev); + if (ret) { + dev_err(dev->dev, "omap_modeset_init failed: ret=%d\n", ret); + dev->dev_private = NULL; + kfree(priv); + return ret; + } + + priv->fbdev = omap_fbdev_init(dev); + if (!priv->fbdev) { + dev_warn(dev->dev, "omap_fbdev_init failed\n"); + /* well, limp along without an fbdev.. maybe X11 will work? */ + } + + drm_kms_helper_poll_init(dev); + + ret = drm_vblank_init(dev, priv->num_crtcs); + if (ret) { + dev_warn(dev->dev, "could not init vblank\n"); + } + + return 0; +} + +static int dev_unload(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + + DBG("unload: dev=%p", dev); + + drm_vblank_cleanup(dev); + drm_kms_helper_poll_fini(dev); + + omap_fbdev_free(dev); + omap_modeset_free(dev); + omap_gem_deinit(dev); + + flush_workqueue(priv->wq); + destroy_workqueue(priv->wq); + + kfree(dev->dev_private); + dev->dev_private = NULL; + + return 0; +} + +static int dev_open(struct drm_device *dev, struct drm_file *file) +{ + file->driver_priv = NULL; + + DBG("open: dev=%p, file=%p", dev, file); + + return 0; +} + +static int dev_firstopen(struct drm_device *dev) +{ + DBG("firstopen: dev=%p", dev); + return 0; +} + +/** + * lastclose - clean up after all DRM clients have exited + * @dev: DRM device + * + * Take care of cleaning up after all DRM clients have exited. In the + * mode setting case, we want to restore the kernel's initial mode (just + * in case the last client left us in a bad state). + */ +static void dev_lastclose(struct drm_device *dev) +{ + /* we don't support vga-switcheroo.. so just make sure the fbdev + * mode is active + */ + struct omap_drm_private *priv = dev->dev_private; + int ret; + + DBG("lastclose: dev=%p", dev); + + ret = drm_fb_helper_restore_fbdev_mode(priv->fbdev); + if (ret) + DBG("failed to restore crtc mode"); +} + +static void dev_preclose(struct drm_device *dev, struct drm_file *file) +{ + DBG("preclose: dev=%p", dev); +} + +static void dev_postclose(struct drm_device *dev, struct drm_file *file) +{ + DBG("postclose: dev=%p, file=%p", dev, file); +} + +/** + * enable_vblank - enable vblank interrupt events + * @dev: DRM device + * @crtc: which irq to enable + * + * Enable vblank interrupts for @crtc. If the device doesn't have + * a hardware vblank counter, this routine should be a no-op, since + * interrupts will have to stay on to keep the count accurate. + * + * RETURNS + * Zero on success, appropriate errno if the given @crtc's vblank + * interrupt cannot be enabled. + */ +static int dev_enable_vblank(struct drm_device *dev, int crtc) +{ + DBG("enable_vblank: dev=%p, crtc=%d", dev, crtc); + return 0; +} + +/** + * disable_vblank - disable vblank interrupt events + * @dev: DRM device + * @crtc: which irq to enable + * + * Disable vblank interrupts for @crtc. If the device doesn't have + * a hardware vblank counter, this routine should be a no-op, since + * interrupts will have to stay on to keep the count accurate. + */ +static void dev_disable_vblank(struct drm_device *dev, int crtc) +{ + DBG("disable_vblank: dev=%p, crtc=%d", dev, crtc); +} + +static irqreturn_t dev_irq_handler(DRM_IRQ_ARGS) +{ + return IRQ_HANDLED; +} + +static void dev_irq_preinstall(struct drm_device *dev) +{ + DBG("irq_preinstall: dev=%p", dev); +} + +static int dev_irq_postinstall(struct drm_device *dev) +{ + DBG("irq_postinstall: dev=%p", dev); + return 0; +} + +static void dev_irq_uninstall(struct drm_device *dev) +{ + DBG("irq_uninstall: dev=%p", dev); +} + +static struct vm_operations_struct omap_gem_vm_ops = { + .fault = omap_gem_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static const struct file_operations omapdriver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .unlocked_ioctl = drm_ioctl, + .release = drm_release, + .mmap = omap_gem_mmap, + .poll = drm_poll, + .fasync = drm_fasync, + .read = drm_read, + .llseek = noop_llseek, +}; + +static struct drm_driver omap_drm_driver = { + .driver_features = + DRIVER_HAVE_IRQ | DRIVER_MODESET | DRIVER_GEM, + .load = dev_load, + .unload = dev_unload, + .open = dev_open, + .firstopen = dev_firstopen, + .lastclose = dev_lastclose, + .preclose = dev_preclose, + .postclose = dev_postclose, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = dev_enable_vblank, + .disable_vblank = dev_disable_vblank, + .irq_preinstall = dev_irq_preinstall, + .irq_postinstall = dev_irq_postinstall, + .irq_uninstall = dev_irq_uninstall, + .irq_handler = dev_irq_handler, + .reclaim_buffers = drm_core_reclaim_buffers, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = omap_debugfs_init, + .debugfs_cleanup = omap_debugfs_cleanup, +#endif + .gem_init_object = omap_gem_init_object, + .gem_free_object = omap_gem_free_object, + .gem_vm_ops = &omap_gem_vm_ops, + .dumb_create = omap_gem_dumb_create, + .dumb_map_offset = omap_gem_dumb_map_offset, + .dumb_destroy = omap_gem_dumb_destroy, + .ioctls = ioctls, + .num_ioctls = DRM_OMAP_NUM_IOCTLS, + .fops = &omapdriver_fops, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, +}; + +static int pdev_suspend(struct platform_device *pDevice, pm_message_t state) +{ + DBG(""); + return 0; +} + +static int pdev_resume(struct platform_device *device) +{ + DBG(""); + return 0; +} + +static void pdev_shutdown(struct platform_device *device) +{ + DBG(""); +} + +static int pdev_probe(struct platform_device *device) +{ + DBG("%s", device->name); + return drm_platform_init(&omap_drm_driver, device); +} + +static int pdev_remove(struct platform_device *device) +{ + DBG(""); + drm_platform_exit(&omap_drm_driver, device); + + platform_driver_unregister(&omap_dmm_driver); + return 0; +} + +struct platform_driver pdev = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = pdev_probe, + .remove = pdev_remove, + .suspend = pdev_suspend, + .resume = pdev_resume, + .shutdown = pdev_shutdown, +}; + +static int __init omap_drm_init(void) +{ + DBG("init"); + if (platform_driver_register(&omap_dmm_driver)) { + /* we can continue on without DMM.. so not fatal */ + dev_err(NULL, "DMM registration failed\n"); + } + return platform_driver_register(&pdev); +} + +static void __exit omap_drm_fini(void) +{ + DBG("fini"); + platform_driver_unregister(&pdev); +} + +/* need late_initcall() so we load after dss_driver's are loaded */ +late_initcall(omap_drm_init); +module_exit(omap_drm_fini); + +MODULE_AUTHOR("Rob Clark "); +MODULE_DESCRIPTION("OMAP DRM Display Driver"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/omapdrm/omap_drv.h b/drivers/staging/omapdrm/omap_drv.h new file mode 100644 index 00000000..b7e0f077 --- /dev/null +++ b/drivers/staging/omapdrm/omap_drv.h @@ -0,0 +1,190 @@ +/* + * drivers/staging/omapdrm/omap_drv.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __OMAP_DRV_H__ +#define __OMAP_DRV_H__ + +#include